xtensa/esp32: Adds I2C polled support.

This commit is contained in:
Sara Souza 2021-06-10 14:56:39 -03:00 committed by Xiang Xiao
parent bafac8b560
commit 7300bc8f1c

View File

@ -35,6 +35,7 @@
#include <errno.h>
#include <debug.h>
#include <time.h>
#include <sys/time.h>
#include <nuttx/arch.h>
#include <nuttx/irq.h>
@ -71,6 +72,12 @@
((_ack_val) << 10) + \
(_bytes))
/* Helper */
#ifdef CONFIG_I2C_POLLED
#define TIMESPEC_TO_US(sec, nano) ((sec * USEC_PER_SEC) + (nano / NSEC_PER_USEC))
#endif
/* Default option */
#define I2C_FIFO_SIZE (255)
@ -107,7 +114,9 @@ enum esp32_i2cstate_e
I2CSTATE_IDLE = 0,
I2CSTATE_PROC,
I2CSTATE_STOP,
#ifndef CONFIG_I2C_POLLED
I2CSTATE_FINISH
#endif
};
/* I2C hardware command */
@ -160,9 +169,11 @@ struct esp32_i2c_config_s
uint8_t scl_pin; /* GPIO configuration for SCL as SCL */
uint8_t sda_pin; /* GPIO configuration for SDA as SDA */
#ifndef CONFIG_I2C_POLLED
uint8_t cpu; /* CPU ID */
uint8_t periph; /* peripher ID */
uint8_t periph; /* Peripheral ID */
uint8_t irq; /* Interrupt ID */
#endif
uint32_t clk_bit; /* Clock enable bit */
uint32_t rst_bit; /* I2C reset bit */
@ -183,9 +194,12 @@ struct esp32_i2c_priv_s
/* Port configuration */
const struct esp32_i2c_config_s *config;
int refs; /* Referernce count */
int refs; /* Reference count */
sem_t sem_excl; /* Mutual exclusion semaphore */
#ifndef CONFIG_I2C_POLLED
sem_t sem_isr; /* Interrupt wait semaphore */
#endif
/* I2C work state (see enum esp32_i2cstate_e) */
@ -196,11 +210,13 @@ struct esp32_i2c_priv_s
uint8_t msgid; /* Current message ID */
ssize_t bytes; /* Processed data bytes */
#ifndef CONFIG_I2C_POLLED
int cpuint; /* CPU interrupt assigned to this I2C */
#endif
uint32_t error; /* I2C transform error */
bool ready_read; /* If I2C has can read date */
bool ready_read; /* If I2C has read data */
uint32_t clk_freq; /* Current I2C Clock frequency */
@ -227,6 +243,11 @@ static void esp32_i2c_deinit(FAR struct esp32_i2c_priv_s *priv);
static int esp32_i2c_transfer(FAR struct i2c_master_s *dev,
FAR struct i2c_msg_s *msgs,
int count);
static inline void esp32_i2c_process(struct esp32_i2c_priv_s *priv,
uint32_t status);
#ifdef CONFIG_I2C_POLLED
static int esp32_i2c_polling_waitdone(FAR struct esp32_i2c_priv_s *priv);
#endif
#ifdef CONFIG_I2C_RESET
static int esp32_i2c_reset(FAR struct i2c_master_s *dev);
#endif
@ -265,9 +286,11 @@ static const struct esp32_i2c_config_s esp32_i2c0_config =
.clk_freq = I2C_CLK_FREQ_DEF,
.scl_pin = CONFIG_ESP32_I2C0_SCLPIN,
.sda_pin = CONFIG_ESP32_I2C0_SDAPIN,
#ifndef CONFIG_I2C_POLLED
.cpu = 0,
.periph = ESP32_PERIPH_I2C_EXT0,
.irq = ESP32_IRQ_I2C_EXT0,
#endif
.clk_bit = DPORT_I2C_EXT0_CLK_EN,
.rst_bit = DPORT_I2C_EXT0_RST,
.scl_insig = I2CEXT0_SCL_IN_IDX,
@ -296,9 +319,11 @@ static const struct esp32_i2c_config_s esp32_i2c1_config =
.clk_freq = I2C_CLK_FREQ_DEF,
.scl_pin = CONFIG_ESP32_I2C1_SCLPIN,
.sda_pin = CONFIG_ESP32_I2C1_SDAPIN,
#ifndef CONFIG_I2C_POLLED
.cpu = 0,
.periph = ESP32_PERIPH_I2C_EXT1,
.irq = ESP32_IRQ_I2C_EXT1,
#endif
.clk_bit = DPORT_I2C_EXT1_CLK_EN,
.rst_bit = DPORT_I2C_EXT1_RST,
.scl_insig = I2CEXT1_SCL_IN_IDX,
@ -415,16 +440,29 @@ static void esp32_i2c_sendstart(FAR struct esp32_i2c_priv_s *priv)
{
struct i2c_msg_s *msg = &priv->msgv[priv->msgid];
/* Load a start cmd, write and end in controller. */
esp32_i2c_set_reg(priv, I2C_COMD0_OFFSET,
I2C_BASE_CMD(I2C_CMD_RESTART, 0));
esp32_i2c_set_reg(priv, I2C_COMD1_OFFSET,
I2C_SEND_CMD(I2C_CMD_WRITE, 1, 1));
esp32_i2c_set_reg(priv, I2C_COMD2_OFFSET,
I2C_BASE_CMD(I2C_CMD_END, 0));
/* Load the slave address and the flags to the FIFO. */
esp32_i2c_set_reg(priv, I2C_DATA_OFFSET, (msg->addr << 1) |
(msg->flags & I2C_M_READ));
/* Enable interrupts to detect the execution of an end cmd and
* multiple errors.
*/
esp32_i2c_set_reg(priv, I2C_INT_ENA_OFFSET, I2C_END_DETECT_INT_ENA |
I2C_INT_ERR_EN_BITS);
/* Start sending the slave address and hold line. */
esp32_i2c_set_reg_bits(priv, I2C_CTR_OFFSET, I2C_TRANS_START_M);
}
@ -444,11 +482,15 @@ static void esp32_i2c_senddata(FAR struct esp32_i2c_priv_s *priv)
n = n < I2C_FIFO_SIZE ? n : I2C_FIFO_SIZE;
/* Load a write operation */
esp32_i2c_set_reg(priv, I2C_COMD0_OFFSET,
I2C_SEND_CMD(I2C_CMD_WRITE, 1, n));
esp32_i2c_set_reg(priv, I2C_COMD1_OFFSET,
I2C_BASE_CMD(I2C_CMD_END, 0));
/* Transfer the data from the msg buffer to the TX FIFO */
for (i = 0; i < n; i ++)
{
esp32_i2c_set_reg(priv, I2C_DATA_OFFSET,
@ -457,6 +499,10 @@ static void esp32_i2c_senddata(FAR struct esp32_i2c_priv_s *priv)
priv->bytes += n;
/* Enable interrupts to detect the execution of an end cmd and
* multiple errors.
*/
esp32_i2c_set_reg(priv, I2C_INT_ENA_OFFSET, I2C_END_DETECT_INT_ENA |
I2C_INT_ERR_EN_BITS);
esp32_i2c_set_reg_bits(priv, I2C_CTR_OFFSET, I2C_TRANS_START_M);
@ -466,7 +512,7 @@ static void esp32_i2c_senddata(FAR struct esp32_i2c_priv_s *priv)
* Name: esp32_i2c_recvdata
*
* Description:
* Receive I2C data
* Transfer the received data from the RX FIFO to the message buffer.
*
****************************************************************************/
@ -490,8 +536,9 @@ static void esp32_i2c_recvdata(struct esp32_i2c_priv_s *priv)
* Name: esp32_i2c_startrecv
*
* Description:
* Configure I2C to prepare receiving data and it will create an interrupt
* to receive real data
* Configure I2C to prepare receiving data and it will enable an interrupt
* to detect the end of the receiving operation and to load the content
* into the RX buffer.
*
****************************************************************************/
@ -512,11 +559,17 @@ static void esp32_i2c_startrecv(struct esp32_i2c_priv_s *priv)
ack_value = 1;
}
/* Load read and end cmds */
esp32_i2c_set_reg(priv, I2C_COMD0_OFFSET,
I2C_RECV_CMD(I2C_CMD_READ, ack_value, n));
esp32_i2c_set_reg(priv, I2C_COMD1_OFFSET,
I2C_BASE_CMD(I2C_CMD_END, 0));
/* Enable interrupts to detect the execution of an end cmd and
* multiple errors.
*/
esp32_i2c_set_reg(priv, I2C_INT_ENA_OFFSET, I2C_END_DETECT_INT_ENA |
I2C_INT_ERR_EN_BITS);
esp32_i2c_set_reg_bits(priv, I2C_CTR_OFFSET, I2C_TRANS_START_M);
@ -532,6 +585,10 @@ static void esp32_i2c_startrecv(struct esp32_i2c_priv_s *priv)
static void esp32_i2c_sendstop(struct esp32_i2c_priv_s *priv)
{
/* Load a stop cmd and enable the interrupts that indicate end of a stop
* cmd or error.
*/
esp32_i2c_set_reg(priv, I2C_COMD0_OFFSET, I2C_BASE_CMD(I2C_CMD_STOP, 0));
esp32_i2c_set_reg(priv, I2C_INT_ENA_OFFSET, I2C_TRANS_COMPLETE_INT_ENA |
I2C_INT_ERR_EN_BITS);
@ -656,24 +713,146 @@ static void esp32_i2c_reset_fsmc(FAR struct esp32_i2c_priv_s *priv)
* Name: esp32_i2c_sem_waitdone
*
* Description:
* Wait for a transfer to complete
* Wait for a transfer to complete using an interruptible semaphore which
* in unlocked from within the ISR context. This function is only used in
* interrupt driven mode.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
****************************************************************************/
#ifndef CONFIG_I2C_POLLED
static int esp32_i2c_sem_waitdone(FAR struct esp32_i2c_priv_s *priv)
{
int ret;
struct timespec abstime;
/* Get the current absolute time and adds a offset as timeout */
clock_gettime(CLOCK_REALTIME, &abstime);
abstime.tv_sec += 10;
abstime.tv_nsec += 0;
/* Wait on ISR semaphore */
ret = nxsem_timedwait_uninterruptible(&priv->sem_isr, &abstime);
return ret;
}
#endif
/****************************************************************************
* Name: esp32_i2c_polling_waitdone
*
* Description:
* Wait for a transfer to complete by polling status interrupt registers,
* which indicates the status of the I2C operations. This function is only
* used in polling driven mode.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
*
* Returned Values:
* Zero (OK) is returned on successfull transfer. -ETIMEDOUT is returned
* in case a transfer didn't finish within the timeout interval. And ERROR
* is returned in case of any I2C error during the transfer has happened.
*
****************************************************************************/
#ifdef CONFIG_I2C_POLLED
static int esp32_i2c_polling_waitdone(FAR struct esp32_i2c_priv_s *priv)
{
int ret;
struct timespec current_time;
struct timespec timeout;
uint64_t current_us;
uint64_t timeout_us;
uint32_t status = 0;
/* Get the current absolute time and add an offset as timeout.
* Preferable to use monotonic, so in case the time changes,
* the time reference is kept, i.e., current time can't jump
* forward and backwards.
*/
#ifdef CONFIG_CLOCK_MONOTONIC
clock_gettime(CLOCK_MONOTONIC, &current_time);
#else
clock_gettime(CLOCK_REALTIME, &current_time);
#endif
timeout.tv_sec = current_time.tv_sec + 10;
timeout.tv_nsec = current_time.tv_nsec + 0;
current_us = TIMESPEC_TO_US(current_time.tv_sec, current_time.tv_nsec);
timeout_us = TIMESPEC_TO_US(timeout.tv_sec, timeout.tv_nsec);
/* Loop while a transfer is in progress
* and an error didn't occur within the timeout
*/
while ((current_us < timeout_us) && (priv->error == 0))
{
/* Check if any interrupt triggered, clear them
* process the operation.
*/
status = esp32_i2c_get_reg(priv, I2C_INT_STATUS_OFFSET);
if (status != 0)
{
/* Check if the stop operation ended. Don't use
* I2CSTATE_FINISH, because it is set before the stop
* signal really ends. This works for interrupts because
* the i2c_state is checked in the next interrupt when
* stop signal has concluded. This is not the case of
* polling.
*/
if (status & I2C_TRANS_COMPLETE_INT_ST_M)
{
esp32_i2c_set_reg(priv, I2C_INT_CLR_OFFSET, status);
break;
}
esp32_i2c_set_reg(priv, I2C_INT_CLR_OFFSET, status);
esp32_i2c_process(priv, status);
}
/* Update current time */
#ifdef CONFIG_CLOCK_MONOTONIC
clock_gettime(CLOCK_MONOTONIC, &current_time);
#else
clock_gettime(CLOCK_REALTIME, &current_time);
#endif
current_us = TIMESPEC_TO_US(current_time.tv_sec, current_time.tv_nsec);
}
/* Return a negated value in case of timeout, and in the other scenarios
* return a positive value.
* The transfer function will check the status of priv to check the other
* scenarios.
*/
if (current_us >= timeout_us)
{
ret = -ETIMEDOUT;
}
else if (priv->error)
{
ret = ERROR;
}
else
{
ret = OK;
}
/* Disable all interrupts */
esp32_i2c_set_reg(priv, I2C_INT_ENA_OFFSET, 0);
return ret;
}
#endif
/****************************************************************************
* Name: esp32_i2c_sem_wait
@ -712,7 +891,9 @@ static void esp32_i2c_sem_post(struct esp32_i2c_priv_s *priv)
static void esp32_i2c_sem_destroy(FAR struct esp32_i2c_priv_s *priv)
{
nxsem_destroy(&priv->sem_excl);
#ifndef CONFIG_I2C_POLLED
nxsem_destroy(&priv->sem_isr);
#endif
}
/****************************************************************************
@ -731,8 +912,10 @@ static inline void esp32_i2c_sem_init(FAR struct esp32_i2c_priv_s *priv)
* priority inheritance enabled.
*/
#ifndef CONFIG_I2C_POLLED
nxsem_init(&priv->sem_isr, 0, 0);
nxsem_set_protocol(&priv->sem_isr, SEM_PRIO_NONE);
#endif
}
/****************************************************************************
@ -763,16 +946,26 @@ static int esp32_i2c_transfer(FAR struct i2c_master_s *dev,
return ret;
}
/* If previous state is different than idle,
* reset the fsmc to the idle state.
*/
if (priv->i2cstate != I2CSTATE_IDLE)
{
esp32_i2c_reset_fsmc(priv);
priv->i2cstate = I2CSTATE_IDLE;
}
/* Transfer the messages to the internal struct
* and loop count times to make all transfers.
*/
priv->msgv = msgs;
for (i = 0; i < count; i++)
{
/* Clear TX and RX FIFOs. */
esp32_i2c_reset_fifo(priv);
priv->bytes = 0;
@ -781,6 +974,8 @@ static int esp32_i2c_transfer(FAR struct i2c_master_s *dev,
priv->error = 0;
priv->i2cstate = I2CSTATE_PROC;
/* Set the SCLK frequency for the current msg. */
esp32_i2c_init_clock(priv, msgs[i].frequency);
/* Reset I2C trace logic */
@ -790,10 +985,15 @@ static int esp32_i2c_transfer(FAR struct i2c_master_s *dev,
esp32_i2c_traceevent(priv, I2CEVENT_SENDADDR, msgs[i].addr,
esp32_i2c_get_reg(priv, I2C_SR_OFFSET));
/* Send the start cmd */
esp32_i2c_sendstart(priv);
#ifndef CONFIG_I2C_POLLED
if (esp32_i2c_sem_waitdone(priv) < 0)
{
/* Timed out - transfer was not completed within the timeout */
ret = -ETIMEDOUT;
break;
}
@ -801,15 +1001,41 @@ static int esp32_i2c_transfer(FAR struct i2c_master_s *dev,
{
if (priv->error)
{
/* An error occurred */
ret = -EIO;
break;
}
else
{
/* Successful transfer, update the I2C state to idle */
priv->i2cstate = I2CSTATE_IDLE;
ret = OK;
}
}
#else
ret = esp32_i2c_polling_waitdone(priv);
if (ret < 0)
{
if (ret == -ETIMEDOUT)
{
break;
}
else
{
ret = -EIO;
break;
}
}
else
{
/* Successful transfer, update the I2C state to idle */
priv->i2cstate = I2CSTATE_IDLE;
ret = OK;
}
#endif
}
/* Dump the trace result */
@ -1048,28 +1274,71 @@ static void esp32_i2c_tracedump(struct esp32_i2c_priv_s *priv)
*
****************************************************************************/
#ifndef CONFIG_I2C_POLLED
static int esp32_i2c_irq(int cpuint, void *context, FAR void *arg)
{
struct esp32_i2c_priv_s *priv = (struct esp32_i2c_priv_s *)arg;
struct i2c_msg_s *msg = &priv->msgv[priv->msgid];
uint32_t status = esp32_i2c_get_reg(priv, I2C_INT_STATUS_OFFSET);
/* Get the interrupt status and clear the interrupts that
* triggered.
*/
uint32_t status = esp32_i2c_get_reg(priv, I2C_INT_STATUS_OFFSET);
esp32_i2c_set_reg(priv, I2C_INT_CLR_OFFSET, status);
esp32_i2c_process(priv, status);
return OK;
}
#endif
/****************************************************************************
* Name: esp32_i2c_process
*
* Description:
* This routine manages the transfer. It's called after some specific
* commands from the I2C controller are executed or in case of errors.
* It's responsible for writing/reading operations and transferring data
* from/to FIFO.
* It's called in the interrupt and polled driven mode.
*
* Parameters:
* priv - Pointer to the internal driver state structure.
* status - The current interrupt status register.
*
****************************************************************************/
static inline void esp32_i2c_process(struct esp32_i2c_priv_s *priv,
uint32_t status)
{
struct i2c_msg_s *msg = &priv->msgv[priv->msgid];
/* Check for any errors */
if (I2C_INT_ERR_EN_BITS & status)
{
/* Save the errors, register the error event, disable interrupts
* and release the semaphore to conclude the transfer.
*/
priv->error = status & I2C_INT_ERR_EN_BITS;
esp32_i2c_traceevent(priv, I2CEVENT_ERROR, priv->error,
esp32_i2c_get_reg(priv, I2C_SR_OFFSET));
esp32_i2c_set_reg(priv, I2C_INT_ENA_OFFSET, 0);
#ifndef CONFIG_I2C_POLLED
nxsem_post(&priv->sem_isr);
#endif
}
else
else /* If no error */
{
/* If a transfer has just initialized */
if (priv->i2cstate == I2CSTATE_PROC)
{
/* Check the flags to perform a read or write operation */
if (msg->flags & I2C_M_READ)
{
/* RX FIFO has available data */
if (priv->ready_read)
{
esp32_i2c_traceevent(priv, I2CEVENT_RCVBYTE, priv->bytes,
@ -1079,15 +1348,20 @@ static int esp32_i2c_irq(int cpuint, void *context, FAR void *arg)
priv->ready_read = 0;
}
/* Received all data. Finish the transaction
* and update the state I2C state.
*/
if (priv->bytes == msg->length)
{
esp32_i2c_traceevent(priv, I2CEVENT_STOP, msg->length,
esp32_i2c_get_reg(priv, I2C_SR_OFFSET));
esp32_i2c_sendstop(priv);
#ifndef CONFIG_I2C_POLLED
priv->i2cstate = I2CSTATE_FINISH;
#endif
}
else
else /* Start a receive operation */
{
esp32_i2c_traceevent(priv, I2CEVENT_RCVMODEEN, 0,
esp32_i2c_get_reg(priv, I2C_SR_OFFSET));
@ -1096,12 +1370,16 @@ static int esp32_i2c_irq(int cpuint, void *context, FAR void *arg)
priv->ready_read = 1;
}
}
else
else /* Write operation */
{
esp32_i2c_traceevent(priv, I2CEVENT_SENDBYTE, priv->bytes,
esp32_i2c_get_reg(priv, I2C_SR_OFFSET));
esp32_i2c_senddata(priv);
/* Finally sent the entire message. Update the I2C state to
* send a stop signal in the next interrupt.
*/
if (priv->bytes == msg->length)
{
priv->i2cstate = I2CSTATE_STOP;
@ -1110,19 +1388,27 @@ static int esp32_i2c_irq(int cpuint, void *context, FAR void *arg)
}
else if (priv->i2cstate == I2CSTATE_STOP)
{
/* Transmitted all data. Finish the transaction sending a stop
* and update the state I2C state.
*/
esp32_i2c_traceevent(priv, I2CEVENT_STOP, msg->length,
esp32_i2c_get_reg(priv, I2C_SR_OFFSET));
esp32_i2c_sendstop(priv);
#ifndef CONFIG_I2C_POLLED
priv->i2cstate = I2CSTATE_FINISH;
#endif
}
#ifndef CONFIG_I2C_POLLED
else if (priv->i2cstate == I2CSTATE_FINISH)
{
/* Disable all interrupts and release the semaphore */
esp32_i2c_set_reg(priv, I2C_INT_ENA_OFFSET, 0);
nxsem_post(&priv->sem_isr);
}
#endif
}
return 0;
}
/****************************************************************************
@ -1139,10 +1425,12 @@ static int esp32_i2c_irq(int cpuint, void *context, FAR void *arg)
FAR struct i2c_master_s *esp32_i2cbus_initialize(int port)
{
int ret;
irqstate_t flags;
struct esp32_i2c_priv_s *priv;
#ifndef CONFIG_I2C_POLLED
const struct esp32_i2c_config_s *config;
int ret;
#endif
switch (port)
{
@ -1160,8 +1448,6 @@ FAR struct i2c_master_s *esp32_i2cbus_initialize(int port)
return NULL;
}
config = priv->config;
flags = enter_critical_section();
if ((volatile int)priv->refs++ != 0)
@ -1171,6 +1457,8 @@ FAR struct i2c_master_s *esp32_i2cbus_initialize(int port)
return (struct i2c_master_s *)priv;
}
#ifndef CONFIG_I2C_POLLED
config = priv->config;
priv->cpuint = esp32_alloc_levelint(1);
if (priv->cpuint < 0)
{
@ -1196,6 +1484,7 @@ FAR struct i2c_master_s *esp32_i2cbus_initialize(int port)
}
up_enable_irq(priv->cpuint);
#endif
esp32_i2c_sem_init(priv);
@ -1236,11 +1525,13 @@ int esp32_i2cbus_uninitialize(FAR struct i2c_master_s *dev)
leave_critical_section(flags);
#ifndef CONFIG_I2C_POLLED
up_disable_irq(priv->cpuint);
esp32_detach_peripheral(priv->config->cpu,
priv->config->periph,
priv->cpuint);
esp32_free_cpuint(priv->cpuint);
#endif
esp32_i2c_deinit(priv);