Message ID | 1346915351-25819-1-git-send-email-shawn.guo@linaro.org (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On Thursday, September 06, 2012, Shawn Guo wrote: > 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.guo@linaro.org> > Acked-by: Santosh Shilimkar <santosh.shilimkar@ti.com> > Tested-by: AnilKumar Ch <anilkumar@ti.com> I tentatively added it to the linux-next branch of the linux-pm.git tree, but I will drop it if I don't get a patch removing the code duplication added by it (that I will actually like). Thanks, Rafael > --- > Changes since v3: > * Update copyright info per Rafael. > > .../devicetree/bindings/cpufreq/cpufreq-cpu0.txt | 55 ++++ > drivers/cpufreq/Kconfig | 11 + > drivers/cpufreq/Makefile | 2 + > drivers/cpufreq/cpufreq-cpu0.c | 269 ++++++++++++++++++++ > 4 files changed, 337 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..4416ccc > --- /dev/null > +++ b/Documentation/devicetree/bindings/cpufreq/cpufreq-cpu0.txt > @@ -0,0 +1,55 @@ > +Generic CPU0 cpufreq driver > + > +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: > +- clock-latency: Specify the possible maximum transition latency for clock, > + 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 */ > + 792000 1100000 > + 396000 950000 > + 198000 850000 > + >; > + 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..ea512f4 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 CPU0 cpufreq driver" > + 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..e915827 > --- /dev/null > +++ b/drivers/cpufreq/cpufreq-cpu0.c > @@ -0,0 +1,269 @@ > +/* > + * Copyright (C) 2012 Freescale Semiconductor, Inc. > + * > + * The OPP code in function cpu0_set_target() is reused from > + * drivers/cpufreq/omap-cpufreq.c > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + */ > + > +#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> > + > +static unsigned int transition_latency; > +static unsigned int voltage_tolerance; /* in percentage */ > + > +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; > + > + 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_online_cpu(cpu) { > + 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) { > + ret = regulator_set_voltage_tol(cpu_reg, volt, tol); > + if (ret) { > + pr_err("failed to scale voltage up: %d\n", ret); > + freqs.new = freqs.old; > + return ret; > + } > + } > + > + ret = clk_set_rate(cpu_clk, freqs.new * 1000); > + if (ret) { > + pr_err("failed to set clock rate: %d\n", ret); > + if (cpu_reg) > + regulator_set_voltage_tol(cpu_reg, volt_old, tol); > + return ret; > + } > + > + /* scaling down? scale voltage after frequency */ > + if (cpu_reg && freqs.new < freqs.old) { > + ret = regulator_set_voltage_tol(cpu_reg, volt, tol); > + if (ret) { > + pr_err("failed to scale voltage down: %d\n", ret); > + clk_set_rate(cpu_clk, freqs.old * 1000); > + freqs.new = freqs.old; > + return ret; > + } > + } > + > + for_each_online_cpu(cpu) { > + freqs.cpu = cpu; > + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); > + } > + > + return 0; > +} > + > +static int cpu0_cpufreq_init(struct cpufreq_policy *policy) > +{ > + int ret; > + > + if (policy->cpu != 0) > + return -EINVAL; > + > + ret = cpufreq_frequency_table_cpuinfo(policy, freq_table); > + if (ret) { > + pr_err("invalid frequency table: %d\n", ret); > + return ret; > + } > + > + policy->cpuinfo.transition_latency = transition_latency; > + policy->cur = clk_get_rate(cpu_clk) / 1000; > + > + /* > + * 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); > + > + return 0; > +} > + > +static int cpu0_cpufreq_exit(struct cpufreq_policy *policy) > +{ > + cpufreq_frequency_table_put_attr(policy->cpu); > + > + 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) > +{ > + struct device_node *np; > + int ret; > + > + 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; > + > + cpu_clk = clk_get(cpu_dev, NULL); > + if (IS_ERR(cpu_clk)) { > + ret = PTR_ERR(cpu_clk); > + pr_err("failed to get cpu0 clock: %d\n", ret); > + goto out_put_node; > + } > + > + cpu_reg = regulator_get(cpu_dev, "cpu0"); > + if (IS_ERR(cpu_reg)) { > + pr_warn("failed to get cpu0 regulator\n"); > + cpu_reg = NULL; > + } > + > + ret = of_init_opp_table(cpu_dev); > + if (ret) { > + pr_err("failed to init OPP table: %d\n", ret); > + goto out_put_node; > + } > + > + ret = opp_init_cpufreq_table(cpu_dev, &freq_table); > + if (ret) { > + pr_err("failed to init cpufreq table: %d\n", ret); > + goto out_put_node; > + } > + > + of_property_read_u32(np, "voltage-tolerance", &voltage_tolerance); > + > + if (of_property_read_u32(np, "clock-latency", &transition_latency)) > + transition_latency = CPUFREQ_ETERNAL; > + > + if (cpu_reg) { > + struct opp *opp; > + unsigned long min_uV, max_uV; > + int i; > + > + /* > + * OPP is maintained in order of increasing frequency, and > + * freq_table initialised from OPP is therefore sorted in the > + * same order. > + */ > + for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) > + ; > + opp = opp_find_freq_exact(cpu_dev, > + freq_table[0].frequency * 1000, true); > + min_uV = opp_get_voltage(opp); > + opp = opp_find_freq_exact(cpu_dev, > + freq_table[i-1].frequency * 1000, true); > + max_uV = opp_get_voltage(opp); > + ret = regulator_set_voltage_time(cpu_reg, min_uV, max_uV); > + if (ret > 0) > + transition_latency += ret * 1000; > + } > + > + ret = cpufreq_register_driver(&cpu0_cpufreq_driver); > + if (ret) { > + pr_err("failed register driver: %d\n", ret); > + goto out_free_table; > + } > + > + 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; > +} > +late_initcall(cpu0_cpufreq_driver_init); > + > +MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>"); > +MODULE_DESCRIPTION("Generic CPU0 cpufreq driver"); > +MODULE_LICENSE("GPL"); >
diff --git a/Documentation/devicetree/bindings/cpufreq/cpufreq-cpu0.txt b/Documentation/devicetree/bindings/cpufreq/cpufreq-cpu0.txt new file mode 100644 index 0000000..4416ccc --- /dev/null +++ b/Documentation/devicetree/bindings/cpufreq/cpufreq-cpu0.txt @@ -0,0 +1,55 @@ +Generic CPU0 cpufreq driver + +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: +- clock-latency: Specify the possible maximum transition latency for clock, + 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 */ + 792000 1100000 + 396000 950000 + 198000 850000 + >; + 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..ea512f4 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 CPU0 cpufreq driver" + 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..e915827 --- /dev/null +++ b/drivers/cpufreq/cpufreq-cpu0.c @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2012 Freescale Semiconductor, Inc. + * + * The OPP code in function cpu0_set_target() is reused from + * drivers/cpufreq/omap-cpufreq.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#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> + +static unsigned int transition_latency; +static unsigned int voltage_tolerance; /* in percentage */ + +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; + + 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_online_cpu(cpu) { + 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) { + ret = regulator_set_voltage_tol(cpu_reg, volt, tol); + if (ret) { + pr_err("failed to scale voltage up: %d\n", ret); + freqs.new = freqs.old; + return ret; + } + } + + ret = clk_set_rate(cpu_clk, freqs.new * 1000); + if (ret) { + pr_err("failed to set clock rate: %d\n", ret); + if (cpu_reg) + regulator_set_voltage_tol(cpu_reg, volt_old, tol); + return ret; + } + + /* scaling down? scale voltage after frequency */ + if (cpu_reg && freqs.new < freqs.old) { + ret = regulator_set_voltage_tol(cpu_reg, volt, tol); + if (ret) { + pr_err("failed to scale voltage down: %d\n", ret); + clk_set_rate(cpu_clk, freqs.old * 1000); + freqs.new = freqs.old; + return ret; + } + } + + for_each_online_cpu(cpu) { + freqs.cpu = cpu; + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + } + + return 0; +} + +static int cpu0_cpufreq_init(struct cpufreq_policy *policy) +{ + int ret; + + if (policy->cpu != 0) + return -EINVAL; + + ret = cpufreq_frequency_table_cpuinfo(policy, freq_table); + if (ret) { + pr_err("invalid frequency table: %d\n", ret); + return ret; + } + + policy->cpuinfo.transition_latency = transition_latency; + policy->cur = clk_get_rate(cpu_clk) / 1000; + + /* + * 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); + + return 0; +} + +static int cpu0_cpufreq_exit(struct cpufreq_policy *policy) +{ + cpufreq_frequency_table_put_attr(policy->cpu); + + 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) +{ + struct device_node *np; + int ret; + + 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; + + cpu_clk = clk_get(cpu_dev, NULL); + if (IS_ERR(cpu_clk)) { + ret = PTR_ERR(cpu_clk); + pr_err("failed to get cpu0 clock: %d\n", ret); + goto out_put_node; + } + + cpu_reg = regulator_get(cpu_dev, "cpu0"); + if (IS_ERR(cpu_reg)) { + pr_warn("failed to get cpu0 regulator\n"); + cpu_reg = NULL; + } + + ret = of_init_opp_table(cpu_dev); + if (ret) { + pr_err("failed to init OPP table: %d\n", ret); + goto out_put_node; + } + + ret = opp_init_cpufreq_table(cpu_dev, &freq_table); + if (ret) { + pr_err("failed to init cpufreq table: %d\n", ret); + goto out_put_node; + } + + of_property_read_u32(np, "voltage-tolerance", &voltage_tolerance); + + if (of_property_read_u32(np, "clock-latency", &transition_latency)) + transition_latency = CPUFREQ_ETERNAL; + + if (cpu_reg) { + struct opp *opp; + unsigned long min_uV, max_uV; + int i; + + /* + * OPP is maintained in order of increasing frequency, and + * freq_table initialised from OPP is therefore sorted in the + * same order. + */ + for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) + ; + opp = opp_find_freq_exact(cpu_dev, + freq_table[0].frequency * 1000, true); + min_uV = opp_get_voltage(opp); + opp = opp_find_freq_exact(cpu_dev, + freq_table[i-1].frequency * 1000, true); + max_uV = opp_get_voltage(opp); + ret = regulator_set_voltage_time(cpu_reg, min_uV, max_uV); + if (ret > 0) + transition_latency += ret * 1000; + } + + ret = cpufreq_register_driver(&cpu0_cpufreq_driver); + if (ret) { + pr_err("failed register driver: %d\n", ret); + goto out_free_table; + } + + 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; +} +late_initcall(cpu0_cpufreq_driver_init); + +MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>"); +MODULE_DESCRIPTION("Generic CPU0 cpufreq driver"); +MODULE_LICENSE("GPL");