aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/cpufreq
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/cpufreq')
-rw-r--r--drivers/cpufreq/Kconfig.arm22
-rw-r--r--drivers/cpufreq/Makefile1
-rw-r--r--drivers/cpufreq/s3c2416-cpufreq.c542
3 files changed, 565 insertions, 0 deletions
diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
index c8bde43281c7..dc59abf3360f 100644
--- a/drivers/cpufreq/Kconfig.arm
+++ b/drivers/cpufreq/Kconfig.arm
@@ -2,6 +2,28 @@
2# ARM CPU Frequency scaling drivers 2# ARM CPU Frequency scaling drivers
3# 3#
4 4
5config ARM_S3C2416_CPUFREQ
6 bool "S3C2416 CPU Frequency scaling support"
7 depends on CPU_S3C2416
8 help
9 This adds the CPUFreq driver for the Samsung S3C2416 and
10 S3C2450 SoC. The S3C2416 supports changing the rate of the
11 armdiv clock source and also entering a so called dynamic
12 voltage scaling mode in which it is possible to reduce the
13 core voltage of the cpu.
14
15 If in doubt, say N.
16
17config ARM_S3C2416_CPUFREQ_VCORESCALE
18 bool "Allow voltage scaling for S3C2416 arm core (EXPERIMENTAL)"
19 depends on ARM_S3C2416_CPUFREQ && REGULATOR && EXPERIMENTAL
20 help
21 Enable CPU voltage scaling when entering the dvs mode.
22 It uses information gathered through existing hardware and
23 tests but not documented in any datasheet.
24
25 If in doubt, say N.
26
5config ARM_S3C64XX_CPUFREQ 27config ARM_S3C64XX_CPUFREQ
6 bool "Samsung S3C64XX" 28 bool "Samsung S3C64XX"
7 depends on CPU_S3C6410 29 depends on CPU_S3C6410
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index ac000fa76bbb..042fbaeb140a 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -40,6 +40,7 @@ obj-$(CONFIG_X86_CPUFREQ_NFORCE2) += cpufreq-nforce2.o
40################################################################################## 40##################################################################################
41# ARM SoC drivers 41# ARM SoC drivers
42obj-$(CONFIG_UX500_SOC_DB8500) += db8500-cpufreq.o 42obj-$(CONFIG_UX500_SOC_DB8500) += db8500-cpufreq.o
43obj-$(CONFIG_ARM_S3C2416_CPUFREQ) += s3c2416-cpufreq.o
43obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o 44obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o
44obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o 45obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o
45obj-$(CONFIG_ARM_EXYNOS_CPUFREQ) += exynos-cpufreq.o 46obj-$(CONFIG_ARM_EXYNOS_CPUFREQ) += exynos-cpufreq.o
diff --git a/drivers/cpufreq/s3c2416-cpufreq.c b/drivers/cpufreq/s3c2416-cpufreq.c
new file mode 100644
index 000000000000..50d2f15a3c8a
--- /dev/null
+++ b/drivers/cpufreq/s3c2416-cpufreq.c
@@ -0,0 +1,542 @@
1/*
2 * S3C2416/2450 CPUfreq Support
3 *
4 * Copyright 2011 Heiko Stuebner <heiko@sntech.de>
5 *
6 * based on s3c64xx_cpufreq.c
7 *
8 * Copyright 2009 Wolfson Microelectronics plc
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2 as
12 * published by the Free Software Foundation.
13 */
14
15#include <linux/kernel.h>
16#include <linux/types.h>
17#include <linux/init.h>
18#include <linux/cpufreq.h>
19#include <linux/clk.h>
20#include <linux/err.h>
21#include <linux/regulator/consumer.h>
22#include <linux/reboot.h>
23#include <linux/module.h>
24
25static DEFINE_MUTEX(cpufreq_lock);
26
27struct s3c2416_data {
28 struct clk *armdiv;
29 struct clk *armclk;
30 struct clk *hclk;
31
32 unsigned long regulator_latency;
33#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
34 struct regulator *vddarm;
35#endif
36
37 struct cpufreq_frequency_table *freq_table;
38
39 bool is_dvs;
40 bool disable_dvs;
41};
42
43static struct s3c2416_data s3c2416_cpufreq;
44
45struct s3c2416_dvfs {
46 unsigned int vddarm_min;
47 unsigned int vddarm_max;
48};
49
50/* pseudo-frequency for dvs mode */
51#define FREQ_DVS 132333
52
53/* frequency to sleep and reboot in
54 * it's essential to leave dvs, as some boards do not reconfigure the
55 * regulator on reboot
56 */
57#define FREQ_SLEEP 133333
58
59/* Sources for the ARMCLK */
60#define SOURCE_HCLK 0
61#define SOURCE_ARMDIV 1
62
63#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
64/* S3C2416 only supports changing the voltage in the dvs-mode.
65 * Voltages down to 1.0V seem to work, so we take what the regulator
66 * can get us.
67 */
68static struct s3c2416_dvfs s3c2416_dvfs_table[] = {
69 [SOURCE_HCLK] = { 950000, 1250000 },
70 [SOURCE_ARMDIV] = { 1250000, 1350000 },
71};
72#endif
73
74static struct cpufreq_frequency_table s3c2416_freq_table[] = {
75 { SOURCE_HCLK, FREQ_DVS },
76 { SOURCE_ARMDIV, 133333 },
77 { SOURCE_ARMDIV, 266666 },
78 { SOURCE_ARMDIV, 400000 },
79 { 0, CPUFREQ_TABLE_END },
80};
81
82static struct cpufreq_frequency_table s3c2450_freq_table[] = {
83 { SOURCE_HCLK, FREQ_DVS },
84 { SOURCE_ARMDIV, 133500 },
85 { SOURCE_ARMDIV, 267000 },
86 { SOURCE_ARMDIV, 534000 },
87 { 0, CPUFREQ_TABLE_END },
88};
89
90static int s3c2416_cpufreq_verify_speed(struct cpufreq_policy *policy)
91{
92 struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
93
94 if (policy->cpu != 0)
95 return -EINVAL;
96
97 return cpufreq_frequency_table_verify(policy, s3c_freq->freq_table);
98}
99
100static unsigned int s3c2416_cpufreq_get_speed(unsigned int cpu)
101{
102 struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
103
104 if (cpu != 0)
105 return 0;
106
107 /* return our pseudo-frequency when in dvs mode */
108 if (s3c_freq->is_dvs)
109 return FREQ_DVS;
110
111 return clk_get_rate(s3c_freq->armclk) / 1000;
112}
113
114static int s3c2416_cpufreq_set_armdiv(struct s3c2416_data *s3c_freq,
115 unsigned int freq)
116{
117 int ret;
118
119 if (clk_get_rate(s3c_freq->armdiv) / 1000 != freq) {
120 ret = clk_set_rate(s3c_freq->armdiv, freq * 1000);
121 if (ret < 0) {
122 pr_err("cpufreq: Failed to set armdiv rate %dkHz: %d\n",
123 freq, ret);
124 return ret;
125 }
126 }
127
128 return 0;
129}
130
131static int s3c2416_cpufreq_enter_dvs(struct s3c2416_data *s3c_freq, int idx)
132{
133#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
134 struct s3c2416_dvfs *dvfs;
135#endif
136 int ret;
137
138 if (s3c_freq->is_dvs) {
139 pr_debug("cpufreq: already in dvs mode, nothing to do\n");
140 return 0;
141 }
142
143 pr_debug("cpufreq: switching armclk to hclk (%lukHz)\n",
144 clk_get_rate(s3c_freq->hclk) / 1000);
145 ret = clk_set_parent(s3c_freq->armclk, s3c_freq->hclk);
146 if (ret < 0) {
147 pr_err("cpufreq: Failed to switch armclk to hclk: %d\n", ret);
148 return ret;
149 }
150
151#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
152 /* changing the core voltage is only allowed when in dvs mode */
153 if (s3c_freq->vddarm) {
154 dvfs = &s3c2416_dvfs_table[idx];
155
156 pr_debug("cpufreq: setting regultor to %d-%d\n",
157 dvfs->vddarm_min, dvfs->vddarm_max);
158 ret = regulator_set_voltage(s3c_freq->vddarm,
159 dvfs->vddarm_min,
160 dvfs->vddarm_max);
161
162 /* when lowering the voltage failed, there is nothing to do */
163 if (ret != 0)
164 pr_err("cpufreq: Failed to set VDDARM: %d\n", ret);
165 }
166#endif
167
168 s3c_freq->is_dvs = 1;
169
170 return 0;
171}
172
173static int s3c2416_cpufreq_leave_dvs(struct s3c2416_data *s3c_freq, int idx)
174{
175#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
176 struct s3c2416_dvfs *dvfs;
177#endif
178 int ret;
179
180 if (!s3c_freq->is_dvs) {
181 pr_debug("cpufreq: not in dvs mode, so can't leave\n");
182 return 0;
183 }
184
185#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
186 if (s3c_freq->vddarm) {
187 dvfs = &s3c2416_dvfs_table[idx];
188
189 pr_debug("cpufreq: setting regultor to %d-%d\n",
190 dvfs->vddarm_min, dvfs->vddarm_max);
191 ret = regulator_set_voltage(s3c_freq->vddarm,
192 dvfs->vddarm_min,
193 dvfs->vddarm_max);
194 if (ret != 0) {
195 pr_err("cpufreq: Failed to set VDDARM: %d\n", ret);
196 return ret;
197 }
198 }
199#endif
200
201 /* force armdiv to hclk frequency for transition from dvs*/
202 if (clk_get_rate(s3c_freq->armdiv) > clk_get_rate(s3c_freq->hclk)) {
203 pr_debug("cpufreq: force armdiv to hclk frequency (%lukHz)\n",
204 clk_get_rate(s3c_freq->hclk) / 1000);
205 ret = s3c2416_cpufreq_set_armdiv(s3c_freq,
206 clk_get_rate(s3c_freq->hclk) / 1000);
207 if (ret < 0) {
208 pr_err("cpufreq: Failed to to set the armdiv to %lukHz: %d\n",
209 clk_get_rate(s3c_freq->hclk) / 1000, ret);
210 return ret;
211 }
212 }
213
214 pr_debug("cpufreq: switching armclk parent to armdiv (%lukHz)\n",
215 clk_get_rate(s3c_freq->armdiv) / 1000);
216
217 ret = clk_set_parent(s3c_freq->armclk, s3c_freq->armdiv);
218 if (ret < 0) {
219 pr_err("cpufreq: Failed to switch armclk clock parent to armdiv: %d\n",
220 ret);
221 return ret;
222 }
223
224 s3c_freq->is_dvs = 0;
225
226 return 0;
227}
228
229static int s3c2416_cpufreq_set_target(struct cpufreq_policy *policy,
230 unsigned int target_freq,
231 unsigned int relation)
232{
233 struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
234 struct cpufreq_freqs freqs;
235 int idx, ret, to_dvs = 0;
236 unsigned int i;
237
238 mutex_lock(&cpufreq_lock);
239
240 pr_debug("cpufreq: to %dKHz, relation %d\n", target_freq, relation);
241
242 ret = cpufreq_frequency_table_target(policy, s3c_freq->freq_table,
243 target_freq, relation, &i);
244 if (ret != 0)
245 goto out;
246
247 idx = s3c_freq->freq_table[i].index;
248
249 if (idx == SOURCE_HCLK)
250 to_dvs = 1;
251
252 /* switching to dvs when it's not allowed */
253 if (to_dvs && s3c_freq->disable_dvs) {
254 pr_debug("cpufreq: entering dvs mode not allowed\n");
255 ret = -EINVAL;
256 goto out;
257 }
258
259 freqs.cpu = 0;
260 freqs.flags = 0;
261 freqs.old = s3c_freq->is_dvs ? FREQ_DVS
262 : clk_get_rate(s3c_freq->armclk) / 1000;
263
264 /* When leavin dvs mode, always switch the armdiv to the hclk rate
265 * The S3C2416 has stability issues when switching directly to
266 * higher frequencies.
267 */
268 freqs.new = (s3c_freq->is_dvs && !to_dvs)
269 ? clk_get_rate(s3c_freq->hclk) / 1000
270 : s3c_freq->freq_table[i].frequency;
271
272 pr_debug("cpufreq: Transition %d-%dkHz\n", freqs.old, freqs.new);
273
274 if (!to_dvs && freqs.old == freqs.new)
275 goto out;
276
277 cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
278
279 if (to_dvs) {
280 pr_debug("cpufreq: enter dvs\n");
281 ret = s3c2416_cpufreq_enter_dvs(s3c_freq, idx);
282 } else if (s3c_freq->is_dvs) {
283 pr_debug("cpufreq: leave dvs\n");
284 ret = s3c2416_cpufreq_leave_dvs(s3c_freq, idx);
285 } else {
286 pr_debug("cpufreq: change armdiv to %dkHz\n", freqs.new);
287 ret = s3c2416_cpufreq_set_armdiv(s3c_freq, freqs.new);
288 }
289
290 cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
291
292out:
293 mutex_unlock(&cpufreq_lock);
294
295 return ret;
296}
297
298#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
299static void __init s3c2416_cpufreq_cfg_regulator(struct s3c2416_data *s3c_freq)
300{
301 int count, v, i, found;
302 struct cpufreq_frequency_table *freq;
303 struct s3c2416_dvfs *dvfs;
304
305 count = regulator_count_voltages(s3c_freq->vddarm);
306 if (count < 0) {
307 pr_err("cpufreq: Unable to check supported voltages\n");
308 return;
309 }
310
311 freq = s3c_freq->freq_table;
312 while (count > 0 && freq->frequency != CPUFREQ_TABLE_END) {
313 if (freq->frequency == CPUFREQ_ENTRY_INVALID)
314 continue;
315
316 dvfs = &s3c2416_dvfs_table[freq->index];
317 found = 0;
318
319 /* Check only the min-voltage, more is always ok on S3C2416 */
320 for (i = 0; i < count; i++) {
321 v = regulator_list_voltage(s3c_freq->vddarm, i);
322 if (v >= dvfs->vddarm_min)
323 found = 1;
324 }
325
326 if (!found) {
327 pr_debug("cpufreq: %dkHz unsupported by regulator\n",
328 freq->frequency);
329 freq->frequency = CPUFREQ_ENTRY_INVALID;
330 }
331
332 freq++;
333 }
334
335 /* Guessed */
336 s3c_freq->regulator_latency = 1 * 1000 * 1000;
337}
338#endif
339
340static int s3c2416_cpufreq_reboot_notifier_evt(struct notifier_block *this,
341 unsigned long event, void *ptr)
342{
343 struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
344 int ret;
345
346 mutex_lock(&cpufreq_lock);
347
348 /* disable further changes */
349 s3c_freq->disable_dvs = 1;
350
351 mutex_unlock(&cpufreq_lock);
352
353 /* some boards don't reconfigure the regulator on reboot, which
354 * could lead to undervolting the cpu when the clock is reset.
355 * Therefore we always leave the DVS mode on reboot.
356 */
357 if (s3c_freq->is_dvs) {
358 pr_debug("cpufreq: leave dvs on reboot\n");
359 ret = cpufreq_driver_target(cpufreq_cpu_get(0), FREQ_SLEEP, 0);
360 if (ret < 0)
361 return NOTIFY_BAD;
362 }
363
364 return NOTIFY_DONE;
365}
366
367static struct notifier_block s3c2416_cpufreq_reboot_notifier = {
368 .notifier_call = s3c2416_cpufreq_reboot_notifier_evt,
369};
370
371static int __init s3c2416_cpufreq_driver_init(struct cpufreq_policy *policy)
372{
373 struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
374 struct cpufreq_frequency_table *freq;
375 struct clk *msysclk;
376 unsigned long rate;
377 int ret;
378
379 if (policy->cpu != 0)
380 return -EINVAL;
381
382 msysclk = clk_get(NULL, "msysclk");
383 if (IS_ERR(msysclk)) {
384 ret = PTR_ERR(msysclk);
385 pr_err("cpufreq: Unable to obtain msysclk: %d\n", ret);
386 return ret;
387 }
388
389 /*
390 * S3C2416 and S3C2450 share the same processor-ID and also provide no
391 * other means to distinguish them other than through the rate of
392 * msysclk. On S3C2416 msysclk runs at 800MHz and on S3C2450 at 533MHz.
393 */
394 rate = clk_get_rate(msysclk);
395 if (rate == 800 * 1000 * 1000) {
396 pr_info("cpufreq: msysclk running at %lukHz, using S3C2416 frequency table\n",
397 rate / 1000);
398 s3c_freq->freq_table = s3c2416_freq_table;
399 policy->cpuinfo.max_freq = 400000;
400 } else if (rate / 1000 == 534000) {
401 pr_info("cpufreq: msysclk running at %lukHz, using S3C2450 frequency table\n",
402 rate / 1000);
403 s3c_freq->freq_table = s3c2450_freq_table;
404 policy->cpuinfo.max_freq = 534000;
405 }
406
407 /* not needed anymore */
408 clk_put(msysclk);
409
410 if (s3c_freq->freq_table == NULL) {
411 pr_err("cpufreq: No frequency information for this CPU, msysclk at %lukHz\n",
412 rate / 1000);
413 return -ENODEV;
414 }
415
416 s3c_freq->is_dvs = 0;
417
418 s3c_freq->armdiv = clk_get(NULL, "armdiv");
419 if (IS_ERR(s3c_freq->armdiv)) {
420 ret = PTR_ERR(s3c_freq->armdiv);
421 pr_err("cpufreq: Unable to obtain ARMDIV: %d\n", ret);
422 return ret;
423 }
424
425 s3c_freq->hclk = clk_get(NULL, "hclk");
426 if (IS_ERR(s3c_freq->hclk)) {
427 ret = PTR_ERR(s3c_freq->hclk);
428 pr_err("cpufreq: Unable to obtain HCLK: %d\n", ret);
429 goto err_hclk;
430 }
431
432 /* chech hclk rate, we only support the common 133MHz for now
433 * hclk could also run at 66MHz, but this not often used
434 */
435 rate = clk_get_rate(s3c_freq->hclk);
436 if (rate < 133 * 1000 * 1000) {
437 pr_err("cpufreq: HCLK not at 133MHz\n");
438 clk_put(s3c_freq->hclk);
439 ret = -EINVAL;
440 goto err_armclk;
441 }
442
443 s3c_freq->armclk = clk_get(NULL, "armclk");
444 if (IS_ERR(s3c_freq->armclk)) {
445 ret = PTR_ERR(s3c_freq->armclk);
446 pr_err("cpufreq: Unable to obtain ARMCLK: %d\n", ret);
447 goto err_armclk;
448 }
449
450#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
451 s3c_freq->vddarm = regulator_get(NULL, "vddarm");
452 if (IS_ERR(s3c_freq->vddarm)) {
453 ret = PTR_ERR(s3c_freq->vddarm);
454 pr_err("cpufreq: Failed to obtain VDDARM: %d\n", ret);
455 goto err_vddarm;
456 }
457
458 s3c2416_cpufreq_cfg_regulator(s3c_freq);
459#else
460 s3c_freq->regulator_latency = 0;
461#endif
462
463 freq = s3c_freq->freq_table;
464 while (freq->frequency != CPUFREQ_TABLE_END) {
465 /* special handling for dvs mode */
466 if (freq->index == 0) {
467 if (!s3c_freq->hclk) {
468 pr_debug("cpufreq: %dkHz unsupported as it would need unavailable dvs mode\n",
469 freq->frequency);
470 freq->frequency = CPUFREQ_ENTRY_INVALID;
471 } else {
472 freq++;
473 continue;
474 }
475 }
476
477 /* Check for frequencies we can generate */
478 rate = clk_round_rate(s3c_freq->armdiv,
479 freq->frequency * 1000);
480 rate /= 1000;
481 if (rate != freq->frequency) {
482 pr_debug("cpufreq: %dkHz unsupported by clock (clk_round_rate return %lu)\n",
483 freq->frequency, rate);
484 freq->frequency = CPUFREQ_ENTRY_INVALID;
485 }
486
487 freq++;
488 }
489
490 policy->cur = clk_get_rate(s3c_freq->armclk) / 1000;
491
492 /* Datasheet says PLL stabalisation time must be at least 300us,
493 * so but add some fudge. (reference in LOCKCON0 register description)
494 */
495 policy->cpuinfo.transition_latency = (500 * 1000) +
496 s3c_freq->regulator_latency;
497
498 ret = cpufreq_frequency_table_cpuinfo(policy, s3c_freq->freq_table);
499 if (ret)
500 goto err_freq_table;
501
502 cpufreq_frequency_table_get_attr(s3c_freq->freq_table, 0);
503
504 register_reboot_notifier(&s3c2416_cpufreq_reboot_notifier);
505
506 return 0;
507
508err_freq_table:
509#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
510 regulator_put(s3c_freq->vddarm);
511err_vddarm:
512#endif
513 clk_put(s3c_freq->armclk);
514err_armclk:
515 clk_put(s3c_freq->hclk);
516err_hclk:
517 clk_put(s3c_freq->armdiv);
518
519 return ret;
520}
521
522static struct freq_attr *s3c2416_cpufreq_attr[] = {
523 &cpufreq_freq_attr_scaling_available_freqs,
524 NULL,
525};
526
527static struct cpufreq_driver s3c2416_cpufreq_driver = {
528 .owner = THIS_MODULE,
529 .flags = 0,
530 .verify = s3c2416_cpufreq_verify_speed,
531 .target = s3c2416_cpufreq_set_target,
532 .get = s3c2416_cpufreq_get_speed,
533 .init = s3c2416_cpufreq_driver_init,
534 .name = "s3c2416",
535 .attr = s3c2416_cpufreq_attr,
536};
537
538static int __init s3c2416_cpufreq_init(void)
539{
540 return cpufreq_register_driver(&s3c2416_cpufreq_driver);
541}
542module_init(s3c2416_cpufreq_init);