]> Gitweb @ Texas Instruments - Open Source Git Repositories - git.TI.com/gitweb - sitara-epos/sitara-epos-kernel.git/blob - drivers/cpufreq/omap-cpufreq.c
cpufreq: OMAP: AM33XX: Use PM notifiers to scale voltage in suspend
[sitara-epos/sitara-epos-kernel.git] / drivers / cpufreq / omap-cpufreq.c
1 /*
2  *  CPU frequency scaling for OMAP using OPP information
3  *
4  *  Copyright (C) 2005 Nokia Corporation
5  *  Written by Tony Lindgren <tony@atomide.com>
6  *
7  *  Based on cpu-sa1110.c, Copyright (C) 2001 Russell King
8  *
9  * Copyright (C) 2007-2011 Texas Instruments, Inc.
10  * - OMAP3/4 support by Rajendra Nayak, Santosh Shilimkar
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License version 2 as
14  * published by the Free Software Foundation.
15  */
16 #include <linux/types.h>
17 #include <linux/kernel.h>
18 #include <linux/sched.h>
19 #include <linux/cpufreq.h>
20 #include <linux/delay.h>
21 #include <linux/init.h>
22 #include <linux/err.h>
23 #include <linux/clk.h>
24 #include <linux/io.h>
25 #include <linux/opp.h>
26 #include <linux/cpu.h>
27 #include <linux/module.h>
28 #include <linux/regulator/consumer.h>
29 #include <linux/suspend.h>
31 #include <asm/system.h>
32 #include <asm/smp_plat.h>
33 #include <asm/cpu.h>
35 #include <plat/clock.h>
36 #include <plat/omap-pm.h>
37 #include <plat/common.h>
38 #include <plat/omap_device.h>
40 #include <mach/hardware.h>
42 /* Tolerance for MPU voltage is 4%, we have to pass +4% as a
43  * maximum voltage while setting the MPU regulator voltage.
44  * Which is taken from AM33XX datasheet */
45 #define MPU_TOLERANCE   4
46 #define PER_ROUND_VAL   100
48 /* Use 275MHz when entering suspend */
49 #define SLEEP_FREQ      (275 * 1000)
52 #ifdef CONFIG_SMP
53 struct lpj_info {
54         unsigned long   ref;
55         unsigned int    freq;
56 };
58 static DEFINE_PER_CPU(struct lpj_info, lpj_ref);
59 static struct lpj_info global_lpj_ref;
60 #endif
62 static struct cpufreq_frequency_table *freq_table;
63 static atomic_t freq_table_users = ATOMIC_INIT(0);
64 static struct clk *mpu_clk;
65 static char *mpu_clk_name;
66 static struct device *mpu_dev;
67 static struct regulator *mpu_reg;
68 static DEFINE_MUTEX(omap_cpu_lock);
69 static bool is_suspended;
71 static int omap_verify_speed(struct cpufreq_policy *policy)
72 {
73         if (!freq_table)
74                 return -EINVAL;
75         return cpufreq_frequency_table_verify(policy, freq_table);
76 }
78 static unsigned int omap_getspeed(unsigned int cpu)
79 {
80         unsigned long rate;
82         if (cpu >= NR_CPUS)
83                 return 0;
85         rate = clk_get_rate(mpu_clk) / 1000;
86         return rate;
87 }
89 static int omap_target(struct cpufreq_policy *policy,
90                        unsigned int target_freq,
91                        unsigned int relation)
92 {
93         unsigned int i;
94         int ret = 0;
95         struct cpufreq_freqs freqs;
96         struct opp *opp;
97         int volt_old = 0, volt_new = 0;
99         if (is_suspended)
100                 return -EBUSY;
102         if (!freq_table) {
103                 dev_err(mpu_dev, "%s: cpu%d: no freq table!\n", __func__,
104                                 policy->cpu);
105                 return -EINVAL;
106         }
108         ret = cpufreq_frequency_table_target(policy, freq_table, target_freq,
109                         relation, &i);
110         if (ret) {
111                 dev_dbg(mpu_dev, "%s: cpu%d: no freq match for %d(ret=%d)\n",
112                         __func__, policy->cpu, target_freq, ret);
113                 return ret;
114         }
115         freqs.new = freq_table[i].frequency;
116         if (!freqs.new) {
117                 dev_err(mpu_dev, "%s: cpu%d: no match for freq %d\n", __func__,
118                         policy->cpu, target_freq);
119                 return -EINVAL;
120         }
122         freqs.old = omap_getspeed(policy->cpu);
123         freqs.cpu = policy->cpu;
125         if (freqs.old == freqs.new && policy->cur == freqs.new)
126                 return ret;
128         opp = opp_find_freq_exact(mpu_dev, freqs.new * 1000, true);
129         if (IS_ERR(opp)) {
130                 dev_err(mpu_dev, "%s: cpu%d: no opp match for freq %d\n",
131                         __func__, policy->cpu, target_freq);
132                 return -EINVAL;
133         }
135         volt_new = opp_get_voltage(opp);
136         if (!volt_new) {
137                 dev_err(mpu_dev, "%s: cpu%d: no opp voltage for freq %d\n",
138                         __func__, policy->cpu, target_freq);
139                 return -EINVAL;
140         }
142         volt_old = regulator_get_voltage(mpu_reg);
144 #ifdef CONFIG_CPU_FREQ_DEBUG
145         pr_info("cpufreq-omap: frequency transition: %u --> %u\n",
146                         freqs.old, freqs.new);
147         pr_info("cpufreq-omap: voltage transition: %d --> %d\n",
148                         volt_old, volt_new);
149 #endif
151         if (freqs.new > freqs.old) {
152                 ret = regulator_set_voltage(mpu_reg, volt_new,
153                         volt_new + (volt_new * MPU_TOLERANCE) / PER_ROUND_VAL);
154                 if (ret) {
155                         dev_err(mpu_dev, "%s: unable to set voltage to %d uV (for %u MHz)\n",
156                                 __func__, volt_new, freqs.new/1000);
157                         return ret;
158                 }
159         }
161         /* notifiers */
162         for_each_cpu(i, policy->cpus) {
163                 freqs.cpu = i;
164                 cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
165         }
167         ret = clk_set_rate(mpu_clk, freqs.new * 1000);
168         freqs.new = omap_getspeed(policy->cpu);
170 #ifdef CONFIG_SMP
171         /*
172          * Note that loops_per_jiffy is not updated on SMP systems in
173          * cpufreq driver. So, update the per-CPU loops_per_jiffy value
174          * on frequency transition. We need to update all dependent CPUs.
175          */
176         for_each_cpu(i, policy->cpus) {
177                 struct lpj_info *lpj = &per_cpu(lpj_ref, i);
178                 if (!lpj->freq) {
179                         lpj->ref = per_cpu(cpu_data, i).loops_per_jiffy;
180                         lpj->freq = freqs.old;
181                 }
183                 per_cpu(cpu_data, i).loops_per_jiffy =
184                         cpufreq_scale(lpj->ref, lpj->freq, freqs.new);
185         }
187         /* And don't forget to adjust the global one */
188         if (!global_lpj_ref.freq) {
189                 global_lpj_ref.ref = loops_per_jiffy;
190                 global_lpj_ref.freq = freqs.old;
191         }
192         loops_per_jiffy = cpufreq_scale(global_lpj_ref.ref, global_lpj_ref.freq,
193                                         freqs.new);
194 #endif
196         /* notifiers */
197         for_each_cpu(i, policy->cpus) {
198                 freqs.cpu = i;
199                 cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
200         }
202         if (freqs.new < freqs.old) {
203                 ret = regulator_set_voltage(mpu_reg, volt_new,
204                         volt_new + (volt_new * MPU_TOLERANCE) / PER_ROUND_VAL);
205                 if (ret) {
206                         unsigned int temp;
208                         dev_err(mpu_dev, "%s: unable to set voltage to %d uV (for %u MHz)\n",
209                                 __func__, volt_new, freqs.new/1000);
211                         if (clk_set_rate(mpu_clk, freqs.old * 1000)) {
212                                 dev_err(mpu_dev,
213                                         "%s: failed restoring clock rate to %u MHz, clock rate is %u MHz",
214                                         __func__,
215                                         freqs.old/1000, freqs.new/1000);
216                                 return ret;
217                         }
219                         temp = freqs.new;
220                         freqs.new = freqs.old;
221                         freqs.old = temp;
223                         for_each_cpu(i, policy->cpus) {
224                                 freqs.cpu = i;
225                                 cpufreq_notify_transition(&freqs,
226                                         CPUFREQ_PRECHANGE);
227                                 cpufreq_notify_transition(&freqs,
228                                         CPUFREQ_POSTCHANGE);
229                         }
230                         return ret;
231                 }
232         }
234         return ret;
237 static inline void freq_table_free(void)
239         if (atomic_dec_and_test(&freq_table_users))
240                 opp_free_cpufreq_table(mpu_dev, &freq_table);
243 static int omap_pm_notify(struct notifier_block *nb, unsigned long event,
244                                 void *dummy)
246         struct cpufreq_policy *policy = cpufreq_cpu_get(0);
247         static unsigned int saved_frequency;
249         mutex_lock(&omap_cpu_lock);
250         switch (event) {
251         case PM_SUSPEND_PREPARE:
252                 if (is_suspended)
253                         goto out;
255                 saved_frequency = omap_getspeed(0);
257                 mutex_unlock(&omap_cpu_lock);
258                 omap_target(policy, SLEEP_FREQ, CPUFREQ_RELATION_H);
259                 mutex_lock(&omap_cpu_lock);
260                 is_suspended = true;
261                 break;
263         case PM_POST_SUSPEND:
264                 is_suspended = false;
265                 mutex_unlock(&omap_cpu_lock);
266                 omap_target(policy, saved_frequency, CPUFREQ_RELATION_H);
267                 mutex_lock(&omap_cpu_lock);
268                 break;
269         }
270 out:
271         mutex_unlock(&omap_cpu_lock);
273         return NOTIFY_OK;
276 static struct notifier_block omap_cpu_pm_notifier = {
277         .notifier_call = omap_pm_notify,
278 };
280 static int __cpuinit omap_cpu_init(struct cpufreq_policy *policy)
282         int result = 0;
284         mpu_clk = clk_get(NULL, mpu_clk_name);
285         if (IS_ERR(mpu_clk))
286                 return PTR_ERR(mpu_clk);
288         mpu_reg = regulator_get(NULL, "vdd_mpu");
289         if (IS_ERR(mpu_reg)) {
290                 result = -EINVAL;
291                 goto fail_ck;
292         }
294         /* success of regulator_get doesn't gurantee presence of driver for
295            physical regulator and presence of physical regulator (this
296            situation arises if dummy regulator is enabled),so check voltage
297            to verify that physical regulator and it's driver is present
298          */
299         if (regulator_get_voltage(mpu_reg) < 0) {
300                 result = -EINVAL;
301                 goto fail_reg;
302         }
304         if (policy->cpu >= NR_CPUS) {
305                 result = -EINVAL;
306                 goto fail_reg;
307         }
309         policy->cur = policy->min = policy->max = omap_getspeed(policy->cpu);
311         if (atomic_inc_return(&freq_table_users) == 1)
312                 result = opp_init_cpufreq_table(mpu_dev, &freq_table);
314         if (result) {
315                 dev_err(mpu_dev, "%s: cpu%d: failed creating freq table[%d]\n",
316                                 __func__, policy->cpu, result);
317                 goto fail_reg;
318         }
320         result = cpufreq_frequency_table_cpuinfo(policy, freq_table);
321         if (result)
322                 goto fail_table;
324         cpufreq_frequency_table_get_attr(freq_table, policy->cpu);
326         policy->min = policy->cpuinfo.min_freq;
327         policy->max = policy->cpuinfo.max_freq;
328         policy->cur = omap_getspeed(policy->cpu);
330         /*
331          * On OMAP SMP configuartion, both processors share the voltage
332          * and clock. So both CPUs needs to be scaled together and hence
333          * needs software co-ordination. Use cpufreq affected_cpus
334          * interface to handle this scenario. Additional is_smp() check
335          * is to keep SMP_ON_UP build working.
336          */
337         if (is_smp()) {
338                 policy->shared_type = CPUFREQ_SHARED_TYPE_ANY;
339                 cpumask_setall(policy->cpus);
340         }
342         /* FIXME: what's the actual transition time? */
343         policy->cpuinfo.transition_latency = 300 * 1000;
345         register_pm_notifier(&omap_cpu_pm_notifier);
347         return 0;
349 fail_table:
350         freq_table_free();
351 fail_reg:
352         regulator_put(mpu_reg);
353 fail_ck:
354         clk_put(mpu_clk);
355         return result;
358 static int omap_cpu_exit(struct cpufreq_policy *policy)
360         freq_table_free();
361         clk_put(mpu_clk);
362         return 0;
365 static struct freq_attr *omap_cpufreq_attr[] = {
366         &cpufreq_freq_attr_scaling_available_freqs,
367         NULL,
368 };
370 static struct cpufreq_driver omap_driver = {
371         .flags          = CPUFREQ_STICKY,
372         .verify         = omap_verify_speed,
373         .target         = omap_target,
374         .get            = omap_getspeed,
375         .init           = omap_cpu_init,
376         .exit           = omap_cpu_exit,
377         .name           = "omap",
378         .attr           = omap_cpufreq_attr,
379 };
381 static int __init omap_cpufreq_init(void)
383         if (cpu_is_omap24xx())
384                 mpu_clk_name = "virt_prcm_set";
385         else if (cpu_is_omap34xx() && !cpu_is_am33xx())
386                 mpu_clk_name = "dpll1_ck";
387         else if (cpu_is_omap44xx() || cpu_is_am33xx())
388                 mpu_clk_name = "dpll_mpu_ck";
390         if (!mpu_clk_name) {
391                 pr_err("%s: unsupported Silicon?\n", __func__);
392                 return -EINVAL;
393         }
395         mpu_dev = omap_device_get_by_hwmod_name("mpu");
396         if (!mpu_dev) {
397                 pr_warning("%s: unable to get the mpu device\n", __func__);
398                 return -EINVAL;
399         }
401         return cpufreq_register_driver(&omap_driver);
404 static void __exit omap_cpufreq_exit(void)
406         cpufreq_unregister_driver(&omap_driver);
409 MODULE_DESCRIPTION("cpufreq driver for OMAP SoCs");
410 MODULE_LICENSE("GPL");
411 module_init(omap_cpufreq_init);
412 module_exit(omap_cpufreq_exit);