mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-11 12:28:41 +08:00
mfd: Add IRQ domain support for the AB8500
As the AB8500 is an IRQ controller in its own right, here we provide the AB8500 driver with IRQ domain support. This is required if we wish to reference any of its IRQs from a platform's Device Tree. Cc: Naga Radheshy <naga.radheshy@stericsson.com> Cc: Mattias Wallin <mattias.wallin@stericsson.com> Cc: Daniel Willerud <daniel.willerud@stericsson.com> Signed-off-by: Lee Jones <lee.jones@linaro.org> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
This commit is contained in:
parent
3c14476204
commit
06e589efa5
@ -709,6 +709,7 @@ config AB8500_CORE
|
|||||||
bool "ST-Ericsson AB8500 Mixed Signal Power Management chip"
|
bool "ST-Ericsson AB8500 Mixed Signal Power Management chip"
|
||||||
depends on GENERIC_HARDIRQS && ABX500_CORE && MFD_DB8500_PRCMU
|
depends on GENERIC_HARDIRQS && ABX500_CORE && MFD_DB8500_PRCMU
|
||||||
select MFD_CORE
|
select MFD_CORE
|
||||||
|
select IRQ_DOMAIN
|
||||||
help
|
help
|
||||||
Select this option to enable access to AB8500 power management
|
Select this option to enable access to AB8500 power management
|
||||||
chip. This connects to U8500 either on the SSP/SPI bus (deprecated
|
chip. This connects to U8500 either on the SSP/SPI bus (deprecated
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/irq.h>
|
#include <linux/irq.h>
|
||||||
|
#include <linux/irqdomain.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
@ -361,7 +362,7 @@ static void ab8500_irq_sync_unlock(struct irq_data *data)
|
|||||||
static void ab8500_irq_mask(struct irq_data *data)
|
static void ab8500_irq_mask(struct irq_data *data)
|
||||||
{
|
{
|
||||||
struct ab8500 *ab8500 = irq_data_get_irq_chip_data(data);
|
struct ab8500 *ab8500 = irq_data_get_irq_chip_data(data);
|
||||||
int offset = data->irq - ab8500->irq_base;
|
int offset = data->hwirq;
|
||||||
int index = offset / 8;
|
int index = offset / 8;
|
||||||
int mask = 1 << (offset % 8);
|
int mask = 1 << (offset % 8);
|
||||||
|
|
||||||
@ -371,7 +372,7 @@ static void ab8500_irq_mask(struct irq_data *data)
|
|||||||
static void ab8500_irq_unmask(struct irq_data *data)
|
static void ab8500_irq_unmask(struct irq_data *data)
|
||||||
{
|
{
|
||||||
struct ab8500 *ab8500 = irq_data_get_irq_chip_data(data);
|
struct ab8500 *ab8500 = irq_data_get_irq_chip_data(data);
|
||||||
int offset = data->irq - ab8500->irq_base;
|
int offset = data->hwirq;
|
||||||
int index = offset / 8;
|
int index = offset / 8;
|
||||||
int mask = 1 << (offset % 8);
|
int mask = 1 << (offset % 8);
|
||||||
|
|
||||||
@ -510,38 +511,51 @@ static irqreturn_t ab8500_irq(int irq, void *dev)
|
|||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ab8500_irq_init(struct ab8500 *ab8500)
|
/**
|
||||||
|
* ab8500_irq_get_virq(): Map an interrupt on a chip to a virtual IRQ
|
||||||
|
*
|
||||||
|
* @ab8500: ab8500_irq controller to operate on.
|
||||||
|
* @irq: index of the interrupt requested in the chip IRQs
|
||||||
|
*
|
||||||
|
* Useful for drivers to request their own IRQs.
|
||||||
|
*/
|
||||||
|
int ab8500_irq_get_virq(struct ab8500 *ab8500, int irq)
|
||||||
{
|
{
|
||||||
int base = ab8500->irq_base;
|
if (!ab8500)
|
||||||
int irq;
|
return -EINVAL;
|
||||||
int num_irqs;
|
|
||||||
|
|
||||||
if (is_ab9540(ab8500))
|
return irq_create_mapping(ab8500->domain, irq);
|
||||||
num_irqs = AB9540_NR_IRQS;
|
}
|
||||||
else if (is_ab8505(ab8500))
|
EXPORT_SYMBOL_GPL(ab8500_irq_get_virq);
|
||||||
num_irqs = AB8505_NR_IRQS;
|
|
||||||
else
|
|
||||||
num_irqs = AB8500_NR_IRQS;
|
|
||||||
|
|
||||||
for (irq = base; irq < base + num_irqs; irq++) {
|
static int ab8500_irq_map(struct irq_domain *d, unsigned int virq,
|
||||||
irq_set_chip_data(irq, ab8500);
|
irq_hw_number_t hwirq)
|
||||||
irq_set_chip_and_handler(irq, &ab8500_irq_chip,
|
{
|
||||||
handle_simple_irq);
|
struct ab8500 *ab8500 = d->host_data;
|
||||||
irq_set_nested_thread(irq, 1);
|
|
||||||
|
if (!ab8500)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
irq_set_chip_data(virq, ab8500);
|
||||||
|
irq_set_chip_and_handler(virq, &ab8500_irq_chip,
|
||||||
|
handle_simple_irq);
|
||||||
|
irq_set_nested_thread(virq, 1);
|
||||||
#ifdef CONFIG_ARM
|
#ifdef CONFIG_ARM
|
||||||
set_irq_flags(irq, IRQF_VALID);
|
set_irq_flags(virq, IRQF_VALID);
|
||||||
#else
|
#else
|
||||||
irq_set_noprobe(irq);
|
irq_set_noprobe(virq);
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ab8500_irq_remove(struct ab8500 *ab8500)
|
static struct irq_domain_ops ab8500_irq_ops = {
|
||||||
|
.map = ab8500_irq_map,
|
||||||
|
.xlate = irq_domain_xlate_twocell,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int ab8500_irq_init(struct ab8500 *ab8500, struct device_node *np)
|
||||||
{
|
{
|
||||||
int base = ab8500->irq_base;
|
|
||||||
int irq;
|
|
||||||
int num_irqs;
|
int num_irqs;
|
||||||
|
|
||||||
if (is_ab9540(ab8500))
|
if (is_ab9540(ab8500))
|
||||||
@ -551,13 +565,22 @@ static void ab8500_irq_remove(struct ab8500 *ab8500)
|
|||||||
else
|
else
|
||||||
num_irqs = AB8500_NR_IRQS;
|
num_irqs = AB8500_NR_IRQS;
|
||||||
|
|
||||||
for (irq = base; irq < base + num_irqs; irq++) {
|
if (ab8500->irq_base) {
|
||||||
#ifdef CONFIG_ARM
|
ab8500->domain = irq_domain_add_legacy(
|
||||||
set_irq_flags(irq, 0);
|
NULL, num_irqs, ab8500->irq_base,
|
||||||
#endif
|
0, &ab8500_irq_ops, ab8500);
|
||||||
irq_set_chip_and_handler(irq, NULL, NULL);
|
|
||||||
irq_set_chip_data(irq, NULL);
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
ab8500->domain = irq_domain_add_linear(
|
||||||
|
np, num_irqs, &ab8500_irq_ops, ab8500);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ab8500->domain) {
|
||||||
|
dev_err(ab8500->dev, "Failed to create irqdomain\n");
|
||||||
|
return -ENOSYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ab8500_suspend(struct ab8500 *ab8500)
|
int ab8500_suspend(struct ab8500 *ab8500)
|
||||||
@ -1233,14 +1256,6 @@ static int __devinit ab8500_probe(struct platform_device *pdev)
|
|||||||
|
|
||||||
if (plat)
|
if (plat)
|
||||||
ab8500->irq_base = plat->irq_base;
|
ab8500->irq_base = plat->irq_base;
|
||||||
else if (np)
|
|
||||||
ret = of_property_read_u32(np, "stericsson,irq-base", &ab8500->irq_base);
|
|
||||||
|
|
||||||
if (!ab8500->irq_base) {
|
|
||||||
dev_info(&pdev->dev, "couldn't find irq-base\n");
|
|
||||||
ret = -EINVAL;
|
|
||||||
goto out_free_ab8500;
|
|
||||||
}
|
|
||||||
|
|
||||||
ab8500->dev = &pdev->dev;
|
ab8500->dev = &pdev->dev;
|
||||||
|
|
||||||
@ -1323,7 +1338,7 @@ static int __devinit ab8500_probe(struct platform_device *pdev)
|
|||||||
AB8500_SWITCH_OFF_STATUS, &value);
|
AB8500_SWITCH_OFF_STATUS, &value);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
dev_info(ab8500->dev, "switch off status: %#x", value);
|
dev_info(ab8500->dev, "switch off status: %#x\n", value);
|
||||||
|
|
||||||
if (plat && plat->init)
|
if (plat && plat->init)
|
||||||
plat->init(ab8500);
|
plat->init(ab8500);
|
||||||
@ -1352,25 +1367,25 @@ static int __devinit ab8500_probe(struct platform_device *pdev)
|
|||||||
for (i = 0; i < ab8500->mask_size; i++)
|
for (i = 0; i < ab8500->mask_size; i++)
|
||||||
ab8500->mask[i] = ab8500->oldmask[i] = 0xff;
|
ab8500->mask[i] = ab8500->oldmask[i] = 0xff;
|
||||||
|
|
||||||
if (ab8500->irq_base) {
|
ret = ab8500_irq_init(ab8500, np);
|
||||||
ret = ab8500_irq_init(ab8500);
|
if (ret)
|
||||||
if (ret)
|
goto out_freeoldmask;
|
||||||
goto out_freeoldmask;
|
|
||||||
|
|
||||||
/* Activate this feature only in ab9540 */
|
/* Activate this feature only in ab9540 */
|
||||||
/* till tests are done on ab8500 1p2 or later*/
|
/* till tests are done on ab8500 1p2 or later*/
|
||||||
if (is_ab9540(ab8500))
|
if (is_ab9540(ab8500)) {
|
||||||
ret = request_threaded_irq(ab8500->irq, NULL,
|
ret = request_threaded_irq(ab8500->irq, NULL,
|
||||||
ab8500_hierarchical_irq,
|
ab8500_hierarchical_irq,
|
||||||
IRQF_ONESHOT | IRQF_NO_SUSPEND,
|
IRQF_ONESHOT | IRQF_NO_SUSPEND,
|
||||||
"ab8500", ab8500);
|
"ab8500", ab8500);
|
||||||
else
|
}
|
||||||
ret = request_threaded_irq(ab8500->irq, NULL,
|
else {
|
||||||
|
ret = request_threaded_irq(ab8500->irq, NULL,
|
||||||
ab8500_irq,
|
ab8500_irq,
|
||||||
IRQF_ONESHOT | IRQF_NO_SUSPEND,
|
IRQF_ONESHOT | IRQF_NO_SUSPEND,
|
||||||
"ab8500", ab8500);
|
"ab8500", ab8500);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto out_removeirq;
|
goto out_freeoldmask;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!np) {
|
if (!np) {
|
||||||
@ -1417,15 +1432,11 @@ static int __devinit ab8500_probe(struct platform_device *pdev)
|
|||||||
&ab8500_attr_group);
|
&ab8500_attr_group);
|
||||||
if (ret)
|
if (ret)
|
||||||
dev_err(ab8500->dev, "error creating sysfs entries\n");
|
dev_err(ab8500->dev, "error creating sysfs entries\n");
|
||||||
else
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
out_freeirq:
|
out_freeirq:
|
||||||
if (ab8500->irq_base)
|
free_irq(ab8500->irq, ab8500);
|
||||||
free_irq(ab8500->irq, ab8500);
|
|
||||||
out_removeirq:
|
|
||||||
if (ab8500->irq_base)
|
|
||||||
ab8500_irq_remove(ab8500);
|
|
||||||
out_freeoldmask:
|
out_freeoldmask:
|
||||||
kfree(ab8500->oldmask);
|
kfree(ab8500->oldmask);
|
||||||
out_freemask:
|
out_freemask:
|
||||||
@ -1444,11 +1455,10 @@ static int __devexit ab8500_remove(struct platform_device *pdev)
|
|||||||
sysfs_remove_group(&ab8500->dev->kobj, &ab9540_attr_group);
|
sysfs_remove_group(&ab8500->dev->kobj, &ab9540_attr_group);
|
||||||
else
|
else
|
||||||
sysfs_remove_group(&ab8500->dev->kobj, &ab8500_attr_group);
|
sysfs_remove_group(&ab8500->dev->kobj, &ab8500_attr_group);
|
||||||
|
|
||||||
mfd_remove_devices(ab8500->dev);
|
mfd_remove_devices(ab8500->dev);
|
||||||
if (ab8500->irq_base) {
|
free_irq(ab8500->irq, ab8500);
|
||||||
free_irq(ab8500->irq, ab8500);
|
|
||||||
ab8500_irq_remove(ab8500);
|
|
||||||
}
|
|
||||||
kfree(ab8500->oldmask);
|
kfree(ab8500->oldmask);
|
||||||
kfree(ab8500->mask);
|
kfree(ab8500->mask);
|
||||||
kfree(ab8500);
|
kfree(ab8500);
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include <linux/atomic.h>
|
#include <linux/atomic.h>
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/irqdomain.h>
|
||||||
|
|
||||||
struct device;
|
struct device;
|
||||||
|
|
||||||
@ -227,6 +228,7 @@ enum ab8500_version {
|
|||||||
* @irq_lock: genirq bus lock
|
* @irq_lock: genirq bus lock
|
||||||
* @transfer_ongoing: 0 if no transfer ongoing
|
* @transfer_ongoing: 0 if no transfer ongoing
|
||||||
* @irq: irq line
|
* @irq: irq line
|
||||||
|
* @irq_domain: irq domain
|
||||||
* @version: chip version id (e.g. ab8500 or ab9540)
|
* @version: chip version id (e.g. ab8500 or ab9540)
|
||||||
* @chip_id: chip revision id
|
* @chip_id: chip revision id
|
||||||
* @write: register write
|
* @write: register write
|
||||||
@ -247,6 +249,7 @@ struct ab8500 {
|
|||||||
atomic_t transfer_ongoing;
|
atomic_t transfer_ongoing;
|
||||||
int irq_base;
|
int irq_base;
|
||||||
int irq;
|
int irq;
|
||||||
|
struct irq_domain *domain;
|
||||||
enum ab8500_version version;
|
enum ab8500_version version;
|
||||||
u8 chip_id;
|
u8 chip_id;
|
||||||
|
|
||||||
@ -336,4 +339,6 @@ static inline int is_ab8500_2p0(struct ab8500 *ab)
|
|||||||
return (is_ab8500(ab) && (ab->chip_id == AB8500_CUT2P0));
|
return (is_ab8500(ab) && (ab->chip_id == AB8500_CUT2P0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ab8500_irq_get_virq(struct ab8500 *ab8500, int irq);
|
||||||
|
|
||||||
#endif /* MFD_AB8500_H */
|
#endif /* MFD_AB8500_H */
|
||||||
|
Loading…
Reference in New Issue
Block a user