mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-12-24 22:13:52 +01:00
cpu/cc26x0/i2c: Rework and add error handling
This commit cleans up magic number and defines bitfields. Adds error codes for ADDR/DATA NACK and ARBLOSS Adds error handling, it corrects when an error occurs Protects from flags that could lockup the bus
This commit is contained in:
parent
44ecf0e7f2
commit
4c9890b269
@ -55,6 +55,138 @@ typedef struct {
|
||||
reg32_t MCR; /**< master configuration */
|
||||
} i2c_regs_t;
|
||||
|
||||
/**
|
||||
* @brief I2C master function enable
|
||||
* @details 0h = Master mode is disabled.
|
||||
* 1h = Master mode is enabled
|
||||
*/
|
||||
#define MCR_MFE 0x00000010
|
||||
|
||||
/**
|
||||
* @brief SCL clock period set to 100 kHZ
|
||||
* @details {PERDMACLK / [2 × (SCL_LP + SCL_HP) × SCL_CLK]} – 1
|
||||
* with SCL_LP==6 && SCL_HP==4 use 0x17 for 100kHZ with 48MHZ CPU clock
|
||||
* This field specifies the period of the SCL clock.
|
||||
* SCL_PRD = 2*(1+TPR)*(SCL_LP + SCL_HP)*CLK_PRD
|
||||
* where:
|
||||
* SCL_PRD is the SCL line period (I2C clock).
|
||||
* TPR is the timer period register value (range of 1 to 127)
|
||||
* SCL_LP is the SCL low period (fixed at 6).
|
||||
* SCL_HP is the SCL high period (fixed at 4).
|
||||
* CLK_PRD is the system clock period in ns.
|
||||
*/
|
||||
#define MTPR_TPR_100KHZ 0x00000017
|
||||
|
||||
/**
|
||||
* @brief Receive or Send
|
||||
* @details This bit-field specifies if the next operation is a receive (high) or a
|
||||
* transmit/send (low) from the addressed slave SA.
|
||||
* 0h = Transmit/send data to slave
|
||||
* 1h = Receive data from slave
|
||||
*/
|
||||
#define MSA_RS 0x00000001
|
||||
|
||||
/**
|
||||
* @brief Bus busy
|
||||
* @details 0: The I2C bus is idle.
|
||||
* 1: The I2C bus is busy.
|
||||
* The bit changes based on the MCTRL.START and MCTRL.STOP
|
||||
* conditions.
|
||||
*/
|
||||
#define MSTAT_BUSBSY 0x00000040
|
||||
|
||||
/**
|
||||
* @brief I2C idle
|
||||
* @details 0: The I2C controller is not idle.
|
||||
* 1: The I2C controller is idle.
|
||||
*/
|
||||
#define MSTAT_IDLE 0x00000020
|
||||
|
||||
|
||||
/**
|
||||
* @brief Arbitration lost
|
||||
* @details 0: The I2C controller won arbitration.
|
||||
* 1: The I2C controller lost arbitration.
|
||||
*/
|
||||
#define MSTAT_ARBLST 0x00000010
|
||||
|
||||
/**
|
||||
* @brief Data Was Not Acknowledge
|
||||
* @details 0: The transmitted data was acknowledged.
|
||||
* 1: The transmitted data was not acknowledged.
|
||||
*/
|
||||
#define MSTAT_DATACK_N 0x00000008
|
||||
|
||||
/**
|
||||
* @brief Address Was Not Acknowledge
|
||||
* @details 0: The transmitted address was acknowledged.
|
||||
* 1: The transmitted address was not acknowledged.
|
||||
*/
|
||||
#define MSTAT_ADRACK_N 0x00000004
|
||||
|
||||
/**
|
||||
* @brief Error
|
||||
* @details 0: No error was detected on the last operation.
|
||||
* 1: An error occurred on the last operation.
|
||||
*/
|
||||
#define MSTAT_ERR 0x00000002
|
||||
|
||||
/**
|
||||
* @brief I2C busy
|
||||
* @details 0: The controller is idle.
|
||||
* 1: The controller is busy.
|
||||
* When this bit-field is set, the other status bits are not valid.
|
||||
@note The I2C controller requires four SYSBUS clock cycles to
|
||||
* assert the BUSY status after I2C master operation has been initiated
|
||||
* through MCTRL register.
|
||||
* Hence after programming MCTRL register, application is requested
|
||||
* to wait for four SYSBUS clock cycles before issuing a controller
|
||||
* status inquiry through MSTAT register.
|
||||
* Any prior inquiry would result in wrong status being reported.
|
||||
*/
|
||||
#define MSTAT_BUSY 0x00000001
|
||||
|
||||
/**
|
||||
* @brief Data acknowledge enable
|
||||
* @details 0: The received data byte is not acknowledged automatically by the
|
||||
* master.
|
||||
* 1: The received data byte is acknowledged automatically by the
|
||||
* master.
|
||||
* This bit-field must be cleared when the I2C bus controller requires
|
||||
* no further data to be transmitted from the slave transmitter.
|
||||
* 0h = Disable acknowledge
|
||||
* 1h = Enable acknowledge
|
||||
*/
|
||||
#define MCTRL_ACK 0x00000008
|
||||
|
||||
/**
|
||||
* @brief This bit-field determines if the cycle stops at the end of the data
|
||||
cycle or continues on to a repeated START condition
|
||||
* @details 0: The controller does not generate the Stop condition.
|
||||
* 1: The controller generates the Stop condition.
|
||||
* 0h = Disable STOP
|
||||
* 1h = Enable STOP
|
||||
*/
|
||||
#define MCTRL_STOP 0x00000004
|
||||
|
||||
/**
|
||||
* @brief This bit-field generates the Start or Repeated Start condition
|
||||
* @details 0: The controller does not generate the Start condition.
|
||||
* 1: The controller generates the Start condition.
|
||||
* 0h = Disable START
|
||||
* 1h = Enable START
|
||||
*/
|
||||
#define MCTRL_START 0x00000002
|
||||
|
||||
/**
|
||||
* @brief I2C master enable
|
||||
* @details 0: The master is disabled.
|
||||
* 1: The master is enabled to transmit or receive data.
|
||||
* 0h = Disable Master
|
||||
* 1h = Enable Master
|
||||
*/
|
||||
#define MCTRL_RUN 0x00000001
|
||||
|
||||
/** @ingroup cpu_specific_peripheral_memory_map
|
||||
* @{
|
||||
*/
|
||||
|
||||
@ -13,6 +13,8 @@
|
||||
*
|
||||
* @file
|
||||
* @brief Low-level I2C driver implementation
|
||||
* @note This CPU has weak pullups, external pullup resistors may be
|
||||
* required.
|
||||
*
|
||||
* @author Kaspar Schleiser <kaspar@schleiser.de>
|
||||
*
|
||||
@ -31,22 +33,71 @@
|
||||
#define ENABLE_DEBUG 0
|
||||
#include "debug.h"
|
||||
|
||||
#if ENABLE_DEBUG
|
||||
#define PREG(x) DEBUG("%s=0x%08x\n", #x, (unsigned)x);
|
||||
#else
|
||||
#define PREG(x)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Array holding one pre-initialized mutex for each I2C device
|
||||
* @brief Mutex lock for the only available I2C periph
|
||||
* @note If multiple I2C devices are added locks must be an array for each one.
|
||||
*/
|
||||
static mutex_t _locks[I2C_NUMOF];
|
||||
static mutex_t _lock;
|
||||
|
||||
static int _check_errors(void)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
/* The reference manual (SWCU117H) is ambiguous on how to wait:
|
||||
*
|
||||
* 1. 21.4 8. says "wait until BUSBUSY is cleared"
|
||||
* 2. command flow diagrams (e.g., 21.3.5.1) indicate to wait while
|
||||
* BUSY is set
|
||||
*
|
||||
* (3. 21.5.1.10 says BUSY is only valid after 4 SYSBUS clock cycles)
|
||||
*
|
||||
* Waiting first for cleared IDLE and then for cleared BUSY works fine.
|
||||
*/
|
||||
|
||||
/* wait for transfer to be complete, this also could be a few nops... */
|
||||
while (I2C->MSTAT & MSTAT_IDLE) {}
|
||||
while (I2C->MSTAT & MSTAT_BUSY) {}
|
||||
/* check if there was an error */
|
||||
if (I2C->MSTAT & MSTAT_ERR) {
|
||||
DEBUG("%s\n", __FUNCTION__);
|
||||
PREG(I2C->MSTAT);
|
||||
ret = -ETIMEDOUT;
|
||||
if (I2C->MSTAT & MSTAT_ADRACK_N) {
|
||||
DEBUG("ADDRESS NACK\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
else if (I2C->MSTAT & MSTAT_DATACK_N) {
|
||||
DEBUG("DATA NACK\n");
|
||||
ret = -EIO;
|
||||
}
|
||||
else if (I2C->MSTAT & MSTAT_ARBLST) {
|
||||
DEBUG("ARBITRATION LOSS\n");
|
||||
ret = -EAGAIN;
|
||||
}
|
||||
/*
|
||||
* If a non-NACK error occurs we must reinit or lock up.
|
||||
* dev = 0 since it is the only one, if more are added it should be
|
||||
* the dev num, this is done to avoid passing in arguments and
|
||||
* increasing code size.
|
||||
*/
|
||||
i2c_init(0);
|
||||
return ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void i2c_init(i2c_t devnum)
|
||||
{
|
||||
(void)devnum;
|
||||
assert(devnum < I2C_NUMOF);
|
||||
|
||||
/* Make sure everything is shut off in case of reinit */
|
||||
PRCM->PDCTL0SERIAL = 0;
|
||||
I2C->MCR = 0;
|
||||
PRCM->I2CCLKGR = 0;
|
||||
|
||||
/* enable SERIAL power domain */
|
||||
PRCM->PDCTL0SERIAL = 1;
|
||||
while (!(PRCM->PDSTAT0 & PDSTAT0_SERIAL_ON)) {}
|
||||
@ -67,25 +118,25 @@ void i2c_init(i2c_t devnum)
|
||||
| IOCFG_PULLCTL_UP);
|
||||
|
||||
/* initialize I2C master */
|
||||
I2C->MCR = 0x00000010;
|
||||
I2C->MCR = MCR_MFE;
|
||||
|
||||
/* configure clock speed */
|
||||
/*{PERDMACLK / [2 × (SCL_LP + SCL_HP) × SCL_CLK]} – 1*/
|
||||
/* with SCL_LP==6 && SCL_HP==4 use 0x17 for 100kHZ with 48MHZ CPU clock */
|
||||
I2C->MTPR = 0x00000017;
|
||||
/* configure clock speed
|
||||
* {PERDMACLK / [2 × (SCL_LP + SCL_HP) × SCL_CLK]} – 1
|
||||
* with SCL_LP==6 && SCL_HP==4 use 0x17 for 100kHZ with 48MHZ CPU clock */
|
||||
I2C->MTPR = MTPR_TPR_100KHZ;
|
||||
}
|
||||
|
||||
int i2c_acquire(i2c_t dev)
|
||||
{
|
||||
assert(dev < I2C_NUMOF);
|
||||
mutex_lock(&_locks[dev]);
|
||||
mutex_lock(&_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int i2c_release(i2c_t dev)
|
||||
{
|
||||
assert(dev < I2C_NUMOF);
|
||||
mutex_unlock(&_locks[dev]);
|
||||
mutex_unlock(&_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -93,9 +144,14 @@ int i2c_read_bytes(i2c_t dev, uint16_t addr,
|
||||
void *data, size_t len, uint8_t flags)
|
||||
{
|
||||
(void)dev;
|
||||
DEBUG("i2c_read_bytes() %u\n", len);
|
||||
int ret = 0;
|
||||
char *bufpos = data;
|
||||
|
||||
DEBUG("%s %u\n", __FUNCTION__, len);
|
||||
PREG(I2C->MSTAT);
|
||||
|
||||
assert(dev < I2C_NUMOF);
|
||||
assert(data != NULL);
|
||||
|
||||
/* Check for unsupported operations */
|
||||
if (flags & I2C_ADDR10) {
|
||||
@ -103,21 +159,25 @@ int i2c_read_bytes(i2c_t dev, uint16_t addr,
|
||||
}
|
||||
|
||||
/* Check for wrong arguments given */
|
||||
if (data == NULL || len == 0) {
|
||||
if (len == 0) {
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!(I2C->MSTAT & MSTAT_BUSBSY) && (flags & I2C_NOSTART)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
char *bufpos = data;
|
||||
/* Sequence may be omitted in a single master system */
|
||||
while (I2C->MSTAT & MSTAT_BUSY) {}
|
||||
|
||||
I2C->MSA = ((uint32_t)addr << 1) | 0x1;
|
||||
I2C->MSA = ((uint32_t)addr << 1) | MSA_RS;
|
||||
|
||||
while (len--) {
|
||||
DEBUG("LOOP %u\n", len);
|
||||
/* setup transfer */
|
||||
uint32_t mctrl = 0x1; /* RUN */
|
||||
uint32_t mctrl = MCTRL_RUN;
|
||||
if (!(flags & I2C_NOSTART)) {
|
||||
DEBUG("START\n");
|
||||
mctrl |= 0x2; /* START */
|
||||
mctrl |= MCTRL_START;
|
||||
|
||||
/* make note not to generate START from second byte onwards */
|
||||
flags |= I2C_NOSTART;
|
||||
@ -126,25 +186,21 @@ int i2c_read_bytes(i2c_t dev, uint16_t addr,
|
||||
/* after last byte, generate STOP unless told not to */
|
||||
if (!len && !(flags & I2C_NOSTOP)) {
|
||||
DEBUG("STOP\n");
|
||||
mctrl |= 0x4;
|
||||
mctrl |= MCTRL_STOP;
|
||||
}
|
||||
else {
|
||||
DEBUG("ACK\n");
|
||||
mctrl |= 0x8; /* ACK */
|
||||
mctrl |= MCTRL_ACK;
|
||||
}
|
||||
|
||||
while (I2C->MSTAT & MSTAT_BUSY) {}
|
||||
/* initiate transfer */
|
||||
I2C->MCTRL = mctrl;
|
||||
|
||||
/* wait for transfer to be complete */
|
||||
while (I2C->MSTAT & 0x20) {} /* BUSBSY */
|
||||
while (I2C->MSTAT & 0x1) {} /* BUSY */
|
||||
|
||||
/* check if there was an error */
|
||||
if (I2C->MSTAT & 0x2) { /* ERR */
|
||||
DEBUG("EIO\n");
|
||||
PREG(I2C->MSTAT);
|
||||
return -EIO;
|
||||
ret = _check_errors();
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
/* copy next byte from I2C data register */
|
||||
DEBUG("IN=0x%02x\n", (unsigned)I2C->MDR);
|
||||
@ -158,35 +214,44 @@ int i2c_write_bytes(i2c_t dev, uint16_t addr, const void *data, size_t len,
|
||||
uint8_t flags)
|
||||
{
|
||||
(void)dev;
|
||||
DEBUG("i2c_write_bytes() %u\n", len);
|
||||
assert(dev < I2C_NUMOF);
|
||||
|
||||
int ret = 0;
|
||||
const unsigned char *bufpos = data;
|
||||
|
||||
DEBUG("%s %u\n", __FUNCTION__, len);
|
||||
PREG(I2C->MSTAT);
|
||||
|
||||
assert(dev < I2C_NUMOF);
|
||||
assert(data != NULL);
|
||||
|
||||
/* Check for unsupported operations */
|
||||
if (flags & I2C_ADDR10) {
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
/* Check for wrong arguments given */
|
||||
if (data == NULL || len == 0) {
|
||||
if (len == 0) {
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!(I2C->MSTAT & MSTAT_BUSBSY) && (flags & I2C_NOSTART)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
PREG(I2C->MSTAT);
|
||||
|
||||
/* Since write is 0 we just need shift the address in */
|
||||
I2C->MSA = (uint32_t)addr << 1;
|
||||
|
||||
/* Sequence may be omitted in a single master system. */
|
||||
while (I2C->MSTAT & MSTAT_BUSY) {}
|
||||
|
||||
while (len--) {
|
||||
DEBUG("LOOP %u 0x%2x\n", len, (unsigned)*bufpos);
|
||||
/* copy next byte into I2C data register */
|
||||
I2C->MDR = *bufpos++;
|
||||
|
||||
/* setup transfer */
|
||||
uint32_t mctrl = 0x1; /* RUN */
|
||||
uint32_t mctrl = MCTRL_RUN;
|
||||
if (!(flags & I2C_NOSTART)) {
|
||||
DEBUG("START\n");
|
||||
mctrl |= 0x2; /* START */
|
||||
mctrl |= MCTRL_START;
|
||||
|
||||
/* make note not to generate START from second byte onwards */
|
||||
flags |= I2C_NOSTART;
|
||||
@ -195,34 +260,18 @@ int i2c_write_bytes(i2c_t dev, uint16_t addr, const void *data, size_t len,
|
||||
/* after last byte, generate STOP unless told not to */
|
||||
if (!len && !(flags & I2C_NOSTOP)) {
|
||||
DEBUG("STOP\n");
|
||||
mctrl |= 0x4;
|
||||
mctrl |= MCTRL_STOP;
|
||||
}
|
||||
|
||||
/* initiate transfer */
|
||||
I2C->MCTRL = mctrl;
|
||||
|
||||
/* The reference manual (SWCU117H) is ambiguous on how to wait:
|
||||
*
|
||||
* 1. 21.4 8. says "wait until BUSBUSY is cleared"
|
||||
* 2. command flow diagrams (e.g., 21.3.5.1) indicate to wait while
|
||||
* BUSY is set
|
||||
*
|
||||
* (3. 21.5.1.10 says BUSY is only valid after 4 SYSBUS clock cycles)
|
||||
*
|
||||
* Waiting first for cleared BUSBUSY and then for cleared BUSY works fine.
|
||||
*/
|
||||
|
||||
/* wait for transfer to be complete */
|
||||
while (I2C->MSTAT & 0x20) {} /* BUSBSY */
|
||||
while (I2C->MSTAT & 0x1) {} /* BUSY */
|
||||
|
||||
/* check if there was an error */
|
||||
if (I2C->MSTAT & 0x2) { /* ERR */
|
||||
DEBUG("EIO\n");
|
||||
PREG(I2C->MSTAT);
|
||||
return -EIO;
|
||||
ret = _check_errors();
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user