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 | syscon_config.reg_stride = reg_io_width; |
109 | syscon_config.val_bits = reg_io_width * 8; |
110 | syscon_config.max_register = resource_size(res: &res) - reg_io_width; |
111 | |
112 | regmap = regmap_init_mmio(NULL, base, &syscon_config); |
113 | kfree(objp: syscon_config.name); |
114 | if (IS_ERR(ptr: regmap)) { |
115 | pr_err("regmap init failed\n" ); |
116 | ret = PTR_ERR(ptr: regmap); |
117 | goto err_regmap; |
118 | } |
119 | |
120 | if (check_res) { |
121 | clk = of_clk_get(np, index: 0); |
122 | if (IS_ERR(ptr: clk)) { |
123 | ret = PTR_ERR(ptr: clk); |
124 | /* clock is optional */ |
125 | if (ret != -ENOENT) |
126 | goto err_clk; |
127 | } else { |
128 | ret = regmap_mmio_attach_clk(map: regmap, clk); |
129 | if (ret) |
130 | goto err_attach_clk; |
131 | } |
132 | |
133 | reset = of_reset_control_get_optional_exclusive(node: np, NULL); |
134 | if (IS_ERR(ptr: reset)) { |
135 | ret = PTR_ERR(ptr: reset); |
136 | goto err_attach_clk; |
137 | } |
138 | |
139 | ret = reset_control_deassert(rstc: reset); |
140 | if (ret) |
141 | goto err_reset; |
142 | } |
143 | |
144 | syscon->regmap = regmap; |
145 | syscon->np = np; |
146 | |
147 | spin_lock(lock: &syscon_list_slock); |
148 | list_add_tail(new: &syscon->list, head: &syscon_list); |
149 | spin_unlock(lock: &syscon_list_slock); |
150 | |
151 | return syscon; |
152 | |
153 | err_reset: |
154 | reset_control_put(rstc: reset); |
155 | err_attach_clk: |
156 | if (!IS_ERR(ptr: clk)) |
157 | clk_put(clk); |
158 | err_clk: |
159 | regmap_exit(map: regmap); |
160 | err_regmap: |
161 | iounmap(addr: base); |
162 | err_map: |
163 | kfree(objp: syscon); |
164 | return ERR_PTR(error: ret); |
165 | } |
166 | |
167 | static struct regmap *device_node_get_regmap(struct device_node *np, |
168 | bool check_res) |
169 | { |
170 | struct syscon *entry, *syscon = NULL; |
171 | |
172 | spin_lock(lock: &syscon_list_slock); |
173 | |
174 | list_for_each_entry(entry, &syscon_list, list) |
175 | if (entry->np == np) { |
176 | syscon = entry; |
177 | break; |
178 | } |
179 | |
180 | spin_unlock(lock: &syscon_list_slock); |
181 | |
182 | if (!syscon) |
183 | syscon = of_syscon_register(np, check_res); |
184 | |
185 | if (IS_ERR(ptr: syscon)) |
186 | return ERR_CAST(ptr: syscon); |
187 | |
188 | return syscon->regmap; |
189 | } |
190 | |
191 | struct regmap *device_node_to_regmap(struct device_node *np) |
192 | { |
193 | return device_node_get_regmap(np, check_res: false); |
194 | } |
195 | EXPORT_SYMBOL_GPL(device_node_to_regmap); |
196 | |
197 | struct regmap *syscon_node_to_regmap(struct device_node *np) |
198 | { |
199 | if (!of_device_is_compatible(device: np, "syscon" )) |
200 | return ERR_PTR(error: -EINVAL); |
201 | |
202 | return device_node_get_regmap(np, check_res: true); |
203 | } |
204 | EXPORT_SYMBOL_GPL(syscon_node_to_regmap); |
205 | |
206 | struct regmap *syscon_regmap_lookup_by_compatible(const char *s) |
207 | { |
208 | struct device_node *syscon_np; |
209 | struct regmap *regmap; |
210 | |
211 | syscon_np = of_find_compatible_node(NULL, NULL, compat: s); |
212 | if (!syscon_np) |
213 | return ERR_PTR(error: -ENODEV); |
214 | |
215 | regmap = syscon_node_to_regmap(syscon_np); |
216 | of_node_put(node: syscon_np); |
217 | |
218 | return regmap; |
219 | } |
220 | EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_compatible); |
221 | |
222 | struct regmap *syscon_regmap_lookup_by_phandle(struct device_node *np, |
223 | const char *property) |
224 | { |
225 | struct device_node *syscon_np; |
226 | struct regmap *regmap; |
227 | |
228 | if (property) |
229 | syscon_np = of_parse_phandle(np, phandle_name: property, index: 0); |
230 | else |
231 | syscon_np = np; |
232 | |
233 | if (!syscon_np) |
234 | return ERR_PTR(error: -ENODEV); |
235 | |
236 | regmap = syscon_node_to_regmap(syscon_np); |
237 | of_node_put(node: syscon_np); |
238 | |
239 | return regmap; |
240 | } |
241 | EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle); |
242 | |
243 | struct regmap *syscon_regmap_lookup_by_phandle_args(struct device_node *np, |
244 | const char *property, |
245 | int arg_count, |
246 | unsigned int *out_args) |
247 | { |
248 | struct device_node *syscon_np; |
249 | struct of_phandle_args args; |
250 | struct regmap *regmap; |
251 | unsigned int index; |
252 | int rc; |
253 | |
254 | rc = of_parse_phandle_with_fixed_args(np, list_name: property, cell_count: arg_count, |
255 | index: 0, out_args: &args); |
256 | if (rc) |
257 | return ERR_PTR(error: rc); |
258 | |
259 | syscon_np = args.np; |
260 | if (!syscon_np) |
261 | return ERR_PTR(error: -ENODEV); |
262 | |
263 | regmap = syscon_node_to_regmap(syscon_np); |
264 | for (index = 0; index < arg_count; index++) |
265 | out_args[index] = args.args[index]; |
266 | of_node_put(node: syscon_np); |
267 | |
268 | return regmap; |
269 | } |
270 | EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle_args); |
271 | |
272 | /* |
273 | * It behaves the same as syscon_regmap_lookup_by_phandle() except where |
274 | * there is no regmap phandle. In this case, instead of returning -ENODEV, |
275 | * the function returns NULL. |
276 | */ |
277 | struct regmap *syscon_regmap_lookup_by_phandle_optional(struct device_node *np, |
278 | const char *property) |
279 | { |
280 | struct regmap *regmap; |
281 | |
282 | regmap = syscon_regmap_lookup_by_phandle(np, property); |
283 | if (IS_ERR(ptr: regmap) && PTR_ERR(ptr: regmap) == -ENODEV) |
284 | return NULL; |
285 | |
286 | return regmap; |
287 | } |
288 | EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle_optional); |
289 | |
290 | static int syscon_probe(struct platform_device *pdev) |
291 | { |
292 | struct device *dev = &pdev->dev; |
293 | struct syscon_platform_data *pdata = dev_get_platdata(dev); |
294 | struct syscon *syscon; |
295 | struct regmap_config syscon_config = syscon_regmap_config; |
296 | struct resource *res; |
297 | void __iomem *base; |
298 | |
299 | syscon = devm_kzalloc(dev, size: sizeof(*syscon), GFP_KERNEL); |
300 | if (!syscon) |
301 | return -ENOMEM; |
302 | |
303 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
304 | if (!res) |
305 | return -ENOENT; |
306 | |
307 | base = devm_ioremap(dev, offset: res->start, size: resource_size(res)); |
308 | if (!base) |
309 | return -ENOMEM; |
310 | |
311 | syscon_config.max_register = resource_size(res) - 4; |
312 | if (pdata) |
313 | syscon_config.name = pdata->label; |
314 | syscon->regmap = devm_regmap_init_mmio(dev, base, &syscon_config); |
315 | if (IS_ERR(ptr: syscon->regmap)) { |
316 | dev_err(dev, "regmap init failed\n" ); |
317 | return PTR_ERR(ptr: syscon->regmap); |
318 | } |
319 | |
320 | platform_set_drvdata(pdev, data: syscon); |
321 | |
322 | dev_dbg(dev, "regmap %pR registered\n" , res); |
323 | |
324 | return 0; |
325 | } |
326 | |
327 | static const struct platform_device_id syscon_ids[] = { |
328 | { "syscon" , }, |
329 | { } |
330 | }; |
331 | |
332 | static struct platform_driver syscon_driver = { |
333 | .driver = { |
334 | .name = "syscon" , |
335 | }, |
336 | .probe = syscon_probe, |
337 | .id_table = syscon_ids, |
338 | }; |
339 | |
340 | static int __init syscon_init(void) |
341 | { |
342 | return platform_driver_register(&syscon_driver); |
343 | } |
344 | postcore_initcall(syscon_init); |
345 | |