From: Andrei Cherechesu <andrei.cherechesu@nxp.com>
The LINFlexD UART is an UART controller available on NXP S32
processors family targeting automotive (for example: S32G2, S32G3,
S32R).
S32G3 Reference Manual:
https://www.nxp.com/webapp/Download?colCode=RMS32G3.
Signed-off-by: Andrei Cherechesu <andrei.cherechesu@nxp.com>
Signed-off-by: Peter van der Perk <peter.vander.perk@nxp.com>
---
xen/arch/arm/include/asm/linflex-uart.h | 63 ++++
xen/drivers/char/Kconfig | 8 +
xen/drivers/char/Makefile | 1 +
xen/drivers/char/linflex-uart.c | 381 ++++++++++++++++++++++++
4 files changed, 453 insertions(+)
create mode 100644 xen/arch/arm/include/asm/linflex-uart.h
create mode 100644 xen/drivers/char/linflex-uart.c
diff --git a/xen/arch/arm/include/asm/linflex-uart.h b/xen/arch/arm/include/asm/linflex-uart.h
new file mode 100644
index 0000000000..95dcbcb476
--- /dev/null
+++ b/xen/arch/arm/include/asm/linflex-uart.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * xen/arch/arm/include/asm/linflex-uart.h
+ *
+ * Common constant definition between early printk and the UART driver
+ * for NXP LINFlexD UART.
+ *
+ * Andrei Cherechesu <andrei.cherechesu@nxp.com>
+ * Copyright 2018, 2021, 2024 NXP
+ */
+
+#ifndef __ASM_ARM_LINFLEX_UART_H
+#define __ASM_ARM_LINFLEX_UART_H
+
+/* 32-bit register offsets */
+#define LINCR1 (0x0)
+#define LINIER (0x4)
+#define LINSR (0x8)
+#define UARTCR (0x10)
+#define UARTSR (0x14)
+#define LINFBRR (0x24)
+#define LINIBRR (0x28)
+#define BDRL (0x38)
+#define BDRM (0x3C)
+#define UARTPTO (0x50)
+
+#define LINCR1_INIT BIT(0, U)
+#define LINCR1_MME BIT(4, U)
+#define LINCR1_BF BIT(7, U)
+
+#define LINSR_LINS GENMASK(15, 12)
+#define LINSR_LINS_INIT BIT(12, U)
+
+#define LINIER_DRIE BIT(2, U)
+#define LINIER_DTIE BIT(1, U)
+
+#define UARTCR_UART BIT(0, U)
+#define UARTCR_WL0 BIT(1, U)
+#define UARTCR_PC0 BIT(3, U)
+#define UARTCR_TXEN BIT(4, U)
+#define UARTCR_RXEN BIT(5, U)
+#define UARTCR_PC1 BIT(6, U)
+#define UARTCR_TFBM BIT(8, U)
+#define UARTCR_RFBM BIT(9, U)
+#define UARTCR_RDFLRFC GENMASK(12, 10)
+#define UARTCR_TDFLTFC GENMASK(15, 13)
+#define UARTCR_ROSE BIT(23, U)
+#define UARTCR_OSR_SHIFT (24)
+#define UARTCR_OSR GENMASK(27, UARTCR_OSR_SHIFT)
+
+#define UARTSR_DTFTFF BIT(1, U)
+#define UARTSR_DRFRFE BIT(2, U)
+
+#endif /* __ASM_ARM_LINFLEX_UART_H */
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
diff --git a/xen/drivers/char/Kconfig b/xen/drivers/char/Kconfig
index 3f836ab301..e175d07c02 100644
--- a/xen/drivers/char/Kconfig
+++ b/xen/drivers/char/Kconfig
@@ -13,6 +13,14 @@ config HAS_CADENCE_UART
This selects the Xilinx Zynq Cadence UART. If you have a Xilinx Zynq
based board, say Y.
+config HAS_LINFLEX
+ bool "NXP LINFlexD UART driver"
+ default y
+ depends on ARM_64
+ help
+ This selects the NXP LINFlexD UART. If you have an NXP S32G or S32R
+ based board, say Y.
+
config HAS_IMX_LPUART
bool "i.MX LPUART driver"
default y
diff --git a/xen/drivers/char/Makefile b/xen/drivers/char/Makefile
index e7e374775d..d3b987da1d 100644
--- a/xen/drivers/char/Makefile
+++ b/xen/drivers/char/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_HAS_SCIF) += scif-uart.o
obj-$(CONFIG_HAS_EHCI) += ehci-dbgp.o
obj-$(CONFIG_XHCI) += xhci-dbc.o
obj-$(CONFIG_HAS_IMX_LPUART) += imx-lpuart.o
+obj-$(CONFIG_HAS_LINFLEX) += linflex-uart.o
obj-$(CONFIG_ARM) += arm-uart.o
obj-y += serial.o
obj-$(CONFIG_XEN_GUEST) += xen_pv_console.o
diff --git a/xen/drivers/char/linflex-uart.c b/xen/drivers/char/linflex-uart.c
new file mode 100644
index 0000000000..2fa195cbf6
--- /dev/null
+++ b/xen/drivers/char/linflex-uart.c
@@ -0,0 +1,381 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * xen/drivers/char/linflex-uart.c
+ *
+ * Driver for NXP LINFlexD UART.
+ *
+ * Andrei Cherechesu <andrei.cherechesu@nxp.com>
+ * Copyright 2018, 2021-2022, 2024 NXP
+ */
+
+#include <xen/config.h>
+#include <xen/console.h>
+#include <xen/errno.h>
+#include <xen/init.h>
+#include <xen/irq.h>
+#include <xen/mm.h>
+#include <xen/serial.h>
+#include <asm/device.h>
+#include <asm/io.h>
+#include <asm/linflex-uart.h>
+
+#define LINFLEX_CLK_FREQ (125000000)
+#define LINFLEX_MAX_BAUDRATE (2000000)
+#define LINFLEX_BAUDRATE (115200)
+#define LINFLEX_LDIV_MULTIPLIER (16)
+
+static struct linflex_uart {
+ uint32_t baud, clock_hz;
+ uint32_t irq;
+ char __iomem *regs;
+ struct irqaction irqaction;
+ struct vuart_info vuart;
+} linflex_com;
+
+static uint32_t linflex_uart_readl(const struct linflex_uart *uart,
+ uint32_t off)
+{
+ return readl(uart->regs + off);
+}
+
+static void linflex_uart_writel(const struct linflex_uart *uart, uint32_t off,
+ uint32_t val)
+{
+ writel(val, uart->regs + off);
+}
+
+static void linflex_uart_writeb(const struct linflex_uart *uart, uint32_t off,
+ uint8_t val)
+{
+ writeb(val, uart->regs + off);
+}
+
+static uint32_t linflex_uart_get_osr(uint32_t uartcr)
+{
+ return (uartcr & UARTCR_OSR) >> UARTCR_OSR_SHIFT;
+}
+
+static bool linflex_uart_tx_fifo_mode(const struct linflex_uart *uart)
+{
+ return !!(linflex_uart_readl(uart, UARTCR) & UARTCR_TFBM);
+}
+
+static bool linflex_uart_rx_fifo_mode(const struct linflex_uart *uart)
+{
+ return !!(linflex_uart_readl(uart, UARTCR) & UARTCR_RFBM);
+}
+
+static uint32_t linflex_uart_ldiv_multiplier(const struct linflex_uart *uart)
+{
+ uint32_t uartcr, mul = LINFLEX_LDIV_MULTIPLIER;
+
+ uartcr = linflex_uart_readl(uart, UARTCR);
+ if ( uartcr & UARTCR_ROSE )
+ mul = linflex_uart_get_osr(uartcr);
+
+ return mul;
+}
+
+static void linflex_uart_flush(struct serial_port *port)
+{
+ const struct linflex_uart *uart = port->uart;
+
+ if ( linflex_uart_tx_fifo_mode(uart) )
+ while ( linflex_uart_readl(uart, UARTCR) & UARTCR_TDFLTFC )
+ cpu_relax();
+
+ if ( linflex_uart_rx_fifo_mode(uart) )
+ while ( linflex_uart_readl(uart, UARTCR) & UARTCR_RDFLRFC )
+ cpu_relax();
+}
+
+static void __init linflex_uart_init_preirq(struct serial_port *port)
+{
+ struct linflex_uart *uart = port->uart;
+ uint32_t ibr, fbr, divisr, dividr, ctrl;
+
+ /* Disable RX/TX before init mode */
+ ctrl = linflex_uart_readl(uart, UARTCR);
+ ctrl &= ~(UARTCR_RXEN | UARTCR_TXEN);
+ linflex_uart_writel(uart, UARTCR, ctrl);
+
+ /*
+ * Smoothen the transition from early_printk by waiting
+ * for all pending characters to transmit
+ */
+ linflex_uart_flush(port);
+
+ /* Init mode */
+ ctrl = LINCR1_INIT;
+ linflex_uart_writel(uart, LINCR1, ctrl);
+
+ /* Waiting for init mode entry */
+ while ( (linflex_uart_readl(uart, LINSR) & LINSR_LINS) != LINSR_LINS_INIT )
+ cpu_relax();
+
+ /* Set Master Mode */
+ ctrl |= LINCR1_MME;
+ linflex_uart_writel(uart, LINCR1, ctrl);
+
+ if ( uart->baud > LINFLEX_MAX_BAUDRATE )
+ uart->baud = LINFLEX_MAX_BAUDRATE;
+
+ /* Provide data bits, parity, stop bit, etc */
+ divisr = uart->clock_hz;
+ dividr = uart->baud * linflex_uart_ldiv_multiplier(uart);
+
+ ibr = divisr / dividr;
+ fbr = ((divisr % dividr) * 16 / dividr) & 0xF;
+
+ linflex_uart_writel(uart, LINIBRR, ibr);
+ linflex_uart_writel(uart, LINFBRR, fbr);
+
+ /* Set preset timeout register value */
+ linflex_uart_writel(uart, UARTPTO, 0xF);
+
+ /* Setting UARTCR[UART] bit is required for writing other bits in UARTCR */
+ linflex_uart_writel(uart, UARTCR, UARTCR_UART);
+
+ /* 8 bit data, no parity, UART mode, Buffer mode */
+ linflex_uart_writel(uart, UARTCR, UARTCR_PC1 | UARTCR_PC0 | UARTCR_WL0 |
+ UARTCR_UART);
+
+ /* end init mode */
+ ctrl = linflex_uart_readl(uart, LINCR1);
+ ctrl &= ~LINCR1_INIT;
+ linflex_uart_writel(uart, LINCR1, ctrl);
+
+ /* Enable RX/TX after exiting init mode */
+ ctrl = linflex_uart_readl(uart, UARTCR);
+ ctrl |= UARTCR_RXEN | UARTCR_TXEN;
+ linflex_uart_writel(uart, UARTCR, ctrl);
+}
+
+static void linflex_uart_interrupt(int irq, void *data)
+{
+ struct serial_port *port = data;
+ const struct linflex_uart *uart = port->uart;
+ uint32_t sts;
+
+ sts = linflex_uart_readl(uart, UARTSR);
+
+ if ( sts & UARTSR_DRFRFE )
+ serial_rx_interrupt(port);
+
+ if ( sts & UARTSR_DTFTFF )
+ serial_tx_interrupt(port);
+}
+
+static void __init linflex_uart_init_postirq(struct serial_port *port)
+{
+ struct linflex_uart *uart = port->uart;
+ uint32_t temp;
+
+ uart->irqaction.handler = linflex_uart_interrupt;
+ uart->irqaction.name = "linflex_uart";
+ uart->irqaction.dev_id = port;
+
+ if ( setup_irq(uart->irq, 0, &uart->irqaction) != 0 )
+ {
+ printk("linflex-uart: Failed to allocate IRQ %d\n", uart->irq);
+ return;
+ }
+
+ /* Enable interrupts */
+ temp = linflex_uart_readl(uart, LINIER);
+ temp |= (LINIER_DRIE | LINIER_DTIE);
+ linflex_uart_writel(uart, LINIER, temp);
+ printk("linflex-uart: IRQ %d enabled\n", uart->irq);
+}
+
+static int linflex_uart_tx_ready(struct serial_port *port)
+{
+ const struct linflex_uart *uart = port->uart;
+
+ if ( linflex_uart_tx_fifo_mode(uart) )
+ return (linflex_uart_readl(uart, UARTSR) & UARTSR_DTFTFF) == 0 ? 1 : 0;
+
+ /*
+ * Buffer Mode => TX is waited to be ready after sending a char,
+ * so we can assume it is always ready before.
+ */
+ return 1;
+}
+
+static void linflex_uart_putc(struct serial_port *port, char c)
+{
+ const struct linflex_uart *uart = port->uart;
+ uint32_t uartsr;
+
+ if ( c == '\n' )
+ linflex_uart_putc(port, '\r');
+
+ linflex_uart_writeb(uart, BDRL, c);
+
+ /* Buffer Mode */
+ if ( !linflex_uart_tx_fifo_mode(uart) )
+ {
+ while ( (linflex_uart_readl(uart, UARTSR) & UARTSR_DTFTFF) == 0 )
+ cpu_relax();
+
+ uartsr = linflex_uart_readl(uart, UARTSR) | (UARTSR_DTFTFF);
+ linflex_uart_writel(uart, UARTSR, uartsr);
+ }
+}
+
+static int linflex_uart_getc(struct serial_port *port, char *pc)
+{
+ const struct linflex_uart *uart = port->uart;
+ uint32_t ch, uartsr, rx_fifo_mode;
+
+ rx_fifo_mode = linflex_uart_rx_fifo_mode(uart);
+
+ if ( rx_fifo_mode )
+ while ( linflex_uart_readl(uart, UARTSR) & UARTSR_DRFRFE )
+ cpu_relax();
+ else
+ while ( !(linflex_uart_readl(uart, UARTSR) & UARTSR_DRFRFE) )
+ cpu_relax();
+
+ ch = linflex_uart_readl(uart, BDRM);
+ *pc = ch & 0xff;
+
+ if ( !rx_fifo_mode ) {
+ uartsr = linflex_uart_readl(uart, UARTSR) | UARTSR_DRFRFE;
+ linflex_uart_writel(uart, UARTSR, uartsr);
+ }
+
+ return 1;
+}
+
+static int __init linflex_uart_irq(struct serial_port *port)
+{
+ const struct linflex_uart *uart = port->uart;
+
+ return ((uart->irq > 0) ? uart->irq : -1);
+}
+
+static const struct vuart_info *linflex_vuart_info(struct serial_port *port)
+{
+ const struct linflex_uart *uart = port->uart;
+
+ return &uart->vuart;
+}
+
+static void linflex_uart_start_tx(struct serial_port *port)
+{
+ const struct linflex_uart *uart = port->uart;
+ uint32_t temp;
+
+ temp = linflex_uart_readl(uart, LINIER);
+ linflex_uart_writel(uart, LINIER, temp | LINIER_DTIE);
+}
+
+static void linflex_uart_stop_tx(struct serial_port *port)
+{
+ const struct linflex_uart *uart = port->uart;
+ uint32_t temp;
+
+ temp = linflex_uart_readl(uart, LINIER);
+ temp &= ~(LINIER_DTIE);
+ linflex_uart_writel(uart, LINIER, temp);
+}
+
+static struct uart_driver __read_mostly linflex_uart_driver = {
+ .init_preirq = linflex_uart_init_preirq,
+ .init_postirq = linflex_uart_init_postirq,
+ .tx_ready = linflex_uart_tx_ready,
+ .putc = linflex_uart_putc,
+ .flush = linflex_uart_flush,
+ .getc = linflex_uart_getc,
+ .irq = linflex_uart_irq,
+ .start_tx = linflex_uart_start_tx,
+ .stop_tx = linflex_uart_stop_tx,
+ .vuart_info = linflex_vuart_info,
+};
+
+static int __init linflex_uart_init(struct dt_device_node *dev, const void *data)
+{
+ const char *config = data;
+ struct linflex_uart *uart;
+ paddr_t addr, size;
+ uint32_t baud = 0;
+ int res;
+
+ if ( strcmp(config, "") )
+ {
+ baud = simple_strtoul(config, &config, 10);
+ if ( strcmp(config, "") )
+ printk("linflex-uart: Only baud rate is configurable, discarding other options: %s\n",
+ config);
+ }
+ else
+ {
+ /* Configuration not provided, use a default one */
+ baud = LINFLEX_BAUDRATE;
+ printk("linflex-uart: Baud rate not provided, using %d as default\n",
+ baud);
+ }
+
+ uart = &linflex_com;
+
+ res = dt_device_get_paddr(dev, 0, &addr, &size);
+ if ( res )
+ {
+ printk("linflex-uart: Unable to retrieve the base address of the UART\n");
+ return res;
+ }
+
+ res = platform_get_irq(dev, 0);
+ if ( res < 0 )
+ {
+ printk("linflex-uart: Unable to retrieve the IRQ\n");
+ return -EINVAL;
+ }
+ uart->irq = res;
+
+ uart->regs = ioremap_nocache(addr, size);
+ if ( !uart->regs )
+ {
+ printk("linflex-uart: Unable to map the UART memory\n");
+ return -ENOMEM;
+ }
+
+ uart->baud = baud;
+ uart->clock_hz = LINFLEX_CLK_FREQ;
+
+ uart->vuart.base_addr = addr;
+ uart->vuart.size = size;
+ uart->vuart.data_off = BDRL;
+ uart->vuart.status_off = UARTSR;
+ uart->vuart.status = UARTSR_DTFTFF;
+
+ /* Register with generic serial driver */
+ serial_register_uart(SERHND_DTUART, &linflex_uart_driver, uart);
+
+ dt_device_set_used_by(dev, DOMID_XEN);
+
+ return 0;
+}
+
+static const struct dt_device_match linflex_uart_dt_compat[] __initconst =
+{
+ DT_MATCH_COMPATIBLE("nxp,s32g2-linflexuart"),
+ DT_MATCH_COMPATIBLE("nxp,s32g3-linflexuart"),
+ DT_MATCH_COMPATIBLE("fsl,s32v234-linflexuart"),
+ { /* sentinel */ },
+};
+
+DT_DEVICE_START(linflex_uart, "NXP LINFlexD UART", DEVICE_SERIAL)
+ .dt_match = linflex_uart_dt_compat,
+ .init = linflex_uart_init,
+DT_DEVICE_END
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
--
2.45.2
Hi Andrei, On 30/09/2024 12:47, Andrei Cherechesu (OSS) wrote: > +static void __init linflex_uart_init_preirq(struct serial_port *port) > +{ > + struct linflex_uart *uart = port->uart; > + uint32_t ibr, fbr, divisr, dividr, ctrl; > + > + /* Disable RX/TX before init mode */ > + ctrl = linflex_uart_readl(uart, UARTCR); > + ctrl &= ~(UARTCR_RXEN | UARTCR_TXEN); > + linflex_uart_writel(uart, UARTCR, ctrl); > + > + /* > + * Smoothen the transition from early_printk by waiting > + * for all pending characters to transmit > + */ Just to note that early_printk() will still be used by secondary CPUs when booting which happens after init_preirq() is called. Will this be a problem for you? I haven't compared the code against the specification. But the logic LGTM from a Xen PoV. So: Acked-by: Julien Grall <jgrall@amazon.com> Cheers, -- Julien Grall
Hi Julien, On 01/10/2024 12:20, Julien Grall wrote: > Hi Andrei, > > On 30/09/2024 12:47, Andrei Cherechesu (OSS) wrote: >> +static void __init linflex_uart_init_preirq(struct serial_port *port) >> +{ >> + struct linflex_uart *uart = port->uart; >> + uint32_t ibr, fbr, divisr, dividr, ctrl; >> + >> + /* Disable RX/TX before init mode */ >> + ctrl = linflex_uart_readl(uart, UARTCR); >> + ctrl &= ~(UARTCR_RXEN | UARTCR_TXEN); >> + linflex_uart_writel(uart, UARTCR, ctrl); >> + >> + /* >> + * Smoothen the transition from early_printk by waiting >> + * for all pending characters to transmit >> + */ > > Just to note that early_printk() will still be used by secondary CPUs when booting which happens after init_preirq() is called. Will this be a problem for you? > No, there's no problem with that. At the end of init_preirq() the UART can still be used via early_printk(). I know the secondary cores have a mechanism to print some messages themselves when being brought up, otherwise their initialization fails. But they're being enabled correctly in our case: (XEN) Bringing up CPU1 - CPU 0000000000000001 booting - - Current EL 0000000000000008 - - Initialize CPU - - Turning on paging - - Paging turned on - - Ready - (XEN) GICv3: CPU1: Found redistributor in region 0 @00000a004003e000 (XEN) CPU1: Guest atomics will try 13 times before pausing the domain (XEN) CPU 1 booted. (XEN) Bringing up CPU2 - CPU 0000000000000002 booting - - Current EL 0000000000000008 - - Initialize CPU - - Turning on paging - - Paging turned on - - Ready - (XEN) GICv3: CPU2: Found redistributor in region 0 @00000a004005e000 (XEN) CPU2: Guest atomics will try 13 times before pausing the domain (XEN) CPU 2 booted. (XEN) Bringing up CPU3 ... > I haven't compared the code against the specification. But the logic LGTM from a Xen PoV. So: > > Acked-by: Julien Grall <jgrall@amazon.com> > > Cheers, > Thank you for the review! Regards, Andrei Cherechesu
© 2016 - 2024 Red Hat, Inc.