/*
 * Copyright (c) 2021 Henrik Brix Andersen <henrik@brixandersen.dk>
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT neorv32_uart

#include <zephyr/device.h>
#include <zephyr/drivers/syscon.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/pm/device.h>
#include <zephyr/sys/sys_io.h>

#include <zephyr/logging/log.h>
#include <zephyr/irq.h>
LOG_MODULE_REGISTER(uart_neorv32, CONFIG_UART_LOG_LEVEL);

/* NEORV32 UART registers offsets */
#define NEORV32_UART_CTRL_OFFSET 0x00
#define NEORV32_UART_DATA_OFFSET 0x04

/* UART_CTRL register bits */
#define NEORV32_UART_CTRL_EN              BIT(0)
#define NEORV32_UART_CTRL_SIM_MODE        BIT(1)
#define NEORV32_UART_CTRL_HWFC_EN         BIT(2)
#define NEORV32_UART_CTRL_PRSC_POS        3U
#define NEORV32_UART_CTRL_PRSC_MASK       BIT_MASK(3)
#define NEORV32_UART_CTRL_BAUD_POS        6U
#define NEORV32_UART_CTRL_BAUD_MASK       BIT_MASK(10)
#define NEORV32_UART_CTRL_RX_NEMPTY       BIT(16)
#define NEORV32_UART_CTRL_RX_HALF         BIT(17)
#define NEORV32_UART_CTRL_RX_FULL         BIT(18)
#define NEORV32_UART_CTRL_TX_NEMPTY       BIT(19)
#define NEORV32_UART_CTRL_TX_HALF         BIT(20)
#define NEORV32_UART_CTRL_TX_FULL         BIT(21)
#define NEORV32_UART_CTRL_IRQ_RX_NEMPTY   BIT(22)
#define NEORV32_UART_CTRL_IRQ_RX_HALF     BIT(23)
#define NEORV32_UART_CTRL_IRQ_RX_FULL     BIT(24)
#define NEORV32_UART_CTRL_IRQ_TX_EMPTY    BIT(25)
#define NEORV32_UART_CTRL_IRQ_TX_NHALF    BIT(26)
#define NEORV32_UART_CTRL_RX_OVER         BIT(30)
#define NEORV32_UART_CTRL_TX_BUSY         BIT(31)

struct neorv32_uart_config {
	const struct device *syscon;
	uint32_t feature_mask;
	mm_reg_t base;
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
	void (*irq_config_func)(const struct device *dev);
	unsigned int tx_irq;
	unsigned int rx_irq;
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
};

struct neorv32_uart_data {
	struct uart_config uart_cfg;
	uint32_t last_data;
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
	struct k_timer timer;
	uart_irq_callback_user_data_t callback;
	void *callback_data;
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
};

static inline uint32_t neorv32_uart_read_ctrl(const struct device *dev)
{
	const struct neorv32_uart_config *config = dev->config;

	return sys_read32(config->base + NEORV32_UART_CTRL_OFFSET);
}

static inline void neorv32_uart_write_ctrl(const struct device *dev, uint32_t ctrl)
{
	const struct neorv32_uart_config *config = dev->config;

	sys_write32(ctrl, config->base + NEORV32_UART_CTRL_OFFSET);
}

static inline uint32_t neorv32_uart_read_data(const struct device *dev)
{
	const struct neorv32_uart_config *config = dev->config;
	struct neorv32_uart_data *data = dev->data;
	uint32_t reg;

	/* Cache status bits as they are cleared upon read */
	reg = sys_read32(config->base + NEORV32_UART_DATA_OFFSET);
	data->last_data = reg;

	return reg;
}

static inline void neorv32_uart_write_data(const struct device *dev, uint32_t data)
{
	const struct neorv32_uart_config *config = dev->config;

	sys_write32(data, config->base + NEORV32_UART_DATA_OFFSET);
}

static int neorv32_uart_poll_in(const struct device *dev, unsigned char *c)
{
	uint32_t data;

	data = neorv32_uart_read_data(dev);

	if ((data & NEORV32_UART_CTRL_RX_NEMPTY) != 0) {
		*c = data & BIT_MASK(8);
		return 0;
	}

	return -1;
}

static void neorv32_uart_poll_out(const struct device *dev, unsigned char c)
{
	while ((neorv32_uart_read_ctrl(dev) & NEORV32_UART_CTRL_TX_BUSY) != 0) {
	}

	neorv32_uart_write_data(dev, c);
}

static int neorv32_uart_configure(const struct device *dev, const struct uart_config *cfg)
{
	const struct neorv32_uart_config *config = dev->config;
	struct neorv32_uart_data *data = dev->data;
	uint32_t ctrl = NEORV32_UART_CTRL_EN;
	uint16_t baudxx = 0;
	uint8_t prscx = 0;
	uint32_t clk;
	int err;

	__ASSERT_NO_MSG(cfg != NULL);

	if (cfg->stop_bits != UART_CFG_STOP_BITS_1) {
		LOG_ERR("hardware only supports one stop bit");
		return -ENOTSUP;
	}

	if (cfg->data_bits != UART_CFG_DATA_BITS_8) {
		LOG_ERR("hardware only supports 8 data bits");
		return -ENOTSUP;
	}

	switch (cfg->parity) {
	case UART_CFG_PARITY_NONE:
		break;
	default:
		LOG_ERR("unsupported parity mode %d", cfg->parity);
		return -ENOTSUP;
	}

	switch (cfg->flow_ctrl) {
	case UART_CFG_FLOW_CTRL_NONE:
		ctrl |= 0;
		break;
	case UART_CFG_FLOW_CTRL_RTS_CTS:
		ctrl |= NEORV32_UART_CTRL_HWFC_EN;
		break;
	default:
		LOG_ERR("unsupported flow control mode %d", cfg->flow_ctrl);
		return -ENOTSUP;
	}

	err = syscon_read_reg(config->syscon, NEORV32_SYSINFO_CLK, &clk);
	if (err < 0) {
		LOG_ERR("failed to determine clock rate (err %d)", err);
		return -EIO;
	}

	if (cfg->baudrate == 0) {
		LOG_ERR("invalid baud rate 0");
		return -EINVAL;
	}

	/*
	 * Calculate clock prescaler and baud prescaler. Initial prscx = 0 is
	 * clock / 2.
	 */
	baudxx = clk / (2 * cfg->baudrate);
	while (baudxx >= NEORV32_UART_CTRL_BAUD_MASK) {
		if ((prscx == 2) || (prscx == 4)) {
			baudxx >>= 3;
		} else {
			baudxx >>= 1;
		}

		prscx++;
	}

	if (prscx > NEORV32_UART_CTRL_PRSC_MASK) {
		LOG_ERR("unsupported baud rate %d", cfg->baudrate);
		return -ENOTSUP;
	}

	ctrl |= (baudxx - 1) << NEORV32_UART_CTRL_BAUD_POS;
	ctrl |= prscx << NEORV32_UART_CTRL_PRSC_POS;

	data->uart_cfg = *cfg;
	neorv32_uart_write_ctrl(dev, ctrl);

	return 0;
}

static int neorv32_uart_config_get(const struct device *dev, struct uart_config *cfg)
{
	struct neorv32_uart_data *data = dev->data;

	__ASSERT_NO_MSG(cfg != NULL);

	*cfg = data->uart_cfg;

	return 0;
}

#ifdef CONFIG_UART_INTERRUPT_DRIVEN
static int neorv32_uart_fifo_fill(const struct device *dev, const uint8_t *tx_data, int len)
{
	uint32_t ctrl;

	if (len <= 0) {
		return 0;
	}

	__ASSERT_NO_MSG(tx_data != NULL);

	ctrl = neorv32_uart_read_ctrl(dev);
	if ((ctrl & NEORV32_UART_CTRL_TX_BUSY) == 0) {
		neorv32_uart_write_data(dev, *tx_data);
		return 1;
	}

	return 0;
}

static int neorv32_uart_fifo_read(const struct device *dev, uint8_t *rx_data, const int size)
{
	struct neorv32_uart_data *data = dev->data;
	int count = 0;

	if (size <= 0) {
		return 0;
	}

	__ASSERT_NO_MSG(rx_data != NULL);

	while ((data->last_data & NEORV32_UART_CTRL_RX_NEMPTY) != 0) {
		rx_data[count++] = data->last_data & BIT_MASK(8);
		data->last_data &= ~(NEORV32_UART_CTRL_RX_NEMPTY);

		if (count >= size) {
			break;
		}

		(void)neorv32_uart_read_data(dev);
	}

	return count;
}

static void neorv32_uart_tx_soft_isr(struct k_timer *timer)
{
	const struct device *dev = k_timer_user_data_get(timer);
	struct neorv32_uart_data *data = dev->data;
	uart_irq_callback_user_data_t callback = data->callback;

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

static void neorv32_uart_irq_tx_enable(const struct device *dev)
{
	const struct neorv32_uart_config *config = dev->config;
	struct neorv32_uart_data *data = dev->data;
	uint32_t ctrl;

	irq_enable(config->tx_irq);

	ctrl = neorv32_uart_read_ctrl(dev);
	if ((ctrl & NEORV32_UART_CTRL_TX_BUSY) == 0) {
		/*
		 * TX done event already generated an edge interrupt. Generate a
		 * soft interrupt and have it call the callback function in
		 * timer isr context.
		 */
		k_timer_start(&data->timer, K_NO_WAIT, K_NO_WAIT);
	}
}

static void neorv32_uart_irq_tx_disable(const struct device *dev)
{
	const struct neorv32_uart_config *config = dev->config;

	irq_disable(config->tx_irq);
}

static int neorv32_uart_irq_tx_ready(const struct device *dev)
{
	const struct neorv32_uart_config *config = dev->config;
	uint32_t ctrl;

	if (!irq_is_enabled(config->tx_irq)) {
		return 0;
	}

	ctrl = neorv32_uart_read_ctrl(dev);

	return (ctrl & NEORV32_UART_CTRL_TX_BUSY) == 0;
}

static void neorv32_uart_irq_rx_enable(const struct device *dev)
{
	const struct neorv32_uart_config *config = dev->config;

	irq_enable(config->rx_irq);
}

static void neorv32_uart_irq_rx_disable(const struct device *dev)
{
	const struct neorv32_uart_config *config = dev->config;

	irq_disable(config->rx_irq);
}

static int neorv32_uart_irq_tx_complete(const struct device *dev)
{
	uint32_t ctrl;

	ctrl = neorv32_uart_read_ctrl(dev);

	return (ctrl & NEORV32_UART_CTRL_TX_BUSY) == 0;
}

static int neorv32_uart_irq_rx_ready(const struct device *dev)
{
	const struct neorv32_uart_config *config = dev->config;
	struct neorv32_uart_data *data = dev->data;

	if (!irq_is_enabled(config->rx_irq)) {
		return 0;
	}

	return (data->last_data & NEORV32_UART_CTRL_RX_NEMPTY) != 0;
}

static int neorv32_uart_irq_is_pending(const struct device *dev)
{
	return (neorv32_uart_irq_tx_ready(dev) ||
		neorv32_uart_irq_rx_ready(dev));
}

static int neorv32_uart_irq_update(const struct device *dev)
{
	const struct neorv32_uart_config *config = dev->config;

	if (irq_is_enabled(config->rx_irq)) {
		/* Cache data for use by rx_ready() and fifo_read() */
		(void)neorv32_uart_read_data(dev);
	}

	return 1;
}

static void neorv32_uart_irq_callback_set(const struct device *dev,
					  uart_irq_callback_user_data_t cb, void *user_data)
{
	struct neorv32_uart_data *data = dev->data;

	data->callback = cb;
	data->callback_data = user_data;
}

static void neorv32_uart_isr(const struct device *dev)
{
	struct neorv32_uart_data *data = dev->data;
	uart_irq_callback_user_data_t callback = data->callback;

	if (callback) {
		callback(dev, data->callback_data);
	}
}
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */

static int neorv32_uart_init(const struct device *dev)
{
	const struct neorv32_uart_config *config = dev->config;
	struct neorv32_uart_data *data = dev->data;
	uint32_t features;
	int err;

	if (!device_is_ready(config->syscon)) {
		LOG_ERR("syscon device not ready");
		return -EINVAL;
	}

	err = syscon_read_reg(config->syscon, NEORV32_SYSINFO_FEATURES, &features);
	if (err < 0) {
		LOG_ERR("failed to determine implemented features (err %d)", err);
		return -EIO;
	}

	if ((features & config->feature_mask) == 0) {
		LOG_ERR("neorv32 uart instance not supported");
		return -ENODEV;
	}

#ifdef CONFIG_UART_INTERRUPT_DRIVEN
	k_timer_init(&data->timer, &neorv32_uart_tx_soft_isr, NULL);
	k_timer_user_data_set(&data->timer, (void *)dev);

	config->irq_config_func(dev);
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */

	return neorv32_uart_configure(dev, &data->uart_cfg);
}

#ifdef CONFIG_PM_DEVICE
static int neorv32_uart_pm_action(const struct device *dev,
				  enum pm_device_action action)
{
	uint32_t ctrl = neorv32_uart_read_ctrl(dev);

	switch (action) {
	case PM_DEVICE_ACTION_SUSPEND:
		ctrl &= ~(NEORV32_UART_CTRL_EN);
		break;
	case PM_DEVICE_ACTION_RESUME:
		ctrl |= NEORV32_UART_CTRL_EN;
		break;
	default:
		return -ENOTSUP;
	}

	neorv32_uart_write_ctrl(dev, ctrl);

	return 0;
}
#endif /* CONFIG_PM_DEVICE */

static const struct uart_driver_api neorv32_uart_driver_api = {
	.poll_in = neorv32_uart_poll_in,
	.poll_out = neorv32_uart_poll_out,
#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
	.configure = neorv32_uart_configure,
	.config_get = neorv32_uart_config_get,
#endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
	.fifo_fill = neorv32_uart_fifo_fill,
	.fifo_read = neorv32_uart_fifo_read,
	.irq_tx_enable = neorv32_uart_irq_tx_enable,
	.irq_tx_disable = neorv32_uart_irq_tx_disable,
	.irq_tx_ready = neorv32_uart_irq_tx_ready,
	.irq_rx_enable = neorv32_uart_irq_rx_enable,
	.irq_rx_disable = neorv32_uart_irq_rx_disable,
	.irq_tx_complete = neorv32_uart_irq_tx_complete,
	.irq_rx_ready = neorv32_uart_irq_rx_ready,
	.irq_is_pending = neorv32_uart_irq_is_pending,
	.irq_update = neorv32_uart_irq_update,
	.irq_callback_set = neorv32_uart_irq_callback_set,
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
};

#ifdef CONFIG_UART_INTERRUPT_DRIVEN
#define NEORV32_UART_CONFIG_FUNC(node_id, n)	\
	static void neorv32_uart_config_func_##n(const struct device *dev) \
	{								\
		IRQ_CONNECT(DT_IRQ_BY_NAME(node_id, tx, irq),		\
			    DT_IRQ_BY_NAME(node_id, tx, priority),	\
			    neorv32_uart_isr,				\
			    DEVICE_DT_GET(node_id), 0);			\
									\
		IRQ_CONNECT(DT_IRQ_BY_NAME(node_id, rx, irq),		\
			    DT_IRQ_BY_NAME(node_id, rx, priority),	\
			    neorv32_uart_isr,				\
			    DEVICE_DT_GET(node_id), 0);			\
	}
#define NEORV32_UART_CONFIG_INIT(node_id, n)				\
	.irq_config_func = neorv32_uart_config_func_##n,		\
	.tx_irq = DT_IRQ_BY_NAME(node_id, tx, irq),			\
	.rx_irq = DT_IRQ_BY_NAME(node_id, rx, irq),
#else
#define NEORV32_UART_CONFIG_FUNC(node_id, n)
#define NEORV32_UART_CONFIG_INIT(node_id, n)
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */

#define NEORV32_UART_INIT(node_id, n)					\
	NEORV32_UART_CONFIG_FUNC(node_id, n)				\
									\
	static struct neorv32_uart_data neorv32_uart_##n##_data = {	\
		.uart_cfg = {						\
			.baudrate = DT_PROP(node_id, current_speed),	\
			.parity = DT_ENUM_IDX_OR(node_id, parity,	\
						 UART_CFG_PARITY_NONE),	\
			.stop_bits = UART_CFG_STOP_BITS_1,		\
			.data_bits = UART_CFG_DATA_BITS_8,		\
			.flow_ctrl = DT_PROP(node_id, hw_flow_control) ? \
				UART_CFG_FLOW_CTRL_RTS_CTS :		\
				UART_CFG_FLOW_CTRL_NONE,		\
		},							\
	};								\
									\
	static const struct neorv32_uart_config neorv32_uart_##n##_config = { \
		.syscon = DEVICE_DT_GET(DT_PHANDLE(node_id, syscon)),	\
		.feature_mask = NEORV32_SYSINFO_FEATURES_IO_UART##n,	\
		.base = DT_REG_ADDR(node_id),				\
		NEORV32_UART_CONFIG_INIT(node_id, n)			\
	};								\
									\
	PM_DEVICE_DT_DEFINE(node_id, neorv32_uart_pm_action);		\
									\
	DEVICE_DT_DEFINE(node_id, &neorv32_uart_init,			\
			 PM_DEVICE_DT_GET(node_id),			\
			 &neorv32_uart_##n##_data,			\
			 &neorv32_uart_##n##_config,			\
			 PRE_KERNEL_1,					\
			 CONFIG_SERIAL_INIT_PRIORITY,			\
			 &neorv32_uart_driver_api)

#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(uart0), DT_DRV_COMPAT, okay)
NEORV32_UART_INIT(DT_NODELABEL(uart0), 0);
#endif

#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(uart1), DT_DRV_COMPAT, okay)
NEORV32_UART_INIT(DT_NODELABEL(uart1), 1);
#endif
