[PATCH v3 4/4] clocksource/timer-econet-en751221: Support EN751627 without percpu IRQ
From: Caleb James DeLisle
Date: Sat May 16 2026 - 14:28:45 EST
EN751627 is based on the 1004Kc which uses a different interrupt number
for each CPU timer. Support both this and the EN751221 which uses a
single percpu interrupt.
Signed-off-by: Caleb James DeLisle <cjd@xxxxxxxx>
---
drivers/clocksource/timer-econet-en751221.c | 110 ++++++++++++++++----
1 file changed, 87 insertions(+), 23 deletions(-)
diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
index ed750e39cc4f..dea6dbafa16e 100644
--- a/drivers/clocksource/timer-econet-en751221.c
+++ b/drivers/clocksource/timer-econet-en751221.c
@@ -21,10 +21,12 @@
#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_NUM_IRQS NR_CPUS
static struct {
void __iomem *membase[ECONET_NUM_BLOCKS];
- int irq;
+ int irqs[ECONET_NUM_IRQS];
+ bool is_percpu;
u32 freq_hz;
} econet_timer __ro_after_init;
@@ -99,6 +101,25 @@ static int cevt_init_cpu(uint cpu)
struct clock_event_device *cd = &per_cpu(econet_timer_pcpu, cpu);
u32 reg;
+ if (!reg_ctl(cpu)) {
+ pr_err("%s: missing address resource for CPU %d\n", cd->name,
+ cpu);
+ return -EINVAL;
+ }
+ if (cd->irq <= 0) {
+ pr_err("%s: missing IRQ for CPU %d\n", cd->name, cpu);
+ return -EINVAL;
+ }
+ if (!econet_timer.is_percpu) {
+ int ret = irq_force_affinity(cd->irq, cpumask_of(cpu));
+
+ if (ret) {
+ pr_err("%s: failed to set IRQ affinity to CPU %d: %pe\n",
+ cd->name, cpu, ERR_PTR(ret));
+ return ret;
+ }
+ }
+
pr_debug("%s: Setting up clockevent for CPU %d\n", cd->name, cpu);
reg = ioread32(reg_ctl(cpu)) | ctl_bit_enabled(cpu);
@@ -107,7 +128,10 @@ static int cevt_init_cpu(uint cpu)
clockevents_config_and_register(cd, econet_timer.freq_hz,
ECONET_MIN_DELTA, ECONET_MAX_DELTA);
- enable_percpu_irq(cd->irq, IRQ_TYPE_NONE);
+ if (econet_timer.is_percpu)
+ enable_percpu_irq(cd->irq, IRQ_TYPE_NONE);
+ else
+ enable_irq(cd->irq);
return 0;
}
@@ -138,7 +162,12 @@ static void __init cevt_init(struct device_node *np)
CLOCK_EVT_FEAT_C3STOP |
CLOCK_EVT_FEAT_PERCPU;
cd->set_next_event = cevt_set_next_event;
- cd->irq = econet_timer.irq;
+
+ if (econet_timer.is_percpu)
+ cd->irq = econet_timer.irqs[0];
+ else
+ cd->irq = econet_timer.irqs[i];
+
cd->cpumask = cpumask_of(i);
cd->name = np->name;
@@ -148,9 +177,23 @@ static void __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);
+ int num_blocks = of_address_count(np);
+ int num_irqs = of_irq_count(np);
struct clk *clk;
- int ret;
+ int ret, i;
+
+ econet_timer.is_percpu = of_device_is_compatible(np, "econet,en751221-timer");
+
+ if (econet_timer.is_percpu && num_irqs != 1) {
+ pr_err("%pOFn: EN751221 clock must have 1 IRQ not %d\n", np,
+ num_irqs);
+ return -EINVAL;
+ }
+ if (num_irqs > ARRAY_SIZE(econet_timer.irqs)) {
+ pr_err("%pOFn: Too many IRQs max %d got %d\n", np,
+ ARRAY_SIZE(econet_timer.irqs), num_irqs);
+ return -EINVAL;
+ }
clk = of_clk_get(np, 0);
if (IS_ERR(clk)) {
@@ -160,7 +203,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 (i = 0; i < 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);
@@ -169,22 +212,33 @@ static int __init timer_init(struct device_node *np)
}
}
- econet_timer.irq = irq_of_parse_and_map(np, 0);
- if (econet_timer.irq <= 0) {
- pr_err("%pOFn: irq_of_parse_and_map failed\n", np);
- ret = -EINVAL;
- goto out_membase;
+ for (i = 0; i < num_irqs; i++) {
+ econet_timer.irqs[i] = irq_of_parse_and_map(np, i);
+ if (econet_timer.irqs[i] <= 0) {
+ pr_err("%pOFn: failed mapping irq %d\n", np, i);
+ ret = -EINVAL;
+ goto out_irq_mapping;
+ }
}
- irq_set_status_flags(econet_timer.irq, IRQ_NOAUTOEN);
-
- ret = request_percpu_irq(econet_timer.irq, cevt_interrupt, np->name,
- &econet_timer_pcpu);
-
- if (ret < 0) {
- pr_err("%pOFn: IRQ %d setup failed (%d)\n", np,
- econet_timer.irq, ret);
- goto out_irq_mapping;
+ for (i = 0; i < num_irqs; i++) {
+ irq_set_status_flags(econet_timer.irqs[i], IRQ_NOAUTOEN);
+
+ if (econet_timer.is_percpu)
+ ret = request_percpu_irq(econet_timer.irqs[i],
+ cevt_interrupt, np->name,
+ &econet_timer_pcpu);
+ else
+ 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: %pe\n", np,
+ i, ERR_PTR(ret));
+ i--;
+ goto out_irq_free;
+ }
}
cevt_init(np);
@@ -216,11 +270,20 @@ static int __init timer_init(struct device_node *np)
return 0;
out_irq_free:
- free_percpu_irq(econet_timer.irq, &econet_timer_pcpu);
+ for (; i >= 0; i--) {
+ if (econet_timer.is_percpu) {
+ free_percpu_irq(econet_timer.irqs[i], &econet_timer_pcpu);
+ } else {
+ free_irq(econet_timer.irqs[i], NULL);
+ }
+ }
out_irq_mapping:
- irq_dispose_mapping(econet_timer.irq);
+ for (i = 0; i < num_irqs; i++) {
+ if (econet_timer.irqs[i] > 0)
+ irq_dispose_mapping(econet_timer.irqs[i]);
+ }
out_membase:
- for (int i = 0; i < ARRAY_SIZE(econet_timer.membase); i++) {
+ for (i = 0; i < num_blocks; i++) {
if (econet_timer.membase[i])
iounmap(econet_timer.membase[i]);
}
@@ -229,3 +292,4 @@ static int __init timer_init(struct device_node *np)
}
TIMER_OF_DECLARE(econet_timer_hpt, "econet,en751221-timer", timer_init);
+TIMER_OF_DECLARE(econet_timer_en751627, "econet,en751627-timer", timer_init);
--
2.39.5