diff options
-rw-r--r-- | arch/arm/mach-exynos/include/mach/cpufreq.h | 34 | ||||
-rw-r--r-- | drivers/cpufreq/Kconfig.arm | 15 | ||||
-rw-r--r-- | drivers/cpufreq/Makefile | 1 | ||||
-rw-r--r-- | drivers/cpufreq/exynos-cpufreq.c | 296 | ||||
-rw-r--r-- | drivers/cpufreq/exynos4210-cpufreq.c | 385 |
5 files changed, 414 insertions, 317 deletions
diff --git a/arch/arm/mach-exynos/include/mach/cpufreq.h b/arch/arm/mach-exynos/include/mach/cpufreq.h new file mode 100644 index 000000000000..3df27f2d5034 --- /dev/null +++ b/arch/arm/mach-exynos/include/mach/cpufreq.h | |||
@@ -0,0 +1,34 @@ | |||
1 | /* linux/arch/arm/mach-exynos/include/mach/cpufreq.h | ||
2 | * | ||
3 | * Copyright (c) 2010 Samsung Electronics Co., Ltd. | ||
4 | * http://www.samsung.com | ||
5 | * | ||
6 | * EXYNOS - CPUFreq 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 | enum cpufreq_level_index { | ||
14 | L0, L1, L2, L3, L4, | ||
15 | L5, L6, L7, L8, L9, | ||
16 | L10, L11, L12, L13, L14, | ||
17 | L15, L16, L17, L18, L19, | ||
18 | L20, | ||
19 | }; | ||
20 | |||
21 | struct exynos_dvfs_info { | ||
22 | unsigned long mpll_freq_khz; | ||
23 | unsigned int pll_safe_idx; | ||
24 | unsigned int pm_lock_idx; | ||
25 | unsigned int max_support_idx; | ||
26 | unsigned int min_support_idx; | ||
27 | struct clk *cpu_clk; | ||
28 | unsigned int *volt_table; | ||
29 | struct cpufreq_frequency_table *freq_table; | ||
30 | void (*set_freq)(unsigned int, unsigned int); | ||
31 | bool (*need_apll_change)(unsigned int, unsigned int); | ||
32 | }; | ||
33 | |||
34 | extern int exynos4210_cpufreq_init(struct exynos_dvfs_info *); | ||
diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index 72a0044c1baa..e0664fed018a 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm | |||
@@ -21,12 +21,19 @@ config ARM_S5PV210_CPUFREQ | |||
21 | 21 | ||
22 | If in doubt, say N. | 22 | If in doubt, say N. |
23 | 23 | ||
24 | config ARM_EXYNOS_CPUFREQ | ||
25 | bool "SAMSUNG EXYNOS SoCs" | ||
26 | depends on ARCH_EXYNOS | ||
27 | select ARM_EXYNOS4210_CPUFREQ if CPU_EXYNOS4210 | ||
28 | default y | ||
29 | help | ||
30 | This adds the CPUFreq driver common part for Samsung | ||
31 | EXYNOS SoCs. | ||
32 | |||
33 | If in doubt, say N. | ||
34 | |||
24 | config ARM_EXYNOS4210_CPUFREQ | 35 | config ARM_EXYNOS4210_CPUFREQ |
25 | bool "Samsung EXYNOS4210" | 36 | bool "Samsung EXYNOS4210" |
26 | depends on CPU_EXYNOS4210 | ||
27 | default y | ||
28 | help | 37 | help |
29 | This adds the CPUFreq driver for Samsung EXYNOS4210 | 38 | This adds the CPUFreq driver for Samsung EXYNOS4210 |
30 | SoC (S5PV310 or S5PC210). | 39 | SoC (S5PV310 or S5PC210). |
31 | |||
32 | If in doubt, say N. | ||
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index ce75fcbcca4f..ac000fa76bbb 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile | |||
@@ -42,6 +42,7 @@ obj-$(CONFIG_X86_CPUFREQ_NFORCE2) += cpufreq-nforce2.o | |||
42 | obj-$(CONFIG_UX500_SOC_DB8500) += db8500-cpufreq.o | 42 | obj-$(CONFIG_UX500_SOC_DB8500) += db8500-cpufreq.o |
43 | obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o | 43 | obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o |
44 | obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o | 44 | obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o |
45 | obj-$(CONFIG_ARM_EXYNOS_CPUFREQ) += exynos-cpufreq.o | ||
45 | obj-$(CONFIG_ARM_EXYNOS4210_CPUFREQ) += exynos4210-cpufreq.o | 46 | obj-$(CONFIG_ARM_EXYNOS4210_CPUFREQ) += exynos4210-cpufreq.o |
46 | obj-$(CONFIG_ARCH_OMAP2PLUS) += omap-cpufreq.o | 47 | obj-$(CONFIG_ARCH_OMAP2PLUS) += omap-cpufreq.o |
47 | 48 | ||
diff --git a/drivers/cpufreq/exynos-cpufreq.c b/drivers/cpufreq/exynos-cpufreq.c new file mode 100644 index 000000000000..24e4dd453fab --- /dev/null +++ b/drivers/cpufreq/exynos-cpufreq.c | |||
@@ -0,0 +1,296 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. | ||
3 | * http://www.samsung.com | ||
4 | * | ||
5 | * EXYNOS - CPU frequency scaling support for EXYNOS series | ||
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 | #include <linux/suspend.h> | ||
21 | #include <linux/reboot.h> | ||
22 | |||
23 | #include <mach/map.h> | ||
24 | #include <mach/regs-clock.h> | ||
25 | #include <mach/regs-mem.h> | ||
26 | #include <mach/cpufreq.h> | ||
27 | |||
28 | #include <plat/clock.h> | ||
29 | #include <plat/pm.h> | ||
30 | |||
31 | static struct exynos_dvfs_info *exynos_info; | ||
32 | |||
33 | static struct regulator *arm_regulator; | ||
34 | static struct cpufreq_freqs freqs; | ||
35 | |||
36 | static unsigned int locking_frequency; | ||
37 | static bool frequency_locked; | ||
38 | static DEFINE_MUTEX(cpufreq_lock); | ||
39 | |||
40 | int exynos_verify_speed(struct cpufreq_policy *policy) | ||
41 | { | ||
42 | return cpufreq_frequency_table_verify(policy, | ||
43 | exynos_info->freq_table); | ||
44 | } | ||
45 | |||
46 | unsigned int exynos_getspeed(unsigned int cpu) | ||
47 | { | ||
48 | return clk_get_rate(exynos_info->cpu_clk) / 1000; | ||
49 | } | ||
50 | |||
51 | static int exynos_target(struct cpufreq_policy *policy, | ||
52 | unsigned int target_freq, | ||
53 | unsigned int relation) | ||
54 | { | ||
55 | unsigned int index, old_index; | ||
56 | unsigned int arm_volt, safe_arm_volt = 0; | ||
57 | int ret = 0; | ||
58 | struct cpufreq_frequency_table *freq_table = exynos_info->freq_table; | ||
59 | unsigned int *volt_table = exynos_info->volt_table; | ||
60 | unsigned int mpll_freq_khz = exynos_info->mpll_freq_khz; | ||
61 | |||
62 | mutex_lock(&cpufreq_lock); | ||
63 | |||
64 | freqs.old = policy->cur; | ||
65 | |||
66 | if (frequency_locked && target_freq != locking_frequency) { | ||
67 | ret = -EAGAIN; | ||
68 | goto out; | ||
69 | } | ||
70 | |||
71 | if (cpufreq_frequency_table_target(policy, freq_table, | ||
72 | freqs.old, relation, &old_index)) { | ||
73 | ret = -EINVAL; | ||
74 | goto out; | ||
75 | } | ||
76 | |||
77 | if (cpufreq_frequency_table_target(policy, freq_table, | ||
78 | target_freq, relation, &index)) { | ||
79 | ret = -EINVAL; | ||
80 | goto out; | ||
81 | } | ||
82 | |||
83 | freqs.new = freq_table[index].frequency; | ||
84 | freqs.cpu = policy->cpu; | ||
85 | |||
86 | /* | ||
87 | * ARM clock source will be changed APLL to MPLL temporary | ||
88 | * To support this level, need to control regulator for | ||
89 | * required voltage level | ||
90 | */ | ||
91 | if (exynos_info->need_apll_change != NULL) { | ||
92 | if (exynos_info->need_apll_change(old_index, index) && | ||
93 | (freq_table[index].frequency < mpll_freq_khz) && | ||
94 | (freq_table[old_index].frequency < mpll_freq_khz)) | ||
95 | safe_arm_volt = volt_table[exynos_info->pll_safe_idx]; | ||
96 | } | ||
97 | arm_volt = volt_table[index]; | ||
98 | |||
99 | cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); | ||
100 | |||
101 | /* When the new frequency is higher than current frequency */ | ||
102 | if ((freqs.new > freqs.old) && !safe_arm_volt) { | ||
103 | /* Firstly, voltage up to increase frequency */ | ||
104 | regulator_set_voltage(arm_regulator, arm_volt, | ||
105 | arm_volt); | ||
106 | } | ||
107 | |||
108 | if (safe_arm_volt) | ||
109 | regulator_set_voltage(arm_regulator, safe_arm_volt, | ||
110 | safe_arm_volt); | ||
111 | if (freqs.new != freqs.old) | ||
112 | exynos_info->set_freq(old_index, index); | ||
113 | |||
114 | cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); | ||
115 | |||
116 | /* When the new frequency is lower than current frequency */ | ||
117 | if ((freqs.new < freqs.old) || | ||
118 | ((freqs.new > freqs.old) && safe_arm_volt)) { | ||
119 | /* down the voltage after frequency change */ | ||
120 | regulator_set_voltage(arm_regulator, arm_volt, | ||
121 | arm_volt); | ||
122 | } | ||
123 | |||
124 | out: | ||
125 | mutex_unlock(&cpufreq_lock); | ||
126 | |||
127 | return ret; | ||
128 | } | ||
129 | |||
130 | #ifdef CONFIG_PM | ||
131 | static int exynos_cpufreq_suspend(struct cpufreq_policy *policy) | ||
132 | { | ||
133 | return 0; | ||
134 | } | ||
135 | |||
136 | static int exynos_cpufreq_resume(struct cpufreq_policy *policy) | ||
137 | { | ||
138 | return 0; | ||
139 | } | ||
140 | #endif | ||
141 | |||
142 | /** | ||
143 | * exynos_cpufreq_pm_notifier - block CPUFREQ's activities in suspend-resume | ||
144 | * context | ||
145 | * @notifier | ||
146 | * @pm_event | ||
147 | * @v | ||
148 | * | ||
149 | * While frequency_locked == true, target() ignores every frequency but | ||
150 | * locking_frequency. The locking_frequency value is the initial frequency, | ||
151 | * which is set by the bootloader. In order to eliminate possible | ||
152 | * inconsistency in clock values, we save and restore frequencies during | ||
153 | * suspend and resume and block CPUFREQ activities. Note that the standard | ||
154 | * suspend/resume cannot be used as they are too deep (syscore_ops) for | ||
155 | * regulator actions. | ||
156 | */ | ||
157 | static int exynos_cpufreq_pm_notifier(struct notifier_block *notifier, | ||
158 | unsigned long pm_event, void *v) | ||
159 | { | ||
160 | struct cpufreq_policy *policy = cpufreq_cpu_get(0); /* boot CPU */ | ||
161 | static unsigned int saved_frequency; | ||
162 | unsigned int temp; | ||
163 | |||
164 | mutex_lock(&cpufreq_lock); | ||
165 | switch (pm_event) { | ||
166 | case PM_SUSPEND_PREPARE: | ||
167 | if (frequency_locked) | ||
168 | goto out; | ||
169 | |||
170 | frequency_locked = true; | ||
171 | |||
172 | if (locking_frequency) { | ||
173 | saved_frequency = exynos_getspeed(0); | ||
174 | |||
175 | mutex_unlock(&cpufreq_lock); | ||
176 | exynos_target(policy, locking_frequency, | ||
177 | CPUFREQ_RELATION_H); | ||
178 | mutex_lock(&cpufreq_lock); | ||
179 | } | ||
180 | break; | ||
181 | |||
182 | case PM_POST_SUSPEND: | ||
183 | if (saved_frequency) { | ||
184 | /* | ||
185 | * While frequency_locked, only locking_frequency | ||
186 | * is valid for target(). In order to use | ||
187 | * saved_frequency while keeping frequency_locked, | ||
188 | * we temporarly overwrite locking_frequency. | ||
189 | */ | ||
190 | temp = locking_frequency; | ||
191 | locking_frequency = saved_frequency; | ||
192 | |||
193 | mutex_unlock(&cpufreq_lock); | ||
194 | exynos_target(policy, locking_frequency, | ||
195 | CPUFREQ_RELATION_H); | ||
196 | mutex_lock(&cpufreq_lock); | ||
197 | |||
198 | locking_frequency = temp; | ||
199 | } | ||
200 | frequency_locked = false; | ||
201 | break; | ||
202 | } | ||
203 | out: | ||
204 | mutex_unlock(&cpufreq_lock); | ||
205 | |||
206 | return NOTIFY_OK; | ||
207 | } | ||
208 | |||
209 | static struct notifier_block exynos_cpufreq_nb = { | ||
210 | .notifier_call = exynos_cpufreq_pm_notifier, | ||
211 | }; | ||
212 | |||
213 | static int exynos_cpufreq_cpu_init(struct cpufreq_policy *policy) | ||
214 | { | ||
215 | policy->cur = policy->min = policy->max = exynos_getspeed(policy->cpu); | ||
216 | |||
217 | cpufreq_frequency_table_get_attr(exynos_info->freq_table, policy->cpu); | ||
218 | |||
219 | /* set the transition latency value */ | ||
220 | policy->cpuinfo.transition_latency = 100000; | ||
221 | |||
222 | /* | ||
223 | * EXYNOS4 multi-core processors has 2 cores | ||
224 | * that the frequency cannot be set independently. | ||
225 | * Each cpu is bound to the same speed. | ||
226 | * So the affected cpu is all of the cpus. | ||
227 | */ | ||
228 | if (num_online_cpus() == 1) { | ||
229 | cpumask_copy(policy->related_cpus, cpu_possible_mask); | ||
230 | cpumask_copy(policy->cpus, cpu_online_mask); | ||
231 | } else { | ||
232 | cpumask_setall(policy->cpus); | ||
233 | } | ||
234 | |||
235 | return cpufreq_frequency_table_cpuinfo(policy, exynos_info->freq_table); | ||
236 | } | ||
237 | |||
238 | static struct cpufreq_driver exynos_driver = { | ||
239 | .flags = CPUFREQ_STICKY, | ||
240 | .verify = exynos_verify_speed, | ||
241 | .target = exynos_target, | ||
242 | .get = exynos_getspeed, | ||
243 | .init = exynos_cpufreq_cpu_init, | ||
244 | .name = "exynos_cpufreq", | ||
245 | #ifdef CONFIG_PM | ||
246 | .suspend = exynos_cpufreq_suspend, | ||
247 | .resume = exynos_cpufreq_resume, | ||
248 | #endif | ||
249 | }; | ||
250 | |||
251 | static int __init exynos_cpufreq_init(void) | ||
252 | { | ||
253 | int ret = -EINVAL; | ||
254 | |||
255 | exynos_info = kzalloc(sizeof(struct exynos_dvfs_info), GFP_KERNEL); | ||
256 | if (!exynos_info) | ||
257 | return -ENOMEM; | ||
258 | |||
259 | if (soc_is_exynos4210()) | ||
260 | ret = exynos4210_cpufreq_init(exynos_info); | ||
261 | else | ||
262 | pr_err("%s: CPU type not found\n", __func__); | ||
263 | |||
264 | if (ret) | ||
265 | goto err_vdd_arm; | ||
266 | |||
267 | if (exynos_info->set_freq == NULL) { | ||
268 | pr_err("%s: No set_freq function (ERR)\n", __func__); | ||
269 | goto err_vdd_arm; | ||
270 | } | ||
271 | |||
272 | arm_regulator = regulator_get(NULL, "vdd_arm"); | ||
273 | if (IS_ERR(arm_regulator)) { | ||
274 | pr_err("%s: failed to get resource vdd_arm\n", __func__); | ||
275 | goto err_vdd_arm; | ||
276 | } | ||
277 | |||
278 | register_pm_notifier(&exynos_cpufreq_nb); | ||
279 | |||
280 | if (cpufreq_register_driver(&exynos_driver)) { | ||
281 | pr_err("%s: failed to register cpufreq driver\n", __func__); | ||
282 | goto err_cpufreq; | ||
283 | } | ||
284 | |||
285 | return 0; | ||
286 | err_cpufreq: | ||
287 | unregister_pm_notifier(&exynos_cpufreq_nb); | ||
288 | |||
289 | if (!IS_ERR(arm_regulator)) | ||
290 | regulator_put(arm_regulator); | ||
291 | err_vdd_arm: | ||
292 | kfree(exynos_info); | ||
293 | pr_debug("%s: failed initialization\n", __func__); | ||
294 | return -EINVAL; | ||
295 | } | ||
296 | late_initcall(exynos_cpufreq_init); | ||
diff --git a/drivers/cpufreq/exynos4210-cpufreq.c b/drivers/cpufreq/exynos4210-cpufreq.c index a0af2d4448a9..6bc4ada56df1 100644 --- a/drivers/cpufreq/exynos4210-cpufreq.c +++ b/drivers/cpufreq/exynos4210-cpufreq.c | |||
@@ -2,7 +2,7 @@ | |||
2 | * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. | 2 | * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. |
3 | * http://www.samsung.com | 3 | * http://www.samsung.com |
4 | * | 4 | * |
5 | * EXYNOS4 - CPU frequency scaling support | 5 | * EXYNOS4210 - CPU frequency scaling support |
6 | * | 6 | * |
7 | * This program is free software; you can redistribute it and/or modify | 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 | 8 | * it under the terms of the GNU General Public License version 2 as |
@@ -23,10 +23,16 @@ | |||
23 | #include <mach/map.h> | 23 | #include <mach/map.h> |
24 | #include <mach/regs-clock.h> | 24 | #include <mach/regs-clock.h> |
25 | #include <mach/regs-mem.h> | 25 | #include <mach/regs-mem.h> |
26 | #include <mach/cpufreq.h> | ||
26 | 27 | ||
27 | #include <plat/clock.h> | 28 | #include <plat/clock.h> |
28 | #include <plat/pm.h> | 29 | #include <plat/pm.h> |
29 | 30 | ||
31 | #define CPUFREQ_LEVEL_END L5 | ||
32 | |||
33 | static int max_support_idx = L0; | ||
34 | static int min_support_idx = (CPUFREQ_LEVEL_END - 1); | ||
35 | |||
30 | static struct clk *cpu_clk; | 36 | static struct clk *cpu_clk; |
31 | static struct clk *moutcore; | 37 | static struct clk *moutcore; |
32 | static struct clk *mout_mpll; | 38 | static struct clk *mout_mpll; |
@@ -37,20 +43,18 @@ static struct regulator *arm_regulator; | |||
37 | static struct cpufreq_freqs freqs; | 43 | static struct cpufreq_freqs freqs; |
38 | 44 | ||
39 | struct cpufreq_clkdiv { | 45 | struct cpufreq_clkdiv { |
46 | unsigned int index; | ||
40 | unsigned int clkdiv; | 47 | unsigned int clkdiv; |
41 | }; | 48 | }; |
42 | 49 | ||
43 | static unsigned int locking_frequency; | 50 | static unsigned int exynos4210_volt_table[CPUFREQ_LEVEL_END] = { |
44 | static bool frequency_locked; | 51 | 1250000, 1150000, 1050000, 975000, 950000, |
45 | static DEFINE_MUTEX(cpufreq_lock); | ||
46 | |||
47 | enum cpufreq_level_index { | ||
48 | L0, L1, L2, L3, L4, CPUFREQ_LEVEL_END, | ||
49 | }; | 52 | }; |
50 | 53 | ||
51 | static struct cpufreq_clkdiv exynos4_clkdiv_table[CPUFREQ_LEVEL_END]; | ||
52 | 54 | ||
53 | static struct cpufreq_frequency_table exynos4_freq_table[] = { | 55 | static struct cpufreq_clkdiv exynos4210_clkdiv_table[CPUFREQ_LEVEL_END]; |
56 | |||
57 | static struct cpufreq_frequency_table exynos4210_freq_table[] = { | ||
54 | {L0, 1200*1000}, | 58 | {L0, 1200*1000}, |
55 | {L1, 1000*1000}, | 59 | {L1, 1000*1000}, |
56 | {L2, 800*1000}, | 60 | {L2, 800*1000}, |
@@ -104,31 +108,7 @@ static unsigned int clkdiv_cpu1[CPUFREQ_LEVEL_END][2] = { | |||
104 | { 3, 0 }, | 108 | { 3, 0 }, |
105 | }; | 109 | }; |
106 | 110 | ||
107 | struct cpufreq_voltage_table { | 111 | static unsigned int exynos4210_apll_pms_table[CPUFREQ_LEVEL_END] = { |
108 | unsigned int index; /* any */ | ||
109 | unsigned int arm_volt; /* uV */ | ||
110 | }; | ||
111 | |||
112 | static struct cpufreq_voltage_table exynos4_volt_table[CPUFREQ_LEVEL_END] = { | ||
113 | { | ||
114 | .index = L0, | ||
115 | .arm_volt = 1350000, | ||
116 | }, { | ||
117 | .index = L1, | ||
118 | .arm_volt = 1300000, | ||
119 | }, { | ||
120 | .index = L2, | ||
121 | .arm_volt = 1200000, | ||
122 | }, { | ||
123 | .index = L3, | ||
124 | .arm_volt = 1100000, | ||
125 | }, { | ||
126 | .index = L4, | ||
127 | .arm_volt = 1050000, | ||
128 | }, | ||
129 | }; | ||
130 | |||
131 | static unsigned int exynos4_apll_pms_table[CPUFREQ_LEVEL_END] = { | ||
132 | /* APLL FOUT L0: 1200MHz */ | 112 | /* APLL FOUT L0: 1200MHz */ |
133 | ((150 << 16) | (3 << 8) | 1), | 113 | ((150 << 16) | (3 << 8) | 1), |
134 | 114 | ||
@@ -145,23 +125,13 @@ static unsigned int exynos4_apll_pms_table[CPUFREQ_LEVEL_END] = { | |||
145 | ((200 << 16) | (6 << 8) | 3), | 125 | ((200 << 16) | (6 << 8) | 3), |
146 | }; | 126 | }; |
147 | 127 | ||
148 | static int exynos4_verify_speed(struct cpufreq_policy *policy) | 128 | static void exynos4210_set_clkdiv(unsigned int div_index) |
149 | { | ||
150 | return cpufreq_frequency_table_verify(policy, exynos4_freq_table); | ||
151 | } | ||
152 | |||
153 | static unsigned int exynos4_getspeed(unsigned int cpu) | ||
154 | { | ||
155 | return clk_get_rate(cpu_clk) / 1000; | ||
156 | } | ||
157 | |||
158 | static void exynos4_set_clkdiv(unsigned int div_index) | ||
159 | { | 129 | { |
160 | unsigned int tmp; | 130 | unsigned int tmp; |
161 | 131 | ||
162 | /* Change Divider - CPU0 */ | 132 | /* Change Divider - CPU0 */ |
163 | 133 | ||
164 | tmp = exynos4_clkdiv_table[div_index].clkdiv; | 134 | tmp = exynos4210_clkdiv_table[div_index].clkdiv; |
165 | 135 | ||
166 | __raw_writel(tmp, S5P_CLKDIV_CPU); | 136 | __raw_writel(tmp, S5P_CLKDIV_CPU); |
167 | 137 | ||
@@ -185,7 +155,7 @@ static void exynos4_set_clkdiv(unsigned int div_index) | |||
185 | } while (tmp & 0x11); | 155 | } while (tmp & 0x11); |
186 | } | 156 | } |
187 | 157 | ||
188 | static void exynos4_set_apll(unsigned int index) | 158 | static void exynos4210_set_apll(unsigned int index) |
189 | { | 159 | { |
190 | unsigned int tmp; | 160 | unsigned int tmp; |
191 | 161 | ||
@@ -204,7 +174,7 @@ static void exynos4_set_apll(unsigned int index) | |||
204 | /* 3. Change PLL PMS values */ | 174 | /* 3. Change PLL PMS values */ |
205 | tmp = __raw_readl(S5P_APLL_CON0); | 175 | tmp = __raw_readl(S5P_APLL_CON0); |
206 | tmp &= ~((0x3ff << 16) | (0x3f << 8) | (0x7 << 0)); | 176 | tmp &= ~((0x3ff << 16) | (0x3f << 8) | (0x7 << 0)); |
207 | tmp |= exynos4_apll_pms_table[index]; | 177 | tmp |= exynos4210_apll_pms_table[index]; |
208 | __raw_writel(tmp, S5P_APLL_CON0); | 178 | __raw_writel(tmp, S5P_APLL_CON0); |
209 | 179 | ||
210 | /* 4. wait_lock_time */ | 180 | /* 4. wait_lock_time */ |
@@ -221,305 +191,90 @@ static void exynos4_set_apll(unsigned int index) | |||
221 | } while (tmp != (0x1 << S5P_CLKSRC_CPU_MUXCORE_SHIFT)); | 191 | } while (tmp != (0x1 << S5P_CLKSRC_CPU_MUXCORE_SHIFT)); |
222 | } | 192 | } |
223 | 193 | ||
224 | static void exynos4_set_frequency(unsigned int old_index, unsigned int new_index) | 194 | bool exynos4210_pms_change(unsigned int old_index, unsigned int new_index) |
195 | { | ||
196 | unsigned int old_pm = (exynos4210_apll_pms_table[old_index] >> 8); | ||
197 | unsigned int new_pm = (exynos4210_apll_pms_table[new_index] >> 8); | ||
198 | |||
199 | return (old_pm == new_pm) ? 0 : 1; | ||
200 | } | ||
201 | |||
202 | static void exynos4210_set_frequency(unsigned int old_index, | ||
203 | unsigned int new_index) | ||
225 | { | 204 | { |
226 | unsigned int tmp; | 205 | unsigned int tmp; |
227 | 206 | ||
228 | if (old_index > new_index) { | 207 | if (old_index > new_index) { |
229 | /* | 208 | if (!exynos4210_pms_change(old_index, new_index)) { |
230 | * L1/L3, L2/L4 Level change require | ||
231 | * to only change s divider value | ||
232 | */ | ||
233 | if (((old_index == L3) && (new_index == L1)) || | ||
234 | ((old_index == L4) && (new_index == L2))) { | ||
235 | /* 1. Change the system clock divider values */ | 209 | /* 1. Change the system clock divider values */ |
236 | exynos4_set_clkdiv(new_index); | 210 | exynos4210_set_clkdiv(new_index); |
237 | 211 | ||
238 | /* 2. Change just s value in apll m,p,s value */ | 212 | /* 2. Change just s value in apll m,p,s value */ |
239 | tmp = __raw_readl(S5P_APLL_CON0); | 213 | tmp = __raw_readl(S5P_APLL_CON0); |
240 | tmp &= ~(0x7 << 0); | 214 | tmp &= ~(0x7 << 0); |
241 | tmp |= (exynos4_apll_pms_table[new_index] & 0x7); | 215 | tmp |= (exynos4210_apll_pms_table[new_index] & 0x7); |
242 | __raw_writel(tmp, S5P_APLL_CON0); | 216 | __raw_writel(tmp, S5P_APLL_CON0); |
243 | } else { | 217 | } else { |
244 | /* Clock Configuration Procedure */ | 218 | /* Clock Configuration Procedure */ |
245 | /* 1. Change the system clock divider values */ | 219 | /* 1. Change the system clock divider values */ |
246 | exynos4_set_clkdiv(new_index); | 220 | exynos4210_set_clkdiv(new_index); |
247 | /* 2. Change the apll m,p,s value */ | 221 | /* 2. Change the apll m,p,s value */ |
248 | exynos4_set_apll(new_index); | 222 | exynos4210_set_apll(new_index); |
249 | } | 223 | } |
250 | } else if (old_index < new_index) { | 224 | } else if (old_index < new_index) { |
251 | /* | 225 | if (!exynos4210_pms_change(old_index, new_index)) { |
252 | * L1/L3, L2/L4 Level change require | ||
253 | * to only change s divider value | ||
254 | */ | ||
255 | if (((old_index == L1) && (new_index == L3)) || | ||
256 | ((old_index == L2) && (new_index == L4))) { | ||
257 | /* 1. Change just s value in apll m,p,s value */ | 226 | /* 1. Change just s value in apll m,p,s value */ |
258 | tmp = __raw_readl(S5P_APLL_CON0); | 227 | tmp = __raw_readl(S5P_APLL_CON0); |
259 | tmp &= ~(0x7 << 0); | 228 | tmp &= ~(0x7 << 0); |
260 | tmp |= (exynos4_apll_pms_table[new_index] & 0x7); | 229 | tmp |= (exynos4210_apll_pms_table[new_index] & 0x7); |
261 | __raw_writel(tmp, S5P_APLL_CON0); | 230 | __raw_writel(tmp, S5P_APLL_CON0); |
262 | 231 | ||
263 | /* 2. Change the system clock divider values */ | 232 | /* 2. Change the system clock divider values */ |
264 | exynos4_set_clkdiv(new_index); | 233 | exynos4210_set_clkdiv(new_index); |
265 | } else { | 234 | } else { |
266 | /* Clock Configuration Procedure */ | 235 | /* Clock Configuration Procedure */ |
267 | /* 1. Change the apll m,p,s value */ | 236 | /* 1. Change the apll m,p,s value */ |
268 | exynos4_set_apll(new_index); | 237 | exynos4210_set_apll(new_index); |
269 | /* 2. Change the system clock divider values */ | 238 | /* 2. Change the system clock divider values */ |
270 | exynos4_set_clkdiv(new_index); | 239 | exynos4210_set_clkdiv(new_index); |
271 | } | 240 | } |
272 | } | 241 | } |
273 | } | 242 | } |
274 | 243 | ||
275 | static int exynos4_target(struct cpufreq_policy *policy, | 244 | int exynos4210_cpufreq_init(struct exynos_dvfs_info *info) |
276 | unsigned int target_freq, | ||
277 | unsigned int relation) | ||
278 | { | ||
279 | unsigned int index, old_index; | ||
280 | unsigned int arm_volt; | ||
281 | int err = -EINVAL; | ||
282 | |||
283 | freqs.old = exynos4_getspeed(policy->cpu); | ||
284 | |||
285 | mutex_lock(&cpufreq_lock); | ||
286 | |||
287 | if (frequency_locked && target_freq != locking_frequency) { | ||
288 | err = -EAGAIN; | ||
289 | goto out; | ||
290 | } | ||
291 | |||
292 | if (cpufreq_frequency_table_target(policy, exynos4_freq_table, | ||
293 | freqs.old, relation, &old_index)) | ||
294 | goto out; | ||
295 | |||
296 | if (cpufreq_frequency_table_target(policy, exynos4_freq_table, | ||
297 | target_freq, relation, &index)) | ||
298 | goto out; | ||
299 | |||
300 | err = 0; | ||
301 | |||
302 | freqs.new = exynos4_freq_table[index].frequency; | ||
303 | freqs.cpu = policy->cpu; | ||
304 | |||
305 | if (freqs.new == freqs.old) | ||
306 | goto out; | ||
307 | |||
308 | /* get the voltage value */ | ||
309 | arm_volt = exynos4_volt_table[index].arm_volt; | ||
310 | |||
311 | cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); | ||
312 | |||
313 | /* control regulator */ | ||
314 | if (freqs.new > freqs.old) { | ||
315 | /* Voltage up */ | ||
316 | regulator_set_voltage(arm_regulator, arm_volt, arm_volt); | ||
317 | } | ||
318 | |||
319 | /* Clock Configuration Procedure */ | ||
320 | exynos4_set_frequency(old_index, index); | ||
321 | |||
322 | cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); | ||
323 | |||
324 | /* control regulator */ | ||
325 | if (freqs.new < freqs.old) { | ||
326 | /* Voltage down */ | ||
327 | regulator_set_voltage(arm_regulator, arm_volt, arm_volt); | ||
328 | } | ||
329 | |||
330 | out: | ||
331 | mutex_unlock(&cpufreq_lock); | ||
332 | return err; | ||
333 | } | ||
334 | |||
335 | #ifdef CONFIG_PM | ||
336 | /* | ||
337 | * These suspend/resume are used as syscore_ops, it is already too | ||
338 | * late to set regulator voltages at this stage. | ||
339 | */ | ||
340 | static int exynos4_cpufreq_suspend(struct cpufreq_policy *policy) | ||
341 | { | ||
342 | return 0; | ||
343 | } | ||
344 | |||
345 | static int exynos4_cpufreq_resume(struct cpufreq_policy *policy) | ||
346 | { | ||
347 | return 0; | ||
348 | } | ||
349 | #endif | ||
350 | |||
351 | /** | ||
352 | * exynos4_cpufreq_pm_notifier - block CPUFREQ's activities in suspend-resume | ||
353 | * context | ||
354 | * @notifier | ||
355 | * @pm_event | ||
356 | * @v | ||
357 | * | ||
358 | * While frequency_locked == true, target() ignores every frequency but | ||
359 | * locking_frequency. The locking_frequency value is the initial frequency, | ||
360 | * which is set by the bootloader. In order to eliminate possible | ||
361 | * inconsistency in clock values, we save and restore frequencies during | ||
362 | * suspend and resume and block CPUFREQ activities. Note that the standard | ||
363 | * suspend/resume cannot be used as they are too deep (syscore_ops) for | ||
364 | * regulator actions. | ||
365 | */ | ||
366 | static int exynos4_cpufreq_pm_notifier(struct notifier_block *notifier, | ||
367 | unsigned long pm_event, void *v) | ||
368 | { | ||
369 | struct cpufreq_policy *policy = cpufreq_cpu_get(0); /* boot CPU */ | ||
370 | static unsigned int saved_frequency; | ||
371 | unsigned int temp; | ||
372 | |||
373 | mutex_lock(&cpufreq_lock); | ||
374 | switch (pm_event) { | ||
375 | case PM_SUSPEND_PREPARE: | ||
376 | if (frequency_locked) | ||
377 | goto out; | ||
378 | frequency_locked = true; | ||
379 | |||
380 | if (locking_frequency) { | ||
381 | saved_frequency = exynos4_getspeed(0); | ||
382 | |||
383 | mutex_unlock(&cpufreq_lock); | ||
384 | exynos4_target(policy, locking_frequency, | ||
385 | CPUFREQ_RELATION_H); | ||
386 | mutex_lock(&cpufreq_lock); | ||
387 | } | ||
388 | |||
389 | break; | ||
390 | case PM_POST_SUSPEND: | ||
391 | |||
392 | if (saved_frequency) { | ||
393 | /* | ||
394 | * While frequency_locked, only locking_frequency | ||
395 | * is valid for target(). In order to use | ||
396 | * saved_frequency while keeping frequency_locked, | ||
397 | * we temporarly overwrite locking_frequency. | ||
398 | */ | ||
399 | temp = locking_frequency; | ||
400 | locking_frequency = saved_frequency; | ||
401 | |||
402 | mutex_unlock(&cpufreq_lock); | ||
403 | exynos4_target(policy, locking_frequency, | ||
404 | CPUFREQ_RELATION_H); | ||
405 | mutex_lock(&cpufreq_lock); | ||
406 | |||
407 | locking_frequency = temp; | ||
408 | } | ||
409 | |||
410 | frequency_locked = false; | ||
411 | break; | ||
412 | } | ||
413 | out: | ||
414 | mutex_unlock(&cpufreq_lock); | ||
415 | |||
416 | return NOTIFY_OK; | ||
417 | } | ||
418 | |||
419 | static struct notifier_block exynos4_cpufreq_nb = { | ||
420 | .notifier_call = exynos4_cpufreq_pm_notifier, | ||
421 | }; | ||
422 | |||
423 | static int exynos4_cpufreq_cpu_init(struct cpufreq_policy *policy) | ||
424 | { | ||
425 | int ret; | ||
426 | |||
427 | policy->cur = policy->min = policy->max = exynos4_getspeed(policy->cpu); | ||
428 | |||
429 | cpufreq_frequency_table_get_attr(exynos4_freq_table, policy->cpu); | ||
430 | |||
431 | /* set the transition latency value */ | ||
432 | policy->cpuinfo.transition_latency = 100000; | ||
433 | |||
434 | /* | ||
435 | * EXYNOS4 multi-core processors has 2 cores | ||
436 | * that the frequency cannot be set independently. | ||
437 | * Each cpu is bound to the same speed. | ||
438 | * So the affected cpu is all of the cpus. | ||
439 | */ | ||
440 | if (!cpu_online(1)) { | ||
441 | cpumask_copy(policy->related_cpus, cpu_possible_mask); | ||
442 | cpumask_copy(policy->cpus, cpu_online_mask); | ||
443 | } else { | ||
444 | cpumask_setall(policy->cpus); | ||
445 | } | ||
446 | |||
447 | ret = cpufreq_frequency_table_cpuinfo(policy, exynos4_freq_table); | ||
448 | if (ret) | ||
449 | return ret; | ||
450 | |||
451 | cpufreq_frequency_table_get_attr(exynos4_freq_table, policy->cpu); | ||
452 | |||
453 | return 0; | ||
454 | } | ||
455 | |||
456 | static int exynos4_cpufreq_cpu_exit(struct cpufreq_policy *policy) | ||
457 | { | ||
458 | cpufreq_frequency_table_put_attr(policy->cpu); | ||
459 | return 0; | ||
460 | } | ||
461 | |||
462 | static struct freq_attr *exynos4_cpufreq_attr[] = { | ||
463 | &cpufreq_freq_attr_scaling_available_freqs, | ||
464 | NULL, | ||
465 | }; | ||
466 | |||
467 | static struct cpufreq_driver exynos4_driver = { | ||
468 | .flags = CPUFREQ_STICKY, | ||
469 | .verify = exynos4_verify_speed, | ||
470 | .target = exynos4_target, | ||
471 | .get = exynos4_getspeed, | ||
472 | .init = exynos4_cpufreq_cpu_init, | ||
473 | .exit = exynos4_cpufreq_cpu_exit, | ||
474 | .name = "exynos4_cpufreq", | ||
475 | .attr = exynos4_cpufreq_attr, | ||
476 | #ifdef CONFIG_PM | ||
477 | .suspend = exynos4_cpufreq_suspend, | ||
478 | .resume = exynos4_cpufreq_resume, | ||
479 | #endif | ||
480 | }; | ||
481 | |||
482 | static int __init exynos4_cpufreq_init(void) | ||
483 | { | 245 | { |
484 | int i; | 246 | int i; |
485 | unsigned int tmp; | 247 | unsigned int tmp; |
248 | unsigned long rate; | ||
486 | 249 | ||
487 | cpu_clk = clk_get(NULL, "armclk"); | 250 | cpu_clk = clk_get(NULL, "armclk"); |
488 | if (IS_ERR(cpu_clk)) | 251 | if (IS_ERR(cpu_clk)) |
489 | return PTR_ERR(cpu_clk); | 252 | return PTR_ERR(cpu_clk); |
490 | 253 | ||
491 | locking_frequency = exynos4_getspeed(0); | ||
492 | |||
493 | moutcore = clk_get(NULL, "moutcore"); | 254 | moutcore = clk_get(NULL, "moutcore"); |
494 | if (IS_ERR(moutcore)) | 255 | if (IS_ERR(moutcore)) |
495 | goto out; | 256 | goto err_moutcore; |
496 | 257 | ||
497 | mout_mpll = clk_get(NULL, "mout_mpll"); | 258 | mout_mpll = clk_get(NULL, "mout_mpll"); |
498 | if (IS_ERR(mout_mpll)) | 259 | if (IS_ERR(mout_mpll)) |
499 | goto out; | 260 | goto err_mout_mpll; |
261 | |||
262 | rate = clk_get_rate(mout_mpll) / 1000; | ||
500 | 263 | ||
501 | mout_apll = clk_get(NULL, "mout_apll"); | 264 | mout_apll = clk_get(NULL, "mout_apll"); |
502 | if (IS_ERR(mout_apll)) | 265 | if (IS_ERR(mout_apll)) |
503 | goto out; | 266 | goto err_mout_apll; |
504 | |||
505 | arm_regulator = regulator_get(NULL, "vdd_arm"); | ||
506 | if (IS_ERR(arm_regulator)) { | ||
507 | printk(KERN_ERR "failed to get resource %s\n", "vdd_arm"); | ||
508 | goto out; | ||
509 | } | ||
510 | |||
511 | register_pm_notifier(&exynos4_cpufreq_nb); | ||
512 | 267 | ||
513 | tmp = __raw_readl(S5P_CLKDIV_CPU); | 268 | tmp = __raw_readl(S5P_CLKDIV_CPU); |
514 | 269 | ||
515 | for (i = L0; i < CPUFREQ_LEVEL_END; i++) { | 270 | for (i = L0; i < CPUFREQ_LEVEL_END; i++) { |
516 | tmp &= ~(S5P_CLKDIV_CPU0_CORE_MASK | | 271 | tmp &= ~(S5P_CLKDIV_CPU0_CORE_MASK | |
517 | S5P_CLKDIV_CPU0_COREM0_MASK | | 272 | S5P_CLKDIV_CPU0_COREM0_MASK | |
518 | S5P_CLKDIV_CPU0_COREM1_MASK | | 273 | S5P_CLKDIV_CPU0_COREM1_MASK | |
519 | S5P_CLKDIV_CPU0_PERIPH_MASK | | 274 | S5P_CLKDIV_CPU0_PERIPH_MASK | |
520 | S5P_CLKDIV_CPU0_ATB_MASK | | 275 | S5P_CLKDIV_CPU0_ATB_MASK | |
521 | S5P_CLKDIV_CPU0_PCLKDBG_MASK | | 276 | S5P_CLKDIV_CPU0_PCLKDBG_MASK | |
522 | S5P_CLKDIV_CPU0_APLL_MASK); | 277 | S5P_CLKDIV_CPU0_APLL_MASK); |
523 | 278 | ||
524 | tmp |= ((clkdiv_cpu0[i][0] << S5P_CLKDIV_CPU0_CORE_SHIFT) | | 279 | tmp |= ((clkdiv_cpu0[i][0] << S5P_CLKDIV_CPU0_CORE_SHIFT) | |
525 | (clkdiv_cpu0[i][1] << S5P_CLKDIV_CPU0_COREM0_SHIFT) | | 280 | (clkdiv_cpu0[i][1] << S5P_CLKDIV_CPU0_COREM0_SHIFT) | |
@@ -529,29 +284,33 @@ static int __init exynos4_cpufreq_init(void) | |||
529 | (clkdiv_cpu0[i][5] << S5P_CLKDIV_CPU0_PCLKDBG_SHIFT) | | 284 | (clkdiv_cpu0[i][5] << S5P_CLKDIV_CPU0_PCLKDBG_SHIFT) | |
530 | (clkdiv_cpu0[i][6] << S5P_CLKDIV_CPU0_APLL_SHIFT)); | 285 | (clkdiv_cpu0[i][6] << S5P_CLKDIV_CPU0_APLL_SHIFT)); |
531 | 286 | ||
532 | exynos4_clkdiv_table[i].clkdiv = tmp; | 287 | exynos4210_clkdiv_table[i].clkdiv = tmp; |
533 | } | 288 | } |
534 | 289 | ||
535 | return cpufreq_register_driver(&exynos4_driver); | 290 | info->mpll_freq_khz = rate; |
536 | 291 | info->pm_lock_idx = L2; | |
537 | out: | 292 | info->pll_safe_idx = L2; |
538 | if (!IS_ERR(cpu_clk)) | 293 | info->max_support_idx = max_support_idx; |
539 | clk_put(cpu_clk); | 294 | info->min_support_idx = min_support_idx; |
295 | info->cpu_clk = cpu_clk; | ||
296 | info->volt_table = exynos4210_volt_table; | ||
297 | info->freq_table = exynos4210_freq_table; | ||
298 | info->set_freq = exynos4210_set_frequency; | ||
299 | info->need_apll_change = exynos4210_pms_change; | ||
540 | 300 | ||
541 | if (!IS_ERR(moutcore)) | 301 | return 0; |
542 | clk_put(moutcore); | ||
543 | 302 | ||
303 | err_mout_apll: | ||
544 | if (!IS_ERR(mout_mpll)) | 304 | if (!IS_ERR(mout_mpll)) |
545 | clk_put(mout_mpll); | 305 | clk_put(mout_mpll); |
306 | err_mout_mpll: | ||
307 | if (!IS_ERR(moutcore)) | ||
308 | clk_put(moutcore); | ||
309 | err_moutcore: | ||
310 | if (!IS_ERR(cpu_clk)) | ||
311 | clk_put(cpu_clk); | ||
546 | 312 | ||
547 | if (!IS_ERR(mout_apll)) | 313 | pr_debug("%s: failed initialization\n", __func__); |
548 | clk_put(mout_apll); | ||
549 | |||
550 | if (!IS_ERR(arm_regulator)) | ||
551 | regulator_put(arm_regulator); | ||
552 | |||
553 | printk(KERN_ERR "%s: failed initialization\n", __func__); | ||
554 | |||
555 | return -EINVAL; | 314 | return -EINVAL; |
556 | } | 315 | } |
557 | late_initcall(exynos4_cpufreq_init); | 316 | EXPORT_SYMBOL(exynos4210_cpufreq_init); |