blob: 63534a4da08a42ec43f166e73f6e534e8323a227 [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
53static int set_cpu_freq(struct cpufreq_policy *policy, unsigned int new_freq)
54{
55 int ret = 0;
56 struct cpufreq_freqs freqs;
57
58 freqs.old = policy->cur;
David Ng3f76d272012-02-08 10:43:37 -080059 freqs.new = new_freq;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070060 freqs.cpu = policy->cpu;
61 cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
62 ret = acpuclk_set_rate(policy->cpu, new_freq, SETRATE_CPUFREQ);
63 if (!ret)
64 cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
65
66 return ret;
67}
68
69#ifdef CONFIG_SMP
70static void set_cpu_work(struct work_struct *work)
71{
72 struct cpufreq_work_struct *cpu_work =
73 container_of(work, struct cpufreq_work_struct, work);
74
75 cpu_work->status = set_cpu_freq(cpu_work->policy, cpu_work->frequency);
76 complete(&cpu_work->complete);
77}
78#endif
79
80static int msm_cpufreq_target(struct cpufreq_policy *policy,
81 unsigned int target_freq,
82 unsigned int relation)
83{
84 int ret = -EFAULT;
85 int index;
86 struct cpufreq_frequency_table *table;
87#ifdef CONFIG_SMP
88 struct cpufreq_work_struct *cpu_work = NULL;
89 cpumask_var_t mask;
90
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070091 if (!cpu_active(policy->cpu)) {
92 pr_info("cpufreq: cpu %d is not active.\n", policy->cpu);
93 return -ENODEV;
94 }
Praveen Chidambaramfdf788b2012-04-03 17:46:09 -060095
96 if (!alloc_cpumask_var(&mask, GFP_KERNEL))
97 return -ENOMEM;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070098#endif
99
100 mutex_lock(&per_cpu(cpufreq_suspend, policy->cpu).suspend_mutex);
101
102 if (per_cpu(cpufreq_suspend, policy->cpu).device_suspended) {
103 pr_debug("cpufreq: cpu%d scheduling frequency change "
104 "in suspend.\n", policy->cpu);
105 ret = -EFAULT;
106 goto done;
107 }
108
109 table = cpufreq_frequency_get_table(policy->cpu);
110 if (cpufreq_frequency_table_target(policy, table, target_freq, relation,
111 &index)) {
112 pr_err("cpufreq: invalid target_freq: %d\n", target_freq);
113 ret = -EINVAL;
114 goto done;
115 }
116
117#ifdef CONFIG_CPU_FREQ_DEBUG
118 pr_debug("CPU[%d] target %d relation %d (%d-%d) selected %d\n",
119 policy->cpu, target_freq, relation,
120 policy->min, policy->max, table[index].frequency);
121#endif
122
123#ifdef CONFIG_SMP
124 cpu_work = &per_cpu(cpufreq_work, policy->cpu);
125 cpu_work->policy = policy;
126 cpu_work->frequency = table[index].frequency;
127 cpu_work->status = -ENODEV;
128
129 cpumask_clear(mask);
130 cpumask_set_cpu(policy->cpu, mask);
131 if (cpumask_equal(mask, &current->cpus_allowed)) {
132 ret = set_cpu_freq(cpu_work->policy, cpu_work->frequency);
133 goto done;
134 } else {
135 cancel_work_sync(&cpu_work->work);
136 INIT_COMPLETION(cpu_work->complete);
137 queue_work_on(policy->cpu, msm_cpufreq_wq, &cpu_work->work);
138 wait_for_completion(&cpu_work->complete);
139 }
140
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700141 ret = cpu_work->status;
142#else
143 ret = set_cpu_freq(policy, table[index].frequency);
144#endif
145
146done:
Praveen Chidambaramfdf788b2012-04-03 17:46:09 -0600147#ifdef CONFIG_SMP
148 free_cpumask_var(mask);
149#endif
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700150 mutex_unlock(&per_cpu(cpufreq_suspend, policy->cpu).suspend_mutex);
151 return ret;
152}
153
154static int msm_cpufreq_verify(struct cpufreq_policy *policy)
155{
156 cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq,
157 policy->cpuinfo.max_freq);
158 return 0;
159}
160
161static int __cpuinit msm_cpufreq_init(struct cpufreq_policy *policy)
162{
163 int cur_freq;
164 int index;
165 struct cpufreq_frequency_table *table;
166#ifdef CONFIG_SMP
167 struct cpufreq_work_struct *cpu_work = NULL;
168#endif
169
Pankaj Kumaref7c3942012-04-03 18:00:52 +0530170
171 table = cpufreq_frequency_get_table(policy->cpu);
172 if (table == NULL)
173 return -ENODEV;
Pankaj Kumar0f263682012-01-05 17:01:57 +0530174 /*
175 * In 8625 both cpu core's frequency can not
176 * be changed independently. Each cpu is bound to
177 * same frequency. Hence set the cpumask to all cpu.
178 */
179 if (cpu_is_msm8625())
180 cpumask_setall(policy->cpus);
181
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700182 if (cpufreq_frequency_table_cpuinfo(policy, table)) {
183#ifdef CONFIG_MSM_CPU_FREQ_SET_MIN_MAX
184 policy->cpuinfo.min_freq = CONFIG_MSM_CPU_FREQ_MIN;
185 policy->cpuinfo.max_freq = CONFIG_MSM_CPU_FREQ_MAX;
186#endif
187 }
188#ifdef CONFIG_MSM_CPU_FREQ_SET_MIN_MAX
189 policy->min = CONFIG_MSM_CPU_FREQ_MIN;
190 policy->max = CONFIG_MSM_CPU_FREQ_MAX;
191#endif
192
193 cur_freq = acpuclk_get_rate(policy->cpu);
194 if (cpufreq_frequency_table_target(policy, table, cur_freq,
Matt Wagantallb31e4682011-10-12 12:50:27 -0700195 CPUFREQ_RELATION_H, &index) &&
196 cpufreq_frequency_table_target(policy, table, cur_freq,
197 CPUFREQ_RELATION_L, &index)) {
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700198 pr_info("cpufreq: cpu%d at invalid freq: %d\n",
199 policy->cpu, cur_freq);
200 return -EINVAL;
201 }
202
203 if (cur_freq != table[index].frequency) {
204 int ret = 0;
205 ret = acpuclk_set_rate(policy->cpu, table[index].frequency,
206 SETRATE_CPUFREQ);
207 if (ret)
208 return ret;
209 pr_info("cpufreq: cpu%d init at %d switching to %d\n",
210 policy->cpu, cur_freq, table[index].frequency);
211 cur_freq = table[index].frequency;
212 }
213
214 policy->cur = cur_freq;
215
216 policy->cpuinfo.transition_latency =
217 acpuclk_get_switch_time() * NSEC_PER_USEC;
218#ifdef CONFIG_SMP
219 cpu_work = &per_cpu(cpufreq_work, policy->cpu);
220 INIT_WORK(&cpu_work->work, set_cpu_work);
221 init_completion(&cpu_work->complete);
222#endif
223
224 return 0;
225}
226
227static int msm_cpufreq_suspend(void)
228{
229 int cpu;
230
231 for_each_possible_cpu(cpu) {
232 mutex_lock(&per_cpu(cpufreq_suspend, cpu).suspend_mutex);
233 per_cpu(cpufreq_suspend, cpu).device_suspended = 1;
234 mutex_unlock(&per_cpu(cpufreq_suspend, cpu).suspend_mutex);
235 }
236
237 return NOTIFY_DONE;
238}
239
240static int msm_cpufreq_resume(void)
241{
242 int cpu;
243
244 for_each_possible_cpu(cpu) {
245 per_cpu(cpufreq_suspend, cpu).device_suspended = 0;
246 }
247
248 return NOTIFY_DONE;
249}
250
251static int msm_cpufreq_pm_event(struct notifier_block *this,
252 unsigned long event, void *ptr)
253{
254 switch (event) {
255 case PM_POST_HIBERNATION:
256 case PM_POST_SUSPEND:
257 return msm_cpufreq_resume();
258 case PM_HIBERNATION_PREPARE:
259 case PM_SUSPEND_PREPARE:
260 return msm_cpufreq_suspend();
261 default:
262 return NOTIFY_DONE;
263 }
264}
265
Stepan Moskovchenko5627bb42011-10-13 16:25:41 -0700266static struct freq_attr *msm_freq_attr[] = {
267 &cpufreq_freq_attr_scaling_available_freqs,
268 NULL,
269};
270
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700271static struct cpufreq_driver msm_cpufreq_driver = {
272 /* lps calculations are handled here. */
273 .flags = CPUFREQ_STICKY | CPUFREQ_CONST_LOOPS,
274 .init = msm_cpufreq_init,
275 .verify = msm_cpufreq_verify,
276 .target = msm_cpufreq_target,
277 .name = "msm",
Stepan Moskovchenko5627bb42011-10-13 16:25:41 -0700278 .attr = msm_freq_attr,
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700279};
280
281static struct notifier_block msm_cpufreq_pm_notifier = {
282 .notifier_call = msm_cpufreq_pm_event,
283};
284
285static int __init msm_cpufreq_register(void)
286{
287 int cpu;
288
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700289 for_each_possible_cpu(cpu) {
290 mutex_init(&(per_cpu(cpufreq_suspend, cpu).suspend_mutex));
291 per_cpu(cpufreq_suspend, cpu).device_suspended = 0;
292 }
293
294#ifdef CONFIG_SMP
295 msm_cpufreq_wq = create_workqueue("msm-cpufreq");
296#endif
297
298 register_pm_notifier(&msm_cpufreq_pm_notifier);
299 return cpufreq_register_driver(&msm_cpufreq_driver);
300}
301
302late_initcall(msm_cpufreq_register);
303