i2c: i2c_xilinx_axi: Fix switching from controller to target mode
It appears that a controller re-initialization is sometimes needed for
proper operation when handling target mode requests after a request from
the core is completed in controller mode. Add this re-initialization as
was already done between controller mode requests.
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 665e488..58bf138 100644
--- a/drivers/i2c/i2c_xilinx_axi.c
+++ b/drivers/i2c/i2c_xilinx_axi.c
@@ -39,19 +39,36 @@
#endif
};
+static void i2c_xilinx_axi_reinit(const struct i2c_xilinx_axi_config *config)
+{
+ LOG_DBG("Controller reinit");
+ sys_write32(SOFTR_KEY, config->base + REG_SOFTR);
+ sys_write32(CR_TX_FIFO_RST, config->base + REG_CR);
+ sys_write32(CR_EN, config->base + REG_CR);
+ sys_write32(GIE_ENABLE, config->base + REG_GIE);
+}
+
#if defined(CONFIG_I2C_TARGET)
#define I2C_XILINX_AXI_TARGET_INTERRUPTS \
(ISR_ADDR_TARGET | ISR_NOT_ADDR_TARGET | ISR_RX_FIFO_FULL | ISR_TX_FIFO_EMPTY | \
ISR_TX_ERR_TARGET_COMP)
+static void i2c_xilinx_axi_target_setup(const struct i2c_xilinx_axi_config *config,
+ struct i2c_target_config *cfg)
+{
+ i2c_xilinx_axi_reinit(config);
+
+ sys_write32(ISR_ADDR_TARGET, config->base + REG_IER);
+ sys_write32(cfg->address << 1, config->base + REG_ADR);
+ sys_write32(0, config->base + REG_RX_FIFO_PIRQ);
+}
+
static int i2c_xilinx_axi_target_register(const struct device *dev, struct i2c_target_config *cfg)
{
const struct i2c_xilinx_axi_config *config = dev->config;
struct i2c_xilinx_axi_data *data = dev->data;
k_spinlock_key_t key;
- uint32_t int_enable;
- uint32_t int_status;
int ret;
if (cfg->flags & I2C_TARGET_FLAGS_ADDR_10_BITS) {
@@ -68,20 +85,7 @@
}
data->target_cfg = cfg;
-
- int_status = sys_read32(config->base + REG_ISR);
- if (int_status & I2C_XILINX_AXI_TARGET_INTERRUPTS) {
- sys_write32(int_status & I2C_XILINX_AXI_TARGET_INTERRUPTS, config->base + REG_ISR);
- }
-
- sys_write32(CR_EN, config->base + REG_CR);
- int_enable = sys_read32(config->base + REG_IER);
- int_enable |= ISR_ADDR_TARGET;
- sys_write32(int_enable, config->base + REG_IER);
-
- sys_write32(cfg->address << 1, config->base + REG_ADR);
- sys_write32(0, config->base + REG_RX_FIFO_PIRQ);
-
+ i2c_xilinx_axi_target_setup(config, cfg);
ret = 0;
out_unlock:
@@ -119,7 +123,6 @@
int_enable = sys_read32(config->base + REG_IER);
int_enable &= ~I2C_XILINX_AXI_TARGET_INTERRUPTS;
sys_write32(int_enable, config->base + REG_IER);
-
ret = 0;
out_unlock:
@@ -250,15 +253,6 @@
k_event_post(&data->irq_event, int_status);
}
-static void i2c_xilinx_axi_reinit(const struct i2c_xilinx_axi_config *config)
-{
- LOG_DBG("Controller reinit");
- sys_write32(SOFTR_KEY, config->base + REG_SOFTR);
- sys_write32(CR_TX_FIFO_RST, config->base + REG_CR);
- sys_write32(CR_EN, config->base + REG_CR);
- sys_write32(GIE_ENABLE, config->base + REG_GIE);
-}
-
static int i2c_xilinx_axi_configure(const struct device *dev, uint32_t dev_config)
{
const struct i2c_xilinx_axi_config *config = dev->config;
@@ -549,12 +543,6 @@
k_mutex_lock(&data->mutex, K_FOREVER);
- /**
- * Reinitializing before each transfer shouldn't technically be needed, but
- * seems to improve general reliability. The Linux driver also does this.
- */
- i2c_xilinx_axi_reinit(config);
-
ret = i2c_xilinx_axi_wait_not_busy(config, data);
if (ret) {
goto out_unlock;
@@ -564,11 +552,17 @@
goto out_unlock;
}
+ /**
+ * Reinitializing before each transfer shouldn't technically be needed, but
+ * seems to improve general reliability. The Linux driver also does this.
+ */
+ i2c_xilinx_axi_reinit(config);
+
do {
if (msgs->flags & I2C_MSG_ADDR_10_BITS) {
/* Optionally supported in core, but not implemented in driver yet */
ret = -EOPNOTSUPP;
- goto out_unlock;
+ goto out_check_target;
}
if (msgs->flags & I2C_MSG_READ) {
if (config->dyn_read_working && msgs->len <= MAX_DYNAMIC_READ_LEN) {
@@ -583,12 +577,25 @@
ret = i2c_xilinx_axi_wait_not_busy(config, data);
}
if (ret) {
- goto out_unlock;
+ goto out_check_target;
}
msgs++;
num_msgs--;
} while (num_msgs);
+out_check_target:
+#if defined(CONFIG_I2C_TARGET)
+ /* If a target is registered, then ensure the controller gets put back
+ * into a suitable state to handle target transfers.
+ */
+ k_spinlock_key_t key = k_spin_lock(&data->lock);
+
+ if (data->target_cfg) {
+ i2c_xilinx_axi_target_setup(config, data->target_cfg);
+ }
+ k_spin_unlock(&data->lock, key);
+#endif
+
out_unlock:
k_mutex_unlock(&data->mutex);
return ret;