[PATCH 3/5] ARM: vexpress/TC2: add cpu clock support
Nicolas Pitre
nicolas.pitre at linaro.org
Fri Oct 18 14:53:28 EDT 2013
On Wed, 16 Oct 2013, Sudeep KarkadaNagesha wrote:
> From: Sudeep KarkadaNagesha <sudeep.karkadanagesha at arm.com>
>
> On TC2, the cpu clocks are controlled by the external M3 microcontroller
> and SPC provides the interface between the CPU and the power controller.
>
> The generic cpufreq drivers use the clock APIs to get the cpu clocks.
> This patch add virtual spc clocks for all the cpus to control the cpu
> operating frequency via the clock framework.
>
> Signed-off-by: Sudeep KarkadaNagesha <sudeep.karkadanagesha at arm.com>
> Cc: Pawel Moll <Pawel.Moll at arm.com>
> Cc: Viresh Kumar <viresh.kumar at linaro.org>
Ninor comment below.
> ---
> arch/arm/mach-vexpress/spc.c | 119 +++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 119 insertions(+)
>
> diff --git a/arch/arm/mach-vexpress/spc.c b/arch/arm/mach-vexpress/spc.c
> index ef7e652..a8b8310 100644
> --- a/arch/arm/mach-vexpress/spc.c
> +++ b/arch/arm/mach-vexpress/spc.c
> @@ -17,6 +17,9 @@
> * GNU General Public License for more details.
> */
>
> +#include <linux/clk-provider.h>
> +#include <linux/clkdev.h>
> +#include <linux/cpu.h>
> #include <linux/delay.h>
> #include <linux/err.h>
> #include <linux/interrupt.h>
> @@ -416,3 +419,119 @@ int __init ve_spc_init(void __iomem *baseaddr, u32 a15_clusid, int irq)
>
> return 0;
> }
> +
> +struct clk_spc {
> + struct clk_hw hw;
> + int cluster;
> +};
> +
> +#define to_clk_spc(spc) container_of(spc, struct clk_spc, hw)
> +static unsigned long spc_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + struct clk_spc *spc = to_clk_spc(hw);
> + u32 freq;
> +
> + if (ve_spc_get_performance(spc->cluster, &freq))
> + return -EIO;
> +
> + return freq * 1000;
> +}
> +
> +static long spc_round_rate(struct clk_hw *hw, unsigned long drate,
> + unsigned long *parent_rate)
> +{
> + struct clk_spc *spc = to_clk_spc(hw);
> +
> + return ve_spc_round_performance(spc->cluster, drate);
> +}
> +
> +static int spc_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + struct clk_spc *spc = to_clk_spc(hw);
> +
> + return ve_spc_set_performance(spc->cluster, rate / 1000);
> +}
> +
> +static struct clk_ops clk_spc_ops = {
> + .recalc_rate = spc_recalc_rate,
> + .round_rate = spc_round_rate,
> + .set_rate = spc_set_rate,
> +};
> +
> +static struct clk *ve_spc_clk_register(struct device *cpu_dev)
> +{
> + struct clk_init_data init;
> + struct clk_spc *spc;
> +
> + spc = kzalloc(sizeof(*spc), GFP_KERNEL);
> + if (!spc) {
> + pr_err("could not allocate spc clk\n");
> + return ERR_PTR(-ENOMEM);
> + }
> +
> + spc->hw.init = &init;
> + spc->cluster = topology_physical_package_id(cpu_dev->id);
> +
> + init.name = dev_name(cpu_dev);
> + init.ops = &clk_spc_ops;
> + init.flags = CLK_IS_ROOT | CLK_GET_RATE_NOCACHE;
> + init.num_parents = 0;
> +
> + return devm_clk_register(cpu_dev, &spc->hw);
> +}
> +
> +static int ve_init_opp_table(struct device *cpu_dev)
> +{
> + int cluster = topology_physical_package_id(cpu_dev->id);
> + int idx, ret = 0, max_opp = info->num_opps[cluster];
> + struct ve_spc_opp *opps = info->opps[cluster];
> +
> + for (idx = 0; idx < max_opp; idx++, opps++) {
> + ret = opp_add(cpu_dev, opps->freq * 1000, opps->u_volt);
> + if (ret) {
> + dev_warn(cpu_dev, "failed to add opp %lu %lu\n",
> + opps->freq, opps->u_volt);
> + return ret;
> + }
> + }
> + return ret;
> +}
> +
> +static int __init ve_spc_clk_init(void)
> +{
> + int cpu;
> + struct clk *clk;
> +
> + if (!info)
> + return 0; /* Continue only if SPC is initialised */
> +
> + if (ve_spc_populate_opps(0) || ve_spc_populate_opps(1)) {
> + pr_err("failed to build OPP table\n");
> + return -ENODEV;
> + }
> +
> + for_each_possible_cpu(cpu) {
> + struct device *cpu_dev = get_cpu_device(cpu);
> + if (!cpu_dev) {
> + pr_warn("failed to get cpu%d device\n", cpu);
> + continue;
> + }
> + clk = ve_spc_clk_register(cpu_dev);
> + if (IS_ERR(clk)) {
> + pr_warn("failed to register cpu%d clock\n", cpu);
> + continue;
> + }
> + if (clk_register_clkdev(clk, NULL, dev_name(cpu_dev))) {
> + pr_warn("failed to register cpu%d clock lookup\n", cpu);
> + continue;
> + }
> +
> + if (ve_init_opp_table(cpu_dev))
> + pr_warn("failed to initialise cpu%d opp table\n", cpu);
> + }
> +
> + return 0;
> +}
> +module_init(ve_spc_clk_init);
This file is not compilable as a module as it is needed to start up
secondary CPUs during boot. In that case the module_init() is converted
to device_initcall() which is the same level as used by ve_spc_cpufreq
when both are compiled in. To avoid possible unordered init calls, I'd
suggest making this into a higher priority init call such as
subsys_initcall(() ... unless this one is already used by or earlier
than the clock infrastructure initialization (I didn't check). Despite
the name fs_initcall() ought to do the job.
With that change, you may add
Acked-by: Nicolas Pitre <nico at linaro.org>
Nicolas
More information about the linux-arm-kernel
mailing list