]> git.kernelconcepts.de Git - karo-tx-uboot.git/blob - drivers/core/device.c
dm: Allow drivers to be marked 'before relocation'
[karo-tx-uboot.git] / drivers / core / device.c
1 /*
2  * Device manager
3  *
4  * Copyright (c) 2013 Google, Inc
5  *
6  * (C) Copyright 2012
7  * Pavel Herrmann <morpheus.ibis@gmail.com>
8  *
9  * SPDX-License-Identifier:     GPL-2.0+
10  */
11
12 #include <common.h>
13 #include <malloc.h>
14 #include <dm/device.h>
15 #include <dm/device-internal.h>
16 #include <dm/lists.h>
17 #include <dm/platdata.h>
18 #include <dm/uclass.h>
19 #include <dm/uclass-internal.h>
20 #include <dm/util.h>
21 #include <linux/err.h>
22 #include <linux/list.h>
23
24 /**
25  * device_chld_unbind() - Unbind all device's children from the device
26  *
27  * On error, the function continues to unbind all children, and reports the
28  * first error.
29  *
30  * @dev:        The device that is to be stripped of its children
31  * @return 0 on success, -ve on error
32  */
33 static int device_chld_unbind(struct udevice *dev)
34 {
35         struct udevice *pos, *n;
36         int ret, saved_ret = 0;
37
38         assert(dev);
39
40         list_for_each_entry_safe(pos, n, &dev->child_head, sibling_node) {
41                 ret = device_unbind(pos);
42                 if (ret && !saved_ret)
43                         saved_ret = ret;
44         }
45
46         return saved_ret;
47 }
48
49 /**
50  * device_chld_remove() - Stop all device's children
51  * @dev:        The device whose children are to be removed
52  * @return 0 on success, -ve on error
53  */
54 static int device_chld_remove(struct udevice *dev)
55 {
56         struct udevice *pos, *n;
57         int ret;
58
59         assert(dev);
60
61         list_for_each_entry_safe(pos, n, &dev->child_head, sibling_node) {
62                 ret = device_remove(pos);
63                 if (ret)
64                         return ret;
65         }
66
67         return 0;
68 }
69
70 int device_bind(struct udevice *parent, struct driver *drv, const char *name,
71                 void *platdata, int of_offset, struct udevice **devp)
72 {
73         struct udevice *dev;
74         struct uclass *uc;
75         int ret = 0;
76
77         *devp = NULL;
78         if (!name)
79                 return -EINVAL;
80
81         ret = uclass_get(drv->id, &uc);
82         if (ret)
83                 return ret;
84
85         dev = calloc(1, sizeof(struct udevice));
86         if (!dev)
87                 return -ENOMEM;
88
89         INIT_LIST_HEAD(&dev->sibling_node);
90         INIT_LIST_HEAD(&dev->child_head);
91         INIT_LIST_HEAD(&dev->uclass_node);
92         dev->platdata = platdata;
93         dev->name = name;
94         dev->of_offset = of_offset;
95         dev->parent = parent;
96         dev->driver = drv;
97         dev->uclass = uc;
98         if (!dev->platdata && drv->platdata_auto_alloc_size)
99                 dev->flags |= DM_FLAG_ALLOC_PDATA;
100
101         /* put dev into parent's successor list */
102         if (parent)
103                 list_add_tail(&dev->sibling_node, &parent->child_head);
104
105         ret = uclass_bind_device(dev);
106         if (ret)
107                 goto fail_bind;
108
109         /* if we fail to bind we remove device from successors and free it */
110         if (drv->bind) {
111                 ret = drv->bind(dev);
112                 if (ret) {
113                         if (uclass_unbind_device(dev)) {
114                                 dm_warn("Failed to unbind dev '%s' on error path\n",
115                                         dev->name);
116                         }
117                         goto fail_bind;
118                 }
119         }
120         if (parent)
121                 dm_dbg("Bound device %s to %s\n", dev->name, parent->name);
122         *devp = dev;
123
124         return 0;
125
126 fail_bind:
127         list_del(&dev->sibling_node);
128         free(dev);
129         return ret;
130 }
131
132 int device_bind_by_name(struct udevice *parent, bool pre_reloc_only,
133                         const struct driver_info *info, struct udevice **devp)
134 {
135         struct driver *drv;
136
137         drv = lists_driver_lookup_name(info->name);
138         if (!drv)
139                 return -ENOENT;
140         if (pre_reloc_only && !(drv->flags & DM_FLAG_PRE_RELOC))
141                 return -EPERM;
142
143         return device_bind(parent, drv, info->name, (void *)info->platdata,
144                            -1, devp);
145 }
146
147 int device_unbind(struct udevice *dev)
148 {
149         struct driver *drv;
150         int ret;
151
152         if (!dev)
153                 return -EINVAL;
154
155         if (dev->flags & DM_FLAG_ACTIVATED)
156                 return -EINVAL;
157
158         drv = dev->driver;
159         assert(drv);
160
161         if (drv->unbind) {
162                 ret = drv->unbind(dev);
163                 if (ret)
164                         return ret;
165         }
166
167         ret = device_chld_unbind(dev);
168         if (ret)
169                 return ret;
170
171         ret = uclass_unbind_device(dev);
172         if (ret)
173                 return ret;
174
175         if (dev->parent)
176                 list_del(&dev->sibling_node);
177         free(dev);
178
179         return 0;
180 }
181
182 /**
183  * device_free() - Free memory buffers allocated by a device
184  * @dev:        Device that is to be started
185  */
186 static void device_free(struct udevice *dev)
187 {
188         int size;
189
190         if (dev->driver->priv_auto_alloc_size) {
191                 free(dev->priv);
192                 dev->priv = NULL;
193         }
194         if (dev->flags & DM_FLAG_ALLOC_PDATA) {
195                 free(dev->platdata);
196                 dev->platdata = NULL;
197         }
198         size = dev->uclass->uc_drv->per_device_auto_alloc_size;
199         if (size) {
200                 free(dev->uclass_priv);
201                 dev->uclass_priv = NULL;
202         }
203 }
204
205 int device_probe(struct udevice *dev)
206 {
207         struct driver *drv;
208         int size = 0;
209         int ret;
210
211         if (!dev)
212                 return -EINVAL;
213
214         if (dev->flags & DM_FLAG_ACTIVATED)
215                 return 0;
216
217         drv = dev->driver;
218         assert(drv);
219
220         /* Allocate private data and platdata if requested */
221         if (drv->priv_auto_alloc_size) {
222                 dev->priv = calloc(1, drv->priv_auto_alloc_size);
223                 if (!dev->priv) {
224                         ret = -ENOMEM;
225                         goto fail;
226                 }
227         }
228         /* Allocate private data if requested */
229         if (dev->flags & DM_FLAG_ALLOC_PDATA) {
230                 dev->platdata = calloc(1, drv->platdata_auto_alloc_size);
231                 if (!dev->platdata) {
232                         ret = -ENOMEM;
233                         goto fail;
234                 }
235         }
236         size = dev->uclass->uc_drv->per_device_auto_alloc_size;
237         if (size) {
238                 dev->uclass_priv = calloc(1, size);
239                 if (!dev->uclass_priv) {
240                         ret = -ENOMEM;
241                         goto fail;
242                 }
243         }
244
245         /* Ensure all parents are probed */
246         if (dev->parent) {
247                 ret = device_probe(dev->parent);
248                 if (ret)
249                         goto fail;
250         }
251
252         if (drv->ofdata_to_platdata && dev->of_offset >= 0) {
253                 ret = drv->ofdata_to_platdata(dev);
254                 if (ret)
255                         goto fail;
256         }
257
258         if (drv->probe) {
259                 ret = drv->probe(dev);
260                 if (ret)
261                         goto fail;
262         }
263
264         dev->flags |= DM_FLAG_ACTIVATED;
265
266         ret = uclass_post_probe_device(dev);
267         if (ret) {
268                 dev->flags &= ~DM_FLAG_ACTIVATED;
269                 goto fail_uclass;
270         }
271
272         return 0;
273 fail_uclass:
274         if (device_remove(dev)) {
275                 dm_warn("%s: Device '%s' failed to remove on error path\n",
276                         __func__, dev->name);
277         }
278 fail:
279         device_free(dev);
280
281         return ret;
282 }
283
284 int device_remove(struct udevice *dev)
285 {
286         struct driver *drv;
287         int ret;
288
289         if (!dev)
290                 return -EINVAL;
291
292         if (!(dev->flags & DM_FLAG_ACTIVATED))
293                 return 0;
294
295         drv = dev->driver;
296         assert(drv);
297
298         ret = uclass_pre_remove_device(dev);
299         if (ret)
300                 return ret;
301
302         ret = device_chld_remove(dev);
303         if (ret)
304                 goto err;
305
306         if (drv->remove) {
307                 ret = drv->remove(dev);
308                 if (ret)
309                         goto err_remove;
310         }
311
312         device_free(dev);
313
314         dev->flags &= ~DM_FLAG_ACTIVATED;
315
316         return 0;
317
318 err_remove:
319         /* We can't put the children back */
320         dm_warn("%s: Device '%s' failed to remove, but children are gone\n",
321                 __func__, dev->name);
322 err:
323         ret = uclass_post_probe_device(dev);
324         if (ret) {
325                 dm_warn("%s: Device '%s' failed to post_probe on error path\n",
326                         __func__, dev->name);
327         }
328
329         return ret;
330 }
331
332 void *dev_get_platdata(struct udevice *dev)
333 {
334         if (!dev) {
335                 dm_warn("%s: null device", __func__);
336                 return NULL;
337         }
338
339         return dev->platdata;
340 }
341
342 void *dev_get_priv(struct udevice *dev)
343 {
344         if (!dev) {
345                 dm_warn("%s: null device", __func__);
346                 return NULL;
347         }
348
349         return dev->priv;
350 }