1 /*
2  * Copyright (c) 2025 Cypress Semiconductor Corporation (an Infineon company) or
3  * an affiliate of Cypress Semiconductor Corporation
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
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 <cyhal_lptimer.h>
22 
23 #include <zephyr/logging/log.h>
24 LOG_MODULE_REGISTER(ifx_cat1_lp_timer, CONFIG_KERNEL_LOG_LEVEL);
25 
26 /* The application only needs one lptimer. Report an error if more than one is selected. */
27 #if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) > 1
28 #error Only one LPTIMER instance should be enabled
29 #endif
30 
31 #define LPTIMER_INTR_PRIORITY (3u)
32 #define LPTIMER_FREQ          (32768u)
33 
34 /* We need to know the number of MCWDT instances.  Unfortunately, this information is not available
35  * in a header in the HAL code.  This was extracted from the cyhal_lptimer.c file in the HAL
36  */
37 #if (defined(CY_IP_MXS40SRSS) || defined(CY_IP_MXS40SSRSS) || defined(CY_IP_MXS28SRSS) ||          \
38 	defined(CY_IP_MXS22SRSS)) &&                                                               \
39 	!((defined(CY_IP_MXS40SRSS) && (CY_IP_MXS40SRSS_VERSION >= 3)) ||                          \
40 	((SRSS_NUM_MCWDT_B) > 0))
41 #define NUM_LPTIMERS SRSS_NUM_MCWDT
42 #else
43 #error "Selected device doesn't support low power timers at this time."
44 #endif
45 
46 cyhal_lptimer_t lptimer_obj;
47 static uint32_t last_lptimer_value;
48 static struct k_spinlock lock;
49 
lptimer_interrupt_handler(void * handler_arg,cyhal_lptimer_event_t event)50 static void lptimer_interrupt_handler(void *handler_arg, cyhal_lptimer_event_t event)
51 {
52 	CY_UNUSED_PARAMETER(handler_arg);
53 	CY_UNUSED_PARAMETER(event);
54 
55 	k_spinlock_key_t key = k_spin_lock(&lock);
56 
57 	/* announce the elapsed time in ms */
58 	uint32_t lptimer_value = cyhal_lptimer_read(&lptimer_obj);
59 	uint32_t delta_ticks =
60 		((uint64_t)(lptimer_value - last_lptimer_value) * CONFIG_SYS_CLOCK_TICKS_PER_SEC) /
61 		LPTIMER_FREQ;
62 	sys_clock_announce(IS_ENABLED(CONFIG_TICKLESS_KERNEL) ? delta_ticks : (delta_ticks > 0));
63 	last_lptimer_value += (delta_ticks * LPTIMER_FREQ) / CONFIG_SYS_CLOCK_TICKS_PER_SEC;
64 
65 	k_spin_unlock(&lock, key);
66 }
67 
sys_clock_set_timeout(int32_t ticks,bool idle)68 void sys_clock_set_timeout(int32_t ticks, bool idle)
69 {
70 	ARG_UNUSED(idle);
71 
72 	k_spinlock_key_t key = {0};
73 
74 	if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
75 		return;
76 	}
77 
78 	if (ticks == K_TICKS_FOREVER) {
79 		key = k_spin_lock(&lock);
80 		/* Disable the LPTIMER events */
81 		cyhal_lptimer_enable_event(&lptimer_obj, CYHAL_LPTIMER_COMPARE_MATCH,
82 					   LPTIMER_INTR_PRIORITY, false);
83 		k_spin_unlock(&lock, key);
84 		return;
85 	}
86 
87 	/* passing ticks==1 means "announce the next tick", ticks value of zero (or even negative)
88 	 * is legal and treated identically: it simply indicates the kernel would like the next
89 	 * tick announcement as soon as possible.
90 	 */
91 	if (ticks < 1) {
92 		ticks = 1;
93 	}
94 
95 	uint32_t set_ticks = ((uint32_t)(ticks)*LPTIMER_FREQ) / CONFIG_SYS_CLOCK_TICKS_PER_SEC;
96 
97 	key = k_spin_lock(&lock);
98 
99 	/* Configure and Enable the LPTIMER events */
100 	cyhal_lptimer_enable_event(&lptimer_obj, CYHAL_LPTIMER_COMPARE_MATCH, LPTIMER_INTR_PRIORITY,
101 				   true);
102 	/* Set the delay value for the next wakeup interrupt */
103 	cyhal_lptimer_set_delay(&lptimer_obj, set_ticks);
104 
105 	k_spin_unlock(&lock, key);
106 }
107 
sys_clock_elapsed(void)108 uint32_t sys_clock_elapsed(void)
109 {
110 	if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
111 		return 0;
112 	}
113 
114 	k_spinlock_key_t key = k_spin_lock(&lock);
115 
116 	uint32_t lptimer_value = cyhal_lptimer_read(&lptimer_obj);
117 
118 	k_spin_unlock(&lock, key);
119 
120 	/* gives the value of LPTIM counter (ms) since the previous 'announce' */
121 	uint64_t ret = (((uint64_t)(lptimer_value - last_lptimer_value)) *
122 			CONFIG_SYS_CLOCK_TICKS_PER_SEC) /
123 		       LPTIMER_FREQ;
124 
125 	return (uint32_t)ret;
126 }
127 
sys_clock_cycle_get_32(void)128 uint32_t sys_clock_cycle_get_32(void)
129 {
130 	/* just gives the accumulated count in a number of hw cycles */
131 
132 	k_spinlock_key_t key = k_spin_lock(&lock);
133 
134 	uint32_t lp_time = cyhal_lptimer_read(&lptimer_obj);
135 
136 	k_spin_unlock(&lock, key);
137 
138 	/* convert lptim count in a nb of hw cycles with precision */
139 	uint64_t ret = ((uint64_t)lp_time * sys_clock_hw_cycles_per_sec()) / LPTIMER_FREQ;
140 
141 	/* convert in hw cycles (keeping 32bit value) */
142 	return (uint32_t)ret;
143 }
144 
sys_clock_driver_init(void)145 static int sys_clock_driver_init(void)
146 {
147 	cy_rslt_t result;
148 	cyhal_lptimer_t lptimer_objs[NUM_LPTIMERS];
149 
150 	/* Currently with the HAL, there is no way to directly/explicitly select the MCWDT
151 	 * enabled in the <board>.dts file.  So, instead, initialize LPTIMERs until we find
152 	 * the one from the <board>.dts file.  Free the others when done.
153 	 */
154 	for (int32_t lptimer_index = 0; lptimer_index < NUM_LPTIMERS; lptimer_index++) {
155 		/* Initialize the LPTIMER with default configuration */
156 		result = cyhal_lptimer_init(&lptimer_obj);
157 
158 		if (result != CY_RSLT_SUCCESS) {
159 			LOG_ERR("LPTimer instance not found. Error: 0x%08X\n",
160 				(unsigned int)result);
161 			return -EIO;
162 		}
163 
164 		if ((uint32_t)lptimer_obj.base == DT_INST_REG_ADDR(0)) {
165 			for (lptimer_index--; lptimer_index >= 0; lptimer_index--) {
166 				cyhal_lptimer_free(&lptimer_objs[lptimer_index]);
167 			}
168 			break;
169 		}
170 
171 		cyhal_lptimer_free(&lptimer_obj);
172 		cyhal_lptimer_init(&lptimer_objs[lptimer_index]);
173 	}
174 
175 	/* Register the callback handler which will be invoked when the interrupt triggers */
176 	cyhal_lptimer_register_callback(&lptimer_obj, lptimer_interrupt_handler, NULL);
177 
178 	if (result != CY_RSLT_SUCCESS) {
179 		LOG_ERR("Sys Clock initialization failed. Error: 0x%08X\n", (unsigned int)result);
180 		return -EIO;
181 	}
182 
183 	return 0;
184 }
185 
186 SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2, CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);
187