/** @file
  This file contains PSF routines for RC usage

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

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

#include <Uefi/UefiBaseType.h>
#include <Library/IoLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/PchPcrLib.h>
#include <Library/PchInfoLib.h>
#include <Library/SataLib.h>
#include <Library/SaPlatformLib.h>
#include <Private/Library/PchPsfPrivateLib.h>
#include <PchLimits.h>
#include <Register/PchRegsPsf.h>
#include <Register/PchRegsPcie.h>
#include "PchPsfPrivateLibInternal.h"

/**
  Disable device at PSF level
  Method not for bridges (e.g. PCIe Root Port)

  @param[in] PsfPort  PSF PORT data structure
**/
VOID
PsfDisableDevice (
  IN PSF_PORT  PsfPort
  )
{
  if (PSF_IS_PORT_NULL (PsfPort)) {
    ASSERT (FALSE);
    return;
  }

  //
  // Read back is needed to enforce the sideband and primary ordering.
  //
  PchPcrAndThenOr32WithReadback (
    PsfPort.PsfPid,
    PsfPort.RegBase + R_PCH_PSFX_PCR_T0_SHDW_PCIEN,
    ~0u,
    B_PCH_PSFX_PCR_T0_SHDW_PCIEN_FUNDIS
    );
}

/**
  Hide PciCfgSpace of device at PSF level
  Method not for bridges (e.g. PCIe Root Port)

  @param[in] PsfPort  PSF PORT data structure
**/
VOID
PsfHideDevice (
  IN PSF_PORT  PsfPort
  )
{
  if (PSF_IS_PORT_NULL (PsfPort)) {
    ASSERT (FALSE);
    return;
  }

  //
  // Read back is needed to enforce the sideband and primary ordering.
  // If there is PCI access right after the PSF hide device, the device might
  // still be accessible since the PSF cycle is not completed yet, and causes
  // the race condition between sideband and primary cycles.
  //
  PchPcrAndThenOr32WithReadback (
    PsfPort.PsfPid,
    PsfPort.RegBase + R_PCH_PSFX_PCR_T0_SHDW_CFG_DIS,
    ~0u,
    B_PCH_PSFX_PCR_T0_SHDW_CFG_DIS_CFGDIS
    );
}

/**
  Unhide PciCfgSpace of device at PSF level
  Method not for bridges (e.g. PCIe Root Port)

  @param[in] PsfPort  PSF PORT data structure
**/
VOID
PsfUnhideDevice (
  IN PSF_PORT  PsfPort
  )
{
  if (PSF_IS_PORT_NULL (PsfPort)) {
    ASSERT (FALSE);
    return;
  }

  //
  // Read back is needed to enforce the sideband and primary ordering.
  //
  PchPcrAndThenOr32WithReadback (
    PsfPort.PsfPid,
    PsfPort.RegBase + R_PCH_PSFX_PCR_T0_SHDW_CFG_DIS,
    (UINT32) ~(B_PCH_PSFX_PCR_T0_SHDW_CFG_DIS_CFGDIS),
    0
    );
}

/**
  Disable device BARs at PSF level
  Method not for bridges (e.g. PCIe Root Port)

  @param[in] PsfPort     PSF PORT data structure
  @param[in] BarDisMask  BIT0-BAR0, BIT1-BAR1,...
                         Mask corresponds to 32bit wide BARs
**/
VOID
PsfDisableDeviceBar (
  IN PSF_PORT  PsfPort,
  IN UINT32    BarDisMask
  )
{
  if (PSF_IS_PORT_NULL (PsfPort)) {
    ASSERT (FALSE);
    return;
  }

  //
  // BAR0-5 supported
  //
  ASSERT (BarDisMask < BIT6);

  //
  // Read back is needed to enforce the sideband and primary ordering.
  //
  PchPcrAndThenOr32WithReadback (
    PsfPort.PsfPid,
    PsfPort.RegBase + R_PCH_PSFX_PCR_T0_SHDW_PCIEN,
    ~0u,
    BarDisMask << N_PCH_PSFX_PCR_T0_SHDW_PCIEN_BARXDIS
    );
}

/**
  Enable device BARs at PSF level
  Method not for bridges (e.g. PCIe Root Port)

  @param[in] PsfPort     PSF PORT data structure
  @param[in] BarEnMask   BIT0-BAR0, BIT1-BAR1,...
                         Mask corresponds to 32bit wide BARs
**/
VOID
PsfEnableDeviceBar (
  IN PSF_PORT  PsfPort,
  IN UINT32    BarEnMask
  )
{
  if (PSF_IS_PORT_NULL (PsfPort)) {
    ASSERT (FALSE);
    return;
  }

  //
  // BAR0-5 supported
  //
  ASSERT (BarEnMask < BIT6);

  //
  // Read back is needed to enforce the sideband and primary ordering.
  //
  PchPcrAndThenOr32WithReadback (
    PsfPort.PsfPid,
    PsfPort.RegBase + R_PCH_PSFX_PCR_T0_SHDW_PCIEN,
    (UINT32)~(BarEnMask << N_PCH_PSFX_PCR_T0_SHDW_PCIEN_BARXDIS),
    0
    );
}

/**
  Disable device IOSpace at PSF level
  Method not for bridges (e.g. PCIe Root Port)

  @param[in] PsfPort     PSF PORT data structure
**/
VOID
PsfDisableDeviceIoSpace (
  IN PSF_PORT  PsfPort
  )
{
  if (PSF_IS_PORT_NULL (PsfPort)) {
    ASSERT (FALSE);
    return;
  }

  //
  // Read back is needed to enforce the sideband and primary ordering.
  //
  PchPcrAndThenOr32WithReadback (
    PsfPort.PsfPid,
    PsfPort.RegBase + R_PCH_PSFX_PCR_T0_SHDW_PCIEN,
    ~(UINT32)(B_PCH_PSFX_PCR_T0_SHDW_PCIEN_IOEN),
    0
    );
}

/**
  Enable device IOSpace at PSF level
  Method not for bridges (e.g. PCIe Root Port)

  @param[in] PsfPort     PSF PORT data structure
**/
VOID
PsfEnableDeviceIoSpace (
  IN PSF_PORT  PsfPort
  )
{
  if (PSF_IS_PORT_NULL (PsfPort)) {
    ASSERT (FALSE);
    return;
  }

  //
  // Read back is needed to enforce the sideband and primary ordering.
  //
  PchPcrAndThenOr32WithReadback (
    PsfPort.PsfPid,
    PsfPort.RegBase + R_PCH_PSFX_PCR_T0_SHDW_PCIEN,
    ~0u,
    B_PCH_PSFX_PCR_T0_SHDW_PCIEN_IOEN
    );
}

/**
  Set device BARx address at PSF level
  Method not for bridges (e.g. PCIe Root Port)

  @param[in] PsfPort     PSF PORT data structure
  @param[in] BarNum      BAR Number (0:BAR0, 1:BAR1, ...)
  @param[in] BarValue    32bit BAR value
**/
VOID
PsfSetDeviceBarValue (
  IN PSF_PORT  PsfPort,
  IN UINT8     BarNum,
  IN UINT32    BarValue
  )
{
  ASSERT (BarNum < 6);

  if (PSF_IS_PORT_NULL (PsfPort)) {
    ASSERT (FALSE);
    return;
  }

  //
  // Read back is needed to enforce the sideband and primary ordering.
  //
  PchPcrAndThenOr32WithReadback (
    PsfPort.PsfPid,
    PsfPort.RegBase + R_PCH_PSFX_PCR_T0_SHDW_BAR0 + BarNum * 0x4,
    0,
    BarValue
    );
}

/**
  Hide PMC device at PSF level
**/
VOID
PsfHidePmcDevice (
  VOID
  )
{
  PsfHideDevice (PsfPmcPort ());
}

/**
  Set PMC ABASE value in PSF

  @param[in] Address     Address for ACPI base address.
**/
VOID
PsfSetPmcAbase (
  IN  UINT16       Address
  )
{
  PSF_PORT PsfPort;

  PsfPort = PsfPmcPort ();

  ASSERT (PchPcrRead32 (PsfPort.PsfPid, PsfPort.RegBase + R_PCH_PSFX_PCR_T0_SHDW_BAR4) != 0xFFFFFFFF);

  //
  // Disable IOSpace before changing the address
  //
  PsfDisableDeviceIoSpace (PsfPort);

  //
  // Program ABASE in PSF PMC space BAR4
  //
  PsfSetDeviceBarValue (PsfPort, 4, Address);

  //
  // Enable IOSpace
  //
  PsfEnableDeviceIoSpace (PsfPort);
}

/**
  Get PMC ABASE value from PSF

  @retval Address     Address for ACPI base.
**/
UINT16
PsfGetPmcAbase (
  VOID
  )
{
  UINT16    Address;
  PSF_PORT  PsfPort;

  PsfPort = PsfPmcPort ();
  //
  // Read ABASE from PSF PMC space BAR4
  //
  Address = PchPcrRead16 (
              PsfPort.PsfPid,
              PsfPort.RegBase + R_PCH_PSFX_PCR_T0_SHDW_BAR4
              );

  ASSERT (Address != 0xFFFF);

  return Address;
}

/**
  Get PMC PWRMBASE value from PSF

  @retval Address     Address for PWRM base.
**/
UINT32
PsfGetPmcPwrmBase (
  VOID
  )
{
  UINT32    Address;
  PSF_PORT  PsfPort;

  PsfPort = PsfPmcPort ();
  //
  // Read PWRMBASE from PSF PMC space BAR0
  //
  Address = PchPcrRead32 (
              PsfPort.PsfPid,
              PsfPort.RegBase + R_PCH_PSFX_PCR_T0_SHDW_BAR0
              );

  ASSERT (Address != 0xFFFFFFFF);

  return Address;
}

/**
  Get PSF SideBand Port ID from PSF ID (1 - PSF1, 2 - PSF2, ...)

  @param[in] PsfId             PSF ID (1 - PSF1, 2 - PSF2, ...)

  @retval PSF SideBand Port ID
**/
PCH_SBI_PID
PsfSbPortId (
  UINT32        PsfId
  )
{
  UINT32          PsfTableIndex;
  PSF_SEGMENT     *PsfTable;
  UINT32          PsfTableSize;

  PsfSegments (&PsfTable, &PsfTableSize);

  for (PsfTableIndex = 0; PsfTableIndex < PsfTableSize; PsfTableIndex++) {
    if (PsfTable[PsfTableIndex].Id == PsfId) {
      return PsfTable[PsfTableIndex].SbPid;
    }
  }

  ASSERT (FALSE);
  return 0;
}


/**
  Get PCH Root PSF ID. This is the PSF segment to which OPDMI/DMI is connected.

  @retval PsfId             Root PSF ID
**/
UINT32
PsfRootId (
  VOID
  )
{
  PSF_SEGMENT     *PsfTable;
  UINT32          PsfTableSize;

  PsfSegments (&PsfTable, &PsfTableSize);

  return PsfTable[0].Id;
}

/**
  Add EOI Target in a given PSF

  @param[in] PsfId             PSF ID (1 - PSF1, 2 - PSF2, ...)
  @param[in] TargetId          EOI Target ID
**/
STATIC
VOID
PsfAddEoiTarget (
  UINT32           PsfId,
  PSF_PORT_DEST_ID TargetId
  )
{
  UINT16      EoiTargetBase;
  UINT16      EoiControlBase;
  UINT8       NumOfEnabledTargets;
  UINT8       MaximalNumberOfTargets;
  PCH_SBI_PID PsfSbiPortId;
  UINT32      Data32;
  UINT8       TargetIndex;

  MaximalNumberOfTargets = PsfEoiRegData (PsfId, &EoiTargetBase, &EoiControlBase);
  PsfSbiPortId = PsfSbPortId (PsfId);

  //
  // Get number of enabled agents from PSF_x_PSF_MC_CONTROL_MCAST0_RS0_EOI register
  //
  Data32 = PchPcrRead32 (PsfSbiPortId, EoiControlBase);
  NumOfEnabledTargets = (UINT8) (Data32 >> N_PCH_PSFX_PCR_MC_CONTROL_MCASTX_NUMMC);

  //
  // Check if target was not already enabled
  // Targets from a different PSF segment are aggregated into single destination on
  // current PSF segment.
  //
  for (TargetIndex = 0; TargetIndex < NumOfEnabledTargets; TargetIndex++) {
    Data32 = PchPcrRead32 (PsfSbiPortId, EoiTargetBase + TargetIndex * 4);
    //
    // If target already added don't add it again
    //
    if (Data32 == TargetId.RegVal) {
      ASSERT (FALSE);
      return;
    }
    //
    // If target is from different PSF segment than currently being analyzed
    // it is enough that its PsfID is matching
    //
    if ((Data32 & B_PCH_PSFX_PCR_TARGET_PSFID) >> N_PCH_PSFX_PCR_TARGET_PSFID == TargetId.Fields.PsfId) {
      return;
    }
  }

  //
  // Check if next one can be added
  //
  if (NumOfEnabledTargets >= MaximalNumberOfTargets) {
    ASSERT (FALSE);
    return;
  }

  //
  // Add next target
  // Configure Multicast Destination ID register with target device on PSF.
  // Configuration must be done in next available PSF_MC_AGENT_MCAST0_RS0_TGT<x>_EOI register
  // so that other targets  are not overridden. <x> is known from the number of multicast agents
  // in Multicast Control Register. Value programmed is based on
  // PsfID, PortGroupID, PortID and ChannelID of the target
  //
  PchPcrWrite32 (PsfSbiPortId, EoiTargetBase + NumOfEnabledTargets * 4, TargetId.RegVal);

  //
  // Enable new target
  // Configure PSF_x_PSF_MC_CONTROL_MCAST0_RS0_EOI, increase NumMc and set MultCEn
  //
  NumOfEnabledTargets++;
  Data32 = (NumOfEnabledTargets << N_PCH_PSFX_PCR_MC_CONTROL_MCASTX_NUMMC) | B_PCH_PSFX_PCR_MC_CONTROL_MCASTX_MULTCEN;
  PchPcrWrite32 (PsfSbiPortId, EoiControlBase, Data32);
}

/**
  Enable EOI Target

  @param[in] TargetId  Target ID
**/
STATIC
VOID
PsfEnableEoiTarget (
  PSF_PORT_DEST_ID     TargetId
  )
{
  UINT32 RootLevelPsf;

  RootLevelPsf = PsfRootId ();

  //
  // Enable EOI target in root PSF
  //
  PsfAddEoiTarget (RootLevelPsf, TargetId);

  //
  // Enable EOI target on other PSF segment if target
  // is not located on root PSF
  //
  if (TargetId.Fields.PsfId != RootLevelPsf) {
    PsfAddEoiTarget (TargetId.Fields.PsfId, TargetId);
  }
}

/**
  This function enables EOI message forwarding in PSF for PCIe ports
  for cases where IOAPIC is present behind this root port.

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

  @retval Status
**/
EFI_STATUS
PsfConfigurEoiForPciePort (
  IN  UINT32  RpIndex
  )
{
  ASSERT (RpIndex < GetPchMaxPciePortNum ());

  //
  // If there is an IOAPIC discovered behind root port program PSF Multicast registers
  // accordingly to PCH BWG PSF EOI Multicast Configuration
  // Since there is a device behind RootPort to which EOI needs to be forwarded
  // enable multicast (MULTCEN) and increase the number of multicast agents (NUMMC)
  // in Multicast Control Register.
  //
  PsfEnableEoiTarget (PsfPcieDestinationId (RpIndex));

  return EFI_SUCCESS;
}
