From 35269fe8952eff7b6ecf6050b65a4ede2d1f9d7f Mon Sep 17 00:00:00 2001 From: zwei4 Date: Mon, 12 Feb 2018 16:06:29 +0800 Subject: SMBus Library. Apollo Lake South Cluster SMBus executive code, which is common for PEI, DXE and SMM modules. Contributed-under: TianoCore Contribution Agreement 1.1 Signed-off-by: zwei4 Cc: Mang Guo --- .../PeiDxeSmmScSmbusCommonLib.inf | 35 ++ .../PeiDxeSmmScSmbusCommonLib/ScSmbusExec.c | 653 +++++++++++++++++++++ 2 files changed, 688 insertions(+) create mode 100644 Silicon/BroxtonSoC/BroxtonSiPkg/SouthCluster/Library/Private/PeiDxeSmmScSmbusCommonLib/PeiDxeSmmScSmbusCommonLib.inf create mode 100644 Silicon/BroxtonSoC/BroxtonSiPkg/SouthCluster/Library/Private/PeiDxeSmmScSmbusCommonLib/ScSmbusExec.c diff --git a/Silicon/BroxtonSoC/BroxtonSiPkg/SouthCluster/Library/Private/PeiDxeSmmScSmbusCommonLib/PeiDxeSmmScSmbusCommonLib.inf b/Silicon/BroxtonSoC/BroxtonSiPkg/SouthCluster/Library/Private/PeiDxeSmmScSmbusCommonLib/PeiDxeSmmScSmbusCommonLib.inf new file mode 100644 index 0000000000..2ca6549b88 --- /dev/null +++ b/Silicon/BroxtonSoC/BroxtonSiPkg/SouthCluster/Library/Private/PeiDxeSmmScSmbusCommonLib/PeiDxeSmmScSmbusCommonLib.inf @@ -0,0 +1,35 @@ +## @file +# Apollo Lake South Cluster Smbus Common Lib. +# +# Copyright (c) 1999 - 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. +# +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = PeiDxeSmmPchSmbusCommonLib + FILE_GUID = 490CEB45-F3A8-41CD-89CB-C47BF7F7A4B4 + MODULE_TYPE = BASE + VERSION_STRING = 1.0 + LIBRARY_CLASS = ScSmbusCommonLib + +[Sources] + ScSmbusExec.c + +[Packages] + MdePkg/MdePkg.dec + BroxtonSiPkg/BroxtonSiPkg.dec + BroxtonSiPkg/BroxtonSiPrivate.dec + +[LibraryClasses] + IoLib + DebugLib + MmPciLib diff --git a/Silicon/BroxtonSoC/BroxtonSiPkg/SouthCluster/Library/Private/PeiDxeSmmScSmbusCommonLib/ScSmbusExec.c b/Silicon/BroxtonSoC/BroxtonSiPkg/SouthCluster/Library/Private/PeiDxeSmmScSmbusCommonLib/ScSmbusExec.c new file mode 100644 index 0000000000..2c5fe9b7b8 --- /dev/null +++ b/Silicon/BroxtonSoC/BroxtonSiPkg/SouthCluster/Library/Private/PeiDxeSmmScSmbusCommonLib/ScSmbusExec.c @@ -0,0 +1,653 @@ +/** @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); +} \ No newline at end of file -- cgit v1.2.3