summaryrefslogtreecommitdiff
path: root/drivers/irqchip
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/irqchip')
-rw-r--r--drivers/irqchip/Kconfig18
-rw-r--r--drivers/irqchip/Makefile3
-rw-r--r--drivers/irqchip/irq-atmel-aic-common.c254
-rw-r--r--drivers/irqchip/irq-atmel-aic-common.h39
-rw-r--r--drivers/irqchip/irq-atmel-aic.c262
-rw-r--r--drivers/irqchip/irq-atmel-aic5.c353
-rw-r--r--drivers/irqchip/irq-crossbar.c168
-rw-r--r--drivers/irqchip/irq-nvic.c13
-rw-r--r--drivers/irqchip/irq-or1k-pic.c182
-rw-r--r--drivers/irqchip/spear-shirq.c304
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);