]>
Commit | Line | Data |
---|---|---|
1802d0be | 1 | // SPDX-License-Identifier: GPL-2.0-only |
e13cf046 DG |
2 | /* |
3 | * TI CPUFreq/OPP hw-supported driver | |
4 | * | |
5 | * Copyright (C) 2016-2017 Texas Instruments, Inc. | |
6 | * Dave Gerlach <d-gerlach@ti.com> | |
e13cf046 DG |
7 | */ |
8 | ||
9 | #include <linux/cpu.h> | |
10 | #include <linux/io.h> | |
11 | #include <linux/mfd/syscon.h> | |
db410b2b | 12 | #include <linux/module.h> |
149ab864 | 13 | #include <linux/init.h> |
e13cf046 DG |
14 | #include <linux/of.h> |
15 | #include <linux/of_platform.h> | |
16 | #include <linux/pm_opp.h> | |
17 | #include <linux/regmap.h> | |
18 | #include <linux/slab.h> | |
19 | ||
20 | #define REVISION_MASK 0xF | |
21 | #define REVISION_SHIFT 28 | |
22 | ||
23 | #define AM33XX_800M_ARM_MPU_MAX_FREQ 0x1E2F | |
24 | #define AM43XX_600M_ARM_MPU_MAX_FREQ 0xFFA | |
25 | ||
26 | #define DRA7_EFUSE_HAS_OD_MPU_OPP 11 | |
27 | #define DRA7_EFUSE_HAS_HIGH_MPU_OPP 15 | |
0ea4fb29 | 28 | #define DRA76_EFUSE_HAS_PLUS_MPU_OPP 18 |
e13cf046 | 29 | #define DRA7_EFUSE_HAS_ALL_MPU_OPP 23 |
0ea4fb29 | 30 | #define DRA76_EFUSE_HAS_ALL_MPU_OPP 24 |
e13cf046 DG |
31 | |
32 | #define DRA7_EFUSE_NOM_MPU_OPP BIT(0) | |
33 | #define DRA7_EFUSE_OD_MPU_OPP BIT(1) | |
34 | #define DRA7_EFUSE_HIGH_MPU_OPP BIT(2) | |
0ea4fb29 | 35 | #define DRA76_EFUSE_PLUS_MPU_OPP BIT(3) |
e13cf046 | 36 | |
b4bc9f9e NS |
37 | #define OMAP3_CONTROL_DEVICE_STATUS 0x4800244C |
38 | #define OMAP3_CONTROL_IDCODE 0x4830A204 | |
39 | #define OMAP34xx_ProdID_SKUID 0x4830A20C | |
40 | #define OMAP3_SYSCON_BASE (0x48000000 + 0x2000 + 0x270) | |
41 | ||
e13cf046 DG |
42 | #define VERSION_COUNT 2 |
43 | ||
44 | struct ti_cpufreq_data; | |
45 | ||
46 | struct ti_cpufreq_soc_data { | |
42e52616 | 47 | const char * const *reg_names; |
e13cf046 DG |
48 | unsigned long (*efuse_xlate)(struct ti_cpufreq_data *opp_data, |
49 | unsigned long efuse); | |
50 | unsigned long efuse_fallback; | |
51 | unsigned long efuse_offset; | |
52 | unsigned long efuse_mask; | |
53 | unsigned long efuse_shift; | |
54 | unsigned long rev_offset; | |
c8343e83 | 55 | bool multi_regulator; |
e13cf046 DG |
56 | }; |
57 | ||
58 | struct ti_cpufreq_data { | |
59 | struct device *cpu_dev; | |
60 | struct device_node *opp_node; | |
61 | struct regmap *syscon; | |
62 | const struct ti_cpufreq_soc_data *soc_data; | |
c8343e83 | 63 | struct opp_table *opp_table; |
e13cf046 DG |
64 | }; |
65 | ||
66 | static unsigned long amx3_efuse_xlate(struct ti_cpufreq_data *opp_data, | |
67 | unsigned long efuse) | |
68 | { | |
69 | if (!efuse) | |
70 | efuse = opp_data->soc_data->efuse_fallback; | |
71 | /* AM335x and AM437x use "OPP disable" bits, so invert */ | |
72 | return ~efuse; | |
73 | } | |
74 | ||
75 | static unsigned long dra7_efuse_xlate(struct ti_cpufreq_data *opp_data, | |
76 | unsigned long efuse) | |
77 | { | |
78 | unsigned long calculated_efuse = DRA7_EFUSE_NOM_MPU_OPP; | |
79 | ||
80 | /* | |
81 | * The efuse on dra7 and am57 parts contains a specific | |
82 | * value indicating the highest available OPP. | |
83 | */ | |
84 | ||
85 | switch (efuse) { | |
0ea4fb29 LV |
86 | case DRA76_EFUSE_HAS_PLUS_MPU_OPP: |
87 | case DRA76_EFUSE_HAS_ALL_MPU_OPP: | |
88 | calculated_efuse |= DRA76_EFUSE_PLUS_MPU_OPP; | |
df561f66 | 89 | fallthrough; |
e13cf046 DG |
90 | case DRA7_EFUSE_HAS_ALL_MPU_OPP: |
91 | case DRA7_EFUSE_HAS_HIGH_MPU_OPP: | |
92 | calculated_efuse |= DRA7_EFUSE_HIGH_MPU_OPP; | |
df561f66 | 93 | fallthrough; |
e13cf046 DG |
94 | case DRA7_EFUSE_HAS_OD_MPU_OPP: |
95 | calculated_efuse |= DRA7_EFUSE_OD_MPU_OPP; | |
96 | } | |
97 | ||
98 | return calculated_efuse; | |
99 | } | |
100 | ||
b4bc9f9e NS |
101 | static unsigned long omap3_efuse_xlate(struct ti_cpufreq_data *opp_data, |
102 | unsigned long efuse) | |
103 | { | |
104 | /* OPP enable bit ("Speed Binned") */ | |
105 | return BIT(efuse); | |
106 | } | |
107 | ||
e13cf046 DG |
108 | static struct ti_cpufreq_soc_data am3x_soc_data = { |
109 | .efuse_xlate = amx3_efuse_xlate, | |
110 | .efuse_fallback = AM33XX_800M_ARM_MPU_MAX_FREQ, | |
111 | .efuse_offset = 0x07fc, | |
112 | .efuse_mask = 0x1fff, | |
113 | .rev_offset = 0x600, | |
c8343e83 | 114 | .multi_regulator = false, |
e13cf046 DG |
115 | }; |
116 | ||
117 | static struct ti_cpufreq_soc_data am4x_soc_data = { | |
118 | .efuse_xlate = amx3_efuse_xlate, | |
119 | .efuse_fallback = AM43XX_600M_ARM_MPU_MAX_FREQ, | |
120 | .efuse_offset = 0x0610, | |
121 | .efuse_mask = 0x3f, | |
122 | .rev_offset = 0x600, | |
c8343e83 | 123 | .multi_regulator = false, |
e13cf046 DG |
124 | }; |
125 | ||
126 | static struct ti_cpufreq_soc_data dra7_soc_data = { | |
127 | .efuse_xlate = dra7_efuse_xlate, | |
128 | .efuse_offset = 0x020c, | |
129 | .efuse_mask = 0xf80000, | |
130 | .efuse_shift = 19, | |
131 | .rev_offset = 0x204, | |
c8343e83 | 132 | .multi_regulator = true, |
e13cf046 DG |
133 | }; |
134 | ||
b4bc9f9e NS |
135 | /* |
136 | * OMAP35x TRM (SPRUF98K): | |
137 | * CONTROL_IDCODE (0x4830 A204) describes Silicon revisions. | |
138 | * Control OMAP Status Register 15:0 (Address 0x4800 244C) | |
139 | * to separate between omap3503, omap3515, omap3525, omap3530 | |
140 | * and feature presence. | |
141 | * There are encodings for versions limited to 400/266MHz | |
142 | * but we ignore. | |
143 | * Not clear if this also holds for omap34xx. | |
144 | * some eFuse values e.g. CONTROL_FUSE_OPP1_VDD1 | |
145 | * are stored in the SYSCON register range | |
146 | * Register 0x4830A20C [ProdID.SKUID] [0:3] | |
147 | * 0x0 for normal 600/430MHz device. | |
148 | * 0x8 for 720/520MHz device. | |
149 | * Not clear what omap34xx value is. | |
150 | */ | |
151 | ||
152 | static struct ti_cpufreq_soc_data omap34xx_soc_data = { | |
153 | .efuse_xlate = omap3_efuse_xlate, | |
154 | .efuse_offset = OMAP34xx_ProdID_SKUID - OMAP3_SYSCON_BASE, | |
155 | .efuse_shift = 3, | |
156 | .efuse_mask = BIT(3), | |
157 | .rev_offset = OMAP3_CONTROL_IDCODE - OMAP3_SYSCON_BASE, | |
158 | .multi_regulator = false, | |
159 | }; | |
160 | ||
161 | /* | |
162 | * AM/DM37x TRM (SPRUGN4M) | |
163 | * CONTROL_IDCODE (0x4830 A204) describes Silicon revisions. | |
164 | * Control Device Status Register 15:0 (Address 0x4800 244C) | |
165 | * to separate between am3703, am3715, dm3725, dm3730 | |
166 | * and feature presence. | |
167 | * Speed Binned = Bit 9 | |
168 | * 0 800/600 MHz | |
169 | * 1 1000/800 MHz | |
170 | * some eFuse values e.g. CONTROL_FUSE_OPP 1G_VDD1 | |
171 | * are stored in the SYSCON register range. | |
172 | * There is no 0x4830A20C [ProdID.SKUID] register (exists but | |
173 | * seems to always read as 0). | |
174 | */ | |
175 | ||
42e52616 NS |
176 | static const char * const omap3_reg_names[] = {"cpu0", "vbb"}; |
177 | ||
b4bc9f9e | 178 | static struct ti_cpufreq_soc_data omap36xx_soc_data = { |
42e52616 | 179 | .reg_names = omap3_reg_names, |
b4bc9f9e NS |
180 | .efuse_xlate = omap3_efuse_xlate, |
181 | .efuse_offset = OMAP3_CONTROL_DEVICE_STATUS - OMAP3_SYSCON_BASE, | |
182 | .efuse_shift = 9, | |
183 | .efuse_mask = BIT(9), | |
184 | .rev_offset = OMAP3_CONTROL_IDCODE - OMAP3_SYSCON_BASE, | |
341afbc9 | 185 | .multi_regulator = true, |
b4bc9f9e NS |
186 | }; |
187 | ||
3fbeef39 AF |
188 | /* |
189 | * AM3517 is quite similar to AM/DM37x except that it has no | |
190 | * high speed grade eFuse and no abb ldo | |
191 | */ | |
192 | ||
193 | static struct ti_cpufreq_soc_data am3517_soc_data = { | |
194 | .efuse_xlate = omap3_efuse_xlate, | |
195 | .efuse_offset = OMAP3_CONTROL_DEVICE_STATUS - OMAP3_SYSCON_BASE, | |
196 | .efuse_shift = 0, | |
197 | .efuse_mask = 0, | |
198 | .rev_offset = OMAP3_CONTROL_IDCODE - OMAP3_SYSCON_BASE, | |
199 | .multi_regulator = false, | |
200 | }; | |
201 | ||
202 | ||
e13cf046 DG |
203 | /** |
204 | * ti_cpufreq_get_efuse() - Parse and return efuse value present on SoC | |
205 | * @opp_data: pointer to ti_cpufreq_data context | |
206 | * @efuse_value: Set to the value parsed from efuse | |
207 | * | |
208 | * Returns error code if efuse not read properly. | |
209 | */ | |
210 | static int ti_cpufreq_get_efuse(struct ti_cpufreq_data *opp_data, | |
211 | u32 *efuse_value) | |
212 | { | |
213 | struct device *dev = opp_data->cpu_dev; | |
214 | u32 efuse; | |
215 | int ret; | |
216 | ||
217 | ret = regmap_read(opp_data->syscon, opp_data->soc_data->efuse_offset, | |
218 | &efuse); | |
b4bc9f9e NS |
219 | if (ret == -EIO) { |
220 | /* not a syscon register! */ | |
221 | void __iomem *regs = ioremap(OMAP3_SYSCON_BASE + | |
222 | opp_data->soc_data->efuse_offset, 4); | |
223 | ||
224 | if (!regs) | |
225 | return -ENOMEM; | |
226 | efuse = readl(regs); | |
227 | iounmap(regs); | |
228 | } | |
229 | else if (ret) { | |
e13cf046 DG |
230 | dev_err(dev, |
231 | "Failed to read the efuse value from syscon: %d\n", | |
232 | ret); | |
233 | return ret; | |
234 | } | |
235 | ||
236 | efuse = (efuse & opp_data->soc_data->efuse_mask); | |
237 | efuse >>= opp_data->soc_data->efuse_shift; | |
238 | ||
239 | *efuse_value = opp_data->soc_data->efuse_xlate(opp_data, efuse); | |
240 | ||
241 | return 0; | |
242 | } | |
243 | ||
244 | /** | |
245 | * ti_cpufreq_get_rev() - Parse and return rev value present on SoC | |
246 | * @opp_data: pointer to ti_cpufreq_data context | |
247 | * @revision_value: Set to the value parsed from revision register | |
248 | * | |
249 | * Returns error code if revision not read properly. | |
250 | */ | |
251 | static int ti_cpufreq_get_rev(struct ti_cpufreq_data *opp_data, | |
252 | u32 *revision_value) | |
253 | { | |
254 | struct device *dev = opp_data->cpu_dev; | |
255 | u32 revision; | |
256 | int ret; | |
257 | ||
258 | ret = regmap_read(opp_data->syscon, opp_data->soc_data->rev_offset, | |
259 | &revision); | |
b4bc9f9e NS |
260 | if (ret == -EIO) { |
261 | /* not a syscon register! */ | |
262 | void __iomem *regs = ioremap(OMAP3_SYSCON_BASE + | |
263 | opp_data->soc_data->rev_offset, 4); | |
264 | ||
265 | if (!regs) | |
266 | return -ENOMEM; | |
267 | revision = readl(regs); | |
268 | iounmap(regs); | |
269 | } | |
270 | else if (ret) { | |
e13cf046 DG |
271 | dev_err(dev, |
272 | "Failed to read the revision number from syscon: %d\n", | |
273 | ret); | |
274 | return ret; | |
275 | } | |
276 | ||
277 | *revision_value = BIT((revision >> REVISION_SHIFT) & REVISION_MASK); | |
278 | ||
279 | return 0; | |
280 | } | |
281 | ||
282 | static int ti_cpufreq_setup_syscon_register(struct ti_cpufreq_data *opp_data) | |
283 | { | |
284 | struct device *dev = opp_data->cpu_dev; | |
285 | struct device_node *np = opp_data->opp_node; | |
286 | ||
287 | opp_data->syscon = syscon_regmap_lookup_by_phandle(np, | |
288 | "syscon"); | |
289 | if (IS_ERR(opp_data->syscon)) { | |
290 | dev_err(dev, | |
291 | "\"syscon\" is missing, cannot use OPPv2 table.\n"); | |
292 | return PTR_ERR(opp_data->syscon); | |
293 | } | |
294 | ||
295 | return 0; | |
296 | } | |
297 | ||
298 | static const struct of_device_id ti_cpufreq_of_match[] = { | |
299 | { .compatible = "ti,am33xx", .data = &am3x_soc_data, }, | |
3fbeef39 | 300 | { .compatible = "ti,am3517", .data = &am3517_soc_data, }, |
039cc1c1 | 301 | { .compatible = "ti,am43", .data = &am4x_soc_data, }, |
e13cf046 | 302 | { .compatible = "ti,dra7", .data = &dra7_soc_data }, |
b4bc9f9e NS |
303 | { .compatible = "ti,omap34xx", .data = &omap34xx_soc_data, }, |
304 | { .compatible = "ti,omap36xx", .data = &omap36xx_soc_data, }, | |
305 | /* legacy */ | |
306 | { .compatible = "ti,omap3430", .data = &omap34xx_soc_data, }, | |
307 | { .compatible = "ti,omap3630", .data = &omap36xx_soc_data, }, | |
e13cf046 DG |
308 | {}, |
309 | }; | |
310 | ||
d98ccfc3 DG |
311 | static const struct of_device_id *ti_cpufreq_match_node(void) |
312 | { | |
313 | struct device_node *np; | |
314 | const struct of_device_id *match; | |
315 | ||
316 | np = of_find_node_by_path("/"); | |
317 | match = of_match_node(ti_cpufreq_of_match, np); | |
318 | of_node_put(np); | |
319 | ||
320 | return match; | |
321 | } | |
322 | ||
db410b2b | 323 | static int ti_cpufreq_probe(struct platform_device *pdev) |
e13cf046 DG |
324 | { |
325 | u32 version[VERSION_COUNT]; | |
e13cf046 | 326 | const struct of_device_id *match; |
c8343e83 | 327 | struct opp_table *ti_opp_table; |
e13cf046 | 328 | struct ti_cpufreq_data *opp_data; |
42e52616 | 329 | const char * const default_reg_names[] = {"vdd", "vbb"}; |
e13cf046 DG |
330 | int ret; |
331 | ||
d98ccfc3 | 332 | match = dev_get_platdata(&pdev->dev); |
e13cf046 DG |
333 | if (!match) |
334 | return -ENODEV; | |
335 | ||
d7231f99 | 336 | opp_data = devm_kzalloc(&pdev->dev, sizeof(*opp_data), GFP_KERNEL); |
e13cf046 DG |
337 | if (!opp_data) |
338 | return -ENOMEM; | |
339 | ||
340 | opp_data->soc_data = match->data; | |
341 | ||
342 | opp_data->cpu_dev = get_cpu_device(0); | |
343 | if (!opp_data->cpu_dev) { | |
344 | pr_err("%s: Failed to get device for CPU0\n", __func__); | |
d7231f99 | 345 | return -ENODEV; |
e13cf046 DG |
346 | } |
347 | ||
348 | opp_data->opp_node = dev_pm_opp_of_get_opp_desc_node(opp_data->cpu_dev); | |
349 | if (!opp_data->opp_node) { | |
350 | dev_info(opp_data->cpu_dev, | |
351 | "OPP-v2 not supported, cpufreq-dt will attempt to use legacy tables.\n"); | |
352 | goto register_cpufreq_dt; | |
353 | } | |
354 | ||
355 | ret = ti_cpufreq_setup_syscon_register(opp_data); | |
356 | if (ret) | |
357 | goto fail_put_node; | |
358 | ||
359 | /* | |
360 | * OPPs determine whether or not they are supported based on | |
361 | * two metrics: | |
362 | * 0 - SoC Revision | |
363 | * 1 - eFuse value | |
364 | */ | |
365 | ret = ti_cpufreq_get_rev(opp_data, &version[0]); | |
366 | if (ret) | |
367 | goto fail_put_node; | |
368 | ||
369 | ret = ti_cpufreq_get_efuse(opp_data, &version[1]); | |
370 | if (ret) | |
371 | goto fail_put_node; | |
372 | ||
c8343e83 DG |
373 | ti_opp_table = dev_pm_opp_set_supported_hw(opp_data->cpu_dev, |
374 | version, VERSION_COUNT); | |
375 | if (IS_ERR(ti_opp_table)) { | |
e13cf046 DG |
376 | dev_err(opp_data->cpu_dev, |
377 | "Failed to set supported hardware\n"); | |
c8343e83 | 378 | ret = PTR_ERR(ti_opp_table); |
e13cf046 DG |
379 | goto fail_put_node; |
380 | } | |
381 | ||
c8343e83 DG |
382 | opp_data->opp_table = ti_opp_table; |
383 | ||
384 | if (opp_data->soc_data->multi_regulator) { | |
42e52616 NS |
385 | const char * const *reg_names = default_reg_names; |
386 | ||
387 | if (opp_data->soc_data->reg_names) | |
388 | reg_names = opp_data->soc_data->reg_names; | |
c8343e83 DG |
389 | ti_opp_table = dev_pm_opp_set_regulators(opp_data->cpu_dev, |
390 | reg_names, | |
42e52616 | 391 | ARRAY_SIZE(default_reg_names)); |
c8343e83 DG |
392 | if (IS_ERR(ti_opp_table)) { |
393 | dev_pm_opp_put_supported_hw(opp_data->opp_table); | |
394 | ret = PTR_ERR(ti_opp_table); | |
395 | goto fail_put_node; | |
396 | } | |
397 | } | |
9a6e91d0 | 398 | |
c8343e83 | 399 | of_node_put(opp_data->opp_node); |
e13cf046 DG |
400 | register_cpufreq_dt: |
401 | platform_device_register_simple("cpufreq-dt", -1, NULL, 0); | |
402 | ||
403 | return 0; | |
404 | ||
405 | fail_put_node: | |
406 | of_node_put(opp_data->opp_node); | |
407 | ||
408 | return ret; | |
409 | } | |
db410b2b DG |
410 | |
411 | static int ti_cpufreq_init(void) | |
412 | { | |
d98ccfc3 DG |
413 | const struct of_device_id *match; |
414 | ||
415 | /* Check to ensure we are on a compatible platform */ | |
416 | match = ti_cpufreq_match_node(); | |
417 | if (match) | |
418 | platform_device_register_data(NULL, "ti-cpufreq", -1, match, | |
419 | sizeof(*match)); | |
420 | ||
db410b2b DG |
421 | return 0; |
422 | } | |
423 | module_init(ti_cpufreq_init); | |
424 | ||
425 | static struct platform_driver ti_cpufreq_driver = { | |
426 | .probe = ti_cpufreq_probe, | |
427 | .driver = { | |
428 | .name = "ti-cpufreq", | |
429 | }, | |
430 | }; | |
44a264ee | 431 | builtin_platform_driver(ti_cpufreq_driver); |
db410b2b DG |
432 | |
433 | MODULE_DESCRIPTION("TI CPUFreq/OPP hw-supported driver"); | |
434 | MODULE_AUTHOR("Dave Gerlach <d-gerlach@ti.com>"); | |
435 | MODULE_LICENSE("GPL v2"); |