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