Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Copyright (c) 2025 Realtek Semiconductor Corp.
4 */
5
6#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
7
8#include <linux/irqflags.h>
9#include <linux/interrupt.h>
10#include "timer-of.h"
11
12#define ENBL 1
13#define DSBL 0
14
15#define SYSTIMER_RATE 1000000
16#define SYSTIMER_MIN_DELTA 0x64
17#define SYSTIMER_MAX_DELTA ULONG_MAX
18
19/* SYSTIMER Register Offset (RTK Internal Use) */
20#define TS_LW_OFST 0x0
21#define TS_HW_OFST 0x4
22#define TS_CMP_VAL_LW_OFST 0x8
23#define TS_CMP_VAL_HW_OFST 0xC
24#define TS_CMP_CTRL_OFST 0x10
25#define TS_CMP_STAT_OFST 0x14
26
27/* SYSTIMER CMP CTRL REG Mask */
28#define TS_CMP_EN_MASK 0x1
29#define TS_WR_EN0_MASK 0x2
30
31static void __iomem *systimer_base;
32
33static u64 rtk_ts64_read(void)
34{
35 u32 low, high;
36 u64 ts;
37
38 /* Caution: Read LSB word (TS_LW_OFST) first then MSB (TS_HW_OFST) */
39 low = readl(systimer_base + TS_LW_OFST);
40 high = readl(systimer_base + TS_HW_OFST);
41 ts = ((u64)high << 32) | low;
42
43 return ts;
44}
45
46static void rtk_cmp_value_write(u64 value)
47{
48 u32 high, low;
49
50 low = value & 0xFFFFFFFF;
51 high = value >> 32;
52
53 writel(high, systimer_base + TS_CMP_VAL_HW_OFST);
54 writel(low, systimer_base + TS_CMP_VAL_LW_OFST);
55}
56
57static inline void rtk_cmp_en_write(bool cmp_en)
58{
59 u32 val;
60
61 val = TS_WR_EN0_MASK;
62 if (cmp_en == ENBL)
63 val |= TS_CMP_EN_MASK;
64
65 writel(val, systimer_base + TS_CMP_CTRL_OFST);
66}
67
68static int rtk_syst_clkevt_next_event(unsigned long cycles, struct clock_event_device *clkevt)
69{
70 u64 cmp_val;
71
72 rtk_cmp_en_write(DSBL);
73 cmp_val = rtk_ts64_read();
74
75 /* Set CMP value to current timestamp plus delta_us */
76 rtk_cmp_value_write(cmp_val + cycles);
77 rtk_cmp_en_write(ENBL);
78 return 0;
79}
80
81static irqreturn_t rtk_ts_match_intr_handler(int irq, void *dev_id)
82{
83 struct clock_event_device *clkevt = dev_id;
84 void __iomem *reg_base;
85 u32 val;
86
87 /* Disable TS CMP Match */
88 rtk_cmp_en_write(DSBL);
89
90 /* Clear TS CMP INTR */
91 reg_base = systimer_base + TS_CMP_STAT_OFST;
92 val = readl(reg_base) & TS_CMP_EN_MASK;
93 writel(val | TS_CMP_EN_MASK, reg_base);
94 clkevt->event_handler(clkevt);
95
96 return IRQ_HANDLED;
97}
98
99static int rtk_syst_shutdown(struct clock_event_device *clkevt)
100{
101 void __iomem *reg_base;
102 u64 cmp_val = 0;
103
104 /* Disable TS CMP Match */
105 rtk_cmp_en_write(DSBL);
106 /* Set compare value to 0 */
107 rtk_cmp_value_write(cmp_val);
108
109 /* Clear TS CMP INTR */
110 reg_base = systimer_base + TS_CMP_STAT_OFST;
111 writel(TS_CMP_EN_MASK, reg_base);
112 return 0;
113}
114
115static struct timer_of rtk_timer_to = {
116 .flags = TIMER_OF_IRQ | TIMER_OF_BASE,
117
118 .clkevt = {
119 .name = "rtk-clkevt",
120 .rating = 300,
121 .cpumask = cpu_possible_mask,
122 .features = CLOCK_EVT_FEAT_DYNIRQ |
123 CLOCK_EVT_FEAT_ONESHOT,
124 .set_next_event = rtk_syst_clkevt_next_event,
125 .set_state_oneshot = rtk_syst_shutdown,
126 .set_state_shutdown = rtk_syst_shutdown,
127 },
128
129 .of_irq = {
130 .flags = IRQF_TIMER | IRQF_IRQPOLL,
131 .handler = rtk_ts_match_intr_handler,
132 },
133};
134
135static int __init rtk_systimer_init(struct device_node *node)
136{
137 int ret;
138
139 ret = timer_of_init(node, &rtk_timer_to);
140 if (ret)
141 return ret;
142
143 systimer_base = timer_of_base(&rtk_timer_to);
144 clockevents_config_and_register(&rtk_timer_to.clkevt, SYSTIMER_RATE,
145 SYSTIMER_MIN_DELTA, SYSTIMER_MAX_DELTA);
146
147 return 0;
148}
149
150TIMER_OF_DECLARE(rtk_systimer, "realtek,rtd1625-systimer", rtk_systimer_init);