i2c: i2c_xilinx_axi: Fix for target mode interrupt handling
When a target was registered with the controller but a controller-mode
request was performed, the driver failed to mask the interrupts that
were occurring as a result of the controller-mode transfer, causing an
interrupt flood. Ensure that all interrupts not handled by target mode
are handled as potential controller-mode events and acknowledged
properly.
Signed-off-by: Robert Hancock <robert.hancock@calian.com>
diff --git a/drivers/i2c/i2c_xilinx_axi.c b/drivers/i2c/i2c_xilinx_axi.c
index 58bf138..c940e8d 100644
--- a/drivers/i2c/i2c_xilinx_axi.c
+++ b/drivers/i2c/i2c_xilinx_axi.c
@@ -133,11 +133,12 @@
}
static void i2c_xilinx_axi_target_isr(const struct i2c_xilinx_axi_config *config,
- struct i2c_xilinx_axi_data *data, uint32_t int_status,
+ struct i2c_xilinx_axi_data *data, uint32_t *int_status,
uint32_t *ints_to_clear, uint32_t *int_enable)
{
- if (int_status & ISR_ADDR_TARGET) {
+ if (*int_status & ISR_ADDR_TARGET) {
LOG_DBG("Addressed as target");
+ *int_status &= ~ISR_ADDR_TARGET;
*int_enable &= ~ISR_ADDR_TARGET;
*int_enable |= ISR_NOT_ADDR_TARGET;
*ints_to_clear |= ISR_NOT_ADDR_TARGET;
@@ -166,7 +167,7 @@
sys_write32(cr, config->base + REG_CR);
}
}
- } else if (int_status & ISR_NOT_ADDR_TARGET) {
+ } else if (*int_status & ISR_NOT_ADDR_TARGET) {
LOG_DBG("Not addressed as target");
(*data->target_cfg->callbacks->stop)(data->target_cfg);
data->target_reading = false;
@@ -174,10 +175,12 @@
data->target_writing = false;
sys_write32(CR_EN, config->base + REG_CR);
+ *int_status &= ~ISR_NOT_ADDR_TARGET;
*int_enable &= ~I2C_XILINX_AXI_TARGET_INTERRUPTS;
*int_enable |= ISR_ADDR_TARGET;
*ints_to_clear |= ISR_ADDR_TARGET;
- } else if (int_status & ISR_RX_FIFO_FULL) {
+ } else if (data->target_writing && (*int_status & ISR_RX_FIFO_FULL)) {
+ *int_status &= ~ISR_RX_FIFO_FULL;
const uint8_t written_byte =
sys_read32(config->base + REG_RX_FIFO) & RX_FIFO_DATA_MASK;
@@ -189,31 +192,25 @@
cr |= CR_TXAK;
sys_write32(cr, config->base + REG_CR);
}
- } else if (int_status & ISR_TX_ERR_TARGET_COMP) {
- if (data->target_reading) {
- /* Controller has NAKed the last byte read, so no more to send.
- * Ignore TX FIFO empty so we don't write an extra byte.
- */
- LOG_DBG("target read completed");
- *int_enable &= ~ISR_TX_FIFO_EMPTY;
- *ints_to_clear |= ISR_TX_FIFO_EMPTY;
- } else {
- LOG_WRN("Unexpected TX complete");
- }
- } else if (int_status & ISR_TX_FIFO_EMPTY) {
- if (data->target_reading) {
- uint8_t read_byte = 0xFF;
+ } else if (data->target_reading && (*int_status & ISR_TX_ERR_TARGET_COMP)) {
+ /* Controller has NAKed the last byte read, so no more to send.
+ * Ignore TX FIFO empty so we don't write an extra byte.
+ */
+ LOG_DBG("target read completed");
+ *int_status &= ~ISR_TX_ERR_TARGET_COMP;
+ *int_enable &= ~ISR_TX_FIFO_EMPTY;
+ *ints_to_clear |= ISR_TX_FIFO_EMPTY;
+ } else if (data->target_reading && (*int_status & ISR_TX_FIFO_EMPTY)) {
+ *int_status &= ~ISR_TX_FIFO_EMPTY;
+ uint8_t read_byte = 0xFF;
- if (!data->target_read_aborted &&
- (*data->target_cfg->callbacks->read_processed)(data->target_cfg,
- &read_byte)) {
- LOG_DBG("target read_processed rejected");
- data->target_read_aborted = true;
- }
- sys_write32(read_byte, config->base + REG_TX_FIFO);
- } else {
- LOG_WRN("Unexpected TX empty");
+ if (!data->target_read_aborted &&
+ (*data->target_cfg->callbacks->read_processed)(data->target_cfg,
+ &read_byte)) {
+ LOG_DBG("target read_processed rejected");
+ data->target_read_aborted = true;
}
+ sys_write32(read_byte, config->base + REG_TX_FIFO);
}
}
#endif
@@ -224,9 +221,8 @@
struct i2c_xilinx_axi_data *data = dev->data;
const k_spinlock_key_t key = k_spin_lock(&data->lock);
uint32_t int_enable = sys_read32(config->base + REG_IER);
- const uint32_t int_status = sys_read32(config->base + REG_ISR) & int_enable;
+ uint32_t int_status = sys_read32(config->base + REG_ISR) & int_enable;
uint32_t ints_to_clear = int_status;
- uint32_t ints_to_mask = int_status;
LOG_DBG("ISR called for 0x%08" PRIxPTR ", status 0x%02x", config->base, int_status);
@@ -240,17 +236,20 @@
#if defined(CONFIG_I2C_TARGET)
if (data->target_cfg && (int_status & I2C_XILINX_AXI_TARGET_INTERRUPTS)) {
- ints_to_mask &= ~(int_status & I2C_XILINX_AXI_TARGET_INTERRUPTS);
- i2c_xilinx_axi_target_isr(config, data, int_status, &ints_to_clear, &int_enable);
+ /* This clears events from int_status which are already handled */
+ i2c_xilinx_axi_target_isr(config, data, &int_status, &ints_to_clear, &int_enable);
}
#endif
- sys_write32(int_enable & ~ints_to_mask, config->base + REG_IER);
+ /* Mask any interrupts which have not already been handled separately */
+ sys_write32(int_enable & ~int_status, config->base + REG_IER);
/* Be careful, writing 1 to a bit that is not currently set in ISR will SET it! */
sys_write32(ints_to_clear & sys_read32(config->base + REG_ISR), config->base + REG_ISR);
k_spin_unlock(&data->lock, key);
- k_event_post(&data->irq_event, int_status);
+ if (int_status) {
+ k_event_post(&data->irq_event, int_status);
+ }
}
static int i2c_xilinx_axi_configure(const struct device *dev, uint32_t dev_config)