]>
Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
87d68730 DA |
2 | /* |
3 | * System Control Driver | |
4 | * | |
5 | * Copyright (C) 2012 Freescale Semiconductor, Inc. | |
6 | * Copyright (C) 2012 Linaro Ltd. | |
7 | * | |
8 | * Author: Dong Aisheng <[email protected]> | |
87d68730 DA |
9 | */ |
10 | ||
a00406b7 | 11 | #include <linux/clk.h> |
87d68730 | 12 | #include <linux/err.h> |
3bafc09e | 13 | #include <linux/hwspinlock.h> |
87d68730 | 14 | #include <linux/io.h> |
1345da73 | 15 | #include <linux/init.h> |
bdb0066d | 16 | #include <linux/list.h> |
87d68730 DA |
17 | #include <linux/of.h> |
18 | #include <linux/of_address.h> | |
19 | #include <linux/of_platform.h> | |
29f9b6cf | 20 | #include <linux/platform_data/syscon.h> |
87d68730 DA |
21 | #include <linux/platform_device.h> |
22 | #include <linux/regmap.h> | |
7d1e3bd9 | 23 | #include <linux/reset.h> |
75177dee | 24 | #include <linux/mfd/syscon.h> |
bdb0066d | 25 | #include <linux/slab.h> |
87d68730 DA |
26 | |
27 | static struct platform_driver syscon_driver; | |
28 | ||
bdb0066d PD |
29 | static DEFINE_SPINLOCK(syscon_list_slock); |
30 | static LIST_HEAD(syscon_list); | |
31 | ||
87d68730 | 32 | struct syscon { |
bdb0066d | 33 | struct device_node *np; |
87d68730 | 34 | struct regmap *regmap; |
7d1e3bd9 | 35 | struct reset_control *reset; |
bdb0066d PD |
36 | struct list_head list; |
37 | }; | |
38 | ||
c131045d | 39 | static const struct regmap_config syscon_regmap_config = { |
bdb0066d PD |
40 | .reg_bits = 32, |
41 | .val_bits = 32, | |
42 | .reg_stride = 4, | |
87d68730 DA |
43 | }; |
44 | ||
7d1e3bd9 | 45 | static struct syscon *of_syscon_register(struct device_node *np, bool check_res) |
87d68730 | 46 | { |
a00406b7 | 47 | struct clk *clk; |
bdb0066d PD |
48 | struct syscon *syscon; |
49 | struct regmap *regmap; | |
50 | void __iomem *base; | |
db2fb60c | 51 | u32 reg_io_width; |
bdb0066d PD |
52 | int ret; |
53 | struct regmap_config syscon_config = syscon_regmap_config; | |
ca668f0e | 54 | struct resource res; |
7d1e3bd9 | 55 | struct reset_control *reset; |
bdb0066d | 56 | |
bdb0066d PD |
57 | syscon = kzalloc(sizeof(*syscon), GFP_KERNEL); |
58 | if (!syscon) | |
59 | return ERR_PTR(-ENOMEM); | |
60 | ||
ca668f0e PZ |
61 | if (of_address_to_resource(np, 0, &res)) { |
62 | ret = -ENOMEM; | |
63 | goto err_map; | |
64 | } | |
65 | ||
452d0741 | 66 | base = of_iomap(np, 0); |
bdb0066d PD |
67 | if (!base) { |
68 | ret = -ENOMEM; | |
69 | goto err_map; | |
70 | } | |
71 | ||
ca4582c2 JD |
72 | /* Parse the device's DT node for an endianness specification */ |
73 | if (of_property_read_bool(np, "big-endian")) | |
74 | syscon_config.val_format_endian = REGMAP_ENDIAN_BIG; | |
75 | else if (of_property_read_bool(np, "little-endian")) | |
76 | syscon_config.val_format_endian = REGMAP_ENDIAN_LITTLE; | |
77 | else if (of_property_read_bool(np, "native-endian")) | |
78 | syscon_config.val_format_endian = REGMAP_ENDIAN_NATIVE; | |
79 | ||
db2fb60c DR |
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, "reg-io-width", ®_io_width); | |
86 | if (ret) | |
87 | reg_io_width = 4; | |
88 | ||
3bafc09e BW |
89 | ret = of_hwspin_lock_get_id(np, 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); | |
df561f66 | 101 | fallthrough; |
3bafc09e BW |
102 | case -EPROBE_DEFER: |
103 | goto err_regmap; | |
104 | } | |
105 | } | |
106 | ||
7ff7d5ff | 107 | syscon_config.name = kasprintf(GFP_KERNEL, "%pOFn@%pa", np, &res.start); |
db2fb60c DR |
108 | syscon_config.reg_stride = reg_io_width; |
109 | syscon_config.val_bits = reg_io_width * 8; | |
ca668f0e | 110 | syscon_config.max_register = resource_size(&res) - reg_io_width; |
db2fb60c | 111 | |
bdb0066d | 112 | regmap = regmap_init_mmio(NULL, base, &syscon_config); |
56a11881 | 113 | kfree(syscon_config.name); |
bdb0066d PD |
114 | if (IS_ERR(regmap)) { |
115 | pr_err("regmap init failed\n"); | |
116 | ret = PTR_ERR(regmap); | |
117 | goto err_regmap; | |
118 | } | |
119 | ||
7d1e3bd9 | 120 | if (check_res) { |
39233b7c PC |
121 | clk = of_clk_get(np, 0); |
122 | if (IS_ERR(clk)) { | |
123 | ret = PTR_ERR(clk); | |
124 | /* clock is optional */ | |
125 | if (ret != -ENOENT) | |
126 | goto err_clk; | |
127 | } else { | |
128 | ret = regmap_mmio_attach_clk(regmap, clk); | |
129 | if (ret) | |
7d1e3bd9 | 130 | goto err_attach_clk; |
39233b7c | 131 | } |
7d1e3bd9 JK |
132 | |
133 | reset = of_reset_control_get_optional_exclusive(np, NULL); | |
134 | if (IS_ERR(reset)) { | |
135 | ret = PTR_ERR(reset); | |
136 | goto err_attach_clk; | |
137 | } | |
138 | ||
139 | ret = reset_control_deassert(reset); | |
140 | if (ret) | |
141 | goto err_reset; | |
a00406b7 FG |
142 | } |
143 | ||
bdb0066d PD |
144 | syscon->regmap = regmap; |
145 | syscon->np = np; | |
146 | ||
147 | spin_lock(&syscon_list_slock); | |
148 | list_add_tail(&syscon->list, &syscon_list); | |
149 | spin_unlock(&syscon_list_slock); | |
87d68730 | 150 | |
bdb0066d PD |
151 | return syscon; |
152 | ||
7d1e3bd9 JK |
153 | err_reset: |
154 | reset_control_put(reset); | |
155 | err_attach_clk: | |
a00406b7 FG |
156 | if (!IS_ERR(clk)) |
157 | clk_put(clk); | |
158 | err_clk: | |
159 | regmap_exit(regmap); | |
bdb0066d PD |
160 | err_regmap: |
161 | iounmap(base); | |
162 | err_map: | |
163 | kfree(syscon); | |
164 | return ERR_PTR(ret); | |
87d68730 DA |
165 | } |
166 | ||
39233b7c | 167 | static struct regmap *device_node_get_regmap(struct device_node *np, |
7d1e3bd9 | 168 | bool check_res) |
87d68730 | 169 | { |
bdb0066d | 170 | struct syscon *entry, *syscon = NULL; |
87d68730 | 171 | |
bdb0066d | 172 | spin_lock(&syscon_list_slock); |
87d68730 | 173 | |
bdb0066d PD |
174 | list_for_each_entry(entry, &syscon_list, list) |
175 | if (entry->np == np) { | |
176 | syscon = entry; | |
177 | break; | |
178 | } | |
179 | ||
180 | spin_unlock(&syscon_list_slock); | |
181 | ||
182 | if (!syscon) | |
7d1e3bd9 | 183 | syscon = of_syscon_register(np, check_res); |
bdb0066d PD |
184 | |
185 | if (IS_ERR(syscon)) | |
186 | return ERR_CAST(syscon); | |
87d68730 DA |
187 | |
188 | return syscon->regmap; | |
189 | } | |
39233b7c PC |
190 | |
191 | struct regmap *device_node_to_regmap(struct device_node *np) | |
192 | { | |
193 | return device_node_get_regmap(np, 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(np, "syscon")) | |
200 | return ERR_PTR(-EINVAL); | |
201 | ||
202 | return device_node_get_regmap(np, true); | |
203 | } | |
87d68730 DA |
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, s); | |
212 | if (!syscon_np) | |
213 | return ERR_PTR(-ENODEV); | |
214 | ||
215 | regmap = syscon_node_to_regmap(syscon_np); | |
216 | of_node_put(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 | ||
45330bb4 PD |
228 | if (property) |
229 | syscon_np = of_parse_phandle(np, property, 0); | |
230 | else | |
231 | syscon_np = np; | |
232 | ||
87d68730 DA |
233 | if (!syscon_np) |
234 | return ERR_PTR(-ENODEV); | |
235 | ||
236 | regmap = syscon_node_to_regmap(syscon_np); | |
237 | of_node_put(syscon_np); | |
238 | ||
239 | return regmap; | |
240 | } | |
241 | EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle); | |
242 | ||
6a24f567 OZ |
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, property, arg_count, | |
255 | 0, &args); | |
256 | if (rc) | |
257 | return ERR_PTR(rc); | |
258 | ||
259 | syscon_np = args.np; | |
260 | if (!syscon_np) | |
261 | return ERR_PTR(-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(syscon_np); | |
267 | ||
268 | return regmap; | |
269 | } | |
270 | EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle_args); | |
271 | ||
86b9d170 EBS |
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(regmap) && PTR_ERR(regmap) == -ENODEV) | |
284 | return NULL; | |
285 | ||
286 | return regmap; | |
287 | } | |
288 | EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle_optional); | |
289 | ||
f791be49 | 290 | static int syscon_probe(struct platform_device *pdev) |
87d68730 DA |
291 | { |
292 | struct device *dev = &pdev->dev; | |
29f9b6cf | 293 | struct syscon_platform_data *pdata = dev_get_platdata(dev); |
87d68730 | 294 | struct syscon *syscon; |
c131045d | 295 | struct regmap_config syscon_config = syscon_regmap_config; |
5ab3a89a | 296 | struct resource *res; |
f10111cc | 297 | void __iomem *base; |
87d68730 | 298 | |
5ab3a89a | 299 | syscon = devm_kzalloc(dev, sizeof(*syscon), GFP_KERNEL); |
87d68730 DA |
300 | if (!syscon) |
301 | return -ENOMEM; | |
302 | ||
5ab3a89a AS |
303 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
304 | if (!res) | |
305 | return -ENOENT; | |
87d68730 | 306 | |
f10111cc AS |
307 | base = devm_ioremap(dev, res->start, resource_size(res)); |
308 | if (!base) | |
5ab3a89a | 309 | return -ENOMEM; |
87d68730 | 310 | |
f2a19c5b | 311 | syscon_config.max_register = resource_size(res) - 4; |
29f9b6cf | 312 | if (pdata) |
c131045d PZ |
313 | syscon_config.name = pdata->label; |
314 | syscon->regmap = devm_regmap_init_mmio(dev, base, &syscon_config); | |
87d68730 DA |
315 | if (IS_ERR(syscon->regmap)) { |
316 | dev_err(dev, "regmap init failed\n"); | |
317 | return PTR_ERR(syscon->regmap); | |
318 | } | |
319 | ||
87d68730 DA |
320 | platform_set_drvdata(pdev, syscon); |
321 | ||
38d8974e | 322 | dev_dbg(dev, "regmap %pR registered\n", res); |
87d68730 DA |
323 | |
324 | return 0; | |
325 | } | |
326 | ||
5ab3a89a AS |
327 | static const struct platform_device_id syscon_ids[] = { |
328 | { "syscon", }, | |
329 | { } | |
330 | }; | |
87d68730 DA |
331 | |
332 | static struct platform_driver syscon_driver = { | |
333 | .driver = { | |
334 | .name = "syscon", | |
87d68730 DA |
335 | }, |
336 | .probe = syscon_probe, | |
5ab3a89a | 337 | .id_table = syscon_ids, |
87d68730 DA |
338 | }; |
339 | ||
340 | static int __init syscon_init(void) | |
341 | { | |
342 | return platform_driver_register(&syscon_driver); | |
343 | } | |
344 | postcore_initcall(syscon_init); |