1 /***************************************************************************//**
2  * @file
3  * @brief SLEEPTIMER hardware abstraction implementation for RTCC.
4  *******************************************************************************
5  * # License
6  * <b>Copyright 2019 Silicon Laboratories Inc. www.silabs.com</b>
7  *******************************************************************************
8  *
9  * SPDX-License-Identifier: Zlib
10  *
11  * The licensor of this software is Silicon Laboratories Inc.
12  *
13  * This software is provided 'as-is', without any express or implied
14  * warranty. In no event will the authors be held liable for any damages
15  * arising from the use of this software.
16  *
17  * Permission is granted to anyone to use this software for any purpose,
18  * including commercial applications, and to alter it and redistribute it
19  * freely, subject to the following restrictions:
20  *
21  * 1. The origin of this software must not be misrepresented; you must not
22  *    claim that you wrote the original software. If you use this software
23  *    in a product, an acknowledgment in the product documentation would be
24  *    appreciated but is not required.
25  * 2. Altered source versions must be plainly marked as such, and must not be
26  *    misrepresented as being the original software.
27  * 3. This notice may not be removed or altered from any source distribution.
28  *
29  ******************************************************************************/
30 
31 // Define module name for Power Manager debug feature
32 #define CURRENT_MODULE_NAME    "SLEEPTIMER_RTCC"
33 
34 #include "em_rtcc.h"
35 #include "sl_sleeptimer.h"
36 #include "sli_sleeptimer_hal.h"
37 #include "sl_core.h"
38 #include "sl_clock_manager.h"
39 #include "sl_interrupt_manager.h"
40 #include "sl_device_peripheral.h"
41 
42 #if defined(SL_CATALOG_POWER_MANAGER_PRESENT)
43 #include "sl_power_manager.h"
44 #endif
45 
46 #if SL_SLEEPTIMER_PERIPHERAL == SL_SLEEPTIMER_PERIPHERAL_RTCC
47 
48 // Minimum difference between current count value and what the comparator of the timer can be set to.
49 // 1 tick is added to the minimum diff for the algorithm of compensation for the IRQ handler that
50 // triggers when CNT == compare_value + 1. For more details refer to sleeptimer_hal_set_compare() function's header.
51 #define SLEEPTIMER_COMPARE_MIN_DIFF  (2 + 1)
52 
53 #define SLEEPTIMER_TMR_WIDTH (_RTCC_CNT_MASK)
54 
55 static bool cc_disabled = true;
56 
57 __STATIC_INLINE uint32_t get_time_diff(uint32_t a,
58                                        uint32_t b);
59 
60 /******************************************************************************
61  * Initializes RTCC sleep timer.
62  *****************************************************************************/
sleeptimer_hal_init_timer(void)63 void sleeptimer_hal_init_timer(void)
64 {
65   RTCC_Init_TypeDef rtcc_init   = RTCC_INIT_DEFAULT;
66   RTCC_CCChConf_TypeDef channel = RTCC_CH_INIT_COMPARE_DEFAULT;
67 
68   sl_clock_manager_enable_bus_clock(SL_BUS_CLOCK_RTCC);
69 
70   rtcc_init.enable = false;
71   rtcc_init.presc = (RTCC_CntPresc_TypeDef)(sleeptimer_hal_presc_to_log2(SL_SLEEPTIMER_FREQ_DIVIDER - 1));
72 #if (SL_SLEEPTIMER_DEBUGRUN == 1)
73   rtcc_init.debugRun = true;
74 #endif
75 
76   RTCC_Init(&rtcc_init);
77 
78   // Compare channel starts disabled and is enabled only when compare match interrupt is enabled.
79   channel.chMode = rtccCapComChModeOff;
80   RTCC_ChannelInit(1u, &channel);
81 
82   RTCC_IntDisable(_RTCC_IEN_MASK);
83   RTCC_IntClear(_RTCC_IF_MASK);
84   RTCC_CounterSet(0u);
85 
86   RTCC_Enable(true);
87 
88   sl_interrupt_manager_clear_irq_pending(RTCC_IRQn);
89   sl_interrupt_manager_enable_irq(RTCC_IRQn);
90 }
91 
92 /******************************************************************************
93  * Gets RTCC counter value.
94  *****************************************************************************/
sleeptimer_hal_get_counter(void)95 uint32_t sleeptimer_hal_get_counter(void)
96 {
97   return RTCC_CounterGet();
98 }
99 
100 /******************************************************************************
101  * Gets RTCC compare value.
102  *****************************************************************************/
sleeptimer_hal_get_compare(void)103 uint32_t sleeptimer_hal_get_compare(void)
104 {
105   return RTCC_ChannelCCVGet(1u);
106 }
107 
108 /******************************************************************************
109  * Sets RTCC compare value.
110  *
111  * @note Compare match value is set to the requested value - 1. This is done
112  * to compensate for the fact that the RTCC compare match interrupt always
113  * triggers at the end of the requested ticks and that the IRQ handler is
114  * executed when current tick count == compare_value + 1.
115  *****************************************************************************/
sleeptimer_hal_set_compare(uint32_t value)116 void sleeptimer_hal_set_compare(uint32_t value)
117 {
118   CORE_DECLARE_IRQ_STATE;
119   uint32_t counter;
120   uint32_t compare;
121   uint32_t compare_value = value;
122 
123   CORE_ENTER_CRITICAL();
124   counter = sleeptimer_hal_get_counter();
125   compare = sleeptimer_hal_get_compare();
126   if (((RTCC_IntGet() & RTCC_IEN_CC1) != 0)
127       || get_time_diff(compare, counter) > SLEEPTIMER_COMPARE_MIN_DIFF
128       || compare == counter) {
129     // Add margin if necessary
130     if (get_time_diff(compare_value, counter) < SLEEPTIMER_COMPARE_MIN_DIFF) {
131       compare_value = counter + SLEEPTIMER_COMPARE_MIN_DIFF;
132     }
133     compare_value %= SLEEPTIMER_TMR_WIDTH;
134 
135     RTCC_ChannelCCVSet(1u, compare_value - 1u);
136     sleeptimer_hal_enable_int(SLEEPTIMER_EVENT_COMP);
137   }
138   CORE_EXIT_CRITICAL();
139 
140   if (cc_disabled) {
141     RTCC->CC[1].CTRL |= RTCC_CC_CTRL_MODE_OUTPUTCOMPARE;
142     cc_disabled = false;
143   }
144 }
145 
146 /******************************************************************************
147  * Enables RTCC interrupts.
148  *****************************************************************************/
sleeptimer_hal_enable_int(uint8_t local_flag)149 void sleeptimer_hal_enable_int(uint8_t local_flag)
150 {
151   uint32_t rtcc_ien = 0u;
152 
153   if (local_flag & SLEEPTIMER_EVENT_OF) {
154     rtcc_ien |= RTCC_IEN_OF;
155   }
156 
157   if (local_flag & SLEEPTIMER_EVENT_COMP) {
158     rtcc_ien |= RTCC_IEN_CC1;
159   }
160 
161   RTCC_IntEnable(rtcc_ien);
162 }
163 
164 /******************************************************************************
165  * Disables RTCC interrupts.
166  *****************************************************************************/
sleeptimer_hal_disable_int(uint8_t local_flag)167 void sleeptimer_hal_disable_int(uint8_t local_flag)
168 {
169   uint32_t rtcc_int_dis = 0u;
170 
171   if (local_flag & SLEEPTIMER_EVENT_OF) {
172     rtcc_int_dis |= RTCC_IEN_OF;
173   }
174 
175   if (local_flag & SLEEPTIMER_EVENT_COMP) {
176     rtcc_int_dis |= RTCC_IEN_CC1;
177 
178     cc_disabled = true;
179     RTCC->CC[1].CTRL &= ~_RTCC_CC_CTRL_MODE_MASK;
180   }
181 
182   RTCC_IntDisable(rtcc_int_dis);
183 }
184 
185 /*******************************************************************************
186  * Hardware Abstraction Layer to set timer interrupts.
187  ******************************************************************************/
sleeptimer_hal_set_int(uint8_t local_flag)188 void sleeptimer_hal_set_int(uint8_t local_flag)
189 {
190   if (local_flag & SLEEPTIMER_EVENT_COMP) {
191     RTCC_IntSet(RTCC_IF_CC1);
192   }
193 }
194 
195 /******************************************************************************
196  * Gets status of specified interrupt.
197  *
198  * Note: This function must be called with interrupts disabled.
199  *****************************************************************************/
sli_sleeptimer_hal_is_int_status_set(uint8_t local_flag)200 bool sli_sleeptimer_hal_is_int_status_set(uint8_t local_flag)
201 {
202   bool int_is_set = false;
203   uint32_t irq_flag = RTCC_IntGet();
204 
205   switch (local_flag) {
206     case SLEEPTIMER_EVENT_COMP:
207       int_is_set = ((irq_flag & RTCC_IF_CC1) == RTCC_IF_CC1);
208       break;
209 
210     case SLEEPTIMER_EVENT_OF:
211       int_is_set = ((irq_flag & RTCC_IF_OF) == RTCC_IF_OF);
212       break;
213 
214     default:
215       break;
216   }
217 
218   return int_is_set;
219 }
220 
221 /*******************************************************************************
222  * RTCC interrupt handler.
223  ******************************************************************************/
RTCC_IRQHandler(void)224 void RTCC_IRQHandler(void)
225 {
226   CORE_DECLARE_IRQ_STATE;
227   uint8_t local_flag = 0;
228   uint32_t irq_flag;
229 
230   CORE_ENTER_ATOMIC();
231   irq_flag = RTCC_IntGet();
232 
233   if (irq_flag & RTCC_IF_OF) {
234     local_flag |= SLEEPTIMER_EVENT_OF;
235   }
236   if (irq_flag & RTCC_IF_CC1) {
237     local_flag |= SLEEPTIMER_EVENT_COMP;
238   }
239   RTCC_IntClear(irq_flag & (RTCC_IF_OF | RTCC_IF_CC1 | RTCC_IF_CC0));
240 
241   process_timer_irq(local_flag);
242 
243   CORE_EXIT_ATOMIC();
244 }
245 
246 /*******************************************************************************
247  * Gets RTCC timer frequency.
248  ******************************************************************************/
sleeptimer_hal_get_timer_frequency(void)249 uint32_t sleeptimer_hal_get_timer_frequency(void)
250 {
251   uint32_t frequency;
252   sl_clock_branch_t clock_branch;
253 
254   clock_branch = sl_device_peripheral_get_clock_branch(SL_PERIPHERAL_RTCC);
255   sl_clock_manager_get_clock_branch_frequency(clock_branch, &frequency);
256   return (frequency >> (sleeptimer_hal_presc_to_log2(SL_SLEEPTIMER_FREQ_DIVIDER - 1)));
257 }
258 
259 /*******************************************************************************
260  * Computes difference between two times taking into account timer wrap-around.
261  *
262  * @param a Time.
263  * @param b Time to substract from a.
264  *
265  * @return Time difference.
266  ******************************************************************************/
get_time_diff(uint32_t a,uint32_t b)267 __STATIC_INLINE uint32_t get_time_diff(uint32_t a,
268                                        uint32_t b)
269 {
270   return (a - b);
271 }
272 
273 /*******************************************************************************
274  * @brief
275  *   Gets the precision (in PPM) of the sleeptimer's clock.
276  *
277  * @return
278  *   Clock accuracy, in PPM.
279  *
280  ******************************************************************************/
sleeptimer_hal_get_clock_accuracy(void)281 uint16_t sleeptimer_hal_get_clock_accuracy(void)
282 {
283   uint16_t precision;
284   sl_clock_manager_get_clock_branch_precision(SL_CLOCK_BRANCH_RTCCCLK, &precision);
285   return precision;
286 }
287 
288 /*******************************************************************************
289  * Hardware Abstraction Layer to get the capture channel value.
290  ******************************************************************************/
sleeptimer_hal_get_capture(void)291 uint32_t sleeptimer_hal_get_capture(void)
292 {
293   // Invalid for RTCC peripheral
294   EFM_ASSERT(0);
295   return 0;
296 }
297 
298 /*******************************************************************************
299  * Hardware Abstraction Layer to reset PRS signal triggered by the associated
300  * peripheral.
301  ******************************************************************************/
sleeptimer_hal_reset_prs_signal(void)302 void sleeptimer_hal_reset_prs_signal(void)
303 {
304   // Invalid for RTCC peripheral
305   EFM_ASSERT(0);
306 }
307 
308 /***************************************************************************//**
309  * Set lowest energy mode based on a project's configurations and clock source
310  *
311  * @note If power_manager_no_deepsleep component is included in a project, the
312  *       lowest possible energy mode is EM1, else lowest energy mode is
313  *       determined by clock source.
314  ******************************************************************************/
315 #if defined(SL_CATALOG_POWER_MANAGER_PRESENT)
sli_sleeptimer_set_pm_em_requirement(void)316 void sli_sleeptimer_set_pm_em_requirement(void)
317 {
318 #if defined(_CMU_RTCCCLKCTRL_CLKSEL_MASK)
319   switch (CMU->RTCCCLKCTRL & _CMU_RTCCCLKCTRL_CLKSEL_MASK) {
320     case CMU_RTCCCLKCTRL_CLKSEL_LFRCO:
321     case CMU_RTCCCLKCTRL_CLKSEL_LFXO:
322       sl_power_manager_add_em_requirement(SL_POWER_MANAGER_EM2);
323       break;
324     default:
325       break;
326   }
327 #elif defined(_CMU_LFECLKEN0_RTCC_MASK)
328   switch ((CMU->LFECLKSEL & _CMU_LFECLKSEL_LFE_MASK) >> _CMU_LFECLKSEL_LFE_SHIFT) {
329     case CMU_LFECLKSEL_LFE_LFRCO:
330     case CMU_LFECLKSEL_LFE_LFXO:
331       sl_power_manager_add_em_requirement(SL_POWER_MANAGER_EM2);
332       break;
333     default:
334       break;
335   }
336 #elif defined(_CMU_LFACLKEN0_RTCC_MASK)
337   switch ((CMU->LFACLKSEL & _CMU_LFACLKSEL_LFA_MASK) >> _CMU_LFACLKSEL_LFA_SHIFT) {
338     case CMU_LFACLKSEL_LFA_LFRCO:
339     case CMU_LFACLKSEL_LFA_LFXO:
340       sl_power_manager_add_em_requirement(SL_POWER_MANAGER_EM2);
341       break;
342     default:
343       break;
344   }
345 #endif
346 }
347 #endif
348 #endif
349