/** @file
  PCH Serial IO Lib implementation.
  All function in this library is available for PEI, DXE, and SMM,
  But do not support UEFI RUNTIME environment call.

Copyright (c) 2017, 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/BaseLib.h>
#include <IndustryStandard/Pci30.h>
#include <PchAccess.h>
#include <Library/PchPcrLib.h>
#include <Library/PchInfoLib.h>
#include <Library/MmPciLib.h>
#include <Library/GpioNativeLib.h>
#include <Library/PchSerialIoLib.h>
#include <Library/PchPsfLib.h>
#include <Include/PcieRegs.h>

typedef struct {
  UINT32 Bar0;
  UINT32 Bar1;
} SERIAL_IO_CONTROLLER_DESCRIPTOR;

typedef struct {
  UINT8  DevNum;
  UINT8  FuncNum;
} SERIAL_IO_BDF_NUMBERS;

typedef struct {
  UINT16 PciDevIdPchLp;
  UINT16 PciDevIdSklPchH;
  UINT16 PciDevIdKblPchH;
  CHAR8  AcpiHid[SERIALIO_HID_LENGTH];
} SERIAL_IO_ID;

GLOBAL_REMOVE_IF_UNREFERENCED SERIAL_IO_ID mSerialIoId [PCH_SERIALIO_MAX_CONTROLLERS] =
{
  {V_PCH_LP_SERIAL_IO_I2C0_DEVICE_ID,  V_SKL_PCH_H_SERIAL_IO_I2C0_DEVICE_ID,  V_KBL_PCH_H_SERIAL_IO_I2C0_DEVICE_ID,  "INT3442"},
  {V_PCH_LP_SERIAL_IO_I2C1_DEVICE_ID,  V_SKL_PCH_H_SERIAL_IO_I2C1_DEVICE_ID,  V_KBL_PCH_H_SERIAL_IO_I2C1_DEVICE_ID,  "INT3443"},
  {V_PCH_LP_SERIAL_IO_I2C2_DEVICE_ID,  V_SKL_PCH_H_SERIAL_IO_I2C2_DEVICE_ID,  V_KBL_PCH_H_SERIAL_IO_I2C2_DEVICE_ID,  "INT3444"},
  {V_PCH_LP_SERIAL_IO_I2C3_DEVICE_ID,  V_SKL_PCH_H_SERIAL_IO_I2C3_DEVICE_ID,  V_KBL_PCH_H_SERIAL_IO_I2C3_DEVICE_ID,  "INT3445"},
  {V_PCH_LP_SERIAL_IO_I2C4_DEVICE_ID,  0                                   ,  0                                   ,  "INT3446"},
  {V_PCH_LP_SERIAL_IO_I2C5_DEVICE_ID,  0                                   ,  0                                   ,  "INT3447"},
  {V_PCH_LP_SERIAL_IO_SPI0_DEVICE_ID,  V_SKL_PCH_H_SERIAL_IO_SPI0_DEVICE_ID,  V_KBL_PCH_H_SERIAL_IO_SPI0_DEVICE_ID,  "INT3440"},
  {V_PCH_LP_SERIAL_IO_SPI1_DEVICE_ID,  V_SKL_PCH_H_SERIAL_IO_SPI1_DEVICE_ID,  V_KBL_PCH_H_SERIAL_IO_SPI1_DEVICE_ID,  "INT3441"},
  {V_PCH_LP_SERIAL_IO_UART0_DEVICE_ID, V_SKL_PCH_H_SERIAL_IO_UART0_DEVICE_ID, V_KBL_PCH_H_SERIAL_IO_UART0_DEVICE_ID, "INT3448"},
  {V_PCH_LP_SERIAL_IO_UART1_DEVICE_ID, V_SKL_PCH_H_SERIAL_IO_UART1_DEVICE_ID, V_KBL_PCH_H_SERIAL_IO_UART1_DEVICE_ID, "INT3449"},
  {V_PCH_LP_SERIAL_IO_UART2_DEVICE_ID, V_SKL_PCH_H_SERIAL_IO_UART2_DEVICE_ID, V_KBL_PCH_H_SERIAL_IO_UART2_DEVICE_ID, "INT344A"}
};

GLOBAL_REMOVE_IF_UNREFERENCED SERIAL_IO_BDF_NUMBERS mSerialIoBdf [PCH_SERIALIO_MAX_CONTROLLERS] =
{
  {PCI_DEVICE_NUMBER_PCH_SERIAL_IO_I2C0,  PCI_FUNCTION_NUMBER_PCH_SERIAL_IO_I2C0},
  {PCI_DEVICE_NUMBER_PCH_SERIAL_IO_I2C1,  PCI_FUNCTION_NUMBER_PCH_SERIAL_IO_I2C1},
  {PCI_DEVICE_NUMBER_PCH_SERIAL_IO_I2C2,  PCI_FUNCTION_NUMBER_PCH_SERIAL_IO_I2C2},
  {PCI_DEVICE_NUMBER_PCH_SERIAL_IO_I2C3,  PCI_FUNCTION_NUMBER_PCH_SERIAL_IO_I2C3},
  {PCI_DEVICE_NUMBER_PCH_SERIAL_IO_I2C4,  PCI_FUNCTION_NUMBER_PCH_SERIAL_IO_I2C4},
  {PCI_DEVICE_NUMBER_PCH_SERIAL_IO_I2C5,  PCI_FUNCTION_NUMBER_PCH_SERIAL_IO_I2C5},
  {PCI_DEVICE_NUMBER_PCH_SERIAL_IO_SPI0,  PCI_FUNCTION_NUMBER_PCH_SERIAL_IO_SPI0},
  {PCI_DEVICE_NUMBER_PCH_SERIAL_IO_SPI1,  PCI_FUNCTION_NUMBER_PCH_SERIAL_IO_SPI1},
  {PCI_DEVICE_NUMBER_PCH_SERIAL_IO_UART0, PCI_FUNCTION_NUMBER_PCH_SERIAL_IO_UART0},
  {PCI_DEVICE_NUMBER_PCH_SERIAL_IO_UART1, PCI_FUNCTION_NUMBER_PCH_SERIAL_IO_UART1},
  {PCI_DEVICE_NUMBER_PCH_SERIAL_IO_UART2, PCI_FUNCTION_NUMBER_PCH_SERIAL_IO_UART2}
};

GLOBAL_REMOVE_IF_UNREFERENCED SERIAL_IO_CONTROLLER_DESCRIPTOR mSerialIoAcpiAddress [PCH_SERIALIO_MAX_CONTROLLERS] =
{
  {PCH_SERIAL_IO_BASE_ADDRESS + 0x0000,  PCH_SERIAL_IO_BASE_ADDRESS + 0x1000},
  {PCH_SERIAL_IO_BASE_ADDRESS + 0x2000,  PCH_SERIAL_IO_BASE_ADDRESS + 0x3000},
  {PCH_SERIAL_IO_BASE_ADDRESS + 0x4000,  PCH_SERIAL_IO_BASE_ADDRESS + 0x5000},
  {PCH_SERIAL_IO_BASE_ADDRESS + 0x6000,  PCH_SERIAL_IO_BASE_ADDRESS + 0x7000},
  {PCH_SERIAL_IO_BASE_ADDRESS + 0x8000,  PCH_SERIAL_IO_BASE_ADDRESS + 0x9000},
  {PCH_SERIAL_IO_BASE_ADDRESS + 0xA000,  PCH_SERIAL_IO_BASE_ADDRESS + 0xB000},
  {PCH_SERIAL_IO_BASE_ADDRESS + 0xC000,  PCH_SERIAL_IO_BASE_ADDRESS + 0xD000},
  {PCH_SERIAL_IO_BASE_ADDRESS + 0xE000,  PCH_SERIAL_IO_BASE_ADDRESS + 0xF000},
  {PCH_SERIAL_IO_BASE_ADDRESS + 0x10000, PCH_SERIAL_IO_BASE_ADDRESS + 0x11000},
  {PCH_SERIAL_IO_BASE_ADDRESS + 0x12000, PCH_SERIAL_IO_BASE_ADDRESS + 0x13000},
  {PCH_SERIAL_IO_BASE_ADDRESS + 0x14000, PCH_SERIAL_IO_BASE_ADDRESS + 0x15000}
};

GLOBAL_REMOVE_IF_UNREFERENCED UINT16 mPchLpSerialIoPciCfgCtrAddr [PCH_SERIALIO_MAX_CONTROLLERS] =
{
  R_PCH_PCR_SERIAL_IO_PCICFGCTRL1,
  R_PCH_PCR_SERIAL_IO_PCICFGCTRL2,
  R_PCH_PCR_SERIAL_IO_PCICFGCTRL3,
  R_PCH_PCR_SERIAL_IO_PCICFGCTRL4,
  R_PCH_PCR_SERIAL_IO_PCICFGCTRL5,
  R_PCH_PCR_SERIAL_IO_PCICFGCTRL6,
  R_PCH_PCR_SERIAL_IO_PCICFGCTRL13,
  R_PCH_PCR_SERIAL_IO_PCICFGCTRL14,
  R_PCH_PCR_SERIAL_IO_PCICFGCTRL9,
  R_PCH_PCR_SERIAL_IO_PCICFGCTRL10,
  R_PCH_PCR_SERIAL_IO_PCICFGCTRL11
};

GLOBAL_REMOVE_IF_UNREFERENCED UINT16 mPchHSerialIoPciCfgCtrAddr[PCH_SERIALIO_MAX_CONTROLLERS] =
{
  R_PCH_PCR_SERIAL_IO_PCICFGCTRL1,
  R_PCH_PCR_SERIAL_IO_PCICFGCTRL2,
  R_PCH_PCR_SERIAL_IO_PCICFGCTRL3,
  R_PCH_PCR_SERIAL_IO_PCICFGCTRL4,
  0,
  0,
  R_PCH_PCR_SERIAL_IO_PCICFGCTRL13,
  R_PCH_PCR_SERIAL_IO_PCICFGCTRL14,
  R_PCH_PCR_SERIAL_IO_PCICFGCTRL9,
  R_PCH_PCR_SERIAL_IO_PCICFGCTRL10,
  R_PCH_PCR_SERIAL_IO_PCICFGCTRL11
};


/**
  Returns index of the last i2c controller

  @param[in] Number  Number of SerialIo controller

  @retval            Index of I2C controller
**/
PCH_SERIAL_IO_CONTROLLER
GetMaxI2cNumber (
  VOID
  )
{
  if (GetPchSeries () == PchH) {
    return PchSerialIoIndexI2C3;
  } else {
    return PchSerialIoIndexI2C5;
  }
}

/**
  Returns string with AcpiHid assigned to selected SerialIo controller

  @param[in] Number  Number of SerialIo controller

  @retval            pointer to 8-byte string
**/
CHAR8*
GetSerialIoAcpiHID (
  IN PCH_SERIAL_IO_CONTROLLER Number
  )
{
  return mSerialIoId[Number].AcpiHid;
}

/**
  Checks if Device with given PciDeviceId is one of SerialIo controllers
  If yes, its number is returned through Number parameter, otherwise Number is not updated

  @param[in]  PciDevId  Device ID
  @param[out] Number    Number of SerialIo controller

  @retval TRUE          Yes it is a SerialIo controller
  @retval FALSE         No it isn't a SerialIo controller
**/
BOOLEAN
IsSerialIoPciDevId (
  IN  UINT16                    PciDevId,
  OUT PCH_SERIAL_IO_CONTROLLER  *Number
  )
{
  PCH_SERIAL_IO_CONTROLLER Controller;
  PCH_SERIES               PchSeries;
  PCH_GENERATION           PchGen;

  PchSeries =  GetPchSeries ();
  PchGen =  GetPchGeneration ();
  for (Controller = 0; Controller < PCH_SERIALIO_MAX_CONTROLLERS; Controller++) {
    if (((PchSeries == PchLp) && (PciDevId == mSerialIoId[Controller].PciDevIdPchLp)) ||
        ((PchSeries == PchH) && (PchGen == SklPch) && (PciDevId == mSerialIoId[Controller].PciDevIdSklPchH)) ||
        ((PchSeries == PchH) && (PchGen == KblPch) && (PciDevId == mSerialIoId[Controller].PciDevIdKblPchH))) {
      *Number = Controller;
      return TRUE;
    }
  }
  return FALSE;
}

/**
  Checks if Device with given AcpiHid string is one of SerialIo controllers
  If yes, its number is returned through Number parameter, otherwise Number is not updated

  @param[in]  AcpiHid   String
  @param[out] Number    Number of SerialIo controller

  @retval TRUE          yes it is a SerialIo controller
  @retval FALSE         no it isn't a SerialIo controller
**/
BOOLEAN
IsSerialIoAcpiHid (
  IN CHAR8                      *AcpiHid,
  OUT PCH_SERIAL_IO_CONTROLLER  *Number
  )
{
  PCH_SERIAL_IO_CONTROLLER Controller;
  for (Controller = 0; Controller < PCH_SERIALIO_MAX_CONTROLLERS; Controller++) {
    if (!AsciiStrCmp ((const CHAR8 *) AcpiHid, mSerialIoId[Controller].AcpiHid)) {
      *Number = Controller;
      return TRUE;
    }
  }
  return FALSE;
}

/**
  Finds PCI Device Number of SerialIo devices.

  @param[in] SerialIoNumber             0=I2C0, ..., 11=UART2

  @retval                               SerialIo device number
**/
UINT8
GetSerialIoDeviceNumber (
  IN PCH_SERIAL_IO_CONTROLLER  SerialIoNumber
  )
{
  return mSerialIoBdf[SerialIoNumber].DevNum;
}

/**
  Finds PCI Function Number of SerialIo devices.

  @param[in] SerialIoNumber             0=I2C0, ..., 11=UART2

  @retval                               SerialIo funciton number
**/
UINT8
GetSerialIoFunctionNumber (
  IN PCH_SERIAL_IO_CONTROLLER  SerialIoNumber
  )
{
  return mSerialIoBdf[SerialIoNumber].FuncNum;
}

/**
  Finds BAR values of SerialIo devices.
  SerialIo devices can be configured to not appear on PCI so traditional method of reading BAR might not work.
  If the SerialIo device is in PCI mode, a request for BAR1 will return its PCI CFG space instead

  @param[in] SerialIoDevice             0=I2C0, ..., 11=UART2
  @param[in] BarNumber                  0=BAR0, 1=BAR1

  @retval                               SerialIo Bar value
**/
UINTN
FindSerialIoBar (
  IN PCH_SERIAL_IO_CONTROLLER           SerialIoDevice,
  IN UINT8                              BarNumber
  )
{
  UINT64  Bar;
  UINTN   PcieBase;
  UINT32  VenId;

  PcieBase = MmPciBase (DEFAULT_PCI_BUS_NUMBER_PCH, GetSerialIoDeviceNumber (SerialIoDevice), GetSerialIoFunctionNumber (SerialIoDevice));
  VenId = MmioRead32 (PcieBase + PCI_VENDOR_ID_OFFSET) & 0xFFFF;
  if (VenId == V_PCH_INTEL_VENDOR_ID) {
    if (BarNumber == 1) {
      return PcieBase;
    }
    Bar = MmioRead32 (PcieBase + PCI_BASE_ADDRESSREG_OFFSET);
    //
    // For 64-Bit Memory Space BARs ((BAR[x] & 0xFFFFFFF0) + ((BAR[x+1] & 0xFFFFFFFF) << 32)
    //
    if ((Bar & B_PCI_BAR_MEMORY_TYPE_MASK) == B_PCI_BAR_MEMORY_TYPE_64) {
      Bar = (Bar & 0xFFFFF000) + LShiftU64 (MmioRead32 (PcieBase + PCI_BASE_ADDRESSREG_OFFSET + 4) & 0xFFFFFFFF, 32);
      return (UINTN) Bar;
    }
    return (UINTN) (Bar & 0xFFFFF000);
  }
  //
  //PCI mode failed? Try hardcoded addresses from ACPI
  //
  if (BarNumber == 0) {
    Bar = mSerialIoAcpiAddress[SerialIoDevice].Bar0;
  } else {
    Bar = mSerialIoAcpiAddress[SerialIoDevice].Bar1;
  }
  return (UINTN) Bar;
}

/**
  Configures Serial IO Controller

  @param[in] Controller          Serial Io controller selector
  @param[in] DeviceMode          Device mode selector
**/
VOID
ConfigureSerialIoController (
  IN PCH_SERIAL_IO_CONTROLLER Controller,
  IN PCH_SERIAL_IO_MODE       DeviceMode
  )
{
  UINTN     PciCfgBase;
  UINT32    Data32And;
  UINT32    Data32Or;
  UINT16    *SerialIoPciCfgCtrAddr;

  if (Controller >= PchSerialIoIndexMax) {
    return;
  }

  if (DeviceMode == PchSerialIoSkipInit) {
    return;
  }

  PciCfgBase = MmPciBase (0, GetSerialIoDeviceNumber (Controller), GetSerialIoFunctionNumber (Controller));
  //
  // Do not modify a device that has already been disabled/hidden
  //
  if (MmioRead16 (PciCfgBase + PCI_VENDOR_ID_OFFSET) != V_PCH_INTEL_VENDOR_ID) {
    return;
  }

  ///
  /// Step 0. set Bit 16,17,18.
  ///
  MmioOr32 (PciCfgBase + R_PCH_SERIAL_IO_D0I3MAXDEVPG, BIT18 | BIT17 | BIT16);

  //
  // special case for I2C4 and I2C5 on SPT-H
  //
  if (GetPchSeries () == PchH && (Controller  == PchSerialIoIndexI2C4 || Controller  == PchSerialIoIndexI2C5)) {
    return;
  }

  if (GetPchSeries () == PchLp) {
    SerialIoPciCfgCtrAddr = mPchLpSerialIoPciCfgCtrAddr;
  } else {
    SerialIoPciCfgCtrAddr = mPchHSerialIoPciCfgCtrAddr;
  }

  switch (DeviceMode) {
    case PchSerialIoDisabled:
      ///
      /// Step 1. Put device in D3
      /// Step 2. Function Disable in PSF
      ///
      MmioOr32 (PciCfgBase + R_PCH_SERIAL_IO_PME_CTRL_STS, BIT1 | BIT0);
      PsfDisableSerialIoDevice (Controller);
      break;

    case PchSerialIoAcpi:
    case PchSerialIoAcpiHidden:
    case PchSerialIoLegacyUart:
      ///
      /// reenable BAR1 in case it was disabled earlier
      ///
      PchPcrAndThenOr32 (PID_SERIALIO, SerialIoPciCfgCtrAddr[Controller],(UINT32) ~(B_PCH_PCR_SERIAL_IO_PCICFGCTRL_BAR1_DIS),0x0);
      PsfEnableSerialIoDeviceBar1 (Controller);
      ///
      /// Step 1. Assign BAR0
      /// Step 2. Assign BAR1
      ///
      MmioWrite32 (PciCfgBase + R_PCH_SERIAL_IO_BAR0_LOW,  mSerialIoAcpiAddress[Controller].Bar0);
      MmioWrite32 (PciCfgBase + R_PCH_SERIAL_IO_BAR0_HIGH, 0x0);
      MmioWrite32 (PciCfgBase + R_PCH_SERIAL_IO_BAR1_LOW,  mSerialIoAcpiAddress[Controller].Bar1);
      MmioWrite32 (PciCfgBase + R_PCH_SERIAL_IO_BAR1_HIGH, 0x0);
      ///
      /// Step 3. Set Memory space Enable
      ///
      MmioOr32 (PciCfgBase + PCI_COMMAND_OFFSET, EFI_PCI_COMMAND_MEMORY_SPACE | EFI_PCI_COMMAND_BUS_MASTER);
      ///
      /// Step 4. Disable device's PciCfg and enable ACPI interrupts
      ///
      PchPcrAndThenOr32 (PID_SERIALIO, SerialIoPciCfgCtrAddr[Controller], 0xFFFFFFFF, (B_PCH_PCR_SERIAL_IO_PCICFGCTRL_PCI_CFG_DIS | B_PCH_PCR_SERIAL_IO_PCICFGCTRL_ACPI_INTR_EN));
      ///
      /// Step 5. Disable device's PciCfg in PSF
      ///
      PsfHideSerialIoDevice (Controller);
      ///
      /// get controller out of reset
      ///
      MmioOr32 (mSerialIoAcpiAddress[Controller].Bar0 + R_PCH_SERIAL_IO_PPR_RESETS,
        B_PCH_SERIAL_IO_PPR_RESETS_FUNC | B_PCH_SERIAL_IO_PPR_RESETS_APB | B_PCH_SERIAL_IO_PPR_RESETS_IDMA);
      break;
    case PchSerialIoPci:
      ///
      /// reenable PciCfg in case it was disabled earlier
      ///
      PchPcrAndThenOr32 (PID_SERIALIO, SerialIoPciCfgCtrAddr[Controller], (UINT32) ~(B_PCH_PCR_SERIAL_IO_PCICFGCTRL_PCI_CFG_DIS | B_PCH_PCR_SERIAL_IO_PCICFGCTRL_ACPI_INTR_EN),0x0);
      PsfRevealSerialIoDevice (Controller);
      ///
      /// Disable Bar1
      /// Disable Bar1 in PSF
      ///
      PchPcrAndThenOr32 (PID_SERIALIO, SerialIoPciCfgCtrAddr[Controller], 0xFFFFFFFF, B_PCH_PCR_SERIAL_IO_PCICFGCTRL_BAR1_DIS);
      PsfDisableSerialIoDeviceBar1 (Controller);
      //
      // Assign BAR0 and Set Memory space Enable
      //
      MmioWrite32 (PciCfgBase + R_PCH_SERIAL_IO_BAR0_LOW,  mSerialIoAcpiAddress[Controller].Bar0);
      MmioWrite32 (PciCfgBase + R_PCH_SERIAL_IO_BAR0_HIGH, 0x0);
      MmioOr32    (PciCfgBase + PCI_COMMAND_OFFSET, EFI_PCI_COMMAND_MEMORY_SPACE | EFI_PCI_COMMAND_BUS_MASTER);
      ///
      /// get controller out of reset
      ///
      MmioOr32 (mSerialIoAcpiAddress[Controller].Bar0 + R_PCH_SERIAL_IO_PPR_RESETS,
        B_PCH_SERIAL_IO_PPR_RESETS_FUNC | B_PCH_SERIAL_IO_PPR_RESETS_APB | B_PCH_SERIAL_IO_PPR_RESETS_IDMA);
      break;
    default:
      return;
  }

  ///
  /// Step X. Program clock dividers for UARTs
  /// Step Y. Enable Byte addressing for UARTs in legacy mode
  ///
  if (Controller >= PchSerialIoIndexUart0 && Controller <= PchSerialIoIndexUart2) {
    MmioWrite32 (mSerialIoAcpiAddress[Controller].Bar0 + R_PCH_SERIAL_IO_PPR_CLK,
      (B_PCH_SERIAL_IO_PPR_CLK_UPDATE | (V_PCH_SERIAL_IO_PPR_CLK_N_DIV << 16) |
       (V_PCH_SERIAL_IO_PPR_CLK_M_DIV << 1) | B_PCH_SERIAL_IO_PPR_CLK_EN )
      );

    Data32And = (UINT32) (~(B_PCH_PCR_SERIAL_IO_GPPRVRW7_UART0_BYTE_ADDR_EN << (Controller - PchSerialIoIndexUart0)));
    Data32Or = 0x0;
    if (DeviceMode == PchSerialIoLegacyUart) {
      Data32Or = (B_PCH_PCR_SERIAL_IO_GPPRVRW7_UART0_BYTE_ADDR_EN << (Controller - PchSerialIoIndexUart0));
    }
    PchPcrAndThenOr32 (PID_SERIALIO, R_PCH_PCR_SERIAL_IO_GPPRVRW7,Data32And,Data32Or);
    //
    // Dummy read after setting any of GPPRVRW7.
    // Required for UART 16550 8-bit Legacy mode to become active
    //
    MmioRead32 (mSerialIoAcpiAddress[Controller].Bar0 + R_PCH_SERIAL_IO_PPR_CLK);
  }
  ///
  /// Step Z. Program I2C SDA hold registers
  ///
  if (Controller >= PchSerialIoIndexI2C0 && Controller <= GetMaxI2cNumber ()) {
    if (DeviceMode != PchSerialIoDisabled) {
      MmioOr32 (mSerialIoAcpiAddress[Controller].Bar0 + R_PCH_SERIAL_IO_I2C_SDA_HOLD, V_PCH_SERIAL_IO_I2C_SDA_HOLD_VALUE);
    }
  }

}

/**
  Initializes GPIO pins used by SerialIo I2C devices

  @param[in] Controller          Serial Io controller selector
  @param[in] DeviceMode          Device mode selector
  @param[in] I2cVoltage          Voltage selector
**/
VOID
SerialIoI2cGpioInit (
  IN PCH_SERIAL_IO_CONTROLLER Controller,
  IN PCH_SERIAL_IO_MODE       DeviceMode,
  IN UINT32                   I2cVoltage
  )
{
  if ((DeviceMode == PchSerialIoDisabled) || (DeviceMode == PchSerialIoSkipInit)) {
    return;
  }
  GpioSetSerialIoI2cPinsIntoNativeMode (Controller);
  if (I2cVoltage == PchSerialIoIs18V) {
    GpioSetSerialIoI2CPinsTolerance (Controller, TRUE);
  }
}

/**
  Initializes GPIO pins used by SerialIo SPI devices

  @param[in] Controller          Serial Io controller selector
  @param[in] DeviceMode          Device mode selector
  @param[in] SpiCsPolarity       Polarity selector
**/
VOID
SerialIoSpiGpioInit (
  IN PCH_SERIAL_IO_CONTROLLER Controller,
  IN PCH_SERIAL_IO_MODE       DeviceMode,
  IN UINT32                   SpiCsPolarity
  )
{
  if ((DeviceMode == PchSerialIoDisabled) || (DeviceMode == PchSerialIoSkipInit)) {
    return;
  }

  //
  // set Invert Frame Signal before enabling pins to ensure correct initial ChipSelect polarity
  //
  if (SpiCsPolarity == PchSerialIoCsActiveLow) {
    MmioAnd32 (mSerialIoAcpiAddress[Controller].Bar0 + R_PCH_SERIAL_IO_SSCR1, (UINT32) ~(B_PCH_SERIAL_IO_SSCR1_IFS));
    MmioOr32 (mSerialIoAcpiAddress[Controller].Bar0 + R_PCH_SERIAL_IO_SPI_CS_CONTROL, B_PCH_SERIAL_IO_SPI_CS_CONTROL_STATE);
  } else {
    MmioOr32 (mSerialIoAcpiAddress[Controller].Bar0 + R_PCH_SERIAL_IO_SSCR1, B_PCH_SERIAL_IO_SSCR1_IFS);
    MmioAnd32 (mSerialIoAcpiAddress[Controller].Bar0 + R_PCH_SERIAL_IO_SPI_CS_CONTROL, (UINT32) ~B_PCH_SERIAL_IO_SPI_CS_CONTROL_STATE);
  }

  MmioOr32 (mSerialIoAcpiAddress[Controller].Bar0 + R_PCH_SERIAL_IO_SPI_CS_CONTROL, B_PCH_SERIAL_IO_SPI_CS_CONTROL_MODE);
  GpioSetSerialIoSpiPinsIntoNativeMode (Controller-PchSerialIoIndexSpi0);
}

/**
  Initializes GPIO pins used by SerialIo devices

  @param[in] Controller          Serial Io controller selector
  @param[in] DeviceMode          Device mode selector
  @param[in] HardwareFlowControl Hardware flow control selector
**/
VOID
SerialIoUartGpioInit (
  IN PCH_SERIAL_IO_CONTROLLER Controller,
  IN PCH_SERIAL_IO_MODE       DeviceMode,
  IN BOOLEAN                  HardwareFlowControl
  )
{

  if ((DeviceMode == PchSerialIoDisabled) || (DeviceMode == PchSerialIoSkipInit)) {
    return;
  }
  GpioSetSerialIoUartPinsIntoNativeMode (Controller-PchSerialIoIndexUart0, HardwareFlowControl);
}


