/*
 * Copyright (c) 2022, Shenzhen CVA Innovation CO.,LTD
 * All rights reserved.
 *
 * Shenzhen CVA Innovation CO.,LTD (CVA chip) is supplying this file for use
 * exclusively with CVA's microcontroller products. This file can be freely
 * distributed within development tools that are supporting such microcontroller
 * products.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS".  NO WARRANTIES, WHETHER EXPRESS, IMPLIED
 * OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE.
 * CVA SHALL NOT, IN ANY CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL,
 * OR CONSEQUENTIAL DAMAGES, FOR ANY REASON WHATSOEVER.
 */

/*******************************************************************************
 * the includes
 ******************************************************************************/

#include "../cpu/cpu_drv.h"
#include "power_drv.h"
#include "pmc_reg.h"

/*******************************************************************************
 * the defines
 ******************************************************************************/

/*******************************************************************************
 * the typedefs
 ******************************************************************************/

/*******************************************************************************
 * the globals
 ******************************************************************************/

/*******************************************************************************
 * the functions
 ******************************************************************************/

void PowerDrv_Init(PowerDrvType *obj, struct _PmcRegType_ *pmcReg, struct _Scg2RegType_ *scg2Reg, struct _Scg1RegType_ *scg1Reg, struct _Scg0RegType_ *scg0Reg)
{
    obj->pmcReg  = pmcReg;
    obj->scg2Reg = scg2Reg;
    obj->scg1Reg = scg1Reg;
    obj->scg0Reg = scg0Reg;
}

void PowerDrv_UnlockRegWriteAccess(PowerDrvType *obj)
{
    PmcReg_SetKey(obj->pmcReg, 0x20220509);
}

void PowerDrv_LockRegWriteAccess(PowerDrvType *obj)
{
    PmcReg_SetKey(obj->pmcReg, 0);
}

void PowerDrv_EnableLvwWarning(PowerDrvType *obj)
{
    PmcReg_SetDAIntfCtrlEnLvw(obj->pmcReg, true);
}

void PowerDrv_DisableLvwWarning(PowerDrvType *obj)
{
    PmcReg_SetDAIntfCtrlEnLvw(obj->pmcReg, false);
}

void PowerDrv_SetLvwLevel(PowerDrvType *obj, PowerDrvLvwLevelType level)
{
    PmcReg_SetDAIntfCtrlSelLvw(obj->pmcReg, level);
}

void PowerDrv_EnableLvdDetect(PowerDrvType *obj)
{
    PmcReg_SetDAIntfCtrlEnLvd(obj->pmcReg, true);
}

void PowerDrv_DisableLvdDetect(PowerDrvType *obj)
{
    PmcReg_SetDAIntfCtrlEnLvd(obj->pmcReg, false);
}

void PowerDrv_EnableOtpWarning(PowerDrvType *obj)
{
    PmcReg_SetDAIntfCtrlOtpEn(obj->pmcReg, true);
}

void PowerDrv_DisableOtpWarning(PowerDrvType *obj)
{
    PmcReg_SetDAIntfCtrlOtpEn(obj->pmcReg, false);
}

void PowerDrv_EnableFldoDetect(PowerDrvType *obj)
{
    PmcReg_SetDAIntfCtrlEnFldoVDet(obj->pmcReg, true);
}

void PowerDrv_DisableFldoDetect(PowerDrvType *obj)
{
    PmcReg_SetDAIntfCtrlEnFldoVDet(obj->pmcReg, false);
}

void PowerDrv_ReleasePadKeep(PowerDrvType *obj)
{
    /* this bit is W1C, user should release it as early as possible */
    PowerDrv_UnlockRegWriteAccess(obj);
    PmcReg_ClearStatusPadKeep(obj->pmcReg);
    PowerDrv_LockRegWriteAccess(obj);
}

uint8_t PowerDrv_GetStbAbortFlag(PowerDrvType *obj)
{
    uint8_t retval = 0;
    retval         = PmcReg_GetStatusStdbyAbortSts(obj->pmcReg);
    return retval;
}

void PowerDrv_ClrStbAbortFlag(PowerDrvType *obj)
{
    if(PmcReg_GetStatusStdbyAbortSts(obj->pmcReg))
    {
        /* only clear when this bit is set, and this bit is W1C */
        PmcReg_ClearStatusStdbyAbortSts(obj->pmcReg);
    }
}

void PowerDrv_ClrAllWakupFlags(PowerDrvType *obj)
{
    /* clear all the wakeup flags */
    PowerDrv_UnlockRegWriteAccess(obj);
    PmcReg_ClearWakupFlgCmpWakupEvtFlag(obj->pmcReg);
    PmcReg_ClearWakupFlgLptmrWakupEvtFlg(obj->pmcReg);
    PmcReg_ClearWakupFlgLpitWakupEvtFlg(obj->pmcReg);
    PmcReg_ClearWakupFlgRtcWakupEvtFlg(obj->pmcReg);
    PmcReg_ClearWakupFlgExtrstWakupEvtFlg(obj->pmcReg);
    PmcReg_ClearWakupFlgPortEWakupEvtFlg(obj->pmcReg);
    PmcReg_ClearWakupFlgPortDWakupEvtFlg(obj->pmcReg);
    PmcReg_ClearWakupFlgPortCWakupEvtFlg(obj->pmcReg);
    PmcReg_ClearWakupFlgPortBWakupEvtFlg(obj->pmcReg);
    PmcReg_ClearWakupFlgPortAWakupEvtFlg(obj->pmcReg);
    PowerDrv_LockRegWriteAccess(obj);
}

void PowerDrv_ClrWakeupFlag(PowerDrvType *obj, PowerDrvWakeupFlagType flag)
{
    PowerDrv_UnlockRegWriteAccess(obj);
    switch(flag)
    {
    case POWERDRV_PORTA_WU:
        PmcReg_ClearWakupFlgPortAWakupEvtFlg(obj->pmcReg);
        break;
    case POWERDRV_PORTB_WU:
        PmcReg_ClearWakupFlgPortBWakupEvtFlg(obj->pmcReg);
        break;
    case POWERDRV_PORTC_WU:
        PmcReg_ClearWakupFlgPortCWakupEvtFlg(obj->pmcReg);
        break;
    case POWERDRV_PORTD_WU:
        PmcReg_ClearWakupFlgPortDWakupEvtFlg(obj->pmcReg);
        break;
    case POWERDRV_PORTE_WU:
        PmcReg_ClearWakupFlgPortEWakupEvtFlg(obj->pmcReg);
        break;
    case POWERDRV_RSTPIN_WU:
        PmcReg_ClearWakupFlgExtrstWakupEvtFlg(obj->pmcReg);
        break;
    case POWERDRV_RTC_WU:
        PmcReg_ClearWakupFlgRtcWakupEvtFlg(obj->pmcReg);
        break;
    case POWERDRV_LPIT_WU:
        PmcReg_ClearWakupFlgLpitWakupEvtFlg(obj->pmcReg);
        break;
    case POWERDRV_LPTMR_WU:
        PmcReg_ClearWakupFlgLptmrWakupEvtFlg(obj->pmcReg);
        break;
    case POWERDRV_CMP_WU:
        PmcReg_ClearWakupFlgCmpWakupEvtFlag(obj->pmcReg);
        break;
    default:
        break;
    }
    PowerDrv_LockRegWriteAccess(obj);
}

PowerDrvWakeupFlagType PowerDrv_GetWakeupSource(PowerDrvType *obj)
{
    PowerDrvWakeupFlagType rtv = POWERDRV_NO_WU;
    /* scan and return the wakeup flag which is set */
    for(uint8_t i = POWERDRV_PORTA_WU; i <= POWERDRV_CMP_WU; i++)
    {
        uint32_t temp = 1;
        temp <<= i;
        if(obj->pmcReg->WAKUP_FLG & temp)
        {
            rtv = (PowerDrvWakeupFlagType)i;
        }
    }
    return rtv;
}

void PowerDrv_StandbyModePreTask(PowerDrvType *obj)
{
    /* make sure these bits setting are correct before standby mode */
    PowerDrv_DisableOtpWarning(obj);
    PowerDrv_DisableLvdDetect(obj);
    PowerDrv_DisableLvwWarning(obj);
    Scg0Reg_SetOscCtrlEnSosc(obj->scg0Reg, 0);
    Scg0Reg_SetOscCtrlEnExtClk(obj->scg0Reg, 0);
    Scg0Reg_SetPllCtrl0PllEn(obj->scg0Reg, 0);
}

void PowerDrv_SetPowerMode(PowerDrvType *obj, PowerDrvPowerModeType mode)
{
    PowerDrv_UnlockRegWriteAccess(obj);
    Scg0Reg_SetKey(obj->scg0Reg, 0x19962020);
    /* target mode is stop mode */
    if(mode == POWERDRV_POWER_MODE_STOP)
    {
        /* enable stop mode */
        PmcReg_SetGlbCtrlStdbyEn(obj->pmcReg, 0);
        PmcReg_SetGlbCtrlStopEn(obj->pmcReg, 1);
        PmcReg_SetGlbCtrlCpuRetionEn(obj->pmcReg, 0);
        Scg1Reg_SetCpuModeRet(obj->scg1Reg, 0);
        SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;
    }
    /* target mode is standby mode */
    else
    {
        Scg2Reg_SetCsrSrcSys(obj->scg2Reg, 0);
        PmcReg_SetGlbCtrlStopEn(obj->pmcReg, 0);
        PmcReg_SetGlbCtrlStdbyEn(obj->pmcReg, 1);
        PowerDrv_StandbyModePreTask(obj);
        if(mode == POWERDRV_POWER_MODE_STANDBY_WITH_CPU_RETENTION)
        {
            PmcReg_SetGlbCtrlCpuRetionEn(obj->pmcReg, 1);
            Scg1Reg_SetCpuModeRet(obj->scg1Reg, 1);
        }
        else
        {
            PmcReg_SetGlbCtrlCpuRetionEn(obj->pmcReg, 0);
            Scg1Reg_SetCpuModeRet(obj->scg1Reg, 0);
        }
        PowerDrv_ClrAllWakupFlags(obj);
        SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
    }
    PowerDrv_LockRegWriteAccess(obj);
    Scg0Reg_SetKey(obj->scg0Reg, 0);
    __WFI();
}
