Commit | Line | Data |
---|---|---|
f328584f YL |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Allwinner CPUFreq nvmem based driver | |
4 | * | |
5 | * The sun50i-cpufreq-nvmem driver reads the efuse value from the SoC to | |
6 | * provide the OPP framework with required information. | |
7 | * | |
8 | * Copyright (C) 2019 Yangtao Li <tiny.windzz@gmail.com> | |
9 | */ | |
10 | ||
11 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
12 | ||
e2e2dcd2 | 13 | #include <linux/arm-smccc.h> |
a88fb960 | 14 | #include <linux/cpu.h> |
f328584f YL |
15 | #include <linux/module.h> |
16 | #include <linux/nvmem-consumer.h> | |
a88fb960 | 17 | #include <linux/of.h> |
f328584f YL |
18 | #include <linux/platform_device.h> |
19 | #include <linux/pm_opp.h> | |
20 | #include <linux/slab.h> | |
21 | ||
f328584f YL |
22 | #define NVMEM_MASK 0x7 |
23 | #define NVMEM_SHIFT 5 | |
24 | ||
25 | static struct platform_device *cpufreq_dt_pdev, *sun50i_cpufreq_pdev; | |
26 | ||
6cc4bcce BCF |
27 | struct sunxi_cpufreq_data { |
28 | u32 (*efuse_xlate)(u32 speedbin); | |
29 | }; | |
30 | ||
31 | static u32 sun50i_h6_efuse_xlate(u32 speedbin) | |
32 | { | |
33 | u32 efuse_value; | |
34 | ||
35 | efuse_value = (speedbin >> NVMEM_SHIFT) & NVMEM_MASK; | |
36 | ||
37 | /* | |
38 | * We treat unexpected efuse values as if the SoC was from | |
39 | * the slowest bin. Expected efuse values are 1-3, slowest | |
40 | * to fastest. | |
41 | */ | |
42 | if (efuse_value >= 1 && efuse_value <= 3) | |
43 | return efuse_value - 1; | |
44 | else | |
45 | return 0; | |
46 | } | |
47 | ||
e2e2dcd2 MB |
48 | static int get_soc_id_revision(void) |
49 | { | |
50 | #ifdef CONFIG_HAVE_ARM_SMCCC_DISCOVERY | |
51 | return arm_smccc_get_soc_id_revision(); | |
52 | #else | |
53 | return SMCCC_RET_NOT_SUPPORTED; | |
54 | #endif | |
55 | } | |
56 | ||
57 | /* | |
58 | * Judging by the OPP tables in the vendor BSP, the quality order of the | |
59 | * returned speedbin index is 4 -> 0/2 -> 3 -> 1, from worst to best. | |
60 | * 0 and 2 seem identical from the OPP tables' point of view. | |
61 | */ | |
62 | static u32 sun50i_h616_efuse_xlate(u32 speedbin) | |
63 | { | |
64 | int ver_bits = get_soc_id_revision(); | |
65 | u32 value = 0; | |
66 | ||
67 | switch (speedbin & 0xffff) { | |
68 | case 0x2000: | |
69 | value = 0; | |
70 | break; | |
71 | case 0x2400: | |
72 | case 0x7400: | |
73 | case 0x2c00: | |
74 | case 0x7c00: | |
75 | if (ver_bits != SMCCC_RET_NOT_SUPPORTED && ver_bits <= 1) { | |
76 | /* ic version A/B */ | |
77 | value = 1; | |
78 | } else { | |
79 | /* ic version C and later version */ | |
80 | value = 2; | |
81 | } | |
82 | break; | |
83 | case 0x5000: | |
84 | case 0x5400: | |
85 | case 0x6000: | |
86 | value = 3; | |
87 | break; | |
88 | case 0x5c00: | |
89 | value = 4; | |
90 | break; | |
91 | case 0x5d00: | |
92 | value = 0; | |
93 | break; | |
ce5b5bef RW |
94 | case 0x6c00: |
95 | value = 5; | |
96 | break; | |
e2e2dcd2 MB |
97 | default: |
98 | pr_warn("sun50i-cpufreq-nvmem: unknown speed bin 0x%x, using default bin 0\n", | |
99 | speedbin & 0xffff); | |
100 | value = 0; | |
101 | break; | |
102 | } | |
103 | ||
104 | return value; | |
105 | } | |
106 | ||
6cc4bcce BCF |
107 | static struct sunxi_cpufreq_data sun50i_h6_cpufreq_data = { |
108 | .efuse_xlate = sun50i_h6_efuse_xlate, | |
109 | }; | |
110 | ||
e2e2dcd2 MB |
111 | static struct sunxi_cpufreq_data sun50i_h616_cpufreq_data = { |
112 | .efuse_xlate = sun50i_h616_efuse_xlate, | |
113 | }; | |
114 | ||
6cc4bcce BCF |
115 | static const struct of_device_id cpu_opp_match_list[] = { |
116 | { .compatible = "allwinner,sun50i-h6-operating-points", | |
117 | .data = &sun50i_h6_cpufreq_data, | |
118 | }, | |
e2e2dcd2 MB |
119 | { .compatible = "allwinner,sun50i-h616-operating-points", |
120 | .data = &sun50i_h616_cpufreq_data, | |
121 | }, | |
6cc4bcce BCF |
122 | {} |
123 | }; | |
124 | ||
fa5aec95 AP |
125 | /** |
126 | * dt_has_supported_hw() - Check if any OPPs use opp-supported-hw | |
127 | * | |
128 | * If we ask the cpufreq framework to use the opp-supported-hw feature, it | |
129 | * will ignore every OPP node without that DT property. If none of the OPPs | |
130 | * have it, the driver will fail probing, due to the lack of OPPs. | |
131 | * | |
132 | * Returns true if we have at least one OPP with the opp-supported-hw property. | |
133 | */ | |
134 | static bool dt_has_supported_hw(void) | |
135 | { | |
136 | bool has_opp_supported_hw = false; | |
fa5aec95 AP |
137 | struct device *cpu_dev; |
138 | ||
139 | cpu_dev = get_cpu_device(0); | |
140 | if (!cpu_dev) | |
76a6fc56 | 141 | return false; |
fa5aec95 | 142 | |
fa8036e5 JC |
143 | struct device_node *np __free(device_node) = |
144 | dev_pm_opp_of_get_opp_desc_node(cpu_dev); | |
fa5aec95 | 145 | if (!np) |
76a6fc56 | 146 | return false; |
fa5aec95 | 147 | |
6282fba6 | 148 | for_each_child_of_node_scoped(np, opp) { |
fa5aec95 AP |
149 | if (of_find_property(opp, "opp-supported-hw", NULL)) { |
150 | has_opp_supported_hw = true; | |
151 | break; | |
152 | } | |
153 | } | |
154 | ||
fa5aec95 AP |
155 | return has_opp_supported_hw; |
156 | } | |
157 | ||
f328584f | 158 | /** |
c2373448 | 159 | * sun50i_cpufreq_get_efuse() - Determine speed grade from efuse value |
f328584f | 160 | * |
6cc4bcce BCF |
161 | * Returns non-negative speed bin index on success, a negative error |
162 | * value otherwise. | |
f328584f | 163 | */ |
6cc4bcce | 164 | static int sun50i_cpufreq_get_efuse(void) |
f328584f | 165 | { |
6cc4bcce | 166 | const struct sunxi_cpufreq_data *opp_data; |
f328584f | 167 | struct nvmem_cell *speedbin_nvmem; |
6cc4bcce | 168 | const struct of_device_id *match; |
f328584f | 169 | struct device *cpu_dev; |
6cc4bcce | 170 | u32 *speedbin; |
f328584f YL |
171 | int ret; |
172 | ||
173 | cpu_dev = get_cpu_device(0); | |
174 | if (!cpu_dev) | |
175 | return -ENODEV; | |
176 | ||
fa8036e5 JC |
177 | struct device_node *np __free(device_node) = |
178 | dev_pm_opp_of_get_opp_desc_node(cpu_dev); | |
f328584f YL |
179 | if (!np) |
180 | return -ENOENT; | |
181 | ||
6cc4bcce | 182 | match = of_match_node(cpu_opp_match_list, np); |
fa8036e5 | 183 | if (!match) |
f328584f | 184 | return -ENOENT; |
fa8036e5 | 185 | |
6cc4bcce | 186 | opp_data = match->data; |
f328584f YL |
187 | |
188 | speedbin_nvmem = of_nvmem_cell_get(np, NULL); | |
889a50ae YY |
189 | if (IS_ERR(speedbin_nvmem)) |
190 | return dev_err_probe(cpu_dev, PTR_ERR(speedbin_nvmem), | |
191 | "Could not get nvmem cell\n"); | |
f328584f | 192 | |
6cc4bcce | 193 | speedbin = nvmem_cell_read(speedbin_nvmem, NULL); |
f328584f YL |
194 | nvmem_cell_put(speedbin_nvmem); |
195 | if (IS_ERR(speedbin)) | |
196 | return PTR_ERR(speedbin); | |
197 | ||
6cc4bcce | 198 | ret = opp_data->efuse_xlate(*speedbin); |
f328584f YL |
199 | |
200 | kfree(speedbin); | |
6cc4bcce BCF |
201 | |
202 | return ret; | |
f328584f YL |
203 | }; |
204 | ||
205 | static int sun50i_cpufreq_nvmem_probe(struct platform_device *pdev) | |
206 | { | |
298098e5 | 207 | int *opp_tokens; |
d2059d3b | 208 | char name[] = "speedXXXXXXXXXXX"; /* Integers can take 11 chars max */ |
fa5aec95 AP |
209 | unsigned int cpu, supported_hw; |
210 | struct dev_pm_opp_config config = {}; | |
6cc4bcce | 211 | int speed; |
f328584f YL |
212 | int ret; |
213 | ||
298098e5 | 214 | opp_tokens = kcalloc(num_possible_cpus(), sizeof(*opp_tokens), |
f328584f | 215 | GFP_KERNEL); |
298098e5 | 216 | if (!opp_tokens) |
f328584f YL |
217 | return -ENOMEM; |
218 | ||
6cc4bcce BCF |
219 | speed = sun50i_cpufreq_get_efuse(); |
220 | if (speed < 0) { | |
298098e5 | 221 | kfree(opp_tokens); |
6cc4bcce | 222 | return speed; |
1aa24a8f | 223 | } |
f328584f | 224 | |
fa5aec95 AP |
225 | /* |
226 | * We need at least one OPP with the "opp-supported-hw" property, | |
227 | * or else the upper layers will ignore every OPP and will bail out. | |
228 | */ | |
229 | if (dt_has_supported_hw()) { | |
230 | supported_hw = 1U << speed; | |
231 | config.supported_hw = &supported_hw; | |
232 | config.supported_hw_count = 1; | |
233 | } | |
234 | ||
d2059d3b | 235 | snprintf(name, sizeof(name), "speed%d", speed); |
fa5aec95 | 236 | config.prop_name = name; |
f328584f YL |
237 | |
238 | for_each_possible_cpu(cpu) { | |
239 | struct device *cpu_dev = get_cpu_device(cpu); | |
240 | ||
241 | if (!cpu_dev) { | |
242 | ret = -ENODEV; | |
243 | goto free_opp; | |
244 | } | |
245 | ||
fa5aec95 AP |
246 | ret = dev_pm_opp_set_config(cpu_dev, &config); |
247 | if (ret < 0) | |
f328584f | 248 | goto free_opp; |
fa5aec95 AP |
249 | |
250 | opp_tokens[cpu] = ret; | |
f328584f YL |
251 | } |
252 | ||
253 | cpufreq_dt_pdev = platform_device_register_simple("cpufreq-dt", -1, | |
254 | NULL, 0); | |
255 | if (!IS_ERR(cpufreq_dt_pdev)) { | |
298098e5 | 256 | platform_set_drvdata(pdev, opp_tokens); |
f328584f YL |
257 | return 0; |
258 | } | |
259 | ||
260 | ret = PTR_ERR(cpufreq_dt_pdev); | |
261 | pr_err("Failed to register platform device\n"); | |
262 | ||
263 | free_opp: | |
298098e5 | 264 | for_each_possible_cpu(cpu) |
fa5aec95 | 265 | dev_pm_opp_clear_config(opp_tokens[cpu]); |
298098e5 | 266 | kfree(opp_tokens); |
f328584f YL |
267 | |
268 | return ret; | |
269 | } | |
270 | ||
a7fb1727 | 271 | static void sun50i_cpufreq_nvmem_remove(struct platform_device *pdev) |
f328584f | 272 | { |
298098e5 | 273 | int *opp_tokens = platform_get_drvdata(pdev); |
f328584f YL |
274 | unsigned int cpu; |
275 | ||
276 | platform_device_unregister(cpufreq_dt_pdev); | |
277 | ||
278 | for_each_possible_cpu(cpu) | |
fa5aec95 | 279 | dev_pm_opp_clear_config(opp_tokens[cpu]); |
f328584f | 280 | |
298098e5 | 281 | kfree(opp_tokens); |
f328584f YL |
282 | } |
283 | ||
284 | static struct platform_driver sun50i_cpufreq_driver = { | |
285 | .probe = sun50i_cpufreq_nvmem_probe, | |
a7fb1727 | 286 | .remove_new = sun50i_cpufreq_nvmem_remove, |
f328584f YL |
287 | .driver = { |
288 | .name = "sun50i-cpufreq-nvmem", | |
289 | }, | |
290 | }; | |
291 | ||
292 | static const struct of_device_id sun50i_cpufreq_match_list[] = { | |
293 | { .compatible = "allwinner,sun50i-h6" }, | |
e2e2dcd2 MB |
294 | { .compatible = "allwinner,sun50i-h616" }, |
295 | { .compatible = "allwinner,sun50i-h618" }, | |
296 | { .compatible = "allwinner,sun50i-h700" }, | |
f328584f YL |
297 | {} |
298 | }; | |
af2096f2 | 299 | MODULE_DEVICE_TABLE(of, sun50i_cpufreq_match_list); |
f328584f YL |
300 | |
301 | static const struct of_device_id *sun50i_cpufreq_match_node(void) | |
302 | { | |
fa8036e5 | 303 | struct device_node *np __free(device_node) = of_find_node_by_path("/"); |
f328584f | 304 | |
fa8036e5 | 305 | return of_match_node(sun50i_cpufreq_match_list, np); |
f328584f YL |
306 | } |
307 | ||
308 | /* | |
309 | * Since the driver depends on nvmem drivers, which may return EPROBE_DEFER, | |
310 | * all the real activity is done in the probe, which may be defered as well. | |
311 | * The init here is only registering the driver and the platform device. | |
312 | */ | |
313 | static int __init sun50i_cpufreq_init(void) | |
314 | { | |
315 | const struct of_device_id *match; | |
316 | int ret; | |
317 | ||
318 | match = sun50i_cpufreq_match_node(); | |
319 | if (!match) | |
320 | return -ENODEV; | |
321 | ||
322 | ret = platform_driver_register(&sun50i_cpufreq_driver); | |
323 | if (unlikely(ret < 0)) | |
324 | return ret; | |
325 | ||
326 | sun50i_cpufreq_pdev = | |
327 | platform_device_register_simple("sun50i-cpufreq-nvmem", | |
328 | -1, NULL, 0); | |
329 | ret = PTR_ERR_OR_ZERO(sun50i_cpufreq_pdev); | |
330 | if (ret == 0) | |
331 | return 0; | |
332 | ||
333 | platform_driver_unregister(&sun50i_cpufreq_driver); | |
334 | return ret; | |
335 | } | |
336 | module_init(sun50i_cpufreq_init); | |
337 | ||
338 | static void __exit sun50i_cpufreq_exit(void) | |
339 | { | |
340 | platform_device_unregister(sun50i_cpufreq_pdev); | |
341 | platform_driver_unregister(&sun50i_cpufreq_driver); | |
342 | } | |
343 | module_exit(sun50i_cpufreq_exit); | |
344 | ||
345 | MODULE_DESCRIPTION("Sun50i-h6 cpufreq driver"); | |
346 | MODULE_LICENSE("GPL v2"); |