[PATCH 1/4] bsps/shared: Add new PL011 driver with IRQ support

Utkarsh Verma utkarsh at bitbanged.com
Tue Aug 29 04:44:48 UTC 2023


This commit adds a new driver for the PL011. It uses the termios API
with support for setting attributes and interrupts. Interrupts can be
enabled using the BSP_CONSOLE_USE_INTERRUPTS macro.
---
 bsps/include/dev/serial/arm-pl011-regs.h      | 143 ------
 .../dev/serial/{arm-pl011.h => pl011.h}       |  62 +--
 bsps/shared/dev/serial/arm-pl011.c            | 104 ----
 bsps/shared/dev/serial/pl011.c                | 470 ++++++++++++++++++
 4 files changed, 503 insertions(+), 276 deletions(-)
 delete mode 100644 bsps/include/dev/serial/arm-pl011-regs.h
 rename bsps/include/dev/serial/{arm-pl011.h => pl011.h} (51%)
 delete mode 100644 bsps/shared/dev/serial/arm-pl011.c
 create mode 100644 bsps/shared/dev/serial/pl011.c

diff --git a/bsps/include/dev/serial/arm-pl011-regs.h b/bsps/include/dev/serial/arm-pl011-regs.h
deleted file mode 100644
index d6ea9ae11a..0000000000
--- a/bsps/include/dev/serial/arm-pl011-regs.h
+++ /dev/null
@@ -1,143 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-
-/**
- *  @file
- *
- *  @ingroup RTEMSBSPsARMShared
- *
- *  @brief ARM PL011 Register definitions
- */
-
-/*
- * Copyright (c) 2013 embedded brains GmbH & Co. KG
- *
- * 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_ARM_SHARED_ARM_PL011_REGS_H
-#define LIBBSP_ARM_SHARED_ARM_PL011_REGS_H
-
-#include <bsp/utility.h>
-
-typedef struct {
-  uint32_t uartdr;
-#define PL011_UARTDR_OE BSP_BIT32(11)
-#define PL011_UARTDR_BE BSP_BIT32(10)
-#define PL011_UARTDR_PE BSP_BIT32(9)
-#define PL011_UARTDR_FE BSP_BIT32(8)
-#define PL011_UARTDR_DATA(val) BSP_FLD32(val, 0, 7)
-#define PL011_UARTDR_DATA_GET(reg) BSP_FLD32GET(reg, 0, 7)
-#define PL011_UARTDR_DATA_SET(reg, val) BSP_FLD32SET(reg, val, 0, 7)
-  uint32_t uartrsr_uartecr;
-#define PL011_UARTRSR_UARTECR_OE BSP_BIT32(3)
-#define PL011_UARTRSR_UARTECR_BE BSP_BIT32(2)
-#define PL011_UARTRSR_UARTECR_PE BSP_BIT32(1)
-#define PL011_UARTRSR_UARTECR_FE BSP_BIT32(0)
-  uint32_t reserved_08[4];
-  uint32_t uartfr;
-#define PL011_UARTFR_RI BSP_BIT32(8)
-#define PL011_UARTFR_TXFE BSP_BIT32(7)
-#define PL011_UARTFR_RXFF BSP_BIT32(6)
-#define PL011_UARTFR_TXFF BSP_BIT32(5)
-#define PL011_UARTFR_RXFE BSP_BIT32(4)
-#define PL011_UARTFR_BUSY BSP_BIT32(3)
-#define PL011_UARTFR_DCD BSP_BIT32(2)
-#define PL011_UARTFR_DSR BSP_BIT32(1)
-#define PL011_UARTFR_CTS BSP_BIT32(0)
-  uint32_t reserved_1c;
-  uint32_t uartilpr;
-#define PL011_UARTILPR_ILPDVSR(val) BSP_FLD32(val, 0, 7)
-#define PL011_UARTILPR_ILPDVSR_GET(reg) BSP_FLD32GET(reg, 0, 7)
-#define PL011_UARTILPR_ILPDVSR_SET(reg, val) BSP_FLD32SET(reg, val, 0, 7)
-  uint32_t uartibrd;
-#define PL011_UARTIBRD_BAUD_DIVINT(val) BSP_FLD32(val, 0, 15)
-#define PL011_UARTIBRD_BAUD_DIVINT_GET(reg) BSP_FLD32GET(reg, 0, 15)
-#define PL011_UARTIBRD_BAUD_DIVINT_SET(reg, val) BSP_FLD32SET(reg, val, 0, 15)
-  uint32_t uartfbrd;
-#define PL011_UARTFBRD_BAUD_DIVFRAC(val) BSP_FLD32(val, 0, 5)
-#define PL011_UARTFBRD_BAUD_DIVFRAC_GET(reg) BSP_FLD32GET(reg, 0, 5)
-#define PL011_UARTFBRD_BAUD_DIVFRAC_SET(reg, val) BSP_FLD32SET(reg, val, 0, 5)
-  uint32_t uartlcr_h;
-#define PL011_UARTLCR_H_SPS BSP_BIT32(7)
-#define PL011_UARTLCR_H_WLEN(val) BSP_FLD32(val, 5, 6)
-#define PL011_UARTLCR_H_WLEN_GET(reg) BSP_FLD32GET(reg, 5, 6)
-#define PL011_UARTLCR_H_WLEN_SET(reg, val) BSP_FLD32SET(reg, val, 5, 6)
-#define PL011_UARTLCR_H_WLEN_5 0x00U
-#define PL011_UARTLCR_H_WLEN_6 0x01U
-#define PL011_UARTLCR_H_WLEN_7 0x02U
-#define PL011_UARTLCR_H_WLEN_8 0x03U
-#define PL011_UARTLCR_H_FEN BSP_BIT32(4)
-#define PL011_UARTLCR_H_STP2 BSP_BIT32(3)
-#define PL011_UARTLCR_H_EPS BSP_BIT32(2)
-#define PL011_UARTLCR_H_PEN BSP_BIT32(1)
-#define PL011_UARTLCR_H_BRK BSP_BIT32(0)
-  uint32_t uartcr;
-#define PL011_UARTCR_CTSEN BSP_BIT32(15)
-#define PL011_UARTCR_RTSEN BSP_BIT32(14)
-#define PL011_UARTCR_OUT2 BSP_BIT32(13)
-#define PL011_UARTCR_OUT1 BSP_BIT32(12)
-#define PL011_UARTCR_RTS BSP_BIT32(11)
-#define PL011_UARTCR_DTR BSP_BIT32(10)
-#define PL011_UARTCR_RXE BSP_BIT32(9)
-#define PL011_UARTCR_TXE BSP_BIT32(8)
-#define PL011_UARTCR_LBE BSP_BIT32(7)
-#define PL011_UARTCR_SIRLP BSP_BIT32(3)
-#define PL011_UARTCR_SIREN BSP_BIT32(2)
-#define PL011_UARTCR_UARTEN BSP_BIT32(1)
-  uint32_t uartifls;
-#define PL011_UARTIFLS_RXIFLSEL(val) BSP_FLD32(val, 3, 5)
-#define PL011_UARTIFLS_RXIFLSEL_GET(reg) BSP_FLD32GET(reg, 3, 5)
-#define PL011_UARTIFLS_RXIFLSEL_SET(reg, val) BSP_FLD32SET(reg, val, 3, 5)
-#define PL011_UARTIFLS_TXIFLSEL(val) BSP_FLD32(val, 0, 2)
-#define PL011_UARTIFLS_TXIFLSEL_GET(reg) BSP_FLD32GET(reg, 0, 2)
-#define PL011_UARTIFLS_TXIFLSEL_SET(reg, val) BSP_FLD32SET(reg, val, 0, 2)
-  uint32_t uartimsc;
-  uint32_t uartris;
-  uint32_t uartmis;
-  uint32_t uarticr;
-#define PL011_UARTI_OEI BSP_BIT32(10)
-#define PL011_UARTI_BEI BSP_BIT32(9)
-#define PL011_UARTI_PEI BSP_BIT32(8)
-#define PL011_UARTI_FEI BSP_BIT32(7)
-#define PL011_UARTI_RTI BSP_BIT32(6)
-#define PL011_UARTI_TXI BSP_BIT32(5)
-#define PL011_UARTI_RXI BSP_BIT32(4)
-#define PL011_UARTI_DSRMI BSP_BIT32(3)
-#define PL011_UARTI_DCDMI BSP_BIT32(2)
-#define PL011_UARTI_CTSMI BSP_BIT32(1)
-#define PL011_UARTI_RIMI BSP_BIT32(0)
-  uint32_t uartdmacr;
-#define PL011_UARTDMACR_DMAONERR BSP_BIT32(2)
-#define PL011_UARTDMACR_TXDMAE BSP_BIT32(1)
-#define PL011_UARTDMACR_RXDMAE BSP_BIT32(0)
-  uint32_t reserved_4c[997];
-  uint32_t uartperiphid0;
-  uint32_t uartperiphid1;
-  uint32_t uartperiphid2;
-  uint32_t uartperiphid3;
-  uint32_t uartpcellid0;
-  uint32_t uartpcellid1;
-  uint32_t uartpcellid2;
-  uint32_t uartpcellid3;
-} pl011;
-
-#endif /* LIBBSP_ARM_SHARED_ARM_PL011_REGS_H */
diff --git a/bsps/include/dev/serial/arm-pl011.h b/bsps/include/dev/serial/pl011.h
similarity index 51%
rename from bsps/include/dev/serial/arm-pl011.h
rename to bsps/include/dev/serial/pl011.h
index a22fa1ac06..128bdce5f5 100644
--- a/bsps/include/dev/serial/arm-pl011.h
+++ b/bsps/include/dev/serial/pl011.h
@@ -1,15 +1,8 @@
 /* SPDX-License-Identifier: BSD-2-Clause */
 
-/**
- *  @file
- *
- *  @ingroup RTEMSBSPsARMShared
- *
- *  @brief ARM PL011 Support Package
- */
-
 /*
- * Copyright (C) 2013, 2014 embedded brains GmbH & Co. KG
+ * Copyright (C) 2023 Utkarsh Verma
+ *
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -33,32 +26,43 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef LIBBSP_ARM_SHARED_ARM_PL011_H
-#define LIBBSP_ARM_SHARED_ARM_PL011_H
+#ifndef LIBBSP_SHARED_DEV_SERIAL_PL011_H
+#define LIBBSP_SHARED_DEV_SERIAL_PL011_H
 
+#include <bspopts.h>
+#include <rtems/rtems/intr.h>
+#include <rtems/termiosdevice.h>
 #include <rtems/termiostypes.h>
-
-#include <dev/serial/arm-pl011-regs.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
+#include <stddef.h>
+#include <stdint.h>
 
 typedef struct {
-  rtems_termios_device_context base;
-  volatile pl011 *regs;
-  rtems_vector_number irq;
-  uint32_t initial_baud;
-} arm_pl011_context;
+    rtems_termios_device_context context;
+    const uintptr_t regs_base;
+    const uint32_t clock;
+    const uint32_t initial_baud;
+    const rtems_vector_number irq;
 
-bool arm_pl011_probe(rtems_termios_device_context *base);
+#ifdef BSP_CONSOLE_USE_INTERRUPTS
+    /*
+     * Due to HW limitation, the first TX interrupt should be triggered by the
+     * software. This is because TX interrupts are based on transition through
+     * a level, rather than on the level itself. When the UART interrupt and
+     * UART is enabled before any data is written to the TXFIFO, the interrupt
+     * is not set. The interrupt is only set once the TXFIFO becomes empty
+     * after being filled to the trigger level. Until then, this flag variable
+     * ensures that the interrupt handler is software triggered.
+     */
+    volatile bool needs_sw_triggered_tx_irq;
 
-void arm_pl011_write_polled(rtems_termios_device_context *base, char c);
+    volatile int tx_queued_chars;
+    rtems_termios_tty* tty;
+#endif
+} pl011_context;
 
-extern const rtems_termios_device_handler arm_pl011_fns;
+extern const rtems_termios_device_handler pl011_handler;
 
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
+void pl011_write_char_polled(const rtems_termios_device_context* base,
+                             const char ch);
 
-#endif /* LIBBSP_ARM_SHARED_ARM_PL011_H */
+#endif /* LIBBSP_SHARED_DEV_SERIAL_PL011_H */
diff --git a/bsps/shared/dev/serial/arm-pl011.c b/bsps/shared/dev/serial/arm-pl011.c
deleted file mode 100644
index e9a8e3f5a4..0000000000
--- a/bsps/shared/dev/serial/arm-pl011.c
+++ /dev/null
@@ -1,104 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-
-/*
- * Copyright (C) 2013, 2014 embedded brains GmbH & Co. KG
- *
- * 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/arm-pl011.h>
-
-static volatile pl011 *pl011_get_regs(rtems_termios_device_context *base)
-{
-  arm_pl011_context *ctx = (arm_pl011_context *) base;
-
-  return ctx->regs;
-}
-
-
-bool arm_pl011_probe(rtems_termios_device_context *base)
-{
-  volatile pl011 *regs = pl011_get_regs(base);
-
-  regs->uartlcr_h = PL011_UARTLCR_H_WLEN(PL011_UARTLCR_H_WLEN_8);
-  regs->uartcr = PL011_UARTCR_RXE
-    | PL011_UARTCR_TXE
-    | PL011_UARTCR_UARTEN;
-
-  return true;
-}
-
-static bool pl011_first_open(
-  struct rtems_termios_tty *tty,
-  rtems_termios_device_context *base,
-  struct termios *term,
-  rtems_libio_open_close_args_t *args
-)
-{
-  arm_pl011_context *ctx = (arm_pl011_context *) base;
-
-  rtems_termios_set_initial_baud(tty, ctx->initial_baud);
-
-  return true;
-}
-
-static int pl011_read_polled(rtems_termios_device_context *base)
-{
-  volatile pl011 *regs = pl011_get_regs(base);
-
-  if ((regs->uartfr & PL011_UARTFR_RXFE) != 0) {
-    return -1;
-  } else {
-    return PL011_UARTDR_DATA_GET(regs->uartdr);
-  }
-}
-
-void arm_pl011_write_polled(rtems_termios_device_context *base, char c)
-{
-  volatile pl011 *regs = pl011_get_regs(base);
-
-  while ((regs->uartfr & PL011_UARTFR_TXFF) != 0) {
-    /* Wait */
-  }
-
-  regs->uartdr = PL011_UARTDR_DATA(c);
-}
-
-static void pl011_write_support_polled(
-  rtems_termios_device_context *base,
-  const char *s,
-  size_t n
-)
-{
-  size_t i;
-
-  for (i = 0; i < n; ++i) {
-    arm_pl011_write_polled(base, s[i]);
-  }
-}
-
-const rtems_termios_device_handler arm_pl011_fns = {
-  .first_open = pl011_first_open,
-  .poll_read = pl011_read_polled,
-  .write = pl011_write_support_polled,
-  .mode = TERMIOS_POLLED
-};
diff --git a/bsps/shared/dev/serial/pl011.c b/bsps/shared/dev/serial/pl011.c
new file mode 100644
index 0000000000..5bee3aac9c
--- /dev/null
+++ b/bsps/shared/dev/serial/pl011.c
@@ -0,0 +1,470 @@
+/* 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/pl011.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)
+
+#define DR(base)                REG(base + 0x00)
+#define DR_DATA_MASK            BSP_MSK32(0, 7)
+#define FR(base)                REG(base + 0x18)
+#define FR_BUSY                 BSP_BIT32(3)
+#define FR_RXFE                 BSP_BIT32(4)
+#define FR_TXFF                 BSP_BIT32(5)
+#define FR_TXFE                 BSP_BIT32(7)
+#define IBRD(base)              REG(base + 0x24)
+#define IBRD_BAUD_DIVINT_WIDTH  16
+#define IBRD_BAUD_DIVINT_MASK   BSP_MSK32(0, IBRD_BAUD_DIVINT_WIDTH - 1)
+#define FBRD(base)              REG(base + 0x28)
+#define FBRD_BAUD_DIVFRAC_WIDTH 6
+#define FBRD_BAUD_DIVFRAC_MASK  BSP_MSK32(0, FBRD_BAUD_DIVFRAC_WIDTH - 1)
+#define LCRH(base)              REG(base + 0x2c)
+#define LCRH_PEN                BSP_BIT32(1)
+#define LCRH_EPS                BSP_BIT32(2)
+#define LCRH_STP2               BSP_BIT32(3)
+#define LCRH_FEN                BSP_BIT32(4)
+#define LCRH_WLEN_MASK          BSP_MSK32(5, 6)
+#define LCRH_WLEN_5BITS         BSP_FLD32(0, 5, 6)
+#define LCRH_WLEN_6BITS         BSP_FLD32(1, 5, 6)
+#define LCRH_WLEN_7BITS         BSP_FLD32(2, 5, 6)
+#define LCRH_WLEN_8BITS         BSP_FLD32(3, 5, 6)
+#define CR(base)                REG(base + 0x30)
+#define CR_UARTEN               BSP_BIT32(0)
+#define CR_TXE                  BSP_BIT32(8)
+#define CR_RXE                  BSP_BIT32(9)
+#define CR_RTSEN                BSP_BIT32(14)
+#define CR_CTSEN                BSP_BIT32(15)
+#define IFLS(base)              REG(base + 0x34)
+#define IFLS_TXIFLSEL_MASK      BSP_MSK32(0, 2)
+#define IFLS_TXIFLSEL(level)    BSP_FLD32(level, 0, 2)
+#define IFLS_RXIFLSEL_MASK      BSP_MSK32(3, 5)
+#define IFLS_RXIFLSEL(level)    BSP_FLD32(level, 3, 5)
+#define IMSC(base)              REG(base + 0x38)
+#define MIS(base)               REG(base + 0x40)
+#define ICR(base)               REG(base + 0x44)
+
+/* Applies to IMSC, ICR, and MIS */
+#define IRQ_RX_BIT BSP_BIT32(4)
+#define IRQ_RT_BIT BSP_BIT32(6)
+#define IRQ_TX_BIT BSP_BIT32(5)
+#define IRQ_FE_BIT BSP_BIT32(7)
+#define IRQ_PE_BIT BSP_BIT32(8)
+#define IRQ_BE_BIT BSP_BIT32(9)
+#define IRQ_OE_BIT BSP_BIT32(10)
+#define IRQ_MASK   BSP_MSK32(0, 10)
+
+#ifdef BSP_CONSOLE_USE_INTERRUPTS
+#define FIFO_SIZE                32
+#define TXFIFO_IRQ_TRIGGER_LEVEL FIFO_LEVEL_ONE_EIGHTH
+#define RXFIFO_IRQ_TRIGGER_LEVEL FIFO_LEVEL_ONE_HALF
+
+enum fifo_trigger_level {
+    FIFO_LEVEL_ONE_EIGHTH,
+    FIFO_LEVEL_ONE_FOURTH,
+    FIFO_LEVEL_ONE_HALF,
+    FIFO_LEVEL_THREE_FOURTH,
+    FIFO_LELEL_SEVEN_HALF,
+};
+#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);
+
+#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 pl011_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 DR(regs_base) & DR_DATA_MASK;
+}
+
+static inline void write_char(const uintptr_t regs_base, const char ch) {
+    DR(regs_base) = ch;
+}
+
+static inline bool is_rxfifo_empty(const uintptr_t regs_base) {
+    return (FR(regs_base) & FR_RXFE) != 0;
+}
+
+static inline bool is_txfifo_full(const uintptr_t regs_base) {
+    return (FR(regs_base) & FR_TXFF) != 0;
+}
+
+static void flush_fifos(const pl011_context *context) {
+    const uintptr_t regs_base = context->regs_base;
+
+    /* Wait for pending transactions */
+    while ((FR(regs_base) & FR_BUSY) != 0)
+        ;
+
+    LCRH(regs_base) &= ~LCRH_FEN;
+    LCRH(regs_base) |= LCRH_FEN;
+}
+
+#ifdef BSP_CONSOLE_USE_INTERRUPTS
+static inline void clear_irq(const uintptr_t regs_base,
+                             const uint32_t irq_mask) {
+    /* ICR is a write-only register */
+    ICR(regs_base) = irq_mask;
+}
+
+static inline void enable_irq(const uintptr_t regs_base,
+                              const uint32_t irq_mask) {
+    IMSC(regs_base) |= irq_mask;
+}
+#endif
+
+static inline void disable_irq(uintptr_t regs_base, uint32_t irq_mask) {
+    IMSC(regs_base) &= ~irq_mask;
+}
+
+#ifdef BSP_CONSOLE_USE_INTERRUPTS
+static void irq_handler(void *arg) {
+    pl011_context *context    = (void *)arg;
+    const uintptr_t regs_base = context->regs_base;
+    const uint32_t irqs       = MIS(regs_base);
+
+    /* RXFIFO got data */
+    const uint32_t rx_irq_mask = IRQ_RT_BIT | IRQ_RX_BIT;
+    if ((irqs & rx_irq_mask) != 0) {
+        char buffer[FIFO_SIZE];
+
+        unsigned int i = 0;
+        while (i < sizeof(buffer) && !is_rxfifo_empty(regs_base)) {
+            buffer[i] = read_char(regs_base);
+            i++;
+        }
+
+        (void)rtems_termios_enqueue_raw_characters(context->tty, buffer, i);
+
+        /* Clear all interrupts */
+        clear_irq(regs_base, rx_irq_mask);
+    }
+
+    /*
+     * Some characters got queued in the TXFIFO, so dequeue them from Termios'
+     * structures.
+     */
+    if (context->tx_queued_chars != -1) {
+        /*
+         * First interrupt was raised, so no need to trigger the handler
+         * through software anymore.
+         */
+        if (context->needs_sw_triggered_tx_irq && (irqs & IRQ_TX_BIT) != 0)
+            context->needs_sw_triggered_tx_irq = false;
+
+        (void)rtems_termios_dequeue_characters(context->tty,
+                                               context->tx_queued_chars);
+
+        /* No need to clear the interrupt. It will automatically get cleared
+         * when TXFIFO is filled above the trigger level. */
+    }
+}
+#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
+        pl011_context *context = (void *)base;
+
+#ifdef BSP_CONSOLE_USE_INTERRUPTS
+    context->tty = tty;
+#endif
+
+    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 uintptr_t regs_base = context->regs_base;
+
+    /* Set FIFO trigger levels for interrupts */
+    uint32_t ifls = IFLS(regs_base);
+    ifls &= ~(IFLS_RXIFLSEL_MASK | IFLS_TXIFLSEL_MASK);
+    ifls |= IFLS_TXIFLSEL(TXFIFO_IRQ_TRIGGER_LEVEL) |
+            IFLS_RXIFLSEL(RXFIFO_IRQ_TRIGGER_LEVEL);
+    IFLS(regs_base) = ifls;
+
+    const rtems_status_code sc = rtems_interrupt_handler_install(
+        context->irq, "UART", RTEMS_INTERRUPT_SHARED, irq_handler, context);
+    if (sc != 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 pl011_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 pl011_context *context = (void *)base;
+    const uintptr_t regs_base    = context->regs_base;
+
+    if (is_rxfifo_empty(regs_base))
+        return -1;
+
+    return read_char(regs_base);
+}
+#endif
+
+static void write_buffer(rtems_termios_device_context *base,
+                         const char *buffer, size_t n) {
+#ifdef BSP_CONSOLE_USE_INTERRUPTS
+    pl011_context *context    = (void *)base;
+    const uintptr_t regs_base = context->regs_base;
+
+    if (n > 0) {
+        size_t i = 0;
+        while (!is_txfifo_full(regs_base) && i < n) {
+            write_char(regs_base, buffer[i]);
+            i++;
+        }
+
+        context->tx_queued_chars = i;
+        enable_irq(regs_base, IRQ_TX_BIT);
+
+        if (context->needs_sw_triggered_tx_irq)
+            irq_handler(context);
+
+        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, IRQ_TX_BIT);
+#else
+    for (size_t i = 0; i < n; i++)
+        pl011_write_char_polled(base, buffer[i]);
+#endif
+}
+
+static int compute_baudrate_params(uint32_t *ibrd, uint32_t *fbrd,
+                                   const uint32_t baudrate,
+                                   const uint32_t clock,
+                                   const unsigned short max_error) {
+    /*
+     * The integer baudrate divisor, i.e. (clock / (baudrate * 16)), value
+     * should lie on [1, 2^16 - 1]. To ensure this, clock and baudrate have to
+     * be validated.
+     */
+    *ibrd = clock / 16 / baudrate;
+    if (*ibrd < 1 || *ibrd > IBRD_BAUD_DIVINT_MASK)
+        return 2;
+
+    /* Find the fractional part */
+    const uint16_t scalar         = 1 << (FBRD_BAUD_DIVFRAC_WIDTH + 1);
+    const uint64_t scaled_bauddiv = ((uint64_t)clock * scalar) / 16 / baudrate;
+    const unsigned short round_off = scaled_bauddiv & 0x1;
+    *fbrd = ((scaled_bauddiv >> 1) & FBRD_BAUD_DIVFRAC_MASK) + round_off;
+
+    /* Calculate the baudrate and check if the error is too large */
+    const uint32_t computed_bauddiv =
+        (*ibrd << FBRD_BAUD_DIVFRAC_WIDTH) | *fbrd;
+    const uint32_t computed_baudrate =
+        ((uint64_t)clock << FBRD_BAUD_DIVFRAC_WIDTH) / 16 / computed_bauddiv;
+
+    uint32_t baud_error = computed_baudrate - baudrate;
+    if (baudrate > computed_baudrate)
+        baud_error = baudrate - computed_baudrate;
+
+    const unsigned short percent_error = (baud_error * 100) / baudrate;
+    if (percent_error >= max_error)
+        return 1;
+
+    return 0;
+}
+
+static bool set_attributes(rtems_termios_device_context *base,
+                           const struct termios *term) {
+    pl011_context *context    = (void *)base;
+    const uintptr_t regs_base = context->regs_base;
+
+    /* Determine baudrate parameters */
+    const uint32_t baud = rtems_termios_number_to_baud(term->c_ospeed);
+    if (baud == B0)
+        return false;
+
+    uint32_t ibrd, fbrd;
+    if (compute_baudrate_params(&ibrd, &fbrd, baud, context->clock, 3))
+        return false;
+
+    /* Start mode configuration from a clean slate */
+    uint32_t lcrh = LCRH(regs_base) & LCRH_FEN;
+
+    /* Mode: parity */
+    if ((term->c_cflag & PARENB) != 0) {
+        lcrh |= LCRH_PEN;
+
+        if ((term->c_cflag & PARODD) == 0)
+            lcrh |= LCRH_EPS;
+    }
+
+    /* Mode: character size */
+    switch (term->c_cflag & CSIZE) {
+        case CS5:
+            lcrh |= LCRH_WLEN_5BITS;
+            break;
+        case CS6:
+            lcrh |= LCRH_WLEN_6BITS;
+            break;
+        case CS7:
+            lcrh |= LCRH_WLEN_7BITS;
+            break;
+        case CS8:
+        default:
+            lcrh |= LCRH_WLEN_8BITS;
+    }
+
+    /* Mode: stop bits */
+    if ((term->c_cflag & CSTOPB) != 0)
+        lcrh |= LCRH_STP2;
+
+    /* Control: Disable UART */
+    CR(regs_base) &= ~(CR_UARTEN | CR_RXE | CR_TXE);
+    uint32_t cr = CR(regs_base);
+
+    /*
+     * Control: Configure flow control
+     * NOTE: Flow control is untested
+     */
+    cr &= ~(CR_CTSEN | CR_RTSEN);
+    if ((term->c_cflag & CCTS_OFLOW) != 0)
+        cr |= CR_CTSEN;
+    if ((term->c_cflag & CRTS_IFLOW) != 0)
+        cr |= CR_RTSEN;
+
+    /* Control: Configure receiver */
+    const bool rx_enabled = (term->c_cflag & CREAD) != 0;
+    if (rx_enabled)
+        cr |= CR_RXE;
+
+    /* Control: Re-enable UART */
+    cr |= CR_UARTEN | CR_TXE;
+
+    /* Disable all interrupts */
+    disable_irq(regs_base, IRQ_MASK);
+
+    flush_fifos(context);
+
+    /* Set the baudrate */
+    IBRD(regs_base) = ibrd;
+    FBRD(regs_base) = fbrd;
+
+    /*
+     * Commit mode configurations
+     * NOTE:
+     * This has to happen after IBRD and FBRD as writing to LCRH is
+     * required to trigger the baudrate update.
+     */
+    LCRH(regs_base) = lcrh;
+
+    /* Commit changes to control register */
+    CR(regs_base) = cr;
+
+#ifdef BSP_CONSOLE_USE_INTERRUPTS
+    /* Clear all interrupts  */
+    clear_irq(regs_base, IRQ_MASK);
+
+    /* Re-enable RX interrupts */
+    if (rx_enabled)
+        enable_irq(regs_base, IRQ_RT_BIT | IRQ_RX_BIT);
+
+    /*
+     * UART is freshly enabled, TXFIFO is empty, and interrupt will be enabled,
+     * so the next transmission will required software-trigger interrupt.
+     */
+    context->needs_sw_triggered_tx_irq = true;
+#endif
+
+    return true;
+}
+
+void pl011_write_char_polled(const rtems_termios_device_context *base,
+                             const char ch) {
+    const pl011_context *context = (void *)base;
+    const uintptr_t regs_base    = context->regs_base;
+
+    /* Wait until TXFIFO has space */
+    while (is_txfifo_full(regs_base))
+        ;
+
+    write_char(regs_base, ch);
+}
-- 
2.41.0



More information about the devel mailing list