356 lines
12 KiB
C
356 lines
12 KiB
C
/***************************************************************************//**
|
|
* @file
|
|
* @brief Watchdog (WDOG) peripheral API
|
|
*******************************************************************************
|
|
* # License
|
|
* <b>Copyright 2018 Silicon Laboratories Inc. www.silabs.com</b>
|
|
*******************************************************************************
|
|
*
|
|
* SPDX-License-Identifier: Zlib
|
|
*
|
|
* The licensor of this software is Silicon Laboratories Inc.
|
|
*
|
|
* This software is provided 'as-is', without any express or implied
|
|
* warranty. In no event will the authors be held liable for any damages
|
|
* arising from the use of this software.
|
|
*
|
|
* Permission is granted to anyone to use this software for any purpose,
|
|
* including commercial applications, and to alter it and redistribute it
|
|
* freely, subject to the following restrictions:
|
|
*
|
|
* 1. The origin of this software must not be misrepresented; you must not
|
|
* claim that you wrote the original software. If you use this software
|
|
* in a product, an acknowledgment in the product documentation would be
|
|
* appreciated but is not required.
|
|
* 2. Altered source versions must be plainly marked as such, and must not be
|
|
* misrepresented as being the original software.
|
|
* 3. This notice may not be removed or altered from any source distribution.
|
|
*
|
|
******************************************************************************/
|
|
|
|
#include "em_wdog.h"
|
|
#if defined(WDOG_COUNT) && (WDOG_COUNT > 0)
|
|
|
|
#include "em_bus.h"
|
|
#include "em_core.h"
|
|
|
|
/***************************************************************************//**
|
|
* @addtogroup wdog WDOG - Watchdog
|
|
* @brief Watchdog (WDOG) Peripheral API
|
|
* @details
|
|
* This module contains functions to control the WDOG peripheral of Silicon
|
|
* Labs 32-bit MCUs and SoCs. The WDOG resets the system in case of a fault
|
|
* condition.
|
|
* @{
|
|
******************************************************************************/
|
|
|
|
/** In some scenarioes when the watchdog is disabled the synchronization
|
|
* register might be set and not be cleared until the watchdog is enabled
|
|
* again. This will happen when for instance some watchdog register is modified
|
|
* while the watchdog clock is disabled. In these scenarioes we need to make
|
|
* sure that the software does not wait forever. */
|
|
#define WDOG_SYNC_TIMEOUT 30000
|
|
|
|
/*******************************************************************************
|
|
************************** GLOBAL FUNCTIONS *******************************
|
|
******************************************************************************/
|
|
|
|
/***************************************************************************//**
|
|
* @brief
|
|
* Enable/disable the watchdog timer.
|
|
*
|
|
* @note
|
|
* This function modifies the WDOG CTRL register which requires
|
|
* synchronization into the low-frequency domain. If this register is modified
|
|
* before a previous update to the same register has completed, this function
|
|
* will stall until the previous synchronization has completed.
|
|
*
|
|
* @param[in] wdog
|
|
* A pointer to the WDOG peripheral register block.
|
|
*
|
|
* @param[in] enable
|
|
* True to enable Watchdog, false to disable. Watchdog cannot be disabled if
|
|
* it's been locked.
|
|
******************************************************************************/
|
|
void WDOGn_Enable(WDOG_TypeDef *wdog, bool enable)
|
|
{
|
|
// SYNCBUSY may stall when locked.
|
|
#if defined(_WDOG_STATUS_MASK)
|
|
if ((wdog->STATUS & _WDOG_STATUS_LOCK_MASK) == WDOG_STATUS_LOCK_LOCKED) {
|
|
return;
|
|
}
|
|
#else
|
|
if (wdog->CTRL & WDOG_CTRL_LOCK) {
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#if defined(_WDOG_EN_MASK)
|
|
if (!enable) {
|
|
while (wdog->SYNCBUSY & WDOG_SYNCBUSY_CMD) {
|
|
}
|
|
wdog->EN_CLR = WDOG_EN_EN;
|
|
#if defined(_WDOG_EN_DISABLING_MASK)
|
|
while (wdog->EN & _WDOG_EN_DISABLING_MASK) {
|
|
}
|
|
#endif
|
|
} else {
|
|
wdog->EN_SET = WDOG_EN_EN;
|
|
}
|
|
#else
|
|
// Wait for previous operations/modifications to complete
|
|
int i = 0;
|
|
while (((wdog->SYNCBUSY & WDOG_SYNCBUSY_CTRL) != 0U)
|
|
&& (i < WDOG_SYNC_TIMEOUT)) {
|
|
i++;
|
|
}
|
|
|
|
bool wdogState = ((wdog->CTRL & _WDOG_CTRL_EN_MASK) != 0U);
|
|
|
|
// Make sure to only write to the CTRL register if we are changing mode
|
|
if (wdogState != enable) {
|
|
BUS_RegBitWrite(&wdog->CTRL, _WDOG_CTRL_EN_SHIFT, enable);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/***************************************************************************//**
|
|
* @brief
|
|
* Feed WDOG.
|
|
*
|
|
* @details
|
|
* When WDOG is activated, it must be fed (i.e., clearing the counter)
|
|
* before it reaches the defined timeout period. Otherwise, WDOG
|
|
* will generate a reset.
|
|
*
|
|
* @note
|
|
* Note that WDOG is an asynchronous peripheral and when calling the
|
|
* WDOGn_Feed() function the hardware starts the process of clearing the
|
|
* counter. This process takes some time before it completes depending on the
|
|
* selected oscillator (up to 4 peripheral clock cycles). When using the
|
|
* ULFRCO for instance as the oscillator the watchdog runs on a 1 kHz clock
|
|
* and a watchdog clear operation might take up to 4 ms.
|
|
*
|
|
* If the device enters EM2 or EM3 while a command is in progress then that
|
|
* command will be aborted. An application can use @ref WDOGn_SyncWait()
|
|
* to wait for a command to complete.
|
|
*
|
|
* @param[in] wdog
|
|
* A pointer to the WDOG peripheral register block.
|
|
******************************************************************************/
|
|
void WDOGn_Feed(WDOG_TypeDef *wdog)
|
|
{
|
|
#if (_SILICON_LABS_32B_SERIES < 2)
|
|
|
|
// WDOG should not be fed while it is disabled.
|
|
if (!(wdog->CTRL & WDOG_CTRL_EN)) {
|
|
return;
|
|
}
|
|
|
|
// If a previous clearing is synchronized to the LF domain, there
|
|
// is no point in waiting for it to complete before clearing over again.
|
|
// This avoids stalling the core in the typical use case where some idle loop
|
|
// keeps clearing WDOG.
|
|
if (wdog->SYNCBUSY & WDOG_SYNCBUSY_CMD) {
|
|
return;
|
|
}
|
|
// Before writing to the WDOG_CMD register, make sure that
|
|
// any previous write to the WDOG_CTRL is complete.
|
|
while ( (wdog->SYNCBUSY & WDOG_SYNCBUSY_CTRL) != 0U ) {
|
|
}
|
|
|
|
wdog->CMD = WDOG_CMD_CLEAR;
|
|
|
|
#else // Series 2 devices
|
|
|
|
CORE_DECLARE_IRQ_STATE;
|
|
|
|
// WDOG should not be fed while it is disabled.
|
|
if ((wdog->EN & WDOG_EN_EN) == 0U) {
|
|
return;
|
|
}
|
|
|
|
// We need an atomic section around the check for sync and the clear command
|
|
// because sending a clear command while a previous command is being synchronized
|
|
// will cause a BusFault.
|
|
CORE_ENTER_ATOMIC();
|
|
if ((wdog->SYNCBUSY & WDOG_SYNCBUSY_CMD) == 0U) {
|
|
wdog->CMD = WDOG_CMD_CLEAR;
|
|
}
|
|
CORE_EXIT_ATOMIC();
|
|
|
|
#endif
|
|
}
|
|
|
|
/***************************************************************************//**
|
|
* @brief
|
|
* Initialize WDOG (assuming the WDOG configuration has not been
|
|
* locked).
|
|
*
|
|
* @note
|
|
* This function modifies the WDOG CTRL register which requires
|
|
* synchronization into the low-frequency domain. If this register is modified
|
|
* before a previous update to the same register has completed, this function
|
|
* will stall until the previous synchronization has completed.
|
|
*
|
|
* @param[in] wdog
|
|
* Pointer to the WDOG peripheral register block.
|
|
*
|
|
* @param[in] init
|
|
* The structure holding the WDOG configuration. A default setting
|
|
* #WDOG_INIT_DEFAULT is available for initialization.
|
|
******************************************************************************/
|
|
void WDOGn_Init(WDOG_TypeDef *wdog, const WDOG_Init_TypeDef *init)
|
|
{
|
|
#if defined(_WDOG_CFG_MASK)
|
|
// Handle series-2 devices
|
|
|
|
if (wdog->EN != 0U) {
|
|
while (wdog->SYNCBUSY != 0U) {
|
|
// Wait for any potential synchronization to finish
|
|
}
|
|
wdog->EN_CLR = WDOG_EN_EN;
|
|
#if defined(_WDOG_EN_DISABLING_MASK)
|
|
while (wdog->EN & _WDOG_EN_DISABLING_MASK) {
|
|
/* Wait for disabling to finish */
|
|
}
|
|
#endif
|
|
}
|
|
|
|
wdog->CFG = (init->debugRun ? WDOG_CFG_DEBUGRUN : 0U)
|
|
| (init->clrSrc ? WDOG_CFG_CLRSRC : 0U)
|
|
#if defined(_WDOG_CFG_EM1RUN_MASK)
|
|
| (init->em1Run ? WDOG_CFG_EM1RUN : 0U)
|
|
#endif
|
|
| (init->em2Run ? WDOG_CFG_EM2RUN : 0U)
|
|
| (init->em3Run ? WDOG_CFG_EM3RUN : 0U)
|
|
| (init->em4Block ? WDOG_CFG_EM4BLOCK : 0U)
|
|
| (init->prs0MissRstEn ? WDOG_CFG_PRS0MISSRSTEN : 0U)
|
|
| (init->prs1MissRstEn ? WDOG_CFG_PRS1MISSRSTEN : 0U)
|
|
| (init->resetDisable ? WDOG_CFG_WDOGRSTDIS : 0U)
|
|
| ((uint32_t)(init->warnSel) << _WDOG_CFG_WARNSEL_SHIFT)
|
|
| ((uint32_t)(init->winSel) << _WDOG_CFG_WINSEL_SHIFT)
|
|
| ((uint32_t)(init->perSel) << _WDOG_CFG_PERSEL_SHIFT);
|
|
|
|
WDOGn_Enable(wdog, init->enable);
|
|
|
|
if (init->lock) {
|
|
WDOGn_Lock(wdog);
|
|
}
|
|
#else
|
|
// Handle series-0 and series-1 devices
|
|
uint32_t setting;
|
|
|
|
setting = (init->enable ? WDOG_CTRL_EN : 0U)
|
|
| (init->debugRun ? WDOG_CTRL_DEBUGRUN : 0U)
|
|
#if defined(_WDOG_CTRL_CLRSRC_MASK)
|
|
| (init->clrSrc ? WDOG_CTRL_CLRSRC : 0U)
|
|
#endif
|
|
| (init->em2Run ? WDOG_CTRL_EM2RUN : 0U)
|
|
| (init->em3Run ? WDOG_CTRL_EM3RUN : 0U)
|
|
| (init->em4Block ? WDOG_CTRL_EM4BLOCK : 0U)
|
|
| (init->swoscBlock ? WDOG_CTRL_SWOSCBLOCK : 0U)
|
|
| (init->lock ? WDOG_CTRL_LOCK : 0U)
|
|
| ((uint32_t)(init->clkSel) << _WDOG_CTRL_CLKSEL_SHIFT)
|
|
| ((uint32_t)(init->perSel) << _WDOG_CTRL_PERSEL_SHIFT);
|
|
|
|
#if defined(_WDOG_CTRL_WDOGRSTDIS_MASK)
|
|
setting |= (init->resetDisable ? WDOG_CTRL_WDOGRSTDIS : 0U);
|
|
#endif
|
|
#if defined(_WDOG_CTRL_WARNSEL_MASK)
|
|
setting |= ((uint32_t)(init->warnSel) << _WDOG_CTRL_WARNSEL_SHIFT);
|
|
#endif
|
|
#if defined(_WDOG_CTRL_WINSEL_MASK)
|
|
setting |= ((uint32_t)(init->winSel) << _WDOG_CTRL_WINSEL_SHIFT);
|
|
#endif
|
|
|
|
// Wait for previous operations/modifications to complete
|
|
int i = 0;
|
|
while (((wdog->SYNCBUSY & WDOG_SYNCBUSY_CTRL) != 0U)
|
|
&& (i < WDOG_SYNC_TIMEOUT)) {
|
|
i++;
|
|
}
|
|
wdog->CTRL = setting;
|
|
#endif
|
|
}
|
|
|
|
/***************************************************************************//**
|
|
* @brief
|
|
* Lock the WDOG configuration.
|
|
*
|
|
* @details
|
|
* This prevents errors from overwriting the WDOG configuration, possibly
|
|
* disabling it. Only a reset can unlock the WDOG configuration once locked.
|
|
*
|
|
* If the LFRCO or LFXO clocks are used to clock WDOG,
|
|
* consider using the option of inhibiting those clocks to be disabled.
|
|
* See the WDOG_Enable() initialization structure.
|
|
*
|
|
* @note
|
|
* This function modifies the WDOG CTRL register which requires
|
|
* synchronization into the low-frequency domain. If this register is modified
|
|
* before a previous update to the same register has completed, this function
|
|
* will stall until the previous synchronization has completed.
|
|
*
|
|
* @param[in] wdog
|
|
* A pointer to WDOG peripheral register block.
|
|
******************************************************************************/
|
|
void WDOGn_Lock(WDOG_TypeDef *wdog)
|
|
{
|
|
#if defined(_WDOG_LOCK_MASK)
|
|
wdog->LOCK = _WDOG_LOCK_LOCKKEY_LOCK;
|
|
#else
|
|
// Wait for any pending previous write operation to have been completed in
|
|
// the low-frequency domain.
|
|
while ( (wdog->SYNCBUSY & WDOG_SYNCBUSY_CTRL) != 0U ) {
|
|
}
|
|
|
|
// Disable writing to the control register.
|
|
BUS_RegBitWrite(&wdog->CTRL, _WDOG_CTRL_LOCK_SHIFT, 1);
|
|
#endif
|
|
}
|
|
|
|
/***************************************************************************//**
|
|
* @brief
|
|
* Wait for the WDOG to complete all synchronization of register changes
|
|
* and commands.
|
|
*
|
|
* @param[in] wdog
|
|
* A pointer to WDOG peripheral register block.
|
|
******************************************************************************/
|
|
void WDOGn_SyncWait(WDOG_TypeDef *wdog)
|
|
{
|
|
#if defined(_SILICON_LABS_32B_SERIES_2)
|
|
while ((wdog->EN != 0U) && (wdog->SYNCBUSY != 0U)) {
|
|
// Wait for synchronization to finish
|
|
}
|
|
#else
|
|
while (wdog->SYNCBUSY != 0U) {
|
|
// Wait for synchronization to finish
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/***************************************************************************//**
|
|
* @brief
|
|
* Unlock the WDOG configuration.
|
|
*
|
|
* @details
|
|
* Note that this function will have no effect on devices where a reset is
|
|
* the only way to unlock the watchdog.
|
|
*
|
|
* @param[in] wdog
|
|
* A pointer to WDOG peripheral register block.
|
|
******************************************************************************/
|
|
void WDOGn_Unlock(WDOG_TypeDef *wdog)
|
|
{
|
|
#if defined(_WDOG_LOCK_MASK)
|
|
wdog->LOCK = _WDOG_LOCK_LOCKKEY_UNLOCK;
|
|
#else
|
|
(void) wdog;
|
|
#endif
|
|
}
|
|
|
|
/** @} (end addtogroup wdog) */
|
|
#endif /* defined(WDOG_COUNT) && (WDOG_COUNT > 0) */
|