/* Copyright Statement:
 *
 * This software/firmware and related documentation ("AutoChips Software") are
 * protected under relevant copyright laws. The information contained herein is
 * confidential and proprietary to AutoChips Inc. and/or its licensors. Without
 * the prior written permission of AutoChips inc. and/or its licensors, any
 * reproduction, modification, use or disclosure of AutoChips Software, and
 * information contained herein, in whole or in part, shall be strictly
 * prohibited.
 *
 * AutoChips Inc. (C) 2021. All rights reserved.
 *
 * BY OPENING THIS FILE, RECEIVER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
 * THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("AUTOCHIPS SOFTWARE")
 * RECEIVED FROM AUTOCHIPS AND/OR ITS REPRESENTATIVES ARE PROVIDED TO RECEIVER
 * ON AN "AS-IS" BASIS ONLY. AUTOCHIPS EXPRESSLY DISCLAIMS ANY AND ALL
 * WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NONINFRINGEMENT. NEITHER DOES AUTOCHIPS PROVIDE ANY WARRANTY WHATSOEVER WITH
 * RESPECT TO THE SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY,
 * INCORPORATED IN, OR SUPPLIED WITH THE AUTOCHIPS SOFTWARE, AND RECEIVER AGREES
 * TO LOOK ONLY TO SUCH THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO.
 * RECEIVER EXPRESSLY ACKNOWLEDGES THAT IT IS RECEIVER'S SOLE RESPONSIBILITY TO
 * OBTAIN FROM ANY THIRD PARTY ALL PROPER LICENSES CONTAINED IN AUTOCHIPS
 * SOFTWARE. AUTOCHIPS SHALL ALSO NOT BE RESPONSIBLE FOR ANY AUTOCHIPS SOFTWARE
 * RELEASES MADE TO RECEIVER'S SPECIFICATION OR TO CONFORM TO A PARTICULAR
 * STANDARD OR OPEN FORUM. RECEIVER'S SOLE AND EXCLUSIVE REMEDY AND AUTOCHIPS'S
 * ENTIRE AND CUMULATIVE LIABILITY WITH RESPECT TO THE AUTOCHIPS SOFTWARE
 * RELEASED HEREUNDER WILL BE, AT AUTOCHIPS'S OPTION, TO REVISE OR REPLACE THE
 * AUTOCHIPS SOFTWARE AT ISSUE, OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE
 * CHARGE PAID BY RECEIVER TO AUTOCHIPS FOR SUCH AUTOCHIPS SOFTWARE AT ISSUE.
 */

/*!
 * @file spm_drv.c
 *
 * @brief This file provides spm module integration functions.
 *
 */

/* PRQA S 0380 EOF */ /* Number of macro definitions exceeds 4095 */
/* PRQA S 0306,0303 EOF */ /* Type conversion. */
/* PRQA S 1503,1505,3408 EOF */ /* Is defined but not used. */

/* ===========================================  Includes  =========================================== */
#include "spm_hw.h"
#include "../ckgen/ckgen_hw.h"

/* ============================================  Define  ============================================ */
#define SPM_MODE_SWITCH_TIMEOUT_VALUE   (1000UL)
#define REG_DEBUG_MODE_ADDR             0x40008030UL
#define REG_PWR_MGR_SPLL_ADDR           0x4000803CUL
#define REG_PWR_MGR_XOSC_ADDR           0x40008048UL

/* ===========================================  Typedef  ============================================ */

/* ==========================================  Variables  =========================================== */
/* for SPM Event call back function */
spm_callback_t g_spmCallback = NULL;
/* for LVD Event call back function */
spm_callback_t g_lvdCallback = NULL;

/* ====================================  Functions declaration  ===================================== */

/* =====================================  Functions definition  ===================================== */

/* PRQA S 3006,2889,3415,1006 ++ */
/*!
 * @brief Set the power mode.
 *
 * @param[in] power mode
 *            - POWER_MODE_RUN
 *            - POWER_MODE_VLPR
 *            - POWER_MODE_STOP1
 *            - POWER_MODE_STOP2
 *            - POWER_MODE_VLPS
 *            - POWER_MODE_STANDBY
 * @return operation status
 *         - STATUS_SUCCESS: mode switch success
 *         - STATUS_ERROR: mode switch error
 */
status_t SPM_DRV_SetPowerMode(spm_power_mode_t mode)
{
    DEVICE_ASSERT((POWER_MODE_RUN == mode)   ||
                  (POWER_MODE_VLPR == mode)  ||
                  (POWER_MODE_STOP1 == mode) ||
                  (POWER_MODE_STOP2 == mode) ||
                  (POWER_MODE_VLPS == mode) ||
                  (POWER_MODE_STANDBY == mode && ADVANCED_OPTIMIZATION_SET));

    uint32_t csr, rcmEn = CKGEN->RCM_EN;
    volatile uint32_t timeout;
    spm_power_mode_t curMode;
    status_t ret = STATUS_SUCCESS;
    uint32_t rollbackPA5RegValue;
    uint32_t rollbackRcmRegValue;
#if SWITCH_SYSCLK_BEFORE_LP
    uint8_t sw = 0U;
#endif
    if ((POWER_MODE_STANDBY == mode) && !ADVANCED_OPTIMIZATION_SET)
    {
        return STATUS_UNSUPPORTED;
    }

    /* Set power mode */
    SPM_SetPowerMode(mode);

    if ((POWER_MODE_VLPS == mode) || (POWER_MODE_RUN == mode))
    {
        CKGEN->RCM_EN = rcmEn & (~(CKGEN_RCM_EN_PLL_UNLOCK_RST_EN_Msk | CKGEN_RCM_EN_XOSC_LOSS_RST_EN_Msk));
    }

    if ((POWER_MODE_RUN == mode) || (POWER_MODE_VLPR == mode)) /* run mode */
    {
        timeout = SPM_MODE_SWITCH_TIMEOUT_VALUE;
        do
        {
            curMode = SPM_GetPowerMode();
            timeout--;
        } while ((curMode != mode) && (timeout != 0U));

        if (0U == timeout)
        {
            ret = STATUS_ERROR;
        }
    }
    else /* stop/standby mode */
    {
        /* On VLPS/Standby mode reset, must first wakeup then reset. */
        if (POWER_MODE_VLPS == mode && !ADVANCED_OPTIMIZATION_SET)
        {
            /* Enable PA5 interrupt. */
            uint32_t regValue = PORTA->PCR[5U];
            rollbackPA5RegValue = regValue;
            regValue &= ~(PORT_PCR_MUX_Msk);
            regValue |= (1U << PORT_PCR_MUX_Pos);

            regValue &= ~(PORT_PCR_IRQC_Msk);
            regValue |= (0xAU << PORT_PCR_IRQC_Pos);
            PORTA->PCR[5U] = regValue;
            NVIC_EnableIRQ(PORTA_IRQn);

            /* Enable RCM wdg interrupt. */
            regValue = CKGEN->RCM_CTRL;
            rollbackRcmRegValue = regValue;
            regValue |= (CKGEN_RCM_CTRL_RST_GLB_INT_EN_Msk | CKGEN_RCM_CTRL_WDG_RST_INT_EN_Msk);
            regValue &= ~(CKGEN_RCM_CTRL_RST_DLY_TIME_Msk);
            regValue |= (0x1U << CKGEN_RCM_CTRL_RST_DLY_TIME_Pos);
            CKGEN->RCM_CTRL = regValue;
            NVIC_EnableIRQ(RCM_IRQn);
        }

        if (POWER_MODE_STANDBY == mode)
        {
            /* Clear standby wakeup status */
            SPM_ClearStandbyWakeupStatus();
        }
        /* Delay disable pll when enter vlps mode */
        if ((POWER_MODE_VLPS == mode) && SPM_GetSPLLStatus() && (1U == CKGEN_GetSourcSPLL(CKGEN)))
        {
            *(__IO uint32_t *)REG_DEBUG_MODE_ADDR = 0x78400001U;
            *(__IO uint32_t *)REG_PWR_MGR_SPLL_ADDR = 0x00024202U;
            *(__IO uint32_t *)REG_PWR_MGR_XOSC_ADDR = 0x01122008U;
#if SWITCH_SYSCLK_BEFORE_LP
            /* Switch sytem clock to vhsi before enter VLPS mode */
            CKGEN_CtrlRegisterLock(false);
            CKGEN_SetRunSysClkSrc(CKGEN, (uint32_t)CKGEN_SYSTEM_CLOCK_SRC_VHSI);
            sw = 1U;
#endif
        }
        /* Disable systick */
        csr = AC784X_SYSTICK->CSR;
        AC784X_SYSTICK->CSR = csr & (~AC784X_SysTick_CSR_ENABLE_Msk);
        /* Clear Pending Tick IRQ */
        SCB->ICSR |= SCB_ICSR_PENDSTCLR_Msk;
        /* Set the SLEEPDEEP bit to enable deep sleep mode */
        SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
#if defined (__GNUC__)
        __asm volatile("wfi");
#else
        __asm("wfi");
#endif
        /* Write systick back */
        AC784X_SYSTICK->CSR = csr;

        if (POWER_MODE_STANDBY == mode)
        {
            /* Enter standby mode fail, please check is there any interrupt before enter low power mode */
            ret = STATUS_ERROR;
        }
    }

    if ((POWER_MODE_VLPS == mode) || (POWER_MODE_RUN == mode))
    {
        /* Wait xosc ready */
        if (0U != (SPM->PWR_MGR_CFG1 & SPM_PWR_MGR_CFG1_XOSC_HSEEN_Msk))
        {
            timeout = 50000U;
            while (!SPM_GetXOSCStatus() && (timeout > 0U))
            {
                 timeout--;
             }
        }
#if SWITCH_SYSCLK_BEFORE_LP
        /* Switch sytem clock back */
        if (1U == sw)
        {
            /* Wait spll ready */
            timeout = 1000U;
            while (!SPM_GetSPLLStatus() && (timeout > 0U))
            {
                timeout--;
            }
            if (timeout != 0U)
            {
                CKGEN_SetRunSysClkSrc(CKGEN, (uint32_t)CKGEN_SYSTEM_CLOCK_SRC_SPLL);
            }
        }
#endif
        /* Write rcm_en back */
        CKGEN->RCM_EN = rcmEn;
    }

    /* On VLPS mode, rollback PA5,Rcm. */
    if (POWER_MODE_VLPS == mode && !ADVANCED_OPTIMIZATION_SET)
    {
        PORTA->PCR[5U] = rollbackPA5RegValue;
        CKGEN->RCM_CTRL = rollbackRcmRegValue;
    }

    #if CKGEN_AUTO_TEST_CLK
    if ((POWER_MODE_VLPR != mode) && SPM_GetSPLLStatus())
    {
        MODIFY_REG32(CKGEN->CLK_DIV2, CKGEN_CLK_DIV2_SPLL_DIV2_Msk, CKGEN_CLK_DIV2_SPLL_DIV2_Pos, 0U);
        for (uint32_t i = 0U; i < 100U; i++)
        {
            __asm("nop");
        }
        MODIFY_REG32(CKGEN->CLK_DIV2, CKGEN_CLK_DIV2_SPLL_DIV2_Msk, CKGEN_CLK_DIV2_SPLL_DIV2_Pos, 1U);
    }
    #endif

    return ret;
}
/* PRQA S 3006,2889,3415,1006 -- */

/*!
 * @brief Get the current power mode.
 *
 * @param[in] none
 * @return the current power mode, value can be
 *         - POWER_MODE_RUN
 *         - POWER_MODE_VLPR
 *         - POWER_MODE_STOP1
 *         - POWER_MODE_STOP2
 *         - POWER_MODE_VLPS
 *         - POWER_MODE_STANDBY
 */
spm_power_mode_t SPM_DRV_GetPowerMode(void)
{
    return SPM_GetPowerMode();
}

/*!
 * @brief Get the power control mode.
 *
 * @param[in] none
 * @return the power control mode, value can be
 *         - POWER_MODE_RUN
 *         - POWER_MODE_VLPR
 *         - POWER_MODE_STOP1
 *         - POWER_MODE_STOP2
 *         - POWER_MODE_VLPS
 *         - POWER_MODE_STANDBY
 */
spm_power_mode_t SPM_DRV_GetPowerControlMode(void)
{
    return SPM_GetPowerControlMode();
}

/*!
 * @brief Select lvd threshold.
 *
 * @param[in] threshold value
 *            - LVD_LOW_THRESHOLD
 *            - LVD_HIGH_THRESHOLD
 * @return none
 */
void SPM_DRV_SelectLVDThreshold(spm_lvd_threshold_t threshold)
{
    DEVICE_ASSERT((LVD_LOW_THRESHOLD == threshold) || (LVD_HIGH_THRESHOLD == threshold));

    SPM_SelectLVDThreshold(threshold);
}

/*!
 * @brief Select lvr threshold, it is recommended to use low gear only in VLPS mode.
 *
 * @param[in] threshold value
 *            - LVR_HIGH_THRESHOLD
 *            - LVR_LOW_THRESHOLD
 * @return none
 */
void SPM_DRV_SelectLVRThreshold(spm_lvr_threshold_t threshold)
{
    DEVICE_ASSERT((LVR_LOW_THRESHOLD == threshold) || (LVR_HIGH_THRESHOLD == threshold));

    SPM_SelectLVRThreshold(threshold);
}

/*!
 * @brief Set standby wakeup.
 *
 * @param[in] source: standby wakeup source
 * @param[in] type: standby wakeup type
 * @return none
 */
void SPM_DRV_SetStandbyWakeup(spm_standby_wakeup_source_t source, spm_standby_wakeup_t type)
{
    DEVICE_ASSERT((source <= STANDBY_WAKEUP_RTC) && (type <= STANDBY_WAKEUP_EITHER_EDGE));

    SPM_SetStandbyWakeup(source, type);
}

/*!
 * @brief Get standby wakeup status.
 *
 * @param[in] none
 * @return standby wakeup status
 */
uint32_t SPM_DRV_GetStandbyWakeupStatus(void)
{
    return SPM_GetStandbyWakeupStatus();
}

/*!
 * @brief Clear standby wakeup status.
 *
 * @param[in] none
 * @return none
 */
void SPM_DRV_ClearStandbyWakeupStatus(void)
{
    SPM_ClearStandbyWakeupStatus();
}

/*!
 * @brief Get module version information.
 *
 * @param[out] versionInfo: Module version information address.
 * @return void
 */
void SPM_DRV_GetVersionInfo(Drv_VersionInfo_Type *versionInfo)
{
    DEVICE_ASSERT(versionInfo != NULL);

    versionInfo->moduleID = SPM_HAL_MODULE_ID;
    versionInfo->majorVersion = SPM_HAL_SW_MAJOR_VERSION;
    versionInfo->minorVersion = SPM_HAL_SW_MINOR_VERSION;
    versionInfo->patchVersion = SPM_HAL_SW_PATCH_VERSION;
}

/*!
 * @brief Set SPM IRQHandler callback function.
 *
 * @param[in] eventFunc: the pointer of the call back function, which will be called in SPM_IRQHandler
 * @return none
 */
void SPM_DRV_InstallCallback(const spm_callback_t eventFunc)
{
    g_spmCallback = eventFunc;
}

/*!
 * @brief Set LVD Handler callback function.
 *
 * @param[in] eventFunc: the pointer of the call back function, which will be called in LVD_IRQHandler
 * @return none
 */
void LVD_DRV_InstallCallback(const spm_callback_t eventFunc)
{
    g_lvdCallback = eventFunc;
}

/* =============================================  EOF  ============================================== */
