/*
 * Copyright (c) 2021 BrainCo Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT gd_gd32_i2c

#include <errno.h>

#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/clock_control/gd32.h>
#include <zephyr/kernel.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/reset.h>
#include <zephyr/drivers/i2c.h>

#include <gd32_i2c.h>

#include <zephyr/logging/log.h>
#include <zephyr/irq.h>
LOG_MODULE_REGISTER(i2c_gd32, CONFIG_I2C_LOG_LEVEL);

#include "i2c-priv.h"

/* Bus error */
#define I2C_GD32_ERR_BERR BIT(0)
/* Arbitration lost */
#define I2C_GD32_ERR_LARB BIT(1)
/* No ACK received */
#define I2C_GD32_ERR_AERR BIT(2)
/* I2C bus busy */
#define I2C_GD32_ERR_BUSY BIT(4)

struct i2c_gd32_config {
	uint32_t reg;
	uint32_t bitrate;
	uint16_t clkid;
	struct reset_dt_spec reset;
	const struct pinctrl_dev_config *pcfg;
	void (*irq_cfg_func)(void);
};

struct i2c_gd32_data {
	struct k_sem bus_mutex;
	struct k_sem sync_sem;
	uint32_t dev_config;
	uint16_t addr1;
	uint16_t addr2;
	uint32_t xfer_len;
	struct i2c_msg *current;
	uint8_t errs;
	bool is_restart;
};

static inline void i2c_gd32_enable_interrupts(const struct i2c_gd32_config *cfg)
{
	I2C_CTL1(cfg->reg) |= I2C_CTL1_ERRIE;
	I2C_CTL1(cfg->reg) |= I2C_CTL1_EVIE;
	I2C_CTL1(cfg->reg) |= I2C_CTL1_BUFIE;
}

static inline void i2c_gd32_disable_interrupts(const struct i2c_gd32_config *cfg)
{
	I2C_CTL1(cfg->reg) &= ~I2C_CTL1_ERRIE;
	I2C_CTL1(cfg->reg) &= ~I2C_CTL1_EVIE;
	I2C_CTL1(cfg->reg) &= ~I2C_CTL1_BUFIE;
}

static inline void i2c_gd32_xfer_read(struct i2c_gd32_data *data,
				      const struct i2c_gd32_config *cfg)
{
	data->current->len--;
	*data->current->buf = I2C_DATA(cfg->reg);
	data->current->buf++;

	if ((data->xfer_len > 0U) &&
	    (data->current->len == 0U)) {
		data->current++;
	}
}

static inline void i2c_gd32_xfer_write(struct i2c_gd32_data *data,
				       const struct i2c_gd32_config *cfg)
{
	data->current->len--;
	I2C_DATA(cfg->reg) = *data->current->buf;
	data->current->buf++;

	if ((data->xfer_len > 0U) &&
	    (data->current->len == 0U)) {
		data->current++;
	}
}

static void i2c_gd32_handle_rbne(const struct device *dev)
{
	struct i2c_gd32_data *data = dev->data;
	const struct i2c_gd32_config *cfg = dev->config;

	switch (data->xfer_len) {
	case 0:
		/* Unwanted data received, ignore it. */
		k_sem_give(&data->sync_sem);
		break;
	case 1:
		/* If total_read_length == 1, read the data directly. */
		data->xfer_len--;
		i2c_gd32_xfer_read(data, cfg);

		k_sem_give(&data->sync_sem);

		break;
	case 2:
		__fallthrough;
	case 3:
		/*
		 * If total_read_length == 2, or total_read_length > 3
		 * and remaining_read_length == 3, disable the RBNE
		 * interrupt.
		 * Remaining data will be read from BTC interrupt.
		 */
		I2C_CTL1(cfg->reg) &= ~I2C_CTL1_BUFIE;
		break;
	default:
		/*
		 * If total_read_length > 3 and remaining_read_length > 3,
		 * read the data directly.
		 */
		data->xfer_len--;
		i2c_gd32_xfer_read(data, cfg);
		break;
	}

}

static void i2c_gd32_handle_tbe(const struct device *dev)
{
	struct i2c_gd32_data *data = dev->data;
	const struct i2c_gd32_config *cfg = dev->config;

	if (data->xfer_len > 0U) {
		data->xfer_len--;
		if (data->xfer_len == 0U) {
			/*
			 * This is the last data to transmit, disable the TBE interrupt.
			 * Use the BTC interrupt to indicate the write data complete state.
			 */
			I2C_CTL1(cfg->reg) &= ~I2C_CTL1_BUFIE;
		}
		i2c_gd32_xfer_write(data, cfg);

	} else {
		/* Enter stop condition */
		I2C_CTL0(cfg->reg) |= I2C_CTL0_STOP;

		k_sem_give(&data->sync_sem);
	}
}

static void i2c_gd32_handle_btc(const struct device *dev)
{
	struct i2c_gd32_data *data = dev->data;
	const struct i2c_gd32_config *cfg = dev->config;

	if (data->current->flags & I2C_MSG_READ) {
		uint32_t counter = 0U;

		switch (data->xfer_len) {
		case 2:
			/* Stop condition must be generated before reading the last two bytes. */
			I2C_CTL0(cfg->reg) |= I2C_CTL0_STOP;

			for (counter = 2U; counter > 0; counter--) {
				data->xfer_len--;
				i2c_gd32_xfer_read(data, cfg);
			}

			k_sem_give(&data->sync_sem);

			break;
		case 3:
			/* Clear ACKEN bit */
			I2C_CTL0(cfg->reg) &= ~I2C_CTL0_ACKEN;

			data->xfer_len--;
			i2c_gd32_xfer_read(data, cfg);

			break;
		default:
			i2c_gd32_handle_rbne(dev);
			break;
		}
	} else {
		i2c_gd32_handle_tbe(dev);
	}
}

static void i2c_gd32_handle_addsend(const struct device *dev)
{
	struct i2c_gd32_data *data = dev->data;
	const struct i2c_gd32_config *cfg = dev->config;

	if ((data->current->flags & I2C_MSG_READ) && (data->xfer_len <= 2U)) {
		I2C_CTL0(cfg->reg) &= ~I2C_CTL0_ACKEN;
	}

	/* Clear ADDSEND bit */
	I2C_STAT0(cfg->reg);
	I2C_STAT1(cfg->reg);

	if (data->is_restart) {
		data->is_restart = false;
		data->current->flags &= ~I2C_MSG_RW_MASK;
		data->current->flags |= I2C_MSG_READ;
		/* Enter repeated start condition */
		I2C_CTL0(cfg->reg) |= I2C_CTL0_START;

		return;
	}

	if ((data->current->flags & I2C_MSG_READ) && (data->xfer_len == 1U)) {
		/* Enter stop condition */
		I2C_CTL0(cfg->reg) |= I2C_CTL0_STOP;
	}
}

static void i2c_gd32_event_isr(const struct device *dev)
{
	struct i2c_gd32_data *data = dev->data;
	const struct i2c_gd32_config *cfg = dev->config;
	uint32_t stat;

	stat = I2C_STAT0(cfg->reg);

	if (stat & I2C_STAT0_SBSEND) {
		if (data->current->flags & I2C_MSG_READ) {
			I2C_DATA(cfg->reg) = (data->addr1 << 1U) | 1U;
		} else {
			I2C_DATA(cfg->reg) = (data->addr1 << 1U) | 0U;
		}
	} else if (stat & I2C_STAT0_ADD10SEND) {
		I2C_DATA(cfg->reg) = data->addr2;
	} else if (stat & I2C_STAT0_ADDSEND) {
		i2c_gd32_handle_addsend(dev);
	/*
	 * Must handle BTC first.
	 * For I2C_STAT0, BTC is the superset of RBNE and TBE.
	 */
	} else if (stat & I2C_STAT0_BTC) {
		i2c_gd32_handle_btc(dev);
	} else if (stat & I2C_STAT0_RBNE) {
		i2c_gd32_handle_rbne(dev);
	} else if (stat & I2C_STAT0_TBE) {
		i2c_gd32_handle_tbe(dev);
	}
}

static void i2c_gd32_error_isr(const struct device *dev)
{
	struct i2c_gd32_data *data = dev->data;
	const struct i2c_gd32_config *cfg = dev->config;
	uint32_t stat;

	stat = I2C_STAT0(cfg->reg);

	if (stat & I2C_STAT0_BERR) {
		I2C_STAT0(cfg->reg) &= ~I2C_STAT0_BERR;
		data->errs |= I2C_GD32_ERR_BERR;
	}

	if (stat & I2C_STAT0_LOSTARB) {
		I2C_STAT0(cfg->reg) &= ~I2C_STAT0_LOSTARB;
		data->errs |= I2C_GD32_ERR_LARB;
	}

	if (stat & I2C_STAT0_AERR) {
		I2C_STAT0(cfg->reg) &= ~I2C_STAT0_AERR;
		data->errs |= I2C_GD32_ERR_AERR;
	}

	if (data->errs != 0U) {
		/* Enter stop condition */
		I2C_CTL0(cfg->reg) |= I2C_CTL0_STOP;

		k_sem_give(&data->sync_sem);
	}
}

static void i2c_gd32_log_err(struct i2c_gd32_data *data)
{
	if (data->errs & I2C_GD32_ERR_BERR) {
		LOG_ERR("Bus error");
	}

	if (data->errs & I2C_GD32_ERR_LARB) {
		LOG_ERR("Arbitration lost");
	}

	if (data->errs & I2C_GD32_ERR_AERR) {
		LOG_ERR("No ACK received");
	}

	if (data->errs & I2C_GD32_ERR_BUSY) {
		LOG_ERR("I2C bus busy");
	}
}

static void i2c_gd32_xfer_begin(const struct device *dev)
{
	struct i2c_gd32_data *data = dev->data;
	const struct i2c_gd32_config *cfg = dev->config;

	k_sem_reset(&data->sync_sem);

	data->errs = 0U;
	data->is_restart = false;

	/* Default to set ACKEN bit. */
	I2C_CTL0(cfg->reg) |= I2C_CTL0_ACKEN;

	if (data->current->flags & I2C_MSG_READ) {
		/* For 2 bytes read, use POAP bit to give NACK for the last data receiving. */
		if (data->xfer_len == 2U) {
			I2C_CTL0(cfg->reg) |= I2C_CTL0_POAP;
		}

		/*
		 * For read on 10 bits address mode, start condition will happen twice.
		 * Transfer sequence as below:
		 *   S addr1+W addr2 S addr1+R
		 * Use a is_restart flag to cover this case.
		 */
		if (data->dev_config & I2C_ADDR_10_BITS) {
			data->is_restart = true;
			data->current->flags &= ~I2C_MSG_RW_MASK;
		}
	}

	i2c_gd32_enable_interrupts(cfg);

	/* Enter repeated start condition */
	I2C_CTL0(cfg->reg) |= I2C_CTL0_START;
}

static int i2c_gd32_xfer_end(const struct device *dev)
{
	struct i2c_gd32_data *data = dev->data;
	const struct i2c_gd32_config *cfg = dev->config;

	i2c_gd32_disable_interrupts(cfg);

	/* Wait for stop condition is done. */
	while (I2C_STAT1(cfg->reg) & I2C_STAT1_I2CBSY) {
		/* NOP */
	}

	if (data->errs) {
		return -EIO;
	}

	return 0;
}

static int i2c_gd32_msg_read(const struct device *dev)
{
	struct i2c_gd32_data *data = dev->data;
	const struct i2c_gd32_config *cfg = dev->config;

	if (I2C_STAT1(cfg->reg) & I2C_STAT1_I2CBSY) {
		data->errs = I2C_GD32_ERR_BUSY;
		return -EBUSY;
	}

	i2c_gd32_xfer_begin(dev);

	k_sem_take(&data->sync_sem, K_FOREVER);

	return i2c_gd32_xfer_end(dev);
}

static int i2c_gd32_msg_write(const struct device *dev)
{
	struct i2c_gd32_data *data = dev->data;
	const struct i2c_gd32_config *cfg = dev->config;

	if (I2C_STAT1(cfg->reg) & I2C_STAT1_I2CBSY) {
		data->errs = I2C_GD32_ERR_BUSY;
		return -EBUSY;
	}

	i2c_gd32_xfer_begin(dev);

	k_sem_take(&data->sync_sem, K_FOREVER);

	return i2c_gd32_xfer_end(dev);
}

static int i2c_gd32_transfer(const struct device *dev,
			     struct i2c_msg *msgs,
			     uint8_t num_msgs,
			     uint16_t addr)
{
	struct i2c_gd32_data *data = dev->data;
	const struct i2c_gd32_config *cfg = dev->config;
	struct i2c_msg *current, *next;
	uint8_t itr;
	int err = 0;

	current = msgs;

	/* First message flags implicitly contain I2C_MSG_RESTART flag. */
	current->flags |= I2C_MSG_RESTART;

	for (uint8_t i = 1; i <= num_msgs; i++) {

		if (i < num_msgs) {
			next = current + 1;

			/*
			 * If there have a R/W transfer state change between messages,
			 * An explicit I2C_MSG_RESTART flag is needed for the second message.
			 */
			if ((current->flags & I2C_MSG_RW_MASK) !=
			(next->flags & I2C_MSG_RW_MASK)) {
				if ((next->flags & I2C_MSG_RESTART) == 0U) {
					return -EINVAL;
				}
			}

			/* Only the last message need I2C_MSG_STOP flag to free the Bus. */
			if (current->flags & I2C_MSG_STOP) {
				return -EINVAL;
			}
		}

		if ((current->buf == NULL) ||
		    (current->len == 0U)) {
			return -EINVAL;
		}

		current++;
	}

	k_sem_take(&data->bus_mutex, K_FOREVER);

	/* Enable i2c device */
	I2C_CTL0(cfg->reg) |= I2C_CTL0_I2CEN;

	if (data->dev_config & I2C_ADDR_10_BITS) {
		data->addr1 = 0xF0 | ((addr & BITS(8, 9)) >> 8U);
		data->addr2 = addr & BITS(0, 7);
	} else {
		data->addr1 = addr & BITS(0, 6);
	}

	for (uint8_t i = 0; i < num_msgs; i = itr) {
		data->current = &msgs[i];
		data->xfer_len = msgs[i].len;

		for (itr = i + 1; itr < num_msgs; itr++) {
			if ((data->current->flags & I2C_MSG_RW_MASK) !=
			    (msgs[itr].flags & I2C_MSG_RW_MASK)) {
				break;
			}
			data->xfer_len += msgs[itr].len;
		}

		if (data->current->flags & I2C_MSG_READ) {
			err = i2c_gd32_msg_read(dev);
		} else {
			err = i2c_gd32_msg_write(dev);
		}

		if (err < 0) {
			i2c_gd32_log_err(data);
			break;
		}
	}

	/* Disable I2C device */
	I2C_CTL0(cfg->reg) &= ~I2C_CTL0_I2CEN;

	k_sem_give(&data->bus_mutex);

	return err;
}

static int i2c_gd32_configure(const struct device *dev,
			      uint32_t dev_config)
{
	struct i2c_gd32_data *data = dev->data;
	const struct i2c_gd32_config *cfg = dev->config;
	uint32_t pclk1, freq, clkc;
	int err = 0;

	k_sem_take(&data->bus_mutex, K_FOREVER);

	/* Disable I2C device */
	I2C_CTL0(cfg->reg) &= ~I2C_CTL0_I2CEN;

	(void)clock_control_get_rate(GD32_CLOCK_CONTROLLER,
				     (clock_control_subsys_t)&cfg->clkid,
				     &pclk1);

	/* i2c clock frequency, us */
	freq = pclk1 / 1000000U;
	if (freq > I2CCLK_MAX) {
		LOG_ERR("I2C max clock freq %u, current is %u\n",
			I2CCLK_MAX, freq);
		err = -ENOTSUP;
		goto error;
	}

	/*
	 * Refer from SoC user manual.
	 * In standard mode:
	 *   T_high = CLKC * T_pclk1
	 *   T_low  = CLKC * T_pclk1
	 *
	 * In fast mode and fast mode plus with DTCY=1:
	 *   T_high = 9 * CLKC * T_pclk1
	 *   T_low  = 16 * CLKC * T_pclk1
	 *
	 * T_pclk1 is reciprocal of pclk1:
	 *   T_pclk1 = 1 / pclk1
	 *
	 * T_high and T_low construct the bit transfer:
	 *  T_high + T_low = 1 / bitrate
	 *
	 * And then, we can get the CLKC equation.
	 * Standard mode:
	 *   CLKC = pclk1 / (bitrate * 2)
	 * Fast mode and fast mode plus:
	 *   CLKC = pclk1 / (bitrate * 25)
	 *
	 * Variable list:
	 *   T_high:  high period of the SCL clock
	 *   T_low:   low period of the SCL clock
	 *   T_pclk1: duration of single pclk1 pulse
	 *   pclk1:   i2c device clock frequency
	 *   bitrate: 100 Kbits for standard mode
	 */
	switch (I2C_SPEED_GET(dev_config)) {
	case I2C_SPEED_STANDARD:
		if (freq < I2CCLK_MIN) {
			LOG_ERR("I2C standard-mode min clock freq %u, current is %u\n",
				I2CCLK_MIN, freq);
			err = -ENOTSUP;
			goto error;
		}
		I2C_CTL1(cfg->reg) &= ~I2C_CTL1_I2CCLK;
		I2C_CTL1(cfg->reg) |= freq;

		/* Standard-mode risetime maximum value: 1000ns */
		if (freq == I2CCLK_MAX) {
			I2C_RT(cfg->reg) = I2CCLK_MAX;
		} else {
			I2C_RT(cfg->reg) = freq + 1U;
		}

		/* CLKC = pclk1 / (bitrate * 2) */
		clkc = pclk1 / (I2C_BITRATE_STANDARD * 2U);

		I2C_CKCFG(cfg->reg) &= ~I2C_CKCFG_CLKC;
		I2C_CKCFG(cfg->reg) |= clkc;
		/* standard-mode */
		I2C_CKCFG(cfg->reg) &= ~I2C_CKCFG_FAST;

		break;
	case I2C_SPEED_FAST:
		if (freq < I2CCLK_FM_MIN) {
			LOG_ERR("I2C fast-mode min clock freq %u, current is %u\n",
				I2CCLK_FM_MIN, freq);
			err = -ENOTSUP;
			goto error;
		}

		/* Fast-mode risetime maximum value: 300ns */
		I2C_RT(cfg->reg) = freq * 300U / 1000U + 1U;

		/* CLKC = pclk1 / (bitrate * 25) */
		clkc = pclk1 / (I2C_BITRATE_FAST * 25U);
		if (clkc == 0U) {
			clkc = 1U;
		}

		/* Default DCTY to 1 */
		I2C_CKCFG(cfg->reg) |= I2C_CKCFG_DTCY;
		I2C_CKCFG(cfg->reg) &= ~I2C_CKCFG_CLKC;
		I2C_CKCFG(cfg->reg) |= clkc;
		/* Transfer mode: fast-mode */
		I2C_CKCFG(cfg->reg) |= I2C_CKCFG_FAST;

#ifdef I2C_FMPCFG
		/* Disable transfer mode: fast-mode plus */
		I2C_FMPCFG(cfg->reg) &= ~I2C_FMPCFG_FMPEN;
#endif /* I2C_FMPCFG */

		break;
#ifdef I2C_FMPCFG
	case I2C_SPEED_FAST_PLUS:
		if (freq < I2CCLK_FM_PLUS_MIN) {
			LOG_ERR("I2C fast-mode plus min clock freq %u, current is %u\n",
				I2CCLK_FM_PLUS_MIN, freq);
			err = -ENOTSUP;
			goto error;
		}

		/* Fast-mode plus risetime maximum value: 120ns */
		I2C_RT(cfg->reg) = freq * 120U / 1000U + 1U;

		/* CLKC = pclk1 / (bitrate * 25) */
		clkc = pclk1 / (I2C_BITRATE_FAST_PLUS * 25U);
		if (clkc == 0U) {
			clkc = 1U;
		}

		/* Default DCTY to 1 */
		I2C_CKCFG(cfg->reg) |= I2C_CKCFG_DTCY;
		I2C_CKCFG(cfg->reg) &= ~I2C_CKCFG_CLKC;
		I2C_CKCFG(cfg->reg) |= clkc;
		/* Transfer mode: fast-mode */
		I2C_CKCFG(cfg->reg) |= I2C_CKCFG_FAST;

		/* Enable transfer mode: fast-mode plus */
		I2C_FMPCFG(cfg->reg) |= I2C_FMPCFG_FMPEN;

		break;
#endif /* I2C_FMPCFG */
	default:
		err = -EINVAL;
		goto error;
	}

	data->dev_config = dev_config;
error:
	k_sem_give(&data->bus_mutex);

	return err;
}

static DEVICE_API(i2c, i2c_gd32_driver_api) = {
	.configure = i2c_gd32_configure,
	.transfer = i2c_gd32_transfer,
#ifdef CONFIG_I2C_RTIO
	.iodev_submit = i2c_iodev_submit_fallback,
#endif
};

static int i2c_gd32_init(const struct device *dev)
{
	struct i2c_gd32_data *data = dev->data;
	const struct i2c_gd32_config *cfg = dev->config;
	uint32_t bitrate_cfg;
	int err;

	err = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
	if (err < 0) {
		return err;
	}

	/* Mutex semaphore to protect the i2c api in multi-thread env. */
	k_sem_init(&data->bus_mutex, 1, 1);

	/* Sync semaphore to sync i2c state between isr and transfer api. */
	k_sem_init(&data->sync_sem, 0, K_SEM_MAX_LIMIT);

	(void)clock_control_on(GD32_CLOCK_CONTROLLER,
			       (clock_control_subsys_t)&cfg->clkid);

	(void)reset_line_toggle_dt(&cfg->reset);

	cfg->irq_cfg_func();

	bitrate_cfg = i2c_map_dt_bitrate(cfg->bitrate);

	i2c_gd32_configure(dev, I2C_MODE_CONTROLLER | bitrate_cfg);

	return 0;
}

#define I2C_GD32_INIT(inst)							\
	PINCTRL_DT_INST_DEFINE(inst);						\
	static void i2c_gd32_irq_cfg_func_##inst(void)				\
	{									\
		IRQ_CONNECT(DT_INST_IRQ_BY_NAME(inst, event, irq),		\
			    DT_INST_IRQ_BY_NAME(inst, event, priority),		\
			    i2c_gd32_event_isr,					\
			    DEVICE_DT_INST_GET(inst),				\
			    0);							\
		irq_enable(DT_INST_IRQ_BY_NAME(inst, event, irq));		\
										\
		IRQ_CONNECT(DT_INST_IRQ_BY_NAME(inst, error, irq),		\
			    DT_INST_IRQ_BY_NAME(inst, error, priority),		\
			    i2c_gd32_error_isr,					\
			    DEVICE_DT_INST_GET(inst),				\
			    0);							\
		irq_enable(DT_INST_IRQ_BY_NAME(inst, error, irq));		\
	}									\
	static struct i2c_gd32_data i2c_gd32_data_##inst;			\
	const static struct i2c_gd32_config i2c_gd32_cfg_##inst = {		\
		.reg = DT_INST_REG_ADDR(inst),					\
		.bitrate = DT_INST_PROP(inst, clock_frequency),			\
		.clkid = DT_INST_CLOCKS_CELL(inst, id),				\
		.reset = RESET_DT_SPEC_INST_GET(inst),				\
		.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst),			\
		.irq_cfg_func = i2c_gd32_irq_cfg_func_##inst,			\
	};									\
	I2C_DEVICE_DT_INST_DEFINE(inst,						\
				  i2c_gd32_init, NULL,				\
				  &i2c_gd32_data_##inst, &i2c_gd32_cfg_##inst,	\
				  POST_KERNEL, CONFIG_I2C_INIT_PRIORITY,	\
				  &i2c_gd32_driver_api);			\

DT_INST_FOREACH_STATUS_OKAY(I2C_GD32_INIT)
