/** @file
PCH Smbus Executive Code (common PEI/DXE/SMM code)
Copyright (c) 2017, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include
#include
#include
#include
#include
#include
#include
#include
#include
/**
Get SMBUS IO Base address
@retval UINT32 The SMBUS IO Base Address
**/
UINT32
SmbusGetIoBase (
VOID
)
{
UINT32 SmbusIoBase;
SmbusIoBase = MmioRead32 (
MmPciBase (
DEFAULT_PCI_BUS_NUMBER_PCH,
PCI_DEVICE_NUMBER_PCH_SMBUS,
PCI_FUNCTION_NUMBER_PCH_SMBUS)
+ R_PCH_SMBUS_BASE) & B_PCH_SMBUS_BASE_BAR;
ASSERT (SmbusIoBase != B_PCH_SMBUS_BASE_BAR && SmbusIoBase != 0);
return SmbusIoBase;
}
/**
This function provides a standard way to read PCH Smbus IO registers.
@param[in] Offset Register offset from Smbus base IO address.
@retval UINT8 Returns data read from IO.
**/
UINT8
EFIAPI
SmbusIoRead (
IN UINT8 Offset
)
{
return IoRead8 (SmbusGetIoBase () + Offset);
}
/**
This function provides a standard way to write PCH Smbus IO registers.
@param[in] Offset Register offset from Smbus base IO address.
@param[in] Data Data to write to register.
**/
VOID
EFIAPI
SmbusIoWrite (
IN UINT8 Offset,
IN UINT8 Data
)
{
///
/// Write New Value
///
IoWrite8 (SmbusGetIoBase () + Offset, Data);
return;
}
/**
This function provides a standard way to check if an SMBus transaction has
completed.
@param[in] StsReg Not used for input. On return, contains the
value of the SMBus status register.
@retval TRUE Transaction is complete
@retval FALSE Otherwise.
**/
BOOLEAN
EFIAPI
IoDone (
IN UINT8 *StsReg
)
{
///
/// Wait for IO to complete
///
UINTN StallIndex;
UINTN StallTries;
StallTries = STALL_TIME / STALL_PERIOD;
for (StallIndex = 0; StallIndex < StallTries; StallIndex++) {
*StsReg = SmbusIoRead (R_PCH_SMBUS_HSTS);
if (*StsReg & (B_PCH_SMBUS_INTR | B_PCH_SMBUS_BYTE_DONE_STS | B_PCH_SMBUS_DERR | B_PCH_SMBUS_BERR)) {
return TRUE;
} else {
MicroSecondDelay (STALL_PERIOD);
}
}
return FALSE;
}
/**
Check if it's ok to use the bus.
@retval EFI_SUCCESS SmBus is acquired and it's safe to send commands.
@retval EFI_TIMEOUT SmBus is busy, it's not safe to send commands.
**/
EFI_STATUS
AcquireBus (
VOID
)
{
UINT8 StsReg;
StsReg = 0;
StsReg = SmbusIoRead (R_PCH_SMBUS_HSTS);
if (StsReg & B_PCH_SMBUS_IUS) {
return EFI_TIMEOUT;
} else if (StsReg & B_PCH_SMBUS_HBSY) {
///
/// Clear Status Register and exit
///
SmbusIoWrite (R_PCH_SMBUS_HSTS, B_PCH_SMBUS_HSTS_ALL);
return EFI_TIMEOUT;
} else {
///
/// Clear out any odd status information (Will Not Clear In Use)
///
SmbusIoWrite (R_PCH_SMBUS_HSTS, StsReg);
return EFI_SUCCESS;
}
}
/**
This function provides a standard way to execute Smbus protocols
as defined in the SMBus Specification. The data can either be of
the Length byte, word, or a block of data. The resulting transaction will be
either the SMBus Slave Device accepts this transaction or this function
returns with an error
@param[in] SlaveAddress Smbus Slave device the command is directed at
@param[in] Command Slave Device dependent
@param[in] Operation Which SMBus protocol will be used
@param[in] PecCheck Defines if Packet Error Code Checking is to be used
@param[in, out] Length How many bytes to read. Must be 0 <= Length <= 32 depending on Operation
It will contain the actual number of bytes read/written.
@param[in, out] Buffer Contain the data read/written.
@retval EFI_SUCCESS The operation completed successfully.
@exception EFI_UNSUPPORTED The operation is unsupported.
@retval EFI_INVALID_PARAMETER Length or Buffer is NULL for any operation besides
quick read or quick write.
@retval EFI_TIMEOUT The transaction did not complete within an internally
specified timeout period, or the controller is not
available for use.
@retval EFI_DEVICE_ERROR There was an Smbus error (NACK) during the operation.
This could indicate the slave device is not present
or is in a hung condition.
**/
EFI_STATUS
SmbusExec (
IN EFI_SMBUS_DEVICE_ADDRESS SlaveAddress,
IN EFI_SMBUS_DEVICE_COMMAND Command,
IN EFI_SMBUS_OPERATION Operation,
IN BOOLEAN PecCheck,
IN OUT UINTN *Length,
IN OUT VOID *Buffer
)
{
EFI_STATUS Status;
UINT8 AuxcReg;
UINT8 AuxStsReg;
UINT8 SmbusOperation;
UINT8 StsReg;
UINT8 SlvAddrReg;
UINT8 HostCmdReg;
UINT8 BlockCount;
BOOLEAN BufferTooSmall;
UINTN Index;
UINTN BusIndex;
UINT8 *CallBuffer;
UINT8 SmbusHctl;
UINT32 Timeout;
CallBuffer = Buffer;
BlockCount = 0;
///
/// For any operations besides quick read & write, the pointers to
/// Length and Buffer must not be NULL.
///
if ((Operation != EfiSmbusQuickRead) && (Operation != EfiSmbusQuickWrite)) {
if ((Length == NULL) || (Buffer == NULL)) {
return EFI_INVALID_PARAMETER;
}
}
///
/// See if its ok to use the bus based upon INUSE_STS bit.
///
Status = AcquireBus ();
if (EFI_ERROR (Status)) {
return Status;
}
///
/// This is the main operation loop. If the operation results in a Smbus
/// collision with another master on the bus, it attempts the requested
/// transaction again at least BUS_TRIES attempts.
///
for (BusIndex = 0; BusIndex < BUS_TRIES; BusIndex++) {
///
/// Operation Specifics (pre-execution)
///
Status = EFI_SUCCESS;
SmbusOperation = V_PCH_SMBUS_SMB_CMD_QUICK;
SlvAddrReg = (UINT8) ((SlaveAddress.SmbusDeviceAddress << 1) | 1);
HostCmdReg = (UINT8) Command;
AuxcReg = 0;
switch (Operation) {
case EfiSmbusQuickWrite:
SlvAddrReg--;
///
/// The "break;" command is not present here to allow code execution
/// do drop into the next case, which contains common code to this case.
///
case EfiSmbusQuickRead:
if (PecCheck == TRUE) {
Status = EFI_UNSUPPORTED;
}
break;
case EfiSmbusSendByte:
HostCmdReg = CallBuffer[0];
SlvAddrReg--;
///
/// The "break;" command is not present here to allow code execution
/// do drop into the next case, which contains common code to this case.
///
case EfiSmbusReceiveByte:
SmbusOperation = V_PCH_SMBUS_SMB_CMD_BYTE;
if (*Length < 1) {
Status = EFI_BUFFER_TOO_SMALL;
}
*Length = 1;
break;
case EfiSmbusWriteByte:
SmbusIoWrite (R_PCH_SMBUS_HD0, CallBuffer[0]);
SlvAddrReg--;
*Length = 1;
///
/// The "break;" command is not present here to allow code execution
/// do drop into the next case, which contains common code to this case.
///
case EfiSmbusReadByte:
if (*Length < 1) {
Status = EFI_BUFFER_TOO_SMALL;
} else if (*Length == 1) {
SmbusOperation = V_PCH_SMBUS_SMB_CMD_BYTE_DATA;
} else if (*Length <= 256) {
if (PecCheck == TRUE) {
///
/// The I2C Read command with either PEC_EN or AAC bit set
/// produces undefined results.
///
Status = EFI_UNSUPPORTED;
}
SmbusOperation = V_PCH_SMBUS_SMB_CMD_IIC_READ;
} else {
Status = EFI_INVALID_PARAMETER;
}
break;
case EfiSmbusReadWord:
SmbusOperation = V_PCH_SMBUS_SMB_CMD_WORD_DATA;
if (*Length < 2) {
Status = EFI_BUFFER_TOO_SMALL;
}
*Length = 2;
break;
case EfiSmbusWriteWord:
SmbusOperation = V_PCH_SMBUS_SMB_CMD_WORD_DATA;
SlvAddrReg--;
SmbusIoWrite (R_PCH_SMBUS_HD1, CallBuffer[1]);
SmbusIoWrite (R_PCH_SMBUS_HD0, CallBuffer[0]);
if (*Length < 2) {
Status = EFI_BUFFER_TOO_SMALL;
}
*Length = 2;
break;
case EfiSmbusWriteBlock:
SmbusIoWrite (R_PCH_SMBUS_HD0, *(UINT8 *) Length);
SlvAddrReg--;
BlockCount = (UINT8) (*Length);
///
/// The "break;" command is not present here to allow code execution
/// do drop into the next case, which contains common code to this case.
///
case EfiSmbusReadBlock:
SmbusOperation = V_PCH_SMBUS_SMB_CMD_BLOCK;
if ((*Length < 1) || (*Length > 32)) {
Status = EFI_INVALID_PARAMETER;
break;
}
AuxcReg |= B_PCH_SMBUS_E32B;
break;
case EfiSmbusProcessCall:
SmbusOperation = V_PCH_SMBUS_SMB_CMD_PROCESS_CALL;
SmbusIoWrite (R_PCH_SMBUS_HD1, CallBuffer[1]);
SmbusIoWrite (R_PCH_SMBUS_HD0, CallBuffer[0]);
if (*Length < 2) {
Status = EFI_BUFFER_TOO_SMALL;
}
*Length = 2;
break;
case EfiSmbusBWBRProcessCall:
///
/// The write byte count cannot be zero or more than
/// 32 bytes.
///
if ((*Length < 1) || (*Length > 32)) {
Status = EFI_INVALID_PARAMETER;
break;
}
SmbusIoWrite (R_PCH_SMBUS_HD0, *(UINT8 *) Length);
BlockCount = (UINT8) (*Length);
SmbusOperation = V_PCH_SMBUS_SMB_CMD_BLOCK_PROCESS;
AuxcReg |= B_PCH_SMBUS_E32B;
break;
default:
Status = EFI_INVALID_PARAMETER;
break;
}
if (EFI_ERROR (Status)) {
break;
}
if (PecCheck == TRUE) {
AuxcReg |= B_PCH_SMBUS_AAC;
}
///
/// Set Auxiliary Control register
///
SmbusIoWrite (R_PCH_SMBUS_AUXC, AuxcReg);
///
/// Reset the pointer of the internal buffer
///
SmbusIoRead (R_PCH_SMBUS_HCTL);
///
/// Now that the 32 byte buffer is turned on, we can write th block data
/// into it
///
if ((Operation == EfiSmbusWriteBlock) || (Operation == EfiSmbusBWBRProcessCall)) {
for (Index = 0; Index < BlockCount; Index++) {
///
/// Write next byte
///
SmbusIoWrite (R_PCH_SMBUS_HBD, CallBuffer[Index]);
}
}
///
/// Set SMBus slave address for the device to send/receive from
///
SmbusIoWrite (R_PCH_SMBUS_TSA, SlvAddrReg);
///
/// For I2C read, send DATA1 register for the offset (address)
/// within the serial memory chips
///
if ((Operation == EfiSmbusReadByte) && (*Length > 1)) {
SmbusIoWrite (R_PCH_SMBUS_HD1, HostCmdReg);
} else {
///
/// Set Command register
///
SmbusIoWrite (R_PCH_SMBUS_HCMD, HostCmdReg);
}
///
/// Set Control Register (Initiate Operation, Interrupt disabled)
///
SmbusIoWrite (R_PCH_SMBUS_HCTL, (UINT8) (SmbusOperation + B_PCH_SMBUS_START));
///
/// Wait for IO to complete
///
if (!IoDone (&StsReg)) {
Status = EFI_TIMEOUT;
break;
} else if (StsReg & B_PCH_SMBUS_DERR) {
AuxStsReg = SmbusIoRead (R_PCH_SMBUS_AUXS);
if (AuxStsReg & B_PCH_SMBUS_CRCE) {
Status = EFI_CRC_ERROR;
} else {
Status = EFI_DEVICE_ERROR;
}
break;
} else if (StsReg & B_PCH_SMBUS_BERR) {
///
/// Clear the Bus Error for another try
///
Status = EFI_DEVICE_ERROR;
SmbusIoWrite (R_PCH_SMBUS_HSTS, B_PCH_SMBUS_BERR);
///
/// Clear Status Registers
///
SmbusIoWrite (R_PCH_SMBUS_HSTS, B_PCH_SMBUS_HSTS_ALL);
SmbusIoWrite (R_PCH_SMBUS_AUXS, B_PCH_SMBUS_CRCE);
///
/// If bus collision happens, stall some time, then try again
/// Here we choose 10 milliseconds to avoid MTCP transfer.
///
MicroSecondDelay (STALL_PERIOD);
continue;
}
///
/// successfull completion
/// Operation Specifics (post-execution)
///
switch (Operation) {
case EfiSmbusReadWord:
///
/// The "break;" command is not present here to allow code execution
/// do drop into the next case, which contains common code to this case.
///
case EfiSmbusProcessCall:
CallBuffer[1] = SmbusIoRead (R_PCH_SMBUS_HD1);
CallBuffer[0] = SmbusIoRead (R_PCH_SMBUS_HD0);
break;
case EfiSmbusReadByte:
if (*Length > 1) {
for (Index = 0; Index < *Length; Index++) {
///
/// Read the byte
///
CallBuffer[Index] = SmbusIoRead (R_PCH_SMBUS_HBD);
///
/// After receiving byte n-1 (1-base) of the message, the
/// software will then set the LAST BYTE bit. The software
/// will then clear the BYTE_DONE_STS bit.
///
if (Index == ((*Length - 1) - 1)) {
SmbusHctl = SmbusIoRead (R_PCH_SMBUS_HCTL) | (UINT8) B_PCH_SMBUS_LAST_BYTE;
SmbusIoWrite (R_PCH_SMBUS_HCTL, SmbusHctl);
} else if (Index == (*Length - 1)) {
///
/// Clear the LAST BYTE bit after receiving byte n (1-base) of the message
///
SmbusHctl = SmbusIoRead (R_PCH_SMBUS_HCTL) & (UINT8) ~B_PCH_SMBUS_LAST_BYTE;
SmbusIoWrite (R_PCH_SMBUS_HCTL, SmbusHctl);
}
///
/// Clear the BYTE_DONE_STS bit
///
SmbusIoWrite (R_PCH_SMBUS_HSTS, B_PCH_SMBUS_BYTE_DONE_STS);
///
/// Check BYTE_DONE_STS bit to know if it has completed transmission
/// of a byte. No need to check it for the last byte.
///
if (Index < (*Length - 1)) {
///
/// If somehow board operates at 10Khz, it will take 0.9 ms (9/10Khz) for another byte.
/// Add 10 us delay for a loop of 100 that the total timeout is 1 ms to take care of
/// the slowest case.
///
for (Timeout = 0; Timeout < 100; Timeout++) {
if ((SmbusIoRead (R_PCH_SMBUS_HSTS) & (UINT8) B_PCH_SMBUS_BYTE_DONE_STS) != 0) {
break;
}
///
/// Delay 10 us
///
MicroSecondDelay (STALL_PERIOD);
}
if (Timeout >= 100) {
Status = EFI_TIMEOUT;
break;
}
}
}
break;
}
case EfiSmbusReceiveByte:
CallBuffer[0] = SmbusIoRead (R_PCH_SMBUS_HD0);
break;
case EfiSmbusWriteBlock:
SmbusIoWrite (R_PCH_SMBUS_HSTS, B_PCH_SMBUS_BYTE_DONE_STS);
break;
case EfiSmbusReadBlock:
BufferTooSmall = FALSE;
///
/// Find out how many bytes will be in the block
///
BlockCount = SmbusIoRead (R_PCH_SMBUS_HD0);
if (*Length < BlockCount) {
BufferTooSmall = TRUE;
} else {
for (Index = 0; Index < BlockCount; Index++) {
///
/// Read the byte
///
CallBuffer[Index] = SmbusIoRead (R_PCH_SMBUS_HBD);
}
}
*Length = BlockCount;
if (BufferTooSmall) {
Status = EFI_BUFFER_TOO_SMALL;
}
break;
case EfiSmbusBWBRProcessCall:
///
/// Find out how many bytes will be in the block
///
BlockCount = SmbusIoRead (R_PCH_SMBUS_HD0);
///
/// The read byte count cannot be zero.
///
if (BlockCount < 1) {
Status = EFI_BUFFER_TOO_SMALL;
break;
}
///
/// The combined data payload (the write byte count + the read byte count)
/// must not exceed 32 bytes
///
if (((UINT8) (*Length) + BlockCount) > 32) {
Status = EFI_DEVICE_ERROR;
break;
}
for (Index = 0; Index < BlockCount; Index++) {
///
/// Read the byte
///
CallBuffer[Index] = SmbusIoRead (R_PCH_SMBUS_HBD);
}
*Length = BlockCount;
break;
default:
break;
};
if ((StsReg & B_PCH_SMBUS_BERR) && (Status != EFI_BUFFER_TOO_SMALL)) {
///
/// Clear the Bus Error for another try
///
Status = EFI_DEVICE_ERROR;
SmbusIoWrite (R_PCH_SMBUS_HSTS, B_PCH_SMBUS_BERR);
///
/// If bus collision happens, stall some time, then try again
/// Here we choose 10 milliseconds to avoid MTCP transfer.
///
MicroSecondDelay (STALL_PERIOD);
continue;
} else {
break;
}
}
///
/// Clear Status Registers and exit
///
SmbusIoWrite (R_PCH_SMBUS_HSTS, B_PCH_SMBUS_HSTS_ALL);
SmbusIoWrite (R_PCH_SMBUS_AUXS, B_PCH_SMBUS_CRCE);
SmbusIoWrite (R_PCH_SMBUS_AUXC, 0);
return Status;
}
/**
This function initializes the Smbus Registers.
**/
VOID
InitializeSmbusRegisters (
VOID
)
{
UINTN SmbusRegBase;
SmbusRegBase = MmPciBase (
DEFAULT_PCI_BUS_NUMBER_PCH,
PCI_DEVICE_NUMBER_PCH_SMBUS,
PCI_FUNCTION_NUMBER_PCH_SMBUS
);
///
/// Enable the Smbus I/O Enable
///
MmioOr8 (SmbusRegBase + PCI_COMMAND_OFFSET, (UINT8) EFI_PCI_COMMAND_IO_SPACE);
///
/// Enable the Smbus host controller
///
MmioAndThenOr8 (
SmbusRegBase + R_PCH_SMBUS_HOSTC,
(UINT8) (~(B_PCH_SMBUS_HOSTC_SMI_EN | B_PCH_SMBUS_HOSTC_I2C_EN)),
B_PCH_SMBUS_HOSTC_HST_EN
);
SmbusIoWrite (R_PCH_SMBUS_HSTS, B_PCH_SMBUS_HSTS_ALL);
}