/*
 * Copyright (c) 2017-2023 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @brief UART driver for Intel FPGA UART Core IP
 * Reference : Embedded Peripherals IP User Guide : 12. JTAG UART Core
 *
 */

#include <zephyr/kernel.h>
#include <zephyr/arch/cpu.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/sys/sys_io.h>

#include <zephyr/sys/__assert.h>

#define DT_DRV_COMPAT   altr_jtag_uart

#define UART_ALTERA_JTAG_DATA_OFFSET    0x00        /* DATA : Register offset */
#define UART_ALTERA_JTAG_CTRL_OFFSET    0x04        /* CTRL : Register offset */
#define UART_IE_TX                      (1 << 1)    /* CTRL : TX Interrupt Enable */
#define UART_IE_RX                      (1 << 0)    /* CTRL : RX Interrupt Enable */
#define UART_DATA_MASK                  0xFF        /* DATA : Data Mask */
#define UART_WFIFO_MASK                 0xFFFF0000  /* CTRL : Transmit FIFO Mask */
#define UART_WFIFO_OFST                 (16)

#define ALTERA_AVALON_JTAG_UART_DATA_DATA_OFST            (0)
#define ALTERA_AVALON_JTAG_UART_DATA_RVALID_MSK           (0x00008000)

#define ALTERA_AVALON_JTAG_UART_CONTROL_RI_MSK            (0x00000100)
#define ALTERA_AVALON_JTAG_UART_CONTROL_WI_MSK            (0x00000200)

#ifdef CONFIG_UART_ALTERA_JTAG_HAL
#include "altera_avalon_jtag_uart.h"
#include "altera_avalon_jtag_uart_regs.h"

extern int altera_avalon_jtag_uart_read(altera_avalon_jtag_uart_state *sp,
		char *buffer, int space, int flags);
extern int altera_avalon_jtag_uart_write(altera_avalon_jtag_uart_state *sp,
		const char *ptr, int count, int flags);
#else

/* device data */
struct uart_altera_jtag_device_data {
	struct k_spinlock lock;
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
	uart_irq_callback_user_data_t cb;  /* Callback function pointer */
	void *cb_data;                     /* Callback function arg */
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
};

/* device config */
struct uart_altera_jtag_device_config {
	mm_reg_t base;
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
	uart_irq_config_func_t  irq_config_func;
	unsigned int irq_num;
	uint16_t write_fifo_depth;
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
};
#endif /* CONFIG_UART_ALTERA_JTAG_HAL */

#ifndef CONFIG_UART_ALTERA_JTAG_HAL
/**
 * @brief Poll the device for input.
 *
 * @param dev UART device instance
 * @param c Pointer to character
 *
 * @return 0 if a character arrived, -1 otherwise.
 * -EINVAL if c is null pointer.
 */
static int uart_altera_jtag_poll_in(const struct device *dev,
					       unsigned char *c)
{
	int ret = -1;
	const struct uart_altera_jtag_device_config *config = dev->config;
	struct uart_altera_jtag_device_data *data = dev->data;
	unsigned int input_data;

	/* generate fatal error if CONFIG_ASSERT is enabled. */
	__ASSERT(c != NULL, "c is null pointer!");

	/* Stop, if c is null pointer */
	if (c == NULL) {
		return -EINVAL;
	}

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	input_data = sys_read32(config->base + UART_ALTERA_JTAG_DATA_OFFSET);

	/* check if data is valid. */
	if (input_data & ALTERA_AVALON_JTAG_UART_DATA_RVALID_MSK) {
		*c = (input_data & UART_DATA_MASK) >> ALTERA_AVALON_JTAG_UART_DATA_DATA_OFST;
		ret = 0;
	}

	k_spin_unlock(&data->lock, key);

	return ret;
}
#endif  /* CONFIG_UART_ALTERA_JTAG_HAL */

/**
 * @brief Output a character in polled mode.
 *
 * This routine checks if the transmitter is full.
 * When the transmitter is not full, it writes a character to the data register.
 * It waits and blocks the calling thread, otherwise. This function is a blocking call.
 *
 * @param dev UART device instance
 * @param c Character to send
 */
static void uart_altera_jtag_poll_out(const struct device *dev,
					       unsigned char c)
{
#ifdef CONFIG_UART_ALTERA_JTAG_HAL
	altera_avalon_jtag_uart_state ustate;

	ustate.base = JTAG_UART_0_BASE;
	altera_avalon_jtag_uart_write(&ustate, &c, 1, 0);
#else
	const struct uart_altera_jtag_device_config *config = dev->config;
	struct uart_altera_jtag_device_data *data = dev->data;

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	/* While TX FIFO full */
	while (!(sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET) & UART_WFIFO_MASK)) {
	}

	sys_write8(c, config->base + UART_ALTERA_JTAG_DATA_OFFSET);

	k_spin_unlock(&data->lock, key);
#endif /* CONFIG_UART_ALTERA_JTAG_HAL */
}

/**
 * @brief Initialise an instance of the driver
 *
 * This function initialise the interrupt configuration for the driver.
 *
 * @param dev UART device instance
 *
 * @return 0 to indicate success.
 */
static int uart_altera_jtag_init(const struct device *dev)
{
	/*
	 * Work around to clear interrupt enable bits
	 * as it is not being done by HAL driver explicitly.
	 */
#ifdef CONFIG_UART_ALTERA_JTAG_HAL
	IOWR_ALTERA_AVALON_JTAG_UART_CONTROL(JTAG_UART_0_BASE, 0);
#else
	const struct uart_altera_jtag_device_config *config = dev->config;
	uint32_t ctrl_val = sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET);

#ifdef CONFIG_UART_INTERRUPT_DRIVEN
	/*
	 * Enable hardware interrupt.
	 * The corresponding csr from IP still needs to be set,
	 * so that the IP generates interrupt signal.
	 */
	config->irq_config_func(dev);
#endif
	/*  Disable the tx and rx interrupt signals from JTAG core IP. */
	ctrl_val &= ~(UART_IE_TX | UART_IE_RX);
	sys_write32(ctrl_val, config->base + UART_ALTERA_JTAG_CTRL_OFFSET);
#endif /* CONFIG_UART_ALTERA_JTAG_HAL */
	return 0;
}

/*
 * Functions for Interrupt driven API
 */
#if defined(CONFIG_UART_INTERRUPT_DRIVEN) && !defined(CONFIG_UART_ALTERA_JTAG_HAL)

/**
 * @brief Fill FIFO with data
 * This function is expected to be called from UART interrupt handler (ISR),
 * if uart_irq_tx_ready() returns true.
 *
 * @param dev UART device instance
 * @param tx_data Data to transmit
 * @param size Number of bytes to send
 *
 * @return Number of bytes sent
 */
static int uart_altera_jtag_fifo_fill(const struct device *dev,
									  const uint8_t *tx_data,
									  int size)
{
	const struct uart_altera_jtag_device_config *config = dev->config;
	struct uart_altera_jtag_device_data *data = dev->data;
	uint32_t ctrl_val;
	uint32_t space = 0;

	int i;

	/* generate fatal error if CONFIG_ASSERT is enabled. */
	__ASSERT(tx_data != NULL, "tx buffer is null pointer!");

	/* Stop, if buffer is null pointer */
	if (tx_data == NULL) {
		return 0;
	}

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	ctrl_val = sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET);
	space = (ctrl_val & UART_WFIFO_MASK) >> UART_WFIFO_OFST;

	/* guard for tx data overflow:
	 * make sure that driver is not sending more than current available space.
	 */
	for (i = 0; (i < size) && (i < space); i++) {
		sys_write8(tx_data[i], config->base + UART_ALTERA_JTAG_DATA_OFFSET);
	}

	k_spin_unlock(&data->lock, key);

	return i;
}

/**
 * @brief Read data from FIFO
 * This function is expected to be called from UART interrupt handler (ISR),
 * if uart_irq_rx_ready() returns true.
 *
 * @param dev UART device instance
 * @param rx_data Data container
 * @param size Container size
 *
 * @return Number of bytes read
 */
static int uart_altera_jtag_fifo_read(const struct device *dev, uint8_t *rx_data,
									  const int size)
{
	const struct uart_altera_jtag_device_config *config = dev->config;
	struct uart_altera_jtag_device_data *data = dev->data;
	int i;
	unsigned int input_data;

	/* generate fatal error if CONFIG_ASSERT is enabled. */
	__ASSERT(rx_data != NULL, "Rx buffer is null pointer!");

	/* Stop, if buffer is null pointer */
	if (rx_data == NULL) {
		return 0;
	}

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	for (i = 0; i < size; i++) {
		input_data = sys_read32(config->base + UART_ALTERA_JTAG_DATA_OFFSET);

		if (input_data & ALTERA_AVALON_JTAG_UART_DATA_RVALID_MSK) {
			rx_data[i] = (input_data & UART_DATA_MASK) >>
						  ALTERA_AVALON_JTAG_UART_DATA_DATA_OFST;
		} else {
			/* break upon invalid data or no more data */
			break;
		}
	}

	k_spin_unlock(&data->lock, key);

	return i;
}

/**
 * @brief Enable TX interrupt in IER
 *
 * @param dev UART device instance
 */
static void uart_altera_jtag_irq_tx_enable(const struct device *dev)
{
	struct uart_altera_jtag_device_data *data = dev->data;
	const struct uart_altera_jtag_device_config *config = dev->config;
	uint32_t ctrl_val = sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET);

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	ctrl_val |= UART_IE_TX;
	sys_write32(ctrl_val, config->base + UART_ALTERA_JTAG_CTRL_OFFSET);

	k_spin_unlock(&data->lock, key);
}

/**
 * @brief Disable TX interrupt in IER
 *
 * @param dev UART device instance
 */
static void uart_altera_jtag_irq_tx_disable(const struct device *dev)
{
	struct uart_altera_jtag_device_data *data = dev->data;
	const struct uart_altera_jtag_device_config *config = dev->config;
	uint32_t ctrl_val = sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET);

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	ctrl_val &= ~UART_IE_TX;
	sys_write32(ctrl_val, config->base + UART_ALTERA_JTAG_CTRL_OFFSET);

	k_spin_unlock(&data->lock, key);
}

/**
 * @brief Check if UART TX buffer can accept a new char.
 *
 * @param dev UART device instance
 *
 * @return 1 if TX interrupt is enabled and at least one char can be written to UART.
 *         0 If device is not ready to write a new byte.
 */
static int uart_altera_jtag_irq_tx_ready(const struct device *dev)
{
	struct uart_altera_jtag_device_data *data = dev->data;
	const struct uart_altera_jtag_device_config *config = dev->config;
	uint32_t ctrl_val = sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET);
	int ret = 0;
	uint32_t space = 0;

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	/* if TX interrupt is enabled */
	if (ctrl_val & ALTERA_AVALON_JTAG_UART_CONTROL_WI_MSK) {
		/* check for space in tx fifo */
		space = (ctrl_val & UART_WFIFO_MASK) >> UART_WFIFO_OFST;
		if (space) {
			ret = 1;
		}
	}

	k_spin_unlock(&data->lock, key);

	return ret;
}

/**
 * @brief Check if nothing remains to be transmitted
 *
 * @param dev UART device instance
 *
 * @return 1 if nothing remains to be transmitted, 0 otherwise
 */
static int uart_altera_jtag_irq_tx_complete(const struct device *dev)
{
	struct uart_altera_jtag_device_data *data = dev->data;
	const struct uart_altera_jtag_device_config *config = dev->config;
	uint32_t ctrl_val = sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET);
	int ret = 0;
	uint32_t space = 0;

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	/* note: This is checked indirectly via the space in tx fifo. */
	space = (ctrl_val & UART_WFIFO_MASK) >> UART_WFIFO_OFST;
	if (space == config->write_fifo_depth) {
		ret = 1;
	}

	k_spin_unlock(&data->lock, key);

	return ret;
}

/**
 * @brief Enable RX interrupt in IER
 *
 * @param dev UART device instance
 */
static void uart_altera_jtag_irq_rx_enable(const struct device *dev)
{
	struct uart_altera_jtag_device_data *data = dev->data;
	const struct uart_altera_jtag_device_config *config = dev->config;
	uint32_t ctrl_val = sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET);

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	ctrl_val |= UART_IE_RX;
	sys_write32(ctrl_val, config->base + UART_ALTERA_JTAG_CTRL_OFFSET);

	k_spin_unlock(&data->lock, key);
}

/**
 * @brief Disable RX interrupt in IER
 *
 * @param dev UART device instance
 */
static void uart_altera_jtag_irq_rx_disable(const struct device *dev)
{
	struct uart_altera_jtag_device_data *data = dev->data;
	const struct uart_altera_jtag_device_config *config = dev->config;
	uint32_t ctrl_val = sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET);

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	ctrl_val &= ~UART_IE_RX;
	sys_write32(ctrl_val, config->base + UART_ALTERA_JTAG_CTRL_OFFSET);

	k_spin_unlock(&data->lock, key);
}

/**
 * @brief Check if Rx IRQ has been raised
 *
 * @param dev UART device instance
 *
 * @return 1 if an IRQ is ready, 0 otherwise
 */
static int uart_altera_jtag_irq_rx_ready(const struct device *dev)
{
	struct uart_altera_jtag_device_data *data = dev->data;
	const struct uart_altera_jtag_device_config *config = dev->config;
	uint32_t ctrl_val = sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET);
	int ret = 0;

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	if (ctrl_val & ALTERA_AVALON_JTAG_UART_CONTROL_RI_MSK) {
		ret = 1;
	}

	k_spin_unlock(&data->lock, key);

	return ret;
}

/**
 * @brief Update cached contents of IIR
 *
 * @param dev UART device instance
 *
 * @return Always 1
 */
static int uart_altera_jtag_irq_update(const struct device *dev)
{
	return 1;
}

/**
 * @brief Check if any IRQ is pending
 *
 * @param dev UART device instance
 *
 * @return 1 if an IRQ is pending, 0 otherwise
 */
static int uart_altera_jtag_irq_is_pending(const struct device *dev)
{
	struct uart_altera_jtag_device_data *data = dev->data;
	k_spinlock_key_t key = k_spin_lock(&data->lock);
	const struct uart_altera_jtag_device_config *config = dev->config;
	uint32_t ctrl_val = sys_read32(config->base + UART_ALTERA_JTAG_CTRL_OFFSET);
	int ret = 0;

	if (ctrl_val &
		(ALTERA_AVALON_JTAG_UART_CONTROL_RI_MSK|ALTERA_AVALON_JTAG_UART_CONTROL_WI_MSK)) {
		ret = 1;
	}

	k_spin_unlock(&data->lock, key);

	return ret;
}

/**
 * @brief Set the callback function pointer for IRQ.
 *
 * @param dev UART device instance
 * @param cb Callback function pointer.
 * @param cb_data Data to pass to callback function.
 */
static void uart_altera_jtag_irq_callback_set(const struct device *dev,
								  uart_irq_callback_user_data_t cb,
								  void *cb_data)
{
	struct uart_altera_jtag_device_data *data = dev->data;

	/* generate fatal error if CONFIG_ASSERT is enabled. */
	__ASSERT(cb != NULL, "uart_irq_callback_user_data_t cb is null pointer!");

	k_spinlock_key_t key = k_spin_lock(&data->lock);

	data->cb = cb;
	data->cb_data = cb_data;

	k_spin_unlock(&data->lock, key);
}

/**
 * @brief Interrupt service routine.
 *
 * This simply calls the callback function, if one exists.
 *
 * @param dev Pointer to UART device struct
 */
static void uart_altera_jtag_isr(const struct device *dev)
{
	struct uart_altera_jtag_device_data *data = dev->data;
	uart_irq_callback_user_data_t callback = data->cb;

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

#endif /* CONFIG_UART_INTERRUPT_DRIVEN && !CONFIG_UART_ALTERA_JTAG_HAL */

static const struct uart_driver_api uart_altera_jtag_driver_api = {
#ifndef CONFIG_UART_ALTERA_JTAG_HAL
	.poll_in = uart_altera_jtag_poll_in,
#endif /* CONFIG_UART_ALTERA_JTAG_HAL */
	.poll_out = uart_altera_jtag_poll_out,
	.err_check = NULL,
#if defined(CONFIG_UART_INTERRUPT_DRIVEN) && !defined(CONFIG_UART_ALTERA_JTAG_HAL)
	.fifo_fill = uart_altera_jtag_fifo_fill,
	.fifo_read = uart_altera_jtag_fifo_read,
	.irq_tx_enable = uart_altera_jtag_irq_tx_enable,
	.irq_tx_disable = uart_altera_jtag_irq_tx_disable,
	.irq_tx_ready = uart_altera_jtag_irq_tx_ready,
	.irq_tx_complete = uart_altera_jtag_irq_tx_complete,
	.irq_rx_enable = uart_altera_jtag_irq_rx_enable,
	.irq_rx_disable = uart_altera_jtag_irq_rx_disable,
	.irq_rx_ready = uart_altera_jtag_irq_rx_ready,
	.irq_is_pending = uart_altera_jtag_irq_is_pending,
	.irq_update = uart_altera_jtag_irq_update,
	.irq_callback_set = uart_altera_jtag_irq_callback_set,
#endif /* CONFIG_UART_INTERRUPT_DRIVEN && !CONFIG_UART_ALTERA_JTAG_HAL */
};

#ifdef CONFIG_UART_ALTERA_JTAG_HAL
#define UART_ALTERA_JTAG_DEVICE_INIT(n)						\
DEVICE_DT_INST_DEFINE(n, uart_altera_jtag_init, NULL, NULL, NULL, PRE_KERNEL_1,	\
		      CONFIG_SERIAL_INIT_PRIORITY,					\
		      &uart_altera_jtag_driver_api);
#else

#ifdef CONFIG_UART_INTERRUPT_DRIVEN
#define UART_ALTERA_JTAG_CONFIG_FUNC(n)					\
	static void uart_altera_jtag_irq_config_func_##n(const struct device *dev)  \
	{										\
		IRQ_CONNECT(DT_INST_IRQN(n),		\
				DT_INST_IRQ(n, priority),	\
				uart_altera_jtag_isr,		\
				DEVICE_DT_INST_GET(n), 0);	\
											\
		irq_enable(DT_INST_IRQN(n));		\
	}

#define UART_ALTERA_JTAG_CONFIG_INIT(n)		\
	.irq_config_func = uart_altera_jtag_irq_config_func_##n,	\
	.irq_num = DT_INST_IRQN(n),				\
	.write_fifo_depth = DT_INST_PROP_OR(n, write_fifo_depth, 0),\

#else
#define UART_ALTERA_JTAG_CONFIG_FUNC(n)
#define UART_ALTERA_JTAG_CONFIG_INIT(n)
#define UART_ALTERA_JTAG_DATA_INIT(n)
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */

#define UART_ALTERA_JTAG_DEVICE_INIT(n)				\
UART_ALTERA_JTAG_CONFIG_FUNC(n)						\
static struct uart_altera_jtag_device_data uart_altera_jtag_device_data_##n = {		\
};											\
											\
static const struct uart_altera_jtag_device_config uart_altera_jtag_dev_cfg_##n = { \
	.base = DT_INST_REG_ADDR(n),					\
	UART_ALTERA_JTAG_CONFIG_INIT(n)					\
};											\
DEVICE_DT_INST_DEFINE(n,							\
		      uart_altera_jtag_init,				\
		      NULL,									\
		      &uart_altera_jtag_device_data_##n,	\
		      &uart_altera_jtag_dev_cfg_##n,		\
		      PRE_KERNEL_1,							\
		      CONFIG_SERIAL_INIT_PRIORITY,			\
		      &uart_altera_jtag_driver_api);
#endif /* CONFIG_UART_ALTERA_JTAG_HAL */

DT_INST_FOREACH_STATUS_OKAY(UART_ALTERA_JTAG_DEVICE_INIT)
