root/drivers/cpufreq/sun50i-cpufreq-nvmem.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. sun50i_cpufreq_get_efuse
  2. sun50i_cpufreq_nvmem_probe
  3. sun50i_cpufreq_nvmem_remove
  4. sun50i_cpufreq_match_node
  5. sun50i_cpufreq_init
  6. sun50i_cpufreq_exit

   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 
  25 static 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  */
  33 static 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 
  87 static 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                 return ret;
 103 
 104         snprintf(name, MAX_NAME_LEN, "speed%d", speed);
 105 
 106         for_each_possible_cpu(cpu) {
 107                 struct device *cpu_dev = get_cpu_device(cpu);
 108 
 109                 if (!cpu_dev) {
 110                         ret = -ENODEV;
 111                         goto free_opp;
 112                 }
 113 
 114                 opp_tables[cpu] = dev_pm_opp_set_prop_name(cpu_dev, name);
 115                 if (IS_ERR(opp_tables[cpu])) {
 116                         ret = PTR_ERR(opp_tables[cpu]);
 117                         pr_err("Failed to set prop name\n");
 118                         goto free_opp;
 119                 }
 120         }
 121 
 122         cpufreq_dt_pdev = platform_device_register_simple("cpufreq-dt", -1,
 123                                                           NULL, 0);
 124         if (!IS_ERR(cpufreq_dt_pdev)) {
 125                 platform_set_drvdata(pdev, opp_tables);
 126                 return 0;
 127         }
 128 
 129         ret = PTR_ERR(cpufreq_dt_pdev);
 130         pr_err("Failed to register platform device\n");
 131 
 132 free_opp:
 133         for_each_possible_cpu(cpu) {
 134                 if (IS_ERR_OR_NULL(opp_tables[cpu]))
 135                         break;
 136                 dev_pm_opp_put_prop_name(opp_tables[cpu]);
 137         }
 138         kfree(opp_tables);
 139 
 140         return ret;
 141 }
 142 
 143 static int sun50i_cpufreq_nvmem_remove(struct platform_device *pdev)
 144 {
 145         struct opp_table **opp_tables = platform_get_drvdata(pdev);
 146         unsigned int cpu;
 147 
 148         platform_device_unregister(cpufreq_dt_pdev);
 149 
 150         for_each_possible_cpu(cpu)
 151                 dev_pm_opp_put_prop_name(opp_tables[cpu]);
 152 
 153         kfree(opp_tables);
 154 
 155         return 0;
 156 }
 157 
 158 static struct platform_driver sun50i_cpufreq_driver = {
 159         .probe = sun50i_cpufreq_nvmem_probe,
 160         .remove = sun50i_cpufreq_nvmem_remove,
 161         .driver = {
 162                 .name = "sun50i-cpufreq-nvmem",
 163         },
 164 };
 165 
 166 static const struct of_device_id sun50i_cpufreq_match_list[] = {
 167         { .compatible = "allwinner,sun50i-h6" },
 168         {}
 169 };
 170 
 171 static const struct of_device_id *sun50i_cpufreq_match_node(void)
 172 {
 173         const struct of_device_id *match;
 174         struct device_node *np;
 175 
 176         np = of_find_node_by_path("/");
 177         match = of_match_node(sun50i_cpufreq_match_list, np);
 178         of_node_put(np);
 179 
 180         return match;
 181 }
 182 
 183 /*
 184  * Since the driver depends on nvmem drivers, which may return EPROBE_DEFER,
 185  * all the real activity is done in the probe, which may be defered as well.
 186  * The init here is only registering the driver and the platform device.
 187  */
 188 static int __init sun50i_cpufreq_init(void)
 189 {
 190         const struct of_device_id *match;
 191         int ret;
 192 
 193         match = sun50i_cpufreq_match_node();
 194         if (!match)
 195                 return -ENODEV;
 196 
 197         ret = platform_driver_register(&sun50i_cpufreq_driver);
 198         if (unlikely(ret < 0))
 199                 return ret;
 200 
 201         sun50i_cpufreq_pdev =
 202                 platform_device_register_simple("sun50i-cpufreq-nvmem",
 203                                                 -1, NULL, 0);
 204         ret = PTR_ERR_OR_ZERO(sun50i_cpufreq_pdev);
 205         if (ret == 0)
 206                 return 0;
 207 
 208         platform_driver_unregister(&sun50i_cpufreq_driver);
 209         return ret;
 210 }
 211 module_init(sun50i_cpufreq_init);
 212 
 213 static void __exit sun50i_cpufreq_exit(void)
 214 {
 215         platform_device_unregister(sun50i_cpufreq_pdev);
 216         platform_driver_unregister(&sun50i_cpufreq_driver);
 217 }
 218 module_exit(sun50i_cpufreq_exit);
 219 
 220 MODULE_DESCRIPTION("Sun50i-h6 cpufreq driver");
 221 MODULE_LICENSE("GPL v2");

/* [<][>][^][v][top][bottom][index][help] */