/** @file
  This is the Common driver that initializes the Intel PCH.

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

**/
#include "PchInit.h"

//
// Module variables
//
GLOBAL_REMOVE_IF_UNREFERENCED PCH_CONFIG_HOB           *mPchConfigHob;
GLOBAL_REMOVE_IF_UNREFERENCED SI_CONFIG_HOB            *mSiConfigHob;

/**
  Common PchInit Module Entry Point
**/
VOID
PchInitEntryPointCommon (
  VOID
  )
{
  EFI_PEI_HOB_POINTERS  HobPtr;

  DEBUG ((DEBUG_INFO, "PchInitEntryPointCommon() Start\n"));

  //
  // Get PCH Config HOB.
  //
  HobPtr.Guid   = GetFirstGuidHob (&gPchConfigHobGuid);
  ASSERT (HobPtr.Guid != NULL);
  mPchConfigHob = (PCH_CONFIG_HOB *) GET_GUID_HOB_DATA (HobPtr.Guid);

  //
  // Get Silicon Config data HOB
  //
  HobPtr.Guid   = GetFirstGuidHob (&gSiConfigHobGuid);
  ASSERT (HobPtr.Guid != NULL);
  mSiConfigHob = (SI_CONFIG_HOB *) GET_GUID_HOB_DATA (HobPtr.Guid);

  DEBUG ((DEBUG_INFO, "PchInitEntryPointCommon() End\n"));

  return;
}

/**
  Lock USB registers before boot

  @param[in] PchConfigHob      The PCH Config HOB
**/
VOID
LockXhciConfiguration (
  IN  CONST PCH_CONFIG_HOB    *PchConfigHob
  )
{
  UINTN                       XhciPciMmBase;
  UINT32                      XhccCfg;

  DEBUG ((DEBUG_INFO, "LockXhciConfiguration () - Start\n"));

  XhciPciMmBase = MmPciBase (
                    DEFAULT_PCI_BUS_NUMBER_PCH,
                    PCI_DEVICE_NUMBER_PCH_XHCI,
                    PCI_FUNCTION_NUMBER_PCH_XHCI
                    );

  ///
  /// PCH BIOS Spec Section 13.2.4 Locking xHCI Register Settings
  /// PCH BIOS Spec Locking xHCI Register settings
  /// After xHCI is initialized, BIOS should lock the xHCI configuration registers to RO.
  /// This prevent any unintended changes. There is also a lockdown feature for OverCurrent
  /// registers. BIOS should set these bits to lock down the settings prior to end of POST.
  /// 1. Set Access Control bit at XHCI PCI offset 40h[31] to 1b to lock xHCI register settings.
  /// 2. Set OC Configuration Done bit at XHCI PCI offset 44h[31] to lock overcurrent mappings from
  ///    further changes.
  ///
  MmioOr32 (XhciPciMmBase + R_PCH_XHCI_XHCC2, (UINT32) (BIT31));
  S3BootScriptSaveMemWrite (
    S3BootScriptWidthUint32,
    (UINTN) (XhciPciMmBase + R_PCH_XHCI_XHCC2),
    1,
    (VOID *) (UINTN) (XhciPciMmBase + R_PCH_XHCI_XHCC2)
    );

  ///
  /// PCH BIOS Spec xHCI controller setup
  /// Note:
  /// XHCI PCI offset 40h is write once register.
  /// Unsupported Request Detected bit is write clear
  ///
  XhccCfg = MmioRead32 (XhciPciMmBase + R_PCH_XHCI_XHCC1);
  XhccCfg &= (UINT32) ~(B_PCH_XHCI_XHCC1_URD);
  XhccCfg |= (UINT32) (B_PCH_XHCI_XHCC1_ACCTRL);
  MmioWrite32 (XhciPciMmBase + R_PCH_XHCI_XHCC1, XhccCfg);
  S3BootScriptSaveMemWrite (
    S3BootScriptWidthUint32,
    (UINTN) (XhciPciMmBase + R_PCH_XHCI_XHCC1),
    1,
    (VOID *) (UINTN) (XhciPciMmBase + R_PCH_XHCI_XHCC1)
    );

  DEBUG ((DEBUG_INFO, "LockXhciConfiguration () - End\n"));
}

/**
  Process all the lock downs
**/
VOID
ProcessAllLocks (
  VOID
  )
{
  UINTN         Index;
  UINT8         Data8;
  UINT16        Data16And;
  UINT16        Data16Or;
  UINT32        Data32;
  UINT32        Data32And;
  UINT32        Data32Or;
  UINT32        DlockValue;
  UINTN         PciLpcRegBase;
  UINTN         PciSpiRegBase;
  UINTN         PciPmcRegBase;
  UINT16        TcoBase;
  UINT32        PchPwrmBase;
  UINTN         PchSpiBar0;

  PciLpcRegBase   = MmPciBase (
                      DEFAULT_PCI_BUS_NUMBER_PCH,
                      PCI_DEVICE_NUMBER_PCH_LPC,
                      PCI_FUNCTION_NUMBER_PCH_LPC
                      );
  PciPmcRegBase   = MmPciBase (
                      DEFAULT_PCI_BUS_NUMBER_PCH,
                      PCI_DEVICE_NUMBER_PCH_PMC,
                      PCI_FUNCTION_NUMBER_PCH_PMC
                      );
  PciSpiRegBase   = MmPciBase (
                      DEFAULT_PCI_BUS_NUMBER_PCH,
                      PCI_DEVICE_NUMBER_PCH_SPI,
                      PCI_FUNCTION_NUMBER_PCH_SPI
                      );
  PchSpiBar0      = MmioRead32 (PciSpiRegBase + R_PCH_SPI_BAR0) &~(B_PCH_SPI_BAR0_MASK);

  PchTcoBaseGet (&TcoBase);

  //
  // Lock XHCI configuration
  //
  LockXhciConfiguration (mPchConfigHob);

  ///
  /// Set PWRMBASE + 620h [31] to lock the ST and NST PG register fields.
  ///
  ConfigurePmcStaticFunctionDisableLock ();

  ///
  /// SKL PCH BWG 7.2.4 Additional PCH DMI and OP-DMI Programming Steps
  /// Step 9.2
  /// Set PCR[DMI] + 2234h [31] = 1b.
  /// Leave this in DXE since setting it in PEI would break the ActiveBIOS module.
  ///
  Data32And = 0xFFFFFFFF;
  Data32Or = B_PCH_PCR_DMI_DMIC_SRL;
  PchPcrAndThenOr32 (
    PID_DMI, R_PCH_PCR_DMI_DMIC,
    Data32And,
    Data32Or
    );
  PCH_PCR_BOOT_SCRIPT_READ_WRITE (
    S3BootScriptWidthUint32,
    PID_DMI, R_PCH_PCR_DMI_DMIC,
    &Data32Or,
    &Data32And
    );


  ///
  /// Program the Flash Protection Range Register based on policy
  ///
  DlockValue = MmioRead32 (PchSpiBar0 + R_PCH_SPI_DLOCK);
  for (Index = 0; Index < PCH_FLASH_PROTECTED_RANGES; ++Index) {
    if ((mPchConfigHob->ProtectRange[Index].WriteProtectionEnable ||
         mPchConfigHob->ProtectRange[Index].ReadProtectionEnable) != TRUE) {
      continue;
    }

    ///
    /// Proceed to program the register after ensure it is enabled
    ///
    Data32 = 0;
    Data32 |= (mPchConfigHob->ProtectRange[Index].WriteProtectionEnable == TRUE) ? B_PCH_SPI_PRX_WPE : 0;
    Data32 |= (mPchConfigHob->ProtectRange[Index].ReadProtectionEnable == TRUE) ? B_PCH_SPI_PRX_RPE : 0;
    Data32 |= ((UINT32) mPchConfigHob->ProtectRange[Index].ProtectedRangeLimit << N_PCH_SPI_PRX_PRL) & B_PCH_SPI_PRX_PRL_MASK;
    Data32 |= ((UINT32) mPchConfigHob->ProtectRange[Index].ProtectedRangeBase << N_PCH_SPI_PRX_PRB) & B_PCH_SPI_PRX_PRB_MASK;
    DEBUG ((DEBUG_INFO, "Protected range %d: 0x%08x \n", Index, Data32));

    DlockValue |= (UINT32) (B_PCH_SPI_DLOCK_PR0LOCKDN << Index);
    MmioWrite32 ((UINTN) (PchSpiBar0 + (R_PCH_SPI_PR0 + (Index * S_PCH_SPI_PRX))), Data32);
    S3BootScriptSaveMemWrite (
      S3BootScriptWidthUint32,
      (UINTN) (PchSpiBar0 + (R_PCH_SPI_PR0 + (Index * S_PCH_SPI_PRX))),
      1,
      (VOID *) (UINTN) (PchSpiBar0 + (R_PCH_SPI_PR0 + (Index * S_PCH_SPI_PRX)))
      );
  }
  //
  // Program DLOCK register
  //
  MmioWrite32 ((UINTN) (PchSpiBar0 + R_PCH_SPI_DLOCK), DlockValue);
  S3BootScriptSaveMemWrite (
    S3BootScriptWidthUint32,
    (UINTN) (PchSpiBar0 + R_PCH_SPI_DLOCK),
    1,
    (VOID *) (UINTN) (PchSpiBar0 + R_PCH_SPI_DLOCK)
    );

  ///
  /// PCH BIOS Spec Section 3.6 Flash Security Recommendation
  /// In PCH SPI controller the BIOS should set the Flash Configuration Lock-Down bit
  /// (SPI_BAR0 + 04[15]) at end of post.  When set to 1, those Flash Program Registers
  /// that are locked down by this FLOCKDN bit cannot be written.
  /// Please refer to the EDS for which program registers are impacted.
  ///
  MmioOr32 ((UINTN) (PchSpiBar0 + R_PCH_SPI_HSFSC), (UINT32) (B_PCH_SPI_HSFSC_FLOCKDN));
  S3BootScriptSaveMemWrite (
    S3BootScriptWidthUint32,
    (UINTN) (PchSpiBar0 + R_PCH_SPI_HSFSC),
    1,
    (VOID *) (UINTN) (PchSpiBar0 + R_PCH_SPI_HSFSC)
    );

  ///
  /// SPI Flash Programming Guide Section 5.5.2 Vendor Component Lock
  /// It is strongly recommended that BIOS sets the Vendor Component Lock (VCL) bits. VCL applies
  /// the lock to both VSCC0 and VSCC1 even if VSCC0 is not used. Without the VCL bits set, it is
  /// possible to make Host/GbE VSCC register(s) changes in that can cause undesired host and
  /// integrated GbE Serial Flash functionality.
  ///
  MmioOr32 ((UINTN) (PchSpiBar0 + R_PCH_SPI_SFDP0_VSCC0), B_PCH_SPI_SFDP0_VSCC0_VCL);
  S3BootScriptSaveMemWrite (
    S3BootScriptWidthUint32,
    (UINTN) (PchSpiBar0 + R_PCH_SPI_SFDP0_VSCC0),
    1,
    (VOID *) (UINTN) (PchSpiBar0 + R_PCH_SPI_SFDP0_VSCC0)
    );

  ///
  /// Additional Power Management Programming
  /// Step 3
  /// Set GEN_PMCON_LOCK register, PMC PCI offset A6h = 06h, after stretch and ACPI base programming completed.
  ///
  MmioOr8 (
    (UINTN) (PciPmcRegBase + R_PCH_PMC_GEN_PMCON_B + 2),
    (UINT8) ((B_PCH_PMC_GEN_PMCON_B_SLPSX_STR_POL_LOCK | B_PCH_PMC_GEN_PMCON_B_ACPI_BASE_LOCK) >> 16)
    );
  S3BootScriptSaveMemWrite (
    S3BootScriptWidthUint8,
    (UINTN) (PciPmcRegBase + R_PCH_PMC_GEN_PMCON_B + 2),
    1,
    (VOID *) (UINTN) (PciPmcRegBase + R_PCH_PMC_GEN_PMCON_B + 2)
    );

  ///
  /// PCH BIOS Spec Section 3.6 Flash Security Recommendation
  /// BIOS needs to enable the BIOS Lock Enable (BLE) feature of the PCH by setting
  /// SPI/eSPI/LPC PCI offset DCh[1] = 1b.
  /// When this bit is set, attempts to write the BIOS Write Enable (BIOSWE) bit
  /// in PCH will cause a SMI which will allow the BIOS to verify that the write is
  /// from a valid source.
  /// Remember that BIOS needs to set SPI/LPC/eSPI PCI Offset DC [0] = 0b to enable
  /// BIOS region protection before exiting the SMI handler.
  /// Also, TCO_EN bit needs to be set (SMI_EN Register, ABASE + 30h[13] = 1b) to keep
  /// BLE feature enabled after booting to the OS.
  /// Intel requires that BIOS enables the Lock Enable (LE) feature of the PCH to
  /// ensure SMM protection of flash.
  /// Left to platform code to register a callback function to handle BiosWp SMI
  ///
  if (mPchConfigHob->LockDown.BiosLock == TRUE) {
    //
    // eSPI/LPC
    //
    if (! (MmioRead8 (PciLpcRegBase + R_PCH_LPC_BC) & B_PCH_LPC_BC_LE)) {
      DEBUG ((DEBUG_INFO, "Set LPC bios lock\n"));
      MmioOr8 ((UINTN) (PciLpcRegBase + R_PCH_LPC_BC), B_PCH_LPC_BC_LE);
      S3BootScriptSaveMemWrite (
        S3BootScriptWidthUint8,
        (UINTN) (PciLpcRegBase + R_PCH_LPC_BC),
        1,
        (VOID *) (UINTN) (PciLpcRegBase + R_PCH_LPC_BC)
        );
    }
    //
    // SPI
    //
    if (! (MmioRead8 (PciSpiRegBase + R_PCH_SPI_BC) & B_PCH_SPI_BC_LE)) {
      DEBUG ((DEBUG_INFO, "Set SPI bios lock\n"));
      MmioOr8 ((UINTN) (PciSpiRegBase + R_PCH_SPI_BC), (UINT8) B_PCH_SPI_BC_LE);
      S3BootScriptSaveMemWrite (
        S3BootScriptWidthUint8,
        (UINTN) (PciSpiRegBase + R_PCH_SPI_BC),
        1,
        (VOID *) (UINTN) (PciSpiRegBase + R_PCH_SPI_BC)
        );
    }
  }

  ///
  /// PCH BIOS Spec Section 3.6 Flash Security Recommendation
  /// BIOS also needs to set the BIOS Interface Lock Down bit in multiple locations
  /// (PCR[DMI] + 274Ch[0], LPC/eSPI PCI offset DCh[7] and SPI PCI offset DCh[7]).
  /// Setting these bits will prevent writes to the Top Swap bit (under their respective locations)
  /// and the Boot BIOS Straps. Enabling this bit will mitigate malicious software
  /// attempts to replace the system BIOS option ROM with its own code.
  ///
  if (mPchConfigHob->LockDown.BiosInterface == TRUE) {
    ///
    /// LPC
    ///
    MmioOr8 ((UINTN) (PciLpcRegBase + R_PCH_LPC_BC), (UINT32) B_PCH_LPC_BC_BILD);
    S3BootScriptSaveMemWrite (
      S3BootScriptWidthUint8,
      (UINTN) (PciLpcRegBase + R_PCH_LPC_BC),
      1,
      (VOID *) (UINTN) (PciLpcRegBase + R_PCH_LPC_BC)
      );

    ///
    /// Reads back for posted write to take effect
    ///
    Data8 = MmioRead8 ((UINTN) (PciLpcRegBase + R_PCH_LPC_BC));
    S3BootScriptSaveMemPoll  (
      S3BootScriptWidthUint8,
      (UINTN) (PciLpcRegBase + R_PCH_LPC_BC),
      &Data8,  // BitMask
      &Data8,  // BitValue
      1,          // Duration
      1           // LoopTimes
      );

    ///
    /// SPI
    ///
    MmioOr8 ((UINTN) (PciSpiRegBase + R_PCH_SPI_BC), (UINT32) B_PCH_SPI_BC_BILD);
    S3BootScriptSaveMemWrite (
      S3BootScriptWidthUint8,
      (UINTN) (PciSpiRegBase + R_PCH_SPI_BC),
      1,
      (VOID *) (UINTN) (PciSpiRegBase + R_PCH_SPI_BC)
      );

    ///
    /// Reads back for posted write to take effect
    ///
    Data8 = MmioRead8 ((UINTN) (PciSpiRegBase + R_PCH_SPI_BC));
    S3BootScriptSaveMemPoll  (
      S3BootScriptWidthUint8,
      (UINTN) (PciSpiRegBase + R_PCH_SPI_BC),
      &Data8,     // BitMask
      &Data8,     // BitValue
      1,          // Duration
      1           // LoopTimes
      );

    ///
    /// Set PCR[DMI] + 274C[0] = 1b
    ///
    Data32And = 0xFFFFFFFF;
    Data32Or = B_PCH_PCR_DMI_BILD;
    PchPcrAndThenOr32 (PID_DMI, R_PCH_PCR_DMI_GCS, Data32And, Data32Or);
    PCH_PCR_BOOT_SCRIPT_READ_WRITE (
      S3BootScriptWidthUint32,
      PID_DMI, R_PCH_PCR_DMI_GCS,
      &Data32Or,
      &Data32And
      );
  }

  ///
  /// PCH BIOS Spec Section 5.13 BIOS guide on using RTC RAM
  /// For Data integrity protection, set RTC Memory locks (Upper 128 Byte Lock and
  /// Lower 128 Byte Lock) at SBCR[RTC] + 3400h[4] and SBCR[RTC] + 3400h[3]. Note once locked
  /// bytes 0x38 - 0x3F in each of the Upper and Lower Byte blocks, respectively,
  /// cannot be unlocked until next reset.
  ///
  if (mPchConfigHob->LockDown.RtcLock == TRUE) {
    Data32And = 0xFFFFFFFF;
    Data32Or  = (B_PCH_PCR_RTC_CONF_UCMOS_LOCK | B_PCH_PCR_RTC_CONF_LCMOS_LOCK | B_PCH_PCR_RTC_CONF_RESERVED);
    PchPcrAndThenOr32 (
      PID_RTC, R_PCH_PCR_RTC_CONF,
      Data32And,
      Data32Or
      );
    PCH_PCR_BOOT_SCRIPT_READ_WRITE (
      S3BootScriptWidthUint32,
      PID_RTC, R_PCH_PCR_RTC_CONF,
      &Data32Or,
      &Data32And
      );
  }

  ///
  /// Lock Down TCO
  ///
  Data16And = 0xFFFF;
  Data16Or  = B_PCH_TCO_CNT_LOCK;
  IoOr16 (TcoBase + R_PCH_TCO1_CNT, Data16Or);
  S3BootScriptSaveIoReadWrite (
    S3BootScriptWidthUint16,
    (UINTN) (TcoBase + R_PCH_TCO1_CNT),
    &Data16Or,  // Data to be ORed
    &Data16And  // Data to be ANDed
    );

  ///
  /// PCH BIOS Spec Section 5.15.1 Additional Chipset Initialization
  /// Step 1
  /// Set SPIBAR0 + F0h [0] to 1b
  ///
  MmioOr8 ((UINTN) (PchSpiBar0 + R_PCH_SPI_SSML), B_PCH_SPI_SSML_SSL);

  //
  // Lock Down PMC
  // Set PWRM + 0x18 [27, 22] prior to OS.
  //
  PchPwrmBaseGet (&PchPwrmBase);
  Data32Or = BIT27;
  if (mPchConfigHob->Pm.PmcReadDisable) {
    Data32Or |= BIT22;
  }
  MmioOr32 (PchPwrmBase + R_PCH_PWRM_CFG, Data32Or);
  S3BootScriptSaveMemWrite (
    S3BootScriptWidthUint32,
    (UINTN) (PchPwrmBase + R_PCH_PWRM_CFG),
    1,
    (VOID *) (UINTN) (PchPwrmBase + R_PCH_PWRM_CFG)
    );
}

/**
  Set eSPI BME bit
**/
VOID
ConfigureEspiBme (
  VOID
  )
{
}

/**
  Common PCH initialization before Boot Sript Table is closed

**/
VOID
PchOnPciEnumCompleteCommon (
  VOID
  )
{
  UINT32                                    Data32Or;
  UINT32                                    Data32And;

  DEBUG ((DEBUG_INFO, "PchOnPciEnumCompleteCommon() Start\n"));

  ProcessAllLocks ();

  ///
  /// Perform remaining configuration for PCH SATA on End of DXE
  ///
  ConfigurePchSataOnEndOfDxe ();

  ///
  /// PSTHCTL (0xD00h[2]) = 1, PSTH IOSF Primary Trunk Clock Gating Enable (PSTHIOSFPTCGE)
  ///
  Data32And = 0xFFFFFFFF;
  Data32Or =  B_PCH_PCR_PSTH_PSTHIOSFPTCGE;
  PchPcrAndThenOr32 (PID_PSTH, R_PCH_PCR_PSTH_PSTHCTL, Data32And, Data32Or);
  PCH_PCR_BOOT_SCRIPT_READ_WRITE (
    S3BootScriptWidthUint32,
    PID_PSTH, R_PCH_PCR_PSTH_PSTHCTL,
    &Data32Or,
    &Data32And
    );

  //
  // Set eSPI BME after PCI enumeration
  //
  ConfigureEspiBme ();

  //
  // This function process P2SB configuration
  // This MUST be executed in very end of End Of Dxe
  // All PSF access after this function will not acceptable
  //
  ConfigureP2sbSbiLock ((BOOLEAN) mPchConfigHob->P2sb.SbiUnlock);

  DEBUG ((DEBUG_INFO, "PchOnPciEnumCompleteCommon() End\n"));
}
