/*
 * Copyright (c) 2021 Telink Semiconductor
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include "analog.h"
#include "clock.h"

#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/irq.h>


/* Driver dts compatibility: telink,b91_uart */
#define DT_DRV_COMPAT telink_b91_uart

/* Get UART instance */
#define GET_UART(dev)      ((volatile struct uart_b91_t *) \
			    ((const struct uart_b91_config *)dev->config)->uart_addr)

/* UART TX buffer count max value */
#define UART_TX_BUF_CNT    ((uint8_t)8u)

/* UART TX/RX data registers size */
#define UART_DATA_SIZE     ((uint8_t)4u)

/* Parity type */
#define UART_PARITY_NONE   ((uint8_t)0u)
#define UART_PARITY_EVEN   ((uint8_t)1u)
#define UART_PARITY_ODD    ((uint8_t)2u)

/* Stop bits length */
#define UART_STOP_BIT_1    ((uint8_t)0u)
#define UART_STOP_BIT_1P5  BIT(4)
#define UART_STOP_BIT_2    BIT(5)

/* TX RX reset bits */
#define UART_RX_RESET_BIT BIT(6)
#define UART_TX_RESET_BIT BIT(7)

/* B91 UART registers structure */
struct uart_b91_t {
	uint8_t data_buf[UART_DATA_SIZE];
	uint16_t clk_div;
	uint8_t ctrl0;
	uint8_t ctrl1;
	uint8_t ctrl2;
	uint8_t ctrl3;
	uint16_t rxtimeout;
	uint8_t bufcnt;
	uint8_t status;
	uint8_t txrx_status;
	uint8_t state;
};

/* B91 UART data structure */
struct uart_b91_data {
	uint8_t tx_byte_index;
	uint8_t rx_byte_index;
	struct uart_config cfg;
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
	uart_irq_callback_user_data_t callback;
	void *cb_data;
#endif
};

/* B91 UART config structure */
struct uart_b91_config {
	const struct pinctrl_dev_config *pcfg;
	uint32_t uart_addr;
	uint32_t baud_rate;
	void (*pirq_connect)(void);
};

/* rxtimeout register enums */
enum {
	UART_ERR_IRQ_MASK = BIT(15),
};

/* ctrl0 register enums */
enum {
	UART_RX_IRQ_MASK        = BIT(6),
	UART_TX_IRQ_MASK        = BIT(7),
};

/* ctrl3 register enums */
enum {
	FLD_UART_RX_IRQ_TRIQ_LEV_OFFSET = 0,
	FLD_UART_TX_IRQ_TRIQ_LEV_OFFSET = 4,
};

/* bufcnt register enums */
enum {
	FLD_UART_RX_BUF_CNT_OFFSET      = 0,
	FLD_UART_TX_BUF_CNT_OFFSET      = 4,
};

/* status register enums */
enum {
	UART_IRQ_STATUS         = BIT(3),
	UART_RX_ERR_STATUS      = BIT(7),
};


/* Get tx fifo count */
static inline uint8_t uart_b91_get_tx_bufcnt(volatile struct uart_b91_t *uart)
{
	return (uart->bufcnt & FLD_UART_TX_BUF_CNT) >> FLD_UART_TX_BUF_CNT_OFFSET;
}

/* Get rx fifo count */
static inline uint8_t uart_b91_get_rx_bufcnt(volatile struct uart_b91_t *uart)
{
	return (uart->bufcnt & FLD_UART_RX_BUF_CNT) >> FLD_UART_RX_BUF_CNT_OFFSET;
}

/* Check for prime */
static uint8_t uart_b91_is_prime(uint32_t n)
{
	uint32_t i = 5;

	if (n <= 3) {
		return 1;
	} else if ((n % 2 == 0) || (n % 3 == 0)) {
		return 0;
	}

	for (i = 5; i * i < n; i += 6) {
		if ((n % i == 0) || (n % (i + 2)) == 0) {
			return 0;
		}
	}

	return 1;
}

/* Calculate the best bit width */
static void uart_b91_cal_div_and_bwpc(uint32_t baudrate, uint32_t pclk,
				      uint16_t *divider, uint8_t *bwpc)
{
	uint8_t i = 0, j = 0;
	uint32_t primeInt = 0;
	uint8_t primeDec = 0;
	uint32_t D_intdec[13], D_int[13];
	uint8_t D_dec[13];

	primeInt = pclk / baudrate;
	primeDec = 10 * pclk / baudrate - 10 * primeInt;

	if (uart_b91_is_prime(primeInt)) {
		primeInt += 1;
	} else if (primeDec > 5) {
		primeInt += 1;
		if (uart_b91_is_prime(primeInt)) {
			primeInt -= 1;
		}
	}

	for (i = 3; i <= 15; i++) {
		D_intdec[i - 3] = (10 * primeInt) / (i + 1);
		D_dec[i - 3] = D_intdec[i - 3] - 10 * (D_intdec[i - 3] / 10);
		D_int[i - 3] = D_intdec[i - 3] / 10;
	}

	/* find the max and min one decimation point */
	uint8_t position_min = 0, position_max = 0;
	uint32_t min = 0xffffffff, max = 0x00;

	for (j = 0; j < 13; j++) {
		if ((D_dec[j] <= min) && (D_int[j] != 0x01)) {
			min = D_dec[j];
			position_min = j;
		}
		if (D_dec[j] >= max) {
			max = D_dec[j];
			position_max = j;
		}
	}

	if ((D_dec[position_min] < 5) && (D_dec[position_max] >= 5)) {
		if (D_dec[position_min] < (10 - D_dec[position_max])) {
			*bwpc = position_min + 3;
			*divider = D_int[position_min] - 1;
		} else {
			*bwpc = position_max + 3;
			*divider = D_int[position_max];
		}
	} else if ((D_dec[position_min] < 5) && (D_dec[position_max] < 5)) {
		*bwpc = position_min + 3;
		*divider = D_int[position_min] - 1;
	} else {
		*bwpc = position_max + 3;
		*divider = D_int[position_max];
	}
}

/* Initializes the UART instance */
static void uart_b91_init(volatile struct uart_b91_t *uart, uint16_t divider,
			  uint8_t bwpc, uint8_t parity, uint8_t stop_bit)
{
	/* config clock */
	divider = divider | FLD_UART_CLK_DIV_EN;
	uart->ctrl0 = bwpc;
	uart->clk_div = divider;

	/* config parity */
	if (parity) {
		/* enable parity function */
		uart->ctrl1 |= FLD_UART_PARITY_ENABLE;

		if (parity == UART_PARITY_EVEN) {
			/* enable even parity */
			uart->ctrl1 &= (~FLD_UART_PARITY_POLARITY);
		} else if (parity == UART_PARITY_ODD) {
			/* enable odd parity */
			uart->ctrl1 |= FLD_UART_PARITY_POLARITY;
		}
	} else {
		uart->ctrl1 &= (~FLD_UART_PARITY_ENABLE); /* disable parity function */
	}

	/* stop bit config */
	uart->ctrl1 &= (~FLD_UART_STOP_SEL);
	uart->ctrl1 |= stop_bit;
}

/* API implementation: irq handler */
static void uart_b91_irq_handler(const struct device *dev)
{
#ifndef CONFIG_UART_INTERRUPT_DRIVEN
	ARG_UNUSED(dev);
#else
	struct uart_b91_data *data = dev->data;

	if (data->callback != NULL) {
		data->callback(dev, data->cb_data);
	}
#endif
}

#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
/* API implementation: configure */
static int uart_b91_configure(const struct device *dev,
			      const struct uart_config *cfg)
{
	struct uart_b91_data *data = dev->data;
	uint16_t divider;
	uint8_t bwpc;
	uint8_t parity;
	uint8_t stop_bits;

	volatile struct uart_b91_t *uart = GET_UART(dev);

	/* check parity */
	if (cfg->parity == UART_CFG_PARITY_NONE) {
		parity = UART_PARITY_NONE;
	} else if (cfg->parity == UART_CFG_PARITY_ODD) {
		parity = UART_PARITY_ODD;
	} else if (cfg->parity == UART_CFG_PARITY_EVEN) {
		parity = UART_PARITY_EVEN;
	} else {
		return -ENOTSUP;
	}

	/* check stop bits */
	if (cfg->stop_bits == UART_CFG_STOP_BITS_1) {
		stop_bits = UART_STOP_BIT_1;
	} else if (cfg->stop_bits == UART_CFG_STOP_BITS_1_5) {
		stop_bits = UART_STOP_BIT_1P5;
	} else if (cfg->stop_bits == UART_CFG_STOP_BITS_2) {
		stop_bits = UART_STOP_BIT_2;
	} else {
		return -ENOTSUP;
	}

	/* check flow control */
	if (cfg->flow_ctrl != UART_CFG_FLOW_CTRL_NONE) {
		return -ENOTSUP;
	}

	/* UART configure */
	uart_b91_cal_div_and_bwpc(cfg->baudrate, sys_clk.pclk * 1000 * 1000, &divider, &bwpc);
	uart_b91_init(uart, divider, bwpc, parity, stop_bits);

	/* save configuration */
	data->cfg = *cfg;

	return 0;
}

/* API implementation: config_get */
static int uart_b91_config_get(const struct device *dev,
			       struct uart_config *cfg)
{
	struct uart_b91_data *data = dev->data;

	*cfg = data->cfg;

	return 0;
}
#endif

/* API implementation: driver initialization */
static int uart_b91_driver_init(const struct device *dev)
{
	int status = 0;
	uint16_t divider = 0u;
	uint8_t bwpc = 0u;
	volatile struct uart_b91_t *uart = GET_UART(dev);
	const struct uart_b91_config *cfg = dev->config;
	struct uart_b91_data *data = dev->data;

	/* Reset Tx, Rx status before usage */
	uart->status |= UART_RX_RESET_BIT | UART_TX_RESET_BIT;
	data->rx_byte_index = 0;
	data->tx_byte_index = 0;

	/* configure pins */
	status = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
	if (status < 0) {
		return status;
	}

	uart_b91_cal_div_and_bwpc(cfg->baud_rate, sys_clk.pclk * 1000 * 1000, &divider, &bwpc);
	uart_b91_init(uart, divider, bwpc, UART_PARITY_NONE, UART_STOP_BIT_1);

#ifdef CONFIG_UART_INTERRUPT_DRIVEN
	cfg->pirq_connect();
#endif

	return 0;
}

/* API implementation: poll_out */
static void uart_b91_poll_out(const struct device *dev, uint8_t c)
{
	volatile struct uart_b91_t *uart = GET_UART(dev);
	struct uart_b91_data *data = dev->data;

	while (uart_b91_get_tx_bufcnt(uart) >= UART_TX_BUF_CNT) {
	};

	uart->data_buf[data->tx_byte_index] = c;
	data->tx_byte_index = (data->tx_byte_index + 1) % ARRAY_SIZE(uart->data_buf);
}

/* API implementation: poll_in */
static int uart_b91_poll_in(const struct device *dev, unsigned char *c)
{
	volatile struct uart_b91_t *uart = GET_UART(dev);
	struct uart_b91_data *data = dev->data;

	if (uart_b91_get_rx_bufcnt(uart) == 0) {
		return -1;
	}

	*c = uart->data_buf[data->rx_byte_index];
	data->rx_byte_index = (data->rx_byte_index + 1) % ARRAY_SIZE(uart->data_buf);

	return 0;
}

/* API implementation: err_check */
static int uart_b91_err_check(const struct device *dev)
{
	volatile struct uart_b91_t *uart = GET_UART(dev);

	return ((uart->status & UART_RX_ERR_STATUS) != 0) ? 1 : 0;
}

#ifdef CONFIG_UART_INTERRUPT_DRIVEN

/* API implementation: fifo_fill */
static int uart_b91_fifo_fill(const struct device *dev,
			      const uint8_t *tx_data,
			      int size)
{
	int i = 0;
	volatile struct uart_b91_t *uart = GET_UART(dev);

	if (size > UART_DATA_SIZE) {
		size = UART_DATA_SIZE;
	}

	for (i = 0; i < size; i++) {
		if (uart_b91_get_rx_bufcnt(uart) != 0) {
			break;
		}

		uart_b91_poll_out(dev, tx_data[i]);
	}

	return i;
}

/* API implementation: fifo_read */
static int uart_b91_fifo_read(const struct device *dev,
			      uint8_t *rx_data,
			      const int size)
{
	int rx_count;
	volatile struct uart_b91_t *uart = GET_UART(dev);

	for (rx_count = 0; rx_count < size; rx_count++) {
		if (uart_b91_get_rx_bufcnt(uart) == 0) {
			break;
		}

		uart_b91_poll_in(dev, &rx_data[rx_count]);
	}

	return rx_count;
}

/* API implementation: irq_tx_enable */
static void uart_b91_irq_tx_enable(const struct device *dev)
{
	volatile struct uart_b91_t *uart = GET_UART(dev);

	uart->ctrl3 = (uart->ctrl3 & (~FLD_UART_TX_IRQ_TRIQ_LEV)) |
		      BIT(FLD_UART_TX_IRQ_TRIQ_LEV_OFFSET);
	uart->ctrl0 |= UART_TX_IRQ_MASK;
}

/* API implementation: irq_tx_disable */
static void uart_b91_irq_tx_disable(const struct device *dev)
{
	volatile struct uart_b91_t *uart = GET_UART(dev);

	uart->ctrl0 &= ~UART_TX_IRQ_MASK;
}

/* API implementation: irq_tx_ready */
static int uart_b91_irq_tx_ready(const struct device *dev)
{
	volatile struct uart_b91_t *uart = GET_UART(dev);

	return ((uart_b91_get_tx_bufcnt(uart) < UART_TX_BUF_CNT) &&
		((uart->ctrl0 & UART_TX_IRQ_MASK) != 0)) ? 1 : 0;
}

/* API implementation: irq_tx_complete */
static int uart_b91_irq_tx_complete(const struct device *dev)
{
	volatile struct uart_b91_t *uart = GET_UART(dev);

	return (uart_b91_get_tx_bufcnt(uart) == 0) ? 1 : 0;
}

/* API implementation: irq_rx_enable */
static void uart_b91_irq_rx_enable(const struct device *dev)
{
	volatile struct uart_b91_t *uart = GET_UART(dev);

	uart->ctrl3 = (uart->ctrl3 & (~FLD_UART_RX_IRQ_TRIQ_LEV)) |
		      BIT(FLD_UART_RX_IRQ_TRIQ_LEV_OFFSET);
	uart->ctrl0 |= UART_RX_IRQ_MASK;
}

/* API implementation: irq_rx_disable */
static void uart_b91_irq_rx_disable(const struct device *dev)
{
	volatile struct uart_b91_t *uart = GET_UART(dev);

	uart->ctrl0 &= ~UART_RX_IRQ_MASK;
}

/* API implementation: irq_rx_ready */
static int uart_b91_irq_rx_ready(const struct device *dev)
{
	volatile struct uart_b91_t *uart = GET_UART(dev);

	return (uart_b91_get_rx_bufcnt(uart) > 0) ? 1 : 0;
}

/* API implementation: irq_err_enable */
static void uart_b91_irq_err_enable(const struct device *dev)
{
	volatile struct uart_b91_t *uart = GET_UART(dev);

	uart->rxtimeout |= UART_ERR_IRQ_MASK;
}

/* API implementation: irq_err_disable*/
static void uart_b91_irq_err_disable(const struct device *dev)
{
	volatile struct uart_b91_t *uart = GET_UART(dev);

	uart->rxtimeout &= ~UART_ERR_IRQ_MASK;
}

/* API implementation: irq_is_pending */
static int uart_b91_irq_is_pending(const struct device *dev)
{
	volatile struct uart_b91_t *uart = GET_UART(dev);

	return ((uart->status & UART_IRQ_STATUS) != 0) ? 1 : 0;
}

/* API implementation: irq_update */
static int uart_b91_irq_update(const struct device *dev)
{
	ARG_UNUSED(dev);

	/* nothing to be done */
	return 1;
}

/* API implementation: irq_callback_set */
static void uart_b91_irq_callback_set(const struct device *dev,
				      uart_irq_callback_user_data_t cb,
				      void *cb_data)
{
	struct uart_b91_data *data = dev->data;

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

#endif /* CONFIG_UART_INTERRUPT_DRIVEN */

static const struct uart_driver_api uart_b91_driver_api = {
	.poll_in = uart_b91_poll_in,
	.poll_out = uart_b91_poll_out,
	.err_check = uart_b91_err_check,
#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
	.configure = uart_b91_configure,
	.config_get = uart_b91_config_get,
#endif
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
	.fifo_fill = uart_b91_fifo_fill,
	.fifo_read = uart_b91_fifo_read,
	.irq_tx_enable = uart_b91_irq_tx_enable,
	.irq_tx_disable = uart_b91_irq_tx_disable,
	.irq_tx_ready = uart_b91_irq_tx_ready,
	.irq_tx_complete = uart_b91_irq_tx_complete,
	.irq_rx_enable = uart_b91_irq_rx_enable,
	.irq_rx_disable = uart_b91_irq_rx_disable,
	.irq_rx_ready = uart_b91_irq_rx_ready,
	.irq_err_enable = uart_b91_irq_err_enable,
	.irq_err_disable = uart_b91_irq_err_disable,
	.irq_is_pending = uart_b91_irq_is_pending,
	.irq_update = uart_b91_irq_update,
	.irq_callback_set = uart_b91_irq_callback_set,
#endif
};


#define UART_B91_INIT(n)							    \
										    \
	static void uart_b91_irq_connect_##n(void);				    \
										    \
	PINCTRL_DT_INST_DEFINE(n);						    \
										    \
	static const struct uart_b91_config uart_b91_cfg_##n =			    \
	{									    \
		.uart_addr = DT_INST_REG_ADDR(n),				    \
		.baud_rate = DT_INST_PROP(n, current_speed),			    \
		.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),			    \
		.pirq_connect = uart_b91_irq_connect_##n			    \
	};									    \
										    \
	static struct uart_b91_data uart_b91_data_##n;				    \
										    \
	DEVICE_DT_INST_DEFINE(n, uart_b91_driver_init,				    \
			      NULL,						    \
			      &uart_b91_data_##n,				    \
			      &uart_b91_cfg_##n,				    \
			      PRE_KERNEL_1,					    \
			      CONFIG_SERIAL_INIT_PRIORITY,			    \
			      (void *)&uart_b91_driver_api);			    \
										    \
	static void uart_b91_irq_connect_##n(void)				    \
	{									    \
		IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority),		    \
			    uart_b91_irq_handler,				    \
			    DEVICE_DT_INST_GET(n), 0);				    \
										    \
		riscv_plic_irq_enable(DT_INST_IRQN(n));				    \
		riscv_plic_set_priority(DT_INST_IRQN(n), DT_INST_IRQ(n, priority)); \
	}

DT_INST_FOREACH_STATUS_OKAY(UART_B91_INIT)
