diff options
Diffstat (limited to 'drivers/core')
| -rw-r--r-- | drivers/core/Makefile | 7 | ||||
| -rw-r--r-- | drivers/core/device.c | 348 | ||||
| -rw-r--r-- | drivers/core/lists.c | 155 | ||||
| -rw-r--r-- | drivers/core/root.c | 102 | ||||
| -rw-r--r-- | drivers/core/uclass.c | 285 | ||||
| -rw-r--r-- | drivers/core/util.c | 37 | 
6 files changed, 934 insertions, 0 deletions
| diff --git a/drivers/core/Makefile b/drivers/core/Makefile new file mode 100644 index 000000000..90b2a7f06 --- /dev/null +++ b/drivers/core/Makefile @@ -0,0 +1,7 @@ +# +# Copyright (c) 2013 Google, Inc +# +# SPDX-License-Identifier:	GPL-2.0+ +# + +obj-$(CONFIG_DM)	:= device.o lists.o root.o uclass.o util.o diff --git a/drivers/core/device.c b/drivers/core/device.c new file mode 100644 index 000000000..55ba281be --- /dev/null +++ b/drivers/core/device.c @@ -0,0 +1,348 @@ +/* + * Device manager + * + * Copyright (c) 2013 Google, Inc + * + * (C) Copyright 2012 + * Pavel Herrmann <morpheus.ibis@gmail.com> + * + * SPDX-License-Identifier:	GPL-2.0+ + */ + +#include <common.h> +#include <malloc.h> +#include <dm/device.h> +#include <dm/device-internal.h> +#include <dm/lists.h> +#include <dm/platdata.h> +#include <dm/uclass.h> +#include <dm/uclass-internal.h> +#include <dm/util.h> +#include <linux/err.h> +#include <linux/list.h> + +/** + * device_chld_unbind() - Unbind all device's children from the device + * + * On error, the function continues to unbind all children, and reports the + * first error. + * + * @dev:	The device that is to be stripped of its children + * @return 0 on success, -ve on error + */ +static int device_chld_unbind(struct device *dev) +{ +	struct device *pos, *n; +	int ret, saved_ret = 0; + +	assert(dev); + +	list_for_each_entry_safe(pos, n, &dev->child_head, sibling_node) { +		ret = device_unbind(pos); +		if (ret && !saved_ret) +			saved_ret = ret; +	} + +	return saved_ret; +} + +/** + * device_chld_remove() - Stop all device's children + * @dev:	The device whose children are to be removed + * @return 0 on success, -ve on error + */ +static int device_chld_remove(struct device *dev) +{ +	struct device *pos, *n; +	int ret; + +	assert(dev); + +	list_for_each_entry_safe(pos, n, &dev->child_head, sibling_node) { +		ret = device_remove(pos); +		if (ret) +			return ret; +	} + +	return 0; +} + +int device_bind(struct device *parent, struct driver *drv, const char *name, +		void *platdata, int of_offset, struct device **devp) +{ +	struct device *dev; +	struct uclass *uc; +	int ret = 0; + +	*devp = NULL; +	if (!name) +		return -EINVAL; + +	ret = uclass_get(drv->id, &uc); +	if (ret) +		return ret; + +	dev = calloc(1, sizeof(struct device)); +	if (!dev) +		return -ENOMEM; + +	INIT_LIST_HEAD(&dev->sibling_node); +	INIT_LIST_HEAD(&dev->child_head); +	INIT_LIST_HEAD(&dev->uclass_node); +	dev->platdata = platdata; +	dev->name = name; +	dev->of_offset = of_offset; +	dev->parent = parent; +	dev->driver = drv; +	dev->uclass = uc; +	if (!dev->platdata && drv->platdata_auto_alloc_size) +		dev->flags |= DM_FLAG_ALLOC_PDATA; + +	/* put dev into parent's successor list */ +	if (parent) +		list_add_tail(&dev->sibling_node, &parent->child_head); + +	ret = uclass_bind_device(dev); +	if (ret) +		goto fail_bind; + +	/* if we fail to bind we remove device from successors and free it */ +	if (drv->bind) { +		ret = drv->bind(dev); +		if (ret) { +			if (uclass_unbind_device(dev)) { +				dm_warn("Failed to unbind dev '%s' on error path\n", +					dev->name); +			} +			goto fail_bind; +		} +	} +	if (parent) +		dm_dbg("Bound device %s to %s\n", dev->name, parent->name); +	*devp = dev; + +	return 0; + +fail_bind: +	list_del(&dev->sibling_node); +	free(dev); +	return ret; +} + +int device_bind_by_name(struct device *parent, const struct driver_info *info, +			struct device **devp) +{ +	struct driver *drv; + +	drv = lists_driver_lookup_name(info->name); +	if (!drv) +		return -ENOENT; + +	return device_bind(parent, drv, info->name, (void *)info->platdata, +			   -1, devp); +} + +int device_unbind(struct device *dev) +{ +	struct driver *drv; +	int ret; + +	if (!dev) +		return -EINVAL; + +	if (dev->flags & DM_FLAG_ACTIVATED) +		return -EINVAL; + +	drv = dev->driver; +	assert(drv); + +	if (drv->unbind) { +		ret = drv->unbind(dev); +		if (ret) +			return ret; +	} + +	ret = device_chld_unbind(dev); +	if (ret) +		return ret; + +	ret = uclass_unbind_device(dev); +	if (ret) +		return ret; + +	if (dev->parent) +		list_del(&dev->sibling_node); +	free(dev); + +	return 0; +} + +/** + * device_free() - Free memory buffers allocated by a device + * @dev:	Device that is to be started + */ +static void device_free(struct device *dev) +{ +	int size; + +	if (dev->driver->priv_auto_alloc_size) { +		free(dev->priv); +		dev->priv = NULL; +	} +	if (dev->flags & DM_FLAG_ALLOC_PDATA) { +		free(dev->platdata); +		dev->platdata = NULL; +	} +	size = dev->uclass->uc_drv->per_device_auto_alloc_size; +	if (size) { +		free(dev->uclass_priv); +		dev->uclass_priv = NULL; +	} +} + +int device_probe(struct device *dev) +{ +	struct driver *drv; +	int size = 0; +	int ret; + +	if (!dev) +		return -EINVAL; + +	if (dev->flags & DM_FLAG_ACTIVATED) +		return 0; + +	drv = dev->driver; +	assert(drv); + +	/* Allocate private data and platdata if requested */ +	if (drv->priv_auto_alloc_size) { +		dev->priv = calloc(1, drv->priv_auto_alloc_size); +		if (!dev->priv) { +			ret = -ENOMEM; +			goto fail; +		} +	} +	/* Allocate private data if requested */ +	if (dev->flags & DM_FLAG_ALLOC_PDATA) { +		dev->platdata = calloc(1, drv->platdata_auto_alloc_size); +		if (!dev->platdata) { +			ret = -ENOMEM; +			goto fail; +		} +	} +	size = dev->uclass->uc_drv->per_device_auto_alloc_size; +	if (size) { +		dev->uclass_priv = calloc(1, size); +		if (!dev->uclass_priv) { +			ret = -ENOMEM; +			goto fail; +		} +	} + +	/* Ensure all parents are probed */ +	if (dev->parent) { +		ret = device_probe(dev->parent); +		if (ret) +			goto fail; +	} + +	if (drv->ofdata_to_platdata && dev->of_offset >= 0) { +		ret = drv->ofdata_to_platdata(dev); +		if (ret) +			goto fail; +	} + +	if (drv->probe) { +		ret = drv->probe(dev); +		if (ret) +			goto fail; +	} + +	dev->flags |= DM_FLAG_ACTIVATED; + +	ret = uclass_post_probe_device(dev); +	if (ret) { +		dev->flags &= ~DM_FLAG_ACTIVATED; +		goto fail_uclass; +	} + +	return 0; +fail_uclass: +	if (device_remove(dev)) { +		dm_warn("%s: Device '%s' failed to remove on error path\n", +			__func__, dev->name); +	} +fail: +	device_free(dev); + +	return ret; +} + +int device_remove(struct device *dev) +{ +	struct driver *drv; +	int ret; + +	if (!dev) +		return -EINVAL; + +	if (!(dev->flags & DM_FLAG_ACTIVATED)) +		return 0; + +	drv = dev->driver; +	assert(drv); + +	ret = uclass_pre_remove_device(dev); +	if (ret) +		return ret; + +	ret = device_chld_remove(dev); +	if (ret) +		goto err; + +	if (drv->remove) { +		ret = drv->remove(dev); +		if (ret) +			goto err_remove; +	} + +	device_free(dev); + +	dev->flags &= ~DM_FLAG_ACTIVATED; + +	return 0; + +err_remove: +	/* We can't put the children back */ +	dm_warn("%s: Device '%s' failed to remove, but children are gone\n", +		__func__, dev->name); +err: +	ret = uclass_post_probe_device(dev); +	if (ret) { +		dm_warn("%s: Device '%s' failed to post_probe on error path\n", +			__func__, dev->name); +	} + +	return ret; +} + +void *dev_get_platdata(struct device *dev) +{ +	if (!dev) { +		dm_warn("%s: null device", __func__); +		return NULL; +	} + +	return dev->platdata; +} + +void *dev_get_priv(struct device *dev) +{ +	if (!dev) { +		dm_warn("%s: null device", __func__); +		return NULL; +	} + +	return dev->priv; +} diff --git a/drivers/core/lists.c b/drivers/core/lists.c new file mode 100644 index 000000000..4f2c12631 --- /dev/null +++ b/drivers/core/lists.c @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2013 Google, Inc + * + * (C) Copyright 2012 + * Marek Vasut <marex@denx.de> + * + * SPDX-License-Identifier:	GPL-2.0+ + */ + +#include <common.h> +#include <errno.h> +#include <dm/device.h> +#include <dm/device-internal.h> +#include <dm/platdata.h> +#include <dm/uclass.h> +#include <dm/util.h> +#include <linux/compiler.h> + +struct driver *lists_driver_lookup_name(const char *name) +{ +	struct driver *drv = +		ll_entry_start(struct driver, driver); +	const int n_ents = ll_entry_count(struct driver, driver); +	struct driver *entry; +	int len; + +	if (!drv || !n_ents) +		return NULL; + +	len = strlen(name); + +	for (entry = drv; entry != drv + n_ents; entry++) { +		if (strncmp(name, entry->name, len)) +			continue; + +		/* Full match */ +		if (len == strlen(entry->name)) +			return entry; +	} + +	/* Not found */ +	return NULL; +} + +struct uclass_driver *lists_uclass_lookup(enum uclass_id id) +{ +	struct uclass_driver *uclass = +		ll_entry_start(struct uclass_driver, uclass); +	const int n_ents = ll_entry_count(struct uclass_driver, uclass); +	struct uclass_driver *entry; + +	if ((id == UCLASS_INVALID) || !uclass) +		return NULL; + +	for (entry = uclass; entry != uclass + n_ents; entry++) { +		if (entry->id == id) +			return entry; +	} + +	return NULL; +} + +int lists_bind_drivers(struct device *parent) +{ +	struct driver_info *info = +		ll_entry_start(struct driver_info, driver_info); +	const int n_ents = ll_entry_count(struct driver_info, driver_info); +	struct driver_info *entry; +	struct device *dev; +	int result = 0; +	int ret; + +	for (entry = info; entry != info + n_ents; entry++) { +		ret = device_bind_by_name(parent, entry, &dev); +		if (ret) { +			dm_warn("No match for driver '%s'\n", entry->name); +			if (!result || ret != -ENOENT) +				result = ret; +		} +	} + +	return result; +} + +#ifdef CONFIG_OF_CONTROL +/** + * driver_check_compatible() - Check if a driver is compatible with this node + * + * @param blob:		Device tree pointer + * @param offset:	Offset of node in device tree + * @param of_matchL	List of compatible strings to match + * @return 0 if there is a match, -ENOENT if no match, -ENODEV if the node + * does not have a compatible string, other error <0 if there is a device + * tree error + */ +static int driver_check_compatible(const void *blob, int offset, +				   const struct device_id *of_match) +{ +	int ret; + +	if (!of_match) +		return -ENOENT; + +	while (of_match->compatible) { +		ret = fdt_node_check_compatible(blob, offset, +						of_match->compatible); +		if (!ret) +			return 0; +		else if (ret == -FDT_ERR_NOTFOUND) +			return -ENODEV; +		else if (ret < 0) +			return -EINVAL; +		of_match++; +	} + +	return -ENOENT; +} + +int lists_bind_fdt(struct device *parent, const void *blob, int offset) +{ +	struct driver *driver = ll_entry_start(struct driver, driver); +	const int n_ents = ll_entry_count(struct driver, driver); +	struct driver *entry; +	struct device *dev; +	const char *name; +	int result = 0; +	int ret; + +	dm_dbg("bind node %s\n", fdt_get_name(blob, offset, NULL)); +	for (entry = driver; entry != driver + n_ents; entry++) { +		ret = driver_check_compatible(blob, offset, entry->of_match); +		if (ret == -ENOENT) { +			continue; +		} else if (ret == -ENODEV) { +			break; +		} else if (ret) { +			dm_warn("Device tree error at offset %d\n", offset); +			if (!result || ret != -ENOENT) +				result = ret; +			break; +		} + +		name = fdt_get_name(blob, offset, NULL); +		dm_dbg("   - found match at '%s'\n", entry->name); +		ret = device_bind(parent, entry, name, NULL, offset, &dev); +		if (ret) { +			dm_warn("No match for driver '%s'\n", entry->name); +			if (!result || ret != -ENOENT) +				result = ret; +		} +	} + +	return result; +} +#endif diff --git a/drivers/core/root.c b/drivers/core/root.c new file mode 100644 index 000000000..407bc0d04 --- /dev/null +++ b/drivers/core/root.c @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2013 Google, Inc + * + * (C) Copyright 2012 + * Pavel Herrmann <morpheus.ibis@gmail.com> + * + * SPDX-License-Identifier:	GPL-2.0+ + */ + +#include <common.h> +#include <errno.h> +#include <malloc.h> +#include <dm/device.h> +#include <dm/device-internal.h> +#include <dm/lists.h> +#include <dm/platdata.h> +#include <dm/uclass.h> +#include <dm/util.h> +#include <linux/list.h> + +DECLARE_GLOBAL_DATA_PTR; + +static const struct driver_info root_info = { +	.name		= "root_driver", +}; + +struct device *dm_root(void) +{ +	if (!gd->dm_root) { +		dm_warn("Virtual root driver does not exist!\n"); +		return NULL; +	} + +	return gd->dm_root; +} + +int dm_init(void) +{ +	int ret; + +	if (gd->dm_root) { +		dm_warn("Virtual root driver already exists!\n"); +		return -EINVAL; +	} +	INIT_LIST_HEAD(&gd->uclass_root); + +	ret = device_bind_by_name(NULL, &root_info, &gd->dm_root); +	if (ret) +		return ret; + +	return 0; +} + +int dm_scan_platdata(void) +{ +	int ret; + +	ret = lists_bind_drivers(gd->dm_root); +	if (ret == -ENOENT) { +		dm_warn("Some drivers were not found\n"); +		ret = 0; +	} +	if (ret) +		return ret; + +	return 0; +} + +#ifdef CONFIG_OF_CONTROL +int dm_scan_fdt(const void *blob) +{ +	int offset = 0; +	int ret = 0, err; +	int depth = 0; + +	do { +		offset = fdt_next_node(blob, offset, &depth); +		if (offset > 0 && depth == 1) { +			err = lists_bind_fdt(gd->dm_root, blob, offset); +			if (err && !ret) +				ret = err; +		} +	} while (offset > 0); + +	if (ret) +		dm_warn("Some drivers failed to bind\n"); + +	return ret; +} +#endif + +/* This is the root driver - all drivers are children of this */ +U_BOOT_DRIVER(root_driver) = { +	.name	= "root_driver", +	.id	= UCLASS_ROOT, +}; + +/* This is the root uclass */ +UCLASS_DRIVER(root) = { +	.name	= "root", +	.id	= UCLASS_ROOT, +}; diff --git a/drivers/core/uclass.c b/drivers/core/uclass.c new file mode 100644 index 000000000..4df5a8bd3 --- /dev/null +++ b/drivers/core/uclass.c @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2013 Google, Inc + * + * (C) Copyright 2012 + * Pavel Herrmann <morpheus.ibis@gmail.com> + * + * SPDX-License-Identifier:	GPL-2.0+ + */ + +#include <common.h> +#include <errno.h> +#include <malloc.h> +#include <dm/device.h> +#include <dm/device-internal.h> +#include <dm/lists.h> +#include <dm/uclass.h> +#include <dm/uclass-internal.h> +#include <dm/util.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct uclass *uclass_find(enum uclass_id key) +{ +	struct uclass *uc; + +	/* +	 * TODO(sjg@chromium.org): Optimise this, perhaps moving the found +	 * node to the start of the list, or creating a linear array mapping +	 * id to node. +	 */ +	list_for_each_entry(uc, &gd->uclass_root, sibling_node) { +		if (uc->uc_drv->id == key) +			return uc; +	} + +	return NULL; +} + +/** + * uclass_add() - Create new uclass in list + * @id: Id number to create + * @ucp: Returns pointer to uclass, or NULL on error + * @return 0 on success, -ve on error + * + * The new uclass is added to the list. There must be only one uclass for + * each id. + */ +static int uclass_add(enum uclass_id id, struct uclass **ucp) +{ +	struct uclass_driver *uc_drv; +	struct uclass *uc; +	int ret; + +	*ucp = NULL; +	uc_drv = lists_uclass_lookup(id); +	if (!uc_drv) { +		dm_warn("Cannot find uclass for id %d: please add the UCLASS_DRIVER() declaration for this UCLASS_... id\n", +			id); +		return -ENOENT; +	} +	if (uc_drv->ops) { +		dm_warn("No ops for uclass id %d\n", id); +		return -EINVAL; +	} +	uc = calloc(1, sizeof(*uc)); +	if (!uc) +		return -ENOMEM; +	if (uc_drv->priv_auto_alloc_size) { +		uc->priv = calloc(1, uc_drv->priv_auto_alloc_size); +		if (!uc->priv) { +			ret = -ENOMEM; +			goto fail_mem; +		} +	} +	uc->uc_drv = uc_drv; +	INIT_LIST_HEAD(&uc->sibling_node); +	INIT_LIST_HEAD(&uc->dev_head); +	list_add(&uc->sibling_node, &gd->uclass_root); + +	if (uc_drv->init) { +		ret = uc_drv->init(uc); +		if (ret) +			goto fail; +	} + +	*ucp = uc; + +	return 0; +fail: +	if (uc_drv->priv_auto_alloc_size) { +		free(uc->priv); +		uc->priv = NULL; +	} +	list_del(&uc->sibling_node); +fail_mem: +	free(uc); + +	return ret; +} + +int uclass_destroy(struct uclass *uc) +{ +	struct uclass_driver *uc_drv; +	struct device *dev, *tmp; +	int ret; + +	list_for_each_entry_safe(dev, tmp, &uc->dev_head, uclass_node) { +		ret = device_remove(dev); +		if (ret) +			return ret; +		ret = device_unbind(dev); +		if (ret) +			return ret; +	} + +	uc_drv = uc->uc_drv; +	if (uc_drv->destroy) +		uc_drv->destroy(uc); +	list_del(&uc->sibling_node); +	if (uc_drv->priv_auto_alloc_size) +		free(uc->priv); +	free(uc); + +	return 0; +} + +int uclass_get(enum uclass_id id, struct uclass **ucp) +{ +	struct uclass *uc; + +	*ucp = NULL; +	uc = uclass_find(id); +	if (!uc) +		return uclass_add(id, ucp); +	*ucp = uc; + +	return 0; +} + +int uclass_find_device(enum uclass_id id, int index, struct device **devp) +{ +	struct uclass *uc; +	struct device *dev; +	int ret; + +	*devp = NULL; +	ret = uclass_get(id, &uc); +	if (ret) +		return ret; + +	list_for_each_entry(dev, &uc->dev_head, uclass_node) { +		if (!index--) { +			*devp = dev; +			return 0; +		} +	} + +	return -ENODEV; +} + +int uclass_get_device(enum uclass_id id, int index, struct device **devp) +{ +	struct device *dev; +	int ret; + +	*devp = NULL; +	ret = uclass_find_device(id, index, &dev); +	if (ret) +		return ret; + +	ret = device_probe(dev); +	if (ret) +		return ret; + +	*devp = dev; + +	return 0; +} + +int uclass_first_device(enum uclass_id id, struct device **devp) +{ +	struct uclass *uc; +	struct device *dev; +	int ret; + +	*devp = NULL; +	ret = uclass_get(id, &uc); +	if (ret) +		return ret; +	if (list_empty(&uc->dev_head)) +		return 0; + +	dev = list_first_entry(&uc->dev_head, struct device, uclass_node); +	ret = device_probe(dev); +	if (ret) +		return ret; +	*devp = dev; + +	return 0; +} + +int uclass_next_device(struct device **devp) +{ +	struct device *dev = *devp; +	int ret; + +	*devp = NULL; +	if (list_is_last(&dev->uclass_node, &dev->uclass->dev_head)) +		return 0; + +	dev = list_entry(dev->uclass_node.next, struct device, uclass_node); +	ret = device_probe(dev); +	if (ret) +		return ret; +	*devp = dev; + +	return 0; +} + +int uclass_bind_device(struct device *dev) +{ +	struct uclass *uc; +	int ret; + +	uc = dev->uclass; + +	list_add_tail(&dev->uclass_node, &uc->dev_head); + +	if (uc->uc_drv->post_bind) { +		ret = uc->uc_drv->post_bind(dev); +		if (ret) { +			list_del(&dev->uclass_node); +			return ret; +		} +	} + +	return 0; +} + +int uclass_unbind_device(struct device *dev) +{ +	struct uclass *uc; +	int ret; + +	uc = dev->uclass; +	if (uc->uc_drv->pre_unbind) { +		ret = uc->uc_drv->pre_unbind(dev); +		if (ret) +			return ret; +	} + +	list_del(&dev->uclass_node); +	return 0; +} + +int uclass_post_probe_device(struct device *dev) +{ +	struct uclass_driver *uc_drv = dev->uclass->uc_drv; + +	if (uc_drv->post_probe) +		return uc_drv->post_probe(dev); + +	return 0; +} + +int uclass_pre_remove_device(struct device *dev) +{ +	struct uclass_driver *uc_drv; +	struct uclass *uc; +	int ret; + +	uc = dev->uclass; +	uc_drv = uc->uc_drv; +	if (uc->uc_drv->pre_remove) { +		ret = uc->uc_drv->pre_remove(dev); +		if (ret) +			return ret; +	} +	if (uc_drv->per_device_auto_alloc_size) { +		free(dev->uclass_priv); +		dev->uclass_priv = NULL; +	} + +	return 0; +} diff --git a/drivers/core/util.c b/drivers/core/util.c new file mode 100644 index 000000000..e01dd06d2 --- /dev/null +++ b/drivers/core/util.c @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013 Google, Inc + * + * SPDX-License-Identifier:	GPL-2.0+ + */ + +#include <common.h> +#include <vsprintf.h> + +void dm_warn(const char *fmt, ...) +{ +	va_list args; + +	va_start(args, fmt); +	vprintf(fmt, args); +	va_end(args); +} + +void dm_dbg(const char *fmt, ...) +{ +	va_list args; + +	va_start(args, fmt); +	vprintf(fmt, args); +	va_end(args); +} + +int list_count_items(struct list_head *head) +{ +	struct list_head *node; +	int count = 0; + +	list_for_each(node, head) +		count++; + +	return count; +} |