/** @file
  Install Iio Port Bifurcation Init Data.

  @copyright
  Copyright 2017 - 2021 Intel Corporation. <BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <PiPei.h>
#include <Upi/KtiHost.h>

#include <Guid/SetupVariable.h>
#include <Guid/PlatformInfo.h>
#include <Ppi/Smbus2.h>
#include <GpioInitData.h>

#include <Library/PeiServicesTablePointerLib.h>
#include <Library/GpioLib.h>
#include <Library/UbaIioConfigLib.h>
#include <Library/UbaPlatLib.h>
#include <Library/HobLib.h>
#include <Library/UbaIioPortBifurcationInitLib.h>
#include <Library/PchMultiPchBase.h>
#include <CpuAndRevisionDefines.h>

#include <Ppi/DynamicSiLibraryPpi.h>

#include "PeiCommonBoardInitLib.h"
#include "IioBifurcationSlotTable.h"


#define IIO_D_UBALOG(...)  { DEBUG((DEBUG_ERROR, "[IIO](UBA) " __VA_ARGS__)); }          // Important log always printed
#define IIO_D_UBADBG(...)  { DEBUG((DEBUG_INFO,  "[IIO](UBA) " __VA_ARGS__)); }          // Debug log, printed with BIOS debug jumper
#define IIO_D_UBAWARN(...) { DEBUG((DEBUG_ERROR, "[IIO](UBA) WARNING: " __VA_ARGS__)); } // Warning log, not necessary error
#define IIO_D_UBAERR(...)  { DEBUG((DEBUG_ERROR, "[IIO](UBA) ERROR: " __VA_ARGS__)); }   // Errorneous situation, probably bug or hw problem

EFI_PLATFORM_INFO*
GetPlatformInfoFromHob (
  VOID
  )
{
  EFI_HOB_GUID_TYPE               *GuidHob;

  GuidHob = GetFirstGuidHob (&gEfiPlatformInfoGuid);
  if (GuidHob == NULL) {
      ASSERT (FALSE);
      return NULL;
    }
  return GET_GUID_HOB_DATA (GuidHob);
}

/**
  Check if QAT-UPLINK override is needed

  @param[in]  Socket       Socket index
  @param[in]  Iou          IOU index
  @param[out] Bifurcation  Buffer for bifurcation needed to support QAT

  @return TRUE if custom bifurcation is necessary
 */
BOOLEAN
IsQatUplinkOverrideNeeded (
  IN  UINT8                       Socket,
  IN  UINT8                       Iou,
  IN  DYNAMIC_SI_LIBARY_PPI       *DynamicSiLibraryPpi,
  OUT UINT8                       *Bifurcation
  )
{
  EFI_PLATFORM_INFO               *PlatformInfo;
  UINT32                          Gpio;
  EFI_STATUS                      Status;

  PlatformInfo = GetPlatformInfoFromHob ();
  if (PlatformInfo == NULL) {
    return FALSE;
  }

  switch (PlatformInfo->BoardId) {
  case TypeWilsonCityRP:
  case TypeWilsonCityModular:
  case TypeWilsonCitySMT:
  case TypeWilsonCityPPV:
  case TypeWilsonPointRP:
  case TypeWilsonPointModular:
  case TypeCooperCityRP:
    if (Socket %2 == 0 && Iou == 3) {
      //
      // It is possible that special bifurcation for QAT-UPLINK if needed
      // It can be recognized by GPIO D8: 0 means that there is no UPLINK
      //
      Status = DynamicSiLibraryPpi->GpioGetInputValueByPchId (PCH_LEGACY_ID, GPIO_SKL_H_GPP_D8, &Gpio);
      if (EFI_ERROR (Status)) {
        return FALSE;
      }
      if (Gpio != 0) {
        *Bifurcation = IIO_BIFURCATE_xxx8xxx8;
        return TRUE;
      } else {
        return FALSE;
      }
    }
    break;
  }

  //
  // By default there is no QAT-UPLINK support
  //
  return FALSE;
}

/**
  Check if QAT-CABLE override is needed

  @param[in]  Socket       Socket index
  @param[in]  Iou          IOU index
  @param[out] Bifurcation  Buffer for bifurcation needed to support QAT

  @return TRUE if custom bifurcation is necessary
 */
BOOLEAN
IsQatCableOverrideNeeded (
  IN  UINT8                       Socket,
  IN  UINT8                       Iou,
  IN  DYNAMIC_SI_LIBARY_PPI       *DynamicSiLibraryPpi,
  OUT UINT8                       *Bifurcation
  )
{
  EFI_PLATFORM_INFO               *PlatformInfo;
  UINT32                          Gpio;
  EFI_STATUS                      Status;

  PlatformInfo = GetPlatformInfoFromHob ();
  if (PlatformInfo == NULL) {
    return FALSE;
  }

  switch (PlatformInfo->BoardId) {
  case TypeWilsonCityRP:
  case TypeWilsonCityModular:
  case TypeWilsonCitySMT:
  case TypeWilsonCityPPV:
  case TypeWilsonPointRP:
  case TypeWilsonPointModular:
    if (Iou == 4) {
      //
      // It is possible that special bifurcation for Oculink QAT cable is needed
      // Then cable is used to connect oculinks ports with QAT
      // To determine it if cable is needed GPIO-B3 needs to be examined
      // GPIO-B3 == 0 means that cable is present
      //
      Status = DynamicSiLibraryPpi->GpioGetInputValueByPchId (PCH_LEGACY_ID, GPIO_SKL_H_GPP_B3, &Gpio);
      if (EFI_ERROR (Status)) {
        return FALSE;
      }

      if (Gpio != 0) {
        //
        // There is no cable
        //
        return FALSE;
      }

      //
      // Cable is present to check which MEZZ IOU is used GPIO B4 needs to be examined
      // GPIO-B4 == 1: MEZZ IOU from Socket0 is used;
      // GPIO-B4 == 0: MEZZ IOU from Socket1 is used;
      //
      Status = DynamicSiLibraryPpi->GpioGetInputValueByPchId (PCH_LEGACY_ID, GPIO_SKL_H_GPP_B4, &Gpio);
      if (EFI_ERROR (Status)) {
        return FALSE;
      }

      if ((Socket == 0 && Gpio == 1) || (Socket == 1 && Gpio == 0)) {
        *Bifurcation = IIO_BIFURCATE_xxx8x4x4;
        return TRUE;
      }
    }
  } //switch: Board-Id

  //
  // By default there is no QAT-cable support
  //
  return FALSE;
}

/**
  Check if QAT override is needed.
  There are two possible QAT variants:
    - QAT uplink
    - QAT cable
  If LBG with QAT is detected (by GPIO read) custom bifurcation should be set.

  @param[in]  IioGlobalData  Pointer to IioGlobalData
 */
VOID
CheckQatBifurcationOverrides (
  IN  IIO_GLOBALS                 *IioGlobalData,
  IN  DYNAMIC_SI_LIBARY_PPI       *DynamicSiLibraryPpi
  )
{
  UINT8 Socket;
  UINT8 Iou;
  UINT8 Bifurcation;
  UINT8 StackIndex;

  for (Socket = 0; Socket < MAX_SOCKET; Socket++) {
    for (Iou = 0; Iou < MAX_IOU_PER_SOCKET; Iou++) {

      StackIndex = Iou+1;

      if (!DynamicSiLibraryPpi->IioIsSocketPresent (Socket)) {
        //
        // Skip sockets which are not present on the platform
        //
        continue;
      }

      if (!DynamicSiLibraryPpi->IioIsStackPresent (Socket, StackIndex)) {
        //
        // Skips stacks which are not present on the platform
        //
        continue;
      }
      //
      // Override if QAT is enabled
      //
      if (IsQatUplinkOverrideNeeded (Socket, Iou, DynamicSiLibraryPpi, &Bifurcation)) {
        if (IioGlobalData->SetupData.ConfigIOU[Socket][Iou] == IIO_BIFURCATE_AUTO) {
          IioGlobalData->SetupData.ConfigIOU[Socket][Iou] = Bifurcation;
          IIO_D_UBALOG ("[%d.%d] QAT-uplink detected; bif=%d\n", Socket, StackIndex, Bifurcation);
        }
      } else if (IsQatCableOverrideNeeded (Socket, Iou, DynamicSiLibraryPpi, &Bifurcation)) {
        if (IioGlobalData->SetupData.ConfigIOU[Socket][Iou] == IIO_BIFURCATE_AUTO) {
          IioGlobalData->SetupData.ConfigIOU[Socket][Iou] = Bifurcation;
          IIO_D_UBALOG ("[%d.%d] QAT-cable detected; bif=%d\n", Socket, StackIndex, Bifurcation);
        }
      }
    } // for each Iou
  } // for each Socket
}

/**
  Check if MRL sensor is present for given port.
  It depends on board architecture.
  Sometimes slot doesn't support it in contrast to Ext.Card.

  @param[in]  TotalPortIndex  Index of the port (across all ports on platform)
  @param[in]  ExtCardPresent  TRUE if Ext. card presence was detected

  @return TRUE if MRL sensor presence is expected
 */
BOOLEAN
IsMrlSensorPresent (
  IN  UINT16                      TotalPortIndex,
  IN  BOOLEAN                     ExtCardPresent
  )
{
  EFI_HOB_GUID_TYPE               *GuidHob;
  EFI_PLATFORM_INFO               *PlatformInfo;

  //
  // This data should be read from platform specific config (slot table IIO_BIFURCATION_DATA_ENTRY_EX)
  // but for now there is no such information
  // Let this be a temp. workaround until the update of UBA slot configs
  //

  if (ExtCardPresent) {
    //
    // MRL sensor is always present for Ext. Cards
    //
    return TRUE;
  }

  GuidHob = GetFirstGuidHob (&gEfiPlatformInfoGuid);
  if (GuidHob == NULL) {
    ASSERT (FALSE);
    return FALSE;
  }

  PlatformInfo  = GET_GUID_HOB_DATA (GuidHob);

  switch (PlatformInfo->BoardId) {
  case TypeWilsonCityRP:
    //
    // This is version for ICX
    //
    switch (TotalPortIndex) {
    case SOCKET_1_INDEX + PORT_1A_INDEX:
    case SOCKET_1_INDEX + PORT_1B_INDEX:
    case SOCKET_1_INDEX + PORT_1C_INDEX:
    case SOCKET_1_INDEX + PORT_1D_INDEX:
      //
      // only one slot on WilsonCity supports MRL
      //
      return TRUE;
    default:
      return FALSE;
    }
    break;

  case TypeCooperCityRP:
    //
    // Let's assume that MRL is supported by all slots on CooperCity - where HP is enabled!
    //
    return TRUE;
  default:
    //
    // For the rest of platforms the situation is unknown for now
    //
    return FALSE;
  }
}

/**
  Verify if VMD is enabled and override Slot configuration
  based on the VMD settings

  @param[in]  IioGlobalData           Pointer to Iio Globals.
  @param[in]  Slot                    Slot configuration settings
  @param[in]  SlotEntries             Number of slot entries
  @param[in]  ExtCardPresent          Table with results of Ext. Card detection
**/
#define  SLOT_NUM_OFFSET_FOR_EXT_CARD_SLOT      0x50

VOID
ConfigureSlots (
  IN  OUT IIO_GLOBALS                *IioGlobalData,
  IN  IIO_SLOT_CONFIG_DATA_ENTRY_EX  *Slot,
  IN  UINT8                          SlotEntries,
  IN  DYNAMIC_SI_LIBARY_PPI          *DynamicSiLibraryPpi,
  IN  BOOLEAN                        ExtCardPresent[MAX_SOCKET][MAX_IOU_PER_SOCKET]
  )
{
  UINT8     Index;
  UINT16    TotalPortIndex;
  UINT8     IioIndex = 0;
  UINT8     Stack;
  UINT8     PortIndex = 0;
  UINT8     Iou;
  BOOLEAN   IsHpSupportedBySlot;
  BOOLEAN   IsVmdEnabledForSlot = TRUE;
  BOOLEAN   IsExtCardInSlot = TRUE;
  BOOLEAN   AreLanesAssignedToPort = TRUE;

  for (Index = 0; Index < SlotEntries; Index ++) {

    TotalPortIndex = Slot[Index].PortIndex;
    if (DynamicSiLibraryPpi->GetMaxPortNumPerSocket() == 0 || DynamicSiLibraryPpi->GetMaxPortNumPerSocket() == 0) {
      return;
    }
    IioIndex = (UINT8) (TotalPortIndex / DynamicSiLibraryPpi->GetMaxPortNumPerSocket ());
    PortIndex = (UINT8) (TotalPortIndex - (IioIndex * DynamicSiLibraryPpi->GetMaxPortNumPerSocket ()));
    Stack = IioGlobalData->IioVar.IioVData.StackPerPort[IioIndex][PortIndex];
    Iou = Stack - 1;
    IsHpSupportedBySlot = FALSE;
    IsVmdEnabledForSlot = DynamicSiLibraryPpi->IsVMDEnabledForPort (IioIndex, PortIndex);
    IsExtCardInSlot = (Slot[Index].ExtensionCardSupport && ExtCardPresent[IioIndex][Iou]);
    AreLanesAssignedToPort = DynamicSiLibraryPpi->IioAreLanesAssignedToPort (IioGlobalData, IioIndex, PortIndex);

    if (Slot[Index].Hidden || !AreLanesAssignedToPort) {
      //
      // Hide rootports which are hidden in slots table or don't have any lanes assigned
      //
      IioGlobalData->SetupData.HidePEXPMenu[TotalPortIndex] = HIDE;
      IioGlobalData->SetupData.PEXPHIDE[TotalPortIndex] = HIDE;
    } else {
      //
      // Unhide active rootports (hided by default)
      //
      IioGlobalData->SetupData.HidePEXPMenu[TotalPortIndex] = NOT_HIDE;
      IioGlobalData->SetupData.PEXPHIDE[TotalPortIndex] = NOT_HIDE;
    }

    if (Slot[Index].SlotPowerLimitScale != PWR_SCL_MAX) {
      IioGlobalData->SetupData.SLOTSPLS[TotalPortIndex] = Slot[Index].SlotPowerLimitScale;
      IioGlobalData->SetupData.SLOTSPLV[TotalPortIndex] = Slot[Index].SlotPowerLimitValue;
    }

    if (Slot[Index].SlotNumber != NO_SLT_IMP) {
       IioGlobalData->SetupData.SLOTIMP[TotalPortIndex] = SLT_IMP;
       IioGlobalData->SetupData.SLOTPSP[TotalPortIndex] = Slot[Index].SlotNumber;
    } else if (AreLanesAssignedToPort) {
      //
      // If slot is not marked as implemented yet, but is exposed in current bifurcation
      // assume that there will be a slot
      //
      IioGlobalData->SetupData.SLOTIMP[TotalPortIndex] = 1;
      IioGlobalData->SetupData.SLOTPSP[TotalPortIndex] = SLOT_NUM_OFFSET_FOR_EXT_CARD_SLOT  + TotalPortIndex;
    }

    if (IsVmdEnabledForSlot) {
      //
      // Disable Electromechanical Interlock presence for VMD
      //
      IioGlobalData->SetupData.SLOTEIP[TotalPortIndex] = DISABLE;
    } else {
      IioGlobalData->SetupData.SLOTEIP[TotalPortIndex] = Slot[Index].InterLockPresent;
    }

    //
    // HotPlug related settings:
    //  - first check the presence of Ext. Card in the slot and apply specific setting
    //  - next apply common settings for HotPlug
    //
    if (IsExtCardInSlot && Slot[Index].ExtnCardHotPlugCapable) {
      IsHpSupportedBySlot = TRUE;
      //
      // Apply specific overrides for HP with Ext. Card
      //
      if (Slot[Index].ExtnCardHPVppPort != VPP_PORT_MAX) {
        IioGlobalData->SetupData.VppEnabled[TotalPortIndex] = TRUE;
        IioGlobalData->SetupData.VppPort[TotalPortIndex]    = Slot[Index].ExtnCardHPVppPort;
        IioGlobalData->SetupData.VppAddress[TotalPortIndex] = Slot[Index].ExtnCardHPVppAddress;
      }
    } else if (Slot[Index].HotPlugCapable) {
      //
      // HP settings when there is no Ext. Card
      //
      IsHpSupportedBySlot = TRUE;

      if (Slot[Index].VppPort != VPP_PORT_MAX) {
        IioGlobalData->SetupData.VppEnabled[TotalPortIndex] = TRUE;
        IioGlobalData->SetupData.VppPort[TotalPortIndex]    = Slot[Index].VppPort;
        IioGlobalData->SetupData.VppAddress[TotalPortIndex] = Slot[Index].VppAddress;
      }
    }

    if (IsHpSupportedBySlot) {
      //
      // There are common setting for HP
      //
      IioGlobalData->SetupData.SLOTHPCAP[TotalPortIndex] = ENABLE;
      IioGlobalData->SetupData.SLOTAIP[TotalPortIndex]   = ENABLE;   // Attention Indicator Present
      IioGlobalData->SetupData.SLOTPIP[TotalPortIndex]   = ENABLE;   // Power Indicator Present

      if (IsVmdEnabledForSlot) {
        IioGlobalData->SetupData.SLOTHPSUP[TotalPortIndex] = ENABLE; // HotPlug Surprise is always enabled for VMD
      }

      if (!IsVmdEnabledForSlot && IsMrlSensorPresent (TotalPortIndex, IsExtCardInSlot)) {
        //
        // Let's assume that if MRL sensor is present Attention button and Power Controller is also present
        //
        IioGlobalData->SetupData.SLOTMRLSP[TotalPortIndex] = ENABLE;
        IioGlobalData->SetupData.SLOTABP[TotalPortIndex]   = ENABLE;
        IioGlobalData->SetupData.SLOTPCP[TotalPortIndex]   = ENABLE;
      } else {
        IioGlobalData->SetupData.SLOTMRLSP[TotalPortIndex] = DISABLE;
        IioGlobalData->SetupData.SLOTABP[TotalPortIndex]   = DISABLE;
        IioGlobalData->SetupData.SLOTPCP[TotalPortIndex]   = DISABLE;
      }
    }
  } // for each slot entry
}

/**
  Check if lanes are reversed for given IOU - this is important when non-symetrical
  bifurcation is requested.

  @param[in]  BifurcationTable  record from bifurcation table

  @return TRUE if lanes are in reversed order
 */
BOOLEAN
IsLaneReversed (
  IN IIO_BIFURCATION_DATA_ENTRY_EX *BifurcationTable
  )
{
  EFI_HOB_GUID_TYPE               *GuidHob;
  EFI_PLATFORM_INFO               *PlatformInfo;

  //
  // This data should be read from platform specific config - but for now we don't have this information there
  // Let this be a temp. workaround until the update of biffurcation config.
  //

  GuidHob = GetFirstGuidHob (&gEfiPlatformInfoGuid);
  if (GuidHob == NULL) {
    ASSERT (FALSE);
    return FALSE;
  }

  PlatformInfo  = GET_GUID_HOB_DATA (GuidHob);

  switch (PlatformInfo->BoardId) {
    case TypeCooperCityRP:
    //
    // For CooperCityRP lane reversed are:
    // - on even sockets: on IOU4
    // - on odd sockets: on IOU1
    //
    return ((BifurcationTable->Socket&1) == 1 && BifurcationTable->IouNumber == Iio_Iou1);
    default:
      //
      // Let's assume that WilsonCity config is a default one.
      //
      //
      // This is version for ICX (default)
      //
      return (BifurcationTable->Socket == 1 &&
                   (BifurcationTable->IouNumber == Iio_Iou0 || BifurcationTable->IouNumber == Iio_Iou4));
  }
}

/**
  Read the bifurcation info stored at I/O Expander (PCA9554) which BIOS
  can get through Smbus.
  To communicate with IO Expander first MUX PCA9545 needs to be configured.

  The bifurcation encoding is [3:0]:
  BW5_BIFURCATE_x4x4x4x4  1 0 0 0
  BW5_BIFURCATE_x4x4xxx8  1 0 0 1 Lanes to port ACD on board
  BW5_BIFURCATE_xxx8x4x4  1 0 1 0 Lanes to port ABC on board
  BW5_BIFURCATE_xxx8xxx8  1 0 1 1 Lanes to port AC on board
  No BW5                  1 1 x x

  @param IioGlobalData - Pointer to IioGlobals
  @param BroadwayTablePtr         - Pointer to BroadwayTable

  @retval  IIO_BIFURCATE_xxxxxxxx  BW communication failure
  @retval  IIO_BIFURCATE_x4x4x4x4  Requested bifurcation x4x4x4x4
  @retval  IIO_BIFURCATE_x4x4xxx8  Requested bifurcation x4x4xxx8
  @retval  IIO_BIFURCATE_xxx8x4x4  Requested bifurcation xxx8x4x4
  @retval  IIO_BIFURCATE_xxx8xxx8  Requested bifurcation xxx8xxx8
**/
UINT8
GetBw5Bifurcation (
  IN  IIO_BIFURCATION_DATA_ENTRY_EX   *BifurcationTable,
  IN  IN DYNAMIC_SI_LIBARY_PPI        *DynamicSiLibraryPpi
  )
{
  EFI_STATUS                Status;
  EFI_SMBUS_DEVICE_ADDRESS  SmbDevAddr;
  EFI_PEI_SMBUS2_PPI        *SmbPpi = NULL;
  UINT16                    SmbData = 0;
  UINT8                     SmbByteData = 0;
  UINT8                     RetryCount;
  UINTN                     SmbLen = 2;
  UINTN                     SmbByteLen = 1;

  if (BifurcationTable->ExtnCardSMBusAddress == SMB_ADDR_MAX ) {
    return IIO_BIFURCATE_xxxxxxxx;
  }

  Status = PeiServicesLocatePpi (&gEfiPeiSmbus2PpiGuid, 0, NULL, (VOID **) &SmbPpi);

  // Initialize Bw5Id to not present

  if (Status != EFI_SUCCESS || SmbPpi == NULL) {
    DEBUG ((DEBUG_ERROR, "[Iio] Get SMBus protocol error %x\n", Status));
    return IIO_BIFURCATE_xxxxxxxx;
  }

  for (RetryCount = 0; RetryCount < NUM_OF_RETRIES; RetryCount++) {

    //
    // Configure through the smbus MUX PCA9545 to start communicate to GPIO expander
    //
    SmbDevAddr.SmbusDeviceAddress = BifurcationTable->MuxSMBusAddress >> 1;
    SmbByteData = BifurcationTable->MuxSMBusChannel;
    Status = SmbPpi->Execute (SmbPpi, SmbDevAddr, 0, EfiSmbusSendByte, FALSE, &SmbByteLen, &SmbByteData);
    if (EFI_ERROR (Status)) {
      continue;
    }

    //
    // Read the current I/O pins configuration for Port0
    //
    SmbDevAddr.SmbusDeviceAddress = BifurcationTable->ExtnCardSMBusAddress >> 1;
    Status = SmbPpi->Execute (SmbPpi, SmbDevAddr, BW4_CONFIG_OFFSET, EfiSmbusReadWord, FALSE, &SmbLen, &SmbData);
    if (!EFI_ERROR (Status)) {
     //
     // Configure the direction of I/O pins for Port0/Port1 as Input.
     //
      SmbData = SmbData | BW5_CONFIG_REG_MASK;
      Status = SmbPpi->Execute (SmbPpi, SmbDevAddr, BW4_CONFIG_OFFSET, EfiSmbusWriteWord, FALSE, &SmbLen, &SmbData);
      if (!EFI_ERROR (Status)) {
        //
        // Read Input Port0/Port1 register to identify BW5 Id
        //
        Status = SmbPpi->Execute (SmbPpi, SmbDevAddr, BW4_DATA_OFFSET, EfiSmbusReadWord, FALSE, &SmbLen, &SmbData);
        if (!EFI_ERROR (Status)){
          //
          // Mask the Input Port0/1 register data [15:0] to get BW5 ID [3:0].
          //
          // The bifurcation encoding is [3:0]:
          // BW5_BIFURCATE_x4x4x4x4  1 0 0 0
          // BW5_BIFURCATE_x4x4xxx8  1 0 0 1 Lanes to port ACD on board
          // BW5_BIFURCATE_xxx8x4x4  1 0 1 0 Lanes to port ABC on board
          // BW5_BIFURCATE_xxx8xxx8  1 0 1 1 Lanes to port AC on board
          // No BW5                  1 1 x x
          //
          switch (SmbData & 0xF) {
            case 0x8:
              return IIO_BIFURCATE_x4x4x4x4;
              break;
            case 0x9:
              if (IsLaneReversed (BifurcationTable)) {
                return IIO_BIFURCATE_xxx8x4x4;
              } else {
                return IIO_BIFURCATE_x4x4xxx8;
              }
              break;
            case 0xA:
              if (IsLaneReversed (BifurcationTable)) {
                return IIO_BIFURCATE_x4x4xxx8;
              } else {
                return IIO_BIFURCATE_xxx8x4x4;
              }
              break;
            case 0xB:
              return IIO_BIFURCATE_xxx8xxx8;
              break;
            default:
              return IIO_BIFURCATE_xxxxxxxx;
              break;
          }
        } // Read Port0
      } // Configure Port0
    } // Read Port0 Config

    //
    // Configure through the smbus MUX PCA9545 to end communicate to GPIO expander
    //
    SmbDevAddr.SmbusDeviceAddress = BifurcationTable->MuxSMBusAddress >> 1;
    SmbByteData &= ~BifurcationTable->MuxSMBusChannel;
    Status = SmbPpi->Execute (SmbPpi, SmbDevAddr, 0, EfiSmbusSendByte, FALSE, &SmbByteLen, &SmbByteData);
    if (EFI_ERROR (Status)) {
      continue;
    }
  } //RetryCount

  return IIO_BIFURCATE_xxxxxxxx;
}

/**
  Set bifurcation base of default settings and Ext. Card presence
  (detected by BW)

  @param[in]  IioGlobalData           Pointer to Iio Globals.
  @param[in]  BifurcationTable        Bifurcation configuration settings
  @param[in]  BifurcationEntries      Number of bif. table entries
  @param[in]  ExtCardPresent          Table with results of Ext. Card detection
**/
VOID
DetectExtCards (
  IN   IIO_GLOBALS                    *IioGlobalData,
  IN   IIO_BIFURCATION_DATA_ENTRY_EX  *BifurcationTable,
  IN   UINT8                          BifurcationEntries,
  IN   DYNAMIC_SI_LIBARY_PPI          *DynamicSiLibraryPpi,
  OUT  BOOLEAN                        ExtCardPresent[MAX_SOCKET][MAX_IOU_PER_SOCKET]
  )
{
  UINT8  Index;
  UINT8  BwBifurcation;
  UINT8  Socket;
  UINT8  Iou;

  //
  // Try to communicate with Ext.Card which can request custom bifurcation on the stack
  //
  for (Index = 0; Index < BifurcationEntries; Index++){

    Socket = BifurcationTable[Index].Socket;
    Iou = BifurcationTable[Index].IouNumber;

    ExtCardPresent[Socket][Iou] = FALSE; // by default

    if (BifurcationTable[Index].ExtnCardSMBusAddress != SMB_ADDR_MAX) {
      //
      // ExtCard can be supported at this stack - try to detect it
      //
      BwBifurcation = GetBw5Bifurcation (&BifurcationTable[Index], DynamicSiLibraryPpi);
      if (BwBifurcation != IIO_BIFURCATE_xxxxxxxx) {
        //
        // Ext. Card is detected on this IOU
        //
        IIO_D_UBALOG ("[%d.%d] Ext. Card detected; bif=%d\n", Socket, Iou+1, BwBifurcation);
        ExtCardPresent[Socket][Iou] = TRUE;
      }
    }

    //
    // Set custom bifurcation requested by Ext. Card
    //
    if (IioGlobalData->SetupData.ConfigIOU[Socket][Iou] == IIO_BIFURCATE_AUTO) {
      //
      // There are no overrides for this IOU
      //
      if (ExtCardPresent[Socket][Iou]) {
        //
        // Set bifurcation requested by Ext. Card
        //
        IioGlobalData->SetupData.ConfigIOU[Socket][Iou] = BwBifurcation;
      } else {
        //
        // Set the default bifurcation for this IOU
        //
        IioGlobalData->SetupData.ConfigIOU[Socket][Iou] = BifurcationTable[Index].Bifurcation;
      }
    }

    IIO_D_UBADBG ("[%d.%d] final bifurcation = %X\n", Socket, Iou+1, IioGlobalData->SetupData.ConfigIOU[Socket][Iou]);

  } // for bifurcation table entry
}

/**
  Dump prepared configuration to log.

  @param[in]  SetupData  Pointer to SetupData structure
**/
VOID
DumpConfiguration (
  IN IIO_CONFIG                  *SetupData,
  IN DYNAMIC_SI_LIBARY_PPI       *DynamicSiLibraryPpi
  )
{
  UINT8  IioIndex;
  UINT8  StackIndex = 0;
  UINT8  Iou;
  UINT8  PortIndex = 0;
  UINT8  PortInStackIndex = 0;
  UINT16 TotalPortIndex =0;

  for (IioIndex = 0; IioIndex < MAX_SOCKET ; IioIndex++) {

    if (!DynamicSiLibraryPpi->IioIsSocketPresent (IioIndex)) {
      //
      // Skips sockets which are not present on the platform
      //
      continue;
    }
    for (StackIndex = 0; StackIndex <  DynamicSiLibraryPpi->GetMaxStackNumPerSocket (); StackIndex++) {

      if (DynamicSiLibraryPpi->IsDmiStack (StackIndex)) {
        //
        // Skip DMI stacks in dump
        //
        continue;
      }

      if (!DynamicSiLibraryPpi->IioIsStackPresent (IioIndex, StackIndex)) {
        //
        // Skips stacks which are not present on the platform
        //
        continue;
      }

      if (StackIndex >= MAX_IIO_STACK) {
        break;
      }
      Iou = StackIndex - 1;

      IIO_D_UBALOG("[%d.%d] Bifurcation = %X\n", IioIndex, StackIndex, SetupData->ConfigIOU[IioIndex][Iou]);
      IIO_D_UBALOG ("  [ IDX ], PortHide, SlotImpl, SlotNumber, HotPlug,  Vpp,  Interlock\n");

      for (PortInStackIndex = 0; PortInStackIndex < DynamicSiLibraryPpi->GetMaxPortNumPerStack (StackIndex); PortInStackIndex++) {
        PortIndex = DynamicSiLibraryPpi->GetPortIndexbyStack (StackIndex, PortInStackIndex);
        TotalPortIndex = (IioIndex * DynamicSiLibraryPpi->GetMaxPortNumPerSocket ()) + PortIndex;
        IIO_D_UBALOG ("  [%d p%02d]    %2d    |   %2d    |   %3d     |   %3d  |  0x%02x   |  %2d      \n",
                       IioIndex, PortIndex,
                       SetupData->PEXPHIDE[TotalPortIndex],
                       SetupData->SLOTIMP[TotalPortIndex],
                       SetupData->SLOTPSP[TotalPortIndex],
                       SetupData->SLOTHPCAP[TotalPortIndex],
                       SetupData->VppAddress[TotalPortIndex] | SetupData->VppPort[TotalPortIndex],
                       SetupData->SLOTEIP[TotalPortIndex]);
      } // foreach PortInStack
    } //foreach StackIndex
  } // foreach IioIndex
}

/**
  This function prepare the data for silicon initialization based on
  bifurcations and slots table

  This function is for tables in version PLATFORM_IIO_CONFIG_UPDATE_VERSION = 2

  @param[in]  IioGlobalData  IIO Global data structure
  @param[in]  IioConfigTable
 */
VOID
IioPortBifurcationInitVer2 (
  IN  IIO_GLOBALS                          *IioGlobalData,
  IN  PLATFORM_IIO_CONFIG_UPDATE_TABLE_EX  IioConfigTable,
  IN  DYNAMIC_SI_LIBARY_PPI                *DynamicSiLibraryPpi
  )
{
  IIO_BIFURCATION_DATA_ENTRY_EX   *BifurcationTableEx;
  UINT8                           BifurcationEntries;
  IIO_SLOT_CONFIG_DATA_ENTRY_EX   *SlotTableEx;
  UINT8                           SlotEntries;
  BOOLEAN                         ExtCardDetected[MAX_SOCKET][MAX_IOU_PER_SOCKET];

  //
  // Init bifurcation and slots tables
  //
  BifurcationTableEx = IioConfigTable.IioBifurcationTablePtr;
  BifurcationEntries = (UINT8) (IioConfigTable.IioBifurcationTableSize / sizeof (IIO_BIFURCATION_DATA_ENTRY_EX));
  SlotTableEx = IioConfigTable.IioSlotTablePtr;
  SlotEntries = (UINT8) (IioConfigTable.IioSlotTableSize / sizeof (IIO_SLOT_CONFIG_DATA_ENTRY_EX));

  //
  // Set the bifurcations for each IOU:
  //  - if any override is set in setup menu - stay bifurcation not changed
  //  - next check if QAT is detected (this is LBG specific) - QAT requires special bifurcation
  //  - next try to detect ext. cards with BW bifurcation
  //  - at the end set default bifurcation if no other options wer applied before

  if (DynamicSiLibraryPpi->IsCpuAndRevision (CPU_ICXSP, REV_ALL)   ||
      DynamicSiLibraryPpi->IsCpuAndRevision (CPU_CPX, REV_ALL)   ||
      DynamicSiLibraryPpi->IsCpuAndRevision (CPU_SKX, REV_ALL)   ||
      DynamicSiLibraryPpi->IsCpuAndRevision (CPU_CLX, REV_ALL)
  ) {
    //
    // This is important only for programs with LBG PCH
    //
    CheckQatBifurcationOverrides (IioGlobalData, DynamicSiLibraryPpi);
  }
  DetectExtCards (IioGlobalData, BifurcationTableEx, BifurcationEntries, DynamicSiLibraryPpi, ExtCardDetected);

  //
  // Set each rootport settings based on slots table
  //
  ConfigureSlots (IioGlobalData, SlotTableEx, SlotEntries, DynamicSiLibraryPpi, ExtCardDetected);

  //
  // Configuration is ready - dump the config to log
  //
  DumpConfiguration (&IioGlobalData->SetupData, DynamicSiLibraryPpi);
}

/**

  Program the IIO_GLOBALS data structure with OEM IIO init values for SLOTs and Bifurcation.

  @param mSB - pointer to this protocol
  @param IioUds - Pointer to the IIO UDS data structure.

  @retval EFI_SUCCESS
**/
VOID
IioPortBifurcationInitCallback (
  IN IIO_GLOBALS *IioGlobalData
  )
{
  EFI_STATUS                            Status;
  UBA_CONFIG_DATABASE_PPI               *UbaConfigPpi = NULL;
  DYNAMIC_SI_LIBARY_PPI                 *DynamicSiLibraryPpi = NULL;
  PLATFORM_IIO_CONFIG_UPDATE_TABLE_EX   IioConfigTable;
  UINTN                                 TableSize;

  //
  // Locate PLATFORM_IIO_CONFIG_UPDATE_TABLE_EX
  //
  Status = PeiServicesLocatePpi (&gUbaConfigDatabasePpiGuid, 0, NULL, (VOID **) &UbaConfigPpi);
  if (EFI_ERROR (Status)) {
    ASSERT (FALSE);
    return;
  }

  Status = PeiServicesLocatePpi (&gDynamicSiLibraryPpiGuid, 0, NULL, (VOID **) &DynamicSiLibraryPpi);
  if (EFI_ERROR (Status)) {
    ASSERT_EFI_ERROR (Status);
    return;
  }

  TableSize = sizeof (IioConfigTable);
  Status = UbaConfigPpi->GetData (UbaConfigPpi, &gPlatformIioConfigDataGuid, &IioConfigTable, &TableSize);
  if (EFI_ERROR (Status)) {
    ASSERT (FALSE);
    return;
  }

  ASSERT (IioConfigTable.Signature == PLATFORM_IIO_CONFIG_UPDATE_SIGNATURE);

  //
  // Call the right version of bifurcation init procedure
  //
  switch (IioConfigTable.Version) {
  case PLATFORM_IIO_CONFIG_UPDATE_VERSION_2:
    IioPortBifurcationInitVer2 (IioGlobalData, IioConfigTable, DynamicSiLibraryPpi);
    break;
  case PLATFORM_IIO_CONFIG_UPDATE_VERSION:
    IioPortBifurcationInitVer1 (IioGlobalData);
    break;
  default:
    ASSERT (FALSE);
    break;
  }
}

IIO_PORT_BIFURCATION_INIT_TABLE IioPortBifurcationInitTable =
  {
    IIO_PORT_BIFURCATION_INIT_SIGNATURE,
    IIO_PORT_BIFURCATION_INIT_VERSION,
    IioPortBifurcationInitCallback
  };

EFI_STATUS
InstallIioPortBifurcationInitData (
  IN UBA_CONFIG_DATABASE_PPI    *UbaConfigPpi
  )
{
  return UbaConfigPpi->AddData (
           UbaConfigPpi,
           &gIioPortBifurcationInitDataGuid,
           &IioPortBifurcationInitTable,
           sizeof (IioPortBifurcationInitTable)
           );
}
