- Use 64-bits for timer compensation for IoT usage where the suspend

time is much longer than what 32-bits can provide (Enlin Mu)
 
 - Add delay support on sp804 for ARM32 platforms (Stephen Eta Zhou)
 
 - Fix missing resource release on error in the probe path of in the
   ralink driver (Haotian Zhang)
 
 - Fix double deregistration on probe failure in the NXP STM driver
   (Johan Hovold)
 
 - Disable runtime PM for the Renesas SH CMT timer because it is
   incompatible with PREEMPT_RT=y (Niklas Söderlund)
 
 - Fix section mismatches in the NXP STM driver (Johan Hovold)
 
 - Preventing unbinding the NXP PIT, STM and MMIO ARM Arch timers as
   the code does not suppport bind/unbind (Johan Hovold)
 
 - Use the clocksource instead of ticks on the RDA8810PL platform
   (Enlin Mu)
 
 - Drop the unused module alias for the STM32-LP (Johan Hovold)
 
 - Add Realtek system timer driver (Hao-Wen Ting)
 -----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCAAdFiEEGn3N4YVz0WNVyHskqDIjiipP6E8FAmkm6t4ACgkQqDIjiipP
 6E8xqQgAlXnV3vRJmEbjd3ILECvbKMLI2haHV2eA+75P+DvbfriL+ePMHkfkOPI6
 CC5UhCSy410cQLO88tzy5+9K8Po2KnHxb+lVS2P6zzcdefL5ZWMZ9Q+CAOwSo1s9
 An1A4nUgcTB52mAR+jlz++SF1VV/fMvskMrtiTg8bSIScSc+xi4sEC3GaZR09qSG
 RODtzmVsyeoHQ1u6ziRJen8GzpX1q6vUP0eAAr+vXqTUXdCuUL8P20h2mwzxPJWH
 mFo53OuKVbTMOoY1Av7euvO1ZZ1tsHsS4NxJfD1qatq+eh1As1dxYodB4dp44qZt
 jjnVuj0QrE40VB6EnHAA4kKb6WWpow==
 =WXsR
 -----END PGP SIGNATURE-----

Merge tag 'timers-v6.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/daniel.lezcano/linux into timers/clocksource

Pull clocksource/event changes from Daniel Lezcano:

    - Use 64-bits for timer compensation for IoT usage where the suspend
      time is much longer than what 32-bits can provide (Enlin Mu)

    - Add delay support on sp804 for ARM32 platforms (Stephen Eta Zhou)

    - Fix missing resource release on error in the probe path of in the
      ralink driver (Haotian Zhang)

    - Fix double deregistration on probe failure in the NXP STM driver
      (Johan Hovold)

    - Disable runtime PM for the Renesas SH CMT timer because it is
      incompatible with PREEMPT_RT=y (Niklas Söderlund)

    - Fix section mismatches in the NXP STM driver (Johan Hovold)

    - Preventing unbinding the NXP PIT, STM and MMIO ARM Arch timers as
      the code does not suppport bind/unbind (Johan Hovold)

    - Use the clocksource instead of ticks on the RDA8810PL platform
      (Enlin Mu)

    - Drop the unused module alias for the STM32-LP (Johan Hovold)

    - Add Realtek system timer driver (Hao-Wen Ting)

Link: https://lore.kernel.org/all/9303b790-28d4-4bd9-b01d-28fb05493596@linaro.org
This commit is contained in:
Thomas Gleixner 2025-11-26 15:36:52 +01:00
commit 2437f79880
14 changed files with 291 additions and 56 deletions

View File

@ -0,0 +1,47 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/timer/realtek,rtd1625-systimer.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Realtek System Timer
maintainers:
- Hao-Wen Ting <haowen.ting@realtek.com>
description:
The Realtek SYSTIMER (System Timer) is a 64-bit global hardware counter operating
at a fixed 1MHz frequency. Thanks to its compare match interrupt capability,
the timer natively supports oneshot mode for tick broadcast functionality.
properties:
compatible:
oneOf:
- const: realtek,rtd1625-systimer
- items:
- const: realtek,rtd1635-systimer
- const: realtek,rtd1625-systimer
reg:
maxItems: 1
interrupts:
maxItems: 1
required:
- compatible
- reg
- interrupts
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/arm-gic.h>
timer@89420 {
compatible = "realtek,rtd1635-systimer",
"realtek,rtd1625-systimer";
reg = <0x89420 0x18>;
interrupts = <GIC_SPI 112 IRQ_TYPE_LEVEL_HIGH>;
};

View File

@ -21669,6 +21669,11 @@ S: Maintained
F: Documentation/devicetree/bindings/spi/realtek,rtl9301-snand.yaml
F: drivers/spi/spi-realtek-rtl-snand.c
REALTEK SYSTIMER DRIVER
M: Hao-Wen Ting <haowen.ting@realtek.com>
S: Maintained
F: drivers/clocksource/timer-realtek.c
REALTEK WIRELESS DRIVER (rtlwifi family)
M: Ping-Ke Shih <pkshih@realtek.com>
L: linux-wireless@vger.kernel.org

View File

@ -782,4 +782,15 @@ config NXP_STM_TIMER
Enables the support for NXP System Timer Module found in the
s32g NXP platform series.
config RTK_SYSTIMER
bool "Realtek SYSTIMER support"
depends on ARM || ARM64
depends on ARCH_REALTEK || COMPILE_TEST
select TIMER_OF
help
This option enables the driver that registers the global 1 MHz hardware
counter as a clock event device on Realtek SoCs. Make sure to enable
this option only when building for a Realtek platform or for compilation
testing.
endmenu

View File

@ -95,3 +95,4 @@ obj-$(CONFIG_CLKSRC_LOONGSON1_PWM) += timer-loongson1-pwm.o
obj-$(CONFIG_EP93XX_TIMER) += timer-ep93xx.o
obj-$(CONFIG_RALINK_TIMER) += timer-ralink.o
obj-$(CONFIG_NXP_STM_TIMER) += timer-nxp-stm.o
obj-$(CONFIG_RTK_SYSTIMER) += timer-realtek.o

View File

@ -426,6 +426,7 @@ static struct platform_driver arch_timer_mmio_drv = {
.driver = {
.name = "arch-timer-mmio",
.of_match_table = arch_timer_mmio_of_table,
.suppress_bind_attrs = true,
},
.probe = arch_timer_mmio_probe,
};
@ -434,6 +435,7 @@ builtin_platform_driver(arch_timer_mmio_drv);
static struct platform_driver arch_timer_mmio_acpi_drv = {
.driver = {
.name = "gtdt-arm-mmio-timer",
.suppress_bind_attrs = true,
},
.probe = arch_timer_mmio_probe,
};

View File

@ -355,14 +355,6 @@ static int sh_cmt_enable(struct sh_cmt_channel *ch)
dev_pm_syscore_device(&ch->cmt->pdev->dev, true);
/* enable clock */
ret = clk_enable(ch->cmt->clk);
if (ret) {
dev_err(&ch->cmt->pdev->dev, "ch%u: cannot enable clock\n",
ch->index);
goto err0;
}
/* make sure channel is disabled */
sh_cmt_start_stop_ch(ch, 0);
@ -384,19 +376,12 @@ static int sh_cmt_enable(struct sh_cmt_channel *ch)
if (ret || sh_cmt_read_cmcnt(ch)) {
dev_err(&ch->cmt->pdev->dev, "ch%u: cannot clear CMCNT\n",
ch->index);
ret = -ETIMEDOUT;
goto err1;
return -ETIMEDOUT;
}
/* enable channel */
sh_cmt_start_stop_ch(ch, 1);
return 0;
err1:
/* stop clock */
clk_disable(ch->cmt->clk);
err0:
return ret;
}
static void sh_cmt_disable(struct sh_cmt_channel *ch)
@ -407,9 +392,6 @@ static void sh_cmt_disable(struct sh_cmt_channel *ch)
/* disable interrupts in CMT block */
sh_cmt_write_cmcsr(ch, 0);
/* stop clock */
clk_disable(ch->cmt->clk);
dev_pm_syscore_device(&ch->cmt->pdev->dev, false);
}
@ -583,8 +565,6 @@ static int sh_cmt_start_clocksource(struct sh_cmt_channel *ch)
int ret = 0;
unsigned long flags;
pm_runtime_get_sync(&ch->cmt->pdev->dev);
raw_spin_lock_irqsave(&ch->lock, flags);
if (!(ch->flags & (FLAG_CLOCKEVENT | FLAG_CLOCKSOURCE)))
@ -619,8 +599,6 @@ static void sh_cmt_stop_clocksource(struct sh_cmt_channel *ch)
sh_cmt_disable(ch);
raw_spin_unlock_irqrestore(&ch->lock, flags);
pm_runtime_put(&ch->cmt->pdev->dev);
}
static int sh_cmt_start_clockevent(struct sh_cmt_channel *ch)
@ -630,10 +608,8 @@ static int sh_cmt_start_clockevent(struct sh_cmt_channel *ch)
raw_spin_lock_irqsave(&ch->lock, flags);
if (!(ch->flags & (FLAG_CLOCKEVENT | FLAG_CLOCKSOURCE))) {
pm_runtime_get_sync(&ch->cmt->pdev->dev);
if (!(ch->flags & (FLAG_CLOCKEVENT | FLAG_CLOCKSOURCE)))
ret = sh_cmt_enable(ch);
}
if (ret)
goto out;
@ -656,10 +632,8 @@ static void sh_cmt_stop_clockevent(struct sh_cmt_channel *ch)
ch->flags &= ~FLAG_CLOCKEVENT;
if (f && !(ch->flags & (FLAG_CLOCKEVENT | FLAG_CLOCKSOURCE))) {
if (f && !(ch->flags & (FLAG_CLOCKEVENT | FLAG_CLOCKSOURCE)))
sh_cmt_disable(ch);
pm_runtime_put(&ch->cmt->pdev->dev);
}
/* adjust the timeout to maximum if only clocksource left */
if (ch->flags & FLAG_CLOCKSOURCE)
@ -1134,8 +1108,6 @@ static int sh_cmt_setup(struct sh_cmt_device *cmt, struct platform_device *pdev)
mask &= ~(1 << hwidx);
}
clk_disable(cmt->clk);
platform_set_drvdata(pdev, cmt);
return 0;
@ -1183,8 +1155,6 @@ static int sh_cmt_probe(struct platform_device *pdev)
out:
if (cmt->has_clockevent || cmt->has_clocksource)
pm_runtime_irq_safe(&pdev->dev);
else
pm_runtime_idle(&pdev->dev);
return 0;
}

View File

@ -374,9 +374,10 @@ static struct platform_driver nxp_pit_driver = {
.driver = {
.name = "nxp-pit",
.of_match_table = pit_timer_of_match,
.suppress_bind_attrs = true,
},
.probe = pit_timer_probe,
};
module_platform_driver(nxp_pit_driver);
builtin_platform_driver(nxp_pit_driver);
TIMER_OF_DECLARE(vf610, "fsl,vf610-pit", pit_timer_init);

View File

@ -177,15 +177,15 @@ static void nxp_stm_clocksource_resume(struct clocksource *cs)
nxp_stm_clocksource_enable(cs);
}
static void __init devm_clocksource_unregister(void *data)
static void devm_clocksource_unregister(void *data)
{
struct stm_timer *stm_timer = data;
clocksource_unregister(&stm_timer->cs);
}
static int __init nxp_stm_clocksource_init(struct device *dev, struct stm_timer *stm_timer,
const char *name, void __iomem *base, struct clk *clk)
static int nxp_stm_clocksource_init(struct device *dev, struct stm_timer *stm_timer,
const char *name, void __iomem *base, struct clk *clk)
{
int ret;
@ -208,10 +208,8 @@ static int __init nxp_stm_clocksource_init(struct device *dev, struct stm_timer
return ret;
ret = devm_add_action_or_reset(dev, devm_clocksource_unregister, stm_timer);
if (ret) {
clocksource_unregister(&stm_timer->cs);
if (ret)
return ret;
}
stm_sched_clock = stm_timer;
@ -298,9 +296,9 @@ static void nxp_stm_clockevent_resume(struct clock_event_device *ced)
nxp_stm_module_get(stm_timer);
}
static int __init nxp_stm_clockevent_per_cpu_init(struct device *dev, struct stm_timer *stm_timer,
const char *name, void __iomem *base, int irq,
struct clk *clk, int cpu)
static int nxp_stm_clockevent_per_cpu_init(struct device *dev, struct stm_timer *stm_timer,
const char *name, void __iomem *base, int irq,
struct clk *clk, int cpu)
{
stm_timer->base = base;
stm_timer->rate = clk_get_rate(clk);
@ -388,7 +386,7 @@ static irqreturn_t nxp_stm_module_interrupt(int irq, void *dev_id)
return IRQ_HANDLED;
}
static int __init nxp_stm_timer_probe(struct platform_device *pdev)
static int nxp_stm_timer_probe(struct platform_device *pdev)
{
struct stm_timer *stm_timer;
struct device *dev = &pdev->dev;
@ -484,14 +482,15 @@ static const struct of_device_id nxp_stm_of_match[] = {
};
MODULE_DEVICE_TABLE(of, nxp_stm_of_match);
static struct platform_driver nxp_stm_probe = {
static struct platform_driver nxp_stm_driver = {
.probe = nxp_stm_timer_probe,
.driver = {
.name = "nxp-stm",
.of_match_table = nxp_stm_of_match,
.suppress_bind_attrs = true,
},
};
module_platform_driver(nxp_stm_probe);
builtin_platform_driver(nxp_stm_driver);
MODULE_DESCRIPTION("NXP System Timer Module driver");
MODULE_LICENSE("GPL");

View File

@ -130,14 +130,15 @@ static int __init ralink_systick_init(struct device_node *np)
systick.dev.irq = irq_of_parse_and_map(np, 0);
if (!systick.dev.irq) {
pr_err("%pOFn: request_irq failed", np);
return -EINVAL;
ret = -EINVAL;
goto err_iounmap;
}
ret = clocksource_mmio_init(systick.membase + SYSTICK_COUNT, np->name,
SYSTICK_FREQ, 301, 16,
clocksource_mmio_readl_up);
if (ret)
return ret;
goto err_free_irq;
clockevents_register_device(&systick.dev);
@ -145,6 +146,12 @@ static int __init ralink_systick_init(struct device_node *np)
np, systick.dev.mult, systick.dev.shift);
return 0;
err_free_irq:
irq_dispose_mapping(systick.dev.irq);
err_iounmap:
iounmap(systick.membase);
return ret;
}
TIMER_OF_DECLARE(systick, "ralink,cevt-systick", ralink_systick_init);

View File

@ -13,6 +13,7 @@
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/sched_clock.h>
#include "timer-of.h"
@ -153,7 +154,7 @@ static struct timer_of rda_ostimer_of = {
},
};
static u64 rda_hwtimer_read(struct clocksource *cs)
static u64 rda_hwtimer_clocksource_read(void)
{
void __iomem *base = timer_of_base(&rda_ostimer_of);
u32 lo, hi;
@ -167,6 +168,11 @@ static u64 rda_hwtimer_read(struct clocksource *cs)
return ((u64)hi << 32) | lo;
}
static u64 rda_hwtimer_read(struct clocksource *cs)
{
return rda_hwtimer_clocksource_read();
}
static struct clocksource rda_hwtimer_clocksource = {
.name = "rda-timer",
.rating = 400,
@ -185,6 +191,7 @@ static int __init rda_timer_init(struct device_node *np)
return ret;
clocksource_register_hz(&rda_hwtimer_clocksource, rate);
sched_clock_register(rda_hwtimer_clocksource_read, 64, rate);
clockevents_config_and_register(&rda_ostimer_of.clkevt, rate,
0x2, UINT_MAX);

View File

@ -0,0 +1,150 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2025 Realtek Semiconductor Corp.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/irqflags.h>
#include <linux/interrupt.h>
#include "timer-of.h"
#define ENBL 1
#define DSBL 0
#define SYSTIMER_RATE 1000000
#define SYSTIMER_MIN_DELTA 0x64
#define SYSTIMER_MAX_DELTA ULONG_MAX
/* SYSTIMER Register Offset (RTK Internal Use) */
#define TS_LW_OFST 0x0
#define TS_HW_OFST 0x4
#define TS_CMP_VAL_LW_OFST 0x8
#define TS_CMP_VAL_HW_OFST 0xC
#define TS_CMP_CTRL_OFST 0x10
#define TS_CMP_STAT_OFST 0x14
/* SYSTIMER CMP CTRL REG Mask */
#define TS_CMP_EN_MASK 0x1
#define TS_WR_EN0_MASK 0x2
static void __iomem *systimer_base;
static u64 rtk_ts64_read(void)
{
u32 low, high;
u64 ts;
/* Caution: Read LSB word (TS_LW_OFST) first then MSB (TS_HW_OFST) */
low = readl(systimer_base + TS_LW_OFST);
high = readl(systimer_base + TS_HW_OFST);
ts = ((u64)high << 32) | low;
return ts;
}
static void rtk_cmp_value_write(u64 value)
{
u32 high, low;
low = value & 0xFFFFFFFF;
high = value >> 32;
writel(high, systimer_base + TS_CMP_VAL_HW_OFST);
writel(low, systimer_base + TS_CMP_VAL_LW_OFST);
}
static inline void rtk_cmp_en_write(bool cmp_en)
{
u32 val;
val = TS_WR_EN0_MASK;
if (cmp_en == ENBL)
val |= TS_CMP_EN_MASK;
writel(val, systimer_base + TS_CMP_CTRL_OFST);
}
static int rtk_syst_clkevt_next_event(unsigned long cycles, struct clock_event_device *clkevt)
{
u64 cmp_val;
rtk_cmp_en_write(DSBL);
cmp_val = rtk_ts64_read();
/* Set CMP value to current timestamp plus delta_us */
rtk_cmp_value_write(cmp_val + cycles);
rtk_cmp_en_write(ENBL);
return 0;
}
static irqreturn_t rtk_ts_match_intr_handler(int irq, void *dev_id)
{
struct clock_event_device *clkevt = dev_id;
void __iomem *reg_base;
u32 val;
/* Disable TS CMP Match */
rtk_cmp_en_write(DSBL);
/* Clear TS CMP INTR */
reg_base = systimer_base + TS_CMP_STAT_OFST;
val = readl(reg_base) & TS_CMP_EN_MASK;
writel(val | TS_CMP_EN_MASK, reg_base);
clkevt->event_handler(clkevt);
return IRQ_HANDLED;
}
static int rtk_syst_shutdown(struct clock_event_device *clkevt)
{
void __iomem *reg_base;
u64 cmp_val = 0;
/* Disable TS CMP Match */
rtk_cmp_en_write(DSBL);
/* Set compare value to 0 */
rtk_cmp_value_write(cmp_val);
/* Clear TS CMP INTR */
reg_base = systimer_base + TS_CMP_STAT_OFST;
writel(TS_CMP_EN_MASK, reg_base);
return 0;
}
static struct timer_of rtk_timer_to = {
.flags = TIMER_OF_IRQ | TIMER_OF_BASE,
.clkevt = {
.name = "rtk-clkevt",
.rating = 300,
.cpumask = cpu_possible_mask,
.features = CLOCK_EVT_FEAT_DYNIRQ |
CLOCK_EVT_FEAT_ONESHOT,
.set_next_event = rtk_syst_clkevt_next_event,
.set_state_oneshot = rtk_syst_shutdown,
.set_state_shutdown = rtk_syst_shutdown,
},
.of_irq = {
.flags = IRQF_TIMER | IRQF_IRQPOLL,
.handler = rtk_ts_match_intr_handler,
},
};
static int __init rtk_systimer_init(struct device_node *node)
{
int ret;
ret = timer_of_init(node, &rtk_timer_to);
if (ret)
return ret;
systimer_base = timer_of_base(&rtk_timer_to);
clockevents_config_and_register(&rtk_timer_to.clkevt, SYSTIMER_RATE,
SYSTIMER_MIN_DELTA, SYSTIMER_MAX_DELTA);
return 0;
}
TIMER_OF_DECLARE(rtk_systimer, "realtek,rtd1625-systimer", rtk_systimer_init);

View File

@ -21,6 +21,10 @@
#include <linux/of_irq.h>
#include <linux/sched_clock.h>
#ifdef CONFIG_ARM
#include <linux/delay.h>
#endif
#include "timer-sp.h"
/* Hisilicon 64-bit timer(a variant of ARM SP804) */
@ -102,6 +106,23 @@ static u64 notrace sp804_read(void)
return ~readl_relaxed(sched_clkevt->value);
}
#ifdef CONFIG_ARM
static struct delay_timer delay;
static unsigned long sp804_read_delay_timer_read(void)
{
return sp804_read();
}
static void sp804_register_delay_timer(int freq)
{
delay.freq = freq;
delay.read_current_timer = sp804_read_delay_timer_read;
register_current_timer_delay(&delay);
}
#else
static inline void sp804_register_delay_timer(int freq) {}
#endif
static int __init sp804_clocksource_and_sched_clock_init(void __iomem *base,
const char *name,
struct clk *clk,
@ -114,6 +135,8 @@ static int __init sp804_clocksource_and_sched_clock_init(void __iomem *base,
if (rate < 0)
return -EINVAL;
sp804_register_delay_timer(rate);
clkevt = sp804_clkevt_get(base);
writel(0, clkevt->ctrl);
@ -318,6 +341,7 @@ static int __init sp804_of_init(struct device_node *np, struct sp804_timer *time
if (ret)
goto err;
}
initialized = true;
return 0;

View File

@ -30,6 +30,7 @@
#define TIMER_VALUE_SHDW_HI 0x1c
#define TIMER_VALUE_LO_MASK GENMASK(31, 0)
#define TIMER_VALUE_HI_MASK GENMASK(31, 0)
static void sprd_timer_enable(void __iomem *base, u32 flag)
{
@ -162,15 +163,26 @@ static struct timer_of suspend_to = {
static u64 sprd_suspend_timer_read(struct clocksource *cs)
{
return ~(u64)readl_relaxed(timer_of_base(&suspend_to) +
TIMER_VALUE_SHDW_LO) & cs->mask;
u32 lo, hi;
do {
hi = readl_relaxed(timer_of_base(&suspend_to) +
TIMER_VALUE_SHDW_HI);
lo = readl_relaxed(timer_of_base(&suspend_to) +
TIMER_VALUE_SHDW_LO);
} while (hi != readl_relaxed(timer_of_base(&suspend_to) + TIMER_VALUE_SHDW_HI));
return ~(((u64)hi << 32) | lo);
}
static int sprd_suspend_timer_enable(struct clocksource *cs)
{
sprd_timer_update_counter(timer_of_base(&suspend_to),
TIMER_VALUE_LO_MASK);
sprd_timer_enable(timer_of_base(&suspend_to), TIMER_CTL_PERIOD_MODE);
writel_relaxed(TIMER_VALUE_LO_MASK,
timer_of_base(&suspend_to) + TIMER_LOAD_LO);
writel_relaxed(TIMER_VALUE_HI_MASK,
timer_of_base(&suspend_to) + TIMER_LOAD_HI);
sprd_timer_enable(timer_of_base(&suspend_to),
TIMER_CTL_PERIOD_MODE|TIMER_CTL_64BIT_WIDTH);
return 0;
}
@ -186,7 +198,7 @@ static struct clocksource suspend_clocksource = {
.read = sprd_suspend_timer_read,
.enable = sprd_suspend_timer_enable,
.disable = sprd_suspend_timer_disable,
.mask = CLOCKSOURCE_MASK(32),
.mask = CLOCKSOURCE_MASK(64),
.flags = CLOCK_SOURCE_IS_CONTINUOUS | CLOCK_SOURCE_SUSPEND_NONSTOP,
};

View File

@ -289,5 +289,4 @@ static struct platform_driver stm32_clkevent_lp_driver = {
};
module_platform_driver(stm32_clkevent_lp_driver);
MODULE_ALIAS("platform:stm32-lptimer-timer");
MODULE_DESCRIPTION("STMicroelectronics STM32 clockevent low power driver");