Skip to content

Commit f7b2c3c

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 d419e49 commit f7b2c3c

File tree

5 files changed

+311
-0
lines changed

5 files changed

+311
-0
lines changed

drivers/timer/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,5 @@ zephyr_library_sources_ifdef(CONFIG_RV32M1_LPTMR_TIMER rv32m1_lptmr_timer.c)
3030
zephyr_library_sources_ifdef(CONFIG_SAM0_RTC_TIMER sam0_rtc_timer.c)
3131
zephyr_library_sources_ifdef(CONFIG_STM32_LPTIM_TIMER stm32_lptim_timer.c)
3232
zephyr_library_sources_ifdef(CONFIG_XLNX_PSTTC_TIMER xlnx_psttc_timer.c)
33+
zephyr_library_sources_ifdef(CONFIG_XLNX_TMRCTR xlnx_tmrctr.c)
3334
zephyr_library_sources_ifdef(CONFIG_XTENSA_TIMER xtensa_sys_timer.c)

drivers/timer/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ source "drivers/timer/Kconfig.rv32m1_lptmr"
9090
source "drivers/timer/Kconfig.sam0_rtc"
9191
source "drivers/timer/Kconfig.stm32_lptim"
9292
source "drivers/timer/Kconfig.xlnx_psttc"
93+
source "drivers/timer/Kconfig.xlnx_tmrctr"
9394
source "drivers/timer/Kconfig.xtensa"
9495

9596
endmenu

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+
default y
11+
help
12+
This module implements a kernel device driver for the MICROBLAZE AXI Timer devices.
13+
AXI timer is not capable of matching against a preloaded value. Thus, it's not
14+
capable of providing TICKLESS_KERNEL.
15+
16+
config XLNX_TMRCTR_TIMER_INDEX
17+
int "Xilinx TMRCTR timer index"
18+
default 0
19+
depends on XLNX_TMRCTR
20+
help
21+
This is the index of timer/counter picked to provide system clock.
22+

drivers/timer/xlnx_tmrctr.c

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
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+
9+
#include <errno.h>
10+
#include <zephyr/device.h>
11+
#include <zephyr/devicetree.h>
12+
#include <zephyr/drivers/timer/system_timer.h>
13+
14+
#include <zephyr/sys_clock.h>
15+
#include <zephyr/arch/cpu.h>
16+
#include <soc.h>
17+
18+
#define DT_DRV_COMPAT xlnx_tmrctr
19+
20+
#define IRQ_TIMER DT_INST_IRQN(CONFIG_XLNX_TMRCTR_TIMER_INDEX)
21+
#define TIMER_CYCLES_PER_SEC DT_INST_PROP(CONFIG_XLNX_TMRCTR_TIMER_INDEX, clock_frequency)
22+
#define BASE_ADDRESS DT_INST_REG_ADDR(0)
23+
24+
#define TICK_TIMER_COUNTER_NUMBER 0U
25+
#define SYS_CLOCK_COUNTER_NUMBER 1U
26+
27+
#define TIMER_CYCLES_PER_TICK (TIMER_CYCLES_PER_SEC / CONFIG_SYS_CLOCK_TICKS_PER_SEC)
28+
#define TICK_TIMER_TOP_VALUE (TIMER_CYCLES_PER_TICK - 1UL)
29+
30+
#define NUM_COUNTERS 2
31+
32+
/* Register definitions */
33+
#define XTC_TCSR_OFFSET 0 /**< Control/Status register */
34+
#define XTC_TLR_OFFSET 4 /**< Load register */
35+
#define XTC_TCR_OFFSET 8 /**< Timer counter register */
36+
37+
/* Control status register mask */
38+
#define XTC_CSR_CASC_MASK 0x00000800
39+
#define XTC_CSR_ENABLE_ALL_MASK 0x00000400
40+
#define XTC_CSR_ENABLE_PWM_MASK 0x00000200
41+
#define XTC_CSR_INT_OCCURRED_MASK 0x00000100
42+
#define XTC_CSR_ENABLE_TMR_MASK 0x00000080
43+
#define XTC_CSR_ENABLE_INT_MASK 0x00000040
44+
#define XTC_CSR_LOAD_MASK 0x00000020
45+
#define XTC_CSR_AUTO_RELOAD_MASK 0x00000010
46+
#define XTC_CSR_EXT_CAPTURE_MASK 0x00000008
47+
#define XTC_CSR_EXT_GENERATE_MASK 0x00000004
48+
#define XTC_CSR_DOWN_COUNT_MASK 0x00000002
49+
#define XTC_CSR_CAPTURE_MODE_MASK 0x00000001
50+
51+
/* 1st counter is at offset 0, 2nd counter is at offset 16 */
52+
#define NUM_REGS_PER_COUNTER 16
53+
#define COUNTER_REG_OFFSET(idx) (NUM_REGS_PER_COUNTER * idx)
54+
55+
static uint32_t last_cycles;
56+
57+
BUILD_ASSERT(TIMER_CYCLES_PER_SEC >= CONFIG_SYS_CLOCK_TICKS_PER_SEC,
58+
"Timer clock frequency must be greater than the system tick "
59+
"frequency");
60+
61+
BUILD_ASSERT((TIMER_CYCLES_PER_SEC % CONFIG_SYS_CLOCK_TICKS_PER_SEC) == 0,
62+
"Timer clock frequency is not divisible by the system tick "
63+
"frequency");
64+
65+
BUILD_ASSERT((CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC % TIMER_CYCLES_PER_SEC) == 0,
66+
"CPU clock frequency is not divisible by the Timer clock frequency "
67+
"frequency");
68+
69+
enum xlnx_tmrctr_state {
70+
XLNX_TMRCTR_INIT, /* Initial (inactive) state */
71+
XLNX_TMRCTR_READY, /* Initialised */
72+
XLNX_TMRCTR_RUNNING /* Started */
73+
};
74+
75+
struct xlnx_tmrctr_data {
76+
mm_reg_t base;
77+
enum xlnx_tmrctr_state state;
78+
};
79+
80+
struct xlnx_tmrctr_data xlnx_tmrctr = {
81+
.base = BASE_ADDRESS,
82+
.state = XLNX_TMRCTR_INIT,
83+
};
84+
85+
#define xlnx_tmrctr_read32(counter_number, offset) \
86+
sys_read32(BASE_ADDRESS + COUNTER_REG_OFFSET(counter_number) + offset)
87+
88+
#define xlnx_tmrctr_write32(counter_number, value, offset) \
89+
sys_write32(value, BASE_ADDRESS + COUNTER_REG_OFFSET(counter_number) + offset)
90+
91+
volatile uint32_t xlnx_tmrctr_read_count(void)
92+
{
93+
return xlnx_tmrctr_read32(SYS_CLOCK_COUNTER_NUMBER, XTC_TCR_OFFSET);
94+
}
95+
96+
volatile uint32_t xlnx_tmrctr_read_hw_cycle_count(void)
97+
{
98+
return (CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC / TIMER_CYCLES_PER_SEC) *
99+
xlnx_tmrctr_read_count();
100+
}
101+
102+
static void xlnx_tmrctr_clear_interrupt(void)
103+
{
104+
uint32_t control_status_register =
105+
xlnx_tmrctr_read32(TICK_TIMER_COUNTER_NUMBER, XTC_TCSR_OFFSET);
106+
107+
xlnx_tmrctr_write32(TICK_TIMER_COUNTER_NUMBER,
108+
control_status_register | XTC_CSR_INT_OCCURRED_MASK, XTC_TCSR_OFFSET);
109+
}
110+
111+
static void xlnx_tmrctr_irq_handler(const void *unused)
112+
{
113+
uint32_t delta_ticks;
114+
115+
ARG_UNUSED(unused);
116+
117+
uint32_t cycles = xlnx_tmrctr_read_hw_cycle_count();
118+
119+
delta_ticks = (cycles - last_cycles) / CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC;
120+
if (delta_ticks == 0) {
121+
/* This is an implicit TICKFUL mode implementation
122+
* We expect the cycle difference to be very low and
123+
* hence the delta_ticks to be 0, if IRQ fires on a timely basis.
124+
* But if an IRQ fire was missed due to some long running critical
125+
* section or TICKLESS mode, then delta should be large enough.
126+
*/
127+
delta_ticks = 1;
128+
}
129+
last_cycles = cycles;
130+
131+
sys_clock_announce(delta_ticks);
132+
133+
xlnx_tmrctr_clear_interrupt();
134+
xlnx_intc_irq_acknowledge(BIT(IRQ_TIMER));
135+
}
136+
137+
uint32_t sys_clock_elapsed(void)
138+
{
139+
/* This is an implicit TICKFUL mode implementation
140+
* We expect the cycle difference to be very low and
141+
* hence the delta_ticks to be 0, if IRQ fires on a timely basis.
142+
*/
143+
uint32_t cycles = xlnx_tmrctr_read_hw_cycle_count();
144+
145+
return (cycles - last_cycles) / CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC;
146+
}
147+
148+
uint32_t sys_clock_cycle_get_32(void)
149+
{
150+
return xlnx_tmrctr_read_hw_cycle_count();
151+
}
152+
153+
static int xlnx_tmrctr_initialize(void)
154+
{
155+
if (xlnx_tmrctr.state != XLNX_TMRCTR_INIT) {
156+
return -EEXIST;
157+
}
158+
159+
xlnx_tmrctr.state = XLNX_TMRCTR_READY;
160+
161+
for (uint8_t counter_number = 0; counter_number < NUM_COUNTERS; counter_number++) {
162+
/* Set the compare register to 0. */
163+
xlnx_tmrctr_write32(counter_number, 0, XTC_TLR_OFFSET);
164+
/* Reset the timer and the interrupt. */
165+
xlnx_tmrctr_write32(counter_number, XTC_CSR_INT_OCCURRED_MASK | XTC_CSR_LOAD_MASK,
166+
XTC_TCSR_OFFSET);
167+
/* Release the reset. */
168+
xlnx_tmrctr_write32(counter_number, 0, XTC_TCSR_OFFSET);
169+
}
170+
171+
return 0;
172+
}
173+
174+
static inline void xlnx_tmrctr_set_reset_value(uint8_t counter_number, uint32_t reset_value)
175+
{
176+
xlnx_tmrctr_write32(counter_number, reset_value, XTC_TLR_OFFSET);
177+
}
178+
179+
static inline void xlnx_tmrctr_set_options(uint8_t counter_number, uint32_t options)
180+
{
181+
xlnx_tmrctr_write32(counter_number, options, XTC_TCSR_OFFSET);
182+
}
183+
184+
static int xlnx_tmrctr_start(void)
185+
{
186+
if (xlnx_tmrctr.state == XLNX_TMRCTR_INIT) {
187+
return -ENODEV;
188+
}
189+
if (xlnx_tmrctr.state == XLNX_TMRCTR_RUNNING) {
190+
return -EALREADY;
191+
}
192+
193+
int control_status_register = xlnx_tmrctr_read32(
194+
TICK_TIMER_COUNTER_NUMBER, XTC_TCSR_OFFSET);
195+
xlnx_tmrctr_write32(TICK_TIMER_COUNTER_NUMBER, XTC_CSR_LOAD_MASK, XTC_TCSR_OFFSET);
196+
xlnx_tmrctr_write32(TICK_TIMER_COUNTER_NUMBER,
197+
control_status_register | XTC_CSR_ENABLE_TMR_MASK, XTC_TCSR_OFFSET);
198+
199+
control_status_register = xlnx_tmrctr_read32(SYS_CLOCK_COUNTER_NUMBER, XTC_TCSR_OFFSET);
200+
xlnx_tmrctr_write32(SYS_CLOCK_COUNTER_NUMBER, XTC_CSR_LOAD_MASK, XTC_TCSR_OFFSET);
201+
xlnx_tmrctr_write32(SYS_CLOCK_COUNTER_NUMBER,
202+
control_status_register | XTC_CSR_ENABLE_TMR_MASK, XTC_TCSR_OFFSET);
203+
204+
xlnx_tmrctr.state = XLNX_TMRCTR_RUNNING;
205+
206+
return 0;
207+
}
208+
209+
static int sys_clock_driver_init(void)
210+
{
211+
int status = xlnx_tmrctr_initialize();
212+
213+
if (status != 0) {
214+
return status;
215+
}
216+
217+
xlnx_tmrctr_set_reset_value(TICK_TIMER_COUNTER_NUMBER, TICK_TIMER_TOP_VALUE);
218+
219+
xlnx_tmrctr_set_options(TICK_TIMER_COUNTER_NUMBER, XTC_CSR_ENABLE_INT_MASK |
220+
XTC_CSR_AUTO_RELOAD_MASK |
221+
XTC_CSR_DOWN_COUNT_MASK);
222+
223+
xlnx_tmrctr_set_options(SYS_CLOCK_COUNTER_NUMBER, XTC_CSR_AUTO_RELOAD_MASK);
224+
225+
status = xlnx_tmrctr_start();
226+
227+
if (status != 0) {
228+
return status;
229+
}
230+
231+
last_cycles = xlnx_tmrctr_read_hw_cycle_count();
232+
233+
IRQ_CONNECT(IRQ_TIMER, 0, xlnx_tmrctr_irq_handler, NULL, 0);
234+
irq_enable(IRQ_TIMER);
235+
236+
return 0;
237+
}
238+
239+
#if defined(CONFIG_MICROBLAZE)
240+
/**
241+
* @brief Overwrite cycle based busy wait
242+
* Implementation is derived from z_impl_k_busy_wait@kernel/timeout.c
243+
*
244+
* @param usec_to_wait
245+
* @note Microblaze arch already implements an unaccurate, nop based
246+
* no-timer-required busy wait. This routine simply overrides it with
247+
* a much more accurate version.
248+
*/
249+
void arch_busy_wait(uint32_t usec_to_wait)
250+
{
251+
uint32_t start_cycles = xlnx_tmrctr_read_count();
252+
253+
/* use 64-bit math to prevent overflow when multiplying */
254+
uint32_t cycles_to_wait =
255+
(uint32_t)((uint64_t)usec_to_wait * (uint64_t)TIMER_CYCLES_PER_SEC /
256+
(uint64_t)USEC_PER_SEC);
257+
258+
for (;;) {
259+
uint32_t current_cycles = xlnx_tmrctr_read_count();
260+
261+
/* this handles the rollover on an unsigned 32-bit value */
262+
if ((current_cycles - start_cycles) >= cycles_to_wait) {
263+
break;
264+
}
265+
}
266+
}
267+
#endif
268+
269+
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)