Merge tag 'clk-fixes-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git...
[platform/kernel/linux-rpi.git] / drivers / cpufreq / sun50i-cpufreq-nvmem.c
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() - Parse and return efuse value present on SoC
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         switch (efuse_value) {
73         case 0b0001:
74                 *versions = 1;
75                 break;
76         case 0b0011:
77                 *versions = 2;
78                 break;
79         default:
80                 /*
81                  * For other situations, we treat it as bin0.
82                  * This vf table can be run for any good cpu.
83                  */
84                 *versions = 0;
85                 break;
86         }
87
88         kfree(speedbin);
89         return 0;
90 };
91
92 static int sun50i_cpufreq_nvmem_probe(struct platform_device *pdev)
93 {
94         struct opp_table **opp_tables;
95         char name[MAX_NAME_LEN];
96         unsigned int cpu;
97         u32 speed = 0;
98         int ret;
99
100         opp_tables = kcalloc(num_possible_cpus(), sizeof(*opp_tables),
101                              GFP_KERNEL);
102         if (!opp_tables)
103                 return -ENOMEM;
104
105         ret = sun50i_cpufreq_get_efuse(&speed);
106         if (ret)
107                 return ret;
108
109         snprintf(name, MAX_NAME_LEN, "speed%d", speed);
110
111         for_each_possible_cpu(cpu) {
112                 struct device *cpu_dev = get_cpu_device(cpu);
113
114                 if (!cpu_dev) {
115                         ret = -ENODEV;
116                         goto free_opp;
117                 }
118
119                 opp_tables[cpu] = dev_pm_opp_set_prop_name(cpu_dev, name);
120                 if (IS_ERR(opp_tables[cpu])) {
121                         ret = PTR_ERR(opp_tables[cpu]);
122                         pr_err("Failed to set prop name\n");
123                         goto free_opp;
124                 }
125         }
126
127         cpufreq_dt_pdev = platform_device_register_simple("cpufreq-dt", -1,
128                                                           NULL, 0);
129         if (!IS_ERR(cpufreq_dt_pdev)) {
130                 platform_set_drvdata(pdev, opp_tables);
131                 return 0;
132         }
133
134         ret = PTR_ERR(cpufreq_dt_pdev);
135         pr_err("Failed to register platform device\n");
136
137 free_opp:
138         for_each_possible_cpu(cpu) {
139                 if (IS_ERR_OR_NULL(opp_tables[cpu]))
140                         break;
141                 dev_pm_opp_put_prop_name(opp_tables[cpu]);
142         }
143         kfree(opp_tables);
144
145         return ret;
146 }
147
148 static int sun50i_cpufreq_nvmem_remove(struct platform_device *pdev)
149 {
150         struct opp_table **opp_tables = platform_get_drvdata(pdev);
151         unsigned int cpu;
152
153         platform_device_unregister(cpufreq_dt_pdev);
154
155         for_each_possible_cpu(cpu)
156                 dev_pm_opp_put_prop_name(opp_tables[cpu]);
157
158         kfree(opp_tables);
159
160         return 0;
161 }
162
163 static struct platform_driver sun50i_cpufreq_driver = {
164         .probe = sun50i_cpufreq_nvmem_probe,
165         .remove = sun50i_cpufreq_nvmem_remove,
166         .driver = {
167                 .name = "sun50i-cpufreq-nvmem",
168         },
169 };
170
171 static const struct of_device_id sun50i_cpufreq_match_list[] = {
172         { .compatible = "allwinner,sun50i-h6" },
173         {}
174 };
175
176 static const struct of_device_id *sun50i_cpufreq_match_node(void)
177 {
178         const struct of_device_id *match;
179         struct device_node *np;
180
181         np = of_find_node_by_path("/");
182         match = of_match_node(sun50i_cpufreq_match_list, np);
183         of_node_put(np);
184
185         return match;
186 }
187
188 /*
189  * Since the driver depends on nvmem drivers, which may return EPROBE_DEFER,
190  * all the real activity is done in the probe, which may be defered as well.
191  * The init here is only registering the driver and the platform device.
192  */
193 static int __init sun50i_cpufreq_init(void)
194 {
195         const struct of_device_id *match;
196         int ret;
197
198         match = sun50i_cpufreq_match_node();
199         if (!match)
200                 return -ENODEV;
201
202         ret = platform_driver_register(&sun50i_cpufreq_driver);
203         if (unlikely(ret < 0))
204                 return ret;
205
206         sun50i_cpufreq_pdev =
207                 platform_device_register_simple("sun50i-cpufreq-nvmem",
208                                                 -1, NULL, 0);
209         ret = PTR_ERR_OR_ZERO(sun50i_cpufreq_pdev);
210         if (ret == 0)
211                 return 0;
212
213         platform_driver_unregister(&sun50i_cpufreq_driver);
214         return ret;
215 }
216 module_init(sun50i_cpufreq_init);
217
218 static void __exit sun50i_cpufreq_exit(void)
219 {
220         platform_device_unregister(sun50i_cpufreq_pdev);
221         platform_driver_unregister(&sun50i_cpufreq_driver);
222 }
223 module_exit(sun50i_cpufreq_exit);
224
225 MODULE_DESCRIPTION("Sun50i-h6 cpufreq driver");
226 MODULE_LICENSE("GPL v2");