Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Allwinner CPUFreq nvmem based driver
0004  *
0005  * The sun50i-cpufreq-nvmem driver reads the efuse value from the SoC to
0006  * provide the OPP framework with required information.
0007  *
0008  * Copyright (C) 2019 Yangtao Li <tiny.windzz@gmail.com>
0009  */
0010 
0011 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0012 
0013 #include <linux/module.h>
0014 #include <linux/nvmem-consumer.h>
0015 #include <linux/of_device.h>
0016 #include <linux/platform_device.h>
0017 #include <linux/pm_opp.h>
0018 #include <linux/slab.h>
0019 
0020 #define MAX_NAME_LEN    7
0021 
0022 #define NVMEM_MASK  0x7
0023 #define NVMEM_SHIFT 5
0024 
0025 static struct platform_device *cpufreq_dt_pdev, *sun50i_cpufreq_pdev;
0026 
0027 /**
0028  * sun50i_cpufreq_get_efuse() - Determine speed grade from efuse value
0029  * @versions: Set to the value parsed from efuse
0030  *
0031  * Returns 0 if success.
0032  */
0033 static int sun50i_cpufreq_get_efuse(u32 *versions)
0034 {
0035     struct nvmem_cell *speedbin_nvmem;
0036     struct device_node *np;
0037     struct device *cpu_dev;
0038     u32 *speedbin, efuse_value;
0039     size_t len;
0040     int ret;
0041 
0042     cpu_dev = get_cpu_device(0);
0043     if (!cpu_dev)
0044         return -ENODEV;
0045 
0046     np = dev_pm_opp_of_get_opp_desc_node(cpu_dev);
0047     if (!np)
0048         return -ENOENT;
0049 
0050     ret = of_device_is_compatible(np,
0051                       "allwinner,sun50i-h6-operating-points");
0052     if (!ret) {
0053         of_node_put(np);
0054         return -ENOENT;
0055     }
0056 
0057     speedbin_nvmem = of_nvmem_cell_get(np, NULL);
0058     of_node_put(np);
0059     if (IS_ERR(speedbin_nvmem)) {
0060         if (PTR_ERR(speedbin_nvmem) != -EPROBE_DEFER)
0061             pr_err("Could not get nvmem cell: %ld\n",
0062                    PTR_ERR(speedbin_nvmem));
0063         return PTR_ERR(speedbin_nvmem);
0064     }
0065 
0066     speedbin = nvmem_cell_read(speedbin_nvmem, &len);
0067     nvmem_cell_put(speedbin_nvmem);
0068     if (IS_ERR(speedbin))
0069         return PTR_ERR(speedbin);
0070 
0071     efuse_value = (*speedbin >> NVMEM_SHIFT) & NVMEM_MASK;
0072 
0073     /*
0074      * We treat unexpected efuse values as if the SoC was from
0075      * the slowest bin. Expected efuse values are 1-3, slowest
0076      * to fastest.
0077      */
0078     if (efuse_value >= 1 && efuse_value <= 3)
0079         *versions = efuse_value - 1;
0080     else
0081         *versions = 0;
0082 
0083     kfree(speedbin);
0084     return 0;
0085 };
0086 
0087 static int sun50i_cpufreq_nvmem_probe(struct platform_device *pdev)
0088 {
0089     int *opp_tokens;
0090     char name[MAX_NAME_LEN];
0091     unsigned int cpu;
0092     u32 speed = 0;
0093     int ret;
0094 
0095     opp_tokens = kcalloc(num_possible_cpus(), sizeof(*opp_tokens),
0096                  GFP_KERNEL);
0097     if (!opp_tokens)
0098         return -ENOMEM;
0099 
0100     ret = sun50i_cpufreq_get_efuse(&speed);
0101     if (ret) {
0102         kfree(opp_tokens);
0103         return ret;
0104     }
0105 
0106     snprintf(name, MAX_NAME_LEN, "speed%d", speed);
0107 
0108     for_each_possible_cpu(cpu) {
0109         struct device *cpu_dev = get_cpu_device(cpu);
0110 
0111         if (!cpu_dev) {
0112             ret = -ENODEV;
0113             goto free_opp;
0114         }
0115 
0116         opp_tokens[cpu] = dev_pm_opp_set_prop_name(cpu_dev, name);
0117         if (opp_tokens[cpu] < 0) {
0118             ret = opp_tokens[cpu];
0119             pr_err("Failed to set prop name\n");
0120             goto free_opp;
0121         }
0122     }
0123 
0124     cpufreq_dt_pdev = platform_device_register_simple("cpufreq-dt", -1,
0125                               NULL, 0);
0126     if (!IS_ERR(cpufreq_dt_pdev)) {
0127         platform_set_drvdata(pdev, opp_tokens);
0128         return 0;
0129     }
0130 
0131     ret = PTR_ERR(cpufreq_dt_pdev);
0132     pr_err("Failed to register platform device\n");
0133 
0134 free_opp:
0135     for_each_possible_cpu(cpu)
0136         dev_pm_opp_put_prop_name(opp_tokens[cpu]);
0137     kfree(opp_tokens);
0138 
0139     return ret;
0140 }
0141 
0142 static int sun50i_cpufreq_nvmem_remove(struct platform_device *pdev)
0143 {
0144     int *opp_tokens = platform_get_drvdata(pdev);
0145     unsigned int cpu;
0146 
0147     platform_device_unregister(cpufreq_dt_pdev);
0148 
0149     for_each_possible_cpu(cpu)
0150         dev_pm_opp_put_prop_name(opp_tokens[cpu]);
0151 
0152     kfree(opp_tokens);
0153 
0154     return 0;
0155 }
0156 
0157 static struct platform_driver sun50i_cpufreq_driver = {
0158     .probe = sun50i_cpufreq_nvmem_probe,
0159     .remove = sun50i_cpufreq_nvmem_remove,
0160     .driver = {
0161         .name = "sun50i-cpufreq-nvmem",
0162     },
0163 };
0164 
0165 static const struct of_device_id sun50i_cpufreq_match_list[] = {
0166     { .compatible = "allwinner,sun50i-h6" },
0167     {}
0168 };
0169 MODULE_DEVICE_TABLE(of, sun50i_cpufreq_match_list);
0170 
0171 static const struct of_device_id *sun50i_cpufreq_match_node(void)
0172 {
0173     const struct of_device_id *match;
0174     struct device_node *np;
0175 
0176     np = of_find_node_by_path("/");
0177     match = of_match_node(sun50i_cpufreq_match_list, np);
0178     of_node_put(np);
0179 
0180     return match;
0181 }
0182 
0183 /*
0184  * Since the driver depends on nvmem drivers, which may return EPROBE_DEFER,
0185  * all the real activity is done in the probe, which may be defered as well.
0186  * The init here is only registering the driver and the platform device.
0187  */
0188 static int __init sun50i_cpufreq_init(void)
0189 {
0190     const struct of_device_id *match;
0191     int ret;
0192 
0193     match = sun50i_cpufreq_match_node();
0194     if (!match)
0195         return -ENODEV;
0196 
0197     ret = platform_driver_register(&sun50i_cpufreq_driver);
0198     if (unlikely(ret < 0))
0199         return ret;
0200 
0201     sun50i_cpufreq_pdev =
0202         platform_device_register_simple("sun50i-cpufreq-nvmem",
0203                         -1, NULL, 0);
0204     ret = PTR_ERR_OR_ZERO(sun50i_cpufreq_pdev);
0205     if (ret == 0)
0206         return 0;
0207 
0208     platform_driver_unregister(&sun50i_cpufreq_driver);
0209     return ret;
0210 }
0211 module_init(sun50i_cpufreq_init);
0212 
0213 static void __exit sun50i_cpufreq_exit(void)
0214 {
0215     platform_device_unregister(sun50i_cpufreq_pdev);
0216     platform_driver_unregister(&sun50i_cpufreq_driver);
0217 }
0218 module_exit(sun50i_cpufreq_exit);
0219 
0220 MODULE_DESCRIPTION("Sun50i-h6 cpufreq driver");
0221 MODULE_LICENSE("GPL v2");