All of lore.kernel.org
 help / color / mirror / Atom feed
From: Caleb James DeLisle <cjd@cjdns.fr>
To: linux-mips@vger.kernel.org
Cc: naseefkm@gmail.com, daniel.lezcano@kernel.org, tglx@kernel.org,
	robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
	linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
	Caleb James DeLisle <cjd@cjdns.fr>
Subject: [PATCH 2/2] clocksource/timer-econet-en751221: Support irq number per timer
Date: Thu, 16 Apr 2026 17:51:01 +0000	[thread overview]
Message-ID: <20260416175101.958073-3-cjd@cjdns.fr> (raw)
In-Reply-To: <20260416175101.958073-1-cjd@cjdns.fr>

This timer was first developed on the EN751221 which is a MIPS 34Kc
and therefore has a custom interrupt controller. The hardware for
econet,en751221-intc implements percpu routing of the timer
interrupts.

However, the EN751627 and EN7528 are MIPS 1004Kc based, and
therefore use the standard mti,gic compatible interrupt controller.
This interrupt controller uses a different IRQ number for each
timer interrupt.

Add support for both models in this timer driver.

Co-developed-by: Ahmed Naseef <naseefkm@gmail.com>
Signed-off-by: Ahmed Naseef <naseefkm@gmail.com>
Link: https://github.com/openwrt/openwrt/commit/fab098cb6121647ca9cc6e501d56ebe8a9ea550b#diff-a09ee5e4166e89df337d03c1455dce7b81eb89797b1d0f714476b188e6685334

[cjd@cjdns.fr minor changes:
Set ECONET_MAX_IRQS to NR_CPUS rather than 4
Use is_percpu_irq() instead of field
Do not set CLOCK_EVT_FEAT_PERCPU in non-percpu mode
Fold cevt_init() into timer_init()
]

Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
---
 drivers/clocksource/Kconfig                 |   5 +-
 drivers/clocksource/timer-econet-en751221.c | 137 ++++++++++++++++----
 2 files changed, 114 insertions(+), 28 deletions(-)

diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index d1a33a231a44..9a77f38d5fb7 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -79,7 +79,10 @@ config ECONET_EN751221_TIMER
 	select CLKSRC_MMIO
 	select TIMER_OF
 	help
-	  Support for CPU timer found on EcoNet MIPS based SoCs.
+	  Support for CPU timer found on EcoNet EN75xx MIPS based SoCs
+	  (EN751221, EN751627, EN7528). The driver supports both GIC-based
+	  (separate IRQ per CPU) and legacy interrupt controller (percpu IRQ)
+	  modes.
 
 config FTTMR010_TIMER
 	bool "Faraday Technology timer driver" if COMPILE_TEST
diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
index 4008076b1a21..e280ee8c2b1c 100644
--- a/drivers/clocksource/timer-econet-en751221.c
+++ b/drivers/clocksource/timer-econet-en751221.c
@@ -3,11 +3,13 @@
  * Timer present on EcoNet EN75xx MIPS based SoCs.
  *
  * Copyright (C) 2025 by Caleb James DeLisle <cjd@cjdns.fr>
+ * Copyright (C) 2025 by Ahmed Naseef <naseefkm@gmail.com>
  */
 
 #include <linux/io.h>
 #include <linux/cpumask.h>
 #include <linux/interrupt.h>
+#include <linux/irq.h>
 #include <linux/clockchips.h>
 #include <linux/sched_clock.h>
 #include <linux/of.h>
@@ -21,14 +23,26 @@
 #define ECONET_MAX_DELTA		GENMASK(ECONET_BITS - 2, 0)
 /* 34Kc hardware has 1 block and 1004Kc has 2. */
 #define ECONET_NUM_BLOCKS		DIV_ROUND_UP(NR_CPUS, 2)
+#define ECONET_MAX_IRQS			NR_CPUS
 
 static struct {
 	void __iomem	*membase[ECONET_NUM_BLOCKS];
 	u32		freq_hz;
+	int		irqs[ECONET_MAX_IRQS];
+	int		num_irqs;
 } econet_timer __ro_after_init;
 
 static DEFINE_PER_CPU(struct clock_event_device, econet_timer_pcpu);
 
+/* This timer supports two interrupt controller models, either 1 IRQ which is in per-cpu
+ * mode which is used on 34Kc CPUs, and separate IRQ number per CPU which is used on
+ * 1004Kc CPUs with GIC intc.
+ */
+static inline bool is_percpu_irq(void)
+{
+	return econet_timer.num_irqs == 1;
+}
+
 /* Each memory block has 2 timers, the order of registers is:
  * CTL, CMR0, CNT0, CMR1, CNT1
  */
@@ -98,12 +112,21 @@ static int cevt_init_cpu(uint cpu)
 	struct clock_event_device *cd = &per_cpu(econet_timer_pcpu, cpu);
 	u32 reg;
 
+	if (!is_percpu_irq() && cpu >= econet_timer.num_irqs)
+		return -EINVAL;
+
 	pr_debug("%s: Setting up clockevent for CPU %d\n", cd->name, cpu);
 
 	reg = ioread32(reg_ctl(cpu)) | ctl_bit_enabled(cpu);
 	iowrite32(reg, reg_ctl(cpu));
 
-	enable_percpu_irq(cd->irq, IRQ_TYPE_NONE);
+	if (is_percpu_irq()) {
+		enable_percpu_irq(cd->irq, IRQ_TYPE_NONE);
+	} else {
+		if (irq_force_affinity(econet_timer.irqs[cpu], cpumask_of(cpu)))
+			pr_warn("%s: failed to set IRQ %d affinity to CPU %d\n",
+				cd->name, econet_timer.irqs[cpu], cpu);
+	}
 
 	/* Do this last because it synchronously configures the timer */
 	clockevents_config_and_register(cd, econet_timer.freq_hz,
@@ -126,7 +149,20 @@ static void __init cevt_dev_init(uint cpu)
 	iowrite32(U32_MAX, reg_compare(cpu));
 }
 
-static int __init cevt_init(struct device_node *np)
+static void __init cevt_setup_clockevent(struct clock_event_device *cd,
+					 struct device_node *np,
+					 int irq, int cpu)
+{
+	cd->rating		= 310;
+	cd->features		= CLOCK_EVT_FEAT_ONESHOT |
+				  CLOCK_EVT_FEAT_C3STOP;
+	cd->set_next_event	= cevt_set_next_event;
+	cd->irq			= irq;
+	cd->cpumask		= cpumask_of(cpu);
+	cd->name		= np->name;
+}
+
+static int __init cevt_init_percpu(struct device_node *np)
 {
 	int i, irq, ret;
 
@@ -137,42 +173,65 @@ static int __init cevt_init(struct device_node *np)
 	}
 
 	ret = request_percpu_irq(irq, cevt_interrupt, np->name, &econet_timer_pcpu);
-
 	if (ret < 0) {
 		pr_err("%pOFn: IRQ %d setup failed (%d)\n", np, irq, ret);
-		goto err_unmap_irq;
+		irq_dispose_mapping(irq);
+		return ret;
 	}
 
 	for_each_possible_cpu(i) {
 		struct clock_event_device *cd = &per_cpu(econet_timer_pcpu, i);
 
-		cd->rating		= 310;
-		cd->features		= CLOCK_EVT_FEAT_ONESHOT |
-					  CLOCK_EVT_FEAT_C3STOP |
-					  CLOCK_EVT_FEAT_PERCPU;
-		cd->set_next_event	= cevt_set_next_event;
-		cd->irq			= irq;
-		cd->cpumask		= cpumask_of(i);
-		cd->name		= np->name;
+		cevt_setup_clockevent(cd, np, irq, i);
+		cd->features |= CLOCK_EVT_FEAT_PERCPU;
+		cevt_dev_init(i);
+	}
+
+	return 0;
+}
 
+static int __init cevt_init_separate(struct device_node *np)
+{
+	int i, ret;
+
+	for (i = 0; i < econet_timer.num_irqs; i++) {
+		struct clock_event_device *cd = &per_cpu(econet_timer_pcpu, i);
+
+		econet_timer.irqs[i] = irq_of_parse_and_map(np, i);
+		if (econet_timer.irqs[i] <= 0) {
+			pr_err("%pOFn: irq_of_parse_and_map failed", np);
+			ret = -EINVAL;
+			goto err_free_irqs;
+		}
+
+		ret = request_irq(econet_timer.irqs[i], cevt_interrupt,
+				  IRQF_TIMER | IRQF_NOBALANCING,
+				  np->name, NULL);
+		if (ret < 0) {
+			pr_err("%pOFn: IRQ %d setup failed (%d)\n", np,
+			       econet_timer.irqs[i], ret);
+			irq_dispose_mapping(econet_timer.irqs[i]);
+			goto err_free_irqs;
+		}
+
+		cevt_setup_clockevent(cd, np, econet_timer.irqs[i], i);
 		cevt_dev_init(i);
 	}
 
-	cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
-			  "clockevents/econet/timer:starting",
-			  cevt_init_cpu, NULL);
 	return 0;
 
-err_unmap_irq:
-	irq_dispose_mapping(irq);
+err_free_irqs:
+	while (--i >= 0) {
+		free_irq(econet_timer.irqs[i], NULL);
+		irq_dispose_mapping(econet_timer.irqs[i]);
+	}
 	return ret;
 }
 
 static int __init timer_init(struct device_node *np)
 {
-	int num_blocks = DIV_ROUND_UP(num_possible_cpus(), 2);
 	struct clk *clk;
-	int ret;
+	int ret, i;
 
 	clk = of_clk_get(np, 0);
 	if (IS_ERR(clk)) {
@@ -182,11 +241,18 @@ static int __init timer_init(struct device_node *np)
 
 	econet_timer.freq_hz = clk_get_rate(clk);
 
-	for (int i = 0; i < num_blocks; i++) {
+	econet_timer.num_irqs = of_irq_count(np);
+	if (econet_timer.num_irqs <= 0 || econet_timer.num_irqs > ECONET_MAX_IRQS) {
+		pr_err("%pOFn: invalid IRQ count %d\n", np, econet_timer.num_irqs);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < ECONET_NUM_BLOCKS; i++) {
 		econet_timer.membase[i] = of_iomap(np, i);
 		if (!econet_timer.membase[i]) {
 			pr_err("%pOFn: failed to map register [%d]\n", np, i);
-			return -ENXIO;
+			ret = -ENXIO;
+			goto err_unmap;
 		}
 	}
 
@@ -196,21 +262,38 @@ static int __init timer_init(struct device_node *np)
 				    clocksource_mmio_readl_up);
 	if (ret) {
 		pr_err("%pOFn: clocksource_mmio_init failed: %d", np, ret);
-		return ret;
+		goto err_unmap;
 	}
 
-	ret = cevt_init(np);
+	if (is_percpu_irq())
+		ret = cevt_init_percpu(np);
+	else
+		ret = cevt_init_separate(np);
+
 	if (ret < 0)
-		return ret;
+		goto err_unmap;
+
+	cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
+			  "clockevents/econet/timer:starting",
+			  cevt_init_cpu, NULL);
 
 	sched_clock_register(sched_clock_read, ECONET_BITS,
 			     econet_timer.freq_hz);
 
-	pr_info("%pOFn: using %u.%03u MHz high precision timer\n", np,
+	pr_info("%pOFn: using %u.%03u MHz high precision timer (%s mode)\n", np,
 		econet_timer.freq_hz / 1000000,
-		(econet_timer.freq_hz / 1000) % 1000);
+		(econet_timer.freq_hz / 1000) % 1000,
+		is_percpu_irq() ? "percpu" : "separate IRQ");
 
 	return 0;
+
+err_unmap:
+	for (i = 0; i < ECONET_NUM_BLOCKS; i++) {
+		if (econet_timer.membase[i])
+			iounmap(econet_timer.membase[i]);
+	}
+
+	return ret;
 }
 
-TIMER_OF_DECLARE(econet_timer_hpt, "econet,en751221-timer", timer_init);
+TIMER_OF_DECLARE(econet_en751221_timer, "econet,en751221-timer", timer_init);
-- 
2.39.5


  parent reply	other threads:[~2026-04-16 17:51 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-16 17:50 [PATCH 0/2] clocksource/timer-econet-en751221: Support irq number per timer Caleb James DeLisle
2026-04-16 17:51 ` [PATCH 1/2] dt-bindings: timer: econet: Update EN751627 for multi-IRQ Caleb James DeLisle
2026-04-17 16:17   ` Conor Dooley
2026-04-16 17:51 ` Caleb James DeLisle [this message]
2026-05-06 14:39   ` [PATCH 2/2] clocksource/timer-econet-en751221: Support irq number per timer Daniel Lezcano
2026-05-06 15:27     ` Caleb James DeLisle
2026-05-06 15:39       ` Daniel Lezcano

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=20260416175101.958073-3-cjd@cjdns.fr \
    --to=cjd@cjdns.fr \
    --cc=conor+dt@kernel.org \
    --cc=daniel.lezcano@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mips@vger.kernel.org \
    --cc=naseefkm@gmail.com \
    --cc=robh@kernel.org \
    --cc=tglx@kernel.org \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.