/** @file
*
*  Copyright (c) 2018, Hisilicon Limited. All rights reserved.
*  Copyright (c) 2018, Linaro Limited. All rights reserved.
*
*  SPDX-License-Identifier: BSD-2-Clause-Patent
*
**/

#include <Uefi.h>
#include <IndustryStandard/Acpi.h>
#include <Library/DebugLib.h>
#include <Library/IoLib.h>
#include <Library/OemMiscLib.h>
#include <Library/PcdLib.h>
#include <Library/PciExpressLib.h>
#include <Library/PlatformPciLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Protocol/PciHostBridgeResourceAllocation.h>
#include <Protocol/PciPlatform.h>
#include <Protocol/PciRootBridgeIo.h>
#include <Regs/HisiPcieV1RegOffset.h>

#define INVALID_CAPABILITY_00       0x00
#define INVALID_CAPABILITY_FF       0xFF
#define PCI_CAPABILITY_POINTER_MASK 0xFC

STATIC
UINT64
GetPcieCfgAddress (
    UINT64 Ecam,
    UINTN Bus,
    UINTN Device,
    UINTN Function,
    UINTN Reg
    )
{
  return Ecam + PCI_EXPRESS_LIB_ADDRESS (Bus, Device, Function, Reg);
}

STATIC
PCI_ROOT_BRIDGE_RESOURCE_APPETURE *
GetAppetureByRootBridgeIo (
    IN  EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL  *RootBridge
    )
{
  EFI_STATUS Status;
  EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *Configuration = NULL;
  UINTN Hb;
  UINTN Rb;

  Status = RootBridge->Configuration (
      RootBridge,
      (VOID **)&Configuration
      );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "[%a:%d] RootBridgeIo->Configuration failed %r\n",
          __FUNCTION__, __LINE__, Status));
    return NULL;
  };

  while (Configuration->Desc == ACPI_ADDRESS_SPACE_DESCRIPTOR) {
    if (Configuration->ResType == ACPI_ADDRESS_SPACE_TYPE_BUS) {
      break;
    }
    Configuration++;
  }

  if (Configuration->Desc != ACPI_ADDRESS_SPACE_DESCRIPTOR) {
    DEBUG ((DEBUG_ERROR, "[%a:%d] Can't find bus descriptor\n", __FUNCTION__, __LINE__));
    return NULL;
  }

  for (Hb = 0; Hb < PCIE_MAX_HOSTBRIDGE; Hb++) {
    for (Rb = 0; Rb < PCIE_MAX_ROOTBRIDGE; Rb++) {
      if (RootBridge->SegmentNumber == mResAppeture[Hb][Rb].Segment &&
          Configuration->AddrRangeMin >= mResAppeture[Hb][Rb].BusBase &&
          Configuration->AddrRangeMax <= mResAppeture[Hb][Rb].BusLimit) {
        return &mResAppeture[Hb][Rb];
      }
    }
  }

  DEBUG ((DEBUG_ERROR, "[%a:%d] Can't find PCI appeture\n", __FUNCTION__, __LINE__));
  return NULL;
}

STATIC
VOID
SetAtuConfig0RW (
    PCI_ROOT_BRIDGE_RESOURCE_APPETURE *Private,
    UINT32 Index
    )
{
  UINTN RbPciBase = Private->RbPciBar;
  UINT64 MemLimit = GetPcieCfgAddress (Private->Ecam, Private->BusBase + 1, 1, 0, 0) - 1;
  UINT64 MemBase = GetPcieCfgAddress (Private->Ecam, Private->BusBase, 0, 0, 0);


  MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_VIEW_POINT, Index);
  MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_BASE_LOW, (UINT32)MemBase);
  MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_BASE_HIGH, (UINT32)(MemBase >> 32));
  MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_BASE_LIMIT, (UINT32) MemLimit);
  MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_TARGET_LOW, 0);
  MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_TARGET_HIGH, 0);
  MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_CTRL1, IATU_CTRL1_TYPE_CONFIG0);
  MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_CTRL2, IATU_SHIIF_MODE);

  {
    UINTN i;
    for (i=0; i<0x20; i+=4) {
      DEBUG ((DEBUG_ERROR, "[%a:%d] - Base=%p value=%x\n", __FUNCTION__, __LINE__, RbPciBase + 0x900 + i, MmioRead32(RbPciBase + 0x900 + i)));
    }
  }
}

STATIC
VOID
SetAtuConfig1RW (
    PCI_ROOT_BRIDGE_RESOURCE_APPETURE *Private,
    UINT32 Index
    )
{
  UINTN RbPciBase = Private->RbPciBar;
  UINT64 MemLimit = GetPcieCfgAddress (Private->Ecam, Private->BusLimit + 1, 0, 0, 0) - 1;
  UINT64 MemBase = GetPcieCfgAddress (Private->Ecam, Private->BusBase + 2, 0, 0, 0);


  MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_VIEW_POINT, Index);
  MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_CTRL1, IATU_CTRL1_TYPE_CONFIG1);
  MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_BASE_LOW, (UINT32)MemBase);
  MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_BASE_HIGH, (UINT32)(MemBase >> 32));
  MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_BASE_LIMIT, (UINT32) MemLimit);
  MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_TARGET_LOW, 0);
  MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_TARGET_HIGH, 0);
  MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_CTRL2, IATU_SHIIF_MODE);

  {
    UINTN i;
    for (i=0; i<0x20; i+=4) {
      DEBUG ((DEBUG_ERROR, "[%a:%d] - Base=%p value=%x\n", __FUNCTION__, __LINE__, RbPciBase + 0x900 + i, MmioRead32(RbPciBase + 0x900 + i)));
    }
  }
}

STATIC
VOID
SetAtuIoRW (UINT64 RbPciBase,UINT64 IoBase,UINT64 CpuIoRegionLimit, UINT64 CpuIoRegionBase, UINT32 Index)
{

    MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_VIEW_POINT, Index);
    MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_CTRL1, IATU_CTRL1_TYPE_IO);

    MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_BASE_LOW, (UINT32)(CpuIoRegionBase));
    MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_BASE_HIGH, (UINT32)((UINT64)CpuIoRegionBase >> 32));
    MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_BASE_LIMIT, (UINT32)(CpuIoRegionLimit));
    MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_TARGET_LOW, (UINT32)(IoBase));
    MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_TARGET_HIGH, (UINT32)((UINT64)(IoBase) >> 32));

    MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_CTRL2, IATU_NORMAL_MODE);

    {
      UINTN i;
      for (i=0; i<0x20; i+=4) {
        DEBUG ((DEBUG_ERROR, "[%a:%d] - Base=%p value=%x\n", __FUNCTION__, __LINE__, RbPciBase + 0x900 + i, MmioRead32(RbPciBase + 0x900 + i)));
      }
    }
}

STATIC
VOID
SetAtuMemRW(UINT64 RbPciBase,UINT64 MemBase,UINT64 CpuMemRegionLimit, UINT64 CpuMemRegionBase, UINT32 Index)
{

    MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_VIEW_POINT, Index);
    MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_CTRL1, IATU_CTRL1_TYPE_MEM);

    MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_BASE_LOW, (UINT32)(CpuMemRegionBase));
    MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_BASE_HIGH, (UINT32)((UINT64)(CpuMemRegionBase) >> 32));
    MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_BASE_LIMIT, (UINT32)(CpuMemRegionLimit));
    MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_TARGET_LOW, (UINT32)(MemBase));
    MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_TARGET_HIGH, (UINT32)((UINT64)(MemBase) >> 32));

    MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_CTRL2, IATU_NORMAL_MODE);

    {
      UINTN i;
      for (i=0; i<0x20; i+=4) {
        DEBUG ((DEBUG_ERROR, "[%a:%d] - Base=%p value=%x\n", __FUNCTION__, __LINE__, RbPciBase + 0x900 + i, MmioRead32(RbPciBase + 0x900 + i)));
      }
    }
}

VOID
InitAtu (PCI_ROOT_BRIDGE_RESOURCE_APPETURE *Private)
{
  SetAtuMemRW (Private->RbPciBar, Private->PciRegionBase, Private->PciRegionLimit, Private->CpuMemRegionBase, 0);
  SetAtuConfig0RW (Private, 1);
  SetAtuConfig1RW (Private, 2);
  SetAtuIoRW (Private->RbPciBar, Private->IoBase, Private->IoLimit, Private->CpuIoRegionBase, 3);
}

/*++

Routine Description:

  Perform Platform initialization first in PciPlatform.

Arguments:

Returns:

 VOID.

--*/
VOID
EFIAPI
PciInitPlatform (
  VOID
  )
{
  UINT32             Port;
  UINT32             HostBridgeNum = 0;

  for (HostBridgeNum = 0; HostBridgeNum < PCIE_MAX_HOSTBRIDGE; HostBridgeNum++) {
    for (Port = 0; Port < PCIE_MAX_ROOTBRIDGE; Port++) {
      InitAtu (&mResAppeture[HostBridgeNum][Port]);
    }
  }

  return;
}

STATIC
BOOLEAN
PcieCheckAriFwdEn (
  UINTN  PciBaseAddr
  )
{
  UINT8   PciPrimaryStatus;
  UINT8   CapabilityOffset;
  UINT8   CapId;
  UINT8   TempData;

  PciPrimaryStatus = MmioRead16 (PciBaseAddr + PCI_PRIMARY_STATUS_OFFSET);

  if (PciPrimaryStatus & EFI_PCI_STATUS_CAPABILITY) {
    CapabilityOffset = MmioRead8 (PciBaseAddr + PCI_CAPBILITY_POINTER_OFFSET);
    CapabilityOffset &= PCI_CAPABILITY_POINTER_MASK;

    while ((CapabilityOffset != INVALID_CAPABILITY_00) && (CapabilityOffset != INVALID_CAPABILITY_FF)) {
      CapId = MmioRead8 (PciBaseAddr + CapabilityOffset);
      if (CapId == EFI_PCI_CAPABILITY_ID_PCIEXP) {
        break;
      }
      CapabilityOffset = MmioRead8 (PciBaseAddr + CapabilityOffset + 1);
      CapabilityOffset &= PCI_CAPABILITY_POINTER_MASK;
    }
  } else {
    return FALSE;
  }

  if ((CapabilityOffset == INVALID_CAPABILITY_FF) || (CapabilityOffset == INVALID_CAPABILITY_00)) {
    return FALSE;
  }

  TempData = MmioRead16 (PciBaseAddr + CapabilityOffset +
                          EFI_PCIE_CAPABILITY_DEVICE_CONTROL_2_OFFSET);
  TempData &= EFI_PCIE_CAPABILITY_DEVICE_CAPABILITIES_2_ARI_FORWARDING;

  if (TempData == EFI_PCIE_CAPABILITY_DEVICE_CAPABILITIES_2_ARI_FORWARDING) {
    return TRUE;
  } else {
    return FALSE;
  }
}

VOID
EnlargeAtuConfig0 (
  IN EFI_HANDLE HostBridge
  )
{
  EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL    *ResAlloc = NULL;
  EFI_STATUS                                          Status;
  EFI_HANDLE RootBridgeHandle = NULL;
  EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *RootBridgeIo = NULL;
  PCI_ROOT_BRIDGE_RESOURCE_APPETURE *Appeture;
  UINTN                           RbPciBase;
  UINT64                          MemLimit;

  DEBUG ((DEBUG_INFO, "In Enlarge RP iATU Config 0.\n"));

  Status = gBS->HandleProtocol (
      HostBridge,
      &gEfiPciHostBridgeResourceAllocationProtocolGuid,
      (VOID **)&ResAlloc
      );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "[%a:%d] - HandleProtocol failed %r\n", __FUNCTION__,
          __LINE__, Status));
    return;
  }

  while (TRUE) {
    Status = ResAlloc->GetNextRootBridge (
        ResAlloc,
        &RootBridgeHandle
        );
    if (EFI_ERROR (Status)) {
      break;
    }
    Status = gBS->HandleProtocol (
        RootBridgeHandle,
        &gEfiPciRootBridgeIoProtocolGuid,
        (VOID **)&RootBridgeIo
        );
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "[%a:%d] - HandleProtocol failed %r\n", __FUNCTION__, __LINE__, Status));
      // This should never happen so that it is a fatal error and we don't try
      // to continue
      break;
    }

    Appeture = GetAppetureByRootBridgeIo (RootBridgeIo);
    if (Appeture == NULL) {
      DEBUG ((DEBUG_ERROR, "[%a:%d] Get appeture failed\n", __FUNCTION__,
            __LINE__));
      continue;
    }

    RbPciBase = Appeture->RbPciBar;
    // Those ARI FWD Enable Root Bridge, need enlarge iATU window.
    if (PcieCheckAriFwdEn (RbPciBase)) {
      MemLimit = GetPcieCfgAddress (Appeture->Ecam, Appeture->BusBase + 2, 0, 0, 0) - 1;
      MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_VIEW_POINT, 1);
      MmioWrite32 (RbPciBase + IATU_OFFSET + IATU_REGION_BASE_LIMIT, (UINT32) MemLimit);
    }
  }
}

/*++

Routine Description:

  Perform Platform initialization by the phase indicated.

Arguments:

  HostBridge    -  The associated PCI host bridge handle.
  Phase         -  The phase of the PCI controller enumeration.
  ChipsetPhase  -  Defines the execution phase of the PCI chipset driver.

Returns:

--*/
VOID
EFIAPI
PhaseNotifyPlatform (
  IN  EFI_HANDLE                                     HostBridge,
  IN  EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PHASE  Phase,
  IN  EFI_PCI_CHIPSET_EXECUTION_PHASE                ChipsetPhase
  )
{
  switch (Phase) {
  case EfiPciHostBridgeEndEnumeration:
    // Only do once
    if (ChipsetPhase == ChipsetEntry) {
      DEBUG ((DEBUG_INFO, "PCI end enumeration platform hook\n"));
      EnlargeAtuConfig0 (HostBridge);
    }
    break;
  default:
    break;
  }

  return ;
}

