/***************************************************************************//**
 * @file em_wdog.c
 * @brief Watchdog (WDOG) peripheral API
 *   devices.
 * @version 4.2.1
 *******************************************************************************
 * @section License
 * <b>(C) Copyright 2015 Silicon Labs, http://www.silabs.com</b>
 *******************************************************************************
 *
 * 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.
 * 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.
 *
 * DISCLAIMER OF WARRANTY/LIMITATION OF REMEDIES: Silicon Labs has no
 * obligation to support this Software. Silicon Labs is providing the
 * Software "AS IS", with no express or implied warranties of any kind,
 * including, but not limited to, any implied warranties of merchantability
 * or fitness for any particular purpose or warranties against infringement
 * of any proprietary rights of a third party.
 *
 * Silicon Labs will not be liable for any consequential, incidental, or
 * special damages, or any other relief, or for any claim by any third party,
 * arising from your use of this Software.
 *
 ******************************************************************************/

#include "em_wdog.h"
#if defined(WDOG_COUNT) && (WDOG_COUNT > 0)

#if defined(WDOG0)
#define WDOG WDOG0
#if (WDOG_COUNT > 1)
#warning "Multiple watchdogs not supported"
#endif
#endif

#include "em_bus.h"

/***************************************************************************//**
 * @addtogroup EM_Library
 * @{
 ******************************************************************************/

/***************************************************************************//**
 * @addtogroup WDOG
 * @brief Watchdog (WDOG) Peripheral API
 * @{
 ******************************************************************************/

/*******************************************************************************
 **************************   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] enable
 *   true to enable watchdog, false to disable. Watchdog cannot be disabled if
 *   watchdog has been locked.
 ******************************************************************************/
void WDOG_Enable(bool enable)
{
  if (!enable)
  {
    /* Wait for any pending previous write operation to have been completed in */
    /* low frequency domain */
    while (WDOG->SYNCBUSY & WDOG_SYNCBUSY_CTRL)
      ;
  }
  BUS_RegBitWrite(&(WDOG->CTRL), _WDOG_CTRL_EN_SHIFT, enable);
}


/***************************************************************************//**
 * @brief
 *   Feed the watchdog.
 *
 * @details
 *   When the watchdog is activated, it must be fed (ie clearing the counter)
 *   before it reaches the defined timeout period. Otherwise, the watchdog
 *   will generate a reset.
 ******************************************************************************/
void WDOG_Feed(void)
{
  /* The watchdog should not be fed while it is disabled */
  if ( !(WDOG->CTRL & WDOG_CTRL_EN) )
  {
    return;
  }

  /* If a previous clearing is being synchronized to LF domain, then 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 the watchdog. */
  if (WDOG->SYNCBUSY & WDOG_SYNCBUSY_CMD)
  {
    return;
  }
  /* Before writing to the WDOG_CMD register we also need to make sure that
   * any previous write to WDOG_CTRL is complete. */
  while ( WDOG->SYNCBUSY & WDOG_SYNCBUSY_CTRL )
    ;

  WDOG->CMD = WDOG_CMD_CLEAR;
}


/***************************************************************************//**
 * @brief
 *   Initialize watchdog (assuming the watchdog 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] init
 *   Structure holding watchdog configuration. A default setting
 *   #WDOG_INIT_DEFAULT is available for init.
 ******************************************************************************/
void WDOG_Init(const WDOG_Init_TypeDef *init)
{
  uint32_t setting;

  if (init->enable)
  {
    setting = WDOG_CTRL_EN;
  }
  else
  {
    setting = 0;
  }

  if (init->debugRun)
  {
    setting |= WDOG_CTRL_DEBUGRUN;
  }

  if (init->em2Run)
  {
    setting |= WDOG_CTRL_EM2RUN;
  }

  if (init->em3Run)
  {
    setting |= WDOG_CTRL_EM3RUN;
  }

  if (init->em4Block)
  {
    setting |= WDOG_CTRL_EM4BLOCK;
  }

  if (init->swoscBlock)
  {
    setting |= WDOG_CTRL_SWOSCBLOCK;
  }

  setting |= ((uint32_t)(init->clkSel)   << _WDOG_CTRL_CLKSEL_SHIFT)
             | ((uint32_t)(init->perSel) << _WDOG_CTRL_PERSEL_SHIFT);

  /* Wait for any pending previous write operation to have been completed in */
  /* low frequency domain */
  while (WDOG->SYNCBUSY & WDOG_SYNCBUSY_CTRL)
    ;

  WDOG->CTRL = setting;

  /* Optional register locking */
  if (init->lock)
  {
    if (init->enable)
    {
      WDOG_Lock();
    }
    else
    {
      BUS_RegBitWrite(&(WDOG->CTRL), _WDOG_CTRL_LOCK_SHIFT, 1);
    }
  }
}


/***************************************************************************//**
 * @brief
 *   Lock the watchdog configuration.
 *
 * @details
 *   This prevents errors from overwriting the watchdog configuration, possibly
 *   disabling it. Only a reset can unlock the watchdog config, once locked.
 *
 *   If the LFRCO or LFXO clocks are used to clock the watchdog, one should
 *   consider using the option of inhibiting those clocks to be disabled,
 *   please see the WDOG_Enable() init 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.
 ******************************************************************************/
void WDOG_Lock(void)
{
  /* Wait for any pending previous write operation to have been completed in */
  /* low frequency domain */
  while (WDOG->SYNCBUSY & WDOG_SYNCBUSY_CTRL)
    ;

  /* Disable writing to the control register */
  BUS_RegBitWrite(&(WDOG->CTRL), _WDOG_CTRL_LOCK_SHIFT, 1);
}


/** @} (end addtogroup WDOG) */
/** @} (end addtogroup EM_Library) */
#endif /* defined(WDOG_COUNT) && (WDOG_COUNT > 0) */
