/** @file I2c Lib to control I2c controller. Copyright 2017, 2020 NXP SPDX-License-Identifier: BSD-2-Clause-Patent @par Revision Reference: - PI Version 1.7 @par Glossary: - Ibfd - I2c Bus Frequency Divider **/ #include #include #include #include #include #include "I2cLibInternal.h" /** I2C divisor and Ibfd register values when glitch filter is enabled In case of duplicate SCL Divisor value, the Ibfd value with high MUL value has been selected. A higher MUL value results in a lower sampling rate of the I2C signals. This gives the I2C module greater immunity against glitches in the I2C signals. These values can be referred from NXP QorIQ Layerscape series SOC Reference Manual in which this I2C ip has been used. e.g. LX2160ARM, LS1043ARM **/ STATIC CONST I2C_CLOCK_DIVISOR_PAIR mI2cClockDivisorGlitchEnabled[] = { { 34, 0x0 }, { 36, 0x1 }, { 38, 0x2 }, { 40, 0x3 }, { 42, 0x4 }, { 44, 0x8 }, { 48, 0x9 }, { 52, 0xA }, { 54, 0x7 }, { 56, 0xB }, { 60, 0xC }, { 64, 0x10 }, { 68, 0x40 }, { 72, 0x41 }, { 76, 0x42 }, { 80, 0x43 }, { 84, 0x44 }, { 88, 0x48 }, { 96, 0x49 }, { 104, 0x4A }, { 108, 0x47 }, { 112, 0x4B }, { 120, 0x4C }, { 128, 0x50 }, { 136, 0x80 }, { 144, 0x81 }, { 152, 0x82 }, { 160, 0x83 }, { 168, 0x84 }, { 176, 0x88 }, { 192, 0x89 }, { 208, 0x8A }, { 216, 0x87 }, { 224, 0x8B }, { 240, 0x8C }, { 256, 0x90 }, { 288, 0x91 }, { 320, 0x92 }, { 336, 0x8F }, { 352, 0x93 }, { 384, 0x98 }, { 416, 0x95 }, { 448, 0x99 }, { 480, 0x96 }, { 512, 0x9A }, { 576, 0x9B }, { 640, 0xA0 }, { 704, 0x9D }, { 768, 0xA1 }, { 832, 0x9E }, { 896, 0xA2 }, { 960, 0x67 }, { 1024, 0xA3 }, { 1152, 0xA4 }, { 1280, 0xA8 }, { 1536, 0xA9 }, { 1792, 0xAA }, { 1920, 0xA7 }, { 2048, 0xAB }, { 2304, 0xAC }, { 2560, 0xB0 }, { 3072, 0xB1 }, { 3584, 0xB2 }, { 3840, 0xAF }, { 4096, 0xB3 }, { 4608, 0xB4 }, { 5120, 0xB8 }, { 6144, 0xB9 }, { 7168, 0xBA }, { 7680, 0xB7 }, { 8192, 0xBB }, { 9216, 0xBC }, { 10240, 0xBD }, { 12288, 0xBE }, { 15360, 0xBF } }; /** I2C divisor and Ibfd register values when glitch filter is disabled In case of duplicate SCL Divisor value, the Ibfd value with high MUL value has been selected. A higher MUL value results in a lower sampling rate of the I2C signals. This gives the I2C module greater immunity against glitches in the I2C signals. These values can be referred from NXP QorIQ Layerscape series SOC Reference Manual in which this I2C ip has been used. e.g. LX2160ARM, LS1043ARM **/ STATIC CONST I2C_CLOCK_DIVISOR_PAIR mI2cClockDivisorGlitchDisabled[] = { { 20, 0x0 },{ 22, 0x1 },{ 24, 0x2 },{ 26, 0x3 }, { 28, 0x8 },{ 30, 0x5 },{ 32, 0x9 },{ 34, 0x6 }, { 36, 0x0A },{ 40, 0x40 },{ 44, 0x41 },{ 48, 0x42 }, { 52, 0x43 },{ 56, 0x48 },{ 60, 0x45 },{ 64, 0x49 }, { 68, 0x46 },{ 72, 0x4A },{ 80, 0x80 },{ 88, 0x81 }, { 96, 0x82 },{ 104, 0x83 },{ 112, 0x88 },{ 120, 0x85 }, { 128, 0x89 },{ 136, 0x86 },{ 144, 0x8A },{ 160, 0x8B }, { 176, 0x8C },{ 192, 0x90 },{ 208, 0x56 },{ 224, 0x91 }, { 240, 0x1F },{ 256, 0x92 },{ 272, 0x8F },{ 288, 0x93 }, { 320, 0x98 },{ 352, 0x95 },{ 384, 0x99 },{ 416, 0x96 }, { 448, 0x9A },{ 480, 0x5F },{ 512, 0x9B },{ 576, 0x9C }, { 640, 0xA0 },{ 768, 0xA1 },{ 896, 0xA2 },{ 960, 0x9F }, { 1024, 0xA3 },{ 1152, 0xA4 },{ 1280, 0xA8 },{ 1536, 0xA9 }, { 1792, 0xAA },{ 1920, 0xA7 },{ 2048, 0xAB },{ 2304, 0xAC }, { 2560, 0xAD },{ 3072, 0xB1 },{ 3584, 0xB2 },{ 3840, 0xAF }, { 4096, 0xB3 },{ 4608, 0xB4 },{ 5120, 0xB8 },{ 6144, 0xB9 }, { 7168, 0xBA },{ 7680, 0xB7 },{ 8192, 0xBB },{ 9216, 0xBC }, { 10240, 0xBD },{ 12288, 0xBE },{ 15360, 0xBF } }; /** A-009203 : I2C may not work reliably with the default setting Description : The clocking circuitry of I2C module may not work reliably due to the slow rise time of SCL signal. Workaround : Enable the receiver digital filter by setting IBDBG[GLFLT_EN] to 1. refer https://patchwork.ozlabs.org/patch/453575/ **/ STATIC VOID I2cErratumA009203 ( IN UINTN Base ) { I2C_REGS *Regs; Regs = (I2C_REGS *)Base; MmioOr8 ((UINTN)&Regs->Ibdbg, I2C_IBDBG_GLFLT_EN); } /** software reset of the entire I2C module. The module is reset and disabled. Status register fields (IBSR) are cleared. @param[in] Base Base Address of I2c controller's registers @return EFI_SUCCESS successfuly reset the I2c module **/ EFI_STATUS I2cReset ( IN UINTN Base ) { I2C_REGS *Regs; Regs = (I2C_REGS *)Base; MmioOr8 ((UINTN)&Regs->Ibcr, I2C_IBCR_MDIS); MmioOr8 ((UINTN)&Regs->Ibsr, (I2C_IBSR_IBAL | I2C_IBSR_IBIF)); MmioAnd8 ((UINTN)&Regs->Ibcr, ~(I2C_IBCR_IBIE | I2C_IBCR_DMAEN)); MmioAnd8 ((UINTN)&Regs->Ibic, (UINT8)(~I2C_IBIC_BIIE)); return EFI_SUCCESS; } /** Configure I2c bus to operate at a given speed @param[in] Base Base Address of I2c controller's registers @param[in] I2cBusClock Input clock to I2c controller @param[in] Speed speed to be configured for I2c bus @return EFI_SUCCESS successfuly setup the i2c bus **/ EFI_STATUS I2cInitialize ( IN UINTN Base, IN UINT64 I2cBusClock, IN UINT64 Speed ) { I2C_REGS *Regs; UINT16 ClockDivisor; UINT8 Ibfd; // I2c Bus Frequency Divider Register CONST I2C_CLOCK_DIVISOR_PAIR *ClockDivisorPair; UINT32 ClockDivisorPairSize; UINT32 Index; Regs = (I2C_REGS *)Base; if (FeaturePcdGet (PcdI2cErratumA009203)) { // Apply Erratum A-009203 before writing Ibfd register I2cErratumA009203 (Base); } Ibfd = 0; ClockDivisor = (I2cBusClock + Speed - 1) / Speed; if (MmioRead8 ((UINTN)&Regs->Ibdbg) & I2C_IBDBG_GLFLT_EN) { ClockDivisorPair = mI2cClockDivisorGlitchEnabled; ClockDivisorPairSize = ARRAY_SIZE (mI2cClockDivisorGlitchEnabled); } else { ClockDivisorPair = mI2cClockDivisorGlitchDisabled; ClockDivisorPairSize = ARRAY_SIZE (mI2cClockDivisorGlitchDisabled); } if (ClockDivisor > ClockDivisorPair[ClockDivisorPairSize - 1].Divisor) { Ibfd = ClockDivisorPair[ClockDivisorPairSize - 1].Ibfd; } else { for (Index = 0; Index < ClockDivisorPairSize; Index++) { if (ClockDivisorPair[Index].Divisor >= ClockDivisor) { Ibfd = ClockDivisorPair[Index].Ibfd; break; } } } MmioWrite8 ((UINTN)&Regs->Ibfd, Ibfd); I2cReset (Base); return EFI_SUCCESS; } STATIC EFI_STATUS I2cBusTestBusBusy ( IN I2C_REGS *Regs, IN BOOLEAN TestBusy ) { UINT32 Index; UINT8 Reg; for (Index = 0; Index < I2C_NUM_RETRIES; Index++) { Reg = MmioRead8 ((UINTN)&Regs->Ibsr); if (Reg & I2C_IBSR_IBAL) { MmioWrite8 ((UINTN)&Regs->Ibsr, Reg); return EFI_NOT_READY; } if (TestBusy && (Reg & I2C_IBSR_IBB)) { break; } if (!TestBusy && !(Reg & I2C_IBSR_IBB)) { break; } MicroSecondDelay (1); } if (Index == I2C_NUM_RETRIES) { return EFI_TIMEOUT; } return EFI_SUCCESS; } STATIC EFI_STATUS I2cTransferComplete ( IN I2C_REGS *Regs, IN BOOLEAN TestRxAck ) { UINT32 Index; UINT8 Reg; for (Index = 0; Index < I2C_NUM_RETRIES; Index++) { Reg = MmioRead8 ((UINTN)&Regs->Ibsr); if (Reg & I2C_IBSR_IBIF) { // Write 1 to clear the IBIF field MmioWrite8 ((UINTN)&Regs->Ibsr, Reg); break; } MicroSecondDelay (1); } if (Index == I2C_NUM_RETRIES) { return EFI_TIMEOUT; } if (TestRxAck && (Reg & I2C_IBSR_RXAK)) { return EFI_NO_RESPONSE; } if (Reg & I2C_IBSR_TCF) { return EFI_SUCCESS; } return EFI_DEVICE_ERROR; } STATIC EFI_STATUS I2cRead ( IN I2C_REGS *Regs, IN UINT32 SlaveAddress, IN EFI_I2C_OPERATION *Operation, IN BOOLEAN IsLastOperation ) { EFI_STATUS Status; UINTN Index; // Write Slave Address MmioWrite8 ((UINTN)&Regs->Ibdr, (SlaveAddress << BIT0) | BIT0); Status = I2cTransferComplete (Regs, I2C_BUS_TEST_RX_ACK); if (EFI_ERROR (Status)) { return Status; } // select Receive mode. MmioAnd8 ((UINTN)&Regs->Ibcr, ~I2C_IBCR_TXRX); if (Operation->LengthInBytes > 1) { // Set No ACK = 0 MmioAnd8 ((UINTN)&Regs->Ibcr, ~I2C_IBCR_NOACK); } // Perform a dummy read to initiate the receive operation. MmioRead8 ((UINTN)&Regs->Ibdr); for (Index = 0; Index < Operation->LengthInBytes; Index++) { Status = I2cTransferComplete (Regs, I2C_BUS_NO_TEST_RX_ACK); if (EFI_ERROR (Status)) { return Status; } if (Index == (Operation->LengthInBytes - 2)) { // Set No ACK = 1 MmioOr8 ((UINTN)&Regs->Ibcr, I2C_IBCR_NOACK); } else if (Index == (Operation->LengthInBytes - 1)) { if (!IsLastOperation) { // select Transmit mode (for repeat start) MmioOr8 ((UINTN)&Regs->Ibcr, I2C_IBCR_TXRX); } else { // Generate Stop Signal MmioAnd8 ((UINTN)&Regs->Ibcr, ~(I2C_IBCR_MSSL | I2C_IBCR_TXRX)); Status = I2cBusTestBusBusy (Regs, I2C_BUS_TEST_IDLE); if (EFI_ERROR (Status)) { return Status; } } } Operation->Buffer[Index] = MmioRead8 ((UINTN)&Regs->Ibdr); } return EFI_SUCCESS; } STATIC EFI_STATUS I2cWrite ( IN I2C_REGS *Regs, IN UINT32 SlaveAddress, IN EFI_I2C_OPERATION *Operation ) { EFI_STATUS Status; UINTN Index; // Write Slave Address MmioWrite8 ((UINTN)&Regs->Ibdr, (SlaveAddress << BIT0) & (UINT8)(~BIT0)); Status = I2cTransferComplete (Regs, I2C_BUS_TEST_RX_ACK); if (EFI_ERROR (Status)) { return Status; } // Write Data for (Index = 0; Index < Operation->LengthInBytes; Index++) { MmioWrite8 ((UINTN)&Regs->Ibdr, Operation->Buffer[Index]); Status = I2cTransferComplete (Regs, I2C_BUS_TEST_RX_ACK); if (EFI_ERROR (Status)) { return Status; } } return EFI_SUCCESS; } STATIC EFI_STATUS I2cStop ( IN I2C_REGS *Regs ) { EFI_STATUS Status; UINT8 Reg; Status = EFI_SUCCESS; Reg = MmioRead8 ((UINTN)&Regs->Ibsr); if (Reg & I2C_IBSR_IBB) { // Generate Stop Signal MmioAnd8 ((UINTN)&Regs->Ibcr, ~(I2C_IBCR_MSSL | I2C_IBCR_TXRX)); Status = I2cBusTestBusBusy (Regs, I2C_BUS_TEST_IDLE); if (EFI_ERROR (Status)) { return Status; } } // Disable I2c Controller MmioOr8 ((UINTN)&Regs->Ibcr, I2C_IBCR_MDIS); return Status; } STATIC EFI_STATUS I2cStart ( IN I2C_REGS *Regs ) { EFI_STATUS Status; MmioOr8 ((UINTN)&Regs->Ibsr, (I2C_IBSR_IBAL | I2C_IBSR_IBIF)); MmioAnd8 ((UINTN)&Regs->Ibcr, (UINT8)(~I2C_IBCR_MDIS)); //Wait controller to be stable MicroSecondDelay (1); // Generate Start Signal MmioOr8 ((UINTN)&Regs->Ibcr, I2C_IBCR_MSSL); Status = I2cBusTestBusBusy (Regs, I2C_BUS_TEST_BUSY); if (EFI_ERROR (Status)) { return Status; } // Select Transmit Mode. set No ACK = 1 MmioOr8 ((UINTN)&Regs->Ibcr, (I2C_IBCR_TXRX | I2C_IBCR_NOACK)); return Status; } /** Transfer data to/from I2c slave device @param[in] Base Base Address of I2c controller's registers @param[in] SlaveAddress Slave Address from which data is to be read @param[in] RequestPacket Pointer to an EFI_I2C_REQUEST_PACKET structure describing the I2C transaction @return EFI_SUCCESS successfuly transfer the data @return EFI_DEVICE_ERROR There was an error while transferring data through I2c bus @return EFI_NO_RESPONSE There was no Ack from i2c device @return EFI_TIMEOUT I2c Bus is busy @return EFI_NOT_READY I2c Bus Arbitration lost **/ EFI_STATUS I2cBusXfer ( IN UINTN Base, IN UINT32 SlaveAddress, IN EFI_I2C_REQUEST_PACKET *RequestPacket ) { UINTN Index; I2C_REGS *Regs; EFI_I2C_OPERATION *Operation; EFI_STATUS Status; BOOLEAN IsLastOperation; Regs = (I2C_REGS *)Base; IsLastOperation = FALSE; Status = I2cBusTestBusBusy (Regs, I2C_BUS_TEST_IDLE); if (EFI_ERROR (Status)) { goto ErrorExit; } Status = I2cStart (Regs); if (EFI_ERROR (Status)) { goto ErrorExit; } for (Index = 0, Operation = RequestPacket->Operation; Index < RequestPacket->OperationCount; Index++, Operation++) { if (Index == (RequestPacket->OperationCount - 1)) { IsLastOperation = TRUE; } // Send repeat start after first transmit/recieve if (Index) { MmioOr8 ((UINTN)&Regs->Ibcr, I2C_IBCR_RSTA); Status = I2cBusTestBusBusy (Regs, I2C_BUS_TEST_BUSY); if (EFI_ERROR (Status)) { goto ErrorExit; } } // Read/write data if (Operation->Flags & I2C_FLAG_READ) { Status = I2cRead (Regs, SlaveAddress, Operation, IsLastOperation); } else { Status = I2cWrite (Regs, SlaveAddress, Operation); } if (EFI_ERROR (Status)) { goto ErrorExit; } } ErrorExit: I2cStop (Regs); return Status; } /** Read a register from I2c slave device. This API is wrapper around I2cBusXfer @param[in] Base Base Address of I2c controller's registers @param[in] SlaveAddress Slave Address from which register value is to be read @param[in] RegAddress Register Address in Slave's memory map @param[in] RegAddressWidthInBytes Number of bytes in RegAddress to send to I2c Slave for simple reads without any register, make this value = 0 (RegAddress is don't care in that case) @param[out] RegValue Value to be read from I2c slave's regiser @param[in] RegValueNumBytes Number of bytes to read from I2c slave register @return EFI_SUCCESS successfuly read the registers @return EFI_DEVICE_ERROR There was an error while transferring data through I2c bus @return EFI_NO_RESPONSE There was no Ack from i2c device @return EFI_TIMEOUT I2c Bus is busy @return EFI_NOT_READY I2c Bus Arbitration lost **/ EFI_STATUS I2cBusReadReg ( IN UINTN Base, IN UINT32 SlaveAddress, IN UINT64 RegAddress, IN UINT8 RegAddressWidthInBytes, OUT UINT8 *RegValue, IN UINT32 RegValueNumBytes ) { EFI_I2C_OPERATION *Operations; I2C_REG_REQUEST RequestPacket; UINTN OperationCount; UINT8 Address[sizeof (RegAddress)]; UINT8 *AddressPtr; EFI_STATUS Status; ZeroMem (&RequestPacket, sizeof (RequestPacket)); OperationCount = 0; Operations = RequestPacket.Operation; AddressPtr = Address; if (RegAddressWidthInBytes > ARRAY_SIZE (Address)) { return EFI_INVALID_PARAMETER; } if (RegAddressWidthInBytes != 0) { Operations[OperationCount].LengthInBytes = RegAddressWidthInBytes; Operations[OperationCount].Buffer = AddressPtr; while (RegAddressWidthInBytes--) { *AddressPtr++ = RegAddress >> (8 * RegAddressWidthInBytes); } OperationCount++; } Operations[OperationCount].LengthInBytes = RegValueNumBytes; Operations[OperationCount].Buffer = RegValue; Operations[OperationCount].Flags = I2C_FLAG_READ; OperationCount++; RequestPacket.OperationCount = OperationCount; Status = I2cBusXfer ( Base, SlaveAddress, (EFI_I2C_REQUEST_PACKET *)&RequestPacket ); return Status; }