/******************************************************************************
 * $Revision: 423 $
 * $Date:: 2017-04-07 16:03:30 +0900#$
 *****************************************************************************/
/* __DISCLAIMER_START__                                                      */
/******************************************************************************
* (c)2017, Cypress Semiconductor Corporation
* or a subsidiary of Cypress Semiconductor Corporation. All rights
* reserved.
*
* This software, including source code, documentation and related
* materials ( "Software" ), is owned by Cypress Semiconductor
* Corporation or one of its subsidiaries ( "Cypress" ) and is protected by
* and subject to worldwide patent protection (United States and foreign),
* United States copyright laws and international treaty provisions.
* Therefore, you may use this Software only as provided in the license
* agreement accompanying the software package from which you
* obtained this Software ( "EULA" ).
*
* If no EULA applies, Cypress hereby grants you a personal, nonexclusive,
* non-transferable license to copy, modify, and compile the
* Software source code solely for use in connection with Cypress' s
* integrated circuit products. Any reproduction, modification, translation,
* compilation, or representation of this Software except as specified
* above is prohibited without the express written permission of Cypress.
*
* Disclaimer: THIS SOFTWARE IS PROVIDED AS-IS, WITH NO
* WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING,
* BUT NOT LIMITED TO, NONINFRINGEMENT, IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE. Cypress reserves the right to make
* changes to the Software without notice. Cypress does not assume any
* liability arising out of the application or use of the Software or any
* product or circuit described in the Software. Cypress does not
* authorize its products for use in any products where a malfunction or
* failure of the Cypress product may reasonably be expected to result in
* significant property damage, injury or death ( "High Risk Product" ). By
* including Cypress' s product in a High Risk Product, the manufacturer
* of such system or application assumes all risk of such use and in doing
* so agrees to indemnify Cypress against all liability.
******************************************************************************/
/* __DISCLAIMER_END__                                                        */
/*****************************************************************************
 ** \file swdg.c
 **
 ** A detailed description is available at
 ** @link SwdgGroup Software Watchdog Module description @endlink
 **
 ** History:
 **   - 2014-05-16  0.01  HS  Initial version for Traveo
 *****************************************************************************/

/*****************************************************************************/
/* Include files                                                             */
/*****************************************************************************/

#include "abstract.h"
#include "interrupts.h"

#if defined( PDL_PERIPHERAL_SWDG_ACTIVE )

/**
 *****************************************************************************
 ** \addtogroup SwdgGroup
 *****************************************************************************/
/*! @{ */


/*****************************************************************************/
/* Local pre-processor symbols/macros ('#define')                            */
/*****************************************************************************/
/*! key for unlocking SW-WDG regs */
#define SWDG_PROT_UNLOCK        (0xEDACCE55)
/*! trigger value for updating RUN profile */
#define SYSC_TRGRUNCNTR_APPLY_PROFILE  (0xAB)
/*! key for unlocking SYSC */
#define SYSC_PROTKEYR_UNLOCK    (0x5CACCE55)

/*****************************************************************************/
/* Global variable definitions (declared in header file with 'extern')       */
/*****************************************************************************/

/*****************************************************************************/
/* Local type definitions ('typedef')                                        */
/*****************************************************************************/
/**
 *****************************************************************************
 ** \brief Data needed by SWDG module for operation
 *****************************************************************************/
typedef struct stc_swdg_intern_data
{
    func_ptr_t pfnPreWarnCallback;  /*!< Callback for SWDG prior warning interrupt */
    func_ptr_t pfnNmiCallback;      /*!< Callback for SWDG NMI interrupt */
} stc_swdg_intern_data_t;

/*****************************************************************************/
/* Local function prototypes ('static')                                      */
/*****************************************************************************/

/*****************************************************************************/
/* Local variable definitions ('static')                                     */
/*****************************************************************************/
static stc_swdg_intern_data_t m_stcSwdgInternData;

/*****************************************************************************/
/* Function implementation - global ('extern') and local ('static')          */
/*****************************************************************************/

/**
 *****************************************************************************
  ** \brief ISR for SWDG pre warning interrupt.
 **
 ** This ISR calls the callback function that has been given configuration
 ** (see Swdg_Init() and #stc_swdg_config_t).
 *****************************************************************************/
FN_IRQ_DEFINE_BEGIN(Swdg_Isr_PreWarn, INTERRUPTS_IRQ_NUMBER_3)
{
    /* Clear prior warning interrupt flag. Registers need to be unlocked for */
    /* writing first. */
    SWDG_PROT = SWDG_PROT_UNLOCK;
    PDL_WRITE_REG_SYNC(SWDG_INTCLR_IRQCLR, 1);

    /* Call registered interrupt callback function if available. */
    /* If no callback is registered, the interrupt should not be enabled anyway. */
    /* The NULL pointer check is done just for safety reasons. */
    if (m_stcSwdgInternData.pfnPreWarnCallback != NULL)
    {
        m_stcSwdgInternData.pfnPreWarnCallback();
    }
}
FN_IRQ_DEFINE_END()

/**
 *****************************************************************************
  ** \brief ISR for SWDG NMI.
 **
 ** This ISR calls the callback function that has been given configuration
 ** (see Swdg_Init() and #stc_swdg_config_t).
 *****************************************************************************/
FN_NMI_DEFINE_BEGIN(Swdg_Isr_NMI, INTERRUPTS_NMI_NUMBER_7)
{
    /* Clear prior warning interrupt flag. Registers need to be unlocked for */
    /* writing first. */
    SWDG_PROT = SWDG_PROT_UNLOCK;
    PDL_WRITE_REG_SYNC(SWDG_INTCLR_NMICLR, 1);

    /* Call registered interrupt callback function if available. */
    /* If no callback is registered, the interrupt should not be enabled anyway. */
    /* The NULL pointer check is done just for safety reasons. */
    if (m_stcSwdgInternData.pfnNmiCallback != NULL)
    {
        m_stcSwdgInternData.pfnNmiCallback();
    }
}
FN_NMI_DEFINE_END()

/**
 *****************************************************************************
 ** \brief Initialize software WDG
 **
 ** This function initializes the software WDG module. After a reset,
 ** the SWDG can be initialized exactly once. 0nce started the WDG
 ** cannot be stopped. After calling Swdg_Init() the WDG is locked.
 ** The last WDG reset cause will be cleared.
 ** If get the last reset cause, please use Swdg_GetResetCause()
 ** before calling this function.
 **
 ** \note This function can be called only once during startup. Afterwards the
 **       WDG is locked.
 **
 ** \pre Must be run from privileged mode and be run after the source
 **      clock setting is completed.
 **
 ** \param [in]  pstcConfig         SWDG configuration
 **
 ** \retval Ok                      Initialization of SWDG module successful
 ** \retval Error                   If following conditions are met:
 **             - SWDG module has already been initialized
 **             - Selected source clock oscillation is disabled
 **             - RUN profile has not been completed yet
 ** \retval ErrorInvalidParameter   If following conditions are met:
 **             - pstcConfig is NULL
 **             - pstcConfig->u32WindowLowerLimit greater or equal
 **               pstcConfig->u32WindowUpperLimit (either RUN or PSS)
 ** \retval ErrorAccessRights       If called from non-privileged mode
 *****************************************************************************/
en_result_t Swdg_Init(const stc_swdg_config_t *pstcConfig)
{
    boolean_t bPrivilegedMode;
    un_swdg_int_t unInt = { 0 };
    un_swdg_cfg_t unCfg = { 0 };

    /* --------------- Check parameters and conditions ---------------- */
    /* Check CPU mode */
    /* WDG configurations can be set only when CPU is in the privilege mode */
    bPrivilegedMode = Cpu_CpuIsInPrivilegedMode();
    if (bPrivilegedMode == FALSE)
    {
        return ErrorAccessRights;
    }

    /* NULL pointer check */
    if (pstcConfig == NULL)
    {
        return ErrorInvalidParameter;
    }

    /* Check window limit */
    /* Window upper limit should be greater than lower limit */
    if ((pstcConfig->stcRunModeSettings.bWdgEnable != FALSE) &&
        (pstcConfig->stcRunModeSettings.u32WindowLowerLimit >=
            pstcConfig->stcRunModeSettings.u32WindowUpperLimit))
    {
        return ErrorInvalidParameter;
    }
    if ((pstcConfig->stcPssModeSettings.bWdgEnable != FALSE) &&
        (pstcConfig->stcPssModeSettings.u32WindowLowerLimit >=
            pstcConfig->stcPssModeSettings.u32WindowUpperLimit))
    {
        return ErrorInvalidParameter;
    }
    /* SWDG configuration registers can be written only once */
    if (SWDG_CFG_LOCK != 0)
    {
        return Error;
    }

    /* Check clock selection */
    /* If selected source clock oscillation is disabled, return as error*/
    switch (pstcConfig->enClockSelection)
    {
    case SwdgSlowRcClk:
        if (SYSC0_APPCKSRER_SCROSCEN == 0)
        {
            return Error;
        }
        break;
    case SwdgRcClk:
        if (SYSC0_APPCKSRER_CROSCEN == 0)
        {
            return Error;
        }
        break;
    case SwdgMainClk:
        if (SYSC0_APPCKSRER_MOSCEN == 0)
        {
            return Error;
        }
        break;
    default:
        return ErrorInvalidParameter;   /* Should never reach */
    }

    /* Check if profile update has not completed */
    if (SYSC0_SYSSTSR_RUNSTS0 != 0)
    {
        return Error;
    }

    /* --------------- Construct register settings ---------------- */
    /* Setting of SWDG_INT register */
    /* If a callback for NMI was given, watchdog interrupt request (NMI) is enabled. */
    unInt.stcField.u1RSTEN =
            (pstcConfig->pfnNmiCallback != NULL) ? 0 : 1;
    /* If a callback for prior warning interrupt was given, enable the interrupt */
    unInt.stcField.u1IRQEN =
            (pstcConfig->pfnPreWarnCallback != NULL) ? 1 : 0;
    /* Store callback function */
    m_stcSwdgInternData.pfnNmiCallback = pstcConfig->pfnNmiCallback;
    m_stcSwdgInternData.pfnPreWarnCallback = pstcConfig->pfnPreWarnCallback;

    /* Setting of SWDG_CFG register */
    unCfg.stcField.u1WDENRUN =
            (pstcConfig->stcRunModeSettings.bWdgEnable == FALSE) ? 0 : 1;
    unCfg.stcField.u1WDENPSS =
            (pstcConfig->stcPssModeSettings.bWdgEnable == FALSE) ? 0 : 1;
    unCfg.stcField.u1ALLOWSTOPCLK =
            (pstcConfig->bAllowClockStopInPss == FALSE) ? 0 : 1;
    unCfg.stcField.u2CLKSEL = pstcConfig->enClockSelection;
    unCfg.stcField.u5OBSSEL = (pstcConfig->u8OBSSelection & 0x1F);

    /* ---------------  Access registers ---------------- */
    /* Disable IRQs for protected sequences */
    IRQ_DISABLE_LOCAL();

    /* Clear interrupt flags */
    SWDG_PROT = SWDG_PROT_UNLOCK;
    SWDG_INTCLR_IRQCLR = 1;
    SWDG_PROT = SWDG_PROT_UNLOCK;
    SWDG_INTCLR_NMICLR = 1;

    /* Clear reset cause */
    SWDG_PROT = SWDG_PROT_UNLOCK;
    SWDG_RSTCAUSE = 0x00000000;

    /* Set the configuration of the interrupt */
    SWDG_PROT = SWDG_PROT_UNLOCK;
    SWDG_INT = unInt.u32Register;

    /* Set WDG trigger values */
    SWDG_PROT = SWDG_PROT_UNLOCK;
    SWDG_TRG0CFG = pstcConfig->u8TriggerKey0;
    SWDG_PROT = SWDG_PROT_UNLOCK;
    SWDG_TRG1CFG = pstcConfig->u8TriggerKey1;

    /* Set window lower limit for RUN mode */
    SWDG_PROT = SWDG_PROT_UNLOCK;
    SWDG_RUNLLS = pstcConfig->stcRunModeSettings.u32WindowLowerLimit;

    /* Set window upper limit for RUN mode */
    SWDG_PROT = SWDG_PROT_UNLOCK;
    SWDG_RUNULS = pstcConfig->stcRunModeSettings.u32WindowUpperLimit;

    /* Set window lower limit for PSS mode */
    SWDG_PROT = SWDG_PROT_UNLOCK;
    SWDG_PSSLLS = pstcConfig->stcPssModeSettings.u32WindowLowerLimit;

    /* Set window upper limit for PSS mode */
    SWDG_PROT = SWDG_PROT_UNLOCK;
    SWDG_PSSULS = pstcConfig->stcPssModeSettings.u32WindowUpperLimit;

    /* Set delay cycle for Reset or NMI. */
    SWDG_PROT = SWDG_PROT_UNLOCK;
    SWDG_RSTDLY_WDGRSTDLY = pstcConfig->u16ResetDelay;

    /* Set WDG configuration */
    SWDG_PROT = SWDG_PROT_UNLOCK;
    SWDG_CFG = unCfg.u32Register;

    /* Lock WDG configuration */
    SWDG_PROT = SWDG_PROT_UNLOCK;
    SWDG_CFG_LOCK = 1;

    /* Update RUN profile in order to switch the source clock of the SWDG */
    /* Clear RUN profile done flag */
    SYSC0_PROTKEYR = SYSC_PROTKEYR_UNLOCK;
    SYSC0_SYSICLR_RUNDFCLR0 = 1;

    /* Enable RUN profile update */
    SYSC1_PROTKEYR = SYSC_PROTKEYR_UNLOCK;
    SYSC1_RUNENR_0_RUNEN1 = SYSC_TRGRUNCNTR_APPLY_PROFILE;

    /* Trigger to apply the RUN profile */
    SYSC0_PROTKEYR = SYSC_PROTKEYR_UNLOCK;
    SYSC0_TRGRUNCNTR_APPLY_RUN = SYSC_TRGRUNCNTR_APPLY_PROFILE;

    while (SYSC0_SYSSTSR_RUNSTS0 == 1)
    {
        /* Wait until the RUN profile is applied */
    }

    /* restore IRQ state */
    IRQ_RESTORE();

    /* SWDG setup completed */
    return Ok;
}


/**
 *****************************************************************************
 ** \brief Return the SWDG reset cause
 **
 ** The last WDG reset cause will be cleared by Swdg_Init().
 ** If this function is called after Swdg_Init(), it will always return "no cause".
 **
 ** \pre Must be run before calling Swdg_Init()
 **
 ** \param [out] pstcResetCause     Returns previous SWDG reset cause.
 **
 ** \retval Ok                      Getting of reset cause is successful
 ** \retval ErrorInvalidParameter   If Swdg_GetResetCause#pstcResetCause is NULL
 *****************************************************************************/
en_result_t Swdg_GetResetCause(stc_swdg_reset_cause_t* pstcResetCause)
{
    /* --------------- Check parameters and conditions ---------------- */
    if (pstcResetCause == NULL)
    {
        return ErrorInvalidParameter;
    }

    /* ---------------  Access registers ---------------- */
    pstcResetCause->bSwdgTriggerWhileUnlocked =
            (SWDG_RSTCAUSE_RSTCAUSE4 == 0) ? FALSE : TRUE;
    pstcResetCause->bSwdgTriggerTooEarly =
            (SWDG_RSTCAUSE_RSTCAUSE3 == 0) ? FALSE : TRUE;
    pstcResetCause->bSwdgNotTriggered =
            (SWDG_RSTCAUSE_RSTCAUSE2 == 0) ? FALSE : TRUE;
    pstcResetCause->bSwdgTriggerSequenceViolated =
            (SWDG_RSTCAUSE_RSTCAUSE1 == 0) ? FALSE : TRUE;
    pstcResetCause->bSwdgWrongTriggerValue =
            (SWDG_RSTCAUSE_RSTCAUSE0 == 0) ? FALSE : TRUE;

    return Ok;
}


/**
 *****************************************************************************
 ** \brief Clear the SWDG counter
 **
 ** \pre Must be run after calling Swdg_Init()
 **
 ** \param [in] u8TrgVal0,1         Trigger values of the SWDG counter clearing.
 **                                 This values must be set to the same value as
 **                                 SWDG_TRG0CFG, SWDG_TRG1CFG.
 **
 ** \retval Ok                      Clearing the SWDG is successful.
 ** \retval Error                   The SWDG counter has not reached window lower limit.
 *****************************************************************************/
en_result_t Swdg_Clear(uint8_t u8TrgVal0, uint8_t u8TrgVal1)
{
    uint32_t cnt;

    /* --------------- Check parameters and conditions ---------------- */
    cnt = Swdg_GetCounterValue();
    if (cnt < SWDG_RUNLLS )
    {
        return Error;
    }

    /* ---------------  Access registers ---------------- */
    /* Clear SWDG */
    IRQ_DISABLE_LOCAL();
    SWDG_TRG0 = u8TrgVal0;
    SWDG_TRG1 = u8TrgVal1;
    IRQ_RESTORE();

    return Ok;
}


/**
 *****************************************************************************
 ** \brief Get SWDG counter value
 **
 ** This function returns the current count value of the SWDG counter
 ** register.
 **
 ** \retval   Current watchdog counter value
 *****************************************************************************/
uint32_t Swdg_GetCounterValue(void)
{
    /* ---------------  Access registers ---------------- */
    return SWDG_CNT;
}

/*! @} */
#endif /* PDL_PERIPHERAL_SWDG_ACTIVE */

/*****************************************************************************/
/* EOF (not truncated)                                                       */
/*****************************************************************************/
