Commit | Line | Data |
---|---|---|
f328584f YL |
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 | /** | |
c2373448 | 28 | * sun50i_cpufreq_get_efuse() - Determine speed grade from efuse value |
f328584f YL |
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; | |
c2373448 OJ |
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 | |
f328584f | 81 | *versions = 0; |
f328584f YL |
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"); |