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