1 /***************************************************************************//**
2 * @file
3 * @brief Backup Real Time Counter (BURTC) Peripheral API
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 "em_burtc.h"
32 #if defined(BURTC_PRESENT)
33
34 /***************************************************************************//**
35 * @addtogroup burtc BURTC - Backup RTC
36 * @brief Backup Real Time Counter (BURTC) Peripheral API
37 * @details
38 * This module contains functions to control the BURTC peripheral of Silicon
39 * Labs 32-bit MCUs. The Backup Real Time Counter allows timekeeping in all
40 * energy modes. The Backup RTC is also available when the system is in backup
41 * mode.
42 * @{
43 ******************************************************************************/
44
45 /*******************************************************************************
46 ******************************* DEFINES ***********************************
47 ******************************************************************************/
48
49 /*******************************************************************************
50 ************************** LOCAL FUNCTIONS ********************************
51 ******************************************************************************/
52
53 /** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
54 /***************************************************************************//**
55 * @brief Convert dividend to a prescaler logarithmic value. Only works for even
56 * numbers equal to 2^n.
57 * @param[in] div Unscaled dividend,
58 * @return Base 2 logarithm of input, as used by fixed prescalers.
59 ******************************************************************************/
divToLog2(uint32_t div)60 __STATIC_INLINE uint32_t divToLog2(uint32_t div)
61 {
62 uint32_t log2;
63
64 /* Prescaler accepts an argument of 128 or less, valid values being 2^n. */
65 EFM_ASSERT((div > 0) && (div <= 32768));
66
67 /* Count leading zeroes and "reverse" result, Cortex-M3 intrinsic. */
68 log2 = (31 - __CLZ(div));
69
70 return log2;
71 }
72
73 /***************************************************************************//**
74 * @brief
75 * Wait for an ongoing sync of register(s) to low frequency domain to complete.
76 *
77 * @param[in] mask
78 * A bitmask corresponding to SYNCBUSY register defined bits, indicating
79 * registers that must complete any ongoing synchronization.
80 ******************************************************************************/
regSync(uint32_t mask)81 __STATIC_INLINE void regSync(uint32_t mask)
82 {
83 #if defined(_BURTC_FREEZE_MASK)
84 /* Avoid deadlock if modifying the same register twice when freeze mode is
85 activated or when a clock is not selected for the BURTC. If a clock is
86 not selected, then the sync is done once the clock source is set. */
87 if ((BURTC->FREEZE & BURTC_FREEZE_REGFREEZE)
88 || ((BURTC->CTRL & _BURTC_CTRL_CLKSEL_MASK) == BURTC_CTRL_CLKSEL_NONE)
89 || ((BURTC->CTRL & _BURTC_CTRL_RSTEN_MASK) == BURTC_CTRL_RSTEN)) {
90 return;
91 }
92 #endif
93
94 /* Wait for any pending previous write operation to complete */
95 /* in low frequency domain. This is only required for the Gecko Family. */
96 while ((BURTC->SYNCBUSY & mask) != 0U)
97 ;
98 }
99 /** @endcond */
100
101 /*******************************************************************************
102 ************************** GLOBAL FUNCTIONS *******************************
103 ******************************************************************************/
104
105 /***************************************************************************//**
106 * @brief Initialize BURTC.
107 *
108 * @details
109 * Configures the BURTC peripheral.
110 *
111 * @note
112 * Before initialization, BURTC module must first be enabled by clearing the
113 * reset bit in the RMU, i.e.,
114 * @verbatim
115 * RMU_ResetControl(rmuResetBU, rmuResetModeClear);
116 * @endverbatim
117 * Compare channel 0 must be configured outside this function, before
118 * initialization if enable is set to true. The counter will always be reset.
119 *
120 * @param[in] burtcInit
121 * A pointer to the BURTC initialization structure.
122 ******************************************************************************/
BURTC_Init(const BURTC_Init_TypeDef * burtcInit)123 void BURTC_Init(const BURTC_Init_TypeDef *burtcInit)
124 {
125 #if defined(_SILICON_LABS_32B_SERIES_0)
126 uint32_t ctrl;
127 uint32_t presc;
128
129 /* Check initializer structure integrity. */
130 EFM_ASSERT(burtcInit != (BURTC_Init_TypeDef *) 0);
131 /* Clock divider must be between 1 and 128, really on the form 2^n. */
132 EFM_ASSERT((burtcInit->clkDiv >= 1) && (burtcInit->clkDiv <= 128));
133
134 /* Ignored compare bits during low power operation must be less than 7. */
135 /* Note! Giant Gecko revision C errata, do NOT use LPCOMP=7. */
136 EFM_ASSERT(burtcInit->lowPowerComp <= 6);
137 /* You cannot enable the BURTC if mode is set to disabled. */
138 EFM_ASSERT((burtcInit->enable == false)
139 || ((burtcInit->enable == true)
140 && (burtcInit->mode != burtcModeDisable)));
141 /* Low power mode is only available with LFRCO or LFXO as clock source. */
142 EFM_ASSERT((burtcInit->clkSel != burtcClkSelULFRCO)
143 || ((burtcInit->clkSel == burtcClkSelULFRCO)
144 && (burtcInit->lowPowerMode == burtcLPDisable)));
145
146 /* Calculate a prescaler value from the clock divider input. */
147 /* Note! If clock select (clkSel) is ULFRCO, a clock divisor (clkDiv) of
148 value 1 will select a 2 kHz ULFRCO clock, while any other value will
149 select a 1 kHz ULFRCO clock source. */
150 presc = divToLog2(burtcInit->clkDiv);
151
152 /* Make sure all registers are updated simultaneously. */
153 if (burtcInit->enable) {
154 BURTC_FreezeEnable(true);
155 }
156
157 /* Modification of LPMODE register requires sync with potential ongoing
158 * register updates in LF domain. */
159 regSync(BURTC_SYNCBUSY_LPMODE);
160
161 /* Configure low power mode. */
162 BURTC->LPMODE = (uint32_t) (burtcInit->lowPowerMode);
163
164 /* New configuration. */
165 ctrl = (BURTC_CTRL_RSTEN
166 | (burtcInit->mode)
167 | (burtcInit->debugRun << _BURTC_CTRL_DEBUGRUN_SHIFT)
168 | (burtcInit->compare0Top << _BURTC_CTRL_COMP0TOP_SHIFT)
169 | (burtcInit->lowPowerComp << _BURTC_CTRL_LPCOMP_SHIFT)
170 | (presc << _BURTC_CTRL_PRESC_SHIFT)
171 | (burtcInit->clkSel)
172 | (burtcInit->timeStamp << _BURTC_CTRL_BUMODETSEN_SHIFT));
173
174 /* Clear interrupts. */
175 BURTC_IntClear(0xFFFFFFFF);
176
177 /* Set the new configuration. */
178 BURTC->CTRL = ctrl;
179
180 /* Enable BURTC and counter. */
181 if (burtcInit->enable) {
182 /* To enable BURTC counter, disable reset. */
183 BURTC_Enable(true);
184
185 /* Clear freeze. */
186 BURTC_FreezeEnable(false);
187 }
188 #elif defined(_SILICON_LABS_32B_SERIES_2)
189 uint32_t presc;
190
191 presc = divToLog2(burtcInit->clkDiv);
192
193 if (BURTC->EN != 0U) {
194 BURTC_SyncWait();
195 }
196 BURTC->EN_CLR = BURTC_EN_EN;
197 #if defined(_BURTC_SYNCBUSY_EN_MASK)
198 regSync(BURTC_SYNCBUSY_EN);
199 #elif defined(_BURTC_EN_DISABLING_MASK)
200 while (BURTC->EN & _BURTC_EN_DISABLING_MASK) {
201 /* Wait for disabling to finish */
202 }
203 #endif
204
205 BURTC->CFG = (presc << _BURTC_CFG_CNTPRESC_SHIFT)
206 | ((burtcInit->compare0Top ? 1U : 0U) << _BURTC_CFG_COMPTOP_SHIFT)
207 | ((burtcInit->debugRun ? 1U : 0U) << _BURTC_CFG_DEBUGRUN_SHIFT);
208 BURTC->EM4WUEN = ((burtcInit->em4comp ? 1U : 0U) << _BURTC_EM4WUEN_COMPEM4WUEN_SHIFT)
209 | ((burtcInit->em4overflow ? 1U : 0U) << _BURTC_EM4WUEN_OFEM4WUEN_SHIFT);
210 BURTC->EN_SET = BURTC_EN_EN;
211 if (burtcInit->start) {
212 BURTC_Start();
213 }
214 #endif
215 }
216
217 #if defined(_SILICON_LABS_32B_SERIES_2)
218 /***************************************************************************//**
219 * @brief
220 * Enable or Disable BURTC peripheral.
221 *
222 * @param[in] enable
223 * true to enable, false to disable.
224 ******************************************************************************/
BURTC_Enable(bool enable)225 void BURTC_Enable(bool enable)
226 {
227 #if defined(_BURTC_SYNCBUSY_EN_MASK)
228 regSync(BURTC_SYNCBUSY_EN);
229 #endif
230
231 if ((BURTC->EN == 0U) && !enable) {
232 /* Trying to disable BURTC when it's already disabled */
233 return;
234 }
235
236 if (BURTC->EN != 0U) {
237 /* Modifying the enable bit while synchronization is active will BusFault */
238 BURTC_SyncWait();
239 }
240
241 if (enable) {
242 BURTC->EN_SET = BURTC_EN_EN;
243 } else {
244 BURTC_Stop();
245 BURTC_SyncWait(); /* Wait for the stop to synchronize */
246 BURTC->EN_CLR = BURTC_EN_EN;
247 #if defined(_BURTC_EN_DISABLING_MASK)
248 while (BURTC->EN & _BURTC_EN_DISABLING_MASK) {
249 /* Wait for disabling to finish */
250 }
251 #endif
252 }
253 }
254 #elif defined(_SILICON_LABS_32B_SERIES_0)
255 /***************************************************************************//**
256 * @brief
257 * Enable or Disable BURTC peripheral reset and start counter
258 * @param[in] enable
259 * If true; asserts reset to BURTC, halts counter, if false; deassert reset
260 ******************************************************************************/
BURTC_Enable(bool enable)261 void BURTC_Enable(bool enable)
262 {
263 /* Note! If mode is disabled, BURTC counter will not start */
264 EFM_ASSERT(((enable == true)
265 && ((BURTC->CTRL & _BURTC_CTRL_MODE_MASK)
266 != BURTC_CTRL_MODE_DISABLE))
267 || (enable == false));
268 BUS_RegBitWrite(&BURTC->CTRL, _BURTC_CTRL_RSTEN_SHIFT, (uint32_t) !enable);
269 }
270 #endif
271
272 /***************************************************************************//**
273 * @brief Set BURTC compare channel.
274 *
275 * @param[in] comp Compare the channel index, must be 0 for current devices.
276 *
277 * @param[in] value New compare value.
278 ******************************************************************************/
BURTC_CompareSet(unsigned int comp,uint32_t value)279 void BURTC_CompareSet(unsigned int comp, uint32_t value)
280 {
281 (void) comp; /* Unused parameter when EFM_ASSERT is undefined. */
282
283 EFM_ASSERT(comp == 0U);
284
285 #if defined(_BURTC_COMP0_MASK)
286 /* Modification of COMP0 register requires sync with potential ongoing
287 * register updates in LF domain. */
288 regSync(BURTC_SYNCBUSY_COMP0);
289
290 /* Configure compare channel 0/. */
291 BURTC->COMP0 = value;
292 #else
293 /* Wait for last potential write to complete. */
294 regSync(BURTC_SYNCBUSY_COMP);
295
296 /* Configure compare channel 0 */
297 BURTC->COMP = value;
298 regSync(BURTC_SYNCBUSY_COMP);
299 #endif
300 }
301
302 /***************************************************************************//**
303 * @brief Get the BURTC compare value.
304 *
305 * @param[in] comp Compare the channel index value, must be 0 for Giant/Leopard Gecko.
306 *
307 * @return The currently configured value for this compare channel.
308 ******************************************************************************/
BURTC_CompareGet(unsigned int comp)309 uint32_t BURTC_CompareGet(unsigned int comp)
310 {
311 (void) comp; /* Unused parameter when EFM_ASSERT is undefined. */
312
313 EFM_ASSERT(comp == 0U);
314 #if defined(_BURTC_COMP0_MASK)
315 return BURTC->COMP0;
316 #else
317 return BURTC->COMP;
318 #endif
319 }
320
321 /***************************************************************************//**
322 * @brief Reset counter
323 ******************************************************************************/
BURTC_CounterReset(void)324 void BURTC_CounterReset(void)
325 {
326 #if defined(_BURTC_CTRL_MASK)
327 /* Set and clear reset bit */
328 BUS_RegBitWrite(&BURTC->CTRL, _BURTC_CTRL_RSTEN_SHIFT, 1U);
329 BUS_RegBitWrite(&BURTC->CTRL, _BURTC_CTRL_RSTEN_SHIFT, 0U);
330 #else
331 BURTC_Stop();
332 BURTC->CNT = 0U;
333 BURTC_Start();
334 #endif
335 }
336
337 /***************************************************************************//**
338 * @brief
339 * Restore BURTC to reset state.
340 * @note
341 * Before accessing the BURTC, BURSTEN in RMU->CTRL must be cleared.
342 * LOCK will not be reset to default value, as this will disable access
343 * to core BURTC registers.
344 ******************************************************************************/
BURTC_Reset(void)345 void BURTC_Reset(void)
346 {
347 #if defined(_SILICON_LABS_32B_SERIES_0)
348 bool buResetState;
349
350 /* Read reset state, set reset, and restore state. */
351 buResetState = BUS_RegBitRead(&RMU->CTRL, _RMU_CTRL_BURSTEN_SHIFT);
352 BUS_RegBitWrite(&RMU->CTRL, _RMU_CTRL_BURSTEN_SHIFT, 1);
353 BUS_RegBitWrite(&RMU->CTRL, _RMU_CTRL_BURSTEN_SHIFT, buResetState);
354 #elif defined(_SILICON_LABS_32B_SERIES_2)
355 if (BURTC->EN != 0U) {
356 BURTC_SyncWait();
357 }
358 BURTC->EN_SET = BURTC_EN_EN;
359 BURTC_Stop();
360 BURTC->CNT = 0x0;
361 BURTC->PRECNT = 0x0;
362 BURTC->COMP = 0x0;
363 BURTC->EM4WUEN = _BURTC_EM4WUEN_RESETVALUE;
364 BURTC->IEN = _BURTC_IEN_RESETVALUE;
365 BURTC->IF_CLR = _BURTC_IF_MASK;
366 /* Wait for all values to synchronize. BusFaults can happen if we don't
367 * do this before the enable bit is cleared. */
368 BURTC_SyncWait();
369 BURTC->EN_CLR = BURTC_EN_EN;
370 #if defined(_BURTC_SYNCBUSY_EN_MASK)
371 while (BURTC->SYNCBUSY != 0U) {
372 // Wait for the EN=0 to synchronize
373 }
374 #elif defined(_BURTC_EN_DISABLING_MASK)
375 while (BURTC->EN & _BURTC_EN_DISABLING_MASK) {
376 /* Wait for disabling to finish */
377 }
378 #endif
379 BURTC->CFG = _BURTC_CFG_RESETVALUE;
380 #endif
381 }
382
383 #if defined(_BURTC_CTRL_MASK)
384 /***************************************************************************//**
385 * @brief
386 * Get the clock frequency of the BURTC.
387 *
388 * @return
389 * The current frequency in Hz.
390 ******************************************************************************/
BURTC_ClockFreqGet(void)391 uint32_t BURTC_ClockFreqGet(void)
392 {
393 uint32_t clkSel;
394 uint32_t clkDiv;
395 uint32_t frequency;
396
397 clkSel = BURTC->CTRL & _BURTC_CTRL_CLKSEL_MASK;
398 clkDiv = (BURTC->CTRL & _BURTC_CTRL_PRESC_MASK) >> _BURTC_CTRL_PRESC_SHIFT;
399
400 switch (clkSel) {
401 /** Ultra-low frequency (1 kHz) clock. */
402 case BURTC_CTRL_CLKSEL_ULFRCO:
403 if (_BURTC_CTRL_PRESC_DIV1 == clkDiv) {
404 frequency = 2000; /* 2 kHz when clock divisor is 1. */
405 } else {
406 frequency = SystemULFRCOClockGet(); /* 1 kHz when divisor is different
407 from 1. */
408 }
409 break;
410
411 /** Low-frequency RC oscillator. */
412 case BURTC_CTRL_CLKSEL_LFRCO:
413 frequency = SystemLFRCOClockGet() / (1 << clkDiv); /* freq=32768/2^clkDiv */
414 break;
415
416 /** Low-frequency crystal oscillator. */
417 case BURTC_CTRL_CLKSEL_LFXO:
418 frequency = SystemLFXOClockGet() / (1 << clkDiv); /* freq=32768/2^clkDiv */
419 break;
420
421 default:
422 /* No clock selected for BURTC. */
423 frequency = 0;
424 }
425 return frequency;
426 }
427 #endif
428
429 /** @} (end addtogroup burtc) */
430
431 #endif /* BURTC_PRESENT */
432