/** @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/HdaVerbTableLib.h>
#include <Library/IoLib.h>
#include <Library/HobLib.h>
#include <Library/PcdLib.h>
#include <Library/PchCycleDecodingLib.h>
#include <Library/PciLib.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 <IoExpander.h>
#include <AttemptUsbFirst.h>
#include <PeiPlatformHookLib.h>
#include <Library/PeiPolicyInitLib.h>
#include <Library/PchInfoLib.h>
#include <FirwmareConfigurations.h>
#include "CometlakeURvpInit.h"
#include <Library/ConfigBlockLib.h>

EFI_STATUS
BoardFunctionInit (
  IN UINT16 BoardId
  );

/**
  GPIO init function for PEI post memory phase.

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

  @retval     EFI_SUCCESS   The function completed successfully.
**/
EFI_STATUS
BoardGpioInit(
  IN UINT16 BoardId
  )
{
  //
  // GPIO Table Init.
  //
  switch (BoardId) {

    case BoardIdCometLakeULpddr3Rvp:
      PcdSet32S (PcdBoardGpioTable, (UINTN) mGpioTableCmlULpddr3);
      PcdSet16S (PcdBoardGpioTableSize, mGpioTableCmlULpddr3Size);
      PcdSet32S (PcdBoardGpioTable2, 0);
      PcdSet16S (PcdBoardGpioTable2Size, 0);
      break;

    default:
      DEBUG ((DEBUG_INFO, "For Unknown Board ID..Use Default GPIO Table...\n"));
      PcdSet32S (PcdBoardGpioTable, (UINTN) mGpioTableDefault);
      PcdSet16S (PcdBoardGpioTableSize, mGpioTableDefaultSize);
      break;
  }

  return EFI_SUCCESS;
}

/**
  Touch panel GPIO init function for PEI post memory phase.

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

  @retval     EFI_SUCCESS   The function completed successfully.
**/
EFI_STATUS
TouchPanelGpioInit (
  IN UINT16 BoardId
  )
{
  switch (BoardId) {
    default:
      PcdSet32S (PcdBoardGpioTableTouchPanel, 0);
    break;
  }
  return EFI_SUCCESS;
}

/**
  Misc. init function for PEI post memory phase.

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

  @retval     EFI_SUCCESS   The function completed successfully.
**/
EFI_STATUS
BoardMiscInit (
  IN UINT16 BoardId
  )
{
  PcdSetBoolS (PcdDebugUsbUartEnable, FALSE);

  switch (BoardId) {

    case BoardIdCometLakeULpddr3Rvp:

      PcdSetBoolS(PcdMipiCamGpioEnable, FALSE);
      break;

    default:
      PcdSetBoolS(PcdMipiCamGpioEnable, FALSE);
      break;
  }

  return EFI_SUCCESS;
}

/**
  Security GPIO init function for PEI post memory phase.

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

  @retval     EFI_SUCCESS   The function completed successfully.
**/
EFI_STATUS
BoardSecurityInit (
  IN UINT16 BoardId
  )
{
  switch (BoardId) {

    case BoardIdCometLakeULpddr3Rvp:

      // TPM interrupt connects to GPIO_CNL_H_GPP_A_7
      PcdSet32S (PcdTpm2CurrentIrqNum, 0x1F);
      break;
  }

  return EFI_SUCCESS;
}

/**
  Board configuration initialization in the post-memory boot phase.
**/
VOID
BoardConfigInit (
  VOID
  )
{
  EFI_STATUS  Status;
  UINT16      BoardId;

  BoardId = BoardIdCometLakeULpddr3Rvp;

  Status = BoardGpioInit (BoardId);
  ASSERT_EFI_ERROR (Status);

  Status = TouchPanelGpioInit (BoardId);
  ASSERT_EFI_ERROR (Status);

  Status = HdaVerbTableInit (BoardId);
  ASSERT_EFI_ERROR (Status);

  Status = BoardMiscInit (BoardId);
  ASSERT_EFI_ERROR (Status);

  Status = BoardFunctionInit (BoardId);
  ASSERT_EFI_ERROR (Status);

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

/**
  Create the HOB for hotkey status for 'Attempt USB First' feature

  @retval  EFI_SUCCESS  HOB Creating successful.
  @retval  Others       HOB Creating failed.
**/
EFI_STATUS
CreateAttemptUsbFirstHotkeyInfoHob (
  VOID
  )
{
  EFI_STATUS                     Status;
  ATTEMPT_USB_FIRST_HOTKEY_INFO  AttemptUsbFirstHotkeyInfo;

  Status = EFI_SUCCESS;

  ZeroMem (
    &AttemptUsbFirstHotkeyInfo,
    sizeof (AttemptUsbFirstHotkeyInfo)
    );

  AttemptUsbFirstHotkeyInfo.RevisonId = 0;
  AttemptUsbFirstHotkeyInfo.HotkeyTriggered = FALSE;

  ///
  /// Build HOB for Attempt USB First feature
  ///
  BuildGuidDataHob (
    &gAttemptUsbFirstHotkeyInfoHobGuid,
    &(AttemptUsbFirstHotkeyInfo),
    sizeof (ATTEMPT_USB_FIRST_HOTKEY_INFO)
    );

  return Status;
}

/**
  Search and identify the physical address of a
  file module inside the FW_BINARIES_FV_SIGNED FV

  @retval  EFI_SUCCESS  If address has been found
  @retval  Others       If address has not been found
**/
EFI_STATUS
FindModuleInFlash2 (
  IN EFI_FIRMWARE_VOLUME_HEADER *FvHeader,
  IN EFI_GUID                   *GuidPtr,
  IN OUT UINT32                 *ModulePtr,
  IN OUT UINT32                 *ModuleSize
  )
{
  EFI_FFS_FILE_HEADER        *FfsHeader;
  EFI_FV_FILE_INFO           FileInfo;
  EFI_PEI_FILE_HANDLE        FileHandle;
  EFI_COMMON_SECTION_HEADER  *SectionHeader;
  VOID                       *FileBuffer;
  EFI_STATUS                 Status;

  FfsHeader = NULL;
  FileHandle = NULL;
  SectionHeader = NULL;
  FileBuffer = NULL;

  while (TRUE) {
    //
    // Locate FV_IMAGE file type in the FW_BINARIES_FV_SIGNED firmware volume
    //
    Status = PeiServicesFfsFindNextFile (EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE, FvHeader, &FileHandle);
    if (EFI_ERROR (Status)) {
      // unable to find FV_IMAGE file in this FV
      break;
    }

    FfsHeader = (EFI_FFS_FILE_HEADER*)FileHandle;
    DEBUG ((DEBUG_INFO, "FfsHeader 0x%X:\n", FfsHeader));
    DEBUG ((DEBUG_INFO, " Name = 0x%g\n", &FfsHeader->Name));
    DEBUG ((DEBUG_INFO, " Type = 0x%X\n", FfsHeader->Type));
    if (IS_FFS_FILE2 (FfsHeader)) {
      DEBUG ((DEBUG_INFO, " Size = 0x%X\n", FFS_FILE2_SIZE(FfsHeader)));
    }
    else {
      DEBUG ((DEBUG_INFO, " Size = 0x%X\n", FFS_FILE_SIZE(FfsHeader)));
    }

    //
    // Locate FW_BINARIES_FV FV_IMAGE Section
    //
    Status = PeiServicesFfsFindSectionData (EFI_SECTION_FIRMWARE_VOLUME_IMAGE, FileHandle, &FileBuffer);
    if (EFI_ERROR (Status)) {
      // continue to search for the next FV_IMAGE file
      DEBUG ((DEBUG_INFO, "FW_BINARIES_FV section not found. Status = %r\n", Status));
      continue;
    }

    SectionHeader = (EFI_COMMON_SECTION_HEADER *)FileBuffer;
    DEBUG ((DEBUG_INFO, "GUIDED SectionHeader 0x%X:\n",
    (UINT32)(UINT8 *)SectionHeader));
    if (IS_SECTION2(SectionHeader)) {
      DEBUG ((DEBUG_INFO, " Guid      = 0x%g\n",
        &((EFI_GUID_DEFINED_SECTION2 *)SectionHeader)->SectionDefinitionGuid));
      DEBUG ((DEBUG_INFO, " DataOfset = 0x%X\n",
        ((EFI_GUID_DEFINED_SECTION2 *)SectionHeader)->DataOffset));
    }
    else {
      DEBUG ((DEBUG_INFO, " Guid      = 0x%g\n",
        &((EFI_GUID_DEFINED_SECTION *)SectionHeader)->SectionDefinitionGuid));
      DEBUG ((DEBUG_INFO, " DataOfset = 0x%X\n",
        ((EFI_GUID_DEFINED_SECTION *)SectionHeader)->DataOffset));
    }
    DEBUG ((DEBUG_INFO, " Type      = 0x%X\n", SectionHeader->Type));

    //
    // Locate Firmware File System file within Firmware Volume
    //
    Status = PeiServicesFfsFindFileByName (GuidPtr, FileBuffer, (VOID **)&FfsHeader);
    if (EFI_ERROR (Status)) {
      // continue to search for the next FV_IMAGE file
      DEBUG ((DEBUG_INFO, "Module not found. Status = %r\n", Status));
      continue;
    }

    *ModulePtr = (UINT32)((UINT8 *)FfsHeader + sizeof(EFI_FFS_FILE_HEADER));

    //
    // Get File Information
    //
    Status = PeiServicesFfsGetFileInfo (FfsHeader, &FileInfo);
    if (!EFI_ERROR (Status)) {
      *ModuleSize = (UINT32)FileInfo.BufferSize;
      DEBUG ((DEBUG_INFO, "Module {0x%g} found at = 0x%X, Size = 0x%X\n",
        &FfsHeader->Name, *ModulePtr, *ModuleSize));
      return Status;
    }
  }

  return EFI_NOT_FOUND;
}

/**
  Get the ChipsetInit Binary pointer.

  @retval EFI_SUCCESS               - ChipsetInit Binary found.
  @retval EFI_NOT_FOUND             - ChipsetInit Binary not found.
**/
EFI_STATUS
UpdateChipsetInitPtr (
  VOID
  )
{
  EFI_STATUS                    Status;
  PCH_STEPPING                  PchStep;
  EFI_FIRMWARE_VOLUME_HEADER    *FvHeader;
  EFI_GUID                      *ChipsetInitBinaryGuidPtr;
  SI_POLICY_PPI                 *SiPolicyPpi;
  PCH_HSIO_CONFIG               *HsioConfig;
  UINT32                        ModuleAddr;
  UINT32                        ModuleSize;

  ModuleAddr = 0;
  ModuleSize = 0;
  PchStep = PchStepping ();

  Status = PeiServicesLocatePpi (
    &gSiPolicyPpiGuid,
    0,
    NULL,
    (VOID **)&SiPolicyPpi
    );
  ASSERT_EFI_ERROR (Status);

  Status = GetConfigBlock ((VOID *)SiPolicyPpi, &gHsioConfigGuid, (VOID *)&HsioConfig);
  ASSERT_EFI_ERROR (Status);

  ChipsetInitBinaryGuidPtr = NULL;
  if (IsPchLp()) {
    switch (PchStep) {
      case PCH_A0:
      case PCH_D0:
      case PCH_D1:
        ChipsetInitBinaryGuidPtr = &gCnlPchLpChipsetInitTableDxGuid;
        DEBUG ((DEBUG_INFO, "Using CnlPchLpChipsetInitTable_Dx table \n"));
        break;
      default:
        return EFI_NOT_FOUND;
    }
  } else {
    return EFI_NOT_FOUND;
  }

  //
  // Locate Firmware Volume header
  //
  FvHeader = (EFI_FIRMWARE_VOLUME_HEADER *) FixedPcdGet32 (PcdFlashFvPostMemoryBase);
  Status = FindModuleInFlash2 (FvHeader, ChipsetInitBinaryGuidPtr, &ModuleAddr, &ModuleSize);
  //
  // Get ChipsetInit Binary Pointer
  //
  HsioConfig->ChipsetInitBinPtr = ModuleAddr;

  //
  // Get File Size
  //
  HsioConfig->ChipsetInitBinLen = ModuleSize;

  DEBUG ((DEBUG_INFO, "ChipsetInit Binary Location: %x\n", HsioConfig->ChipsetInitBinPtr));
  DEBUG ((DEBUG_INFO, "ChipsetInit Binary Size: %x\n", HsioConfig->ChipsetInitBinLen));

  return Status;
}

/**
  Configure GPIO and SIO

  @retval  EFI_SUCCESS   Operation success.
**/
EFI_STATUS
EFIAPI
CometlakeURvpBoardInitBeforeSiliconInit(
  VOID
  )
{
  EFI_STATUS      Status;
  UINT8           FwConfig;

  BoardConfigInit ();
  //
  // Configure GPIO and SIO
  //
  Status = BoardInit ();
  ASSERT_EFI_ERROR (Status);

  FwConfig = FwConfigProduction;
  PeiPolicyInit (FwConfig);

  //
  // Create USB Boot First hotkey information HOB
  //
  CreateAttemptUsbFirstHotkeyInfoHob ();

  //
  // Initializing Platform Specific Programming
  //
  Status = PlatformSpecificInit ();
  ASSERT_EFI_ERROR(Status);

  //
  // Update ChipsetInitPtr
  //
  Status = UpdateChipsetInitPtr ();

  ///
  /// Do Late PCH init
  ///
  LateSiliconInit ();

  return EFI_SUCCESS;
}
