| // 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); |