/** @file
*  Initialize the XPress-RICH3 PCIe Root complex
*
*  Copyright (c) 2011-2015, ARM Ltd. All rights reserved.
*  Copyright (c) 2017, Linaro, Ltd. All rights reserved.
*
*  SPDX-License-Identifier: BSD-2-Clause-Patent
*
**/

#include <PiDxe.h>

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

#include <IndustryStandard/Pci22.h>

#include "XPressRich3.h"
#include "ArmPlatform.h"

#define PCI_BRIDGE_REVISION_ID                        1
#define CLASS_CODE_REGISTER(Class, SubClass, ProgIf)  ((Class << 16) | (SubClass << 8) | ProgIf)
#define PLDA_BRIDGE_CCR                               CLASS_CODE_REGISTER(PCI_CLASS_BRIDGE, \
                                                                          PCI_CLASS_BRIDGE_P2P, \
                                                                          PCI_IF_BRIDGE_P2P)

STATIC
VOID
SetTranslationAddressEntry (
  IN  EFI_CPU_IO2_PROTOCOL    *CpuIo,
  IN  UINTN                   Entry,
  IN  UINT64                  SourceAddress,
  IN  UINT64                  TranslatedAddress,
  IN  UINT64                  TranslationSize,
  IN  UINT64                  TranslationParameter
  )
{
  UINTN Log2Size = HighBitSet64 (TranslationSize);

  // Ensure the size is a power of two. Restriction form the AXI Translation logic
  // Othwerwise we increase the translation size
  if (TranslationSize != (1ULL << Log2Size)) {
    DEBUG ((EFI_D_WARN, "PCI: The size 0x%lX of the region 0x%lx has been increased to "
                        "be a power of two for the AXI translation table.\n",
                        TranslationSize, SourceAddress));
    Log2Size++;
  }

  PCIE_ROOTPORT_WRITE32 (Entry + PCI_ATR_SRC_ADDR_LOW_SIZE,
      (UINT32)SourceAddress | ((Log2Size - 1) << 1) | 0x1);
  PCIE_ROOTPORT_WRITE32 (Entry + PCI_ATR_SRC_ADDR_HI, SourceAddress >> 32);

  PCIE_ROOTPORT_WRITE32 (Entry + PCI_ATR_TRSL_ADDR_LOW, (UINT32)TranslatedAddress);
  PCIE_ROOTPORT_WRITE32 (Entry + PCI_ATR_TRSL_ADDR_HI, TranslatedAddress >> 32);

  PCIE_ROOTPORT_WRITE32 (Entry + PCI_ATR_TRSL_PARAM, TranslationParameter);
}

EFI_STATUS
HWPciRbInit (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  UINT32                  Value;
  UINT32                  Index;
  UINTN                   TranslationTable;
  EFI_CPU_IO2_PROTOCOL    *CpuIo;
  EFI_STATUS              Status;

  PCI_TRACE ("VExpressPciRbInit()");

  PCI_TRACE ("PCIe Setting up Address Translation");

  Status = gBS->LocateProtocol (&gEfiCpuIo2ProtocolGuid, NULL,
                  (VOID **)&CpuIo);
  ASSERT_EFI_ERROR (Status);

  // The Juno PIO window is 8M, so we need full 32-bit PIO decoding.
  PCIE_ROOTPORT_WRITE32 (PCIE_BAR_WIN, PCIE_BAR_WIN_SUPPORT_IO | PCIE_BAR_WIN_SUPPORT_IO32 |
                         PCIE_BAR_WIN_SUPPORT_MEM | PCIE_BAR_WIN_SUPPORT_MEM64);

  // Setup the PCI Configuration Registers
  // Offset 0a: SubClass       04 PCI-PCI Bridge
  // Offset 0b: BaseClass      06 Bridge Device
  // The Class Code register is a 24 bit and can be configured by setting up the PCIE_PCI_IDS
  // Refer [1] Chapter 13
  PCIE_ROOTPORT_WRITE32 (PCIE_PCI_IDS + PCIE_PCI_IDS_CLASSCODE_OFFSET, ((PLDA_BRIDGE_CCR << 8) | PCI_BRIDGE_REVISION_ID));

  //
  // PCIE Window 0 -> AXI4 Master 0 Address Translations
  //
  TranslationTable = VEXPRESS_ATR_PCIE_WIN0;

  // MSI Support
  SetTranslationAddressEntry (CpuIo, TranslationTable, ARM_JUNO_GIV2M_MSI_BASE, ARM_JUNO_GIV2M_MSI_BASE,
      ARM_JUNO_GIV2M_MSI_SZ, PCI_ATR_TRSLID_AXIDEVICE);
  TranslationTable += PCI_ATR_ENTRY_SIZE;

  // System Memory Support
  SetTranslationAddressEntry (CpuIo, TranslationTable, PcdGet64 (PcdSystemMemoryBase), PcdGet64 (PcdSystemMemoryBase),
      PcdGet64 (PcdSystemMemorySize), PCI_ATR_TRSLID_AXIMEMORY);
  TranslationTable += PCI_ATR_ENTRY_SIZE;
  SetTranslationAddressEntry (CpuIo, TranslationTable, ARM_JUNO_EXTRA_SYSTEM_MEMORY_BASE, ARM_JUNO_EXTRA_SYSTEM_MEMORY_BASE,
      ARM_JUNO_EXTRA_SYSTEM_MEMORY_SZ, PCI_ATR_TRSLID_AXIMEMORY);

  //
  // AXI4 Slave 1 -> PCIE Window 0 Address Translations
  //
  TranslationTable = VEXPRESS_ATR_AXI4_SLV1;

  // PCI ECAM Support
  SetTranslationAddressEntry (CpuIo, TranslationTable, PCI_ECAM_BASE, PCI_ECAM_BASE, PCI_ECAM_SIZE, PCI_ATR_TRSLID_PCIE_CONF);
  TranslationTable += PCI_ATR_ENTRY_SIZE;

  // PCI IO Support, the PIO space is translated from the arm MMIO PCI_IO_BASE address to the PIO base address of 0
  // AKA, PIO addresses used by endpoints are generally in the range of 0-64K.
  SetTranslationAddressEntry (CpuIo, TranslationTable, PCI_IO_BASE, 0, PCI_IO_SIZE, PCI_ATR_TRSLID_PCIE_IO);
  TranslationTable += PCI_ATR_ENTRY_SIZE;

  // PCI MEM32 Support
  SetTranslationAddressEntry (CpuIo, TranslationTable, PCI_MEM32_BASE, PCI_MEM32_BASE, PCI_MEM32_SIZE, PCI_ATR_TRSLID_PCIE_MEMORY);
  TranslationTable += PCI_ATR_ENTRY_SIZE;

  // PCI MEM64 Support
  SetTranslationAddressEntry (CpuIo, TranslationTable, PCI_MEM64_BASE, PCI_MEM64_BASE, PCI_MEM64_SIZE, PCI_ATR_TRSLID_PCIE_MEMORY);

  // Add credits
  PCIE_ROOTPORT_WRITE32 (PCIE_VC_CRED, 0x00f0b818);
  PCIE_ROOTPORT_WRITE32 (PCIE_VC_CRED + 4, 0x1);

  // Allow ECRC
  PCIE_ROOTPORT_WRITE32 (PCIE_PEX_SPC2, 0x6006);

  // Reset controller
  PCIE_CONTROL_WRITE32 (PCIE_CONTROL_RST_CTL, PCIE_CONTROL_RST_CTL_RCPHY_REL);

  // Wait for reset
  for (Index = 0; Index < 1000; Index++) {
    gBS->Stall (1000);
    PCIE_CONTROL_READ32 (PCIE_CONTROL_RST_STS, Value);
    if ((Value & PCIE_CONTROL_RST_STS_RCPHYPLL_OUT) == PCIE_CONTROL_RST_STS_RCPHYPLL_OUT) {
      break;
    }
  }

  // Check for reset
  if (!(Value & PCIE_CONTROL_RST_STS_RCPHYPLL_OUT)) {
    DEBUG ((EFI_D_ERROR, "PCIe failed to come out of reset: %x.\n", Value));
    return EFI_NOT_READY;
  }

  gBS->Stall (1000);
  PCI_TRACE ("Checking link Status...");

  // Wait for Link Up
  for (Index = 0; Index < 1000; Index++) {
    gBS->Stall (1000);
    PCIE_ROOTPORT_READ32 (VEXPRESS_BASIC_STATUS, Value);
    if (Value & LINK_UP) {
      break;
    }
  }

  // Check for link up
  if (!(Value & LINK_UP)) {
    DEBUG ((EFI_D_ERROR, "PCIe link not up: %x.\n", Value));
    return EFI_NOT_READY;
  }

  PCIE_ROOTPORT_WRITE32 (PCIE_IMASK_LOCAL, PCIE_INT_MSI | PCIE_INT_INTx);

  return EFI_SUCCESS;
}
