From 1cde72a35d4ef7c04ac169e50c64bbacd8293144 Mon Sep 17 00:00:00 2001 From: Henrik Brix Andersen Date: Wed, 29 Jul 2020 00:14:22 +0200 Subject: [PATCH] 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 --- CODEOWNERS | 1 + drivers/serial/CMakeLists.txt | 1 + drivers/serial/Kconfig.xlnx | 7 + drivers/serial/uart_xlnx_uartlite.c | 388 ++++++++++++++++++++++++++++ 4 files changed, 397 insertions(+) create mode 100644 drivers/serial/uart_xlnx_uartlite.c diff --git a/CODEOWNERS b/CODEOWNERS index 3dc4c14de67..276d92aa073 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -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 diff --git a/drivers/serial/CMakeLists.txt b/drivers/serial/CMakeLists.txt index e14448c87e1..9dcaf8a513e 100644 --- a/drivers/serial/CMakeLists.txt +++ b/drivers/serial/CMakeLists.txt @@ -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) diff --git a/drivers/serial/Kconfig.xlnx b/drivers/serial/Kconfig.xlnx index b861500339d..4f0d67d24cb 100644 --- a/drivers/serial/Kconfig.xlnx +++ b/drivers/serial/Kconfig.xlnx @@ -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. diff --git a/drivers/serial/uart_xlnx_uartlite.c b/drivers/serial/uart_xlnx_uartlite.c new file mode 100644 index 00000000000..c1922dcadc3 --- /dev/null +++ b/drivers/serial/uart_xlnx_uartlite.c @@ -0,0 +1,388 @@ +/* + * Copyright (c) 2020 Henrik Brix Andersen + * + * 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 +#include +#include + +/* 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)