cpufreq: enable scpi cpufreq

PD#138714: add scpi_cpufreq

Change-Id: Iaf90f7573657593f21200fed48209a2758fa189a
Signed-off-by: Jianxin Pan <jianxin.pan@amlogic.com>
This commit is contained in:
Jianxin Pan
2017-02-15 14:26:11 +08:00
parent 3427c5f8a1
commit 2a780f9beb
10 changed files with 419 additions and 11 deletions

View File

@@ -13481,3 +13481,7 @@ F: include/linux/amlogic/usb-gxbbtv.h
F: include/linux/amlogic/usb-gxl.h
F: include/linux/amlogic/usb-gxbb.h
F: include/linux/amlogic/usbtype.h
AMLOGIC scpi cpufreq
M: jianxin.pan <jianxin.pan@amlogic.com>
F: drivers/amlogic/clk/clk-scpi.c

View File

@@ -45,6 +45,8 @@
compatible = "arm,cortex-a53","arm,armv8";
reg = <0x0 0x0>;
enable-method = "psci";
clocks = <&scpi_dvfs 0>;
clock-names = "cpu-cluster.0";
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
};
@@ -53,6 +55,8 @@
compatible = "arm,cortex-a53","arm,armv8";
reg = <0x0 0x1>;
enable-method = "psci";
clocks = <&scpi_dvfs 0>;
clock-names = "cpu-cluster.0";
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
};
CPU2:cpu@2 {
@@ -60,6 +64,8 @@
compatible = "arm,cortex-a53","arm,armv8";
reg = <0x0 0x2>;
enable-method = "psci";
clocks = <&scpi_dvfs 0>;
clock-names = "cpu-cluster.0";
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
};
@@ -68,6 +74,8 @@
compatible = "arm,cortex-a53","arm,armv8";
reg = <0x0 0x3>;
enable-method = "psci";
clocks = <&scpi_dvfs 0>;
clock-names = "cpu-cluster.0";
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
};
@@ -174,13 +182,25 @@
compatible = "amlogic, meson_mhu";
reg = <0x0 0xc883c400 0x0 0x4c>, /* MHU registers */
<0x0 0xc8013000 0x0 0x800>; /* Payload area */
interrupts = <0 209 8>, /* low priority interrupt */
<0 210 8>; /* high priority interrupt */
interrupts = <0 209 1>, /* low priority interrupt */
<0 210 1>; /* high priority interrupt */
#mbox-cells = <1>;
mbox-names = "cpu_to_scp_low", "cpu_to_scp_high";
mboxes = <&mailbox 0 &mailbox 1>;
};
scpi_clocks {
compatible = "arm, scpi-clks";
scpi_dvfs: scpi_clocks@0 {
compatible = "arm, scpi-clk-indexed";
#clock-cells = <1>;
clock-indices = <0>;
clock-output-names = "vcpu";
};
};
xtal: xtal-clk {
compatible = "fixed-clock";
clock-frequency = <24000000>;
@@ -358,7 +378,6 @@
"jtag_clk_0",
"jtag_tms_0";
function = "jtag";
bias-pull-down;
};
};

View File

@@ -60,6 +60,8 @@
compatible = "arm,cortex-a53","arm,armv8";
reg = <0x0 0x0>;
enable-method = "psci";
clocks = <&scpi_dvfs 0>;
clock-names = "cpu-cluster.0";
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
};
@@ -68,6 +70,8 @@
compatible = "arm,cortex-a53","arm,armv8";
reg = <0x0 0x1>;
enable-method = "psci";
clocks = <&scpi_dvfs 0>;
clock-names = "cpu-cluster.0";
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
};
CPU2:cpu@2 {
@@ -75,6 +79,8 @@
compatible = "arm,cortex-a53","arm,armv8";
reg = <0x0 0x2>;
enable-method = "psci";
clocks = <&scpi_dvfs 0>;
clock-names = "cpu-cluster.0";
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
};
@@ -83,6 +89,8 @@
compatible = "arm,cortex-a53","arm,armv8";
reg = <0x0 0x3>;
enable-method = "psci";
clocks = <&scpi_dvfs 0>;
clock-names = "cpu-cluster.0";
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
};
@@ -91,6 +99,8 @@
compatible = "arm,cortex-a53","arm,armv8";
reg = <0x0 0x100>;
enable-method = "psci";
clocks = <&scpi_dvfs 1>;
clock-names = "cpu-cluster.1";
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
};
@@ -99,6 +109,8 @@
compatible = "arm,cortex-a53","arm,armv8";
reg = <0x0 0x101>;
enable-method = "psci";
clocks = <&scpi_dvfs 1>;
clock-names = "cpu-cluster.1";
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
};
CPU6:cpu@102 {
@@ -106,6 +118,8 @@
compatible = "arm,cortex-a53","arm,armv8";
reg = <0x0 0x102>;
enable-method = "psci";
clocks = <&scpi_dvfs 1>;
clock-names = "cpu-cluster.1";
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
};
@@ -114,6 +128,8 @@
compatible = "arm,cortex-a53","arm,armv8";
reg = <0x0 0x103>;
enable-method = "psci";
clocks = <&scpi_dvfs 1>;
clock-names = "cpu-cluster.1";
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
};
@@ -226,6 +242,17 @@
mboxes = <&mailbox 0 &mailbox 1>;
};
scpi_clocks {
compatible = "arm, scpi-clks";
scpi_dvfs: scpi_clocks@0 {
compatible = "arm, scpi-clk-indexed";
#clock-cells = <1>;
clock-indices = <0 1>;
clock-output-names = "vbig", "vlittle";
};
};
xtal: xtal-clk {
compatible = "fixed-clock";
clock-frequency = <24000000>;
@@ -403,7 +430,6 @@
"jtag_clk_0",
"jtag_tms_0";
function = "jtag";
bias-pull-down;
};
};

View File

@@ -31,6 +31,7 @@ CONFIG_COMPAT=y
CONFIG_CPU_IDLE=y
CONFIG_CPU_FREQ=y
CONFIG_ARM_BIG_LITTLE_CPUFREQ=y
CONFIG_ARM_SCPI_CPUFREQ=y
CONFIG_NET=y
CONFIG_PACKET=y
CONFIG_PACKET_DIAG=y
@@ -173,6 +174,7 @@ CONFIG_AMLOGIC_REG_ACCESS=y
CONFIG_AMLOGIC_TIMER=y
CONFIG_AMLOGIC_BC_TIMER=y
CONFIG_AMLOGIC_CLK=y
CONFIG_AMLOGIC_COMMON_CLK_SCPI=y
CONFIG_AMLOGIC_CRYPTO=y
CONFIG_AMLOGIC_INPUT=y
CONFIG_AMLOGIC_INPUT_KEYBOARD=y

View File

@@ -13,3 +13,13 @@ config AMLOGIC_RESET
default AMLOGIC_CLK
help
This enables the reset driver for Amlogic Meson SoCs.
config AMLOGIC_COMMON_CLK_SCPI
tristate "Clock driver controlled via SCPI interface"
depends on ARM_SCPI_PROTOCOL || COMPILE_TEST
---help---
This driver provides support for clocks that are controlled
by firmware that implements the SCPI interface.
This driver uses SCPI Message Protocol to interact with the
firmware providing all the clock controls.

View File

@@ -2,6 +2,7 @@
# Makefile for Meson specific clk
#
obj-$(CONFIG_AMLOGIC_COMMON_CLK_SCPI) += clk-scpi.o
obj-$(CONFIG_AMLOGIC_RESET) += rstc.o
obj-$(CONFIG_AMLOGIC_CLK) += clk-pll.o clk-cpu.o clk-mpll.o \

View File

@@ -0,0 +1,314 @@
/*
* drivers/amlogic/clk/clk-scpi.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#include <linux/clkdev.h>
#include <linux/clk-provider.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/amlogic/scpi_protocol.h>
struct scpi_clk {
u32 id;
const char *name;
struct clk_hw hw;
struct scpi_dvfs_info *opps;
unsigned long rate_min;
unsigned long rate_max;
};
static struct platform_device *cpufreq_dev;
#define to_scpi_clk(clk) container_of(clk, struct scpi_clk, hw)
static unsigned long scpi_clk_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct scpi_clk *clk = to_scpi_clk(hw);
return scpi_clk_get_val(clk->id);
}
static long scpi_clk_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *parent_rate)
{
struct scpi_clk *clk = to_scpi_clk(hw);
if (clk->rate_min && rate < clk->rate_min)
rate = clk->rate_min;
if (clk->rate_max && rate > clk->rate_max)
rate = clk->rate_max;
return rate;
}
static int scpi_clk_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct scpi_clk *clk = to_scpi_clk(hw);
return scpi_clk_set_val(clk->id, rate);
}
static const struct clk_ops scpi_clk_ops = {
.recalc_rate = scpi_clk_recalc_rate,
.round_rate = scpi_clk_round_rate,
.set_rate = scpi_clk_set_rate,
};
/* find closest match to given frequency in OPP table */
static int __scpi_dvfs_round_rate(struct scpi_clk *clk, unsigned long rate)
{
int idx, max_opp = clk->opps->count;
struct scpi_opp_entry *opp = clk->opps->opp;
u32 fmin = 0, fmax = ~0, ftmp;
for (idx = 0; idx < max_opp; idx++, opp++) {
ftmp = opp->freq_hz;
if (ftmp >= (u32)rate) {
if (ftmp <= fmax)
fmax = ftmp;
} else {
if (ftmp >= fmin)
fmin = ftmp;
}
}
if (fmax != ~0)
return fmax;
else
return fmin;
}
static unsigned long scpi_dvfs_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct scpi_clk *clk = to_scpi_clk(hw);
int idx = scpi_dvfs_get_idx(clk->id);
struct scpi_opp_entry *opp = clk->opps->opp;
if (idx < 0)
return 0;
else
return opp[idx].freq_hz;
}
static long scpi_dvfs_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *parent_rate)
{
struct scpi_clk *clk = to_scpi_clk(hw);
return __scpi_dvfs_round_rate(clk, rate);
}
static int __scpi_find_dvfs_index(struct scpi_clk *clk, unsigned long rate)
{
int idx, max_opp = clk->opps->count;
struct scpi_opp_entry *opp = clk->opps->opp;
for (idx = 0; idx < max_opp; idx++, opp++)
if (opp->freq_hz == (u32)rate)
break;
return (idx == max_opp) ? -EINVAL : idx;
}
static int scpi_dvfs_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct scpi_clk *clk = to_scpi_clk(hw);
int ret = __scpi_find_dvfs_index(clk, rate);
if (ret < 0)
return ret;
else
return scpi_dvfs_set_idx(clk->id, (u8)ret);
}
static const struct clk_ops scpi_dvfs_ops = {
.recalc_rate = scpi_dvfs_recalc_rate,
.round_rate = scpi_dvfs_round_rate,
.set_rate = scpi_dvfs_set_rate,
};
static struct clk *
scpi_dvfs_ops_init(struct device *dev, struct device_node *np,
struct scpi_clk *sclk)
{
struct clk_init_data init;
struct scpi_dvfs_info *opps;
init.name = sclk->name;
init.flags = 0;
init.num_parents = 0;
init.ops = &scpi_dvfs_ops;
sclk->hw.init = &init;
opps = scpi_dvfs_get_opps(sclk->id);
if (IS_ERR(opps))
return (struct clk *)opps;
sclk->opps = opps;
return devm_clk_register(dev, &sclk->hw);
}
static struct clk *
scpi_clk_ops_init(struct device *dev, struct device_node *np,
struct scpi_clk *sclk)
{
struct clk_init_data init;
u32 range[2];
int ret;
init.name = sclk->name;
init.flags = 0;
init.num_parents = 0;
init.ops = &scpi_clk_ops;
sclk->hw.init = &init;
ret = of_property_read_u32_array(np, "frequency-range", range,
ARRAY_SIZE(range));
if (ret)
return ERR_PTR(ret);
sclk->rate_min = range[0];
sclk->rate_max = range[1];
return devm_clk_register(dev, &sclk->hw);
}
static int scpi_clk_setup(struct device *dev, struct device_node *np,
const void *data)
{
struct clk * (*setup_ops)(struct device *, struct device_node *,
struct scpi_clk *) = data;
struct clk_onecell_data *clk_data;
struct clk **clks;
size_t count;
int idx;
count = of_property_count_strings(np, "clock-output-names");
if (count < 0) {
dev_err(dev, "%s: invalid clock output count\n", np->name);
return -EINVAL;
}
clk_data = devm_kmalloc(dev, sizeof(*clk_data), GFP_KERNEL);
if (!clk_data)
return -ENOMEM;
clks = devm_kmalloc(dev, count * sizeof(*clks), GFP_KERNEL);
if (!clks)
return -ENOMEM;
for (idx = 0; idx < count; idx++) {
struct scpi_clk *sclk;
u32 val;
sclk = devm_kzalloc(dev, sizeof(*sclk), GFP_KERNEL);
if (!sclk)
return -ENOMEM;
if (of_property_read_string_index(np, "clock-output-names",
idx, &sclk->name)) {
dev_err(dev, "invalid clock name @ %s\n", np->name);
return -EINVAL;
}
if (of_property_read_u32_index(np, "clock-indices",
idx, &val)) {
dev_err(dev, "invalid clock index @ %s\n", np->name);
return -EINVAL;
}
sclk->id = val;
clks[idx] = setup_ops(dev, np, sclk);
if (IS_ERR(clks[idx])) {
dev_err(dev, "failed to register clock '%s'\n",
sclk->name);
return PTR_ERR(clks[idx]);
}
dev_dbg(dev, "Registered clock '%s'\n", sclk->name);
}
clk_data->clks = clks;
clk_data->clk_num = count;
of_clk_add_provider(np, of_clk_src_onecell_get, clk_data);
return 0;
}
static const struct of_device_id clk_match[] = {
{ .compatible = "arm, scpi-clk-indexed", .data = scpi_dvfs_ops_init, },
{ .compatible = "arm, scpi-clk-range", .data = &scpi_clk_ops_init, },
{}
};
static int scpi_clk_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node, *child;
const struct of_device_id *match;
int ret;
for_each_child_of_node(np, child) {
match = of_match_node(clk_match, child);
if (!match)
continue;
ret = scpi_clk_setup(dev, child, match->data);
if (ret)
return ret;
}
/* Add the virtual cpufreq device */
cpufreq_dev = platform_device_register_simple("scpi-cpufreq",
-1, NULL, 0);
if (!cpufreq_dev)
pr_warn("unable to register cpufreq device");
return 0;
}
static const struct of_device_id scpi_clk_ids[] = {
{ .compatible = "arm, scpi-clks", },
{}
};
static struct platform_driver scpi_clk_driver = {
.driver = {
.name = "aml_scpi_clocks",
.of_match_table = scpi_clk_ids,
},
.probe = scpi_clk_probe,
};
static int __init scpi_clk_init(void)
{
return platform_driver_register(&scpi_clk_driver);
}
postcore_initcall(scpi_clk_init);
static void __exit scpi_clk_exit(void)
{
platform_driver_unregister(&scpi_clk_driver);
}
module_exit(scpi_clk_exit);
MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
MODULE_DESCRIPTION("ARM SCPI clock driver");
MODULE_LICENSE("GPL");

View File

@@ -195,7 +195,7 @@ config ARM_SA1110_CPUFREQ
config ARM_SCPI_CPUFREQ
tristate "SCPI based CPUfreq driver"
depends on ARM_BIG_LITTLE_CPUFREQ && ARM_SCPI_PROTOCOL && COMMON_CLK_SCPI
depends on ARM_BIG_LITTLE_CPUFREQ && ARM_SCPI_PROTOCOL && (COMMON_CLK_SCPI || AMLOGIC_COMMON_CLK_SCPI)
help
This adds the CPUfreq driver support for ARM big.LITTLE platforms
using SCPI protocol for CPU power management.

View File

@@ -134,7 +134,14 @@ bL_cpufreq_set_rate(u32 cpu, u32 old_cluster, u32 new_cluster, u32 rate)
int ret;
bool bLs = is_bL_switching_enabled();
#ifdef CONFIG_AMLOGIC_COMMON_CLK_SCPI
/* MARK: cluster0 and cluster share the same scpi lock,
* and don't send scpi command at the same time
*/
mutex_lock(&cluster_lock[0]);
#else
mutex_lock(&cluster_lock[new_cluster]);
#endif
if (bLs) {
prev_rate = per_cpu(cpu_last_req_freq, cpu);
@@ -172,12 +179,20 @@ bL_cpufreq_set_rate(u32 cpu, u32 old_cluster, u32 new_cluster, u32 rate)
per_cpu(physical_cluster, cpu) = old_cluster;
}
#ifdef CONFIG_AMLOGIC_COMMON_CLK_SCPI
mutex_unlock(&cluster_lock[0]);
#else
mutex_unlock(&cluster_lock[new_cluster]);
#endif
return ret;
}
#ifdef CONFIG_AMLOGIC_COMMON_CLK_SCPI
mutex_unlock(&cluster_lock[0]);
#else
mutex_unlock(&cluster_lock[new_cluster]);
#endif
/* Recalc freq for old cluster when switching clusters */
if (old_cluster != new_cluster) {
@@ -187,7 +202,11 @@ bL_cpufreq_set_rate(u32 cpu, u32 old_cluster, u32 new_cluster, u32 rate)
/* Switch cluster */
bL_switch_request(cpu, new_cluster);
mutex_lock(&cluster_lock[old_cluster]);
#ifdef CONFIG_AMLOGIC_COMMON_CLK_SCPI
mutex_lock(&cluster_lock[0]);
#else
mutex_lock(&cluster_lock[new_cluster]);
#endif
/* Set freq of old cluster if there are cpus left on it */
new_rate = find_cluster_maxfreq(old_cluster);
@@ -201,7 +220,11 @@ bL_cpufreq_set_rate(u32 cpu, u32 old_cluster, u32 new_cluster, u32 rate)
pr_err("%s: clk_set_rate failed: %d, old cluster: %d\n",
__func__, ret, old_cluster);
}
mutex_unlock(&cluster_lock[old_cluster]);
#ifdef CONFIG_AMLOGIC_COMMON_CLK_SCPI
mutex_unlock(&cluster_lock[0]);
#else
mutex_unlock(&cluster_lock[new_cluster]);
#endif
}
return 0;

View File

@@ -29,6 +29,7 @@
#include "arm_big_little.h"
static struct scpi_ops *scpi_ops;
struct scpi_dvfs_info *scpi_dvfs_get_opps(u8 domain);
static struct scpi_dvfs_info *scpi_get_dvfs_info(struct device *cpu_dev)
{
@@ -36,7 +37,12 @@ static struct scpi_dvfs_info *scpi_get_dvfs_info(struct device *cpu_dev)
if (domain < 0)
return ERR_PTR(-EINVAL);
return scpi_ops->dvfs_get_info(domain);
/* TODO: Use API from 3.14 temporary.
* When 4.4 arm_mhu and arm_scpi is ported,
* tihs will be replaced by: return scpi_ops->dvfs_get_info(domain);
*/
return scpi_dvfs_get_opps(domain);
}
static int scpi_get_transition_latency(struct device *cpu_dev)
@@ -88,9 +94,12 @@ static struct cpufreq_arm_bL_ops scpi_cpufreq_ops = {
static int scpi_cpufreq_probe(struct platform_device *pdev)
{
scpi_ops = get_scpi_ops();
if (!scpi_ops)
return -EIO;
/* TODO: TODO: Use API from 3.14 temporary.
* When 4.4 arm_mhu and arm_scpi is ported, these lines should be added:
* scpi_ops = get_scpi_ops();
* if (!scpi_ops)
* return -EIO;
*/
return bL_cpufreq_register(&scpi_cpufreq_ops);
}