/*
 * 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_SetKeyVal(obj->pmcReg, 0x1015FF11);
}

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

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

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

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

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

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

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

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

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

void PowerDrv_ClrAllWakupFlags(PowerDrvType *obj)
{
    /* clear all the wakeup flags */
    PowerDrv_UnlockRegWriteAccess(obj);
    PmcReg_ClearWkSrcTouchWakeup(obj->pmcReg);
    PmcReg_ClearWkSrcLptmrWakeup(obj->pmcReg);
    PmcReg_ClearWkSrcLpitWakeup(obj->pmcReg);
    PmcReg_ClearWkSrcRtcWakeup(obj->pmcReg);
    PmcReg_ClearWkSrcResetWakeup(obj->pmcReg);
    PmcReg_ClearWkSrcPortcWakeup(obj->pmcReg);
    PmcReg_ClearWkSrcPortbWakeup(obj->pmcReg);
    PmcReg_ClearWkSrcPortaWakeup(obj->pmcReg);
    PowerDrv_LockRegWriteAccess(obj);
}

void PowerDrv_ClrWakeupFlag(PowerDrvType *obj, PowerDrvWakeupFlagType flag)
{
    PowerDrv_UnlockRegWriteAccess(obj);
    switch(flag)
    {
    case POWERDRV_PORTA_WU:
        PmcReg_ClearWkSrcPortaWakeup(obj->pmcReg);
        break;
    case POWERDRV_PORTB_WU:
        PmcReg_ClearWkSrcPortbWakeup(obj->pmcReg);
        break;
    case POWERDRV_PORTC_WU:
        PmcReg_ClearWkSrcPortcWakeup(obj->pmcReg);
        break;
    case POWERDRV_RSTPIN_WU:
        PmcReg_ClearWkSrcResetWakeup(obj->pmcReg);
        break;
    case POWERDRV_RTC_WU:
        PmcReg_ClearWkSrcRtcWakeup(obj->pmcReg);
        break;
    case POWERDRV_LPIT_WU:
        PmcReg_ClearWkSrcLpitWakeup(obj->pmcReg);
        break;
    case POWERDRV_LPTMR_WU:
        PmcReg_ClearWkSrcLptmrWakeup(obj->pmcReg);
        break;
    case POWERDRV_TOUCH_WU:
        PmcReg_ClearWkSrcTouchWakeup(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_TOUCH_WU; i++)
    {
        uint32_t temp = 1;
        temp <<= i;
        if(obj->pmcReg->WKSRC & temp)
        {
            rtv = (PowerDrvWakeupFlagType)i;
        }
    }
    return rtv;
}

void PowerDrv_SetHandShakeValidTime(PowerDrvType *obj, uint32_t value)
{
    /* make sure the time is no more than the touch sense period*/
    PowerDrv_UnlockRegWriteAccess(obj);
    PmcReg_SetTkctlTkPmc2cpuHshkVal(obj->pmcReg, value);
    PowerDrv_LockRegWriteAccess(obj);
}

void PowerDrv_StandbyModePreTask(PowerDrvType *obj)
{
    /* make sure these bits setting are correct before standby mode */
    PowerDrv_DisableOtpWarning(obj);
    PowerDrv_DisableLvdDetect(obj);
    PowerDrv_DisableLvwWarning(obj);
    Scg2Reg_SetCsrSrcSys(obj->scg2Reg, 0);
    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_SetGlbCtlCpuRetetionEn(obj->pmcReg, 0);
        Scg1Reg_SetCpuModeRet(obj->scg1Reg, 0);
        SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;
    }
    /* target mode is standby mode */
    else
    {
        PowerDrv_StandbyModePreTask(obj);
        if(mode == POWERDRV_POWER_MODE_STANDBY_WITH_CPU_RETENTION)
        {
            PmcReg_SetGlbCtlCpuRetetionEn(obj->pmcReg, 1);
            Scg1Reg_SetCpuModeRet(obj->scg1Reg, 1);
            PmcReg_SetTklpTkLpen(obj->pmcReg, 0);
        }
        else if(mode == POWERDRV_POWER_MODE_STANDBY_WITH_CPU_RETENTION_WITH_TOUCH)
        {
            PmcReg_SetGlbCtlCpuRetetionEn(obj->pmcReg, 1);
            Scg1Reg_SetCpuModeRet(obj->scg1Reg, 1);
            /* User should set the proper handshake time before this polling */
            while(PmcReg_GetTkctlTkPmc2cpuHshkVld(obj->pmcReg) == 0)
            {
            }
            PmcReg_SetTklpTkLpen(obj->pmcReg, 1);
        }
        else if(mode == POWERDRV_POWER_MODE_STANDBY_WITHOUT_CPU_RETENTION)
        {
            PmcReg_SetGlbCtlCpuRetetionEn(obj->pmcReg, 0);
            Scg1Reg_SetCpuModeRet(obj->scg1Reg, 0);
            PmcReg_SetTklpTkLpen(obj->pmcReg, 0);
        }
        else
        {
            PmcReg_SetGlbCtlCpuRetetionEn(obj->pmcReg, 0);
            Scg1Reg_SetCpuModeRet(obj->scg1Reg, 0);
            /* User should set the proper handshake time before this polling */
            while(PmcReg_GetTkctlTkPmc2cpuHshkVld(obj->pmcReg) == 0)
            {
            }
            PmcReg_SetTklpTkLpen(obj->pmcReg, 1);
        }
        PowerDrv_ClrAllWakupFlags(obj);
        SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
    }
    PowerDrv_LockRegWriteAccess(obj);
    Scg0Reg_SetKey(obj->scg0Reg, 0);
    __WFI();
}
