]>
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> | |
7d127095 | 25 | #include <linux/of_device.h> |
46e2856b | 26 | #include <linux/platform_device.h> |
1cb8339c | 27 | #include <linux/pm_domain.h> |
46e2856b IL |
28 | #include <linux/pm_opp.h> |
29 | #include <linux/slab.h> | |
30 | #include <linux/soc/qcom/smem.h> | |
31 | ||
32 | #define MSM_ID_SMEM 137 | |
33 | ||
34 | enum _msm_id { | |
35 | MSM8996V3 = 0xF6ul, | |
36 | APQ8096V3 = 0x123ul, | |
37 | MSM8996SG = 0x131ul, | |
38 | APQ8096SG = 0x138ul, | |
39 | }; | |
40 | ||
41 | enum _msm8996_version { | |
42 | MSM8996_V3, | |
43 | MSM8996_SG, | |
44 | NUM_OF_MSM8996_VERSIONS, | |
45 | }; | |
46 | ||
57f2f8b4 NC |
47 | struct qcom_cpufreq_drv; |
48 | ||
49 | struct qcom_cpufreq_match_data { | |
50 | int (*get_version)(struct device *cpu_dev, | |
51 | struct nvmem_cell *speedbin_nvmem, | |
a8811ec7 | 52 | char **pvs_name, |
57f2f8b4 | 53 | struct qcom_cpufreq_drv *drv); |
1cb8339c | 54 | const char **genpd_names; |
57f2f8b4 NC |
55 | }; |
56 | ||
57 | struct qcom_cpufreq_drv { | |
a8811ec7 AS |
58 | struct opp_table **names_opp_tables; |
59 | struct opp_table **hw_opp_tables; | |
1cb8339c | 60 | struct opp_table **genpd_opp_tables; |
57f2f8b4 NC |
61 | u32 versions; |
62 | const struct qcom_cpufreq_match_data *data; | |
63 | }; | |
64 | ||
7d127095 | 65 | static struct platform_device *cpufreq_dt_pdev, *cpufreq_pdev; |
5ad7346b | 66 | |
a8811ec7 AS |
67 | static void get_krait_bin_format_a(struct device *cpu_dev, |
68 | int *speed, int *pvs, int *pvs_ver, | |
69 | struct nvmem_cell *pvs_nvmem, u8 *buf) | |
70 | { | |
71 | u32 pte_efuse; | |
72 | ||
73 | pte_efuse = *((u32 *)buf); | |
74 | ||
75 | *speed = pte_efuse & 0xf; | |
76 | if (*speed == 0xf) | |
77 | *speed = (pte_efuse >> 4) & 0xf; | |
78 | ||
79 | if (*speed == 0xf) { | |
80 | *speed = 0; | |
81 | dev_warn(cpu_dev, "Speed bin: Defaulting to %d\n", *speed); | |
82 | } else { | |
83 | dev_dbg(cpu_dev, "Speed bin: %d\n", *speed); | |
84 | } | |
85 | ||
86 | *pvs = (pte_efuse >> 10) & 0x7; | |
87 | if (*pvs == 0x7) | |
88 | *pvs = (pte_efuse >> 13) & 0x7; | |
89 | ||
90 | if (*pvs == 0x7) { | |
91 | *pvs = 0; | |
92 | dev_warn(cpu_dev, "PVS bin: Defaulting to %d\n", *pvs); | |
93 | } else { | |
94 | dev_dbg(cpu_dev, "PVS bin: %d\n", *pvs); | |
95 | } | |
96 | } | |
97 | ||
98 | static void get_krait_bin_format_b(struct device *cpu_dev, | |
99 | int *speed, int *pvs, int *pvs_ver, | |
100 | struct nvmem_cell *pvs_nvmem, u8 *buf) | |
101 | { | |
102 | u32 pte_efuse, redundant_sel; | |
103 | ||
104 | pte_efuse = *((u32 *)buf); | |
105 | redundant_sel = (pte_efuse >> 24) & 0x7; | |
106 | ||
107 | *pvs_ver = (pte_efuse >> 4) & 0x3; | |
108 | ||
109 | switch (redundant_sel) { | |
110 | case 1: | |
111 | *pvs = ((pte_efuse >> 28) & 0x8) | ((pte_efuse >> 6) & 0x7); | |
112 | *speed = (pte_efuse >> 27) & 0xf; | |
113 | break; | |
114 | case 2: | |
115 | *pvs = (pte_efuse >> 27) & 0xf; | |
116 | *speed = pte_efuse & 0x7; | |
117 | break; | |
118 | default: | |
119 | /* 4 bits of PVS are in efuse register bits 31, 8-6. */ | |
120 | *pvs = ((pte_efuse >> 28) & 0x8) | ((pte_efuse >> 6) & 0x7); | |
121 | *speed = pte_efuse & 0x7; | |
122 | } | |
123 | ||
124 | /* Check SPEED_BIN_BLOW_STATUS */ | |
125 | if (pte_efuse & BIT(3)) { | |
126 | dev_dbg(cpu_dev, "Speed bin: %d\n", *speed); | |
127 | } else { | |
128 | dev_warn(cpu_dev, "Speed bin not set. Defaulting to 0!\n"); | |
129 | *speed = 0; | |
130 | } | |
131 | ||
132 | /* Check PVS_BLOW_STATUS */ | |
133 | pte_efuse = *(((u32 *)buf) + 4); | |
134 | pte_efuse &= BIT(21); | |
135 | if (pte_efuse) { | |
136 | dev_dbg(cpu_dev, "PVS bin: %d\n", *pvs); | |
137 | } else { | |
138 | dev_warn(cpu_dev, "PVS bin not set. Defaulting to 0!\n"); | |
139 | *pvs = 0; | |
140 | } | |
141 | ||
142 | dev_dbg(cpu_dev, "PVS version: %d\n", *pvs_ver); | |
143 | } | |
144 | ||
7d127095 | 145 | static enum _msm8996_version qcom_cpufreq_get_msm_id(void) |
46e2856b IL |
146 | { |
147 | size_t len; | |
148 | u32 *msm_id; | |
149 | enum _msm8996_version version; | |
150 | ||
151 | msm_id = qcom_smem_get(QCOM_SMEM_HOST_ANY, MSM_ID_SMEM, &len); | |
152 | if (IS_ERR(msm_id)) | |
153 | return NUM_OF_MSM8996_VERSIONS; | |
154 | ||
155 | /* The first 4 bytes are format, next to them is the actual msm-id */ | |
156 | msm_id++; | |
157 | ||
158 | switch ((enum _msm_id)*msm_id) { | |
159 | case MSM8996V3: | |
160 | case APQ8096V3: | |
161 | version = MSM8996_V3; | |
162 | break; | |
163 | case MSM8996SG: | |
164 | case APQ8096SG: | |
165 | version = MSM8996_SG; | |
166 | break; | |
167 | default: | |
168 | version = NUM_OF_MSM8996_VERSIONS; | |
169 | } | |
170 | ||
171 | return version; | |
172 | } | |
173 | ||
7d127095 S |
174 | static int qcom_cpufreq_kryo_name_version(struct device *cpu_dev, |
175 | struct nvmem_cell *speedbin_nvmem, | |
a8811ec7 | 176 | char **pvs_name, |
57f2f8b4 | 177 | struct qcom_cpufreq_drv *drv) |
46e2856b | 178 | { |
7d127095 S |
179 | size_t len; |
180 | u8 *speedbin; | |
46e2856b | 181 | enum _msm8996_version msm8996_version; |
a8811ec7 | 182 | *pvs_name = NULL; |
7d127095 S |
183 | |
184 | msm8996_version = qcom_cpufreq_get_msm_id(); | |
185 | if (NUM_OF_MSM8996_VERSIONS == msm8996_version) { | |
186 | dev_err(cpu_dev, "Not Snapdragon 820/821!"); | |
187 | return -ENODEV; | |
188 | } | |
189 | ||
190 | speedbin = nvmem_cell_read(speedbin_nvmem, &len); | |
191 | if (IS_ERR(speedbin)) | |
192 | return PTR_ERR(speedbin); | |
193 | ||
194 | switch (msm8996_version) { | |
195 | case MSM8996_V3: | |
57f2f8b4 | 196 | drv->versions = 1 << (unsigned int)(*speedbin); |
7d127095 S |
197 | break; |
198 | case MSM8996_SG: | |
57f2f8b4 | 199 | drv->versions = 1 << ((unsigned int)(*speedbin) + 4); |
7d127095 S |
200 | break; |
201 | default: | |
202 | BUG(); | |
203 | break; | |
204 | } | |
205 | ||
206 | kfree(speedbin); | |
207 | return 0; | |
208 | } | |
209 | ||
a8811ec7 AS |
210 | static int qcom_cpufreq_krait_name_version(struct device *cpu_dev, |
211 | struct nvmem_cell *speedbin_nvmem, | |
212 | char **pvs_name, | |
213 | struct qcom_cpufreq_drv *drv) | |
214 | { | |
215 | int speed = 0, pvs = 0, pvs_ver = 0; | |
216 | u8 *speedbin; | |
217 | size_t len; | |
218 | ||
219 | speedbin = nvmem_cell_read(speedbin_nvmem, &len); | |
220 | ||
221 | if (IS_ERR(speedbin)) | |
222 | return PTR_ERR(speedbin); | |
223 | ||
224 | switch (len) { | |
225 | case 4: | |
226 | get_krait_bin_format_a(cpu_dev, &speed, &pvs, &pvs_ver, | |
227 | speedbin_nvmem, speedbin); | |
228 | break; | |
229 | case 8: | |
230 | get_krait_bin_format_b(cpu_dev, &speed, &pvs, &pvs_ver, | |
231 | speedbin_nvmem, speedbin); | |
232 | break; | |
233 | default: | |
234 | dev_err(cpu_dev, "Unable to read nvmem data. Defaulting to 0!\n"); | |
235 | return -ENODEV; | |
236 | } | |
237 | ||
238 | snprintf(*pvs_name, sizeof("speedXX-pvsXX-vXX"), "speed%d-pvs%d-v%d", | |
239 | speed, pvs, pvs_ver); | |
240 | ||
241 | drv->versions = (1 << speed); | |
242 | ||
243 | kfree(speedbin); | |
244 | return 0; | |
245 | } | |
246 | ||
57f2f8b4 NC |
247 | static const struct qcom_cpufreq_match_data match_data_kryo = { |
248 | .get_version = qcom_cpufreq_kryo_name_version, | |
249 | }; | |
250 | ||
a8811ec7 AS |
251 | static const struct qcom_cpufreq_match_data match_data_krait = { |
252 | .get_version = qcom_cpufreq_krait_name_version, | |
253 | }; | |
254 | ||
1cb8339c NC |
255 | static const char *qcs404_genpd_names[] = { "cpr", NULL }; |
256 | ||
257 | static const struct qcom_cpufreq_match_data match_data_qcs404 = { | |
258 | .genpd_names = qcs404_genpd_names, | |
259 | }; | |
260 | ||
7d127095 S |
261 | static int qcom_cpufreq_probe(struct platform_device *pdev) |
262 | { | |
57f2f8b4 | 263 | struct qcom_cpufreq_drv *drv; |
46e2856b IL |
264 | struct nvmem_cell *speedbin_nvmem; |
265 | struct device_node *np; | |
266 | struct device *cpu_dev; | |
a8811ec7 | 267 | char *pvs_name = "speedXX-pvsXX-vXX"; |
46e2856b | 268 | unsigned cpu; |
7d127095 | 269 | const struct of_device_id *match; |
46e2856b IL |
270 | int ret; |
271 | ||
272 | cpu_dev = get_cpu_device(0); | |
1dd2058f DC |
273 | if (!cpu_dev) |
274 | return -ENODEV; | |
46e2856b | 275 | |
46e2856b | 276 | np = dev_pm_opp_of_get_opp_desc_node(cpu_dev); |
1dd2058f DC |
277 | if (!np) |
278 | return -ENOENT; | |
46e2856b | 279 | |
2dea6516 | 280 | ret = of_device_is_compatible(np, "operating-points-v2-kryo-cpu"); |
46e2856b IL |
281 | if (!ret) { |
282 | of_node_put(np); | |
283 | return -ENOENT; | |
284 | } | |
285 | ||
57f2f8b4 NC |
286 | drv = kzalloc(sizeof(*drv), GFP_KERNEL); |
287 | if (!drv) | |
288 | return -ENOMEM; | |
289 | ||
290 | match = pdev->dev.platform_data; | |
291 | drv->data = match->data; | |
292 | if (!drv->data) { | |
293 | ret = -ENODEV; | |
294 | goto free_drv; | |
46e2856b IL |
295 | } |
296 | ||
57f2f8b4 NC |
297 | if (drv->data->get_version) { |
298 | speedbin_nvmem = of_nvmem_cell_get(np, NULL); | |
299 | if (IS_ERR(speedbin_nvmem)) { | |
300 | if (PTR_ERR(speedbin_nvmem) != -EPROBE_DEFER) | |
301 | dev_err(cpu_dev, | |
302 | "Could not get nvmem cell: %ld\n", | |
303 | PTR_ERR(speedbin_nvmem)); | |
304 | ret = PTR_ERR(speedbin_nvmem); | |
305 | goto free_drv; | |
306 | } | |
46e2856b | 307 | |
a8811ec7 AS |
308 | ret = drv->data->get_version(cpu_dev, |
309 | speedbin_nvmem, &pvs_name, drv); | |
57f2f8b4 NC |
310 | if (ret) { |
311 | nvmem_cell_put(speedbin_nvmem); | |
312 | goto free_drv; | |
313 | } | |
314 | nvmem_cell_put(speedbin_nvmem); | |
315 | } | |
316 | of_node_put(np); | |
317 | ||
a8811ec7 AS |
318 | drv->names_opp_tables = kcalloc(num_possible_cpus(), |
319 | sizeof(*drv->names_opp_tables), | |
57f2f8b4 | 320 | GFP_KERNEL); |
a8811ec7 | 321 | if (!drv->names_opp_tables) { |
57f2f8b4 NC |
322 | ret = -ENOMEM; |
323 | goto free_drv; | |
324 | } | |
a8811ec7 AS |
325 | drv->hw_opp_tables = kcalloc(num_possible_cpus(), |
326 | sizeof(*drv->hw_opp_tables), | |
327 | GFP_KERNEL); | |
328 | if (!drv->hw_opp_tables) { | |
329 | ret = -ENOMEM; | |
330 | goto free_opp_names; | |
331 | } | |
0334906c | 332 | |
1cb8339c NC |
333 | drv->genpd_opp_tables = kcalloc(num_possible_cpus(), |
334 | sizeof(*drv->genpd_opp_tables), | |
335 | GFP_KERNEL); | |
336 | if (!drv->genpd_opp_tables) { | |
337 | ret = -ENOMEM; | |
338 | goto free_opp; | |
339 | } | |
340 | ||
46e2856b IL |
341 | for_each_possible_cpu(cpu) { |
342 | cpu_dev = get_cpu_device(cpu); | |
343 | if (NULL == cpu_dev) { | |
344 | ret = -ENODEV; | |
1cb8339c | 345 | goto free_genpd_opp; |
46e2856b IL |
346 | } |
347 | ||
57f2f8b4 | 348 | if (drv->data->get_version) { |
a8811ec7 AS |
349 | |
350 | if (pvs_name) { | |
351 | drv->names_opp_tables[cpu] = dev_pm_opp_set_prop_name( | |
352 | cpu_dev, | |
353 | pvs_name); | |
354 | if (IS_ERR(drv->names_opp_tables[cpu])) { | |
355 | ret = PTR_ERR(drv->names_opp_tables[cpu]); | |
356 | dev_err(cpu_dev, "Failed to add OPP name %s\n", | |
357 | pvs_name); | |
358 | goto free_opp; | |
359 | } | |
360 | } | |
361 | ||
362 | drv->hw_opp_tables[cpu] = dev_pm_opp_set_supported_hw( | |
363 | cpu_dev, &drv->versions, 1); | |
364 | if (IS_ERR(drv->hw_opp_tables[cpu])) { | |
365 | ret = PTR_ERR(drv->hw_opp_tables[cpu]); | |
57f2f8b4 NC |
366 | dev_err(cpu_dev, |
367 | "Failed to set supported hardware\n"); | |
1cb8339c NC |
368 | goto free_genpd_opp; |
369 | } | |
370 | } | |
371 | ||
372 | if (drv->data->genpd_names) { | |
373 | drv->genpd_opp_tables[cpu] = | |
374 | dev_pm_opp_attach_genpd(cpu_dev, | |
375 | drv->data->genpd_names, | |
376 | NULL); | |
377 | if (IS_ERR(drv->genpd_opp_tables[cpu])) { | |
378 | ret = PTR_ERR(drv->genpd_opp_tables[cpu]); | |
379 | if (ret != -EPROBE_DEFER) | |
380 | dev_err(cpu_dev, | |
381 | "Could not attach to pm_domain: %d\n", | |
382 | ret); | |
383 | goto free_genpd_opp; | |
57f2f8b4 | 384 | } |
46e2856b IL |
385 | } |
386 | } | |
387 | ||
388 | cpufreq_dt_pdev = platform_device_register_simple("cpufreq-dt", -1, | |
389 | NULL, 0); | |
0334906c | 390 | if (!IS_ERR(cpufreq_dt_pdev)) { |
57f2f8b4 | 391 | platform_set_drvdata(pdev, drv); |
46e2856b | 392 | return 0; |
0334906c | 393 | } |
46e2856b IL |
394 | |
395 | ret = PTR_ERR(cpufreq_dt_pdev); | |
396 | dev_err(cpu_dev, "Failed to register platform device\n"); | |
397 | ||
1cb8339c NC |
398 | free_genpd_opp: |
399 | for_each_possible_cpu(cpu) { | |
2ff8fe13 | 400 | if (IS_ERR(drv->genpd_opp_tables[cpu])) |
1cb8339c NC |
401 | break; |
402 | dev_pm_opp_detach_genpd(drv->genpd_opp_tables[cpu]); | |
403 | } | |
404 | kfree(drv->genpd_opp_tables); | |
46e2856b IL |
405 | free_opp: |
406 | for_each_possible_cpu(cpu) { | |
2ff8fe13 | 407 | if (IS_ERR(drv->names_opp_tables[cpu])) |
a8811ec7 AS |
408 | break; |
409 | dev_pm_opp_put_prop_name(drv->names_opp_tables[cpu]); | |
410 | } | |
411 | for_each_possible_cpu(cpu) { | |
2ff8fe13 | 412 | if (IS_ERR(drv->hw_opp_tables[cpu])) |
46e2856b | 413 | break; |
a8811ec7 | 414 | dev_pm_opp_put_supported_hw(drv->hw_opp_tables[cpu]); |
46e2856b | 415 | } |
a8811ec7 AS |
416 | kfree(drv->hw_opp_tables); |
417 | free_opp_names: | |
418 | kfree(drv->names_opp_tables); | |
57f2f8b4 NC |
419 | free_drv: |
420 | kfree(drv); | |
46e2856b IL |
421 | |
422 | return ret; | |
423 | } | |
424 | ||
7d127095 | 425 | static int qcom_cpufreq_remove(struct platform_device *pdev) |
5ad7346b | 426 | { |
57f2f8b4 | 427 | struct qcom_cpufreq_drv *drv = platform_get_drvdata(pdev); |
0334906c VK |
428 | unsigned int cpu; |
429 | ||
5ad7346b | 430 | platform_device_unregister(cpufreq_dt_pdev); |
0334906c | 431 | |
1cb8339c | 432 | for_each_possible_cpu(cpu) { |
2ff8fe13 VK |
433 | dev_pm_opp_put_supported_hw(drv->names_opp_tables[cpu]); |
434 | dev_pm_opp_put_supported_hw(drv->hw_opp_tables[cpu]); | |
435 | dev_pm_opp_detach_genpd(drv->genpd_opp_tables[cpu]); | |
1cb8339c | 436 | } |
0334906c | 437 | |
a8811ec7 AS |
438 | kfree(drv->names_opp_tables); |
439 | kfree(drv->hw_opp_tables); | |
1cb8339c | 440 | kfree(drv->genpd_opp_tables); |
57f2f8b4 | 441 | kfree(drv); |
0334906c | 442 | |
5ad7346b IL |
443 | return 0; |
444 | } | |
445 | ||
7d127095 S |
446 | static struct platform_driver qcom_cpufreq_driver = { |
447 | .probe = qcom_cpufreq_probe, | |
448 | .remove = qcom_cpufreq_remove, | |
46e2856b | 449 | .driver = { |
7d127095 | 450 | .name = "qcom-cpufreq-nvmem", |
46e2856b IL |
451 | }, |
452 | }; | |
453 | ||
7d127095 | 454 | static const struct of_device_id qcom_cpufreq_match_list[] __initconst = { |
57f2f8b4 NC |
455 | { .compatible = "qcom,apq8096", .data = &match_data_kryo }, |
456 | { .compatible = "qcom,msm8996", .data = &match_data_kryo }, | |
1cb8339c | 457 | { .compatible = "qcom,qcs404", .data = &match_data_qcs404 }, |
a8811ec7 AS |
458 | { .compatible = "qcom,ipq8064", .data = &match_data_krait }, |
459 | { .compatible = "qcom,apq8064", .data = &match_data_krait }, | |
460 | { .compatible = "qcom,msm8974", .data = &match_data_krait }, | |
461 | { .compatible = "qcom,msm8960", .data = &match_data_krait }, | |
7d127095 | 462 | {}, |
46e2856b | 463 | }; |
a5a60316 | 464 | MODULE_DEVICE_TABLE(of, qcom_cpufreq_match_list); |
46e2856b IL |
465 | |
466 | /* | |
467 | * Since the driver depends on smem and nvmem drivers, which may | |
468 | * return EPROBE_DEFER, all the real activity is done in the probe, | |
469 | * which may be defered as well. The init here is only registering | |
470 | * the driver and the platform device. | |
471 | */ | |
7d127095 | 472 | static int __init qcom_cpufreq_init(void) |
46e2856b IL |
473 | { |
474 | struct device_node *np = of_find_node_by_path("/"); | |
475 | const struct of_device_id *match; | |
476 | int ret; | |
477 | ||
478 | if (!np) | |
479 | return -ENODEV; | |
480 | ||
7d127095 | 481 | match = of_match_node(qcom_cpufreq_match_list, np); |
46e2856b IL |
482 | of_node_put(np); |
483 | if (!match) | |
484 | return -ENODEV; | |
485 | ||
7d127095 | 486 | ret = platform_driver_register(&qcom_cpufreq_driver); |
46e2856b IL |
487 | if (unlikely(ret < 0)) |
488 | return ret; | |
489 | ||
7d127095 S |
490 | cpufreq_pdev = platform_device_register_data(NULL, "qcom-cpufreq-nvmem", |
491 | -1, match, sizeof(*match)); | |
492 | ret = PTR_ERR_OR_ZERO(cpufreq_pdev); | |
46e2856b IL |
493 | if (0 == ret) |
494 | return 0; | |
495 | ||
7d127095 | 496 | platform_driver_unregister(&qcom_cpufreq_driver); |
46e2856b IL |
497 | return ret; |
498 | } | |
7d127095 | 499 | module_init(qcom_cpufreq_init); |
46e2856b | 500 | |
7d127095 | 501 | static void __exit qcom_cpufreq_exit(void) |
5ad7346b | 502 | { |
7d127095 S |
503 | platform_device_unregister(cpufreq_pdev); |
504 | platform_driver_unregister(&qcom_cpufreq_driver); | |
5ad7346b | 505 | } |
7d127095 | 506 | module_exit(qcom_cpufreq_exit); |
5ad7346b | 507 | |
7d127095 | 508 | MODULE_DESCRIPTION("Qualcomm Technologies, Inc. CPUfreq driver"); |
46e2856b | 509 | MODULE_LICENSE("GPL v2"); |