]>
Commit | Line | Data |
---|---|---|
6494d708 SG |
1 | /* |
2 | * Copyright (c) 2013 Google, Inc | |
3 | * | |
4 | * (C) Copyright 2012 | |
5 | * Pavel Herrmann <morpheus.ibis@gmail.com> | |
6 | * | |
7 | * SPDX-License-Identifier: GPL-2.0+ | |
8 | */ | |
9 | ||
10 | #include <common.h> | |
11 | #include <errno.h> | |
12 | #include <malloc.h> | |
13 | #include <dm/device.h> | |
14 | #include <dm/device-internal.h> | |
15 | #include <dm/lists.h> | |
16 | #include <dm/uclass.h> | |
17 | #include <dm/uclass-internal.h> | |
18 | #include <dm/util.h> | |
19 | ||
20 | DECLARE_GLOBAL_DATA_PTR; | |
21 | ||
22 | struct uclass *uclass_find(enum uclass_id key) | |
23 | { | |
24 | struct uclass *uc; | |
25 | ||
26 | /* | |
27 | * TODO(sjg@chromium.org): Optimise this, perhaps moving the found | |
28 | * node to the start of the list, or creating a linear array mapping | |
29 | * id to node. | |
30 | */ | |
31 | list_for_each_entry(uc, &gd->uclass_root, sibling_node) { | |
32 | if (uc->uc_drv->id == key) | |
33 | return uc; | |
34 | } | |
35 | ||
36 | return NULL; | |
37 | } | |
38 | ||
39 | /** | |
40 | * uclass_add() - Create new uclass in list | |
41 | * @id: Id number to create | |
42 | * @ucp: Returns pointer to uclass, or NULL on error | |
43 | * @return 0 on success, -ve on error | |
44 | * | |
45 | * The new uclass is added to the list. There must be only one uclass for | |
46 | * each id. | |
47 | */ | |
48 | static int uclass_add(enum uclass_id id, struct uclass **ucp) | |
49 | { | |
50 | struct uclass_driver *uc_drv; | |
51 | struct uclass *uc; | |
52 | int ret; | |
53 | ||
54 | *ucp = NULL; | |
55 | uc_drv = lists_uclass_lookup(id); | |
56 | if (!uc_drv) { | |
57 | dm_warn("Cannot find uclass for id %d: please add the UCLASS_DRIVER() declaration for this UCLASS_... id\n", | |
58 | id); | |
59 | return -ENOENT; | |
60 | } | |
61 | if (uc_drv->ops) { | |
62 | dm_warn("No ops for uclass id %d\n", id); | |
63 | return -EINVAL; | |
64 | } | |
65 | uc = calloc(1, sizeof(*uc)); | |
66 | if (!uc) | |
67 | return -ENOMEM; | |
68 | if (uc_drv->priv_auto_alloc_size) { | |
69 | uc->priv = calloc(1, uc_drv->priv_auto_alloc_size); | |
70 | if (!uc->priv) { | |
71 | ret = -ENOMEM; | |
72 | goto fail_mem; | |
73 | } | |
74 | } | |
75 | uc->uc_drv = uc_drv; | |
76 | INIT_LIST_HEAD(&uc->sibling_node); | |
77 | INIT_LIST_HEAD(&uc->dev_head); | |
89876a55 | 78 | list_add(&uc->sibling_node, &DM_UCLASS_ROOT_NON_CONST); |
6494d708 SG |
79 | |
80 | if (uc_drv->init) { | |
81 | ret = uc_drv->init(uc); | |
82 | if (ret) | |
83 | goto fail; | |
84 | } | |
85 | ||
86 | *ucp = uc; | |
87 | ||
88 | return 0; | |
89 | fail: | |
90 | if (uc_drv->priv_auto_alloc_size) { | |
91 | free(uc->priv); | |
92 | uc->priv = NULL; | |
93 | } | |
94 | list_del(&uc->sibling_node); | |
95 | fail_mem: | |
96 | free(uc); | |
97 | ||
98 | return ret; | |
99 | } | |
100 | ||
101 | int uclass_destroy(struct uclass *uc) | |
102 | { | |
103 | struct uclass_driver *uc_drv; | |
54c5d08a | 104 | struct udevice *dev, *tmp; |
6494d708 SG |
105 | int ret; |
106 | ||
107 | list_for_each_entry_safe(dev, tmp, &uc->dev_head, uclass_node) { | |
108 | ret = device_remove(dev); | |
109 | if (ret) | |
110 | return ret; | |
111 | ret = device_unbind(dev); | |
112 | if (ret) | |
113 | return ret; | |
114 | } | |
115 | ||
116 | uc_drv = uc->uc_drv; | |
117 | if (uc_drv->destroy) | |
118 | uc_drv->destroy(uc); | |
119 | list_del(&uc->sibling_node); | |
120 | if (uc_drv->priv_auto_alloc_size) | |
121 | free(uc->priv); | |
122 | free(uc); | |
123 | ||
124 | return 0; | |
125 | } | |
126 | ||
127 | int uclass_get(enum uclass_id id, struct uclass **ucp) | |
128 | { | |
129 | struct uclass *uc; | |
130 | ||
131 | *ucp = NULL; | |
132 | uc = uclass_find(id); | |
133 | if (!uc) | |
134 | return uclass_add(id, ucp); | |
135 | *ucp = uc; | |
136 | ||
137 | return 0; | |
138 | } | |
139 | ||
54c5d08a | 140 | int uclass_find_device(enum uclass_id id, int index, struct udevice **devp) |
6494d708 SG |
141 | { |
142 | struct uclass *uc; | |
54c5d08a | 143 | struct udevice *dev; |
6494d708 SG |
144 | int ret; |
145 | ||
146 | *devp = NULL; | |
147 | ret = uclass_get(id, &uc); | |
148 | if (ret) | |
149 | return ret; | |
150 | ||
151 | list_for_each_entry(dev, &uc->dev_head, uclass_node) { | |
152 | if (!index--) { | |
153 | *devp = dev; | |
154 | return 0; | |
155 | } | |
156 | } | |
157 | ||
158 | return -ENODEV; | |
159 | } | |
160 | ||
54c5d08a | 161 | int uclass_get_device(enum uclass_id id, int index, struct udevice **devp) |
6494d708 | 162 | { |
54c5d08a | 163 | struct udevice *dev; |
6494d708 SG |
164 | int ret; |
165 | ||
166 | *devp = NULL; | |
167 | ret = uclass_find_device(id, index, &dev); | |
168 | if (ret) | |
169 | return ret; | |
170 | ||
171 | ret = device_probe(dev); | |
172 | if (ret) | |
173 | return ret; | |
174 | ||
175 | *devp = dev; | |
176 | ||
177 | return 0; | |
178 | } | |
179 | ||
54c5d08a | 180 | int uclass_first_device(enum uclass_id id, struct udevice **devp) |
6494d708 SG |
181 | { |
182 | struct uclass *uc; | |
54c5d08a | 183 | struct udevice *dev; |
6494d708 SG |
184 | int ret; |
185 | ||
186 | *devp = NULL; | |
187 | ret = uclass_get(id, &uc); | |
188 | if (ret) | |
189 | return ret; | |
190 | if (list_empty(&uc->dev_head)) | |
191 | return 0; | |
192 | ||
54c5d08a | 193 | dev = list_first_entry(&uc->dev_head, struct udevice, uclass_node); |
6494d708 SG |
194 | ret = device_probe(dev); |
195 | if (ret) | |
196 | return ret; | |
197 | *devp = dev; | |
198 | ||
199 | return 0; | |
200 | } | |
201 | ||
54c5d08a | 202 | int uclass_next_device(struct udevice **devp) |
6494d708 | 203 | { |
54c5d08a | 204 | struct udevice *dev = *devp; |
6494d708 SG |
205 | int ret; |
206 | ||
207 | *devp = NULL; | |
208 | if (list_is_last(&dev->uclass_node, &dev->uclass->dev_head)) | |
209 | return 0; | |
210 | ||
54c5d08a HS |
211 | dev = list_entry(dev->uclass_node.next, struct udevice, |
212 | uclass_node); | |
6494d708 SG |
213 | ret = device_probe(dev); |
214 | if (ret) | |
215 | return ret; | |
216 | *devp = dev; | |
217 | ||
218 | return 0; | |
219 | } | |
220 | ||
54c5d08a | 221 | int uclass_bind_device(struct udevice *dev) |
6494d708 SG |
222 | { |
223 | struct uclass *uc; | |
224 | int ret; | |
225 | ||
226 | uc = dev->uclass; | |
227 | ||
228 | list_add_tail(&dev->uclass_node, &uc->dev_head); | |
229 | ||
230 | if (uc->uc_drv->post_bind) { | |
231 | ret = uc->uc_drv->post_bind(dev); | |
232 | if (ret) { | |
233 | list_del(&dev->uclass_node); | |
234 | return ret; | |
235 | } | |
236 | } | |
237 | ||
238 | return 0; | |
239 | } | |
240 | ||
54c5d08a | 241 | int uclass_unbind_device(struct udevice *dev) |
6494d708 SG |
242 | { |
243 | struct uclass *uc; | |
244 | int ret; | |
245 | ||
246 | uc = dev->uclass; | |
247 | if (uc->uc_drv->pre_unbind) { | |
248 | ret = uc->uc_drv->pre_unbind(dev); | |
249 | if (ret) | |
250 | return ret; | |
251 | } | |
252 | ||
253 | list_del(&dev->uclass_node); | |
254 | return 0; | |
255 | } | |
256 | ||
54c5d08a | 257 | int uclass_post_probe_device(struct udevice *dev) |
6494d708 SG |
258 | { |
259 | struct uclass_driver *uc_drv = dev->uclass->uc_drv; | |
260 | ||
261 | if (uc_drv->post_probe) | |
262 | return uc_drv->post_probe(dev); | |
263 | ||
264 | return 0; | |
265 | } | |
266 | ||
54c5d08a | 267 | int uclass_pre_remove_device(struct udevice *dev) |
6494d708 SG |
268 | { |
269 | struct uclass_driver *uc_drv; | |
270 | struct uclass *uc; | |
271 | int ret; | |
272 | ||
273 | uc = dev->uclass; | |
274 | uc_drv = uc->uc_drv; | |
275 | if (uc->uc_drv->pre_remove) { | |
276 | ret = uc->uc_drv->pre_remove(dev); | |
277 | if (ret) | |
278 | return ret; | |
279 | } | |
280 | if (uc_drv->per_device_auto_alloc_size) { | |
281 | free(dev->uclass_priv); | |
282 | dev->uclass_priv = NULL; | |
283 | } | |
284 | ||
285 | return 0; | |
286 | } |