From e6425fedbd394009384701d00c7d387502e7ef2f Mon Sep 17 00:00:00 2001
From: Axel Heider <axel.heider@hensoldt.net>
Date: Tue, 29 Nov 2022 00:13:42 +0100
Subject: [PATCH] libplatsupport: separate drivers from platforms

Separate serial drivers

Signed-off-by: Axel Heider <axel.heider@hensoldt.net>
---
 .../platsupport/driver/ns16550/ns16550.h      | 118 ++++++++++++++++++
 .../include/platsupport/driver/pl011/pl011.h  | 115 +++++++++++++++++
 .../qemu-riscv-virt/platsupport/plat/serial.h |  43 +------
 libplatsupport/src/plat/fvp/serial.c          |  51 ++++----
 libplatsupport/src/plat/hikey/serial.c        |  48 ++++---
 .../src/plat/qemu-arm-virt/serial.c           |  51 ++++----
 .../src/plat/qemu-riscv-virt/serial.c         |  92 +++++---------
 7 files changed, 334 insertions(+), 184 deletions(-)
 create mode 100644 libplatsupport/include/platsupport/driver/ns16550/ns16550.h
 create mode 100644 libplatsupport/include/platsupport/driver/pl011/pl011.h

diff --git a/libplatsupport/include/platsupport/driver/ns16550/ns16550.h b/libplatsupport/include/platsupport/driver/ns16550/ns16550.h
new file mode 100644
index 000000000..adf4a724c
--- /dev/null
+++ b/libplatsupport/include/platsupport/driver/ns16550/ns16550.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2022, HENSOLDT Cyber GmbH
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Driver for a 16550 compatible UART.
+ */
+
+#pragma once
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <utils/arith.h>
+
+#define NS16550_IER_ERBFI   BIT(0)   /* Enable Received Data Available Interrupt */
+#define NS16550_IER_ETBEI   BIT(1)   /* Enable Transmitter Holding Register Empty Interrupt */
+#define NS16550_IER_ELSI    BIT(2)   /* Enable Receiver Line Status Interrupt */
+#define NS16550_IER_EDSSI   BIT(3)   /* Enable MODEM Status Interrupt */
+
+#define NS16550_FCR_ENABLE_FIFOS   BIT(0)
+#define NS16550_FCR_RESET_RX_FIFO  BIT(1)
+#define NS16550_FCR_RESET_TX_FIFO  BIT(2)
+#define NS16550_FCR_TRIGGER_1      (0u << 6)
+#define NS16550_FCR_TRIGGER_4      (1u << 6)
+#define NS16550_FCR_TRIGGER_8      (2u << 6)
+#define NS16550_FCR_TRIGGER_14     (3u << 6)
+
+#define NS16550_LCR_DLAB    BIT(7)   /* Divisor Latch Access */
+
+#define NS16550_LSR_DR      BIT(0)   /* Data Ready */
+#define NS16550_LSR_THRE    BIT(5)   /* Transmitter Holding Register Empty */
+
+/* There are different NS16550 hardware implementations. The classic size of
+ * each register is just one byte, but some implementations stated to use 32-bit
+ * registers, as this fits better with the natural alignment
+ */
+#if defined(NS16550_WITH_REG32)
+typedef volatile uint32_t ns16550_reg_t;
+#elif defined(NS16550_WITH_REG8)
+typedef volatile uint8_t ns16550_reg_t;
+#else
+#error "define NS16550_WITH_REG[8|32]"
+#endif
+
+typedef struct {
+    /* 0x00 */
+    ns16550_reg_t rbr_dll_thr; /* Receiver Buffer Register (Read Only)
+                                * Divisor Latch (LSB)
+                                * Transmitter Holding Register (Write Only)
+                                */
+    /* 0x01 or 0x04 */
+    ns16550_reg_t dlm_ier;     /* Divisor Latch (MSB)
+                                * Interrupt Enable Register
+                                */
+    /* 0x02 or 0x08 */
+    ns16550_reg_t iir_fcr;     /* Interrupt Identification Register (Read Only)
+                                * FIFO Control Register (Write Only)
+                                */
+    /* 0x03 or 0x0c */
+    ns16550_reg_t lcr;         /* Line Control Register */
+    /* 0x04 or 0x10 */
+    ns16550_reg_t mcr;         /* MODEM Control Register */
+    /* 0x05 or 0x14 */
+    ns16550_reg_t lsr;         /* Line Status Register */
+    /* 0x06 or 0x18 */
+    ns16550_reg_t msr;         /* MODEM Status Register */
+    /* 0x07 or 0x1c */
+} ns16550_regs_t;
+
+
+/*
+ *******************************************************************************
+ * UART access primitives
+ *******************************************************************************
+ */
+
+static bool ns16550_is_tx_empty(ns16550_regs_t *regs)
+{
+    /* The THRE bit is set when the FIFO is fully empty. There seems no way to
+     * detect if the FIFO is partially empty only, so we can't implement a
+     * "tx_ready" check.
+     */
+    return (0 != (regs->lsr & NS16550_LSR_THRE));
+}
+
+static void ns16550_tx_byte(ns16550_regs_t *regs, uint8_t byte)
+{
+    /* Caller has to ensure TX FIFO is ready */
+    regs->rbr_dll_thr = byte;
+}
+
+static bool ns16550_is_rx_empty(ns16550_regs_t *regs)
+{
+    return (0 == (regs->lsr & NS16550_LSR_DR));
+}
+
+
+static int ns16550_rx_byte(ns16550_regs_t *regs)
+{
+    /* Caller has to ensure RX FIFO has data */
+    return regs->rbr_dll_thr;
+}
+
+
+/*
+ *******************************************************************************
+ * UART access helpers
+ *******************************************************************************
+ */
+
+/*
+ * Returns a char from the TX FIFO or EOF if the FIFO is empty.
+ */
+static int ns16550_get_char_or_EOF(ns16550_regs_t *regs)
+{
+    return ns16550_is_rx_empty(regs) ? EOF : ns16550_rx_byte(regs);
+}
diff --git a/libplatsupport/include/platsupport/driver/pl011/pl011.h b/libplatsupport/include/platsupport/driver/pl011/pl011.h
new file mode 100644
index 000000000..482754a9d
--- /dev/null
+++ b/libplatsupport/include/platsupport/driver/pl011/pl011.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2022, HENSOLDT Cyber GmbH
+ * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Driver for a ARM PL011 UART.
+ */
+
+#pragma once
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <utils/arith.h>
+
+#define PL011_FR_TXFF       BIT(5)
+#define PL011_FR_RXFE       BIT(4)
+
+typedef volatile struct {
+    uint32_t dr;        /* 0x00 */
+    uint32_t _rfu_04;   /* 0x04 */
+    uint32_t _rfu_08;   /* 0x08 */
+    uint32_t _rfu_0c;   /* 0x0c */
+    uint32_t _rfu_10;   /* 0x10 */
+    uint32_t _rfu_14;   /* 0x14 */
+    uint32_t fr;        /* 0x18 */
+    uint32_t _rfu_1c;   /* 0x1c */
+    uint32_t _rfu_20;   /* 0x20 */
+    uint32_t _rfu_24;   /* 0x24 */
+    uint32_t _rfu_28;   /* 0x28 */
+    uint32_t _rfu_2c;   /* 0x2c */
+    uint32_t _rfu_30;   /* 0x30 */
+    uint32_t _rfu_34;   /* 0x34 */
+    uint32_t imsc;      /* 0x38 */
+    uint32_t _rfu_3c;   /* 0x3c */
+    uint32_t _rfu_40;   /* 0x40 */
+    uint32_t icr;       /* 0x44 */
+    uint32_t _rfu_48;   /* 0x48 */
+    uint32_t _rfu_4c;   /* 0x4c */
+} pl011_regs_t;
+
+
+/*
+ *******************************************************************************
+ * UART access primitives
+ *******************************************************************************
+ */
+
+static bool pl011_is_rx_fifo_empty(pl011_regs_t *regs)
+{
+    return (0 != (regs->fr & PL011_FR_RXFE));
+}
+
+static bool pl011_is_tx_fifo_full(pl011_regs_t *regs)
+{
+    return (0 != (regs->fr & PL011_FR_TXFF));
+}
+
+static void pl011_write_char(pl011_regs_t *regs, uint8_t c)
+{
+    /* Caller has to ensure TX FIFO has space */
+    regs->dr = c;
+}
+
+static uint8_t pl011_read_char(pl011_regs_t *regs)
+{
+    return (uint8_t)(regs->dr & 0xFF);
+}
+
+static void pl011_clear_interrupt(pl011_regs_t *regs)
+{
+    regs->icr = 0x7f0;
+}
+
+static void pl011_init(pl011_regs_t *regs)
+{
+    regs->imsc = 0x50;
+}
+
+
+/*
+ *******************************************************************************
+ * UART access helpers
+ *******************************************************************************
+ */
+
+/*
+ * Returns a char from the TX FIFO or EOF if the FIFO is empty.
+ */
+int pl011_get_char_or_EOF(pl011_regs_t *regs)
+{
+    return pl011_is_rx_fifo_empty(regs) ? EOF : pl011_read_char(regs);
+}
+
+/*
+ * Block until there is space in the TX FIFO, then outputs the char. Optionally
+ * output a CR (\r) first in case of LF (\n) to support terminal use case.
+ */
+void pl011_put_char_blocking(pl011_regs_t *regs, uint8_t c, bool is_auto_cr)
+{
+
+    /* output CR (\r) before LF (\n) automatically if UART is a terminal */
+    if ((c == '\n') && is_auto_cr) {
+        while (pl011_is_tx_fifo_full(regs)) {
+            /* busy loop */
+        }
+        pl011_write_char(regs, '\r');
+    }
+
+    while (pl011_is_tx_fifo_full(regs)) {
+        /* busy loop */
+    }
+    pl011_write_char(regs, c);
+}
diff --git a/libplatsupport/plat_include/qemu-riscv-virt/platsupport/plat/serial.h b/libplatsupport/plat_include/qemu-riscv-virt/platsupport/plat/serial.h
index 47981329b..b7dad7198 100644
--- a/libplatsupport/plat_include/qemu-riscv-virt/platsupport/plat/serial.h
+++ b/libplatsupport/plat_include/qemu-riscv-virt/platsupport/plat/serial.h
@@ -2,12 +2,14 @@
  * Copyright 2022, HENSOLDT Cyber GmbH
  *
  * SPDX-License-Identifier: BSD-2-Clause
+ *
+ *
+ * QEMU RISC-V virt emulates a 16550 compatible UART.
+ *
  */
 
 #pragma once
 
-#include <autoconf.h>
-
 /* This information is taken from the device tree. */
 #define UART0_PADDR     0x10000000
 #define UART0_IRQ       10
@@ -22,40 +24,3 @@ enum chardev_id {
 
 #define DEFAULT_SERIAL_PADDR        UART0_PADDR
 #define DEFAULT_SERIAL_INTERRUPT    UART0_IRQ
-
-/* QEMU RISC-V virt emulates a 16550 compatible UART. */
-
-#define UART_IER_ERBFI   BIT(0)   /* Enable Received Data Available Interrupt */
-#define UART_IER_ETBEI   BIT(1)   /* Enable Transmitter Holding Register Empty Interrupt */
-#define UART_IER_ELSI    BIT(2)   /* Enable Receiver Line Status Interrupt */
-#define UART_IER_EDSSI   BIT(3)   /* Enable MODEM Status Interrupt */
-
-#define UART_FCR_ENABLE_FIFOS   BIT(0)
-#define UART_FCR_RESET_RX_FIFO  BIT(1)
-#define UART_FCR_RESET_TX_FIFO  BIT(2)
-#define UART_FCR_TRIGGER_1      (0u << 6)
-#define UART_FCR_TRIGGER_4      (1u << 6)
-#define UART_FCR_TRIGGER_8      (2u << 6)
-#define UART_FCR_TRIGGER_14     (3u << 6)
-
-#define UART_LCR_DLAB    BIT(7)   /* Divisor Latch Access */
-
-#define UART_LSR_DR      BIT(0)   /* Data Ready */
-#define UART_LSR_THRE    BIT(5)   /* Transmitter Holding Register Empty */
-
-typedef volatile struct {
-    uint8_t rbr_dll_thr; /* 0x00 Receiver Buffer Register (Read Only)
-                           *   Divisor Latch (LSB)
-                           *   Transmitter Holding Register (Write Only)
-                           */
-    uint8_t dlm_ier;     /* 0x04 Divisor Latch (MSB)
-                           *   Interrupt Enable Register
-                           */
-    uint8_t iir_fcr;     /* 0x08 Interrupt Identification Register (Read Only)
-                           *    FIFO Control Register (Write Only)
-                           */
-    uint8_t lcr;         /* 0xC Line Control Register */
-    uint8_t mcr;         /* 0x10 MODEM Control Register */
-    uint8_t lsr;         /* 0x14 Line Status Register */
-    uint8_t msr;         /* 0x18 MODEM Status Register */
-} uart_regs_t;
diff --git a/libplatsupport/src/plat/fvp/serial.c b/libplatsupport/src/plat/fvp/serial.c
index 50fb44a5d..97663ebdd 100644
--- a/libplatsupport/src/plat/fvp/serial.c
+++ b/libplatsupport/src/plat/fvp/serial.c
@@ -1,54 +1,42 @@
 /*
+ * Copyright 2022, HENSOLDT Cyber GmbH
  * Copyright 2019, Data61, CSIRO (ABN 41 687 119 230)
  *
  * SPDX-License-Identifier: BSD-2-Clause
+ *
+ *
+ * FVP emulates PL011 UARTs.
+ *
  */
 
-/* Mostly copy/paste from the HiKey plat.
- * Should be moved to a common driver file for PL011 */
-
 #include <string.h>
 #include <stdlib.h>
 #include <platsupport/serial.h>
+#include <platsupport/driver/pl011/pl011.h>
 #include "../../chardev.h"
 
-#define RHR_MASK                MASK(8)
-#define UARTDR                  0x000
-#define UARTFR                  0x018
-#define UARTIMSC                0x038
-#define UARTICR                 0x044
-#define PL011_UARTFR_TXFF       BIT(5)
-#define PL011_UARTFR_RXFE       BIT(4)
-
-#define REG_PTR(base, off)     ((volatile uint32_t *)((base) + (off)))
+static pl011_regs_t *get_pl011_regs(ps_chardevice_t *dev)
+{
+    return (pl011_regs_t *)(dev->vaddr);
+}
 
 int uart_getchar(ps_chardevice_t *d)
 {
-    int ch = EOF;
-
-    if ((*REG_PTR(d->vaddr, UARTFR) & PL011_UARTFR_RXFE) == 0) {
-        ch = *REG_PTR(d->vaddr, UARTDR) & RHR_MASK;
-    }
-    return ch;
+    pl011_regs_t *regs = get_pl011_regs(d);
+    return pl011_get_char_or_EOF(regs);
 }
 
 int uart_putchar(ps_chardevice_t *d, int c)
 {
-    if (c == '\n' && (d->flags & SERIAL_AUTO_CR)) {
-        uart_putchar(d, '\r');
-    }
-
-    while ((*REG_PTR(d->vaddr, UARTFR) & PL011_UARTFR_TXFF) != 0) {
-        /* busy loop */
-    }
-    *REG_PTR(d->vaddr, UARTDR) = c;
-
+    pl011_regs_t *regs = get_pl011_regs(d);
+    pl011_put_char_blocking_auto_cr(regs, c, d->flags & SERIAL_AUTO_CR);
     return c;
 }
 
 static void uart_handle_irq(ps_chardevice_t *dev)
 {
-    *REG_PTR(dev->vaddr, UARTICR) = 0x7f0;
+    pl011_regs_t *regs = get_pl011_regs(dev);
+    pl011_clear_interrupt(regs);
 }
 
 int uart_init(const struct dev_defn *defn,
@@ -56,6 +44,8 @@ int uart_init(const struct dev_defn *defn,
               ps_chardevice_t *dev)
 {
     memset(dev, 0, sizeof(*dev));
+
+    /* Map device. */
     void *vaddr = chardev_map(defn, ops);
     if (vaddr == NULL) {
         return -1;
@@ -71,6 +61,9 @@ int uart_init(const struct dev_defn *defn,
     dev->ioops      = *ops;
     dev->flags      = SERIAL_AUTO_CR;
 
-    *REG_PTR(dev->vaddr, UARTIMSC) = 0x50;
+    /* Initialize the device. */
+    pl011_regs_t *regs = get_pl011_regs(dev);
+    pl011_init(regs);
+
     return 0;
 }
diff --git a/libplatsupport/src/plat/hikey/serial.c b/libplatsupport/src/plat/hikey/serial.c
index 38b0e8cdb..eed6dabe0 100644
--- a/libplatsupport/src/plat/hikey/serial.c
+++ b/libplatsupport/src/plat/hikey/serial.c
@@ -1,51 +1,42 @@
 /*
+ * Copyright 2022, HENSOLDT Cyber GmbH
  * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
  *
  * SPDX-License-Identifier: BSD-2-Clause
+ *
+ *
+ * HiKey uses PL011 UARTs.
+ *
  */
 
 #include <string.h>
 #include <stdlib.h>
 #include <platsupport/serial.h>
+#include <platsupport/driver/pl011/pl011.h>
 #include "../../chardev.h"
 
-#define RHR_MASK                MASK(8)
-#define UARTDR                  0x000
-#define UARTFR                  0x018
-#define UARTIMSC                0x038
-#define UARTICR                 0x044
-#define PL011_UARTFR_TXFF       BIT(5)
-#define PL011_UARTFR_RXFE       BIT(4)
-
-#define REG_PTR(base, off)     ((volatile uint32_t *)((base) + (off)))
+static pl011_regs_t *get_pl011_regs(ps_chardevice_t *dev)
+{
+    return (pl011_regs_t *)(dev->vaddr);
+}
 
 int uart_getchar(ps_chardevice_t *d)
 {
-    int ch = EOF;
-
-    if ((*REG_PTR(d->vaddr, UARTFR) & PL011_UARTFR_RXFE) == 0) {
-        ch = *REG_PTR(d->vaddr, UARTDR) & RHR_MASK;
-    }
-    return ch;
+    pl011_regs_t *regs = get_pl011_regs(d);
+    return pl011_get_char_or_EOF(regs);
 }
 
 int uart_putchar(ps_chardevice_t *d, int c)
 {
-    if (c == '\n' && (d->flags & SERIAL_AUTO_CR)) {
-        uart_putchar(d, '\r');
-    }
-
-    while ((*REG_PTR(d->vaddr, UARTFR) & PL011_UARTFR_TXFF) != 0) {
-        /* busy loop */
-    }
-    *REG_PTR(d->vaddr, UARTDR) = c;
-
+    pl011_regs_t *regs = get_pl011_regs(d);
+    pl011_put_char_blocking(regs, c, d->flags & SERIAL_AUTO_CR);
     return c;
 }
 
 static void uart_handle_irq(ps_chardevice_t *dev)
 {
-    *REG_PTR(dev->vaddr, UARTICR) = 0x7f0;
+    pl011_regs_t *regs = get_pl011_regs(dev);
+    pl011_clear_interrupt(regs);
 }
 
 int uart_init(const struct dev_defn *defn,
@@ -53,6 +44,8 @@ int uart_init(const struct dev_defn *defn,
               ps_chardevice_t *dev)
 {
     memset(dev, 0, sizeof(*dev));
+
+    /* Map device. */
     void *vaddr = chardev_map(defn, ops);
     if (vaddr == NULL) {
         return -1;
@@ -68,6 +61,9 @@ int uart_init(const struct dev_defn *defn,
     dev->ioops      = *ops;
     dev->flags      = SERIAL_AUTO_CR;
 
-    *REG_PTR(dev->vaddr, UARTIMSC) = 0x50;
+    /* Initialize the device. */
+    pl011_regs_t *regs = get_pl011_regs(dev);
+    pl011_init(regs);
+
     return 0;
 }
diff --git a/libplatsupport/src/plat/qemu-arm-virt/serial.c b/libplatsupport/src/plat/qemu-arm-virt/serial.c
index 47b665e16..79d49ae8e 100644
--- a/libplatsupport/src/plat/qemu-arm-virt/serial.c
+++ b/libplatsupport/src/plat/qemu-arm-virt/serial.c
@@ -1,52 +1,42 @@
 /*
+ * Copyright 2022, HENSOLDT Cyber GmbH
  * Copyright 2019, Data61, CSIRO (ABN 41 687 119 230)
  *
  * SPDX-License-Identifier: BSD-2-Clause
+ *
+ *
+ * QEMU arm-virt emulates a PL011 UART.
+ *
  */
 
-/* Mostly copy/paste from the HiKey plat.
- * Should be moved to a common driver file for PL011 */
-
 #include <string.h>
 #include <stdlib.h>
 #include <platsupport/serial.h>
+#include <platsupport/driver/pl011/pl011.h>
 #include "../../chardev.h"
 
-#define RHR_MASK                MASK(8)
-#define UARTDR                  0x000
-#define UARTFR                  0x018
-#define UARTIMSC                0x038
-#define UARTICR                 0x044
-#define PL011_UARTFR_TXFF       BIT(5)
-#define PL011_UARTFR_RXFE       BIT(4)
-
-#define REG_PTR(base, off)     ((volatile uint32_t *)((base) + (off)))
+static pl011_regs_t *get_pl011_regs(ps_chardevice_t *dev)
+{
+    return (pl011_regs_t *)(dev->vaddr);
+}
 
 int uart_getchar(ps_chardevice_t *d)
 {
-    int ch = EOF;
-
-    if ((*REG_PTR(d->vaddr, UARTFR) & PL011_UARTFR_RXFE) == 0) {
-        ch = *REG_PTR(d->vaddr, UARTDR) & RHR_MASK;
-    }
-    return ch;
+    pl011_regs_t *regs = get_pl011_regs(d);
+    return pl011_get_char_or_EOF(regs);
 }
 
 int uart_putchar(ps_chardevice_t *d, int c)
 {
-    while ((*REG_PTR(d->vaddr, UARTFR) & PL011_UARTFR_TXFF) != 0);
-
-    *REG_PTR(d->vaddr, UARTDR) = c;
-    if (c == '\n' && (d->flags & SERIAL_AUTO_CR)) {
-        uart_putchar(d, '\r');
-    }
-
+    pl011_regs_t *regs = get_pl011_regs(d);
+    pl011_put_char_blocking(regs, c, d->flags & SERIAL_AUTO_CR);
     return c;
 }
 
 static void uart_handle_irq(ps_chardevice_t *dev)
 {
-    *REG_PTR(dev->vaddr, UARTICR) = 0x7f0;
+    pl011_regs_t *regs = get_pl011_regs(dev);
+    pl011_clear_interrupt(regs);
 }
 
 int uart_init(const struct dev_defn *defn,
@@ -54,12 +44,14 @@ int uart_init(const struct dev_defn *defn,
               ps_chardevice_t *dev)
 {
     memset(dev, 0, sizeof(*dev));
+
+    /* Map device. */
     void *vaddr = chardev_map(defn, ops);
     if (vaddr == NULL) {
         return -1;
     }
 
-    /* Set up all the  device properties. */
+    /* Set up all the device properties. */
     dev->id         = defn->id;
     dev->vaddr      = (void *)vaddr;
     dev->read       = &uart_read;
@@ -69,6 +61,9 @@ int uart_init(const struct dev_defn *defn,
     dev->ioops      = *ops;
     dev->flags      = SERIAL_AUTO_CR;
 
-    *REG_PTR(dev->vaddr, UARTIMSC) = 0x50;
+    /* Initialize the device. */
+    pl011_regs_t *regs = get_pl011_regs(dev);
+    pl011_init(regs);
+
     return 0;
 }
diff --git a/libplatsupport/src/plat/qemu-riscv-virt/serial.c b/libplatsupport/src/plat/qemu-riscv-virt/serial.c
index b5165a6c8..1b8d3c196 100644
--- a/libplatsupport/src/plat/qemu-riscv-virt/serial.c
+++ b/libplatsupport/src/plat/qemu-riscv-virt/serial.c
@@ -2,55 +2,29 @@
  * Copyright 2022, HENSOLDT Cyber GmbH
  *
  * SPDX-License-Identifier: BSD-2-Clause
+ *
+ *
+ * QEMU riscv-virt emulates a 16550 compatible UART, where the register width
+ * sticks to the classic size of 8 bits. This is fine, because it's just a
+ * simulation, but contradicts many actual hardware implementations. There the
+ * peripheral registers are usually 32-bit wide, because this fits much better
+ * to the natural bus transfer sizes and alignments.
  */
 
-/* QEMU RISC-V virt emulates a 16550 compatible UART. */
-
 #include <string.h>
 #include <stdlib.h>
 #include <platsupport/serial.h>
-#include "../../chardev.h"
-
-static uart_regs_t *uart_get_regs(ps_chardevice_t *dev)
-{
-    return (uart_regs_t *)(dev->vaddr);
-}
 
-/*
- *******************************************************************************
- * UART access primitives
- *******************************************************************************
- */
-
-static bool internal_uart_is_tx_empty(uart_regs_t *regs)
-{
-    /* The THRE bit is set when the FIFO is fully empty. On real hardware, there
-     * seems no way to detect if the FIFO is partially empty only, so we can't
-     * implement a "tx_ready" check. Since QEMU does not emulate a FIFO, this
-     * does not really matter.
-     */
-    return (0 != (regs->lsr & UART_LSR_THRE));
-}
-
-static void internal_uart_tx_byte(uart_regs_t *regs, uint8_t byte)
-{
-    /* Caller has to ensure TX FIFO is ready */
-    regs->rbr_dll_thr = byte;
-}
-
-static bool internal_uart_is_rx_empty(uart_regs_t *regs)
-{
-    return (0 == (regs->lsr & UART_LSR_DR));
-}
+#define NS16550_WITH_REG8
+#include <platsupport/driver/ns16550/ns16550.h>
 
+#include "../../chardev.h"
 
-static int internal_uart_rx_byte(uart_regs_t *regs)
+static ns16550_regs_t *uart_get_regs(ps_chardevice_t *dev)
 {
-    /* Caller has to ensure RX FIFO has data */
-    return regs->rbr_dll_thr;
+    return (ns16550_regs_t *)(dev->vaddr);
 }
 
-
 /*
  *******************************************************************************
  * UART access API
@@ -59,7 +33,7 @@ static int internal_uart_rx_byte(uart_regs_t *regs)
 
 int uart_putchar(ps_chardevice_t *dev, int c)
 {
-    uart_regs_t *regs = uart_get_regs(dev);
+    ns16550_regs_t *regs = uart_get_regs(dev);
 
     /* There is no way to check for "TX ready", the only thing we have is a
      * check for "TX FIFO empty". This is not optimal, as we might wait here
@@ -71,7 +45,7 @@ int uart_putchar(ps_chardevice_t *dev, int c)
      * However, since QEMU does not emulate a FIFO, we can just implement a
      * simple model here and block - expecting to never block practically.
      */
-    while (!internal_uart_is_tx_empty(regs)) {
+    while (!ns16550_is_tx_empty(regs)) {
         /* busy waiting loop */
     }
 
@@ -80,27 +54,21 @@ int uart_putchar(ps_chardevice_t *dev, int c)
 
     /* If SERIAL_AUTO_CR is enabled, a CR is sent before any LF. */
     if ((byte == '\n') && (dev->flags & SERIAL_AUTO_CR)) {
-        internal_uart_tx_byte(regs, '\r');
+        ns16550_tx_byte(regs, '\r');
         /* Since we have blocked until the FIFO is empty, we don't have to wait
          * here. And QEMU does not emulate a FIFOs anyway.
          */
     }
 
-    internal_uart_tx_byte(regs, byte);
+    ns16550_tx_byte(regs, byte);
 
     return byte;
 }
 
 int uart_getchar(ps_chardevice_t *dev)
 {
-    uart_regs_t *regs = uart_get_regs(dev);
-
-    /* if UART is empty return an error */
-    if (internal_uart_is_rx_empty(regs)) {
-        return EOF;
-    }
-
-    return internal_uart_rx_byte(regs) & 0xFF;
+    ns16550_regs_t *regs = uart_get_regs(dev);
+    return ns16550_get_char_or_EOF(regs);
 }
 
 static void uart_handle_irq(ps_chardevice_t *dev)
@@ -108,29 +76,28 @@ static void uart_handle_irq(ps_chardevice_t *dev)
     /* No IRQ handling required here. */
 }
 
-static void uart_setup(ps_chardevice_t *dev)
+static void ns16550_init(ns16550_regs_t *regs)
 {
-    uart_regs_t *regs = uart_get_regs(dev);
-
-    regs->dlm_ier = 0; // disable interrupts
+    /* disable interrupts */
+    regs->dlm_ier = 0;
 
     /* Baudrates and serial line parameters are not emulated by QEMU, so the
      * divisor is just a dummy.
      */
     uint16_t clk_divisor = 1; /* dummy, would be for 115200 baud */
-    regs->lcr = UART_LCR_DLAB; /* baud rate divisor setup */
+    regs->lcr = NS16550_LCR_DLAB; /* baud rate divisor setup */
     regs->dlm_ier = (clk_divisor >> 8) & 0xFF;
     regs->rbr_dll_thr = clk_divisor & 0xFF;
     regs->lcr = 0x03; /* set 8N1, clear DLAB to end baud rate divisor setup */
 
     /* enable and reset FIFOs, interrupt for each byte */
-    regs->iir_fcr = UART_FCR_ENABLE_FIFOS
-                    | UART_FCR_RESET_RX_FIFO
-                    | UART_FCR_RESET_TX_FIFO
-                    | UART_FCR_TRIGGER_1;
+    regs->iir_fcr = NS16550_FCR_ENABLE_FIFOS
+                    | NS16550_FCR_RESET_RX_FIFO
+                    | NS16550_FCR_RESET_TX_FIFO
+                    | NS16550_FCR_TRIGGER_1;
 
     /* enable RX interrupts */
-    regs->dlm_ier = UART_IER_ERBFI;
+    regs->dlm_ier = NS16550_IER_ERBFI;
 }
 
 int uart_init(const struct dev_defn *defn,
@@ -155,8 +122,9 @@ int uart_init(const struct dev_defn *defn,
     dev->ioops      = *ops;
     dev->flags      = SERIAL_AUTO_CR;
 
-    /* Set up the device. */
-    uart_setup(dev);
+    /* Initialize the device. */
+    ns16550_regs_t *regs = uart_get_regs(dev);
+    ns16550_init(regs);
 
     return 0;
 }