aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/cpufreq/cpufreq-cpu0.c
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 /drivers/cpufreq/cpufreq-cpu0.c
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>
Diffstat (limited to 'drivers/cpufreq/cpufreq-cpu0.c')
-rw-r--r--drivers/cpufreq/cpufreq-cpu0.c269
1 files changed, 269 insertions, 0 deletions
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");