/** @file
  This file contains routines for eSPI

  Copyright (c) 2019 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/BaseMemoryLib.h>
#include <Library/PchEspiLib.h>
#include <Library/PchPcrLib.h>
#include <Library/PchInfoLib.h>
#include <Library/TimerLib.h>
#include <PchLimits.h>
#include <Register/PchRegsPcr.h>
#include <Register/PchRegsLpc.h>

#define CHANNEL_RESET_TIMEOUT     100   ///< Channel reset timeout in us after which to report error
#define SLAVE_CHANNELS_MAX        7     ///< Max number of channels

//
// eSPI Slave registers
//
#define R_ESPI_SLAVE_GENCAP               0x08      ///< General Capabilities and Configurations
#define B_ESPI_SLAVE_GENCAP_SUPPCHAN      0xFF      ///< Channels supported bit mask
#define R_ESPI_SLAVE_CHACAP_BASE          0x10      ///< Base address from which channel Cap and Conf registers start on slave
#define S_ESPI_SLAVE_CHACAP_OFFSET        0x10      ///< Offset for each channel from base
#define B_ESPI_SLAVE_CHACAP_CHEN          BIT0      ///< Slave Channel enable bit
#define B_ESPI_SLAVE_CHACAP_CHRDY         BIT1      ///< Slave Channel ready bit

/**
  Checks if second slave capability is enabled

  @retval TRUE      There's second slave
  @retval FALSE     There's no second slave
**/
BOOLEAN
IsEspiSecondSlaveSupported (
  VOID
  )
{
  return (IsPchH () && ((PchPcrRead32 (PID_ESPISPI, R_ESPI_PCR_SOFTSTRAPS) & R_ESPI_PCR_SOFTSTRAPS_CS1_EN) != 0));
}

/**
  Checks in slave General Capabilities register if it supports channel with requested number

  @param[in]  SlaveId         Id of slave to check
  @param[in]  ChannelNumber   Number of channel of which to check

  @retval TRUE      Channel with requested number is supported by slave device
  @retval FALSE     Channel with requested number is not supported by slave device
**/
BOOLEAN
IsEspiSlaveChannelSupported (
  UINT8   SlaveId,
  UINT8   ChannelNumber
  )
{
  EFI_STATUS  Status;
  UINT32      Data32;
  UINT8       SupportedChannels;

  Status = PchEspiSlaveGetConfig (SlaveId, R_ESPI_SLAVE_GENCAP, &Data32);
  if (EFI_ERROR (Status)) {
    return FALSE;
  }
  SupportedChannels = (UINT8) (Data32 & B_ESPI_SLAVE_GENCAP_SUPPCHAN);

  DEBUG ((DEBUG_INFO, "Slave %d supported channels 0x%4X\n", SlaveId, SupportedChannels));

  if (ChannelNumber > SLAVE_CHANNELS_MAX || !(SupportedChannels & (BIT0 << ChannelNumber))) {
    // Incorrect channel number was specified. Either exceeded max or Slave doesn't support that channel.
    return FALSE;
  }

  return TRUE;
}

/**
  Is eSPI enabled in strap.

  @retval TRUE          Espi is enabled in strap
  @retval FALSE         Espi is disabled in strap
**/
BOOLEAN
IsEspiEnabled (
  VOID
  )
{
  return (PchPcrRead32 (PID_ESPISPI, R_ESPI_PCR_CFG_VAL) & B_ESPI_PCR_CFG_VAL_ESPI_EN) != 0;
}

/**
  eSPI helper function to clear slave configuration register status

  @retval EFI_SUCCESS Write to private config space succeed
  @retval others      Read / Write failed
**/
STATIC
VOID
EspiClearScrs (
  VOID
  )
{
  PchPcrAndThenOr32 (
    PID_ESPISPI,
    R_ESPI_PCR_SLV_CFG_REG_CTL,
    (UINT32) ~0,
     B_ESPI_PCR_SLV_CFG_REG_CTL_SCRS
     );
}

/**
  eSPI helper function to poll slave configuration register enable for 0
  and to check for slave configuration register status

  @retval EFI_SUCCESS       Enable bit is zero and no error in status bits
  @retval EFI_DEVICE_ERROR  Error in SCRS
  @retval others            Read / Write to private config space failed
**/
STATIC
EFI_STATUS
EspiPollScreAndCheckScrs (
  VOID
  )
{
  UINT32     ScrStat;

  do {
    ScrStat = PchPcrRead32 (PID_ESPISPI, R_ESPI_PCR_SLV_CFG_REG_CTL);
  } while ((ScrStat & B_ESPI_PCR_SLV_CFG_REG_CTL_SCRE) != 0);

  ScrStat = (ScrStat & B_ESPI_PCR_SLV_CFG_REG_CTL_SCRS) >> N_ESPI_PCR_SLV_CFG_REG_CTL_SCRS;
  if (ScrStat != V_ESPI_PCR_SLV_CFG_REG_CTL_SCRS_NOERR) {
    DEBUG ((DEBUG_ERROR, "eSPI slave config register status (error) is %x \n", ScrStat));
    return EFI_DEVICE_ERROR;
  }
  return EFI_SUCCESS;
}

typedef enum {
  EspiSlaveOperationConfigRead,
  EspiSlaveOperationConfigWrite,
  EspiSlaveOperationStatusRead,
  EspiSlaveOperationInBandReset
} ESPI_SLAVE_OPERATION;

/**
  Helper library to do all the operations regards to eSPI slave

  @param[in]      SlaveId         eSPI Slave ID
  @param[in]      SlaveAddress    Slave address to be put in R_ESPI_PCR_SLV_CFG_REG_CTL[11:0]
  @param[in]      SlaveOperation  Based on ESPI_SLAVE_OPERATION
  @param[in,out]  Data

  @retval EFI_SUCCESS           Operation succeed
  @retval EFI_INVALID_PARAMETER Slave ID is not supported or SlaveId 1 is used in PCH_LP
  @retval EFI_INVALID_PARAMETER Slave configuration register address exceed maximum allowed
  @retval EFI_INVALID_PARAMETER Slave configuration register address is not DWord aligned
  @retval EFI_ACCESS_DENIED     eSPI Slave write to address range 0 to 0x7FF has been locked
  @retval EFI_DEVICE_ERROR      Error in SCRS during polling stage of operation
**/
STATIC
EFI_STATUS
EspiSlaveOperationHelper (
  IN     UINT32               SlaveId,
  IN     UINT32               SlaveAddress,
  IN     ESPI_SLAVE_OPERATION SlaveOperation,
  IN OUT UINT32               *Data
  )
{
  EFI_STATUS  Status;
  UINT32      Data32;

  //
  // Check the SlaveId is 0 or 1
  //
  if (SlaveId >= PCH_MAX_ESPI_SLAVES) {
    DEBUG ((DEBUG_ERROR, "eSPI Slave ID of %d or more is not accepted \n", PCH_MAX_ESPI_SLAVES));
    return EFI_INVALID_PARAMETER;
  }
  //
  // Check if SlaveId 1 is used, it is a PCH_H
  //
  if ((SlaveId == 1) && (IsPchLp ())) {
    DEBUG ((DEBUG_ERROR, "eSPI Slave ID of 1 is only available on PCH_H \n"));
    return EFI_INVALID_PARAMETER;
  }
  //
  // Check the address is not more then 0xFFF
  //
  if (SlaveAddress > B_ESPI_PCR_SLV_CFG_REG_CTL_SCRA) {
    DEBUG ((DEBUG_ERROR, "eSPI Slave address must be less than 0x%x \n", (B_ESPI_PCR_SLV_CFG_REG_CTL_SCRA + 1)));
    return EFI_INVALID_PARAMETER;
  }
  //
  // Check the address is DWord aligned
  //
  if ((SlaveAddress & 0x3) != 0) {
    DEBUG ((DEBUG_ERROR, "eSPI Slave address must be DWord aligned \n"));
    return EFI_INVALID_PARAMETER;
  }

  //
  // Check if write is allowed
  //
  if ((SlaveOperation == EspiSlaveOperationConfigWrite) &&
      (SlaveAddress <= 0x7FF)) {

    //
    // If the SLCRR is not set in corresponding slave, we will check the lock bit
    //
    Data32 = PchPcrRead32 (PID_ESPISPI, (UINT16) (R_ESPI_PCR_LNKERR_SLV0 + (SlaveId * S_ESPI_PCR_LNKERR_SLV0)));
    if ((Data32 & B_ESPI_PCR_LNKERR_SLV0_SLCRR) == 0) {

      Data32 = PchPcrRead32 (PID_ESPISPI, (UINT16) R_ESPI_PCR_SLV_CFG_REG_CTL);
      if ((Data32 & B_ESPI_PCR_SLV_CFG_REG_CTL_SBLCL) != 0) {
        DEBUG ((DEBUG_ERROR, "eSPI Slave write to address range 0 to 0x7FF has been locked \n"));
        return EFI_ACCESS_DENIED;
      }
    }
  }

  //
  // Input check done, now go through all the processes
  //
   EspiClearScrs ();

  if (SlaveOperation == EspiSlaveOperationConfigWrite) {
    PchPcrWrite32 (
      PID_ESPISPI,
      (UINT16) R_ESPI_PCR_SLV_CFG_REG_DATA,
      *Data
      );
  }

  PchPcrAndThenOr32 (
    PID_ESPISPI,
    (UINT16) R_ESPI_PCR_SLV_CFG_REG_CTL,
    (UINT32) ~(B_ESPI_PCR_SLV_CFG_REG_CTL_SID | B_ESPI_PCR_SLV_CFG_REG_CTL_SCRT | B_ESPI_PCR_SLV_CFG_REG_CTL_SCRA),
    (B_ESPI_PCR_SLV_CFG_REG_CTL_SCRE |
     (SlaveId << N_ESPI_PCR_SLV_CFG_REG_CTL_SID) |
     (((UINT32) SlaveOperation) << N_ESPI_PCR_SLV_CFG_REG_CTL_SCRT) |
     SlaveAddress
     )
    );

  Status = EspiPollScreAndCheckScrs ();
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if ((SlaveOperation == EspiSlaveOperationConfigRead) || (SlaveOperation == EspiSlaveOperationStatusRead)) {
    Data32 = PchPcrRead32 (
               PID_ESPISPI,
               (UINT16) R_ESPI_PCR_SLV_CFG_REG_DATA
               );
    if (SlaveOperation == EspiSlaveOperationStatusRead) {
      *Data = Data32 & 0xFFFF;
    } else {
      *Data = Data32;
    }
  }

  return EFI_SUCCESS;
}

/**
  Get configuration from eSPI slave

  @param[in]  SlaveId       eSPI slave ID
  @param[in]  SlaveAddress  Slave Configuration Register Address
  @param[out] OutData       Configuration data read

  @retval EFI_SUCCESS           Operation succeed
  @retval EFI_INVALID_PARAMETER Slave ID is not supported
  @retval EFI_INVALID_PARAMETER Slave ID is not supported or SlaveId 1 is used in PCH_LP
  @retval EFI_INVALID_PARAMETER Slave configuration register address exceed maximum allowed
  @retval EFI_INVALID_PARAMETER Slave configuration register address is not DWord aligned
  @retval EFI_DEVICE_ERROR      Error in SCRS during polling stage of operation
**/
EFI_STATUS
PchEspiSlaveGetConfig (
  IN  UINT32 SlaveId,
  IN  UINT32 SlaveAddress,
  OUT UINT32 *OutData
  )
{
  //
  // 1. Clear status from previous transaction by writing 111b to status in SCRS, PCR[eSPI] + 4000h [30:28]
  // 2. Program SLV_CFG_REG_CTL with the right value (Bit[31]=01, Bit [20:19]=<SlvID>, Bit [17:16] = 00b, Bit[11:0] = <addr_xxx>.
  // 3. Poll the SCRE (PCR[eSPI] +4000h [31]) to be set back to 0
  // 4. Check the transaction status in SCRS (bits [30:28])
  // 5. Read SLV_CFG_REG_DATA.
  //
  return EspiSlaveOperationHelper (SlaveId, SlaveAddress, EspiSlaveOperationConfigRead, OutData);
}

/**
  Set eSPI slave configuration

  Note: A Set_Configuration must always be followed by a Get_Configuration in order to ensure
  that the internal state of the eSPI-MC is consistent with the Slave's register settings.

  @param[in]  SlaveId       eSPI slave ID
  @param[in]  SlaveAddress  Slave Configuration Register Address
  @param[in]  InData        Configuration data to write

  @retval EFI_SUCCESS           Operation succeed
  @retval EFI_INVALID_PARAMETER Slave ID is not supported or SlaveId 1 is used in PCH_LP
  @retval EFI_INVALID_PARAMETER Slave configuration register address exceed maximum allowed
  @retval EFI_INVALID_PARAMETER Slave configuration register address is not DWord aligned
  @retval EFI_ACCESS_DENIED     eSPI Slave write to address range 0 to 0x7FF has been locked
  @retval EFI_DEVICE_ERROR      Error in SCRS during polling stage of operation
**/
EFI_STATUS
PchEspiSlaveSetConfig (
  IN  UINT32 SlaveId,
  IN  UINT32 SlaveAddress,
  IN  UINT32 InData
  )
{
  EFI_STATUS  Status;
  UINT32      Data32;

  //
  // 1. Clear status from previous transaction by writing 111b to status in SCRS, PCR[eSPI] + 4000h [30:28]
  // 2. Program SLV_CFG_REG_DATA with the write value.
  // 3. Program SLV_CFG_REG_CTL with the right value (Bit[31]=01, Bit [20:19]=<SlvID>, Bit [17:16] = 01b, Bit[11:0] = <addr_xxx>.
  // 4. Poll the SCRE (PCR[eSPI] +4000h [31]) to be set back to 0
  // 5. Check the transaction status in SCRS (bits [30:28])
  //
  Status = EspiSlaveOperationHelper (SlaveId, SlaveAddress, EspiSlaveOperationConfigWrite, &InData);
  if (EFI_ERROR (Status)) {
    return Status;
  }
  Status = PchEspiSlaveGetConfig (SlaveId, SlaveAddress, &Data32);
  return Status;
}

/**
  Get status from eSPI slave

  @param[in]  SlaveId       eSPI slave ID
  @param[out] OutData       Configuration data read

  @retval EFI_SUCCESS           Operation succeed
  @retval EFI_INVALID_PARAMETER Slave ID is not supported or SlaveId 1 is used in PCH_LP
  @retval EFI_DEVICE_ERROR      Error in SCRS during polling stage of operation
**/
EFI_STATUS
PchEspiSlaveGetStatus (
  IN  UINT32 SlaveId,
  OUT UINT16 *OutData
  )
{
  EFI_STATUS  Status;
  UINT32      TempOutData;

  TempOutData = 0;

  //
  // 1. Clear status from previous transaction by writing 111b to status in SCRS, PCR[eSPI] + 4000h [30:28]
  // 2. Program SLV_CFG_REG_CTL with the right value (Bit[31]=01, Bit [20:19]=<SlvID>, Bit [17:16] = 10b, Bit[11:0] = <addr_xxx>.
  // 3. Poll the SCRE (PCR[eSPI] +4000h [31]) to be set back to 0
  // 4. Check the transaction status in SCRS (bits [30:28])
  // 5. Read SLV_CFG_REG_DATA [15:0].
  //
  Status = EspiSlaveOperationHelper (SlaveId, 0, EspiSlaveOperationStatusRead, &TempOutData);
  *OutData = (UINT16) TempOutData;

  return Status;
}

/**
  eSPI slave in-band reset

  @param[in]  SlaveId           eSPI slave ID

  @retval EFI_SUCCESS           Operation succeed
  @retval EFI_INVALID_PARAMETER Slave ID is not supported or SlaveId 1 is used in PCH_LP
  @retval EFI_DEVICE_ERROR      Error in SCRS during polling stage of operation
**/
EFI_STATUS
PchEspiSlaveInBandReset (
  IN  UINT32 SlaveId
  )
{
  //
  // 1. Clear status from previous transaction by writing 111b to status in SCRS, PCR[eSPI] + 4000h [30:28]
  // 2. Program SLV_CFG_REG_CTL with the right value (Bit[31]=01, Bit [20:19]=<SlvID>, Bit [17:16] = 11b).
  // 3. Poll the SCRE (PCR[eSPI] +4000h [31]) to be set back to 0
  // 4. Check the transaction status in SCRS (bits [30:28])
  //
  return EspiSlaveOperationHelper (SlaveId, 0, EspiSlaveOperationInBandReset, NULL);
}

/**
  eSPI Slave channel reset helper function

  @param[in]  SlaveId           eSPI slave ID
  @param[in]  ChannelNumber     Number of channel to reset

  @retval     EFI_SUCCESS       Operation succeeded
  @retval     EFI_UNSUPPORTED   Slave doesn't support that channel or invalid number specified
  @retval     EFI_TIMEOUT       Operation has timeouted
**/
EFI_STATUS
PchEspiSlaveChannelReset (
  IN  UINT8   SlaveId,
  IN  UINT8   ChannelNumber
  )
{
  UINT8       Timeout;
  UINT32      Data32;
  UINT32      SlaveChannelAddress;
  BOOLEAN     SlaveBmeSet;
  EFI_STATUS  Status;

  DEBUG ((DEBUG_INFO, "eSPI slave %d channel %d reset\n", SlaveId, ChannelNumber));

  Timeout = CHANNEL_RESET_TIMEOUT;
  SlaveBmeSet = FALSE;

  if (!IsEspiSlaveChannelSupported (SlaveId, ChannelNumber)) {
    // Incorrect channel number was specified. Either exceeded max or Slave doesn't support that channel.
    DEBUG ((DEBUG_ERROR, "Channel %d is not valid channel number for slave %d!\n", ChannelNumber, SlaveId));
    return EFI_UNSUPPORTED;
  }

  // Calculating slave channel address
  SlaveChannelAddress = R_ESPI_SLAVE_CHACAP_BASE + (S_ESPI_SLAVE_CHACAP_OFFSET * ChannelNumber);

  // If we're resetting Peripheral Channel then we need to disable Bus Mastering first and reenable after reset
  if (ChannelNumber == 0) {
    Status = PchEspiSlaveGetConfig (SlaveId, SlaveChannelAddress, &Data32);
    if (EFI_ERROR (Status)) {
      return Status;
    }
    if ((Data32 & B_ESPI_SLAVE_BME) != 0) {
      Data32 &= ~(B_ESPI_SLAVE_BME);
      Status = PchEspiSlaveSetConfig (SlaveId, SlaveChannelAddress, Data32);
      if (EFI_ERROR (Status)) {
        return Status;
      }
      SlaveBmeSet = TRUE;
    }
  }

  // Disable channel
  Status = PchEspiSlaveGetConfig (SlaveId, SlaveChannelAddress, &Data32);
  if (EFI_ERROR (Status)) {
    return Status;
  }
  Data32 &= ~(B_ESPI_SLAVE_CHACAP_CHEN);
  Status = PchEspiSlaveSetConfig (SlaveId, SlaveChannelAddress, Data32);
  if (EFI_ERROR (Status)) {
    return Status;
  }
  // Enable channel
  Status = PchEspiSlaveGetConfig (SlaveId, SlaveChannelAddress, &Data32);
  if (EFI_ERROR (Status)) {
    return Status;
  }
  Data32 |= B_ESPI_SLAVE_CHACAP_CHEN;
  Status = PchEspiSlaveSetConfig (SlaveId, SlaveChannelAddress, Data32);
  if (EFI_ERROR (Status)) {
    return Status;
  }
  DEBUG ((DEBUG_INFO, "Waiting for Channel Ready bit\n"));
  // Wait until channel is ready by polling Channel Ready bit
  while (((Data32 & B_ESPI_SLAVE_CHACAP_CHRDY) == 0) && (Timeout > 0)) {
    Status = PchEspiSlaveGetConfig (SlaveId, SlaveChannelAddress, &Data32);
    if (EFI_ERROR (Status)) {
      return Status;
    }
    MicroSecondDelay (1);
    --Timeout;
  }

  if (Timeout == 0) {
    // The waiting for channel to be ready has timed out
    DEBUG ((DEBUG_ERROR, "The operation of channel %d reset for slave %d has timed out!\n", ChannelNumber, SlaveId));
    return EFI_TIMEOUT;
  }

  if (ChannelNumber == 0 && SlaveBmeSet) {
    Status = PchEspiSlaveGetConfig (SlaveId, SlaveChannelAddress, &Data32);
    if (EFI_ERROR (Status)) {
      return Status;
    }
    Data32 |= B_ESPI_SLAVE_BME;
    Status = PchEspiSlaveSetConfig (SlaveId, SlaveChannelAddress, Data32);
    if (EFI_ERROR (Status)) {
      return Status;
    }
  }

  return EFI_SUCCESS;
}
