1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Copyright (c) 2018 JJ Hiblot <jjhiblot@ti.com>
4 */
5
6#include <common.h>
7#include <command.h>
8#include <dm.h>
9#include <dm/device-internal.h>
10#include <dm/lists.h>
11#include <dm/root.h>
12#include <dm/uclass-internal.h>
13
14static int bind_by_class_index(const char *uclass, int index,
15			       const char *drv_name)
16{
17	static enum uclass_id uclass_id;
18	struct udevice *dev;
19	struct udevice *parent;
20	int ret;
21	struct driver *drv;
22
23	drv = lists_driver_lookup_name(drv_name);
24	if (!drv) {
25		printf("Cannot find driver '%s'\n", drv_name);
26		return -ENOENT;
27	}
28
29	uclass_id = uclass_get_by_name(uclass);
30	if (uclass_id == UCLASS_INVALID) {
31		printf("%s is not a valid uclass\n", uclass);
32		return -EINVAL;
33	}
34
35	ret = uclass_find_device(uclass_id, index, &parent);
36	if (!parent || ret) {
37		printf("Cannot find device %d of class %s\n", index, uclass);
38		return ret;
39	}
40
41	ret = device_bind_with_driver_data(parent, drv, drv->name, 0,
42					   ofnode_null(), &dev);
43	if (!dev || ret) {
44		printf("Unable to bind. err:%d\n", ret);
45		return ret;
46	}
47
48	return 0;
49}
50
51static int find_dev(const char *uclass, int index, struct udevice **devp)
52{
53	static enum uclass_id uclass_id;
54	int rc;
55
56	uclass_id = uclass_get_by_name(uclass);
57	if (uclass_id == UCLASS_INVALID) {
58		printf("%s is not a valid uclass\n", uclass);
59		return -EINVAL;
60	}
61
62	rc = uclass_find_device(uclass_id, index, devp);
63	if (!*devp || rc) {
64		printf("Cannot find device %d of class %s\n", index, uclass);
65		return rc;
66	}
67
68	return 0;
69}
70
71static int unbind_by_class_index(const char *uclass, int index)
72{
73	int ret;
74	struct udevice *dev;
75
76	ret = find_dev(uclass, index, &dev);
77	if (ret)
78		return ret;
79
80	ret = device_remove(dev, DM_REMOVE_NORMAL);
81	if (ret) {
82		printf("Unable to remove. err:%d\n", ret);
83		return ret;
84	}
85
86	ret = device_unbind(dev);
87	if (ret) {
88		printf("Unable to unbind. err:%d\n", ret);
89		return ret;
90	}
91
92	return 0;
93}
94
95static int unbind_child_by_class_index(const char *uclass, int index,
96				       const char *drv_name)
97{
98	struct udevice *parent;
99	int ret;
100	struct driver *drv;
101
102	drv = lists_driver_lookup_name(drv_name);
103	if (!drv) {
104		printf("Cannot find driver '%s'\n", drv_name);
105		return -ENOENT;
106	}
107
108	ret = find_dev(uclass, index, &parent);
109	if (ret)
110		return ret;
111
112	ret = device_chld_remove(parent, drv, DM_REMOVE_NORMAL);
113	if (ret)
114		printf("Unable to remove all. err:%d\n", ret);
115
116	ret = device_chld_unbind(parent, drv);
117	if (ret)
118		printf("Unable to unbind all. err:%d\n", ret);
119
120	return ret;
121}
122
123static int bind_by_node_path(const char *path, const char *drv_name)
124{
125	struct udevice *dev;
126	struct udevice *parent = NULL;
127	int ret;
128	ofnode ofnode;
129	struct driver *drv;
130
131	drv = lists_driver_lookup_name(drv_name);
132	if (!drv) {
133		printf("%s is not a valid driver name\n", drv_name);
134		return -ENOENT;
135	}
136
137	ofnode = ofnode_path(path);
138	if (!ofnode_valid(ofnode)) {
139		printf("%s is not a valid node path\n", path);
140		return -EINVAL;
141	}
142
143	while (ofnode_valid(ofnode)) {
144		if (!device_find_global_by_ofnode(ofnode, &parent))
145			break;
146		ofnode = ofnode_get_parent(ofnode);
147	}
148
149	if (!parent) {
150		printf("Cannot find a parent device for node path %s\n", path);
151		return -ENODEV;
152	}
153
154	ofnode = ofnode_path(path);
155	ret = lists_bind_fdt(parent, ofnode, &dev, drv, false);
156
157	if (!dev || ret) {
158		printf("Unable to bind. err:%d\n", ret);
159		return ret;
160	}
161
162	return 0;
163}
164
165static int unbind_by_node_path(const char *path)
166{
167	struct udevice *dev;
168	int ret;
169	ofnode ofnode;
170
171	ofnode = ofnode_path(path);
172	if (!ofnode_valid(ofnode)) {
173		printf("%s is not a valid node path\n", path);
174		return -EINVAL;
175	}
176
177	ret = device_find_global_by_ofnode(ofnode, &dev);
178
179	if (!dev || ret) {
180		printf("Cannot find a device with path %s\n", path);
181		return -ENODEV;
182	}
183
184	ret = device_remove(dev, DM_REMOVE_NORMAL);
185	if (ret) {
186		printf("Unable to remove. err:%d\n", ret);
187		return ret;
188	}
189
190	ret = device_unbind(dev);
191	if (ret) {
192		printf("Unable to unbind. err:%d\n", ret);
193		return ret;
194	}
195
196	return 0;
197}
198
199static int do_bind_unbind(struct cmd_tbl *cmdtp, int flag, int argc,
200			  char *const argv[])
201{
202	int ret = 0;
203	bool bind;
204	bool by_node;
205
206	if (argc < 2)
207		return CMD_RET_USAGE;
208
209	bind = (argv[0][0] == 'b');
210	by_node = (argv[1][0] == '/');
211
212	if (by_node && bind) {
213		if (argc != 3)
214			return CMD_RET_USAGE;
215		ret = bind_by_node_path(argv[1], argv[2]);
216	} else if (by_node && !bind) {
217		if (argc != 2)
218			return CMD_RET_USAGE;
219		ret = unbind_by_node_path(argv[1]);
220	} else if (!by_node && bind) {
221		int index = (argc > 2) ? dectoul(argv[2], NULL) : 0;
222
223		if (argc != 4)
224			return CMD_RET_USAGE;
225		ret = bind_by_class_index(argv[1], index, argv[3]);
226	} else if (!by_node && !bind) {
227		int index = (argc > 2) ? dectoul(argv[2], NULL) : 0;
228
229		if (argc == 3)
230			ret = unbind_by_class_index(argv[1], index);
231		else if (argc == 4)
232			ret = unbind_child_by_class_index(argv[1], index,
233							  argv[3]);
234		else
235			return CMD_RET_USAGE;
236	}
237
238	if (ret)
239		return CMD_RET_FAILURE;
240	else
241		return CMD_RET_SUCCESS;
242}
243
244U_BOOT_CMD(
245	bind,	4,	0,	do_bind_unbind,
246	"Bind a device to a driver",
247	"<node path> <driver>\n"
248	"bind <class> <index> <driver>\n"
249	"Use 'dm tree' to list all devices registered in the driver model,\n"
250	"their path, class, index and current driver.\n"
251);
252
253U_BOOT_CMD(
254	unbind,	4,	0,	do_bind_unbind,
255	"Unbind a device from a driver",
256	"<node path>\n"
257	"unbind <class> <index>\n"
258	"unbind <class> <index> <driver>\n"
259	"Use 'dm tree' to list all devices registered in the driver model,\n"
260	"their path, class, index and current driver.\n"
261);
262