|  | /* SPDX-License-Identifier: Apache-2.0 */ | 
|  | /* | 
|  | * Copyright © 2023 Calian Ltd.  All rights reserved. | 
|  | * | 
|  | * Driver for the Xilinx AXI IIC Bus Interface. | 
|  | * This is an FPGA logic core as described by Xilinx document PG090. | 
|  | */ | 
|  |  | 
|  | #include <errno.h> | 
|  | #include <zephyr/drivers/i2c.h> | 
|  | #include <zephyr/sys/util.h> | 
|  | #include <zephyr/logging/log.h> | 
|  | #include <zephyr/irq.h> | 
|  |  | 
|  | LOG_MODULE_REGISTER(i2c_xilinx_axi, CONFIG_I2C_LOG_LEVEL); | 
|  |  | 
|  | #include "i2c-priv.h" | 
|  | #include "i2c_xilinx_axi.h" | 
|  |  | 
|  | struct i2c_xilinx_axi_config { | 
|  | mem_addr_t base; | 
|  | void (*irq_config_func)(const struct device *dev); | 
|  | /* Whether device has working dynamic read (broken prior to core rev. 2.1) */ | 
|  | bool dyn_read_working; | 
|  | }; | 
|  |  | 
|  | struct i2c_xilinx_axi_data { | 
|  | struct k_event irq_event; | 
|  | /* Serializes between ISR and other calls */ | 
|  | struct k_spinlock lock; | 
|  | /* Provides exclusion against multiple concurrent requests */ | 
|  | struct k_mutex mutex; | 
|  |  | 
|  | #if defined(CONFIG_I2C_TARGET) | 
|  | struct i2c_target_config *target_cfg; | 
|  | bool target_reading; | 
|  | bool target_read_aborted; | 
|  | bool target_writing; | 
|  | #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; | 
|  | int ret; | 
|  |  | 
|  | if (cfg->flags & I2C_TARGET_FLAGS_ADDR_10_BITS) { | 
|  | /* Optionally supported in core, but not implemented in driver yet */ | 
|  | return -EOPNOTSUPP; | 
|  | } | 
|  |  | 
|  | k_mutex_lock(&data->mutex, K_FOREVER); | 
|  | key = k_spin_lock(&data->lock); | 
|  |  | 
|  | if (data->target_cfg) { | 
|  | ret = -EBUSY; | 
|  | goto out_unlock; | 
|  | } | 
|  |  | 
|  | data->target_cfg = cfg; | 
|  | i2c_xilinx_axi_target_setup(config, cfg); | 
|  | ret = 0; | 
|  |  | 
|  | out_unlock: | 
|  | k_spin_unlock(&data->lock, key); | 
|  | LOG_DBG("Target register ret=%d", ret); | 
|  | k_mutex_unlock(&data->mutex); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int i2c_xilinx_axi_target_unregister(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; | 
|  | int ret; | 
|  |  | 
|  | k_mutex_lock(&data->mutex, K_FOREVER); | 
|  | key = k_spin_lock(&data->lock); | 
|  |  | 
|  | if (!data->target_cfg) { | 
|  | ret = -EINVAL; | 
|  | goto out_unlock; | 
|  | } | 
|  |  | 
|  | if (data->target_reading || data->target_writing) { | 
|  | ret = -EBUSY; | 
|  | goto out_unlock; | 
|  | } | 
|  |  | 
|  | data->target_cfg = NULL; | 
|  | sys_write32(0, config->base + REG_ADR); | 
|  |  | 
|  | sys_write32(CR_EN, config->base + REG_CR); | 
|  | 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: | 
|  | k_spin_unlock(&data->lock, key); | 
|  | LOG_DBG("Target unregister ret=%d", ret); | 
|  | k_mutex_unlock(&data->mutex); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void i2c_xilinx_axi_target_isr(const struct i2c_xilinx_axi_config *config, | 
|  | 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) { | 
|  | 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; | 
|  |  | 
|  | if (sys_read32(config->base + REG_SR) & SR_SRW) { | 
|  | uint8_t read_byte; | 
|  |  | 
|  | data->target_reading = true; | 
|  | *ints_to_clear |= ISR_TX_FIFO_EMPTY | ISR_TX_ERR_TARGET_COMP; | 
|  | *int_enable |= ISR_TX_FIFO_EMPTY | ISR_TX_ERR_TARGET_COMP; | 
|  | if ((*data->target_cfg->callbacks->read_requested)(data->target_cfg, | 
|  | &read_byte)) { | 
|  | LOG_DBG("target read_requested rejected"); | 
|  | data->target_read_aborted = true; | 
|  | read_byte = 0xFF; | 
|  | } | 
|  | sys_write32(read_byte, config->base + REG_TX_FIFO); | 
|  | } else { | 
|  | data->target_writing = true; | 
|  | *int_enable |= ISR_RX_FIFO_FULL; | 
|  | if ((*data->target_cfg->callbacks->write_requested)(data->target_cfg)) { | 
|  | uint32_t cr = sys_read32(config->base + REG_CR); | 
|  |  | 
|  | LOG_DBG("target write_requested rejected"); | 
|  | cr |= CR_TXAK; | 
|  | sys_write32(cr, config->base + REG_CR); | 
|  | } | 
|  | } | 
|  | } 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; | 
|  | data->target_read_aborted = false; | 
|  | 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 (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; | 
|  |  | 
|  | if ((*data->target_cfg->callbacks->write_received)(data->target_cfg, | 
|  | written_byte)) { | 
|  | uint32_t cr = sys_read32(config->base + REG_CR); | 
|  |  | 
|  | LOG_DBG("target write_received rejected"); | 
|  | cr |= CR_TXAK; | 
|  | sys_write32(cr, config->base + REG_CR); | 
|  | } | 
|  | } 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); | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  | static void i2c_xilinx_axi_isr(const struct device *dev) | 
|  | { | 
|  | const struct i2c_xilinx_axi_config *config = dev->config; | 
|  | 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); | 
|  | uint32_t int_status = sys_read32(config->base + REG_ISR) & int_enable; | 
|  | uint32_t ints_to_clear = int_status; | 
|  |  | 
|  | LOG_DBG("ISR called for 0x%08" PRIxPTR ", status 0x%02x", config->base, int_status); | 
|  |  | 
|  | if (int_status & ISR_ARB_LOST) { | 
|  | /* Must clear MSMS before clearing interrupt */ | 
|  | uint32_t cr = sys_read32(config->base + REG_CR); | 
|  |  | 
|  | cr &= ~CR_MSMS; | 
|  | sys_write32(cr, config->base + REG_CR); | 
|  | } | 
|  |  | 
|  | #if defined(CONFIG_I2C_TARGET) | 
|  | if (data->target_cfg && (int_status & I2C_XILINX_AXI_TARGET_INTERRUPTS)) { | 
|  | /* 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 | 
|  |  | 
|  | /* 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); | 
|  | 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) | 
|  | { | 
|  | const struct i2c_xilinx_axi_config *config = dev->config; | 
|  |  | 
|  | LOG_INF("Configuring %s at 0x%08" PRIxPTR, dev->name, config->base); | 
|  | i2c_xilinx_axi_reinit(config); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static uint32_t i2c_xilinx_axi_wait_interrupt(const struct i2c_xilinx_axi_config *config, | 
|  | struct i2c_xilinx_axi_data *data, uint32_t int_mask) | 
|  | { | 
|  | const k_spinlock_key_t key = k_spin_lock(&data->lock); | 
|  | const uint32_t int_enable = sys_read32(config->base + REG_IER) | int_mask; | 
|  | uint32_t events; | 
|  |  | 
|  | LOG_DBG("Set IER to 0x%02x", int_enable); | 
|  | sys_write32(int_enable, config->base + REG_IER); | 
|  | k_event_clear(&data->irq_event, int_mask); | 
|  | k_spin_unlock(&data->lock, key); | 
|  |  | 
|  | events = k_event_wait(&data->irq_event, int_mask, false, K_MSEC(100)); | 
|  |  | 
|  | LOG_DBG("Got ISR events 0x%02x", events); | 
|  | if (!events) { | 
|  | LOG_ERR("Timeout waiting for ISR events 0x%02x, SR 0x%02x, ISR 0x%02x", int_mask, | 
|  | sys_read32(config->base + REG_SR), sys_read32(config->base + REG_ISR)); | 
|  | } | 
|  | return events; | 
|  | } | 
|  |  | 
|  | static void i2c_xilinx_axi_clear_interrupt_no_lock(const struct i2c_xilinx_axi_config *config, | 
|  | struct i2c_xilinx_axi_data *data, | 
|  | uint32_t int_mask) | 
|  | { | 
|  | const uint32_t int_status = sys_read32(config->base + REG_ISR); | 
|  |  | 
|  | if (int_status & int_mask) { | 
|  | sys_write32(int_status & int_mask, config->base + REG_ISR); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void i2c_xilinx_axi_clear_interrupt(const struct i2c_xilinx_axi_config *config, | 
|  | struct i2c_xilinx_axi_data *data, uint32_t int_mask) | 
|  | { | 
|  | const k_spinlock_key_t key = k_spin_lock(&data->lock); | 
|  |  | 
|  | i2c_xilinx_axi_clear_interrupt_no_lock(config, data, int_mask); | 
|  |  | 
|  | k_spin_unlock(&data->lock, key); | 
|  | } | 
|  |  | 
|  | static int i2c_xilinx_axi_wait_rx_full(const struct i2c_xilinx_axi_config *config, | 
|  | struct i2c_xilinx_axi_data *data, uint32_t read_bytes) | 
|  | { | 
|  | uint32_t events; | 
|  |  | 
|  | i2c_xilinx_axi_clear_interrupt(config, data, ISR_RX_FIFO_FULL); | 
|  | if (!(sys_read32(config->base + REG_SR) & SR_RX_FIFO_EMPTY) && | 
|  | (sys_read32(config->base + REG_RX_FIFO_OCY) & RX_FIFO_OCY_MASK) + 1 >= read_bytes) { | 
|  | LOG_DBG("RX already full on checking, SR 0x%02x RXOCY 0x%02x", | 
|  | sys_read32(config->base + REG_SR), | 
|  | sys_read32(config->base + REG_RX_FIFO_OCY)); | 
|  | return 0; | 
|  | } | 
|  | events = i2c_xilinx_axi_wait_interrupt(config, data, ISR_RX_FIFO_FULL | ISR_ARB_LOST); | 
|  | if (!events) { | 
|  | return -ETIMEDOUT; | 
|  | } | 
|  | if (events & ISR_ARB_LOST) { | 
|  | LOG_ERR("Arbitration lost on RX"); | 
|  | return -ENXIO; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int i2c_xilinx_axi_read_nondyn(const struct i2c_xilinx_axi_config *config, | 
|  | struct i2c_xilinx_axi_data *data, struct i2c_msg *msg, | 
|  | uint16_t addr) | 
|  | { | 
|  | uint8_t *read_ptr = msg->buf; | 
|  | uint32_t bytes_left = msg->len; | 
|  | uint32_t cr = CR_EN | CR_MSMS; | 
|  |  | 
|  | if (!bytes_left) { | 
|  | return -EINVAL; | 
|  | } | 
|  | if (bytes_left == 1) { | 
|  | /* Set TXAK bit now, to NAK after the first byte is received */ | 
|  | cr |= CR_TXAK; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * The Xilinx core's RX FIFO full logic seems rather broken in that the interrupt | 
|  | * is triggered, and the I2C receive is throttled, only when the FIFO occupancy | 
|  | * equals the PIRQ threshold, not when greater or equal. In the non-dynamic mode | 
|  | * of operation, we need to stop the read prior to the last bytes being received | 
|  | * from the target in order to set the TXAK bit and clear MSMS to terminate the | 
|  | * receive properly. | 
|  | * However, if we previously allowed multiple bytes into the RX FIFO, this requires | 
|  | * reducing the PIRQ threshold to 0 (single byte) during the receive operation. This | 
|  | * can cause the receive to unthrottle (since FIFO occupancy now exceeds PIRQ | 
|  | * threshold) and depending on timing between the driver code and the core, | 
|  | * this can cause the core to try to receive more data into the FIFO than desired | 
|  | * and cause various unexpected results. | 
|  | * | 
|  | * To avoid this, we only receive one byte at a time in the non-dynamic mode. | 
|  | * Dynamic mode doesn't have this issue as it provides the RX byte count to the | 
|  | * controller specifically and the TXAK and MSMS bits are handled automatically. | 
|  | */ | 
|  | sys_write32(0, config->base + REG_RX_FIFO_PIRQ); | 
|  |  | 
|  | if (msg->flags & I2C_MSG_RESTART) { | 
|  | cr |= CR_RSTA; | 
|  |  | 
|  | sys_write32(cr, config->base + REG_CR); | 
|  | sys_write32((addr << 1) | I2C_MSG_READ, config->base + REG_TX_FIFO); | 
|  | } else { | 
|  | sys_write32((addr << 1) | I2C_MSG_READ, config->base + REG_TX_FIFO); | 
|  | sys_write32(cr, config->base + REG_CR); | 
|  | } | 
|  |  | 
|  | while (bytes_left) { | 
|  | int ret = i2c_xilinx_axi_wait_rx_full(config, data, 1); | 
|  |  | 
|  | if (ret) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | if (bytes_left == 2) { | 
|  | /* Set TXAK so the last byte is NAKed */ | 
|  | cr |= CR_TXAK; | 
|  | } else if (bytes_left == 1 && (msg->flags & I2C_MSG_STOP)) { | 
|  | /* Before reading the last byte, clear MSMS to issue a stop if required */ | 
|  | cr &= ~CR_MSMS; | 
|  | } | 
|  | cr &= ~CR_RSTA; | 
|  | sys_write32(cr, config->base + REG_CR); | 
|  |  | 
|  | *read_ptr++ = sys_read32(config->base + REG_RX_FIFO) & RX_FIFO_DATA_MASK; | 
|  | bytes_left--; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int i2c_xilinx_axi_read_dyn(const struct i2c_xilinx_axi_config *config, | 
|  | struct i2c_xilinx_axi_data *data, struct i2c_msg *msg, | 
|  | uint16_t addr) | 
|  | { | 
|  | uint8_t *read_ptr = msg->buf; | 
|  | uint32_t bytes_left = msg->len; | 
|  | uint32_t bytes_to_read = bytes_left; | 
|  | uint32_t cr = CR_EN; | 
|  | uint32_t len_word = bytes_left; | 
|  |  | 
|  | if (!bytes_left || bytes_left > MAX_DYNAMIC_READ_LEN) { | 
|  | return -EINVAL; | 
|  | } | 
|  | if (msg->flags & I2C_MSG_RESTART) { | 
|  | cr |= CR_MSMS | CR_RSTA; | 
|  | } | 
|  | sys_write32(cr, config->base + REG_CR); | 
|  |  | 
|  | if (bytes_to_read > FIFO_SIZE) { | 
|  | bytes_to_read = FIFO_SIZE; | 
|  | } | 
|  | sys_write32(bytes_to_read - 1, config->base + REG_RX_FIFO_PIRQ); | 
|  | sys_write32((addr << 1) | I2C_MSG_READ | TX_FIFO_START, config->base + REG_TX_FIFO); | 
|  |  | 
|  | if (msg->flags & I2C_MSG_STOP) { | 
|  | len_word |= TX_FIFO_STOP; | 
|  | } | 
|  | sys_write32(len_word, config->base + REG_TX_FIFO); | 
|  |  | 
|  | while (bytes_left) { | 
|  | int ret; | 
|  |  | 
|  | bytes_to_read = bytes_left; | 
|  | if (bytes_to_read > FIFO_SIZE) { | 
|  | bytes_to_read = FIFO_SIZE; | 
|  | } | 
|  |  | 
|  | sys_write32(bytes_to_read - 1, config->base + REG_RX_FIFO_PIRQ); | 
|  | ret = i2c_xilinx_axi_wait_rx_full(config, data, bytes_to_read); | 
|  | if (ret) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | while (bytes_to_read) { | 
|  | *read_ptr++ = sys_read32(config->base + REG_RX_FIFO) & RX_FIFO_DATA_MASK; | 
|  | bytes_to_read--; | 
|  | bytes_left--; | 
|  | } | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int i2c_xilinx_axi_wait_tx_done(const struct i2c_xilinx_axi_config *config, | 
|  | struct i2c_xilinx_axi_data *data) | 
|  | { | 
|  | const uint32_t finish_bits = ISR_BUS_NOT_BUSY | ISR_TX_FIFO_EMPTY; | 
|  |  | 
|  | uint32_t events = i2c_xilinx_axi_wait_interrupt( | 
|  | config, data, finish_bits | ISR_TX_ERR_TARGET_COMP | ISR_ARB_LOST); | 
|  | if (!(events & finish_bits) || (events & ~finish_bits)) { | 
|  | if (!events) { | 
|  | return -ETIMEDOUT; | 
|  | } | 
|  | if (events & ISR_ARB_LOST) { | 
|  | LOG_ERR("Arbitration lost on TX"); | 
|  | return -EAGAIN; | 
|  | } | 
|  | LOG_ERR("TX received NAK"); | 
|  | return -ENXIO; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int i2c_xilinx_axi_wait_not_busy(const struct i2c_xilinx_axi_config *config, | 
|  | struct i2c_xilinx_axi_data *data) | 
|  | { | 
|  | if (sys_read32(config->base + REG_SR) & SR_BB) { | 
|  | uint32_t events = i2c_xilinx_axi_wait_interrupt(config, data, ISR_BUS_NOT_BUSY); | 
|  |  | 
|  | if (events != ISR_BUS_NOT_BUSY) { | 
|  | LOG_ERR("Bus stuck busy"); | 
|  | i2c_xilinx_axi_reinit(config); | 
|  | return -EBUSY; | 
|  | } | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int i2c_xilinx_axi_write(const struct i2c_xilinx_axi_config *config, | 
|  | struct i2c_xilinx_axi_data *data, const struct i2c_msg *msg, | 
|  | uint16_t addr) | 
|  | { | 
|  | const uint8_t *write_ptr = msg->buf; | 
|  | uint32_t bytes_left = msg->len; | 
|  | uint32_t cr = CR_EN | CR_TX; | 
|  | uint32_t fifo_space = FIFO_SIZE - 1; /* account for address being written */ | 
|  |  | 
|  | if (msg->flags & I2C_MSG_RESTART) { | 
|  | cr |= CR_MSMS | CR_RSTA; | 
|  | } | 
|  |  | 
|  | i2c_xilinx_axi_clear_interrupt(config, data, ISR_TX_ERR_TARGET_COMP | ISR_ARB_LOST); | 
|  |  | 
|  | sys_write32(cr, config->base + REG_CR); | 
|  | sys_write32((addr << 1) | TX_FIFO_START, config->base + REG_TX_FIFO); | 
|  |  | 
|  | /* TX FIFO empty detection is somewhat fragile because the status register | 
|  | * TX_FIFO_EMPTY bit can be set prior to the transaction actually being | 
|  | * complete, so we have to rely on the TX empty interrupt. | 
|  | * However, delays in writing data to the TX FIFO could cause it | 
|  | * to run empty in the middle of the process, causing us to get a spurious | 
|  | * completion detection from the interrupt. Therefore we disable interrupts | 
|  | * while the TX FIFO is being filled up to try to avoid this. | 
|  | */ | 
|  |  | 
|  | while (bytes_left) { | 
|  | uint32_t bytes_to_send = bytes_left; | 
|  | const k_spinlock_key_t key = k_spin_lock(&data->lock); | 
|  | int ret; | 
|  |  | 
|  | if (bytes_to_send > fifo_space) { | 
|  | bytes_to_send = fifo_space; | 
|  | } | 
|  | while (bytes_to_send) { | 
|  | uint32_t write_word = *write_ptr++; | 
|  |  | 
|  | if (bytes_left == 1 && (msg->flags & I2C_MSG_STOP)) { | 
|  | write_word |= TX_FIFO_STOP; | 
|  | } | 
|  | sys_write32(write_word, config->base + REG_TX_FIFO); | 
|  | bytes_to_send--; | 
|  | bytes_left--; | 
|  | } | 
|  | i2c_xilinx_axi_clear_interrupt_no_lock(config, data, | 
|  | ISR_TX_FIFO_EMPTY | ISR_BUS_NOT_BUSY); | 
|  | k_spin_unlock(&data->lock, key); | 
|  |  | 
|  | ret = i2c_xilinx_axi_wait_tx_done(config, data); | 
|  | if (ret) { | 
|  | return ret; | 
|  | } | 
|  | fifo_space = FIFO_SIZE; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int i2c_xilinx_axi_transfer(const struct device *dev, struct i2c_msg *msgs, uint8_t num_msgs, | 
|  | uint16_t addr) | 
|  | { | 
|  | const struct i2c_xilinx_axi_config *config = dev->config; | 
|  | struct i2c_xilinx_axi_data *data = dev->data; | 
|  | int ret; | 
|  |  | 
|  | k_mutex_lock(&data->mutex, K_FOREVER); | 
|  |  | 
|  | ret = i2c_xilinx_axi_wait_not_busy(config, data); | 
|  | if (ret) { | 
|  | goto out_unlock; | 
|  | } | 
|  |  | 
|  | if (!num_msgs) { | 
|  | 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_check_target; | 
|  | } | 
|  | if (msgs->flags & I2C_MSG_READ) { | 
|  | if (config->dyn_read_working && msgs->len <= MAX_DYNAMIC_READ_LEN) { | 
|  | ret = i2c_xilinx_axi_read_dyn(config, data, msgs, addr); | 
|  | } else { | 
|  | ret = i2c_xilinx_axi_read_nondyn(config, data, msgs, addr); | 
|  | } | 
|  | } else { | 
|  | ret = i2c_xilinx_axi_write(config, data, msgs, addr); | 
|  | } | 
|  | if (!ret && (msgs->flags & I2C_MSG_STOP)) { | 
|  | ret = i2c_xilinx_axi_wait_not_busy(config, data); | 
|  | } | 
|  | if (ret) { | 
|  | 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; | 
|  | } | 
|  |  | 
|  | static int i2c_xilinx_axi_init(const struct device *dev) | 
|  | { | 
|  | const struct i2c_xilinx_axi_config *config = dev->config; | 
|  | struct i2c_xilinx_axi_data *data = dev->data; | 
|  | int error; | 
|  |  | 
|  | k_event_init(&data->irq_event); | 
|  | k_mutex_init(&data->mutex); | 
|  |  | 
|  | error = i2c_xilinx_axi_configure(dev, I2C_MODE_CONTROLLER); | 
|  | if (error) { | 
|  | return error; | 
|  | } | 
|  |  | 
|  | config->irq_config_func(dev); | 
|  |  | 
|  | LOG_INF("initialized"); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static DEVICE_API(i2c, i2c_xilinx_axi_driver_api) = { | 
|  | .configure = i2c_xilinx_axi_configure, | 
|  | .transfer = i2c_xilinx_axi_transfer, | 
|  | #if defined(CONFIG_I2C_TARGET) | 
|  | .target_register = i2c_xilinx_axi_target_register, | 
|  | .target_unregister = i2c_xilinx_axi_target_unregister, | 
|  | #endif | 
|  | #ifdef CONFIG_I2C_RTIO | 
|  | .iodev_submit = i2c_iodev_submit_fallback, | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | #define I2C_XILINX_AXI_INIT(n, compat)                                                             \ | 
|  | static void i2c_xilinx_axi_config_func_##compat##_##n(const struct device *dev);           \ | 
|  | \ | 
|  | static const struct i2c_xilinx_axi_config i2c_xilinx_axi_config_##compat##_##n = {         \ | 
|  | .base = DT_INST_REG_ADDR(n),                                                       \ | 
|  | .irq_config_func = i2c_xilinx_axi_config_func_##compat##_##n,                      \ | 
|  | .dyn_read_working = DT_INST_NODE_HAS_COMPAT(n, xlnx_xps_iic_2_1)};                 \ | 
|  | \ | 
|  | static struct i2c_xilinx_axi_data i2c_xilinx_axi_data_##compat##_##n;                      \ | 
|  | \ | 
|  | I2C_DEVICE_DT_INST_DEFINE(n, i2c_xilinx_axi_init, NULL,                                    \ | 
|  | &i2c_xilinx_axi_data_##compat##_##n,                             \ | 
|  | &i2c_xilinx_axi_config_##compat##_##n, POST_KERNEL,              \ | 
|  | CONFIG_I2C_INIT_PRIORITY, &i2c_xilinx_axi_driver_api);           \ | 
|  | \ | 
|  | static void i2c_xilinx_axi_config_func_##compat##_##n(const struct device *dev)            \ | 
|  | {                                                                                          \ | 
|  | ARG_UNUSED(dev);                                                                   \ | 
|  | \ | 
|  | IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), i2c_xilinx_axi_isr,         \ | 
|  | DEVICE_DT_INST_GET(n), 0);                                             \ | 
|  | \ | 
|  | irq_enable(DT_INST_IRQN(n));                                                       \ | 
|  | } | 
|  |  | 
|  | #define DT_DRV_COMPAT xlnx_xps_iic_2_1 | 
|  | DT_INST_FOREACH_STATUS_OKAY_VARGS(I2C_XILINX_AXI_INIT, DT_DRV_COMPAT) | 
|  | #undef DT_DRV_COMPAT | 
|  | #define DT_DRV_COMPAT xlnx_xps_iic_2_00_a | 
|  | DT_INST_FOREACH_STATUS_OKAY_VARGS(I2C_XILINX_AXI_INIT, DT_DRV_COMPAT) |