1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Copyright (C) 2015 Google, Inc
4 * Written by Simon Glass <sjg@chromium.org>
5 */
6
7#define LOG_CATEGORY UCLASS_SYSCON
8
9#include <log.h>
10#include <syscon.h>
11#include <dm.h>
12#include <errno.h>
13#include <regmap.h>
14#include <dm/device-internal.h>
15#include <dm/device_compat.h>
16#include <dm/lists.h>
17#include <dm/root.h>
18#include <linux/err.h>
19
20/*
21 * Caution:
22 * This API requires the given device has already been bound to the syscon
23 * driver. For example,
24 *
25 *    compatible = "syscon", "simple-mfd";
26 *
27 * works, but
28 *
29 *    compatible = "simple-mfd", "syscon";
30 *
31 * does not. The behavior is different from Linux.
32 */
33struct regmap *syscon_get_regmap(struct udevice *dev)
34{
35	struct syscon_uc_info *priv;
36
37	if (device_get_uclass_id(dev) != UCLASS_SYSCON)
38		return ERR_PTR(-ENOEXEC);
39	priv = dev_get_uclass_priv(dev);
40	return priv->regmap;
41}
42
43static int syscon_pre_probe(struct udevice *dev)
44{
45	struct syscon_uc_info *priv = dev_get_uclass_priv(dev);
46
47	/* Special case for PCI devices, which don't have a regmap */
48	if (device_get_uclass_id(dev->parent) == UCLASS_PCI)
49		return 0;
50
51#if CONFIG_IS_ENABLED(OF_PLATDATA)
52	/*
53	 * With OF_PLATDATA we really have no way of knowing the format of
54	 * the device-specific platform data. So we assume that it starts with
55	 * a 'reg' member that holds a single address and size. Drivers
56	 * using OF_PLATDATA will need to ensure that this is true. In case of
57	 * odd reg structures other then the syscon_base_plat structure
58	 * below the regmap must be defined in the individual syscon driver.
59	 */
60	struct syscon_base_plat {
61		phys_addr_t reg[2];
62	};
63
64	struct syscon_base_plat *plat = dev_get_plat(dev);
65
66	/*
67	 * Return if the regmap is already defined in the individual
68	 * syscon driver.
69	 */
70	if (priv->regmap)
71		return 0;
72
73	return regmap_init_mem_plat(dev, plat->reg, sizeof(plat->reg[0]),
74				    ARRAY_SIZE(plat->reg) / 2, &priv->regmap);
75#else
76	return regmap_init_mem(dev_ofnode(dev), &priv->regmap);
77#endif
78}
79
80static int syscon_probe_by_ofnode(ofnode node, struct udevice **devp)
81{
82	struct udevice *dev, *parent;
83	int ret;
84
85	/* found node with "syscon" compatible, not bounded to SYSCON UCLASS */
86	if (!ofnode_device_is_compatible(node, "syscon")) {
87		log_debug("invalid compatible for syscon device\n");
88		return -EINVAL;
89	}
90
91	/* bound to driver with same ofnode or to root if not found */
92	if (device_find_global_by_ofnode(node, &parent))
93		parent = dm_root();
94
95	/* force bound to syscon class */
96	ret = device_bind_driver_to_node(parent, "syscon",
97					 ofnode_get_name(node),
98					 node, &dev);
99	if (ret) {
100		dev_dbg(dev, "unable to bound syscon device\n");
101		return ret;
102	}
103	ret = device_probe(dev);
104	if (ret) {
105		dev_dbg(dev, "unable to probe syscon device\n");
106		return ret;
107	}
108
109	*devp = dev;
110	return 0;
111}
112
113struct regmap *syscon_regmap_lookup_by_phandle(struct udevice *dev,
114					       const char *name)
115{
116	struct udevice *syscon;
117	struct regmap *r;
118	u32 phandle;
119	ofnode node;
120	int err;
121
122	err = uclass_get_device_by_phandle(UCLASS_SYSCON, dev,
123					   name, &syscon);
124	if (err) {
125		/* found node with "syscon" compatible, not bounded to SYSCON */
126		err = ofnode_read_u32(dev_ofnode(dev), name, &phandle);
127		if (err)
128			return ERR_PTR(err);
129
130		node = ofnode_get_by_phandle(phandle);
131		if (!ofnode_valid(node)) {
132			dev_dbg(dev, "unable to find syscon device\n");
133			return ERR_PTR(-EINVAL);
134		}
135		err = syscon_probe_by_ofnode(node, &syscon);
136		if (err)
137			return ERR_PTR(-ENODEV);
138	}
139
140	r = syscon_get_regmap(syscon);
141	if (!r) {
142		dev_dbg(dev, "unable to find regmap\n");
143		return ERR_PTR(-ENODEV);
144	}
145
146	return r;
147}
148
149int syscon_get_by_driver_data(ulong driver_data, struct udevice **devp)
150{
151	int ret;
152
153	*devp = NULL;
154
155	ret = uclass_first_device_drvdata(UCLASS_SYSCON, driver_data, devp);
156	if (ret)
157		return ret;
158
159	return 0;
160}
161
162struct regmap *syscon_get_regmap_by_driver_data(ulong driver_data)
163{
164	struct syscon_uc_info *priv;
165	struct udevice *dev;
166	int ret;
167
168	ret = syscon_get_by_driver_data(driver_data, &dev);
169	if (ret)
170		return ERR_PTR(ret);
171	priv = dev_get_uclass_priv(dev);
172
173	return priv->regmap;
174}
175
176void *syscon_get_first_range(ulong driver_data)
177{
178	struct regmap *map;
179
180	map = syscon_get_regmap_by_driver_data(driver_data);
181	if (IS_ERR(map))
182		return map;
183	return regmap_get_range(map, 0);
184}
185
186UCLASS_DRIVER(syscon) = {
187	.id		= UCLASS_SYSCON,
188	.name		= "syscon",
189	.per_device_auto	= sizeof(struct syscon_uc_info),
190	.pre_probe = syscon_pre_probe,
191};
192
193static const struct udevice_id generic_syscon_ids[] = {
194	{ .compatible = "syscon" },
195	{ }
196};
197
198U_BOOT_DRIVER(generic_syscon) = {
199	.name	= "syscon",
200	.id	= UCLASS_SYSCON,
201#if CONFIG_IS_ENABLED(OF_REAL)
202	.bind           = dm_scan_fdt_dev,
203#endif
204	.of_match = generic_syscon_ids,
205};
206
207/*
208 * Linux-compatible syscon-to-regmap
209 * The syscon node can be bound to another driver, but still works
210 * as a syscon provider.
211 */
212struct regmap *syscon_node_to_regmap(ofnode node)
213{
214	struct udevice *dev;
215	struct regmap *r;
216
217	if (uclass_get_device_by_ofnode(UCLASS_SYSCON, node, &dev))
218		if (syscon_probe_by_ofnode(node, &dev))
219			return ERR_PTR(-ENODEV);
220
221	r = syscon_get_regmap(dev);
222	if (!r) {
223		dev_dbg(dev, "unable to find regmap\n");
224		return ERR_PTR(-ENODEV);
225	}
226
227	return r;
228}
229