summaryrefslogtreecommitdiff
path: root/arch/avr32/mach-at32ap/extint.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/avr32/mach-at32ap/extint.c')
-rw-r--r--arch/avr32/mach-at32ap/extint.c272
1 files changed, 272 insertions, 0 deletions
diff --git a/arch/avr32/mach-at32ap/extint.c b/arch/avr32/mach-at32ap/extint.c
new file mode 100644
index 000000000..cfb298d66
--- /dev/null
+++ b/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);