diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2014-08-05 17:38:45 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-08-05 17:38:45 -0700 |
commit | 08d69a25714429850cf9ef71f22d8cdc9189d93f (patch) | |
tree | c03f4e91834b5280a4695f0c805b58c599e895cc /drivers/irqchip | |
parent | ed5c41d30ef2ce578fd6b6e2f7ec23f2a58b1eba (diff) | |
parent | c6f1224573c3b609bd8073b39f496637a16cc06f (diff) | |
download | linux-08d69a25714429850cf9ef71f22d8cdc9189d93f.tar.bz2 |
Merge branch 'irq-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull irq updates from Thomas Gleixner:
"Nothing spectacular from the irq department this time:
- overhaul of the crossbar chip driver
- overhaul of the spear shirq chip driver
- support for the atmel-aic chip
- code move from arch to drivers
- the usual tiny fixlets
- two reverts worth to mention which undo the too simple attempt of
supporting wakeup interrupts on shared interrupt lines"
* 'irq-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (41 commits)
Revert "irq: Warn when shared interrupts do not match on NO_SUSPEND"
Revert "PM / sleep / irq: Do not suspend wakeup interrupts"
irq: Warn when shared interrupts do not match on NO_SUSPEND
irqchip: atmel-aic: Define irq fixups for atmel SoCs
irqchip: atmel-aic: Implement RTC irq fixup
irqchip: atmel-aic: Add irq fixup infrastructure
irqchip: atmel-aic: Add atmel AIC/AIC5 drivers
irqchip: atmel-aic: Move binding doc to interrupt-controller directory
genirq: generic chip: Export irq_map_generic_chip function
PM / sleep / irq: Do not suspend wakeup interrupts
irqchip: or1k-pic: Migrate from arch/openrisc/
irqchip: crossbar: Allow for quirky hardware with direct hardwiring of GIC
documentation: dt: omap: crossbar: Add description for interrupt consumer
irqchip: crossbar: Introduce centralized check for crossbar write
irqchip: crossbar: Introduce ti, max-crossbar-sources to identify valid crossbar mapping
irqchip: crossbar: Add kerneldoc for crossbar_domain_unmap callback
irqchip: crossbar: Set cb pointer to null in case of error
irqchip: crossbar: Change the goto naming
irqchip: crossbar: Return proper error value
irqchip: crossbar: Fix kerneldoc warning
...
Diffstat (limited to 'drivers/irqchip')
-rw-r--r-- | drivers/irqchip/Kconfig | 18 | ||||
-rw-r--r-- | drivers/irqchip/Makefile | 3 | ||||
-rw-r--r-- | drivers/irqchip/irq-atmel-aic-common.c | 254 | ||||
-rw-r--r-- | drivers/irqchip/irq-atmel-aic-common.h | 39 | ||||
-rw-r--r-- | drivers/irqchip/irq-atmel-aic.c | 262 | ||||
-rw-r--r-- | drivers/irqchip/irq-atmel-aic5.c | 353 | ||||
-rw-r--r-- | drivers/irqchip/irq-crossbar.c | 168 | ||||
-rw-r--r-- | drivers/irqchip/irq-nvic.c | 13 | ||||
-rw-r--r-- | drivers/irqchip/irq-or1k-pic.c | 182 | ||||
-rw-r--r-- | drivers/irqchip/spear-shirq.c | 304 |
10 files changed, 1395 insertions, 201 deletions
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 7f0c2a30267b..4e230e7c76ee 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -35,6 +35,20 @@ config ARM_VIC_NR The maximum number of VICs available in the system, for power management. +config ATMEL_AIC_IRQ + bool + select GENERIC_IRQ_CHIP + select IRQ_DOMAIN + 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 BRCMSTB_L2_IRQ bool depends on ARM @@ -58,6 +72,10 @@ config CLPS711X_IRQCHIP select SPARSE_IRQ default y +config OR1K_PIC + bool + select IRQ_DOMAIN + config ORION_IRQCHIP bool select IRQ_DOMAIN diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index c57e642700d4..73052ba9ca62 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_METAG) += irq-metag-ext.o obj-$(CONFIG_METAG_PERFCOUNTER_IRQS) += irq-metag.o obj-$(CONFIG_ARCH_MOXART) += irq-moxart.o obj-$(CONFIG_CLPS711X_IRQCHIP) += irq-clps711x.o +obj-$(CONFIG_OR1K_PIC) += irq-or1k-pic.o obj-$(CONFIG_ORION_IRQCHIP) += irq-orion.o obj-$(CONFIG_ARCH_SUNXI) += irq-sun4i.o obj-$(CONFIG_ARCH_SUNXI) += irq-sunxi-nmi.o @@ -19,6 +20,8 @@ obj-$(CONFIG_ARM_GIC) += irq-gic.o irq-gic-common.o obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-common.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 irq-atmel-aic.o +obj-$(CONFIG_ATMEL_AIC5_IRQ) += irq-atmel-aic-common.o irq-atmel-aic5.o obj-$(CONFIG_IMGPDC_IRQ) += irq-imgpdc.o obj-$(CONFIG_SIRF_IRQ) += irq-sirfsoc.o obj-$(CONFIG_RENESAS_INTC_IRQPIN) += irq-renesas-intc-irqpin.o diff --git a/drivers/irqchip/irq-atmel-aic-common.c b/drivers/irqchip/irq-atmel-aic-common.c new file mode 100644 index 000000000000..6ae3cdee0681 --- /dev/null +++ b/drivers/irqchip/irq-atmel-aic-common.c @@ -0,0 +1,254 @@ +/* + * Atmel AT91 common AIC (Advanced Interrupt Controller) code shared by + * irq-atmel-aic and irq-atmel-aic5 drivers + * + * Copyright (C) 2004 SAN People + * Copyright (C) 2004 ATMEL + * Copyright (C) Rick Bronson + * Copyright (C) 2014 Free Electrons + * + * Author: Boris BREZILLON <boris.brezillon@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/io.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/slab.h> + +#include "irq-atmel-aic-common.h" + +#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(7, 6) +#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; +}; + +static void aic_common_shutdown(struct irq_data *d) +{ + struct irq_chip_type *ct = irq_data_get_chip_type(d); + + ct->chip.irq_mask(d); +} + +int aic_common_set_type(struct irq_data *d, unsigned type, unsigned *val) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct aic_chip_data *aic = gc->private; + unsigned aic_type; + + switch (type) { + case IRQ_TYPE_LEVEL_HIGH: + aic_type = AT91_AIC_SRCTYPE_HIGH; + break; + case IRQ_TYPE_EDGE_RISING: + aic_type = AT91_AIC_SRCTYPE_RISING; + break; + case IRQ_TYPE_LEVEL_LOW: + if (!(d->mask & aic->ext_irqs)) + return -EINVAL; + + aic_type = AT91_AIC_SRCTYPE_LOW; + break; + case IRQ_TYPE_EDGE_FALLING: + if (!(d->mask & aic->ext_irqs)) + return -EINVAL; + + aic_type = AT91_AIC_SRCTYPE_FALLING; + break; + default: + return -EINVAL; + } + + *val &= AT91_AIC_SRCTYPE; + *val |= aic_type; + + return 0; +} + +int aic_common_set_priority(int priority, unsigned *val) +{ + if (priority < AT91_AIC_IRQ_MIN_PRIORITY || + priority > AT91_AIC_IRQ_MAX_PRIORITY) + return -EINVAL; + + *val &= AT91_AIC_PRIOR; + *val |= priority; + + return 0; +} + +int aic_common_irq_domain_xlate(struct irq_domain *d, + struct device_node *ctrlr, + const u32 *intspec, + unsigned int intsize, + irq_hw_number_t *out_hwirq, + unsigned int *out_type) +{ + if (WARN_ON(intsize < 3)) + return -EINVAL; + + if (WARN_ON((intspec[2] < AT91_AIC_IRQ_MIN_PRIORITY) || + (intspec[2] > AT91_AIC_IRQ_MAX_PRIORITY))) + return -EINVAL; + + *out_hwirq = intspec[0]; + *out_type = intspec[1] & IRQ_TYPE_SENSE_MASK; + + return 0; +} + +static void __init aic_common_ext_irq_of_init(struct irq_domain *domain) +{ + struct device_node *node = domain->of_node; + 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 % 32)); + } +} + +#define AT91_RTC_IDR 0x24 +#define AT91_RTC_IMR 0x28 +#define AT91_RTC_IRQ_MASK 0x1f + +void __init aic_common_rtc_irq_fixup(struct device_node *root) +{ + struct device_node *np; + void __iomem *regs; + + np = of_find_compatible_node(root, NULL, "atmel,at91rm9200-rtc"); + if (!np) + np = of_find_compatible_node(root, NULL, + "atmel,at91sam9x5-rtc"); + + if (!np) + return; + + regs = of_iomap(np, 0); + of_node_put(np); + + if (!regs) + return; + + writel(AT91_RTC_IRQ_MASK, regs + AT91_RTC_IDR); + + iounmap(regs); +} + +void __init aic_common_irq_fixup(const struct of_device_id *matches) +{ + struct device_node *root = of_find_node_by_path("/"); + const struct of_device_id *match; + + if (!root) + return; + + match = of_match_node(matches, root); + of_node_put(root); + + if (match) { + void (*fixup)(struct device_node *) = match->data; + fixup(root); + } + + of_node_put(root); +} + +struct irq_domain *__init aic_common_of_init(struct device_node *node, + const struct irq_domain_ops *ops, + const char *name, int nirqs) +{ + struct irq_chip_generic *gc; + struct irq_domain *domain; + struct aic_chip_data *aic; + void __iomem *reg_base; + int nchips; + int ret; + int i; + + nchips = DIV_ROUND_UP(nirqs, 32); + + reg_base = of_iomap(node, 0); + if (!reg_base) + return ERR_PTR(-ENOMEM); + + aic = kcalloc(nchips, sizeof(*aic), GFP_KERNEL); + if (!aic) { + ret = -ENOMEM; + goto err_iounmap; + } + + domain = irq_domain_add_linear(node, nchips * 32, ops, aic); + if (!domain) { + ret = -ENOMEM; + goto err_free_aic; + } + + ret = irq_alloc_domain_generic_chips(domain, 32, 1, name, + handle_level_irq, 0, 0, + IRQCHIP_SKIP_SET_WAKE); + if (ret) + goto err_domain_remove; + + for (i = 0; i < nchips; i++) { + gc = irq_get_domain_generic_chip(domain, i * 32); + + 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].handler = handle_fasteoi_irq; + 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_common_shutdown; + gc->private = &aic[i]; + } + + aic_common_ext_irq_of_init(domain); + + return domain; + +err_domain_remove: + irq_domain_remove(domain); + +err_free_aic: + kfree(aic); + +err_iounmap: + iounmap(reg_base); + + return ERR_PTR(ret); +} diff --git a/drivers/irqchip/irq-atmel-aic-common.h b/drivers/irqchip/irq-atmel-aic-common.h new file mode 100644 index 000000000000..90aa00e918d6 --- /dev/null +++ b/drivers/irqchip/irq-atmel-aic-common.h @@ -0,0 +1,39 @@ +/* + * Atmel AT91 common AIC (Advanced Interrupt Controller) header file + * + * Copyright (C) 2004 SAN People + * Copyright (C) 2004 ATMEL + * Copyright (C) Rick Bronson + * Copyright (C) 2014 Free Electrons + * + * Author: Boris BREZILLON <boris.brezillon@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. + */ + +#ifndef __IRQ_ATMEL_AIC_COMMON_H +#define __IRQ_ATMEL_AIC_COMMON_H + + +int aic_common_set_type(struct irq_data *d, unsigned type, unsigned *val); + +int aic_common_set_priority(int priority, unsigned *val); + +int aic_common_irq_domain_xlate(struct irq_domain *d, + struct device_node *ctrlr, + const u32 *intspec, + unsigned int intsize, + irq_hw_number_t *out_hwirq, + unsigned int *out_type); + +struct irq_domain *__init aic_common_of_init(struct device_node *node, + const struct irq_domain_ops *ops, + const char *name, int nirqs); + +void __init aic_common_rtc_irq_fixup(struct device_node *root); + +void __init aic_common_irq_fixup(const struct of_device_id *matches); + +#endif /* __IRQ_ATMEL_AIC_COMMON_H */ diff --git a/drivers/irqchip/irq-atmel-aic.c b/drivers/irqchip/irq-atmel-aic.c new file mode 100644 index 000000000000..a82869e9fb26 --- /dev/null +++ b/drivers/irqchip/irq-atmel-aic.c @@ -0,0 +1,262 @@ +/* + * Atmel AT91 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.brezillon@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/init.h> +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/bitmap.h> +#include <linux/types.h> +#include <linux/irq.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/irqdomain.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/io.h> + +#include <asm/exception.h> +#include <asm/mach/irq.h> + +#include "irq-atmel-aic-common.h" +#include "irqchip.h" + +/* Number of irq lines managed by AIC */ +#define NR_AIC_IRQS 32 + +#define AT91_AIC_SMR(n) ((n) * 4) + +#define AT91_AIC_SVR(n) (0x80 + ((n) * 4)) +#define AT91_AIC_IVR 0x100 +#define AT91_AIC_FVR 0x104 +#define AT91_AIC_ISR 0x108 + +#define AT91_AIC_IPR 0x10c +#define AT91_AIC_IMR 0x110 +#define AT91_AIC_CISR 0x114 + +#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 + +static struct irq_domain *aic_domain; + +static asmlinkage void __exception_irq_entry +aic_handle(struct pt_regs *regs) +{ + struct irq_domain_chip_generic *dgc = aic_domain->gc; + struct irq_chip_generic *gc = dgc->gc[0]; + u32 irqnr; + u32 irqstat; + + irqnr = irq_reg_readl(gc->reg_base + AT91_AIC_IVR); + irqstat = irq_reg_readl(gc->reg_base + AT91_AIC_ISR); + + irqnr = irq_find_mapping(aic_domain, irqnr); + + if (!irqstat) + irq_reg_writel(0, gc->reg_base + AT91_AIC_EOICR); + else + handle_IRQ(irqnr, regs); +} + +static int aic_retrigger(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + + /* Enable interrupt on AIC5 */ + irq_gc_lock(gc); + irq_reg_writel(d->mask, gc->reg_base + AT91_AIC_ISCR); + irq_gc_unlock(gc); + + return 0; +} + +static int aic_set_type(struct irq_data *d, unsigned type) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + unsigned int smr; + int ret; + + smr = irq_reg_readl(gc->reg_base + AT91_AIC_SMR(d->hwirq)); + ret = aic_common_set_type(d, type, &smr); + if (ret) + return ret; + + irq_reg_writel(smr, gc->reg_base + AT91_AIC_SMR(d->hwirq)); + + return 0; +} + +#ifdef CONFIG_PM +static void aic_suspend(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + + irq_gc_lock(gc); + irq_reg_writel(gc->mask_cache, gc->reg_base + AT91_AIC_IDCR); + irq_reg_writel(gc->wake_active, gc->reg_base + AT91_AIC_IECR); + irq_gc_unlock(gc); +} + +static void aic_resume(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + + irq_gc_lock(gc); + irq_reg_writel(gc->wake_active, gc->reg_base + AT91_AIC_IDCR); + irq_reg_writel(gc->mask_cache, gc->reg_base + AT91_AIC_IECR); + irq_gc_unlock(gc); +} + +static void aic_pm_shutdown(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + + irq_gc_lock(gc); + irq_reg_writel(0xffffffff, gc->reg_base + AT91_AIC_IDCR); + irq_reg_writel(0xffffffff, gc->reg_base + AT91_AIC_ICCR); + irq_gc_unlock(gc); +} +#else +#define aic_suspend NULL +#define aic_resume NULL +#define aic_pm_shutdown NULL +#endif /* CONFIG_PM */ + +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(0, gc->reg_base + AT91_AIC_EOICR); + + /* + * 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(0xffffffff, gc->reg_base + AT91_AIC_SPU); + + /* No debugging in AIC: Debug (Protect) Control Register */ + irq_reg_writel(0, gc->reg_base + AT91_AIC_DCR); + + /* Disable and clear all interrupts initially */ + irq_reg_writel(0xffffffff, gc->reg_base + AT91_AIC_IDCR); + irq_reg_writel(0xffffffff, gc->reg_base + AT91_AIC_ICCR); + + for (i = 0; i < 32; i++) + irq_reg_writel(i, gc->reg_base + AT91_AIC_SVR(i)); +} + +static int aic_irq_domain_xlate(struct irq_domain *d, + struct device_node *ctrlr, + const u32 *intspec, unsigned int intsize, + irq_hw_number_t *out_hwirq, + unsigned int *out_type) +{ + struct irq_domain_chip_generic *dgc = d->gc; + struct irq_chip_generic *gc; + unsigned smr; + int idx; + int ret; + + if (!dgc) + return -EINVAL; + + ret = aic_common_irq_domain_xlate(d, ctrlr, intspec, intsize, + out_hwirq, out_type); + if (ret) + return ret; + + idx = intspec[0] / dgc->irqs_per_chip; + if (idx >= dgc->num_chips) + return -EINVAL; + + gc = dgc->gc[idx]; + + irq_gc_lock(gc); + smr = irq_reg_readl(gc->reg_base + AT91_AIC_SMR(*out_hwirq)); + ret = aic_common_set_priority(intspec[2], &smr); + if (!ret) + irq_reg_writel(smr, gc->reg_base + AT91_AIC_SMR(*out_hwirq)); + irq_gc_unlock(gc); + + return ret; +} + +static const struct irq_domain_ops aic_irq_ops = { + .map = irq_map_generic_chip, + .xlate = aic_irq_domain_xlate, +}; + +static void __init at91sam9_aic_irq_fixup(struct device_node *root) +{ + aic_common_rtc_irq_fixup(root); +} + +static const struct of_device_id __initdata aic_irq_fixups[] = { + { .compatible = "atmel,at91sam9g45", .data = at91sam9_aic_irq_fixup }, + { .compatible = "atmel,at91sam9n12", .data = at91sam9_aic_irq_fixup }, + { .compatible = "atmel,at91sam9rl", .data = at91sam9_aic_irq_fixup }, + { .compatible = "atmel,at91sam9x5", .data = at91sam9_aic_irq_fixup }, + { /* sentinel */ }, +}; + +static int __init aic_of_init(struct device_node *node, + struct device_node *parent) +{ + struct irq_chip_generic *gc; + struct irq_domain *domain; + + if (aic_domain) + return -EEXIST; + + domain = aic_common_of_init(node, &aic_irq_ops, "atmel-aic", + NR_AIC_IRQS); + if (IS_ERR(domain)) + return PTR_ERR(domain); + + aic_common_irq_fixup(aic_irq_fixups); + + aic_domain = domain; + gc = irq_get_domain_generic_chip(domain, 0); + + gc->chip_types[0].regs.eoi = AT91_AIC_EOICR; + gc->chip_types[0].regs.enable = AT91_AIC_IECR; + gc->chip_types[0].regs.disable = AT91_AIC_IDCR; + gc->chip_types[0].chip.irq_mask = irq_gc_mask_disable_reg; + gc->chip_types[0].chip.irq_unmask = irq_gc_unmask_enable_reg; + 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; + + aic_hw_init(domain); + set_handle_irq(aic_handle); + + return 0; +} +IRQCHIP_DECLARE(at91rm9200_aic, "atmel,at91rm9200-aic", aic_of_init); diff --git a/drivers/irqchip/irq-atmel-aic5.c b/drivers/irqchip/irq-atmel-aic5.c new file mode 100644 index 000000000000..edb227081524 --- /dev/null +++ b/drivers/irqchip/irq-atmel-aic5.c @@ -0,0 +1,353 @@ +/* + * Atmel AT91 AIC5 (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.brezillon@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/init.h> +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/bitmap.h> +#include <linux/types.h> +#include <linux/irq.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/irqdomain.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/io.h> + +#include <asm/exception.h> +#include <asm/mach/irq.h> + +#include "irq-atmel-aic-common.h" +#include "irqchip.h" + +/* Number of irq lines managed by AIC */ +#define NR_AIC5_IRQS 128 + +#define AT91_AIC5_SSR 0x0 +#define AT91_AIC5_INTSEL_MSK (0x7f << 0) + +#define AT91_AIC5_SMR 0x4 + +#define AT91_AIC5_SVR 0x8 +#define AT91_AIC5_IVR 0x10 +#define AT91_AIC5_FVR 0x14 +#define AT91_AIC5_ISR 0x18 + +#define AT91_AIC5_IPR0 0x20 +#define AT91_AIC5_IPR1 0x24 +#define AT91_AIC5_IPR2 0x28 +#define AT91_AIC5_IPR3 0x2c +#define AT91_AIC5_IMR 0x30 +#define AT91_AIC5_CISR 0x34 + +#define AT91_AIC5_IECR 0x40 +#define AT91_AIC5_IDCR 0x44 +#define AT91_AIC5_ICCR 0x48 +#define AT91_AIC5_ISCR 0x4c +#define AT91_AIC5_EOICR 0x38 +#define AT91_AIC5_SPU 0x3c +#define AT91_AIC5_DCR 0x6c + +#define AT91_AIC5_FFER 0x50 +#define AT91_AIC5_FFDR 0x54 +#define AT91_AIC5_FFSR 0x58 + +static struct irq_domain *aic5_domain; + +static asmlinkage void __exception_irq_entry +aic5_handle(struct pt_regs *regs) +{ + struct irq_domain_chip_generic *dgc = aic5_domain->gc; + struct irq_chip_generic *gc = dgc->gc[0]; + u32 irqnr; + u32 irqstat; + + irqnr = irq_reg_readl(gc->reg_base + AT91_AIC5_IVR); + irqstat = irq_reg_readl(gc->reg_base + AT91_AIC5_ISR); + + irqnr = irq_find_mapping(aic5_domain, irqnr); + + if (!irqstat) + irq_reg_writel(0, gc->reg_base + AT91_AIC5_EOICR); + else + handle_IRQ(irqnr, regs); +} + +static void aic5_mask(struct irq_data *d) +{ + struct irq_domain *domain = d->domain; + struct irq_domain_chip_generic *dgc = domain->gc; + struct irq_chip_generic *gc = dgc->gc[0]; + + /* Disable interrupt on AIC5 */ + irq_gc_lock(gc); + irq_reg_writel(d->hwirq, gc->reg_base + AT91_AIC5_SSR); + irq_reg_writel(1, gc->reg_base + AT91_AIC5_IDCR); + gc->mask_cache &= ~d->mask; + irq_gc_unlock(gc); +} + +static void aic5_unmask(struct irq_data *d) +{ + struct irq_domain *domain = d->domain; + struct irq_domain_chip_generic *dgc = domain->gc; + struct irq_chip_generic *gc = dgc->gc[0]; + + /* Enable interrupt on AIC5 */ + irq_gc_lock(gc); + irq_reg_writel(d->hwirq, gc->reg_base + AT91_AIC5_SSR); + irq_reg_writel(1, gc->reg_base + AT91_AIC5_IECR); + gc->mask_cache |= d->mask; + irq_gc_unlock(gc); +} + +static int aic5_retrigger(struct irq_data *d) +{ + struct irq_domain *domain = d->domain; + struct irq_domain_chip_generic *dgc = domain->gc; + struct irq_chip_generic *gc = dgc->gc[0]; + + /* Enable interrupt on AIC5 */ + irq_gc_lock(gc); + irq_reg_writel(d->hwirq, gc->reg_base + AT91_AIC5_SSR); + irq_reg_writel(1, gc->reg_base + AT91_AIC5_ISCR); + irq_gc_unlock(gc); + + return 0; +} + +static int aic5_set_type(struct irq_data *d, unsigned type) +{ + struct irq_domain *domain = d->domain; + struct irq_domain_chip_generic *dgc = domain->gc; + struct irq_chip_generic *gc = dgc->gc[0]; + unsigned int smr; + int ret; + + irq_gc_lock(gc); + irq_reg_writel(d->hwirq, gc->reg_base + AT91_AIC5_SSR); + smr = irq_reg_readl(gc->reg_base + AT91_AIC5_SMR); + ret = aic_common_set_type(d, type, &smr); + if (!ret) + irq_reg_writel(smr, gc->reg_base + AT91_AIC5_SMR); + irq_gc_unlock(gc); + + return ret; +} + +#ifdef CONFIG_PM +static void aic5_suspend(struct irq_data *d) +{ + struct irq_domain *domain = d->domain; + struct irq_domain_chip_generic *dgc = domain->gc; + struct irq_chip_generic *bgc = dgc->gc[0]; + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + int i; + u32 mask; + + irq_gc_lock(bgc); + for (i = 0; i < dgc->irqs_per_chip; i++) { + mask = 1 << i; + if ((mask & gc->mask_cache) == (mask & gc->wake_active)) + continue; + + irq_reg_writel(i + gc->irq_base, + bgc->reg_base + AT91_AIC5_SSR); + if (mask & gc->wake_active) + irq_reg_writel(1, bgc->reg_base + AT91_AIC5_IECR); + else + irq_reg_writel(1, bgc->reg_base + AT91_AIC5_IDCR); + } + irq_gc_unlock(bgc); +} + +static void aic5_resume(struct irq_data *d) +{ + struct irq_domain *domain = d->domain; + struct irq_domain_chip_generic *dgc = domain->gc; + struct irq_chip_generic *bgc = dgc->gc[0]; + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + int i; + u32 mask; + + irq_gc_lock(bgc); + for (i = 0; i < dgc->irqs_per_chip; i++) { + mask = 1 << i; + if ((mask & gc->mask_cache) == (mask & gc->wake_active)) + continue; + + irq_reg_writel(i + gc->irq_base, + bgc->reg_base + AT91_AIC5_SSR); + if (mask & gc->mask_cache) + irq_reg_writel(1, bgc->reg_base + AT91_AIC5_IECR); + else + irq_reg_writel(1, bgc->reg_base + AT91_AIC5_IDCR); + } + irq_gc_unlock(bgc); +} + +static void aic5_pm_shutdown(struct irq_data *d) +{ + struct irq_domain *domain = d->domain; + struct irq_domain_chip_generic *dgc = domain->gc; + struct irq_chip_generic *bgc = dgc->gc[0]; + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + int i; + + irq_gc_lock(bgc); + for (i = 0; i < dgc->irqs_per_chip; i++) { + irq_reg_writel(i + gc->irq_base, + bgc->reg_base + AT91_AIC5_SSR); + irq_reg_writel(1, bgc->reg_base + AT91_AIC5_IDCR); + irq_reg_writel(1, bgc->reg_base + AT91_AIC5_ICCR); + } + irq_gc_unlock(bgc); +} +#else +#define aic5_suspend NULL +#define aic5_resume NULL +#define aic5_pm_shutdown NULL +#endif /* CONFIG_PM */ + +static void __init aic5_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(0, gc->reg_base + AT91_AIC5_EOICR); + + /* + * 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(0xffffffff, gc->reg_base + AT91_AIC5_SPU); + + /* No debugging in AIC: Debug (Protect) Control Register */ + irq_reg_writel(0, gc->reg_base + AT91_AIC5_DCR); + + /* Disable and clear all interrupts initially */ + for (i = 0; i < domain->revmap_size; i++) { + irq_reg_writel(i, gc->reg_base + AT91_AIC5_SSR); + irq_reg_writel(i, gc->reg_base + AT91_AIC5_SVR); + irq_reg_writel(1, gc->reg_base + AT91_AIC5_IDCR); + irq_reg_writel(1, gc->reg_base + AT91_AIC5_ICCR); + } +} + +static int aic5_irq_domain_xlate(struct irq_domain *d, + struct device_node *ctrlr, + const u32 *intspec, unsigned int intsize, + irq_hw_number_t *out_hwirq, + unsigned int *out_type) +{ + struct irq_domain_chip_generic *dgc = d->gc; + struct irq_chip_generic *gc; + unsigned smr; + int ret; + + if (!dgc) + return -EINVAL; + + ret = aic_common_irq_domain_xlate(d, ctrlr, intspec, intsize, + out_hwirq, out_type); + if (ret) + return ret; + + gc = dgc->gc[0]; + + irq_gc_lock(gc); + irq_reg_writel(*out_hwirq, gc->reg_base + AT91_AIC5_SSR); + smr = irq_reg_readl(gc->reg_base + AT91_AIC5_SMR); + ret = aic_common_set_priority(intspec[2], &smr); + if (!ret) + irq_reg_writel(intspec[2] | smr, gc->reg_base + AT91_AIC5_SMR); + irq_gc_unlock(gc); + + return ret; +} + +static const struct irq_domain_ops aic5_irq_ops = { + .map = irq_map_generic_chip, + .xlate = aic5_irq_domain_xlate, +}; + +static void __init sama5d3_aic_irq_fixup(struct device_node *root) +{ + aic_common_rtc_irq_fixup(root); +} + +static const struct of_device_id __initdata aic5_irq_fixups[] = { + { .compatible = "atmel,sama5d3", .data = sama5d3_aic_irq_fixup }, + { /* sentinel */ }, +}; + +static int __init aic5_of_init(struct device_node *node, + struct device_node *parent, + int nirqs) +{ + struct irq_chip_generic *gc; + struct irq_domain *domain; + int nchips; + int i; + + if (nirqs > NR_AIC5_IRQS) + return -EINVAL; + + if (aic5_domain) + return -EEXIST; + + domain = aic_common_of_init(node, &aic5_irq_ops, "atmel-aic5", + nirqs); + if (IS_ERR(domain)) + return PTR_ERR(domain); + + aic_common_irq_fixup(aic5_irq_fixups); + + aic5_domain = domain; + nchips = aic5_domain->revmap_size / 32; + for (i = 0; i < nchips; i++) { + gc = irq_get_domain_generic_chip(domain, i * 32); + + gc->chip_types[0].regs.eoi = AT91_AIC5_EOICR; + gc->chip_types[0].chip.irq_mask = aic5_mask; + gc->chip_types[0].chip.irq_unmask = aic5_unmask; + gc->chip_types[0].chip.irq_retrigger = aic5_retrigger; + gc->chip_types[0].chip.irq_set_type = aic5_set_type; + gc->chip_types[0].chip.irq_suspend = aic5_suspend; + gc->chip_types[0].chip.irq_resume = aic5_resume; + gc->chip_types[0].chip.irq_pm_shutdown = aic5_pm_shutdown; + } + + aic5_hw_init(domain); + set_handle_irq(aic5_handle); + + return 0; +} + +#define NR_SAMA5D3_IRQS 50 + +static int __init sama5d3_aic5_of_init(struct device_node *node, + struct device_node *parent) +{ + return aic5_of_init(node, parent, NR_SAMA5D3_IRQS); +} +IRQCHIP_DECLARE(sama5d3_aic5, "atmel,sama5d3-aic", sama5d3_aic5_of_init); diff --git a/drivers/irqchip/irq-crossbar.c b/drivers/irqchip/irq-crossbar.c index 3d15d16a7088..85c2985d8bcb 100644 --- a/drivers/irqchip/irq-crossbar.c +++ b/drivers/irqchip/irq-crossbar.c @@ -15,22 +15,31 @@ #include <linux/of_irq.h> #include <linux/slab.h> #include <linux/irqchip/arm-gic.h> +#include <linux/irqchip/irq-crossbar.h> #define IRQ_FREE -1 +#define IRQ_RESERVED -2 +#define IRQ_SKIP -3 #define GIC_IRQ_START 32 -/* +/** + * struct crossbar_device - crossbar device description * @int_max: maximum number of supported interrupts + * @safe_map: safe default value to initialize the crossbar + * @max_crossbar_sources: Maximum number of crossbar sources * @irq_map: array of interrupts to crossbar number mapping * @crossbar_base: crossbar base address * @register_offsets: offsets for each irq number + * @write: register write function pointer */ struct crossbar_device { uint int_max; + uint safe_map; + uint max_crossbar_sources; uint *irq_map; void __iomem *crossbar_base; int *register_offsets; - void (*write) (int, int); + void (*write)(int, int); }; static struct crossbar_device *cb; @@ -50,11 +59,22 @@ static inline void crossbar_writeb(int irq_no, int cb_no) writeb(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]); } +static inline int get_prev_map_irq(int cb_no) +{ + int i; + + for (i = cb->int_max - 1; i >= 0; i--) + if (cb->irq_map[i] == cb_no) + return i; + + return -ENODEV; +} + static inline int allocate_free_irq(int cb_no) { int i; - for (i = 0; i < cb->int_max; i++) { + for (i = cb->int_max - 1; i >= 0; i--) { if (cb->irq_map[i] == IRQ_FREE) { cb->irq_map[i] = cb_no; return i; @@ -64,19 +84,47 @@ static inline int allocate_free_irq(int cb_no) return -ENODEV; } +static inline bool needs_crossbar_write(irq_hw_number_t hw) +{ + int cb_no; + + if (hw > GIC_IRQ_START) { + cb_no = cb->irq_map[hw - GIC_IRQ_START]; + if (cb_no != IRQ_RESERVED && cb_no != IRQ_SKIP) + return true; + } + + return false; +} + static int crossbar_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw) { - cb->write(hw - GIC_IRQ_START, cb->irq_map[hw - GIC_IRQ_START]); + if (needs_crossbar_write(hw)) + cb->write(hw - GIC_IRQ_START, cb->irq_map[hw - GIC_IRQ_START]); + return 0; } +/** + * crossbar_domain_unmap - unmap a crossbar<->irq connection + * @d: domain of irq to unmap + * @irq: virq number + * + * We do not maintain a use count of total number of map/unmap + * calls for a particular irq to find out if a irq can be really + * unmapped. This is because unmap is called during irq_dispose_mapping(irq), + * after which irq is anyways unusable. So an explicit map has to be called + * after that. + */ static void crossbar_domain_unmap(struct irq_domain *d, unsigned int irq) { irq_hw_number_t hw = irq_get_irq_data(irq)->hwirq; - if (hw > GIC_IRQ_START) + if (needs_crossbar_write(hw)) { cb->irq_map[hw - GIC_IRQ_START] = IRQ_FREE; + cb->write(hw - GIC_IRQ_START, cb->safe_map); + } } static int crossbar_domain_xlate(struct irq_domain *d, @@ -85,18 +133,41 @@ static int crossbar_domain_xlate(struct irq_domain *d, unsigned long *out_hwirq, unsigned int *out_type) { - unsigned long ret; + int ret; + int req_num = intspec[1]; + int direct_map_num; + + if (req_num >= cb->max_crossbar_sources) { + direct_map_num = req_num - cb->max_crossbar_sources; + if (direct_map_num < cb->int_max) { + ret = cb->irq_map[direct_map_num]; + if (ret == IRQ_RESERVED || ret == IRQ_SKIP) { + /* We use the interrupt num as h/w irq num */ + ret = direct_map_num; + goto found; + } + } + + pr_err("%s: requested crossbar number %d > max %d\n", + __func__, req_num, cb->max_crossbar_sources); + return -EINVAL; + } - ret = allocate_free_irq(intspec[1]); + ret = get_prev_map_irq(req_num); + if (ret >= 0) + goto found; - if (IS_ERR_VALUE(ret)) + ret = allocate_free_irq(req_num); + + if (ret < 0) return ret; +found: *out_hwirq = ret + GIC_IRQ_START; return 0; } -const struct irq_domain_ops routable_irq_domain_ops = { +static const struct irq_domain_ops routable_irq_domain_ops = { .map = crossbar_domain_map, .unmap = crossbar_domain_unmap, .xlate = crossbar_domain_xlate @@ -104,22 +175,36 @@ const struct irq_domain_ops routable_irq_domain_ops = { static int __init crossbar_of_init(struct device_node *node) { - int i, size, max, reserved = 0, entry; + int i, size, max = 0, reserved = 0, entry; const __be32 *irqsr; + int ret = -ENOMEM; cb = kzalloc(sizeof(*cb), GFP_KERNEL); if (!cb) - return -ENOMEM; + return ret; cb->crossbar_base = of_iomap(node, 0); if (!cb->crossbar_base) - goto err1; + goto err_cb; + + of_property_read_u32(node, "ti,max-crossbar-sources", + &cb->max_crossbar_sources); + if (!cb->max_crossbar_sources) { + pr_err("missing 'ti,max-crossbar-sources' property\n"); + ret = -EINVAL; + goto err_base; + } of_property_read_u32(node, "ti,max-irqs", &max); - cb->irq_map = kzalloc(max * sizeof(int), GFP_KERNEL); + if (!max) { + pr_err("missing 'ti,max-irqs' property\n"); + ret = -EINVAL; + goto err_base; + } + cb->irq_map = kcalloc(max, sizeof(int), GFP_KERNEL); if (!cb->irq_map) - goto err2; + goto err_base; cb->int_max = max; @@ -137,15 +222,35 @@ static int __init crossbar_of_init(struct device_node *node) i, &entry); if (entry > max) { pr_err("Invalid reserved entry\n"); - goto err3; + ret = -EINVAL; + goto err_irq_map; + } + cb->irq_map[entry] = IRQ_RESERVED; + } + } + + /* Skip irqs hardwired to bypass the crossbar */ + irqsr = of_get_property(node, "ti,irqs-skip", &size); + if (irqsr) { + size /= sizeof(__be32); + + for (i = 0; i < size; i++) { + of_property_read_u32_index(node, + "ti,irqs-skip", + i, &entry); + if (entry > max) { + pr_err("Invalid skip entry\n"); + ret = -EINVAL; + goto err_irq_map; } - cb->irq_map[entry] = 0; + cb->irq_map[entry] = IRQ_SKIP; } } - cb->register_offsets = kzalloc(max * sizeof(int), GFP_KERNEL); + + cb->register_offsets = kcalloc(max, sizeof(int), GFP_KERNEL); if (!cb->register_offsets) - goto err3; + goto err_irq_map; of_property_read_u32(node, "ti,reg-size", &size); @@ -161,7 +266,8 @@ static int __init crossbar_of_init(struct device_node *node) break; default: pr_err("Invalid reg-size property\n"); - goto err4; + ret = -EINVAL; + goto err_reg_offset; break; } @@ -170,25 +276,37 @@ static int __init crossbar_of_init(struct device_node *node) * reserved irqs. so find and store the offsets once. */ for (i = 0; i < max; i++) { - if (!cb->irq_map[i]) + if (cb->irq_map[i] == IRQ_RESERVED) continue; cb->register_offsets[i] = reserved; reserved += size; } + of_property_read_u32(node, "ti,irqs-safe-map", &cb->safe_map); + /* Initialize the crossbar with safe map to start with */ + for (i = 0; i < max; i++) { + if (cb->irq_map[i] == IRQ_RESERVED || + cb->irq_map[i] == IRQ_SKIP) + continue; + + cb->write(i, cb->safe_map); + } + register_routable_domain_ops(&routable_irq_domain_ops); return 0; -err4: +err_reg_offset: kfree(cb->register_offsets); -err3: +err_irq_map: kfree(cb->irq_map); -err2: +err_base: iounmap(cb->crossbar_base); -err1: +err_cb: kfree(cb); - return -ENOMEM; + + cb = NULL; + return ret; } static const struct of_device_id crossbar_match[] __initconst = { diff --git a/drivers/irqchip/irq-nvic.c b/drivers/irqchip/irq-nvic.c index 70bdf6edb7bb..4ff0805fca01 100644 --- a/drivers/irqchip/irq-nvic.c +++ b/drivers/irqchip/irq-nvic.c @@ -49,14 +49,6 @@ nvic_handle_irq(irq_hw_number_t hwirq, struct pt_regs *regs) handle_IRQ(irq, regs); } -static void nvic_eoi(struct irq_data *d) -{ - /* - * This is a no-op as end of interrupt is signaled by the exception - * return sequence. - */ -} - static int __init nvic_of_init(struct device_node *node, struct device_node *parent) { @@ -102,7 +94,10 @@ static int __init nvic_of_init(struct device_node *node, gc->chip_types[0].regs.disable = NVIC_ICER; gc->chip_types[0].chip.irq_mask = irq_gc_mask_disable_reg; gc->chip_types[0].chip.irq_unmask = irq_gc_unmask_enable_reg; - gc->chip_types[0].chip.irq_eoi = nvic_eoi; + /* This is a no-op as end of interrupt is signaled by the + * exception return sequence. + */ + gc->chip_types[0].chip.irq_eoi = irq_gc_noop; /* disable interrupts */ writel_relaxed(~0, gc->reg_base + NVIC_ICER); diff --git a/drivers/irqchip/irq-or1k-pic.c b/drivers/irqchip/irq-or1k-pic.c new file mode 100644 index 000000000000..17ff033d9925 --- /dev/null +++ b/drivers/irqchip/irq-or1k-pic.c @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2010-2011 Jonas Bonn <jonas@southpole.se> + * Copyright (C) 2014 Stefan Kristansson <stefan.kristiansson@saunalahti.fi> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/irq.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/of_address.h> + +#include "irqchip.h" + +/* OR1K PIC implementation */ + +struct or1k_pic_dev { + struct irq_chip chip; + irq_flow_handler_t handle; + unsigned long flags; +}; + +/* + * We're a couple of cycles faster than the generic implementations with + * these 'fast' versions. + */ + +static void or1k_pic_mask(struct irq_data *data) +{ + mtspr(SPR_PICMR, mfspr(SPR_PICMR) & ~(1UL << data->hwirq)); +} + +static void or1k_pic_unmask(struct irq_data *data) +{ + mtspr(SPR_PICMR, mfspr(SPR_PICMR) | (1UL << data->hwirq)); +} + +static void or1k_pic_ack(struct irq_data *data) +{ + mtspr(SPR_PICSR, (1UL << data->hwirq)); +} + +static void or1k_pic_mask_ack(struct irq_data *data) +{ + mtspr(SPR_PICMR, mfspr(SPR_PICMR) & ~(1UL << data->hwirq)); + mtspr(SPR_PICSR, (1UL << data->hwirq)); +} + +/* + * There are two oddities with the OR1200 PIC implementation: + * i) LEVEL-triggered interrupts are latched and need to be cleared + * ii) the interrupt latch is cleared by writing a 0 to the bit, + * as opposed to a 1 as mandated by the spec + */ +static void or1k_pic_or1200_ack(struct irq_data *data) +{ + mtspr(SPR_PICSR, mfspr(SPR_PICSR) & ~(1UL << data->hwirq)); +} + +static void or1k_pic_or1200_mask_ack(struct irq_data *data) +{ + mtspr(SPR_PICMR, mfspr(SPR_PICMR) & ~(1UL << data->hwirq)); + mtspr(SPR_PICSR, mfspr(SPR_PICSR) & ~(1UL << data->hwirq)); +} + +static struct or1k_pic_dev or1k_pic_level = { + .chip = { + .name = "or1k-PIC-level", + .irq_unmask = or1k_pic_unmask, + .irq_mask = or1k_pic_mask, + .irq_mask_ack = or1k_pic_mask, + }, + .handle = handle_level_irq, + .flags = IRQ_LEVEL | IRQ_NOPROBE, +}; + +static struct or1k_pic_dev or1k_pic_edge = { + .chip = { + .name = "or1k-PIC-edge", + .irq_unmask = or1k_pic_unmask, + .irq_mask = or1k_pic_mask, + .irq_ack = or1k_pic_ack, + .irq_mask_ack = or1k_pic_mask_ack, + }, + .handle = handle_edge_irq, + .flags = IRQ_LEVEL | IRQ_NOPROBE, +}; + +static struct or1k_pic_dev or1k_pic_or1200 = { + .chip = { + .name = "or1200-PIC", + .irq_unmask = or1k_pic_unmask, + .irq_mask = or1k_pic_mask, + .irq_ack = or1k_pic_or1200_ack, + .irq_mask_ack = or1k_pic_or1200_mask_ack, + }, + .handle = handle_level_irq, + .flags = IRQ_LEVEL | IRQ_NOPROBE, +}; + +static struct irq_domain *root_domain; + +static inline int pic_get_irq(int first) +{ + int hwirq; + + hwirq = ffs(mfspr(SPR_PICSR) >> first); + if (!hwirq) + return NO_IRQ; + else + hwirq = hwirq + first - 1; + + return irq_find_mapping(root_domain, hwirq); +} + +static void or1k_pic_handle_irq(struct pt_regs *regs) +{ + int irq = -1; + + while ((irq = pic_get_irq(irq + 1)) != NO_IRQ) + handle_IRQ(irq, regs); +} + +static int or1k_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw) +{ + struct or1k_pic_dev *pic = d->host_data; + + irq_set_chip_and_handler(irq, &pic->chip, pic->handle); + irq_set_status_flags(irq, pic->flags); + + return 0; +} + +static const struct irq_domain_ops or1k_irq_domain_ops = { + .xlate = irq_domain_xlate_onecell, + .map = or1k_map, +}; + +/* + * This sets up the IRQ domain for the PIC built in to the OpenRISC + * 1000 CPU. This is the "root" domain as these are the interrupts + * that directly trigger an exception in the CPU. + */ +static int __init or1k_pic_init(struct device_node *node, + struct or1k_pic_dev *pic) +{ + /* Disable all interrupts until explicitly requested */ + mtspr(SPR_PICMR, (0UL)); + + root_domain = irq_domain_add_linear(node, 32, &or1k_irq_domain_ops, + pic); + + set_handle_irq(or1k_pic_handle_irq); + + return 0; +} + +static int __init or1k_pic_or1200_init(struct device_node *node, + struct device_node *parent) +{ + return or1k_pic_init(node, &or1k_pic_or1200); +} +IRQCHIP_DECLARE(or1k_pic_or1200, "opencores,or1200-pic", or1k_pic_or1200_init); +IRQCHIP_DECLARE(or1k_pic, "opencores,or1k-pic", or1k_pic_or1200_init); + +static int __init or1k_pic_level_init(struct device_node *node, + struct device_node *parent) +{ + return or1k_pic_init(node, &or1k_pic_level); +} +IRQCHIP_DECLARE(or1k_pic_level, "opencores,or1k-pic-level", + or1k_pic_level_init); + +static int __init or1k_pic_edge_init(struct device_node *node, + struct device_node *parent) +{ + return or1k_pic_init(node, &or1k_pic_edge); +} +IRQCHIP_DECLARE(or1k_pic_edge, "opencores,or1k-pic-edge", or1k_pic_edge_init); diff --git a/drivers/irqchip/spear-shirq.c b/drivers/irqchip/spear-shirq.c index 6ce6bd3441bf..9c145a7cb056 100644 --- a/drivers/irqchip/spear-shirq.c +++ b/drivers/irqchip/spear-shirq.c @@ -19,7 +19,6 @@ #include <linux/io.h> #include <linux/irq.h> #include <linux/irqdomain.h> -#include <linux/irqchip/spear-shirq.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_irq.h> @@ -27,20 +26,73 @@ #include "irqchip.h" -static DEFINE_SPINLOCK(lock); +/* + * struct spear_shirq: shared irq structure + * + * base: Base register address + * status_reg: Status register offset for chained interrupt handler + * mask_reg: Mask register offset for irq chip + * mask: Mask to apply to the status register + * virq_base: Base virtual interrupt number + * nr_irqs: Number of interrupts handled by this block + * offset: Bit offset of the first interrupt + * irq_chip: Interrupt controller chip used for this instance, + * if NULL group is disabled, but accounted + */ +struct spear_shirq { + void __iomem *base; + u32 status_reg; + u32 mask_reg; + u32 mask; + u32 virq_base; + u32 nr_irqs; + u32 offset; + struct irq_chip *irq_chip; +}; /* spear300 shared irq registers offsets and masks */ #define SPEAR300_INT_ENB_MASK_REG 0x54 #define SPEAR300_INT_STS_MASK_REG 0x58 +static DEFINE_RAW_SPINLOCK(shirq_lock); + +static void shirq_irq_mask(struct irq_data *d) +{ + struct spear_shirq *shirq = irq_data_get_irq_chip_data(d); + u32 val, shift = d->irq - shirq->virq_base + shirq->offset; + u32 __iomem *reg = shirq->base + shirq->mask_reg; + + raw_spin_lock(&shirq_lock); + val = readl(reg) & ~(0x1 << shift); + writel(val, reg); + raw_spin_unlock(&shirq_lock); +} + +static void shirq_irq_unmask(struct irq_data *d) +{ + struct spear_shirq *shirq = irq_data_get_irq_chip_data(d); + u32 val, shift = d->irq - shirq->virq_base + shirq->offset; + u32 __iomem *reg = shirq->base + shirq->mask_reg; + + raw_spin_lock(&shirq_lock); + val = readl(reg) | (0x1 << shift); + writel(val, reg); + raw_spin_unlock(&shirq_lock); +} + +static struct irq_chip shirq_chip = { + .name = "spear-shirq", + .irq_mask = shirq_irq_mask, + .irq_unmask = shirq_irq_unmask, +}; + static struct spear_shirq spear300_shirq_ras1 = { - .irq_nr = 9, - .irq_bit_off = 0, - .regs = { - .enb_reg = SPEAR300_INT_ENB_MASK_REG, - .status_reg = SPEAR300_INT_STS_MASK_REG, - .clear_reg = -1, - }, + .offset = 0, + .nr_irqs = 9, + .mask = ((0x1 << 9) - 1) << 0, + .irq_chip = &shirq_chip, + .status_reg = SPEAR300_INT_STS_MASK_REG, + .mask_reg = SPEAR300_INT_ENB_MASK_REG, }; static struct spear_shirq *spear300_shirq_blocks[] = { @@ -51,43 +103,35 @@ static struct spear_shirq *spear300_shirq_blocks[] = { #define SPEAR310_INT_STS_MASK_REG 0x04 static struct spear_shirq spear310_shirq_ras1 = { - .irq_nr = 8, - .irq_bit_off = 0, - .regs = { - .enb_reg = -1, - .status_reg = SPEAR310_INT_STS_MASK_REG, - .clear_reg = -1, - }, + .offset = 0, + .nr_irqs = 8, + .mask = ((0x1 << 8) - 1) << 0, + .irq_chip = &dummy_irq_chip, + .status_reg = SPEAR310_INT_STS_MASK_REG, }; static struct spear_shirq spear310_shirq_ras2 = { - .irq_nr = 5, - .irq_bit_off = 8, - .regs = { - .enb_reg = -1, - .status_reg = SPEAR310_INT_STS_MASK_REG, - .clear_reg = -1, - }, + .offset = 8, + .nr_irqs = 5, + .mask = ((0x1 << 5) - 1) << 8, + .irq_chip = &dummy_irq_chip, + .status_reg = SPEAR310_INT_STS_MASK_REG, }; static struct spear_shirq spear310_shirq_ras3 = { - .irq_nr = 1, - .irq_bit_off = 13, - .regs = { - .enb_reg = -1, - .status_reg = SPEAR310_INT_STS_MASK_REG, - .clear_reg = -1, - }, + .offset = 13, + .nr_irqs = 1, + .mask = ((0x1 << 1) - 1) << 13, + .irq_chip = &dummy_irq_chip, + .status_reg = SPEAR310_INT_STS_MASK_REG, }; static struct spear_shirq spear310_shirq_intrcomm_ras = { - .irq_nr = 3, - .irq_bit_off = 14, - .regs = { - .enb_reg = -1, - .status_reg = SPEAR310_INT_STS_MASK_REG, - .clear_reg = -1, - }, + .offset = 14, + .nr_irqs = 3, + .mask = ((0x1 << 3) - 1) << 14, + .irq_chip = &dummy_irq_chip, + .status_reg = SPEAR310_INT_STS_MASK_REG, }; static struct spear_shirq *spear310_shirq_blocks[] = { @@ -102,50 +146,34 @@ static struct spear_shirq *spear310_shirq_blocks[] = { #define SPEAR320_INT_CLR_MASK_REG 0x04 #define SPEAR320_INT_ENB_MASK_REG 0x08 -static struct spear_shirq spear320_shirq_ras1 = { - .irq_nr = 3, - .irq_bit_off = 7, - .regs = { - .enb_reg = -1, - .status_reg = SPEAR320_INT_STS_MASK_REG, - .clear_reg = SPEAR320_INT_CLR_MASK_REG, - .reset_to_clear = 1, - }, +static struct spear_shirq spear320_shirq_ras3 = { + .offset = 0, + .nr_irqs = 7, + .mask = ((0x1 << 7) - 1) << 0, }; -static struct spear_shirq spear320_shirq_ras2 = { - .irq_nr = 1, - .irq_bit_off = 10, - .regs = { - .enb_reg = -1, - .status_reg = SPEAR320_INT_STS_MASK_REG, - .clear_reg = SPEAR320_INT_CLR_MASK_REG, - .reset_to_clear = 1, - }, +static struct spear_shirq spear320_shirq_ras1 = { + .offset = 7, + .nr_irqs = 3, + .mask = ((0x1 << 3) - 1) << 7, + .irq_chip = &dummy_irq_chip, + .status_reg = SPEAR320_INT_STS_MASK_REG, }; -static struct spear_shirq spear320_shirq_ras3 = { - .irq_nr = 7, - .irq_bit_off = 0, - .invalid_irq = 1, - .regs = { - .enb_reg = SPEAR320_INT_ENB_MASK_REG, - .reset_to_enb = 1, - .status_reg = SPEAR320_INT_STS_MASK_REG, - .clear_reg = SPEAR320_INT_CLR_MASK_REG, - .reset_to_clear = 1, - }, +static struct spear_shirq spear320_shirq_ras2 = { + .offset = 10, + .nr_irqs = 1, + .mask = ((0x1 << 1) - 1) << 10, + .irq_chip = &dummy_irq_chip, + .status_reg = SPEAR320_INT_STS_MASK_REG, }; static struct spear_shirq spear320_shirq_intrcomm_ras = { - .irq_nr = 11, - .irq_bit_off = 11, - .regs = { - .enb_reg = -1, - .status_reg = SPEAR320_INT_STS_MASK_REG, - .clear_reg = SPEAR320_INT_CLR_MASK_REG, - .reset_to_clear = 1, - }, + .offset = 11, + .nr_irqs = 11, + .mask = ((0x1 << 11) - 1) << 11, + .irq_chip = &dummy_irq_chip, + .status_reg = SPEAR320_INT_STS_MASK_REG, }; static struct spear_shirq *spear320_shirq_blocks[] = { @@ -155,104 +183,46 @@ static struct spear_shirq *spear320_shirq_blocks[] = { &spear320_shirq_intrcomm_ras, }; -static void shirq_irq_mask_unmask(struct irq_data *d, bool mask) -{ - struct spear_shirq *shirq = irq_data_get_irq_chip_data(d); - u32 val, offset = d->irq - shirq->irq_base; - unsigned long flags; - - if (shirq->regs.enb_reg == -1) - return; - - spin_lock_irqsave(&lock, flags); - val = readl(shirq->base + shirq->regs.enb_reg); - - if (mask ^ shirq->regs.reset_to_enb) - val &= ~(0x1 << shirq->irq_bit_off << offset); - else - val |= 0x1 << shirq->irq_bit_off << offset; - - writel(val, shirq->base + shirq->regs.enb_reg); - spin_unlock_irqrestore(&lock, flags); - -} - -static void shirq_irq_mask(struct irq_data *d) -{ - shirq_irq_mask_unmask(d, 1); -} - -static void shirq_irq_unmask(struct irq_data *d) -{ - shirq_irq_mask_unmask(d, 0); -} - -static struct irq_chip shirq_chip = { - .name = "spear-shirq", - .irq_ack = shirq_irq_mask, - .irq_mask = shirq_irq_mask, - .irq_unmask = shirq_irq_unmask, -}; - static void shirq_handler(unsigned irq, struct irq_desc *desc) { - u32 i, j, val, mask, tmp; - struct irq_chip *chip; struct spear_shirq *shirq = irq_get_handler_data(irq); + u32 pend; - chip = irq_get_chip(irq); - chip->irq_ack(&desc->irq_data); - - mask = ((0x1 << shirq->irq_nr) - 1) << shirq->irq_bit_off; - while ((val = readl(shirq->base + shirq->regs.status_reg) & - mask)) { - - val >>= shirq->irq_bit_off; - for (i = 0, j = 1; i < shirq->irq_nr; i++, j <<= 1) { - - if (!(j & val)) - continue; + pend = readl(shirq->base + shirq->status_reg) & shirq->mask; + pend >>= shirq->offset; - generic_handle_irq(shirq->irq_base + i); + while (pend) { + int irq = __ffs(pend); - /* clear interrupt */ - if (shirq->regs.clear_reg == -1) - continue; - - tmp = readl(shirq->base + shirq->regs.clear_reg); - if (shirq->regs.reset_to_clear) - tmp &= ~(j << shirq->irq_bit_off); - else - tmp |= (j << shirq->irq_bit_off); - writel(tmp, shirq->base + shirq->regs.clear_reg); - } + pend &= ~(0x1 << irq); + generic_handle_irq(shirq->virq_base + irq); } - chip->irq_unmask(&desc->irq_data); } -static void __init spear_shirq_register(struct spear_shirq *shirq) +static void __init spear_shirq_register(struct spear_shirq *shirq, + int parent_irq) { int i; - if (shirq->invalid_irq) + if (!shirq->irq_chip) return; - irq_set_chained_handler(shirq->irq, shirq_handler); - for (i = 0; i < shirq->irq_nr; i++) { - irq_set_chip_and_handler(shirq->irq_base + i, - &shirq_chip, handle_simple_irq); - set_irq_flags(shirq->irq_base + i, IRQF_VALID); - irq_set_chip_data(shirq->irq_base + i, shirq); - } + irq_set_chained_handler(parent_irq, shirq_handler); + irq_set_handler_data(parent_irq, shirq); - irq_set_handler_data(shirq->irq, shirq); + for (i = 0; i < shirq->nr_irqs; i++) { + irq_set_chip_and_handler(shirq->virq_base + i, + shirq->irq_chip, handle_simple_irq); + set_irq_flags(shirq->virq_base + i, IRQF_VALID); + irq_set_chip_data(shirq->virq_base + i, shirq); + } } static int __init shirq_init(struct spear_shirq **shirq_blocks, int block_nr, struct device_node *np) { - int i, irq_base, hwirq = 0, irq_nr = 0; - static struct irq_domain *shirq_domain; + int i, parent_irq, virq_base, hwirq = 0, nr_irqs = 0; + struct irq_domain *shirq_domain; void __iomem *base; base = of_iomap(np, 0); @@ -262,15 +232,15 @@ static int __init shirq_init(struct spear_shirq **shirq_blocks, int block_nr, } for (i = 0; i < block_nr; i++) - irq_nr += shirq_blocks[i]->irq_nr; + nr_irqs += shirq_blocks[i]->nr_irqs; - irq_base = irq_alloc_descs(-1, 0, irq_nr, 0); - if (IS_ERR_VALUE(irq_base)) { + virq_base = irq_alloc_descs(-1, 0, nr_irqs, 0); + if (IS_ERR_VALUE(virq_base)) { pr_err("%s: irq desc alloc failed\n", __func__); goto err_unmap; } - shirq_domain = irq_domain_add_legacy(np, irq_nr, irq_base, 0, + shirq_domain = irq_domain_add_legacy(np, nr_irqs, virq_base, 0, &irq_domain_simple_ops, NULL); if (WARN_ON(!shirq_domain)) { pr_warn("%s: irq domain init failed\n", __func__); @@ -279,41 +249,41 @@ static int __init shirq_init(struct spear_shirq **shirq_blocks, int block_nr, for (i = 0; i < block_nr; i++) { shirq_blocks[i]->base = base; - shirq_blocks[i]->irq_base = irq_find_mapping(shirq_domain, + shirq_blocks[i]->virq_base = irq_find_mapping(shirq_domain, hwirq); - shirq_blocks[i]->irq = irq_of_parse_and_map(np, i); - spear_shirq_register(shirq_blocks[i]); - hwirq += shirq_blocks[i]->irq_nr; + parent_irq = irq_of_parse_and_map(np, i); + spear_shirq_register(shirq_blocks[i], parent_irq); + hwirq += shirq_blocks[i]->nr_irqs; } return 0; err_free_desc: - irq_free_descs(irq_base, irq_nr); + irq_free_descs(virq_base, nr_irqs); err_unmap: iounmap(base); return -ENXIO; } -int __init spear300_shirq_of_init(struct device_node *np, - struct device_node *parent) +static int __init spear300_shirq_of_init(struct device_node *np, + struct device_node *parent) { return shirq_init(spear300_shirq_blocks, ARRAY_SIZE(spear300_shirq_blocks), np); } IRQCHIP_DECLARE(spear300_shirq, "st,spear300-shirq", spear300_shirq_of_init); -int __init spear310_shirq_of_init(struct device_node *np, - struct device_node *parent) +static int __init spear310_shirq_of_init(struct device_node *np, + struct device_node *parent) { return shirq_init(spear310_shirq_blocks, ARRAY_SIZE(spear310_shirq_blocks), np); } IRQCHIP_DECLARE(spear310_shirq, "st,spear310-shirq", spear310_shirq_of_init); -int __init spear320_shirq_of_init(struct device_node *np, - struct device_node *parent) +static int __init spear320_shirq_of_init(struct device_node *np, + struct device_node *parent) { return shirq_init(spear320_shirq_blocks, ARRAY_SIZE(spear320_shirq_blocks), np); |