It adds a generic cpufreq driver for CPU0 frequency management based on clk, regulator, OPP and device tree support. It can support both uniprocessor (UP) and those symmetric multiprocessor (SMP) systems which share clock and voltage across all CPUs.
Signed-off-by: Shawn Guo <shawn....@linaro.org> --- .../devicetree/bindings/cpufreq/cpufreq-cpu0.txt | 58 +++++ drivers/cpufreq/Kconfig | 11 + drivers/cpufreq/Makefile | 2 + drivers/cpufreq/cpufreq-cpu0.c | 235 ++++++++++++++++++++ 4 files changed, 306 insertions(+), 0 deletions(-) create mode 100644 Documentation/devicetree/bindings/cpufreq/cpufreq-cpu0.txt create mode 100644 drivers/cpufreq/cpufreq-cpu0.c diff --git a/Documentation/devicetree/bindings/cpufreq/cpufreq-cpu0.txt b/Documentation/devicetree/bindings/cpufreq/cpufreq-cpu0.txt new file mode 100644 index 0000000..94799bc --- /dev/null +++ b/Documentation/devicetree/bindings/cpufreq/cpufreq-cpu0.txt @@ -0,0 +1,58 @@ +Generic cpufreq driver for CPU0 + +It is a generic cpufreq driver for CPU0 frequency management. It +supports both uniprocessor (UP) and symmetric multiprocessor (SMP) +systems which share clock and voltage across all CPUs. + +Both required and optional properties listed below must be defined +under node /cpus/cpu@0. + +Required properties: +- operating-points: Refer to Documentation/devicetree/bindings/power/opp.txt + for details + +Optional properties: +- transition-latency: Specify the possible maximum transition latency, + in unit of nanoseconds. +- voltage-tolerance: Specify the CPU voltage tolerance in percentage. + +Examples: + +cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@0 { + compatible = "arm,cortex-a9"; + reg = <0>; + next-level-cache = <&L2>; + operating-points = < + /* kHz uV en */ + 1200000 1275000 0 + 996000 1225000 1 + 792000 1100000 1 + 672000 1100000 0 + 396000 950000 1 + 198000 850000 1 + >; + transition-latency = <61036>; /* two CLK32 periods */ + }; + + cpu@1 { + compatible = "arm,cortex-a9"; + reg = <1>; + next-level-cache = <&L2>; + }; + + cpu@2 { + compatible = "arm,cortex-a9"; + reg = <2>; + next-level-cache = <&L2>; + }; + + cpu@3 { + compatible = "arm,cortex-a9"; + reg = <3>; + next-level-cache = <&L2>; + }; +}; diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig index e24a2a1..e77a1e0 100644 --- a/drivers/cpufreq/Kconfig +++ b/drivers/cpufreq/Kconfig @@ -179,6 +179,17 @@ config CPU_FREQ_GOV_CONSERVATIVE If in doubt, say N. +config GENERIC_CPUFREQ_CPU0 + bool "Generic cpufreq driver for CPU0" + depends on HAVE_CLK && REGULATOR && PM_OPP && OF + select CPU_FREQ_TABLE + help + This adds a generic cpufreq driver for CPU0 frequency management. + It supports both uniprocessor (UP) and symmetric multiprocessor (SMP) + systems which share clock and voltage across all CPUs. + + 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 9531fc2..a378ed2 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_GENERIC_CPUFREQ_CPU0) += cpufreq-cpu0.o + ################################################################################## # x86 drivers. # Link order matters. K8 is preferred to ACPI because of firmware bugs in early diff --git a/drivers/cpufreq/cpufreq-cpu0.c b/drivers/cpufreq/cpufreq-cpu0.c new file mode 100644 index 0000000..f1bb7f37 --- /dev/null +++ b/drivers/cpufreq/cpufreq-cpu0.c @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2012 Freescale Semiconductor, Inc. + * + * Reuse some codes from drivers/cpufreq/omap-cpufreq.c + * + * 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/clk.h> +#include <linux/cpu.h> +#include <linux/cpufreq.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/opp.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +/* voltage tolerance in percentage */ +static unsigned int voltage_tolerance; + +static struct device *cpu_dev; +static struct clk *cpu_clk; +static struct regulator *cpu_reg; +static struct cpufreq_frequency_table *freq_table; + +static int cpu0_verify_speed(struct cpufreq_policy *policy) +{ + return cpufreq_frequency_table_verify(policy, freq_table); +} + +static unsigned int cpu0_get_speed(unsigned int cpu) +{ + return clk_get_rate(cpu_clk) / 1000; +} + +static int cpu0_set_target(struct cpufreq_policy *policy, + unsigned int target_freq, unsigned int relation) +{ + struct cpufreq_freqs freqs; + struct opp *opp; + unsigned long freq_Hz, volt = 0, volt_old = 0, tol = 0; + unsigned int index, cpu; + int ret = 0; + + ret = cpufreq_frequency_table_target(policy, freq_table, target_freq, + relation, &index); + if (ret) { + pr_err("failed to match target freqency %d: %d\n", + target_freq, ret); + return ret; + } + + freq_Hz = clk_round_rate(cpu_clk, freq_table[index].frequency * 1000); + if (freq_Hz < 0) + freq_Hz = freq_table[index].frequency * 1000; + freqs.new = freq_Hz / 1000; + freqs.old = clk_get_rate(cpu_clk) / 1000; + + if (freqs.old == freqs.new) + return 0; + + for_each_cpu(cpu, policy->cpus) { + freqs.cpu = cpu; + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + } + + if (cpu_reg) { + opp = opp_find_freq_ceil(cpu_dev, &freq_Hz); + if (IS_ERR(opp)) { + pr_err("failed to find OPP for %ld\n", freq_Hz); + return PTR_ERR(opp); + } + volt = opp_get_voltage(opp); + tol = volt * voltage_tolerance / 100; + volt_old = regulator_get_voltage(cpu_reg); + } + + pr_debug("%u MHz, %ld mV --> %u MHz, %ld mV\n", + freqs.old / 1000, volt_old ? volt_old / 1000 : -1, + freqs.new / 1000, volt ? volt / 1000 : -1); + + /* scaling up? scale voltage before frequency */ + if (cpu_reg && freqs.new > freqs.old) { + if (regulator_set_voltage(cpu_reg, volt - tol, volt + tol)) { + pr_warn("Unable to scale voltage up\n"); + freqs.new = freqs.old; + goto done; + } + } + + ret = clk_set_rate(cpu_clk, freqs.new * 1000); + + /* scaling down? scale voltage after frequency */ + if (cpu_reg && (freqs.new < freqs.old)) { + if (regulator_set_voltage(cpu_reg, volt - tol, volt + tol)) { + pr_warn("Unable to scale voltage down\n"); + ret = clk_set_rate(cpu_clk, freqs.old * 1000); + freqs.new = freqs.old; + goto done; + } + } + +done: + for_each_cpu(cpu, policy->cpus) { + freqs.cpu = cpu; + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + } + + return ret; +} + +static int cpu0_cpufreq_init(struct cpufreq_policy *policy) +{ + struct device_node *np; + int ret; + + /* Initialize resources via CPU0 */ + if (policy->cpu != 0) + return -EINVAL; + + np = of_find_node_by_path("/cpus/cpu@0"); + if (!np) { + pr_err("failed to find cpu0 node\n"); + return -ENOENT; + } + + cpu_dev = get_cpu_device(0); + if (!cpu_dev) { + pr_err("failed to get cpu0 device\n"); + ret = -ENODEV; + goto out_put_node; + } + + cpu_dev->of_node = np; + ret = of_init_opp_table(cpu_dev); + if (ret) { + pr_err("failed to init OPP table\n"); + goto out_put_node; + } + + ret = opp_init_cpufreq_table(cpu_dev, &freq_table); + if (ret) { + pr_err("failed to init cpufreq table\n"); + goto out_put_node; + } + + ret = cpufreq_frequency_table_cpuinfo(policy, freq_table); + if (ret) { + pr_err("invalid frequency table for cpu0\n"); + goto out_free_table; + } + + if (of_property_read_u32(np, "transition-latency", + &policy->cpuinfo.transition_latency)) + policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL; + + of_property_read_u32(np, "voltage-tolerance", &voltage_tolerance); + + cpu_clk = clk_get(cpu_dev, NULL); + if (IS_ERR(cpu_clk)) { + pr_err("failed to get cpu0 clock\n"); + ret = PTR_ERR(cpu_clk); + goto out_free_table; + } + policy->cur = clk_get_rate(cpu_clk) / 1000; + + cpu_reg = regulator_get(cpu_dev, "cpu0"); + if (IS_ERR(cpu_reg)) { + pr_warn("failed to get cpu0 regulator\n"); + cpu_reg = NULL; + } + + /* + * The driver only supports the SMP configuartion where all processors + * share the clock and voltage and clock. Use cpufreq affected_cpus + * interface to have all CPUs scaled together. + */ + policy->shared_type = CPUFREQ_SHARED_TYPE_ANY; + cpumask_setall(policy->cpus); + + cpufreq_frequency_table_get_attr(freq_table, policy->cpu); + + of_node_put(np); + return 0; + +out_free_table: + opp_free_cpufreq_table(cpu_dev, &freq_table); +out_put_node: + of_node_put(np); + return ret; +} + +static int cpu0_cpufreq_exit(struct cpufreq_policy *policy) +{ + cpufreq_frequency_table_put_attr(policy->cpu); + regulator_put(cpu_reg); + clk_put(cpu_clk); + opp_free_cpufreq_table(cpu_dev, &freq_table); + + return 0; +} + +static struct freq_attr *cpu0_cpufreq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +static struct cpufreq_driver cpu0_cpufreq_driver = { + .flags = CPUFREQ_STICKY, + .verify = cpu0_verify_speed, + .target = cpu0_set_target, + .get = cpu0_get_speed, + .init = cpu0_cpufreq_init, + .exit = cpu0_cpufreq_exit, + .name = "generic_cpu0", + .attr = cpu0_cpufreq_attr, +}; + +static int __devinit cpu0_cpufreq_driver_init(void) +{ + return cpufreq_register_driver(&cpu0_cpufreq_driver); +} +late_initcall(cpu0_cpufreq_driver_init); + +MODULE_AUTHOR("Shawn Guo <shawn....@linaro.org>"); +MODULE_DESCRIPTION("Generic cpufreq driver for CPU0"); +MODULE_LICENSE("GPL"); -- 1.7.5.4 _______________________________________________ devicetree-discuss mailing list devicetree-discuss@lists.ozlabs.org https://lists.ozlabs.org/listinfo/devicetree-discuss