/* SPDX-License-Identifier: LGPL-2.1-only OR BSD-3-Clause */
/******************************************************************************
 *
 * This file is provided under a dual license.  When you use or
 * distribute this software, you may choose to be licensed under
 * version 2.1 of the GNU Lesser General Public License
 * ("LGPLv2.1 License") or BSD License.
 *
 * LGPLv2.1 License
 *
 * Copyright(C) 2019 MediaTek Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2.1 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html for more
 * details.
 *
 * BSD LICENSE
 *
 * Copyright(C) 2019 MediaTek Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *  * Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *****************************************************************************/

/*-----------------------------------------------------------------------------
 *
 * $Author: brianpc.huang $
 * $Date: 2015/05/29 $
 * $RCSfile: timer.c,v $
 * $Revision: #1 $
 *
 *---------------------------------------------------------------------------*/

/** @file timer.c
 *  timer.c provides timer function from boot time.
 */


//-----------------------------------------------------------------------------
// Include files
//-----------------------------------------------------------------------------

#include "x_bim.h"
#include "x_timer.h"
#include "x_assert.h"
// #include "x_os.h"
#include "x_ckgen.h"


//-----------------------------------------------------------------------------
// Constant definitions
//-----------------------------------------------------------------------------

#define TICKS_PER_SECOND            1

// Valid TIMER_ID: 0, 1, 2
//
#define TIMER_ID                    2

#define TIMER_VECTOR                (VECTOR_T0 + TIMER_ID)
#define TIMER_IRQ                   (1 << TIMER_VECTOR)
#define TIMER_LOW_REG               (REG_RW_TIMER0_LOW + (TIMER_ID * 8))
#define TIMER_HIGH_REG              (REG_RW_TIMER0_HIGH + (TIMER_ID * 8))
#define TIMER_LIMIT_LOW_REG         (REG_RW_TIMER0_LLMT + (TIMER_ID * 8))
#define TIMER_LIMIT_HIGH_REG        (REG_RW_TIMER0_HLMT + (TIMER_ID * 8))
#define TCTL_TIMER_EN               (TMR_CNTDWN_EN(TIMER_ID))
#define TCTL_TIMER_AL               (TMR_AUTOLD_EN(TIMER_ID))
#define MAX_TIMER_INTERVAL          (0xffffffff)


//-----------------------------------------------------------------------------
// Static variables
//-----------------------------------------------------------------------------

#ifndef CC_MTK_PRELOADER
static BOOL _fgInited = FALSE;
static UINT32 _u4TimerInterval;
static UINT32 _u4SysClock;
#else /* CC_MTK_PRELOADER */
#define _u4TimerInterval    (GET_XTAL_CLK() / TICKS_PER_SECOND)
#endif /* CC_MTK_PRELOADER */

extern UINT64 u8Div6432(UINT64 u8Dividend, UINT64 u8Divider, UINT64 *pu8Remainder);

//-----------------------------------------------------------------------------
// Static functions
//-----------------------------------------------------------------------------

#ifndef CC_MTK_PRELOADER
//-----------------------------------------------------------------------------
/** _ResetTimer() Reset timer
 */
//-----------------------------------------------------------------------------
static void _ResetTimer(void)
{
    // Reset timer value
//    BIM_WRITE32(TIMER_LOW_REG, MAX_TIMER_INTERVAL);
//    BIM_WRITE32(TIMER_HIGH_REG, MAX_TIMER_INTERVAL);

#ifndef CC_SECURE_WORLD
    // Clear timer interrupt
	HAL_ClrTimerIntr(TIMER_VECTOR);
    VERIFY(BIM_ClearIrq(TIMER_VECTOR));
#endif
}
#endif /* CC_MTK_PRELOADER */


//-----------------------------------------------------------------------------
// Public functions
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
/** HAL_InitTimer() Timer initialization
 *  @retval TRUE on success, FALSE otherwise
 */
//-----------------------------------------------------------------------------
BOOL HAL_InitTimer(void)
{
#ifdef CC_SECURE_WORLD
    // Get system clock and timer interval
    _u4SysClock = GET_XTAL_CLK();
    _u4TimerInterval = _u4SysClock / TICKS_PER_SECOND;
#else

#ifndef CC_MTK_PRELOADER
    if (_fgInited)
    {
        return TRUE;
    }
    _fgInited = TRUE;

    // Get system clock and timer interval
    _u4SysClock = GET_XTAL_CLK();
    _u4TimerInterval = _u4SysClock / TICKS_PER_SECOND;

    // Disable timer
    BIM_REG32(REG_RW_TIMER_CTRL) &= ~(TCTL_TIMER_EN | TCTL_TIMER_AL);

    // Setup timer interrupt interval
    BIM_WRITE32(TIMER_LIMIT_LOW_REG, MAX_TIMER_INTERVAL);
    BIM_WRITE32(TIMER_LIMIT_HIGH_REG, MAX_TIMER_INTERVAL);

    // Reset timer
    _ResetTimer();

    // Enable timer with auto-load
    BIM_REG32(REG_RW_TIMER_CTRL) |= (TCTL_TIMER_EN | TCTL_TIMER_AL);
#endif /* CC_MTK_PRELOADER */

#endif
    return TRUE;
}


#ifndef CC_MTK_PRELOADER
//-----------------------------------------------------------------------------
/** HAL_ResetTimer() Reset timer
 *  @retval TRUE on success, FALSE otherwise
 */
//-----------------------------------------------------------------------------
BOOL HAL_ResetTimer(void)
{
    if (!_fgInited)
    {
        // Should initialize timer first
        return HAL_InitTimer();
    }

    _ResetTimer();

    return TRUE;
}
#endif /* CC_MTK_PRELOADER */
//for uart verification
UINT32 HAL_GetTimeRand(void)
{
    return BIM_READ32(TIMER_LOW_REG);
}


//-----------------------------------------------------------------------------
/** HAL_GetRawTime() Get system ticks and clock cycles since startup
 *  @param pRawTime [out] - A pointer to RAW_TIME_T to receive raw time
 */
//-----------------------------------------------------------------------------
void HAL_GetRawTime(HAL_RAW_TIME_T* pRawTime)
{
    UINT32 u4NewHigh, u4OldHigh, u4Low;

    ASSERT(pRawTime != NULL);

    u4OldHigh = BIM_READ32(TIMER_HIGH_REG);
    u4Low = BIM_READ32(TIMER_LOW_REG);
    u4NewHigh = BIM_READ32(TIMER_HIGH_REG);

    if (u4OldHigh != u4NewHigh)
    {
        pRawTime->u4Ticks = ~((u4Low < 0x10000000) ? u4OldHigh : u4NewHigh);
    }
    else
    {
        pRawTime->u4Ticks = ~u4OldHigh;
    }
    pRawTime->u4Cycles = ~u4Low;
}


//-----------------------------------------------------------------------------
/** HAL_RawToTime() Convert RAW_TIME_T to TIME_T
 *  @param pRawTime [in]  - Pointer to RAW_TIME_T, source
 *  @param pTime    [out] - Pointer to TIME_T, destination
 */
//-----------------------------------------------------------------------------
void HAL_RawToTime(const HAL_RAW_TIME_T* pRawTime, HAL_TIME_T* pTime)
{
    if ((pRawTime != NULL) && (pTime != NULL))
    {
        UINT64 u8Cycles;
#if defined(__GCC__)
        UINT64 u8Remainder;
#endif

        u8Cycles = pRawTime->u4Ticks;
        u8Cycles = u8Cycles << 32;
        u8Cycles += pRawTime->u4Cycles;
#if defined(__GCC__)
        pTime->u4Seconds = (UINT32)u8Div6432(u8Cycles, (UINT64)_u4TimerInterval, &u8Remainder);
        pTime->u4Micros = (UINT32)u8Div6432(u8Remainder * 1000000L, (UINT64)_u4TimerInterval, NULL);
#else
        pTime->u4Seconds = (UINT32)(u8Cycles / (UINT64)_u4TimerInterval);
        pTime->u4Micros = (UINT32)(((u8Cycles % (UINT64)_u4TimerInterval) * 1000000L) / _u4TimerInterval);
#endif
    }
}


//-----------------------------------------------------------------------------
/** HAL_GetTime() Get system time from startup
 *  @param pTime    [out] - Pointer to TIME_T to store system time
 */
//-----------------------------------------------------------------------------
void HAL_GetTime(HAL_TIME_T* pTime)
{
    HAL_RAW_TIME_T rt;

    HAL_GetRawTime(&rt);
    HAL_RawToTime(&rt, pTime);
}


//-----------------------------------------------------------------------------
/** HAL_GetDeltaTime() Get delta time of two time stamps
 *  @param pResult  [out] - The result
 *  @param pOlder   [in]  - The older time
 *  @param pNewer   [in]  - The newer time
 */
//-----------------------------------------------------------------------------
void HAL_GetDeltaTime(HAL_TIME_T* pResult, HAL_TIME_T* pT0,
    HAL_TIME_T* pT1)
{
    HAL_TIME_T* pNewer;
    HAL_TIME_T* pOlder;

    ASSERT(pResult != NULL);
    ASSERT(pT0 != NULL);
    ASSERT(pT1 != NULL);
    ASSERT((pT0->u4Micros < 1000000) && (pT1->u4Micros < 1000000));

    // Decide which one is newer
    if ((pT0->u4Seconds > pT1->u4Seconds) ||
        ((pT0->u4Seconds == pT1->u4Seconds) &&
        (pT0->u4Micros > pT1->u4Micros)))
    {
        pNewer = pT0;
        pOlder = pT1;
    }
    else
    {
        pNewer = pT1;
        pOlder = pT0;
    }

    if (pNewer->u4Seconds > pOlder->u4Seconds)
    {
        pResult->u4Seconds = pNewer->u4Seconds - pOlder->u4Seconds;
        if (pNewer->u4Micros >= pOlder->u4Micros)
        {
            pResult->u4Micros = pNewer->u4Micros - pOlder->u4Micros;
        }
        else
        {
            pResult->u4Micros = (1000000 + pNewer->u4Micros)
                - pOlder->u4Micros;
            pResult->u4Seconds--;
        }
    }
    else
    {
        // pNewer->u4Secons == pOlder->u4Seconds
        ASSERT(pNewer->u4Micros >= pOlder->u4Micros);
        pResult->u4Seconds = 0;
        pResult->u4Micros = pNewer->u4Micros - pOlder->u4Micros;
    }
}

//-----------------------------------------------------------------------------
/** HAL_GetDeltaTime() Get delta time of two time stamps
 *  @param pResult  [out] - The result
 *  @param pOlder   [in]  - The older time
 *  @param pNewer   [in]  - The newer time
 */
//-----------------------------------------------------------------------------
void HAL_GetDeltaRawTime(HAL_RAW_TIME_T* pResult, const HAL_RAW_TIME_T* pT0,
    const HAL_RAW_TIME_T* pT1)
{
	/*
    HAL_RAW_TIME_T* pNewer;
    HAL_RAW_TIME_T* pOlder;
	*/
	UINT64 u8New;
	UINT64 u8Old;
	UINT64 u8Result;

    ASSERT(pResult != NULL);
    ASSERT(pT0 != NULL);
    ASSERT(pT1 != NULL);

    u8New     = ((UINT64)pT1->u4Ticks << 32) | pT1->u4Cycles;
    u8Old     = ((UINT64)pT0->u4Ticks << 32) | pT0->u4Cycles;
    u8Result  = u8New - u8Old;
    pResult->u4Ticks  = (UINT32)(u8Result >> 32);
    pResult->u4Cycles = u8Result & 0xffffffff;

    /*
    ASSERT((pT0->u4Cycles < _u4TimerInterval) &&
        (pT1->u4Cycles < _u4TimerInterval));

    if ((pT0->u4Ticks > pT1->u4Ticks) ||
        ((pT0->u4Ticks == pT1->u4Ticks) && (pT0->u4Cycles > pT1->u4Cycles)))
    {
        pNewer = pT0;
        pOlder = pT1;
    }
    else
    {
        pNewer = pT1;
        pOlder = pT0;
    }

    if (pNewer->u4Ticks > pOlder->u4Ticks)
    {
        pResult->u4Ticks = pNewer->u4Ticks - pOlder->u4Ticks;
        if (pNewer->u4Cycles >= pOlder->u4Cycles)
        {
            pResult->u4Cycles = pNewer->u4Cycles - pOlder->u4Cycles;
        }
        else
        {
            pResult->u4Cycles = (_u4TimerInterval + pNewer->u4Cycles)
                - pOlder->u4Cycles;
            pResult->u4Ticks--;
        }
    }
    else
    {
        // pNewer->u4Secons == pOlder->u4Seconds
        ASSERT(pNewer->u4Cycles >= pOlder->u4Cycles);
        pResult->u4Ticks = 0;
        pResult->u4Cycles = pNewer->u4Cycles - pOlder->u4Cycles;
    }
    */
}

//-----------------------------------------------------------------------------
/** HAL_Delay_us() delay X micro seconds
 *  @param u4Micro
 */
//-----------------------------------------------------------------------------
void HAL_Delay_us(UINT32 u4Micros)
{
    HAL_TIME_T rOrgTime, rNewTime, rDiffTime;

    // This function cannot delay more than 10 seconds.
    ASSERT(u4Micros < 10000000);

    if (u4Micros > 10000000)
    {
        u4Micros = 10000000;
    }

    HAL_GetTime(&rOrgTime);
    do {
        HAL_GetTime(&rNewTime);
        HAL_GetDeltaTime(&rDiffTime, &rOrgTime, &rNewTime);
    } while (((1000000*rDiffTime.u4Seconds)+rDiffTime.u4Micros) < u4Micros);
}
//-----------------------------------------------------------------------------
/** HAL_ClrTimerIntr() clear timer0/1/2 interrupt status
 *  @param u4Vector
 */
//-----------------------------------------------------------------------------
void HAL_ClrTimerIntr(UINT32 u4Vector)
{
#ifdef CC_NO_BIM_INTR
	if (u4Vector == VECTOR_T0) 
	{
        BIM_WRITE32(REG_BIM_INTR_CLEAR, TMR0_INTR_CLEAR);
    } 
	else if (u4Vector == VECTOR_T1) 
	{
        BIM_WRITE32(REG_BIM_INTR_CLEAR, TMR1_INTR_CLEAR);
    } 
	else if (u4Vector == VECTOR_T2) 
	{
        BIM_WRITE32(REG_BIM_INTR_CLEAR, TMR2_INTR_CLEAR);
    }
#else
	UNUSED(u4Vector);
#endif	
}
#ifndef __USERDRV__
#ifdef CC_ARM_TIMER
                            //op1 op2 CRm CRn
#define REG_ARMTIMER_PVAL   (0x002E)    //PL1 physical timer value
#define REG_ARMTIMER_PCTL   (0x012E)    //PL1 physical timer control
#define REG_ARMTIMER_VVAL   (0x003E)    //virtual timer value
#define REG_ARMTIMER_VCTL   (0x013E)    //virtual timer control
    #define REG_ARMTIMER_CTL_STATUS     (1 << 2)
    #define REG_ARMTIMER_CTL_MASK       (1 << 1)
    #define REG_ARMTIMER_CTL_ENABLE     (1 << 0)


void ARMTIMER_WRITE32(UINT32 u4Reg, UINT32 u4Val)
{
    switch(u4Reg)
    {
    case REG_ARMTIMER_PVAL:
        #ifdef __GCC__
        asm volatile("mcr p15, 0, %0, c14, c2, 0" : "=r" (u4Val));
        #else
        __asm
        {
            MCR     p15, 0, u4Val, c14, c2, 0
        }
        #endif
        break;
    case REG_ARMTIMER_PCTL:
        #ifdef __GCC__
        asm volatile("mcr p15, 0, %0, c14, c2, 1" : "=r" (u4Val));
        #else
        __asm
        {
            MCR     p15, 0, u4Val, c14, c2, 1
        }
        #endif
        break;
    case REG_ARMTIMER_VVAL:
        #ifdef __GCC__
        asm volatile("mcr p15, 0, %0, c14, c3, 0" : "=r" (u4Val));
        #else
        __asm
        {
            MCR     p15, 0, u4Val, c14, c3, 0
        }
        #endif
        break;
    case REG_ARMTIMER_VCTL:
        #ifdef __GCC__
        asm volatile("mcr p15, 0, %0, c14, c3, 1" : "=r" (u4Val));
        #else
        __asm
        {
            MCR     p15, 0, u4Val, c14, c3, 1
        }
        #endif
        break;
    }
    UNUSED(u4Val);
    //_ISB();
}

UINT32 ARMTIMER_READ32(UINT32 u4Reg)
{
    UINT32 u4Val = 0;

    switch(u4Reg)
    {
    case REG_ARMTIMER_PVAL:
        #ifdef __GCC__
        asm volatile("mrc p15, 0, %0, c14, c2, 0" : "=r" (u4Val));
        #else
        __asm
        {
            MRC     p15, 0, u4Val, c14, c2, 0
        }
        #endif
        break;
    case REG_ARMTIMER_PCTL:
        #ifdef __GCC__
        asm volatile("mrc p15, 0, %0, c14, c2, 1" : "=r" (u4Val));
        #else
        __asm
        {
            MRC     p15, 0, u4Val, c14, c2, 1
        }
        #endif
        break;
    case REG_ARMTIMER_VVAL:
        #ifdef __GCC__
        asm volatile("mrc p15, 0, %0, c14, c3, 0" : "=r" (u4Val));
        #else
        __asm
        {
            MRC     p15, 0, u4Val, c14, c3, 0
        }
        #endif
        break;
    case REG_ARMTIMER_VCTL:
        #ifdef __GCC__
        asm volatile("mrc p15, 0, %0, c14, c3, 1" : "=r" (u4Val));
        #else
        __asm
        {
            MRC     p15, 0, u4Val, c14, c3, 1
        }
        #endif
        break;
    }

    return u4Val;
}

#endif /* CC_ARM_TIMER */
#endif
