//
// 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.
//
/*++
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Module Name:

   Abstract:

   Notes:
   --*/
#include <windows.h>
#include <nled.h>
#include <led_drvr.h>
#include <types.h>
#include <bsp.h>
#include <drvmsg.h>
#include <drvlib_mem.h>

// Default on/off times
#define ONTIME_DFT				500000					// microseconds
#define OFFTIME_DFT				500000


#define	NUM_OF_LEDS				2

//masked by terry 20110929   
//DWORD g_LEDPort[NUM_OF_LEDS] = {LED6, LED7};

DWORD g_LEDPort[NUM_OF_LEDS] = {0, 0};


#define LED_OFF			0
#define LED_ON			1
#define LED_BLINK		2


#define LEDMSG(x, y) DBGMSG(x, (TEXT("[LED] : "))); DBGMSG(x, y);
#define LEDERR(x, y) ERRMSG((TEXT("[LED:ERR] : "))); ERRMSG(y);


const struct NLED_SUPPORTS_INFO g_LEDSupportsInfo[NUM_OF_LEDS] =
{
	// LED0
	// The on and off times are independently adjustable.
	{
		0,				// LedNum
		250000,			// lCycleAdjust
		FALSE,			// fAdjustTotalCycleTime
		TRUE,			// fAdjustOnTime
		TRUE,			// fAdjustOffTime
		TRUE,			// fMetaCycleOn
		TRUE			// fMetaCycleOff
	},

	// LED1
	// The on and off times are independently adjustable.
	{
		1,				// LedNum
		250000,			// lCycleAdjust
		FALSE,			// fAdjustTotalCycleTime
		TRUE,			// fAdjustOnTime
		TRUE,			// fAdjustOffTime
		TRUE,			// fMetaCycleOn
		TRUE			// fMetaCycleOff
	}
	
};

CRITICAL_SECTION g_Lock; // Protects g_LEDSettingInfo

volatile static GPIO_REG* v_pGPIOregs = NULL;

NLED_SETTINGS_INFO g_LEDSettingInfo[NUM_OF_LEDS];

HANDLE		g_LEDEvent[NUM_OF_LEDS];
HANDLE		g_LEDThread[NUM_OF_LEDS];

DWORD		g_LEDStatus[NUM_OF_LEDS];
DWORD		g_LEDBlinkCount[NUM_OF_LEDS];
DWORD		g_LEDBlinkOnCycle[NUM_OF_LEDS];
DWORD		g_LEDBlinkOffCycle[NUM_OF_LEDS];
DWORD		g_LEDPrevStatus[NUM_OF_LEDS];
BOOL		g_LEDInitialized[NUM_OF_LEDS];


BOOL HW_LED_INIT(DWORD dwID)
{
	BOOL bReturn = TRUE;
	
	LEDMSG(LED_FUNC, (TEXT("+%s\r\n"), _T(__FUNCTION__)));

	if(dwID >= NUM_OF_LEDS)
	{
		LEDERR(TRUE, (TEXT("LED%d is not available.\r\n"), dwID));
		bReturn = FALSE;
	}
	else
	{
		switch(dwID)
		{
			case 0:
			case 1:
				Set_PinFunction(v_pGPIOregs, g_LEDPort[dwID]);
				Set_PinPullUD(v_pGPIOregs, g_LEDPort[dwID], sgip_PULL_DISABLE);
				break;

			default:
				break;
		}
	}
	
	LEDMSG(LED_FUNC, (TEXT("-%s\r\n"), _T(__FUNCTION__)));

	return bReturn;
}


BOOL HW_LED_ON(DWORD dwID)
{
	BOOL bReturn = TRUE;
	
	LEDMSG(LED_FUNC, (TEXT("+%s\r\n"), _T(__FUNCTION__)));

	if(dwID >= NUM_OF_LEDS)
	{
		LEDERR(TRUE, (TEXT("LED%d is not available.\r\n"), dwID));
		bReturn = FALSE;
	}
	else
	{
		switch(dwID)
		{
			case 0:
			case 1:
				Set_PinData(v_pGPIOregs, g_LEDPort[dwID], 1);
				break;

			default:
				break;
		}

		g_LEDStatus[dwID] = LED_ON;
	}
	
	LEDMSG(LED_FUNC, (TEXT("-%s\r\n"), _T(__FUNCTION__)));

	return bReturn;
}



BOOL HW_LED_OFF(DWORD dwID)
{
	BOOL bReturn = TRUE;
	
	LEDMSG(LED_FUNC, (TEXT("+%s\r\n"), _T(__FUNCTION__)));

	if(dwID >= NUM_OF_LEDS)
	{
		LEDERR(TRUE, (TEXT("LED%d is not available.\r\n"), dwID));
		bReturn = FALSE;
	}
	else
	{
		switch(dwID)
		{
			case 0:
			case 1:
				Set_PinData(v_pGPIOregs, g_LEDPort[dwID], 0);
				break;

			default:
				break;
		}

		g_LEDStatus[dwID] = LED_OFF;
	}
	
	LEDMSG(LED_FUNC, (TEXT("-%s\r\n"), _T(__FUNCTION__)));

	return bReturn;
}


void NLED_Thread(DWORD dwID)
{
	DWORD dwBlinkCnt = 0;

	g_LEDEvent[dwID] = CreateEvent(NULL, FALSE, FALSE, NULL);

	// LED blink
	for (;;)
	{
		LEDMSG(LED_DBG, (TEXT("<LED%d> + Wait for an event\r\n"), dwID));
		WaitForSingleObject(g_LEDEvent[dwID], INFINITE);
		LEDMSG(LED_DBG, (TEXT("<LED%d> - Wait for an event\r\n"), dwID));

		while(g_LEDBlinkOnCycle[dwID] > 0)
		{
			LEDMSG(LED_DBG, (TEXT("<LED%d> Blink On Cycle..\r\n")));
		
			if (g_LEDStatus[dwID] == LED_ON)
			{
				// ON -> OFF
				HW_LED_OFF(dwID);
				Sleep(g_LEDSettingInfo[dwID].OffTime / 1000);
	
				HW_LED_ON(dwID);
				Sleep(g_LEDSettingInfo[dwID].OnTime / 1000);
			}
			else
			{
				// OFF -> ON
				HW_LED_ON(dwID);
				Sleep(g_LEDSettingInfo[dwID].OnTime / 1000);

				HW_LED_OFF(dwID);
				Sleep(g_LEDSettingInfo[dwID].OffTime / 1000);
			}

			g_LEDBlinkOnCycle[dwID]--;
		}


		while(g_LEDBlinkOffCycle[dwID] > 0)
		{
			LEDMSG(LED_DBG, (TEXT("<LED%d> Blink Off Cycle..\r\n")));
		
			Sleep(g_LEDSettingInfo[dwID].OffTime / 1000);
			Sleep(g_LEDSettingInfo[dwID].OnTime / 1000);

			g_LEDBlinkOffCycle[dwID]--;
		}
		
		// Restore previous LED state
		if (g_LEDPrevStatus[dwID] == LED_OFF)
			HW_LED_OFF(dwID);
		else
			HW_LED_ON(dwID);
	}

}



void GetLEDInfoFromHardware(const UINT LedNum, NLED_SETTINGS_INFO* pInfo)
{
	LEDMSG(LED_FUNC, (TEXT("+%s\r\n"), _T(__FUNCTION__)));
	EnterCriticalSection(&g_Lock);
	memcpy(pInfo, &g_LEDSettingInfo[LedNum], sizeof(*pInfo));
	LeaveCriticalSection(&g_Lock);
	LEDMSG(LED_FUNC, (TEXT("-%s\r\n"), _T(__FUNCTION__)));
}


BOOL SetLEDInfoToHardware(const NLED_SETTINGS_INFO* pInfo)
{
	BOOL bReturn = TRUE;

	LEDMSG(LED_FUNC, (TEXT("+%s\r\n"), _T(__FUNCTION__)));
	
	LEDMSG(LED_DBG, (TEXT("LednNum=%d, TotalCycleTime=%d, OnTime=%d, OffTime=%d, OffOnBlink=%d, MetaCycleOn=%d, MetaCycleOff=%d\r\n"),
		pInfo->LedNum, pInfo->TotalCycleTime, pInfo->OnTime, pInfo->OffTime, pInfo->OffOnBlink, pInfo->MetaCycleOn, pInfo->MetaCycleOff));

	// The order matters:  the write to OnOffBlink should come at the end, as it
	// is what triggers the emulator to update how it displays notifications.
	EnterCriticalSection(&g_Lock);

	// Validate the pInfo fields
	if (pInfo->LedNum >= NUM_OF_LEDS
	    || pInfo->OnTime < 0
	    || pInfo->OffTime < 0
	    || pInfo->OffOnBlink < 0
	    || pInfo->OffOnBlink > 2)
//	    || (pInfo->MetaCycleOn != 0 && pInfo->OnTime == 0) // allow MetaCycleOn=1 only if OnTime != 0
//	    || pInfo->MetaCycleOff != 0)
	{
		SetLastError(ERROR_INVALID_PARAMETER);
		bReturn = FALSE;
		goto CleanUp;
	}

	// Back-up the setting information.
	memcpy(&g_LEDSettingInfo[pInfo->LedNum], pInfo, sizeof(NLED_SETTINGS_INFO));

	switch (pInfo->OffOnBlink)
	{
	case LED_OFF:
		HW_LED_OFF(pInfo->LedNum);
		break;

	case LED_ON:
		HW_LED_ON(pInfo->LedNum);
		break;

	case LED_BLINK:
		// if On/Off time is zero(0) set to default value (250ms)
		if (pInfo->OnTime == 0)
			g_LEDSettingInfo[pInfo->LedNum].OnTime = ONTIME_DFT;
		if (pInfo->OffTime == 0)
			g_LEDSettingInfo[pInfo->LedNum].OffTime = OFFTIME_DFT;

		// set timeout for blink according to previous led status
		g_LEDPrevStatus[pInfo->LedNum] = g_LEDStatus[pInfo->LedNum];

		// calculate the number of blink cycles
		g_LEDBlinkOnCycle[pInfo->LedNum] = pInfo->MetaCycleOn;
		g_LEDBlinkOffCycle[pInfo->LedNum] = pInfo->MetaCycleOff;

		SetEvent(g_LEDEvent[pInfo->LedNum]);
		break;

	default:
		LEDMSG(LED_DBG, (TEXT("An invalid OffOnBlink parameter.\r\n")));
		SetLastError(ERROR_INVALID_PARAMETER);
		bReturn = FALSE;
		goto CleanUp;
		break;
	}


CleanUp:
	LeaveCriticalSection(&g_Lock);
	
	LEDMSG(LED_FUNC, (TEXT("-%s\r\n"), _T(__FUNCTION__)));
	
	return TRUE;
}




// The NLED MDD calls this routine to initialize the underlying NLED hardware.
// This routine should return TRUE if successful.  If there's a problem
// it should return FALSE and call SetLastError() to pass back the reason
// for the failure.
BOOL WINAPI
NLedDriverInitialize(
        VOID
        )
{
	int i;
	BOOL bReturn = TRUE;
	
	LEDMSG(LED_FUNC, (TEXT("+%s\r\n"), _T(__FUNCTION__)));

	// GPIO initialization
	v_pGPIOregs = (GPIO_REG*)DrvLib_MapIoSpace(BASE_REG_PA_GPIO, sizeof(GPIO_REG), FALSE);
	if (v_pGPIOregs == NULL)
	{
		LEDERR(TRUE, (_T("Failed to allocate the v_pGPIOregs.\r\n")));
		bReturn = FALSE;
		goto CleanUp;
	}

	// Critical section initialization
	InitializeCriticalSection(&g_Lock);


	for(i = 0;i < NUM_OF_LEDS;i++)
	{
		// Turn off all of LEDs.
		HW_LED_INIT(i);
		HW_LED_OFF(i);

		// Create a thread
		g_LEDThread[i] = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) NLED_Thread, (LPVOID)i, 0, 0);
		if(g_LEDThread[i] == NULL)
		{
			LEDERR(TRUE, (TEXT("Failed to create a thread for LED%d, error code = 0x%X\r\n"), i, GetLastError()));
			bReturn = FALSE;
			goto CleanUp;
		}
	}

CleanUp:
	LEDMSG(LED_FUNC, (TEXT("-%s\r\n"), _T(__FUNCTION__)));

	return (bReturn);
}


// The NLED MDD calls this routine to deinitialize the underlying NLED
// hardware as the NLED driver is unloaded.  It should return TRUE if
// successful.  If there's a problem this routine should return FALSE
// and call SetLastError() to pass back the reason for the failure.
BOOL WINAPI
NLedDriverDeInitialize(
        VOID
        )
{
	int i;
	
	LEDMSG(LED_FUNC, (TEXT("+%s\r\n"), _T(__FUNCTION__)));

	if (v_pGPIOregs != NULL)
	{
		DrvLib_UnmapIoSpace((PVOID)v_pGPIOregs);
		v_pGPIOregs = NULL;
	}

	for(i = 0;i < NUM_OF_LEDS;i++)
	{
		if(g_LEDThread[i] != NULL)
		{
			CloseHandle(g_LEDThread[i]);
			g_LEDThread[i] = NULL;
		}

		if(g_LEDEvent[i] != NULL)
		{
			CloseHandle(g_LEDEvent[i]);
			g_LEDEvent[i] = NULL;
		}
	}
	
	DeleteCriticalSection(&g_Lock);

	LEDMSG(LED_FUNC, (TEXT("-%s\r\n"), _T(__FUNCTION__)));

	return (TRUE);
}


// This routine retrieves information about the NLED device(s) that
// this driver supports.  The nInfoId parameter indicates what specific
// information is being queried and pOutput is a buffer to be filled in.
// The size of pOutput depends on the type of data being requested.  This
// routine returns TRUE if successful, or FALSE if there's a problem -- in
// which case it also calls SetLastError() to pass back more complete
// error information.  The NLED MDD invokes this routine when an application
// calls NLedGetDeviceInfo().
BOOL
WINAPI
NLedDriverGetDeviceInfo(
        INT nInfoId,
        PVOID pOutput                  // note: this is an IN/OUT parameter
        )
{
	BOOL bReturn = TRUE;
	DWORD dwLEDNumber = 0;
	
	LEDMSG(LED_FUNC, (TEXT("+%s\r\n"), _T(__FUNCTION__)));

	switch(nInfoId)
	{
		case NLED_COUNT_INFO_ID:
			LEDMSG(LED_DBG, (TEXT("NLED_COUNT_INFO_ID\r\n")));
			__try
			{
				((NLED_COUNT_INFO *)pOutput)->cLeds = NUM_OF_LEDS;
			}
			__except(EXCEPTION_EXECUTE_HANDLER) 
			{
				SetLastError(ERROR_INVALID_PARAMETER);
				bReturn = FALSE;
			}
			
			break;

		case NLED_SUPPORTS_INFO_ID:
			LEDMSG(LED_DBG, (TEXT("NLED_SUPPORTS_INFO_ID\r\n")));
			__try
			{
				dwLEDNumber = ((NLED_SUPPORTS_INFO*)pOutput)->LedNum;
				if (dwLEDNumber < NUM_OF_LEDS)
				{
					memcpy(pOutput, &g_LEDSupportsInfo[dwLEDNumber], sizeof(NLED_SUPPORTS_INFO));
				}
				else
				{
					SetLastError(ERROR_INVALID_PARAMETER);
					bReturn = FALSE;
				}
			}
			__except(EXCEPTION_EXECUTE_HANDLER) 
			{
				SetLastError(ERROR_INVALID_PARAMETER);
				bReturn = FALSE;
			}
			
			break;
			
		case NLED_SETTINGS_INFO_ID:
			LEDMSG(LED_DBG, (TEXT("NLED_SETTINGS_INFO_ID\r\n")));
			__try
			{
				dwLEDNumber = ((NLED_SETTINGS_INFO*)pOutput)->LedNum;
			}
			__except(EXCEPTION_EXECUTE_HANDLER)
			{
				SetLastError(ERROR_INVALID_PARAMETER);
				bReturn = FALSE;
				goto CleanUp;
			}

			if (dwLEDNumber < NUM_OF_LEDS)
			{
				GetLEDInfoFromHardware(dwLEDNumber, (NLED_SETTINGS_INFO *)pOutput);
			}
			else
			{
				SetLastError(ERROR_INVALID_PARAMETER);
				bReturn = FALSE;
				goto CleanUp;
			}

			break;

		default:
			LEDERR(TRUE, (TEXT("An invalid nInfoId for NLedDriverGetDeviceInfo().\r\n")));
			SetLastError(ERROR_INVALID_PARAMETER);
			bReturn = FALSE;
			break;
	}
	
CleanUp:

	LEDMSG(LED_FUNC, (TEXT("-%s\r\n"), _T(__FUNCTION__)));

	return (bReturn);
}


// This routine changes the configuration of an LED.  The nInfoId parameter
// indicates what kind of configuration information is being changed.
// Currently only the NLED_SETTINGS_INFO_ID value is supported.  The pInput
// parameter points to a buffer containing the data to be updated.  The size
// of the buffer depends on the value of nInfoId.  This routine returns TRUE
// if successful or FALSE if there's a problem -- in which case it also calls
// SetLastError().  The NLED MDD invokes this routine when an application
// calls NLedSetDevice().
BOOL
WINAPI
NLedDriverSetDevice(
        INT nInfoId,
        PVOID pInput
        )
{
	BOOL bReturn = TRUE;

	LEDMSG(LED_FUNC, (TEXT("+%s\r\n"), _T(__FUNCTION__)));

	if ( nInfoId == NLED_SETTINGS_INFO_ID )
	{
		if(pInput != NULL)
		{
			LEDMSG(LED_DBG, (TEXT("LedNum=%d, OnTime=%d, OffTime=%d, OffOnBlink=%d\r\n"), 
				((NLED_SETTINGS_INFO *)pInput)->LedNum, 
				((NLED_SETTINGS_INFO *)pInput)->OnTime,
				((NLED_SETTINGS_INFO *)pInput)->OffTime, 
				((NLED_SETTINGS_INFO *)pInput)->OffOnBlink));
				
			bReturn = SetLEDInfoToHardware((NLED_SETTINGS_INFO *)pInput);
		}
		else
		{
			LEDERR(TRUE, (TEXT("An invalid parameter pInput for NLedDriverSetDevice().\r\n")));
			SetLastError(ERROR_INVALID_PARAMETER);
			bReturn = FALSE;
		}
	}
	else
	{
		LEDERR(TRUE, (TEXT("NLED_SETTINGS_INFO_ID is only supported for the nInfoId.\r\n")));
		SetLastError(ERROR_INVALID_PARAMETER);
		bReturn = FALSE;
	}

	LEDMSG(LED_FUNC, (TEXT("-%s\r\n"), _T(__FUNCTION__)));

	return (bReturn);
}


// This routine is invoked by the driver MDD when the system suspends or
// resumes.  The power_down flag indicates whether the system is powering
// up or powering down.
VOID WINAPI
NLedDriverPowerDown(
        BOOL power_down
        )
{
	LEDMSG(LED_FUNC, (TEXT("+%s\r\n"), _T(__FUNCTION__)));

	LEDMSG(LED_FUNC, (TEXT("-%s\r\n"), _T(__FUNCTION__)));
}


