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->clrSrc        ? WDOG_CFG_CLRSRC          : 0U)
222 #if defined(_WDOG_CFG_EM1RUN_MASK)
223               | (init->em1Run        ? WDOG_CFG_EM1RUN          : 0U)
224 #endif
225               | (init->em2Run        ? WDOG_CFG_EM2RUN          : 0U)
226               | (init->em3Run        ? WDOG_CFG_EM3RUN          : 0U)
227               | (init->em4Block      ? WDOG_CFG_EM4BLOCK        : 0U)
228               | (init->prs0MissRstEn ? WDOG_CFG_PRS0MISSRSTEN   : 0U)
229               | (init->prs1MissRstEn ? WDOG_CFG_PRS1MISSRSTEN   : 0U)
230               | (init->resetDisable  ? WDOG_CFG_WDOGRSTDIS : 0U)
231               | ((uint32_t)(init->warnSel) << _WDOG_CFG_WARNSEL_SHIFT)
232               | ((uint32_t)(init->winSel) << _WDOG_CFG_WINSEL_SHIFT)
233               | ((uint32_t)(init->perSel) << _WDOG_CFG_PERSEL_SHIFT);
234 
235   WDOGn_Enable(wdog, init->enable);
236 
237   if (init->lock) {
238     WDOGn_Lock(wdog);
239   }
240 #else
241   // Handle series-0 and series-1 devices
242   uint32_t setting;
243 
244   setting = (init->enable       ? WDOG_CTRL_EN         : 0U)
245             | (init->debugRun   ? WDOG_CTRL_DEBUGRUN   : 0U)
246 #if defined(_WDOG_CTRL_CLRSRC_MASK)
247             | (init->clrSrc     ? WDOG_CTRL_CLRSRC     : 0U)
248 #endif
249             | (init->em2Run     ? WDOG_CTRL_EM2RUN     : 0U)
250             | (init->em3Run     ? WDOG_CTRL_EM3RUN     : 0U)
251             | (init->em4Block   ? WDOG_CTRL_EM4BLOCK   : 0U)
252             | (init->swoscBlock ? WDOG_CTRL_SWOSCBLOCK : 0U)
253             | (init->lock       ? WDOG_CTRL_LOCK       : 0U)
254             | ((uint32_t)(init->clkSel) << _WDOG_CTRL_CLKSEL_SHIFT)
255             | ((uint32_t)(init->perSel) << _WDOG_CTRL_PERSEL_SHIFT);
256 
257 #if defined(_WDOG_CTRL_WDOGRSTDIS_MASK)
258   setting |= (init->resetDisable ? WDOG_CTRL_WDOGRSTDIS : 0U);
259 #endif
260 #if defined(_WDOG_CTRL_WARNSEL_MASK)
261   setting |= ((uint32_t)(init->warnSel) << _WDOG_CTRL_WARNSEL_SHIFT);
262 #endif
263 #if defined(_WDOG_CTRL_WINSEL_MASK)
264   setting |= ((uint32_t)(init->winSel) << _WDOG_CTRL_WINSEL_SHIFT);
265 #endif
266 
267   // Wait for previous operations/modifications to complete
268   int i = 0;
269   while (((wdog->SYNCBUSY & WDOG_SYNCBUSY_CTRL) != 0U)
270          && (i < WDOG_SYNC_TIMEOUT)) {
271     i++;
272   }
273   wdog->CTRL = setting;
274 #endif
275 }
276 
277 /***************************************************************************//**
278  * @brief
279  *   Lock the WDOG configuration.
280  *
281  * @details
282  *   This prevents errors from overwriting the WDOG configuration, possibly
283  *   disabling it. Only a reset can unlock the WDOG configuration once locked.
284  *
285  *   If the LFRCO or LFXO clocks are used to clock WDOG,
286  *   consider using the option of inhibiting those clocks to be disabled.
287  *   See the WDOG_Enable() initialization structure.
288  *
289  * @note
290  *   This function modifies the WDOG CTRL register which requires
291  *   synchronization into the low-frequency domain. If this register is modified
292  *   before a previous update to the same register has completed, this function
293  *   will stall until the previous synchronization has completed.
294  *
295  * @param[in] wdog
296  *   A pointer to WDOG peripheral register block.
297  ******************************************************************************/
WDOGn_Lock(WDOG_TypeDef * wdog)298 void WDOGn_Lock(WDOG_TypeDef *wdog)
299 {
300 #if defined(_WDOG_LOCK_MASK)
301   wdog->LOCK = _WDOG_LOCK_LOCKKEY_LOCK;
302 #else
303   // Wait for any pending previous write operation to have been completed in
304   // the low-frequency domain.
305   while ( (wdog->SYNCBUSY & WDOG_SYNCBUSY_CTRL) != 0U ) {
306   }
307 
308   // Disable writing to the control register.
309   BUS_RegBitWrite(&wdog->CTRL, _WDOG_CTRL_LOCK_SHIFT, 1);
310 #endif
311 }
312 
313 /***************************************************************************//**
314  * @brief
315  *   Wait for the WDOG to complete all synchronization of register changes
316  *   and commands.
317  *
318  * @param[in] wdog
319  *   A pointer to WDOG peripheral register block.
320  ******************************************************************************/
WDOGn_SyncWait(WDOG_TypeDef * wdog)321 void WDOGn_SyncWait(WDOG_TypeDef *wdog)
322 {
323 #if defined(_SILICON_LABS_32B_SERIES_2)
324   while ((wdog->EN != 0U) && (wdog->SYNCBUSY != 0U)) {
325     // Wait for synchronization to finish
326   }
327 #else
328   while (wdog->SYNCBUSY != 0U) {
329     // Wait for synchronization to finish
330   }
331 #endif
332 }
333 
334 /***************************************************************************//**
335  * @brief
336  *   Unlock the WDOG configuration.
337  *
338  * @details
339  *   Note that this function will have no effect on devices where a reset is
340  *   the only way to unlock the watchdog.
341  *
342  * @param[in] wdog
343  *   A pointer to WDOG peripheral register block.
344  ******************************************************************************/
WDOGn_Unlock(WDOG_TypeDef * wdog)345 void WDOGn_Unlock(WDOG_TypeDef *wdog)
346 {
347 #if defined(_WDOG_LOCK_MASK)
348   wdog->LOCK = _WDOG_LOCK_LOCKKEY_UNLOCK;
349 #else
350   (void) wdog;
351 #endif
352 }
353 
354 /** @} (end addtogroup wdog) */
355 #endif /* defined(WDOG_COUNT) && (WDOG_COUNT > 0) */
356