/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * Copyright (c) 2020 Linumiz
 * Author: Saravanan Sekar <saravanan@linumiz.com>
 */

#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/uart.h>
#include <NuMicro.h>
#include <string.h>

#define DT_DRV_COMPAT nuvoton_numicro_uart

struct uart_numicro_config {
	UART_T *uart;
	uint32_t id_rst;
	uint32_t id_clk;
	const struct pinctrl_dev_config *pincfg;
};

struct uart_numicro_data {
	const struct device *clock;
	struct uart_config ucfg;
};

static int uart_numicro_poll_in(const struct device *dev, unsigned char *c)
{
	const struct uart_numicro_config *config = dev->config;

	if ((config->uart->FIFOSTS & UART_FIFOSTS_RXEMPTY_Msk) != 0) {
		return -1;
	}

	*c = (uint8_t)config->uart->DAT;

	return 0;
}

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

	UART_Write(config->uart, &c, 1);
}

static int uart_numicro_err_check(const struct device *dev)
{
	return 0;
}

static inline int32_t uart_numicro_convert_stopbit(enum uart_config_stop_bits sb)
{
	switch (sb) {
	case UART_CFG_STOP_BITS_1:
		return UART_STOP_BIT_1;
	case UART_CFG_STOP_BITS_1_5:
		return UART_STOP_BIT_1_5;
	case UART_CFG_STOP_BITS_2:
		return UART_STOP_BIT_2;
	default:
		return -ENOTSUP;
	}
};

static inline int32_t uart_numicro_convert_datalen(enum uart_config_data_bits db)
{
	switch (db) {
	case UART_CFG_DATA_BITS_5:
		return UART_WORD_LEN_5;
	case UART_CFG_DATA_BITS_6:
		return UART_WORD_LEN_6;
	case UART_CFG_DATA_BITS_7:
		return UART_WORD_LEN_7;
	case UART_CFG_DATA_BITS_8:
		return UART_WORD_LEN_8;
	default:
		return -ENOTSUP;
	}
}

static inline uint32_t uart_numicro_convert_parity(enum uart_config_parity parity)
{
	switch (parity) {
	case UART_CFG_PARITY_ODD:
		return UART_PARITY_ODD;
	case UART_CFG_PARITY_EVEN:
		return UART_PARITY_EVEN;
	case UART_CFG_PARITY_MARK:
		return UART_PARITY_MARK;
	case UART_CFG_PARITY_SPACE:
		return UART_PARITY_SPACE;
	case UART_CFG_PARITY_NONE:
	default:
		return UART_PARITY_NONE;
	}
}

#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
static int uart_numicro_configure(const struct device *dev,
				  const struct uart_config *cfg)
{
	const struct uart_numicro_config *config = dev->config;
	struct uart_numicro_data *ddata = dev->data;
	int32_t databits, stopbits;
	uint32_t parity;

	databits = uart_numicro_convert_datalen(cfg->data_bits);
	if (databits < 0) {
		return databits;
	}

	stopbits = uart_numicro_convert_stopbit(cfg->stop_bits);
	if (stopbits < 0) {
		return stopbits;
	}

	if (cfg->flow_ctrl == UART_CFG_FLOW_CTRL_NONE) {
		UART_DisableFlowCtrl(config->uart);
	} else if (cfg->flow_ctrl == UART_CFG_FLOW_CTRL_RTS_CTS) {
		UART_EnableFlowCtrl(config->uart);
	} else {
		return -ENOTSUP;
	}

	parity = uart_numicro_convert_parity(cfg->parity);

	UART_SetLineConfig(config->uart, cfg->baudrate, databits, parity,
			   stopbits);

	memcpy(&ddata->ucfg, cfg, sizeof(*cfg));

	return 0;
}

static int uart_numicro_config_get(const struct device *dev,
				   struct uart_config *cfg)
{
	struct uart_numicro_data *ddata = dev->data;

	memcpy(cfg, &ddata->ucfg, sizeof(*cfg));

	return 0;
}
#endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */

static int uart_numicro_init(const struct device *dev)
{
	const struct uart_numicro_config *config = dev->config;
	struct uart_numicro_data *ddata = dev->data;
	int err;

	SYS_ResetModule(config->id_rst);

	SYS_UnlockReg();

	/* Enable UART module clock */
	CLK_EnableModuleClock(config->id_clk);

	/* Select UART0 clock source is PLL */
	CLK_SetModuleClock(config->id_clk, CLK_CLKSEL1_UART0SEL_PLL,
			   CLK_CLKDIV0_UART0(0));

	SYS_LockReg();

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

	UART_Open(config->uart, ddata->ucfg.baudrate);

	return 0;
}

static const struct uart_driver_api uart_numicro_driver_api = {
	.poll_in          = uart_numicro_poll_in,
	.poll_out         = uart_numicro_poll_out,
	.err_check        = uart_numicro_err_check,
#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
	.configure        = uart_numicro_configure,
	.config_get       = uart_numicro_config_get,
#endif
};

#define NUMICRO_INIT(index)						\
PINCTRL_DT_INST_DEFINE(index);						\
									\
static const struct uart_numicro_config uart_numicro_cfg_##index = {	\
	.uart = (UART_T *)DT_INST_REG_ADDR(index),			\
	.id_rst = UART##index##_RST,					\
	.id_clk = UART##index##_MODULE,					\
	.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(index),		\
};									\
									\
static struct uart_numicro_data uart_numicro_data_##index = {		\
	.ucfg = {							\
		.baudrate = DT_INST_PROP(index, current_speed),		\
	},								\
};									\
									\
DEVICE_DT_INST_DEFINE(index,						\
		    &uart_numicro_init,					\
		    NULL,						\
		    &uart_numicro_data_##index,				\
		    &uart_numicro_cfg_##index,				\
		    PRE_KERNEL_1, CONFIG_SERIAL_INIT_PRIORITY,		\
		    &uart_numicro_driver_api);

DT_INST_FOREACH_STATUS_OKAY(NUMICRO_INIT)
