[PATCH] irqchip: Add support for Sunplus SP7021 interrupt controller

qinjian qinjian at cqplus1.com
Fri Oct 22 01:23:28 PDT 2021


Add interrupt driver for Sunplus SP7021 SoC.

Signed-off-by: qinjian <qinjian at cqplus1.com>
---
 MAINTAINERS                       |   1 +
 drivers/irqchip/Kconfig           |   9 +
 drivers/irqchip/Makefile          |   1 +
 drivers/irqchip/irq-sp7021-intc.c | 557 ++++++++++++++++++++++++++++++
 4 files changed, 568 insertions(+)
 create mode 100644 drivers/irqchip/irq-sp7021-intc.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 123616bb9..65cd295e9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2663,6 +2663,7 @@ F:	Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
 F:	Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
 F:	Documentation/devicetree/bindings/interrupt-controller/sunplus,sp7021-intc.yaml
 F:	Documentation/devicetree/bindings/reset/sunplus,reset.yaml
+F:	drivers/irqchip/irq-sp7021-intc.c
 F:	include/dt-bindings/clock/sp-sp7021.h
 F:	include/dt-bindings/interrupt-controller/sp7021-intc.h
 F:	include/dt-bindings/reset/sp-sp7021.h
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index aca7b595c..52bd16363 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -602,4 +602,13 @@ config APPLE_AIC
 	  Support for the Apple Interrupt Controller found on Apple Silicon SoCs,
 	  such as the M1.
 
+config SUNPLUS_SP7021_INTC
+	bool "Sunplus SP7021 interrupt controller"
+	help
+	  Support for the Sunplus SP7021 Interrupt Controller IP core.
+	  This is used as a primary controller with SP7021 ChipP and can also
+	  be used as a secondary chained controller on SP7021 ChipC.
+
+	  If you don't know what to do here, say Y.
+
 endmenu
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index f88cbf36a..75411f654 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -116,3 +116,4 @@ obj-$(CONFIG_MACH_REALTEK_RTL)		+= irq-realtek-rtl.o
 obj-$(CONFIG_WPCM450_AIC)		+= irq-wpcm450-aic.o
 obj-$(CONFIG_IRQ_IDT3243X)		+= irq-idt3243x.o
 obj-$(CONFIG_APPLE_AIC)			+= irq-apple-aic.o
+obj-$(CONFIG_SUNPLUS_SP7021_INTC)	+= irq-sp7021-intc.o
diff --git a/drivers/irqchip/irq-sp7021-intc.c b/drivers/irqchip/irq-sp7021-intc.c
new file mode 100644
index 000000000..2d26681d3
--- /dev/null
+++ b/drivers/irqchip/irq-sp7021-intc.c
@@ -0,0 +1,557 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) Sunplus Technology Co., Ltd.
+ *       All rights reserved.
+ */
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/io.h>
+#ifdef CONFIG_ARM
+#include <asm/exception.h>
+#include <asm/mach/irq.h>
+#else
+#define __exception_irq_entry
+#endif
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <dt-bindings/interrupt-controller/sp7021-intc.h>
+
+#if defined(CONFIG_SOC_SP7021)
+#define SUPPORT_IRQ_GRP_REG
+#endif
+
+#define SP_INTC_HWIRQ_MIN     0
+#define SP_INTC_HWIRQ_MAX     223
+
+struct intG0Reg_st {
+	/* Interrupt G0 */
+	u32 intr_type[7];
+	u32 intr_polarity[7];
+	u32 priority[7];
+	u32 intr_mask[7];
+	u32 g15_reserved_0[4];
+};
+
+struct intG1Reg_st {
+	/* Interrupt G1 */
+	u32 intr_clr[7];
+	u32 masked_fiqs[7];
+	u32 masked_irqs[7];
+	u32 g21_reserved_0[10];
+#ifdef SUPPORT_IRQ_GRP_REG
+	u32 intr_group;
+#else
+	u32 rsvd_31;
+#endif
+};
+
+static struct sp_intctl {
+	__iomem struct intG0Reg_st *g0;
+	__iomem struct intG1Reg_st *g1;
+	int hwirq_start;
+	int hwirq_end;   /* exclude */
+	struct irq_domain *domain;
+	struct device_node *node;
+	spinlock_t lock;
+	int virq[2];
+} sp_intc;
+
+#define WORKAROUND_FOR_EDGE_TRIGGER_BUG 1
+#ifdef WORKAROUND_FOR_EDGE_TRIGGER_BUG
+#define HW_IRQ_GPIO_INT0                120
+#define HW_IRQ_GPIO_INT7                127
+#define SP_IRQ_TYPE_EDGE_NONE           0x00
+#define SP_IRQ_TYPE_EDGE_RISING         0x01
+#define SP_IRQ_TYPE_EDGE_FALLING        0x02
+#define SP_IRQ_TYPE_EDGE_ACTIVE         0x80
+static char edge_trigger[SP_INTC_HWIRQ_MAX-SP_INTC_HWIRQ_MIN];
+#endif
+
+static void sp_intc_ack_irq(struct irq_data *data);
+static void sp_intc_mask_irq(struct irq_data *data);
+static void sp_intc_unmask_irq(struct irq_data *data);
+static int sp_intc_set_type(struct irq_data *data, unsigned int flow_type);
+
+static struct irq_chip sp_intc_chip = {
+	.name = "sp_intc",
+	.irq_ack = sp_intc_ack_irq,
+	.irq_mask = sp_intc_mask_irq,
+	.irq_unmask = sp_intc_unmask_irq,
+	.irq_set_type = sp_intc_set_type,
+};
+
+static void sp_intc_ack_irq(struct irq_data *data)
+{
+	u32 idx, mask;
+
+	if ((data->hwirq < sp_intc.hwirq_start) || (data->hwirq >= sp_intc.hwirq_end))
+		return;
+
+	idx = data->hwirq / 32;
+	mask = (1 << (data->hwirq % 32));
+
+	spin_lock(&sp_intc.lock);
+#ifdef WORKAROUND_FOR_EDGE_TRIGGER_BUG
+	if (edge_trigger[data->hwirq] & (SP_IRQ_TYPE_EDGE_RISING|SP_IRQ_TYPE_EDGE_FALLING)) {
+		u32 trig_lvl = readl_relaxed(&sp_intc.g0->intr_polarity[idx]);
+
+		if (edge_trigger[data->hwirq] == SP_IRQ_TYPE_EDGE_RISING)
+			trig_lvl |= mask;
+		else
+			trig_lvl &= ~mask;
+
+		writel_relaxed(trig_lvl, &sp_intc.g0->intr_polarity[idx]);
+		edge_trigger[data->hwirq] |= SP_IRQ_TYPE_EDGE_ACTIVE;
+	}
+#endif
+	writel_relaxed(mask, &sp_intc.g1->intr_clr[idx]);
+	spin_unlock(&sp_intc.lock);
+}
+
+static void sp_intc_mask_irq(struct irq_data *data)
+{
+	u32 idx, mask;
+
+	if ((data->hwirq < sp_intc.hwirq_start) || (data->hwirq >= sp_intc.hwirq_end))
+		return;
+
+	idx = data->hwirq / 32;
+
+	spin_lock(&sp_intc.lock);
+	mask = readl_relaxed(&sp_intc.g0->intr_mask[idx]);
+	mask &= ~(1 << (data->hwirq % 32));
+	writel_relaxed(mask, &sp_intc.g0->intr_mask[idx]);
+	spin_unlock(&sp_intc.lock);
+}
+
+static void sp_intc_unmask_irq(struct irq_data *data)
+{
+	u32 idx, mask;
+
+	if ((data->hwirq < sp_intc.hwirq_start) || (data->hwirq >= sp_intc.hwirq_end))
+		return;
+
+	idx = data->hwirq / 32;
+	spin_lock(&sp_intc.lock);
+	mask = readl_relaxed(&sp_intc.g0->intr_mask[idx]);
+	mask |= (1 << (data->hwirq % 32));
+	writel_relaxed(mask, &sp_intc.g0->intr_mask[idx]);
+	spin_unlock(&sp_intc.lock);
+}
+
+static int sp_intc_set_type(struct irq_data *data, unsigned int flow_type)
+{
+	u32 idx, mask;
+	u32 edge_type; /* 0=level, 1=edge */
+	u32 trig_lvl;  /* 0=high, 1=low */
+	unsigned long flags;
+
+	if ((data->hwirq < sp_intc.hwirq_start) || (data->hwirq >= sp_intc.hwirq_end))
+		return -EBADR;
+
+	/* update the chip/handler */
+	if (flow_type & IRQ_TYPE_LEVEL_MASK)
+		irq_set_chip_handler_name_locked(data, &sp_intc_chip,
+						   handle_level_irq, NULL);
+	else
+		irq_set_chip_handler_name_locked(data, &sp_intc_chip,
+						   handle_edge_irq, NULL);
+
+	idx = data->hwirq / 32;
+
+	spin_lock_irqsave(&sp_intc.lock, flags);
+
+	edge_type = readl_relaxed(&sp_intc.g0->intr_type[idx]);
+	trig_lvl = readl_relaxed(&sp_intc.g0->intr_polarity[idx]);
+	mask = (1 << (data->hwirq % 32));
+
+	switch (flow_type) {
+	case IRQ_TYPE_EDGE_RISING:
+#ifdef WORKAROUND_FOR_EDGE_TRIGGER_BUG
+		if ((data->hwirq >= HW_IRQ_GPIO_INT0) && (data->hwirq <= HW_IRQ_GPIO_INT7)) {
+			edge_trigger[data->hwirq] = SP_IRQ_TYPE_EDGE_RISING;
+			writel_relaxed((edge_type & ~mask), &sp_intc.g0->intr_type[idx]);
+		} else {
+			writel_relaxed((edge_type | mask), &sp_intc.g0->intr_type[idx]);
+		}
+#else
+		writel_relaxed((edge_type | mask), &sp_intc.g0->intr_type[idx]);
+#endif
+		writel_relaxed((trig_lvl & ~mask), &sp_intc.g0->intr_polarity[idx]);
+		break;
+	case IRQ_TYPE_EDGE_FALLING:
+#ifdef WORKAROUND_FOR_EDGE_TRIGGER_BUG
+		if ((data->hwirq >= HW_IRQ_GPIO_INT0) && (data->hwirq <= HW_IRQ_GPIO_INT7)) {
+			edge_trigger[data->hwirq] = SP_IRQ_TYPE_EDGE_FALLING;
+			writel_relaxed((edge_type & ~mask), &sp_intc.g0->intr_type[idx]);
+		} else {
+			writel_relaxed((edge_type | mask), &sp_intc.g0->intr_type[idx]);
+		}
+#else
+		writel_relaxed((edge_type | mask), &sp_intc.g0->intr_type[idx]);
+#endif
+		writel_relaxed((trig_lvl | mask), &sp_intc.g0->intr_polarity[idx]);
+		break;
+	case IRQ_TYPE_LEVEL_HIGH:
+#ifdef WORKAROUND_FOR_EDGE_TRIGGER_BUG
+		edge_trigger[data->hwirq] = SP_IRQ_TYPE_EDGE_NONE;
+#endif
+		writel_relaxed((edge_type & ~mask), &sp_intc.g0->intr_type[idx]);
+		writel_relaxed((trig_lvl & ~mask), &sp_intc.g0->intr_polarity[idx]);
+		break;
+	case IRQ_TYPE_LEVEL_LOW:
+#ifdef WORKAROUND_FOR_EDGE_TRIGGER_BUG
+		edge_trigger[data->hwirq] = SP_IRQ_TYPE_EDGE_NONE;
+#endif
+		writel_relaxed((edge_type & ~mask), &sp_intc.g0->intr_type[idx]);
+		writel_relaxed((trig_lvl | mask), &sp_intc.g0->intr_polarity[idx]);
+		break;
+	default:
+#ifdef WORKAROUND_FOR_EDGE_TRIGGER_BUG
+		edge_trigger[data->hwirq] = SP_IRQ_TYPE_EDGE_NONE;
+#endif
+		spin_unlock_irqrestore(&sp_intc.lock, flags);
+		pr_err("%s: type=%d\n", __func__, flow_type);
+		return -EBADR;
+	}
+
+	spin_unlock_irqrestore(&sp_intc.lock, flags);
+
+	return IRQ_SET_MASK_OK;
+}
+
+/* prio=1 (normal), prio=0 (dedicated) */
+static void sp_intc_set_prio(u32 hwirq, u32 prio)
+{
+	u32 idx, mask;
+
+	if ((hwirq < sp_intc.hwirq_start) || (hwirq >= sp_intc.hwirq_end))
+		return;
+
+	idx = hwirq / 32;
+
+	spin_lock(&sp_intc.lock);
+	mask = readl_relaxed(&sp_intc.g0->priority[idx]);
+	if (prio)
+		mask |= (1 << (hwirq % 32));
+	else
+		mask &= ~(1 << (hwirq % 32));
+	writel_relaxed(mask, &sp_intc.g0->priority[idx]);
+	spin_unlock(&sp_intc.lock);
+}
+
+/* return -1 if no interrupt # */
+static int sp_intc_get_ext0_irq(void)
+{
+	int hwirq, mask;
+	int i;
+
+#ifdef SUPPORT_IRQ_GRP_REG
+	mask = (readl_relaxed(&sp_intc.g1->intr_group) >> 8) & 0x7f; /* [14:8] for irq group */
+	if (mask) {
+		i = fls(mask) - 1;
+#else
+	for (i = 0; i < 7; i++) {
+#endif
+		mask = readl_relaxed(&sp_intc.g1->masked_irqs[i]);
+		if (mask) {
+			hwirq = (i << 5) + fls(mask) - 1;
+			return hwirq;
+		}
+	}
+	return -1;
+}
+
+static int sp_intc_get_ext1_irq(void)
+{
+	int hwirq, mask;
+	int i;
+
+#ifdef SUPPORT_IRQ_GRP_REG
+	mask = (readl_relaxed(&sp_intc.g1->intr_group) >> 0) & 0x7f; /* [6:0] for fiq group */
+	if (mask) {
+		i = fls(mask) - 1;
+#else
+	for (i = 0; i < 7; i++) {
+#endif
+		mask = readl_relaxed(&sp_intc.g1->masked_fiqs[i]);
+		if (mask) {
+			hwirq = (i << 5) + fls(mask) - 1;
+			return hwirq;
+		}
+	}
+	return -1;
+}
+
+static void sp_intc_handle_ext0_cascaded(struct irq_desc *desc)
+{
+	struct irq_chip *host_chip = irq_desc_get_chip(desc);
+	int hwirq;
+
+	chained_irq_enter(host_chip, desc);
+
+	while ((hwirq = sp_intc_get_ext0_irq()) >= 0) {
+#ifdef WORKAROUND_FOR_EDGE_TRIGGER_BUG
+		if (edge_trigger[hwirq] & SP_IRQ_TYPE_EDGE_ACTIVE) {
+			u32 idx = hwirq / 32;
+			u32 trig_lvl = readl_relaxed(&sp_intc.g0->intr_polarity[idx]);
+			u32 mask = 1 << (hwirq % 32);
+
+			edge_trigger[hwirq] &= ~SP_IRQ_TYPE_EDGE_ACTIVE;
+			if (edge_trigger[hwirq] == SP_IRQ_TYPE_EDGE_RISING)
+				trig_lvl &= ~mask;
+			else
+				trig_lvl |= mask;
+
+			writel_relaxed(trig_lvl, &sp_intc.g0->intr_polarity[idx]);
+		} else
+			generic_handle_irq(irq_find_mapping(sp_intc.domain, (unsigned int)hwirq));
+#else
+		generic_handle_irq(irq_find_mapping(sp_intc.domain, (unsigned int)hwirq));
+#endif
+	}
+
+	chained_irq_exit(host_chip, desc);
+}
+
+static void sp_intc_handle_ext1_cascaded(struct irq_desc *desc)
+{
+	struct irq_chip *host_chip = irq_desc_get_chip(desc);
+	int hwirq;
+
+	chained_irq_enter(host_chip, desc);
+
+	while ((hwirq = sp_intc_get_ext1_irq()) >= 0) {
+#ifdef WORKAROUND_FOR_EDGE_TRIGGER_BUG
+		if (edge_trigger[hwirq] & SP_IRQ_TYPE_EDGE_ACTIVE) {
+			u32 idx = hwirq / 32;
+			u32 trig_lvl = readl_relaxed(&sp_intc.g0->intr_polarity[idx]);
+			u32 mask = 1 << (hwirq % 32);
+
+			edge_trigger[hwirq] &= ~SP_IRQ_TYPE_EDGE_ACTIVE;
+			if (edge_trigger[hwirq] == SP_IRQ_TYPE_EDGE_RISING)
+				trig_lvl &= ~mask;
+			else
+				trig_lvl |= mask;
+
+			writel_relaxed(trig_lvl, &sp_intc.g0->intr_polarity[idx]);
+		} else
+			generic_handle_irq(irq_find_mapping(sp_intc.domain, (unsigned int)hwirq));
+#else
+		generic_handle_irq(irq_find_mapping(sp_intc.domain, (unsigned int)hwirq));
+#endif
+	}
+
+	chained_irq_exit(host_chip, desc);
+}
+
+static int sp_intc_handle_one_round(struct pt_regs *regs)
+{
+	int ret = -EINVAL;
+	int hwirq;
+
+	while ((hwirq = sp_intc_get_ext0_irq()) >= 0) {
+		handle_domain_irq(sp_intc.domain, hwirq, regs);
+		ret = 0;
+	}
+
+	return ret;
+}
+
+/* 8388: level-triggered hwirq# may come slower than IRQ */
+#define SPURIOUS_RETRY  30
+
+static void __exception_irq_entry sp_intc_handle_irq(struct pt_regs *regs)
+{
+	int err;
+	int first_int = 1;
+	int retry = 0;
+
+	do {
+		err = sp_intc_handle_one_round(regs);
+
+		if (!err)
+			first_int = 0;
+
+		if (first_int && err) { /* spurious irq */
+			if (retry++ < SPURIOUS_RETRY)
+				continue;
+		}
+	} while (!err);
+}
+
+static int sp_intc_irq_domain_map(struct irq_domain *domain, unsigned int irq,
+				  irq_hw_number_t hwirq)
+{
+	irq_set_chip_and_handler(irq, &sp_intc_chip, handle_level_irq);
+	irq_set_chip_data(irq, &sp_intc_chip);
+	irq_set_noprobe(irq);
+
+	return 0;
+}
+
+static void __init sp_intc_chip_init(int hwirq_start, int hwirq_end,
+				     void __iomem *base0, void __iomem *base1)
+{
+	int i;
+
+	sp_intc.g0 = base0;
+	sp_intc.g1 = base1;
+	sp_intc.hwirq_start = hwirq_start;
+	sp_intc.hwirq_end = hwirq_end;
+
+	for (i = 0; i < 7; i++) {
+		/* all mask */
+		writel_relaxed(0, &sp_intc.g0->intr_mask[i]);
+		/* all edge */
+		writel_relaxed(~0, &sp_intc.g0->intr_type[i]);
+		/* all high-active */
+		writel_relaxed(0, &sp_intc.g0->intr_polarity[i]);
+		/* all irq */
+		writel_relaxed(~0, &sp_intc.g0->priority[i]);
+		/* all clear */
+		writel_relaxed(~0, &sp_intc.g1->intr_clr[i]);
+	}
+}
+
+int sp_intc_xlate_of(struct irq_domain *d, struct device_node *node,
+			  const u32 *intspec, unsigned int intsize,
+			  irq_hw_number_t *out_hwirq, unsigned int *out_type)
+{
+	int ret = 0;
+	u32 ext_num;
+
+	if (WARN_ON(intsize < 2))
+		return -EINVAL;
+
+	*out_hwirq = intspec[0];
+	*out_type = intspec[1] & IRQ_TYPE_SENSE_MASK;
+	ext_num = (intspec[1] & SP_INTC_EXT_INT_MASK) >> SP_INTC_EXT_INT_SHFIT;
+
+	/* set ext_int1 to high priority */
+	if (ext_num != 1)
+		ext_num = 0;
+
+	if (ext_num)
+		sp_intc_set_prio(*out_hwirq, 0); /* priority=0 */
+
+	return ret;
+}
+
+static const struct irq_domain_ops sp_intc_dm_ops = {
+	.xlate = sp_intc_xlate_of,
+	.map = sp_intc_irq_domain_map,
+};
+
+int __init sp_intc_init(int hwirq_start, int irqs, void __iomem *base0, void __iomem *base1)
+{
+	sp_intc_chip_init(hwirq_start, hwirq_start + irqs, base0, base1);
+
+	sp_intc.domain = irq_domain_add_legacy(NULL, irqs, hwirq_start,
+			sp_intc.hwirq_start, &sp_intc_dm_ops, &sp_intc);
+	if (!sp_intc.domain)
+		panic("%s: unable to create legacy domain\n", __func__);
+
+	set_handle_irq(sp_intc_handle_irq);
+
+	return 0;
+}
+
+#if defined(CONFIG_SOC_SP7021)
+static cpumask_t *u2cpumask(u32 mask, cpumask_t *cpumask)
+{
+	unsigned int i;
+
+	for (i = 0; i < 4; i++) {
+		if (mask & (1 << i))
+			cpumask_set_cpu(i, cpumask);
+		else
+			cpumask_clear_cpu(i, cpumask);
+	}
+	return cpumask;
+}
+
+static int __init sp_intc_ext_adjust(void)
+{
+	u32 mask;
+	static cpumask_t cpumask;
+	struct device_node *node = sp_intc.node;
+
+	if (num_online_cpus() <= 1) {
+		pr_info("single core: skip ext adjust\n");
+		return 0;
+	}
+
+	cpumask = CPU_MASK_NONE;
+	if (!of_property_read_u32(node, "ext0-mask", &mask)) {
+		pr_info("%d: ext0-mask=0x%x\n", sp_intc.virq[0], mask);
+		if (irq_set_affinity(sp_intc.virq[0], u2cpumask(mask, &cpumask)))
+			pr_err("failed to set ext0 cpumask=0x%x\n", mask);
+	}
+
+	cpumask = CPU_MASK_NONE;
+	if (!of_property_read_u32(node, "ext1-mask", &mask)) {
+		pr_info("%d: ext1-mask=0x%x\n", sp_intc.virq[1], mask);
+		if (irq_set_affinity(sp_intc.virq[1], u2cpumask(mask, &cpumask)))
+			pr_err("failed to set ext1 cpumask=0x%x\n", mask);
+	}
+
+	return 0;
+}
+arch_initcall(sp_intc_ext_adjust)
+#endif
+
+#ifdef CONFIG_OF
+int __init sp_intc_init_dt(struct device_node *node, struct device_node *parent)
+{
+	void __iomem *base0, *base1;
+
+	base0 = of_iomap(node, 0);
+	if (!base0)
+		panic("unable to map sp-intc base 0\n");
+
+	base1 = of_iomap(node, 1);
+	if (!base1)
+		panic("unable to map sp-intc base 1\n");
+
+	sp_intc.node = node;
+
+	sp_intc_chip_init(SP_INTC_HWIRQ_MIN, SP_INTC_HWIRQ_MAX, base0, base1);
+
+	sp_intc.domain = irq_domain_add_linear(node, sp_intc.hwirq_end - sp_intc.hwirq_start,
+			&sp_intc_dm_ops, &sp_intc);
+	if (!sp_intc.domain)
+		panic("%s: unable to create linear domain\n", __func__);
+
+	spin_lock_init(&sp_intc.lock);
+
+	if (parent) {
+		sp_intc.virq[0] = irq_of_parse_and_map(node, 0);
+		if (sp_intc.virq[0]) {
+			irq_set_chained_handler_and_data(sp_intc.virq[0],
+				sp_intc_handle_ext0_cascaded, &sp_intc);
+		} else {
+			panic("%s: missed ext0 irq in DT\n", __func__);
+		}
+
+		sp_intc.virq[1] = irq_of_parse_and_map(node, 1);
+		if (sp_intc.virq[1]) {
+			irq_set_chained_handler_and_data(sp_intc.virq[1],
+				sp_intc_handle_ext1_cascaded, &sp_intc);
+		} else {
+			panic("%s: missed ext1 irq in DT\n", __func__);
+		}
+	} else {
+		set_handle_irq(sp_intc_handle_irq);
+	}
+
+	return 0;
+}
+IRQCHIP_DECLARE(sp_intc, "sunplus,sp7021-intc", sp_intc_init_dt);
+#endif
+
+MODULE_AUTHOR("Qin Jian <qinjian at cqplus1.com>");
+MODULE_DESCRIPTION("Sunplus SP7021 Interrupt Controller Driver");
+MODULE_LICENSE("GPL v2");
-- 
2.33.1




More information about the linux-arm-kernel mailing list