2014-02-26 22:59:18 +00:00
|
|
|
/*
|
|
|
|
* 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>
|
2014-07-23 12:55:12 +00:00
|
|
|
#include <fdtdec.h>
|
2015-09-02 05:41:12 +00:00
|
|
|
#include <fdt_support.h>
|
2014-02-26 22:59:18 +00:00
|
|
|
#include <malloc.h>
|
|
|
|
#include <dm/device.h>
|
|
|
|
#include <dm/device-internal.h>
|
|
|
|
#include <dm/lists.h>
|
pinctrl: add pin control uclass support
This creates a new framework for handling of pin control devices,
i.e. devices that control different aspects of package pins.
This uclass handles pinmuxing and pin configuration; pinmuxing
controls switching among silicon blocks that share certain physical
pins, pin configuration handles electronic properties such as pin-
biasing, load capacitance etc.
This framework can support the same device tree bindings, but if you
do not need full interface support, you can disable some features to
reduce memory foot print. Typically around 1.5KB is necessary to
include full-featured uclass support on ARM board (CONFIG_PINCTRL +
CONFIG_PINCTRL_FULL + CONFIG_PINCTRL_GENERIC + CONFIG_PINCTRL_PINMUX),
for example.
We are often limited on code size for SPL. Besides, we still have
many boards that do not support device tree configuration. The full
pinctrl, which requires OF_CONTROL, does not make sense for those
boards. So, this framework also has a Do-It-Yourself (let's say
simple pinctrl) interface. With CONFIG_PINCTRL_FULL disabled, the
uclass itself provides no systematic mechanism for identifying the
peripheral device, applying pinctrl settings, etc. They must be
done in each low-level driver. In return, you can save much memory
footprint and it might be useful especially for SPL.
Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
Acked-by: Simon Glass <sjg@chromium.org>
2015-08-27 03:44:29 +00:00
|
|
|
#include <dm/pinctrl.h>
|
2014-02-26 22:59:18 +00:00
|
|
|
#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>
|
|
|
|
|
2014-07-23 12:55:12 +00:00
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
|
|
|
2015-03-25 18:21:54 +00:00
|
|
|
int device_bind(struct udevice *parent, const struct driver *drv,
|
|
|
|
const char *name, void *platdata, int of_offset,
|
|
|
|
struct udevice **devp)
|
2014-02-26 22:59:18 +00:00
|
|
|
{
|
2014-05-22 10:43:05 +00:00
|
|
|
struct udevice *dev;
|
2014-02-26 22:59:18 +00:00
|
|
|
struct uclass *uc;
|
2015-04-15 11:07:18 +00:00
|
|
|
int size, ret = 0;
|
2014-02-26 22:59:18 +00:00
|
|
|
|
2015-08-27 03:44:28 +00:00
|
|
|
if (devp)
|
|
|
|
*devp = NULL;
|
2014-02-26 22:59:18 +00:00
|
|
|
if (!name)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
ret = uclass_get(drv->id, &uc);
|
2015-08-30 22:55:16 +00:00
|
|
|
if (ret) {
|
|
|
|
debug("Missing uclass for driver %s\n", drv->name);
|
2014-02-26 22:59:18 +00:00
|
|
|
return ret;
|
2015-08-30 22:55:16 +00:00
|
|
|
}
|
2014-02-26 22:59:18 +00:00
|
|
|
|
2014-05-22 10:43:05 +00:00
|
|
|
dev = calloc(1, sizeof(struct udevice));
|
2014-02-26 22:59:18 +00:00
|
|
|
if (!dev)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&dev->sibling_node);
|
|
|
|
INIT_LIST_HEAD(&dev->child_head);
|
|
|
|
INIT_LIST_HEAD(&dev->uclass_node);
|
2015-07-25 12:52:37 +00:00
|
|
|
#ifdef CONFIG_DEVRES
|
devres: introduce Devres (Managed Device Resource) framework
In U-Boot's driver model, memory is basically allocated and freed
in the core framework. So, low level drivers generally only have
to specify the size of needed memory with .priv_auto_alloc_size,
.platdata_auto_alloc_size, etc. Nevertheless, some drivers still
need to allocate/free memory on their own in case they cannot
statically know the necessary memory size. So, I believe it is
reasonable enough to port Devres into U-boot.
Devres, which originates in Linux, manages device resources for each
device and automatically releases them on driver detach. With devres,
device resources are guaranteed to be freed whether initialization
fails half-way or the device gets detached.
The basic idea is totally the same to that of Linux, but I tweaked
it a bit so that it fits in U-Boot's driver model.
In U-Boot, drivers are activated in two steps: binding and probing.
Binding puts a driver and a device together. It is just data
manipulation on the system memory, so nothing has happened on the
hardware device at this moment. When the device is really used, it
is probed. Probing initializes the real hardware device to make it
really ready for use.
So, the resources acquired during the probing process must be freed
when the device is removed. Likewise, what has been allocated in
binding should be released when the device is unbound. The struct
devres has a member "probe" to remember when the resource was
allocated.
CONFIG_DEBUG_DEVRES is also supported for easier debugging.
If enabled, debug messages are printed each time a resource is
allocated/freed.
Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
Acked-by: Simon Glass <sjg@chromium.org>
2015-07-25 12:52:35 +00:00
|
|
|
INIT_LIST_HEAD(&dev->devres_head);
|
2015-07-25 12:52:37 +00:00
|
|
|
#endif
|
2014-02-26 22:59:18 +00:00
|
|
|
dev->platdata = platdata;
|
|
|
|
dev->name = name;
|
|
|
|
dev->of_offset = of_offset;
|
|
|
|
dev->parent = parent;
|
|
|
|
dev->driver = drv;
|
|
|
|
dev->uclass = uc;
|
2014-07-23 12:55:12 +00:00
|
|
|
|
|
|
|
dev->seq = -1;
|
2015-01-25 15:27:05 +00:00
|
|
|
dev->req_seq = -1;
|
2016-01-07 17:00:45 +00:00
|
|
|
if (CONFIG_IS_ENABLED(OF_CONTROL) && CONFIG_IS_ENABLED(DM_SEQ_ALIAS)) {
|
2015-02-28 05:06:30 +00:00
|
|
|
/*
|
|
|
|
* Some devices, such as a SPI bus, I2C bus and serial ports
|
|
|
|
* are numbered using aliases.
|
|
|
|
*
|
|
|
|
* This is just a 'requested' sequence, and will be
|
|
|
|
* resolved (and ->seq updated) when the device is probed.
|
|
|
|
*/
|
|
|
|
if (uc->uc_drv->flags & DM_UC_FLAG_SEQ_ALIAS) {
|
|
|
|
if (uc->uc_drv->name && of_offset != -1) {
|
|
|
|
fdtdec_get_alias_seq(gd->fdt_blob,
|
|
|
|
uc->uc_drv->name, of_offset,
|
|
|
|
&dev->req_seq);
|
|
|
|
}
|
2015-01-25 15:27:05 +00:00
|
|
|
}
|
2014-07-23 12:55:12 +00:00
|
|
|
}
|
2015-02-28 05:06:30 +00:00
|
|
|
|
2015-01-25 15:27:00 +00:00
|
|
|
if (!dev->platdata && drv->platdata_auto_alloc_size) {
|
2014-02-26 22:59:18 +00:00
|
|
|
dev->flags |= DM_FLAG_ALLOC_PDATA;
|
2015-01-25 15:27:00 +00:00
|
|
|
dev->platdata = calloc(1, drv->platdata_auto_alloc_size);
|
|
|
|
if (!dev->platdata) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto fail_alloc1;
|
|
|
|
}
|
|
|
|
}
|
2015-01-25 15:27:01 +00:00
|
|
|
|
2015-04-15 11:07:18 +00:00
|
|
|
size = uc->uc_drv->per_device_platdata_auto_alloc_size;
|
|
|
|
if (size) {
|
|
|
|
dev->flags |= DM_FLAG_ALLOC_UCLASS_PDATA;
|
|
|
|
dev->uclass_platdata = calloc(1, size);
|
|
|
|
if (!dev->uclass_platdata) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto fail_alloc2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parent) {
|
|
|
|
size = parent->driver->per_child_platdata_auto_alloc_size;
|
2015-01-25 15:27:02 +00:00
|
|
|
if (!size) {
|
|
|
|
size = parent->uclass->uc_drv->
|
|
|
|
per_child_platdata_auto_alloc_size;
|
|
|
|
}
|
2015-01-25 15:27:01 +00:00
|
|
|
if (size) {
|
|
|
|
dev->flags |= DM_FLAG_ALLOC_PARENT_PDATA;
|
|
|
|
dev->parent_platdata = calloc(1, size);
|
|
|
|
if (!dev->parent_platdata) {
|
|
|
|
ret = -ENOMEM;
|
2015-04-15 11:07:18 +00:00
|
|
|
goto fail_alloc3;
|
2015-01-25 15:27:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-02-26 22:59:18 +00:00
|
|
|
|
|
|
|
/* 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)
|
2015-01-25 15:26:59 +00:00
|
|
|
goto fail_uclass_bind;
|
2014-02-26 22:59:18 +00:00
|
|
|
|
|
|
|
/* if we fail to bind we remove device from successors and free it */
|
|
|
|
if (drv->bind) {
|
|
|
|
ret = drv->bind(dev);
|
2015-01-25 15:26:59 +00:00
|
|
|
if (ret)
|
2014-02-26 22:59:18 +00:00
|
|
|
goto fail_bind;
|
|
|
|
}
|
2015-01-25 15:27:03 +00:00
|
|
|
if (parent && parent->driver->child_post_bind) {
|
|
|
|
ret = parent->driver->child_post_bind(dev);
|
|
|
|
if (ret)
|
|
|
|
goto fail_child_post_bind;
|
|
|
|
}
|
2016-01-05 16:30:59 +00:00
|
|
|
if (uc->uc_drv->post_bind) {
|
|
|
|
ret = uc->uc_drv->post_bind(dev);
|
|
|
|
if (ret)
|
|
|
|
goto fail_uclass_post_bind;
|
|
|
|
}
|
2015-01-25 15:27:03 +00:00
|
|
|
|
2014-02-26 22:59:18 +00:00
|
|
|
if (parent)
|
|
|
|
dm_dbg("Bound device %s to %s\n", dev->name, parent->name);
|
2015-08-27 03:44:28 +00:00
|
|
|
if (devp)
|
|
|
|
*devp = dev;
|
2014-02-26 22:59:18 +00:00
|
|
|
|
2015-07-25 12:52:34 +00:00
|
|
|
dev->flags |= DM_FLAG_BOUND;
|
|
|
|
|
2014-02-26 22:59:18 +00:00
|
|
|
return 0;
|
|
|
|
|
2016-01-05 16:30:59 +00:00
|
|
|
fail_uclass_post_bind:
|
|
|
|
/* There is no child unbind() method, so no clean-up required */
|
2015-01-25 15:27:03 +00:00
|
|
|
fail_child_post_bind:
|
2015-08-11 22:31:52 +00:00
|
|
|
if (CONFIG_IS_ENABLED(DM_DEVICE_REMOVE)) {
|
2015-02-28 05:06:33 +00:00
|
|
|
if (drv->unbind && drv->unbind(dev)) {
|
|
|
|
dm_warn("unbind() method failed on dev '%s' on error path\n",
|
|
|
|
dev->name);
|
|
|
|
}
|
2015-01-25 15:27:03 +00:00
|
|
|
}
|
|
|
|
|
2014-02-26 22:59:18 +00:00
|
|
|
fail_bind:
|
2015-08-11 22:31:52 +00:00
|
|
|
if (CONFIG_IS_ENABLED(DM_DEVICE_REMOVE)) {
|
2015-02-28 05:06:33 +00:00
|
|
|
if (uclass_unbind_device(dev)) {
|
|
|
|
dm_warn("Failed to unbind dev '%s' on error path\n",
|
|
|
|
dev->name);
|
|
|
|
}
|
2015-01-25 15:26:59 +00:00
|
|
|
}
|
|
|
|
fail_uclass_bind:
|
2015-08-11 22:31:52 +00:00
|
|
|
if (CONFIG_IS_ENABLED(DM_DEVICE_REMOVE)) {
|
2015-02-28 05:06:33 +00:00
|
|
|
list_del(&dev->sibling_node);
|
|
|
|
if (dev->flags & DM_FLAG_ALLOC_PARENT_PDATA) {
|
|
|
|
free(dev->parent_platdata);
|
|
|
|
dev->parent_platdata = NULL;
|
|
|
|
}
|
2015-01-25 15:27:01 +00:00
|
|
|
}
|
2015-04-15 11:07:18 +00:00
|
|
|
fail_alloc3:
|
|
|
|
if (dev->flags & DM_FLAG_ALLOC_UCLASS_PDATA) {
|
|
|
|
free(dev->uclass_platdata);
|
|
|
|
dev->uclass_platdata = NULL;
|
|
|
|
}
|
2015-01-25 15:27:01 +00:00
|
|
|
fail_alloc2:
|
2015-01-25 15:27:00 +00:00
|
|
|
if (dev->flags & DM_FLAG_ALLOC_PDATA) {
|
|
|
|
free(dev->platdata);
|
|
|
|
dev->platdata = NULL;
|
|
|
|
}
|
|
|
|
fail_alloc1:
|
devres: introduce Devres (Managed Device Resource) framework
In U-Boot's driver model, memory is basically allocated and freed
in the core framework. So, low level drivers generally only have
to specify the size of needed memory with .priv_auto_alloc_size,
.platdata_auto_alloc_size, etc. Nevertheless, some drivers still
need to allocate/free memory on their own in case they cannot
statically know the necessary memory size. So, I believe it is
reasonable enough to port Devres into U-boot.
Devres, which originates in Linux, manages device resources for each
device and automatically releases them on driver detach. With devres,
device resources are guaranteed to be freed whether initialization
fails half-way or the device gets detached.
The basic idea is totally the same to that of Linux, but I tweaked
it a bit so that it fits in U-Boot's driver model.
In U-Boot, drivers are activated in two steps: binding and probing.
Binding puts a driver and a device together. It is just data
manipulation on the system memory, so nothing has happened on the
hardware device at this moment. When the device is really used, it
is probed. Probing initializes the real hardware device to make it
really ready for use.
So, the resources acquired during the probing process must be freed
when the device is removed. Likewise, what has been allocated in
binding should be released when the device is unbound. The struct
devres has a member "probe" to remember when the resource was
allocated.
CONFIG_DEBUG_DEVRES is also supported for easier debugging.
If enabled, debug messages are printed each time a resource is
allocated/freed.
Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
Acked-by: Simon Glass <sjg@chromium.org>
2015-07-25 12:52:35 +00:00
|
|
|
devres_release_all(dev);
|
|
|
|
|
2014-02-26 22:59:18 +00:00
|
|
|
free(dev);
|
2015-01-25 15:26:59 +00:00
|
|
|
|
2014-02-26 22:59:18 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-07-23 12:55:03 +00:00
|
|
|
int device_bind_by_name(struct udevice *parent, bool pre_reloc_only,
|
|
|
|
const struct driver_info *info, struct udevice **devp)
|
2014-02-26 22:59:18 +00:00
|
|
|
{
|
|
|
|
struct driver *drv;
|
|
|
|
|
|
|
|
drv = lists_driver_lookup_name(info->name);
|
|
|
|
if (!drv)
|
|
|
|
return -ENOENT;
|
2014-07-23 12:55:03 +00:00
|
|
|
if (pre_reloc_only && !(drv->flags & DM_FLAG_PRE_RELOC))
|
|
|
|
return -EPERM;
|
2014-02-26 22:59:18 +00:00
|
|
|
|
|
|
|
return device_bind(parent, drv, info->name, (void *)info->platdata,
|
|
|
|
-1, devp);
|
|
|
|
}
|
|
|
|
|
2015-03-25 18:21:53 +00:00
|
|
|
static void *alloc_priv(int size, uint flags)
|
|
|
|
{
|
|
|
|
void *priv;
|
|
|
|
|
|
|
|
if (flags & DM_FLAG_ALLOC_PRIV_DMA) {
|
|
|
|
priv = memalign(ARCH_DMA_MINALIGN, size);
|
|
|
|
if (priv)
|
|
|
|
memset(priv, '\0', size);
|
|
|
|
} else {
|
|
|
|
priv = calloc(1, size);
|
|
|
|
}
|
|
|
|
|
|
|
|
return priv;
|
|
|
|
}
|
|
|
|
|
2014-10-14 05:41:50 +00:00
|
|
|
int device_probe_child(struct udevice *dev, void *parent_priv)
|
2014-02-26 22:59:18 +00:00
|
|
|
{
|
2015-03-25 18:21:54 +00:00
|
|
|
const struct driver *drv;
|
2014-02-26 22:59:18 +00:00
|
|
|
int size = 0;
|
|
|
|
int ret;
|
2014-07-23 12:55:12 +00:00
|
|
|
int seq;
|
2014-02-26 22:59:18 +00:00
|
|
|
|
|
|
|
if (!dev)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (dev->flags & DM_FLAG_ACTIVATED)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
drv = dev->driver;
|
|
|
|
assert(drv);
|
|
|
|
|
2015-08-24 08:14:02 +00:00
|
|
|
/* Allocate private data if requested and not reentered */
|
|
|
|
if (drv->priv_auto_alloc_size && !dev->priv) {
|
2015-03-25 18:21:53 +00:00
|
|
|
dev->priv = alloc_priv(drv->priv_auto_alloc_size, drv->flags);
|
2014-02-26 22:59:18 +00:00
|
|
|
if (!dev->priv) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
}
|
2015-08-24 08:14:02 +00:00
|
|
|
/* Allocate private data if requested and not reentered */
|
2014-02-26 22:59:18 +00:00
|
|
|
size = dev->uclass->uc_drv->per_device_auto_alloc_size;
|
2015-08-24 08:14:02 +00:00
|
|
|
if (size && !dev->uclass_priv) {
|
2014-02-26 22:59:18 +00:00
|
|
|
dev->uclass_priv = calloc(1, size);
|
|
|
|
if (!dev->uclass_priv) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Ensure all parents are probed */
|
|
|
|
if (dev->parent) {
|
2014-07-23 12:55:20 +00:00
|
|
|
size = dev->parent->driver->per_child_auto_alloc_size;
|
2015-01-25 15:27:06 +00:00
|
|
|
if (!size) {
|
|
|
|
size = dev->parent->uclass->uc_drv->
|
|
|
|
per_child_auto_alloc_size;
|
|
|
|
}
|
2015-08-24 08:14:02 +00:00
|
|
|
if (size && !dev->parent_priv) {
|
2015-03-25 18:21:53 +00:00
|
|
|
dev->parent_priv = alloc_priv(size, drv->flags);
|
2014-07-23 12:55:20 +00:00
|
|
|
if (!dev->parent_priv) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto fail;
|
|
|
|
}
|
2014-10-14 05:41:50 +00:00
|
|
|
if (parent_priv)
|
|
|
|
memcpy(dev->parent_priv, parent_priv, size);
|
2014-07-23 12:55:20 +00:00
|
|
|
}
|
|
|
|
|
2014-02-26 22:59:18 +00:00
|
|
|
ret = device_probe(dev->parent);
|
|
|
|
if (ret)
|
|
|
|
goto fail;
|
2015-08-24 08:14:02 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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;
|
2014-02-26 22:59:18 +00:00
|
|
|
}
|
|
|
|
|
2014-07-23 12:55:12 +00:00
|
|
|
seq = uclass_resolve_seq(dev);
|
|
|
|
if (seq < 0) {
|
|
|
|
ret = seq;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
dev->seq = seq;
|
|
|
|
|
2015-03-25 18:21:56 +00:00
|
|
|
dev->flags |= DM_FLAG_ACTIVATED;
|
|
|
|
|
2015-09-12 14:45:19 +00:00
|
|
|
/*
|
|
|
|
* Process pinctrl for everything except the root device, and
|
2016-01-22 02:43:25 +00:00
|
|
|
* continue regardless of the result of pinctrl. Don't process pinctrl
|
|
|
|
* settings for pinctrl devices since the device may not yet be
|
|
|
|
* probed.
|
2015-09-12 14:45:19 +00:00
|
|
|
*/
|
2016-01-22 02:43:25 +00:00
|
|
|
if (dev->parent && device_get_uclass_id(dev) != UCLASS_PINCTRL)
|
2015-09-12 14:45:19 +00:00
|
|
|
pinctrl_select_state(dev, "default");
|
pinctrl: add pin control uclass support
This creates a new framework for handling of pin control devices,
i.e. devices that control different aspects of package pins.
This uclass handles pinmuxing and pin configuration; pinmuxing
controls switching among silicon blocks that share certain physical
pins, pin configuration handles electronic properties such as pin-
biasing, load capacitance etc.
This framework can support the same device tree bindings, but if you
do not need full interface support, you can disable some features to
reduce memory foot print. Typically around 1.5KB is necessary to
include full-featured uclass support on ARM board (CONFIG_PINCTRL +
CONFIG_PINCTRL_FULL + CONFIG_PINCTRL_GENERIC + CONFIG_PINCTRL_PINMUX),
for example.
We are often limited on code size for SPL. Besides, we still have
many boards that do not support device tree configuration. The full
pinctrl, which requires OF_CONTROL, does not make sense for those
boards. So, this framework also has a Do-It-Yourself (let's say
simple pinctrl) interface. With CONFIG_PINCTRL_FULL disabled, the
uclass itself provides no systematic mechanism for identifying the
peripheral device, applying pinctrl settings, etc. They must be
done in each low-level driver. In return, you can save much memory
footprint and it might be useful especially for SPL.
Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
Acked-by: Simon Glass <sjg@chromium.org>
2015-08-27 03:44:29 +00:00
|
|
|
|
2015-03-05 19:25:22 +00:00
|
|
|
ret = uclass_pre_probe_device(dev);
|
2015-01-25 15:27:10 +00:00
|
|
|
if (ret)
|
|
|
|
goto fail;
|
|
|
|
|
2014-07-23 12:55:21 +00:00
|
|
|
if (dev->parent && dev->parent->driver->child_pre_probe) {
|
|
|
|
ret = dev->parent->driver->child_pre_probe(dev);
|
|
|
|
if (ret)
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2014-02-26 22:59:18 +00:00
|
|
|
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);
|
2015-03-05 19:25:21 +00:00
|
|
|
if (ret) {
|
|
|
|
dev->flags &= ~DM_FLAG_ACTIVATED;
|
2014-02-26 22:59:18 +00:00
|
|
|
goto fail;
|
2015-03-05 19:25:21 +00:00
|
|
|
}
|
2014-02-26 22:59:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ret = uclass_post_probe_device(dev);
|
2015-03-25 18:21:56 +00:00
|
|
|
if (ret)
|
2014-02-26 22:59:18 +00:00
|
|
|
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:
|
2015-03-25 18:21:56 +00:00
|
|
|
dev->flags &= ~DM_FLAG_ACTIVATED;
|
|
|
|
|
2014-07-23 12:55:12 +00:00
|
|
|
dev->seq = -1;
|
2014-02-26 22:59:18 +00:00
|
|
|
device_free(dev);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-10-14 05:41:50 +00:00
|
|
|
int device_probe(struct udevice *dev)
|
|
|
|
{
|
|
|
|
return device_probe_child(dev, NULL);
|
|
|
|
}
|
|
|
|
|
2014-05-22 10:43:05 +00:00
|
|
|
void *dev_get_platdata(struct udevice *dev)
|
2014-02-26 22:59:18 +00:00
|
|
|
{
|
|
|
|
if (!dev) {
|
2014-12-10 15:55:56 +00:00
|
|
|
dm_warn("%s: null device\n", __func__);
|
2014-02-26 22:59:18 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return dev->platdata;
|
|
|
|
}
|
|
|
|
|
2015-01-25 15:27:01 +00:00
|
|
|
void *dev_get_parent_platdata(struct udevice *dev)
|
|
|
|
{
|
|
|
|
if (!dev) {
|
2015-07-06 22:47:40 +00:00
|
|
|
dm_warn("%s: null device\n", __func__);
|
2015-01-25 15:27:01 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return dev->parent_platdata;
|
|
|
|
}
|
|
|
|
|
2015-04-15 11:07:18 +00:00
|
|
|
void *dev_get_uclass_platdata(struct udevice *dev)
|
|
|
|
{
|
|
|
|
if (!dev) {
|
2015-07-06 22:47:40 +00:00
|
|
|
dm_warn("%s: null device\n", __func__);
|
2015-04-15 11:07:18 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return dev->uclass_platdata;
|
|
|
|
}
|
|
|
|
|
2014-05-22 10:43:05 +00:00
|
|
|
void *dev_get_priv(struct udevice *dev)
|
2014-02-26 22:59:18 +00:00
|
|
|
{
|
|
|
|
if (!dev) {
|
2014-12-10 15:55:56 +00:00
|
|
|
dm_warn("%s: null device\n", __func__);
|
2014-02-26 22:59:18 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return dev->priv;
|
|
|
|
}
|
2014-07-23 12:55:19 +00:00
|
|
|
|
2015-03-05 19:25:20 +00:00
|
|
|
void *dev_get_uclass_priv(struct udevice *dev)
|
|
|
|
{
|
|
|
|
if (!dev) {
|
|
|
|
dm_warn("%s: null device\n", __func__);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return dev->uclass_priv;
|
|
|
|
}
|
|
|
|
|
2015-09-29 05:32:01 +00:00
|
|
|
void *dev_get_parent_priv(struct udevice *dev)
|
2014-07-23 12:55:20 +00:00
|
|
|
{
|
|
|
|
if (!dev) {
|
2014-12-10 15:55:56 +00:00
|
|
|
dm_warn("%s: null device\n", __func__);
|
2014-07-23 12:55:20 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return dev->parent_priv;
|
|
|
|
}
|
|
|
|
|
2014-07-23 12:55:19 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-06-23 21:38:38 +00:00
|
|
|
int device_get_child_by_of_offset(struct udevice *parent, int node,
|
2014-07-23 12:55:19 +00:00
|
|
|
struct udevice **devp)
|
|
|
|
{
|
|
|
|
struct udevice *dev;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
*devp = NULL;
|
2015-06-23 21:38:38 +00:00
|
|
|
ret = device_find_child_by_of_offset(parent, node, &dev);
|
2014-07-23 12:55:19 +00:00
|
|
|
return device_get_device_tail(dev, ret, devp);
|
|
|
|
}
|
2014-10-14 05:41:49 +00:00
|
|
|
|
2015-06-23 21:38:37 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2014-10-14 05:41:49 +00:00
|
|
|
int device_find_first_child(struct udevice *parent, struct udevice **devp)
|
|
|
|
{
|
|
|
|
if (list_empty(&parent->child_head)) {
|
|
|
|
*devp = NULL;
|
|
|
|
} else {
|
|
|
|
*devp = list_first_entry(&parent->child_head, struct udevice,
|
|
|
|
sibling_node);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int device_find_next_child(struct udevice **devp)
|
|
|
|
{
|
|
|
|
struct udevice *dev = *devp;
|
|
|
|
struct udevice *parent = dev->parent;
|
|
|
|
|
|
|
|
if (list_is_last(&dev->sibling_node, &parent->child_head)) {
|
|
|
|
*devp = NULL;
|
|
|
|
} else {
|
|
|
|
*devp = list_entry(dev->sibling_node.next, struct udevice,
|
|
|
|
sibling_node);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2014-11-11 17:46:18 +00:00
|
|
|
|
2014-11-11 17:46:19 +00:00
|
|
|
struct udevice *dev_get_parent(struct udevice *child)
|
|
|
|
{
|
|
|
|
return child->parent;
|
|
|
|
}
|
|
|
|
|
2015-03-25 18:21:55 +00:00
|
|
|
ulong dev_get_driver_data(struct udevice *dev)
|
2014-11-11 17:46:18 +00:00
|
|
|
{
|
2015-03-25 18:21:55 +00:00
|
|
|
return dev->driver_data;
|
2014-11-11 17:46:18 +00:00
|
|
|
}
|
2015-01-25 15:27:04 +00:00
|
|
|
|
2015-04-15 11:07:24 +00:00
|
|
|
const void *dev_get_driver_ops(struct udevice *dev)
|
|
|
|
{
|
|
|
|
if (!dev || !dev->driver->ops)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return dev->driver->ops;
|
|
|
|
}
|
|
|
|
|
2015-01-25 15:27:04 +00:00
|
|
|
enum uclass_id device_get_uclass_id(struct udevice *dev)
|
|
|
|
{
|
|
|
|
return dev->uclass->uc_drv->id;
|
|
|
|
}
|
2015-02-10 06:46:32 +00:00
|
|
|
|
2015-04-15 11:07:25 +00:00
|
|
|
const char *dev_get_uclass_name(struct udevice *dev)
|
|
|
|
{
|
|
|
|
if (!dev)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return dev->uclass->uc_drv->name;
|
|
|
|
}
|
|
|
|
|
2015-12-23 15:09:36 +00:00
|
|
|
fdt_addr_t dev_get_addr_index(struct udevice *dev, int index)
|
2015-02-10 06:46:32 +00:00
|
|
|
{
|
2015-08-11 22:31:55 +00:00
|
|
|
#if CONFIG_IS_ENABLED(OF_CONTROL)
|
2015-07-08 02:53:44 +00:00
|
|
|
fdt_addr_t addr;
|
|
|
|
|
2015-09-02 05:41:12 +00:00
|
|
|
if (CONFIG_IS_ENABLED(OF_TRANSLATE)) {
|
|
|
|
const fdt32_t *reg;
|
2015-12-23 15:09:36 +00:00
|
|
|
int len = 0;
|
|
|
|
int na, ns;
|
2015-09-02 05:41:12 +00:00
|
|
|
|
2015-12-23 15:09:36 +00:00
|
|
|
na = fdt_address_cells(gd->fdt_blob, dev->parent->of_offset);
|
|
|
|
if (na < 1) {
|
|
|
|
debug("bad #address-cells\n");
|
2015-09-02 05:41:12 +00:00
|
|
|
return FDT_ADDR_T_NONE;
|
2015-12-23 15:09:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ns = fdt_size_cells(gd->fdt_blob, dev->parent->of_offset);
|
|
|
|
if (ns < 0) {
|
|
|
|
debug("bad #size-cells\n");
|
|
|
|
return FDT_ADDR_T_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
reg = fdt_getprop(gd->fdt_blob, dev->of_offset, "reg", &len);
|
|
|
|
if (!reg || (len <= (index * sizeof(fdt32_t) * (na + ns)))) {
|
|
|
|
debug("Req index out of range\n");
|
|
|
|
return FDT_ADDR_T_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
reg += index * (na + ns);
|
2015-09-02 05:41:12 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Use the full-fledged translate function for complex
|
|
|
|
* bus setups.
|
|
|
|
*/
|
2015-12-14 15:18:15 +00:00
|
|
|
addr = fdt_translate_address((void *)gd->fdt_blob,
|
2015-09-02 05:41:12 +00:00
|
|
|
dev->of_offset, reg);
|
2015-12-14 15:18:15 +00:00
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* Use the "simple" translate function for less complex
|
|
|
|
* bus setups.
|
|
|
|
*/
|
|
|
|
addr = fdtdec_get_addr_size_auto_parent(gd->fdt_blob,
|
|
|
|
dev->parent->of_offset,
|
|
|
|
dev->of_offset, "reg",
|
2015-12-23 15:09:36 +00:00
|
|
|
index, NULL);
|
2015-12-14 15:18:15 +00:00
|
|
|
if (CONFIG_IS_ENABLED(SIMPLE_BUS) && addr != FDT_ADDR_T_NONE) {
|
|
|
|
if (device_get_uclass_id(dev->parent) ==
|
|
|
|
UCLASS_SIMPLE_BUS)
|
|
|
|
addr = simple_bus_translate(dev->parent, addr);
|
|
|
|
}
|
2015-09-02 05:41:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2015-12-14 15:18:15 +00:00
|
|
|
* Some platforms need a special address translation. Those
|
|
|
|
* platforms (e.g. mvebu in SPL) can configure a translation
|
|
|
|
* offset in the DM by calling dm_set_translation_offset() that
|
|
|
|
* will get added to all addresses returned by dev_get_addr().
|
2015-09-02 05:41:12 +00:00
|
|
|
*/
|
2015-12-14 15:18:15 +00:00
|
|
|
addr += dm_get_translation_offset();
|
2015-07-08 02:53:44 +00:00
|
|
|
|
|
|
|
return addr;
|
2015-02-10 06:46:32 +00:00
|
|
|
#else
|
|
|
|
return FDT_ADDR_T_NONE;
|
|
|
|
#endif
|
2015-07-08 02:53:44 +00:00
|
|
|
}
|
2015-03-25 18:21:57 +00:00
|
|
|
|
2015-12-23 15:09:36 +00:00
|
|
|
fdt_addr_t dev_get_addr(struct udevice *dev)
|
|
|
|
{
|
|
|
|
return dev_get_addr_index(dev, 0);
|
|
|
|
}
|
|
|
|
|
2015-03-25 18:21:57 +00:00
|
|
|
bool device_has_children(struct udevice *dev)
|
|
|
|
{
|
|
|
|
return !list_empty(&dev->child_head);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool device_has_active_children(struct udevice *dev)
|
|
|
|
{
|
|
|
|
struct udevice *child;
|
|
|
|
|
|
|
|
for (device_find_first_child(dev, &child);
|
|
|
|
child;
|
|
|
|
device_find_next_child(&child)) {
|
|
|
|
if (device_active(child))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool device_is_last_sibling(struct udevice *dev)
|
|
|
|
{
|
|
|
|
struct udevice *parent = dev->parent;
|
|
|
|
|
|
|
|
if (!parent)
|
|
|
|
return false;
|
|
|
|
return list_is_last(&dev->sibling_node, &parent->child_head);
|
|
|
|
}
|
2015-07-30 19:40:39 +00:00
|
|
|
|
|
|
|
int device_set_name(struct udevice *dev, const char *name)
|
|
|
|
{
|
|
|
|
name = strdup(name);
|
|
|
|
if (!name)
|
|
|
|
return -ENOMEM;
|
|
|
|
dev->name = name;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|