cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

sun50i-cpufreq-nvmem.c (5153B)


      1// SPDX-License-Identifier: GPL-2.0
      2/*
      3 * Allwinner CPUFreq nvmem based driver
      4 *
      5 * The sun50i-cpufreq-nvmem driver reads the efuse value from the SoC to
      6 * provide the OPP framework with required information.
      7 *
      8 * Copyright (C) 2019 Yangtao Li <tiny.windzz@gmail.com>
      9 */
     10
     11#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
     12
     13#include <linux/module.h>
     14#include <linux/nvmem-consumer.h>
     15#include <linux/of_device.h>
     16#include <linux/platform_device.h>
     17#include <linux/pm_opp.h>
     18#include <linux/slab.h>
     19
     20#define MAX_NAME_LEN	7
     21
     22#define NVMEM_MASK	0x7
     23#define NVMEM_SHIFT	5
     24
     25static struct platform_device *cpufreq_dt_pdev, *sun50i_cpufreq_pdev;
     26
     27/**
     28 * sun50i_cpufreq_get_efuse() - Determine speed grade from efuse value
     29 * @versions: Set to the value parsed from efuse
     30 *
     31 * Returns 0 if success.
     32 */
     33static int sun50i_cpufreq_get_efuse(u32 *versions)
     34{
     35	struct nvmem_cell *speedbin_nvmem;
     36	struct device_node *np;
     37	struct device *cpu_dev;
     38	u32 *speedbin, efuse_value;
     39	size_t len;
     40	int ret;
     41
     42	cpu_dev = get_cpu_device(0);
     43	if (!cpu_dev)
     44		return -ENODEV;
     45
     46	np = dev_pm_opp_of_get_opp_desc_node(cpu_dev);
     47	if (!np)
     48		return -ENOENT;
     49
     50	ret = of_device_is_compatible(np,
     51				      "allwinner,sun50i-h6-operating-points");
     52	if (!ret) {
     53		of_node_put(np);
     54		return -ENOENT;
     55	}
     56
     57	speedbin_nvmem = of_nvmem_cell_get(np, NULL);
     58	of_node_put(np);
     59	if (IS_ERR(speedbin_nvmem)) {
     60		if (PTR_ERR(speedbin_nvmem) != -EPROBE_DEFER)
     61			pr_err("Could not get nvmem cell: %ld\n",
     62			       PTR_ERR(speedbin_nvmem));
     63		return PTR_ERR(speedbin_nvmem);
     64	}
     65
     66	speedbin = nvmem_cell_read(speedbin_nvmem, &len);
     67	nvmem_cell_put(speedbin_nvmem);
     68	if (IS_ERR(speedbin))
     69		return PTR_ERR(speedbin);
     70
     71	efuse_value = (*speedbin >> NVMEM_SHIFT) & NVMEM_MASK;
     72
     73	/*
     74	 * We treat unexpected efuse values as if the SoC was from
     75	 * the slowest bin. Expected efuse values are 1-3, slowest
     76	 * to fastest.
     77	 */
     78	if (efuse_value >= 1 && efuse_value <= 3)
     79		*versions = efuse_value - 1;
     80	else
     81		*versions = 0;
     82
     83	kfree(speedbin);
     84	return 0;
     85};
     86
     87static int sun50i_cpufreq_nvmem_probe(struct platform_device *pdev)
     88{
     89	struct opp_table **opp_tables;
     90	char name[MAX_NAME_LEN];
     91	unsigned int cpu;
     92	u32 speed = 0;
     93	int ret;
     94
     95	opp_tables = kcalloc(num_possible_cpus(), sizeof(*opp_tables),
     96			     GFP_KERNEL);
     97	if (!opp_tables)
     98		return -ENOMEM;
     99
    100	ret = sun50i_cpufreq_get_efuse(&speed);
    101	if (ret) {
    102		kfree(opp_tables);
    103		return ret;
    104	}
    105
    106	snprintf(name, MAX_NAME_LEN, "speed%d", speed);
    107
    108	for_each_possible_cpu(cpu) {
    109		struct device *cpu_dev = get_cpu_device(cpu);
    110
    111		if (!cpu_dev) {
    112			ret = -ENODEV;
    113			goto free_opp;
    114		}
    115
    116		opp_tables[cpu] = dev_pm_opp_set_prop_name(cpu_dev, name);
    117		if (IS_ERR(opp_tables[cpu])) {
    118			ret = PTR_ERR(opp_tables[cpu]);
    119			pr_err("Failed to set prop name\n");
    120			goto free_opp;
    121		}
    122	}
    123
    124	cpufreq_dt_pdev = platform_device_register_simple("cpufreq-dt", -1,
    125							  NULL, 0);
    126	if (!IS_ERR(cpufreq_dt_pdev)) {
    127		platform_set_drvdata(pdev, opp_tables);
    128		return 0;
    129	}
    130
    131	ret = PTR_ERR(cpufreq_dt_pdev);
    132	pr_err("Failed to register platform device\n");
    133
    134free_opp:
    135	for_each_possible_cpu(cpu) {
    136		if (IS_ERR_OR_NULL(opp_tables[cpu]))
    137			break;
    138		dev_pm_opp_put_prop_name(opp_tables[cpu]);
    139	}
    140	kfree(opp_tables);
    141
    142	return ret;
    143}
    144
    145static int sun50i_cpufreq_nvmem_remove(struct platform_device *pdev)
    146{
    147	struct opp_table **opp_tables = platform_get_drvdata(pdev);
    148	unsigned int cpu;
    149
    150	platform_device_unregister(cpufreq_dt_pdev);
    151
    152	for_each_possible_cpu(cpu)
    153		dev_pm_opp_put_prop_name(opp_tables[cpu]);
    154
    155	kfree(opp_tables);
    156
    157	return 0;
    158}
    159
    160static struct platform_driver sun50i_cpufreq_driver = {
    161	.probe = sun50i_cpufreq_nvmem_probe,
    162	.remove = sun50i_cpufreq_nvmem_remove,
    163	.driver = {
    164		.name = "sun50i-cpufreq-nvmem",
    165	},
    166};
    167
    168static const struct of_device_id sun50i_cpufreq_match_list[] = {
    169	{ .compatible = "allwinner,sun50i-h6" },
    170	{}
    171};
    172MODULE_DEVICE_TABLE(of, sun50i_cpufreq_match_list);
    173
    174static const struct of_device_id *sun50i_cpufreq_match_node(void)
    175{
    176	const struct of_device_id *match;
    177	struct device_node *np;
    178
    179	np = of_find_node_by_path("/");
    180	match = of_match_node(sun50i_cpufreq_match_list, np);
    181	of_node_put(np);
    182
    183	return match;
    184}
    185
    186/*
    187 * Since the driver depends on nvmem drivers, which may return EPROBE_DEFER,
    188 * all the real activity is done in the probe, which may be defered as well.
    189 * The init here is only registering the driver and the platform device.
    190 */
    191static int __init sun50i_cpufreq_init(void)
    192{
    193	const struct of_device_id *match;
    194	int ret;
    195
    196	match = sun50i_cpufreq_match_node();
    197	if (!match)
    198		return -ENODEV;
    199
    200	ret = platform_driver_register(&sun50i_cpufreq_driver);
    201	if (unlikely(ret < 0))
    202		return ret;
    203
    204	sun50i_cpufreq_pdev =
    205		platform_device_register_simple("sun50i-cpufreq-nvmem",
    206						-1, NULL, 0);
    207	ret = PTR_ERR_OR_ZERO(sun50i_cpufreq_pdev);
    208	if (ret == 0)
    209		return 0;
    210
    211	platform_driver_unregister(&sun50i_cpufreq_driver);
    212	return ret;
    213}
    214module_init(sun50i_cpufreq_init);
    215
    216static void __exit sun50i_cpufreq_exit(void)
    217{
    218	platform_device_unregister(sun50i_cpufreq_pdev);
    219	platform_driver_unregister(&sun50i_cpufreq_driver);
    220}
    221module_exit(sun50i_cpufreq_exit);
    222
    223MODULE_DESCRIPTION("Sun50i-h6 cpufreq driver");
    224MODULE_LICENSE("GPL v2");