1 /***************************************************************************//**
2 * \file cyhal_lptimer.c
3 *
4 * \brief
5 * Provides a high level interface for interacting with the Infineon Low-Power Timer.
6 * This interface abstracts out the chip specific details. If any chip specific
7 * functionality is necessary, or performance is critical the low level functions
8 * can be used directly.
9 *
10 ********************************************************************************
11 * \copyright
12 * Copyright 2018-2022 Cypress Semiconductor Corporation (an Infineon company) or
13 * an affiliate of Cypress Semiconductor Corporation
14 *
15 * SPDX-License-Identifier: Apache-2.0
16 *
17 * Licensed under the Apache License, Version 2.0 (the "License");
18 * you may not use this file except in compliance with the License.
19 * You may obtain a copy of the License at
20 *
21 * http://www.apache.org/licenses/LICENSE-2.0
22 *
23 * Unless required by applicable law or agreed to in writing, software
24 * distributed under the License is distributed on an "AS IS" BASIS,
25 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26 * See the License for the specific language governing permissions and
27 * limitations under the License.
28 *******************************************************************************/
29
30 /**
31 * \addtogroup group_hal_impl_lptimer LPTimer (Low-Power Timer)
32 * \ingroup group_hal_impl
33 * \{
34 * The maximum number of ticks that can set to an LPTimer is 0xFFF0FFFF for non MCWDT-B devices.
35 * It is not recommended to use 0xFFFFFFFF. This is to avoid overflowing both C0 and C1.
36 * For MCWDT-B devices, the maximum number of ticks that can be set to an LPTimer is 0xFFFFFFFF
37 * since C0 and C1 do not cascade.
38 *
39 * \section section_cat1c_lptimer_set_match LPTimer Set Match
40 * For CAT1C devices (XMC), \ref cyhal_lptimer_set_match will only trigger a single interrupt.
41 *
42 * \} group_hal_impl_lptimer
43 */
44
45 #include "cmsis_compiler.h"
46 #include "cy_syslib.h"
47 #include "cy_sysint.h"
48 #include "cyhal_lptimer.h"
49 #include "cyhal_hwmgr.h"
50 #include "cyhal_system_impl.h"
51 #include "cyhal_utils.h"
52 #include "cyhal_irq_impl.h"
53 #include "cyhal_clock.h"
54
55 #if (CYHAL_DRIVER_AVAILABLE_LPTIMER)
56
57 #if defined(CY_IP_MXS40SRSS) || defined(CY_IP_MXS40SSRSS) || defined(CY_IP_MXS28SRSS) || defined(CY_IP_MXS22SRSS)
58 #include "cy_mcwdt.h"
59 #define _CYHAL_LPTIMER_MCWDT
60 #if (defined (CY_IP_MXS40SRSS) && (CY_IP_MXS40SRSS_VERSION >= 2)) || ((SRSS_NUM_MCWDT_B) > 0)
61 #define _CYHAL_LPTIMER_MCWDT_B
62 #if !defined(SRSS_NUM_MCWDT_B)
63 #define SRSS_NUM_MCWDT_B (SRSS_NUM_MCWDT)
64 #endif
65 #endif
66 #elif defined (CY_IP_M0S8WCO)
67 #include "cy_wdc.h"
68 #define Cy_MCWDT_Init Cy_WDC_Init
69 #define Cy_MCWDT_DeInit Cy_WDC_DeInit
70 #define Cy_MCWDT_Enable Cy_WDC_Enable
71 #define Cy_MCWDT_Disable Cy_WDC_Disable
72 #define Cy_MCWDT_GetInterruptStatus Cy_WDC_GetInterruptStatus
73 #define Cy_MCWDT_ClearInterrupt Cy_WDC_ClearInterrupt
74 #define Cy_MCWDT_GetCount Cy_WDC_GetCount
75 #define Cy_MCWDT_SetMatch Cy_WDC_SetMatch
76 #define Cy_MCWDT_GetMatch Cy_WDC_GetMatch
77 #define Cy_MCWDT_ResetCounters Cy_WDC_ResetCounters
78 #define Cy_MCWDT_GetEnabledStatus Cy_WDC_GetEnabledStatus
79 #define CY_MCWDT_COUNTER0 CY_WDC_COUNTER0
80 #define CY_MCWDT_COUNTER1 CY_WDC_COUNTER1
81 #define CY_MCWDT_COUNTER2 CY_WDC_COUNTER2
82 #define CY_MCWDT_CTR0 CY_WDC_COUNTER0_Msk
83 #define CY_MCWDT_CTR1 CY_WDC_COUNTER1_Msk
84 #define CY_MCWDT_CTR2 CY_WDC_COUNTER2_Msk
85 #define srss_interrupt_mcwdt_0_IRQn wco_interrupt_IRQn
86 #else
87 #error "Current HW block is not supported"
88 #endif
89
90 #if defined(__cplusplus)
91 extern "C" {
92 #endif
93
94 #if defined(_CYHAL_LPTIMER_MCWDT)
95 #if defined (_CYHAL_LPTIMER_MCWDT_B)
96 #define _CYHAL_LPTIMER_INSTANCES (SRSS_NUM_MCWDT_B)
97 static MCWDT_Type * const _CYHAL_LPTIMER_BASE_ADDRESSES[] = {
98 #if SRSS_NUM_MCWDT >= 1
99 MCWDT0,
100 #endif
101 #if SRSS_NUM_MCWDT >= 2
102 MCWDT1,
103 #endif
104 #if SRSS_NUM_MCWDT >= 3
105 MCWDT2,
106 #endif
107 #else
108 #define _CYHAL_LPTIMER_INSTANCES SRSS_NUM_MCWDT
109 static MCWDT_STRUCT_Type * const _CYHAL_LPTIMER_BASE_ADDRESSES[] = {
110 #if SRSS_NUM_MCWDT >= 1
111 MCWDT_STRUCT0,
112 #endif
113 #if SRSS_NUM_MCWDT >= 2
114 MCWDT_STRUCT1,
115 #endif
116 #if SRSS_NUM_MCWDT >= 3
117 #error "SRSS_NUM_MCWDT > 2 not supported"
118 #endif
119 #endif
120 };
121 #elif defined (CY_IP_M0S8WCO_INSTANCES)
122 #define _CYHAL_LPTIMER_INSTANCES CY_IP_M0S8WCO_INSTANCES
123 static WCO_Type * const _CYHAL_LPTIMER_BASE_ADDRESSES[] = {
124 #if CY_IP_M0S8WCO_INSTANCES >= 1
125 WCO,
126 #endif
127 #if CY_IP_M0S8WCO_INSTANCES >= 2
128 #error "CY_IP_M0S8WCO_INSTANCES > 1 not supported"
129 #endif
130 };
131 #else
132 #error "Current HW block is not supported"
133 #endif
134
135 #define _CYHAL_LPTIMER_CTRL (CY_MCWDT_CTR0 | CY_MCWDT_CTR1 | CY_MCWDT_CTR2)
136 #define _CYHAL_LPTIMER_MIN_DELAY (3U) /* minimum amount of lfclk cycles of that LPTIMER can delay for. */
137 #if defined (_CYHAL_LPTIMER_MCWDT_B)
138 // The max sleep time for CAT1C devices is ~17.5 hours. This is because the toggle bit that is set for Counter2
139 // match value toggles every time the bit changes from 0 to 1 or from 1 to 0.
140 #define _CYHAL_LPTIMER_MAX_DELAY_TICKS (0xffffffffUL) /* ~36hours, set to 0xffffffff since C0 and C1 do not cascade */
141 #else
142 #define _CYHAL_LPTIMER_MAX_DELAY_TICKS (0xfff0ffffUL) /* ~36hours, Not set to 0xffffffff to avoid C0 and C1 both overflowing */
143 #endif
144 #define _CYHAL_LPTIMER_MAX_COUNTER_VAL (0xffffffffUL) /* Maximum value of the counter before it rolls over */
145
146 #define _CYHAL_LPTIMER_DEFAULT_PRIORITY (3U)
147
148 #define _CYHAL_LPTIMER_ISR_CALL_USER_CB_MASK (0x01)
149 #define _CYHAL_LPTIMER_ISR_CRITICAL_SECTION_MASK (0x02)
150
151 #if defined(_CYHAL_LPTIMER_MCWDT)
152 static cyhal_lptimer_t *_cyhal_lptimer_config_structs[_CYHAL_LPTIMER_INSTANCES];
153 #if defined (_CYHAL_LPTIMER_MCWDT_B)
154 static const uint16_t _CYHAL_LPTIMER_RESET_TIME_US = 93;
155 static const uint16_t _CYHAL_LPTIMER_SETMATCH_TIME_US = 93;
156 static const cy_stc_mcwdt_config_t default_cfg = {
157 .c0UpperLimit = 0xFFFF,
158 .c0WarnLimit = 0xFFFF,
159 .c0WarnAction = CY_MCWDT_WARN_ACTION_NONE,
160 .c0UpperAction = CY_MCWDT_ACTION_NONE,
161 .c0DebugRun = CY_MCWDT_ENABLE,
162 .c0SleepDeepPause = CY_MCWDT_DISABLE,
163 .c1UpperLimit = 0xFFFF,
164 .c1UpperAction = CY_MCWDT_ACTION_NONE,
165 .c1DebugRun = CY_MCWDT_ENABLE,
166 .c1SleepDeepPause = CY_MCWDT_DISABLE,
167 .c1WarnLimit = 0xFFFF,
168 .c1WarnAction = CY_MCWDT_WARN_ACTION_INT,
169 .c1AutoService = CY_MCWDT_ENABLE,
170 .c2ToggleBit = 0,
171 .c2Action = CY_MCWDT_CNT2_ACTION_INT,
172 .c2DebugRun = CY_MCWDT_ENABLE,
173 .c2SleepDeepPause = CY_MCWDT_DISABLE,
174 .coreSelect = CY_MCWDT_PAUSED_BY_NO_CORE,
175 };
176 #else
177 #if defined(CY_IP_MXS40SSRSS)
178 static const uint16_t _CYHAL_LPTIMER_RESET_TIME_US = 93; // Recommended value per PDL
179 #else
180 static const uint16_t _CYHAL_LPTIMER_RESET_TIME_US = 62;
181 #endif
182 static const uint16_t _CYHAL_LPTIMER_SETMATCH_TIME_US = 0;
183 static const cy_stc_mcwdt_config_t default_cfg = {
184 .c0Match = 0xFFFF,
185 .c1Match = 0xFFFF,
186 .c0Mode = CY_MCWDT_MODE_INT,
187 .c1Mode = CY_MCWDT_MODE_INT,
188 .c2Mode = CY_MCWDT_MODE_NONE,
189 .c2ToggleBit = 0,
190 .c0ClearOnMatch = false,
191 .c1ClearOnMatch = false,
192 .c0c1Cascade = true,
193 .c1c2Cascade = false
194 };
195 #endif
196 #elif defined (CY_IP_M0S8WCO)
197 static cyhal_lptimer_t *_cyhal_lptimer_config_structs[CY_IP_M0S8WCO_INSTANCES];
198 static const uint16_t _CYHAL_LPTIMER_RESET_TIME_US = 180;
199 static const uint16_t _CYHAL_LPTIMER_SETMATCH_TIME_US = 180;
200 static const cy_stc_wdc_config_t default_cfg = {
201 .counter0Match = 0xFFFF,
202 .counter1Match = 0xFFFF,
203 .counter2ToggleBit = 0,
204 .counter0Interrupt = false,
205 .counter1Interrupt = true,
206 .counter2Interrupt = false,
207 .counter0ClearOnMatch = false,
208 .counter1ClearOnMatch = true,
209 .countersCascade = CY_WDC_CASCADE_COUNTERS01,
210 .clockSource = CY_WDC_CLOCK_ILO
211 };
212 #else
213 #error "Current HW block is not supported"
214 #endif
215
216 #if (_CYHAL_IRQ_MUXING)
217 static const _cyhal_system_irq_t _CYHAL_MCWDT_DISCONNECTED_IRQ = disconnected_IRQn;
218 #else
219 static const _cyhal_system_irq_t _CYHAL_MCWDT_DISCONNECTED_IRQ = unconnected_IRQn;
220 #endif
221
222 #if defined(_CYHAL_LPTIMER_INSTANCES)
223 static const _cyhal_system_irq_t _CYHAL_MCWDT_IRQS[_CYHAL_LPTIMER_INSTANCES] =
224 {
225 #if (defined(COMPONENT_CM55) && defined(CY_IP_MXS22SRSS))
226 //For CM0P/CM55 cores for cat1d, there is no 0 irqn.
227 unconnected_IRQn,
228 #else
229 srss_interrupt_mcwdt_0_IRQn,
230 #endif
231 #if (_CYHAL_LPTIMER_INSTANCES >=2)
232 #if (defined(COMPONENT_CM33) && defined(CY_IP_MXS22SRSS))
233 //For CM0P/CM33 cores for cat1d, there is no 1 irqn
234 unconnected_IRQn,
235 #else
236 srss_interrupt_mcwdt_1_IRQn,
237 #endif
238 #endif
239 #if (_CYHAL_LPTIMER_INSTANCES ==3)
240 srss_interrupt_mcwdt_2_IRQn,
241 #endif
242 #if (_CYHAL_LPTIMER_INSTANCES > 3)
243 #error "_CYHAL_LPTIMER_INSTANCES > 3 not supported"
244 #endif
245 };
246 #elif defined (CY_IP_M0S8WCO_INSTANCES)//MOS8WCO only has one instance
247 static const _cyhal_system_irq_t _CYHAL_MCWDT_IRQS[1] =
248 {
249 wco_interrupt_IRQn,
250 };
251 #else
252 #error "HW Block not supported"
253 #endif
254
_cyhal_lptimer_get_instance_from_irq(void)255 static uint32_t _cyhal_lptimer_get_instance_from_irq(void)
256 /* Gets instance from the IRQ array*/
257 {
258 _cyhal_system_irq_t irqn = _cyhal_irq_get_active();
259 for(uint8_t i = 0; i < sizeof(_CYHAL_MCWDT_IRQS)/sizeof(_CYHAL_MCWDT_IRQS[0]); ++i)
260 {
261 if (_CYHAL_MCWDT_IRQS[i] == irqn)
262 {
263 return i;
264 }
265 }
266
267 /* The IRQ may be muxed and may be triggered by other sources */
268 return _CYHAL_MCWDT_DISCONNECTED_IRQ;
269 }
270
271 #if defined (_CYHAL_LPTIMER_MCWDT_B)
272 /* Get the mask (for e.g. ClearInterrupt) associated with a particular counter */
_cyhal_lptimer_counter_to_mask(cy_en_mcwdtctr_t counter)273 uint32_t _cyhal_lptimer_counter_to_mask(cy_en_mcwdtctr_t counter)
274 {
275 return 1u << ((uint8_t)counter);
276 }
277 #endif
278
_cyhal_lptimer_irq_handler(void)279 static void _cyhal_lptimer_irq_handler(void)
280 {
281 uint32_t instance = (uint32_t)_cyhal_lptimer_get_instance_from_irq();
282 if (instance != _CYHAL_MCWDT_DISCONNECTED_IRQ)
283 {
284 cyhal_lptimer_t *obj = _cyhal_lptimer_config_structs[instance];
285 #if defined (_CYHAL_LPTIMER_MCWDT_B)
286 uint32_t c2_count = Cy_MCWDT_GetCount(obj->base, CY_MCWDT_COUNTER2);
287 //Since CAT1C does not use Counter0, there is no need to clear the interrupt
288 Cy_MCWDT_ClearInterrupt(obj->base, (CY_MCWDT_CTR1 | CY_MCWDT_CTR2));
289 #else
290 Cy_MCWDT_ClearInterrupt(obj->base, (CY_MCWDT_CTR0 | CY_MCWDT_CTR1 | CY_MCWDT_CTR2));
291 #endif
292 #if defined (_CYHAL_LPTIMER_MCWDT_B)
293 // We want to clear the interrupt mask everytime we enter the IRQ handler
294 // for CAT1C devices regardless if set_delay or set_match was called.
295 // This means if cyhal_lptimer_set_match was called, a single interrupt will be triggered.
296 Cy_MCWDT_SetInterruptMask(obj->base, Cy_MCWDT_GetInterruptMask(obj->base) & ~_cyhal_lptimer_counter_to_mask(obj->counter));
297 #endif
298 /* Clear interrupt mask if set only from cyhal_lptimer_set_delay() function */
299 if (obj->clear_int_mask)
300 {
301 #if defined (_CYHAL_LPTIMER_MCWDT)
302 Cy_MCWDT_SetInterruptMask(obj->base, 0);
303 #else
304 Cy_WDC_InterruptDisable(obj->base, CY_WDC_COUNTER1);
305 #endif
306 }
307 #if defined (_CYHAL_LPTIMER_MCWDT_B)
308 if(obj->final_time - c2_count > 0 && obj->final_time > c2_count)
309 {
310 cyhal_lptimer_set_delay(obj, obj->final_time - c2_count);
311 }
312 else
313 #endif
314 {
315 if (NULL != obj->callback_data.callback && ((obj->isr_instruction & _CYHAL_LPTIMER_ISR_CALL_USER_CB_MASK) != 0))
316 {
317 cyhal_lptimer_event_callback_t callback = (cyhal_lptimer_event_callback_t) obj->callback_data.callback;
318 (callback)(obj->callback_data.callback_arg, CYHAL_LPTIMER_COMPARE_MATCH);
319 }
320 }
321 #if (_CYHAL_IRQ_MUXING)
322 /* Check if the event flag should have been cleared when inside a critical situation */
323 if ((obj->isr_instruction & _CYHAL_LPTIMER_ISR_CRITICAL_SECTION_MASK) != 0)
324 {
325 #if defined (_CYHAL_LPTIMER_MCWDT_B)
326 Cy_MCWDT_ClearInterrupt(obj->base, _cyhal_lptimer_counter_to_mask(obj->counter));
327 Cy_MCWDT_SetInterruptMask(obj->base, Cy_MCWDT_GetInterruptMask(obj->base) & ~_cyhal_lptimer_counter_to_mask(obj->counter));
328 #elif defined(_CYHAL_LPTIMER_MCWDT)
329 Cy_MCWDT_ClearInterrupt(obj->base, CY_MCWDT_CTR1);
330 Cy_MCWDT_SetInterruptMask(obj->base, 0);
331 #elif defined (CY_IP_M0S8WCO)
332 _cyhal_irq_disable(irqn);
333 #endif
334 obj->isr_instruction &= ~_CYHAL_LPTIMER_ISR_CRITICAL_SECTION_MASK;
335 }
336 #endif
337 }
338 }
339 #if defined (_CYHAL_LPTIMER_MCWDT_B)
_cyhal_lptimer_get_toggle_bit(uint32_t c2_current,uint32_t delay)340 uint32_t _cyhal_lptimer_get_toggle_bit(uint32_t c2_current, uint32_t delay)
341 {
342 uint32_t val = c2_current ^ (c2_current + delay);
343 int bit = 0;
344 while (val > 0)
345 {
346 bit++;
347 val >>= 1;
348 }
349 return bit - 1;
350 }
_cyhal_lptimer_set_delay_common(cyhal_lptimer_t * obj,uint32_t delay)351 static uint32_t _cyhal_lptimer_set_delay_common(cyhal_lptimer_t *obj, uint32_t delay)
352 {
353 // Check to see if Counter1 or Counter2 is enabled.
354 // If neither is enabled, return Error Disabled.
355 // We do not check to see if Counter0 is enabled as it is not used
356 // for this IP implementation.
357 if ((Cy_MCWDT_GetEnabledStatus(obj->base, CY_MCWDT_COUNTER1) == 0UL)
358 || (Cy_MCWDT_GetEnabledStatus(obj->base, CY_MCWDT_COUNTER2) == 0UL))
359 {
360 return CYHAL_LPTIMER_RSLT_ERR_DISABLED;
361 }
362 if (delay <= _CYHAL_LPTIMER_MIN_DELAY)
363 {
364 delay = _CYHAL_LPTIMER_MIN_DELAY;
365 }
366 #if (_CYHAL_LPTIMER_MAX_DELAY_TICKS != 0xffffffffUL)
367 /* No point in this check if uint32_t can't exceed the max */
368 if (delay > _CYHAL_LPTIMER_MAX_DELAY_TICKS)
369 {
370 delay = _CYHAL_LPTIMER_MAX_DELAY_TICKS;
371 }
372 #endif
373 // If the delay is greater than 2^16 we will use Counter2 for interrupts
374 // we must then clear the Counter2 interrupt before setting the new match.
375 obj->counter = (delay > ((1 << 16)-1)) ? CY_MCWDT_COUNTER2 : CY_MCWDT_COUNTER1;
376 uint32_t critical_section = cyhal_system_critical_section_enter();
377 uint32_t counter_value = Cy_MCWDT_GetCount(obj->base, obj->counter);
378 // if the counter value + the delay exceeds 32 bits, it is expected
379 // that the wrap around will be handled by the uin32_t variable
380 uint32_t match_value = counter_value + delay;
381 Cy_MCWDT_Unlock(obj->base);
382 if(obj->counter == CY_MCWDT_COUNTER1)
383 {
384 if(match_value > ((1 << 16)-1))
385 {
386 // Wait 3 LFClk cycles for reset
387 Cy_MCWDT_ResetCounters(obj->base, _cyhal_lptimer_counter_to_mask(obj->counter), _CYHAL_LPTIMER_RESET_TIME_US);
388 // Since we waited 3 LFClk cycles to reset the counter, we want to deduct that from our delay
389 match_value = delay - _CYHAL_LPTIMER_MIN_DELAY;
390 if(match_value < _CYHAL_LPTIMER_MIN_DELAY)
391 {
392 // After resetting the counter and deducting the number of clock cycles it took
393 // The delay is below our minumum delay.
394 Cy_MCWDT_Lock(obj->base);
395 cyhal_system_critical_section_exit(critical_section);
396 return CYHAL_LPTIMER_RSLT_ERR_BAD_ARGUMENT;
397 }
398 }
399 Cy_MCWDT_SetWarnLimit(obj->base, obj->counter, (uint16_t)match_value, _CYHAL_LPTIMER_SETMATCH_TIME_US);
400 }
401 else
402 {
403 // We want to get the monitored bit to set for the toggle point
404 uint32_t toggle_bit = _cyhal_lptimer_get_toggle_bit(counter_value, delay);
405 // We want to set an interrupt for Counter2 to the 2^n value closest to the match value without going over
406 // If there is a delta between our power of value of 2^n and the match value
407 // We set the remaining delay time on the object which will trigger a follow up interrupt
408 // In the IRQ handler
409 Cy_MCWDT_SetToggleBit(obj->base, toggle_bit);
410 // Wait 3 LFClk cycles to make sure the toggle bit was updated
411 cyhal_system_delay_us(_CYHAL_LPTIMER_SETMATCH_TIME_US);
412 }
413 Cy_MCWDT_Lock(obj->base);
414 cyhal_system_critical_section_exit(critical_section);
415 Cy_MCWDT_ClearInterrupt(obj->base, _cyhal_lptimer_counter_to_mask(obj->counter));
416 Cy_MCWDT_SetInterruptMask(obj->base, _cyhal_lptimer_counter_to_mask(obj->counter));
417 return CY_RSLT_SUCCESS;
418 }
419 #else
_cyhal_lptimer_set_delay_common(cyhal_lptimer_t * obj,uint32_t delay)420 static uint32_t _cyhal_lptimer_set_delay_common(cyhal_lptimer_t *obj, uint32_t delay)
421 {
422 if ((Cy_MCWDT_GetEnabledStatus(obj->base, CY_MCWDT_COUNTER0) == 0UL)
423 || (Cy_MCWDT_GetEnabledStatus(obj->base, CY_MCWDT_COUNTER1) == 0UL)
424 || (Cy_MCWDT_GetEnabledStatus(obj->base, CY_MCWDT_COUNTER2) == 0UL))
425 {
426 return CYHAL_LPTIMER_RSLT_ERR_DISABLED;
427 }
428
429 /**
430 * - 16 bit Counter0 (C0) & Counter1 (C1) are cascaded to generated a 32 bit counter.
431 * - Counter2 (C2) is a free running counter.
432 * - C0 continues counting after reaching its match value. On PSoC™ 4 Counter1 is reset on
433 * match. On PSoC™ 6 it continues counting.
434 * - An interrupt is generated when C1 reaches the match value. On PSoC™ 4 this happens when
435 * the counter increments to the same value as match. On PSoC™ 6 this happens when it increments
436 * past the match value.
437 *
438 * EXAMPLE:
439 * Supposed T=C0=C1=0, and we need to trigger an interrupt at T=0x18000.
440 * We set C0_match to 0x8000 and C1 match to 2 on PSoC™ 4 and 1 on PSoC™ 6.
441 * At T = 0x8000, C0_value matches C0_match so C1 get incremented. C1/C0=0x18000.
442 * At T = 0x18000, C0_value matches C0_match again so C1 get incremented from 1 to 2.
443 * When C1 get incremented from 1 to 2 the interrupt is generated.
444 * At T = 0x18000, C1/C0 = 0x28000.
445 */
446
447 if (delay <= _CYHAL_LPTIMER_MIN_DELAY)
448 {
449 delay = _CYHAL_LPTIMER_MIN_DELAY;
450 }
451 if (delay > _CYHAL_LPTIMER_MAX_DELAY_TICKS)
452 {
453 delay = _CYHAL_LPTIMER_MAX_DELAY_TICKS;
454 }
455
456 Cy_MCWDT_ClearInterrupt(obj->base, CY_MCWDT_CTR1);
457 uint16_t c0_old_match = (uint16_t)Cy_MCWDT_GetMatch(obj->base, CY_MCWDT_COUNTER0);
458
459 uint32_t critical_section = cyhal_system_critical_section_enter();
460
461 /* Cascading from C0 match into C1 is queued and can take 1 full LF clk cycle.
462 * There are 3 cases:
463 * Case 1: if c0 = match0 then the cascade into C1 will happen 1 cycle from now. The value c1_current_ticks is 1 lower than expected.
464 * Case 2: if c0 = match0 -1 then cascade may or not happen before new match value would occur. Match occurs on rising clock edge.
465 * Synchronizing match value occurs on falling edge. Wait until c0 = match0 to ensure cascade occurs.
466 * Case 3: everything works as expected.
467 *
468 * Note: timeout is needed here just in case the LFCLK source gives out. This avoids device lockup.
469 *
470 * ((2 * Cycles_LFClk) / Cycles_cpu_iteration) * (HFCLk_max / LFClk_min) = Iterations_required
471 * Typical case: (2 / 100) * ((150x10^6)/33576) = 89 iterations
472 * Worst case: (2 / 100) * ((150x10^6)/1) = 3x10^6 iterations
473 * Compromise: (2 / 100) * ((150x10^6)/0xFFFF iterations) = 45 Hz = LFClk_min
474 */
475 const uint32_t DEFAULT_TIMEOUT = 0xFFFFUL;
476 uint32_t timeout = DEFAULT_TIMEOUT;
477 uint16_t c0_current_ticks = (uint16_t)Cy_MCWDT_GetCount(obj->base, CY_MCWDT_COUNTER0);
478 // Wait until the cascade has definitively happened. It takes a clock cycle for the cascade to happen, and potentially another a full
479 // LFCLK clock cycle for the cascade to propagate up to the HFCLK-domain registers that the CPU reads.
480 while (((((uint16_t)(c0_old_match - 1)) == c0_current_ticks) ||
481 (c0_old_match == c0_current_ticks) ||
482 (((uint16_t)(c0_old_match + 1)) == c0_current_ticks)) && (timeout != 0UL))
483 {
484 c0_current_ticks = (uint16_t)Cy_MCWDT_GetCount(obj->base, CY_MCWDT_COUNTER0);
485 timeout--;
486 }
487 if (timeout == 0UL)
488 {
489 // Timeout has occurred. There could have been a clock failure while waiting for the count value to update.
490 cyhal_system_critical_section_exit(critical_section);
491 return CYHAL_LPTIMER_RSLT_ERR_DISABLED;
492 }
493
494 uint16_t c0_match = (uint16_t)(c0_current_ticks + delay);
495 // Changes can take up to 2 clk_lf cycles to propagate. If we set the match within this window of the current value,
496 // then it is nondeterministic whether the first cascade will trigger immediately or after 2^16 cycles. Wait until
497 // c0 is in a more predictable state.
498 timeout = DEFAULT_TIMEOUT;
499 uint32_t c0_new_ticks = c0_current_ticks;
500 while(((c0_new_ticks == c0_match) ||
501 (c0_new_ticks == ((uint16_t)(c0_match + 1))) ||
502 (c0_new_ticks == ((uint16_t)(c0_match + 2))))
503 && (timeout != 0UL))
504 {
505 c0_new_ticks = (uint16_t)Cy_MCWDT_GetCount(obj->base, CY_MCWDT_COUNTER0);
506 timeout--;
507 }
508
509 delay -= (c0_new_ticks >= c0_current_ticks)
510 ? (c0_new_ticks - c0_current_ticks)
511 : ((0xFFFFU - c0_current_ticks) + c0_new_ticks);
512
513 c0_match = (uint16_t)(c0_current_ticks + delay);
514 uint16_t c1_current_ticks = (uint16_t)Cy_MCWDT_GetCount(obj->base, CY_MCWDT_COUNTER1);
515 uint16_t c1_match = (uint16_t)(c1_current_ticks + (delay >> 16));
516
517 #if defined(CY_IP_M0S8WCO)
518 c1_match += 1;
519 // Increase away from rollover glitch; can't go backward or c1 interrupt will not trigger
520 if (c0_match == 0)
521 c0_match = 1;
522 #endif
523 Cy_MCWDT_SetMatch(obj->base, CY_MCWDT_COUNTER0, c0_match, _CYHAL_LPTIMER_SETMATCH_TIME_US);
524 Cy_MCWDT_SetMatch(obj->base, CY_MCWDT_COUNTER1, c1_match, _CYHAL_LPTIMER_SETMATCH_TIME_US);
525 cyhal_system_critical_section_exit(critical_section);
526 #ifdef _CYHAL_LPTIMER_MCWDT
527 Cy_MCWDT_SetInterruptMask(obj->base, CY_MCWDT_CTR1);
528 #else
529 Cy_WDC_InterruptEnable(obj->base, CY_MCWDT_COUNTER1);
530 #endif
531 return CY_RSLT_SUCCESS;
532 }
533 #endif
534
cyhal_lptimer_init(cyhal_lptimer_t * obj)535 cy_rslt_t cyhal_lptimer_init(cyhal_lptimer_t *obj)
536 {
537 CY_ASSERT(NULL != obj);
538
539 obj->resource.type = CYHAL_RSC_INVALID;
540 obj->clear_int_mask = false;
541 obj->isr_instruction = 0;
542 cy_rslt_t rslt = CYHAL_LPTIMER_RSLT_ERR_NOT_SUPPORTED;
543 for(uint8_t inst=0; inst < _CYHAL_LPTIMER_INSTANCES; ++inst)
544 {
545 if(_CYHAL_MCWDT_IRQS[inst] != _CYHAL_MCWDT_DISCONNECTED_IRQ)
546 {
547 // Make a temp LPTIMER cyhal_resource_inst_t with block_num = inst
548 cyhal_resource_inst_t temp = {
549 .type = CYHAL_RSC_LPTIMER,
550 .block_num = inst,
551 .channel_num = 0,
552 };
553
554 rslt = cyhal_hwmgr_reserve(&temp);
555 if(rslt == CY_RSLT_SUCCESS)
556 {
557 obj->resource = temp;
558 break;
559 }
560 }
561 }
562 if (CY_RSLT_SUCCESS == rslt)
563 {
564 obj->base = _CYHAL_LPTIMER_BASE_ADDRESSES[obj->resource.block_num];
565 #if defined (_CYHAL_LPTIMER_MCWDT_B)
566 obj->offset = 0;
567 obj->final_time = 0;
568 obj->counter = CY_MCWDT_COUNTER1;
569 cy_stc_mcwdt_config_t cfg = default_cfg;
570 #elif defined (_CYHAL_LPTIMER_MCWDT)
571 cy_stc_mcwdt_config_t cfg = default_cfg;
572 #elif defined (CY_IP_M0S8WCO)
573 cy_stc_wdc_config_t cfg = default_cfg;
574 // The WDC_SEL clock source is populated into the config
575 // register by the clock driver. Extract it so that the
576 // PDL init doesn't revert it to default
577 cfg.clockSource = Cy_WDC_GetClockSource(WCO);
578 #endif
579 rslt = (cy_rslt_t) Cy_MCWDT_Init(obj->base, &cfg);
580 }
581
582 if (CY_RSLT_SUCCESS == rslt)
583 {
584 obj->callback_data.callback = NULL;
585 obj->callback_data.callback_arg = NULL;
586 _cyhal_lptimer_config_structs[obj->resource.block_num] = obj;
587 }
588
589 if (CY_RSLT_SUCCESS == rslt)
590 {
591 _cyhal_system_irq_t irqn = _CYHAL_MCWDT_IRQS[obj->resource.block_num];
592 _cyhal_irq_register(irqn, _CYHAL_LPTIMER_DEFAULT_PRIORITY, _cyhal_lptimer_irq_handler);
593
594 if (CY_RSLT_SUCCESS == rslt)
595 {
596 _cyhal_irq_enable(irqn);
597 #if defined (_CYHAL_LPTIMER_MCWDT_B)
598 Cy_MCWDT_Unlock(obj->base);
599 Cy_MCWDT_Enable(obj->base, (CY_MCWDT_CTR1 | CY_MCWDT_CTR2), _CYHAL_LPTIMER_RESET_TIME_US);
600 Cy_MCWDT_Lock(obj->base);
601 #else
602 Cy_MCWDT_Enable(obj->base, _CYHAL_LPTIMER_CTRL, _CYHAL_LPTIMER_RESET_TIME_US);
603 #endif
604 }
605 }
606
607 if (CY_RSLT_SUCCESS != rslt)
608 {
609 cyhal_lptimer_free(obj);
610 }
611
612 return rslt;
613 }
614
cyhal_lptimer_free(cyhal_lptimer_t * obj)615 void cyhal_lptimer_free(cyhal_lptimer_t *obj)
616 {
617 if (CYHAL_RSC_INVALID != obj->resource.type)
618 {
619 _cyhal_system_irq_t irqn = _CYHAL_MCWDT_IRQS[obj->resource.block_num];
620 _cyhal_irq_free(irqn);
621
622 cyhal_hwmgr_free(&(obj->resource));
623 obj->resource.type = CYHAL_RSC_INVALID;
624 }
625 if (NULL != obj->base)
626 {
627 #if defined (_CYHAL_LPTIMER_MCWDT_B)
628 Cy_MCWDT_Unlock(obj->base);
629 // only need to disable counter1 and counter2 since those are the only counters
630 // used for the IP.
631 Cy_MCWDT_Disable(obj->base, (CY_MCWDT_CTR1 | CY_MCWDT_CTR2), _CYHAL_LPTIMER_RESET_TIME_US);
632 Cy_MCWDT_Lock(obj->base);
633 #else
634 Cy_MCWDT_Disable(obj->base, _CYHAL_LPTIMER_CTRL, _CYHAL_LPTIMER_RESET_TIME_US);
635 #endif
636 // When we're using the WDC, the clock source selection is stored in the WDC_CONFIG
637 // register and we want that to persist across free/init calls. So don't call deinit
638 // because that will clear it out.
639 #if !defined(CY_IP_M0S8WCO)
640 Cy_MCWDT_DeInit(obj->base);
641 #endif
642 obj->base = NULL;
643 }
644 }
645
cyhal_lptimer_reload(cyhal_lptimer_t * obj)646 cy_rslt_t cyhal_lptimer_reload(cyhal_lptimer_t *obj)
647 {
648 #if defined (_CYHAL_LPTIMER_MCWDT_B)
649 // we save the current value for counter2 as our offset.
650 // This is done because for CAT1C devices we cannot reset counter2.
651 // This gives us the ability to mimic a reset by keeping track of
652 // the last time reload was called. The offset will be deducted
653 // from the current counter2 value when cyhal_lptimer_read is called
654 obj->offset = Cy_MCWDT_GetCount(obj->base, CY_MCWDT_COUNTER2);
655 Cy_MCWDT_Unlock(obj->base);
656 Cy_MCWDT_ResetCounters(obj->base, CY_MCWDT_CTR1, _CYHAL_LPTIMER_RESET_TIME_US);
657 Cy_MCWDT_Lock(obj->base);
658
659 #else
660 Cy_MCWDT_ResetCounters(obj->base, (CY_MCWDT_CTR0 | CY_MCWDT_CTR1 | CY_MCWDT_CTR2), 2 * _CYHAL_LPTIMER_RESET_TIME_US);
661 #endif
662 return CY_RSLT_SUCCESS;
663 }
664
cyhal_lptimer_set_match(cyhal_lptimer_t * obj,uint32_t ticks)665 cy_rslt_t cyhal_lptimer_set_match(cyhal_lptimer_t *obj, uint32_t ticks)
666 {
667 obj->clear_int_mask = false;
668 uint32_t c2_current = cyhal_lptimer_read(obj);
669 #if defined (_CYHAL_LPTIMER_MCWDT_B)
670 // The reason we add the offset to ticks is because we want to get
671 // the current value of counter2 without the offset value.
672 // Since our delay is ticks - c2_current, this will get us our
673 // current time + delay
674 obj->final_time = ticks + obj->offset;
675 #endif
676 return _cyhal_lptimer_set_delay_common(obj, ticks - c2_current);
677 }
678
cyhal_lptimer_set_delay(cyhal_lptimer_t * obj,uint32_t delay)679 cy_rslt_t cyhal_lptimer_set_delay(cyhal_lptimer_t *obj, uint32_t delay)
680 {
681 obj->clear_int_mask = true;
682 #if defined (_CYHAL_LPTIMER_MCWDT_B)
683 obj->final_time = (Cy_MCWDT_GetCount(obj->base, CY_MCWDT_COUNTER2) + delay);
684 #endif
685 return _cyhal_lptimer_set_delay_common(obj, delay);
686 }
687
cyhal_lptimer_read(const cyhal_lptimer_t * obj)688 uint32_t cyhal_lptimer_read(const cyhal_lptimer_t *obj)
689 {
690 #if defined (_CYHAL_LPTIMER_MCWDT_B)
691 uint32_t ctr2_count = Cy_MCWDT_GetCount(obj->base, CY_MCWDT_COUNTER2);
692 if(obj->offset > ctr2_count)
693 {
694 return (uint32_t)((((uint64_t)1 << 32) - obj->offset) + ctr2_count);
695 }
696 else
697 {
698 return ctr2_count - obj->offset;
699 }
700 #else
701 return Cy_MCWDT_GetCount(obj->base, CY_MCWDT_COUNTER2);
702 #endif
703 }
704
cyhal_lptimer_register_callback(cyhal_lptimer_t * obj,cyhal_lptimer_event_callback_t callback,void * callback_arg)705 void cyhal_lptimer_register_callback(cyhal_lptimer_t *obj, cyhal_lptimer_event_callback_t callback, void *callback_arg)
706 {
707 CY_ASSERT(CYHAL_RSC_INVALID != obj->resource.block_num);
708
709 uint32_t savedIntrStatus = cyhal_system_critical_section_enter();
710 obj->callback_data.callback = (cy_israddress) callback;
711 obj->callback_data.callback_arg = callback_arg;
712 cyhal_system_critical_section_exit(savedIntrStatus);
713 }
714
cyhal_lptimer_enable_event(cyhal_lptimer_t * obj,cyhal_lptimer_event_t event,uint8_t intr_priority,bool enable)715 void cyhal_lptimer_enable_event(cyhal_lptimer_t *obj, cyhal_lptimer_event_t event, uint8_t intr_priority, bool enable)
716 {
717 CY_UNUSED_PARAMETER(event);
718 CY_ASSERT(event == CYHAL_LPTIMER_COMPARE_MATCH);
719
720 obj->isr_instruction &= ~_CYHAL_LPTIMER_ISR_CALL_USER_CB_MASK;
721 obj->isr_instruction |= (uint8_t)enable;
722
723 _cyhal_system_irq_t irqn =_CYHAL_MCWDT_IRQS[obj->resource.block_num];
724 _cyhal_irq_set_priority(irqn, intr_priority);
725
726 if (enable)
727 {
728 #if defined (_CYHAL_LPTIMER_MCWDT_B)
729 Cy_MCWDT_ClearInterrupt(obj->base, _cyhal_lptimer_counter_to_mask(obj->counter));
730 Cy_MCWDT_SetInterruptMask(obj->base, _cyhal_lptimer_counter_to_mask(obj->counter));
731 #elif defined(_CYHAL_LPTIMER_MCWDT)
732 Cy_MCWDT_ClearInterrupt(obj->base, CY_MCWDT_CTR1);
733 Cy_MCWDT_SetInterruptMask(obj->base, CY_MCWDT_CTR1);
734 #elif defined (CY_IP_M0S8WCO)
735 _cyhal_irq_enable(irqn);
736 #endif
737 }
738 else
739 {
740 #if (_CYHAL_IRQ_MUXING)
741 /* We may be in a critical section. Only clear the interrupt status if there isn't a pending interrupt */
742 if (Cy_MCWDT_GetInterruptStatus(obj->base) != 0)
743 {
744 obj->isr_instruction |= _CYHAL_LPTIMER_ISR_CRITICAL_SECTION_MASK;
745 }
746 else
747 #endif
748 {
749 #if defined (_CYHAL_LPTIMER_MCWDT_B)
750 Cy_MCWDT_ClearInterrupt(obj->base, _cyhal_lptimer_counter_to_mask(obj->counter));
751 Cy_MCWDT_SetInterruptMask(obj->base, Cy_MCWDT_GetInterruptMask(obj->base) & ~_cyhal_lptimer_counter_to_mask(obj->counter));
752 #elif defined(_CYHAL_LPTIMER_MCWDT)
753 Cy_MCWDT_ClearInterrupt(obj->base, CY_MCWDT_CTR1);
754 Cy_MCWDT_SetInterruptMask(obj->base, 0);
755 #elif defined (CY_IP_M0S8WCO)
756 _cyhal_irq_disable(irqn);
757 #endif
758 }
759 }
760 }
761
cyhal_lptimer_irq_trigger(cyhal_lptimer_t * obj)762 void cyhal_lptimer_irq_trigger(cyhal_lptimer_t *obj)
763 {
764 CY_ASSERT(CYHAL_RSC_INVALID != obj->resource.block_num);
765 cyhal_lptimer_event_callback_t callback = (cyhal_lptimer_event_callback_t) obj->callback_data.callback;
766 (callback)(obj->callback_data.callback_arg, CYHAL_LPTIMER_COMPARE_MATCH);
767 }
768
cyhal_lptimer_get_info(cyhal_lptimer_t * obj,cyhal_lptimer_info_t * info)769 void cyhal_lptimer_get_info(cyhal_lptimer_t *obj, cyhal_lptimer_info_t *info)
770 {
771 CY_UNUSED_PARAMETER(obj);
772 CY_ASSERT(info != NULL);
773
774 #if (WCO_WDT_EN == 1)
775 const cyhal_clock_t *lf_obj = &CYHAL_CLOCK_WDCSEL;
776 #else
777 const cyhal_clock_t *lf_obj = &CYHAL_CLOCK_LF;
778 #endif
779 info->frequency_hz = cyhal_clock_get_frequency(lf_obj);
780 info->min_set_delay = _CYHAL_LPTIMER_MIN_DELAY;
781 info->max_counter_value = _CYHAL_LPTIMER_MAX_COUNTER_VAL;
782 }
783
784 #if defined(__cplusplus)
785 }
786 #endif
787
788 #endif // CYHAL_DRIVER_AVAILABLE_LPTIMER
789