]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
clocksource/drivers: Add Realtek system timer driver
authorHao-Wen Ting <haowen.ting@realtek.com>
Wed, 26 Nov 2025 06:01:10 +0000 (14:01 +0800)
committerDaniel Lezcano <daniel.lezcano@linaro.org>
Wed, 26 Nov 2025 10:25:15 +0000 (11:25 +0100)
Add a system timer driver for Realtek SoCs.

This driver registers the 1 MHz global hardware counter on Realtek
platforms as a clock event device. Since this hardware counter starts
counting automatically after SoC power-on, no clock initialization is
required. Because the counter does not stop or get affected by CPU power
down, and it supports oneshot mode, it is typically used as a tick
broadcast timer.

Signed-off-by: Hao-Wen Ting <haowen.ting@realtek.com>
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Acked-by: Thomas Gleixner <tglx@linutronix.de>
Link: https://patch.msgid.link/20251126060110.198330-3-haowen.ting@realtek.com
MAINTAINERS
drivers/clocksource/Kconfig
drivers/clocksource/Makefile
drivers/clocksource/timer-realtek.c [new file with mode: 0644]

index 3da2c26a796b82b9de4143c1591a9b1d44d42194..b72b9873b90e99ea1ca67386b512eb0e0e1c3e8e 100644 (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
index ffcd23668763fe7707a4e917bf240caadbb09a8c..aa59e5b133510f9602578d9b78f19e82f1357449 100644 (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
index ec4452ee958f1a814c708aeba6412bea61d24892..b46376af6b4901a9163a5b94f79a3082e0ab6b9e 100644 (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
diff --git a/drivers/clocksource/timer-realtek.c b/drivers/clocksource/timer-realtek.c
new file mode 100644 (file)
index 0000000..4f0439d
--- /dev/null
@@ -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);