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