From 1d2f35dfe9c511fbdce6db1997154cf397e51b04 Mon Sep 17 00:00:00 2001 From: Robert Csordas Date: Sun, 13 Nov 2016 18:35:22 +0100 Subject: [PATCH] Fix i2c hangs when disconnecting the left half. --- shared/i2c_adddresses.c | 227 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 209 insertions(+), 18 deletions(-) diff --git a/shared/i2c_adddresses.c b/shared/i2c_adddresses.c index 3f3a650..3c844aa 100644 --- a/shared/i2c_adddresses.c +++ b/shared/i2c_adddresses.c @@ -1,28 +1,219 @@ #include "fsl_i2c.h" #include "i2c_addresses.h" +/** + * This file has couple of hacks in it, because the blocking i2c api provided by kinetis does not handle + * i2c errors properly. Because of this, there were some stalls when keyboards were disconnected in middle + * of a transfer. + * + */ + +#define TO_CLEAR_FLAGS (kI2C_ArbitrationLostFlag | kI2C_IntPendingFlag | kI2C_StartDetectFlag | kI2C_StopDetectFlag) + +static status_t I2C_CheckAndClearError(I2C_Type *base, uint32_t status) +{ + status_t result = kStatus_Success; + + /* Check arbitration lost. */ + if (status & kI2C_ArbitrationLostFlag) + { + /* Clear arbitration lost flag. */ + base->S = kI2C_ArbitrationLostFlag; + result = kStatus_I2C_ArbitrationLost; + } + /* Check NAK */ + else if (status & kI2C_ReceiveNakFlag) + { + result = kStatus_I2C_Nak; + } + else + { + } + + return result; +} + +status_t pollForFlag(I2C_Type *baseAddress, uint32_t flag){ + uint16_t timeout=-1; + status_t result = kStatus_Success; + + while ((!(baseAddress->S & flag)) && (timeout!=0)) + { + timeout--; + } + + if (timeout==0){ + result=I2C_CheckAndClearError(baseAddress, baseAddress->S); + I2C_MasterStop(baseAddress); + + if (result!=kStatus_Success){ + result=kStatus_I2C_Timeout; + } + } + + return result; +} + +status_t i2c_preamble(I2C_Type *baseAddress, uint8_t i2cAddress, i2c_direction_t dir){ + + I2C_MasterClearStatusFlags(baseAddress, TO_CLEAR_FLAGS); + /* Wait until ready to complete. */ + + status_t result=pollForFlag(baseAddress, kI2C_TransferCompleteFlag); + if (result){ + return result; + } + + result = I2C_MasterStart(baseAddress, i2cAddress, dir); + if (result){ + return result; + } + /* Wait until address + command transfer complete. */ + result=pollForFlag(baseAddress, kI2C_IntPendingFlag); + if (result){ + return result; + } + + /* Check if there's transfer error. */ + result = I2C_CheckAndClearError(baseAddress, baseAddress->S); + + /* Return if error. */ + if (result == kStatus_I2C_Nak) { + I2C_MasterStop(baseAddress); + } + + if (result){ + return result; + } + + /* Wait until the data register is ready for transmit. */ + result=pollForFlag(baseAddress, kI2C_TransferCompleteFlag); + + return result; +} + status_t I2cRead(I2C_Type *baseAddress, uint8_t i2cAddress, uint8_t *data, uint8_t size) { - i2c_master_transfer_t masterXfer; - masterXfer.slaveAddress = i2cAddress; - masterXfer.direction = kI2C_Read; - masterXfer.subaddress = 0; - masterXfer.subaddressSize = 0; - masterXfer.data = data; - masterXfer.dataSize = size; - masterXfer.flags = kI2C_TransferDefaultFlag; - return I2C_MasterTransferBlocking(baseAddress, &masterXfer); + status_t result = i2c_preamble(baseAddress, i2cAddress, kI2C_Read); + if (result){ + return result; + } + + result = kStatus_Success; + volatile uint8_t dummy = 0; + + /* Add this to avoid build warning. */ + dummy++; + + /* Clear the IICIF flag. */ + baseAddress->S = kI2C_IntPendingFlag; + + /* Setup the I2C peripheral to receive data. */ + baseAddress->C1 &= ~(I2C_C1_TX_MASK | I2C_C1_TXAK_MASK); + + /* If rxSize equals 1, configure to send NAK. */ + if (size == 1) + { + /* Issue NACK on read. */ + baseAddress->C1 |= I2C_C1_TXAK_MASK; + } + + /* Do dummy read. */ + dummy = baseAddress->D; + + while ((size--)) + { + /* Wait until data transfer complete. */ + result=pollForFlag(baseAddress, kI2C_IntPendingFlag); + if (result){ + return result; + } + + /* Clear the IICIF flag. */ + baseAddress->S = kI2C_IntPendingFlag; + + /* Single byte use case. */ + if (size == 0) + { + /* Read the final byte. */ + result = I2C_MasterStop(baseAddress); + } + + if (size == 1) + { + /* Issue NACK on read. */ + baseAddress->C1 |= I2C_C1_TXAK_MASK; + } + + /* Read from the data register. */ + *data++ = baseAddress->D; + } + + return result; } status_t I2cWrite(I2C_Type *baseAddress, uint8_t i2cAddress, uint8_t *data, uint8_t size) { - i2c_master_transfer_t masterXfer; - masterXfer.slaveAddress = i2cAddress; - masterXfer.direction = kI2C_Write; - masterXfer.subaddress = 0; - masterXfer.subaddressSize = 0; - masterXfer.data = data; - masterXfer.dataSize = size; - masterXfer.flags = kI2C_TransferDefaultFlag; - return I2C_MasterTransferBlocking(baseAddress, &masterXfer); + status_t result = i2c_preamble(baseAddress, i2cAddress, kI2C_Read); + if (result){ + return result; + } + + uint8_t statusFlags = 0; + + /* Clear the IICIF flag. */ + baseAddress->S = kI2C_IntPendingFlag; + + /* Setup the I2C peripheral to transmit data. */ + baseAddress->C1 |= I2C_C1_TX_MASK; + + while (size--) + { + /* Send a byte of data. */ + baseAddress->D = *data++; + + /* Wait until data transfer complete. */ + result=pollForFlag(baseAddress, kI2C_IntPendingFlag); + if (result){ + return result; + } + + statusFlags = baseAddress->S; + + /* Clear the IICIF flag. */ + baseAddress->S = kI2C_IntPendingFlag; + + /* Check if arbitration lost or no acknowledgement (NAK), return failure status. */ + if (statusFlags & kI2C_ArbitrationLostFlag) + { + baseAddress->S = kI2C_ArbitrationLostFlag; + result = kStatus_I2C_ArbitrationLost; + } + + if (statusFlags & kI2C_ReceiveNakFlag) + { + baseAddress->S = kI2C_ReceiveNakFlag; + result = kStatus_I2C_Nak; + } + + if (result != kStatus_Success) + { + /* Breaking out of the send loop. */ + break; + } + } + + + + if ((result == kStatus_Success) || (result == kStatus_I2C_Nak)) + { + /* Clear the IICIF flag. */ + baseAddress->S = kI2C_IntPendingFlag; + + /* Send stop. */ + result = I2C_MasterStop(baseAddress); + } + + return result; } +