xref: /linux/drivers/mfd/syscon.c (revision 7f4f3b14e8079ecde096bd734af10e30d40c27b7)
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/io.h>
16 #include <linux/init.h>
17 #include <linux/list.h>
18 #include <linux/of.h>
19 #include <linux/of_address.h>
20 #include <linux/of_platform.h>
21 #include <linux/platform_data/syscon.h>
22 #include <linux/platform_device.h>
23 #include <linux/regmap.h>
24 #include <linux/reset.h>
25 #include <linux/mfd/syscon.h>
26 #include <linux/slab.h>
27 
28 static struct platform_driver syscon_driver;
29 
30 static DEFINE_SPINLOCK(syscon_list_slock);
31 static LIST_HEAD(syscon_list);
32 
33 struct syscon {
34 	struct device_node *np;
35 	struct regmap *regmap;
36 	struct reset_control *reset;
37 	struct list_head list;
38 };
39 
40 static const struct regmap_config syscon_regmap_config = {
41 	.reg_bits = 32,
42 	.val_bits = 32,
43 	.reg_stride = 4,
44 };
45 
46 static struct syscon *of_syscon_register(struct device_node *np, bool check_res)
47 {
48 	struct clk *clk;
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 	struct syscon *syscon __free(kfree) = kzalloc(sizeof(*syscon), GFP_KERNEL);
58 	if (!syscon)
59 		return ERR_PTR(-ENOMEM);
60 
61 	if (of_address_to_resource(np, 0, &res))
62 		return ERR_PTR(-ENOMEM);
63 
64 	base = of_iomap(np, 0);
65 	if (!base)
66 		return ERR_PTR(-ENOMEM);
67 
68 	/* Parse the device's DT node for an endianness specification */
69 	if (of_property_read_bool(np, "big-endian"))
70 		syscon_config.val_format_endian = REGMAP_ENDIAN_BIG;
71 	else if (of_property_read_bool(np, "little-endian"))
72 		syscon_config.val_format_endian = REGMAP_ENDIAN_LITTLE;
73 	else if (of_property_read_bool(np, "native-endian"))
74 		syscon_config.val_format_endian = REGMAP_ENDIAN_NATIVE;
75 
76 	/*
77 	 * search for reg-io-width property in DT. If it is not provided,
78 	 * default to 4 bytes. regmap_init_mmio will return an error if values
79 	 * are invalid so there is no need to check them here.
80 	 */
81 	ret = of_property_read_u32(np, "reg-io-width", &reg_io_width);
82 	if (ret)
83 		reg_io_width = 4;
84 
85 	ret = of_hwspin_lock_get_id(np, 0);
86 	if (ret > 0 || (IS_ENABLED(CONFIG_HWSPINLOCK) && ret == 0)) {
87 		syscon_config.use_hwlock = true;
88 		syscon_config.hwlock_id = ret;
89 		syscon_config.hwlock_mode = HWLOCK_IRQSTATE;
90 	} else if (ret < 0) {
91 		switch (ret) {
92 		case -ENOENT:
93 			/* Ignore missing hwlock, it's optional. */
94 			break;
95 		default:
96 			pr_err("Failed to retrieve valid hwlock: %d\n", ret);
97 			fallthrough;
98 		case -EPROBE_DEFER:
99 			goto err_regmap;
100 		}
101 	}
102 
103 	syscon_config.name = kasprintf(GFP_KERNEL, "%pOFn@%pa", np, &res.start);
104 	if (!syscon_config.name) {
105 		ret = -ENOMEM;
106 		goto err_regmap;
107 	}
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) - reg_io_width;
111 	if (!syscon_config.max_register)
112 		syscon_config.max_register_is_0 = true;
113 
114 	regmap = regmap_init_mmio(NULL, base, &syscon_config);
115 	kfree(syscon_config.name);
116 	if (IS_ERR(regmap)) {
117 		pr_err("regmap init failed\n");
118 		ret = PTR_ERR(regmap);
119 		goto err_regmap;
120 	}
121 
122 	if (check_res) {
123 		clk = of_clk_get(np, 0);
124 		if (IS_ERR(clk)) {
125 			ret = PTR_ERR(clk);
126 			/* clock is optional */
127 			if (ret != -ENOENT)
128 				goto err_clk;
129 		} else {
130 			ret = regmap_mmio_attach_clk(regmap, clk);
131 			if (ret)
132 				goto err_attach_clk;
133 		}
134 
135 		reset = of_reset_control_get_optional_exclusive(np, NULL);
136 		if (IS_ERR(reset)) {
137 			ret = PTR_ERR(reset);
138 			goto err_attach_clk;
139 		}
140 
141 		ret = reset_control_deassert(reset);
142 		if (ret)
143 			goto err_reset;
144 	}
145 
146 	syscon->regmap = regmap;
147 	syscon->np = np;
148 
149 	spin_lock(&syscon_list_slock);
150 	list_add_tail(&syscon->list, &syscon_list);
151 	spin_unlock(&syscon_list_slock);
152 
153 	return_ptr(syscon);
154 
155 err_reset:
156 	reset_control_put(reset);
157 err_attach_clk:
158 	if (!IS_ERR(clk))
159 		clk_put(clk);
160 err_clk:
161 	regmap_exit(regmap);
162 err_regmap:
163 	iounmap(base);
164 	return ERR_PTR(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(&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(&syscon_list_slock);
181 
182 	if (!syscon)
183 		syscon = of_syscon_register(np, check_res);
184 
185 	if (IS_ERR(syscon))
186 		return ERR_CAST(syscon);
187 
188 	return syscon->regmap;
189 }
190 
191 /**
192  * of_syscon_register_regmap() - Register regmap for specified device node
193  * @np: Device tree node
194  * @regmap: Pointer to regmap object
195  *
196  * Register an externally created regmap object with syscon for the specified
197  * device tree node. This regmap will then be returned to client drivers using
198  * the syscon_regmap_lookup_by_phandle() API.
199  *
200  * Return: 0 on success, negative error code on failure.
201  */
202 int of_syscon_register_regmap(struct device_node *np, struct regmap *regmap)
203 {
204 	struct syscon *entry, *syscon = NULL;
205 	int ret;
206 
207 	if (!np || !regmap)
208 		return -EINVAL;
209 
210 	syscon = kzalloc(sizeof(*syscon), GFP_KERNEL);
211 	if (!syscon)
212 		return -ENOMEM;
213 
214 	/* check if syscon entry already exists */
215 	spin_lock(&syscon_list_slock);
216 
217 	list_for_each_entry(entry, &syscon_list, list)
218 		if (entry->np == np) {
219 			ret = -EEXIST;
220 			goto err_unlock;
221 		}
222 
223 	syscon->regmap = regmap;
224 	syscon->np = np;
225 
226 	/* register the regmap in syscon list */
227 	list_add_tail(&syscon->list, &syscon_list);
228 	spin_unlock(&syscon_list_slock);
229 
230 	return 0;
231 
232 err_unlock:
233 	spin_unlock(&syscon_list_slock);
234 	kfree(syscon);
235 	return ret;
236 }
237 EXPORT_SYMBOL_GPL(of_syscon_register_regmap);
238 
239 struct regmap *device_node_to_regmap(struct device_node *np)
240 {
241 	return device_node_get_regmap(np, false);
242 }
243 EXPORT_SYMBOL_GPL(device_node_to_regmap);
244 
245 struct regmap *syscon_node_to_regmap(struct device_node *np)
246 {
247 	if (!of_device_is_compatible(np, "syscon"))
248 		return ERR_PTR(-EINVAL);
249 
250 	return device_node_get_regmap(np, true);
251 }
252 EXPORT_SYMBOL_GPL(syscon_node_to_regmap);
253 
254 struct regmap *syscon_regmap_lookup_by_compatible(const char *s)
255 {
256 	struct device_node *syscon_np;
257 	struct regmap *regmap;
258 
259 	syscon_np = of_find_compatible_node(NULL, NULL, s);
260 	if (!syscon_np)
261 		return ERR_PTR(-ENODEV);
262 
263 	regmap = syscon_node_to_regmap(syscon_np);
264 	of_node_put(syscon_np);
265 
266 	return regmap;
267 }
268 EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_compatible);
269 
270 struct regmap *syscon_regmap_lookup_by_phandle(struct device_node *np,
271 					const char *property)
272 {
273 	struct device_node *syscon_np;
274 	struct regmap *regmap;
275 
276 	if (property)
277 		syscon_np = of_parse_phandle(np, property, 0);
278 	else
279 		syscon_np = np;
280 
281 	if (!syscon_np)
282 		return ERR_PTR(-ENODEV);
283 
284 	regmap = syscon_node_to_regmap(syscon_np);
285 
286 	if (property)
287 		of_node_put(syscon_np);
288 
289 	return regmap;
290 }
291 EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle);
292 
293 struct regmap *syscon_regmap_lookup_by_phandle_args(struct device_node *np,
294 					const char *property,
295 					int arg_count,
296 					unsigned int *out_args)
297 {
298 	struct device_node *syscon_np;
299 	struct of_phandle_args args;
300 	struct regmap *regmap;
301 	unsigned int index;
302 	int rc;
303 
304 	rc = of_parse_phandle_with_fixed_args(np, property, arg_count,
305 			0, &args);
306 	if (rc)
307 		return ERR_PTR(rc);
308 
309 	syscon_np = args.np;
310 	if (!syscon_np)
311 		return ERR_PTR(-ENODEV);
312 
313 	regmap = syscon_node_to_regmap(syscon_np);
314 	for (index = 0; index < arg_count; index++)
315 		out_args[index] = args.args[index];
316 	of_node_put(syscon_np);
317 
318 	return regmap;
319 }
320 EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle_args);
321 
322 /*
323  * It behaves the same as syscon_regmap_lookup_by_phandle() except where
324  * there is no regmap phandle. In this case, instead of returning -ENODEV,
325  * the function returns NULL.
326  */
327 struct regmap *syscon_regmap_lookup_by_phandle_optional(struct device_node *np,
328 					const char *property)
329 {
330 	struct regmap *regmap;
331 
332 	regmap = syscon_regmap_lookup_by_phandle(np, property);
333 	if (IS_ERR(regmap) && PTR_ERR(regmap) == -ENODEV)
334 		return NULL;
335 
336 	return regmap;
337 }
338 EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle_optional);
339 
340 static int syscon_probe(struct platform_device *pdev)
341 {
342 	struct device *dev = &pdev->dev;
343 	struct syscon_platform_data *pdata = dev_get_platdata(dev);
344 	struct syscon *syscon;
345 	struct regmap_config syscon_config = syscon_regmap_config;
346 	struct resource *res;
347 	void __iomem *base;
348 
349 	syscon = devm_kzalloc(dev, sizeof(*syscon), GFP_KERNEL);
350 	if (!syscon)
351 		return -ENOMEM;
352 
353 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
354 	if (!res)
355 		return -ENOENT;
356 
357 	base = devm_ioremap(dev, res->start, resource_size(res));
358 	if (!base)
359 		return -ENOMEM;
360 
361 	syscon_config.max_register = resource_size(res) - 4;
362 	if (!syscon_config.max_register)
363 		syscon_config.max_register_is_0 = true;
364 
365 	if (pdata)
366 		syscon_config.name = pdata->label;
367 	syscon->regmap = devm_regmap_init_mmio(dev, base, &syscon_config);
368 	if (IS_ERR(syscon->regmap)) {
369 		dev_err(dev, "regmap init failed\n");
370 		return PTR_ERR(syscon->regmap);
371 	}
372 
373 	platform_set_drvdata(pdev, syscon);
374 
375 	dev_dbg(dev, "regmap %pR registered\n", res);
376 
377 	return 0;
378 }
379 
380 static const struct platform_device_id syscon_ids[] = {
381 	{ "syscon", },
382 	{ }
383 };
384 
385 static struct platform_driver syscon_driver = {
386 	.driver = {
387 		.name = "syscon",
388 	},
389 	.probe		= syscon_probe,
390 	.id_table	= syscon_ids,
391 };
392 
393 static int __init syscon_init(void)
394 {
395 	return platform_driver_register(&syscon_driver);
396 }
397 postcore_initcall(syscon_init);
398