diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/core/device.c | 28 | ||||
-rw-r--r-- | drivers/core/uclass.c | 78 |
2 files changed, 106 insertions, 0 deletions
diff --git a/drivers/core/device.c b/drivers/core/device.c index 86b9ff8911..848ce3b675 100644 --- a/drivers/core/device.c +++ b/drivers/core/device.c @@ -10,6 +10,7 @@ */ #include <common.h> +#include <fdtdec.h> #include <malloc.h> #include <dm/device.h> #include <dm/device-internal.h> @@ -21,6 +22,8 @@ #include <linux/err.h> #include <linux/list.h> +DECLARE_GLOBAL_DATA_PTR; + /** * device_chld_unbind() - Unbind all device's children from the device * @@ -95,6 +98,21 @@ int device_bind(struct udevice *parent, struct driver *drv, const char *name, dev->parent = parent; dev->driver = drv; dev->uclass = uc; + + /* + * For some devices, such as a SPI or I2C bus, the 'reg' property + * is a reasonable indicator of the sequence number. But if there is + * an alias, we use that in preference. In any case, this is just + * a 'requested' sequence, and will be resolved (and ->seq updated) + * when the device is probed. + */ + dev->req_seq = fdtdec_get_int(gd->fdt_blob, of_offset, "reg", -1); + dev->seq = -1; + if (uc->uc_drv->name && of_offset != -1) { + fdtdec_get_alias_seq(gd->fdt_blob, uc->uc_drv->name, of_offset, + &dev->req_seq); + } + if (!dev->platdata && drv->platdata_auto_alloc_size) dev->flags |= DM_FLAG_ALLOC_PDATA; @@ -207,6 +225,7 @@ int device_probe(struct udevice *dev) struct driver *drv; int size = 0; int ret; + int seq; if (!dev) return -EINVAL; @@ -249,6 +268,13 @@ int device_probe(struct udevice *dev) goto fail; } + seq = uclass_resolve_seq(dev); + if (seq < 0) { + ret = seq; + goto fail; + } + dev->seq = seq; + if (drv->ofdata_to_platdata && dev->of_offset >= 0) { ret = drv->ofdata_to_platdata(dev); if (ret) @@ -276,6 +302,7 @@ fail_uclass: __func__, dev->name); } fail: + dev->seq = -1; device_free(dev); return ret; @@ -311,6 +338,7 @@ int device_remove(struct udevice *dev) device_free(dev); + dev->seq = -1; dev->flags &= ~DM_FLAG_ACTIVATED; return 0; diff --git a/drivers/core/uclass.c b/drivers/core/uclass.c index db915267d6..c28cf6795f 100644 --- a/drivers/core/uclass.c +++ b/drivers/core/uclass.c @@ -158,6 +158,35 @@ int uclass_find_device(enum uclass_id id, int index, struct udevice **devp) return -ENODEV; } +int uclass_find_device_by_seq(enum uclass_id id, int seq_or_req_seq, + bool find_req_seq, struct udevice **devp) +{ + struct uclass *uc; + struct udevice *dev; + int ret; + + *devp = NULL; + debug("%s: %d %d\n", __func__, find_req_seq, seq_or_req_seq); + if (seq_or_req_seq == -1) + return -ENODEV; + ret = uclass_get(id, &uc); + if (ret) + return ret; + + list_for_each_entry(dev, &uc->dev_head, uclass_node) { + debug(" - %d %d\n", dev->req_seq, dev->seq); + if ((find_req_seq ? dev->req_seq : dev->seq) == + seq_or_req_seq) { + *devp = dev; + debug(" - found\n"); + return 0; + } + } + debug(" - not found\n"); + + return -ENODEV; +} + /** * uclass_get_device_tail() - handle the end of a get_device call * @@ -193,6 +222,23 @@ int uclass_get_device(enum uclass_id id, int index, struct udevice **devp) return uclass_get_device_tail(dev, ret, devp); } +int uclass_get_device_by_seq(enum uclass_id id, int seq, struct udevice **devp) +{ + struct udevice *dev; + int ret; + + *devp = NULL; + ret = uclass_find_device_by_seq(id, seq, false, &dev); + if (ret == -ENODEV) { + /* + * We didn't find it in probed devices. See if there is one + * that will request this seq if probed. + */ + ret = uclass_find_device_by_seq(id, seq, true, &dev); + } + return uclass_get_device_tail(dev, ret, devp); +} + int uclass_first_device(enum uclass_id id, struct udevice **devp) { struct uclass *uc; @@ -270,6 +316,37 @@ int uclass_unbind_device(struct udevice *dev) return 0; } +int uclass_resolve_seq(struct udevice *dev) +{ + struct udevice *dup; + int seq; + int ret; + + assert(dev->seq == -1); + ret = uclass_find_device_by_seq(dev->uclass->uc_drv->id, dev->req_seq, + false, &dup); + if (!ret) { + dm_warn("Device '%s': seq %d is in use by '%s'\n", + dev->name, dev->req_seq, dup->name); + } else if (ret == -ENODEV) { + /* Our requested sequence number is available */ + if (dev->req_seq != -1) + return dev->req_seq; + } else { + return ret; + } + + for (seq = 0; seq < DM_MAX_SEQ; seq++) { + ret = uclass_find_device_by_seq(dev->uclass->uc_drv->id, seq, + false, &dup); + if (ret == -ENODEV) + break; + if (ret) + return ret; + } + return seq; +} + int uclass_post_probe_device(struct udevice *dev) { struct uclass_driver *uc_drv = dev->uclass->uc_drv; @@ -297,6 +374,7 @@ int uclass_pre_remove_device(struct udevice *dev) free(dev->uclass_priv); dev->uclass_priv = NULL; } + dev->seq = -1; return 0; } |