/** @file
  This driver does SA PCI Express ACPI table initialization.

  Copyright (c) 2019 - 2020 Intel Corporation. All rights reserved. <BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include "PciExpressInit.h"
#include <Library/ConfigBlockLib.h>

extern SYSTEM_AGENT_NVS_AREA_PROTOCOL   mSaNvsAreaProtocol;
extern SA_CONFIG_HOB                    *mSaConfigHob;

/**
  PCI Express Dxe Initialization.
  Run before PCI Bus Init, where assignment of Bus, Memory,
    and I/O Resources are assigned.

  @param[in] SaPolicy     -     SA DXE Policy protocol

  @retval EFI_SUCCESS     - Pci Express successfully started and ready to be used
**/
EFI_STATUS
PciExpressInit (
  IN SA_POLICY_PROTOCOL *SaPolicy
  )
{
  EFI_STATUS                                    Status;
  PCIE_DXE_CONFIG                               *PcieDxeConfig;
  MSR_BROADWELL_PKG_CST_CONFIG_CONTROL_REGISTER Msr;

  Status = GetConfigBlock ((VOID *) SaPolicy, &gPcieDxeConfigGuid, (VOID *)&PcieDxeConfig);
  ASSERT_EFI_ERROR (Status);


  Msr.Uint64 = AsmReadMsr64 (MSR_BROADWELL_PKG_CST_CONFIG_CONTROL);
  mSaNvsAreaProtocol.Area->PackageCstateLimit  = (UINT8) Msr.Bits.Limit;

  mSaNvsAreaProtocol.Area->PwrDnBundlesGlobalEnable  = 0;

  if (mSaConfigHob != NULL) {
    mSaNvsAreaProtocol.Area->Peg0PowerDownUnusedBundles  = mSaConfigHob->PowerDownUnusedBundles[0];
    mSaNvsAreaProtocol.Area->Peg1PowerDownUnusedBundles  = mSaConfigHob->PowerDownUnusedBundles[1];
    mSaNvsAreaProtocol.Area->Peg2PowerDownUnusedBundles  = mSaConfigHob->PowerDownUnusedBundles[2];
    if (SA_PEG_MAX_FUN > 3) {
      mSaNvsAreaProtocol.Area->Peg3PowerDownUnusedBundles  = mSaConfigHob->PowerDownUnusedBundles[3];
    }
  }
  ///
  /// LTR/OBFF
  ///
  mSaNvsAreaProtocol.Area->Peg0LtrEnable                = PcieDxeConfig->PegPwrOpt[0].LtrEnable;
  mSaNvsAreaProtocol.Area->Peg0ObffEnable               = PcieDxeConfig->PegPwrOpt[0].ObffEnable;
  mSaNvsAreaProtocol.Area->Peg1LtrEnable                = PcieDxeConfig->PegPwrOpt[1].LtrEnable;
  mSaNvsAreaProtocol.Area->Peg1ObffEnable               = PcieDxeConfig->PegPwrOpt[1].ObffEnable;
  mSaNvsAreaProtocol.Area->Peg2LtrEnable                = PcieDxeConfig->PegPwrOpt[2].LtrEnable;
  mSaNvsAreaProtocol.Area->Peg2ObffEnable               = PcieDxeConfig->PegPwrOpt[2].ObffEnable;
  mSaNvsAreaProtocol.Area->PegLtrMaxSnoopLatency        = LTR_MAX_SNOOP_LATENCY_VALUE;
  mSaNvsAreaProtocol.Area->PegLtrMaxNoSnoopLatency      = LTR_MAX_NON_SNOOP_LATENCY_VALUE;

  return EFI_SUCCESS;
}

/**
  Find the Offset to a given Capabilities ID
  CAPID list:
    0x01 = PCI Power Management Interface
    0x04 = Slot Identification
    0x05 = MSI Capability
    0x10 = PCI Express Capability

  @param[in] Segment   -   Pci Segment Number
  @param[in] Bus       -   Pci Bus Number
  @param[in] Device    -   Pci Device Number
  @param[in] Function  -   Pci Function Number
  @param[in] CapId     -   CAPID to search for

  @retval 0       - CAPID not found
  @retval Other   - CAPID found, Offset of desired CAPID
**/
UINT32
PcieFindCapId (
  IN UINT8   Segment,
  IN UINT8   Bus,
  IN UINT8   Device,
  IN UINT8   Function,
  IN UINT8   CapId
  )
{
  UINT64 DeviceBaseAddress;
  UINT8  CapHeader;

  ///
  /// Always start at Offset 0x34
  ///
  DeviceBaseAddress = PCI_SEGMENT_LIB_ADDRESS (Segment, Bus, Device, Function, 0);
  CapHeader         = PciSegmentRead8 (DeviceBaseAddress + PCI_CAPBILITY_POINTER_OFFSET);
  if (CapHeader == 0xFF) {
    return 0;
  }

  while (CapHeader != 0) {
    ///
    /// Bottom 2 bits of the pointers are reserved per PCI Local Bus Spec 2.2
    ///
    CapHeader &= ~(BIT1 + BIT0);
    ///
    /// Search for desired CapID
    ///
    if (PciSegmentRead8 (DeviceBaseAddress + CapHeader) == CapId) {
      return CapHeader;
    }

    CapHeader = PciSegmentRead8 (DeviceBaseAddress + CapHeader + 1);
  }

  return 0;
}

/**
  Search and return the offset of desired Pci Express Capability ID
  CAPID list:
    0x0001 = Advanced Error Rreporting Capability
    0x0002 = Virtual Channel Capability
    0x0003 = Device Serial Number Capability
    0x0004 = Power Budgeting Capability

  @param[in] Segment   -   Pci Segment Number
  @param[in] Bus       -   Pci Bus Number
  @param[in] Device    -   Pci Device Number
  @param[in] Function  -   Pci Function Number
  @param[in] CapId     -   Extended CAPID to search for

  @retval 0       - CAPID not found
  @retval Other   - CAPID found, Offset of desired CAPID
**/
UINT32
PcieFindExtendedCapId (
  IN UINT8   Segment,
  IN UINT8   Bus,
  IN UINT8   Device,
  IN UINT8   Function,
  IN UINT16  CapId
  )
{
  UINT64  DeviceBaseAddress;
  UINT16  CapHeaderOffset;
  UINT16  CapHeaderId;

  ///
  /// Start to search at Offset 0x100
  /// Get Capability Header
  ///
  DeviceBaseAddress = PCI_SEGMENT_LIB_ADDRESS (Segment, Bus, Device, Function, 0);
  CapHeaderId     = 0;
  CapHeaderOffset = 0x100;

  while (CapHeaderOffset != 0 && CapHeaderId != 0xFFFF) {
    ///
    /// Search for desired CapID
    ///
    CapHeaderId = PciSegmentRead16 (DeviceBaseAddress + CapHeaderOffset);
    if (CapHeaderId == CapId) {
      return CapHeaderOffset;
    }

    CapHeaderOffset = (PciSegmentRead16 (DeviceBaseAddress + CapHeaderOffset + 2) >> 4);
  }

  return 0;
}
