diff options
Diffstat (limited to 'kernel/arch/avr32/mach-at32ap/extint.c')
-rw-r--r-- | kernel/arch/avr32/mach-at32ap/extint.c | 272 |
1 files changed, 272 insertions, 0 deletions
diff --git a/kernel/arch/avr32/mach-at32ap/extint.c b/kernel/arch/avr32/mach-at32ap/extint.c new file mode 100644 index 000000000..cfb298d66 --- /dev/null +++ b/kernel/arch/avr32/mach-at32ap/extint.c @@ -0,0 +1,272 @@ +/* + * External interrupt handling for AT32AP CPUs + * + * Copyright (C) 2006 Atmel Corporation + * + * 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/errno.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/platform_device.h> +#include <linux/random.h> +#include <linux/slab.h> + +#include <asm/io.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; +}; + +static struct eic *nmi_eic; +static bool nmi_enabled; + +static void eic_ack_irq(struct irq_data *d) +{ + struct eic *eic = irq_data_get_irq_chip_data(d); + eic_writel(eic, ICR, 1 << (d->irq - eic->first_irq)); +} + +static void eic_mask_irq(struct irq_data *d) +{ + struct eic *eic = irq_data_get_irq_chip_data(d); + eic_writel(eic, IDR, 1 << (d->irq - eic->first_irq)); +} + +static void eic_mask_ack_irq(struct irq_data *d) +{ + 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 eic_unmask_irq(struct irq_data *d) +{ + struct eic *eic = irq_data_get_irq_chip_data(d); + eic_writel(eic, IER, 1 << (d->irq - eic->first_irq)); +} + +static int eic_set_irq_type(struct irq_data *d, unsigned int flow_type) +{ + 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; + + flow_type &= IRQ_TYPE_SENSE_MASK; + if (flow_type == IRQ_TYPE_NONE) + flow_type = IRQ_TYPE_LEVEL_LOW; + + mode = eic_readl(eic, MODE); + edge = eic_readl(eic, EDGE); + level = eic_readl(eic, LEVEL); + + switch (flow_type) { + case IRQ_TYPE_LEVEL_LOW: + mode |= 1 << i; + level &= ~(1 << i); + break; + case IRQ_TYPE_LEVEL_HIGH: + mode |= 1 << i; + level |= 1 << i; + break; + case IRQ_TYPE_EDGE_RISING: + mode &= ~(1 << i); + edge |= 1 << i; + break; + case IRQ_TYPE_EDGE_FALLING: + mode &= ~(1 << i); + edge &= ~(1 << i); + break; + default: + return -EINVAL; + } + + eic_writel(eic, MODE, mode); + eic_writel(eic, EDGE, edge); + eic_writel(eic, LEVEL, level); + + 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 IRQ_SET_MASK_OK_NOCOPY; +} + +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_eic_irq(unsigned int irq, struct irq_desc *desc) +{ + struct eic *eic = irq_desc_get_handler_data(desc); + unsigned long status, pending; + unsigned int i; + + status = eic_readl(eic, ISR); + pending = status & eic_readl(eic, IMR); + + while (pending) { + i = fls(pending) - 1; + pending &= ~(1 << i); + + 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); + + nmi_enabled = false; +} + +static int __init eic_probe(struct platform_device *pdev) +{ + struct eic *eic; + struct resource *regs; + unsigned int i; + unsigned int nr_of_irqs; + unsigned int int_irq; + int ret; + u32 pattern; + + 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. + */ + eic_writel(eic, IDR, ~0UL); + eic_writel(eic, MODE, ~0UL); + pattern = eic_readl(eic, MODE); + nr_of_irqs = fls(pattern); + + /* Trigger on low level unless overridden by driver */ + eic_writel(eic, EDGE, 0UL); + eic_writel(eic, LEVEL, 0UL); + + eic->chip = &eic_chip; + + 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); + } + + 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(); + } + + 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(eic_init); |