/* 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)
