/*
 * Copyright (c) 2023 Nuvoton Technology Corporation.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT nuvoton_numaker_i2c

#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/clock_control/clock_control_numaker.h>
#include <zephyr/drivers/reset.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(i2c_numaker, CONFIG_I2C_LOG_LEVEL);

#include "i2c-priv.h"
#include <soc.h>
#include <NuMicro.h>

/* i2c Master Mode Status */
#define  M_START               0x08  /* Start */
#define  M_REPEAT_START        0x10  /* Master Repeat Start */
#define  M_TRAN_ADDR_ACK       0x18  /* Master Transmit Address ACK */
#define  M_TRAN_ADDR_NACK      0x20  /* Master Transmit Address NACK */
#define  M_TRAN_DATA_ACK       0x28  /* Master Transmit Data ACK */
#define  M_TRAN_DATA_NACK      0x30  /* Master Transmit Data NACK */
#define  M_ARB_LOST            0x38  /* Master Arbitration Los */
#define  M_RECE_ADDR_ACK       0x40  /* Master Receive Address ACK */
#define  M_RECE_ADDR_NACK      0x48  /* Master Receive Address NACK */
#define  M_RECE_DATA_ACK       0x50  /* Master Receive Data ACK */
#define  M_RECE_DATA_NACK      0x58  /* Master Receive Data NACK */
#define  BUS_ERROR             0x00  /* Bus error */

/* i2c Slave Mode Status */
#define  S_REPEAT_START_STOP   0xA0  /* Slave Transmit Repeat Start or Stop */
#define  S_TRAN_ADDR_ACK       0xA8  /* Slave Transmit Address ACK */
#define  S_TRAN_DATA_ACK       0xB8  /* Slave Transmit Data ACK */
#define  S_TRAN_DATA_NACK      0xC0  /* Slave Transmit Data NACK */
#define  S_TRAN_LAST_DATA_ACK  0xC8  /* Slave Transmit Last Data ACK */
#define  S_RECE_ADDR_ACK       0x60  /* Slave Receive Address ACK */
#define  S_RECE_ARB_LOST       0x68  /* Slave Receive Arbitration Lost */
#define  S_RECE_DATA_ACK       0x80  /* Slave Receive Data ACK */
#define  S_RECE_DATA_NACK      0x88  /* Slave Receive Data NACK */

/* i2c GC Mode Status */
#define  GC_ADDR_ACK           0x70  /* GC mode Address ACK */
#define  GC_ARB_LOST           0x78  /* GC mode Arbitration Lost */
#define  GC_DATA_ACK           0x90  /* GC mode Data ACK */
#define  GC_DATA_NACK          0x98  /* GC mode Data NACK */

/* i2c Other Status */
#define  ADDR_TRAN_ARB_LOST    0xB0  /* Address Transmit Arbitration Lost */
#define  BUS_RELEASED          0xF8  /* Bus Released */

struct i2c_numaker_config {
	I2C_T *i2c_base;
	const struct reset_dt_spec reset;
	uint32_t clk_modidx;
	uint32_t clk_src;
	uint32_t clk_div;
	const struct device *clkctrl_dev;
	uint32_t irq_n;
	void (*irq_config_func)(const struct device *dev);
	const struct pinctrl_dev_config *pincfg;
	uint32_t bitrate;
};

struct i2c_numaker_data {
	struct k_sem lock;
	uint32_t dev_config;
	/* Master transfer context */
	struct {
		struct k_sem xfer_sync;
		uint16_t addr;
		struct i2c_msg *msgs_beg;
		struct i2c_msg *msgs_pos;
		struct i2c_msg *msgs_end;
		uint8_t *buf_beg;
		uint8_t *buf_pos;
		uint8_t *buf_end;
	} master_xfer;
#ifdef CONFIG_I2C_TARGET
	/* Slave transfer context */
	struct {
		struct i2c_target_config *slave_config;
		bool slave_addressed;
	} slave_xfer;
#endif
};

/* ACK/NACK last data byte, dependent on whether or not message merge is allowed */
static void m_numaker_i2c_master_xfer_msg_read_last_byte(const struct device *dev)
{
	const struct i2c_numaker_config *config = dev->config;
	struct i2c_numaker_data *data = dev->data;
	I2C_T *i2c_base = config->i2c_base;

	/* Shouldn't invoke with message pointer OOB */
	__ASSERT_NO_MSG(data->master_xfer.msgs_pos < data->master_xfer.msgs_end);
	/* Should invoke with exactly one data byte remaining for read */
	__ASSERT_NO_MSG((data->master_xfer.msgs_pos->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ);
	__ASSERT_NO_MSG((data->master_xfer.buf_end - data->master_xfer.buf_pos) == 1);

	/* Flags of previous message */
	bool do_stop_prev = data->master_xfer.msgs_pos->flags & I2C_MSG_STOP;

	/* Advance to next messages temporarily */
	data->master_xfer.msgs_pos++;

	/* Has next message? */
	if (data->master_xfer.msgs_pos < data->master_xfer.msgs_end) {
		/* Flags of next message */
		struct i2c_msg *msgs_pos = data->master_xfer.msgs_pos;
		bool is_read_next = (msgs_pos->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ;
		bool do_restart_next = data->master_xfer.msgs_pos->flags & I2C_MSG_RESTART;

		/*
		 * Different R/W bit so message merge is disallowed.
		 * Force I2C Repeat Start on I2C Stop/Repeat Start missing
		 */
		if (!is_read_next) {
			if (!do_stop_prev && !do_restart_next) {
				do_restart_next = true;
			}
		}

		if (do_stop_prev || do_restart_next) {
			/* NACK last data byte (required for Master Receiver) */
			I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
		} else {
			/* ACK last data byte, so to merge adjacent messages into one transaction */
			I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
		}
	} else {
		/* NACK last data byte (required for Master Receiver) */
		I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
	}

	/* Roll back message pointer */
	data->master_xfer.msgs_pos--;
}

/* End the transfer, involving I2C Stop and signal to thread */
static void m_numaker_i2c_master_xfer_end(const struct device *dev, bool do_stop)
{
	const struct i2c_numaker_config *config = dev->config;
	struct i2c_numaker_data *data = dev->data;
	I2C_T *i2c_base = config->i2c_base;

	if (do_stop) {
		/* Do I2C Stop */
		I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_STO_Msk | I2C_CTL0_SI_Msk);
	}

	/* Signal master transfer end */
	k_sem_give(&data->master_xfer.xfer_sync);
}

static void m_numaker_i2c_master_xfer_msg_end(const struct device *dev);
/* Read next data byte, involving ACK/NACK last data byte and message merge */
static void m_numaker_i2c_master_xfer_msg_read_next_byte(const struct device *dev)
{
	const struct i2c_numaker_config *config = dev->config;
	struct i2c_numaker_data *data = dev->data;
	I2C_T *i2c_base = config->i2c_base;

	switch (data->master_xfer.buf_end - data->master_xfer.buf_pos) {
	case 0:
		/* Last data byte ACKed, we'll do message merge */
		m_numaker_i2c_master_xfer_msg_end(dev);
		break;
	case 1:
		/* Read last data byte for this message */
		m_numaker_i2c_master_xfer_msg_read_last_byte(dev);
		break;
	default:
		/* ACK non-last data byte */
		I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
	}
}

/* End one message transfer, involving message merge and transfer end */
static void m_numaker_i2c_master_xfer_msg_end(const struct device *dev)
{
	const struct i2c_numaker_config *config = dev->config;
	struct i2c_numaker_data *data = dev->data;
	I2C_T *i2c_base = config->i2c_base;

	/* Shouldn't invoke with message pointer OOB */
	__ASSERT_NO_MSG(data->master_xfer.msgs_pos < data->master_xfer.msgs_end);
	/* Should have transferred up */
	__ASSERT_NO_MSG((data->master_xfer.buf_end - data->master_xfer.buf_pos) == 0);

	/* Flags of previous message */
	bool is_read_prev = (data->master_xfer.msgs_pos->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ;
	bool do_stop_prev = data->master_xfer.msgs_pos->flags & I2C_MSG_STOP;

	/* Advance to next messages */
	data->master_xfer.msgs_pos++;

	/* Has next message? */
	if (data->master_xfer.msgs_pos < data->master_xfer.msgs_end) {
		/* Flags of next message */
		struct i2c_msg *msgs_pos = data->master_xfer.msgs_pos;
		bool is_read_next = (msgs_pos->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ;
		bool do_restart_next = data->master_xfer.msgs_pos->flags & I2C_MSG_RESTART;

		/*
		 * Different R/W bit so message merge is disallowed.
		 * Force I2C Repeat Start on I2C Stop/Repeat Start missing
		 */
		if (!is_read_prev != !is_read_next) {   /* Logical XOR idiom */
			if (!do_stop_prev && !do_restart_next) {
				LOG_WRN("Cannot merge adjacent messages, force I2C Repeat Start");
				do_restart_next = true;
			}
		}

		if (do_stop_prev) {
			/* Do I2C Stop and then Start */
			I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_STA_Msk |
					    I2C_CTL0_STO_Msk | I2C_CTL0_SI_Msk);
		} else if (do_restart_next) {
			/* Do I2C Repeat Start */
			I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_STA_Msk | I2C_CTL0_SI_Msk);
		} else {
			/* Merge into the same transaction */

			/* Prepare buffer for current message */
			data->master_xfer.buf_beg = data->master_xfer.msgs_pos->buf;
			data->master_xfer.buf_pos = data->master_xfer.msgs_pos->buf;
			data->master_xfer.buf_end = data->master_xfer.msgs_pos->buf +
						    data->master_xfer.msgs_pos->len;

			if (is_read_prev) {
				m_numaker_i2c_master_xfer_msg_read_next_byte(dev);
			} else {
				/*
				 * Interrupt flag not cleared, expect to re-enter ISR with
				 * context unchanged, except buffer changed for message change.
				 */
			}
		}
	} else {
		if (!do_stop_prev) {
			LOG_WRN("Last message not marked I2C Stop");
		}

		m_numaker_i2c_master_xfer_end(dev, do_stop_prev);
	}
}

static int i2c_numaker_configure(const struct device *dev, uint32_t dev_config)
{
	const struct i2c_numaker_config *config = dev->config;
	struct i2c_numaker_data *data = dev->data;
	uint32_t bitrate;

	/* Check address size */
	if (dev_config & I2C_ADDR_10_BITS) {
		LOG_ERR("10-bits address not supported");
		return -ENOTSUP;
	}

	switch (I2C_SPEED_GET(dev_config)) {
	case I2C_SPEED_STANDARD:
		bitrate = KHZ(100);
		break;
	case I2C_SPEED_FAST:
		bitrate = KHZ(400);
		break;
	case I2C_SPEED_FAST_PLUS:
		bitrate = MHZ(1);
		break;
	default:
		LOG_ERR("Speed code %d not supported", I2C_SPEED_GET(dev_config));
		return -ENOTSUP;
	}

	I2C_T *i2c_base = config->i2c_base;
	int err = 0;

	k_sem_take(&data->lock, K_FOREVER);
	irq_disable(config->irq_n);

#ifdef CONFIG_I2C_TARGET
	if (data->slave_xfer.slave_addressed) {
		LOG_ERR("Reconfigure with slave being busy");
		err = -EBUSY;
		goto done;
	}
#endif

	I2C_Open(i2c_base, bitrate);
	/* INTEN bit and FSM control bits (STA, STO, SI, AA) are packed in one register CTL0. */
	i2c_base->CTL0 |= (I2C_CTL0_INTEN_Msk | I2C_CTL0_I2CEN_Msk);
	data->dev_config = dev_config;

done:

	irq_enable(config->irq_n);
	k_sem_give(&data->lock);

	return err;
}

static int i2c_numaker_get_config(const struct device *dev, uint32_t *dev_config)
{
	struct i2c_numaker_data *data = dev->data;

	if (!dev_config) {
		return -EINVAL;
	}

	k_sem_take(&data->lock, K_FOREVER);
	*dev_config = data->dev_config;
	k_sem_give(&data->lock);

	return 0;
}

/*
 * Master active transfer:
 * 1. Do I2C Start to start the transfer (thread)
 * 2. I2C FSM (ISR)
 * 3. Force I2C Stop to end the transfer (thread)
 * Slave passive transfer:
 * 1. Prepare callback (thread)
 * 2. Do data transfer via above callback (ISR)
 */
static int i2c_numaker_transfer(const struct device *dev, struct i2c_msg *msgs,
				uint8_t num_msgs, uint16_t addr)
{
	const struct i2c_numaker_config *config = dev->config;
	struct i2c_numaker_data *data = dev->data;
	I2C_T *i2c_base = config->i2c_base;
	int err = 0;

	k_sem_take(&data->lock, K_FOREVER);
	irq_disable(config->irq_n);

	if (data->slave_xfer.slave_addressed) {
		LOG_ERR("Master transfer with slave being busy");
		err = -EBUSY;
		goto cleanup;
	}

	if (num_msgs == 0) {
		goto cleanup;
	}

	/* Prepare to start transfer */
	data->master_xfer.addr = addr;
	data->master_xfer.msgs_beg = msgs;
	data->master_xfer.msgs_pos = msgs;
	data->master_xfer.msgs_end = msgs + num_msgs;

	/* Do I2C Start to start the transfer */
	I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_STA_Msk | I2C_CTL0_SI_Msk);

	irq_enable(config->irq_n);
	k_sem_take(&data->master_xfer.xfer_sync, K_FOREVER);
	irq_disable(config->irq_n);

	/* Check transfer result */
	if (data->master_xfer.msgs_pos != data->master_xfer.msgs_end) {
		bool is_read;
		bool is_10bit;

		is_read = (data->master_xfer.msgs_pos->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ;
		is_10bit = data->master_xfer.msgs_pos->flags & I2C_MSG_ADDR_10_BITS;
		LOG_ERR("Failed message:");
		LOG_ERR("MSG IDX: %d", data->master_xfer.msgs_pos - data->master_xfer.msgs_beg);
		LOG_ERR("ADDR (%d-bit): 0x%04X", is_10bit ? 10 : 7, addr);
		LOG_ERR("DIR: %s", is_read ? "R" : "W");
		LOG_ERR("Expected %d bytes transferred, but actual %d",
			data->master_xfer.msgs_pos->len,
			data->master_xfer.buf_pos - data->master_xfer.buf_beg);
		err = -EIO;
		goto i2c_stop;
	}

i2c_stop:

	/* Do I2C Stop to release bus ownership */
	I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_STO_Msk | I2C_CTL0_SI_Msk);

#ifdef CONFIG_I2C_TARGET
	/* Enable slave mode if one slave is registered */
	if (data->slave_xfer.slave_config) {
		I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
	}
#endif

cleanup:

	irq_enable(config->irq_n);
	k_sem_give(&data->lock);

	return err;
}

#ifdef CONFIG_I2C_TARGET
static int i2c_numaker_slave_register(const struct device *dev,
				      struct i2c_target_config *slave_config)
{
	if (!slave_config || !slave_config->callbacks) {
		return -EINVAL;
	}

	if (slave_config->flags & I2C_ADDR_10_BITS) {
		LOG_ERR("10-bits address not supported");
		return -ENOTSUP;
	}

	const struct i2c_numaker_config *config = dev->config;
	struct i2c_numaker_data *data = dev->data;
	I2C_T *i2c_base = config->i2c_base;
	int err = 0;

	k_sem_take(&data->lock, K_FOREVER);
	irq_disable(config->irq_n);

	if (data->slave_xfer.slave_config) {
		err = -EBUSY;
		goto cleanup;
	}

	data->slave_xfer.slave_config = slave_config;
	/* Slave address */
	I2C_SetSlaveAddr(i2c_base,
			 0,
			 slave_config->address,
			 I2C_GCMODE_DISABLE);

	/* Slave address state */
	data->slave_xfer.slave_addressed = false;

	/* Enable slave mode */
	I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);

cleanup:

	irq_enable(config->irq_n);
	k_sem_give(&data->lock);

	return err;
}

static int i2c_numaker_slave_unregister(const struct device *dev,
					struct i2c_target_config *slave_config)
{
	const struct i2c_numaker_config *config = dev->config;
	struct i2c_numaker_data *data = dev->data;
	I2C_T *i2c_base = config->i2c_base;
	int err = 0;

	if (!slave_config) {
		return -EINVAL;
	}

	k_sem_take(&data->lock, K_FOREVER);
	irq_disable(config->irq_n);

	if (data->slave_xfer.slave_config != slave_config) {
		err = -EINVAL;
		goto cleanup;
	}

	if (data->slave_xfer.slave_addressed) {
		LOG_ERR("Unregister slave driver with slave being busy");
		err = -EBUSY;
		goto cleanup;
	}

	/* Slave address: Zero */
	I2C_SetSlaveAddr(i2c_base,
			 0,
			 0,
			 I2C_GCMODE_DISABLE);

	/* Slave address state */
	data->slave_xfer.slave_addressed = false;

	/* Disable slave mode */
	I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
	data->slave_xfer.slave_config = NULL;

cleanup:

	irq_enable(config->irq_n);
	k_sem_give(&data->lock);

	return err;
}
#endif

static int i2c_numaker_recover_bus(const struct device *dev)
{
	const struct i2c_numaker_config *config = dev->config;
	struct i2c_numaker_data *data = dev->data;
	I2C_T *i2c_base = config->i2c_base;

	k_sem_take(&data->lock, K_FOREVER);
	/* Do I2C Stop to release bus ownership */
	I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_STO_Msk | I2C_CTL0_SI_Msk);
	k_sem_give(&data->lock);

	return 0;
}

static void i2c_numaker_isr(const struct device *dev)
{
	const struct i2c_numaker_config *config = dev->config;
	struct i2c_numaker_data *data = dev->data;
	I2C_T *i2c_base = config->i2c_base;
#ifdef CONFIG_I2C_TARGET
	struct i2c_target_config *slave_config = data->slave_xfer.slave_config;
	const struct i2c_target_callbacks *slave_callbacks =
		slave_config ? slave_config->callbacks : NULL;
	uint8_t data_byte;
#endif
	uint32_t status;

	if (I2C_GET_TIMEOUT_FLAG(i2c_base)) {
		I2C_ClearTimeoutFlag(i2c_base);
		return;
	}

	status = I2C_GET_STATUS(i2c_base);

	switch (status) {
	case M_START: /* Start */
	case M_REPEAT_START: /* Master Repeat Start */
		/* Prepare buffer for current message */
		data->master_xfer.buf_beg = data->master_xfer.msgs_pos->buf;
		data->master_xfer.buf_pos = data->master_xfer.msgs_pos->buf;
		data->master_xfer.buf_end = data->master_xfer.msgs_pos->buf +
					      data->master_xfer.msgs_pos->len;

		/* Write I2C address */
		struct i2c_msg *msgs_pos = data->master_xfer.msgs_pos;
		bool is_read = (msgs_pos->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ;
		uint16_t addr = data->master_xfer.addr;
		int addr_rw = is_read ? ((addr << 1) | 1) : (addr << 1);

		I2C_SET_DATA(i2c_base, (uint8_t) (addr_rw & 0xFF));
		I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
		break;
	case M_TRAN_ADDR_ACK: /* Master Transmit Address ACK */
	case M_TRAN_DATA_ACK: /* Master Transmit Data ACK */
		__ASSERT_NO_MSG(data->master_xfer.buf_pos);
		if (data->master_xfer.buf_pos < data->master_xfer.buf_end) {
			I2C_SET_DATA(i2c_base, *data->master_xfer.buf_pos++);
			I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
		} else {
			/* End this message */
			m_numaker_i2c_master_xfer_msg_end(dev);
		}
		break;
	case M_TRAN_ADDR_NACK:  /* Master Transmit Address NACK */
	case M_TRAN_DATA_NACK:  /* Master Transmit Data NACK */
	case M_RECE_ADDR_NACK:  /* Master Receive Address NACK */
	case M_ARB_LOST:  /* Master Arbitration Lost */
		m_numaker_i2c_master_xfer_end(dev, true);
		break;
	case M_RECE_ADDR_ACK:  /* Master Receive Address ACK */
	case M_RECE_DATA_ACK:  /* Master Receive Data ACK */
		__ASSERT_NO_MSG(data->master_xfer.buf_pos);

		if (status == M_RECE_ADDR_ACK) {
			__ASSERT_NO_MSG(data->master_xfer.buf_pos < data->master_xfer.buf_end);
		} else if (status == M_RECE_DATA_ACK) {
			__ASSERT_NO_MSG((data->master_xfer.buf_end -
					 data->master_xfer.buf_pos) >= 1);
			*data->master_xfer.buf_pos++ = I2C_GET_DATA(i2c_base);
		}

		m_numaker_i2c_master_xfer_msg_read_next_byte(dev);
		break;
	case M_RECE_DATA_NACK:  /* Master Receive Data NACK */
		__ASSERT_NO_MSG((data->master_xfer.buf_end - data->master_xfer.buf_pos) == 1);
		*data->master_xfer.buf_pos++ = I2C_GET_DATA(i2c_base);
		/* End this message */
		m_numaker_i2c_master_xfer_msg_end(dev);
		break;
	case BUS_ERROR:	 /* Bus error */
		m_numaker_i2c_master_xfer_end(dev, true);
		break;
#ifdef CONFIG_I2C_TARGET
	/* NOTE: Don't disable interrupt here because slave mode relies on */
	/* for passive transfer in ISR. */

	/* Slave Transmit */
	case S_TRAN_ADDR_ACK:  /* Slave Transmit Address ACK */
	case ADDR_TRAN_ARB_LOST:  /* Slave Transmit Arbitration Lost */
		data->slave_xfer.slave_addressed = true;
		if (slave_callbacks->read_requested(slave_config, &data_byte) == 0) {
			/* Non-last data byte */
			I2C_SET_DATA(i2c_base, data_byte);
			I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
		} else {
			/* Go S_TRAN_LAST_DATA_ACK on error */
			I2C_SET_DATA(i2c_base, 0xFF);
			I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
		}
		break;
	case S_TRAN_DATA_ACK:  /* Slave Transmit Data ACK */
		if (slave_callbacks->read_processed(slave_config, &data_byte) == 0) {
			/* Non-last data byte */
			I2C_SET_DATA(i2c_base, data_byte);
			I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
		} else {
			/* Go S_TRAN_LAST_DATA_ACK on error */
			I2C_SET_DATA(i2c_base, 0xFF);
			I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
		}
		break;
	case S_TRAN_DATA_NACK:  /* Slave Transmit Data NACK */
	case S_TRAN_LAST_DATA_ACK:  /* Slave Transmit Last Data ACK */
		/* Go slave end */
		data->slave_xfer.slave_addressed = false;
		slave_callbacks->stop(slave_config);
		I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
		break;
		/* Slave Receive */
	case S_RECE_DATA_ACK:  /* Slave Receive Data ACK */
		data_byte = I2C_GET_DATA(i2c_base);
		if (slave_callbacks->write_received(slave_config, data_byte) == 0) {
			/* Write OK, ACK next data byte */
			I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
		} else {
			/* Write FAILED, NACK next data byte */
			I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
		}
		break;
	case S_RECE_DATA_NACK:  /* Slave Receive Data NACK */
		/* Go slave end */
		data->slave_xfer.slave_addressed = false;
		slave_callbacks->stop(slave_config);
		I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
		break;
	case S_RECE_ADDR_ACK:  /* Slave Receive Address ACK */
	case S_RECE_ARB_LOST:  /* Slave Receive Arbitration Lost */
		data->slave_xfer.slave_addressed = true;
		if (slave_callbacks->write_requested(slave_config) == 0) {
			/* Write ready, ACK next byte */
			I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
		} else {
			/* Write not ready, NACK next byte */
			I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
		}
		break;
	case S_REPEAT_START_STOP:  /* Slave Transmit/Receive Repeat Start or Stop */
		/* Go slave end */
		data->slave_xfer.slave_addressed = false;
		slave_callbacks->stop(slave_config);
		I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
		break;
#endif  /* CONFIG_I2C_TARGET */

	case BUS_RELEASED:  /* Bus Released */
		/* Ignore the interrupt raised by BUS_RELEASED. */
		break;
	default:
		__ASSERT(false, "Uncaught I2C FSM state");
		m_numaker_i2c_master_xfer_end(dev, true);
	}
}

static int i2c_numaker_init(const struct device *dev)
{
	const struct i2c_numaker_config *config = dev->config;
	struct i2c_numaker_data *data = dev->data;
	int err = 0;
	struct numaker_scc_subsys scc_subsys;

	/* Validate this module's reset object */
	if (!device_is_ready(config->reset.dev)) {
		LOG_ERR("reset controller not ready");
		return -ENODEV;
	}

	/* Clean mutable context */
	memset(data, 0x00, sizeof(*data));

	k_sem_init(&data->lock, 1, 1);
	k_sem_init(&data->master_xfer.xfer_sync, 0, 1);

	SYS_UnlockReg();

	memset(&scc_subsys, 0x00, sizeof(scc_subsys));
	scc_subsys.subsys_id = NUMAKER_SCC_SUBSYS_ID_PCC;
	scc_subsys.pcc.clk_modidx = config->clk_modidx;
	scc_subsys.pcc.clk_src = config->clk_src;
	scc_subsys.pcc.clk_div = config->clk_div;

	/* Equivalent to CLK_EnableModuleClock() */
	err = clock_control_on(config->clkctrl_dev, (clock_control_subsys_t) &scc_subsys);
	if (err != 0) {
		goto cleanup;
	}
	/* Equivalent to CLK_SetModuleClock() */
	err = clock_control_configure(config->clkctrl_dev,
				      (clock_control_subsys_t) &scc_subsys,
				      NULL);
	if (err != 0) {
		goto cleanup;
	}

	/* Configure pinmux (NuMaker's SYS MFP) */
	err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
	if (err != 0) {
		goto cleanup;
	}

	/* Reset I2C to default state, same as BSP's SYS_ResetModule(id_rst) */
	reset_line_toggle_dt(&config->reset);

	err = i2c_numaker_configure(dev, I2C_MODE_CONTROLLER | i2c_map_dt_bitrate(config->bitrate));
	if (err != 0) {
		goto cleanup;
	}

	config->irq_config_func(dev);

cleanup:

	SYS_LockReg();
	return err;
}

static const struct i2c_driver_api i2c_numaker_driver_api = {
	.configure = i2c_numaker_configure,
	.get_config = i2c_numaker_get_config,
	.transfer = i2c_numaker_transfer,
#ifdef CONFIG_I2C_TARGET
	.target_register = i2c_numaker_slave_register,
	.target_unregister = i2c_numaker_slave_unregister,
#endif
	.recover_bus = i2c_numaker_recover_bus,
};

#define I2C_NUMAKER_INIT(inst)                                                     \
	PINCTRL_DT_INST_DEFINE(inst);                                              \
                                                                                   \
	static void i2c_numaker_irq_config_func_##inst(const struct device *dev)   \
	{                                                                          \
		IRQ_CONNECT(DT_INST_IRQN(inst),                                    \
			    DT_INST_IRQ(inst, priority),                           \
			    i2c_numaker_isr,                                       \
			    DEVICE_DT_INST_GET(inst),                              \
			    0);                                                    \
                                                                                   \
		irq_enable(DT_INST_IRQN(inst));                                    \
	}                                                                          \
                                                                                   \
	static const struct i2c_numaker_config i2c_numaker_config_##inst = {       \
		.i2c_base = (I2C_T *) DT_INST_REG_ADDR(inst),                      \
		.reset = RESET_DT_SPEC_INST_GET(inst),                             \
		.clk_modidx = DT_INST_CLOCKS_CELL(inst, clock_module_index),       \
		.clk_src = DT_INST_CLOCKS_CELL(inst, clock_source),                \
		.clk_div = DT_INST_CLOCKS_CELL(inst, clock_divider),               \
		.clkctrl_dev = DEVICE_DT_GET(DT_PARENT(DT_INST_CLOCKS_CTLR(inst))),\
		.irq_n = DT_INST_IRQN(inst),                                       \
		.irq_config_func = i2c_numaker_irq_config_func_##inst,             \
		.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst),                    \
		.bitrate = DT_INST_PROP(inst, clock_frequency),                    \
	};                                                                         \
                                                                                   \
	static struct i2c_numaker_data i2c_numaker_data_##inst;                    \
                                                                                   \
	I2C_DEVICE_DT_INST_DEFINE(inst,                                            \
				  i2c_numaker_init,                                \
				  NULL,                                            \
				  &i2c_numaker_data_##inst,                        \
				  &i2c_numaker_config_##inst,                      \
				  POST_KERNEL,                                     \
				  CONFIG_I2C_INIT_PRIORITY,                        \
				  &i2c_numaker_driver_api);

DT_INST_FOREACH_STATUS_OKAY(I2C_NUMAKER_INIT);
