/** @file
  PCH PCR library.
  All function in this library is available for PEI, DXE, and SMM,
  But do not support UEFI RUNTIME environment call.

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

**/

#include <Base.h>
#include <Uefi/UefiBaseType.h>
#include <Library/IoLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseLib.h>
#include <Library/MmPciLib.h>
#include <PchAccess.h>
#include <Library/PchInfoLib.h>

/**
  Read PCR register. (This is internal function)
  It returns PCR register and size in 1byte/2bytes/4bytes.
  The Offset should not exceed 0xFFFF and must be aligned with size.

  @param[in]  Pid                       Port ID
  @param[in]  Offset                    Register offset of this Port ID
  @param[in]  Size                      Size for read. Must be 1 or 2 or 4.
  @param[out] OutData                   Buffer of Output Data. Must be the same size as Size parameter.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid offset passed.
**/
STATIC
EFI_STATUS
PchPcrRead (
  IN  PCH_SBI_PID                       Pid,
  IN  UINT16                            Offset,
  IN  UINTN                             Size,
  OUT UINT32                            *OutData
  )
{
  if ((Offset & (Size - 1)) != 0) {
    DEBUG ((DEBUG_ERROR, "PchPcrRead error. Invalid Offset: %x Size: %x", Offset, Size));
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }
  //
  // @todo SKL PCH: check PID that not expected to use this routine, such as CAM_FLIS, CSME0
  //

  switch (Size) {
    case 4:
      *(UINT32*) OutData = MmioRead32 (PCH_PCR_ADDRESS (Pid, Offset));
      break;
    case 2:
      *(UINT16*) OutData = MmioRead16 (PCH_PCR_ADDRESS (Pid, Offset));
      break;
    case 1:
      *(UINT8*) OutData = MmioRead8  (PCH_PCR_ADDRESS (Pid, Offset));
      break;
    default:
      break;
  }
  return EFI_SUCCESS;
}

/**
  Read PCR register.
  It returns PCR register and size in 4bytes.
  The Offset should not exceed 0xFFFF and must be aligned with size.

  @param[in]  Pid                       Port ID
  @param[in]  Offset                    Register offset of this Port ID
  @param[out] OutData                   Buffer of Output Data. Must be the same size as Size parameter.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid offset passed.
**/
EFI_STATUS
PchPcrRead32 (
  IN  PCH_SBI_PID                       Pid,
  IN  UINT16                            Offset,
  OUT UINT32                            *OutData
  )
{
  return PchPcrRead (Pid, Offset, 4, (UINT32*) OutData);
}

/**
  Read PCR register.
  It returns PCR register and size in 2bytes.
  The Offset should not exceed 0xFFFF and must be aligned with size.

  @param[in]  Pid                       Port ID
  @param[in]  Offset                    Register offset of this Port ID
  @param[out] OutData                   Buffer of Output Data. Must be the same size as Size parameter.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid offset passed.
**/
EFI_STATUS
PchPcrRead16 (
  IN  PCH_SBI_PID                       Pid,
  IN  UINT16                            Offset,
  OUT UINT16                            *OutData
  )
{
  return PchPcrRead (Pid, Offset, 2, (UINT32*) OutData);
}

/**
  Read PCR register.
  It returns PCR register and size in 1bytes.
  The Offset should not exceed 0xFFFF and must be aligned with size.

  @param[in]  Pid                       Port ID
  @param[in]  Offset                    Register offset of this Port ID
  @param[out] OutData                   Buffer of Output Data. Must be the same size as Size parameter.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid offset passed.
**/
EFI_STATUS
PchPcrRead8 (
  IN  PCH_SBI_PID                       Pid,
  IN  UINT16                            Offset,
  OUT UINT8                             *OutData
  )
{
  return PchPcrRead (Pid, Offset, 1, (UINT32*) OutData);
}

BOOLEAN
PchPcrWriteMmioCheck (
  IN  PCH_SBI_PID                       Pid,
  IN  UINT16                            Offset
  )
{
  DEBUG_CODE_BEGIN ();
  PCH_SERIES  PchSeries;

  PchSeries = GetPchSeries ();
  //
  // 1. USB2 AFE register must use SBI method
  //

  //
  // 2. GPIO unlock register field must use SBI method
  //
  if (Pid == PID_GPIOCOM0) {
    if (((PchSeries == PchLp) &&
         ((Offset == R_PCH_LP_PCR_GPIO_GPP_A_PADCFGLOCK)   ||
          (Offset == R_PCH_LP_PCR_GPIO_GPP_A_PADCFGLOCKTX) ||
          (Offset == R_PCH_LP_PCR_GPIO_GPP_B_PADCFGLOCK)   ||
          (Offset == R_PCH_LP_PCR_GPIO_GPP_B_PADCFGLOCKTX))) ||
        ((PchSeries == PchH) &&
         ((Offset == R_PCH_H_PCR_GPIO_GPP_A_PADCFGLOCK)   ||
          (Offset == R_PCH_H_PCR_GPIO_GPP_A_PADCFGLOCKTX) ||
          (Offset == R_PCH_H_PCR_GPIO_GPP_B_PADCFGLOCK)   ||
          (Offset == R_PCH_H_PCR_GPIO_GPP_B_PADCFGLOCKTX)))) {
      return FALSE;
    }
  }
  if (Pid == PID_GPIOCOM1) {
    if (((PchSeries == PchLp) &&
         ((Offset == R_PCH_LP_PCR_GPIO_GPP_C_PADCFGLOCK)   ||
          (Offset == R_PCH_LP_PCR_GPIO_GPP_C_PADCFGLOCKTX) ||
          (Offset == R_PCH_LP_PCR_GPIO_GPP_D_PADCFGLOCK)   ||
          (Offset == R_PCH_LP_PCR_GPIO_GPP_D_PADCFGLOCKTX) ||
          (Offset == R_PCH_LP_PCR_GPIO_GPP_E_PADCFGLOCK)   ||
          (Offset == R_PCH_LP_PCR_GPIO_GPP_E_PADCFGLOCKTX))) ||
        ((PchSeries == PchH) &&
         ((Offset == R_PCH_H_PCR_GPIO_GPP_C_PADCFGLOCK)   ||
          (Offset == R_PCH_H_PCR_GPIO_GPP_C_PADCFGLOCKTX) ||
          (Offset == R_PCH_H_PCR_GPIO_GPP_D_PADCFGLOCK)   ||
          (Offset == R_PCH_H_PCR_GPIO_GPP_D_PADCFGLOCKTX) ||
          (Offset == R_PCH_H_PCR_GPIO_GPP_E_PADCFGLOCK)   ||
          (Offset == R_PCH_H_PCR_GPIO_GPP_E_PADCFGLOCKTX) ||
          (Offset == R_PCH_H_PCR_GPIO_GPP_F_PADCFGLOCK)   ||
          (Offset == R_PCH_H_PCR_GPIO_GPP_F_PADCFGLOCKTX) ||
          (Offset == R_PCH_H_PCR_GPIO_GPP_G_PADCFGLOCK)   ||
          (Offset == R_PCH_H_PCR_GPIO_GPP_G_PADCFGLOCKTX) ||
          (Offset == R_PCH_H_PCR_GPIO_GPP_H_PADCFGLOCK)   ||
          (Offset == R_PCH_H_PCR_GPIO_GPP_H_PADCFGLOCKTX)))) {
      return FALSE;
    }
  }
  if (Pid == PID_GPIOCOM2) {
    if (((PchSeries == PchLp) &&
         ((Offset == R_PCH_LP_PCR_GPIO_GPD_PADCFGLOCK)   ||
          (Offset == R_PCH_LP_PCR_GPIO_GPD_PADCFGLOCKTX))) ||
        ((PchSeries == PchH) &&
         ((Offset == R_PCH_H_PCR_GPIO_GPD_PADCFGLOCK)   ||
          (Offset == R_PCH_H_PCR_GPIO_GPD_PADCFGLOCKTX)))) {
      return FALSE;
    }
  }
  if (Pid == PID_GPIOCOM3) {
    if (((PchSeries == PchLp) &&
         ((Offset == R_PCH_LP_PCR_GPIO_GPP_F_PADCFGLOCK)   ||
          (Offset == R_PCH_LP_PCR_GPIO_GPP_F_PADCFGLOCKTX) ||
          (Offset == R_PCH_LP_PCR_GPIO_GPP_G_PADCFGLOCK)   ||
          (Offset == R_PCH_LP_PCR_GPIO_GPP_G_PADCFGLOCKTX))) ||
        ((PchSeries == PchH) &&
         ((Offset == R_PCH_H_PCR_GPIO_GPP_I_PADCFGLOCK)   ||
          (Offset == R_PCH_H_PCR_GPIO_GPP_I_PADCFGLOCKTX)))) {
      return FALSE;
    }
  }
  //
  // 3. CIO2 FLIS regsiter must use SBI method
  //

  //
  // 4. CSME0 based PCR should use the SBI method due to the FID requirement
  //
  if (Pid == PID_CSME0) {
    return FALSE;
  }
  DEBUG_CODE_END ();
  return TRUE;

}

/**
  Write PCR register. (This is internal function)
  It programs PCR register and size in 1byte/2bytes/4bytes.
  The Offset should not exceed 0xFFFF and must be aligned with size.

  @param[in]  Pid                       Port ID
  @param[in]  Offset                    Register offset of Port ID.
  @param[in]  Size                      Size for read. Must be 1 or 2 or 4.
  @param[in]  AndData                   AND Data. Must be the same size as Size parameter.
  @param[in]  OrData                    OR Data. Must be the same size as Size parameter.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid offset passed.
**/
STATIC
EFI_STATUS
PchPcrWrite (
  IN  PCH_SBI_PID                       Pid,
  IN  UINT16                            Offset,
  IN  UINTN                             Size,
  IN  UINT32                            InData
  )
{
  if ((Offset & (Size - 1)) != 0) {
    DEBUG ((DEBUG_ERROR, "PchPcrWrite error. Invalid Offset: %x Size: %x", Offset, Size));
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }
  DEBUG_CODE_BEGIN ();
  if (!PchPcrWriteMmioCheck (Pid, Offset)) {
    DEBUG ((DEBUG_ERROR, "PchPcrWrite error. Pid: %x Offset: %x should access through SBI interface", Pid, Offset));
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }
  DEBUG_CODE_END ();

  //
  // Write the PCR register with provided data
  // Then read back PCR register to prevent from back to back write.
  //
  switch (Size) {
    case 4:
      MmioWrite32 (PCH_PCR_ADDRESS (Pid, Offset), (UINT32) InData);
      break;
    case 2:
      MmioWrite16 (PCH_PCR_ADDRESS (Pid, Offset), (UINT16) InData);
      break;
    case 1:
      MmioWrite8  (PCH_PCR_ADDRESS (Pid, Offset), (UINT8) InData);
      break;
    default:
      break;
  }
  MmioRead32  (PCH_PCR_ADDRESS (PID_LPC, R_PCH_PCR_LPC_GCFD));

  return EFI_SUCCESS;
}

/**
  Write PCR register.
  It programs PCR register and size in 4bytes.
  The Offset should not exceed 0xFFFF and must be aligned with size.

  @param[in]  Pid                       Port ID
  @param[in]  Offset                    Register offset of Port ID.
  @param[in]  InData                    Input Data. Must be the same size as Size parameter.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid offset passed.
**/
EFI_STATUS
PchPcrWrite32 (
  IN  PCH_SBI_PID                       Pid,
  IN  UINT16                            Offset,
  IN  UINT32                            InData
  )
{
  return PchPcrWrite (Pid, Offset, 4, InData);
}

/**
  Write PCR register.
  It programs PCR register and size in 2bytes.
  The Offset should not exceed 0xFFFF and must be aligned with size.

  @param[in]  Pid                       Port ID
  @param[in]  Offset                    Register offset of Port ID.
  @param[in]  InData                    Input Data. Must be the same size as Size parameter.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid offset passed.
**/
EFI_STATUS
PchPcrWrite16 (
  IN  PCH_SBI_PID                       Pid,
  IN  UINT16                            Offset,
  IN  UINT16                            InData
  )
{
  return PchPcrWrite (Pid, Offset, 2, InData);
}

/**
  Write PCR register.
  It programs PCR register and size in 1bytes.
  The Offset should not exceed 0xFFFF and must be aligned with size.

  @param[in]  Pid                       Port ID
  @param[in]  Offset                    Register offset of Port ID.
  @param[in]  InData                    Input Data. Must be the same size as Size parameter.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid offset passed.
**/
EFI_STATUS
PchPcrWrite8 (
  IN  PCH_SBI_PID                       Pid,
  IN  UINT16                            Offset,
  IN  UINT8                             InData
  )
{
  return PchPcrWrite (Pid, Offset, 1, InData);
}

/**
  Write PCR register.
  It programs PCR register and size in 4bytes.
  The Offset should not exceed 0xFFFF and must be aligned with size.

  @param[in]  Pid                       Port ID
  @param[in]  Offset                    Register offset of Port ID.
  @param[in]  AndData                   AND Data. Must be the same size as Size parameter.
  @param[in]  OrData                    OR Data. Must be the same size as Size parameter.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid offset passed.
**/
EFI_STATUS
PchPcrAndThenOr32 (
  IN  PCH_SBI_PID                       Pid,
  IN  UINT16                            Offset,
  IN  UINT32                            AndData,
  IN  UINT32                            OrData
  )
{
  EFI_STATUS                            Status;
  UINT32                                Data32;

  Data32 = 0x00;
  Status  = PchPcrRead (Pid, Offset, 4, &Data32);
  if (EFI_ERROR (Status)) {
    return Status;
  }
  Data32 &= AndData;
  Data32 |= OrData;
  Status  = PchPcrWrite (Pid, Offset, 4, Data32);
  return Status;
}

/**
  Write PCR register.
  It programs PCR register and size in 2bytes.
  The Offset should not exceed 0xFFFF and must be aligned with size.

  @param[in]  Pid                       Port ID
  @param[in]  Offset                    Register offset of Port ID.
  @param[in]  AndData                   AND Data. Must be the same size as Size parameter.
  @param[in]  OrData                    OR Data. Must be the same size as Size parameter.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid offset passed.
**/
EFI_STATUS
PchPcrAndThenOr16 (
  IN  PCH_SBI_PID                       Pid,
  IN  UINT16                            Offset,
  IN  UINT16                            AndData,
  IN  UINT16                            OrData
  )
{
  EFI_STATUS                            Status;
  UINT16                                Data16;

  Data16 = 0x00;
  Status  = PchPcrRead (Pid, Offset, 2, (UINT32*) &Data16);
  if (EFI_ERROR (Status)) {
    return Status;
  }
  Data16 &= AndData;
  Data16 |= OrData;
  Status  = PchPcrWrite (Pid, Offset, 2, Data16);
  return Status;
}

/**
  Write PCR register.
  It programs PCR register and size in 1bytes.
  The Offset should not exceed 0xFFFF and must be aligned with size.

  @param[in]  Pid                       Port ID
  @param[in]  Offset                    Register offset of Port ID.
  @param[in]  AndData                   AND Data. Must be the same size as Size parameter.
  @param[in]  OrData                    OR Data. Must be the same size as Size parameter.

  @retval EFI_SUCCESS                   Successfully completed.
  @retval EFI_INVALID_PARAMETER         Invalid offset passed.
**/
EFI_STATUS
PchPcrAndThenOr8 (
  IN  PCH_SBI_PID                       Pid,
  IN  UINT16                            Offset,
  IN  UINT8                             AndData,
  IN  UINT8                             OrData
  )
{
  EFI_STATUS                            Status;
  UINT8                                 Data8;

  Status  = PchPcrRead (Pid, Offset, 1, (UINT32*) &Data8);
  if (EFI_ERROR (Status)) {
    return Status;
  }
  Data8 &= AndData;
  Data8 |= OrData;
  Status  = PchPcrWrite (Pid, Offset, 1, Data8);
  return Status;
}

