zephyr/drivers/serial/uart_altera_jtag.c
Goh Shun Jing 5858cca8b8 drivers: serial: uart_altera_jtag: enhancement
implement uart poll in and interrupt driven api.

Signed-off-by: Goh Shun Jing <shun.jing.goh@intel.com>
2023-01-27 14:24:43 -05:00

589 lines
17 KiB
C

/*
* 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)