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