1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Match running platform with pre-defined OPP values for CPUFreq |
4 | * |
5 | * Author: Ajit Pal Singh <ajitpal.singh@st.com> |
6 | * Lee Jones <lee.jones@linaro.org> |
7 | * |
8 | * Copyright (C) 2015 STMicroelectronics (R&D) Limited |
9 | */ |
10 | |
11 | #include <linux/cpu.h> |
12 | #include <linux/io.h> |
13 | #include <linux/mfd/syscon.h> |
14 | #include <linux/module.h> |
15 | #include <linux/of.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/pm_opp.h> |
18 | #include <linux/regmap.h> |
19 | |
20 | #define VERSION_ELEMENTS 3 |
21 | #define MAX_PCODE_NAME_LEN 7 |
22 | |
23 | #define VERSION_SHIFT 28 |
24 | #define HW_INFO_INDEX 1 |
25 | #define MAJOR_ID_INDEX 1 |
26 | #define MINOR_ID_INDEX 2 |
27 | |
28 | /* |
29 | * Only match on "suitable for ALL versions" entries |
30 | * |
31 | * This will be used with the BIT() macro. It sets the |
32 | * top bit of a 32bit value and is equal to 0x80000000. |
33 | */ |
34 | #define DEFAULT_VERSION 31 |
35 | |
36 | enum { |
37 | PCODE = 0, |
38 | SUBSTRATE, |
39 | DVFS_MAX_REGFIELDS, |
40 | }; |
41 | |
42 | /** |
43 | * struct sti_cpufreq_ddata - ST CPUFreq Driver Data |
44 | * |
45 | * @cpu: CPU's OF node |
46 | * @syscfg_eng: Engineering Syscon register map |
47 | * @syscfg: Syscon register map |
48 | */ |
49 | static struct sti_cpufreq_ddata { |
50 | struct device *cpu; |
51 | struct regmap *syscfg_eng; |
52 | struct regmap *syscfg; |
53 | } ddata; |
54 | |
55 | static int sti_cpufreq_fetch_major(void) { |
56 | struct device_node *np = ddata.cpu->of_node; |
57 | struct device *dev = ddata.cpu; |
58 | unsigned int major_offset; |
59 | unsigned int socid; |
60 | int ret; |
61 | |
62 | ret = of_property_read_u32_index(np, propname: "st,syscfg" , |
63 | MAJOR_ID_INDEX, out_value: &major_offset); |
64 | if (ret) { |
65 | dev_err(dev, "No major number offset provided in %pOF [%d]\n" , |
66 | np, ret); |
67 | return ret; |
68 | } |
69 | |
70 | ret = regmap_read(map: ddata.syscfg, reg: major_offset, val: &socid); |
71 | if (ret) { |
72 | dev_err(dev, "Failed to read major number from syscon [%d]\n" , |
73 | ret); |
74 | return ret; |
75 | } |
76 | |
77 | return ((socid >> VERSION_SHIFT) & 0xf) + 1; |
78 | } |
79 | |
80 | static int sti_cpufreq_fetch_minor(void) |
81 | { |
82 | struct device *dev = ddata.cpu; |
83 | struct device_node *np = dev->of_node; |
84 | unsigned int minor_offset; |
85 | unsigned int minid; |
86 | int ret; |
87 | |
88 | ret = of_property_read_u32_index(np, propname: "st,syscfg-eng" , |
89 | MINOR_ID_INDEX, out_value: &minor_offset); |
90 | if (ret) { |
91 | dev_err(dev, |
92 | "No minor number offset provided %pOF [%d]\n" , |
93 | np, ret); |
94 | return ret; |
95 | } |
96 | |
97 | ret = regmap_read(map: ddata.syscfg_eng, reg: minor_offset, val: &minid); |
98 | if (ret) { |
99 | dev_err(dev, |
100 | "Failed to read the minor number from syscon [%d]\n" , |
101 | ret); |
102 | return ret; |
103 | } |
104 | |
105 | return minid & 0xf; |
106 | } |
107 | |
108 | static int sti_cpufreq_fetch_regmap_field(const struct reg_field *reg_fields, |
109 | int hw_info_offset, int field) |
110 | { |
111 | struct regmap_field *regmap_field; |
112 | struct reg_field reg_field = reg_fields[field]; |
113 | struct device *dev = ddata.cpu; |
114 | unsigned int value; |
115 | int ret; |
116 | |
117 | reg_field.reg = hw_info_offset; |
118 | regmap_field = devm_regmap_field_alloc(dev, |
119 | regmap: ddata.syscfg_eng, |
120 | reg_field); |
121 | if (IS_ERR(ptr: regmap_field)) { |
122 | dev_err(dev, "Failed to allocate reg field\n" ); |
123 | return PTR_ERR(ptr: regmap_field); |
124 | } |
125 | |
126 | ret = regmap_field_read(field: regmap_field, val: &value); |
127 | if (ret) { |
128 | dev_err(dev, "Failed to read %s code\n" , |
129 | field ? "SUBSTRATE" : "PCODE" ); |
130 | return ret; |
131 | } |
132 | |
133 | return value; |
134 | } |
135 | |
136 | static const struct reg_field sti_stih407_dvfs_regfields[DVFS_MAX_REGFIELDS] = { |
137 | [PCODE] = REG_FIELD(0, 16, 19), |
138 | [SUBSTRATE] = REG_FIELD(0, 0, 2), |
139 | }; |
140 | |
141 | static const struct reg_field *sti_cpufreq_match(void) |
142 | { |
143 | if (of_machine_is_compatible(compat: "st,stih407" ) || |
144 | of_machine_is_compatible(compat: "st,stih410" ) || |
145 | of_machine_is_compatible(compat: "st,stih418" )) |
146 | return sti_stih407_dvfs_regfields; |
147 | |
148 | return NULL; |
149 | } |
150 | |
151 | static int sti_cpufreq_set_opp_info(void) |
152 | { |
153 | struct device *dev = ddata.cpu; |
154 | struct device_node *np = dev->of_node; |
155 | const struct reg_field *reg_fields; |
156 | unsigned int hw_info_offset; |
157 | unsigned int version[VERSION_ELEMENTS]; |
158 | int pcode, substrate, major, minor; |
159 | int opp_token, ret; |
160 | char name[MAX_PCODE_NAME_LEN]; |
161 | struct dev_pm_opp_config config = { |
162 | .supported_hw = version, |
163 | .supported_hw_count = ARRAY_SIZE(version), |
164 | .prop_name = name, |
165 | }; |
166 | |
167 | reg_fields = sti_cpufreq_match(); |
168 | if (!reg_fields) { |
169 | dev_err(dev, "This SoC doesn't support voltage scaling\n" ); |
170 | return -ENODEV; |
171 | } |
172 | |
173 | ret = of_property_read_u32_index(np, propname: "st,syscfg-eng" , |
174 | HW_INFO_INDEX, out_value: &hw_info_offset); |
175 | if (ret) { |
176 | dev_warn(dev, "Failed to read HW info offset from DT\n" ); |
177 | substrate = DEFAULT_VERSION; |
178 | pcode = 0; |
179 | goto use_defaults; |
180 | } |
181 | |
182 | pcode = sti_cpufreq_fetch_regmap_field(reg_fields, |
183 | hw_info_offset, |
184 | field: PCODE); |
185 | if (pcode < 0) { |
186 | dev_warn(dev, "Failed to obtain process code\n" ); |
187 | /* Use default pcode */ |
188 | pcode = 0; |
189 | } |
190 | |
191 | substrate = sti_cpufreq_fetch_regmap_field(reg_fields, |
192 | hw_info_offset, |
193 | field: SUBSTRATE); |
194 | if (substrate) { |
195 | dev_warn(dev, "Failed to obtain substrate code\n" ); |
196 | /* Use default substrate */ |
197 | substrate = DEFAULT_VERSION; |
198 | } |
199 | |
200 | use_defaults: |
201 | major = sti_cpufreq_fetch_major(); |
202 | if (major < 0) { |
203 | dev_err(dev, "Failed to obtain major version\n" ); |
204 | /* Use default major number */ |
205 | major = DEFAULT_VERSION; |
206 | } |
207 | |
208 | minor = sti_cpufreq_fetch_minor(); |
209 | if (minor < 0) { |
210 | dev_err(dev, "Failed to obtain minor version\n" ); |
211 | /* Use default minor number */ |
212 | minor = DEFAULT_VERSION; |
213 | } |
214 | |
215 | snprintf(buf: name, MAX_PCODE_NAME_LEN, fmt: "pcode%d" , pcode); |
216 | |
217 | version[0] = BIT(major); |
218 | version[1] = BIT(minor); |
219 | version[2] = BIT(substrate); |
220 | |
221 | opp_token = dev_pm_opp_set_config(dev, config: &config); |
222 | if (opp_token < 0) { |
223 | dev_err(dev, "Failed to set OPP config\n" ); |
224 | return opp_token; |
225 | } |
226 | |
227 | dev_dbg(dev, "pcode: %d major: %d minor: %d substrate: %d\n" , |
228 | pcode, major, minor, substrate); |
229 | dev_dbg(dev, "version[0]: %x version[1]: %x version[2]: %x\n" , |
230 | version[0], version[1], version[2]); |
231 | |
232 | return 0; |
233 | } |
234 | |
235 | static int sti_cpufreq_fetch_syscon_registers(void) |
236 | { |
237 | struct device *dev = ddata.cpu; |
238 | struct device_node *np = dev->of_node; |
239 | |
240 | ddata.syscfg = syscon_regmap_lookup_by_phandle(np, property: "st,syscfg" ); |
241 | if (IS_ERR(ptr: ddata.syscfg)) { |
242 | dev_err(dev, "\"st,syscfg\" not supplied\n" ); |
243 | return PTR_ERR(ptr: ddata.syscfg); |
244 | } |
245 | |
246 | ddata.syscfg_eng = syscon_regmap_lookup_by_phandle(np, property: "st,syscfg-eng" ); |
247 | if (IS_ERR(ptr: ddata.syscfg_eng)) { |
248 | dev_err(dev, "\"st,syscfg-eng\" not supplied\n" ); |
249 | return PTR_ERR(ptr: ddata.syscfg_eng); |
250 | } |
251 | |
252 | return 0; |
253 | } |
254 | |
255 | static int __init sti_cpufreq_init(void) |
256 | { |
257 | int ret; |
258 | |
259 | if ((!of_machine_is_compatible(compat: "st,stih407" )) && |
260 | (!of_machine_is_compatible(compat: "st,stih410" )) && |
261 | (!of_machine_is_compatible(compat: "st,stih418" ))) |
262 | return -ENODEV; |
263 | |
264 | ddata.cpu = get_cpu_device(cpu: 0); |
265 | if (!ddata.cpu) { |
266 | dev_err(ddata.cpu, "Failed to get device for CPU0\n" ); |
267 | goto skip_voltage_scaling; |
268 | } |
269 | |
270 | if (!of_get_property(node: ddata.cpu->of_node, name: "operating-points-v2" , NULL)) { |
271 | dev_err(ddata.cpu, "OPP-v2 not supported\n" ); |
272 | goto skip_voltage_scaling; |
273 | } |
274 | |
275 | ret = sti_cpufreq_fetch_syscon_registers(); |
276 | if (ret) |
277 | goto skip_voltage_scaling; |
278 | |
279 | ret = sti_cpufreq_set_opp_info(); |
280 | if (!ret) |
281 | goto register_cpufreq_dt; |
282 | |
283 | skip_voltage_scaling: |
284 | dev_err(ddata.cpu, "Not doing voltage scaling\n" ); |
285 | |
286 | register_cpufreq_dt: |
287 | platform_device_register_simple(name: "cpufreq-dt" , id: -1, NULL, num: 0); |
288 | |
289 | return 0; |
290 | } |
291 | module_init(sti_cpufreq_init); |
292 | |
293 | static const struct of_device_id __maybe_unused sti_cpufreq_of_match[] = { |
294 | { .compatible = "st,stih407" }, |
295 | { .compatible = "st,stih410" }, |
296 | { }, |
297 | }; |
298 | MODULE_DEVICE_TABLE(of, sti_cpufreq_of_match); |
299 | |
300 | MODULE_DESCRIPTION("STMicroelectronics CPUFreq/OPP driver" ); |
301 | MODULE_AUTHOR("Ajitpal Singh <ajitpal.singh@st.com>" ); |
302 | MODULE_AUTHOR("Lee Jones <lee.jones@linaro.org>" ); |
303 | MODULE_LICENSE("GPL v2" ); |
304 | |