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

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

**/

#include <Base.h>
#include <Uefi/UefiBaseType.h>
#include <Library/IoLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseLib.h>
#include <PchAccess.h>
#include <Library/PchInfoLib.h>
#include <Library/PchPcrLib.h>
#include <Library/PchHsioLib.h>

/**
  The function returns the Port Id and lane owner for the specified lane

  @param[in]  PhyMode             Phymode that needs to be checked
  @param[out] PortId              Common Lane End Point ID
  @param[out] LaneOwner           Lane Owner

  @retval EFI_SUCCESS             Read success
  @retval EFI_INVALID_PARAMETER   Invalid lane number
**/
EFI_STATUS
EFIAPI
PchGetLaneInfo (
  IN  UINT32                            LaneNum,
  OUT UINT8                             *PortId,
  OUT UINT8                             *LaneOwner
  )
{
  PCH_SERIES      PchSeries;
  PCH_GENERATION  PchGeneration;
  UINT32 Los1;
  UINT32 Los2;
  UINT32 Los3;
  UINT32 Los4;

  Los1 = 0;
  Los2 = 0;
  Los3 = 0;
  Los4 = 0;

  PchSeries = GetPchSeries ();
  PchGeneration = GetPchGeneration ();

  if (((LaneNum > 15) && (PchSeries == PchLp)) ||
      ((LaneNum > 29) && (PchSeries == PchH) && (PchGeneration == SklPch)) ||
      (LaneNum > 33) ) {
    return EFI_INVALID_PARAMETER;
  } else if (LaneNum < 8) {
    PchPcrRead32 (PID_FIA, R_PCH_PCR_FIA_LOS1_REG_BASE, &Los1);
  } else if (LaneNum < 16) {
    PchPcrRead32 (PID_FIA, R_PCH_PCR_FIA_LOS2_REG_BASE, &Los2);
  } else if (LaneNum < 24) {
    PchPcrRead32 (PID_FIA, R_PCH_PCR_FIA_LOS3_REG_BASE, &Los3);
  } else {
    PchPcrRead32 (PID_FIA, R_PCH_PCR_FIA_LOS4_REG_BASE, &Los4);
  }

  if (PchSeries == PchLp) {
    switch (LaneNum) {
      case 0:
        *LaneOwner = (UINT8) ((Los1 & B_PCH_PCR_FIA_L0O));
        *PortId = PID_MODPHY0;
        break;
      case 1:
        *LaneOwner = (UINT8) ((Los1 & B_PCH_PCR_FIA_L1O) >>4);
        *PortId = PID_MODPHY0;
        break;
      case 2:
        *LaneOwner = (UINT8) ((Los1 & B_PCH_PCR_FIA_L2O) >>8);
        *PortId = PID_MODPHY0;
        break;
      case 3:
        *LaneOwner = (UINT8) ((Los1 & B_PCH_PCR_FIA_L3O) >>12);
        *PortId = PID_MODPHY0;
        break;
      case 4:
        *LaneOwner = (UINT8) ((Los1 & B_PCH_PCR_FIA_L4O) >>16);
        *PortId = PID_MODPHY0;
        break;
      case 5:
        *LaneOwner = (UINT8) ((Los1 & B_PCH_PCR_FIA_L5O) >>20);
        *PortId = PID_MODPHY0;
        break;
      case 6:
        *LaneOwner = (UINT8) ((Los1 & B_PCH_PCR_FIA_L6O) >>24);
        *PortId = PID_MODPHY1;
        break;
      case 7:
        *LaneOwner = (UINT8) ((Los1 & B_PCH_PCR_FIA_L7O) >>28);
        *PortId = PID_MODPHY1;
        break;
      case 8:
        *LaneOwner = (UINT8) (Los2 & B_PCH_PCR_FIA_L8O);
        *PortId = PID_MODPHY1;
        break;
      case 9:
        *LaneOwner = (UINT8) ((Los2 & B_PCH_PCR_FIA_L9O) >>4);
        *PortId = PID_MODPHY1;
        break;
      case 10:
        *LaneOwner = (UINT8) ((Los2 & B_PCH_PCR_FIA_L10O) >>8);
        *PortId = PID_MODPHY2;
        break;
      case 11:
        *LaneOwner = (UINT8) ((Los2 & B_PCH_PCR_FIA_L11O) >>12);
        *PortId = PID_MODPHY2;
        break;
      case 12:
        *LaneOwner = (UINT8) ((Los2 & B_PCH_PCR_FIA_L12O) >>16);
        *PortId = PID_MODPHY2;
        break;
      case 13:
        *LaneOwner = (UINT8) ((Los2 & B_PCH_PCR_FIA_L13O) >>20);
        *PortId = PID_MODPHY2;
        break;
      case 14:
        *LaneOwner = (UINT8) ((Los2 & B_PCH_PCR_FIA_L14O) >>24);
        *PortId = PID_MODPHY2;
        break;
      case 15:
        *LaneOwner = (UINT8) ((Los2 & B_PCH_PCR_FIA_L15O) >>28);
        *PortId = PID_MODPHY2;
        break;
    }
  } else {
    switch (LaneNum) {
      case 0:
        *LaneOwner = (UINT8) ((Los1 & B_PCH_PCR_FIA_L0O));
        *PortId = PID_MODPHY0;
        break;
      case 1:
        *LaneOwner = (UINT8) ((Los1 & B_PCH_PCR_FIA_L1O) >>4);
        *PortId = PID_MODPHY0;
        break;
      case 2:
        *LaneOwner = (UINT8) ((Los1 & B_PCH_PCR_FIA_L2O) >>8);
        *PortId = PID_MODPHY0;
        break;
      case 3:
        *LaneOwner = (UINT8) ((Los1 & B_PCH_PCR_FIA_L3O) >>12);
        *PortId = PID_MODPHY0;
        break;
      case 4:
        *LaneOwner = (UINT8) ((Los1 & B_PCH_PCR_FIA_L4O) >>16);
        *PortId = PID_MODPHY0;
        break;
      case 5:
        *LaneOwner = (UINT8) ((Los1 & B_PCH_PCR_FIA_L5O) >>20);
        *PortId = PID_MODPHY0;
        break;
      case 6:
        *LaneOwner = (UINT8) ((Los1 & B_PCH_PCR_FIA_L6O) >>24);
        *PortId = PID_MODPHY1;
        break;
      case 7:
        *LaneOwner = (UINT8) ((Los1 & B_PCH_PCR_FIA_L7O) >>28);
        *PortId = PID_MODPHY1;
        break;
      case 8:
        *LaneOwner = (UINT8) (Los2 & B_PCH_PCR_FIA_L8O);
        *PortId = PID_MODPHY1;
        break;
      case 9:
        *LaneOwner = (UINT8) ((Los2 & B_PCH_PCR_FIA_L9O) >>4);
        *PortId = PID_MODPHY1;
        break;
      case 10:
        *LaneOwner = (UINT8) ((Los2 & B_PCH_PCR_FIA_L10O) >>8);
        *PortId = PID_MODPHY1;
        break;
      case 11:
        *LaneOwner = (UINT8) ((Los2 & B_PCH_PCR_FIA_L11O) >>12);
        *PortId = PID_MODPHY1;
        break;
      case 12:
        *LaneOwner = (UINT8) ((Los2 & B_PCH_PCR_FIA_L12O) >>16);
        *PortId = PID_MODPHY1;
        break;
      case 13:
        *LaneOwner = (UINT8) ((Los2 & B_PCH_PCR_FIA_L13O) >>20);
        *PortId = PID_MODPHY1;
        break;
      case 14:
        *LaneOwner = (UINT8) ((Los2 & B_PCH_PCR_FIA_L14O) >>24);
        *PortId = PID_MODPHY2;
        break;
      case 15:
        *LaneOwner = (UINT8) ((Los2 & B_PCH_PCR_FIA_L15O) >>28);
        *PortId = PID_MODPHY2;
        break;
      case 16:
        *LaneOwner = (UINT8) (Los3 & B_PCH_PCR_FIA_L16O);
        *PortId = PID_MODPHY2;
        break;
      case 17:
        *LaneOwner = (UINT8) ((Los3 & B_PCH_PCR_FIA_L17O) >>4);
        *PortId = PID_MODPHY2;
        break;
      case 18:
        *LaneOwner = (UINT8) ((Los3 & B_PCH_PCR_FIA_L18O) >>8);
        *PortId = PID_MODPHY2;
        break;
      case 19:
        *LaneOwner = (UINT8) ((Los3 & B_PCH_PCR_FIA_L19O) >>12);
        *PortId = PID_MODPHY2;
        break;
      case 20:
        *LaneOwner = (UINT8) ((Los3 & B_PCH_PCR_FIA_L20O) >>16);
        *PortId = PID_MODPHY2;
        break;
      case 21:
        *LaneOwner = (UINT8) ((Los3 & B_PCH_PCR_FIA_L21O) >>20);
        *PortId = PID_MODPHY2;
        break;
      case 22:
        *LaneOwner = (UINT8) ((Los3 & B_PCH_PCR_FIA_L22O) >>24);
        *PortId = PID_MODPHY3;
        break;
      case 23:
        *LaneOwner = (UINT8) ((Los3 & B_PCH_PCR_FIA_L23O) >>28);
        *PortId = PID_MODPHY3;
        break;
      case 24:
        *LaneOwner = (UINT8) (Los4 & B_PCH_PCR_FIA_L24O);
        *PortId = PID_MODPHY3;
        break;
      case 25:
        *LaneOwner = (UINT8) ((Los4 & B_PCH_PCR_FIA_L25O) >>4);
        *PortId = PID_MODPHY3;
        break;
      case 26:
        *LaneOwner = (UINT8) ((Los4 & B_PCH_PCR_FIA_L26O) >>8);
        *PortId = PID_MODPHY3;
        break;
      case 27:
        *LaneOwner = (UINT8) ((Los4 & B_PCH_PCR_FIA_L27O) >>12);
        *PortId = PID_MODPHY3;
        break;
      case 28:
        *LaneOwner = (UINT8) ((Los4 & B_PCH_PCR_FIA_L28O) >>16);
        *PortId = PID_MODPHY3;
        break;
      case 29:
        *LaneOwner = (UINT8) ((Los4 & B_PCH_PCR_FIA_L29O) >>20);
        *PortId = PID_MODPHY3;
        break;
      case 30:
      case 31:
      case 32:
      case 33:
        //
        // Lanes 30-33 don't have ownership registers and are always configured to PCIE
        //
        *LaneOwner = V_PCH_PCR_FIA_LANE_OWN_PCIEDMI;
        *PortId = PID_MODPHY4;
          break;
    }
  }
  return EFI_SUCCESS;
}

/**
  Determine the lane number of a specified port

  @param[out] LaneNum                   GBE Lane Number

  @retval EFI_SUCCESS                   Lane number valid.
  @retval EFI_UNSUPPORTED               Incorrect input device port
**/
EFI_STATUS
PchGetGbeLaneNum (
  UINT8               *LaneNum
  )
{
  PCH_SERIES  PchSeries;
  UINT32 Los1;
  UINT32 Los2;
  UINT32 Los3;

  PchSeries = GetPchSeries ();
  PchPcrRead32 (PID_FIA, R_PCH_PCR_FIA_LOS2_REG_BASE, &Los2);

  if (PchSeries == PchLp) {
    PchPcrRead32 (PID_FIA, R_PCH_PCR_FIA_LOS1_REG_BASE, &Los1);
    if (((Los1 & B_PCH_PCR_FIA_L6O) >> 24) == V_PCH_PCR_FIA_LANE_OWN_GBE) {
      *LaneNum = 6;
      return EFI_SUCCESS;
    } else if (((Los1 & B_PCH_PCR_FIA_L7O) >> 28) == V_PCH_PCR_FIA_LANE_OWN_GBE) {
      *LaneNum = 7;
      return EFI_SUCCESS;
    } else if ((Los2 & B_PCH_PCR_FIA_L8O) == V_PCH_PCR_FIA_LANE_OWN_GBE) {
      *LaneNum = 8;
      return EFI_SUCCESS;
    } else if (((Los2 & B_PCH_PCR_FIA_L12O) >> 16) == V_PCH_PCR_FIA_LANE_OWN_GBE) {
      *LaneNum = 12;
      return EFI_SUCCESS;
    } else if (((Los2 & B_PCH_PCR_FIA_L13O) >> 20) == V_PCH_PCR_FIA_LANE_OWN_GBE) {
      *LaneNum = 13;
      return EFI_SUCCESS;
    }
  } else {
    PchPcrRead32 (PID_FIA, R_PCH_PCR_FIA_LOS3_REG_BASE, &Los3);
    if (((Los2 & B_PCH_PCR_FIA_L9O) >> 4) == V_PCH_PCR_FIA_LANE_OWN_GBE) {
      *LaneNum = 9;
      return EFI_SUCCESS;
    } else if (((Los2 & B_PCH_PCR_FIA_L10O) >> 8) == V_PCH_PCR_FIA_LANE_OWN_GBE) {
      *LaneNum = 10;
      return EFI_SUCCESS;
    } else if (((Los3 & B_PCH_PCR_FIA_L18O) >> 8) == V_PCH_PCR_FIA_LANE_OWN_GBE) {
      *LaneNum = 18;
      return EFI_SUCCESS;
    } else if (((Los3 & B_PCH_PCR_FIA_L21O) >> 20) == V_PCH_PCR_FIA_LANE_OWN_GBE) {
      *LaneNum = 21;
      return EFI_SUCCESS;
    } else if (((Los3 & B_PCH_PCR_FIA_L22O) >> 24) == V_PCH_PCR_FIA_LANE_OWN_GBE) {
      *LaneNum = 22;
      return EFI_SUCCESS;
    }
  }
  return EFI_UNSUPPORTED;
}

/**
  Determine the lane number of a specified port

  @param[in]  Usb3LaneIndex             USB3 Lane Index
  @param[out] LaneNum                   Lane Number

  @retval EFI_SUCCESS                   Lane number valid.
  @retval EFI_UNSUPPORTED               Incorrect input device port
**/
EFI_STATUS
PchGetUsb3LaneNum (
  UINT32              Usb3LaneIndex,
  UINT8               *LaneNum
  )
{
  PCH_SERIES  PchSeries;
  UINT32 Los1;
  UINT32 Los2;
  UINT32 Los3;
  UINT32 Los4;

  PchSeries = GetPchSeries ();
  PchPcrRead32 (PID_FIA, R_PCH_PCR_FIA_LOS1_REG_BASE, &Los1);
  PchPcrRead32 (PID_FIA, R_PCH_PCR_FIA_LOS2_REG_BASE, &Los2);

  if (PchSeries == PchLp) {
    switch (Usb3LaneIndex) {
      case 0:
        if ((Los1 & B_PCH_PCR_FIA_L0O) == V_PCH_PCR_FIA_LANE_OWN_USB3) {
          *LaneNum = 0;
          return EFI_SUCCESS;
        }
        break;
      case 1:
        if (((Los1 & B_PCH_PCR_FIA_L1O) >> 4) == V_PCH_PCR_FIA_LANE_OWN_USB3) {
          *LaneNum = 1;
          return EFI_SUCCESS;
        }
        break;
      case 2:
        if (((Los1 & B_PCH_PCR_FIA_L2O) >> 8) == V_PCH_PCR_FIA_LANE_OWN_USB3) {
          *LaneNum = 2;
          return EFI_SUCCESS;
        }
        break;
      case 3:
        if (((Los1 & B_PCH_PCR_FIA_L3O) >> 12) == V_PCH_PCR_FIA_LANE_OWN_USB3) {
          *LaneNum = 3;
          return EFI_SUCCESS;
        }
        break;
      case 4:
        if (((Los1 & B_PCH_PCR_FIA_L4O) >> 16) == V_PCH_PCR_FIA_LANE_OWN_USB3) {
          *LaneNum = 4;
          return EFI_SUCCESS;
        }
        break;
      case 5:
        if (((Los1 & B_PCH_PCR_FIA_L5O) >> 20) == V_PCH_PCR_FIA_LANE_OWN_USB3) {
          *LaneNum = 5;
          return EFI_SUCCESS;
        }
        break;
      default:
        DEBUG ((DEBUG_ERROR, "Unsupported USB3 Lane Index"));
        ASSERT (FALSE);
        return EFI_UNSUPPORTED;
        break;
    }
  } else {
    PchPcrRead32 (PID_FIA, R_PCH_PCR_FIA_LOS3_REG_BASE, &Los3);
    PchPcrRead32 (PID_FIA, R_PCH_PCR_FIA_LOS4_REG_BASE, &Los4);
    switch (Usb3LaneIndex) {
      case 0:
        if ((Los1 & B_PCH_PCR_FIA_L0O) == V_PCH_PCR_FIA_LANE_OWN_USB3) {
          *LaneNum = 0;
          return EFI_SUCCESS;
        }
        break;
      case 1:
        if (((Los1 & B_PCH_PCR_FIA_L1O) >> 4) == V_PCH_PCR_FIA_LANE_OWN_USB3) {
          *LaneNum = 1;
          return EFI_SUCCESS;
        }
        break;
      case 2:
        if (((Los1 & B_PCH_PCR_FIA_L2O) >> 8) == V_PCH_PCR_FIA_LANE_OWN_USB3) {
          *LaneNum = 2;
          return EFI_SUCCESS;
        }
        break;
      case 3:
        if (((Los1 & B_PCH_PCR_FIA_L3O) >> 12) == V_PCH_PCR_FIA_LANE_OWN_USB3) {
          *LaneNum = 3;
          return EFI_SUCCESS;
        }
        break;
      case 4:
        if (((Los1 & B_PCH_PCR_FIA_L4O) >> 16) == V_PCH_PCR_FIA_LANE_OWN_USB3) {
          *LaneNum = 4;
          return EFI_SUCCESS;
        }
        break;
      case 5:
        if (((Los1 & B_PCH_PCR_FIA_L5O) >> 20) == V_PCH_PCR_FIA_LANE_OWN_USB3) {
          *LaneNum = 5;
          return EFI_SUCCESS;
        }
        break;
      case 6:
        if (((Los1 & B_PCH_PCR_FIA_L6O) >> 24) == V_PCH_PCR_FIA_LANE_OWN_USB3) {
          *LaneNum = 6;
          return EFI_SUCCESS;
        }
        break;
      case 7:
        if (((Los1 & B_PCH_PCR_FIA_L7O) >> 28) == V_PCH_PCR_FIA_LANE_OWN_USB3) {
          *LaneNum = 7;
          return EFI_SUCCESS;
        }
        break;
      case 8:
        if ((Los2 & B_PCH_PCR_FIA_L8O) == V_PCH_PCR_FIA_LANE_OWN_USB3) {
          *LaneNum = 8;
          return EFI_SUCCESS;
        }
        break;
      case 9:
        if (((Los2 & B_PCH_PCR_FIA_L9O) >> 4) == V_PCH_PCR_FIA_LANE_OWN_USB3) {
          *LaneNum = 9;
          return EFI_SUCCESS;
        }
        break;
      default:
        DEBUG ((DEBUG_ERROR, "Unsupported USB3 Lane Index"));
        ASSERT (FALSE);
        return EFI_UNSUPPORTED;
        break;
    }
  }
  return EFI_UNSUPPORTED;
}

/**
  Determine the lane number of a specified port

  @param[in]  SataLaneIndex             Sata Lane Index
  @param[out] LaneNum                   Lane Number

  @retval EFI_SUCCESS                   Lane number valid.
  @retval EFI_UNSUPPORTED               Incorrect input device port
**/
EFI_STATUS
PchGetSataLaneNum (
  UINT32              SataLaneIndex,
  UINT8               *LaneNum
  )
{
  PCH_SERIES  PchSeries;
  UINT32 Los1;
  UINT32 Los2;
  UINT32 Los3;
  UINT32 Los4;

  PchSeries = GetPchSeries ();
  PchPcrRead32 (PID_FIA, R_PCH_PCR_FIA_LOS1_REG_BASE, &Los1);
  PchPcrRead32 (PID_FIA, R_PCH_PCR_FIA_LOS2_REG_BASE, &Los2);

  if (PchSeries == PchLp) {
    switch (SataLaneIndex) {
      case 0:
        if (((Los2 & B_PCH_PCR_FIA_L10O) >> 8) == V_PCH_PCR_FIA_LANE_OWN_SATA) {
          *LaneNum = 10;
          return EFI_SUCCESS;
        }
        break;
      case 1:
        if (((Los2 & B_PCH_PCR_FIA_L11O) >> 12) == V_PCH_PCR_FIA_LANE_OWN_SATA) {
          *LaneNum = 11;
          return EFI_SUCCESS;
        } else if (((Los2 & B_PCH_PCR_FIA_L14O) >> 24) == V_PCH_PCR_FIA_LANE_OWN_SATA) {
          *LaneNum = 14;
          return EFI_SUCCESS;
        }
        break;
      case 2:
        if (((Los2 & B_PCH_PCR_FIA_L15O) >> 28) == V_PCH_PCR_FIA_LANE_OWN_SATA) {
          *LaneNum = 15;
          return EFI_SUCCESS;
        }
        break;
      default:
        DEBUG ((DEBUG_ERROR, "Unsupported SATA Lane Index"));
        ASSERT (FALSE);
        return EFI_UNSUPPORTED;
        break;
    }
  } else {
    PchPcrRead32 (PID_FIA, R_PCH_PCR_FIA_LOS3_REG_BASE, &Los3);
    PchPcrRead32 (PID_FIA, R_PCH_PCR_FIA_LOS4_REG_BASE, &Los4);
    switch (SataLaneIndex) {
      case 0:
        if (((Los3 & B_PCH_PCR_FIA_L18O) >> 8) == V_PCH_PCR_FIA_LANE_OWN_SATA) {
          *LaneNum = 18;
          return EFI_SUCCESS;
        } else if (((Los3 & B_PCH_PCR_FIA_L22O) >> 24) == V_PCH_PCR_FIA_LANE_OWN_SATA) {
          *LaneNum = 22;
          return EFI_SUCCESS;
        }
        break;
      case 1:
        if (((Los3 & B_PCH_PCR_FIA_L19O) >> 12) == V_PCH_PCR_FIA_LANE_OWN_SATA) {
          *LaneNum = 19;
          return EFI_SUCCESS;
        } else if (((Los3 & B_PCH_PCR_FIA_L23O) >> 28) == V_PCH_PCR_FIA_LANE_OWN_SATA) {
          *LaneNum = 23;
          return EFI_SUCCESS;
        }
        break;
      case 2:
        if ((Los4 & B_PCH_PCR_FIA_L24O) == V_PCH_PCR_FIA_LANE_OWN_SATA) {
          *LaneNum = 24;
          return EFI_SUCCESS;
        }
        break;
      case 3:
        if (((Los4 & B_PCH_PCR_FIA_L25O) >> 4) == V_PCH_PCR_FIA_LANE_OWN_SATA) {
          *LaneNum = 25;
          return EFI_SUCCESS;
        }
        break;
      case 4:
        if (((Los4 & B_PCH_PCR_FIA_L26O) >> 8) == V_PCH_PCR_FIA_LANE_OWN_SATA) {
          *LaneNum = 26;
          return EFI_SUCCESS;
        }
        break;
      case 5:
        if (((Los4 & B_PCH_PCR_FIA_L27O) >> 12) == V_PCH_PCR_FIA_LANE_OWN_SATA) {
          *LaneNum = 27;
          return EFI_SUCCESS;
        }
        break;
      case 6:
        if (((Los4 & B_PCH_PCR_FIA_L28O) >> 16) == V_PCH_PCR_FIA_LANE_OWN_SATA) {
          *LaneNum = 28;
          return EFI_SUCCESS;
        }
        break;
      case 7:
        if (((Los4 & B_PCH_PCR_FIA_L29O) >> 20) == V_PCH_PCR_FIA_LANE_OWN_SATA) {
          *LaneNum = 29;
          return EFI_SUCCESS;
        }
        break;
      default:
        DEBUG ((DEBUG_ERROR, "Unsupported SATA Lane Index"));
        ASSERT (FALSE);
        return EFI_UNSUPPORTED;
        break;
    }
  }
  return EFI_UNSUPPORTED;
}

/**
  Determine the lane number of a specified port

  @param[in]  PcieLaneIndex             PCIE Root Port Lane Index
  @param[out] LaneNum                   Lane Number

  @retval EFI_SUCCESS                   Lane number valid.
  @retval EFI_UNSUPPORTED               Incorrect input device port
**/
EFI_STATUS
PchGetPcieLaneNum (
  UINT32              PcieLaneIndex,
  UINT8               *LaneNum
  )
{
  PCH_SERIES  PchSeries;
  UINT32 Los1;
  UINT32 Los2;
  UINT32 Los3;
  UINT32 Los4;

  PchSeries = GetPchSeries ();
  PchPcrRead32 (PID_FIA, R_PCH_PCR_FIA_LOS1_REG_BASE, &Los1);
  PchPcrRead32 (PID_FIA, R_PCH_PCR_FIA_LOS2_REG_BASE, &Los2);

  if (PchSeries == PchLp) {
    switch (PcieLaneIndex) {
      case 0:
        if (((Los1 & B_PCH_PCR_FIA_L4O) >> 16) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 4;
          return EFI_SUCCESS;
        }
        break;
      case 1:
        if (((Los1 & B_PCH_PCR_FIA_L5O) >> 20) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 5;
          return EFI_SUCCESS;
        }
        break;
      case 2:
        if (((Los1 & B_PCH_PCR_FIA_L6O) >> 24) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 6;
          return EFI_SUCCESS;
        }
        break;
      case 3:
        if (((Los1 & B_PCH_PCR_FIA_L7O) >> 28) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 7;
          return EFI_SUCCESS;
        }
        break;
      case 4:
        if ((Los2 & B_PCH_PCR_FIA_L8O) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 8;
          return EFI_SUCCESS;
        }
        break;
      case 5:
        if (((Los2 & B_PCH_PCR_FIA_L9O) >> 4) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 9;
          return EFI_SUCCESS;
        }
        break;
      case 6:
        if (((Los2 & B_PCH_PCR_FIA_L10O) >> 8) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 10;
          return EFI_SUCCESS;
        }
        break;
      case 7:
        if (((Los2 & B_PCH_PCR_FIA_L11O) >> 12) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 11;
          return EFI_SUCCESS;
        }
        break;
      case 8:
        if (((Los2 & B_PCH_PCR_FIA_L12O) >> 16) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 12;
          return EFI_SUCCESS;
        }
        break;
      case 9:
        if (((Los2 & B_PCH_PCR_FIA_L13O) >> 20) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 13;
          return EFI_SUCCESS;
        }
        break;
      case 10:
        if (((Los2 & B_PCH_PCR_FIA_L14O) >> 24) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 14;
          return EFI_SUCCESS;
        }
        break;
      case 11:
        if (((Los2 & B_PCH_PCR_FIA_L15O) >> 28) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 15;
          return EFI_SUCCESS;
        }
        break;
      default:
        DEBUG ((DEBUG_ERROR, "Unsupported PCIE Root Port Lane Index"));
        ASSERT (FALSE);
        return EFI_UNSUPPORTED;
        break;
    }
  } else {
    PchPcrRead32 (PID_FIA, R_PCH_PCR_FIA_LOS3_REG_BASE, &Los3);
    PchPcrRead32 (PID_FIA, R_PCH_PCR_FIA_LOS4_REG_BASE, &Los4);
    switch (PcieLaneIndex) {
      case 0:
        if (((Los1 & B_PCH_PCR_FIA_L6O) >> 24) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 6;
          return EFI_SUCCESS;
        }
        break;
      case 1:
        if (((Los1 & B_PCH_PCR_FIA_L7O) >> 28) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 7;
          return EFI_SUCCESS;
        }
        break;
      case 2:
        if ((Los2 & B_PCH_PCR_FIA_L8O) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 8;
          return EFI_SUCCESS;
        }
        break;
      case 3:
        if (((Los2 & B_PCH_PCR_FIA_L9O) >> 4) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 9;
          return EFI_SUCCESS;
        }
        break;
      case 4:
        if (((Los2 & B_PCH_PCR_FIA_L10O) >> 8) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 10;
          return EFI_SUCCESS;
        }
        break;
      case 5:
        if (((Los2 & B_PCH_PCR_FIA_L11O) >> 12) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 11;
          return EFI_SUCCESS;
        }
        break;
      case 6:
        if (((Los2 & B_PCH_PCR_FIA_L12O) >> 16) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 12;
          return EFI_SUCCESS;
        }
        break;
      case 7:
        if (((Los2 & B_PCH_PCR_FIA_L13O) >> 20) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 13;
          return EFI_SUCCESS;
        }
        break;
      case 8:
        if (((Los3 & B_PCH_PCR_FIA_L18O) >> 8) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 18;
          return EFI_SUCCESS;
        }
        break;
      case 9:
        if (((Los3 & B_PCH_PCR_FIA_L19O) >> 12) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 19;
          return EFI_SUCCESS;
        }
        break;
      case 10:
        if (((Los3 & B_PCH_PCR_FIA_L20O) >> 16) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 20;
          return EFI_SUCCESS;
        }
        break;
      case 11:
        if (((Los3 & B_PCH_PCR_FIA_L21O) >> 20) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 21;
          return EFI_SUCCESS;
        }
        break;
      case 12:
        if (((Los3 & B_PCH_PCR_FIA_L22O) >> 24) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 22;
          return EFI_SUCCESS;
        }
        break;
      case 13:
        if (((Los3 & B_PCH_PCR_FIA_L23O) >> 28) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 23;
          return EFI_SUCCESS;
        }
        break;
      case 14:
        if ((Los4 & B_PCH_PCR_FIA_L24O) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 24;
          return EFI_SUCCESS;
        }
        break;
      case 15:
        if (((Los4 & B_PCH_PCR_FIA_L25O) >> 4) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 25;
          return EFI_SUCCESS;
        }
        break;
      case 16:
        if (((Los4 & B_PCH_PCR_FIA_L26O) >> 8) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 26;
          return EFI_SUCCESS;
        }
        break;
      case 17:
        if (((Los4 & B_PCH_PCR_FIA_L27O) >> 12) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 27;
          return EFI_SUCCESS;
        }
        break;
      case 18:
        if (((Los4 & B_PCH_PCR_FIA_L28O) >> 16) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 28;
          return EFI_SUCCESS;
        }
        break;
      case 19:
        if (((Los4 & B_PCH_PCR_FIA_L29O) >> 20) == V_PCH_PCR_FIA_LANE_OWN_PCIEDMI) {
          *LaneNum = 29;
          return EFI_SUCCESS;
        }
        break;
      case 20:
        if (GetPchGeneration () == KblPch) { // there's no Los register for 4 last PCIe rootports on KBL
          *LaneNum = 30;
          return EFI_SUCCESS;
        } else {
          return EFI_UNSUPPORTED;
        }
      case 21:
        if (GetPchGeneration () == KblPch) { // there's no Los register for 4 last PCIe rootports on KBL
          *LaneNum = 31;
          return EFI_SUCCESS;
        } else {
          return EFI_UNSUPPORTED;
        }
      case 22:
        if (GetPchGeneration () == KblPch) { // there's no Los register for 4 last PCIe rootports on KBL
          *LaneNum = 32;
          return EFI_SUCCESS;
        } else {
          return EFI_UNSUPPORTED;
        }
      case 23:
        if (GetPchGeneration () == KblPch) { // there's no Los register for 4 last PCIe rootports on KBL
          *LaneNum = 33;
          return EFI_SUCCESS;
        } else {
          return EFI_UNSUPPORTED;
        }
      default:
        DEBUG ((DEBUG_ERROR, "Unsupported PCIE Root Port Lane Index"));
        ASSERT (FALSE);
        return EFI_UNSUPPORTED;
        break;
    }
  }
  return EFI_UNSUPPORTED;
}

/**
  Get HSIO lane representation needed to perform any operation on the lane.

  @param[in]  LaneIndex  Number of the HSIO lane
  @param[out] HsioLane   HSIO lane representation
**/
VOID
HsioGetLane (
  IN   UINT8       LaneIndex,
  OUT  HSIO_LANE   *HsioLane
  )
{
  // SPT-LP
  // iolane  0 -  5 : 0xEA - 000, 200, 400, 600, 800, a00
  // iolane  6 -  9 : 0xE9 - 000, 200, 400, 600
  // iolane 10 - 15 : 0xA9 - 000, 200, 400, 600, 800, a00
  // SPT-H
  // iolane  0 -  5 : 0xEA - 000, 200, 400, 600, 800, a00
  // iolane  6 - 13 : 0xE9 - 000, 200, 400, 600, 800, a00, c00, e00
  // iolane 14 - 21 : 0xA9 - 000, 200, 400, 600, 800, a00, c00, e00
  // iolane 22 - 29 : 0xA8 - 000, 200, 400, 600, 800, a00, c00, e00
  // KBL-H: as for SKL-H plus the following lanes
  // iolane 30 - 33 : 0xB0 - 000, 200, 400, 600

  static UINT8 IoLanesLp[] = { 0, 6, 10, 16 };
  static UINT8 IoLanesH[] = { 0, 6, 14, 22, 30, 34 };
  static UINT8 Pids[] = { PID_MODPHY0, PID_MODPHY1, PID_MODPHY2, PID_MODPHY3, PID_MODPHY4 };

  UINT8* IoLanes;
  UINT8  PidMax;
  UINT32 Index;

  ASSERT (HsioLane != NULL);

  if (GetPchSeries () == PchLp) {
    IoLanes = IoLanesLp;
    PidMax = 3;
  } else {
    IoLanes = IoLanesH;
    if (GetPchGeneration () == KblPch) {
      PidMax = 5;
    } else {
      PidMax = 4;
    }
  }
  ASSERT (LaneIndex < IoLanes[PidMax]);

  for (Index = 0; Index < PidMax; ++Index) {
    if (LaneIndex < IoLanes[Index + 1]) {
      HsioLane->Index = LaneIndex;
      HsioLane->Pid = Pids[Index];
      HsioLane->Base = (LaneIndex - IoLanes[Index]) * 0x200;
      return;
    }
  }
  ASSERT (FALSE);
}

