From: Caleb James DeLisle <cjd@cjdns.fr>
To: linux-mips@vger.kernel.org
Cc: Thomas Gleixner <tglx@linutronix.de>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Thomas Bogendoerfer <tsbogend@alpha.franken.de>,
Daniel Lezcano <daniel.lezcano@linaro.org>,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
benjamin.larsson@genexis.eu, Caleb James DeLisle <cjd@cjdns.fr>
Subject: [PATCH v1 3/8] irqchip: Add EcoNet EN751221 INTC
Date: Fri, 21 Mar 2025 13:46:28 +0000 [thread overview]
Message-ID: <20250321134633.2155141-4-cjd@cjdns.fr> (raw)
In-Reply-To: <20250321134633.2155141-1-cjd@cjdns.fr>
Add a driver for the interrupt controller in the EcoNet EN751221 MIPS SoC.
Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
---
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@cjdns.fr>
+ */
+
+#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
next prev parent reply other threads:[~2025-03-21 13:53 UTC|newest]
Thread overview: 27+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-03-21 13:46 [PATCH v1 0/8] Add EcoNet EN751221 MIPS platform support Caleb James DeLisle
2025-03-21 13:46 ` [PATCH v1 1/8] dt-bindings: vendor-prefixes: Add EcoNet Caleb James DeLisle
2025-03-21 13:46 ` [PATCH v1 2/8] dt-bindings: interrupt-controller: Add EcoNet EN751221 INTC Caleb James DeLisle
2025-03-21 15:52 ` Rob Herring (Arm)
2025-03-21 17:19 ` Caleb James DeLisle
2025-03-21 21:17 ` Rob Herring
2025-03-21 23:55 ` Caleb James DeLisle
2025-03-21 13:46 ` Caleb James DeLisle [this message]
2025-03-21 20:26 ` [PATCH v1 3/8] irqchip: " Thomas Gleixner
2025-03-21 22:20 ` Caleb James DeLisle
2025-03-22 8:20 ` Thomas Gleixner
2025-03-23 3:06 ` Caleb James DeLisle
2025-03-23 17:56 ` Thomas Gleixner
2025-03-21 13:46 ` [PATCH v1 4/8] dt-bindings: timer: Add EcoNet HPT CPU Timer Caleb James DeLisle
2025-03-21 20:56 ` Krzysztof Kozlowski
2025-03-21 23:21 ` Caleb James DeLisle
2025-03-23 12:39 ` Krzysztof Kozlowski
2025-03-23 23:53 ` Caleb James DeLisle
2025-03-24 7:13 ` Krzysztof Kozlowski
2025-03-24 12:25 ` Caleb James DeLisle
2025-03-21 13:46 ` [PATCH v1 5/8] clocksource/drivers: Add EcoNet Timer HPT driver Caleb James DeLisle
2025-03-21 13:46 ` [PATCH v1 6/8] dt-bindings: mips: Add EcoNet platform binding Caleb James DeLisle
2025-03-21 20:57 ` Krzysztof Kozlowski
2025-03-21 13:46 ` [PATCH v1 7/8] mips: Add EcoNet MIPS platform support Caleb James DeLisle
2025-03-21 21:00 ` Krzysztof Kozlowski
2025-03-21 23:43 ` Caleb James DeLisle
2025-03-21 13:46 ` [PATCH v1 8/8] MAINTAINERS: Add EcoNet MIPS platform entry Caleb James DeLisle
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250321134633.2155141-4-cjd@cjdns.fr \
--to=cjd@cjdns.fr \
--cc=benjamin.larsson@genexis.eu \
--cc=conor+dt@kernel.org \
--cc=daniel.lezcano@linaro.org \
--cc=devicetree@vger.kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mips@vger.kernel.org \
--cc=robh@kernel.org \
--cc=tglx@linutronix.de \
--cc=tsbogend@alpha.franken.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®