diff options
Diffstat (limited to 'drivers/cpufreq/qcom-cpufreq-nvmem.c')
-rw-r--r-- | drivers/cpufreq/qcom-cpufreq-nvmem.c | 352 |
1 files changed, 352 insertions, 0 deletions
diff --git a/drivers/cpufreq/qcom-cpufreq-nvmem.c b/drivers/cpufreq/qcom-cpufreq-nvmem.c new file mode 100644 index 000000000000..f0d2d5035413 --- /dev/null +++ b/drivers/cpufreq/qcom-cpufreq-nvmem.c | |||
@@ -0,0 +1,352 @@ | |||
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/of_device.h> | ||
26 | #include <linux/platform_device.h> | ||
27 | #include <linux/pm_domain.h> | ||
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 | |||
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, | ||
52 | struct qcom_cpufreq_drv *drv); | ||
53 | const char **genpd_names; | ||
54 | }; | ||
55 | |||
56 | struct qcom_cpufreq_drv { | ||
57 | struct opp_table **opp_tables; | ||
58 | struct opp_table **genpd_opp_tables; | ||
59 | u32 versions; | ||
60 | const struct qcom_cpufreq_match_data *data; | ||
61 | }; | ||
62 | |||
63 | static struct platform_device *cpufreq_dt_pdev, *cpufreq_pdev; | ||
64 | |||
65 | static enum _msm8996_version qcom_cpufreq_get_msm_id(void) | ||
66 | { | ||
67 | size_t len; | ||
68 | u32 *msm_id; | ||
69 | enum _msm8996_version version; | ||
70 | |||
71 | msm_id = qcom_smem_get(QCOM_SMEM_HOST_ANY, MSM_ID_SMEM, &len); | ||
72 | if (IS_ERR(msm_id)) | ||
73 | return NUM_OF_MSM8996_VERSIONS; | ||
74 | |||
75 | /* The first 4 bytes are format, next to them is the actual msm-id */ | ||
76 | msm_id++; | ||
77 | |||
78 | switch ((enum _msm_id)*msm_id) { | ||
79 | case MSM8996V3: | ||
80 | case APQ8096V3: | ||
81 | version = MSM8996_V3; | ||
82 | break; | ||
83 | case MSM8996SG: | ||
84 | case APQ8096SG: | ||
85 | version = MSM8996_SG; | ||
86 | break; | ||
87 | default: | ||
88 | version = NUM_OF_MSM8996_VERSIONS; | ||
89 | } | ||
90 | |||
91 | return version; | ||
92 | } | ||
93 | |||
94 | static int qcom_cpufreq_kryo_name_version(struct device *cpu_dev, | ||
95 | struct nvmem_cell *speedbin_nvmem, | ||
96 | struct qcom_cpufreq_drv *drv) | ||
97 | { | ||
98 | size_t len; | ||
99 | u8 *speedbin; | ||
100 | enum _msm8996_version msm8996_version; | ||
101 | |||
102 | msm8996_version = qcom_cpufreq_get_msm_id(); | ||
103 | if (NUM_OF_MSM8996_VERSIONS == msm8996_version) { | ||
104 | dev_err(cpu_dev, "Not Snapdragon 820/821!"); | ||
105 | return -ENODEV; | ||
106 | } | ||
107 | |||
108 | speedbin = nvmem_cell_read(speedbin_nvmem, &len); | ||
109 | if (IS_ERR(speedbin)) | ||
110 | return PTR_ERR(speedbin); | ||
111 | |||
112 | switch (msm8996_version) { | ||
113 | case MSM8996_V3: | ||
114 | drv->versions = 1 << (unsigned int)(*speedbin); | ||
115 | break; | ||
116 | case MSM8996_SG: | ||
117 | drv->versions = 1 << ((unsigned int)(*speedbin) + 4); | ||
118 | break; | ||
119 | default: | ||
120 | BUG(); | ||
121 | break; | ||
122 | } | ||
123 | |||
124 | kfree(speedbin); | ||
125 | return 0; | ||
126 | } | ||
127 | |||
128 | static const struct qcom_cpufreq_match_data match_data_kryo = { | ||
129 | .get_version = qcom_cpufreq_kryo_name_version, | ||
130 | }; | ||
131 | |||
132 | static const char *qcs404_genpd_names[] = { "cpr", NULL }; | ||
133 | |||
134 | static const struct qcom_cpufreq_match_data match_data_qcs404 = { | ||
135 | .genpd_names = qcs404_genpd_names, | ||
136 | }; | ||
137 | |||
138 | static int qcom_cpufreq_probe(struct platform_device *pdev) | ||
139 | { | ||
140 | struct qcom_cpufreq_drv *drv; | ||
141 | struct nvmem_cell *speedbin_nvmem; | ||
142 | struct device_node *np; | ||
143 | struct device *cpu_dev; | ||
144 | unsigned cpu; | ||
145 | const struct of_device_id *match; | ||
146 | int ret; | ||
147 | |||
148 | cpu_dev = get_cpu_device(0); | ||
149 | if (!cpu_dev) | ||
150 | return -ENODEV; | ||
151 | |||
152 | np = dev_pm_opp_of_get_opp_desc_node(cpu_dev); | ||
153 | if (!np) | ||
154 | return -ENOENT; | ||
155 | |||
156 | ret = of_device_is_compatible(np, "operating-points-v2-kryo-cpu"); | ||
157 | if (!ret) { | ||
158 | of_node_put(np); | ||
159 | return -ENOENT; | ||
160 | } | ||
161 | |||
162 | drv = kzalloc(sizeof(*drv), GFP_KERNEL); | ||
163 | if (!drv) | ||
164 | return -ENOMEM; | ||
165 | |||
166 | match = pdev->dev.platform_data; | ||
167 | drv->data = match->data; | ||
168 | if (!drv->data) { | ||
169 | ret = -ENODEV; | ||
170 | goto free_drv; | ||
171 | } | ||
172 | |||
173 | if (drv->data->get_version) { | ||
174 | speedbin_nvmem = of_nvmem_cell_get(np, NULL); | ||
175 | if (IS_ERR(speedbin_nvmem)) { | ||
176 | if (PTR_ERR(speedbin_nvmem) != -EPROBE_DEFER) | ||
177 | dev_err(cpu_dev, | ||
178 | "Could not get nvmem cell: %ld\n", | ||
179 | PTR_ERR(speedbin_nvmem)); | ||
180 | ret = PTR_ERR(speedbin_nvmem); | ||
181 | goto free_drv; | ||
182 | } | ||
183 | |||
184 | ret = drv->data->get_version(cpu_dev, speedbin_nvmem, drv); | ||
185 | if (ret) { | ||
186 | nvmem_cell_put(speedbin_nvmem); | ||
187 | goto free_drv; | ||
188 | } | ||
189 | nvmem_cell_put(speedbin_nvmem); | ||
190 | } | ||
191 | of_node_put(np); | ||
192 | |||
193 | drv->opp_tables = kcalloc(num_possible_cpus(), sizeof(*drv->opp_tables), | ||
194 | GFP_KERNEL); | ||
195 | if (!drv->opp_tables) { | ||
196 | ret = -ENOMEM; | ||
197 | goto free_drv; | ||
198 | } | ||
199 | |||
200 | drv->genpd_opp_tables = kcalloc(num_possible_cpus(), | ||
201 | sizeof(*drv->genpd_opp_tables), | ||
202 | GFP_KERNEL); | ||
203 | if (!drv->genpd_opp_tables) { | ||
204 | ret = -ENOMEM; | ||
205 | goto free_opp; | ||
206 | } | ||
207 | |||
208 | for_each_possible_cpu(cpu) { | ||
209 | cpu_dev = get_cpu_device(cpu); | ||
210 | if (NULL == cpu_dev) { | ||
211 | ret = -ENODEV; | ||
212 | goto free_genpd_opp; | ||
213 | } | ||
214 | |||
215 | if (drv->data->get_version) { | ||
216 | drv->opp_tables[cpu] = | ||
217 | dev_pm_opp_set_supported_hw(cpu_dev, | ||
218 | &drv->versions, 1); | ||
219 | if (IS_ERR(drv->opp_tables[cpu])) { | ||
220 | ret = PTR_ERR(drv->opp_tables[cpu]); | ||
221 | dev_err(cpu_dev, | ||
222 | "Failed to set supported hardware\n"); | ||
223 | goto free_genpd_opp; | ||
224 | } | ||
225 | } | ||
226 | |||
227 | if (drv->data->genpd_names) { | ||
228 | drv->genpd_opp_tables[cpu] = | ||
229 | dev_pm_opp_attach_genpd(cpu_dev, | ||
230 | drv->data->genpd_names, | ||
231 | NULL); | ||
232 | if (IS_ERR(drv->genpd_opp_tables[cpu])) { | ||
233 | ret = PTR_ERR(drv->genpd_opp_tables[cpu]); | ||
234 | if (ret != -EPROBE_DEFER) | ||
235 | dev_err(cpu_dev, | ||
236 | "Could not attach to pm_domain: %d\n", | ||
237 | ret); | ||
238 | goto free_genpd_opp; | ||
239 | } | ||
240 | } | ||
241 | } | ||
242 | |||
243 | cpufreq_dt_pdev = platform_device_register_simple("cpufreq-dt", -1, | ||
244 | NULL, 0); | ||
245 | if (!IS_ERR(cpufreq_dt_pdev)) { | ||
246 | platform_set_drvdata(pdev, drv); | ||
247 | return 0; | ||
248 | } | ||
249 | |||
250 | ret = PTR_ERR(cpufreq_dt_pdev); | ||
251 | dev_err(cpu_dev, "Failed to register platform device\n"); | ||
252 | |||
253 | free_genpd_opp: | ||
254 | for_each_possible_cpu(cpu) { | ||
255 | if (IS_ERR_OR_NULL(drv->genpd_opp_tables[cpu])) | ||
256 | break; | ||
257 | dev_pm_opp_detach_genpd(drv->genpd_opp_tables[cpu]); | ||
258 | } | ||
259 | kfree(drv->genpd_opp_tables); | ||
260 | free_opp: | ||
261 | for_each_possible_cpu(cpu) { | ||
262 | if (IS_ERR_OR_NULL(drv->opp_tables[cpu])) | ||
263 | break; | ||
264 | dev_pm_opp_put_supported_hw(drv->opp_tables[cpu]); | ||
265 | } | ||
266 | kfree(drv->opp_tables); | ||
267 | free_drv: | ||
268 | kfree(drv); | ||
269 | |||
270 | return ret; | ||
271 | } | ||
272 | |||
273 | static int qcom_cpufreq_remove(struct platform_device *pdev) | ||
274 | { | ||
275 | struct qcom_cpufreq_drv *drv = platform_get_drvdata(pdev); | ||
276 | unsigned int cpu; | ||
277 | |||
278 | platform_device_unregister(cpufreq_dt_pdev); | ||
279 | |||
280 | for_each_possible_cpu(cpu) { | ||
281 | if (drv->opp_tables[cpu]) | ||
282 | dev_pm_opp_put_supported_hw(drv->opp_tables[cpu]); | ||
283 | if (drv->genpd_opp_tables[cpu]) | ||
284 | dev_pm_opp_detach_genpd(drv->genpd_opp_tables[cpu]); | ||
285 | } | ||
286 | |||
287 | kfree(drv->opp_tables); | ||
288 | kfree(drv->genpd_opp_tables); | ||
289 | kfree(drv); | ||
290 | |||
291 | return 0; | ||
292 | } | ||
293 | |||
294 | static struct platform_driver qcom_cpufreq_driver = { | ||
295 | .probe = qcom_cpufreq_probe, | ||
296 | .remove = qcom_cpufreq_remove, | ||
297 | .driver = { | ||
298 | .name = "qcom-cpufreq-nvmem", | ||
299 | }, | ||
300 | }; | ||
301 | |||
302 | static const struct of_device_id qcom_cpufreq_match_list[] __initconst = { | ||
303 | { .compatible = "qcom,apq8096", .data = &match_data_kryo }, | ||
304 | { .compatible = "qcom,msm8996", .data = &match_data_kryo }, | ||
305 | { .compatible = "qcom,qcs404", .data = &match_data_qcs404 }, | ||
306 | {}, | ||
307 | }; | ||
308 | |||
309 | /* | ||
310 | * Since the driver depends on smem and nvmem drivers, which may | ||
311 | * return EPROBE_DEFER, all the real activity is done in the probe, | ||
312 | * which may be defered as well. The init here is only registering | ||
313 | * the driver and the platform device. | ||
314 | */ | ||
315 | static int __init qcom_cpufreq_init(void) | ||
316 | { | ||
317 | struct device_node *np = of_find_node_by_path("/"); | ||
318 | const struct of_device_id *match; | ||
319 | int ret; | ||
320 | |||
321 | if (!np) | ||
322 | return -ENODEV; | ||
323 | |||
324 | match = of_match_node(qcom_cpufreq_match_list, np); | ||
325 | of_node_put(np); | ||
326 | if (!match) | ||
327 | return -ENODEV; | ||
328 | |||
329 | ret = platform_driver_register(&qcom_cpufreq_driver); | ||
330 | if (unlikely(ret < 0)) | ||
331 | return ret; | ||
332 | |||
333 | cpufreq_pdev = platform_device_register_data(NULL, "qcom-cpufreq-nvmem", | ||
334 | -1, match, sizeof(*match)); | ||
335 | ret = PTR_ERR_OR_ZERO(cpufreq_pdev); | ||
336 | if (0 == ret) | ||
337 | return 0; | ||
338 | |||
339 | platform_driver_unregister(&qcom_cpufreq_driver); | ||
340 | return ret; | ||
341 | } | ||
342 | module_init(qcom_cpufreq_init); | ||
343 | |||
344 | static void __exit qcom_cpufreq_exit(void) | ||
345 | { | ||
346 | platform_device_unregister(cpufreq_pdev); | ||
347 | platform_driver_unregister(&qcom_cpufreq_driver); | ||
348 | } | ||
349 | module_exit(qcom_cpufreq_exit); | ||
350 | |||
351 | MODULE_DESCRIPTION("Qualcomm Technologies, Inc. CPUfreq driver"); | ||
352 | MODULE_LICENSE("GPL v2"); | ||