drivers: i2c: support for Nuvoton numaker series

Add Nuvoton numaker series I2C controller feature.
Support dual role and at most one slave at one time

Signed-off-by: cyliang tw <cyliang@nuvoton.com>
diff --git a/drivers/i2c/CMakeLists.txt b/drivers/i2c/CMakeLists.txt
index f7f363e..406ce5d 100644
--- a/drivers/i2c/CMakeLists.txt
+++ b/drivers/i2c/CMakeLists.txt
@@ -55,6 +55,7 @@
 zephyr_library_sources_ifdef(CONFIG_I2C_SEDI		i2c_sedi.c)
 zephyr_library_sources_ifdef(CONFIG_I2C_AMBIQ		i2c_ambiq.c)
 zephyr_library_sources_ifdef(CONFIG_GPIO_I2C_SWITCH	gpio_i2c_switch.c)
+zephyr_library_sources_ifdef(CONFIG_I2C_NUMAKER		i2c_numaker.c)
 
 zephyr_library_sources_ifdef(CONFIG_I2C_STM32_V1
 	i2c_ll_stm32_v1.c
diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig
index 2338791..b486977 100644
--- a/drivers/i2c/Kconfig
+++ b/drivers/i2c/Kconfig
@@ -92,6 +92,7 @@
 source "drivers/i2c/Kconfig.mchp_mss"
 source "drivers/i2c/Kconfig.sedi"
 source "drivers/i2c/Kconfig.ambiq"
+source "drivers/i2c/Kconfig.numaker"
 
 config I2C_INIT_PRIORITY
 	int "Init priority"
diff --git a/drivers/i2c/Kconfig.numaker b/drivers/i2c/Kconfig.numaker
new file mode 100644
index 0000000..622592c
--- /dev/null
+++ b/drivers/i2c/Kconfig.numaker
@@ -0,0 +1,14 @@
+# NUMAKER I2C driver configuration options
+
+# Copyright (c) 2023 Nuvoton Technology Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+config I2C_NUMAKER
+	bool "Nuvoton NuMaker I2C driver"
+	default y
+	select HAS_NUMAKER_I2C
+	depends on DT_HAS_NUVOTON_NUMAKER_I2C_ENABLED
+	help
+	  This option enables I2C driver for Nuvoton NuMaker family of
+	  processors.
+	  Say y if you wish to enable NuMaker I2C.
diff --git a/drivers/i2c/i2c_numaker.c b/drivers/i2c/i2c_numaker.c
new file mode 100644
index 0000000..0c85601
--- /dev/null
+++ b/drivers/i2c/i2c_numaker.c
@@ -0,0 +1,784 @@
+/*
+ * 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);
diff --git a/dts/arm/nuvoton/m46x.dtsi b/dts/arm/nuvoton/m46x.dtsi
index 3e6efbb..bb0b95c 100644
--- a/dts/arm/nuvoton/m46x.dtsi
+++ b/dts/arm/nuvoton/m46x.dtsi
@@ -10,6 +10,7 @@
 #include <zephyr/dt-bindings/clock/numaker_m46x_clock.h>
 #include <zephyr/dt-bindings/reset/numaker_m46x_reset.h>
 #include <zephyr/dt-bindings/gpio/gpio.h>
+#include <zephyr/dt-bindings/i2c/i2c.h>
 
 / {
 	chosen {
@@ -502,6 +503,66 @@
 			clocks = <&pcc NUMAKER_EMAC0_MODULE 0 0>;
 			status = "disabled";
 		};
+
+		i2c0: i2c@40080000 {
+			compatible = "nuvoton,numaker-i2c";
+			clock-frequency = <I2C_BITRATE_STANDARD>;
+			reg = <0x40080000 0x1000>;
+			interrupts = <38 0>;
+			resets = <&rst NUMAKER_I2C0_RST>;
+			clocks = <&pcc NUMAKER_I2C0_MODULE 0 0>;
+			status = "disabled";
+			#address-cells = <1>;
+			#size-cells = <0>;
+		};
+
+		i2c1: i2c@40081000 {
+			compatible = "nuvoton,numaker-i2c";
+			clock-frequency = <I2C_BITRATE_STANDARD>;
+			reg = <0x40081000 0x1000>;
+			interrupts = <39 0>;
+			resets = <&rst NUMAKER_I2C1_RST>;
+			clocks = <&pcc NUMAKER_I2C1_MODULE 0 0>;
+			status = "disabled";
+			#address-cells = <1>;
+			#size-cells = <0>;
+		};
+
+		i2c2: i2c@40082000 {
+			compatible = "nuvoton,numaker-i2c";
+			clock-frequency = <I2C_BITRATE_STANDARD>;
+			reg = <0x40082000 0x1000>;
+			interrupts = <82 0>;
+			resets = <&rst NUMAKER_I2C2_RST>;
+			clocks = <&pcc NUMAKER_I2C2_MODULE 0 0>;
+			status = "disabled";
+			#address-cells = <1>;
+			#size-cells = <0>;
+		};
+
+		i2c3: i2c@40083000 {
+			compatible = "nuvoton,numaker-i2c";
+			clock-frequency = <I2C_BITRATE_STANDARD>;
+			reg = <0x40083000 0x1000>;
+			interrupts = <83 0>;
+			resets = <&rst NUMAKER_I2C3_RST>;
+			clocks = <&pcc NUMAKER_I2C3_MODULE 0 0>;
+			status = "disabled";
+			#address-cells = <1>;
+			#size-cells = <0>;
+		};
+
+		i2c4: i2c@40084000 {
+			compatible = "nuvoton,numaker-i2c";
+			clock-frequency = <I2C_BITRATE_STANDARD>;
+			reg = <0x40084000 0x1000>;
+			interrupts = <118 0>;
+			resets = <&rst NUMAKER_I2C4_RST>;
+			clocks = <&pcc NUMAKER_I2C4_MODULE 0 0>;
+			status = "disabled";
+			#address-cells = <1>;
+			#size-cells = <0>;
+		};
 	};
 };
 
diff --git a/dts/bindings/i2c/nuvoton,numaker-i2c.yaml b/dts/bindings/i2c/nuvoton,numaker-i2c.yaml
new file mode 100644
index 0000000..5ba8764
--- /dev/null
+++ b/dts/bindings/i2c/nuvoton,numaker-i2c.yaml
@@ -0,0 +1,21 @@
+# Copyright (c) 2023 Nuvoton Technology Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+description: Nuvoton, NuMaker I2C controller
+
+compatible: "nuvoton,numaker-i2c"
+
+include: [i2c-controller.yaml, reset-device.yaml, pinctrl-device.yaml]
+
+properties:
+  reg:
+    required: true
+
+  interrupts:
+    required: true
+
+  resets:
+    required: true
+
+  clocks:
+    required: true