From 14fb79af2ffa7b8579bb05459cc4e621e257a394 Mon Sep 17 00:00:00 2001 From: Chen Pei Date: Tue, 21 Jul 2026 14:07:42 +0800 Subject: [PATCH] lib: utils/serial: Add ARM PL011 UART driver OpenSBI has no driver for the ARM PrimeCell PL011 UART, so platforms that expose a PL011 as their console (for example a QEMU RISC-V virt machine wired with an "arm,pl011" serial port) cannot use it as the firmware console. Add a polled MMIO PL011 driver modelled on the existing cadence-uart driver, together with an FDT glue driver matching "arm,pl011" and "arm,sbsa-uart", and wire it into the generic platform via a new FDT_SERIAL_PL011 / SERIAL_PL011 Kconfig pair enabled in the generic defconfig. The baud-rate divisors are only programmed when both the input clock frequency and the target baud rate are known; on platforms that do not expose "clock-frequency" (such as QEMU) the divisor registers are left untouched, matching the cadence-uart behaviour. The MMIO window is registered with the root domain so the console keeps working under domain isolation. Signed-off-by: Chen Pei --- include/sbi_utils/serial/pl011.h | 14 ++++ lib/utils/serial/Kconfig | 9 +++ lib/utils/serial/fdt_serial_pl011.c | 33 +++++++++ lib/utils/serial/objects.mk | 4 ++ lib/utils/serial/pl011.c | 108 ++++++++++++++++++++++++++++ platform/generic/configs/defconfig | 1 + 6 files changed, 169 insertions(+) create mode 100644 include/sbi_utils/serial/pl011.h create mode 100644 lib/utils/serial/fdt_serial_pl011.c create mode 100644 lib/utils/serial/pl011.c diff --git a/include/sbi_utils/serial/pl011.h b/include/sbi_utils/serial/pl011.h new file mode 100644 index 00000000..1d0e428f --- /dev/null +++ b/include/sbi_utils/serial/pl011.h @@ -0,0 +1,14 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026 Alibaba Group Holding Limited. + */ + +#ifndef __SERIAL_PL011_UART_H__ +#define __SERIAL_PL011_UART_H__ + +#include + +int pl011_uart_init(unsigned long base, u32 in_freq, u32 baudrate); + +#endif diff --git a/lib/utils/serial/Kconfig b/lib/utils/serial/Kconfig index e3589cac..ea83bd16 100644 --- a/lib/utils/serial/Kconfig +++ b/lib/utils/serial/Kconfig @@ -54,6 +54,11 @@ config FDT_SERIAL_XILINX_UARTLITE select SERIAL_XILINX_UARTLITE default n +config FDT_SERIAL_PL011 + bool "ARM PL011 UART FDT driver" + select SERIAL_PL011 + default n + endif config SERIAL_CADENCE @@ -88,6 +93,10 @@ config SERIAL_XILINX_UARTLITE bool "Xilinx UART Lite support" default n +config SERIAL_PL011 + bool "ARM PL011 UART support" + default n + config SERIAL_SEMIHOSTING bool "Semihosting support" default n diff --git a/lib/utils/serial/fdt_serial_pl011.c b/lib/utils/serial/fdt_serial_pl011.c new file mode 100644 index 00000000..4eb84378 --- /dev/null +++ b/lib/utils/serial/fdt_serial_pl011.c @@ -0,0 +1,33 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026 Alibaba Group Holding Limited. + */ + +#include +#include +#include + +static int serial_pl011_init(const void *fdt, int nodeoff, + const struct fdt_match *match) +{ + int rc; + struct platform_uart_data uart = { 0 }; + + rc = fdt_parse_uart_node(fdt, nodeoff, &uart); + if (rc) + return rc; + + return pl011_uart_init(uart.addr, uart.freq, uart.baud); +} + +static const struct fdt_match serial_pl011_match[] = { + { .compatible = "arm,pl011" }, + { .compatible = "arm,sbsa-uart" }, + { }, +}; + +const struct fdt_driver fdt_serial_pl011 = { + .match_table = serial_pl011_match, + .init = serial_pl011_init +}; diff --git a/lib/utils/serial/objects.mk b/lib/utils/serial/objects.mk index 7c973c83..eff3cc30 100644 --- a/lib/utils/serial/objects.mk +++ b/lib/utils/serial/objects.mk @@ -37,6 +37,9 @@ libsbiutils-objs-$(CONFIG_FDT_SERIAL_UART8250) += serial/fdt_serial_uart8250.o carray-fdt_serial_drivers-$(CONFIG_FDT_SERIAL_XILINX_UARTLITE) += fdt_serial_xlnx_uartlite libsbiutils-objs-$(CONFIG_FDT_SERIAL_XILINX_UARTLITE) += serial/fdt_serial_xlnx_uartlite.o +carray-fdt_serial_drivers-$(CONFIG_FDT_SERIAL_PL011) += fdt_serial_pl011 +libsbiutils-objs-$(CONFIG_FDT_SERIAL_PL011) += serial/fdt_serial_pl011.o + libsbiutils-objs-$(CONFIG_SERIAL_CADENCE) += serial/cadence-uart.o libsbiutils-objs-$(CONFIG_SERIAL_GAISLER) += serial/gaisler-uart.o libsbiutils-objs-$(CONFIG_SERIAL_RENESAS_SCIF) += serial/renesas_scif.o @@ -45,4 +48,5 @@ libsbiutils-objs-$(CONFIG_SERIAL_SIFIVE) += serial/sifive-uart.o libsbiutils-objs-$(CONFIG_SERIAL_LITEX) += serial/litex-uart.o libsbiutils-objs-$(CONFIG_SERIAL_UART8250) += serial/uart8250.o libsbiutils-objs-$(CONFIG_SERIAL_XILINX_UARTLITE) += serial/xlnx-uartlite.o +libsbiutils-objs-$(CONFIG_SERIAL_PL011) += serial/pl011.o libsbiutils-objs-$(CONFIG_SERIAL_SEMIHOSTING) += serial/semihosting.o diff --git a/lib/utils/serial/pl011.c b/lib/utils/serial/pl011.c new file mode 100644 index 00000000..d87b8897 --- /dev/null +++ b/lib/utils/serial/pl011.c @@ -0,0 +1,108 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026 Alibaba Group Holding Limited. + * + * ARM PrimeCell PL011 UART driver. + */ + +#include +#include +#include +#include + +/* clang-format off */ + +#define UART_REG_DR 0x00 /* Data register */ +#define UART_REG_FR 0x18 /* Flag register */ +#define UART_REG_IBRD 0x24 /* Integer baud rate divisor */ +#define UART_REG_FBRD 0x28 /* Fractional baud rate divisor */ +#define UART_REG_LCR_H 0x2c /* Line control register */ +#define UART_REG_CR 0x30 /* Control register */ +#define UART_REG_IMSC 0x38 /* Interrupt mask set/clear register */ + +#define UART_FR_RXFE (1 << 4) /* Receive FIFO empty */ +#define UART_FR_TXFF (1 << 5) /* Transmit FIFO full */ + +#define UART_LCR_H_FEN (1 << 4) /* Enable FIFOs */ +#define UART_LCR_H_WLEN_8 (3 << 5) /* 8 bits word length */ + +#define UART_CR_UARTEN (1 << 0) /* UART enable */ +#define UART_CR_TXE (1 << 8) /* Transmit enable */ +#define UART_CR_RXE (1 << 9) /* Receive enable */ + +/* clang-format on */ + +static volatile void *uart_base; +static u32 uart_in_freq; +static u32 uart_baudrate; + +static u32 get_reg(u32 offset) +{ + return readl(uart_base + offset); +} + +static void set_reg(u32 offset, u32 val) +{ + writel(val, uart_base + offset); +} + +static void pl011_uart_putc(char ch) +{ + while (get_reg(UART_REG_FR) & UART_FR_TXFF) + ; + + set_reg(UART_REG_DR, ch); +} + +static int pl011_uart_getc(void) +{ + if (!(get_reg(UART_REG_FR) & UART_FR_RXFE)) + return get_reg(UART_REG_DR); + + return -1; +} + +static struct sbi_console_device pl011_console = { + .name = "pl011", + .console_putc = pl011_uart_putc, + .console_getc = pl011_uart_getc +}; + +int pl011_uart_init(unsigned long base, u32 in_freq, u32 baudrate) +{ + uart_base = (volatile void *)base; + uart_in_freq = in_freq; + uart_baudrate = baudrate; + + /* Disable the UART before configuration */ + set_reg(UART_REG_CR, 0); + + /* Mask all interrupts */ + set_reg(UART_REG_IMSC, 0); + + /* + * Program the baud rate divisors only when both the input clock + * frequency and the target baud rate are known. On platforms (such + * as QEMU) that do not expose "clock-frequency", in_freq is 0 and + * the existing divisor configuration is left untouched. + */ + if (in_freq && baudrate) { + u32 div = (in_freq * 4) / baudrate; + + set_reg(UART_REG_IBRD, div >> 6); + set_reg(UART_REG_FBRD, div & 0x3f); + } + + /* 8 bits word length, enable FIFOs, no parity, 1 stop bit */ + set_reg(UART_REG_LCR_H, UART_LCR_H_WLEN_8 | UART_LCR_H_FEN); + + /* Enable the UART with both transmit and receive paths */ + set_reg(UART_REG_CR, UART_CR_UARTEN | UART_CR_TXE | UART_CR_RXE); + + sbi_console_set_device(&pl011_console); + + return sbi_domain_root_add_memrange(base, PAGE_SIZE, PAGE_SIZE, + (SBI_DOMAIN_MEMREGION_MMIO | + SBI_DOMAIN_MEMREGION_SHARED_SURW_MRW)); +} diff --git a/platform/generic/configs/defconfig b/platform/generic/configs/defconfig index 287bd2fc..532076e5 100644 --- a/platform/generic/configs/defconfig +++ b/platform/generic/configs/defconfig @@ -49,6 +49,7 @@ CONFIG_FDT_SERIAL_SIFIVE=y CONFIG_FDT_SERIAL_LITEX=y CONFIG_FDT_SERIAL_UART8250=y CONFIG_FDT_SERIAL_XILINX_UARTLITE=y +CONFIG_FDT_SERIAL_PL011=y CONFIG_FDT_SUSPEND=y CONFIG_FDT_SUSPEND_RPMI=y CONFIG_FDT_TIMER=y