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