summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorKukjin Kim <kgene.kim@samsung.com>2011-06-01 17:18:22 -0400
committerDave Jones <davej@redhat.com>2011-07-13 18:29:51 -0400
commitf7d770790f29781116d0de1339214934b8020c1e (patch)
treeecdc61b77b8d7c560ea35dd40169a2c2452db9b8 /drivers
parentbe2de99beaca6506a1f97a636750c108a41b5c00 (diff)
[CPUFREQ] Move ARM Samsung cpufreq drivers to drivers/cpufreq/
According to discussion of the ARM arch subsystem migration, ARM cpufreq drivers move to drivers/cpufreq. So this patch adds Kconfig.arm for ARM like x86 and adds Samsung S5PV210 and EXYNOS4210 cpufreq driver compile in there. As a note, otherw will be moved. Cc: Dave Jones <davej@redhat.com> Signed-off-by: Kukjin Kim <kgene.kim@samsung.com> Signed-off-by: Dave Jones <davej@redhat.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/cpufreq/Kconfig5
-rw-r--r--drivers/cpufreq/Kconfig.arm23
-rw-r--r--drivers/cpufreq/Makefile2
-rw-r--r--drivers/cpufreq/exynos4210-cpufreq.c568
-rw-r--r--drivers/cpufreq/s5pv210-cpufreq.c484
5 files changed, 1082 insertions, 0 deletions
diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig
index 9fb84853d8e3..e898215b88af 100644
--- a/drivers/cpufreq/Kconfig
+++ b/drivers/cpufreq/Kconfig
@@ -184,5 +184,10 @@ depends on X86
184source "drivers/cpufreq/Kconfig.x86" 184source "drivers/cpufreq/Kconfig.x86"
185endmenu 185endmenu
186 186
187menu "ARM CPU frequency scaling drivers"
188depends on ARM
189source "drivers/cpufreq/Kconfig.arm"
190endmenu
191
187endif 192endif
188endmenu 193endmenu
diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
new file mode 100644
index 000000000000..e5c56c7b3389
--- /dev/null
+++ b/drivers/cpufreq/Kconfig.arm
@@ -0,0 +1,23 @@
1#
2# ARM CPU Frequency scaling drivers
3#
4
5config ARM_S5PV210_CPUFREQ
6 bool "Samsung S5PV210 and S5PC110"
7 depends on CPU_S5PV210
8 default y
9 help
10 This adds the CPUFreq driver for Samsung S5PV210 and
11 S5PC110 SoCs.
12
13 If in doubt, say N.
14
15config ARM_EXYNOS4210_CPUFREQ
16 bool "Samsung EXYNOS4210"
17 depends on CPU_EXYNOS4210
18 default y
19 help
20 This adds the CPUFreq driver for Samsung EXYNOS4210
21 SoC (S5PV310 or S5PC210).
22
23 If in doubt, say N.
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index 0fd8cae1c828..9922294cc775 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -42,3 +42,5 @@ obj-$(CONFIG_X86_CPUFREQ_NFORCE2) += cpufreq-nforce2.o
42# ARM SoC drivers 42# ARM SoC drivers
43obj-$(CONFIG_UX500_SOC_DB8500) += db8500-cpufreq.o 43obj-$(CONFIG_UX500_SOC_DB8500) += db8500-cpufreq.o
44obj-$(CONFIG_CPU_FREQ_S3C64XX) += s3c64xx.o 44obj-$(CONFIG_CPU_FREQ_S3C64XX) += s3c64xx.o
45obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o
46obj-$(CONFIG_ARM_EXYNOS4210_CPUFREQ) += exynos4210-cpufreq.o
diff --git a/drivers/cpufreq/exynos4210-cpufreq.c b/drivers/cpufreq/exynos4210-cpufreq.c
new file mode 100644
index 000000000000..54025fc4b52b
--- /dev/null
+++ b/drivers/cpufreq/exynos4210-cpufreq.c
@@ -0,0 +1,568 @@
1/*
2 * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
3 * http://www.samsung.com
4 *
5 * EXYNOS4 - CPU frequency scaling support
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#include <linux/types.h>
13#include <linux/kernel.h>
14#include <linux/err.h>
15#include <linux/clk.h>
16#include <linux/io.h>
17#include <linux/slab.h>
18#include <linux/regulator/consumer.h>
19#include <linux/cpufreq.h>
20
21#include <mach/map.h>
22#include <mach/regs-clock.h>
23#include <mach/regs-mem.h>
24
25#include <plat/clock.h>
26#include <plat/pm.h>
27
28static struct clk *cpu_clk;
29static struct clk *moutcore;
30static struct clk *mout_mpll;
31static struct clk *mout_apll;
32
33static struct regulator *arm_regulator;
34static struct regulator *int_regulator;
35
36static struct cpufreq_freqs freqs;
37static unsigned int memtype;
38
39enum exynos4_memory_type {
40 DDR2 = 4,
41 LPDDR2,
42 DDR3,
43};
44
45enum cpufreq_level_index {
46 L0, L1, L2, L3, CPUFREQ_LEVEL_END,
47};
48
49static struct cpufreq_frequency_table exynos4_freq_table[] = {
50 {L0, 1000*1000},
51 {L1, 800*1000},
52 {L2, 400*1000},
53 {L3, 100*1000},
54 {0, CPUFREQ_TABLE_END},
55};
56
57static unsigned int clkdiv_cpu0[CPUFREQ_LEVEL_END][7] = {
58 /*
59 * Clock divider value for following
60 * { DIVCORE, DIVCOREM0, DIVCOREM1, DIVPERIPH,
61 * DIVATB, DIVPCLK_DBG, DIVAPLL }
62 */
63
64 /* ARM L0: 1000MHz */
65 { 0, 3, 7, 3, 3, 0, 1 },
66
67 /* ARM L1: 800MHz */
68 { 0, 3, 7, 3, 3, 0, 1 },
69
70 /* ARM L2: 400MHz */
71 { 0, 1, 3, 1, 3, 0, 1 },
72
73 /* ARM L3: 100MHz */
74 { 0, 0, 1, 0, 3, 1, 1 },
75};
76
77static unsigned int clkdiv_cpu1[CPUFREQ_LEVEL_END][2] = {
78 /*
79 * Clock divider value for following
80 * { DIVCOPY, DIVHPM }
81 */
82
83 /* ARM L0: 1000MHz */
84 { 3, 0 },
85
86 /* ARM L1: 800MHz */
87 { 3, 0 },
88
89 /* ARM L2: 400MHz */
90 { 3, 0 },
91
92 /* ARM L3: 100MHz */
93 { 3, 0 },
94};
95
96static unsigned int clkdiv_dmc0[CPUFREQ_LEVEL_END][8] = {
97 /*
98 * Clock divider value for following
99 * { DIVACP, DIVACP_PCLK, DIVDPHY, DIVDMC, DIVDMCD
100 * DIVDMCP, DIVCOPY2, DIVCORE_TIMERS }
101 */
102
103 /* DMC L0: 400MHz */
104 { 3, 1, 1, 1, 1, 1, 3, 1 },
105
106 /* DMC L1: 400MHz */
107 { 3, 1, 1, 1, 1, 1, 3, 1 },
108
109 /* DMC L2: 266.7MHz */
110 { 7, 1, 1, 2, 1, 1, 3, 1 },
111
112 /* DMC L3: 200MHz */
113 { 7, 1, 1, 3, 1, 1, 3, 1 },
114};
115
116static unsigned int clkdiv_top[CPUFREQ_LEVEL_END][5] = {
117 /*
118 * Clock divider value for following
119 * { DIVACLK200, DIVACLK100, DIVACLK160, DIVACLK133, DIVONENAND }
120 */
121
122 /* ACLK200 L0: 200MHz */
123 { 3, 7, 4, 5, 1 },
124
125 /* ACLK200 L1: 200MHz */
126 { 3, 7, 4, 5, 1 },
127
128 /* ACLK200 L2: 160MHz */
129 { 4, 7, 5, 7, 1 },
130
131 /* ACLK200 L3: 133.3MHz */
132 { 5, 7, 7, 7, 1 },
133};
134
135static unsigned int clkdiv_lr_bus[CPUFREQ_LEVEL_END][2] = {
136 /*
137 * Clock divider value for following
138 * { DIVGDL/R, DIVGPL/R }
139 */
140
141 /* ACLK_GDL/R L0: 200MHz */
142 { 3, 1 },
143
144 /* ACLK_GDL/R L1: 200MHz */
145 { 3, 1 },
146
147 /* ACLK_GDL/R L2: 160MHz */
148 { 4, 1 },
149
150 /* ACLK_GDL/R L3: 133.3MHz */
151 { 5, 1 },
152};
153
154struct cpufreq_voltage_table {
155 unsigned int index; /* any */
156 unsigned int arm_volt; /* uV */
157 unsigned int int_volt;
158};
159
160static struct cpufreq_voltage_table exynos4_volt_table[CPUFREQ_LEVEL_END] = {
161 {
162 .index = L0,
163 .arm_volt = 1200000,
164 .int_volt = 1100000,
165 }, {
166 .index = L1,
167 .arm_volt = 1100000,
168 .int_volt = 1100000,
169 }, {
170 .index = L2,
171 .arm_volt = 1000000,
172 .int_volt = 1000000,
173 }, {
174 .index = L3,
175 .arm_volt = 900000,
176 .int_volt = 1000000,
177 },
178};
179
180static unsigned int exynos4_apll_pms_table[CPUFREQ_LEVEL_END] = {
181 /* APLL FOUT L0: 1000MHz */
182 ((250 << 16) | (6 << 8) | 1),
183
184 /* APLL FOUT L1: 800MHz */
185 ((200 << 16) | (6 << 8) | 1),
186
187 /* APLL FOUT L2 : 400MHz */
188 ((200 << 16) | (6 << 8) | 2),
189
190 /* APLL FOUT L3: 100MHz */
191 ((200 << 16) | (6 << 8) | 4),
192};
193
194int exynos4_verify_speed(struct cpufreq_policy *policy)
195{
196 return cpufreq_frequency_table_verify(policy, exynos4_freq_table);
197}
198
199unsigned int exynos4_getspeed(unsigned int cpu)
200{
201 return clk_get_rate(cpu_clk) / 1000;
202}
203
204void exynos4_set_clkdiv(unsigned int div_index)
205{
206 unsigned int tmp;
207
208 /* Change Divider - CPU0 */
209
210 tmp = __raw_readl(S5P_CLKDIV_CPU);
211
212 tmp &= ~(S5P_CLKDIV_CPU0_CORE_MASK | S5P_CLKDIV_CPU0_COREM0_MASK |
213 S5P_CLKDIV_CPU0_COREM1_MASK | S5P_CLKDIV_CPU0_PERIPH_MASK |
214 S5P_CLKDIV_CPU0_ATB_MASK | S5P_CLKDIV_CPU0_PCLKDBG_MASK |
215 S5P_CLKDIV_CPU0_APLL_MASK);
216
217 tmp |= ((clkdiv_cpu0[div_index][0] << S5P_CLKDIV_CPU0_CORE_SHIFT) |
218 (clkdiv_cpu0[div_index][1] << S5P_CLKDIV_CPU0_COREM0_SHIFT) |
219 (clkdiv_cpu0[div_index][2] << S5P_CLKDIV_CPU0_COREM1_SHIFT) |
220 (clkdiv_cpu0[div_index][3] << S5P_CLKDIV_CPU0_PERIPH_SHIFT) |
221 (clkdiv_cpu0[div_index][4] << S5P_CLKDIV_CPU0_ATB_SHIFT) |
222 (clkdiv_cpu0[div_index][5] << S5P_CLKDIV_CPU0_PCLKDBG_SHIFT) |
223 (clkdiv_cpu0[div_index][6] << S5P_CLKDIV_CPU0_APLL_SHIFT));
224
225 __raw_writel(tmp, S5P_CLKDIV_CPU);
226
227 do {
228 tmp = __raw_readl(S5P_CLKDIV_STATCPU);
229 } while (tmp & 0x1111111);
230
231 /* Change Divider - CPU1 */
232
233 tmp = __raw_readl(S5P_CLKDIV_CPU1);
234
235 tmp &= ~((0x7 << 4) | 0x7);
236
237 tmp |= ((clkdiv_cpu1[div_index][0] << 4) |
238 (clkdiv_cpu1[div_index][1] << 0));
239
240 __raw_writel(tmp, S5P_CLKDIV_CPU1);
241
242 do {
243 tmp = __raw_readl(S5P_CLKDIV_STATCPU1);
244 } while (tmp & 0x11);
245
246 /* Change Divider - DMC0 */
247
248 tmp = __raw_readl(S5P_CLKDIV_DMC0);
249
250 tmp &= ~(S5P_CLKDIV_DMC0_ACP_MASK | S5P_CLKDIV_DMC0_ACPPCLK_MASK |
251 S5P_CLKDIV_DMC0_DPHY_MASK | S5P_CLKDIV_DMC0_DMC_MASK |
252 S5P_CLKDIV_DMC0_DMCD_MASK | S5P_CLKDIV_DMC0_DMCP_MASK |
253 S5P_CLKDIV_DMC0_COPY2_MASK | S5P_CLKDIV_DMC0_CORETI_MASK);
254
255 tmp |= ((clkdiv_dmc0[div_index][0] << S5P_CLKDIV_DMC0_ACP_SHIFT) |
256 (clkdiv_dmc0[div_index][1] << S5P_CLKDIV_DMC0_ACPPCLK_SHIFT) |
257 (clkdiv_dmc0[div_index][2] << S5P_CLKDIV_DMC0_DPHY_SHIFT) |
258 (clkdiv_dmc0[div_index][3] << S5P_CLKDIV_DMC0_DMC_SHIFT) |
259 (clkdiv_dmc0[div_index][4] << S5P_CLKDIV_DMC0_DMCD_SHIFT) |
260 (clkdiv_dmc0[div_index][5] << S5P_CLKDIV_DMC0_DMCP_SHIFT) |
261 (clkdiv_dmc0[div_index][6] << S5P_CLKDIV_DMC0_COPY2_SHIFT) |
262 (clkdiv_dmc0[div_index][7] << S5P_CLKDIV_DMC0_CORETI_SHIFT));
263
264 __raw_writel(tmp, S5P_CLKDIV_DMC0);
265
266 do {
267 tmp = __raw_readl(S5P_CLKDIV_STAT_DMC0);
268 } while (tmp & 0x11111111);
269
270 /* Change Divider - TOP */
271
272 tmp = __raw_readl(S5P_CLKDIV_TOP);
273
274 tmp &= ~(S5P_CLKDIV_TOP_ACLK200_MASK | S5P_CLKDIV_TOP_ACLK100_MASK |
275 S5P_CLKDIV_TOP_ACLK160_MASK | S5P_CLKDIV_TOP_ACLK133_MASK |
276 S5P_CLKDIV_TOP_ONENAND_MASK);
277
278 tmp |= ((clkdiv_top[div_index][0] << S5P_CLKDIV_TOP_ACLK200_SHIFT) |
279 (clkdiv_top[div_index][1] << S5P_CLKDIV_TOP_ACLK100_SHIFT) |
280 (clkdiv_top[div_index][2] << S5P_CLKDIV_TOP_ACLK160_SHIFT) |
281 (clkdiv_top[div_index][3] << S5P_CLKDIV_TOP_ACLK133_SHIFT) |
282 (clkdiv_top[div_index][4] << S5P_CLKDIV_TOP_ONENAND_SHIFT));
283
284 __raw_writel(tmp, S5P_CLKDIV_TOP);
285
286 do {
287 tmp = __raw_readl(S5P_CLKDIV_STAT_TOP);
288 } while (tmp & 0x11111);
289
290 /* Change Divider - LEFTBUS */
291
292 tmp = __raw_readl(S5P_CLKDIV_LEFTBUS);
293
294 tmp &= ~(S5P_CLKDIV_BUS_GDLR_MASK | S5P_CLKDIV_BUS_GPLR_MASK);
295
296 tmp |= ((clkdiv_lr_bus[div_index][0] << S5P_CLKDIV_BUS_GDLR_SHIFT) |
297 (clkdiv_lr_bus[div_index][1] << S5P_CLKDIV_BUS_GPLR_SHIFT));
298
299 __raw_writel(tmp, S5P_CLKDIV_LEFTBUS);
300
301 do {
302 tmp = __raw_readl(S5P_CLKDIV_STAT_LEFTBUS);
303 } while (tmp & 0x11);
304
305 /* Change Divider - RIGHTBUS */
306
307 tmp = __raw_readl(S5P_CLKDIV_RIGHTBUS);
308
309 tmp &= ~(S5P_CLKDIV_BUS_GDLR_MASK | S5P_CLKDIV_BUS_GPLR_MASK);
310
311 tmp |= ((clkdiv_lr_bus[div_index][0] << S5P_CLKDIV_BUS_GDLR_SHIFT) |
312 (clkdiv_lr_bus[div_index][1] << S5P_CLKDIV_BUS_GPLR_SHIFT));
313
314 __raw_writel(tmp, S5P_CLKDIV_RIGHTBUS);
315
316 do {
317 tmp = __raw_readl(S5P_CLKDIV_STAT_RIGHTBUS);
318 } while (tmp & 0x11);
319}
320
321static void exynos4_set_apll(unsigned int index)
322{
323 unsigned int tmp;
324
325 /* 1. MUX_CORE_SEL = MPLL, ARMCLK uses MPLL for lock time */
326 clk_set_parent(moutcore, mout_mpll);
327
328 do {
329 tmp = (__raw_readl(S5P_CLKMUX_STATCPU)
330 >> S5P_CLKSRC_CPU_MUXCORE_SHIFT);
331 tmp &= 0x7;
332 } while (tmp != 0x2);
333
334 /* 2. Set APLL Lock time */
335 __raw_writel(S5P_APLL_LOCKTIME, S5P_APLL_LOCK);
336
337 /* 3. Change PLL PMS values */
338 tmp = __raw_readl(S5P_APLL_CON0);
339 tmp &= ~((0x3ff << 16) | (0x3f << 8) | (0x7 << 0));
340 tmp |= exynos4_apll_pms_table[index];
341 __raw_writel(tmp, S5P_APLL_CON0);
342
343 /* 4. wait_lock_time */
344 do {
345 tmp = __raw_readl(S5P_APLL_CON0);
346 } while (!(tmp & (0x1 << S5P_APLLCON0_LOCKED_SHIFT)));
347
348 /* 5. MUX_CORE_SEL = APLL */
349 clk_set_parent(moutcore, mout_apll);
350
351 do {
352 tmp = __raw_readl(S5P_CLKMUX_STATCPU);
353 tmp &= S5P_CLKMUX_STATCPU_MUXCORE_MASK;
354 } while (tmp != (0x1 << S5P_CLKSRC_CPU_MUXCORE_SHIFT));
355}
356
357static void exynos4_set_frequency(unsigned int old_index, unsigned int new_index)
358{
359 unsigned int tmp;
360
361 if (old_index > new_index) {
362 /* The frequency changing to L0 needs to change apll */
363 if (freqs.new == exynos4_freq_table[L0].frequency) {
364 /* 1. Change the system clock divider values */
365 exynos4_set_clkdiv(new_index);
366
367 /* 2. Change the apll m,p,s value */
368 exynos4_set_apll(new_index);
369 } else {
370 /* 1. Change the system clock divider values */
371 exynos4_set_clkdiv(new_index);
372
373 /* 2. Change just s value in apll m,p,s value */
374 tmp = __raw_readl(S5P_APLL_CON0);
375 tmp &= ~(0x7 << 0);
376 tmp |= (exynos4_apll_pms_table[new_index] & 0x7);
377 __raw_writel(tmp, S5P_APLL_CON0);
378 }
379 }
380
381 else if (old_index < new_index) {
382 /* The frequency changing from L0 needs to change apll */
383 if (freqs.old == exynos4_freq_table[L0].frequency) {
384 /* 1. Change the apll m,p,s value */
385 exynos4_set_apll(new_index);
386
387 /* 2. Change the system clock divider values */
388 exynos4_set_clkdiv(new_index);
389 } else {
390 /* 1. Change just s value in apll m,p,s value */
391 tmp = __raw_readl(S5P_APLL_CON0);
392 tmp &= ~(0x7 << 0);
393 tmp |= (exynos4_apll_pms_table[new_index] & 0x7);
394 __raw_writel(tmp, S5P_APLL_CON0);
395
396 /* 2. Change the system clock divider values */
397 exynos4_set_clkdiv(new_index);
398 }
399 }
400}
401
402static int exynos4_target(struct cpufreq_policy *policy,
403 unsigned int target_freq,
404 unsigned int relation)
405{
406 unsigned int index, old_index;
407 unsigned int arm_volt, int_volt;
408
409 freqs.old = exynos4_getspeed(policy->cpu);
410
411 if (cpufreq_frequency_table_target(policy, exynos4_freq_table,
412 freqs.old, relation, &old_index))
413 return -EINVAL;
414
415 if (cpufreq_frequency_table_target(policy, exynos4_freq_table,
416 target_freq, relation, &index))
417 return -EINVAL;
418
419 freqs.new = exynos4_freq_table[index].frequency;
420 freqs.cpu = policy->cpu;
421
422 if (freqs.new == freqs.old)
423 return 0;
424
425 /* get the voltage value */
426 arm_volt = exynos4_volt_table[index].arm_volt;
427 int_volt = exynos4_volt_table[index].int_volt;
428
429 cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
430
431 /* control regulator */
432 if (freqs.new > freqs.old) {
433 /* Voltage up */
434 regulator_set_voltage(arm_regulator, arm_volt, arm_volt);
435 regulator_set_voltage(int_regulator, int_volt, int_volt);
436 }
437
438 /* Clock Configuration Procedure */
439 exynos4_set_frequency(old_index, index);
440
441 /* control regulator */
442 if (freqs.new < freqs.old) {
443 /* Voltage down */
444 regulator_set_voltage(arm_regulator, arm_volt, arm_volt);
445 regulator_set_voltage(int_regulator, int_volt, int_volt);
446 }
447
448 cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
449
450 return 0;
451}
452
453#ifdef CONFIG_PM
454static int exynos4_cpufreq_suspend(struct cpufreq_policy *policy)
455{
456 return 0;
457}
458
459static int exynos4_cpufreq_resume(struct cpufreq_policy *policy)
460{
461 return 0;
462}
463#endif
464
465static int exynos4_cpufreq_cpu_init(struct cpufreq_policy *policy)
466{
467 policy->cur = policy->min = policy->max = exynos4_getspeed(policy->cpu);
468
469 cpufreq_frequency_table_get_attr(exynos4_freq_table, policy->cpu);
470
471 /* set the transition latency value */
472 policy->cpuinfo.transition_latency = 100000;
473
474 /*
475 * EXYNOS4 multi-core processors has 2 cores
476 * that the frequency cannot be set independently.
477 * Each cpu is bound to the same speed.
478 * So the affected cpu is all of the cpus.
479 */
480 cpumask_setall(policy->cpus);
481
482 return cpufreq_frequency_table_cpuinfo(policy, exynos4_freq_table);
483}
484
485static struct cpufreq_driver exynos4_driver = {
486 .flags = CPUFREQ_STICKY,
487 .verify = exynos4_verify_speed,
488 .target = exynos4_target,
489 .get = exynos4_getspeed,
490 .init = exynos4_cpufreq_cpu_init,
491 .name = "exynos4_cpufreq",
492#ifdef CONFIG_PM
493 .suspend = exynos4_cpufreq_suspend,
494 .resume = exynos4_cpufreq_resume,
495#endif
496};
497
498static int __init exynos4_cpufreq_init(void)
499{
500 cpu_clk = clk_get(NULL, "armclk");
501 if (IS_ERR(cpu_clk))
502 return PTR_ERR(cpu_clk);
503
504 moutcore = clk_get(NULL, "moutcore");
505 if (IS_ERR(moutcore))
506 goto out;
507
508 mout_mpll = clk_get(NULL, "mout_mpll");
509 if (IS_ERR(mout_mpll))
510 goto out;
511
512 mout_apll = clk_get(NULL, "mout_apll");
513 if (IS_ERR(mout_apll))
514 goto out;
515
516 arm_regulator = regulator_get(NULL, "vdd_arm");
517 if (IS_ERR(arm_regulator)) {
518 printk(KERN_ERR "failed to get resource %s\n", "vdd_arm");
519 goto out;
520 }
521
522 int_regulator = regulator_get(NULL, "vdd_int");
523 if (IS_ERR(int_regulator)) {
524 printk(KERN_ERR "failed to get resource %s\n", "vdd_int");
525 goto out;
526 }
527
528 /*
529 * Check DRAM type.
530 * Because DVFS level is different according to DRAM type.
531 */
532 memtype = __raw_readl(S5P_VA_DMC0 + S5P_DMC0_MEMCON_OFFSET);
533 memtype = (memtype >> S5P_DMC0_MEMTYPE_SHIFT);
534 memtype &= S5P_DMC0_MEMTYPE_MASK;
535
536 if ((memtype < DDR2) && (memtype > DDR3)) {
537 printk(KERN_ERR "%s: wrong memtype= 0x%x\n", __func__, memtype);
538 goto out;
539 } else {
540 printk(KERN_DEBUG "%s: memtype= 0x%x\n", __func__, memtype);
541 }
542
543 return cpufreq_register_driver(&exynos4_driver);
544
545out:
546 if (!IS_ERR(cpu_clk))
547 clk_put(cpu_clk);
548
549 if (!IS_ERR(moutcore))
550 clk_put(moutcore);
551
552 if (!IS_ERR(mout_mpll))
553 clk_put(mout_mpll);
554
555 if (!IS_ERR(mout_apll))
556 clk_put(mout_apll);
557
558 if (!IS_ERR(arm_regulator))
559 regulator_put(arm_regulator);
560
561 if (!IS_ERR(int_regulator))
562 regulator_put(int_regulator);
563
564 printk(KERN_ERR "%s: failed initialization\n", __func__);
565
566 return -EINVAL;
567}
568late_initcall(exynos4_cpufreq_init);
diff --git a/drivers/cpufreq/s5pv210-cpufreq.c b/drivers/cpufreq/s5pv210-cpufreq.c
new file mode 100644
index 000000000000..ea35d3f74e3d
--- /dev/null
+++ b/drivers/cpufreq/s5pv210-cpufreq.c
@@ -0,0 +1,484 @@
1/*
2 * Copyright (c) 2010 Samsung Electronics Co., Ltd.
3 * http://www.samsung.com
4 *
5 * CPU frequency scaling for S5PC110/S5PV210
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#include <linux/types.h>
13#include <linux/kernel.h>
14#include <linux/init.h>
15#include <linux/err.h>
16#include <linux/clk.h>
17#include <linux/io.h>
18#include <linux/cpufreq.h>
19
20#include <mach/map.h>
21#include <mach/regs-clock.h>
22
23static struct clk *cpu_clk;
24static struct clk *dmc0_clk;
25static struct clk *dmc1_clk;
26static struct cpufreq_freqs freqs;
27
28/* APLL M,P,S values for 1G/800Mhz */
29#define APLL_VAL_1000 ((1 << 31) | (125 << 16) | (3 << 8) | 1)
30#define APLL_VAL_800 ((1 << 31) | (100 << 16) | (3 << 8) | 1)
31
32/*
33 * DRAM configurations to calculate refresh counter for changing
34 * frequency of memory.
35 */
36struct dram_conf {
37 unsigned long freq; /* HZ */
38 unsigned long refresh; /* DRAM refresh counter * 1000 */
39};
40
41/* DRAM configuration (DMC0 and DMC1) */
42static struct dram_conf s5pv210_dram_conf[2];
43
44enum perf_level {
45 L0, L1, L2, L3, L4,
46};
47
48enum s5pv210_mem_type {
49 LPDDR = 0x1,
50 LPDDR2 = 0x2,
51 DDR2 = 0x4,
52};
53
54enum s5pv210_dmc_port {
55 DMC0 = 0,
56 DMC1,
57};
58
59static struct cpufreq_frequency_table s5pv210_freq_table[] = {
60 {L0, 1000*1000},
61 {L1, 800*1000},
62 {L2, 400*1000},
63 {L3, 200*1000},
64 {L4, 100*1000},
65 {0, CPUFREQ_TABLE_END},
66};
67
68static u32 clkdiv_val[5][11] = {
69 /*
70 * Clock divider value for following
71 * { APLL, A2M, HCLK_MSYS, PCLK_MSYS,
72 * HCLK_DSYS, PCLK_DSYS, HCLK_PSYS, PCLK_PSYS,
73 * ONEDRAM, MFC, G3D }
74 */
75
76 /* L0 : [1000/200/100][166/83][133/66][200/200] */
77 {0, 4, 4, 1, 3, 1, 4, 1, 3, 0, 0},
78
79 /* L1 : [800/200/100][166/83][133/66][200/200] */
80 {0, 3, 3, 1, 3, 1, 4, 1, 3, 0, 0},
81
82 /* L2 : [400/200/100][166/83][133/66][200/200] */
83 {1, 3, 1, 1, 3, 1, 4, 1, 3, 0, 0},
84
85 /* L3 : [200/200/100][166/83][133/66][200/200] */
86 {3, 3, 1, 1, 3, 1, 4, 1, 3, 0, 0},
87
88 /* L4 : [100/100/100][83/83][66/66][100/100] */
89 {7, 7, 0, 0, 7, 0, 9, 0, 7, 0, 0},
90};
91
92/*
93 * This function set DRAM refresh counter
94 * accoriding to operating frequency of DRAM
95 * ch: DMC port number 0 or 1
96 * freq: Operating frequency of DRAM(KHz)
97 */
98static void s5pv210_set_refresh(enum s5pv210_dmc_port ch, unsigned long freq)
99{
100 unsigned long tmp, tmp1;
101 void __iomem *reg = NULL;
102
103 if (ch == DMC0) {
104 reg = (S5P_VA_DMC0 + 0x30);
105 } else if (ch == DMC1) {
106 reg = (S5P_VA_DMC1 + 0x30);
107 } else {
108 printk(KERN_ERR "Cannot find DMC port\n");
109 return;
110 }
111
112 /* Find current DRAM frequency */
113 tmp = s5pv210_dram_conf[ch].freq;
114
115 do_div(tmp, freq);
116
117 tmp1 = s5pv210_dram_conf[ch].refresh;
118
119 do_div(tmp1, tmp);
120
121 __raw_writel(tmp1, reg);
122}
123
124int s5pv210_verify_speed(struct cpufreq_policy *policy)
125{
126 if (policy->cpu)
127 return -EINVAL;
128
129 return cpufreq_frequency_table_verify(policy, s5pv210_freq_table);
130}
131
132unsigned int s5pv210_getspeed(unsigned int cpu)
133{
134 if (cpu)
135 return 0;
136
137 return clk_get_rate(cpu_clk) / 1000;
138}
139
140static int s5pv210_target(struct cpufreq_policy *policy,
141 unsigned int target_freq,
142 unsigned int relation)
143{
144 unsigned long reg;
145 unsigned int index, priv_index;
146 unsigned int pll_changing = 0;
147 unsigned int bus_speed_changing = 0;
148
149 freqs.old = s5pv210_getspeed(0);
150
151 if (cpufreq_frequency_table_target(policy, s5pv210_freq_table,
152 target_freq, relation, &index))
153 return -EINVAL;
154
155 freqs.new = s5pv210_freq_table[index].frequency;
156 freqs.cpu = 0;
157
158 if (freqs.new == freqs.old)
159 return 0;
160
161 /* Finding current running level index */
162 if (cpufreq_frequency_table_target(policy, s5pv210_freq_table,
163 freqs.old, relation, &priv_index))
164 return -EINVAL;
165
166 cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
167
168 if (freqs.new > freqs.old) {
169 /* Voltage up: will be implemented */
170 }
171
172 /* Check if there need to change PLL */
173 if ((index == L0) || (priv_index == L0))
174 pll_changing = 1;
175
176 /* Check if there need to change System bus clock */
177 if ((index == L4) || (priv_index == L4))
178 bus_speed_changing = 1;
179
180 if (bus_speed_changing) {
181 /*
182 * Reconfigure DRAM refresh counter value for minimum
183 * temporary clock while changing divider.
184 * expected clock is 83Mhz : 7.8usec/(1/83Mhz) = 0x287
185 */
186 if (pll_changing)
187 s5pv210_set_refresh(DMC1, 83000);
188 else
189 s5pv210_set_refresh(DMC1, 100000);
190
191 s5pv210_set_refresh(DMC0, 83000);
192 }
193
194 /*
195 * APLL should be changed in this level
196 * APLL -> MPLL(for stable transition) -> APLL
197 * Some clock source's clock API are not prepared.
198 * Do not use clock API in below code.
199 */
200 if (pll_changing) {
201 /*
202 * 1. Temporary Change divider for MFC and G3D
203 * SCLKA2M(200/1=200)->(200/4=50)Mhz
204 */
205 reg = __raw_readl(S5P_CLK_DIV2);
206 reg &= ~(S5P_CLKDIV2_G3D_MASK | S5P_CLKDIV2_MFC_MASK);
207 reg |= (3 << S5P_CLKDIV2_G3D_SHIFT) |
208 (3 << S5P_CLKDIV2_MFC_SHIFT);
209 __raw_writel(reg, S5P_CLK_DIV2);
210
211 /* For MFC, G3D dividing */
212 do {
213 reg = __raw_readl(S5P_CLKDIV_STAT0);
214 } while (reg & ((1 << 16) | (1 << 17)));
215
216 /*
217 * 2. Change SCLKA2M(200Mhz)to SCLKMPLL in MFC_MUX, G3D MUX
218 * (200/4=50)->(667/4=166)Mhz
219 */
220 reg = __raw_readl(S5P_CLK_SRC2);
221 reg &= ~(S5P_CLKSRC2_G3D_MASK | S5P_CLKSRC2_MFC_MASK);
222 reg |= (1 << S5P_CLKSRC2_G3D_SHIFT) |
223 (1 << S5P_CLKSRC2_MFC_SHIFT);
224 __raw_writel(reg, S5P_CLK_SRC2);
225
226 do {
227 reg = __raw_readl(S5P_CLKMUX_STAT1);
228 } while (reg & ((1 << 7) | (1 << 3)));
229
230 /*
231 * 3. DMC1 refresh count for 133Mhz if (index == L4) is
232 * true refresh counter is already programed in upper
233 * code. 0x287@83Mhz
234 */
235 if (!bus_speed_changing)
236 s5pv210_set_refresh(DMC1, 133000);
237
238 /* 4. SCLKAPLL -> SCLKMPLL */
239 reg = __raw_readl(S5P_CLK_SRC0);
240 reg &= ~(S5P_CLKSRC0_MUX200_MASK);
241 reg |= (0x1 << S5P_CLKSRC0_MUX200_SHIFT);
242 __raw_writel(reg, S5P_CLK_SRC0);
243
244 do {
245 reg = __raw_readl(S5P_CLKMUX_STAT0);
246 } while (reg & (0x1 << 18));
247
248 }
249
250 /* Change divider */
251 reg = __raw_readl(S5P_CLK_DIV0);
252
253 reg &= ~(S5P_CLKDIV0_APLL_MASK | S5P_CLKDIV0_A2M_MASK |
254 S5P_CLKDIV0_HCLK200_MASK | S5P_CLKDIV0_PCLK100_MASK |
255 S5P_CLKDIV0_HCLK166_MASK | S5P_CLKDIV0_PCLK83_MASK |
256 S5P_CLKDIV0_HCLK133_MASK | S5P_CLKDIV0_PCLK66_MASK);
257
258 reg |= ((clkdiv_val[index][0] << S5P_CLKDIV0_APLL_SHIFT) |
259 (clkdiv_val[index][1] << S5P_CLKDIV0_A2M_SHIFT) |
260 (clkdiv_val[index][2] << S5P_CLKDIV0_HCLK200_SHIFT) |
261 (clkdiv_val[index][3] << S5P_CLKDIV0_PCLK100_SHIFT) |
262 (clkdiv_val[index][4] << S5P_CLKDIV0_HCLK166_SHIFT) |
263 (clkdiv_val[index][5] << S5P_CLKDIV0_PCLK83_SHIFT) |
264 (clkdiv_val[index][6] << S5P_CLKDIV0_HCLK133_SHIFT) |
265 (clkdiv_val[index][7] << S5P_CLKDIV0_PCLK66_SHIFT));
266
267 __raw_writel(reg, S5P_CLK_DIV0);
268
269 do {
270 reg = __raw_readl(S5P_CLKDIV_STAT0);
271 } while (reg & 0xff);
272
273 /* ARM MCS value changed */
274 reg = __raw_readl(S5P_ARM_MCS_CON);
275 reg &= ~0x3;
276 if (index >= L3)
277 reg |= 0x3;
278 else
279 reg |= 0x1;
280
281 __raw_writel(reg, S5P_ARM_MCS_CON);
282
283 if (pll_changing) {
284 /* 5. Set Lock time = 30us*24Mhz = 0x2cf */
285 __raw_writel(0x2cf, S5P_APLL_LOCK);
286
287 /*
288 * 6. Turn on APLL
289 * 6-1. Set PMS values
290 * 6-2. Wait untile the PLL is locked
291 */
292 if (index == L0)
293 __raw_writel(APLL_VAL_1000, S5P_APLL_CON);
294 else
295 __raw_writel(APLL_VAL_800, S5P_APLL_CON);
296
297 do {
298 reg = __raw_readl(S5P_APLL_CON);
299 } while (!(reg & (0x1 << 29)));
300
301 /*
302 * 7. Change souce clock from SCLKMPLL(667Mhz)
303 * to SCLKA2M(200Mhz) in MFC_MUX and G3D MUX
304 * (667/4=166)->(200/4=50)Mhz
305 */
306 reg = __raw_readl(S5P_CLK_SRC2);
307 reg &= ~(S5P_CLKSRC2_G3D_MASK | S5P_CLKSRC2_MFC_MASK);
308 reg |= (0 << S5P_CLKSRC2_G3D_SHIFT) |
309 (0 << S5P_CLKSRC2_MFC_SHIFT);
310 __raw_writel(reg, S5P_CLK_SRC2);
311
312 do {
313 reg = __raw_readl(S5P_CLKMUX_STAT1);
314 } while (reg & ((1 << 7) | (1 << 3)));
315
316 /*
317 * 8. Change divider for MFC and G3D
318 * (200/4=50)->(200/1=200)Mhz
319 */
320 reg = __raw_readl(S5P_CLK_DIV2);
321 reg &= ~(S5P_CLKDIV2_G3D_MASK | S5P_CLKDIV2_MFC_MASK);
322 reg |= (clkdiv_val[index][10] << S5P_CLKDIV2_G3D_SHIFT) |
323 (clkdiv_val[index][9] << S5P_CLKDIV2_MFC_SHIFT);
324 __raw_writel(reg, S5P_CLK_DIV2);
325
326 /* For MFC, G3D dividing */
327 do {
328 reg = __raw_readl(S5P_CLKDIV_STAT0);
329 } while (reg & ((1 << 16) | (1 << 17)));
330
331 /* 9. Change MPLL to APLL in MSYS_MUX */
332 reg = __raw_readl(S5P_CLK_SRC0);
333 reg &= ~(S5P_CLKSRC0_MUX200_MASK);
334 reg |= (0x0 << S5P_CLKSRC0_MUX200_SHIFT);
335 __raw_writel(reg, S5P_CLK_SRC0);
336
337 do {
338 reg = __raw_readl(S5P_CLKMUX_STAT0);
339 } while (reg & (0x1 << 18));
340
341 /*
342 * 10. DMC1 refresh counter
343 * L4 : DMC1 = 100Mhz 7.8us/(1/100) = 0x30c
344 * Others : DMC1 = 200Mhz 7.8us/(1/200) = 0x618
345 */
346 if (!bus_speed_changing)
347 s5pv210_set_refresh(DMC1, 200000);
348 }
349
350 /*
351 * L4 level need to change memory bus speed, hence onedram clock divier
352 * and memory refresh parameter should be changed
353 */
354 if (bus_speed_changing) {
355 reg = __raw_readl(S5P_CLK_DIV6);
356 reg &= ~S5P_CLKDIV6_ONEDRAM_MASK;
357 reg |= (clkdiv_val[index][8] << S5P_CLKDIV6_ONEDRAM_SHIFT);
358 __raw_writel(reg, S5P_CLK_DIV6);
359
360 do {
361 reg = __raw_readl(S5P_CLKDIV_STAT1);
362 } while (reg & (1 << 15));
363
364 /* Reconfigure DRAM refresh counter value */
365 if (index != L4) {
366 /*
367 * DMC0 : 166Mhz
368 * DMC1 : 200Mhz
369 */
370 s5pv210_set_refresh(DMC0, 166000);
371 s5pv210_set_refresh(DMC1, 200000);
372 } else {
373 /*
374 * DMC0 : 83Mhz
375 * DMC1 : 100Mhz
376 */
377 s5pv210_set_refresh(DMC0, 83000);
378 s5pv210_set_refresh(DMC1, 100000);
379 }
380 }
381
382 if (freqs.new < freqs.old) {
383 /* Voltage down: will be implemented */
384 }
385
386 cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
387
388 printk(KERN_DEBUG "Perf changed[L%d]\n", index);
389
390 return 0;
391}
392
393#ifdef CONFIG_PM
394static int s5pv210_cpufreq_suspend(struct cpufreq_policy *policy)
395{
396 return 0;
397}
398
399static int s5pv210_cpufreq_resume(struct cpufreq_policy *policy)
400{
401 return 0;
402}
403#endif
404
405static int check_mem_type(void __iomem *dmc_reg)
406{
407 unsigned long val;
408
409 val = __raw_readl(dmc_reg + 0x4);
410 val = (val & (0xf << 8));
411
412 return val >> 8;
413}
414
415static int __init s5pv210_cpu_init(struct cpufreq_policy *policy)
416{
417 unsigned long mem_type;
418
419 cpu_clk = clk_get(NULL, "armclk");
420 if (IS_ERR(cpu_clk))
421 return PTR_ERR(cpu_clk);
422
423 dmc0_clk = clk_get(NULL, "sclk_dmc0");
424 if (IS_ERR(dmc0_clk)) {
425 clk_put(cpu_clk);
426 return PTR_ERR(dmc0_clk);
427 }
428
429 dmc1_clk = clk_get(NULL, "hclk_msys");
430 if (IS_ERR(dmc1_clk)) {
431 clk_put(dmc0_clk);
432 clk_put(cpu_clk);
433 return PTR_ERR(dmc1_clk);
434 }
435
436 if (policy->cpu != 0)
437 return -EINVAL;
438
439 /*
440 * check_mem_type : This driver only support LPDDR & LPDDR2.
441 * other memory type is not supported.
442 */
443 mem_type = check_mem_type(S5P_VA_DMC0);
444
445 if ((mem_type != LPDDR) && (mem_type != LPDDR2)) {
446 printk(KERN_ERR "CPUFreq doesn't support this memory type\n");
447 return -EINVAL;
448 }
449
450 /* Find current refresh counter and frequency each DMC */
451 s5pv210_dram_conf[0].refresh = (__raw_readl(S5P_VA_DMC0 + 0x30) * 1000);
452 s5pv210_dram_conf[0].freq = clk_get_rate(dmc0_clk);
453
454 s5pv210_dram_conf[1].refresh = (__raw_readl(S5P_VA_DMC1 + 0x30) * 1000);
455 s5pv210_dram_conf[1].freq = clk_get_rate(dmc1_clk);
456
457 policy->cur = policy->min = policy->max = s5pv210_getspeed(0);
458
459 cpufreq_frequency_table_get_attr(s5pv210_freq_table, policy->cpu);
460
461 policy->cpuinfo.transition_latency = 40000;
462
463 return cpufreq_frequency_table_cpuinfo(policy, s5pv210_freq_table);
464}
465
466static struct cpufreq_driver s5pv210_driver = {
467 .flags = CPUFREQ_STICKY,
468 .verify = s5pv210_verify_speed,
469 .target = s5pv210_target,
470 .get = s5pv210_getspeed,
471 .init = s5pv210_cpu_init,
472 .name = "s5pv210",
473#ifdef CONFIG_PM
474 .suspend = s5pv210_cpufreq_suspend,
475 .resume = s5pv210_cpufreq_resume,
476#endif
477};
478
479static int __init s5pv210_cpufreq_init(void)
480{
481 return cpufreq_register_driver(&s5pv210_driver);
482}
483
484late_initcall(s5pv210_cpufreq_init);