blob: 81d01c24cf15b0dd38304562b2dade0605882422 [file] [log] [blame]
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001/* arch/arm/mach-msm/cpufreq.c
2 *
3 * MSM architecture cpufreq driver
4 *
5 * Copyright (C) 2007 Google, Inc.
Vikram Mulukutlabc2e9572011-11-04 03:41:38 -07006 * Copyright (c) 2007-2012, Code Aurora Forum. All rights reserved.
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07007 * Author: Mike A. Chan <mikechan@google.com>
8 *
9 * This software is licensed under the terms of the GNU General Public
10 * License version 2, as published by the Free Software Foundation, and
11 * may be copied, distributed, and modified under those terms.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 */
19
20#include <linux/earlysuspend.h>
21#include <linux/init.h>
22#include <linux/cpufreq.h>
23#include <linux/workqueue.h>
24#include <linux/completion.h>
25#include <linux/cpu.h>
26#include <linux/cpumask.h>
27#include <linux/sched.h>
28#include <linux/suspend.h>
Stepan Moskovchenkoaf25dd92011-08-05 18:12:48 -070029#include <mach/socinfo.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070030
31#include "acpuclock.h"
32
33#ifdef CONFIG_SMP
34struct cpufreq_work_struct {
35 struct work_struct work;
36 struct cpufreq_policy *policy;
37 struct completion complete;
38 int frequency;
39 int status;
40};
41
42static DEFINE_PER_CPU(struct cpufreq_work_struct, cpufreq_work);
43static struct workqueue_struct *msm_cpufreq_wq;
44#endif
45
46struct cpufreq_suspend_t {
47 struct mutex suspend_mutex;
48 int device_suspended;
49};
50
51static DEFINE_PER_CPU(struct cpufreq_suspend_t, cpufreq_suspend);
52
David Ngc79a2e02011-03-26 06:13:32 -070053static int override_cpu;
54
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070055static int set_cpu_freq(struct cpufreq_policy *policy, unsigned int new_freq)
56{
57 int ret = 0;
58 struct cpufreq_freqs freqs;
59
60 freqs.old = policy->cur;
David Ngc79a2e02011-03-26 06:13:32 -070061 if (override_cpu) {
62 if (policy->cur == policy->max)
63 return 0;
64 else
65 freqs.new = policy->max;
66 } else
67 freqs.new = new_freq;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070068 freqs.cpu = policy->cpu;
69 cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
70 ret = acpuclk_set_rate(policy->cpu, new_freq, SETRATE_CPUFREQ);
71 if (!ret)
72 cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
73
74 return ret;
75}
76
77#ifdef CONFIG_SMP
78static void set_cpu_work(struct work_struct *work)
79{
80 struct cpufreq_work_struct *cpu_work =
81 container_of(work, struct cpufreq_work_struct, work);
82
83 cpu_work->status = set_cpu_freq(cpu_work->policy, cpu_work->frequency);
84 complete(&cpu_work->complete);
85}
86#endif
87
88static int msm_cpufreq_target(struct cpufreq_policy *policy,
89 unsigned int target_freq,
90 unsigned int relation)
91{
92 int ret = -EFAULT;
93 int index;
94 struct cpufreq_frequency_table *table;
95#ifdef CONFIG_SMP
96 struct cpufreq_work_struct *cpu_work = NULL;
97 cpumask_var_t mask;
98
99 if (!alloc_cpumask_var(&mask, GFP_KERNEL))
100 return -ENOMEM;
101
102 if (!cpu_active(policy->cpu)) {
103 pr_info("cpufreq: cpu %d is not active.\n", policy->cpu);
104 return -ENODEV;
105 }
106#endif
107
108 mutex_lock(&per_cpu(cpufreq_suspend, policy->cpu).suspend_mutex);
109
110 if (per_cpu(cpufreq_suspend, policy->cpu).device_suspended) {
111 pr_debug("cpufreq: cpu%d scheduling frequency change "
112 "in suspend.\n", policy->cpu);
113 ret = -EFAULT;
114 goto done;
115 }
116
117 table = cpufreq_frequency_get_table(policy->cpu);
118 if (cpufreq_frequency_table_target(policy, table, target_freq, relation,
119 &index)) {
120 pr_err("cpufreq: invalid target_freq: %d\n", target_freq);
121 ret = -EINVAL;
122 goto done;
123 }
124
125#ifdef CONFIG_CPU_FREQ_DEBUG
126 pr_debug("CPU[%d] target %d relation %d (%d-%d) selected %d\n",
127 policy->cpu, target_freq, relation,
128 policy->min, policy->max, table[index].frequency);
129#endif
130
131#ifdef CONFIG_SMP
132 cpu_work = &per_cpu(cpufreq_work, policy->cpu);
133 cpu_work->policy = policy;
134 cpu_work->frequency = table[index].frequency;
135 cpu_work->status = -ENODEV;
136
137 cpumask_clear(mask);
138 cpumask_set_cpu(policy->cpu, mask);
139 if (cpumask_equal(mask, &current->cpus_allowed)) {
140 ret = set_cpu_freq(cpu_work->policy, cpu_work->frequency);
141 goto done;
142 } else {
143 cancel_work_sync(&cpu_work->work);
144 INIT_COMPLETION(cpu_work->complete);
145 queue_work_on(policy->cpu, msm_cpufreq_wq, &cpu_work->work);
146 wait_for_completion(&cpu_work->complete);
147 }
148
149 free_cpumask_var(mask);
150 ret = cpu_work->status;
151#else
152 ret = set_cpu_freq(policy, table[index].frequency);
153#endif
154
155done:
156 mutex_unlock(&per_cpu(cpufreq_suspend, policy->cpu).suspend_mutex);
157 return ret;
158}
159
160static int msm_cpufreq_verify(struct cpufreq_policy *policy)
161{
162 cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq,
163 policy->cpuinfo.max_freq);
164 return 0;
165}
166
167static int __cpuinit msm_cpufreq_init(struct cpufreq_policy *policy)
168{
169 int cur_freq;
170 int index;
171 struct cpufreq_frequency_table *table;
172#ifdef CONFIG_SMP
173 struct cpufreq_work_struct *cpu_work = NULL;
174#endif
175
176 table = cpufreq_frequency_get_table(policy->cpu);
Taniya Dasbe151612011-12-02 15:55:47 +0530177 if (table == NULL)
178 return -ENODEV;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700179 if (cpufreq_frequency_table_cpuinfo(policy, table)) {
180#ifdef CONFIG_MSM_CPU_FREQ_SET_MIN_MAX
181 policy->cpuinfo.min_freq = CONFIG_MSM_CPU_FREQ_MIN;
182 policy->cpuinfo.max_freq = CONFIG_MSM_CPU_FREQ_MAX;
183#endif
184 }
185#ifdef CONFIG_MSM_CPU_FREQ_SET_MIN_MAX
186 policy->min = CONFIG_MSM_CPU_FREQ_MIN;
187 policy->max = CONFIG_MSM_CPU_FREQ_MAX;
188#endif
189
190 cur_freq = acpuclk_get_rate(policy->cpu);
191 if (cpufreq_frequency_table_target(policy, table, cur_freq,
Matt Wagantallb31e4682011-10-12 12:50:27 -0700192 CPUFREQ_RELATION_H, &index) &&
193 cpufreq_frequency_table_target(policy, table, cur_freq,
194 CPUFREQ_RELATION_L, &index)) {
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700195 pr_info("cpufreq: cpu%d at invalid freq: %d\n",
196 policy->cpu, cur_freq);
197 return -EINVAL;
198 }
199
200 if (cur_freq != table[index].frequency) {
201 int ret = 0;
202 ret = acpuclk_set_rate(policy->cpu, table[index].frequency,
203 SETRATE_CPUFREQ);
204 if (ret)
205 return ret;
206 pr_info("cpufreq: cpu%d init at %d switching to %d\n",
207 policy->cpu, cur_freq, table[index].frequency);
208 cur_freq = table[index].frequency;
209 }
210
211 policy->cur = cur_freq;
212
213 policy->cpuinfo.transition_latency =
214 acpuclk_get_switch_time() * NSEC_PER_USEC;
215#ifdef CONFIG_SMP
216 cpu_work = &per_cpu(cpufreq_work, policy->cpu);
217 INIT_WORK(&cpu_work->work, set_cpu_work);
218 init_completion(&cpu_work->complete);
219#endif
220
221 return 0;
222}
223
224static int msm_cpufreq_suspend(void)
225{
226 int cpu;
227
228 for_each_possible_cpu(cpu) {
229 mutex_lock(&per_cpu(cpufreq_suspend, cpu).suspend_mutex);
230 per_cpu(cpufreq_suspend, cpu).device_suspended = 1;
231 mutex_unlock(&per_cpu(cpufreq_suspend, cpu).suspend_mutex);
232 }
233
234 return NOTIFY_DONE;
235}
236
237static int msm_cpufreq_resume(void)
238{
239 int cpu;
240
241 for_each_possible_cpu(cpu) {
242 per_cpu(cpufreq_suspend, cpu).device_suspended = 0;
243 }
244
245 return NOTIFY_DONE;
246}
247
248static int msm_cpufreq_pm_event(struct notifier_block *this,
249 unsigned long event, void *ptr)
250{
251 switch (event) {
252 case PM_POST_HIBERNATION:
253 case PM_POST_SUSPEND:
254 return msm_cpufreq_resume();
255 case PM_HIBERNATION_PREPARE:
256 case PM_SUSPEND_PREPARE:
257 return msm_cpufreq_suspend();
258 default:
259 return NOTIFY_DONE;
260 }
261}
262
David Ngc79a2e02011-03-26 06:13:32 -0700263static ssize_t store_mfreq(struct sysdev_class *class,
264 struct sysdev_class_attribute *attr,
265 const char *buf, size_t count)
266{
267 u64 val;
268
269 if (strict_strtoull(buf, 0, &val) < 0) {
270 pr_err("Invalid parameter to mfreq\n");
271 return 0;
272 }
273 if (val)
274 override_cpu = 1;
275 else
276 override_cpu = 0;
277 return count;
278}
279
280static SYSDEV_CLASS_ATTR(mfreq, 0200, NULL, store_mfreq);
281
Stepan Moskovchenko5627bb42011-10-13 16:25:41 -0700282static struct freq_attr *msm_freq_attr[] = {
283 &cpufreq_freq_attr_scaling_available_freqs,
284 NULL,
285};
286
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700287static struct cpufreq_driver msm_cpufreq_driver = {
288 /* lps calculations are handled here. */
289 .flags = CPUFREQ_STICKY | CPUFREQ_CONST_LOOPS,
290 .init = msm_cpufreq_init,
291 .verify = msm_cpufreq_verify,
292 .target = msm_cpufreq_target,
293 .name = "msm",
Stepan Moskovchenko5627bb42011-10-13 16:25:41 -0700294 .attr = msm_freq_attr,
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700295};
296
297static struct notifier_block msm_cpufreq_pm_notifier = {
298 .notifier_call = msm_cpufreq_pm_event,
299};
300
301static int __init msm_cpufreq_register(void)
302{
303 int cpu;
304
David Ngc79a2e02011-03-26 06:13:32 -0700305 int err = sysfs_create_file(&cpu_sysdev_class.kset.kobj,
306 &attr_mfreq.attr);
307 if (err)
308 pr_err("Failed to create sysfs mfreq\n");
309
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700310 for_each_possible_cpu(cpu) {
311 mutex_init(&(per_cpu(cpufreq_suspend, cpu).suspend_mutex));
312 per_cpu(cpufreq_suspend, cpu).device_suspended = 0;
313 }
314
315#ifdef CONFIG_SMP
316 msm_cpufreq_wq = create_workqueue("msm-cpufreq");
317#endif
318
319 register_pm_notifier(&msm_cpufreq_pm_notifier);
320 return cpufreq_register_driver(&msm_cpufreq_driver);
321}
322
323late_initcall(msm_cpufreq_register);
324