summaryrefslogtreecommitdiff
path: root/drivers/core
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/core')
-rw-r--r--drivers/core/Kconfig51
-rw-r--r--drivers/core/Makefile10
-rw-r--r--drivers/core/device-remove.c30
-rw-r--r--drivers/core/device.c95
-rw-r--r--drivers/core/devres.c259
-rw-r--r--drivers/core/dump.c96
-rw-r--r--drivers/core/lists.c8
-rw-r--r--drivers/core/regmap.c86
-rw-r--r--drivers/core/root.c6
-rw-r--r--drivers/core/simple-bus.c30
-rw-r--r--drivers/core/syscon-uclass.c76
-rw-r--r--drivers/core/uclass.c52
12 files changed, 747 insertions, 52 deletions
diff --git a/drivers/core/Kconfig b/drivers/core/Kconfig
index 2861b430795..788f8b739bf 100644
--- a/drivers/core/Kconfig
+++ b/drivers/core/Kconfig
@@ -1,3 +1,5 @@
+menu "Generic Driver Options"
+
config DM
bool "Enable Driver Model"
help
@@ -38,6 +40,10 @@ config DM_DEVICE_REMOVE
device. This is not normally required in SPL, so by default this
option is disabled for SPL.
+ Note that this may have undesirable results in the USB subsystem as
+ it causes unplugged devices to linger around in the dm-tree, and it
+ causes USB host controllers to not be stopped when booting the OS.
+
config DM_STDIO
bool "Support stdio registration"
depends on DM
@@ -55,3 +61,48 @@ config DM_SEQ_ALIAS
Most boards will have a '/aliases' node containing the path to
numbered devices (e.g. serial0 = &serial0). This feature can be
disabled if it is not required, to save code space in SPL.
+
+config REGMAP
+ bool "Support register maps"
+ depends on DM
+ help
+ Hardware peripherals tend to have one or more sets of registers
+ which can be accessed to control the hardware. A register map
+ models this with a simple read/write interface. It can in principle
+ support any bus type (I2C, SPI) but so far this only supports
+ direct memory access.
+
+config SYSCON
+ bool "Support system controllers"
+ depends on REGMAP
+ help
+ Many SoCs have a number of system controllers which are dealt with
+ as a group by a single driver. Some common functionality is provided
+ by this uclass, including accessing registers via regmap and
+ assigning a unique number to each.
+
+config DEVRES
+ bool "Managed device resources"
+ depends on DM
+ help
+ This option enables the Managed device resources core support.
+ Device resources managed by the devres framework are automatically
+ released whether initialization fails half-way or the device gets
+ detached.
+
+ If this option is disabled, devres functions fall back to
+ non-managed variants. For example, devres_alloc() to kzalloc(),
+ devm_kmalloc() to kmalloc(), etc.
+
+config DEBUG_DEVRES
+ bool "Managed device resources debugging functions"
+ depends on DEVRES
+ help
+ If this option is enabled, devres debug messages are printed.
+ Also, a function is available to dump a list of device resources.
+ Select this if you are having a problem with devres or want to
+ debug resource management for a managed device.
+
+ If you are unsure about this, Say N here.
+
+endmenu
diff --git a/drivers/core/Makefile b/drivers/core/Makefile
index a3fec385039..11e0276e56b 100644
--- a/drivers/core/Makefile
+++ b/drivers/core/Makefile
@@ -4,8 +4,12 @@
# SPDX-License-Identifier: GPL-2.0+
#
-obj-$(CONFIG_DM) += device.o lists.o root.o uclass.o util.o
+obj-y += device.o lists.o root.o uclass.o util.o
+obj-$(CONFIG_DEVRES) += devres.o
ifndef CONFIG_SPL_BUILD
-obj-$(CONFIG_OF_CONTROL) += simple-bus.o
+obj-$(CONFIG_$(SPL_)OF_CONTROL) += simple-bus.o
endif
-obj-$(CONFIG_DM_DEVICE_REMOVE) += device-remove.o
+obj-$(CONFIG_$(SPL_)DM_DEVICE_REMOVE) += device-remove.o
+obj-$(CONFIG_DM) += dump.o
+obj-$(CONFIG_REGMAP) += regmap.o
+obj-$(CONFIG_SYSCON) += syscon-uclass.o
diff --git a/drivers/core/device-remove.c b/drivers/core/device-remove.c
index 6a16b4f690f..bd6d4062c93 100644
--- a/drivers/core/device-remove.c
+++ b/drivers/core/device-remove.c
@@ -18,16 +18,7 @@
#include <dm/uclass-internal.h>
#include <dm/util.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 udevice *dev)
+int device_unbind_children(struct udevice *dev)
{
struct udevice *pos, *n;
int ret, saved_ret = 0;
@@ -43,12 +34,7 @@ static int device_chld_unbind(struct udevice *dev)
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 udevice *dev)
+int device_remove_children(struct udevice *dev)
{
struct udevice *pos, *n;
int ret;
@@ -75,6 +61,9 @@ int device_unbind(struct udevice *dev)
if (dev->flags & DM_FLAG_ACTIVATED)
return -EINVAL;
+ if (!(dev->flags & DM_FLAG_BOUND))
+ return -EINVAL;
+
drv = dev->driver;
assert(drv);
@@ -84,7 +73,7 @@ int device_unbind(struct udevice *dev)
return ret;
}
- ret = device_chld_unbind(dev);
+ ret = device_unbind_children(dev);
if (ret)
return ret;
@@ -106,6 +95,9 @@ int device_unbind(struct udevice *dev)
if (dev->parent)
list_del(&dev->sibling_node);
+
+ devres_release_all(dev);
+
free(dev);
return 0;
@@ -139,6 +131,8 @@ void device_free(struct udevice *dev)
dev->parent_priv = NULL;
}
}
+
+ devres_release_probe(dev);
}
int device_remove(struct udevice *dev)
@@ -159,7 +153,7 @@ int device_remove(struct udevice *dev)
if (ret)
return ret;
- ret = device_chld_remove(dev);
+ ret = device_remove_children(dev);
if (ret)
goto err;
diff --git a/drivers/core/device.c b/drivers/core/device.c
index 85fd1fc7350..a31e25f6b5c 100644
--- a/drivers/core/device.c
+++ b/drivers/core/device.c
@@ -47,6 +47,9 @@ int device_bind(struct udevice *parent, const struct driver *drv,
INIT_LIST_HEAD(&dev->sibling_node);
INIT_LIST_HEAD(&dev->child_head);
INIT_LIST_HEAD(&dev->uclass_node);
+#ifdef CONFIG_DEVRES
+ INIT_LIST_HEAD(&dev->devres_head);
+#endif
dev->platdata = platdata;
dev->name = name;
dev->of_offset = of_offset;
@@ -56,7 +59,7 @@ int device_bind(struct udevice *parent, const struct driver *drv,
dev->seq = -1;
dev->req_seq = -1;
- if (IS_ENABLED(CONFIG_OF_CONTROL) && IS_ENABLED(CONFIG_DM_SEQ_ALIAS)) {
+ if (CONFIG_IS_ENABLED(OF_CONTROL) && IS_ENABLED(CONFIG_DM_SEQ_ALIAS)) {
/*
* Some devices, such as a SPI bus, I2C bus and serial ports
* are numbered using aliases.
@@ -132,10 +135,12 @@ int device_bind(struct udevice *parent, const struct driver *drv,
dm_dbg("Bound device %s to %s\n", dev->name, parent->name);
*devp = dev;
+ dev->flags |= DM_FLAG_BOUND;
+
return 0;
fail_child_post_bind:
- if (IS_ENABLED(CONFIG_DM_DEVICE_REMOVE)) {
+ if (CONFIG_IS_ENABLED(DM_DEVICE_REMOVE)) {
if (drv->unbind && drv->unbind(dev)) {
dm_warn("unbind() method failed on dev '%s' on error path\n",
dev->name);
@@ -143,14 +148,14 @@ fail_child_post_bind:
}
fail_bind:
- if (IS_ENABLED(CONFIG_DM_DEVICE_REMOVE)) {
+ if (CONFIG_IS_ENABLED(DM_DEVICE_REMOVE)) {
if (uclass_unbind_device(dev)) {
dm_warn("Failed to unbind dev '%s' on error path\n",
dev->name);
}
}
fail_uclass_bind:
- if (IS_ENABLED(CONFIG_DM_DEVICE_REMOVE)) {
+ if (CONFIG_IS_ENABLED(DM_DEVICE_REMOVE)) {
list_del(&dev->sibling_node);
if (dev->flags & DM_FLAG_ALLOC_PARENT_PDATA) {
free(dev->parent_platdata);
@@ -168,6 +173,8 @@ fail_alloc2:
dev->platdata = NULL;
}
fail_alloc1:
+ devres_release_all(dev);
+
free(dev);
return ret;
@@ -219,17 +226,17 @@ int device_probe_child(struct udevice *dev, void *parent_priv)
drv = dev->driver;
assert(drv);
- /* Allocate private data if requested */
- if (drv->priv_auto_alloc_size) {
+ /* Allocate private data if requested and not reentered */
+ if (drv->priv_auto_alloc_size && !dev->priv) {
dev->priv = alloc_priv(drv->priv_auto_alloc_size, drv->flags);
if (!dev->priv) {
ret = -ENOMEM;
goto fail;
}
}
- /* Allocate private data if requested */
+ /* Allocate private data if requested and not reentered */
size = dev->uclass->uc_drv->per_device_auto_alloc_size;
- if (size) {
+ if (size && !dev->uclass_priv) {
dev->uclass_priv = calloc(1, size);
if (!dev->uclass_priv) {
ret = -ENOMEM;
@@ -244,7 +251,7 @@ int device_probe_child(struct udevice *dev, void *parent_priv)
size = dev->parent->uclass->uc_drv->
per_child_auto_alloc_size;
}
- if (size) {
+ if (size && !dev->parent_priv) {
dev->parent_priv = alloc_priv(size, drv->flags);
if (!dev->parent_priv) {
ret = -ENOMEM;
@@ -257,6 +264,15 @@ int device_probe_child(struct udevice *dev, void *parent_priv)
ret = device_probe(dev->parent);
if (ret)
goto fail;
+
+ /*
+ * The device might have already been probed during
+ * the call to device_probe() on its parent device
+ * (e.g. PCI bridge devices). Test the flags again
+ * so that we don't mess up the device.
+ */
+ if (dev->flags & DM_FLAG_ACTIVATED)
+ return 0;
}
seq = uclass_resolve_seq(dev);
@@ -284,7 +300,6 @@ int device_probe_child(struct udevice *dev, void *parent_priv)
goto fail;
}
- dev->flags |= DM_FLAG_ACTIVATED;
if (drv->probe) {
ret = drv->probe(dev);
if (ret) {
@@ -330,7 +345,7 @@ void *dev_get_platdata(struct udevice *dev)
void *dev_get_parent_platdata(struct udevice *dev)
{
if (!dev) {
- dm_warn("%s: null device", __func__);
+ dm_warn("%s: null device\n", __func__);
return NULL;
}
@@ -340,7 +355,7 @@ void *dev_get_parent_platdata(struct udevice *dev)
void *dev_get_uclass_platdata(struct udevice *dev)
{
if (!dev) {
- dm_warn("%s: null device", __func__);
+ dm_warn("%s: null device\n", __func__);
return NULL;
}
@@ -459,17 +474,42 @@ int device_find_child_by_of_offset(struct udevice *parent, int of_offset,
return -ENODEV;
}
-int device_get_child_by_of_offset(struct udevice *parent, int seq,
+int device_get_child_by_of_offset(struct udevice *parent, int node,
struct udevice **devp)
{
struct udevice *dev;
int ret;
*devp = NULL;
- ret = device_find_child_by_of_offset(parent, seq, &dev);
+ ret = device_find_child_by_of_offset(parent, node, &dev);
return device_get_device_tail(dev, ret, devp);
}
+static struct udevice *_device_find_global_by_of_offset(struct udevice *parent,
+ int of_offset)
+{
+ struct udevice *dev, *found;
+
+ if (parent->of_offset == of_offset)
+ return parent;
+
+ list_for_each_entry(dev, &parent->child_head, sibling_node) {
+ found = _device_find_global_by_of_offset(dev, of_offset);
+ if (found)
+ return found;
+ }
+
+ return NULL;
+}
+
+int device_get_global_by_of_offset(int of_offset, struct udevice **devp)
+{
+ struct udevice *dev;
+
+ dev = _device_find_global_by_of_offset(gd->dm_root, of_offset);
+ return device_get_device_tail(dev, dev ? 0 : -ENOENT, devp);
+}
+
int device_find_first_child(struct udevice *parent, struct udevice **devp)
{
if (list_empty(&parent->child_head)) {
@@ -528,17 +568,22 @@ const char *dev_get_uclass_name(struct udevice *dev)
return dev->uclass->uc_drv->name;
}
-#ifdef CONFIG_OF_CONTROL
fdt_addr_t dev_get_addr(struct udevice *dev)
{
- return fdtdec_get_addr(gd->fdt_blob, dev->of_offset, "reg");
-}
+#if CONFIG_IS_ENABLED(OF_CONTROL)
+ fdt_addr_t addr;
+
+ addr = fdtdec_get_addr(gd->fdt_blob, dev->of_offset, "reg");
+ if (addr != FDT_ADDR_T_NONE) {
+ if (device_get_uclass_id(dev->parent) == UCLASS_SIMPLE_BUS)
+ addr = simple_bus_translate(dev->parent, addr);
+ }
+
+ return addr;
#else
-fdt_addr_t dev_get_addr(struct udevice *dev)
-{
return FDT_ADDR_T_NONE;
-}
#endif
+}
bool device_has_children(struct udevice *dev)
{
@@ -567,3 +612,13 @@ bool device_is_last_sibling(struct udevice *dev)
return false;
return list_is_last(&dev->sibling_node, &parent->child_head);
}
+
+int device_set_name(struct udevice *dev, const char *name)
+{
+ name = strdup(name);
+ if (!name)
+ return -ENOMEM;
+ dev->name = name;
+
+ return 0;
+}
diff --git a/drivers/core/devres.c b/drivers/core/devres.c
new file mode 100644
index 00000000000..605295bd14d
--- /dev/null
+++ b/drivers/core/devres.c
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2015 Masahiro Yamada <yamada.masahiro@socionext.com>
+ *
+ * Based on the original work in Linux by
+ * Copyright (c) 2006 SUSE Linux Products GmbH
+ * Copyright (c) 2006 Tejun Heo <teheo@suse.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <common.h>
+#include <linux/compat.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <dm/device.h>
+#include <dm/root.h>
+#include <dm/util.h>
+
+/**
+ * struct devres - Bookkeeping info for managed device resource
+ * @entry: List to associate this structure with a device
+ * @release: Callback invoked when this resource is released
+ * @probe: Flag to show when this resource was allocated
+ (true = probe, false = bind)
+ * @name: Name of release function
+ * @size: Size of resource data
+ * @data: Resource data
+ */
+struct devres {
+ struct list_head entry;
+ dr_release_t release;
+ bool probe;
+#ifdef CONFIG_DEBUG_DEVRES
+ const char *name;
+ size_t size;
+#endif
+ unsigned long long data[];
+};
+
+#ifdef CONFIG_DEBUG_DEVRES
+static void set_node_dbginfo(struct devres *dr, const char *name, size_t size)
+{
+ dr->name = name;
+ dr->size = size;
+}
+
+static void devres_log(struct udevice *dev, struct devres *dr,
+ const char *op)
+{
+ printf("%s: DEVRES %3s %p %s (%lu bytes)\n",
+ dev->name, op, dr, dr->name, (unsigned long)dr->size);
+}
+#else /* CONFIG_DEBUG_DEVRES */
+#define set_node_dbginfo(dr, n, s) do {} while (0)
+#define devres_log(dev, dr, op) do {} while (0)
+#endif
+
+#if CONFIG_DEBUG_DEVRES
+void *__devres_alloc(dr_release_t release, size_t size, gfp_t gfp,
+ const char *name)
+#else
+void *_devres_alloc(dr_release_t release, size_t size, gfp_t gfp)
+#endif
+{
+ size_t tot_size = sizeof(struct devres) + size;
+ struct devres *dr;
+
+ dr = kmalloc(tot_size, gfp);
+ if (unlikely(!dr))
+ return NULL;
+
+ INIT_LIST_HEAD(&dr->entry);
+ dr->release = release;
+ set_node_dbginfo(dr, name, size);
+
+ return dr->data;
+}
+
+void devres_free(void *res)
+{
+ if (res) {
+ struct devres *dr = container_of(res, struct devres, data);
+
+ BUG_ON(!list_empty(&dr->entry));
+ kfree(dr);
+ }
+}
+
+void devres_add(struct udevice *dev, void *res)
+{
+ struct devres *dr = container_of(res, struct devres, data);
+
+ devres_log(dev, dr, "ADD");
+ BUG_ON(!list_empty(&dr->entry));
+ dr->probe = dev->flags & DM_FLAG_BOUND ? true : false;
+ list_add_tail(&dr->entry, &dev->devres_head);
+}
+
+void *devres_find(struct udevice *dev, dr_release_t release,
+ dr_match_t match, void *match_data)
+{
+ struct devres *dr;
+
+ list_for_each_entry_reverse(dr, &dev->devres_head, entry) {
+ if (dr->release != release)
+ continue;
+ if (match && !match(dev, dr->data, match_data))
+ continue;
+ return dr->data;
+ }
+
+ return NULL;
+}
+
+void *devres_get(struct udevice *dev, void *new_res,
+ dr_match_t match, void *match_data)
+{
+ struct devres *new_dr = container_of(new_res, struct devres, data);
+ void *res;
+
+ res = devres_find(dev, new_dr->release, match, match_data);
+ if (!res) {
+ devres_add(dev, new_res);
+ res = new_res;
+ new_res = NULL;
+ }
+ devres_free(new_res);
+
+ return res;
+}
+
+void *devres_remove(struct udevice *dev, dr_release_t release,
+ dr_match_t match, void *match_data)
+{
+ void *res;
+
+ res = devres_find(dev, release, match, match_data);
+ if (res) {
+ struct devres *dr = container_of(res, struct devres, data);
+
+ list_del_init(&dr->entry);
+ devres_log(dev, dr, "REM");
+ }
+
+ return res;
+}
+
+int devres_destroy(struct udevice *dev, dr_release_t release,
+ dr_match_t match, void *match_data)
+{
+ void *res;
+
+ res = devres_remove(dev, release, match, match_data);
+ if (unlikely(!res))
+ return -ENOENT;
+
+ devres_free(res);
+ return 0;
+}
+
+int devres_release(struct udevice *dev, dr_release_t release,
+ dr_match_t match, void *match_data)
+{
+ void *res;
+
+ res = devres_remove(dev, release, match, match_data);
+ if (unlikely(!res))
+ return -ENOENT;
+
+ (*release)(dev, res);
+ devres_free(res);
+ return 0;
+}
+
+static void release_nodes(struct udevice *dev, struct list_head *head,
+ bool probe_only)
+{
+ struct devres *dr, *tmp;
+
+ list_for_each_entry_safe_reverse(dr, tmp, head, entry) {
+ if (probe_only && !dr->probe)
+ break;
+ devres_log(dev, dr, "REL");
+ dr->release(dev, dr->data);
+ list_del(&dr->entry);
+ kfree(dr);
+ }
+}
+
+void devres_release_probe(struct udevice *dev)
+{
+ release_nodes(dev, &dev->devres_head, true);
+}
+
+void devres_release_all(struct udevice *dev)
+{
+ release_nodes(dev, &dev->devres_head, false);
+}
+
+#ifdef CONFIG_DEBUG_DEVRES
+static void dump_resources(struct udevice *dev, int depth)
+{
+ struct devres *dr;
+ struct udevice *child;
+
+ printf("- %s\n", dev->name);
+
+ list_for_each_entry(dr, &dev->devres_head, entry)
+ printf(" %p (%lu byte) %s %s\n", dr,
+ (unsigned long)dr->size, dr->name,
+ dr->probe ? "PROBE" : "BIND");
+
+ list_for_each_entry(child, &dev->child_head, sibling_node)
+ dump_resources(child, depth + 1);
+}
+
+void dm_dump_devres(void)
+{
+ struct udevice *root;
+
+ root = dm_root();
+ if (root)
+ dump_resources(root, 0);
+}
+#endif
+
+/*
+ * Managed kmalloc/kfree
+ */
+static void devm_kmalloc_release(struct udevice *dev, void *res)
+{
+ /* noop */
+}
+
+static int devm_kmalloc_match(struct udevice *dev, void *res, void *data)
+{
+ return res == data;
+}
+
+void *devm_kmalloc(struct udevice *dev, size_t size, gfp_t gfp)
+{
+ void *data;
+
+ data = _devres_alloc(devm_kmalloc_release, size, gfp);
+ if (unlikely(!data))
+ return NULL;
+
+ devres_add(dev, data);
+
+ return data;
+}
+
+void devm_kfree(struct udevice *dev, void *p)
+{
+ int rc;
+
+ rc = devres_destroy(dev, devm_kmalloc_release, devm_kmalloc_match, p);
+ WARN_ON(rc);
+}
diff --git a/drivers/core/dump.c b/drivers/core/dump.c
new file mode 100644
index 00000000000..fd4596ee68b
--- /dev/null
+++ b/drivers/core/dump.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2015 Google, Inc
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <mapmem.h>
+#include <dm/root.h>
+
+static void show_devices(struct udevice *dev, int depth, int last_flag)
+{
+ int i, is_last;
+ struct udevice *child;
+ char class_name[12];
+
+ /* print the first 11 characters to not break the tree-format. */
+ strlcpy(class_name, dev->uclass->uc_drv->name, sizeof(class_name));
+ printf(" %-11s [ %c ] ", class_name,
+ dev->flags & DM_FLAG_ACTIVATED ? '+' : ' ');
+
+ for (i = depth; i >= 0; i--) {
+ is_last = (last_flag >> i) & 1;
+ if (i) {
+ if (is_last)
+ printf(" ");
+ else
+ printf("| ");
+ } else {
+ if (is_last)
+ printf("`-- ");
+ else
+ printf("|-- ");
+ }
+ }
+
+ printf("%s\n", dev->name);
+
+ list_for_each_entry(child, &dev->child_head, sibling_node) {
+ is_last = list_is_last(&child->sibling_node, &dev->child_head);
+ show_devices(child, depth + 1, (last_flag << 1) | is_last);
+ }
+}
+
+void dm_dump_all(void)
+{
+ struct udevice *root;
+
+ root = dm_root();
+ if (root) {
+ printf(" Class Probed Name\n");
+ printf("----------------------------------------\n");
+ show_devices(root, -1, 0);
+ }
+}
+
+/**
+ * dm_display_line() - Display information about a single device
+ *
+ * Displays a single line of information with an option prefix
+ *
+ * @dev: Device to display
+ */
+static void dm_display_line(struct udevice *dev)
+{
+ printf("- %c %s @ %08lx",
+ dev->flags & DM_FLAG_ACTIVATED ? '*' : ' ',
+ dev->name, (ulong)map_to_sysmem(dev));
+ if (dev->seq != -1 || dev->req_seq != -1)
+ printf(", seq %d, (req %d)", dev->seq, dev->req_seq);
+ puts("\n");
+}
+
+void dm_dump_uclass(void)
+{
+ struct uclass *uc;
+ int ret;
+ int id;
+
+ for (id = 0; id < UCLASS_COUNT; id++) {
+ struct udevice *dev;
+
+ ret = uclass_get(id, &uc);
+ if (ret)
+ continue;
+
+ printf("uclass %d: %s\n", id, uc->uc_drv->name);
+ if (list_empty(&uc->dev_head))
+ continue;
+ list_for_each_entry(dev, &uc->dev_head, uclass_node) {
+ dm_display_line(dev);
+ }
+ puts("\n");
+ }
+}
diff --git a/drivers/core/lists.c b/drivers/core/lists.c
index 0c49d99f47e..a1c94780dd3 100644
--- a/drivers/core/lists.c
+++ b/drivers/core/lists.c
@@ -86,20 +86,20 @@ int device_bind_driver_to_node(struct udevice *parent, const char *drv_name,
drv = lists_driver_lookup_name(drv_name);
if (!drv) {
- printf("Cannot find driver '%s'\n", drv_name);
+ debug("Cannot find driver '%s'\n", drv_name);
return -ENOENT;
}
ret = device_bind(parent, drv, dev_name, NULL, node, devp);
if (ret) {
- printf("Cannot create device named '%s' (err=%d)\n",
- dev_name, ret);
+ debug("Cannot create device named '%s' (err=%d)\n",
+ dev_name, ret);
return ret;
}
return 0;
}
-#ifdef CONFIG_OF_CONTROL
+#if CONFIG_IS_ENABLED(OF_CONTROL)
/**
* driver_check_compatible() - Check if a driver is compatible with this node
*
diff --git a/drivers/core/regmap.c b/drivers/core/regmap.c
new file mode 100644
index 00000000000..519832f1733
--- /dev/null
+++ b/drivers/core/regmap.c
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2015 Google, Inc
+ * Written by Simon Glass <sjg@chromium.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <libfdt.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <regmap.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+int regmap_init_mem(struct udevice *dev, struct regmap **mapp)
+{
+ const void *blob = gd->fdt_blob;
+ struct regmap_range *range;
+ const fdt32_t *cell;
+ struct regmap *map;
+ int count;
+ int addr_len, size_len, both_len;
+ int parent;
+ int len;
+
+ parent = dev->parent->of_offset;
+ addr_len = fdt_address_cells(blob, parent);
+ size_len = fdt_size_cells(blob, parent);
+ both_len = addr_len + size_len;
+
+ cell = fdt_getprop(blob, dev->of_offset, "reg", &len);
+ len /= sizeof(*cell);
+ count = len / both_len;
+ if (!cell || !count)
+ return -EINVAL;
+
+ map = malloc(sizeof(struct regmap));
+ if (!map)
+ return -ENOMEM;
+
+ if (count <= 1) {
+ map->range = &map->base_range;
+ } else {
+ map->range = malloc(count * sizeof(struct regmap_range));
+ if (!map->range) {
+ free(map);
+ return -ENOMEM;
+ }
+ }
+
+ map->base = fdtdec_get_number(cell, addr_len);
+ map->range_count = count;
+
+ for (range = map->range; count > 0;
+ count--, cell += both_len, range++) {
+ range->start = fdtdec_get_number(cell, addr_len);
+ range->size = fdtdec_get_number(cell + addr_len, size_len);
+ }
+
+ *mapp = map;
+
+ return 0;
+}
+
+void *regmap_get_range(struct regmap *map, unsigned int range_num)
+{
+ struct regmap_range *range;
+
+ if (range_num >= map->range_count)
+ return NULL;
+ range = &map->range[range_num];
+
+ return map_sysmem(range->start, range->size);
+}
+
+int regmap_uninit(struct regmap *map)
+{
+ if (map->range_count > 1)
+ free(map->range);
+ free(map);
+
+ return 0;
+}
diff --git a/drivers/core/root.c b/drivers/core/root.c
index 12d046051fd..78ab00c7bfb 100644
--- a/drivers/core/root.c
+++ b/drivers/core/root.c
@@ -114,7 +114,7 @@ int dm_init(void)
ret = device_bind_by_name(NULL, false, &root_info, &DM_ROOT_NON_CONST);
if (ret)
return ret;
-#ifdef CONFIG_OF_CONTROL
+#if CONFIG_IS_ENABLED(OF_CONTROL)
DM_ROOT_NON_CONST->of_offset = 0;
#endif
ret = device_probe(DM_ROOT_NON_CONST);
@@ -145,7 +145,7 @@ int dm_scan_platdata(bool pre_reloc_only)
return ret;
}
-#ifdef CONFIG_OF_CONTROL
+#if CONFIG_IS_ENABLED(OF_CONTROL)
int dm_scan_fdt_node(struct udevice *parent, const void *blob, int offset,
bool pre_reloc_only)
{
@@ -198,7 +198,7 @@ int dm_init_and_scan(bool pre_reloc_only)
return ret;
}
- if (OF_CONTROL) {
+ if (CONFIG_IS_ENABLED(OF_CONTROL)) {
ret = dm_scan_fdt(gd->fdt_blob, pre_reloc_only);
if (ret) {
debug("dm_scan_fdt() failed: %d\n", ret);
diff --git a/drivers/core/simple-bus.c b/drivers/core/simple-bus.c
index 3ea4d8230bd..913c3ccc70b 100644
--- a/drivers/core/simple-bus.c
+++ b/drivers/core/simple-bus.c
@@ -10,8 +10,37 @@
DECLARE_GLOBAL_DATA_PTR;
+struct simple_bus_plat {
+ u32 base;
+ u32 size;
+ u32 target;
+};
+
+fdt_addr_t simple_bus_translate(struct udevice *dev, fdt_addr_t addr)
+{
+ struct simple_bus_plat *plat = dev_get_uclass_platdata(dev);
+
+ if (addr >= plat->base && addr < plat->base + plat->size)
+ addr = (addr - plat->base) + plat->target;
+
+ return addr;
+}
+
static int simple_bus_post_bind(struct udevice *dev)
{
+ u32 cell[3];
+ int ret;
+
+ ret = fdtdec_get_int_array(gd->fdt_blob, dev->of_offset, "ranges",
+ cell, ARRAY_SIZE(cell));
+ if (!ret) {
+ struct simple_bus_plat *plat = dev_get_uclass_platdata(dev);
+
+ plat->base = cell[0];
+ plat->target = cell[1];
+ plat->size = cell[2];
+ }
+
return dm_scan_fdt_node(dev, gd->fdt_blob, dev->of_offset, false);
}
@@ -19,6 +48,7 @@ UCLASS_DRIVER(simple_bus) = {
.id = UCLASS_SIMPLE_BUS,
.name = "simple_bus",
.post_bind = simple_bus_post_bind,
+ .per_device_platdata_auto_alloc_size = sizeof(struct simple_bus_plat),
};
static const struct udevice_id generic_simple_bus_ids[] = {
diff --git a/drivers/core/syscon-uclass.c b/drivers/core/syscon-uclass.c
new file mode 100644
index 00000000000..686c32056ec
--- /dev/null
+++ b/drivers/core/syscon-uclass.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2015 Google, Inc
+ * Written by Simon Glass <sjg@chromium.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <common.h>
+#include <syscon.h>
+#include <dm.h>
+#include <errno.h>
+#include <regmap.h>
+#include <dm/device-internal.h>
+#include <dm/lists.h>
+#include <dm/root.h>
+#include <linux/err.h>
+
+struct regmap *syscon_get_regmap(struct udevice *dev)
+{
+ struct syscon_uc_info *priv;
+
+ if (device_get_uclass_id(dev) != UCLASS_SYSCON)
+ return ERR_PTR(-ENOEXEC);
+ priv = dev_get_uclass_priv(dev);
+ return priv->regmap;
+}
+
+static int syscon_pre_probe(struct udevice *dev)
+{
+ struct syscon_uc_info *priv = dev_get_uclass_priv(dev);
+
+ return regmap_init_mem(dev, &priv->regmap);
+}
+
+struct regmap *syscon_get_regmap_by_driver_data(ulong driver_data)
+{
+ struct udevice *dev;
+ struct uclass *uc;
+ int ret;
+
+ ret = uclass_get(UCLASS_SYSCON, &uc);
+ if (ret)
+ return ERR_PTR(ret);
+ uclass_foreach_dev(dev, uc) {
+ if (dev->driver_data == driver_data) {
+ struct syscon_uc_info *priv;
+ int ret;
+
+ ret = device_probe(dev);
+ if (ret)
+ return ERR_PTR(ret);
+ priv = dev_get_uclass_priv(dev);
+
+ return priv->regmap;
+ }
+ }
+
+ return ERR_PTR(-ENODEV);
+}
+
+void *syscon_get_first_range(ulong driver_data)
+{
+ struct regmap *map;
+
+ map = syscon_get_regmap_by_driver_data(driver_data);
+ if (IS_ERR(map))
+ return map;
+ return regmap_get_range(map, 0);
+}
+
+UCLASS_DRIVER(syscon) = {
+ .id = UCLASS_SYSCON,
+ .name = "syscon",
+ .per_device_auto_alloc_size = sizeof(struct syscon_uc_info),
+ .pre_probe = syscon_pre_probe,
+};
diff --git a/drivers/core/uclass.c b/drivers/core/uclass.c
index 7de817324b9..f63ff599a69 100644
--- a/drivers/core/uclass.c
+++ b/drivers/core/uclass.c
@@ -56,8 +56,8 @@ static int uclass_add(enum uclass_id id, struct uclass **ucp)
*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);
+ debug("Cannot find uclass for id %d: please add the UCLASS_DRIVER() declaration for this UCLASS_... id\n",
+ id);
return -ENOENT;
}
uc = calloc(1, sizeof(*uc));
@@ -153,6 +153,8 @@ int uclass_find_device(enum uclass_id id, int index, struct udevice **devp)
ret = uclass_get(id, &uc);
if (ret)
return ret;
+ if (list_empty(&uc->dev_head))
+ return -ENODEV;
list_for_each_entry(dev, &uc->dev_head, uclass_node) {
if (!index--) {
@@ -271,6 +273,37 @@ static int uclass_find_device_by_of_offset(enum uclass_id id, int node,
return -ENODEV;
}
+static int uclass_find_device_by_phandle(enum uclass_id id,
+ struct udevice *parent,
+ const char *name,
+ struct udevice **devp)
+{
+ struct udevice *dev;
+ struct uclass *uc;
+ int find_phandle;
+ int ret;
+
+ *devp = NULL;
+ find_phandle = fdtdec_get_int(gd->fdt_blob, parent->of_offset, name,
+ -1);
+ if (find_phandle <= 0)
+ return -ENOENT;
+ ret = uclass_get(id, &uc);
+ if (ret)
+ return ret;
+
+ list_for_each_entry(dev, &uc->dev_head, uclass_node) {
+ uint phandle = fdt_get_phandle(gd->fdt_blob, dev->of_offset);
+
+ if (phandle == find_phandle) {
+ *devp = dev;
+ return 0;
+ }
+ }
+
+ return -ENODEV;
+}
+
int uclass_get_device_tail(struct udevice *dev, int ret,
struct udevice **devp)
{
@@ -336,6 +369,17 @@ int uclass_get_device_by_of_offset(enum uclass_id id, int node,
return uclass_get_device_tail(dev, ret, devp);
}
+int uclass_get_device_by_phandle(enum uclass_id id, struct udevice *parent,
+ const char *name, struct udevice **devp)
+{
+ struct udevice *dev;
+ int ret;
+
+ *devp = NULL;
+ ret = uclass_find_device_by_phandle(id, parent, name, &dev);
+ return uclass_get_device_tail(dev, ret, devp);
+}
+
int uclass_first_device(enum uclass_id id, struct udevice **devp)
{
struct udevice *dev;
@@ -391,7 +435,7 @@ err:
return ret;
}
-#ifdef CONFIG_DM_DEVICE_REMOVE
+#if CONFIG_IS_ENABLED(DM_DEVICE_REMOVE)
int uclass_unbind_device(struct udevice *dev)
{
struct uclass *uc;
@@ -471,7 +515,7 @@ int uclass_post_probe_device(struct udevice *dev)
return 0;
}
-#ifdef CONFIG_DM_DEVICE_REMOVE
+#if CONFIG_IS_ENABLED(DM_DEVICE_REMOVE)
int uclass_pre_remove_device(struct udevice *dev)
{
struct uclass_driver *uc_drv;