//
// 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.
//
//------------------------------------------------------------------------------
//
//  File:  delay.c
//
// 	2009.06.01 First Generated by JOHNLAY
//  Notice: The Delay(wait)function has error tolerance of 1% because of PCLKPSYS(66.7MHz)
//           Which is used as the input clock source of PWM Timer

#include <windows.h>
#include <ceddk.h>
#include <bsp.h>
#include <oal.h>

static BOOL PWMInitialized = FALSE;
volatile PWM_REG *pPWMRegs;

#define PWM_1SEC             3031818
#define PRESCALAR_DELAY      10

void OALWaitInitialize()
{
    UINT32 tcon;
    pPWMRegs = (PWM_REG *)OALPAtoVA(BASE_REG_PA_PWMTIMER,FALSE);

    // Set prescaler 1 to 1
    OUTREG32(&pPWMRegs->TCFG0, INREG32(&pPWMRegs->TCFG0) & ~(PRESCALER1_MASK));
    OUTREG32(&pPWMRegs->TCFG0, INREG32(&pPWMRegs->TCFG0) | PRESCALAR_DELAY << PRESCALER1_START);
    OUTREG32(&pPWMRegs->TCFG1, INREG32(&pPWMRegs->TCFG1) & ~(DIV_MUX4_MASK));
    OUTREG32(&pPWMRegs->TCFG1, INREG32(&pPWMRegs->TCFG1) | (D1_2 << DIV_MUX4_START)); //PCLK 66MHz / 33/ 2 = 1MHz = 1us
    // Set timer register to 1 seconds
    OUTREG32(&pPWMRegs->TCNTB4, PWM_1SEC - 1);

    // Start timer in auto-reload mode
    tcon = INREG32(&pPWMRegs->TCON) & ~(TIMER4_MASK);
    OUTREG32(&pPWMRegs->TCON, tcon);
    OUTREG32(&pPWMRegs->TCON, tcon | (TIMER4_MANUAL_UPDATE) );
    OUTREG32(&pPWMRegs->TCON, tcon | (TIMER4_START | TIMER4_AUTO_RELOAD) );							//One-Shot 1, Auto-Reload 5

    // Timer4 Enable Interrupt
    DISABLE_PWMINT(pPWMRegs->TINT_CSTAT, 4);
    // Timer4 Clear Interrupt Pending
    CLR_PWMINT(pPWMRegs->TINT_CSTAT, 4);

    PWMInitialized = TRUE;
}

int OALWaitUsec(UINT32 microSeconds)
{
    if(microSeconds)
    {
        UINT32 InitTCNTO;
        UINT32 TargetTCNTO;
        volatile UINT32 Timeout;
        SYSTEM_CLOCK *SystemClocks;
        volatile PWM_REG *pPWMRegs = (PWM_REG*)OALPAtoVA(BASE_REG_PA_PWMTIMER,FALSE);
        
        SystemClocks = (SYSTEM_CLOCK *)OALArgsQuery(OAL_ARGS_QUERY_SYSTEMCLOCKS);

        OALMSG(OAL_DEBUG,(TEXT("OALWaitUsec Call\r\n")));
        if(microSeconds > 200)
        {
            OALMSG(1,(_T("[Error : OALWaitUsec] The input parameter can't over 200 microseconds.\r\n")));
            return (0);
        }

        if(!PWMInitialized)
        {
            //RETAILMSG(1,(_T("OALWaitUsec() is executed without initialization\r\n")));
            OALWaitInitialize();
            OALMSG(OAL_INFO,(_T("OALWaitUsec Initialization done\r\n")));
        }

        Timeout = (SystemClocks->ARM_CLK)/((SystemClocks->PCLKPSYS_CLK) / (PRESCALAR_DELAY+1) / (1 << D1_2))  * 3 * 5000 ;
        InitTCNTO = pPWMRegs->TCNTO4;

        // Error compensation because PWM Timer hase 1% error tolerance.
        if(microSeconds<100)
        TargetTCNTO = microSeconds * 3;
        else
        TargetTCNTO = (microSeconds+1) * 3;   

        if((pPWMRegs->TCFG0 & PRESCALER1_MASK) != ( PRESCALAR_DELAY << PRESCALER1_START) ||  \
            (pPWMRegs->TCFG1 & DIV_MUX4_MASK) != (D1_2 << DIV_MUX4_START) ||  \
            pPWMRegs->TCNTB4  != (PWM_1SEC - 1) ||  \
            (pPWMRegs->TCON & TIMER4_MASK) != (TIMER4_START | TIMER4_AUTO_RELOAD))
        {
            //OALMSG(OAL_INFO,(_T("[OALWaitUsec] Corrupted PWM Timer control register(0x%x, 0x%x, 0x%x, 0x%x\r\n"), pPWMRegs->TCFG0, pPWMRegs->TCFG1, pPWMRegs->TCON, pPWMRegs->TCNTB4));
            OALWaitInitialize();
            InitTCNTO = pPWMRegs->TCNTO4;
        }

        if(TargetTCNTO > InitTCNTO) // Turn over the Timer counts
        {
            TargetTCNTO = PWM_1SEC + InitTCNTO - TargetTCNTO;
            while(((pPWMRegs->TCNTO4 > TargetTCNTO) || (pPWMRegs->TCNTO4 < InitTCNTO)) && (Timeout-- > 0));
        }
        else
        {
            TargetTCNTO = InitTCNTO - TargetTCNTO;
            while(((pPWMRegs->TCNTO4 < InitTCNTO) && (pPWMRegs->TCNTO4 > TargetTCNTO))&&(Timeout-- > 0));// RETAILMSG(1,(_T("%d"), pPWMRegs->TCNTO4));
        }

        InitTCNTO = pPWMRegs->TCNTO4;
        return ((TargetTCNTO > InitTCNTO)?(TargetTCNTO - InitTCNTO):(InitTCNTO - TargetTCNTO));
    }
    return (0);
}
