// Copyright (c) 2007, 2008 BSQUARE Corporation. All rights reserved.

/*
================================================================================
*             Texas Instruments OMAP(TM) Platform Software
* (c) Copyright Texas Instruments, Incorporated. All Rights Reserved.
*
* Use of this software is controlled by the terms and conditions found
* in the license agreement under which this software has been supplied.
*
================================================================================
*/
//
//  File:  rtc.c
//
//  This file implements OAL real time module. 
//
//  Implementation uses system tick to calculate realtime clock. Tritons
//  chip driver should call IOCTL_HAL_INIT_RTC periodically to update
//  internal RTC time with real one.
//
//
#include <windows.h>
#include <nkexport.h>
#include <nkintr.h>
#include <bsp.h>
#include <bsp_twl4030.h>

#if BSP_OAL_RTC_USES_TWL4030

//------------------------------------------------------------------------------
//
//  Define:  RTC_BASE_YEAR
//
//  Delta from which RTC counts years
//  Resolution of RTC years is from 2000 to 2099
//
#define RTC_BASE_YEAR_MIN       2000
#define RTC_BASE_YEAR_MAX       2099


#ifndef HKEY_LOCAL_MACHINE
#define HKEY_LOCAL_MACHINE      ((HKEY)(ULONG_PTR)0x80000002)
#endif

#define RTC_REG_KEY             TEXT("Drivers\\BuiltIn\\RTC")
#define RTC_REG_OFFSET          TEXT("SecureOffset")


//------------------------------------------------------------------------------

#define BCD2BIN(b)              (((b) >> 4)*10 + ((b)&0xF))
#define BIN2BCD(b)              ((((UINT8)(b)/10) << 4)|((UINT8)(b)%10))

//------------------------------------------------------------------------------

static struct {

    BOOL initialized;           // Is RTC subsystem intialized?
    CRITICAL_SECTION cs;
    UINT32     sysIntRTC;
    UINT32     updateFlag;      // Inidicates the update type 
    
    ULONGLONG  baseFiletime;    // Base filetime 
    ULONGLONG  baseOffset;      // Secure time offset from base filetime
    DWORD      baseTickCount;   // Tick count from base filetime

    ULONGLONG  alarmFiletime;   // Alarm filetime 

} s_rtc = { FALSE };

    
//------------------------------------------------------------------------------

UINT32
OEMGetTickCount(
    );

BOOL
FiletimeToHWTime(
    ULONGLONG fileTime, 
    UCHAR bcdTime[6]
    );

//------------------------------------------------------------------------------

#if 0
static UINT32
LocalGetTickCount(
    )
{
    return OEMGetTickCount();
}
#else
static UINT32
// TODO: change this function and where used to return 64 bit second count
// TODO: add support for rollover detection (add 2^^32)
LocalGetTickCount(
    )
{
	DWORD RegValue[2];
    OMAP_32KSYNCNT_REGS *p32KSynCntRegs = (OMAP_32KSYNCNT_REGS *)OALPAtoUA(OMAP_32KSYNC_REGS_PA);

	do
	{
		RegValue[0] = p32KSynCntRegs->CR;
		RegValue[1] = p32KSynCntRegs->CR;
	} while (RegValue[0] != RegValue[1]);

    // CR / 32768 = number of seconds, times 1000 gives number of milliseconds
    return (RegValue[0] >> 15) * 1000;
}
#endif

//------------------------------------------------------------------------------

LPCWSTR
SystemTimeToString(
    SYSTEMTIME *pSystemTime
    )
{
    static WCHAR buffer[64];

    OALLogPrintf(
        buffer, 64, L"%04d.%02d.%02d %02d:%02d:%02d.%03d",
        pSystemTime->wYear, pSystemTime->wMonth, pSystemTime->wDay,
        pSystemTime->wHour, pSystemTime->wMinute, pSystemTime->wSecond, 
        pSystemTime->wMilliseconds
        );        
    return buffer;
}

//------------------------------------------------------------------------------

LPCWSTR
HWTimeToString(
    UCHAR bcdTime[6]
    )
{
    static WCHAR buffer[64];

    OALLogPrintf(
        buffer, 64, L"%04d.%02d.%02d %02d:%02d:%02d",
        BCD2BIN(bcdTime[5]) + RTC_BASE_YEAR_MIN, 
        BCD2BIN(bcdTime[4]), 
        BCD2BIN(bcdTime[3]),
        BCD2BIN(bcdTime[2]), 
        BCD2BIN(bcdTime[1]), 
        BCD2BIN(bcdTime[0])
        );        
    return buffer;
}

//------------------------------------------------------------------------------
//
//  Function:  OALIoCtlHalInitRTC
//
//  This function is called by WinCE OS to initialize the time after boot. 
//  Input buffer contains SYSTEMTIME structure with default time value.
//
//
BOOL
OALIoCtlHalInitRTC(
    UINT32 code, 
    VOID *pInBuffer, 
    UINT32 inSize, 
    VOID *pOutBuffer, 
    UINT32 outSize, 
    UINT32 *pOutSize
    )
{
    BOOL            rc = FALSE;
    void*           hTWL;
    SYSTEMTIME      *pGivenTime = (LPSYSTEMTIME) pInBuffer;
    UCHAR           bcdTime[6];
    UCHAR           status;
    UCHAR           secure;
    UINT32          irq;
#if 0
    OMAP_GPIO_REGS *pGPIO1Regs = OALPAtoUA(OMAP_GPIO1_REGS_PA);
	OMAP_SYSC_PADCONFS_REGS *pPADCONFS_REGS = OALPAtoUA(OMAP_SYSC_PADCONFS_REGS_PA);
#endif
//    BOOL           *pColdBoot;

    OALMSG(OAL_TIMER && OAL_FUNC, (L"+OALIoCtlHalInitRTC()\r\n"));

	if (!s_rtc.initialized)
	{
	    // Initialize RTC critical section
    	InitializeCriticalSection(&s_rtc.cs);
	}

#if 0
	// The EVM TWL4030 MSEC pin is floating (at CPU connector D pin 31), 
	// nothing can be done to bring the TWL4030 out of secure mode...

    // Set GPIO_22(MSECURE) to be output/high (unsecure)
    // This allows write access to the T2 RTC calendar/time registers
    // OMAP3530 GP only
    if( dwOEMHighSecurity == OEM_HIGH_SECURITY_GP )
    {
        // SYS_DRM_MSEC signal = GPIO_22 on ETK_D8
        #define MODIFY_PADCONF_REG_LOWER(name, value) {pPADCONFS_REGS->name = (pPADCONFS_REGS->name & 0xffff0000) | (value);}
        MODIFY_PADCONF_REG_LOWER(CONTROL_PADCONF_ETK_D8, MUX_MODE_4 | INPUT_ENABLE)
        CLRREG32(&pGPIO1Regs->OE, 1 << 22);
        SETREG32(&pGPIO1Regs->DATAOUT, 1 << 22);
    }
#endif

    // First read RTC status from Triton 
    hTWL = OALTritonOpen();
    if (hTWL == NULL)
        {
        OALMSG(OAL_ERROR, (L" OALIoCtlHalInitRTC(): Failed to open Triton\r\n"));
        goto cleanUp;
        }

	if (!s_rtc.initialized)
	{
	    // Request SYSINTR for RTC Query SW interrupt
    	irq = IRQ_SW_RTC_QUERY;

	    s_rtc.sysIntRTC = OALIntrRequestSysIntr(1, &irq, OAL_INTR_TRANSLATE);
    	if (s_rtc.sysIntRTC == SYSINTR_UNDEFINED)
        	{
        	OALMSG(OAL_ERROR, (L" OALIoCtlHalInitRTC(): Failed to get RTC SysIntr\r\n"));
        	goto cleanUp;
        	}
	}
			
    // Read secure registers for secure time hash (0 indicates time has reset)
    status = 0;

    OALTritonRead(hTWL, TWL_SECURED_REG_A, &secure);
    status |= secure;

    OALTritonRead(hTWL, TWL_SECURED_REG_B, &secure);
    status |= secure;

    OALTritonRead(hTWL, TWL_SECURED_REG_C, &secure);
    status |= secure;

    OALTritonRead(hTWL, TWL_SECURED_REG_D, &secure);
    status |= secure;


    OALMSG(OAL_TIMER && OAL_FUNC, (L" OALIoCtlHalInitRTC():  RTC TWL_SECURED_REG_= 0x%x\r\n", status));


    // Check for a clean boot of device - if so, reset date/time to system default (LTK2026)
   // pColdBoot = OALArgsQuery(OAL_ARGS_QUERY_COLDBOOT);
   // if ((pColdBoot != NULL) && *pColdBoot)
   //     {
   //     RETAILMSG(1, (L" OALIoCtlHalInitRTC():  Clean boot, reset date time\r\n"));
   //     status = 0;
   //     }

    // Start RTC when it isn't running
    if (status == 0 && pGivenTime != NULL)
        {
        OALMSG(OAL_TIMER && OAL_FUNC, (L" OALIoCtlHalInitRTC():  Resetting RTC\r\n"));

        // Write power_up and alarm bits to clear power up flag (and any interrupt flag)
        OALTritonWrite(hTWL, TWL_RTC_STATUS_REG, TWL_RTC_STATUS_POWER_UP|TWL_RTC_STATUS_ALARM);

        //  Convert system time to BCD
        bcdTime[5] = BIN2BCD(pGivenTime->wYear - RTC_BASE_YEAR_MIN);
        bcdTime[4] = BIN2BCD(pGivenTime->wMonth);
        bcdTime[3] = BIN2BCD(pGivenTime->wDay);
        bcdTime[2] = BIN2BCD(pGivenTime->wHour);
        bcdTime[1] = BIN2BCD(pGivenTime->wMinute);
        bcdTime[0] = BIN2BCD(pGivenTime->wSecond);

        //  Initialize RTC with given values
        OALTritonWrite(hTWL, TWL_YEARS_REG, bcdTime[5]);
        OALTritonWrite(hTWL, TWL_MONTHS_REG, bcdTime[4]);
        OALTritonWrite(hTWL, TWL_DAYS_REG, bcdTime[3]);
        OALTritonWrite(hTWL, TWL_HOURS_REG, bcdTime[2]);
        OALTritonWrite(hTWL, TWL_MINUTES_REG, bcdTime[1]);
        OALTritonWrite(hTWL, TWL_SECONDS_REG, bcdTime[0]);

        //  Enable RTC
        OALTritonWrite(hTWL, TWL_RTC_CTRL_REG, TWL_RTC_CTRL_RUN);

        //  Write a hash to secure registers
        OALTritonWrite(hTWL, TWL_SECURED_REG_A, 0xAA);
        OALTritonWrite(hTWL, TWL_SECURED_REG_B, 0xBB);
        OALTritonWrite(hTWL, TWL_SECURED_REG_C, 0xCC);
        OALTritonWrite(hTWL, TWL_SECURED_REG_D, 0xDD);

        //  Convert given time initialization date/time to FILETIME
        NKSystemTimeToFileTime(pGivenTime, (FILETIME*)&s_rtc.baseFiletime);

        //  Set a value for base offset (100 nanoseconds won't make a difference)
        //  to ensure that the OS time is reset to a default and offset stored
        //  in registry is ignored
        s_rtc.baseOffset = 1;

        }
    else
        {
        SYSTEMTIME  baseSystemTime;

        OALMSG(OAL_TIMER && OAL_FUNC, (L" OALIoCtlHalInitRTC():  Getting RTC\r\n"));

        //  Set get time flag            
        OALTritonRead(hTWL, TWL_RTC_CTRL_REG, &status);

        status |= TWL_RTC_CTRL_RUN | TWL_RTC_CTRL_GET_TIME;
        OALTritonWrite(hTWL, TWL_RTC_CTRL_REG, status);

        //  Get date and time;
        OALTritonRead(hTWL, TWL_YEARS_REG, &bcdTime[5]);
        OALTritonRead(hTWL, TWL_MONTHS_REG, &bcdTime[4]);
        OALTritonRead(hTWL, TWL_DAYS_REG, &bcdTime[3]);
        OALTritonRead(hTWL, TWL_HOURS_REG, &bcdTime[2]);
        OALTritonRead(hTWL, TWL_MINUTES_REG, &bcdTime[1]);
        OALTritonRead(hTWL, TWL_SECONDS_REG, &bcdTime[0]);

        //  Convert current RTC date/time to FILETIME
        baseSystemTime.wYear    = BCD2BIN(bcdTime[5]) + RTC_BASE_YEAR_MIN;
        baseSystemTime.wMonth   = BCD2BIN(bcdTime[4]);
        baseSystemTime.wDay     = BCD2BIN(bcdTime[3]);
        baseSystemTime.wHour    = BCD2BIN(bcdTime[2]);
        baseSystemTime.wMinute  = BCD2BIN(bcdTime[1]);
        baseSystemTime.wSecond  = BCD2BIN(bcdTime[0]);
        baseSystemTime.wMilliseconds = 0;

        NKSystemTimeToFileTime(&baseSystemTime, (FILETIME*)&s_rtc.baseFiletime);

        //  Reset base offset to an overwriteable value
        //  The RTC driver will set this value from registery
        s_rtc.baseOffset = 0;
        }        

    // We are done with Triton
    OALTritonClose(hTWL);

    OALMSG(OAL_TIMER && OAL_FUNC, (L" OALIoCtlHalInitRTC():  RTC = %s\r\n", HWTimeToString(bcdTime)));


    // Now update RTC state values
    s_rtc.initialized   = TRUE;
    s_rtc.baseTickCount = LocalGetTickCount();


    //  Success
    rc = TRUE;


cleanUp:
    OALMSG(OAL_TIMER && OAL_FUNC, (L"-OALIoCtlHalInitRTC() rc = %d\r\n", rc));
    return rc;
}


//------------------------------------------------------------------------------
//
//  Function:  OEMGetRealTime
//
//  This function is called by the kernel to retrieve the time from
//  the real-time clock.
//
BOOL
OEMGetRealTime(
    SYSTEMTIME *pSystemTime
    ) 
{
    DWORD       delta;
    ULONGLONG   time;

    OALMSG(OAL_TIMER && OAL_FUNC, (L"+OEMGetRealTime()\r\n"));

    if (!s_rtc.initialized)
        {
        // Return default time if RTC isn't initialized
        pSystemTime->wYear   = RTC_BASE_YEAR_MIN;
        pSystemTime->wMonth  = 1;
        pSystemTime->wDay    = 1;
        pSystemTime->wHour   = 0;
        pSystemTime->wMinute = 0;
        pSystemTime->wSecond = 0;
        pSystemTime->wDayOfWeek    = 0;
        pSystemTime->wMilliseconds = 0;
        }
    else
        {
        EnterCriticalSection(&s_rtc.cs);
        delta = LocalGetTickCount() - s_rtc.baseTickCount;
        // Note: baseFiletime and baseOffset are in 100ns units, delta is in 1ms units.
        time = s_rtc.baseFiletime + s_rtc.baseOffset + ((ULONGLONG)delta) * 10000;
        NKFileTimeToSystemTime((FILETIME*)&time, pSystemTime);
        // don't return milliseconds, causes CETK failure
        pSystemTime->wMilliseconds = 0;
        LeaveCriticalSection(&s_rtc.cs);
        }

    OALMSG(OAL_TIMER && OAL_FUNC, (L"-OEMGetRealTime() = %s\r\n", SystemTimeToString(pSystemTime)));

    return TRUE;
}

//------------------------------------------------------------------------------
//
//  Function:  OEMSetRealTime
//
//  This function is called by the kernel to set the real-time clock. A secure
//  timer requirement means that the time change is noted in baseOffset and
//  used to compute the time delta from the non-alterable RTC in T2
//
BOOL
OEMSetRealTime(
    SYSTEMTIME *pSystemTime
    ) 
{
    BOOL        rc = FALSE;
    ULONGLONG   fileTime;
    DWORD       tickDelta;
//    HKEY        hKey;
//    DWORD       dwOffset;

    RETAILMSG(1, (L"+OEMSetRealTime(%s)\r\n", SystemTimeToString(pSystemTime)));

    if (s_rtc.initialized)
        {
        // Save time to global structure
        EnterCriticalSection(&s_rtc.cs);

        // Discard milliseconds
        pSystemTime->wMilliseconds = 0;

		{
		// add by p.j.g
			UCHAR           bcdTime[6];
			HANDLE hTWL = OALTritonOpen();

			//RETAILMSG(1, (TEXT("Update time to RTC register\n")));
			if (hTWL != NULL)
			{
			 	bcdTime[5] = BIN2BCD(pSystemTime->wYear - RTC_BASE_YEAR_MIN);
		        bcdTime[4] = BIN2BCD(pSystemTime->wMonth);
		        bcdTime[3] = BIN2BCD(pSystemTime->wDay);
		        bcdTime[2] = BIN2BCD(pSystemTime->wHour);
		        bcdTime[1] = BIN2BCD(pSystemTime->wMinute);
		        bcdTime[0] = BIN2BCD(pSystemTime->wSecond);

		        //  Initialize RTC with given values
		        OALTritonWrite(hTWL, TWL_YEARS_REG, bcdTime[5]);
		        OALTritonWrite(hTWL, TWL_MONTHS_REG, bcdTime[4]);
		        OALTritonWrite(hTWL, TWL_DAYS_REG, bcdTime[3]);
		        OALTritonWrite(hTWL, TWL_HOURS_REG, bcdTime[2]);
		        OALTritonWrite(hTWL, TWL_MINUTES_REG, bcdTime[1]);
		        OALTritonWrite(hTWL, TWL_SECONDS_REG, bcdTime[0]);

				OALTritonClose(hTWL);
			}
		}	
        // Convert to filetime
        if (NKSystemTimeToFileTime(pSystemTime, (FILETIME*)&fileTime))
            {
            // Compute the tick delta (indicates the time in the RTC)
            tickDelta = LocalGetTickCount() - s_rtc.baseTickCount;
            
            // Update all the parameters
            s_rtc.baseFiletime  = s_rtc.baseFiletime + ((ULONGLONG)tickDelta)*10000;
            s_rtc.baseOffset    = fileTime - s_rtc.baseFiletime;
            s_rtc.baseTickCount = LocalGetTickCount();

/*
// This code is to support implementation of a secure RTC clock for HS devices
// Unfortunately, we cannot call these registry functions here as it causes 
// critical section ordering violations early on in boot (visible in checked
// builds).  
            // Store the new secure offset value in registry
            if( NKRegCreateKeyEx(HKEY_LOCAL_MACHINE, RTC_REG_KEY, 0, NULL, 0, 0, NULL, &hKey, NULL) == ERROR_SUCCESS )
                {
                //  Convert offset to secs and store
                dwOffset = (DWORD)(s_rtc.baseOffset/10000000);

                NKRegSetValueEx(hKey, RTC_REG_OFFSET, 0, REG_DWORD, (UCHAR*)&dwOffset, sizeof(DWORD));

                //  Close key
                NKRegCloseKey(hKey);
                }
*/

            // Done
            rc = TRUE;
            }

        LeaveCriticalSection(&s_rtc.cs);
        }
    
    OALMSG(OAL_TIMER && OAL_FUNC, (L"-OEMSetRealTime\r\n"));

    return rc;
}

//------------------------------------------------------------------------------
//
//  Function:  OEMSetAlarmTime
//
//  This function is called by the kernel to set the real-time clock alarm.
//
BOOL
OEMSetAlarmTime(
    SYSTEMTIME *pSystemTime
    ) 
{
    BOOL rc = FALSE;

    RETAILMSG(1, (L"+OEMSetAlarmTime(%s)\r\n", SystemTimeToString(pSystemTime)));

    if (s_rtc.initialized)
        {
        // Save time to global structure
        EnterCriticalSection(&s_rtc.cs);

        // Round to seconds
        pSystemTime->wMilliseconds = 0;

        // Convert to filetime
        if (NKSystemTimeToFileTime(pSystemTime, (FILETIME*)&s_rtc.alarmFiletime))
            {
            //  Adjust alarm time by secure offset
            s_rtc.alarmFiletime  = s_rtc.alarmFiletime - s_rtc.baseOffset;

            //  Interrupt RTC driver to inform RTC HW that an alarm time was set
            s_rtc.updateFlag |= IOCTL_HAL_RTC_QUERY_SET_ALARM;

            NKSetInterruptEvent(s_rtc.sysIntRTC);
                    
            // Done
            rc = TRUE;
            }

        LeaveCriticalSection(&s_rtc.cs);
        }
    
    return rc;
}

//------------------------------------------------------------------------------
//
//  Function:  OALIoCtlHalRtcQuery
//
//  This function is called by RTC driver on each interrupt to look
//  set OEMSetAlarmTime actions.
//
BOOL
OALIoCtlHalRtcQuery(
    UINT32 code, 
    VOID *pInBuffer, 
    UINT32 inSize, 
    VOID *pOutBuffer, 
    UINT32 outSize, 
    UINT32 *pOutSize
    )
{
    BOOL rc = FALSE;
    IOCTL_HAL_RTC_QUERY_OUT *pQueryOut;

    OALMSG(OAL_TIMER && OAL_FUNC, (L"+OALIoCtlHalRtcQuery()\r\n"));

    //  Return Alarm info
    if (pOutSize != NULL) *pOutSize = sizeof(IOCTL_HAL_RTC_QUERY_OUT);

    if ((pOutBuffer == NULL) || 
        (outSize < sizeof(IOCTL_HAL_RTC_QUERY_OUT)))
        {
        NKSetLastError(ERROR_INVALID_PARAMETER);
        goto cleanUp;
        }

    //  Initialize return structure
    pQueryOut = (IOCTL_HAL_RTC_QUERY_OUT*) pOutBuffer;
    memset( pQueryOut, 0, sizeof(IOCTL_HAL_RTC_QUERY_OUT));

    // Copy and clear info...
    EnterCriticalSection(&s_rtc.cs);

    pQueryOut->flags = s_rtc.updateFlag;

    FiletimeToHWTime( s_rtc.baseFiletime, pQueryOut->time );
    FiletimeToHWTime( s_rtc.alarmFiletime, pQueryOut->alarm );

    s_rtc.updateFlag = 0;

    LeaveCriticalSection(&s_rtc.cs);

    // Done
    rc = TRUE;
    
cleanUp:    
    return rc;
}

//------------------------------------------------------------------------------
//
//  Function:  OALIoCtlHalRtcUpdate
//
//  This function is called periodically by RTC driver to update
//  RTC value. 
//
BOOL
OALIoCtlHalRtcUpdate(
    UINT32 code, 
    VOID *pInBuffer, 
    UINT32 inSize, 
    VOID *pOutBuffer, 
    UINT32 outSize, 
    UINT32 *pOutSize
    )
{
    BOOL    rc = FALSE;
    IOCTL_HAL_RTC_UPDATE_IN *rtcUpdate;
    SYSTEMTIME              baseSystemTime;

    RETAILMSG(1, (L"+OALIoCtlHalRtcUpdate()\r\n"));


    // Check basic parameters
    if ((pInBuffer == NULL) || (inSize < sizeof(IOCTL_HAL_RTC_UPDATE_IN)))
        {
        NKSetLastError(ERROR_INVALID_PARAMETER);
        goto cleanUp;
        }


    //  The RTC in Triton2 is set to periodically sync with the kernel time
    //  to ensure there is no clock drift.  When a sync event is triggered,
    //  the T2 RTC is used to set the base time in the kernel.  The RTC driver
    //  also loads any stored offset from the registry for restoring the time
    //  on warm boot scenarios

    EnterCriticalSection(&s_rtc.cs);

    rtcUpdate = (IOCTL_HAL_RTC_UPDATE_IN*) pInBuffer;

    //  Update the offset if it is currently 0
    if( s_rtc.baseOffset == 0 )
    {
        // We had saved signed 32bit offset value as unsigned in the registry.
        // Make sure that we convert the DWORD from registry to signed 32 bit integer.
        INT32 temp = (INT32)(rtcUpdate->offset);

        // Convert the signed 32 bit integer to 64bit signed integer.
        // This way, we recovered correct sign in 64bit baseOffset.
        s_rtc.baseOffset = (LONGLONG)temp; 

        // Convert offset in seconds to filetime base.
        s_rtc.baseOffset = s_rtc.baseOffset * 10000000;
    }

    //  Convert Triton2 RTC date/time to FILETIME
    baseSystemTime.wYear    = BCD2BIN(rtcUpdate->time[5]) + RTC_BASE_YEAR_MIN;
    baseSystemTime.wMonth   = BCD2BIN(rtcUpdate->time[4]);
    baseSystemTime.wDay     = BCD2BIN(rtcUpdate->time[3]);
    baseSystemTime.wHour    = BCD2BIN(rtcUpdate->time[2]);
    baseSystemTime.wMinute  = BCD2BIN(rtcUpdate->time[1]);
    baseSystemTime.wSecond  = BCD2BIN(rtcUpdate->time[0]);
    baseSystemTime.wMilliseconds = 0;

    NKSystemTimeToFileTime(&baseSystemTime, (FILETIME*)&s_rtc.baseFiletime);

    //  Reset the tick count
    s_rtc.baseTickCount = LocalGetTickCount();

    LeaveCriticalSection(&s_rtc.cs);

cleanUp:
    OALMSG(OAL_TIMER && OAL_FUNC, (L"-OALIoCtlHalRtcUpdate()\r\n"));

    return rc;
}

//------------------------------------------------------------------------------
//
//  Function:  OALIoCtlHalRtcAlarm
//
//  This function is called by RTC driver when alarm interrupt
//  occurs.
//
BOOL
OALIoCtlHalRtcAlarm(
    UINT32 code, 
    VOID *pInBuffer, 
    UINT32 inSize, 
    VOID *pOutBuffer, 
    UINT32 outSize, 
    UINT32 *pOutSize
    )
{
    OALMSG(OAL_TIMER && OAL_FUNC, (L"+OALIoCtlHalRtcAlarm()\r\n"));

    //  Alarm has been triggered by RTC driver.
    NKSetInterruptEvent(SYSINTR_RTC_ALARM);
    return TRUE;
}

BOOL 
OALIoCtlHalRtcSetAlarm(
    UINT32 code, 
    VOID *pInBuffer, 
    UINT32 inSize, 
    VOID *pOutBuffer, 
    UINT32 outSize, 
    UINT32 *pOutSize
)
{
	SYSTEMTIME *pSystemTime = NULL;

	if ((pInBuffer == NULL) || (inSize < sizeof(SYSTEMTIME)))
    {
     	NKSetLastError(ERROR_INVALID_PARAMETER);
    	return FALSE;
    }

	pSystemTime = (SYSTEMTIME*)pInBuffer;
	
	OEMSetAlarmTime(pSystemTime);
    
	return TRUE;
}

//------------------------------------------------------------------------------

BOOL
FiletimeToHWTime(
    ULONGLONG fileTime, 
    UCHAR bcdTime[6]
    )
{
    SYSTEMTIME systemTime;

    //  Convert filetime to RTC HW time format
    NKFileTimeToSystemTime((FILETIME*)&fileTime, &systemTime);

    //  Limit RTC year range
    if( systemTime.wYear < RTC_BASE_YEAR_MIN )
        systemTime.wYear = RTC_BASE_YEAR_MIN;

    if( systemTime.wYear > RTC_BASE_YEAR_MAX )
        systemTime.wYear = RTC_BASE_YEAR_MAX;

    bcdTime[5] = BIN2BCD(systemTime.wYear - RTC_BASE_YEAR_MIN);
    bcdTime[4] = BIN2BCD(systemTime.wMonth);
    bcdTime[3] = BIN2BCD(systemTime.wDay);
    bcdTime[2] = BIN2BCD(systemTime.wHour);
    bcdTime[1] = BIN2BCD(systemTime.wMinute);
    bcdTime[0] = BIN2BCD(systemTime.wSecond);
        
    return TRUE;
}

//------------------------------------------------------------------------------

#else

// RTC in TWL4030 cannot be used, use sync32k counter as RTC count register

// TODO: need place to store s_rtc.BaseFileTime that will survive warm boot...
// TODO: add alarm emulation using timer match interrupt?

//------------------------------------------------------------------------------
//
//  Define:  RTC_BASE_YEAR
//
//  Delta from which RTC counts years
//  Resolution of RTC years is from 2000 to 2099
//
#define RTC_BASE_YEAR_MIN       	2000
#define RTC_BASE_YEAR_DEFAULT		2009
#define RTC_BASE_YEAR_MAX       	2099

#define FILETIME_UNITS_PER_SECOND	10000000

//------------------------------------------------------------------------------

// Note Base... values need to be stored somewhere that survives warm reset
// For this RTC emulation, the end of the OAL ARGS area has been choosen.

typedef struct _RTC_SAVE_DATA {
	ULONGLONG BaseRtcFileTime;
	ULONG BaseRtcCount;
	//ULONGLONG AlarmFileTime;
} RTC_SAVE_DATA, *PRTC_SAVE_DATA;

static struct {
    BOOL bInitialized;           // Is RTC subsystem intialized?
    CRITICAL_SECTION cs;
	PRTC_SAVE_DATA pRtcSaveData;
} s_rtc = { FALSE };

//------------------------------------------------------------------------------

static DWORD GetRtcCount(void)
{
	// TODO: change to use GPTIMER to allow longer time between rollovers
	DWORD RegValue[2];
    OMAP_32KSYNCNT_REGS *p32KSynCntRegs = (OMAP_32KSYNCNT_REGS *)OALPAtoUA(OMAP_32KSYNC_REGS_PA);

	// not sure if this is required, should not cause problems even if not needed
	do
	{
		RegValue[0] = p32KSynCntRegs->CR;
		RegValue[1] = p32KSynCntRegs->CR;
	} 
	while (RegValue[0] != RegValue[1]);

	return RegValue[0];
}

static void GetBaseRtcTime(ULONGLONG *pBaseRtcFileTime, ULONG *pBaseRtcCount)
{
	*pBaseRtcFileTime = s_rtc.pRtcSaveData->BaseRtcFileTime;
	*pBaseRtcCount = s_rtc.pRtcSaveData->BaseRtcCount;
    //OALMSG(1, (L"GetBaseRtcTime: BaseRtcFileTime = 0x%x 0x%x, BaseRtcCount = 0x%x\r\n", (ULONG)(*pBaseRtcFileTime >> 32), (ULONG)*pBaseRtcFileTime, *pBaseRtcCount));
}

static void SetBaseRtcTime(ULONGLONG BaseRtcFileTime, ULONG BaseRtcCount)
{
    //OALMSG(1, (L"SetBaseRtcTime: BaseRtcFileTime = 0x%x 0x%x, BaseRtcCount = 0x%x\r\n", (ULONG)(BaseRtcFileTime >> 32), (ULONG)BaseRtcFileTime, BaseRtcCount));
	s_rtc.pRtcSaveData->BaseRtcFileTime = BaseRtcFileTime;
	s_rtc.pRtcSaveData->BaseRtcCount = BaseRtcCount;
}

//------------------------------------------------------------------------------

LPCWSTR
SystemTimeToString(
    SYSTEMTIME *pSystemTime
    )
{
    static WCHAR buffer[64];

    OALLogPrintf(
        buffer, 64, L"%04d.%02d.%02d %02d:%02d:%02d.%03d",
        pSystemTime->wYear, pSystemTime->wMonth, pSystemTime->wDay,
        pSystemTime->wHour, pSystemTime->wMinute, pSystemTime->wSecond, 
        pSystemTime->wMilliseconds
        );        
    return buffer;
}

//------------------------------------------------------------------------------
//
//  Function:  OALIoCtlHalInitRTC
//
//  This function is called by WinCE OS to initialize the time after cold boot. 
//  Input buffer contains SYSTEMTIME structure with default time value.
//
//  This function is also called by the OAL during IOCTL_HAL_INITREGISTRY processing.
//
BOOL
OALIoCtlHalInitRTC(
    UINT32 code, 
    VOID *pInBuffer, 
    UINT32 inSize, 
    VOID *pOutBuffer, 
    UINT32 outSize, 
    UINT32 *pOutSize
    )
{
    BOOL            rc = FALSE;
    SYSTEMTIME      *pGivenSystemTime = (LPSYSTEMTIME) pInBuffer;
    ULONGLONG      	GivenFileTime;
    BOOL            *pColdBoot;
	
    OALMSG(OAL_TIMER && OAL_FUNC, (L"+OALIoCtlHalInitRTC()\r\n"));

	// TODO: change to use GPTIMER to allow longer time between rollovers
	//s_rtc.pTimerRegs = OALPAtoUA(OMAP_GPTIMER10_REGS_PA); 

    // enable gptimer10 (configured in bsp_cfg.h to use 32,768 Hz clock)
    //PrcmDeviceEnableClocks(OMAP_DEVICE_GPTIMER10, TRUE);

    // Enable timer in auto-reload, divide by 256 (one count every 1/(32768/256) sec = 128 counts per second)
    //OUTREG32(&s_rtc.pTimerRegs->TCLR, GPTIMER_TCLR_AR|GPTIMER_TCLR_ST|GPTIMER_TCLR_PRE|GPTIMER_TCLR_PTV_DIV_256);

    // Wait until write is done
    //while ((INREG32(&s_rtc.pTimerRegs->TWPS) & GPTIMER_TWPS_TCLR) != 0)
	//{
	//}

	if (!s_rtc.bInitialized)
	{
		// the last sizeof(RTC_SAVE_DATA) bytes of the OAL args area are used by the RTC emulation
	    s_rtc.pRtcSaveData = (PRTC_SAVE_DATA) ((DWORD)OALPAtoUA(IMAGE_SHARE_ARGS_PA) + IMAGE_SHARE_ARGS_SIZE - sizeof(RTC_SAVE_DATA));

	    // Initialize RTC critical section
	    InitializeCriticalSection(&s_rtc.cs);
	}

    pColdBoot = OALArgsQuery(OAL_ARGS_QUERY_COLDBOOT);
    if ((pColdBoot != NULL) && *pColdBoot && pGivenSystemTime)
    {
		// reset time to given time
        OALMSG(OAL_TIMER && OAL_FUNC, (L" OALIoCtlHalInitRTC(): Clean boot, reset date time\r\n"));

		// convert given time to file time
        NKSystemTimeToFileTime(pGivenSystemTime, (FILETIME*)&GivenFileTime);

		// new time is now the base time
		SetBaseRtcTime(GivenFileTime, GetRtcCount());
    }

    // Now update RTC state values
    s_rtc.bInitialized = TRUE;

    //  Success
    rc = TRUE;

    OALMSG(OAL_TIMER && OAL_FUNC, (L"-OALIoCtlHalInitRTC() rc = %d\r\n", rc));
    return rc;
}


//------------------------------------------------------------------------------
//
//  Function:  OEMGetRealTime
//
//  This function is called by the kernel to retrieve the time from
//  the real-time clock.
//
BOOL
OEMGetRealTime(
    SYSTEMTIME *pSystemTime
    ) 
{
	DWORD		RtcDeltaInSeconds;
	ULONG		BaseRtcCount;
	ULONGLONG	BaseRtcFileTime;
	
    OALMSG(OAL_TIMER && OAL_FUNC, (L"+OEMGetRealTime()\r\n"));

    if (!s_rtc.bInitialized)
    {
        // Return default time if RTC isn't initialized
        pSystemTime->wYear   = RTC_BASE_YEAR_DEFAULT;
        pSystemTime->wMonth  = 1;
        pSystemTime->wDay    = 1;
        pSystemTime->wHour   = 0;
        pSystemTime->wMinute = 0;
        pSystemTime->wSecond = 0;
        pSystemTime->wDayOfWeek    = 0;
        pSystemTime->wMilliseconds = 0;
    }
    else
    {
        EnterCriticalSection(&s_rtc.cs);

		GetBaseRtcTime(&BaseRtcFileTime, &BaseRtcCount);

		// get number of seconds elapsed since last set or get realtime call
		RtcDeltaInSeconds = (GetRtcCount() - BaseRtcCount) >> 15;

		// add elapsed RTC count to base file time
		BaseRtcFileTime += (ULONGLONG)RtcDeltaInSeconds * FILETIME_UNITS_PER_SECOND;

		// update base rtc count by number of seconds elapsed
		BaseRtcCount += RtcDeltaInSeconds << 15;

		// save new base time
		SetBaseRtcTime(BaseRtcFileTime, BaseRtcCount);

        NKFileTimeToSystemTime((FILETIME*)&BaseRtcFileTime, pSystemTime);

        // don't return milliseconds, causes CETK failure
        pSystemTime->wMilliseconds = 0;

        LeaveCriticalSection(&s_rtc.cs);
    }

    OALMSG(OAL_TIMER && OAL_FUNC, (L"-OEMGetRealTime() = %s\r\n", SystemTimeToString(pSystemTime)));

    return TRUE;
}

//------------------------------------------------------------------------------
//
//  Function:  OEMSetRealTime
//
//  This function is called by the kernel to set the real-time clock.
//
BOOL
OEMSetRealTime(
    SYSTEMTIME *pSystemTime
    ) 
{
    BOOL        rc = FALSE;
    ULONGLONG   FileTime;
    OALMSG(OAL_TIMER && OAL_FUNC, (L"+OEMSetRealTime(%s)\r\n", SystemTimeToString(pSystemTime)));

    if (s_rtc.bInitialized)
    {
	    //  Limit RTC year range
    	if( pSystemTime->wYear < RTC_BASE_YEAR_MIN )
        	return rc;

	    if( pSystemTime->wYear > RTC_BASE_YEAR_MAX )
    	    return rc;

        EnterCriticalSection(&s_rtc.cs);

        // Discard milliseconds
        pSystemTime->wMilliseconds = 0;

        // Convert new systemtime to filetime
        if (NKSystemTimeToFileTime(pSystemTime, (FILETIME*)&FileTime))
        {
			SetBaseRtcTime(FileTime, GetRtcCount());

            // Done
            rc = TRUE;
        }

        LeaveCriticalSection(&s_rtc.cs);
    }
    
    OALMSG(OAL_TIMER && OAL_FUNC, (L"-OEMSetRealTime\r\n"));

    return rc;
}

//------------------------------------------------------------------------------
//
//  Function:  OEMSetAlarmTime
//
//  This function is called by the kernel to set the real-time clock alarm.
//
BOOL
OEMSetAlarmTime(
    SYSTEMTIME *pSystemTime
    ) 
{
    BOOL rc = FALSE;
	
    OALMSG(OAL_TIMER && OAL_FUNC, (L"+OEMSetAlarmTime(%s)\r\n", SystemTimeToString(pSystemTime)));

    if (s_rtc.bInitialized)
    {
        EnterCriticalSection(&s_rtc.cs);

        // Round to seconds
        pSystemTime->wMilliseconds = 0;

		// TODO: use GPTIMER interrupt/wake to emulate alarm

        LeaveCriticalSection(&s_rtc.cs);
    }
    
    return rc;
}

//------------------------------------------------------------------------------
//
//  Function:  OALIoCtlHalRtcQuery
//
//  This function is called by RTC driver on each interrupt to look
//  set OEMSetAlarmTime actions.
//
BOOL
OALIoCtlHalRtcQuery(
    UINT32 code, 
    VOID *pInBuffer, 
    UINT32 inSize, 
    VOID *pOutBuffer, 
    UINT32 outSize, 
    UINT32 *pOutSize
    )
{
    return FALSE;
}

//------------------------------------------------------------------------------
//
//  Function:  OALIoCtlHalRtcUpdate
//
//  This function is called periodically by RTC driver to update
//  RTC value. 
//
BOOL
OALIoCtlHalRtcUpdate(
    UINT32 code, 
    VOID *pInBuffer, 
    UINT32 inSize, 
    VOID *pOutBuffer, 
    UINT32 outSize, 
    UINT32 *pOutSize
    )
{
    return FALSE;
}

//------------------------------------------------------------------------------
//
//  Function:  OALIoCtlHalRtcAlarm
//
//  This function is called by RTC driver when alarm interrupt
//  occurs.
//
BOOL
OALIoCtlHalRtcAlarm(
    UINT32 code, 
    VOID *pInBuffer, 
    UINT32 inSize, 
    VOID *pOutBuffer, 
    UINT32 outSize, 
    UINT32 *pOutSize
    )
{
    OALMSG(OAL_TIMER && OAL_FUNC, (L"+OALIoCtlHalRtcAlarm()\r\n"));

    //  Alarm has been triggered by RTC driver.
    //NKSetInterruptEvent(SYSINTR_RTC_ALARM);
    return TRUE;
}

//------------------------------------------------------------------------------
#endif
