summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/core/Makefile7
-rw-r--r--drivers/core/device.c348
-rw-r--r--drivers/core/lists.c155
-rw-r--r--drivers/core/root.c102
-rw-r--r--drivers/core/uclass.c285
-rw-r--r--drivers/core/util.c37
6 files changed, 934 insertions, 0 deletions
diff --git a/drivers/core/Makefile b/drivers/core/Makefile
new file mode 100644
index 0000000..90b2a7f
--- /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 0000000..55ba281
--- /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 0000000..4f2c126
--- /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 0000000..407bc0d
--- /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 0000000..4df5a8b
--- /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 0000000..e01dd06
--- /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;
+}