[PATCH v1 3/8] irqchip: Add EcoNet EN751221 INTC

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Add a driver for the interrupt controller in the EcoNet EN751221 MIPS SoC.

Signed-off-by: Caleb James DeLisle <cjd@xxxxxxxx>
---
If CPU_MIPSR2_IRQ_EI / CPU_MIPSR2_IRQ_VI are enabled in the build, this
device switches to sending all interrupts as vectored - which IRQ_MIPS_CPU
is not prepared to handle. If anybody knows how to either disable this
behavior, or handle vectored interrupts without ugly code that breaks
cascading, please let me know and I will implement that and add
MIPS_MT_SMP in a future patchset.
---
 drivers/irqchip/Kconfig               |   5 +
 drivers/irqchip/Makefile              |   1 +
 drivers/irqchip/irq-econet-en751221.c | 280 ++++++++++++++++++++++++++
 3 files changed, 286 insertions(+)
 create mode 100644 drivers/irqchip/irq-econet-en751221.c

diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index c11b9965c4ad..a591ad3156dc 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -147,6 +147,11 @@ config DW_APB_ICTL
 	select GENERIC_IRQ_CHIP
 	select IRQ_DOMAIN_HIERARCHY
 
+config ECONET_EN751221_INTC
+	bool
+	select GENERIC_IRQ_CHIP
+	select IRQ_DOMAIN
+
 config FARADAY_FTINTC010
 	bool
 	select IRQ_DOMAIN
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 25e9ad29b8c4..1ee83823928d 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_ARCH_BCM2835)		+= irq-bcm2836.o
 obj-$(CONFIG_ARCH_ACTIONS)		+= irq-owl-sirq.o
 obj-$(CONFIG_DAVINCI_CP_INTC)		+= irq-davinci-cp-intc.o
 obj-$(CONFIG_EXYNOS_IRQ_COMBINER)	+= exynos-combiner.o
+obj-$(CONFIG_ECONET_EN751221_INTC)	+= irq-econet-en751221.o
 obj-$(CONFIG_FARADAY_FTINTC010)		+= irq-ftintc010.o
 obj-$(CONFIG_ARCH_HIP04)		+= irq-hip04.o
 obj-$(CONFIG_ARCH_LPC32XX)		+= irq-lpc32xx.o
diff --git a/drivers/irqchip/irq-econet-en751221.c b/drivers/irqchip/irq-econet-en751221.c
new file mode 100644
index 000000000000..edbb8a3d6d51
--- /dev/null
+++ b/drivers/irqchip/irq-econet-en751221.c
@@ -0,0 +1,280 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * EN751221 Interrupt Controller Driver.
+ *
+ * Copyright (C) 2025 Caleb James DeLisle <cjd@xxxxxxxx>
+ */
+
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+
+#define INTC_IRQ_COUNT		40
+
+#define INTC_NO_SHADOW		0xff
+#define INTC_IS_SHADOW		0xfe
+
+#define REG_MASK0		0x04
+#define REG_MASK1		0x50
+#define REG_PENDING0		0x08
+#define REG_PENDING1		0x54
+
+static const struct econet_intc {
+	const struct irq_chip chip;
+
+	const struct irq_domain_ops domain_ops;
+} econet_intc;
+
+static struct {
+	void __iomem *membase;
+	u8 shadow_interrupts[INTC_IRQ_COUNT];
+} econet_intc_rai __ro_after_init;
+
+static DEFINE_RAW_SPINLOCK(irq_lock);
+
+static void econet_wreg(u32 reg, u32 val, u32 mask)
+{
+	unsigned long flags;
+	u32 v;
+
+	raw_spin_lock_irqsave(&irq_lock, flags);
+
+	v = ioread32(econet_intc_rai.membase + reg);
+	v &= ~mask;
+	v |= val & mask;
+	iowrite32(v, econet_intc_rai.membase + reg);
+
+	raw_spin_unlock_irqrestore(&irq_lock, flags);
+}
+
+static void econet_chmask(u32 hwirq, bool unmask)
+{
+	u32 reg;
+	u32 mask;
+	u32 bit;
+	u8 shadow;
+
+	shadow = econet_intc_rai.shadow_interrupts[hwirq];
+	if (WARN_ON_ONCE(shadow == INTC_IS_SHADOW))
+		return;
+	else if (shadow < INTC_NO_SHADOW && smp_processor_id() > 0)
+		hwirq = shadow;
+
+	if (hwirq >= 32) {
+		reg = REG_MASK1;
+		mask = BIT(hwirq - 32);
+	} else {
+		reg = REG_MASK0;
+		mask = BIT(hwirq);
+	}
+	bit = (unmask) ? mask : 0;
+
+	econet_wreg(reg, bit, mask);
+}
+
+static void econet_intc_mask(struct irq_data *d)
+{
+	econet_chmask(d->hwirq, false);
+}
+
+static void econet_intc_unmask(struct irq_data *d)
+{
+	econet_chmask(d->hwirq, true);
+}
+
+static void econet_mask_all(void)
+{
+	econet_wreg(REG_MASK0, 0, ~0);
+	econet_wreg(REG_MASK1, 0, ~0);
+}
+
+static void econet_intc_handle_pending(struct irq_domain *d, u32 pending, u32 offset)
+{
+	int hwirq;
+
+	while (pending) {
+		hwirq = fls(pending) - 1;
+		generic_handle_domain_irq(d, hwirq + offset);
+		pending &= ~BIT(hwirq);
+	}
+}
+
+static void econet_intc_from_parent(struct irq_desc *desc)
+{
+	struct irq_chip *chip = irq_desc_get_chip(desc);
+	struct irq_domain *domain;
+	u32 pending0;
+	u32 pending1;
+
+	chained_irq_enter(chip, desc);
+
+	pending0 = ioread32(econet_intc_rai.membase + REG_PENDING0);
+	pending1 = ioread32(econet_intc_rai.membase + REG_PENDING1);
+
+	if (unlikely(!(pending0 | pending1))) {
+		spurious_interrupt();
+		goto out;
+	}
+
+	domain = irq_desc_get_handler_data(desc);
+
+	econet_intc_handle_pending(domain, pending0, 0);
+	econet_intc_handle_pending(domain, pending1, 32);
+
+out:
+	chained_irq_exit(chip, desc);
+}
+
+static int econet_intc_map(struct irq_domain *d, u32 irq, irq_hw_number_t hwirq)
+{
+	int ret;
+
+	if (hwirq >= INTC_IRQ_COUNT) {
+		pr_err("%s: hwirq %lu out of range\n", __func__, hwirq);
+		return -EINVAL;
+	} else if (econet_intc_rai.shadow_interrupts[hwirq] == INTC_IS_SHADOW) {
+		pr_err("%s: can't map hwirq %lu, it is a shadow interrupt\n",
+		       __func__, hwirq);
+		return -EINVAL;
+	}
+	if (econet_intc_rai.shadow_interrupts[hwirq] != INTC_NO_SHADOW) {
+		irq_set_chip_and_handler(
+			irq, &econet_intc.chip, handle_percpu_devid_irq);
+		ret = irq_set_percpu_devid(irq);
+		if (ret) {
+			pr_warn("%s: Failed irq_set_percpu_devid for %u: %d\n",
+				d->name, irq, ret);
+		}
+	} else {
+		irq_set_chip_and_handler(
+			irq, &econet_intc.chip, handle_level_irq);
+	}
+	irq_set_chip_data(irq, NULL);
+	return 0;
+}
+
+static const struct econet_intc econet_intc = {
+	.chip = {
+		.name		= "en751221-intc",
+		.irq_unmask	= econet_intc_unmask,
+		.irq_mask	= econet_intc_mask,
+		.irq_mask_ack	= econet_intc_mask,
+	},
+	.domain_ops = {
+		.xlate = irq_domain_xlate_onecell,
+		.map = econet_intc_map,
+	},
+};
+
+static int __init get_shadow_interrupts(struct device_node *node)
+{
+	const char *field = "econet,shadow-interrupts";
+	int n_shadow_interrupts;
+	u32 *shadow_interrupts;
+
+	n_shadow_interrupts = of_property_count_u32_elems(node, field);
+	memset(econet_intc_rai.shadow_interrupts, INTC_NO_SHADOW,
+	       sizeof(econet_intc_rai.shadow_interrupts));
+	if (n_shadow_interrupts <= 0) {
+		return 0;
+	} else if (n_shadow_interrupts % 2) {
+		pr_err("%pOF: %s count is odd, ignoring\n", node, field);
+		return 0;
+	}
+	shadow_interrupts = kmalloc_array(n_shadow_interrupts, sizeof(u32),
+					  GFP_KERNEL);
+	if (!shadow_interrupts)
+		return -ENOMEM;
+	if (of_property_read_u32_array(node, field,
+				       shadow_interrupts, n_shadow_interrupts)
+	) {
+		pr_err("%pOF: Failed to read %s\n", node, field);
+		kfree(shadow_interrupts);
+		return -EINVAL;
+	}
+	for (int i = 0; i < n_shadow_interrupts; i += 2) {
+		u32 shadow = shadow_interrupts[i + 1];
+		u32 target = shadow_interrupts[i];
+
+		if (shadow > INTC_IRQ_COUNT) {
+			pr_err("%pOF: %s[%d] shadow(%d) out of range\n",
+			       node, field, i, shadow);
+			continue;
+		}
+		if (target >= INTC_IRQ_COUNT) {
+			pr_err("%pOF: %s[%d] target(%d) out of range\n",
+			       node, field, i + 1, target);
+			continue;
+		}
+		econet_intc_rai.shadow_interrupts[target] = shadow;
+		econet_intc_rai.shadow_interrupts[shadow] = INTC_IS_SHADOW;
+	}
+	kfree(shadow_interrupts);
+	return 0;
+}
+
+static int __init econet_intc_of_init(struct device_node *node, struct device_node *parent)
+{
+	int ret;
+	int irq;
+	struct resource res;
+	struct irq_domain *domain;
+
+	ret = get_shadow_interrupts(node);
+	if (ret)
+		return ret;
+
+	irq = irq_of_parse_and_map(node, 0);
+	if (!irq) {
+		pr_err("%pOF: DT: Failed to get IRQ from 'interrupts'\n", node);
+		return -EINVAL;
+	}
+
+	if (of_address_to_resource(node, 0, &res)) {
+		pr_err("%pOF: DT: Failed to get 'reg'\n", node);
+		ret = -EINVAL;
+		goto err_dispose_mapping;
+	}
+
+	if (!request_mem_region(res.start, resource_size(&res), res.name)) {
+		pr_err("%pOF: Failed to request memory\n", node);
+		ret = -EBUSY;
+		goto err_dispose_mapping;
+	}
+
+	econet_intc_rai.membase = ioremap(res.start, resource_size(&res));
+	if (!econet_intc_rai.membase) {
+		pr_err("%pOF: Failed to remap membase\n", node);
+		ret = -ENOMEM;
+		goto err_release;
+	}
+
+	econet_mask_all();
+
+	domain = irq_domain_add_linear(
+		node, INTC_IRQ_COUNT,
+		&econet_intc.domain_ops, NULL);
+	if (!domain) {
+		pr_err("%pOF: Failed to add irqdomain\n", node);
+		ret = -ENOMEM;
+		goto err_unmap;
+	}
+
+	irq_set_chained_handler_and_data(irq, econet_intc_from_parent, domain);
+
+	return 0;
+
+err_unmap:
+	iounmap(econet_intc_rai.membase);
+err_release:
+	release_mem_region(res.start, resource_size(&res));
+err_dispose_mapping:
+	irq_dispose_mapping(irq);
+	return ret;
+}
+
+IRQCHIP_DECLARE(econet_en751221_intc, "econet,en751221-intc", econet_intc_of_init);
-- 
2.30.2





[Index of Archives]     [LKML Archive]     [Linux ARM Kernel]     [Linux ARM]     [Git]     [Yosemite News]     [Linux SCSI]     [Linux Hams]

  Powered by Linux