Skip to content

Commit 927df19

Browse files
billwatersiiimcatee-infineon
authored andcommitted
drivers: timer: infineon pdl lp_timer
Add PDL-based low-power timer for the E84 board Signed-off-by: Bill Waters <bill.waters@infineon.com>
1 parent 3b38d6c commit 927df19

File tree

4 files changed

+407
-0
lines changed

4 files changed

+407
-0
lines changed

drivers/timer/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ zephyr_library_sources_ifdef(CONFIG_ESP32_SYS_TIMER esp32_sys_timer.c)
1919
zephyr_library_sources_ifdef(CONFIG_GECKO_BURTC_TIMER gecko_burtc_timer.c)
2020
zephyr_library_sources_ifdef(CONFIG_HPET_TIMER hpet.c)
2121
zephyr_library_sources_ifdef(CONFIG_INFINEON_CAT1_LP_TIMER infineon_lp_timer.c)
22+
zephyr_library_sources_ifdef(CONFIG_INFINEON_CAT1_LP_TIMER_PDL ifx_cat1_lp_timer_pdl.c)
2223
zephyr_library_sources_ifdef(CONFIG_INTEL_ADSP_TIMER intel_adsp_timer.c)
2324
zephyr_library_sources_ifdef(CONFIG_ITE_IT51XXX_TIMER ite_it51xxx_timer.c)
2425
zephyr_library_sources_ifdef(CONFIG_ITE_IT8XXX2_TIMER ite_it8xxx2_timer.c)

drivers/timer/Kconfig.infineon_lp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,14 @@ config INFINEON_CAT1_LP_TIMER
1515
help
1616
This module implements a kernel device driver for the LowPower Timer
1717
and provides the standard "system clock driver" interfaces.
18+
19+
config INFINEON_CAT1_LP_TIMER_PDL
20+
bool "Infineon CAT1 Low Power Timer driver"
21+
default y
22+
depends on DT_HAS_INFINEON_CAT1_LP_TIMER_PDL_ENABLED
23+
depends on PM
24+
select USE_INFINEON_LPTIMER
25+
select TICKLESS_CAPABLE
26+
help
27+
This module implements a kernel device driver for the LowPower Timer
28+
and provides the standard "system clock driver" interfaces.
Lines changed: 375 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,375 @@
1+
/*
2+
* Copyright (c) 2025 Infineon Technologies AG,
3+
* or an affiliate of Infineon Technologies AG.
4+
*
5+
* SPDX-License-Identifier: Apache-2.0
6+
*/
7+
8+
/**
9+
* @brief Low Power timer driver for Infineon CAT1 MCU family.
10+
*/
11+
12+
#define DT_DRV_COMPAT infineon_cat1_lp_timer_pdl
13+
14+
#include <zephyr/device.h>
15+
#include <zephyr/drivers/timer/system_timer.h>
16+
#include <zephyr/irq.h>
17+
#include <zephyr/spinlock.h>
18+
#include <zephyr/sys_clock.h>
19+
#include <zephyr/drivers/gpio.h>
20+
21+
#include <zephyr/logging/log.h>
22+
LOG_MODULE_REGISTER(ifx_cat1_lp_timer_pdl, CONFIG_KERNEL_LOG_LEVEL);
23+
24+
/* Enable the LPTimer counters. Here we enable two 16-bit counters and one 32-bit counter to
25+
* create a 64-bit counter
26+
*/
27+
#define LPTIMER_COUNTERS (CY_MCWDT_CTR0 | CY_MCWDT_CTR1 | CY_MCWDT_CTR2)
28+
29+
/* The application only needs one lptimer. Report an error if more than one is selected. */
30+
#if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) > 1
31+
#error Only one LPTIMER instance should be enabled
32+
#endif /* DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) > 1 */
33+
34+
/* Minimum amount of lfclk cycles of that LPTIMER can delay for. */
35+
#define LPTIMER_MIN_DELAY (3U)
36+
/* ~36hours. Not set to 0xffffffff to avoid C0 and C1 both overflowing */
37+
#define LPTIMER_MAX_DELAY_TICKS (0xfff0ffffUL)
38+
39+
static bool clear_int_mask;
40+
static uint8_t isr_instruction;
41+
42+
static const MCWDT_STRUCT_Type *reg_addr = (MCWDT_STRUCT_Type *)DT_INST_REG_ADDR(0);
43+
static const uint32_t clock_frequency = DT_INST_PROP(0, clock_frequency);
44+
45+
#define DEFAULT_TIMEOUT (0xFFFFUL)
46+
47+
#include "cy_mcwdt.h"
48+
49+
#if defined(CY_IP_MXS40SSRSS)
50+
static const uint16_t LPTIMER_RESET_TIME_US = 93;
51+
#else
52+
static const uint16_t LPTIMER_RESET_TIME_US = 62;
53+
#endif
54+
55+
static const uint16_t LPTIMER_SETMATCH_TIME_US;
56+
static const cy_stc_mcwdt_config_t lptimer_default_cfg = {.c0Match = 0xFFFF,
57+
.c1Match = 0xFFFF,
58+
.c0Mode = CY_MCWDT_MODE_INT,
59+
.c1Mode = CY_MCWDT_MODE_INT,
60+
.c2Mode = CY_MCWDT_MODE_NONE,
61+
.c2ToggleBit = 0,
62+
.c0ClearOnMatch = false,
63+
.c1ClearOnMatch = false,
64+
.c0c1Cascade = true,
65+
.c1c2Cascade = false};
66+
67+
static uint32_t last_lptimer_value;
68+
static struct k_spinlock lock;
69+
70+
static void lptimer_enable_event(bool enable)
71+
{
72+
#define LPTIMER_ISR_CALL_USER_CB_MASK (0x01)
73+
isr_instruction &= ~LPTIMER_ISR_CALL_USER_CB_MASK;
74+
isr_instruction |= (uint8_t)enable;
75+
76+
if (enable) {
77+
Cy_MCWDT_ClearInterrupt(reg_addr, CY_MCWDT_CTR1);
78+
Cy_MCWDT_SetInterruptMask(reg_addr, CY_MCWDT_CTR1);
79+
80+
} else {
81+
Cy_MCWDT_ClearInterrupt(reg_addr, CY_MCWDT_CTR1);
82+
Cy_MCWDT_SetInterruptMask(reg_addr, 0);
83+
}
84+
}
85+
86+
static void lptimer_set_delay(uint32_t delay)
87+
{
88+
uint16_t c0_old_match;
89+
uint32_t critical_section;
90+
uint32_t timeout = DEFAULT_TIMEOUT;
91+
uint16_t c0_current_ticks;
92+
uint16_t c0_match;
93+
uint32_t c0_new_ticks;
94+
uint16_t c1_current_ticks;
95+
uint16_t c1_match;
96+
97+
clear_int_mask = true;
98+
99+
if ((Cy_MCWDT_GetEnabledStatus(reg_addr, CY_MCWDT_COUNTER0) == 0UL) ||
100+
(Cy_MCWDT_GetEnabledStatus(reg_addr, CY_MCWDT_COUNTER1) == 0UL) ||
101+
(Cy_MCWDT_GetEnabledStatus(reg_addr, CY_MCWDT_COUNTER2) == 0UL)) {
102+
return;
103+
}
104+
105+
/* - 16 bit Counter0 (C0) & Counter1 (C1) are cascaded to generated a 32 bit counter.
106+
* - Counter2 (C2) is a free running counter.
107+
* - C0 continues counting after reaching its match value. On PSoC™ 4 Counter1 is reset on
108+
* match. On PSoC™ 6 it continues counting.
109+
* - An interrupt is generated when C1 reaches the match value. On PSoC™ 4 this happens
110+
* when the counter increments to the same value as match. On PSoC™ 6 this happens when it
111+
* increments past the match value.
112+
*
113+
* EXAMPLE:
114+
* Supposed T=C0=C1=0, and we need to trigger an interrupt at T=0x18000.
115+
* We set C0_match to 0x8000 and C1 match to 2 on PSoC™ 4 and 1 on PSoC™ 6.
116+
* At T = 0x8000, C0_value matches C0_match so C1 get incremented. C1/C0=0x18000.
117+
* At T = 0x18000, C0_value matches C0_match again so C1 get incremented from 1 to 2.
118+
* When C1 get incremented from 1 to 2 the interrupt is generated.
119+
* At T = 0x18000, C1/C0 = 0x28000.
120+
*/
121+
122+
if (delay <= LPTIMER_MIN_DELAY) {
123+
delay = LPTIMER_MIN_DELAY;
124+
}
125+
if (delay > LPTIMER_MAX_DELAY_TICKS) {
126+
delay = LPTIMER_MAX_DELAY_TICKS;
127+
}
128+
129+
Cy_MCWDT_ClearInterrupt(reg_addr, CY_MCWDT_CTR1);
130+
c0_old_match = (uint16_t)Cy_MCWDT_GetMatch(reg_addr, CY_MCWDT_COUNTER0);
131+
critical_section = Cy_SysLib_EnterCriticalSection();
132+
133+
/* Cascading from C0 match into C1 is queued and can take 1 full LF clk cycle.
134+
* There are 3 cases:
135+
* Case 1: if c0 = match0 then the cascade into C1 will happen 1 cycle from now. The value
136+
* c1_current_ticks is 1 lower than expected. Case 2: if c0 = match0 -1 then cascade may or
137+
* not happen before new match value would occur. Match occurs on rising clock edge.
138+
* Synchronizing match value occurs on falling edge. Wait until c0 = match0 to
139+
* ensure cascade occurs. Case 3: everything works as expected.
140+
*
141+
* Note: timeout is needed here just in case the LFCLK source gives out. This avoids device
142+
* lockup.
143+
*
144+
* ((2 * Cycles_LFClk) / Cycles_cpu_iteration) * (HFCLk_max / LFClk_min) =
145+
* Iterations_required Typical case: (2 / 100) * ((150x10^6)/33576) = 89 iterations Worst
146+
* case: (2 / 100) * ((150x10^6)/1) = 3x10^6 iterations Compromise: (2 / 100) *
147+
* ((150x10^6)/0xFFFF iterations) = 45 Hz = LFClk_min
148+
*/
149+
c0_current_ticks = (uint16_t)Cy_MCWDT_GetCount(reg_addr, CY_MCWDT_COUNTER0);
150+
/* Wait until the cascade has definitively happened. It takes a clock cycle for the
151+
* cascade to happen, and potentially another a full LFCLK clock cycle for the
152+
* cascade to propagate up to the HFCLK-domain registers that the CPU reads.
153+
*/
154+
while (((((uint16_t)(c0_old_match - 1)) == c0_current_ticks) ||
155+
(c0_old_match == c0_current_ticks) ||
156+
(((uint16_t)(c0_old_match + 1)) == c0_current_ticks)) &&
157+
(timeout != 0UL)) {
158+
c0_current_ticks = (uint16_t)Cy_MCWDT_GetCount(reg_addr, CY_MCWDT_COUNTER0);
159+
timeout--;
160+
}
161+
162+
if (timeout == 0UL) {
163+
/* Timeout has occurred. There could have been a clock failure while waiting for the
164+
* count value to update.
165+
*/
166+
Cy_SysLib_ExitCriticalSection(critical_section);
167+
return;
168+
}
169+
170+
c0_match = (uint16_t)(c0_current_ticks + delay);
171+
/* Changes can take up to 2 clk_lf cycles to propagate. If we set the match within this
172+
* window of the current value, then it is nondeterministic whether the first cascade will
173+
* trigger immediately or after 2^16 cycles. Wait until c0 is in a more predictable state.
174+
*/
175+
timeout = DEFAULT_TIMEOUT;
176+
c0_new_ticks = c0_current_ticks;
177+
178+
while (((c0_new_ticks == c0_match) || (c0_new_ticks == ((uint16_t)(c0_match + 1))) ||
179+
(c0_new_ticks == ((uint16_t)(c0_match + 2)))) &&
180+
(timeout != 0UL)) {
181+
c0_new_ticks = (uint16_t)Cy_MCWDT_GetCount(reg_addr, CY_MCWDT_COUNTER0);
182+
timeout--;
183+
}
184+
185+
delay -= (c0_new_ticks >= c0_current_ticks) ? (c0_new_ticks - c0_current_ticks)
186+
: ((0xFFFFU - c0_current_ticks) + c0_new_ticks);
187+
188+
c0_match = (uint16_t)(c0_current_ticks + delay);
189+
c1_current_ticks = (uint16_t)Cy_MCWDT_GetCount(reg_addr, CY_MCWDT_COUNTER1);
190+
c1_match = (uint16_t)(c1_current_ticks + (delay >> 16));
191+
192+
Cy_MCWDT_SetMatch(reg_addr, CY_MCWDT_COUNTER0, c0_match, LPTIMER_SETMATCH_TIME_US);
193+
Cy_MCWDT_SetMatch(reg_addr, CY_MCWDT_COUNTER1, c1_match, LPTIMER_SETMATCH_TIME_US);
194+
195+
Cy_SysLib_ExitCriticalSection(critical_section);
196+
Cy_MCWDT_SetInterruptMask(reg_addr, CY_MCWDT_CTR1);
197+
}
198+
199+
void sys_clock_set_timeout(int32_t ticks, bool idle)
200+
{
201+
const struct device *lptimer_dev =
202+
DEVICE_DT_GET(DT_COMPAT_GET_ANY_STATUS_OKAY(DT_DRV_COMPAT));
203+
uint32_t current_cycles;
204+
uint32_t cycles_per_tick;
205+
uint32_t delay_cycles;
206+
uint32_t next_tick_cycles;
207+
k_spinlock_key_t key;
208+
209+
ARG_UNUSED(idle);
210+
211+
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
212+
return;
213+
}
214+
215+
if (ticks == K_TICKS_FOREVER) {
216+
/* Disable the LPTIMER events */
217+
lptimer_enable_event(lptimer_dev, false);
218+
return;
219+
}
220+
221+
/* Configure and Enable the LPTIMER events */
222+
lptimer_enable_event(lptimer_dev, true);
223+
224+
/* passing ticks==1 means "announce the next tick", ticks value of zero (or even negative)
225+
* is legal and treated identically: it simply indicates the kernel would like the next
226+
* tick announcement as soon as possible.
227+
*/
228+
if (ticks < 1) {
229+
ticks = 1;
230+
}
231+
232+
/* Calculate cycles per tick */
233+
cycles_per_tick = clock_frequency / CONFIG_SYS_CLOCK_TICKS_PER_SEC;
234+
235+
/* Get current cycle count from the free-running counter */
236+
key = k_spin_lock(&lock);
237+
current_cycles = Cy_MCWDT_GetCount(reg_addr, CY_MCWDT_COUNTER2);
238+
k_spin_unlock(&lock, key);
239+
240+
/* Calculate the next tick-aligned cycle count that is at least 'ticks' in the future.
241+
* This ensures: next_tick_cycles % cycles_per_tick == 0 (tick-aligned)
242+
* AND: next_tick_cycles >= current_cycles + ticks * cycles_per_tick
243+
*/
244+
next_tick_cycles = ((current_cycles / cycles_per_tick) + ticks) * cycles_per_tick;
245+
246+
/* Verify we're at least ticks * cycles_per_tick in the future.
247+
* Due to integer division, we may be slightly less if not at a tick boundary.
248+
* Add one more tick period if needed to satisfy the invariant.
249+
*/
250+
if (next_tick_cycles < current_cycles + (ticks * cycles_per_tick)) {
251+
next_tick_cycles += cycles_per_tick;
252+
}
253+
254+
/* Calculate delay from current position to next tick-aligned position.
255+
* Unsigned arithmetic handles rollover correctly.
256+
*/
257+
delay_cycles = next_tick_cycles - current_cycles;
258+
259+
/* Ensure minimum delay requirement and check for excessive delays.
260+
* The hardware requires a minimum delay and has a maximum delay constraint.
261+
*/
262+
if (delay_cycles < LPTIMER_MIN_DELAY) {
263+
/* If calculated delay is too short, move to next tick boundary */
264+
next_tick_cycles += cycles_per_tick;
265+
delay_cycles = next_tick_cycles - current_cycles;
266+
}
267+
268+
if (delay_cycles > LPTIMER_MAX_DELAY_TICKS) {
269+
/* Delay exceeds maximum supported by hardware, clamp to maximum */
270+
delay_cycles = LPTIMER_MAX_DELAY_TICKS;
271+
}
272+
273+
/* Set the delay value for the next wakeup interrupt */
274+
lptimer_set_delay(lptimer_dev, delay_cycles);
275+
}
276+
277+
uint32_t sys_clock_elapsed(void)
278+
{
279+
const struct device *lptimer_dev =
280+
DEVICE_DT_GET(DT_COMPAT_GET_ANY_STATUS_OKAY(DT_DRV_COMPAT));
281+
uint32_t current_cycles;
282+
uint32_t cycles_per_tick;
283+
uint32_t delta_cycles;
284+
uint32_t delta_ticks;
285+
286+
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
287+
return 0;
288+
}
289+
290+
cycles_per_tick = clock_frequency / CONFIG_SYS_CLOCK_TICKS_PER_SEC;
291+
292+
k_spinlock_key_t key = k_spin_lock(&lock);
293+
294+
current_cycles = Cy_MCWDT_GetCount(reg_addr, CY_MCWDT_COUNTER2);
295+
296+
/* Calculate elapsed hardware cycles since the last announcement */
297+
delta_cycles = current_cycles - last_lptimer_value;
298+
299+
k_spin_unlock(&lock, key);
300+
301+
/* Convert hardware cycles to kernel ticks */
302+
delta_ticks = delta_cycles / cycles_per_tick;
303+
304+
return delta_ticks;
305+
}
306+
307+
uint32_t sys_clock_cycle_get_32(void)
308+
{
309+
const struct device *lptimer_dev =
310+
DEVICE_DT_GET(DT_COMPAT_GET_ANY_STATUS_OKAY(DT_DRV_COMPAT));
311+
uint32_t cycles;
312+
313+
/* Read the current hardware cycle count from free-running counter */
314+
k_spinlock_key_t key = k_spin_lock(&lock);
315+
316+
cycles = Cy_MCWDT_GetCount(reg_addr, CY_MCWDT_COUNTER2);
317+
318+
k_spin_unlock(&lock, key);
319+
320+
return cycles;
321+
}
322+
323+
static void lptimer_isr(void)
324+
{
325+
Cy_MCWDT_ClearInterrupt(reg_addr, LPTIMER_COUNTERS);
326+
327+
/* Clear interrupt mask if set only from lptimer_set_delay() function */
328+
if (clear_int_mask) {
329+
Cy_MCWDT_SetInterruptMask(reg_addr, 0);
330+
}
331+
332+
if ((isr_instruction & LPTIMER_ISR_CALL_USER_CB_MASK) != 0) {
333+
/* Announce the number of ticks that have elapsed since the last announcement */
334+
uint32_t current_cycles = Cy_MCWDT_GetCount(reg_addr, CY_MCWDT_COUNTER2);
335+
uint32_t cycles_per_tick = clock_frequency / CONFIG_SYS_CLOCK_TICKS_PER_SEC;
336+
uint32_t delta_cycles;
337+
uint32_t delta_ticks;
338+
k_spinlock_key_t key = k_spin_lock(&lock);
339+
340+
delta_cycles = current_cycles - last_lptimer_value;
341+
delta_ticks = delta_cycles / cycles_per_tick;
342+
343+
/* Update last announced position to maintain tick alignment */
344+
last_lptimer_value += delta_ticks * cycles_per_tick;
345+
k_spin_unlock(&lock, key);
346+
347+
sys_clock_announce(IS_ENABLED(CONFIG_TICKLESS_KERNEL) ? delta_ticks
348+
: (delta_ticks > 0));
349+
}
350+
}
351+
352+
static int lptimer_init(void)
353+
{
354+
cy_rslt_t rslt = CY_MCWDT_BAD_PARAM;
355+
cy_stc_mcwdt_config_t cfg = lptimer_default_cfg;
356+
357+
clear_int_mask = false;
358+
isr_instruction = LPTIMER_ISR_CALL_USER_CB_MASK;
359+
360+
rslt = (cy_rslt_t)Cy_MCWDT_Init(reg_addr, &cfg);
361+
if (rslt == CY_RSLT_SUCCESS) {
362+
Cy_MCWDT_Enable(reg_addr, LPTIMER_COUNTERS, LPTIMER_RESET_TIME_US);
363+
} else {
364+
Cy_MCWDT_Disable(reg_addr, LPTIMER_COUNTERS, LPTIMER_RESET_TIME_US);
365+
Cy_MCWDT_DeInit(reg_addr);
366+
}
367+
368+
IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), lptimer_isr, DEVICE_DT_INST_GET(0),
369+
0);
370+
irq_enable(DT_INST_IRQN(0));
371+
372+
return 0;
373+
}
374+
375+
SYS_INIT(lptimer_init, PRE_KERNEL_2, CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);

0 commit comments

Comments
 (0)