/*
 * Copyright (c) 2025 ITE Technology Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT ite_it51xxx_i3cs

#include <zephyr/logging/log.h>
#include <zephyr/logging/log_instance.h>
LOG_MODULE_REGISTER(i3cs_it51xxx);

#include <zephyr/drivers/i3c.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/device.h>
#include <zephyr/kernel.h>
#include <zephyr/irq.h>
#include <zephyr/pm/policy.h>

#include <soc_common.h>

#define BYTE_0(x) FIELD_GET(GENMASK(7, 0), x)
#define BYTE_1(x) FIELD_GET(GENMASK(15, 8), x)
#define BYTE_2(x) FIELD_GET(GENMASK(23, 16), x)
#define BYTE_3(x) FIELD_GET(GENMASK(31, 24), x)

/* used for tx/rx fifo base address setting */
#define FIFO_ADDR_LB(x) FIELD_GET(GENMASK(10, 3), x)
#define FIFO_ADDR_HB(x) FIELD_GET(GENMASK(18, 11), x)

#define I3CS05_CONFIG_1 0x05
#define ID_RANDOM       BIT(0)

#define I3CS07_CONFIG_2        0x07
#define I3CS_TARGET_ADDRESS(n) FIELD_PREP(GENMASK(7, 1), n)

#define I3CS08_STATUS_0 0x08
#define BUS_IS_BUSY     BIT(0)

#define I3CS09_STATUS_1     0x09
#define INT_ERROR_WARNING   BIT(7)
#define INT_CCC             BIT(6)
#define INT_DYN_ADDR_CHANGE BIT(5)
#define INT_RX_PENDING      BIT(3)
#define INT_STOP            BIT(2)
#define INT_ADDR_MATCHED    BIT(1)

#define I3CS0A_STATUS_2   0x0A
#define EVENT_DETECT_MASK GENMASK(5, 4)
#define INT_TARGET_RST    BIT(3)
#define INT_EVENT         BIT(2)

#define I3CS0B_STATUS_3 0x0B
#define HJ_DISABLED     BIT(3)
#define IBI_DISABLED    BIT(0)

#define I3CS0C_CONTROL_0     0x0C
#define EXTENDED_IBI_DATA    BIT(3)
#define I3CS_EVENT_SELECT(n) FIELD_PREP(GENMASK(1, 0), n)

#define I3CS0D_CONTROL_1               0x0D
#define I3CS0F_CONTROL_3               0x0F
#define I3CS11_INTERRUPT_ENABLE_CTRL_0 0x11

#define I3CS14_DIRECT_TX_FIFO_BASE_ADDR_LB 0x14
#define I3CS15_DIRECT_TX_FIFO_BASE_ADDR_HB 0x15
#define I3CS16_DIRECT_RX_FIFO_BASE_ADDR_LB 0x16
#define I3CS17_DIRECT_RX_FIFO_BASE_ADDR_HB 0x17
#define I3CS1A_DIRECT_TX_LENGTH_LB         0x1A
#define I3CS1B_DIRECT_TX_LENGTH_HB         0x1B

#define I3CS1C_ERROR_WARNING_REG_0 0x1C
#define INVALID_START              BIT(4)
#define CONTROLLER_TERMINATED      BIT(3)
#define TX_FIFO_UNDERRUN           (BIT(2) | BIT(1))
#define RX_FIFO_OVERRUN            BIT(0)

#define I3CS1D_ERROR_WARNING_REG_1 0x1D
#define S0_OR_S1_ERROR             BIT(3)
#define SDR_PARITY_ERROR           BIT(0)

#define I3CS2C_DATA_CTRL_0 0x2C
#define FLUSH_TX_FIFO      BIT(0)

#define I3CS41_TX_RX_FIFO_BASE_ADDR_HB 0x41
#define I3CS42_TX_FIFO_BASE_ADDR_LB    0x42
#define I3CS43_RX_FIFO_BASE_ADDR_LB    0x43
#define I3CS45_RX_FIFO_READ_PTR        0x45
#define I3CS4A_TX_FIFO_SIZE            0x4A
#define I3CS_TX_FIFO_SIZE_MASK         GENMASK(3, 0)

#define I3CS4D_CONTROL_REG_4             0x4D
#define I3CS_DIRECT_MODE_AUTO_CLR_TX_CNT BIT(6)
#define I3CS_DIRECT_MODE_ENABLE          BIT(5) | BIT(4)

#define I3CS4E_DIRECT_FIFO_STATUS 0x4E
#define I3CS_DIRECT_TX_DONE       BIT(1)
#define I3CS_DIRECT_RX_DONE       BIT(0)

#define I3CS58_TX_FIFO_BYTE_COUNT_LB 0x58
#define I3CS59_TX_FIFO_BYTE_COUNT_HB 0x59
#define I3CS5A_RX_FIFO_BYTE_COUNT_LB 0x5A
#define I3CS5B_RX_FIFO_BYTE_COUNT_HB 0x5B
#define I3CS64_DYNAMIC_ADDRESS       0x64
#define DYNAMIC_ADDRESS(x)           FIELD_GET(GENMASK(7, 1), x)
#define DYNAMIC_ADDRESS_VALID        BIT(0)

#define I3CS68_MRL_SET_BY_CTRL_LB 0x68
#define I3CS69_MRL_SET_BY_CTRL_HB 0x69
#define I3CS6A_MWL_SET_BY_CTRL_LB 0x6A
#define I3CS6B_MWL_SET_BY_CTRL_HB 0x6B
#define I3CS6C_PRAT_NUMBER_0      0x6C
#define I3CS6D_PRAT_NUMBER_1      0x6D
#define I3CS6E_PRAT_NUMBER_2      0x6E
#define I3CS6F_PRAT_NUMBER_3      0x6F
#define I3CS71_DCR                0x71
#define I3CS72_BCR                0x72
#define I3CS76_TX_FIFO_READ_PTR   0x76
#define I3CS7A_RX_FIFO_SIZE       0x7A
#define I3CS_RX_FIFO_SIZE_MASK    GENMASK(3, 0)

#define IBI_MDB_GROUP_MASK              GENMASK(7, 5)
#define IBI_MDB_GROUP_PENDING_READ_NOTI 5

#define IT51XXX_DIRECT_MODE_FIFO_SIZE 4096
#define IT51XXX_I3CS_MAX_MRL_MWL      0xFFF /* 4095 bytes */

enum it51xxx_i3cs_event_type {
	EVT_NORMAL_MODE = 0,
	EVT_IBI,
	EVT_CONTROL_REQUEST,
	EVT_HOT_JOIN,
};

enum it51xxx_i3cs_request_event {
	NONE = 0,
	REQUEST_NOT_SENT,
	REQUEST_NACK_EVT,
	REQUEST_ACK_EVT,
};

static const struct fifo_size_mapping_t {
	uint16_t fifo_size;
	uint8_t value;
} fifo_size_table[5] = {[0] = {.fifo_size = 16, .value = 0x0},
			[1] = {.fifo_size = 32, .value = 0x5},
			[2] = {.fifo_size = 64, .value = 0x6},
			[3] = {.fifo_size = 128, .value = 0x7},
			[4] = {.fifo_size = 4096, .value = 0xC}};

struct it51xxx_i3cs_data {
	struct i3c_driver_data common;
	struct i3c_target_config *target_config;

	/* configuration parameters for I3C hardware to act as target device */
	struct i3c_config_target config_target;

#ifdef CONFIG_I3C_USE_IBI
	struct k_sem ibi_sync_sem;
#endif /* CONFIG_I3C_USE_IBI */

	struct k_mutex lock;

	struct {
		uint8_t tx_data[CONFIG_I3CS_IT51XXX_TX_FIFO_SIZE];
		uint8_t rx_data[CONFIG_I3CS_IT51XXX_RX_FIFO_SIZE];
	} fifo __aligned(IT51XXX_DIRECT_MODE_FIFO_SIZE);
};

struct it51xxx_i3cs_config {
	/* common i3c driver config */
	struct i3c_driver_config common;

	const struct pinctrl_dev_config *pcfg;
	mm_reg_t base;
	uint8_t io_channel;
	uint8_t vendor_info;

	struct {
		mm_reg_t addr;
		uint8_t bit_mask;
	} extern_enable;

	void (*irq_config_func)(const struct device *dev);

	LOG_INSTANCE_PTR_DECLARE(log);
};

static inline bool rx_direct_mode_is_enabled(const struct device *dev)
{
	struct it51xxx_i3cs_data *data = dev->data;

	return sizeof(data->fifo.rx_data) == IT51XXX_DIRECT_MODE_FIFO_SIZE;
}

static inline bool tx_direct_mode_is_enabled(const struct device *dev)
{
	struct it51xxx_i3cs_data *data = dev->data;

	return sizeof(data->fifo.tx_data) == IT51XXX_DIRECT_MODE_FIFO_SIZE;
}

static inline uint16_t rx_byte_cnt_in_fifo(const struct device *dev)
{
	const struct it51xxx_i3cs_config *cfg = dev->config;

	return (sys_read8(cfg->base + I3CS5B_RX_FIFO_BYTE_COUNT_HB) << 8) +
	       sys_read8(cfg->base + I3CS5A_RX_FIFO_BYTE_COUNT_LB);
}

static inline uint16_t tx_byte_cnt_in_fifo(const struct device *dev)
{
	const struct it51xxx_i3cs_config *cfg = dev->config;

	return (sys_read8(cfg->base + I3CS59_TX_FIFO_BYTE_COUNT_HB) << 8) +
	       sys_read8(cfg->base + I3CS58_TX_FIFO_BYTE_COUNT_LB);
}

static inline void set_mrl_value(const struct device *dev, const uint16_t value)
{
	const struct it51xxx_i3cs_config *cfg = dev->config;
	uint16_t mrl;

	mrl = (value > IT51XXX_I3CS_MAX_MRL_MWL) ? IT51XXX_I3CS_MAX_MRL_MWL : value;

	sys_write8(BYTE_0(mrl), cfg->base + I3CS68_MRL_SET_BY_CTRL_LB);
	sys_write8(BYTE_1(mrl), cfg->base + I3CS69_MRL_SET_BY_CTRL_HB);
}

static inline void set_mwl_value(const struct device *dev, const uint16_t value)
{
	const struct it51xxx_i3cs_config *cfg = dev->config;
	uint16_t mwl;

	mwl = (value > IT51XXX_I3CS_MAX_MRL_MWL) ? IT51XXX_I3CS_MAX_MRL_MWL : value;

	sys_write8(BYTE_0(mwl), cfg->base + I3CS6A_MWL_SET_BY_CTRL_LB);
	sys_write8(BYTE_1(mwl), cfg->base + I3CS6B_MWL_SET_BY_CTRL_HB);
}

static int it51xxx_i3cs_prepare_tx_fifo(const struct device *dev, uint8_t *buf, uint16_t len)
{
	const struct it51xxx_i3cs_config *cfg = dev->config;
	struct it51xxx_i3cs_data *data = dev->data;
	uint16_t tx_count;

	if (len > sizeof(data->fifo.tx_data)) {
		return -ENOSPC;
	}

	tx_count = tx_byte_cnt_in_fifo(dev);
	if (tx_count) {
		LOG_INST_WRN(cfg->log, "dropped the remaining %d bytes in the tx fifo", tx_count);
	}

	/* flush tx fifo */
	sys_write8(sys_read8(cfg->base + I3CS2C_DATA_CTRL_0) | FLUSH_TX_FIFO,
		   cfg->base + I3CS2C_DATA_CTRL_0);

	/* set tx length */
	if (tx_direct_mode_is_enabled(dev)) {
		sys_write8(BYTE_0(len), cfg->base + I3CS1A_DIRECT_TX_LENGTH_LB);
		sys_write8(BYTE_1(len), cfg->base + I3CS1B_DIRECT_TX_LENGTH_HB);
	} else {
		sys_write8(len, cfg->base + I3CS76_TX_FIFO_READ_PTR);
	}

	/* fill tx fifo with data */
	memcpy(data->fifo.tx_data, buf, len);

	return 0;
}

static int it51xxx_i3cs_target_register(const struct device *dev, struct i3c_target_config *tgt_cfg)
{
	const struct it51xxx_i3cs_config *cfg = dev->config;
	struct it51xxx_i3cs_data *data = dev->data;

	if (!data->target_config) {
		data->target_config = tgt_cfg;

		chip_block_idle();
		pm_policy_state_lock_get(PM_STATE_STANDBY, PM_ALL_SUBSTATES);
	} else {
		LOG_INST_WRN(cfg->log, "the target has already been registered");
	}

	return 0;
}

static int it51xxx_i3cs_target_unregister(const struct device *dev,
					  struct i3c_target_config *tgt_cfg)
{
	ARG_UNUSED(tgt_cfg);

	const struct it51xxx_i3cs_config *cfg = dev->config;
	struct it51xxx_i3cs_data *data = dev->data;

	if (data->target_config) {
		data->target_config = NULL;

		/* Permit to enter power policy and idle mode. */
		pm_policy_state_lock_put(PM_STATE_STANDBY, PM_ALL_SUBSTATES);
		chip_permit_idle();
	} else {
		LOG_INST_WRN(cfg->log, "the target has not been registered");
	}

	return 0;
}

static int it51xxx_i3cs_target_tx_write(const struct device *dev, uint8_t *buf, uint16_t len,
					uint8_t hdr_mode)
{
	const struct it51xxx_i3cs_config *cfg = dev->config;
	struct it51xxx_i3cs_data *data = dev->data;
	int ret;

	if (!buf || len == 0) {
		LOG_INST_ERR(cfg->log, "null buffer or zero length");
		return -EINVAL;
	}

	if (hdr_mode != 0) {
		LOG_INST_ERR(cfg->log, "unsupported hdr mode");
		return -ENOTSUP;
	}

	if (len > sizeof(data->fifo.tx_data)) {
		LOG_INST_ERR(cfg->log, "invalid tx length(%d)", len);
		return -ENOSPC;
	}

	k_mutex_lock(&data->lock, K_FOREVER);
	ret = it51xxx_i3cs_prepare_tx_fifo(dev, buf, len);
	k_mutex_unlock(&data->lock);

	return ret ? ret : len;
}

static inline bool it51xxx_i3cs_dynamic_addr_valid(const struct device *dev)
{
	const struct it51xxx_i3cs_config *cfg = dev->config;

	return ((sys_read8(cfg->base + I3CS64_DYNAMIC_ADDRESS) & DYNAMIC_ADDRESS_VALID) ==
		DYNAMIC_ADDRESS_VALID);
}

#ifdef CONFIG_I3C_USE_IBI
static inline bool it51xxx_i3cs_is_ibi_disable(const struct device *dev)
{
	const struct it51xxx_i3cs_config *cfg = dev->config;

	return ((sys_read8(cfg->base + I3CS0B_STATUS_3) & IBI_DISABLED) == IBI_DISABLED);
}

static inline bool it51xxx_i3cs_is_hj_disable(const struct device *dev)
{
	const struct it51xxx_i3cs_config *cfg = dev->config;

	return ((sys_read8(cfg->base + I3CS0B_STATUS_3) & HJ_DISABLED) == HJ_DISABLED);
}

static int it51xxx_i3cs_wait_to_complete(const struct device *dev)
{
	const struct it51xxx_i3cs_config *cfg = dev->config;
	struct it51xxx_i3cs_data *data = dev->data;

	if (k_sem_take(&data->ibi_sync_sem, K_MSEC(CONFIG_I3CS_IT51XXX_IBI_TIMEOUT_MS)) != 0) {
		LOG_INST_ERR(cfg->log, "ibi event transmission timed out");
		return -ETIMEDOUT;
	}

	return 0;
}

static int it51xxx_i3cs_target_ibi_raise(const struct device *dev, struct i3c_ibi *request)
{
	const struct it51xxx_i3cs_config *cfg = dev->config;
	struct it51xxx_i3cs_data *data = dev->data;
	struct i3c_config_target *config_target = &data->config_target;
	int ret = 0;
	uint8_t reg_val;

	if (!request) {
		LOG_INST_ERR(cfg->log, "ibi request is null");
		return -EINVAL;
	}

	k_mutex_lock(&data->lock, K_FOREVER);

	reg_val = sys_read8(cfg->base + I3CS08_STATUS_0);
	if (reg_val & BUS_IS_BUSY) {
		LOG_INST_ERR(cfg->log, "bus is busy");
		ret = -EBUSY;
		goto out;
	}

	switch (request->ibi_type) {
	case I3C_IBI_TARGET_INTR:
		if (it51xxx_i3cs_is_ibi_disable(dev) || !it51xxx_i3cs_dynamic_addr_valid(dev)) {
			LOG_INST_ERR(cfg->log, "ibi is disabled or dynamic address is invalid");
			ret = -EINVAL;
			goto out;
		}
		if (request->payload_len > sizeof(data->fifo.tx_data) + 1) {
			LOG_INST_ERR(cfg->log, "payload too large for ibi tir");
			ret = -ENOMEM;
			goto out;
		}
		if (config_target->bcr & I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE &&
		    request->payload_len == 0) {
			LOG_INST_ERR(cfg->log, "ibi should be with payload");
			ret = -EINVAL;
			goto out;
		}
		if (!(config_target->bcr & I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE) &&
		    request->payload_len != 0) {
			LOG_INST_ERR(cfg->log, "ibi should not be with payload");
			ret = -EINVAL;
			goto out;
		}

		if (request->payload_len == 0) {
			LOG_INST_DBG(cfg->log, "send ibi without payload");
			sys_write8(I3CS_EVENT_SELECT(EVT_IBI), cfg->base + I3CS0C_CONTROL_0);
		} else {
			/* set mandatory data byte */
			sys_write8(request->payload[0], cfg->base + I3CS0D_CONTROL_1);

			if (request->payload_len > 1) {
				if (FIELD_GET(IBI_MDB_GROUP_MASK, request->payload[0]) ==
				    IBI_MDB_GROUP_PENDING_READ_NOTI) {
					/* since the fifo for ibi payload and pending data is
					 * shared, the i3cs controller cannot issue an ibi with
					 * pending data notification if the ibi payload size
					 * exceeds 1.
					 */
					LOG_INST_ERR(cfg->log, "unsupported multiple payloads with "
							       "pending read noti. group");
					ret = -ENOTSUP;
					goto out;
				}

				ret = it51xxx_i3cs_prepare_tx_fifo(dev, &request->payload[1],
								   request->payload_len - 1);
				if (ret) {
					goto out;
				}

				sys_write8(EXTENDED_IBI_DATA | I3CS_EVENT_SELECT(EVT_IBI),
					   cfg->base + I3CS0C_CONTROL_0);
			} else {
				sys_write8(I3CS_EVENT_SELECT(EVT_IBI),
					   cfg->base + I3CS0C_CONTROL_0);
			}
		}
		break;
	case I3C_IBI_HOTJOIN:
		if (it51xxx_i3cs_is_hj_disable(dev) || it51xxx_i3cs_dynamic_addr_valid(dev)) {
			LOG_INST_ERR(cfg->log,
				     "hj is disabled or dynamic address is already assigned");
			ret = -EINVAL;
			goto out;
		}
		sys_write8(I3CS_EVENT_SELECT(EVT_HOT_JOIN), cfg->base + I3CS0C_CONTROL_0);
		break;
	case I3C_IBI_CONTROLLER_ROLE_REQUEST:
		LOG_INST_ERR(cfg->log, "unsupported controller role request");
		ret = -ENOTSUP;
		goto out;
	default:
		LOG_INST_ERR(cfg->log, "invalid ibi type(0x%x)", request->ibi_type);
		ret = -EINVAL;
		goto out;
	}

	if (it51xxx_i3cs_wait_to_complete(dev) != 0) {
		LOG_INST_WRN(cfg->log, "failed to issue ibi. maybe the controller is offline");
		sys_write8(I3CS_EVENT_SELECT(EVT_NORMAL_MODE), cfg->base + I3CS0C_CONTROL_0);
	}
out:
	k_mutex_unlock(&data->lock);

	return ret;
}
#endif /* CONFIG_I3C_USE_IBI */

static int it51xxx_i3cs_set_fifo_address(const struct device *dev)
{
	const struct it51xxx_i3cs_config *cfg = dev->config;
	struct it51xxx_i3cs_data *data = dev->data;

	if (sizeof(data->fifo.rx_data) <= 128 && sizeof(data->fifo.tx_data) <= 128) {
		if (FIFO_ADDR_HB(((uint32_t)&data->fifo.tx_data)) !=
		    FIFO_ADDR_HB(((uint32_t)&data->fifo.rx_data))) {
			LOG_INST_ERR(cfg->log,
				     "the msb of tx and rx fifo address should be the same");
			return -EINVAL;
		}
		sys_write8(FIFO_ADDR_LB((uint32_t)&data->fifo.rx_data),
			   cfg->base + I3CS43_RX_FIFO_BASE_ADDR_LB);
		sys_write8(FIFO_ADDR_LB((uint32_t)&data->fifo.tx_data),
			   cfg->base + I3CS42_TX_FIFO_BASE_ADDR_LB);
		sys_write8(FIFO_ADDR_HB((uint32_t)&data->fifo.tx_data),
			   cfg->base + I3CS41_TX_RX_FIFO_BASE_ADDR_HB);
		return 0;
	}

	if (!rx_direct_mode_is_enabled(dev) || !tx_direct_mode_is_enabled(dev)) {
		/* The tx and rx direct modes must be enabled simultaneously. */
		LOG_INST_ERR(cfg->log, "tx or rx fifo size is invalid for direct mode");
		return -EINVAL;
	}

	LOG_INST_DBG(cfg->log, "direct mode is enabled");
	sys_write8(sys_read8(cfg->base + I3CS4D_CONTROL_REG_4) | I3CS_DIRECT_MODE_ENABLE,
		   cfg->base + I3CS4D_CONTROL_REG_4);
	sys_write8(FIFO_ADDR_LB((uint32_t)&data->fifo.rx_data),
		   cfg->base + I3CS16_DIRECT_RX_FIFO_BASE_ADDR_LB);
	sys_write8(FIFO_ADDR_HB((uint32_t)&data->fifo.rx_data),
		   cfg->base + I3CS17_DIRECT_RX_FIFO_BASE_ADDR_HB);
	sys_write8(FIFO_ADDR_LB((uint32_t)&data->fifo.tx_data),
		   cfg->base + I3CS14_DIRECT_TX_FIFO_BASE_ADDR_LB);
	sys_write8(FIFO_ADDR_HB((uint32_t)&data->fifo.tx_data),
		   cfg->base + I3CS15_DIRECT_TX_FIFO_BASE_ADDR_HB);

	return 0;
}

static int it51xxx_i3cs_init(const struct device *dev)
{
	const struct it51xxx_i3cs_config *cfg = dev->config;
	struct it51xxx_i3cs_data *data = dev->data;
	struct i3c_config_target *config_target = &data->config_target;
	uint8_t reg_val;
	int ret;

	ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
	if (ret != 0) {
		LOG_INST_ERR(cfg->log, "failed to apply pinctrl, ret %d", ret);
		return ret;
	}

	/* set i3cs channel selection */
	sys_write8(cfg->io_channel, cfg->base + I3CS4D_CONTROL_REG_4);
	LOG_INST_DBG(cfg->log, "select io channel %d", cfg->io_channel);

	/* set extern enable bit */
	if (cfg->extern_enable.bit_mask > 7) {
		LOG_INST_ERR(cfg->log, "invalid bit mask %d for extern enable setting",
			     cfg->extern_enable.bit_mask);
		return -EINVAL;
	}
	sys_write8(sys_read8(cfg->extern_enable.addr) | BIT(cfg->extern_enable.bit_mask),
		   cfg->extern_enable.addr);

	/* set static address */
	sys_write8(I3CS_TARGET_ADDRESS(config_target->static_addr), cfg->base + I3CS07_CONFIG_2);

	/* set msb(vendor info) of get device status ccc */
	sys_write8(cfg->vendor_info, cfg->base + I3CS0F_CONTROL_3);

	/* set pid, bcr and dcr */
	if (config_target->pid_random) {
		sys_write8(sys_read8(cfg->base + I3CS05_CONFIG_1) | ID_RANDOM,
			   cfg->base + I3CS05_CONFIG_1);
		sys_write8(BYTE_0(config_target->pid), cfg->base + I3CS6C_PRAT_NUMBER_0);
		sys_write8(BYTE_1(config_target->pid), cfg->base + I3CS6D_PRAT_NUMBER_1);
		sys_write8(BYTE_2(config_target->pid), cfg->base + I3CS6E_PRAT_NUMBER_2);
		sys_write8(BYTE_3(config_target->pid), cfg->base + I3CS6F_PRAT_NUMBER_3);
		LOG_INST_INF(cfg->log, "set pid random value: %#llx", config_target->pid);
	}
	if (I3C_BCR_DEVICE_ROLE(config_target->bcr) == I3C_BCR_DEVICE_ROLE_I3C_CONTROLLER_CAPABLE) {
		LOG_INST_ERR(cfg->log, "i3cs doesn't support controller capability");
		return -ENOTSUP;
	}
	sys_write8(config_target->bcr, cfg->base + I3CS72_BCR);
	sys_write8(config_target->dcr, cfg->base + I3CS71_DCR);

	LOG_INST_INF(cfg->log, "tx fifo size(%d), address(0x%x)", sizeof(data->fifo.tx_data),
		     (uint32_t)&data->fifo.tx_data);
	LOG_INST_INF(cfg->log, "rx fifo size(%d), address(0x%x)", sizeof(data->fifo.rx_data),
		     (uint32_t)&data->fifo.rx_data);

	/* set tx and rx fifo size */
	for (uint8_t i = 0; i <= ARRAY_SIZE(fifo_size_table); i++) {
		if (i == ARRAY_SIZE(fifo_size_table)) {
			LOG_INST_ERR(cfg->log, "unknown rx fifo size %d",
				     sizeof(data->fifo.rx_data));
			return -ENOTSUP;
		}
		if (sizeof(data->fifo.rx_data) == fifo_size_table[i].fifo_size) {
			sys_write8(FIELD_PREP(I3CS_RX_FIFO_SIZE_MASK, fifo_size_table[i].value),
				   cfg->base + I3CS7A_RX_FIFO_SIZE);
			set_mwl_value(dev, sizeof(data->fifo.rx_data));
			break;
		}
	}
	for (uint8_t i = 0; i <= ARRAY_SIZE(fifo_size_table); i++) {
		if (i == ARRAY_SIZE(fifo_size_table)) {
			LOG_INST_ERR(cfg->log, "unknown tx fifo size %d",
				     sizeof(data->fifo.tx_data));
			return -ENOTSUP;
		}
		if (sizeof(data->fifo.tx_data) == fifo_size_table[i].fifo_size) {
			sys_write8(FIELD_PREP(I3CS_TX_FIFO_SIZE_MASK, fifo_size_table[i].value),
				   cfg->base + I3CS4A_TX_FIFO_SIZE);
			set_mrl_value(dev, sizeof(data->fifo.tx_data));
			break;
		}
	}

	ret = it51xxx_i3cs_set_fifo_address(dev);
	if (ret) {
		return ret;
	}

	if (tx_direct_mode_is_enabled(dev)) {
		sys_write8(sys_read8(cfg->base + I3CS4D_CONTROL_REG_4) |
				   I3CS_DIRECT_MODE_AUTO_CLR_TX_CNT,
			   cfg->base + I3CS4D_CONTROL_REG_4);
	}

#ifdef CONFIG_I3C_USE_IBI
	k_sem_init(&data->ibi_sync_sem, 0, 1);
#endif /*CONFIG_I3C_USE_IBI */

	k_mutex_init(&data->lock);

	/* clear interrupt/errwarn status and enable interrupt */
	sys_write8(sys_read8(cfg->base + I3CS1C_ERROR_WARNING_REG_0),
		   cfg->base + I3CS1C_ERROR_WARNING_REG_0);
	sys_write8(sys_read8(cfg->base + I3CS1D_ERROR_WARNING_REG_1),
		   cfg->base + I3CS1D_ERROR_WARNING_REG_1);
	sys_write8(sys_read8(cfg->base + I3CS09_STATUS_1), cfg->base + I3CS09_STATUS_1);
	reg_val = INT_STOP | INT_ERROR_WARNING;
	sys_write8(reg_val, cfg->base + I3CS11_INTERRUPT_ENABLE_CTRL_0);

	cfg->irq_config_func(dev);

	return 0;
}

static DEVICE_API(i3c, it51xxx_i3cs_api) = {
	.target_tx_write = it51xxx_i3cs_target_tx_write,
	.target_register = it51xxx_i3cs_target_register,
	.target_unregister = it51xxx_i3cs_target_unregister,

#ifdef CONFIG_I3C_USE_IBI
	.ibi_raise = it51xxx_i3cs_target_ibi_raise,
#endif /* CONFIG_I3C_USE_IBI */
};

static void it51xxx_i3cs_check_errwarn(const struct device *dev)
{
	const struct it51xxx_i3cs_config *cfg = dev->config;
	uint8_t errwarn0_val, errwarn1_val;

	errwarn0_val = sys_read8(cfg->base + I3CS1C_ERROR_WARNING_REG_0);
	errwarn1_val = sys_read8(cfg->base + I3CS1D_ERROR_WARNING_REG_1);
	if (errwarn0_val & INVALID_START) {
		LOG_INST_ERR(cfg->log, "isr: invalid start");
	}
	if (errwarn0_val & CONTROLLER_TERMINATED) {
		LOG_INST_WRN(cfg->log,
			     "isr: terminated by controller, flush the remaining %d bytes",
			     tx_byte_cnt_in_fifo(dev));
		sys_write8(sys_read8(cfg->base + I3CS2C_DATA_CTRL_0) | FLUSH_TX_FIFO,
			   cfg->base + I3CS2C_DATA_CTRL_0);
	}
	if (errwarn0_val & TX_FIFO_UNDERRUN) {
		LOG_INST_ERR(cfg->log, "isr: the tx fifo is underrun");
	}
	if (errwarn0_val & RX_FIFO_OVERRUN) {
		LOG_INST_ERR(cfg->log, "isr: the rx fifo is overrun");
	}
	if (errwarn1_val & S0_OR_S1_ERROR) {
		LOG_INST_ERR(cfg->log, "isr: s0 or s1 error is detected");
	}
	if (errwarn1_val & SDR_PARITY_ERROR) {
		LOG_INST_ERR(cfg->log, "isr: sdr parity error");
	}
	LOG_INST_DBG(cfg->log, "isr: error/warning is detected(0x%x, 0x%x)", errwarn0_val,
		     errwarn1_val);

	/* write 1 to clear the error and warning registers */
	sys_write8(errwarn0_val, cfg->base + I3CS1C_ERROR_WARNING_REG_0);
	sys_write8(errwarn1_val, cfg->base + I3CS1D_ERROR_WARNING_REG_1);
}

static inline void invoke_rx_cb(const struct device *dev, const bool ccc, uint8_t *buf,
				const size_t buf_len)
{
	struct it51xxx_i3cs_data *data = dev->data;
	const struct i3c_target_callbacks *target_cb =
		data->target_config ? data->target_config->callbacks : NULL;

	if (!ccc) {
		LOG_HEXDUMP_DBG(buf, buf_len, "isr: rx:");
#ifdef CONFIG_I3C_TARGET_BUFFER_MODE
		if (target_cb && target_cb->buf_write_received_cb) {
			target_cb->buf_write_received_cb(data->target_config, buf, buf_len);
		}
#endif /* CONFIG_I3C_TARGET_BUFFER_MODE */
	} else {
		LOG_HEXDUMP_WRN(buf, buf_len, "isr: unhandled ccc:");
	}
}

static void it51xxx_i3cs_process_rx_fifo(const struct device *dev, const bool ccc)
{
	const struct it51xxx_i3cs_config *cfg = dev->config;
	struct it51xxx_i3cs_data *data = dev->data;
	uint16_t byte_count = rx_byte_cnt_in_fifo(dev);

	if (rx_direct_mode_is_enabled(dev)) {
		uint8_t dfifo_status = sys_read8(cfg->base + I3CS4E_DIRECT_FIFO_STATUS);

		if (dfifo_status & I3CS_DIRECT_RX_DONE) {
			sys_write8(I3CS_DIRECT_RX_DONE, cfg->base + I3CS4E_DIRECT_FIFO_STATUS);
		} else {
			LOG_INST_WRN(cfg->log, "isr: rx pending, but rx not completed");
			return;
		}
		invoke_rx_cb(dev, ccc, data->fifo.rx_data, byte_count);
	} else {
		uint8_t read_ptr, idx;
		uint8_t rx_buf[sizeof(data->fifo.rx_data)];
		const size_t rx_fifo_sz = sizeof(data->fifo.rx_data);

		read_ptr = sys_read8(cfg->base + I3CS45_RX_FIFO_READ_PTR);
		idx = read_ptr % rx_fifo_sz;
		for (size_t i = 0; i < byte_count; i++) {
			rx_buf[i] =
				data->fifo.rx_data[(idx + i) >= rx_fifo_sz ? (idx + i) - rx_fifo_sz
									   : (idx + i)];
		}
		sys_write8(read_ptr + byte_count, cfg->base + I3CS45_RX_FIFO_READ_PTR);
		invoke_rx_cb(dev, ccc, rx_buf, byte_count);
	}
}

static void it51xxx_i3cs_process_tx_fifo(const struct device *dev, const bool ccc)
{
	const struct it51xxx_i3cs_config *cfg = dev->config;
	struct it51xxx_i3cs_data *data = dev->data;
	const struct i3c_target_callbacks *target_cb =
		data->target_config ? data->target_config->callbacks : NULL;

	if (tx_direct_mode_is_enabled(dev)) {
		uint8_t dfifo_status = sys_read8(cfg->base + I3CS4E_DIRECT_FIFO_STATUS);

		if (dfifo_status & I3CS_DIRECT_TX_DONE) {
			sys_write8(I3CS_DIRECT_TX_DONE, cfg->base + I3CS4E_DIRECT_FIFO_STATUS);
		} else {
			return;
		}
	}

	if (!ccc) {
#ifdef CONFIG_I3C_TARGET_BUFFER_MODE
		if (target_cb && target_cb->buf_read_requested_cb) {
			target_cb->buf_read_requested_cb(data->target_config, NULL, NULL, NULL);
		}
#endif /* CONFIG_I3C_TARGET_BUFFER_MODE */
	}
}

static void it51xxx_i3cs_isr(const struct device *dev)
{
	const struct it51xxx_i3cs_config *cfg = dev->config;
	struct it51xxx_i3cs_data *data = dev->data;
	const struct i3c_target_callbacks *target_cb =
		data->target_config ? data->target_config->callbacks : NULL;
	uint8_t int_status_1, int_status_2;

	int_status_1 = sys_read8(cfg->base + I3CS09_STATUS_1);
	int_status_2 = sys_read8(cfg->base + I3CS0A_STATUS_2);
	LOG_INST_DBG(cfg->log, "isr: interrupt status 0x%x 0x%x", int_status_1, int_status_2);

	if (int_status_1 & INT_DYN_ADDR_CHANGE) {
		if (it51xxx_i3cs_dynamic_addr_valid(dev)) {
			if (data->target_config) {
				data->target_config->address = DYNAMIC_ADDRESS(
					sys_read8(cfg->base + I3CS64_DYNAMIC_ADDRESS));
			}
			LOG_INST_DBG(cfg->log, "dynamic address is assigned");
		} else {
			if (data->target_config) {
				data->target_config->address = 0;
			}
			LOG_INST_DBG(cfg->log, "dynamic address is reset");
		}
	}

	if (int_status_1 & INT_ERROR_WARNING) {
		it51xxx_i3cs_check_errwarn(dev);
	}

	if (int_status_1 & INT_STOP) {
		bool is_unhandled_ccc =
			(!(int_status_1 & INT_ADDR_MATCHED) || (int_status_1 & INT_CCC));

		if (int_status_1 & INT_RX_PENDING) {
			it51xxx_i3cs_process_rx_fifo(dev, is_unhandled_ccc);
		} else {
			it51xxx_i3cs_process_tx_fifo(dev, is_unhandled_ccc);
		}

		if (!is_unhandled_ccc) {
			if (target_cb != NULL && target_cb->stop_cb) {
				target_cb->stop_cb(data->target_config);
			}
		}
	}

	switch (int_status_2 & EVENT_DETECT_MASK) {
	case FIELD_PREP(EVENT_DETECT_MASK, REQUEST_NACK_EVT):
		LOG_INST_ERR(cfg->log, "isr: nack is detected");
		break;
	case FIELD_PREP(EVENT_DETECT_MASK, REQUEST_NOT_SENT):
		LOG_INST_ERR(cfg->log, "isr: request is not sent yet");
		break;
	case FIELD_PREP(EVENT_DETECT_MASK, REQUEST_ACK_EVT):
		if (int_status_2 & INT_EVENT) {
			LOG_INST_DBG(cfg->log, "isr: tir/hj is completed");
		}
		break;
	default:
		break;
	};

	if (int_status_2 & EVENT_DETECT_MASK) {
#ifdef CONFIG_I3C_USE_IBI
		k_sem_give(&data->ibi_sync_sem);
#endif /* CONFIG_I3C_USE_IBI */
	}

	if (int_status_2 & INT_TARGET_RST) {
		LOG_INST_INF(cfg->log, "isr: target reset pattern is detected");
	}

	sys_write8(int_status_1, cfg->base + I3CS09_STATUS_1);
	sys_write8(int_status_2, cfg->base + I3CS0A_STATUS_2);
}

#define IT51XXX_I3CS_EXTERN_ENABLE(n)                                                              \
	{                                                                                          \
		.addr = DT_INST_PROP_BY_IDX(n, extern_enable, 0),                                  \
		.bit_mask = DT_INST_PROP_BY_IDX(n, extern_enable, 1),                              \
	}

#define IT51XXX_I3CS_INIT(n)                                                                       \
	LOG_INSTANCE_REGISTER(DT_NODE_FULL_NAME_TOKEN(DT_DRV_INST(n)), n,                          \
			      CONFIG_I3C_IT51XXX_LOG_LEVEL);                                       \
	PINCTRL_DT_INST_DEFINE(n);                                                                 \
	static void it51xxx_i3cs_config_func_##n(const struct device *dev)                         \
	{                                                                                          \
		IRQ_CONNECT(DT_INST_IRQN(n), 0, it51xxx_i3cs_isr, DEVICE_DT_INST_GET(n), 0);       \
		irq_enable(DT_INST_IRQN(n));                                                       \
	};                                                                                         \
	static const struct it51xxx_i3cs_config i3c_config_##n = {                                 \
		.base = DT_INST_REG_ADDR(n),                                                       \
		.irq_config_func = it51xxx_i3cs_config_func_##n,                                   \
		.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),                                         \
		.io_channel = DT_INST_PROP(n, io_channel),                                         \
		.extern_enable = IT51XXX_I3CS_EXTERN_ENABLE(n),                                    \
		.vendor_info = DT_INST_PROP_OR(n, vendor_info_fields, 0x0),                        \
		LOG_INSTANCE_PTR_INIT(log, DT_NODE_FULL_NAME_TOKEN(DT_DRV_INST(n)), n)};           \
	static struct it51xxx_i3cs_data i3c_data_##n = {                                           \
		.config_target.static_addr = DT_INST_PROP_OR(n, static_address, 0),                \
		.config_target.pid = DT_INST_PROP_OR(n, pid_random_value, 0),                      \
		.config_target.pid_random = DT_INST_NODE_HAS_PROP(n, pid_random_value),            \
		.config_target.bcr = DT_INST_PROP_OR(n, bcr, 0x0F),                                \
		.config_target.dcr = DT_INST_PROP_OR(n, dcr, 0),                                   \
		.config_target.supported_hdr = false,                                              \
	};                                                                                         \
	DEVICE_DT_INST_DEFINE(n, it51xxx_i3cs_init, NULL, &i3c_data_##n, &i3c_config_##n,          \
			      POST_KERNEL, CONFIG_I3C_CONTROLLER_INIT_PRIORITY,                    \
			      &it51xxx_i3cs_api);

DT_INST_FOREACH_STATUS_OKAY(IT51XXX_I3CS_INIT)
