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 > 0UL) && (div <= 32768UL));
66
67 /* Count leading zeroes and "reverse" result, Cortex-M3 intrinsic. */
68 log2 = (31UL - __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 ? 1UL : 0UL) << _BURTC_CFG_COMPTOP_SHIFT)
207 | ((burtcInit->debugRun ? 1UL : 0UL) << _BURTC_CFG_DEBUGRUN_SHIFT);
208 BURTC->EM4WUEN = ((burtcInit->em4comp ? 1UL : 0UL) << _BURTC_EM4WUEN_COMPEM4WUEN_SHIFT)
209 | ((burtcInit->em4overflow ? 1UL : 0UL) << _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_SYNCBUSY_EN_MASK)
248 regSync(BURTC_SYNCBUSY_EN);
249 #elif defined(_BURTC_EN_DISABLING_MASK)
250 while (BURTC->EN & _BURTC_EN_DISABLING_MASK) {
251 /* Wait for disabling to finish */
252 }
253 #endif
254 }
255 }
256 #elif defined(_SILICON_LABS_32B_SERIES_0)
257 /***************************************************************************//**
258 * @brief
259 * Enable or Disable BURTC peripheral reset and start counter
260 * @param[in] enable
261 * If true; asserts reset to BURTC, halts counter, if false; deassert reset
262 ******************************************************************************/
BURTC_Enable(bool enable)263 void BURTC_Enable(bool enable)
264 {
265 /* Note! If mode is disabled, BURTC counter will not start */
266 EFM_ASSERT(((enable == true)
267 && ((BURTC->CTRL & _BURTC_CTRL_MODE_MASK)
268 != BURTC_CTRL_MODE_DISABLE))
269 || (enable == false));
270 BUS_RegBitWrite(&BURTC->CTRL, _BURTC_CTRL_RSTEN_SHIFT, (uint32_t) !enable);
271 }
272 #endif
273
274 /***************************************************************************//**
275 * @brief Set BURTC compare channel.
276 *
277 * @param[in] comp Compare the channel index, must be 0 for current devices.
278 *
279 * @param[in] value New compare value.
280 ******************************************************************************/
BURTC_CompareSet(unsigned int comp,uint32_t value)281 void BURTC_CompareSet(unsigned int comp, uint32_t value)
282 {
283 (void) comp; /* Unused parameter when EFM_ASSERT is undefined. */
284
285 EFM_ASSERT(comp == 0U);
286
287 #if defined(_BURTC_COMP0_MASK)
288 /* Modification of COMP0 register requires sync with potential ongoing
289 * register updates in LF domain. */
290 regSync(BURTC_SYNCBUSY_COMP0);
291
292 /* Configure compare channel 0/. */
293 BURTC->COMP0 = value;
294 #else
295 /* Wait for last potential write to complete. */
296 regSync(BURTC_SYNCBUSY_COMP);
297
298 /* Configure compare channel 0 */
299 BURTC->COMP = value;
300 regSync(BURTC_SYNCBUSY_COMP);
301 #endif
302 }
303
304 /***************************************************************************//**
305 * @brief Get the BURTC compare value.
306 *
307 * @param[in] comp Compare the channel index value, must be 0 for Giant/Leopard Gecko.
308 *
309 * @return The currently configured value for this compare channel.
310 ******************************************************************************/
BURTC_CompareGet(unsigned int comp)311 uint32_t BURTC_CompareGet(unsigned int comp)
312 {
313 (void) comp; /* Unused parameter when EFM_ASSERT is undefined. */
314
315 EFM_ASSERT(comp == 0U);
316 #if defined(_BURTC_COMP0_MASK)
317 return BURTC->COMP0;
318 #else
319 return BURTC->COMP;
320 #endif
321 }
322
323 /***************************************************************************//**
324 * @brief Reset counter
325 ******************************************************************************/
BURTC_CounterReset(void)326 void BURTC_CounterReset(void)
327 {
328 #if defined(_BURTC_CTRL_MASK)
329 /* Set and clear reset bit */
330 BUS_RegBitWrite(&BURTC->CTRL, _BURTC_CTRL_RSTEN_SHIFT, 1U);
331 BUS_RegBitWrite(&BURTC->CTRL, _BURTC_CTRL_RSTEN_SHIFT, 0U);
332 #else
333 BURTC_Stop();
334 BURTC->CNT = 0U;
335 BURTC_Start();
336 #endif
337 }
338
339 /***************************************************************************//**
340 * @brief
341 * Restore BURTC to reset state.
342 * @note
343 * Before accessing the BURTC, BURSTEN in RMU->CTRL must be cleared.
344 * LOCK will not be reset to default value, as this will disable access
345 * to core BURTC registers.
346 ******************************************************************************/
BURTC_Reset(void)347 void BURTC_Reset(void)
348 {
349 #if defined(_SILICON_LABS_32B_SERIES_0)
350 bool buResetState;
351
352 /* Read reset state, set reset, and restore state. */
353 buResetState = BUS_RegBitRead(&RMU->CTRL, _RMU_CTRL_BURSTEN_SHIFT);
354 BUS_RegBitWrite(&RMU->CTRL, _RMU_CTRL_BURSTEN_SHIFT, 1);
355 BUS_RegBitWrite(&RMU->CTRL, _RMU_CTRL_BURSTEN_SHIFT, buResetState);
356 #elif defined(_SILICON_LABS_32B_SERIES_2)
357 if (BURTC->EN != 0U) {
358 BURTC_SyncWait();
359 }
360 BURTC->EN_SET = BURTC_EN_EN;
361 BURTC_Stop();
362 BURTC->CNT = 0x0;
363 BURTC->PRECNT = 0x0;
364 BURTC->COMP = 0x0;
365 BURTC->EM4WUEN = _BURTC_EM4WUEN_RESETVALUE;
366 BURTC->IEN = _BURTC_IEN_RESETVALUE;
367 BURTC->IF_CLR = _BURTC_IF_MASK;
368 /* Wait for all values to synchronize. BusFaults can happen if we don't
369 * do this before the enable bit is cleared. */
370 BURTC_SyncWait();
371 BURTC->EN_CLR = BURTC_EN_EN;
372 #if defined(_BURTC_SYNCBUSY_EN_MASK)
373 while (BURTC->SYNCBUSY != 0U) {
374 // Wait for the EN=0 to synchronize
375 }
376 #elif defined(_BURTC_EN_DISABLING_MASK)
377 while (BURTC->EN & _BURTC_EN_DISABLING_MASK) {
378 /* Wait for disabling to finish */
379 }
380 #endif
381 BURTC->CFG = _BURTC_CFG_RESETVALUE;
382 #endif
383 }
384
385 #if defined(_BURTC_CTRL_MASK)
386 /***************************************************************************//**
387 * @brief
388 * Get the clock frequency of the BURTC.
389 *
390 * @return
391 * The current frequency in Hz.
392 ******************************************************************************/
BURTC_ClockFreqGet(void)393 uint32_t BURTC_ClockFreqGet(void)
394 {
395 uint32_t clkSel;
396 uint32_t clkDiv;
397 uint32_t frequency;
398
399 clkSel = BURTC->CTRL & _BURTC_CTRL_CLKSEL_MASK;
400 clkDiv = (BURTC->CTRL & _BURTC_CTRL_PRESC_MASK) >> _BURTC_CTRL_PRESC_SHIFT;
401
402 switch (clkSel) {
403 /** Ultra-low frequency (1 kHz) clock. */
404 case BURTC_CTRL_CLKSEL_ULFRCO:
405 if (_BURTC_CTRL_PRESC_DIV1 == clkDiv) {
406 frequency = 2000; /* 2 kHz when clock divisor is 1. */
407 } else {
408 frequency = SystemULFRCOClockGet(); /* 1 kHz when divisor is different
409 from 1. */
410 }
411 break;
412
413 /** Low-frequency RC oscillator. */
414 case BURTC_CTRL_CLKSEL_LFRCO:
415 frequency = SystemLFRCOClockGet() / (1 << clkDiv); /* freq=32768/2^clkDiv */
416 break;
417
418 /** Low-frequency crystal oscillator. */
419 case BURTC_CTRL_CLKSEL_LFXO:
420 frequency = SystemLFXOClockGet() / (1 << clkDiv); /* freq=32768/2^clkDiv */
421 break;
422
423 default:
424 /* No clock selected for BURTC. */
425 frequency = 0;
426 }
427 return frequency;
428 }
429 #endif
430
431 /** @} (end addtogroup burtc) */
432
433 #endif /* BURTC_PRESENT */
434