aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm
diff options
context:
space:
mode:
authorSunyoung Kang <sy0816.kang@samsung.com>2010-09-16 04:59:21 -0400
committerKukjin Kim <kgene.kim@samsung.com>2010-12-23 00:53:40 -0500
commitf40f91fefcf3a9049bcfa31ac53bc0e775444dab (patch)
treeb4a55889902d5f23aed516efb49ecc75d6935040 /arch/arm
parentdd0b7e20da906b40d55f24bb2dc21abd58ed3f55 (diff)
ARM: S5PV310: Add support CPUFREQ
This patch adds support CPUFREQ driver for S5PV310 and S5PC210. This can support DVFS(Dynamic Voltage and Frequency Scaling). The voltage scaling depends on existence of regulator. Sigend-off-by: Sunyoung Kang <sy0816.kang@samsung.com> Signed-off-by: Kukjin Kim <kgene.kim@samsung.com>
Diffstat (limited to 'arch/arm')
-rw-r--r--arch/arm/mach-s5pv310/cpufreq.c561
1 files changed, 561 insertions, 0 deletions
diff --git a/arch/arm/mach-s5pv310/cpufreq.c b/arch/arm/mach-s5pv310/cpufreq.c
new file mode 100644
index 000000000000..bcd4ebf93e56
--- /dev/null
+++ b/arch/arm/mach-s5pv310/cpufreq.c
@@ -0,0 +1,561 @@
1/* linux/arch/arm/mach-s5pv310/cpufreq.c
2 *
3 * Copyright (c) 2010 Samsung Electronics Co., Ltd.
4 * http://www.samsung.com
5 *
6 * S5PV310 - CPU frequency scaling support
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2 as
10 * published by the Free Software Foundation.
11*/
12
13#include <linux/types.h>
14#include <linux/kernel.h>
15#include <linux/err.h>
16#include <linux/clk.h>
17#include <linux/io.h>
18#include <linux/slab.h>
19#include <linux/regulator/consumer.h>
20#include <linux/cpufreq.h>
21
22#include <mach/map.h>
23#include <mach/regs-clock.h>
24#include <mach/regs-mem.h>
25
26#include <plat/clock.h>
27
28static struct clk *cpu_clk;
29static struct clk *moutcore;
30static struct clk *mout_mpll;
31static struct clk *mout_apll;
32
33#ifdef CONFIG_REGULATOR
34static struct regulator *arm_regulator;
35static struct regulator *int_regulator;
36#endif
37
38static struct cpufreq_freqs freqs;
39static unsigned int armclk_use_apll;
40static unsigned int memtype;
41
42enum s5pv310_memory_type {
43 DDR2 = 4,
44 LPDDR2,
45 DDR3,
46};
47
48enum cpufreq_level_index {
49 L0, L1, L2, L3, L4, CPUFREQ_LEVEL_END,
50};
51
52static struct cpufreq_frequency_table s5pv310_freq_table[] = {
53 {L0, 1000*1000},
54 {L1, 800*1000},
55 {L2, 400*1000},
56 {L3, 200*1000},
57 {L4, 100*1000},
58 {0, CPUFREQ_TABLE_END},
59};
60
61static unsigned int clkdiv_cpu0[CPUFREQ_LEVEL_END + 1][7] = {
62 /*
63 * Clock divider value for following
64 * { DIVCORE, DIVCOREM0, DIVCOREM1, DIVPERIPH,
65 * DIVATB, DIVPCLK_DBG, DIVAPLL }
66 */
67
68 /* ARM L0: 1000MHz */
69 { 0, 3, 7, 3, 3, 0, 0 },
70
71 /* ARM L1: 800MHz */
72 { 0, 3, 7, 3, 3, 0, 0 },
73
74 /* ARM L2: 400MHz */
75 { 1, 1, 3, 1, 1, 0, 0 },
76
77 /* ARM L3: 200MHz */
78 { 3, 0, 1, 0, 0, 0, 0 },
79
80 /* ARM L4A: 100MHz, for DDR2/3 */
81 { 7, 0, 1, 0, 0, 0, 0 },
82
83 /* ARM L4B: 100MHz, for LPDDR2 (SMDKV310 has LPDDR2) */
84 { 7, 0, 1, 0, 0, 0, 0 },
85};
86
87static unsigned int clkdiv_dmc0[CPUFREQ_LEVEL_END + 1][8] = {
88 /*
89 * Clock divider value for following
90 * { DIVACP, DIVACP_PCLK, DIVDPHY, DIVDMC, DIVDMCD
91 * DIVDMCP, DIVCOPY2, DIVCORE_TIMERS }
92 */
93
94 /* DMC L0: 400MHz */
95 { 3, 1, 1, 1, 1, 1, 3, 1 },
96
97 /* DMC L1: 400MHz */
98 { 3, 1, 1, 1, 1, 1, 3, 1 },
99
100 /* DMC L2: 400MHz */
101 { 3, 1, 1, 1, 1, 1, 3, 1 },
102
103 /* DMC L3: 400MHz */
104 { 3, 1, 1, 1, 1, 1, 3, 1 },
105
106 /* DMC L4A: 400MHz, for DDR2/3 */
107 { 7, 1, 1, 1, 1, 1, 3, 1 },
108
109 /* DMC L4B: 200MHz, for LPDDR2 */
110 { 7, 1, 1, 3, 1, 1, 3, 1 },
111};
112
113static unsigned int clkdiv_top[CPUFREQ_LEVEL_END + 1][5] = {
114 /*
115 * Clock divider value for following
116 * { DIVACLK200, DIVACLK100, DIVACLK160, DIVACLK133, DIVONENAND }
117 */
118
119 /* ACLK200 L0: 200MHz */
120 { 3, 7, 4, 5, 1 },
121
122 /* ACLK200 L1: 200MHz */
123 { 3, 7, 4, 5, 1 },
124
125 /* ACLK200 L2: 200MHz */
126 { 3, 7, 4, 5, 1 },
127
128 /* ACLK200 L3: 200MHz */
129 { 3, 7, 4, 5, 1 },
130
131 /* ACLK200 L4A: 100MHz */
132 { 7, 7, 7, 7, 1 },
133
134 /* ACLK200 L4B: 100MHz */
135 { 7, 7, 7, 7, 1 },
136};
137
138static unsigned int clkdiv_lr_bus[CPUFREQ_LEVEL_END + 1][2] = {
139 /*
140 * Clock divider value for following
141 * { DIVGDL/R, DIVGPL/R }
142 */
143
144 /* ACLK_GDL/R L0: 200MHz */
145 { 3, 1 },
146
147 /* ACLK_GDL/R L1: 200MHz */
148 { 3, 1 },
149
150 /* ACLK_GDL/R L2: 200MHz */
151 { 3, 1 },
152
153 /* ACLK_GDL/R L3: 200MHz */
154 { 3, 1 },
155
156 /* ACLK_GDL/R L4A: 100MHz */
157 { 7, 1 },
158
159 /* ACLK_GDL/R L4B: 100MHz */
160 { 7, 1 },
161};
162
163struct cpufreq_voltage_table {
164 unsigned int index; /* any */
165 unsigned int arm_volt; /* uV */
166 unsigned int int_volt;
167};
168
169static struct cpufreq_voltage_table s5pv310_volt_table[] = {
170 {
171 .index = L0,
172 .arm_volt = 1200000,
173 .int_volt = 1100000,
174 }, {
175 .index = L1,
176 .arm_volt = 1100000,
177 .int_volt = 1100000,
178 }, {
179 .index = L2,
180 .arm_volt = 1050000,
181 .int_volt = 1100000,
182 }, {
183 .index = L3,
184 .arm_volt = 1050000,
185 .int_volt = 1100000,
186 }, {
187 .index = L4,
188 .arm_volt = 1000000,
189 .int_volt = 1000000,
190 },
191};
192
193int s5pv310_verify_speed(struct cpufreq_policy *policy)
194{
195 return cpufreq_frequency_table_verify(policy, s5pv310_freq_table);
196}
197
198unsigned int s5pv310_getspeed(unsigned int cpu)
199{
200 return clk_get_rate(cpu_clk) / 1000;
201}
202
203void s5pv310_set_clkdiv(unsigned int div_index)
204{
205 unsigned int tmp;
206
207 /* Change Divider - CPU0 */
208
209 tmp = __raw_readl(S5P_CLKDIV_CPU);
210
211 tmp &= ~(S5P_CLKDIV_CPU0_CORE_MASK | S5P_CLKDIV_CPU0_COREM0_MASK |
212 S5P_CLKDIV_CPU0_COREM1_MASK | S5P_CLKDIV_CPU0_PERIPH_MASK |
213 S5P_CLKDIV_CPU0_ATB_MASK | S5P_CLKDIV_CPU0_PCLKDBG_MASK |
214 S5P_CLKDIV_CPU0_APLL_MASK);
215
216 tmp |= ((clkdiv_cpu0[div_index][0] << S5P_CLKDIV_CPU0_CORE_SHIFT) |
217 (clkdiv_cpu0[div_index][1] << S5P_CLKDIV_CPU0_COREM0_SHIFT) |
218 (clkdiv_cpu0[div_index][2] << S5P_CLKDIV_CPU0_COREM1_SHIFT) |
219 (clkdiv_cpu0[div_index][3] << S5P_CLKDIV_CPU0_PERIPH_SHIFT) |
220 (clkdiv_cpu0[div_index][4] << S5P_CLKDIV_CPU0_ATB_SHIFT) |
221 (clkdiv_cpu0[div_index][5] << S5P_CLKDIV_CPU0_PCLKDBG_SHIFT) |
222 (clkdiv_cpu0[div_index][6] << S5P_CLKDIV_CPU0_APLL_SHIFT));
223
224 __raw_writel(tmp, S5P_CLKDIV_CPU);
225
226 do {
227 tmp = __raw_readl(S5P_CLKDIV_STATCPU);
228 } while (tmp & 0x1111111);
229
230 /* Change Divider - DMC0 */
231
232 tmp = __raw_readl(S5P_CLKDIV_DMC0);
233
234 tmp &= ~(S5P_CLKDIV_DMC0_ACP_MASK | S5P_CLKDIV_DMC0_ACPPCLK_MASK |
235 S5P_CLKDIV_DMC0_DPHY_MASK | S5P_CLKDIV_DMC0_DMC_MASK |
236 S5P_CLKDIV_DMC0_DMCD_MASK | S5P_CLKDIV_DMC0_DMCP_MASK |
237 S5P_CLKDIV_DMC0_COPY2_MASK | S5P_CLKDIV_DMC0_CORETI_MASK);
238
239 tmp |= ((clkdiv_dmc0[div_index][0] << S5P_CLKDIV_DMC0_ACP_SHIFT) |
240 (clkdiv_dmc0[div_index][1] << S5P_CLKDIV_DMC0_ACPPCLK_SHIFT) |
241 (clkdiv_dmc0[div_index][2] << S5P_CLKDIV_DMC0_DPHY_SHIFT) |
242 (clkdiv_dmc0[div_index][3] << S5P_CLKDIV_DMC0_DMC_SHIFT) |
243 (clkdiv_dmc0[div_index][4] << S5P_CLKDIV_DMC0_DMCD_SHIFT) |
244 (clkdiv_dmc0[div_index][5] << S5P_CLKDIV_DMC0_DMCP_SHIFT) |
245 (clkdiv_dmc0[div_index][6] << S5P_CLKDIV_DMC0_COPY2_SHIFT) |
246 (clkdiv_dmc0[div_index][7] << S5P_CLKDIV_DMC0_CORETI_SHIFT));
247
248 __raw_writel(tmp, S5P_CLKDIV_DMC0);
249
250 do {
251 tmp = __raw_readl(S5P_CLKDIV_STAT_DMC0);
252 } while (tmp & 0x11111111);
253
254 /* Change Divider - TOP */
255
256 tmp = __raw_readl(S5P_CLKDIV_TOP);
257
258 tmp &= ~(S5P_CLKDIV_TOP_ACLK200_MASK | S5P_CLKDIV_TOP_ACLK100_MASK |
259 S5P_CLKDIV_TOP_ACLK160_MASK | S5P_CLKDIV_TOP_ACLK133_MASK |
260 S5P_CLKDIV_TOP_ONENAND_MASK);
261
262 tmp |= ((clkdiv_top[div_index][0] << S5P_CLKDIV_TOP_ACLK200_SHIFT) |
263 (clkdiv_top[div_index][1] << S5P_CLKDIV_TOP_ACLK100_SHIFT) |
264 (clkdiv_top[div_index][2] << S5P_CLKDIV_TOP_ACLK160_SHIFT) |
265 (clkdiv_top[div_index][3] << S5P_CLKDIV_TOP_ACLK133_SHIFT) |
266 (clkdiv_top[div_index][4] << S5P_CLKDIV_TOP_ONENAND_SHIFT));
267
268 __raw_writel(tmp, S5P_CLKDIV_TOP);
269
270 do {
271 tmp = __raw_readl(S5P_CLKDIV_STAT_TOP);
272 } while (tmp & 0x11111);
273
274 /* Change Divider - LEFTBUS */
275
276 tmp = __raw_readl(S5P_CLKDIV_LEFTBUS);
277
278 tmp &= ~(S5P_CLKDIV_BUS_GDLR_MASK | S5P_CLKDIV_BUS_GPLR_MASK);
279
280 tmp |= ((clkdiv_lr_bus[div_index][0] << S5P_CLKDIV_BUS_GDLR_SHIFT) |
281 (clkdiv_lr_bus[div_index][1] << S5P_CLKDIV_BUS_GPLR_SHIFT));
282
283 __raw_writel(tmp, S5P_CLKDIV_LEFTBUS);
284
285 do {
286 tmp = __raw_readl(S5P_CLKDIV_STAT_LEFTBUS);
287 } while (tmp & 0x11);
288
289 /* Change Divider - RIGHTBUS */
290
291 tmp = __raw_readl(S5P_CLKDIV_RIGHTBUS);
292
293 tmp &= ~(S5P_CLKDIV_BUS_GDLR_MASK | S5P_CLKDIV_BUS_GPLR_MASK);
294
295 tmp |= ((clkdiv_lr_bus[div_index][0] << S5P_CLKDIV_BUS_GDLR_SHIFT) |
296 (clkdiv_lr_bus[div_index][1] << S5P_CLKDIV_BUS_GPLR_SHIFT));
297
298 __raw_writel(tmp, S5P_CLKDIV_RIGHTBUS);
299
300 do {
301 tmp = __raw_readl(S5P_CLKDIV_STAT_RIGHTBUS);
302 } while (tmp & 0x11);
303}
304
305static int s5pv310_target(struct cpufreq_policy *policy,
306 unsigned int target_freq,
307 unsigned int relation)
308{
309 unsigned int index, div_index, tmp;
310 unsigned int arm_volt, int_volt;
311 unsigned int need_apll = 0;
312
313 freqs.old = s5pv310_getspeed(policy->cpu);
314
315 if (cpufreq_frequency_table_target(policy, s5pv310_freq_table,
316 target_freq, relation, &index))
317 return -EINVAL;
318
319 freqs.new = s5pv310_freq_table[index].frequency;
320 freqs.cpu = policy->cpu;
321
322 if (freqs.new == freqs.old)
323 return 0;
324
325 /*
326 * If freqs.new is higher than 800MHz
327 * cpufreq driver should turn on apll
328 */
329 if (index < L1)
330 need_apll = 1;
331
332 /* If the memory type is LPDDR2, use L4-B instead of L4-A */
333 if ((index == L4) && (memtype == LPDDR2))
334 div_index = index + 1;
335 else
336 div_index = index;
337
338 /* get the voltage value */
339 arm_volt = s5pv310_volt_table[index].arm_volt;
340 int_volt = s5pv310_volt_table[index].int_volt;
341
342 cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
343
344 /* control regulator */
345 if (freqs.new > freqs.old) {
346 /* Voltage up */
347#ifdef CONFIG_REGULATOR
348 regulator_set_voltage(arm_regulator, arm_volt, arm_volt);
349 regulator_set_voltage(int_regulator, int_volt, int_volt);
350#endif
351 }
352
353 /* Clock Configuration Procedure */
354
355 /* 1. Change the system clock divider values */
356 s5pv310_set_clkdiv(div_index);
357
358 /* 2. Change the divider values for special clocks in CMU_TOP */
359 /* currently nothing */
360
361 /* 3. Change the XPLL values or Select the parent XPLL */
362 if (need_apll) {
363 if (!armclk_use_apll) {
364 /*
365 * If the parent clock of armclk isn't apll
366 * here need to set apll (include m,p,s value)
367 */
368
369 /* a. MUX_CORE_SEL = MPLL,
370 * ARMCLK uses MPLL for lock time */
371 clk_set_parent(moutcore, mout_mpll);
372
373 do {
374 tmp = (__raw_readl(S5P_CLKMUX_STATCPU)
375 >> S5P_CLKSRC_CPU_MUXCORE_SHIFT);
376 tmp &= 0x7;
377 } while (tmp != 0x2);
378
379 /* b. Set APLL Lock time */
380 __raw_writel(S5P_APLL_LOCKTIME, S5P_APLL_LOCK);
381
382 /* c. Change PLL PMS values */
383 __raw_writel(S5P_APLL_VAL_1000, S5P_APLL_CON0);
384
385 /* d. Turn on a PLL */
386 tmp = __raw_readl(S5P_APLL_CON0);
387 tmp |= (0x1 << S5P_APLLCON0_ENABLE_SHIFT);
388 __raw_writel(tmp, S5P_APLL_CON0);
389
390 /* e. wait_lock_time */
391 do {
392 tmp = __raw_readl(S5P_APLL_CON0);
393 } while (!(tmp & (0x1 << S5P_APLLCON0_LOCKED_SHIFT)));
394
395 armclk_use_apll = 1;
396
397 }
398
399 /* MUX_CORE_SEL = APLL */
400 clk_set_parent(moutcore, mout_apll);
401
402 do {
403 tmp = __raw_readl(S5P_CLKMUX_STATCPU);
404 tmp &= S5P_CLKMUX_STATCPU_MUXCORE_MASK;
405 } while (tmp != (0x1 << S5P_CLKSRC_CPU_MUXCORE_SHIFT));
406
407 } else {
408 if (clk_get_parent(moutcore) != mout_mpll) {
409 clk_set_parent(moutcore, mout_mpll);
410
411 do {
412 tmp = __raw_readl(S5P_CLKMUX_STATCPU);
413 tmp &= S5P_CLKMUX_STATCPU_MUXCORE_MASK;
414 } while (tmp != (0x2 << S5P_CLKSRC_CPU_MUXCORE_SHIFT));
415 }
416 }
417
418 /* control regulator */
419 if (freqs.new < freqs.old) {
420 /* Voltage down */
421#ifdef CONFIG_REGULATOR
422 regulator_set_voltage(arm_regulator, arm_volt, arm_volt);
423 regulator_set_voltage(int_regulator, int_volt, int_volt);
424#endif
425 }
426
427 cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
428
429 return 0;
430}
431
432#ifdef CONFIG_PM
433static int s5pv310_cpufreq_suspend(struct cpufreq_policy *policy,
434 pm_message_t pmsg)
435{
436 return 0;
437}
438
439static int s5pv310_cpufreq_resume(struct cpufreq_policy *policy)
440{
441 return 0;
442}
443#endif
444
445static int s5pv310_cpufreq_cpu_init(struct cpufreq_policy *policy)
446{
447 policy->cur = policy->min = policy->max = s5pv310_getspeed(policy->cpu);
448
449 cpufreq_frequency_table_get_attr(s5pv310_freq_table, policy->cpu);
450
451 /* set the transition latency value */
452 policy->cpuinfo.transition_latency = 100000;
453
454 /*
455 * S5PV310 multi-core processors has 2 cores
456 * that the frequency cannot be set independently.
457 * Each cpu is bound to the same speed.
458 * So the affected cpu is all of the cpus.
459 */
460 cpumask_setall(policy->cpus);
461
462 return cpufreq_frequency_table_cpuinfo(policy, s5pv310_freq_table);
463}
464
465static struct cpufreq_driver s5pv310_driver = {
466 .flags = CPUFREQ_STICKY,
467 .verify = s5pv310_verify_speed,
468 .target = s5pv310_target,
469 .get = s5pv310_getspeed,
470 .init = s5pv310_cpufreq_cpu_init,
471 .name = "s5pv310_cpufreq",
472#ifdef CONFIG_PM
473 .suspend = s5pv310_cpufreq_suspend,
474 .resume = s5pv310_cpufreq_resume,
475#endif
476};
477
478static int __init s5pv310_cpufreq_init(void)
479{
480 unsigned int tmp;
481
482 cpu_clk = clk_get(NULL, "armclk");
483 if (IS_ERR(cpu_clk))
484 return PTR_ERR(cpu_clk);
485
486 moutcore = clk_get(NULL, "moutcore");
487 if (IS_ERR(moutcore))
488 goto out;
489
490 mout_mpll = clk_get(NULL, "mout_mpll");
491 if (IS_ERR(mout_mpll))
492 goto out;
493
494 mout_apll = clk_get(NULL, "mout_apll");
495 if (IS_ERR(mout_apll))
496 goto out;
497
498#ifdef CONFIG_REGULATOR
499 arm_regulator = regulator_get(NULL, "vdd_arm");
500 if (IS_ERR(arm_regulator)) {
501 printk(KERN_ERR "failed to get resource %s\n", "vdd_arm");
502 goto out;
503 }
504
505 int_regulator = regulator_get(NULL, "vdd_int");
506 if (IS_ERR(int_regulator)) {
507 printk(KERN_ERR "failed to get resource %s\n", "vdd_int");
508 goto out;
509 }
510#endif
511
512 /* check parent clock of armclk */
513 tmp = __raw_readl(S5P_CLKSRC_CPU);
514 if (tmp & S5P_CLKSRC_CPU_MUXCORE_SHIFT)
515 armclk_use_apll = 0;
516 else
517 armclk_use_apll = 1;
518
519 /*
520 * Check DRAM type.
521 * Because DVFS level is different according to DRAM type.
522 */
523 memtype = __raw_readl(S5P_VA_DMC0 + S5P_DMC0_MEMCON_OFFSET);
524 memtype = (memtype >> S5P_DMC0_MEMTYPE_SHIFT);
525 memtype &= S5P_DMC0_MEMTYPE_MASK;
526
527 if ((memtype < DDR2) && (memtype > DDR3)) {
528 printk(KERN_ERR "%s: wrong memtype= 0x%x\n", __func__, memtype);
529 goto out;
530 } else {
531 printk(KERN_DEBUG "%s: memtype= 0x%x\n", __func__, memtype);
532 }
533
534 return cpufreq_register_driver(&s5pv310_driver);
535
536out:
537 if (!IS_ERR(cpu_clk))
538 clk_put(cpu_clk);
539
540 if (!IS_ERR(moutcore))
541 clk_put(moutcore);
542
543 if (!IS_ERR(mout_mpll))
544 clk_put(mout_mpll);
545
546 if (!IS_ERR(mout_apll))
547 clk_put(mout_apll);
548
549#ifdef CONFIG_REGULATOR
550 if (!IS_ERR(arm_regulator))
551 regulator_put(arm_regulator);
552
553 if (!IS_ERR(int_regulator))
554 regulator_put(int_regulator);
555#endif
556
557 printk(KERN_ERR "%s: failed initialization\n", __func__);
558
559 return -EINVAL;
560}
561late_initcall(s5pv310_cpufreq_init);