[PATCH 2/4] bsps/shared: Add new Mini UART driver

Utkarsh Verma utkarsh at bitbanged.com
Wed Aug 30 04:17:05 UTC 2023


This commit adds a new driver for the Mini UART peripheral present on
Raspberry Pi devices. It implements the termios API with support for
setting attributes. It supports interrupts through the
BSP_CONSOLE_USE_INTERRUPTS macro.
---
 bsps/include/dev/serial/mini-uart.h |  54 +++++
 bsps/shared/dev/serial/mini-uart.c  | 316 ++++++++++++++++++++++++++++
 2 files changed, 370 insertions(+)
 create mode 100644 bsps/include/dev/serial/mini-uart.h
 create mode 100644 bsps/shared/dev/serial/mini-uart.c

diff --git a/bsps/include/dev/serial/mini-uart.h b/bsps/include/dev/serial/mini-uart.h
new file mode 100644
index 0000000000..7fce5d0d97
--- /dev/null
+++ b/bsps/include/dev/serial/mini-uart.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+
+/*
+ * Copyright (C) 2023 Utkarsh Verma
+ *
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LIBBSP_SHARED_DEV_SERIAL_MINI_UART_H
+#define LIBBSP_SHARED_DEV_SERIAL_MINI_UART_H
+
+#include <bspopts.h>
+#include <rtems/rtems/intr.h>
+#include <rtems/termiosdevice.h>
+#include <stdint.h>
+
+typedef struct {
+    rtems_termios_device_context context;
+    const uintptr_t regs_base;
+    const uint32_t clock;
+    const uint32_t initial_baud;
+    const rtems_vector_number irq;
+
+#ifdef BSP_CONSOLE_USE_INTERRUPTS
+    volatile int tx_queued_chars;
+#endif
+} mini_uart_context;
+
+extern const rtems_termios_device_handler mini_uart_handler;
+
+void mini_uart_write_char_polled(const rtems_termios_device_context* context,
+                                 const char ch);
+
+#endif /* LIBBSP_SHARED_DEV_SERIAL_MINI_UART_H */
diff --git a/bsps/shared/dev/serial/mini-uart.c b/bsps/shared/dev/serial/mini-uart.c
new file mode 100644
index 0000000000..e84d7e786e
--- /dev/null
+++ b/bsps/shared/dev/serial/mini-uart.c
@@ -0,0 +1,316 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+
+/*
+ * Copyright (C) 2023 Utkarsh Verma
+ *
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "dev/serial/mini-uart.h"
+
+#include <bsp/utility.h>
+#include <bspopts.h>
+#include <rtems/libio.h>
+#include <rtems/termiosdevice.h>
+#include <rtems/termiostypes.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/_termios.h>
+
+#define REG(addr) *(volatile uint32_t *)(addr)
+
+/*
+ * NOTE:
+ * The datasheet will specify swapped bits for RX and TX interrupts in IER_REG,
+ * which is incorrect. The correct values are used here.
+ */
+#define IO_REG(base)              REG(base + 0x00)
+#define IO_REG_DATA_MASK          BSP_MSK32(0, 7)
+#define IER_REG(base)             REG(base + 0x04)
+#define IER_REG_RXE               BSP_BIT32(0)
+#define IER_REG_TXE               BSP_BIT32(1)
+#define IER_REG_IRQ_MASK          BSP_MSK32(0, 1)
+#define IIR_REG(base)             REG(base + 0x08)
+#define IIR_REG_IRQ_NOT_PENDING   BSP_BIT32(0)
+#define IIR_REG_TXFIFO_EMPTY      BSP_BIT32(1)
+#define IIR_REG_CLEAR_RXFIFO      BSP_BIT32(1)
+#define IIR_REG_RXFIFO_GOT_DATA   BSP_BIT32(2)
+#define IIR_REG_CLEAR_TXFIFO      BSP_BIT32(2)
+#define LCR_REG(base)             REG(base + 0x0c)
+#define LCR_REG_WLEN_8            BSP_BIT32(0)
+#define MCR_REG(base)             REG(base + 0x10)
+#define MCR_REG_RTS_LOW           BSP_BIT32(1)
+#define CNTL_REG(base)            REG(base + 0x20)
+#define CNTL_REG_TXE              BSP_BIT32(0)
+#define CNTL_REG_RXE              BSP_BIT32(1)
+#define STAT_REG(base)            REG(base + 0x24)
+#define STAT_REG_RXFIFO_NOT_EMPTY BSP_BIT32(0)
+#define STAT_REG_TXFIFO_HAS_SPACE BSP_BIT32(1)
+#define STAT_REG_TXFIFO_FULL      BSP_BIT32(5)
+#define STAT_REG_TX_IDLE          BSP_BIT32(3)
+#define BAUD_REG(base)            REG(base + 0x28)
+
+#define FIFO_SIZE 8
+
+static bool first_open(struct rtems_termios_tty *tty,
+                       rtems_termios_device_context *base,
+                       struct termios *term,
+                       rtems_libio_open_close_args_t *args);
+
+#ifdef BSP_CONSOLE_USE_INTERRUPTS
+static void last_close(rtems_termios_tty *tty,
+                       rtems_termios_device_context *base,
+                       rtems_libio_open_close_args_t *args);
+#else
+static int read_char_polled(rtems_termios_device_context *base);
+#endif
+
+static void write_buffer(rtems_termios_device_context *base,
+                         const char *buffer, size_t n);
+
+static bool set_attributes(rtems_termios_device_context *base,
+                           const struct termios *term);
+
+const rtems_termios_device_handler mini_uart_handler = {
+    .first_open     = first_open,
+    .write          = write_buffer,
+    .set_attributes = set_attributes,
+    .ioctl          = NULL,
+#ifdef BSP_CONSOLE_USE_INTERRUPTS
+    .last_close = last_close,
+    .poll_read  = NULL,
+    .mode       = TERMIOS_IRQ_DRIVEN,
+#else
+    .last_close = NULL,
+    .poll_read  = read_char_polled,
+    .mode       = TERMIOS_POLLED,
+#endif
+};
+
+static inline char read_char(const uintptr_t regs_base) {
+    return IO_REG(regs_base) & IO_REG_DATA_MASK;
+}
+
+static inline void write_char(const uintptr_t regs_base, const char ch) {
+    IO_REG(regs_base) = ch;
+}
+
+#ifdef BSP_CONSOLE_USE_INTERRUPTS
+static inline void enable_irq(const uintptr_t regs_base,
+                              const uint32_t irq_mask) {
+    IER_REG(regs_base) |= irq_mask;
+}
+#endif
+
+static inline void disable_irq(const uintptr_t regs_base,
+                               const uint32_t irq_mask) {
+    IER_REG(regs_base) &= ~irq_mask;
+}
+
+static inline bool is_rxfifo_empty(const uintptr_t regs_base) {
+    return (STAT_REG(regs_base) & STAT_REG_RXFIFO_NOT_EMPTY) != 0;
+}
+
+static inline bool txfifo_has_space(const uintptr_t regs_base) {
+    return (STAT_REG(regs_base) & STAT_REG_TXFIFO_HAS_SPACE) != 0;
+}
+
+#ifdef BSP_CONSOLE_USE_INTERRUPTS
+static void irq_handler(void *arg) {
+    rtems_termios_tty *tty     = arg;
+    mini_uart_context *context = rtems_termios_get_device_context(tty);
+    const uintptr_t regs_base  = context->regs_base;
+    const uint32_t irqs        = IIR_REG(regs_base);
+
+    /* RXFIFO got data */
+    if ((irqs & IIR_REG_RXFIFO_GOT_DATA) != 0) {
+        char buffer[FIFO_SIZE];
+
+        unsigned int i = 0;
+        while (i < sizeof(buffer) && !is_rxfifo_empty(regs_base))
+            buffer[i++] = read_char(regs_base);
+
+        (void)rtems_termios_enqueue_raw_characters(tty, buffer, i);
+    }
+
+    /*
+     * Transmission was queued last time and TXFIFO is now empty, so tell
+     * Termios to dequeue the sent bytes.
+     */
+    if ((irqs & IIR_REG_TXFIFO_EMPTY) != 0)
+        (void)rtems_termios_dequeue_characters(tty, context->tx_queued_chars);
+}
+#endif
+
+static bool first_open(struct rtems_termios_tty *tty,
+                       rtems_termios_device_context *base,
+                       struct termios *term,
+                       rtems_libio_open_close_args_t *args) {
+#ifndef BSP_CONSOLE_USE_INTERRUPTS
+    const
+#endif
+        mini_uart_context *context = (void *)base;
+
+    if (rtems_termios_set_initial_baud(tty, context->initial_baud) != 0)
+        return false;
+
+    if (!set_attributes(base, term))
+        return false;
+
+#ifdef BSP_CONSOLE_USE_INTERRUPTS
+    const rtems_status_code status = rtems_interrupt_handler_install(
+        context->irq, "UART", RTEMS_INTERRUPT_SHARED, irq_handler, tty);
+    if (status != RTEMS_SUCCESSFUL)
+        return false;
+#endif
+
+    return true;
+}
+
+#ifdef BSP_CONSOLE_USE_INTERRUPTS
+static void last_close(rtems_termios_tty *tty,
+                       rtems_termios_device_context *base,
+                       rtems_libio_open_close_args_t *args) {
+    const mini_uart_context *context = (void *)base;
+    (void)rtems_interrupt_handler_remove(context->irq, irq_handler, tty);
+}
+#else
+static int read_char_polled(rtems_termios_device_context *base) {
+    const mini_uart_context *context = (void *)base;
+    const uintptr_t regs_base        = context->regs_base;
+
+    /* Data is available to be read */
+    if (!is_rxfifo_empty(regs_base))
+        return read_char(regs_base);
+
+    /* There is no data to be read */
+    return -1;
+}
+#endif
+
+static void write_buffer(rtems_termios_device_context *base,
+                         const char *buffer, size_t n) {
+#ifdef BSP_CONSOLE_USE_INTERRUPTS
+    mini_uart_context *context = (void *)base;
+    const uintptr_t regs_base  = context->regs_base;
+
+    if (n > 0) {
+        size_t i = 0;
+        while (txfifo_has_space(regs_base) && i < n) {
+            write_char(regs_base, buffer[i]);
+            i++;
+        }
+
+        context->tx_queued_chars = i;
+        enable_irq(regs_base, IER_REG_TXE);
+
+        return;
+    }
+
+    /*
+     * Termios will set n to zero to indicate that the transmitter is now
+     * inactive. The output buffer is empty in this case. The driver may
+     * disable the transmit interrupts now.
+     */
+    disable_irq(regs_base, IER_REG_TXE);
+#else
+    for (size_t i = 0; i < n; i++)
+        mini_uart_write_char_polled(base, buffer[i]);
+#endif
+}
+
+static bool set_attributes(rtems_termios_device_context *base,
+                           const struct termios *term) {
+    const mini_uart_context *ctx = (void *)base;
+    const uintptr_t regs_base    = ctx->regs_base;
+
+    /* Disable interrupts */
+    disable_irq(regs_base, IER_REG_IRQ_MASK);
+
+    /* Disable data transfers */
+    CNTL_REG(regs_base) &= ~(CNTL_REG_RXE | CNTL_REG_TXE);
+    uint32_t cntl = CNTL_REG(regs_base);
+
+    /*
+     * Flush both FIFOs
+     * NOTE: Only the FIFO bits support writing so direct assignment is safe.
+     */
+    IIR_REG(regs_base) = IIR_REG_CLEAR_RXFIFO | IIR_REG_CLEAR_TXFIFO;
+
+    /*
+     * Set the character size
+     * Mini UART only supports 7-bit and 8-bit sizes
+     */
+    switch (term->c_cflag & CSIZE) {
+        case CS7:
+            LCR_REG(regs_base) &= ~LCR_REG_WLEN_8;
+            break;
+        case CS8:
+        default:
+            LCR_REG(regs_base) |= LCR_REG_WLEN_8;
+    }
+
+    /*
+     * TODO:
+     * Add hardware-flow control.
+     * For now, this just asserts RTS HIGH all the time.
+     */
+    MCR_REG(regs_base) &= ~MCR_REG_RTS_LOW;
+
+    /* Set the baudrate */
+    speed_t baud = rtems_termios_number_to_baud(term->c_ospeed);
+    if (baud == B0)
+        return false;
+    BAUD_REG(regs_base) = ctx->clock / 8 / baud - 1;
+
+    /* Re-enable transmission */
+    cntl |= CNTL_REG_TXE;
+
+    /* Configure receiver */
+    const bool rx_enabled = (term->c_cflag & CREAD) != 0;
+    if (rx_enabled)
+        cntl |= CNTL_REG_RXE;
+
+    /* Commit changes to the peripheral */
+    CNTL_REG(regs_base) = cntl;
+
+#ifdef BSP_CONSOLE_USE_INTERRUPTS
+    /* Re-enable RX interrupts */
+    if (rx_enabled)
+        enable_irq(regs_base, IER_REG_RXE);
+#endif
+
+    return true;
+}
+
+void mini_uart_write_char_polled(const rtems_termios_device_context *base,
+                                 const char ch) {
+    const mini_uart_context *context = (void *)base;
+    const uintptr_t regs_base        = context->regs_base;
+
+    while (!txfifo_has_space(regs_base))
+        ;
+
+    write_char(regs_base, ch);
+}
-- 
2.41.0



More information about the devel mailing list