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