diff options
Diffstat (limited to 'drivers/clocksource/timer-realtek.c')
| -rw-r--r-- | drivers/clocksource/timer-realtek.c | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/drivers/clocksource/timer-realtek.c b/drivers/clocksource/timer-realtek.c new file mode 100644 index 000000000000..4f0439de9939 --- /dev/null +++ b/drivers/clocksource/timer-realtek.c @@ -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); |
