/*
* linux/arch/arm/common/amba.c
*
* Copyright (C) 2003 Deep Blue Solutions Ltd, All Rights Reserved.
*
* 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/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/amba/bus.h>
#include <asm/irq.h>
#include <asm/sizes.h>
#define to_amba_driver(d) container_of(d, struct amba_driver, drv)
static const struct amba_id *
amba_lookup(const struct amba_id *table, struct amba_device *dev)
{
int ret = 0;
while (table->mask) {
ret = (dev->periphid & table->mask) == table->id;
if (ret)
break;
table++;
}
return ret ? table : NULL;
}
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);
return amba_lookup(pcdrv->id_table, pcdev) != NULL;
}
#ifdef CONFIG_HOTPLUG
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);
return retval;
}
#else
#define amba_uevent NULL
#endif
#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); \
}
#define amba_attr(name,fmt,arg...) \
amba_attr_func(name,fmt,arg) \
static DEVICE_ATTR(name, S_IRUGO, name##_show, NULL)
amba_attr_func(id, "%08x\n", dev->periphid);
amba_attr(irq0, "%u\n", dev->irq[0]);
amba_attr(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 device_attribute amba_dev_attrs[] = {
__ATTR_RO(id),
__ATTR_RO(resource),
__ATTR_NULL,
};
#ifdef CONFIG_PM_SLEEP
static int amba_legacy_suspend(struct device *dev, pm_message_t mesg)
{
struct amba_driver *adrv = to_amba_driver(dev->driver);
struct amba_device *adev = to_amba_device(dev);
int ret = 0;
if (dev->driver && adrv->suspend)
ret = adrv->suspend(adev, mesg);
return ret;
}
static int amba_legacy_resume(struct device *dev)
{
struct amba_driver *adrv = to_amba_driver(dev->driver);
struct amba_device *adev = to_amba_device(dev);
int ret = 0;
if (dev->driver && adrv->resume)
ret = adrv->resume(adev);
return ret;
}
static int amba_pm_prepare(struct device *dev)
{
struct device_driver *drv = dev->driver;
int ret = 0;
if (drv && drv->pm && drv->pm->prepare)
ret = drv->pm->prepare(dev);
return ret;
}
static void amba_pm_complete(struct device *dev)
{
struct device_driver *drv = dev->driver;
if (drv && drv->pm && drv->pm->complete)
drv->pm->complete(dev);
}
#else /* !CONFIG_PM_SLEEP */
#define amba_pm_prepare NULL
#define amba_pm_complete NULL
#endif /* !CONFIG_PM_SLEEP */
#ifdef CONFIG_SUSPEND
static int amba_pm_suspend(struct device *dev)
{
struct device_driver *drv = dev->driver;
int ret = 0;
if (!drv)
return 0;
if (drv->pm) {
if (drv->pm->suspend)
ret = drv->pm->suspend(dev);
} else {
ret = amba_legacy_suspend(dev, PMSG_SUSPEND);
}
return ret;
}
static int amba_pm_suspend_noirq(struct device *dev)
{
struct device_driver *drv = dev->driver;
int ret = 0;
if (!drv)
return 0;
if (drv->pm) {
if (drv->pm->suspend_noirq)
ret = drv->pm->suspend_noirq(dev);
}
return ret;
}
static int amba_pm_resume(struct device *dev)
{
struct device_driver *drv = dev->driver;
int ret = 0;
if (!drv)
return 0;
if (drv->pm) {
if (drv->pm->resume)
ret = drv->pm->resume(dev);
} else {
ret = amba_legacy_resume(dev);
}
return ret;
}
static int amba_pm_resume_noirq(struct