/*
 * Copyright (c) 2024 GARDENA GmbH
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT silabs_si32_usart

#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/init.h>
#include <zephyr/kernel.h>
#include <zephyr/sys_clock.h>

#include <SI32_CLKCTRL_A_Type.h>
#include <SI32_USART_A_Type.h>
#include <si32_device.h>

struct usart_si32_config {
	SI32_USART_A_Type *usart;
	bool hw_flow_control;
	uint8_t parity;
#if defined(CONFIG_UART_INTERRUPT_DRIVEN)
	uart_irq_config_func_t irq_config_func;
#endif
	const struct device *clock_dev;
};

struct usart_si32_data {
	uint32_t baud_rate;
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
	uart_irq_callback_user_data_t callback;
	void *cb_data;
#endif
};

static int usart_si32_poll_in(const struct device *dev, unsigned char *c)
{
	const struct usart_si32_config *config = dev->config;
	int ret = -1;

	if (SI32_USART_A_read_rx_fifo_count(config->usart) != 0) {
		*c = SI32_USART_A_read_data_u8(config->usart);
		ret = 0;
	}

	return ret;
}

static void usart_si32_poll_out(const struct device *dev, unsigned char c)
{
	const struct usart_si32_config *config = dev->config;

	while (SI32_USART_A_read_tx_fifo_count(config->usart) ||
	       SI32_USART_A_is_tx_busy(config->usart)) {
		/* busy wait */
	}

	SI32_USART_A_write_data_u8(config->usart, c);
}

static int usart_si32_err_check(const struct device *dev)
{
	const struct usart_si32_config *config = dev->config;
	int ret = 0;

	if (SI32_USART_A_is_tx_fifo_error_interrupt_pending(config->usart)) {
		SI32_USART_A_clear_tx_fifo_error_interrupt(config->usart);
	}

	if (SI32_USART_A_is_rx_overrun_interrupt_pending(config->usart)) {
		SI32_USART_A_clear_rx_overrun_error_interrupt(config->usart);
		ret |= UART_ERROR_OVERRUN;
	}

	if (SI32_USART_A_is_rx_parity_error_interrupt_pending(config->usart)) {
		SI32_USART_A_clear_rx_parity_error_interrupt(config->usart);
		ret |= UART_ERROR_PARITY;
	}

	if (SI32_USART_A_is_rx_frame_error_interrupt_pending(config->usart)) {
		SI32_USART_A_clear_rx_frame_error_interrupt(config->usart);
		ret |= UART_ERROR_FRAMING;
	}

	return ret;
}

#ifdef CONFIG_UART_INTERRUPT_DRIVEN
static int usart_si32_fifo_fill(const struct device *dev, const uint8_t *tx_data, int size)
{
	const struct usart_si32_config *config = dev->config;
	int i;

	/* NOTE: Checking `SI32_USART_A_is_tx_busy` is a workaround.
	 *       For some reason data gets corrupted when writing to the FIFO
	 *       while a write is happening.
	 */
	for (i = 0; i < size && SI32_USART_A_read_tx_fifo_count(config->usart) == 0 &&
		    !SI32_USART_A_is_tx_busy(config->usart);
	     i++) {
		SI32_USART_A_write_data_u8(config->usart, tx_data[i]);
	}

	return i;
}

static int usart_si32_fifo_read(const struct device *dev, uint8_t *rx_data, const int size)
{
	const struct usart_si32_config *config = dev->config;
	int i;

	for (i = 0; i < size; i++) {
		if (!SI32_USART_A_read_rx_fifo_count(config->usart)) {
			break;
		}

		rx_data[i] = SI32_USART_A_read_data_u8(config->usart);
	}

	return i;
}

static void usart_si32_irq_tx_enable(const struct device *dev)
{
	const struct usart_si32_config *config = dev->config;

	SI32_USART_A_enable_tx_data_request_interrupt(config->usart);
}

static void usart_si32_irq_tx_disable(const struct device *dev)
{
	const struct usart_si32_config *config = dev->config;

	SI32_USART_A_disable_tx_data_request_interrupt(config->usart);
}

static int usart_si32_irq_tx_ready(const struct device *dev)
{
	const struct usart_si32_config *config = dev->config;

	return SI32_USART_A_is_tx_data_request_interrupt_pending(config->usart);
}

static int usart_si32_irq_tx_complete(const struct device *dev)
{
	const struct usart_si32_config *config = dev->config;

	return SI32_USART_A_is_tx_complete(config->usart);
}

static void usart_si32_irq_rx_enable(const struct device *dev)
{
	const struct usart_si32_config *config = dev->config;

	SI32_USART_A_enable_rx_data_request_interrupt(config->usart);
}

static void usart_si32_irq_rx_disable(const struct device *dev)
{
	const struct usart_si32_config *config = dev->config;

	SI32_USART_A_disable_rx_data_request_interrupt(config->usart);
}

static int usart_si32_irq_rx_ready(const struct device *dev)
{
	const struct usart_si32_config *config = dev->config;

	return SI32_USART_A_is_rx_data_request_interrupt_pending(config->usart);
}

static void usart_si32_irq_err_enable(const struct device *dev)
{
	const struct usart_si32_config *config = dev->config;

	SI32_USART_A_enable_rx_error_interrupts(config->usart);
	SI32_USART_A_enable_tx_error_interrupts(config->usart);
}

static void usart_si32_irq_err_disable(const struct device *dev)
{
	const struct usart_si32_config *config = dev->config;

	SI32_USART_A_disable_rx_error_interrupts(config->usart);
	SI32_USART_A_disable_tx_error_interrupts(config->usart);
}

static int usart_si32_irq_is_pending(const struct device *dev)
{
	return usart_si32_irq_rx_ready(dev) || usart_si32_irq_tx_ready(dev);
}

static int usart_si32_irq_update(const struct device *dev)
{
	ARG_UNUSED(dev);

	return 1;
}

static void usart_si32_irq_callback_set(const struct device *dev, uart_irq_callback_user_data_t cb,
					void *cb_data)
{
	struct usart_si32_data *data = dev->data;

	data->callback = cb;
	data->cb_data = cb_data;
}

static void usart_si32_irq_handler(const struct device *dev)
{
	struct usart_si32_data *data = dev->data;

	if (data->callback) {
		data->callback(dev, data->cb_data);
	}

	usart_si32_err_check(dev);
}
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */

static const struct uart_driver_api usart_si32_driver_api = {
	.poll_in = usart_si32_poll_in,
	.poll_out = usart_si32_poll_out,
	.err_check = usart_si32_err_check,
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
	.fifo_fill = usart_si32_fifo_fill,
	.fifo_read = usart_si32_fifo_read,
	.irq_tx_enable = usart_si32_irq_tx_enable,
	.irq_tx_disable = usart_si32_irq_tx_disable,
	.irq_tx_ready = usart_si32_irq_tx_ready,
	.irq_tx_complete = usart_si32_irq_tx_complete,
	.irq_rx_enable = usart_si32_irq_rx_enable,
	.irq_rx_disable = usart_si32_irq_rx_disable,
	.irq_rx_ready = usart_si32_irq_rx_ready,
	.irq_err_enable = usart_si32_irq_err_enable,
	.irq_err_disable = usart_si32_irq_err_disable,
	.irq_is_pending = usart_si32_irq_is_pending,
	.irq_update = usart_si32_irq_update,
	.irq_callback_set = usart_si32_irq_callback_set,
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
};

static int usart_si32_init(const struct device *dev)
{
	const struct usart_si32_config *config = dev->config;
	struct usart_si32_data *data = dev->data;
	uint32_t apb_freq;
	uint32_t baud_register_value;
	int ret;
	enum SI32_USART_A_PARITY_Enum parity = SI32_USART_A_PARITY_ODD;
	bool parity_enabled;

	if (!device_is_ready(config->clock_dev)) {
		return -ENODEV;
	}

	ret = clock_control_get_rate(config->clock_dev, NULL, &apb_freq);
	if (ret) {
		return ret;
	}

	switch (config->parity) {
	case UART_CFG_PARITY_NONE:
		parity_enabled = false;
		break;
	case UART_CFG_PARITY_ODD:
		parity = SI32_USART_A_PARITY_ODD;
		parity_enabled = true;
		break;
	case UART_CFG_PARITY_EVEN:
		parity = SI32_USART_A_PARITY_EVEN;
		parity_enabled = true;
		break;
	case UART_CFG_PARITY_MARK:
		parity = SI32_USART_A_PARITY_SET;
		parity_enabled = true;
		break;
	case UART_CFG_PARITY_SPACE:
		parity = SI32_USART_A_PARITY_CLEAR;
		parity_enabled = true;
		break;
	default:
		return -ENOTSUP;
	}

	if (config->usart == SI32_USART_0) {
		SI32_CLKCTRL_A_enable_apb_to_modules_0(SI32_CLKCTRL_0,
						       SI32_CLKCTRL_A_APBCLKG0_USART0);
	} else if (config->usart == SI32_USART_1) {
		SI32_CLKCTRL_A_enable_apb_to_modules_0(SI32_CLKCTRL_0,
						       SI32_CLKCTRL_A_APBCLKG0_USART1);
	} else {
		return -ENOTSUP;
	}

	baud_register_value = (apb_freq / (2 * data->baud_rate)) - 1;

	SI32_USART_A_exit_loopback_mode(config->usart);

	if (config->hw_flow_control) {
		SI32_USART_A_enable_rts(config->usart);
		SI32_USART_A_select_rts_deassert_on_byte_free(config->usart);
		SI32_USART_A_disable_rts_inversion(config->usart);

		SI32_USART_A_enable_cts(config->usart);
		SI32_USART_A_disable_cts_inversion(config->usart);
	}

	/* Transmitter */
	if (parity_enabled) {
		SI32_USART_A_select_tx_parity(config->usart, parity);
		SI32_USART_A_enable_tx_parity_bit(config->usart);
	} else {
		SI32_USART_A_disable_tx_parity_bit(config->usart);
	}
	SI32_USART_A_select_tx_data_length(config->usart, SI32_USART_A_DATA_LENGTH_8_BITS);
	SI32_USART_A_enable_tx_start_bit(config->usart);
	SI32_USART_A_enable_tx_stop_bit(config->usart);
	SI32_USART_A_select_tx_stop_bits(config->usart, SI32_USART_A_STOP_BITS_1_BIT);
	SI32_USART_A_set_tx_baudrate(config->usart, (uint16_t)baud_register_value);
	SI32_USART_A_select_tx_asynchronous_mode(config->usart);
	SI32_USART_A_disable_tx_signal_inversion(config->usart);
	SI32_USART_A_select_tx_fifo_threshold_for_request_to_1(config->usart);
	SI32_USART_A_enable_tx(config->usart);

	/* Receiver */
	if (parity_enabled) {
		SI32_USART_A_select_rx_parity(config->usart, parity);
		SI32_USART_A_enable_rx_parity_bit(config->usart);
	} else {
		SI32_USART_A_disable_rx_parity_bit(config->usart);
	}
	SI32_USART_A_select_rx_data_length(config->usart, SI32_USART_A_DATA_LENGTH_8_BITS);
	SI32_USART_A_enable_rx_start_bit(config->usart);
	SI32_USART_A_enable_rx_stop_bit(config->usart);
	SI32_USART_A_select_rx_stop_bits(config->usart, SI32_USART_A_STOP_BITS_1_BIT);
	SI32_USART_A_set_rx_baudrate(config->usart, (uint16_t)baud_register_value);
	SI32_USART_A_select_rx_asynchronous_mode(config->usart);
	SI32_USART_A_disable_rx_signal_inversion(config->usart);
	SI32_USART_A_select_rx_fifo_threshold_1(config->usart);
	SI32_USART_A_enable_rx(config->usart);

	SI32_USART_A_flush_tx_fifo(config->usart);
	SI32_USART_A_flush_rx_fifo(config->usart);

#if defined(CONFIG_UART_INTERRUPT_DRIVEN)
	config->irq_config_func(dev);
#endif

	return 0;
}

#if defined(CONFIG_UART_INTERRUPT_DRIVEN)
#define SI32_USART_IRQ_HANDLER_DECL(index)                                                         \
	static void usart_si32_irq_config_func_##index(const struct device *dev);
#define SI32_USART_IRQ_HANDLER(index)                                                              \
	static void usart_si32_irq_config_func_##index(const struct device *dev)                   \
	{                                                                                          \
		IRQ_CONNECT(DT_INST_IRQN(index), DT_INST_IRQ(index, priority),                     \
			    usart_si32_irq_handler, DEVICE_DT_INST_GET(index), 0);                 \
		irq_enable(DT_INST_IRQN(index));                                                   \
	}
#else
#define SI32_USART_IRQ_HANDLER_DECL(index) /* Not used */
#define SI32_USART_IRQ_HANDLER(index)      /* Not used */
#endif

#if defined(CONFIG_UART_INTERRUPT_DRIVEN)
#define SI32_USART_IRQ_HANDLER_FUNC(index) .irq_config_func = usart_si32_irq_config_func_##index,
#else
#define SI32_USART_IRQ_HANDLER_FUNC(index) /* Not used */
#endif

#define SI32_USART_INIT(index)                                                                     \
	SI32_USART_IRQ_HANDLER_DECL(index)                                                         \
                                                                                                   \
	static const struct usart_si32_config usart_si32_cfg_##index = {                           \
		.usart = (SI32_USART_A_Type *)DT_INST_REG_ADDR(index),                             \
		.hw_flow_control = DT_INST_PROP(index, hw_flow_control),                           \
		.parity = DT_INST_ENUM_IDX_OR(index, parity, UART_CFG_PARITY_NONE),                \
		.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(index)),                            \
		SI32_USART_IRQ_HANDLER_FUNC(index)};                                               \
                                                                                                   \
	static struct usart_si32_data usart_si32_data_##index = {                                  \
		.baud_rate = DT_INST_PROP(index, current_speed),                                   \
	};                                                                                         \
                                                                                                   \
	DEVICE_DT_INST_DEFINE(index, &usart_si32_init, NULL, &usart_si32_data_##index,             \
			      &usart_si32_cfg_##index, PRE_KERNEL_1, CONFIG_SERIAL_INIT_PRIORITY,  \
			      &usart_si32_driver_api);                                             \
                                                                                                   \
	SI32_USART_IRQ_HANDLER(index)

DT_INST_FOREACH_STATUS_OKAY(SI32_USART_INIT)
