qcom-cpufreq-nvmem.c 10.6 KB
Newer Older
I
Ilia Lin 已提交
1 2 3 4 5 6 7 8 9 10 11
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2018, The Linux Foundation. All rights reserved.
 */

/*
 * In Certain QCOM SoCs like apq8096 and msm8996 that have KRYO processors,
 * the CPU frequency subset and voltage value of each OPP varies
 * based on the silicon variant in use. Qualcomm Process Voltage Scaling Tables
 * defines the voltage and frequency value based on the msm-id in SMEM
 * and speedbin blown in the efuse combination.
12
 * The qcom-cpufreq-nvmem driver reads the msm-id and efuse value from the SoC
I
Ilia Lin 已提交
13 14 15 16 17 18 19 20 21 22 23 24
 * to provide the OPP framework with required information.
 * This is used to determine the voltage and frequency value for each OPP of
 * operating-points-v2 table when it is parsed by the OPP framework.
 */

#include <linux/cpu.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/nvmem-consumer.h>
#include <linux/of.h>
25
#include <linux/of_device.h>
I
Ilia Lin 已提交
26
#include <linux/platform_device.h>
27
#include <linux/pm_domain.h>
I
Ilia Lin 已提交
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
#include <linux/pm_opp.h>
#include <linux/slab.h>
#include <linux/soc/qcom/smem.h>

#define MSM_ID_SMEM	137

enum _msm_id {
	MSM8996V3 = 0xF6ul,
	APQ8096V3 = 0x123ul,
	MSM8996SG = 0x131ul,
	APQ8096SG = 0x138ul,
};

enum _msm8996_version {
	MSM8996_V3,
	MSM8996_SG,
	NUM_OF_MSM8996_VERSIONS,
};

47 48 49 50 51
struct qcom_cpufreq_drv;

struct qcom_cpufreq_match_data {
	int (*get_version)(struct device *cpu_dev,
			   struct nvmem_cell *speedbin_nvmem,
52
			   char **pvs_name,
53
			   struct qcom_cpufreq_drv *drv);
54
	const char **genpd_names;
55 56 57
};

struct qcom_cpufreq_drv {
58
	int *opp_tokens;
59 60 61 62
	u32 versions;
	const struct qcom_cpufreq_match_data *data;
};

63
static struct platform_device *cpufreq_dt_pdev, *cpufreq_pdev;
64

65 66
static void get_krait_bin_format_a(struct device *cpu_dev,
					  int *speed, int *pvs, int *pvs_ver,
67
					  u8 *buf)
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
{
	u32 pte_efuse;

	pte_efuse = *((u32 *)buf);

	*speed = pte_efuse & 0xf;
	if (*speed == 0xf)
		*speed = (pte_efuse >> 4) & 0xf;

	if (*speed == 0xf) {
		*speed = 0;
		dev_warn(cpu_dev, "Speed bin: Defaulting to %d\n", *speed);
	} else {
		dev_dbg(cpu_dev, "Speed bin: %d\n", *speed);
	}

	*pvs = (pte_efuse >> 10) & 0x7;
	if (*pvs == 0x7)
		*pvs = (pte_efuse >> 13) & 0x7;

	if (*pvs == 0x7) {
		*pvs = 0;
		dev_warn(cpu_dev, "PVS bin: Defaulting to %d\n", *pvs);
	} else {
		dev_dbg(cpu_dev, "PVS bin: %d\n", *pvs);
	}
}

static void get_krait_bin_format_b(struct device *cpu_dev,
					  int *speed, int *pvs, int *pvs_ver,
98
					  u8 *buf)
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
{
	u32 pte_efuse, redundant_sel;

	pte_efuse = *((u32 *)buf);
	redundant_sel = (pte_efuse >> 24) & 0x7;

	*pvs_ver = (pte_efuse >> 4) & 0x3;

	switch (redundant_sel) {
	case 1:
		*pvs = ((pte_efuse >> 28) & 0x8) | ((pte_efuse >> 6) & 0x7);
		*speed = (pte_efuse >> 27) & 0xf;
		break;
	case 2:
		*pvs = (pte_efuse >> 27) & 0xf;
		*speed = pte_efuse & 0x7;
		break;
	default:
		/* 4 bits of PVS are in efuse register bits 31, 8-6. */
		*pvs = ((pte_efuse >> 28) & 0x8) | ((pte_efuse >> 6) & 0x7);
		*speed = pte_efuse & 0x7;
	}

	/* Check SPEED_BIN_BLOW_STATUS */
	if (pte_efuse & BIT(3)) {
		dev_dbg(cpu_dev, "Speed bin: %d\n", *speed);
	} else {
		dev_warn(cpu_dev, "Speed bin not set. Defaulting to 0!\n");
		*speed = 0;
	}

	/* Check PVS_BLOW_STATUS */
131
	pte_efuse = *(((u32 *)buf) + 1);
132 133 134 135 136 137 138 139 140 141 142
	pte_efuse &= BIT(21);
	if (pte_efuse) {
		dev_dbg(cpu_dev, "PVS bin: %d\n", *pvs);
	} else {
		dev_warn(cpu_dev, "PVS bin not set. Defaulting to 0!\n");
		*pvs = 0;
	}

	dev_dbg(cpu_dev, "PVS version: %d\n", *pvs_ver);
}

143
static enum _msm8996_version qcom_cpufreq_get_msm_id(void)
I
Ilia Lin 已提交
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
{
	size_t len;
	u32 *msm_id;
	enum _msm8996_version version;

	msm_id = qcom_smem_get(QCOM_SMEM_HOST_ANY, MSM_ID_SMEM, &len);
	if (IS_ERR(msm_id))
		return NUM_OF_MSM8996_VERSIONS;

	/* The first 4 bytes are format, next to them is the actual msm-id */
	msm_id++;

	switch ((enum _msm_id)*msm_id) {
	case MSM8996V3:
	case APQ8096V3:
		version = MSM8996_V3;
		break;
	case MSM8996SG:
	case APQ8096SG:
		version = MSM8996_SG;
		break;
	default:
		version = NUM_OF_MSM8996_VERSIONS;
	}

	return version;
}

172 173
static int qcom_cpufreq_kryo_name_version(struct device *cpu_dev,
					  struct nvmem_cell *speedbin_nvmem,
174
					  char **pvs_name,
175
					  struct qcom_cpufreq_drv *drv)
I
Ilia Lin 已提交
176
{
177 178
	size_t len;
	u8 *speedbin;
I
Ilia Lin 已提交
179
	enum _msm8996_version msm8996_version;
180
	*pvs_name = NULL;
181 182 183 184 185 186 187 188 189 190 191 192 193

	msm8996_version = qcom_cpufreq_get_msm_id();
	if (NUM_OF_MSM8996_VERSIONS == msm8996_version) {
		dev_err(cpu_dev, "Not Snapdragon 820/821!");
		return -ENODEV;
	}

	speedbin = nvmem_cell_read(speedbin_nvmem, &len);
	if (IS_ERR(speedbin))
		return PTR_ERR(speedbin);

	switch (msm8996_version) {
	case MSM8996_V3:
194
		drv->versions = 1 << (unsigned int)(*speedbin);
195 196
		break;
	case MSM8996_SG:
197
		drv->versions = 1 << ((unsigned int)(*speedbin) + 4);
198 199 200 201 202 203 204 205 206 207
		break;
	default:
		BUG();
		break;
	}

	kfree(speedbin);
	return 0;
}

208 209 210 211 212 213 214 215
static int qcom_cpufreq_krait_name_version(struct device *cpu_dev,
					   struct nvmem_cell *speedbin_nvmem,
					   char **pvs_name,
					   struct qcom_cpufreq_drv *drv)
{
	int speed = 0, pvs = 0, pvs_ver = 0;
	u8 *speedbin;
	size_t len;
216
	int ret = 0;
217 218 219 220 221 222 223 224 225

	speedbin = nvmem_cell_read(speedbin_nvmem, &len);

	if (IS_ERR(speedbin))
		return PTR_ERR(speedbin);

	switch (len) {
	case 4:
		get_krait_bin_format_a(cpu_dev, &speed, &pvs, &pvs_ver,
226
				       speedbin);
227 228 229
		break;
	case 8:
		get_krait_bin_format_b(cpu_dev, &speed, &pvs, &pvs_ver,
230
				       speedbin);
231 232 233
		break;
	default:
		dev_err(cpu_dev, "Unable to read nvmem data. Defaulting to 0!\n");
234 235
		ret = -ENODEV;
		goto len_error;
236 237 238 239 240 241 242
	}

	snprintf(*pvs_name, sizeof("speedXX-pvsXX-vXX"), "speed%d-pvs%d-v%d",
		 speed, pvs, pvs_ver);

	drv->versions = (1 << speed);

243
len_error:
244
	kfree(speedbin);
245
	return ret;
246 247
}

248 249 250 251
static const struct qcom_cpufreq_match_data match_data_kryo = {
	.get_version = qcom_cpufreq_kryo_name_version,
};

252 253 254 255
static const struct qcom_cpufreq_match_data match_data_krait = {
	.get_version = qcom_cpufreq_krait_name_version,
};

256 257 258 259 260 261
static const char *qcs404_genpd_names[] = { "cpr", NULL };

static const struct qcom_cpufreq_match_data match_data_qcs404 = {
	.genpd_names = qcs404_genpd_names,
};

262 263
static int qcom_cpufreq_probe(struct platform_device *pdev)
{
264
	struct qcom_cpufreq_drv *drv;
I
Ilia Lin 已提交
265 266 267
	struct nvmem_cell *speedbin_nvmem;
	struct device_node *np;
	struct device *cpu_dev;
268 269
	char pvs_name_buffer[] = "speedXX-pvsXX-vXX";
	char *pvs_name = pvs_name_buffer;
I
Ilia Lin 已提交
270
	unsigned cpu;
271
	const struct of_device_id *match;
I
Ilia Lin 已提交
272 273 274
	int ret;

	cpu_dev = get_cpu_device(0);
275 276
	if (!cpu_dev)
		return -ENODEV;
I
Ilia Lin 已提交
277 278

	np = dev_pm_opp_of_get_opp_desc_node(cpu_dev);
279 280
	if (!np)
		return -ENOENT;
I
Ilia Lin 已提交
281

282
	ret = of_device_is_compatible(np, "operating-points-v2-kryo-cpu");
I
Ilia Lin 已提交
283 284 285 286 287
	if (!ret) {
		of_node_put(np);
		return -ENOENT;
	}

288 289 290 291 292 293 294 295 296
	drv = kzalloc(sizeof(*drv), GFP_KERNEL);
	if (!drv)
		return -ENOMEM;

	match = pdev->dev.platform_data;
	drv->data = match->data;
	if (!drv->data) {
		ret = -ENODEV;
		goto free_drv;
I
Ilia Lin 已提交
297 298
	}

299 300 301
	if (drv->data->get_version) {
		speedbin_nvmem = of_nvmem_cell_get(np, NULL);
		if (IS_ERR(speedbin_nvmem)) {
302 303
			ret = dev_err_probe(cpu_dev, PTR_ERR(speedbin_nvmem),
					    "Could not get nvmem cell\n");
304 305
			goto free_drv;
		}
I
Ilia Lin 已提交
306

307 308
		ret = drv->data->get_version(cpu_dev,
							speedbin_nvmem, &pvs_name, drv);
309 310 311 312 313 314 315 316
		if (ret) {
			nvmem_cell_put(speedbin_nvmem);
			goto free_drv;
		}
		nvmem_cell_put(speedbin_nvmem);
	}
	of_node_put(np);

317
	drv->opp_tokens = kcalloc(num_possible_cpus(), sizeof(*drv->opp_tokens),
318
				  GFP_KERNEL);
319
	if (!drv->opp_tokens) {
320 321 322
		ret = -ENOMEM;
		goto free_drv;
	}
323

I
Ilia Lin 已提交
324
	for_each_possible_cpu(cpu) {
325 326 327 328
		struct dev_pm_opp_config config = {
			.supported_hw = NULL,
		};

I
Ilia Lin 已提交
329 330 331
		cpu_dev = get_cpu_device(cpu);
		if (NULL == cpu_dev) {
			ret = -ENODEV;
332
			goto free_opp;
I
Ilia Lin 已提交
333 334
		}

335
		if (drv->data->get_version) {
336 337
			config.supported_hw = &drv->versions;
			config.supported_hw_count = 1;
338

339 340
			if (pvs_name)
				config.prop_name = pvs_name;
341 342 343
		}

		if (drv->data->genpd_names) {
344 345 346 347 348 349 350 351 352 353
			config.genpd_names = drv->data->genpd_names;
			config.virt_devs = NULL;
		}

		if (config.supported_hw || config.genpd_names) {
			drv->opp_tokens[cpu] = dev_pm_opp_set_config(cpu_dev, &config);
			if (drv->opp_tokens[cpu] < 0) {
				ret = drv->opp_tokens[cpu];
				dev_err(cpu_dev, "Failed to set OPP config\n");
				goto free_opp;
354
			}
I
Ilia Lin 已提交
355 356 357 358 359
		}
	}

	cpufreq_dt_pdev = platform_device_register_simple("cpufreq-dt", -1,
							  NULL, 0);
360
	if (!IS_ERR(cpufreq_dt_pdev)) {
361
		platform_set_drvdata(pdev, drv);
I
Ilia Lin 已提交
362
		return 0;
363
	}
I
Ilia Lin 已提交
364 365 366 367 368

	ret = PTR_ERR(cpufreq_dt_pdev);
	dev_err(cpu_dev, "Failed to register platform device\n");

free_opp:
369 370 371
	for_each_possible_cpu(cpu)
		dev_pm_opp_clear_config(drv->opp_tokens[cpu]);
	kfree(drv->opp_tokens);
372 373
free_drv:
	kfree(drv);
I
Ilia Lin 已提交
374 375 376 377

	return ret;
}

378
static int qcom_cpufreq_remove(struct platform_device *pdev)
379
{
380
	struct qcom_cpufreq_drv *drv = platform_get_drvdata(pdev);
381 382
	unsigned int cpu;

383
	platform_device_unregister(cpufreq_dt_pdev);
384

385 386
	for_each_possible_cpu(cpu)
		dev_pm_opp_clear_config(drv->opp_tokens[cpu]);
387

388
	kfree(drv->opp_tokens);
389
	kfree(drv);
390

391 392 393
	return 0;
}

394 395 396
static struct platform_driver qcom_cpufreq_driver = {
	.probe = qcom_cpufreq_probe,
	.remove = qcom_cpufreq_remove,
I
Ilia Lin 已提交
397
	.driver = {
398
		.name = "qcom-cpufreq-nvmem",
I
Ilia Lin 已提交
399 400 401
	},
};

402
static const struct of_device_id qcom_cpufreq_match_list[] __initconst = {
403 404
	{ .compatible = "qcom,apq8096", .data = &match_data_kryo },
	{ .compatible = "qcom,msm8996", .data = &match_data_kryo },
405
	{ .compatible = "qcom,qcs404", .data = &match_data_qcs404 },
406 407 408 409
	{ .compatible = "qcom,ipq8064", .data = &match_data_krait },
	{ .compatible = "qcom,apq8064", .data = &match_data_krait },
	{ .compatible = "qcom,msm8974", .data = &match_data_krait },
	{ .compatible = "qcom,msm8960", .data = &match_data_krait },
410
	{},
I
Ilia Lin 已提交
411
};
412
MODULE_DEVICE_TABLE(of, qcom_cpufreq_match_list);
I
Ilia Lin 已提交
413 414 415 416 417 418 419

/*
 * Since the driver depends on smem and nvmem drivers, which may
 * return EPROBE_DEFER, all the real activity is done in the probe,
 * which may be defered as well. The init here is only registering
 * the driver and the platform device.
 */
420
static int __init qcom_cpufreq_init(void)
I
Ilia Lin 已提交
421 422 423 424 425 426 427 428
{
	struct device_node *np = of_find_node_by_path("/");
	const struct of_device_id *match;
	int ret;

	if (!np)
		return -ENODEV;

429
	match = of_match_node(qcom_cpufreq_match_list, np);
I
Ilia Lin 已提交
430 431 432 433
	of_node_put(np);
	if (!match)
		return -ENODEV;

434
	ret = platform_driver_register(&qcom_cpufreq_driver);
I
Ilia Lin 已提交
435 436 437
	if (unlikely(ret < 0))
		return ret;

438 439 440
	cpufreq_pdev = platform_device_register_data(NULL, "qcom-cpufreq-nvmem",
						     -1, match, sizeof(*match));
	ret = PTR_ERR_OR_ZERO(cpufreq_pdev);
I
Ilia Lin 已提交
441 442 443
	if (0 == ret)
		return 0;

444
	platform_driver_unregister(&qcom_cpufreq_driver);
I
Ilia Lin 已提交
445 446
	return ret;
}
447
module_init(qcom_cpufreq_init);
I
Ilia Lin 已提交
448

449
static void __exit qcom_cpufreq_exit(void)
450
{
451 452
	platform_device_unregister(cpufreq_pdev);
	platform_driver_unregister(&qcom_cpufreq_driver);
453
}
454
module_exit(qcom_cpufreq_exit);
455

456
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. CPUfreq driver");
I
Ilia Lin 已提交
457
MODULE_LICENSE("GPL v2");