blob: 24e4dd453fab259a957d7a5b0f420bb6fd5ff426 [file] [log] [blame]
Jaecheol Leea125a172012-01-07 20:18:35 +09001/*
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
31static struct exynos_dvfs_info *exynos_info;
32
33static struct regulator *arm_regulator;
34static struct cpufreq_freqs freqs;
35
36static unsigned int locking_frequency;
37static bool frequency_locked;
38static DEFINE_MUTEX(cpufreq_lock);
39
40int exynos_verify_speed(struct cpufreq_policy *policy)
41{
42 return cpufreq_frequency_table_verify(policy,
43 exynos_info->freq_table);
44}
45
46unsigned int exynos_getspeed(unsigned int cpu)
47{
48 return clk_get_rate(exynos_info->cpu_clk) / 1000;
49}
50
51static 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
124out:
125 mutex_unlock(&cpufreq_lock);
126
127 return ret;
128}
129
130#ifdef CONFIG_PM
131static int exynos_cpufreq_suspend(struct cpufreq_policy *policy)
132{
133 return 0;
134}
135
136static 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 */
157static 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 }
203out:
204 mutex_unlock(&cpufreq_lock);
205
206 return NOTIFY_OK;
207}
208
209static struct notifier_block exynos_cpufreq_nb = {
210 .notifier_call = exynos_cpufreq_pm_notifier,
211};
212
213static 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
238static 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
251static 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;
286err_cpufreq:
287 unregister_pm_notifier(&exynos_cpufreq_nb);
288
289 if (!IS_ERR(arm_regulator))
290 regulator_put(arm_regulator);
291err_vdd_arm:
292 kfree(exynos_info);
293 pr_debug("%s: failed initialization\n", __func__);
294 return -EINVAL;
295}
296late_initcall(exynos_cpufreq_init);