]>
Commit | Line | Data |
---|---|---|
83d290c5 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
3ac435d3 SG |
2 | /* |
3 | * Device manager | |
4 | * | |
5 | * Copyright (c) 2014 Google, Inc | |
6 | * | |
7 | * (C) Copyright 2012 | |
8 | * Pavel Herrmann <[email protected]> | |
3ac435d3 SG |
9 | */ |
10 | ||
c51d2e70 SG |
11 | #define LOG_CATEGORY LOGC_DM |
12 | ||
3ac435d3 SG |
13 | #include <common.h> |
14 | #include <errno.h> | |
8474da94 | 15 | #include <log.h> |
3ac435d3 SG |
16 | #include <malloc.h> |
17 | #include <dm/device.h> | |
18 | #include <dm/device-internal.h> | |
19 | #include <dm/uclass.h> | |
20 | #include <dm/uclass-internal.h> | |
21 | #include <dm/util.h> | |
52edfed6 | 22 | #include <power-domain.h> |
401d1c4f | 23 | #include <asm/global_data.h> |
3ac435d3 | 24 | |
3be9bcb0 | 25 | int device_chld_unbind(struct udevice *dev, struct driver *drv) |
3ac435d3 SG |
26 | { |
27 | struct udevice *pos, *n; | |
28 | int ret, saved_ret = 0; | |
29 | ||
30 | assert(dev); | |
31 | ||
501a9a7e | 32 | device_foreach_child_safe(pos, n, dev) { |
3be9bcb0 JJH |
33 | if (drv && (pos->driver != drv)) |
34 | continue; | |
35 | ||
3ac435d3 | 36 | ret = device_unbind(pos); |
8474da94 SG |
37 | if (ret && !saved_ret) { |
38 | log_warning("device '%s' failed to unbind\n", | |
39 | pos->name); | |
3ac435d3 | 40 | saved_ret = ret; |
8474da94 | 41 | } |
3ac435d3 SG |
42 | } |
43 | ||
8474da94 | 44 | return log_ret(saved_ret); |
3ac435d3 SG |
45 | } |
46 | ||
3be9bcb0 JJH |
47 | int device_chld_remove(struct udevice *dev, struct driver *drv, |
48 | uint flags) | |
3ac435d3 SG |
49 | { |
50 | struct udevice *pos, *n; | |
cc6f4c8f | 51 | int result = 0; |
3ac435d3 SG |
52 | |
53 | assert(dev); | |
54 | ||
501a9a7e | 55 | device_foreach_child_safe(pos, n, dev) { |
cc6f4c8f MV |
56 | int ret; |
57 | ||
3be9bcb0 JJH |
58 | if (drv && (pos->driver != drv)) |
59 | continue; | |
60 | ||
706865af | 61 | ret = device_remove(pos, flags); |
cc6f4c8f MV |
62 | if (ret == -EPROBE_DEFER) |
63 | result = ret; | |
64 | else if (ret && ret != -EKEYREJECTED) | |
3ac435d3 SG |
65 | return ret; |
66 | } | |
67 | ||
cc6f4c8f | 68 | return result; |
3ac435d3 SG |
69 | } |
70 | ||
71 | int device_unbind(struct udevice *dev) | |
72 | { | |
3479253d | 73 | const struct driver *drv; |
3ac435d3 SG |
74 | int ret; |
75 | ||
76 | if (!dev) | |
8474da94 | 77 | return log_msg_ret("dev", -EINVAL); |
3ac435d3 | 78 | |
73466df3 | 79 | if (dev_get_flags(dev) & DM_FLAG_ACTIVATED) |
8474da94 | 80 | return log_msg_ret("active", -EINVAL); |
3ac435d3 | 81 | |
73466df3 | 82 | if (!(dev_get_flags(dev) & DM_FLAG_BOUND)) |
8474da94 | 83 | return log_msg_ret("not-bound", -EINVAL); |
aed1a4dd | 84 | |
3ac435d3 SG |
85 | drv = dev->driver; |
86 | assert(drv); | |
87 | ||
88 | if (drv->unbind) { | |
89 | ret = drv->unbind(dev); | |
90 | if (ret) | |
8474da94 | 91 | return log_msg_ret("unbind", ret); |
3ac435d3 SG |
92 | } |
93 | ||
3be9bcb0 | 94 | ret = device_chld_unbind(dev, NULL); |
3ac435d3 | 95 | if (ret) |
8474da94 | 96 | return log_msg_ret("child unbind", ret); |
3ac435d3 | 97 | |
80443183 SG |
98 | ret = uclass_pre_unbind_device(dev); |
99 | if (ret) | |
100 | return log_msg_ret("uc", ret); | |
73466df3 | 101 | if (dev_get_flags(dev) & DM_FLAG_ALLOC_PDATA) { |
0fd3d911 SG |
102 | free(dev_get_plat(dev)); |
103 | dev_set_plat(dev, NULL); | |
f8a85449 | 104 | } |
73466df3 | 105 | if (dev_get_flags(dev) & DM_FLAG_ALLOC_UCLASS_PDATA) { |
0fd3d911 | 106 | free(dev_get_uclass_plat(dev)); |
89ba6d55 | 107 | dev_set_uclass_plat(dev, NULL); |
5eaed880 | 108 | } |
73466df3 | 109 | if (dev_get_flags(dev) & DM_FLAG_ALLOC_PARENT_PDATA) { |
0fd3d911 | 110 | free(dev_get_parent_plat(dev)); |
89ba6d55 | 111 | dev_set_parent_plat(dev, NULL); |
cdc133bd | 112 | } |
3ac435d3 SG |
113 | ret = uclass_unbind_device(dev); |
114 | if (ret) | |
8474da94 | 115 | return log_msg_ret("uc", ret); |
3ac435d3 SG |
116 | |
117 | if (dev->parent) | |
118 | list_del(&dev->sibling_node); | |
608f26c5 MY |
119 | |
120 | devres_release_all(dev); | |
121 | ||
73466df3 | 122 | if (dev_get_flags(dev) & DM_FLAG_NAME_ALLOCED) |
a2040fac | 123 | free((char *)dev->name); |
3ac435d3 SG |
124 | free(dev); |
125 | ||
126 | return 0; | |
127 | } | |
128 | ||
129 | /** | |
130 | * device_free() - Free memory buffers allocated by a device | |
131 | * @dev: Device that is to be started | |
132 | */ | |
133 | void device_free(struct udevice *dev) | |
134 | { | |
135 | int size; | |
136 | ||
41575d8e | 137 | if (dev->driver->priv_auto) { |
0fd3d911 SG |
138 | free(dev_get_priv(dev)); |
139 | dev_set_priv(dev, NULL); | |
3ac435d3 | 140 | } |
41575d8e | 141 | size = dev->uclass->uc_drv->per_device_auto; |
3ac435d3 | 142 | if (size) { |
0fd3d911 | 143 | free(dev_get_uclass_priv(dev)); |
89ba6d55 | 144 | dev_set_uclass_priv(dev, NULL); |
3ac435d3 SG |
145 | } |
146 | if (dev->parent) { | |
41575d8e | 147 | size = dev->parent->driver->per_child_auto; |
80443183 SG |
148 | if (!size) |
149 | size = dev->parent->uclass->uc_drv->per_child_auto; | |
3ac435d3 | 150 | if (size) { |
0fd3d911 | 151 | free(dev_get_parent_priv(dev)); |
89ba6d55 | 152 | dev_set_parent_priv(dev, NULL); |
3ac435d3 SG |
153 | } |
154 | } | |
73466df3 | 155 | dev_bic_flags(dev, DM_FLAG_PLATDATA_VALID); |
608f26c5 MY |
156 | |
157 | devres_release_probe(dev); | |
3ac435d3 SG |
158 | } |
159 | ||
c51d2e70 SG |
160 | /** |
161 | * flags_remove() - Figure out whether to remove a device | |
162 | * | |
cc6f4c8f MV |
163 | * If this is called with @flags == DM_REMOVE_NON_VITAL | DM_REMOVE_ACTIVE_DMA, |
164 | * then it returns 0 (=go head and remove) if the device is not matked vital | |
165 | * but is marked DM_REMOVE_ACTIVE_DMA. | |
166 | * | |
167 | * If this is called with @flags == DM_REMOVE_ACTIVE_DMA, | |
168 | * then it returns 0 (=go head and remove) if the device is marked | |
169 | * DM_REMOVE_ACTIVE_DMA, regardless of whether it is marked vital. | |
170 | * | |
c51d2e70 SG |
171 | * @flags: Flags passed to device_remove() |
172 | * @drv_flags: Driver flags | |
185f812c | 173 | * Return: 0 if the device should be removed, |
c51d2e70 SG |
174 | * -EKEYREJECTED if @flags includes a flag in DM_REMOVE_ACTIVE_ALL but |
175 | * @drv_flags does not (indicates that this device has nothing to do for | |
176 | * DMA shutdown or OS prepare) | |
cc6f4c8f MV |
177 | * -EPROBE_DEFER if @flags is DM_REMOVE_NON_VITAL but @drv_flags contains |
178 | * DM_FLAG_VITAL (indicates the device is vital and should not be removed) | |
c51d2e70 SG |
179 | */ |
180 | static int flags_remove(uint flags, uint drv_flags) | |
426f99fa | 181 | { |
cc6f4c8f MV |
182 | if (!(flags & DM_REMOVE_NORMAL)) { |
183 | bool vital_match; | |
184 | bool active_match; | |
185 | ||
186 | active_match = !(flags & DM_REMOVE_ACTIVE_ALL) || | |
187 | (drv_flags & flags); | |
188 | vital_match = !(flags & DM_REMOVE_NON_VITAL) || | |
189 | !(drv_flags & DM_FLAG_VITAL); | |
190 | if (!vital_match) | |
191 | return -EPROBE_DEFER; | |
192 | if (!active_match) | |
193 | return -EKEYREJECTED; | |
194 | } | |
426f99fa | 195 | |
cc6f4c8f | 196 | return 0; |
426f99fa SR |
197 | } |
198 | ||
706865af | 199 | int device_remove(struct udevice *dev, uint flags) |
3ac435d3 | 200 | { |
3479253d | 201 | const struct driver *drv; |
3ac435d3 SG |
202 | int ret; |
203 | ||
204 | if (!dev) | |
205 | return -EINVAL; | |
206 | ||
73466df3 | 207 | if (!(dev_get_flags(dev) & DM_FLAG_ACTIVATED)) |
3ac435d3 SG |
208 | return 0; |
209 | ||
5b896ed5 SG |
210 | ret = device_notify(dev, EVT_DM_PRE_REMOVE); |
211 | if (ret) | |
212 | return ret; | |
213 | ||
c51d2e70 SG |
214 | /* |
215 | * If the child returns EKEYREJECTED, continue. It just means that it | |
216 | * didn't match the flags. | |
217 | */ | |
218 | ret = device_chld_remove(dev, NULL, flags); | |
219 | if (ret && ret != -EKEYREJECTED) | |
220 | return ret; | |
221 | ||
222 | /* | |
223 | * Remove the device if called with the "normal" remove flag set, | |
224 | * or if the remove flag matches any of the drivers remove flags | |
225 | */ | |
3ac435d3 SG |
226 | drv = dev->driver; |
227 | assert(drv); | |
c51d2e70 SG |
228 | ret = flags_remove(flags, drv->flags); |
229 | if (ret) { | |
230 | log_debug("%s: When removing: flags=%x, drv->flags=%x, err=%d\n", | |
231 | dev->name, flags, drv->flags, ret); | |
3ac435d3 | 232 | return ret; |
c51d2e70 | 233 | } |
3ac435d3 | 234 | |
b1f25fcf | 235 | ret = uclass_pre_remove_device(dev); |
3ac435d3 | 236 | if (ret) |
b1f25fcf | 237 | return ret; |
3ac435d3 | 238 | |
c51d2e70 | 239 | if (drv->remove) { |
3ac435d3 SG |
240 | ret = drv->remove(dev); |
241 | if (ret) | |
242 | goto err_remove; | |
243 | } | |
244 | ||
245 | if (dev->parent && dev->parent->driver->child_post_remove) { | |
246 | ret = dev->parent->driver->child_post_remove(dev); | |
247 | if (ret) { | |
248 | dm_warn("%s: Device '%s' failed child_post_remove()", | |
249 | __func__, dev->name); | |
250 | } | |
251 | } | |
252 | ||
ced10804 SG |
253 | if (!(flags & DM_REMOVE_NO_PD) && |
254 | !(drv->flags & | |
a547fcb8 | 255 | (DM_FLAG_DEFAULT_PD_CTRL_OFF | DM_FLAG_LEAVE_PD_ON)) && |
5349e255 | 256 | dev != gd->cur_serial_dev) |
52edfed6 AG |
257 | dev_power_domain_off(dev); |
258 | ||
c51d2e70 | 259 | device_free(dev); |
3ac435d3 | 260 | |
c51d2e70 | 261 | dev_bic_flags(dev, DM_FLAG_ACTIVATED); |
3ac435d3 | 262 | |
5b896ed5 SG |
263 | ret = device_notify(dev, EVT_DM_POST_REMOVE); |
264 | if (ret) | |
265 | goto err_remove; | |
266 | ||
c51d2e70 | 267 | return 0; |
3ac435d3 SG |
268 | |
269 | err_remove: | |
270 | /* We can't put the children back */ | |
271 | dm_warn("%s: Device '%s' failed to remove, but children are gone\n", | |
272 | __func__, dev->name); | |
3ac435d3 SG |
273 | |
274 | return ret; | |
275 | } |