/** @file

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

#include <PiPei.h>
#include <SaPolicyCommon.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/IoLib.h>
#include <Library/HobLib.h>
#include <Library/PcdLib.h>
#include <Library/PchCycleDecodingLib.h>
#include <Library/PciLib.h>
#include <Library/PcdLib.h>
#include <Library/BaseMemoryLib.h>

#include <Library/PeiSaPolicyLib.h>
#include <Library/BoardInitLib.h>
#include <PchAccess.h>
#include <Library/GpioNativeLib.h>
#include <Library/GpioLib.h>
#include <GpioPinsSklLp.h>
#include <GpioPinsSklH.h>
#include <Library/GpioExpanderLib.h>
#include <SioRegs.h>
#include <Library/PchPcrLib.h>

#include "UpXtremeInit.h"
#include <ConfigBlock.h>
#include <Setup.h>
#include <ConfigBlock/MemoryConfig.h>
#include <Library/PeiServicesLib.h>
#include <Library/PchPcrLib.h>
#include <Library/PchInfoLib.h>
#include <Register/PchRegsPcr.h>
#include <Library/PchResetLib.h>
#include <Register/PchRegsLpc.h>
#include <Library/StallPpiLib.h>
#include <Library/PeiPolicyInitLib.h>
#include <Ppi/Reset.h>
#include <PlatformBoardConfig.h>
#include <GpioPinsCnlLp.h>
#include <Library/PmcLib.h>
#include <Library/PciSegmentLib.h>
#include <PeiPlatformHookLib.h>
#include <FirwmareConfigurations.h>
#include <Guid/TcoWdtHob.h>
#include <Library/OcWdtLib.h>
#include <CpuRegs.h>
#include <Library/BaseLib.h>

///
/// Reset Generator I/O Port
///
#define RESET_GENERATOR_PORT           0xCF9

typedef struct {
  EFI_PHYSICAL_ADDRESS    BaseAddress;
  UINT64                  Length;
} MEMORY_MAP;

GLOBAL_REMOVE_IF_UNREFERENCED MEMORY_MAP MmioMap[] = {
  { FixedPcdGet64(PcdApicLocalAddress), FixedPcdGet32(PcdApicLocalMmioSize) },
  { FixedPcdGet64(PcdMchBaseAddress), FixedPcdGet32(PcdMchMmioSize) },
  { FixedPcdGet64(PcdDmiBaseAddress), FixedPcdGet32(PcdDmiMmioSize) },
  { FixedPcdGet64(PcdEpBaseAddress), FixedPcdGet32(PcdEpMmioSize) },
  { FixedPcdGet64(PcdGdxcBaseAddress), FixedPcdGet32(PcdGdxcMmioSize) }
};

EFI_STATUS
MrcConfigInit(
  IN UINT16 BoardId
);

EFI_STATUS
SaGpioConfigInit(
  IN UINT16 BoardId
);

EFI_STATUS
  SaMiscConfigInit(
IN UINT16         BoardId
);

EFI_STATUS
  RootPortClkInfoInit(
IN UINT16 BoardId
);

EFI_STATUS
  UsbConfigInit(
IN UINT16 BoardId
);

EFI_STATUS
GpioGroupTierInit(
  IN UINT16 BoardId
);

EFI_STATUS
GpioTablePreMemInit(
  IN UINT16 BoardId
);

EFI_STATUS
PchPmConfigInit(
  IN UINT16 BoardId
);

EFI_STATUS
SaDisplayConfigInit(
  IN UINT16 BoardId
);

EFI_STATUS
EFIAPI
PlatformInitPreMemCallBack(
  IN CONST EFI_PEI_SERVICES      **PeiServices,
  IN EFI_PEI_NOTIFY_DESCRIPTOR   *NotifyDescriptor,
  IN VOID                        *Ppi
);

EFI_STATUS
EFIAPI
MemoryDiscoveredPpiNotify(
  IN CONST EFI_PEI_SERVICES      **PeiServices,
  IN EFI_PEI_NOTIFY_DESCRIPTOR   *NotifyDescriptor,
  IN VOID                        *Ppi
);

EFI_STATUS
EFIAPI
PchReset(
  IN CONST EFI_PEI_SERVICES    **PeiServices
);

static EFI_PEI_RESET_PPI mResetPpi = {
  PchReset
};

static EFI_PEI_PPI_DESCRIPTOR mPreMemPpiList[] = {
  {
    (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
    &gEfiPeiResetPpiGuid,
    &mResetPpi
  }
};

static EFI_PEI_NOTIFY_DESCRIPTOR mPreMemNotifyList = {
  (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
  &gEfiPeiReadOnlyVariable2PpiGuid,
  (EFI_PEIM_NOTIFY_ENTRY_POINT)PlatformInitPreMemCallBack
};

static EFI_PEI_NOTIFY_DESCRIPTOR mMemDiscoveredNotifyList = {
  (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
  &gEfiPeiMemoryDiscoveredPpiGuid,
  (EFI_PEIM_NOTIFY_ENTRY_POINT)MemoryDiscoveredPpiNotify
};

/**
Board misc init function for PEI pre-memory phase.

@param[in]  BoardId   An unsigned integer represent the board id.

@retval EFI_SUCCESS   The function completed successfully.
**/
EFI_STATUS
BoardMiscInitPreMem(
  IN UINT16 BoardId
  )
{
  //
  // Configure WWAN Full Card Power Off and reset pins
  //
  switch (BoardId) {
    // todo for UP Xtreme
    case BoardIdWhiskeyLakeRvp:
      //
      // According to board default settings, GPP_D16 is used to enable/disable modem
      // power. An alternative way to contol modem power is to toggle FCP_OFF via GPP_D13
      // but board rework is required.
      //
      PcdSet32S (PcdWwanFullCardPowerOffGpio, GPIO_CNL_LP_GPP_D16);
      PcdSet32S (PcdWwanBbrstGpio, GPIO_CNL_LP_GPP_F1);
      PcdSet32S (PcdWwanPerstGpio, GPIO_CNL_LP_GPP_E15);
      PcdSet8S (PcdWwanPerstGpioPolarity, 1);
      break;

    default:
      break;
  }

  //
  // Pc8374SioKbc Present
  //
  PcdSetBoolS (PcdPc8374SioKbcPresent, FALSE);

  return EFI_SUCCESS;
}

//@todo it should be moved to Si Pkg.
/**
Early Platform PCH initialization
**/
VOID
EarlyPlatformPchInit(
  VOID
)
{
  UINT8        Data8;
  UINT16       Data16;
  UINT8        TcoRebootHappened;
  TCO_WDT_HOB  *TcoWdtHobPtr;
  EFI_STATUS   Status;

  ///
  /// Halt the TCO timer
  ///
  Data16 = IoRead16 (PcdGet16 (PcdTcoBaseAddress) + R_TCO_IO_TCO1_CNT);
  Data16 |= B_TCO_IO_TCO1_CNT_TMR_HLT;
  IoWrite16 (PcdGet16 (PcdTcoBaseAddress) + R_TCO_IO_TCO1_CNT, Data16);

  ///
  /// Read the Second TO status bit
  ///
  Data8 = IoRead8 (PcdGet16 (PcdTcoBaseAddress) + R_TCO_IO_TCO2_STS);
  if ((Data8 & B_TCO_IO_TCO2_STS_SECOND_TO) == B_TCO_IO_TCO2_STS_SECOND_TO) {
    TcoRebootHappened = 1;
    DEBUG ((DEBUG_INFO, "PlatformInitPreMem - TCO Second TO status bit is set. This might be a TCO reboot\n"));
  }
  else {
    TcoRebootHappened = 0;
  }

  ///
  /// Create HOB
  ///
  Status = PeiServicesCreateHob (EFI_HOB_TYPE_GUID_EXTENSION, sizeof (TCO_WDT_HOB), (VOID **) &TcoWdtHobPtr);
  if (!EFI_ERROR (Status)) {
    TcoWdtHobPtr->Header.Name = gTcoWdtHobGuid;
    TcoWdtHobPtr->TcoRebootHappened = TcoRebootHappened;
  }

  ///
  /// Clear the Second TO status bit
  ///
  IoWrite8 (PcdGet16(PcdTcoBaseAddress) + R_TCO_IO_TCO2_STS, B_TCO_IO_TCO2_STS_SECOND_TO);
}

/**
  Board configuration initialization in the pre-memory boot phase.

**/
VOID
BoardConfigInitPreMem (
  VOID
  )
{
  EFI_STATUS Status;
  UINT16 BoardId;

  BoardId = BoardIdUpXtreme;

  Status = MrcConfigInit (BoardId);
  ASSERT_EFI_ERROR (Status);

  Status = SaGpioConfigInit (BoardId);
  ASSERT_EFI_ERROR (Status);

  Status = SaMiscConfigInit (BoardId);
  ASSERT_EFI_ERROR (Status);

  Status = RootPortClkInfoInit (BoardId);
  ASSERT_EFI_ERROR (Status);

  Status = UsbConfigInit (BoardId);
  ASSERT_EFI_ERROR (Status);

  Status = GpioGroupTierInit (BoardId);
  ASSERT_EFI_ERROR (Status);

  Status = GpioTablePreMemInit (BoardId);
  ASSERT_EFI_ERROR (Status);

  Status = PchPmConfigInit (BoardId);
  ASSERT_EFI_ERROR (Status);

  Status = BoardMiscInitPreMem (BoardId);
  ASSERT_EFI_ERROR (Status);

  Status = SaDisplayConfigInit (BoardId);
  ASSERT_EFI_ERROR (Status);
}

/**
This function handles PlatformInit task after PeiReadOnlyVariable2 PPI produced

@param[in]  PeiServices   Pointer to PEI Services Table.
@param[in]  NotifyDesc    Pointer to the descriptor for the Notification event that
                          caused this function to execute.
@param[in]  Ppi           Pointer to the PPI data associated with this function.

@retval     EFI_SUCCESS  The function completes successfully
@retval     others
**/
EFI_STATUS
EFIAPI
PlatformInitPreMemCallBack(
  IN CONST EFI_PEI_SERVICES     **PeiServices,
  IN EFI_PEI_NOTIFY_DESCRIPTOR  *NotifyDescriptor,
  IN VOID                       *Ppi
)
{
  EFI_STATUS                        Status;
  UINT16                            ABase;
  UINT8                             FwConfig;

  //
  // Init Board Config Pcd.
  //
  BoardConfigInitPreMem ();

  FwConfig = FwConfigProduction;
  PcdSetBoolS (PcdPcieWwanEnable, FALSE);
  PcdSetBoolS (PcdWwanResetWorkaround, FALSE);

  //
  // Early Board Configuration before memory is ready.
  //
  Status = BoardInitEarlyPreMem ();
  ASSERT_EFI_ERROR (Status);

  ///
  /// If there was unexpected reset but no WDT expiration and no resume from S3/S4,
  /// clear unexpected reset status and enforce expiration. This is to inform Firmware
  /// which has no access to unexpected reset status bit, that something went wrong.
  ///
  OcWdtResetCheck ();

  Status = OcWdtInit ();
  ASSERT_EFI_ERROR (Status);

  //
  // Initialize Intel PEI Platform Policy
  //
  PeiPolicyInitPreMem (FwConfig);

  ///
  /// Configure GPIO and SIO
  ///
  Status = BoardInitPreMem ();
  ASSERT_EFI_ERROR (Status);

  ABase = PmcGetAcpiBase ();

  ///
  /// Clear all pending SMI. On S3 clear power button enable so it will not generate an SMI.
  ///
  IoWrite16 (ABase + R_ACPI_IO_PM1_EN, 0);
  IoWrite32 (ABase + R_ACPI_IO_GPE0_EN_127_96, 0);

  ///
  /// Install Pre Memory PPIs
  ///
  Status = PeiServicesInstallPpi (&mPreMemPpiList[0]);
  ASSERT_EFI_ERROR (Status);

  return Status;
}

/**
Provide hard reset PPI service.
To generate full hard reset, write 0x0E to PCH RESET_GENERATOR_PORT (0xCF9).

@param[in]  PeiServices       General purpose services available to every PEIM.

@retval     Not return        System reset occured.
@retval     EFI_DEVICE_ERROR  Device error, could not reset the system.
**/
EFI_STATUS
EFIAPI
PchReset(
  IN CONST EFI_PEI_SERVICES    **PeiServices
)
{
  DEBUG ((DEBUG_INFO, "Perform Cold Reset\n"));
  IoWrite8 (RESET_GENERATOR_PORT, 0x0E);

  CpuDeadLoop ();

  ///
  /// System reset occured, should never reach at this line.
  ///
  ASSERT_EFI_ERROR (EFI_DEVICE_ERROR);

  return EFI_DEVICE_ERROR;
}

/**
Install Firmware Volume Hob's once there is main memory

@param[in]  PeiServices       General purpose services available to every PEIM.
@param[in]  NotifyDescriptor  Notify that this module published.
@param[in]  Ppi               PPI that was installed.

@retval     EFI_SUCCESS       The function completed successfully.
**/
EFI_STATUS
EFIAPI
MemoryDiscoveredPpiNotify(
  IN CONST EFI_PEI_SERVICES     **PeiServices,
  IN EFI_PEI_NOTIFY_DESCRIPTOR  *NotifyDescriptor,
  IN VOID                       *Ppi
)
{
  EFI_STATUS                    Status;
  EFI_BOOT_MODE                 BootMode;
  UINTN                         Index;
  UINT8                         PhysicalAddressBits;
  UINT32                        RegEax;
  MEMORY_MAP                    PcieMmioMap;

  Index = 0;

  Status = PeiServicesGetBootMode (&BootMode);
  ASSERT_EFI_ERROR (Status);

  AsmCpuid (0x80000000, &RegEax, NULL, NULL, NULL);
  if (RegEax >= 0x80000008) {
    AsmCpuid (0x80000008, &RegEax, NULL, NULL, NULL);
    PhysicalAddressBits = (UINT8) RegEax;
  }
  else {
    PhysicalAddressBits = 36;
  }

  ///
  /// Create a CPU hand-off information
  ///
  BuildCpuHob (PhysicalAddressBits, 16);

  ///
  /// Build Memory Mapped IO Resource which is used to build E820 Table in LegacyBios.
  ///
  PcieMmioMap.BaseAddress = FixedPcdGet64 (PcdPciExpressBaseAddress);
  PcieMmioMap.Length = PcdGet32 (PcdPciExpressRegionLength);

  BuildResourceDescriptorHob (
    EFI_RESOURCE_MEMORY_MAPPED_IO,
    (EFI_RESOURCE_ATTRIBUTE_PRESENT |
     EFI_RESOURCE_ATTRIBUTE_INITIALIZED |
     EFI_RESOURCE_ATTRIBUTE_UNCACHEABLE),
    PcieMmioMap.BaseAddress,
    PcieMmioMap.Length
  );
  BuildMemoryAllocationHob (
    PcieMmioMap.BaseAddress,
    PcieMmioMap.Length,
    EfiMemoryMappedIO
  );
  for (Index = 0; Index < sizeof (MmioMap) / (sizeof (MEMORY_MAP)); Index++) {
    BuildResourceDescriptorHob (
      EFI_RESOURCE_MEMORY_MAPPED_IO,
      (EFI_RESOURCE_ATTRIBUTE_PRESENT |
       EFI_RESOURCE_ATTRIBUTE_INITIALIZED |
       EFI_RESOURCE_ATTRIBUTE_UNCACHEABLE),
      MmioMap[Index].BaseAddress,
      MmioMap[Index].Length
    );
    BuildMemoryAllocationHob (
      MmioMap[Index].BaseAddress,
      MmioMap[Index].Length,
      EfiMemoryMappedIO
    );
  }

  //
  // Report resource HOB for flash FV
  //
  BuildResourceDescriptorHob (
    EFI_RESOURCE_MEMORY_MAPPED_IO,
    (EFI_RESOURCE_ATTRIBUTE_PRESENT |
     EFI_RESOURCE_ATTRIBUTE_INITIALIZED |
     EFI_RESOURCE_ATTRIBUTE_UNCACHEABLE),
    (UINTN) FixedPcdGet32 (PcdFlashAreaBaseAddress),
    (UINTN) FixedPcdGet32 (PcdFlashAreaSize)
  );
  BuildMemoryAllocationHob (
    (UINTN) FixedPcdGet32 (PcdFlashAreaBaseAddress),
    (UINTN) FixedPcdGet32 (PcdFlashAreaSize),
    EfiMemoryMappedIO
  );

  BuildFvHob (
    (UINTN) FixedPcdGet32 (PcdFlashAreaBaseAddress),
    (UINTN) FixedPcdGet32 (PcdFlashAreaSize)
  );

  return Status;
}


/**
  Board configuration init function for PEI pre-memory phase.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   The parameter is NULL.
**/
EFI_STATUS
EFIAPI
UpXtremeInitPreMem (
  VOID
  )
{
  EFI_STATUS Status;

  ///
  /// Install Stall PPI
  ///
  Status = InstallStallPpi ();
  ASSERT_EFI_ERROR (Status);

  ///@todo it should be moved to Si Pkg.
  ///
  /// Do Early PCH init
  ///
  EarlyPlatformPchInit ();

  //
  // Install PCH RESET PPI and EFI RESET2 PeiService
  //
  Status = PchInitializeReset ();
  ASSERT_EFI_ERROR (Status);

  ///
  /// Performing PlatformInitPreMemCallBack after PeiReadOnlyVariable2 PPI produced
  ///
  Status = PeiServicesNotifyPpi (&mPreMemNotifyList);

  ///
  /// After code reorangized, memorycallback will run because the PPI is already
  /// installed when code run to here, it is supposed that the InstallEfiMemory is
  /// done before.
  ///
  Status = PeiServicesNotifyPpi (&mMemDiscoveredNotifyList);

  return EFI_SUCCESS;
}

/**
  Configure GPIO and SIO before memory ready

  @retval  EFI_SUCCESS   Operation success.
**/
EFI_STATUS
EFIAPI
UpXtremeBoardInitBeforeMemoryInit (
  VOID
  )
{
  UpXtremeInitPreMem ();

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
UpXtremeBoardDebugInit (
  VOID
  )
{
  // Serial port initialization is performed in the PlatformHookSerialPortInitialize ()
  // function invoked by SerialPortInitialize () in SerialPortLib
DEBUG_CODE_BEGIN();
  DEBUG_CONFIG_DATA_HOB    *DebugConfigDataHob;

  DebugConfigDataHob = BuildGuidHob (&gDebugConfigHobGuid, sizeof (DEBUG_CONFIG_DATA_HOB));
  ASSERT (DebugConfigDataHob != NULL);
  if (DebugConfigDataHob == NULL) {
    DEBUG ((DEBUG_ERROR, "Build Debug Config Hob failed!\n"));
    return EFI_OUT_OF_RESOURCES;
  }

  switch (PcdGet32 (PcdSerialBaudRate)) {
  case 9600:
    DebugConfigDataHob->SerialDebugBaudRate = 3;
    break;
  case 19200:
    DebugConfigDataHob->SerialDebugBaudRate = 4;
    break;
  case 57600:
    DebugConfigDataHob->SerialDebugBaudRate = 6;
    break;
  default:
    DebugConfigDataHob->SerialDebugBaudRate = 7;
  }
  DebugConfigDataHob->SerialDebug = 0x3;
  DebugConfigDataHob->RamDebugInterface = 0;
  DebugConfigDataHob->UartDebugInterface = 0;
  DebugConfigDataHob->Usb3DebugInterface = 0;
  DebugConfigDataHob->SerialIoDebugInterface = (FeaturePcdGet (PcdSerialIoUartEnable)) ? 1 : 0;
  DebugConfigDataHob->TraceHubDebugInterface = 0;

DEBUG_CODE_END();

  return EFI_SUCCESS;
}

EFI_BOOT_MODE
EFIAPI
UpXtremeBoardBootModeDetect (
  VOID
  )
{
  return BOOT_WITH_FULL_CONFIGURATION;
}


