/** @file
  PeiTbtInit library implementition with empty functions.

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

**/

#include <Library/DebugLib.h>
#include <Uefi/UefiBaseType.h>
#include <Library/PchPcieRpLib.h>
#include <Library/TbtCommonLib.h>
#include <Library/PciSegmentLib.h>
#include <Library/TimerLib.h>
#include <Library/BaseLib.h>
#include <Library/GpioLib.h>


/**
  Selects the proper TBT Root port to assign resources
  based on the user input value

  @param[in]  SetupData          Pointer to Setup data

  @retval     TbtSelectorChosen  Rootport number.
**/
VOID
GetRootporttoSetResourcesforTbt (
  IN UINTN                              RpIndex,
  OUT UINT8                             *RsvdExtraBusNum,
  OUT UINT16                            *RsvdPcieMegaMem,
  OUT UINT8                             *PcieMemAddrRngMax,
  OUT UINT16                            *RsvdPciePMegaMem,
  OUT UINT8                             *PciePMemAddrRngMax,
  OUT BOOLEAN                           *SetResourceforTbt
  )
{
  UINTN TbtRpNumber;
  TbtRpNumber = (UINTN) PcdGet8 (PcdDTbtPcieRpNumber);

    if (RpIndex == (TbtRpNumber - 1)) {
        *RsvdExtraBusNum = PcdGet8 (PcdDTbtPcieExtraBusRsvd);
        *RsvdPcieMegaMem = PcdGet16 (PcdDTbtPcieMemRsvd);
        *PcieMemAddrRngMax = PcdGet8 (PcdDTbtPcieMemAddrRngMax);
        *RsvdPciePMegaMem = PcdGet16 (PcdDTbtPciePMemRsvd);
        *PciePMemAddrRngMax = PcdGet8 (PcdDTbtPciePMemAddrRngMax);
        *SetResourceforTbt = TRUE;
      }
      else {
        *SetResourceforTbt = FALSE;
      }
  }

/**
  Internal function to Wait for Tbt2PcieDone Bit.to Set or clear
  @param[in]  CommandOffsetAddress      Tbt2Pcie Register Address
  @param[in]  TimeOut                   Time out with 100 ms garnularity
  @param[in]  Tbt2PcieDone              Wait condition (wait for Bit to Clear/Set)
  @param[out] *Tbt2PcieValue Function   Register value
**/
BOOLEAN
InternalWaitforCommandCompletion(
  IN  UINT64   CommandOffsetAddress,
  IN  UINT32   TimeOut,
  IN  BOOLEAN  Tbt2PcieDone,
  OUT UINT32   *Tbt2PcieValue
  )
{
  BOOLEAN ReturnFlag;
  UINT32  Tbt2PcieCheck;

  ReturnFlag = FALSE;
  while (TimeOut-- > 0) {
    *Tbt2PcieValue = PciSegmentRead32 (CommandOffsetAddress);

    if (0xFFFFFFFF == *Tbt2PcieValue ) {
      //
      // Device is not here return now
      //
      ReturnFlag     = FALSE;
      break;
    }

    if(Tbt2PcieDone) {
      Tbt2PcieCheck  =  *Tbt2PcieValue & TBT2PCIE_DON_R;
    } else {
      Tbt2PcieCheck  = !(*Tbt2PcieValue & TBT2PCIE_DON_R);
    }

    if (Tbt2PcieCheck) {
      ReturnFlag     = TRUE;
      break;
    }

    MicroSecondDelay(TBT_MAIL_BOX_DELAY);
  }
  return ReturnFlag;
}
/**
  Get Security Level.
  @param[in]  Bus       Bus number Host Router (DTBT)
  @param[in]  Device    Device number for Host Router (DTBT)
  @param[in]  Function  Function number for  Host Router (DTBT)
  @param[in]  Command   Command for  Host Router (DTBT)
  @param[in]  Timeout   Time out with 100 ms garnularity
**/
UINT8
GetSecLevel (
  IN    UINT8                   Bus,
  IN    UINT8                   Device,
  IN    UINT8                   Function,
  IN    UINT8                   Command,
  IN    UINT32                  Timeout
  )
{
  UINT64       Pcie2Tbt;
  UINT64       Tbt2Pcie;
  UINT32       RegisterValue;
  UINT8        ReturnFlag;

  ReturnFlag           = 0xFF;

  DEBUG ((DEBUG_INFO, "GetSecLevel() \n"));

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

  PciSegmentWrite32 (Pcie2Tbt, Command | PCIE2TBT_VLD_B);

  if(InternalWaitforCommandCompletion(Tbt2Pcie, Timeout, TRUE, &RegisterValue)) {
    ReturnFlag     = (UINT8) (0xFF & (RegisterValue >> 8));
  }

  PciSegmentWrite32 (Pcie2Tbt, 0);

  InternalWaitforCommandCompletion(Tbt2Pcie, Timeout, FALSE, &RegisterValue);
  DEBUG ((DEBUG_INFO, "Security Level configured to %x \n", ReturnFlag));

  return ReturnFlag;
}

/**
  Set Security Level.
  @param[in]  Data      Security State
  @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]  Command   Command for  Host Router (DTBT)
  @param[in]  Timeout   Time out with 100 ms garnularity
**/
BOOLEAN
SetSecLevel (
  IN    UINT8                   Data,
  IN    UINT8                   Bus,
  IN    UINT8                   Device,
  IN    UINT8                   Function,
  IN    UINT8                   Command,
  IN    UINT32                  Timeout
  )
{
  UINT64       Pcie2Tbt;
  UINT64       Tbt2Pcie;
  UINT32       RegisterValue;
  BOOLEAN      ReturnFlag;

  ReturnFlag   = FALSE;

  DEBUG ((DEBUG_INFO, "SetSecLevel() \n"));

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

  PciSegmentWrite32 (Pcie2Tbt, (Data << 8) | Command | PCIE2TBT_VLD_B);

  ReturnFlag = InternalWaitforCommandCompletion(Tbt2Pcie, Timeout, TRUE, &RegisterValue);
  DEBUG ((DEBUG_INFO, "RegisterValue %x \n", RegisterValue));
  PciSegmentWrite32 (Pcie2Tbt, 0);

  InternalWaitforCommandCompletion(Tbt2Pcie, Timeout, FALSE, &RegisterValue);
  DEBUG ((DEBUG_INFO, "Return value %x \n", ReturnFlag));
  return ReturnFlag;
}

/**
Based on the Security Mode Selection, BIOS drives FORCE_PWR.

@param[in]  GpioNumber
@param[in]  Value
**/
VOID
ForceDtbtPower(
  IN  UINT8          GpioAccessType,
  IN  UINT8          Expander,
  IN  UINT32         GpioNumber,
  IN  BOOLEAN        Value
)
{
  if (GpioAccessType == 0x01) {
    // PCH
    GpioSetOutputValue (GpioNumber, (UINT32)Value);
  } else if (GpioAccessType == 0x02) {
    // IoExpander {TCA6424A}
    GpioExpSetOutput (Expander, (UINT8)GpioNumber, (UINT8)Value);
  }
}

/**
Execute TBT Mail Box Command

@param[in]  Command   TBT Command
@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
TbtSetPcie2TbtCommand(
   IN    UINT8                   Command,
   IN    UINT8                   Bus,
   IN    UINT8                   Device,
   IN    UINT8                   Function,
   IN    UINT32                  Timeout
)
{
   UINT64      Pcie2Tbt;
   UINT64      Tbt2Pcie;
   UINT32      RegisterValue;
   BOOLEAN     ReturnFlag;

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

   PciSegmentWrite32 (Pcie2Tbt, Command | PCIE2TBT_VLD_B);

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

   PciSegmentWrite32(Pcie2Tbt, 0);

   return ReturnFlag;
}
/**
  Get Pch/Peg Pcie Root Port Device and Function Number for TBT by Root Port physical Number

  @param[in]  RpNumber              Root port physical number. (0-based)
  @param[out] RpDev                 Return corresponding root port device number.
  @param[out] RpFun                 Return corresponding root port function number.

  @retval     EFI_SUCCESS           Root port device and function is retrieved
  @retval     EFI_INVALID_PARAMETER If Invalid Root Port Number or TYPE is Passed
**/
EFI_STATUS
EFIAPI
GetDTbtRpDevFun (
  IN  BOOLEAN Type,
  IN  UINTN   RpNumber,
  OUT UINTN   *RpDev,
  OUT UINTN   *RpFunc
  )
{
  EFI_STATUS            Status;
  UINTN                 TbtRpDev;
  UINTN                 TbtRpFunc;

  Status = EFI_INVALID_PARAMETER; // Update the Status to EFI_SUCCESS if valid input found.
  //
  // CNL PCH-H can support up to 24 root ports. PEG0,PEG1 and PEG2 will be
  // with device number 0x1 and Function number 0,1 and 2 respectively.
  //
  if (Type == DTBT_TYPE_PEG)
  {
    //
    //  PEG Rootport
    //
    if (RpNumber <= 2) {
      *RpDev  =   0x01;
      *RpFunc =   RpNumber;
      Status  =   EFI_SUCCESS;
    }
  }
  if (Type == DTBT_TYPE_PCH)
  {
    //
    //  PCH Rootport
    //
    if (RpNumber <= 23) {
      Status  = GetPchPcieRpDevFun (RpNumber, &TbtRpDev, &TbtRpFunc);
      *RpDev  = TbtRpDev;
      *RpFunc = TbtRpFunc;
    }
  }

  ASSERT_EFI_ERROR (Status);
  return Status;
}

BOOLEAN
IsTbtHostRouter (
  IN    UINT16  DeviceID
  )
{
  switch (DeviceID) {
  case AR_HR_2C:
  case AR_HR_4C:
  case AR_HR_LP:
  case AR_HR_C0_2C:
  case AR_HR_C0_4C:
  case TR_HR_2C:
  case TR_HR_4C:
    return TRUE;
  }

  return FALSE;
} // IsTbtHostRouter

