Linux Audio

Check our new training course

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