Skip to contentSkip to conversation

drivers/irqchip: add support for Socionext Synquacer EXIU controller

RFC PATCH New Ard Biesheuvel 2 participants 3 messages Oct 30, 2017 – Oct 31, 2017

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

RFC PATCH 1/1 drivers/irqchip: add support for Socionext Synquacer EXIU controller

The Socionext Synquacer SoC has an external interrupt unit (EXIU) that forwards a block of 32 input lines to 32 adjacent GICv3 SPIs.

The EXIU has per-interrupt level/edge and polarity controls, and mask bits that keep the outgoing lines de-asserted, even though the controller may still latch occurring interrupt conditions while the line is masked.

---

If this doesn't look to insane, I will follow up with a DT binding as well.

    exiu: exiu at 510c0000 {
        compatible = "socionext,sc2a11-exiu";
        reg = <0x0 0x510c0000 0x0 0x20>;
        interrupt-controller;
        interrupt-parent = <&gic>;
        #interrupt-cells = <3>;
        spi-base = <112>;
    };
 arch/arm64/Kconfig.platforms   |   3 +
 drivers/irqchip/Makefile       |   1 +
 drivers/irqchip/irq-sni-exiu.c | 230 ++++++++++++++++++++
 3 files changed, 234 insertions(+)
arch/arm64/Kconfig.platforms +3 -0
index 6b54ee8c1262..d0fc6c2a32e7 100644
@@ -161,6 +161,9 @@ config ARCH_SEATTLE
 config ARCH_SHMOBILE
 	bool
 
+config ARCH_SYNQUACER
+	bool "Socionext Synquacer SoC Family"
+
 config ARCH_RENESAS
 	bool "Renesas SoC Platforms"
 	select ARCH_SHMOBILE
drivers/irqchip/Makefile +1 -0
index 845abc107ad5..f6825aa30098 100644
@@ -79,3 +79,4 @@ obj-$(CONFIG_ARCH_ASPEED)		+= irq-aspeed-vic.o irq-aspeed-i2c-ic.o
 obj-$(CONFIG_STM32_EXTI) 		+= irq-stm32-exti.o
 obj-$(CONFIG_QCOM_IRQ_COMBINER)		+= qcom-irq-combiner.o
 obj-$(CONFIG_IRQ_UNIPHIER_AIDET)	+= irq-uniphier-aidet.o
+obj-$(CONFIG_ARCH_SYNQUACER)		+= irq-sni-exiu.c
drivers/irqchip/irq-sni-exiu.c new file +230 -0

230 lines not on this page · raw message

Signature · 1 line
2.11.0
#1 lore raw

RFC PATCH 1/1 drivers/irqchip: add support for Socionext Synquacer EXIU controller

Quoted 2 lines · Ard Biesheuvel

The Socionext Synquacer SoC has an external interrupt unit (EXIU) that forwards a block of 32 input lines to 32 adjacent GICv3 SPIs.

Hooray, yet another one! ;-)

Quoted 148 lines

The EXIU has per-interrupt level/edge and polarity controls, and mask bits that keep the outgoing lines de-asserted, even though the controller may still latch occurring interrupt conditions while the line is masked.

---

If this doesn't look to insane, I will follow up with a DT binding as well.

    exiu: exiu at 510c0000 {
        compatible = "socionext,sc2a11-exiu";
        reg = <0x0 0x510c0000 0x0 0x20>;
        interrupt-controller;
        interrupt-parent = <&gic>;
        #interrupt-cells = <3>;
        spi-base = <112>;
    };
 arch/arm64/Kconfig.platforms   |   3 +
 drivers/irqchip/Makefile       |   1 +
 drivers/irqchip/irq-sni-exiu.c | 230 ++++++++++++++++++++
 3 files changed, 234 insertions(+)
arch/arm64/Kconfig.platforms +3 -0
index 6b54ee8c1262..d0fc6c2a32e7 100644
@@ -161,6 +161,9 @@ config ARCH_SEATTLE
 config ARCH_SHMOBILE
 	bool
 
+config ARCH_SYNQUACER
+	bool "Socionext Synquacer SoC Family"
+
 config ARCH_RENESAS
 	bool "Renesas SoC Platforms"
 	select ARCH_SHMOBILE
drivers/irqchip/Makefile +1 -0
index 845abc107ad5..f6825aa30098 100644
@@ -79,3 +79,4 @@ obj-$(CONFIG_ARCH_ASPEED)		+= irq-aspeed-vic.o irq-aspeed-i2c-ic.o
 obj-$(CONFIG_STM32_EXTI) 		+= irq-stm32-exti.o
 obj-$(CONFIG_QCOM_IRQ_COMBINER)		+= qcom-irq-combiner.o
 obj-$(CONFIG_IRQ_UNIPHIER_AIDET)	+= irq-uniphier-aidet.o
+obj-$(CONFIG_ARCH_SYNQUACER)		+= irq-sni-exiu.c
drivers/irqchip/irq-sni-exiu.c new file +94 -0
new file mode 100644
index 000000000000..0ab812523a11
@@ -0,0 +1,230 @@
+/*
+ * Driver for Socionext External Interrupt Unit (EXIU)
+ *
+ * Copyright (c) 2017 Linaro, Ltd. <[email protected]>
+ *
+ * Based on irq-tegra.c:
+ *   Copyright (C) 2011 Google, Inc.
+ *   Copyright (C) 2010,2013, NVIDIA Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqdomain.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+#define NUM_IRQS	32
+
+#define EIMASK		0x00
+#define EISRCSEL	0x04
+#define EIREQSTA	0x08
+#define EIRAWREQSTA	0x0C
+#define EIREQCLR	0x10
+#define EILVL		0x14
+#define EIEDG		0x18
+#define EISIR		0x1C
+
+struct exiu_irq_data {
+	void __iomem	*base;
+	u32		spi_base;
+};
+
+static void exiu_irq_eoi(struct irq_data *d)
+{
+	struct exiu_irq_data *data = irq_data_get_irq_chip_data(d);
+
+	writel(BIT(d->hwirq), data->base + EIREQCLR);
+	irq_chip_eoi_parent(d);
+}
+
+static void exiu_irq_mask(struct irq_data *d)
+{
+	struct exiu_irq_data *data = irq_data_get_irq_chip_data(d);
+	u32 val;
+
+	val = readl_relaxed(data->base + EIMASK) | BIT(d->hwirq);
+	writel_relaxed(val, data->base + EIMASK);
+	irq_chip_mask_parent(d);
+}
+
+static void exiu_irq_unmask(struct irq_data *d)
+{
+	struct exiu_irq_data *data = irq_data_get_irq_chip_data(d);
+	u32 val;
+
+	val = readl_relaxed(data->base + EIMASK) & ~BIT(d->hwirq);
+	writel_relaxed(val, data->base + EIMASK);
+	irq_chip_unmask_parent(d);
+}
+
+static void exiu_irq_enable(struct irq_data *d)
+{
+	struct exiu_irq_data *data = irq_data_get_irq_chip_data(d);
+	u32 val;
+
+	/* clear interrupts that were latched while disabled */
+	writel_relaxed(BIT(d->hwirq), data->base + EIREQCLR);
+
+	val = readl_relaxed(data->base + EIMASK) & ~BIT(d->hwirq);
+	writel_relaxed(val, data->base + EIMASK);
+	irq_chip_enable_parent(d);
+}
+
+static int exiu_irq_set_type(struct irq_data *d, unsigned int type)
+{
+	struct exiu_irq_data *data = irq_data_get_irq_chip_data(d);
+	u32 val;
+
+	val = readl_relaxed(data->base + EILVL);
+	if (type == IRQ_TYPE_EDGE_RISING || type == IRQ_TYPE_LEVEL_HIGH)
+		val |= BIT(d->hwirq);
+	else
+		val &= ~BIT(d->hwirq);
+	writel_relaxed(val, data->base + EILVL);
+

drivers/irqchip/irq-sni-exiu.c line 104

Quoted 10 lines
drivers/irqchip/irq-sni-exiu.c new file +10 -0
+	val = readl_relaxed(data->base + EIEDG);
+	if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_LEVEL_HIGH)
+		val &= ~BIT(d->hwirq);
+	else
+		val |= BIT(d->hwirq);
+	writel_relaxed(val, data->base + EIEDG);
+
+	writel_relaxed(BIT(d->hwirq), data->base + EIREQCLR);
+
+	return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH);

This is a bit confusing. Does the controller always present a level interrupt to the GIC, no matter if the input is actually edge? If so, that would definitely deserve a comment.

Also, can you change the trigger without disabling the interrupt first? Are you guaranteed that you won't see spurious interrupts being generated on the GIC input? It is probably not a big deal, but I thought I'd ask...

Quoted 45 lines
drivers/irqchip/irq-sni-exiu.c new file +45 -0
+}
+
+static struct irq_chip exiu_irq_chip = {
+	.name			= "EXIU",
+	.irq_eoi		= exiu_irq_eoi,
+	.irq_enable		= exiu_irq_enable,
+	.irq_mask		= exiu_irq_mask,
+	.irq_unmask		= exiu_irq_unmask,
+	.irq_set_type		= exiu_irq_set_type,
+	.irq_set_affinity	= irq_chip_set_affinity_parent,
+	.flags			= IRQCHIP_SET_TYPE_MASKED |
+				  IRQCHIP_SKIP_SET_WAKE |
+				  IRQCHIP_EOI_THREADED |
+				  IRQCHIP_MASK_ON_SUSPEND,
+};
+
+static int exiu_domain_translate(struct irq_domain *domain,
+				 struct irq_fwspec *fwspec,
+				 unsigned long *hwirq,
+				 unsigned int *type)
+{
+	struct exiu_irq_data *info = domain->host_data;
+
+	if (is_of_node(fwspec->fwnode)) {
+		if (fwspec->param_count != 3)
+			return -EINVAL;
+
+		if (fwspec->param[0] != GIC_SPI)
+			return -EINVAL; /* No PPI should point to this domain */
+
+		*hwirq = fwspec->param[1] - info->spi_base;
+		*type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int exiu_domain_alloc(struct irq_domain *domain, unsigned int virq,
+			     unsigned int nr_irqs, void *data)
+{
+	struct irq_fwspec *fwspec = data;
+	struct irq_fwspec parent_fwspec;
+	struct exiu_irq_data *info = domain->host_data;
+	irq_hw_number_t hwirq;
+	unsigned int i;

drivers/irqchip/irq-sni-exiu.c line 159

Quoted 10 lines
drivers/irqchip/irq-sni-exiu.c new file +10 -0
+
+	if (fwspec->param_count != 3)
+		return -EINVAL;	/* Not GIC compliant */
+	if (fwspec->param[0] != GIC_SPI)
+		return -EINVAL;	/* No PPI should point to this domain */
+
+	hwirq = fwspec->param[1] - info->spi_base;
+	for (i = 0; i < nr_irqs; i++)
+		irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
+					      &exiu_irq_chip, info);

You should be able to drop this loop. Only MSI chips end-up being the target of multiple interrupts at the same time (multi-MSI support). You can replace it with a WARN_ON(nr_irqs != 1).

Quoted 61 lines
drivers/irqchip/irq-sni-exiu.c new file +61 -0
+
+	parent_fwspec = *fwspec;
+	parent_fwspec.fwnode = domain->parent->fwnode;
+	return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs,
+					    &parent_fwspec);
+}
+
+static const struct irq_domain_ops exiu_domain_ops = {
+	.translate	= exiu_domain_translate,
+	.alloc		= exiu_domain_alloc,
+	.free		= irq_domain_free_irqs_common,
+};
+
+static int __init exiu_init(struct device_node *node,
+			    struct device_node *parent)
+{
+	struct irq_domain *parent_domain, *domain;
+	struct exiu_irq_data *data;
+	int err;
+
+	if (!parent) {
+		pr_err("%pOF: no parent, giving up\n", node);
+		return -ENODEV;
+	}
+
+	parent_domain = irq_find_host(parent);
+	if (!parent_domain) {
+		pr_err("%pOF: unable to obtain parent domain\n", node);
+		return -ENXIO;
+	}
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	if (of_property_read_u32(node, "spi-base", &data->spi_base)) {
+		pr_err("%pOF: failed to parse 'spi-base' property\n", node);
+		err = -ENODEV;
+		goto out_free;
+	}
+
+	data->base = of_iomap(node, 0);
+	if (IS_ERR(data->base)) {
+		err = PTR_ERR(data->base);
+		goto out_free;
+	}
+
+	/* clear and mask all interrupts */
+	writel_relaxed(0xFFFFFFFF, data->base + EIREQCLR);
+	writel_relaxed(0xFFFFFFFF, data->base + EIMASK);
+
+	domain = irq_domain_add_hierarchy(parent_domain, 0, NUM_IRQS, node,
+					  &exiu_domain_ops, data);
+	if (!domain) {
+		pr_err("%pOF: failed to allocated domain\n", node);
+		err = -ENOMEM;
+		goto out_unmap;
+	}
+
+	pr_info("%pOF: %d interrupts forwarded to %pOF\n", node, NUM_IRQS,
+		parent);

drivers/irqchip/irq-sni-exiu.c line 230

Quoted 10 lines
drivers/irqchip/irq-sni-exiu.c new file +10 -0
+
+	return 0;
+
+out_unmap:
+	iounmap(data->base);
+out_free:
+	kfree(data);
+	return err;
+}
+IRQCHIP_DECLARE(exiu, "socionext,sc2a11-exiu", exiu_init);

Otherwise, looks pretty good.

Thanks,

	M.
Signature · 1 line
Jazz is not dead. It just smells funny.
#2 lore raw

RFC PATCH 1/1 drivers/irqchip: add support for Socionext Synquacer EXIU controller

Quoted 6 lines · Marc Zyngier
Quoted 2 lines · Ard Biesheuvel

The Socionext Synquacer SoC has an external interrupt unit (EXIU) that forwards a block of 32 input lines to 32 adjacent GICv3 SPIs.

Hooray, yet another one! ;-)

No comment.

Quoted 154 lines
Quoted 154 lines

The EXIU has per-interrupt level/edge and polarity controls, and mask bits that keep the outgoing lines de-asserted, even though the controller may still latch occurring interrupt conditions while the line is masked.

---

If this doesn't look to insane, I will follow up with a DT binding as well.

    exiu: exiu at 510c0000 {
        compatible = "socionext,sc2a11-exiu";
        reg = <0x0 0x510c0000 0x0 0x20>;
        interrupt-controller;
        interrupt-parent = <&gic>;
        #interrupt-cells = <3>;
        spi-base = <112>;
    };
 arch/arm64/Kconfig.platforms   |   3 +
 drivers/irqchip/Makefile       |   1 +
 drivers/irqchip/irq-sni-exiu.c | 230 ++++++++++++++++++++
 3 files changed, 234 insertions(+)
arch/arm64/Kconfig.platforms +3 -0
index 6b54ee8c1262..d0fc6c2a32e7 100644
@@ -161,6 +161,9 @@ config ARCH_SEATTLE
 config ARCH_SHMOBILE
      bool
 
+config ARCH_SYNQUACER
+     bool "Socionext Synquacer SoC Family"
+
 config ARCH_RENESAS
      bool "Renesas SoC Platforms"
      select ARCH_SHMOBILE
drivers/irqchip/Makefile +1 -0
index 845abc107ad5..f6825aa30098 100644
@@ -79,3 +79,4 @@ obj-$(CONFIG_ARCH_ASPEED)           += irq-aspeed-vic.o irq-aspeed-i2c-ic.o
 obj-$(CONFIG_STM32_EXTI)             += irq-stm32-exti.o
 obj-$(CONFIG_QCOM_IRQ_COMBINER)              += qcom-irq-combiner.o
 obj-$(CONFIG_IRQ_UNIPHIER_AIDET)     += irq-uniphier-aidet.o
+obj-$(CONFIG_ARCH_SYNQUACER)         += irq-sni-exiu.c
drivers/irqchip/irq-sni-exiu.c new file +100 -0
new file mode 100644
index 000000000000..0ab812523a11
@@ -0,0 +1,230 @@
+/*
+ * Driver for Socionext External Interrupt Unit (EXIU)
+ *
+ * Copyright (c) 2017 Linaro, Ltd. <[email protected]>
+ *
+ * Based on irq-tegra.c:
+ *   Copyright (C) 2011 Google, Inc.
+ *   Copyright (C) 2010,2013, NVIDIA Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqdomain.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+#define NUM_IRQS     32
+
+#define EIMASK               0x00
+#define EISRCSEL     0x04
+#define EIREQSTA     0x08
+#define EIRAWREQSTA  0x0C
+#define EIREQCLR     0x10
+#define EILVL                0x14
+#define EIEDG                0x18
+#define EISIR                0x1C
+
+struct exiu_irq_data {
+     void __iomem    *base;
+     u32             spi_base;
+};
+
+static void exiu_irq_eoi(struct irq_data *d)
+{
+     struct exiu_irq_data *data = irq_data_get_irq_chip_data(d);
+
+     writel(BIT(d->hwirq), data->base + EIREQCLR);
+     irq_chip_eoi_parent(d);
+}
+
+static void exiu_irq_mask(struct irq_data *d)
+{
+     struct exiu_irq_data *data = irq_data_get_irq_chip_data(d);
+     u32 val;
+
+     val = readl_relaxed(data->base + EIMASK) | BIT(d->hwirq);
+     writel_relaxed(val, data->base + EIMASK);
+     irq_chip_mask_parent(d);
+}
+
+static void exiu_irq_unmask(struct irq_data *d)
+{
+     struct exiu_irq_data *data = irq_data_get_irq_chip_data(d);
+     u32 val;
+
+     val = readl_relaxed(data->base + EIMASK) & ~BIT(d->hwirq);
+     writel_relaxed(val, data->base + EIMASK);
+     irq_chip_unmask_parent(d);
+}
+
+static void exiu_irq_enable(struct irq_data *d)
+{
+     struct exiu_irq_data *data = irq_data_get_irq_chip_data(d);
+     u32 val;
+
+     /* clear interrupts that were latched while disabled */
+     writel_relaxed(BIT(d->hwirq), data->base + EIREQCLR);
+
+     val = readl_relaxed(data->base + EIMASK) & ~BIT(d->hwirq);
+     writel_relaxed(val, data->base + EIMASK);
+     irq_chip_enable_parent(d);
+}
+
+static int exiu_irq_set_type(struct irq_data *d, unsigned int type)
+{
+     struct exiu_irq_data *data = irq_data_get_irq_chip_data(d);
+     u32 val;
+
+     val = readl_relaxed(data->base + EILVL);
+     if (type == IRQ_TYPE_EDGE_RISING || type == IRQ_TYPE_LEVEL_HIGH)
+             val |= BIT(d->hwirq);
+     else
+             val &= ~BIT(d->hwirq);
+     writel_relaxed(val, data->base + EILVL);
+
+     val = readl_relaxed(data->base + EIEDG);
+     if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_LEVEL_HIGH)
+             val &= ~BIT(d->hwirq);
+     else
+             val |= BIT(d->hwirq);
+     writel_relaxed(val, data->base + EIEDG);
Quoted 9 lines

drivers/irqchip/irq-sni-exiu.c line 104

Quoted 4 lines
drivers/irqchip/irq-sni-exiu.c new file +4 -0
+
+     writel_relaxed(BIT(d->hwirq), data->base + EIREQCLR);
+
+     return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH);

This is a bit confusing. Does the controller always present a level interrupt to the GIC, no matter if the input is actually edge? If so, that would definitely deserve a comment.

Yes, as far as I can tell. An edge interrupt drives the outgoing line high until you clear it by writing a 1 to the EIREQCLR register.

Quoted 5 lines

Also, can you change the trigger without disabling the interrupt first? Are you guaranteed that you won't see spurious interrupts being generated on the GIC input? It is probably not a big deal, but I thought I'd ask...

No, but I thought that was what IRQCHIP_SET_TYPE_MASKED is for?

Quoted 51 lines
Quoted 51 lines
drivers/irqchip/irq-sni-exiu.c new file +51 -0
+}
+
+static struct irq_chip exiu_irq_chip = {
+     .name                   = "EXIU",
+     .irq_eoi                = exiu_irq_eoi,
+     .irq_enable             = exiu_irq_enable,
+     .irq_mask               = exiu_irq_mask,
+     .irq_unmask             = exiu_irq_unmask,
+     .irq_set_type           = exiu_irq_set_type,
+     .irq_set_affinity       = irq_chip_set_affinity_parent,
+     .flags                  = IRQCHIP_SET_TYPE_MASKED |
+                               IRQCHIP_SKIP_SET_WAKE |
+                               IRQCHIP_EOI_THREADED |
+                               IRQCHIP_MASK_ON_SUSPEND,
+};
+
+static int exiu_domain_translate(struct irq_domain *domain,
+                              struct irq_fwspec *fwspec,
+                              unsigned long *hwirq,
+                              unsigned int *type)
+{
+     struct exiu_irq_data *info = domain->host_data;
+
+     if (is_of_node(fwspec->fwnode)) {
+             if (fwspec->param_count != 3)
+                     return -EINVAL;
+
+             if (fwspec->param[0] != GIC_SPI)
+                     return -EINVAL; /* No PPI should point to this domain */
+
+             *hwirq = fwspec->param[1] - info->spi_base;
+             *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
+             return 0;
+     }
+     return -EINVAL;
+}
+
+static int exiu_domain_alloc(struct irq_domain *domain, unsigned int virq,
+                          unsigned int nr_irqs, void *data)
+{
+     struct irq_fwspec *fwspec = data;
+     struct irq_fwspec parent_fwspec;
+     struct exiu_irq_data *info = domain->host_data;
+     irq_hw_number_t hwirq;
+     unsigned int i;
+
+     if (fwspec->param_count != 3)
+             return -EINVAL; /* Not GIC compliant */
+     if (fwspec->param[0] != GIC_SPI)
+             return -EINVAL; /* No PPI should point to this domain */
+
Quoted 9 lines

drivers/irqchip/irq-sni-exiu.c line 159

Quoted 4 lines
drivers/irqchip/irq-sni-exiu.c new file +4 -0
+     hwirq = fwspec->param[1] - info->spi_base;
+     for (i = 0; i < nr_irqs; i++)
+             irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
+                                           &exiu_irq_chip, info);

You should be able to drop this loop. Only MSI chips end-up being the target of multiple interrupts at the same time (multi-MSI support). You can replace it with a WARN_ON(nr_irqs != 1).

Ah ok, I didn't know that. Will drop it.

Quoted 65 lines
Quoted 65 lines
drivers/irqchip/irq-sni-exiu.c new file +65 -0
+
+     parent_fwspec = *fwspec;
+     parent_fwspec.fwnode = domain->parent->fwnode;
+     return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs,
+                                         &parent_fwspec);
+}
+
+static const struct irq_domain_ops exiu_domain_ops = {
+     .translate      = exiu_domain_translate,
+     .alloc          = exiu_domain_alloc,
+     .free           = irq_domain_free_irqs_common,
+};
+
+static int __init exiu_init(struct device_node *node,
+                         struct device_node *parent)
+{
+     struct irq_domain *parent_domain, *domain;
+     struct exiu_irq_data *data;
+     int err;
+
+     if (!parent) {
+             pr_err("%pOF: no parent, giving up\n", node);
+             return -ENODEV;
+     }
+
+     parent_domain = irq_find_host(parent);
+     if (!parent_domain) {
+             pr_err("%pOF: unable to obtain parent domain\n", node);
+             return -ENXIO;
+     }
+
+     data = kzalloc(sizeof(*data), GFP_KERNEL);
+     if (!data)
+             return -ENOMEM;
+
+     if (of_property_read_u32(node, "spi-base", &data->spi_base)) {
+             pr_err("%pOF: failed to parse 'spi-base' property\n", node);
+             err = -ENODEV;
+             goto out_free;
+     }
+
+     data->base = of_iomap(node, 0);
+     if (IS_ERR(data->base)) {
+             err = PTR_ERR(data->base);
+             goto out_free;
+     }
+
+     /* clear and mask all interrupts */
+     writel_relaxed(0xFFFFFFFF, data->base + EIREQCLR);
+     writel_relaxed(0xFFFFFFFF, data->base + EIMASK);
+
+     domain = irq_domain_add_hierarchy(parent_domain, 0, NUM_IRQS, node,
+                                       &exiu_domain_ops, data);
+     if (!domain) {
+             pr_err("%pOF: failed to allocated domain\n", node);
+             err = -ENOMEM;
+             goto out_unmap;
+     }
+
+     pr_info("%pOF: %d interrupts forwarded to %pOF\n", node, NUM_IRQS,
+             parent);
+
+     return 0;
+
+out_unmap:
Quoted 9 lines

drivers/irqchip/irq-sni-exiu.c line 230

Quoted 6 lines
drivers/irqchip/irq-sni-exiu.c new file +6 -0
+     iounmap(data->base);
+out_free:
+     kfree(data);
+     return err;
+}
+IRQCHIP_DECLARE(exiu, "socionext,sc2a11-exiu", exiu_init);

Otherwise, looks pretty good.

Thanks!