Add a tick broadcast timer driver for Realtek SoCs.
On Realtek platforms, CPUs can enter deep idle states (C-states) where
the local timer is stopped and powered off. Without a global tick
broadcast timer, one CPU must remain awake to wake up the others,
preventing all CPUs from entering deep idle simultaneously.
This driver provides a tick broadcast timer which remains active
during deep idle states. This allows all CPUs to enter power-cut
idle states simultaneously, significantly reducing overall power
consumption.
The timer operates at 1MHz and supports oneshot mode.
Signed-off-by: Hao-Wen Ting <haowen.ting@realtek.com>
---
MAINTAINERS | 5 +
drivers/clocksource/Kconfig | 10 ++
drivers/clocksource/Makefile | 1 +
drivers/clocksource/timer-realtek.c | 173 ++++++++++++++++++++++++++++
4 files changed, 189 insertions(+)
create mode 100644 drivers/clocksource/timer-realtek.c
diff --git a/MAINTAINERS b/MAINTAINERS
index c7a116b795d5..59dfd7543c39 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -28395,3 +28395,8 @@ S: Buried alive in reporters
T: git git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
F: *
F: */
+
+REALTEK SYSTIMER DRIVER
+M: Hao-Wen Ting <haowen.ting@realtek.com>
+S: Maintained
+F: drivers/clocksource/timer-realtek.c
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index ffcd23668763..e86905378f82 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -782,4 +782,14 @@ 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 OF
+ select TIMER_OF
+ help
+ This enables the global tick-broadcast timer on Realtek platforms.
+ If your Realtek platform supports power-cut level CPU idle states,
+ enabling this timer allows all CPUs to enter power-cut simultaneously
+ to achieve lower power consumption.
+
endmenu
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index ec4452ee958f..b46376af6b49 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -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
diff --git a/drivers/clocksource/timer-realtek.c b/drivers/clocksource/timer-realtek.c
new file mode 100644
index 000000000000..3f3ab276aa86
--- /dev/null
+++ b/drivers/clocksource/timer-realtek.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * 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)
+{
+ u64 ts;
+ u32 low, high;
+
+ /* 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);
+
+ pr_debug("64bit-TS:HW=0x%08x,LW=0x%08x\n", high, low);
+ ts = ((u64)high << 32) | low;
+
+ return ts;
+}
+
+static void rtk_cmp_value_write(u64 value)
+{
+ u32 high, low;
+
+ low = value & 0xFFFFFFFF;
+ high = value >> 32;
+ pr_debug("Write 64bit-CMP:HW=0x%08x,LW=0x%08x\n", high, low);
+
+ 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);
+ pr_debug("Write TS CMP CTRL = 0x%08x\n", val);
+}
+
+static int rtk_syst_clkevt_next_ktime(ktime_t expires,
+ struct clock_event_device *clkevt)
+{
+ u64 cmp_val;
+ unsigned long flags;
+ ktime_t now = ktime_get();
+ s64 delta_ns = ktime_to_ns(ktime_sub(expires, now));
+ u64 delta_us = delta_ns / 1000;
+
+ pr_debug("delta_ns = %lld, clkevt.min_delta_ns = %llu\n",
+ delta_ns, clkevt->min_delta_ns);
+
+ if (delta_ns <= (s64)clkevt->min_delta_ns) {
+ delta_ns = clkevt->min_delta_ns;
+ delta_us = delta_ns / 1000;
+ pr_debug("Clamping delta_ns to min_delta_ns\n");
+ }
+
+ rtk_cmp_en_write(DSBL);
+ local_irq_save(flags);
+ cmp_val = rtk_ts64_read();
+
+ /* Set CMP value to current timestamp plus delta_us */
+ rtk_cmp_value_write(cmp_val + delta_us);
+ rtk_cmp_en_write(ENBL);
+ local_irq_restore(flags);
+ return 0;
+}
+
+static irqreturn_t rtk_ts_match_intr_handler(int irq, void *dev_id)
+{
+ u32 val;
+ void __iomem *reg_base;
+ struct clock_event_device *clkevt = dev_id;
+
+ /* 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 _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 |
+ CLOCK_EVT_FEAT_KTIME,
+ .set_next_ktime = rtk_syst_clkevt_next_ktime,
+ .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, &_to);
+ if (ret)
+ return ret;
+
+ systimer_base = timer_of_base(&_to);
+ clockevents_config_and_register(&_to.clkevt, SYSTIMER_RATE,
+ SYSTIMER_MIN_DELTA, SYSTIMER_MAX_DELTA);
+
+ pr_info("Realtek SYSTIMER registered\n");
+ return 0;
+}
+
+TIMER_OF_DECLARE(rtk_systimer, "realtek,systimer", rtk_systimer_init);
--
2.34.1
On 11/11/2025 10:29, Hao-Wen Ting wrote:
> Add a tick broadcast timer driver for Realtek SoCs.
>
> On Realtek platforms, CPUs can enter deep idle states (C-states) where
> the local timer is stopped and powered off. Without a global tick
> broadcast timer, one CPU must remain awake to wake up the others,
> preventing all CPUs from entering deep idle simultaneously.
>
> This driver provides a tick broadcast timer which remains active
> during deep idle states. This allows all CPUs to enter power-cut
> idle states simultaneously, significantly reducing overall power
> consumption.
>
> The timer operates at 1MHz and supports oneshot mode.
>
> Signed-off-by: Hao-Wen Ting <haowen.ting@realtek.com>
> ---
> MAINTAINERS | 5 +
> drivers/clocksource/Kconfig | 10 ++
> drivers/clocksource/Makefile | 1 +
> drivers/clocksource/timer-realtek.c | 173 ++++++++++++++++++++++++++++
> 4 files changed, 189 insertions(+)
> create mode 100644 drivers/clocksource/timer-realtek.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index c7a116b795d5..59dfd7543c39 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -28395,3 +28395,8 @@ S: Buried alive in reporters
> T: git git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
> F: *
> F: */
> +
> +REALTEK SYSTIMER DRIVER
Why are you adding to the end of the file? Did you look at this file at
all before changing this?
> +M: Hao-Wen Ting <haowen.ting@realtek.com>
> +S: Maintained
> +F: drivers/clocksource/timer-realtek.c
> diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
> index ffcd23668763..e86905378f82 100644
> --- a/drivers/clocksource/Kconfig
> +++ b/drivers/clocksource/Kconfig
> @@ -782,4 +782,14 @@ 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 OF
Missing depends on ARCH. Please don't send drivers which do not have any
possible user.
> + select TIMER_OF
> + help
> + This enables the global tick-broadcast timer on Realtek platforms.
> + If your Realtek platform supports power-cut level CPU idle states,
> + enabling this timer allows all CPUs to enter power-cut simultaneously
> + to achieve lower power consumption.
> +
...
> +
> +static int __init rtk_systimer_init(struct device_node *node)
> +{
> + int ret;
> +
> + ret = timer_of_init(node, &_to);
> + if (ret)
> + return ret;
> +
> + systimer_base = timer_of_base(&_to);
> + clockevents_config_and_register(&_to.clkevt, SYSTIMER_RATE,
> + SYSTIMER_MIN_DELTA, SYSTIMER_MAX_DELTA);
> +
> + pr_info("Realtek SYSTIMER registered\n");
Drop.
This does not look like useful printk message. Drivers should be silent
on success:
https://elixir.bootlin.com/linux/v6.15-rc7/source/Documentation/process/coding-style.rst#L913
https://elixir.bootlin.com/linux/v6.15-rc7/source/Documentation/process/debugging/driver_development_debugging_guide.rst#L79
> + return 0;
> +}
> +
> +TIMER_OF_DECLARE(rtk_systimer, "realtek,systimer", rtk_systimer_init);
Best regards,
Krzysztof
© 2016 - 2026 Red Hat, Inc.