libplatsupport/bcm2711: PL011 + mini UART support

The UART device numbering is now aligned with the device tree.
BCM2711_UART0 previously referred to the serial1 device.

Signed-off-by: Lukas Graber <lukas.graber@hensoldt-cyber.de>
diff --git a/libplatsupport/plat_include/bcm2711/platsupport/plat/serial.h b/libplatsupport/plat_include/bcm2711/platsupport/plat/serial.h
index 9e7861d..3add49f 100644
--- a/libplatsupport/plat_include/bcm2711/platsupport/plat/serial.h
+++ b/libplatsupport/plat_include/bcm2711/platsupport/plat/serial.h
@@ -6,8 +6,8 @@
  */
 #pragma once
 
-#define BUS_ADDR_OFFSET             0x7E000000
-#define PADDDR_OFFSET               0xFE000000
+#define BUS_ADDR_OFFSET     0x7E000000
+#define PADDDR_OFFSET       0xFE000000
 
 /*
  * The mini UART (UART1) is one of the 3 auxiliary
@@ -16,9 +16,9 @@
  *          /soc/aux@7e215000
  *          /soc/serial@7e215040
  */
-#define UART_BUSADDR                0x7E215000
+#define UART_BUSADDR        0x7E215000
 
-#define UART_PADDR_0                (UART_BUSADDR-BUS_ADDR_OFFSET+PADDDR_OFFSET)
+#define UART_PADDR_1        (UART_BUSADDR - BUS_ADDR_OFFSET + PADDDR_OFFSET)
 /*
  * BCM2711 TRM
  * The mini UART (UART1) is part of AUX
@@ -26,17 +26,27 @@
  * section 6.3.   GIC-400 - VC peripheral IRQs:     96
  * => mini UART IRQ: 96 + 29 = 125
  */
-#define UART_IRQ_0                  (125)
+#define UART_IRQ_1          (125)
 
 enum chardev_id {
     BCM2711_UART0,
+    BCM2711_UART1,
+    BCM2711_UART2,
+    BCM2711_UART3,
+    BCM2711_UART4,
+    BCM2711_UART5,
 
     NUM_CHARDEV,
     /* Aliases */
     PS_SERIAL0 = BCM2711_UART0,
+    PS_SERIAL1 = BCM2711_UART1,
+    PS_SERIAL2 = BCM2711_UART2,
+    PS_SERIAL3 = BCM2711_UART3,
+    PS_SERIAL4 = BCM2711_UART4,
+    PS_SERIAL5 = BCM2711_UART5,
     /* defaults */
-    PS_SERIAL_DEFAULT = BCM2711_UART0
+    PS_SERIAL_DEFAULT = BCM2711_UART1
 };
 
-#define DEFAULT_SERIAL_PADDR UART_PADDR_0
-#define DEFAULT_SERIAL_INTERRUPT UART_IRQ_0
+#define DEFAULT_SERIAL_PADDR UART_PADDR_1
+#define DEFAULT_SERIAL_INTERRUPT UART_IRQ_1
diff --git a/libplatsupport/src/plat/bcm2711/chardev.c b/libplatsupport/src/plat/bcm2711/chardev.c
index dec89ef..fd222d2 100644
--- a/libplatsupport/src/plat/bcm2711/chardev.c
+++ b/libplatsupport/src/plat/bcm2711/chardev.c
@@ -16,18 +16,36 @@
 
 #include "serial.h"
 
-static const int uart_irqs_0[] = {-1};
+static const int uart0_irqs[] = {UART0_IRQ, -1};
+static const int uart1_irqs[] = {UART1_IRQ, -1};
+static const int uart2_irqs[] = {UART2_IRQ, -1};
+static const int uart3_irqs[] = {UART3_IRQ, -1};
+static const int uart4_irqs[] = {UART4_IRQ, -1};
+static const int uart5_irqs[] = {UART5_IRQ, -1};
 
-#define UART_DEFN(devid) {          \
+#define PL011_UART_DEFN(devid) {    \
     .id      = BCM2711_UART##devid, \
-    .paddr   = UART_PADDR_##devid,  \
+    .paddr   = PL011_UART_BASE,     \
     .size    = BIT(12),             \
-    .irqs    = uart_irqs_##devid,   \
+    .irqs    = uart##devid##_irqs,  \
+    .init_fn = &uart_init           \
+}
+
+#define MINI_UART_DEFN(devid) {     \
+    .id      = BCM2711_UART##devid, \
+    .paddr   = MINI_UART_BASE,      \
+    .size    = BIT(12),             \
+    .irqs    = uart##devid##_irqs,  \
     .init_fn = &uart_init           \
 }
 
 static const struct dev_defn dev_defn[] = {
-    UART_DEFN(0),
+    PL011_UART_DEFN(0),
+    MINI_UART_DEFN(1),
+    PL011_UART_DEFN(2),
+    PL011_UART_DEFN(3),
+    PL011_UART_DEFN(4),
+    PL011_UART_DEFN(5)
 };
 
 struct ps_chardevice *
diff --git a/libplatsupport/src/plat/bcm2711/mini_serial.c b/libplatsupport/src/plat/bcm2711/mini_serial.c
new file mode 100644
index 0000000..1ddc40a
--- /dev/null
+++ b/libplatsupport/src/plat/bcm2711/mini_serial.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021, Hensoldt Cyber GmbH
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <platsupport/serial.h>
+#include <platsupport/plat/serial.h>
+#include "serial.h"
+#include "mini_serial.h"
+
+typedef volatile struct mini_uart_regs_s {
+    uint32_t mu_io;         // 0x40: mini UART I/O Data
+    uint32_t mu_ier;        // 0x44: mini UART interrupt enable
+    uint32_t mu_iir;        // 0x48: mini UART interrupt identify
+    uint32_t mu_lcr;        // 0x4c: mini UART line control
+    uint32_t mu_mcr;        // 0x50: mini UART modem control
+    uint32_t mu_lsr;        // 0x54: mini UART line status
+    uint32_t mu_msr;        // 0x58: mini UART modem status
+    uint32_t mu_scratch;    // 0x5c: mini UART scratch
+    uint32_t mu_cntl;       // 0x60: mini UART extra control
+    uint32_t mu_stat;       // 0x64: mini UART extra status
+    uint32_t mu_baud;       // 0x68: mini UART baudrate
+}
+mini_uart_regs_t;
+
+/* This bit is set if the transmit FIFO can accept at least one byte.*/
+#define MU_LSR_TXEMPTY   BIT(5)
+/* This bit is set if the transmit FIFO is empty and the
+ * transmitter is idle. (Finished shifting out the last bit). */
+#define MU_LSR_TXIDLE    BIT(6)
+#define MU_LSR_RXOVERRUN BIT(1)
+#define MU_LSR_DATAREADY BIT(0)
+
+#define MU_LCR_DLAB      BIT(7)
+#define MU_LCR_BREAK     BIT(6)
+#define MU_LCR_DATASIZE  BIT(0)
+
+static void mini_uart_handle_irq(ps_chardevice_t *dev)
+{
+}
+
+int mini_uart_init(const struct dev_defn *defn,
+                   const ps_io_ops_t *ops,
+                   ps_chardevice_t *dev)
+{
+    /* Attempt to map the virtual address, assure this works */
+    void *vaddr = chardev_map(defn, ops);
+    memset(dev, 0, sizeof(*dev));
+    if (vaddr == NULL) {
+        return -1;
+    }
+
+    // When mapping the virtual address space, the base addresses need to be
+    // 4k byte aligned. Since the real base addresses of the UART peripherals
+    // are not 4k byte aligned, we have to add the required offset.
+    uint32_t addr_offset = 0;
+    switch (defn->id) {
+    case 1:
+        addr_offset = UART1_OFFSET;
+        break;
+    default:
+        ZF_LOGE("Mini UART with ID %d does not exist!", defn->id);
+        return -1;
+        break;
+    }
+
+    /* Set up all the  device properties. */
+    dev->id         = defn->id;
+    dev->vaddr      = (void *)vaddr + addr_offset; // use real base address
+    dev->read       = &uart_read;
+    dev->write      = &uart_write;
+    dev->handle_irq = &mini_uart_handle_irq;
+    dev->irqs       = defn->irqs;
+    dev->ioops      = *ops;
+    dev->flags      = SERIAL_AUTO_CR;
+
+    return 0;
+}
+
+int mini_uart_getchar(ps_chardevice_t *d)
+{
+    while (!((mini_uart_regs_t *)d->vaddr)->mu_lsr & MU_LSR_DATAREADY);
+    return ((mini_uart_regs_t *)d->vaddr)->mu_io;
+}
+
+int mini_uart_putchar(ps_chardevice_t *d, int c)
+{
+    while (!((mini_uart_regs_t *)d->vaddr)->mu_lsr & MU_LSR_TXIDLE);
+    ((mini_uart_regs_t *)d->vaddr)->mu_io = (c & 0xff);
+
+    return 0;
+}
diff --git a/libplatsupport/src/plat/bcm2711/mini_serial.h b/libplatsupport/src/plat/bcm2711/mini_serial.h
new file mode 100644
index 0000000..ede4ff0
--- /dev/null
+++ b/libplatsupport/src/plat/bcm2711/mini_serial.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021, Hensoldt Cyber GmbH
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include "../../chardev.h"
+#include <autoconf.h>
+#include <platsupport/gen_config.h>
+
+int mini_uart_init(const struct dev_defn *defn,
+                   const ps_io_ops_t *ops,
+                   ps_chardevice_t *dev);
+
+int mini_uart_getchar(ps_chardevice_t *d);
+
+int mini_uart_putchar(ps_chardevice_t *d, int c);
\ No newline at end of file
diff --git a/libplatsupport/src/plat/bcm2711/pl011_serial.c b/libplatsupport/src/plat/bcm2711/pl011_serial.c
new file mode 100644
index 0000000..2149a40
--- /dev/null
+++ b/libplatsupport/src/plat/bcm2711/pl011_serial.c
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2021, Hensoldt Cyber GmbH
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <platsupport/serial.h>
+#include <platsupport/plat/serial.h>
+#include "serial.h"
+#include "pl011_serial.h"
+
+#define DEV_OFFSET(devid) UART##devid##_OFFSET
+
+typedef volatile struct uart_regs_s {
+    uint32_t dr;            // 0x00: data register
+    uint32_t rsrecr;        // 0x04: receive status/error clear register
+    uint64_t unused0[2];    // 0x08
+    uint32_t fr;            // 0x18: flag register
+    uint32_t unused1;       // 0x1c
+    uint32_t ilpr;          // 0x20: not in use
+    uint32_t ibrd;          // 0x24: integer baud rate divisor
+    uint32_t fbrd;          // 0x28: fractional baud rate divisor
+    uint32_t lcrh;          // 0x2c: line control register
+    uint32_t cr;            // 0x30: control register
+    uint32_t ifls;          // 0x34: interrupt FIFO level select register
+    uint32_t imsc;          // 0x38: interrupt mask set clear register
+    uint32_t ris;           // 0x3c: raw interrupt status register
+    uint32_t mis;           // 0x40: masked interrupt status register
+    uint32_t icr;           // 0x44: interrupt clear register
+    uint32_t dmacr;         // 0x48: DMA control register
+}
+uart_regs_t;
+
+/* LCRH register */
+#define LCRH_SPS        (1 << 7) // stick parity select
+#define LCRH_WLEN_8BIT  (3 << 5) // word length
+#define LCRH_WLEN_7BIT  (2 << 5) // word length
+#define LCRH_WLEN_6BIT  (1 << 5) // word length
+#define LCRH_WLEN_5BIT  (0 << 5) // word length
+#define LCRH_FEN        (1 << 4) // enable fifos
+#define LCRH_STP2       (1 << 3) // two stop bits select
+#define LCRH_EPS        (1 << 2) // Even parity select
+#define LCRH_PEN        (1 << 1) // Parity enable
+#define LCRH_BRK        (1 << 0) // send break
+
+/* CR register */
+#define CR_RXE          (1 << 9) // receive enable
+#define CR_TXE          (1 << 8) // transmit enable
+#define CR_UARTEN       (1 << 0) // UART enable
+
+/* FR register */
+#define FR_TXFE         (1 << 7) // Transmit FIFO empty
+#define FR_RXFF         (1 << 6) // Receive FIFO full
+#define FR_TXFF         (1 << 5) // Transmit FIFO full
+#define FR_RXFE         (1 << 4) // Receive FIFO empty
+#define FR_BUSY         (1 << 3) // UART busy
+
+static void pl011_uart_handle_irq(ps_chardevice_t *dev)
+{
+    // clear interrupts
+    ((uart_regs_t *)dev->vaddr)->icr = 0x7f0;
+}
+
+static int pl011_uart_cr_configure(ps_chardevice_t *dev)
+{
+    uint32_t val = ((uart_regs_t *)dev->vaddr)->cr;
+
+    val |= CR_TXE; // transmit enable
+    val |= CR_RXE; // receive enable
+
+    ((uart_regs_t *)dev->vaddr)->cr = val;
+
+    return 0;
+}
+
+static int pl011_uart_lcrh_configure(ps_chardevice_t *dev)
+{
+    uint32_t val = ((uart_regs_t *)dev->vaddr)->lcrh;
+
+    val |= LCRH_WLEN_8BIT; // character size is 8bit
+    val &= ~LCRH_STP2;     // only one stop bit
+    val &= ~LCRH_PEN;      // no parity
+
+    ((uart_regs_t *)dev->vaddr)->lcrh = val;
+
+    return 0;
+}
+
+/*
+ * Set UART baud rate divisor value
+ *
+ * More information: https://developer.arm.com/documentation/ddi0183/g/programmers-model/register-descriptions/fractional-baud-rate-register--uartfbrd
+ */
+static int pl011_uart_baudrate_div_configure(ps_chardevice_t *dev)
+{
+    // Base UART clock according to https://github.com/raspberrypi/firmware/issues/951
+    const uint32_t freq_uart_clk = 48000000;
+    const uint32_t baud_rate = 115200;
+
+    double baud_div = (double) freq_uart_clk / (double)(16.0 * baud_rate);
+    double frac_div = baud_div - (uint32_t) baud_div;
+
+    // Set IBRD register
+    uint32_t val = ((uart_regs_t *)dev->vaddr)->ibrd;
+    val |= (uint32_t) baud_div;
+    ((uart_regs_t *)dev->vaddr)->ibrd = val;
+
+    // Set FBRD register
+    val = ((uart_regs_t *)dev->vaddr)->fbrd;
+    val |= (uint32_t)(frac_div * 64.0 + 0.5);
+    ((uart_regs_t *)dev->vaddr)->fbrd = val;
+
+    return 0;
+}
+
+/*
+ * Configure UART registers
+ *
+ * This configuration process is based on the description in the BCM2711 TRM
+ * section 11.5 Register View, more precisely on the information given for the
+ * CR register. Furthermore, the LCRH and the baudrate divider registers (IBRD,
+ * FBRD) are configured. For all of these registers the UART must be disabled.
+ */
+static int pl011_uart_configure(ps_chardevice_t *dev)
+{
+    /* ---------------------------------------------------------------------- */
+    /* -------------------------    Disable UART     ------------------------ */
+    /* ---------------------------------------------------------------------- */
+    // disable UART
+    uint32_t val = ((uart_regs_t *)dev->vaddr)->cr;
+    val &= ~CR_UARTEN;
+    ((uart_regs_t *)dev->vaddr)->cr = val;
+
+    // wait till UART is not busy anymore
+    while (((uart_regs_t *)dev->vaddr)->fr & FR_BUSY);
+
+    // disable FIFO
+    val = ((uart_regs_t *)dev->vaddr)->lcrh;
+    val &= ~LCRH_FEN;
+    ((uart_regs_t *)dev->vaddr)->lcrh = val;
+
+    /* ---------------------------------------------------------------------- */
+    /* --------------------    Configure UART registers   ------------------- */
+    /* ---------------------------------------------------------------------- */
+    // Set control register (CR)
+    pl011_uart_cr_configure(dev);
+
+    // Set line control register (LCRH)
+    pl011_uart_lcrh_configure(dev);
+
+    // Set baudrate divider
+    pl011_uart_baudrate_div_configure(dev);
+
+    /* ---------------------------------------------------------------------- */
+    /* -------------------------    Enable UART     ------------------------- */
+    /* ---------------------------------------------------------------------- */
+    // enable FIFO
+    val = ((uart_regs_t *)dev->vaddr)->lcrh;
+    val |= LCRH_FEN;
+    ((uart_regs_t *)dev->vaddr)->lcrh = val;
+
+    // enable UART
+    val = ((uart_regs_t *)dev->vaddr)->cr;
+    val |= CR_UARTEN;
+    ((uart_regs_t *)dev->vaddr)->cr = val;
+
+    // Set Interrupt Mask Set/Clear Register (IMSC)
+    ((uart_regs_t *)dev->vaddr)->imsc = 0x10; // activate receive interrupt
+
+    return 0;
+}
+
+int pl011_uart_init(
+    const struct dev_defn *defn,
+    const ps_io_ops_t *ops,
+    ps_chardevice_t *dev
+)
+{
+    /* Attempt to map the virtual address, assure this works */
+    void *vaddr = chardev_map(defn, ops);
+    memset(dev, 0, sizeof(*dev));
+    if (vaddr == NULL) {
+        return -1;
+    }
+
+    // When mapping the virtual address space, the base addresses need to be
+    // 4k byte aligned. Since the real base addresses of the UART peripherals
+    // are not 4k byte aligned, we have to add the required offset.
+    uint32_t addr_offset = 0;
+    switch (defn->id) {
+    case 0:
+        addr_offset = UART0_OFFSET;
+        break;
+    // case 1: Mini UART
+    case 2:
+        addr_offset = UART2_OFFSET;
+        break;
+    case 3:
+        addr_offset = UART3_OFFSET;
+        break;
+    case 4:
+        addr_offset = UART4_OFFSET;
+        break;
+    case 5:
+        addr_offset = UART5_OFFSET;
+        break;
+    default:
+        ZF_LOGE("PL011 UART with ID %d does not exist!", defn->id);
+        return -1;
+        break;
+    }
+
+    /* Set up all the  device properties. */
+    dev->id         = defn->id;
+    dev->vaddr      = (void *)vaddr + addr_offset; // use real base address
+    dev->read       = &uart_read;
+    dev->write      = &uart_write;
+    dev->handle_irq = &pl011_uart_handle_irq;
+    dev->irqs       = defn->irqs;
+    dev->ioops      = *ops;
+    dev->flags      = SERIAL_AUTO_CR;
+
+    pl011_uart_configure(dev);
+
+    return 0;
+}
+
+int pl011_uart_getchar(ps_chardevice_t *d)
+{
+    int ch = EOF;
+
+    // only if receive fifo is not empty
+    if ((((uart_regs_t *)d->vaddr)->fr & FR_RXFE) == 0) {
+        ch = ((uart_regs_t *)d->vaddr)->dr & MASK(8);
+    }
+    return ch;
+}
+
+int pl011_uart_putchar(ps_chardevice_t *d, int c)
+{
+    // only if transmit fifo is not full
+    while ((((uart_regs_t *)d->vaddr)->fr & FR_TXFF) != 0);
+
+    ((uart_regs_t *)d->vaddr)->dr = c;
+    if (c == '\n' && (d->flags & SERIAL_AUTO_CR)) {
+        uart_putchar(d, '\r');
+    }
+
+    return c;
+}
diff --git a/libplatsupport/src/plat/bcm2711/pl011_serial.h b/libplatsupport/src/plat/bcm2711/pl011_serial.h
new file mode 100644
index 0000000..a3758b2
--- /dev/null
+++ b/libplatsupport/src/plat/bcm2711/pl011_serial.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021, Hensoldt Cyber GmbH
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include "../../chardev.h"
+#include <autoconf.h>
+#include <platsupport/gen_config.h>
+
+int pl011_uart_init(const struct dev_defn *defn,
+                    const ps_io_ops_t *ops,
+                    ps_chardevice_t *dev);
+
+int pl011_uart_getchar(ps_chardevice_t *d);
+
+int pl011_uart_putchar(ps_chardevice_t *d, int c);
diff --git a/libplatsupport/src/plat/bcm2711/serial.c b/libplatsupport/src/plat/bcm2711/serial.c
index fabe84b..b3f1854 100644
--- a/libplatsupport/src/plat/bcm2711/serial.c
+++ b/libplatsupport/src/plat/bcm2711/serial.c
@@ -6,77 +6,164 @@
  */
 
 #include <stdlib.h>
+#include <platsupport/gpio.h>
+#include <platsupport/plat/gpio.h>
 #include <platsupport/serial.h>
 #include <platsupport/plat/serial.h>
+
 #include "serial.h"
-#include <string.h>
+#include "mini_serial.h"
+#include "pl011_serial.h"
 
-#define REG_PTR(base, off)     ((volatile uint32_t *)((base) + (off)))
+typedef struct {
+    int (*uart_init)(const struct dev_defn *defn,
+                     const ps_io_ops_t *ops,
+                     ps_chardevice_t *dev);
+    int (*uart_getchar)(ps_chardevice_t *d);
+    int (*uart_putchar)(ps_chardevice_t *d, int c);
+} uart_funcs_t;
 
-/* When DLAB=1, MU_IO is a baud rate register.
- * Otherwise, write to TX, read to RX */
-#define MU_IO       0x40
-/* When DLAB=1, MU_IIR is a baud rate register.
- * Otherwise IRQ enable */
-#define MU_IIR      0x44
-#define MU_IER      0x48
-#define MU_LCR      0x4C
-#define MU_MCR      0x50
-#define MU_LSR      0x54
-#define MU_MSR      0x58
-#define MU_SCRATCH  0x5C
-#define MU_CNTL     0x60
+uart_funcs_t uart_funcs;
 
-/* This bit is set if the transmit FIFO can accept at least one byte.*/
-#define MU_LSR_TXEMPTY   BIT(5)
-/* This bit is set if the transmit FIFO is empty and the
- * transmitter is idle. (Finished shifting out the last bit). */
-#define MU_LSR_TXIDLE    BIT(6)
-#define MU_LSR_RXOVERRUN BIT(1)
-#define MU_LSR_DATAREADY BIT(0)
-
-#define MU_LCR_DLAB      BIT(7)
-#define MU_LCR_BREAK     BIT(6)
-#define MU_LCR_DATASIZE  BIT(0)
-
-static void uart_handle_irq(ps_chardevice_t *d UNUSED)
+int uart_getchar(ps_chardevice_t *d)
 {
+    uart_funcs.uart_getchar(d);
 }
 
 int uart_putchar(ps_chardevice_t *d, int c)
 {
-    while (!(*REG_PTR(d->vaddr, MU_LSR) & MU_LSR_TXIDLE));
-    *REG_PTR(d->vaddr, MU_IO) = (c & 0xff);
-
-    return 0;
+    uart_funcs.uart_putchar(d, c);
 }
 
-int uart_getchar(ps_chardevice_t *d UNUSED)
+/*
+ * Configure UART GPIO pins.
+ *
+ * On the RPi4, there are 6 UARTs: One miniUART (UART1) and 5 PL011 UARTs (UART0
+ * + UART2-5). There is also a possibility to configure these pins via the
+ * config.txt file statically. Since these pins might be used for other
+ * functionality than UART, it might be best to configure these pins here
+ * dynamically.
+ *
+ * BCM2711 TRM, Section 11.3. Primary UART Inputs and Outputs.
+ */
+int uart_gpio_configure(enum chardev_id id, const ps_io_ops_t *o)
 {
-    while (!(*REG_PTR(d->vaddr, MU_LSR) & MU_LSR_DATAREADY));
-    return *REG_PTR(d->vaddr, MU_IO);
+    gpio_sys_t gpio_sys;
+    gpio_t gpio;
+
+    // We only configure the TX/RX pins for each UART peripheral and ignore flow
+    // control (CTS/RTS pins) for now.
+    int tx_pin = -1;
+    int rx_pin = -1;
+    int alt_function = -1;
+
+    /*
+     * GPIO pin configuration
+     *
+     * GPIO pins 14/15 are mapped accordingly: https://pinout.xyz/pinout/pin8_gpio14#
+     *
+     * This means:
+     *  UART0: these pins are RX/TX lines; pins must be configured for ALT0
+     *  UART1: these pins are RX/TX lines; pins must be configured for ALT5
+     *  UART5: these pins are CTS/RTS lines; we ignore cts/rts for now
+    */
+    switch (id) {
+    case 0:
+        // UART 0 uses GPIO pins 14-15
+        tx_pin = 14;
+        rx_pin = 15;
+        alt_function = BCM2711_GPIO_FSEL_ALT0;
+    case 1:
+        // UART 1 uses GPIO pins 14-15
+        tx_pin = 14;
+        rx_pin = 15;
+        alt_function = BCM2711_GPIO_FSEL_ALT5;
+    case 2:
+        // UART 2 uses GPIO pins 0-3
+        tx_pin = 0;
+        rx_pin = 1;
+        alt_function = BCM2711_GPIO_FSEL_ALT4;
+        break;
+    case 3:
+        // UART 3 uses GPIO pins 4-7
+        tx_pin = 4;
+        rx_pin = 5;
+        alt_function = BCM2711_GPIO_FSEL_ALT4;
+        break;
+    case 4:
+        // UART 4 uses GPIO pins 8-11
+        tx_pin = 8;
+        rx_pin = 9;
+        alt_function = BCM2711_GPIO_FSEL_ALT4;
+        break;
+    case 5:
+        // UART 5 uses GPIO pins 12-13
+        tx_pin = 12;
+        rx_pin = 13;
+        alt_function = BCM2711_GPIO_FSEL_ALT4;
+        break;
+    default:
+        ZF_LOGD("No pin configuration required!");
+        return 0;
+        break;
+    }
+
+    if (tx_pin < 0 || rx_pin < 0) {
+        ZF_LOGE("TX/RX pins wrongly configured!");
+        return -1;
+    }
+
+    if (alt_function < 0) {
+        ZF_LOGE("ALT function wrongly configured!");
+        return -1;
+    }
+
+    // GPIO initialization
+    int ret = gpio_sys_init((ps_io_ops_t *)o, &gpio_sys);
+    if (ret) {
+        ZF_LOGE("gpio_sys_init() failed: ret = %i", ret);
+        return -1;
+    }
+
+    // configure tx pin
+    gpio_sys.init(&gpio_sys, tx_pin, 0, &gpio);
+    bcm2711_gpio_fsel(&gpio, alt_function);
+
+    // configure rx pin
+    gpio_sys.init(&gpio_sys, rx_pin, 0, &gpio);
+    bcm2711_gpio_fsel(&gpio, alt_function);
+
+    return 0;
 }
 
 int uart_init(const struct dev_defn *defn,
               const ps_io_ops_t *ops,
               ps_chardevice_t *dev)
 {
-    /* Attempt to map the virtual address, assure this works */
-    void *vaddr = chardev_map(defn, ops);
-    memset(dev, 0, sizeof(*dev));
-    if (vaddr == NULL) {
+    switch (defn->id) {
+    case 1:
+        uart_funcs.uart_init    = &mini_uart_init;
+        uart_funcs.uart_getchar = &mini_uart_getchar;
+        uart_funcs.uart_putchar = &mini_uart_putchar;
+        break;
+    case 0:
+    case 2:
+    case 3:
+    case 4:
+    case 5:
+        uart_funcs.uart_init    = &pl011_uart_init;
+        uart_funcs.uart_getchar = &pl011_uart_getchar;
+        uart_funcs.uart_putchar = &pl011_uart_putchar;
+        break;
+    default:
+        ZF_LOGE("UART with id %d does not exist!", defn->id);
         return -1;
+        break;
     }
 
-    /* Set up all the  device properties. */
-    dev->id         = defn->id;
-    dev->vaddr      = (void *)vaddr;
-    dev->read       = &uart_read;
-    dev->write      = &uart_write;
-    dev->handle_irq = &uart_handle_irq;
-    dev->irqs       = defn->irqs;
-    dev->ioops      = *ops;
-    dev->flags      = SERIAL_AUTO_CR;
+    uart_gpio_configure(defn->id, ops);
+
+    uart_funcs.uart_init(defn, ops, dev);
 
     return 0;
 }
diff --git a/libplatsupport/src/plat/bcm2711/serial.h b/libplatsupport/src/plat/bcm2711/serial.h
index e9a40d5..96ec4d5 100644
--- a/libplatsupport/src/plat/bcm2711/serial.h
+++ b/libplatsupport/src/plat/bcm2711/serial.h
@@ -5,6 +5,34 @@
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
+#pragma once
+
 #include "../../chardev.h"
+#include <autoconf.h>
+#include <platsupport/gen_config.h>
+
+#define UART0_IRQ       153
+#define UART1_IRQ       125
+#define UART2_IRQ       153
+#define UART3_IRQ       153
+#define UART4_IRQ       153
+#define UART5_IRQ       153
+
+#define PL011_UART_BASE 0xfe201000
+#define MINI_UART_BASE  0xfe215000
+
+#define UART0_OFFSET    0x0     // UART0: PL011
+#define UART1_OFFSET    0x40    // UART1: Mini UART
+#define UART2_OFFSET    0x400   // UART2: PL011
+#define UART3_OFFSET    0x600   // UART3: PL011
+#define UART4_OFFSET    0x800   // UART4: PL011
+#define UART5_OFFSET    0xa00   // UART5: PL011
+
+#define UART0_PADDR  (PL011_UART_BASE + UART0_OFFSET)   // 0xfe201000
+#define UART1_PADDR  (MINI_UART_BASE  + UART1_OFFSET)   // 0xfe215040
+#define UART2_PADDR  (PL011_UART_BASE + UART2_OFFSET)   // 0xfe201400
+#define UART3_PADDR  (PL011_UART_BASE + UART3_OFFSET)   // 0xfe201600
+#define UART4_PADDR  (PL011_UART_BASE + UART4_OFFSET)   // 0xfe201800
+#define UART5_PADDR  (PL011_UART_BASE + UART5_OFFSET)   // 0xfe201a00
 
 int uart_init(const struct dev_defn *defn, const ps_io_ops_t *ops, ps_chardevice_t *dev);