// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2025 Realtek Semiconductor Corp. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #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);