/** @file
  This file contains routines that support PCI Express initialization

  Copyright (c) 2021, Intel Corporation. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <Uefi/UefiBaseType.h>
#include <IndustryStandard/Pci.h>
#include <Library/BaseMemoryLib.h>
#include <Library/PcieHelperLib.h>
#include <Library/PchSbiAccessLib.h>
#include <Library/PchPciBdfLib.h>
#include <Library/PcieRpLib.h>
#include <Library/PchInfoLib.h>
#include <Library/PchPcieRpLib.h>
#include <Library/PsfLib.h>
#include <Library/HobLib.h>
#include <Register/PchPcieRpRegs.h>
#include <Register/PcieSipRegs.h>
#include <PcieRegs.h>
#include <PchPcieRpInfo.h>
#include <CpuPcieInfo.h>
#include <CpuPcieHob.h>

/**
  Get PCIe port number for enabled port.
  @param[in] RpBase    Root Port pci segment base address

  @retval Root Port number (1 based)
**/
UINT32
PciePortNum (
  IN     UINT64  RpBase
  )
{
  return PciSegmentRead32 (RpBase + R_PCH_PCIE_CFG_LCAP) >> N_PCH_PCIE_CFG_LCAP_PN;
}

/**
  Get PCIe root port index

  @param[in] RpBase    Root Port pci segment base address

  @retval Root Port index (0 based)
**/
UINT32
PciePortIndex (
  IN     UINT64  RpBase
  )
{
  return PciePortNum (RpBase) - 1;
}

/**
  This function checks whether PHY lane power gating is enabled on the port.

  @param[in] RpBase                 Root Port base address

  @retval TRUE                      PHY power gating is enabled
  @retval FALSE                     PHY power gating disabled
**/
BOOLEAN
PcieIsPhyLanePgEnabled (
  IN     UINT64  RpBase
  )
{
  UINT32 Data32;

  Data32 = PciSegmentRead32 (RpBase + R_PCH_PCIE_CFG_PCIEPMECTL);
  return (Data32 & B_PCH_PCIE_CFG_PCIEPMECTL_DLSULPPGE) != 0;
}

/**
  Configures Root Port packet split.

  @param[in] Segment,Bus,Device,Function    address of currently visited PCIe device
  @param[in] Mps                            maximum packet size
**/
VOID
ConfigureRpPacketSplit (
  UINT64 RpBase,
  UINT8  Mps
  )
{
  PciSegmentAndThenOr32 (RpBase + R_PCIE_CFG_CCFG, (UINT32) ~(B_PCIE_CFG_CCFG_UNRS), Mps << N_PCIE_CFG_CCFG_UNRS);
}

/**
  Configures LTR override in Root Port's proprietary registers.

  @param[in] Segment,Bus,Device,Function    address of currently visited PCIe device
  @param[in] DevNum                         currently visited device number
  @param[in] RpConfig                       Root Port LTR configuration
  @param[in] AspmOverride                   combination of LTR override values from all devices under this Root Port
**/
VOID
ConfigureRpLtrOverride (
  UINT64           RpBase,
  UINT32           DevNum,
  LTR_OVERRIDE     *TreeLtr,
  PCIE_LTR_CONFIG  *LtrConfig
  )
{
  UINT32       OvrEn;
  UINT32       OvrVal;
  BOOLEAN      IsCpuPcie;

  IsCpuPcie = FALSE;
  OvrEn = 0;
  OvrVal = 0;

  if (DevNum == SA_PEG0_DEV_NUM || DevNum == SA_PEG3_DEV_NUM) {
    IsCpuPcie = TRUE;
  }

  //
  // LTR settings from LTROVR register only get acknowledged on rising edge of LTROVR2[1:0]
  // If those bits were already set (that can happen on a plug-hotUnplug-hotPlug scenario),
  // they need to be toggled
  //
  if (PciSegmentRead32 (RpBase + R_PCH_PCIE_CFG_LTROVR2) != 0) {
    PciSegmentWrite32 (RpBase + R_PCH_PCIE_CFG_LTROVR2, 0);
  }
  //
  // (*)LatencyOverrideMode = 0 -> no override
  //                          1 -> override with RP policy values
  //                          2 -> override with endpoint's override values
  //

  if (LtrConfig->ForceLtrOverride || TreeLtr->ForceOverride) {
    OvrEn |= B_PCH_PCIE_CFG_LTROVR2_FORCE_OVERRIDE;
  }
  if (LtrConfig->LtrConfigLock == TRUE) {
    OvrEn |= B_PCH_PCIE_CFG_LTROVR2_LOCK;
  }

  if (LtrConfig->SnoopLatencyOverrideMode == 1) {
    OvrEn |= B_PCH_PCIE_CFG_LTROVR2_LTRSOVREN;
    OvrVal |= LtrConfig->SnoopLatencyOverrideValue;
    OvrVal |= LtrConfig->SnoopLatencyOverrideMultiplier << 10;
    OvrVal |= B_PCH_PCIE_CFG_LTROVR_LTRSROVR;
  } else if (LtrConfig->SnoopLatencyOverrideMode == 2) {
    if (TreeLtr->MaxSnoopLatencyRequirement) {
      OvrEn |= B_PCH_PCIE_CFG_LTROVR2_LTRSOVREN;
      OvrVal |= TreeLtr->MaxSnoopLatencyValue;
      OvrVal |= TreeLtr->MaxSnoopLatencyScale << 10;
      OvrVal |= B_PCH_PCIE_CFG_LTROVR_LTRSROVR;
    }
  }
  if (LtrConfig->NonSnoopLatencyOverrideMode == 1) {
    OvrEn |= B_PCH_PCIE_CFG_LTROVR2_LTRNSOVREN;
    OvrVal |= LtrConfig->NonSnoopLatencyOverrideValue << 16;
    OvrVal |= LtrConfig->NonSnoopLatencyOverrideMultiplier << 26;
    OvrVal |= B_PCH_PCIE_CFG_LTROVR_LTRNSROVR;
  } else if (LtrConfig->NonSnoopLatencyOverrideMode == 2) {
    if (TreeLtr->MaxNoSnoopLatencyRequirement) {
      OvrEn |= B_PCH_PCIE_CFG_LTROVR2_LTRNSOVREN;
      OvrVal |= TreeLtr->MaxNoSnoopLatencyValue << 16;
      OvrVal |= TreeLtr->MaxNoSnoopLatencyScale << 26;
      OvrVal |= B_PCH_PCIE_CFG_LTROVR_LTRNSROVR;
    }
  }
  PciSegmentWrite32 (RpBase + R_PCH_PCIE_CFG_LTROVR, OvrVal);
  PciSegmentWrite32 (RpBase + R_PCH_PCIE_CFG_LTROVR2, OvrEn);

  DEBUG ((DEBUG_INFO, "ConfigureRpLtrOverride IsCpuPcie=%d\n", IsCpuPcie));
  DEBUG ((DEBUG_INFO, "ConfigureRpLtrOverride %x Val %x En %x\n", RpBase, OvrVal, OvrEn));
}

/**
  This function configures EOI message forwarding for PCIe port.
  If there's an IoAPIC behind this port, forwarding will be enabled
  Otherwise it will be disabled to minimize bus traffic

  @param[in] Segment,Bus,Device,Function    address of currently visited PCIe device
  @param[in] IoApicPresent  TRUE if there's IoAPIC behind this Root Port
**/
VOID
ConfigureEoiForwarding (
  UINT64   RpBase,
  BOOLEAN  IoApicPresent
  )
{
  UINT32 RpIndex;

  RpIndex = PciePortIndex (RpBase);

  if (IoApicPresent == FALSE) {
   PciSegmentOr32 (RpBase + R_PCH_PCIE_CFG_MPC2, B_PCH_PCIE_CFG_MPC2_EOIFD);
  } else {
    ///
    /// If there is an IOAPIC discovered behind Root Port, program PSF Multicast registers
    /// in accordance with PCH PCIe BWG  PSF EOI Multicast Configuration
    ///
    PciSegmentAnd32 (RpBase + R_PCH_PCIE_CFG_MPC2, (UINT32)~B_PCH_PCIE_CFG_MPC2_EOIFD);
    PsfConfigurEoiForPciePort (RpIndex);
  }
}

/**
  Configures proprietary parts of L1 substates configuration in Root Port

  @param[in] RpSbdf       segment:bus:device:function coordinates of Root Port
  @param[in] LtrCapable   TRUE if Root Port is LTR capable
**/
VOID
L1ssProprietaryConfiguration (
  UINT64  RpBase,
  BOOLEAN LtrCapable
  )
{
  BOOLEAN ClkreqSupported;
  BOOLEAN L1ssEnabled;
  UINT16  PcieCapOffset;
  UINT32  Data32;
  BOOLEAN L1LowSupported;

  ClkreqSupported = PcieIsPhyLanePgEnabled (RpBase);

  PcieCapOffset = PcieBaseFindExtendedCapId (RpBase, V_PCIE_EX_L1S_CID);
  if (PcieCapOffset == 0) {
    L1ssEnabled = FALSE;
  } else {
    Data32 = PciSegmentRead32 (RpBase + PcieCapOffset + R_PCIE_EX_L1SCTL1_OFFSET);
    L1ssEnabled = Data32 & (B_PCIE_EX_L1SCAP_AL1SS | B_PCIE_EX_L1SCAP_AL12S | B_PCIE_EX_L1SCAP_PPL11S |B_PCIE_EX_L1SCAP_PPL12S);
  }
  L1LowSupported = ClkreqSupported && LtrCapable && !L1ssEnabled;

  ///
  /// If L1.SNOOZ and L1.OFF (L1 Sub-States) are not supported and per-port CLKREQ# is supported, and LTR is supported:
  /// Enable L1.LOW by setting Dxx:Fn:420[17] = 1b
  ///
  if (L1LowSupported) {
    PciSegmentOr32 (RpBase + R_PCH_PCIE_CFG_PCIEPMECTL, (UINT32) B_PCH_PCIE_CFG_PCIEPMECTL_L1LE);
  } else {
    PciSegmentAnd32 (RpBase + R_PCH_PCIE_CFG_PCIEPMECTL, (UINT32) ~B_PCH_PCIE_CFG_PCIEPMECTL_L1LE);
  }

  if (L1LowSupported || L1ssEnabled) {
    ///
    /// f.  Set Dxx:Fn:420h[0] to 1b prior to L1 enabling if any L1substate is enabled (including L1.LOW)
    ///
    PciSegmentOr32 (RpBase + R_PCH_PCIE_CFG_PCIEPMECTL, B_PCH_PCIE_CFG_PCIEPMECTL_L1FSOE);
  }
}
