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

#define DT_DRV_COMPAT ene_kb1200_i2c

#include <zephyr/kernel.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/pinctrl.h>
#include <errno.h>
#include <reg/fsmbm.h>

struct i2c_kb1200_config {
	struct fsmbm_regs *fsmbm;
	const struct pinctrl_dev_config *pcfg;
};

struct i2c_kb1200_data {
	struct k_sem mutex;
	volatile uint8_t *msg_buf;
	volatile uint32_t msg_len;
	volatile uint8_t msg_flags;
	volatile int state;
	volatile uint32_t index;
	volatile int err_code;
};

/* I2C Master local functions */
static void i2c_kb1200_isr(const struct device *dev)
{
	const struct i2c_kb1200_config *config = dev->config;
	struct i2c_kb1200_data *data = dev->data;

	if (data->state == STATE_SENDING) {
		if (config->fsmbm->FSMBMPF & FSMBM_BLOCK_FINISH_EVENT) {
			/* continue block */
			uint32_t remain = data->msg_len - data->index;
			uint32_t send_bytes =
				remain > FSMBM_BUFFER_SIZE ? FSMBM_BUFFER_SIZE : remain;
			memcpy((void *)&config->fsmbm->FSMBMDAT[0],
			       (void *)&data->msg_buf[data->index], send_bytes);
			data->index += send_bytes;
			/* Increase CNT setting let hw can't match counter */
			config->fsmbm->FSMBMPRTC_C += send_bytes;
			/* If it was the last protocol recover the correct length value*/
			if (data->msg_len == data->index) {
				config->fsmbm->FSMBMPRTC_C -= 1;
			}
			config->fsmbm->FSMBMPF = FSMBM_BLOCK_FINISH_EVENT;
		} else if (config->fsmbm->FSMBMPF & FSMBM_COMPLETE_EVENT) {
			/* complete */
			if (((config->fsmbm->FSMBMSTS & FSMBM_STS_MASK) == FSMBM_SMBUS_BUSY) &&
			    ((config->fsmbm->FSMBMFRT & ___STOP) == ___NONE)) {
				/* while packet finish without STOP, the error message is
				 * FSMBM_SMBUS_BUSY
				 */
				data->err_code = 0;
			} else {
				data->err_code = config->fsmbm->FSMBMSTS & FSMBM_STS_MASK;
			}
			data->state = STATE_COMPLETE;
			config->fsmbm->FSMBMPF = FSMBM_COMPLETE_EVENT;
		} else {
			data->err_code = config->fsmbm->FSMBMSTS & FSMBM_STS_MASK;
			data->state = STATE_COMPLETE;
		}
	} else if (data->state == STATE_RECEIVING) {
		uint32_t remain = data->msg_len - data->index;
		uint32_t receive_bytes = (remain > FSMBM_BUFFER_SIZE) ? FSMBM_BUFFER_SIZE : remain;

		memcpy((void *)&data->msg_buf[data->index], (void *)&config->fsmbm->FSMBMDAT[0],
		       receive_bytes);
		data->index += receive_bytes;
		if (config->fsmbm->FSMBMPF & FSMBM_BLOCK_FINISH_EVENT) {
			/* continue block */
			/* Check next protocl information */
			remain = data->msg_len - data->index;
			uint32_t NextLen =
				(remain > FSMBM_BUFFER_SIZE) ? FSMBM_BUFFER_SIZE : remain;
			/* Increase CNT setting let hw can't match counter */
			config->fsmbm->FSMBMPRTC_C += NextLen;
			/* If it was the last protocol recover the correct length value */
			if (data->msg_len == (data->index + NextLen)) {
				config->fsmbm->FSMBMPRTC_C -= 1;
			}
			config->fsmbm->FSMBMPF = FSMBM_BLOCK_FINISH_EVENT;
		} else if (config->fsmbm->FSMBMPF & FSMBM_COMPLETE_EVENT) {
			/* complete */
			if (((config->fsmbm->FSMBMSTS & FSMBM_STS_MASK) == FSMBM_SMBUS_BUSY) &&
			    ((config->fsmbm->FSMBMFRT & ___STOP) == ___NONE)) {
				/* while packet finish without STOP, the error message is
				 * FSMBM_SMBUS_BUSY
				 */
				data->err_code = 0;
			} else {
				data->err_code = config->fsmbm->FSMBMSTS & FSMBM_STS_MASK;
			}
			data->state = STATE_COMPLETE;
			config->fsmbm->FSMBMPF = FSMBM_COMPLETE_EVENT;
		} else {
			data->err_code = config->fsmbm->FSMBMSTS & FSMBM_STS_MASK;
			data->state = STATE_COMPLETE;
		}
	} else if (data->state == STATE_COMPLETE) {
		config->fsmbm->FSMBMPF = (FSMBM_COMPLETE_EVENT | FSMBM_BLOCK_FINISH_EVENT);
	}
}

static int i2c_kb1200_poll_write(const struct device *dev, struct i2c_msg msg, uint16_t addr)
{
	const struct i2c_kb1200_config *config = dev->config;
	struct i2c_kb1200_data *data = dev->data;
	uint8_t send_bytes;

	if (msg.flags & I2C_MSG_STOP) {
		/* No CMD, No CNT, No PEC, with STOP*/
		config->fsmbm->FSMBMFRT = ___STOP;
	} else {
		/* No CMD, No CNT, No PEC, no STOP*/
		config->fsmbm->FSMBMFRT = ___NONE;
	}
	data->msg_len = msg.len;
	data->msg_buf = msg.buf;
	data->msg_flags = msg.flags;
	data->state = STATE_IDLE;
	data->index = 0;
	data->err_code = 0;

	send_bytes = (msg.len > FSMBM_BUFFER_SIZE) ? FSMBM_BUFFER_SIZE : msg.len;
	memcpy((void *)&config->fsmbm->FSMBMDAT[0], (void *)&data->msg_buf[data->index],
	       send_bytes);
	data->index += send_bytes;
	data->state = STATE_SENDING;

	config->fsmbm->FSMBMCMD = 0;
	config->fsmbm->FSMBMADR = (addr & ~BIT(0)) | FSMBM_WRITE;
	config->fsmbm->FSMBMPF = (FSMBM_COMPLETE_EVENT | FSMBM_BLOCK_FINISH_EVENT);
	/* If data over bufferSize increase 1 to force continue transmit */
	if (msg.len >= (FSMBM_BUFFER_SIZE + 1)) {
		config->fsmbm->FSMBMPRTC_C = FSMBM_BUFFER_SIZE + 1;
	} else {
		config->fsmbm->FSMBMPRTC_C = send_bytes;
	}
	config->fsmbm->FSMBMIE = (FSMBM_COMPLETE_EVENT | FSMBM_BLOCK_FINISH_EVENT);
	config->fsmbm->FSMBMPRTC_P = FLEXIBLE_PROTOCOL;
	while (data->state != STATE_COMPLETE)
		;
	data->state = STATE_IDLE;
	if (data->err_code != 0) {
		/* reset HW  */
		config->fsmbm->FSMBMCFG |= FSMBM_HW_RESET;
		return data->err_code;
	}
	return 0;
}

static int i2c_kb1200_poll_read(const struct device *dev, struct i2c_msg msg, uint16_t addr)
{
	const struct i2c_kb1200_config *config = dev->config;
	struct i2c_kb1200_data *data = dev->data;

	if (msg.flags & I2C_MSG_STOP) {
		/* No CMD, No CNT, No PEC, with STOP*/
		config->fsmbm->FSMBMFRT = ___STOP;
	} else {
		/* No CMD, No CNT, No PEC, no STOP*/
		config->fsmbm->FSMBMFRT = ___NONE;
	}
	data->msg_len = msg.len;
	data->msg_buf = msg.buf;
	data->msg_flags = msg.flags;
	data->state = STATE_IDLE;
	data->index = 0;
	data->err_code = 0;
	data->state = STATE_RECEIVING;

	config->fsmbm->FSMBMCMD = 0;
	config->fsmbm->FSMBMADR = (addr & ~BIT(0)) | FSMBM_READ;
	config->fsmbm->FSMBMPF = (FSMBM_COMPLETE_EVENT | FSMBM_BLOCK_FINISH_EVENT);
	/* If data over bufferSize increase 1 to force continue receive */
	if (msg.len >= (FSMBM_BUFFER_SIZE + 1)) {
		config->fsmbm->FSMBMPRTC_C = FSMBM_BUFFER_SIZE + 1;
	} else {
		config->fsmbm->FSMBMPRTC_C = msg.len;
	}
	config->fsmbm->FSMBMIE = (FSMBM_COMPLETE_EVENT | FSMBM_BLOCK_FINISH_EVENT);
	config->fsmbm->FSMBMPRTC_P = FLEXIBLE_PROTOCOL;
	while (data->state != STATE_COMPLETE)
		;
	data->state = STATE_IDLE;
	if (data->err_code != 0) {
		/* reset HW  */
		config->fsmbm->FSMBMCFG |= FSMBM_HW_RESET;
		return data->err_code;
	}
	return 0;
}

/* I2C Master api functions */
static int i2c_kb1200_configure(const struct device *dev, uint32_t dev_config)
{
	const struct i2c_kb1200_config *config = dev->config;

	if (!(dev_config & I2C_MODE_CONTROLLER)) {
		return -ENOTSUP;
	}

	if (dev_config & I2C_ADDR_10_BITS) {
		return -ENOTSUP;
	}

	uint32_t speed = I2C_SPEED_GET(dev_config);

	switch (speed) {
	case I2C_SPEED_STANDARD:
		config->fsmbm->FSMBMCFG = (FSMBM_CLK_100K << FSMBM_CLK_POS);
		break;
	case I2C_SPEED_FAST:
		config->fsmbm->FSMBMCFG = (FSMBM_CLK_400K << FSMBM_CLK_POS);
		break;
	case I2C_SPEED_FAST_PLUS:
		config->fsmbm->FSMBMCFG = (FSMBM_CLK_1M << FSMBM_CLK_POS);
		break;
	default:
		return -EINVAL;
	}

	config->fsmbm->FSMBMPF = (FSMBM_COMPLETE_EVENT | FSMBM_BLOCK_FINISH_EVENT);
	config->fsmbm->FSMBMIE = (FSMBM_COMPLETE_EVENT | FSMBM_BLOCK_FINISH_EVENT);
	/* HW reset, Enable FSMBM function, Timeout function*/
	config->fsmbm->FSMBMCFG |= FSMBM_HW_RESET | FSMBM_TIMEOUT_ENABLE | FSMBM_FUNCTION_ENABLE;

	return 0;
}

static int i2c_kb1200_get_config(const struct device *dev, uint32_t *dev_config)
{
	const struct i2c_kb1200_config *config = dev->config;

	if ((config->fsmbm->FSMBMCFG & FSMBM_FUNCTION_ENABLE) == 0x00) {
		printk("Cannot find i2c controller on 0x%p!\n", config->fsmbm);
		return -EIO;
	}

	switch ((config->fsmbm->FSMBMCFG >> FSMBM_CLK_POS) & FSMBM_CLK_MASK) {
	case FSMBM_CLK_100K:
		*dev_config = I2C_MODE_CONTROLLER | I2C_SPEED_SET(I2C_SPEED_STANDARD);
		break;
	case FSMBM_CLK_400K:
		*dev_config = I2C_MODE_CONTROLLER | I2C_SPEED_SET(I2C_SPEED_FAST);
		break;
	case FSMBM_CLK_1M:
		*dev_config = I2C_MODE_CONTROLLER | I2C_SPEED_SET(I2C_SPEED_FAST_PLUS);
		break;
	default:
		return -ERANGE;
	}

	return 0;
}

static int i2c_kb1200_transfer(const struct device *dev, struct i2c_msg *msgs, uint8_t num_msgs,
			       uint16_t addr)
{
	struct i2c_kb1200_data *data = dev->data;
	int ret;

	/* get the mutex */
	k_sem_take(&data->mutex, K_FOREVER);
	for (int i = 0U; i < num_msgs; i++) {
		if ((msgs[i].flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) {
			ret = i2c_kb1200_poll_write(dev, msgs[i], addr);
			if (ret) {
				printk("%s Write error: 0x%X\n", dev->name, ret);
				break;
			}
		} else {
			ret = i2c_kb1200_poll_read(dev, msgs[i], addr);
			if (ret) {
				printk("%s Read error: 0x%X\n", dev->name, ret);
				break;
			}
		}
	}
	/* release the mutex */
	k_sem_give(&data->mutex);

	return ret;
}

/* I2C Master driver registration */
static const struct i2c_driver_api i2c_kb1200_api = {
	.configure = i2c_kb1200_configure,
	.get_config = i2c_kb1200_get_config,
	.transfer = i2c_kb1200_transfer,
};

#define KB1200_FSMBM_DEV(inst) DEVICE_DT_INST_GET(inst),
static const struct device *const fsmbm_devices[] = {DT_INST_FOREACH_STATUS_OKAY(KB1200_FSMBM_DEV)};
static void i2c_kb1200_isr_wrap(void)
{
	for (size_t i = 0; i < ARRAY_SIZE(fsmbm_devices); i++) {
		const struct device *dev_ = fsmbm_devices[i];
		const struct i2c_kb1200_config *config = dev_->config;

		if (config->fsmbm->FSMBMIE & config->fsmbm->FSMBMPF) {
			i2c_kb1200_isr(dev_);
		}
	}
}

static bool init_irq = true;
static void kb1200_fsmbm_irq_init(void)
{
	if (init_irq) {
		init_irq = false;
		IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), i2c_kb1200_isr_wrap, NULL,
			    0);
		irq_enable(DT_INST_IRQN(0));
	}
}

static int i2c_kb1200_init(const struct device *dev)
{
	int ret;
	const struct i2c_kb1200_config *config = dev->config;
	struct i2c_kb1200_data *data = dev->data;

	ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
	if (ret != 0) {
		return ret;
	}

	/* init mutex */
	k_sem_init(&data->mutex, 1, 1);
	kb1200_fsmbm_irq_init();

	return 0;
}

#define I2C_KB1200_DEVICE(inst)                                                                    \
	PINCTRL_DT_INST_DEFINE(inst);                                                              \
	static struct i2c_kb1200_data i2c_kb1200_data_##inst;                                      \
	static const struct i2c_kb1200_config i2c_kb1200_config_##inst = {                         \
		.fsmbm = (struct fsmbm_regs *)DT_INST_REG_ADDR(inst),                              \
		.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst),                                      \
	};                                                                                         \
	DEVICE_DT_INST_DEFINE(inst, &i2c_kb1200_init, NULL, &i2c_kb1200_data_##inst,               \
			      &i2c_kb1200_config_##inst, PRE_KERNEL_1,                             \
			      CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &i2c_kb1200_api);

DT_INST_FOREACH_STATUS_OKAY(I2C_KB1200_DEVICE)
