//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft end-user
// license agreement (EULA) under which you licensed this SOFTWARE PRODUCT.
// If you did not accept the terms of the EULA, you are not authorized to use
// this source code. For a copy of the EULA, please see the LICENSE.RTF on your
// install media.
//
//------------------------------------------------------------------------------
//
//  Module: timer.c
//
//  Interface to OAL timer services.
//
#include <windows.h>

#include <oal.h>
#include "soc_cfg.h"
#include "base_regs.h"
#include "register_map.h"
#include "bsp_gpio.h"
#include "bsp_args.h"
#include "image_cfg.h"

#include <pmplatform.h>
#include <dvfs.h>

#define BP_VIC_ST       26
#define BP_VIC_RTC_TICK 29

#ifdef TIMER_LED_DISPLAY
static volatile GPIO_REG *g_pGPIOReg = NULL;
#endif



//In order to test the tick count.
#define COMPENSATION_COUNT 	        1
#define MIN_IDLE_COUNT              240 //20us
#define MIN_UPDATE_COUNT            200

// Local Variables
static volatile SYSTIMER_REG *g_pSYSTimerRegs = NULL;
static volatile PMU_PM_REG *g_pPMUPMRegs = NULL;
static volatile PMU_MISC_REG *g_pPMUMISCRegs = NULL;
static volatile VIC_REG *g_pVIC0Reg = NULL;

// System timer can have some accuracy error while system tick is passed in long time period
// Added for compensation error by using RTC tick 
// In this case, RTC tick has to be accurated
#ifdef RTC_TICK_ERROR_COMPENSATION
volatile RTC_REG *g_pRTCRegs = NULL;
#endif

extern void OALCPUIdle_WFI(void);
#ifdef BSP_USEDVFS
BOOL       bIntercepted = FALSE;
DWORD   gIDLESnapshot;
DWORD   gTotalSnapshot;
static volatile BSP_ARGS    *TimerBSPArgs;
#endif // BSP_USEDVFS

void InitSystemTimer(UINT32 countsPerSysTick)
{
    OALMSG(OAL_TIMER&&OAL_FUNC,(L"[OAL] ++InitSystemTimer\r\n"));
    
    //System Timer Reset
    OUTREG32(&g_pSYSTimerRegs->TCFG, INREG32(&g_pSYSTimerRegs->TCFG) & ~(0x1<<16));
    OUTREG32(&g_pSYSTimerRegs->TCFG, INREG32(&g_pSYSTimerRegs->TCFG) | (0x1<<16));

    // Disable System Timer Interrupt
    DISABLE_SYSTEM_INTR(g_pSYSTimerRegs->INT_CSTAT);
        
    // Disable All Interrupts(TCON/ICNTB/TFCNTB/TICNTB Write INT, Interrupt Counter Expired INT) 
    DISABLE_ALL_INTR(g_pSYSTimerRegs->INT_CSTAT);
    
    // Set clock source, prescaler, Mux, 
    OUTREG32(&g_pSYSTimerRegs->TCFG, INREG32(&g_pSYSTimerRegs->TCFG) & ~(0xf<<12));
    OUTREG32(&g_pSYSTimerRegs->TCFG, INREG32(&g_pSYSTimerRegs->TCFG) |(0x2<<12));

    // Set Prescalar value
    OUTREG32(&g_pSYSTimerRegs->TCFG, INREG32(&g_pSYSTimerRegs->TCFG) & ~(0xff));
    OUTREG32(&g_pSYSTimerRegs->TCFG, INREG32(&g_pSYSTimerRegs->TCFG) | SYS_TIMER_PRESCALER);
    
    OUTREG32(&g_pSYSTimerRegs->TCFG, INREG32(&g_pSYSTimerRegs->TCFG) & ~(0x7<<8));

    switch(SYS_TIMER_DIVIDER)
    {
    case 1:
        OUTREG32(&g_pSYSTimerRegs->TCFG, INREG32(&g_pSYSTimerRegs->TCFG) | (0 << 8));  // SYS_TIMER_DIVIDER = 1
        break;
    case 2:
        OUTREG32(&g_pSYSTimerRegs->TCFG, INREG32(&g_pSYSTimerRegs->TCFG) | (1 << 8));  // SYS_TIMER_DIVIDER = 2
        break;
    case 4:
        OUTREG32(&g_pSYSTimerRegs->TCFG, INREG32(&g_pSYSTimerRegs->TCFG) | (2 << 8));  // SYS_TIMER_DIVIDER = 4
        break;
    case 8:
        OUTREG32(&g_pSYSTimerRegs->TCFG, INREG32(&g_pSYSTimerRegs->TCFG) | (3 << 8));  // SYS_TIMER_DIVIDER = 8
        break;
    case 16:
        OUTREG32(&g_pSYSTimerRegs->TCFG, INREG32(&g_pSYSTimerRegs->TCFG) | (4 << 8));  // SYS_TIMER_DIVIDER = 16
        break;
    default:
        OUTREG32(&g_pSYSTimerRegs->TCFG, INREG32(&g_pSYSTimerRegs->TCFG) | (0 << 8));  // SYS_TIMER_DIVIDER = 1
        break;
    }

    g_pVIC0Reg->VICINTENCLEAR |= (1<<BP_VIC_ST);

    // Enable ICNTB Expire Interrupt Enable
    ENABLE_ICNT_EXPIRE_INTR(g_pSYSTimerRegs->INT_CSTAT);

    // Set timer register //--------------------------------------------------------------------------------
    OUTREG32(&g_pSYSTimerRegs->TICNTB, countsPerSysTick - 1);      // TICK = 1ms
    WAIT_FOR_TICNTB_UPDATE(g_pSYSTimerRegs->INT_CSTAT);

#if(S5PV210_EVT==0)        
    OUTREG32(&g_pSYSTimerRegs->ICNTB, 0);                                    // INT = 1ms
    WAIT_FOR_ICNTB_UPDATE(g_pSYSTimerRegs->INT_CSTAT);

    OUTREG32(&g_pSYSTimerRegs->TCON, (0x1<<4)); //Update ICNTB
    WAIT_FOR_TCON_UPDATE(g_pSYSTimerRegs->INT_CSTAT);
#else
    OUTREG32(&g_pSYSTimerRegs->ICNTB, (0x1<<31) | 0);   
    WAIT_FOR_ICNTB_UPDATE(g_pSYSTimerRegs->INT_CSTAT);
#endif
    //////////-----------------------------------------------------------------------------------------
    OUTREG32(&g_pSYSTimerRegs->TCON, (0x29) ); //Start & Auto reload
    WAIT_FOR_TCON_UPDATE(g_pSYSTimerRegs->INT_CSTAT);

    // System Timer Clear Interrupt Pending
    CLEAR_SYSTEM_INTR(g_pSYSTimerRegs->INT_CSTAT);
    // Timer4 Enable Interrupt
    ENABLE_SYSTEM_INTR(g_pSYSTimerRegs->INT_CSTAT);
    
    g_pVIC0Reg->VICINTENABLE |= (1<<BP_VIC_ST);

#ifdef RTC_TICK_ERROR_COMPENSATION
    g_pVIC0Reg->VICINTENCLEAR |= (1<<BP_VIC_RTC_TICK);
    // Generation RTC Tick interrupt by 1sec for accuracy test of system timer
    //RTC enable
    OUTREG32(&g_pRTCRegs->RTCCON, INREG32(&g_pRTCRegs->RTCCON) | 1<<0);
    //RTC Clock count reset
    OUTREG32(&g_pRTCRegs->RTCCON, INREG32(&g_pRTCRegs->RTCCON) & ~(1<<3));
    // RTC tick timer clock selection(32768Hz)
    OUTREG32(&g_pRTCRegs->RTCCON, INREG32(&g_pRTCRegs->RTCCON) & ~(0xF<<4));

    // RTC tick count(327679 = 10s)
    OUTREG32(&g_pRTCRegs->TICCNT, 327679);

    // RTC Tick pending clear
    OUTREG32(&g_pRTCRegs->INTP, 1<<0);
    // RTC Tick start
    OUTREG32(&g_pRTCRegs->RTCCON, INREG32(&g_pRTCRegs->RTCCON)| 1<<8);

    //RTCCON disable
    OUTREG32(&g_pRTCRegs->RTCCON, INREG32(&g_pRTCRegs->RTCCON) & ~(1<<0));
    g_pVIC0Reg->VICINTENABLE |= (1<<BP_VIC_RTC_TICK);
#endif    
    OALMSG(OAL_TIMER&&OAL_FUNC,(L"[OAL] ++InitSystemTimer\r\n"));    
}

//------------------------------------------------------------------------------
//  Function: OALTimerInit
//
//  This function is typically called from the OEMInit to initialize
//  Windows CE system timer. The tickMSec parameter determine timer
//  period in milliseconds. On most platform timer period will be
//  1 ms, but it can be usefull to use higher value for some
//  specific (low-power) devices.
//
BOOL OALTimerInit(UINT32 msecPerSysTick, UINT32 countsPerMSec, UINT32 countsMargin)
{
    BOOL rc = FALSE;
    UINT32 countsPerSysTick;
    UINT32 sysIntr, irq;
#ifdef RTC_TICK_ERROR_COMPENSATION
    UINT32 sysIntr2, irq2; 
#endif
    
    g_pSYSTimerRegs = (volatile SYSTIMER_REG*)OALPAtoVA(BASE_REG_PA_SYSTIMER,FALSE);
    g_pGPIOReg = (volatile GPIO_REG *)OALPAtoVA(BASE_REG_PA_GPIO, FALSE);
    g_pVIC0Reg = (volatile VIC_REG *)OALPAtoVA(BASE_REG_PA_VIC0,FALSE);
    g_pPMUPMRegs = (volatile PMU_PM_REG *)OALPAtoVA(BASE_REG_PA_PMU_PM,FALSE);
    g_pPMUMISCRegs = (volatile PMU_MISC_REG *)OALPAtoVA(BASE_REG_PA_PMU_MISC,FALSE);
#ifdef RTC_TICK_ERROR_COMPENSATION
    g_pRTCRegs = (volatile RTC_REG*)OALPAtoVA(BASE_REG_PA_RTC, FALSE);
#endif      
#ifdef BSP_USEDVFS
    TimerBSPArgs = (volatile BSP_ARGS*)IMAGE_SHARE_ARGS_UA_START;
#endif
    OALMSG(OAL_TIMER&&OAL_FUNC,(L"[OAL] +OALTimerInit( %d, %d, %d )\r\n",msecPerSysTick,countsPerMSec,countsMargin));

    // Validate Input parameters
    countsPerSysTick = countsPerMSec * msecPerSysTick;
    if(msecPerSysTick<1 || msecPerSysTick>1000 || countsPerSysTick<1 || countsPerSysTick>0xFFFFFFFF)
    {
        OALMSG(OAL_ERROR,(L"ERROR: OALTimerInit: System tick period out of range..."));
        goto cleanUp;
    }

    // Initialize timer state global variable
    g_oalTimer.msecPerSysTick = msecPerSysTick;         // miliseconds per system tick
    g_oalTimer.countsPerMSec = countsPerMSec;           // Clock tick  per miliseconds
    g_oalTimer.countsMargin = countsMargin;
    g_oalTimer.countsPerSysTick = countsPerSysTick;     // Clock tick  per system tick
    g_oalTimer.curCounts = 0;
    g_oalTimer.maxPeriodMSec = 0xFFFFFFFF/g_oalTimer.countsPerMSec;

    g_oalTimer.actualMSecPerSysTick = g_oalTimer.msecPerSysTick;
    g_oalTimer.actualCountsPerSysTick = g_oalTimer.countsPerSysTick;

    // Set kernel exported globals to initial values
    idleconv = countsPerMSec;
    curridlehigh = 0;
    curridlelow = 0;

    // Initialize high resolution timer function pointers
    pQueryPerformanceFrequency = OALTimerQueryPerformanceFrequency;
    pQueryPerformanceCounter = OALTimerQueryPerformanceCounter;

    // Initialize Variable System Tick reschedule time update function pointer
    pOEMUpdateRescheduleTime = OALTimerUpdateRescheduleTime;

    // Create SYSINTR for timer
    irq = IRQ_SYSTIMER;
    sysIntr = OALIntrRequestSysIntr(1, &irq, OAL_INTR_FORCE_STATIC);
#ifdef RTC_TICK_ERROR_COMPENSATION
    irq2 = IRQ_RTC_TIC;
    sysIntr2 = OALIntrRequestSysIntr(1, &irq2, OAL_INTR_FORCE_STATIC);
#endif
    //---------------------
    // Initialize System Timer
    //---------------------
    InitSystemTimer(g_oalTimer.msecPerSysTick*g_oalTimer.countsPerMSec);

#ifdef BSP_USEDVFS
    ResetSnapshot(gTotalSnapshot, gIDLESnapshot);
    InitializeDVFS();
#endif // BSP_USEDVFS
    // Enable System Tick interrupt
    if (!OEMInterruptEnable(sysIntr, NULL, 0))
    {
        OALMSG(OAL_ERROR,(L"ERROR: OALTimerInit: Interrupt enable for system timer failed"));
        goto cleanUp;
    }
#ifdef RTC_TICK_ERROR_COMPENSATION
    if (!OEMInterruptEnable(sysIntr2, NULL, 0))
    {
        OALMSG(OAL_ERROR,(L"ERROR: OALTimerInit: Interrupt enable for timer2 failed"));
        goto cleanUp;
    }
#endif

// Define ENABLE_WATCH_DOG to enable watchdog timer support.
// NOTE: When watchdog is enabled, the device will reset itself if watchdog timer is not refreshed within ~4.5 second.
//       Therefore it should not be enabled when kernel debugger is connected, as the watchdog timer will not be refreshed.
#ifdef ENABLE_WATCH_DOG
    {
        extern void SMDKInitWatchDogTimer (void);
        SMDKInitWatchDogTimer ();
    }
#endif

    rc = TRUE;

    cleanUp:
    OALMSG(OAL_TIMER&&OAL_FUNC, (L"-OALTimerInit(rc = %d)\r\n", rc));

    return rc;
}

//------------------------------------------------------------------------------
//  Function: OALTimerIntrHandler
//
//  This function implement timer interrupt handler. It is called from common
//  ARM interrupt handler.
UINT32 OALTimerIntrHandler()
{
    UINT32 sysIntr = SYSINTR_NOP;

#ifdef BSP_USEDVFS
    if(bIntercepted == TRUE)
    {
        bIntercepted = FALSE;
        sysIntr = SYSINTR_RESCHED;
        goto CleanUp_TimerHandler;
    }
#endif


    // Update high resolution counter
    g_oalTimer.curCounts += g_oalTimer.countsPerSysTick;

    // Update the millisecond counter
    CurMSec += g_oalTimer.msecPerSysTick;

#ifdef BSP_USEDVFS
    UpdateTotalSnapshot(TimerBSPArgs, gTotalSnapshot, g_oalTimer.countsPerSysTick);

    if(DVFSCHECK_BEFOREHAND(TimerBSPArgs))
    {
        if(DecideDVFS(gTotalSnapshot, gIDLESnapshot))
        {
            sysIntr = SYSINTR_DVFS;

            if (CurMSec  >= dwReschedTime)
            {
                SetIntercepted(bIntercepted, TRUE);
                return sysIntr;
            }
            else
            {
                SetIntercepted(bIntercepted, FALSE);
                goto CleanUp_TimerHandler;
            }
        }
    }
#endif
    
    // Reschedule?
    if ((int)(CurMSec - dwReschedTime) >= 0) sysIntr = SYSINTR_RESCHED;

#ifdef OAL_ILTIMING
    if (g_oalILT.active)
    {
        if (--g_oalILT.counter == 0)
        {
            sysIntr = SYSINTR_TIMING;
            g_oalILT.counter = g_oalILT.counterSet;
            g_oalILT.isrTime2 = OALTimerCountsSinceSysTick();
        }
    }
#endif

#ifdef BSP_USEDVFS
CleanUp_TimerHandler:
#endif

#ifdef BSP_USEDVFS
    ExpireSnapshot(gTotalSnapshot, gIDLESnapshot);
#endif

    if (g_pSYSTimerRegs->INT_CSTAT & ST_INTR_STS) // (CurMSec == baseMSec)
    {
       CLEAR_SYSTEM_INTR(g_pSYSTimerRegs->INT_CSTAT);
    }
    else
    {
        OALMSG(1,(TEXT("Dummy(0x%x)\r\n"),g_pSYSTimerRegs->INT_CSTAT));
        CLEAR_ALL_INTR(g_pSYSTimerRegs->INT_CSTAT);
    }

    return sysIntr;
}

//------------------------------------------------------------------------------
//  Function: OALTimerCountsSinceSysTick
//
//  This function return count of hi res ticks since system tick.
//
//  Timer 4 counts down, so we should substract actual value from
//  system tick period.
INT32 OALTimerCountsSinceSysTick()
{
    return (g_oalTimer.countsPerSysTick - INREG32(&g_pSYSTimerRegs->TICNTO));
}

//------------------------------------------------------------------------------
//
//  Function: OALTimerUpdate
//
//  This function is called to change length of actual system timer period.
//  If end of actual period is closer than margin period isn't changed (so
//  original period elapse). Function returns time which already expires
//  in new period length units. If end of new period is closer to actual time
//  than margin period end is shifted by margin (but next period should fix
//  this shift - this is reason why OALTimerRecharge doesn't read back
//  compare register and it uses saved value instead).
UINT32 OALTimerUpdate(UINT32 period, UINT32 margin)
{
    OALMSG(OAL_TIMER&&OAL_FUNC, (L"+OALTimerUpdate()\r\n"));
    return 0;
}

//------------------------------------------------------------------------------
//  Function:     OEMIdle
//
//  This function is called by the kernel when there are no threads ready to
//  run. The CPU should be put into a reduced power mode if possible and halted.
//  It is important to be able to resume execution quickly upon receiving an
//  interrupt.
//
//  Interrupts are disabled when OEMIdle is called and when it returns.
//
//  Note that system timer must be running when CPU/SoC is moved to reduced
//  power mode.
void OEMIdle(DWORD idleParam)
{
    UINT32 baseMSec, idleMSec;
    INT32 usedCounts, idleCounts;
    ULARGE_INTEGER idle;

    // Get current system timer counter
    baseMSec = CurMSec;

    // Remained Idle Time
    idleMSec = dwReschedTime -baseMSec;
    
    
    // Find how many hi-res ticks was already used
    usedCounts = OALTimerCountsSinceSysTick();
    if (usedCounts + MIN_IDLE_COUNT >= (INT32)g_oalTimer.countsPerSysTick)
    {
        // Abandon Idle
        return;
    }


	{
        // Move SoC/CPU to idle mode
#ifdef TIMER_LED_DISPLAY
        Set_PinData(g_pGPIOReg, LED8, 1);
#endif
#if 0
        g_pPMUPMRegs->PWR_CONF.IDLE_CFG = (g_pPMUPMRegs->PWR_CONF.IDLE_CFG & ~(BW_CFG_DIDLE<<BP_CFG_DIDLE)) |
                                          (CFG_NODEEP_IDLE<<BP_CFG_DIDLE);
        g_pPMUPMRegs->PWR_CONF.PWR_CFG = (g_pPMUPMRegs->PWR_CONF.PWR_CFG & ~(BW_CFG_STANDBYWFI<<BP_CFG_STANDBYWFI)) | 
                                         (CFG_ENTER_IDLE<<BP_CFG_STANDBYWFI);
        g_pPMUMISCRegs->SYS_CON.OTHERS |= (1<<BP_SYSCON_INT_DISABLE);
#endif        
        OALCPUIdle_WFI();
#if 0        
        g_pPMUPMRegs->PWR_CONF.PWR_CFG &= ~(BW_CFG_STANDBYWFI<<BP_CFG_STANDBYWFI);
#endif
#ifdef TIMER_LED_DISPLAY
        Set_PinData(g_pGPIOReg, LED8, 0);
#endif
	}

    idleCounts = OALTimerCountsSinceSysTick() - usedCounts;

    // Get real idle value. If result is negative, idle period is laid across the Time quantum
    idleCounts = (idleCounts >= 0) ? (idleCounts) : (idleCounts + g_oalTimer.countsPerSysTick);

#ifdef BSP_USEDVFS
    UpdateIdleSnapshot(TimerBSPArgs, gIDLESnapshot, idleCounts);
#endif
    // Update idle counters
    idle.LowPart = curridlelow;
    idle.HighPart = curridlehigh;
    idle.QuadPart += idleCounts;
    curridlelow  = idle.LowPart;
    curridlehigh = idle.HighPart;

}

VOID OALTimerUpdateRescheduleTime(DWORD time)
{
}



