/*
 * Copyright (c) 2023 Cypress Semiconductor Corporation (an Infineon company) or
 * an affiliate of Cypress Semiconductor Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @brief I2C driver for Infineon CAT1 MCU family.
 */

#define DT_DRV_COMPAT infineon_cat1_i2c

#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/pinctrl.h>
#include <cyhal_i2c.h>
#include <cyhal_utils_impl.h>
#include <cyhal_utils_impl.h>
#include <cyhal_scb_common.h>

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

#define I2C_CAT1_EVENTS_MASK  (CYHAL_I2C_MASTER_WR_CMPLT_EVENT | CYHAL_I2C_MASTER_RD_CMPLT_EVENT | \
			       CYHAL_I2C_MASTER_ERR_EVENT)

#define I2C_CAT1_SLAVE_EVENTS_MASK                                                                 \
	(CYHAL_I2C_SLAVE_READ_EVENT | CYHAL_I2C_SLAVE_WRITE_EVENT |                                \
	 CYHAL_I2C_SLAVE_RD_BUF_EMPTY_EVENT | CYHAL_I2C_SLAVE_RD_CMPLT_EVENT |                     \
	 CYHAL_I2C_SLAVE_WR_CMPLT_EVENT | CYHAL_I2C_SLAVE_RD_BUF_EMPTY_EVENT |                     \
	 CYHAL_I2C_SLAVE_ERR_EVENT)

/* States for ASYNC operations */
#define CAT1_I2C_PENDING_NONE           (0U)
#define CAT1_I2C_PENDING_RX             (1U)
#define CAT1_I2C_PENDING_TX             (2U)
#define CAT1_I2C_PENDING_TX_RX          (3U)

/* I2C speed */
#define CAT1_I2C_SPEED_STANDARD_HZ      (100000UL)
#define CAT1_I2C_SPEED_FAST_HZ          (400000UL)
#define CAT1_I2C_SPEED_FAST_PLUS_HZ     (1000000UL)

/* Data structure */
struct ifx_cat1_i2c_data {
	cyhal_i2c_t obj;
	cyhal_i2c_cfg_t cfg;
	struct k_sem operation_sem;
	struct k_sem transfer_sem;
	uint32_t error_status;
	uint32_t async_pending;
	cyhal_resource_inst_t hw_resource;
	cyhal_clock_t clock;
	struct i2c_target_config *p_target_config;
	uint8_t i2c_target_wr_byte;
	uint8_t target_wr_buffer[CONFIG_I2C_INFINEON_CAT1_TARGET_BUF];
};

/* Device config structure */
struct ifx_cat1_i2c_config {
	uint32_t master_frequency;
	CySCB_Type *reg_addr;
	const struct pinctrl_dev_config *pcfg;
	uint8_t irq_priority;
};

/* Default SCB/I2C configuration structure */
static const cy_stc_scb_i2c_config_t _cyhal_i2c_default_config = {
	.i2cMode = CY_SCB_I2C_MASTER,
	.useRxFifo = false,
	.useTxFifo = true,
	.slaveAddress = 0U,
	.slaveAddressMask = 0U,
	.acceptAddrInFifo = false,
	.ackGeneralAddr = false,
	.enableWakeFromSleep = false,
	.enableDigitalFilter = false,
	.lowPhaseDutyCycle = 8U,
	.highPhaseDutyCycle = 8U,
};

static int32_t _get_hw_block_num(CySCB_Type *reg_addr)
{
	uint32_t i;

	for (i = 0u; i < _SCB_ARRAY_SIZE; i++) {
		if (_CYHAL_SCB_BASE_ADDRESSES[i] == reg_addr) {
			return i;
		}
	}

	return -ENOMEM;
}

#ifdef CONFIG_I2C_INFINEON_CAT1_ASYNC
static void ifx_master_event_handler(void *callback_arg, cyhal_i2c_event_t event)
{
	const struct device *dev = (const struct device *) callback_arg;
	struct ifx_cat1_i2c_data *data = dev->data;

	if (((CYHAL_I2C_MASTER_ERR_EVENT | CYHAL_I2C_SLAVE_ERR_EVENT) & event) != 0U) {
		/* In case of error abort transfer */
		(void)cyhal_i2c_abort_async(&data->obj);
		data->error_status = 1;
	}

	/* Release semaphore if operation complete
	 * When we have pending TX, RX operations, the semaphore will be released
	 * after TX, RX complete.
	 */
	if (((data->async_pending == CAT1_I2C_PENDING_TX_RX) &&
	     ((CYHAL_I2C_MASTER_RD_CMPLT_EVENT & event) != 0U)) ||
	    (data->async_pending != CAT1_I2C_PENDING_TX_RX)) {

		/* Release semaphore (After I2C async transfer is complete) */
		k_sem_give(&data->transfer_sem);
	}

	if (0 != (CYHAL_I2C_SLAVE_READ_EVENT & event)) {
		if (data->p_target_config->callbacks->read_requested) {
			data->p_target_config->callbacks->read_requested(data->p_target_config,
									 &data->i2c_target_wr_byte);
			data->obj.context.slaveTxBufferIdx = 0;
			data->obj.context.slaveTxBufferCnt = 0;
			data->obj.context.slaveTxBufferSize = 1;
			data->obj.context.slaveTxBuffer = &data->i2c_target_wr_byte;
		}
	}

	if (0 != (CYHAL_I2C_SLAVE_RD_BUF_EMPTY_EVENT & event)) {
		if (data->p_target_config->callbacks->read_processed) {
			data->p_target_config->callbacks->read_processed(data->p_target_config,
									 &data->i2c_target_wr_byte);
			data->obj.context.slaveTxBufferIdx = 0;
			data->obj.context.slaveTxBufferCnt = 0;
			data->obj.context.slaveTxBufferSize = 1;
			data->obj.context.slaveTxBuffer = &data->i2c_target_wr_byte;
		}
	}

	if (0 != (CYHAL_I2C_SLAVE_WRITE_EVENT & event)) {
		cyhal_i2c_slave_config_write_buffer(&data->obj, data->target_wr_buffer,
						    CONFIG_I2C_INFINEON_CAT1_TARGET_BUF);
		if (data->p_target_config->callbacks->write_requested) {
			data->p_target_config->callbacks->write_requested(data->p_target_config);
		}
	}

	if (0 != (CYHAL_I2C_SLAVE_WR_CMPLT_EVENT & event)) {
		if (data->p_target_config->callbacks->write_received) {
			for (int i = 0; i < data->obj.context.slaveRxBufferIdx; i++) {
				data->p_target_config->callbacks->write_received(
					data->p_target_config, data->target_wr_buffer[i]);
			}
		}
		if (data->p_target_config->callbacks->stop) {
			data->p_target_config->callbacks->stop(data->p_target_config);
		}
	}

	if (0 != (CYHAL_I2C_SLAVE_RD_CMPLT_EVENT & event)) {
		if (data->p_target_config->callbacks->stop) {
			data->p_target_config->callbacks->stop(data->p_target_config);
		}
	}
}
#endif

static int ifx_cat1_i2c_configure(const struct device *dev, uint32_t dev_config)
{
	struct ifx_cat1_i2c_data *data = dev->data;
	cy_rslt_t rslt;
	int ret;

	if (dev_config != 0) {
		switch (I2C_SPEED_GET(dev_config)) {
		case I2C_SPEED_STANDARD:
			data->cfg.frequencyhal_hz = CAT1_I2C_SPEED_STANDARD_HZ;
			break;
		case I2C_SPEED_FAST:
			data->cfg.frequencyhal_hz = CAT1_I2C_SPEED_FAST_HZ;
			break;
		case I2C_SPEED_FAST_PLUS:
			data->cfg.frequencyhal_hz = CAT1_I2C_SPEED_FAST_PLUS_HZ;
			break;
		default:
			LOG_ERR("Unsupported speed");
			return -ERANGE;
		}

		/* This is deprecated and could be ignored in the future */
		if (dev_config & I2C_ADDR_10_BITS) {
			LOG_ERR("10-bit addressing mode is not supported");
			return -EIO;
		}
	}

	/* Acquire semaphore (block I2C operation for another thread) */
	ret = k_sem_take(&data->operation_sem, K_FOREVER);
	if (ret) {
		return -EIO;
	}

	/* Configure the I2C resource to be master */
	rslt = cyhal_i2c_configure(&data->obj, &data->cfg);
	if (rslt != CY_RSLT_SUCCESS) {
		LOG_ERR("cyhal_i2c_configure failed with err 0x%x", rslt);
		k_sem_give(&data->operation_sem);
		return -EIO;
	}

#ifdef CONFIG_I2C_INFINEON_CAT1_ASYNC
	/* Register an I2C event callback handler */
	cyhal_i2c_register_callback(&data->obj, ifx_master_event_handler, (void *)dev);
#endif
	/* Release semaphore */
	k_sem_give(&data->operation_sem);
	return 0;
}

static int ifx_cat1_i2c_get_config(const struct device *dev, uint32_t *dev_config)
{
	struct ifx_cat1_i2c_data *data = dev->data;
	uint32_t config;

	switch (data->cfg.frequencyhal_hz) {
	case CAT1_I2C_SPEED_STANDARD_HZ:
		config = I2C_SPEED_SET(I2C_SPEED_STANDARD);
		break;
	case CAT1_I2C_SPEED_FAST_HZ:
		config = I2C_SPEED_SET(I2C_SPEED_FAST);
		break;
	case CAT1_I2C_SPEED_FAST_PLUS_HZ:
		config = I2C_SPEED_SET(I2C_SPEED_FAST_PLUS);
		break;
	default:
		LOG_ERR("Unsupported speed");
		return -ERANGE;
	}

	/* Return current configuration */
	*dev_config = config | I2C_MODE_CONTROLLER;

	return 0;
}

static int ifx_cat1_i2c_msg_validate(struct i2c_msg *msg, uint8_t num_msgs)
{
	for (uint32_t i = 0u; i < num_msgs; i++) {
		if ((I2C_MSG_ADDR_10_BITS & msg[i].flags) || (msg[i].buf == NULL)) {
			return -EINVAL;
		}
	}
	return 0;
}

static int ifx_cat1_i2c_transfer(const struct device *dev, struct i2c_msg *msg, uint8_t num_msgs,
				 uint16_t addr)
{
	struct ifx_cat1_i2c_data *data = dev->data;
	cy_rslt_t rslt = CY_RSLT_SUCCESS;
	int ret;

	if (!num_msgs) {
		return 0;
	}

	/* Acquire semaphore (block I2C transfer for another thread) */
	ret = k_sem_take(&data->operation_sem, K_FOREVER);
	if (ret) {
		return -EIO;
	}

	/* This function checks if msg.buf is not NULL and if
	 * target address is not 10 bit.
	 */
	if (ifx_cat1_i2c_msg_validate(msg, num_msgs) != 0) {
		k_sem_give(&data->operation_sem);
		return -EINVAL;
	}

#ifdef CONFIG_I2C_INFINEON_CAT1_ASYNC
	const struct ifx_cat1_i2c_config *const config = dev->config;

	struct i2c_msg *tx_msg;
	struct i2c_msg *rx_msg;

	data->error_status = 0;
	data->async_pending = CAT1_I2C_PENDING_NONE;

	/* Enable I2C Interrupt */
	cyhal_i2c_enable_event(&data->obj, (cyhal_i2c_event_t)I2C_CAT1_EVENTS_MASK,
			       config->irq_priority, true);

	for (uint32_t i = 0u; i < num_msgs; i++) {
		tx_msg = NULL;
		rx_msg = NULL;

		if ((msg[i].flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) {
			tx_msg = &msg[i];
			data->async_pending = CAT1_I2C_PENDING_TX;
		}

		if ((msg[i].flags & I2C_MSG_RW_MASK) == I2C_MSG_READ) {
			rx_msg = &msg[i];
			data->async_pending = CAT1_I2C_PENDING_TX;
		}

		if ((tx_msg != NULL) && ((i + 1U) < num_msgs) &&
		    ((msg[i + 1U].flags & I2C_MSG_RW_MASK) == I2C_MSG_READ)) {
			rx_msg = &msg[i + 1U];
			i++;
			data->async_pending = CAT1_I2C_PENDING_TX_RX;
		}

		/* Initiate master write and read transfer
		 * using tx_buff and rx_buff respectively
		 */
		rslt = cyhal_i2c_master_transfer_async(&data->obj, addr,
						       (tx_msg == NULL) ? NULL : tx_msg->buf,
						       (tx_msg == NULL) ? 0u : tx_msg->len,
						       (rx_msg == NULL) ? NULL : rx_msg->buf,
						       (rx_msg == NULL) ? 0u : rx_msg->len);

		if (rslt != CY_RSLT_SUCCESS) {
			k_sem_give(&data->operation_sem);
			return -EIO;
		}

		/* Acquire semaphore (block I2C async transfer for another thread) */
		ret = k_sem_take(&data->transfer_sem, K_FOREVER);
		if (ret) {
			k_sem_give(&data->operation_sem);
			return -EIO;
		}

		/* If error_status != 1 we have error during transfer async.
		 * error_status is handling in master_event_handler function.
		 */
		if (data->error_status != 0) {
			/* Release semaphore */
			k_sem_give(&data->operation_sem);
			return -EIO;
		}
	}

	/* Disable I2C Interrupt */
	cyhal_i2c_enable_event(&data->obj, (cyhal_i2c_event_t)
			       I2C_CAT1_EVENTS_MASK, config->irq_priority, false);
#else
	for (uint32_t i = 0u; i < num_msgs; i++) {
		bool stop_flag = ((msg[i].flags & I2C_MSG_STOP) != 0u) ? true : false;

		if ((msg[i].flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) {
			rslt = cyhal_i2c_master_write(&data->obj,
						      addr, msg[i].buf, msg[i].len, 0, stop_flag);
		}
		if ((msg[i].flags & I2C_MSG_RW_MASK) == I2C_MSG_READ) {
			rslt = cyhal_i2c_master_read(&data->obj,
						     addr, msg[i].buf, msg[i].len, 0, stop_flag);
		}

		if (rslt != CY_RSLT_SUCCESS) {
			/* Release semaphore */
			k_sem_give(&data->operation_sem);
			return -EIO;
		}
	}
#endif
	/* Release semaphore (After I2C transfer is complete) */
	k_sem_give(&data->operation_sem);
	return 0;
}

static int ifx_cat1_i2c_init(const struct device *dev)
{
	struct ifx_cat1_i2c_data *data = dev->data;
	const struct ifx_cat1_i2c_config *config = dev->config;
	cy_rslt_t result;
	int ret;

	/* Configuration structure to initialisation I2C */
	cyhal_i2c_configurator_t i2c_init_cfg = {
		.resource = &data->hw_resource,
		.config = &_cyhal_i2c_default_config,
		.clock = &data->clock,
	};

	/* Dedicate SCB HW resource */
	data->hw_resource.type = CYHAL_RSC_SCB;
	data->hw_resource.block_num = _get_hw_block_num(config->reg_addr);


	/* Configure semaphores */
	ret = k_sem_init(&data->transfer_sem, 0, 1);
	if (ret) {
		return ret;
	}

	ret = k_sem_init(&data->operation_sem, 1, 1);
	if (ret) {
		return ret;
	}

	/* Configure dt provided device signals when available */
	ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
	if (ret < 0) {
		return ret;
	}

	/* Allocating clock for I2C driver */
	result = _cyhal_utils_allocate_clock(&data->clock, &data->hw_resource,
					     CYHAL_CLOCK_BLOCK_PERIPHERAL_16BIT, true);
	if (result != CY_RSLT_SUCCESS) {
		return -ENOTSUP;
	}

	/* Assigns a programmable divider to a selected IP block */
	en_clk_dst_t clk_idx = _cyhal_scb_get_clock_index(i2c_init_cfg.resource->block_num);

	result = _cyhal_utils_peri_pclk_assign_divider(clk_idx, i2c_init_cfg.clock);
	if (result != CY_RSLT_SUCCESS) {
		return -ENOTSUP;
	}

	/* Initialize the I2C peripheral */
	result = cyhal_i2c_init_cfg(&data->obj, &i2c_init_cfg);
	if (result != CY_RSLT_SUCCESS) {
		return -ENOTSUP;
	}
	data->obj.is_clock_owned = true;

	/* Store Master initial configuration */
	data->cfg.is_slave = false;
	data->cfg.address = 0;
	data->cfg.frequencyhal_hz = config->master_frequency;

	if (ifx_cat1_i2c_configure(dev, 0) != 0) {
		/* Free I2C resource */
		cyhal_i2c_free(&data->obj);
	}
	return 0;
}

static int ifx_cat1_i2c_target_register(const struct device *dev, struct i2c_target_config *cfg)
{
	struct ifx_cat1_i2c_data *data = (struct ifx_cat1_i2c_data *)dev->data;
	const struct ifx_cat1_i2c_config *config = dev->config;

	if (!cfg) {
		return -EINVAL;
	}

	if (cfg->flags & I2C_TARGET_FLAGS_ADDR_10_BITS) {
		return -ENOTSUP;
	}

	data->p_target_config = cfg;
	data->cfg.is_slave = true;
	data->cfg.address = data->p_target_config->address;
	data->cfg.frequencyhal_hz = 100000;

	if (ifx_cat1_i2c_configure(dev, I2C_SPEED_SET(I2C_SPEED_FAST)) != 0) {
		/* Free I2C resource */
		cyhal_i2c_free(&data->obj);
		/* Release semaphore */
		k_sem_give(&data->operation_sem);
		return -EIO;
	}

	cyhal_i2c_enable_event(&data->obj, (cyhal_i2c_event_t)I2C_CAT1_SLAVE_EVENTS_MASK,
			       config->irq_priority, true);
	return 0;
}

static int ifx_cat1_i2c_target_unregister(const struct device *dev, struct i2c_target_config *cfg)
{
	struct ifx_cat1_i2c_data *data = (struct ifx_cat1_i2c_data *)dev->data;
	const struct ifx_cat1_i2c_config *config = dev->config;

	/* Acquire semaphore (block I2C operation for another thread) */
	k_sem_take(&data->operation_sem, K_FOREVER);

	cyhal_i2c_free(&data->obj);
	data->p_target_config = NULL;
	cyhal_i2c_enable_event(&data->obj, (cyhal_i2c_event_t)I2C_CAT1_SLAVE_EVENTS_MASK,
			       config->irq_priority, false);

	/* Release semaphore */
	k_sem_give(&data->operation_sem);
	return 0;
}

/* I2C API structure */
static const struct i2c_driver_api i2c_cat1_driver_api = {
	.configure = ifx_cat1_i2c_configure,
	.transfer = ifx_cat1_i2c_transfer,
	.get_config = ifx_cat1_i2c_get_config,
	.target_register = ifx_cat1_i2c_target_register,
	.target_unregister = ifx_cat1_i2c_target_unregister};

/* Macros for I2C instance declaration */
#define INFINEON_CAT1_I2C_INIT(n)                                                                  \
	PINCTRL_DT_INST_DEFINE(n);                                                                 \
                                                                                                   \
	static struct ifx_cat1_i2c_data ifx_cat1_i2c_data##n;                                      \
                                                                                                   \
	static const struct ifx_cat1_i2c_config i2c_cat1_cfg_##n = {                               \
		.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),                                         \
		.master_frequency = DT_INST_PROP_OR(n, clock_frequency, 100000),                   \
		.reg_addr = (CySCB_Type *)DT_INST_REG_ADDR(n),                                     \
		.irq_priority = DT_INST_IRQ(n, priority),                                          \
	};                                                                                         \
                                                                                                   \
	I2C_DEVICE_DT_INST_DEFINE(n, ifx_cat1_i2c_init, NULL, &ifx_cat1_i2c_data##n,               \
				  &i2c_cat1_cfg_##n, POST_KERNEL,                                  \
				  CONFIG_I2C_INIT_PRIORITY, &i2c_cat1_driver_api);

DT_INST_FOREACH_STATUS_OKAY(INFINEON_CAT1_I2C_INIT)
