Thanks Arnd, Mark, Jamie, Rob, for your review.
Changes in v4: - add depends on HAVE_CLK && OF && REGULATOR - add set_cpu_freq fail check - regulator_put wehn module exit - add pr_fmt and convert all printk to pr_xxx - use voltage range - comment and doc fix - add cpu_volts value pre-check in module init - add helpfull module parameter max_freq - remove compatible string check on Arnd's comment. - remove generic-cpufreq to clk-reg-cpufreq
Changes in v3: - move adjusting smp loops_per_jiffy to arm common code, and also adjust global loops_per_jiffy. - remove adjusting loops_per_jiffy in imx and omap cpufreq drivers. - check compatible "generic-cpufreq" when module_init - change printk to pr_xxx - add generic-cpufreq DT binding doc
Changes in v2: - add volatage change support - change '_' in property name to '-' - use initial value to calculate loops_per_jiffy - fix reading cpu_volts property bug - let cpufreq_frequency_table_cpuinfo routines handle cpu_freq_khz_max/min - don't change freq in arm_cpufreq_exit, because every core share the same freq. - use unsigned long describe frequency as much as possible. Because clk use unsigned long, but cpufreq use unsigned int.
[PATCH v4 1/7] ARM: add cpufreq transiton notifier to adjust [PATCH v4 2/7] arm/imx: cpufreq: remove loops_per_jiffy recalculate [PATCH v4 3/7] cpufreq: OMAP: remove loops_per_jiffy recalculate for [PATCH v4 4/7] cpufreq: add clk-reg cpufreq driver [PATCH v4 5/7] dts/imx6q: add cpufreq property [PATCH v4 6/7] arm/imx6q: register arm_clk as cpu to clkdev [PATCH v4 7/7] arm/imx6q: select ARCH_HAS_CPUFREQ
If CONFIG_SMP, cpufreq skips loops_per_jiffy update, because different arch has different per-cpu loops_per_jiffy definition.
Signed-off-by: Richard Zhao richard.zhao@linaro.org --- arch/arm/kernel/smp.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 54 insertions(+), 0 deletions(-)
diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index ef5640b..ac9cadc 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -25,6 +25,7 @@ #include <linux/percpu.h> #include <linux/clockchips.h> #include <linux/completion.h> +#include <linux/cpufreq.h>
#include <linux/atomic.h> #include <asm/cacheflush.h> @@ -631,3 +632,56 @@ int setup_profiling_timer(unsigned int multiplier) { return -EINVAL; } + +#ifdef CONFIG_CPU_FREQ + +static DEFINE_PER_CPU(unsigned long, l_p_j_ref); +static DEFINE_PER_CPU(unsigned long, l_p_j_ref_freq); +static unsigned long global_l_p_j_ref; +static unsigned long global_l_p_j_ref_freq; + +static int cpufreq_callback(struct notifier_block *nb, + unsigned long val, void *data) +{ + struct cpufreq_freqs *freq = data; + int cpu = freq->cpu; + + if (freq->flags & CPUFREQ_CONST_LOOPS) + return NOTIFY_OK; + + if (!per_cpu(l_p_j_ref, cpu)) { + per_cpu(l_p_j_ref, cpu) = + per_cpu(cpu_data, cpu).loops_per_jiffy; + per_cpu(l_p_j_ref_freq, cpu) = freq->old; + if (!global_l_p_j_ref) { + global_l_p_j_ref = loops_per_jiffy; + global_l_p_j_ref_freq = freq->old; + } + } + + if ((val == CPUFREQ_PRECHANGE && freq->old < freq->new) || + (val == CPUFREQ_POSTCHANGE && freq->old > freq->new) || + (val == CPUFREQ_RESUMECHANGE || val == CPUFREQ_SUSPENDCHANGE)) { + loops_per_jiffy = cpufreq_scale(global_l_p_j_ref, + global_l_p_j_ref_freq, + freq->new); + per_cpu(cpu_data, cpu).loops_per_jiffy = + cpufreq_scale(per_cpu(l_p_j_ref, cpu), + per_cpu(l_p_j_ref_freq, cpu), + freq->new); + } + return NOTIFY_OK; +} + +static struct notifier_block cpufreq_notifier = { + .notifier_call = cpufreq_callback, +}; + +static int __init register_cpufreq_notifier(void) +{ + return cpufreq_register_notifier(&cpufreq_notifier, + CPUFREQ_TRANSITION_NOTIFIER); +} +core_initcall(register_cpufreq_notifier); + +#endif
arm registered cpufreq transition notifier to recalculate it.
Signed-off-by: Richard Zhao richard.zhao@linaro.org --- arch/arm/plat-mxc/cpufreq.c | 10 ---------- 1 files changed, 0 insertions(+), 10 deletions(-)
diff --git a/arch/arm/plat-mxc/cpufreq.c b/arch/arm/plat-mxc/cpufreq.c index c937e75..364793a 100644 --- a/arch/arm/plat-mxc/cpufreq.c +++ b/arch/arm/plat-mxc/cpufreq.c @@ -99,16 +99,6 @@ static int mxc_set_target(struct cpufreq_policy *policy,
ret = set_cpu_freq(freq_Hz);
-#ifdef CONFIG_SMP - /* loops_per_jiffy is not updated by the cpufreq core for SMP systems. - * So update it for all CPUs. - */ - for_each_possible_cpu(cpu) - per_cpu(cpu_data, cpu).loops_per_jiffy = - cpufreq_scale(per_cpu(cpu_data, cpu).loops_per_jiffy, - freqs.old, freqs.new); -#endif - for_each_possible_cpu(cpu) { freqs.cpu = cpu; cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
arm registered cpufreq transition notifier to recalculate it.
Signed-off-by: Richard Zhao richard.zhao@linaro.org --- drivers/cpufreq/omap-cpufreq.c | 36 ------------------------------------ 1 files changed, 0 insertions(+), 36 deletions(-)
diff --git a/drivers/cpufreq/omap-cpufreq.c b/drivers/cpufreq/omap-cpufreq.c index 5d04c57..17da4c4 100644 --- a/drivers/cpufreq/omap-cpufreq.c +++ b/drivers/cpufreq/omap-cpufreq.c @@ -37,16 +37,6 @@
#include <mach/hardware.h>
-#ifdef CONFIG_SMP -struct lpj_info { - unsigned long ref; - unsigned int freq; -}; - -static DEFINE_PER_CPU(struct lpj_info, lpj_ref); -static struct lpj_info global_lpj_ref; -#endif - static struct cpufreq_frequency_table *freq_table; static atomic_t freq_table_users = ATOMIC_INIT(0); static struct clk *mpu_clk; @@ -118,32 +108,6 @@ static int omap_target(struct cpufreq_policy *policy, ret = clk_set_rate(mpu_clk, freqs.new * 1000); freqs.new = omap_getspeed(policy->cpu);
-#ifdef CONFIG_SMP - /* - * Note that loops_per_jiffy is not updated on SMP systems in - * cpufreq driver. So, update the per-CPU loops_per_jiffy value - * on frequency transition. We need to update all dependent CPUs. - */ - for_each_cpu(i, policy->cpus) { - struct lpj_info *lpj = &per_cpu(lpj_ref, i); - if (!lpj->freq) { - lpj->ref = per_cpu(cpu_data, i).loops_per_jiffy; - lpj->freq = freqs.old; - } - - per_cpu(cpu_data, i).loops_per_jiffy = - cpufreq_scale(lpj->ref, lpj->freq, freqs.new); - } - - /* And don't forget to adjust the global one */ - if (!global_lpj_ref.freq) { - global_lpj_ref.ref = loops_per_jiffy; - global_lpj_ref.freq = freqs.old; - } - loops_per_jiffy = cpufreq_scale(global_lpj_ref.ref, global_lpj_ref.freq, - freqs.new); -#endif - /* notifiers */ for_each_cpu(i, policy->cpus) { freqs.cpu = i;
The driver get cpu operation point table from device tree cpu0 node, and adjusts operating points using clk and regulator APIs.
It support single core and multi-core ARM SoCs. But currently it assume all cores share the same frequency and voltage.
Signed-off-by: Richard Zhao richard.zhao@linaro.org --- .../devicetree/bindings/cpufreq/clk-reg-cpufreq | 7 + drivers/cpufreq/Kconfig | 10 + drivers/cpufreq/Makefile | 2 + drivers/cpufreq/clk-reg-cpufreq.c | 289 ++++++++++++++++++++ 4 files changed, 308 insertions(+), 0 deletions(-) create mode 100644 Documentation/devicetree/bindings/cpufreq/clk-reg-cpufreq create mode 100644 drivers/cpufreq/clk-reg-cpufreq.c
diff --git a/Documentation/devicetree/bindings/cpufreq/clk-reg-cpufreq b/Documentation/devicetree/bindings/cpufreq/clk-reg-cpufreq new file mode 100644 index 0000000..bf07c1b --- /dev/null +++ b/Documentation/devicetree/bindings/cpufreq/clk-reg-cpufreq @@ -0,0 +1,7 @@ +Generic cpufreq driver based on clk and regulator APIs + +Required properties in /cpus/cpu@0: +- cpu-freqs : cpu frequency points it support, in unit of Hz. +- cpu-volts : cpu voltages required by the frequency point at the same index, + in unit of uV. +- trans-latency : transition_latency, in unit of ns. diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig index e24a2a1..95470f1 100644 --- a/drivers/cpufreq/Kconfig +++ b/drivers/cpufreq/Kconfig @@ -179,6 +179,16 @@ config CPU_FREQ_GOV_CONSERVATIVE
If in doubt, say N.
+config CLK_REG_CPUFREQ_DRIVER + tristate "Generic cpufreq driver using clk and regulator APIs" + depends on HAVE_CLK && OF && REGULATOR + select CPU_FREQ_TABLE + help + This adds generic CPUFreq driver based on clk and regulator APIs. + It assumes all cores of the CPU share the same clock and voltage. + + If in doubt, say N. + menu "x86 CPU frequency scaling drivers" depends on X86 source "drivers/cpufreq/Kconfig.x86" diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index ce75fcb..2c4eb33 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -13,6 +13,8 @@ obj-$(CONFIG_CPU_FREQ_GOV_CONSERVATIVE) += cpufreq_conservative.o # CPUfreq cross-arch helpers obj-$(CONFIG_CPU_FREQ_TABLE) += freq_table.o
+obj-$(CONFIG_CLK_REG_CPUFREQ_DRIVER) += clk-reg-cpufreq.o + ################################################################################## # x86 drivers. # Link order matters. K8 is preferred to ACPI because of firmware bugs in early diff --git a/drivers/cpufreq/clk-reg-cpufreq.c b/drivers/cpufreq/clk-reg-cpufreq.c new file mode 100644 index 0000000..c30d2c5 --- /dev/null +++ b/drivers/cpufreq/clk-reg-cpufreq.c @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/cpufreq.h> +#include <linux/clk.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/of.h> + +static u32 *cpu_freqs; /* Hz */ +static u32 *cpu_volts; /* uV */ +static u32 trans_latency; /* ns */ +static int cpu_op_nr; +static unsigned int cur_index; + +static struct clk *cpu_clk; +static struct regulator *cpu_reg; +static struct cpufreq_frequency_table *freq_table; + +static int set_cpu_freq(unsigned long freq, int index, int higher) +{ + int ret = 0; + + if (higher && cpu_reg) { + ret = regulator_set_voltage(cpu_reg, + cpu_volts[index * 2], cpu_volts[index * 2 + 1]); + if (ret) { + pr_err("set cpu voltage failed!\n"); + return ret; + } + } + + ret = clk_set_rate(cpu_clk, freq); + if (ret) { + if (cpu_reg) + regulator_set_voltage(cpu_reg, cpu_volts[cur_index * 2], + cpu_volts[cur_index * 2 + 1]); + pr_err("cannot set CPU clock rate\n"); + return ret; + } + + if (!higher && cpu_reg) { + ret = regulator_set_voltage(cpu_reg, + cpu_volts[index * 2], cpu_volts[index * 2 + 1]); + if (ret) + pr_warn("set cpu voltage failed, might run on" + " higher voltage!\n"); + ret = 0; + } + + return ret; +} + +static int clk_reg_verify_speed(struct cpufreq_policy *policy) +{ + return cpufreq_frequency_table_verify(policy, freq_table); +} + +static unsigned int clk_reg_get_speed(unsigned int cpu) +{ + return clk_get_rate(cpu_clk) / 1000; +} + +static int clk_reg_set_target(struct cpufreq_policy *policy, + unsigned int target_freq, unsigned int relation) +{ + struct cpufreq_freqs freqs; + unsigned long freq_Hz; + int cpu; + int ret = 0; + unsigned int index; + + cpufreq_frequency_table_target(policy, freq_table, + target_freq, relation, &index); + freq_Hz = clk_round_rate(cpu_clk, cpu_freqs[index]); + freq_Hz = freq_Hz ? freq_Hz : cpu_freqs[index]; + freqs.old = clk_get_rate(cpu_clk) / 1000; + freqs.new = freq_Hz / 1000; + freqs.flags = 0; + + if (freqs.old == freqs.new) + return 0; + + for_each_possible_cpu(cpu) { + freqs.cpu = cpu; + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + } + + ret = set_cpu_freq(freq_Hz, index, (freqs.new > freqs.old)); + if (ret) + freqs.new = clk_get_rate(cpu_clk) / 1000; + else + cur_index = index; + + for_each_possible_cpu(cpu) { + freqs.cpu = cpu; + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + } + + return ret; +} + +static int clk_reg_cpufreq_init(struct cpufreq_policy *policy) +{ + int ret; + + if (policy->cpu >= num_possible_cpus()) + return -EINVAL; + + policy->cur = clk_get_rate(cpu_clk) / 1000; + policy->shared_type = CPUFREQ_SHARED_TYPE_ANY; + cpumask_setall(policy->cpus); + policy->cpuinfo.transition_latency = trans_latency; + + ret = cpufreq_frequency_table_cpuinfo(policy, freq_table); + + if (ret < 0) { + pr_err("invalid frequency table for cpu %d\n", + policy->cpu); + return ret; + } + + cpufreq_frequency_table_get_attr(freq_table, policy->cpu); + cpufreq_frequency_table_target(policy, freq_table, policy->cur, + CPUFREQ_RELATION_H, &cur_index); + return 0; +} + +static int clk_reg_cpufreq_exit(struct cpufreq_policy *policy) +{ + cpufreq_frequency_table_put_attr(policy->cpu); + return 0; +} + +static struct cpufreq_driver clk_reg_cpufreq_driver = { + .flags = CPUFREQ_STICKY, + .verify = clk_reg_verify_speed, + .target = clk_reg_set_target, + .get = clk_reg_get_speed, + .init = clk_reg_cpufreq_init, + .exit = clk_reg_cpufreq_exit, + .name = "clk-reg", +}; + + +static u32 max_freq = UINT_MAX / 1000; /* kHz */ +module_param(max_freq, uint, 0); +MODULE_PARM_DESC(max_freq, "max cpu frequency in unit of kHz"); + +static int __devinit clk_reg_cpufreq_driver_init(void) +{ + struct device_node *cpu0; + const struct property *pp; + int i, ret; + + cpu0 = of_find_node_by_path("/cpus/cpu@0"); + if (!cpu0) + return -ENODEV; + + pp = of_find_property(cpu0, "cpu-freqs", NULL); + if (!pp) { + ret = -ENODEV; + goto put_node; + } + cpu_op_nr = pp->length / sizeof(u32); + if (!cpu_op_nr) { + ret = -ENODEV; + goto put_node; + } + ret = -ENOMEM; + cpu_freqs = kzalloc(sizeof(*cpu_freqs) * cpu_op_nr, GFP_KERNEL); + if (!cpu_freqs) + goto put_node; + of_property_read_u32_array(cpu0, "cpu-freqs", cpu_freqs, cpu_op_nr); + + pp = of_find_property(cpu0, "cpu-volts", NULL); + if (pp) { + if (cpu_op_nr * 2 == pp->length / sizeof(u32)) { + cpu_volts = kzalloc(sizeof(*cpu_volts) * cpu_op_nr * 2, + GFP_KERNEL); + if (!cpu_volts) + goto free_cpu_freqs; + of_property_read_u32_array(cpu0, "cpu-volts", + cpu_volts, cpu_op_nr * 2); + } else + pr_warn("invalid cpu_volts!\n"); + } + + if (of_property_read_u32(cpu0, "trans-latency", &trans_latency)) + trans_latency = CPUFREQ_ETERNAL; + + cpu_clk = clk_get(NULL, "cpu"); + if (IS_ERR(cpu_clk)) { + pr_err("failed to get cpu clock\n"); + ret = PTR_ERR(cpu_clk); + goto free_cpu_volts; + } + + if (cpu_volts) { + cpu_reg = regulator_get(NULL, "cpu"); + if (IS_ERR(cpu_reg)) { + pr_warn("regulator cpu get failed.\n"); + cpu_reg = NULL; + } + } + + freq_table = kmalloc(sizeof(struct cpufreq_frequency_table) + * (cpu_op_nr + 1), GFP_KERNEL); + if (!freq_table) { + ret = -ENOMEM; + goto reg_put; + } + + for (i = 0; i < cpu_op_nr; i++) { + freq_table[i].index = i; + if (cpu_freqs[i] > max_freq * 1000) { + freq_table[i].frequency = CPUFREQ_ENTRY_INVALID; + continue; + } + + if (cpu_reg) { + ret = regulator_is_supported_voltage(cpu_reg, + cpu_volts[i * 2], cpu_volts[i * 2 + 1]); + if (ret <= 0) { + freq_table[i].frequency = CPUFREQ_ENTRY_INVALID; + continue; + } + } + freq_table[i].frequency = cpu_freqs[i] / 1000; + } + + freq_table[i].index = i; + freq_table[i].frequency = CPUFREQ_TABLE_END; + + ret = cpufreq_register_driver(&clk_reg_cpufreq_driver); + if (ret) + goto free_freq_table; + + of_node_put(cpu0); + + return 0; + +free_freq_table: + kfree(freq_table); +reg_put: + if (cpu_reg) + regulator_put(cpu_reg); + clk_put(cpu_clk); +free_cpu_volts: + kfree(cpu_volts); +free_cpu_freqs: + kfree(cpu_freqs); +put_node: + of_node_put(cpu0); + + return ret; +} + +static void clk_reg_cpufreq_driver_exit(void) +{ + cpufreq_unregister_driver(&clk_reg_cpufreq_driver); + kfree(cpu_freqs); + kfree(cpu_volts); + clk_put(cpu_clk); + if (cpu_reg) + regulator_put(cpu_reg); + kfree(freq_table); +} + +module_init(clk_reg_cpufreq_driver_init); +module_exit(clk_reg_cpufreq_driver_exit); + +MODULE_AUTHOR("Freescale Semiconductor Inc. Richard Zhao richard.zhao@freescale.com"); +MODULE_DESCRIPTION("Generic CPUFreq driver based on clk and regulator APIs"); +MODULE_LICENSE("GPL");
On Thu, Dec 22, 2011 at 03:09:10PM +0800, Richard Zhao wrote:
The driver get cpu operation point table from device tree cpu0 node, and adjusts operating points using clk and regulator APIs.
Reviewed-by: Mark Brown broonie@opensource.wolfsonmicro.com
but one nit:
+Required properties in /cpus/cpu@0: +- cpu-freqs : cpu frequency points it support, in unit of Hz. +- cpu-volts : cpu voltages required by the frequency point at the same index,
in unit of uV.
+- trans-latency : transition_latency, in unit of ns.
trans-latency should really say what latency is being measured (the CPU core only or the whole operation).
On Fri, Dec 23, 2011 at 01:18:51PM +0000, Mark Brown wrote:
On Thu, Dec 22, 2011 at 03:09:10PM +0800, Richard Zhao wrote:
The driver get cpu operation point table from device tree cpu0 node, and adjusts operating points using clk and regulator APIs.
Reviewed-by: Mark Brown broonie@opensource.wolfsonmicro.com
Thanks.
but one nit:
+Required properties in /cpus/cpu@0: +- cpu-freqs : cpu frequency points it support, in unit of Hz. +- cpu-volts : cpu voltages required by the frequency point at the same index,
in unit of uV.
+- trans-latency : transition_latency, in unit of ns.
trans-latency should really say what latency is being measured (the CPU core only or the whole operation).
dts only descibe hw info. so the transition latency is for hw. - trans-latency : transition latency of HW, in unit of ns.
Thanks Richard
On Sat, Dec 24, 2011 at 04:55:42PM +0800, Richard Zhao wrote:
On Fri, Dec 23, 2011 at 01:18:51PM +0000, Mark Brown wrote:
+- trans-latency : transition_latency, in unit of ns.
trans-latency should really say what latency is being measured (the CPU core only or the whole operation).
dts only descibe hw info. so the transition latency is for hw.
- trans-latency : transition latency of HW, in unit of ns.
The issue is that you need to clarify which hardware this is - it's the clocks only, not the time for regulators.
On Sat, Dec 24, 2011 at 12:24:11PM +0000, Mark Brown wrote:
On Sat, Dec 24, 2011 at 04:55:42PM +0800, Richard Zhao wrote:
On Fri, Dec 23, 2011 at 01:18:51PM +0000, Mark Brown wrote:
+- trans-latency : transition_latency, in unit of ns.
trans-latency should really say what latency is being measured (the CPU core only or the whole operation).
dts only descibe hw info. so the transition latency is for hw.
- trans-latency : transition latency of HW, in unit of ns.
The issue is that you need to clarify which hardware this is - it's the clocks only, not the time for regulators.
- trans-latency : transition latency of cpu freq and related regulator, in unit of ns.
Does it look better? If you think regulator thansition latency is board specific, then the board dts can overrite it.
Thanks Richard
On Sat, Dec 24, 2011 at 09:28:33PM +0800, Richard Zhao wrote:
On Sat, Dec 24, 2011 at 12:24:11PM +0000, Mark Brown wrote:
- trans-latency : transition latency of cpu freq and related regulator, in unit of ns.
Does it look better?
I think it shouldn't include the regulator part of things.
If you think regulator thansition latency is board specific, then the board dts can overrite it.
We should just query this information from the regulator subsystem (there's hooks but currently nothing implements them). The regulators can define their own bindings if they need to read it from device tree, most of them should be able to do this as a function of knowing about the device. None of this is specific to cpufreq so cpufreq shouldn't have to define its own support for this.
On Sat, Dec 24, 2011 at 01:42:29PM +0000, Mark Brown wrote:
On Sat, Dec 24, 2011 at 09:28:33PM +0800, Richard Zhao wrote:
On Sat, Dec 24, 2011 at 12:24:11PM +0000, Mark Brown wrote:
- trans-latency : transition latency of cpu freq and related regulator, in unit of ns.
Does it look better?
I think it shouldn't include the regulator part of things.
If you think regulator thansition latency is board specific, then the board dts can overrite it.
We should just query this information from the regulator subsystem (there's hooks but currently nothing implements them). The regulators can define their own bindings if they need to read it from device tree, most of them should be able to do this as a function of knowing about the device. None of this is specific to cpufreq so cpufreq shouldn't have to define its own support for this.
I'd like to query the latency by call clk and regulator APIs. but as you said both of them have not implemented it yet. I think, for now, we can use the property to get the total latency. Once I can get it at runtime, I'll remove it. So the definition of trans-latency is just the same as cpufreq transition_latency, people get less confused. What do you think?
Thanks Richard
On Sat, Dec 24, 2011 at 11:52:29PM +0800, Richard Zhao wrote:
On Sat, Dec 24, 2011 at 01:42:29PM +0000, Mark Brown wrote:
On Sat, Dec 24, 2011 at 09:28:33PM +0800, Richard Zhao wrote:
If you think regulator thansition latency is board specific, then the board dts can overrite it.
We should just query this information from the regulator subsystem (there's hooks but currently nothing implements them). The regulators can define their own bindings if they need to read it from device tree, most of them should be able to do this as a function of knowing about the device. None of this is specific to cpufreq so cpufreq shouldn't have to define its own support for this.
I'd like to query the latency by call clk and regulator APIs. but as you said both of them have not implemented it yet. I think, for now, we can use the
The *call* is there in the regulator subsystem, it's just that none of the drivers back it up with an actual implementation yet. Which turns out to be a good thing as cpufreq can't currently understand variable latencies and the governors don't deal well with non-trivial latencies anyway.
property to get the total latency. Once I can get it at runtime, I'll remove it. So the definition of trans-latency is just the same as cpufreq transition_latency, people get less confused. What do you think?
The problem with device tree is that once you've defined a binding you're stuck with it, it's very hard to change - witness all the magic number based stuff with the interrupt bindings for example
On Mon, Dec 26, 2011 at 11:10:30AM +0000, Mark Brown wrote:
On Sat, Dec 24, 2011 at 11:52:29PM +0800, Richard Zhao wrote:
On Sat, Dec 24, 2011 at 01:42:29PM +0000, Mark Brown wrote:
On Sat, Dec 24, 2011 at 09:28:33PM +0800, Richard Zhao wrote:
If you think regulator thansition latency is board specific, then the board dts can overrite it.
We should just query this information from the regulator subsystem (there's hooks but currently nothing implements them). The regulators can define their own bindings if they need to read it from device tree, most of them should be able to do this as a function of knowing about the device. None of this is specific to cpufreq so cpufreq shouldn't have to define its own support for this.
I'd like to query the latency by call clk and regulator APIs. but as you said both of them have not implemented it yet. I think, for now, we can use the
The *call* is there in the regulator subsystem, it's just that none of the drivers back it up with an actual implementation yet. Which turns out to be a good thing as cpufreq can't currently understand variable latencies and the governors don't deal well with non-trivial latencies anyway.
but clk API don't have such calls. and many SoCs only adjust clk frequencies, using one single voltage.
property to get the total latency. Once I can get it at runtime, I'll remove it. So the definition of trans-latency is just the same as cpufreq transition_latency, people get less confused. What do you think?
The problem with device tree is that once you've defined a binding you're stuck with it, it's very hard to change - witness all the magic number based stuff with the interrupt bindings for example
So what's your suggestion? We can not set transition_latency to set random number.
Thanks Richard
On Mon, Dec 26, 2011 at 09:44:52PM +0800, Richard Zhao wrote:
On Mon, Dec 26, 2011 at 11:10:30AM +0000, Mark Brown wrote:
Fix your mailer to word wrap properly please.
The *call* is there in the regulator subsystem, it's just that none of the drivers back it up with an actual implementation yet. Which turns out to be a good thing as cpufreq can't currently understand variable latencies and the governors don't deal well with non-trivial latencies anyway.
but clk API don't have such calls. and many SoCs only adjust clk frequencies, using one single voltage.
I've not suggested doing this in the clock API, only for the regulator. For the clocks it's less clear that it's useful as you don't have the bulk operations and it's much rarer to need them.
The problem with device tree is that once you've defined a binding you're stuck with it, it's very hard to change - witness all the magic number based stuff with the interrupt bindings for example
So what's your suggestion? We can not set transition_latency to set random number.
As I've repeatedly said I think you should define it to be the latency for the SoC only, not for the regulators.
On Mon, Dec 26, 2011 at 02:22:34PM +0000, Mark Brown wrote:
On Mon, Dec 26, 2011 at 09:44:52PM +0800, Richard Zhao wrote:
On Mon, Dec 26, 2011 at 11:10:30AM +0000, Mark Brown wrote:
Fix your mailer to word wrap properly please.
If you mean last mail I sent, I didn't see anything wrong. I use mutt.
The *call* is there in the regulator subsystem, it's just that none of the drivers back it up with an actual implementation yet. Which turns out to be a good thing as cpufreq can't currently understand variable latencies and the governors don't deal well with non-trivial latencies anyway.
but clk API don't have such calls. and many SoCs only adjust clk frequencies, using one single voltage.
I've not suggested doing this in the clock API, only for the regulator. For the clocks it's less clear that it's useful as you don't have the bulk operations and it's much rarer to need them.
The problem with device tree is that once you've defined a binding you're stuck with it, it's very hard to change - witness all the magic number based stuff with the interrupt bindings for example
So what's your suggestion? We can not set transition_latency to set random number.
As I've repeatedly said I think you should define it to be the latency for the SoC only, not for the regulators.
Sometimes, regulators are in SoC too. To avoid confusion, I'll use below: clk-trans-latency = <61036>;
Thanks Richard
linux-arm-kernel mailing list linux-arm-kernel@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
On Tue, Dec 27, 2011 at 09:51:10AM +0800, Richard Zhao wrote:
On Mon, Dec 26, 2011 at 02:22:34PM +0000, Mark Brown wrote:
Fix your mailer to word wrap properly please.
If you mean last mail I sent, I didn't see anything wrong. I use mutt.
It's wrapping at a bit more than 80 columns a lot of the time.
So what's your suggestion? We can not set transition_latency to set random number.
As I've repeatedly said I think you should define it to be the latency for the SoC only, not for the regulators.
Sometimes, regulators are in SoC too. To avoid confusion, I'll use below: clk-trans-latency = <61036>;
Makes sense.
On Mon, Dec 26, 2011 at 09:44:52PM +0800, Richard Zhao wrote:
On Mon, Dec 26, 2011 at 11:10:30AM +0000, Mark Brown wrote:
The *call* is there in the regulator subsystem, it's just that none of the drivers back it up with an actual implementation yet. Which turns out to be a good thing as cpufreq can't currently understand variable latencies and the governors don't deal well with non-trivial latencies anyway.
but clk API don't have such calls. and many SoCs only adjust clk frequencies, using one single voltage.
That's because it's often not known - especially in the case of PLLs, data sheets don't tend to specify how long it takes for the PLL to relock after a requested change. If it's important that the PLL be locked, there will be a bit to poll (or they'll cause the CPU itself to stall while the PLL is not locked.)
So, for these kinds of situations, how do you suggest that the clk API provides this information?
Hi Russel,
On 3 January 2012 17:06, Russell King - ARM Linux linux@arm.linux.org.uk wrote:
On Mon, Dec 26, 2011 at 09:44:52PM +0800, Richard Zhao wrote:
On Mon, Dec 26, 2011 at 11:10:30AM +0000, Mark Brown wrote:
The *call* is there in the regulator subsystem, it's just that none of the drivers back it up with an actual implementation yet. Which turns out to be a good thing as cpufreq can't currently understand variable latencies and the governors don't deal well with non-trivial latencies anyway.
but clk API don't have such calls. and many SoCs only adjust clk frequencies, using one single voltage.
That's because it's often not known - especially in the case of PLLs, data sheets don't tend to specify how long it takes for the PLL to relock after a requested change. If it's important that the PLL be locked, there will be a bit to poll (or they'll cause the CPU itself to stall while the PLL is not locked.)
So, for these kinds of situations, how do you suggest that the clk API provides this information?
In latest v6 version, I get clk transition latency from dt property, and get regulator transition latency from regulator API. Could you please help review other arm common changes in v6 version?
Thanks Richard
On Tue, Jan 03, 2012 at 09:25:30PM +0800, Richard Zhao wrote:
Hi Russel,
On 3 January 2012 17:06, Russell King - ARM Linux linux@arm.linux.org.uk wrote:
On Mon, Dec 26, 2011 at 09:44:52PM +0800, Richard Zhao wrote:
On Mon, Dec 26, 2011 at 11:10:30AM +0000, Mark Brown wrote:
The *call* is there in the regulator subsystem, it's just that none of the drivers back it up with an actual implementation yet. Which turns out to be a good thing as cpufreq can't currently understand variable latencies and the governors don't deal well with non-trivial latencies anyway.
but clk API don't have such calls. and many SoCs only adjust clk frequencies, using one single voltage.
That's because it's often not known - especially in the case of PLLs, data sheets don't tend to specify how long it takes for the PLL to relock after a requested change. If it's important that the PLL be locked, there will be a bit to poll (or they'll cause the CPU itself to stall while the PLL is not locked.)
So, for these kinds of situations, how do you suggest that the clk API provides this information?
In latest v6 version, I get clk transition latency from dt property, and get regulator transition latency from regulator API. Could you please help review other arm common changes in v6 version?
You didn't get my point: how do you specify a clock transition latency for a clock with a PLL when the data sheets don't tell you what that is, and they instead give you a bit to poll?
Do you:
(a) make up some number and hope that it's representative (b) not specify any transition latency (c) think about the problem _now_ and define what it means for a clock without a transition latency.
On 3 January 2012 21:47, Russell King - ARM Linux linux@arm.linux.org.uk wrote:
On Tue, Jan 03, 2012 at 09:25:30PM +0800, Richard Zhao wrote:
Hi Russel,
On 3 January 2012 17:06, Russell King - ARM Linux linux@arm.linux.org.uk wrote:
On Mon, Dec 26, 2011 at 09:44:52PM +0800, Richard Zhao wrote:
On Mon, Dec 26, 2011 at 11:10:30AM +0000, Mark Brown wrote:
The *call* is there in the regulator subsystem, it's just that none of the drivers back it up with an actual implementation yet. Which turns out to be a good thing as cpufreq can't currently understand variable latencies and the governors don't deal well with non-trivial latencies anyway.
but clk API don't have such calls. and many SoCs only adjust clk frequencies, using one single voltage.
That's because it's often not known - especially in the case of PLLs, data sheets don't tend to specify how long it takes for the PLL to relock after a requested change. If it's important that the PLL be locked, there will be a bit to poll (or they'll cause the CPU itself to stall while the PLL is not locked.)
So, for these kinds of situations, how do you suggest that the clk API provides this information?
In latest v6 version, I get clk transition latency from dt property, and get regulator transition latency from regulator API. Could you please help review other arm common changes in v6 version?
You didn't get my point: how do you specify a clock transition latency for a clock with a PLL when the data sheets don't tell you what that is, and they instead give you a bit to poll?
clk-trans-latency is not a MUST property. If the datasheet don't provide it or you don't want to measure, you may not specify the property. In such case cpufreq governor ondemand and conservative will not work, because they can not determine the sample rate without the latency value.
Do you:
(a) make up some number and hope that it's representative
No, I always get it from dt.
(b) not specify any transition latency
It's allowed.
(c) think about the problem _now_ and define what it means for a clock without a transition latency.
It's ondemand/conservative governors that strongly need the latency value. If user don't want to use it, it's fine.
Thanks Richard
On Tue, Jan 03, 2012 at 01:47:09PM +0000, Russell King - ARM Linux wrote:
On Tue, Jan 03, 2012 at 09:25:30PM +0800, Richard Zhao wrote:
In latest v6 version, I get clk transition latency from dt property, and get regulator transition latency from regulator API. Could you please help review other arm common changes in v6 version?
You didn't get my point: how do you specify a clock transition latency for a clock with a PLL when the data sheets don't tell you what that is, and they instead give you a bit to poll?
I'd rather suspect you'll find there are actually specs for these things but they're hidden in the electrical characteristics which don't tend to go into the published datasheets (though ). Not useful if we don't actually see them though.
Do you:
(a) make up some number and hope that it's representative (b) not specify any transition latency
These are the traditional approaches, pioneered by essentially every existing cpufreq driver (well, "make up" is a bit harsh - usually the numbers are measured, though possibly only on a limited set of systems).
(c) think about the problem _now_ and define what it means for a clock without a transition latency.
This would be nice, in both the clock and cpufreq code (the cpufreq code is pretty limited here in that it assumes an equal transition latency for all transitions which isn't the case usually). You can generally do something useful with measurement, probably we can arrange to measure the times we're seeing on the actua system or something.
Hi Richard,
This is looking really nice. A couple of really minor nits inline, otherwise:
Reviewed-by: Jamie Iles jamie@jamieiles.com
On Thu, Dec 22, 2011 at 03:09:10PM +0800, Richard Zhao wrote:
The driver get cpu operation point table from device tree cpu0 node, and adjusts operating points using clk and regulator APIs.
It support single core and multi-core ARM SoCs. But currently it assume all cores share the same frequency and voltage.
Signed-off-by: Richard Zhao richard.zhao@linaro.org
.../devicetree/bindings/cpufreq/clk-reg-cpufreq | 7 + drivers/cpufreq/Kconfig | 10 + drivers/cpufreq/Makefile | 2 + drivers/cpufreq/clk-reg-cpufreq.c | 289 ++++++++++++++++++++ 4 files changed, 308 insertions(+), 0 deletions(-) create mode 100644 Documentation/devicetree/bindings/cpufreq/clk-reg-cpufreq create mode 100644 drivers/cpufreq/clk-reg-cpufreq.c
diff --git a/Documentation/devicetree/bindings/cpufreq/clk-reg-cpufreq b/Documentation/devicetree/bindings/cpufreq/clk-reg-cpufreq new file mode 100644 index 0000000..bf07c1b --- /dev/null +++ b/Documentation/devicetree/bindings/cpufreq/clk-reg-cpufreq @@ -0,0 +1,7 @@ +Generic cpufreq driver based on clk and regulator APIs
+Required properties in /cpus/cpu@0: +- cpu-freqs : cpu frequency points it support, in unit of Hz. +- cpu-volts : cpu voltages required by the frequency point at the same index,
in unit of uV.
Perhaps clarify here that these are arrays of u32's? It may be worth giving an example node just to be clear?
+- trans-latency : transition_latency, in unit of ns. diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig index e24a2a1..95470f1 100644 --- a/drivers/cpufreq/Kconfig +++ b/drivers/cpufreq/Kconfig @@ -179,6 +179,16 @@ config CPU_FREQ_GOV_CONSERVATIVE If in doubt, say N. +config CLK_REG_CPUFREQ_DRIVER
- tristate "Generic cpufreq driver using clk and regulator APIs"
- depends on HAVE_CLK && OF && REGULATOR
- select CPU_FREQ_TABLE
- help
This adds generic CPUFreq driver based on clk and regulator APIs.
It assumes all cores of the CPU share the same clock and voltage.
If in doubt, say N.
menu "x86 CPU frequency scaling drivers" depends on X86 source "drivers/cpufreq/Kconfig.x86" diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index ce75fcb..2c4eb33 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -13,6 +13,8 @@ obj-$(CONFIG_CPU_FREQ_GOV_CONSERVATIVE) += cpufreq_conservative.o # CPUfreq cross-arch helpers obj-$(CONFIG_CPU_FREQ_TABLE) += freq_table.o +obj-$(CONFIG_CLK_REG_CPUFREQ_DRIVER) += clk-reg-cpufreq.o
################################################################################## # x86 drivers. # Link order matters. K8 is preferred to ACPI because of firmware bugs in early diff --git a/drivers/cpufreq/clk-reg-cpufreq.c b/drivers/cpufreq/clk-reg-cpufreq.c new file mode 100644 index 0000000..c30d2c5 --- /dev/null +++ b/drivers/cpufreq/clk-reg-cpufreq.c @@ -0,0 +1,289 @@ +/*
- Copyright (C) 2011 Freescale Semiconductor, Inc.
- */
+/*
- The code contained herein is licensed under the GNU General Public
- License. You may obtain a copy of the GNU General Public License
- Version 2 or later at the following locations:
- */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/module.h> +#include <linux/cpufreq.h> +#include <linux/clk.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/of.h>
+static u32 *cpu_freqs; /* Hz */ +static u32 *cpu_volts; /* uV */ +static u32 trans_latency; /* ns */ +static int cpu_op_nr; +static unsigned int cur_index;
+static struct clk *cpu_clk; +static struct regulator *cpu_reg; +static struct cpufreq_frequency_table *freq_table;
+static int set_cpu_freq(unsigned long freq, int index, int higher) +{
- int ret = 0;
- if (higher && cpu_reg) {
ret = regulator_set_voltage(cpu_reg,
cpu_volts[index * 2], cpu_volts[index * 2 + 1]);
if (ret) {
pr_err("set cpu voltage failed!\n");
return ret;
}
- }
- ret = clk_set_rate(cpu_clk, freq);
- if (ret) {
if (cpu_reg)
regulator_set_voltage(cpu_reg, cpu_volts[cur_index * 2],
cpu_volts[cur_index * 2 + 1]);
pr_err("cannot set CPU clock rate\n");
return ret;
- }
- if (!higher && cpu_reg) {
ret = regulator_set_voltage(cpu_reg,
cpu_volts[index * 2], cpu_volts[index * 2 + 1]);
if (ret)
pr_warn("set cpu voltage failed, might run on"
" higher voltage!\n");
ret = 0;
- }
- return ret;
+}
+static int clk_reg_verify_speed(struct cpufreq_policy *policy) +{
- return cpufreq_frequency_table_verify(policy, freq_table);
+}
+static unsigned int clk_reg_get_speed(unsigned int cpu) +{
- return clk_get_rate(cpu_clk) / 1000;
+}
+static int clk_reg_set_target(struct cpufreq_policy *policy,
unsigned int target_freq, unsigned int relation)
+{
- struct cpufreq_freqs freqs;
- unsigned long freq_Hz;
- int cpu;
- int ret = 0;
- unsigned int index;
- cpufreq_frequency_table_target(policy, freq_table,
target_freq, relation, &index);
- freq_Hz = clk_round_rate(cpu_clk, cpu_freqs[index]);
- freq_Hz = freq_Hz ? freq_Hz : cpu_freqs[index];
- freqs.old = clk_get_rate(cpu_clk) / 1000;
- freqs.new = freq_Hz / 1000;
- freqs.flags = 0;
- if (freqs.old == freqs.new)
return 0;
- for_each_possible_cpu(cpu) {
freqs.cpu = cpu;
cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
- }
- ret = set_cpu_freq(freq_Hz, index, (freqs.new > freqs.old));
- if (ret)
freqs.new = clk_get_rate(cpu_clk) / 1000;
- else
cur_index = index;
- for_each_possible_cpu(cpu) {
freqs.cpu = cpu;
cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
- }
- return ret;
+}
+static int clk_reg_cpufreq_init(struct cpufreq_policy *policy) +{
- int ret;
- if (policy->cpu >= num_possible_cpus())
return -EINVAL;
- policy->cur = clk_get_rate(cpu_clk) / 1000;
- policy->shared_type = CPUFREQ_SHARED_TYPE_ANY;
- cpumask_setall(policy->cpus);
- policy->cpuinfo.transition_latency = trans_latency;
- ret = cpufreq_frequency_table_cpuinfo(policy, freq_table);
- if (ret < 0) {
pr_err("invalid frequency table for cpu %d\n",
policy->cpu);
return ret;
- }
- cpufreq_frequency_table_get_attr(freq_table, policy->cpu);
- cpufreq_frequency_table_target(policy, freq_table, policy->cur,
CPUFREQ_RELATION_H, &cur_index);
- return 0;
+}
+static int clk_reg_cpufreq_exit(struct cpufreq_policy *policy) +{
- cpufreq_frequency_table_put_attr(policy->cpu);
- return 0;
+}
+static struct cpufreq_driver clk_reg_cpufreq_driver = {
- .flags = CPUFREQ_STICKY,
- .verify = clk_reg_verify_speed,
- .target = clk_reg_set_target,
- .get = clk_reg_get_speed,
- .init = clk_reg_cpufreq_init,
- .exit = clk_reg_cpufreq_exit,
- .name = "clk-reg",
+};
Drop the extra newline?
+static u32 max_freq = UINT_MAX / 1000; /* kHz */ +module_param(max_freq, uint, 0); +MODULE_PARM_DESC(max_freq, "max cpu frequency in unit of kHz");
+static int __devinit clk_reg_cpufreq_driver_init(void) +{
- struct device_node *cpu0;
- const struct property *pp;
- int i, ret;
- cpu0 = of_find_node_by_path("/cpus/cpu@0");
- if (!cpu0)
return -ENODEV;
- pp = of_find_property(cpu0, "cpu-freqs", NULL);
- if (!pp) {
ret = -ENODEV;
goto put_node;
- }
- cpu_op_nr = pp->length / sizeof(u32);
- if (!cpu_op_nr) {
ret = -ENODEV;
goto put_node;
- }
- ret = -ENOMEM;
- cpu_freqs = kzalloc(sizeof(*cpu_freqs) * cpu_op_nr, GFP_KERNEL);
- if (!cpu_freqs)
goto put_node;
- of_property_read_u32_array(cpu0, "cpu-freqs", cpu_freqs, cpu_op_nr);
- pp = of_find_property(cpu0, "cpu-volts", NULL);
- if (pp) {
if (cpu_op_nr * 2 == pp->length / sizeof(u32)) {
cpu_volts = kzalloc(sizeof(*cpu_volts) * cpu_op_nr * 2,
GFP_KERNEL);
if (!cpu_volts)
goto free_cpu_freqs;
of_property_read_u32_array(cpu0, "cpu-volts",
cpu_volts, cpu_op_nr * 2);
} else
pr_warn("invalid cpu_volts!\n");
- }
- if (of_property_read_u32(cpu0, "trans-latency", &trans_latency))
trans_latency = CPUFREQ_ETERNAL;
- cpu_clk = clk_get(NULL, "cpu");
- if (IS_ERR(cpu_clk)) {
pr_err("failed to get cpu clock\n");
ret = PTR_ERR(cpu_clk);
goto free_cpu_volts;
- }
- if (cpu_volts) {
cpu_reg = regulator_get(NULL, "cpu");
if (IS_ERR(cpu_reg)) {
pr_warn("regulator cpu get failed.\n");
cpu_reg = NULL;
}
- }
- freq_table = kmalloc(sizeof(struct cpufreq_frequency_table)
* (cpu_op_nr + 1), GFP_KERNEL);
- if (!freq_table) {
ret = -ENOMEM;
goto reg_put;
- }
- for (i = 0; i < cpu_op_nr; i++) {
freq_table[i].index = i;
if (cpu_freqs[i] > max_freq * 1000) {
freq_table[i].frequency = CPUFREQ_ENTRY_INVALID;
continue;
}
if (cpu_reg) {
ret = regulator_is_supported_voltage(cpu_reg,
cpu_volts[i * 2], cpu_volts[i * 2 + 1]);
if (ret <= 0) {
freq_table[i].frequency = CPUFREQ_ENTRY_INVALID;
continue;
}
}
freq_table[i].frequency = cpu_freqs[i] / 1000;
- }
- freq_table[i].index = i;
- freq_table[i].frequency = CPUFREQ_TABLE_END;
- ret = cpufreq_register_driver(&clk_reg_cpufreq_driver);
- if (ret)
goto free_freq_table;
- of_node_put(cpu0);
- return 0;
+free_freq_table:
- kfree(freq_table);
+reg_put:
- if (cpu_reg)
regulator_put(cpu_reg);
- clk_put(cpu_clk);
+free_cpu_volts:
- kfree(cpu_volts);
+free_cpu_freqs:
- kfree(cpu_freqs);
+put_node:
- of_node_put(cpu0);
- return ret;
+}
+static void clk_reg_cpufreq_driver_exit(void) +{
- cpufreq_unregister_driver(&clk_reg_cpufreq_driver);
- kfree(cpu_freqs);
- kfree(cpu_volts);
- clk_put(cpu_clk);
- if (cpu_reg)
regulator_put(cpu_reg);
- kfree(freq_table);
+}
+module_init(clk_reg_cpufreq_driver_init); +module_exit(clk_reg_cpufreq_driver_exit);
+MODULE_AUTHOR("Freescale Semiconductor Inc. Richard Zhao richard.zhao@freescale.com"); +MODULE_DESCRIPTION("Generic CPUFreq driver based on clk and regulator APIs");
+MODULE_LICENSE("GPL");
1.7.5.4
On Sat, Dec 24, 2011 at 01:10:40PM +0000, Jamie Iles wrote:
Hi Richard,
This is looking really nice. A couple of really minor nits inline, otherwise:
Reviewed-by: Jamie Iles jamie@jamieiles.com
Thanks.
On Thu, Dec 22, 2011 at 03:09:10PM +0800, Richard Zhao wrote:
The driver get cpu operation point table from device tree cpu0 node, and adjusts operating points using clk and regulator APIs.
It support single core and multi-core ARM SoCs. But currently it assume all cores share the same frequency and voltage.
Signed-off-by: Richard Zhao richard.zhao@linaro.org
.../devicetree/bindings/cpufreq/clk-reg-cpufreq | 7 + drivers/cpufreq/Kconfig | 10 + drivers/cpufreq/Makefile | 2 + drivers/cpufreq/clk-reg-cpufreq.c | 289 ++++++++++++++++++++ 4 files changed, 308 insertions(+), 0 deletions(-) create mode 100644 Documentation/devicetree/bindings/cpufreq/clk-reg-cpufreq create mode 100644 drivers/cpufreq/clk-reg-cpufreq.c
diff --git a/Documentation/devicetree/bindings/cpufreq/clk-reg-cpufreq b/Documentation/devicetree/bindings/cpufreq/clk-reg-cpufreq new file mode 100644 index 0000000..bf07c1b --- /dev/null +++ b/Documentation/devicetree/bindings/cpufreq/clk-reg-cpufreq @@ -0,0 +1,7 @@ +Generic cpufreq driver based on clk and regulator APIs
+Required properties in /cpus/cpu@0: +- cpu-freqs : cpu frequency points it support, in unit of Hz. +- cpu-volts : cpu voltages required by the frequency point at the same index,
in unit of uV.
Perhaps clarify here that these are arrays of u32's?
I'm not sure we can use u32 here, because dts always use xxx-cells to describe the length.
It may be worth giving an example node just to be clear?
I'll give an example.
+- trans-latency : transition_latency, in unit of ns. diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig index e24a2a1..95470f1 100644 --- a/drivers/cpufreq/Kconfig +++ b/drivers/cpufreq/Kconfig @@ -179,6 +179,16 @@ config CPU_FREQ_GOV_CONSERVATIVE If in doubt, say N. +config CLK_REG_CPUFREQ_DRIVER
- tristate "Generic cpufreq driver using clk and regulator APIs"
- depends on HAVE_CLK && OF && REGULATOR
- select CPU_FREQ_TABLE
- help
This adds generic CPUFreq driver based on clk and regulator APIs.
It assumes all cores of the CPU share the same clock and voltage.
If in doubt, say N.
menu "x86 CPU frequency scaling drivers" depends on X86 source "drivers/cpufreq/Kconfig.x86" diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index ce75fcb..2c4eb33 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -13,6 +13,8 @@ obj-$(CONFIG_CPU_FREQ_GOV_CONSERVATIVE) += cpufreq_conservative.o # CPUfreq cross-arch helpers obj-$(CONFIG_CPU_FREQ_TABLE) += freq_table.o +obj-$(CONFIG_CLK_REG_CPUFREQ_DRIVER) += clk-reg-cpufreq.o
################################################################################## # x86 drivers. # Link order matters. K8 is preferred to ACPI because of firmware bugs in early diff --git a/drivers/cpufreq/clk-reg-cpufreq.c b/drivers/cpufreq/clk-reg-cpufreq.c new file mode 100644 index 0000000..c30d2c5 --- /dev/null +++ b/drivers/cpufreq/clk-reg-cpufreq.c @@ -0,0 +1,289 @@ +/*
- Copyright (C) 2011 Freescale Semiconductor, Inc.
- */
+/*
- The code contained herein is licensed under the GNU General Public
- License. You may obtain a copy of the GNU General Public License
- Version 2 or later at the following locations:
- */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/module.h> +#include <linux/cpufreq.h> +#include <linux/clk.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/of.h>
+static u32 *cpu_freqs; /* Hz */ +static u32 *cpu_volts; /* uV */ +static u32 trans_latency; /* ns */ +static int cpu_op_nr; +static unsigned int cur_index;
+static struct clk *cpu_clk; +static struct regulator *cpu_reg; +static struct cpufreq_frequency_table *freq_table;
+static int set_cpu_freq(unsigned long freq, int index, int higher) +{
- int ret = 0;
- if (higher && cpu_reg) {
ret = regulator_set_voltage(cpu_reg,
cpu_volts[index * 2], cpu_volts[index * 2 + 1]);
if (ret) {
pr_err("set cpu voltage failed!\n");
return ret;
}
- }
- ret = clk_set_rate(cpu_clk, freq);
- if (ret) {
if (cpu_reg)
regulator_set_voltage(cpu_reg, cpu_volts[cur_index * 2],
cpu_volts[cur_index * 2 + 1]);
pr_err("cannot set CPU clock rate\n");
return ret;
- }
- if (!higher && cpu_reg) {
ret = regulator_set_voltage(cpu_reg,
cpu_volts[index * 2], cpu_volts[index * 2 + 1]);
if (ret)
pr_warn("set cpu voltage failed, might run on"
" higher voltage!\n");
ret = 0;
- }
- return ret;
+}
+static int clk_reg_verify_speed(struct cpufreq_policy *policy) +{
- return cpufreq_frequency_table_verify(policy, freq_table);
+}
+static unsigned int clk_reg_get_speed(unsigned int cpu) +{
- return clk_get_rate(cpu_clk) / 1000;
+}
+static int clk_reg_set_target(struct cpufreq_policy *policy,
unsigned int target_freq, unsigned int relation)
+{
- struct cpufreq_freqs freqs;
- unsigned long freq_Hz;
- int cpu;
- int ret = 0;
- unsigned int index;
- cpufreq_frequency_table_target(policy, freq_table,
target_freq, relation, &index);
- freq_Hz = clk_round_rate(cpu_clk, cpu_freqs[index]);
- freq_Hz = freq_Hz ? freq_Hz : cpu_freqs[index];
- freqs.old = clk_get_rate(cpu_clk) / 1000;
- freqs.new = freq_Hz / 1000;
- freqs.flags = 0;
- if (freqs.old == freqs.new)
return 0;
- for_each_possible_cpu(cpu) {
freqs.cpu = cpu;
cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
- }
- ret = set_cpu_freq(freq_Hz, index, (freqs.new > freqs.old));
- if (ret)
freqs.new = clk_get_rate(cpu_clk) / 1000;
- else
cur_index = index;
- for_each_possible_cpu(cpu) {
freqs.cpu = cpu;
cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
- }
- return ret;
+}
+static int clk_reg_cpufreq_init(struct cpufreq_policy *policy) +{
- int ret;
- if (policy->cpu >= num_possible_cpus())
return -EINVAL;
- policy->cur = clk_get_rate(cpu_clk) / 1000;
- policy->shared_type = CPUFREQ_SHARED_TYPE_ANY;
- cpumask_setall(policy->cpus);
- policy->cpuinfo.transition_latency = trans_latency;
- ret = cpufreq_frequency_table_cpuinfo(policy, freq_table);
- if (ret < 0) {
pr_err("invalid frequency table for cpu %d\n",
policy->cpu);
return ret;
- }
- cpufreq_frequency_table_get_attr(freq_table, policy->cpu);
- cpufreq_frequency_table_target(policy, freq_table, policy->cur,
CPUFREQ_RELATION_H, &cur_index);
- return 0;
+}
+static int clk_reg_cpufreq_exit(struct cpufreq_policy *policy) +{
- cpufreq_frequency_table_put_attr(policy->cpu);
- return 0;
+}
+static struct cpufreq_driver clk_reg_cpufreq_driver = {
- .flags = CPUFREQ_STICKY,
- .verify = clk_reg_verify_speed,
- .target = clk_reg_set_target,
- .get = clk_reg_get_speed,
- .init = clk_reg_cpufreq_init,
- .exit = clk_reg_cpufreq_exit,
- .name = "clk-reg",
+};
Drop the extra newline?
Thanks.
Richard
+static u32 max_freq = UINT_MAX / 1000; /* kHz */ +module_param(max_freq, uint, 0); +MODULE_PARM_DESC(max_freq, "max cpu frequency in unit of kHz");
+static int __devinit clk_reg_cpufreq_driver_init(void) +{
- struct device_node *cpu0;
- const struct property *pp;
- int i, ret;
- cpu0 = of_find_node_by_path("/cpus/cpu@0");
- if (!cpu0)
return -ENODEV;
- pp = of_find_property(cpu0, "cpu-freqs", NULL);
- if (!pp) {
ret = -ENODEV;
goto put_node;
- }
- cpu_op_nr = pp->length / sizeof(u32);
- if (!cpu_op_nr) {
ret = -ENODEV;
goto put_node;
- }
- ret = -ENOMEM;
- cpu_freqs = kzalloc(sizeof(*cpu_freqs) * cpu_op_nr, GFP_KERNEL);
- if (!cpu_freqs)
goto put_node;
- of_property_read_u32_array(cpu0, "cpu-freqs", cpu_freqs, cpu_op_nr);
- pp = of_find_property(cpu0, "cpu-volts", NULL);
- if (pp) {
if (cpu_op_nr * 2 == pp->length / sizeof(u32)) {
cpu_volts = kzalloc(sizeof(*cpu_volts) * cpu_op_nr * 2,
GFP_KERNEL);
if (!cpu_volts)
goto free_cpu_freqs;
of_property_read_u32_array(cpu0, "cpu-volts",
cpu_volts, cpu_op_nr * 2);
} else
pr_warn("invalid cpu_volts!\n");
- }
- if (of_property_read_u32(cpu0, "trans-latency", &trans_latency))
trans_latency = CPUFREQ_ETERNAL;
- cpu_clk = clk_get(NULL, "cpu");
- if (IS_ERR(cpu_clk)) {
pr_err("failed to get cpu clock\n");
ret = PTR_ERR(cpu_clk);
goto free_cpu_volts;
- }
- if (cpu_volts) {
cpu_reg = regulator_get(NULL, "cpu");
if (IS_ERR(cpu_reg)) {
pr_warn("regulator cpu get failed.\n");
cpu_reg = NULL;
}
- }
- freq_table = kmalloc(sizeof(struct cpufreq_frequency_table)
* (cpu_op_nr + 1), GFP_KERNEL);
- if (!freq_table) {
ret = -ENOMEM;
goto reg_put;
- }
- for (i = 0; i < cpu_op_nr; i++) {
freq_table[i].index = i;
if (cpu_freqs[i] > max_freq * 1000) {
freq_table[i].frequency = CPUFREQ_ENTRY_INVALID;
continue;
}
if (cpu_reg) {
ret = regulator_is_supported_voltage(cpu_reg,
cpu_volts[i * 2], cpu_volts[i * 2 + 1]);
if (ret <= 0) {
freq_table[i].frequency = CPUFREQ_ENTRY_INVALID;
continue;
}
}
freq_table[i].frequency = cpu_freqs[i] / 1000;
- }
- freq_table[i].index = i;
- freq_table[i].frequency = CPUFREQ_TABLE_END;
- ret = cpufreq_register_driver(&clk_reg_cpufreq_driver);
- if (ret)
goto free_freq_table;
- of_node_put(cpu0);
- return 0;
+free_freq_table:
- kfree(freq_table);
+reg_put:
- if (cpu_reg)
regulator_put(cpu_reg);
- clk_put(cpu_clk);
+free_cpu_volts:
- kfree(cpu_volts);
+free_cpu_freqs:
- kfree(cpu_freqs);
+put_node:
- of_node_put(cpu0);
- return ret;
+}
+static void clk_reg_cpufreq_driver_exit(void) +{
- cpufreq_unregister_driver(&clk_reg_cpufreq_driver);
- kfree(cpu_freqs);
- kfree(cpu_volts);
- clk_put(cpu_clk);
- if (cpu_reg)
regulator_put(cpu_reg);
- kfree(freq_table);
+}
+module_init(clk_reg_cpufreq_driver_init); +module_exit(clk_reg_cpufreq_driver_exit);
+MODULE_AUTHOR("Freescale Semiconductor Inc. Richard Zhao richard.zhao@freescale.com"); +MODULE_DESCRIPTION("Generic CPUFreq driver based on clk and regulator APIs");
+MODULE_LICENSE("GPL");
1.7.5.4
Signed-off-by: Richard Zhao richard.zhao@linaro.org --- arch/arm/boot/dts/imx6q.dtsi | 7 +++++++ 1 files changed, 7 insertions(+), 0 deletions(-)
diff --git a/arch/arm/boot/dts/imx6q.dtsi b/arch/arm/boot/dts/imx6q.dtsi index 263e8f3..2087db7 100644 --- a/arch/arm/boot/dts/imx6q.dtsi +++ b/arch/arm/boot/dts/imx6q.dtsi @@ -29,6 +29,13 @@ compatible = "arm,cortex-a9"; reg = <0>; next-level-cache = <&L2>; + cpu-freqs = <996000000 792000000 396000000 198000000>; + cpu-volts = < /* min max */ + 1225000 1450000 /* 996M */ + 1100000 1450000 /* 792M */ + 950000 1450000 /* 396M */ + 850000 1450000>; /* 198M */ + trans-latency = <61036>; /* two CLK32 periods */ };
cpu@1 {
cpufreq needs cpu clock to change frequency.
Signed-off-by: Richard Zhao richard.zhao@linaro.org --- arch/arm/mach-imx/clock-imx6q.c | 1 + 1 files changed, 1 insertions(+), 0 deletions(-)
diff --git a/arch/arm/mach-imx/clock-imx6q.c b/arch/arm/mach-imx/clock-imx6q.c index 039a7ab..72acbc2 100644 --- a/arch/arm/mach-imx/clock-imx6q.c +++ b/arch/arm/mach-imx/clock-imx6q.c @@ -1911,6 +1911,7 @@ static struct clk_lookup lookups[] = { _REGISTER_CLOCK(NULL, "gpmi_io_clk", gpmi_io_clk), _REGISTER_CLOCK(NULL, "usboh3_clk", usboh3_clk), _REGISTER_CLOCK(NULL, "sata_clk", sata_clk), + _REGISTER_CLOCK(NULL, "cpu", arm_clk), };
int imx6q_set_lpm(enum mxc_cpu_pwr_mode mode)
Signed-off-by: Richard Zhao richard.zhao@linaro.org --- arch/arm/mach-imx/Kconfig | 1 + 1 files changed, 1 insertions(+), 0 deletions(-)
diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig index c44aa97..39cf00a 100644 --- a/arch/arm/mach-imx/Kconfig +++ b/arch/arm/mach-imx/Kconfig @@ -595,6 +595,7 @@ comment "i.MX6 family:"
config SOC_IMX6Q bool "i.MX6 Quad support" + select ARCH_HAS_CPUFREQ select ARM_GIC select CACHE_L2X0 select CPU_V7