]> Pileus Git - ~andy/linux/blob - drivers/cpufreq/tegra-cpufreq.c
keep shadowed vfsmounts together
[~andy/linux] / drivers / cpufreq / tegra-cpufreq.c
1 /*
2  * Copyright (C) 2010 Google, Inc.
3  *
4  * Author:
5  *      Colin Cross <ccross@google.com>
6  *      Based on arch/arm/plat-omap/cpu-omap.c, (C) 2005 Nokia Corporation
7  *
8  * This software is licensed under the terms of the GNU General Public
9  * License version 2, as published by the Free Software Foundation, and
10  * may be copied, distributed, and modified under those terms.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  */
18
19 #include <linux/kernel.h>
20 #include <linux/module.h>
21 #include <linux/types.h>
22 #include <linux/sched.h>
23 #include <linux/cpufreq.h>
24 #include <linux/delay.h>
25 #include <linux/init.h>
26 #include <linux/err.h>
27 #include <linux/clk.h>
28 #include <linux/io.h>
29 #include <linux/suspend.h>
30
31 static struct cpufreq_frequency_table freq_table[] = {
32         { .frequency = 216000 },
33         { .frequency = 312000 },
34         { .frequency = 456000 },
35         { .frequency = 608000 },
36         { .frequency = 760000 },
37         { .frequency = 816000 },
38         { .frequency = 912000 },
39         { .frequency = 1000000 },
40         { .frequency = CPUFREQ_TABLE_END },
41 };
42
43 #define NUM_CPUS        2
44
45 static struct clk *cpu_clk;
46 static struct clk *pll_x_clk;
47 static struct clk *pll_p_clk;
48 static struct clk *emc_clk;
49
50 static DEFINE_MUTEX(tegra_cpu_lock);
51 static bool is_suspended;
52
53 static int tegra_cpu_clk_set_rate(unsigned long rate)
54 {
55         int ret;
56
57         /*
58          * Take an extra reference to the main pll so it doesn't turn
59          * off when we move the cpu off of it
60          */
61         clk_prepare_enable(pll_x_clk);
62
63         ret = clk_set_parent(cpu_clk, pll_p_clk);
64         if (ret) {
65                 pr_err("Failed to switch cpu to clock pll_p\n");
66                 goto out;
67         }
68
69         if (rate == clk_get_rate(pll_p_clk))
70                 goto out;
71
72         ret = clk_set_rate(pll_x_clk, rate);
73         if (ret) {
74                 pr_err("Failed to change pll_x to %lu\n", rate);
75                 goto out;
76         }
77
78         ret = clk_set_parent(cpu_clk, pll_x_clk);
79         if (ret) {
80                 pr_err("Failed to switch cpu to clock pll_x\n");
81                 goto out;
82         }
83
84 out:
85         clk_disable_unprepare(pll_x_clk);
86         return ret;
87 }
88
89 static int tegra_update_cpu_speed(struct cpufreq_policy *policy,
90                 unsigned long rate)
91 {
92         int ret = 0;
93
94         /*
95          * Vote on memory bus frequency based on cpu frequency
96          * This sets the minimum frequency, display or avp may request higher
97          */
98         if (rate >= 816000)
99                 clk_set_rate(emc_clk, 600000000); /* cpu 816 MHz, emc max */
100         else if (rate >= 456000)
101                 clk_set_rate(emc_clk, 300000000); /* cpu 456 MHz, emc 150Mhz */
102         else
103                 clk_set_rate(emc_clk, 100000000);  /* emc 50Mhz */
104
105         ret = tegra_cpu_clk_set_rate(rate * 1000);
106         if (ret)
107                 pr_err("cpu-tegra: Failed to set cpu frequency to %lu kHz\n",
108                         rate);
109
110         return ret;
111 }
112
113 static int tegra_target(struct cpufreq_policy *policy, unsigned int index)
114 {
115         int ret = -EBUSY;
116
117         mutex_lock(&tegra_cpu_lock);
118
119         if (!is_suspended)
120                 ret = tegra_update_cpu_speed(policy,
121                                 freq_table[index].frequency);
122
123         mutex_unlock(&tegra_cpu_lock);
124         return ret;
125 }
126
127 static int tegra_pm_notify(struct notifier_block *nb, unsigned long event,
128         void *dummy)
129 {
130         mutex_lock(&tegra_cpu_lock);
131         if (event == PM_SUSPEND_PREPARE) {
132                 struct cpufreq_policy *policy = cpufreq_cpu_get(0);
133                 is_suspended = true;
134                 pr_info("Tegra cpufreq suspend: setting frequency to %d kHz\n",
135                         freq_table[0].frequency);
136                 if (clk_get_rate(cpu_clk) / 1000 != freq_table[0].frequency)
137                         tegra_update_cpu_speed(policy, freq_table[0].frequency);
138                 cpufreq_cpu_put(policy);
139         } else if (event == PM_POST_SUSPEND) {
140                 is_suspended = false;
141         }
142         mutex_unlock(&tegra_cpu_lock);
143
144         return NOTIFY_OK;
145 }
146
147 static struct notifier_block tegra_cpu_pm_notifier = {
148         .notifier_call = tegra_pm_notify,
149 };
150
151 static int tegra_cpu_init(struct cpufreq_policy *policy)
152 {
153         int ret;
154
155         if (policy->cpu >= NUM_CPUS)
156                 return -EINVAL;
157
158         clk_prepare_enable(emc_clk);
159         clk_prepare_enable(cpu_clk);
160
161         /* FIXME: what's the actual transition time? */
162         ret = cpufreq_generic_init(policy, freq_table, 300 * 1000);
163         if (ret) {
164                 clk_disable_unprepare(cpu_clk);
165                 clk_disable_unprepare(emc_clk);
166                 return ret;
167         }
168
169         if (policy->cpu == 0)
170                 register_pm_notifier(&tegra_cpu_pm_notifier);
171
172         policy->clk = cpu_clk;
173         return 0;
174 }
175
176 static int tegra_cpu_exit(struct cpufreq_policy *policy)
177 {
178         cpufreq_frequency_table_put_attr(policy->cpu);
179         clk_disable_unprepare(cpu_clk);
180         clk_disable_unprepare(emc_clk);
181         return 0;
182 }
183
184 static struct cpufreq_driver tegra_cpufreq_driver = {
185         .flags          = CPUFREQ_NEED_INITIAL_FREQ_CHECK,
186         .verify         = cpufreq_generic_frequency_table_verify,
187         .target_index   = tegra_target,
188         .get            = cpufreq_generic_get,
189         .init           = tegra_cpu_init,
190         .exit           = tegra_cpu_exit,
191         .name           = "tegra",
192         .attr           = cpufreq_generic_attr,
193 };
194
195 static int __init tegra_cpufreq_init(void)
196 {
197         cpu_clk = clk_get_sys(NULL, "cclk");
198         if (IS_ERR(cpu_clk))
199                 return PTR_ERR(cpu_clk);
200
201         pll_x_clk = clk_get_sys(NULL, "pll_x");
202         if (IS_ERR(pll_x_clk))
203                 return PTR_ERR(pll_x_clk);
204
205         pll_p_clk = clk_get_sys(NULL, "pll_p");
206         if (IS_ERR(pll_p_clk))
207                 return PTR_ERR(pll_p_clk);
208
209         emc_clk = clk_get_sys("cpu", "emc");
210         if (IS_ERR(emc_clk)) {
211                 clk_put(cpu_clk);
212                 return PTR_ERR(emc_clk);
213         }
214
215         return cpufreq_register_driver(&tegra_cpufreq_driver);
216 }
217
218 static void __exit tegra_cpufreq_exit(void)
219 {
220         cpufreq_unregister_driver(&tegra_cpufreq_driver);
221         clk_put(emc_clk);
222         clk_put(cpu_clk);
223 }
224
225
226 MODULE_AUTHOR("Colin Cross <ccross@android.com>");
227 MODULE_DESCRIPTION("cpufreq driver for Nvidia Tegra2");
228 MODULE_LICENSE("GPL");
229 module_init(tegra_cpufreq_init);
230 module_exit(tegra_cpufreq_exit);