/** @file
  PCI Host Bridge Library instance for Marvell 70x0/80x0

  Copyright (c) 2017, Linaro Ltd. All rights reserved.<BR>
  Copyright (c) 2019 Marvell International Ltd. All rights reserved.<BR>

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

**/

#include <PiDxe.h>

#include <IndustryStandard/Pci22.h>

#include <Library/ArmLib.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/IoLib.h>
#include <Library/MvGpioLib.h>
#include <Library/UefiBootServicesTableLib.h>

#include <Protocol/BoardDesc.h>

#include "PciHostBridgeLibConstructor.h"

/**
  This function configures PCIE controllers IATU windows.

  @param [in] PcieDbiAddress  PCIE controller base address.
  @param [in] Index           IATU window index.
  @param [in] CpuBase         Address from the CPU perspective.
  @param [in] PciBase         Target PCIE address.
  @param [in] Size            IATU window size.
  @param [in] Type            IATU window type.
  @param [in] EnableFlags     Extra configuration flags.

  @retval none

**/
STATIC
VOID
ConfigureWindow (
  IN EFI_PHYSICAL_ADDRESS PcieDbiAddress,
  IN UINTN                Index,
  IN UINT64               CpuBase,
  IN UINT64               PciBase,
  IN UINT64               Size,
  IN UINTN                Type,
  IN UINTN                EnableFlags
  )
{
  ArmDataMemoryBarrier ();

  MmioWrite32 (PcieDbiAddress + IATU_VIEWPORT_OFF,
    IATU_VIEWPORT_OUTBOUND | IATU_VIEWPORT_REGION_INDEX (Index));

  ArmDataMemoryBarrier ();

  MmioWrite32 (PcieDbiAddress + IATU_LWR_BASE_ADDR_OFF_OUTBOUND_0,
    (UINT32)(CpuBase & MAX_UINT32));
  MmioWrite32 (PcieDbiAddress + IATU_UPPER_BASE_ADDR_OFF_OUTBOUND_0,
    (UINT32)(CpuBase >> 32));
  MmioWrite32 (PcieDbiAddress + IATU_LIMIT_ADDR_OFF_OUTBOUND_0,
    (UINT32)(CpuBase + Size - 1));
  MmioWrite32 (PcieDbiAddress + IATU_LWR_TARGET_ADDR_OFF_OUTBOUND_0,
    (UINT32)(PciBase & MAX_UINT32));
  MmioWrite32 (PcieDbiAddress + IATU_UPPER_TARGET_ADDR_OFF_OUTBOUND_0,
    (UINT32)(PciBase >> 32));
  MmioWrite32 (PcieDbiAddress + IATU_REGION_CTRL_1_OFF_OUTBOUND_0,
    Type);
  MmioWrite32 (PcieDbiAddress + IATU_REGION_CTRL_2_OFF_OUTBOUND_0,
    IATU_REGION_CTRL_2_OFF_OUTBOUND_0_REGION_EN | EnableFlags);
}

/**
  Perform PCIE slot reset using external GPIO pin.

  @param [in] PcieDbiAddress  PCIE controller base address.

  @retval none

**/
STATIC
VOID
WaitForLink (
  IN EFI_PHYSICAL_ADDRESS PcieDbiAddress
  )
{
  UINT32 Mask;
  UINT32 Status;
  UINT32 Timeout;

  if (!(MmioRead32 (PcieDbiAddress + PCIE_PM_STATUS) & PCIE_PM_LTSSM_STAT_MASK)) {
    DEBUG ((DEBUG_INIT, "%a: no PCIE device detected\n", __FUNCTION__));
    return;
  }

  /* Wait for the link to establish itself. */
  DEBUG ((DEBUG_INIT, "%a: waiting for PCIE link\n", __FUNCTION__));

  Mask = PCIE_GLOBAL_STATUS_RDLH_LINK_UP | PCIE_GLOBAL_STATUS_PHY_LINK_UP;
  Timeout = PCIE_LINK_UP_TIMEOUT_US / 10;
  do {
    Status = MmioRead32 (PcieDbiAddress + PCIE_GLOBAL_STATUS_REG);
    if ((Status & Mask) == Mask) {
      DEBUG ((DEBUG_ERROR, "pcie@0x%x link UP\n", PcieDbiAddress));
      break;
    }
    /* Wait 10us between each link status check. */
    gBS->Stall (10);
  } while (Timeout--);
}

/**
  Perform PCIE slot reset using external GPIO pin.

  @param [in] *PcieResetGpio  GPIO pin description.

  @retval EFI_SUCEESS         PCIE slot reset succeeded.
  @retval Other               Return error status.

**/
STATIC
EFI_STATUS
ResetPcieSlot (
  IN MV_GPIO_PIN CONST *PcieResetGpio
  )
{
  EMBEDDED_GPIO_MODE   Mode;
  EMBEDDED_GPIO_PIN    GpioPin;
  EMBEDDED_GPIO       *GpioProtocol;
  EFI_STATUS           Status;

  /* Get GPIO protocol. */
  Status = MvGpioGetProtocol (PcieResetGpio->ControllerType, &GpioProtocol);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a: Unable to find GPIO protocol\n", __FUNCTION__));
    return Status;
  }

  GpioPin = GPIO (PcieResetGpio->ControllerId, PcieResetGpio->PinNumber),

  /* Activate reset. */
  Mode = PcieResetGpio->ActiveHigh ? GPIO_MODE_OUTPUT_1 : GPIO_MODE_OUTPUT_0;
  Status = GpioProtocol->Set (GpioProtocol, GpioPin, Mode);

  /*
   * According to the PCIE specification, the reset signal must be active
   * for minimum 100ms. To be on a safe side, use 150ms delay.
   */
  MemoryFence ();
  gBS->Stall (150 * 1000);

  /* Dectivate reset. */
  Mode = PcieResetGpio->ActiveHigh ? GPIO_MODE_OUTPUT_0 : GPIO_MODE_OUTPUT_1;
  Status = GpioProtocol->Set (GpioProtocol, GpioPin, Mode);

  /*
   * The controller cannot be configured (e.g. the clocks have to establish)
   * during 20ms period after the reset is deactivated.
   */
  MemoryFence ();
  gBS->Stall (20 * 1000);

  return EFI_SUCCESS;
}

/**
  Obtain resources and perform a low-level PCIE controllers
  configuration.

  @param [in]  ImageHandle  The image handle.
  @param [in] *SystemTable  The system table.

  @retval EFI_SUCEESS       PCIE configuration successful.
  @retval Other             Return error status.

**/
EFI_STATUS
EFIAPI
Armada7k8kPciHostBridgeLibConstructor (
  IN EFI_HANDLE       ImageHandle,
  IN EFI_SYSTEM_TABLE *SystemTable
  )
{
  MV_BOARD_PCIE_DESCRIPTION CONST *BoardPcieDescription;
  MARVELL_BOARD_DESC_PROTOCOL     *BoardDescriptionProtocol;
  MV_PCIE_CONTROLLER CONST        *PcieController;
  EFI_PHYSICAL_ADDRESS             PcieDbiAddress;
  EFI_STATUS                       Status;
  UINTN                            Index;

  /* Obtain list of available controllers */
  Status = gBS->LocateProtocol (&gMarvellBoardDescProtocolGuid,
                  NULL,
                  (VOID **)&BoardDescriptionProtocol);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR,
      "%a: Cannot locate BoardDesc protocol\n",
      __FUNCTION__));
    return EFI_DEVICE_ERROR;
  }

  Status = BoardDescriptionProtocol->PcieDescriptionGet (
                                       BoardDescriptionProtocol,
                                       &BoardPcieDescription);
  if (Status == EFI_NOT_FOUND) {
    /* No controllers used on the platform, exit silently */
    return EFI_SUCCESS;
  } else if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR,
      "%a: Cannot get Pcie board desc from BoardDesc protocol\n",
      __FUNCTION__));
    return EFI_DEVICE_ERROR;
  }

  for (Index = 0; Index < BoardPcieDescription->PcieControllerCount; Index++) {

    PcieController = &(BoardPcieDescription->PcieControllers[Index]);

    ASSERT (PcieController->PcieBusMin == 0);

    if (PcieController->HaveResetGpio == TRUE) {
      /* Reset PCIE slot */
      Status = ResetPcieSlot (&PcieController->PcieResetGpio);
      if (EFI_ERROR (Status)) {
        DEBUG ((DEBUG_ERROR,
          "%a: Cannot reset Pcie Slot\n",
          __FUNCTION__));
        return EFI_DEVICE_ERROR;
      }
    }

    /* Low level PCIE controller configuration */
    PcieDbiAddress = PcieController->PcieDbiAddress;

    MmioAndThenOr32 (PcieDbiAddress + PORT_LINK_CTRL_OFF,
      ~PORT_LINK_CTRL_OFF_LINK_CAPABLE_MASK,
      PORT_LINK_CTRL_OFF_LINK_CAPABLE_x4);

    MmioAndThenOr32 (PcieDbiAddress + GEN2_CTRL_OFF,
      ~GEN2_CTRL_OFF_NUM_OF_LANES_MASK,
      GEN2_CTRL_OFF_NUM_OF_LANES(4) | GEN2_CTRL_OFF_DIRECT_SPEED_CHANGE);

    MmioAndThenOr32 (PcieDbiAddress + PCIE_GLOBAL_CTRL_OFFSET,
      ~(PCIE_GLOBAL_CTRL_DEVICE_TYPE_MASK | PCIE_GLOBAL_APP_LTSSM_EN),
      PCIE_GLOBAL_CTRL_DEVICE_TYPE_RC);

    MmioWrite32 (PcieDbiAddress + PCIE_ARCACHE_TRC_REG,
      ARCACHE_DEFAULT_VALUE);

    MmioWrite32 (PcieDbiAddress + PCIE_AWCACHE_TRC_REG,
      AWCACHE_DEFAULT_VALUE);

    MmioAndThenOr32 (PcieDbiAddress + PCIE_ARUSER_REG,
      ~AX_USER_DOMAIN_MASK,
      AX_USER_DOMAIN_OUTER_SHAREABLE);

    MmioAndThenOr32 (PcieDbiAddress + PCIE_AWUSER_REG,
      ~AX_USER_DOMAIN_MASK,
      AX_USER_DOMAIN_OUTER_SHAREABLE);

    MmioAndThenOr32 (PcieDbiAddress + PCIE_LINK_CTL_2,
      ~TARGET_LINK_SPEED_MASK,
      LINK_SPEED_GEN_3);

    MmioAndThenOr32 (PcieDbiAddress + PCIE_LINK_CAPABILITY,
      ~TARGET_LINK_SPEED_MASK,
      LINK_SPEED_GEN_3);

    MmioOr32 (PcieDbiAddress + PCIE_GEN3_EQU_CTRL,
      GEN3_EQU_EVAL_2MS_DISABLE);

    MmioOr32 (PcieDbiAddress + PCIE_GLOBAL_CTRL_OFFSET,
      PCIE_GLOBAL_APP_LTSSM_EN);

    /* Region 0: MMIO32 range */
    ConfigureWindow (PcieDbiAddress,
      PcieController->PcieMmio32WinBase - PcieController->PcieMmio32Translation,
      PcieController->PcieMmio32WinBase,
      PcieController->PcieMmio32WinBase,
      PcieController->PcieMmio32WinSize,
      IATU_REGION_CTRL_1_OFF_OUTBOUND_0_TYPE_MEM,
      0);

    /* Region 1: Type 0 config space */
    ConfigureWindow (PcieDbiAddress,
      1,
      PcieController->ConfigSpaceAddress,
      0x0,
      SIZE_64KB,
      IATU_REGION_CTRL_1_OFF_OUTBOUND_0_TYPE_CFG0,
      IATU_REGION_CTRL_2_OFF_OUTBOUND_0_CFG_SHIFT_MODE);

    /* Region 2: Type 1 config space */
    ConfigureWindow (PcieDbiAddress,
      2,
      PcieController->ConfigSpaceAddress + SIZE_64KB,
      0x0,
      PcieController->PcieBusMax * SIZE_1MB,
      IATU_REGION_CTRL_1_OFF_OUTBOUND_0_TYPE_CFG1,
      IATU_REGION_CTRL_2_OFF_OUTBOUND_0_CFG_SHIFT_MODE);

    /* Region 3: port I/O range */
    ConfigureWindow (PcieDbiAddress,
      3,
      PcieController->PcieIoTranslation,
      PcieController->PcieIoWinBase,
      PcieController->PcieIoWinSize,
      IATU_REGION_CTRL_1_OFF_OUTBOUND_0_TYPE_IO,
      0);

    /* Region 4: MMIO64 range */
    ConfigureWindow (PcieDbiAddress,
      4,
      PcieController->PcieMmio64WinBase,
      PcieController->PcieMmio64WinBase,
      PcieController->PcieMmio64WinSize,
      IATU_REGION_CTRL_1_OFF_OUTBOUND_0_TYPE_MEM,
      0);

    MmioOr32 (PcieDbiAddress + PCIE_GLOBAL_INT_MASK1_REG,
      PCIE_INT_A_ASSERT_MASK |
      PCIE_INT_B_ASSERT_MASK |
      PCIE_INT_C_ASSERT_MASK |
      PCIE_INT_D_ASSERT_MASK);

    WaitForLink (PcieDbiAddress);

    /* Enable the RC */
    MmioOr32 (PcieDbiAddress + PCI_COMMAND_OFFSET,
      EFI_PCI_COMMAND_IO_SPACE |
      EFI_PCI_COMMAND_MEMORY_SPACE |
      EFI_PCI_COMMAND_BUS_MASTER);
  }

  return EFI_SUCCESS;
}
