GK SDK 源码库: XMIPCLinuxV100R005C00SPC030 (kernel/tools/open_source excluded)
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
menu "Generic Driver Options"
|
||||
|
||||
config DM
|
||||
bool "Enable Driver Model"
|
||||
help
|
||||
This config option enables Driver Model. This brings in the core
|
||||
support, including scanning of platform data on start-up. If
|
||||
CONFIG_OF_CONTROL is enabled, the device tree will be scanned also
|
||||
when available.
|
||||
|
||||
config SPL_DM
|
||||
bool "Enable Driver Model for SPL"
|
||||
depends on DM && SPL
|
||||
help
|
||||
Enable driver model in SPL. You will need to provide a
|
||||
suitable malloc() implementation. If you are not using the
|
||||
full malloc() enabled by CONFIG_SYS_SPL_MALLOC_START,
|
||||
consider using CONFIG_SYS_MALLOC_SIMPLE. In that case you
|
||||
must provide CONFIG_SPL_SYS_MALLOC_F_LEN to set the size.
|
||||
In most cases driver model will only allocate a few uclasses
|
||||
and devices in SPL, so 1KB should be enable. See
|
||||
CONFIG_SPL_SYS_MALLOC_F_LEN for more details on how to enable it.
|
||||
|
||||
config TPL_DM
|
||||
bool "Enable Driver Model for TPL"
|
||||
depends on DM && TPL
|
||||
help
|
||||
Enable driver model in TPL. You will need to provide a
|
||||
suitable malloc() implementation. If you are not using the
|
||||
full malloc() enabled by CONFIG_SYS_SPL_MALLOC_START,
|
||||
consider using CONFIG_SYS_MALLOC_SIMPLE. In that case you
|
||||
must provide CONFIG_SPL_SYS_MALLOC_F_LEN to set the size.
|
||||
In most cases driver model will only allocate a few uclasses
|
||||
and devices in SPL, so 1KB should be enough. See
|
||||
CONFIG_SPL_SYS_MALLOC_F_LEN for more details on how to enable it.
|
||||
Disable this for very small implementations.
|
||||
|
||||
config DM_WARN
|
||||
bool "Enable warnings in driver model"
|
||||
depends on DM
|
||||
default y
|
||||
help
|
||||
The dm_warn() function can use up quite a bit of space for its
|
||||
strings. By default this is disabled for SPL builds to save space.
|
||||
This will cause dm_warn() to be compiled out - it will do nothing
|
||||
when called.
|
||||
|
||||
config DM_DEBUG
|
||||
bool "Enable debug messages in driver model core"
|
||||
depends on DM
|
||||
help
|
||||
Say Y here if you want to compile in debug messages in DM core.
|
||||
|
||||
config DM_DEVICE_REMOVE
|
||||
bool "Support device removal"
|
||||
depends on DM
|
||||
default y
|
||||
help
|
||||
We can save some code space by dropping support for removing a
|
||||
device.
|
||||
|
||||
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 SPL_DM_DEVICE_REMOVE
|
||||
bool "Support device removal in SPL"
|
||||
depends on SPL_DM
|
||||
default n
|
||||
help
|
||||
We can save some code space by dropping support for removing a
|
||||
device. This is not normally required in SPL, so by default this
|
||||
option is disabled for SPL.
|
||||
|
||||
config DM_STDIO
|
||||
bool "Support stdio registration"
|
||||
depends on DM
|
||||
default y
|
||||
help
|
||||
Normally serial drivers register with stdio so that they can be used
|
||||
as normal output devices. In SPL we don't normally use stdio, so
|
||||
we can omit this feature.
|
||||
|
||||
config DM_SEQ_ALIAS
|
||||
bool "Support numbered aliases in device tree"
|
||||
depends on DM
|
||||
default y
|
||||
help
|
||||
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.
|
||||
|
||||
config SPL_DM_SEQ_ALIAS
|
||||
bool "Support numbered aliases in device tree in SPL"
|
||||
depends on SPL_DM
|
||||
default n
|
||||
help
|
||||
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 SPL_REGMAP
|
||||
bool "Support register maps in SPL"
|
||||
depends on SPL_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 TPL_REGMAP
|
||||
bool "Support register maps in TPL"
|
||||
depends on TPL_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 SPL_SYSCON
|
||||
bool "Support system controllers in SPL"
|
||||
depends on SPL_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 TPL_SYSCON
|
||||
bool "Support system controllers in TPL"
|
||||
depends on TPL_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.
|
||||
|
||||
config SIMPLE_BUS
|
||||
bool "Support simple-bus driver"
|
||||
depends on DM && OF_CONTROL
|
||||
default y
|
||||
help
|
||||
Supports the 'simple-bus' driver, which is used on some systems.
|
||||
|
||||
config SPL_SIMPLE_BUS
|
||||
bool "Support simple-bus driver in SPL"
|
||||
depends on SPL_DM && SPL_OF_CONTROL
|
||||
default y
|
||||
help
|
||||
Supports the 'simple-bus' driver, which is used on some systems
|
||||
in SPL.
|
||||
|
||||
config OF_TRANSLATE
|
||||
bool "Translate addresses using fdt_translate_address"
|
||||
depends on DM && OF_CONTROL
|
||||
default y
|
||||
help
|
||||
If this option is enabled, the reg property will be translated
|
||||
using the fdt_translate_address() function. This is necessary
|
||||
on some platforms (e.g. MVEBU) using complex "ranges"
|
||||
properties in many nodes. As this translation is not handled
|
||||
correctly in the default simple_bus_translate() function.
|
||||
|
||||
If this option is not enabled, simple_bus_translate() will be
|
||||
used for the address translation. This function is faster and
|
||||
smaller in size than fdt_translate_address().
|
||||
|
||||
config SPL_OF_TRANSLATE
|
||||
bool "Translate addresses using fdt_translate_address in SPL"
|
||||
depends on SPL_DM && SPL_OF_CONTROL
|
||||
default n
|
||||
help
|
||||
If this option is enabled, the reg property will be translated
|
||||
using the fdt_translate_address() function. This is necessary
|
||||
on some platforms (e.g. MVEBU) using complex "ranges"
|
||||
properties in many nodes. As this translation is not handled
|
||||
correctly in the default simple_bus_translate() function.
|
||||
|
||||
If this option is not enabled, simple_bus_translate() will be
|
||||
used for the address translation. This function is faster and
|
||||
smaller in size than fdt_translate_address().
|
||||
|
||||
config TRANSLATION_OFFSET
|
||||
bool "Platforms specific translation offset"
|
||||
depends on DM && OF_CONTROL
|
||||
help
|
||||
Some platforms need a special address translation. Those
|
||||
platforms (e.g. mvebu in SPL) can configure a translation
|
||||
offset by enabling this option and setting the translation_offset
|
||||
variable in the GD in their platform- / board-specific code.
|
||||
|
||||
config OF_ISA_BUS
|
||||
bool
|
||||
depends on OF_TRANSLATE
|
||||
help
|
||||
Is this option is enabled then support for the ISA bus will
|
||||
be included for addresses read from DT. This is something that
|
||||
should be known to be required or not based upon the board
|
||||
being targeted, and whether or not it makes use of an ISA bus.
|
||||
|
||||
The bus is matched based upon its node name equalling "isa". The
|
||||
busses #address-cells should equal 2, with the first cell being
|
||||
used to hold flags & flag 0x1 indicating that the address range
|
||||
should be accessed using I/O port in/out accessors. The second
|
||||
cell holds the offset into ISA bus address space. The #size-cells
|
||||
property should equal 1, and of course holds the size of the
|
||||
address range used by a device.
|
||||
|
||||
If this option is not enabled then support for the ISA bus is
|
||||
not included and any such busses used in DT will be treated as
|
||||
typical simple-bus compatible busses. This will lead to
|
||||
mistranslation of device addresses, so ensure that this is
|
||||
enabled if your board does include an ISA bus.
|
||||
|
||||
config DM_DEV_READ_INLINE
|
||||
bool
|
||||
default y if !OF_LIVE
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,18 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
#
|
||||
# Copyright (c) 2013 Google, Inc
|
||||
|
||||
obj-y += device.o fdtaddr.o lists.o root.o uclass.o util.o
|
||||
obj-$(CONFIG_DEVRES) += devres.o
|
||||
obj-$(CONFIG_$(SPL_)DM_DEVICE_REMOVE) += device-remove.o
|
||||
obj-$(CONFIG_$(SPL_)SIMPLE_BUS) += simple-bus.o
|
||||
obj-$(CONFIG_DM) += dump.o
|
||||
obj-$(CONFIG_$(SPL_TPL_)REGMAP) += regmap.o
|
||||
obj-$(CONFIG_$(SPL_TPL_)SYSCON) += syscon-uclass.o
|
||||
obj-$(CONFIG_OF_LIVE) += of_access.o of_addr.o
|
||||
ifndef CONFIG_DM_DEV_READ_INLINE
|
||||
obj-$(CONFIG_OF_CONTROL) += read.o
|
||||
endif
|
||||
obj-$(CONFIG_OF_CONTROL) += of_extra.o ofnode.o read_extra.o
|
||||
|
||||
ccflags-$(CONFIG_DM_DEBUG) += -DDEBUG
|
||||
@@ -0,0 +1,221 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Device manager
|
||||
*
|
||||
* Copyright (c) 2014 Google, Inc
|
||||
*
|
||||
* (C) Copyright 2012
|
||||
* Pavel Herrmann <morpheus.ibis@gmail.com>
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <errno.h>
|
||||
#include <malloc.h>
|
||||
#include <dm/device.h>
|
||||
#include <dm/device-internal.h>
|
||||
#include <dm/uclass.h>
|
||||
#include <dm/uclass-internal.h>
|
||||
#include <dm/util.h>
|
||||
#include <power-domain.h>
|
||||
|
||||
int device_chld_unbind(struct udevice *dev, struct driver *drv)
|
||||
{
|
||||
struct udevice *pos, *n;
|
||||
int ret, saved_ret = 0;
|
||||
|
||||
assert(dev);
|
||||
|
||||
list_for_each_entry_safe(pos, n, &dev->child_head, sibling_node) {
|
||||
if (drv && (pos->driver != drv))
|
||||
continue;
|
||||
|
||||
ret = device_unbind(pos);
|
||||
if (ret && !saved_ret)
|
||||
saved_ret = ret;
|
||||
}
|
||||
|
||||
return saved_ret;
|
||||
}
|
||||
|
||||
int device_chld_remove(struct udevice *dev, struct driver *drv,
|
||||
uint flags)
|
||||
{
|
||||
struct udevice *pos, *n;
|
||||
int ret;
|
||||
|
||||
assert(dev);
|
||||
|
||||
list_for_each_entry_safe(pos, n, &dev->child_head, sibling_node) {
|
||||
if (drv && (pos->driver != drv))
|
||||
continue;
|
||||
|
||||
ret = device_remove(pos, flags);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int device_unbind(struct udevice *dev)
|
||||
{
|
||||
const struct driver *drv;
|
||||
int ret;
|
||||
|
||||
if (!dev)
|
||||
return -EINVAL;
|
||||
|
||||
if (dev->flags & DM_FLAG_ACTIVATED)
|
||||
return -EINVAL;
|
||||
|
||||
if (!(dev->flags & DM_FLAG_BOUND))
|
||||
return -EINVAL;
|
||||
|
||||
drv = dev->driver;
|
||||
assert(drv);
|
||||
|
||||
if (drv->unbind) {
|
||||
ret = drv->unbind(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = device_chld_unbind(dev, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (dev->flags & DM_FLAG_ALLOC_PDATA) {
|
||||
free(dev->platdata);
|
||||
dev->platdata = NULL;
|
||||
}
|
||||
if (dev->flags & DM_FLAG_ALLOC_UCLASS_PDATA) {
|
||||
free(dev->uclass_platdata);
|
||||
dev->uclass_platdata = NULL;
|
||||
}
|
||||
if (dev->flags & DM_FLAG_ALLOC_PARENT_PDATA) {
|
||||
free(dev->parent_platdata);
|
||||
dev->parent_platdata = NULL;
|
||||
}
|
||||
ret = uclass_unbind_device(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (dev->parent)
|
||||
list_del(&dev->sibling_node);
|
||||
|
||||
devres_release_all(dev);
|
||||
|
||||
if (dev->flags & DM_FLAG_NAME_ALLOCED)
|
||||
free((char *)dev->name);
|
||||
free(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* device_free() - Free memory buffers allocated by a device
|
||||
* @dev: Device that is to be started
|
||||
*/
|
||||
void device_free(struct udevice *dev)
|
||||
{
|
||||
int size;
|
||||
|
||||
if (dev->driver->priv_auto_alloc_size) {
|
||||
free(dev->priv);
|
||||
dev->priv = NULL;
|
||||
}
|
||||
size = dev->uclass->uc_drv->per_device_auto_alloc_size;
|
||||
if (size) {
|
||||
free(dev->uclass_priv);
|
||||
dev->uclass_priv = NULL;
|
||||
}
|
||||
if (dev->parent) {
|
||||
size = dev->parent->driver->per_child_auto_alloc_size;
|
||||
if (!size) {
|
||||
size = dev->parent->uclass->uc_drv->
|
||||
per_child_auto_alloc_size;
|
||||
}
|
||||
if (size) {
|
||||
free(dev->parent_priv);
|
||||
dev->parent_priv = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
devres_release_probe(dev);
|
||||
}
|
||||
|
||||
static bool flags_remove(uint flags, uint drv_flags)
|
||||
{
|
||||
if ((flags & DM_REMOVE_NORMAL) ||
|
||||
(flags & (drv_flags & (DM_FLAG_ACTIVE_DMA | DM_FLAG_OS_PREPARE))))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int device_remove(struct udevice *dev, uint flags)
|
||||
{
|
||||
const 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, NULL, flags);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
/*
|
||||
* Remove the device if called with the "normal" remove flag set,
|
||||
* or if the remove flag matches any of the drivers remove flags
|
||||
*/
|
||||
if (drv->remove && flags_remove(flags, drv->flags)) {
|
||||
ret = drv->remove(dev);
|
||||
if (ret)
|
||||
goto err_remove;
|
||||
}
|
||||
|
||||
if (dev->parent && dev->parent->driver->child_post_remove) {
|
||||
ret = dev->parent->driver->child_post_remove(dev);
|
||||
if (ret) {
|
||||
dm_warn("%s: Device '%s' failed child_post_remove()",
|
||||
__func__, dev->name);
|
||||
}
|
||||
}
|
||||
|
||||
if (!(drv->flags & DM_FLAG_DEFAULT_PD_CTRL_OFF) &&
|
||||
(dev != gd->cur_serial_dev))
|
||||
dev_power_domain_off(dev);
|
||||
|
||||
if (flags_remove(flags, drv->flags)) {
|
||||
device_free(dev);
|
||||
|
||||
dev->seq = -1;
|
||||
dev->flags &= ~DM_FLAG_ACTIVATED;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,904 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Device manager
|
||||
*
|
||||
* Copyright (c) 2013 Google, Inc
|
||||
*
|
||||
* (C) Copyright 2012
|
||||
* Pavel Herrmann <morpheus.ibis@gmail.com>
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <cpu_func.h>
|
||||
#include <asm/io.h>
|
||||
#include <clk.h>
|
||||
#include <fdtdec.h>
|
||||
#include <fdt_support.h>
|
||||
#include <malloc.h>
|
||||
#include <dm/device.h>
|
||||
#include <dm/device-internal.h>
|
||||
#include <dm/lists.h>
|
||||
#include <dm/of_access.h>
|
||||
#include <dm/pinctrl.h>
|
||||
#include <dm/platdata.h>
|
||||
#include <dm/read.h>
|
||||
#include <dm/uclass.h>
|
||||
#include <dm/uclass-internal.h>
|
||||
#include <dm/util.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/list.h>
|
||||
#include <power-domain.h>
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
||||
static int device_bind_common(struct udevice *parent, const struct driver *drv,
|
||||
const char *name, void *platdata,
|
||||
ulong driver_data, ofnode node,
|
||||
uint of_platdata_size, struct udevice **devp)
|
||||
{
|
||||
struct udevice *dev;
|
||||
struct uclass *uc;
|
||||
int size, ret = 0;
|
||||
|
||||
if (devp)
|
||||
*devp = NULL;
|
||||
if (!name)
|
||||
return -EINVAL;
|
||||
|
||||
ret = uclass_get(drv->id, &uc);
|
||||
if (ret) {
|
||||
debug("Missing uclass for driver %s\n", drv->name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev = calloc(1, sizeof(struct udevice));
|
||||
if (!dev)
|
||||
return -ENOMEM;
|
||||
|
||||
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->driver_data = driver_data;
|
||||
dev->name = name;
|
||||
dev->node = node;
|
||||
dev->parent = parent;
|
||||
dev->driver = drv;
|
||||
dev->uclass = uc;
|
||||
|
||||
dev->seq = -1;
|
||||
dev->req_seq = -1;
|
||||
if (CONFIG_IS_ENABLED(DM_SEQ_ALIAS) &&
|
||||
(uc->uc_drv->flags & DM_UC_FLAG_SEQ_ALIAS)) {
|
||||
/*
|
||||
* 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 (CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)) {
|
||||
if (uc->uc_drv->name && ofnode_valid(node))
|
||||
dev_read_alias_seq(dev, &dev->req_seq);
|
||||
#if CONFIG_IS_ENABLED(OF_PRIOR_STAGE)
|
||||
if (dev->req_seq == -1)
|
||||
dev->req_seq =
|
||||
uclass_find_next_free_req_seq(drv->id);
|
||||
#endif
|
||||
} else {
|
||||
dev->req_seq = uclass_find_next_free_req_seq(drv->id);
|
||||
}
|
||||
}
|
||||
|
||||
if (drv->platdata_auto_alloc_size) {
|
||||
bool alloc = !platdata;
|
||||
|
||||
if (CONFIG_IS_ENABLED(OF_PLATDATA)) {
|
||||
if (of_platdata_size) {
|
||||
dev->flags |= DM_FLAG_OF_PLATDATA;
|
||||
if (of_platdata_size <
|
||||
drv->platdata_auto_alloc_size)
|
||||
alloc = true;
|
||||
}
|
||||
}
|
||||
if (alloc) {
|
||||
dev->flags |= DM_FLAG_ALLOC_PDATA;
|
||||
dev->platdata = calloc(1,
|
||||
drv->platdata_auto_alloc_size);
|
||||
if (!dev->platdata) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_alloc1;
|
||||
}
|
||||
if (CONFIG_IS_ENABLED(OF_PLATDATA) && platdata) {
|
||||
memcpy(dev->platdata, platdata,
|
||||
of_platdata_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
if (!size) {
|
||||
size = parent->uclass->uc_drv->
|
||||
per_child_platdata_auto_alloc_size;
|
||||
}
|
||||
if (size) {
|
||||
dev->flags |= DM_FLAG_ALLOC_PARENT_PDATA;
|
||||
dev->parent_platdata = calloc(1, size);
|
||||
if (!dev->parent_platdata) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_alloc3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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_uclass_bind;
|
||||
|
||||
/* if we fail to bind we remove device from successors and free it */
|
||||
if (drv->bind) {
|
||||
ret = drv->bind(dev);
|
||||
if (ret)
|
||||
goto fail_bind;
|
||||
}
|
||||
if (parent && parent->driver->child_post_bind) {
|
||||
ret = parent->driver->child_post_bind(dev);
|
||||
if (ret)
|
||||
goto fail_child_post_bind;
|
||||
}
|
||||
if (uc->uc_drv->post_bind) {
|
||||
ret = uc->uc_drv->post_bind(dev);
|
||||
if (ret)
|
||||
goto fail_uclass_post_bind;
|
||||
}
|
||||
|
||||
if (parent)
|
||||
pr_debug("Bound device %s to %s\n", dev->name, parent->name);
|
||||
if (devp)
|
||||
*devp = dev;
|
||||
|
||||
dev->flags |= DM_FLAG_BOUND;
|
||||
|
||||
return 0;
|
||||
|
||||
fail_uclass_post_bind:
|
||||
/* There is no child unbind() method, so no clean-up required */
|
||||
fail_child_post_bind:
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
fail_bind:
|
||||
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 (CONFIG_IS_ENABLED(DM_DEVICE_REMOVE)) {
|
||||
list_del(&dev->sibling_node);
|
||||
if (dev->flags & DM_FLAG_ALLOC_PARENT_PDATA) {
|
||||
free(dev->parent_platdata);
|
||||
dev->parent_platdata = NULL;
|
||||
}
|
||||
}
|
||||
fail_alloc3:
|
||||
if (dev->flags & DM_FLAG_ALLOC_UCLASS_PDATA) {
|
||||
free(dev->uclass_platdata);
|
||||
dev->uclass_platdata = NULL;
|
||||
}
|
||||
fail_alloc2:
|
||||
if (dev->flags & DM_FLAG_ALLOC_PDATA) {
|
||||
free(dev->platdata);
|
||||
dev->platdata = NULL;
|
||||
}
|
||||
fail_alloc1:
|
||||
devres_release_all(dev);
|
||||
|
||||
free(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int device_bind_with_driver_data(struct udevice *parent,
|
||||
const struct driver *drv, const char *name,
|
||||
ulong driver_data, ofnode node,
|
||||
struct udevice **devp)
|
||||
{
|
||||
return device_bind_common(parent, drv, name, NULL, driver_data, node,
|
||||
0, devp);
|
||||
}
|
||||
|
||||
int device_bind(struct udevice *parent, const struct driver *drv,
|
||||
const char *name, void *platdata, int of_offset,
|
||||
struct udevice **devp)
|
||||
{
|
||||
return device_bind_common(parent, drv, name, platdata, 0,
|
||||
offset_to_ofnode(of_offset), 0, devp);
|
||||
}
|
||||
|
||||
int device_bind_ofnode(struct udevice *parent, const struct driver *drv,
|
||||
const char *name, void *platdata, ofnode node,
|
||||
struct udevice **devp)
|
||||
{
|
||||
return device_bind_common(parent, drv, name, platdata, 0, node, 0,
|
||||
devp);
|
||||
}
|
||||
|
||||
int device_bind_by_name(struct udevice *parent, bool pre_reloc_only,
|
||||
const struct driver_info *info, struct udevice **devp)
|
||||
{
|
||||
struct driver *drv;
|
||||
uint platdata_size = 0;
|
||||
|
||||
drv = lists_driver_lookup_name(info->name);
|
||||
if (!drv)
|
||||
return -ENOENT;
|
||||
if (pre_reloc_only && !(drv->flags & DM_FLAG_PRE_RELOC))
|
||||
return -EPERM;
|
||||
|
||||
#if CONFIG_IS_ENABLED(OF_PLATDATA)
|
||||
platdata_size = info->platdata_size;
|
||||
#endif
|
||||
return device_bind_common(parent, drv, info->name,
|
||||
(void *)info->platdata, 0, ofnode_null(), platdata_size,
|
||||
devp);
|
||||
}
|
||||
|
||||
static void *alloc_priv(int size, uint flags)
|
||||
{
|
||||
void *priv;
|
||||
|
||||
if (flags & DM_FLAG_ALLOC_PRIV_DMA) {
|
||||
size = ROUND(size, ARCH_DMA_MINALIGN);
|
||||
priv = memalign(ARCH_DMA_MINALIGN, size);
|
||||
if (priv) {
|
||||
memset(priv, '\0', size);
|
||||
|
||||
/*
|
||||
* Ensure that the zero bytes are flushed to memory.
|
||||
* This prevents problems if the driver uses this as
|
||||
* both an input and an output buffer:
|
||||
*
|
||||
* 1. Zeroes written to buffer (here) and sit in the
|
||||
* cache
|
||||
* 2. Driver issues a read command to DMA
|
||||
* 3. CPU runs out of cache space and evicts some cache
|
||||
* data in the buffer, writing zeroes to RAM from
|
||||
* the memset() above
|
||||
* 4. DMA completes
|
||||
* 5. Buffer now has some DMA data and some zeroes
|
||||
* 6. Data being read is now incorrect
|
||||
*
|
||||
* To prevent this, ensure that the cache is clean
|
||||
* within this range at the start. The driver can then
|
||||
* use normal flush-after-write, invalidate-before-read
|
||||
* procedures.
|
||||
*
|
||||
* TODO(sjg@chromium.org): Drop this microblaze
|
||||
* exception.
|
||||
*/
|
||||
#ifndef CONFIG_MICROBLAZE
|
||||
flush_dcache_range((ulong)priv, (ulong)priv + size);
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
priv = calloc(1, size);
|
||||
}
|
||||
|
||||
return priv;
|
||||
}
|
||||
|
||||
int device_probe(struct udevice *dev)
|
||||
{
|
||||
const struct driver *drv;
|
||||
int size = 0;
|
||||
int ret;
|
||||
int seq;
|
||||
|
||||
if (!dev)
|
||||
return -EINVAL;
|
||||
|
||||
if (dev->flags & DM_FLAG_ACTIVATED)
|
||||
return 0;
|
||||
|
||||
drv = dev->driver;
|
||||
assert(drv);
|
||||
|
||||
/* 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 and not reentered */
|
||||
size = dev->uclass->uc_drv->per_device_auto_alloc_size;
|
||||
if (size && !dev->uclass_priv) {
|
||||
dev->uclass_priv = alloc_priv(size,
|
||||
dev->uclass->uc_drv->flags);
|
||||
if (!dev->uclass_priv) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure all parents are probed */
|
||||
if (dev->parent) {
|
||||
size = dev->parent->driver->per_child_auto_alloc_size;
|
||||
if (!size) {
|
||||
size = dev->parent->uclass->uc_drv->
|
||||
per_child_auto_alloc_size;
|
||||
}
|
||||
if (size && !dev->parent_priv) {
|
||||
dev->parent_priv = alloc_priv(size, drv->flags);
|
||||
if (!dev->parent_priv) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (seq < 0) {
|
||||
ret = seq;
|
||||
goto fail;
|
||||
}
|
||||
dev->seq = seq;
|
||||
|
||||
dev->flags |= DM_FLAG_ACTIVATED;
|
||||
|
||||
/*
|
||||
* Process pinctrl for everything except the root device, and
|
||||
* continue regardless of the result of pinctrl. Don't process pinctrl
|
||||
* settings for pinctrl devices since the device may not yet be
|
||||
* probed.
|
||||
*/
|
||||
if (dev->parent && device_get_uclass_id(dev) != UCLASS_PINCTRL)
|
||||
pinctrl_select_state(dev, "default");
|
||||
|
||||
if (CONFIG_IS_ENABLED(POWER_DOMAIN) && dev->parent &&
|
||||
(device_get_uclass_id(dev) != UCLASS_POWER_DOMAIN) &&
|
||||
!(drv->flags & DM_FLAG_DEFAULT_PD_CTRL_OFF)) {
|
||||
ret = dev_power_domain_on(dev);
|
||||
if (ret)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = uclass_pre_probe_device(dev);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
if (dev->parent && dev->parent->driver->child_pre_probe) {
|
||||
ret = dev->parent->driver->child_pre_probe(dev);
|
||||
if (ret)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (drv->ofdata_to_platdata &&
|
||||
(CONFIG_IS_ENABLED(OF_PLATDATA) || dev_has_of_node(dev))) {
|
||||
ret = drv->ofdata_to_platdata(dev);
|
||||
if (ret)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Only handle devices that have a valid ofnode */
|
||||
if (dev_of_valid(dev)) {
|
||||
/*
|
||||
* Process 'assigned-{clocks/clock-parents/clock-rates}'
|
||||
* properties
|
||||
*/
|
||||
ret = clk_set_defaults(dev, 0);
|
||||
if (ret)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (drv->probe) {
|
||||
ret = drv->probe(dev);
|
||||
if (ret) {
|
||||
dev->flags &= ~DM_FLAG_ACTIVATED;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
ret = uclass_post_probe_device(dev);
|
||||
if (ret)
|
||||
goto fail_uclass;
|
||||
|
||||
if (dev->parent && device_get_uclass_id(dev) == UCLASS_PINCTRL)
|
||||
pinctrl_select_state(dev, "default");
|
||||
|
||||
return 0;
|
||||
fail_uclass:
|
||||
if (device_remove(dev, DM_REMOVE_NORMAL)) {
|
||||
dm_warn("%s: Device '%s' failed to remove on error path\n",
|
||||
__func__, dev->name);
|
||||
}
|
||||
fail:
|
||||
dev->flags &= ~DM_FLAG_ACTIVATED;
|
||||
|
||||
dev->seq = -1;
|
||||
device_free(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void *dev_get_platdata(const struct udevice *dev)
|
||||
{
|
||||
if (!dev) {
|
||||
dm_warn("%s: null device\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return dev->platdata;
|
||||
}
|
||||
|
||||
void *dev_get_parent_platdata(const struct udevice *dev)
|
||||
{
|
||||
if (!dev) {
|
||||
dm_warn("%s: null device\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return dev->parent_platdata;
|
||||
}
|
||||
|
||||
void *dev_get_uclass_platdata(const struct udevice *dev)
|
||||
{
|
||||
if (!dev) {
|
||||
dm_warn("%s: null device\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return dev->uclass_platdata;
|
||||
}
|
||||
|
||||
void *dev_get_priv(const struct udevice *dev)
|
||||
{
|
||||
if (!dev) {
|
||||
dm_warn("%s: null device\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return dev->priv;
|
||||
}
|
||||
|
||||
void *dev_get_uclass_priv(const struct udevice *dev)
|
||||
{
|
||||
if (!dev) {
|
||||
dm_warn("%s: null device\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return dev->uclass_priv;
|
||||
}
|
||||
|
||||
void *dev_get_parent_priv(const struct udevice *dev)
|
||||
{
|
||||
if (!dev) {
|
||||
dm_warn("%s: null device\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return dev->parent_priv;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#if CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)
|
||||
/**
|
||||
* device_find_by_ofnode() - Return device associated with given ofnode
|
||||
*
|
||||
* The returned device is *not* activated.
|
||||
*
|
||||
* @node: The ofnode for which a associated device should be looked up
|
||||
* @devp: Pointer to structure to hold the found device
|
||||
* Return: 0 if OK, -ve on error
|
||||
*/
|
||||
static int device_find_by_ofnode(ofnode node, struct udevice **devp)
|
||||
{
|
||||
struct uclass *uc;
|
||||
struct udevice *dev;
|
||||
int ret;
|
||||
|
||||
list_for_each_entry(uc, &gd->uclass_root, sibling_node) {
|
||||
ret = uclass_find_device_by_ofnode(uc->uc_drv->id, node,
|
||||
&dev);
|
||||
if (!ret || dev) {
|
||||
*devp = dev;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif
|
||||
|
||||
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_get_child_count(struct udevice *parent)
|
||||
{
|
||||
struct udevice *dev;
|
||||
int count = 0;
|
||||
|
||||
list_for_each_entry(dev, &parent->child_head, sibling_node)
|
||||
count++;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
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(dev) == of_offset) {
|
||||
*devp = dev;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
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, node, &dev);
|
||||
return device_get_device_tail(dev, ret, devp);
|
||||
}
|
||||
|
||||
static struct udevice *_device_find_global_by_ofnode(struct udevice *parent,
|
||||
ofnode ofnode)
|
||||
{
|
||||
struct udevice *dev, *found;
|
||||
|
||||
if (ofnode_equal(dev_ofnode(parent), ofnode))
|
||||
return parent;
|
||||
|
||||
list_for_each_entry(dev, &parent->child_head, sibling_node) {
|
||||
found = _device_find_global_by_ofnode(dev, ofnode);
|
||||
if (found)
|
||||
return found;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int device_find_global_by_ofnode(ofnode ofnode, struct udevice **devp)
|
||||
{
|
||||
*devp = _device_find_global_by_ofnode(gd->dm_root, ofnode);
|
||||
|
||||
return *devp ? 0 : -ENOENT;
|
||||
}
|
||||
|
||||
int device_get_global_by_ofnode(ofnode ofnode, struct udevice **devp)
|
||||
{
|
||||
struct udevice *dev;
|
||||
|
||||
dev = _device_find_global_by_ofnode(gd->dm_root, ofnode);
|
||||
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)) {
|
||||
*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;
|
||||
}
|
||||
|
||||
int device_find_first_inactive_child(struct udevice *parent,
|
||||
enum uclass_id uclass_id,
|
||||
struct udevice **devp)
|
||||
{
|
||||
struct udevice *dev;
|
||||
|
||||
*devp = NULL;
|
||||
list_for_each_entry(dev, &parent->child_head, sibling_node) {
|
||||
if (!device_active(dev) &&
|
||||
device_get_uclass_id(dev) == uclass_id) {
|
||||
*devp = dev;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
int device_find_first_child_by_uclass(struct udevice *parent,
|
||||
enum uclass_id uclass_id,
|
||||
struct udevice **devp)
|
||||
{
|
||||
struct udevice *dev;
|
||||
|
||||
*devp = NULL;
|
||||
list_for_each_entry(dev, &parent->child_head, sibling_node) {
|
||||
if (device_get_uclass_id(dev) == uclass_id) {
|
||||
*devp = dev;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
int device_find_child_by_name(struct udevice *parent, const char *name,
|
||||
struct udevice **devp)
|
||||
{
|
||||
struct udevice *dev;
|
||||
|
||||
*devp = NULL;
|
||||
|
||||
list_for_each_entry(dev, &parent->child_head, sibling_node) {
|
||||
if (!strcmp(dev->name, name)) {
|
||||
*devp = dev;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
struct udevice *dev_get_parent(const struct udevice *child)
|
||||
{
|
||||
return child->parent;
|
||||
}
|
||||
|
||||
ulong dev_get_driver_data(const struct udevice *dev)
|
||||
{
|
||||
return dev->driver_data;
|
||||
}
|
||||
|
||||
const void *dev_get_driver_ops(const struct udevice *dev)
|
||||
{
|
||||
if (!dev || !dev->driver->ops)
|
||||
return NULL;
|
||||
|
||||
return dev->driver->ops;
|
||||
}
|
||||
|
||||
enum uclass_id device_get_uclass_id(const struct udevice *dev)
|
||||
{
|
||||
return dev->uclass->uc_drv->id;
|
||||
}
|
||||
|
||||
const char *dev_get_uclass_name(const struct udevice *dev)
|
||||
{
|
||||
if (!dev)
|
||||
return NULL;
|
||||
|
||||
return dev->uclass->uc_drv->name;
|
||||
}
|
||||
|
||||
bool device_has_children(const 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);
|
||||
}
|
||||
|
||||
void device_set_name_alloced(struct udevice *dev)
|
||||
{
|
||||
dev->flags |= DM_FLAG_NAME_ALLOCED;
|
||||
}
|
||||
|
||||
int device_set_name(struct udevice *dev, const char *name)
|
||||
{
|
||||
name = strdup(name);
|
||||
if (!name)
|
||||
return -ENOMEM;
|
||||
dev->name = name;
|
||||
device_set_name_alloced(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)
|
||||
bool device_is_compatible(struct udevice *dev, const char *compat)
|
||||
{
|
||||
return ofnode_device_is_compatible(dev_ofnode(dev), compat);
|
||||
}
|
||||
|
||||
bool of_machine_is_compatible(const char *compat)
|
||||
{
|
||||
const void *fdt = gd->fdt_blob;
|
||||
|
||||
return !fdt_node_check_compatible(fdt, 0, compat);
|
||||
}
|
||||
|
||||
int dev_disable_by_path(const char *path)
|
||||
{
|
||||
struct uclass *uc;
|
||||
ofnode node = ofnode_path(path);
|
||||
struct udevice *dev;
|
||||
int ret = 1;
|
||||
|
||||
if (!of_live_active())
|
||||
return -ENOSYS;
|
||||
|
||||
list_for_each_entry(uc, &gd->uclass_root, sibling_node) {
|
||||
ret = uclass_find_device_by_ofnode(uc->uc_drv->id, node, &dev);
|
||||
if (!ret)
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = device_remove(dev, DM_REMOVE_NORMAL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = device_unbind(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return ofnode_set_enabled(node, false);
|
||||
}
|
||||
|
||||
int dev_enable_by_path(const char *path)
|
||||
{
|
||||
ofnode node = ofnode_path(path);
|
||||
ofnode pnode = ofnode_get_parent(node);
|
||||
struct udevice *parent;
|
||||
int ret = 1;
|
||||
|
||||
if (!of_live_active())
|
||||
return -ENOSYS;
|
||||
|
||||
ret = device_find_by_ofnode(pnode, &parent);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = ofnode_set_enabled(node, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return lists_bind_fdt(parent, node, NULL, false);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,258 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* 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>
|
||||
*/
|
||||
|
||||
#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);
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (c) 2015 Google, Inc
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
#include <mapmem.h>
|
||||
#include <dm/root.h>
|
||||
#include <dm/util.h>
|
||||
#include <dm/uclass-internal.h>
|
||||
|
||||
static void show_devices(struct udevice *dev, int depth, int last_flag)
|
||||
{
|
||||
int i, is_last;
|
||||
struct udevice *child;
|
||||
|
||||
/* print the first 20 characters to not break the tree-format. */
|
||||
printf(" %-10.10s %3d [ %c ] %-20.20s ", dev->uclass->uc_drv->name,
|
||||
dev_get_uclass_index(dev, NULL),
|
||||
dev->flags & DM_FLAG_ACTIVATED ? '+' : ' ', dev->driver->name);
|
||||
|
||||
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 Index Probed Driver 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, int index)
|
||||
{
|
||||
printf("%-3i %c %s @ %08lx", index,
|
||||
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;
|
||||
int i = 0;
|
||||
|
||||
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;
|
||||
uclass_foreach_dev(dev, uc) {
|
||||
dm_display_line(dev, i);
|
||||
i++;
|
||||
}
|
||||
puts("\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Device addresses
|
||||
*
|
||||
* Copyright (c) 2017 Google, Inc
|
||||
*
|
||||
* (C) Copyright 2012
|
||||
* Pavel Herrmann <morpheus.ibis@gmail.com>
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
#include <fdt_support.h>
|
||||
#include <asm/io.h>
|
||||
#include <dm/device-internal.h>
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
||||
fdt_addr_t devfdt_get_addr_index(struct udevice *dev, int index)
|
||||
{
|
||||
#if CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)
|
||||
fdt_addr_t addr;
|
||||
|
||||
if (CONFIG_IS_ENABLED(OF_TRANSLATE)) {
|
||||
const fdt32_t *reg;
|
||||
int len = 0;
|
||||
int na, ns;
|
||||
|
||||
na = fdt_address_cells(gd->fdt_blob,
|
||||
dev_of_offset(dev->parent));
|
||||
if (na < 1) {
|
||||
debug("bad #address-cells\n");
|
||||
return FDT_ADDR_T_NONE;
|
||||
}
|
||||
|
||||
ns = fdt_size_cells(gd->fdt_blob, dev_of_offset(dev->parent));
|
||||
if (ns < 0) {
|
||||
debug("bad #size-cells\n");
|
||||
return FDT_ADDR_T_NONE;
|
||||
}
|
||||
|
||||
reg = fdt_getprop(gd->fdt_blob, dev_of_offset(dev), "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);
|
||||
|
||||
if (ns) {
|
||||
/*
|
||||
* Use the full-fledged translate function for complex
|
||||
* bus setups.
|
||||
*/
|
||||
addr = fdt_translate_address((void *)gd->fdt_blob,
|
||||
dev_of_offset(dev), reg);
|
||||
} else {
|
||||
/* Non translatable if #size-cells == 0 */
|
||||
addr = fdt_read_number(reg, na);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Use the "simple" translate function for less complex
|
||||
* bus setups.
|
||||
*/
|
||||
addr = fdtdec_get_addr_size_auto_parent(gd->fdt_blob,
|
||||
dev_of_offset(dev->parent), dev_of_offset(dev),
|
||||
"reg", index, NULL, false);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(CONFIG_TRANSLATION_OFFSET)
|
||||
/*
|
||||
* Some platforms need a special address translation. Those
|
||||
* platforms (e.g. mvebu in SPL) can configure a translation
|
||||
* offset by setting this value in the GD and enaling this
|
||||
* feature via CONFIG_TRANSLATION_OFFSET. This value will
|
||||
* get added to all addresses returned by devfdt_get_addr().
|
||||
*/
|
||||
addr += gd->translation_offset;
|
||||
#endif
|
||||
|
||||
return addr;
|
||||
#else
|
||||
return FDT_ADDR_T_NONE;
|
||||
#endif
|
||||
}
|
||||
|
||||
fdt_addr_t devfdt_get_addr_size_index(struct udevice *dev, int index,
|
||||
fdt_size_t *size)
|
||||
{
|
||||
#if CONFIG_IS_ENABLED(OF_CONTROL)
|
||||
/*
|
||||
* Only get the size in this first call. We'll get the addr in the
|
||||
* next call to the exisiting dev_get_xxx function which handles
|
||||
* all config options.
|
||||
*/
|
||||
fdtdec_get_addr_size_auto_noparent(gd->fdt_blob, dev_of_offset(dev),
|
||||
"reg", index, size, false);
|
||||
|
||||
/*
|
||||
* Get the base address via the existing function which handles
|
||||
* all Kconfig cases
|
||||
*/
|
||||
return devfdt_get_addr_index(dev, index);
|
||||
#else
|
||||
return FDT_ADDR_T_NONE;
|
||||
#endif
|
||||
}
|
||||
|
||||
fdt_addr_t devfdt_get_addr_name(struct udevice *dev, const char *name)
|
||||
{
|
||||
#if CONFIG_IS_ENABLED(OF_CONTROL)
|
||||
int index;
|
||||
|
||||
index = fdt_stringlist_search(gd->fdt_blob, dev_of_offset(dev),
|
||||
"reg-names", name);
|
||||
if (index < 0)
|
||||
return index;
|
||||
|
||||
return devfdt_get_addr_index(dev, index);
|
||||
#else
|
||||
return FDT_ADDR_T_NONE;
|
||||
#endif
|
||||
}
|
||||
|
||||
fdt_addr_t devfdt_get_addr_size_name(struct udevice *dev, const char *name,
|
||||
fdt_size_t *size)
|
||||
{
|
||||
#if CONFIG_IS_ENABLED(OF_CONTROL)
|
||||
int index;
|
||||
|
||||
index = fdt_stringlist_search(gd->fdt_blob, dev_of_offset(dev),
|
||||
"reg-names", name);
|
||||
if (index < 0)
|
||||
return index;
|
||||
|
||||
return devfdt_get_addr_size_index(dev, index, size);
|
||||
#else
|
||||
return FDT_ADDR_T_NONE;
|
||||
#endif
|
||||
}
|
||||
|
||||
fdt_addr_t devfdt_get_addr(struct udevice *dev)
|
||||
{
|
||||
return devfdt_get_addr_index(dev, 0);
|
||||
}
|
||||
|
||||
void *devfdt_get_addr_ptr(struct udevice *dev)
|
||||
{
|
||||
return (void *)(uintptr_t)devfdt_get_addr_index(dev, 0);
|
||||
}
|
||||
|
||||
void *devfdt_remap_addr_index(struct udevice *dev, int index)
|
||||
{
|
||||
fdt_addr_t addr = devfdt_get_addr_index(dev, index);
|
||||
|
||||
if (addr == FDT_ADDR_T_NONE)
|
||||
return NULL;
|
||||
|
||||
return map_physmem(addr, 0, MAP_NOCACHE);
|
||||
}
|
||||
|
||||
void *devfdt_remap_addr_name(struct udevice *dev, const char *name)
|
||||
{
|
||||
fdt_addr_t addr = devfdt_get_addr_name(dev, name);
|
||||
|
||||
if (addr == FDT_ADDR_T_NONE)
|
||||
return NULL;
|
||||
|
||||
return map_physmem(addr, 0, MAP_NOCACHE);
|
||||
}
|
||||
|
||||
void *devfdt_remap_addr(struct udevice *dev)
|
||||
{
|
||||
return devfdt_remap_addr_index(dev, 0);
|
||||
}
|
||||
|
||||
void *devfdt_map_physmem(struct udevice *dev, unsigned long size)
|
||||
{
|
||||
fdt_addr_t addr = devfdt_get_addr(dev);
|
||||
|
||||
if (addr == FDT_ADDR_T_NONE)
|
||||
return NULL;
|
||||
|
||||
return map_physmem(addr, size, MAP_NOCACHE);
|
||||
}
|
||||
|
||||
fdt_addr_t devfdt_get_addr_pci(struct udevice *dev)
|
||||
{
|
||||
ulong addr;
|
||||
|
||||
addr = devfdt_get_addr(dev);
|
||||
if (CONFIG_IS_ENABLED(PCI) && IS_ENABLED(CONFIG_DM_PCI) &&
|
||||
addr == FDT_ADDR_T_NONE) {
|
||||
struct fdt_pci_addr pci_addr;
|
||||
u32 bar;
|
||||
int ret;
|
||||
|
||||
ret = ofnode_read_pci_addr(dev_ofnode(dev), FDT_PCI_SPACE_MEM32,
|
||||
"reg", &pci_addr);
|
||||
if (ret) {
|
||||
/* try if there is any i/o-mapped register */
|
||||
ret = ofnode_read_pci_addr(dev_ofnode(dev),
|
||||
FDT_PCI_SPACE_IO, "reg",
|
||||
&pci_addr);
|
||||
if (ret)
|
||||
return FDT_ADDR_T_NONE;
|
||||
}
|
||||
ret = fdtdec_get_pci_bar32(dev, &pci_addr, &bar);
|
||||
if (ret)
|
||||
return FDT_ADDR_T_NONE;
|
||||
addr = bar;
|
||||
}
|
||||
|
||||
return addr;
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (c) 2013 Google, Inc
|
||||
*
|
||||
* (C) Copyright 2012
|
||||
* Marek Vasut <marex@denx.de>
|
||||
*/
|
||||
|
||||
#define LOG_CATEGORY LOGC_DM
|
||||
|
||||
#include <common.h>
|
||||
#include <errno.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 <fdtdec.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;
|
||||
|
||||
for (entry = drv; entry != drv + n_ents; entry++) {
|
||||
if (!strcmp(name, 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;
|
||||
|
||||
for (entry = uclass; entry != uclass + n_ents; entry++) {
|
||||
if (entry->id == id)
|
||||
return entry;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int lists_bind_drivers(struct udevice *parent, bool pre_reloc_only)
|
||||
{
|
||||
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 udevice *dev;
|
||||
int result = 0;
|
||||
int ret;
|
||||
|
||||
for (entry = info; entry != info + n_ents; entry++) {
|
||||
ret = device_bind_by_name(parent, pre_reloc_only, entry, &dev);
|
||||
if (ret && ret != -EPERM) {
|
||||
dm_warn("No match for driver '%s'\n", entry->name);
|
||||
if (!result || ret != -ENOENT)
|
||||
result = ret;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int device_bind_driver(struct udevice *parent, const char *drv_name,
|
||||
const char *dev_name, struct udevice **devp)
|
||||
{
|
||||
return device_bind_driver_to_node(parent, drv_name, dev_name,
|
||||
ofnode_null(), devp);
|
||||
}
|
||||
|
||||
int device_bind_driver_to_node(struct udevice *parent, const char *drv_name,
|
||||
const char *dev_name, ofnode node,
|
||||
struct udevice **devp)
|
||||
{
|
||||
struct driver *drv;
|
||||
int ret;
|
||||
|
||||
drv = lists_driver_lookup_name(drv_name);
|
||||
if (!drv) {
|
||||
debug("Cannot find driver '%s'\n", drv_name);
|
||||
return -ENOENT;
|
||||
}
|
||||
ret = device_bind_with_driver_data(parent, drv, dev_name, 0 /* data */,
|
||||
node, devp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)
|
||||
/**
|
||||
* driver_check_compatible() - Check if a driver matches a compatible string
|
||||
*
|
||||
* @param of_match: List of compatible strings to match
|
||||
* @param of_idp: Returns the match that was found
|
||||
* @param compat: The compatible string to search for
|
||||
* @return 0 if there is a match, -ENOENT if no match
|
||||
*/
|
||||
static int driver_check_compatible(const struct udevice_id *of_match,
|
||||
const struct udevice_id **of_idp,
|
||||
const char *compat)
|
||||
{
|
||||
if (!of_match)
|
||||
return -ENOENT;
|
||||
|
||||
while (of_match->compatible) {
|
||||
if (!strcmp(of_match->compatible, compat)) {
|
||||
*of_idp = of_match;
|
||||
return 0;
|
||||
}
|
||||
of_match++;
|
||||
}
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
int lists_bind_fdt(struct udevice *parent, ofnode node, struct udevice **devp,
|
||||
bool pre_reloc_only)
|
||||
{
|
||||
struct driver *driver = ll_entry_start(struct driver, driver);
|
||||
const int n_ents = ll_entry_count(struct driver, driver);
|
||||
const struct udevice_id *id;
|
||||
struct driver *entry;
|
||||
struct udevice *dev;
|
||||
bool found = false;
|
||||
const char *name, *compat_list, *compat;
|
||||
int compat_length, i;
|
||||
int result = 0;
|
||||
int ret = 0;
|
||||
|
||||
if (devp)
|
||||
*devp = NULL;
|
||||
name = ofnode_get_name(node);
|
||||
log_debug("bind node %s\n", name);
|
||||
|
||||
compat_list = ofnode_get_property(node, "compatible", &compat_length);
|
||||
if (!compat_list) {
|
||||
if (compat_length == -FDT_ERR_NOTFOUND) {
|
||||
log_debug("Device '%s' has no compatible string\n",
|
||||
name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
dm_warn("Device tree error at node '%s'\n", name);
|
||||
return compat_length;
|
||||
}
|
||||
|
||||
/*
|
||||
* Walk through the compatible string list, attempting to match each
|
||||
* compatible string in order such that we match in order of priority
|
||||
* from the first string to the last.
|
||||
*/
|
||||
for (i = 0; i < compat_length; i += strlen(compat) + 1) {
|
||||
compat = compat_list + i;
|
||||
log_debug(" - attempt to match compatible string '%s'\n",
|
||||
compat);
|
||||
|
||||
for (entry = driver; entry != driver + n_ents; entry++) {
|
||||
ret = driver_check_compatible(entry->of_match, &id,
|
||||
compat);
|
||||
if (!ret)
|
||||
break;
|
||||
}
|
||||
if (entry == driver + n_ents)
|
||||
continue;
|
||||
|
||||
if (pre_reloc_only) {
|
||||
if (!dm_ofnode_pre_reloc(node) &&
|
||||
!(entry->flags & DM_FLAG_PRE_RELOC))
|
||||
return 0;
|
||||
}
|
||||
|
||||
log_debug(" - found match at '%s': '%s' matches '%s'\n",
|
||||
entry->name, entry->of_match->compatible,
|
||||
id->compatible);
|
||||
ret = device_bind_with_driver_data(parent, entry, name,
|
||||
id->data, node, &dev);
|
||||
if (ret == -ENODEV) {
|
||||
log_debug("Driver '%s' refuses to bind\n", entry->name);
|
||||
continue;
|
||||
}
|
||||
if (ret) {
|
||||
dm_warn("Error binding driver '%s': %d\n", entry->name,
|
||||
ret);
|
||||
return ret;
|
||||
} else {
|
||||
found = true;
|
||||
if (devp)
|
||||
*devp = dev;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found && !result && ret != -ENODEV)
|
||||
log_debug("No match for node '%s'\n", name);
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,836 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Originally from Linux v4.9
|
||||
* Paul Mackerras August 1996.
|
||||
* Copyright (C) 1996-2005 Paul Mackerras.
|
||||
*
|
||||
* Adapted for 64bit PowerPC by Dave Engebretsen and Peter Bergner.
|
||||
* {engebret|bergner}@us.ibm.com
|
||||
*
|
||||
* Adapted for sparc and sparc64 by David S. Miller davem@davemloft.net
|
||||
*
|
||||
* Reconsolidated from arch/x/kernel/prom.c by Stephen Rothwell and
|
||||
* Grant Likely.
|
||||
*
|
||||
* Modified for U-Boot
|
||||
* Copyright (c) 2017 Google, Inc
|
||||
*
|
||||
* This file follows drivers/of/base.c with functions in the same order as the
|
||||
* Linux version.
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <linux/libfdt.h>
|
||||
#include <dm/of_access.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/ioport.h>
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
||||
/* list of struct alias_prop aliases */
|
||||
LIST_HEAD(aliases_lookup);
|
||||
|
||||
/* "/aliaes" node */
|
||||
static struct device_node *of_aliases;
|
||||
|
||||
/* "/chosen" node */
|
||||
static struct device_node *of_chosen;
|
||||
|
||||
/* node pointed to by the stdout-path alias */
|
||||
static struct device_node *of_stdout;
|
||||
|
||||
/* pointer to options given after the alias (separated by :) or NULL if none */
|
||||
static const char *of_stdout_options;
|
||||
|
||||
/**
|
||||
* struct alias_prop - Alias property in 'aliases' node
|
||||
*
|
||||
* The structure represents one alias property of 'aliases' node as
|
||||
* an entry in aliases_lookup list.
|
||||
*
|
||||
* @link: List node to link the structure in aliases_lookup list
|
||||
* @alias: Alias property name
|
||||
* @np: Pointer to device_node that the alias stands for
|
||||
* @id: Index value from end of alias name
|
||||
* @stem: Alias string without the index
|
||||
*/
|
||||
struct alias_prop {
|
||||
struct list_head link;
|
||||
const char *alias;
|
||||
struct device_node *np;
|
||||
int id;
|
||||
char stem[0];
|
||||
};
|
||||
|
||||
int of_n_addr_cells(const struct device_node *np)
|
||||
{
|
||||
const __be32 *ip;
|
||||
|
||||
do {
|
||||
if (np->parent)
|
||||
np = np->parent;
|
||||
ip = of_get_property(np, "#address-cells", NULL);
|
||||
if (ip)
|
||||
return be32_to_cpup(ip);
|
||||
} while (np->parent);
|
||||
|
||||
/* No #address-cells property for the root node */
|
||||
return OF_ROOT_NODE_ADDR_CELLS_DEFAULT;
|
||||
}
|
||||
|
||||
int of_n_size_cells(const struct device_node *np)
|
||||
{
|
||||
const __be32 *ip;
|
||||
|
||||
do {
|
||||
if (np->parent)
|
||||
np = np->parent;
|
||||
ip = of_get_property(np, "#size-cells", NULL);
|
||||
if (ip)
|
||||
return be32_to_cpup(ip);
|
||||
} while (np->parent);
|
||||
|
||||
/* No #size-cells property for the root node */
|
||||
return OF_ROOT_NODE_SIZE_CELLS_DEFAULT;
|
||||
}
|
||||
|
||||
int of_simple_addr_cells(const struct device_node *np)
|
||||
{
|
||||
const __be32 *ip;
|
||||
|
||||
ip = of_get_property(np, "#address-cells", NULL);
|
||||
if (ip)
|
||||
return be32_to_cpup(ip);
|
||||
|
||||
/* Return a default of 2 to match fdt_address_cells()*/
|
||||
return 2;
|
||||
}
|
||||
|
||||
int of_simple_size_cells(const struct device_node *np)
|
||||
{
|
||||
const __be32 *ip;
|
||||
|
||||
ip = of_get_property(np, "#size-cells", NULL);
|
||||
if (ip)
|
||||
return be32_to_cpup(ip);
|
||||
|
||||
/* Return a default of 2 to match fdt_size_cells()*/
|
||||
return 2;
|
||||
}
|
||||
|
||||
struct property *of_find_property(const struct device_node *np,
|
||||
const char *name, int *lenp)
|
||||
{
|
||||
struct property *pp;
|
||||
|
||||
if (!np)
|
||||
return NULL;
|
||||
|
||||
for (pp = np->properties; pp; pp = pp->next) {
|
||||
if (strcmp(pp->name, name) == 0) {
|
||||
if (lenp)
|
||||
*lenp = pp->length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!pp && lenp)
|
||||
*lenp = -FDT_ERR_NOTFOUND;
|
||||
|
||||
return pp;
|
||||
}
|
||||
|
||||
struct device_node *of_find_all_nodes(struct device_node *prev)
|
||||
{
|
||||
struct device_node *np;
|
||||
|
||||
if (!prev) {
|
||||
np = gd->of_root;
|
||||
} else if (prev->child) {
|
||||
np = prev->child;
|
||||
} else {
|
||||
/*
|
||||
* Walk back up looking for a sibling, or the end of the
|
||||
* structure
|
||||
*/
|
||||
np = prev;
|
||||
while (np->parent && !np->sibling)
|
||||
np = np->parent;
|
||||
np = np->sibling; /* Might be null at the end of the tree */
|
||||
}
|
||||
|
||||
return np;
|
||||
}
|
||||
|
||||
const void *of_get_property(const struct device_node *np, const char *name,
|
||||
int *lenp)
|
||||
{
|
||||
struct property *pp = of_find_property(np, name, lenp);
|
||||
|
||||
return pp ? pp->value : NULL;
|
||||
}
|
||||
|
||||
static const char *of_prop_next_string(struct property *prop, const char *cur)
|
||||
{
|
||||
const void *curv = cur;
|
||||
|
||||
if (!prop)
|
||||
return NULL;
|
||||
|
||||
if (!cur)
|
||||
return prop->value;
|
||||
|
||||
curv += strlen(cur) + 1;
|
||||
if (curv >= prop->value + prop->length)
|
||||
return NULL;
|
||||
|
||||
return curv;
|
||||
}
|
||||
|
||||
int of_device_is_compatible(const struct device_node *device,
|
||||
const char *compat, const char *type,
|
||||
const char *name)
|
||||
{
|
||||
struct property *prop;
|
||||
const char *cp;
|
||||
int index = 0, score = 0;
|
||||
|
||||
/* Compatible match has highest priority */
|
||||
if (compat && compat[0]) {
|
||||
prop = of_find_property(device, "compatible", NULL);
|
||||
for (cp = of_prop_next_string(prop, NULL); cp;
|
||||
cp = of_prop_next_string(prop, cp), index++) {
|
||||
if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
|
||||
score = INT_MAX/2 - (index << 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!score)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Matching type is better than matching name */
|
||||
if (type && type[0]) {
|
||||
if (!device->type || of_node_cmp(type, device->type))
|
||||
return 0;
|
||||
score += 2;
|
||||
}
|
||||
|
||||
/* Matching name is a bit better than not */
|
||||
if (name && name[0]) {
|
||||
if (!device->name || of_node_cmp(name, device->name))
|
||||
return 0;
|
||||
score++;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
bool of_device_is_available(const struct device_node *device)
|
||||
{
|
||||
const char *status;
|
||||
int statlen;
|
||||
|
||||
if (!device)
|
||||
return false;
|
||||
|
||||
status = of_get_property(device, "status", &statlen);
|
||||
if (status == NULL)
|
||||
return true;
|
||||
|
||||
if (statlen > 0) {
|
||||
if (!strcmp(status, "okay"))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
struct device_node *of_get_parent(const struct device_node *node)
|
||||
{
|
||||
const struct device_node *np;
|
||||
|
||||
if (!node)
|
||||
return NULL;
|
||||
|
||||
np = of_node_get(node->parent);
|
||||
|
||||
return (struct device_node *)np;
|
||||
}
|
||||
|
||||
static struct device_node *__of_get_next_child(const struct device_node *node,
|
||||
struct device_node *prev)
|
||||
{
|
||||
struct device_node *next;
|
||||
|
||||
if (!node)
|
||||
return NULL;
|
||||
|
||||
next = prev ? prev->sibling : node->child;
|
||||
/*
|
||||
* coverity[dead_error_line : FALSE]
|
||||
* Dead code here since our current implementation of of_node_get()
|
||||
* always returns NULL (Coverity CID 163245). But we leave it as is
|
||||
* since we may want to implement get/put later.
|
||||
*/
|
||||
for (; next; next = next->sibling)
|
||||
if (of_node_get(next))
|
||||
break;
|
||||
of_node_put(prev);
|
||||
return next;
|
||||
}
|
||||
|
||||
#define __for_each_child_of_node(parent, child) \
|
||||
for (child = __of_get_next_child(parent, NULL); child != NULL; \
|
||||
child = __of_get_next_child(parent, child))
|
||||
|
||||
static struct device_node *__of_find_node_by_path(struct device_node *parent,
|
||||
const char *path)
|
||||
{
|
||||
struct device_node *child;
|
||||
int len;
|
||||
|
||||
len = strcspn(path, "/:");
|
||||
if (!len)
|
||||
return NULL;
|
||||
|
||||
__for_each_child_of_node(parent, child) {
|
||||
const char *name = strrchr(child->full_name, '/');
|
||||
|
||||
name++;
|
||||
if (strncmp(path, name, len) == 0 && (strlen(name) == len))
|
||||
return child;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define for_each_property_of_node(dn, pp) \
|
||||
for (pp = dn->properties; pp != NULL; pp = pp->next)
|
||||
|
||||
struct device_node *of_find_node_opts_by_path(const char *path,
|
||||
const char **opts)
|
||||
{
|
||||
struct device_node *np = NULL;
|
||||
struct property *pp;
|
||||
const char *separator = strchr(path, ':');
|
||||
|
||||
if (opts)
|
||||
*opts = separator ? separator + 1 : NULL;
|
||||
|
||||
if (strcmp(path, "/") == 0)
|
||||
return of_node_get(gd->of_root);
|
||||
|
||||
/* The path could begin with an alias */
|
||||
if (*path != '/') {
|
||||
int len;
|
||||
const char *p = separator;
|
||||
|
||||
if (!p)
|
||||
p = strchrnul(path, '/');
|
||||
len = p - path;
|
||||
|
||||
/* of_aliases must not be NULL */
|
||||
if (!of_aliases)
|
||||
return NULL;
|
||||
|
||||
for_each_property_of_node(of_aliases, pp) {
|
||||
if (strlen(pp->name) == len && !strncmp(pp->name, path,
|
||||
len)) {
|
||||
np = of_find_node_by_path(pp->value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!np)
|
||||
return NULL;
|
||||
path = p;
|
||||
}
|
||||
|
||||
/* Step down the tree matching path components */
|
||||
if (!np)
|
||||
np = of_node_get(gd->of_root);
|
||||
while (np && *path == '/') {
|
||||
struct device_node *tmp = np;
|
||||
|
||||
path++; /* Increment past '/' delimiter */
|
||||
np = __of_find_node_by_path(np, path);
|
||||
of_node_put(tmp);
|
||||
path = strchrnul(path, '/');
|
||||
if (separator && separator < path)
|
||||
break;
|
||||
}
|
||||
|
||||
return np;
|
||||
}
|
||||
|
||||
struct device_node *of_find_compatible_node(struct device_node *from,
|
||||
const char *type, const char *compatible)
|
||||
{
|
||||
struct device_node *np;
|
||||
|
||||
for_each_of_allnodes_from(from, np)
|
||||
if (of_device_is_compatible(np, compatible, type, NULL) &&
|
||||
of_node_get(np))
|
||||
break;
|
||||
of_node_put(from);
|
||||
|
||||
return np;
|
||||
}
|
||||
|
||||
static int of_device_has_prop_value(const struct device_node *device,
|
||||
const char *propname, const void *propval,
|
||||
int proplen)
|
||||
{
|
||||
struct property *prop = of_find_property(device, propname, NULL);
|
||||
|
||||
if (!prop || !prop->value || prop->length != proplen)
|
||||
return 0;
|
||||
return !memcmp(prop->value, propval, proplen);
|
||||
}
|
||||
|
||||
struct device_node *of_find_node_by_prop_value(struct device_node *from,
|
||||
const char *propname,
|
||||
const void *propval, int proplen)
|
||||
{
|
||||
struct device_node *np;
|
||||
|
||||
for_each_of_allnodes_from(from, np) {
|
||||
if (of_device_has_prop_value(np, propname, propval, proplen) &&
|
||||
of_node_get(np))
|
||||
break;
|
||||
}
|
||||
of_node_put(from);
|
||||
|
||||
return np;
|
||||
}
|
||||
|
||||
struct device_node *of_find_node_by_phandle(phandle handle)
|
||||
{
|
||||
struct device_node *np;
|
||||
|
||||
if (!handle)
|
||||
return NULL;
|
||||
|
||||
for_each_of_allnodes(np)
|
||||
if (np->phandle == handle)
|
||||
break;
|
||||
(void)of_node_get(np);
|
||||
|
||||
return np;
|
||||
}
|
||||
|
||||
/**
|
||||
* of_find_property_value_of_size() - find property of given size
|
||||
*
|
||||
* Search for a property in a device node and validate the requested size.
|
||||
*
|
||||
* @np: device node from which the property value is to be read.
|
||||
* @propname: name of the property to be searched.
|
||||
* @len: requested length of property value
|
||||
*
|
||||
* @return the property value on success, -EINVAL if the property does not
|
||||
* exist, -ENODATA if property does not have a value, and -EOVERFLOW if the
|
||||
* property data isn't large enough.
|
||||
*/
|
||||
static void *of_find_property_value_of_size(const struct device_node *np,
|
||||
const char *propname, u32 len)
|
||||
{
|
||||
struct property *prop = of_find_property(np, propname, NULL);
|
||||
|
||||
if (!prop)
|
||||
return ERR_PTR(-EINVAL);
|
||||
if (!prop->value)
|
||||
return ERR_PTR(-ENODATA);
|
||||
if (len > prop->length)
|
||||
return ERR_PTR(-EOVERFLOW);
|
||||
|
||||
return prop->value;
|
||||
}
|
||||
|
||||
int of_read_u32(const struct device_node *np, const char *propname, u32 *outp)
|
||||
{
|
||||
const __be32 *val;
|
||||
|
||||
debug("%s: %s: ", __func__, propname);
|
||||
if (!np)
|
||||
return -EINVAL;
|
||||
val = of_find_property_value_of_size(np, propname, sizeof(*outp));
|
||||
if (IS_ERR(val)) {
|
||||
debug("(not found)\n");
|
||||
return PTR_ERR(val);
|
||||
}
|
||||
|
||||
*outp = be32_to_cpup(val);
|
||||
debug("%#x (%d)\n", *outp, *outp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int of_read_u32_array(const struct device_node *np, const char *propname,
|
||||
u32 *out_values, size_t sz)
|
||||
{
|
||||
const __be32 *val;
|
||||
|
||||
debug("%s: %s: ", __func__, propname);
|
||||
val = of_find_property_value_of_size(np, propname,
|
||||
sz * sizeof(*out_values));
|
||||
|
||||
if (IS_ERR(val))
|
||||
return PTR_ERR(val);
|
||||
|
||||
debug("size %zd\n", sz);
|
||||
while (sz--)
|
||||
*out_values++ = be32_to_cpup(val++);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int of_read_u64(const struct device_node *np, const char *propname, u64 *outp)
|
||||
{
|
||||
const __be64 *val;
|
||||
|
||||
debug("%s: %s: ", __func__, propname);
|
||||
if (!np)
|
||||
return -EINVAL;
|
||||
val = of_find_property_value_of_size(np, propname, sizeof(*outp));
|
||||
if (IS_ERR(val)) {
|
||||
debug("(not found)\n");
|
||||
return PTR_ERR(val);
|
||||
}
|
||||
|
||||
*outp = be64_to_cpup(val);
|
||||
debug("%#llx (%lld)\n", (unsigned long long)*outp,
|
||||
(unsigned long long)*outp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int of_property_match_string(const struct device_node *np, const char *propname,
|
||||
const char *string)
|
||||
{
|
||||
const struct property *prop = of_find_property(np, propname, NULL);
|
||||
size_t l;
|
||||
int i;
|
||||
const char *p, *end;
|
||||
|
||||
if (!prop)
|
||||
return -EINVAL;
|
||||
if (!prop->value)
|
||||
return -ENODATA;
|
||||
|
||||
p = prop->value;
|
||||
end = p + prop->length;
|
||||
|
||||
for (i = 0; p < end; i++, p += l) {
|
||||
l = strnlen(p, end - p) + 1;
|
||||
if (p + l > end)
|
||||
return -EILSEQ;
|
||||
debug("comparing %s with %s\n", string, p);
|
||||
if (strcmp(string, p) == 0)
|
||||
return i; /* Found it; return index */
|
||||
}
|
||||
return -ENODATA;
|
||||
}
|
||||
|
||||
/**
|
||||
* of_property_read_string_helper() - Utility helper for parsing string properties
|
||||
* @np: device node from which the property value is to be read.
|
||||
* @propname: name of the property to be searched.
|
||||
* @out_strs: output array of string pointers.
|
||||
* @sz: number of array elements to read.
|
||||
* @skip: Number of strings to skip over at beginning of list.
|
||||
*
|
||||
* Don't call this function directly. It is a utility helper for the
|
||||
* of_property_read_string*() family of functions.
|
||||
*/
|
||||
int of_property_read_string_helper(const struct device_node *np,
|
||||
const char *propname, const char **out_strs,
|
||||
size_t sz, int skip)
|
||||
{
|
||||
const struct property *prop = of_find_property(np, propname, NULL);
|
||||
int l = 0, i = 0;
|
||||
const char *p, *end;
|
||||
|
||||
if (!prop)
|
||||
return -EINVAL;
|
||||
if (!prop->value)
|
||||
return -ENODATA;
|
||||
p = prop->value;
|
||||
end = p + prop->length;
|
||||
|
||||
for (i = 0; p < end && (!out_strs || i < skip + sz); i++, p += l) {
|
||||
l = strnlen(p, end - p) + 1;
|
||||
if (p + l > end)
|
||||
return -EILSEQ;
|
||||
if (out_strs && i >= skip)
|
||||
*out_strs++ = p;
|
||||
}
|
||||
i -= skip;
|
||||
return i <= 0 ? -ENODATA : i;
|
||||
}
|
||||
|
||||
static int __of_parse_phandle_with_args(const struct device_node *np,
|
||||
const char *list_name,
|
||||
const char *cells_name,
|
||||
int cell_count, int index,
|
||||
struct of_phandle_args *out_args)
|
||||
{
|
||||
const __be32 *list, *list_end;
|
||||
int rc = 0, cur_index = 0;
|
||||
uint32_t count = 0;
|
||||
struct device_node *node = NULL;
|
||||
phandle phandle;
|
||||
int size;
|
||||
|
||||
/* Retrieve the phandle list property */
|
||||
list = of_get_property(np, list_name, &size);
|
||||
if (!list)
|
||||
return -ENOENT;
|
||||
list_end = list + size / sizeof(*list);
|
||||
|
||||
/* Loop over the phandles until all the requested entry is found */
|
||||
while (list < list_end) {
|
||||
rc = -EINVAL;
|
||||
count = 0;
|
||||
|
||||
/*
|
||||
* If phandle is 0, then it is an empty entry with no
|
||||
* arguments. Skip forward to the next entry.
|
||||
*/
|
||||
phandle = be32_to_cpup(list++);
|
||||
if (phandle) {
|
||||
/*
|
||||
* Find the provider node and parse the #*-cells
|
||||
* property to determine the argument length.
|
||||
*
|
||||
* This is not needed if the cell count is hard-coded
|
||||
* (i.e. cells_name not set, but cell_count is set),
|
||||
* except when we're going to return the found node
|
||||
* below.
|
||||
*/
|
||||
if (cells_name || cur_index == index) {
|
||||
node = of_find_node_by_phandle(phandle);
|
||||
if (!node) {
|
||||
debug("%s: could not find phandle\n",
|
||||
np->full_name);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (cells_name) {
|
||||
if (of_read_u32(node, cells_name, &count)) {
|
||||
debug("%s: could not get %s for %s\n",
|
||||
np->full_name, cells_name,
|
||||
node->full_name);
|
||||
goto err;
|
||||
}
|
||||
} else {
|
||||
count = cell_count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure that the arguments actually fit in the
|
||||
* remaining property data length
|
||||
*/
|
||||
if (list + count > list_end) {
|
||||
debug("%s: arguments longer than property\n",
|
||||
np->full_name);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* All of the error cases above bail out of the loop, so at
|
||||
* this point, the parsing is successful. If the requested
|
||||
* index matches, then fill the out_args structure and return,
|
||||
* or return -ENOENT for an empty entry.
|
||||
*/
|
||||
rc = -ENOENT;
|
||||
if (cur_index == index) {
|
||||
if (!phandle)
|
||||
goto err;
|
||||
|
||||
if (out_args) {
|
||||
int i;
|
||||
if (WARN_ON(count > OF_MAX_PHANDLE_ARGS))
|
||||
count = OF_MAX_PHANDLE_ARGS;
|
||||
out_args->np = node;
|
||||
out_args->args_count = count;
|
||||
for (i = 0; i < count; i++)
|
||||
out_args->args[i] =
|
||||
be32_to_cpup(list++);
|
||||
} else {
|
||||
of_node_put(node);
|
||||
}
|
||||
|
||||
/* Found it! return success */
|
||||
return 0;
|
||||
}
|
||||
|
||||
of_node_put(node);
|
||||
node = NULL;
|
||||
list += count;
|
||||
cur_index++;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unlock node before returning result; will be one of:
|
||||
* -ENOENT : index is for empty phandle
|
||||
* -EINVAL : parsing error on data
|
||||
* [1..n] : Number of phandle (count mode; when index = -1)
|
||||
*/
|
||||
rc = index < 0 ? cur_index : -ENOENT;
|
||||
err:
|
||||
if (node)
|
||||
of_node_put(node);
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct device_node *of_parse_phandle(const struct device_node *np,
|
||||
const char *phandle_name, int index)
|
||||
{
|
||||
struct of_phandle_args args;
|
||||
|
||||
if (index < 0)
|
||||
return NULL;
|
||||
|
||||
if (__of_parse_phandle_with_args(np, phandle_name, NULL, 0, index,
|
||||
&args))
|
||||
return NULL;
|
||||
|
||||
return args.np;
|
||||
}
|
||||
|
||||
int of_parse_phandle_with_args(const struct device_node *np,
|
||||
const char *list_name, const char *cells_name,
|
||||
int index, struct of_phandle_args *out_args)
|
||||
{
|
||||
if (index < 0)
|
||||
return -EINVAL;
|
||||
|
||||
return __of_parse_phandle_with_args(np, list_name, cells_name, 0,
|
||||
index, out_args);
|
||||
}
|
||||
|
||||
int of_count_phandle_with_args(const struct device_node *np,
|
||||
const char *list_name, const char *cells_name)
|
||||
{
|
||||
return __of_parse_phandle_with_args(np, list_name, cells_name, 0,
|
||||
-1, NULL);
|
||||
}
|
||||
|
||||
static void of_alias_add(struct alias_prop *ap, struct device_node *np,
|
||||
int id, const char *stem, int stem_len)
|
||||
{
|
||||
ap->np = np;
|
||||
ap->id = id;
|
||||
strncpy(ap->stem, stem, stem_len);
|
||||
ap->stem[stem_len] = 0;
|
||||
list_add_tail(&ap->link, &aliases_lookup);
|
||||
debug("adding DT alias:%s: stem=%s id=%i node=%s\n",
|
||||
ap->alias, ap->stem, ap->id, of_node_full_name(np));
|
||||
}
|
||||
|
||||
int of_alias_scan(void)
|
||||
{
|
||||
struct property *pp;
|
||||
|
||||
of_aliases = of_find_node_by_path("/aliases");
|
||||
of_chosen = of_find_node_by_path("/chosen");
|
||||
if (of_chosen == NULL)
|
||||
of_chosen = of_find_node_by_path("/chosen@0");
|
||||
|
||||
if (of_chosen) {
|
||||
const char *name;
|
||||
|
||||
name = of_get_property(of_chosen, "stdout-path", NULL);
|
||||
if (name)
|
||||
of_stdout = of_find_node_opts_by_path(name,
|
||||
&of_stdout_options);
|
||||
}
|
||||
|
||||
if (!of_aliases)
|
||||
return 0;
|
||||
|
||||
for_each_property_of_node(of_aliases, pp) {
|
||||
const char *start = pp->name;
|
||||
const char *end = start + strlen(start);
|
||||
struct device_node *np;
|
||||
struct alias_prop *ap;
|
||||
ulong id;
|
||||
int len;
|
||||
|
||||
/* Skip those we do not want to proceed */
|
||||
if (!strcmp(pp->name, "name") ||
|
||||
!strcmp(pp->name, "phandle") ||
|
||||
!strcmp(pp->name, "linux,phandle"))
|
||||
continue;
|
||||
|
||||
np = of_find_node_by_path(pp->value);
|
||||
if (!np)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* walk the alias backwards to extract the id and work out
|
||||
* the 'stem' string
|
||||
*/
|
||||
while (isdigit(*(end-1)) && end > start)
|
||||
end--;
|
||||
len = end - start;
|
||||
|
||||
if (strict_strtoul(end, 10, &id) < 0)
|
||||
continue;
|
||||
|
||||
/* Allocate an alias_prop with enough space for the stem */
|
||||
ap = malloc(sizeof(*ap) + len + 1);
|
||||
if (!ap)
|
||||
return -ENOMEM;
|
||||
memset(ap, 0, sizeof(*ap) + len + 1);
|
||||
ap->alias = start;
|
||||
of_alias_add(ap, np, id, start, len);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int of_alias_get_id(const struct device_node *np, const char *stem)
|
||||
{
|
||||
struct alias_prop *app;
|
||||
int id = -ENODEV;
|
||||
|
||||
mutex_lock(&of_mutex);
|
||||
list_for_each_entry(app, &aliases_lookup, link) {
|
||||
if (strcmp(app->stem, stem) != 0)
|
||||
continue;
|
||||
|
||||
if (np == app->np) {
|
||||
id = app->id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&of_mutex);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
int of_alias_get_highest_id(const char *stem)
|
||||
{
|
||||
struct alias_prop *app;
|
||||
int id = -1;
|
||||
|
||||
mutex_lock(&of_mutex);
|
||||
list_for_each_entry(app, &aliases_lookup, link) {
|
||||
if (strcmp(app->stem, stem) != 0)
|
||||
continue;
|
||||
|
||||
if (app->id > id)
|
||||
id = app->id;
|
||||
}
|
||||
mutex_unlock(&of_mutex);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
struct device_node *of_get_stdout(void)
|
||||
{
|
||||
return of_stdout;
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Taken from Linux v4.9 drivers/of/address.c
|
||||
*
|
||||
* Modified for U-Boot
|
||||
* Copyright (c) 2017 Google, Inc
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <linux/libfdt.h>
|
||||
#include <dm/of_access.h>
|
||||
#include <dm/of_addr.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/ioport.h>
|
||||
|
||||
/* Max address size we deal with */
|
||||
#define OF_MAX_ADDR_CELLS 4
|
||||
#define OF_CHECK_ADDR_COUNT(na) ((na) > 0 && (na) <= OF_MAX_ADDR_CELLS)
|
||||
#define OF_CHECK_COUNTS(na, ns) (OF_CHECK_ADDR_COUNT(na) && (ns) > 0)
|
||||
|
||||
static struct of_bus *of_match_bus(struct device_node *np);
|
||||
|
||||
/* Debug utility */
|
||||
#ifdef DEBUG
|
||||
static void of_dump_addr(const char *s, const __be32 *addr, int na)
|
||||
{
|
||||
debug("%s", s);
|
||||
while (na--)
|
||||
pr_cont(" %08x", be32_to_cpu(*(addr++)));
|
||||
pr_cont("\n");
|
||||
}
|
||||
#else
|
||||
static void of_dump_addr(const char *s, const __be32 *addr, int na) { }
|
||||
#endif
|
||||
|
||||
/* Callbacks for bus specific translators */
|
||||
struct of_bus {
|
||||
const char *name;
|
||||
const char *addresses;
|
||||
int (*match)(struct device_node *parent);
|
||||
void (*count_cells)(const struct device_node *child, int *addrc,
|
||||
int *sizec);
|
||||
u64 (*map)(__be32 *addr, const __be32 *range, int na, int ns, int pna);
|
||||
int (*translate)(__be32 *addr, u64 offset, int na);
|
||||
unsigned int (*get_flags)(const __be32 *addr);
|
||||
};
|
||||
|
||||
static void of_bus_default_count_cells(const struct device_node *np,
|
||||
int *addrc, int *sizec)
|
||||
{
|
||||
if (addrc)
|
||||
*addrc = of_n_addr_cells(np);
|
||||
if (sizec)
|
||||
*sizec = of_n_size_cells(np);
|
||||
}
|
||||
|
||||
static u64 of_bus_default_map(__be32 *addr, const __be32 *range,
|
||||
int na, int ns, int pna)
|
||||
{
|
||||
u64 cp, s, da;
|
||||
|
||||
cp = of_read_number(range, na);
|
||||
s = of_read_number(range + na + pna, ns);
|
||||
da = of_read_number(addr, na);
|
||||
|
||||
debug("default map, cp=%llx, s=%llx, da=%llx\n",
|
||||
(unsigned long long)cp, (unsigned long long)s,
|
||||
(unsigned long long)da);
|
||||
|
||||
if (da < cp || da >= (cp + s))
|
||||
return OF_BAD_ADDR;
|
||||
return da - cp;
|
||||
}
|
||||
|
||||
static int of_bus_default_translate(__be32 *addr, u64 offset, int na)
|
||||
{
|
||||
u64 a = of_read_number(addr, na);
|
||||
memset(addr, 0, na * 4);
|
||||
a += offset;
|
||||
if (na > 1)
|
||||
addr[na - 2] = cpu_to_be32(a >> 32);
|
||||
addr[na - 1] = cpu_to_be32(a & 0xffffffffu);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int of_bus_default_get_flags(const __be32 *addr)
|
||||
{
|
||||
return IORESOURCE_MEM;
|
||||
}
|
||||
|
||||
/*
|
||||
* Array of bus-specific translators
|
||||
*/
|
||||
static struct of_bus of_busses[] = {
|
||||
/* Default */
|
||||
{
|
||||
.name = "default",
|
||||
.addresses = "reg",
|
||||
.match = NULL,
|
||||
.count_cells = of_bus_default_count_cells,
|
||||
.map = of_bus_default_map,
|
||||
.translate = of_bus_default_translate,
|
||||
.get_flags = of_bus_default_get_flags,
|
||||
},
|
||||
};
|
||||
|
||||
static struct of_bus *of_match_bus(struct device_node *np)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(of_busses); i++)
|
||||
if (!of_busses[i].match || of_busses[i].match(np))
|
||||
return &of_busses[i];
|
||||
BUG();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void dev_count_cells(const struct device_node *np, int *nap, int *nsp)
|
||||
{
|
||||
of_bus_default_count_cells(np, nap, nsp);
|
||||
}
|
||||
|
||||
const __be32 *of_get_address(const struct device_node *dev, int index,
|
||||
u64 *size, unsigned int *flags)
|
||||
{
|
||||
const __be32 *prop;
|
||||
int psize;
|
||||
struct device_node *parent;
|
||||
struct of_bus *bus;
|
||||
int onesize, i, na, ns;
|
||||
|
||||
/* Get parent & match bus type */
|
||||
parent = of_get_parent(dev);
|
||||
if (parent == NULL)
|
||||
return NULL;
|
||||
dev_count_cells(dev, &na, &ns);
|
||||
bus = of_match_bus(parent);
|
||||
bus->count_cells(dev, &na, &ns);
|
||||
of_node_put(parent);
|
||||
if (!OF_CHECK_ADDR_COUNT(na))
|
||||
return NULL;
|
||||
|
||||
/* Get "reg" or "assigned-addresses" property */
|
||||
prop = of_get_property(dev, "reg", &psize);
|
||||
if (prop == NULL)
|
||||
return NULL;
|
||||
psize /= 4;
|
||||
|
||||
onesize = na + ns;
|
||||
for (i = 0; psize >= onesize; psize -= onesize, prop += onesize, i++)
|
||||
if (i == index) {
|
||||
if (size)
|
||||
*size = of_read_number(prop + na, ns);
|
||||
if (flags)
|
||||
*flags = bus->get_flags(prop);
|
||||
return prop;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(of_get_address);
|
||||
|
||||
static int of_empty_ranges_quirk(const struct device_node *np)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static int of_translate_one(const struct device_node *parent,
|
||||
struct of_bus *bus, struct of_bus *pbus,
|
||||
__be32 *addr, int na, int ns, int pna,
|
||||
const char *rprop)
|
||||
{
|
||||
const __be32 *ranges;
|
||||
int rlen;
|
||||
int rone;
|
||||
u64 offset = OF_BAD_ADDR;
|
||||
|
||||
/*
|
||||
* Normally, an absence of a "ranges" property means we are
|
||||
* crossing a non-translatable boundary, and thus the addresses
|
||||
* below the current cannot be converted to CPU physical ones.
|
||||
* Unfortunately, while this is very clear in the spec, it's not
|
||||
* what Apple understood, and they do have things like /uni-n or
|
||||
* /ht nodes with no "ranges" property and a lot of perfectly
|
||||
* useable mapped devices below them. Thus we treat the absence of
|
||||
* "ranges" as equivalent to an empty "ranges" property which means
|
||||
* a 1:1 translation at that level. It's up to the caller not to try
|
||||
* to translate addresses that aren't supposed to be translated in
|
||||
* the first place. --BenH.
|
||||
*
|
||||
* As far as we know, this damage only exists on Apple machines, so
|
||||
* This code is only enabled on powerpc. --gcl
|
||||
*/
|
||||
ranges = of_get_property(parent, rprop, &rlen);
|
||||
if (ranges == NULL && !of_empty_ranges_quirk(parent)) {
|
||||
debug("no ranges; cannot translate\n");
|
||||
return 1;
|
||||
}
|
||||
if (ranges == NULL || rlen == 0) {
|
||||
offset = of_read_number(addr, na);
|
||||
memset(addr, 0, pna * 4);
|
||||
debug("empty ranges; 1:1 translation\n");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
debug("walking ranges...\n");
|
||||
|
||||
/* Now walk through the ranges */
|
||||
rlen /= 4;
|
||||
rone = na + pna + ns;
|
||||
for (; rlen >= rone; rlen -= rone, ranges += rone) {
|
||||
offset = bus->map(addr, ranges, na, ns, pna);
|
||||
if (offset != OF_BAD_ADDR)
|
||||
break;
|
||||
}
|
||||
if (offset == OF_BAD_ADDR) {
|
||||
debug("not found !\n");
|
||||
return 1;
|
||||
}
|
||||
memcpy(addr, ranges + na, 4 * pna);
|
||||
|
||||
finish:
|
||||
of_dump_addr("parent translation for:", addr, pna);
|
||||
debug("with offset: %llx\n", (unsigned long long)offset);
|
||||
|
||||
/* Translate it into parent bus space */
|
||||
return pbus->translate(addr, offset, pna);
|
||||
}
|
||||
|
||||
/*
|
||||
* Translate an address from the device-tree into a CPU physical address,
|
||||
* this walks up the tree and applies the various bus mappings on the
|
||||
* way.
|
||||
*
|
||||
* Note: We consider that crossing any level with #size-cells == 0 to mean
|
||||
* that translation is impossible (that is we are not dealing with a value
|
||||
* that can be mapped to a cpu physical address). This is not really specified
|
||||
* that way, but this is traditionally the way IBM at least do things
|
||||
*/
|
||||
static u64 __of_translate_address(const struct device_node *dev,
|
||||
const __be32 *in_addr, const char *rprop)
|
||||
{
|
||||
struct device_node *parent = NULL;
|
||||
struct of_bus *bus, *pbus;
|
||||
__be32 addr[OF_MAX_ADDR_CELLS];
|
||||
int na, ns, pna, pns;
|
||||
u64 result = OF_BAD_ADDR;
|
||||
|
||||
debug("** translation for device %s **\n", of_node_full_name(dev));
|
||||
|
||||
/* Increase refcount at current level */
|
||||
(void)of_node_get(dev);
|
||||
|
||||
/* Get parent & match bus type */
|
||||
parent = of_get_parent(dev);
|
||||
if (parent == NULL)
|
||||
goto bail;
|
||||
bus = of_match_bus(parent);
|
||||
|
||||
/* Count address cells & copy address locally */
|
||||
bus->count_cells(dev, &na, &ns);
|
||||
if (!OF_CHECK_COUNTS(na, ns)) {
|
||||
debug("Bad cell count for %s\n", of_node_full_name(dev));
|
||||
goto bail;
|
||||
}
|
||||
memcpy(addr, in_addr, na * 4);
|
||||
|
||||
debug("bus is %s (na=%d, ns=%d) on %s\n", bus->name, na, ns,
|
||||
of_node_full_name(parent));
|
||||
of_dump_addr("translating address:", addr, na);
|
||||
|
||||
/* Translate */
|
||||
for (;;) {
|
||||
/* Switch to parent bus */
|
||||
of_node_put(dev);
|
||||
dev = parent;
|
||||
parent = of_get_parent(dev);
|
||||
|
||||
/* If root, we have finished */
|
||||
if (parent == NULL) {
|
||||
debug("reached root node\n");
|
||||
result = of_read_number(addr, na);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Get new parent bus and counts */
|
||||
pbus = of_match_bus(parent);
|
||||
pbus->count_cells(dev, &pna, &pns);
|
||||
if (!OF_CHECK_COUNTS(pna, pns)) {
|
||||
debug("Bad cell count for %s\n",
|
||||
of_node_full_name(dev));
|
||||
break;
|
||||
}
|
||||
|
||||
debug("parent bus is %s (na=%d, ns=%d) on %s\n", pbus->name,
|
||||
pna, pns, of_node_full_name(parent));
|
||||
|
||||
/* Apply bus translation */
|
||||
if (of_translate_one(dev, bus, pbus, addr, na, ns, pna, rprop))
|
||||
break;
|
||||
|
||||
/* Complete the move up one level */
|
||||
na = pna;
|
||||
ns = pns;
|
||||
bus = pbus;
|
||||
|
||||
of_dump_addr("one level translation:", addr, na);
|
||||
}
|
||||
bail:
|
||||
of_node_put(parent);
|
||||
of_node_put(dev);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
u64 of_translate_address(const struct device_node *dev, const __be32 *in_addr)
|
||||
{
|
||||
return __of_translate_address(dev, in_addr, "ranges");
|
||||
}
|
||||
|
||||
u64 of_translate_dma_address(const struct device_node *dev, const __be32 *in_addr)
|
||||
{
|
||||
return __of_translate_address(dev, in_addr, "dma-ranges");
|
||||
}
|
||||
|
||||
static int __of_address_to_resource(const struct device_node *dev,
|
||||
const __be32 *addrp, u64 size, unsigned int flags,
|
||||
const char *name, struct resource *r)
|
||||
{
|
||||
u64 taddr;
|
||||
|
||||
if ((flags & (IORESOURCE_IO | IORESOURCE_MEM)) == 0)
|
||||
return -EINVAL;
|
||||
taddr = of_translate_address(dev, addrp);
|
||||
if (taddr == OF_BAD_ADDR)
|
||||
return -EINVAL;
|
||||
memset(r, 0, sizeof(struct resource));
|
||||
r->start = taddr;
|
||||
r->end = taddr + size - 1;
|
||||
r->flags = flags;
|
||||
r->name = name ? name : dev->full_name;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int of_address_to_resource(const struct device_node *dev, int index,
|
||||
struct resource *r)
|
||||
{
|
||||
const __be32 *addrp;
|
||||
u64 size;
|
||||
unsigned int flags;
|
||||
const char *name = NULL;
|
||||
|
||||
addrp = of_get_address(dev, index, &size, &flags);
|
||||
if (addrp == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
/* Get optional "reg-names" property to add a name to a resource */
|
||||
of_property_read_string_index(dev, "reg-names", index, &name);
|
||||
|
||||
return __of_address_to_resource(dev, addrp, size, flags, name, r);
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (c) 2017 Google, Inc
|
||||
* Written by Simon Glass <sjg@chromium.org>
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <linux/libfdt.h>
|
||||
#include <dm/of_access.h>
|
||||
#include <dm/of_extra.h>
|
||||
#include <dm/ofnode.h>
|
||||
|
||||
int ofnode_read_fmap_entry(ofnode node, struct fmap_entry *entry)
|
||||
{
|
||||
const char *prop;
|
||||
|
||||
if (ofnode_read_u32(node, "image-pos", &entry->offset)) {
|
||||
debug("Node '%s' has bad/missing 'image-pos' property\n",
|
||||
ofnode_get_name(node));
|
||||
return log_ret(-ENOENT);
|
||||
}
|
||||
if (ofnode_read_u32(node, "size", &entry->length)) {
|
||||
debug("Node '%s' has bad/missing 'size' property\n",
|
||||
ofnode_get_name(node));
|
||||
return log_ret(-ENOENT);
|
||||
}
|
||||
entry->used = ofnode_read_s32_default(node, "used", entry->length);
|
||||
prop = ofnode_read_string(node, "compress");
|
||||
if (prop) {
|
||||
if (!strcmp(prop, "lz4"))
|
||||
entry->compress_algo = FMAP_COMPRESS_LZ4;
|
||||
else
|
||||
return log_msg_ret("Unknown compression algo",
|
||||
-EINVAL);
|
||||
} else {
|
||||
entry->compress_algo = FMAP_COMPRESS_NONE;
|
||||
}
|
||||
entry->unc_length = ofnode_read_s32_default(node, "uncomp-size",
|
||||
entry->length);
|
||||
prop = ofnode_read_string(node, "hash");
|
||||
if (prop)
|
||||
entry->hash_size = strlen(prop);
|
||||
entry->hash_algo = prop ? FMAP_HASH_SHA256 : FMAP_HASH_NONE;
|
||||
entry->hash = (uint8_t *)prop;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ofnode_decode_region(ofnode node, const char *prop_name, fdt_addr_t *basep,
|
||||
fdt_size_t *sizep)
|
||||
{
|
||||
const fdt_addr_t *cell;
|
||||
int len;
|
||||
|
||||
debug("%s: %s: %s\n", __func__, ofnode_get_name(node), prop_name);
|
||||
cell = ofnode_get_property(node, prop_name, &len);
|
||||
if (!cell || (len < sizeof(fdt_addr_t) * 2)) {
|
||||
debug("cell=%p, len=%d\n", cell, len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*basep = fdt_addr_to_cpu(*cell);
|
||||
*sizep = fdt_size_to_cpu(cell[1]);
|
||||
debug("%s: base=%08lx, size=%lx\n", __func__, (ulong)*basep,
|
||||
(ulong)*sizep);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ofnode_decode_memory_region(ofnode config_node, const char *mem_type,
|
||||
const char *suffix, fdt_addr_t *basep,
|
||||
fdt_size_t *sizep)
|
||||
{
|
||||
char prop_name[50];
|
||||
const char *mem;
|
||||
fdt_size_t size, offset_size;
|
||||
fdt_addr_t base, offset;
|
||||
ofnode node;
|
||||
|
||||
if (!ofnode_valid(config_node)) {
|
||||
config_node = ofnode_path("/config");
|
||||
if (!ofnode_valid(config_node)) {
|
||||
debug("%s: Cannot find /config node\n", __func__);
|
||||
return -ENOENT;
|
||||
}
|
||||
}
|
||||
if (!suffix)
|
||||
suffix = "";
|
||||
|
||||
snprintf(prop_name, sizeof(prop_name), "%s-memory%s", mem_type,
|
||||
suffix);
|
||||
mem = ofnode_read_string(config_node, prop_name);
|
||||
if (!mem) {
|
||||
debug("%s: No memory type for '%s', using /memory\n", __func__,
|
||||
prop_name);
|
||||
mem = "/memory";
|
||||
}
|
||||
|
||||
node = ofnode_path(mem);
|
||||
if (!ofnode_valid(node)) {
|
||||
debug("%s: Failed to find node '%s'\n", __func__, mem);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
/*
|
||||
* Not strictly correct - the memory may have multiple banks. We just
|
||||
* use the first
|
||||
*/
|
||||
if (ofnode_decode_region(node, "reg", &base, &size)) {
|
||||
debug("%s: Failed to decode memory region %s\n", __func__,
|
||||
mem);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
snprintf(prop_name, sizeof(prop_name), "%s-offset%s", mem_type,
|
||||
suffix);
|
||||
if (ofnode_decode_region(config_node, prop_name, &offset,
|
||||
&offset_size)) {
|
||||
debug("%s: Failed to decode memory region '%s'\n", __func__,
|
||||
prop_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*basep = base + offset;
|
||||
*sizep = offset_size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,895 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (c) 2017 Google, Inc
|
||||
* Written by Simon Glass <sjg@chromium.org>
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
#include <fdtdec.h>
|
||||
#include <fdt_support.h>
|
||||
#include <linux/libfdt.h>
|
||||
#include <dm/of_access.h>
|
||||
#include <dm/of_addr.h>
|
||||
#include <dm/ofnode.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/ioport.h>
|
||||
|
||||
int ofnode_read_u32(ofnode node, const char *propname, u32 *outp)
|
||||
{
|
||||
assert(ofnode_valid(node));
|
||||
debug("%s: %s: ", __func__, propname);
|
||||
|
||||
if (ofnode_is_np(node)) {
|
||||
return of_read_u32(ofnode_to_np(node), propname, outp);
|
||||
} else {
|
||||
const fdt32_t *cell;
|
||||
int len;
|
||||
|
||||
cell = fdt_getprop(gd->fdt_blob, ofnode_to_offset(node),
|
||||
propname, &len);
|
||||
if (!cell || len < sizeof(int)) {
|
||||
debug("(not found)\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
*outp = fdt32_to_cpu(cell[0]);
|
||||
}
|
||||
debug("%#x (%d)\n", *outp, *outp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 ofnode_read_u32_default(ofnode node, const char *propname, u32 def)
|
||||
{
|
||||
assert(ofnode_valid(node));
|
||||
ofnode_read_u32(node, propname, &def);
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
int ofnode_read_s32_default(ofnode node, const char *propname, s32 def)
|
||||
{
|
||||
assert(ofnode_valid(node));
|
||||
ofnode_read_u32(node, propname, (u32 *)&def);
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
int ofnode_read_u64(ofnode node, const char *propname, u64 *outp)
|
||||
{
|
||||
const unaligned_fdt64_t *cell;
|
||||
int len;
|
||||
|
||||
assert(ofnode_valid(node));
|
||||
debug("%s: %s: ", __func__, propname);
|
||||
|
||||
if (ofnode_is_np(node))
|
||||
return of_read_u64(ofnode_to_np(node), propname, outp);
|
||||
|
||||
cell = fdt_getprop(gd->fdt_blob, ofnode_to_offset(node), propname,
|
||||
&len);
|
||||
if (!cell || len < sizeof(*cell)) {
|
||||
debug("(not found)\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
*outp = fdt64_to_cpu(cell[0]);
|
||||
debug("%#llx (%lld)\n", (unsigned long long)*outp,
|
||||
(unsigned long long)*outp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 ofnode_read_u64_default(ofnode node, const char *propname, u64 def)
|
||||
{
|
||||
assert(ofnode_valid(node));
|
||||
ofnode_read_u64(node, propname, &def);
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
bool ofnode_read_bool(ofnode node, const char *propname)
|
||||
{
|
||||
const void *prop;
|
||||
|
||||
assert(ofnode_valid(node));
|
||||
debug("%s: %s: ", __func__, propname);
|
||||
|
||||
prop = ofnode_get_property(node, propname, NULL);
|
||||
|
||||
debug("%s\n", prop ? "true" : "false");
|
||||
|
||||
return prop ? true : false;
|
||||
}
|
||||
|
||||
const char *ofnode_read_string(ofnode node, const char *propname)
|
||||
{
|
||||
const char *str = NULL;
|
||||
int len = -1;
|
||||
|
||||
assert(ofnode_valid(node));
|
||||
debug("%s: %s: ", __func__, propname);
|
||||
|
||||
if (ofnode_is_np(node)) {
|
||||
struct property *prop = of_find_property(
|
||||
ofnode_to_np(node), propname, NULL);
|
||||
|
||||
if (prop) {
|
||||
str = prop->value;
|
||||
len = prop->length;
|
||||
}
|
||||
} else {
|
||||
str = fdt_getprop(gd->fdt_blob, ofnode_to_offset(node),
|
||||
propname, &len);
|
||||
}
|
||||
if (!str) {
|
||||
debug("<not found>\n");
|
||||
return NULL;
|
||||
}
|
||||
if (strnlen(str, len) >= len) {
|
||||
debug("<invalid>\n");
|
||||
return NULL;
|
||||
}
|
||||
debug("%s\n", str);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
ofnode ofnode_find_subnode(ofnode node, const char *subnode_name)
|
||||
{
|
||||
ofnode subnode;
|
||||
|
||||
assert(ofnode_valid(node));
|
||||
debug("%s: %s: ", __func__, subnode_name);
|
||||
|
||||
if (ofnode_is_np(node)) {
|
||||
const struct device_node *np = ofnode_to_np(node);
|
||||
|
||||
for (np = np->child; np; np = np->sibling) {
|
||||
if (!strcmp(subnode_name, np->name))
|
||||
break;
|
||||
}
|
||||
subnode = np_to_ofnode(np);
|
||||
} else {
|
||||
int ooffset = fdt_subnode_offset(gd->fdt_blob,
|
||||
ofnode_to_offset(node), subnode_name);
|
||||
subnode = offset_to_ofnode(ooffset);
|
||||
}
|
||||
debug("%s\n", ofnode_valid(subnode) ?
|
||||
ofnode_get_name(subnode) : "<none>");
|
||||
|
||||
return subnode;
|
||||
}
|
||||
|
||||
int ofnode_read_u32_array(ofnode node, const char *propname,
|
||||
u32 *out_values, size_t sz)
|
||||
{
|
||||
assert(ofnode_valid(node));
|
||||
debug("%s: %s: ", __func__, propname);
|
||||
|
||||
if (ofnode_is_np(node)) {
|
||||
return of_read_u32_array(ofnode_to_np(node), propname,
|
||||
out_values, sz);
|
||||
} else {
|
||||
return fdtdec_get_int_array(gd->fdt_blob,
|
||||
ofnode_to_offset(node), propname,
|
||||
out_values, sz);
|
||||
}
|
||||
}
|
||||
|
||||
ofnode ofnode_first_subnode(ofnode node)
|
||||
{
|
||||
assert(ofnode_valid(node));
|
||||
if (ofnode_is_np(node))
|
||||
return np_to_ofnode(node.np->child);
|
||||
|
||||
return offset_to_ofnode(
|
||||
fdt_first_subnode(gd->fdt_blob, ofnode_to_offset(node)));
|
||||
}
|
||||
|
||||
ofnode ofnode_next_subnode(ofnode node)
|
||||
{
|
||||
assert(ofnode_valid(node));
|
||||
if (ofnode_is_np(node))
|
||||
return np_to_ofnode(node.np->sibling);
|
||||
|
||||
return offset_to_ofnode(
|
||||
fdt_next_subnode(gd->fdt_blob, ofnode_to_offset(node)));
|
||||
}
|
||||
|
||||
ofnode ofnode_get_parent(ofnode node)
|
||||
{
|
||||
ofnode parent;
|
||||
|
||||
assert(ofnode_valid(node));
|
||||
if (ofnode_is_np(node))
|
||||
parent = np_to_ofnode(of_get_parent(ofnode_to_np(node)));
|
||||
else
|
||||
parent.of_offset = fdt_parent_offset(gd->fdt_blob,
|
||||
ofnode_to_offset(node));
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
const char *ofnode_get_name(ofnode node)
|
||||
{
|
||||
if (!ofnode_valid(node)) {
|
||||
debug("%s node not valid\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ofnode_is_np(node))
|
||||
return strrchr(node.np->full_name, '/') + 1;
|
||||
|
||||
return fdt_get_name(gd->fdt_blob, ofnode_to_offset(node), NULL);
|
||||
}
|
||||
|
||||
ofnode ofnode_get_by_phandle(uint phandle)
|
||||
{
|
||||
ofnode node;
|
||||
|
||||
if (of_live_active())
|
||||
node = np_to_ofnode(of_find_node_by_phandle(phandle));
|
||||
else
|
||||
node.of_offset = fdt_node_offset_by_phandle(gd->fdt_blob,
|
||||
phandle);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
int ofnode_read_size(ofnode node, const char *propname)
|
||||
{
|
||||
int len;
|
||||
|
||||
if (ofnode_is_np(node)) {
|
||||
struct property *prop = of_find_property(
|
||||
ofnode_to_np(node), propname, NULL);
|
||||
|
||||
if (prop)
|
||||
return prop->length;
|
||||
} else {
|
||||
if (fdt_getprop(gd->fdt_blob, ofnode_to_offset(node), propname,
|
||||
&len))
|
||||
return len;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
fdt_addr_t ofnode_get_addr_size_index(ofnode node, int index, fdt_size_t *size)
|
||||
{
|
||||
int na, ns;
|
||||
|
||||
if (ofnode_is_np(node)) {
|
||||
const __be32 *prop_val;
|
||||
u64 size64;
|
||||
uint flags;
|
||||
|
||||
prop_val = of_get_address(ofnode_to_np(node), index, &size64,
|
||||
&flags);
|
||||
if (!prop_val)
|
||||
return FDT_ADDR_T_NONE;
|
||||
if (size)
|
||||
*size = size64;
|
||||
|
||||
ns = of_n_size_cells(ofnode_to_np(node));
|
||||
|
||||
if (IS_ENABLED(CONFIG_OF_TRANSLATE) && ns > 0) {
|
||||
return of_translate_address(ofnode_to_np(node), prop_val);
|
||||
} else {
|
||||
na = of_n_addr_cells(ofnode_to_np(node));
|
||||
return of_read_number(prop_val, na);
|
||||
}
|
||||
} else {
|
||||
na = ofnode_read_simple_addr_cells(ofnode_get_parent(node));
|
||||
ns = ofnode_read_simple_size_cells(ofnode_get_parent(node));
|
||||
return fdtdec_get_addr_size_fixed(gd->fdt_blob,
|
||||
ofnode_to_offset(node), "reg",
|
||||
index, na, ns, size, true);
|
||||
}
|
||||
|
||||
return FDT_ADDR_T_NONE;
|
||||
}
|
||||
|
||||
fdt_addr_t ofnode_get_addr_index(ofnode node, int index)
|
||||
{
|
||||
fdt_size_t size;
|
||||
|
||||
return ofnode_get_addr_size_index(node, index, &size);
|
||||
}
|
||||
|
||||
fdt_addr_t ofnode_get_addr(ofnode node)
|
||||
{
|
||||
return ofnode_get_addr_index(node, 0);
|
||||
}
|
||||
|
||||
int ofnode_stringlist_search(ofnode node, const char *property,
|
||||
const char *string)
|
||||
{
|
||||
if (ofnode_is_np(node)) {
|
||||
return of_property_match_string(ofnode_to_np(node),
|
||||
property, string);
|
||||
} else {
|
||||
int ret;
|
||||
|
||||
ret = fdt_stringlist_search(gd->fdt_blob,
|
||||
ofnode_to_offset(node), property,
|
||||
string);
|
||||
if (ret == -FDT_ERR_NOTFOUND)
|
||||
return -ENODATA;
|
||||
else if (ret < 0)
|
||||
return -EINVAL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
int ofnode_read_string_index(ofnode node, const char *property, int index,
|
||||
const char **outp)
|
||||
{
|
||||
if (ofnode_is_np(node)) {
|
||||
return of_property_read_string_index(ofnode_to_np(node),
|
||||
property, index, outp);
|
||||
} else {
|
||||
int len;
|
||||
|
||||
*outp = fdt_stringlist_get(gd->fdt_blob, ofnode_to_offset(node),
|
||||
property, index, &len);
|
||||
if (len < 0)
|
||||
return -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int ofnode_read_string_count(ofnode node, const char *property)
|
||||
{
|
||||
if (ofnode_is_np(node)) {
|
||||
return of_property_count_strings(ofnode_to_np(node), property);
|
||||
} else {
|
||||
return fdt_stringlist_count(gd->fdt_blob,
|
||||
ofnode_to_offset(node), property);
|
||||
}
|
||||
}
|
||||
|
||||
static void ofnode_from_fdtdec_phandle_args(struct fdtdec_phandle_args *in,
|
||||
struct ofnode_phandle_args *out)
|
||||
{
|
||||
assert(OF_MAX_PHANDLE_ARGS == MAX_PHANDLE_ARGS);
|
||||
out->node = offset_to_ofnode(in->node);
|
||||
out->args_count = in->args_count;
|
||||
memcpy(out->args, in->args, sizeof(out->args));
|
||||
}
|
||||
|
||||
static void ofnode_from_of_phandle_args(struct of_phandle_args *in,
|
||||
struct ofnode_phandle_args *out)
|
||||
{
|
||||
assert(OF_MAX_PHANDLE_ARGS == MAX_PHANDLE_ARGS);
|
||||
out->node = np_to_ofnode(in->np);
|
||||
out->args_count = in->args_count;
|
||||
memcpy(out->args, in->args, sizeof(out->args));
|
||||
}
|
||||
|
||||
int ofnode_parse_phandle_with_args(ofnode node, const char *list_name,
|
||||
const char *cells_name, int cell_count,
|
||||
int index,
|
||||
struct ofnode_phandle_args *out_args)
|
||||
{
|
||||
if (ofnode_is_np(node)) {
|
||||
struct of_phandle_args args;
|
||||
int ret;
|
||||
|
||||
ret = of_parse_phandle_with_args(ofnode_to_np(node),
|
||||
list_name, cells_name, index,
|
||||
&args);
|
||||
if (ret)
|
||||
return ret;
|
||||
ofnode_from_of_phandle_args(&args, out_args);
|
||||
} else {
|
||||
struct fdtdec_phandle_args args;
|
||||
int ret;
|
||||
|
||||
ret = fdtdec_parse_phandle_with_args(gd->fdt_blob,
|
||||
ofnode_to_offset(node),
|
||||
list_name, cells_name,
|
||||
cell_count, index, &args);
|
||||
if (ret)
|
||||
return ret;
|
||||
ofnode_from_fdtdec_phandle_args(&args, out_args);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ofnode_count_phandle_with_args(ofnode node, const char *list_name,
|
||||
const char *cells_name)
|
||||
{
|
||||
if (ofnode_is_np(node))
|
||||
return of_count_phandle_with_args(ofnode_to_np(node),
|
||||
list_name, cells_name);
|
||||
else
|
||||
return fdtdec_parse_phandle_with_args(gd->fdt_blob,
|
||||
ofnode_to_offset(node), list_name, cells_name,
|
||||
0, -1, NULL);
|
||||
}
|
||||
|
||||
ofnode ofnode_path(const char *path)
|
||||
{
|
||||
if (of_live_active())
|
||||
return np_to_ofnode(of_find_node_by_path(path));
|
||||
else
|
||||
return offset_to_ofnode(fdt_path_offset(gd->fdt_blob, path));
|
||||
}
|
||||
|
||||
const char *ofnode_get_chosen_prop(const char *name)
|
||||
{
|
||||
ofnode chosen_node;
|
||||
|
||||
chosen_node = ofnode_path("/chosen");
|
||||
|
||||
return ofnode_read_string(chosen_node, name);
|
||||
}
|
||||
|
||||
ofnode ofnode_get_chosen_node(const char *name)
|
||||
{
|
||||
const char *prop;
|
||||
|
||||
prop = ofnode_get_chosen_prop(name);
|
||||
if (!prop)
|
||||
return ofnode_null();
|
||||
|
||||
return ofnode_path(prop);
|
||||
}
|
||||
|
||||
static int decode_timing_property(ofnode node, const char *name,
|
||||
struct timing_entry *result)
|
||||
{
|
||||
int length, ret = 0;
|
||||
|
||||
length = ofnode_read_size(node, name);
|
||||
if (length < 0) {
|
||||
debug("%s: could not find property %s\n",
|
||||
ofnode_get_name(node), name);
|
||||
return length;
|
||||
}
|
||||
|
||||
if (length == sizeof(u32)) {
|
||||
result->typ = ofnode_read_u32_default(node, name, 0);
|
||||
result->min = result->typ;
|
||||
result->max = result->typ;
|
||||
} else {
|
||||
ret = ofnode_read_u32_array(node, name, &result->min, 3);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ofnode_decode_display_timing(ofnode parent, int index,
|
||||
struct display_timing *dt)
|
||||
{
|
||||
int i;
|
||||
ofnode timings, node;
|
||||
u32 val = 0;
|
||||
int ret = 0;
|
||||
|
||||
timings = ofnode_find_subnode(parent, "display-timings");
|
||||
if (!ofnode_valid(timings))
|
||||
return -EINVAL;
|
||||
|
||||
i = 0;
|
||||
ofnode_for_each_subnode(node, timings) {
|
||||
if (i++ == index)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ofnode_valid(node))
|
||||
return -EINVAL;
|
||||
|
||||
memset(dt, 0, sizeof(*dt));
|
||||
|
||||
ret |= decode_timing_property(node, "hback-porch", &dt->hback_porch);
|
||||
ret |= decode_timing_property(node, "hfront-porch", &dt->hfront_porch);
|
||||
ret |= decode_timing_property(node, "hactive", &dt->hactive);
|
||||
ret |= decode_timing_property(node, "hsync-len", &dt->hsync_len);
|
||||
ret |= decode_timing_property(node, "vback-porch", &dt->vback_porch);
|
||||
ret |= decode_timing_property(node, "vfront-porch", &dt->vfront_porch);
|
||||
ret |= decode_timing_property(node, "vactive", &dt->vactive);
|
||||
ret |= decode_timing_property(node, "vsync-len", &dt->vsync_len);
|
||||
ret |= decode_timing_property(node, "clock-frequency", &dt->pixelclock);
|
||||
|
||||
dt->flags = 0;
|
||||
val = ofnode_read_u32_default(node, "vsync-active", -1);
|
||||
if (val != -1) {
|
||||
dt->flags |= val ? DISPLAY_FLAGS_VSYNC_HIGH :
|
||||
DISPLAY_FLAGS_VSYNC_LOW;
|
||||
}
|
||||
val = ofnode_read_u32_default(node, "hsync-active", -1);
|
||||
if (val != -1) {
|
||||
dt->flags |= val ? DISPLAY_FLAGS_HSYNC_HIGH :
|
||||
DISPLAY_FLAGS_HSYNC_LOW;
|
||||
}
|
||||
val = ofnode_read_u32_default(node, "de-active", -1);
|
||||
if (val != -1) {
|
||||
dt->flags |= val ? DISPLAY_FLAGS_DE_HIGH :
|
||||
DISPLAY_FLAGS_DE_LOW;
|
||||
}
|
||||
val = ofnode_read_u32_default(node, "pixelclk-active", -1);
|
||||
if (val != -1) {
|
||||
dt->flags |= val ? DISPLAY_FLAGS_PIXDATA_POSEDGE :
|
||||
DISPLAY_FLAGS_PIXDATA_NEGEDGE;
|
||||
}
|
||||
|
||||
if (ofnode_read_bool(node, "interlaced"))
|
||||
dt->flags |= DISPLAY_FLAGS_INTERLACED;
|
||||
if (ofnode_read_bool(node, "doublescan"))
|
||||
dt->flags |= DISPLAY_FLAGS_DOUBLESCAN;
|
||||
if (ofnode_read_bool(node, "doubleclk"))
|
||||
dt->flags |= DISPLAY_FLAGS_DOUBLECLK;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
const void *ofnode_get_property(ofnode node, const char *propname, int *lenp)
|
||||
{
|
||||
if (ofnode_is_np(node))
|
||||
return of_get_property(ofnode_to_np(node), propname, lenp);
|
||||
else
|
||||
return fdt_getprop(gd->fdt_blob, ofnode_to_offset(node),
|
||||
propname, lenp);
|
||||
}
|
||||
|
||||
bool ofnode_is_available(ofnode node)
|
||||
{
|
||||
if (ofnode_is_np(node))
|
||||
return of_device_is_available(ofnode_to_np(node));
|
||||
else
|
||||
return fdtdec_get_is_enabled(gd->fdt_blob,
|
||||
ofnode_to_offset(node));
|
||||
}
|
||||
|
||||
fdt_addr_t ofnode_get_addr_size(ofnode node, const char *property,
|
||||
fdt_size_t *sizep)
|
||||
{
|
||||
if (ofnode_is_np(node)) {
|
||||
int na, ns;
|
||||
int psize;
|
||||
const struct device_node *np = ofnode_to_np(node);
|
||||
const __be32 *prop = of_get_property(np, property, &psize);
|
||||
|
||||
if (!prop)
|
||||
return FDT_ADDR_T_NONE;
|
||||
na = of_n_addr_cells(np);
|
||||
ns = of_n_size_cells(np);
|
||||
*sizep = of_read_number(prop + na, ns);
|
||||
|
||||
if (CONFIG_IS_ENABLED(OF_TRANSLATE) && ns > 0)
|
||||
return of_translate_address(np, prop);
|
||||
else
|
||||
return of_read_number(prop, na);
|
||||
} else {
|
||||
return fdtdec_get_addr_size(gd->fdt_blob,
|
||||
ofnode_to_offset(node), property,
|
||||
sizep);
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t *ofnode_read_u8_array_ptr(ofnode node, const char *propname,
|
||||
size_t sz)
|
||||
{
|
||||
if (ofnode_is_np(node)) {
|
||||
const struct device_node *np = ofnode_to_np(node);
|
||||
int psize;
|
||||
const __be32 *prop = of_get_property(np, propname, &psize);
|
||||
|
||||
if (!prop || sz != psize)
|
||||
return NULL;
|
||||
return (uint8_t *)prop;
|
||||
|
||||
} else {
|
||||
return fdtdec_locate_byte_array(gd->fdt_blob,
|
||||
ofnode_to_offset(node), propname, sz);
|
||||
}
|
||||
}
|
||||
|
||||
int ofnode_read_pci_addr(ofnode node, enum fdt_pci_space type,
|
||||
const char *propname, struct fdt_pci_addr *addr)
|
||||
{
|
||||
const fdt32_t *cell;
|
||||
int len;
|
||||
int ret = -ENOENT;
|
||||
|
||||
debug("%s: %s: ", __func__, propname);
|
||||
|
||||
/*
|
||||
* If we follow the pci bus bindings strictly, we should check
|
||||
* the value of the node's parent node's #address-cells and
|
||||
* #size-cells. They need to be 3 and 2 accordingly. However,
|
||||
* for simplicity we skip the check here.
|
||||
*/
|
||||
cell = ofnode_get_property(node, propname, &len);
|
||||
if (!cell)
|
||||
goto fail;
|
||||
|
||||
if ((len % FDT_PCI_REG_SIZE) == 0) {
|
||||
int num = len / FDT_PCI_REG_SIZE;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
debug("pci address #%d: %08lx %08lx %08lx\n", i,
|
||||
(ulong)fdt32_to_cpu(cell[0]),
|
||||
(ulong)fdt32_to_cpu(cell[1]),
|
||||
(ulong)fdt32_to_cpu(cell[2]));
|
||||
if ((fdt32_to_cpu(*cell) & type) == type) {
|
||||
addr->phys_hi = fdt32_to_cpu(cell[0]);
|
||||
addr->phys_mid = fdt32_to_cpu(cell[1]);
|
||||
addr->phys_lo = fdt32_to_cpu(cell[2]);
|
||||
break;
|
||||
}
|
||||
|
||||
cell += (FDT_PCI_ADDR_CELLS +
|
||||
FDT_PCI_SIZE_CELLS);
|
||||
}
|
||||
|
||||
if (i == num) {
|
||||
ret = -ENXIO;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = -EINVAL;
|
||||
|
||||
fail:
|
||||
debug("(not found)\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ofnode_read_pci_vendev(ofnode node, u16 *vendor, u16 *device)
|
||||
{
|
||||
const char *list, *end;
|
||||
int len;
|
||||
|
||||
list = ofnode_get_property(node, "compatible", &len);
|
||||
if (!list)
|
||||
return -ENOENT;
|
||||
|
||||
end = list + len;
|
||||
while (list < end) {
|
||||
len = strlen(list);
|
||||
if (len >= strlen("pciVVVV,DDDD")) {
|
||||
char *s = strstr(list, "pci");
|
||||
|
||||
/*
|
||||
* check if the string is something like pciVVVV,DDDD.RR
|
||||
* or just pciVVVV,DDDD
|
||||
*/
|
||||
if (s && s[7] == ',' &&
|
||||
(s[12] == '.' || s[12] == 0)) {
|
||||
s += 3;
|
||||
*vendor = simple_strtol(s, NULL, 16);
|
||||
|
||||
s += 5;
|
||||
*device = simple_strtol(s, NULL, 16);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
list += (len + 1);
|
||||
}
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
int ofnode_read_addr_cells(ofnode node)
|
||||
{
|
||||
if (ofnode_is_np(node))
|
||||
return of_n_addr_cells(ofnode_to_np(node));
|
||||
else /* NOTE: this call should walk up the parent stack */
|
||||
return fdt_address_cells(gd->fdt_blob, ofnode_to_offset(node));
|
||||
}
|
||||
|
||||
int ofnode_read_size_cells(ofnode node)
|
||||
{
|
||||
if (ofnode_is_np(node))
|
||||
return of_n_size_cells(ofnode_to_np(node));
|
||||
else /* NOTE: this call should walk up the parent stack */
|
||||
return fdt_size_cells(gd->fdt_blob, ofnode_to_offset(node));
|
||||
}
|
||||
|
||||
int ofnode_read_simple_addr_cells(ofnode node)
|
||||
{
|
||||
if (ofnode_is_np(node))
|
||||
return of_simple_addr_cells(ofnode_to_np(node));
|
||||
else
|
||||
return fdt_address_cells(gd->fdt_blob, ofnode_to_offset(node));
|
||||
}
|
||||
|
||||
int ofnode_read_simple_size_cells(ofnode node)
|
||||
{
|
||||
if (ofnode_is_np(node))
|
||||
return of_simple_size_cells(ofnode_to_np(node));
|
||||
else
|
||||
return fdt_size_cells(gd->fdt_blob, ofnode_to_offset(node));
|
||||
}
|
||||
|
||||
bool ofnode_pre_reloc(ofnode node)
|
||||
{
|
||||
#if defined(CONFIG_SPL_BUILD) || defined(CONFIG_TPL_BUILD)
|
||||
/* for SPL and TPL the remaining nodes after the fdtgrep 1st pass
|
||||
* had property dm-pre-reloc or u-boot,dm-spl/tpl.
|
||||
* They are removed in final dtb (fdtgrep 2nd pass)
|
||||
*/
|
||||
return true;
|
||||
#else
|
||||
if (ofnode_read_bool(node, "u-boot,dm-pre-reloc"))
|
||||
return true;
|
||||
if (ofnode_read_bool(node, "u-boot,dm-pre-proper"))
|
||||
return true;
|
||||
|
||||
/*
|
||||
* In regular builds individual spl and tpl handling both
|
||||
* count as handled pre-relocation for later second init.
|
||||
*/
|
||||
if (ofnode_read_bool(node, "u-boot,dm-spl") ||
|
||||
ofnode_read_bool(node, "u-boot,dm-tpl"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
int ofnode_read_resource(ofnode node, uint index, struct resource *res)
|
||||
{
|
||||
if (ofnode_is_np(node)) {
|
||||
return of_address_to_resource(ofnode_to_np(node), index, res);
|
||||
} else {
|
||||
struct fdt_resource fres;
|
||||
int ret;
|
||||
|
||||
ret = fdt_get_resource(gd->fdt_blob, ofnode_to_offset(node),
|
||||
"reg", index, &fres);
|
||||
if (ret < 0)
|
||||
return -EINVAL;
|
||||
memset(res, '\0', sizeof(*res));
|
||||
res->start = fres.start;
|
||||
res->end = fres.end;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int ofnode_read_resource_byname(ofnode node, const char *name,
|
||||
struct resource *res)
|
||||
{
|
||||
int index;
|
||||
|
||||
index = ofnode_stringlist_search(node, "reg-names", name);
|
||||
if (index < 0)
|
||||
return index;
|
||||
|
||||
return ofnode_read_resource(node, index, res);
|
||||
}
|
||||
|
||||
u64 ofnode_translate_address(ofnode node, const fdt32_t *in_addr)
|
||||
{
|
||||
if (ofnode_is_np(node))
|
||||
return of_translate_address(ofnode_to_np(node), in_addr);
|
||||
else
|
||||
return fdt_translate_address(gd->fdt_blob, ofnode_to_offset(node), in_addr);
|
||||
}
|
||||
|
||||
u64 ofnode_translate_dma_address(ofnode node, const fdt32_t *in_addr)
|
||||
{
|
||||
if (ofnode_is_np(node))
|
||||
return of_translate_dma_address(ofnode_to_np(node), in_addr);
|
||||
else
|
||||
return fdt_translate_dma_address(gd->fdt_blob, ofnode_to_offset(node), in_addr);
|
||||
}
|
||||
|
||||
int ofnode_device_is_compatible(ofnode node, const char *compat)
|
||||
{
|
||||
if (ofnode_is_np(node))
|
||||
return of_device_is_compatible(ofnode_to_np(node), compat,
|
||||
NULL, NULL);
|
||||
else
|
||||
return !fdt_node_check_compatible(gd->fdt_blob,
|
||||
ofnode_to_offset(node),
|
||||
compat);
|
||||
}
|
||||
|
||||
ofnode ofnode_by_compatible(ofnode from, const char *compat)
|
||||
{
|
||||
if (of_live_active()) {
|
||||
return np_to_ofnode(of_find_compatible_node(
|
||||
(struct device_node *)ofnode_to_np(from), NULL,
|
||||
compat));
|
||||
} else {
|
||||
return offset_to_ofnode(fdt_node_offset_by_compatible(
|
||||
gd->fdt_blob, ofnode_to_offset(from), compat));
|
||||
}
|
||||
}
|
||||
|
||||
ofnode ofnode_by_prop_value(ofnode from, const char *propname,
|
||||
const void *propval, int proplen)
|
||||
{
|
||||
if (of_live_active()) {
|
||||
return np_to_ofnode(of_find_node_by_prop_value(
|
||||
(struct device_node *)ofnode_to_np(from), propname,
|
||||
propval, proplen));
|
||||
} else {
|
||||
return offset_to_ofnode(fdt_node_offset_by_prop_value(
|
||||
gd->fdt_blob, ofnode_to_offset(from),
|
||||
propname, propval, proplen));
|
||||
}
|
||||
}
|
||||
|
||||
int ofnode_write_prop(ofnode node, const char *propname, int len,
|
||||
const void *value)
|
||||
{
|
||||
const struct device_node *np = ofnode_to_np(node);
|
||||
struct property *pp;
|
||||
struct property *pp_last = NULL;
|
||||
struct property *new;
|
||||
|
||||
if (!of_live_active())
|
||||
return -ENOSYS;
|
||||
|
||||
if (!np)
|
||||
return -EINVAL;
|
||||
|
||||
for (pp = np->properties; pp; pp = pp->next) {
|
||||
if (strcmp(pp->name, propname) == 0) {
|
||||
/* Property exists -> change value */
|
||||
pp->value = (void *)value;
|
||||
pp->length = len;
|
||||
return 0;
|
||||
}
|
||||
pp_last = pp;
|
||||
}
|
||||
|
||||
if (!pp_last)
|
||||
return -ENOENT;
|
||||
|
||||
/* Property does not exist -> append new property */
|
||||
new = malloc(sizeof(struct property));
|
||||
if (!new)
|
||||
return -ENOMEM;
|
||||
|
||||
new->name = strdup(propname);
|
||||
if (!new->name) {
|
||||
free(new);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
new->value = (void *)value;
|
||||
new->length = len;
|
||||
new->next = NULL;
|
||||
|
||||
pp_last->next = new;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ofnode_write_string(ofnode node, const char *propname, const char *value)
|
||||
{
|
||||
if (!of_live_active())
|
||||
return -ENOSYS;
|
||||
|
||||
assert(ofnode_valid(node));
|
||||
|
||||
debug("%s: %s = %s", __func__, propname, value);
|
||||
|
||||
return ofnode_write_prop(node, propname, strlen(value) + 1, value);
|
||||
}
|
||||
|
||||
int ofnode_set_enabled(ofnode node, bool value)
|
||||
{
|
||||
if (!of_live_active())
|
||||
return -ENOSYS;
|
||||
|
||||
assert(ofnode_valid(node));
|
||||
|
||||
if (value)
|
||||
return ofnode_write_string(node, "status", "okay");
|
||||
else
|
||||
return ofnode_write_string(node, "status", "disabled");
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (c) 2017 Google, Inc
|
||||
* Written by Simon Glass <sjg@chromium.org>
|
||||
*/
|
||||
|
||||
#include <asm/types.h>
|
||||
#include <asm/io.h>
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
#include <mapmem.h>
|
||||
#include <dm/of_access.h>
|
||||
|
||||
int dev_read_u32(struct udevice *dev, const char *propname, u32 *outp)
|
||||
{
|
||||
return ofnode_read_u32(dev_ofnode(dev), propname, outp);
|
||||
}
|
||||
|
||||
int dev_read_u32_default(struct udevice *dev, const char *propname, int def)
|
||||
{
|
||||
return ofnode_read_u32_default(dev_ofnode(dev), propname, def);
|
||||
}
|
||||
|
||||
int dev_read_s32(struct udevice *dev, const char *propname, s32 *outp)
|
||||
{
|
||||
return ofnode_read_u32(dev_ofnode(dev), propname, (u32 *)outp);
|
||||
}
|
||||
|
||||
int dev_read_s32_default(struct udevice *dev, const char *propname, int def)
|
||||
{
|
||||
return ofnode_read_u32_default(dev_ofnode(dev), propname, def);
|
||||
}
|
||||
|
||||
int dev_read_u32u(struct udevice *dev, const char *propname, uint *outp)
|
||||
{
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = ofnode_read_u32(dev_ofnode(dev), propname, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
*outp = val;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dev_read_u64(struct udevice *dev, const char *propname, u64 *outp)
|
||||
{
|
||||
return ofnode_read_u64(dev_ofnode(dev), propname, outp);
|
||||
}
|
||||
|
||||
u64 dev_read_u64_default(struct udevice *dev, const char *propname, u64 def)
|
||||
{
|
||||
return ofnode_read_u64_default(dev_ofnode(dev), propname, def);
|
||||
}
|
||||
|
||||
const char *dev_read_string(struct udevice *dev, const char *propname)
|
||||
{
|
||||
return ofnode_read_string(dev_ofnode(dev), propname);
|
||||
}
|
||||
|
||||
bool dev_read_bool(struct udevice *dev, const char *propname)
|
||||
{
|
||||
return ofnode_read_bool(dev_ofnode(dev), propname);
|
||||
}
|
||||
|
||||
ofnode dev_read_subnode(struct udevice *dev, const char *subnode_name)
|
||||
{
|
||||
return ofnode_find_subnode(dev_ofnode(dev), subnode_name);
|
||||
}
|
||||
|
||||
ofnode dev_read_first_subnode(struct udevice *dev)
|
||||
{
|
||||
return ofnode_first_subnode(dev_ofnode(dev));
|
||||
}
|
||||
|
||||
ofnode dev_read_next_subnode(ofnode node)
|
||||
{
|
||||
return ofnode_next_subnode(node);
|
||||
}
|
||||
|
||||
int dev_read_size(struct udevice *dev, const char *propname)
|
||||
{
|
||||
return ofnode_read_size(dev_ofnode(dev), propname);
|
||||
}
|
||||
|
||||
fdt_addr_t dev_read_addr_index(struct udevice *dev, int index)
|
||||
{
|
||||
if (ofnode_is_np(dev_ofnode(dev)))
|
||||
return ofnode_get_addr_index(dev_ofnode(dev), index);
|
||||
else
|
||||
return devfdt_get_addr_index(dev, index);
|
||||
}
|
||||
|
||||
fdt_addr_t dev_read_addr_size_index(struct udevice *dev, int index,
|
||||
fdt_size_t *size)
|
||||
{
|
||||
if (ofnode_is_np(dev_ofnode(dev)))
|
||||
return ofnode_get_addr_size_index(dev_ofnode(dev), index, size);
|
||||
else
|
||||
return devfdt_get_addr_size_index(dev, index, size);
|
||||
}
|
||||
|
||||
void *dev_remap_addr_index(struct udevice *dev, int index)
|
||||
{
|
||||
fdt_addr_t addr = dev_read_addr_index(dev, index);
|
||||
|
||||
if (addr == FDT_ADDR_T_NONE)
|
||||
return NULL;
|
||||
|
||||
return map_physmem(addr, 0, MAP_NOCACHE);
|
||||
}
|
||||
|
||||
fdt_addr_t dev_read_addr_name(struct udevice *dev, const char *name)
|
||||
{
|
||||
int index = dev_read_stringlist_search(dev, "reg-names", name);
|
||||
|
||||
if (index < 0)
|
||||
return FDT_ADDR_T_NONE;
|
||||
else
|
||||
return dev_read_addr_index(dev, index);
|
||||
}
|
||||
|
||||
fdt_addr_t dev_read_addr_size_name(struct udevice *dev, const char *name,
|
||||
fdt_size_t *size)
|
||||
{
|
||||
int index = dev_read_stringlist_search(dev, "reg-names", name);
|
||||
|
||||
if (index < 0)
|
||||
return FDT_ADDR_T_NONE;
|
||||
else
|
||||
return dev_read_addr_size_index(dev, index, size);
|
||||
}
|
||||
|
||||
void *dev_remap_addr_name(struct udevice *dev, const char *name)
|
||||
{
|
||||
fdt_addr_t addr = dev_read_addr_name(dev, name);
|
||||
|
||||
if (addr == FDT_ADDR_T_NONE)
|
||||
return NULL;
|
||||
|
||||
return map_physmem(addr, 0, MAP_NOCACHE);
|
||||
}
|
||||
|
||||
fdt_addr_t dev_read_addr(struct udevice *dev)
|
||||
{
|
||||
return dev_read_addr_index(dev, 0);
|
||||
}
|
||||
|
||||
void *dev_read_addr_ptr(struct udevice *dev)
|
||||
{
|
||||
fdt_addr_t addr = dev_read_addr(dev);
|
||||
|
||||
return (addr == FDT_ADDR_T_NONE) ? NULL : map_sysmem(addr, 0);
|
||||
}
|
||||
|
||||
void *dev_remap_addr(struct udevice *dev)
|
||||
{
|
||||
return dev_remap_addr_index(dev, 0);
|
||||
}
|
||||
|
||||
fdt_addr_t dev_read_addr_size(struct udevice *dev, const char *property,
|
||||
fdt_size_t *sizep)
|
||||
{
|
||||
return ofnode_get_addr_size(dev_ofnode(dev), property, sizep);
|
||||
}
|
||||
|
||||
const char *dev_read_name(struct udevice *dev)
|
||||
{
|
||||
return ofnode_get_name(dev_ofnode(dev));
|
||||
}
|
||||
|
||||
int dev_read_stringlist_search(struct udevice *dev, const char *property,
|
||||
const char *string)
|
||||
{
|
||||
return ofnode_stringlist_search(dev_ofnode(dev), property, string);
|
||||
}
|
||||
|
||||
int dev_read_string_index(struct udevice *dev, const char *propname, int index,
|
||||
const char **outp)
|
||||
{
|
||||
return ofnode_read_string_index(dev_ofnode(dev), propname, index, outp);
|
||||
}
|
||||
|
||||
int dev_read_string_count(struct udevice *dev, const char *propname)
|
||||
{
|
||||
return ofnode_read_string_count(dev_ofnode(dev), propname);
|
||||
}
|
||||
|
||||
int dev_read_phandle_with_args(struct udevice *dev, const char *list_name,
|
||||
const char *cells_name, int cell_count,
|
||||
int index, struct ofnode_phandle_args *out_args)
|
||||
{
|
||||
return ofnode_parse_phandle_with_args(dev_ofnode(dev), list_name,
|
||||
cells_name, cell_count, index,
|
||||
out_args);
|
||||
}
|
||||
|
||||
int dev_count_phandle_with_args(struct udevice *dev, const char *list_name,
|
||||
const char *cells_name)
|
||||
{
|
||||
return ofnode_count_phandle_with_args(dev_ofnode(dev), list_name,
|
||||
cells_name);
|
||||
}
|
||||
|
||||
int dev_read_addr_cells(struct udevice *dev)
|
||||
{
|
||||
return ofnode_read_addr_cells(dev_ofnode(dev));
|
||||
}
|
||||
|
||||
int dev_read_size_cells(struct udevice *dev)
|
||||
{
|
||||
return ofnode_read_size_cells(dev_ofnode(dev));
|
||||
}
|
||||
|
||||
int dev_read_simple_addr_cells(struct udevice *dev)
|
||||
{
|
||||
return ofnode_read_simple_addr_cells(dev_ofnode(dev));
|
||||
}
|
||||
|
||||
int dev_read_simple_size_cells(struct udevice *dev)
|
||||
{
|
||||
return ofnode_read_simple_size_cells(dev_ofnode(dev));
|
||||
}
|
||||
|
||||
int dev_read_phandle(struct udevice *dev)
|
||||
{
|
||||
ofnode node = dev_ofnode(dev);
|
||||
|
||||
if (ofnode_is_np(node))
|
||||
return ofnode_to_np(node)->phandle;
|
||||
else
|
||||
return fdt_get_phandle(gd->fdt_blob, ofnode_to_offset(node));
|
||||
}
|
||||
|
||||
const void *dev_read_prop(struct udevice *dev, const char *propname, int *lenp)
|
||||
{
|
||||
return ofnode_get_property(dev_ofnode(dev), propname, lenp);
|
||||
}
|
||||
|
||||
int dev_read_alias_seq(struct udevice *dev, int *devnump)
|
||||
{
|
||||
ofnode node = dev_ofnode(dev);
|
||||
const char *uc_name = dev->uclass->uc_drv->name;
|
||||
int ret;
|
||||
|
||||
if (ofnode_is_np(node)) {
|
||||
ret = of_alias_get_id(ofnode_to_np(node), uc_name);
|
||||
if (ret >= 0)
|
||||
*devnump = ret;
|
||||
} else {
|
||||
ret = fdtdec_get_alias_seq(gd->fdt_blob, uc_name,
|
||||
ofnode_to_offset(node), devnump);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int dev_read_u32_array(struct udevice *dev, const char *propname,
|
||||
u32 *out_values, size_t sz)
|
||||
{
|
||||
return ofnode_read_u32_array(dev_ofnode(dev), propname, out_values, sz);
|
||||
}
|
||||
|
||||
const uint8_t *dev_read_u8_array_ptr(struct udevice *dev, const char *propname,
|
||||
size_t sz)
|
||||
{
|
||||
return ofnode_read_u8_array_ptr(dev_ofnode(dev), propname, sz);
|
||||
}
|
||||
|
||||
int dev_read_enabled(struct udevice *dev)
|
||||
{
|
||||
ofnode node = dev_ofnode(dev);
|
||||
|
||||
if (ofnode_is_np(node))
|
||||
return of_device_is_available(ofnode_to_np(node));
|
||||
else
|
||||
return fdtdec_get_is_enabled(gd->fdt_blob,
|
||||
ofnode_to_offset(node));
|
||||
}
|
||||
|
||||
int dev_read_resource(struct udevice *dev, uint index, struct resource *res)
|
||||
{
|
||||
return ofnode_read_resource(dev_ofnode(dev), index, res);
|
||||
}
|
||||
|
||||
int dev_read_resource_byname(struct udevice *dev, const char *name,
|
||||
struct resource *res)
|
||||
{
|
||||
return ofnode_read_resource_byname(dev_ofnode(dev), name, res);
|
||||
}
|
||||
|
||||
u64 dev_translate_address(struct udevice *dev, const fdt32_t *in_addr)
|
||||
{
|
||||
return ofnode_translate_address(dev_ofnode(dev), in_addr);
|
||||
}
|
||||
|
||||
u64 dev_translate_dma_address(struct udevice *dev, const fdt32_t *in_addr)
|
||||
{
|
||||
return ofnode_translate_dma_address(dev_ofnode(dev), in_addr);
|
||||
}
|
||||
|
||||
int dev_read_alias_highest_id(const char *stem)
|
||||
{
|
||||
if (of_live_active())
|
||||
return of_alias_get_highest_id(stem);
|
||||
|
||||
return fdtdec_get_alias_highest_id(gd->fdt_blob, stem);
|
||||
}
|
||||
|
||||
fdt_addr_t dev_read_addr_pci(struct udevice *dev)
|
||||
{
|
||||
ulong addr;
|
||||
|
||||
addr = dev_read_addr(dev);
|
||||
if (addr == FDT_ADDR_T_NONE && !of_live_active())
|
||||
addr = devfdt_get_addr_pci(dev);
|
||||
|
||||
return addr;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (c) 2017 Google, Inc
|
||||
* Written by Simon Glass <sjg@chromium.org>
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
#include <dm/of_addr.h>
|
||||
#include <dm/read.h>
|
||||
#include <linux/ioport.h>
|
||||
|
||||
/* This file can hold non-inlined dev_read_...() functions */
|
||||
@@ -0,0 +1,474 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (c) 2015 Google, Inc
|
||||
* Written by Simon Glass <sjg@chromium.org>
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
#include <errno.h>
|
||||
#include <linux/libfdt.h>
|
||||
#include <malloc.h>
|
||||
#include <mapmem.h>
|
||||
#include <regmap.h>
|
||||
#include <asm/io.h>
|
||||
#include <dm/of_addr.h>
|
||||
#include <linux/ioport.h>
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
||||
/**
|
||||
* regmap_alloc() - Allocate a regmap with a given number of ranges.
|
||||
*
|
||||
* @count: Number of ranges to be allocated for the regmap.
|
||||
* Return: A pointer to the newly allocated regmap, or NULL on error.
|
||||
*/
|
||||
static struct regmap *regmap_alloc(int count)
|
||||
{
|
||||
struct regmap *map;
|
||||
|
||||
map = malloc(sizeof(*map) + sizeof(map->ranges[0]) * count);
|
||||
if (!map)
|
||||
return NULL;
|
||||
map->range_count = count;
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
#if CONFIG_IS_ENABLED(OF_PLATDATA)
|
||||
int regmap_init_mem_platdata(struct udevice *dev, fdt_val_t *reg, int count,
|
||||
struct regmap **mapp)
|
||||
{
|
||||
struct regmap_range *range;
|
||||
struct regmap *map;
|
||||
|
||||
map = regmap_alloc(count);
|
||||
if (!map)
|
||||
return -ENOMEM;
|
||||
|
||||
for (range = map->ranges; count > 0; reg += 2, range++, count--) {
|
||||
range->start = *reg;
|
||||
range->size = reg[1];
|
||||
}
|
||||
|
||||
*mapp = map;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
/**
|
||||
* init_range() - Initialize a single range of a regmap
|
||||
* @node: Device node that will use the map in question
|
||||
* @range: Pointer to a regmap_range structure that will be initialized
|
||||
* @addr_len: The length of the addr parts of the reg property
|
||||
* @size_len: The length of the size parts of the reg property
|
||||
* @index: The index of the range to initialize
|
||||
*
|
||||
* This function will read the necessary 'reg' information from the device tree
|
||||
* (the 'addr' part, and the 'length' part), and initialize the range in
|
||||
* quesion.
|
||||
*
|
||||
* Return: 0 if OK, -ve on error
|
||||
*/
|
||||
static int init_range(ofnode node, struct regmap_range *range, int addr_len,
|
||||
int size_len, int index)
|
||||
{
|
||||
fdt_size_t sz;
|
||||
struct resource r;
|
||||
|
||||
if (of_live_active()) {
|
||||
int ret;
|
||||
|
||||
ret = of_address_to_resource(ofnode_to_np(node),
|
||||
index, &r);
|
||||
if (ret) {
|
||||
debug("%s: Could not read resource of range %d (ret = %d)\n",
|
||||
ofnode_get_name(node), index, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
range->start = r.start;
|
||||
range->size = r.end - r.start + 1;
|
||||
} else {
|
||||
int offset = ofnode_to_offset(node);
|
||||
|
||||
range->start = fdtdec_get_addr_size_fixed(gd->fdt_blob, offset,
|
||||
"reg", index,
|
||||
addr_len, size_len,
|
||||
&sz, true);
|
||||
if (range->start == FDT_ADDR_T_NONE) {
|
||||
debug("%s: Could not read start of range %d\n",
|
||||
ofnode_get_name(node), index);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
range->size = sz;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int regmap_init_mem_index(ofnode node, struct regmap **mapp, int index)
|
||||
{
|
||||
struct regmap *map;
|
||||
int addr_len, size_len;
|
||||
int ret;
|
||||
|
||||
addr_len = ofnode_read_simple_addr_cells(ofnode_get_parent(node));
|
||||
if (addr_len < 0) {
|
||||
debug("%s: Error while reading the addr length (ret = %d)\n",
|
||||
ofnode_get_name(node), addr_len);
|
||||
return addr_len;
|
||||
}
|
||||
|
||||
size_len = ofnode_read_simple_size_cells(ofnode_get_parent(node));
|
||||
if (size_len < 0) {
|
||||
debug("%s: Error while reading the size length: (ret = %d)\n",
|
||||
ofnode_get_name(node), size_len);
|
||||
return size_len;
|
||||
}
|
||||
|
||||
map = regmap_alloc(1);
|
||||
if (!map)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = init_range(node, map->ranges, addr_len, size_len, index);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
if (ofnode_read_bool(node, "little-endian"))
|
||||
map->endianness = REGMAP_LITTLE_ENDIAN;
|
||||
else if (ofnode_read_bool(node, "big-endian"))
|
||||
map->endianness = REGMAP_BIG_ENDIAN;
|
||||
else if (ofnode_read_bool(node, "native-endian"))
|
||||
map->endianness = REGMAP_NATIVE_ENDIAN;
|
||||
else /* Default: native endianness */
|
||||
map->endianness = REGMAP_NATIVE_ENDIAN;
|
||||
|
||||
*mapp = map;
|
||||
|
||||
return 0;
|
||||
err:
|
||||
regmap_uninit(map);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int regmap_init_mem(ofnode node, struct regmap **mapp)
|
||||
{
|
||||
struct regmap_range *range;
|
||||
struct regmap *map;
|
||||
int count;
|
||||
int addr_len, size_len, both_len;
|
||||
int len;
|
||||
int index;
|
||||
int ret;
|
||||
|
||||
addr_len = ofnode_read_simple_addr_cells(ofnode_get_parent(node));
|
||||
if (addr_len < 0) {
|
||||
debug("%s: Error while reading the addr length (ret = %d)\n",
|
||||
ofnode_get_name(node), addr_len);
|
||||
return addr_len;
|
||||
}
|
||||
|
||||
size_len = ofnode_read_simple_size_cells(ofnode_get_parent(node));
|
||||
if (size_len < 0) {
|
||||
debug("%s: Error while reading the size length: (ret = %d)\n",
|
||||
ofnode_get_name(node), size_len);
|
||||
return size_len;
|
||||
}
|
||||
|
||||
both_len = addr_len + size_len;
|
||||
if (!both_len) {
|
||||
debug("%s: Both addr and size length are zero\n",
|
||||
ofnode_get_name(node));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
len = ofnode_read_size(node, "reg");
|
||||
if (len < 0) {
|
||||
debug("%s: Error while reading reg size (ret = %d)\n",
|
||||
ofnode_get_name(node), len);
|
||||
return len;
|
||||
}
|
||||
len /= sizeof(fdt32_t);
|
||||
count = len / both_len;
|
||||
if (!count) {
|
||||
debug("%s: Not enough data in reg property\n",
|
||||
ofnode_get_name(node));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
map = regmap_alloc(count);
|
||||
if (!map)
|
||||
return -ENOMEM;
|
||||
|
||||
for (range = map->ranges, index = 0; count > 0;
|
||||
count--, range++, index++) {
|
||||
ret = init_range(node, range, addr_len, size_len, index);
|
||||
if (ret)
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (ofnode_read_bool(node, "little-endian"))
|
||||
map->endianness = REGMAP_LITTLE_ENDIAN;
|
||||
else if (ofnode_read_bool(node, "big-endian"))
|
||||
map->endianness = REGMAP_BIG_ENDIAN;
|
||||
else if (ofnode_read_bool(node, "native-endian"))
|
||||
map->endianness = REGMAP_NATIVE_ENDIAN;
|
||||
else /* Default: native endianness */
|
||||
map->endianness = REGMAP_NATIVE_ENDIAN;
|
||||
|
||||
*mapp = map;
|
||||
|
||||
return 0;
|
||||
err:
|
||||
regmap_uninit(map);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
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->ranges[range_num];
|
||||
|
||||
return map_sysmem(range->start, range->size);
|
||||
}
|
||||
|
||||
int regmap_uninit(struct regmap *map)
|
||||
{
|
||||
free(map);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u8 __read_8(u8 *addr, enum regmap_endianness_t endianness)
|
||||
{
|
||||
return readb(addr);
|
||||
}
|
||||
|
||||
static inline u16 __read_16(u16 *addr, enum regmap_endianness_t endianness)
|
||||
{
|
||||
switch (endianness) {
|
||||
case REGMAP_LITTLE_ENDIAN:
|
||||
return in_le16(addr);
|
||||
case REGMAP_BIG_ENDIAN:
|
||||
return in_be16(addr);
|
||||
case REGMAP_NATIVE_ENDIAN:
|
||||
return readw(addr);
|
||||
}
|
||||
|
||||
return readw(addr);
|
||||
}
|
||||
|
||||
static inline u32 __read_32(u32 *addr, enum regmap_endianness_t endianness)
|
||||
{
|
||||
switch (endianness) {
|
||||
case REGMAP_LITTLE_ENDIAN:
|
||||
return in_le32(addr);
|
||||
case REGMAP_BIG_ENDIAN:
|
||||
return in_be32(addr);
|
||||
case REGMAP_NATIVE_ENDIAN:
|
||||
return readl(addr);
|
||||
}
|
||||
|
||||
return readl(addr);
|
||||
}
|
||||
|
||||
#if defined(in_le64) && defined(in_be64) && defined(readq)
|
||||
static inline u64 __read_64(u64 *addr, enum regmap_endianness_t endianness)
|
||||
{
|
||||
switch (endianness) {
|
||||
case REGMAP_LITTLE_ENDIAN:
|
||||
return in_le64(addr);
|
||||
case REGMAP_BIG_ENDIAN:
|
||||
return in_be64(addr);
|
||||
case REGMAP_NATIVE_ENDIAN:
|
||||
return readq(addr);
|
||||
}
|
||||
|
||||
return readq(addr);
|
||||
}
|
||||
#endif
|
||||
|
||||
int regmap_raw_read_range(struct regmap *map, uint range_num, uint offset,
|
||||
void *valp, size_t val_len)
|
||||
{
|
||||
struct regmap_range *range;
|
||||
void *ptr;
|
||||
|
||||
if (range_num >= map->range_count) {
|
||||
debug("%s: range index %d larger than range count\n",
|
||||
__func__, range_num);
|
||||
return -ERANGE;
|
||||
}
|
||||
range = &map->ranges[range_num];
|
||||
|
||||
ptr = map_physmem(range->start + offset, val_len, MAP_NOCACHE);
|
||||
|
||||
if (offset + val_len > range->size) {
|
||||
debug("%s: offset/size combination invalid\n", __func__);
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
switch (val_len) {
|
||||
case REGMAP_SIZE_8:
|
||||
*((u8 *)valp) = __read_8(ptr, map->endianness);
|
||||
break;
|
||||
case REGMAP_SIZE_16:
|
||||
*((u16 *)valp) = __read_16(ptr, map->endianness);
|
||||
break;
|
||||
case REGMAP_SIZE_32:
|
||||
*((u32 *)valp) = __read_32(ptr, map->endianness);
|
||||
break;
|
||||
#if defined(in_le64) && defined(in_be64) && defined(readq)
|
||||
case REGMAP_SIZE_64:
|
||||
*((u64 *)valp) = __read_64(ptr, map->endianness);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
debug("%s: regmap size %zu unknown\n", __func__, val_len);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int regmap_raw_read(struct regmap *map, uint offset, void *valp, size_t val_len)
|
||||
{
|
||||
return regmap_raw_read_range(map, 0, offset, valp, val_len);
|
||||
}
|
||||
|
||||
int regmap_read(struct regmap *map, uint offset, uint *valp)
|
||||
{
|
||||
return regmap_raw_read(map, offset, valp, REGMAP_SIZE_32);
|
||||
}
|
||||
|
||||
static inline void __write_8(u8 *addr, const u8 *val,
|
||||
enum regmap_endianness_t endianness)
|
||||
{
|
||||
writeb(*val, addr);
|
||||
}
|
||||
|
||||
static inline void __write_16(u16 *addr, const u16 *val,
|
||||
enum regmap_endianness_t endianness)
|
||||
{
|
||||
switch (endianness) {
|
||||
case REGMAP_NATIVE_ENDIAN:
|
||||
writew(*val, addr);
|
||||
break;
|
||||
case REGMAP_LITTLE_ENDIAN:
|
||||
out_le16(addr, *val);
|
||||
break;
|
||||
case REGMAP_BIG_ENDIAN:
|
||||
out_be16(addr, *val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void __write_32(u32 *addr, const u32 *val,
|
||||
enum regmap_endianness_t endianness)
|
||||
{
|
||||
switch (endianness) {
|
||||
case REGMAP_NATIVE_ENDIAN:
|
||||
writel(*val, addr);
|
||||
break;
|
||||
case REGMAP_LITTLE_ENDIAN:
|
||||
out_le32(addr, *val);
|
||||
break;
|
||||
case REGMAP_BIG_ENDIAN:
|
||||
out_be32(addr, *val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(out_le64) && defined(out_be64) && defined(writeq)
|
||||
static inline void __write_64(u64 *addr, const u64 *val,
|
||||
enum regmap_endianness_t endianness)
|
||||
{
|
||||
switch (endianness) {
|
||||
case REGMAP_NATIVE_ENDIAN:
|
||||
writeq(*val, addr);
|
||||
break;
|
||||
case REGMAP_LITTLE_ENDIAN:
|
||||
out_le64(addr, *val);
|
||||
break;
|
||||
case REGMAP_BIG_ENDIAN:
|
||||
out_be64(addr, *val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int regmap_raw_write_range(struct regmap *map, uint range_num, uint offset,
|
||||
const void *val, size_t val_len)
|
||||
{
|
||||
struct regmap_range *range;
|
||||
void *ptr;
|
||||
|
||||
if (range_num >= map->range_count) {
|
||||
debug("%s: range index %d larger than range count\n",
|
||||
__func__, range_num);
|
||||
return -ERANGE;
|
||||
}
|
||||
range = &map->ranges[range_num];
|
||||
|
||||
ptr = map_physmem(range->start + offset, val_len, MAP_NOCACHE);
|
||||
|
||||
if (offset + val_len > range->size) {
|
||||
debug("%s: offset/size combination invalid\n", __func__);
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
switch (val_len) {
|
||||
case REGMAP_SIZE_8:
|
||||
__write_8(ptr, val, map->endianness);
|
||||
break;
|
||||
case REGMAP_SIZE_16:
|
||||
__write_16(ptr, val, map->endianness);
|
||||
break;
|
||||
case REGMAP_SIZE_32:
|
||||
__write_32(ptr, val, map->endianness);
|
||||
break;
|
||||
#if defined(out_le64) && defined(out_be64) && defined(writeq)
|
||||
case REGMAP_SIZE_64:
|
||||
__write_64(ptr, val, map->endianness);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
debug("%s: regmap size %zu unknown\n", __func__, val_len);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int regmap_raw_write(struct regmap *map, uint offset, const void *val,
|
||||
size_t val_len)
|
||||
{
|
||||
return regmap_raw_write_range(map, 0, offset, val, val_len);
|
||||
}
|
||||
|
||||
int regmap_write(struct regmap *map, uint offset, uint val)
|
||||
{
|
||||
return regmap_raw_write(map, offset, &val, REGMAP_SIZE_32);
|
||||
}
|
||||
|
||||
int regmap_update_bits(struct regmap *map, uint offset, uint mask, uint val)
|
||||
{
|
||||
uint reg;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(map, offset, ®);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
reg &= ~mask;
|
||||
|
||||
return regmap_write(map, offset, reg | (val & mask));
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (c) 2013 Google, Inc
|
||||
*
|
||||
* (C) Copyright 2012
|
||||
* Pavel Herrmann <morpheus.ibis@gmail.com>
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <errno.h>
|
||||
#include <fdtdec.h>
|
||||
#include <malloc.h>
|
||||
#include <linux/libfdt.h>
|
||||
#include <dm/device.h>
|
||||
#include <dm/device-internal.h>
|
||||
#include <dm/lists.h>
|
||||
#include <dm/of.h>
|
||||
#include <dm/of_access.h>
|
||||
#include <dm/platdata.h>
|
||||
#include <dm/read.h>
|
||||
#include <dm/root.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 udevice *dm_root(void)
|
||||
{
|
||||
if (!gd->dm_root) {
|
||||
dm_warn("Virtual root driver does not exist!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return gd->dm_root;
|
||||
}
|
||||
|
||||
void dm_fixup_for_gd_move(struct global_data *new_gd)
|
||||
{
|
||||
/* The sentinel node has moved, so update things that point to it */
|
||||
if (gd->dm_root) {
|
||||
new_gd->uclass_root.next->prev = &new_gd->uclass_root;
|
||||
new_gd->uclass_root.prev->next = &new_gd->uclass_root;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(CONFIG_NEEDS_MANUAL_RELOC)
|
||||
void fix_drivers(void)
|
||||
{
|
||||
struct driver *drv =
|
||||
ll_entry_start(struct driver, driver);
|
||||
const int n_ents = ll_entry_count(struct driver, driver);
|
||||
struct driver *entry;
|
||||
|
||||
for (entry = drv; entry != drv + n_ents; entry++) {
|
||||
if (entry->of_match)
|
||||
entry->of_match = (const struct udevice_id *)
|
||||
((u32)entry->of_match + gd->reloc_off);
|
||||
if (entry->bind)
|
||||
entry->bind += gd->reloc_off;
|
||||
if (entry->probe)
|
||||
entry->probe += gd->reloc_off;
|
||||
if (entry->remove)
|
||||
entry->remove += gd->reloc_off;
|
||||
if (entry->unbind)
|
||||
entry->unbind += gd->reloc_off;
|
||||
if (entry->ofdata_to_platdata)
|
||||
entry->ofdata_to_platdata += gd->reloc_off;
|
||||
if (entry->child_post_bind)
|
||||
entry->child_post_bind += gd->reloc_off;
|
||||
if (entry->child_pre_probe)
|
||||
entry->child_pre_probe += gd->reloc_off;
|
||||
if (entry->child_post_remove)
|
||||
entry->child_post_remove += gd->reloc_off;
|
||||
/* OPS are fixed in every uclass post_probe function */
|
||||
if (entry->ops)
|
||||
entry->ops += gd->reloc_off;
|
||||
}
|
||||
}
|
||||
|
||||
void fix_uclass(void)
|
||||
{
|
||||
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;
|
||||
|
||||
for (entry = uclass; entry != uclass + n_ents; entry++) {
|
||||
if (entry->post_bind)
|
||||
entry->post_bind += gd->reloc_off;
|
||||
if (entry->pre_unbind)
|
||||
entry->pre_unbind += gd->reloc_off;
|
||||
if (entry->pre_probe)
|
||||
entry->pre_probe += gd->reloc_off;
|
||||
if (entry->post_probe)
|
||||
entry->post_probe += gd->reloc_off;
|
||||
if (entry->pre_remove)
|
||||
entry->pre_remove += gd->reloc_off;
|
||||
if (entry->child_post_bind)
|
||||
entry->child_post_bind += gd->reloc_off;
|
||||
if (entry->child_pre_probe)
|
||||
entry->child_pre_probe += gd->reloc_off;
|
||||
if (entry->init)
|
||||
entry->init += gd->reloc_off;
|
||||
if (entry->destroy)
|
||||
entry->destroy += gd->reloc_off;
|
||||
/* FIXME maybe also need to fix these ops */
|
||||
if (entry->ops)
|
||||
entry->ops += gd->reloc_off;
|
||||
}
|
||||
}
|
||||
|
||||
void fix_devices(void)
|
||||
{
|
||||
struct driver_info *dev =
|
||||
ll_entry_start(struct driver_info, driver_info);
|
||||
const int n_ents = ll_entry_count(struct driver_info, driver_info);
|
||||
struct driver_info *entry;
|
||||
|
||||
for (entry = dev; entry != dev + n_ents; entry++) {
|
||||
if (entry->platdata)
|
||||
entry->platdata += gd->reloc_off;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int dm_init(bool of_live)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (gd->dm_root) {
|
||||
dm_warn("Virtual root driver already exists!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
INIT_LIST_HEAD(&DM_UCLASS_ROOT_NON_CONST);
|
||||
|
||||
#if defined(CONFIG_NEEDS_MANUAL_RELOC)
|
||||
fix_drivers();
|
||||
fix_uclass();
|
||||
fix_devices();
|
||||
#endif
|
||||
|
||||
ret = device_bind_by_name(NULL, false, &root_info, &DM_ROOT_NON_CONST);
|
||||
if (ret)
|
||||
return ret;
|
||||
#if CONFIG_IS_ENABLED(OF_CONTROL)
|
||||
# if CONFIG_IS_ENABLED(OF_LIVE)
|
||||
if (of_live)
|
||||
DM_ROOT_NON_CONST->node = np_to_ofnode(gd->of_root);
|
||||
else
|
||||
#endif
|
||||
DM_ROOT_NON_CONST->node = offset_to_ofnode(0);
|
||||
#endif
|
||||
ret = device_probe(DM_ROOT_NON_CONST);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dm_uninit(void)
|
||||
{
|
||||
device_remove(dm_root(), DM_REMOVE_NORMAL);
|
||||
device_unbind(dm_root());
|
||||
gd->dm_root = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if CONFIG_IS_ENABLED(DM_DEVICE_REMOVE)
|
||||
int dm_remove_devices_flags(uint flags)
|
||||
{
|
||||
device_remove(dm_root(), flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int dm_scan_platdata(bool pre_reloc_only)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = lists_bind_drivers(DM_ROOT_NON_CONST, pre_reloc_only);
|
||||
if (ret == -ENOENT) {
|
||||
dm_warn("Some drivers were not found\n");
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if CONFIG_IS_ENABLED(OF_LIVE)
|
||||
static int dm_scan_fdt_live(struct udevice *parent,
|
||||
const struct device_node *node_parent,
|
||||
bool pre_reloc_only)
|
||||
{
|
||||
struct device_node *np;
|
||||
int ret = 0, err;
|
||||
|
||||
for (np = node_parent->child; np; np = np->sibling) {
|
||||
/* "chosen" node isn't a device itself but may contain some: */
|
||||
if (!strcmp(np->name, "chosen")) {
|
||||
pr_debug("parsing subnodes of \"chosen\"\n");
|
||||
|
||||
err = dm_scan_fdt_live(parent, np, pre_reloc_only);
|
||||
if (err && !ret)
|
||||
ret = err;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!of_device_is_available(np)) {
|
||||
pr_debug(" - ignoring disabled device\n");
|
||||
continue;
|
||||
}
|
||||
err = lists_bind_fdt(parent, np_to_ofnode(np), NULL,
|
||||
pre_reloc_only);
|
||||
if (err && !ret) {
|
||||
ret = err;
|
||||
debug("%s: ret=%d\n", np->name, ret);
|
||||
}
|
||||
}
|
||||
|
||||
if (ret)
|
||||
dm_warn("Some drivers failed to bind\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif /* CONFIG_IS_ENABLED(OF_LIVE) */
|
||||
|
||||
#if CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)
|
||||
/**
|
||||
* dm_scan_fdt_node() - Scan the device tree and bind drivers for a node
|
||||
*
|
||||
* This scans the subnodes of a device tree node and and creates a driver
|
||||
* for each one.
|
||||
*
|
||||
* @parent: Parent device for the devices that will be created
|
||||
* @blob: Pointer to device tree blob
|
||||
* @offset: Offset of node to scan
|
||||
* @pre_reloc_only: If true, bind only drivers with the DM_FLAG_PRE_RELOC
|
||||
* flag. If false bind all drivers.
|
||||
* @return 0 if OK, -ve on error
|
||||
*/
|
||||
static int dm_scan_fdt_node(struct udevice *parent, const void *blob,
|
||||
int offset, bool pre_reloc_only)
|
||||
{
|
||||
int ret = 0, err;
|
||||
|
||||
for (offset = fdt_first_subnode(blob, offset);
|
||||
offset > 0;
|
||||
offset = fdt_next_subnode(blob, offset)) {
|
||||
const char *node_name = fdt_get_name(blob, offset, NULL);
|
||||
|
||||
/*
|
||||
* The "chosen" and "firmware" nodes aren't devices
|
||||
* themselves but may contain some:
|
||||
*/
|
||||
if (!strcmp(node_name, "chosen") ||
|
||||
!strcmp(node_name, "firmware")) {
|
||||
pr_debug("parsing subnodes of \"%s\"\n", node_name);
|
||||
|
||||
err = dm_scan_fdt_node(parent, blob, offset,
|
||||
pre_reloc_only);
|
||||
if (err && !ret)
|
||||
ret = err;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!fdtdec_get_is_enabled(blob, offset)) {
|
||||
pr_debug(" - ignoring disabled device\n");
|
||||
continue;
|
||||
}
|
||||
err = lists_bind_fdt(parent, offset_to_ofnode(offset), NULL,
|
||||
pre_reloc_only);
|
||||
if (err && !ret) {
|
||||
ret = err;
|
||||
debug("%s: ret=%d\n", node_name, ret);
|
||||
}
|
||||
}
|
||||
|
||||
if (ret)
|
||||
dm_warn("Some drivers failed to bind\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int dm_scan_fdt_dev(struct udevice *dev)
|
||||
{
|
||||
if (!dev_of_valid(dev))
|
||||
return 0;
|
||||
|
||||
#if CONFIG_IS_ENABLED(OF_LIVE)
|
||||
if (of_live_active())
|
||||
return dm_scan_fdt_live(dev, dev_np(dev),
|
||||
gd->flags & GD_FLG_RELOC ? false : true);
|
||||
else
|
||||
#endif
|
||||
return dm_scan_fdt_node(dev, gd->fdt_blob, dev_of_offset(dev),
|
||||
gd->flags & GD_FLG_RELOC ? false : true);
|
||||
}
|
||||
|
||||
int dm_scan_fdt(const void *blob, bool pre_reloc_only)
|
||||
{
|
||||
#if CONFIG_IS_ENABLED(OF_LIVE)
|
||||
if (of_live_active())
|
||||
return dm_scan_fdt_live(gd->dm_root, gd->of_root,
|
||||
pre_reloc_only);
|
||||
else
|
||||
#endif
|
||||
return dm_scan_fdt_node(gd->dm_root, blob, 0, pre_reloc_only);
|
||||
}
|
||||
|
||||
static int dm_scan_fdt_ofnode_path(const char *path, bool pre_reloc_only)
|
||||
{
|
||||
ofnode node;
|
||||
|
||||
node = ofnode_path(path);
|
||||
if (!ofnode_valid(node))
|
||||
return 0;
|
||||
|
||||
#if CONFIG_IS_ENABLED(OF_LIVE)
|
||||
if (of_live_active())
|
||||
return dm_scan_fdt_live(gd->dm_root, node.np, pre_reloc_only);
|
||||
#endif
|
||||
return dm_scan_fdt_node(gd->dm_root, gd->fdt_blob, node.of_offset,
|
||||
pre_reloc_only);
|
||||
}
|
||||
|
||||
int dm_extended_scan_fdt(const void *blob, bool pre_reloc_only)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = dm_scan_fdt(blob, pre_reloc_only);
|
||||
if (ret) {
|
||||
debug("dm_scan_fdt() failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = dm_scan_fdt_ofnode_path("/clocks", pre_reloc_only);
|
||||
if (ret) {
|
||||
debug("scan for /clocks failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = dm_scan_fdt_ofnode_path("/firmware", pre_reloc_only);
|
||||
if (ret)
|
||||
debug("scan for /firmware failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
__weak int dm_scan_other(bool pre_reloc_only)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dm_init_and_scan(bool pre_reloc_only)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = dm_init(IS_ENABLED(CONFIG_OF_LIVE));
|
||||
if (ret) {
|
||||
debug("dm_init() failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
ret = dm_scan_platdata(pre_reloc_only);
|
||||
if (ret) {
|
||||
debug("dm_scan_platdata() failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)) {
|
||||
ret = dm_extended_scan_fdt(gd->fdt_blob, pre_reloc_only);
|
||||
if (ret) {
|
||||
debug("dm_extended_scan_dt() failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = dm_scan_other(pre_reloc_only);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 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,
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (c) 2014 Google, Inc
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
|
||||
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)
|
||||
{
|
||||
#if CONFIG_IS_ENABLED(OF_PLATDATA)
|
||||
return 0;
|
||||
#else
|
||||
u32 cell[3];
|
||||
int ret;
|
||||
|
||||
ret = dev_read_u32_array(dev, "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_dev(dev);
|
||||
#endif
|
||||
}
|
||||
|
||||
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[] = {
|
||||
{ .compatible = "simple-bus" },
|
||||
{ .compatible = "simple-mfd" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(simple_bus_drv) = {
|
||||
.name = "generic_simple_bus",
|
||||
.id = UCLASS_SIMPLE_BUS,
|
||||
.of_match = generic_simple_bus_ids,
|
||||
.flags = DM_FLAG_PRE_RELOC,
|
||||
};
|
||||
@@ -0,0 +1,215 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2015 Google, Inc
|
||||
* Written by Simon Glass <sjg@chromium.org>
|
||||
*/
|
||||
|
||||
#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>
|
||||
|
||||
/*
|
||||
* Caution:
|
||||
* This API requires the given device has alerady been bound to syscon driver.
|
||||
* For example,
|
||||
* compatible = "syscon", "simple-mfd";
|
||||
* works, but
|
||||
* compatible = "simple-mfd", "syscon";
|
||||
* does not. The behavior is different from Linux.
|
||||
*/
|
||||
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);
|
||||
|
||||
/* Special case for PCI devices, which don't have a regmap */
|
||||
if (device_get_uclass_id(dev->parent) == UCLASS_PCI)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* With OF_PLATDATA we really have no way of knowing the format of
|
||||
* the device-specific platform data. So we assume that it starts with
|
||||
* a 'reg' member, and this holds a single address and size. Drivers
|
||||
* using OF_PLATDATA will need to ensure that this is true.
|
||||
*/
|
||||
#if CONFIG_IS_ENABLED(OF_PLATDATA)
|
||||
struct syscon_base_platdata *plat = dev_get_platdata(dev);
|
||||
|
||||
return regmap_init_mem_platdata(dev, plat->reg, ARRAY_SIZE(plat->reg),
|
||||
&priv->regmap);
|
||||
#else
|
||||
return regmap_init_mem(dev_ofnode(dev), &priv->regmap);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int syscon_probe_by_ofnode(ofnode node, struct udevice **devp)
|
||||
{
|
||||
struct udevice *dev, *parent;
|
||||
int ret;
|
||||
|
||||
/* found node with "syscon" compatible, not bounded to SYSCON UCLASS */
|
||||
if (!ofnode_device_is_compatible(node, "syscon")) {
|
||||
dev_dbg(dev, "invalid compatible for syscon device\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* bound to driver with same ofnode or to root if not found */
|
||||
if (device_find_global_by_ofnode(node, &parent))
|
||||
parent = dm_root();
|
||||
|
||||
/* force bound to syscon class */
|
||||
ret = device_bind_driver_to_node(parent, "syscon",
|
||||
ofnode_get_name(node),
|
||||
node, &dev);
|
||||
if (ret) {
|
||||
dev_dbg(dev, "unable to bound syscon device\n");
|
||||
return ret;
|
||||
}
|
||||
ret = device_probe(dev);
|
||||
if (ret) {
|
||||
dev_dbg(dev, "unable to probe syscon device\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
*devp = dev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct regmap *syscon_regmap_lookup_by_phandle(struct udevice *dev,
|
||||
const char *name)
|
||||
{
|
||||
struct udevice *syscon;
|
||||
struct regmap *r;
|
||||
u32 phandle;
|
||||
ofnode node;
|
||||
int err;
|
||||
|
||||
err = uclass_get_device_by_phandle(UCLASS_SYSCON, dev,
|
||||
name, &syscon);
|
||||
if (err) {
|
||||
/* found node with "syscon" compatible, not bounded to SYSCON */
|
||||
err = ofnode_read_u32(dev_ofnode(dev), name, &phandle);
|
||||
if (err)
|
||||
return ERR_PTR(err);
|
||||
|
||||
node = ofnode_get_by_phandle(phandle);
|
||||
if (!ofnode_valid(node)) {
|
||||
dev_dbg(dev, "unable to find syscon device\n");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
err = syscon_probe_by_ofnode(node, &syscon);
|
||||
if (err)
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
r = syscon_get_regmap(syscon);
|
||||
if (!r) {
|
||||
dev_dbg(dev, "unable to find regmap\n");
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int syscon_get_by_driver_data(ulong driver_data, struct udevice **devp)
|
||||
{
|
||||
struct udevice *dev;
|
||||
struct uclass *uc;
|
||||
int ret;
|
||||
|
||||
*devp = NULL;
|
||||
ret = uclass_get(UCLASS_SYSCON, &uc);
|
||||
if (ret)
|
||||
return ret;
|
||||
uclass_foreach_dev(dev, uc) {
|
||||
if (dev->driver_data == driver_data) {
|
||||
*devp = dev;
|
||||
return device_probe(dev);
|
||||
}
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
struct regmap *syscon_get_regmap_by_driver_data(ulong driver_data)
|
||||
{
|
||||
struct syscon_uc_info *priv;
|
||||
struct udevice *dev;
|
||||
int ret;
|
||||
|
||||
ret = syscon_get_by_driver_data(driver_data, &dev);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
priv = dev_get_uclass_priv(dev);
|
||||
|
||||
return priv->regmap;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
static const struct udevice_id generic_syscon_ids[] = {
|
||||
{ .compatible = "syscon" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(generic_syscon) = {
|
||||
.name = "syscon",
|
||||
.id = UCLASS_SYSCON,
|
||||
#if !CONFIG_IS_ENABLED(OF_PLATDATA)
|
||||
.bind = dm_scan_fdt_dev,
|
||||
#endif
|
||||
.of_match = generic_syscon_ids,
|
||||
};
|
||||
|
||||
/*
|
||||
* Linux-compatible syscon-to-regmap
|
||||
* The syscon node can be bound to another driver, but still works
|
||||
* as a syscon provider.
|
||||
*/
|
||||
struct regmap *syscon_node_to_regmap(ofnode node)
|
||||
{
|
||||
struct udevice *dev;
|
||||
struct regmap *r;
|
||||
|
||||
if (uclass_get_device_by_ofnode(UCLASS_SYSCON, node, &dev))
|
||||
if (syscon_probe_by_ofnode(node, &dev))
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
r = syscon_get_regmap(dev);
|
||||
if (!r) {
|
||||
dev_dbg(dev, "unable to find regmap\n");
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
@@ -0,0 +1,775 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (c) 2013 Google, Inc
|
||||
*
|
||||
* (C) Copyright 2012
|
||||
* Pavel Herrmann <morpheus.ibis@gmail.com>
|
||||
*/
|
||||
|
||||
#define LOG_CATEGORY LOGC_DM
|
||||
|
||||
#include <common.h>
|
||||
#include <dm.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;
|
||||
|
||||
if (!gd->dm_root)
|
||||
return NULL;
|
||||
/*
|
||||
* 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) {
|
||||
debug("Cannot find uclass for id %d: please add the UCLASS_DRIVER() declaration for this UCLASS_... id\n",
|
||||
id);
|
||||
/*
|
||||
* Use a strange error to make this case easier to find. When
|
||||
* a uclass is not available it can prevent driver model from
|
||||
* starting up and this failure is otherwise hard to debug.
|
||||
*/
|
||||
return -EPFNOSUPPORT;
|
||||
}
|
||||
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, &DM_UCLASS_ROOT_NON_CONST);
|
||||
|
||||
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 udevice *dev;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* We cannot use list_for_each_entry_safe() here. If a device in this
|
||||
* uclass has a child device also in this uclass, it will be also be
|
||||
* unbound (by the recursion in the call to device_unbind() below).
|
||||
* We can loop until the list is empty.
|
||||
*/
|
||||
while (!list_empty(&uc->dev_head)) {
|
||||
dev = list_first_entry(&uc->dev_head, struct udevice,
|
||||
uclass_node);
|
||||
ret = device_remove(dev, DM_REMOVE_NORMAL);
|
||||
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;
|
||||
}
|
||||
|
||||
const char *uclass_get_name(enum uclass_id id)
|
||||
{
|
||||
struct uclass *uc;
|
||||
|
||||
if (uclass_get(id, &uc))
|
||||
return NULL;
|
||||
return uc->uc_drv->name;
|
||||
}
|
||||
|
||||
enum uclass_id uclass_get_by_name(const char *name)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < UCLASS_COUNT; i++) {
|
||||
struct uclass_driver *uc_drv = lists_uclass_lookup(i);
|
||||
|
||||
if (uc_drv && !strcmp(uc_drv->name, name))
|
||||
return i;
|
||||
}
|
||||
|
||||
return UCLASS_INVALID;
|
||||
}
|
||||
|
||||
int dev_get_uclass_index(struct udevice *dev, struct uclass **ucp)
|
||||
{
|
||||
struct udevice *iter;
|
||||
struct uclass *uc = dev->uclass;
|
||||
int i = 0;
|
||||
|
||||
if (list_empty(&uc->dev_head))
|
||||
return -ENODEV;
|
||||
|
||||
uclass_foreach_dev(iter, uc) {
|
||||
if (iter == dev) {
|
||||
if (ucp)
|
||||
*ucp = uc;
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
int uclass_find_device(enum uclass_id id, int index, struct udevice **devp)
|
||||
{
|
||||
struct uclass *uc;
|
||||
struct udevice *dev;
|
||||
int ret;
|
||||
|
||||
*devp = NULL;
|
||||
ret = uclass_get(id, &uc);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (list_empty(&uc->dev_head))
|
||||
return -ENODEV;
|
||||
|
||||
uclass_foreach_dev(dev, uc) {
|
||||
if (!index--) {
|
||||
*devp = dev;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
int uclass_find_first_device(enum uclass_id id, struct udevice **devp)
|
||||
{
|
||||
struct uclass *uc;
|
||||
int ret;
|
||||
|
||||
*devp = NULL;
|
||||
ret = uclass_get(id, &uc);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (list_empty(&uc->dev_head))
|
||||
return 0;
|
||||
|
||||
*devp = list_first_entry(&uc->dev_head, struct udevice, uclass_node);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uclass_find_next_device(struct udevice **devp)
|
||||
{
|
||||
struct udevice *dev = *devp;
|
||||
|
||||
*devp = NULL;
|
||||
if (list_is_last(&dev->uclass_node, &dev->uclass->dev_head))
|
||||
return 0;
|
||||
|
||||
*devp = list_entry(dev->uclass_node.next, struct udevice, uclass_node);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uclass_find_device_by_name(enum uclass_id id, const char *name,
|
||||
struct udevice **devp)
|
||||
{
|
||||
struct uclass *uc;
|
||||
struct udevice *dev;
|
||||
int ret;
|
||||
|
||||
*devp = NULL;
|
||||
if (!name)
|
||||
return -EINVAL;
|
||||
ret = uclass_get(id, &uc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
uclass_foreach_dev(dev, uc) {
|
||||
if (!strcmp(dev->name, name)) {
|
||||
*devp = dev;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
#if !CONFIG_IS_ENABLED(OF_CONTROL) || \
|
||||
CONFIG_IS_ENABLED(OF_PLATDATA) || \
|
||||
CONFIG_IS_ENABLED(OF_PRIOR_STAGE)
|
||||
int uclass_find_next_free_req_seq(enum uclass_id id)
|
||||
{
|
||||
struct uclass *uc;
|
||||
struct udevice *dev;
|
||||
int ret;
|
||||
int max = -1;
|
||||
|
||||
ret = uclass_get(id, &uc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
list_for_each_entry(dev, &uc->dev_head, uclass_node) {
|
||||
if ((dev->req_seq != -1) && (dev->req_seq > max))
|
||||
max = dev->req_seq;
|
||||
}
|
||||
|
||||
if (max == -1)
|
||||
return 0;
|
||||
|
||||
return max + 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
int uclass_find_device_by_seq(enum uclass_id id, int seq_or_req_seq,
|
||||
bool find_req_seq, struct udevice **devp)
|
||||
{
|
||||
struct uclass *uc;
|
||||
struct udevice *dev;
|
||||
int ret;
|
||||
|
||||
*devp = NULL;
|
||||
log_debug("%d %d\n", find_req_seq, seq_or_req_seq);
|
||||
if (seq_or_req_seq == -1)
|
||||
return -ENODEV;
|
||||
ret = uclass_get(id, &uc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
uclass_foreach_dev(dev, uc) {
|
||||
log_debug(" - %d %d '%s'\n",
|
||||
dev->req_seq, dev->seq, dev->name);
|
||||
if ((find_req_seq ? dev->req_seq : dev->seq) ==
|
||||
seq_or_req_seq) {
|
||||
*devp = dev;
|
||||
log_debug(" - found\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
log_debug(" - not found\n");
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
int uclass_find_device_by_of_offset(enum uclass_id id, int node,
|
||||
struct udevice **devp)
|
||||
{
|
||||
struct uclass *uc;
|
||||
struct udevice *dev;
|
||||
int ret;
|
||||
|
||||
*devp = NULL;
|
||||
if (node < 0)
|
||||
return -ENODEV;
|
||||
ret = uclass_get(id, &uc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
uclass_foreach_dev(dev, uc) {
|
||||
if (dev_of_offset(dev) == node) {
|
||||
*devp = dev;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
int uclass_find_device_by_ofnode(enum uclass_id id, ofnode node,
|
||||
struct udevice **devp)
|
||||
{
|
||||
struct uclass *uc;
|
||||
struct udevice *dev;
|
||||
int ret;
|
||||
|
||||
log(LOGC_DM, LOGL_DEBUG, "Looking for %s\n", ofnode_get_name(node));
|
||||
*devp = NULL;
|
||||
if (!ofnode_valid(node))
|
||||
return -ENODEV;
|
||||
ret = uclass_get(id, &uc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
uclass_foreach_dev(dev, uc) {
|
||||
log(LOGC_DM, LOGL_DEBUG_CONTENT, " - checking %s\n",
|
||||
dev->name);
|
||||
if (ofnode_equal(dev_ofnode(dev), node)) {
|
||||
*devp = dev;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
ret = -ENODEV;
|
||||
|
||||
done:
|
||||
log(LOGC_DM, LOGL_DEBUG, " - result for %s: %s (ret=%d)\n",
|
||||
ofnode_get_name(node), *devp ? (*devp)->name : "(none)", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if CONFIG_IS_ENABLED(OF_CONTROL)
|
||||
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 = dev_read_u32_default(parent, name, -1);
|
||||
if (find_phandle <= 0)
|
||||
return -ENOENT;
|
||||
ret = uclass_get(id, &uc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
uclass_foreach_dev(dev, uc) {
|
||||
uint phandle;
|
||||
|
||||
phandle = dev_read_phandle(dev);
|
||||
|
||||
if (phandle == find_phandle) {
|
||||
*devp = dev;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif
|
||||
|
||||
int uclass_get_device_by_driver(enum uclass_id id,
|
||||
const struct driver *find_drv,
|
||||
struct udevice **devp)
|
||||
{
|
||||
struct udevice *dev;
|
||||
struct uclass *uc;
|
||||
int ret;
|
||||
|
||||
ret = uclass_get(id, &uc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
uclass_foreach_dev(dev, uc) {
|
||||
if (dev->driver == find_drv)
|
||||
return uclass_get_device_tail(dev, 0, devp);
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
int uclass_get_device_tail(struct udevice *dev, int ret, struct udevice **devp)
|
||||
{
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
assert(dev);
|
||||
ret = device_probe(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*devp = dev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uclass_get_device(enum uclass_id id, int index, struct udevice **devp)
|
||||
{
|
||||
struct udevice *dev;
|
||||
int ret;
|
||||
|
||||
*devp = NULL;
|
||||
ret = uclass_find_device(id, index, &dev);
|
||||
return uclass_get_device_tail(dev, ret, devp);
|
||||
}
|
||||
|
||||
int uclass_get_device_by_name(enum uclass_id id, const char *name,
|
||||
struct udevice **devp)
|
||||
{
|
||||
struct udevice *dev;
|
||||
int ret;
|
||||
|
||||
*devp = NULL;
|
||||
ret = uclass_find_device_by_name(id, name, &dev);
|
||||
return uclass_get_device_tail(dev, ret, devp);
|
||||
}
|
||||
|
||||
int uclass_get_device_by_seq(enum uclass_id id, int seq, struct udevice **devp)
|
||||
{
|
||||
struct udevice *dev;
|
||||
int ret;
|
||||
|
||||
*devp = NULL;
|
||||
ret = uclass_find_device_by_seq(id, 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 = uclass_find_device_by_seq(id, seq, true, &dev);
|
||||
}
|
||||
return uclass_get_device_tail(dev, ret, devp);
|
||||
}
|
||||
|
||||
int uclass_get_device_by_of_offset(enum uclass_id id, int node,
|
||||
struct udevice **devp)
|
||||
{
|
||||
struct udevice *dev;
|
||||
int ret;
|
||||
|
||||
*devp = NULL;
|
||||
ret = uclass_find_device_by_of_offset(id, node, &dev);
|
||||
return uclass_get_device_tail(dev, ret, devp);
|
||||
}
|
||||
|
||||
int uclass_get_device_by_ofnode(enum uclass_id id, ofnode node,
|
||||
struct udevice **devp)
|
||||
{
|
||||
struct udevice *dev;
|
||||
int ret;
|
||||
|
||||
log(LOGC_DM, LOGL_DEBUG, "Looking for %s\n", ofnode_get_name(node));
|
||||
*devp = NULL;
|
||||
ret = uclass_find_device_by_ofnode(id, node, &dev);
|
||||
log(LOGC_DM, LOGL_DEBUG, " - result for %s: %s (ret=%d)\n",
|
||||
ofnode_get_name(node), dev ? dev->name : "(none)", ret);
|
||||
|
||||
return uclass_get_device_tail(dev, ret, devp);
|
||||
}
|
||||
|
||||
#if CONFIG_IS_ENABLED(OF_CONTROL)
|
||||
int uclass_get_device_by_phandle_id(enum uclass_id id, uint phandle_id,
|
||||
struct udevice **devp)
|
||||
{
|
||||
struct udevice *dev;
|
||||
struct uclass *uc;
|
||||
int ret;
|
||||
|
||||
*devp = NULL;
|
||||
ret = uclass_get(id, &uc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
uclass_foreach_dev(dev, uc) {
|
||||
uint phandle;
|
||||
|
||||
phandle = dev_read_phandle(dev);
|
||||
|
||||
if (phandle == phandle_id) {
|
||||
*devp = dev;
|
||||
return uclass_get_device_tail(dev, ret, devp);
|
||||
}
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
#endif
|
||||
|
||||
int uclass_first_device(enum uclass_id id, struct udevice **devp)
|
||||
{
|
||||
struct udevice *dev;
|
||||
int ret;
|
||||
|
||||
*devp = NULL;
|
||||
ret = uclass_find_first_device(id, &dev);
|
||||
if (!dev)
|
||||
return 0;
|
||||
return uclass_get_device_tail(dev, ret, devp);
|
||||
}
|
||||
|
||||
int uclass_first_device_err(enum uclass_id id, struct udevice **devp)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = uclass_first_device(id, devp);
|
||||
if (ret)
|
||||
return ret;
|
||||
else if (!*devp)
|
||||
return -ENODEV;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uclass_next_device(struct udevice **devp)
|
||||
{
|
||||
struct udevice *dev = *devp;
|
||||
int ret;
|
||||
|
||||
*devp = NULL;
|
||||
ret = uclass_find_next_device(&dev);
|
||||
if (!dev)
|
||||
return 0;
|
||||
return uclass_get_device_tail(dev, ret, devp);
|
||||
}
|
||||
|
||||
int uclass_next_device_err(struct udevice **devp)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = uclass_next_device(devp);
|
||||
if (ret)
|
||||
return ret;
|
||||
else if (!*devp)
|
||||
return -ENODEV;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uclass_first_device_check(enum uclass_id id, struct udevice **devp)
|
||||
{
|
||||
int ret;
|
||||
|
||||
*devp = NULL;
|
||||
ret = uclass_find_first_device(id, devp);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (!*devp)
|
||||
return 0;
|
||||
|
||||
return device_probe(*devp);
|
||||
}
|
||||
|
||||
int uclass_next_device_check(struct udevice **devp)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = uclass_find_next_device(devp);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (!*devp)
|
||||
return 0;
|
||||
|
||||
return device_probe(*devp);
|
||||
}
|
||||
|
||||
int uclass_bind_device(struct udevice *dev)
|
||||
{
|
||||
struct uclass *uc;
|
||||
int ret;
|
||||
|
||||
uc = dev->uclass;
|
||||
list_add_tail(&dev->uclass_node, &uc->dev_head);
|
||||
|
||||
if (dev->parent) {
|
||||
struct uclass_driver *uc_drv = dev->parent->uclass->uc_drv;
|
||||
|
||||
if (uc_drv->child_post_bind) {
|
||||
ret = uc_drv->child_post_bind(dev);
|
||||
if (ret)
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
err:
|
||||
/* There is no need to undo the parent's post_bind call */
|
||||
list_del(&dev->uclass_node);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if CONFIG_IS_ENABLED(DM_DEVICE_REMOVE)
|
||||
int uclass_unbind_device(struct udevice *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;
|
||||
}
|
||||
#endif
|
||||
|
||||
int uclass_resolve_seq(struct udevice *dev)
|
||||
{
|
||||
struct udevice *dup;
|
||||
int seq;
|
||||
int ret;
|
||||
|
||||
assert(dev->seq == -1);
|
||||
ret = uclass_find_device_by_seq(dev->uclass->uc_drv->id, dev->req_seq,
|
||||
false, &dup);
|
||||
if (!ret) {
|
||||
dm_warn("Device '%s': seq %d is in use by '%s'\n",
|
||||
dev->name, dev->req_seq, dup->name);
|
||||
} else if (ret == -ENODEV) {
|
||||
/* Our requested sequence number is available */
|
||||
if (dev->req_seq != -1)
|
||||
return dev->req_seq;
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (seq = 0; seq < DM_MAX_SEQ; seq++) {
|
||||
ret = uclass_find_device_by_seq(dev->uclass->uc_drv->id, seq,
|
||||
false, &dup);
|
||||
if (ret == -ENODEV)
|
||||
break;
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
return seq;
|
||||
}
|
||||
|
||||
int uclass_pre_probe_device(struct udevice *dev)
|
||||
{
|
||||
struct uclass_driver *uc_drv;
|
||||
int ret;
|
||||
|
||||
uc_drv = dev->uclass->uc_drv;
|
||||
if (uc_drv->pre_probe) {
|
||||
ret = uc_drv->pre_probe(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!dev->parent)
|
||||
return 0;
|
||||
uc_drv = dev->parent->uclass->uc_drv;
|
||||
if (uc_drv->child_pre_probe) {
|
||||
ret = uc_drv->child_pre_probe(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uclass_post_probe_device(struct udevice *dev)
|
||||
{
|
||||
struct uclass_driver *uc_drv;
|
||||
int ret;
|
||||
|
||||
if (dev->parent) {
|
||||
uc_drv = dev->parent->uclass->uc_drv;
|
||||
if (uc_drv->child_post_probe) {
|
||||
ret = uc_drv->child_post_probe(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
uc_drv = dev->uclass->uc_drv;
|
||||
if (uc_drv->post_probe) {
|
||||
ret = uc_drv->post_probe(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if CONFIG_IS_ENABLED(DM_DEVICE_REMOVE)
|
||||
int uclass_pre_remove_device(struct udevice *dev)
|
||||
{
|
||||
struct uclass *uc;
|
||||
int ret;
|
||||
|
||||
uc = dev->uclass;
|
||||
if (uc->uc_drv->pre_remove) {
|
||||
ret = uc->uc_drv->pre_remove(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
UCLASS_DRIVER(nop) = {
|
||||
.id = UCLASS_NOP,
|
||||
.name = "nop",
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (c) 2013 Google, Inc
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <dm/ofnode.h>
|
||||
#include <dm/util.h>
|
||||
#include <linux/libfdt.h>
|
||||
#include <vsprintf.h>
|
||||
|
||||
#ifdef CONFIG_DM_WARN
|
||||
void dm_warn(const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
vprintf(fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
#endif
|
||||
|
||||
int list_count_items(struct list_head *head)
|
||||
{
|
||||
struct list_head *node;
|
||||
int count = 0;
|
||||
|
||||
list_for_each(node, head)
|
||||
count++;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
#if CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)
|
||||
bool dm_ofnode_pre_reloc(ofnode node)
|
||||
{
|
||||
#if defined(CONFIG_SPL_BUILD) || defined(CONFIG_TPL_BUILD)
|
||||
/* for SPL and TPL the remaining nodes after the fdtgrep 1st pass
|
||||
* had property dm-pre-reloc or u-boot,dm-spl/tpl.
|
||||
* They are removed in final dtb (fdtgrep 2nd pass)
|
||||
*/
|
||||
return true;
|
||||
#else
|
||||
if (ofnode_read_bool(node, "u-boot,dm-pre-reloc"))
|
||||
return true;
|
||||
if (ofnode_read_bool(node, "u-boot,dm-pre-proper"))
|
||||
return true;
|
||||
|
||||
/*
|
||||
* In regular builds individual spl and tpl handling both
|
||||
* count as handled pre-relocation for later second init.
|
||||
*/
|
||||
if (ofnode_read_bool(node, "u-boot,dm-spl") ||
|
||||
ofnode_read_bool(node, "u-boot,dm-tpl"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user