diff options
Diffstat (limited to 'arch/avr32/mach-at32ap/extint.c')
| -rw-r--r-- | arch/avr32/mach-at32ap/extint.c | 253 |
1 files changed, 177 insertions, 76 deletions
diff --git a/arch/avr32/mach-at32ap/extint.c b/arch/avr32/mach-at32ap/extint.c index 7da9c5f7a0e..cfb298d6630 100644 --- a/arch/avr32/mach-at32ap/extint.c +++ b/arch/avr32/mach-at32ap/extint.c @@ -14,53 +14,92 @@ #include <linux/irq.h> #include <linux/platform_device.h> #include <linux/random.h> +#include <linux/slab.h> #include <asm/io.h> -#include <asm/arch/sm.h> +/* EIC register offsets */ +#define EIC_IER 0x0000 +#define EIC_IDR 0x0004 +#define EIC_IMR 0x0008 +#define EIC_ISR 0x000c +#define EIC_ICR 0x0010 +#define EIC_MODE 0x0014 +#define EIC_EDGE 0x0018 +#define EIC_LEVEL 0x001c +#define EIC_NMIC 0x0024 + +/* Bitfields in NMIC */ +#define EIC_NMIC_ENABLE (1 << 0) + +/* Bit manipulation macros */ +#define EIC_BIT(name) \ + (1 << EIC_##name##_OFFSET) +#define EIC_BF(name,value) \ + (((value) & ((1 << EIC_##name##_SIZE) - 1)) \ + << EIC_##name##_OFFSET) +#define EIC_BFEXT(name,value) \ + (((value) >> EIC_##name##_OFFSET) \ + & ((1 << EIC_##name##_SIZE) - 1)) +#define EIC_BFINS(name,value,old) \ + (((old) & ~(((1 << EIC_##name##_SIZE) - 1) \ + << EIC_##name##_OFFSET)) \ + | EIC_BF(name,value)) + +/* Register access macros */ +#define eic_readl(port,reg) \ + __raw_readl((port)->regs + EIC_##reg) +#define eic_writel(port,reg,value) \ + __raw_writel((value), (port)->regs + EIC_##reg) + +struct eic { + void __iomem *regs; + struct irq_chip *chip; + unsigned int first_irq; +}; -#include "sm.h" +static struct eic *nmi_eic; +static bool nmi_enabled; -static void eim_ack_irq(unsigned int irq) +static void eic_ack_irq(struct irq_data *d) { - struct at32_sm *sm = get_irq_chip_data(irq); - sm_writel(sm, EIM_ICR, 1 << (irq - sm->eim_first_irq)); + struct eic *eic = irq_data_get_irq_chip_data(d); + eic_writel(eic, ICR, 1 << (d->irq - eic->first_irq)); } -static void eim_mask_irq(unsigned int irq) +static void eic_mask_irq(struct irq_data *d) { - struct at32_sm *sm = get_irq_chip_data(irq); - sm_writel(sm, EIM_IDR, 1 << (irq - sm->eim_first_irq)); + struct eic *eic = irq_data_get_irq_chip_data(d); + eic_writel(eic, IDR, 1 << (d->irq - eic->first_irq)); } -static void eim_mask_ack_irq(unsigned int irq) +static void eic_mask_ack_irq(struct irq_data *d) { - struct at32_sm *sm = get_irq_chip_data(irq); - sm_writel(sm, EIM_ICR, 1 << (irq - sm->eim_first_irq)); - sm_writel(sm, EIM_IDR, 1 << (irq - sm->eim_first_irq)); + struct eic *eic = irq_data_get_irq_chip_data(d); + eic_writel(eic, ICR, 1 << (d->irq - eic->first_irq)); + eic_writel(eic, IDR, 1 << (d->irq - eic->first_irq)); } -static void eim_unmask_irq(unsigned int irq) +static void eic_unmask_irq(struct irq_data *d) { - struct at32_sm *sm = get_irq_chip_data(irq); - sm_writel(sm, EIM_IER, 1 << (irq - sm->eim_first_irq)); + struct eic *eic = irq_data_get_irq_chip_data(d); + eic_writel(eic, IER, 1 << (d->irq - eic->first_irq)); } -static int eim_set_irq_type(unsigned int irq, unsigned int flow_type) +static int eic_set_irq_type(struct irq_data *d, unsigned int flow_type) { - struct at32_sm *sm = get_irq_chip_data(irq); - unsigned int i = irq - sm->eim_first_irq; + struct eic *eic = irq_data_get_irq_chip_data(d); + unsigned int irq = d->irq; + unsigned int i = irq - eic->first_irq; u32 mode, edge, level; - unsigned long flags; - int ret = 0; flow_type &= IRQ_TYPE_SENSE_MASK; + if (flow_type == IRQ_TYPE_NONE) + flow_type = IRQ_TYPE_LEVEL_LOW; - spin_lock_irqsave(&sm->lock, flags); - - mode = sm_readl(sm, EIM_MODE); - edge = sm_readl(sm, EIM_EDGE); - level = sm_readl(sm, EIM_LEVEL); + mode = eic_readl(eic, MODE); + edge = eic_readl(eic, EDGE); + level = eic_readl(eic, LEVEL); switch (flow_type) { case IRQ_TYPE_LEVEL_LOW: @@ -80,92 +119,154 @@ static int eim_set_irq_type(unsigned int irq, unsigned int flow_type) edge &= ~(1 << i); break; default: - ret = -EINVAL; - break; + return -EINVAL; } - sm_writel(sm, EIM_MODE, mode); - sm_writel(sm, EIM_EDGE, edge); - sm_writel(sm, EIM_LEVEL, level); + eic_writel(eic, MODE, mode); + eic_writel(eic, EDGE, edge); + eic_writel(eic, LEVEL, level); - spin_unlock_irqrestore(&sm->lock, flags); + irqd_set_trigger_type(d, flow_type); + if (flow_type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) + __irq_set_handler_locked(irq, handle_level_irq); + else + __irq_set_handler_locked(irq, handle_edge_irq); - return ret; + return IRQ_SET_MASK_OK_NOCOPY; } -struct irq_chip eim_chip = { - .name = "eim", - .ack = eim_ack_irq, - .mask = eim_mask_irq, - .mask_ack = eim_mask_ack_irq, - .unmask = eim_unmask_irq, - .set_type = eim_set_irq_type, +static struct irq_chip eic_chip = { + .name = "eic", + .irq_ack = eic_ack_irq, + .irq_mask = eic_mask_irq, + .irq_mask_ack = eic_mask_ack_irq, + .irq_unmask = eic_unmask_irq, + .irq_set_type = eic_set_irq_type, }; -static void demux_eim_irq(unsigned int irq, struct irq_desc *desc, - struct pt_regs *regs) +static void demux_eic_irq(unsigned int irq, struct irq_desc *desc) { - struct at32_sm *sm = desc->handler_data; - struct irq_desc *ext_desc; + struct eic *eic = irq_desc_get_handler_data(desc); unsigned long status, pending; - unsigned int i, ext_irq; - - spin_lock(&sm->lock); + unsigned int i; - status = sm_readl(sm, EIM_ISR); - pending = status & sm_readl(sm, EIM_IMR); + status = eic_readl(eic, ISR); + pending = status & eic_readl(eic, IMR); while (pending) { i = fls(pending) - 1; pending &= ~(1 << i); - ext_irq = i + sm->eim_first_irq; - ext_desc = irq_desc + ext_irq; - ext_desc->handle_irq(ext_irq, ext_desc, regs); + generic_handle_irq(i + eic->first_irq); } +} + +int nmi_enable(void) +{ + nmi_enabled = true; + + if (nmi_eic) + eic_writel(nmi_eic, NMIC, EIC_NMIC_ENABLE); + + return 0; +} + +void nmi_disable(void) +{ + if (nmi_eic) + eic_writel(nmi_eic, NMIC, 0); - spin_unlock(&sm->lock); + nmi_enabled = false; } -static int __init eim_init(void) +static int __init eic_probe(struct platform_device *pdev) { - struct at32_sm *sm = &system_manager; + struct eic *eic; + struct resource *regs; unsigned int i; - unsigned int nr_irqs; + unsigned int nr_of_irqs; unsigned int int_irq; + int ret; u32 pattern; - /* - * The EIM is really the same module as SM, so register - * mapping, etc. has been taken care of already. - */ + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + int_irq = platform_get_irq(pdev, 0); + if (!regs || (int)int_irq <= 0) { + dev_dbg(&pdev->dev, "missing regs and/or irq resource\n"); + return -ENXIO; + } + + ret = -ENOMEM; + eic = kzalloc(sizeof(struct eic), GFP_KERNEL); + if (!eic) { + dev_dbg(&pdev->dev, "no memory for eic structure\n"); + goto err_kzalloc; + } + + eic->first_irq = EIM_IRQ_BASE + 32 * pdev->id; + eic->regs = ioremap(regs->start, resource_size(regs)); + if (!eic->regs) { + dev_dbg(&pdev->dev, "failed to map regs\n"); + goto err_ioremap; + } /* * Find out how many interrupt lines that are actually * implemented in hardware. */ - sm_writel(sm, EIM_IDR, ~0UL); - sm_writel(sm, EIM_MODE, ~0UL); - pattern = sm_readl(sm, EIM_MODE); - nr_irqs = fls(pattern); + eic_writel(eic, IDR, ~0UL); + eic_writel(eic, MODE, ~0UL); + pattern = eic_readl(eic, MODE); + nr_of_irqs = fls(pattern); - sm->eim_chip = &eim_chip; + /* Trigger on low level unless overridden by driver */ + eic_writel(eic, EDGE, 0UL); + eic_writel(eic, LEVEL, 0UL); - for (i = 0; i < nr_irqs; i++) { - set_irq_chip(sm->eim_first_irq + i, &eim_chip); - set_irq_chip_data(sm->eim_first_irq + i, sm); - } + eic->chip = &eic_chip; - int_irq = platform_get_irq_byname(sm->pdev, "eim"); + for (i = 0; i < nr_of_irqs; i++) { + irq_set_chip_and_handler(eic->first_irq + i, &eic_chip, + handle_level_irq); + irq_set_chip_data(eic->first_irq + i, eic); + } - set_irq_chained_handler(int_irq, demux_eim_irq); - set_irq_data(int_irq, sm); + irq_set_chained_handler(int_irq, demux_eic_irq); + irq_set_handler_data(int_irq, eic); + + if (pdev->id == 0) { + nmi_eic = eic; + if (nmi_enabled) + /* + * Someone tried to enable NMI before we were + * ready. Do it now. + */ + nmi_enable(); + } - printk("EIM: External Interrupt Module at 0x%p, IRQ %u\n", - sm->regs, int_irq); - printk("EIM: Handling %u external IRQs, starting with IRQ %u\n", - nr_irqs, sm->eim_first_irq); + dev_info(&pdev->dev, + "External Interrupt Controller at 0x%p, IRQ %u\n", + eic->regs, int_irq); + dev_info(&pdev->dev, + "Handling %u external IRQs, starting with IRQ %u\n", + nr_of_irqs, eic->first_irq); return 0; + +err_ioremap: + kfree(eic); +err_kzalloc: + return ret; +} + +static struct platform_driver eic_driver = { + .driver = { + .name = "at32_eic", + }, +}; + +static int __init eic_init(void) +{ + return platform_driver_probe(&eic_driver, eic_probe); } -arch_initcall(eim_init); +arch_initcall(eic_init); |
