From 1ccca10755107bc8c649937d1ba69651d1ef9da2 Mon Sep 17 00:00:00 2001 From: Nick Hu Date: Sun, 19 Apr 2026 23:18:55 -0700 Subject: [PATCH 01/18] dt-bindings: timer: Remove sifive,fine-ctr-bits property The counter width can be inferred from the compatible string, making the explicit "sifive,fine-ctr-bits" property redundant. Remove the property to simplify the bindings. Fixes: 0f920690a82c ("dt-bindings: timer: Add SiFive CLINT2") Suggested-by: Conor Dooley Signed-off-by: Nick Hu Signed-off-by: Daniel Lezcano Acked-by: Conor Dooley Link: https://lore.kernel.org/linux-riscv/20260330-relative-hardened-5ce35fe1ef57@spud/ Link: https://patch.msgid.link/20260419-clintv2-remove-fine-ctr-v1-1-7527f4d45850@sifive.com --- .../devicetree/bindings/timer/sifive,clint.yaml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/Documentation/devicetree/bindings/timer/sifive,clint.yaml b/Documentation/devicetree/bindings/timer/sifive,clint.yaml index 3c16b260db04..051edb1da0d7 100644 --- a/Documentation/devicetree/bindings/timer/sifive,clint.yaml +++ b/Documentation/devicetree/bindings/timer/sifive,clint.yaml @@ -72,22 +72,6 @@ properties: minItems: 1 maxItems: 4095 - sifive,fine-ctr-bits: - maximum: 15 - description: The width in bits of the fine counter. - -if: - properties: - compatible: - contains: - const: sifive,clint2 -then: - required: - - sifive,fine-ctr-bits -else: - properties: - sifive,fine-ctr-bits: false - additionalProperties: false required: From 6afd8c292d678091d3ba873295cddc1d0502f476 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Fri, 10 Apr 2026 19:35:26 +0300 Subject: [PATCH 02/18] dt-bindings: timer: renesas,rz-mtu3: Remove TCIU8 interrupt The TCIU8 interrupt used to be documented in earlier revisions of the user manuals, but has since been removed. The corresponding entry is now marked as reserved in the interrupt mapping tables of all supported SoCs. * Page 486, Table 8.2 Interrupt mapping (7/13) in the Renesas RZ/G2UL Rev.1.40 User Manual * Page 363, Table 8.2 Interrupt Mapping (6/13) in the Renesas RZ/Five Rev.1.30 User Manual * Page 528, Table 8.2 Interrupt mapping (7/13) in the Renesas RZ/G2L and RZ/G2LC Rev.1.50 User Manual * Page 540, Table 8.2 Interrupt mapping (7/13) in the Renesas RZ/V2L Rev.1.50 User Manual Remove the TCIU8 interrupt. Signed-off-by: Cosmin Tanislav Signed-off-by: Daniel Lezcano Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260410163530.383818-7-cosmin-gabriel.tanislav.xa@renesas.com --- .../devicetree/bindings/timer/renesas,rz-mtu3.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Documentation/devicetree/bindings/timer/renesas,rz-mtu3.yaml b/Documentation/devicetree/bindings/timer/renesas,rz-mtu3.yaml index 3ad10c5b66ba..00cd5cbcf6e9 100644 --- a/Documentation/devicetree/bindings/timer/renesas,rz-mtu3.yaml +++ b/Documentation/devicetree/bindings/timer/renesas,rz-mtu3.yaml @@ -162,7 +162,6 @@ properties: - description: MTU8.TGRC input capture/compare match - description: MTU8.TGRD input capture/compare match - description: MTU8.TCNT overflow - - description: MTU8.TCNT underflow interrupt-names: items: @@ -209,7 +208,6 @@ properties: - const: tgic8 - const: tgid8 - const: tciv8 - - const: tciu8 clocks: maxItems: 1 @@ -287,8 +285,7 @@ examples: , , , - , - ; + ; interrupt-names = "tgia0", "tgib0", "tgic0", "tgid0", "tciv0", "tgie0", "tgif0", "tgia1", "tgib1", "tciv1", "tciu1", @@ -298,7 +295,7 @@ examples: "tgiu5", "tgiv5", "tgiw5", "tgia6", "tgib6", "tgic6", "tgid6", "tciv6", "tgia7", "tgib7", "tgic7", "tgid7", "tciv7", - "tgia8", "tgib8", "tgic8", "tgid8", "tciv8", "tciu8"; + "tgia8", "tgib8", "tgic8", "tgid8", "tciv8"; clocks = <&cpg CPG_MOD R9A07G044_MTU_X_MCK_MTU3>; power-domains = <&cpg>; resets = <&cpg R9A07G044_MTU_X_PRESET_MTU3>; From 1a0797fab5c86f4d907fea06846d226e43bed32c Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Fri, 10 Apr 2026 19:35:27 +0300 Subject: [PATCH 03/18] dt-bindings: timer: renesas,rz-mtu3: document RZ/{T2H,N2H} Compared to the previously supported SoCs, the Renesas RZ/T2H and RZ/N2H SoCs do not have a reset line. Add support for them by moving the required reset into a conditional matching all compatibles for the existing SoCs. Disable the resets for RZ/T2H and RZ/N2H. Document RZ/T2H and RZ/N2H, and use the generic compatible as a fallback, as functionality is the same. Signed-off-by: Cosmin Tanislav Signed-off-by: Daniel Lezcano Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260410163530.383818-8-cosmin-gabriel.tanislav.xa@renesas.com --- .../bindings/timer/renesas,rz-mtu3.yaml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/timer/renesas,rz-mtu3.yaml b/Documentation/devicetree/bindings/timer/renesas,rz-mtu3.yaml index 00cd5cbcf6e9..ecff2912d812 100644 --- a/Documentation/devicetree/bindings/timer/renesas,rz-mtu3.yaml +++ b/Documentation/devicetree/bindings/timer/renesas,rz-mtu3.yaml @@ -112,6 +112,8 @@ properties: - renesas,r9a07g043-mtu3 # RZ/{G2UL,Five} - renesas,r9a07g044-mtu3 # RZ/G2{L,LC} - renesas,r9a07g054-mtu3 # RZ/V2L + - renesas,r9a09g077-mtu3 # RZ/T2H + - renesas,r9a09g087-mtu3 # RZ/N2H - const: renesas,rz-mtu3 reg: @@ -231,7 +233,22 @@ required: - interrupt-names - clocks - power-domains - - resets + +allOf: + - if: + properties: + compatible: + contains: + enum: + - renesas,r9a07g043-mtu3 + - renesas,r9a07g044-mtu3 + - renesas,r9a07g054-mtu3 + then: + required: + - resets + else: + properties: + resets: false additionalProperties: false From 0712d2137dd832a4442156a04cdbb34cbe14d872 Mon Sep 17 00:00:00 2001 From: Ley Foon Tan Date: Thu, 2 Apr 2026 01:40:16 -0700 Subject: [PATCH 04/18] dt-bindings: timer: Add StarFive JHB100 clint Add compatible string for the StarFive JHB100 clint. Signed-off-by: Ley Foon Tan Signed-off-by: Changhuang Liang Signed-off-by: Daniel Lezcano Acked-by: Conor Dooley Link: https://patch.msgid.link/20260402084019.440708-3-changhuang.liang@starfivetech.com --- Documentation/devicetree/bindings/timer/sifive,clint.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/timer/sifive,clint.yaml b/Documentation/devicetree/bindings/timer/sifive,clint.yaml index 051edb1da0d7..67cea8edb59f 100644 --- a/Documentation/devicetree/bindings/timer/sifive,clint.yaml +++ b/Documentation/devicetree/bindings/timer/sifive,clint.yaml @@ -38,6 +38,7 @@ properties: - starfive,jh7100-clint # StarFive JH7100 - starfive,jh7110-clint # StarFive JH7110 - starfive,jh8100-clint # StarFive JH8100 + - starfive,jhb100-clint # StarFive JHB100 - tenstorrent,blackhole-clint # Tenstorrent Blackhole - const: sifive,clint0 # SiFive CLINT v0 IP block - items: From fee410ecff3b9aef22f105e693067610ca27e067 Mon Sep 17 00:00:00 2001 From: Michal Piekos Date: Tue, 28 Apr 2026 18:26:58 +0200 Subject: [PATCH 05/18] dt-bindings: timer: allwinner,sun5i-a13-hstimer: add H616 and D1 D1 is similar to existing sun5i, but with different register offsets. H616 uses same offsets as D1. Add allwinner,sun20i-d1-hstimer Add allwinner,sun50i-h616-hstimer with fallback to allwinner,sun20i-d1-hstimer Extend schema condition for interrupts to cover D1 compatible variant. Signed-off-by: Michal Piekos Signed-off-by: Daniel Lezcano Acked-by: Conor Dooley Link: https://patch.msgid.link/20260428-h616-t113s-hstimer-v3-1-7e02178a93ee@mmpsystems.pl --- .../bindings/timer/allwinner,sun5i-a13-hstimer.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/timer/allwinner,sun5i-a13-hstimer.yaml b/Documentation/devicetree/bindings/timer/allwinner,sun5i-a13-hstimer.yaml index f1853daec2f9..3e2725c56995 100644 --- a/Documentation/devicetree/bindings/timer/allwinner,sun5i-a13-hstimer.yaml +++ b/Documentation/devicetree/bindings/timer/allwinner,sun5i-a13-hstimer.yaml @@ -15,9 +15,13 @@ properties: oneOf: - const: allwinner,sun5i-a13-hstimer - const: allwinner,sun7i-a20-hstimer + - const: allwinner,sun20i-d1-hstimer - items: - const: allwinner,sun6i-a31-hstimer - const: allwinner,sun7i-a20-hstimer + - items: + - const: allwinner,sun50i-h616-hstimer + - const: allwinner,sun20i-d1-hstimer reg: maxItems: 1 @@ -45,7 +49,10 @@ required: if: properties: compatible: - const: allwinner,sun5i-a13-hstimer + anyOf: + - const: allwinner,sun5i-a13-hstimer + - contains: + const: allwinner,sun20i-d1-hstimer then: properties: From f182fa740218dec7ead6275b2e096da1642272b5 Mon Sep 17 00:00:00 2001 From: Michal Piekos Date: Tue, 28 Apr 2026 18:26:59 +0200 Subject: [PATCH 06/18] clocksource/drivers/sun5i: Add D1 hstimer support D1 high speed timer differs from existing timer-sun5i by register base offset. Add sunxi quirks to handle D1 specific offset. Add D1 compatible string to OF match table. Signed-off-by: Michal Piekos Signed-off-by: Daniel Lezcano Reviewed-by: Chen-Yu Tsai Link: https://patch.msgid.link/20260428-h616-t113s-hstimer-v3-2-7e02178a93ee@mmpsystems.pl --- drivers/clocksource/timer-sun5i.c | 84 ++++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 19 deletions(-) diff --git a/drivers/clocksource/timer-sun5i.c b/drivers/clocksource/timer-sun5i.c index d7e012992170..6ab300d22621 100644 --- a/drivers/clocksource/timer-sun5i.c +++ b/drivers/clocksource/timer-sun5i.c @@ -18,21 +18,30 @@ #include #include -#define TIMER_IRQ_EN_REG 0x00 +#define TIMER_IRQ_EN_REG 0x00 #define TIMER_IRQ_EN(val) BIT(val) -#define TIMER_IRQ_ST_REG 0x04 -#define TIMER_CTL_REG(val) (0x20 * (val) + 0x10) +#define TIMER_IRQ_ST_REG 0x04 +#define TIMER_CTL_REG(val, offset) (0x20 * (val) + 0x10 + (offset)) #define TIMER_CTL_ENABLE BIT(0) #define TIMER_CTL_RELOAD BIT(1) #define TIMER_CTL_CLK_PRES(val) (((val) & 0x7) << 4) #define TIMER_CTL_ONESHOT BIT(7) -#define TIMER_INTVAL_LO_REG(val) (0x20 * (val) + 0x14) -#define TIMER_INTVAL_HI_REG(val) (0x20 * (val) + 0x18) -#define TIMER_CNTVAL_LO_REG(val) (0x20 * (val) + 0x1c) -#define TIMER_CNTVAL_HI_REG(val) (0x20 * (val) + 0x20) +#define TIMER_INTVAL_LO_REG(val, offset) (0x20 * (val) + 0x14 + (offset)) +#define TIMER_INTVAL_HI_REG(val, offset) (0x20 * (val) + 0x18 + (offset)) +#define TIMER_CNTVAL_LO_REG(val, offset) (0x20 * (val) + 0x1c + (offset)) +#define TIMER_CNTVAL_HI_REG(val, offset) (0x20 * (val) + 0x20 + (offset)) #define TIMER_SYNC_TICKS 3 +/** + * struct sunxi_timer_quirks - Differences between SoC variants. + * + * @from_ctl_base_offset: offset applied from ctl register onwards + */ +struct sunxi_timer_quirks { + u32 from_ctl_base_offset; +}; + struct sun5i_timer { void __iomem *base; struct clk *clk; @@ -40,6 +49,7 @@ struct sun5i_timer { u32 ticks_per_jiffy; struct clocksource clksrc; struct clock_event_device clkevt; + const struct sunxi_timer_quirks *quirks; }; #define nb_to_sun5i_timer(x) \ @@ -57,28 +67,36 @@ struct sun5i_timer { */ static void sun5i_clkevt_sync(struct sun5i_timer *ce) { - u32 old = readl(ce->base + TIMER_CNTVAL_LO_REG(1)); + u32 offset = ce->quirks->from_ctl_base_offset; + u32 old = readl(ce->base + TIMER_CNTVAL_LO_REG(1, offset)); - while ((old - readl(ce->base + TIMER_CNTVAL_LO_REG(1))) < TIMER_SYNC_TICKS) + while ((old - readl(ce->base + TIMER_CNTVAL_LO_REG(1, offset))) < + TIMER_SYNC_TICKS) cpu_relax(); } static void sun5i_clkevt_time_stop(struct sun5i_timer *ce, u8 timer) { - u32 val = readl(ce->base + TIMER_CTL_REG(timer)); - writel(val & ~TIMER_CTL_ENABLE, ce->base + TIMER_CTL_REG(timer)); + u32 offset = ce->quirks->from_ctl_base_offset; + u32 val = readl(ce->base + TIMER_CTL_REG(timer, offset)); + + writel(val & ~TIMER_CTL_ENABLE, + ce->base + TIMER_CTL_REG(timer, offset)); sun5i_clkevt_sync(ce); } static void sun5i_clkevt_time_setup(struct sun5i_timer *ce, u8 timer, u32 delay) { - writel(delay, ce->base + TIMER_INTVAL_LO_REG(timer)); + u32 offset = ce->quirks->from_ctl_base_offset; + + writel(delay, ce->base + TIMER_INTVAL_LO_REG(timer, offset)); } static void sun5i_clkevt_time_start(struct sun5i_timer *ce, u8 timer, bool periodic) { - u32 val = readl(ce->base + TIMER_CTL_REG(timer)); + u32 offset = ce->quirks->from_ctl_base_offset; + u32 val = readl(ce->base + TIMER_CTL_REG(timer, offset)); if (periodic) val &= ~TIMER_CTL_ONESHOT; @@ -86,7 +104,7 @@ static void sun5i_clkevt_time_start(struct sun5i_timer *ce, u8 timer, bool perio val |= TIMER_CTL_ONESHOT; writel(val | TIMER_CTL_ENABLE | TIMER_CTL_RELOAD, - ce->base + TIMER_CTL_REG(timer)); + ce->base + TIMER_CTL_REG(timer, offset)); } static int sun5i_clkevt_shutdown(struct clock_event_device *clkevt) @@ -141,8 +159,9 @@ static irqreturn_t sun5i_timer_interrupt(int irq, void *dev_id) static u64 sun5i_clksrc_read(struct clocksource *clksrc) { struct sun5i_timer *cs = clksrc_to_sun5i_timer(clksrc); + u32 offset = cs->quirks->from_ctl_base_offset; - return ~readl(cs->base + TIMER_CNTVAL_LO_REG(1)); + return ~readl(cs->base + TIMER_CNTVAL_LO_REG(1, offset)); } static int sun5i_rate_cb(struct notifier_block *nb, @@ -173,12 +192,13 @@ static int sun5i_setup_clocksource(struct platform_device *pdev, unsigned long rate) { struct sun5i_timer *cs = platform_get_drvdata(pdev); + u32 offset = cs->quirks->from_ctl_base_offset; void __iomem *base = cs->base; int ret; - writel(~0, base + TIMER_INTVAL_LO_REG(1)); + writel(~0, base + TIMER_INTVAL_LO_REG(1, offset)); writel(TIMER_CTL_ENABLE | TIMER_CTL_RELOAD, - base + TIMER_CTL_REG(1)); + base + TIMER_CTL_REG(1, offset)); cs->clksrc.name = pdev->dev.of_node->name; cs->clksrc.rating = 340; @@ -237,6 +257,7 @@ static int sun5i_setup_clockevent(struct platform_device *pdev, static int sun5i_timer_probe(struct platform_device *pdev) { + const struct sunxi_timer_quirks *quirks; struct device *dev = &pdev->dev; struct sun5i_timer *st; struct reset_control *rstc; @@ -273,11 +294,18 @@ static int sun5i_timer_probe(struct platform_device *pdev) return -EINVAL; } + quirks = of_device_get_match_data(&pdev->dev); + if (!quirks) { + dev_err(&pdev->dev, "Failed to determine the quirks to use\n"); + return -ENODEV; + } + st->base = timer_base; st->ticks_per_jiffy = DIV_ROUND_UP(rate, HZ); st->clk = clk; st->clk_rate_cb.notifier_call = sun5i_rate_cb; st->clk_rate_cb.next = NULL; + st->quirks = quirks; ret = devm_clk_notifier_register(dev, clk, &st->clk_rate_cb); if (ret) { @@ -314,9 +342,27 @@ static void sun5i_timer_remove(struct platform_device *pdev) clocksource_unregister(&st->clksrc); } +static const struct sunxi_timer_quirks sun5i_sun7i_hstimer_quirks = { + .from_ctl_base_offset = 0x0, +}; + +static const struct sunxi_timer_quirks sun20i_d1_hstimer_quirks = { + .from_ctl_base_offset = 0x10, +}; + static const struct of_device_id sun5i_timer_of_match[] = { - { .compatible = "allwinner,sun5i-a13-hstimer" }, - { .compatible = "allwinner,sun7i-a20-hstimer" }, + { + .compatible = "allwinner,sun5i-a13-hstimer", + .data = &sun5i_sun7i_hstimer_quirks, + }, + { + .compatible = "allwinner,sun7i-a20-hstimer", + .data = &sun5i_sun7i_hstimer_quirks, + }, + { + .compatible = "allwinner,sun20i-d1-hstimer", + .data = &sun20i_d1_hstimer_quirks, + }, {}, }; MODULE_DEVICE_TABLE(of, sun5i_timer_of_match); From 002dcb3a893627fcedf12692063b80c720869ea5 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Sat, 23 May 2026 15:02:26 +0100 Subject: [PATCH 07/18] ACPI: GTDT: Account for GTDTv3 size when walking the platform timer descriptors Since ARMv8.1, the architecture has grown an EL2-private virtual timer. This has been described in ACPI since ACPI v6.3 and revision 3 of the GTDT table. An aditional structure was added in ACPICA, though in a rather bizarre way, and merged in v5.1 as 8f5a14d053100 ("ACPICA: ACPI 6.3: add GTDT Revision 3 support"). Finally plug the table parsing in GTDT, and correct the parsing of the platform timer subtables to account for the expanded size of the base table. This also comes with some extra sanitisation of the table, in the unlikely case someone got it wrong... Suggested-by: Sudeep Holla Signed-off-by: Marc Zyngier Signed-off-by: Daniel Lezcano Reviewed-by: Hanjun Guo Reviewed-by: Sudeep Holla Link: https://patch.msgid.link/20260523140242.586031-2-maz@kernel.org --- drivers/acpi/arm64/gtdt.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/arm64/gtdt.c b/drivers/acpi/arm64/gtdt.c index ffc867bac2d6..950d5efdf85e 100644 --- a/drivers/acpi/arm64/gtdt.c +++ b/drivers/acpi/arm64/gtdt.c @@ -34,14 +34,25 @@ struct acpi_gtdt_descriptor { void *platform_timer; }; +struct gtdt_v3 { + struct acpi_table_gtdt gtdt_v2; + struct acpi_gtdt_el2 el2_vtimer; +}; + static struct acpi_gtdt_descriptor acpi_gtdt_desc __initdata; static __init bool platform_timer_valid(void *platform_timer) { struct acpi_gtdt_header *gh = platform_timer; + void *platform_timer_begin; - return (platform_timer >= (void *)(acpi_gtdt_desc.gtdt + 1) && - platform_timer < acpi_gtdt_desc.gtdt_end && + if (acpi_gtdt_desc.gtdt->header.revision >= 3) + platform_timer_begin = container_of(acpi_gtdt_desc.gtdt, struct gtdt_v3, gtdt_v2) + 1; + else + platform_timer_begin = acpi_gtdt_desc.gtdt + 1; + + return (platform_timer >= platform_timer_begin && + platform_timer + sizeof(*gh) <= acpi_gtdt_desc.gtdt_end && gh->length != 0 && platform_timer + gh->length <= acpi_gtdt_desc.gtdt_end); } @@ -166,6 +177,13 @@ int __init acpi_gtdt_init(struct acpi_table_header *table, u32 cnt = 0; gtdt = container_of(table, struct acpi_table_gtdt, header); + + if ((gtdt->header.revision >= 3 && gtdt->header.length < sizeof(struct gtdt_v3)) || + (gtdt->header.revision == 2 && gtdt->header.length < sizeof(*gtdt))) { + pr_err(FW_BUG "GTDT with invalid size %d\n", gtdt->header.length); + return -EINVAL; + } + acpi_gtdt_desc.gtdt = gtdt; acpi_gtdt_desc.gtdt_end = (void *)table + table->length; acpi_gtdt_desc.platform_timer = NULL; From fe15af3e75298533056ce73f8e66cd3da31f2b4a Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Sat, 23 May 2026 15:02:27 +0100 Subject: [PATCH 08/18] ACPI: GTDT: Parse information related to the EL2 virtual timer Now that we have a way to identify GTDTv3, allow the information related to the EL2 virtual timer to be retrieved by the interface used by the architected timer driver. Signed-off-by: Marc Zyngier Signed-off-by: Daniel Lezcano Reviewed-by: Sudeep Holla Reviewed-by: Hanjun Guo Link: https://patch.msgid.link/20260523140242.586031-3-maz@kernel.org --- drivers/acpi/arm64/gtdt.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/acpi/arm64/gtdt.c b/drivers/acpi/arm64/gtdt.c index 950d5efdf85e..00158c8aa6d9 100644 --- a/drivers/acpi/arm64/gtdt.c +++ b/drivers/acpi/arm64/gtdt.c @@ -41,6 +41,14 @@ struct gtdt_v3 { static struct acpi_gtdt_descriptor acpi_gtdt_desc __initdata; +static __init struct acpi_gtdt_el2 *gtdt_to_el2_vtimer(struct acpi_table_gtdt *gtdt) +{ + if (gtdt->header.revision < 3) + return NULL; + + return &container_of(gtdt, struct gtdt_v3, gtdt_v2)->el2_vtimer; +} + static __init bool platform_timer_valid(void *platform_timer) { struct acpi_gtdt_header *gh = platform_timer; @@ -112,6 +120,7 @@ static int __init map_gt_gsi(u32 interrupt, u32 flags) int __init acpi_gtdt_map_ppi(int type) { struct acpi_table_gtdt *gtdt = acpi_gtdt_desc.gtdt; + struct acpi_gtdt_el2 *el2_vtimer = gtdt_to_el2_vtimer(gtdt); switch (type) { case ARCH_TIMER_PHYS_NONSECURE_PPI: @@ -124,6 +133,12 @@ int __init acpi_gtdt_map_ppi(int type) case ARCH_TIMER_HYP_PPI: return map_gt_gsi(gtdt->non_secure_el2_interrupt, gtdt->non_secure_el2_flags); + case ARCH_TIMER_HYP_VIRT_PPI: + if (el2_vtimer && el2_vtimer->virtual_el2_timer_gsiv) + return map_gt_gsi(el2_vtimer->virtual_el2_timer_gsiv, + el2_vtimer->virtual_el2_timer_flags); + + return 0; default: pr_err("Failed to map timer interrupt: invalid type.\n"); } @@ -141,6 +156,7 @@ int __init acpi_gtdt_map_ppi(int type) bool __init acpi_gtdt_c3stop(int type) { struct acpi_table_gtdt *gtdt = acpi_gtdt_desc.gtdt; + struct acpi_gtdt_el2 *el2_vtimer = gtdt_to_el2_vtimer(gtdt); switch (type) { case ARCH_TIMER_PHYS_NONSECURE_PPI: @@ -152,6 +168,10 @@ bool __init acpi_gtdt_c3stop(int type) case ARCH_TIMER_HYP_PPI: return !(gtdt->non_secure_el2_flags & ACPI_GTDT_ALWAYS_ON); + case ARCH_TIMER_HYP_VIRT_PPI: + return el2_vtimer && el2_vtimer->virtual_el2_timer_gsiv && + !(el2_vtimer->virtual_el2_timer_flags & ACPI_GTDT_ALWAYS_ON); + default: pr_err("Failed to get c3stop info: invalid type.\n"); } From d87773de9efe1df6fe2ba379926f9df92f1a5913 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Sat, 23 May 2026 15:02:28 +0100 Subject: [PATCH 09/18] clocksource/drivers/arm_arch_timer: Default to EL2 virtual timer when running VHE When running with at EL2 with VHE enabled, the architecture provides two EL2 timer/counters, dubbed physical and virtual. Apart from their names, they are strictly identical. However, they don't get virtualised the same way, specially when it comes to adding arbitrary offsets to the timers. When running as a guest, the host CNTVOFF_EL2 does apply to the guest's view of CNTHV*_El2. This is not true for CNTPOFF_EL2 and CNTHP*_EL2, as the architecture is broken past the first level of virtualisation (it lacks some essential mechanisms to be usable, despite what the ARM ARM pretends). This means that when running as a L2 guest hypervisor, using the physical timer results in traps to L0, which are then forwarded to L1 in order to emulate the offset, leading to even worse performance due to massive trap amplification (the combination of register and ERET trapping is absolutely lethal). Switch the arch timer code to using the virtual timer when running in VHE by default, only using the physical timer if the interrupt is not correctly described in the firmware tables (which seems to be an unfortunately common case). This comes as no impact on bare-metal, and slightly improves the situation in the virtualised case. Signed-off-by: Marc Zyngier Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260523140242.586031-4-maz@kernel.org --- drivers/clocksource/arm_arch_timer.c | 55 +++++++++++++++++----------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/drivers/clocksource/arm_arch_timer.c b/drivers/clocksource/arm_arch_timer.c index 90aeff44a276..4adf756423de 100644 --- a/drivers/clocksource/arm_arch_timer.c +++ b/drivers/clocksource/arm_arch_timer.c @@ -688,6 +688,7 @@ static void __arch_timer_setup(struct clock_event_device *clk) clk->irq = arch_timer_ppi[arch_timer_uses_ppi]; switch (arch_timer_uses_ppi) { case ARCH_TIMER_VIRT_PPI: + case ARCH_TIMER_HYP_VIRT_PPI: clk->set_state_shutdown = arch_timer_shutdown_virt; clk->set_state_oneshot_stopped = arch_timer_shutdown_virt; sne = erratum_handler(set_next_event_virt); @@ -879,7 +880,7 @@ static void __init arch_timer_banner(void) pr_info("cp15 timer running at %lu.%02luMHz (%s).\n", (unsigned long)arch_timer_rate / 1000000, (unsigned long)(arch_timer_rate / 10000) % 100, - (arch_timer_uses_ppi == ARCH_TIMER_VIRT_PPI) ? "virt" : "phys"); + arch_timer_ppi_names[arch_timer_uses_ppi]); } u32 arch_timer_get_rate(void) @@ -912,7 +913,8 @@ static void __init arch_counter_register(void) int width; if ((IS_ENABLED(CONFIG_ARM64) && !is_hyp_mode_available()) || - arch_timer_uses_ppi == ARCH_TIMER_VIRT_PPI) { + arch_timer_uses_ppi == ARCH_TIMER_VIRT_PPI || + arch_timer_uses_ppi == ARCH_TIMER_HYP_VIRT_PPI) { if (arch_timer_counter_has_wa()) { rd = arch_counter_get_cntvct_stable; scr = raw_counter_get_cntvct_stable; @@ -1023,6 +1025,7 @@ static int __init arch_timer_register(void) ppi = arch_timer_ppi[arch_timer_uses_ppi]; switch (arch_timer_uses_ppi) { case ARCH_TIMER_VIRT_PPI: + case ARCH_TIMER_HYP_VIRT_PPI: err = request_percpu_irq(ppi, arch_timer_handler_virt, "arch_timer", arch_timer_evt); break; @@ -1090,25 +1093,34 @@ static int __init arch_timer_common_init(void) /** * arch_timer_select_ppi() - Select suitable PPI for the current system. * - * If HYP mode is available, we know that the physical timer - * has been configured to be accessible from PL1. Use it, so - * that a guest can use the virtual timer instead. + * On AArch32, if HYP mode is available, we know that the physical + * timer has been configured to be accessible from PL1. Use it, so + * that a guest can use the virtual timer instead (though KVM host + * support has long been removed). * - * On ARMv8.1 with VH extensions, the kernel runs in HYP. VHE - * accesses to CNTP_*_EL1 registers are silently redirected to - * their CNTHP_*_EL2 counterparts, and use a different PPI - * number. + * On ARMv8.1 with FEAT_VHE, the kernel runs in EL2. Accesses to + * CNTV_*_EL1 registers are silently redirected to their CNTHV_*_EL2 + * counterparts, and the timer uses a different PPI number. Similar + * thing happen when using the EL2 physical timer. Note that a bunch + * of DTs out there omit the virtual EL2 timer, so fallback gracefully + * on the physical timer. + * + * Without VHE, if no interrupt provided for virtual timer, we'll have + * to stick to the physical timer. It'd better be accessible... * - * If no interrupt provided for virtual timer, we'll have to - * stick to the physical timer. It'd better be accessible... * For arm64 we never use the secure interrupt. * * Return: a suitable PPI type for the current system. */ static enum arch_timer_ppi_nr __init arch_timer_select_ppi(void) { - if (is_kernel_in_hyp_mode()) + if (is_kernel_in_hyp_mode()) { + if (arch_timer_ppi[ARCH_TIMER_HYP_VIRT_PPI]) + return ARCH_TIMER_HYP_VIRT_PPI; + + pr_warn_once(FW_BUG "VHE-capable CPU without EL2 virtual timer interrupt\n"); return ARCH_TIMER_HYP_PPI; + } if (!is_hyp_mode_available() && arch_timer_ppi[ARCH_TIMER_VIRT_PPI]) return ARCH_TIMER_VIRT_PPI; @@ -1200,14 +1212,9 @@ static int __init arch_timer_acpi_init(struct acpi_table_header *table) if (ret) return ret; - arch_timer_ppi[ARCH_TIMER_PHYS_NONSECURE_PPI] = - acpi_gtdt_map_ppi(ARCH_TIMER_PHYS_NONSECURE_PPI); - - arch_timer_ppi[ARCH_TIMER_VIRT_PPI] = - acpi_gtdt_map_ppi(ARCH_TIMER_VIRT_PPI); - - arch_timer_ppi[ARCH_TIMER_HYP_PPI] = - acpi_gtdt_map_ppi(ARCH_TIMER_HYP_PPI); + /* The GTDT parser can't be bothered with the secure timer */ + for (int i = ARCH_TIMER_PHYS_NONSECURE_PPI; i < ARCH_TIMER_MAX_TIMER_PPI; i++) + arch_timer_ppi[i] = acpi_gtdt_map_ppi(i); arch_timer_populate_kvm_info(); @@ -1253,10 +1260,14 @@ int kvm_arch_ptp_get_crosststamp(u64 *cycle, struct timespec64 *ts, if (!IS_ENABLED(CONFIG_HAVE_ARM_SMCCC_DISCOVERY)) return -EOPNOTSUPP; - if (arch_timer_uses_ppi == ARCH_TIMER_VIRT_PPI) + switch (arch_timer_uses_ppi) { + case ARCH_TIMER_VIRT_PPI: + case ARCH_TIMER_HYP_VIRT_PPI: ptp_counter = KVM_PTP_VIRT_COUNTER; - else + break; + default: ptp_counter = KVM_PTP_PHYS_COUNTER; + } arm_smccc_1_1_invoke(ARM_SMCCC_VENDOR_HYP_KVM_PTP_FUNC_ID, ptp_counter, &hvc_res); From 6951e870868d0b4bc385cbc851b871effa595330 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Sat, 23 May 2026 15:02:29 +0100 Subject: [PATCH 10/18] dt-bindings: timer: arm,arch_timer: Fix requirements for interrupt description The arm,arch_timer DT binding is extremely imprecise in describing the requirements for interrupts. Follow the architecture by making it explicit that: - the EL1 secure timer irq is required if EL3 is implemented - the EL1 physical timer irq is always required - the EL1 virtual timer irq is always required - the EL2 physical timer irq is required if EL2 is implemented - the EL2 virtual timer irq is required if FEAT_VHE is implemented The consequence of the above is that the minimum number of interrupts to be described is 2, and not 1. Finally, clean up the description which made the assumption that the timers are plugged into a GIC (unfortunately, that's not always true), drop the MMIO nonsense that has long be moved to a separate binding, and use the architectural terminology to describe the various interrupts. Signed-off-by: Marc Zyngier Signed-off-by: Daniel Lezcano Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260523140242.586031-5-maz@kernel.org --- .../bindings/timer/arm,arch_timer.yaml | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/Documentation/devicetree/bindings/timer/arm,arch_timer.yaml b/Documentation/devicetree/bindings/timer/arm,arch_timer.yaml index c5fc3b6c8bd0..c65e48a155ab 100644 --- a/Documentation/devicetree/bindings/timer/arm,arch_timer.yaml +++ b/Documentation/devicetree/bindings/timer/arm,arch_timer.yaml @@ -10,13 +10,8 @@ maintainers: - Marc Zyngier - Mark Rutland description: |+ - ARM cores may have a per-core architected timer, which provides per-cpu timers, - or a memory mapped architected timer, which provides up to 8 frames with a - physical and optional virtual timer per frame. - - The per-core architected timer is attached to a GIC to deliver its - per-processor interrupts via PPIs. The memory mapped timer is attached to a GIC - to deliver its interrupts via SPIs. + The per-core architected timer is expected to deliver per-CPU interrupts + (commonly to a GIC to deliver its per-processor interrupts as PPIs). properties: compatible: @@ -33,13 +28,13 @@ properties: - const: arm,armv7-timer interrupts: - minItems: 1 + minItems: 2 items: - - description: secure timer irq - - description: non-secure timer irq - - description: virtual timer irq - - description: hypervisor timer irq - - description: hypervisor virtual timer irq + - description: EL1 secure physical timer irq, if EL3 is implemented + - description: EL1 non-secure physical timer irq + - description: EL1 virtual timer irq + - description: EL2 physical timer irq, if EL2 is implemented + - description: EL2 virtual timer irq, if FEAT_VHE is implemented interrupt-names: oneOf: From 8ee4e6dc8b960122b4c8504978d20774634b6633 Mon Sep 17 00:00:00 2001 From: "Markus Schneider-Pargmann (TI)" Date: Fri, 8 May 2026 15:39:27 +0200 Subject: [PATCH 11/18] clocksource/drivers/timer-ti-dm: Fix property name in comment ti,always-on property doesn't exist. ti,timer-alwon is meant here. Fix this minor bug in the comment. Signed-off-by: Markus Schneider-Pargmann (TI) Signed-off-by: Daniel Lezcano Reviewed-by: Kevin Hilman Reviewed-by: Dhruva Gole Link: https://patch.msgid.link/20260508-topic-ti-dm-clkevt-v6-16-v5-1-61d546a0aff9@baylibre.com --- drivers/clocksource/timer-ti-dm-systimer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clocksource/timer-ti-dm-systimer.c b/drivers/clocksource/timer-ti-dm-systimer.c index eb0dfe4b9b7c..3804c1234522 100644 --- a/drivers/clocksource/timer-ti-dm-systimer.c +++ b/drivers/clocksource/timer-ti-dm-systimer.c @@ -226,7 +226,7 @@ static bool __init dmtimer_is_preferred(struct device_node *np) * Some omap3 boards with unreliable oscillator must not use the counter_32k * or dmtimer1 with 32 KiHz source. Additionally, the boards with unreliable * oscillator should really set counter_32k as disabled, and delete dmtimer1 - * ti,always-on property, but let's not count on it. For these quirky cases, + * ti,timer-alwon property, but let's not count on it. For these quirky cases, * we prefer using the always-on secure dmtimer12 with the internal 32 KiHz * clock as the clocksource, and any available dmtimer as clockevent. * From b8eeeca5545659c5d67264b151784e74b18c8254 Mon Sep 17 00:00:00 2001 From: "Markus Schneider-Pargmann (TI)" Date: Fri, 8 May 2026 15:39:28 +0200 Subject: [PATCH 12/18] clocksource/drivers/timer-ti-dm: Add clocksource support Add support for using the TI Dual-Mode Timer as a clocksource. The driver automatically picks the first timer that is marked as always-on on with the "ti,timer-alwon" property to be the clocksource. The timer can then be used for CPU independent time keeping. Signed-off-by: Markus Schneider-Pargmann (TI) Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260508-topic-ti-dm-clkevt-v6-16-v5-2-61d546a0aff9@baylibre.com --- drivers/clocksource/timer-ti-dm.c | 103 ++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/drivers/clocksource/timer-ti-dm.c b/drivers/clocksource/timer-ti-dm.c index 793e7cdcb1b1..98cc34308768 100644 --- a/drivers/clocksource/timer-ti-dm.c +++ b/drivers/clocksource/timer-ti-dm.c @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -29,6 +30,7 @@ #include #include #include +#include #include #include @@ -148,6 +150,14 @@ static u32 omap_reserved_systimers; static LIST_HEAD(omap_timer_list); static DEFINE_SPINLOCK(dm_timer_lock); +struct dmtimer_clocksource { + struct clocksource dev; + struct dmtimer *timer; + unsigned int loadval; +}; + +static void __iomem *omap_dm_timer_sched_clock_counter; + enum { REQUEST_ANY = 0, REQUEST_BY_ID, @@ -1185,6 +1195,92 @@ static const struct dev_pm_ops omap_dm_timer_pm_ops = { static const struct of_device_id omap_timer_match[]; +static struct dmtimer_clocksource *omap_dm_timer_to_clocksource(struct clocksource *cs) +{ + return container_of(cs, struct dmtimer_clocksource, dev); +} + +static u64 omap_dm_timer_read_cycles(struct clocksource *cs) +{ + struct dmtimer_clocksource *clksrc = omap_dm_timer_to_clocksource(cs); + struct dmtimer *timer = clksrc->timer; + + return (u64)__omap_dm_timer_read_counter(timer); +} + +static u64 notrace omap_dm_timer_read_sched_clock(void) +{ + /* Posted mode is not active here, so we can read directly */ + return readl_relaxed(omap_dm_timer_sched_clock_counter); +} + +static void omap_dm_timer_clocksource_suspend(struct clocksource *cs) +{ + struct dmtimer_clocksource *clksrc = omap_dm_timer_to_clocksource(cs); + struct dmtimer *timer = clksrc->timer; + + clksrc->loadval = __omap_dm_timer_read_counter(timer); + __omap_dm_timer_stop(timer); +} + +static void omap_dm_timer_clocksource_resume(struct clocksource *cs) +{ + struct dmtimer_clocksource *clksrc = omap_dm_timer_to_clocksource(cs); + struct dmtimer *timer = clksrc->timer; + + dmtimer_write(timer, OMAP_TIMER_COUNTER_REG, clksrc->loadval); + dmtimer_write(timer, OMAP_TIMER_CTRL_REG, OMAP_TIMER_CTRL_ST | OMAP_TIMER_CTRL_AR); +} + +static void omap_dm_timer_clocksource_unregister(void *data) +{ + struct clocksource *cs = data; + + clocksource_unregister(cs); +} + +static int omap_dm_timer_setup_clocksource(struct dmtimer *timer) +{ + struct device *dev = &timer->pdev->dev; + struct dmtimer_clocksource *clksrc; + int err; + + __omap_dm_timer_init_regs(timer); + + timer->reserved = 1; + + clksrc = devm_kzalloc(dev, sizeof(*clksrc), GFP_KERNEL); + if (!clksrc) + return -ENOMEM; + + clksrc->timer = timer; + + clksrc->dev.name = "omap_dm_timer"; + clksrc->dev.rating = 300; + clksrc->dev.read = omap_dm_timer_read_cycles; + clksrc->dev.mask = CLOCKSOURCE_MASK(32); + clksrc->dev.flags = CLOCK_SOURCE_IS_CONTINUOUS; + clksrc->dev.suspend = omap_dm_timer_clocksource_suspend; + clksrc->dev.resume = omap_dm_timer_clocksource_resume; + + dmtimer_write(timer, OMAP_TIMER_COUNTER_REG, 0); + dmtimer_write(timer, OMAP_TIMER_LOAD_REG, 0); + dmtimer_write(timer, OMAP_TIMER_CTRL_REG, OMAP_TIMER_CTRL_ST | OMAP_TIMER_CTRL_AR); + + omap_dm_timer_sched_clock_counter = timer->func_base + _OMAP_TIMER_COUNTER_OFFSET; + sched_clock_register(omap_dm_timer_read_sched_clock, 32, timer->fclk_rate); + + err = clocksource_register_hz(&clksrc->dev, timer->fclk_rate); + if (err) + return dev_err_probe(dev, err, "Could not register as clocksource\n"); + + err = devm_add_action_or_reset(dev, omap_dm_timer_clocksource_unregister, &clksrc->dev); + if (err) + return dev_err_probe(dev, err, "Could not register clocksource_unregister action\n"); + + return 0; +} + /** * omap_dm_timer_probe - probe function called for every registered device * @pdev: pointer to current timer platform device @@ -1272,6 +1368,13 @@ static int omap_dm_timer_probe(struct platform_device *pdev) timer->pdev = pdev; + if (timer->capability & OMAP_TIMER_ALWON && !IS_ERR_OR_NULL(timer->fclk) && + !omap_dm_timer_sched_clock_counter) { + ret = omap_dm_timer_setup_clocksource(timer); + if (ret) + return ret; + } + pm_runtime_enable(dev); if (!timer->reserved) { From e393cca0388c2fe6f67d4658b2e05f57e244285b Mon Sep 17 00:00:00 2001 From: "Markus Schneider-Pargmann (TI)" Date: Fri, 8 May 2026 15:39:29 +0200 Subject: [PATCH 13/18] clocksource/drivers/timer-ti-dm: Add clockevent support Add support for using the TI Dual-Mode Timer for clockevents. The second always on device with the "ti,timer-alwon" property is selected to be used for clockevents. The first one is used as clocksource. This allows clockevents to be setup independently of the CPU. Signed-off-by: Markus Schneider-Pargmann (TI) Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260508-topic-ti-dm-clkevt-v6-16-v5-3-61d546a0aff9@baylibre.com --- drivers/clocksource/timer-ti-dm.c | 124 ++++++++++++++++++++++++++++-- 1 file changed, 119 insertions(+), 5 deletions(-) diff --git a/drivers/clocksource/timer-ti-dm.c b/drivers/clocksource/timer-ti-dm.c index 98cc34308768..bd06afb7d522 100644 --- a/drivers/clocksource/timer-ti-dm.c +++ b/drivers/clocksource/timer-ti-dm.c @@ -21,8 +21,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -156,6 +158,13 @@ struct dmtimer_clocksource { unsigned int loadval; }; +struct omap_dm_timer_clockevent { + struct clock_event_device dev; + struct dmtimer *timer; + u32 period; +}; + +static bool omap_dm_timer_clockevent_setup; static void __iomem *omap_dm_timer_sched_clock_counter; enum { @@ -1281,6 +1290,106 @@ static int omap_dm_timer_setup_clocksource(struct dmtimer *timer) return 0; } +static struct omap_dm_timer_clockevent *to_dm_timer_clockevent(struct clock_event_device *evt) +{ + return container_of(evt, struct omap_dm_timer_clockevent, dev); +} + +static int omap_dm_timer_evt_set_next_event(unsigned long cycles, + struct clock_event_device *evt) +{ + struct omap_dm_timer_clockevent *clkevt = to_dm_timer_clockevent(evt); + struct dmtimer *timer = clkevt->timer; + + dmtimer_write(timer, OMAP_TIMER_COUNTER_REG, 0xffffffff - cycles); + dmtimer_write(timer, OMAP_TIMER_CTRL_REG, OMAP_TIMER_CTRL_ST); + + return 0; +} + +static int omap_dm_timer_evt_shutdown(struct clock_event_device *evt) +{ + struct omap_dm_timer_clockevent *clkevt = to_dm_timer_clockevent(evt); + struct dmtimer *timer = clkevt->timer; + + __omap_dm_timer_stop(timer); + + return 0; +} + +static int omap_dm_timer_evt_set_periodic(struct clock_event_device *evt) +{ + struct omap_dm_timer_clockevent *clkevt = to_dm_timer_clockevent(evt); + struct dmtimer *timer = clkevt->timer; + + omap_dm_timer_evt_shutdown(evt); + + omap_dm_timer_set_load(&timer->cookie, clkevt->period); + dmtimer_write(timer, OMAP_TIMER_COUNTER_REG, clkevt->period); + dmtimer_write(timer, OMAP_TIMER_CTRL_REG, + OMAP_TIMER_CTRL_AR | OMAP_TIMER_CTRL_ST); + + return 0; +} + +static irqreturn_t omap_dm_timer_evt_interrupt(int irq, void *dev_id) +{ + struct omap_dm_timer_clockevent *clkevt = dev_id; + struct dmtimer *timer = clkevt->timer; + + __omap_dm_timer_write_status(timer, OMAP_TIMER_INT_OVERFLOW); + + clkevt->dev.event_handler(&clkevt->dev); + + return IRQ_HANDLED; +} + +static int omap_dm_timer_setup_clockevent(struct dmtimer *timer) +{ + struct device *dev = &timer->pdev->dev; + struct omap_dm_timer_clockevent *clkevt; + int ret; + + clkevt = devm_kzalloc(dev, sizeof(*clkevt), GFP_KERNEL); + if (!clkevt) + return -ENOMEM; + + timer->reserved = 1; + clkevt->timer = timer; + + clkevt->dev.name = "omap_dm_timer"; + clkevt->dev.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT; + clkevt->dev.rating = 300; + clkevt->dev.set_next_event = omap_dm_timer_evt_set_next_event; + clkevt->dev.set_state_shutdown = omap_dm_timer_evt_shutdown; + clkevt->dev.set_state_periodic = omap_dm_timer_evt_set_periodic; + clkevt->dev.set_state_oneshot = omap_dm_timer_evt_shutdown; + clkevt->dev.set_state_oneshot_stopped = omap_dm_timer_evt_shutdown; + clkevt->dev.tick_resume = omap_dm_timer_evt_shutdown; + clkevt->dev.cpumask = cpu_possible_mask; + clkevt->period = 0xffffffff - DIV_ROUND_CLOSEST(timer->fclk_rate, HZ); + + __omap_dm_timer_init_regs(timer); + __omap_dm_timer_stop(timer); + __omap_dm_timer_enable_posted(timer); + + ret = devm_request_irq(dev, timer->irq, omap_dm_timer_evt_interrupt, + IRQF_TIMER, "omap_dm_timer_clockevent", clkevt); + if (ret) { + dev_err(dev, "Failed to request interrupt: %d\n", ret); + return ret; + } + + __omap_dm_timer_int_enable(timer, OMAP_TIMER_INT_OVERFLOW); + + clockevents_config_and_register(&clkevt->dev, timer->fclk_rate, + 3, + 0xffffffff); + + omap_dm_timer_clockevent_setup = true; + return 0; +} + /** * omap_dm_timer_probe - probe function called for every registered device * @pdev: pointer to current timer platform device @@ -1368,11 +1477,16 @@ static int omap_dm_timer_probe(struct platform_device *pdev) timer->pdev = pdev; - if (timer->capability & OMAP_TIMER_ALWON && !IS_ERR_OR_NULL(timer->fclk) && - !omap_dm_timer_sched_clock_counter) { - ret = omap_dm_timer_setup_clocksource(timer); - if (ret) - return ret; + if (timer->capability & OMAP_TIMER_ALWON && !IS_ERR_OR_NULL(timer->fclk)) { + if (!omap_dm_timer_sched_clock_counter) { + ret = omap_dm_timer_setup_clocksource(timer); + if (ret) + return ret; + } else if (!omap_dm_timer_clockevent_setup) { + ret = omap_dm_timer_setup_clockevent(timer); + if (ret) + return ret; + } } pm_runtime_enable(dev); From ca57bf46e7a94f8c53d05c376df9fcfdcb482100 Mon Sep 17 00:00:00 2001 From: Kartik Rajput Date: Thu, 7 May 2026 21:15:54 +0530 Subject: [PATCH 14/18] clocksource/drivers/timer-tegra186: Fix support for multiple watchdog instances Tegra SoCs support multiple watchdogs; currently only one (WDT0) is used. When multiple watchdogs are registered, tegra186_wdt_enable() overwrites the TKEIE(x) register, discarding any existing watchdog interrupt enable bits. As a result, enabling one watchdog inadvertently disables interrupts for the others. Fix this by preserving the existing TKEIE(x) value and updating it using a read-modify-write sequence. Fixes: 42cee19a9f83 ("clocksource: Add Tegra186 timers support") Cc: stable@vger.kernel.org Signed-off-by: Kartik Rajput Signed-off-by: Daniel Lezcano Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260507154557.2082697-2-kkartik@nvidia.com --- drivers/clocksource/timer-tegra186.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/clocksource/timer-tegra186.c b/drivers/clocksource/timer-tegra186.c index 355558893e5f..bfe16d2d5104 100644 --- a/drivers/clocksource/timer-tegra186.c +++ b/drivers/clocksource/timer-tegra186.c @@ -149,7 +149,8 @@ static void tegra186_wdt_enable(struct tegra186_wdt *wdt) u32 value; /* unmask hardware IRQ, this may have been lost across powergate */ - value = TKEIE_WDT_MASK(wdt->index, 1); + value = readl(tegra->regs + TKEIE(wdt->tmr->hwirq)); + value |= TKEIE_WDT_MASK(wdt->index, 1); writel(value, tegra->regs + TKEIE(wdt->tmr->hwirq)); /* clear interrupt */ From 341305ea9c89a55c55c0d6252541e5d01098a00a Mon Sep 17 00:00:00 2001 From: Kartik Rajput Date: Thu, 7 May 2026 21:15:55 +0530 Subject: [PATCH 15/18] clocksource/drivers/timer-tegra186: Correct num_wdts for Tegra186 and Tegra234 On Tegra186 and Tegra234, WDT2 is connected to the Audio Processing Engine (APE) and cannot be accessed from Linux. Only WDT0 and WDT1 are accessible to Linux. Update num_wdts from 3 to 2 for both Tegra186 and Tegra234 to reflect the actual number of watchdogs available to Linux. Signed-off-by: Kartik Rajput Signed-off-by: Daniel Lezcano Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260507154557.2082697-3-kkartik@nvidia.com --- drivers/clocksource/timer-tegra186.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clocksource/timer-tegra186.c b/drivers/clocksource/timer-tegra186.c index bfe16d2d5104..fd82a73ab2d2 100644 --- a/drivers/clocksource/timer-tegra186.c +++ b/drivers/clocksource/timer-tegra186.c @@ -511,12 +511,12 @@ static SIMPLE_DEV_PM_OPS(tegra186_timer_pm_ops, tegra186_timer_suspend, static const struct tegra186_timer_soc tegra186_timer = { .num_timers = 10, - .num_wdts = 3, + .num_wdts = 2, }; static const struct tegra186_timer_soc tegra234_timer = { .num_timers = 16, - .num_wdts = 3, + .num_wdts = 2, }; static const struct of_device_id tegra186_timer_of_match[] = { From 5eb5d8eaeacf09f924458dacd590b7f9b1607cea Mon Sep 17 00:00:00 2001 From: Kartik Rajput Date: Thu, 7 May 2026 21:15:56 +0530 Subject: [PATCH 16/18] clocksource/drivers/timer-tegra186: Register all accessible watchdog timers Tegra186+ SoCs expose multiple watchdog timers, but the driver only registers WDT(0). Iterate over num_wdts and, for each WDT, check the SCR (firewall) registers in the TKE block to determine whether Linux has read and write access. Register the watchdogs that are accessible. Signed-off-by: Kartik Rajput Signed-off-by: Daniel Lezcano Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260507154557.2082697-4-kkartik@nvidia.com --- drivers/clocksource/timer-tegra186.c | 61 +++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/drivers/clocksource/timer-tegra186.c b/drivers/clocksource/timer-tegra186.c index fd82a73ab2d2..dd1d1a0dd63e 100644 --- a/drivers/clocksource/timer-tegra186.c +++ b/drivers/clocksource/timer-tegra186.c @@ -57,6 +57,13 @@ #define WDTUR 0x00c #define WDTUR_UNLOCK_PATTERN 0x0000c45a +/* WDT security configuration registers */ +#define WDTSCR(x) (0xf02c + (x) * 4) +#define WDTSCR_SEC_WEN BIT(28) +#define WDTSCR_SEC_REN BIT(27) +#define WDTSCR_SEC_G1W BIT(9) +#define WDTSCR_SEC_G1R BIT(1) + struct tegra186_timer_soc { unsigned int num_timers; unsigned int num_wdts; @@ -89,7 +96,7 @@ struct tegra186_timer { struct device *dev; void __iomem *regs; - struct tegra186_wdt *wdt; + struct tegra186_wdt **wdts; struct clocksource usec; struct clocksource tsc; struct clocksource osc; @@ -298,6 +305,23 @@ static const struct watchdog_ops tegra186_wdt_ops = { .get_timeleft = tegra186_wdt_get_timeleft, }; +static bool tegra186_wdt_is_accessible(struct tegra186_timer *tegra, unsigned int index) +{ + u32 value; + + value = readl_relaxed(tegra->regs + WDTSCR(index)); + + /* Check OS write access if write blocking is enabled. */ + if ((value & WDTSCR_SEC_WEN) && !(value & WDTSCR_SEC_G1W)) + return false; + + /* Check OS read access if read blocking is enabled. */ + if ((value & WDTSCR_SEC_REN) && !(value & WDTSCR_SEC_G1R)) + return false; + + return true; +} + static struct tegra186_wdt *tegra186_wdt_create(struct tegra186_timer *tegra, unsigned int index) { @@ -424,6 +448,7 @@ static int tegra186_timer_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct tegra186_timer *tegra; + unsigned int i; int err; tegra = devm_kzalloc(dev, sizeof(*tegra), GFP_KERNEL); @@ -442,12 +467,20 @@ static int tegra186_timer_probe(struct platform_device *pdev) if (err < 0) return err; - /* create a watchdog using a preconfigured timer */ - tegra->wdt = tegra186_wdt_create(tegra, 0); - if (IS_ERR(tegra->wdt)) { - err = PTR_ERR(tegra->wdt); - dev_err(dev, "failed to create WDT: %d\n", err); - return err; + tegra->wdts = devm_kcalloc(dev, tegra->soc->num_wdts, sizeof(*tegra->wdts), GFP_KERNEL); + if (!tegra->wdts) + return -ENOMEM; + + for (i = 0; i < tegra->soc->num_wdts; i++) { + if (!tegra186_wdt_is_accessible(tegra, i)) { + dev_warn(dev, "WDT%u is not accessible\n", i); + continue; + } + + tegra->wdts[i] = tegra186_wdt_create(tegra, i); + if (IS_ERR(tegra->wdts[i])) + return dev_err_probe(dev, PTR_ERR(tegra->wdts[i]), + "failed to create WDT%u\n", i); } err = tegra186_timer_tsc_init(tegra); @@ -489,9 +522,12 @@ static void tegra186_timer_remove(struct platform_device *pdev) static int __maybe_unused tegra186_timer_suspend(struct device *dev) { struct tegra186_timer *tegra = dev_get_drvdata(dev); + unsigned int i; - if (watchdog_active(&tegra->wdt->base)) - tegra186_wdt_disable(tegra->wdt); + for (i = 0; i < tegra->soc->num_wdts; i++) { + if (tegra->wdts[i] && watchdog_active(&tegra->wdts[i]->base)) + tegra186_wdt_disable(tegra->wdts[i]); + } return 0; } @@ -499,9 +535,12 @@ static int __maybe_unused tegra186_timer_suspend(struct device *dev) static int __maybe_unused tegra186_timer_resume(struct device *dev) { struct tegra186_timer *tegra = dev_get_drvdata(dev); + unsigned int i; - if (watchdog_active(&tegra->wdt->base)) - tegra186_wdt_enable(tegra->wdt); + for (i = 0; i < tegra->soc->num_wdts; i++) { + if (tegra->wdts[i] && watchdog_active(&tegra->wdts[i]->base)) + tegra186_wdt_enable(tegra->wdts[i]); + } return 0; } From 46fb093b87a0d92e391b6f6cadd63c3ef50a59b2 Mon Sep 17 00:00:00 2001 From: Kartik Rajput Date: Thu, 7 May 2026 21:15:57 +0530 Subject: [PATCH 17/18] clocksource/drivers/timer-tegra186: Reserve and service a kernel watchdog Tegra SoCs supports multiple watchdog timers. If the kernel crashes or hangs before userspace enables a watchdog, the system cannot recover and may remain bricked, e.g. after a failed OTA update. The driver currently leaves all watchdogs disabled until userspace configures them. Reserve first available watchdog as a kernel-only watchdog for Tegra186 and Tegra234. Arm it during probe (120s timeout) and keep it alive in the driver IRQ handler. Do not register it to userspace. Other available watchdogs remain exposed to userspace. This guarantees the system can reset itself in case of a hang or crash even when userspace never starts. Signed-off-by: Kartik Rajput Signed-off-by: Daniel Lezcano Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260507154557.2082697-5-kkartik@nvidia.com --- drivers/clocksource/timer-tegra186.c | 62 ++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/drivers/clocksource/timer-tegra186.c b/drivers/clocksource/timer-tegra186.c index dd1d1a0dd63e..78600ddeb1c6 100644 --- a/drivers/clocksource/timer-tegra186.c +++ b/drivers/clocksource/timer-tegra186.c @@ -57,6 +57,8 @@ #define WDTUR 0x00c #define WDTUR_UNLOCK_PATTERN 0x0000c45a +#define TEGRA186_KERNEL_WDT_TIMEOUT 120 + /* WDT security configuration registers */ #define WDTSCR(x) (0xf02c + (x) * 4) #define WDTSCR_SEC_WEN BIT(28) @@ -82,6 +84,7 @@ struct tegra186_wdt { void __iomem *regs; unsigned int index; bool locked; + bool is_kernel_wdt; struct tegra186_tmr *tmr; }; @@ -182,6 +185,10 @@ static void tegra186_wdt_enable(struct tegra186_wdt *wdt) value &= ~WDTCR_PERIOD_MASK; value |= WDTCR_PERIOD(1); + /* enable local interrupt for kernel watchdog */ + if (wdt->is_kernel_wdt) + value |= WDTCR_LOCAL_INT_ENABLE; + /* enable system POR reset */ value |= WDTCR_SYSTEM_POR_RESET_ENABLE; @@ -219,6 +226,16 @@ static int tegra186_wdt_ping(struct watchdog_device *wdd) return 0; } +static irqreturn_t tegra186_wdt_irq(int irq, void *data) +{ + struct tegra186_wdt *wdt = data; + + tegra186_wdt_disable(wdt); + tegra186_wdt_enable(wdt); + + return IRQ_HANDLED; +} + static int tegra186_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout) { @@ -361,10 +378,6 @@ static struct tegra186_wdt *tegra186_wdt_create(struct tegra186_timer *tegra, if (err < 0) return ERR_PTR(err); - err = devm_watchdog_register_device(tegra->dev, &wdt->base); - if (err < 0) - return ERR_PTR(err); - return wdt; } @@ -446,9 +459,11 @@ static int tegra186_timer_usec_init(struct tegra186_timer *tegra) static int tegra186_timer_probe(struct platform_device *pdev) { + struct tegra186_wdt *kernel_wdt = NULL; struct device *dev = &pdev->dev; struct tegra186_timer *tegra; unsigned int i; + int irq; int err; tegra = devm_kzalloc(dev, sizeof(*tegra), GFP_KERNEL); @@ -467,6 +482,8 @@ static int tegra186_timer_probe(struct platform_device *pdev) if (err < 0) return err; + irq = err; + tegra->wdts = devm_kcalloc(dev, tegra->soc->num_wdts, sizeof(*tegra->wdts), GFP_KERNEL); if (!tegra->wdts) return -ENOMEM; @@ -481,6 +498,17 @@ static int tegra186_timer_probe(struct platform_device *pdev) if (IS_ERR(tegra->wdts[i])) return dev_err_probe(dev, PTR_ERR(tegra->wdts[i]), "failed to create WDT%u\n", i); + + /* Reserve the first accessible WDT for the Kernel. */ + if (!kernel_wdt) { + kernel_wdt = tegra->wdts[i]; + kernel_wdt->is_kernel_wdt = true; + } else { + err = devm_watchdog_register_device(dev, &tegra->wdts[i]->base); + if (err < 0) + return dev_err_probe(dev, err, + "failed to register WDT%u\n", i); + } } err = tegra186_timer_tsc_init(tegra); @@ -501,8 +529,22 @@ static int tegra186_timer_probe(struct platform_device *pdev) goto unregister_osc; } + if (kernel_wdt) { + err = devm_request_irq(dev, irq, tegra186_wdt_irq, 0, + dev_name(dev), kernel_wdt); + if (err < 0) { + dev_err(dev, "failed to request kernel WDT IRQ: %d\n", err); + goto unregister_usec; + } + + tegra186_wdt_set_timeout(&kernel_wdt->base, TEGRA186_KERNEL_WDT_TIMEOUT); + tegra186_wdt_enable(kernel_wdt); + } + return 0; +unregister_usec: + clocksource_unregister(&tegra->usec); unregister_osc: clocksource_unregister(&tegra->osc); unregister_tsc: @@ -525,8 +567,10 @@ static int __maybe_unused tegra186_timer_suspend(struct device *dev) unsigned int i; for (i = 0; i < tegra->soc->num_wdts; i++) { - if (tegra->wdts[i] && watchdog_active(&tegra->wdts[i]->base)) - tegra186_wdt_disable(tegra->wdts[i]); + struct tegra186_wdt *wdt = tegra->wdts[i]; + + if (wdt && (wdt->is_kernel_wdt || watchdog_active(&wdt->base))) + tegra186_wdt_disable(wdt); } return 0; @@ -538,8 +582,10 @@ static int __maybe_unused tegra186_timer_resume(struct device *dev) unsigned int i; for (i = 0; i < tegra->soc->num_wdts; i++) { - if (tegra->wdts[i] && watchdog_active(&tegra->wdts[i]->base)) - tegra186_wdt_enable(tegra->wdts[i]); + struct tegra186_wdt *wdt = tegra->wdts[i]; + + if (wdt && (wdt->is_kernel_wdt || watchdog_active(&wdt->base))) + tegra186_wdt_enable(wdt); } return 0; From a9ac745bc320cbdc2ed3c851eb78f91f22ff975b Mon Sep 17 00:00:00 2001 From: Enric Balletbo i Serra Date: Thu, 14 May 2026 13:14:17 +0200 Subject: [PATCH 18/18] clocksource: move NXP timer selection to drivers/clocksource The Kconfig logic for selecting the scheduler clocksource on NXP Vybrid (VF610) uses a `choice` block restricted to 32-bit ARM. This prevents 64-bit architectures, such as the NXP S32 family, from enabling the NXP Periodic Interrupt Timer (PIT) driver (CONFIG_NXP_PIT_TIMER). Relocate the NXP clocksource selection from arch/arm/mach-imx/Kconfig to drivers/clocksource/Kconfig. This allows the configuration to be shared across different architectures. Update the selection to include support for ARCH_S32 and add a "None" option restricted to ARCH_S32, since Vybrid lacks the ARM Architected Timer. The Vybrid Global Timer option is restricted to ARCH_MULTI_V7 SOC_VF610 platforms to prevent it from being visible on Cortex-M4 builds, which lack the ARM Global Timer hardware. Fixes: bee33f22d7c3 ("clocksource/drivers/nxp-pit: Add NXP Automotive s32g2 / s32g3 support") Signed-off-by: Enric Balletbo i Serra Signed-off-by: Daniel Lezcano Reviewed-by: Frank Li Link: https://patch.msgid.link/20260514-fix-nxp-timer-v3-1-a3e68fdb505e@redhat.com --- arch/arm/mach-imx/Kconfig | 21 --------------------- drivers/clocksource/Kconfig | 31 +++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig index 6ea1bd55acf8..a361840d7a04 100644 --- a/arch/arm/mach-imx/Kconfig +++ b/arch/arm/mach-imx/Kconfig @@ -227,27 +227,6 @@ config SOC_VF610 help This enables support for Freescale Vybrid VF610 processor. -choice - prompt "Clocksource for scheduler clock" - depends on SOC_VF610 - default VF_USE_ARM_GLOBAL_TIMER - - config VF_USE_ARM_GLOBAL_TIMER - bool "Use ARM Global Timer" - depends on ARCH_MULTI_V7 - select ARM_GLOBAL_TIMER - select CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK - help - Use the ARM Global Timer as clocksource - - config VF_USE_PIT_TIMER - bool "Use PIT timer" - select NXP_PIT_TIMER - help - Use SoC Periodic Interrupt Timer (PIT) as clocksource - -endchoice - endif endif diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index d1a33a231a44..d9c76dd443f8 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -793,4 +793,35 @@ config RTK_SYSTIMER this option only when building for a Realtek platform or for compilation testing. +choice + prompt "NXP clocksource for scheduler clock" + depends on SOC_VF610 || ARCH_S32 + # Default to Global Timer for Vybrid (32-bit) + default VF_USE_ARM_GLOBAL_TIMER if SOC_VF610 + # Default to None for S32 (64-bit) + default VF_TIMER_NONE if ARCH_S32 + + config VF_USE_ARM_GLOBAL_TIMER + bool "Use NXP Vybrid Global Timer" + depends on ARCH_MULTI_V7 && SOC_VF610 + select ARM_GLOBAL_TIMER + select CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK + help + Use the NXP Vybrid Global Timer as clocksource. + + config VF_USE_PIT_TIMER + bool "Use NXP PIT timer" + select NXP_PIT_TIMER + help + Use NXP Periodic Interrupt Timer (PIT) as clocksource. + + config VF_TIMER_NONE + bool "None (Use standard Arch Timer)" + depends on ARCH_S32 + help + Do not use any specific NXP timer driver. Use the standard + ARM Architected Timer instead. + +endchoice + endmenu