'irq-aic' is consolidated interrupt driver for Atmel SoCs. Rename the driver file: irq-atmel-aic-common.c -> irq-aic.c
Kconfig: ATMEL_AIC5_IRQ is removed Cc: Thomas Gleixner <t...@linutronix.de> Cc: Jason Cooper <ja...@lakedaemon.net> Cc: Marc Zyngier <marc.zyng...@arm.com> Cc: Alexandre Belloni <alexandre.bell...@free-electrons.com> Cc: Boris BREZILLON <boris.brezil...@free-electrons.com> Cc: Ludovic Desroches <ludovic.desroc...@atmel.com> Cc: Nicolas Ferre <nicolas.fe...@atmel.com> Cc: linux-kernel@vger.kernel.org Signed-off-by: Milo Kim <milo....@ti.com> --- arch/arm/mach-at91/Kconfig | 2 +- drivers/irqchip/Kconfig | 7 - drivers/irqchip/Makefile | 3 +- drivers/irqchip/irq-aic.c | 609 +++++++++++++++++++++++++++++++++ drivers/irqchip/irq-atmel-aic-common.c | 609 --------------------------------- 5 files changed, 611 insertions(+), 619 deletions(-) create mode 100644 drivers/irqchip/irq-aic.c delete mode 100644 drivers/irqchip/irq-atmel-aic-common.c diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig index 23be2e4..315195c 100644 --- a/arch/arm/mach-at91/Kconfig +++ b/arch/arm/mach-at91/Kconfig @@ -122,7 +122,7 @@ config SOC_SAM_V7 config SOC_SAMA5 bool - select ATMEL_AIC5_IRQ + select ATMEL_AIC_IRQ select ATMEL_SDRAMC select MEMORY select SOC_SAM_V7 diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index b5f5133..b926f79 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -67,13 +67,6 @@ config ATMEL_AIC_IRQ select MULTI_IRQ_HANDLER select SPARSE_IRQ -config ATMEL_AIC5_IRQ - bool - select GENERIC_IRQ_CHIP - select IRQ_DOMAIN - select MULTI_IRQ_HANDLER - select SPARSE_IRQ - config I8259 bool select IRQ_DOMAIN diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 6e43333..8d6494f 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -28,8 +28,7 @@ obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v3-its-pci-msi.o irq-g obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o obj-$(CONFIG_ARM_NVIC) += irq-nvic.o obj-$(CONFIG_ARM_VIC) += irq-vic.o -obj-$(CONFIG_ATMEL_AIC_IRQ) += irq-atmel-aic-common.o -obj-$(CONFIG_ATMEL_AIC5_IRQ) += irq-atmel-aic-common.o +obj-$(CONFIG_ATMEL_AIC_IRQ) += irq-aic.o obj-$(CONFIG_I8259) += irq-i8259.o obj-$(CONFIG_IMGPDC_IRQ) += irq-imgpdc.o obj-$(CONFIG_IRQ_MIPS_CPU) += irq-mips-cpu.o diff --git a/drivers/irqchip/irq-aic.c b/drivers/irqchip/irq-aic.c new file mode 100644 index 0000000..1d3978e --- /dev/null +++ b/drivers/irqchip/irq-aic.c @@ -0,0 +1,609 @@ +/* + * Atmel AIC (Advanced Interrupt Controller) Driver + * + * Copyright (C) 2004 SAN People + * Copyright (C) 2004 ATMEL + * Copyright (C) Rick Bronson + * Copyright (C) 2014 Free Electrons + * + * Author: Boris BREZILLON <boris.brezil...@free-electrons.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/irqchip.h> +#include <linux/irqdesc.h> +#include <linux/irqdomain.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/slab.h> + +#include <asm/exception.h> +#include <asm/mach/irq.h> + +#define AIC_IRQS_PER_CHIP 32 +#define NR_AT91RM9200_IRQS 32 +#define NR_SAMA5D2_IRQS 77 +#define NR_SAMA5D3_IRQS 48 +#define NR_SAMA5D4_IRQS 68 + +#define AT91_AIC_SMR_BASE 0 +#define AT91_AIC_SVR_BASE 0x80 +#define AT91_AIC_IVR 0x100 +#define AT91_AIC_ISR 0x108 +#define AT91_AIC_IECR 0x120 +#define AT91_AIC_IDCR 0x124 +#define AT91_AIC_ICCR 0x128 +#define AT91_AIC_ISCR 0x12c +#define AT91_AIC_EOICR 0x130 +#define AT91_AIC_SPU 0x134 +#define AT91_AIC_DCR 0x138 +#define AT91_INVALID_OFFSET (-1) + +#define AT91_AIC5_SSR 0x0 +#define AT91_AIC5_SMR 0x4 +#define AT91_AIC5_SVR 0x8 +#define AT91_AIC5_IVR 0x10 +#define AT91_AIC5_ISR 0x18 +#define AT91_AIC5_EOICR 0x38 +#define AT91_AIC5_SPU 0x3c +#define AT91_AIC5_IECR 0x40 +#define AT91_AIC5_IDCR 0x44 +#define AT91_AIC5_ICCR 0x48 +#define AT91_AIC5_ISCR 0x4c +#define AT91_AIC5_DCR 0x6c + +#define AT91_AIC_PRIOR GENMASK(2, 0) +#define AT91_AIC_IRQ_MIN_PRIORITY 0 +#define AT91_AIC_IRQ_MAX_PRIORITY 7 + +#define AT91_AIC_SRCTYPE GENMASK(6, 5) +#define AT91_AIC_SRCTYPE_LOW (0 << 5) +#define AT91_AIC_SRCTYPE_FALLING (1 << 5) +#define AT91_AIC_SRCTYPE_HIGH (2 << 5) +#define AT91_AIC_SRCTYPE_RISING (3 << 5) + +struct aic_chip_data { + u32 ext_irqs; +}; + +/** + * struct aic_reg_offset + * + * @eoi: End of interrupt command register + * @smr: Source mode register + * @ssr: Source select register + * @iscr: Interrupt set command register + * @idcr: Interrupt disable command register + * @iccr: Interrupt clear command register + * @iecr: Interrupt enable command register + * @spu: Spurious interrupt vector register + * @dcr: Debug control register + * @svr: Source vector register + * @ivr: Interrupt vector register + * @isr: Interrupt status register + * + * Each value means register offset. + */ +struct aic_reg_offset { + int eoi; + int smr; + int ssr; + int iscr; + int idcr; + int iccr; + int iecr; + int spu; + int dcr; + int svr; + int ivr; + int isr; +}; + +static const struct aic_reg_offset aic_regs = { + .eoi = AT91_AIC_EOICR, + .smr = AT91_AIC_SMR_BASE, + .ssr = AT91_INVALID_OFFSET, /* No SSR exists */ + .iscr = AT91_AIC_ISCR, + .idcr = AT91_AIC_IDCR, + .iccr = AT91_AIC_ICCR, + .iecr = AT91_AIC_IECR, + .spu = AT91_AIC_SPU, + .dcr = AT91_AIC_DCR, + .svr = AT91_AIC_SVR_BASE, + .ivr = AT91_AIC_IVR, + .isr = AT91_AIC_ISR, +}; + +static const struct aic_reg_offset aic5_regs = { + .eoi = AT91_AIC5_EOICR, + .smr = AT91_AIC5_SMR, + .ssr = AT91_AIC5_SSR, + .iscr = AT91_AIC5_ISCR, + .idcr = AT91_AIC5_IDCR, + .iccr = AT91_AIC5_ICCR, + .iecr = AT91_AIC5_IECR, + .spu = AT91_AIC5_SPU, + .dcr = AT91_AIC5_DCR, + .svr = AT91_AIC5_SVR, + .ivr = AT91_AIC5_IVR, + .isr = AT91_AIC5_ISR, +}; + +static struct irq_domain *aic_domain; +static const struct aic_reg_offset *aic_reg_data; + +static asmlinkage void __exception_irq_entry +aic_handle(struct pt_regs *regs) +{ + struct irq_chip_generic *gc = irq_get_domain_generic_chip(aic_domain, + 0); + u32 hwirq = irq_reg_readl(gc, aic_reg_data->ivr); + u32 status = irq_reg_readl(gc, aic_reg_data->isr); + + if (!status) + irq_reg_writel(gc, 0, aic_reg_data->eoi); + else + handle_domain_irq(aic_domain, hwirq, regs); +} + +static inline bool aic_is_ssr_used(void) +{ + return aic_reg_data->ssr != AT91_INVALID_OFFSET; +} + +static void aic_update_smr(struct irq_chip_generic *gc, int hwirq, + u32 mask, u32 val) +{ + int reg = aic_reg_data->smr; + u32 tmp = 0; + + if (aic_is_ssr_used()) + irq_reg_writel(gc, hwirq, aic_reg_data->ssr); + else + reg += hwirq * 4; + + tmp = irq_reg_readl(gc, reg); + tmp &= mask; + tmp |= val; + + irq_reg_writel(gc, tmp, reg); +} + +static int aic_irq_domain_xlate(struct irq_domain *d, struct device_node *node, + const u32 *intspec, unsigned int intsize, + irq_hw_number_t *out_hwirq, + unsigned int *out_type) +{ + struct irq_chip_generic *gc = irq_get_domain_generic_chip(d, 0); + bool condition = (intsize < 3) || + (intspec[2] < AT91_AIC_IRQ_MIN_PRIORITY) || + (intspec[2] > AT91_AIC_IRQ_MAX_PRIORITY); + + if (!gc || WARN_ON(condition)) + return -EINVAL; + + /* + * intspec[0]: HW IRQ number + * intspec[1]: IRQ flag + * intspec[2]: IRQ priority + */ + + *out_hwirq = intspec[0]; + *out_type = intspec[1] & IRQ_TYPE_SENSE_MASK; + + irq_gc_lock(gc); + aic_update_smr(gc, *out_hwirq, ~AT91_AIC_PRIOR, intspec[2]); + irq_gc_unlock(gc); + + return 0; +} + +static const struct irq_domain_ops aic_irq_ops = { + .map = irq_map_generic_chip, + .xlate = aic_irq_domain_xlate, +}; + +static void aic_irq_shutdown(struct irq_data *d) +{ + struct irq_chip_type *ct = irq_data_get_chip_type(d); + + ct->chip.irq_mask(d); +} + +static void aic_mask(struct irq_data *d) +{ + struct irq_domain *domain = d->domain; + struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0); + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + u32 mask = d->mask; + + /* + * Disable interrupt. We always take the lock of the + * first irq chip as all chips share the same registers. + */ + irq_gc_lock(bgc); + + if (aic_is_ssr_used()) { + irq_reg_writel(gc, d->hwirq, aic_reg_data->ssr); + irq_reg_writel(gc, 1, aic_reg_data->idcr); + } else { + irq_reg_writel(gc, mask, aic_reg_data->idcr); + } + + gc->mask_cache &= ~mask; + + irq_gc_unlock(bgc); +} + +static void aic_unmask(struct irq_data *d) +{ + struct irq_domain *domain = d->domain; + struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0); + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + u32 mask = d->mask; + + /* + * Enable interrupt. We always take the lock of the + * first irq chip as all chips share the same registers. + */ + irq_gc_lock(bgc); + + if (aic_is_ssr_used()) { + irq_reg_writel(gc, d->hwirq, aic_reg_data->ssr); + irq_reg_writel(gc, 1, aic_reg_data->iecr); + } else { + irq_reg_writel(gc, mask, aic_reg_data->iecr); + } + + gc->mask_cache |= mask; + + irq_gc_unlock(bgc); +} + +static int aic_retrigger(struct irq_data *d) +{ + struct irq_domain *domain = d->domain; + struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0); + + /* Set interrupt */ + irq_gc_lock(bgc); + + if (aic_is_ssr_used()) { + irq_reg_writel(bgc, d->hwirq, aic_reg_data->ssr); + irq_reg_writel(bgc, 1, aic_reg_data->iscr); + } else { + irq_reg_writel(bgc, d->mask, aic_reg_data->iscr); + } + + irq_gc_unlock(bgc); + + return 0; +} + +static int aic_set_type(struct irq_data *d, unsigned int type) +{ + struct irq_domain *domain = d->domain; + struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0); + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct aic_chip_data *aic = gc->private; + u32 val; + + switch (type) { + case IRQ_TYPE_LEVEL_HIGH: + val = AT91_AIC_SRCTYPE_HIGH; + break; + case IRQ_TYPE_EDGE_RISING: + val = AT91_AIC_SRCTYPE_RISING; + break; + case IRQ_TYPE_LEVEL_LOW: + if (!(d->mask & aic->ext_irqs)) + return -EINVAL; + + val = AT91_AIC_SRCTYPE_LOW; + break; + case IRQ_TYPE_EDGE_FALLING: + if (!(d->mask & aic->ext_irqs)) + return -EINVAL; + + val = AT91_AIC_SRCTYPE_FALLING; + break; + default: + return -EINVAL; + } + + irq_gc_lock(bgc); + aic_update_smr(bgc, d->hwirq, ~AT91_AIC_SRCTYPE, val); + irq_gc_unlock(bgc); + + return 0; +} + +#ifdef CONFIG_PM + +enum aic_pm_mode { + AIC_PM_SUSPEND, + AIC_PM_RESUME, +}; + +static void aic_pm_ctrl_ssr(struct irq_data *d, enum aic_pm_mode mode) +{ + struct irq_domain *domain = d->domain; + struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0); + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + u32 mask; + u32 which; + int i; + + if (mode == AIC_PM_SUSPEND) + which = gc->wake_active; + else + which = gc->mask_cache; + + irq_gc_lock(bgc); + + for (i = 0; i < AIC_IRQS_PER_CHIP; i++) { + mask = 1 << i; + if ((mask & gc->mask_cache) == (mask & gc->wake_active)) + continue; + + irq_reg_writel(bgc, i + gc->irq_base, aic_reg_data->ssr); + + if (mask & which) + irq_reg_writel(bgc, 1, aic_reg_data->iecr); + else + irq_reg_writel(bgc, 1, aic_reg_data->idcr); + } + + irq_gc_unlock(bgc); +} + +static void aic_pm_ctrl(struct irq_data *d, enum aic_pm_mode mode) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + u32 mask_idcr; + u32 mask_iecr; + + if (mode == AIC_PM_SUSPEND) { + mask_idcr = gc->mask_cache; + mask_iecr = gc->wake_active; + } else { + mask_idcr = gc->wake_active; + mask_iecr = gc->mask_cache; + } + + irq_gc_lock(gc); + irq_reg_writel(gc, mask_idcr, aic_reg_data->idcr); + irq_reg_writel(gc, mask_iecr, aic_reg_data->iecr); + irq_gc_unlock(gc); +} + +static void aic_suspend(struct irq_data *d) +{ + if (aic_is_ssr_used()) + aic_pm_ctrl_ssr(d, AIC_PM_SUSPEND); + else + aic_pm_ctrl(d, AIC_PM_SUSPEND); +} + +static void aic_resume(struct irq_data *d) +{ + if (aic_is_ssr_used()) + aic_pm_ctrl_ssr(d, AIC_PM_RESUME); + else + aic_pm_ctrl(d, AIC_PM_RESUME); +} + +static void aic_pm_shutdown(struct irq_data *d) +{ + struct irq_domain *domain = d->domain; + struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0); + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + int i; + + if (aic_is_ssr_used()) { + irq_gc_lock(bgc); + for (i = 0; i < AIC_IRQS_PER_CHIP; i++) { + irq_reg_writel(bgc, i + gc->irq_base, + aic_reg_data->ssr); + irq_reg_writel(bgc, 1, aic_reg_data->idcr); + irq_reg_writel(bgc, 1, aic_reg_data->iccr); + } + irq_gc_unlock(bgc); + } else { + irq_gc_lock(gc); + irq_reg_writel(gc, 0xffffffff, aic_reg_data->idcr); + irq_reg_writel(gc, 0xffffffff, aic_reg_data->iccr); + irq_gc_unlock(gc); + } +} +#else +#define aic_suspend NULL +#define aic_resume NULL +#define aic_pm_shutdown NULL +#endif /* CONFIG_PM */ + +static int __init aic_get_num_chips(struct device_node *node) +{ + int nirqs; + + /* Get total number of IRQs and configure register data */ + if (of_device_is_compatible(node, "atmel,at91rm9200-aic")) { + nirqs = NR_AT91RM9200_IRQS; + aic_reg_data = &aic_regs; + } else if (of_device_is_compatible(node, "atmel,sama5d2-aic")) { + nirqs = NR_SAMA5D2_IRQS; + aic_reg_data = &aic5_regs; + } else if (of_device_is_compatible(node, "atmel,sama5d3-aic")) { + nirqs = NR_SAMA5D3_IRQS; + aic_reg_data = &aic5_regs; + } else if (of_device_is_compatible(node, "atmel,sama5d4-aic")) { + nirqs = NR_SAMA5D4_IRQS; + aic_reg_data = &aic5_regs; + } else { + return -EINVAL; + } + + return DIV_ROUND_UP(nirqs, AIC_IRQS_PER_CHIP); +} + +static void __init aic_ext_irq_of_init(struct irq_domain *domain) +{ + struct device_node *node = irq_domain_get_of_node(domain); + struct irq_chip_generic *gc; + struct aic_chip_data *aic; + struct property *prop; + const __be32 *p; + u32 hwirq; + + gc = irq_get_domain_generic_chip(domain, 0); + + aic = gc->private; + aic->ext_irqs |= 1; + + of_property_for_each_u32(node, "atmel,external-irqs", prop, p, hwirq) { + gc = irq_get_domain_generic_chip(domain, hwirq); + if (!gc) { + pr_warn("AIC: external irq %d >= %d skip it\n", + hwirq, domain->revmap_size); + continue; + } + + aic = gc->private; + aic->ext_irqs |= (1 << (hwirq % AIC_IRQS_PER_CHIP)); + } +} + +static void __init aic_hw_init(struct irq_domain *domain) +{ + struct irq_chip_generic *gc = irq_get_domain_generic_chip(domain, 0); + int i; + + /* + * Perform 8 End Of Interrupt Command to make sure AIC + * will not Lock out nIRQ + */ + for (i = 0; i < 8; i++) + irq_reg_writel(gc, 0, aic_reg_data->eoi); + + /* + * Spurious Interrupt ID in Spurious Vector Register. + * When there is no current interrupt, the IRQ Vector Register + * reads the value stored in AIC_SPU + */ + irq_reg_writel(gc, 0xffffffff, aic_reg_data->spu); + + /* No debugging in AIC: Debug (Protect) Control Register */ + irq_reg_writel(gc, 0, aic_reg_data->dcr); + + /* Disable and clear all interrupts initially */ + if (aic_is_ssr_used()) { + for (i = 0; i < domain->revmap_size; i++) { + irq_reg_writel(gc, i, aic_reg_data->ssr); + irq_reg_writel(gc, i, aic_reg_data->svr); + irq_reg_writel(gc, 1, aic_reg_data->idcr); + irq_reg_writel(gc, 1, aic_reg_data->iccr); + } + } else { + irq_reg_writel(gc, 0xffffffff, aic_reg_data->idcr); + irq_reg_writel(gc, 0xffffffff, aic_reg_data->iccr); + + for (i = 0; i < NR_AT91RM9200_IRQS; i++) + irq_reg_writel(gc, i, aic_reg_data->svr + (i * 4)); + } +} + +static int __init aic_of_init(struct device_node *node, + struct device_node *parent) +{ + struct irq_chip_generic *gc; + struct irq_domain *domain; + struct aic_chip_data *aic; + void __iomem *reg_base; + int nchips; + int ret; + int i; + + if (aic_domain) + return -EEXIST; + + nchips = aic_get_num_chips(node); + if (nchips < 0) + return -EINVAL; + + reg_base = of_iomap(node, 0); + if (!reg_base) + return -ENOMEM; + + aic = kcalloc(nchips, sizeof(*aic), GFP_KERNEL); + if (!aic) { + ret = -ENOMEM; + goto err_iounmap; + } + + domain = irq_domain_add_linear(node, nchips * AIC_IRQS_PER_CHIP, + &aic_irq_ops, aic); + if (!domain) { + ret = -ENOMEM; + goto err_free_aic; + } + + ret = irq_alloc_domain_generic_chips(domain, AIC_IRQS_PER_CHIP, 1, + "atmel-aic", handle_fasteoi_irq, + IRQ_NOREQUEST | IRQ_NOPROBE | + IRQ_NOAUTOEN, 0, 0); + if (ret) + goto err_domain_remove; + + for (i = 0; i < nchips; i++) { + gc = irq_get_domain_generic_chip(domain, i * AIC_IRQS_PER_CHIP); + + gc->reg_base = reg_base; + gc->unused = 0; + gc->wake_enabled = ~0; + + gc->chip_types[0].type = IRQ_TYPE_SENSE_MASK; + gc->chip_types[0].regs.eoi = aic_reg_data->eoi; + gc->chip_types[0].chip.irq_eoi = irq_gc_eoi; + gc->chip_types[0].chip.irq_set_wake = irq_gc_set_wake; + gc->chip_types[0].chip.irq_shutdown = aic_irq_shutdown; + gc->chip_types[0].chip.irq_mask = aic_mask; + gc->chip_types[0].chip.irq_unmask = aic_unmask; + gc->chip_types[0].chip.irq_retrigger = aic_retrigger; + gc->chip_types[0].chip.irq_set_type = aic_set_type; + gc->chip_types[0].chip.irq_suspend = aic_suspend; + gc->chip_types[0].chip.irq_resume = aic_resume; + gc->chip_types[0].chip.irq_pm_shutdown = aic_pm_shutdown; + + gc->private = &aic[i]; + } + + aic_domain = domain; + aic_ext_irq_of_init(domain); + aic_hw_init(domain); + set_handle_irq(aic_handle); + + return 0; + +err_domain_remove: + irq_domain_remove(domain); + +err_free_aic: + kfree(aic); + +err_iounmap: + iounmap(reg_base); + return ret; +} +IRQCHIP_DECLARE(at91rm9200_aic, "atmel,at91rm9200-aic", aic_of_init); +IRQCHIP_DECLARE(sama5d2_aic5, "atmel,sama5d2-aic", aic_of_init); +IRQCHIP_DECLARE(sama5d3_aic5, "atmel,sama5d3-aic", aic_of_init); +IRQCHIP_DECLARE(sama5d4_aic5, "atmel,sama5d4-aic", aic_of_init); diff --git a/drivers/irqchip/irq-atmel-aic-common.c b/drivers/irqchip/irq-atmel-aic-common.c deleted file mode 100644 index 1d3978e..0000000 --- a/drivers/irqchip/irq-atmel-aic-common.c +++ /dev/null @@ -1,609 +0,0 @@ -/* - * Atmel AIC (Advanced Interrupt Controller) Driver - * - * Copyright (C) 2004 SAN People - * Copyright (C) 2004 ATMEL - * Copyright (C) Rick Bronson - * Copyright (C) 2014 Free Electrons - * - * Author: Boris BREZILLON <boris.brezil...@free-electrons.com> - * - * This file is licensed under the terms of the GNU General Public - * License version 2. This program is licensed "as is" without any - * warranty of any kind, whether express or implied. - */ - -#include <linux/errno.h> -#include <linux/init.h> -#include <linux/io.h> -#include <linux/irq.h> -#include <linux/irqchip.h> -#include <linux/irqdesc.h> -#include <linux/irqdomain.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/of.h> -#include <linux/of_address.h> -#include <linux/slab.h> - -#include <asm/exception.h> -#include <asm/mach/irq.h> - -#define AIC_IRQS_PER_CHIP 32 -#define NR_AT91RM9200_IRQS 32 -#define NR_SAMA5D2_IRQS 77 -#define NR_SAMA5D3_IRQS 48 -#define NR_SAMA5D4_IRQS 68 - -#define AT91_AIC_SMR_BASE 0 -#define AT91_AIC_SVR_BASE 0x80 -#define AT91_AIC_IVR 0x100 -#define AT91_AIC_ISR 0x108 -#define AT91_AIC_IECR 0x120 -#define AT91_AIC_IDCR 0x124 -#define AT91_AIC_ICCR 0x128 -#define AT91_AIC_ISCR 0x12c -#define AT91_AIC_EOICR 0x130 -#define AT91_AIC_SPU 0x134 -#define AT91_AIC_DCR 0x138 -#define AT91_INVALID_OFFSET (-1) - -#define AT91_AIC5_SSR 0x0 -#define AT91_AIC5_SMR 0x4 -#define AT91_AIC5_SVR 0x8 -#define AT91_AIC5_IVR 0x10 -#define AT91_AIC5_ISR 0x18 -#define AT91_AIC5_EOICR 0x38 -#define AT91_AIC5_SPU 0x3c -#define AT91_AIC5_IECR 0x40 -#define AT91_AIC5_IDCR 0x44 -#define AT91_AIC5_ICCR 0x48 -#define AT91_AIC5_ISCR 0x4c -#define AT91_AIC5_DCR 0x6c - -#define AT91_AIC_PRIOR GENMASK(2, 0) -#define AT91_AIC_IRQ_MIN_PRIORITY 0 -#define AT91_AIC_IRQ_MAX_PRIORITY 7 - -#define AT91_AIC_SRCTYPE GENMASK(6, 5) -#define AT91_AIC_SRCTYPE_LOW (0 << 5) -#define AT91_AIC_SRCTYPE_FALLING (1 << 5) -#define AT91_AIC_SRCTYPE_HIGH (2 << 5) -#define AT91_AIC_SRCTYPE_RISING (3 << 5) - -struct aic_chip_data { - u32 ext_irqs; -}; - -/** - * struct aic_reg_offset - * - * @eoi: End of interrupt command register - * @smr: Source mode register - * @ssr: Source select register - * @iscr: Interrupt set command register - * @idcr: Interrupt disable command register - * @iccr: Interrupt clear command register - * @iecr: Interrupt enable command register - * @spu: Spurious interrupt vector register - * @dcr: Debug control register - * @svr: Source vector register - * @ivr: Interrupt vector register - * @isr: Interrupt status register - * - * Each value means register offset. - */ -struct aic_reg_offset { - int eoi; - int smr; - int ssr; - int iscr; - int idcr; - int iccr; - int iecr; - int spu; - int dcr; - int svr; - int ivr; - int isr; -}; - -static const struct aic_reg_offset aic_regs = { - .eoi = AT91_AIC_EOICR, - .smr = AT91_AIC_SMR_BASE, - .ssr = AT91_INVALID_OFFSET, /* No SSR exists */ - .iscr = AT91_AIC_ISCR, - .idcr = AT91_AIC_IDCR, - .iccr = AT91_AIC_ICCR, - .iecr = AT91_AIC_IECR, - .spu = AT91_AIC_SPU, - .dcr = AT91_AIC_DCR, - .svr = AT91_AIC_SVR_BASE, - .ivr = AT91_AIC_IVR, - .isr = AT91_AIC_ISR, -}; - -static const struct aic_reg_offset aic5_regs = { - .eoi = AT91_AIC5_EOICR, - .smr = AT91_AIC5_SMR, - .ssr = AT91_AIC5_SSR, - .iscr = AT91_AIC5_ISCR, - .idcr = AT91_AIC5_IDCR, - .iccr = AT91_AIC5_ICCR, - .iecr = AT91_AIC5_IECR, - .spu = AT91_AIC5_SPU, - .dcr = AT91_AIC5_DCR, - .svr = AT91_AIC5_SVR, - .ivr = AT91_AIC5_IVR, - .isr = AT91_AIC5_ISR, -}; - -static struct irq_domain *aic_domain; -static const struct aic_reg_offset *aic_reg_data; - -static asmlinkage void __exception_irq_entry -aic_handle(struct pt_regs *regs) -{ - struct irq_chip_generic *gc = irq_get_domain_generic_chip(aic_domain, - 0); - u32 hwirq = irq_reg_readl(gc, aic_reg_data->ivr); - u32 status = irq_reg_readl(gc, aic_reg_data->isr); - - if (!status) - irq_reg_writel(gc, 0, aic_reg_data->eoi); - else - handle_domain_irq(aic_domain, hwirq, regs); -} - -static inline bool aic_is_ssr_used(void) -{ - return aic_reg_data->ssr != AT91_INVALID_OFFSET; -} - -static void aic_update_smr(struct irq_chip_generic *gc, int hwirq, - u32 mask, u32 val) -{ - int reg = aic_reg_data->smr; - u32 tmp = 0; - - if (aic_is_ssr_used()) - irq_reg_writel(gc, hwirq, aic_reg_data->ssr); - else - reg += hwirq * 4; - - tmp = irq_reg_readl(gc, reg); - tmp &= mask; - tmp |= val; - - irq_reg_writel(gc, tmp, reg); -} - -static int aic_irq_domain_xlate(struct irq_domain *d, struct device_node *node, - const u32 *intspec, unsigned int intsize, - irq_hw_number_t *out_hwirq, - unsigned int *out_type) -{ - struct irq_chip_generic *gc = irq_get_domain_generic_chip(d, 0); - bool condition = (intsize < 3) || - (intspec[2] < AT91_AIC_IRQ_MIN_PRIORITY) || - (intspec[2] > AT91_AIC_IRQ_MAX_PRIORITY); - - if (!gc || WARN_ON(condition)) - return -EINVAL; - - /* - * intspec[0]: HW IRQ number - * intspec[1]: IRQ flag - * intspec[2]: IRQ priority - */ - - *out_hwirq = intspec[0]; - *out_type = intspec[1] & IRQ_TYPE_SENSE_MASK; - - irq_gc_lock(gc); - aic_update_smr(gc, *out_hwirq, ~AT91_AIC_PRIOR, intspec[2]); - irq_gc_unlock(gc); - - return 0; -} - -static const struct irq_domain_ops aic_irq_ops = { - .map = irq_map_generic_chip, - .xlate = aic_irq_domain_xlate, -}; - -static void aic_irq_shutdown(struct irq_data *d) -{ - struct irq_chip_type *ct = irq_data_get_chip_type(d); - - ct->chip.irq_mask(d); -} - -static void aic_mask(struct irq_data *d) -{ - struct irq_domain *domain = d->domain; - struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0); - struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); - u32 mask = d->mask; - - /* - * Disable interrupt. We always take the lock of the - * first irq chip as all chips share the same registers. - */ - irq_gc_lock(bgc); - - if (aic_is_ssr_used()) { - irq_reg_writel(gc, d->hwirq, aic_reg_data->ssr); - irq_reg_writel(gc, 1, aic_reg_data->idcr); - } else { - irq_reg_writel(gc, mask, aic_reg_data->idcr); - } - - gc->mask_cache &= ~mask; - - irq_gc_unlock(bgc); -} - -static void aic_unmask(struct irq_data *d) -{ - struct irq_domain *domain = d->domain; - struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0); - struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); - u32 mask = d->mask; - - /* - * Enable interrupt. We always take the lock of the - * first irq chip as all chips share the same registers. - */ - irq_gc_lock(bgc); - - if (aic_is_ssr_used()) { - irq_reg_writel(gc, d->hwirq, aic_reg_data->ssr); - irq_reg_writel(gc, 1, aic_reg_data->iecr); - } else { - irq_reg_writel(gc, mask, aic_reg_data->iecr); - } - - gc->mask_cache |= mask; - - irq_gc_unlock(bgc); -} - -static int aic_retrigger(struct irq_data *d) -{ - struct irq_domain *domain = d->domain; - struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0); - - /* Set interrupt */ - irq_gc_lock(bgc); - - if (aic_is_ssr_used()) { - irq_reg_writel(bgc, d->hwirq, aic_reg_data->ssr); - irq_reg_writel(bgc, 1, aic_reg_data->iscr); - } else { - irq_reg_writel(bgc, d->mask, aic_reg_data->iscr); - } - - irq_gc_unlock(bgc); - - return 0; -} - -static int aic_set_type(struct irq_data *d, unsigned int type) -{ - struct irq_domain *domain = d->domain; - struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0); - struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); - struct aic_chip_data *aic = gc->private; - u32 val; - - switch (type) { - case IRQ_TYPE_LEVEL_HIGH: - val = AT91_AIC_SRCTYPE_HIGH; - break; - case IRQ_TYPE_EDGE_RISING: - val = AT91_AIC_SRCTYPE_RISING; - break; - case IRQ_TYPE_LEVEL_LOW: - if (!(d->mask & aic->ext_irqs)) - return -EINVAL; - - val = AT91_AIC_SRCTYPE_LOW; - break; - case IRQ_TYPE_EDGE_FALLING: - if (!(d->mask & aic->ext_irqs)) - return -EINVAL; - - val = AT91_AIC_SRCTYPE_FALLING; - break; - default: - return -EINVAL; - } - - irq_gc_lock(bgc); - aic_update_smr(bgc, d->hwirq, ~AT91_AIC_SRCTYPE, val); - irq_gc_unlock(bgc); - - return 0; -} - -#ifdef CONFIG_PM - -enum aic_pm_mode { - AIC_PM_SUSPEND, - AIC_PM_RESUME, -}; - -static void aic_pm_ctrl_ssr(struct irq_data *d, enum aic_pm_mode mode) -{ - struct irq_domain *domain = d->domain; - struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0); - struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); - u32 mask; - u32 which; - int i; - - if (mode == AIC_PM_SUSPEND) - which = gc->wake_active; - else - which = gc->mask_cache; - - irq_gc_lock(bgc); - - for (i = 0; i < AIC_IRQS_PER_CHIP; i++) { - mask = 1 << i; - if ((mask & gc->mask_cache) == (mask & gc->wake_active)) - continue; - - irq_reg_writel(bgc, i + gc->irq_base, aic_reg_data->ssr); - - if (mask & which) - irq_reg_writel(bgc, 1, aic_reg_data->iecr); - else - irq_reg_writel(bgc, 1, aic_reg_data->idcr); - } - - irq_gc_unlock(bgc); -} - -static void aic_pm_ctrl(struct irq_data *d, enum aic_pm_mode mode) -{ - struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); - u32 mask_idcr; - u32 mask_iecr; - - if (mode == AIC_PM_SUSPEND) { - mask_idcr = gc->mask_cache; - mask_iecr = gc->wake_active; - } else { - mask_idcr = gc->wake_active; - mask_iecr = gc->mask_cache; - } - - irq_gc_lock(gc); - irq_reg_writel(gc, mask_idcr, aic_reg_data->idcr); - irq_reg_writel(gc, mask_iecr, aic_reg_data->iecr); - irq_gc_unlock(gc); -} - -static void aic_suspend(struct irq_data *d) -{ - if (aic_is_ssr_used()) - aic_pm_ctrl_ssr(d, AIC_PM_SUSPEND); - else - aic_pm_ctrl(d, AIC_PM_SUSPEND); -} - -static void aic_resume(struct irq_data *d) -{ - if (aic_is_ssr_used()) - aic_pm_ctrl_ssr(d, AIC_PM_RESUME); - else - aic_pm_ctrl(d, AIC_PM_RESUME); -} - -static void aic_pm_shutdown(struct irq_data *d) -{ - struct irq_domain *domain = d->domain; - struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0); - struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); - int i; - - if (aic_is_ssr_used()) { - irq_gc_lock(bgc); - for (i = 0; i < AIC_IRQS_PER_CHIP; i++) { - irq_reg_writel(bgc, i + gc->irq_base, - aic_reg_data->ssr); - irq_reg_writel(bgc, 1, aic_reg_data->idcr); - irq_reg_writel(bgc, 1, aic_reg_data->iccr); - } - irq_gc_unlock(bgc); - } else { - irq_gc_lock(gc); - irq_reg_writel(gc, 0xffffffff, aic_reg_data->idcr); - irq_reg_writel(gc, 0xffffffff, aic_reg_data->iccr); - irq_gc_unlock(gc); - } -} -#else -#define aic_suspend NULL -#define aic_resume NULL -#define aic_pm_shutdown NULL -#endif /* CONFIG_PM */ - -static int __init aic_get_num_chips(struct device_node *node) -{ - int nirqs; - - /* Get total number of IRQs and configure register data */ - if (of_device_is_compatible(node, "atmel,at91rm9200-aic")) { - nirqs = NR_AT91RM9200_IRQS; - aic_reg_data = &aic_regs; - } else if (of_device_is_compatible(node, "atmel,sama5d2-aic")) { - nirqs = NR_SAMA5D2_IRQS; - aic_reg_data = &aic5_regs; - } else if (of_device_is_compatible(node, "atmel,sama5d3-aic")) { - nirqs = NR_SAMA5D3_IRQS; - aic_reg_data = &aic5_regs; - } else if (of_device_is_compatible(node, "atmel,sama5d4-aic")) { - nirqs = NR_SAMA5D4_IRQS; - aic_reg_data = &aic5_regs; - } else { - return -EINVAL; - } - - return DIV_ROUND_UP(nirqs, AIC_IRQS_PER_CHIP); -} - -static void __init aic_ext_irq_of_init(struct irq_domain *domain) -{ - struct device_node *node = irq_domain_get_of_node(domain); - struct irq_chip_generic *gc; - struct aic_chip_data *aic; - struct property *prop; - const __be32 *p; - u32 hwirq; - - gc = irq_get_domain_generic_chip(domain, 0); - - aic = gc->private; - aic->ext_irqs |= 1; - - of_property_for_each_u32(node, "atmel,external-irqs", prop, p, hwirq) { - gc = irq_get_domain_generic_chip(domain, hwirq); - if (!gc) { - pr_warn("AIC: external irq %d >= %d skip it\n", - hwirq, domain->revmap_size); - continue; - } - - aic = gc->private; - aic->ext_irqs |= (1 << (hwirq % AIC_IRQS_PER_CHIP)); - } -} - -static void __init aic_hw_init(struct irq_domain *domain) -{ - struct irq_chip_generic *gc = irq_get_domain_generic_chip(domain, 0); - int i; - - /* - * Perform 8 End Of Interrupt Command to make sure AIC - * will not Lock out nIRQ - */ - for (i = 0; i < 8; i++) - irq_reg_writel(gc, 0, aic_reg_data->eoi); - - /* - * Spurious Interrupt ID in Spurious Vector Register. - * When there is no current interrupt, the IRQ Vector Register - * reads the value stored in AIC_SPU - */ - irq_reg_writel(gc, 0xffffffff, aic_reg_data->spu); - - /* No debugging in AIC: Debug (Protect) Control Register */ - irq_reg_writel(gc, 0, aic_reg_data->dcr); - - /* Disable and clear all interrupts initially */ - if (aic_is_ssr_used()) { - for (i = 0; i < domain->revmap_size; i++) { - irq_reg_writel(gc, i, aic_reg_data->ssr); - irq_reg_writel(gc, i, aic_reg_data->svr); - irq_reg_writel(gc, 1, aic_reg_data->idcr); - irq_reg_writel(gc, 1, aic_reg_data->iccr); - } - } else { - irq_reg_writel(gc, 0xffffffff, aic_reg_data->idcr); - irq_reg_writel(gc, 0xffffffff, aic_reg_data->iccr); - - for (i = 0; i < NR_AT91RM9200_IRQS; i++) - irq_reg_writel(gc, i, aic_reg_data->svr + (i * 4)); - } -} - -static int __init aic_of_init(struct device_node *node, - struct device_node *parent) -{ - struct irq_chip_generic *gc; - struct irq_domain *domain; - struct aic_chip_data *aic; - void __iomem *reg_base; - int nchips; - int ret; - int i; - - if (aic_domain) - return -EEXIST; - - nchips = aic_get_num_chips(node); - if (nchips < 0) - return -EINVAL; - - reg_base = of_iomap(node, 0); - if (!reg_base) - return -ENOMEM; - - aic = kcalloc(nchips, sizeof(*aic), GFP_KERNEL); - if (!aic) { - ret = -ENOMEM; - goto err_iounmap; - } - - domain = irq_domain_add_linear(node, nchips * AIC_IRQS_PER_CHIP, - &aic_irq_ops, aic); - if (!domain) { - ret = -ENOMEM; - goto err_free_aic; - } - - ret = irq_alloc_domain_generic_chips(domain, AIC_IRQS_PER_CHIP, 1, - "atmel-aic", handle_fasteoi_irq, - IRQ_NOREQUEST | IRQ_NOPROBE | - IRQ_NOAUTOEN, 0, 0); - if (ret) - goto err_domain_remove; - - for (i = 0; i < nchips; i++) { - gc = irq_get_domain_generic_chip(domain, i * AIC_IRQS_PER_CHIP); - - gc->reg_base = reg_base; - gc->unused = 0; - gc->wake_enabled = ~0; - - gc->chip_types[0].type = IRQ_TYPE_SENSE_MASK; - gc->chip_types[0].regs.eoi = aic_reg_data->eoi; - gc->chip_types[0].chip.irq_eoi = irq_gc_eoi; - gc->chip_types[0].chip.irq_set_wake = irq_gc_set_wake; - gc->chip_types[0].chip.irq_shutdown = aic_irq_shutdown; - gc->chip_types[0].chip.irq_mask = aic_mask; - gc->chip_types[0].chip.irq_unmask = aic_unmask; - gc->chip_types[0].chip.irq_retrigger = aic_retrigger; - gc->chip_types[0].chip.irq_set_type = aic_set_type; - gc->chip_types[0].chip.irq_suspend = aic_suspend; - gc->chip_types[0].chip.irq_resume = aic_resume; - gc->chip_types[0].chip.irq_pm_shutdown = aic_pm_shutdown; - - gc->private = &aic[i]; - } - - aic_domain = domain; - aic_ext_irq_of_init(domain); - aic_hw_init(domain); - set_handle_irq(aic_handle); - - return 0; - -err_domain_remove: - irq_domain_remove(domain); - -err_free_aic: - kfree(aic); - -err_iounmap: - iounmap(reg_base); - return ret; -} -IRQCHIP_DECLARE(at91rm9200_aic, "atmel,at91rm9200-aic", aic_of_init); -IRQCHIP_DECLARE(sama5d2_aic5, "atmel,sama5d2-aic", aic_of_init); -IRQCHIP_DECLARE(sama5d3_aic5, "atmel,sama5d3-aic", aic_of_init); -IRQCHIP_DECLARE(sama5d4_aic5, "atmel,sama5d4-aic", aic_of_init); -- 2.6.4 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/