1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * System Control Driver |
4 | * |
5 | * Copyright (C) 2012 Freescale Semiconductor, Inc. |
6 | * Copyright (C) 2012 Linaro Ltd. |
7 | * |
8 | * Author: Dong Aisheng <dong.aisheng@linaro.org> |
9 | */ |
10 | |
11 | #include <linux/cleanup.h> |
12 | #include <linux/clk.h> |
13 | #include <linux/err.h> |
14 | #include <linux/hwspinlock.h> |
15 | #include <linux/list.h> |
16 | #include <linux/mutex.h> |
17 | #include <linux/of.h> |
18 | #include <linux/of_address.h> |
19 | #include <linux/regmap.h> |
20 | #include <linux/reset.h> |
21 | #include <linux/mfd/syscon.h> |
22 | #include <linux/slab.h> |
23 | |
24 | static DEFINE_MUTEX(syscon_list_lock); |
25 | static LIST_HEAD(syscon_list); |
26 | |
27 | struct syscon { |
28 | struct device_node *np; |
29 | struct regmap *regmap; |
30 | struct reset_control *reset; |
31 | struct list_head list; |
32 | }; |
33 | |
34 | static const struct regmap_config syscon_regmap_config = { |
35 | .reg_bits = 32, |
36 | .val_bits = 32, |
37 | .reg_stride = 4, |
38 | }; |
39 | |
40 | static struct syscon *of_syscon_register(struct device_node *np, bool check_res) |
41 | { |
42 | struct clk *clk; |
43 | struct regmap *regmap; |
44 | void __iomem *base; |
45 | u32 reg_io_width; |
46 | int ret; |
47 | struct regmap_config syscon_config = syscon_regmap_config; |
48 | struct resource res; |
49 | struct reset_control *reset; |
50 | resource_size_t res_size; |
51 | |
52 | WARN_ON(!mutex_is_locked(&syscon_list_lock)); |
53 | |
54 | struct syscon *syscon __free(kfree) = kzalloc(sizeof(*syscon), GFP_KERNEL); |
55 | if (!syscon) |
56 | return ERR_PTR(error: -ENOMEM); |
57 | |
58 | if (of_address_to_resource(dev: np, index: 0, r: &res)) |
59 | return ERR_PTR(error: -ENOMEM); |
60 | |
61 | base = of_iomap(node: np, index: 0); |
62 | if (!base) |
63 | return ERR_PTR(error: -ENOMEM); |
64 | |
65 | /* Parse the device's DT node for an endianness specification */ |
66 | if (of_property_read_bool(np, propname: "big-endian" )) |
67 | syscon_config.val_format_endian = REGMAP_ENDIAN_BIG; |
68 | else if (of_property_read_bool(np, propname: "little-endian" )) |
69 | syscon_config.val_format_endian = REGMAP_ENDIAN_LITTLE; |
70 | else if (of_property_read_bool(np, propname: "native-endian" )) |
71 | syscon_config.val_format_endian = REGMAP_ENDIAN_NATIVE; |
72 | |
73 | /* |
74 | * search for reg-io-width property in DT. If it is not provided, |
75 | * default to 4 bytes. regmap_init_mmio will return an error if values |
76 | * are invalid so there is no need to check them here. |
77 | */ |
78 | ret = of_property_read_u32(np, propname: "reg-io-width" , out_value: ®_io_width); |
79 | if (ret) |
80 | reg_io_width = 4; |
81 | |
82 | ret = of_hwspin_lock_get_id(np, index: 0); |
83 | if (ret > 0 || (IS_ENABLED(CONFIG_HWSPINLOCK) && ret == 0)) { |
84 | syscon_config.use_hwlock = true; |
85 | syscon_config.hwlock_id = ret; |
86 | syscon_config.hwlock_mode = HWLOCK_IRQSTATE; |
87 | } else if (ret < 0) { |
88 | switch (ret) { |
89 | case -ENOENT: |
90 | /* Ignore missing hwlock, it's optional. */ |
91 | break; |
92 | default: |
93 | pr_err("Failed to retrieve valid hwlock: %d\n" , ret); |
94 | fallthrough; |
95 | case -EPROBE_DEFER: |
96 | goto err_regmap; |
97 | } |
98 | } |
99 | |
100 | res_size = resource_size(res: &res); |
101 | if (res_size < reg_io_width) { |
102 | ret = -EFAULT; |
103 | goto err_regmap; |
104 | } |
105 | |
106 | syscon_config.name = kasprintf(GFP_KERNEL, fmt: "%pOFn@%pa" , np, &res.start); |
107 | if (!syscon_config.name) { |
108 | ret = -ENOMEM; |
109 | goto err_regmap; |
110 | } |
111 | syscon_config.reg_stride = reg_io_width; |
112 | syscon_config.val_bits = reg_io_width * 8; |
113 | syscon_config.max_register = res_size - reg_io_width; |
114 | if (!syscon_config.max_register) |
115 | syscon_config.max_register_is_0 = true; |
116 | |
117 | regmap = regmap_init_mmio(NULL, base, &syscon_config); |
118 | kfree(objp: syscon_config.name); |
119 | if (IS_ERR(ptr: regmap)) { |
120 | pr_err("regmap init failed\n" ); |
121 | ret = PTR_ERR(ptr: regmap); |
122 | goto err_regmap; |
123 | } |
124 | |
125 | if (check_res) { |
126 | clk = of_clk_get(np, index: 0); |
127 | if (IS_ERR(ptr: clk)) { |
128 | ret = PTR_ERR(ptr: clk); |
129 | /* clock is optional */ |
130 | if (ret != -ENOENT) |
131 | goto err_clk; |
132 | } else { |
133 | ret = regmap_mmio_attach_clk(map: regmap, clk); |
134 | if (ret) |
135 | goto err_attach_clk; |
136 | } |
137 | |
138 | reset = of_reset_control_get_optional_exclusive(node: np, NULL); |
139 | if (IS_ERR(ptr: reset)) { |
140 | ret = PTR_ERR(ptr: reset); |
141 | goto err_attach_clk; |
142 | } |
143 | |
144 | ret = reset_control_deassert(rstc: reset); |
145 | if (ret) |
146 | goto err_reset; |
147 | } |
148 | |
149 | syscon->regmap = regmap; |
150 | syscon->np = np; |
151 | |
152 | list_add_tail(new: &syscon->list, head: &syscon_list); |
153 | |
154 | return_ptr(syscon); |
155 | |
156 | err_reset: |
157 | reset_control_put(rstc: reset); |
158 | err_attach_clk: |
159 | if (!IS_ERR(ptr: clk)) |
160 | clk_put(clk); |
161 | err_clk: |
162 | regmap_exit(map: regmap); |
163 | err_regmap: |
164 | iounmap(addr: base); |
165 | return ERR_PTR(error: ret); |
166 | } |
167 | |
168 | static struct regmap *device_node_get_regmap(struct device_node *np, |
169 | bool create_regmap, |
170 | bool check_res) |
171 | { |
172 | struct syscon *entry, *syscon = NULL; |
173 | |
174 | mutex_lock(&syscon_list_lock); |
175 | |
176 | list_for_each_entry(entry, &syscon_list, list) |
177 | if (entry->np == np) { |
178 | syscon = entry; |
179 | break; |
180 | } |
181 | |
182 | if (!syscon) { |
183 | if (create_regmap) |
184 | syscon = of_syscon_register(np, check_res); |
185 | else |
186 | syscon = ERR_PTR(error: -EINVAL); |
187 | } |
188 | mutex_unlock(lock: &syscon_list_lock); |
189 | |
190 | if (IS_ERR(ptr: syscon)) |
191 | return ERR_CAST(ptr: syscon); |
192 | |
193 | return syscon->regmap; |
194 | } |
195 | |
196 | /** |
197 | * of_syscon_register_regmap() - Register regmap for specified device node |
198 | * @np: Device tree node |
199 | * @regmap: Pointer to regmap object |
200 | * |
201 | * Register an externally created regmap object with syscon for the specified |
202 | * device tree node. This regmap will then be returned to client drivers using |
203 | * the syscon_regmap_lookup_by_phandle() API. |
204 | * |
205 | * Return: 0 on success, negative error code on failure. |
206 | */ |
207 | int of_syscon_register_regmap(struct device_node *np, struct regmap *regmap) |
208 | { |
209 | struct syscon *entry, *syscon = NULL; |
210 | int ret; |
211 | |
212 | if (!np || !regmap) |
213 | return -EINVAL; |
214 | |
215 | syscon = kzalloc(sizeof(*syscon), GFP_KERNEL); |
216 | if (!syscon) |
217 | return -ENOMEM; |
218 | |
219 | /* check if syscon entry already exists */ |
220 | mutex_lock(&syscon_list_lock); |
221 | |
222 | list_for_each_entry(entry, &syscon_list, list) |
223 | if (entry->np == np) { |
224 | ret = -EEXIST; |
225 | goto err_unlock; |
226 | } |
227 | |
228 | syscon->regmap = regmap; |
229 | syscon->np = np; |
230 | |
231 | /* register the regmap in syscon list */ |
232 | list_add_tail(new: &syscon->list, head: &syscon_list); |
233 | mutex_unlock(lock: &syscon_list_lock); |
234 | |
235 | return 0; |
236 | |
237 | err_unlock: |
238 | mutex_unlock(lock: &syscon_list_lock); |
239 | kfree(objp: syscon); |
240 | return ret; |
241 | } |
242 | EXPORT_SYMBOL_GPL(of_syscon_register_regmap); |
243 | |
244 | /** |
245 | * device_node_to_regmap() - Get or create a regmap for specified device node |
246 | * @np: Device tree node |
247 | * |
248 | * Get a regmap for the specified device node. If there's not an existing |
249 | * regmap, then one is instantiated. This function should not be used if the |
250 | * device node has a custom regmap driver or has resources (clocks, resets) to |
251 | * be managed. Use syscon_node_to_regmap() instead for those cases. |
252 | * |
253 | * Return: regmap ptr on success, negative error code on failure. |
254 | */ |
255 | struct regmap *device_node_to_regmap(struct device_node *np) |
256 | { |
257 | return device_node_get_regmap(np, create_regmap: true, check_res: false); |
258 | } |
259 | EXPORT_SYMBOL_GPL(device_node_to_regmap); |
260 | |
261 | /** |
262 | * syscon_node_to_regmap() - Get or create a regmap for specified syscon device node |
263 | * @np: Device tree node |
264 | * |
265 | * Get a regmap for the specified device node. If there's not an existing |
266 | * regmap, then one is instantiated if the node is a generic "syscon". This |
267 | * function is safe to use for a syscon registered with |
268 | * of_syscon_register_regmap(). |
269 | * |
270 | * Return: regmap ptr on success, negative error code on failure. |
271 | */ |
272 | struct regmap *syscon_node_to_regmap(struct device_node *np) |
273 | { |
274 | return device_node_get_regmap(np, create_regmap: of_device_is_compatible(device: np, "syscon" ), check_res: true); |
275 | } |
276 | EXPORT_SYMBOL_GPL(syscon_node_to_regmap); |
277 | |
278 | struct regmap *syscon_regmap_lookup_by_compatible(const char *s) |
279 | { |
280 | struct device_node *syscon_np; |
281 | struct regmap *regmap; |
282 | |
283 | syscon_np = of_find_compatible_node(NULL, NULL, compat: s); |
284 | if (!syscon_np) |
285 | return ERR_PTR(error: -ENODEV); |
286 | |
287 | regmap = syscon_node_to_regmap(syscon_np); |
288 | of_node_put(node: syscon_np); |
289 | |
290 | return regmap; |
291 | } |
292 | EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_compatible); |
293 | |
294 | struct regmap *syscon_regmap_lookup_by_phandle(struct device_node *np, |
295 | const char *property) |
296 | { |
297 | struct device_node *syscon_np; |
298 | struct regmap *regmap; |
299 | |
300 | if (property) |
301 | syscon_np = of_parse_phandle(np, phandle_name: property, index: 0); |
302 | else |
303 | syscon_np = np; |
304 | |
305 | if (!syscon_np) |
306 | return ERR_PTR(error: -ENODEV); |
307 | |
308 | regmap = syscon_node_to_regmap(syscon_np); |
309 | |
310 | if (property) |
311 | of_node_put(node: syscon_np); |
312 | |
313 | return regmap; |
314 | } |
315 | EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle); |
316 | |
317 | struct regmap *syscon_regmap_lookup_by_phandle_args(struct device_node *np, |
318 | const char *property, |
319 | int arg_count, |
320 | unsigned int *out_args) |
321 | { |
322 | struct device_node *syscon_np; |
323 | struct of_phandle_args args; |
324 | struct regmap *regmap; |
325 | unsigned int index; |
326 | int rc; |
327 | |
328 | rc = of_parse_phandle_with_fixed_args(np, list_name: property, cell_count: arg_count, |
329 | index: 0, out_args: &args); |
330 | if (rc) |
331 | return ERR_PTR(error: rc); |
332 | |
333 | syscon_np = args.np; |
334 | if (!syscon_np) |
335 | return ERR_PTR(error: -ENODEV); |
336 | |
337 | regmap = syscon_node_to_regmap(syscon_np); |
338 | for (index = 0; index < arg_count; index++) |
339 | out_args[index] = args.args[index]; |
340 | of_node_put(node: syscon_np); |
341 | |
342 | return regmap; |
343 | } |
344 | EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle_args); |
345 | |
346 | /* |
347 | * It behaves the same as syscon_regmap_lookup_by_phandle() except where |
348 | * there is no regmap phandle. In this case, instead of returning -ENODEV, |
349 | * the function returns NULL. |
350 | */ |
351 | struct regmap *syscon_regmap_lookup_by_phandle_optional(struct device_node *np, |
352 | const char *property) |
353 | { |
354 | struct regmap *regmap; |
355 | |
356 | regmap = syscon_regmap_lookup_by_phandle(np, property); |
357 | if (IS_ERR(ptr: regmap) && PTR_ERR(ptr: regmap) == -ENODEV) |
358 | return NULL; |
359 | |
360 | return regmap; |
361 | } |
362 | EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle_optional); |
363 | |