/** @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); }