1 /***************************************************************************//**
2  * @file
3  * @brief Watchdog (WDOG) 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_wdog.h"
32 #if defined(WDOG_COUNT) && (WDOG_COUNT > 0)
33 
34 #include "em_bus.h"
35 #include "em_core.h"
36 
37 /***************************************************************************//**
38  * @addtogroup wdog WDOG - Watchdog
39  * @brief Watchdog (WDOG) Peripheral API
40  * @details
41  *  This module contains functions to control the WDOG peripheral of Silicon
42  *  Labs 32-bit MCUs and SoCs. The WDOG resets the system in case of a fault
43  *  condition.
44  * @{
45  ******************************************************************************/
46 
47 /** In some scenarioes when the watchdog is disabled the synchronization
48  * register might be set and not be cleared until the watchdog is enabled
49  * again. This will happen when for instance some watchdog register is modified
50  * while the watchdog clock is disabled. In these scenarioes we need to make
51  * sure that the software does not wait forever. */
52 #define WDOG_SYNC_TIMEOUT  30000
53 
54 /*******************************************************************************
55  **************************   GLOBAL FUNCTIONS   *******************************
56  ******************************************************************************/
57 
58 /***************************************************************************//**
59  * @brief
60  *   Enable/disable the watchdog timer.
61  *
62  * @note
63  *   This function modifies the WDOG CTRL register which requires
64  *   synchronization into the low-frequency domain. If this register is modified
65  *   before a previous update to the same register has completed, this function
66  *   will stall until the previous synchronization has completed.
67  *
68  * @param[in] wdog
69  *   A pointer to the WDOG peripheral register block.
70  *
71  * @param[in] enable
72  *   True to enable Watchdog, false to disable. Watchdog cannot be disabled if
73  *   it's been locked.
74  ******************************************************************************/
WDOGn_Enable(WDOG_TypeDef * wdog,bool enable)75 void WDOGn_Enable(WDOG_TypeDef *wdog, bool enable)
76 {
77   // SYNCBUSY may stall when locked.
78 #if defined(_WDOG_STATUS_MASK)
79   if ((wdog->STATUS & _WDOG_STATUS_LOCK_MASK) == WDOG_STATUS_LOCK_LOCKED) {
80     return;
81   }
82 #else
83   if (wdog->CTRL & WDOG_CTRL_LOCK) {
84     return;
85   }
86 #endif
87 
88 #if defined(_WDOG_EN_MASK)
89   if (!enable) {
90     while (wdog->SYNCBUSY & WDOG_SYNCBUSY_CMD) {
91     }
92     wdog->EN_CLR = WDOG_EN_EN;
93 #if defined(_WDOG_EN_DISABLING_MASK)
94     while (wdog->EN & _WDOG_EN_DISABLING_MASK) {
95     }
96 #endif
97   } else {
98     wdog->EN_SET = WDOG_EN_EN;
99   }
100 #else
101   // Wait for previous operations/modifications to complete
102   int i = 0;
103   while (((wdog->SYNCBUSY & WDOG_SYNCBUSY_CTRL) != 0U)
104          && (i < WDOG_SYNC_TIMEOUT)) {
105     i++;
106   }
107 
108   bool wdogState = ((wdog->CTRL & _WDOG_CTRL_EN_MASK) != 0U);
109 
110   // Make sure to only write to the CTRL register if we are changing mode
111   if (wdogState != enable) {
112     BUS_RegBitWrite(&wdog->CTRL, _WDOG_CTRL_EN_SHIFT, enable);
113   }
114 #endif
115 }
116 
117 /***************************************************************************//**
118  * @brief
119  *   Feed WDOG.
120  *
121  * @details
122  *   When WDOG is activated, it must be fed (i.e., clearing the counter)
123  *   before it reaches the defined timeout period. Otherwise, WDOG
124  *   will generate a reset.
125  *
126  * @note
127  *   Note that WDOG is an asynchronous peripheral and when calling the
128  *   WDOGn_Feed() function the hardware starts the process of clearing the
129  *   counter. This process takes some time before it completes depending on the
130  *   selected oscillator (up to 4 peripheral clock cycles). When using the
131  *   ULFRCO for instance as the oscillator the watchdog runs on a 1 kHz clock
132  *   and a watchdog clear operation might take up to 4 ms.
133  *
134  *   If the device enters EM2 or EM3 while a command is in progress then that
135  *   command will be aborted. An application can use @ref WDOGn_SyncWait()
136  *   to wait for a command to complete.
137  *
138  * @param[in] wdog
139  *   A pointer to the WDOG peripheral register block.
140  ******************************************************************************/
WDOGn_Feed(WDOG_TypeDef * wdog)141 void WDOGn_Feed(WDOG_TypeDef *wdog)
142 {
143 #if (_SILICON_LABS_32B_SERIES < 2)
144 
145   // WDOG should not be fed while it is disabled.
146   if (!(wdog->CTRL & WDOG_CTRL_EN)) {
147     return;
148   }
149 
150   // If a previous clearing is synchronized to the LF domain, there
151   // is no point in waiting for it to complete before clearing over again.
152   // This avoids stalling the core in the typical use case where some idle loop
153   // keeps clearing WDOG.
154   if (wdog->SYNCBUSY & WDOG_SYNCBUSY_CMD) {
155     return;
156   }
157   // Before writing to the WDOG_CMD register, make sure that
158   // any previous write to the WDOG_CTRL is complete.
159   while ( (wdog->SYNCBUSY & WDOG_SYNCBUSY_CTRL) != 0U ) {
160   }
161 
162   wdog->CMD = WDOG_CMD_CLEAR;
163 
164 #else // Series 2 devices
165 
166   CORE_DECLARE_IRQ_STATE;
167 
168   // WDOG should not be fed while it is disabled.
169   if ((wdog->EN & WDOG_EN_EN) == 0U) {
170     return;
171   }
172 
173   // We need an atomic section around the check for sync and the clear command
174   // because sending a clear command while a previous command is being synchronized
175   // will cause a BusFault.
176   CORE_ENTER_ATOMIC();
177   if ((wdog->SYNCBUSY & WDOG_SYNCBUSY_CMD) == 0U) {
178     wdog->CMD = WDOG_CMD_CLEAR;
179   }
180   CORE_EXIT_ATOMIC();
181 
182 #endif
183 }
184 
185 /***************************************************************************//**
186  * @brief
187  *   Initialize WDOG (assuming the WDOG configuration has not been
188  *   locked).
189  *
190  * @note
191  *   This function modifies the WDOG CTRL register which requires
192  *   synchronization into the low-frequency domain. If this register is modified
193  *   before a previous update to the same register has completed, this function
194  *   will stall until the previous synchronization has completed.
195  *
196  * @param[in] wdog
197  *   Pointer to the WDOG peripheral register block.
198  *
199  * @param[in] init
200  *   The structure holding the WDOG configuration. A default setting
201  *   #WDOG_INIT_DEFAULT is available for initialization.
202  ******************************************************************************/
WDOGn_Init(WDOG_TypeDef * wdog,const WDOG_Init_TypeDef * init)203 void WDOGn_Init(WDOG_TypeDef *wdog, const WDOG_Init_TypeDef *init)
204 {
205 #if defined(_WDOG_CFG_MASK)
206   // Handle series-2 devices
207 
208   if (wdog->EN != 0U) {
209     while (wdog->SYNCBUSY != 0U) {
210       // Wait for any potential synchronization to finish
211     }
212     wdog->EN_CLR = WDOG_EN_EN;
213 #if defined(_WDOG_EN_DISABLING_MASK)
214     while (wdog->EN & _WDOG_EN_DISABLING_MASK) {
215       /* Wait for disabling to finish */
216     }
217 #endif
218   }
219 
220   wdog->CFG = (init->debugRun       ? WDOG_CFG_DEBUGRUN   : 0U)
221               | (init->em2Run       ? WDOG_CFG_EM2RUN     : 0U)
222               | (init->em3Run       ? WDOG_CFG_EM3RUN     : 0U)
223               | (init->em4Block     ? WDOG_CFG_EM4BLOCK   : 0U)
224               | (init->resetDisable ? WDOG_CFG_WDOGRSTDIS : 0U)
225               | ((uint32_t)(init->warnSel) << _WDOG_CFG_WARNSEL_SHIFT)
226               | ((uint32_t)(init->winSel) << _WDOG_CFG_WINSEL_SHIFT)
227               | ((uint32_t)(init->perSel) << _WDOG_CFG_PERSEL_SHIFT);
228 
229   WDOGn_Enable(wdog, init->enable);
230 
231   if (init->lock) {
232     WDOGn_Lock(wdog);
233   }
234 #else
235   // Handle series-0 and series-1 devices
236   uint32_t setting;
237 
238   setting = (init->enable       ? WDOG_CTRL_EN         : 0U)
239             | (init->debugRun   ? WDOG_CTRL_DEBUGRUN   : 0U)
240             | (init->em2Run     ? WDOG_CTRL_EM2RUN     : 0U)
241             | (init->em3Run     ? WDOG_CTRL_EM3RUN     : 0U)
242             | (init->em4Block   ? WDOG_CTRL_EM4BLOCK   : 0U)
243             | (init->swoscBlock ? WDOG_CTRL_SWOSCBLOCK : 0U)
244             | (init->lock       ? WDOG_CTRL_LOCK       : 0U)
245             | ((uint32_t)(init->clkSel) << _WDOG_CTRL_CLKSEL_SHIFT)
246             | ((uint32_t)(init->perSel) << _WDOG_CTRL_PERSEL_SHIFT);
247 
248 #if defined(_WDOG_CTRL_WDOGRSTDIS_MASK)
249   setting |= (init->resetDisable ? WDOG_CTRL_WDOGRSTDIS : 0U);
250 #endif
251 #if defined(_WDOG_CTRL_WARNSEL_MASK)
252   setting |= ((uint32_t)(init->warnSel) << _WDOG_CTRL_WARNSEL_SHIFT);
253 #endif
254 #if defined(_WDOG_CTRL_WINSEL_MASK)
255   setting |= ((uint32_t)(init->winSel) << _WDOG_CTRL_WINSEL_SHIFT);
256 #endif
257 
258   // Wait for previous operations/modifications to complete
259   int i = 0;
260   while (((wdog->SYNCBUSY & WDOG_SYNCBUSY_CTRL) != 0U)
261          && (i < WDOG_SYNC_TIMEOUT)) {
262     i++;
263   }
264   wdog->CTRL = setting;
265 #endif
266 }
267 
268 /***************************************************************************//**
269  * @brief
270  *   Lock the WDOG configuration.
271  *
272  * @details
273  *   This prevents errors from overwriting the WDOG configuration, possibly
274  *   disabling it. Only a reset can unlock the WDOG configuration once locked.
275  *
276  *   If the LFRCO or LFXO clocks are used to clock WDOG,
277  *   consider using the option of inhibiting those clocks to be disabled.
278  *   See the WDOG_Enable() initialization structure.
279  *
280  * @note
281  *   This function modifies the WDOG CTRL register which requires
282  *   synchronization into the low-frequency domain. If this register is modified
283  *   before a previous update to the same register has completed, this function
284  *   will stall until the previous synchronization has completed.
285  *
286  * @param[in] wdog
287  *   A pointer to WDOG peripheral register block.
288  ******************************************************************************/
WDOGn_Lock(WDOG_TypeDef * wdog)289 void WDOGn_Lock(WDOG_TypeDef *wdog)
290 {
291 #if defined(_WDOG_LOCK_MASK)
292   wdog->LOCK = _WDOG_LOCK_LOCKKEY_LOCK;
293 #else
294   // Wait for any pending previous write operation to have been completed in
295   // the low-frequency domain.
296   while ( (wdog->SYNCBUSY & WDOG_SYNCBUSY_CTRL) != 0U ) {
297   }
298 
299   // Disable writing to the control register.
300   BUS_RegBitWrite(&wdog->CTRL, _WDOG_CTRL_LOCK_SHIFT, 1);
301 #endif
302 }
303 
304 /***************************************************************************//**
305  * @brief
306  *   Wait for the WDOG to complete all synchronization of register changes
307  *   and commands.
308  *
309  * @param[in] wdog
310  *   A pointer to WDOG peripheral register block.
311  ******************************************************************************/
WDOGn_SyncWait(WDOG_TypeDef * wdog)312 void WDOGn_SyncWait(WDOG_TypeDef *wdog)
313 {
314 #if defined(_SILICON_LABS_32B_SERIES_2)
315   while ((wdog->EN != 0U) && (wdog->SYNCBUSY != 0U)) {
316     // Wait for synchronization to finish
317   }
318 #else
319   while (wdog->SYNCBUSY != 0U) {
320     // Wait for synchronization to finish
321   }
322 #endif
323 }
324 
325 /***************************************************************************//**
326  * @brief
327  *   Unlock the WDOG configuration.
328  *
329  * @details
330  *   Note that this function will have no effect on devices where a reset is
331  *   the only way to unlock the watchdog.
332  *
333  * @param[in] wdog
334  *   A pointer to WDOG peripheral register block.
335  ******************************************************************************/
WDOGn_Unlock(WDOG_TypeDef * wdog)336 void WDOGn_Unlock(WDOG_TypeDef *wdog)
337 {
338 #if defined(_WDOG_LOCK_MASK)
339   wdog->LOCK = _WDOG_LOCK_LOCKKEY_UNLOCK;
340 #else
341   (void) wdog;
342 #endif
343 }
344 
345 /** @} (end addtogroup wdog) */
346 #endif /* defined(WDOG_COUNT) && (WDOG_COUNT > 0) */
347