| /* |
| * 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(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_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_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_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++) { |
| 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_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); |