1 /***************************************************************************//**
2  * @file
3  * @brief SLEEPTIMER hardware abstraction implementation for SYSRTC.
4  *******************************************************************************
5  * # License
6  * <b>Copyright 2018 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 #include "peripheral_sysrtc.h"
32 #include "sl_sleeptimer.h"
33 #include "sli_sleeptimer_hal.h"
34 #if !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT) && defined(SL_CATALOG_POWER_MANAGER_PRESENT)
35 #include "sli_hfxo_manager.h"
36 #endif
37 #include "em_core.h"
38 #include "em_cmu.h"
39 #include "em_prs.h"
40 
41 #if defined(SL_CATALOG_POWER_MANAGER_PRESENT)
42 #include "sl_power_manager.h"
43 #endif
44 
45 #if SL_SLEEPTIMER_PERIPHERAL == SL_SLEEPTIMER_PERIPHERAL_SYSRTC
46 
47 // Minimum difference between current count value and what the comparator of the timer can be set to.
48 // 1 tick is added to the minimum diff for the algorithm of compensation for the IRQ handler that
49 // triggers when CNT == compare_value + 1. For more details refer to sleeptimer_hal_set_compare() function's header.
50 #define SLEEPTIMER_COMPARE_MIN_DIFF  (2 + 1)
51 
52 #define SLEEPTIMER_TMR_WIDTH (_SYSRTC_CNT_MASK)
53 
54 static bool cc_disabled = true;
55 
56 static bool cc1_disabled = true;
57 
58 __STATIC_INLINE uint32_t get_time_diff(uint32_t a,
59                                        uint32_t b);
60 
61 /******************************************************************************
62  * Initializes SYSRTC sleep timer.
63  *****************************************************************************/
sleeptimer_hal_init_timer(void)64 void sleeptimer_hal_init_timer(void)
65 {
66   sl_sysrtc_config_t sysrtc_config = SYSRTC_CONFIG_DEFAULT;
67   sl_sysrtc_group_config_t group_config = SYSRTC_GROUP_CONFIG_DEFAULT;
68   const sl_sysrtc_group_channel_compare_config_t group_compare_channel_config = SYSRTC_GROUP_CHANNEL_COMPARE_CONFIG_EARLY_WAKEUP;
69 
70   CMU_ClockEnable(cmuClock_SYSRTC, true);
71 
72 #if (SL_SLEEPTIMER_DEBUGRUN == 1)
73   sysrtc_config.enable_debug_run = true;
74 #endif
75 
76   sl_sysrtc_init(&sysrtc_config);
77 
78   group_config.compare_channel0_enable = false;
79   group_config.compare_channel1_enable = false;
80   group_config.p_compare_channel1_config = &group_compare_channel_config;
81   sl_sysrtc_init_group(0u, &group_config);
82 
83   sl_sysrtc_disable_group_interrupts(0u, _SYSRTC_GRP0_IEN_MASK);
84   sl_sysrtc_clear_group_interrupts(0u, _SYSRTC_GRP0_IF_MASK);
85   sl_sysrtc_enable();
86   sl_sysrtc_set_counter(0u);
87 
88   NVIC_ClearPendingIRQ(SYSRTC_APP_IRQn);
89   NVIC_EnableIRQ(SYSRTC_APP_IRQn);
90 
91   // Initialize PRS to start HFXO for early wakeup
92   CMU_ClockEnable(cmuClock_PRS, true);
93   PRS_ConnectSignal(1UL, prsTypeAsync, prsSignalSYSRTC0_GRP0OUT1);
94   PRS_ConnectConsumer(1UL, prsTypeAsync, prsConsumerHFXO0_OSCREQ);
95 }
96 
97 /******************************************************************************
98  * Gets SYSRTC counter value.
99  *****************************************************************************/
sleeptimer_hal_get_counter(void)100 uint32_t sleeptimer_hal_get_counter(void)
101 {
102   return sl_sysrtc_get_counter();
103 }
104 
105 /******************************************************************************
106  * Gets SYSRTC channel zero's compare value.
107  *****************************************************************************/
sleeptimer_hal_get_compare(void)108 uint32_t sleeptimer_hal_get_compare(void)
109 {
110   return sl_sysrtc_get_group_compare_channel_value(0u, 0u);
111 }
112 
113 /******************************************************************************
114  * Sets SYSRTC channel zero's compare value.
115  *
116  * @note Compare match value is set to the requested value - 1. This is done
117  * to compensate for the fact that the BURTC compare match interrupt always
118  * triggers at the end of the requested ticks and that the IRQ handler is
119  * executed when current tick count == compare_value + 1.
120  *****************************************************************************/
sleeptimer_hal_set_compare(uint32_t value)121 void sleeptimer_hal_set_compare(uint32_t value)
122 {
123   CORE_DECLARE_IRQ_STATE;
124   uint32_t counter;
125   uint32_t compare;
126   uint32_t compare_value = value;
127 
128   CORE_ENTER_CRITICAL();
129   counter = sleeptimer_hal_get_counter();
130   compare = sleeptimer_hal_get_compare();
131 
132   if (((sl_sysrtc_get_group_interrupts(0u) & SYSRTC_GRP0_IEN_CMP0) != 0)
133       || get_time_diff(compare, counter) > SLEEPTIMER_COMPARE_MIN_DIFF
134       || compare == counter) {
135     // Add margin if necessary
136     if (get_time_diff(compare_value, counter) < SLEEPTIMER_COMPARE_MIN_DIFF) {
137       compare_value = counter + SLEEPTIMER_COMPARE_MIN_DIFF;
138     }
139     compare_value %= SLEEPTIMER_TMR_WIDTH;
140 
141     sl_sysrtc_set_group_compare_channel_value(0u, 0u, compare_value - 1);
142     sleeptimer_hal_enable_int(SLEEPTIMER_EVENT_COMP);
143   }
144   CORE_EXIT_CRITICAL();
145 
146   if (cc_disabled) {
147     SYSRTC0->GRP0_CTRL |= SYSRTC_GRP0_CTRL_CMP0EN;
148     cc_disabled = false;
149   }
150 }
151 
152 /*******************************************************************************
153  * Sets SYSRTC channel one's compare value.
154  *
155  * @note Compare match value is set to the requested value - 1. This is done
156  * to compensate for the fact that the BURTC compare match interrupt always
157  * triggers at the end of the requested ticks and that the IRQ handler is
158  * executed when current tick count == compare_value + 1.
159  ******************************************************************************/
sleeptimer_hal_set_compare_prs_hfxo_startup(int32_t value)160 void sleeptimer_hal_set_compare_prs_hfxo_startup(int32_t value)
161 {
162   CORE_DECLARE_IRQ_STATE;
163   uint32_t counter;
164   uint32_t compare_value;
165 
166   CORE_ENTER_CRITICAL();
167 
168   counter = sleeptimer_hal_get_counter();
169 
170   compare_value = value + counter;
171 
172   // Add margin if necessary
173   if (get_time_diff(compare_value, counter) < SLEEPTIMER_COMPARE_MIN_DIFF) {
174     compare_value = counter + SLEEPTIMER_COMPARE_MIN_DIFF;
175   }
176 
177 #if !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT) && defined(SL_CATALOG_POWER_MANAGER_PRESENT)
178   sli_hfxo_prs_manager_begin_startup_measurement(compare_value);
179 #endif
180 
181   compare_value %= SLEEPTIMER_TMR_WIDTH;
182 
183   sl_sysrtc_set_group_compare_channel_value(0u, 1u, compare_value - 1);
184 
185   CORE_EXIT_CRITICAL();
186 
187   if (cc1_disabled) {
188     SYSRTC0->GRP0_CTRL |= SYSRTC_GRP0_CTRL_CMP1EN;
189     cc1_disabled = false;
190   }
191 }
192 
193 /******************************************************************************
194  * Enables SYSRTC interrupts.
195  *****************************************************************************/
sleeptimer_hal_enable_int(uint8_t local_flag)196 void sleeptimer_hal_enable_int(uint8_t local_flag)
197 {
198   uint32_t sysrtc_ien = 0u;
199 
200   if (local_flag & SLEEPTIMER_EVENT_OF) {
201     sysrtc_ien |= SYSRTC_GRP0_IEN_OVF;
202   }
203 
204   if (local_flag & SLEEPTIMER_EVENT_COMP) {
205     sysrtc_ien |= SYSRTC_GRP0_IEN_CMP0;
206   }
207 
208   sl_sysrtc_enable_group_interrupts(0u, sysrtc_ien);
209 }
210 
211 /******************************************************************************
212  * Disables SYSRTC interrupts.
213  *****************************************************************************/
sleeptimer_hal_disable_int(uint8_t local_flag)214 void sleeptimer_hal_disable_int(uint8_t local_flag)
215 {
216   uint32_t sysrtc_int_dis = 0u;
217 
218   if (local_flag & SLEEPTIMER_EVENT_OF) {
219     sysrtc_int_dis |= SYSRTC_GRP0_IEN_OVF;
220   }
221 
222   if (local_flag & SLEEPTIMER_EVENT_COMP) {
223     sysrtc_int_dis |= SYSRTC_GRP0_IEN_CMP0;
224 
225     cc_disabled = true;
226     SYSRTC0->GRP0_CTRL &= ~_SYSRTC_GRP0_CTRL_CMP0EN_MASK;
227   }
228 
229   sl_sysrtc_disable_group_interrupts(0u, sysrtc_int_dis);
230 }
231 
232 /*******************************************************************************
233  * Hardware Abstraction Layer to set timer interrupts.
234  ******************************************************************************/
sleeptimer_hal_set_int(uint8_t local_flag)235 void sleeptimer_hal_set_int(uint8_t local_flag)
236 {
237   if (local_flag & SLEEPTIMER_EVENT_COMP) {
238     SYSRTC0->GRP0_IF_SET = SYSRTC_GRP0_IF_CMP0;
239   }
240 }
241 
242 /******************************************************************************
243  * Gets status of specified interrupt.
244  *
245  * Note: This function must be called with interrupts disabled.
246  *****************************************************************************/
sli_sleeptimer_hal_is_int_status_set(uint8_t local_flag)247 bool sli_sleeptimer_hal_is_int_status_set(uint8_t local_flag)
248 {
249   bool int_is_set = false;
250   uint32_t irq_flag = sl_sysrtc_get_group_interrupts(0u);
251 
252   switch (local_flag) {
253     case SLEEPTIMER_EVENT_COMP:
254       int_is_set = ((irq_flag & SYSRTC_GRP0_IF_CMP0) == SYSRTC_GRP0_IF_CMP0);
255       break;
256 
257     case SLEEPTIMER_EVENT_OF:
258       int_is_set = ((irq_flag & SYSRTC_GRP0_IF_OVF) == SYSRTC_GRP0_IF_OVF);
259       break;
260 
261     default:
262       break;
263   }
264 
265   return int_is_set;
266 }
267 
268 /*******************************************************************************
269  * SYSRTC interrupt handler.
270  ******************************************************************************/
SYSRTC_APP_IRQHandler(void)271 void SYSRTC_APP_IRQHandler(void)
272 {
273   CORE_DECLARE_IRQ_STATE;
274   uint8_t local_flag = 0;
275   uint32_t irq_flag;
276 
277   CORE_ENTER_ATOMIC();
278   irq_flag = sl_sysrtc_get_group_interrupts(0u);
279 
280   if (irq_flag & SYSRTC_GRP0_IF_OVF) {
281     local_flag |= SLEEPTIMER_EVENT_OF;
282   }
283 
284   if (irq_flag & SYSRTC_GRP0_IF_CMP0) {
285     local_flag |= SLEEPTIMER_EVENT_COMP;
286   }
287   sl_sysrtc_clear_group_interrupts(0u, irq_flag & (SYSRTC_GRP0_IF_OVF | SYSRTC_GRP0_IF_CMP0));
288 
289   process_timer_irq(local_flag);
290 
291   CORE_EXIT_ATOMIC();
292 }
293 
294 /*******************************************************************************
295  * Gets SYSRTC timer frequency.
296  ******************************************************************************/
sleeptimer_hal_get_timer_frequency(void)297 uint32_t sleeptimer_hal_get_timer_frequency(void)
298 {
299   return (CMU_ClockFreqGet(cmuClock_SYSRTC));
300 }
301 
302 /*******************************************************************************
303  * Computes difference between two times taking into account timer wrap-around.
304  *
305  * @param a Time.
306  * @param b Time to substract from a.
307  *
308  * @return Time difference.
309  ******************************************************************************/
get_time_diff(uint32_t a,uint32_t b)310 __STATIC_INLINE uint32_t get_time_diff(uint32_t a,
311                                        uint32_t b)
312 {
313   return (a - b);
314 }
315 
316 /*******************************************************************************
317  * @brief
318  *   Gets the precision (in PPM) of the sleeptimer's clock.
319  *
320  * @return
321  *   Clock accuracy, in PPM.
322  *
323  ******************************************************************************/
sleeptimer_hal_get_clock_accuracy(void)324 uint16_t sleeptimer_hal_get_clock_accuracy(void)
325 {
326   return CMU_LF_ClockPrecisionGet(cmuClock_SYSRTC);
327 }
328 
329 /***************************************************************************//**
330  * Set lowest energy mode based on a project's configurations and clock source
331  *
332  * @note If power_manager_no_deepsleep component is included in a project, the
333  *       lowest possible energy mode is EM1, else lowest energy mode is
334  *       determined by clock source.
335  ******************************************************************************/
336 #if defined(SL_CATALOG_POWER_MANAGER_PRESENT)
sli_sleeptimer_set_pm_em_requirement(void)337 void sli_sleeptimer_set_pm_em_requirement(void)
338 {
339   switch (CMU->SYSRTC0CLKCTRL & _CMU_SYSRTC0CLKCTRL_CLKSEL_MASK) {
340     case CMU_SYSRTC0CLKCTRL_CLKSEL_LFRCO:
341     case CMU_SYSRTC0CLKCTRL_CLKSEL_LFXO:
342       sl_power_manager_add_em_requirement(SL_POWER_MANAGER_EM2);
343       break;
344     default:
345       break;
346   }
347 }
348 #endif
349 #endif
350