diff mbox series

[v1,1/5] xen/arm: Add NXP LINFlexD UART Driver

Message ID 20240910143411.178704-2-andrei.cherechesu@oss.nxp.com (mailing list archive)
State New
Headers show
Series xen/arm: Add support for S32CC platforms and LINFlexD UART | expand

Commit Message

Andrei Cherechesu Sept. 10, 2024, 2:34 p.m. UTC
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 |  62 ++++
 xen/drivers/char/Kconfig                |   8 +
 xen/drivers/char/Makefile               |   1 +
 xen/drivers/char/linflex-uart.c         | 365 ++++++++++++++++++++++++
 4 files changed, 436 insertions(+)
 create mode 100644 xen/arch/arm/include/asm/linflex-uart.h
 create mode 100644 xen/drivers/char/linflex-uart.c

Comments

Julien Grall Sept. 10, 2024, 9:55 p.m. UTC | #1
Hi,

On 10/09/2024 15:34, Andrei Cherechesu (OSS) wrote:
> 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.

It looks like you need to create an account. Is there any public version 
of the specification?

> 
> 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 |  62 ++++
>   xen/drivers/char/Kconfig                |   8 +
>   xen/drivers/char/Makefile               |   1 +
>   xen/drivers/char/linflex-uart.c         | 365 ++++++++++++++++++++++++
>   4 files changed, 436 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..62dc54d155
> --- /dev/null
> +++ b/xen/arch/arm/include/asm/linflex-uart.h
> @@ -0,0 +1,62 @@
> +/* SPDX-License-Identifier: GPL-2.0 */

The identifier GPL-2.0 was deprecated (see LICENSES/GPL-2.0). The new 
tag should be GPL-2.0-only. The resulting license is the same.

> +/*
> + * 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      GENMASK(27, 24)
> +
> +#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..4ca8f732ae
> --- /dev/null
> +++ b/xen/drivers/char/linflex-uart.c
> @@ -0,0 +1,365 @@
> +/* SPDX-License-Identifier: GPL-2.0 */

Ditto.

> +/*
> + * 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/serial.h>

In Xen, we tend to order the include alphabetically within the same 
directory. So this wants to be after xen/mm.h.

> +#include <xen/init.h>
> +#include <xen/irq.h>
> +#include <xen/mm.h>
> +#include <asm/device.h>
> +#include <asm/io.h>
> +#include <asm/linflex-uart.h>
> +
> +#define LINFLEX_CLK_FREQ        (125000000)
> +#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(struct linflex_uart *uart, uint32_t off)
> +{
> +    return readl(uart->regs + off);
> +}
> +
> +static void linflex_uart_writel(struct linflex_uart *uart, uint32_t off,
> +                                uint32_t val)
> +{
> +    writel(val, uart->regs + off);
> +}
> +
> +static void linflex_uart_writeb(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) >> 24;

Please provide a define for 24. This would also make easier to correlate 
with UARTCR_OSR.

> +}
> +
> +static uint32_t linflex_uart_tx_fifo_mode(struct linflex_uart *uart)

AFAICT, UARTCR_TFBM is one-bit. So should this return a bool?

> +{
> +    return linflex_uart_readl(uart, UARTCR) & UARTCR_TFBM;
> +}
> +
> +static uint32_t linflex_uart_rx_fifo_mode(struct linflex_uart *uart)

Same here.

> +{
> +    return linflex_uart_readl(uart, UARTCR) & UARTCR_RFBM;
> +}
> +
> +static uint32_t linflex_uart_ldiv_multiplier(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)
> +{
> +	struct linflex_uart *uart = port->uart;

Above, youa re using tab hard but above you use soft tab. Is the code 
intended to follow Xen coding style? If so, you want to use soft tab.

> +
> +	if ( linflex_uart_tx_fifo_mode(uart) )
> +		while ( linflex_uart_readl(uart, UARTCR) & UARTCR_TDFLTFC );
> +            cpu_relax();

The indentation is really confusing here. It leads to think that 
cpu_relax() should be part of while() but you are using ';'. I guess you 
really intended to have cpu_relax() within the while loop?

> +
> +	if ( linflex_uart_rx_fifo_mode(uart) )
> +		while ( linflex_uart_readl(uart, UARTCR) & UARTCR_RDFLRFC );
> +            cpu_relax();

Same here.

> +}
> +
> +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
> +    */

The indentation for comment in Xen is:

/*
  * Foor
  * Bar
  */

> +    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);
> +
> +    /* Provide data bits, parity, stop bit, etc */
> +    divisr = uart->clock_hz;
> +    dividr = (uint32_t)(uart->baud * linflex_uart_ldiv_multiplier(uart));
> +
> +    ibr = (uint32_t)(divisr / dividr);
> +    fbr = (uint32_t)((divisr % dividr) * 16 / dividr) & 0xF;

On the 3 lines above, why do you need to cast to 32-bit? Is this because 
the result is 64-bit? If so, why do you need to ignore the top bits?

> 
> +    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;
> +    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 )
> +    {
> +        dprintk(XENLOG_ERR, "Failed to allocate linflex_uart IRQ %d\n",
> +                uart->irq);

NIT: This should only be called once during boot. So I would consider to 
use printk() so it can be printed in production.

> +        return;
> +    }
> +
> +    /* Enable interrupts */
> +    temp = linflex_uart_readl(uart, LINIER);
> +    temp |= (LINIER_DRIE | LINIER_DTIE);
> +    linflex_uart_writel(uart, LINIER, temp);
> +    dprintk(XENLOG_DEBUG, "IRQ %d enabled\n", uart->irq);

Same here.

> +}
> +
> +static int linflex_uart_tx_ready(struct serial_port *port)
> +{
> +    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.
> +    */

Coding style. See above how it should be done for multi-line comments.

> +    return 1;
> +}
> +
> +static void linflex_uart_putc(struct serial_port *port, char c)
> +{
> +    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)
> +{
> +    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)
> +{
> +    struct linflex_uart *uart = port->uart;
> +
> +    return ((uart->irq > 0) ? uart->irq : -1);
> +}
> +
> +static const struct vuart_info *linflex_uart_vuart_info(
> +                struct serial_port *port)
> +{
> +    struct linflex_uart *uart = port->uart;

NIT: You are not modifying uart. So this coul be const.

> +
> +    return &uart->vuart;
> +}
> +
> +static void linflex_uart_start_tx(struct serial_port *port)
> +{
> +    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)
> +{
> +    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_uart_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;
> +    int res;
> +
> +    if ( strcmp(config, "") )
> +        printk("WARNING: UART configuration is not supported\n");
> +
> +    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->clock_hz = LINFLEX_CLK_FREQ;
> +    uart->baud = LINFLEX_BAUDRATE;
> +
> +    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:
> + */

Cheers,
Andrei Cherechesu Sept. 11, 2024, 7:50 p.m. UTC | #2
Hi, Julien, and thank you for the review!

On 11/09/2024 00:55, Julien Grall wrote:
> Hi,
>
> On 10/09/2024 15:34, Andrei Cherechesu (OSS) wrote:
>> 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.
>
> It looks like you need to create an account. Is there any public version of the specification?
>
Unfortunately, the Reference Manual requires registration on NXP website, as per company policy.
The only public resource I can provide is the Data Sheet: https://www.nxp.com/docs/en/data-sheet/S32G3.pdf.
>>
>> 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 |  62 ++++
>>   xen/drivers/char/Kconfig                |   8 +
>>   xen/drivers/char/Makefile               |   1 +
>>   xen/drivers/char/linflex-uart.c         | 365 ++++++++++++++++++++++++
>>   4 files changed, 436 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..62dc54d155
>> --- /dev/null
>> +++ b/xen/arch/arm/include/asm/linflex-uart.h
>> @@ -0,0 +1,62 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>
> The identifier GPL-2.0 was deprecated (see LICENSES/GPL-2.0). The new tag should be GPL-2.0-only. The resulting license is the same.
Will fix in v2.
>
>> +/*
>> + * 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      GENMASK(27, 24)
>> +
>> +#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..4ca8f732ae
>> --- /dev/null
>> +++ b/xen/drivers/char/linflex-uart.c
>> @@ -0,0 +1,365 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>
> Ditto.
Will fix in v2.
>
>
>> +/*
>> + * 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/serial.h>
>
> In Xen, we tend to order the include alphabetically within the same directory. So this wants to be after xen/mm.h.
Right, will address in v2.
>
>
>> +#include <xen/init.h>
>> +#include <xen/irq.h>
>> +#include <xen/mm.h>
>> +#include <asm/device.h>
>> +#include <asm/io.h>
>> +#include <asm/linflex-uart.h>
>> +
>> +#define LINFLEX_CLK_FREQ        (125000000)
>> +#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(struct linflex_uart *uart, uint32_t off)
>> +{
>> +    return readl(uart->regs + off);
>> +}
>> +
>> +static void linflex_uart_writel(struct linflex_uart *uart, uint32_t off,
>> +                                uint32_t val)
>> +{
>> +    writel(val, uart->regs + off);
>> +}
>> +
>> +static void linflex_uart_writeb(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) >> 24;
>
> Please provide a define for 24. This would also make easier to correlate with UARTCR_OSR.
Will address in v2.
>
>
>> +}
>> +
>> +static uint32_t linflex_uart_tx_fifo_mode(struct linflex_uart *uart)
>
> AFAICT, UARTCR_TFBM is one-bit. So should this return a bool?
Yes, it is one bit and I agree a bool would fit better. Will address in v2.
>
>
>> +{
>> +    return linflex_uart_readl(uart, UARTCR) & UARTCR_TFBM;
>> +}
>> +
>> +static uint32_t linflex_uart_rx_fifo_mode(struct linflex_uart *uart)
>
> Same here.
Will address in v2.
>
>> +{
>> +    return linflex_uart_readl(uart, UARTCR) & UARTCR_RFBM;
>> +}
>> +
>> +static uint32_t linflex_uart_ldiv_multiplier(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)
>> +{
>> +    struct linflex_uart *uart = port->uart;
>
> Above, youa re using tab hard but above you use soft tab. Is the code intended to follow Xen coding style? If so, you want to use soft tab.
Will fix in v2.
>
>
>> +
>> +    if ( linflex_uart_tx_fifo_mode(uart) )
>> +        while ( linflex_uart_readl(uart, UARTCR) & UARTCR_TDFLTFC );
>> +            cpu_relax();
>
> The indentation is really confusing here. It leads to think that cpu_relax() should be part of while() but you are using ';'. I guess you really intended to have cpu_relax() within the while loop?
Indeed, the intention was to have cpu_relax() called within the while loop, but functionally, this variant works almost the same. Thank you for spotting the mistake!
>
>
>> +
>> +    if ( linflex_uart_rx_fifo_mode(uart) )
>> +        while ( linflex_uart_readl(uart, UARTCR) & UARTCR_RDFLRFC );
>> +            cpu_relax();
>
> Same here.
>
>> +}
>> +
>> +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
>> +    */
>
> The indentation for comment in Xen is:
>
> /*
>  * Foor
>  * Bar
>  */
>
Will fix in v2.
>> +    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);
>> +
>> +    /* Provide data bits, parity, stop bit, etc */
>> +    divisr = uart->clock_hz;
>> +    dividr = (uint32_t)(uart->baud * linflex_uart_ldiv_multiplier(uart));
>> +
>> +    ibr = (uint32_t)(divisr / dividr);
>> +    fbr = (uint32_t)((divisr % dividr) * 16 / dividr) & 0xF;
>
> On the 3 lines above, why do you need to cast to 32-bit? Is this because the result is 64-bit? If so, why do you need to ignore the top bits?
>
Indeed, the casts are not needed. The maximum baud rate supported by LINFlexD is 2000000 bps, and the maximum value returned by linflex_uart_ldiv_multiplier() is 16.
Thus, "dividr" should never overflow (32000000 max value) and neither should the "fbr" computation. In v2, I will remove the casts and upper bound the baud rate to the maximum
supported value, if that's ok for you.
It would be also nice having a generic CONFIG_BAUDRATE (or similar) set by each platform, similar to U-Boot, to make this configurable.
>>
>> +    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;
>> +    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 )
>> +    {
>> +        dprintk(XENLOG_ERR, "Failed to allocate linflex_uart IRQ %d\n",
>> +                uart->irq);
>
> NIT: This should only be called once during boot. So I would consider to use printk() so it can be printed in production.
Will fix in v2.
>
>> +        return;
>> +    }
>> +
>> +    /* Enable interrupts */
>> +    temp = linflex_uart_readl(uart, LINIER);
>> +    temp |= (LINIER_DRIE | LINIER_DTIE);
>> +    linflex_uart_writel(uart, LINIER, temp);
>> +    dprintk(XENLOG_DEBUG, "IRQ %d enabled\n", uart->irq);
>
> Same here.
Will fix in v2.
>
>> +}
>> +
>> +static int linflex_uart_tx_ready(struct serial_port *port)
>> +{
>> +    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.
>> +    */
>
> Coding style. See above how it should be done for multi-line comments.
Will fix in v2.
>
>> +    return 1;
>> +}
>> +
>> +static void linflex_uart_putc(struct serial_port *port, char c)
>> +{
>> +    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)
>> +{
>> +    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)
>> +{
>> +    struct linflex_uart *uart = port->uart;
>> +
>> +    return ((uart->irq > 0) ? uart->irq : -1);
>> +}
>> +
>> +static const struct vuart_info *linflex_uart_vuart_info(
>> +                struct serial_port *port)
>> +{
>> +    struct linflex_uart *uart = port->uart;
>
> NIT: You are not modifying uart. So this coul be const.
Sure, but no other driver does this. Actually, this applies to most functions in this driver, right?
I can implement this in v2 if you agree to break the pattern.
>
>
>> +
>> +    return &uart->vuart;
>> +}
>> +
>> +static void linflex_uart_start_tx(struct serial_port *port)
>> +{
>> +    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)
>> +{
>> +    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_uart_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;
>> +    int res;
>> +
>> +    if ( strcmp(config, "") )
>> +        printk("WARNING: UART configuration is not supported\n");
>> +
>> +    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->clock_hz = LINFLEX_CLK_FREQ;
>> +    uart->baud = LINFLEX_BAUDRATE;
>> +
>> +    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:
>> + */
>
> Cheers,
>
Regards,
Andrei Cherechesu
diff mbox series

Patch

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..62dc54d155
--- /dev/null
+++ b/xen/arch/arm/include/asm/linflex-uart.h
@@ -0,0 +1,62 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * 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      GENMASK(27, 24)
+
+#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..4ca8f732ae
--- /dev/null
+++ b/xen/drivers/char/linflex-uart.c
@@ -0,0 +1,365 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * 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/serial.h>
+#include <xen/init.h>
+#include <xen/irq.h>
+#include <xen/mm.h>
+#include <asm/device.h>
+#include <asm/io.h>
+#include <asm/linflex-uart.h>
+
+#define LINFLEX_CLK_FREQ        (125000000)
+#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(struct linflex_uart *uart, uint32_t off)
+{
+    return readl(uart->regs + off);
+}
+
+static void linflex_uart_writel(struct linflex_uart *uart, uint32_t off,
+                                uint32_t val)
+{
+    writel(val, uart->regs + off);
+}
+
+static void linflex_uart_writeb(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) >> 24;
+}
+
+static uint32_t linflex_uart_tx_fifo_mode(struct linflex_uart *uart)
+{
+    return linflex_uart_readl(uart, UARTCR) & UARTCR_TFBM;
+}
+
+static uint32_t linflex_uart_rx_fifo_mode(struct linflex_uart *uart)
+{
+    return linflex_uart_readl(uart, UARTCR) & UARTCR_RFBM;
+}
+
+static uint32_t linflex_uart_ldiv_multiplier(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)
+{
+	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);
+
+    /* Provide data bits, parity, stop bit, etc */
+    divisr = uart->clock_hz;
+    dividr = (uint32_t)(uart->baud * linflex_uart_ldiv_multiplier(uart));
+
+    ibr = (uint32_t)(divisr / dividr);
+    fbr = (uint32_t)((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;
+    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 )
+    {
+        dprintk(XENLOG_ERR, "Failed to allocate linflex_uart 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);
+    dprintk(XENLOG_DEBUG, "IRQ %d enabled\n", uart->irq);
+}
+
+static int linflex_uart_tx_ready(struct serial_port *port)
+{
+    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)
+{
+    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)
+{
+    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)
+{
+    struct linflex_uart *uart = port->uart;
+
+    return ((uart->irq > 0) ? uart->irq : -1);
+}
+
+static const struct vuart_info *linflex_uart_vuart_info(
+                struct serial_port *port)
+{
+    struct linflex_uart *uart = port->uart;
+
+    return &uart->vuart;
+}
+
+static void linflex_uart_start_tx(struct serial_port *port)
+{
+    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)
+{
+    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_uart_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;
+    int res;
+
+    if ( strcmp(config, "") )
+        printk("WARNING: UART configuration is not supported\n");
+
+    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->clock_hz = LINFLEX_CLK_FREQ;
+    uart->baud = LINFLEX_BAUDRATE;
+
+    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:
+ */