/** @file
  This file contains routines for GPIO

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

**/
#include "GpioLibrary.h"

/**
  This procedure will check if GpioPad is owned by host.

  @param[in] GpioPad       GPIO pad

  @retval TRUE             GPIO pad is owned by host
  @retval FALSE            GPIO pad is not owned by host and should not be used with GPIO lib API
**/
BOOLEAN
GpioIsPadHostOwned (
  IN GPIO_PAD             GpioPad
  )
{
  UINT32               GroupIndex;
  UINT32               PadNumber;
  GPIO_PAD_OWN         PadOwnVal;

  //
  // Check if selected GPIO Pad is not owned by CSME/ISH
  // If GPIO is not owned by Host all access to PadCfg will be dropped
  //
  GpioGetPadOwnership (GpioPad, &PadOwnVal);
  if (PadOwnVal != GpioPadOwnHost) {
    GroupIndex = GpioGetGroupIndexFromGpioPad (GpioPad);
    PadNumber = GpioGetPadNumberFromGpioPad (GpioPad);
    DEBUG ((DEBUG_ERROR, "GPIO ERROR: Gpio pad not owned by host (Group=%d, Pad=%d)!\n", GroupIndex, PadNumber));
    return FALSE;
  }

  return TRUE;
}

/**
  This procedure will check if GpioPad argument is valid.
  Function will check below conditions:
   - GpioPad represents a pad for current PCH
   - GpioPad belongs to valid GpioGroup
   - GPIO PadNumber is not greater than number of pads for this group

  @param[in] GpioPad       GPIO pad

  @retval TRUE             GPIO pad is valid and can be used with GPIO lib API
  @retval FALSE            GPIO pad is invalid and cannot be used with GPIO lib API
**/
BOOLEAN
GpioIsPadValid (
  IN GPIO_PAD             GpioPad
  )
{
  GPIO_GROUP_INFO      *GpioGroupInfo;
  UINTN                GpioGroupInfoLength;
  UINT32               GroupIndex;
  UINT32               PadNumber;

  GroupIndex = GpioGetGroupIndexFromGpioPad (GpioPad);
  PadNumber = GpioGetPadNumberFromGpioPad (GpioPad);

  if (!GpioIsCorrectPadForThisChipset (GpioPad)) {
    DEBUG ((DEBUG_ERROR, "GPIO ERROR: Incorrect GpioPad used on this chipset (Group=%d, Pad=%d)!\n", GroupIndex, PadNumber));
    return FALSE;
  }

  GpioGroupInfo = GpioGetGroupInfoTable (&GpioGroupInfoLength);

  //
  // Check if group argument exceeds GPIO GROUP INFO array
  //
  if ((UINTN) GroupIndex >= GpioGroupInfoLength) {
    DEBUG ((DEBUG_ERROR, "GPIO ERROR: Group argument (%d) exceeds GPIO group range\n", GroupIndex));
    return FALSE;
  }

  if ((GroupIndex < GpioGetGroupIndexFromGroup (GpioGetLowestGroup ())) ||
      (GroupIndex > GpioGetGroupIndexFromGroup (GpioGetHighestGroup ()))) {
    DEBUG ((DEBUG_ERROR, "GPIO ERROR: Group argument (%d) is not within range of possible groups for this PCH\n", GroupIndex));
    return FALSE;
  }

  //
  // Check if legal pin number
  //
  if (PadNumber >= GpioGroupInfo[GroupIndex].PadPerGroup) {
    DEBUG ((DEBUG_ERROR, "GPIO ERROR: Pin number (%d) exceeds possible range for this group\n", PadNumber));
    return FALSE;
  }

  return TRUE;
}

/**
  This procedure will check if GpioGroup argument is correct and
  supplied DW reg number can be used for this group to access DW registers.
  Function will check below conditions:
   - Valid GpioGroup
   - DwNum is has valid value for this group

  @param[in] Group        GPIO group
  @param[in] DwNum        Register number for current group (parameter applicable in accessing whole register).
                          For group which has less then 32 pads per group DwNum must be 0.

  @retval TRUE             DW Reg number and GpioGroup is valid
  @retval FALSE            DW Reg number and GpioGroup is invalid
**/
STATIC
BOOLEAN
GpioIsGroupAndDwNumValid (
  IN GPIO_GROUP             Group,
  IN UINT32                 DwNum
  )
{
  UINT32               GroupIndex;
  GPIO_GROUP_INFO      *GpioGroupInfo;
  UINTN                GpioGroupInfoLength;

  GpioGroupInfo = GpioGetGroupInfoTable (&GpioGroupInfoLength);

  GroupIndex = GpioGetGroupIndexFromGroup (Group);

  //
  // Check if group argument exceeds GPIO GROUP INFO array
  //
  if ((UINTN) GroupIndex >= GpioGroupInfoLength) {
    DEBUG ((DEBUG_ERROR, "GPIO ERROR: Group argument (%d) exceeds GPIO group range\n", GroupIndex));
    return FALSE;
  }

  if ((GroupIndex < GpioGetGroupIndexFromGroup (GpioGetLowestGroup ())) ||
      (GroupIndex > GpioGetGroupIndexFromGroup (GpioGetHighestGroup ()))) {
    DEBUG ((DEBUG_ERROR, "GPIO ERROR: Group argument (%d) is not within range of possible groups for this PCH\n", GroupIndex));
    return FALSE;
  }

  //
  // Check if DwNum argument does not exceed number of DWord registers
  // resulting from available pads for certain group
  //
  if (DwNum > GPIO_GET_DW_NUM (GpioGroupInfo[GroupIndex].PadPerGroup - 1)){
    return FALSE;
  }

  return TRUE;
}

/**
  This procedure will write or read GPIO Pad Configuration register

  @param[in] GpioPad              GPIO pad
  @param[in] DwReg                Choose PADCFG register: 0:DW0, 1:DW1
  @param[out] PadCfgRegValue      Read data

  @retval none
**/
VOID
GpioReadPadCfgReg (
  IN GPIO_PAD             GpioPad,
  IN UINT8                DwReg,
  OUT UINT32              *PadCfgRegValue
  )
{
  UINT32               PadCfgReg;
  GPIO_GROUP_INFO      *GpioGroupInfo;
  UINTN                GpioGroupInfoLength;
  UINT32               GroupIndex;
  UINT32               PadNumber;

  GroupIndex = GpioGetGroupIndexFromGpioPad (GpioPad);
  PadNumber = GpioGetPadNumberFromGpioPad (GpioPad);

  GpioGroupInfo = GpioGetGroupInfoTable (&GpioGroupInfoLength);

  //
  // Create Pad Configuration register offset
  //
  PadCfgReg = GpioGroupInfo[GroupIndex].PadCfgOffset + PAD_CFG_SIZE * PadNumber + 0x4 * DwReg;

  *PadCfgRegValue = MmioRead32 (PCH_PCR_ADDRESS (GpioGroupInfo[GroupIndex].Community, PadCfgReg));
}

/**
  This procedure will write or read GPIO Pad Configuration register

  @param[in] GpioPad              GPIO pad
  @param[in] DwReg                Choose PADCFG register: 0:DW0, 1:DW1
  @param[in] PadCfgAndMask        Mask to be AND'ed with PADCFG reg value
  @param[in] PadCfgOrMask         Mask to be OR'ed with PADCFG reg value

  @retval none
**/
VOID
GpioWritePadCfgReg (
  IN GPIO_PAD             GpioPad,
  IN UINT8                DwReg,
  IN UINT32               PadCfgAndMask,
  IN UINT32               PadCfgOrMask
  )
{
  UINT32               PadCfgReg;
  GPIO_GROUP_INFO      *GpioGroupInfo;
  UINTN                GpioGroupInfoLength;
  UINT32               GroupIndex;
  UINT32               PadNumber;

  GroupIndex = GpioGetGroupIndexFromGpioPad (GpioPad);
  PadNumber = GpioGetPadNumberFromGpioPad (GpioPad);

  GpioGroupInfo = GpioGetGroupInfoTable (&GpioGroupInfoLength);

  //
  // Create Pad Configuration register offset
  //
  PadCfgReg = GpioGroupInfo[GroupIndex].PadCfgOffset + PAD_CFG_SIZE * PadNumber + 0x4 * DwReg;

  MmioAndThenOr32 (
    PCH_PCR_ADDRESS (GpioGroupInfo[GroupIndex].Community, PadCfgReg),
    PadCfgAndMask,
    PadCfgOrMask
    );
}

//
// Possible registers to be accessed using GpioReadReg()/GpioWriteReg() functions
//
typedef enum {
  GpioHostOwnershipRegister = 0,
  GpioGpeEnableRegister,
  GpioGpeStatusRegister,
  GpioSmiEnableRegister,
  GpioSmiStatusRegister,
  GpioNmiEnableRegister,
  GpioPadConfigLockRegister,
  GpioPadLockOutputRegister
} GPIO_REG;

/**
  This procedure will read GPIO register

  @param[in] RegType              GPIO register type
  @param[in] Group                GPIO group
  @param[in] DwNum                Register number for current group (parameter applicable in accessing whole register).
                                  For group which has less then 32 pads per group DwNum must be 0.
  @param[out] ReadVal             Read data

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_UNSUPPORTED         Feature is not supported for this group or pad
**/
STATIC
EFI_STATUS
GpioReadReg (
  IN GPIO_REG               RegType,
  IN GPIO_GROUP             Group,
  IN UINT32                 DwNum,
  OUT UINT32                *ReadVal
  )
{
  UINT32               RegOffset;
  UINT32               GroupIndex;
  GPIO_GROUP_INFO      *GpioGroupInfo;
  UINTN                GpioGroupInfoLength;

  RegOffset = NO_REGISTER_FOR_PROPERTY;
  GroupIndex = GpioGetGroupIndexFromGroup (Group);

  GpioGroupInfo = GpioGetGroupInfoTable (&GpioGroupInfoLength);

  switch (RegType) {
    case GpioHostOwnershipRegister:
      RegOffset = GpioGroupInfo[GroupIndex].HostOwnOffset;
      break;
    case GpioGpeEnableRegister:
      RegOffset = GpioGroupInfo[GroupIndex].GpiGpeEnOffset;
      break;
    case GpioGpeStatusRegister:
      RegOffset = GpioGroupInfo[GroupIndex].GpiGpeStsOffset;
      break;
    case GpioSmiEnableRegister:
      RegOffset = GpioGroupInfo[GroupIndex].SmiEnOffset;
      break;
    case GpioSmiStatusRegister:
      RegOffset = GpioGroupInfo[GroupIndex].SmiStsOffset;
      break;
    case GpioNmiEnableRegister:
      RegOffset = GpioGroupInfo[GroupIndex].NmiEnOffset;
      break;
    case GpioPadConfigLockRegister:
      RegOffset = GpioGroupInfo[GroupIndex].PadCfgLockOffset;
      break;
    case GpioPadLockOutputRegister:
      RegOffset = GpioGroupInfo[GroupIndex].PadCfgLockTxOffset;
      break;
    default:
      ASSERT (FALSE);
      break;
  }

  //
  // Check if selected register exists
  //
  if (RegOffset == NO_REGISTER_FOR_PROPERTY) {
    return EFI_UNSUPPORTED;
  }

  //
  // If there are more then 32 pads per group then certain
  // group information would be split into more then one DWord register.
  //
  if ((RegType == GpioPadConfigLockRegister) || (RegType == GpioPadLockOutputRegister)) {
    //
    // PadConfigLock and OutputLock registers when used for group containing more than 32 pads
    // are not placed in a continuous way, e.g:
    // 0x0 - PadConfigLock_DW0
    // 0x4 - OutputLock_DW0
    // 0x8 - PadConfigLock_DW1
    // 0xC - OutputLock_DW1
    //
    RegOffset += DwNum * 0x8;
  } else {
    RegOffset += DwNum * 0x4;
  }

  *ReadVal = MmioRead32 (PCH_PCR_ADDRESS (GpioGroupInfo[GroupIndex].Community, RegOffset));

  return EFI_SUCCESS;
}

/**
  This procedure will write GPIO register

  @param[in] RegType              GPIO register type
  @param[in] Group                GPIO group
  @param[in] DwNum                Register number for current group (parameter applicable in accessing whole register).
                                  For group which has less then 32 pads per group DwNum must be 0.
  @param[in] RegAndMask           Mask which will be AND'ed with register value
  @param[in] RegOrMask            Mask which will be OR'ed with register value

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_UNSUPPORTED         Feature is not supported for this group or pad
**/
STATIC
EFI_STATUS
GpioWriteReg (
  IN GPIO_REG               RegType,
  IN GPIO_GROUP             Group,
  IN UINT32                 DwNum,
  IN UINT32                 RegAndMask,
  IN UINT32                 RegOrMask
  )
{
  UINT32               RegOffset;
  UINT32               GroupIndex;
  GPIO_GROUP_INFO      *GpioGroupInfo;
  UINTN                GpioGroupInfoLength;

  RegOffset = NO_REGISTER_FOR_PROPERTY;
  GroupIndex = GpioGetGroupIndexFromGroup (Group);

  GpioGroupInfo = GpioGetGroupInfoTable (&GpioGroupInfoLength);

  switch (RegType) {
    case GpioHostOwnershipRegister:
      RegOffset = GpioGroupInfo[GroupIndex].HostOwnOffset;
      break;
    case GpioGpeEnableRegister:
      RegOffset = GpioGroupInfo[GroupIndex].GpiGpeEnOffset;
      break;
    case GpioGpeStatusRegister:
      RegOffset = GpioGroupInfo[GroupIndex].GpiGpeStsOffset;
      break;
    case GpioSmiEnableRegister:
      RegOffset = GpioGroupInfo[GroupIndex].SmiEnOffset;
      break;
    case GpioSmiStatusRegister:
      RegOffset = GpioGroupInfo[GroupIndex].SmiStsOffset;
      break;
    case GpioNmiEnableRegister:
      RegOffset = GpioGroupInfo[GroupIndex].NmiEnOffset;
      break;
    case GpioPadConfigLockRegister:
    case GpioPadLockOutputRegister:
      ASSERT (FALSE);
      break;
    default:
      ASSERT (FALSE);
      break;
  }

  //
  // Check if selected register exists
  //
  if (RegOffset == NO_REGISTER_FOR_PROPERTY) {
    return EFI_UNSUPPORTED;
  }

  //
  // If there are more then 32 pads per group then certain
  // group information would be split into more then one DWord register.
  //
  RegOffset += DwNum * 0x4;

  MmioAndThenOr32 (
    PCH_PCR_ADDRESS (GpioGroupInfo[GroupIndex].Community, RegOffset),
    RegAndMask,
    RegOrMask
    );

  return EFI_SUCCESS;
}

/**
  This procedure will write GPIO Lock/LockTx register using SBI.

  @param[in] RegType              GPIO register (Lock or LockTx)
  @param[in] Group                GPIO group number
  @param[in] DwNum                Register number for current group.
                                  For group which has less then 32 pads per group DwNum must be 0.
  @param[in] LockRegAndMask       Mask which will be AND'ed with Lock register value
  @param[in] LockRegOrMask        Mask which will be Or'ed with Lock register value

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_UNSUPPORTED         Feature is not supported for this group or pad
**/
STATIC
EFI_STATUS
GpioWriteLockReg (
  IN GPIO_REG                  RegType,
  IN GPIO_GROUP                Group,
  IN UINT32                    DwNum,
  IN UINT32                    LockRegAndMask,
  IN UINT32                    LockRegOrMask
  )
{
  UINT8                Response;
  EFI_STATUS           Status;
  GPIO_GROUP_INFO      *GpioGroupInfo;
  UINTN                GpioGroupInfoLength;
  UINT32               RegOffset;
  UINT32               OldPadCfgLockRegVal;
  UINT32               NewPadCfgLockRegVal;
  UINT32               GroupIndex;

  RegOffset = NO_REGISTER_FOR_PROPERTY;
  GroupIndex = GpioGetGroupIndexFromGroup (Group);

  GpioGroupInfo = GpioGetGroupInfoTable (&GpioGroupInfoLength);

  switch (RegType) {
    case GpioPadConfigLockRegister:
      RegOffset = GpioGroupInfo[GroupIndex].PadCfgLockOffset;
      break;
    case GpioPadLockOutputRegister:
      RegOffset = GpioGroupInfo[GroupIndex].PadCfgLockTxOffset;
      break;
    default:
      ASSERT (FALSE);
      break;
  }

  //
  // Check if selected register exists
  //
  if (RegOffset == NO_REGISTER_FOR_PROPERTY) {
    return EFI_UNSUPPORTED;
  }

  //
  // If there are more then 32 pads per group then certain
  // group information would be split into more then one DWord register.
  // PadConfigLock and OutputLock registers when used for group containing more than 32 pads
  // are not placed in a continuous way, e.g:
  // 0x0 - PadConfigLock_DW0
  // 0x4 - OutputLock_DW0
  // 0x8 - PadConfigLock_DW1
  // 0xC - OutputLock_DW1
  //
  RegOffset += DwNum *0x8;

  GpioGetPadCfgLockForGroupDw (Group, DwNum, &OldPadCfgLockRegVal);

  NewPadCfgLockRegVal = (OldPadCfgLockRegVal & LockRegAndMask) | LockRegOrMask;

  Status = PchSbiExecution (
             GpioGroupInfo[GroupIndex].Community,
             RegOffset,
             GpioLockUnlock,
             FALSE,
             &NewPadCfgLockRegVal,
             &Response
             );
  ASSERT_EFI_ERROR (Status);
  return Status;
}

/**
  This internal procedure will calculate GPIO_RESET_CONFIG value  (new type)
  based on provided PadRstCfg for a specific GPIO Pad.

  @param[in]  GpioPad               GPIO Pad
  @param[in]  PadRstCfg             GPIO PadRstCfg value

  @retval GpioResetConfig           GPIO Reset configuration (new type)
**/
GPIO_RESET_CONFIG
GpioResetConfigFromPadRstCfg (
  IN  GPIO_PAD           GpioPad,
  IN  UINT32             PadRstCfg
  )
{
  GPIO_GROUP           Group;

  static GPIO_RESET_CONFIG  GppPadRstCfgToGpioResetConfigMap[] = {
                              GpioResumeReset,
                              GpioHostDeepReset,
                              GpioPlatformReset};
  static GPIO_RESET_CONFIG  GpdPadRstCfgToGpioResetConfigMap[] = {
                              GpioDswReset,
                              GpioHostDeepReset,
                              GpioPlatformReset,
                              GpioResumeReset};

  Group = GpioGetGroupFromGpioPad (GpioPad);

  if ((Group == GPIO_SKL_LP_GROUP_GPD) || (Group == GPIO_SKL_H_GROUP_GPD)) {
    ASSERT (PadRstCfg < 4);
    if (PadRstCfg >= 4) return GpioResumeReset;
    return GpdPadRstCfgToGpioResetConfigMap[PadRstCfg];
  } else {
    ASSERT (PadRstCfg < 3);
    if (PadRstCfg >= 3) return GpioResumeReset;
    return GppPadRstCfgToGpioResetConfigMap[PadRstCfg];
  }
}

/**
  This internal procedure will calculate PadRstCfg register value based
  on provided GPIO Reset configuration for a certain pad.

  @param[in]  GpioPad                   GPIO Pad
  @param[in]  GpioResetConfig           GPIO Reset configuration
  @param[out] PadRstCfg                 GPIO PadRstCfg value

  @retval EFI_SUCCESS                   The function completed successfully
  @retval EFI_INVALID_PARAMETER         Invalid configuration
**/
EFI_STATUS
GpioPadRstCfgFromResetConfig (
  IN  GPIO_PAD           GpioPad,
  IN  GPIO_RESET_CONFIG  GpioResetConfig,
  OUT UINT32             *PadRstCfg
  )
{
  GPIO_GROUP           Group;

  Group = GpioGetGroupFromGpioPad (GpioPad);

  switch (GpioResetConfig) {
    case GpioResetDefault:
      *PadRstCfg = 0x0;
      break;
    case GpioResetPwrGood:  //old reset type
      *PadRstCfg = V_PCH_GPIO_RST_CONF_POW_GOOD;
      break;
    case GpioResetDeep:  //old reset type
    case GpioHostDeepReset:  //new reset type
      *PadRstCfg = V_PCH_GPIO_RST_CONF_DEEP_RST;
      break;
    case GpioResetNormal:  //old reset type
    case GpioPlatformReset:  //new reset type
      *PadRstCfg = V_PCH_GPIO_RST_CONF_GPIO_RST;
      break;
    case GpioResetResume:  //old reset type
      if ((Group == GPIO_SKL_LP_GROUP_GPD) || (Group == GPIO_SKL_H_GROUP_GPD)) {
        *PadRstCfg = V_PCH_GPIO_RST_CONF_RESUME_RST;
      } else {
        DEBUG ((DEBUG_ERROR, "GPIO ERROR: Only GPD group pads can use GpioResetResume (Group=%d, Pad=%d)!\n", GpioGetGroupIndexFromGpioPad (GpioPad), GpioGetPadNumberFromGpioPad (GpioPad)));
        return EFI_INVALID_PARAMETER;
      }
      break;
    case GpioResumeReset:  //new reset type
      if ((Group == GPIO_SKL_LP_GROUP_GPD) || (Group == GPIO_SKL_H_GROUP_GPD)) {
        *PadRstCfg = V_PCH_GPIO_RST_CONF_RESUME_RST;
      } else {
        *PadRstCfg = V_PCH_GPIO_RST_CONF_POW_GOOD;
      }
      break;
    case GpioDswReset:  //new reset type
      if ((Group == GPIO_SKL_LP_GROUP_GPD) || (Group == GPIO_SKL_H_GROUP_GPD)) {
        *PadRstCfg = V_PCH_GPIO_RST_CONF_POW_GOOD;
      } else {
        DEBUG ((DEBUG_ERROR, "GPIO ERROR: Only GPD group pads can use GpioDswReset (Group=%d, Pad=%d)!\n", GpioGetGroupIndexFromGpioPad (GpioPad), GpioGetPadNumberFromGpioPad (GpioPad)));
        return EFI_INVALID_PARAMETER;
      }
      break;
    default:
      ASSERT (FALSE);
      break;
  }

  return EFI_SUCCESS;
}

/**
  This internal procedure will get GPIO_CONFIG data from PADCFG registers value

  @param[in]  GpioPad                   GPIO Pad
  @param[in]  PadCfgDwReg               PADCFG DWx register values
  @param[out] GpioData                  GPIO Configuration data

  @retval Status
**/
STATIC
VOID
GpioConfigFromPadCfgRegValue (
  IN GPIO_PAD      GpioPad,
  IN CONST UINT32  *PadCfgDwReg,
  OUT GPIO_CONFIG  *GpioConfig
  )
{
  UINT32               PadRstCfg;

  //
  // Get Reset Type (PadRstCfg)
  //
  PadRstCfg = (PadCfgDwReg[0] & B_PCH_GPIO_RST_CONF) >> N_PCH_GPIO_RST_CONF;

  GpioConfig->PowerConfig = GpioResetConfigFromPadRstCfg (
                              GpioPad,
                              PadRstCfg
                              );

  //
  // Get how interrupt is triggered (RxEvCfg)
  //
  GpioConfig->InterruptConfig = ((PadCfgDwReg[0] & B_PCH_GPIO_RX_LVL_EDG) >> (N_PCH_GPIO_RX_LVL_EDG - (N_GPIO_INT_CONFIG_INT_TYPE_BIT_POS + 1))) | (0x1 << N_GPIO_INT_CONFIG_INT_TYPE_BIT_POS);

  //
  // Get interrupt generation (GPIRoutIOxAPIC/SCI/SMI/NMI)
  //
  GpioConfig->InterruptConfig |= ((PadCfgDwReg[0] & (B_PCH_GPIO_RX_NMI_ROUTE | B_PCH_GPIO_RX_SCI_ROUTE | B_PCH_GPIO_RX_SMI_ROUTE | B_PCH_GPIO_RX_APIC_ROUTE)) >> (N_PCH_GPIO_RX_NMI_ROUTE - (N_GPIO_INT_CONFIG_INT_SOURCE_BIT_POS + 1))) | (0x1 << N_GPIO_INT_CONFIG_INT_SOURCE_BIT_POS);

  //
  // Get GPIO direction (GPIORxDis and GPIOTxDis)
  //
  GpioConfig->Direction = ((PadCfgDwReg[0] & (B_PCH_GPIO_RXDIS | B_PCH_GPIO_TXDIS)) >> (N_PCH_GPIO_TXDIS - (N_GPIO_DIRECTION_DIR_BIT_POS + 1))) | (0x1 << N_GPIO_DIRECTION_DIR_BIT_POS);

  //
  // Get GPIO input inversion (RXINV)
  // (Only meaningful if input enabled)
  //
  if((PadCfgDwReg[0] & B_PCH_GPIO_RXDIS) == 0) {
    GpioConfig->Direction |= ((PadCfgDwReg[0] & B_PCH_GPIO_RXINV) >> (N_PCH_GPIO_RXINV - (N_GPIO_DIRECTION_INV_BIT_POS + 1))) | (0x1 << N_GPIO_DIRECTION_INV_BIT_POS);
  }

  //
  // Get GPIO output state (GPIOTxState)
  //
  GpioConfig->OutputState = ((PadCfgDwReg[0] & B_PCH_GPIO_TX_STATE) << (N_PCH_GPIO_TX_STATE + (N_GPIO_OUTPUT_BIT_POS + 1))) | (0x1 << N_GPIO_OUTPUT_BIT_POS);

  //
  // Configure GPIO RX raw override to '1' (RXRAW1)
  //
  GpioConfig->OtherSettings = ((PadCfgDwReg[0] & B_PCH_GPIO_RX_RAW1) >> (N_PCH_GPIO_RX_RAW1 - (N_GPIO_OTHER_CONFIG_RXRAW_BIT_POS + 1))) | (0x1 << N_GPIO_OTHER_CONFIG_RXRAW_BIT_POS);

  //
  // Get GPIO Pad Mode (PMode)
  //
  GpioConfig->PadMode = ((PadCfgDwReg[0] & B_PCH_GPIO_PAD_MODE) >> (N_PCH_GPIO_PAD_MODE - (N_GPIO_PAD_MODE_BIT_POS + 1))) | (0x1 << N_GPIO_PAD_MODE_BIT_POS);

  //
  // Get GPIO termination (Term)
  //
  GpioConfig->ElectricalConfig = ((PadCfgDwReg[1] & B_PCH_GPIO_TERM) >> (N_PCH_GPIO_TERM - (N_GPIO_ELECTRICAL_CONFIG_TERMINATION_BIT_POS + 1))) | (0x1 << N_GPIO_ELECTRICAL_CONFIG_TERMINATION_BIT_POS);

  //
  // Get GPIO pad tolerance (padtol)
  //
  GpioConfig->ElectricalConfig |= ((PadCfgDwReg[1] & B_PCH_GPIO_PADTOL) >> (N_PCH_GPIO_PADTOL - (N_GPIO_ELECTRICAL_CONFIG_1V8_TOLERANCE_BIT_POS + 1))) | (0x1 << N_GPIO_ELECTRICAL_CONFIG_1V8_TOLERANCE_BIT_POS);
}

/**
  This procedure will read multiple GPIO settings

  @param[in]  GpioPad                   GPIO Pad
  @param[out] GpioData                  GPIO data structure

  @retval EFI_SUCCESS                   The function completed successfully
  @retval EFI_INVALID_PARAMETER         Invalid group or pad number
**/
EFI_STATUS
GpioGetPadConfig (
  IN  GPIO_PAD               GpioPad,
  OUT GPIO_CONFIG            *GpioData
  )
{
  EFI_STATUS           Status;
  UINT32               PadCfgDwReg[2];
  UINT32               RegVal;
  GPIO_GROUP           Group;
  UINT32               PadNumber;
  UINT32               PadBitPosition;

  Group = GpioGetGroupFromGpioPad (GpioPad);
  PadNumber = GpioGetPadNumberFromGpioPad (GpioPad);
  PadBitPosition = GPIO_GET_PAD_POSITION (PadNumber);

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }

  if (!GpioIsPadHostOwned (GpioPad)) {
    return EFI_UNSUPPORTED;
  }

  //
  // Read PADCFG DW0 register
  //
  GpioReadPadCfgReg (
    GpioPad,
    0,
    &PadCfgDwReg[0]
    );

  //
  // Read PADCFG DW1 register
  //
  GpioReadPadCfgReg (
    GpioPad,
    1,
    &PadCfgDwReg[1]
    );

  GpioConfigFromPadCfgRegValue (
    GpioPad,
    PadCfgDwReg,
    GpioData
    );

  //
  // Read HOSTSW_OWN registers
  //
  Status = GpioReadReg (
             GpioHostOwnershipRegister,
             Group,
             GPIO_GET_DW_NUM (PadNumber),
             &RegVal
             );

  ASSERT_EFI_ERROR (Status);

  //
  // Get Host Software Ownership
  //
  GpioData->HostSoftPadOwn = (((RegVal >> PadBitPosition) & 0x1) << (N_GPIO_HOSTSW_OWN_BIT_POS + 1)) | (0x1 << N_GPIO_HOSTSW_OWN_BIT_POS);

  //
  // Read PADCFGLOCK register
  //
  Status = GpioReadReg (
             GpioPadConfigLockRegister,
             Group,
             GPIO_GET_DW_NUM (PadNumber),
             &RegVal
             );

  ASSERT_EFI_ERROR (Status);

  //
  // Get Pad Configuration Lock state
  //
  GpioData->LockConfig = (((RegVal >> PadBitPosition) & 0x1) << 1) | 0x1;

  //
  // Read PADCFGLOCKTX register
  //
  Status = GpioReadReg (
             GpioPadLockOutputRegister,
             Group,
             GPIO_GET_DW_NUM (PadNumber),
             &RegVal
             );

  ASSERT_EFI_ERROR (Status);

  //
  // Get Pad Configuration Lock Tx state
  //
  GpioData->LockConfig |= (((RegVal >> PadBitPosition) & 0x1) << 2) | 0x1;

  return Status;
}

/**
  This procedure will calculate PADCFG register value based on GpioConfig data

  @param[in]  GpioPad                   GPIO Pad
  @param[in]  GpioConfig                GPIO Configuration data
  @param[out] PadCfgDwReg               PADCFG DWx register value
  @param[out] PadCfgDwRegMask           Mask with PADCFG DWx register bits to be modified

  @retval Status
**/
EFI_STATUS
GpioPadCfgRegValueFromGpioConfig (
  IN  GPIO_PAD           GpioPad,
  IN  CONST GPIO_CONFIG  *GpioConfig,
  OUT UINT32             *PadCfgDwReg,
  OUT UINT32             *PadCfgDwRegMask
  )
{
  EFI_STATUS           Status;
  UINT32               PadRstCfg;

  //
  // Configure Reset Type (PadRstCfg)
  // Reset configuration depends on group type.
  // This field requires support for new and deprecated settings.
  //
  Status = GpioPadRstCfgFromResetConfig (
             GpioPad,
             GpioConfig->PowerConfig,
             &PadRstCfg
             );
  ASSERT_EFI_ERROR (Status);

  PadCfgDwRegMask[0] |= ((((GpioConfig->PowerConfig & B_GPIO_RESET_CONFIG_RESET_MASK) >> N_GPIO_RESET_CONFIG_RESET_BIT_POS) == GpioHardwareDefault) ? 0x0 : B_PCH_GPIO_RST_CONF);
  PadCfgDwReg[0] |= PadRstCfg << N_PCH_GPIO_RST_CONF;

  //
  // Configure how interrupt is triggered (RxEvCfg)
  //
  PadCfgDwRegMask[0] |= ((((GpioConfig->InterruptConfig & B_GPIO_INT_CONFIG_INT_TYPE_MASK) >> N_GPIO_INT_CONFIG_INT_TYPE_BIT_POS) == GpioHardwareDefault) ? 0x0 : B_PCH_GPIO_RX_LVL_EDG);
  PadCfgDwReg[0] |= (((GpioConfig->InterruptConfig & B_GPIO_INT_CONFIG_INT_TYPE_MASK) >> (N_GPIO_INT_CONFIG_INT_TYPE_BIT_POS + 1)) << N_PCH_GPIO_RX_LVL_EDG);

  //
  // Configure interrupt generation (GPIRoutIOxAPIC/SCI/SMI/NMI)
  //
  PadCfgDwRegMask[0] |= ((((GpioConfig->InterruptConfig & B_GPIO_INT_CONFIG_INT_SOURCE_MASK) >> N_GPIO_INT_CONFIG_INT_SOURCE_BIT_POS) == GpioHardwareDefault)  ? 0x0 : (B_PCH_GPIO_RX_NMI_ROUTE | B_PCH_GPIO_RX_SCI_ROUTE | B_PCH_GPIO_RX_SMI_ROUTE | B_PCH_GPIO_RX_APIC_ROUTE));
  PadCfgDwReg[0] |= (((GpioConfig->InterruptConfig & B_GPIO_INT_CONFIG_INT_SOURCE_MASK) >> (N_GPIO_INT_CONFIG_INT_SOURCE_BIT_POS + 1)) << N_PCH_GPIO_RX_NMI_ROUTE);

  //
  // Configure GPIO direction (GPIORxDis and GPIOTxDis)
  //
  PadCfgDwRegMask[0] |= ((((GpioConfig->Direction & B_GPIO_DIRECTION_DIR_MASK) >> N_GPIO_DIRECTION_DIR_BIT_POS) == GpioHardwareDefault) ? 0x0 : (B_PCH_GPIO_RXDIS | B_PCH_GPIO_TXDIS));
  PadCfgDwReg[0] |= (((GpioConfig->Direction & B_GPIO_DIRECTION_DIR_MASK) >> (N_GPIO_DIRECTION_DIR_BIT_POS + 1)) << N_PCH_GPIO_TXDIS);

  //
  // Configure GPIO input inversion (RXINV)
  //
  PadCfgDwRegMask[0] |= ((((GpioConfig->Direction & B_GPIO_DIRECTION_INV_MASK) >> N_GPIO_DIRECTION_INV_BIT_POS) == GpioHardwareDefault) ?  0x0 : B_PCH_GPIO_RXINV);
  PadCfgDwReg[0] |= (((GpioConfig->Direction & B_GPIO_DIRECTION_INV_MASK) >> (N_GPIO_DIRECTION_INV_BIT_POS + 1)) << N_PCH_GPIO_RXINV);

  //
  // Configure GPIO output state (GPIOTxState)
  //
  PadCfgDwRegMask[0] |= ((((GpioConfig->OutputState & B_GPIO_OUTPUT_MASK) >> N_GPIO_OUTPUT_BIT_POS) == GpioHardwareDefault) ? 0x0 : B_PCH_GPIO_TX_STATE);
  PadCfgDwReg[0] |= (((GpioConfig->OutputState & B_GPIO_OUTPUT_MASK) >> (N_GPIO_OUTPUT_BIT_POS + 1)) << N_PCH_GPIO_TX_STATE);

  //
  // Configure GPIO RX raw override to '1' (RXRAW1)
  //
  PadCfgDwRegMask[0] |= ((((GpioConfig->OtherSettings & B_GPIO_OTHER_CONFIG_RXRAW_MASK) >> N_GPIO_OTHER_CONFIG_RXRAW_BIT_POS) == GpioHardwareDefault) ? 0x0 : B_PCH_GPIO_RX_RAW1);
  PadCfgDwReg[0] |= (((GpioConfig->OtherSettings & B_GPIO_OTHER_CONFIG_RXRAW_MASK) >> (N_GPIO_OTHER_CONFIG_RXRAW_BIT_POS + 1)) << N_PCH_GPIO_RX_RAW1);

  //
  // Configure GPIO Pad Mode (PMode)
  //
  PadCfgDwRegMask[0] |= ((((GpioConfig->PadMode & B_GPIO_PAD_MODE_MASK) >> N_GPIO_PAD_MODE_BIT_POS) == GpioHardwareDefault) ? 0x0 : B_PCH_GPIO_PAD_MODE);
  PadCfgDwReg[0] |= (((GpioConfig->PadMode & B_GPIO_PAD_MODE_MASK) >> (N_GPIO_PAD_MODE_BIT_POS + 1)) << N_PCH_GPIO_PAD_MODE);

  //
  // Configure GPIO termination (Term)
  //
  PadCfgDwRegMask[1] |= ((((GpioConfig->ElectricalConfig & B_GPIO_ELECTRICAL_CONFIG_TERMINATION_MASK) >> N_GPIO_ELECTRICAL_CONFIG_TERMINATION_BIT_POS) == GpioHardwareDefault) ? 0x0 : B_PCH_GPIO_TERM);
  PadCfgDwReg[1] |= (((GpioConfig->ElectricalConfig & B_GPIO_ELECTRICAL_CONFIG_TERMINATION_MASK) >> (N_GPIO_ELECTRICAL_CONFIG_TERMINATION_BIT_POS + 1)) << N_PCH_GPIO_TERM);

  //
  // Configure GPIO pad tolerance (padtol)
  //
  PadCfgDwRegMask[1] |= ((((GpioConfig->ElectricalConfig & B_GPIO_ELECTRICAL_CONFIG_1V8_TOLERANCE_MASK) >> N_GPIO_ELECTRICAL_CONFIG_1V8_TOLERANCE_BIT_POS) == GpioHardwareDefault) ? 0x0 : B_PCH_GPIO_PADTOL);
  PadCfgDwReg[1] |= (((GpioConfig->ElectricalConfig & B_GPIO_ELECTRICAL_CONFIG_1V8_TOLERANCE_MASK) >> (N_GPIO_ELECTRICAL_CONFIG_1V8_TOLERANCE_BIT_POS + 1)) << N_PCH_GPIO_PADTOL);

  return Status;
}

/**
  This procedure will configure multiple GPIO settings

  @param[in] GpioPad                    GPIO Pad
  @param[in] GpioData                   GPIO data structure

  @retval EFI_SUCCESS                   The function completed successfully
  @retval EFI_INVALID_PARAMETER         Invalid group or pad number
**/
EFI_STATUS
GpioSetPadConfig (
  IN GPIO_PAD                  GpioPad,
  IN GPIO_CONFIG               *GpioData
  )
{
  EFI_STATUS           Status;
  UINT32               PadCfgDwReg[2];
  UINT32               PadCfgDwRegMask[2];
  UINT32               HostSoftOwnReg;
  UINT32               HostSoftOwnRegMask;
  UINT32               GpiGpeEnReg;
  UINT32               GpiGpeEnRegMask;
  UINT32               GpiNmiEnReg;
  UINT32               GpiNmiEnRegMask;
  GPIO_GROUP           Group;
  UINT32               GroupIndex;
  UINT32               PadNumber;
  UINT32               PadBitPosition;
  UINT32               DwNum;

  ZeroMem (PadCfgDwReg, sizeof (PadCfgDwReg));
  ZeroMem (PadCfgDwRegMask, sizeof (PadCfgDwRegMask));

  Group = GpioGetGroupFromGpioPad (GpioPad);
  GroupIndex = GpioGetGroupIndexFromGpioPad (GpioPad);
  PadNumber = GpioGetPadNumberFromGpioPad (GpioPad);
  PadBitPosition = GPIO_GET_PAD_POSITION (PadNumber);
  DwNum = GPIO_GET_DW_NUM (PadNumber);

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }

  if (!GpioIsPadHostOwned (GpioPad)) {
    return EFI_UNSUPPORTED;
  }

  //
  // Get GPIO PADCFG register value from GPIO config data
  //
  GpioPadCfgRegValueFromGpioConfig (
    GpioPad,
    GpioData,
    PadCfgDwReg,
    PadCfgDwRegMask
    );

  //
  // Write PADCFG DW0 register
  //
  GpioWritePadCfgReg (
    GpioPad,
    0,
    ~PadCfgDwRegMask[0],
    PadCfgDwReg[0]
    );

  //
  // Write PADCFG DW1 register
  //
  GpioWritePadCfgReg (
    GpioPad,
    1,
    ~PadCfgDwRegMask[1],
    PadCfgDwReg[1]
    );

  //
  // Update value to be programmed in HOSTSW_OWN register
  //
  HostSoftOwnRegMask = (GpioData->HostSoftPadOwn & 0x1) << PadBitPosition;
  HostSoftOwnReg = (GpioData->HostSoftPadOwn >> 0x1) << PadBitPosition;

  //
  // Write HOSTSW_OWN registers
  //
  Status = GpioWriteReg (
             GpioHostOwnershipRegister,
             Group,
             DwNum,
             ~HostSoftOwnRegMask,
             HostSoftOwnReg
             );
  ASSERT_EFI_ERROR (Status);

  //
  // Update value to be programmed in GPI_GPE_EN register
  //
  GpiGpeEnRegMask = (GpioData->InterruptConfig & 0x1) << PadBitPosition;
  GpiGpeEnReg = ((GpioData->InterruptConfig & GpioIntSci) >> 3) << PadBitPosition;

  //
  // Write GPI_GPE_EN registers
  //
  Status = GpioWriteReg (
             GpioGpeEnableRegister,
             Group,
             DwNum,
             ~GpiGpeEnRegMask,
             GpiGpeEnReg
             );
  ASSERT_EFI_ERROR (Status);

  //
  // Update value to be programmed in GPI_NMI_EN register
  //
  GpiNmiEnRegMask = (GpioData->InterruptConfig & 0x1) << PadBitPosition;
  GpiNmiEnReg = ((GpioData->InterruptConfig & GpioIntNmi) >> 1) << PadBitPosition;

  Status = GpioWriteReg (
             GpioNmiEnableRegister,
             Group,
             DwNum,
             ~GpiNmiEnRegMask,
             GpiNmiEnReg
             );
  if (Status == EFI_UNSUPPORTED) {
    if (GpiNmiEnReg == 0) {
      //
      // Not all GPIO have NMI capabilities. Since we always try to program this register,
      // even when not enabling NMI for a pad so do not report such access as an error
      //
      Status = EFI_SUCCESS;
    } else {
      DEBUG ((DEBUG_ERROR, "GPIO ERROR: Group %d has no pads supporting NMI\n", GroupIndex));
      ASSERT (FALSE);
      return Status;
    }
  }

  //
  // Program Pad Configuration Lock
  //
  if ((GpioData->LockConfig & GpioPadConfigLock) == GpioPadConfigLock) {
    GpioWriteLockReg (
      GpioPadConfigLockRegister,
      Group,
      DwNum,
      ~0u,
      1 << PadBitPosition
      );
  }

  //
  // Program Pad Configuration Lock Tx
  //
  if ((GpioData->LockConfig & GpioOutputStateLock) == GpioOutputStateLock) {
    GpioWriteLockReg (
      GpioPadLockOutputRegister,
      Group,
      DwNum,
      ~0u,
      1 << PadBitPosition
      );
  }
  return Status;
}

/**
  This procedure will set GPIO output level

  @param[in] GpioPad              GPIO pad
  @param[in] Value                Output value
                                  0: OutputLow, 1: OutputHigh

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid GpioPad
**/
EFI_STATUS
GpioSetOutputValue (
  IN GPIO_PAD                  GpioPad,
  IN UINT32                    Value
  )
{
  if (Value > 1) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  if (!GpioIsPadHostOwned (GpioPad)) {
    return EFI_UNSUPPORTED;
  }

  GpioWritePadCfgReg (
    GpioPad,
    0,
    (UINT32)~B_PCH_GPIO_TX_STATE,
    Value << N_PCH_GPIO_TX_STATE
    );

  return EFI_SUCCESS;
}

/**
  This procedure will get GPIO output level

  @param[in]  GpioPad             GPIO pad
  @param[out] OutputVal           GPIO Output value
                                  0: OutputLow, 1: OutputHigh

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid GpioPad
**/
EFI_STATUS
GpioGetOutputValue (
  IN GPIO_PAD                  GpioPad,
  OUT UINT32                   *OutputVal
  )
{
  UINT32      PadCfgReg;

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  if (!GpioIsPadHostOwned (GpioPad)) {
    return EFI_UNSUPPORTED;
  }

  GpioReadPadCfgReg (
    GpioPad,
    0,
    &PadCfgReg
    );

  *OutputVal = (PadCfgReg & B_PCH_GPIO_TX_STATE) >> N_PCH_GPIO_TX_STATE;

  return EFI_SUCCESS;
}

/**
  This procedure will get GPIO input level

  @param[in] GpioPad              GPIO pad
  @param[out] InputVal            GPIO Input value
                                  0: InputLow, 1: InpuHigh

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid GpioPad
**/
EFI_STATUS
GpioGetInputValue (
  IN GPIO_PAD                  GpioPad,
  OUT UINT32                   *InputVal
  )
{
  UINT32      PadCfgReg;

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  if (!GpioIsPadHostOwned (GpioPad)) {
    return EFI_UNSUPPORTED;
  }

  GpioReadPadCfgReg (
    GpioPad,
    0,
    &PadCfgReg
    );

  *InputVal = (PadCfgReg & B_PCH_GPIO_RX_STATE) >> N_PCH_GPIO_RX_STATE;

  return EFI_SUCCESS;
}

/**
  This procedure will get GPIO IOxAPIC interrupt number

  @param[in]  GpioPad             GPIO pad
  @param[out] IrqNum              IRQ number

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid GpioPad
**/
EFI_STATUS
GpioGetPadIoApicIrqNumber (
  IN GPIO_PAD                  GpioPad,
  OUT UINT32                   *IrqNum
  )
{
  UINT32      PadCfgReg;

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  if (!GpioIsPadHostOwned (GpioPad)) {
    return EFI_UNSUPPORTED;
  }

  GpioReadPadCfgReg (
    GpioPad,
    1,
    &PadCfgReg
    );

  *IrqNum = (PadCfgReg & B_PCH_GPIO_INTSEL) >> N_PCH_GPIO_INTSEL;

  return EFI_SUCCESS;
}

/**
  This procedure will configure GPIO input inversion

  @param[in] GpioPad              GPIO pad
  @param[in] Value                Value for GPIO input inversion
                                  0: No input inversion, 1: Invert input

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid GpioPad
**/
EFI_STATUS
GpioSetInputInversion (
  IN GPIO_PAD                  GpioPad,
  IN UINT32                    Value
  )
{
  if (Value > 1) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  if (!GpioIsPadHostOwned (GpioPad)) {
    return EFI_UNSUPPORTED;
  }

  GpioWritePadCfgReg (
    GpioPad,
    0,
    (UINT32)~B_PCH_GPIO_RXINV,
    Value << N_PCH_GPIO_RXINV
    );

  return EFI_SUCCESS;
}

/**
  This procedure will get GPIO pad input inversion value

  @param[in]  GpioPad             GPIO pad
  @param[out] InvertState         GPIO inversion state
                                  0: No input inversion, 1: Inverted input

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid GpioPad
**/
EFI_STATUS
GpioGetInputInversion (
  IN  GPIO_PAD                 GpioPad,
  OUT UINT32                   *InvertState
  )
{
  UINT32      PadCfgReg;

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  if (!GpioIsPadHostOwned (GpioPad)) {
    return EFI_UNSUPPORTED;
  }

  GpioReadPadCfgReg (
    GpioPad,
    0,
    &PadCfgReg
    );

  *InvertState = (PadCfgReg & B_PCH_GPIO_RXINV) >> N_PCH_GPIO_RXINV;
  return EFI_SUCCESS;
}

/**
  This procedure will set GPIO interrupt settings

  @param[in] GpioPad              GPIO pad
  @param[in] Value                Value of Level/Edge
                                  use GPIO_INT_CONFIG as argument

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid GpioPad
**/
EFI_STATUS
GpioSetPadInterruptConfig (
  IN GPIO_PAD                 GpioPad,
  IN GPIO_INT_CONFIG          Value
  )
{
  EFI_STATUS  Status;
  UINT32      RxLvlEdgeValue;
  UINT32      IntRouteValue;
  UINT32      PadNumber;
  UINT32      GpeEnable;
  UINT32      NmiEnable;

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  if (!GpioIsPadHostOwned (GpioPad)) {
    return EFI_UNSUPPORTED;
  }

  Status = EFI_SUCCESS;

  if (((Value & B_GPIO_INT_CONFIG_INT_TYPE_MASK) >> N_GPIO_INT_CONFIG_INT_TYPE_BIT_POS) != GpioHardwareDefault) {
    RxLvlEdgeValue = ((Value & B_GPIO_INT_CONFIG_INT_TYPE_MASK) >> (N_GPIO_INT_CONFIG_INT_TYPE_BIT_POS + 1)) << N_PCH_GPIO_RX_LVL_EDG;

    GpioWritePadCfgReg (
      GpioPad,
      0,
      (UINT32)~B_PCH_GPIO_RX_LVL_EDG,
      RxLvlEdgeValue
      );
  }

  if (((Value & B_GPIO_INT_CONFIG_INT_SOURCE_MASK) >> N_GPIO_INT_CONFIG_INT_SOURCE_BIT_POS) != GpioHardwareDefault) {

    IntRouteValue = ((Value & B_GPIO_INT_CONFIG_INT_SOURCE_MASK) >> (N_GPIO_INT_CONFIG_INT_SOURCE_BIT_POS + 1)) << N_PCH_GPIO_RX_NMI_ROUTE;

    GpioWritePadCfgReg (
      GpioPad,
      0,
      (UINT32)~(B_PCH_GPIO_RX_NMI_ROUTE | B_PCH_GPIO_RX_SCI_ROUTE | B_PCH_GPIO_RX_SMI_ROUTE | B_PCH_GPIO_RX_APIC_ROUTE),
      IntRouteValue
      );

    if ((Value & GpioIntSci) == GpioIntSci) {
      GpeEnable = 0x1;
    } else {
      GpeEnable = 0x0;
    }

    PadNumber = GpioGetPadNumberFromGpioPad (GpioPad);

    Status = GpioWriteReg (
               GpioGpeEnableRegister,
               GpioGetGroupFromGpioPad (GpioPad),
               GPIO_GET_DW_NUM (PadNumber),
               ~(1 << GPIO_GET_PAD_POSITION (PadNumber)),
               GpeEnable << GPIO_GET_PAD_POSITION (PadNumber)
               );
    ASSERT_EFI_ERROR (Status);

    if ((Value & GpioIntNmi) == GpioIntNmi) {
      NmiEnable = 0x1;
    } else {
      NmiEnable = 0x0;
    }

    Status = GpioWriteReg (
               GpioNmiEnableRegister,
               GpioGetGroupFromGpioPad (GpioPad),
               GPIO_GET_DW_NUM (PadNumber),
               ~(1 << GPIO_GET_PAD_POSITION (PadNumber)),
               NmiEnable << GPIO_GET_PAD_POSITION (PadNumber)
               );
    if (Status == EFI_UNSUPPORTED) {
      if (NmiEnable == 0) {
        //
        // Not all GPIO have NMI capabilities. Since we always try to program this register,
        // even when not enabling NMI for a pad so do not report such access as an error
        //
        return EFI_SUCCESS;
      } else {
        DEBUG ((DEBUG_ERROR, "GPIO ERROR: Group %d has no pads supporting NMI\n", GpioGetGroupIndexFromGpioPad (GpioPad)));
      }
    }
    ASSERT_EFI_ERROR (Status);
  }

  return Status;
}

/**
  This procedure will set GPIO electrical settings

  @param[in] GpioPad              GPIO pad
  @param[in] Value                Value of termination
                                  use GPIO_ELECTRICAL_CONFIG as argument

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid GpioPad
**/
EFI_STATUS
GpioSetPadElectricalConfig (
  IN GPIO_PAD                  GpioPad,
  IN GPIO_ELECTRICAL_CONFIG    Value
  )
{
  UINT32      TermValue;
  UINT32      PadTolValue;

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  if (!GpioIsPadHostOwned (GpioPad)) {
    return EFI_UNSUPPORTED;
  }

  if (((Value & B_GPIO_ELECTRICAL_CONFIG_TERMINATION_MASK) >> N_GPIO_ELECTRICAL_CONFIG_TERMINATION_BIT_POS) != GpioHardwareDefault) {
    TermValue = ((Value & B_GPIO_ELECTRICAL_CONFIG_TERMINATION_MASK) >> (N_GPIO_ELECTRICAL_CONFIG_TERMINATION_BIT_POS + 1)) << N_PCH_GPIO_TERM;

    GpioWritePadCfgReg (
      GpioPad,
      1,
      (UINT32)~B_PCH_GPIO_TERM,
      TermValue
      );
  }

  if (((Value & B_GPIO_ELECTRICAL_CONFIG_1V8_TOLERANCE_MASK) >> N_GPIO_ELECTRICAL_CONFIG_1V8_TOLERANCE_BIT_POS) != GpioHardwareDefault) {
    PadTolValue = ((Value & B_GPIO_ELECTRICAL_CONFIG_1V8_TOLERANCE_MASK) >> (N_GPIO_ELECTRICAL_CONFIG_1V8_TOLERANCE_BIT_POS + 1)) << N_PCH_GPIO_PADTOL;

    GpioWritePadCfgReg (
      GpioPad,
      1,
      (UINT32)~B_PCH_GPIO_PADTOL,
      PadTolValue
      );
  }
  return EFI_SUCCESS;
}

/**
  This procedure will set GPIO Reset settings

  @param[in] GpioPad              GPIO pad
  @param[in] Value                Value for Pad Reset Configuration
                                  use GPIO_RESET_CONFIG as argument

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid GpioPad
**/
EFI_STATUS
GpioSetPadResetConfig (
  IN GPIO_PAD                  GpioPad,
  IN GPIO_RESET_CONFIG         Value
  )
{
  EFI_STATUS  Status;
  UINT32      PadRstCfg;

  Status = EFI_SUCCESS;

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  if (!GpioIsPadHostOwned (GpioPad)) {
    return EFI_UNSUPPORTED;
  }

  if (((Value & B_GPIO_RESET_CONFIG_RESET_MASK) >> N_GPIO_RESET_CONFIG_RESET_BIT_POS) != GpioHardwareDefault) {

    //
    // Reset configuration depends on group type.
    // This field requires support for new and deprecated settings.
    //
    Status = GpioPadRstCfgFromResetConfig (
               GpioPad,
               Value,
               &PadRstCfg
               );
    ASSERT_EFI_ERROR (Status);

    GpioWritePadCfgReg (
      GpioPad,
      0,
      (UINT32)~B_PCH_GPIO_RST_CONF,
      PadRstCfg << N_PCH_GPIO_RST_CONF
      );
  }
  return Status;
}

/**
  This procedure will get GPIO Reset settings

  @param[in] GpioPad              GPIO pad
  @param[in] Value                Value of Pad Reset Configuration
                                  based on GPIO_RESET_CONFIG

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid GpioPad
**/
EFI_STATUS
GpioGetPadResetConfig (
  IN GPIO_PAD                  GpioPad,
  IN GPIO_RESET_CONFIG         *Value
  )
{
  UINT32      PadRstCfg;
  UINT32      PadCfgDw0Reg;

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  if (!GpioIsPadHostOwned (GpioPad)) {
    return EFI_UNSUPPORTED;
  }

  GpioReadPadCfgReg (
    GpioPad,
    0,
    &PadCfgDw0Reg
    );

  //
  // Get Reset Type (PadRstCfg)
  //
  PadRstCfg = (PadCfgDw0Reg & B_PCH_GPIO_RST_CONF) >> N_PCH_GPIO_RST_CONF;

  *Value = GpioResetConfigFromPadRstCfg (
             GpioPad,
             PadRstCfg
             );

  return EFI_SUCCESS;
}

/**
  This procedure will get GPIO Host Software Pad Ownership for certain group

  @param[in]  Group               GPIO group
  @param[in]  DwNum               Host Ownership register number for current group.
                                  For group which has less then 32 pads per group DwNum must be 0.
  @param[out] HostSwRegVal        Value of Host Software Pad Ownership register
                                  Bit position - PadNumber
                                  Bit value - 0: ACPI Mode, 1: GPIO Driver mode

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid group or DwNum parameter number
**/
EFI_STATUS
GpioGetHostSwOwnershipForGroupDw (
  IN  GPIO_GROUP                  Group,
  IN  UINT32                      DwNum,
  OUT UINT32                      *HostSwRegVal
  )
{
  EFI_STATUS  Status;

  if (!GpioIsGroupAndDwNumValid (Group, DwNum)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  Status = GpioReadReg (
             GpioHostOwnershipRegister,
             Group,
             DwNum,
             HostSwRegVal
             );

  ASSERT_EFI_ERROR (Status);
  return Status;
}

/**
  This procedure will get GPIO Host Software Pad Ownership for certain group

  @param[in]  Group               GPIO group
  @param[in]  DwNum               Host Ownership register number for current group
                                  For group which has less then 32 pads per group DwNum must be 0.
  @param[in]  HostSwRegVal        Value of Host Software Pad Ownership register
                                  Bit position - PadNumber
                                  Bit value - 0: ACPI Mode, 1: GPIO Driver mode

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid group or DwNum parameter number
**/
EFI_STATUS
GpioSetHostSwOwnershipForGroupDw (
  IN GPIO_GROUP                   Group,
  IN UINT32                       DwNum,
  IN UINT32                       HostSwRegVal
  )
{
  EFI_STATUS  Status;

  if (!GpioIsGroupAndDwNumValid (Group, DwNum)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  Status = GpioWriteReg (
             GpioHostOwnershipRegister,
             Group,
             DwNum,
             0,
             HostSwRegVal
             );

  ASSERT_EFI_ERROR (Status);
  return Status;
}

/**
  This procedure will get Gpio Pad Host Software Ownership

  @param[in]  GpioPad             GPIO pad
  @param[out] PadHostSwOwn        Value of Host Software Pad Owner
                                  0: ACPI Mode, 1: GPIO Driver mode

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid GpioPad
**/
EFI_STATUS
GpioGetHostSwOwnershipForPad (
  IN GPIO_PAD                 GpioPad,
  OUT UINT32                  *PadHostSwOwn
  )
{
  EFI_STATUS  Status;
  UINT32      PadNumber;
  UINT32      HostSwRegVal;

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  PadNumber = GpioGetPadNumberFromGpioPad (GpioPad);

  Status = GpioReadReg (
             GpioHostOwnershipRegister,
             GpioGetGroupFromGpioPad (GpioPad),
             GPIO_GET_DW_NUM (PadNumber),
             &HostSwRegVal
             );
  ASSERT_EFI_ERROR (Status);

  *PadHostSwOwn = (HostSwRegVal >> GPIO_GET_PAD_POSITION (PadNumber)) & 0x1;

  return Status;
}

/**
  This procedure will set Gpio Pad Host Software Ownership

  @param[in] GpioPad              GPIO pad
  @param[in] PadHostSwOwn         Pad Host Software Owner
                                  0: ACPI Mode, 1: GPIO Driver mode

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid GpioPad
**/
EFI_STATUS
GpioSetHostSwOwnershipForPad (
  IN GPIO_PAD                  GpioPad,
  IN UINT32                    PadHostSwOwn
  )
{
  EFI_STATUS  Status;
  UINT32      PadNumber;

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  PadNumber = GpioGetPadNumberFromGpioPad (GpioPad);

  Status = GpioWriteReg (
             GpioHostOwnershipRegister,
             GpioGetGroupFromGpioPad (GpioPad),
             GPIO_GET_DW_NUM (PadNumber),
             ~0u,
             1 << GPIO_GET_PAD_POSITION (PadNumber)
             );

  ASSERT_EFI_ERROR (Status);
  return Status;
}

/**
  This procedure will get Gpio Pad Ownership

  @param[in] GpioPad              GPIO pad
  @param[out] PadOwnVal           Value of Pad Ownership

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid group or pad number
**/
EFI_STATUS
GpioGetPadOwnership (
  IN  GPIO_PAD                GpioPad,
  OUT GPIO_PAD_OWN            *PadOwnVal
  )
{
  UINT32               Mask;
  UINT32               RegOffset;
  UINT32               GroupIndex;
  UINT32               PadNumber;
  GPIO_GROUP_INFO      *GpioGroupInfo;
  UINTN                GpioGroupInfoLength;
  UINT32               PadOwnRegValue;

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  GroupIndex = GpioGetGroupIndexFromGpioPad (GpioPad);
  PadNumber = GpioGetPadNumberFromGpioPad (GpioPad);

  GpioGroupInfo = GpioGetGroupInfoTable (&GpioGroupInfoLength);

  //
  // Calculate RegOffset using Pad Ownership offset and GPIO Pad number.
  // One DWord register contains information for 8 pads.
  //
  RegOffset = GpioGroupInfo[GroupIndex].PadOwnOffset + (PadNumber >> 3) * 0x4;

  //
  // Calculate pad bit position within DWord register
  //
  PadNumber %= 8;
  Mask = (BIT1 | BIT0) << (PadNumber * 4);

  PadOwnRegValue = MmioRead32 (PCH_PCR_ADDRESS (GpioGroupInfo[GroupIndex].Community, RegOffset));

  *PadOwnVal = (GPIO_PAD_OWN) ((PadOwnRegValue & Mask) >> (PadNumber * 4));

  return EFI_SUCCESS;
}

/**
  This procedure will check state of Pad Config Lock for pads within one group

  @param[in]  Group               GPIO group
  @param[in]  DwNum               PadCfgLock register number for current group.
                                  For group which has less then 32 pads per group DwNum must be 0.
  @param[out] PadCfgLockRegVal    Value of PadCfgLock register
                                  Bit position - PadNumber
                                  Bit value - 0: NotLocked, 1: Locked

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid group or DwNum parameter number
**/
EFI_STATUS
GpioGetPadCfgLockForGroupDw (
  IN  GPIO_GROUP                  Group,
  IN  UINT32                      DwNum,
  OUT UINT32                      *PadCfgLockRegVal
  )
{
  EFI_STATUS  Status;

  if (!GpioIsGroupAndDwNumValid (Group, DwNum)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  Status = GpioReadReg (
             GpioPadConfigLockRegister,
             Group,
             DwNum,
             PadCfgLockRegVal
             );

  ASSERT_EFI_ERROR (Status);
  return Status;
}

/**
  This procedure will check state of Pad Config Lock for selected pad

  @param[in] GpioPad              GPIO pad
  @param[out] PadCfgLock          PadCfgLock for selected pad
                                  0: NotLocked, 1: Locked

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid GpioPad
**/
EFI_STATUS
GpioGetPadCfgLock (
  IN GPIO_PAD                   GpioPad,
  OUT UINT32                    *PadCfgLock
  )
{
  EFI_STATUS  Status;
  UINT32      PadNumber;
  UINT32      PadCfgLockRegVal;

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  PadNumber = GpioGetPadNumberFromGpioPad (GpioPad);

  Status = GpioReadReg (
             GpioPadConfigLockRegister,
             GpioGetGroupFromGpioPad (GpioPad),
             GPIO_GET_DW_NUM (PadNumber),
             &PadCfgLockRegVal
             );
  ASSERT_EFI_ERROR (Status);

  *PadCfgLock = (PadCfgLockRegVal >> GPIO_GET_PAD_POSITION (PadNumber)) & 0x1;

  return Status;
}

/**
  This procedure will check state of Pad Config Tx Lock for pads within one group

  @param[in]  Group               GPIO group
  @param[in]  DwNum               PadCfgLockTx register number for current group.
                                  For group which has less then 32 pads per group DwNum must be 0.
  @param[out] PadCfgLockTxRegVal  Value of PadCfgLockTx register
                                  Bit position - PadNumber
                                  Bit value - 0: NotLockedTx, 1: LockedTx

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid group or DwNum parameter number
**/
EFI_STATUS
GpioGetPadCfgLockTxForGroupDw (
  IN  GPIO_GROUP                  Group,
  IN  UINT32                      DwNum,
  OUT UINT32                      *PadCfgLockTxRegVal
  )
{
  EFI_STATUS  Status;

  if (!GpioIsGroupAndDwNumValid (Group, DwNum)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  Status = GpioReadReg (
             GpioPadLockOutputRegister,
             Group,
             DwNum,
             PadCfgLockTxRegVal
             );

  ASSERT_EFI_ERROR (Status);
  return Status;
}

/**
  This procedure will check state of Pad Config Tx Lock for selected pad

  @param[in] GpioPad              GPIO pad
  @param[out] PadCfgLock          PadCfgLockTx for selected pad
                                  0: NotLockedTx, 1: LockedTx

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid GpioPad
**/
EFI_STATUS
GpioGetPadCfgLockTx (
  IN GPIO_PAD                   GpioPad,
  OUT UINT32                    *PadCfgLockTx
  )
{
  EFI_STATUS  Status;
  UINT32      PadNumber;
  UINT32      PadCfgLockTxRegVal;

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  PadNumber = GpioGetPadNumberFromGpioPad (GpioPad);

  Status = GpioReadReg (
             GpioPadLockOutputRegister,
             GpioGetGroupFromGpioPad (GpioPad),
             GPIO_GET_DW_NUM (PadNumber),
             &PadCfgLockTxRegVal
             );
  ASSERT_EFI_ERROR (Status);

  *PadCfgLockTx = (PadCfgLockTxRegVal >> GPIO_GET_PAD_POSITION (PadNumber)) & 0x1;

  return Status;
}

/**
  This procedure will clear PadCfgLock for selected pads within one group.
  This function should be used only inside SMI.

  @param[in]  Group               GPIO group
  @param[in]  DwNum               PadCfgLock register number for current group.
                                  For group which has less then 32 pads per group DwNum must be 0.
  @param[in]  PadsToUnlock        Bitmask for pads which are going to be unlocked,
                                  Bit position - PadNumber
                                  Bit value - 0: DoNotUnlock, 1: Unlock

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid group or pad number
**/
EFI_STATUS
GpioUnlockPadCfgForGroupDw (
  IN GPIO_GROUP                Group,
  IN UINT32                    DwNum,
  IN UINT32                    PadsToUnlock
  )
{
  EFI_STATUS           Status;

  if (!GpioIsGroupAndDwNumValid (Group, DwNum)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  Status = GpioWriteLockReg (
             GpioPadConfigLockRegister,
             Group,
             DwNum,
             ~PadsToUnlock,
             0
             );
  ASSERT_EFI_ERROR (Status);
  return Status;
}

/**
  This procedure will clear PadCfgLock for selected pad.
  This function should be used only inside SMI.

  @param[in] GpioPad              GPIO pad

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid group or pad number
**/
EFI_STATUS
GpioUnlockPadCfg (
  IN GPIO_PAD                   GpioPad
  )
{
  EFI_STATUS   Status;
  GPIO_GROUP   Group;
  UINT32       PadNumber;

  Group = GpioGetGroupFromGpioPad (GpioPad);
  PadNumber = GpioGetPadNumberFromGpioPad (GpioPad);

  Status = GpioUnlockPadCfgForGroupDw (
             Group,
             GPIO_GET_DW_NUM (PadNumber),
             1 << GPIO_GET_PAD_POSITION (PadNumber)
             );

  ASSERT_EFI_ERROR (Status);
  return Status;
}

/**
  This procedure will set PadCfgLock for selected pads within one group

  @param[in]  Group               GPIO group
  @param[in]  DwNum               PadCfgLock register number for current group.
                                  For group which has less then 32 pads per group DwNum must be 0.
  @param[in]  PadsToLock          Bitmask for pads which are going to be locked
                                  Bit position - PadNumber
                                  Bit value - 0: DoNotLock, 1: Lock

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid group or DwNum parameter number
**/
EFI_STATUS
GpioLockPadCfgForGroupDw (
  IN GPIO_GROUP                   Group,
  IN UINT32                       DwNum,
  IN UINT32                       PadsToLock
  )
{
  EFI_STATUS           Status;

  if (!GpioIsGroupAndDwNumValid (Group, DwNum)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  Status = GpioWriteLockReg (
             GpioPadConfigLockRegister,
             Group,
             DwNum,
             ~0u,
             PadsToLock
             );
  ASSERT_EFI_ERROR (Status);
  return Status;
}

/**
  This procedure will set PadCfgLock for selected pad

  @param[in] GpioPad              GPIO pad

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid group or pad number
**/
EFI_STATUS
GpioLockPadCfg (
  IN GPIO_PAD                   GpioPad
  )
{
  EFI_STATUS   Status;
  GPIO_GROUP   Group;
  UINT32       PadNumber;

  Group = GpioGetGroupFromGpioPad (GpioPad);
  PadNumber = GpioGetPadNumberFromGpioPad (GpioPad);

  Status = GpioLockPadCfgForGroupDw (
             Group,
             GPIO_GET_DW_NUM (PadNumber),
             1 << GPIO_GET_PAD_POSITION (PadNumber)
             );

  ASSERT_EFI_ERROR (Status);
  return Status;
}

/**
  This procedure will clear PadCfgLockTx for selected pads within one group.
  This function should be used only inside SMI.

  @param[in]  Group               GPIO group
  @param[in]  DwNum               PadCfgLockTx register number for current group.
                                  For group which has less then 32 pads per group DwNum must be 0.
  @param[in]  PadsToUnlockTx      Bitmask for pads which are going to be unlocked,
                                  Bit position - PadNumber
                                  Bit value - 0: DoNotUnLockTx, 1: LockTx

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid group or pad number
**/
EFI_STATUS
GpioUnlockPadCfgTxForGroupDw (
  IN GPIO_GROUP                Group,
  IN UINT32                    DwNum,
  IN UINT32                    PadsToUnlockTx
  )
{
  EFI_STATUS           Status;

  if (!GpioIsGroupAndDwNumValid (Group, DwNum)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  Status = GpioWriteLockReg (
             GpioPadLockOutputRegister,
             Group,
             DwNum,
             ~PadsToUnlockTx,
             0
             );
  ASSERT_EFI_ERROR (Status);
  return Status;
}

/**
  This procedure will clear PadCfgLockTx for selected pad.
  This function should be used only inside SMI.

  @param[in] GpioPad              GPIO pad

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid group or pad number
**/
EFI_STATUS
GpioUnlockPadCfgTx (
  IN GPIO_PAD                   GpioPad
  )
{
  EFI_STATUS   Status;
  GPIO_GROUP   Group;
  UINT32       PadNumber;

  Group = GpioGetGroupFromGpioPad (GpioPad);
  PadNumber = GpioGetPadNumberFromGpioPad (GpioPad);

  Status = GpioUnlockPadCfgTxForGroupDw (
             Group,
             GPIO_GET_DW_NUM (PadNumber),
             1 << GPIO_GET_PAD_POSITION (PadNumber)
             );

  ASSERT_EFI_ERROR (Status);
  return Status;
}

/**
  This procedure will set PadCfgLockTx for selected pads within one group

  @param[in]  Group               GPIO group
  @param[in]  DwNum               PadCfgLock register number for current group.
                                  For group which has less then 32 pads per group DwNum must be 0.
  @param[in]  PadsToLockTx        Bitmask for pads which are going to be locked,
                                  Bit position - PadNumber
                                  Bit value - 0: DoNotLockTx, 1: LockTx

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid group or DwNum parameter number
**/
EFI_STATUS
GpioLockPadCfgTxForGroupDw (
  IN GPIO_GROUP                   Group,
  IN UINT32                       DwNum,
  IN UINT32                       PadsToLockTx
  )
{
  EFI_STATUS           Status;

  if (!GpioIsGroupAndDwNumValid (Group, DwNum)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  Status = GpioWriteLockReg (
             GpioPadLockOutputRegister,
             Group,
             DwNum,
             ~0u,
             PadsToLockTx
             );
  ASSERT_EFI_ERROR (Status);
  return Status;
}

/**
  This procedure will set PadCfgLockTx for selected pad

  @param[in] GpioPad              GPIO pad

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid group or pad number
**/
EFI_STATUS
GpioLockPadCfgTx (
  IN GPIO_PAD                   GpioPad
  )
{
  EFI_STATUS   Status;
  GPIO_GROUP   Group;
  UINT32       PadNumber;

  Group = GpioGetGroupFromGpioPad (GpioPad);
  PadNumber = GpioGetPadNumberFromGpioPad (GpioPad);

  Status = GpioLockPadCfgTxForGroupDw (
             Group,
             GPIO_GET_DW_NUM (PadNumber),
             1 << GPIO_GET_PAD_POSITION (PadNumber)
             );

  ASSERT_EFI_ERROR (Status);
  return Status;
}


/**
  This procedure will get Group to GPE mapping.

  @param[out] GroupToGpeDw0       GPIO group to be mapped to GPE_DW0
  @param[out] GroupToGpeDw1       GPIO group to be mapped to GPE_DW1
  @param[out] GroupToGpeDw2       GPIO group to be mapped to GPE_DW2

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid group or pad number
**/
EFI_STATUS
GpioGetGroupToGpeDwX (
  IN GPIO_GROUP               *GroupToGpeDw0,
  IN GPIO_GROUP               *GroupToGpeDw1,
  IN GPIO_GROUP               *GroupToGpeDw2
  )
{
  UINT32               Data32;
  UINT32               PchPwrmBase;
  GPIO_GROUP           GpioGroupOffset;

  GpioGroupOffset = GpioGetLowestGroup ();

  PchPwrmBaseGet (&PchPwrmBase);

  Data32 = MmioRead32 ((UINTN) (PchPwrmBase + R_PCH_PWRM_GPIO_CFG));

  *GroupToGpeDw0 = ((Data32 & B_PCH_PWRM_GPIO_CFG_GPE0_DW0) >> N_PCH_PWRM_GPIO_CFG_GPE0_DW0) + GpioGroupOffset;
  *GroupToGpeDw1 = ((Data32 & B_PCH_PWRM_GPIO_CFG_GPE0_DW1) >> N_PCH_PWRM_GPIO_CFG_GPE0_DW1) + GpioGroupOffset;
  *GroupToGpeDw2 = ((Data32 & B_PCH_PWRM_GPIO_CFG_GPE0_DW2) >> N_PCH_PWRM_GPIO_CFG_GPE0_DW2) + GpioGroupOffset;

  return EFI_SUCCESS;
}

/**
  This procedure will set Group to GPE mapping.

  @param[in]  GroupToGpeDw0       GPIO group to be mapped to GPE_DW0
  @param[in]  GroupToGpeDw1       GPIO group to be mapped to GPE_DW1
  @param[in]  GroupToGpeDw2       GPIO group to be mapped to GPE_DW2

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid group or pad number
**/
EFI_STATUS
GpioSetGroupToGpeDwX (
  IN GPIO_GROUP                GroupToGpeDw0,
  IN GPIO_GROUP                GroupToGpeDw1,
  IN GPIO_GROUP                GroupToGpeDw2
  )
{
  UINT32               Data32Or;
  UINT32               Data32And;
  UINT32               PchPwrmBase;
  GPIO_GROUP           GpioGroupLowest;
  GPIO_GROUP           GpioGroupHighest;

  GpioGroupLowest = GpioGetLowestGroup ();
  GpioGroupHighest = GpioGetHighestGroup ();

  //
  // Check if group argument exceeds GPIO group range
  //
  if (((UINT32) GroupToGpeDw0 < GpioGroupLowest) || ((UINT32) GroupToGpeDw0 > GpioGroupHighest) ||
      ((UINT32) GroupToGpeDw1 < GpioGroupLowest) || ((UINT32) GroupToGpeDw1 > GpioGroupHighest) ||
      ((UINT32) GroupToGpeDw2 < GpioGroupLowest) || ((UINT32) GroupToGpeDw2 > GpioGroupHighest)) {
    DEBUG ((DEBUG_ERROR, "GPIO ERROR: Group argument exceeds GPIO group range\n"));
    return EFI_INVALID_PARAMETER;
  }

  //
  // Check if each group number is unique
  //
  if ((GroupToGpeDw0 == GroupToGpeDw1) ||
      (GroupToGpeDw0 == GroupToGpeDw2) ||
      (GroupToGpeDw1 == GroupToGpeDw2)) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Values in GPE0_DWx registers are 0 based (GPP_A = 0h)
  //
  GroupToGpeDw0 = GpioGetGroupIndexFromGroup (GroupToGpeDw0);
  GroupToGpeDw1 = GpioGetGroupIndexFromGroup (GroupToGpeDw1);
  GroupToGpeDw2 = GpioGetGroupIndexFromGroup (GroupToGpeDw2);

  PchPwrmBaseGet (&PchPwrmBase);

  //
  // Program GPIO_CFG (PMRMBASE + 120h) register
  //
  Data32And = (UINT32) ~(B_PCH_PWRM_GPIO_CFG_GPE0_DW2 | B_PCH_PWRM_GPIO_CFG_GPE0_DW1 | B_PCH_PWRM_GPIO_CFG_GPE0_DW0);
  Data32Or  = (UINT32) ((GroupToGpeDw2 << N_PCH_PWRM_GPIO_CFG_GPE0_DW2) |
                        (GroupToGpeDw1 << N_PCH_PWRM_GPIO_CFG_GPE0_DW1) |
                        (GroupToGpeDw0 << N_PCH_PWRM_GPIO_CFG_GPE0_DW0));

  MmioAndThenOr32 (
    (PchPwrmBase + R_PCH_PWRM_GPIO_CFG),
    Data32And,
    Data32Or
    );

  Data32And = (UINT32) ~(B_PCH_PCR_GPIO_MISCCFG_GPE0_DW2 | B_PCH_PCR_GPIO_MISCCFG_GPE0_DW1 | B_PCH_PCR_GPIO_MISCCFG_GPE0_DW0);
  Data32Or = (UINT32) ((GroupToGpeDw2 << N_PCH_PCR_GPIO_MISCCFG_GPE0_DW2) |
                       (GroupToGpeDw1 << N_PCH_PCR_GPIO_MISCCFG_GPE0_DW1) |
                       (GroupToGpeDw0 << N_PCH_PCR_GPIO_MISCCFG_GPE0_DW0));
  //
  // Program MISCCFG register for Community 0
  //
  MmioAndThenOr32 (
    PCH_PCR_ADDRESS (PID_GPIOCOM0, R_PCH_PCR_GPIO_MISCCFG),
    Data32And,
    Data32Or
    );

  //
  // Program MISCCFG register for Community 1
  //
  MmioAndThenOr32 (
    PCH_PCR_ADDRESS (PID_GPIOCOM1, R_PCH_PCR_GPIO_MISCCFG),
    Data32And,
    Data32Or
    );

  //
  // Program MISCCFG register for Community 2
  //
  MmioAndThenOr32 (
    PCH_PCR_ADDRESS (PID_GPIOCOM2, R_PCH_PCR_GPIO_MISCCFG),
    Data32And,
    Data32Or
    );

  //
  // Program MISCCFG register for Community 3
  //
  MmioAndThenOr32 (
    PCH_PCR_ADDRESS (PID_GPIOCOM3, R_PCH_PCR_GPIO_MISCCFG),
    Data32And,
    Data32Or
    );

  return EFI_SUCCESS;
}

/**
  This procedure will get GPE number for provided GpioPad.
  PCH allows to configure mapping between GPIO groups and related GPE (GpioSetGroupToGpeDwX())
  what results in the fact that certain Pad can cause different General Purpose Event. Only three
  GPIO groups can be mapped to cause unique GPE (1-tier), all others groups will be under one common
  event (GPE_111 for 2-tier).

  1-tier:
  Returned GpeNumber is in range <0,95>. GpioGetGpeNumber() can be used
  to determine what _LXX ACPI method would be called on event on selected GPIO pad

  2-tier:
  Returned GpeNumber is 0x6F (111). All GPIO pads which are not mapped to 1-tier GPE
  will be under one master GPE_111 which is linked to _L6F ACPI method. If it is needed to determine
  what Pad from 2-tier has caused the event, _L6F method should check GPI_GPE_STS and GPI_GPE_EN
  registers for all GPIO groups not mapped to 1-tier GPE.

  @param[in]  GpioPad             GPIO pad
  @param[out] GpeNumber           GPE number

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid GpioPad
**/
EFI_STATUS
GpioGetGpeNumber (
  IN  GPIO_PAD                  GpioPad,
  OUT UINT32                    *GpeNumber
  )
{
  GPIO_GROUP           GroupToGpeDw0;
  GPIO_GROUP           GroupToGpeDw1;
  GPIO_GROUP           GroupToGpeDw2;
  GPIO_GROUP           Group;
  UINT32               PadNumber;

  Group = GpioGetGroupFromGpioPad (GpioPad);
  PadNumber = GpioGetPadNumberFromGpioPad (GpioPad);

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  //
  // Get GPIO groups mapping to 1-tier GPE
  // This mapping is dependent on GPIO IP implementation
  // and may change between chipset generations
  //
  GpioGetGroupToGpeDwX (&GroupToGpeDw0,&GroupToGpeDw1,&GroupToGpeDw2);

  if (Group == GroupToGpeDw0) {
    *GpeNumber = PadNumber;
  } else if (Group == GroupToGpeDw1) {
    *GpeNumber = PadNumber + 32;
  } else if (Group == GroupToGpeDw2) {
    *GpeNumber = PadNumber + 64;
  } else {
    //
    // If Group number doesn't match any of above then
    // it means than certain pad is routed to 2-tier GPE
    // which all are under GPE_111 (0x6F)
    //
    *GpeNumber = PCH_GPIO_2_TIER_MASTER_GPE_NUMBER;
  }

  return EFI_SUCCESS;
}

/**
  This procedure is used to clear SMI STS for a specified Pad

  @param[in]  GpioPad             GPIO pad

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid GpioPad
**/
EFI_STATUS
GpioClearGpiSmiSts (
  IN GPIO_PAD                   GpioPad
  )
{
  EFI_STATUS           Status;
  GPIO_GROUP           Group;
  UINT32               PadNumber;
  UINT32               DwNum;
  UINT32               PadBitPosition;

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }

  Group = GpioGetGroupFromGpioPad (GpioPad);
  PadNumber = GpioGetPadNumberFromGpioPad (GpioPad);
  DwNum = GPIO_GET_DW_NUM (PadNumber);
  PadBitPosition = GPIO_GET_PAD_POSITION (PadNumber);

  //
  // Clear GPI SMI Status bit by writing '1'
  //
  Status = GpioWriteReg (
             GpioSmiStatusRegister,
             Group,
             DwNum,
             0u,
             (UINT32) (BIT0 << PadBitPosition)
             );

  return Status;
}

/**
  This procedure is used by PchSmiDispatcher and will clear
  all GPI SMI Status bits

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid group or pad number
**/
EFI_STATUS
GpioClearAllGpiSmiSts (
  VOID
  )
{
  UINT32         DwNum;
  GPIO_GROUP     Group;
  GPIO_GROUP     GroupMin;
  GPIO_GROUP     GroupMax;

  GroupMin = GpioGetLowestGroup ();
  GroupMax = GpioGetHighestGroup ();

  for (Group = GroupMin; Group <= GroupMax; Group++) {
    //
    // Disable all GPI SMI
    //
    for (DwNum = 0; DwNum <= GPIO_GET_DW_NUM (GpioGetPadPerGroup (Group)); DwNum++) {
      GpioWriteReg (
        GpioSmiStatusRegister,
        Group,
        DwNum,
        0u,
        0xFFFFFFFF
        );
    }
  }
  return EFI_SUCCESS;

}

/**
  This procedure is used to disable all GPI SMI

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid group or pad number
**/
EFI_STATUS
GpioDisableAllGpiSmi (
  VOID
  )
{
  UINT32         DwNum;
  GPIO_GROUP     Group;
  GPIO_GROUP     GroupMin;
  GPIO_GROUP     GroupMax;

  GroupMin = GpioGetLowestGroup ();
  GroupMax = GpioGetHighestGroup ();

  for (Group = GroupMin; Group <= GroupMax; Group++) {
    //
    // Disable all GPI SMI
    //
    for (DwNum = 0; DwNum <= GPIO_GET_DW_NUM (GpioGetPadPerGroup (Group)); DwNum++) {
      GpioWriteReg (
        GpioSmiEnableRegister,
        Group,
        DwNum,
        0u,
        0u
        );
    }
  }
  return EFI_SUCCESS;
}

/**
  This procedure is used to register GPI SMI dispatch function.

  @param[in]  GpioPad             GPIO pad
  @param[out] GpiNum              GPI number

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid GpioPad
**/
EFI_STATUS
GpioGetGpiSmiNum (
  IN GPIO_PAD          GpioPad,
  OUT UINTN            *GpiNum
  )
{
  UINT32               GroupIndex;
  UINT32               Index;
  UINT32               PadNumber;
  GPIO_GROUP_INFO      *GpioGroupInfo;
  UINTN                GpioGroupInfoLength;

  GroupIndex = GpioGetGroupIndexFromGpioPad (GpioPad);
  PadNumber = GpioGetPadNumberFromGpioPad (GpioPad);

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }

  GpioGroupInfo = GpioGetGroupInfoTable (&GpioGroupInfoLength);

  *GpiNum = 0;

  for (Index = 0; Index < GroupIndex; Index++) {
    *GpiNum += (UINTN) (GpioGroupInfo[Index].PadPerGroup);
  }
  *GpiNum += (UINTN) PadNumber;

  return EFI_SUCCESS;
}

/**
  This procedure is used to check GPIO inputs belongs to 2 tier or 1 tier architecture

  @param[in]  GpioPad             GPIO pad

  @retval     Data                0 means 1-tier, 1 means 2-tier
**/
BOOLEAN
GpioCheckFor2Tier (
  IN GPIO_PAD                  GpioPad
  )
{
  UINT32               Data32;

  GpioGetGpeNumber (GpioPad, &Data32);
  if (Data32 == PCH_GPIO_2_TIER_MASTER_GPE_NUMBER) {
    return TRUE;
  }

  return FALSE;
}

/**
  This procedure is used to clear GPE STS for a specified GpioPad

  @param[in]  GpioPad             GPIO pad

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid GpioPad
**/
EFI_STATUS
GpioClearGpiGpeSts (
  IN GPIO_PAD                  GpioPad
  )
{
  EFI_STATUS           Status;
  GPIO_GROUP           Group;
  UINT32               PadNumber;
  UINT32               DwNum;
  UINT32               PadBitPosition;

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }

  //
  // Check for 2-tier
  //
  if (!(GpioCheckFor2Tier (GpioPad))) {
    return EFI_INVALID_PARAMETER;
  }

  Group = GpioGetGroupFromGpioPad (GpioPad);
  PadNumber = GpioGetPadNumberFromGpioPad (GpioPad);
  DwNum = GPIO_GET_DW_NUM (PadNumber);
  PadBitPosition = GPIO_GET_PAD_POSITION (PadNumber);

  //
  // Clear GPI GPE Status bit by writing '1'
  //
  Status = GpioWriteReg (
             GpioGpeStatusRegister,
             Group,
             DwNum,
             0u,
             (UINT32) (BIT0 << PadBitPosition)
             );

  return Status;
}

/**
  This procedure is used to read GPE STS for a specified Pad

  @param[in]  GpioPad             GPIO pad
  @param[out] Data                GPE STS data

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid GpioPad
**/
EFI_STATUS
GpioGetGpiGpeSts (
  IN GPIO_PAD                  GpioPad,
  OUT UINT32                   *Data
  )
{
  EFI_STATUS           Status;
  UINT32               GpeStsRegVal;
  GPIO_GROUP           Group;
  UINT32               PadNumber;
  UINT32               DwNum;
  UINT32               PadBitPosition;

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }

  //
  // Check for 2-tier
  //
  if (!(GpioCheckFor2Tier (GpioPad))) {
    return EFI_INVALID_PARAMETER;
  }

  Group = GpioGetGroupFromGpioPad (GpioPad);
  PadNumber = GpioGetPadNumberFromGpioPad (GpioPad);
  DwNum = GPIO_GET_DW_NUM (PadNumber);
  PadBitPosition = GPIO_GET_PAD_POSITION (PadNumber);

  //
  // Read GPI GPE Status bits
  //
  Status = GpioReadReg (
             GpioGpeStatusRegister,
             Group,
             DwNum,
             &GpeStsRegVal
             );
  if (EFI_ERROR (Status)) {
    return EFI_INVALID_PARAMETER;
  }

  *Data = (GpeStsRegVal >> PadBitPosition) & 0x1;

  return Status;
}
