drivers: serial: add driver for the Xilinx UART Lite IP
Add serial driver for the Xilinx UART Lite IP. Signed-off-by: Henrik Brix Andersen <henrik@brixandersen.dk>
This commit is contained in:
parent
a3758eceae
commit
1cde72a35d
4 changed files with 397 additions and 0 deletions
|
@ -229,6 +229,7 @@
|
|||
/drivers/serial/uart_rtt.c @carlescufi @pkral78
|
||||
/drivers/serial/Kconfig.xlnx @wjliang
|
||||
/drivers/serial/uart_xlnx_ps.c @wjliang
|
||||
/drivers/serial/uart_xlnx_uartlite.c @henrikbrixandersen
|
||||
/drivers/serial/*xmc4xxx* @parthitce
|
||||
/drivers/serial/*nuvoton* @ssekar15
|
||||
/drivers/serial/*npcx* @MulinChao
|
||||
|
|
|
@ -31,6 +31,7 @@ zephyr_library_sources_ifdef(CONFIG_UART_RV32M1_LPUART uart_rv32m1_lpuart.c)
|
|||
zephyr_library_sources_ifdef(CONFIG_UART_LITEUART uart_liteuart.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_UART_RTT_DRIVER uart_rtt.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_UART_XLNX_PS uart_xlnx_ps.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_UART_XLNX_UARTLITE uart_xlnx_uartlite.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_UART_XMC4XXX uart_xmc4xxx.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_UART_NPCX uart_npcx.c)
|
||||
|
||||
|
|
|
@ -10,3 +10,10 @@ config UART_XLNX_PS
|
|||
select SERIAL_SUPPORT_INTERRUPT
|
||||
help
|
||||
This option enables the UART driver for Xilinx MPSoC platforms.
|
||||
|
||||
config UART_XLNX_UARTLITE
|
||||
bool "Xilinx UART Lite"
|
||||
select SERIAL_HAS_DRIVER
|
||||
select SERIAL_SUPPORT_INTERRUPT
|
||||
help
|
||||
This option enables the UART driver for Xilinx UART Lite IP.
|
||||
|
|
388
drivers/serial/uart_xlnx_uartlite.c
Normal file
388
drivers/serial/uart_xlnx_uartlite.c
Normal file
|
@ -0,0 +1,388 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Henrik Brix Andersen <henrik@brixandersen.dk>
|
||||
*
|
||||
* Based on uart_mcux_lpuart.c, which is:
|
||||
* Copyright (c) 2017, NXP
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#define DT_DRV_COMPAT xlnx_xps_uartlite_1_00_a
|
||||
|
||||
#include <device.h>
|
||||
#include <drivers/uart.h>
|
||||
#include <sys/sys_io.h>
|
||||
|
||||
/* AXI UART Lite v2 registers offsets (See Xilinx PG142 for details) */
|
||||
#define RX_FIFO_OFFSET 0x00
|
||||
#define TX_FIFO_OFFSET 0x04
|
||||
#define STAT_REG_OFFSET 0x08
|
||||
#define CTRL_REG_OFFSET 0x0c
|
||||
|
||||
/* STAT_REG bit definitions */
|
||||
#define STAT_REG_RX_FIFO_VALID_DATA BIT(0)
|
||||
#define STAT_REG_RX_FIFO_FULL BIT(1)
|
||||
#define STAT_REG_TX_FIFO_EMPTY BIT(2)
|
||||
#define STAT_REG_TX_FIFO_FULL BIT(3)
|
||||
#define STAT_REG_INTR_ENABLED BIT(4)
|
||||
#define STAT_REG_OVERRUN_ERROR BIT(5)
|
||||
#define STAT_REG_FRAME_ERROR BIT(6)
|
||||
#define STAT_REG_PARITY_ERROR BIT(7)
|
||||
|
||||
/* STAT_REG bit masks */
|
||||
#define STAT_REG_ERROR_MASK GENMASK(7, 5)
|
||||
|
||||
/* CTRL_REG bit definitions */
|
||||
#define CTRL_REG_RST_TX_FIFO BIT(0)
|
||||
#define CTRL_REG_RST_RX_FIFO BIT(1)
|
||||
#define CTRL_REG_ENABLE_INTR BIT(4)
|
||||
|
||||
struct xlnx_uartlite_config {
|
||||
mm_reg_t base;
|
||||
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
|
||||
void (*irq_config_func)(struct device *dev);
|
||||
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
|
||||
};
|
||||
|
||||
struct xlnx_uartlite_data {
|
||||
uint32_t errors;
|
||||
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
|
||||
struct device *dev;
|
||||
struct k_timer timer;
|
||||
uart_irq_callback_user_data_t callback;
|
||||
void *callback_data;
|
||||
|
||||
volatile uint8_t tx_irq_enabled : 1;
|
||||
volatile uint8_t rx_irq_enabled : 1;
|
||||
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
|
||||
};
|
||||
|
||||
static inline uint32_t xlnx_uartlite_read_status(struct device *dev)
|
||||
{
|
||||
const struct xlnx_uartlite_config *config = dev->config;
|
||||
struct xlnx_uartlite_data *data = dev->data;
|
||||
uint32_t status;
|
||||
|
||||
/* Cache errors as they are cleared by reading the STAT_REG */
|
||||
status = sys_read32(config->base + STAT_REG_OFFSET);
|
||||
data->errors &= (status & STAT_REG_ERROR_MASK);
|
||||
|
||||
/* Return current status and previously cached errors */
|
||||
return status | data->errors;
|
||||
}
|
||||
|
||||
static inline void xlnx_uartlite_clear_status(struct device *dev)
|
||||
{
|
||||
struct xlnx_uartlite_data *data = dev->data;
|
||||
|
||||
/* Clear cached errors */
|
||||
data->errors = 0;
|
||||
}
|
||||
|
||||
static inline unsigned char xlnx_uartlite_read_rx_fifo(struct device *dev)
|
||||
{
|
||||
const struct xlnx_uartlite_config *config = dev->config;
|
||||
|
||||
return (sys_read32(config->base + RX_FIFO_OFFSET) & BIT_MASK(8));
|
||||
}
|
||||
|
||||
static inline void xlnx_uartlite_write_tx_fifo(struct device *dev,
|
||||
unsigned char c)
|
||||
{
|
||||
const struct xlnx_uartlite_config *config = dev->config;
|
||||
|
||||
sys_write32((uint32_t)c, config->base + TX_FIFO_OFFSET);
|
||||
}
|
||||
|
||||
static int xlnx_uartlite_poll_in(struct device *dev, unsigned char *c)
|
||||
{
|
||||
if (xlnx_uartlite_read_status(dev) & STAT_REG_RX_FIFO_VALID_DATA) {
|
||||
*c = xlnx_uartlite_read_rx_fifo(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void xlnx_uartlite_poll_out(struct device *dev, unsigned char c)
|
||||
{
|
||||
while (xlnx_uartlite_read_status(dev) & STAT_REG_TX_FIFO_FULL) {
|
||||
}
|
||||
|
||||
xlnx_uartlite_write_tx_fifo(dev, c);
|
||||
}
|
||||
|
||||
static int xlnx_uartlite_err_check(struct device *dev)
|
||||
{
|
||||
uint32_t status = xlnx_uartlite_read_status(dev);
|
||||
int err = 0;
|
||||
|
||||
if (status & STAT_REG_OVERRUN_ERROR) {
|
||||
err |= UART_ERROR_OVERRUN;
|
||||
}
|
||||
|
||||
if (status & STAT_REG_PARITY_ERROR) {
|
||||
err |= UART_ERROR_PARITY;
|
||||
}
|
||||
|
||||
if (status & STAT_REG_FRAME_ERROR) {
|
||||
err |= UART_ERROR_FRAMING;
|
||||
}
|
||||
|
||||
xlnx_uartlite_clear_status(dev);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
|
||||
static inline void xlnx_uartlite_irq_enable(struct device *dev)
|
||||
{
|
||||
const struct xlnx_uartlite_config *config = dev->config;
|
||||
|
||||
sys_write32(CTRL_REG_ENABLE_INTR, config->base + CTRL_REG_OFFSET);
|
||||
}
|
||||
|
||||
static inline void xlnx_uartlite_irq_cond_disable(struct device *dev)
|
||||
{
|
||||
const struct xlnx_uartlite_config *config = dev->config;
|
||||
struct xlnx_uartlite_data *data = dev->data;
|
||||
|
||||
/* TX and RX IRQs are shared. Only disable if both are disabled. */
|
||||
if (!data->tx_irq_enabled && !data->rx_irq_enabled) {
|
||||
sys_write32(0, config->base + CTRL_REG_OFFSET);
|
||||
}
|
||||
}
|
||||
|
||||
static int xlnx_uartlite_fifo_fill(struct device *dev, const uint8_t *tx_data,
|
||||
int len)
|
||||
{
|
||||
uint32_t status = xlnx_uartlite_read_status(dev);
|
||||
int count = 0U;
|
||||
|
||||
while ((len - count > 0) && (status & STAT_REG_TX_FIFO_FULL) == 0U) {
|
||||
xlnx_uartlite_write_tx_fifo(dev, tx_data[count++]);
|
||||
status = xlnx_uartlite_read_status(dev);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static int xlnx_uartlite_fifo_read(struct device *dev, uint8_t *rx_data,
|
||||
const int len)
|
||||
{
|
||||
uint32_t status = xlnx_uartlite_read_status(dev);
|
||||
int count = 0U;
|
||||
|
||||
while ((len - count > 0) && (status & STAT_REG_RX_FIFO_VALID_DATA)) {
|
||||
rx_data[count++] = xlnx_uartlite_read_rx_fifo(dev);
|
||||
status = xlnx_uartlite_read_status(dev);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void xlnx_uartlite_tx_soft_isr(struct k_timer *timer)
|
||||
{
|
||||
struct xlnx_uartlite_data *data =
|
||||
CONTAINER_OF(timer, struct xlnx_uartlite_data, timer);
|
||||
|
||||
if (data->callback) {
|
||||
data->callback(data->dev, data->callback_data);
|
||||
}
|
||||
}
|
||||
|
||||
static void xlnx_uartlite_irq_tx_enable(struct device *dev)
|
||||
{
|
||||
struct xlnx_uartlite_data *data = dev->data;
|
||||
uint32_t status;
|
||||
|
||||
data->tx_irq_enabled = true;
|
||||
status = xlnx_uartlite_read_status(dev);
|
||||
xlnx_uartlite_irq_enable(dev);
|
||||
|
||||
if ((status & STAT_REG_TX_FIFO_EMPTY) && data->callback) {
|
||||
/*
|
||||
* TX_FIFO_EMPTY 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 xlnx_uartlite_irq_tx_disable(struct device *dev)
|
||||
{
|
||||
struct xlnx_uartlite_data *data = dev->data;
|
||||
|
||||
data->tx_irq_enabled = false;
|
||||
xlnx_uartlite_irq_cond_disable(dev);
|
||||
}
|
||||
|
||||
static int xlnx_uartlite_irq_tx_ready(struct device *dev)
|
||||
{
|
||||
struct xlnx_uartlite_data *data = dev->data;
|
||||
uint32_t status = xlnx_uartlite_read_status(dev);
|
||||
|
||||
return (((status & STAT_REG_TX_FIFO_FULL) == 0U) &&
|
||||
data->tx_irq_enabled);
|
||||
}
|
||||
|
||||
static int xlnx_uartlite_irq_tx_complete(struct device *dev)
|
||||
{
|
||||
uint32_t status = xlnx_uartlite_read_status(dev);
|
||||
|
||||
return (status & STAT_REG_TX_FIFO_EMPTY);
|
||||
}
|
||||
|
||||
static void xlnx_uartlite_irq_rx_enable(struct device *dev)
|
||||
{
|
||||
struct xlnx_uartlite_data *data = dev->data;
|
||||
|
||||
data->rx_irq_enabled = true;
|
||||
/* RX_FIFO_VALID_DATA generates a level interrupt */
|
||||
xlnx_uartlite_irq_enable(dev);
|
||||
}
|
||||
|
||||
static void xlnx_uartlite_irq_rx_disable(struct device *dev)
|
||||
{
|
||||
struct xlnx_uartlite_data *data = dev->data;
|
||||
|
||||
data->rx_irq_enabled = false;
|
||||
xlnx_uartlite_irq_cond_disable(dev);
|
||||
}
|
||||
|
||||
static int xlnx_uartlite_irq_rx_ready(struct device *dev)
|
||||
{
|
||||
struct xlnx_uartlite_data *data = dev->data;
|
||||
uint32_t status = xlnx_uartlite_read_status(dev);
|
||||
|
||||
return ((status & STAT_REG_RX_FIFO_VALID_DATA) &&
|
||||
data->rx_irq_enabled);
|
||||
}
|
||||
|
||||
static int xlnx_uartlite_irq_is_pending(struct device *dev)
|
||||
{
|
||||
return (xlnx_uartlite_irq_tx_ready(dev) ||
|
||||
xlnx_uartlite_irq_rx_ready(dev));
|
||||
}
|
||||
|
||||
static int xlnx_uartlite_irq_update(struct device *dev)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void xlnx_uartlite_irq_callback_set(struct device *dev,
|
||||
uart_irq_callback_user_data_t cb,
|
||||
void *user_data)
|
||||
{
|
||||
struct xlnx_uartlite_data *data = dev->data;
|
||||
|
||||
data->callback = cb;
|
||||
data->callback_data = user_data;
|
||||
}
|
||||
|
||||
static __unused void xlnx_uartlite_isr(void *arg)
|
||||
{
|
||||
struct device *dev = arg;
|
||||
struct xlnx_uartlite_data *data = dev->data;
|
||||
|
||||
if (data->callback) {
|
||||
data->callback(dev, data->callback_data);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
|
||||
|
||||
static int xlnx_uartlite_init(struct device *dev)
|
||||
{
|
||||
const struct xlnx_uartlite_config *config = dev->config;
|
||||
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
|
||||
struct xlnx_uartlite_data *data = dev->data;
|
||||
|
||||
data->dev = dev;
|
||||
k_timer_init(&data->timer, &xlnx_uartlite_tx_soft_isr, NULL);
|
||||
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
|
||||
|
||||
/* Reset FIFOs and disable interrupts */
|
||||
sys_write32(CTRL_REG_RST_RX_FIFO | CTRL_REG_RST_TX_FIFO,
|
||||
config->base + CTRL_REG_OFFSET);
|
||||
|
||||
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
|
||||
config->irq_config_func(dev);
|
||||
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct uart_driver_api xlnx_uartlite_driver_api = {
|
||||
.poll_in = xlnx_uartlite_poll_in,
|
||||
.poll_out = xlnx_uartlite_poll_out,
|
||||
.err_check = xlnx_uartlite_err_check,
|
||||
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
|
||||
.fifo_fill = xlnx_uartlite_fifo_fill,
|
||||
.fifo_read = xlnx_uartlite_fifo_read,
|
||||
.irq_tx_enable = xlnx_uartlite_irq_tx_enable,
|
||||
.irq_tx_disable = xlnx_uartlite_irq_tx_disable,
|
||||
.irq_tx_ready = xlnx_uartlite_irq_tx_ready,
|
||||
.irq_tx_complete = xlnx_uartlite_irq_tx_complete,
|
||||
.irq_rx_enable = xlnx_uartlite_irq_rx_enable,
|
||||
.irq_rx_disable = xlnx_uartlite_irq_rx_disable,
|
||||
.irq_rx_ready = xlnx_uartlite_irq_rx_ready,
|
||||
.irq_is_pending = xlnx_uartlite_irq_is_pending,
|
||||
.irq_update = xlnx_uartlite_irq_update,
|
||||
.irq_callback_set = xlnx_uartlite_irq_callback_set,
|
||||
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
|
||||
};
|
||||
|
||||
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
|
||||
#define XLNX_UARTLITE_IRQ_INIT(n, i) \
|
||||
do { \
|
||||
IRQ_CONNECT(DT_INST_IRQ_BY_IDX(n, i, irq), \
|
||||
DT_INST_IRQ_BY_IDX(n, i, priority), \
|
||||
xlnx_uartlite_isr, \
|
||||
DEVICE_GET(xlnx_uartlite_##n), 0); \
|
||||
\
|
||||
irq_enable(DT_INST_IRQ_BY_IDX(n, i, irq)); \
|
||||
} while (0)
|
||||
#define XLNX_UARTLITE_CONFIG_FUNC(n) \
|
||||
static void xlnx_uartlite_config_func_##n(struct device *dev) \
|
||||
{ \
|
||||
/* IRQ line not always present on all instances */ \
|
||||
IF_ENABLED(DT_INST_IRQ_HAS_IDX(n, 0), \
|
||||
(XLNX_UARTLITE_IRQ_INIT(n, 0);)) \
|
||||
}
|
||||
#define XLNX_UARTLITE_IRQ_CFG_FUNC_INIT(n) \
|
||||
.irq_config_func = xlnx_uartlite_config_func_##n
|
||||
#define XLNX_UARTLITE_INIT_CFG(n) \
|
||||
XLNX_UARTLITE_DECLARE_CFG(n, XLNX_UARTLITE_IRQ_CFG_FUNC_INIT(n))
|
||||
#else
|
||||
#define XLNX_UARTLITE_CONFIG_FUNC(n)
|
||||
#define XLNX_UARTLITE_IRQ_CFG_FUNC_INIT
|
||||
#define XLNX_UARTLITE_INIT_CFG(n) \
|
||||
XLNX_UARTLITE_DECLARE_CFG(n, XLNX_UARTLITE_IRQ_CFG_FUNC_INIT)
|
||||
#endif
|
||||
|
||||
#define XLNX_UARTLITE_DECLARE_CFG(n, IRQ_FUNC_INIT) \
|
||||
static const struct xlnx_uartlite_config xlnx_uartlite_##n##_config = { \
|
||||
.base = DT_INST_REG_ADDR(n), \
|
||||
IRQ_FUNC_INIT \
|
||||
}
|
||||
|
||||
#define XLNX_UARTLITE_INIT(n) \
|
||||
static struct xlnx_uartlite_data xlnx_uartlite_##n##_data; \
|
||||
\
|
||||
static const struct xlnx_uartlite_config xlnx_uartlite_##n##_config;\
|
||||
\
|
||||
DEVICE_AND_API_INIT(xlnx_uartlite_##n, DT_INST_LABEL(n), \
|
||||
&xlnx_uartlite_init, \
|
||||
&xlnx_uartlite_##n##_data, \
|
||||
&xlnx_uartlite_##n##_config, \
|
||||
PRE_KERNEL_1, \
|
||||
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
|
||||
&xlnx_uartlite_driver_api); \
|
||||
\
|
||||
XLNX_UARTLITE_CONFIG_FUNC(n) \
|
||||
\
|
||||
XLNX_UARTLITE_INIT_CFG(n);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(XLNX_UARTLITE_INIT)
|
Loading…
Add table
Add a link
Reference in a new issue