Skip to content

Commit 844b865

Browse files
committed
drivers: timer: Xilinx AXI Timer Systick Driver
Internal references: FWRIVERHD-4976/FWRIVERHD-4982 Signed-off-by: Alp Sayin <alpsayin@gmail.com>
1 parent 2b150b3 commit 844b865

File tree

5 files changed

+368
-0
lines changed

5 files changed

+368
-0
lines changed

drivers/timer/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ zephyr_library_sources_ifdef(CONFIG_SAM0_RTC_TIMER sam0_rtc_timer.c)
3434
zephyr_library_sources_ifdef(CONFIG_STM32_LPTIM_TIMER stm32_lptim_timer.c)
3535
zephyr_library_sources_ifdef(CONFIG_TI_DM_TIMER ti_dmtimer.c)
3636
zephyr_library_sources_ifdef(CONFIG_XLNX_PSTTC_TIMER xlnx_psttc_timer.c)
37+
zephyr_library_sources_ifdef(CONFIG_XLNX_TMRCTR xlnx_tmrctr.c)
3738
zephyr_library_sources_ifdef(CONFIG_XTENSA_TIMER xtensa_sys_timer.c)
3839
zephyr_library_sources_ifdef(CONFIG_SMARTBOND_TIMER smartbond_timer.c)
3940
zephyr_library_sources_ifdef(CONFIG_MTK_ADSP_TIMER mtk_adsp_timer.c)

drivers/timer/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ source "drivers/timer/Kconfig.smartbond"
9494
source "drivers/timer/Kconfig.stm32_lptim"
9595
source "drivers/timer/Kconfig.ti_dm_timer"
9696
source "drivers/timer/Kconfig.xlnx_psttc"
97+
source "drivers/timer/Kconfig.xlnx_tmrctr"
9798
source "drivers/timer/Kconfig.xtensa"
9899
source "drivers/timer/Kconfig.mtk_adsp"
99100

drivers/timer/Kconfig.xlnx_tmrctr

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Copyright (c) 2023 Advanced Micro Devices, Inc. (AMD)
2+
# Copyright (c) 2023 Alp Sayin <alpsayin@gmail.com>
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
6+
7+
8+
config XLNX_TMRCTR
9+
bool "Xilinx AXI Timer/Counter"
10+
select TICKLESS_CAPABLE
11+
default y if MICROBLAZE
12+
help
13+
This module implements a kernel device driver for the MICROBLAZE AXI Timer devices.
14+
AXI timer is not capable of matching against a preloaded value. Thus, it's not
15+
capable of providing TICKLESS_KERNEL.
16+
17+
config XLNX_TMRCTR_TIMER_INDEX
18+
int "Xilinx TMRCTR timer index"
19+
default 0
20+
depends on XLNX_TMRCTR
21+
help
22+
This is the index of timer/counter picked to provide system clock.

drivers/timer/xlnx_tmrctr.c

Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
/*
2+
* Copyright (c) 2023 Advanced Micro Devices, Inc. (AMD)
3+
* Copyright (c) 2023 Alp Sayin <alpsayin@gmail.com>
4+
*
5+
* SPDX-License-Identifier: Apache-2.0
6+
*/
7+
8+
#include <errno.h>
9+
#include <zephyr/device.h>
10+
#include <zephyr/devicetree.h>
11+
#include <zephyr/drivers/timer/system_timer.h>
12+
13+
#include <zephyr/sys_clock.h>
14+
#include <zephyr/arch/cpu.h>
15+
#include <soc.h>
16+
17+
#define DT_DRV_COMPAT xlnx_tmrctr
18+
19+
#define IRQ_TIMER DT_INST_IRQN(CONFIG_XLNX_TMRCTR_TIMER_INDEX)
20+
#define TIMER_CYCLES_PER_SEC DT_INST_PROP(CONFIG_XLNX_TMRCTR_TIMER_INDEX, clock_frequency)
21+
#define BASE_ADDRESS DT_INST_REG_ADDR(0)
22+
23+
#define TICK_TIMER_COUNTER_NUMBER 0U
24+
#define SYS_CLOCK_COUNTER_NUMBER 1U
25+
26+
#define TIMER_CYCLES_PER_TICK (TIMER_CYCLES_PER_SEC / CONFIG_SYS_CLOCK_TICKS_PER_SEC)
27+
#define TICK_TIMER_TOP_VALUE (TIMER_CYCLES_PER_TICK - 1UL)
28+
29+
#define NUM_COUNTERS 2
30+
31+
/* Register definitions */
32+
#define XTC_TCSR_OFFSET 0 /**< Control/Status register */
33+
#define XTC_TLR_OFFSET 4 /**< Load register */
34+
#define XTC_TCR_OFFSET 8 /**< Timer counter register */
35+
36+
/* Control status register mask */
37+
#define XTC_CSR_CASC_MASK 0x00000800
38+
#define XTC_CSR_ENABLE_ALL_MASK 0x00000400
39+
#define XTC_CSR_ENABLE_PWM_MASK 0x00000200
40+
#define XTC_CSR_INT_OCCURRED_MASK 0x00000100
41+
#define XTC_CSR_ENABLE_TMR_MASK 0x00000080
42+
#define XTC_CSR_ENABLE_INT_MASK 0x00000040
43+
#define XTC_CSR_LOAD_MASK 0x00000020
44+
#define XTC_CSR_AUTO_RELOAD_MASK 0x00000010
45+
#define XTC_CSR_EXT_CAPTURE_MASK 0x00000008
46+
#define XTC_CSR_EXT_GENERATE_MASK 0x00000004
47+
#define XTC_CSR_DOWN_COUNT_MASK 0x00000002
48+
#define XTC_CSR_CAPTURE_MODE_MASK 0x00000001
49+
50+
/* 1st counter is at offset 0, 2nd counter is at offset 16 */
51+
#define NUM_REGS_PER_COUNTER 16
52+
#define COUNTER_REG_OFFSET(idx) (NUM_REGS_PER_COUNTER * idx)
53+
54+
/*
55+
* CYCLES_NEXT_MIN must be large enough to ensure that the timer does not miss
56+
* interrupts. This value was conservatively set, and there is room for improvement.
57+
*/
58+
#define CYCLES_NEXT_MIN (TIMER_CYCLES_PER_SEC / 5000)
59+
/* We allow only half the maximum numerical range of the cycle counters so that we
60+
* can never miss a sysclock overflow. This is also being very conservative.
61+
*/
62+
#define CYCLES_NEXT_MAX (0xFFFFFFFFU / 2)
63+
64+
static volatile uint32_t last_cycles;
65+
66+
BUILD_ASSERT(TIMER_CYCLES_PER_SEC >= CONFIG_SYS_CLOCK_TICKS_PER_SEC,
67+
"Timer clock frequency must be greater than the system tick "
68+
"frequency");
69+
70+
BUILD_ASSERT((TIMER_CYCLES_PER_SEC % CONFIG_SYS_CLOCK_TICKS_PER_SEC) == 0,
71+
"Timer clock frequency is not divisible by the system tick "
72+
"frequency");
73+
74+
BUILD_ASSERT((CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC % TIMER_CYCLES_PER_SEC) == 0,
75+
"CPU clock frequency is not divisible by the Timer clock frequency "
76+
"frequency");
77+
78+
enum xlnx_tmrctr_state {
79+
XLNX_TMRCTR_INIT, /* Initial (inactive) state */
80+
XLNX_TMRCTR_READY, /* Initialised */
81+
XLNX_TMRCTR_RUNNING /* Started */
82+
};
83+
84+
struct xlnx_tmrctr_data {
85+
mm_reg_t base;
86+
enum xlnx_tmrctr_state state;
87+
};
88+
89+
struct xlnx_tmrctr_data xlnx_tmrctr = {
90+
.base = BASE_ADDRESS,
91+
.state = XLNX_TMRCTR_INIT,
92+
};
93+
94+
#define xlnx_tmrctr_read32(counter_number, offset) \
95+
sys_read32(BASE_ADDRESS + COUNTER_REG_OFFSET(counter_number) + offset)
96+
97+
#define xlnx_tmrctr_write32(counter_number, value, offset) \
98+
sys_write32(value, BASE_ADDRESS + COUNTER_REG_OFFSET(counter_number) + offset)
99+
100+
volatile uint32_t xlnx_tmrctr_read_count(void)
101+
{
102+
return xlnx_tmrctr_read32(SYS_CLOCK_COUNTER_NUMBER, XTC_TCR_OFFSET);
103+
}
104+
105+
volatile uint32_t xlnx_tmrctr_read_hw_cycle_count(void)
106+
{
107+
return (CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC / TIMER_CYCLES_PER_SEC) *
108+
xlnx_tmrctr_read_count();
109+
}
110+
111+
static void xlnx_tmrctr_clear_interrupt(void)
112+
{
113+
uint32_t control_status_register =
114+
xlnx_tmrctr_read32(TICK_TIMER_COUNTER_NUMBER, XTC_TCSR_OFFSET);
115+
116+
xlnx_tmrctr_write32(TICK_TIMER_COUNTER_NUMBER,
117+
control_status_register | XTC_CSR_INT_OCCURRED_MASK, XTC_TCSR_OFFSET);
118+
}
119+
120+
static inline void xlnx_tmrctr_set_reset_value(uint8_t counter_number, uint32_t reset_value)
121+
{
122+
xlnx_tmrctr_write32(counter_number, reset_value, XTC_TLR_OFFSET);
123+
}
124+
125+
static inline void xlnx_tmrctr_set_options(uint8_t counter_number, uint32_t options)
126+
{
127+
xlnx_tmrctr_write32(counter_number, options, XTC_TCSR_OFFSET);
128+
}
129+
130+
#ifdef CONFIG_TICKLESS_KERNEL
131+
static void xlnx_tmrctr_reload_tick_timer(uint32_t delta_cycles)
132+
{
133+
uint32_t csr_val;
134+
uint32_t cur_cycle_count = xlnx_tmrctr_read_count();
135+
136+
/* Ensure that the delta_cycles value meets the timing requirements */
137+
if (delta_cycles < CYCLES_NEXT_MIN) {
138+
/* Don't risk missing an interrupt */
139+
delta_cycles = CYCLES_NEXT_MIN;
140+
}
141+
if (delta_cycles > CYCLES_NEXT_MAX - cur_cycle_count) {
142+
/* Don't risk missing a sysclock overflow */
143+
delta_cycles = CYCLES_NEXT_MAX - cur_cycle_count;
144+
}
145+
146+
/* Write counter load value for interrupt generation */
147+
xlnx_tmrctr_set_reset_value(TICK_TIMER_COUNTER_NUMBER, delta_cycles);
148+
149+
/* Load the load value */
150+
csr_val = xlnx_tmrctr_read32(TICK_TIMER_COUNTER_NUMBER, XTC_TCSR_OFFSET);
151+
xlnx_tmrctr_write32(TICK_TIMER_COUNTER_NUMBER, csr_val | XTC_CSR_LOAD_MASK,
152+
XTC_TCSR_OFFSET);
153+
xlnx_tmrctr_write32(TICK_TIMER_COUNTER_NUMBER, csr_val, XTC_TCSR_OFFSET);
154+
}
155+
#endif /* CONFIG_TICKLESS_KERNEL */
156+
157+
static void xlnx_tmrctr_irq_handler(const void *unused)
158+
{
159+
uint32_t cycles;
160+
uint32_t delta_ticks;
161+
162+
ARG_UNUSED(unused);
163+
164+
cycles = xlnx_tmrctr_read_count();
165+
/* Calculate the number of ticks since last announcement */
166+
delta_ticks = (cycles - last_cycles) / TIMER_CYCLES_PER_TICK;
167+
/* Update last cycles count without the rounding error */
168+
last_cycles += (delta_ticks * TIMER_CYCLES_PER_TICK);
169+
170+
/* Announce to the kernel*/
171+
sys_clock_announce(delta_ticks);
172+
173+
xlnx_tmrctr_clear_interrupt();
174+
xlnx_intc_irq_acknowledge(BIT(IRQ_TIMER));
175+
}
176+
177+
void sys_clock_set_timeout(int32_t ticks, bool idle)
178+
{
179+
#ifdef CONFIG_TICKLESS_KERNEL
180+
uint32_t cycles;
181+
uint32_t delta_cycles;
182+
183+
/* Read counter value */
184+
cycles = xlnx_tmrctr_read_count();
185+
186+
/* Calculate timeout counter value */
187+
if (ticks == K_TICKS_FOREVER) {
188+
delta_cycles = CYCLES_NEXT_MAX;
189+
} else {
190+
delta_cycles = ((uint32_t)ticks * TIMER_CYCLES_PER_TICK);
191+
}
192+
193+
/* Set timer reload value for the next interrupt */
194+
xlnx_tmrctr_reload_tick_timer(delta_cycles);
195+
#endif
196+
}
197+
198+
uint32_t sys_clock_elapsed(void)
199+
{
200+
#ifdef CONFIG_TICKLESS_KERNEL
201+
uint32_t cycles = xlnx_tmrctr_read_count();
202+
203+
return (cycles - last_cycles) / TIMER_CYCLES_PER_TICK;
204+
#else
205+
/* Always return 0 for tickful operation */
206+
return 0;
207+
#endif
208+
}
209+
210+
uint32_t sys_clock_cycle_get_32(void)
211+
{
212+
return xlnx_tmrctr_read_hw_cycle_count();
213+
}
214+
215+
static int xlnx_tmrctr_initialize(void)
216+
{
217+
if (xlnx_tmrctr.state != XLNX_TMRCTR_INIT) {
218+
return -EEXIST;
219+
}
220+
221+
xlnx_tmrctr.state = XLNX_TMRCTR_READY;
222+
223+
for (uint8_t counter_number = 0; counter_number < NUM_COUNTERS; counter_number++) {
224+
/* Set the compare register to 0. */
225+
xlnx_tmrctr_write32(counter_number, 0, XTC_TLR_OFFSET);
226+
/* Reset the timer and the interrupt. */
227+
xlnx_tmrctr_write32(counter_number, XTC_CSR_INT_OCCURRED_MASK | XTC_CSR_LOAD_MASK,
228+
XTC_TCSR_OFFSET);
229+
/* Release the reset. */
230+
xlnx_tmrctr_write32(counter_number, 0, XTC_TCSR_OFFSET);
231+
}
232+
233+
return 0;
234+
}
235+
236+
static int xlnx_tmrctr_start(void)
237+
{
238+
if (xlnx_tmrctr.state == XLNX_TMRCTR_INIT) {
239+
return -ENODEV;
240+
}
241+
if (xlnx_tmrctr.state == XLNX_TMRCTR_RUNNING) {
242+
return -EALREADY;
243+
}
244+
245+
int control_status_register = xlnx_tmrctr_read32(
246+
TICK_TIMER_COUNTER_NUMBER, XTC_TCSR_OFFSET);
247+
xlnx_tmrctr_write32(TICK_TIMER_COUNTER_NUMBER, XTC_CSR_LOAD_MASK, XTC_TCSR_OFFSET);
248+
xlnx_tmrctr_write32(TICK_TIMER_COUNTER_NUMBER,
249+
control_status_register | XTC_CSR_ENABLE_TMR_MASK, XTC_TCSR_OFFSET);
250+
251+
control_status_register = xlnx_tmrctr_read32(SYS_CLOCK_COUNTER_NUMBER, XTC_TCSR_OFFSET);
252+
xlnx_tmrctr_write32(SYS_CLOCK_COUNTER_NUMBER, XTC_CSR_LOAD_MASK, XTC_TCSR_OFFSET);
253+
xlnx_tmrctr_write32(SYS_CLOCK_COUNTER_NUMBER,
254+
control_status_register | XTC_CSR_ENABLE_TMR_MASK, XTC_TCSR_OFFSET);
255+
256+
xlnx_tmrctr.state = XLNX_TMRCTR_RUNNING;
257+
258+
return 0;
259+
}
260+
261+
static int sys_clock_driver_init(void)
262+
{
263+
int status = xlnx_tmrctr_initialize();
264+
265+
if (status != 0) {
266+
return status;
267+
}
268+
269+
#if !defined(CONFIG_TICKLESS_KERNEL)
270+
xlnx_tmrctr_set_reset_value(TICK_TIMER_COUNTER_NUMBER, CYCLES_NEXT_MAX);
271+
xlnx_tmrctr_set_options(TICK_TIMER_COUNTER_NUMBER, XTC_CSR_ENABLE_INT_MASK |
272+
XTC_CSR_DOWN_COUNT_MASK);
273+
#else
274+
xlnx_tmrctr_set_reset_value(TICK_TIMER_COUNTER_NUMBER, TIMER_CYCLES_PER_TICK);
275+
xlnx_tmrctr_set_options(TICK_TIMER_COUNTER_NUMBER, XTC_CSR_ENABLE_INT_MASK |
276+
XTC_CSR_AUTO_RELOAD_MASK |
277+
XTC_CSR_DOWN_COUNT_MASK);
278+
#endif
279+
280+
xlnx_tmrctr_set_options(SYS_CLOCK_COUNTER_NUMBER, XTC_CSR_AUTO_RELOAD_MASK);
281+
282+
status = xlnx_tmrctr_start();
283+
284+
if (status != 0) {
285+
return status;
286+
}
287+
288+
last_cycles = xlnx_tmrctr_read_count();
289+
290+
IRQ_CONNECT(IRQ_TIMER, 0, xlnx_tmrctr_irq_handler, NULL, 0);
291+
irq_enable(IRQ_TIMER);
292+
293+
return 0;
294+
}
295+
296+
#if defined(CONFIG_MICROBLAZE)
297+
/**
298+
* @brief Overwrite cycle based busy wait
299+
* Implementation is derived from z_impl_k_busy_wait@kernel/timeout.c
300+
*
301+
* @param usec_to_wait
302+
* @note Microblaze arch already implements an unaccurate, nop based
303+
* no-timer-required busy wait. This routine simply overrides it with
304+
* a much more accurate version.
305+
*/
306+
void arch_busy_wait(uint32_t usec_to_wait)
307+
{
308+
uint32_t start_cycles = xlnx_tmrctr_read_count();
309+
310+
/* use 64-bit math to prevent overflow when multiplying */
311+
uint32_t cycles_to_wait =
312+
(uint32_t)((uint64_t)usec_to_wait * (uint64_t)TIMER_CYCLES_PER_SEC /
313+
(uint64_t)USEC_PER_SEC);
314+
315+
for (;;) {
316+
uint32_t current_cycles = xlnx_tmrctr_read_count();
317+
318+
/* this handles the rollover on an unsigned 32-bit value */
319+
if ((current_cycles - start_cycles) >= cycles_to_wait) {
320+
break;
321+
}
322+
}
323+
}
324+
#endif
325+
326+
SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2, CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);

dts/bindings/timer/xlnx,tmrctr.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copyright (c) 2023 Advanced Micro Devices, Inc. (AMD)
2+
# Copyright (c) 2023 Alp Sayin <alpsayin@gmail.com>
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
description: Xilinx AXI TMRCTR timer counter
6+
7+
compatible: "xlnx,tmrctr"
8+
9+
include: base.yaml
10+
11+
properties:
12+
reg:
13+
required: true
14+
15+
clock-frequency:
16+
type: int
17+
required: true
18+
description: Clock frequency information for Timer operation

0 commit comments

Comments
 (0)