blob: 55ba281be0d784c26c3f13a95bd36e32b466c1f1 [file] [log] [blame]
Simon Glass6494d702014-02-26 15:59:18 -07001/*
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 */
33static int device_chld_unbind(struct device *dev)
34{
35 struct device *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 */
54static int device_chld_remove(struct device *dev)
55{
56 struct device *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
70int device_bind(struct device *parent, struct driver *drv, const char *name,
71 void *platdata, int of_offset, struct device **devp)
72{
73 struct device *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 device));
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
126fail_bind:
127 list_del(&dev->sibling_node);
128 free(dev);
129 return ret;
130}
131
132int device_bind_by_name(struct device *parent, const struct driver_info *info,
133 struct device **devp)
134{
135 struct driver *drv;
136
137 drv = lists_driver_lookup_name(info->name);
138 if (!drv)
139 return -ENOENT;
140
141 return device_bind(parent, drv, info->name, (void *)info->platdata,
142 -1, devp);
143}
144
145int device_unbind(struct device *dev)
146{
147 struct driver *drv;
148 int ret;
149
150 if (!dev)
151 return -EINVAL;
152
153 if (dev->flags & DM_FLAG_ACTIVATED)
154 return -EINVAL;
155
156 drv = dev->driver;
157 assert(drv);
158
159 if (drv->unbind) {
160 ret = drv->unbind(dev);
161 if (ret)
162 return ret;
163 }
164
165 ret = device_chld_unbind(dev);
166 if (ret)
167 return ret;
168
169 ret = uclass_unbind_device(dev);
170 if (ret)
171 return ret;
172
173 if (dev->parent)
174 list_del(&dev->sibling_node);
175 free(dev);
176
177 return 0;
178}
179
180/**
181 * device_free() - Free memory buffers allocated by a device
182 * @dev: Device that is to be started
183 */
184static void device_free(struct device *dev)
185{
186 int size;
187
188 if (dev->driver->priv_auto_alloc_size) {
189 free(dev->priv);
190 dev->priv = NULL;
191 }
192 if (dev->flags & DM_FLAG_ALLOC_PDATA) {
193 free(dev->platdata);
194 dev->platdata = NULL;
195 }
196 size = dev->uclass->uc_drv->per_device_auto_alloc_size;
197 if (size) {
198 free(dev->uclass_priv);
199 dev->uclass_priv = NULL;
200 }
201}
202
203int device_probe(struct device *dev)
204{
205 struct driver *drv;
206 int size = 0;
207 int ret;
208
209 if (!dev)
210 return -EINVAL;
211
212 if (dev->flags & DM_FLAG_ACTIVATED)
213 return 0;
214
215 drv = dev->driver;
216 assert(drv);
217
218 /* Allocate private data and platdata if requested */
219 if (drv->priv_auto_alloc_size) {
220 dev->priv = calloc(1, drv->priv_auto_alloc_size);
221 if (!dev->priv) {
222 ret = -ENOMEM;
223 goto fail;
224 }
225 }
226 /* Allocate private data if requested */
227 if (dev->flags & DM_FLAG_ALLOC_PDATA) {
228 dev->platdata = calloc(1, drv->platdata_auto_alloc_size);
229 if (!dev->platdata) {
230 ret = -ENOMEM;
231 goto fail;
232 }
233 }
234 size = dev->uclass->uc_drv->per_device_auto_alloc_size;
235 if (size) {
236 dev->uclass_priv = calloc(1, size);
237 if (!dev->uclass_priv) {
238 ret = -ENOMEM;
239 goto fail;
240 }
241 }
242
243 /* Ensure all parents are probed */
244 if (dev->parent) {
245 ret = device_probe(dev->parent);
246 if (ret)
247 goto fail;
248 }
249
250 if (drv->ofdata_to_platdata && dev->of_offset >= 0) {
251 ret = drv->ofdata_to_platdata(dev);
252 if (ret)
253 goto fail;
254 }
255
256 if (drv->probe) {
257 ret = drv->probe(dev);
258 if (ret)
259 goto fail;
260 }
261
262 dev->flags |= DM_FLAG_ACTIVATED;
263
264 ret = uclass_post_probe_device(dev);
265 if (ret) {
266 dev->flags &= ~DM_FLAG_ACTIVATED;
267 goto fail_uclass;
268 }
269
270 return 0;
271fail_uclass:
272 if (device_remove(dev)) {
273 dm_warn("%s: Device '%s' failed to remove on error path\n",
274 __func__, dev->name);
275 }
276fail:
277 device_free(dev);
278
279 return ret;
280}
281
282int device_remove(struct device *dev)
283{
284 struct driver *drv;
285 int ret;
286
287 if (!dev)
288 return -EINVAL;
289
290 if (!(dev->flags & DM_FLAG_ACTIVATED))
291 return 0;
292
293 drv = dev->driver;
294 assert(drv);
295
296 ret = uclass_pre_remove_device(dev);
297 if (ret)
298 return ret;
299
300 ret = device_chld_remove(dev);
301 if (ret)
302 goto err;
303
304 if (drv->remove) {
305 ret = drv->remove(dev);
306 if (ret)
307 goto err_remove;
308 }
309
310 device_free(dev);
311
312 dev->flags &= ~DM_FLAG_ACTIVATED;
313
314 return 0;
315
316err_remove:
317 /* We can't put the children back */
318 dm_warn("%s: Device '%s' failed to remove, but children are gone\n",
319 __func__, dev->name);
320err:
321 ret = uclass_post_probe_device(dev);
322 if (ret) {
323 dm_warn("%s: Device '%s' failed to post_probe on error path\n",
324 __func__, dev->name);
325 }
326
327 return ret;
328}
329
330void *dev_get_platdata(struct device *dev)
331{
332 if (!dev) {
333 dm_warn("%s: null device", __func__);
334 return NULL;
335 }
336
337 return dev->platdata;
338}
339
340void *dev_get_priv(struct device *dev)
341{
342 if (!dev) {
343 dm_warn("%s: null device", __func__);
344 return NULL;
345 }
346
347 return dev->priv;
348}