/* * 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 <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(unsigned int irq) { struct eic *eic = get_irq_chip_data(irq); eic_writel(eic, ICR, 1 << (irq - eic->first_irq)); } static void eic_mask_irq(unsigned int irq) { struct eic *eic = get_irq_chip_data(irq); eic_writel(eic, IDR, 1 << (irq - eic->first_irq)); } static void eic_mask_ack_irq(unsigned int irq) { struct eic *eic = get_irq_chip_data(irq); eic_writel(eic, ICR, 1 << (irq - eic->first_irq)); eic_writel(eic, IDR, 1 << (irq - eic->first_irq)); } static void eic_unmask_irq(unsigned int irq) { struct eic *eic = get_irq_chip_data(irq); eic_writel(eic, IER, 1 << (irq - eic->first_irq)); } static int eic_set_irq_type(unsigned int irq, unsigned int flow_type) { struct eic *eic = get_irq_chip_data(irq); struct irq_desc *desc; unsigned int i = irq - eic->first_irq; u32 mode, edge, level; int ret = 0; flow_type &= IRQ_TYPE_SENSE_MASK; if (flow_type == IRQ_TYPE_NONE) flow_type = IRQ_TYPE_LEVEL_LOW; desc = &irq_desc[irq]; 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: ret = -EINVAL; break; } if (ret == 0) { eic_writel(eic, MODE, mode); eic_writel(eic, EDGE, edge); eic_writel(eic, LEVEL, level); if (flow_type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) { flow_type |= IRQ_LEVEL; __set_irq_handler_unlocked(irq, handle_level_irq); } else __set_irq_handler_unlocked(irq, handle_edge_irq); desc->status &= ~(IRQ_TYPE_SENSE_MASK | IRQ_LEVEL); desc->status |= flow_type; } return ret; } static struct irq_chip eic_chip = { .name = "eic", .ack = eic_ack_irq, .mask = eic_mask_irq, .mask_ack = eic_mask_ack_irq, .unmask = eic_unmask_irq, .set_type = eic_set_irq_type, }; static void demux_eic_irq(unsigned int irq, struct irq_desc *desc) { struct eic *eic = desc->handler_data; 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_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_irq) { 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, regs->end - regs->start + 1); 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_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_irqs; i++) { set_irq_chip_and_handler(eic->first_irq + i, &eic_chip, handle_level_irq); set_irq_chip_data(eic->first_irq + i, eic); } set_irq_chained_handler(int_irq, demux_eic_irq); set_irq_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_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);