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/clk.h> |
12 | #include <linux/err.h> |
13 | #include <linux/hwspinlock.h> |
14 | #include <linux/io.h> |
15 | #include <linux/init.h> |
16 | #include <linux/list.h> |
17 | #include <linux/of.h> |
18 | #include <linux/of_address.h> |
19 | #include <linux/of_platform.h> |
20 | #include <linux/platform_data/syscon.h> |
21 | #include <linux/platform_device.h> |
22 | #include <linux/regmap.h> |
23 | #include <linux/reset.h> |
24 | #include <linux/mfd/syscon.h> |
25 | #include <linux/slab.h> |
26 | |
27 | static struct platform_driver syscon_driver; |
28 | |
29 | static DEFINE_SPINLOCK(syscon_list_slock); |
30 | static LIST_HEAD(syscon_list); |
31 | |
32 | struct syscon { |
33 | struct device_node *np; |
34 | struct regmap *regmap; |
35 | struct reset_control *reset; |
36 | struct list_head list; |
37 | }; |
38 | |
39 | static const struct regmap_config syscon_regmap_config = { |
40 | .reg_bits = 32, |
41 | .val_bits = 32, |
42 | .reg_stride = 4, |
43 | }; |
44 | |
45 | static struct syscon *of_syscon_register(struct device_node *np, bool check_res) |
46 | { |
47 | struct clk *clk; |
48 | struct syscon *syscon; |
49 | struct regmap *regmap; |
50 | void __iomem *base; |
51 | u32 reg_io_width; |
52 | int ret; |
53 | struct regmap_config syscon_config = syscon_regmap_config; |
54 | struct resource res; |
55 | struct reset_control *reset; |
56 | |
57 | syscon = kzalloc(size: sizeof(*syscon), GFP_KERNEL); |
58 | if (!syscon) |
59 | return ERR_PTR(error: -ENOMEM); |
60 | |
61 | if (of_address_to_resource(dev: np, index: 0, r: &res)) { |
62 | ret = -ENOMEM; |
63 | goto err_map; |
64 | } |
65 | |
66 | base = of_iomap(node: np, index: 0); |
67 | if (!base) { |
68 | ret = -ENOMEM; |
69 | goto err_map; |
70 | } |
71 | |
72 | /* Parse the device's DT node for an endianness specification */ |
73 | if (of_property_read_bool(np, propname: "big-endian" )) |
74 | syscon_config.val_format_endian = REGMAP_ENDIAN_BIG; |
75 | else if (of_property_read_bool(np, propname: "little-endian" )) |
76 | syscon_config.val_format_endian = REGMAP_ENDIAN_LITTLE; |
77 | else if (of_property_read_bool(np, propname: "native-endian" )) |
78 | syscon_config.val_format_endian = REGMAP_ENDIAN_NATIVE; |
79 | |
80 | /* |
81 | * search for reg-io-width property in DT. If it is not provided, |
82 | * default to 4 bytes. regmap_init_mmio will return an error if values |
83 | * are invalid so there is no need to check them here. |
84 | */ |
85 | ret = of_property_read_u32(np, propname: "reg-io-width" , out_value: ®_io_width); |
86 | if (ret) |
87 | reg_io_width = 4; |
88 | |
89 | ret = of_hwspin_lock_get_id(np, index: 0); |
90 | if (ret > 0 || (IS_ENABLED(CONFIG_HWSPINLOCK) && ret == 0)) { |
91 | syscon_config.use_hwlock = true; |
92 | syscon_config.hwlock_id = ret; |
93 | syscon_config.hwlock_mode = HWLOCK_IRQSTATE; |
94 | } else if (ret < 0) { |
95 | switch (ret) { |
96 | case -ENOENT: |
97 | /* Ignore missing hwlock, it's optional. */ |
98 | break; |
99 | default: |
100 | pr_err("Failed to retrieve valid hwlock: %d\n" , ret); |
101 | fallthrough; |
102 | case -EPROBE_DEFER: |
103 | goto err_regmap; |
104 | } |
105 | } |
106 | |
107 | syscon_config.name = kasprintf(GFP_KERNEL, fmt: "%pOFn@%pa" , np, &res.start); |
108 | if (!syscon_config.name) { |
109 | ret = -ENOMEM; |
110 | goto err_regmap; |
111 | } |
112 | syscon_config.reg_stride = reg_io_width; |
113 | syscon_config.val_bits = reg_io_width * 8; |
114 | syscon_config.max_register = resource_size(res: &res) - reg_io_width; |
115 | |
116 | regmap = regmap_init_mmio(NULL, base, &syscon_config); |
117 | kfree(objp: syscon_config.name); |
118 | if (IS_ERR(ptr: regmap)) { |
119 | pr_err("regmap init failed\n" ); |
120 | ret = PTR_ERR(ptr: regmap); |
121 | goto err_regmap; |
122 | } |
123 | |
124 | if (check_res) { |
125 | clk = of_clk_get(np, index: 0); |
126 | if (IS_ERR(ptr: clk)) { |
127 | ret = PTR_ERR(ptr: clk); |
128 | /* clock is optional */ |
129 | if (ret != -ENOENT) |
130 | goto err_clk; |
131 | } else { |
132 | ret = regmap_mmio_attach_clk(map: regmap, clk); |
133 | if (ret) |
134 | goto err_attach_clk; |
135 | } |
136 | |
137 | reset = of_reset_control_get_optional_exclusive(node: np, NULL); |
138 | if (IS_ERR(ptr: reset)) { |
139 | ret = PTR_ERR(ptr: reset); |
140 | goto err_attach_clk; |
141 | } |
142 | |
143 | ret = reset_control_deassert(rstc: reset); |
144 | if (ret) |
145 | goto err_reset; |
146 | } |
147 | |
148 | syscon->regmap = regmap; |
149 | syscon->np = np; |
150 | |
151 | spin_lock(lock: &syscon_list_slock); |
152 | list_add_tail(new: &syscon->list, head: &syscon_list); |
153 | spin_unlock(lock: &syscon_list_slock); |
154 | |
155 | return syscon; |
156 | |
157 | err_reset: |
158 | reset_control_put(rstc: reset); |
159 | err_attach_clk: |
160 | if (!IS_ERR(ptr: clk)) |
161 | clk_put(clk); |
162 | err_clk: |
163 | regmap_exit(map: regmap); |
164 | err_regmap: |
165 | iounmap(addr: base); |
166 | err_map: |
167 | kfree(objp: syscon); |
168 | return ERR_PTR(error: ret); |
169 | } |
170 | |
171 | static struct regmap *device_node_get_regmap(struct device_node *np, |
172 | bool check_res) |
173 | { |
174 | struct syscon *entry, *syscon = NULL; |
175 | |
176 | spin_lock(lock: &syscon_list_slock); |
177 | |
178 | list_for_each_entry(entry, &syscon_list, list) |
179 | if (entry->np == np) { |
180 | syscon = entry; |
181 | break; |
182 | } |
183 | |
184 | spin_unlock(lock: &syscon_list_slock); |
185 | |
186 | if (!syscon) |
187 | syscon = of_syscon_register(np, check_res); |
188 | |
189 | if (IS_ERR(ptr: syscon)) |
190 | return ERR_CAST(ptr: syscon); |
191 | |
192 | return syscon->regmap; |
193 | } |
194 | |
195 | struct regmap *device_node_to_regmap(struct device_node *np) |
196 | { |
197 | return device_node_get_regmap(np, check_res: false); |
198 | } |
199 | EXPORT_SYMBOL_GPL(device_node_to_regmap); |
200 | |
201 | struct regmap *syscon_node_to_regmap(struct device_node *np) |
202 | { |
203 | if (!of_device_is_compatible(device: np, "syscon" )) |
204 | return ERR_PTR(error: -EINVAL); |
205 | |
206 | return device_node_get_regmap(np, check_res: true); |
207 | } |
208 | EXPORT_SYMBOL_GPL(syscon_node_to_regmap); |
209 | |
210 | struct regmap *syscon_regmap_lookup_by_compatible(const char *s) |
211 | { |
212 | struct device_node *syscon_np; |
213 | struct regmap *regmap; |
214 | |
215 | syscon_np = of_find_compatible_node(NULL, NULL, compat: s); |
216 | if (!syscon_np) |
217 | return ERR_PTR(error: -ENODEV); |
218 | |
219 | regmap = syscon_node_to_regmap(syscon_np); |
220 | of_node_put(node: syscon_np); |
221 | |
222 | return regmap; |
223 | } |
224 | EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_compatible); |
225 | |
226 | struct regmap *syscon_regmap_lookup_by_phandle(struct device_node *np, |
227 | const char *property) |
228 | { |
229 | struct device_node *syscon_np; |
230 | struct regmap *regmap; |
231 | |
232 | if (property) |
233 | syscon_np = of_parse_phandle(np, phandle_name: property, index: 0); |
234 | else |
235 | syscon_np = np; |
236 | |
237 | if (!syscon_np) |
238 | return ERR_PTR(error: -ENODEV); |
239 | |
240 | regmap = syscon_node_to_regmap(syscon_np); |
241 | |
242 | if (property) |
243 | of_node_put(node: syscon_np); |
244 | |
245 | return regmap; |
246 | } |
247 | EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle); |
248 | |
249 | struct regmap *syscon_regmap_lookup_by_phandle_args(struct device_node *np, |
250 | const char *property, |
251 | int arg_count, |
252 | unsigned int *out_args) |
253 | { |
254 | struct device_node *syscon_np; |
255 | struct of_phandle_args args; |
256 | struct regmap *regmap; |
257 | unsigned int index; |
258 | int rc; |
259 | |
260 | rc = of_parse_phandle_with_fixed_args(np, list_name: property, cell_count: arg_count, |
261 | index: 0, out_args: &args); |
262 | if (rc) |
263 | return ERR_PTR(error: rc); |
264 | |
265 | syscon_np = args.np; |
266 | if (!syscon_np) |
267 | return ERR_PTR(error: -ENODEV); |
268 | |
269 | regmap = syscon_node_to_regmap(syscon_np); |
270 | for (index = 0; index < arg_count; index++) |
271 | out_args[index] = args.args[index]; |
272 | of_node_put(node: syscon_np); |
273 | |
274 | return regmap; |
275 | } |
276 | EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle_args); |
277 | |
278 | /* |
279 | * It behaves the same as syscon_regmap_lookup_by_phandle() except where |
280 | * there is no regmap phandle. In this case, instead of returning -ENODEV, |
281 | * the function returns NULL. |
282 | */ |
283 | struct regmap *syscon_regmap_lookup_by_phandle_optional(struct device_node *np, |
284 | const char *property) |
285 | { |
286 | struct regmap *regmap; |
287 | |
288 | regmap = syscon_regmap_lookup_by_phandle(np, property); |
289 | if (IS_ERR(ptr: regmap) && PTR_ERR(ptr: regmap) == -ENODEV) |
290 | return NULL; |
291 | |
292 | return regmap; |
293 | } |
294 | EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle_optional); |
295 | |
296 | static int syscon_probe(struct platform_device *pdev) |
297 | { |
298 | struct device *dev = &pdev->dev; |
299 | struct syscon_platform_data *pdata = dev_get_platdata(dev); |
300 | struct syscon *syscon; |
301 | struct regmap_config syscon_config = syscon_regmap_config; |
302 | struct resource *res; |
303 | void __iomem *base; |
304 | |
305 | syscon = devm_kzalloc(dev, size: sizeof(*syscon), GFP_KERNEL); |
306 | if (!syscon) |
307 | return -ENOMEM; |
308 | |
309 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
310 | if (!res) |
311 | return -ENOENT; |
312 | |
313 | base = devm_ioremap(dev, offset: res->start, size: resource_size(res)); |
314 | if (!base) |
315 | return -ENOMEM; |
316 | |
317 | syscon_config.max_register = resource_size(res) - 4; |
318 | if (pdata) |
319 | syscon_config.name = pdata->label; |
320 | syscon->regmap = devm_regmap_init_mmio(dev, base, &syscon_config); |
321 | if (IS_ERR(ptr: syscon->regmap)) { |
322 | dev_err(dev, "regmap init failed\n" ); |
323 | return PTR_ERR(ptr: syscon->regmap); |
324 | } |
325 | |
326 | platform_set_drvdata(pdev, data: syscon); |
327 | |
328 | dev_dbg(dev, "regmap %pR registered\n" , res); |
329 | |
330 | return 0; |
331 | } |
332 | |
333 | static const struct platform_device_id syscon_ids[] = { |
334 | { "syscon" , }, |
335 | { } |
336 | }; |
337 | |
338 | static struct platform_driver syscon_driver = { |
339 | .driver = { |
340 | .name = "syscon" , |
341 | }, |
342 | .probe = syscon_probe, |
343 | .id_table = syscon_ids, |
344 | }; |
345 | |
346 | static int __init syscon_init(void) |
347 | { |
348 | return platform_driver_register(&syscon_driver); |
349 | } |
350 | postcore_initcall(syscon_init); |
351 | |