Add PL011 UART driver
A minimal PL011 driver containing necessary functions
to print to UART. Based on the assembly implementation
found in TF-A (hash: 39bcbeac9c8c20ae660cb6d9e825ceb01e6144e7;
file: drivers/arm/pl011/aarch64/pl011_console.S).
Signed-off-by: Gabor Toth <gabor.toth2@arm.com>
Signed-off-by: Gabor Ambrus <gabor.ambrus@arm.com>
Change-Id: I644fb011b2b9a68aa09e8cc3a09ebd8b9576bb30
diff --git a/platform/drivers/arm/uart/driver.cmake b/platform/drivers/arm/uart/driver.cmake
new file mode 100644
index 0000000..a14dbb6
--- /dev/null
+++ b/platform/drivers/arm/uart/driver.cmake
@@ -0,0 +1,13 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+
+# Add source files for using uart and adapting it to the platform
+# uart interface.
+target_sources(${TGT} PRIVATE
+ "${CMAKE_CURRENT_LIST_DIR}/uart_adapter.c"
+ "${CMAKE_CURRENT_LIST_DIR}/pl011/pl011.c"
+)
diff --git a/platform/drivers/arm/uart/pl011/pl011.c b/platform/drivers/arm/uart/pl011/pl011.c
new file mode 100644
index 0000000..83fc825
--- /dev/null
+++ b/platform/drivers/arm/uart/pl011/pl011.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2023, ARM Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Based on the driver found in trustedfirmware-a written in assembly (pl011_console.S)
+ */
+
+#include <stdint.h>
+
+#include "../../../common/mmio.h"
+#include "pl011.h"
+
+#define DEFAULT_CLK_IN_HZ 24000000
+#define DEFAULT_BAUDRATE 115200
+
+void uflush(uintptr_t addr)
+{
+ /* Loop until the transmit FIFO is empty */
+ while (mmio_read_32(addr + UARTFR) & PL011_UARTFR_BUSY) {
+ };
+}
+
+void uputc(uint8_t ch, uintptr_t addr)
+{
+ /* Prepend '\r' to '\n' */
+ if (ch == '\n') {
+ /* Check if the transmit FIFO is full */
+ while (mmio_read_32(addr + UARTFR) & PL011_UARTFR_TXFF) {
+ };
+ mmio_write_32(addr + UARTDR, '\r');
+ }
+
+ /* Print the actual character */
+ while (mmio_read_32(addr + UARTFR) & PL011_UARTFR_TXFF) {
+ };
+ mmio_write_32(addr + UARTDR, ch);
+}
+
+void uart_deinit(uintptr_t addr)
+{
+ /* Disable uart before programming */
+ mmio_write_32(addr + UARTCR, 0);
+
+ /* Wait for the end of transmission */
+ uflush(addr);
+
+ /* Flush the transmit FIFO by setting the FEN bit to 0 in the Line Control Register */
+ mmio_write_32(addr + UARTLCR_H, 0);
+}
+
+int uart_init(uintptr_t addr)
+{
+ if (!addr)
+ return -1;
+
+ uart_deinit(addr);
+
+ /* Program the baudrate */
+ uint32_t divisor = (DEFAULT_CLK_IN_HZ * 4) / DEFAULT_BAUDRATE;
+
+ mmio_write_32(addr + UARTIBRD, divisor >> 6);
+ mmio_write_32(addr + UARTFBRD, divisor & 0x3f);
+
+ /* FIFO Enabled / No Parity / 8 Data bit / One Stop Bit */
+ mmio_write_32(addr + UARTLCR_H, PL011_LINE_CONTROL);
+
+ /* Clear any pending errors */
+ mmio_write_32(addr + UARTECR, 0);
+
+ /* Enable tx, rx, and uart overall */
+ mmio_write_32(addr + UARTCR, PL011_UARTCR_RXE | PL011_UARTCR_TXE | PL011_UARTCR_UARTEN);
+
+ return 0;
+}
+
diff --git a/platform/drivers/arm/uart/pl011/pl011.h b/platform/drivers/arm/uart/pl011/pl011.h
new file mode 100644
index 0000000..b1bec95
--- /dev/null
+++ b/platform/drivers/arm/uart/pl011/pl011.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2023, ARM Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef PL011_H
+#define PL011_H
+
+/*
+ * For the documentation of the driver, please refer to:
+ * https://developer.arm.com/documentation/ddi0183/g
+ */
+
+/* PL011 Registers */
+#define UARTDR 0x000
+#define UARTRSR 0x004
+#define UARTECR 0x004
+#define UARTFR 0x018
+#define UARTIMSC 0x038
+#define UARTRIS 0x03C
+#define UARTICR 0x044
+
+/*
+ * PL011 registers which are not part of SBSA specification.
+ * These registers are disabled if the underlying hardware is
+ * only a minimally compliant generic UART, which is a subset
+ * of PL011.
+ */
+#if !PL011_GENERIC_UART
+#define UARTILPR 0x020
+#define UARTIBRD 0x024
+#define UARTFBRD 0x028
+#define UARTLCR_H 0x02C
+#define UARTCR 0x030
+#define UARTIFLS 0x034
+#define UARTMIS 0x040
+#define UARTDMACR 0x048
+#endif /* !PL011_GENERIC_UART */
+
+/* Data status bits */
+#define UART_DATA_ERROR_MASK 0x0F00
+
+/* Status reg bits */
+#define UART_STATUS_ERROR_MASK 0x0F
+
+/* Flag reg bits */
+#define PL011_UARTFR_RI (1 << 8) /* Ring indicator */
+#define PL011_UARTFR_TXFE (1 << 7) /* Transmit FIFO empty */
+#define PL011_UARTFR_RXFF (1 << 6) /* Receive FIFO full */
+#define PL011_UARTFR_TXFF (1 << 5) /* Transmit FIFO full */
+#define PL011_UARTFR_RXFE (1 << 4) /* Receive FIFO empty */
+#define PL011_UARTFR_BUSY (1 << 3) /* UART busy */
+#define PL011_UARTFR_DCD (1 << 2) /* Data carrier detect */
+#define PL011_UARTFR_DSR (1 << 1) /* Data set ready */
+#define PL011_UARTFR_CTS (1 << 0) /* Clear to send */
+
+#define PL011_UARTFR_TXFF_BIT 5 /* Transmit FIFO full bit in UARTFR register */
+#define PL011_UARTFR_RXFE_BIT 4 /* Receive FIFO empty bit in UARTFR register */
+#define PL011_UARTFR_BUSY_BIT 3 /* UART busy bit in UARTFR register */
+
+/* Control reg bits */
+#define PL011_UARTCR_CTSEN (1 << 15) /* CTS hardware flow control enable */
+#define PL011_UARTCR_RTSEN (1 << 14) /* RTS hardware flow control enable */
+#define PL011_UARTCR_RTS (1 << 11) /* Request to send */
+#define PL011_UARTCR_DTR (1 << 10) /* Data transmit ready. */
+#define PL011_UARTCR_RXE (1 << 9) /* Receive enable */
+#define PL011_UARTCR_TXE (1 << 8) /* Transmit enable */
+#define PL011_UARTCR_LBE (1 << 7) /* Loopback enable */
+#define PL011_UARTCR_UARTEN (1 << 0) /* UART Enable */
+
+#if !defined(PL011_LINE_CONTROL)
+/* FIFO Enabled / No Parity / 8 Data bit / One Stop Bit */
+#define PL011_LINE_CONTROL (PL011_UARTLCR_H_FEN | PL011_UARTLCR_H_WLEN_8)
+#endif
+
+/* Line Control Register Bits */
+#define PL011_UARTLCR_H_SPS (1 << 7) /* Stick parity select */
+#define PL011_UARTLCR_H_WLEN_8 (3 << 5)
+#define PL011_UARTLCR_H_WLEN_7 (2 << 5)
+#define PL011_UARTLCR_H_WLEN_6 (1 << 5)
+#define PL011_UARTLCR_H_WLEN_5 (0 << 5)
+#define PL011_UARTLCR_H_FEN (1 << 4) /* FIFOs Enable */
+#define PL011_UARTLCR_H_STP2 (1 << 3) /* Two stop bits select */
+#define PL011_UARTLCR_H_EPS (1 << 2) /* Even parity select */
+#define PL011_UARTLCR_H_PEN (1 << 1) /* Parity Enable */
+#define PL011_UARTLCR_H_BRK (1 << 0) /* Send break */
+
+#define ERROR_NO_PENDING_CHAR (-1)
+
+#endif /* PL011_H */
diff --git a/platform/drivers/arm/uart/uart_adapter.c b/platform/drivers/arm/uart/uart_adapter.c
new file mode 100644
index 0000000..8f43214
--- /dev/null
+++ b/platform/drivers/arm/uart/uart_adapter.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include <assert.h>
+#include <config/interface/config_store.h>
+#include <platform/interface/device_region.h>
+#include <platform/interface/uart.h>
+#include <psa/error.h>
+#include <stdlib.h>
+
+#include "uart_decl.h"
+
+/*
+ * Flush the UART
+ */
+static int uart_flush(platform_uart_context *context)
+{
+ uflush(context->base_address);
+
+ return 0;
+}
+
+/*
+ * Send character to UART
+ */
+static int uart_putc(platform_uart_context *context, uint8_t ch)
+{
+ uputc(ch, context->base_address);
+
+ return 0;
+}
+
+/*
+ * Initialize the driver for the UART
+ */
+int platform_uart_create(struct platform_uart_driver *driver, int instance)
+{
+ static const struct platform_uart_iface iface = { .uart_flush = uart_flush,
+ .uart_putc = uart_putc };
+
+ struct device_region device_region;
+
+ if (!config_store_query(CONFIG_CLASSIFIER_DEVICE_REGION, "uart", instance, &device_region,
+ sizeof(device_region)))
+ return PSA_STATUS_HARDWARE_FAILURE;
+
+ driver->context = malloc(sizeof(platform_uart_context));
+
+ if (driver->context) {
+ /* Set only if context was created */
+ driver->iface = &iface;
+
+ /* A device region has been provided, possibly from an external configuration. */
+ ((platform_uart_context *)driver->context)->base_address = device_region.base_addr;
+
+ return uart_init(device_region.base_addr);
+ }
+
+ return -1;
+}
+
+void platform_uart_destroy(struct platform_uart_driver *driver)
+{
+ if (driver->context) {
+ uart_deinit(((platform_uart_context *) driver->context)->base_address);
+ free(driver->context);
+ driver->context = NULL;
+ }
+
+ driver->iface = NULL;
+}
diff --git a/platform/drivers/arm/uart/uart_decl.h b/platform/drivers/arm/uart/uart_decl.h
new file mode 100644
index 0000000..19bf95f
--- /dev/null
+++ b/platform/drivers/arm/uart/uart_decl.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2017-2023, ARM Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This is a modified version of the uart platform driver from trusted-firmware-a.
+ * The code has been modified to allow the peripheral to be accessed by S-EL0 at
+ * an arbitrary virtual address.
+ */
+
+#ifndef UART_DECL_H
+#define UART_DECL_H
+
+#include <stdint.h>
+
+int uart_deinit(uintptr_t addr);
+int uart_init(uintptr_t addr);
+void uflush(uintptr_t addr);
+void uputc(uint8_t ch, uintptr_t addr);
+
+#endif /* UART_DECL_H */
diff --git a/platform/drivers/common/mmio.h b/platform/drivers/common/mmio.h
new file mode 100644
index 0000000..edb7720
--- /dev/null
+++ b/platform/drivers/common/mmio.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2013-2022, ARM Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copied from trustedfirmware-a and stripped down.
+ */
+
+#ifndef MMIO_H
+#define MMIO_H
+
+#include <stdint.h>
+
+static inline void mmio_write_32(uintptr_t addr, uint32_t value)
+{
+ *(volatile uint32_t *)addr = value;
+}
+
+static inline uint32_t mmio_read_32(uintptr_t addr)
+{
+ return *(volatile uint32_t *)addr;
+}
+
+#endif /* MMIO_H */
diff --git a/platform/interface/uart.h b/platform/interface/uart.h
new file mode 100644
index 0000000..8f0c34f
--- /dev/null
+++ b/platform/interface/uart.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef TS_PLATFORM_INTERFACE_UART_H
+#define TS_PLATFORM_INTERFACE_UART_H
+
+/*
+ * Interface definintion for a platform uart driver. A platform provider will
+ * provide concrete implementations of this interface for each alternative
+ * implementation supported.
+ */
+#include <stddef.h>
+
+#include "device_region.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * A platform uart driver context.
+ */
+typedef struct {
+ /* Base address of the driver instance */
+ uintptr_t base_address;
+} platform_uart_context;
+
+/*
+ * Virtual interface for a platform uart driver. A platform will provide
+ * one or more concrete implementations of this interface.
+ */
+struct platform_uart_iface {
+ /**
+ * \brief Putc to submit a character to platform uart
+ * *
+ * * \param context Platform driver context
+ * \param ch Character to be written to UART
+ *
+ * \return 0 if successful.
+ */
+ int (*uart_putc)(platform_uart_context *context, uint8_t ch);
+ /**
+ * \brief Wait for empty input FIFO in platform uart
+ *
+ * * \param context Platform driver context
+ * *
+ * \return 0 if successful.
+ */
+ int (*uart_flush)(platform_uart_context *context);
+};
+
+/*
+ * A platform uart driver instance.
+ */
+struct platform_uart_driver {
+ void *context; /**< Opaque driver context */
+ const struct platform_uart_iface *iface; /**< Interface methods */
+};
+
+/**
+ * \brief Factory method to construct a platform specific uart driver
+ *
+ * \param driver Pointer to driver structure to initialize on construction.
+ * \param instance Deployment specific uart instance.
+ *
+ * \return 0 if successful.
+ */
+int platform_uart_create(struct platform_uart_driver *driver, int instance);
+
+/**
+ * \brief Destroy a driver constructed using the factory method
+ *
+ * \param driver Pointer to driver structure for constructed driver.
+ */
+void platform_uart_destroy(struct platform_uart_driver *driver);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TS_PLATFORM_INTERFACE_UART_H */
diff --git a/platform/providers/arm/fvp/fvp_base_revc-2xaemv8a/platform.cmake b/platform/providers/arm/fvp/fvp_base_revc-2xaemv8a/platform.cmake
index 694fa8b..143ea46 100644
--- a/platform/providers/arm/fvp/fvp_base_revc-2xaemv8a/platform.cmake
+++ b/platform/providers/arm/fvp/fvp_base_revc-2xaemv8a/platform.cmake
@@ -29,4 +29,8 @@
if ("semihosting" IN_LIST _platform_driver_dependencies)
include(${TS_ROOT}/platform/drivers/tf-a/lib/semihosting/driver.cmake)
+endif()
+
+if ("uart" IN_LIST _platform_driver_dependencies)
+ include(${TS_ROOT}/platform/drivers/arm/uart/driver.cmake)
endif()
\ No newline at end of file