//------------------------------------------------------------------------------
//
//  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:  Snled.cpp
//
//  Implementation of PDD layer of Notification LED(s)
//
//------------------------------------------------------------------------------
//
//  Copyright (C) 2004, Motorola Inc. All Rights Reserved
//
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//
// Copyright (C) 2004-2007, Freescale Semiconductor, Inc. All Rights Reserved.
// THIS SOURCE CODE, AND ITS USE AND DISTRIBUTION, IS SUBJECT TO THE TERMS
// AND CONDITIONS OF THE APPLICABLE LICENSE AGREEMENT
//
//------------------------------------------------------------------------------

#pragma warning(push)
#pragma warning(disable: 4115 4201 4204 4214)
#include <windows.h>
#include <nled.h>
#include <led_drvr.h>
#include <types.h>
#pragma warning(pop)

#include <bsp.h>
#include <bsp_cfg.h>
#include <nkintr.h>
 
#if GET_GPIOPINNAME_PORT(NOTIFICATION_LED_PINNAME) == 1
#define NOTIFICATION_LED_GPIO_PA        CSP_BASE_REG_PA_GPIO1
#elif GET_GPIOPINNAME_PORT(NOTIFICATION_LED_PINNAME) == 2
#define NOTIFICATION_LED_GPIO_PA        CSP_BASE_REG_PA_GPIO2
#elif GET_GPIOPINNAME_PORT(NOTIFICATION_LED_PINNAME) == 3
#define NOTIFICATION_LED_GPIO_PA        CSP_BASE_REG_PA_GPIO3
#else
#error Unknow NOTIFICATION_LED_PINNAME GPIO define
#endif
//------------------------------------------------------------------------------
// External Functions

//------------------------------------------------------------------------------
// External Variables

//------------------------------------------------------------------------------
// Defines 
#define NLED_MAX_LED                    1  // Total number of notification LEDs supported
#define NLED_MAX_OFFONBLINK             2  // Max OffOnBlink value

//------------------------------------------------------------------------------
// Types
struct NLedPddParameters
{
   HANDLE hNLedStopThreadEvent;
   HANDLE hNLedThreadStoppedEvent;
   BOOL   bNledState;
   BOOL   bNLedDriverSetDeviceThreadState;
   NLED_SETTINGS_INFO NLedSettingsInfo;
   NLED_SUPPORTS_INFO NLedSupportsInfo;
};

//------------------------------------------------------------------------------
// Global Variables 
static NLedPddParameters g_NLedPddParam[NLED_MAX_LED];

//------------------------------------------------------------------------------
// Local Variables 
 static PCSP_GPIO_REGS g_pGPIO;

//------------------------------------------------------------------------------
// Local Functions
static VOID TurnOnLED(DWORD LEDNum);
static VOID TurnOffLED(DWORD LEDNum);
DWORD WINAPI NLedDriverSetDeviceThread(LPVOID lParam);

//-----------------------------------------------------------------------------
//
// Function: NLedDriverInitialize
//
// This routine initializes the underlying NLED hardware as the NLED driver is loaded.
//
// Parameters:
//    None
//
// Returns:  
//       This routine should return TRUE if successful. If there's a problem
//       it should return FALSE
//
//-----------------------------------------------------------------------------
BOOL WINAPI NLedDriverInitialize(VOID)
{
   PHYSICAL_ADDRESS phyAddr;
   int ii;
   
   DEBUGMSG(ZONE_PDD, (__WFUNCTION__ _T("+\r\n")));
   
   g_pGPIO = NULL;
   phyAddr.QuadPart = NOTIFICATION_LED_GPIO_PA;
   
   g_pGPIO = (PCSP_GPIO_REGS)MmMapIoSpace(phyAddr, sizeof(CSP_GPIO_REGS), FALSE);
   if (g_pGPIO == NULL) {
      DEBUGMSG(ZONE_ERROR, (__WFUNCTION__ TEXT(": MmMapIoSpace(0x%08X) failed!\r\n"), phyAddr.QuadPart));
      return FALSE;
   }
   
   // initialization NLedPddContext struct
   memset(&g_NLedPddParam, 0, sizeof(g_NLedPddParam));
   
   for (ii=0; ii<ARRAYSIZE(g_NLedPddParam); ii++) {
      g_NLedPddParam[ii].bNledState = FALSE;
      g_NLedPddParam[ii].bNLedDriverSetDeviceThreadState = FALSE;
      g_NLedPddParam[ii].hNLedStopThreadEvent = NULL;
      g_NLedPddParam[ii].hNLedThreadStoppedEvent = NULL;
      
      g_NLedPddParam[ii].NLedSettingsInfo.LedNum = 0;
      g_NLedPddParam[ii].NLedSettingsInfo.MetaCycleOff = 0;
      g_NLedPddParam[ii].NLedSettingsInfo.MetaCycleOn = 0;
      g_NLedPddParam[ii].NLedSettingsInfo.OffOnBlink = 0;
      g_NLedPddParam[ii].NLedSettingsInfo.OffTime = 0;
      g_NLedPddParam[ii].NLedSettingsInfo.OnTime = 0;
      g_NLedPddParam[ii].NLedSettingsInfo.TotalCycleTime = 0;
   
      g_NLedPddParam[ii].NLedSupportsInfo.LedNum = 0;
      g_NLedPddParam[ii].NLedSupportsInfo.fAdjustOffTime = TRUE;
      g_NLedPddParam[ii].NLedSupportsInfo.fAdjustOnTime = TRUE;
      g_NLedPddParam[ii].NLedSupportsInfo.fAdjustTotalCycleTime = FALSE;
      g_NLedPddParam[ii].NLedSupportsInfo.lCycleAdjust = 1000;
      g_NLedPddParam[ii].NLedSupportsInfo.fMetaCycleOff = FALSE;
      g_NLedPddParam[ii].NLedSupportsInfo.fMetaCycleOn = FALSE;
   }
   
   for (ii=0; ii<ARRAYSIZE(g_NLedPddParam); ii++) {
      if ((g_NLedPddParam[ii].hNLedStopThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL) {
         while (ii != 0) {
            ii--;
            CloseHandle(g_NLedPddParam[ii].hNLedStopThreadEvent);
         }
         
         DEBUGMSG(ZONE_PDD, (__WFUNCTION__ _T(": CreateEvent() hNLedStopThreadEvent Failed\r\n")));
         return FALSE;
      }
   }
   
   for (ii=0; ii<ARRAYSIZE(g_NLedPddParam); ii++) {
      if ((g_NLedPddParam[ii].hNLedThreadStoppedEvent = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL) {
         while (ii) {
            ii--;
            CloseHandle(g_NLedPddParam[ii].hNLedThreadStoppedEvent);
         }
         
         DEBUGMSG(ZONE_PDD, (__WFUNCTION__ _T(": CreateEvent() hNLedThreadStoppedEvent Failed\r\n")));
         return FALSE;
      }
   }
   
   for (ii=0; ii<ARRAYSIZE(g_NLedPddParam); ii++) {
      //Turn off all LED
      TurnOffLED(ii);
   }
   
   DEBUGMSG(ZONE_PDD, (_T("-NLedDriverInitialize\r\n")));
       
   return TRUE;
}

//-----------------------------------------------------------------------------
//
// Function: NLedDriverDeInitialize
//
// This routine deinitializes the underlying NLED hardware as the NLED driver is unloaded.
//
// Parameters:
//    None
//
// Returns:  
//      This routine returns TRUE if successful, or FALSE if there's a problem
//
//-----------------------------------------------------------------------------
BOOL WINAPI NLedDriverDeInitialize(VOID)
{
   int ii;
   
   DEBUGMSG(ZONE_PDD, (_T("+NLedDriverDeInitialize: invoked\r\n")));

   for (ii=0; ii<ARRAYSIZE(g_NLedPddParam); ii++) {
      // Stop all threads
      SetEvent(g_NLedPddParam[ii].hNLedStopThreadEvent);
   }
   
   for (ii=0; ii<ARRAYSIZE(g_NLedPddParam); ii++) {
      // Wait for all threads to stop
      if (g_NLedPddParam[ii].bNLedDriverSetDeviceThreadState) {
         WaitForSingleObject(g_NLedPddParam[ii].hNLedThreadStoppedEvent, INFINITE);
      }
   }
   
   for(ii=0; ii<ARRAYSIZE(g_NLedPddParam); ii++) {
      CloseHandle(g_NLedPddParam[ii].hNLedThreadStoppedEvent);
      CloseHandle(g_NLedPddParam[ii].hNLedStopThreadEvent);
   }
   
   if (g_pGPIO != NULL) {
      MmUnmapIoSpace(g_pGPIO, sizeof(CSP_GPIO_REGS));
      g_pGPIO = NULL;
   }
   
   DEBUGMSG(ZONE_PDD, (__WFUNCTION__ _T("-\r\n")));
   
   return TRUE;
}

//-----------------------------------------------------------------------------
//
// Function: NLedDriverGetDeviceInfo
//
// 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.
//
// Parameters:
//      InputParm 
//          [in] INT     nInfoId
//
//      OutputParm 
//          [out] PVOID   pOutput
//
// Returns:  
//      This routine returns TRUE if successful, or FALSE if there's a problem
//
//-----------------------------------------------------------------------------
BOOL WINAPI NLedDriverGetDeviceInfo(
                       INT     nInfoId,
                       PVOID   pOutput
                       )
{
   BOOL fOk = TRUE;
   
   DEBUGMSG(ZONE_PDD, (__WFUNCTION__ _T("+\r\n")));
   
   if (nInfoId == NLED_COUNT_INFO_ID) {
      struct NLED_COUNT_INFO  *p = (struct NLED_COUNT_INFO*)pOutput;
      p -> cLeds = ARRAYSIZE(g_NLedPddParam);
   }
   else if (nInfoId ==  NLED_SUPPORTS_INFO_ID) {
      struct NLED_SUPPORTS_INFO  *p = (struct NLED_SUPPORTS_INFO*)pOutput;
      
      if (p->LedNum >= ARRAYSIZE(g_NLedPddParam)) {
         fOk = FALSE;
      }
      else {
         p->fAdjustOffTime        = g_NLedPddParam[p->LedNum].NLedSupportsInfo.fAdjustOffTime;
         p->fAdjustOnTime         = g_NLedPddParam[p->LedNum].NLedSupportsInfo.fAdjustOnTime;
         p->lCycleAdjust          = g_NLedPddParam[p->LedNum].NLedSupportsInfo.lCycleAdjust;
         p->fMetaCycleOff         = g_NLedPddParam[p->LedNum].NLedSupportsInfo.fMetaCycleOff;
         p->fMetaCycleOn          = g_NLedPddParam[p->LedNum].NLedSupportsInfo.fMetaCycleOn;
         p->fAdjustTotalCycleTime = g_NLedPddParam[p->LedNum].NLedSupportsInfo.fAdjustTotalCycleTime;
      }
   }  
   else if (nInfoId ==  NLED_SETTINGS_INFO_ID) {
      struct NLED_SETTINGS_INFO  *p = (struct NLED_SETTINGS_INFO*)pOutput;
      
      if (p->LedNum >= ARRAYSIZE(g_NLedPddParam)) {
         fOk = FALSE;
      }
      else {
         p->MetaCycleOff   = g_NLedPddParam[p->LedNum].NLedSettingsInfo.MetaCycleOff;
         p->MetaCycleOn    = g_NLedPddParam[p->LedNum].NLedSettingsInfo.MetaCycleOn;
         p->OffOnBlink     = g_NLedPddParam[p->LedNum].NLedSettingsInfo.OffOnBlink;
         p->OffTime        = g_NLedPddParam[p->LedNum].NLedSettingsInfo.OffTime;
         p->OnTime         = g_NLedPddParam[p->LedNum].NLedSettingsInfo.OnTime;
         p->TotalCycleTime = g_NLedPddParam[p->LedNum].NLedSettingsInfo.TotalCycleTime;
      }
   }
   else {
      fOk = FALSE;
   }
   
   DEBUGMSG(ZONE_PDD || (!fOk && ZONE_WARN), (__WFUNCTION__ _T("(%c)-\r\n"), fOk?'T':'F'));
   
   return (fOk);
}

//-----------------------------------------------------------------------------
//
// Function: NLedDriverSetDevice
//
// 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.
//
// Parameters:
//      InputParm 
//          [in] INT       nInfoId
//          [in] PVOID  pInput
//
// Returns:  
//      This routine returns TRUE if successful, or FALSE if there's a problem
//
//-----------------------------------------------------------------------------
BOOL WINAPI NLedDriverSetDevice(INT     nInfoId, PVOID   pInput)
{
   HANDLE hThread = NULL;  
   struct NLED_SETTINGS_INFO *pNledSettingsInfo = (struct NLED_SETTINGS_INFO*)pInput;
   
   DEBUGMSG(ZONE_PDD, (__WFUNCTION__ _T("+\r\n")));
   
   if (nInfoId != NLED_SETTINGS_INFO_ID) {
      DEBUGMSG(ZONE_PDD, (__WFUNCTION__ _T(": Invalid nInfoId\r\n")));   
      return FALSE;
   }
   
   // Check LedNum validity
   if (pNledSettingsInfo->LedNum >= ARRAYSIZE(g_NLedPddParam)) {
      DEBUGMSG(ZONE_PDD, (__WFUNCTION__ _T(": Invalid LedNum\r\n"))); 
      return FALSE;
   }
   
   // Check OffOnBlink validity
   if (pNledSettingsInfo->OffOnBlink > NLED_MAX_OFFONBLINK) {
      DEBUGMSG(ZONE_PDD, (_T("NLedDriverSetDevice: Invalid OffOnBlink\r\n")));
      return FALSE;
   }
   
   if (g_NLedPddParam[pNledSettingsInfo->LedNum].bNLedDriverSetDeviceThreadState) {
      SetEvent(g_NLedPddParam[pNledSettingsInfo->LedNum].hNLedStopThreadEvent);
      DEBUGMSG(ZONE_PDD, (_T("NLedDriverSetDevice: WaitForSingleObject - hNLedThreadStoppedEvent\r\n")));
      WaitForSingleObject(g_NLedPddParam[pNledSettingsInfo->LedNum].hNLedThreadStoppedEvent, INFINITE);
   }
   
   g_NLedPddParam[pNledSettingsInfo->LedNum].NLedSettingsInfo.LedNum = pNledSettingsInfo->LedNum;  
   g_NLedPddParam[pNledSettingsInfo->LedNum].NLedSettingsInfo.OffOnBlink = pNledSettingsInfo->OffOnBlink;
   
   if (pNledSettingsInfo->OffOnBlink == 0) {
      // OffOnBlink setting - OFF
      TurnOffLED(pNledSettingsInfo->LedNum);
      g_NLedPddParam[pNledSettingsInfo->LedNum].bNledState = FALSE;
   }  
   else if (pNledSettingsInfo->OffOnBlink == 1) {
      // OffOnBlink setting - ON
      TurnOnLED(pNledSettingsInfo->LedNum);
      g_NLedPddParam[pNledSettingsInfo->LedNum].bNledState = TRUE;
   }
   else  {
      // OffOnBlink setting - BLINK
      UINT Index = pNledSettingsInfo->LedNum;
      
      g_NLedPddParam[Index].bNLedDriverSetDeviceThreadState = TRUE;
      
      g_NLedPddParam[Index].NLedSettingsInfo = *(pNledSettingsInfo);
      
      // Round up OnTime and OffTime values 1 millisecond
      if (g_NLedPddParam[Index].NLedSettingsInfo.OnTime < 1000) {
         g_NLedPddParam[Index].NLedSettingsInfo.OnTime = 1000;
      }
      
      if(g_NLedPddParam[Index].NLedSettingsInfo.OffTime < 1000) {
         g_NLedPddParam[Index].NLedSettingsInfo.OffTime = 1000;
      }
      hThread = CreateThread(
                        NULL,                                     // Default thread security descriptor
                        0,                                        // Default stack size
                        NLedDriverSetDeviceThread,                // Start routine
                        &g_NLedPddParam[Index].NLedSettingsInfo,  // Start routine parameter
                        0,                                        // Run immediately
                        NULL                                      // Thread ID
                     );
      
      if (hThread == NULL) {
         g_NLedPddParam[Index].bNLedDriverSetDeviceThreadState = FALSE;
         return FALSE;
      }
      
      CloseHandle(hThread);
   }
   
   DEBUGMSG(ZONE_PDD, (__WFUNCTION__ _T("-\r\n")));
   
   return TRUE;
}

//-----------------------------------------------------------------------------
//
// Function: NLedDriverPowerDown
//
// 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.
//
// Parameters:
//      InputParm 
//          [in] BOOL power_down
//
// Returns:  
//       None
//
//-----------------------------------------------------------------------------
VOID WINAPI NLedDriverPowerDown(BOOL power_down)
{
   int ii;
   
   DEBUGMSG(ZONE_PDD, (__WFUNCTION__ _T("+\r\n")));
   
   for (ii=0; ii<ARRAYSIZE(g_NLedPddParam); ii++) {
      if (power_down) {
         if (g_NLedPddParam[ii].bNledState) {
            TurnOffLED(ii);  //Trun Off LED when LED is on
         }
      }
      else {
         if (g_NLedPddParam[ii].bNledState) {
            TurnOnLED(ii);  //restore LED to on
         }
      }
   }
   
   DEBUGMSG(ZONE_PDD, (__WFUNCTION__ _T("-\r\n")));
   
   return;
}

//-----------------------------------------------------------------------------
//
// Function: TurnOnLED
//
// This routine is Turn On LED
//
// Parameters:
//      [in] DWORD LEDNum
//
// Returns:
//
//-----------------------------------------------------------------------------
void TurnOnLED(DWORD LEDNum)
{
   if (LEDNum == 0) {
      // Turn On LED1
      //CLRREG32(&g_pGPIO->PORT[GET_GPIOPINNAME_PORT(NOTIFICATION_LED_PINNAME)].DR, GPIO_PIN_MASK(GET_GPIOPINNAME_PIN(NOTIFICATION_LED_PINNAME)));
      SET_GPIOPINNAME_LOW(g_pGPIO, NOTIFICATION_LED_PINNAME);
   }
#if NLED_MAX_LED > 1
   else if (LEDNum == 1) {
      // Turn On LED2
      OUTREG16(&g_pGPIO->BCTRL1_SET, CSP_BITFMASK(GPIO_BCTRL1_LED_2));  // Turn On LED2
   }
#endif
}

//-----------------------------------------------------------------------------
//
// Function: TurnOffLED
//
// This routine is Turn Off LED
//
// Parameters:
//      [in] DWORD LEDNum
//
// Returns:
//
//----------------------------------------------------------------------------
void TurnOffLED(DWORD LEDNum)
{
   if (LEDNum == 0) {
      // Turn Off LED1
      //SETREG32(&g_pGPIO->PORT[GET_GPIOPINNAME_PORT(NOTIFICATION_LED_PINNAME)].DR, GPIO_PIN_MASK(GET_GPIOPINNAME_PIN(NOTIFICATION_LED_PINNAME)));
      SET_GPIOPINNAME_HIGH(g_pGPIO, NOTIFICATION_LED_PINNAME);
   }
#if NLED_MAX_LED > 1
   else if (LEDNum == 1) {
      // Turn Off LED2
      OUTREG16(&g_pGPIO->BCTRL1_CLEAR, CSP_BITFMASK(GPIO_BCTRL1_LED_2));
   }
#endif
}


//-----------------------------------------------------------------------------
//
// Function: NLedDriverSetDeviceThread
//
// This routine is invoked by CreateThread() function in NLedDriverSetDevice when
// caller attempts to change the configuration of a notification LED to 'Blink' mode
//
// Parameters:
//      InputParm 
//          [in] LPVOID lParam
//
// Returns:  
//      This routine returns 0
//
//-----------------------------------------------------------------------------
DWORD WINAPI NLedDriverSetDeviceThread(LPVOID lParam)
{
   DEBUGMSG(ZONE_PDD, (__WFUNCTION__ _T("+\r\n")));
   
   NLED_SETTINGS_INFO *pNledSettingsInfo = (struct NLED_SETTINGS_INFO*)lParam;
   UINT Index = pNledSettingsInfo->LedNum;
   DWORD OnTimeOut = (DWORD)(pNledSettingsInfo->OnTime/1000);  // to convert micro to milli-seconds 
   DWORD OffTimeOut = (DWORD)(pNledSettingsInfo->OffTime/1000);   // to convert micro to milli-seconds 
   
   for (;;) {
      TurnOnLED(Index);
      g_NLedPddParam[Index].bNledState = TRUE;
      
      if (WaitForSingleObject(g_NLedPddParam[Index].hNLedStopThreadEvent, OnTimeOut) == WAIT_OBJECT_0) {
         break;
      }
      
      TurnOffLED(Index);
      g_NLedPddParam[Index].bNledState = FALSE;
      
      if (WaitForSingleObject(g_NLedPddParam[Index].hNLedStopThreadEvent, OffTimeOut) == WAIT_OBJECT_0) {
         break;
      }
   }
   
   pNledSettingsInfo->LedNum = 0;
   pNledSettingsInfo->MetaCycleOff = 0;
   pNledSettingsInfo->MetaCycleOn = 0;
   pNledSettingsInfo->OffOnBlink = 0;
   pNledSettingsInfo->OffTime = 0;
   pNledSettingsInfo->OnTime = 0;
   pNledSettingsInfo->TotalCycleTime = 0;
   
   SetEvent(g_NLedPddParam[Index].hNLedThreadStoppedEvent);
   
   g_NLedPddParam[Index].bNLedDriverSetDeviceThreadState = FALSE;
   
   DEBUGMSG(ZONE_PDD, (__WFUNCTION__ _T("-\r\n")));
   
   return 0;
}
