Commit | Line | Data |
---|---|---|
7056d423 | 1 | /* |
7056d423 CC |
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 | ||
9a25ba9a | 19 | #include <linux/clk.h> |
7056d423 | 20 | #include <linux/cpufreq.h> |
7056d423 | 21 | #include <linux/err.h> |
9a25ba9a DO |
22 | #include <linux/init.h> |
23 | #include <linux/module.h> | |
dc628cdf | 24 | #include <linux/platform_device.h> |
9a25ba9a | 25 | #include <linux/types.h> |
7056d423 | 26 | |
7056d423 | 27 | static struct cpufreq_frequency_table freq_table[] = { |
5d69030d VK |
28 | { .frequency = 216000 }, |
29 | { .frequency = 312000 }, | |
30 | { .frequency = 456000 }, | |
31 | { .frequency = 608000 }, | |
32 | { .frequency = 760000 }, | |
33 | { .frequency = 816000 }, | |
34 | { .frequency = 912000 }, | |
35 | { .frequency = 1000000 }, | |
36 | { .frequency = CPUFREQ_TABLE_END }, | |
7056d423 CC |
37 | }; |
38 | ||
dc628cdf DO |
39 | struct tegra20_cpufreq { |
40 | struct device *dev; | |
41 | struct cpufreq_driver driver; | |
42 | struct clk *cpu_clk; | |
43 | struct clk *pll_x_clk; | |
44 | struct clk *pll_p_clk; | |
45 | bool pll_x_prepared; | |
46 | }; | |
7056d423 | 47 | |
00917ddc VK |
48 | static unsigned int tegra_get_intermediate(struct cpufreq_policy *policy, |
49 | unsigned int index) | |
50 | { | |
dc628cdf DO |
51 | struct tegra20_cpufreq *cpufreq = cpufreq_get_driver_data(); |
52 | unsigned int ifreq = clk_get_rate(cpufreq->pll_p_clk) / 1000; | |
00917ddc VK |
53 | |
54 | /* | |
55 | * Don't switch to intermediate freq if: | |
56 | * - we are already at it, i.e. policy->cur == ifreq | |
57 | * - index corresponds to ifreq | |
58 | */ | |
c22d1cb0 | 59 | if (freq_table[index].frequency == ifreq || policy->cur == ifreq) |
00917ddc VK |
60 | return 0; |
61 | ||
62 | return ifreq; | |
63 | } | |
64 | ||
65 | static int tegra_target_intermediate(struct cpufreq_policy *policy, | |
66 | unsigned int index) | |
ce32ddaa | 67 | { |
dc628cdf | 68 | struct tegra20_cpufreq *cpufreq = cpufreq_get_driver_data(); |
ce32ddaa SW |
69 | int ret; |
70 | ||
71 | /* | |
72 | * Take an extra reference to the main pll so it doesn't turn | |
00917ddc | 73 | * off when we move the cpu off of it as enabling it again while we |
40cc5490 VK |
74 | * switch to it from tegra_target() would take additional time. |
75 | * | |
76 | * When target-freq is equal to intermediate freq we don't need to | |
77 | * switch to an intermediate freq and so this routine isn't called. | |
78 | * Also, we wouldn't be using pll_x anymore and must not take extra | |
79 | * reference to it, as it can be disabled now to save some power. | |
ce32ddaa | 80 | */ |
dc628cdf | 81 | clk_prepare_enable(cpufreq->pll_x_clk); |
ce32ddaa | 82 | |
dc628cdf | 83 | ret = clk_set_parent(cpufreq->cpu_clk, cpufreq->pll_p_clk); |
00917ddc | 84 | if (ret) |
dc628cdf | 85 | clk_disable_unprepare(cpufreq->pll_x_clk); |
00917ddc | 86 | else |
dc628cdf | 87 | cpufreq->pll_x_prepared = true; |
ce32ddaa | 88 | |
ce32ddaa SW |
89 | return ret; |
90 | } | |
91 | ||
e7b453d3 | 92 | static int tegra_target(struct cpufreq_policy *policy, unsigned int index) |
7056d423 | 93 | { |
dc628cdf | 94 | struct tegra20_cpufreq *cpufreq = cpufreq_get_driver_data(); |
e7b453d3 | 95 | unsigned long rate = freq_table[index].frequency; |
dc628cdf | 96 | unsigned int ifreq = clk_get_rate(cpufreq->pll_p_clk) / 1000; |
f39d4d5e | 97 | int ret; |
7056d423 | 98 | |
00917ddc VK |
99 | /* |
100 | * target freq == pll_p, don't need to take extra reference to pll_x_clk | |
101 | * as it isn't used anymore. | |
102 | */ | |
103 | if (rate == ifreq) | |
dc628cdf | 104 | return clk_set_parent(cpufreq->cpu_clk, cpufreq->pll_p_clk); |
00917ddc | 105 | |
dc628cdf | 106 | ret = clk_set_rate(cpufreq->pll_x_clk, rate * 1000); |
00917ddc | 107 | /* Restore to earlier frequency on error, i.e. pll_x */ |
d4019f0a | 108 | if (ret) |
dc628cdf | 109 | dev_err(cpufreq->dev, "Failed to change pll_x to %lu\n", rate); |
00917ddc | 110 | |
dc628cdf | 111 | ret = clk_set_parent(cpufreq->cpu_clk, cpufreq->pll_x_clk); |
00917ddc VK |
112 | /* This shouldn't fail while changing or restoring */ |
113 | WARN_ON(ret); | |
114 | ||
115 | /* | |
116 | * Drop count to pll_x clock only if we switched to intermediate freq | |
117 | * earlier while transitioning to a target frequency. | |
118 | */ | |
dc628cdf DO |
119 | if (cpufreq->pll_x_prepared) { |
120 | clk_disable_unprepare(cpufreq->pll_x_clk); | |
121 | cpufreq->pll_x_prepared = false; | |
00917ddc | 122 | } |
7056d423 | 123 | |
f56cc99e | 124 | return ret; |
7056d423 CC |
125 | } |
126 | ||
7056d423 CC |
127 | static int tegra_cpu_init(struct cpufreq_policy *policy) |
128 | { | |
dc628cdf | 129 | struct tegra20_cpufreq *cpufreq = cpufreq_get_driver_data(); |
99d428cf VK |
130 | int ret; |
131 | ||
dc628cdf | 132 | clk_prepare_enable(cpufreq->cpu_clk); |
89a5fb84 | 133 | |
7056d423 | 134 | /* FIXME: what's the actual transition time? */ |
99d428cf VK |
135 | ret = cpufreq_generic_init(policy, freq_table, 300 * 1000); |
136 | if (ret) { | |
dc628cdf | 137 | clk_disable_unprepare(cpufreq->cpu_clk); |
99d428cf VK |
138 | return ret; |
139 | } | |
7056d423 | 140 | |
dc628cdf | 141 | policy->clk = cpufreq->cpu_clk; |
d351cb31 | 142 | policy->suspend_freq = freq_table[0].frequency; |
7056d423 CC |
143 | return 0; |
144 | } | |
145 | ||
146 | static int tegra_cpu_exit(struct cpufreq_policy *policy) | |
147 | { | |
dc628cdf DO |
148 | struct tegra20_cpufreq *cpufreq = cpufreq_get_driver_data(); |
149 | ||
150 | clk_disable_unprepare(cpufreq->cpu_clk); | |
7056d423 CC |
151 | return 0; |
152 | } | |
153 | ||
dc628cdf | 154 | static int tegra20_cpufreq_probe(struct platform_device *pdev) |
7056d423 | 155 | { |
dc628cdf | 156 | struct tegra20_cpufreq *cpufreq; |
64cd64e7 DO |
157 | int err; |
158 | ||
dc628cdf DO |
159 | cpufreq = devm_kzalloc(&pdev->dev, sizeof(*cpufreq), GFP_KERNEL); |
160 | if (!cpufreq) | |
161 | return -ENOMEM; | |
a413d2ce | 162 | |
dc628cdf DO |
163 | cpufreq->cpu_clk = clk_get_sys(NULL, "cclk"); |
164 | if (IS_ERR(cpufreq->cpu_clk)) | |
165 | return PTR_ERR(cpufreq->cpu_clk); | |
c26cefd0 | 166 | |
dc628cdf DO |
167 | cpufreq->pll_x_clk = clk_get_sys(NULL, "pll_x"); |
168 | if (IS_ERR(cpufreq->pll_x_clk)) { | |
169 | err = PTR_ERR(cpufreq->pll_x_clk); | |
64cd64e7 DO |
170 | goto put_cpu; |
171 | } | |
c26cefd0 | 172 | |
dc628cdf DO |
173 | cpufreq->pll_p_clk = clk_get_sys(NULL, "pll_p"); |
174 | if (IS_ERR(cpufreq->pll_p_clk)) { | |
175 | err = PTR_ERR(cpufreq->pll_p_clk); | |
64cd64e7 DO |
176 | goto put_pll_x; |
177 | } | |
178 | ||
dc628cdf DO |
179 | cpufreq->dev = &pdev->dev; |
180 | cpufreq->driver.get = cpufreq_generic_get; | |
181 | cpufreq->driver.attr = cpufreq_generic_attr; | |
182 | cpufreq->driver.init = tegra_cpu_init; | |
183 | cpufreq->driver.exit = tegra_cpu_exit; | |
184 | cpufreq->driver.flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK; | |
185 | cpufreq->driver.verify = cpufreq_generic_frequency_table_verify; | |
186 | cpufreq->driver.suspend = cpufreq_generic_suspend; | |
187 | cpufreq->driver.driver_data = cpufreq; | |
188 | cpufreq->driver.target_index = tegra_target; | |
189 | cpufreq->driver.get_intermediate = tegra_get_intermediate; | |
190 | cpufreq->driver.target_intermediate = tegra_target_intermediate; | |
191 | snprintf(cpufreq->driver.name, CPUFREQ_NAME_LEN, "tegra"); | |
192 | ||
193 | err = cpufreq_register_driver(&cpufreq->driver); | |
64cd64e7 DO |
194 | if (err) |
195 | goto put_pll_p; | |
196 | ||
dc628cdf DO |
197 | platform_set_drvdata(pdev, cpufreq); |
198 | ||
64cd64e7 DO |
199 | return 0; |
200 | ||
201 | put_pll_p: | |
dc628cdf | 202 | clk_put(cpufreq->pll_p_clk); |
64cd64e7 | 203 | put_pll_x: |
dc628cdf | 204 | clk_put(cpufreq->pll_x_clk); |
64cd64e7 | 205 | put_cpu: |
dc628cdf | 206 | clk_put(cpufreq->cpu_clk); |
c26cefd0 | 207 | |
64cd64e7 | 208 | return err; |
7056d423 CC |
209 | } |
210 | ||
dc628cdf | 211 | static int tegra20_cpufreq_remove(struct platform_device *pdev) |
7056d423 | 212 | { |
dc628cdf DO |
213 | struct tegra20_cpufreq *cpufreq = platform_get_drvdata(pdev); |
214 | ||
215 | cpufreq_unregister_driver(&cpufreq->driver); | |
216 | ||
217 | clk_put(cpufreq->pll_p_clk); | |
218 | clk_put(cpufreq->pll_x_clk); | |
219 | clk_put(cpufreq->cpu_clk); | |
220 | ||
221 | return 0; | |
7056d423 CC |
222 | } |
223 | ||
dc628cdf DO |
224 | static struct platform_driver tegra20_cpufreq_driver = { |
225 | .probe = tegra20_cpufreq_probe, | |
226 | .remove = tegra20_cpufreq_remove, | |
227 | .driver = { | |
228 | .name = "tegra20-cpufreq", | |
229 | }, | |
230 | }; | |
231 | module_platform_driver(tegra20_cpufreq_driver); | |
232 | ||
233 | MODULE_ALIAS("platform:tegra20-cpufreq"); | |
7056d423 | 234 | MODULE_AUTHOR("Colin Cross <ccross@android.com>"); |
49640277 | 235 | MODULE_DESCRIPTION("NVIDIA Tegra20 cpufreq driver"); |
7056d423 | 236 | MODULE_LICENSE("GPL"); |