mirror of
https://github.com/torvalds/linux.git
synced 2024-11-25 13:41:51 +00:00
854f695c3d
of_amba_device_create() uses irq_of_parse_and_map() to translate a DT interrupt specification into a Linux virtual interrupt number. But it doesn't properly handle the case where the interrupt controller is not yet available, eg, when pl011 interrupt is connected to MBIGEN interrupt controller, because the mbigen initialization is too late, which will lead to no IRQ due to no IRQ domain found, log is shown below, "irq: no irq domain found for uart0 !" use of_irq_get() to return -EPROBE_DEFER as above, and in the function amba_device_try_add()/amba_device_add(), it will properly handle in such case, also return 0 in other fail cases to be consistent as before. Cc: Rob Herring <robh+dt@kernel.org> Cc: Frank Rowand <frowand.list@gmail.com> Reported-by: Ruizhe Lin <linruizhe@huawei.com> Reviewed-by: Rob Herring <robh@kernel.org> Signed-off-by: Kefeng Wang <wangkefeng.wang@huawei.com> Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
789 lines
18 KiB
C
789 lines
18 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* linux/arch/arm/common/amba.c
|
|
*
|
|
* Copyright (C) 2003 Deep Blue Solutions Ltd, All Rights Reserved.
|
|
*/
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/device.h>
|
|
#include <linux/string.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/io.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/pm_domain.h>
|
|
#include <linux/amba/bus.h>
|
|
#include <linux/sizes.h>
|
|
#include <linux/limits.h>
|
|
#include <linux/clk/clk-conf.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/reset.h>
|
|
#include <linux/of_irq.h>
|
|
|
|
#include <asm/irq.h>
|
|
|
|
#define to_amba_driver(d) container_of(d, struct amba_driver, drv)
|
|
|
|
/* called on periphid match and class 0x9 coresight device. */
|
|
static int
|
|
amba_cs_uci_id_match(const struct amba_id *table, struct amba_device *dev)
|
|
{
|
|
int ret = 0;
|
|
struct amba_cs_uci_id *uci;
|
|
|
|
uci = table->data;
|
|
|
|
/* no table data or zero mask - return match on periphid */
|
|
if (!uci || (uci->devarch_mask == 0))
|
|
return 1;
|
|
|
|
/* test against read devtype and masked devarch value */
|
|
ret = (dev->uci.devtype == uci->devtype) &&
|
|
((dev->uci.devarch & uci->devarch_mask) == uci->devarch);
|
|
return ret;
|
|
}
|
|
|
|
static const struct amba_id *
|
|
amba_lookup(const struct amba_id *table, struct amba_device *dev)
|
|
{
|
|
while (table->mask) {
|
|
if (((dev->periphid & table->mask) == table->id) &&
|
|
((dev->cid != CORESIGHT_CID) ||
|
|
(amba_cs_uci_id_match(table, dev))))
|
|
return table;
|
|
table++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int amba_get_enable_pclk(struct amba_device *pcdev)
|
|
{
|
|
int ret;
|
|
|
|
pcdev->pclk = clk_get(&pcdev->dev, "apb_pclk");
|
|
if (IS_ERR(pcdev->pclk))
|
|
return PTR_ERR(pcdev->pclk);
|
|
|
|
ret = clk_prepare_enable(pcdev->pclk);
|
|
if (ret)
|
|
clk_put(pcdev->pclk);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void amba_put_disable_pclk(struct amba_device *pcdev)
|
|
{
|
|
clk_disable_unprepare(pcdev->pclk);
|
|
clk_put(pcdev->pclk);
|
|
}
|
|
|
|
|
|
static ssize_t driver_override_show(struct device *_dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct amba_device *dev = to_amba_device(_dev);
|
|
ssize_t len;
|
|
|
|
device_lock(_dev);
|
|
len = sprintf(buf, "%s\n", dev->driver_override);
|
|
device_unlock(_dev);
|
|
return len;
|
|
}
|
|
|
|
static ssize_t driver_override_store(struct device *_dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct amba_device *dev = to_amba_device(_dev);
|
|
char *driver_override, *old, *cp;
|
|
|
|
/* We need to keep extra room for a newline */
|
|
if (count >= (PAGE_SIZE - 1))
|
|
return -EINVAL;
|
|
|
|
driver_override = kstrndup(buf, count, GFP_KERNEL);
|
|
if (!driver_override)
|
|
return -ENOMEM;
|
|
|
|
cp = strchr(driver_override, '\n');
|
|
if (cp)
|
|
*cp = '\0';
|
|
|
|
device_lock(_dev);
|
|
old = dev->driver_override;
|
|
if (strlen(driver_override)) {
|
|
dev->driver_override = driver_override;
|
|
} else {
|
|
kfree(driver_override);
|
|
dev->driver_override = NULL;
|
|
}
|
|
device_unlock(_dev);
|
|
|
|
kfree(old);
|
|
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR_RW(driver_override);
|
|
|
|
#define amba_attr_func(name,fmt,arg...) \
|
|
static ssize_t name##_show(struct device *_dev, \
|
|
struct device_attribute *attr, char *buf) \
|
|
{ \
|
|
struct amba_device *dev = to_amba_device(_dev); \
|
|
return sprintf(buf, fmt, arg); \
|
|
} \
|
|
static DEVICE_ATTR_RO(name)
|
|
|
|
amba_attr_func(id, "%08x\n", dev->periphid);
|
|
amba_attr_func(irq0, "%u\n", dev->irq[0]);
|
|
amba_attr_func(irq1, "%u\n", dev->irq[1]);
|
|
amba_attr_func(resource, "\t%016llx\t%016llx\t%016lx\n",
|
|
(unsigned long long)dev->res.start, (unsigned long long)dev->res.end,
|
|
dev->res.flags);
|
|
|
|
static struct attribute *amba_dev_attrs[] = {
|
|
&dev_attr_id.attr,
|
|
&dev_attr_resource.attr,
|
|
&dev_attr_driver_override.attr,
|
|
NULL,
|
|
};
|
|
ATTRIBUTE_GROUPS(amba_dev);
|
|
|
|
static int amba_match(struct device *dev, struct device_driver *drv)
|
|
{
|
|
struct amba_device *pcdev = to_amba_device(dev);
|
|
struct amba_driver *pcdrv = to_amba_driver(drv);
|
|
|
|
/* When driver_override is set, only bind to the matching driver */
|
|
if (pcdev->driver_override)
|
|
return !strcmp(pcdev->driver_override, drv->name);
|
|
|
|
return amba_lookup(pcdrv->id_table, pcdev) != NULL;
|
|
}
|
|
|
|
static int amba_uevent(struct device *dev, struct kobj_uevent_env *env)
|
|
{
|
|
struct amba_device *pcdev = to_amba_device(dev);
|
|
int retval = 0;
|
|
|
|
retval = add_uevent_var(env, "AMBA_ID=%08x", pcdev->periphid);
|
|
if (retval)
|
|
return retval;
|
|
|
|
retval = add_uevent_var(env, "MODALIAS=amba:d%08X", pcdev->periphid);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* These are the device model conversion veneers; they convert the
|
|
* device model structures to our more specific structures.
|
|
*/
|
|
static int amba_probe(struct device *dev)
|
|
{
|
|
struct amba_device *pcdev = to_amba_device(dev);
|
|
struct amba_driver *pcdrv = to_amba_driver(dev->driver);
|
|
const struct amba_id *id = amba_lookup(pcdrv->id_table, pcdev);
|
|
int ret;
|
|
|
|
do {
|
|
ret = of_clk_set_defaults(dev->of_node, false);
|
|
if (ret < 0)
|
|
break;
|
|
|
|
ret = dev_pm_domain_attach(dev, true);
|
|
if (ret)
|
|
break;
|
|
|
|
ret = amba_get_enable_pclk(pcdev);
|
|
if (ret) {
|
|
dev_pm_domain_detach(dev, true);
|
|
break;
|
|
}
|
|
|
|
pm_runtime_get_noresume(dev);
|
|
pm_runtime_set_active(dev);
|
|
pm_runtime_enable(dev);
|
|
|
|
ret = pcdrv->probe(pcdev, id);
|
|
if (ret == 0)
|
|
break;
|
|
|
|
pm_runtime_disable(dev);
|
|
pm_runtime_set_suspended(dev);
|
|
pm_runtime_put_noidle(dev);
|
|
|
|
amba_put_disable_pclk(pcdev);
|
|
dev_pm_domain_detach(dev, true);
|
|
} while (0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void amba_remove(struct device *dev)
|
|
{
|
|
struct amba_device *pcdev = to_amba_device(dev);
|
|
struct amba_driver *drv = to_amba_driver(dev->driver);
|
|
|
|
pm_runtime_get_sync(dev);
|
|
if (drv->remove)
|
|
drv->remove(pcdev);
|
|
pm_runtime_put_noidle(dev);
|
|
|
|
/* Undo the runtime PM settings in amba_probe() */
|
|
pm_runtime_disable(dev);
|
|
pm_runtime_set_suspended(dev);
|
|
pm_runtime_put_noidle(dev);
|
|
|
|
amba_put_disable_pclk(pcdev);
|
|
dev_pm_domain_detach(dev, true);
|
|
}
|
|
|
|
static void amba_shutdown(struct device *dev)
|
|
{
|
|
struct amba_driver *drv;
|
|
|
|
if (!dev->driver)
|
|
return;
|
|
|
|
drv = to_amba_driver(dev->driver);
|
|
if (drv->shutdown)
|
|
drv->shutdown(to_amba_device(dev));
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
/*
|
|
* Hooks to provide runtime PM of the pclk (bus clock). It is safe to
|
|
* enable/disable the bus clock at runtime PM suspend/resume as this
|
|
* does not result in loss of context.
|
|
*/
|
|
static int amba_pm_runtime_suspend(struct device *dev)
|
|
{
|
|
struct amba_device *pcdev = to_amba_device(dev);
|
|
int ret = pm_generic_runtime_suspend(dev);
|
|
|
|
if (ret == 0 && dev->driver) {
|
|
if (pm_runtime_is_irq_safe(dev))
|
|
clk_disable(pcdev->pclk);
|
|
else
|
|
clk_disable_unprepare(pcdev->pclk);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int amba_pm_runtime_resume(struct device *dev)
|
|
{
|
|
struct amba_device *pcdev = to_amba_device(dev);
|
|
int ret;
|
|
|
|
if (dev->driver) {
|
|
if (pm_runtime_is_irq_safe(dev))
|
|
ret = clk_enable(pcdev->pclk);
|
|
else
|
|
ret = clk_prepare_enable(pcdev->pclk);
|
|
/* Failure is probably fatal to the system, but... */
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return pm_generic_runtime_resume(dev);
|
|
}
|
|
#endif /* CONFIG_PM */
|
|
|
|
static const struct dev_pm_ops amba_pm = {
|
|
.suspend = pm_generic_suspend,
|
|
.resume = pm_generic_resume,
|
|
.freeze = pm_generic_freeze,
|
|
.thaw = pm_generic_thaw,
|
|
.poweroff = pm_generic_poweroff,
|
|
.restore = pm_generic_restore,
|
|
SET_RUNTIME_PM_OPS(
|
|
amba_pm_runtime_suspend,
|
|
amba_pm_runtime_resume,
|
|
NULL
|
|
)
|
|
};
|
|
|
|
/*
|
|
* Primecells are part of the Advanced Microcontroller Bus Architecture,
|
|
* so we call the bus "amba".
|
|
* DMA configuration for platform and AMBA bus is same. So here we reuse
|
|
* platform's DMA config routine.
|
|
*/
|
|
struct bus_type amba_bustype = {
|
|
.name = "amba",
|
|
.dev_groups = amba_dev_groups,
|
|
.match = amba_match,
|
|
.uevent = amba_uevent,
|
|
.probe = amba_probe,
|
|
.remove = amba_remove,
|
|
.shutdown = amba_shutdown,
|
|
.dma_configure = platform_dma_configure,
|
|
.pm = &amba_pm,
|
|
};
|
|
EXPORT_SYMBOL_GPL(amba_bustype);
|
|
|
|
static int __init amba_init(void)
|
|
{
|
|
return bus_register(&amba_bustype);
|
|
}
|
|
|
|
postcore_initcall(amba_init);
|
|
|
|
/**
|
|
* amba_driver_register - register an AMBA device driver
|
|
* @drv: amba device driver structure
|
|
*
|
|
* Register an AMBA device driver with the Linux device model
|
|
* core. If devices pre-exist, the drivers probe function will
|
|
* be called.
|
|
*/
|
|
int amba_driver_register(struct amba_driver *drv)
|
|
{
|
|
if (!drv->probe)
|
|
return -EINVAL;
|
|
|
|
drv->drv.bus = &amba_bustype;
|
|
|
|
return driver_register(&drv->drv);
|
|
}
|
|
|
|
/**
|
|
* amba_driver_unregister - remove an AMBA device driver
|
|
* @drv: AMBA device driver structure to remove
|
|
*
|
|
* Unregister an AMBA device driver from the Linux device
|
|
* model. The device model will call the drivers remove function
|
|
* for each device the device driver is currently handling.
|
|
*/
|
|
void amba_driver_unregister(struct amba_driver *drv)
|
|
{
|
|
driver_unregister(&drv->drv);
|
|
}
|
|
|
|
|
|
static void amba_device_release(struct device *dev)
|
|
{
|
|
struct amba_device *d = to_amba_device(dev);
|
|
|
|
if (d->res.parent)
|
|
release_resource(&d->res);
|
|
kfree(d);
|
|
}
|
|
|
|
static int of_amba_device_decode_irq(struct amba_device *dev)
|
|
{
|
|
struct device_node *node = dev->dev.of_node;
|
|
int i, irq = 0;
|
|
|
|
if (IS_ENABLED(CONFIG_OF_IRQ) && node) {
|
|
/* Decode the IRQs and address ranges */
|
|
for (i = 0; i < AMBA_NR_IRQS; i++) {
|
|
irq = of_irq_get(node, i);
|
|
if (irq < 0) {
|
|
if (irq == -EPROBE_DEFER)
|
|
return irq;
|
|
irq = 0;
|
|
}
|
|
|
|
dev->irq[i] = irq;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int amba_device_try_add(struct amba_device *dev, struct resource *parent)
|
|
{
|
|
u32 size;
|
|
void __iomem *tmp;
|
|
int i, ret;
|
|
|
|
ret = of_amba_device_decode_irq(dev);
|
|
if (ret)
|
|
goto err_out;
|
|
|
|
ret = request_resource(parent, &dev->res);
|
|
if (ret)
|
|
goto err_out;
|
|
|
|
/* Hard-coded primecell ID instead of plug-n-play */
|
|
if (dev->periphid != 0)
|
|
goto skip_probe;
|
|
|
|
/*
|
|
* Dynamically calculate the size of the resource
|
|
* and use this for iomap
|
|
*/
|
|
size = resource_size(&dev->res);
|
|
tmp = ioremap(dev->res.start, size);
|
|
if (!tmp) {
|
|
ret = -ENOMEM;
|
|
goto err_release;
|
|
}
|
|
|
|
ret = dev_pm_domain_attach(&dev->dev, true);
|
|
if (ret) {
|
|
iounmap(tmp);
|
|
goto err_release;
|
|
}
|
|
|
|
ret = amba_get_enable_pclk(dev);
|
|
if (ret == 0) {
|
|
u32 pid, cid;
|
|
struct reset_control *rstc;
|
|
|
|
/*
|
|
* Find reset control(s) of the amba bus and de-assert them.
|
|
*/
|
|
rstc = of_reset_control_array_get_optional_shared(dev->dev.of_node);
|
|
if (IS_ERR(rstc)) {
|
|
ret = PTR_ERR(rstc);
|
|
if (ret != -EPROBE_DEFER)
|
|
dev_err(&dev->dev, "can't get reset: %d\n",
|
|
ret);
|
|
goto err_reset;
|
|
}
|
|
reset_control_deassert(rstc);
|
|
reset_control_put(rstc);
|
|
|
|
/*
|
|
* Read pid and cid based on size of resource
|
|
* they are located at end of region
|
|
*/
|
|
for (pid = 0, i = 0; i < 4; i++)
|
|
pid |= (readl(tmp + size - 0x20 + 4 * i) & 255) <<
|
|
(i * 8);
|
|
for (cid = 0, i = 0; i < 4; i++)
|
|
cid |= (readl(tmp + size - 0x10 + 4 * i) & 255) <<
|
|
(i * 8);
|
|
|
|
if (cid == CORESIGHT_CID) {
|
|
/* set the base to the start of the last 4k block */
|
|
void __iomem *csbase = tmp + size - 4096;
|
|
|
|
dev->uci.devarch =
|
|
readl(csbase + UCI_REG_DEVARCH_OFFSET);
|
|
dev->uci.devtype =
|
|
readl(csbase + UCI_REG_DEVTYPE_OFFSET) & 0xff;
|
|
}
|
|
|
|
amba_put_disable_pclk(dev);
|
|
|
|
if (cid == AMBA_CID || cid == CORESIGHT_CID) {
|
|
dev->periphid = pid;
|
|
dev->cid = cid;
|
|
}
|
|
|
|
if (!dev->periphid)
|
|
ret = -ENODEV;
|
|
}
|
|
|
|
iounmap(tmp);
|
|
dev_pm_domain_detach(&dev->dev, true);
|
|
|
|
if (ret)
|
|
goto err_release;
|
|
|
|
skip_probe:
|
|
ret = device_add(&dev->dev);
|
|
if (ret)
|
|
goto err_release;
|
|
|
|
if (dev->irq[0])
|
|
ret = device_create_file(&dev->dev, &dev_attr_irq0);
|
|
if (ret == 0 && dev->irq[1])
|
|
ret = device_create_file(&dev->dev, &dev_attr_irq1);
|
|
if (ret == 0)
|
|
return ret;
|
|
|
|
device_unregister(&dev->dev);
|
|
|
|
err_release:
|
|
release_resource(&dev->res);
|
|
err_out:
|
|
return ret;
|
|
|
|
err_reset:
|
|
amba_put_disable_pclk(dev);
|
|
iounmap(tmp);
|
|
dev_pm_domain_detach(&dev->dev, true);
|
|
goto err_release;
|
|
}
|
|
|
|
/*
|
|
* Registration of AMBA device require reading its pid and cid registers.
|
|
* To do this, the device must be turned on (if it is a part of power domain)
|
|
* and have clocks enabled. However in some cases those resources might not be
|
|
* yet available. Returning EPROBE_DEFER is not a solution in such case,
|
|
* because callers don't handle this special error code. Instead such devices
|
|
* are added to the special list and their registration is retried from
|
|
* periodic worker, until all resources are available and registration succeeds.
|
|
*/
|
|
struct deferred_device {
|
|
struct amba_device *dev;
|
|
struct resource *parent;
|
|
struct list_head node;
|
|
};
|
|
|
|
static LIST_HEAD(deferred_devices);
|
|
static DEFINE_MUTEX(deferred_devices_lock);
|
|
|
|
static void amba_deferred_retry_func(struct work_struct *dummy);
|
|
static DECLARE_DELAYED_WORK(deferred_retry_work, amba_deferred_retry_func);
|
|
|
|
#define DEFERRED_DEVICE_TIMEOUT (msecs_to_jiffies(5 * 1000))
|
|
|
|
static int amba_deferred_retry(void)
|
|
{
|
|
struct deferred_device *ddev, *tmp;
|
|
|
|
mutex_lock(&deferred_devices_lock);
|
|
|
|
list_for_each_entry_safe(ddev, tmp, &deferred_devices, node) {
|
|
int ret = amba_device_try_add(ddev->dev, ddev->parent);
|
|
|
|
if (ret == -EPROBE_DEFER)
|
|
continue;
|
|
|
|
list_del_init(&ddev->node);
|
|
kfree(ddev);
|
|
}
|
|
|
|
mutex_unlock(&deferred_devices_lock);
|
|
|
|
return 0;
|
|
}
|
|
late_initcall(amba_deferred_retry);
|
|
|
|
static void amba_deferred_retry_func(struct work_struct *dummy)
|
|
{
|
|
amba_deferred_retry();
|
|
|
|
if (!list_empty(&deferred_devices))
|
|
schedule_delayed_work(&deferred_retry_work,
|
|
DEFERRED_DEVICE_TIMEOUT);
|
|
}
|
|
|
|
/**
|
|
* amba_device_add - add a previously allocated AMBA device structure
|
|
* @dev: AMBA device allocated by amba_device_alloc
|
|
* @parent: resource parent for this devices resources
|
|
*
|
|
* Claim the resource, and read the device cell ID if not already
|
|
* initialized. Register the AMBA device with the Linux device
|
|
* manager.
|
|
*/
|
|
int amba_device_add(struct amba_device *dev, struct resource *parent)
|
|
{
|
|
int ret = amba_device_try_add(dev, parent);
|
|
|
|
if (ret == -EPROBE_DEFER) {
|
|
struct deferred_device *ddev;
|
|
|
|
ddev = kmalloc(sizeof(*ddev), GFP_KERNEL);
|
|
if (!ddev)
|
|
return -ENOMEM;
|
|
|
|
ddev->dev = dev;
|
|
ddev->parent = parent;
|
|
ret = 0;
|
|
|
|
mutex_lock(&deferred_devices_lock);
|
|
|
|
if (list_empty(&deferred_devices))
|
|
schedule_delayed_work(&deferred_retry_work,
|
|
DEFERRED_DEVICE_TIMEOUT);
|
|
list_add_tail(&ddev->node, &deferred_devices);
|
|
|
|
mutex_unlock(&deferred_devices_lock);
|
|
}
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(amba_device_add);
|
|
|
|
static void amba_device_initialize(struct amba_device *dev, const char *name)
|
|
{
|
|
device_initialize(&dev->dev);
|
|
if (name)
|
|
dev_set_name(&dev->dev, "%s", name);
|
|
dev->dev.release = amba_device_release;
|
|
dev->dev.bus = &amba_bustype;
|
|
dev->dev.dma_mask = &dev->dev.coherent_dma_mask;
|
|
dev->dev.dma_parms = &dev->dma_parms;
|
|
dev->res.name = dev_name(&dev->dev);
|
|
}
|
|
|
|
/**
|
|
* amba_device_alloc - allocate an AMBA device
|
|
* @name: sysfs name of the AMBA device
|
|
* @base: base of AMBA device
|
|
* @size: size of AMBA device
|
|
*
|
|
* Allocate and initialize an AMBA device structure. Returns %NULL
|
|
* on failure.
|
|
*/
|
|
struct amba_device *amba_device_alloc(const char *name, resource_size_t base,
|
|
size_t size)
|
|
{
|
|
struct amba_device *dev;
|
|
|
|
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
|
if (dev) {
|
|
amba_device_initialize(dev, name);
|
|
dev->res.start = base;
|
|
dev->res.end = base + size - 1;
|
|
dev->res.flags = IORESOURCE_MEM;
|
|
}
|
|
|
|
return dev;
|
|
}
|
|
EXPORT_SYMBOL_GPL(amba_device_alloc);
|
|
|
|
/**
|
|
* amba_device_register - register an AMBA device
|
|
* @dev: AMBA device to register
|
|
* @parent: parent memory resource
|
|
*
|
|
* Setup the AMBA device, reading the cell ID if present.
|
|
* Claim the resource, and register the AMBA device with
|
|
* the Linux device manager.
|
|
*/
|
|
int amba_device_register(struct amba_device *dev, struct resource *parent)
|
|
{
|
|
amba_device_initialize(dev, dev->dev.init_name);
|
|
dev->dev.init_name = NULL;
|
|
|
|
return amba_device_add(dev, parent);
|
|
}
|
|
|
|
/**
|
|
* amba_device_put - put an AMBA device
|
|
* @dev: AMBA device to put
|
|
*/
|
|
void amba_device_put(struct amba_device *dev)
|
|
{
|
|
put_device(&dev->dev);
|
|
}
|
|
EXPORT_SYMBOL_GPL(amba_device_put);
|
|
|
|
/**
|
|
* amba_device_unregister - unregister an AMBA device
|
|
* @dev: AMBA device to remove
|
|
*
|
|
* Remove the specified AMBA device from the Linux device
|
|
* manager. All files associated with this object will be
|
|
* destroyed, and device drivers notified that the device has
|
|
* been removed. The AMBA device's resources including
|
|
* the amba_device structure will be freed once all
|
|
* references to it have been dropped.
|
|
*/
|
|
void amba_device_unregister(struct amba_device *dev)
|
|
{
|
|
device_unregister(&dev->dev);
|
|
}
|
|
|
|
|
|
struct find_data {
|
|
struct amba_device *dev;
|
|
struct device *parent;
|
|
const char *busid;
|
|
unsigned int id;
|
|
unsigned int mask;
|
|
};
|
|
|
|
static int amba_find_match(struct device *dev, void *data)
|
|
{
|
|
struct find_data *d = data;
|
|
struct amba_device *pcdev = to_amba_device(dev);
|
|
int r;
|
|
|
|
r = (pcdev->periphid & d->mask) == d->id;
|
|
if (d->parent)
|
|
r &= d->parent == dev->parent;
|
|
if (d->busid)
|
|
r &= strcmp(dev_name(dev), d->busid) == 0;
|
|
|
|
if (r) {
|
|
get_device(dev);
|
|
d->dev = pcdev;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
* amba_find_device - locate an AMBA device given a bus id
|
|
* @busid: bus id for device (or NULL)
|
|
* @parent: parent device (or NULL)
|
|
* @id: peripheral ID (or 0)
|
|
* @mask: peripheral ID mask (or 0)
|
|
*
|
|
* Return the AMBA device corresponding to the supplied parameters.
|
|
* If no device matches, returns NULL.
|
|
*
|
|
* NOTE: When a valid device is found, its refcount is
|
|
* incremented, and must be decremented before the returned
|
|
* reference.
|
|
*/
|
|
struct amba_device *
|
|
amba_find_device(const char *busid, struct device *parent, unsigned int id,
|
|
unsigned int mask)
|
|
{
|
|
struct find_data data;
|
|
|
|
data.dev = NULL;
|
|
data.parent = parent;
|
|
data.busid = busid;
|
|
data.id = id;
|
|
data.mask = mask;
|
|
|
|
bus_for_each_dev(&amba_bustype, NULL, &data, amba_find_match);
|
|
|
|
return data.dev;
|
|
}
|
|
|
|
/**
|
|
* amba_request_regions - request all mem regions associated with device
|
|
* @dev: amba_device structure for device
|
|
* @name: name, or NULL to use driver name
|
|
*/
|
|
int amba_request_regions(struct amba_device *dev, const char *name)
|
|
{
|
|
int ret = 0;
|
|
u32 size;
|
|
|
|
if (!name)
|
|
name = dev->dev.driver->name;
|
|
|
|
size = resource_size(&dev->res);
|
|
|
|
if (!request_mem_region(dev->res.start, size, name))
|
|
ret = -EBUSY;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* amba_release_regions - release mem regions associated with device
|
|
* @dev: amba_device structure for device
|
|
*
|
|
* Release regions claimed by a successful call to amba_request_regions.
|
|
*/
|
|
void amba_release_regions(struct amba_device *dev)
|
|
{
|
|
u32 size;
|
|
|
|
size = resource_size(&dev->res);
|
|
release_mem_region(dev->res.start, size);
|
|
}
|
|
|
|
EXPORT_SYMBOL(amba_driver_register);
|
|
EXPORT_SYMBOL(amba_driver_unregister);
|
|
EXPORT_SYMBOL(amba_device_register);
|
|
EXPORT_SYMBOL(amba_device_unregister);
|
|
EXPORT_SYMBOL(amba_find_device);
|
|
EXPORT_SYMBOL(amba_request_regions);
|
|
EXPORT_SYMBOL(amba_release_regions);
|