Commit 6494d708 authored by Simon Glass's avatar Simon Glass Committed by Tom Rini
Browse files

dm: Add base driver model support



Add driver model functionality for generic board.

This includes data structures and base code for registering devices and
uclasses (groups of devices with the same purpose, e.g. all I2C ports will
be in the same uclass).

The feature is enabled with CONFIG_DM.
Signed-off-by: default avatarSimon Glass <sjg@chromium.org>
Signed-off-by: default avatarMarek Vasut <marex@denx.de>
Signed-off-by: default avatarPavel Herrmann <morpheus.ibis@gmail.com>
Signed-off-by: default avatarViktor Křivák <viktor.krivak@gmail.com>
Signed-off-by: default avatarTomas Hlavacek <tmshlvck@gmail.com>
parent 65c70539
......@@ -591,6 +591,7 @@ libs-y += fs/
libs-y += net/
libs-y += disk/
libs-y += drivers/
libs-$(CONFIG_DM) += drivers/core/
libs-y += drivers/dma/
libs-y += drivers/gpio/
libs-y += drivers/i2c/
......
#
# Copyright (c) 2013 Google, Inc
#
# SPDX-License-Identifier: GPL-2.0+
#
obj-$(CONFIG_DM) := device.o lists.o root.o uclass.o util.o
/*
* 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;
}
/*
* 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
/*
* 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,
};
/*
* Copyright (c) 2013 Google,