aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn Guo <shawn.guo@linaro.org>2012-09-06 03:09:11 -0400
committerRafael J. Wysocki <rjw@sisk.pl>2012-09-09 16:06:34 -0400
commit95ceafd46359dfd901f9d3b881b33d3036e4b0ce (patch)
treedc744c4641640513558c18f411241f6abd8a1b8b
parentb496dfbc94ab86f970ef0167eaabe51f930aa5fb (diff)
cpufreq: Add a generic cpufreq-cpu0 driver
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> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
-rw-r--r--Documentation/devicetree/bindings/cpufreq/cpufreq-cpu0.txt55
-rw-r--r--drivers/cpufreq/Kconfig11
-rw-r--r--drivers/cpufreq/Makefile2
-rw-r--r--drivers/cpufreq/cpufreq-cpu0.c269
4 files changed, 337 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/cpufreq/cpufreq-cpu0.txt b/Documentation/devicetree/bindings/cpufreq/cpufreq-cpu0.txt
new file mode 100644
index 000000000000..4416ccc33472
--- /dev/null
+++ b/Documentation/devicetree/bindings/cpufreq/cpufreq-cpu0.txt
@@ -0,0 +1,55 @@
1Generic CPU0 cpufreq driver
2
3It is a generic cpufreq driver for CPU0 frequency management. It
4supports both uniprocessor (UP) and symmetric multiprocessor (SMP)
5systems which share clock and voltage across all CPUs.
6
7Both required and optional properties listed below must be defined
8under node /cpus/cpu@0.
9
10Required properties:
11- operating-points: Refer to Documentation/devicetree/bindings/power/opp.txt
12 for details
13
14Optional properties:
15- clock-latency: Specify the possible maximum transition latency for clock,
16 in unit of nanoseconds.
17- voltage-tolerance: Specify the CPU voltage tolerance in percentage.
18
19Examples:
20
21cpus {
22 #address-cells = <1>;
23 #size-cells = <0>;
24
25 cpu@0 {
26 compatible = "arm,cortex-a9";
27 reg = <0>;
28 next-level-cache = <&L2>;
29 operating-points = <
30 /* kHz uV */
31 792000 1100000
32 396000 950000
33 198000 850000
34 >;
35 transition-latency = <61036>; /* two CLK32 periods */
36 };
37
38 cpu@1 {
39 compatible = "arm,cortex-a9";
40 reg = <1>;
41 next-level-cache = <&L2>;
42 };
43
44 cpu@2 {
45 compatible = "arm,cortex-a9";
46 reg = <2>;
47 next-level-cache = <&L2>;
48 };
49
50 cpu@3 {
51 compatible = "arm,cortex-a9";
52 reg = <3>;
53 next-level-cache = <&L2>;
54 };
55};
diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig
index e24a2a1b6666..ea512f47b789 100644
--- a/drivers/cpufreq/Kconfig
+++ b/drivers/cpufreq/Kconfig
@@ -179,6 +179,17 @@ config CPU_FREQ_GOV_CONSERVATIVE
179 179
180 If in doubt, say N. 180 If in doubt, say N.
181 181
182config GENERIC_CPUFREQ_CPU0
183 bool "Generic CPU0 cpufreq driver"
184 depends on HAVE_CLK && REGULATOR && PM_OPP && OF
185 select CPU_FREQ_TABLE
186 help
187 This adds a generic cpufreq driver for CPU0 frequency management.
188 It supports both uniprocessor (UP) and symmetric multiprocessor (SMP)
189 systems which share clock and voltage across all CPUs.
190
191 If in doubt, say N.
192
182menu "x86 CPU frequency scaling drivers" 193menu "x86 CPU frequency scaling drivers"
183depends on X86 194depends on X86
184source "drivers/cpufreq/Kconfig.x86" 195source "drivers/cpufreq/Kconfig.x86"
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index b99790f400c4..1bc90e1306d8 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -13,6 +13,8 @@ obj-$(CONFIG_CPU_FREQ_GOV_CONSERVATIVE) += cpufreq_conservative.o
13# CPUfreq cross-arch helpers 13# CPUfreq cross-arch helpers
14obj-$(CONFIG_CPU_FREQ_TABLE) += freq_table.o 14obj-$(CONFIG_CPU_FREQ_TABLE) += freq_table.o
15 15
16obj-$(CONFIG_GENERIC_CPUFREQ_CPU0) += cpufreq-cpu0.o
17
16################################################################################## 18##################################################################################
17# x86 drivers. 19# x86 drivers.
18# Link order matters. K8 is preferred to ACPI because of firmware bugs in early 20# 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 000000000000..e9158278c71d
--- /dev/null
+++ b/drivers/cpufreq/cpufreq-cpu0.c
@@ -0,0 +1,269 @@
1/*
2 * Copyright (C) 2012 Freescale Semiconductor, Inc.
3 *
4 * The OPP code in function cpu0_set_target() is reused from
5 * drivers/cpufreq/omap-cpufreq.c
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 */
11
12#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
13
14#include <linux/clk.h>
15#include <linux/cpu.h>
16#include <linux/cpufreq.h>
17#include <linux/err.h>
18#include <linux/module.h>
19#include <linux/of.h>
20#include <linux/opp.h>
21#include <linux/regulator/consumer.h>
22#include <linux/slab.h>
23
24static unsigned int transition_latency;
25static unsigned int voltage_tolerance; /* in percentage */
26
27static struct device *cpu_dev;
28static struct clk *cpu_clk;
29static struct regulator *cpu_reg;
30static struct cpufreq_frequency_table *freq_table;
31
32static int cpu0_verify_speed(struct cpufreq_policy *policy)
33{
34 return cpufreq_frequency_table_verify(policy, freq_table);
35}
36
37static unsigned int cpu0_get_speed(unsigned int cpu)
38{
39 return clk_get_rate(cpu_clk) / 1000;
40}
41
42static int cpu0_set_target(struct cpufreq_policy *policy,
43 unsigned int target_freq, unsigned int relation)
44{
45 struct cpufreq_freqs freqs;
46 struct opp *opp;
47 unsigned long freq_Hz, volt = 0, volt_old = 0, tol = 0;
48 unsigned int index, cpu;
49 int ret;
50
51 ret = cpufreq_frequency_table_target(policy, freq_table, target_freq,
52 relation, &index);
53 if (ret) {
54 pr_err("failed to match target freqency %d: %d\n",
55 target_freq, ret);
56 return ret;
57 }
58
59 freq_Hz = clk_round_rate(cpu_clk, freq_table[index].frequency * 1000);
60 if (freq_Hz < 0)
61 freq_Hz = freq_table[index].frequency * 1000;
62 freqs.new = freq_Hz / 1000;
63 freqs.old = clk_get_rate(cpu_clk) / 1000;
64
65 if (freqs.old == freqs.new)
66 return 0;
67
68 for_each_online_cpu(cpu) {
69 freqs.cpu = cpu;
70 cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
71 }
72
73 if (cpu_reg) {
74 opp = opp_find_freq_ceil(cpu_dev, &freq_Hz);
75 if (IS_ERR(opp)) {
76 pr_err("failed to find OPP for %ld\n", freq_Hz);
77 return PTR_ERR(opp);
78 }
79 volt = opp_get_voltage(opp);
80 tol = volt * voltage_tolerance / 100;
81 volt_old = regulator_get_voltage(cpu_reg);
82 }
83
84 pr_debug("%u MHz, %ld mV --> %u MHz, %ld mV\n",
85 freqs.old / 1000, volt_old ? volt_old / 1000 : -1,
86 freqs.new / 1000, volt ? volt / 1000 : -1);
87
88 /* scaling up? scale voltage before frequency */
89 if (cpu_reg && freqs.new > freqs.old) {
90 ret = regulator_set_voltage_tol(cpu_reg, volt, tol);
91 if (ret) {
92 pr_err("failed to scale voltage up: %d\n", ret);
93 freqs.new = freqs.old;
94 return ret;
95 }
96 }
97
98 ret = clk_set_rate(cpu_clk, freqs.new * 1000);
99 if (ret) {
100 pr_err("failed to set clock rate: %d\n", ret);
101 if (cpu_reg)
102 regulator_set_voltage_tol(cpu_reg, volt_old, tol);
103 return ret;
104 }
105
106 /* scaling down? scale voltage after frequency */
107 if (cpu_reg && freqs.new < freqs.old) {
108 ret = regulator_set_voltage_tol(cpu_reg, volt, tol);
109 if (ret) {
110 pr_err("failed to scale voltage down: %d\n", ret);
111 clk_set_rate(cpu_clk, freqs.old * 1000);
112 freqs.new = freqs.old;
113 return ret;
114 }
115 }
116
117 for_each_online_cpu(cpu) {
118 freqs.cpu = cpu;
119 cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
120 }
121
122 return 0;
123}
124
125static int cpu0_cpufreq_init(struct cpufreq_policy *policy)
126{
127 int ret;
128
129 if (policy->cpu != 0)
130 return -EINVAL;
131
132 ret = cpufreq_frequency_table_cpuinfo(policy, freq_table);
133 if (ret) {
134 pr_err("invalid frequency table: %d\n", ret);
135 return ret;
136 }
137
138 policy->cpuinfo.transition_latency = transition_latency;
139 policy->cur = clk_get_rate(cpu_clk) / 1000;
140
141 /*
142 * The driver only supports the SMP configuartion where all processors
143 * share the clock and voltage and clock. Use cpufreq affected_cpus
144 * interface to have all CPUs scaled together.
145 */
146 policy->shared_type = CPUFREQ_SHARED_TYPE_ANY;
147 cpumask_setall(policy->cpus);
148
149 cpufreq_frequency_table_get_attr(freq_table, policy->cpu);
150
151 return 0;
152}
153
154static int cpu0_cpufreq_exit(struct cpufreq_policy *policy)
155{
156 cpufreq_frequency_table_put_attr(policy->cpu);
157
158 return 0;
159}
160
161static struct freq_attr *cpu0_cpufreq_attr[] = {
162 &cpufreq_freq_attr_scaling_available_freqs,
163 NULL,
164};
165
166static struct cpufreq_driver cpu0_cpufreq_driver = {
167 .flags = CPUFREQ_STICKY,
168 .verify = cpu0_verify_speed,
169 .target = cpu0_set_target,
170 .get = cpu0_get_speed,
171 .init = cpu0_cpufreq_init,
172 .exit = cpu0_cpufreq_exit,
173 .name = "generic_cpu0",
174 .attr = cpu0_cpufreq_attr,
175};
176
177static int __devinit cpu0_cpufreq_driver_init(void)
178{
179 struct device_node *np;
180 int ret;
181
182 np = of_find_node_by_path("/cpus/cpu@0");
183 if (!np) {
184 pr_err("failed to find cpu0 node\n");
185 return -ENOENT;
186 }
187
188 cpu_dev = get_cpu_device(0);
189 if (!cpu_dev) {
190 pr_err("failed to get cpu0 device\n");
191 ret = -ENODEV;
192 goto out_put_node;
193 }
194
195 cpu_dev->of_node = np;
196
197 cpu_clk = clk_get(cpu_dev, NULL);
198 if (IS_ERR(cpu_clk)) {
199 ret = PTR_ERR(cpu_clk);
200 pr_err("failed to get cpu0 clock: %d\n", ret);
201 goto out_put_node;
202 }
203
204 cpu_reg = regulator_get(cpu_dev, "cpu0");
205 if (IS_ERR(cpu_reg)) {
206 pr_warn("failed to get cpu0 regulator\n");
207 cpu_reg = NULL;
208 }
209
210 ret = of_init_opp_table(cpu_dev);
211 if (ret) {
212 pr_err("failed to init OPP table: %d\n", ret);
213 goto out_put_node;
214 }
215
216 ret = opp_init_cpufreq_table(cpu_dev, &freq_table);
217 if (ret) {
218 pr_err("failed to init cpufreq table: %d\n", ret);
219 goto out_put_node;
220 }
221
222 of_property_read_u32(np, "voltage-tolerance", &voltage_tolerance);
223
224 if (of_property_read_u32(np, "clock-latency", &transition_latency))
225 transition_latency = CPUFREQ_ETERNAL;
226
227 if (cpu_reg) {
228 struct opp *opp;
229 unsigned long min_uV, max_uV;
230 int i;
231
232 /*
233 * OPP is maintained in order of increasing frequency, and
234 * freq_table initialised from OPP is therefore sorted in the
235 * same order.
236 */
237 for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++)
238 ;
239 opp = opp_find_freq_exact(cpu_dev,
240 freq_table[0].frequency * 1000, true);
241 min_uV = opp_get_voltage(opp);
242 opp = opp_find_freq_exact(cpu_dev,
243 freq_table[i-1].frequency * 1000, true);
244 max_uV = opp_get_voltage(opp);
245 ret = regulator_set_voltage_time(cpu_reg, min_uV, max_uV);
246 if (ret > 0)
247 transition_latency += ret * 1000;
248 }
249
250 ret = cpufreq_register_driver(&cpu0_cpufreq_driver);
251 if (ret) {
252 pr_err("failed register driver: %d\n", ret);
253 goto out_free_table;
254 }
255
256 of_node_put(np);
257 return 0;
258
259out_free_table:
260 opp_free_cpufreq_table(cpu_dev, &freq_table);
261out_put_node:
262 of_node_put(np);
263 return ret;
264}
265late_initcall(cpu0_cpufreq_driver_init);
266
267MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
268MODULE_DESCRIPTION("Generic CPU0 cpufreq driver");
269MODULE_LICENSE("GPL");