/** @file
This file contains routines for eSPI
Copyright (c) 2019 Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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]=, Bit [17:16] = 00b, Bit[11:0] = .
// 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]=, Bit [17:16] = 01b, Bit[11:0] = .
// 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]=, Bit [17:16] = 10b, Bit[11:0] = .
// 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]=, 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;
}