Commit | Line | Data |
---|---|---|
46e2856b IL |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright (c) 2018, The Linux Foundation. All rights reserved. | |
4 | */ | |
5 | ||
6 | /* | |
7 | * In Certain QCOM SoCs like apq8096 and msm8996 that have KRYO processors, | |
8 | * the CPU frequency subset and voltage value of each OPP varies | |
9 | * based on the silicon variant in use. Qualcomm Process Voltage Scaling Tables | |
10 | * defines the voltage and frequency value based on the msm-id in SMEM | |
11 | * and speedbin blown in the efuse combination. | |
7d127095 | 12 | * The qcom-cpufreq-nvmem driver reads the msm-id and efuse value from the SoC |
46e2856b IL |
13 | * to provide the OPP framework with required information. |
14 | * This is used to determine the voltage and frequency value for each OPP of | |
15 | * operating-points-v2 table when it is parsed by the OPP framework. | |
16 | */ | |
17 | ||
18 | #include <linux/cpu.h> | |
19 | #include <linux/err.h> | |
20 | #include <linux/init.h> | |
21 | #include <linux/kernel.h> | |
22 | #include <linux/module.h> | |
23 | #include <linux/nvmem-consumer.h> | |
24 | #include <linux/of.h> | |
25 | #include <linux/platform_device.h> | |
d6048a19 | 26 | #include <linux/pm.h> |
1cb8339c | 27 | #include <linux/pm_domain.h> |
46e2856b | 28 | #include <linux/pm_opp.h> |
5cbff51e | 29 | #include <linux/pm_runtime.h> |
46e2856b IL |
30 | #include <linux/slab.h> |
31 | #include <linux/soc/qcom/smem.h> | |
32 | ||
865d7e71 | 33 | #include <dt-bindings/arm/qcom,ids.h> |
46e2856b | 34 | |
4a3754f7 CM |
35 | enum ipq806x_versions { |
36 | IPQ8062_VERSION = 0, | |
37 | IPQ8064_VERSION, | |
38 | IPQ8065_VERSION, | |
39 | }; | |
40 | ||
47e161a7 RM |
41 | #define IPQ6000_VERSION BIT(2) |
42 | ||
0b9cd949 RM |
43 | enum ipq8074_versions { |
44 | IPQ8074_HAWKEYE_VERSION = 0, | |
45 | IPQ8074_ACORN_VERSION, | |
46 | }; | |
47 | ||
57f2f8b4 NC |
48 | struct qcom_cpufreq_drv; |
49 | ||
50 | struct qcom_cpufreq_match_data { | |
51 | int (*get_version)(struct device *cpu_dev, | |
52 | struct nvmem_cell *speedbin_nvmem, | |
a8811ec7 | 53 | char **pvs_name, |
57f2f8b4 | 54 | struct qcom_cpufreq_drv *drv); |
1cb8339c | 55 | const char **genpd_names; |
57f2f8b4 NC |
56 | }; |
57 | ||
2a5d46c3 SG |
58 | struct qcom_cpufreq_drv_cpu { |
59 | int opp_token; | |
5cbff51e | 60 | struct device **virt_devs; |
2a5d46c3 SG |
61 | }; |
62 | ||
57f2f8b4 | 63 | struct qcom_cpufreq_drv { |
57f2f8b4 NC |
64 | u32 versions; |
65 | const struct qcom_cpufreq_match_data *data; | |
2a5d46c3 | 66 | struct qcom_cpufreq_drv_cpu cpus[]; |
57f2f8b4 NC |
67 | }; |
68 | ||
7d127095 | 69 | static struct platform_device *cpufreq_dt_pdev, *cpufreq_pdev; |
5ad7346b | 70 | |
f0d64f4a SG |
71 | static int qcom_cpufreq_simple_get_version(struct device *cpu_dev, |
72 | struct nvmem_cell *speedbin_nvmem, | |
73 | char **pvs_name, | |
74 | struct qcom_cpufreq_drv *drv) | |
75 | { | |
76 | u8 *speedbin; | |
77 | ||
78 | *pvs_name = NULL; | |
79 | speedbin = nvmem_cell_read(speedbin_nvmem, NULL); | |
80 | if (IS_ERR(speedbin)) | |
81 | return PTR_ERR(speedbin); | |
82 | ||
83 | dev_dbg(cpu_dev, "speedbin: %d\n", *speedbin); | |
84 | drv->versions = 1 << *speedbin; | |
85 | kfree(speedbin); | |
86 | return 0; | |
87 | } | |
88 | ||
a8811ec7 | 89 | static void get_krait_bin_format_a(struct device *cpu_dev, |
a243a1ed | 90 | int *speed, int *pvs, |
a05887f0 | 91 | u8 *buf) |
a8811ec7 AS |
92 | { |
93 | u32 pte_efuse; | |
94 | ||
95 | pte_efuse = *((u32 *)buf); | |
96 | ||
97 | *speed = pte_efuse & 0xf; | |
98 | if (*speed == 0xf) | |
99 | *speed = (pte_efuse >> 4) & 0xf; | |
100 | ||
101 | if (*speed == 0xf) { | |
102 | *speed = 0; | |
103 | dev_warn(cpu_dev, "Speed bin: Defaulting to %d\n", *speed); | |
104 | } else { | |
105 | dev_dbg(cpu_dev, "Speed bin: %d\n", *speed); | |
106 | } | |
107 | ||
108 | *pvs = (pte_efuse >> 10) & 0x7; | |
109 | if (*pvs == 0x7) | |
110 | *pvs = (pte_efuse >> 13) & 0x7; | |
111 | ||
112 | if (*pvs == 0x7) { | |
113 | *pvs = 0; | |
114 | dev_warn(cpu_dev, "PVS bin: Defaulting to %d\n", *pvs); | |
115 | } else { | |
116 | dev_dbg(cpu_dev, "PVS bin: %d\n", *pvs); | |
117 | } | |
118 | } | |
119 | ||
120 | static void get_krait_bin_format_b(struct device *cpu_dev, | |
121 | int *speed, int *pvs, int *pvs_ver, | |
a05887f0 | 122 | u8 *buf) |
a8811ec7 AS |
123 | { |
124 | u32 pte_efuse, redundant_sel; | |
125 | ||
126 | pte_efuse = *((u32 *)buf); | |
127 | redundant_sel = (pte_efuse >> 24) & 0x7; | |
128 | ||
129 | *pvs_ver = (pte_efuse >> 4) & 0x3; | |
130 | ||
131 | switch (redundant_sel) { | |
132 | case 1: | |
133 | *pvs = ((pte_efuse >> 28) & 0x8) | ((pte_efuse >> 6) & 0x7); | |
134 | *speed = (pte_efuse >> 27) & 0xf; | |
135 | break; | |
136 | case 2: | |
137 | *pvs = (pte_efuse >> 27) & 0xf; | |
138 | *speed = pte_efuse & 0x7; | |
139 | break; | |
140 | default: | |
141 | /* 4 bits of PVS are in efuse register bits 31, 8-6. */ | |
142 | *pvs = ((pte_efuse >> 28) & 0x8) | ((pte_efuse >> 6) & 0x7); | |
143 | *speed = pte_efuse & 0x7; | |
144 | } | |
145 | ||
146 | /* Check SPEED_BIN_BLOW_STATUS */ | |
147 | if (pte_efuse & BIT(3)) { | |
148 | dev_dbg(cpu_dev, "Speed bin: %d\n", *speed); | |
149 | } else { | |
150 | dev_warn(cpu_dev, "Speed bin not set. Defaulting to 0!\n"); | |
151 | *speed = 0; | |
152 | } | |
153 | ||
154 | /* Check PVS_BLOW_STATUS */ | |
4a8a77ab | 155 | pte_efuse = *(((u32 *)buf) + 1); |
a8811ec7 AS |
156 | pte_efuse &= BIT(21); |
157 | if (pte_efuse) { | |
158 | dev_dbg(cpu_dev, "PVS bin: %d\n", *pvs); | |
159 | } else { | |
160 | dev_warn(cpu_dev, "PVS bin not set. Defaulting to 0!\n"); | |
161 | *pvs = 0; | |
162 | } | |
163 | ||
164 | dev_dbg(cpu_dev, "PVS version: %d\n", *pvs_ver); | |
165 | } | |
166 | ||
7d127095 S |
167 | static int qcom_cpufreq_kryo_name_version(struct device *cpu_dev, |
168 | struct nvmem_cell *speedbin_nvmem, | |
a8811ec7 | 169 | char **pvs_name, |
57f2f8b4 | 170 | struct qcom_cpufreq_drv *drv) |
46e2856b | 171 | { |
7d127095 | 172 | size_t len; |
7d0f03d1 | 173 | u32 msm_id; |
7d127095 | 174 | u8 *speedbin; |
7d0f03d1 | 175 | int ret; |
a8811ec7 | 176 | *pvs_name = NULL; |
7d127095 | 177 | |
7d0f03d1 RM |
178 | ret = qcom_smem_get_soc_id(&msm_id); |
179 | if (ret) | |
180 | return ret; | |
7d127095 S |
181 | |
182 | speedbin = nvmem_cell_read(speedbin_nvmem, &len); | |
183 | if (IS_ERR(speedbin)) | |
184 | return PTR_ERR(speedbin); | |
185 | ||
7d0f03d1 RM |
186 | switch (msm_id) { |
187 | case QCOM_ID_MSM8996: | |
188 | case QCOM_ID_APQ8096: | |
ba5a61a0 VN |
189 | case QCOM_ID_IPQ5332: |
190 | case QCOM_ID_IPQ5322: | |
191 | case QCOM_ID_IPQ5312: | |
192 | case QCOM_ID_IPQ5302: | |
193 | case QCOM_ID_IPQ5300: | |
5b5b5806 VN |
194 | case QCOM_ID_IPQ9514: |
195 | case QCOM_ID_IPQ9550: | |
196 | case QCOM_ID_IPQ9554: | |
197 | case QCOM_ID_IPQ9570: | |
198 | case QCOM_ID_IPQ9574: | |
57f2f8b4 | 199 | drv->versions = 1 << (unsigned int)(*speedbin); |
7d127095 | 200 | break; |
7d0f03d1 RM |
201 | case QCOM_ID_MSM8996SG: |
202 | case QCOM_ID_APQ8096SG: | |
57f2f8b4 | 203 | drv->versions = 1 << ((unsigned int)(*speedbin) + 4); |
7d127095 S |
204 | break; |
205 | default: | |
206 | BUG(); | |
207 | break; | |
208 | } | |
209 | ||
210 | kfree(speedbin); | |
211 | return 0; | |
212 | } | |
213 | ||
a8811ec7 AS |
214 | static int qcom_cpufreq_krait_name_version(struct device *cpu_dev, |
215 | struct nvmem_cell *speedbin_nvmem, | |
216 | char **pvs_name, | |
217 | struct qcom_cpufreq_drv *drv) | |
218 | { | |
219 | int speed = 0, pvs = 0, pvs_ver = 0; | |
220 | u8 *speedbin; | |
221 | size_t len; | |
9f42cf54 | 222 | int ret = 0; |
a8811ec7 AS |
223 | |
224 | speedbin = nvmem_cell_read(speedbin_nvmem, &len); | |
225 | ||
226 | if (IS_ERR(speedbin)) | |
227 | return PTR_ERR(speedbin); | |
228 | ||
229 | switch (len) { | |
230 | case 4: | |
a243a1ed | 231 | get_krait_bin_format_a(cpu_dev, &speed, &pvs, speedbin); |
a8811ec7 AS |
232 | break; |
233 | case 8: | |
234 | get_krait_bin_format_b(cpu_dev, &speed, &pvs, &pvs_ver, | |
a05887f0 | 235 | speedbin); |
a8811ec7 AS |
236 | break; |
237 | default: | |
238 | dev_err(cpu_dev, "Unable to read nvmem data. Defaulting to 0!\n"); | |
9f42cf54 FP |
239 | ret = -ENODEV; |
240 | goto len_error; | |
a8811ec7 AS |
241 | } |
242 | ||
243 | snprintf(*pvs_name, sizeof("speedXX-pvsXX-vXX"), "speed%d-pvs%d-v%d", | |
244 | speed, pvs, pvs_ver); | |
245 | ||
246 | drv->versions = (1 << speed); | |
247 | ||
9f42cf54 | 248 | len_error: |
a8811ec7 | 249 | kfree(speedbin); |
9f42cf54 | 250 | return ret; |
a8811ec7 AS |
251 | } |
252 | ||
4a3754f7 CM |
253 | static int qcom_cpufreq_ipq8064_name_version(struct device *cpu_dev, |
254 | struct nvmem_cell *speedbin_nvmem, | |
255 | char **pvs_name, | |
256 | struct qcom_cpufreq_drv *drv) | |
257 | { | |
258 | int speed = 0, pvs = 0; | |
259 | int msm_id, ret = 0; | |
260 | u8 *speedbin; | |
261 | size_t len; | |
262 | ||
263 | speedbin = nvmem_cell_read(speedbin_nvmem, &len); | |
264 | if (IS_ERR(speedbin)) | |
265 | return PTR_ERR(speedbin); | |
266 | ||
267 | if (len != 4) { | |
268 | dev_err(cpu_dev, "Unable to read nvmem data. Defaulting to 0!\n"); | |
269 | ret = -ENODEV; | |
270 | goto exit; | |
271 | } | |
272 | ||
273 | get_krait_bin_format_a(cpu_dev, &speed, &pvs, speedbin); | |
274 | ||
275 | ret = qcom_smem_get_soc_id(&msm_id); | |
276 | if (ret) | |
277 | goto exit; | |
278 | ||
279 | switch (msm_id) { | |
280 | case QCOM_ID_IPQ8062: | |
281 | drv->versions = BIT(IPQ8062_VERSION); | |
282 | break; | |
283 | case QCOM_ID_IPQ8064: | |
284 | case QCOM_ID_IPQ8066: | |
285 | case QCOM_ID_IPQ8068: | |
286 | drv->versions = BIT(IPQ8064_VERSION); | |
287 | break; | |
288 | case QCOM_ID_IPQ8065: | |
289 | case QCOM_ID_IPQ8069: | |
290 | drv->versions = BIT(IPQ8065_VERSION); | |
291 | break; | |
292 | default: | |
293 | dev_err(cpu_dev, | |
294 | "SoC ID %u is not part of IPQ8064 family, limiting to 1.0GHz!\n", | |
295 | msm_id); | |
296 | drv->versions = BIT(IPQ8062_VERSION); | |
297 | break; | |
298 | } | |
299 | ||
300 | /* IPQ8064 speed is never fused. Only pvs values are fused. */ | |
301 | snprintf(*pvs_name, sizeof("speed0-pvsXX"), "speed0-pvs%d", pvs); | |
302 | ||
303 | exit: | |
304 | kfree(speedbin); | |
305 | return ret; | |
306 | } | |
307 | ||
47e161a7 RM |
308 | static int qcom_cpufreq_ipq6018_name_version(struct device *cpu_dev, |
309 | struct nvmem_cell *speedbin_nvmem, | |
310 | char **pvs_name, | |
311 | struct qcom_cpufreq_drv *drv) | |
312 | { | |
313 | u32 msm_id; | |
314 | int ret; | |
315 | u8 *speedbin; | |
316 | *pvs_name = NULL; | |
317 | ||
318 | ret = qcom_smem_get_soc_id(&msm_id); | |
319 | if (ret) | |
320 | return ret; | |
321 | ||
322 | speedbin = nvmem_cell_read(speedbin_nvmem, NULL); | |
323 | if (IS_ERR(speedbin)) | |
324 | return PTR_ERR(speedbin); | |
325 | ||
326 | switch (msm_id) { | |
327 | case QCOM_ID_IPQ6005: | |
328 | case QCOM_ID_IPQ6010: | |
329 | case QCOM_ID_IPQ6018: | |
330 | case QCOM_ID_IPQ6028: | |
331 | /* Fuse Value Freq BIT to set | |
332 | * --------------------------------- | |
333 | * 2’b0 No Limit BIT(0) | |
334 | * 2’b1 1.5 GHz BIT(1) | |
335 | */ | |
336 | drv->versions = 1 << (unsigned int)(*speedbin); | |
337 | break; | |
338 | case QCOM_ID_IPQ6000: | |
339 | /* | |
340 | * IPQ6018 family only has one bit to advertise the CPU | |
341 | * speed-bin, but that is not enough for IPQ6000 which | |
342 | * is only rated up to 1.2GHz. | |
343 | * So for IPQ6000 manually set BIT(2) based on SMEM ID. | |
344 | */ | |
345 | drv->versions = IPQ6000_VERSION; | |
346 | break; | |
347 | default: | |
348 | dev_err(cpu_dev, | |
349 | "SoC ID %u is not part of IPQ6018 family, limiting to 1.2GHz!\n", | |
350 | msm_id); | |
351 | drv->versions = IPQ6000_VERSION; | |
352 | break; | |
353 | } | |
354 | ||
355 | kfree(speedbin); | |
356 | return 0; | |
357 | } | |
358 | ||
0b9cd949 RM |
359 | static int qcom_cpufreq_ipq8074_name_version(struct device *cpu_dev, |
360 | struct nvmem_cell *speedbin_nvmem, | |
361 | char **pvs_name, | |
362 | struct qcom_cpufreq_drv *drv) | |
363 | { | |
364 | u32 msm_id; | |
365 | int ret; | |
366 | *pvs_name = NULL; | |
367 | ||
368 | ret = qcom_smem_get_soc_id(&msm_id); | |
369 | if (ret) | |
370 | return ret; | |
371 | ||
372 | switch (msm_id) { | |
373 | case QCOM_ID_IPQ8070A: | |
374 | case QCOM_ID_IPQ8071A: | |
375 | case QCOM_ID_IPQ8172: | |
376 | case QCOM_ID_IPQ8173: | |
377 | case QCOM_ID_IPQ8174: | |
378 | drv->versions = BIT(IPQ8074_ACORN_VERSION); | |
379 | break; | |
380 | case QCOM_ID_IPQ8072A: | |
381 | case QCOM_ID_IPQ8074A: | |
382 | case QCOM_ID_IPQ8076A: | |
383 | case QCOM_ID_IPQ8078A: | |
384 | drv->versions = BIT(IPQ8074_HAWKEYE_VERSION); | |
385 | break; | |
386 | default: | |
387 | dev_err(cpu_dev, | |
388 | "SoC ID %u is not part of IPQ8074 family, limiting to 1.4GHz!\n", | |
389 | msm_id); | |
390 | drv->versions = BIT(IPQ8074_ACORN_VERSION); | |
391 | break; | |
392 | } | |
393 | ||
394 | return 0; | |
395 | } | |
396 | ||
f0d64f4a SG |
397 | static const char *generic_genpd_names[] = { "perf", NULL }; |
398 | ||
57f2f8b4 NC |
399 | static const struct qcom_cpufreq_match_data match_data_kryo = { |
400 | .get_version = qcom_cpufreq_kryo_name_version, | |
401 | }; | |
402 | ||
a8811ec7 AS |
403 | static const struct qcom_cpufreq_match_data match_data_krait = { |
404 | .get_version = qcom_cpufreq_krait_name_version, | |
405 | }; | |
406 | ||
f0d64f4a SG |
407 | static const struct qcom_cpufreq_match_data match_data_msm8909 = { |
408 | .get_version = qcom_cpufreq_simple_get_version, | |
409 | .genpd_names = generic_genpd_names, | |
410 | }; | |
411 | ||
1cb8339c NC |
412 | static const char *qcs404_genpd_names[] = { "cpr", NULL }; |
413 | ||
414 | static const struct qcom_cpufreq_match_data match_data_qcs404 = { | |
415 | .genpd_names = qcs404_genpd_names, | |
416 | }; | |
417 | ||
47e161a7 RM |
418 | static const struct qcom_cpufreq_match_data match_data_ipq6018 = { |
419 | .get_version = qcom_cpufreq_ipq6018_name_version, | |
420 | }; | |
421 | ||
4a3754f7 CM |
422 | static const struct qcom_cpufreq_match_data match_data_ipq8064 = { |
423 | .get_version = qcom_cpufreq_ipq8064_name_version, | |
424 | }; | |
425 | ||
0b9cd949 RM |
426 | static const struct qcom_cpufreq_match_data match_data_ipq8074 = { |
427 | .get_version = qcom_cpufreq_ipq8074_name_version, | |
428 | }; | |
429 | ||
d6048a19 SG |
430 | static void qcom_cpufreq_suspend_virt_devs(struct qcom_cpufreq_drv *drv, unsigned int cpu) |
431 | { | |
432 | const char * const *name = drv->data->genpd_names; | |
433 | int i; | |
434 | ||
435 | if (!drv->cpus[cpu].virt_devs) | |
436 | return; | |
437 | ||
438 | for (i = 0; *name; i++, name++) | |
439 | device_set_awake_path(drv->cpus[cpu].virt_devs[i]); | |
440 | } | |
441 | ||
5cbff51e SG |
442 | static void qcom_cpufreq_put_virt_devs(struct qcom_cpufreq_drv *drv, unsigned int cpu) |
443 | { | |
444 | const char * const *name = drv->data->genpd_names; | |
445 | int i; | |
446 | ||
447 | if (!drv->cpus[cpu].virt_devs) | |
448 | return; | |
449 | ||
450 | for (i = 0; *name; i++, name++) | |
451 | pm_runtime_put(drv->cpus[cpu].virt_devs[i]); | |
452 | } | |
453 | ||
7d127095 S |
454 | static int qcom_cpufreq_probe(struct platform_device *pdev) |
455 | { | |
57f2f8b4 | 456 | struct qcom_cpufreq_drv *drv; |
46e2856b IL |
457 | struct nvmem_cell *speedbin_nvmem; |
458 | struct device_node *np; | |
459 | struct device *cpu_dev; | |
01039fb8 FP |
460 | char pvs_name_buffer[] = "speedXX-pvsXX-vXX"; |
461 | char *pvs_name = pvs_name_buffer; | |
46e2856b | 462 | unsigned cpu; |
7d127095 | 463 | const struct of_device_id *match; |
46e2856b IL |
464 | int ret; |
465 | ||
466 | cpu_dev = get_cpu_device(0); | |
1dd2058f DC |
467 | if (!cpu_dev) |
468 | return -ENODEV; | |
46e2856b | 469 | |
46e2856b | 470 | np = dev_pm_opp_of_get_opp_desc_node(cpu_dev); |
1dd2058f DC |
471 | if (!np) |
472 | return -ENOENT; | |
46e2856b | 473 | |
ff63282e DB |
474 | ret = of_device_is_compatible(np, "operating-points-v2-kryo-cpu") || |
475 | of_device_is_compatible(np, "operating-points-v2-krait-cpu"); | |
46e2856b IL |
476 | if (!ret) { |
477 | of_node_put(np); | |
478 | return -ENOENT; | |
479 | } | |
480 | ||
2a5d46c3 SG |
481 | drv = devm_kzalloc(&pdev->dev, struct_size(drv, cpus, num_possible_cpus()), |
482 | GFP_KERNEL); | |
57f2f8b4 NC |
483 | if (!drv) |
484 | return -ENOMEM; | |
485 | ||
486 | match = pdev->dev.platform_data; | |
487 | drv->data = match->data; | |
2a5d46c3 SG |
488 | if (!drv->data) |
489 | return -ENODEV; | |
46e2856b | 490 | |
57f2f8b4 NC |
491 | if (drv->data->get_version) { |
492 | speedbin_nvmem = of_nvmem_cell_get(np, NULL); | |
2a5d46c3 SG |
493 | if (IS_ERR(speedbin_nvmem)) |
494 | return dev_err_probe(cpu_dev, PTR_ERR(speedbin_nvmem), | |
495 | "Could not get nvmem cell\n"); | |
46e2856b | 496 | |
a8811ec7 AS |
497 | ret = drv->data->get_version(cpu_dev, |
498 | speedbin_nvmem, &pvs_name, drv); | |
57f2f8b4 NC |
499 | if (ret) { |
500 | nvmem_cell_put(speedbin_nvmem); | |
2a5d46c3 | 501 | return ret; |
57f2f8b4 NC |
502 | } |
503 | nvmem_cell_put(speedbin_nvmem); | |
504 | } | |
505 | of_node_put(np); | |
506 | ||
46e2856b | 507 | for_each_possible_cpu(cpu) { |
5cbff51e | 508 | struct device **virt_devs = NULL; |
49cd000d VK |
509 | struct dev_pm_opp_config config = { |
510 | .supported_hw = NULL, | |
511 | }; | |
512 | ||
46e2856b IL |
513 | cpu_dev = get_cpu_device(cpu); |
514 | if (NULL == cpu_dev) { | |
515 | ret = -ENODEV; | |
49cd000d | 516 | goto free_opp; |
46e2856b IL |
517 | } |
518 | ||
57f2f8b4 | 519 | if (drv->data->get_version) { |
49cd000d VK |
520 | config.supported_hw = &drv->versions; |
521 | config.supported_hw_count = 1; | |
a8811ec7 | 522 | |
49cd000d VK |
523 | if (pvs_name) |
524 | config.prop_name = pvs_name; | |
1cb8339c NC |
525 | } |
526 | ||
527 | if (drv->data->genpd_names) { | |
49cd000d | 528 | config.genpd_names = drv->data->genpd_names; |
5cbff51e | 529 | config.virt_devs = &virt_devs; |
49cd000d VK |
530 | } |
531 | ||
532 | if (config.supported_hw || config.genpd_names) { | |
2a5d46c3 SG |
533 | drv->cpus[cpu].opp_token = dev_pm_opp_set_config(cpu_dev, &config); |
534 | if (drv->cpus[cpu].opp_token < 0) { | |
535 | ret = drv->cpus[cpu].opp_token; | |
49cd000d VK |
536 | dev_err(cpu_dev, "Failed to set OPP config\n"); |
537 | goto free_opp; | |
57f2f8b4 | 538 | } |
46e2856b | 539 | } |
5cbff51e SG |
540 | |
541 | if (virt_devs) { | |
542 | const char * const *name = config.genpd_names; | |
543 | int i, j; | |
544 | ||
545 | for (i = 0; *name; i++, name++) { | |
546 | ret = pm_runtime_resume_and_get(virt_devs[i]); | |
547 | if (ret) { | |
548 | dev_err(cpu_dev, "failed to resume %s: %d\n", | |
549 | *name, ret); | |
550 | ||
551 | /* Rollback previous PM runtime calls */ | |
552 | name = config.genpd_names; | |
553 | for (j = 0; *name && j < i; j++, name++) | |
554 | pm_runtime_put(virt_devs[j]); | |
555 | ||
556 | goto free_opp; | |
557 | } | |
558 | } | |
559 | drv->cpus[cpu].virt_devs = virt_devs; | |
560 | } | |
46e2856b IL |
561 | } |
562 | ||
563 | cpufreq_dt_pdev = platform_device_register_simple("cpufreq-dt", -1, | |
564 | NULL, 0); | |
0334906c | 565 | if (!IS_ERR(cpufreq_dt_pdev)) { |
57f2f8b4 | 566 | platform_set_drvdata(pdev, drv); |
46e2856b | 567 | return 0; |
0334906c | 568 | } |
46e2856b IL |
569 | |
570 | ret = PTR_ERR(cpufreq_dt_pdev); | |
571 | dev_err(cpu_dev, "Failed to register platform device\n"); | |
572 | ||
573 | free_opp: | |
5cbff51e SG |
574 | for_each_possible_cpu(cpu) { |
575 | qcom_cpufreq_put_virt_devs(drv, cpu); | |
2a5d46c3 | 576 | dev_pm_opp_clear_config(drv->cpus[cpu].opp_token); |
5cbff51e | 577 | } |
46e2856b IL |
578 | return ret; |
579 | } | |
580 | ||
40273232 | 581 | static void qcom_cpufreq_remove(struct platform_device *pdev) |
5ad7346b | 582 | { |
57f2f8b4 | 583 | struct qcom_cpufreq_drv *drv = platform_get_drvdata(pdev); |
0334906c VK |
584 | unsigned int cpu; |
585 | ||
5ad7346b | 586 | platform_device_unregister(cpufreq_dt_pdev); |
0334906c | 587 | |
5cbff51e SG |
588 | for_each_possible_cpu(cpu) { |
589 | qcom_cpufreq_put_virt_devs(drv, cpu); | |
2a5d46c3 | 590 | dev_pm_opp_clear_config(drv->cpus[cpu].opp_token); |
5cbff51e | 591 | } |
5ad7346b IL |
592 | } |
593 | ||
d6048a19 SG |
594 | static int qcom_cpufreq_suspend(struct device *dev) |
595 | { | |
596 | struct qcom_cpufreq_drv *drv = dev_get_drvdata(dev); | |
597 | unsigned int cpu; | |
598 | ||
599 | for_each_possible_cpu(cpu) | |
600 | qcom_cpufreq_suspend_virt_devs(drv, cpu); | |
601 | ||
602 | return 0; | |
603 | } | |
604 | ||
605 | static DEFINE_SIMPLE_DEV_PM_OPS(qcom_cpufreq_pm_ops, qcom_cpufreq_suspend, NULL); | |
606 | ||
7d127095 S |
607 | static struct platform_driver qcom_cpufreq_driver = { |
608 | .probe = qcom_cpufreq_probe, | |
40273232 | 609 | .remove_new = qcom_cpufreq_remove, |
46e2856b | 610 | .driver = { |
7d127095 | 611 | .name = "qcom-cpufreq-nvmem", |
d6048a19 | 612 | .pm = pm_sleep_ptr(&qcom_cpufreq_pm_ops), |
46e2856b IL |
613 | }, |
614 | }; | |
615 | ||
7d127095 | 616 | static const struct of_device_id qcom_cpufreq_match_list[] __initconst = { |
57f2f8b4 | 617 | { .compatible = "qcom,apq8096", .data = &match_data_kryo }, |
f0d64f4a | 618 | { .compatible = "qcom,msm8909", .data = &match_data_msm8909 }, |
57f2f8b4 | 619 | { .compatible = "qcom,msm8996", .data = &match_data_kryo }, |
1cb8339c | 620 | { .compatible = "qcom,qcs404", .data = &match_data_qcs404 }, |
ba5a61a0 | 621 | { .compatible = "qcom,ipq5332", .data = &match_data_kryo }, |
47e161a7 | 622 | { .compatible = "qcom,ipq6018", .data = &match_data_ipq6018 }, |
4a3754f7 | 623 | { .compatible = "qcom,ipq8064", .data = &match_data_ipq8064 }, |
0b9cd949 | 624 | { .compatible = "qcom,ipq8074", .data = &match_data_ipq8074 }, |
a8811ec7 | 625 | { .compatible = "qcom,apq8064", .data = &match_data_krait }, |
5b5b5806 | 626 | { .compatible = "qcom,ipq9574", .data = &match_data_kryo }, |
a8811ec7 AS |
627 | { .compatible = "qcom,msm8974", .data = &match_data_krait }, |
628 | { .compatible = "qcom,msm8960", .data = &match_data_krait }, | |
7d127095 | 629 | {}, |
46e2856b | 630 | }; |
a5a60316 | 631 | MODULE_DEVICE_TABLE(of, qcom_cpufreq_match_list); |
46e2856b IL |
632 | |
633 | /* | |
634 | * Since the driver depends on smem and nvmem drivers, which may | |
635 | * return EPROBE_DEFER, all the real activity is done in the probe, | |
636 | * which may be defered as well. The init here is only registering | |
637 | * the driver and the platform device. | |
638 | */ | |
7d127095 | 639 | static int __init qcom_cpufreq_init(void) |
46e2856b IL |
640 | { |
641 | struct device_node *np = of_find_node_by_path("/"); | |
642 | const struct of_device_id *match; | |
643 | int ret; | |
644 | ||
645 | if (!np) | |
646 | return -ENODEV; | |
647 | ||
7d127095 | 648 | match = of_match_node(qcom_cpufreq_match_list, np); |
46e2856b IL |
649 | of_node_put(np); |
650 | if (!match) | |
651 | return -ENODEV; | |
652 | ||
7d127095 | 653 | ret = platform_driver_register(&qcom_cpufreq_driver); |
46e2856b IL |
654 | if (unlikely(ret < 0)) |
655 | return ret; | |
656 | ||
7d127095 S |
657 | cpufreq_pdev = platform_device_register_data(NULL, "qcom-cpufreq-nvmem", |
658 | -1, match, sizeof(*match)); | |
659 | ret = PTR_ERR_OR_ZERO(cpufreq_pdev); | |
46e2856b IL |
660 | if (0 == ret) |
661 | return 0; | |
662 | ||
7d127095 | 663 | platform_driver_unregister(&qcom_cpufreq_driver); |
46e2856b IL |
664 | return ret; |
665 | } | |
7d127095 | 666 | module_init(qcom_cpufreq_init); |
46e2856b | 667 | |
7d127095 | 668 | static void __exit qcom_cpufreq_exit(void) |
5ad7346b | 669 | { |
7d127095 S |
670 | platform_device_unregister(cpufreq_pdev); |
671 | platform_driver_unregister(&qcom_cpufreq_driver); | |
5ad7346b | 672 | } |
7d127095 | 673 | module_exit(qcom_cpufreq_exit); |
5ad7346b | 674 | |
7d127095 | 675 | MODULE_DESCRIPTION("Qualcomm Technologies, Inc. CPUfreq driver"); |
46e2856b | 676 | MODULE_LICENSE("GPL v2"); |