* [PATCH v2 1/5] dt-bindings: timer: econet: Update EN751627 for multi-IRQ
2026-05-14 0:05 [PATCH v2 0/5] clocksource/timer-econet-en751221: Support irq number per timer Caleb James DeLisle
@ 2026-05-14 0:05 ` Caleb James DeLisle
2026-05-14 0:05 ` [PATCH v2 2/5] clocksource/timer-econet-en751221: Move generic logic out of cevt_init Caleb James DeLisle
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Caleb James DeLisle @ 2026-05-14 0:05 UTC (permalink / raw)
To: linux-mips
Cc: conor+dt, daniel.lezcano, devicetree, krzk+dt, linux-kernel,
naseefkm, robh, tglx, Caleb James DeLisle, Conor Dooley
This hardware is found in the EN751221 SoC family as well as the
EN751627. The former uses a percpu IRQ for all timers while the
later uses an individual IRQ numbers per-timer.
Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
Acked-by: Conor Dooley <conor.dooley@microchip.com>
---
.../bindings/timer/econet,en751221-timer.yaml | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/Documentation/devicetree/bindings/timer/econet,en751221-timer.yaml b/Documentation/devicetree/bindings/timer/econet,en751221-timer.yaml
index c1e7c2b6afde..f338739e039c 100644
--- a/Documentation/devicetree/bindings/timer/econet,en751221-timer.yaml
+++ b/Documentation/devicetree/bindings/timer/econet,en751221-timer.yaml
@@ -28,8 +28,8 @@ properties:
maxItems: 2
interrupts:
- maxItems: 1
- description: A percpu-devid timer interrupt shared across CPUs.
+ minItems: 1
+ maxItems: 4
clocks:
maxItems: 1
@@ -52,21 +52,31 @@ allOf:
items:
- description: VPE timers 0 and 1
- description: VPE timers 2 and 3
+ interrupts:
+ description: An interrupt for each timer (one per VPE)
+ minItems: 4
else:
properties:
reg:
items:
- description: VPE timers 0 and 1
+ interrupts:
+ description: A percpu-devid timer interrupt shared across timers
+ maxItems: 1
additionalProperties: false
examples:
- |
+ #include <dt-bindings/interrupt-controller/mips-gic.h>
timer@1fbf0400 {
compatible = "econet,en751627-timer", "econet,en751221-timer";
reg = <0x1fbf0400 0x100>, <0x1fbe0000 0x100>;
interrupt-parent = <&intc>;
- interrupts = <30>;
+ interrupts = <GIC_SHARED 30 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SHARED 29 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SHARED 37 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SHARED 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&hpt_clock>;
};
- |
--
2.39.5
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v2 2/5] clocksource/timer-econet-en751221: Move generic logic out of cevt_init
2026-05-14 0:05 [PATCH v2 0/5] clocksource/timer-econet-en751221: Support irq number per timer Caleb James DeLisle
2026-05-14 0:05 ` [PATCH v2 1/5] dt-bindings: timer: econet: Update EN751627 for multi-IRQ Caleb James DeLisle
@ 2026-05-14 0:05 ` Caleb James DeLisle
2026-05-14 0:05 ` [PATCH v2 3/5] clocksource/timer-econet-en751221: Always map all membase blocks Caleb James DeLisle
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Caleb James DeLisle @ 2026-05-14 0:05 UTC (permalink / raw)
To: linux-mips
Cc: conor+dt, daniel.lezcano, devicetree, krzk+dt, linux-kernel,
naseefkm, robh, tglx, Caleb James DeLisle
In preparation for supporting either a percpu IRQ or multiple IRQ
numbers, simplify cevt_init with common code moved out.
Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
---
drivers/clocksource/timer-econet-en751221.c | 31 +++++++++++++--------
1 file changed, 19 insertions(+), 12 deletions(-)
diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
index 4008076b1a21..5def3e536b21 100644
--- a/drivers/clocksource/timer-econet-en751221.c
+++ b/drivers/clocksource/timer-econet-en751221.c
@@ -126,6 +126,19 @@ static void __init cevt_dev_init(uint cpu)
iowrite32(U32_MAX, reg_compare(cpu));
}
+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(struct device_node *np)
{
int i, irq, ret;
@@ -146,21 +159,11 @@ static int __init cevt_init(struct device_node *np)
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);
}
- cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
- "clockevents/econet/timer:starting",
- cevt_init_cpu, NULL);
return 0;
err_unmap_irq:
@@ -203,6 +206,10 @@ static int __init timer_init(struct device_node *np)
if (ret < 0)
return ret;
+ 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);
--
2.39.5
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v2 3/5] clocksource/timer-econet-en751221: Always map all membase blocks
2026-05-14 0:05 [PATCH v2 0/5] clocksource/timer-econet-en751221: Support irq number per timer Caleb James DeLisle
2026-05-14 0:05 ` [PATCH v2 1/5] dt-bindings: timer: econet: Update EN751627 for multi-IRQ Caleb James DeLisle
2026-05-14 0:05 ` [PATCH v2 2/5] clocksource/timer-econet-en751221: Move generic logic out of cevt_init Caleb James DeLisle
@ 2026-05-14 0:05 ` Caleb James DeLisle
2026-05-14 0:06 ` [PATCH v2 4/5] clocksource/timer-econet-en751221: Unmap io mem on probe error Caleb James DeLisle
2026-05-14 0:06 ` [PATCH v2 5/5] clocksource/timer-econet-en751221: Support irq number per timer Caleb James DeLisle
4 siblings, 0 replies; 6+ messages in thread
From: Caleb James DeLisle @ 2026-05-14 0:05 UTC (permalink / raw)
To: linux-mips
Cc: conor+dt, daniel.lezcano, devicetree, krzk+dt, linux-kernel,
naseefkm, robh, tglx, Caleb James DeLisle
The 34Kc always has 1 block and the 1004Kc always has 2, there's no reason
to not map them all, even if some CPUs are not active. Simplify the logic
to make it more maintainable.
Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
---
drivers/clocksource/timer-econet-en751221.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
index 5def3e536b21..e79069d9a826 100644
--- a/drivers/clocksource/timer-econet-en751221.c
+++ b/drivers/clocksource/timer-econet-en751221.c
@@ -173,7 +173,6 @@ static int __init cevt_init(struct device_node *np)
static int __init timer_init(struct device_node *np)
{
- int num_blocks = DIV_ROUND_UP(num_possible_cpus(), 2);
struct clk *clk;
int ret;
@@ -185,7 +184,7 @@ 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++) {
+ for (int i = 0; i < ARRAY_SIZE(econet_timer.membase); 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);
--
2.39.5
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v2 4/5] clocksource/timer-econet-en751221: Unmap io mem on probe error
2026-05-14 0:05 [PATCH v2 0/5] clocksource/timer-econet-en751221: Support irq number per timer Caleb James DeLisle
` (2 preceding siblings ...)
2026-05-14 0:05 ` [PATCH v2 3/5] clocksource/timer-econet-en751221: Always map all membase blocks Caleb James DeLisle
@ 2026-05-14 0:06 ` Caleb James DeLisle
2026-05-14 0:06 ` [PATCH v2 5/5] clocksource/timer-econet-en751221: Support irq number per timer Caleb James DeLisle
4 siblings, 0 replies; 6+ messages in thread
From: Caleb James DeLisle @ 2026-05-14 0:06 UTC (permalink / raw)
To: linux-mips
Cc: conor+dt, daniel.lezcano, devicetree, krzk+dt, linux-kernel,
naseefkm, robh, tglx, Caleb James DeLisle
In case of error during probe, the io mem blocks should be unmapped.
Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
---
drivers/clocksource/timer-econet-en751221.c | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
index e79069d9a826..4b712eb4db6f 100644
--- a/drivers/clocksource/timer-econet-en751221.c
+++ b/drivers/clocksource/timer-econet-en751221.c
@@ -188,7 +188,8 @@ static int __init timer_init(struct device_node *np)
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;
}
}
@@ -198,12 +199,12 @@ 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 (ret < 0)
- return ret;
+ goto err_unmap;
cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
"clockevents/econet/timer:starting",
@@ -217,6 +218,14 @@ static int __init timer_init(struct device_node *np)
(econet_timer.freq_hz / 1000) % 1000);
return 0;
+
+err_unmap:
+ for (int i = 0; i < ARRAY_SIZE(econet_timer.membase); i++) {
+ if (econet_timer.membase[i])
+ iounmap(econet_timer.membase[i]);
+ }
+
+ return ret;
}
TIMER_OF_DECLARE(econet_timer_hpt, "econet,en751221-timer", timer_init);
--
2.39.5
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v2 5/5] clocksource/timer-econet-en751221: Support irq number per timer
2026-05-14 0:05 [PATCH v2 0/5] clocksource/timer-econet-en751221: Support irq number per timer Caleb James DeLisle
` (3 preceding siblings ...)
2026-05-14 0:06 ` [PATCH v2 4/5] clocksource/timer-econet-en751221: Unmap io mem on probe error Caleb James DeLisle
@ 2026-05-14 0:06 ` Caleb James DeLisle
4 siblings, 0 replies; 6+ messages in thread
From: Caleb James DeLisle @ 2026-05-14 0:06 UTC (permalink / raw)
To: linux-mips
Cc: conor+dt, daniel.lezcano, devicetree, krzk+dt, linux-kernel,
naseefkm, robh, tglx, Caleb James DeLisle
This timer was first developed on the EN751221 which is a MIPS 34Kc
and 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 use the
standard mti,gic compatible interrupt controller. This interrupt
controller uses a different IRQ number for each timer interrupt.
Support both interrupt modes, percpu and individual IRQ per timer.
This is based on work by Ahmed Naseef but has been refactored and
broken up since then.
Originally-by: Ahmed Naseef <naseefkm@gmail.com>
Link: https://github.com/openwrt/openwrt/commit/fab098cb6121647ca9cc6e501d56ebe8a9ea550b#diff-a09ee5e4166e89df337d03c1455dce7b81eb89797b1d0f714476b188e6685334
Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
---
drivers/clocksource/timer-econet-en751221.c | 74 ++++++++++++++++++++-
1 file changed, 72 insertions(+), 2 deletions(-)
diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
index 4b712eb4db6f..642af9fcda60 100644
--- a/drivers/clocksource/timer-econet-en751221.c
+++ b/drivers/clocksource/timer-econet-en751221.c
@@ -8,6 +8,7 @@
#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 +22,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 +111,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,
@@ -171,6 +193,44 @@ static int __init cevt_init(struct device_node *np)
return ret;
}
+static int __init cevt_init_multi_irq(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);
+ }
+
+ return 0;
+
+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)
{
struct clk *clk;
@@ -184,6 +244,12 @@ static int __init timer_init(struct device_node *np)
econet_timer.freq_hz = clk_get_rate(clk);
+ 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 (int i = 0; i < ARRAY_SIZE(econet_timer.membase); i++) {
econet_timer.membase[i] = of_iomap(np, i);
if (!econet_timer.membase[i]) {
@@ -202,7 +268,11 @@ static int __init timer_init(struct device_node *np)
goto err_unmap;
}
- ret = cevt_init(np);
+ if (is_percpu_irq())
+ ret = cevt_init(np);
+ else
+ ret = cevt_init_multi_irq(np);
+
if (ret < 0)
goto err_unmap;
--
2.39.5
^ permalink raw reply related [flat|nested] 6+ messages in thread