/**@file
  Thunderbolt(TM) Pei Library

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

**/
#include <Library/PeiServicesLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/GpioLib.h>
#include <GpioPinsSklLp.h>
#include <GpioPinsSklH.h>
#include <Library/TimerLib.h>
#include <Library/IoLib.h>
#include <Library/MmPciLib.h>
#include <Library/HobLib.h>
#include <Library/PcdLib.h>
#include <Library/GpioExpanderLib.h>
#include <Ppi/ReadOnlyVariable2.h>

#include <Base.h>
#include <Library/TbtCommonLib.h>
#include <TbtBoardInfo.h>
#include <IndustryStandard/Pci22.h>
#include <Library/PchCycleDecodingLib.h>
#include <Ppi/PeiTbtPolicy.h>
#include <Library/PciSegmentLib.h>
#include <Library/PeiTbtPolicyLib.h>
#include <Library/PchPmcLib.h>
#include <Private/Library/PeiDTbtInitLib.h>

/**
Is host router (For dTBT) or End Point (For iTBT) present before sleep

@param[in] ControllerType - DTBT_CONTROLLER or ITBT_CONTROLLER
@param[in] Controller     - Controller begin offset of CMOS

@Retval     TRUE      There is a TBT HostRouter presented before sleep
@Retval     FALSE     There is no TBT HostRouter presented before sleep

BOOLEAN
IsHostRouterPresentBeforeSleep(
IN  UINT8        ControllerType,
IN  UINT8        Controller
)
{
  UINT8 SavedState;

  SavedState = (UINT8)GetTbtHostRouterStatus();
  if (ControllerType == DTBT_CONTROLLER){
    return ((SavedState & (DTBT_SAVE_STATE_OFFSET << Controller)) == (DTBT_SAVE_STATE_OFFSET << Controller));
  } else {
    if (ControllerType == ITBT_CONTROLLER) {
      return ((SavedState & (ITBT_SAVE_STATE_OFFSET << Controller)) == (ITBT_SAVE_STATE_OFFSET << Controller));
    }
  }
  return 0;
}
**/

/**
Execute TBT PCIE2TBT_SX_EXIT_TBT_CONNECTED Mail Box Command for S4 mode with PreBootAclEnable

@param[in]  Bus       Bus number for Host Router (DTBT)
@param[in]  Device    Device number for Host Router (DTBT)
@param[in]  Function  Function number for Host Router (DTBT)
@param[in]  Timeout   Time out with 100 ms garnularity
@Retval     true      if command executes succesfully
**/
BOOLEAN
TbtSetPcie2TbtSxExitCommandWithPreBootAclEnable(
   IN    UINT8                   Bus,
   IN    UINT8                   Device,
   IN    UINT8                   Function,
   IN    UINT32                  Timeout
)
{
  UINT64      Pcie2Tbt;
  UINT64      Tbt2Pcie;
  UINT32      RegisterValue;
  BOOLEAN     ReturnFlag;
  UINT32      Command;

  GET_TBT2PCIE_REGISTER_ADDRESS(0, Bus, Device, Function, Tbt2Pcie)
  GET_PCIE2TBT_REGISTER_ADDRESS(0, Bus, Device, Function, Pcie2Tbt)

// If PreBootAcl is Enable, we need to enable DATA bit while sending SX EXIT MAIL BOX Command
  Command = (1 << 8) | PCIE2TBT_SX_EXIT_TBT_CONNECTED;
  PciSegmentWrite32 (Pcie2Tbt, Command | PCIE2TBT_VLD_B);

  ReturnFlag = InternalWaitforCommandCompletion(Tbt2Pcie, Timeout, TRUE, &RegisterValue);

  PciSegmentWrite32(Pcie2Tbt, 0);

  return ReturnFlag;
}

/**
Set the Sleep Mode if the HR is up.
@param[in]  Bus       Bus number for Host Router (DTBT)
@param[in]  Device    Device number for Host Router (DTBT)
@param[in]  Function  Function number for Host Router (DTBT)
**/
VOID
TbtSetSxMode(
IN    UINT8                   Bus,
IN    UINT8                   Device,
IN    UINT8                   Function,
IN    UINT8                   TbtBootOn
)
{
  UINT64                          TbtUsDevId;
  UINT64                          Tbt2Pcie;
  UINT32                          RegVal;
  UINT32                          MaxLoopCount;
  UINTN                           Delay;
  UINT8                           RetCode;
  EFI_BOOT_MODE                   BootMode;
  EFI_STATUS                      Status;

  TbtUsDevId = PCI_SEGMENT_LIB_ADDRESS(0, Bus, Device, Function, 0);
  GET_TBT2PCIE_REGISTER_ADDRESS(0, Bus, Device, Function, Tbt2Pcie)

  MaxLoopCount = TBT_5S_TIMEOUT;  // Wait 5 sec
  Delay = 100 * 1000;
  RetCode = 0x62;

  Status = PeiServicesGetBootMode(&BootMode);
  ASSERT_EFI_ERROR(Status);

  if ((BootMode == BOOT_ON_S4_RESUME) && (TbtBootOn == 2)) {
    MaxLoopCount = TBT_3S_TIMEOUT;
    if (!TbtSetPcie2TbtSxExitCommandWithPreBootAclEnable(Bus, Device, Function, MaxLoopCount)) {
      //
      // Nothing to wait, HR is not responsive
      //
      return;
    }
  }
  else {
    if (!TbtSetPcie2TbtCommand(PCIE2TBT_SX_EXIT_TBT_CONNECTED, Bus, Device, Function, MaxLoopCount)) {
      //
      // Nothing to wait, HR is not responsive
      //
      return;
    }
  }

  DEBUG((DEBUG_INFO, "Wait for Dev ID != 0xFF\n"));

  while (MaxLoopCount-- > 0) {
    //
    // Check what HR still here
    //
    RegVal = PciSegmentRead32(Tbt2Pcie);
    if (0xFFFFFFFF == RegVal) {
      RetCode = 0x6F;
      break;
    }
    //
    // Check completion of TBT link
    //
    RegVal = PciSegmentRead32(TbtUsDevId);
    if (0xFFFFFFFF != RegVal) {
      RetCode = 0x61;
      break;
    }

    MicroSecondDelay(Delay);
  }

  DEBUG((DEBUG_INFO, "Return code = 0x%x\n", RetCode));
}
/**
  set tPCH25 Timing to 10 ms for DTBT.

  @param[in]  PEI_TBT_POLICY   PeiTbtConfig

  @retval     EFI_SUCCESS  The function completes successfully
  @retval     EFI_UNSUPPORTED  dTBT is not supported.
**/
EFI_STATUS
EFIAPI
DTbtSetTPch25Timing (
  IN  PEI_TBT_POLICY  *PeiTbtConfig
)
{
  DEBUG ((DEBUG_INFO, "DTbtSetTPch25Timing call Inside\n"));
  UINT32                PchPwrmBase;

  //
  //During boot, reboot and wake  tPCH25 Timing should be set to 10 ms
  //
  PchPwrmBaseGet (&PchPwrmBase);
  MmioOr32 (
    (UINTN) (PchPwrmBase + R_PCH_PWRM_CFG),
    (BIT0 | BIT1)
    );

  DEBUG((DEBUG_INFO, "DTbtSetTPch25Timing call Return\n"));
  return EFI_SUCCESS;
}

/**
  Do ForcePower for DTBT Controller

  @param[in]  PEI_TBT_POLICY   PeiTbtConfig

  @retval     EFI_SUCCESS  The function completes successfully
  @retval     EFI_UNSUPPORTED  dTBT is not supported.
**/
EFI_STATUS
EFIAPI
DTbtForcePower (
  IN  PEI_TBT_POLICY  *PeiTbtConfig
)
{

  DEBUG ((DEBUG_INFO, "DTbtForcePower call Inside\n"));

      if (PeiTbtConfig->DTbtCommonConfig.Gpio3ForcePwr) {
        DEBUG((DEBUG_INFO, "ForcePwrGpio.GpioPad = %x \n", PeiTbtConfig-> DTbtControllerConfig.ForcePwrGpio.GpioPad));
        ForceDtbtPower(PeiTbtConfig-> DTbtControllerConfig.ForcePwrGpio.GpioAccessType,PeiTbtConfig-> DTbtControllerConfig.ForcePwrGpio.Expander, PeiTbtConfig-> DTbtControllerConfig.ForcePwrGpio.GpioPad, PeiTbtConfig-> DTbtControllerConfig.ForcePwrGpio.GpioLevel);
        DEBUG((DEBUG_INFO, "ForceDtbtPower asserted \n"));
        MicroSecondDelay(PeiTbtConfig->DTbtCommonConfig.Gpio3ForcePwrDly * 1000);
        DEBUG((DEBUG_INFO, "Delay after ForceDtbtPower = 0x%x ms \n", PeiTbtConfig->DTbtCommonConfig.Gpio3ForcePwrDly));
      }

  DEBUG ((DEBUG_INFO, "DTbtForcePower call Return\n"));
  return EFI_SUCCESS;
}

/**
  Clear VGA Registers for DTBT.

  @param[in]  PEI_TBT_POLICY   PeiTbtConfig

  @retval     EFI_SUCCESS  The function completes successfully
  @retval     EFI_UNSUPPORTED  dTBT is not supported.
**/
EFI_STATUS
EFIAPI
DTbtClearVgaRegisters (
  IN  PEI_TBT_POLICY  *PeiTbtConfig
)
{
  UINTN      RpDev;
  UINTN      RpFunc;
  EFI_STATUS Status;
  UINT64     BridngeBaseAddress;
  UINT16     Data16;

  DEBUG ((DEBUG_INFO, "DTbtClearVgaRegisters call Inside\n"));

  Status = EFI_SUCCESS;

  Status = GetDTbtRpDevFun(PeiTbtConfig-> DTbtControllerConfig.Type, PeiTbtConfig-> DTbtControllerConfig.PcieRpNumber - 1, &RpDev, &RpFunc);
  ASSERT_EFI_ERROR(Status);
  //
  // VGA Enable and VGA 16-bit decode registers of Bridge control register of Root port where
  // Host router resides should be cleaned
  //
 
  BridngeBaseAddress = PCI_SEGMENT_LIB_ADDRESS(0, 0, (UINT32)RpDev, (UINT32)RpFunc, 0);
  Data16 = PciSegmentRead16(BridngeBaseAddress + PCI_BRIDGE_CONTROL_REGISTER_OFFSET);
  Data16 &= (~(EFI_PCI_BRIDGE_CONTROL_VGA | EFI_PCI_BRIDGE_CONTROL_VGA_16));
  PciSegmentWrite16(BridngeBaseAddress + PCI_BRIDGE_CONTROL_REGISTER_OFFSET, Data16);

  DEBUG ((DEBUG_INFO, "DTbtClearVgaRegisters call Return\n"));
  return Status;
}

/**
  Exectue Mail box command "Boot On".

  @param[in]  PEI_TBT_POLICY   PeiTbtConfig

  @retval     EFI_SUCCESS  The function completes successfully
  @retval     EFI_UNSUPPORTED  dTBT is not supported.
**/
EFI_STATUS
EFIAPI
DTbtBootOn(
  IN  PEI_TBT_POLICY  *PeiTbtConfig
)
{
  EFI_STATUS Status;
  UINT32     OrgBusNumberConfiguration;
  UINTN      RpDev;
  UINTN      RpFunc;

  DEBUG((DEBUG_INFO, "DTbtBootOn call Inside\n"));

  Status = EFI_SUCCESS;

      Status = GetDTbtRpDevFun(PeiTbtConfig-> DTbtControllerConfig.Type, PeiTbtConfig-> DTbtControllerConfig.PcieRpNumber - 1, &RpDev, &RpFunc);
      ASSERT_EFI_ERROR(Status);
      OrgBusNumberConfiguration = PciSegmentRead32 (PCI_SEGMENT_LIB_ADDRESS (0, 0, RpDev, RpFunc, PCI_BRIDGE_PRIMARY_BUS_REGISTER_OFFSET));
      //
      // Set Sec/Sub buses to 0xF0
      //
      PciSegmentWrite32(PCI_SEGMENT_LIB_ADDRESS (0, 0, RpDev, RpFunc, PCI_BRIDGE_PRIMARY_BUS_REGISTER_OFFSET), 0x00F0F000);
      //
      //When Thunderbolt(TM) boot [TbtBootOn] is enabled in bios setup we need to do the below:
      //Bios should send "Boot On" message through PCIE2TBT register
      //The Boot On command as described above would include the command and acknowledge from FW (with the default timeout in BIOS),
      //once the Boot On command is completed it is guaranteed that the AlpineRidge(AR) device is there and the PCI tunneling was done by FW,
      //next step from BIOS is enumeration using SMI
      //

      if (PeiTbtConfig->DTbtCommonConfig.TbtBootOn > 0) {
        //
        // Exectue Mail box command "Boot On / Pre-Boot ACL"
        //
        //Command may be executed only during boot/reboot and not during Sx exit flow
        if (PeiTbtConfig->DTbtCommonConfig.TbtBootOn == 1) {
          if (!TbtSetPcie2TbtCommand(PCIE2TBT_BOOT_ON, 0xF0, 0, 0, TBT_5S_TIMEOUT)) {
            //
            // Nothing to wait, HR is not responsive
            //
            DEBUG((DEBUG_INFO, "<TbtPei> DTbtBootOn - Boot On message sent failed \n"));
          }
        }
        if (PeiTbtConfig->DTbtCommonConfig.TbtBootOn == 2) {
          if (!TbtSetPcie2TbtCommand(PCIE2TBT_PREBOOTACL, 0xF0, 0, 0, TBT_3S_TIMEOUT)) {
            //
            // Nothing to wait, HR is not responsive
            //
            DEBUG((DEBUG_INFO, "<TbtPei> DTbtBootOn - Pre-Boot ACL message sent failed \n"));
          }
        }
      }
      //
      // Reset Sec/Sub buses to original value
      //
      PciSegmentWrite32(PCI_SEGMENT_LIB_ADDRESS (0, 0, RpDev, RpFunc, PCI_BRIDGE_PRIMARY_BUS_REGISTER_OFFSET), OrgBusNumberConfiguration);

  DEBUG((DEBUG_INFO, "DTbtBootOn call Return\n"));
  return Status;
}

/**
  Exectue Mail box command "USB On".

  @param[in]  PEI_TBT_POLICY   PeiTbtConfig

  @retval     EFI_SUCCESS  The function completes successfully
  @retval     EFI_UNSUPPORTED  dTBT is not supported.
**/
EFI_STATUS
EFIAPI
DTbtUsbOn(
  IN  PEI_TBT_POLICY  *PeiTbtConfig
)
{
  EFI_STATUS                      Status;
  UINTN                           RpDev;
  UINTN                           RpFunc;
  UINT32                          OrgBusNumberConfiguration;
  UINT64                          TbtBaseAddress;
  UINT32                          MaxWaitIter;
  UINT32                          RegVal;
  EFI_BOOT_MODE                   BootMode;

  DEBUG((DEBUG_INFO, "DTbtUsbOn call Inside\n"));

  Status = EFI_SUCCESS;

      Status = GetDTbtRpDevFun(PeiTbtConfig-> DTbtControllerConfig.Type, PeiTbtConfig-> DTbtControllerConfig.PcieRpNumber - 1, &RpDev, &RpFunc);
      ASSERT_EFI_ERROR(Status);
      OrgBusNumberConfiguration = PciSegmentRead32(PCI_SEGMENT_LIB_ADDRESS (0, 0, RpDev, RpFunc, PCI_BRIDGE_PRIMARY_BUS_REGISTER_OFFSET));
      //
      // Set Sec/Sub buses to 0xF0
      //
      PciSegmentWrite32(PCI_SEGMENT_LIB_ADDRESS (0, 0, RpDev, RpFunc, PCI_BRIDGE_PRIMARY_BUS_REGISTER_OFFSET), 0x00F0F000);

      //
      //When Thunderbolt(TM) Usb boot [TbtUsbOn] is enabled in bios setup we need to do the below:
      //Bios should send "Usb On" message through PCIE2TBT register
      //The Usb On command as described above would include the command and acknowledge from FW (with the default timeout in BIOS),
      //once the Usb On command is completed it is guaranteed that the AlpineRidge(AR) device is there and the PCI tunneling was done by FW,
      //next step from BIOS is enumeration using SMI
      //
      if (PeiTbtConfig->DTbtCommonConfig.TbtUsbOn) {
        if (PeiTbtConfig->DTbtCommonConfig.TbtBootOn > 0) {
          MaxWaitIter = 50;   // Wait 5 sec
          TbtBaseAddress = PCI_SEGMENT_LIB_ADDRESS(0, 0xF0, 0, 0, 0);
          //
          // Driver clears the PCIe2TBT Valid bit to support two consicutive mailbox commands
          //
          PciSegmentWrite32(TbtBaseAddress + PCIE2TBT_DTBT_R, 0);
          DEBUG((DEBUG_INFO, "TbtBaseAddress + PCIE2TBT_DTBT_R = 0x%lx \n", TbtBaseAddress + PCIE2TBT_DTBT_R));
          while (MaxWaitIter-- > 0) {
            RegVal = PciSegmentRead32(TbtBaseAddress + TBT2PCIE_DTBT_R);
            if (0xFFFFFFFF == RegVal) {
              //
              // Device is not here return now
              //
              DEBUG((DEBUG_INFO, "TBT device is not present \n"));
              break;
            }

            if (!(RegVal & TBT2PCIE_DON_R)) {
              break;
            }
            MicroSecondDelay(100 * 1000);
          }
        }

        Status = PeiServicesGetBootMode(&BootMode);
        ASSERT_EFI_ERROR(Status);

        //
        // Exectue Mail box command "Usb On"
        //
        //Command may be executed only during boot/reboot and not during S3 exit flow
        //In case of S4 Exit send USB ON cmd only if Host Router was inactive/not present during S4 entry
        if ((BootMode == BOOT_ON_S4_RESUME) ) {
          // USB_ON cmd not required
        } else {
          if (!TbtSetPcie2TbtCommand(PCIE2TBT_USB_ON, 0xF0, 0, 0, TBT_5S_TIMEOUT)) {
            //
            // Nothing to wait, HR is not responsive
            //
            DEBUG((DEBUG_INFO, "<TbtPei> TbtBootSupport - Usb On message sent failed \n"));
          }
        }
      }
      //
      // Reset Sec/Sub buses to original value
      //
      PciSegmentWrite32(PCI_SEGMENT_LIB_ADDRESS (0, 0, RpDev, RpFunc, PCI_BRIDGE_PRIMARY_BUS_REGISTER_OFFSET), OrgBusNumberConfiguration);

  DEBUG((DEBUG_INFO, "DTbtUsbOn call return\n"));
  return Status;
}

/**
  Exectue Mail box command "Sx Exit".

  @param[in]  PEI_TBT_POLICY   PeiTbtConfig

  @retval     EFI_SUCCESS  The function completes successfully
  @retval     EFI_UNSUPPORTED  dTBT is not supported.
**/
EFI_STATUS
EFIAPI
DTbtSxExitFlow(
  IN  PEI_TBT_POLICY  *PeiTbtConfig
)
{
  EFI_STATUS                      Status;
  UINT32                          OrgBusNumberConfiguration;
  UINTN                           RpDev;
  UINTN                           RpFunc;
  UINT32                          Count;

  DEBUG((DEBUG_INFO, "DTbtSxExitFlow call Inside\n"));

  Status = EFI_SUCCESS;
  Count = 0;

      Status = GetDTbtRpDevFun(PeiTbtConfig-> DTbtControllerConfig.Type, PeiTbtConfig-> DTbtControllerConfig.PcieRpNumber - 1, &RpDev, &RpFunc);
      ASSERT_EFI_ERROR(Status);
      OrgBusNumberConfiguration = PciSegmentRead32(PCI_SEGMENT_LIB_ADDRESS (0, 0, RpDev, RpFunc, PCI_BRIDGE_PRIMARY_BUS_REGISTER_OFFSET));
      //
      // Set Sec/Sub buses to 0xF0
      //
      PciSegmentWrite32(PCI_SEGMENT_LIB_ADDRESS (0, 0, RpDev, RpFunc, PCI_BRIDGE_PRIMARY_BUS_REGISTER_OFFSET), 0x00F0F000);

      if ( (PeiTbtConfig->DTbtCommonConfig.TbtBootOn == 2)) {
        //
        // WA: When system with TBT 3.1 device, resume SX system need to wait device ready. In document that maximum time out should be 500ms.
        //
        while (PciSegmentRead32(PCI_SEGMENT_LIB_ADDRESS(0, 0xf0, 0x0, 0x0, 0x08)) == 0xffffffff) { //End Device will be with Device Number 0x0, Function Number 0x0.
          MicroSecondDelay(STALL_ONE_MICRO_SECOND * 1000); // 1000usec
          Count++;
          if (Count > 10000) { //Allowing Max Delay of 10 sec for CFL-S board.
          break;
          }
        }

        //
        // Upon wake, if BIOS saved pre-Sx Host Router state as active (system went to sleep with
        // attached devices), BIOS should:
        // 1. Execute "Sx_Exit_TBT_Connected" mailbox command.
        // 2. If procedure above returns true, BIOS should perform "wait for fast link bring-up" loop
        // 3. Continue regular wake flow.
        //
        //
        // Exectue Mail box command and perform "wait for fast link bring-up" loop
        //
        TbtSetSxMode(0xF0, 0, 0, PeiTbtConfig->DTbtCommonConfig.TbtBootOn);
      }
      //
      // Reset Sec/Sub buses to original value
      //
      PciSegmentWrite32(PCI_SEGMENT_LIB_ADDRESS (0, 0, RpDev, RpFunc, PCI_BRIDGE_PRIMARY_BUS_REGISTER_OFFSET), OrgBusNumberConfiguration);

  DEBUG((DEBUG_INFO, "DTbtSxExitFlow call Return\n"));
  return Status;
}


/**
  Initialize Thunderbolt(TM)

  @retval     EFI_SUCCESS  The function completes successfully
  @retval     others
**/
EFI_STATUS
EFIAPI
TbtInit (
  VOID
  )
{
  EFI_STATUS            Status;
  PEI_TBT_POLICY             *PeiTbtConfig;

  //
  // Get the TBT Policy
  //
  Status = PeiServicesLocatePpi (
             &gPeiTbtPolicyPpiGuid,
             0,
             NULL,
             (VOID **) &PeiTbtConfig
             );
  if (EFI_ERROR(Status)) {
    DEBUG ((DEBUG_ERROR, " gPeiTbtPolicyPpiGuid Not installed!!!\n"));
  }
  ASSERT_EFI_ERROR (Status);
  //
  // Exectue Mail box command "Boot On"
  //
  Status = DTbtBootOn (PeiTbtConfig);
  //
  // Exectue Mail box command "Usb On"
  //
  Status = DTbtUsbOn (PeiTbtConfig);
  //
  //During boot, reboot and wake  (bits [1:0]) of PCH PM_CFG register should be
  //set to 11b - 10 ms (default value is 0b - 10 us)
  //
  Status = DTbtSetTPch25Timing (PeiTbtConfig);
  //
  // Configure Tbt Force Power 
  //
  Status = DTbtForcePower (PeiTbtConfig);
  //
  // VGA Enable and VGA 16-bit decode registers of Bridge control register of Root port where
  // Host router resides should be cleaned
  //
  Status = DTbtClearVgaRegisters (PeiTbtConfig);
  //
  // Upon wake, if BIOS saved pre-Sx Host Router state as active (system went to sleep with
  // attached devices), BIOS should:
  // 1. Execute "Sx_Exit_TBT_Connected" mailbox command.
  // 2. If procedure above returns true, BIOS should perform "wait for fast link bring-up" loop
  // 3. Continue regular wake flow.
  //
  Status = DTbtSxExitFlow (PeiTbtConfig);
  return EFI_SUCCESS;
}
