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