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