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. |
12 | * The qcom-cpufreq-nvmem driver reads the msm-id and efuse value from the SoC |
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> |
26 | #include <linux/pm.h> |
27 | #include <linux/pm_domain.h> |
28 | #include <linux/pm_opp.h> |
29 | #include <linux/pm_runtime.h> |
30 | #include <linux/slab.h> |
31 | #include <linux/soc/qcom/smem.h> |
32 | |
33 | #include <dt-bindings/arm/qcom,ids.h> |
34 | |
35 | enum ipq806x_versions { |
36 | IPQ8062_VERSION = 0, |
37 | IPQ8064_VERSION, |
38 | IPQ8065_VERSION, |
39 | }; |
40 | |
41 | #define IPQ6000_VERSION BIT(2) |
42 | |
43 | enum ipq8074_versions { |
44 | IPQ8074_HAWKEYE_VERSION = 0, |
45 | IPQ8074_ACORN_VERSION, |
46 | }; |
47 | |
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, |
53 | char **pvs_name, |
54 | struct qcom_cpufreq_drv *drv); |
55 | const char **genpd_names; |
56 | }; |
57 | |
58 | struct qcom_cpufreq_drv_cpu { |
59 | int opp_token; |
60 | struct device **virt_devs; |
61 | }; |
62 | |
63 | struct qcom_cpufreq_drv { |
64 | u32 versions; |
65 | const struct qcom_cpufreq_match_data *data; |
66 | struct qcom_cpufreq_drv_cpu cpus[]; |
67 | }; |
68 | |
69 | static struct platform_device *cpufreq_dt_pdev, *cpufreq_pdev; |
70 | |
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(cell: speedbin_nvmem, NULL); |
80 | if (IS_ERR(ptr: speedbin)) |
81 | return PTR_ERR(ptr: speedbin); |
82 | |
83 | dev_dbg(cpu_dev, "speedbin: %d\n" , *speedbin); |
84 | drv->versions = 1 << *speedbin; |
85 | kfree(objp: speedbin); |
86 | return 0; |
87 | } |
88 | |
89 | static void get_krait_bin_format_a(struct device *cpu_dev, |
90 | int *speed, int *pvs, |
91 | u8 *buf) |
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, |
122 | u8 *buf) |
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 */ |
155 | pte_efuse = *(((u32 *)buf) + 1); |
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 | |
167 | static int qcom_cpufreq_kryo_name_version(struct device *cpu_dev, |
168 | struct nvmem_cell *speedbin_nvmem, |
169 | char **pvs_name, |
170 | struct qcom_cpufreq_drv *drv) |
171 | { |
172 | size_t len; |
173 | u32 msm_id; |
174 | u8 *speedbin; |
175 | int ret; |
176 | *pvs_name = NULL; |
177 | |
178 | ret = qcom_smem_get_soc_id(id: &msm_id); |
179 | if (ret) |
180 | return ret; |
181 | |
182 | speedbin = nvmem_cell_read(cell: speedbin_nvmem, len: &len); |
183 | if (IS_ERR(ptr: speedbin)) |
184 | return PTR_ERR(ptr: speedbin); |
185 | |
186 | switch (msm_id) { |
187 | case QCOM_ID_MSM8996: |
188 | case QCOM_ID_APQ8096: |
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: |
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: |
199 | drv->versions = 1 << (unsigned int)(*speedbin); |
200 | break; |
201 | case QCOM_ID_MSM8996SG: |
202 | case QCOM_ID_APQ8096SG: |
203 | drv->versions = 1 << ((unsigned int)(*speedbin) + 4); |
204 | break; |
205 | default: |
206 | BUG(); |
207 | break; |
208 | } |
209 | |
210 | kfree(objp: speedbin); |
211 | return 0; |
212 | } |
213 | |
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; |
222 | int ret = 0; |
223 | |
224 | speedbin = nvmem_cell_read(cell: speedbin_nvmem, len: &len); |
225 | |
226 | if (IS_ERR(ptr: speedbin)) |
227 | return PTR_ERR(ptr: speedbin); |
228 | |
229 | switch (len) { |
230 | case 4: |
231 | get_krait_bin_format_a(cpu_dev, speed: &speed, pvs: &pvs, buf: speedbin); |
232 | break; |
233 | case 8: |
234 | get_krait_bin_format_b(cpu_dev, speed: &speed, pvs: &pvs, pvs_ver: &pvs_ver, |
235 | buf: speedbin); |
236 | break; |
237 | default: |
238 | dev_err(cpu_dev, "Unable to read nvmem data. Defaulting to 0!\n" ); |
239 | ret = -ENODEV; |
240 | goto len_error; |
241 | } |
242 | |
243 | snprintf(buf: *pvs_name, size: sizeof("speedXX-pvsXX-vXX" ), fmt: "speed%d-pvs%d-v%d" , |
244 | speed, pvs, pvs_ver); |
245 | |
246 | drv->versions = (1 << speed); |
247 | |
248 | len_error: |
249 | kfree(objp: speedbin); |
250 | return ret; |
251 | } |
252 | |
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(cell: speedbin_nvmem, len: &len); |
264 | if (IS_ERR(ptr: speedbin)) |
265 | return PTR_ERR(ptr: 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: &speed, pvs: &pvs, buf: speedbin); |
274 | |
275 | ret = qcom_smem_get_soc_id(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(buf: *pvs_name, size: sizeof("speed0-pvsXX" ), fmt: "speed0-pvs%d" , pvs); |
302 | |
303 | exit: |
304 | kfree(objp: speedbin); |
305 | return ret; |
306 | } |
307 | |
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(id: &msm_id); |
319 | if (ret) |
320 | return ret; |
321 | |
322 | speedbin = nvmem_cell_read(cell: speedbin_nvmem, NULL); |
323 | if (IS_ERR(ptr: speedbin)) |
324 | return PTR_ERR(ptr: 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(objp: speedbin); |
356 | return 0; |
357 | } |
358 | |
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(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 | |
397 | static const char *generic_genpd_names[] = { "perf" , NULL }; |
398 | |
399 | static const struct qcom_cpufreq_match_data match_data_kryo = { |
400 | .get_version = qcom_cpufreq_kryo_name_version, |
401 | }; |
402 | |
403 | static const struct qcom_cpufreq_match_data match_data_krait = { |
404 | .get_version = qcom_cpufreq_krait_name_version, |
405 | }; |
406 | |
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 | |
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 | |
418 | static const struct qcom_cpufreq_match_data match_data_ipq6018 = { |
419 | .get_version = qcom_cpufreq_ipq6018_name_version, |
420 | }; |
421 | |
422 | static const struct qcom_cpufreq_match_data match_data_ipq8064 = { |
423 | .get_version = qcom_cpufreq_ipq8064_name_version, |
424 | }; |
425 | |
426 | static const struct qcom_cpufreq_match_data match_data_ipq8074 = { |
427 | .get_version = qcom_cpufreq_ipq8074_name_version, |
428 | }; |
429 | |
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(dev: drv->cpus[cpu].virt_devs[i]); |
440 | } |
441 | |
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(dev: drv->cpus[cpu].virt_devs[i]); |
452 | } |
453 | |
454 | static int qcom_cpufreq_probe(struct platform_device *pdev) |
455 | { |
456 | struct qcom_cpufreq_drv *drv; |
457 | struct nvmem_cell *speedbin_nvmem; |
458 | struct device_node *np; |
459 | struct device *cpu_dev; |
460 | char pvs_name_buffer[] = "speedXX-pvsXX-vXX" ; |
461 | char *pvs_name = pvs_name_buffer; |
462 | unsigned cpu; |
463 | const struct of_device_id *match; |
464 | int ret; |
465 | |
466 | cpu_dev = get_cpu_device(cpu: 0); |
467 | if (!cpu_dev) |
468 | return -ENODEV; |
469 | |
470 | np = dev_pm_opp_of_get_opp_desc_node(dev: cpu_dev); |
471 | if (!np) |
472 | return -ENOENT; |
473 | |
474 | ret = of_device_is_compatible(device: np, "operating-points-v2-kryo-cpu" ) || |
475 | of_device_is_compatible(device: np, "operating-points-v2-krait-cpu" ); |
476 | if (!ret) { |
477 | of_node_put(node: np); |
478 | return -ENOENT; |
479 | } |
480 | |
481 | drv = devm_kzalloc(dev: &pdev->dev, struct_size(drv, cpus, num_possible_cpus()), |
482 | GFP_KERNEL); |
483 | if (!drv) |
484 | return -ENOMEM; |
485 | |
486 | match = pdev->dev.platform_data; |
487 | drv->data = match->data; |
488 | if (!drv->data) |
489 | return -ENODEV; |
490 | |
491 | if (drv->data->get_version) { |
492 | speedbin_nvmem = of_nvmem_cell_get(np, NULL); |
493 | if (IS_ERR(ptr: speedbin_nvmem)) |
494 | return dev_err_probe(dev: cpu_dev, err: PTR_ERR(ptr: speedbin_nvmem), |
495 | fmt: "Could not get nvmem cell\n" ); |
496 | |
497 | ret = drv->data->get_version(cpu_dev, |
498 | speedbin_nvmem, &pvs_name, drv); |
499 | if (ret) { |
500 | nvmem_cell_put(cell: speedbin_nvmem); |
501 | return ret; |
502 | } |
503 | nvmem_cell_put(cell: speedbin_nvmem); |
504 | } |
505 | of_node_put(node: np); |
506 | |
507 | for_each_possible_cpu(cpu) { |
508 | struct device **virt_devs = NULL; |
509 | struct dev_pm_opp_config config = { |
510 | .supported_hw = NULL, |
511 | }; |
512 | |
513 | cpu_dev = get_cpu_device(cpu); |
514 | if (NULL == cpu_dev) { |
515 | ret = -ENODEV; |
516 | goto free_opp; |
517 | } |
518 | |
519 | if (drv->data->get_version) { |
520 | config.supported_hw = &drv->versions; |
521 | config.supported_hw_count = 1; |
522 | |
523 | if (pvs_name) |
524 | config.prop_name = pvs_name; |
525 | } |
526 | |
527 | if (drv->data->genpd_names) { |
528 | config.genpd_names = drv->data->genpd_names; |
529 | config.virt_devs = &virt_devs; |
530 | } |
531 | |
532 | if (config.supported_hw || config.genpd_names) { |
533 | drv->cpus[cpu].opp_token = dev_pm_opp_set_config(dev: cpu_dev, config: &config); |
534 | if (drv->cpus[cpu].opp_token < 0) { |
535 | ret = drv->cpus[cpu].opp_token; |
536 | dev_err(cpu_dev, "Failed to set OPP config\n" ); |
537 | goto free_opp; |
538 | } |
539 | } |
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(dev: 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(dev: virt_devs[j]); |
555 | |
556 | goto free_opp; |
557 | } |
558 | } |
559 | drv->cpus[cpu].virt_devs = virt_devs; |
560 | } |
561 | } |
562 | |
563 | cpufreq_dt_pdev = platform_device_register_simple(name: "cpufreq-dt" , id: -1, |
564 | NULL, num: 0); |
565 | if (!IS_ERR(ptr: cpufreq_dt_pdev)) { |
566 | platform_set_drvdata(pdev, data: drv); |
567 | return 0; |
568 | } |
569 | |
570 | ret = PTR_ERR(ptr: cpufreq_dt_pdev); |
571 | dev_err(cpu_dev, "Failed to register platform device\n" ); |
572 | |
573 | free_opp: |
574 | for_each_possible_cpu(cpu) { |
575 | qcom_cpufreq_put_virt_devs(drv, cpu); |
576 | dev_pm_opp_clear_config(token: drv->cpus[cpu].opp_token); |
577 | } |
578 | return ret; |
579 | } |
580 | |
581 | static void qcom_cpufreq_remove(struct platform_device *pdev) |
582 | { |
583 | struct qcom_cpufreq_drv *drv = platform_get_drvdata(pdev); |
584 | unsigned int cpu; |
585 | |
586 | platform_device_unregister(cpufreq_dt_pdev); |
587 | |
588 | for_each_possible_cpu(cpu) { |
589 | qcom_cpufreq_put_virt_devs(drv, cpu); |
590 | dev_pm_opp_clear_config(token: drv->cpus[cpu].opp_token); |
591 | } |
592 | } |
593 | |
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 | |
607 | static struct platform_driver qcom_cpufreq_driver = { |
608 | .probe = qcom_cpufreq_probe, |
609 | .remove_new = qcom_cpufreq_remove, |
610 | .driver = { |
611 | .name = "qcom-cpufreq-nvmem" , |
612 | .pm = pm_sleep_ptr(&qcom_cpufreq_pm_ops), |
613 | }, |
614 | }; |
615 | |
616 | static const struct of_device_id qcom_cpufreq_match_list[] __initconst = { |
617 | { .compatible = "qcom,apq8096" , .data = &match_data_kryo }, |
618 | { .compatible = "qcom,msm8909" , .data = &match_data_msm8909 }, |
619 | { .compatible = "qcom,msm8996" , .data = &match_data_kryo }, |
620 | { .compatible = "qcom,qcs404" , .data = &match_data_qcs404 }, |
621 | { .compatible = "qcom,ipq5332" , .data = &match_data_kryo }, |
622 | { .compatible = "qcom,ipq6018" , .data = &match_data_ipq6018 }, |
623 | { .compatible = "qcom,ipq8064" , .data = &match_data_ipq8064 }, |
624 | { .compatible = "qcom,ipq8074" , .data = &match_data_ipq8074 }, |
625 | { .compatible = "qcom,apq8064" , .data = &match_data_krait }, |
626 | { .compatible = "qcom,ipq9574" , .data = &match_data_kryo }, |
627 | { .compatible = "qcom,msm8974" , .data = &match_data_krait }, |
628 | { .compatible = "qcom,msm8960" , .data = &match_data_krait }, |
629 | {}, |
630 | }; |
631 | MODULE_DEVICE_TABLE(of, qcom_cpufreq_match_list); |
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 | */ |
639 | static int __init qcom_cpufreq_init(void) |
640 | { |
641 | struct device_node *np = of_find_node_by_path(path: "/" ); |
642 | const struct of_device_id *match; |
643 | int ret; |
644 | |
645 | if (!np) |
646 | return -ENODEV; |
647 | |
648 | match = of_match_node(matches: qcom_cpufreq_match_list, node: np); |
649 | of_node_put(node: np); |
650 | if (!match) |
651 | return -ENODEV; |
652 | |
653 | ret = platform_driver_register(&qcom_cpufreq_driver); |
654 | if (unlikely(ret < 0)) |
655 | return ret; |
656 | |
657 | cpufreq_pdev = platform_device_register_data(NULL, name: "qcom-cpufreq-nvmem" , |
658 | id: -1, data: match, size: sizeof(*match)); |
659 | ret = PTR_ERR_OR_ZERO(ptr: cpufreq_pdev); |
660 | if (0 == ret) |
661 | return 0; |
662 | |
663 | platform_driver_unregister(&qcom_cpufreq_driver); |
664 | return ret; |
665 | } |
666 | module_init(qcom_cpufreq_init); |
667 | |
668 | static void __exit qcom_cpufreq_exit(void) |
669 | { |
670 | platform_device_unregister(cpufreq_pdev); |
671 | platform_driver_unregister(&qcom_cpufreq_driver); |
672 | } |
673 | module_exit(qcom_cpufreq_exit); |
674 | |
675 | MODULE_DESCRIPTION("Qualcomm Technologies, Inc. CPUfreq driver" ); |
676 | MODULE_LICENSE("GPL v2" ); |
677 | |