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