//
// 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.
//

/* ++

    PCF50606 Power Supply Controller & Battery Management

    Notes:
    ======
    o) I2c client

-- */

#include <windows.h>
#include <nkintr.h>
#include <windev.h>
#include <winbase.h>

#include "oalintr.h"

#include "pwr.h"

static DWORD PWR_IST(LPVOID Context);

// Make sure the driver builds OK in tinykern configuration
HMODULE g_hMod = NULL;
typedef void (*PFN_GwesPowerOffSystem)(void);
PFN_GwesPowerOffSystem g_pfnGwesPowerOffSystem = NULL;

// 
// Write PCF50606 registers directly from our cache
//
DWORD
HW_WriteRegisters(
    PPWR_CONTEXT pPWR,
    PUCHAR pBuff,   // Optional buffer
    UCHAR StartReg, // start register
    DWORD nRegs     // number of registers
    )
{
    return 1;
}


/* ++

Setup the regulators/converters.
Currenly uses default settings.

TODO: determine if we can lower anything.

-- */
DWORD
HW_InitRegulators(
    PPWR_CONTEXT    pPWR
    )
{
    return 1;
}


#define ENABLE  TRUE
#define DISABLE FALSE

DWORD
HW_InitCharger(
    PPWR_CONTEXT    pPWR,
    UCHAR           ucMode,
    UCHAR           ucFlags,
    BOOL            bEnable
    )
{
    return 1;
}


DWORD
HW_InitRegisters(
    PPWR_CONTEXT pPWR
    )
{
    DWORD retry = 0;

	// EINT0, GPF0 Port Init
	IOPreg * v_pIOPregs;

	v_pIOPregs = (volatile IOPreg *)VirtualAlloc(0, sizeof(IOPreg), MEM_RESERVE, PAGE_NOACCESS);
	VirtualCopy((PVOID)v_pIOPregs, (PVOID)(IOP_BASE), sizeof(IOPreg), PAGE_READWRITE | PAGE_NOCACHE);

	v_pIOPregs->rGPFCON  &= ~(0x3 << 0);		/* Set EINT0(GPF0) as EINT0							*/
	v_pIOPregs->rGPFCON  |=  (0x2 << 0);

    v_pIOPregs->rEXTINT0 &= ~(0x7 << 0);		/* Configure EINT0 as Falling Edge Mode				*/
    v_pIOPregs->rEXTINT0 |=  (0x4 << 0);

	VirtualFree((PVOID) v_pIOPregs, 0, MEM_RELEASE);
	// EINT0, GPF0 Port Init Done


    EnterCriticalSection(&pPWR->RegCS);

    // init DRV_GLOBAL
    pPWR->pDriverGlobals->power.State   = PCF50606_STATE_IDLE;
    pPWR->pDriverGlobals->power.Error   = ERROR_SUCCESS;

    pPWR->pDriverGlobals->power.ACLineStatus = 0;
	pPWR->pDriverGlobals->power.ACLineStatus |= AC_LINE_BACKUP_POWER;
	pPWR->pDriverGlobals->power.ACLineStatus |= AC_LINE_ONLINE;

    pPWR->pDriverGlobals->power.BatteryFlag  = 0;
	pPWR->pDriverGlobals->power.BatteryFlag |=  BATTERY_FLAG_CHARGING;
    
    pPWR->pDriverGlobals->power.bADCC1   = 0;
    pPWR->pDriverGlobals->power.bADCC2   = 0;
    pPWR->pDriverGlobals->power.bADCDAT1 = 0;
    pPWR->pDriverGlobals->power.bADCDAT2 = 0;
    pPWR->pDriverGlobals->power.bADCDAT3 = 0;


    LeaveCriticalSection(&pPWR->RegCS);
        
    return 0;
}


/* ++

    The reset value of the PCF50606 INTxM registers is 0: Interrupt enabled.
    Once we enable the interrupt they start firing unless we mask them _before_
    enabeling the interrupt(s).
    
-- */
DWORD
HW_Init(
    PPWR_CONTEXT pPWR
    )
{
    DWORD dwErr = ERROR_SUCCESS;
    BOOL    Ret;

	pPWR->pDriverGlobals = NULL;

	RETAILMSG(1, (TEXT("HW_Init : GetProcAddress \r\n"), dwErr));
    //  Load GwesPowerOffSystem from coredll
    g_hMod = LoadLibraryEx(L"coredll.dll", NULL, DONT_RESOLVE_DLL_REFERENCES);
    if(g_hMod) {
        g_pfnGwesPowerOffSystem = (PFN_GwesPowerOffSystem) GetProcAddress(g_hMod, L"GwesPowerOffSystem");
        if(!g_pfnGwesPowerOffSystem) {
            FreeLibrary(g_hMod);
            g_hMod = NULL;
        }
    }
    
	RETAILMSG(1, (TEXT("HW_Init : ERROR_INVALID_PARAMETER \r\n")));
    if ( !pPWR )
        return ERROR_INVALID_PARAMETER;
        
	RETAILMSG(1, (TEXT("HW_Init : InitializeCriticalSection \r\n")));
    InitializeCriticalSection(&pPWR->RegCS);

	RETAILMSG(1, (TEXT("HW_Init : VirtualAlloc \r\n")));
    // map in DRV_GLOB
    if ( pPWR->pDriverGlobals == NULL )
    {
        pPWR->pDriverGlobals =
            (PDRIVER_GLOBALS)
                  VirtualAlloc( 0,
                                DRIVER_GLOBALS_PHYSICAL_MEMORY_SIZE,
                                MEM_RESERVE,
                                PAGE_NOACCESS
                              );
        if ( pPWR->pDriverGlobals == NULL )
        {
            RETAILMSG( 1, (TEXT( "pPWR->pDriverGlobals: VirtualAlloc failed!\r\n")) );
	        goto _error_exit;
        }

        Ret = VirtualCopy( (LPVOID)pPWR->pDriverGlobals,
                           (LPVOID)DRIVER_GLOBALS_PHYSICAL_MEMORY_START,
                           DRIVER_GLOBALS_PHYSICAL_MEMORY_SIZE,
                           PAGE_READWRITE | PAGE_NOCACHE
                           );

        if ( Ret == FALSE )
        {
            RETAILMSG( 1, (TEXT( "pPWR->pDriverGlobals: VirtualCopy failed!\r\n")) );
	        goto _error_exit;
        }
    }

	RETAILMSG(1, (TEXT("HW_Init : pPWR->State \r\n")));

    // Init H/W
    pPWR->State = INITIALIZE;
    
    RETAILMSG(1, (TEXT("HW_Init : HW_InitRegisters \r\n"), dwErr));
	dwErr = HW_InitRegisters(pPWR);
    if ( dwErr ) {
	    RETAILMSG(1, (TEXT("HW_Init : HW_InitRegisters  Error \r\n"), dwErr));
        goto _error_exit;
    }

    //
    // create PWR_IST
    //
    RETAILMSG(1, (TEXT("HW_Init : CreateEvent \r\n"), dwErr));
    if ( (pPWR->ISTEvent = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL) {
        dwErr = GetLastError();
        RETAILMSG(1,(TEXT("PCF50606 ERROR: Unable to create IST event: %u \r\n"), dwErr));
        goto _error_exit;
    }

    RETAILMSG(1, (TEXT("HW_Init : InterruptInitialize \r\n"), dwErr));
    if( !InterruptInitialize(SYSINTR_POWER, pPWR->ISTEvent, NULL, 0) ) {
        dwErr = GetLastError();
        RETAILMSG(1,(TEXT("PCF50606 ERROR: Unable to initialize interrupt: %u\r\n"), dwErr));
        goto _error_exit;
    }

    RETAILMSG(1, (TEXT("HW_Init : CreateThread \r\n"), dwErr));
    if ( (pPWR->IST = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) PWR_IST, pPWR, 0, NULL)) == NULL) {
        dwErr = GetLastError();
        RETAILMSG(1,(TEXT("PCF50606 ERROR: Unable to create IST: %u\r\n"), dwErr));
        goto _error_exit;
    }
    
    RETAILMSG(1, (TEXT("HW_Init : CeSetThreadPriority \r\n"), dwErr));
    // TODO: registry override
    if ( !CeSetThreadPriority(pPWR->IST, POWER_THREAD_PRIORITY)) {
        dwErr = GetLastError();
        RETAILMSG(1, (TEXT("PCF50606 ERROR: CeSetThreadPriority ERROR:%d \r\n"), dwErr));
        goto _error_exit;
    }

    pPWR->State = RUN;

    // Finally, after we are initialized dispatch any unmasked interrupts 
    // we may have missed during system start up. We do this because the INTx
    // registers are cleared on read.
    if ( !dwErr ) 
    {
        RETAILMSG(1,(TEXT("HW_Init OOCS: 0x%.2x\r\n"), pPWR->ResetReg[0] ));
        RETAILMSG(1,(TEXT("HW_Init INT1: 0x%.2x\r\n"), pPWR->ResetReg[1]));
        RETAILMSG(1,(TEXT("HW_Init INT2: 0x%.2x\r\n"), pPWR->ResetReg[2]));
        RETAILMSG(1,(TEXT("HW_Init INT3: 0x%.2x\r\n"), pPWR->ResetReg[3]));
//        pPWR->ResetRegReady = TRUE;
//        SetEvent(pPWR->ISTEvent);
    }

    RETAILMSG(1, (TEXT("HW_Init : Donw \r\n"), dwErr));

_error_exit:
    return dwErr;
}


DWORD
HW_Deinit(
    PPWR_CONTEXT pPWR
    )
{
    if ( !pPWR )
        return ERROR_INVALID_PARAMETER;
    
    RETAILMSG(1,(TEXT("+PWR_Deinit \r\n")));

    pPWR->State = UNINITIALIZED;

    if (pPWR->hADC && CloseHandle(pPWR->hADC))
        pPWR->hADC = NULL;

    if (pPWR->hTSCPRES && CloseHandle(pPWR->hTSCPRES))
        pPWR->hTSCPRES = NULL;

    if (pPWR->hI2C && pPWR->hI2C != INVALID_HANDLE_VALUE && CloseHandle(pPWR->hI2C))
        pPWR->hI2C = INVALID_HANDLE_VALUE;
    
    InterruptDisable(SYSINTR_POWER);

    if (pPWR->ISTEvent && CloseHandle(pPWR->ISTEvent))
        pPWR->ISTEvent = NULL;
    
    if (pPWR->IST && CloseHandle(pPWR->IST))
        pPWR->IST = NULL;  

    if (pPWR->pDriverGlobals) {
        VirtualFree( pPWR->pDriverGlobals,
                     DRIVER_GLOBALS_PHYSICAL_MEMORY_SIZE ,
                     MEM_RELEASE
                    );
        pPWR->pDriverGlobals = NULL;
    }        

    //  Free the coredll instance if we have allocated one
    if(g_hMod) {
        FreeLibrary(g_hMod);
        g_hMod = NULL;
    }

    DeleteCriticalSection(&pPWR->RegCS);

    RETAILMSG(1,(TEXT("-PWR_Deinit \r\n")));

    return ERROR_SUCCESS;
}


DWORD 
HW_Open(
    PPWR_CONTEXT pPWR
    )
{
    return ERROR_SUCCESS;
}


DWORD 
HW_Close(
    PPWR_CONTEXT pPWR
    )
{
    return ERROR_SUCCESS;
}


BOOL
HW_PowerUp(
    PPWR_CONTEXT pPWR
   )
{
    RETAILMSG(1, (TEXT("PCF: HW_PowerUp \r\n")));
    pPWR->State = RESUME;
    SetInterruptEvent(SYSINTR_POWER);
    return TRUE;
}


BOOL
HW_PowerDown(
    PPWR_CONTEXT pPWR
   )
{
    pPWR->State = SUSPEND;
    return TRUE;
}


BOOL
HW_PowerCapabilities(
    PPWR_CONTEXT pPWR,
    PPOWER_CAPABILITIES ppc
    )
{
    return TRUE;
}


BOOL
HW_PowerSet(
    PPWR_CONTEXT pPWR,
    PCEDEVICE_POWER_STATE pDx   // IN, OUT
   )
{   
    CEDEVICE_POWER_STATE NewDx = *pDx;

    if ( VALID_DX(NewDx) ) 
    {
        // We only support D0, so do nothing.
        // Just return current state.
        pPWR->Dx = *pDx = D0;
        RETAILMSG(1, (TEXT("PCF: IOCTL_POWER_SET: D%u => D%u \r\n"), NewDx, pPWR->Dx));
        return TRUE;
    }

    return FALSE;
}


BOOL
HW_PowerGet(
    PPWR_CONTEXT pPWR,
    PCEDEVICE_POWER_STATE pDx
   )
{   
    // return our Current Dx value
    *pDx = pPWR->Dx;
    RETAILMSG(1, (TEXT("PCF: IOCTL_POWER_GET: D%u \r\n"), pPWR->Dx));

    return TRUE;
}


static DWORD 
PWR_IST(
    PPWR_CONTEXT pPWR
    )
{
	int CurrStatus = RUN;
    DWORD we;
	WCHAR  state[1024] = {0};
	LPWSTR pState = &state[0];
	DWORD dwBufChars = (sizeof(state) / sizeof(state[0]));
	DWORD  dwStateFlags = 0;
	DWORD dwErr;
    
    if (!pPWR)
        return ERROR_INVALID_PARAMETER;

    while (1) {
        __try {
            we = WaitForSingleObject(pPWR->ISTEvent, INFINITE);
			RETAILMSG(1,(TEXT("\r\nPWR_IST: pPWR->State = 0x%x \r\n"), pPWR->State));



			dwErr = GetSystemPowerState(pState, dwBufChars, &dwStateFlags);
			if (ERROR_SUCCESS != dwErr) {
				RETAILMSG(1, (TEXT("PMGET!GetSystemPowerState:ERROR:%d\n"), dwErr));
			} else {
				RETAILMSG(1, (TEXT("PMGET! System Power state is '%s', flags 0x%08x\n"), state, dwStateFlags));
				switch (POWER_STATE(dwStateFlags)) {
				case POWER_STATE_ON:
					RETAILMSG(1, (TEXT("PMGET!\tPOWER_STATE_ON\n")));
					break;
				case POWER_STATE_OFF:
					RETAILMSG(1, (TEXT("PMGET!\tPOWER_STATE_OFF\n")));
					break;
				case POWER_STATE_CRITICAL:
					RETAILMSG(1, (TEXT("PMGET!\tPOWER_STATE_CRITICAL\n")));
					break;
				case POWER_STATE_BOOT:
					RETAILMSG(1, (TEXT("PMGET!\tPOWER_STATE_BOOT\n")));
					break;
				case POWER_STATE_IDLE:
					RETAILMSG(1, (TEXT("PMGET!\tPOWER_STATE_IDLE\n")));
					break;
				case POWER_STATE_SUSPEND:
					RETAILMSG(1, (TEXT("PMGET!\tPOWER_STATE_SUSPEND\n")));
					break;
				case POWER_STATE_RESET:
					RETAILMSG(1, (TEXT("PMGET!\tPOWER_STATE_RESET\n")));
					break;
				case 0:
					break;
				default:
					RETAILMSG(1,(TEXT("PMGET!\tUnknown Power State Flags:0x%x\n"),dwStateFlags));
					ASSERT(0);
					break;
				}
			}

            
			switch (pPWR->State) {
                
                case RUN:
                    RETAILMSG(1, (TEXT("PWR_IST: RUN\r\n")));
					if (CurrStatus == SUSPEND)
					{
	                    RETAILMSG(1, (TEXT("CurrStatus == SUSPEND\r\n")));
						pPWR->State = RUN;
						CurrStatus = RUN;
						// clear any potential pending ADC interrupt
						InterruptDone(SYSINTR_POWER);
						SetSystemPowerState( NULL, POWER_STATE_ON, POWER_FORCE );
						continue;
					}
					else if (CurrStatus == RESUME)
					{
						RETAILMSG(1, (TEXT("CurrStatus == RESUME\r\n")));
						if ( wcscmp(state,TEXT("unattended")) == 0 )
						{
		                    RETAILMSG(1, (TEXT("Unattended Power Off Mode\r\n")));
							pPWR->State = RUN;
							CurrStatus = SUSPEND;
							continue;
						}
//						else if ( wcscmp(state,TEXT("resuming")) == 0 )
//						{
//		                    RETAILMSG(1, (TEXT("Still resuming...\r\n")));
//							Sleep(1000);
//							SetEvent(pPWR->ISTEvent);
//							continue;
//						}
						else
						{
		                    RETAILMSG(1, (TEXT("Power On\r\n")));
							pPWR->State = RUN;
							CurrStatus = RUN;
							// clear any potential pending ADC interrupt
							InterruptDone(SYSINTR_POWER);
							SetSystemPowerState( NULL, POWER_STATE_ON, POWER_FORCE );
							continue;
						}
					}
                    break;

                case INITIALIZE:
                    // Keep from reading our clear on read INTx registers
                    // during init.
                    RETAILMSG(1, (TEXT("PWR_IST: INITIALIZE\r\n")));
                    Sleep(10);
                    continue;
                    break;

                case SUSPEND:
                    RETAILMSG(1, (TEXT("PWR_IST: SUSPEND\r\n")));
//                    continue;
                    break;
                    
                case RESUME:
                case ADC_ERROR:
                    RETAILMSG(1,(TEXT("PWR_IST: %s \r\n"), pPWR->State == RESUME ? L"RESUME" : L"ADC_ERROR" ));
                    pPWR->State = RUN;
					CurrStatus = RESUME;
                    // clear any potential pending ADC interrupt
					InterruptDone(SYSINTR_POWER);
					Sleep(200);
					SetEvent(pPWR->ISTEvent);
//	                SetSystemPowerState( NULL, POWER_STATE_ON, POWER_FORCE );
                    continue;
                    break;
                    
                case UNINITIALIZED:
                    RETAILMSG(1,(TEXT("PWR_IST: ExitThread \r\n")));
                    ExitThread(ERROR_SUCCESS);
                    return ERROR_SUCCESS;

                default:
                    RETAILMSG(1,(TEXT("PWR_IST: unknown state: %u\r\n"), pPWR->State));
                    TEST_TRAP;
                    continue;
                    break;
            }

            //
            // get status
            //
//			EnterCriticalSection(&pPWR->RegCS);
//
//			if ( !pPWR->ResetRegReady ) {
//			} else {
//				// get & restore the cleared registers
//				pPWR->ResetRegReady = FALSE;
//			}

            LeaveCriticalSection(&pPWR->RegCS);
            
            // clear the interrupt before power off
            InterruptDone(SYSINTR_POWER);
			CurrStatus = SUSPEND;
            SetSystemPowerState( NULL, POWER_STATE_SUSPEND, POWER_FORCE );

        } _except(EXCEPTION_EXECUTE_HANDLER) {
            
            RETAILMSG(1,(TEXT("!!! PWR_IST EXCEPTION: 0x%X !!!\r\n"), GetExceptionCode() ));

        }
	}
}


/* ++

 Get/Set the PCF RTC.
 
 One neat PCF feature is the stand alone RTC.
 You could power the ARM core to full off and maintin the RTC & ALARM on the PCF.
 This is not a required feature for PPC2002 release, but OEMs are 
 free to add this as desired. You should sync the ARM & PCF RTC
 and update as appropriate. If you choose to implement this it would
 be good to power as much of the PCF off as possible to maintin it's RTC
 for much longer time.

-- */
BOOL 
PWR_GetRealTime(
    PPWR_CONTEXT pPWR,
    LPSYSTEMTIME lpst
    )
{
    return TRUE;
}


BOOL
PWR_SetRealTime(
    PPWR_CONTEXT pPWR,
    LPSYSTEMTIME lpst
    ) 
{
    return TRUE;
}


BOOL
HW_IOControl(
    PPWR_CONTEXT pPWR,
    DWORD dwCode,
    PBYTE pBufIn,
    DWORD dwLenIn,
    PBYTE pBufOut,
    DWORD dwLenOut,
    PDWORD pdwActualOut
   )
{
	return FALSE;
}


