summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorSimon Glass <sjg@chromium.org>2014-07-23 06:55:12 -0600
committerSimon Glass <sjg@chromium.org>2014-07-23 14:07:25 +0100
commit5a66a8ff86d923367ca9a1f6168e976fbde27391 (patch)
tree429ae5e3e439c59972c845673ee3d48ef23d416e /drivers
parent4e8bc211703d3c93689367745e8c07dc22c68dfc (diff)
dm: Introduce device sequence numbering
In U-Boot it is pretty common to number devices from 0 and access them on the command line using this numbering. While it may come to pass that we will move away from this numbering, the possibility seems remote at present. Given that devices within a uclass will have an implied numbering, it makes sense to build this into driver model as a core feature. The cost is fairly small in terms of code and data space. With each uclass having numbered devices we can ask for SPI port 0 or serial port 1 and receive a single device. Devices typically request a sequence number using aliases in the device tree. These are resolved when the device is probed, to deal with conflicts. Sequence numbers need not be sequential and holes are permitted. At present there is no support for sequence numbers using static platform data. It could easily be added to 'struct driver_info' if needed, but it seems better to add features as we find a use for them, and the use of -1 to mean 'no sequence' makes the default value somewhat painful. Signed-off-by: Simon Glass <sjg@chromium.org>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/core/device.c28
-rw-r--r--drivers/core/uclass.c78
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;
}