/** @file
Apollo Lake South Cluster Smbus Executive Code (common PEI/DXE/SMM code).
Copyright (c) 2014 - 2018, Intel Corporation. All rights reserved.
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution. The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php.
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#include
#include
#include
#include
#include
#include
#include
#include
#include
/**
Get SMBUS IO Base address.
@param[in] None
@retval UINT32 The SMBUS IO Base Address
**/
UINT32
SmbusGetIoBase (
VOID
)
{
UINT32 SmbusIoBase;
SmbusIoBase = MmioRead32 (
MmPciBase (
DEFAULT_PCI_BUS_NUMBER_SC,
PCI_DEVICE_NUMBER_SMBUS,
PCI_FUNCTION_NUMBER_SMBUS)
+ R_SMBUS_BASE) & B_SMBUS_BASE_BAR;
ASSERT (SmbusIoBase != B_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
)
{
IoWrite8 (SmbusGetIoBase () + Offset, Data);
return;
}
/**
This function provides a standard way to check if a 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
)
{
UINTN StallIndex;
UINTN StallTries;
StallTries = STALL_TIME / STALL_PERIOD;
for (StallIndex = 0; StallIndex < StallTries; StallIndex++) {
*StsReg = SmbusIoRead (R_SMBUS_HSTS);
if (*StsReg & (B_SMBUS_INTR | B_SMBUS_BYTE_DONE_STS | B_SMBUS_DERR | B_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_SMBUS_HSTS);
if (StsReg & B_SMBUS_IUS) {
return EFI_TIMEOUT;
} else if (StsReg & B_SMBUS_HBSY) {
///
/// Clear Status Register and exit
///
SmbusIoWrite (R_SMBUS_HSTS, B_SMBUS_HSTS_ALL);
return EFI_TIMEOUT;
} else {
///
/// Clear out any odd status information (Will Not Clear In Use)
///
SmbusIoWrite (R_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_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_SMBUS_SMB_CMD_BYTE;
if (*Length < 1) {
Status = EFI_BUFFER_TOO_SMALL;
}
*Length = 1;
break;
case EfiSmbusWriteByte:
SmbusIoWrite (R_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_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_SMBUS_SMB_CMD_IIC_READ;
} else {
Status = EFI_INVALID_PARAMETER;
}
break;
case EfiSmbusReadWord:
SmbusOperation = V_SMBUS_SMB_CMD_WORD_DATA;
if (*Length < 2) {
Status = EFI_BUFFER_TOO_SMALL;
}
*Length = 2;
break;
case EfiSmbusWriteWord:
SmbusOperation = V_SMBUS_SMB_CMD_WORD_DATA;
SlvAddrReg--;
SmbusIoWrite (R_SMBUS_HD1, CallBuffer[1]);
SmbusIoWrite (R_SMBUS_HD0, CallBuffer[0]);
if (*Length < 2) {
Status = EFI_BUFFER_TOO_SMALL;
}
*Length = 2;
break;
case EfiSmbusWriteBlock:
SmbusIoWrite (R_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_SMBUS_SMB_CMD_BLOCK;
if ((*Length < 1) || (*Length > 32)) {
Status = EFI_INVALID_PARAMETER;
break;
}
AuxcReg |= B_SMBUS_E32B;
break;
case EfiSmbusProcessCall:
SmbusOperation = V_SMBUS_SMB_CMD_PROCESS_CALL;
SmbusIoWrite (R_SMBUS_HD1, CallBuffer[1]);
SmbusIoWrite (R_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_SMBUS_HD0, *(UINT8 *) Length);
BlockCount = (UINT8) (*Length);
SmbusOperation = V_SMBUS_SMB_CMD_BLOCK_PROCESS;
AuxcReg |= B_SMBUS_E32B;
break;
default:
Status = EFI_INVALID_PARAMETER;
break;
}
if (EFI_ERROR (Status)) {
break;
}
if (PecCheck == TRUE) {
AuxcReg |= B_SMBUS_AAC;
}
///
/// Set Auxiliary Control register
///
SmbusIoWrite (R_SMBUS_AUXC, AuxcReg);
///
/// Reset the pointer of the internal buffer
///
SmbusIoRead (R_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_SMBUS_HBD, CallBuffer[Index]);
}
}
///
/// Set SMBus slave address for the device to send/receive from
///
SmbusIoWrite (R_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_SMBUS_HD1, HostCmdReg);
} else {
///
/// Set Command register
///
SmbusIoWrite (R_SMBUS_HCMD, HostCmdReg);
}
///
/// Set Control Register (Initiate Operation, Interrupt disabled)
///
SmbusIoWrite (R_SMBUS_HCTL, (UINT8) (SmbusOperation + B_SMBUS_START));
///
/// Wait for IO to complete
///
if (!IoDone (&StsReg)) {
Status = EFI_TIMEOUT;
break;
} else if (StsReg & B_SMBUS_DERR) {
AuxStsReg = SmbusIoRead (R_SMBUS_AUXS);
if (AuxStsReg & B_SMBUS_CRCE) {
Status = EFI_CRC_ERROR;
} else {
Status = EFI_DEVICE_ERROR;
}
break;
} else if (StsReg & B_SMBUS_BERR) {
///
/// Clear the Bus Error for another try
///
Status = EFI_DEVICE_ERROR;
SmbusIoWrite (R_SMBUS_HSTS, B_SMBUS_BERR);
///
/// Clear Status Registers
///
SmbusIoWrite (R_SMBUS_HSTS, B_SMBUS_HSTS_ALL);
SmbusIoWrite (R_SMBUS_AUXS, B_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_SMBUS_HD1);
CallBuffer[0] = SmbusIoRead (R_SMBUS_HD0);
break;
case EfiSmbusReadByte:
if (*Length > 1) {
for (Index = 0; Index < *Length; Index++) {
///
/// Read the byte
///
CallBuffer[Index] = SmbusIoRead (R_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_SMBUS_HCTL) | (UINT8) B_SMBUS_LAST_BYTE;
SmbusIoWrite (R_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_SMBUS_HCTL) & (UINT8) ~B_SMBUS_LAST_BYTE;
SmbusIoWrite (R_SMBUS_HCTL, SmbusHctl);
}
///
/// Clear the BYTE_DONE_STS bit
///
SmbusIoWrite (R_SMBUS_HSTS, B_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_SMBUS_HSTS) & (UINT8) B_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_SMBUS_HD0);
break;
case EfiSmbusWriteBlock:
SmbusIoWrite (R_SMBUS_HSTS, B_SMBUS_BYTE_DONE_STS);
break;
case EfiSmbusReadBlock:
BufferTooSmall = FALSE;
///
/// Find out how many bytes will be in the block
///
BlockCount = SmbusIoRead (R_SMBUS_HD0);
if (*Length < BlockCount) {
BufferTooSmall = TRUE;
} else {
for (Index = 0; Index < BlockCount; Index++) {
///
/// Read the byte
///
CallBuffer[Index] = SmbusIoRead (R_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_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_SMBUS_HBD);
}
*Length = BlockCount;
break;
default:
break;
};
if ((StsReg & B_SMBUS_BERR) && (Status != EFI_BUFFER_TOO_SMALL)) {
///
/// Clear the Bus Error for another try
///
Status = EFI_DEVICE_ERROR;
SmbusIoWrite (R_SMBUS_HSTS, B_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_SMBUS_HSTS, B_SMBUS_HSTS_ALL);
SmbusIoWrite (R_SMBUS_AUXS, B_SMBUS_CRCE);
SmbusIoWrite (R_SMBUS_AUXC, 0);
return Status;
}
/**
This function initializes the Smbus Registers.
@param[in] None
@retval[in] None
**/
VOID
InitializeSmbusRegisters (
VOID
)
{
UINTN SmbusRegBase;
SmbusRegBase = MmPciBase (
DEFAULT_PCI_BUS_NUMBER_SC,
PCI_DEVICE_NUMBER_SMBUS,
PCI_FUNCTION_NUMBER_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_SMBUS_HOSTC,
(UINT8) (~(B_SMBUS_HOSTC_SMI_EN | B_SMBUS_HOSTC_I2C_EN)),
B_SMBUS_HOSTC_HST_EN
);
SmbusIoWrite (R_SMBUS_HSTS, B_SMBUS_HSTS_ALL);
}