diff options
Diffstat (limited to 'drivers/core/device.c')
| -rw-r--r-- | drivers/core/device.c | 169 | 
1 files changed, 166 insertions, 3 deletions
| diff --git a/drivers/core/device.c b/drivers/core/device.c index c73c339d18c..166b0732ab9 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; @@ -129,14 +147,16 @@ fail_bind:  	return ret;  } -int device_bind_by_name(struct udevice *parent, const struct driver_info *info, -			struct udevice **devp) +int device_bind_by_name(struct udevice *parent, bool pre_reloc_only, +			const struct driver_info *info, struct udevice **devp)  {  	struct driver *drv;  	drv = lists_driver_lookup_name(info->name);  	if (!drv)  		return -ENOENT; +	if (pre_reloc_only && !(drv->flags & DM_FLAG_PRE_RELOC)) +		return -EPERM;  	return device_bind(parent, drv, info->name, (void *)info->platdata,  			   -1, devp); @@ -198,6 +218,13 @@ static void device_free(struct udevice *dev)  		free(dev->uclass_priv);  		dev->uclass_priv = NULL;  	} +	if (dev->parent) { +		size = dev->parent->driver->per_child_auto_alloc_size; +		if (size) { +			free(dev->parent_priv); +			dev->parent_priv = NULL; +		} +	}  }  int device_probe(struct udevice *dev) @@ -205,6 +232,7 @@ int device_probe(struct udevice *dev)  	struct driver *drv;  	int size = 0;  	int ret; +	int seq;  	if (!dev)  		return -EINVAL; @@ -242,11 +270,33 @@ int device_probe(struct udevice *dev)  	/* Ensure all parents are probed */  	if (dev->parent) { +		size = dev->parent->driver->per_child_auto_alloc_size; +		if (size) { +			dev->parent_priv = calloc(1, size); +			if (!dev->parent_priv) { +				ret = -ENOMEM; +				goto fail; +			} +		} +  		ret = device_probe(dev->parent);  		if (ret)  			goto fail;  	} +	seq = uclass_resolve_seq(dev); +	if (seq < 0) { +		ret = seq; +		goto fail; +	} +	dev->seq = seq; + +	if (dev->parent && dev->parent->driver->child_pre_probe) { +		ret = dev->parent->driver->child_pre_probe(dev); +		if (ret) +			goto fail; +	} +  	if (drv->ofdata_to_platdata && dev->of_offset >= 0) {  		ret = drv->ofdata_to_platdata(dev);  		if (ret) @@ -274,6 +324,7 @@ fail_uclass:  			__func__, dev->name);  	}  fail: +	dev->seq = -1;  	device_free(dev);  	return ret; @@ -307,11 +358,20 @@ int device_remove(struct udevice *dev)  			goto err_remove;  	} +	if (dev->parent && dev->parent->driver->child_post_remove) { +		ret = dev->parent->driver->child_post_remove(dev); +		if (ret) { +			dm_warn("%s: Device '%s' failed child_post_remove()", +				__func__, dev->name); +		} +	} +  	device_free(dev); +	dev->seq = -1;  	dev->flags &= ~DM_FLAG_ACTIVATED; -	return 0; +	return ret;  err_remove:  	/* We can't put the children back */ @@ -346,3 +406,106 @@ void *dev_get_priv(struct udevice *dev)  	return dev->priv;  } + +void *dev_get_parentdata(struct udevice *dev) +{ +	if (!dev) { +		dm_warn("%s: null device", __func__); +		return NULL; +	} + +	return dev->parent_priv; +} + +static int device_get_device_tail(struct udevice *dev, int ret, +				  struct udevice **devp) +{ +	if (ret) +		return ret; + +	ret = device_probe(dev); +	if (ret) +		return ret; + +	*devp = dev; + +	return 0; +} + +int device_get_child(struct udevice *parent, int index, struct udevice **devp) +{ +	struct udevice *dev; + +	list_for_each_entry(dev, &parent->child_head, sibling_node) { +		if (!index--) +			return device_get_device_tail(dev, 0, devp); +	} + +	return -ENODEV; +} + +int device_find_child_by_seq(struct udevice *parent, int seq_or_req_seq, +			     bool find_req_seq, struct udevice **devp) +{ +	struct udevice *dev; + +	*devp = NULL; +	if (seq_or_req_seq == -1) +		return -ENODEV; + +	list_for_each_entry(dev, &parent->child_head, sibling_node) { +		if ((find_req_seq ? dev->req_seq : dev->seq) == +				seq_or_req_seq) { +			*devp = dev; +			return 0; +		} +	} + +	return -ENODEV; +} + +int device_get_child_by_seq(struct udevice *parent, int seq, +			    struct udevice **devp) +{ +	struct udevice *dev; +	int ret; + +	*devp = NULL; +	ret = device_find_child_by_seq(parent, 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 = device_find_child_by_seq(parent, seq, true, &dev); +	} +	return device_get_device_tail(dev, ret, devp); +} + +int device_find_child_by_of_offset(struct udevice *parent, int of_offset, +				   struct udevice **devp) +{ +	struct udevice *dev; + +	*devp = NULL; + +	list_for_each_entry(dev, &parent->child_head, sibling_node) { +		if (dev->of_offset == of_offset) { +			*devp = dev; +			return 0; +		} +	} + +	return -ENODEV; +} + +int device_get_child_by_of_offset(struct udevice *parent, int seq, +				  struct udevice **devp) +{ +	struct udevice *dev; +	int ret; + +	*devp = NULL; +	ret = device_find_child_by_of_offset(parent, seq, &dev); +	return device_get_device_tail(dev, ret, devp); +} | 
