/** @file

  Copyright (c) 2004  - 2019, Intel Corporation. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

Module Name:

    Platform.c

Abstract:

    This is a generic template for a child of the IchSmm driver.


--*/

#include "SmmPlatform.h"


//
// Local variables
//
typedef struct {
  UINT8     Device;
  UINT8     Function;
} EFI_PCI_BUS_MASTER;

EFI_PCI_BUS_MASTER  mPciBm[] = {
  { PCI_DEVICE_NUMBER_PCH_PCIE_ROOT_PORTS, PCI_FUNCTION_NUMBER_PCH_PCIE_ROOT_PORT_1 },
  { PCI_DEVICE_NUMBER_PCH_PCIE_ROOT_PORTS, PCI_FUNCTION_NUMBER_PCH_PCIE_ROOT_PORT_2 },
  { PCI_DEVICE_NUMBER_PCH_PCIE_ROOT_PORTS, PCI_FUNCTION_NUMBER_PCH_PCIE_ROOT_PORT_3 },
  { PCI_DEVICE_NUMBER_PCH_PCIE_ROOT_PORTS, PCI_FUNCTION_NUMBER_PCH_PCIE_ROOT_PORT_4 },
  { PCI_DEVICE_NUMBER_PCH_USB, PCI_FUNCTION_NUMBER_PCH_EHCI }
};


UINT16                                  mAcpiBaseAddr;
SYSTEM_CONFIGURATION                    mSystemConfiguration;
EFI_SMM_VARIABLE_PROTOCOL               *mSmmVariable;
EFI_GLOBAL_NVS_AREA_PROTOCOL            *mGlobalNvsAreaPtr;

UINT16									                mPM1_SaveState16;
UINT32									                mGPE_SaveState32;

BOOLEAN                                 mSetSmmVariableProtocolSmiAllowed = TRUE;


//
// Variables. Need to initialize this from Setup
//
BOOLEAN                                 mWakeOnLanS5Variable;
BOOLEAN                                 mWakeOnRtcVariable;
UINT8                                   mWakeupDay;
UINT8                                   mWakeupHour;
UINT8                                   mWakeupMinute;
UINT8                                   mWakeupSecond;

//
// Use an enum. 0 is Stay Off, 1 is Last State, 2 is Stay On
//
UINT8                                   mAcLossVariable;


static
UINT8 mTco1Sources[] = {
  IchnNmi
};

VOID
S4S5ProgClock();

VOID
EnableS5WakeOnRtc();

UINT8
HexToBcd(
  UINT8 HexValue
  );

VOID
CpuSmmSxWorkAround(
  );

/**
  Initializes the SMM Handler Driver

  @param ImageHandle
  @param SystemTable

  @retval None

**/
EFI_STATUS
EFIAPI
InitializePlatformSmm (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS                                Status;
  UINT8                                     Index;
  EFI_HANDLE                                Handle;
  EFI_SMM_POWER_BUTTON_REGISTER_CONTEXT     PowerButtonContext;
  EFI_SMM_POWER_BUTTON_DISPATCH2_PROTOCOL   *PowerButtonDispatch;
  EFI_SMM_ICHN_DISPATCH_CONTEXT             IchnContext;
  EFI_SMM_ICHN_DISPATCH_PROTOCOL            *IchnDispatch;
  EFI_SMM_SX_DISPATCH2_PROTOCOL             *SxDispatch;
  EFI_SMM_SX_REGISTER_CONTEXT               EntryDispatchContext;
  EFI_SMM_SW_DISPATCH2_PROTOCOL             *SwDispatch;
  EFI_SMM_SW_REGISTER_CONTEXT               SwContext;
  UINTN                                     VarSize;
  EFI_BOOT_MODE                             BootMode;

  Handle = NULL;

  //
  //  Locate the Global NVS Protocol.
  //
  Status = gBS->LocateProtocol (
                  &gEfiGlobalNvsAreaProtocolGuid,
                  NULL,
                  (void **)&mGlobalNvsAreaPtr
                  );
  ASSERT_EFI_ERROR (Status);


  //
  // Get the ACPI Base Address
  //

  mAcpiBaseAddr = PchLpcPciCfg16( R_PCH_LPC_ACPI_BASE ) & B_PCH_LPC_ACPI_BASE_BAR;

  VarSize = sizeof(SYSTEM_CONFIGURATION);
  Status = SystemTable->RuntimeServices->GetVariable(
                          L"Setup",
                          &gEfiSetupVariableGuid,
                          NULL,
                          &VarSize,
                          &mSystemConfiguration
                          );
  if (EFI_ERROR (Status) || VarSize != sizeof(SYSTEM_CONFIGURATION)) {
    //The setup variable is corrupted
    VarSize = sizeof(SYSTEM_CONFIGURATION);
    Status = SystemTable->RuntimeServices->GetVariable(
              L"SetupRecovery",
              &gEfiSetupVariableGuid,
              NULL,
              &VarSize,
              &mSystemConfiguration
              );
    ASSERT_EFI_ERROR (Status);
  }  
  if (!EFI_ERROR(Status)) {
    mAcLossVariable = mSystemConfiguration.StateAfterG3;

    //
    // If LAN is disabled, WOL function should be disabled too.
    //
    if (mSystemConfiguration.Lan == 0x01){
      mWakeOnLanS5Variable = mSystemConfiguration.WakeOnLanS5;
    } else {
      mWakeOnLanS5Variable = FALSE;
    }

    mWakeOnRtcVariable = mSystemConfiguration.WakeOnRtcS5;
  }

  BootMode = GetBootModeHob ();

  //
  // Get the Power Button protocol
  //
  Status = gSmst->SmmLocateProtocol(
                    &gEfiSmmPowerButtonDispatch2ProtocolGuid,
                    NULL,
                    (VOID **)&PowerButtonDispatch
                    );
  ASSERT_EFI_ERROR(Status);

  if (BootMode != BOOT_ON_FLASH_UPDATE) {
    //
    // Register for the power button event
    //
    PowerButtonContext.Phase = EfiPowerButtonEntry;
    Status = PowerButtonDispatch->Register(
                                    PowerButtonDispatch,
                                    PowerButtonCallback,
                                    &PowerButtonContext,
                                    &Handle
                                    );
    ASSERT_EFI_ERROR(Status);
  }
  //
  // Get the Sx dispatch protocol
  //
  Status = gSmst->SmmLocateProtocol(
                    &gEfiSmmSxDispatch2ProtocolGuid,
                    NULL,
                    (VOID **)&SxDispatch
                    );
  ASSERT_EFI_ERROR(Status);

  //
  // Register entry phase call back function
  //
  EntryDispatchContext.Type  = SxS3;
  EntryDispatchContext.Phase = SxEntry;

  Status = SxDispatch->Register (
                         SxDispatch,
                         SxSleepEntryCallBack,
                         &EntryDispatchContext,
                         &Handle
                         );


  EntryDispatchContext.Type  = SxS4;

  Status = SxDispatch->Register (
                         SxDispatch,
                         S4S5CallBack,
                         &EntryDispatchContext,
                         &Handle
                         );
  ASSERT_EFI_ERROR(Status);


  EntryDispatchContext.Type  = SxS5;

  Status = SxDispatch->Register (
                         SxDispatch,
                         S4S5CallBack,
                         &EntryDispatchContext,
                         &Handle
                         );
  ASSERT_EFI_ERROR(Status);

  Status = SxDispatch->Register (
                         SxDispatch,
                         S5SleepAcLossCallBack,
                         &EntryDispatchContext,
                         &Handle
                         );
  ASSERT_EFI_ERROR(Status);

  //
  //  Get the Sw dispatch protocol
  //
  Status = gSmst->SmmLocateProtocol (
                    &gEfiSmmSwDispatch2ProtocolGuid,
                    NULL,
                    (VOID **)&SwDispatch
                    );
  ASSERT_EFI_ERROR(Status);

  //
  // Register ACPI enable handler
  //
  SwContext.SwSmiInputValue = ACPI_ENABLE;
  Status = SwDispatch->Register (
                         SwDispatch,
                         EnableAcpiCallback,
                         &SwContext,
                         &Handle
                         );
  ASSERT_EFI_ERROR(Status);

  //
  // Register ACPI disable handler
  //
  SwContext.SwSmiInputValue = ACPI_DISABLE;
  Status = SwDispatch->Register (
                         SwDispatch,
                         DisableAcpiCallback,
                         &SwContext,
                         &Handle
                         );
  ASSERT_EFI_ERROR(Status);


  //
  // Register for SmmReadyToBootCallback
  //
  SwContext.SwSmiInputValue = SMI_SET_SMMVARIABLE_PROTOCOL;
  Status = SwDispatch->Register(
                         SwDispatch,
                         SmmReadyToBootCallback,
                         &SwContext,
                         &Handle
                         );
  ASSERT_EFI_ERROR(Status);

  //
  // Get the ICHn protocol
  //
  Status = gBS->LocateProtocol(
                  &gEfiSmmIchnDispatchProtocolGuid,
                  NULL,
                  (void **)&IchnDispatch
                  );
  ASSERT_EFI_ERROR(Status);

  //
  // Register for the events that may happen that we do not care.
  // This is true for SMI related to TCO since TCO is enabled by BIOS WP
  //
  for (Index = 0; Index < sizeof(mTco1Sources)/sizeof(UINT8); Index++) {
    IchnContext.Type = mTco1Sources[Index];
    Status = IchnDispatch->Register(
                             IchnDispatch,
                             (EFI_SMM_ICHN_DISPATCH)DummyTco1Callback,
                             &IchnContext,
                             &Handle
                             );
    ASSERT_EFI_ERROR( Status );
  }

  //
  // Lock TCO_EN bit.
  //
  IoWrite16( mAcpiBaseAddr + R_PCH_TCO_CNT, IoRead16( mAcpiBaseAddr + R_PCH_TCO_CNT ) | B_PCH_TCO_CNT_LOCK );

  //
  // Set to power on from G3 dependent on WOL instead of AC Loss variable in order to support WOL from G3 feature.
  //
  //
  // Set wake from G3 dependent on AC Loss variable and Wake On LAN variable.
  // This is because no matter how, if WOL enabled or AC Loss variable not disabled, the board needs to wake from G3 to program the LAN WOL settings.
  // This needs to be done after LAN enable/disable so that the PWR_FLR state clear not impacted the WOL from G3 feature.
  //
  if (mAcLossVariable != 0x00) {
    SetAfterG3On (TRUE);
  } else {
    SetAfterG3On (FALSE);
  }




  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
SmmReadyToBootCallback (
  IN EFI_HANDLE  DispatchHandle,
  IN CONST VOID  *Context         OPTIONAL,
  IN OUT VOID    *CommBuffer      OPTIONAL,
  IN OUT UINTN   *CommBufferSize  OPTIONAL
  )
{
  EFI_STATUS Status;

  if (mSetSmmVariableProtocolSmiAllowed)
  {
  	//
    // It is okay to use gBS->LocateProtocol here because
    // we are still in trusted execution.
    //
  Status = gBS->LocateProtocol(
                  &gEfiSmmVariableProtocolGuid,
                  NULL,
                  (void **)&mSmmVariable
                  );

    ASSERT_EFI_ERROR(Status);

    //
    // mSetSmmVariableProtocolSmiAllowed will prevent this function from
    // being executed more than 1 time.
    //
    mSetSmmVariableProtocolSmiAllowed = FALSE;
  }

  return EFI_SUCCESS;
}

/**

  @param DispatchHandle   The handle of this callback, obtained when registering
  @param DispatchContext  The predefined context which contained sleep type and phase


  @retval EFI_SUCCESS     Operation successfully performed

**/
EFI_STATUS
EFIAPI    
SxSleepEntryCallBack (
  IN EFI_HANDLE  DispatchHandle,
  IN CONST VOID  *Context         OPTIONAL,
  IN OUT VOID    *CommBuffer      OPTIONAL,
  IN OUT UINTN   *CommBufferSize  OPTIONAL
  )
{
  EFI_STATUS              Status;

  Status = SaveRuntimeScriptTable ();
  if (EFI_ERROR(Status)) {
    return Status;
  }

  //
  // Workaround for S3 wake hang if C State is enabled
  //
  CpuSmmSxWorkAround();

  return EFI_SUCCESS;
}

VOID
CpuSmmSxWorkAround(
  )
{
  UINT64           MsrValue;

  MsrValue = AsmReadMsr64 (0xE2);

  if (MsrValue & BIT15) {
    return;
  }

  if (MsrValue & BIT10) {
    MsrValue &= ~BIT10;
    AsmWriteMsr64 (0xE2, MsrValue);
  }
}

VOID
ClearP2PBusMaster(
  )
{
  UINT8             Command;
  UINT8             Index;

  for (Index = 0; Index < sizeof(mPciBm)/sizeof(EFI_PCI_BUS_MASTER); Index++) {
    Command = MmioRead8 (
                MmPciAddress (0,
                  DEFAULT_PCI_BUS_NUMBER_PCH,
                  mPciBm[Index].Device,
                  mPciBm[Index].Function,
                  PCI_COMMAND_OFFSET
                )
              );
    Command &= ~EFI_PCI_COMMAND_BUS_MASTER;
    MmioWrite8 (
      MmPciAddress (0,
        DEFAULT_PCI_BUS_NUMBER_PCH,
        mPciBm[Index].Device,
        mPciBm[Index].Function,
        PCI_COMMAND_OFFSET
      ),
      Command
    );
  }
}

/**

  Set the AC Loss to turn on or off.

**/
VOID
SetAfterG3On (
  BOOLEAN Enable
  )
{
  UINT8             PmCon1;

  //
  // ICH handling portion
  //
  PmCon1 = MmioRead8 ( PMC_BASE_ADDRESS + R_PCH_PMC_GEN_PMCON_1 );
  PmCon1 &= ~B_PCH_PMC_GEN_PMCON_AFTERG3_EN;
  if (Enable) {
    PmCon1 |= B_PCH_PMC_GEN_PMCON_AFTERG3_EN;
  }
  MmioWrite8 (PMC_BASE_ADDRESS + R_PCH_PMC_GEN_PMCON_1, PmCon1);

}

/**
  When a power button event happens, it shuts off the machine

**/
EFI_STATUS
EFIAPI
PowerButtonCallback (
  IN EFI_HANDLE  DispatchHandle,
  IN CONST VOID  *Context         OPTIONAL,
  IN OUT VOID    *CommBuffer      OPTIONAL,
  IN OUT UINTN   *CommBufferSize  OPTIONAL
  )
{
  //
  // Check what the state to return to after AC Loss. If Last State, then
  // set it to Off.
  //
  UINT16  data16;

  if (mWakeOnRtcVariable) {
    EnableS5WakeOnRtc();
  }

  if (mAcLossVariable == 1) {
    SetAfterG3On (TRUE);
  }

  ClearP2PBusMaster();

  //
  // Program clock chip
  //
  S4S5ProgClock();


  data16 = (UINT16)(IoRead16(mAcpiBaseAddr + R_PCH_ACPI_GPE0a_EN));
  data16 &= B_PCH_ACPI_GPE0a_EN_PCI_EXP;


  //
  // Clear Sleep SMI Status
  //
  IoWrite16 (mAcpiBaseAddr + R_PCH_SMI_STS,
                (UINT16)(IoRead16 (mAcpiBaseAddr + R_PCH_SMI_STS) | B_PCH_SMI_STS_ON_SLP_EN));
  //
  // Clear Sleep Type Enable
  //
  IoWrite16 (mAcpiBaseAddr + R_PCH_SMI_EN,
                (UINT16)(IoRead16 (mAcpiBaseAddr + R_PCH_SMI_EN) & (~B_PCH_SMI_EN_ON_SLP_EN)));

  //
  // Clear Power Button Status
  //
  IoWrite16(mAcpiBaseAddr + R_PCH_ACPI_PM1_STS, B_PCH_ACPI_PM1_STS_PWRBTN);

  //
  // Shut it off now!
  //
  IoWrite16(mAcpiBaseAddr + R_PCH_ACPI_PM1_CNT, V_PCH_ACPI_PM1_CNT_S5);
  IoWrite16(mAcpiBaseAddr + R_PCH_ACPI_PM1_CNT, B_PCH_ACPI_PM1_CNT_SLP_EN | V_PCH_ACPI_PM1_CNT_S5);

  //
  // Should not return
  //
  CpuDeadLoop();

  return EFI_SUCCESS;
}


/**
  @param DispatchHandle  - The handle of this callback, obtained when registering

  @param DispatchContext - The predefined context which contained sleep type and phase

**/
EFI_STATUS
EFIAPI
S5SleepAcLossCallBack (
  IN EFI_HANDLE  DispatchHandle,
  IN CONST VOID  *Context         OPTIONAL,
  IN OUT VOID    *CommBuffer      OPTIONAL,
  IN OUT UINTN   *CommBufferSize  OPTIONAL
  )
{
  //
  // Check what the state to return to after AC Loss. If Last State, then
  // set it to Off.
  //
  if (mAcLossVariable == 1) {
    SetAfterG3On (TRUE);
  }
  return EFI_SUCCESS;
}

/**

  @param DispatchHandle   The handle of this callback, obtained when registering
  @param DispatchContext  The predefined context which contained sleep type and phase

  @retval Clears the Save State bit in the clock.

**/
EFI_STATUS
EFIAPI
S4S5CallBack (
  IN EFI_HANDLE  DispatchHandle,
  IN CONST VOID  *Context         OPTIONAL,
  IN OUT VOID    *CommBuffer      OPTIONAL,
  IN OUT UINTN   *CommBufferSize  OPTIONAL
  )
{

  UINT32        Data32;

  //
  // Enable/Disable USB Charging
  //
  if (mSystemConfiguration.UsbCharging == 0x01) {
    Data32 = IoRead32 (GPIO_BASE_ADDRESS + R_PCH_GPIO_SC_LVL);
    Data32 |= BIT8;
    IoWrite32(GPIO_BASE_ADDRESS + R_PCH_GPIO_SC_LVL, Data32);
  }

  return EFI_SUCCESS;
}


VOID
S4S5ProgClock()
{
}

/**
  SMI handler to enable ACPI mode

  Dispatched on reads from APM port with value 0xA0

  Disables the SW SMI Timer.
  ACPI events are disabled and ACPI event status is cleared.
  SCI mode is then enabled.

   Disable SW SMI Timer

   Clear all ACPI event status and disable all ACPI events
   Disable PM sources except power button
   Clear status bits

   Disable GPE0 sources
   Clear status bits

   Disable GPE1 sources
   Clear status bits

   Guarantee day-of-month alarm is invalid (ACPI 5.0 Section 4.8.2.4 "Real Time Clock Alarm")

   Enable SCI

  @param DispatchHandle  - EFI Handle
  @param DispatchContext - Pointer to the EFI_SMM_SW_REGISTER_CONTEXT

  @retval Nothing

**/
EFI_STATUS
EFIAPI
EnableAcpiCallback (
  IN EFI_HANDLE  DispatchHandle,
  IN CONST VOID  *Context         OPTIONAL,
  IN OUT VOID    *CommBuffer      OPTIONAL,
  IN OUT UINTN   *CommBufferSize  OPTIONAL
  )
{
  UINT32 SmiEn;
  UINT16 Pm1Cnt;
  UINT16 wordValue;
  UINT32 RegData32;

  //
  // Disable SW SMI Timer
  //
  SmiEn = IoRead32(mAcpiBaseAddr + R_PCH_SMI_EN);
  SmiEn &= ~B_PCH_SMI_STS_SWSMI_TMR;
  IoWrite32(mAcpiBaseAddr + R_PCH_SMI_EN, SmiEn);

  wordValue = IoRead16(mAcpiBaseAddr + R_PCH_ACPI_PM1_STS);
  if(wordValue & B_PCH_ACPI_PM1_STS_WAK) {
	  IoWrite32((mAcpiBaseAddr + R_PCH_ACPI_GPE0a_EN), 0x0000);
	  IoWrite32((mAcpiBaseAddr + R_PCH_ACPI_GPE0a_STS), 0xffffffff);
  }
  else {
		mPM1_SaveState16 = IoRead16(mAcpiBaseAddr + R_PCH_ACPI_PM1_EN);

		//
		// Disable PM sources except power button
		//
    // power button is enabled only for PCAT. Disabled it on Tablet platform
    //
    IoWrite16(mAcpiBaseAddr + R_PCH_ACPI_PM1_EN, B_PCH_ACPI_PM1_EN_PWRBTN);
		IoWrite16(mAcpiBaseAddr + R_PCH_ACPI_PM1_STS, 0xffff);

		mGPE_SaveState32 = IoRead16(mAcpiBaseAddr + R_PCH_ACPI_GPE0a_EN);
		IoWrite32(mAcpiBaseAddr + R_PCH_ACPI_GPE0a_EN, 0x0000);
		IoWrite32(mAcpiBaseAddr + R_PCH_ACPI_GPE0a_STS, 0xffffffff);

  }

  //
  // Guarantee day-of-month alarm is invalid (ACPI 5.0 Section 4.8.2.4 "Real Time Clock Alarm")
  // Clear Status D reg VM bit, Date of month Alarm to make Data in CMOS RAM is no longer Valid
  //
  IoWrite8 (PCAT_RTC_ADDRESS_REGISTER, RTC_ADDRESS_REGISTER_D);
  IoWrite8 (PCAT_RTC_DATA_REGISTER, 0x0);


	RegData32 = IoRead32(ACPI_BASE_ADDRESS + R_PCH_ALT_GP_SMI_EN);
	RegData32 &= ~(BIT7);
    IoWrite32((ACPI_BASE_ADDRESS + R_PCH_ALT_GP_SMI_EN), RegData32);


  //
  // Enable SCI
  //
  Pm1Cnt = IoRead16(mAcpiBaseAddr + R_PCH_ACPI_PM1_CNT);
  Pm1Cnt |= B_PCH_ACPI_PM1_CNT_SCI_EN;
  IoWrite16(mAcpiBaseAddr + R_PCH_ACPI_PM1_CNT, Pm1Cnt);

  return EFI_SUCCESS;
}

/**
  SMI handler to disable ACPI mode

  Dispatched on reads from APM port with value 0xA1

  ACPI events are disabled and ACPI event status is cleared.
  SCI mode is then disabled.
   Clear all ACPI event status and disable all ACPI events
   Disable PM sources except power button
   Clear status bits
   Disable GPE0 sources
   Clear status bits
   Disable GPE1 sources
   Clear status bits
   Disable SCI

  @param DispatchHandle  - EFI Handle
  @param DispatchContext - Pointer to the EFI_SMM_SW_REGISTER_CONTEXT

  @retval Nothing

**/
EFI_STATUS
EFIAPI
DisableAcpiCallback (
  IN EFI_HANDLE  DispatchHandle,
  IN CONST VOID  *Context         OPTIONAL,
  IN OUT VOID    *CommBuffer      OPTIONAL,
  IN OUT UINTN   *CommBufferSize  OPTIONAL
  )
{
  UINT16 Pm1Cnt;

  IoWrite16(mAcpiBaseAddr + R_PCH_ACPI_PM1_STS, 0xffff);
  IoWrite16(mAcpiBaseAddr + R_PCH_ACPI_PM1_EN, mPM1_SaveState16);

  IoWrite32(mAcpiBaseAddr + R_PCH_ACPI_GPE0a_STS, 0xffffffff);
  IoWrite32(mAcpiBaseAddr + R_PCH_ACPI_GPE0a_EN, mGPE_SaveState32);

  //
  // Disable SCI
  //
  Pm1Cnt = IoRead16(mAcpiBaseAddr + R_PCH_ACPI_PM1_CNT);
  Pm1Cnt &= ~B_PCH_ACPI_PM1_CNT_SCI_EN;
  IoWrite16(mAcpiBaseAddr + R_PCH_ACPI_PM1_CNT, Pm1Cnt);

  return EFI_SUCCESS;
}

/**
  When an unknown event happen.

 @retval None

**/
VOID
DummyTco1Callback (
  IN  EFI_HANDLE                              DispatchHandle,
  IN  EFI_SMM_ICHN_DISPATCH_CONTEXT           *DispatchContext
  )
{
}

/**

 @retval 1. Check Alarm interrupt is not set.
         2. Clear Alarm interrupt.
         2. Set RTC wake up date and time.
         2. Enable RTC wake up alarm.
         3. Enable ICH PM1 EN Bit 10(RTC_EN)

**/
VOID
EnableS5WakeOnRtc()
{
  UINT8             CmosData;
  UINTN             i;
  EFI_STATUS        Status;
  UINTN             VarSize;

  //
  // make sure EFI_SMM_VARIABLE_PROTOCOL is available
  //
  if (!mSmmVariable) {
    return;
  }

  VarSize = sizeof(SYSTEM_CONFIGURATION);

  //
  // read the variable into the buffer
  //
  Status = mSmmVariable->SmmGetVariable(
                           L"Setup",
                           &gEfiSetupVariableGuid,
                           NULL,
                           &VarSize,
                           &mSystemConfiguration
                           );
  if (EFI_ERROR(Status) || VarSize != sizeof(SYSTEM_CONFIGURATION)) {
    //The setup variable is corrupted
    VarSize = sizeof(SYSTEM_CONFIGURATION);
    Status = mSmmVariable->SmmGetVariable(
              L"SetupRecovery",
              &gEfiSetupVariableGuid,
              NULL,
              &VarSize,
              &mSystemConfiguration
              );
    ASSERT_EFI_ERROR (Status);
  }

  if (!mSystemConfiguration.WakeOnRtcS5) {
    return;
  }
  mWakeupDay = HexToBcd((UINT8)mSystemConfiguration.RTCWakeupDate);
  mWakeupHour = HexToBcd((UINT8)mSystemConfiguration.RTCWakeupTimeHour);
  mWakeupMinute = HexToBcd((UINT8)mSystemConfiguration.RTCWakeupTimeMinute);
  mWakeupSecond = HexToBcd((UINT8)mSystemConfiguration.RTCWakeupTimeSecond);

  //
  // Check RTC alarm interrupt is enabled.  If enabled, someone already
  // grabbed RTC alarm.  Just return.
  //
  IoWrite8(PCAT_RTC_ADDRESS_REGISTER, RTC_ADDRESS_REGISTER_B);
  if(IoRead8(PCAT_RTC_DATA_REGISTER) & B_RTC_ALARM_INT_ENABLE){
    return;
  }

  //
  // Set Date
  //
  IoWrite8(PCAT_RTC_ADDRESS_REGISTER, RTC_ADDRESS_REGISTER_D);
  CmosData = IoRead8(PCAT_RTC_DATA_REGISTER);
  CmosData &= ~(B_RTC_DATE_ALARM_MASK);
  CmosData |= mWakeupDay ;
  for(i = 0 ; i < 0xffff ; i++){
    IoWrite8(PCAT_RTC_DATA_REGISTER, CmosData);
    SmmStall(1);
    if(((CmosData = IoRead8(PCAT_RTC_DATA_REGISTER)) & B_RTC_DATE_ALARM_MASK)
         == mWakeupDay){
      break;
    }
  }

  //
  // Set Second
  //
  IoWrite8(PCAT_RTC_ADDRESS_REGISTER, RTC_ADDRESS_SECOND_ALARM);
  for(i = 0 ; i < 0xffff ; i++){
    IoWrite8(PCAT_RTC_DATA_REGISTER, mWakeupSecond);
    SmmStall(1);
    if(IoRead8(PCAT_RTC_DATA_REGISTER) == mWakeupSecond){
      break;
    }
  }

  //
  // Set Minute
  //
  IoWrite8(PCAT_RTC_ADDRESS_REGISTER, RTC_ADDRESS_MINUTE_ALARM);
  for(i = 0 ; i < 0xffff ; i++){
    IoWrite8(PCAT_RTC_DATA_REGISTER, mWakeupMinute);
    SmmStall(1);
    if(IoRead8(PCAT_RTC_DATA_REGISTER) == mWakeupMinute){
      break;
    }
  }

  //
  // Set Hour
  //
  IoWrite8(PCAT_RTC_ADDRESS_REGISTER, RTC_ADDRESS_HOUR_ALARM);
  for(i = 0 ; i < 0xffff ; i++){
    IoWrite8(PCAT_RTC_DATA_REGISTER, mWakeupHour);
    SmmStall(1);
    if(IoRead8(PCAT_RTC_DATA_REGISTER) == mWakeupHour){
      break;
    }
  }

  //
  // Wait for UIP to arm RTC alarm
  //
  IoWrite8(PCAT_RTC_ADDRESS_REGISTER, RTC_ADDRESS_REGISTER_A);
  while (IoRead8(PCAT_RTC_DATA_REGISTER) & 0x80);

  //
  // Read RTC register 0C to clear pending RTC interrupts
  //
  IoWrite8(PCAT_RTC_ADDRESS_REGISTER, RTC_ADDRESS_REGISTER_C);
  IoRead8(PCAT_RTC_DATA_REGISTER);

  //
  // Enable RTC Alarm Interrupt
  //
  IoWrite8(PCAT_RTC_ADDRESS_REGISTER, RTC_ADDRESS_REGISTER_B);
  IoWrite8(PCAT_RTC_DATA_REGISTER, IoRead8(PCAT_RTC_DATA_REGISTER) | B_RTC_ALARM_INT_ENABLE);

  //
  // Clear ICH RTC Status
  //
  IoWrite16(mAcpiBaseAddr + R_PCH_ACPI_PM1_STS, B_PCH_ACPI_PM1_STS_RTC);

  //
  // Enable ICH RTC event
  //
  IoWrite16(mAcpiBaseAddr + R_PCH_ACPI_PM1_EN,
              (UINT16)(IoRead16(mAcpiBaseAddr + R_PCH_ACPI_PM1_EN) | B_PCH_ACPI_PM1_EN_RTC));
}

UINT8
HexToBcd(
  IN UINT8 HexValue
  )
{
  UINTN   HighByte;
  UINTN   LowByte;

  HighByte    = (UINTN)HexValue / 10;
  LowByte     = (UINTN)HexValue % 10;

  return ((UINT8)(LowByte + (HighByte << 4)));
}

