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 CAT1C devices.
35 * It is not recommended to use 0xFFFFFFFF. This is to avoid overflowing both C0 and C1.
36 * For CAT1C devices (XMC), 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 >= 3)) || ((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 .c1UpperLimit = 0xFFFF,
163 .c1UpperAction = CY_MCWDT_ACTION_NONE,
164 .c1DebugRun = CY_MCWDT_ENABLE,
165 .c1WarnLimit = 0xFFFF,
166 .c1WarnAction = CY_MCWDT_WARN_ACTION_INT,
167 .c1AutoService = CY_MCWDT_ENABLE,
168 .c2ToggleBit = 0,
169 .c2Action = CY_MCWDT_CNT2_ACTION_INT,
170 .coreSelect = CY_MCWDT_PAUSED_BY_NO_CORE,
171 };
172 #else
173 #if defined(CY_IP_MXS40SSRSS)
174 static const uint16_t _CYHAL_LPTIMER_RESET_TIME_US = 93; // Recommended value per PDL
175 #else
176 static const uint16_t _CYHAL_LPTIMER_RESET_TIME_US = 62;
177 #endif
178 static const uint16_t _CYHAL_LPTIMER_SETMATCH_TIME_US = 0;
179 static const cy_stc_mcwdt_config_t default_cfg = {
180 .c0Match = 0xFFFF,
181 .c1Match = 0xFFFF,
182 .c0Mode = CY_MCWDT_MODE_INT,
183 .c1Mode = CY_MCWDT_MODE_INT,
184 .c2Mode = CY_MCWDT_MODE_NONE,
185 .c2ToggleBit = 0,
186 .c0ClearOnMatch = false,
187 .c1ClearOnMatch = false,
188 .c0c1Cascade = true,
189 .c1c2Cascade = false
190 };
191 #endif
192 #elif defined (CY_IP_M0S8WCO)
193 static cyhal_lptimer_t *_cyhal_lptimer_config_structs[CY_IP_M0S8WCO_INSTANCES];
194 static const uint16_t _CYHAL_LPTIMER_RESET_TIME_US = 180;
195 static const uint16_t _CYHAL_LPTIMER_SETMATCH_TIME_US = 180;
196 static const cy_stc_wdc_config_t default_cfg = {
197 .counter0Match = 0xFFFF,
198 .counter1Match = 0xFFFF,
199 .counter2ToggleBit = 0,
200 .counter0Interrupt = false,
201 .counter1Interrupt = true,
202 .counter2Interrupt = false,
203 .counter0ClearOnMatch = false,
204 .counter1ClearOnMatch = true,
205 .countersCascade = CY_WDC_CASCADE_COUNTERS01,
206 .clockSource = CY_WDC_CLOCK_ILO
207 };
208 #else
209 #error "Current HW block is not supported"
210 #endif
211
212 #if (_CYHAL_IRQ_MUXING)
213 static const _cyhal_system_irq_t _CYHAL_MCWDT_DISCONNECTED_IRQ = disconnected_IRQn;
214 #else
215 static const _cyhal_system_irq_t _CYHAL_MCWDT_DISCONNECTED_IRQ = unconnected_IRQn;
216 #endif
217
218 #if defined(_CYHAL_LPTIMER_INSTANCES)
219 static const _cyhal_system_irq_t _CYHAL_MCWDT_IRQS[_CYHAL_LPTIMER_INSTANCES] =
220 {
221 #if (defined(COMPONENT_CM55) && defined(CY_IP_MXS22SRSS))
222 //For CM0P/CM55 cores for cat1d, there is no 0 irqn.
223 unconnected_IRQn,
224 #else
225 srss_interrupt_mcwdt_0_IRQn,
226 #endif
227 #if (_CYHAL_LPTIMER_INSTANCES >=2)
228 #if (defined(COMPONENT_CM33) && defined(CY_IP_MXS22SRSS))
229 //For CM0P/CM33 cores for cat1d, there is no 1 irqn
230 unconnected_IRQn,
231 #else
232 srss_interrupt_mcwdt_1_IRQn,
233 #endif
234 #endif
235 #if (_CYHAL_LPTIMER_INSTANCES ==3)
236 srss_interrupt_mcwdt_2_IRQn,
237 #endif
238 #if (_CYHAL_LPTIMER_INSTANCES > 3)
239 #error "_CYHAL_LPTIMER_INSTANCES > 3 not supported"
240 #endif
241 };
242 #elif defined (CY_IP_M0S8WCO_INSTANCES)//MOS8WCO only has one instance
243 static const _cyhal_system_irq_t _CYHAL_MCWDT_IRQS[1] =
244 {
245 wco_interrupt_IRQn,
246 };
247 #else
248 #error "HW Block not supported"
249 #endif
250
_cyhal_lptimer_get_instance_from_irq(void)251 static uint32_t _cyhal_lptimer_get_instance_from_irq(void)
252 /* Gets instance from the IRQ array*/
253 {
254 _cyhal_system_irq_t irqn = _cyhal_irq_get_active();
255 for(uint8_t i = 0; i < sizeof(_CYHAL_MCWDT_IRQS)/sizeof(_CYHAL_MCWDT_IRQS[0]); ++i)
256 {
257 if (_CYHAL_MCWDT_IRQS[i] == irqn)
258 {
259 return i;
260 }
261 }
262
263 /* The IRQ may be muxed and may be triggered by other sources */
264 return _CYHAL_MCWDT_DISCONNECTED_IRQ;
265 }
266
267 #if defined (_CYHAL_LPTIMER_MCWDT_B)
268 /* Get the mask (for e.g. ClearInterrupt) associated with a particular counter */
_cyhal_lptimer_counter_to_mask(cy_en_mcwdtctr_t counter)269 uint32_t _cyhal_lptimer_counter_to_mask(cy_en_mcwdtctr_t counter)
270 {
271 return 1u << ((uint8_t)counter);
272 }
273 #endif
274
_cyhal_lptimer_irq_handler(void)275 static void _cyhal_lptimer_irq_handler(void)
276 {
277 uint32_t instance = (uint32_t)_cyhal_lptimer_get_instance_from_irq();
278 if (instance != _CYHAL_MCWDT_DISCONNECTED_IRQ)
279 {
280 cyhal_lptimer_t *obj = _cyhal_lptimer_config_structs[instance];
281 #if defined (_CYHAL_LPTIMER_MCWDT_B)
282 uint32_t c2_count = Cy_MCWDT_GetCount(obj->base, CY_MCWDT_COUNTER2);
283 //Since CAT1C does not use Counter0, there is no need to clear the interrupt
284 Cy_MCWDT_ClearInterrupt(obj->base, (CY_MCWDT_CTR1 | CY_MCWDT_CTR2));
285 #else
286 Cy_MCWDT_ClearInterrupt(obj->base, (CY_MCWDT_CTR0 | CY_MCWDT_CTR1 | CY_MCWDT_CTR2));
287 #endif
288 #if defined (_CYHAL_LPTIMER_MCWDT_B)
289 // We want to clear the interrupt mask everytime we enter the IRQ handler
290 // for CAT1C devices regardless if set_delay or set_match was called.
291 // This means if cyhal_lptimer_set_match was called, a single interrupt will be triggered.
292 Cy_MCWDT_SetInterruptMask(obj->base, Cy_MCWDT_GetInterruptMask(obj->base) & ~_cyhal_lptimer_counter_to_mask(obj->counter));
293 #endif
294 /* Clear interrupt mask if set only from cyhal_lptimer_set_delay() function */
295 if (obj->clear_int_mask)
296 {
297 #if defined (_CYHAL_LPTIMER_MCWDT)
298 Cy_MCWDT_SetInterruptMask(obj->base, 0);
299 #else
300 Cy_WDC_InterruptDisable(obj->base, CY_WDC_COUNTER1);
301 #endif
302 }
303 #if defined (_CYHAL_LPTIMER_MCWDT_B)
304 if(obj->final_time - c2_count > 0 && obj->final_time > c2_count)
305 {
306 cyhal_lptimer_set_delay(obj, obj->final_time - c2_count);
307 }
308 else
309 #endif
310 {
311 if (NULL != obj->callback_data.callback && ((obj->isr_instruction & _CYHAL_LPTIMER_ISR_CALL_USER_CB_MASK) != 0))
312 {
313 cyhal_lptimer_event_callback_t callback = (cyhal_lptimer_event_callback_t) obj->callback_data.callback;
314 (callback)(obj->callback_data.callback_arg, CYHAL_LPTIMER_COMPARE_MATCH);
315 }
316 }
317 #if (_CYHAL_IRQ_MUXING)
318 /* Check if the event flag should have been cleared when inside a critical situation */
319 if ((obj->isr_instruction & _CYHAL_LPTIMER_ISR_CRITICAL_SECTION_MASK) != 0)
320 {
321 #if defined (_CYHAL_LPTIMER_MCWDT_B)
322 Cy_MCWDT_ClearInterrupt(obj->base, _cyhal_lptimer_counter_to_mask(obj->counter));
323 Cy_MCWDT_SetInterruptMask(obj->base, Cy_MCWDT_GetInterruptMask(obj->base) & ~_cyhal_lptimer_counter_to_mask(obj->counter));
324 #elif defined(_CYHAL_LPTIMER_MCWDT)
325 Cy_MCWDT_ClearInterrupt(obj->base, CY_MCWDT_CTR1);
326 Cy_MCWDT_SetInterruptMask(obj->base, 0);
327 #elif defined (CY_IP_M0S8WCO)
328 _cyhal_irq_disable(irqn);
329 #endif
330 obj->isr_instruction &= ~_CYHAL_LPTIMER_ISR_CRITICAL_SECTION_MASK;
331 }
332 #endif
333 }
334 }
335 #if defined (_CYHAL_LPTIMER_MCWDT_B)
_cyhal_lptimer_get_toggle_bit(uint32_t c2_current,uint32_t delay)336 uint32_t _cyhal_lptimer_get_toggle_bit(uint32_t c2_current, uint32_t delay)
337 {
338 uint32_t val = c2_current ^ (c2_current + delay);
339 int bit = 0;
340 while (val > 0)
341 {
342 bit++;
343 val >>= 1;
344 }
345 return bit - 1;
346 }
_cyhal_lptimer_set_delay_common(cyhal_lptimer_t * obj,uint32_t delay)347 static uint32_t _cyhal_lptimer_set_delay_common(cyhal_lptimer_t *obj, uint32_t delay)
348 {
349 // Check to see if Counter1 or Counter2 is enabled.
350 // If neither is enabled, return Error Disabled.
351 // We do not check to see if Counter0 is enabled as it is not used
352 // for this IP implementation.
353 if ((Cy_MCWDT_GetEnabledStatus(obj->base, CY_MCWDT_COUNTER1) == 0UL)
354 || (Cy_MCWDT_GetEnabledStatus(obj->base, CY_MCWDT_COUNTER2) == 0UL))
355 {
356 return CYHAL_LPTIMER_RSLT_ERR_DISABLED;
357 }
358 if (delay <= _CYHAL_LPTIMER_MIN_DELAY)
359 {
360 delay = _CYHAL_LPTIMER_MIN_DELAY;
361 }
362 #if (_CYHAL_LPTIMER_MAX_DELAY_TICKS != 0xffffffffUL)
363 /* No point in this check if uint32_t can't exceed the max */
364 if (delay > _CYHAL_LPTIMER_MAX_DELAY_TICKS)
365 {
366 delay = _CYHAL_LPTIMER_MAX_DELAY_TICKS;
367 }
368 #endif
369 // If the delay is greater than 2^16 we will use Counter2 for interrupts
370 // we must then clear the Counter2 interrupt before setting the new match.
371 obj->counter = (delay > ((1 << 16)-1)) ? CY_MCWDT_COUNTER2 : CY_MCWDT_COUNTER1;
372 uint32_t critical_section = cyhal_system_critical_section_enter();
373 uint32_t counter_value = Cy_MCWDT_GetCount(obj->base, obj->counter);
374 // if the counter value + the delay exceeds 32 bits, it is expected
375 // that the wrap around will be handled by the uin32_t variable
376 uint32_t match_value = counter_value + delay;
377 Cy_MCWDT_Unlock(obj->base);
378 if(obj->counter == CY_MCWDT_COUNTER1)
379 {
380 if(match_value > ((1 << 16)-1))
381 {
382 // Wait 3 LFClk cycles for reset
383 Cy_MCWDT_ResetCounters(obj->base, _cyhal_lptimer_counter_to_mask(obj->counter), _CYHAL_LPTIMER_RESET_TIME_US);
384 // Since we waited 3 LFClk cycles to reset the counter, we want to deduct that from our delay
385 match_value = delay - _CYHAL_LPTIMER_MIN_DELAY;
386 if(match_value < _CYHAL_LPTIMER_MIN_DELAY)
387 {
388 // After resetting the counter and deducting the number of clock cycles it took
389 // The delay is below our minumum delay.
390 Cy_MCWDT_Lock(obj->base);
391 cyhal_system_critical_section_exit(critical_section);
392 return CYHAL_LPTIMER_RSLT_ERR_BAD_ARGUMENT;
393 }
394 }
395 Cy_MCWDT_SetWarnLimit(obj->base, obj->counter, (uint16_t)match_value, _CYHAL_LPTIMER_SETMATCH_TIME_US);
396 }
397 else
398 {
399 // We want to get the monitored bit to set for the toggle point
400 uint32_t toggle_bit = _cyhal_lptimer_get_toggle_bit(counter_value, delay);
401 // We want to set an interrupt for Counter2 to the 2^n value closest to the match value without going over
402 // If there is a delta between our power of value of 2^n and the match value
403 // We set the remaining delay time on the object which will trigger a follow up interrupt
404 // In the IRQ handler
405 Cy_MCWDT_SetToggleBit(obj->base, toggle_bit);
406 // Wait 3 LFClk cycles to make sure the toggle bit was updated
407 cyhal_system_delay_us(_CYHAL_LPTIMER_SETMATCH_TIME_US);
408 }
409 Cy_MCWDT_Lock(obj->base);
410 cyhal_system_critical_section_exit(critical_section);
411 Cy_MCWDT_ClearInterrupt(obj->base, _cyhal_lptimer_counter_to_mask(obj->counter));
412 Cy_MCWDT_SetInterruptMask(obj->base, _cyhal_lptimer_counter_to_mask(obj->counter));
413 return CY_RSLT_SUCCESS;
414 }
415 #else
_cyhal_lptimer_set_delay_common(cyhal_lptimer_t * obj,uint32_t delay)416 static uint32_t _cyhal_lptimer_set_delay_common(cyhal_lptimer_t *obj, uint32_t delay)
417 {
418 if ((Cy_MCWDT_GetEnabledStatus(obj->base, CY_MCWDT_COUNTER0) == 0UL)
419 || (Cy_MCWDT_GetEnabledStatus(obj->base, CY_MCWDT_COUNTER1) == 0UL)
420 || (Cy_MCWDT_GetEnabledStatus(obj->base, CY_MCWDT_COUNTER2) == 0UL))
421 {
422 return CYHAL_LPTIMER_RSLT_ERR_DISABLED;
423 }
424
425 /**
426 * - 16 bit Counter0 (C0) & Counter1 (C1) are cascaded to generated a 32 bit counter.
427 * - Counter2 (C2) is a free running counter.
428 * - C0 continues counting after reaching its match value. On PSoC™ 4 Counter1 is reset on
429 * match. On PSoC™ 6 it continues counting.
430 * - An interrupt is generated when C1 reaches the match value. On PSoC™ 4 this happens when
431 * the counter increments to the same value as match. On PSoC™ 6 this happens when it increments
432 * past the match value.
433 *
434 * EXAMPLE:
435 * Supposed T=C0=C1=0, and we need to trigger an interrupt at T=0x18000.
436 * We set C0_match to 0x8000 and C1 match to 2 on PSoC™ 4 and 1 on PSoC™ 6.
437 * At T = 0x8000, C0_value matches C0_match so C1 get incremented. C1/C0=0x18000.
438 * At T = 0x18000, C0_value matches C0_match again so C1 get incremented from 1 to 2.
439 * When C1 get incremented from 1 to 2 the interrupt is generated.
440 * At T = 0x18000, C1/C0 = 0x28000.
441 */
442
443 if (delay <= _CYHAL_LPTIMER_MIN_DELAY)
444 {
445 delay = _CYHAL_LPTIMER_MIN_DELAY;
446 }
447 if (delay > _CYHAL_LPTIMER_MAX_DELAY_TICKS)
448 {
449 delay = _CYHAL_LPTIMER_MAX_DELAY_TICKS;
450 }
451
452 Cy_MCWDT_ClearInterrupt(obj->base, CY_MCWDT_CTR1);
453 uint16_t c0_old_match = (uint16_t)Cy_MCWDT_GetMatch(obj->base, CY_MCWDT_COUNTER0);
454
455 uint32_t critical_section = cyhal_system_critical_section_enter();
456
457 /* Cascading from C0 match into C1 is queued and can take 1 full LF clk cycle.
458 * There are 3 cases:
459 * 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.
460 * Case 2: if c0 = match0 -1 then cascade may or not happen before new match value would occur. Match occurs on rising clock edge.
461 * Synchronizing match value occurs on falling edge. Wait until c0 = match0 to ensure cascade occurs.
462 * Case 3: everything works as expected.
463 *
464 * Note: timeout is needed here just in case the LFCLK source gives out. This avoids device lockup.
465 *
466 * ((2 * Cycles_LFClk) / Cycles_cpu_iteration) * (HFCLk_max / LFClk_min) = Iterations_required
467 * Typical case: (2 / 100) * ((150x10^6)/33576) = 89 iterations
468 * Worst case: (2 / 100) * ((150x10^6)/1) = 3x10^6 iterations
469 * Compromise: (2 / 100) * ((150x10^6)/0xFFFF iterations) = 45 Hz = LFClk_min
470 */
471 const uint32_t DEFAULT_TIMEOUT = 0xFFFFUL;
472 uint32_t timeout = DEFAULT_TIMEOUT;
473 uint16_t c0_current_ticks = (uint16_t)Cy_MCWDT_GetCount(obj->base, CY_MCWDT_COUNTER0);
474 // Wait until the cascade has definitively happened. It takes a clock cycle for the cascade to happen, and potentially another a full
475 // LFCLK clock cycle for the cascade to propagate up to the HFCLK-domain registers that the CPU reads.
476 while (((((uint16_t)(c0_old_match - 1)) == c0_current_ticks) ||
477 (c0_old_match == c0_current_ticks) ||
478 (((uint16_t)(c0_old_match + 1)) == c0_current_ticks)) && (timeout != 0UL))
479 {
480 c0_current_ticks = (uint16_t)Cy_MCWDT_GetCount(obj->base, CY_MCWDT_COUNTER0);
481 timeout--;
482 }
483 if (timeout == 0UL)
484 {
485 // Timeout has occurred. There could have been a clock failure while waiting for the count value to update.
486 cyhal_system_critical_section_exit(critical_section);
487 return CYHAL_LPTIMER_RSLT_ERR_DISABLED;
488 }
489
490 uint16_t c0_match = (uint16_t)(c0_current_ticks + delay);
491 // Changes can take up to 2 clk_lf cycles to propagate. If we set the match within this window of the current value,
492 // then it is nondeterministic whether the first cascade will trigger immediately or after 2^16 cycles. Wait until
493 // c0 is in a more predictable state.
494 timeout = DEFAULT_TIMEOUT;
495 uint32_t c0_new_ticks = c0_current_ticks;
496 while(((c0_new_ticks == c0_match) ||
497 (c0_new_ticks == ((uint16_t)(c0_match + 1))) ||
498 (c0_new_ticks == ((uint16_t)(c0_match + 2))))
499 && (timeout != 0UL))
500 {
501 c0_new_ticks = (uint16_t)Cy_MCWDT_GetCount(obj->base, CY_MCWDT_COUNTER0);
502 timeout--;
503 }
504
505 delay -= (c0_new_ticks >= c0_current_ticks)
506 ? (c0_new_ticks - c0_current_ticks)
507 : ((0xFFFFU - c0_current_ticks) + c0_new_ticks);
508
509 c0_match = (uint16_t)(c0_current_ticks + delay);
510 uint16_t c1_current_ticks = (uint16_t)Cy_MCWDT_GetCount(obj->base, CY_MCWDT_COUNTER1);
511 uint16_t c1_match = (uint16_t)(c1_current_ticks + (delay >> 16));
512
513 #if defined(CY_IP_M0S8WCO)
514 c1_match += 1;
515 // Increase away from rollover glitch; can't go backward or c1 interrupt will not trigger
516 if (c0_match == 0)
517 c0_match = 1;
518 #endif
519 Cy_MCWDT_SetMatch(obj->base, CY_MCWDT_COUNTER0, c0_match, _CYHAL_LPTIMER_SETMATCH_TIME_US);
520 Cy_MCWDT_SetMatch(obj->base, CY_MCWDT_COUNTER1, c1_match, _CYHAL_LPTIMER_SETMATCH_TIME_US);
521 cyhal_system_critical_section_exit(critical_section);
522 #ifdef _CYHAL_LPTIMER_MCWDT
523 Cy_MCWDT_SetInterruptMask(obj->base, CY_MCWDT_CTR1);
524 #else
525 Cy_WDC_InterruptEnable(obj->base, CY_MCWDT_COUNTER1);
526 #endif
527 return CY_RSLT_SUCCESS;
528 }
529 #endif
530
cyhal_lptimer_init(cyhal_lptimer_t * obj)531 cy_rslt_t cyhal_lptimer_init(cyhal_lptimer_t *obj)
532 {
533 CY_ASSERT(NULL != obj);
534
535 obj->resource.type = CYHAL_RSC_INVALID;
536 obj->clear_int_mask = false;
537 obj->isr_instruction = 0;
538 cy_rslt_t rslt = CYHAL_LPTIMER_RSLT_ERR_NOT_SUPPORTED;
539 for(uint8_t inst=0; inst < _CYHAL_LPTIMER_INSTANCES; ++inst)
540 {
541 if(_CYHAL_MCWDT_IRQS[inst] != _CYHAL_MCWDT_DISCONNECTED_IRQ)
542 {
543 // Make a temp LPTIMER cyhal_resource_inst_t with block_num = inst
544 cyhal_resource_inst_t temp = {
545 .type = CYHAL_RSC_LPTIMER,
546 .block_num = inst,
547 .channel_num = 0,
548 };
549
550 rslt = cyhal_hwmgr_reserve(&temp);
551 if(rslt == CY_RSLT_SUCCESS)
552 {
553 obj->resource = temp;
554 break;
555 }
556 }
557 }
558 if (CY_RSLT_SUCCESS == rslt)
559 {
560 obj->base = _CYHAL_LPTIMER_BASE_ADDRESSES[obj->resource.block_num];
561 #if defined (_CYHAL_LPTIMER_MCWDT_B)
562 obj->offset = 0;
563 obj->final_time = 0;
564 obj->counter = CY_MCWDT_COUNTER1;
565 cy_stc_mcwdt_config_t cfg = default_cfg;
566 #elif defined (_CYHAL_LPTIMER_MCWDT)
567 cy_stc_mcwdt_config_t cfg = default_cfg;
568 #elif defined (CY_IP_M0S8WCO)
569 cy_stc_wdc_config_t cfg = default_cfg;
570 // The WDC_SEL clock source is populated into the config
571 // register by the clock driver. Extract it so that the
572 // PDL init doesn't revert it to default
573 cfg.clockSource = Cy_WDC_GetClockSource(WCO);
574 #endif
575 rslt = (cy_rslt_t) Cy_MCWDT_Init(obj->base, &cfg);
576 }
577
578 if (CY_RSLT_SUCCESS == rslt)
579 {
580 obj->callback_data.callback = NULL;
581 obj->callback_data.callback_arg = NULL;
582 _cyhal_lptimer_config_structs[obj->resource.block_num] = obj;
583 }
584
585 if (CY_RSLT_SUCCESS == rslt)
586 {
587 _cyhal_system_irq_t irqn = _CYHAL_MCWDT_IRQS[obj->resource.block_num];
588 _cyhal_irq_register(irqn, _CYHAL_LPTIMER_DEFAULT_PRIORITY, _cyhal_lptimer_irq_handler);
589
590 if (CY_RSLT_SUCCESS == rslt)
591 {
592 _cyhal_irq_enable(irqn);
593 #if defined (_CYHAL_LPTIMER_MCWDT_B)
594 Cy_MCWDT_Unlock(obj->base);
595 Cy_MCWDT_Enable(obj->base, (CY_MCWDT_CTR1 | CY_MCWDT_CTR2), _CYHAL_LPTIMER_RESET_TIME_US);
596 Cy_MCWDT_Lock(obj->base);
597 #else
598 Cy_MCWDT_Enable(obj->base, _CYHAL_LPTIMER_CTRL, _CYHAL_LPTIMER_RESET_TIME_US);
599 #endif
600 }
601 }
602
603 if (CY_RSLT_SUCCESS != rslt)
604 {
605 cyhal_lptimer_free(obj);
606 }
607
608 return rslt;
609 }
610
cyhal_lptimer_free(cyhal_lptimer_t * obj)611 void cyhal_lptimer_free(cyhal_lptimer_t *obj)
612 {
613 if (CYHAL_RSC_INVALID != obj->resource.type)
614 {
615 _cyhal_system_irq_t irqn = _CYHAL_MCWDT_IRQS[obj->resource.block_num];
616 _cyhal_irq_free(irqn);
617
618 cyhal_hwmgr_free(&(obj->resource));
619 obj->resource.type = CYHAL_RSC_INVALID;
620 }
621 if (NULL != obj->base)
622 {
623 #if defined (_CYHAL_LPTIMER_MCWDT_B)
624 Cy_MCWDT_Unlock(obj->base);
625 // only need to disable counter1 and counter2 since those are the only counters
626 // used for the IP.
627 Cy_MCWDT_Disable(obj->base, (CY_MCWDT_CTR1 | CY_MCWDT_CTR2), _CYHAL_LPTIMER_RESET_TIME_US);
628 Cy_MCWDT_Lock(obj->base);
629 #else
630 Cy_MCWDT_Disable(obj->base, _CYHAL_LPTIMER_CTRL, _CYHAL_LPTIMER_RESET_TIME_US);
631 #endif
632 // When we're using the WDC, the clock source selection is stored in the WDC_CONFIG
633 // register and we want that to persist across free/init calls. So don't call deinit
634 // because that will clear it out.
635 #if !defined(CY_IP_M0S8WCO)
636 Cy_MCWDT_DeInit(obj->base);
637 #endif
638 obj->base = NULL;
639 }
640 }
641
cyhal_lptimer_reload(cyhal_lptimer_t * obj)642 cy_rslt_t cyhal_lptimer_reload(cyhal_lptimer_t *obj)
643 {
644 #if defined (_CYHAL_LPTIMER_MCWDT_B)
645 // we save the current value for counter2 as our offset.
646 // This is done because for CAT1C devices we cannot reset counter2.
647 // This gives us the ability to mimic a reset by keeping track of
648 // the last time reload was called. The offset will be deducted
649 // from the current counter2 value when cyhal_lptimer_read is called
650 obj->offset = Cy_MCWDT_GetCount(obj->base, CY_MCWDT_COUNTER2);
651 Cy_MCWDT_Unlock(obj->base);
652 Cy_MCWDT_ResetCounters(obj->base, CY_MCWDT_CTR1, _CYHAL_LPTIMER_RESET_TIME_US);
653 Cy_MCWDT_Lock(obj->base);
654
655 #else
656 Cy_MCWDT_ResetCounters(obj->base, (CY_MCWDT_CTR0 | CY_MCWDT_CTR1 | CY_MCWDT_CTR2), 2 * _CYHAL_LPTIMER_RESET_TIME_US);
657 #endif
658 return CY_RSLT_SUCCESS;
659 }
660
cyhal_lptimer_set_match(cyhal_lptimer_t * obj,uint32_t ticks)661 cy_rslt_t cyhal_lptimer_set_match(cyhal_lptimer_t *obj, uint32_t ticks)
662 {
663 obj->clear_int_mask = false;
664 uint32_t c2_current = cyhal_lptimer_read(obj);
665 #if defined (_CYHAL_LPTIMER_MCWDT_B)
666 // The reason we add the offset to ticks is because we want to get
667 // the current value of counter2 without the offset value.
668 // Since our delay is ticks - c2_current, this will get us our
669 // current time + delay
670 obj->final_time = ticks + obj->offset;
671 #endif
672 return _cyhal_lptimer_set_delay_common(obj, ticks - c2_current);
673 }
674
cyhal_lptimer_set_delay(cyhal_lptimer_t * obj,uint32_t delay)675 cy_rslt_t cyhal_lptimer_set_delay(cyhal_lptimer_t *obj, uint32_t delay)
676 {
677 obj->clear_int_mask = true;
678 #if defined (_CYHAL_LPTIMER_MCWDT_B)
679 obj->final_time = (Cy_MCWDT_GetCount(obj->base, CY_MCWDT_COUNTER2) + delay);
680 #endif
681 return _cyhal_lptimer_set_delay_common(obj, delay);
682 }
683
cyhal_lptimer_read(const cyhal_lptimer_t * obj)684 uint32_t cyhal_lptimer_read(const cyhal_lptimer_t *obj)
685 {
686 #if defined (_CYHAL_LPTIMER_MCWDT_B)
687 uint32_t ctr2_count = Cy_MCWDT_GetCount(obj->base, CY_MCWDT_COUNTER2);
688 if(obj->offset > ctr2_count)
689 {
690 return (uint32_t)((((uint64_t)1 << 32) - obj->offset) + ctr2_count);
691 }
692 else
693 {
694 return ctr2_count - obj->offset;
695 }
696 #else
697 return Cy_MCWDT_GetCount(obj->base, CY_MCWDT_COUNTER2);
698 #endif
699 }
700
cyhal_lptimer_register_callback(cyhal_lptimer_t * obj,cyhal_lptimer_event_callback_t callback,void * callback_arg)701 void cyhal_lptimer_register_callback(cyhal_lptimer_t *obj, cyhal_lptimer_event_callback_t callback, void *callback_arg)
702 {
703 CY_ASSERT(CYHAL_RSC_INVALID != obj->resource.block_num);
704
705 uint32_t savedIntrStatus = cyhal_system_critical_section_enter();
706 obj->callback_data.callback = (cy_israddress) callback;
707 obj->callback_data.callback_arg = callback_arg;
708 cyhal_system_critical_section_exit(savedIntrStatus);
709 }
710
cyhal_lptimer_enable_event(cyhal_lptimer_t * obj,cyhal_lptimer_event_t event,uint8_t intr_priority,bool enable)711 void cyhal_lptimer_enable_event(cyhal_lptimer_t *obj, cyhal_lptimer_event_t event, uint8_t intr_priority, bool enable)
712 {
713 CY_UNUSED_PARAMETER(event);
714 CY_ASSERT(event == CYHAL_LPTIMER_COMPARE_MATCH);
715
716 obj->isr_instruction &= ~_CYHAL_LPTIMER_ISR_CALL_USER_CB_MASK;
717 obj->isr_instruction |= (uint8_t)enable;
718
719 _cyhal_system_irq_t irqn =_CYHAL_MCWDT_IRQS[obj->resource.block_num];
720 _cyhal_irq_set_priority(irqn, intr_priority);
721
722 if (enable)
723 {
724 #if defined (_CYHAL_LPTIMER_MCWDT_B)
725 Cy_MCWDT_ClearInterrupt(obj->base, _cyhal_lptimer_counter_to_mask(obj->counter));
726 Cy_MCWDT_SetInterruptMask(obj->base, _cyhal_lptimer_counter_to_mask(obj->counter));
727 #elif defined(_CYHAL_LPTIMER_MCWDT)
728 Cy_MCWDT_ClearInterrupt(obj->base, CY_MCWDT_CTR1);
729 Cy_MCWDT_SetInterruptMask(obj->base, CY_MCWDT_CTR1);
730 #elif defined (CY_IP_M0S8WCO)
731 _cyhal_irq_enable(irqn);
732 #endif
733 }
734 else
735 {
736 #if (_CYHAL_IRQ_MUXING)
737 /* We may be in a critical section. Only clear the interrupt status if there isn't a pending interrupt */
738 if (Cy_MCWDT_GetInterruptStatus(obj->base) != 0)
739 {
740 obj->isr_instruction |= _CYHAL_LPTIMER_ISR_CRITICAL_SECTION_MASK;
741 }
742 else
743 #endif
744 {
745 #if defined (_CYHAL_LPTIMER_MCWDT_B)
746 Cy_MCWDT_ClearInterrupt(obj->base, _cyhal_lptimer_counter_to_mask(obj->counter));
747 Cy_MCWDT_SetInterruptMask(obj->base, Cy_MCWDT_GetInterruptMask(obj->base) & ~_cyhal_lptimer_counter_to_mask(obj->counter));
748 #elif defined(_CYHAL_LPTIMER_MCWDT)
749 Cy_MCWDT_ClearInterrupt(obj->base, CY_MCWDT_CTR1);
750 Cy_MCWDT_SetInterruptMask(obj->base, 0);
751 #elif defined (CY_IP_M0S8WCO)
752 _cyhal_irq_disable(irqn);
753 #endif
754 }
755 }
756 }
757
cyhal_lptimer_irq_trigger(cyhal_lptimer_t * obj)758 void cyhal_lptimer_irq_trigger(cyhal_lptimer_t *obj)
759 {
760 CY_ASSERT(CYHAL_RSC_INVALID != obj->resource.block_num);
761 cyhal_lptimer_event_callback_t callback = (cyhal_lptimer_event_callback_t) obj->callback_data.callback;
762 (callback)(obj->callback_data.callback_arg, CYHAL_LPTIMER_COMPARE_MATCH);
763 }
764
cyhal_lptimer_get_info(cyhal_lptimer_t * obj,cyhal_lptimer_info_t * info)765 void cyhal_lptimer_get_info(cyhal_lptimer_t *obj, cyhal_lptimer_info_t *info)
766 {
767 CY_UNUSED_PARAMETER(obj);
768 CY_ASSERT(info != NULL);
769
770 #if (WCO_WDT_EN == 1)
771 const cyhal_clock_t *lf_obj = &CYHAL_CLOCK_WDCSEL;
772 #else
773 const cyhal_clock_t *lf_obj = &CYHAL_CLOCK_LF;
774 #endif
775 info->frequency_hz = cyhal_clock_get_frequency(lf_obj);
776 info->min_set_delay = _CYHAL_LPTIMER_MIN_DELAY;
777 info->max_counter_value = _CYHAL_LPTIMER_MAX_COUNTER_VAL;
778 }
779
780 #if defined(__cplusplus)
781 }
782 #endif
783
784 #endif // CYHAL_DRIVER_AVAILABLE_LPTIMER
785