/**
 * @file hv_cal_Timer.c
 * @brief timer driver chip abstract layer file.
 * @details This file provides the following functions: \n
 *          (1) system timer init/cleanup, start/stop\n
 *          (2) system timer set/read counter \n
 *          (3) system timer set/cleanup irq, set irq handler \n
 *
 * @author HiView SoC Software Team
 * @version 1.0.0
 * @date 2023-06-12
 * @copyright Copyright(c),2023-6, Hiview Software. All rights reserved.
 * @par History:
 * <table>
 * <tr><th>Author       <th>Date            <th>Change Description
 * </table>
 */

#include "hv_vos_Comm.h"
#include "hv_chip_Config.h"
#include "hv_comm_Assert.h"
#include "hv_comm_Define.h"
#include "hv_cal_Timer.h"

static HwTmr *s_pstTmrCfg[TMR_CH_MAX] = {0};
static BOOL  s_bTmrIrqInit = HV_FALSE;
#define TMR_CLEANUP_WAIT_MAX_TIMES      (5)//unit:1ns

/*
BASE_TMR:0x0000
[BASE_TMR0]:    [BASE_TMR] + 0x0000
[BASE_TMR1]:    [BASE_TMR] + 0x0100
[BASE_TMR2]:    [BASE_TMR] + 0x0200
[BASE_TMR3]:    [BASE_TMR] + 0x0300
*/

/** get system timer address offset
 *  @param[in] channel HV_TMR_CH_E parameters
 *  @return offset value (32bit)
 */
static inline UINT32 Hv_Tmr_GetBaseOffset(TmrChannel enChannel)
{
    HV_ASSERT(enChannel < TMR_CH_MAX);
    return (enChannel - TMR_CH_0) * 0x100;
}

/** get system timer irq number
 *  @param[in] channel HV_TMR_CH_E parameters
 *  @return irq number (32bit)
 */
static inline UINT32 Hv_Tmr_GetIrqNum(HwTmr *pstTmr)
{
    UNUSED(pstTmr);
    return HV_CHIP_IRQ_TIMER0_CH0;
}

/** set system timer config register values
 *  @param[in] self HwTmr structure pointer
 */
static VOID Hv_Tmr_SetConfig(HwTmr *pstTmr)
{
    UINT32 uiVal = 0;
    UINT32 uiBaseOfst = 0;
    HwTmrConfig *pstTmrCfg = NULL;
    HV_ASSERT_VALID_PTR_VOID(pstTmr);
    pstTmrCfg = &pstTmr->stTmrCfg;
    uiBaseOfst = pstTmr->uiTmrBaseOffset;

    /* Set the Counter Clock Divided Value */
    HV_W32(CPU_SYS_TM0_REGC_TMRDIV0 + uiBaseOfst, pstTmrCfg->enDivMode);

    /* Set the Counter Enable Value */
    uiVal = HV_R32(CPU_SYS_TM0_REGC_TMRC0 + uiBaseOfst);
    cpu_sys_tm0_regc_tmrc0(uiVal)->TCE0 = TMRC_TCE_STOP;
    cpu_sys_tm0_regc_tmrc0(uiVal)->CCDE0 = pstTmrCfg->enClockDividedEnable;
    cpu_sys_tm0_regc_tmrc0(uiVal)->CRE0 = pstTmrCfg->enClrEnable ? TMRC_CRE_CLR_ENABLE : TMRC_CRE_CLR_DISABLE;
    HV_W32(CPU_SYS_TM0_REGC_TMRC0 + uiBaseOfst, uiVal);

    /* Set the Timer InteruiVal Interrupt Status */
    uiVal = HV_R32(CPU_SYS_TM0_REGC_TMRINT0 + uiBaseOfst);
    cpu_sys_tm0_regc_tmrint0(uiVal)->TIIS0 = pstTmrCfg->enIntStatus;
    HV_W32(CPU_SYS_TM0_REGC_TMRINT0 + uiBaseOfst, uiVal);

    /* Set the Timer Compare Value */
    HV_W32(CPU_SYS_TM0_REGC_TMRCMP0 + uiBaseOfst, pstTmrCfg->uiCompareValue);

    /* Set the Timer Compare Value */
    uiVal = HV_R32(CPU_SYS_TM0_REGC_TMRIVL0 + uiBaseOfst);
    cpu_sys_tm0_regc_tmrivl0(uiVal)->TIIE0 = pstTmrCfg->enIntEnable;
    cpu_sys_tm0_regc_tmrivl0(uiVal)->TZCE0 = pstTmrCfg->enZeroClrEnable;
    HV_W32(CPU_SYS_TM0_REGC_TMRIVL0 + uiBaseOfst, uiVal);

    /* Set the Counter to zero */
    HV_W32(CPU_SYS_TM0_REGC_TMRCNT0 + uiBaseOfst, 0);
    return;
}

/**@brief init tmr parameters
 *  @param params pointer to tmr configure parameters
 *  @return HwTmr structure pointer
 */
HwTmr *Hv_Cal_Tmr_Init(HwTmrConfig *pstTmrCfg)
{
    HwTmr *pstTmr = NULL;
    HV_ASSERT_VALID_PTR_RET(pstTmrCfg, NULL);
    HV_ASSERT_TRUE_RET((pstTmrCfg->enChannel < TMR_CH_MAX), NULL);

    /* Alloc a object and init it */
    pstTmr = HV_MALLOC_OBJECT(HwTmr);
    HV_ASSERT_VALID_PTR_RET(pstTmr, NULL);

    s_pstTmrCfg[pstTmrCfg->enChannel] = pstTmr;
    HV_MEMCPY(&pstTmr->stTmrCfg, pstTmrCfg, sizeof(*pstTmrCfg));

    /* Set channel timer base register addr offset */
    pstTmr->uiTmrBaseOffset = Hv_Tmr_GetBaseOffset(pstTmrCfg->enChannel);

    /* Set the Timer Base configuration */
    Hv_Tmr_SetConfig(pstTmr);
    return pstTmr;
}

/**@brief stop timer
 *  @param[in] self HwTmr structure pointer
 */
VOID Hv_Cal_Tmr_Stop(HwTmr *pstTmr)
{
    UINT32 uiVal = 0;
    UINT32 uiBaseOfst = 0;
    HV_ASSERT_VALID_PTR_VOID(pstTmr);
    uiBaseOfst = pstTmr->uiTmrBaseOffset;
    /* Set the Counter Enable value */
    uiVal = HV_R32(CPU_SYS_TM0_REGC_TMRC0 + uiBaseOfst);
    cpu_sys_tm0_regc_tmrc0(uiVal)->TCE0 = TMRC_TCE_STOP;
    cpu_sys_tm0_regc_tmrc0(uiVal)->CRE0 = pstTmr->stTmrCfg.enClrEnable ? TMRC_CRE_CLR_ENABLE : TMRC_CRE_CLR_DISABLE;
    HV_W32(CPU_SYS_TM0_REGC_TMRC0 + uiBaseOfst, uiVal);
    return;
}

/**@brief Cleanup tmr parameters
 *  @param self HwTmr structure pointer
 */
VOID Hv_Cal_Tmr_Cleanup(HwTmr *pstTmr)
{
    UINT32 uiDelay = 0;
    HV_ASSERT_VALID_PTR_VOID(pstTmr);
    HV_ASSERT_TRUE_VOID(pstTmr->stTmrCfg.enChannel < TMR_CH_MAX);
    HV_ASSERT_TRUE_VOID(s_pstTmrCfg[pstTmr->stTmrCfg.enChannel] == pstTmr);
    Hv_Cal_Tmr_Stop(pstTmr);
    /* Next to free tmr object, here to check and wait irq handler exit to avoid access NULL pointer
     * If current TMR irq handler is in ISR, then delay to wait irq handler exit.
     */
    while (pstTmr->bInISR
           && (uiDelay < TMR_CLEANUP_WAIT_MAX_TIMES))
    {
        Hv_Vos_Delayus(1);
        uiDelay++;
    }
    HV_ASSERT_PEEK_TRUE(s_pstTmrCfg[pstTmr->stTmrCfg.enChannel]->bInISR == HV_FALSE);
    s_pstTmrCfg[pstTmr->stTmrCfg.enChannel] = NULL;
    HV_FREE_OBJECT(pstTmr);
    return;
}

/**@brief set tmr ms counter register values, use before Hv_Cal_Tmr_Start
 *  @param[in] self HwTmr structure pointer
 *  @param[in] uiMsCounts count ms time
 */
VOID Hv_Cal_Tmr_SetMsCounter(HwTmr *pstTmr, UINT32 uiMsCounts)
{
    UINT32 uiDivMode = 0;
    UINT32 uiBaseClk = 0;
    UINT32 uiCmpVal  = 0;
    HV_ASSERT_VALID_PTR_VOID(pstTmr);
    uiDivMode = (UINT32)pstTmr->stTmrCfg.enDivMode;
    uiBaseClk = HV_CHIP_CLK_APB;
    uiCmpVal = (uiBaseClk / HW_TMR_HZ) * uiMsCounts  / (2 << uiDivMode);
    pstTmr->stTmrCfg.uiCompareValue = uiCmpVal;
    /* Set the Timer Compare Value */
    HV_W32(CPU_SYS_TM0_REGC_TMRCMP0 + pstTmr->uiTmrBaseOffset, uiCmpVal);
    return;
}

/**@brief system timer ISR handler function
 *  @param[in] irqNum interrupt Number value
 *  @param[in] arg pointer to tmr configure parameters
 *  @param[out] ISR HV_VOS_ISR_RESULT_E result
 */
HV_VOS_ISR_RESULT_E Hv_Cal_Tmr_IrqHandler(UINT32 uiIrqNum, VOID *pArg)
{
    HwTmr *pstTmr = NULL;
    UINT32 uiVal = 0;
    TmrChannel enChId = 0;
    UNUSED(pArg);
    for (enChId = TMR_CH_0; enChId < TMR_CH_MAX; enChId++)
    {
        pstTmr = s_pstTmrCfg[enChId];
        if (!pstTmr)
        {
            continue;
        }
        pstTmr->bInISR = HV_TRUE;
        if (pstTmr->stTmrCfg.enIntEnable == TMRIVL_TIIE_INT_ENABLE)//enable irq
        {
            uiVal = HV_R32(CPU_SYS_TM0_REGC_TMRINT0 + pstTmr->uiTmrBaseOffset);
            if (cpu_sys_tm0_regc_tmrint0(uiVal)->TIIS0 == TMRINT_TIIS_INT_STAT_MATCH)
            {
                if (pstTmr->stTmrCfg.pfTmrCb)
                {
                    pstTmr->stTmrCfg.pfTmrCb(uiIrqNum);
                }

                //clear int status
                cpu_sys_tm0_regc_tmrint0(uiVal)->TIIS0 = TMRINT_TIIS_INT_STAT_NO_MATCH;
                HV_W32(CPU_SYS_TM0_REGC_TMRINT0 + pstTmr->uiTmrBaseOffset, uiVal);
            }
        }
        pstTmr->bInISR = HV_FALSE;
    }
    return HV_VOS_ISR_HANDLED;
}

/**@brief set system timer interrupt
 *  @param self HwTmr structure pointer
 */
VOID Hv_Cal_Tmr_SetInterrupt(HwTmr *pstTmr)
{
    HV_ASSERT_VALID_PTR_VOID(pstTmr);
    HV_ASSERT_TRUE_VOID(pstTmr->stTmrCfg.enChannel < TMR_CH_MAX);
    HV_ASSERT_TRUE_VOID(s_pstTmrCfg[pstTmr->stTmrCfg.enChannel] == pstTmr);
    s_pstTmrCfg[pstTmr->stTmrCfg.enChannel]->bIntSet = HV_TRUE;
    if (s_bTmrIrqInit != HV_TRUE)
    {
        UINT32 uiIrqNum = Hv_Tmr_GetIrqNum(NULL);
        Hv_Vos_ConfigIrq(uiIrqNum, HV_TRUE, 0);
        Hv_Vos_AttachIsr(uiIrqNum, Hv_Cal_Tmr_IrqHandler, NULL);
        Hv_Vos_UnmaskIrq(uiIrqNum);
        s_bTmrIrqInit = HV_TRUE;
    }
    return;
}

/**@brief clean system timer interrupt
 *  @param self HwTmr structure pointer
 */
VOID Hv_Cal_Tmr_ClearInterrupt(HwTmr *pstTmr)
{
    TmrChannel enChId = 0;
    BOOL bAllCleanUp = HV_TRUE;
    UINT32 uiVal = 0;
    HV_ASSERT_VALID_PTR_VOID(pstTmr);
    HV_ASSERT_TRUE_VOID((pstTmr->stTmrCfg.enChannel < TMR_CH_MAX));
    HV_ASSERT_TRUE_VOID(s_pstTmrCfg[pstTmr->stTmrCfg.enChannel] == pstTmr);
    s_pstTmrCfg[pstTmr->stTmrCfg.enChannel]->bIntSet = HV_FALSE;

    //Clear int status
    uiVal = HV_R32(CPU_SYS_TM0_REGC_TMRINT0 + pstTmr->uiTmrBaseOffset);
    cpu_sys_tm0_regc_tmrint0(uiVal)->TIIS0 = TMRINT_TIIS_INT_STAT_NO_MATCH;
    HV_W32(CPU_SYS_TM0_REGC_TMRINT0 + pstTmr->uiTmrBaseOffset, uiVal);
    HV_LOG_INFO(TIMER, "Clean up HwTmr ch[%d] Interrupt\n", pstTmr->stTmrCfg.enChannel);
    //Clean up All interrupts
    for (enChId = TMR_CH_0; enChId < TMR_CH_MAX; enChId++)
    {
        if (s_pstTmrCfg[enChId]
            && s_pstTmrCfg[enChId]->bIntSet == HV_TRUE)
        {
            bAllCleanUp = HV_FALSE;
        }
    }

    if (bAllCleanUp && s_bTmrIrqInit == HV_TRUE)
    {
        UINT32 uiIrqNum = 0;
        uiIrqNum = Hv_Tmr_GetIrqNum(NULL);
        Hv_Vos_ConfigIrq(uiIrqNum, HV_FALSE, 0);
        Hv_Vos_MaskIrq(uiIrqNum);
        Hv_Vos_DetachIsr(uiIrqNum, Hv_Cal_Tmr_IrqHandler);
        s_bTmrIrqInit = HV_FALSE;
        HV_LOG_INFO(TIMER, "Clean up HwTmr All Interrupts.\n");
    }

    return;
}

/**@brief start system timer
 *  @param self HwTmr structure pointer
 */
VOID Hv_Cal_Tmr_Start(HwTmr *pstTmr)
{
    UINT32 uiVal = 0;
    UINT32 uiOffset = 0;
    HV_ASSERT_VALID_PTR_VOID(pstTmr);
    uiOffset = pstTmr->uiTmrBaseOffset;
    /* Set the Counter Enable value */
    uiVal = HV_R32(CPU_SYS_TM0_REGC_TMRC0 + uiOffset);
    cpu_sys_tm0_regc_tmrc0(uiVal)->TCE0 = TMRC_TCE_RUN;
    cpu_sys_tm0_regc_tmrc0(uiVal)->CRE0 = pstTmr->stTmrCfg.enClrEnable ? TMRC_CRE_CLR_DISABLE : TMRC_CRE_CLR_ENABLE;
    HV_W32(CPU_SYS_TM0_REGC_TMRC0 + uiOffset, uiVal);
    return;
}

/**@brief read system timer counter
 *  @param[in] self HwTmr structure pointer
 *  @param[out] read counter values
 */
UINT32 Hv_Cal_Tmr_ReadCounter(HwTmr *pstTmr)
{
    HV_ASSERT_VALID_PTR_RET(pstTmr, INVALID_PARAM_UINT32);
    return HV_R32(CPU_SYS_TM0_REGC_TMRCNT0 + pstTmr->uiTmrBaseOffset);
}

/** set system timer counter
 *  @param[in] self HwTmr structure pointer
 *  @param[in] count count values
 */
VOID Hv_Cal_Tmr_SetCounter(HwTmr *pstTmr, UINT32 uiCount)
{
    HV_ASSERT_VALID_PTR_VOID(pstTmr);
    HV_W32(CPU_SYS_TM0_REGC_TMRCNT0 + pstTmr->uiTmrBaseOffset, uiCount);
    return;
}

