/** @file
  Pch common library for PCH INIT PEI/DXE/SMM modules

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

**/

#include <Uefi/UefiBaseType.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/IoLib.h>
#include <Library/MmPciLib.h>
#include <PchAccess.h>
#include <PchPolicyCommon.h>
#include <Library/PchCycleDecodingLib.h>
#include <Library/PchPcieRpLib.h>
#include <Library/PchP2sbLib.h>
#include <Library/PchSbiAccessLib.h>
#include <Library/PchInfoLib.h>
#include <Library/S3BootScriptLib.h>
#include <Library/TimerLib.h>

extern CONST PCH_PCIE_CONTROLLER_INFO mPchPcieControllerInfo[];
extern CONST UINT32 mPchPcieControllerInfoSize;

#define PORT_PLS_TIMEOUT 100 ///< 100 * 10 us = 1ms timeout for USB3 PortSC PLS polling

/**
  This function returns PID according to PCIe controller index

  @param[in] ControllerIndex     PCIe controller index

  @retval PCH_SBI_PID    Returns PID for SBI Access
**/
PCH_SBI_PID
PchGetPcieControllerSbiPid (
  IN  UINT32  ControllerIndex
  )
{
  ASSERT (ControllerIndex < mPchPcieControllerInfoSize);
  return mPchPcieControllerInfo[ControllerIndex].Pid;
}

/**
  This function returns PID according to Root Port Number

  @param[in] RpIndex     Root Port Index (0-based)

  @retval PCH_SBI_PID    Returns PID for SBI Access
**/
PCH_SBI_PID
GetRpSbiPid (
  IN  UINTN  RpIndex
  )
{
  return PchGetPcieControllerSbiPid ((UINT32) (RpIndex / PCH_PCIE_CONTROLLER_PORTS));
}

/**
  Calculate root port device number based on physical port index.

  @param[in]  RpIndex              Root port index (0-based).

  @retval     Root port device number.
**/
UINT32
PchGetPcieRpDevice (
  IN  UINT32   RpIndex
  )
{
  UINTN ControllerIndex;
  ControllerIndex = RpIndex / PCH_PCIE_CONTROLLER_PORTS;
  ASSERT (ControllerIndex < mPchPcieControllerInfoSize);
  return mPchPcieControllerInfo[ControllerIndex].DevNum;
}

/**
  This function reads Pci Config register via SBI Access

  @param[in]  RpIndex             Root Port Index (0-based)
  @param[in]  Offset              Offset of Config register
  @param[out] *Data32             Value of Config register

  @retval EFI_SUCCESS             SBI Read successful.
**/
EFI_STATUS
PchSbiRpPciRead32 (
  IN    UINT32  RpIndex,
  IN    UINT32  Offset,
  OUT   UINT32  *Data32
  )
{
  EFI_STATUS    Status;
  UINT32        RpDevice;
  UINT8         Response;
  UINT16        Fid;


  RpDevice = PchGetPcieRpDevice (RpIndex);
  Fid = (UINT16) ((RpDevice << 3) | (RpIndex % 4 ));
  Status = PchSbiExecutionEx (
             GetRpSbiPid (RpIndex),
             Offset,
             PciConfigRead,
             FALSE,
             0xF,
             0,
             Fid,
             Data32,
             &Response
             );
  if (Status != EFI_SUCCESS) {
    DEBUG ((DEBUG_ERROR,"Sideband Read Failed\n"));
    ASSERT (FALSE);
  }
  return Status;
}

/**
  This function And then Or Pci Config register via SBI Access

  @param[in]  RpIndex             Root Port Index (0-based)
  @param[in]  Offset              Offset of Config register
  @param[in]  Data32And           Value of Config register to be And-ed
  @param[in]  Data32AOr           Value of Config register to be Or-ed

  @retval EFI_SUCCESS             SBI Read and Write successful.
**/
EFI_STATUS
PchSbiRpPciAndThenOr32 (
  IN  UINT32  RpIndex,
  IN  UINT32  Offset,
  IN  UINT32  Data32And,
  IN  UINT32  Data32Or
  )
{
  EFI_STATUS  Status;
  UINT32      RpDevice;
  UINT32      Data32;
  UINT8       Response;
  UINT16      Fid;

  RpDevice = PchGetPcieRpDevice (RpIndex);
  Status = PchSbiRpPciRead32 (RpIndex, Offset, &Data32);
  if (Status == EFI_SUCCESS) {
    Data32 &= Data32And;
    Data32 |= Data32Or;
    Fid = (UINT16) ((RpDevice << 3) | (RpIndex % 4 ));
    Status = PchSbiExecutionEx (
               GetRpSbiPid (RpIndex),
               Offset,
               PciConfigWrite,
               FALSE,
               0xF,
               0,
               Fid,
               &Data32,
               &Response
               );
    if (Status != EFI_SUCCESS) {
      DEBUG ((DEBUG_ERROR,"Sideband Write Failed\n"));
      ASSERT (FALSE);
    }
  } else {
    ASSERT (FALSE);
  }
  return Status;
}

/**
  Check if RST PCIe Storage Remapping is enabled based on policy

  @param[in] RstPcieStorageRemap    The PCH PCIe Storage remapping

  @retval TRUE                      RST PCIe Storage Remapping is enabled
  @retval FALSE                     RST PCIe Storage Remapping is disabled
**/
BOOLEAN
IsRstPcieStorageRemapEnabled (
  IN  CONST PCH_RST_PCIE_STORAGE_CONFIG   *RstPcieStorageRemap
  )
{
  BOOLEAN                      RstPcieStorageRemapEnabled;
  UINTN                        Index;

  RstPcieStorageRemapEnabled  = FALSE;

  DEBUG ((DEBUG_INFO, "IsRstPcieStorageRemapEnabled Started\n"));

  for (Index = 0; Index < PCH_MAX_RST_PCIE_STORAGE_CR; Index++) {
    if (RstPcieStorageRemap[Index].Enable == 1) {
      RstPcieStorageRemapEnabled = TRUE;
    }
  }

  DEBUG ((DEBUG_INFO, "IsRstPcieStorageRemapEnabled Ended\n"));
  return RstPcieStorageRemapEnabled;
}

/**
  Disable the RST remap address decoding range while RST is disabled.

  @param[in] AhciBar                ABAR address
**/
VOID
DisableRstRemapDecoding (
  UINT32                            AhciBar
  )
{
  UINTN                             PciSataRegBase;
  UINT32                            OrgAhciBar;
  UINT8                             OrgCmd;

  if (AhciBar == 0) {
    DEBUG ((DEBUG_ERROR, "DisableRstRemapDecoding: Invalid AHCI BAR address.\n"));
    ASSERT (FALSE);
    return;
  }

  PciSataRegBase  = MmPciBase (DEFAULT_PCI_BUS_NUMBER_PCH, PCI_DEVICE_NUMBER_PCH_SATA, PCI_FUNCTION_NUMBER_PCH_SATA);
  OrgCmd          = MmioRead8 (PciSataRegBase + PCI_COMMAND_OFFSET);
  OrgAhciBar      = MmioRead32 (PciSataRegBase + R_PCH_SATA_AHCI_BAR);

  if (AhciBar != OrgAhciBar) {
    MmioWrite8 (PciSataRegBase + PCI_COMMAND_OFFSET, 0);
    MmioWrite32 (PciSataRegBase + R_PCH_SATA_AHCI_BAR, AhciBar);
  }
  MmioWrite8 (PciSataRegBase + PCI_COMMAND_OFFSET, EFI_PCI_COMMAND_MEMORY_SPACE);

  //
  // Program "NVM Remap Memory BAR Enable",  AHCIBar offset A4h [0] to ['0b']
  // Also set NRMO AHCIBar offset A4h [27:16] to 0x00F, and MSL AHCIBar offset A4h [12:1] to 0.
  //
  DEBUG ((DEBUG_INFO, "DisableRstRemapDecoding: Program AHCIBar offset A4h [27:16, 12:1, 0] to [0x00F, 0, 0] to disable remap decoding\n"));
  MmioWrite32 (AhciBar + R_PCH_SATA_VS_CAP, (0x00F << N_PCH_SATA_VS_CAP_NRMO));

  if (AhciBar != OrgAhciBar) {
    MmioWrite8 (PciSataRegBase + PCI_COMMAND_OFFSET, 0);
    MmioWrite32 (PciSataRegBase + R_PCH_SATA_AHCI_BAR, OrgAhciBar);
  }
  MmioWrite8 (PciSataRegBase + PCI_COMMAND_OFFSET, OrgCmd);
}

/**
  This function lock down the P2sb SBI before going into OS.

  @param[in] SbiUnlock
**/
VOID
ConfigureP2sbSbiLock (
  IN  CONST BOOLEAN             SbiUnlock
  )
{
  UINTN                                 P2sbBase;
  BOOLEAN                               P2sbOrgStatus;

  if (SbiUnlock == FALSE) {
    P2sbBase = MmPciBase (DEFAULT_PCI_BUS_NUMBER_PCH, PCI_DEVICE_NUMBER_PCH_P2SB, PCI_FUNCTION_NUMBER_PCH_P2SB);
    PchRevealP2sb (P2sbBase, &P2sbOrgStatus);

    ///
    /// Set P2SB PCI Offset 0xE0[31] to 1 to lock down SBI interface.
    /// @note: SbiLock is write once. Please make sure it's not touched earlier.
    ///
    MmioOr8 (P2sbBase + R_PCH_P2SB_E0 + 3, BIT7);

    if (!P2sbOrgStatus) {
      PchHideP2sb (P2sbBase);
    }
  }
}

/**
  Bios will remove the host accessing right to Sideband register range
  prior to any 3rd party code execution.

  1) Set EPMASK5 Offset C4 (bits 29, 28, 27, 26, 17, 16 (PCH-LP only), 10, 1) to disable Sideband access for PSF and MIPI controller
  2) Set EPMASK7 Offset CC (bits 6, 5) to disable Sideband access for XHCI controller
  3) Set the "Endpoint Mask Lock!", P2SB PCI offset E2h bit[1] to 1.

**/
VOID
RemoveSidebandAccess (
  VOID
  )
{
  UINTN                                 P2sbBase;
  BOOLEAN                               P2sbOrgStatus;

  P2sbBase = MmPciBase (DEFAULT_PCI_BUS_NUMBER_PCH, PCI_DEVICE_NUMBER_PCH_P2SB, PCI_FUNCTION_NUMBER_PCH_P2SB);
  PchRevealP2sb (P2sbBase, &P2sbOrgStatus);

  //
  // Set EPMASK for ports B1, B0, AA, A1 (bits 17, 16 (PCH-LP only), 10, 1) to disable Sideband access for MIPI controller
  // Set EPMASK for ports BD, BC, BB, BA (bits 29, 28, 27, 26) to disable Sideband access for PCI Root Bridge
  //
  if (GetPchSeries () == PchLp) {
    MmioOr32 (P2sbBase + R_PCH_P2SB_EPMASK5, BIT29 | BIT28 | BIT27 | BIT26 | BIT17 | BIT16 | BIT10 | BIT1);
  } else {
    MmioOr32 (P2sbBase + R_PCH_P2SB_EPMASK5, BIT29 | BIT28 | BIT27 | BIT26 | BIT17         | BIT10 | BIT1);
  }

  //
  // Set EPMASK for ports E6, E5 (bits 6, 5) to disable Sideband access for XHCI controller
  //
  MmioOr32(P2sbBase + R_PCH_P2SB_EPMASK7, BIT6 | BIT5);

  DEBUG((DEBUG_INFO, "Lock the EPMASK registers\n"));

  MmioOr8 (P2sbBase + R_PCH_P2SB_E0 + 2, BIT1);

  if (!P2sbOrgStatus) {
    PchHideP2sb (P2sbBase);
  }
}

/**
  Configure PMC static function disable lock
**/
VOID
ConfigurePmcStaticFunctionDisableLock (
  VOID
  )
{
  UINT32                                PchPwrmBase;
  PchPwrmBaseGet (&PchPwrmBase);

  MmioOr32 (PchPwrmBase + R_PCH_PWRM_ST_PG_FDIS_PMC_1, B_PCH_PWRM_ST_PG_FDIS_PMC_1_ST_FDIS_LK);
}

/**
  Print registers value

  @param[in] PrintMmioBase       Mmio base address
  @param[in] PrintSize           Number of registers
  @param[in] OffsetFromBase      Offset from mmio base address

  @retval None
**/
VOID
PrintRegisters (
  IN  UINTN        PrintMmioBase,
  IN  UINT32       PrintSize,
  IN  UINT32       OffsetFromBase
  )
{
  UINT32  Offset;
  DEBUG ((DEBUG_VERBOSE, "       00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"));
  for (Offset = 0; Offset < PrintSize; Offset++) {
    if ((Offset % 16) == 0) {
      DEBUG ((DEBUG_VERBOSE, "\n %04X: ", (Offset + OffsetFromBase) & 0xFFF0));
    }
    DEBUG ((DEBUG_VERBOSE, "%02X ", MmioRead8 (PrintMmioBase + Offset)));
  }
  DEBUG ((DEBUG_VERBOSE, "\n"));
}

VOID
PrintPchPciConfigSpace (
  VOID
  )
{
  DEBUG ((DEBUG_VERBOSE, "[ 0 - 00 - 0 ] Host Bridge\n"));
  PrintRegisters (MmPciBase ( 0,0,0 ), 256, 0);
  DEBUG ((DEBUG_VERBOSE, "[ 0 - 01 - 0 ] PCI Bridge (0-1) x16@1 (x16)\n"));
  PrintRegisters (MmPciBase ( 0,1,0 ), 256, 0);
  DEBUG ((DEBUG_VERBOSE, "[ 0 - 02 - 0 ] IGD\n"));
  PrintRegisters (MmPciBase ( 0,2,0 ), 256, 0);
  DEBUG ((DEBUG_VERBOSE, "[ 0 - 03 - 0 ] High Def Audio\n"));
  PrintRegisters (MmPciBase (00, 03, 00), 256, 0);
  DEBUG ((DEBUG_VERBOSE, "[ 0 - 20(0x14) - 0 ] XHCI Controller\n"));
  PrintRegisters (MmPciBase (00, 20, 00), 256, 0);
  DEBUG ((DEBUG_VERBOSE, "[ 0 - 22(0x16) - 0 ] ME\n"));
  PrintRegisters (MmPciBase (00, 22, 00), 256, 0);
  DEBUG ((DEBUG_VERBOSE, "[ 0 - 22(0x16) - 2 ] ME IDER\n"));
  PrintRegisters (MmPciBase (00, 22, 02), 256, 0);
  DEBUG ((DEBUG_VERBOSE, "[ 0 - 22(0x16) - 3 ] ME SOL\n"));
  PrintRegisters (MmPciBase (00, 22, 03), 256, 0);
  DEBUG ((DEBUG_VERBOSE, "[ 0 - 25(0x19) - 0 ] LAN\n"));
  PrintRegisters (MmPciBase (00, 25, 00), 256, 0);
  DEBUG ((DEBUG_VERBOSE, "[ 0 - 26(0x1A) - 0 ] EHCI Controller\n"));
  PrintRegisters (MmPciBase (00, 26, 00), 256, 0);
  DEBUG ((DEBUG_VERBOSE, "[ 0 - 27(0x1B) - 0 ] High Def Audio\n"));
  PrintRegisters (MmPciBase (00, 27, 00), 256, 0);
  DEBUG ((DEBUG_VERBOSE, "[ 0 - 28(0x1C) - 0 ] PCI Bridge (0-2) x0@1 (x1)\n"));
  PrintRegisters (MmPciBase (00, 28, 00), 256, 0);
  DEBUG ((DEBUG_VERBOSE, "PCI Bridge: AECH\n"));
  PrintRegisters (MmPciBase (00, 28, 00) + 0x0100, 0x040,0x0100);
  DEBUG ((DEBUG_VERBOSE, "PCI Bridge: DFT\n"));
  PrintRegisters (MmPciBase (00, 28, 00) + 0x0310, 0x030,0x0310);
  DEBUG ((DEBUG_VERBOSE, "PCI Bridge: VC0\n"));
  PrintRegisters (MmPciBase (00, 28, 00) + 0x0340, 0x020,0x0340);
  DEBUG ((DEBUG_VERBOSE, "PCI Bridge: Port Configuration Extension\n"));
  PrintRegisters (MmPciBase (00, 28, 00) + 0x0400, 0x020,0x0400);
  DEBUG ((DEBUG_VERBOSE, "[ 0 - 28(0x1C) - 6 ] PCI Bridge (0-3:10) x0@1 (x1)\n"));
  PrintRegisters (MmPciBase (00, 28, 06), 256, 0);
  DEBUG ((DEBUG_VERBOSE, "[ 0 - 29(0x1D) - 0 ] EHCI Controller\n"));
  PrintRegisters (MmPciBase (00, 29, 00), 256, 0);
  DEBUG ((DEBUG_VERBOSE, "[ 0 - 31(0x1F) - 0 ] ISA Bridge\n"));
  PrintRegisters (MmPciBase (00, 31, 00), 256, 0);
  DEBUG ((DEBUG_VERBOSE, "[ 0 - 31(0x1F) - 2 ] SATA\n"));
  PrintRegisters (MmPciBase (00, 31, 02), 256, 0);
  DEBUG ((DEBUG_VERBOSE, "[ 0 - 31(0x1F) - 4 ] SMBus Controller\n"));
  PrintRegisters (MmPciBase (00, 31, 04), 256, 0);

  DEBUG ((DEBUG_VERBOSE, "\n\n"));
}

/**
  Program Xhci Port Disable Override

  @param[in] XhciMmioBase         xHCI controller MBAR0 address
  @param[in] Usb2DisabledPorts    Disabled USB2 ports where each port has its disabling bit
  @param[in] Usb3DisabledPorts    Disabled USB3 ports where each port has its disabling bit

  @retval TRUE if platform reset is needed, otherwise FALSE is returned
**/
BOOLEAN
UsbPdoProgramming (
  IN  UINTN   XhciMmioBase,
  IN  UINT32  Usb2DisabledPorts,
  IN  UINT32  Usb3DisabledPorts
  )
{
  UINT32    Index;
  UINT32    XhciUsb2Pdo;
  UINT32    XhciUsb3Pdo;
  UINT32    XhciUsb2PdoRd;
  UINT32    XhciUsb3PdoRd;
  UINT32    XhciUsb3PortScStartingOffset;
  UINT32    XhciUsb3PortScOffset;
  UINT32    XhciPlsTimeout;

  DEBUG ((DEBUG_INFO, "UsbPdoProgramming () start\n"));
  DEBUG ((DEBUG_INFO, "xHCI: Usb2DisabledPorts = 0x%x\n", Usb2DisabledPorts));
  DEBUG ((DEBUG_INFO, "xHCI: Usb3DisabledPorts = 0x%x\n", Usb3DisabledPorts));

  //
  // XHCI PDO for HS
  //
  if (GetPchSeries () == PchLp) {
    XhciUsb2Pdo = MmioRead32 (XhciMmioBase + R_PCH_XHCI_USB2PDO) & B_PCH_XHCI_LP_USB2PDO_MASK;
  } else {
    XhciUsb2Pdo = MmioRead32 (XhciMmioBase + R_PCH_XHCI_USB2PDO) & B_PCH_XHCI_H_USB2PDO_MASK;
  }
  XhciUsb2Pdo |= Usb2DisabledPorts;

  //
  // XHCI PDO for SS
  //
  if (GetPchSeries () == PchLp) {
    XhciUsb3Pdo = MmioRead32 (XhciMmioBase + R_PCH_XHCI_USB3PDO) & B_PCH_XHCI_LP_USB3PDO_MASK;
    XhciUsb3PortScStartingOffset = R_PCH_LP_XHCI_PORTSC01USB3;
  } else {
    XhciUsb3Pdo = MmioRead32 (XhciMmioBase + R_PCH_XHCI_USB3PDO) & B_PCH_XHCI_H_USB3PDO_MASK;
    XhciUsb3PortScStartingOffset = R_PCH_H_XHCI_PORTSC01USB3;
  }

  for (Index = 0; Index < GetPchXhciMaxUsb3PortNum (); Index++) {
    // Check if port should be disabled
    if ((Usb3DisabledPorts & (BIT0 << Index)) != 0) {
      // Check if PDO bit has not been set yet
      if (((XhciUsb3Pdo & (UINT32)(BIT0 << Index)) == 0)) {
        // Calculate port PortSC register offset in xHCI MMIO space
        XhciUsb3PortScOffset = (XhciUsb3PortScStartingOffset + (Index * 0x10));

        // Disable port before setting PDO bit for it
        MmioOr32 (XhciMmioBase + XhciUsb3PortScOffset, B_PCH_XHCI_PORTSCXUSB3_PED);

        // Poll for PLS (PortSC[5:8]) to reach 0x4 before setting PDO for the port
        for (XhciPlsTimeout = 0; XhciPlsTimeout < PORT_PLS_TIMEOUT; XhciPlsTimeout++) {
          if ((MmioRead32 (XhciMmioBase + XhciUsb3PortScOffset) & B_PCH_XHCI_PORTSCXUSB3_PLS) == V_PCH_XHCI_PORTSCXUSB3_PLS_DISABLED) {
            break;
          }
          MicroSecondDelay (10);
        }

        if (XhciPlsTimeout == PORT_PLS_TIMEOUT) {
          DEBUG ((DEBUG_ERROR, "[xHCI PDO] PLS Polling for Disabled has timed out for port %d\n", Index + 1));
        }

        // A '1' in a bit position prevents the corresponding USB3 port from reporting a Device Connection to the XHC
        XhciUsb3Pdo |= (UINT32) (B_PCH_XHCI_USB3PDO_DIS_PORT0 << Index);

        // Put port back to RxDetect state
        // Need to write LWS and PLS regs with same MMIO operation otherwise PLS write will be ignored
        MmioOr32 (XhciMmioBase + XhciUsb3PortScOffset, (B_PCH_XHCI_PORTSCXUSB3_LWS | V_PCH_XHCI_PORTSCXUSB3_PLS_RXDETECT));
      }
    }
  }

  //
  // USB2PDO and USB3PDO are Write-Once registers and bits in them are in the SUS Well.
  //
  MmioWrite32 (XhciMmioBase + R_PCH_XHCI_USB2PDO, XhciUsb2Pdo);
  MmioWrite32 (XhciMmioBase + R_PCH_XHCI_USB3PDO, XhciUsb3Pdo);

  XhciUsb2PdoRd = MmioRead32 (XhciMmioBase + R_PCH_XHCI_USB2PDO);
  XhciUsb3PdoRd = MmioRead32 (XhciMmioBase + R_PCH_XHCI_USB3PDO);

  //
  // If USB2PDO and USB3PDO are not updated successfully perform Warm Reset to unlock RWO bits.
  //
  if ((XhciUsb2Pdo != XhciUsb2PdoRd) || (XhciUsb3Pdo != XhciUsb3PdoRd)) {
    DEBUG ((DEBUG_ERROR, "UsbPdoProgramming: PDO register locked. Performing Warm Reset\n"));
    return TRUE;
  }

  DEBUG ((DEBUG_INFO, "UsbPdoProgramming () end\n"));
  return FALSE;
}

