549 lines
14 KiB
C
549 lines
14 KiB
C
/*
|
|
* @brief LPC13xx I2C driver
|
|
*
|
|
* @note
|
|
* Copyright(C) NXP Semiconductors, 2012
|
|
* All rights reserved.
|
|
*
|
|
* @par
|
|
* Software that is described herein is for illustrative purposes only
|
|
* which provides customers with programming information regarding the
|
|
* LPC products. This software is supplied "AS IS" without any warranties of
|
|
* any kind, and NXP Semiconductors and its licensor disclaim any and
|
|
* all warranties, express or implied, including all implied warranties of
|
|
* merchantability, fitness for a particular purpose and non-infringement of
|
|
* intellectual property rights. NXP Semiconductors assumes no responsibility
|
|
* or liability for the use of the software, conveys no license or rights under any
|
|
* patent, copyright, mask work right, or any other intellectual property rights in
|
|
* or to any products. NXP Semiconductors reserves the right to make changes
|
|
* in the software without notification. NXP Semiconductors also makes no
|
|
* representation or warranty that such application will be suitable for the
|
|
* specified use without further testing or modification.
|
|
*
|
|
* @par
|
|
* Permission to use, copy, modify, and distribute this software and its
|
|
* documentation is hereby granted, under NXP Semiconductors' and its
|
|
* licensor's relevant copyrights in the software, without fee, provided that it
|
|
* is used in conjunction with NXP Semiconductors microcontrollers. This
|
|
* copyright, permission, and disclaimer notice must appear in all copies of
|
|
* this code.
|
|
*/
|
|
|
|
#include "chip.h"
|
|
|
|
/*****************************************************************************
|
|
* Private types/enumerations/variables
|
|
****************************************************************************/
|
|
|
|
/* Control flags */
|
|
#define I2C_CON_FLAGS (I2C_CON_AA | I2C_CON_SI | I2C_CON_STO | I2C_CON_STA)
|
|
#define LPC_I2Cx(id) ((i2c[id].ip))
|
|
#define SLAVE_ACTIVE(iic) (((iic)->flags & 0xFF00) != 0)
|
|
|
|
/* I2C common interface structure */
|
|
struct i2c_interface {
|
|
LPC_I2C_T *ip; /* IP base address of the I2C device */
|
|
CHIP_SYSCTL_CLOCK_T clk; /* Clock used by I2C */
|
|
I2C_EVENTHANDLER_T mEvent; /* Current active Master event handler */
|
|
I2C_EVENTHANDLER_T sEvent; /* Slave transfer events */
|
|
I2C_XFER_T *mXfer; /* Current active xfer pointer */
|
|
I2C_XFER_T *sXfer; /* Pointer to store xfer when bus is busy */
|
|
uint32_t flags; /* Flags used by I2C master and slave */
|
|
};
|
|
|
|
/* Slave interface structure */
|
|
struct i2c_slave_interface {
|
|
I2C_XFER_T *xfer;
|
|
I2C_EVENTHANDLER_T event;
|
|
};
|
|
|
|
/* I2C interfaces */
|
|
static struct i2c_interface i2c[I2C_NUM_INTERFACE] = {
|
|
{LPC_I2C, SYSCTL_CLOCK_I2C, Chip_I2C_EventHandler, NULL, NULL, NULL, 0}
|
|
};
|
|
|
|
static struct i2c_slave_interface i2c_slave[I2C_NUM_INTERFACE][I2C_SLAVE_NUM_INTERFACE];
|
|
|
|
/*****************************************************************************
|
|
* Public types/enumerations/variables
|
|
****************************************************************************/
|
|
|
|
/*****************************************************************************
|
|
* Private functions
|
|
****************************************************************************/
|
|
|
|
STATIC INLINE void enableClk(I2C_ID_T id)
|
|
{
|
|
Chip_Clock_EnablePeriphClock(i2c[id].clk);
|
|
}
|
|
|
|
STATIC INLINE void disableClk(I2C_ID_T id)
|
|
{
|
|
Chip_Clock_DisablePeriphClock(i2c[id].clk);
|
|
}
|
|
|
|
/* Get the ADC Clock Rate */
|
|
STATIC INLINE uint32_t getClkRate(I2C_ID_T id)
|
|
{
|
|
return Chip_Clock_GetSystemClockRate();
|
|
}
|
|
|
|
/* Enable I2C and start master transfer */
|
|
STATIC INLINE void startMasterXfer(LPC_I2C_T *pI2C)
|
|
{
|
|
/* Reset STA, STO, SI */
|
|
pI2C->CONCLR = I2C_CON_SI | I2C_CON_STO | I2C_CON_STA | I2C_CON_AA;
|
|
|
|
/* Enter to Master Transmitter mode */
|
|
pI2C->CONSET = I2C_CON_I2EN | I2C_CON_STA;
|
|
}
|
|
|
|
/* Enable I2C and enable slave transfers */
|
|
STATIC INLINE void startSlaverXfer(LPC_I2C_T *pI2C)
|
|
{
|
|
/* Reset STA, STO, SI */
|
|
pI2C->CONCLR = I2C_CON_SI | I2C_CON_STO | I2C_CON_STA;
|
|
|
|
/* Enter to Master Transmitter mode */
|
|
pI2C->CONSET = I2C_CON_I2EN | I2C_CON_AA;
|
|
}
|
|
|
|
/* Check if I2C bus is free */
|
|
STATIC INLINE int isI2CBusFree(LPC_I2C_T *pI2C)
|
|
{
|
|
return !(pI2C->CONSET & I2C_CON_STO);
|
|
}
|
|
|
|
/* Get current state of the I2C peripheral */
|
|
STATIC INLINE int getCurState(LPC_I2C_T *pI2C)
|
|
{
|
|
return (int) (pI2C->STAT & I2C_STAT_CODE_BITMASK);
|
|
}
|
|
|
|
/* Check if the active state belongs to master mode*/
|
|
STATIC INLINE int isMasterState(LPC_I2C_T *pI2C)
|
|
{
|
|
return getCurState(pI2C) < 0x60;
|
|
}
|
|
|
|
/* Set OWN slave address for specific slave ID */
|
|
STATIC void setSlaveAddr(LPC_I2C_T *pI2C, I2C_SLAVE_ID sid, uint8_t addr, uint8_t mask)
|
|
{
|
|
uint32_t index = (uint32_t) sid - 1;
|
|
pI2C->MASK[index] = mask;
|
|
if (sid == I2C_SLAVE_0) {
|
|
pI2C->ADR0 = addr;
|
|
}
|
|
else {
|
|
volatile uint32_t *abase = &pI2C->ADR1;
|
|
abase[index - 1] = addr;
|
|
}
|
|
}
|
|
|
|
/* Match the slave address */
|
|
STATIC int isSlaveAddrMatching(uint8_t addr1, uint8_t addr2, uint8_t mask)
|
|
{
|
|
mask |= 1;
|
|
return (addr1 & ~mask) == (addr2 & ~mask);
|
|
}
|
|
|
|
/* Get the index of the active slave */
|
|
STATIC I2C_SLAVE_ID lookupSlaveIndex(LPC_I2C_T *pI2C, uint8_t slaveAddr)
|
|
{
|
|
if (!(slaveAddr >> 1)) {
|
|
return I2C_SLAVE_GENERAL; /* General call address */
|
|
}
|
|
if (isSlaveAddrMatching(pI2C->ADR0, slaveAddr, pI2C->MASK[0])) {
|
|
return I2C_SLAVE_0;
|
|
}
|
|
if (isSlaveAddrMatching(pI2C->ADR1, slaveAddr, pI2C->MASK[1])) {
|
|
return I2C_SLAVE_1;
|
|
}
|
|
if (isSlaveAddrMatching(pI2C->ADR2, slaveAddr, pI2C->MASK[2])) {
|
|
return I2C_SLAVE_2;
|
|
}
|
|
if (isSlaveAddrMatching(pI2C->ADR3, slaveAddr, pI2C->MASK[3])) {
|
|
return I2C_SLAVE_3;
|
|
}
|
|
|
|
/* If everything is fine the code should never come here */
|
|
return I2C_SLAVE_GENERAL;
|
|
}
|
|
|
|
/* Master transfer state change handler handler */
|
|
int handleMasterXferState(LPC_I2C_T *pI2C, I2C_XFER_T *xfer)
|
|
{
|
|
uint32_t cclr = I2C_CON_FLAGS;
|
|
|
|
switch (getCurState(pI2C)) {
|
|
case 0x08: /* Start condition on bus */
|
|
case 0x10: /* Repeated start condition */
|
|
pI2C->DAT = (xfer->slaveAddr << 1) | (xfer->txSz == 0);
|
|
break;
|
|
|
|
/* Tx handling */
|
|
case 0x18: /* SLA+W sent and ACK received */
|
|
case 0x28: /* DATA sent and ACK received */
|
|
if (!xfer->txSz) {
|
|
cclr &= ~(xfer->rxSz ? I2C_CON_STA : I2C_CON_STO);
|
|
}
|
|
else {
|
|
pI2C->DAT = *xfer->txBuff++;
|
|
xfer->txSz--;
|
|
}
|
|
break;
|
|
|
|
/* Rx handling */
|
|
case 0x58: /* Data Received and NACK sent */
|
|
cclr &= ~I2C_CON_STO;
|
|
|
|
case 0x50: /* Data Received and ACK sent */
|
|
*xfer->rxBuff++ = pI2C->DAT;
|
|
xfer->rxSz--;
|
|
|
|
case 0x40: /* SLA+R sent and ACK received */
|
|
if (xfer->rxSz > 1) {
|
|
cclr &= ~I2C_CON_AA;
|
|
}
|
|
break;
|
|
|
|
/* NAK Handling */
|
|
case 0x20: /* SLA+W sent NAK received */
|
|
case 0x30: /* DATA sent NAK received */
|
|
case 0x48: /* SLA+R sent NAK received */
|
|
xfer->status = I2C_STATUS_NAK;
|
|
cclr &= ~I2C_CON_STO;
|
|
break;
|
|
|
|
case 0x38: /* Arbitration lost */
|
|
xfer->status = I2C_STATUS_ARBLOST;
|
|
break;
|
|
|
|
/* Bus Error */
|
|
case 0x00:
|
|
xfer->status = I2C_STATUS_BUSERR;
|
|
cclr &= ~I2C_CON_STO;
|
|
}
|
|
|
|
/* Set clear control flags */
|
|
pI2C->CONSET = cclr ^ I2C_CON_FLAGS;
|
|
pI2C->CONCLR = cclr;
|
|
|
|
/* If stopped return 0 */
|
|
if (!(cclr & I2C_CON_STO) || (xfer->status == I2C_STATUS_ARBLOST)) {
|
|
if (xfer->status == I2C_STATUS_BUSY) {
|
|
xfer->status = I2C_STATUS_DONE;
|
|
}
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* Find the slave address of SLA+W or SLA+R */
|
|
I2C_SLAVE_ID getSlaveIndex(LPC_I2C_T *pI2C)
|
|
{
|
|
switch (getCurState(pI2C)) {
|
|
case 0x60:
|
|
case 0x68:
|
|
case 0x70:
|
|
case 0x78:
|
|
case 0xA8:
|
|
case 0xB0:
|
|
return lookupSlaveIndex(pI2C, pI2C->DAT);
|
|
}
|
|
|
|
/* If everything is fine code should never come here */
|
|
return I2C_SLAVE_GENERAL;
|
|
}
|
|
|
|
/* Slave state machine handler */
|
|
int handleSlaveXferState(LPC_I2C_T *pI2C, I2C_XFER_T *xfer)
|
|
{
|
|
uint32_t cclr = I2C_CON_FLAGS;
|
|
int ret = RET_SLAVE_BUSY;
|
|
|
|
xfer->status = I2C_STATUS_BUSY;
|
|
switch (getCurState(pI2C)) {
|
|
case 0x80: /* SLA: Data received + ACK sent */
|
|
case 0x90: /* GC: Data received + ACK sent */
|
|
*xfer->rxBuff++ = pI2C->DAT;
|
|
xfer->rxSz--;
|
|
ret = RET_SLAVE_RX;
|
|
if (xfer->rxSz > 1) {
|
|
cclr &= ~I2C_CON_AA;
|
|
}
|
|
break;
|
|
|
|
case 0x60: /* Own SLA+W received */
|
|
case 0x68: /* Own SLA+W received after losing arbitration */
|
|
case 0x70: /* GC+W received */
|
|
case 0x78: /* GC+W received after losing arbitration */
|
|
xfer->slaveAddr = pI2C->DAT & ~1;
|
|
if (xfer->rxSz > 1) {
|
|
cclr &= ~I2C_CON_AA;
|
|
}
|
|
break;
|
|
|
|
case 0xA8: /* SLA+R received */
|
|
case 0xB0: /* SLA+R received after losing arbitration */
|
|
xfer->slaveAddr = pI2C->DAT & ~1;
|
|
|
|
case 0xB8: /* DATA sent and ACK received */
|
|
pI2C->DAT = *xfer->txBuff++;
|
|
xfer->txSz--;
|
|
if (xfer->txSz > 0) {
|
|
cclr &= ~I2C_CON_AA;
|
|
}
|
|
ret = RET_SLAVE_TX;
|
|
break;
|
|
|
|
case 0xC0: /* Data transmitted and NAK received */
|
|
case 0xC8: /* Last data transmitted and ACK received */
|
|
case 0x88: /* SLA: Data received + NAK sent */
|
|
case 0x98: /* GC: Data received + NAK sent */
|
|
case 0xA0: /* STOP/Repeated START condition received */
|
|
ret = RET_SLAVE_IDLE;
|
|
cclr &= ~I2C_CON_AA;
|
|
xfer->status = I2C_STATUS_DONE;
|
|
if (xfer->slaveAddr & 1) {
|
|
cclr &= ~I2C_CON_STA;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Set clear control flags */
|
|
pI2C->CONSET = cclr ^ I2C_CON_FLAGS;
|
|
pI2C->CONCLR = cclr;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Public functions
|
|
****************************************************************************/
|
|
/* Chip event handler interrupt based */
|
|
void Chip_I2C_EventHandler(I2C_ID_T id, I2C_EVENT_T event)
|
|
{
|
|
struct i2c_interface *iic = &i2c[id];
|
|
volatile I2C_STATUS_T *stat;
|
|
|
|
/* Only WAIT event needs to be handled */
|
|
if (event != I2C_EVENT_WAIT) {
|
|
return;
|
|
}
|
|
|
|
stat = &iic->mXfer->status;
|
|
/* Wait for the status to change */
|
|
while (*stat == I2C_STATUS_BUSY) {}
|
|
}
|
|
|
|
/* Chip polling event handler */
|
|
void Chip_I2C_EventHandlerPolling(I2C_ID_T id, I2C_EVENT_T event)
|
|
{
|
|
struct i2c_interface *iic = &i2c[id];
|
|
volatile I2C_STATUS_T *stat;
|
|
|
|
/* Only WAIT event needs to be handled */
|
|
if (event != I2C_EVENT_WAIT) {
|
|
return;
|
|
}
|
|
|
|
stat = &iic->mXfer->status;
|
|
/* Call the state change handler till xfer is done */
|
|
while (*stat == I2C_STATUS_BUSY) {
|
|
if (Chip_I2C_IsStateChanged(id)) {
|
|
Chip_I2C_MasterStateHandler(id);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Initializes the LPC_I2C peripheral with specified parameter */
|
|
void Chip_I2C_Init(I2C_ID_T id)
|
|
{
|
|
enableClk(id);
|
|
|
|
/* Set I2C operation to default */
|
|
LPC_I2Cx(id)->CONCLR = (I2C_CON_AA | I2C_CON_SI | I2C_CON_STA | I2C_CON_I2EN);
|
|
}
|
|
|
|
/* De-initializes the I2C peripheral registers to their default reset values */
|
|
void Chip_I2C_DeInit(I2C_ID_T id)
|
|
{
|
|
/* Disable I2C control */
|
|
LPC_I2Cx(id)->CONCLR = I2C_CON_I2EN | I2C_CON_SI | I2C_CON_STO | I2C_CON_STA | I2C_CON_AA;
|
|
|
|
disableClk(id);
|
|
}
|
|
|
|
/* Set up clock rate for LPC_I2C peripheral */
|
|
void Chip_I2C_SetClockRate(I2C_ID_T id, uint32_t clockrate)
|
|
{
|
|
uint32_t SCLValue;
|
|
|
|
SCLValue = (getClkRate(id) / clockrate);
|
|
LPC_I2Cx(id)->SCLH = (uint32_t) (SCLValue >> 1);
|
|
LPC_I2Cx(id)->SCLL = (uint32_t) (SCLValue - LPC_I2Cx(id)->SCLH);
|
|
}
|
|
|
|
/* Get current clock rate for LPC_I2C peripheral */
|
|
uint32_t Chip_I2C_GetClockRate(I2C_ID_T id)
|
|
{
|
|
return getClkRate(id) / (LPC_I2Cx(id)->SCLH + LPC_I2Cx(id)->SCLL);
|
|
}
|
|
|
|
/* Set the master event handler */
|
|
int Chip_I2C_SetMasterEventHandler(I2C_ID_T id, I2C_EVENTHANDLER_T event)
|
|
{
|
|
struct i2c_interface *iic = &i2c[id];
|
|
if (!iic->mXfer) {
|
|
iic->mEvent = event;
|
|
}
|
|
return iic->mEvent == event;
|
|
}
|
|
|
|
/* Get the master event handler */
|
|
I2C_EVENTHANDLER_T Chip_I2C_GetMasterEventHandler(I2C_ID_T id)
|
|
{
|
|
return i2c[id].mEvent;
|
|
}
|
|
|
|
/* Transmit and Receive data in master mode */
|
|
int Chip_I2C_MasterTransfer(I2C_ID_T id, I2C_XFER_T *xfer)
|
|
{
|
|
struct i2c_interface *iic = &i2c[id];
|
|
|
|
iic->mEvent(id, I2C_EVENT_LOCK);
|
|
xfer->status = I2C_STATUS_BUSY;
|
|
iic->mXfer = xfer;
|
|
|
|
/* If slave xfer not in progress */
|
|
if (!iic->sXfer) {
|
|
startMasterXfer(iic->ip);
|
|
}
|
|
iic->mEvent(id, I2C_EVENT_WAIT);
|
|
iic->mXfer = 0;
|
|
|
|
/* Wait for stop condition to appear on bus */
|
|
while (!isI2CBusFree(iic->ip)) {}
|
|
|
|
/* Start slave if one is active */
|
|
if (SLAVE_ACTIVE(iic)) {
|
|
startSlaverXfer(iic->ip);
|
|
}
|
|
|
|
iic->mEvent(id, I2C_EVENT_UNLOCK);
|
|
return (int) xfer->status;
|
|
}
|
|
|
|
/* Master tx only */
|
|
int Chip_I2C_MasterSend(I2C_ID_T id, uint8_t slaveAddr, const uint8_t *buff, uint8_t len)
|
|
{
|
|
I2C_XFER_T xfer = {0};
|
|
xfer.slaveAddr = slaveAddr;
|
|
xfer.txBuff = buff;
|
|
xfer.txSz = len;
|
|
while (Chip_I2C_MasterTransfer(id, &xfer) == I2C_STATUS_ARBLOST) {}
|
|
return len - xfer.txSz;
|
|
}
|
|
|
|
/* Transmit one byte and receive an array of bytes after a repeated start condition is generated in Master mode.
|
|
* This function is useful for communicating with the I2C slave registers
|
|
*/
|
|
int Chip_I2C_MasterCmdRead(I2C_ID_T id, uint8_t slaveAddr, uint8_t cmd, uint8_t *buff, int len)
|
|
{
|
|
I2C_XFER_T xfer = {0};
|
|
xfer.slaveAddr = slaveAddr;
|
|
xfer.txBuff = &cmd;
|
|
xfer.txSz = 1;
|
|
xfer.rxBuff = buff;
|
|
xfer.rxSz = len;
|
|
while (Chip_I2C_MasterTransfer(id, &xfer) == I2C_STATUS_ARBLOST) {}
|
|
return len - xfer.rxSz;
|
|
}
|
|
|
|
/* Sequential master read */
|
|
int Chip_I2C_MasterRead(I2C_ID_T id, uint8_t slaveAddr, uint8_t *buff, int len)
|
|
{
|
|
I2C_XFER_T xfer = {0};
|
|
xfer.slaveAddr = slaveAddr;
|
|
xfer.rxBuff = buff;
|
|
xfer.rxSz = len;
|
|
while (Chip_I2C_MasterTransfer(id, &xfer) == I2C_STATUS_ARBLOST) {}
|
|
return len - xfer.rxSz;
|
|
}
|
|
|
|
/* Check if master state is active */
|
|
int Chip_I2C_IsMasterActive(I2C_ID_T id)
|
|
{
|
|
return isMasterState(i2c[id].ip);
|
|
}
|
|
|
|
/* State change handler for master transfer */
|
|
void Chip_I2C_MasterStateHandler(I2C_ID_T id)
|
|
{
|
|
if (!handleMasterXferState(i2c[id].ip, i2c[id].mXfer)) {
|
|
i2c[id].mEvent(id, I2C_EVENT_DONE);
|
|
}
|
|
}
|
|
|
|
/* Setup slave function */
|
|
void Chip_I2C_SlaveSetup(I2C_ID_T id,
|
|
I2C_SLAVE_ID sid,
|
|
I2C_XFER_T *xfer,
|
|
I2C_EVENTHANDLER_T event,
|
|
uint8_t addrMask)
|
|
{
|
|
struct i2c_interface *iic = &i2c[id];
|
|
struct i2c_slave_interface *si2c = &i2c_slave[id][sid];
|
|
si2c->xfer = xfer;
|
|
si2c->event = event;
|
|
|
|
/* Set up the slave address */
|
|
if (sid != I2C_SLAVE_GENERAL) {
|
|
setSlaveAddr(iic->ip, sid, xfer->slaveAddr, addrMask);
|
|
}
|
|
|
|
if (!SLAVE_ACTIVE(iic) && !iic->mXfer) {
|
|
startSlaverXfer(iic->ip);
|
|
}
|
|
iic->flags |= 1 << (sid + 8);
|
|
}
|
|
|
|
/* I2C Slave event handler */
|
|
void Chip_I2C_SlaveStateHandler(I2C_ID_T id)
|
|
{
|
|
int ret;
|
|
struct i2c_interface *iic = &i2c[id];
|
|
|
|
/* Get the currently addressed slave */
|
|
if (!iic->sXfer) {
|
|
struct i2c_slave_interface *si2c;
|
|
|
|
I2C_SLAVE_ID sid = getSlaveIndex(iic->ip);
|
|
si2c = &i2c_slave[id][sid];
|
|
iic->sXfer = si2c->xfer;
|
|
iic->sEvent = si2c->event;
|
|
}
|
|
|
|
iic->sXfer->slaveAddr |= iic->mXfer != 0;
|
|
ret = handleSlaveXferState(iic->ip, iic->sXfer);
|
|
if (ret) {
|
|
if (iic->sXfer->status == I2C_STATUS_DONE) {
|
|
iic->sXfer = 0;
|
|
}
|
|
iic->sEvent(id, (I2C_EVENT_T) ret);
|
|
}
|
|
}
|
|
|
|
/* Disable I2C device */
|
|
void Chip_I2C_Disable(I2C_ID_T id)
|
|
{
|
|
LPC_I2Cx(id)->CONCLR = I2C_I2CONCLR_I2ENC;
|
|
}
|
|
|
|
/* State change checking */
|
|
int Chip_I2C_IsStateChanged(I2C_ID_T id)
|
|
{
|
|
return (LPC_I2Cx(id)->CONSET & I2C_CON_SI) != 0;
|
|
}
|