/** @file
  PCH cycle deocding configuration and query library.

Copyright (c) 2017 - 2019, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/
#include <Base.h>
#include <Uefi/UefiBaseType.h>
#include <Library/IoLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseLib.h>
#include <Library/MmPciLib.h>
#include <PchAccess.h>
#include <Library/PchInfoLib.h>
#include <Library/PchPcrLib.h>
#include <Library/PchP2sbLib.h>
#include <Library/PchCycleDecodingLib.h>

/**
  Set PCH ACPI base address.
  The Address should not be 0 and should be 256 bytes alignment, and it is IO space, so must not exceed 0xFFFF.
  This cycle decoding is allowed to set when DMIC.SRL is 0.
  Programming steps:
  1. clear PMC PCI offset 44h [7] to diable ACPI base address first before changing base address.
  2. program PMC PCI offset 40h [15:2] to ACPI base address.
  3. set PMC PCI offset 44h [7] to enable ACPI base address.
  4. program "ACPI Base Address" PCR[DMI] + 27B4h[23:18, 15:2, 0] to [0x3F, PMC PCI Offset 40h bit[15:2],  1].
  5. Program "ACPI Base Destination ID"
      For SPT-LP:  Program PCR[DMI] + 27B8h[31:0] to 0x23A0
      For SPT-H:   Program PCR[DMI] + 27B8h[31:0] to 0x23A8

  @param[in] Address                    Address for ACPI base address.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid base address passed.
  @retval EFI_UNSUPPORTED               DMIC.SRL is set.
**/
EFI_STATUS
EFIAPI
PchAcpiBaseSet (
  IN  UINT16                            Address
  )
{
  UINTN                                 PmcBase;
  UINT32                                Dmic;
  UINT32                                Data32;
  PCH_SERIES                            PchSeries;

  PchSeries = GetPchSeries ();

  if (((Address & 0x00FF) != 0) ||
      (Address == 0)) {
    DEBUG ((DEBUG_ERROR, "PchAcpiBaseSet Error. Invalid Address: %x.\n", Address));
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  PchPcrRead32 (PID_DMI, R_PCH_PCR_DMI_DMIC, &Dmic);
  if ((Dmic & B_PCH_PCR_DMI_DMIC_SRL) != 0) {
    DEBUG ((DEBUG_ERROR, "PchAcpiBaseSet Error. DMIC.SRL is set.\n"));
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }

  PmcBase      = MmPciBase (
                   DEFAULT_PCI_BUS_NUMBER_PCH,
                   PCI_DEVICE_NUMBER_PCH_PMC,
                   PCI_FUNCTION_NUMBER_PCH_PMC
                   );
  if (MmioRead16 (PmcBase) == 0xFFFF) {
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }
  //
  // Disable ABASE in PMC Device first before changing base address.
  //
  MmioAnd8 (
    PmcBase + R_PCH_PMC_ACPI_CNT,
    (UINT8) ~B_PCH_PMC_ACPI_CNT_ACPI_EN
    );
  //
  // Program ABASE in PMC Device
  //
  MmioAndThenOr16 (
    PmcBase + R_PCH_PMC_ACPI_BASE,
    (UINT16) (~B_PCH_PMC_ACPI_BASE_BAR),
    Address
    );
  //
  // Enable ABASE in PMC Device
  //
  MmioOr8 (
    PmcBase + R_PCH_PMC_ACPI_CNT,
    B_PCH_PMC_ACPI_CNT_ACPI_EN
    );
  //
  // Program "ACPI Base Address" PCR[DMI] + 27B4h[23:18, 15:2, 0] to [0x3F, PMC PCI Offset 40h bit[15:2],  1]
  //
  PchPcrWrite32 (
    PID_DMI, R_PCH_PCR_DMI_ACPIBA,
    (0x00FC0001 + Address)
    );
  //
  // Program "ACPI Base Destination ID"
  // For SPT-LP:
  //   Program PCR[DMI] + 27B8h[31:0] to 0x23A0
  // For SPT-H:
  //   Program PCR[DMI] + 27B8h[31:0] to 0x23A8
  //
  if (PchSeries == PchLp) {
    Data32 = 0x23A0;
  } else {
    Data32 = 0x23A8;
  }
  PchPcrWrite32 (
    PID_DMI, R_PCH_PCR_DMI_ACPIBDID,
    Data32
    );

  return EFI_SUCCESS;
}

/**
  Get PCH ACPI base address.

  @param[out] Address                   Address of ACPI base address.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid pointer passed.
**/
EFI_STATUS
EFIAPI
PchAcpiBaseGet (
  OUT UINT16                            *Address
  )
{
  UINTN                                 PmcBase;

  if (Address == NULL) {
    DEBUG ((DEBUG_ERROR, "PchAcpiBaseGet Error. Invalid pointer.\n"));
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  PmcBase      = MmPciBase (
                   DEFAULT_PCI_BUS_NUMBER_PCH,
                   PCI_DEVICE_NUMBER_PCH_PMC,
                   PCI_FUNCTION_NUMBER_PCH_PMC
                   );
  if (MmioRead16 (PmcBase) == 0xFFFF) {
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }
  *Address = MmioRead16 (PmcBase + R_PCH_PMC_ACPI_BASE) & B_PCH_PMC_ACPI_BASE_BAR;

  return EFI_SUCCESS;
}

/**
  Set PCH PWRM base address.
  This cycle decoding is allowed to set when DMIC.SRL is 0.
  Programming steps:
  1. clear PMC PCI offset 44h [8] to diable PWRM base address first before changing PWRM base address.
  2. program PMC PCI offset 48h [31:16] to PM base address.
  3. set PMC PCI offset 44h [8] to enable PWRM base address.
  4. program "PM Base Address Memory Range Base" PCR[DMI] + 27ACh[15:0] to the same value programmed in PMC PCI Offset 48h bit[31:16], this has an implication of making sure the PWRMBASE to be 64KB aligned.
     program "PM Base Address Memory Range Limit" PCR[DMI] + 27ACh[31:16] to the value programmed in PMC PCI Offset 48h bit[31:16], this has an implication of making sure the memory allocated to PWRMBASE to be 64KB in size.
  5. Program "PM Base Control"
      For SPT-LP: Program PCR[DMI] + 27B0h[31, 30:0] to [1, 0x23A0]
      For SPT-H:  Program PCR[DMI] + 27B0h[31, 30:0] to [1, 0x23A8]

  @param[in] Address                    Address for PWRM base address.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid base address passed.
  @retval EFI_UNSUPPORTED               DMIC.SRL is set.
**/
EFI_STATUS
EFIAPI
PchPwrmBaseSet (
  IN  UINT32                            Address
  )
{
  UINTN                                 PmcBase;
  UINT32                                Dmic;
  UINT32                                Data32;
  PCH_SERIES                            PchSeries;

  PchSeries = GetPchSeries ();

  if (((Address & (~B_PCH_PMC_PWRM_BASE_BAR)) != 0) ||
      (Address == 0)) {
    DEBUG ((DEBUG_ERROR, "PchPwrmBaseSet Error. Invalid Address: %x.\n", Address));
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  PchPcrRead32 (PID_DMI, R_PCH_PCR_DMI_DMIC, &Dmic);
  if ((Dmic & B_PCH_PCR_DMI_DMIC_SRL) != 0) {
    DEBUG ((DEBUG_ERROR, "PchPwrmBaseSet Error. DMIC.SRL is set.\n"));
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }

  PmcBase      = MmPciBase (
                   DEFAULT_PCI_BUS_NUMBER_PCH,
                   PCI_DEVICE_NUMBER_PCH_PMC,
                   PCI_FUNCTION_NUMBER_PCH_PMC
                   );
  if (MmioRead16 (PmcBase) == 0xFFFF) {
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }

  //
  // Disable PWRMBASE in PMC Device first before changing PWRM base address.
  //
  MmioAnd16 (
    PmcBase + R_PCH_PMC_ACPI_CNT,
    (UINT16) ~B_PCH_PMC_ACPI_CNT_PWRM_EN
    );
  //
  // Program PWRMBASE in PMC Device
  //
  MmioAndThenOr32 (
    PmcBase + R_PCH_PMC_PWRM_BASE,
    (UINT32) (~B_PCH_PMC_PWRM_BASE_BAR),
    Address
    );
  //
  // Enable PWRMBASE in PMC Device
  //
  MmioOr16 (
    PmcBase + R_PCH_PMC_ACPI_CNT,
    B_PCH_PMC_ACPI_CNT_PWRM_EN
    );
  //
  // Program "PM Base Address Memory Range Base" PCR[DMI] + 27ACh[15:0] to the same value programmed in PMC PCI Offset 48h bit[31:16], this has an implication of making sure the PWRMBASE to be 64KB aligned.
  // Program "PM Base Address Memory Range Limit" PCR[DMI] + 27ACh[31:16] to the value programmed in PMC PCI Offset 48h bit[31:16], this has an implication of making sure the memory allocated to PWRMBASE to be 64KB in size.
  //
  PchPcrWrite32 (
    PID_DMI, R_PCH_PCR_DMI_PMBASEA,
    ((Address & 0xFFFF0000) | (Address >> 16))
    );
  //
  // Program "PM Base Control"
  // For SPT-LP:
  //   Program PCR[DMI] + 27B0h[31, 30:0] to [1, 0x23A0]
  // For SPT-H:
  //   Program PCR[DMI] + 27B0h[31, 30:0] to [1, 0x23A8]
  //
  if (PchSeries == PchLp) {
    Data32 = 0x800023A0;
  } else {
    Data32 = 0x800023A8;
  }
  PchPcrWrite32 (
    PID_DMI, R_PCH_PCR_DMI_PMBASEC,
    Data32
    );

  return EFI_SUCCESS;
}

/**
  Get PCH PWRM base address.

  @param[out] Address                   Address of PWRM base address.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid pointer passed.
**/
EFI_STATUS
EFIAPI
PchPwrmBaseGet (
  OUT UINT32                            *Address
  )
{
  UINTN                                 PmcBase;

  if (Address == NULL) {
    DEBUG ((DEBUG_ERROR, "PchPwrmBaseGet Error. Invalid pointer.\n"));
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  PmcBase      = MmPciBase (
                   DEFAULT_PCI_BUS_NUMBER_PCH,
                   PCI_DEVICE_NUMBER_PCH_PMC,
                   PCI_FUNCTION_NUMBER_PCH_PMC
                   );
  if (MmioRead16 (PmcBase) == 0xFFFF) {
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }
  *Address = MmioRead32 (PmcBase + R_PCH_PMC_PWRM_BASE) & B_PCH_PMC_PWRM_BASE_BAR;

  return EFI_SUCCESS;
}

/**
  Check if TCO Base register is present and unlocked.
  This should be called before calling PchTcoBaseSet ()

  @retval FALSE                         Either TCO base is locked or Smbus not present
  @retval TRUE                          TCO base is not locked

**/
BOOLEAN
EFIAPI
PchIsTcoBaseSetValid (
  VOID
  )
{
  UINTN                                 SmbusBase;

  SmbusBase = MmPciBase (
                DEFAULT_PCI_BUS_NUMBER_PCH,
                PCI_DEVICE_NUMBER_PCH_SMBUS,
                PCI_FUNCTION_NUMBER_PCH_SMBUS
                );
  if (MmioRead16 (SmbusBase) == 0xFFFF) {
    return FALSE;
  }
  //
  // Verify TCO base is not locked.
  //
  return ((MmioRead8 (SmbusBase + R_PCH_SMBUS_TCOCTL) & B_PCH_SMBUS_TCOCTL_TCO_BASE_LOCK) == 0);
}

/**
  Set PCH TCO base address.
  This cycle decoding is allowed to set when DMIC.SRL is 0.
  Programming steps:
  1. set Smbus PCI offset 54h [8] to enable TCO base address.
  2. program Smbus PCI offset 50h [15:5] to TCO base address.
  3. set Smbus PCI offset 54h [8] to enable TCO base address.
  4. program "TCO Base Address" PCR[DMI] + 2778h[15:5, 1] to [Smbus PCI offset 50h[15:5], 1].

  @param[in] Address                    Address for TCO base address.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid base address passed.
  @retval EFI_UNSUPPORTED               DMIC.SRL is set, or Smbus device not present
  @retval EFI_DEVICE_ERROR              TCO Base register is locked already
**/
EFI_STATUS
EFIAPI
PchTcoBaseSet (
  IN  UINT16                            Address
  )
{
  UINTN                                 SmbusBase;
  UINT32                                Dmic;

  if ((Address & ~B_PCH_SMBUS_TCOBASE_BAR) != 0) {
    DEBUG ((DEBUG_ERROR, "PchTcoBaseSet Error. Invalid Address: %x.\n", Address));
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }
  PchPcrRead32 (PID_DMI, R_PCH_PCR_DMI_DMIC, &Dmic);
  if ((Dmic & B_PCH_PCR_DMI_DMIC_SRL) != 0) {
    DEBUG ((DEBUG_ERROR, "PchTcoBaseSet Error. DMIC.SRL is set.\n"));
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }

  SmbusBase = MmPciBase (
                DEFAULT_PCI_BUS_NUMBER_PCH,
                PCI_DEVICE_NUMBER_PCH_SMBUS,
                PCI_FUNCTION_NUMBER_PCH_SMBUS
                );
  if (MmioRead16 (SmbusBase) == 0xFFFF) {
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }
  //
  // Verify TCO base is not locked.
  //
  if (!PchIsTcoBaseSetValid ()) {
    ASSERT (FALSE);
    return EFI_DEVICE_ERROR;
  }
  //
  // Disable TCO in SMBUS Device first before changing base address.
  // Byte access to not touch the TCO_BASE_LOCK bit
  //
  MmioAnd8 (
    SmbusBase + R_PCH_SMBUS_TCOCTL + 1,
    (UINT8) ~(B_PCH_SMBUS_TCOCTL_TCO_BASE_EN >> 8)
    );
  //
  // Program TCO in SMBUS Device
  //
  MmioAndThenOr16 (
    SmbusBase + R_PCH_SMBUS_TCOBASE,
    (UINT16) (~B_PCH_SMBUS_TCOBASE_BAR),
    Address
    );
  //
  // Enable TCO in SMBUS Device and lock TCO BASE
  //
  MmioOr16 (
    SmbusBase + R_PCH_SMBUS_TCOCTL,
    B_PCH_SMBUS_TCOCTL_TCO_BASE_EN | B_PCH_SMBUS_TCOCTL_TCO_BASE_LOCK
    );
  //
  // Program "TCO Base Address" PCR[DMI] + 2778h[15:5, 1] to [SMBUS PCI offset 50h[15:5], 1].
  //
  PchPcrWrite16 (
    PID_DMI, R_PCH_PCR_DMI_TCOBASE,
    (Address | BIT1)
    );

  return EFI_SUCCESS;
}

/**
  Get PCH TCO base address.

  @param[out] Address                   Address of TCO base address.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid pointer passed.
**/
EFI_STATUS
EFIAPI
PchTcoBaseGet (
  OUT UINT16                            *Address
  )
{
  if (Address == NULL) {
    DEBUG ((DEBUG_ERROR, "PchTcoBaseGet Error. Invalid pointer.\n"));
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }
  //
  // Read "TCO Base Address" PCR[DMI] + 2778h[15:5]
  // Don't read TCO base address from SMBUS PCI register since SMBUS might be disabled.
  //
  PchPcrRead16 (
    PID_DMI, R_PCH_PCR_DMI_TCOBASE,
    Address
    );
  *Address &= B_PCH_PCR_DMI_TCOBASE_TCOBA;

  return EFI_SUCCESS;
}

/**
  Set PCH LPC/eSPI generic IO range.
  For generic IO range, the base address must align to 4 and less than 0xFFFF, and the length must be power of 2
  and less than or equal to 256. Moreover, the address must be length aligned.
  This function basically checks the address and length, which should not overlap with all other generic ranges.
  If no more generic range register available, it returns out of resource error.
  This cycle decoding is allowed to set when DMIC.SRL is 0.
  Some IO ranges below 0x100 have fixed target. The target might be ITSS,RTC,LPC,PMC or terminated inside P2SB
  but all predefined and can't be changed. IO range below 0x100 will be rejected in this function except below ranges:
    0x00-0x1F,
    0x44-0x4B,
    0x54-0x5F,
    0x68-0x6F,
    0x80-0x8F,
    0xC0-0xFF
  Steps of programming generic IO range:
  1. Program LPC/eSPI PCI Offset 84h ~ 93h of Mask, Address, and Enable.
  2. Program LPC/eSPI Generic IO Range #, PCR[DMI] + 2730h ~ 273Fh to the same value programmed in LPC/eSPI PCI Offset 84h~93h.

  @param[in] Address                    Address for generic IO range base address.
  @param[in] Length                     Length of generic IO range.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid base address or length passed.
  @retval EFI_OUT_OF_RESOURCES          No more generic range available.
  @retval EFI_UNSUPPORTED               DMIC.SRL is set.
**/
EFI_STATUS
EFIAPI
PchLpcGenIoRangeSet (
  IN  UINT16                            Address,
  IN  UINTN                             Length
  )
{
  EFI_STATUS                            Status;
  PCH_LPC_GEN_IO_RANGE_LIST             LpcGenIoRangeList;
  UINTN                                 LpcBase;
  UINTN                                 Index;
  UINTN                                 BaseAddr;
  UINTN                                 MaskLength;
  UINTN                                 TempMaxAddr;
  UINT32                                Data32;
  UINTN                                 ArraySize;
  static struct EXCEPT_RANGE {
    UINT8 Start;
    UINT8 Length;
  } ExceptRanges[] = { {0x00, 0x20}, {0x44, 0x08}, {0x54, 0x0C}, {0x68, 0x08}, {0x80, 0x10}, {0xC0, 0x40} };

  //
  // Note: Inside this function, don't use debug print since it's could used before debug print ready.
  //

  //
  // For generic IO range, the base address must align to 4 and less than 0xFFFF,
  // the length must be power of 2 and less than or equal to 256, and the address must be length aligned.
  // IO range below 0x100 will be rejected in this function except below ranges:
  //   0x00-0x1F,
  //   0x44-0x4B,
  //   0x54-0x5F,
  //   0x68-0x6F,
  //   0x80-0x8F,
  //   0xC0-0xFF
  //
  if (((Length & (Length - 1)) != 0)  ||
      ((Address & (UINT16) ~B_PCH_LPC_GENX_DEC_IOBAR) != 0) ||
      (Length > 256)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }
  if (Address < 0x100) {
    ArraySize = sizeof (ExceptRanges) / sizeof (struct EXCEPT_RANGE);
    for (Index = 0; Index < ArraySize; Index++) {
      if ((Address >= ExceptRanges[Index].Start) &&
          ((Address + Length) <= ((UINTN) ExceptRanges[Index].Start + (UINTN) ExceptRanges[Index].Length))) {
        break;
      }
    }
    if (Index >= ArraySize) {
      ASSERT (FALSE);
      return EFI_INVALID_PARAMETER;
    }
  }

  //
  // check if range overlap
  //
  Status  = PchLpcGenIoRangeGet (&LpcGenIoRangeList);
  if (EFI_ERROR (Status)) {
    ASSERT (FALSE);
    return Status;
  }

  for (Index = 0; Index < PCH_LPC_GEN_IO_RANGE_MAX; Index++) {
    BaseAddr = LpcGenIoRangeList.Range[Index].BaseAddr;
    MaskLength   = LpcGenIoRangeList.Range[Index].Length;
    if (BaseAddr == 0) {
      continue;
    }
    if (((Address >= BaseAddr) && (Address < (BaseAddr + MaskLength))) ||
        (((Address + Length) > BaseAddr) && ((Address + Length) <= (BaseAddr + MaskLength)))) {
      if ((Address >= BaseAddr) && (Length <= MaskLength)) {
        //
        // return SUCCESS while range is covered.
        //
        return EFI_SUCCESS;
      }

      if ((Address + Length) > (BaseAddr + MaskLength)) {
        TempMaxAddr = Address + Length;
      } else {
        TempMaxAddr = BaseAddr + MaskLength;
      }
      if (Address > BaseAddr) {
        Address = (UINT16) BaseAddr;
      }
      Length = TempMaxAddr - Address;
      break;
    }
  }
  //
  // If no range overlap
  //
  if (Index >= PCH_LPC_GEN_IO_RANGE_MAX) {
    //
    // Find a empty register
    //
    for (Index = 0; Index < PCH_LPC_GEN_IO_RANGE_MAX; Index++) {
      BaseAddr = LpcGenIoRangeList.Range[Index].BaseAddr;
      if (BaseAddr == 0) {
        break;
      }
    }
    if (Index >= PCH_LPC_GEN_IO_RANGE_MAX) {
      return EFI_OUT_OF_RESOURCES;
    }
  }

  //
  // This cycle decoding is only allowed to set when DMIC.SRL is 0.
  //
  PchPcrRead32 (PID_DMI, R_PCH_PCR_DMI_DMIC, &Data32);
  if ((Data32 & B_PCH_PCR_DMI_DMIC_SRL) != 0) {
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }

  //
  // Program LPC/eSPI generic IO range register accordingly.
  //
  LpcBase = MmPciBase (
              DEFAULT_PCI_BUS_NUMBER_PCH,
              PCI_DEVICE_NUMBER_PCH_LPC,
              PCI_FUNCTION_NUMBER_PCH_LPC
              );
  Data32 =  (UINT32) (((Length - 1) << 16) & B_PCH_LPC_GENX_DEC_IODRA);
  Data32 |= (UINT32) Address;
  Data32 |= B_PCH_LPC_GENX_DEC_EN;
  //
  // Program LPC/eSPI PCI Offset 84h ~ 93h of Mask, Address, and Enable.
  //
  MmioWrite32 (
    LpcBase + R_PCH_LPC_GEN1_DEC + Index * 4,
    Data32
    );
  //
  // Program LPC Generic IO Range #, PCR[DMI] + 2730h ~ 273Fh to the same value programmed in LPC/eSPI PCI Offset 84h~93h.
  //
  PchPcrWrite32 (
    PID_DMI, (UINT16) (R_PCH_PCR_DMI_LPCLGIR1 + Index * 4),
    Data32
    );

  return EFI_SUCCESS;
}

/**
  Get PCH LPC/eSPI generic IO range list.
  This function returns a list of base address, length, and enable for all LPC/eSPI generic IO range regsiters.

  @param[out] LpcGenIoRangeList         Return all LPC/eSPI generic IO range register status.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid base address passed.
**/
EFI_STATUS
EFIAPI
PchLpcGenIoRangeGet (
  OUT PCH_LPC_GEN_IO_RANGE_LIST         *LpcGenIoRangeList
  )
{
  UINTN                                 Index;
  UINTN                                 LpcBase;
  UINT32                                Data32;

  //
  // Note: Inside this function, don't use debug print since it's could used before debug print ready.
  //

  if (LpcGenIoRangeList == NULL) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  LpcBase      = MmPciBase (
                   DEFAULT_PCI_BUS_NUMBER_PCH,
                   PCI_DEVICE_NUMBER_PCH_LPC,
                   PCI_FUNCTION_NUMBER_PCH_LPC
                   );

  for (Index = 0; Index < PCH_LPC_GEN_IO_RANGE_MAX; Index++) {
    Data32 = MmioRead32 (LpcBase + R_PCH_LPC_GEN1_DEC + Index * 4);
    LpcGenIoRangeList->Range[Index].BaseAddr = Data32 & B_PCH_LPC_GENX_DEC_IOBAR;
    LpcGenIoRangeList->Range[Index].Length   = ((Data32 & B_PCH_LPC_GENX_DEC_IODRA) >> 16) + 4;
    LpcGenIoRangeList->Range[Index].Enable   = Data32 & B_PCH_LPC_GENX_DEC_EN;
  }

  return EFI_SUCCESS;
}

/**
  Set PCH LPC/eSPI memory range decoding.
  This cycle decoding is allowed to set when DMIC.SRL is 0.
  Programming steps:
  1. Program LPC/eSPI PCI Offset 98h [0] to [0] to disable memory decoding first before changing base address.
  2. Program LPC/eSPI PCI Offset 98h [31:16, 0] to [Address, 1].
  3. Program LPC/eSPI Memory Range, PCR[DMI] + 2740h to the same value programmed in LPC/eSPI PCI Offset 98h.

  @param[in] Address                    Address for memory base address.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid base address or length passed.
  @retval EFI_OUT_OF_RESOURCES          No more generic range available.
  @retval EFI_UNSUPPORTED               DMIC.SRL is set.
**/
EFI_STATUS
EFIAPI
PchLpcMemRangeSet (
  IN  UINT32                            Address
  )
{
  UINTN                                 LpcBase;
  UINT32                                Dmic;

  if ((Address & (~B_PCH_LPC_LGMR_MA)) != 0) {
    DEBUG ((DEBUG_ERROR, "PchLpcMemRangeSet Error. Invalid Address: %x.\n", Address));
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }
  PchPcrRead32 (PID_DMI, R_PCH_PCR_DMI_DMIC, &Dmic);
  if ((Dmic & B_PCH_PCR_DMI_DMIC_SRL) != 0) {
    DEBUG ((DEBUG_ERROR, "PchLpcMemRangeSet Error. DMIC.SRL is set.\n"));
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }

  LpcBase      = MmPciBase (
                   DEFAULT_PCI_BUS_NUMBER_PCH,
                   PCI_DEVICE_NUMBER_PCH_LPC,
                   PCI_FUNCTION_NUMBER_PCH_LPC
                   );
  //
  // Program LPC/eSPI PCI Offset 98h [0] to [0] to disable memory decoding first before changing base address.
  //
  MmioAnd32 (
    LpcBase + R_PCH_LPC_LGMR,
    (UINT32) ~B_PCH_LPC_LGMR_LMRD_EN
    );
  //
  // Program LPC/eSPI PCI Offset 98h [31:16, 0] to [Address, 1].
  //
  MmioWrite32 (
    LpcBase + R_PCH_LPC_LGMR,
    (Address | B_PCH_LPC_LGMR_LMRD_EN)
    );
  //
  // Program LPC Memory Range, PCR[DMI] + 2740h to the same value programmed in LPC/eSPI PCI Offset 98h.
  //
  PchPcrWrite32 (
    PID_DMI, R_PCH_PCR_DMI_LPCGMR,
    (Address | B_PCH_LPC_LGMR_LMRD_EN)
    );

  return EFI_SUCCESS;
}

/**
  Get PCH LPC/eSPI memory range decoding address.

  @param[out] Address                   Address of LPC/eSPI memory decoding base address.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid base address passed.
**/
EFI_STATUS
EFIAPI
PchLpcMemRangeGet (
  OUT UINT32                            *Address
  )
{
  UINTN                                 LpcBase;

  if (Address == NULL) {
    DEBUG ((DEBUG_ERROR, "PchLpcMemRangeGet Error. Invalid pointer.\n"));
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  LpcBase      = MmPciBase (
                   DEFAULT_PCI_BUS_NUMBER_PCH,
                   PCI_DEVICE_NUMBER_PCH_LPC,
                   PCI_FUNCTION_NUMBER_PCH_LPC
                   );
  *Address = MmioRead32 (LpcBase + R_PCH_LPC_LGMR) & B_PCH_LPC_LGMR_MA;
  return EFI_SUCCESS;
}

/**
  Set PCH BIOS range deocding.
  This will check General Control and Status bit 10 (GCS.BBS) to identify SPI or LPC/eSPI and program BDE register accordingly.
  Please check EDS for detail of BiosDecodeEnable bit definition.
    bit 15: F8-FF Enable
    bit 14: F0-F8 Enable
    bit 13: E8-EF Enable
    bit 12: E0-E8 Enable
    bit 11: D8-DF Enable
    bit 10: D0-D7 Enable
    bit  9: C8-CF Enable
    bit  8: C0-C7 Enable
    bit  7: Legacy F Segment Enable
    bit  6: Legacy E Segment Enable
    bit  5: Reserved
    bit  4: Reserved
    bit  3: 70-7F Enable
    bit  2: 60-6F Enable
    bit  1: 50-5F Enable
    bit  0: 40-4F Enable
  This cycle decoding is allowed to set when DMIC.SRL is 0.
  Programming steps:
  1. if GCS.BBS is 0 (SPI), program SPI offset D8h to BiosDecodeEnable.
     if GCS.BBS is 1 (LPC/eSPi), program LPC offset D8h to BiosDecodeEnable.
  2. program LPC BIOS Decode Enable, PCR[DMI] + 2744h to the same value programmed in LPC or SPI Offset D8h.

  @param[in] BiosDecodeEnable           Bios decode enable setting.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_UNSUPPORTED               DMIC.SRL is set.
**/
EFI_STATUS
EFIAPI
PchBiosDecodeEnableSet (
  IN  UINT16                            BiosDecodeEnable
  )
{
  UINTN                                 BaseAddr;
  UINT32                                DmiGcsBbs;
  UINT32                                Dmic;

  PchPcrRead32 (PID_DMI, R_PCH_PCR_DMI_DMIC, &Dmic);
  if ((Dmic & B_PCH_PCR_DMI_DMIC_SRL) != 0) {
    DEBUG ((DEBUG_ERROR, "PchBiosDecodeEnableSet Error. DMIC.SRL is set.\n"));
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }

  PchPcrRead32 (PID_DMI, R_PCH_PCR_DMI_GCS, &DmiGcsBbs);
  DmiGcsBbs &= B_PCH_PCR_DMI_BBS;
  //
  // Check General Control and Status (GCS) [10]
  // '0': SPI
  // '1': LPC/eSPI
  //
  if (DmiGcsBbs == 0) {
    BaseAddr = MmPciBase (
                 DEFAULT_PCI_BUS_NUMBER_PCH,
                 PCI_DEVICE_NUMBER_PCH_SPI,
                 PCI_FUNCTION_NUMBER_PCH_SPI
                 );
    //
    // if GCS.BBS is 0 (SPI), program SPI offset D8h to BiosDecodeEnable.
    //
    MmioWrite16 (BaseAddr + R_PCH_SPI_BDE, BiosDecodeEnable);
  } else {
    BaseAddr = MmPciBase (
                 DEFAULT_PCI_BUS_NUMBER_PCH,
                 PCI_DEVICE_NUMBER_PCH_LPC,
                 PCI_FUNCTION_NUMBER_PCH_LPC
                 );
    //
    // if GCS.BBS is 1 (LPC/eSPi), program LPC offset D8h to BiosDecodeEnable.
    //
    MmioWrite16 (BaseAddr + R_PCH_LPC_BDE, BiosDecodeEnable);
  }

  //
  // program LPC BIOS Decode Enable, PCR[DMI] + 2744h to the same value programmed in LPC or SPI Offset D8h.
  //
  PchPcrWrite16 (PID_DMI, R_PCH_PCR_DMI_LPCBDE, BiosDecodeEnable);
  return EFI_SUCCESS;
}

/**
  Set PCH LPC/eSPI IO decode ranges.
  Program LPC/eSPI I/O Decode Ranges, PCR[DMI] + 2770h[15:0] to the same value programmed in LPC/eSPI PCI offset 80h.
  Please check EDS for detail of LPC/eSPI IO decode ranges bit definition.
  Bit  12: FDD range
  Bit 9:8: LPT range
  Bit 6:4: ComB range
  Bit 2:0: ComA range

  @param[in] LpcIoDecodeRanges          LPC/eSPI IO decode ranges bit settings.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_UNSUPPORTED               DMIC.SRL is set.
**/
EFI_STATUS
EFIAPI
PchLpcIoDecodeRangesSet (
  IN  UINT16                            LpcIoDecodeRanges
  )
{
  UINTN                                 LpcBaseAddr;
  UINT32                                Dmic;

  //
  // Note: Inside this function, don't use debug print since it's could used before debug print ready.
  //

  LpcBaseAddr  = MmPciBase (
                   DEFAULT_PCI_BUS_NUMBER_PCH,
                   PCI_DEVICE_NUMBER_PCH_LPC,
                   PCI_FUNCTION_NUMBER_PCH_LPC
                   );
  //
  // check if setting is identical
  //
  if (LpcIoDecodeRanges == MmioRead16 (LpcBaseAddr + R_PCH_LPC_IOD)) {
    return EFI_SUCCESS;
  }

  //
  // This cycle decoding is only allowed to set when DMIC.SRL is 0.
  //
  PchPcrRead32 (PID_DMI, R_PCH_PCR_DMI_DMIC, &Dmic);
  if ((Dmic & B_PCH_PCR_DMI_DMIC_SRL) != 0) {
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }

  //
  // program LPC/eSPI PCI offset 80h.
  //
  MmioWrite16 (LpcBaseAddr + R_PCH_LPC_IOD, LpcIoDecodeRanges);

  //
  // program LPC I/O Decode Ranges, PCR[DMI] + 2770h[15:0] to the same value programmed in LPC/eSPI PCI offset 80h.
  //
  PchPcrWrite16 (PID_DMI, R_PCH_PCR_DMI_LPCIOD, LpcIoDecodeRanges);
  return EFI_SUCCESS;
}

/**
  Set PCH LPC/eSPI IO enable decoding.
  Setup LPC/eSPI I/O Enables, PCR[DMI] + 2774h[15:0] to the same value program in LPC/eSPI PCI offset 82h.
  Note: Bit[15:10] of the source decode register is Read-Only. The IO range indicated by the Enables field
  in LPC/eSPI PCI offset 82h[13:10] is always forwarded by DMI to subtractive agent for handling.
  Please check EDS for detail of Lpc/eSPI IO decode ranges bit definition.

  @param[in] LpcIoEnableDecoding        LPC/eSPI IO enable decoding bit settings.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_UNSUPPORTED               DMIC.SRL is set.
**/
EFI_STATUS
EFIAPI
PchLpcIoEnableDecodingSet (
  IN  UINT16                            LpcIoEnableDecoding
  )
{
  UINTN                                 LpcBaseAddr;
  UINT32                                Dmic;

  //
  // Note: Inside this function, don't use debug print since it's could used before debug print ready.
  //

  LpcBaseAddr  = MmPciBase (
                   DEFAULT_PCI_BUS_NUMBER_PCH,
                   PCI_DEVICE_NUMBER_PCH_LPC,
                   PCI_FUNCTION_NUMBER_PCH_LPC
                   );

  if (LpcIoEnableDecoding == MmioRead16 (LpcBaseAddr + R_PCH_LPC_IOE)) {
    return EFI_SUCCESS;
  }

  //
  // This cycle decoding is only allowed to set when DMIC.SRL is 0.
  //
  PchPcrRead32 (PID_DMI, R_PCH_PCR_DMI_DMIC, &Dmic);
  if ((Dmic & B_PCH_PCR_DMI_DMIC_SRL) != 0) {
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }

  //
  // program LPC/eSPI PCI offset 82h.
  //
  MmioWrite16 (LpcBaseAddr + R_PCH_LPC_IOE, LpcIoEnableDecoding);

  //
  // program LPC I/O Decode Ranges, PCR[DMI] + 2774h[15:0] to the same value programmed in LPC/eSPI PCI offset 82h.
  //
  PchPcrWrite16 (PID_DMI, R_PCH_PCR_DMI_LPCIOE, LpcIoEnableDecoding);
  return EFI_SUCCESS;
}


//
// Root Port Destination ID tables
//
// PCH-LP RPR destination ID table
//
UINT16 mPchLpRprDidTable[] = {
  0x2188, // Dest ID of RP1
  0x2189, // Dest ID of RP2
  0x218A, // Dest ID of RP3
  0x218B, // Dest ID of RP4
  0x2198, // Dest ID of RP5
  0x2199, // Dest ID of RP6
  0x219A, // Dest ID of RP7
  0x219B, // Dest ID of RP8
  0x21A8, // Dest ID of RP9
  0x21A9, // Dest ID of RP10
  0x21AA, // Dest ID of RP11
  0x21AB  // Dest ID of RP12
};

//
// PCH-H RPR destination ID table
//
UINT16 mPchHRprDidTable[] = {
  0x2180, // Dest ID of RP1
  0x2181, // Dest ID of RP2
  0x2182, // Dest ID of RP3
  0x2183, // Dest ID of RP4
  0x2188, // Dest ID of RP5
  0x2189, // Dest ID of RP6
  0x218A, // Dest ID of RP7
  0x218B, // Dest ID of RP8
  0x2198, // Dest ID of RP9
  0x2199, // Dest ID of RP10
  0x219A, // Dest ID of RP11
  0x219B, // Dest ID of RP12
  0x21A8, // Dest ID of RP13
  0x21A9, // Dest ID of RP14
  0x21AA, // Dest ID of RP15
  0x21AB, // Dest ID of RP16
  0x21B8, // Dest ID of RP17
  0x21B9, // Dest ID of RP18
  0x21BA, // Dest ID of RP19
  0x21BB, // Dest ID of RP20
  0x21C0, // Dest ID of RP21 (KBL-PCH-H only)
  0x21C1, // Dest ID of RP22 (KBL-PCH-H only)
  0x21C2, // Dest ID of RP23 (KBL-PCH-H only)
  0x21C3  // Dest ID of RP24 (KBL-PCH-H only)
};

/**
  Set PCH IO port 80h cycle decoding to PCIE root port.
  System BIOS is likely to do this very soon after reset before PCI bus enumeration.
  This cycle decoding is allowed to set when DMIC.SRL is 0.
  Programming steps:
  1. Program "RPR Destination ID", PCR[DMI] + 274Ch[31:16] to the Dest ID of RP.
  2. Program "Reserved Page Route", PCR[DMI] + 274Ch[11] to '1'. Use byte write on GCS+1 and leave the BILD bit which is RWO.

  @param[in] RpNumber                PCIE root port physical number.

  @retval EFI_SUCCESS                   Successfully completed.
**/
EFI_STATUS
EFIAPI
PchIoPort80DecodeSet (
  IN  UINTN                             RpNumber
  )
{
  UINT32                                Dmic;
  UINT16                                *PchRprDidTable;

  PchPcrRead32 (PID_DMI, R_PCH_PCR_DMI_DMIC, &Dmic);
  if ((Dmic & B_PCH_PCR_DMI_DMIC_SRL) != 0) {
    DEBUG ((DEBUG_ERROR, "PchIoPort80DecodeSet Error. DMIC.SRL is set.\n"));
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }

  ///
  /// IO port 80h is typically used by decoder/LED hardware for debug purposes.
  /// By default PCH will forward IO port 80h cycles to LPC bus. The Reserved Page Route (RPR) bit
  /// of General Control and Status register, located at PCR[DMI] + 274Ch[11] , allows software to
  /// re-direct IO port 80h cycles to PCIe bus so that a target (for example, a debug card) on
  /// PCIe bus can receive and claim these cycles.
  /// The "RPR Destination ID", PCR[DMI] + 274Ch[31:16] need to be set accordingly to point
  /// to the root port that decode this range. Reading from Port 80h may not return valid values
  /// if the POST-card itself do not shadow the writes. Unlike LPC, PCIe does not shadow the Port 80 writes.
  ///

  if (GetPchSeries () == PchLp) {
    if (RpNumber >= sizeof (mPchLpRprDidTable) / sizeof (mPchLpRprDidTable[0]) ) {
      return EFI_UNSUPPORTED;
    }
    PchRprDidTable = mPchLpRprDidTable;
  } else {
    if (RpNumber >= sizeof (mPchHRprDidTable) / sizeof (mPchHRprDidTable[0]) ) {
      return EFI_UNSUPPORTED;
    }
    PchRprDidTable = mPchHRprDidTable;
  }

  //
  // Program "RPR Destination ID", PCR[DMI] + 274Ch[31:16] to the Dest ID of RP.
  //
  PchPcrWrite16 (PID_DMI, R_PCH_PCR_DMI_GCS + 2, PchRprDidTable[RpNumber]);
  //
  // Program "Reserved Page Route", PCR[DMI] + 274Ch[11] to '1'.
  // Use byte write on GCS+1 and leave the BILD bit which is RWO.
  //
  PchPcrAndThenOr8 (PID_DMI, R_PCH_PCR_DMI_GCS + 1, 0xFF, (B_PCH_PCR_DMI_RPR >> 8));

  return EFI_SUCCESS;
}

/**
  Get IO APIC regsiters base address.
  It returns IO APIC INDEX, DATA, and EOI regsiter address once the parameter is not NULL.
  This function will be unavailable after P2SB is hidden by PSF.

  @param[out] IoApicIndex               Buffer of IO APIC INDEX regsiter address
  @param[out] IoApicData                Buffer of IO APIC DATA regsiter address

  @retval EFI_SUCCESS                   Successfully completed.
**/
EFI_STATUS
PchIoApicBaseGet (
  OPTIONAL OUT UINT32                   *IoApicIndex,
  OPTIONAL OUT UINT32                   *IoApicData
  )
{
  EFI_STATUS                            Status;
  UINT16                                RegIoac;
  UINT32                                RangeSelect;

  Status = PchP2sbCfgGet16 (R_PCH_P2SB_IOAC, &RegIoac);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  RangeSelect = (RegIoac & B_PCH_P2SB_IOAC_ASEL) << N_PCH_IO_APIC_ASEL;

  if (IoApicIndex != NULL) {
    *IoApicIndex = R_PCH_IO_APIC_INDEX + RangeSelect;
  }
  if (IoApicData != NULL) {
    *IoApicData  = R_PCH_IO_APIC_DATA + RangeSelect;
  }

  return EFI_SUCCESS;
}

/**
  Get HPET base address.
  This function will be unavailable after P2SB is hidden by PSF.

  @param[out] HpetBase                  Buffer of HPET base address

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid offset passed.
**/
EFI_STATUS
PchHpetBaseGet (
  OUT UINT32                            *HpetBase
  )
{
  EFI_STATUS                            Status;
  UINT8                                 RegHptc;

  if (HpetBase == NULL) {
    DEBUG ((DEBUG_ERROR, "PchHpetBaseGet Error. Invalid pointer.\n"));
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  Status = PchP2sbCfgGet8 (R_PCH_P2SB_HPTC, &RegHptc);

  switch (RegHptc & B_PCH_P2SB_HPTC_AS) {
    case 0:
      *HpetBase = V_PCH_HPET_BASE0;
      break;
    case 1:
      *HpetBase = V_PCH_HPET_BASE1;
      break;
    case 2:
      *HpetBase = V_PCH_HPET_BASE2;
      break;
    case 3:
      *HpetBase = V_PCH_HPET_BASE3;
      break;
    default:
      break;
  }

  return EFI_SUCCESS;
}

