mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 01:08:03 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
86
drivers/of/Kconfig
Normal file
86
drivers/of/Kconfig
Normal file
|
@ -0,0 +1,86 @@
|
|||
config DTC
|
||||
bool
|
||||
|
||||
config OF
|
||||
bool
|
||||
|
||||
menu "Device Tree and Open Firmware support"
|
||||
depends on OF
|
||||
|
||||
config OF_SELFTEST
|
||||
bool "Device Tree Runtime self tests"
|
||||
depends on OF_IRQ && OF_EARLY_FLATTREE
|
||||
select OF_DYNAMIC
|
||||
select OF_RESOLVE
|
||||
help
|
||||
This option builds in test cases for the device tree infrastructure
|
||||
that are executed once at boot time, and the results dumped to the
|
||||
console.
|
||||
|
||||
If unsure, say N here, but this option is safe to enable.
|
||||
|
||||
config OF_FLATTREE
|
||||
bool
|
||||
select DTC
|
||||
select LIBFDT
|
||||
|
||||
config OF_EARLY_FLATTREE
|
||||
bool
|
||||
select OF_FLATTREE
|
||||
|
||||
config OF_PROMTREE
|
||||
bool
|
||||
|
||||
# Hardly any platforms need this. It is safe to select, but only do so if you
|
||||
# need it.
|
||||
config OF_DYNAMIC
|
||||
bool
|
||||
|
||||
config OF_ADDRESS
|
||||
def_bool y
|
||||
depends on !SPARC
|
||||
select OF_ADDRESS_PCI if PCI
|
||||
|
||||
config OF_ADDRESS_PCI
|
||||
bool
|
||||
|
||||
config OF_IRQ
|
||||
def_bool y
|
||||
depends on !SPARC
|
||||
|
||||
config OF_NET
|
||||
depends on NETDEVICES
|
||||
def_bool y
|
||||
|
||||
config OF_MDIO
|
||||
def_tristate PHYLIB
|
||||
depends on PHYLIB
|
||||
help
|
||||
OpenFirmware MDIO bus (Ethernet PHY) accessors
|
||||
|
||||
config OF_PCI
|
||||
def_tristate PCI
|
||||
depends on PCI
|
||||
help
|
||||
OpenFirmware PCI bus accessors
|
||||
|
||||
config OF_PCI_IRQ
|
||||
def_tristate PCI
|
||||
depends on OF_PCI && OF_IRQ
|
||||
help
|
||||
OpenFirmware PCI IRQ routing helpers
|
||||
|
||||
config OF_MTD
|
||||
depends on MTD
|
||||
def_bool y
|
||||
|
||||
config OF_RESERVED_MEM
|
||||
depends on OF_EARLY_FLATTREE
|
||||
bool
|
||||
help
|
||||
Helpers to allow for reservation of memory regions
|
||||
|
||||
config OF_RESOLVE
|
||||
bool
|
||||
|
||||
endmenu # OF
|
19
drivers/of/Makefile
Normal file
19
drivers/of/Makefile
Normal file
|
@ -0,0 +1,19 @@
|
|||
obj-y = base.o device.o platform.o
|
||||
obj-$(CONFIG_OF_DYNAMIC) += dynamic.o
|
||||
obj-$(CONFIG_OF_FLATTREE) += fdt.o
|
||||
obj-$(CONFIG_OF_EARLY_FLATTREE) += fdt_address.o
|
||||
obj-$(CONFIG_OF_PROMTREE) += pdt.o
|
||||
obj-$(CONFIG_OF_ADDRESS) += address.o
|
||||
obj-$(CONFIG_OF_IRQ) += irq.o
|
||||
obj-$(CONFIG_OF_NET) += of_net.o
|
||||
obj-$(CONFIG_OF_SELFTEST) += of_selftest.o
|
||||
of_selftest-objs := selftest.o testcase-data/testcases.dtb.o
|
||||
obj-$(CONFIG_OF_MDIO) += of_mdio.o
|
||||
obj-$(CONFIG_OF_PCI) += of_pci.o
|
||||
obj-$(CONFIG_OF_PCI_IRQ) += of_pci_irq.o
|
||||
obj-$(CONFIG_OF_MTD) += of_mtd.o
|
||||
obj-$(CONFIG_OF_RESERVED_MEM) += of_reserved_mem.o
|
||||
obj-$(CONFIG_OF_RESOLVE) += resolver.o
|
||||
|
||||
CFLAGS_fdt.o = -I$(src)/../../scripts/dtc/libfdt
|
||||
CFLAGS_fdt_address.o = -I$(src)/../../scripts/dtc/libfdt
|
1021
drivers/of/address.c
Normal file
1021
drivers/of/address.c
Normal file
File diff suppressed because it is too large
Load diff
2144
drivers/of/base.c
Normal file
2144
drivers/of/base.c
Normal file
File diff suppressed because it is too large
Load diff
192
drivers/of/device.c
Normal file
192
drivers/of/device.c
Normal file
|
@ -0,0 +1,192 @@
|
|||
#include <linux/string.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <asm/errno.h>
|
||||
#include "of_private.h"
|
||||
|
||||
/**
|
||||
* of_match_device - Tell if a struct device matches an of_device_id list
|
||||
* @ids: array of of device match structures to search in
|
||||
* @dev: the of device structure to match against
|
||||
*
|
||||
* Used by a driver to check whether an platform_device present in the
|
||||
* system is in its list of supported devices.
|
||||
*/
|
||||
const struct of_device_id *of_match_device(const struct of_device_id *matches,
|
||||
const struct device *dev)
|
||||
{
|
||||
if ((!matches) || (!dev->of_node))
|
||||
return NULL;
|
||||
return of_match_node(matches, dev->of_node);
|
||||
}
|
||||
EXPORT_SYMBOL(of_match_device);
|
||||
|
||||
struct platform_device *of_dev_get(struct platform_device *dev)
|
||||
{
|
||||
struct device *tmp;
|
||||
|
||||
if (!dev)
|
||||
return NULL;
|
||||
tmp = get_device(&dev->dev);
|
||||
if (tmp)
|
||||
return to_platform_device(tmp);
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(of_dev_get);
|
||||
|
||||
void of_dev_put(struct platform_device *dev)
|
||||
{
|
||||
if (dev)
|
||||
put_device(&dev->dev);
|
||||
}
|
||||
EXPORT_SYMBOL(of_dev_put);
|
||||
|
||||
int of_device_add(struct platform_device *ofdev)
|
||||
{
|
||||
BUG_ON(ofdev->dev.of_node == NULL);
|
||||
|
||||
/* name and id have to be set so that the platform bus doesn't get
|
||||
* confused on matching */
|
||||
ofdev->name = dev_name(&ofdev->dev);
|
||||
ofdev->id = -1;
|
||||
|
||||
/* device_add will assume that this device is on the same node as
|
||||
* the parent. If there is no parent defined, set the node
|
||||
* explicitly */
|
||||
if (!ofdev->dev.parent)
|
||||
set_dev_node(&ofdev->dev, of_node_to_nid(ofdev->dev.of_node));
|
||||
|
||||
return device_add(&ofdev->dev);
|
||||
}
|
||||
|
||||
int of_device_register(struct platform_device *pdev)
|
||||
{
|
||||
device_initialize(&pdev->dev);
|
||||
return of_device_add(pdev);
|
||||
}
|
||||
EXPORT_SYMBOL(of_device_register);
|
||||
|
||||
void of_device_unregister(struct platform_device *ofdev)
|
||||
{
|
||||
device_unregister(&ofdev->dev);
|
||||
}
|
||||
EXPORT_SYMBOL(of_device_unregister);
|
||||
|
||||
ssize_t of_device_get_modalias(struct device *dev, char *str, ssize_t len)
|
||||
{
|
||||
const char *compat;
|
||||
int cplen, i;
|
||||
ssize_t tsize, csize, repend;
|
||||
|
||||
if ((!dev) || (!dev->of_node))
|
||||
return -ENODEV;
|
||||
|
||||
/* Name & Type */
|
||||
csize = snprintf(str, len, "of:N%sT%s", dev->of_node->name,
|
||||
dev->of_node->type);
|
||||
|
||||
/* Get compatible property if any */
|
||||
compat = of_get_property(dev->of_node, "compatible", &cplen);
|
||||
if (!compat)
|
||||
return csize;
|
||||
|
||||
/* Find true end (we tolerate multiple \0 at the end */
|
||||
for (i = (cplen - 1); i >= 0 && !compat[i]; i--)
|
||||
cplen--;
|
||||
if (!cplen)
|
||||
return csize;
|
||||
cplen++;
|
||||
|
||||
/* Check space (need cplen+1 chars including final \0) */
|
||||
tsize = csize + cplen;
|
||||
repend = tsize;
|
||||
|
||||
if (csize >= len) /* @ the limit, all is already filled */
|
||||
return tsize;
|
||||
|
||||
if (tsize >= len) { /* limit compat list */
|
||||
cplen = len - csize - 1;
|
||||
repend = len;
|
||||
}
|
||||
|
||||
/* Copy and do char replacement */
|
||||
memcpy(&str[csize + 1], compat, cplen);
|
||||
for (i = csize; i < repend; i++) {
|
||||
char c = str[i];
|
||||
if (c == '\0')
|
||||
str[i] = 'C';
|
||||
else if (c == ' ')
|
||||
str[i] = '_';
|
||||
}
|
||||
|
||||
return tsize;
|
||||
}
|
||||
|
||||
/**
|
||||
* of_device_uevent - Display OF related uevent information
|
||||
*/
|
||||
void of_device_uevent(struct device *dev, struct kobj_uevent_env *env)
|
||||
{
|
||||
const char *compat;
|
||||
struct alias_prop *app;
|
||||
int seen = 0, cplen, sl;
|
||||
|
||||
if ((!dev) || (!dev->of_node))
|
||||
return;
|
||||
|
||||
add_uevent_var(env, "OF_NAME=%s", dev->of_node->name);
|
||||
add_uevent_var(env, "OF_FULLNAME=%s", dev->of_node->full_name);
|
||||
if (dev->of_node->type && strcmp("<NULL>", dev->of_node->type) != 0)
|
||||
add_uevent_var(env, "OF_TYPE=%s", dev->of_node->type);
|
||||
|
||||
/* Since the compatible field can contain pretty much anything
|
||||
* it's not really legal to split it out with commas. We split it
|
||||
* up using a number of environment variables instead. */
|
||||
compat = of_get_property(dev->of_node, "compatible", &cplen);
|
||||
while (compat && *compat && cplen > 0) {
|
||||
add_uevent_var(env, "OF_COMPATIBLE_%d=%s", seen, compat);
|
||||
sl = strlen(compat) + 1;
|
||||
compat += sl;
|
||||
cplen -= sl;
|
||||
seen++;
|
||||
}
|
||||
add_uevent_var(env, "OF_COMPATIBLE_N=%d", seen);
|
||||
|
||||
seen = 0;
|
||||
mutex_lock(&of_mutex);
|
||||
list_for_each_entry(app, &aliases_lookup, link) {
|
||||
if (dev->of_node == app->np) {
|
||||
add_uevent_var(env, "OF_ALIAS_%d=%s", seen,
|
||||
app->alias);
|
||||
seen++;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&of_mutex);
|
||||
}
|
||||
|
||||
int of_device_uevent_modalias(struct device *dev, struct kobj_uevent_env *env)
|
||||
{
|
||||
int sl;
|
||||
|
||||
if ((!dev) || (!dev->of_node))
|
||||
return -ENODEV;
|
||||
|
||||
/* Devicetree modalias is tricky, we add it in 2 steps */
|
||||
if (add_uevent_var(env, "MODALIAS="))
|
||||
return -ENOMEM;
|
||||
|
||||
sl = of_device_get_modalias(dev, &env->buf[env->buflen-1],
|
||||
sizeof(env->buf) - env->buflen);
|
||||
if (sl >= (sizeof(env->buf) - env->buflen))
|
||||
return -ENOMEM;
|
||||
env->buflen += sl;
|
||||
|
||||
return 0;
|
||||
}
|
663
drivers/of/dynamic.c
Normal file
663
drivers/of/dynamic.c
Normal file
|
@ -0,0 +1,663 @@
|
|||
/*
|
||||
* Support for dynamic device trees.
|
||||
*
|
||||
* On some platforms, the device tree can be manipulated at runtime.
|
||||
* The routines in this section support adding, removing and changing
|
||||
* device tree nodes.
|
||||
*/
|
||||
|
||||
#include <linux/of.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/proc_fs.h>
|
||||
|
||||
#include "of_private.h"
|
||||
|
||||
/**
|
||||
* of_node_get() - Increment refcount of a node
|
||||
* @node: Node to inc refcount, NULL is supported to simplify writing of
|
||||
* callers
|
||||
*
|
||||
* Returns node.
|
||||
*/
|
||||
struct device_node *of_node_get(struct device_node *node)
|
||||
{
|
||||
if (node)
|
||||
kobject_get(&node->kobj);
|
||||
return node;
|
||||
}
|
||||
EXPORT_SYMBOL(of_node_get);
|
||||
|
||||
/**
|
||||
* of_node_put() - Decrement refcount of a node
|
||||
* @node: Node to dec refcount, NULL is supported to simplify writing of
|
||||
* callers
|
||||
*/
|
||||
void of_node_put(struct device_node *node)
|
||||
{
|
||||
if (node)
|
||||
kobject_put(&node->kobj);
|
||||
}
|
||||
EXPORT_SYMBOL(of_node_put);
|
||||
|
||||
void __of_detach_node_sysfs(struct device_node *np)
|
||||
{
|
||||
struct property *pp;
|
||||
|
||||
if (!IS_ENABLED(CONFIG_SYSFS))
|
||||
return;
|
||||
|
||||
BUG_ON(!of_node_is_initialized(np));
|
||||
if (!of_kset)
|
||||
return;
|
||||
|
||||
/* only remove properties if on sysfs */
|
||||
if (of_node_is_attached(np)) {
|
||||
for_each_property_of_node(np, pp)
|
||||
sysfs_remove_bin_file(&np->kobj, &pp->attr);
|
||||
kobject_del(&np->kobj);
|
||||
}
|
||||
|
||||
/* finally remove the kobj_init ref */
|
||||
of_node_put(np);
|
||||
}
|
||||
|
||||
static BLOCKING_NOTIFIER_HEAD(of_reconfig_chain);
|
||||
|
||||
int of_reconfig_notifier_register(struct notifier_block *nb)
|
||||
{
|
||||
return blocking_notifier_chain_register(&of_reconfig_chain, nb);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_reconfig_notifier_register);
|
||||
|
||||
int of_reconfig_notifier_unregister(struct notifier_block *nb)
|
||||
{
|
||||
return blocking_notifier_chain_unregister(&of_reconfig_chain, nb);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_reconfig_notifier_unregister);
|
||||
|
||||
int of_reconfig_notify(unsigned long action, void *p)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = blocking_notifier_call_chain(&of_reconfig_chain, action, p);
|
||||
return notifier_to_errno(rc);
|
||||
}
|
||||
|
||||
int of_property_notify(int action, struct device_node *np,
|
||||
struct property *prop, struct property *oldprop)
|
||||
{
|
||||
struct of_prop_reconfig pr;
|
||||
|
||||
/* only call notifiers if the node is attached */
|
||||
if (!of_node_is_attached(np))
|
||||
return 0;
|
||||
|
||||
pr.dn = np;
|
||||
pr.prop = prop;
|
||||
pr.old_prop = oldprop;
|
||||
return of_reconfig_notify(action, &pr);
|
||||
}
|
||||
|
||||
void __of_attach_node(struct device_node *np)
|
||||
{
|
||||
const __be32 *phandle;
|
||||
int sz;
|
||||
|
||||
np->name = __of_get_property(np, "name", NULL) ? : "<NULL>";
|
||||
np->type = __of_get_property(np, "device_type", NULL) ? : "<NULL>";
|
||||
|
||||
phandle = __of_get_property(np, "phandle", &sz);
|
||||
if (!phandle)
|
||||
phandle = __of_get_property(np, "linux,phandle", &sz);
|
||||
if (IS_ENABLED(PPC_PSERIES) && !phandle)
|
||||
phandle = __of_get_property(np, "ibm,phandle", &sz);
|
||||
np->phandle = (phandle && (sz >= 4)) ? be32_to_cpup(phandle) : 0;
|
||||
|
||||
np->child = NULL;
|
||||
np->sibling = np->parent->child;
|
||||
np->allnext = np->parent->allnext;
|
||||
np->parent->allnext = np;
|
||||
np->parent->child = np;
|
||||
of_node_clear_flag(np, OF_DETACHED);
|
||||
}
|
||||
|
||||
/**
|
||||
* of_attach_node() - Plug a device node into the tree and global list.
|
||||
*/
|
||||
int of_attach_node(struct device_node *np)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
mutex_lock(&of_mutex);
|
||||
raw_spin_lock_irqsave(&devtree_lock, flags);
|
||||
__of_attach_node(np);
|
||||
raw_spin_unlock_irqrestore(&devtree_lock, flags);
|
||||
|
||||
__of_attach_node_sysfs(np);
|
||||
mutex_unlock(&of_mutex);
|
||||
|
||||
of_reconfig_notify(OF_RECONFIG_ATTACH_NODE, np);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void __of_detach_node(struct device_node *np)
|
||||
{
|
||||
struct device_node *parent;
|
||||
|
||||
if (WARN_ON(of_node_check_flag(np, OF_DETACHED)))
|
||||
return;
|
||||
|
||||
parent = np->parent;
|
||||
if (WARN_ON(!parent))
|
||||
return;
|
||||
|
||||
if (of_allnodes == np)
|
||||
of_allnodes = np->allnext;
|
||||
else {
|
||||
struct device_node *prev;
|
||||
for (prev = of_allnodes;
|
||||
prev->allnext != np;
|
||||
prev = prev->allnext)
|
||||
;
|
||||
prev->allnext = np->allnext;
|
||||
}
|
||||
|
||||
if (parent->child == np)
|
||||
parent->child = np->sibling;
|
||||
else {
|
||||
struct device_node *prevsib;
|
||||
for (prevsib = np->parent->child;
|
||||
prevsib->sibling != np;
|
||||
prevsib = prevsib->sibling)
|
||||
;
|
||||
prevsib->sibling = np->sibling;
|
||||
}
|
||||
|
||||
of_node_set_flag(np, OF_DETACHED);
|
||||
}
|
||||
|
||||
/**
|
||||
* of_detach_node() - "Unplug" a node from the device tree.
|
||||
*
|
||||
* The caller must hold a reference to the node. The memory associated with
|
||||
* the node is not freed until its refcount goes to zero.
|
||||
*/
|
||||
int of_detach_node(struct device_node *np)
|
||||
{
|
||||
unsigned long flags;
|
||||
int rc = 0;
|
||||
|
||||
mutex_lock(&of_mutex);
|
||||
raw_spin_lock_irqsave(&devtree_lock, flags);
|
||||
__of_detach_node(np);
|
||||
raw_spin_unlock_irqrestore(&devtree_lock, flags);
|
||||
|
||||
__of_detach_node_sysfs(np);
|
||||
mutex_unlock(&of_mutex);
|
||||
|
||||
of_reconfig_notify(OF_RECONFIG_DETACH_NODE, np);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* of_node_release() - release a dynamically allocated node
|
||||
* @kref: kref element of the node to be released
|
||||
*
|
||||
* In of_node_put() this function is passed to kref_put() as the destructor.
|
||||
*/
|
||||
void of_node_release(struct kobject *kobj)
|
||||
{
|
||||
struct device_node *node = kobj_to_device_node(kobj);
|
||||
struct property *prop = node->properties;
|
||||
|
||||
/* We should never be releasing nodes that haven't been detached. */
|
||||
if (!of_node_check_flag(node, OF_DETACHED)) {
|
||||
pr_err("ERROR: Bad of_node_put() on %s\n", node->full_name);
|
||||
dump_stack();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!of_node_check_flag(node, OF_DYNAMIC))
|
||||
return;
|
||||
|
||||
while (prop) {
|
||||
struct property *next = prop->next;
|
||||
kfree(prop->name);
|
||||
kfree(prop->value);
|
||||
kfree(prop);
|
||||
prop = next;
|
||||
|
||||
if (!prop) {
|
||||
prop = node->deadprops;
|
||||
node->deadprops = NULL;
|
||||
}
|
||||
}
|
||||
kfree(node->full_name);
|
||||
kfree(node->data);
|
||||
kfree(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* __of_prop_dup - Copy a property dynamically.
|
||||
* @prop: Property to copy
|
||||
* @allocflags: Allocation flags (typically pass GFP_KERNEL)
|
||||
*
|
||||
* Copy a property by dynamically allocating the memory of both the
|
||||
* property structure and the property name & contents. The property's
|
||||
* flags have the OF_DYNAMIC bit set so that we can differentiate between
|
||||
* dynamically allocated properties and not.
|
||||
* Returns the newly allocated property or NULL on out of memory error.
|
||||
*/
|
||||
struct property *__of_prop_dup(const struct property *prop, gfp_t allocflags)
|
||||
{
|
||||
struct property *new;
|
||||
|
||||
new = kzalloc(sizeof(*new), allocflags);
|
||||
if (!new)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* NOTE: There is no check for zero length value.
|
||||
* In case of a boolean property, this will allocate a value
|
||||
* of zero bytes. We do this to work around the use
|
||||
* of of_get_property() calls on boolean values.
|
||||
*/
|
||||
new->name = kstrdup(prop->name, allocflags);
|
||||
new->value = kmemdup(prop->value, prop->length, allocflags);
|
||||
new->length = prop->length;
|
||||
if (!new->name || !new->value)
|
||||
goto err_free;
|
||||
|
||||
/* mark the property as dynamic */
|
||||
of_property_set_flag(new, OF_DYNAMIC);
|
||||
|
||||
return new;
|
||||
|
||||
err_free:
|
||||
kfree(new->name);
|
||||
kfree(new->value);
|
||||
kfree(new);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* __of_node_alloc() - Create an empty device node dynamically.
|
||||
* @full_name: Full name of the new device node
|
||||
* @allocflags: Allocation flags (typically pass GFP_KERNEL)
|
||||
*
|
||||
* Create an empty device tree node, suitable for further modification.
|
||||
* The node data are dynamically allocated and all the node flags
|
||||
* have the OF_DYNAMIC & OF_DETACHED bits set.
|
||||
* Returns the newly allocated node or NULL on out of memory error.
|
||||
*/
|
||||
struct device_node *__of_node_alloc(const char *full_name, gfp_t allocflags)
|
||||
{
|
||||
struct device_node *node;
|
||||
|
||||
node = kzalloc(sizeof(*node), allocflags);
|
||||
if (!node)
|
||||
return NULL;
|
||||
|
||||
node->full_name = kstrdup(full_name, allocflags);
|
||||
of_node_set_flag(node, OF_DYNAMIC);
|
||||
of_node_set_flag(node, OF_DETACHED);
|
||||
if (!node->full_name)
|
||||
goto err_free;
|
||||
|
||||
of_node_init(node);
|
||||
|
||||
return node;
|
||||
|
||||
err_free:
|
||||
kfree(node->full_name);
|
||||
kfree(node);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void __of_changeset_entry_destroy(struct of_changeset_entry *ce)
|
||||
{
|
||||
of_node_put(ce->np);
|
||||
list_del(&ce->node);
|
||||
kfree(ce);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
static void __of_changeset_entry_dump(struct of_changeset_entry *ce)
|
||||
{
|
||||
switch (ce->action) {
|
||||
case OF_RECONFIG_ADD_PROPERTY:
|
||||
pr_debug("%p: %s %s/%s\n",
|
||||
ce, "ADD_PROPERTY ", ce->np->full_name,
|
||||
ce->prop->name);
|
||||
break;
|
||||
case OF_RECONFIG_REMOVE_PROPERTY:
|
||||
pr_debug("%p: %s %s/%s\n",
|
||||
ce, "REMOVE_PROPERTY", ce->np->full_name,
|
||||
ce->prop->name);
|
||||
break;
|
||||
case OF_RECONFIG_UPDATE_PROPERTY:
|
||||
pr_debug("%p: %s %s/%s\n",
|
||||
ce, "UPDATE_PROPERTY", ce->np->full_name,
|
||||
ce->prop->name);
|
||||
break;
|
||||
case OF_RECONFIG_ATTACH_NODE:
|
||||
pr_debug("%p: %s %s\n",
|
||||
ce, "ATTACH_NODE ", ce->np->full_name);
|
||||
break;
|
||||
case OF_RECONFIG_DETACH_NODE:
|
||||
pr_debug("%p: %s %s\n",
|
||||
ce, "DETACH_NODE ", ce->np->full_name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
static inline void __of_changeset_entry_dump(struct of_changeset_entry *ce)
|
||||
{
|
||||
/* empty */
|
||||
}
|
||||
#endif
|
||||
|
||||
static void __of_changeset_entry_invert(struct of_changeset_entry *ce,
|
||||
struct of_changeset_entry *rce)
|
||||
{
|
||||
memcpy(rce, ce, sizeof(*rce));
|
||||
|
||||
switch (ce->action) {
|
||||
case OF_RECONFIG_ATTACH_NODE:
|
||||
rce->action = OF_RECONFIG_DETACH_NODE;
|
||||
break;
|
||||
case OF_RECONFIG_DETACH_NODE:
|
||||
rce->action = OF_RECONFIG_ATTACH_NODE;
|
||||
break;
|
||||
case OF_RECONFIG_ADD_PROPERTY:
|
||||
rce->action = OF_RECONFIG_REMOVE_PROPERTY;
|
||||
break;
|
||||
case OF_RECONFIG_REMOVE_PROPERTY:
|
||||
rce->action = OF_RECONFIG_ADD_PROPERTY;
|
||||
break;
|
||||
case OF_RECONFIG_UPDATE_PROPERTY:
|
||||
rce->old_prop = ce->prop;
|
||||
rce->prop = ce->old_prop;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void __of_changeset_entry_notify(struct of_changeset_entry *ce, bool revert)
|
||||
{
|
||||
struct of_changeset_entry ce_inverted;
|
||||
int ret;
|
||||
|
||||
if (revert) {
|
||||
__of_changeset_entry_invert(ce, &ce_inverted);
|
||||
ce = &ce_inverted;
|
||||
}
|
||||
|
||||
switch (ce->action) {
|
||||
case OF_RECONFIG_ATTACH_NODE:
|
||||
case OF_RECONFIG_DETACH_NODE:
|
||||
ret = of_reconfig_notify(ce->action, ce->np);
|
||||
break;
|
||||
case OF_RECONFIG_ADD_PROPERTY:
|
||||
case OF_RECONFIG_REMOVE_PROPERTY:
|
||||
case OF_RECONFIG_UPDATE_PROPERTY:
|
||||
ret = of_property_notify(ce->action, ce->np, ce->prop, ce->old_prop);
|
||||
break;
|
||||
default:
|
||||
pr_err("%s: invalid devicetree changeset action: %i\n", __func__,
|
||||
(int)ce->action);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
pr_err("%s: notifier error @%s\n", __func__, ce->np->full_name);
|
||||
}
|
||||
|
||||
static int __of_changeset_entry_apply(struct of_changeset_entry *ce)
|
||||
{
|
||||
struct property *old_prop, **propp;
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
__of_changeset_entry_dump(ce);
|
||||
|
||||
raw_spin_lock_irqsave(&devtree_lock, flags);
|
||||
switch (ce->action) {
|
||||
case OF_RECONFIG_ATTACH_NODE:
|
||||
__of_attach_node(ce->np);
|
||||
break;
|
||||
case OF_RECONFIG_DETACH_NODE:
|
||||
__of_detach_node(ce->np);
|
||||
break;
|
||||
case OF_RECONFIG_ADD_PROPERTY:
|
||||
/* If the property is in deadprops then it must be removed */
|
||||
for (propp = &ce->np->deadprops; *propp; propp = &(*propp)->next) {
|
||||
if (*propp == ce->prop) {
|
||||
*propp = ce->prop->next;
|
||||
ce->prop->next = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ret = __of_add_property(ce->np, ce->prop);
|
||||
if (ret) {
|
||||
pr_err("%s: add_property failed @%s/%s\n",
|
||||
__func__, ce->np->full_name,
|
||||
ce->prop->name);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case OF_RECONFIG_REMOVE_PROPERTY:
|
||||
ret = __of_remove_property(ce->np, ce->prop);
|
||||
if (ret) {
|
||||
pr_err("%s: remove_property failed @%s/%s\n",
|
||||
__func__, ce->np->full_name,
|
||||
ce->prop->name);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case OF_RECONFIG_UPDATE_PROPERTY:
|
||||
/* If the property is in deadprops then it must be removed */
|
||||
for (propp = &ce->np->deadprops; *propp; propp = &(*propp)->next) {
|
||||
if (*propp == ce->prop) {
|
||||
*propp = ce->prop->next;
|
||||
ce->prop->next = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ret = __of_update_property(ce->np, ce->prop, &old_prop);
|
||||
if (ret) {
|
||||
pr_err("%s: update_property failed @%s/%s\n",
|
||||
__func__, ce->np->full_name,
|
||||
ce->prop->name);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
raw_spin_unlock_irqrestore(&devtree_lock, flags);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (ce->action) {
|
||||
case OF_RECONFIG_ATTACH_NODE:
|
||||
__of_attach_node_sysfs(ce->np);
|
||||
break;
|
||||
case OF_RECONFIG_DETACH_NODE:
|
||||
__of_detach_node_sysfs(ce->np);
|
||||
break;
|
||||
case OF_RECONFIG_ADD_PROPERTY:
|
||||
/* ignore duplicate names */
|
||||
__of_add_property_sysfs(ce->np, ce->prop);
|
||||
break;
|
||||
case OF_RECONFIG_REMOVE_PROPERTY:
|
||||
__of_remove_property_sysfs(ce->np, ce->prop);
|
||||
break;
|
||||
case OF_RECONFIG_UPDATE_PROPERTY:
|
||||
__of_update_property_sysfs(ce->np, ce->prop, ce->old_prop);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int __of_changeset_entry_revert(struct of_changeset_entry *ce)
|
||||
{
|
||||
struct of_changeset_entry ce_inverted;
|
||||
|
||||
__of_changeset_entry_invert(ce, &ce_inverted);
|
||||
return __of_changeset_entry_apply(&ce_inverted);
|
||||
}
|
||||
|
||||
/**
|
||||
* of_changeset_init - Initialize a changeset for use
|
||||
*
|
||||
* @ocs: changeset pointer
|
||||
*
|
||||
* Initialize a changeset structure
|
||||
*/
|
||||
void of_changeset_init(struct of_changeset *ocs)
|
||||
{
|
||||
memset(ocs, 0, sizeof(*ocs));
|
||||
INIT_LIST_HEAD(&ocs->entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* of_changeset_destroy - Destroy a changeset
|
||||
*
|
||||
* @ocs: changeset pointer
|
||||
*
|
||||
* Destroys a changeset. Note that if a changeset is applied,
|
||||
* its changes to the tree cannot be reverted.
|
||||
*/
|
||||
void of_changeset_destroy(struct of_changeset *ocs)
|
||||
{
|
||||
struct of_changeset_entry *ce, *cen;
|
||||
|
||||
list_for_each_entry_safe_reverse(ce, cen, &ocs->entries, node)
|
||||
__of_changeset_entry_destroy(ce);
|
||||
}
|
||||
|
||||
/**
|
||||
* of_changeset_apply - Applies a changeset
|
||||
*
|
||||
* @ocs: changeset pointer
|
||||
*
|
||||
* Applies a changeset to the live tree.
|
||||
* Any side-effects of live tree state changes are applied here on
|
||||
* sucess, like creation/destruction of devices and side-effects
|
||||
* like creation of sysfs properties and directories.
|
||||
* Returns 0 on success, a negative error value in case of an error.
|
||||
* On error the partially applied effects are reverted.
|
||||
*/
|
||||
int of_changeset_apply(struct of_changeset *ocs)
|
||||
{
|
||||
struct of_changeset_entry *ce;
|
||||
int ret;
|
||||
|
||||
/* perform the rest of the work */
|
||||
pr_debug("of_changeset: applying...\n");
|
||||
list_for_each_entry(ce, &ocs->entries, node) {
|
||||
ret = __of_changeset_entry_apply(ce);
|
||||
if (ret) {
|
||||
pr_err("%s: Error applying changeset (%d)\n", __func__, ret);
|
||||
list_for_each_entry_continue_reverse(ce, &ocs->entries, node)
|
||||
__of_changeset_entry_revert(ce);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
pr_debug("of_changeset: applied, emitting notifiers.\n");
|
||||
|
||||
/* drop the global lock while emitting notifiers */
|
||||
mutex_unlock(&of_mutex);
|
||||
list_for_each_entry(ce, &ocs->entries, node)
|
||||
__of_changeset_entry_notify(ce, 0);
|
||||
mutex_lock(&of_mutex);
|
||||
pr_debug("of_changeset: notifiers sent.\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* of_changeset_revert - Reverts an applied changeset
|
||||
*
|
||||
* @ocs: changeset pointer
|
||||
*
|
||||
* Reverts a changeset returning the state of the tree to what it
|
||||
* was before the application.
|
||||
* Any side-effects like creation/destruction of devices and
|
||||
* removal of sysfs properties and directories are applied.
|
||||
* Returns 0 on success, a negative error value in case of an error.
|
||||
*/
|
||||
int of_changeset_revert(struct of_changeset *ocs)
|
||||
{
|
||||
struct of_changeset_entry *ce;
|
||||
int ret;
|
||||
|
||||
pr_debug("of_changeset: reverting...\n");
|
||||
list_for_each_entry_reverse(ce, &ocs->entries, node) {
|
||||
ret = __of_changeset_entry_revert(ce);
|
||||
if (ret) {
|
||||
pr_err("%s: Error reverting changeset (%d)\n", __func__, ret);
|
||||
list_for_each_entry_continue(ce, &ocs->entries, node)
|
||||
__of_changeset_entry_apply(ce);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
pr_debug("of_changeset: reverted, emitting notifiers.\n");
|
||||
|
||||
/* drop the global lock while emitting notifiers */
|
||||
mutex_unlock(&of_mutex);
|
||||
list_for_each_entry_reverse(ce, &ocs->entries, node)
|
||||
__of_changeset_entry_notify(ce, 1);
|
||||
mutex_lock(&of_mutex);
|
||||
pr_debug("of_changeset: notifiers sent.\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* of_changeset_action - Perform a changeset action
|
||||
*
|
||||
* @ocs: changeset pointer
|
||||
* @action: action to perform
|
||||
* @np: Pointer to device node
|
||||
* @prop: Pointer to property
|
||||
*
|
||||
* On action being one of:
|
||||
* + OF_RECONFIG_ATTACH_NODE
|
||||
* + OF_RECONFIG_DETACH_NODE,
|
||||
* + OF_RECONFIG_ADD_PROPERTY
|
||||
* + OF_RECONFIG_REMOVE_PROPERTY,
|
||||
* + OF_RECONFIG_UPDATE_PROPERTY
|
||||
* Returns 0 on success, a negative error value in case of an error.
|
||||
*/
|
||||
int of_changeset_action(struct of_changeset *ocs, unsigned long action,
|
||||
struct device_node *np, struct property *prop)
|
||||
{
|
||||
struct of_changeset_entry *ce;
|
||||
|
||||
ce = kzalloc(sizeof(*ce), GFP_KERNEL);
|
||||
if (!ce) {
|
||||
pr_err("%s: Failed to allocate\n", __func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
/* get a reference to the node */
|
||||
ce->action = action;
|
||||
ce->np = of_node_get(np);
|
||||
ce->prop = prop;
|
||||
|
||||
if (action == OF_RECONFIG_UPDATE_PROPERTY && prop)
|
||||
ce->old_prop = of_find_property(np, prop->name, NULL);
|
||||
|
||||
/* add it to the list */
|
||||
list_add_tail(&ce->node, &ocs->entries);
|
||||
return 0;
|
||||
}
|
1134
drivers/of/fdt.c
Normal file
1134
drivers/of/fdt.c
Normal file
File diff suppressed because it is too large
Load diff
241
drivers/of/fdt_address.c
Normal file
241
drivers/of/fdt_address.c
Normal file
|
@ -0,0 +1,241 @@
|
|||
/*
|
||||
* FDT Address translation based on u-boot fdt_support.c which in turn was
|
||||
* based on the kernel unflattened DT address translation code.
|
||||
*
|
||||
* (C) Copyright 2007
|
||||
* Gerald Van Baren, Custom IDEAS, vanbaren@cideas.com
|
||||
*
|
||||
* Copyright 2010-2011 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/libfdt.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_fdt.h>
|
||||
#include <linux/sizes.h>
|
||||
|
||||
/* Max address size we deal with */
|
||||
#define OF_MAX_ADDR_CELLS 4
|
||||
#define OF_CHECK_COUNTS(na, ns) ((na) > 0 && (na) <= OF_MAX_ADDR_CELLS && \
|
||||
(ns) > 0)
|
||||
|
||||
/* Debug utility */
|
||||
#ifdef DEBUG
|
||||
static void __init of_dump_addr(const char *s, const __be32 *addr, int na)
|
||||
{
|
||||
pr_debug("%s", s);
|
||||
while(na--)
|
||||
pr_cont(" %08x", *(addr++));
|
||||
pr_debug("\n");
|
||||
}
|
||||
#else
|
||||
static void __init of_dump_addr(const char *s, const __be32 *addr, int na) { }
|
||||
#endif
|
||||
|
||||
/* Callbacks for bus specific translators */
|
||||
struct of_bus {
|
||||
void (*count_cells)(const void *blob, int parentoffset,
|
||||
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);
|
||||
};
|
||||
|
||||
/* Default translator (generic bus) */
|
||||
static void __init fdt_bus_default_count_cells(const void *blob, int parentoffset,
|
||||
int *addrc, int *sizec)
|
||||
{
|
||||
const __be32 *prop;
|
||||
|
||||
if (addrc) {
|
||||
prop = fdt_getprop(blob, parentoffset, "#address-cells", NULL);
|
||||
if (prop)
|
||||
*addrc = be32_to_cpup(prop);
|
||||
else
|
||||
*addrc = dt_root_addr_cells;
|
||||
}
|
||||
|
||||
if (sizec) {
|
||||
prop = fdt_getprop(blob, parentoffset, "#size-cells", NULL);
|
||||
if (prop)
|
||||
*sizec = be32_to_cpup(prop);
|
||||
else
|
||||
*sizec = dt_root_size_cells;
|
||||
}
|
||||
}
|
||||
|
||||
static u64 __init fdt_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);
|
||||
|
||||
pr_debug("FDT: default map, cp=%llx, s=%llx, da=%llx\n",
|
||||
cp, s, da);
|
||||
|
||||
if (da < cp || da >= (cp + s))
|
||||
return OF_BAD_ADDR;
|
||||
return da - cp;
|
||||
}
|
||||
|
||||
static int __init fdt_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_fdt32(a >> 32);
|
||||
addr[na - 1] = cpu_to_fdt32(a & 0xffffffffu);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Array of bus specific translators */
|
||||
static const struct of_bus of_busses[] __initconst = {
|
||||
/* Default */
|
||||
{
|
||||
.count_cells = fdt_bus_default_count_cells,
|
||||
.map = fdt_bus_default_map,
|
||||
.translate = fdt_bus_default_translate,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init fdt_translate_one(const void *blob, int parent,
|
||||
const struct of_bus *bus,
|
||||
const 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;
|
||||
|
||||
ranges = fdt_getprop(blob, parent, rprop, &rlen);
|
||||
if (!ranges)
|
||||
return 1;
|
||||
if (rlen == 0) {
|
||||
offset = of_read_number(addr, na);
|
||||
memset(addr, 0, pna * 4);
|
||||
pr_debug("FDT: empty ranges, 1:1 translation\n");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
pr_debug("FDT: 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) {
|
||||
pr_debug("FDT: not found !\n");
|
||||
return 1;
|
||||
}
|
||||
memcpy(addr, ranges + na, 4 * pna);
|
||||
|
||||
finish:
|
||||
of_dump_addr("FDT: parent translation for:", addr, pna);
|
||||
pr_debug("FDT: with offset: %llx\n", 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
|
||||
*/
|
||||
u64 __init fdt_translate_address(const void *blob, int node_offset)
|
||||
{
|
||||
int parent, len;
|
||||
const struct of_bus *bus, *pbus;
|
||||
const __be32 *reg;
|
||||
__be32 addr[OF_MAX_ADDR_CELLS];
|
||||
int na, ns, pna, pns;
|
||||
u64 result = OF_BAD_ADDR;
|
||||
|
||||
pr_debug("FDT: ** translation for device %s **\n",
|
||||
fdt_get_name(blob, node_offset, NULL));
|
||||
|
||||
reg = fdt_getprop(blob, node_offset, "reg", &len);
|
||||
if (!reg) {
|
||||
pr_err("FDT: warning: device tree node '%s' has no address.\n",
|
||||
fdt_get_name(blob, node_offset, NULL));
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* Get parent & match bus type */
|
||||
parent = fdt_parent_offset(blob, node_offset);
|
||||
if (parent < 0)
|
||||
goto bail;
|
||||
bus = &of_busses[0];
|
||||
|
||||
/* Cound address cells & copy address locally */
|
||||
bus->count_cells(blob, parent, &na, &ns);
|
||||
if (!OF_CHECK_COUNTS(na, ns)) {
|
||||
pr_err("FDT: Bad cell count for %s\n",
|
||||
fdt_get_name(blob, node_offset, NULL));
|
||||
goto bail;
|
||||
}
|
||||
memcpy(addr, reg, na * 4);
|
||||
|
||||
pr_debug("FDT: bus (na=%d, ns=%d) on %s\n",
|
||||
na, ns, fdt_get_name(blob, parent, NULL));
|
||||
of_dump_addr("OF: translating address:", addr, na);
|
||||
|
||||
/* Translate */
|
||||
for (;;) {
|
||||
/* Switch to parent bus */
|
||||
node_offset = parent;
|
||||
parent = fdt_parent_offset(blob, node_offset);
|
||||
|
||||
/* If root, we have finished */
|
||||
if (parent < 0) {
|
||||
pr_debug("FDT: reached root node\n");
|
||||
result = of_read_number(addr, na);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Get new parent bus and counts */
|
||||
pbus = &of_busses[0];
|
||||
pbus->count_cells(blob, parent, &pna, &pns);
|
||||
if (!OF_CHECK_COUNTS(pna, pns)) {
|
||||
pr_err("FDT: Bad cell count for %s\n",
|
||||
fdt_get_name(blob, node_offset, NULL));
|
||||
break;
|
||||
}
|
||||
|
||||
pr_debug("FDT: parent bus (na=%d, ns=%d) on %s\n",
|
||||
pna, pns, fdt_get_name(blob, parent, NULL));
|
||||
|
||||
/* Apply bus translation */
|
||||
if (fdt_translate_one(blob, node_offset, bus, pbus,
|
||||
addr, na, ns, pna, "ranges"))
|
||||
break;
|
||||
|
||||
/* Complete the move up one level */
|
||||
na = pna;
|
||||
ns = pns;
|
||||
bus = pbus;
|
||||
|
||||
of_dump_addr("FDT: one level translation:", addr, na);
|
||||
}
|
||||
bail:
|
||||
return result;
|
||||
}
|
578
drivers/of/irq.c
Normal file
578
drivers/of/irq.c
Normal file
|
@ -0,0 +1,578 @@
|
|||
/*
|
||||
* Derived from arch/i386/kernel/irq.c
|
||||
* Copyright (C) 1992 Linus Torvalds
|
||||
* Adapted from arch/i386 by Gary Thomas
|
||||
* Copyright (C) 1995-1996 Gary Thomas (gdt@linuxppc.org)
|
||||
* Updated and modified by Cort Dougan <cort@fsmlabs.com>
|
||||
* Copyright (C) 1996-2001 Cort Dougan
|
||||
* Adapted for Power Macintosh by Paul Mackerras
|
||||
* Copyright (C) 1996 Paul Mackerras (paulus@cs.anu.edu.au)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This file contains the code used to make IRQ descriptions in the
|
||||
* device tree to actual irq numbers on an interrupt controller
|
||||
* driver.
|
||||
*/
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
/**
|
||||
* irq_of_parse_and_map - Parse and map an interrupt into linux virq space
|
||||
* @dev: Device node of the device whose interrupt is to be mapped
|
||||
* @index: Index of the interrupt to map
|
||||
*
|
||||
* This function is a wrapper that chains of_irq_parse_one() and
|
||||
* irq_create_of_mapping() to make things easier to callers
|
||||
*/
|
||||
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
|
||||
{
|
||||
struct of_phandle_args oirq;
|
||||
|
||||
if (of_irq_parse_one(dev, index, &oirq))
|
||||
return 0;
|
||||
|
||||
return irq_create_of_mapping(&oirq);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(irq_of_parse_and_map);
|
||||
|
||||
/**
|
||||
* of_irq_find_parent - Given a device node, find its interrupt parent node
|
||||
* @child: pointer to device node
|
||||
*
|
||||
* Returns a pointer to the interrupt parent node, or NULL if the interrupt
|
||||
* parent could not be determined.
|
||||
*/
|
||||
struct device_node *of_irq_find_parent(struct device_node *child)
|
||||
{
|
||||
struct device_node *p;
|
||||
const __be32 *parp;
|
||||
|
||||
if (!of_node_get(child))
|
||||
return NULL;
|
||||
|
||||
do {
|
||||
parp = of_get_property(child, "interrupt-parent", NULL);
|
||||
if (parp == NULL)
|
||||
p = of_get_parent(child);
|
||||
else {
|
||||
if (of_irq_workarounds & OF_IMAP_NO_PHANDLE)
|
||||
p = of_node_get(of_irq_dflt_pic);
|
||||
else
|
||||
p = of_find_node_by_phandle(be32_to_cpup(parp));
|
||||
}
|
||||
of_node_put(child);
|
||||
child = p;
|
||||
} while (p && of_get_property(p, "#interrupt-cells", NULL) == NULL);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* of_irq_parse_raw - Low level interrupt tree parsing
|
||||
* @parent: the device interrupt parent
|
||||
* @addr: address specifier (start of "reg" property of the device) in be32 format
|
||||
* @out_irq: structure of_irq updated by this function
|
||||
*
|
||||
* Returns 0 on success and a negative number on error
|
||||
*
|
||||
* This function is a low-level interrupt tree walking function. It
|
||||
* can be used to do a partial walk with synthetized reg and interrupts
|
||||
* properties, for example when resolving PCI interrupts when no device
|
||||
* node exist for the parent. It takes an interrupt specifier structure as
|
||||
* input, walks the tree looking for any interrupt-map properties, translates
|
||||
* the specifier for each map, and then returns the translated map.
|
||||
*/
|
||||
int of_irq_parse_raw(const __be32 *addr, struct of_phandle_args *out_irq)
|
||||
{
|
||||
struct device_node *ipar, *tnode, *old = NULL, *newpar = NULL;
|
||||
__be32 initial_match_array[MAX_PHANDLE_ARGS];
|
||||
const __be32 *match_array = initial_match_array;
|
||||
const __be32 *tmp, *imap, *imask, dummy_imask[] = { [0 ... MAX_PHANDLE_ARGS] = ~0 };
|
||||
u32 intsize = 1, addrsize, newintsize = 0, newaddrsize = 0;
|
||||
int imaplen, match, i;
|
||||
|
||||
#ifdef DEBUG
|
||||
of_print_phandle_args("of_irq_parse_raw: ", out_irq);
|
||||
#endif
|
||||
|
||||
ipar = of_node_get(out_irq->np);
|
||||
|
||||
/* First get the #interrupt-cells property of the current cursor
|
||||
* that tells us how to interpret the passed-in intspec. If there
|
||||
* is none, we are nice and just walk up the tree
|
||||
*/
|
||||
do {
|
||||
tmp = of_get_property(ipar, "#interrupt-cells", NULL);
|
||||
if (tmp != NULL) {
|
||||
intsize = be32_to_cpu(*tmp);
|
||||
break;
|
||||
}
|
||||
tnode = ipar;
|
||||
ipar = of_irq_find_parent(ipar);
|
||||
of_node_put(tnode);
|
||||
} while (ipar);
|
||||
if (ipar == NULL) {
|
||||
pr_debug(" -> no parent found !\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pr_debug("of_irq_parse_raw: ipar=%s, size=%d\n", of_node_full_name(ipar), intsize);
|
||||
|
||||
if (out_irq->args_count != intsize)
|
||||
return -EINVAL;
|
||||
|
||||
/* Look for this #address-cells. We have to implement the old linux
|
||||
* trick of looking for the parent here as some device-trees rely on it
|
||||
*/
|
||||
old = of_node_get(ipar);
|
||||
do {
|
||||
tmp = of_get_property(old, "#address-cells", NULL);
|
||||
tnode = of_get_parent(old);
|
||||
of_node_put(old);
|
||||
old = tnode;
|
||||
} while (old && tmp == NULL);
|
||||
of_node_put(old);
|
||||
old = NULL;
|
||||
addrsize = (tmp == NULL) ? 2 : be32_to_cpu(*tmp);
|
||||
|
||||
pr_debug(" -> addrsize=%d\n", addrsize);
|
||||
|
||||
/* Range check so that the temporary buffer doesn't overflow */
|
||||
if (WARN_ON(addrsize + intsize > MAX_PHANDLE_ARGS))
|
||||
goto fail;
|
||||
|
||||
/* Precalculate the match array - this simplifies match loop */
|
||||
for (i = 0; i < addrsize; i++)
|
||||
initial_match_array[i] = addr ? addr[i] : 0;
|
||||
for (i = 0; i < intsize; i++)
|
||||
initial_match_array[addrsize + i] = cpu_to_be32(out_irq->args[i]);
|
||||
|
||||
/* Now start the actual "proper" walk of the interrupt tree */
|
||||
while (ipar != NULL) {
|
||||
/* Now check if cursor is an interrupt-controller and if it is
|
||||
* then we are done
|
||||
*/
|
||||
if (of_get_property(ipar, "interrupt-controller", NULL) !=
|
||||
NULL) {
|
||||
pr_debug(" -> got it !\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* interrupt-map parsing does not work without a reg
|
||||
* property when #address-cells != 0
|
||||
*/
|
||||
if (addrsize && !addr) {
|
||||
pr_debug(" -> no reg passed in when needed !\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Now look for an interrupt-map */
|
||||
imap = of_get_property(ipar, "interrupt-map", &imaplen);
|
||||
/* No interrupt map, check for an interrupt parent */
|
||||
if (imap == NULL) {
|
||||
pr_debug(" -> no map, getting parent\n");
|
||||
newpar = of_irq_find_parent(ipar);
|
||||
goto skiplevel;
|
||||
}
|
||||
imaplen /= sizeof(u32);
|
||||
|
||||
/* Look for a mask */
|
||||
imask = of_get_property(ipar, "interrupt-map-mask", NULL);
|
||||
if (!imask)
|
||||
imask = dummy_imask;
|
||||
|
||||
/* Parse interrupt-map */
|
||||
match = 0;
|
||||
while (imaplen > (addrsize + intsize + 1) && !match) {
|
||||
/* Compare specifiers */
|
||||
match = 1;
|
||||
for (i = 0; i < (addrsize + intsize); i++, imaplen--)
|
||||
match &= !((match_array[i] ^ *imap++) & imask[i]);
|
||||
|
||||
pr_debug(" -> match=%d (imaplen=%d)\n", match, imaplen);
|
||||
|
||||
/* Get the interrupt parent */
|
||||
if (of_irq_workarounds & OF_IMAP_NO_PHANDLE)
|
||||
newpar = of_node_get(of_irq_dflt_pic);
|
||||
else
|
||||
newpar = of_find_node_by_phandle(be32_to_cpup(imap));
|
||||
imap++;
|
||||
--imaplen;
|
||||
|
||||
/* Check if not found */
|
||||
if (newpar == NULL) {
|
||||
pr_debug(" -> imap parent not found !\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!of_device_is_available(newpar))
|
||||
match = 0;
|
||||
|
||||
/* Get #interrupt-cells and #address-cells of new
|
||||
* parent
|
||||
*/
|
||||
tmp = of_get_property(newpar, "#interrupt-cells", NULL);
|
||||
if (tmp == NULL) {
|
||||
pr_debug(" -> parent lacks #interrupt-cells!\n");
|
||||
goto fail;
|
||||
}
|
||||
newintsize = be32_to_cpu(*tmp);
|
||||
tmp = of_get_property(newpar, "#address-cells", NULL);
|
||||
newaddrsize = (tmp == NULL) ? 0 : be32_to_cpu(*tmp);
|
||||
|
||||
pr_debug(" -> newintsize=%d, newaddrsize=%d\n",
|
||||
newintsize, newaddrsize);
|
||||
|
||||
/* Check for malformed properties */
|
||||
if (WARN_ON(newaddrsize + newintsize > MAX_PHANDLE_ARGS))
|
||||
goto fail;
|
||||
if (imaplen < (newaddrsize + newintsize))
|
||||
goto fail;
|
||||
|
||||
imap += newaddrsize + newintsize;
|
||||
imaplen -= newaddrsize + newintsize;
|
||||
|
||||
pr_debug(" -> imaplen=%d\n", imaplen);
|
||||
}
|
||||
if (!match)
|
||||
goto fail;
|
||||
|
||||
/*
|
||||
* Successfully parsed an interrrupt-map translation; copy new
|
||||
* interrupt specifier into the out_irq structure
|
||||
*/
|
||||
out_irq->np = newpar;
|
||||
|
||||
match_array = imap - newaddrsize - newintsize;
|
||||
for (i = 0; i < newintsize; i++)
|
||||
out_irq->args[i] = be32_to_cpup(imap - newintsize + i);
|
||||
out_irq->args_count = intsize = newintsize;
|
||||
addrsize = newaddrsize;
|
||||
|
||||
skiplevel:
|
||||
/* Iterate again with new parent */
|
||||
pr_debug(" -> new parent: %s\n", of_node_full_name(newpar));
|
||||
of_node_put(ipar);
|
||||
ipar = newpar;
|
||||
newpar = NULL;
|
||||
}
|
||||
fail:
|
||||
of_node_put(ipar);
|
||||
of_node_put(newpar);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_irq_parse_raw);
|
||||
|
||||
/**
|
||||
* of_irq_parse_one - Resolve an interrupt for a device
|
||||
* @device: the device whose interrupt is to be resolved
|
||||
* @index: index of the interrupt to resolve
|
||||
* @out_irq: structure of_irq filled by this function
|
||||
*
|
||||
* This function resolves an interrupt for a node by walking the interrupt tree,
|
||||
* finding which interrupt controller node it is attached to, and returning the
|
||||
* interrupt specifier that can be used to retrieve a Linux IRQ number.
|
||||
*/
|
||||
int of_irq_parse_one(struct device_node *device, int index, struct of_phandle_args *out_irq)
|
||||
{
|
||||
struct device_node *p;
|
||||
const __be32 *intspec, *tmp, *addr;
|
||||
u32 intsize, intlen;
|
||||
int i, res;
|
||||
|
||||
pr_debug("of_irq_parse_one: dev=%s, index=%d\n", of_node_full_name(device), index);
|
||||
|
||||
/* OldWorld mac stuff is "special", handle out of line */
|
||||
if (of_irq_workarounds & OF_IMAP_OLDWORLD_MAC)
|
||||
return of_irq_parse_oldworld(device, index, out_irq);
|
||||
|
||||
/* Get the reg property (if any) */
|
||||
addr = of_get_property(device, "reg", NULL);
|
||||
|
||||
/* Try the new-style interrupts-extended first */
|
||||
res = of_parse_phandle_with_args(device, "interrupts-extended",
|
||||
"#interrupt-cells", index, out_irq);
|
||||
if (!res)
|
||||
return of_irq_parse_raw(addr, out_irq);
|
||||
|
||||
/* Get the interrupts property */
|
||||
intspec = of_get_property(device, "interrupts", &intlen);
|
||||
if (intspec == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
intlen /= sizeof(*intspec);
|
||||
|
||||
pr_debug(" intspec=%d intlen=%d\n", be32_to_cpup(intspec), intlen);
|
||||
|
||||
/* Look for the interrupt parent. */
|
||||
p = of_irq_find_parent(device);
|
||||
if (p == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
/* Get size of interrupt specifier */
|
||||
tmp = of_get_property(p, "#interrupt-cells", NULL);
|
||||
if (tmp == NULL) {
|
||||
res = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
intsize = be32_to_cpu(*tmp);
|
||||
|
||||
pr_debug(" intsize=%d intlen=%d\n", intsize, intlen);
|
||||
|
||||
/* Check index */
|
||||
if ((index + 1) * intsize > intlen) {
|
||||
res = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Copy intspec into irq structure */
|
||||
intspec += index * intsize;
|
||||
out_irq->np = p;
|
||||
out_irq->args_count = intsize;
|
||||
for (i = 0; i < intsize; i++)
|
||||
out_irq->args[i] = be32_to_cpup(intspec++);
|
||||
|
||||
/* Check if there are any interrupt-map translations to process */
|
||||
res = of_irq_parse_raw(addr, out_irq);
|
||||
out:
|
||||
of_node_put(p);
|
||||
return res;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_irq_parse_one);
|
||||
|
||||
/**
|
||||
* of_irq_to_resource - Decode a node's IRQ and return it as a resource
|
||||
* @dev: pointer to device tree node
|
||||
* @index: zero-based index of the irq
|
||||
* @r: pointer to resource structure to return result into.
|
||||
*/
|
||||
int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)
|
||||
{
|
||||
int irq = irq_of_parse_and_map(dev, index);
|
||||
|
||||
/* Only dereference the resource if both the
|
||||
* resource and the irq are valid. */
|
||||
if (r && irq) {
|
||||
const char *name = NULL;
|
||||
|
||||
memset(r, 0, sizeof(*r));
|
||||
/*
|
||||
* Get optional "interrupt-names" property to add a name
|
||||
* to the resource.
|
||||
*/
|
||||
of_property_read_string_index(dev, "interrupt-names", index,
|
||||
&name);
|
||||
|
||||
r->start = r->end = irq;
|
||||
r->flags = IORESOURCE_IRQ | irqd_get_trigger_type(irq_get_irq_data(irq));
|
||||
r->name = name ? name : of_node_full_name(dev);
|
||||
}
|
||||
|
||||
return irq;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_irq_to_resource);
|
||||
|
||||
/**
|
||||
* of_irq_get - Decode a node's IRQ and return it as a Linux irq number
|
||||
* @dev: pointer to device tree node
|
||||
* @index: zero-based index of the irq
|
||||
*
|
||||
* Returns Linux irq number on success, or -EPROBE_DEFER if the irq domain
|
||||
* is not yet created.
|
||||
*
|
||||
*/
|
||||
int of_irq_get(struct device_node *dev, int index)
|
||||
{
|
||||
int rc;
|
||||
struct of_phandle_args oirq;
|
||||
struct irq_domain *domain;
|
||||
|
||||
rc = of_irq_parse_one(dev, index, &oirq);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
domain = irq_find_host(oirq.np);
|
||||
if (!domain)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
return irq_create_of_mapping(&oirq);
|
||||
}
|
||||
|
||||
/**
|
||||
* of_irq_get_byname - Decode a node's IRQ and return it as a Linux irq number
|
||||
* @dev: pointer to device tree node
|
||||
* @name: irq name
|
||||
*
|
||||
* Returns Linux irq number on success, or -EPROBE_DEFER if the irq domain
|
||||
* is not yet created, or error code in case of any other failure.
|
||||
*/
|
||||
int of_irq_get_byname(struct device_node *dev, const char *name)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (unlikely(!name))
|
||||
return -EINVAL;
|
||||
|
||||
index = of_property_match_string(dev, "interrupt-names", name);
|
||||
if (index < 0)
|
||||
return index;
|
||||
|
||||
return of_irq_get(dev, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* of_irq_count - Count the number of IRQs a node uses
|
||||
* @dev: pointer to device tree node
|
||||
*/
|
||||
int of_irq_count(struct device_node *dev)
|
||||
{
|
||||
struct of_phandle_args irq;
|
||||
int nr = 0;
|
||||
|
||||
while (of_irq_parse_one(dev, nr, &irq) == 0)
|
||||
nr++;
|
||||
|
||||
return nr;
|
||||
}
|
||||
|
||||
/**
|
||||
* of_irq_to_resource_table - Fill in resource table with node's IRQ info
|
||||
* @dev: pointer to device tree node
|
||||
* @res: array of resources to fill in
|
||||
* @nr_irqs: the number of IRQs (and upper bound for num of @res elements)
|
||||
*
|
||||
* Returns the size of the filled in table (up to @nr_irqs).
|
||||
*/
|
||||
int of_irq_to_resource_table(struct device_node *dev, struct resource *res,
|
||||
int nr_irqs)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nr_irqs; i++, res++)
|
||||
if (!of_irq_to_resource(dev, i, res))
|
||||
break;
|
||||
|
||||
return i;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_irq_to_resource_table);
|
||||
|
||||
struct intc_desc {
|
||||
struct list_head list;
|
||||
struct device_node *dev;
|
||||
struct device_node *interrupt_parent;
|
||||
};
|
||||
|
||||
/**
|
||||
* of_irq_init - Scan and init matching interrupt controllers in DT
|
||||
* @matches: 0 terminated array of nodes to match and init function to call
|
||||
*
|
||||
* This function scans the device tree for matching interrupt controller nodes,
|
||||
* and calls their initialization functions in order with parents first.
|
||||
*/
|
||||
void __init of_irq_init(const struct of_device_id *matches)
|
||||
{
|
||||
struct device_node *np, *parent = NULL;
|
||||
struct intc_desc *desc, *temp_desc;
|
||||
struct list_head intc_desc_list, intc_parent_list;
|
||||
|
||||
INIT_LIST_HEAD(&intc_desc_list);
|
||||
INIT_LIST_HEAD(&intc_parent_list);
|
||||
|
||||
for_each_matching_node(np, matches) {
|
||||
if (!of_find_property(np, "interrupt-controller", NULL) ||
|
||||
!of_device_is_available(np))
|
||||
continue;
|
||||
/*
|
||||
* Here, we allocate and populate an intc_desc with the node
|
||||
* pointer, interrupt-parent device_node etc.
|
||||
*/
|
||||
desc = kzalloc(sizeof(*desc), GFP_KERNEL);
|
||||
if (WARN_ON(!desc))
|
||||
goto err;
|
||||
|
||||
desc->dev = np;
|
||||
desc->interrupt_parent = of_irq_find_parent(np);
|
||||
if (desc->interrupt_parent == np)
|
||||
desc->interrupt_parent = NULL;
|
||||
list_add_tail(&desc->list, &intc_desc_list);
|
||||
}
|
||||
|
||||
/*
|
||||
* The root irq controller is the one without an interrupt-parent.
|
||||
* That one goes first, followed by the controllers that reference it,
|
||||
* followed by the ones that reference the 2nd level controllers, etc.
|
||||
*/
|
||||
while (!list_empty(&intc_desc_list)) {
|
||||
/*
|
||||
* Process all controllers with the current 'parent'.
|
||||
* First pass will be looking for NULL as the parent.
|
||||
* The assumption is that NULL parent means a root controller.
|
||||
*/
|
||||
list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
|
||||
const struct of_device_id *match;
|
||||
int ret;
|
||||
of_irq_init_cb_t irq_init_cb;
|
||||
|
||||
if (desc->interrupt_parent != parent)
|
||||
continue;
|
||||
|
||||
list_del(&desc->list);
|
||||
match = of_match_node(matches, desc->dev);
|
||||
if (WARN(!match->data,
|
||||
"of_irq_init: no init function for %s\n",
|
||||
match->compatible)) {
|
||||
kfree(desc);
|
||||
continue;
|
||||
}
|
||||
|
||||
pr_debug("of_irq_init: init %s @ %p, parent %p\n",
|
||||
match->compatible,
|
||||
desc->dev, desc->interrupt_parent);
|
||||
irq_init_cb = (of_irq_init_cb_t)match->data;
|
||||
ret = irq_init_cb(desc->dev, desc->interrupt_parent);
|
||||
if (ret) {
|
||||
kfree(desc);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* This one is now set up; add it to the parent list so
|
||||
* its children can get processed in a subsequent pass.
|
||||
*/
|
||||
list_add_tail(&desc->list, &intc_parent_list);
|
||||
}
|
||||
|
||||
/* Get the next pending parent that might have children */
|
||||
desc = list_first_entry_or_null(&intc_parent_list,
|
||||
typeof(*desc), list);
|
||||
if (!desc) {
|
||||
pr_err("of_irq_init: children remain, but no parents\n");
|
||||
break;
|
||||
}
|
||||
list_del(&desc->list);
|
||||
parent = desc->dev;
|
||||
kfree(desc);
|
||||
}
|
||||
|
||||
list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) {
|
||||
list_del(&desc->list);
|
||||
kfree(desc);
|
||||
}
|
||||
err:
|
||||
list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
|
||||
list_del(&desc->list);
|
||||
kfree(desc);
|
||||
}
|
||||
}
|
322
drivers/of/of_mdio.c
Normal file
322
drivers/of/of_mdio.c
Normal file
|
@ -0,0 +1,322 @@
|
|||
/*
|
||||
* OF helpers for the MDIO (Ethernet PHY) API
|
||||
*
|
||||
* Copyright (c) 2009 Secret Lab Technologies, Ltd.
|
||||
*
|
||||
* This file is released under the GPLv2
|
||||
*
|
||||
* This file provides helper functions for extracting PHY device information
|
||||
* out of the OpenFirmware device tree and using it to populate an mii_bus.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/phy_fixed.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_mdio.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
/* Extract the clause 22 phy ID from the compatible string of the form
|
||||
* ethernet-phy-idAAAA.BBBB */
|
||||
static int of_get_phy_id(struct device_node *device, u32 *phy_id)
|
||||
{
|
||||
struct property *prop;
|
||||
const char *cp;
|
||||
unsigned int upper, lower;
|
||||
|
||||
of_property_for_each_string(device, "compatible", prop, cp) {
|
||||
if (sscanf(cp, "ethernet-phy-id%4x.%4x", &upper, &lower) == 2) {
|
||||
*phy_id = ((upper & 0xFFFF) << 16) | (lower & 0xFFFF);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int of_mdiobus_register_phy(struct mii_bus *mdio, struct device_node *child,
|
||||
u32 addr)
|
||||
{
|
||||
struct phy_device *phy;
|
||||
bool is_c45;
|
||||
int rc;
|
||||
u32 phy_id;
|
||||
|
||||
is_c45 = of_device_is_compatible(child,
|
||||
"ethernet-phy-ieee802.3-c45");
|
||||
|
||||
if (!is_c45 && !of_get_phy_id(child, &phy_id))
|
||||
phy = phy_device_create(mdio, addr, phy_id, 0, NULL);
|
||||
else
|
||||
phy = get_phy_device(mdio, addr, is_c45);
|
||||
if (!phy || IS_ERR(phy))
|
||||
return 1;
|
||||
|
||||
rc = irq_of_parse_and_map(child, 0);
|
||||
if (rc > 0) {
|
||||
phy->irq = rc;
|
||||
if (mdio->irq)
|
||||
mdio->irq[addr] = rc;
|
||||
} else {
|
||||
if (mdio->irq)
|
||||
phy->irq = mdio->irq[addr];
|
||||
}
|
||||
|
||||
/* Associate the OF node with the device structure so it
|
||||
* can be looked up later */
|
||||
of_node_get(child);
|
||||
phy->dev.of_node = child;
|
||||
|
||||
/* All data is now stored in the phy struct;
|
||||
* register it */
|
||||
rc = phy_device_register(phy);
|
||||
if (rc) {
|
||||
phy_device_free(phy);
|
||||
of_node_put(child);
|
||||
return 1;
|
||||
}
|
||||
|
||||
dev_dbg(&mdio->dev, "registered phy %s at address %i\n",
|
||||
child->name, addr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int of_mdio_parse_addr(struct device *dev, const struct device_node *np)
|
||||
{
|
||||
u32 addr;
|
||||
int ret;
|
||||
|
||||
ret = of_property_read_u32(np, "reg", &addr);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s has invalid PHY address\n", np->full_name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* A PHY must have a reg property in the range [0-31] */
|
||||
if (addr >= PHY_MAX_ADDR) {
|
||||
dev_err(dev, "%s PHY address %i is too large\n",
|
||||
np->full_name, addr);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
/**
|
||||
* of_mdiobus_register - Register mii_bus and create PHYs from the device tree
|
||||
* @mdio: pointer to mii_bus structure
|
||||
* @np: pointer to device_node of MDIO bus.
|
||||
*
|
||||
* This function registers the mii_bus structure and registers a phy_device
|
||||
* for each child node of @np.
|
||||
*/
|
||||
int of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)
|
||||
{
|
||||
struct device_node *child;
|
||||
const __be32 *paddr;
|
||||
bool scanphys = false;
|
||||
int addr, rc, i;
|
||||
|
||||
/* Mask out all PHYs from auto probing. Instead the PHYs listed in
|
||||
* the device tree are populated after the bus has been registered */
|
||||
mdio->phy_mask = ~0;
|
||||
|
||||
/* Clear all the IRQ properties */
|
||||
if (mdio->irq)
|
||||
for (i=0; i<PHY_MAX_ADDR; i++)
|
||||
mdio->irq[i] = PHY_POLL;
|
||||
|
||||
mdio->dev.of_node = np;
|
||||
|
||||
/* Register the MDIO bus */
|
||||
rc = mdiobus_register(mdio);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* Loop over the child nodes and register a phy_device for each one */
|
||||
for_each_available_child_of_node(np, child) {
|
||||
addr = of_mdio_parse_addr(&mdio->dev, child);
|
||||
if (addr < 0) {
|
||||
scanphys = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
rc = of_mdiobus_register_phy(mdio, child, addr);
|
||||
if (rc)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!scanphys)
|
||||
return 0;
|
||||
|
||||
/* auto scan for PHYs with empty reg property */
|
||||
for_each_available_child_of_node(np, child) {
|
||||
/* Skip PHYs with reg property set */
|
||||
paddr = of_get_property(child, "reg", NULL);
|
||||
if (paddr)
|
||||
continue;
|
||||
|
||||
for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
|
||||
/* skip already registered PHYs */
|
||||
if (mdio->phy_map[addr])
|
||||
continue;
|
||||
|
||||
/* be noisy to encourage people to set reg property */
|
||||
dev_info(&mdio->dev, "scan phy %s at address %i\n",
|
||||
child->name, addr);
|
||||
|
||||
rc = of_mdiobus_register_phy(mdio, child, addr);
|
||||
if (rc)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(of_mdiobus_register);
|
||||
|
||||
/* Helper function for of_phy_find_device */
|
||||
static int of_phy_match(struct device *dev, void *phy_np)
|
||||
{
|
||||
return dev->of_node == phy_np;
|
||||
}
|
||||
|
||||
/**
|
||||
* of_phy_find_device - Give a PHY node, find the phy_device
|
||||
* @phy_np: Pointer to the phy's device tree node
|
||||
*
|
||||
* Returns a pointer to the phy_device.
|
||||
*/
|
||||
struct phy_device *of_phy_find_device(struct device_node *phy_np)
|
||||
{
|
||||
struct device *d;
|
||||
if (!phy_np)
|
||||
return NULL;
|
||||
|
||||
d = bus_find_device(&mdio_bus_type, NULL, phy_np, of_phy_match);
|
||||
return d ? to_phy_device(d) : NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(of_phy_find_device);
|
||||
|
||||
/**
|
||||
* of_phy_connect - Connect to the phy described in the device tree
|
||||
* @dev: pointer to net_device claiming the phy
|
||||
* @phy_np: Pointer to device tree node for the PHY
|
||||
* @hndlr: Link state callback for the network device
|
||||
* @iface: PHY data interface type
|
||||
*
|
||||
* Returns a pointer to the phy_device if successful. NULL otherwise
|
||||
*/
|
||||
struct phy_device *of_phy_connect(struct net_device *dev,
|
||||
struct device_node *phy_np,
|
||||
void (*hndlr)(struct net_device *), u32 flags,
|
||||
phy_interface_t iface)
|
||||
{
|
||||
struct phy_device *phy = of_phy_find_device(phy_np);
|
||||
|
||||
if (!phy)
|
||||
return NULL;
|
||||
|
||||
phy->dev_flags = flags;
|
||||
|
||||
return phy_connect_direct(dev, phy, hndlr, iface) ? NULL : phy;
|
||||
}
|
||||
EXPORT_SYMBOL(of_phy_connect);
|
||||
|
||||
/**
|
||||
* of_phy_attach - Attach to a PHY without starting the state machine
|
||||
* @dev: pointer to net_device claiming the phy
|
||||
* @phy_np: Node pointer for the PHY
|
||||
* @flags: flags to pass to the PHY
|
||||
* @iface: PHY data interface type
|
||||
*/
|
||||
struct phy_device *of_phy_attach(struct net_device *dev,
|
||||
struct device_node *phy_np, u32 flags,
|
||||
phy_interface_t iface)
|
||||
{
|
||||
struct phy_device *phy = of_phy_find_device(phy_np);
|
||||
|
||||
if (!phy)
|
||||
return NULL;
|
||||
|
||||
return phy_attach_direct(dev, phy, flags, iface) ? NULL : phy;
|
||||
}
|
||||
EXPORT_SYMBOL(of_phy_attach);
|
||||
|
||||
#if defined(CONFIG_FIXED_PHY)
|
||||
/*
|
||||
* of_phy_is_fixed_link() and of_phy_register_fixed_link() must
|
||||
* support two DT bindings:
|
||||
* - the old DT binding, where 'fixed-link' was a property with 5
|
||||
* cells encoding various informations about the fixed PHY
|
||||
* - the new DT binding, where 'fixed-link' is a sub-node of the
|
||||
* Ethernet device.
|
||||
*/
|
||||
bool of_phy_is_fixed_link(struct device_node *np)
|
||||
{
|
||||
struct device_node *dn;
|
||||
int len;
|
||||
|
||||
/* New binding */
|
||||
dn = of_get_child_by_name(np, "fixed-link");
|
||||
if (dn) {
|
||||
of_node_put(dn);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Old binding */
|
||||
if (of_get_property(np, "fixed-link", &len) &&
|
||||
len == (5 * sizeof(__be32)))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
EXPORT_SYMBOL(of_phy_is_fixed_link);
|
||||
|
||||
int of_phy_register_fixed_link(struct device_node *np)
|
||||
{
|
||||
struct fixed_phy_status status = {};
|
||||
struct device_node *fixed_link_node;
|
||||
const __be32 *fixed_link_prop;
|
||||
int len;
|
||||
struct phy_device *phy;
|
||||
|
||||
/* New binding */
|
||||
fixed_link_node = of_get_child_by_name(np, "fixed-link");
|
||||
if (fixed_link_node) {
|
||||
status.link = 1;
|
||||
status.duplex = of_property_read_bool(fixed_link_node,
|
||||
"full-duplex");
|
||||
if (of_property_read_u32(fixed_link_node, "speed", &status.speed))
|
||||
return -EINVAL;
|
||||
status.pause = of_property_read_bool(fixed_link_node, "pause");
|
||||
status.asym_pause = of_property_read_bool(fixed_link_node,
|
||||
"asym-pause");
|
||||
of_node_put(fixed_link_node);
|
||||
phy = fixed_phy_register(PHY_POLL, &status, np);
|
||||
return IS_ERR(phy) ? PTR_ERR(phy) : 0;
|
||||
}
|
||||
|
||||
/* Old binding */
|
||||
fixed_link_prop = of_get_property(np, "fixed-link", &len);
|
||||
if (fixed_link_prop && len == (5 * sizeof(__be32))) {
|
||||
status.link = 1;
|
||||
status.duplex = be32_to_cpu(fixed_link_prop[1]);
|
||||
status.speed = be32_to_cpu(fixed_link_prop[2]);
|
||||
status.pause = be32_to_cpu(fixed_link_prop[3]);
|
||||
status.asym_pause = be32_to_cpu(fixed_link_prop[4]);
|
||||
phy = fixed_phy_register(PHY_POLL, &status, np);
|
||||
return IS_ERR(phy) ? PTR_ERR(phy) : 0;
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
EXPORT_SYMBOL(of_phy_register_fixed_link);
|
||||
#endif
|
119
drivers/of/of_mtd.c
Normal file
119
drivers/of/of_mtd.c
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright 2012 Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com>
|
||||
*
|
||||
* OF helpers for mtd.
|
||||
*
|
||||
* This file is released under the GPLv2
|
||||
*
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/of_mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/export.h>
|
||||
|
||||
/**
|
||||
* It maps 'enum nand_ecc_modes_t' found in include/linux/mtd/nand.h
|
||||
* into the device tree binding of 'nand-ecc', so that MTD
|
||||
* device driver can get nand ecc from device tree.
|
||||
*/
|
||||
static const char *nand_ecc_modes[] = {
|
||||
[NAND_ECC_NONE] = "none",
|
||||
[NAND_ECC_SOFT] = "soft",
|
||||
[NAND_ECC_HW] = "hw",
|
||||
[NAND_ECC_HW_SYNDROME] = "hw_syndrome",
|
||||
[NAND_ECC_HW_OOB_FIRST] = "hw_oob_first",
|
||||
[NAND_ECC_SOFT_BCH] = "soft_bch",
|
||||
};
|
||||
|
||||
/**
|
||||
* of_get_nand_ecc_mode - Get nand ecc mode for given device_node
|
||||
* @np: Pointer to the given device_node
|
||||
*
|
||||
* The function gets ecc mode string from property 'nand-ecc-mode',
|
||||
* and return its index in nand_ecc_modes table, or errno in error case.
|
||||
*/
|
||||
int of_get_nand_ecc_mode(struct device_node *np)
|
||||
{
|
||||
const char *pm;
|
||||
int err, i;
|
||||
|
||||
err = of_property_read_string(np, "nand-ecc-mode", &pm);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(nand_ecc_modes); i++)
|
||||
if (!strcasecmp(pm, nand_ecc_modes[i]))
|
||||
return i;
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_get_nand_ecc_mode);
|
||||
|
||||
/**
|
||||
* of_get_nand_ecc_step_size - Get ECC step size associated to
|
||||
* the required ECC strength (see below).
|
||||
* @np: Pointer to the given device_node
|
||||
*
|
||||
* return the ECC step size, or errno in error case.
|
||||
*/
|
||||
int of_get_nand_ecc_step_size(struct device_node *np)
|
||||
{
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
ret = of_property_read_u32(np, "nand-ecc-step-size", &val);
|
||||
return ret ? ret : val;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_get_nand_ecc_step_size);
|
||||
|
||||
/**
|
||||
* of_get_nand_ecc_strength - Get required ECC strength over the
|
||||
* correspnding step size as defined by 'nand-ecc-size'
|
||||
* @np: Pointer to the given device_node
|
||||
*
|
||||
* return the ECC strength, or errno in error case.
|
||||
*/
|
||||
int of_get_nand_ecc_strength(struct device_node *np)
|
||||
{
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
ret = of_property_read_u32(np, "nand-ecc-strength", &val);
|
||||
return ret ? ret : val;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_get_nand_ecc_strength);
|
||||
|
||||
/**
|
||||
* of_get_nand_bus_width - Get nand bus witdh for given device_node
|
||||
* @np: Pointer to the given device_node
|
||||
*
|
||||
* return bus width option, or errno in error case.
|
||||
*/
|
||||
int of_get_nand_bus_width(struct device_node *np)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
if (of_property_read_u32(np, "nand-bus-width", &val))
|
||||
return 8;
|
||||
|
||||
switch(val) {
|
||||
case 8:
|
||||
case 16:
|
||||
return val;
|
||||
default:
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_get_nand_bus_width);
|
||||
|
||||
/**
|
||||
* of_get_nand_on_flash_bbt - Get nand on flash bbt for given device_node
|
||||
* @np: Pointer to the given device_node
|
||||
*
|
||||
* return true if present false other wise
|
||||
*/
|
||||
bool of_get_nand_on_flash_bbt(struct device_node *np)
|
||||
{
|
||||
return of_property_read_bool(np, "nand-on-flash-bbt");
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_get_nand_on_flash_bbt);
|
77
drivers/of/of_net.c
Normal file
77
drivers/of/of_net.c
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* OF helpers for network devices.
|
||||
*
|
||||
* This file is released under the GPLv2
|
||||
*
|
||||
* Initially copied out of arch/powerpc/kernel/prom_parse.c
|
||||
*/
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/of_net.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/export.h>
|
||||
|
||||
/**
|
||||
* of_get_phy_mode - Get phy mode for given device_node
|
||||
* @np: Pointer to the given device_node
|
||||
*
|
||||
* The function gets phy interface string from property 'phy-mode' or
|
||||
* 'phy-connection-type', and return its index in phy_modes table, or errno in
|
||||
* error case.
|
||||
*/
|
||||
int of_get_phy_mode(struct device_node *np)
|
||||
{
|
||||
const char *pm;
|
||||
int err, i;
|
||||
|
||||
err = of_property_read_string(np, "phy-mode", &pm);
|
||||
if (err < 0)
|
||||
err = of_property_read_string(np, "phy-connection-type", &pm);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
for (i = 0; i < PHY_INTERFACE_MODE_MAX; i++)
|
||||
if (!strcasecmp(pm, phy_modes(i)))
|
||||
return i;
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_get_phy_mode);
|
||||
|
||||
/**
|
||||
* Search the device tree for the best MAC address to use. 'mac-address' is
|
||||
* checked first, because that is supposed to contain to "most recent" MAC
|
||||
* address. If that isn't set, then 'local-mac-address' is checked next,
|
||||
* because that is the default address. If that isn't set, then the obsolete
|
||||
* 'address' is checked, just in case we're using an old device tree.
|
||||
*
|
||||
* Note that the 'address' property is supposed to contain a virtual address of
|
||||
* the register set, but some DTS files have redefined that property to be the
|
||||
* MAC address.
|
||||
*
|
||||
* All-zero MAC addresses are rejected, because those could be properties that
|
||||
* exist in the device tree, but were not set by U-Boot. For example, the
|
||||
* DTS could define 'mac-address' and 'local-mac-address', with zero MAC
|
||||
* addresses. Some older U-Boots only initialized 'local-mac-address'. In
|
||||
* this case, the real MAC is in 'local-mac-address', and 'mac-address' exists
|
||||
* but is all zeros.
|
||||
*/
|
||||
const void *of_get_mac_address(struct device_node *np)
|
||||
{
|
||||
struct property *pp;
|
||||
|
||||
pp = of_find_property(np, "mac-address", NULL);
|
||||
if (pp && (pp->length == 6) && is_valid_ether_addr(pp->value))
|
||||
return pp->value;
|
||||
|
||||
pp = of_find_property(np, "local-mac-address", NULL);
|
||||
if (pp && (pp->length == 6) && is_valid_ether_addr(pp->value))
|
||||
return pp->value;
|
||||
|
||||
pp = of_find_property(np, "address", NULL);
|
||||
if (pp && (pp->length == 6) && is_valid_ether_addr(pp->value))
|
||||
return pp->value;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(of_get_mac_address);
|
281
drivers/of/of_pci.c
Normal file
281
drivers/of/of_pci.c
Normal file
|
@ -0,0 +1,281 @@
|
|||
#include <linux/kernel.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_pci.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
static inline int __of_pci_pci_compare(struct device_node *node,
|
||||
unsigned int data)
|
||||
{
|
||||
int devfn;
|
||||
|
||||
devfn = of_pci_get_devfn(node);
|
||||
if (devfn < 0)
|
||||
return 0;
|
||||
|
||||
return devfn == data;
|
||||
}
|
||||
|
||||
struct device_node *of_pci_find_child_device(struct device_node *parent,
|
||||
unsigned int devfn)
|
||||
{
|
||||
struct device_node *node, *node2;
|
||||
|
||||
for_each_child_of_node(parent, node) {
|
||||
if (__of_pci_pci_compare(node, devfn))
|
||||
return node;
|
||||
/*
|
||||
* Some OFs create a parent node "multifunc-device" as
|
||||
* a fake root for all functions of a multi-function
|
||||
* device we go down them as well.
|
||||
*/
|
||||
if (!strcmp(node->name, "multifunc-device")) {
|
||||
for_each_child_of_node(node, node2) {
|
||||
if (__of_pci_pci_compare(node2, devfn)) {
|
||||
of_node_put(node);
|
||||
return node2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_pci_find_child_device);
|
||||
|
||||
/**
|
||||
* of_pci_get_devfn() - Get device and function numbers for a device node
|
||||
* @np: device node
|
||||
*
|
||||
* Parses a standard 5-cell PCI resource and returns an 8-bit value that can
|
||||
* be passed to the PCI_SLOT() and PCI_FUNC() macros to extract the device
|
||||
* and function numbers respectively. On error a negative error code is
|
||||
* returned.
|
||||
*/
|
||||
int of_pci_get_devfn(struct device_node *np)
|
||||
{
|
||||
unsigned int size;
|
||||
const __be32 *reg;
|
||||
|
||||
reg = of_get_property(np, "reg", &size);
|
||||
|
||||
if (!reg || size < 5 * sizeof(__be32))
|
||||
return -EINVAL;
|
||||
|
||||
return (be32_to_cpup(reg) >> 8) & 0xff;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_pci_get_devfn);
|
||||
|
||||
/**
|
||||
* of_pci_parse_bus_range() - parse the bus-range property of a PCI device
|
||||
* @node: device node
|
||||
* @res: address to a struct resource to return the bus-range
|
||||
*
|
||||
* Returns 0 on success or a negative error-code on failure.
|
||||
*/
|
||||
int of_pci_parse_bus_range(struct device_node *node, struct resource *res)
|
||||
{
|
||||
const __be32 *values;
|
||||
int len;
|
||||
|
||||
values = of_get_property(node, "bus-range", &len);
|
||||
if (!values || len < sizeof(*values) * 2)
|
||||
return -EINVAL;
|
||||
|
||||
res->name = node->name;
|
||||
res->start = be32_to_cpup(values++);
|
||||
res->end = be32_to_cpup(values);
|
||||
res->flags = IORESOURCE_BUS;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_pci_parse_bus_range);
|
||||
|
||||
/**
|
||||
* This function will try to obtain the host bridge domain number by
|
||||
* finding a property called "linux,pci-domain" of the given device node.
|
||||
*
|
||||
* @node: device tree node with the domain information
|
||||
*
|
||||
* Returns the associated domain number from DT in the range [0-0xffff], or
|
||||
* a negative value if the required property is not found.
|
||||
*/
|
||||
int of_get_pci_domain_nr(struct device_node *node)
|
||||
{
|
||||
const __be32 *value;
|
||||
int len;
|
||||
u16 domain;
|
||||
|
||||
value = of_get_property(node, "linux,pci-domain", &len);
|
||||
if (!value || len < sizeof(*value))
|
||||
return -EINVAL;
|
||||
|
||||
domain = (u16)be32_to_cpup(value);
|
||||
|
||||
return domain;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_get_pci_domain_nr);
|
||||
|
||||
#if defined(CONFIG_OF_ADDRESS)
|
||||
/**
|
||||
* of_pci_get_host_bridge_resources - Parse PCI host bridge resources from DT
|
||||
* @dev: device node of the host bridge having the range property
|
||||
* @busno: bus number associated with the bridge root bus
|
||||
* @bus_max: maximum number of buses for this bridge
|
||||
* @resources: list where the range of resources will be added after DT parsing
|
||||
* @io_base: pointer to a variable that will contain on return the physical
|
||||
* address for the start of the I/O range. Can be NULL if the caller doesn't
|
||||
* expect IO ranges to be present in the device tree.
|
||||
*
|
||||
* It is the caller's job to free the @resources list.
|
||||
*
|
||||
* This function will parse the "ranges" property of a PCI host bridge device
|
||||
* node and setup the resource mapping based on its content. It is expected
|
||||
* that the property conforms with the Power ePAPR document.
|
||||
*
|
||||
* It returns zero if the range parsing has been successful or a standard error
|
||||
* value if it failed.
|
||||
*/
|
||||
int of_pci_get_host_bridge_resources(struct device_node *dev,
|
||||
unsigned char busno, unsigned char bus_max,
|
||||
struct list_head *resources, resource_size_t *io_base)
|
||||
{
|
||||
struct pci_host_bridge_window *window;
|
||||
struct resource *res;
|
||||
struct resource *bus_range;
|
||||
struct of_pci_range range;
|
||||
struct of_pci_range_parser parser;
|
||||
char range_type[4];
|
||||
int err;
|
||||
|
||||
if (io_base)
|
||||
*io_base = (resource_size_t)OF_BAD_ADDR;
|
||||
|
||||
bus_range = kzalloc(sizeof(*bus_range), GFP_KERNEL);
|
||||
if (!bus_range)
|
||||
return -ENOMEM;
|
||||
|
||||
pr_info("PCI host bridge %s ranges:\n", dev->full_name);
|
||||
|
||||
err = of_pci_parse_bus_range(dev, bus_range);
|
||||
if (err) {
|
||||
bus_range->start = busno;
|
||||
bus_range->end = bus_max;
|
||||
bus_range->flags = IORESOURCE_BUS;
|
||||
pr_info(" No bus range found for %s, using %pR\n",
|
||||
dev->full_name, bus_range);
|
||||
} else {
|
||||
if (bus_range->end > bus_range->start + bus_max)
|
||||
bus_range->end = bus_range->start + bus_max;
|
||||
}
|
||||
pci_add_resource(resources, bus_range);
|
||||
|
||||
/* Check for ranges property */
|
||||
err = of_pci_range_parser_init(&parser, dev);
|
||||
if (err)
|
||||
goto parse_failed;
|
||||
|
||||
pr_debug("Parsing ranges property...\n");
|
||||
for_each_of_pci_range(&parser, &range) {
|
||||
/* Read next ranges element */
|
||||
if ((range.flags & IORESOURCE_TYPE_BITS) == IORESOURCE_IO)
|
||||
snprintf(range_type, 4, " IO");
|
||||
else if ((range.flags & IORESOURCE_TYPE_BITS) == IORESOURCE_MEM)
|
||||
snprintf(range_type, 4, "MEM");
|
||||
else
|
||||
snprintf(range_type, 4, "err");
|
||||
pr_info(" %s %#010llx..%#010llx -> %#010llx\n", range_type,
|
||||
range.cpu_addr, range.cpu_addr + range.size - 1,
|
||||
range.pci_addr);
|
||||
|
||||
/*
|
||||
* If we failed translation or got a zero-sized region
|
||||
* then skip this range
|
||||
*/
|
||||
if (range.cpu_addr == OF_BAD_ADDR || range.size == 0)
|
||||
continue;
|
||||
|
||||
res = kzalloc(sizeof(struct resource), GFP_KERNEL);
|
||||
if (!res) {
|
||||
err = -ENOMEM;
|
||||
goto parse_failed;
|
||||
}
|
||||
|
||||
err = of_pci_range_to_resource(&range, dev, res);
|
||||
if (err)
|
||||
goto conversion_failed;
|
||||
|
||||
if (resource_type(res) == IORESOURCE_IO) {
|
||||
if (!io_base) {
|
||||
pr_err("I/O range found for %s. Please provide an io_base pointer to save CPU base address\n",
|
||||
dev->full_name);
|
||||
err = -EINVAL;
|
||||
goto conversion_failed;
|
||||
}
|
||||
if (*io_base != (resource_size_t)OF_BAD_ADDR)
|
||||
pr_warn("More than one I/O resource converted for %s. CPU base address for old range lost!\n",
|
||||
dev->full_name);
|
||||
*io_base = range.cpu_addr;
|
||||
}
|
||||
|
||||
pci_add_resource_offset(resources, res, res->start - range.pci_addr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
conversion_failed:
|
||||
kfree(res);
|
||||
parse_failed:
|
||||
list_for_each_entry(window, resources, list)
|
||||
kfree(window->res);
|
||||
pci_free_resource_list(resources);
|
||||
kfree(bus_range);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_pci_get_host_bridge_resources);
|
||||
#endif /* CONFIG_OF_ADDRESS */
|
||||
|
||||
#ifdef CONFIG_PCI_MSI
|
||||
|
||||
static LIST_HEAD(of_pci_msi_chip_list);
|
||||
static DEFINE_MUTEX(of_pci_msi_chip_mutex);
|
||||
|
||||
int of_pci_msi_chip_add(struct msi_chip *chip)
|
||||
{
|
||||
if (!of_property_read_bool(chip->of_node, "msi-controller"))
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&of_pci_msi_chip_mutex);
|
||||
list_add(&chip->list, &of_pci_msi_chip_list);
|
||||
mutex_unlock(&of_pci_msi_chip_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_pci_msi_chip_add);
|
||||
|
||||
void of_pci_msi_chip_remove(struct msi_chip *chip)
|
||||
{
|
||||
mutex_lock(&of_pci_msi_chip_mutex);
|
||||
list_del(&chip->list);
|
||||
mutex_unlock(&of_pci_msi_chip_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_pci_msi_chip_remove);
|
||||
|
||||
struct msi_chip *of_pci_find_msi_chip_by_node(struct device_node *of_node)
|
||||
{
|
||||
struct msi_chip *c;
|
||||
|
||||
mutex_lock(&of_pci_msi_chip_mutex);
|
||||
list_for_each_entry(c, &of_pci_msi_chip_list, list) {
|
||||
if (c->of_node == of_node) {
|
||||
mutex_unlock(&of_pci_msi_chip_mutex);
|
||||
return c;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&of_pci_msi_chip_mutex);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_pci_find_msi_chip_by_node);
|
||||
|
||||
#endif /* CONFIG_PCI_MSI */
|
116
drivers/of/of_pci_irq.c
Normal file
116
drivers/of/of_pci_irq.c
Normal file
|
@ -0,0 +1,116 @@
|
|||
#include <linux/kernel.h>
|
||||
#include <linux/of_pci.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/export.h>
|
||||
|
||||
/**
|
||||
* of_irq_parse_pci - Resolve the interrupt for a PCI device
|
||||
* @pdev: the device whose interrupt is to be resolved
|
||||
* @out_irq: structure of_irq filled by this function
|
||||
*
|
||||
* This function resolves the PCI interrupt for a given PCI device. If a
|
||||
* device-node exists for a given pci_dev, it will use normal OF tree
|
||||
* walking. If not, it will implement standard swizzling and walk up the
|
||||
* PCI tree until an device-node is found, at which point it will finish
|
||||
* resolving using the OF tree walking.
|
||||
*/
|
||||
int of_irq_parse_pci(const struct pci_dev *pdev, struct of_phandle_args *out_irq)
|
||||
{
|
||||
struct device_node *dn, *ppnode;
|
||||
struct pci_dev *ppdev;
|
||||
__be32 laddr[3];
|
||||
u8 pin;
|
||||
int rc;
|
||||
|
||||
/* Check if we have a device node, if yes, fallback to standard
|
||||
* device tree parsing
|
||||
*/
|
||||
dn = pci_device_to_OF_node(pdev);
|
||||
if (dn) {
|
||||
rc = of_irq_parse_one(dn, 0, out_irq);
|
||||
if (!rc)
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Ok, we don't, time to have fun. Let's start by building up an
|
||||
* interrupt spec. we assume #interrupt-cells is 1, which is standard
|
||||
* for PCI. If you do different, then don't use that routine.
|
||||
*/
|
||||
rc = pci_read_config_byte(pdev, PCI_INTERRUPT_PIN, &pin);
|
||||
if (rc != 0)
|
||||
return rc;
|
||||
/* No pin, exit */
|
||||
if (pin == 0)
|
||||
return -ENODEV;
|
||||
|
||||
/* Now we walk up the PCI tree */
|
||||
for (;;) {
|
||||
/* Get the pci_dev of our parent */
|
||||
ppdev = pdev->bus->self;
|
||||
|
||||
/* Ouch, it's a host bridge... */
|
||||
if (ppdev == NULL) {
|
||||
ppnode = pci_bus_to_OF_node(pdev->bus);
|
||||
|
||||
/* No node for host bridge ? give up */
|
||||
if (ppnode == NULL)
|
||||
return -EINVAL;
|
||||
} else {
|
||||
/* We found a P2P bridge, check if it has a node */
|
||||
ppnode = pci_device_to_OF_node(ppdev);
|
||||
}
|
||||
|
||||
/* Ok, we have found a parent with a device-node, hand over to
|
||||
* the OF parsing code.
|
||||
* We build a unit address from the linux device to be used for
|
||||
* resolution. Note that we use the linux bus number which may
|
||||
* not match your firmware bus numbering.
|
||||
* Fortunately, in most cases, interrupt-map-mask doesn't
|
||||
* include the bus number as part of the matching.
|
||||
* You should still be careful about that though if you intend
|
||||
* to rely on this function (you ship a firmware that doesn't
|
||||
* create device nodes for all PCI devices).
|
||||
*/
|
||||
if (ppnode)
|
||||
break;
|
||||
|
||||
/* We can only get here if we hit a P2P bridge with no node,
|
||||
* let's do standard swizzling and try again
|
||||
*/
|
||||
pin = pci_swizzle_interrupt_pin(pdev, pin);
|
||||
pdev = ppdev;
|
||||
}
|
||||
|
||||
out_irq->np = ppnode;
|
||||
out_irq->args_count = 1;
|
||||
out_irq->args[0] = pin;
|
||||
laddr[0] = cpu_to_be32((pdev->bus->number << 16) | (pdev->devfn << 8));
|
||||
laddr[1] = laddr[2] = cpu_to_be32(0);
|
||||
return of_irq_parse_raw(laddr, out_irq);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_irq_parse_pci);
|
||||
|
||||
/**
|
||||
* of_irq_parse_and_map_pci() - Decode a PCI irq from the device tree and map to a virq
|
||||
* @dev: The pci device needing an irq
|
||||
* @slot: PCI slot number; passed when used as map_irq callback. Unused
|
||||
* @pin: PCI irq pin number; passed when used as map_irq callback. Unused
|
||||
*
|
||||
* @slot and @pin are unused, but included in the function so that this
|
||||
* function can be used directly as the map_irq callback to pci_fixup_irqs().
|
||||
*/
|
||||
int of_irq_parse_and_map_pci(const struct pci_dev *dev, u8 slot, u8 pin)
|
||||
{
|
||||
struct of_phandle_args oirq;
|
||||
int ret;
|
||||
|
||||
ret = of_irq_parse_pci(dev, &oirq);
|
||||
if (ret) {
|
||||
dev_err(&dev->dev, "of_irq_parse_pci() failed with rc=%d\n", ret);
|
||||
return 0; /* Proper return code 0 == NO_IRQ */
|
||||
}
|
||||
|
||||
return irq_create_of_mapping(&oirq);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_irq_parse_and_map_pci);
|
||||
|
93
drivers/of/of_private.h
Normal file
93
drivers/of/of_private.h
Normal file
|
@ -0,0 +1,93 @@
|
|||
#ifndef _LINUX_OF_PRIVATE_H
|
||||
#define _LINUX_OF_PRIVATE_H
|
||||
/*
|
||||
* Private symbols used by OF support code
|
||||
*
|
||||
* Paul Mackerras August 1996.
|
||||
* Copyright (C) 1996-2005 Paul Mackerras.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
/**
|
||||
* struct alias_prop - Alias property in 'aliases' node
|
||||
* @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
|
||||
*
|
||||
* The structure represents one alias property of 'aliases' node as
|
||||
* an entry in aliases_lookup list.
|
||||
*/
|
||||
struct alias_prop {
|
||||
struct list_head link;
|
||||
const char *alias;
|
||||
struct device_node *np;
|
||||
int id;
|
||||
char stem[0];
|
||||
};
|
||||
|
||||
extern struct mutex of_mutex;
|
||||
extern struct list_head aliases_lookup;
|
||||
extern struct kset *of_kset;
|
||||
|
||||
|
||||
static inline struct device_node *kobj_to_device_node(struct kobject *kobj)
|
||||
{
|
||||
return container_of(kobj, struct device_node, kobj);
|
||||
}
|
||||
|
||||
#if defined(CONFIG_OF_DYNAMIC)
|
||||
extern int of_property_notify(int action, struct device_node *np,
|
||||
struct property *prop, struct property *old_prop);
|
||||
extern void of_node_release(struct kobject *kobj);
|
||||
#else /* CONFIG_OF_DYNAMIC */
|
||||
static inline int of_property_notify(int action, struct device_node *np,
|
||||
struct property *prop, struct property *old_prop)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_OF_DYNAMIC */
|
||||
|
||||
/**
|
||||
* General utilities for working with live trees.
|
||||
*
|
||||
* All functions with two leading underscores operate
|
||||
* without taking node references, so you either have to
|
||||
* own the devtree lock or work on detached trees only.
|
||||
*/
|
||||
struct property *__of_prop_dup(const struct property *prop, gfp_t allocflags);
|
||||
struct device_node *__of_node_alloc(const char *full_name, gfp_t allocflags);
|
||||
|
||||
extern const void *__of_get_property(const struct device_node *np,
|
||||
const char *name, int *lenp);
|
||||
extern int __of_add_property(struct device_node *np, struct property *prop);
|
||||
extern int __of_add_property_sysfs(struct device_node *np,
|
||||
struct property *prop);
|
||||
extern int __of_remove_property(struct device_node *np, struct property *prop);
|
||||
extern void __of_remove_property_sysfs(struct device_node *np,
|
||||
struct property *prop);
|
||||
extern int __of_update_property(struct device_node *np,
|
||||
struct property *newprop, struct property **oldprop);
|
||||
extern void __of_update_property_sysfs(struct device_node *np,
|
||||
struct property *newprop, struct property *oldprop);
|
||||
|
||||
extern void __of_attach_node(struct device_node *np);
|
||||
extern int __of_attach_node_sysfs(struct device_node *np);
|
||||
extern void __of_detach_node(struct device_node *np);
|
||||
extern void __of_detach_node_sysfs(struct device_node *np);
|
||||
|
||||
/* iterators for transactions, used for overlays */
|
||||
/* forward iterator */
|
||||
#define for_each_transaction_entry(_oft, _te) \
|
||||
list_for_each_entry(_te, &(_oft)->te_list, node)
|
||||
|
||||
/* reverse iterator */
|
||||
#define for_each_transaction_entry_reverse(_oft, _te) \
|
||||
list_for_each_entry_reverse(_te, &(_oft)->te_list, node)
|
||||
|
||||
#endif /* _LINUX_OF_PRIVATE_H */
|
291
drivers/of/of_reserved_mem.c
Normal file
291
drivers/of/of_reserved_mem.c
Normal file
|
@ -0,0 +1,291 @@
|
|||
/*
|
||||
* Device tree based initialization code for reserved memory.
|
||||
*
|
||||
* Copyright (c) 2013, The Linux Foundation. All Rights Reserved.
|
||||
* Copyright (c) 2013,2014 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com
|
||||
* Author: Marek Szyprowski <m.szyprowski@samsung.com>
|
||||
* Author: Josh Cartwright <joshc@codeaurora.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License or (at your optional) any later version of the license.
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_fdt.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/sizes.h>
|
||||
#include <linux/of_reserved_mem.h>
|
||||
|
||||
#define MAX_RESERVED_REGIONS 16
|
||||
static struct reserved_mem reserved_mem[MAX_RESERVED_REGIONS];
|
||||
static int reserved_mem_count;
|
||||
|
||||
#if defined(CONFIG_HAVE_MEMBLOCK)
|
||||
#include <linux/memblock.h>
|
||||
int __init __weak early_init_dt_alloc_reserved_memory_arch(phys_addr_t size,
|
||||
phys_addr_t align, phys_addr_t start, phys_addr_t end, bool nomap,
|
||||
phys_addr_t *res_base)
|
||||
{
|
||||
/*
|
||||
* We use __memblock_alloc_base() because memblock_alloc_base()
|
||||
* panic()s on allocation failure.
|
||||
*/
|
||||
phys_addr_t base = __memblock_alloc_base(size, align, end);
|
||||
if (!base)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* Check if the allocated region fits in to start..end window
|
||||
*/
|
||||
if (base < start) {
|
||||
memblock_free(base, size);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
*res_base = base;
|
||||
if (nomap)
|
||||
return memblock_remove(base, size);
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
int __init __weak early_init_dt_alloc_reserved_memory_arch(phys_addr_t size,
|
||||
phys_addr_t align, phys_addr_t start, phys_addr_t end, bool nomap,
|
||||
phys_addr_t *res_base)
|
||||
{
|
||||
pr_err("Reserved memory not supported, ignoring region 0x%llx%s\n",
|
||||
size, nomap ? " (nomap)" : "");
|
||||
return -ENOSYS;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* res_mem_save_node() - save fdt node for second pass initialization
|
||||
*/
|
||||
void __init fdt_reserved_mem_save_node(unsigned long node, const char *uname,
|
||||
phys_addr_t base, phys_addr_t size)
|
||||
{
|
||||
struct reserved_mem *rmem = &reserved_mem[reserved_mem_count];
|
||||
|
||||
if (reserved_mem_count == ARRAY_SIZE(reserved_mem)) {
|
||||
pr_err("Reserved memory: not enough space all defined regions.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
rmem->fdt_node = node;
|
||||
rmem->name = uname;
|
||||
rmem->base = base;
|
||||
rmem->size = size;
|
||||
|
||||
reserved_mem_count++;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* res_mem_alloc_size() - allocate reserved memory described by 'size', 'align'
|
||||
* and 'alloc-ranges' properties
|
||||
*/
|
||||
static int __init __reserved_mem_alloc_size(unsigned long node,
|
||||
const char *uname, phys_addr_t *res_base, phys_addr_t *res_size)
|
||||
{
|
||||
int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32);
|
||||
phys_addr_t start = 0, end = 0;
|
||||
phys_addr_t base = 0, align = 0, size;
|
||||
int len;
|
||||
const __be32 *prop;
|
||||
int nomap;
|
||||
int ret;
|
||||
|
||||
prop = of_get_flat_dt_prop(node, "size", &len);
|
||||
if (!prop)
|
||||
return -EINVAL;
|
||||
|
||||
if (len != dt_root_size_cells * sizeof(__be32)) {
|
||||
pr_err("Reserved memory: invalid size property in '%s' node.\n",
|
||||
uname);
|
||||
return -EINVAL;
|
||||
}
|
||||
size = dt_mem_next_cell(dt_root_size_cells, &prop);
|
||||
|
||||
nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;
|
||||
|
||||
prop = of_get_flat_dt_prop(node, "alignment", &len);
|
||||
if (prop) {
|
||||
if (len != dt_root_addr_cells * sizeof(__be32)) {
|
||||
pr_err("Reserved memory: invalid alignment property in '%s' node.\n",
|
||||
uname);
|
||||
return -EINVAL;
|
||||
}
|
||||
align = dt_mem_next_cell(dt_root_addr_cells, &prop);
|
||||
}
|
||||
|
||||
prop = of_get_flat_dt_prop(node, "alloc-ranges", &len);
|
||||
if (prop) {
|
||||
|
||||
if (len % t_len != 0) {
|
||||
pr_err("Reserved memory: invalid alloc-ranges property in '%s', skipping node.\n",
|
||||
uname);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
base = 0;
|
||||
|
||||
while (len > 0) {
|
||||
start = dt_mem_next_cell(dt_root_addr_cells, &prop);
|
||||
end = start + dt_mem_next_cell(dt_root_size_cells,
|
||||
&prop);
|
||||
|
||||
ret = early_init_dt_alloc_reserved_memory_arch(size,
|
||||
align, start, end, nomap, &base);
|
||||
if (ret == 0) {
|
||||
pr_debug("Reserved memory: allocated memory for '%s' node: base %pa, size %ld MiB\n",
|
||||
uname, &base,
|
||||
(unsigned long)size / SZ_1M);
|
||||
break;
|
||||
}
|
||||
len -= t_len;
|
||||
}
|
||||
|
||||
} else {
|
||||
ret = early_init_dt_alloc_reserved_memory_arch(size, align,
|
||||
0, 0, nomap, &base);
|
||||
if (ret == 0)
|
||||
pr_debug("Reserved memory: allocated memory for '%s' node: base %pa, size %ld MiB\n",
|
||||
uname, &base, (unsigned long)size / SZ_1M);
|
||||
}
|
||||
|
||||
if (base == 0) {
|
||||
pr_info("Reserved memory: failed to allocate memory for node '%s'\n",
|
||||
uname);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
*res_base = base;
|
||||
*res_size = size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id __rmem_of_table_sentinel
|
||||
__used __section(__reservedmem_of_table_end);
|
||||
|
||||
/**
|
||||
* res_mem_init_node() - call region specific reserved memory init code
|
||||
*/
|
||||
static int __init __reserved_mem_init_node(struct reserved_mem *rmem)
|
||||
{
|
||||
extern const struct of_device_id __reservedmem_of_table[];
|
||||
const struct of_device_id *i;
|
||||
|
||||
for (i = __reservedmem_of_table; i < &__rmem_of_table_sentinel; i++) {
|
||||
reservedmem_of_init_fn initfn = i->data;
|
||||
const char *compat = i->compatible;
|
||||
|
||||
if (!of_flat_dt_is_compatible(rmem->fdt_node, compat))
|
||||
continue;
|
||||
|
||||
if (initfn(rmem) == 0) {
|
||||
pr_info("Reserved memory: initialized node %s, compatible id %s\n",
|
||||
rmem->name, compat);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* fdt_init_reserved_mem - allocate and init all saved reserved memory regions
|
||||
*/
|
||||
void __init fdt_init_reserved_mem(void)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < reserved_mem_count; i++) {
|
||||
struct reserved_mem *rmem = &reserved_mem[i];
|
||||
unsigned long node = rmem->fdt_node;
|
||||
int len;
|
||||
const __be32 *prop;
|
||||
int err = 0;
|
||||
|
||||
prop = of_get_flat_dt_prop(node, "phandle", &len);
|
||||
if (!prop)
|
||||
prop = of_get_flat_dt_prop(node, "linux,phandle", &len);
|
||||
if (prop)
|
||||
rmem->phandle = of_read_number(prop, len/4);
|
||||
|
||||
if (rmem->size == 0)
|
||||
err = __reserved_mem_alloc_size(node, rmem->name,
|
||||
&rmem->base, &rmem->size);
|
||||
if (err == 0)
|
||||
__reserved_mem_init_node(rmem);
|
||||
}
|
||||
}
|
||||
|
||||
static inline struct reserved_mem *__find_rmem(struct device_node *node)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
if (!node->phandle)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < reserved_mem_count; i++)
|
||||
if (reserved_mem[i].phandle == node->phandle)
|
||||
return &reserved_mem[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* of_reserved_mem_device_init() - assign reserved memory region to given device
|
||||
*
|
||||
* This function assign memory region pointed by "memory-region" device tree
|
||||
* property to the given device.
|
||||
*/
|
||||
int of_reserved_mem_device_init(struct device *dev)
|
||||
{
|
||||
struct reserved_mem *rmem;
|
||||
struct device_node *np;
|
||||
int ret;
|
||||
|
||||
np = of_parse_phandle(dev->of_node, "memory-region", 0);
|
||||
if (!np)
|
||||
return -ENODEV;
|
||||
|
||||
rmem = __find_rmem(np);
|
||||
of_node_put(np);
|
||||
|
||||
if (!rmem || !rmem->ops || !rmem->ops->device_init)
|
||||
return -EINVAL;
|
||||
|
||||
ret = rmem->ops->device_init(rmem, dev);
|
||||
if (ret == 0)
|
||||
dev_info(dev, "assigned reserved memory node %s\n", rmem->name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* of_reserved_mem_device_release() - release reserved memory device structures
|
||||
*
|
||||
* This function releases structures allocated for memory region handling for
|
||||
* the given device.
|
||||
*/
|
||||
void of_reserved_mem_device_release(struct device *dev)
|
||||
{
|
||||
struct reserved_mem *rmem;
|
||||
struct device_node *np;
|
||||
|
||||
np = of_parse_phandle(dev->of_node, "memory-region", 0);
|
||||
if (!np)
|
||||
return;
|
||||
|
||||
rmem = __find_rmem(np);
|
||||
of_node_put(np);
|
||||
|
||||
if (!rmem || !rmem->ops || !rmem->ops->device_release)
|
||||
return;
|
||||
|
||||
rmem->ops->device_release(rmem, dev);
|
||||
}
|
254
drivers/of/pdt.c
Normal file
254
drivers/of/pdt.c
Normal file
|
@ -0,0 +1,254 @@
|
|||
/* pdt.c: OF PROM device tree support code.
|
||||
*
|
||||
* 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 by David S. Miller davem@davemloft.net
|
||||
* Adapted for multiple architectures by Andres Salomon <dilinger@queued.net>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_pdt.h>
|
||||
|
||||
static struct of_pdt_ops *of_pdt_prom_ops __initdata;
|
||||
|
||||
void __initdata (*of_pdt_build_more)(struct device_node *dp,
|
||||
struct device_node ***nextp);
|
||||
|
||||
#if defined(CONFIG_SPARC)
|
||||
unsigned int of_pdt_unique_id __initdata;
|
||||
|
||||
#define of_pdt_incr_unique_id(p) do { \
|
||||
(p)->unique_id = of_pdt_unique_id++; \
|
||||
} while (0)
|
||||
|
||||
static char * __init of_pdt_build_full_name(struct device_node *dp)
|
||||
{
|
||||
int len, ourlen, plen;
|
||||
char *n;
|
||||
|
||||
dp->path_component_name = build_path_component(dp);
|
||||
|
||||
plen = strlen(dp->parent->full_name);
|
||||
ourlen = strlen(dp->path_component_name);
|
||||
len = ourlen + plen + 2;
|
||||
|
||||
n = prom_early_alloc(len);
|
||||
strcpy(n, dp->parent->full_name);
|
||||
if (!of_node_is_root(dp->parent)) {
|
||||
strcpy(n + plen, "/");
|
||||
plen++;
|
||||
}
|
||||
strcpy(n + plen, dp->path_component_name);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
#else /* CONFIG_SPARC */
|
||||
|
||||
static inline void of_pdt_incr_unique_id(void *p) { }
|
||||
static inline void irq_trans_init(struct device_node *dp) { }
|
||||
|
||||
static char * __init of_pdt_build_full_name(struct device_node *dp)
|
||||
{
|
||||
static int failsafe_id = 0; /* for generating unique names on failure */
|
||||
char *buf;
|
||||
int len;
|
||||
|
||||
if (of_pdt_prom_ops->pkg2path(dp->phandle, NULL, 0, &len))
|
||||
goto failsafe;
|
||||
|
||||
buf = prom_early_alloc(len + 1);
|
||||
if (of_pdt_prom_ops->pkg2path(dp->phandle, buf, len, &len))
|
||||
goto failsafe;
|
||||
return buf;
|
||||
|
||||
failsafe:
|
||||
buf = prom_early_alloc(strlen(dp->parent->full_name) +
|
||||
strlen(dp->name) + 16);
|
||||
sprintf(buf, "%s/%s@unknown%i",
|
||||
of_node_is_root(dp->parent) ? "" : dp->parent->full_name,
|
||||
dp->name, failsafe_id++);
|
||||
pr_err("%s: pkg2path failed; assigning %s\n", __func__, buf);
|
||||
return buf;
|
||||
}
|
||||
|
||||
#endif /* !CONFIG_SPARC */
|
||||
|
||||
static struct property * __init of_pdt_build_one_prop(phandle node, char *prev,
|
||||
char *special_name,
|
||||
void *special_val,
|
||||
int special_len)
|
||||
{
|
||||
static struct property *tmp = NULL;
|
||||
struct property *p;
|
||||
int err;
|
||||
|
||||
if (tmp) {
|
||||
p = tmp;
|
||||
memset(p, 0, sizeof(*p) + 32);
|
||||
tmp = NULL;
|
||||
} else {
|
||||
p = prom_early_alloc(sizeof(struct property) + 32);
|
||||
of_pdt_incr_unique_id(p);
|
||||
}
|
||||
|
||||
p->name = (char *) (p + 1);
|
||||
if (special_name) {
|
||||
strcpy(p->name, special_name);
|
||||
p->length = special_len;
|
||||
p->value = prom_early_alloc(special_len);
|
||||
memcpy(p->value, special_val, special_len);
|
||||
} else {
|
||||
err = of_pdt_prom_ops->nextprop(node, prev, p->name);
|
||||
if (err) {
|
||||
tmp = p;
|
||||
return NULL;
|
||||
}
|
||||
p->length = of_pdt_prom_ops->getproplen(node, p->name);
|
||||
if (p->length <= 0) {
|
||||
p->length = 0;
|
||||
} else {
|
||||
int len;
|
||||
|
||||
p->value = prom_early_alloc(p->length + 1);
|
||||
len = of_pdt_prom_ops->getproperty(node, p->name,
|
||||
p->value, p->length);
|
||||
if (len <= 0)
|
||||
p->length = 0;
|
||||
((unsigned char *)p->value)[p->length] = '\0';
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static struct property * __init of_pdt_build_prop_list(phandle node)
|
||||
{
|
||||
struct property *head, *tail;
|
||||
|
||||
head = tail = of_pdt_build_one_prop(node, NULL,
|
||||
".node", &node, sizeof(node));
|
||||
|
||||
tail->next = of_pdt_build_one_prop(node, NULL, NULL, NULL, 0);
|
||||
tail = tail->next;
|
||||
while(tail) {
|
||||
tail->next = of_pdt_build_one_prop(node, tail->name,
|
||||
NULL, NULL, 0);
|
||||
tail = tail->next;
|
||||
}
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
static char * __init of_pdt_get_one_property(phandle node, const char *name)
|
||||
{
|
||||
char *buf = "<NULL>";
|
||||
int len;
|
||||
|
||||
len = of_pdt_prom_ops->getproplen(node, name);
|
||||
if (len > 0) {
|
||||
buf = prom_early_alloc(len);
|
||||
len = of_pdt_prom_ops->getproperty(node, name, buf, len);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static struct device_node * __init of_pdt_create_node(phandle node,
|
||||
struct device_node *parent)
|
||||
{
|
||||
struct device_node *dp;
|
||||
|
||||
if (!node)
|
||||
return NULL;
|
||||
|
||||
dp = prom_early_alloc(sizeof(*dp));
|
||||
of_node_init(dp);
|
||||
of_pdt_incr_unique_id(dp);
|
||||
dp->parent = parent;
|
||||
|
||||
dp->name = of_pdt_get_one_property(node, "name");
|
||||
dp->type = of_pdt_get_one_property(node, "device_type");
|
||||
dp->phandle = node;
|
||||
|
||||
dp->properties = of_pdt_build_prop_list(node);
|
||||
|
||||
irq_trans_init(dp);
|
||||
|
||||
return dp;
|
||||
}
|
||||
|
||||
static struct device_node * __init of_pdt_build_tree(struct device_node *parent,
|
||||
phandle node,
|
||||
struct device_node ***nextp)
|
||||
{
|
||||
struct device_node *ret = NULL, *prev_sibling = NULL;
|
||||
struct device_node *dp;
|
||||
|
||||
while (1) {
|
||||
dp = of_pdt_create_node(node, parent);
|
||||
if (!dp)
|
||||
break;
|
||||
|
||||
if (prev_sibling)
|
||||
prev_sibling->sibling = dp;
|
||||
|
||||
if (!ret)
|
||||
ret = dp;
|
||||
prev_sibling = dp;
|
||||
|
||||
*(*nextp) = dp;
|
||||
*nextp = &dp->allnext;
|
||||
|
||||
dp->full_name = of_pdt_build_full_name(dp);
|
||||
|
||||
dp->child = of_pdt_build_tree(dp,
|
||||
of_pdt_prom_ops->getchild(node), nextp);
|
||||
|
||||
if (of_pdt_build_more)
|
||||
of_pdt_build_more(dp, nextp);
|
||||
|
||||
node = of_pdt_prom_ops->getsibling(node);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void * __init kernel_tree_alloc(u64 size, u64 align)
|
||||
{
|
||||
return prom_early_alloc(size);
|
||||
}
|
||||
|
||||
void __init of_pdt_build_devicetree(phandle root_node, struct of_pdt_ops *ops)
|
||||
{
|
||||
struct device_node **nextp;
|
||||
|
||||
BUG_ON(!ops);
|
||||
of_pdt_prom_ops = ops;
|
||||
|
||||
of_allnodes = of_pdt_create_node(root_node, NULL);
|
||||
#if defined(CONFIG_SPARC)
|
||||
of_allnodes->path_component_name = "";
|
||||
#endif
|
||||
of_allnodes->full_name = "/";
|
||||
|
||||
nextp = &of_allnodes->allnext;
|
||||
of_allnodes->child = of_pdt_build_tree(of_allnodes,
|
||||
of_pdt_prom_ops->getchild(of_allnodes->phandle), &nextp);
|
||||
|
||||
/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
|
||||
of_alias_scan(kernel_tree_alloc);
|
||||
}
|
552
drivers/of/platform.c
Normal file
552
drivers/of/platform.c
Normal file
|
@ -0,0 +1,552 @@
|
|||
/*
|
||||
* Copyright (C) 2006 Benjamin Herrenschmidt, IBM Corp.
|
||||
* <benh@kernel.crashing.org>
|
||||
* and Arnd Bergmann, IBM Corp.
|
||||
* Merged from powerpc/kernel/of_platform.c and
|
||||
* sparc{,64}/kernel/of_device.c by Stephen Rothwell
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
*/
|
||||
#include <linux/errno.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/amba/bus.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
const struct of_device_id of_default_bus_match_table[] = {
|
||||
{ .compatible = "simple-bus", },
|
||||
#ifdef CONFIG_ARM_AMBA
|
||||
{ .compatible = "arm,amba-bus", },
|
||||
#endif /* CONFIG_ARM_AMBA */
|
||||
#ifdef CONFIG_EXYNOS_IOMMU
|
||||
{ .compatible = "samsung,exynos-iommu-bus", },
|
||||
#endif
|
||||
{} /* Empty terminated list */
|
||||
};
|
||||
|
||||
static int of_dev_node_match(struct device *dev, void *data)
|
||||
{
|
||||
return dev->of_node == data;
|
||||
}
|
||||
|
||||
/**
|
||||
* of_find_device_by_node - Find the platform_device associated with a node
|
||||
* @np: Pointer to device tree node
|
||||
*
|
||||
* Returns platform_device pointer, or NULL if not found
|
||||
*/
|
||||
struct platform_device *of_find_device_by_node(struct device_node *np)
|
||||
{
|
||||
struct device *dev;
|
||||
|
||||
dev = bus_find_device(&platform_bus_type, NULL, np, of_dev_node_match);
|
||||
return dev ? to_platform_device(dev) : NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(of_find_device_by_node);
|
||||
|
||||
#ifdef CONFIG_OF_ADDRESS
|
||||
/*
|
||||
* The following routines scan a subtree and registers a device for
|
||||
* each applicable node.
|
||||
*
|
||||
* Note: sparc doesn't use these routines because it has a different
|
||||
* mechanism for creating devices from device tree nodes.
|
||||
*/
|
||||
|
||||
/**
|
||||
* of_device_make_bus_id - Use the device node data to assign a unique name
|
||||
* @dev: pointer to device structure that is linked to a device tree node
|
||||
*
|
||||
* This routine will first try using the translated bus address to
|
||||
* derive a unique name. If it cannot, then it will prepend names from
|
||||
* parent nodes until a unique name can be derived.
|
||||
*/
|
||||
void of_device_make_bus_id(struct device *dev)
|
||||
{
|
||||
struct device_node *node = dev->of_node;
|
||||
const __be32 *reg;
|
||||
u64 addr;
|
||||
|
||||
/* Construct the name, using parent nodes if necessary to ensure uniqueness */
|
||||
while (node->parent) {
|
||||
/*
|
||||
* If the address can be translated, then that is as much
|
||||
* uniqueness as we need. Make it the first component and return
|
||||
*/
|
||||
reg = of_get_property(node, "reg", NULL);
|
||||
if (reg && (addr = of_translate_address(node, reg)) != OF_BAD_ADDR) {
|
||||
dev_set_name(dev, dev_name(dev) ? "%llx.%s:%s" : "%llx.%s",
|
||||
(unsigned long long)addr, node->name,
|
||||
dev_name(dev));
|
||||
return;
|
||||
}
|
||||
|
||||
/* format arguments only used if dev_name() resolves to NULL */
|
||||
dev_set_name(dev, dev_name(dev) ? "%s:%s" : "%s",
|
||||
strrchr(node->full_name, '/') + 1, dev_name(dev));
|
||||
node = node->parent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* of_device_alloc - Allocate and initialize an of_device
|
||||
* @np: device node to assign to device
|
||||
* @bus_id: Name to assign to the device. May be null to use default name.
|
||||
* @parent: Parent device.
|
||||
*/
|
||||
struct platform_device *of_device_alloc(struct device_node *np,
|
||||
const char *bus_id,
|
||||
struct device *parent)
|
||||
{
|
||||
struct platform_device *dev;
|
||||
int rc, i, num_reg = 0, num_irq;
|
||||
struct resource *res, temp_res;
|
||||
|
||||
dev = platform_device_alloc("", -1);
|
||||
if (!dev)
|
||||
return NULL;
|
||||
|
||||
/* count the io and irq resources */
|
||||
while (of_address_to_resource(np, num_reg, &temp_res) == 0)
|
||||
num_reg++;
|
||||
num_irq = of_irq_count(np);
|
||||
|
||||
/* Populate the resource table */
|
||||
if (num_irq || num_reg) {
|
||||
res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);
|
||||
if (!res) {
|
||||
platform_device_put(dev);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dev->num_resources = num_reg + num_irq;
|
||||
dev->resource = res;
|
||||
for (i = 0; i < num_reg; i++, res++) {
|
||||
rc = of_address_to_resource(np, i, res);
|
||||
WARN_ON(rc);
|
||||
}
|
||||
if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
|
||||
pr_debug("not all legacy IRQ resources mapped for %s\n",
|
||||
np->name);
|
||||
}
|
||||
|
||||
dev->dev.of_node = of_node_get(np);
|
||||
dev->dev.parent = parent;
|
||||
|
||||
if (bus_id)
|
||||
dev_set_name(&dev->dev, "%s", bus_id);
|
||||
else
|
||||
of_device_make_bus_id(&dev->dev);
|
||||
|
||||
return dev;
|
||||
}
|
||||
EXPORT_SYMBOL(of_device_alloc);
|
||||
|
||||
/**
|
||||
* of_dma_configure - Setup DMA configuration
|
||||
* @dev: Device to apply DMA configuration
|
||||
*
|
||||
* Try to get devices's DMA configuration from DT and update it
|
||||
* accordingly.
|
||||
*
|
||||
* In case if platform code need to use own special DMA configuration,it
|
||||
* can use Platform bus notifier and handle BUS_NOTIFY_ADD_DEVICE event
|
||||
* to fix up DMA configuration.
|
||||
*/
|
||||
static void of_dma_configure(struct device *dev)
|
||||
{
|
||||
u64 dma_addr, paddr, size;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Set default dma-mask to 32 bit. Drivers are expected to setup
|
||||
* the correct supported dma_mask.
|
||||
*/
|
||||
dev->coherent_dma_mask = DMA_BIT_MASK(32);
|
||||
|
||||
/*
|
||||
* Set it to coherent_dma_mask by default if the architecture
|
||||
* code has not set it.
|
||||
*/
|
||||
if (!dev->dma_mask)
|
||||
dev->dma_mask = &dev->coherent_dma_mask;
|
||||
|
||||
/*
|
||||
* if dma-coherent property exist, call arch hook to setup
|
||||
* dma coherent operations.
|
||||
*/
|
||||
if (of_dma_is_coherent(dev->of_node)) {
|
||||
set_arch_dma_coherent_ops(dev);
|
||||
dev_dbg(dev, "device is dma coherent\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* if dma-ranges property doesn't exist - just return else
|
||||
* setup the dma offset
|
||||
*/
|
||||
ret = of_dma_get_range(dev->of_node, &dma_addr, &paddr, &size);
|
||||
if (ret < 0) {
|
||||
dev_dbg(dev, "no dma range information to setup\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* DMA ranges found. Calculate and set dma_pfn_offset */
|
||||
dev->dma_pfn_offset = PFN_DOWN(paddr - dma_addr);
|
||||
dev_dbg(dev, "dma_pfn_offset(%#08lx)\n", dev->dma_pfn_offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* of_platform_device_create_pdata - Alloc, initialize and register an of_device
|
||||
* @np: pointer to node to create device for
|
||||
* @bus_id: name to assign device
|
||||
* @platform_data: pointer to populate platform_data pointer with
|
||||
* @parent: Linux device model parent device.
|
||||
*
|
||||
* Returns pointer to created platform device, or NULL if a device was not
|
||||
* registered. Unavailable devices will not get registered.
|
||||
*/
|
||||
static struct platform_device *of_platform_device_create_pdata(
|
||||
struct device_node *np,
|
||||
const char *bus_id,
|
||||
void *platform_data,
|
||||
struct device *parent)
|
||||
{
|
||||
struct platform_device *dev;
|
||||
|
||||
if (!of_device_is_available(np) ||
|
||||
of_node_test_and_set_flag(np, OF_POPULATED))
|
||||
return NULL;
|
||||
|
||||
dev = of_device_alloc(np, bus_id, parent);
|
||||
if (!dev)
|
||||
goto err_clear_flag;
|
||||
|
||||
of_dma_configure(&dev->dev);
|
||||
dev->dev.bus = &platform_bus_type;
|
||||
dev->dev.platform_data = platform_data;
|
||||
|
||||
/* We do not fill the DMA ops for platform devices by default.
|
||||
* This is currently the responsibility of the platform code
|
||||
* to do such, possibly using a device notifier
|
||||
*/
|
||||
|
||||
if (of_device_add(dev) != 0) {
|
||||
platform_device_put(dev);
|
||||
goto err_clear_flag;
|
||||
}
|
||||
|
||||
return dev;
|
||||
|
||||
err_clear_flag:
|
||||
of_node_clear_flag(np, OF_POPULATED);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* of_platform_device_create - Alloc, initialize and register an of_device
|
||||
* @np: pointer to node to create device for
|
||||
* @bus_id: name to assign device
|
||||
* @parent: Linux device model parent device.
|
||||
*
|
||||
* Returns pointer to created platform device, or NULL if a device was not
|
||||
* registered. Unavailable devices will not get registered.
|
||||
*/
|
||||
struct platform_device *of_platform_device_create(struct device_node *np,
|
||||
const char *bus_id,
|
||||
struct device *parent)
|
||||
{
|
||||
return of_platform_device_create_pdata(np, bus_id, NULL, parent);
|
||||
}
|
||||
EXPORT_SYMBOL(of_platform_device_create);
|
||||
|
||||
#ifdef CONFIG_ARM_AMBA
|
||||
static struct amba_device *of_amba_device_create(struct device_node *node,
|
||||
const char *bus_id,
|
||||
void *platform_data,
|
||||
struct device *parent)
|
||||
{
|
||||
struct amba_device *dev;
|
||||
const void *prop;
|
||||
int i, ret;
|
||||
|
||||
pr_debug("Creating amba device %s\n", node->full_name);
|
||||
|
||||
if (!of_device_is_available(node) ||
|
||||
of_node_test_and_set_flag(node, OF_POPULATED))
|
||||
return NULL;
|
||||
|
||||
dev = amba_device_alloc(NULL, 0, 0);
|
||||
if (!dev) {
|
||||
pr_err("%s(): amba_device_alloc() failed for %s\n",
|
||||
__func__, node->full_name);
|
||||
goto err_clear_flag;
|
||||
}
|
||||
|
||||
/* setup generic device info */
|
||||
dev->dev.of_node = of_node_get(node);
|
||||
dev->dev.parent = parent;
|
||||
dev->dev.platform_data = platform_data;
|
||||
if (bus_id)
|
||||
dev_set_name(&dev->dev, "%s", bus_id);
|
||||
else
|
||||
of_device_make_bus_id(&dev->dev);
|
||||
of_dma_configure(&dev->dev);
|
||||
|
||||
/* Allow the HW Peripheral ID to be overridden */
|
||||
prop = of_get_property(node, "arm,primecell-periphid", NULL);
|
||||
if (prop)
|
||||
dev->periphid = of_read_ulong(prop, 1);
|
||||
|
||||
/* Decode the IRQs and address ranges */
|
||||
for (i = 0; i < AMBA_NR_IRQS; i++)
|
||||
dev->irq[i] = irq_of_parse_and_map(node, i);
|
||||
|
||||
ret = of_address_to_resource(node, 0, &dev->res);
|
||||
if (ret) {
|
||||
pr_err("%s(): of_address_to_resource() failed (%d) for %s\n",
|
||||
__func__, ret, node->full_name);
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
ret = amba_device_add(dev, &iomem_resource);
|
||||
if (ret) {
|
||||
pr_err("%s(): amba_device_add() failed (%d) for %s\n",
|
||||
__func__, ret, node->full_name);
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
return dev;
|
||||
|
||||
err_free:
|
||||
amba_device_put(dev);
|
||||
err_clear_flag:
|
||||
of_node_clear_flag(node, OF_POPULATED);
|
||||
return NULL;
|
||||
}
|
||||
#else /* CONFIG_ARM_AMBA */
|
||||
static struct amba_device *of_amba_device_create(struct device_node *node,
|
||||
const char *bus_id,
|
||||
void *platform_data,
|
||||
struct device *parent)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
#endif /* CONFIG_ARM_AMBA */
|
||||
|
||||
/**
|
||||
* of_devname_lookup() - Given a device node, lookup the preferred Linux name
|
||||
*/
|
||||
static const struct of_dev_auxdata *of_dev_lookup(const struct of_dev_auxdata *lookup,
|
||||
struct device_node *np)
|
||||
{
|
||||
struct resource res;
|
||||
|
||||
if (!lookup)
|
||||
return NULL;
|
||||
|
||||
for(; lookup->compatible != NULL; lookup++) {
|
||||
if (!of_device_is_compatible(np, lookup->compatible))
|
||||
continue;
|
||||
if (!of_address_to_resource(np, 0, &res))
|
||||
if (res.start != lookup->phys_addr)
|
||||
continue;
|
||||
pr_debug("%s: devname=%s\n", np->full_name, lookup->name);
|
||||
return lookup;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* of_platform_bus_create() - Create a device for a node and its children.
|
||||
* @bus: device node of the bus to instantiate
|
||||
* @matches: match table for bus nodes
|
||||
* @lookup: auxdata table for matching id and platform_data with device nodes
|
||||
* @parent: parent for new device, or NULL for top level.
|
||||
* @strict: require compatible property
|
||||
*
|
||||
* Creates a platform_device for the provided device_node, and optionally
|
||||
* recursively create devices for all the child nodes.
|
||||
*/
|
||||
static int of_platform_bus_create(struct device_node *bus,
|
||||
const struct of_device_id *matches,
|
||||
const struct of_dev_auxdata *lookup,
|
||||
struct device *parent, bool strict)
|
||||
{
|
||||
const struct of_dev_auxdata *auxdata;
|
||||
struct device_node *child;
|
||||
struct platform_device *dev;
|
||||
const char *bus_id = NULL;
|
||||
void *platform_data = NULL;
|
||||
int rc = 0;
|
||||
|
||||
/* Make sure it has a compatible property */
|
||||
if (strict && (!of_get_property(bus, "compatible", NULL))) {
|
||||
pr_debug("%s() - skipping %s, no compatible prop\n",
|
||||
__func__, bus->full_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
auxdata = of_dev_lookup(lookup, bus);
|
||||
if (auxdata) {
|
||||
bus_id = auxdata->name;
|
||||
platform_data = auxdata->platform_data;
|
||||
}
|
||||
|
||||
if (of_device_is_compatible(bus, "arm,primecell")) {
|
||||
/*
|
||||
* Don't return an error here to keep compatibility with older
|
||||
* device tree files.
|
||||
*/
|
||||
of_amba_device_create(bus, bus_id, platform_data, parent);
|
||||
return 0;
|
||||
}
|
||||
|
||||
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
|
||||
if (!dev || !of_match_node(matches, bus))
|
||||
return 0;
|
||||
|
||||
for_each_child_of_node(bus, child) {
|
||||
pr_debug(" create child: %s\n", child->full_name);
|
||||
rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
|
||||
if (rc) {
|
||||
of_node_put(child);
|
||||
break;
|
||||
}
|
||||
}
|
||||
of_node_set_flag(bus, OF_POPULATED_BUS);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* of_platform_bus_probe() - Probe the device-tree for platform buses
|
||||
* @root: parent of the first level to probe or NULL for the root of the tree
|
||||
* @matches: match table for bus nodes
|
||||
* @parent: parent to hook devices from, NULL for toplevel
|
||||
*
|
||||
* Note that children of the provided root are not instantiated as devices
|
||||
* unless the specified root itself matches the bus list and is not NULL.
|
||||
*/
|
||||
int of_platform_bus_probe(struct device_node *root,
|
||||
const struct of_device_id *matches,
|
||||
struct device *parent)
|
||||
{
|
||||
struct device_node *child;
|
||||
int rc = 0;
|
||||
|
||||
root = root ? of_node_get(root) : of_find_node_by_path("/");
|
||||
if (!root)
|
||||
return -EINVAL;
|
||||
|
||||
pr_debug("of_platform_bus_probe()\n");
|
||||
pr_debug(" starting at: %s\n", root->full_name);
|
||||
|
||||
/* Do a self check of bus type, if there's a match, create children */
|
||||
if (of_match_node(matches, root)) {
|
||||
rc = of_platform_bus_create(root, matches, NULL, parent, false);
|
||||
} else for_each_child_of_node(root, child) {
|
||||
if (!of_match_node(matches, child))
|
||||
continue;
|
||||
rc = of_platform_bus_create(child, matches, NULL, parent, false);
|
||||
if (rc)
|
||||
break;
|
||||
}
|
||||
|
||||
of_node_put(root);
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL(of_platform_bus_probe);
|
||||
|
||||
/**
|
||||
* of_platform_populate() - Populate platform_devices from device tree data
|
||||
* @root: parent of the first level to probe or NULL for the root of the tree
|
||||
* @matches: match table, NULL to use the default
|
||||
* @lookup: auxdata table for matching id and platform_data with device nodes
|
||||
* @parent: parent to hook devices from, NULL for toplevel
|
||||
*
|
||||
* Similar to of_platform_bus_probe(), this function walks the device tree
|
||||
* and creates devices from nodes. It differs in that it follows the modern
|
||||
* convention of requiring all device nodes to have a 'compatible' property,
|
||||
* and it is suitable for creating devices which are children of the root
|
||||
* node (of_platform_bus_probe will only create children of the root which
|
||||
* are selected by the @matches argument).
|
||||
*
|
||||
* New board support should be using this function instead of
|
||||
* of_platform_bus_probe().
|
||||
*
|
||||
* Returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
int of_platform_populate(struct device_node *root,
|
||||
const struct of_device_id *matches,
|
||||
const struct of_dev_auxdata *lookup,
|
||||
struct device *parent)
|
||||
{
|
||||
struct device_node *child;
|
||||
int rc = 0;
|
||||
|
||||
root = root ? of_node_get(root) : of_find_node_by_path("/");
|
||||
if (!root)
|
||||
return -EINVAL;
|
||||
|
||||
for_each_child_of_node(root, child) {
|
||||
rc = of_platform_bus_create(child, matches, lookup, parent, true);
|
||||
if (rc)
|
||||
break;
|
||||
}
|
||||
|
||||
of_node_put(root);
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_platform_populate);
|
||||
|
||||
static int of_platform_device_destroy(struct device *dev, void *data)
|
||||
{
|
||||
/* Do not touch devices not populated from the device tree */
|
||||
if (!dev->of_node || !of_node_check_flag(dev->of_node, OF_POPULATED))
|
||||
return 0;
|
||||
|
||||
/* Recurse for any nodes that were treated as busses */
|
||||
if (of_node_check_flag(dev->of_node, OF_POPULATED_BUS))
|
||||
device_for_each_child(dev, NULL, of_platform_device_destroy);
|
||||
|
||||
if (dev->bus == &platform_bus_type)
|
||||
platform_device_unregister(to_platform_device(dev));
|
||||
#ifdef CONFIG_ARM_AMBA
|
||||
else if (dev->bus == &amba_bustype)
|
||||
amba_device_unregister(to_amba_device(dev));
|
||||
#endif
|
||||
|
||||
of_node_clear_flag(dev->of_node, OF_POPULATED);
|
||||
of_node_clear_flag(dev->of_node, OF_POPULATED_BUS);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* of_platform_depopulate() - Remove devices populated from device tree
|
||||
* @parent: device which children will be removed
|
||||
*
|
||||
* Complementary to of_platform_populate(), this function removes children
|
||||
* of the given device (and, recurrently, their children) that have been
|
||||
* created from their respective device tree nodes (and only those,
|
||||
* leaving others - eg. manually created - unharmed).
|
||||
*
|
||||
* Returns 0 when all children devices have been removed or
|
||||
* -EBUSY when some children remained.
|
||||
*/
|
||||
void of_platform_depopulate(struct device *parent)
|
||||
{
|
||||
device_for_each_child(parent, NULL, of_platform_device_destroy);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_platform_depopulate);
|
||||
|
||||
#endif /* CONFIG_OF_ADDRESS */
|
336
drivers/of/resolver.c
Normal file
336
drivers/of/resolver.c
Normal file
|
@ -0,0 +1,336 @@
|
|||
/*
|
||||
* Functions for dealing with DT resolution
|
||||
*
|
||||
* Copyright (C) 2012 Pantelis Antoniou <panto@antoniou-consulting.com>
|
||||
* Copyright (C) 2012 Texas Instruments Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
/* illegal phandle value (set when unresolved) */
|
||||
#define OF_PHANDLE_ILLEGAL 0xdeadbeef
|
||||
|
||||
/**
|
||||
* Find a node with the give full name by recursively following any of
|
||||
* the child node links.
|
||||
*/
|
||||
static struct device_node *__of_find_node_by_full_name(struct device_node *node,
|
||||
const char *full_name)
|
||||
{
|
||||
struct device_node *child, *found;
|
||||
|
||||
if (node == NULL)
|
||||
return NULL;
|
||||
|
||||
/* check */
|
||||
if (of_node_cmp(node->full_name, full_name) == 0)
|
||||
return node;
|
||||
|
||||
for_each_child_of_node(node, child) {
|
||||
found = __of_find_node_by_full_name(child, full_name);
|
||||
if (found != NULL)
|
||||
return found;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find live tree's maximum phandle value.
|
||||
*/
|
||||
static phandle of_get_tree_max_phandle(void)
|
||||
{
|
||||
struct device_node *node;
|
||||
phandle phandle;
|
||||
unsigned long flags;
|
||||
|
||||
/* now search recursively */
|
||||
raw_spin_lock_irqsave(&devtree_lock, flags);
|
||||
phandle = 0;
|
||||
for_each_of_allnodes(node) {
|
||||
if (node->phandle != OF_PHANDLE_ILLEGAL &&
|
||||
node->phandle > phandle)
|
||||
phandle = node->phandle;
|
||||
}
|
||||
raw_spin_unlock_irqrestore(&devtree_lock, flags);
|
||||
|
||||
return phandle;
|
||||
}
|
||||
|
||||
/*
|
||||
* Adjust a subtree's phandle values by a given delta.
|
||||
* Makes sure not to just adjust the device node's phandle value,
|
||||
* but modify the phandle properties values as well.
|
||||
*/
|
||||
static void __of_adjust_tree_phandles(struct device_node *node,
|
||||
int phandle_delta)
|
||||
{
|
||||
struct device_node *child;
|
||||
struct property *prop;
|
||||
phandle phandle;
|
||||
|
||||
/* first adjust the node's phandle direct value */
|
||||
if (node->phandle != 0 && node->phandle != OF_PHANDLE_ILLEGAL)
|
||||
node->phandle += phandle_delta;
|
||||
|
||||
/* now adjust phandle & linux,phandle values */
|
||||
for_each_property_of_node(node, prop) {
|
||||
|
||||
/* only look for these two */
|
||||
if (of_prop_cmp(prop->name, "phandle") != 0 &&
|
||||
of_prop_cmp(prop->name, "linux,phandle") != 0)
|
||||
continue;
|
||||
|
||||
/* must be big enough */
|
||||
if (prop->length < 4)
|
||||
continue;
|
||||
|
||||
/* read phandle value */
|
||||
phandle = be32_to_cpup(prop->value);
|
||||
if (phandle == OF_PHANDLE_ILLEGAL) /* unresolved */
|
||||
continue;
|
||||
|
||||
/* adjust */
|
||||
*(uint32_t *)prop->value = cpu_to_be32(node->phandle);
|
||||
}
|
||||
|
||||
/* now do the children recursively */
|
||||
for_each_child_of_node(node, child)
|
||||
__of_adjust_tree_phandles(child, phandle_delta);
|
||||
}
|
||||
|
||||
static int __of_adjust_phandle_ref(struct device_node *node, struct property *rprop, int value, bool is_delta)
|
||||
{
|
||||
phandle phandle;
|
||||
struct device_node *refnode;
|
||||
struct property *sprop;
|
||||
char *propval, *propcur, *propend, *nodestr, *propstr, *s;
|
||||
int offset, propcurlen;
|
||||
int err = 0;
|
||||
|
||||
/* make a copy */
|
||||
propval = kmalloc(rprop->length, GFP_KERNEL);
|
||||
if (!propval) {
|
||||
pr_err("%s: Could not copy value of '%s'\n",
|
||||
__func__, rprop->name);
|
||||
return -ENOMEM;
|
||||
}
|
||||
memcpy(propval, rprop->value, rprop->length);
|
||||
|
||||
propend = propval + rprop->length;
|
||||
for (propcur = propval; propcur < propend; propcur += propcurlen + 1) {
|
||||
propcurlen = strlen(propcur);
|
||||
|
||||
nodestr = propcur;
|
||||
s = strchr(propcur, ':');
|
||||
if (!s) {
|
||||
pr_err("%s: Illegal symbol entry '%s' (1)\n",
|
||||
__func__, propcur);
|
||||
err = -EINVAL;
|
||||
goto err_fail;
|
||||
}
|
||||
*s++ = '\0';
|
||||
|
||||
propstr = s;
|
||||
s = strchr(s, ':');
|
||||
if (!s) {
|
||||
pr_err("%s: Illegal symbol entry '%s' (2)\n",
|
||||
__func__, (char *)rprop->value);
|
||||
err = -EINVAL;
|
||||
goto err_fail;
|
||||
}
|
||||
|
||||
*s++ = '\0';
|
||||
err = kstrtoint(s, 10, &offset);
|
||||
if (err != 0) {
|
||||
pr_err("%s: Could get offset '%s'\n",
|
||||
__func__, (char *)rprop->value);
|
||||
goto err_fail;
|
||||
}
|
||||
|
||||
/* look into the resolve node for the full path */
|
||||
refnode = __of_find_node_by_full_name(node, nodestr);
|
||||
if (!refnode) {
|
||||
pr_warn("%s: Could not find refnode '%s'\n",
|
||||
__func__, (char *)rprop->value);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* now find the property */
|
||||
for_each_property_of_node(refnode, sprop) {
|
||||
if (of_prop_cmp(sprop->name, propstr) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!sprop) {
|
||||
pr_err("%s: Could not find property '%s'\n",
|
||||
__func__, (char *)rprop->value);
|
||||
err = -ENOENT;
|
||||
goto err_fail;
|
||||
}
|
||||
|
||||
phandle = is_delta ? be32_to_cpup(sprop->value + offset) + value : value;
|
||||
*(__be32 *)(sprop->value + offset) = cpu_to_be32(phandle);
|
||||
}
|
||||
|
||||
err_fail:
|
||||
kfree(propval);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Adjust the local phandle references by the given phandle delta.
|
||||
* Assumes the existances of a __local_fixups__ node at the root
|
||||
* of the tree. Does not take any devtree locks so make sure you
|
||||
* call this on a tree which is at the detached state.
|
||||
*/
|
||||
static int __of_adjust_tree_phandle_references(struct device_node *node,
|
||||
int phandle_delta)
|
||||
{
|
||||
struct device_node *child;
|
||||
struct property *rprop;
|
||||
int err;
|
||||
|
||||
/* locate the symbols & fixups nodes on resolve */
|
||||
for_each_child_of_node(node, child)
|
||||
if (of_node_cmp(child->name, "__local_fixups__") == 0)
|
||||
break;
|
||||
|
||||
/* no local fixups */
|
||||
if (!child)
|
||||
return 0;
|
||||
|
||||
/* find the local fixups property */
|
||||
for_each_property_of_node(child, rprop) {
|
||||
/* skip properties added automatically */
|
||||
if (of_prop_cmp(rprop->name, "name") == 0)
|
||||
continue;
|
||||
|
||||
err = __of_adjust_phandle_ref(node, rprop, phandle_delta, true);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* of_resolve - Resolve the given node against the live tree.
|
||||
*
|
||||
* @resolve: Node to resolve
|
||||
*
|
||||
* Perform dynamic Device Tree resolution against the live tree
|
||||
* to the given node to resolve. This depends on the live tree
|
||||
* having a __symbols__ node, and the resolve node the __fixups__ &
|
||||
* __local_fixups__ nodes (if needed).
|
||||
* The result of the operation is a resolve node that it's contents
|
||||
* are fit to be inserted or operate upon the live tree.
|
||||
* Returns 0 on success or a negative error value on error.
|
||||
*/
|
||||
int of_resolve_phandles(struct device_node *resolve)
|
||||
{
|
||||
struct device_node *child, *refnode;
|
||||
struct device_node *root_sym, *resolve_sym, *resolve_fix;
|
||||
struct property *rprop;
|
||||
const char *refpath;
|
||||
phandle phandle, phandle_delta;
|
||||
int err;
|
||||
|
||||
/* the resolve node must exist, and be detached */
|
||||
if (!resolve || !of_node_check_flag(resolve, OF_DETACHED))
|
||||
return -EINVAL;
|
||||
|
||||
/* first we need to adjust the phandles */
|
||||
phandle_delta = of_get_tree_max_phandle() + 1;
|
||||
__of_adjust_tree_phandles(resolve, phandle_delta);
|
||||
err = __of_adjust_tree_phandle_references(resolve, phandle_delta);
|
||||
if (err != 0)
|
||||
return err;
|
||||
|
||||
root_sym = NULL;
|
||||
resolve_sym = NULL;
|
||||
resolve_fix = NULL;
|
||||
|
||||
/* this may fail (if no fixups are required) */
|
||||
root_sym = of_find_node_by_path("/__symbols__");
|
||||
|
||||
/* locate the symbols & fixups nodes on resolve */
|
||||
for_each_child_of_node(resolve, child) {
|
||||
|
||||
if (!resolve_sym &&
|
||||
of_node_cmp(child->name, "__symbols__") == 0)
|
||||
resolve_sym = child;
|
||||
|
||||
if (!resolve_fix &&
|
||||
of_node_cmp(child->name, "__fixups__") == 0)
|
||||
resolve_fix = child;
|
||||
|
||||
/* both found, don't bother anymore */
|
||||
if (resolve_sym && resolve_fix)
|
||||
break;
|
||||
}
|
||||
|
||||
/* we do allow for the case where no fixups are needed */
|
||||
if (!resolve_fix) {
|
||||
err = 0; /* no error */
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* we need to fixup, but no root symbols... */
|
||||
if (!root_sym) {
|
||||
err = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
for_each_property_of_node(resolve_fix, rprop) {
|
||||
|
||||
/* skip properties added automatically */
|
||||
if (of_prop_cmp(rprop->name, "name") == 0)
|
||||
continue;
|
||||
|
||||
err = of_property_read_string(root_sym,
|
||||
rprop->name, &refpath);
|
||||
if (err != 0) {
|
||||
pr_err("%s: Could not find symbol '%s'\n",
|
||||
__func__, rprop->name);
|
||||
goto out;
|
||||
}
|
||||
|
||||
refnode = of_find_node_by_path(refpath);
|
||||
if (!refnode) {
|
||||
pr_err("%s: Could not find node by path '%s'\n",
|
||||
__func__, refpath);
|
||||
err = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
phandle = refnode->phandle;
|
||||
of_node_put(refnode);
|
||||
|
||||
pr_debug("%s: %s phandle is 0x%08x\n",
|
||||
__func__, rprop->name, phandle);
|
||||
|
||||
err = __of_adjust_phandle_ref(resolve, rprop, phandle, false);
|
||||
if (err)
|
||||
break;
|
||||
}
|
||||
|
||||
out:
|
||||
/* NULL is handled by of_node_put as NOP */
|
||||
of_node_put(root_sym);
|
||||
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_resolve_phandles);
|
962
drivers/of/selftest.c
Normal file
962
drivers/of/selftest.c
Normal file
|
@ -0,0 +1,962 @@
|
|||
/*
|
||||
* Self tests for device tree subsystem
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "### dt-test ### " fmt
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/hashtable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_fdt.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/device.h>
|
||||
|
||||
#include "of_private.h"
|
||||
|
||||
static struct selftest_results {
|
||||
int passed;
|
||||
int failed;
|
||||
} selftest_results;
|
||||
|
||||
#define NO_OF_NODES 3
|
||||
static struct device_node *nodes[NO_OF_NODES];
|
||||
static int last_node_index;
|
||||
static bool selftest_live_tree;
|
||||
|
||||
#define selftest(result, fmt, ...) { \
|
||||
if (!(result)) { \
|
||||
selftest_results.failed++; \
|
||||
pr_err("FAIL %s():%i " fmt, __func__, __LINE__, ##__VA_ARGS__); \
|
||||
} else { \
|
||||
selftest_results.passed++; \
|
||||
pr_debug("pass %s():%i\n", __func__, __LINE__); \
|
||||
} \
|
||||
}
|
||||
|
||||
static void __init of_selftest_find_node_by_name(void)
|
||||
{
|
||||
struct device_node *np;
|
||||
|
||||
np = of_find_node_by_path("/testcase-data");
|
||||
selftest(np && !strcmp("/testcase-data", np->full_name),
|
||||
"find /testcase-data failed\n");
|
||||
of_node_put(np);
|
||||
|
||||
/* Test if trailing '/' works */
|
||||
np = of_find_node_by_path("/testcase-data/");
|
||||
selftest(!np, "trailing '/' on /testcase-data/ should fail\n");
|
||||
|
||||
np = of_find_node_by_path("/testcase-data/phandle-tests/consumer-a");
|
||||
selftest(np && !strcmp("/testcase-data/phandle-tests/consumer-a", np->full_name),
|
||||
"find /testcase-data/phandle-tests/consumer-a failed\n");
|
||||
of_node_put(np);
|
||||
|
||||
np = of_find_node_by_path("testcase-alias");
|
||||
selftest(np && !strcmp("/testcase-data", np->full_name),
|
||||
"find testcase-alias failed\n");
|
||||
of_node_put(np);
|
||||
|
||||
/* Test if trailing '/' works on aliases */
|
||||
np = of_find_node_by_path("testcase-alias/");
|
||||
selftest(!np, "trailing '/' on testcase-alias/ should fail\n");
|
||||
|
||||
np = of_find_node_by_path("testcase-alias/phandle-tests/consumer-a");
|
||||
selftest(np && !strcmp("/testcase-data/phandle-tests/consumer-a", np->full_name),
|
||||
"find testcase-alias/phandle-tests/consumer-a failed\n");
|
||||
of_node_put(np);
|
||||
|
||||
np = of_find_node_by_path("/testcase-data/missing-path");
|
||||
selftest(!np, "non-existent path returned node %s\n", np->full_name);
|
||||
of_node_put(np);
|
||||
|
||||
np = of_find_node_by_path("missing-alias");
|
||||
selftest(!np, "non-existent alias returned node %s\n", np->full_name);
|
||||
of_node_put(np);
|
||||
|
||||
np = of_find_node_by_path("testcase-alias/missing-path");
|
||||
selftest(!np, "non-existent alias with relative path returned node %s\n", np->full_name);
|
||||
of_node_put(np);
|
||||
}
|
||||
|
||||
static void __init of_selftest_dynamic(void)
|
||||
{
|
||||
struct device_node *np;
|
||||
struct property *prop;
|
||||
|
||||
np = of_find_node_by_path("/testcase-data");
|
||||
if (!np) {
|
||||
pr_err("missing testcase data\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Array of 4 properties for the purpose of testing */
|
||||
prop = kzalloc(sizeof(*prop) * 4, GFP_KERNEL);
|
||||
if (!prop) {
|
||||
selftest(0, "kzalloc() failed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Add a new property - should pass*/
|
||||
prop->name = "new-property";
|
||||
prop->value = "new-property-data";
|
||||
prop->length = strlen(prop->value);
|
||||
selftest(of_add_property(np, prop) == 0, "Adding a new property failed\n");
|
||||
|
||||
/* Try to add an existing property - should fail */
|
||||
prop++;
|
||||
prop->name = "new-property";
|
||||
prop->value = "new-property-data-should-fail";
|
||||
prop->length = strlen(prop->value);
|
||||
selftest(of_add_property(np, prop) != 0,
|
||||
"Adding an existing property should have failed\n");
|
||||
|
||||
/* Try to modify an existing property - should pass */
|
||||
prop->value = "modify-property-data-should-pass";
|
||||
prop->length = strlen(prop->value);
|
||||
selftest(of_update_property(np, prop) == 0,
|
||||
"Updating an existing property should have passed\n");
|
||||
|
||||
/* Try to modify non-existent property - should pass*/
|
||||
prop++;
|
||||
prop->name = "modify-property";
|
||||
prop->value = "modify-missing-property-data-should-pass";
|
||||
prop->length = strlen(prop->value);
|
||||
selftest(of_update_property(np, prop) == 0,
|
||||
"Updating a missing property should have passed\n");
|
||||
|
||||
/* Remove property - should pass */
|
||||
selftest(of_remove_property(np, prop) == 0,
|
||||
"Removing a property should have passed\n");
|
||||
|
||||
/* Adding very large property - should pass */
|
||||
prop++;
|
||||
prop->name = "large-property-PAGE_SIZEx8";
|
||||
prop->length = PAGE_SIZE * 8;
|
||||
prop->value = kzalloc(prop->length, GFP_KERNEL);
|
||||
selftest(prop->value != NULL, "Unable to allocate large buffer\n");
|
||||
if (prop->value)
|
||||
selftest(of_add_property(np, prop) == 0,
|
||||
"Adding a large property should have passed\n");
|
||||
}
|
||||
|
||||
static int __init of_selftest_check_node_linkage(struct device_node *np)
|
||||
{
|
||||
struct device_node *child, *allnext_index = np;
|
||||
int count = 0, rc;
|
||||
|
||||
for_each_child_of_node(np, child) {
|
||||
if (child->parent != np) {
|
||||
pr_err("Child node %s links to wrong parent %s\n",
|
||||
child->name, np->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
while (allnext_index && allnext_index != child)
|
||||
allnext_index = allnext_index->allnext;
|
||||
if (allnext_index != child) {
|
||||
pr_err("Node %s is ordered differently in sibling and allnode lists\n",
|
||||
child->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rc = of_selftest_check_node_linkage(child);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
count += rc;
|
||||
}
|
||||
|
||||
return count + 1;
|
||||
}
|
||||
|
||||
static void __init of_selftest_check_tree_linkage(void)
|
||||
{
|
||||
struct device_node *np;
|
||||
int allnode_count = 0, child_count;
|
||||
|
||||
if (!of_allnodes)
|
||||
return;
|
||||
|
||||
for_each_of_allnodes(np)
|
||||
allnode_count++;
|
||||
child_count = of_selftest_check_node_linkage(of_allnodes);
|
||||
|
||||
selftest(child_count > 0, "Device node data structure is corrupted\n");
|
||||
selftest(child_count == allnode_count, "allnodes list size (%i) doesn't match"
|
||||
"sibling lists size (%i)\n", allnode_count, child_count);
|
||||
pr_debug("allnodes list size (%i); sibling lists size (%i)\n", allnode_count, child_count);
|
||||
}
|
||||
|
||||
struct node_hash {
|
||||
struct hlist_node node;
|
||||
struct device_node *np;
|
||||
};
|
||||
|
||||
static DEFINE_HASHTABLE(phandle_ht, 8);
|
||||
static void __init of_selftest_check_phandles(void)
|
||||
{
|
||||
struct device_node *np;
|
||||
struct node_hash *nh;
|
||||
struct hlist_node *tmp;
|
||||
int i, dup_count = 0, phandle_count = 0;
|
||||
|
||||
for_each_of_allnodes(np) {
|
||||
if (!np->phandle)
|
||||
continue;
|
||||
|
||||
hash_for_each_possible(phandle_ht, nh, node, np->phandle) {
|
||||
if (nh->np->phandle == np->phandle) {
|
||||
pr_info("Duplicate phandle! %i used by %s and %s\n",
|
||||
np->phandle, nh->np->full_name, np->full_name);
|
||||
dup_count++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nh = kzalloc(sizeof(*nh), GFP_KERNEL);
|
||||
if (WARN_ON(!nh))
|
||||
return;
|
||||
|
||||
nh->np = np;
|
||||
hash_add(phandle_ht, &nh->node, np->phandle);
|
||||
phandle_count++;
|
||||
}
|
||||
selftest(dup_count == 0, "Found %i duplicates in %i phandles\n",
|
||||
dup_count, phandle_count);
|
||||
|
||||
/* Clean up */
|
||||
hash_for_each_safe(phandle_ht, i, tmp, nh, node) {
|
||||
hash_del(&nh->node);
|
||||
kfree(nh);
|
||||
}
|
||||
}
|
||||
|
||||
static void __init of_selftest_parse_phandle_with_args(void)
|
||||
{
|
||||
struct device_node *np;
|
||||
struct of_phandle_args args;
|
||||
int i, rc;
|
||||
|
||||
np = of_find_node_by_path("/testcase-data/phandle-tests/consumer-a");
|
||||
if (!np) {
|
||||
pr_err("missing testcase data\n");
|
||||
return;
|
||||
}
|
||||
|
||||
rc = of_count_phandle_with_args(np, "phandle-list", "#phandle-cells");
|
||||
selftest(rc == 7, "of_count_phandle_with_args() returned %i, expected 7\n", rc);
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
bool passed = true;
|
||||
rc = of_parse_phandle_with_args(np, "phandle-list",
|
||||
"#phandle-cells", i, &args);
|
||||
|
||||
/* Test the values from tests-phandle.dtsi */
|
||||
switch (i) {
|
||||
case 0:
|
||||
passed &= !rc;
|
||||
passed &= (args.args_count == 1);
|
||||
passed &= (args.args[0] == (i + 1));
|
||||
break;
|
||||
case 1:
|
||||
passed &= !rc;
|
||||
passed &= (args.args_count == 2);
|
||||
passed &= (args.args[0] == (i + 1));
|
||||
passed &= (args.args[1] == 0);
|
||||
break;
|
||||
case 2:
|
||||
passed &= (rc == -ENOENT);
|
||||
break;
|
||||
case 3:
|
||||
passed &= !rc;
|
||||
passed &= (args.args_count == 3);
|
||||
passed &= (args.args[0] == (i + 1));
|
||||
passed &= (args.args[1] == 4);
|
||||
passed &= (args.args[2] == 3);
|
||||
break;
|
||||
case 4:
|
||||
passed &= !rc;
|
||||
passed &= (args.args_count == 2);
|
||||
passed &= (args.args[0] == (i + 1));
|
||||
passed &= (args.args[1] == 100);
|
||||
break;
|
||||
case 5:
|
||||
passed &= !rc;
|
||||
passed &= (args.args_count == 0);
|
||||
break;
|
||||
case 6:
|
||||
passed &= !rc;
|
||||
passed &= (args.args_count == 1);
|
||||
passed &= (args.args[0] == (i + 1));
|
||||
break;
|
||||
case 7:
|
||||
passed &= (rc == -ENOENT);
|
||||
break;
|
||||
default:
|
||||
passed = false;
|
||||
}
|
||||
|
||||
selftest(passed, "index %i - data error on node %s rc=%i\n",
|
||||
i, args.np->full_name, rc);
|
||||
}
|
||||
|
||||
/* Check for missing list property */
|
||||
rc = of_parse_phandle_with_args(np, "phandle-list-missing",
|
||||
"#phandle-cells", 0, &args);
|
||||
selftest(rc == -ENOENT, "expected:%i got:%i\n", -ENOENT, rc);
|
||||
rc = of_count_phandle_with_args(np, "phandle-list-missing",
|
||||
"#phandle-cells");
|
||||
selftest(rc == -ENOENT, "expected:%i got:%i\n", -ENOENT, rc);
|
||||
|
||||
/* Check for missing cells property */
|
||||
rc = of_parse_phandle_with_args(np, "phandle-list",
|
||||
"#phandle-cells-missing", 0, &args);
|
||||
selftest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
|
||||
rc = of_count_phandle_with_args(np, "phandle-list",
|
||||
"#phandle-cells-missing");
|
||||
selftest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
|
||||
|
||||
/* Check for bad phandle in list */
|
||||
rc = of_parse_phandle_with_args(np, "phandle-list-bad-phandle",
|
||||
"#phandle-cells", 0, &args);
|
||||
selftest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
|
||||
rc = of_count_phandle_with_args(np, "phandle-list-bad-phandle",
|
||||
"#phandle-cells");
|
||||
selftest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
|
||||
|
||||
/* Check for incorrectly formed argument list */
|
||||
rc = of_parse_phandle_with_args(np, "phandle-list-bad-args",
|
||||
"#phandle-cells", 1, &args);
|
||||
selftest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
|
||||
rc = of_count_phandle_with_args(np, "phandle-list-bad-args",
|
||||
"#phandle-cells");
|
||||
selftest(rc == -EINVAL, "expected:%i got:%i\n", -EINVAL, rc);
|
||||
}
|
||||
|
||||
static void __init of_selftest_property_string(void)
|
||||
{
|
||||
const char *strings[4];
|
||||
struct device_node *np;
|
||||
int rc;
|
||||
|
||||
np = of_find_node_by_path("/testcase-data/phandle-tests/consumer-a");
|
||||
if (!np) {
|
||||
pr_err("No testcase data in device tree\n");
|
||||
return;
|
||||
}
|
||||
|
||||
rc = of_property_match_string(np, "phandle-list-names", "first");
|
||||
selftest(rc == 0, "first expected:0 got:%i\n", rc);
|
||||
rc = of_property_match_string(np, "phandle-list-names", "second");
|
||||
selftest(rc == 1, "second expected:0 got:%i\n", rc);
|
||||
rc = of_property_match_string(np, "phandle-list-names", "third");
|
||||
selftest(rc == 2, "third expected:0 got:%i\n", rc);
|
||||
rc = of_property_match_string(np, "phandle-list-names", "fourth");
|
||||
selftest(rc == -ENODATA, "unmatched string; rc=%i\n", rc);
|
||||
rc = of_property_match_string(np, "missing-property", "blah");
|
||||
selftest(rc == -EINVAL, "missing property; rc=%i\n", rc);
|
||||
rc = of_property_match_string(np, "empty-property", "blah");
|
||||
selftest(rc == -ENODATA, "empty property; rc=%i\n", rc);
|
||||
rc = of_property_match_string(np, "unterminated-string", "blah");
|
||||
selftest(rc == -EILSEQ, "unterminated string; rc=%i\n", rc);
|
||||
|
||||
/* of_property_count_strings() tests */
|
||||
rc = of_property_count_strings(np, "string-property");
|
||||
selftest(rc == 1, "Incorrect string count; rc=%i\n", rc);
|
||||
rc = of_property_count_strings(np, "phandle-list-names");
|
||||
selftest(rc == 3, "Incorrect string count; rc=%i\n", rc);
|
||||
rc = of_property_count_strings(np, "unterminated-string");
|
||||
selftest(rc == -EILSEQ, "unterminated string; rc=%i\n", rc);
|
||||
rc = of_property_count_strings(np, "unterminated-string-list");
|
||||
selftest(rc == -EILSEQ, "unterminated string array; rc=%i\n", rc);
|
||||
|
||||
/* of_property_read_string_index() tests */
|
||||
rc = of_property_read_string_index(np, "string-property", 0, strings);
|
||||
selftest(rc == 0 && !strcmp(strings[0], "foobar"), "of_property_read_string_index() failure; rc=%i\n", rc);
|
||||
strings[0] = NULL;
|
||||
rc = of_property_read_string_index(np, "string-property", 1, strings);
|
||||
selftest(rc == -ENODATA && strings[0] == NULL, "of_property_read_string_index() failure; rc=%i\n", rc);
|
||||
rc = of_property_read_string_index(np, "phandle-list-names", 0, strings);
|
||||
selftest(rc == 0 && !strcmp(strings[0], "first"), "of_property_read_string_index() failure; rc=%i\n", rc);
|
||||
rc = of_property_read_string_index(np, "phandle-list-names", 1, strings);
|
||||
selftest(rc == 0 && !strcmp(strings[0], "second"), "of_property_read_string_index() failure; rc=%i\n", rc);
|
||||
rc = of_property_read_string_index(np, "phandle-list-names", 2, strings);
|
||||
selftest(rc == 0 && !strcmp(strings[0], "third"), "of_property_read_string_index() failure; rc=%i\n", rc);
|
||||
strings[0] = NULL;
|
||||
rc = of_property_read_string_index(np, "phandle-list-names", 3, strings);
|
||||
selftest(rc == -ENODATA && strings[0] == NULL, "of_property_read_string_index() failure; rc=%i\n", rc);
|
||||
strings[0] = NULL;
|
||||
rc = of_property_read_string_index(np, "unterminated-string", 0, strings);
|
||||
selftest(rc == -EILSEQ && strings[0] == NULL, "of_property_read_string_index() failure; rc=%i\n", rc);
|
||||
rc = of_property_read_string_index(np, "unterminated-string-list", 0, strings);
|
||||
selftest(rc == 0 && !strcmp(strings[0], "first"), "of_property_read_string_index() failure; rc=%i\n", rc);
|
||||
strings[0] = NULL;
|
||||
rc = of_property_read_string_index(np, "unterminated-string-list", 2, strings); /* should fail */
|
||||
selftest(rc == -EILSEQ && strings[0] == NULL, "of_property_read_string_index() failure; rc=%i\n", rc);
|
||||
strings[1] = NULL;
|
||||
|
||||
/* of_property_read_string_array() tests */
|
||||
rc = of_property_read_string_array(np, "string-property", strings, 4);
|
||||
selftest(rc == 1, "Incorrect string count; rc=%i\n", rc);
|
||||
rc = of_property_read_string_array(np, "phandle-list-names", strings, 4);
|
||||
selftest(rc == 3, "Incorrect string count; rc=%i\n", rc);
|
||||
rc = of_property_read_string_array(np, "unterminated-string", strings, 4);
|
||||
selftest(rc == -EILSEQ, "unterminated string; rc=%i\n", rc);
|
||||
/* -- An incorrectly formed string should cause a failure */
|
||||
rc = of_property_read_string_array(np, "unterminated-string-list", strings, 4);
|
||||
selftest(rc == -EILSEQ, "unterminated string array; rc=%i\n", rc);
|
||||
/* -- parsing the correctly formed strings should still work: */
|
||||
strings[2] = NULL;
|
||||
rc = of_property_read_string_array(np, "unterminated-string-list", strings, 2);
|
||||
selftest(rc == 2 && strings[2] == NULL, "of_property_read_string_array() failure; rc=%i\n", rc);
|
||||
strings[1] = NULL;
|
||||
rc = of_property_read_string_array(np, "phandle-list-names", strings, 1);
|
||||
selftest(rc == 1 && strings[1] == NULL, "Overwrote end of string array; rc=%i, str='%s'\n", rc, strings[1]);
|
||||
}
|
||||
|
||||
#define propcmp(p1, p2) (((p1)->length == (p2)->length) && \
|
||||
(p1)->value && (p2)->value && \
|
||||
!memcmp((p1)->value, (p2)->value, (p1)->length) && \
|
||||
!strcmp((p1)->name, (p2)->name))
|
||||
static void __init of_selftest_property_copy(void)
|
||||
{
|
||||
#ifdef CONFIG_OF_DYNAMIC
|
||||
struct property p1 = { .name = "p1", .length = 0, .value = "" };
|
||||
struct property p2 = { .name = "p2", .length = 5, .value = "abcd" };
|
||||
struct property *new;
|
||||
|
||||
new = __of_prop_dup(&p1, GFP_KERNEL);
|
||||
selftest(new && propcmp(&p1, new), "empty property didn't copy correctly\n");
|
||||
kfree(new->value);
|
||||
kfree(new->name);
|
||||
kfree(new);
|
||||
|
||||
new = __of_prop_dup(&p2, GFP_KERNEL);
|
||||
selftest(new && propcmp(&p2, new), "non-empty property didn't copy correctly\n");
|
||||
kfree(new->value);
|
||||
kfree(new->name);
|
||||
kfree(new);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void __init of_selftest_changeset(void)
|
||||
{
|
||||
#ifdef CONFIG_OF_DYNAMIC
|
||||
struct property *ppadd, padd = { .name = "prop-add", .length = 0, .value = "" };
|
||||
struct property *ppupdate, pupdate = { .name = "prop-update", .length = 5, .value = "abcd" };
|
||||
struct property *ppremove;
|
||||
struct device_node *n1, *n2, *n21, *nremove, *parent;
|
||||
struct of_changeset chgset;
|
||||
|
||||
of_changeset_init(&chgset);
|
||||
n1 = __of_node_alloc("/testcase-data/changeset/n1", GFP_KERNEL);
|
||||
selftest(n1, "testcase setup failure\n");
|
||||
n2 = __of_node_alloc("/testcase-data/changeset/n2", GFP_KERNEL);
|
||||
selftest(n2, "testcase setup failure\n");
|
||||
n21 = __of_node_alloc("/testcase-data/changeset/n2/n21", GFP_KERNEL);
|
||||
selftest(n21, "testcase setup failure %p\n", n21);
|
||||
nremove = of_find_node_by_path("/testcase-data/changeset/node-remove");
|
||||
selftest(nremove, "testcase setup failure\n");
|
||||
ppadd = __of_prop_dup(&padd, GFP_KERNEL);
|
||||
selftest(ppadd, "testcase setup failure\n");
|
||||
ppupdate = __of_prop_dup(&pupdate, GFP_KERNEL);
|
||||
selftest(ppupdate, "testcase setup failure\n");
|
||||
parent = nremove->parent;
|
||||
n1->parent = parent;
|
||||
n2->parent = parent;
|
||||
n21->parent = n2;
|
||||
n2->child = n21;
|
||||
ppremove = of_find_property(parent, "prop-remove", NULL);
|
||||
selftest(ppremove, "failed to find removal prop");
|
||||
|
||||
of_changeset_init(&chgset);
|
||||
selftest(!of_changeset_attach_node(&chgset, n1), "fail attach n1\n");
|
||||
selftest(!of_changeset_attach_node(&chgset, n2), "fail attach n2\n");
|
||||
selftest(!of_changeset_detach_node(&chgset, nremove), "fail remove node\n");
|
||||
selftest(!of_changeset_attach_node(&chgset, n21), "fail attach n21\n");
|
||||
selftest(!of_changeset_add_property(&chgset, parent, ppadd), "fail add prop\n");
|
||||
selftest(!of_changeset_update_property(&chgset, parent, ppupdate), "fail update prop\n");
|
||||
selftest(!of_changeset_remove_property(&chgset, parent, ppremove), "fail remove prop\n");
|
||||
mutex_lock(&of_mutex);
|
||||
selftest(!of_changeset_apply(&chgset), "apply failed\n");
|
||||
mutex_unlock(&of_mutex);
|
||||
|
||||
mutex_lock(&of_mutex);
|
||||
selftest(!of_changeset_revert(&chgset), "revert failed\n");
|
||||
mutex_unlock(&of_mutex);
|
||||
|
||||
of_changeset_destroy(&chgset);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void __init of_selftest_parse_interrupts(void)
|
||||
{
|
||||
struct device_node *np;
|
||||
struct of_phandle_args args;
|
||||
int i, rc;
|
||||
|
||||
np = of_find_node_by_path("/testcase-data/interrupts/interrupts0");
|
||||
if (!np) {
|
||||
pr_err("missing testcase data\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
bool passed = true;
|
||||
args.args_count = 0;
|
||||
rc = of_irq_parse_one(np, i, &args);
|
||||
|
||||
passed &= !rc;
|
||||
passed &= (args.args_count == 1);
|
||||
passed &= (args.args[0] == (i + 1));
|
||||
|
||||
selftest(passed, "index %i - data error on node %s rc=%i\n",
|
||||
i, args.np->full_name, rc);
|
||||
}
|
||||
of_node_put(np);
|
||||
|
||||
np = of_find_node_by_path("/testcase-data/interrupts/interrupts1");
|
||||
if (!np) {
|
||||
pr_err("missing testcase data\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
bool passed = true;
|
||||
args.args_count = 0;
|
||||
rc = of_irq_parse_one(np, i, &args);
|
||||
|
||||
/* Test the values from tests-phandle.dtsi */
|
||||
switch (i) {
|
||||
case 0:
|
||||
passed &= !rc;
|
||||
passed &= (args.args_count == 1);
|
||||
passed &= (args.args[0] == 9);
|
||||
break;
|
||||
case 1:
|
||||
passed &= !rc;
|
||||
passed &= (args.args_count == 3);
|
||||
passed &= (args.args[0] == 10);
|
||||
passed &= (args.args[1] == 11);
|
||||
passed &= (args.args[2] == 12);
|
||||
break;
|
||||
case 2:
|
||||
passed &= !rc;
|
||||
passed &= (args.args_count == 2);
|
||||
passed &= (args.args[0] == 13);
|
||||
passed &= (args.args[1] == 14);
|
||||
break;
|
||||
case 3:
|
||||
passed &= !rc;
|
||||
passed &= (args.args_count == 2);
|
||||
passed &= (args.args[0] == 15);
|
||||
passed &= (args.args[1] == 16);
|
||||
break;
|
||||
default:
|
||||
passed = false;
|
||||
}
|
||||
selftest(passed, "index %i - data error on node %s rc=%i\n",
|
||||
i, args.np->full_name, rc);
|
||||
}
|
||||
of_node_put(np);
|
||||
}
|
||||
|
||||
static void __init of_selftest_parse_interrupts_extended(void)
|
||||
{
|
||||
struct device_node *np;
|
||||
struct of_phandle_args args;
|
||||
int i, rc;
|
||||
|
||||
np = of_find_node_by_path("/testcase-data/interrupts/interrupts-extended0");
|
||||
if (!np) {
|
||||
pr_err("missing testcase data\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < 7; i++) {
|
||||
bool passed = true;
|
||||
rc = of_irq_parse_one(np, i, &args);
|
||||
|
||||
/* Test the values from tests-phandle.dtsi */
|
||||
switch (i) {
|
||||
case 0:
|
||||
passed &= !rc;
|
||||
passed &= (args.args_count == 1);
|
||||
passed &= (args.args[0] == 1);
|
||||
break;
|
||||
case 1:
|
||||
passed &= !rc;
|
||||
passed &= (args.args_count == 3);
|
||||
passed &= (args.args[0] == 2);
|
||||
passed &= (args.args[1] == 3);
|
||||
passed &= (args.args[2] == 4);
|
||||
break;
|
||||
case 2:
|
||||
passed &= !rc;
|
||||
passed &= (args.args_count == 2);
|
||||
passed &= (args.args[0] == 5);
|
||||
passed &= (args.args[1] == 6);
|
||||
break;
|
||||
case 3:
|
||||
passed &= !rc;
|
||||
passed &= (args.args_count == 1);
|
||||
passed &= (args.args[0] == 9);
|
||||
break;
|
||||
case 4:
|
||||
passed &= !rc;
|
||||
passed &= (args.args_count == 3);
|
||||
passed &= (args.args[0] == 10);
|
||||
passed &= (args.args[1] == 11);
|
||||
passed &= (args.args[2] == 12);
|
||||
break;
|
||||
case 5:
|
||||
passed &= !rc;
|
||||
passed &= (args.args_count == 2);
|
||||
passed &= (args.args[0] == 13);
|
||||
passed &= (args.args[1] == 14);
|
||||
break;
|
||||
case 6:
|
||||
passed &= !rc;
|
||||
passed &= (args.args_count == 1);
|
||||
passed &= (args.args[0] == 15);
|
||||
break;
|
||||
default:
|
||||
passed = false;
|
||||
}
|
||||
|
||||
selftest(passed, "index %i - data error on node %s rc=%i\n",
|
||||
i, args.np->full_name, rc);
|
||||
}
|
||||
of_node_put(np);
|
||||
}
|
||||
|
||||
static struct of_device_id match_node_table[] = {
|
||||
{ .data = "A", .name = "name0", }, /* Name alone is lowest priority */
|
||||
{ .data = "B", .type = "type1", }, /* followed by type alone */
|
||||
|
||||
{ .data = "Ca", .name = "name2", .type = "type1", }, /* followed by both together */
|
||||
{ .data = "Cb", .name = "name2", }, /* Only match when type doesn't match */
|
||||
{ .data = "Cc", .name = "name2", .type = "type2", },
|
||||
|
||||
{ .data = "E", .compatible = "compat3" },
|
||||
{ .data = "G", .compatible = "compat2", },
|
||||
{ .data = "H", .compatible = "compat2", .name = "name5", },
|
||||
{ .data = "I", .compatible = "compat2", .type = "type1", },
|
||||
{ .data = "J", .compatible = "compat2", .type = "type1", .name = "name8", },
|
||||
{ .data = "K", .compatible = "compat2", .name = "name9", },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct {
|
||||
const char *path;
|
||||
const char *data;
|
||||
} match_node_tests[] = {
|
||||
{ .path = "/testcase-data/match-node/name0", .data = "A", },
|
||||
{ .path = "/testcase-data/match-node/name1", .data = "B", },
|
||||
{ .path = "/testcase-data/match-node/a/name2", .data = "Ca", },
|
||||
{ .path = "/testcase-data/match-node/b/name2", .data = "Cb", },
|
||||
{ .path = "/testcase-data/match-node/c/name2", .data = "Cc", },
|
||||
{ .path = "/testcase-data/match-node/name3", .data = "E", },
|
||||
{ .path = "/testcase-data/match-node/name4", .data = "G", },
|
||||
{ .path = "/testcase-data/match-node/name5", .data = "H", },
|
||||
{ .path = "/testcase-data/match-node/name6", .data = "G", },
|
||||
{ .path = "/testcase-data/match-node/name7", .data = "I", },
|
||||
{ .path = "/testcase-data/match-node/name8", .data = "J", },
|
||||
{ .path = "/testcase-data/match-node/name9", .data = "K", },
|
||||
};
|
||||
|
||||
static void __init of_selftest_match_node(void)
|
||||
{
|
||||
struct device_node *np;
|
||||
const struct of_device_id *match;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(match_node_tests); i++) {
|
||||
np = of_find_node_by_path(match_node_tests[i].path);
|
||||
if (!np) {
|
||||
selftest(0, "missing testcase node %s\n",
|
||||
match_node_tests[i].path);
|
||||
continue;
|
||||
}
|
||||
|
||||
match = of_match_node(match_node_table, np);
|
||||
if (!match) {
|
||||
selftest(0, "%s didn't match anything\n",
|
||||
match_node_tests[i].path);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp(match->data, match_node_tests[i].data) != 0) {
|
||||
selftest(0, "%s got wrong match. expected %s, got %s\n",
|
||||
match_node_tests[i].path, match_node_tests[i].data,
|
||||
(const char *)match->data);
|
||||
continue;
|
||||
}
|
||||
selftest(1, "passed");
|
||||
}
|
||||
}
|
||||
|
||||
static void __init of_selftest_platform_populate(void)
|
||||
{
|
||||
int irq;
|
||||
struct device_node *np, *child;
|
||||
struct platform_device *pdev;
|
||||
struct of_device_id match[] = {
|
||||
{ .compatible = "test-device", },
|
||||
{}
|
||||
};
|
||||
|
||||
np = of_find_node_by_path("/testcase-data");
|
||||
of_platform_populate(np, of_default_bus_match_table, NULL, NULL);
|
||||
|
||||
/* Test that a missing irq domain returns -EPROBE_DEFER */
|
||||
np = of_find_node_by_path("/testcase-data/testcase-device1");
|
||||
pdev = of_find_device_by_node(np);
|
||||
selftest(pdev, "device 1 creation failed\n");
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
selftest(irq == -EPROBE_DEFER, "device deferred probe failed - %d\n", irq);
|
||||
|
||||
/* Test that a parsing failure does not return -EPROBE_DEFER */
|
||||
np = of_find_node_by_path("/testcase-data/testcase-device2");
|
||||
pdev = of_find_device_by_node(np);
|
||||
selftest(pdev, "device 2 creation failed\n");
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
selftest(irq < 0 && irq != -EPROBE_DEFER, "device parsing error failed - %d\n", irq);
|
||||
|
||||
np = of_find_node_by_path("/testcase-data/platform-tests");
|
||||
if (!np) {
|
||||
pr_err("No testcase data in device tree\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for_each_child_of_node(np, child) {
|
||||
struct device_node *grandchild;
|
||||
of_platform_populate(child, match, NULL, NULL);
|
||||
for_each_child_of_node(child, grandchild)
|
||||
selftest(of_find_device_by_node(grandchild),
|
||||
"Could not create device for node '%s'\n",
|
||||
grandchild->name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* update_node_properties - adds the properties
|
||||
* of np into dup node (present in live tree) and
|
||||
* updates parent of children of np to dup.
|
||||
*
|
||||
* @np: node already present in live tree
|
||||
* @dup: node present in live tree to be updated
|
||||
*/
|
||||
static void update_node_properties(struct device_node *np,
|
||||
struct device_node *dup)
|
||||
{
|
||||
struct property *prop;
|
||||
struct device_node *child;
|
||||
|
||||
for_each_property_of_node(np, prop)
|
||||
of_add_property(dup, prop);
|
||||
|
||||
for_each_child_of_node(np, child)
|
||||
child->parent = dup;
|
||||
}
|
||||
|
||||
/**
|
||||
* attach_node_and_children - attaches nodes
|
||||
* and its children to live tree
|
||||
*
|
||||
* @np: Node to attach to live tree
|
||||
*/
|
||||
static int attach_node_and_children(struct device_node *np)
|
||||
{
|
||||
struct device_node *next, *root = np, *dup;
|
||||
|
||||
/* skip root node */
|
||||
np = np->child;
|
||||
/* storing a copy in temporary node */
|
||||
dup = np;
|
||||
|
||||
while (dup) {
|
||||
if (WARN_ON(last_node_index >= NO_OF_NODES))
|
||||
return -EINVAL;
|
||||
nodes[last_node_index++] = dup;
|
||||
dup = dup->sibling;
|
||||
}
|
||||
dup = NULL;
|
||||
|
||||
while (np) {
|
||||
next = np->allnext;
|
||||
dup = of_find_node_by_path(np->full_name);
|
||||
if (dup)
|
||||
update_node_properties(np, dup);
|
||||
else {
|
||||
np->child = NULL;
|
||||
if (np->parent == root)
|
||||
np->parent = of_allnodes;
|
||||
of_attach_node(np);
|
||||
}
|
||||
np = next;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* selftest_data_add - Reads, copies data from
|
||||
* linked tree and attaches it to the live tree
|
||||
*/
|
||||
static int __init selftest_data_add(void)
|
||||
{
|
||||
void *selftest_data;
|
||||
struct device_node *selftest_data_node, *np;
|
||||
extern uint8_t __dtb_testcases_begin[];
|
||||
extern uint8_t __dtb_testcases_end[];
|
||||
const int size = __dtb_testcases_end - __dtb_testcases_begin;
|
||||
int rc;
|
||||
|
||||
if (!size) {
|
||||
pr_warn("%s: No testcase data to attach; not running tests\n",
|
||||
__func__);
|
||||
return -ENODATA;
|
||||
}
|
||||
|
||||
/* creating copy */
|
||||
selftest_data = kmemdup(__dtb_testcases_begin, size, GFP_KERNEL);
|
||||
|
||||
if (!selftest_data) {
|
||||
pr_warn("%s: Failed to allocate memory for selftest_data; "
|
||||
"not running tests\n", __func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
of_fdt_unflatten_tree(selftest_data, &selftest_data_node);
|
||||
if (!selftest_data_node) {
|
||||
pr_warn("%s: No tree to attach; not running tests\n", __func__);
|
||||
return -ENODATA;
|
||||
}
|
||||
of_node_set_flag(selftest_data_node, OF_DETACHED);
|
||||
rc = of_resolve_phandles(selftest_data_node);
|
||||
if (rc) {
|
||||
pr_err("%s: Failed to resolve phandles (rc=%i)\n", __func__, rc);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!of_allnodes) {
|
||||
/* enabling flag for removing nodes */
|
||||
selftest_live_tree = true;
|
||||
of_allnodes = selftest_data_node;
|
||||
|
||||
for_each_of_allnodes(np)
|
||||
__of_attach_node_sysfs(np);
|
||||
of_aliases = of_find_node_by_path("/aliases");
|
||||
of_chosen = of_find_node_by_path("/chosen");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* attach the sub-tree to live tree */
|
||||
return attach_node_and_children(selftest_data_node);
|
||||
}
|
||||
|
||||
/**
|
||||
* detach_node_and_children - detaches node
|
||||
* and its children from live tree
|
||||
*
|
||||
* @np: Node to detach from live tree
|
||||
*/
|
||||
static void detach_node_and_children(struct device_node *np)
|
||||
{
|
||||
while (np->child)
|
||||
detach_node_and_children(np->child);
|
||||
of_detach_node(np);
|
||||
}
|
||||
|
||||
/**
|
||||
* selftest_data_remove - removes the selftest data
|
||||
* nodes from the live tree
|
||||
*/
|
||||
static void selftest_data_remove(void)
|
||||
{
|
||||
struct device_node *np;
|
||||
struct property *prop;
|
||||
|
||||
if (selftest_live_tree) {
|
||||
of_node_put(of_aliases);
|
||||
of_node_put(of_chosen);
|
||||
of_aliases = NULL;
|
||||
of_chosen = NULL;
|
||||
for_each_child_of_node(of_allnodes, np)
|
||||
detach_node_and_children(np);
|
||||
__of_detach_node_sysfs(of_allnodes);
|
||||
of_allnodes = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
while (last_node_index-- > 0) {
|
||||
if (nodes[last_node_index]) {
|
||||
np = of_find_node_by_path(nodes[last_node_index]->full_name);
|
||||
if (np == nodes[last_node_index]) {
|
||||
if (of_aliases == np) {
|
||||
of_node_put(of_aliases);
|
||||
of_aliases = NULL;
|
||||
}
|
||||
detach_node_and_children(np);
|
||||
} else {
|
||||
for_each_property_of_node(np, prop) {
|
||||
if (strcmp(prop->name, "testcase-alias") == 0)
|
||||
of_remove_property(np, prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int __init of_selftest(void)
|
||||
{
|
||||
struct device_node *np;
|
||||
int res;
|
||||
|
||||
/* adding data for selftest */
|
||||
res = selftest_data_add();
|
||||
if (res)
|
||||
return res;
|
||||
if (!of_aliases)
|
||||
of_aliases = of_find_node_by_path("/aliases");
|
||||
|
||||
np = of_find_node_by_path("/testcase-data/phandle-tests/consumer-a");
|
||||
if (!np) {
|
||||
pr_info("No testcase data in device tree; not running tests\n");
|
||||
return 0;
|
||||
}
|
||||
of_node_put(np);
|
||||
|
||||
pr_info("start of selftest - you will see error messages\n");
|
||||
of_selftest_check_tree_linkage();
|
||||
of_selftest_check_phandles();
|
||||
of_selftest_find_node_by_name();
|
||||
of_selftest_dynamic();
|
||||
of_selftest_parse_phandle_with_args();
|
||||
of_selftest_property_string();
|
||||
of_selftest_property_copy();
|
||||
of_selftest_changeset();
|
||||
of_selftest_parse_interrupts();
|
||||
of_selftest_parse_interrupts_extended();
|
||||
of_selftest_match_node();
|
||||
of_selftest_platform_populate();
|
||||
|
||||
/* removing selftest data from live tree */
|
||||
selftest_data_remove();
|
||||
|
||||
/* Double check linkage after removing testcase data */
|
||||
of_selftest_check_tree_linkage();
|
||||
|
||||
pr_info("end of selftest - %i passed, %i failed\n",
|
||||
selftest_results.passed, selftest_results.failed);
|
||||
|
||||
return 0;
|
||||
}
|
||||
late_initcall(of_selftest);
|
50
drivers/of/testcase-data/testcases.dts
Normal file
50
drivers/of/testcase-data/testcases.dts
Normal file
|
@ -0,0 +1,50 @@
|
|||
/dts-v1/;
|
||||
/ {
|
||||
testcase-data {
|
||||
changeset {
|
||||
prop-update = "hello";
|
||||
prop-remove = "world";
|
||||
node-remove {
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
#include "tests-phandle.dtsi"
|
||||
#include "tests-interrupts.dtsi"
|
||||
#include "tests-match.dtsi"
|
||||
#include "tests-platform.dtsi"
|
||||
|
||||
/*
|
||||
* phandle fixup data - generated by dtc patches that aren't upstream.
|
||||
* This data must be regenerated whenever phandle references are modified in
|
||||
* the testdata tree.
|
||||
*
|
||||
* The format of this data may be subject to change. For the time being consider
|
||||
* this a kernel-internal data format.
|
||||
*/
|
||||
/ { __local_fixups__ {
|
||||
fixup = "/testcase-data/testcase-device2:interrupt-parent:0",
|
||||
"/testcase-data/testcase-device1:interrupt-parent:0",
|
||||
"/testcase-data/interrupts/interrupts-extended0:interrupts-extended:60",
|
||||
"/testcase-data/interrupts/interrupts-extended0:interrupts-extended:52",
|
||||
"/testcase-data/interrupts/interrupts-extended0:interrupts-extended:44",
|
||||
"/testcase-data/interrupts/interrupts-extended0:interrupts-extended:36",
|
||||
"/testcase-data/interrupts/interrupts-extended0:interrupts-extended:24",
|
||||
"/testcase-data/interrupts/interrupts-extended0:interrupts-extended:8",
|
||||
"/testcase-data/interrupts/interrupts-extended0:interrupts-extended:0",
|
||||
"/testcase-data/interrupts/interrupts1:interrupt-parent:0",
|
||||
"/testcase-data/interrupts/interrupts0:interrupt-parent:0",
|
||||
"/testcase-data/interrupts/intmap1:interrupt-map:12",
|
||||
"/testcase-data/interrupts/intmap0:interrupt-map:52",
|
||||
"/testcase-data/interrupts/intmap0:interrupt-map:36",
|
||||
"/testcase-data/interrupts/intmap0:interrupt-map:16",
|
||||
"/testcase-data/interrupts/intmap0:interrupt-map:4",
|
||||
"/testcase-data/phandle-tests/consumer-a:phandle-list-bad-args:12",
|
||||
"/testcase-data/phandle-tests/consumer-a:phandle-list-bad-args:0",
|
||||
"/testcase-data/phandle-tests/consumer-a:phandle-list:56",
|
||||
"/testcase-data/phandle-tests/consumer-a:phandle-list:52",
|
||||
"/testcase-data/phandle-tests/consumer-a:phandle-list:40",
|
||||
"/testcase-data/phandle-tests/consumer-a:phandle-list:24",
|
||||
"/testcase-data/phandle-tests/consumer-a:phandle-list:8",
|
||||
"/testcase-data/phandle-tests/consumer-a:phandle-list:0";
|
||||
}; };
|
71
drivers/of/testcase-data/tests-interrupts.dtsi
Normal file
71
drivers/of/testcase-data/tests-interrupts.dtsi
Normal file
|
@ -0,0 +1,71 @@
|
|||
|
||||
/ {
|
||||
testcase-data {
|
||||
interrupts {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
test_intc0: intc0 {
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <1>;
|
||||
};
|
||||
|
||||
test_intc1: intc1 {
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <3>;
|
||||
};
|
||||
|
||||
test_intc2: intc2 {
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <2>;
|
||||
};
|
||||
|
||||
test_intmap0: intmap0 {
|
||||
#interrupt-cells = <1>;
|
||||
#address-cells = <0>;
|
||||
interrupt-map = <1 &test_intc0 9>,
|
||||
<2 &test_intc1 10 11 12>,
|
||||
<3 &test_intc2 13 14>,
|
||||
<4 &test_intc2 15 16>;
|
||||
};
|
||||
|
||||
test_intmap1: intmap1 {
|
||||
#interrupt-cells = <2>;
|
||||
interrupt-map = <0x5000 1 2 &test_intc0 15>;
|
||||
};
|
||||
|
||||
interrupts0 {
|
||||
interrupt-parent = <&test_intc0>;
|
||||
interrupts = <1>, <2>, <3>, <4>;
|
||||
};
|
||||
|
||||
interrupts1 {
|
||||
interrupt-parent = <&test_intmap0>;
|
||||
interrupts = <1>, <2>, <3>, <4>;
|
||||
};
|
||||
|
||||
interrupts-extended0 {
|
||||
reg = <0x5000 0x100>;
|
||||
interrupts-extended = <&test_intc0 1>,
|
||||
<&test_intc1 2 3 4>,
|
||||
<&test_intc2 5 6>,
|
||||
<&test_intmap0 1>,
|
||||
<&test_intmap0 2>,
|
||||
<&test_intmap0 3>,
|
||||
<&test_intmap1 1 2>;
|
||||
};
|
||||
};
|
||||
|
||||
testcase-device1 {
|
||||
compatible = "testcase-device";
|
||||
interrupt-parent = <&test_intc0>;
|
||||
interrupts = <1>;
|
||||
};
|
||||
|
||||
testcase-device2 {
|
||||
compatible = "testcase-device";
|
||||
interrupt-parent = <&test_intc2>;
|
||||
interrupts = <1>; /* invalid specifier - too short */
|
||||
};
|
||||
};
|
||||
|
||||
};
|
19
drivers/of/testcase-data/tests-match.dtsi
Normal file
19
drivers/of/testcase-data/tests-match.dtsi
Normal file
|
@ -0,0 +1,19 @@
|
|||
|
||||
/ {
|
||||
testcase-data {
|
||||
match-node {
|
||||
name0 { };
|
||||
name1 { device_type = "type1"; };
|
||||
a { name2 { device_type = "type1"; }; };
|
||||
b { name2 { }; };
|
||||
c { name2 { device_type = "type2"; }; };
|
||||
name3 { compatible = "compat3"; };
|
||||
name4 { compatible = "compat2", "compat3"; };
|
||||
name5 { compatible = "compat2", "compat3"; };
|
||||
name6 { compatible = "compat1", "compat2", "compat3"; };
|
||||
name7 { compatible = "compat2"; device_type = "type1"; };
|
||||
name8 { compatible = "compat2"; device_type = "type1"; };
|
||||
name9 { compatible = "compat2"; };
|
||||
};
|
||||
};
|
||||
};
|
48
drivers/of/testcase-data/tests-phandle.dtsi
Normal file
48
drivers/of/testcase-data/tests-phandle.dtsi
Normal file
|
@ -0,0 +1,48 @@
|
|||
|
||||
/ {
|
||||
aliases {
|
||||
testcase-alias = &testcase;
|
||||
};
|
||||
|
||||
testcase: testcase-data {
|
||||
security-password = "password";
|
||||
duplicate-name = "duplicate";
|
||||
duplicate-name { };
|
||||
phandle-tests {
|
||||
provider0: provider0 {
|
||||
#phandle-cells = <0>;
|
||||
};
|
||||
|
||||
provider1: provider1 {
|
||||
#phandle-cells = <1>;
|
||||
};
|
||||
|
||||
provider2: provider2 {
|
||||
#phandle-cells = <2>;
|
||||
};
|
||||
|
||||
provider3: provider3 {
|
||||
#phandle-cells = <3>;
|
||||
};
|
||||
|
||||
consumer-a {
|
||||
phandle-list = <&provider1 1>,
|
||||
<&provider2 2 0>,
|
||||
<0>,
|
||||
<&provider3 4 4 3>,
|
||||
<&provider2 5 100>,
|
||||
<&provider0>,
|
||||
<&provider1 7>;
|
||||
phandle-list-names = "first", "second", "third";
|
||||
|
||||
phandle-list-bad-phandle = <12345678 0 0>;
|
||||
phandle-list-bad-args = <&provider2 1 0>,
|
||||
<&provider3 0>;
|
||||
empty-property;
|
||||
string-property = "foobar";
|
||||
unterminated-string = [40 41 42 43];
|
||||
unterminated-string-list = "first", "second", [40 41 42 43];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
35
drivers/of/testcase-data/tests-platform.dtsi
Normal file
35
drivers/of/testcase-data/tests-platform.dtsi
Normal file
|
@ -0,0 +1,35 @@
|
|||
|
||||
/ {
|
||||
testcase-data {
|
||||
platform-tests {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
test-device@0 {
|
||||
compatible = "test-device";
|
||||
reg = <0x0>;
|
||||
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
dev@100 {
|
||||
compatible = "test-sub-device";
|
||||
reg = <0x100>;
|
||||
};
|
||||
};
|
||||
|
||||
test-device@1 {
|
||||
compatible = "test-device";
|
||||
reg = <0x1>;
|
||||
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
dev@100 {
|
||||
compatible = "test-sub-device";
|
||||
reg = <0x100>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue