/*
 * Copyright (c) 2024 Analog Devices, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT adi_max32_i2c

#include <errno.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/i2c/rtio.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/clock_control/adi_max32_clock_control.h>
#include <zephyr/irq.h>

#include <wrap_max32_i2c.h>

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

#define ADI_MAX32_I2C_INT_FL0_MASK 0x00FFFFFF
#define ADI_MAX32_I2C_INT_FL1_MASK 0x7

#define ADI_MAX32_I2C_STATUS_MASTER_BUSY BIT(5)

#define I2C_RECOVER_MAX_RETRIES 3
#define I2C_STANDAR_BITRATE_CLKHI 0x12b

static int complete_flag;

/* Driver config */
struct max32_i2c_config {
	mxc_i2c_regs_t *regs;
	const struct pinctrl_dev_config *pctrl;
	const struct device *clock;
	struct max32_perclk perclk;
	uint32_t bitrate;
#if defined(CONFIG_I2C_MAX32_INTERRUPT)
	uint8_t irqn;
	void (*irq_config_func)(const struct device *dev);
#endif
};

struct max32_i2c_data {
	mxc_i2c_req_t req;
	const struct device *dev;
	uint8_t target_mode;
	uint8_t flags;
	struct i2c_rtio *ctx;
	uint32_t readb;
	uint32_t written;
	uint8_t second_msg_flag;
#if defined(CONFIG_I2C_MAX32_INTERRUPT)
	int err;
#endif
};

static int max32_configure(const struct device *dev,
				uint32_t dev_cfg)
{
	struct i2c_rtio *const ctx = ((struct max32_i2c_data *)
		dev->data)->ctx;

	return i2c_rtio_configure(ctx, dev_cfg);
}

static int max32_do_configure(const struct device *dev, uint32_t dev_cfg)
{
	int ret = 0;
	const struct max32_i2c_config *const cfg = dev->config;
	mxc_i2c_regs_t *i2c = cfg->regs;

	switch (I2C_SPEED_GET(dev_cfg)) {
	case I2C_SPEED_STANDARD: /** I2C Standard Speed: 100 kHz */
		ret = MXC_I2C_SetFrequency(i2c, MXC_I2C_STD_MODE);
		break;

	case I2C_SPEED_FAST: /** I2C Fast Speed: 400 kHz */
		ret = MXC_I2C_SetFrequency(i2c, MXC_I2C_FAST_SPEED);
		break;

#if defined(MXC_I2C_FASTPLUS_SPEED)
	case I2C_SPEED_FAST_PLUS: /** I2C Fast Plus Speed: 1 MHz */
		ret = MXC_I2C_SetFrequency(i2c, MXC_I2C_FASTPLUS_SPEED);
		break;
#endif

#if defined(MXC_I2C_HIGH_SPEED)
	case I2C_SPEED_HIGH: /** I2C High Speed: 3.4 MHz */
		ret = MXC_I2C_SetFrequency(i2c, MXC_I2C_HIGH_SPEED);
		break;
#endif

	default:
		/* Speed not supported */
		return -ENOTSUP;
	}

	return ((ret > 0) ? 0 : -EIO);
}

static void max32_complete(const struct device *dev, int status);

static int max32_msg_start(const struct device *dev, uint8_t flags,
				 uint8_t *buf, size_t buf_len, uint16_t i2c_addr)
{
	int ret = 0;
	const struct max32_i2c_config *const cfg = dev->config;
	struct max32_i2c_data *data = dev->data;
	mxc_i2c_regs_t *i2c = cfg->regs;
	mxc_i2c_req_t *req = &data->req;
	uint8_t target_rw;

	req->i2c = i2c;
	req->addr = i2c_addr;

	if (data->second_msg_flag == 0) {
		MXC_I2C_ClearRXFIFO(i2c);
		MXC_I2C_ClearTXFIFO(i2c);
		MXC_I2C_SetRXThreshold(i2c, 1);

		/* First message should always begin with a START condition */
		flags |= I2C_MSG_RESTART;
	}

	if (flags & I2C_MSG_READ) {
		req->rx_buf = (unsigned char *)buf;
		req->rx_len = buf_len;
		req->tx_buf = NULL;
		req->tx_len = 0;
		target_rw = (i2c_addr << 1) | 0x1;
	} else {
		req->tx_buf = (unsigned char *)buf;
		req->tx_len = buf_len;
		req->rx_buf = NULL;
		req->rx_len = 0;
		target_rw = (i2c_addr << 1) & ~0x1;
	}
	data->flags = flags;
	data->readb = 0;
	data->written = 0;
	data->err = 0;

	MXC_I2C_ClearFlags(i2c, ADI_MAX32_I2C_INT_FL0_MASK, ADI_MAX32_I2C_INT_FL1_MASK);
	MXC_I2C_EnableInt(i2c, ADI_MAX32_I2C_INT_EN0_ERR, 0);
	Wrap_MXC_I2C_SetRxCount(i2c, req->rx_len);
	if ((data->flags & I2C_MSG_RESTART)) {
		MXC_I2C_EnableInt(i2c, ADI_MAX32_I2C_INT_EN0_ADDR_ACK, 0);
		MXC_I2C_Start(i2c);
		Wrap_MXC_I2C_WaitForRestart(i2c);
		MXC_I2C_WriteTXFIFO(i2c, &target_rw, 1);
	} else {
		if (req->tx_len) {
			data->written = MXC_I2C_WriteTXFIFO(i2c, req->tx_buf, 1);
			MXC_I2C_EnableInt(i2c, ADI_MAX32_I2C_INT_EN0_TX_THD, 0);
		} else {
			MXC_I2C_EnableInt(i2c, ADI_MAX32_I2C_INT_EN0_RX_THD, 0);
		}
	}

	if (data->err) {
		MXC_I2C_Stop(i2c);
		ret = data->err;
	}

	return ret;
}

static int max32_transfer(const struct device *dev, struct i2c_msg *msgs, uint8_t num_msgs,
			uint16_t target_address)
{
	struct i2c_rtio *const ctx = ((struct max32_i2c_data *)
		dev->data)->ctx;
	((struct max32_i2c_data *)dev->data)->second_msg_flag = 0;

	return i2c_rtio_transfer(ctx, msgs, num_msgs, target_address);
}



static void i2c_max32_isr_controller(const struct device *dev, mxc_i2c_regs_t *i2c)
{
	struct max32_i2c_data *data = dev->data;
	mxc_i2c_req_t *req = &data->req;
	uint32_t written, readb;
	uint32_t txfifolevel;
	uint32_t int_fl0, int_fl1;
	uint32_t int_en0, int_en1;

	written = data->written;
	readb = data->readb;

	Wrap_MXC_I2C_GetIntEn(i2c, &int_en0, &int_en1);
	MXC_I2C_GetFlags(i2c, &int_fl0, &int_fl1);
	MXC_I2C_ClearFlags(i2c, ADI_MAX32_I2C_INT_FL0_MASK, ADI_MAX32_I2C_INT_FL1_MASK);
	txfifolevel = Wrap_MXC_I2C_GetTxFIFOLevel(i2c);

	if (int_fl0 & ADI_MAX32_I2C_INT_FL0_ERR) {
		data->err = -EIO;
		Wrap_MXC_I2C_SetIntEn(i2c, 0, 0);
		max32_complete(dev, data->err);
		return;
	}

	if (int_fl0 & ADI_MAX32_I2C_INT_FL0_ADDR_ACK) {
		MXC_I2C_DisableInt(i2c, ADI_MAX32_I2C_INT_EN0_ADDR_ACK, 0);
		if (written < req->tx_len) {
			MXC_I2C_EnableInt(i2c, ADI_MAX32_I2C_INT_EN0_TX_THD, 0);
		} else if (readb < req->rx_len) {
			MXC_I2C_EnableInt(
				i2c, ADI_MAX32_I2C_INT_EN0_RX_THD | ADI_MAX32_I2C_INT_EN0_DONE, 0);
		}
	}

	if (req->tx_len &&
	    (int_fl0 & (ADI_MAX32_I2C_INT_FL0_TX_THD | ADI_MAX32_I2C_INT_FL0_DONE))) {
		if (written < req->tx_len) {
			written += MXC_I2C_WriteTXFIFO(i2c, &req->tx_buf[written],
						       req->tx_len - written);
		} else {
			if (!(int_en0 & ADI_MAX32_I2C_INT_EN0_DONE)) {
				/* We are done, stop sending more data */
				MXC_I2C_DisableInt(i2c, ADI_MAX32_I2C_INT_EN0_TX_THD, 0);
				if (data->flags & I2C_MSG_STOP) {
					MXC_I2C_EnableInt(i2c, ADI_MAX32_I2C_INT_EN0_DONE, 0);
					/* Done flag is not set if stop/restart is not set */
					Wrap_MXC_I2C_Stop(i2c);
				} else {
					complete_flag++;
				}
			}

			if ((int_fl0 & ADI_MAX32_I2C_INT_FL0_DONE)) {
				MXC_I2C_DisableInt(i2c, ADI_MAX32_I2C_INT_EN0_DONE, 0);
				complete_flag++;
			}
		}
	} else if ((int_fl0 & (ADI_MAX32_I2C_INT_FL0_RX_THD | ADI_MAX32_I2C_INT_FL0_DONE))) {
		readb += MXC_I2C_ReadRXFIFO(i2c, &req->rx_buf[readb], req->rx_len - readb);
		if (readb == req->rx_len) {
			MXC_I2C_DisableInt(i2c, ADI_MAX32_I2C_INT_EN0_RX_THD, 0);
			if (data->flags & I2C_MSG_STOP) {
				MXC_I2C_DisableInt(i2c, ADI_MAX32_I2C_INT_EN0_DONE, 0);
				Wrap_MXC_I2C_Stop(i2c);
				complete_flag++;
			} else {
				if (int_fl0 & ADI_MAX32_I2C_INT_FL0_DONE) {
					MXC_I2C_DisableInt(i2c, ADI_MAX32_I2C_INT_EN0_DONE, 0);
				}
			}
		} else if ((int_en0 & ADI_MAX32_I2C_INT_EN0_DONE) &&
			   (int_fl0 & ADI_MAX32_I2C_INT_FL0_DONE)) {
			MXC_I2C_DisableInt(
				i2c, (ADI_MAX32_I2C_INT_EN0_RX_THD | ADI_MAX32_I2C_INT_EN0_DONE),
				0);
			Wrap_MXC_I2C_SetRxCount(i2c, req->rx_len - readb);
			MXC_I2C_EnableInt(i2c, ADI_MAX32_I2C_INT_EN0_ADDR_ACK, 0);
			i2c->fifo = (req->addr << 1) | 0x1;
			Wrap_MXC_I2C_Restart(i2c);
		}
	}
	data->written = written;
	data->readb = readb;

	if (complete_flag == 1) {
		max32_complete(dev, 0);
		complete_flag = 0;
	}
}

static bool max32_start(const struct device *dev)
{
	struct max32_i2c_data *data = dev->data;
	struct i2c_rtio *ctx = data->ctx;
	struct rtio_sqe *sqe = &ctx->txn_curr->sqe;
	struct i2c_dt_spec *dt_spec = sqe->iodev->data;
	int res = 0;

	switch (sqe->op) {
	case RTIO_OP_RX:
		return max32_msg_start(dev, I2C_MSG_READ | sqe->iodev_flags,
					    sqe->rx.buf, sqe->rx.buf_len, dt_spec->addr);
	case RTIO_OP_TINY_TX:
		data->second_msg_flag = 0;
		return max32_msg_start(dev, I2C_MSG_WRITE | sqe->iodev_flags,
					    (uint8_t *)sqe->tiny_tx.buf, sqe->tiny_tx.buf_len,
					    dt_spec->addr);
	case RTIO_OP_TX:
		return max32_msg_start(dev, I2C_MSG_WRITE | sqe->iodev_flags,
					    (uint8_t *)sqe->tx.buf, sqe->tx.buf_len,
					    dt_spec->addr);
	case RTIO_OP_I2C_CONFIGURE:
		res = max32_do_configure(dev, sqe->i2c_config);
		return i2c_rtio_complete(data->ctx, res);
	default:
		LOG_ERR("Invalid op code %d for submission %p\n", sqe->op, (void *)sqe);
		return i2c_rtio_complete(data->ctx, -EINVAL);
	}
}

static void max32_complete(const struct device *dev, int status)
{
	struct max32_i2c_data *data = dev->data;
	struct i2c_rtio *const ctx = data->ctx;
	const struct max32_i2c_config *const cfg = dev->config;

	if (cfg->regs->clkhi == I2C_STANDAR_BITRATE_CLKHI) {
		/* When I2C is configured in Standard Bitrate 100KHz
		 * Hardware completes first read sample transaction
		 * and gets stuck in idle instead of starting the
		 * next transaction, if given k_busy_wait for
		 * 20 us ~= 2 additional I2C cycles sample read
		 * won't have any issues but all other transactions
		 * (like setup of sensor) will have this unnecessary
		 * delay. This doesn't happen when using Fast
		 * Bitrate 400Hz.
		 */
		LOG_ERR("For Standard speed HW needs more time to run");
		return;
	}

	if (i2c_rtio_complete(ctx, status)) {
		data->second_msg_flag = 1;
		max32_start(dev);
	} else {
		data->second_msg_flag = 0;
	}
}

static void max32_submit(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe)
{
	struct max32_i2c_data *data = dev->data;
	struct i2c_rtio *const ctx = data->ctx;

	if (i2c_rtio_submit(ctx, iodev_sqe)) {
		max32_start(dev);
	}
}


static void i2c_max32_isr(const struct device *dev)
{
	const struct max32_i2c_config *cfg = dev->config;
	struct max32_i2c_data *data = dev->data;
	mxc_i2c_regs_t *i2c = cfg->regs;

	if (data->target_mode == 0) {
		i2c_max32_isr_controller(dev, i2c);
		return;
	}
}

static int i2c_max32_init(const struct device *dev)
{
	const struct max32_i2c_config *const cfg = dev->config;
	struct max32_i2c_data *data = dev->data;
	mxc_i2c_regs_t *i2c = cfg->regs;
	int ret = 0;

	if (!device_is_ready(cfg->clock)) {
		return -ENODEV;
	}

	MXC_I2C_Shutdown(i2c); /* Clear everything out */

	ret = clock_control_on(cfg->clock, (clock_control_subsys_t)&cfg->perclk);
	if (ret) {
		return ret;
	}

	ret = pinctrl_apply_state(cfg->pctrl, PINCTRL_STATE_DEFAULT);
	if (ret) {
		return ret;
	}

	ret = MXC_I2C_Init(i2c, 1, 0); /* Configure as master */
	if (ret) {
		return ret;
	}

	MXC_I2C_SetFrequency(i2c, cfg->bitrate);

#if defined(CONFIG_I2C_MAX32_INTERRUPT)
	cfg->irq_config_func(dev);
#endif

#ifdef CONFIG_I2C_MAX32_INTERRUPT
	irq_enable(cfg->irqn);

#endif

	data->dev = dev;

	i2c_rtio_init(data->ctx, dev);
	return ret;
}

static const struct i2c_driver_api max32_driver_api = {
	.configure = max32_configure,
	.transfer = max32_transfer,
	.iodev_submit = max32_submit,
};

#if defined(CONFIG_I2C_TARGET) || defined(CONFIG_I2C_MAX32_INTERRUPT)
#define I2C_MAX32_CONFIG_IRQ_FUNC(n)                                                             \
	.irq_config_func = i2c_max32_irq_config_func_##n, .irqn = DT_INST_IRQN(n),

#define I2C_MAX32_IRQ_CONFIG_FUNC(n)                                                              \
	static void i2c_max32_irq_config_func_##n(const struct device *dev)                        \
	{                                                                                          \
		IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), i2c_max32_isr,              \
			    DEVICE_DT_INST_GET(n), 0);                                             \
	}
#else
#define I2C_MAX32_CONFIG_IRQ_FUNC(n)
#define I2C_MAX32_IRQ_CONFIG_FUNC(n)
#endif


#define DEFINE_I2C_MAX32(_num)                                                                   \
	PINCTRL_DT_INST_DEFINE(_num);                                                              \
	I2C_MAX32_IRQ_CONFIG_FUNC(_num)                                                            \
	static const struct max32_i2c_config max32_i2c_dev_cfg_##_num = {                          \
		.regs = (mxc_i2c_regs_t *)DT_INST_REG_ADDR(_num),                                  \
		.pctrl = PINCTRL_DT_INST_DEV_CONFIG_GET(_num),                                     \
		.clock = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(_num)),                                 \
		.perclk.bus = DT_INST_CLOCKS_CELL(_num, offset),                                   \
		.perclk.bit = DT_INST_CLOCKS_CELL(_num, bit),                                      \
		.bitrate = DT_INST_PROP(_num, clock_frequency),                                    \
		I2C_MAX32_CONFIG_IRQ_FUNC(_num)};                                              \
		I2C_RTIO_DEFINE(_i2c##_num##_max32_rtio,			\
		DT_INST_PROP_OR(_num, sq_size, CONFIG_I2C_RTIO_SQ_SIZE),	\
		DT_INST_PROP_OR(_num, cq_size, CONFIG_I2C_RTIO_CQ_SIZE));	\
	static struct max32_i2c_data max32_i2c_data_##_num = {                                     \
		.ctx = &CONCAT(_i2c, _num, _max32_rtio),		\
	};								\
	I2C_DEVICE_DT_INST_DEFINE(_num, i2c_max32_init, NULL, &max32_i2c_data_##_num,              \
				  &max32_i2c_dev_cfg_##_num, PRE_KERNEL_2,                         \
				  CONFIG_I2C_INIT_PRIORITY, &max32_driver_api);

DT_INST_FOREACH_STATUS_OKAY(DEFINE_I2C_MAX32)
