diff mbox series

[2/2] xen/arm: Add i.MX UART driver

Message ID 20240402120557.1822253-3-olekstysh@gmail.com (mailing list archive)
State New, archived
Headers show
Series Add UART support for i.MX 8M Mini EVKB | expand

Commit Message

Oleksandr Tyshchenko April 2, 2024, 12:05 p.m. UTC
From: Oleksandr Tyshchenko <oleksandr_tyshchenko@epam.com>

The i.MX UART Documentation:
https://www.nxp.com/webapp/Download?colCode=IMX8MMRM
Chapter 16.2 Universal Asynchronous Receiver/Transmitter (UART)

Tested on i.MX 8M Mini only, but I guess, it should be
suitable for other i.MX8M* SoCs (those UART device tree nodes
contain "fsl,imx6q-uart" compatible string).

Signed-off-by: Oleksandr Tyshchenko <oleksandr_tyshchenko@epam.com>
---
I used the "earlycon=ec_imx6q,0x30890000" cmd arg and
selected CONFIG_SERIAL_IMX_EARLYCON in Linux for enabling vUART.
---
---
 MAINTAINERS                 |   1 +
 xen/drivers/char/Kconfig    |   7 +
 xen/drivers/char/Makefile   |   1 +
 xen/drivers/char/imx-uart.c | 299 ++++++++++++++++++++++++++++++++++++
 4 files changed, 308 insertions(+)
 create mode 100644 xen/drivers/char/imx-uart.c

Comments

Michal Orzel April 4, 2024, 6:54 a.m. UTC | #1
Hi Oleksandr,

On 02/04/2024 14:05, Oleksandr Tyshchenko wrote:
> 
> 
> From: Oleksandr Tyshchenko <oleksandr_tyshchenko@epam.com>
> 
> The i.MX UART Documentation:
> https://www.nxp.com/webapp/Download?colCode=IMX8MMRM
> Chapter 16.2 Universal Asynchronous Receiver/Transmitter (UART)
> 
> Tested on i.MX 8M Mini only, but I guess, it should be
imperative mood

> suitable for other i.MX8M* SoCs (those UART device tree nodes
> contain "fsl,imx6q-uart" compatible string).
> 
> Signed-off-by: Oleksandr Tyshchenko <oleksandr_tyshchenko@epam.com>
> ---
> I used the "earlycon=ec_imx6q,0x30890000" cmd arg and
> selected CONFIG_SERIAL_IMX_EARLYCON in Linux for enabling vUART.
> ---
> ---
>  MAINTAINERS                 |   1 +
>  xen/drivers/char/Kconfig    |   7 +
>  xen/drivers/char/Makefile   |   1 +
>  xen/drivers/char/imx-uart.c | 299 ++++++++++++++++++++++++++++++++++++
>  4 files changed, 308 insertions(+)
>  create mode 100644 xen/drivers/char/imx-uart.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 1bd22fd75f..bd4084fd20 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -249,6 +249,7 @@ F:  xen/drivers/char/arm-uart.c
>  F:     xen/drivers/char/cadence-uart.c
>  F:     xen/drivers/char/exynos4210-uart.c
>  F:     xen/drivers/char/imx-lpuart.c
> +F:     xen/drivers/char/imx-uart.c
>  F:     xen/drivers/char/meson-uart.c
>  F:     xen/drivers/char/mvebu-uart.c
>  F:     xen/drivers/char/omap-uart.c
> diff --git a/xen/drivers/char/Kconfig b/xen/drivers/char/Kconfig
> index e18ec3788c..f51a1f596a 100644
> --- a/xen/drivers/char/Kconfig
> +++ b/xen/drivers/char/Kconfig
> @@ -20,6 +20,13 @@ config HAS_IMX_LPUART
>         help
>           This selects the i.MX LPUART. If you have i.MX8QM based board, say Y.
> 
> +config HAS_IMX_UART
> +       bool "i.MX UART driver"
> +       default y
> +       depends on ARM_64
> +       help
> +         This selects the i.MX UART. If you have i.MX8M* based board, say Y.
> +
>  config HAS_MVEBU
>         bool "Marvell MVEBU UART driver"
>         default y
> diff --git a/xen/drivers/char/Makefile b/xen/drivers/char/Makefile
> index e7e374775d..147530a1ed 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_IMX_UART) += imx-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/imx-uart.c b/xen/drivers/char/imx-uart.c
> new file mode 100644
> index 0000000000..13bb189063
> --- /dev/null
> +++ b/xen/drivers/char/imx-uart.c
> @@ -0,0 +1,299 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
Can it be GPL-2.0-only? Some companies are worried about v3.

> +/*
> + * xen/drivers/char/imx-uart.c
> + *
> + * Driver for i.MX UART.
> + *
> + * Based on Linux's drivers/tty/serial/imx.c
> + *
> + * Copyright (C) 2024 EPAM Systems Inc.
> + */
> +
> +#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/imx-uart.h>
> +#include <asm/io.h>
> +
> +#define imx_uart_read(uart, off)          readl((uart)->regs + (off))
> +#define imx_uart_write(uart, off, val)    writel((val), (uart)->regs + (off))
> +
> +static struct imx_uart {
> +    uint32_t baud, clock_hz, data_bits, parity, stop_bits, fifo_size;
What's the use of these variables? AFAICT they are set but never used.

> +    uint32_t irq;
unsigned int

> +    char __iomem *regs;
> +    struct irqaction irqaction;
> +    struct vuart_info vuart;
> +} imx_com;
> +
> +static void imx_uart_interrupt(int irq, void *data)
> +{
> +    struct serial_port *port = data;
> +    struct imx_uart *uart = port->uart;
> +    uint32_t usr1, usr2;
> +
> +    usr1 = imx_uart_read(uart, USR1);
> +    usr2 = imx_uart_read(uart, USR2);
> +
> +    if ( usr1 & (USR1_RRDY | USR1_AGTIM) )
> +    {
> +        imx_uart_write(uart, USR1, USR1_AGTIM);
> +        serial_rx_interrupt(port);
> +    }
> +
> +    if ( (usr1 & USR1_TRDY) || (usr2 & USR2_TXDC) )
> +        serial_tx_interrupt(port);
> +}
> +
> +static void imx_uart_clear_rx_errors(struct serial_port *port)
> +{
> +    struct imx_uart *uart = port->uart;
> +    uint32_t usr1, usr2;
> +
> +    usr1 = imx_uart_read(uart, USR1);
> +    usr2 = imx_uart_read(uart, USR2);
> +
> +    if ( usr2 & USR2_BRCD )
> +        imx_uart_write(uart, USR2, USR2_BRCD);
> +    else if ( usr1 & USR1_FRAMERR )
> +        imx_uart_write(uart, USR1, USR1_FRAMERR);
> +    else if ( usr1 & USR1_PARITYERR )
> +        imx_uart_write(uart, USR1, USR1_PARITYERR);
> +
> +    if ( usr2 & USR2_ORE )
> +        imx_uart_write(uart, USR2, USR2_ORE);
> +}
> +
> +static void __init imx_uart_init_preirq(struct serial_port *port)
> +{
> +    struct imx_uart *uart = port->uart;
> +    uint32_t reg;
> +
> +    /*
> +     * Wait for the transmission to complete. This is needed for a smooth
> +     * transition when we come from early printk.
> +     */
> +    while ( !(imx_uart_read(uart, USR2) & USR2_TXDC) )
> +        cpu_relax();
> +
> +    /* Set receiver/transmitter trigger level */
> +    reg = imx_uart_read(uart, UFCR);
> +    reg &= (UFCR_RFDIV | UFCR_DCEDTE);
> +    reg |= TXTL_DEFAULT << UFCR_TXTL_SHF | RXTL_DEFAULT;
please surround TXTL_DEFAULT << UFCR_TXTL_SHF with parentheses

> +    imx_uart_write(uart, UFCR, reg);
> +
> +    /* Enable UART and disable interrupts/DMA */
> +    reg = imx_uart_read(uart, UCR1);
> +    reg |= UCR1_UARTEN;
> +    reg &= ~(UCR1_TRDYEN | UCR1_TXMPTYEN | UCR1_RTSDEN | UCR1_RRDYEN |
> +             UCR1_RXDMAEN | UCR1_TXDMAEN | UCR1_ATDMAEN);
> +    imx_uart_write(uart, UCR1, reg);
> +
> +    /* Enable receiver/transmitter and disable Aging Timer */
> +    reg = imx_uart_read(uart, UCR2);
> +    reg |= UCR2_RXEN | UCR2_TXEN;
> +    reg &= ~UCR2_ATEN;
> +    imx_uart_write(uart, UCR2, reg);
> +
> +    /* Disable interrupts */
> +    reg = imx_uart_read(uart, UCR4);
> +    reg &= ~(UCR4_TCEN | UCR4_DREN);
> +    imx_uart_write(uart, UCR4, reg);
> +}
> +
> +static void __init imx_uart_init_postirq(struct serial_port *port)
> +{
> +    struct imx_uart *uart = port->uart;
> +
> +    uart->irqaction.handler = imx_uart_interrupt;
> +    uart->irqaction.name = "imx_uart";
> +    uart->irqaction.dev_id = port;
> +
> +    if ( setup_irq(uart->irq, 0, &uart->irqaction) != 0 )
> +    {
> +        dprintk(XENLOG_ERR, "Failed to allocate imx_uart IRQ %d\n", uart->irq);
Wrong format specifier for unsigned, use %u
Also, why dprintk?

> +        return;
> +    }
> +
> +    /* Clear possible receiver errors */
> +    imx_uart_clear_rx_errors(port);
> +
> +    /* Enable interrupts */
> +    imx_uart_write(uart, UCR1, imx_uart_read(uart, UCR1) |
> +                   UCR1_RRDYEN | UCR1_TRDYEN);
> +    imx_uart_write(uart, UCR2, imx_uart_read(uart, UCR2) | UCR2_ATEN);
> +}
> +
> +static void imx_uart_suspend(struct serial_port *port)
> +{
> +    BUG();
> +}
no need for this function

> +
> +static void imx_uart_resume(struct serial_port *port)
> +{
> +    BUG();
> +}
no need for this function

> +
> +static int imx_uart_tx_ready(struct serial_port *port)
> +{
> +    struct imx_uart *uart = port->uart;
can be const

> +    uint32_t reg;
> +
> +    reg = imx_uart_read(uart, IMX21_UTS);
> +    if ( reg & UTS_TXEMPTY )
> +        return TX_FIFO_SIZE;
empty line here please

> +    if ( reg & UTS_TXFULL )
> +        return 0;
> +
> +    /*
> +     * If the FIFO is neither full nor empty then there is a space for
> +     * one char at least.
> +     */
> +    return 1;
> +}
> +
> +static void imx_uart_putc(struct serial_port *port, char c)
> +{
> +    struct imx_uart *uart = port->uart;
can be const

> +
> +    while ( imx_uart_read(uart, IMX21_UTS) & UTS_TXFULL )
> +        cpu_relax();
Looking at pl011, cdns, putc contains only a write to data register. This is because
->putc always follows ->tx_ready which contains the check for how many chars can be written.
Therefore, AFAICT this should be dropped.

> +
> +    imx_uart_write(uart, URTX0, c);
> +}
> +
> +static int imx_uart_getc(struct serial_port *port, char *pc)
> +{
> +    struct imx_uart *uart = port->uart;
can be const

> +    uint32_t data;
> +
> +    if ( !(imx_uart_read(uart, USR2) & USR2_RDR) )
> +        return 0;
> +
> +    data = imx_uart_read(uart, URXD0);
> +    *pc = data & URXD_RX_DATA;
> +
> +    if ( unlikely(data & URXD_ERR) )
> +        imx_uart_clear_rx_errors(port);
> +
> +    return 1;
> +}
> +
> +static int __init imx_uart_irq(struct serial_port *port)
> +{
> +    struct imx_uart *uart = port->uart;
can be const

> +
> +    return ((uart->irq > 0) ? uart->irq : -1);
> +}
> +
> +static const struct vuart_info *imx_uart_vuart_info(struct serial_port *port)
> +{
> +    struct imx_uart *uart = port->uart;
can be const

> +
> +    return &uart->vuart;
> +}
> +
> +static void imx_uart_start_tx(struct serial_port *port)
> +{
> +    struct imx_uart *uart = port->uart;
> +
> +    imx_uart_write(uart, UCR1, imx_uart_read(uart, UCR1) | UCR1_TRDYEN);
> +}
> +
> +static void imx_uart_stop_tx(struct serial_port *port)
> +{
> +    struct imx_uart *uart = port->uart;
> +
> +    imx_uart_write(uart, UCR1, imx_uart_read(uart, UCR1) & ~UCR1_TRDYEN);
> +}
> +
> +static struct uart_driver __read_mostly imx_uart_driver = {
> +    .init_preirq = imx_uart_init_preirq,
> +    .init_postirq = imx_uart_init_postirq,
> +    .endboot = NULL,
> +    .suspend = imx_uart_suspend,
> +    .resume = imx_uart_resume,
no need to assign a value for these 3 members

> +    .tx_ready = imx_uart_tx_ready,
> +    .putc = imx_uart_putc,
> +    .getc = imx_uart_getc,
> +    .irq = imx_uart_irq,
> +    .start_tx = imx_uart_start_tx,
> +    .stop_tx = imx_uart_stop_tx,
> +    .vuart_info = imx_uart_vuart_info,
> +};
> +
> +static int __init imx_uart_init(struct dt_device_node *dev, const void *data)
> +{
> +    const char *config = data;
> +    struct imx_uart *uart;
> +    int res;
> +    paddr_t addr, size;
> +
> +    if ( strcmp(config, "") )
> +        printk("WARNING: UART configuration is not supported\n");
> +
> +    uart = &imx_com;
> +
> +    uart->baud = 115200;
> +    uart->data_bits = 8;
> +    uart->parity = 0;
> +    uart->stop_bits = 1;
They are set but never used

> +
> +    res = dt_device_get_paddr(dev, 0, &addr, &size);
> +    if ( res )
> +    {
> +        printk("imx-uart: Unable to retrieve the base address of the UART\n");
> +        return res;
> +    }
> +
> +    res = platform_get_irq(dev, 0);
> +    if ( res < 0 )
> +    {
> +        printk("imx-uart: Unable to retrieve the IRQ\n");
> +        return -EINVAL;
> +    }
> +    uart->irq = res;
> +
> +    uart->regs = ioremap_nocache(addr, size);
> +    if ( !uart->regs )
> +    {
> +        printk("imx-uart: Unable to map the UART memory\n");
> +        return -ENOMEM;
> +    }
> +
> +    uart->vuart.base_addr = addr;
> +    uart->vuart.size = size;
> +    uart->vuart.data_off = URTX0;
> +    uart->vuart.status_off = IMX21_UTS;
> +    uart->vuart.status = UTS_TXEMPTY;
> +
> +    /* Register with generic serial driver */
> +    serial_register_uart(SERHND_DTUART, &imx_uart_driver, uart);
> +
> +    dt_device_set_used_by(dev, DOMID_XEN);
> +
> +    return 0;
> +}
> +
> +static const struct dt_device_match imx_uart_dt_compat[] __initconst =
> +{
> +    DT_MATCH_COMPATIBLE("fsl,imx6q-uart"),
> +    { /* sentinel */ },
> +};
> +
> +DT_DEVICE_START(imx_uart, "i.MX UART", DEVICE_SERIAL)
> +    .dt_match = imx_uart_dt_compat,
> +    .init = imx_uart_init,
> +DT_DEVICE_END
> +
> +/*
> + * Local variables:
> + * mode: C
> + * c-file-style: "BSD"
> + * c-basic-offset: 4
> + * indent-tabs-mode: nil
> + * End:
> + */
> --
> 2.34.1
> 

~Michal
Peng Fan April 7, 2024, 2:43 a.m. UTC | #2
Hi Oleksandr,

> Subject: [PATCH 2/2] xen/arm: Add i.MX UART driver
> 
> From: Oleksandr Tyshchenko <oleksandr_tyshchenko@epam.com>
> 
> The i.MX UART Documentation:
> https://eur01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.
> nxp.com%2Fwebapp%2FDownload%3FcolCode%3DIMX8MMRM&data=05%7
> C02%7Cpeng.fan%40nxp.com%7C6ada06c4133849667f3608dc530d5471%7
> C686ea1d3bc2b4c6fa92cd99c5c301635%7C0%7C0%7C6384765639197564
> 70%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMz
> IiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C0%7C%7C%7C&sdata=RmXgAMb7
> wFZ7epZgYgHJo4LH35rzQhD05yTXSkttXbc%3D&reserved=0
> Chapter 16.2 Universal Asynchronous Receiver/Transmitter (UART)
> 
> Tested on i.MX 8M Mini only, but I guess, it should be suitable for other
> i.MX8M* SoCs (those UART device tree nodes contain "fsl,imx6q-uart"
> compatible string).

Good to see people are interested in XEN on 8M.
I had an implementation back in 2015, you could take a look.

#include <xen/config.h>
#include <xen/console.h>
#include <xen/serial.h>
#include <xen/init.h>
#include <xen/irq.h>
#include <xen/device_tree.h>
#include <asm/device.h>
#include <xen/errno.h>
#include <xen/mm.h>
#include <xen/vmap.h>
#include <asm/imx-uart.h>
#include <asm/io.h>

#define imx_read(uart, off)       readl((uart)->regs + off)
#define imx_write(uart, off, val) writel((val), (uart)->regs + off)

#define CONFIG_IMX8_ZEBU
static struct imx_uart {
    unsigned int baud, clock_hz, data_bits, parity, stop_bits, fifo_size;
    unsigned int irq;
    char __iomem *regs;
    struct irqaction irqaction;
    struct vuart_info vuart;
} imx_com = {0};

static void imx_uart_interrupt(int irq, void *data, struct cpu_user_regs *regs)
{
    struct serial_port *port = data;
    struct imx_uart *uart = port->uart;
    unsigned int sts, sts2;

    sts = imx_read(uart, USR1);

    if ((sts & USR1_RRDY) || (sts & USR1_AGTIM)) {
        if (sts & USR1_AGTIM)
            imx_write(uart, USR1, USR1_AGTIM);
	serial_rx_interrupt(port, regs);
    }

    if ((sts & USR1_TRDY) && (imx_read(uart, UCR1) & UCR1_TXMPTYEN)) {
        serial_tx_interrupt(port, regs);
    }

    if (sts & USR1_AWAKE) {
        imx_write(uart, USR1, USR1_AWAKE);
    }

    if (sts & USR1_AIRINT) {
	    imx_write(uart, USR1, USR1_AIRINT);
    }

    sts2 = imx_read(uart, USR2);
    if (sts2 & USR2_ORE) {
        dprintk(XENLOG_ERR, "uart: rxfifo overrun\n");
	imx_write(uart, USR2, sts2 | USR2_ORE);
    }
}

static void __init imx_uart_init_preirq(struct serial_port *port)
{
    struct imx_uart *uart = port->uart;
    int val;

    dprintk(XENLOG_ERR, "10\n");
    imx_write(uart, USR1, USR1_RTSD);

    dprintk(XENLOG_ERR, "12\n");
    val = imx_read(uart, UCR1);
    val |= UCR1_RRDYEN | UCR1_RTSDEN | UCR1_UARTEN;
    imx_write(uart, UCR1, val);

    dprintk(XENLOG_ERR, "13\n");
    val = imx_read(uart, UCR2);
    val |= UCR2_RXEN | UCR2_TXEN | UCR2_IRTS;
    imx_write(uart, UCR2, val);

//#ifdef CONFIG_IMX8_ZEBU
#if 0
    imx_write(uart, UFCR, (2U << 10) | (5U << 7) || (0U << 6) | (1U << 0));
#else
#define TXTL 2 /* reset default */
#define RXTL 1 /* For console port */
#define RXTL_UART 16 /* For uart */
    val = imx_read(uart, UFCR) & (UFCR_RFDIV | UFCR_DCEDTE);
    val |= TXTL << UFCR_TXTL_SHF | RXTL;
    imx_write(uart, UFCR, val);
#endif
    dprintk(XENLOG_ERR, "14\n");
    dprintk(XENLOG_ERR, "===============UFCR USR1 %x %x\n", imx_read(uart, UFCR), imx_read(uart, USR1));
}

static void __init imx_uart_init_postirq(struct serial_port *port)
{
    struct imx_uart *uart = port->uart;

    dprintk(XENLOG_ERR, "20\n");
    uart->irqaction.handler = imx_uart_interrupt;
    uart->irqaction.name = "imx_uart";
    uart->irqaction.dev_id = port;

    if ( setup_irq(uart->irq, 0, &uart->irqaction) != 0 )
    {
        dprintk(XENLOG_ERR, "Failed to allocated imx_uart IRQ %d\n",
                uart->irq);
        return;
    }

#ifdef CONFIG_IMX8_ZEBU
    // Send autobaud character
    imx_write(uart, UTXD, 0x55);
    imx_write(uart, UTXD, 0x55);
    imx_write(uart, UTXD, 0x0A);
#endif
    /* Enable interrupts */
    dprintk(XENLOG_ERR, "%s\n", __func__);
    imx_write(uart, UCR1, UCR1_TXMPTYEN | UCR1_RRDYEN | UCR1_UARTEN);
    //imx_write(uart, UCR1, UCR1_TXMPTYEN | UCR1_UARTEN);
}

static void imx_uart_suspend(struct serial_port *port)
{
    BUG();
}

static void imx_uart_resume(struct serial_port *port)
{
    BUG();
}

static int imx_uart_tx_ready(struct serial_port *port)
{
    struct imx_uart *uart = port->uart;

    return (imx_read(uart, USR2) & USR2_TXDC) ?  1 : 0;
}

static void imx_uart_putc(struct serial_port *port, char c)
{
    struct imx_uart *uart = port->uart;

    imx_write(uart, UTXD, (uint32_t)(unsigned char)c);
}

static int imx_uart_getc(struct serial_port *port, char *pc)
{
    struct imx_uart *uart = port->uart;

    if (!(imx_read(uart, USR2) & USR2_RDR))
	return 0;

    *pc = imx_read(uart, URXD) & 0xff;

    return 1;
}

static int __init imx_uart_irq(struct serial_port *port)
{
    struct imx_uart *uart = port->uart;

    return ((uart->irq > 0) ? uart->irq : -1);
}

static const struct vuart_info *imx_vuart_info(struct serial_port *port)
{
    struct imx_uart *uart = port->uart;

    return &uart->vuart;
}

static void imx_start_tx(struct serial_port *port)
{
    struct imx_uart *uart = port->uart;
    unsigned long temp;

    /* Clear any pending ORE flag before enabling interrupt */
    temp = imx_read(uart, USR2);
    imx_write(uart, USR2, temp | USR2_ORE);

    temp = imx_read(uart, UCR4);
    temp |= UCR4_OREN;
    imx_write(uart, UCR4, temp);

    temp = imx_read(uart, UCR1);
    imx_write(uart, UCR1, temp | UCR1_TXMPTYEN);

    return;
}

static void imx_stop_tx(struct serial_port *port)
{
    struct imx_uart *uart = port->uart;
    unsigned long temp;

    temp = imx_read(uart, UCR1);
    imx_write(uart, UCR1, temp & ~UCR1_TXMPTYEN);

    return;
}

static struct uart_driver __read_mostly imx_uart_driver = {
    .init_preirq = imx_uart_init_preirq,
    .init_postirq = imx_uart_init_postirq,
    .endboot = NULL,
    .suspend = imx_uart_suspend,
    .resume = imx_uart_resume,
    .tx_ready = imx_uart_tx_ready,
    .putc = imx_uart_putc,
    .getc = imx_uart_getc,
    .irq = imx_uart_irq,
    .start_tx = imx_start_tx,
    .stop_tx = imx_stop_tx,
    .vuart_info = imx_vuart_info,
};

static int __init imx_uart_init(struct dt_device_node *dev,
				const void *data)
{
    const char *config = data;
    struct imx_uart *uart;
    u32 clkspec;
    int res;
    u64 addr, size;

    dprintk(XENLOG_ERR, "xx %x\n", EARLY_UART_BASE_ADDRESS);
    if ( strcmp(config, "") )
        printk("WARNING: UART configuration is not supported\n");

    uart = &imx_com;

#ifdef CONFIG_IMX8_ZEBU
    clkspec = 24000000;
#else
    res = dt_property_read_u32(dev, "clock-frequency", &clkspec);
    if ( !res )
    {
        printk("imx-uart: Unable to retrieve the clock frequency\n");
        return -EINVAL;
    }
#endif

#define PARITY_NONE  (0)
    uart->clock_hz = clkspec;
    uart->baud = 115200;
    uart->data_bits = 8;
    uart->parity = PARITY_NONE;
    uart->stop_bits = 1;

    res = dt_device_get_address(dev, 0, &addr, &size);
    if ( res )
    {
        printk("imx-uart: Unable to retrieve the base"
               " address of the UART\n");
        return res;
    }

    res = platform_get_irq(dev, 0);
    if ( res < 0 )
    {
        printk("imx-uart: Unable to retrieve the IRQ\n");
        return -EINVAL;
    }
    uart->irq = res;

    uart->regs = ioremap_nocache(addr, size);
    if ( !uart->regs )
    {
        printk("imx-uart: Unable to map the UART memory\n");
        return -ENOMEM;
    }


    uart->vuart.base_addr = addr;
    uart->vuart.size = size;
    uart->vuart.data_off = UTXD;
    /* tmp from uboot */
    uart->vuart.status_off = UTS;
    uart->vuart.status = UTS_TXEMPTY;

    dprintk(XENLOG_ERR, "11\n");
    /* Register with generic serial driver */
    serial_register_uart(SERHND_DTUART, &imx_uart_driver, uart);

    dt_device_set_used_by(dev, DOMID_XEN);

    return 0;
}

static const struct dt_device_match imx_uart_dt_compat[] __initconst =
{
    DT_MATCH_COMPATIBLE("fsl,imx21-uart"),
    {},
};

DT_DEVICE_START(imx_uart, "IMX UART", DEVICE_SERIAL)
    .dt_match = imx_uart_dt_compat,
    .init = imx_uart_init,
DT_DEVICE_END

Regards,
Peng.
> 
> Signed-off-by: Oleksandr Tyshchenko <oleksandr_tyshchenko@epam.com>
> ---
> I used the "earlycon=ec_imx6q,0x30890000" cmd arg and selected
> CONFIG_SERIAL_IMX_EARLYCON in Linux for enabling vUART.
> ---
> ---
>  MAINTAINERS                 |   1 +
>  xen/drivers/char/Kconfig    |   7 +
>  xen/drivers/char/Makefile   |   1 +
>  xen/drivers/char/imx-uart.c | 299 ++++++++++++++++++++++++++++++++++++
>  4 files changed, 308 insertions(+)
>  create mode 100644 xen/drivers/char/imx-uart.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 1bd22fd75f..bd4084fd20 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -249,6 +249,7 @@ F:	xen/drivers/char/arm-uart.c
>  F:	xen/drivers/char/cadence-uart.c
>  F:	xen/drivers/char/exynos4210-uart.c
>  F:	xen/drivers/char/imx-lpuart.c
> +F:	xen/drivers/char/imx-uart.c
>  F:	xen/drivers/char/meson-uart.c
>  F:	xen/drivers/char/mvebu-uart.c
>  F:	xen/drivers/char/omap-uart.c
> diff --git a/xen/drivers/char/Kconfig b/xen/drivers/char/Kconfig index
> e18ec3788c..f51a1f596a 100644
> --- a/xen/drivers/char/Kconfig
> +++ b/xen/drivers/char/Kconfig
> @@ -20,6 +20,13 @@ config HAS_IMX_LPUART
>  	help
>  	  This selects the i.MX LPUART. If you have i.MX8QM based board,
> say Y.
> 
> +config HAS_IMX_UART
> +	bool "i.MX UART driver"
> +	default y
> +	depends on ARM_64
> +	help
> +	  This selects the i.MX UART. If you have i.MX8M* based board, say Y.
> +
>  config HAS_MVEBU
>  	bool "Marvell MVEBU UART driver"
>  	default y
> diff --git a/xen/drivers/char/Makefile b/xen/drivers/char/Makefile index
> e7e374775d..147530a1ed 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_IMX_UART) += imx-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/imx-uart.c b/xen/drivers/char/imx-uart.c new file mode
> 100644 index 0000000000..13bb189063
> --- /dev/null
> +++ b/xen/drivers/char/imx-uart.c
> @@ -0,0 +1,299 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * xen/drivers/char/imx-uart.c
> + *
> + * Driver for i.MX UART.
> + *
> + * Based on Linux's drivers/tty/serial/imx.c
> + *
> + * Copyright (C) 2024 EPAM Systems Inc.
> + */
> +
> +#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/imx-uart.h>
> +#include <asm/io.h>
> +
> +#define imx_uart_read(uart, off)          readl((uart)->regs + (off))
> +#define imx_uart_write(uart, off, val)    writel((val), (uart)->regs + (off))
> +
> +static struct imx_uart {
> +    uint32_t baud, clock_hz, data_bits, parity, stop_bits, fifo_size;
> +    uint32_t irq;
> +    char __iomem *regs;
> +    struct irqaction irqaction;
> +    struct vuart_info vuart;
> +} imx_com;
> +
> +static void imx_uart_interrupt(int irq, void *data) {
> +    struct serial_port *port = data;
> +    struct imx_uart *uart = port->uart;
> +    uint32_t usr1, usr2;
> +
> +    usr1 = imx_uart_read(uart, USR1);
> +    usr2 = imx_uart_read(uart, USR2);
> +
> +    if ( usr1 & (USR1_RRDY | USR1_AGTIM) )
> +    {
> +        imx_uart_write(uart, USR1, USR1_AGTIM);
> +        serial_rx_interrupt(port);
> +    }
> +
> +    if ( (usr1 & USR1_TRDY) || (usr2 & USR2_TXDC) )
> +        serial_tx_interrupt(port);
> +}
> +
> +static void imx_uart_clear_rx_errors(struct serial_port *port) {
> +    struct imx_uart *uart = port->uart;
> +    uint32_t usr1, usr2;
> +
> +    usr1 = imx_uart_read(uart, USR1);
> +    usr2 = imx_uart_read(uart, USR2);
> +
> +    if ( usr2 & USR2_BRCD )
> +        imx_uart_write(uart, USR2, USR2_BRCD);
> +    else if ( usr1 & USR1_FRAMERR )
> +        imx_uart_write(uart, USR1, USR1_FRAMERR);
> +    else if ( usr1 & USR1_PARITYERR )
> +        imx_uart_write(uart, USR1, USR1_PARITYERR);
> +
> +    if ( usr2 & USR2_ORE )
> +        imx_uart_write(uart, USR2, USR2_ORE); }
> +
> +static void __init imx_uart_init_preirq(struct serial_port *port) {
> +    struct imx_uart *uart = port->uart;
> +    uint32_t reg;
> +
> +    /*
> +     * Wait for the transmission to complete. This is needed for a smooth
> +     * transition when we come from early printk.
> +     */
> +    while ( !(imx_uart_read(uart, USR2) & USR2_TXDC) )
> +        cpu_relax();
> +
> +    /* Set receiver/transmitter trigger level */
> +    reg = imx_uart_read(uart, UFCR);
> +    reg &= (UFCR_RFDIV | UFCR_DCEDTE);
> +    reg |= TXTL_DEFAULT << UFCR_TXTL_SHF | RXTL_DEFAULT;
> +    imx_uart_write(uart, UFCR, reg);
> +
> +    /* Enable UART and disable interrupts/DMA */
> +    reg = imx_uart_read(uart, UCR1);
> +    reg |= UCR1_UARTEN;
> +    reg &= ~(UCR1_TRDYEN | UCR1_TXMPTYEN | UCR1_RTSDEN |
> UCR1_RRDYEN |
> +             UCR1_RXDMAEN | UCR1_TXDMAEN | UCR1_ATDMAEN);
> +    imx_uart_write(uart, UCR1, reg);
> +
> +    /* Enable receiver/transmitter and disable Aging Timer */
> +    reg = imx_uart_read(uart, UCR2);
> +    reg |= UCR2_RXEN | UCR2_TXEN;
> +    reg &= ~UCR2_ATEN;
> +    imx_uart_write(uart, UCR2, reg);
> +
> +    /* Disable interrupts */
> +    reg = imx_uart_read(uart, UCR4);
> +    reg &= ~(UCR4_TCEN | UCR4_DREN);
> +    imx_uart_write(uart, UCR4, reg);
> +}
> +
> +static void __init imx_uart_init_postirq(struct serial_port *port) {
> +    struct imx_uart *uart = port->uart;
> +
> +    uart->irqaction.handler = imx_uart_interrupt;
> +    uart->irqaction.name = "imx_uart";
> +    uart->irqaction.dev_id = port;
> +
> +    if ( setup_irq(uart->irq, 0, &uart->irqaction) != 0 )
> +    {
> +        dprintk(XENLOG_ERR, "Failed to allocate imx_uart IRQ %d\n", uart->irq);
> +        return;
> +    }
> +
> +    /* Clear possible receiver errors */
> +    imx_uart_clear_rx_errors(port);
> +
> +    /* Enable interrupts */
> +    imx_uart_write(uart, UCR1, imx_uart_read(uart, UCR1) |
> +                   UCR1_RRDYEN | UCR1_TRDYEN);
> +    imx_uart_write(uart, UCR2, imx_uart_read(uart, UCR2) | UCR2_ATEN);
> +}
> +
> +static void imx_uart_suspend(struct serial_port *port) {
> +    BUG();
> +}
> +
> +static void imx_uart_resume(struct serial_port *port) {
> +    BUG();
> +}
> +
> +static int imx_uart_tx_ready(struct serial_port *port) {
> +    struct imx_uart *uart = port->uart;
> +    uint32_t reg;
> +
> +    reg = imx_uart_read(uart, IMX21_UTS);
> +    if ( reg & UTS_TXEMPTY )
> +        return TX_FIFO_SIZE;
> +    if ( reg & UTS_TXFULL )
> +        return 0;
> +
> +    /*
> +     * If the FIFO is neither full nor empty then there is a space for
> +     * one char at least.
> +     */
> +    return 1;
> +}
> +
> +static void imx_uart_putc(struct serial_port *port, char c) {
> +    struct imx_uart *uart = port->uart;
> +
> +    while ( imx_uart_read(uart, IMX21_UTS) & UTS_TXFULL )
> +        cpu_relax();
> +
> +    imx_uart_write(uart, URTX0, c);
> +}
> +
> +static int imx_uart_getc(struct serial_port *port, char *pc) {
> +    struct imx_uart *uart = port->uart;
> +    uint32_t data;
> +
> +    if ( !(imx_uart_read(uart, USR2) & USR2_RDR) )
> +        return 0;
> +
> +    data = imx_uart_read(uart, URXD0);
> +    *pc = data & URXD_RX_DATA;
> +
> +    if ( unlikely(data & URXD_ERR) )
> +        imx_uart_clear_rx_errors(port);
> +
> +    return 1;
> +}
> +
> +static int __init imx_uart_irq(struct serial_port *port) {
> +    struct imx_uart *uart = port->uart;
> +
> +    return ((uart->irq > 0) ? uart->irq : -1); }
> +
> +static const struct vuart_info *imx_uart_vuart_info(struct serial_port
> +*port) {
> +    struct imx_uart *uart = port->uart;
> +
> +    return &uart->vuart;
> +}
> +
> +static void imx_uart_start_tx(struct serial_port *port) {
> +    struct imx_uart *uart = port->uart;
> +
> +    imx_uart_write(uart, UCR1, imx_uart_read(uart, UCR1) |
> +UCR1_TRDYEN); }
> +
> +static void imx_uart_stop_tx(struct serial_port *port) {
> +    struct imx_uart *uart = port->uart;
> +
> +    imx_uart_write(uart, UCR1, imx_uart_read(uart, UCR1) &
> +~UCR1_TRDYEN); }
> +
> +static struct uart_driver __read_mostly imx_uart_driver = {
> +    .init_preirq = imx_uart_init_preirq,
> +    .init_postirq = imx_uart_init_postirq,
> +    .endboot = NULL,
> +    .suspend = imx_uart_suspend,
> +    .resume = imx_uart_resume,
> +    .tx_ready = imx_uart_tx_ready,
> +    .putc = imx_uart_putc,
> +    .getc = imx_uart_getc,
> +    .irq = imx_uart_irq,
> +    .start_tx = imx_uart_start_tx,
> +    .stop_tx = imx_uart_stop_tx,
> +    .vuart_info = imx_uart_vuart_info,
> +};
> +
> +static int __init imx_uart_init(struct dt_device_node *dev, const void
> +*data) {
> +    const char *config = data;
> +    struct imx_uart *uart;
> +    int res;
> +    paddr_t addr, size;
> +
> +    if ( strcmp(config, "") )
> +        printk("WARNING: UART configuration is not supported\n");
> +
> +    uart = &imx_com;
> +
> +    uart->baud = 115200;
> +    uart->data_bits = 8;
> +    uart->parity = 0;
> +    uart->stop_bits = 1;
> +
> +    res = dt_device_get_paddr(dev, 0, &addr, &size);
> +    if ( res )
> +    {
> +        printk("imx-uart: Unable to retrieve the base address of the UART\n");
> +        return res;
> +    }
> +
> +    res = platform_get_irq(dev, 0);
> +    if ( res < 0 )
> +    {
> +        printk("imx-uart: Unable to retrieve the IRQ\n");
> +        return -EINVAL;
> +    }
> +    uart->irq = res;
> +
> +    uart->regs = ioremap_nocache(addr, size);
> +    if ( !uart->regs )
> +    {
> +        printk("imx-uart: Unable to map the UART memory\n");
> +        return -ENOMEM;
> +    }
> +
> +    uart->vuart.base_addr = addr;
> +    uart->vuart.size = size;
> +    uart->vuart.data_off = URTX0;
> +    uart->vuart.status_off = IMX21_UTS;
> +    uart->vuart.status = UTS_TXEMPTY;
> +
> +    /* Register with generic serial driver */
> +    serial_register_uart(SERHND_DTUART, &imx_uart_driver, uart);
> +
> +    dt_device_set_used_by(dev, DOMID_XEN);
> +
> +    return 0;
> +}
> +
> +static const struct dt_device_match imx_uart_dt_compat[] __initconst =
> +{
> +    DT_MATCH_COMPATIBLE("fsl,imx6q-uart"),
> +    { /* sentinel */ },
> +};
> +
> +DT_DEVICE_START(imx_uart, "i.MX UART", DEVICE_SERIAL)
> +    .dt_match = imx_uart_dt_compat,
> +    .init = imx_uart_init,
> +DT_DEVICE_END
> +
> +/*
> + * Local variables:
> + * mode: C
> + * c-file-style: "BSD"
> + * c-basic-offset: 4
> + * indent-tabs-mode: nil
> + * End:
> + */
> --
> 2.34.1
Oleksandr Tyshchenko April 18, 2024, 5:19 p.m. UTC | #3
On 04.04.24 09:54, Michal Orzel wrote:
> Hi Oleksandr,

Hello Michal

sorry for the late response

> 
> On 02/04/2024 14:05, Oleksandr Tyshchenko wrote:
>>
>>
>> From: Oleksandr Tyshchenko <oleksandr_tyshchenko@epam.com>
>>
>> The i.MX UART Documentation:
>> https://www.nxp.com/webapp/Download?colCode=IMX8MMRM
>> Chapter 16.2 Universal Asynchronous Receiver/Transmitter (UART)
>>
>> Tested on i.MX 8M Mini only, but I guess, it should be
> imperative mood

ok

> 
>> suitable for other i.MX8M* SoCs (those UART device tree nodes
>> contain "fsl,imx6q-uart" compatible string).
>>
>> Signed-off-by: Oleksandr Tyshchenko <oleksandr_tyshchenko@epam.com>
>> ---
>> I used the "earlycon=ec_imx6q,0x30890000" cmd arg and
>> selected CONFIG_SERIAL_IMX_EARLYCON in Linux for enabling vUART.
>> ---
>> ---
>>   MAINTAINERS                 |   1 +
>>   xen/drivers/char/Kconfig    |   7 +
>>   xen/drivers/char/Makefile   |   1 +
>>   xen/drivers/char/imx-uart.c | 299 ++++++++++++++++++++++++++++++++++++
>>   4 files changed, 308 insertions(+)
>>   create mode 100644 xen/drivers/char/imx-uart.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 1bd22fd75f..bd4084fd20 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -249,6 +249,7 @@ F:  xen/drivers/char/arm-uart.c
>>   F:     xen/drivers/char/cadence-uart.c
>>   F:     xen/drivers/char/exynos4210-uart.c
>>   F:     xen/drivers/char/imx-lpuart.c
>> +F:     xen/drivers/char/imx-uart.c
>>   F:     xen/drivers/char/meson-uart.c
>>   F:     xen/drivers/char/mvebu-uart.c
>>   F:     xen/drivers/char/omap-uart.c
>> diff --git a/xen/drivers/char/Kconfig b/xen/drivers/char/Kconfig
>> index e18ec3788c..f51a1f596a 100644
>> --- a/xen/drivers/char/Kconfig
>> +++ b/xen/drivers/char/Kconfig
>> @@ -20,6 +20,13 @@ config HAS_IMX_LPUART
>>          help
>>            This selects the i.MX LPUART. If you have i.MX8QM based board, say Y.
>>
>> +config HAS_IMX_UART
>> +       bool "i.MX UART driver"
>> +       default y
>> +       depends on ARM_64
>> +       help
>> +         This selects the i.MX UART. If you have i.MX8M* based board, say Y.
>> +
>>   config HAS_MVEBU
>>          bool "Marvell MVEBU UART driver"
>>          default y
>> diff --git a/xen/drivers/char/Makefile b/xen/drivers/char/Makefile
>> index e7e374775d..147530a1ed 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_IMX_UART) += imx-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/imx-uart.c b/xen/drivers/char/imx-uart.c
>> new file mode 100644
>> index 0000000000..13bb189063
>> --- /dev/null
>> +++ b/xen/drivers/char/imx-uart.c
>> @@ -0,0 +1,299 @@
>> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> Can it be GPL-2.0-only? Some companies are worried about v3.

I guess, it can, will change


> 
>> +/*
>> + * xen/drivers/char/imx-uart.c
>> + *
>> + * Driver for i.MX UART.
>> + *
>> + * Based on Linux's drivers/tty/serial/imx.c
>> + *
>> + * Copyright (C) 2024 EPAM Systems Inc.
>> + */
>> +
>> +#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/imx-uart.h>
>> +#include <asm/io.h>
>> +
>> +#define imx_uart_read(uart, off)          readl((uart)->regs + (off))
>> +#define imx_uart_write(uart, off, val)    writel((val), (uart)->regs + (off))
>> +
>> +static struct imx_uart {
>> +    uint32_t baud, clock_hz, data_bits, parity, stop_bits, fifo_size;
> What's the use of these variables? AFAICT they are set but never used.

right, this is a copy paste from other UART driver, will drop

> 
>> +    uint32_t irq;
> unsigned int
> 
>> +    char __iomem *regs;
>> +    struct irqaction irqaction;
>> +    struct vuart_info vuart;
>> +} imx_com;
>> +
>> +static void imx_uart_interrupt(int irq, void *data)
>> +{
>> +    struct serial_port *port = data;
>> +    struct imx_uart *uart = port->uart;
>> +    uint32_t usr1, usr2;
>> +
>> +    usr1 = imx_uart_read(uart, USR1);
>> +    usr2 = imx_uart_read(uart, USR2);
>> +
>> +    if ( usr1 & (USR1_RRDY | USR1_AGTIM) )
>> +    {
>> +        imx_uart_write(uart, USR1, USR1_AGTIM);
>> +        serial_rx_interrupt(port);
>> +    }
>> +
>> +    if ( (usr1 & USR1_TRDY) || (usr2 & USR2_TXDC) )
>> +        serial_tx_interrupt(port);
>> +}
>> +
>> +static void imx_uart_clear_rx_errors(struct serial_port *port)
>> +{
>> +    struct imx_uart *uart = port->uart;
>> +    uint32_t usr1, usr2;
>> +
>> +    usr1 = imx_uart_read(uart, USR1);
>> +    usr2 = imx_uart_read(uart, USR2);
>> +
>> +    if ( usr2 & USR2_BRCD )
>> +        imx_uart_write(uart, USR2, USR2_BRCD);
>> +    else if ( usr1 & USR1_FRAMERR )
>> +        imx_uart_write(uart, USR1, USR1_FRAMERR);
>> +    else if ( usr1 & USR1_PARITYERR )
>> +        imx_uart_write(uart, USR1, USR1_PARITYERR);
>> +
>> +    if ( usr2 & USR2_ORE )
>> +        imx_uart_write(uart, USR2, USR2_ORE);
>> +}
>> +
>> +static void __init imx_uart_init_preirq(struct serial_port *port)
>> +{
>> +    struct imx_uart *uart = port->uart;
>> +    uint32_t reg;
>> +
>> +    /*
>> +     * Wait for the transmission to complete. This is needed for a smooth
>> +     * transition when we come from early printk.
>> +     */
>> +    while ( !(imx_uart_read(uart, USR2) & USR2_TXDC) )
>> +        cpu_relax();
>> +
>> +    /* Set receiver/transmitter trigger level */
>> +    reg = imx_uart_read(uart, UFCR);
>> +    reg &= (UFCR_RFDIV | UFCR_DCEDTE);
>> +    reg |= TXTL_DEFAULT << UFCR_TXTL_SHF | RXTL_DEFAULT;
> please surround TXTL_DEFAULT << UFCR_TXTL_SHF with parentheses

will do


> 
>> +    imx_uart_write(uart, UFCR, reg);
>> +
>> +    /* Enable UART and disable interrupts/DMA */
>> +    reg = imx_uart_read(uart, UCR1);
>> +    reg |= UCR1_UARTEN;
>> +    reg &= ~(UCR1_TRDYEN | UCR1_TXMPTYEN | UCR1_RTSDEN | UCR1_RRDYEN |
>> +             UCR1_RXDMAEN | UCR1_TXDMAEN | UCR1_ATDMAEN);
>> +    imx_uart_write(uart, UCR1, reg);
>> +
>> +    /* Enable receiver/transmitter and disable Aging Timer */
>> +    reg = imx_uart_read(uart, UCR2);
>> +    reg |= UCR2_RXEN | UCR2_TXEN;
>> +    reg &= ~UCR2_ATEN;
>> +    imx_uart_write(uart, UCR2, reg);
>> +
>> +    /* Disable interrupts */
>> +    reg = imx_uart_read(uart, UCR4);
>> +    reg &= ~(UCR4_TCEN | UCR4_DREN);
>> +    imx_uart_write(uart, UCR4, reg);
>> +}
>> +
>> +static void __init imx_uart_init_postirq(struct serial_port *port)
>> +{
>> +    struct imx_uart *uart = port->uart;
>> +
>> +    uart->irqaction.handler = imx_uart_interrupt;
>> +    uart->irqaction.name = "imx_uart";
>> +    uart->irqaction.dev_id = port;
>> +
>> +    if ( setup_irq(uart->irq, 0, &uart->irqaction) != 0 )
>> +    {
>> +        dprintk(XENLOG_ERR, "Failed to allocate imx_uart IRQ %d\n", uart->irq);
> Wrong format specifier for unsigned, use %u

ok

> Also, why dprintk?

the same answer, copy paste from other UART driver, will change to printk

> 
>> +        return;
>> +    }
>> +
>> +    /* Clear possible receiver errors */
>> +    imx_uart_clear_rx_errors(port);
>> +
>> +    /* Enable interrupts */
>> +    imx_uart_write(uart, UCR1, imx_uart_read(uart, UCR1) |
>> +                   UCR1_RRDYEN | UCR1_TRDYEN);
>> +    imx_uart_write(uart, UCR2, imx_uart_read(uart, UCR2) | UCR2_ATEN);
>> +}
>> +
>> +static void imx_uart_suspend(struct serial_port *port)
>> +{
>> +    BUG();
>> +}
> no need for this function
> 
>> +
>> +static void imx_uart_resume(struct serial_port *port)
>> +{
>> +    BUG();
>> +}
> no need for this function

will drop

> 
>> +
>> +static int imx_uart_tx_ready(struct serial_port *port)
>> +{
>> +    struct imx_uart *uart = port->uart;
> can be const

ok

> 
>> +    uint32_t reg;
>> +
>> +    reg = imx_uart_read(uart, IMX21_UTS);
>> +    if ( reg & UTS_TXEMPTY )
>> +        return TX_FIFO_SIZE;
> empty line here please

ok

> 
>> +    if ( reg & UTS_TXFULL )
>> +        return 0;
>> +
>> +    /*
>> +     * If the FIFO is neither full nor empty then there is a space for
>> +     * one char at least.
>> +     */
>> +    return 1;
>> +}
>> +
>> +static void imx_uart_putc(struct serial_port *port, char c)
>> +{
>> +    struct imx_uart *uart = port->uart;
> can be const

ok

> 
>> +
>> +    while ( imx_uart_read(uart, IMX21_UTS) & UTS_TXFULL )
>> +        cpu_relax();
> Looking at pl011, cdns, putc contains only a write to data register. This is because
> ->putc always follows ->tx_ready which contains the check for how many chars can be written.
> Therefore, AFAICT this should be dropped.

really good point, driver does implement tx_ready cb, so I will drop the 
useless check. I think, this will improve things as we won't need to 
read the hw register every time we write a single character...


> 
>> +
>> +    imx_uart_write(uart, URTX0, c);
>> +}
>> +
>> +static int imx_uart_getc(struct serial_port *port, char *pc)
>> +{
>> +    struct imx_uart *uart = port->uart;
> can be const

ok

> 
>> +    uint32_t data;
>> +
>> +    if ( !(imx_uart_read(uart, USR2) & USR2_RDR) )
>> +        return 0;
>> +
>> +    data = imx_uart_read(uart, URXD0);
>> +    *pc = data & URXD_RX_DATA;
>> +
>> +    if ( unlikely(data & URXD_ERR) )
>> +        imx_uart_clear_rx_errors(port);
>> +
>> +    return 1;
>> +}
>> +
>> +static int __init imx_uart_irq(struct serial_port *port)
>> +{
>> +    struct imx_uart *uart = port->uart;
> can be const

ok

> 
>> +
>> +    return ((uart->irq > 0) ? uart->irq : -1);
>> +}
>> +
>> +static const struct vuart_info *imx_uart_vuart_info(struct serial_port *port)
>> +{
>> +    struct imx_uart *uart = port->uart;
> can be const

ok

> 
>> +
>> +    return &uart->vuart;
>> +}
>> +
>> +static void imx_uart_start_tx(struct serial_port *port)
>> +{
>> +    struct imx_uart *uart = port->uart;
>> +
>> +    imx_uart_write(uart, UCR1, imx_uart_read(uart, UCR1) | UCR1_TRDYEN);
>> +}
>> +
>> +static void imx_uart_stop_tx(struct serial_port *port)
>> +{
>> +    struct imx_uart *uart = port->uart;
>> +
>> +    imx_uart_write(uart, UCR1, imx_uart_read(uart, UCR1) & ~UCR1_TRDYEN);
>> +}
>> +
>> +static struct uart_driver __read_mostly imx_uart_driver = {
>> +    .init_preirq = imx_uart_init_preirq,
>> +    .init_postirq = imx_uart_init_postirq,
>> +    .endboot = NULL,
>> +    .suspend = imx_uart_suspend,
>> +    .resume = imx_uart_resume,
> no need to assign a value for these 3 members

will drop


> 
>> +    .tx_ready = imx_uart_tx_ready,
>> +    .putc = imx_uart_putc,
>> +    .getc = imx_uart_getc,
>> +    .irq = imx_uart_irq,
>> +    .start_tx = imx_uart_start_tx,
>> +    .stop_tx = imx_uart_stop_tx,
>> +    .vuart_info = imx_uart_vuart_info,
>> +};
>> +
>> +static int __init imx_uart_init(struct dt_device_node *dev, const void *data)
>> +{
>> +    const char *config = data;
>> +    struct imx_uart *uart;
>> +    int res;
>> +    paddr_t addr, size;
>> +
>> +    if ( strcmp(config, "") )
>> +        printk("WARNING: UART configuration is not supported\n");
>> +
>> +    uart = &imx_com;
>> +
>> +    uart->baud = 115200;
>> +    uart->data_bits = 8;
>> +    uart->parity = 0;
>> +    uart->stop_bits = 1;
> They are set but never used

you are right, will drop these fields entirely


> 
>> +
>> +    res = dt_device_get_paddr(dev, 0, &addr, &size);
>> +    if ( res )
>> +    {
>> +        printk("imx-uart: Unable to retrieve the base address of the UART\n");
>> +        return res;
>> +    }
>> +
>> +    res = platform_get_irq(dev, 0);
>> +    if ( res < 0 )
>> +    {
>> +        printk("imx-uart: Unable to retrieve the IRQ\n");
>> +        return -EINVAL;
>> +    }
>> +    uart->irq = res;
>> +
>> +    uart->regs = ioremap_nocache(addr, size);
>> +    if ( !uart->regs )
>> +    {
>> +        printk("imx-uart: Unable to map the UART memory\n");
>> +        return -ENOMEM;
>> +    }
>> +
>> +    uart->vuart.base_addr = addr;
>> +    uart->vuart.size = size;
>> +    uart->vuart.data_off = URTX0;
>> +    uart->vuart.status_off = IMX21_UTS;
>> +    uart->vuart.status = UTS_TXEMPTY;
>> +
>> +    /* Register with generic serial driver */
>> +    serial_register_uart(SERHND_DTUART, &imx_uart_driver, uart);
>> +
>> +    dt_device_set_used_by(dev, DOMID_XEN);
>> +
>> +    return 0;
>> +}
>> +
>> +static const struct dt_device_match imx_uart_dt_compat[] __initconst =
>> +{
>> +    DT_MATCH_COMPATIBLE("fsl,imx6q-uart"),
>> +    { /* sentinel */ },
>> +};
>> +
>> +DT_DEVICE_START(imx_uart, "i.MX UART", DEVICE_SERIAL)
>> +    .dt_match = imx_uart_dt_compat,
>> +    .init = imx_uart_init,
>> +DT_DEVICE_END
>> +
>> +/*
>> + * Local variables:
>> + * mode: C
>> + * c-file-style: "BSD"
>> + * c-basic-offset: 4
>> + * indent-tabs-mode: nil
>> + * End:
>> + */
>> --
>> 2.34.1
>>
> 
> ~Michal
>
Oleksandr Tyshchenko April 18, 2024, 6:59 p.m. UTC | #4
On 07.04.24 05:43, Peng Fan wrote:
> Hi Oleksandr,

Hello Peng

> 
>> Subject: [PATCH 2/2] xen/arm: Add i.MX UART driver
>>
>> From: Oleksandr Tyshchenko <oleksandr_tyshchenko@epam.com>
>>
>> The i.MX UART Documentation:
>> https://eur01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.
>> nxp.com%2Fwebapp%2FDownload%3FcolCode%3DIMX8MMRM&data=05%7
>> C02%7Cpeng.fan%40nxp.com%7C6ada06c4133849667f3608dc530d5471%7
>> C686ea1d3bc2b4c6fa92cd99c5c301635%7C0%7C0%7C6384765639197564
>> 70%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMz
>> IiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C0%7C%7C%7C&sdata=RmXgAMb7
>> wFZ7epZgYgHJo4LH35rzQhD05yTXSkttXbc%3D&reserved=0
>> Chapter 16.2 Universal Asynchronous Receiver/Transmitter (UART)
>>
>> Tested on i.MX 8M Mini only, but I guess, it should be suitable for other
>> i.MX8M* SoCs (those UART device tree nodes contain "fsl,imx6q-uart"
>> compatible string).
> 
> Good to see people are interested in XEN on 8M.
> I had an implementation back in 2015, you could take a look.

Thanks.


When I was googling for what was publicly available on Xen exactly for 
i.MX 8M Mini (before start writing this driver), I didn't find that 
implementation.

Interesting to compare


[snip]
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 1bd22fd75f..bd4084fd20 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -249,6 +249,7 @@  F:	xen/drivers/char/arm-uart.c
 F:	xen/drivers/char/cadence-uart.c
 F:	xen/drivers/char/exynos4210-uart.c
 F:	xen/drivers/char/imx-lpuart.c
+F:	xen/drivers/char/imx-uart.c
 F:	xen/drivers/char/meson-uart.c
 F:	xen/drivers/char/mvebu-uart.c
 F:	xen/drivers/char/omap-uart.c
diff --git a/xen/drivers/char/Kconfig b/xen/drivers/char/Kconfig
index e18ec3788c..f51a1f596a 100644
--- a/xen/drivers/char/Kconfig
+++ b/xen/drivers/char/Kconfig
@@ -20,6 +20,13 @@  config HAS_IMX_LPUART
 	help
 	  This selects the i.MX LPUART. If you have i.MX8QM based board, say Y.
 
+config HAS_IMX_UART
+	bool "i.MX UART driver"
+	default y
+	depends on ARM_64
+	help
+	  This selects the i.MX UART. If you have i.MX8M* based board, say Y.
+
 config HAS_MVEBU
 	bool "Marvell MVEBU UART driver"
 	default y
diff --git a/xen/drivers/char/Makefile b/xen/drivers/char/Makefile
index e7e374775d..147530a1ed 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_IMX_UART) += imx-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/imx-uart.c b/xen/drivers/char/imx-uart.c
new file mode 100644
index 0000000000..13bb189063
--- /dev/null
+++ b/xen/drivers/char/imx-uart.c
@@ -0,0 +1,299 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * xen/drivers/char/imx-uart.c
+ *
+ * Driver for i.MX UART.
+ *
+ * Based on Linux's drivers/tty/serial/imx.c
+ *
+ * Copyright (C) 2024 EPAM Systems Inc.
+ */
+
+#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/imx-uart.h>
+#include <asm/io.h>
+
+#define imx_uart_read(uart, off)          readl((uart)->regs + (off))
+#define imx_uart_write(uart, off, val)    writel((val), (uart)->regs + (off))
+
+static struct imx_uart {
+    uint32_t baud, clock_hz, data_bits, parity, stop_bits, fifo_size;
+    uint32_t irq;
+    char __iomem *regs;
+    struct irqaction irqaction;
+    struct vuart_info vuart;
+} imx_com;
+
+static void imx_uart_interrupt(int irq, void *data)
+{
+    struct serial_port *port = data;
+    struct imx_uart *uart = port->uart;
+    uint32_t usr1, usr2;
+
+    usr1 = imx_uart_read(uart, USR1);
+    usr2 = imx_uart_read(uart, USR2);
+
+    if ( usr1 & (USR1_RRDY | USR1_AGTIM) )
+    {
+        imx_uart_write(uart, USR1, USR1_AGTIM);
+        serial_rx_interrupt(port);
+    }
+
+    if ( (usr1 & USR1_TRDY) || (usr2 & USR2_TXDC) )
+        serial_tx_interrupt(port);
+}
+
+static void imx_uart_clear_rx_errors(struct serial_port *port)
+{
+    struct imx_uart *uart = port->uart;
+    uint32_t usr1, usr2;
+
+    usr1 = imx_uart_read(uart, USR1);
+    usr2 = imx_uart_read(uart, USR2);
+
+    if ( usr2 & USR2_BRCD )
+        imx_uart_write(uart, USR2, USR2_BRCD);
+    else if ( usr1 & USR1_FRAMERR )
+        imx_uart_write(uart, USR1, USR1_FRAMERR);
+    else if ( usr1 & USR1_PARITYERR )
+        imx_uart_write(uart, USR1, USR1_PARITYERR);
+
+    if ( usr2 & USR2_ORE )
+        imx_uart_write(uart, USR2, USR2_ORE);
+}
+
+static void __init imx_uart_init_preirq(struct serial_port *port)
+{
+    struct imx_uart *uart = port->uart;
+    uint32_t reg;
+
+    /*
+     * Wait for the transmission to complete. This is needed for a smooth
+     * transition when we come from early printk.
+     */
+    while ( !(imx_uart_read(uart, USR2) & USR2_TXDC) )
+        cpu_relax();
+
+    /* Set receiver/transmitter trigger level */
+    reg = imx_uart_read(uart, UFCR);
+    reg &= (UFCR_RFDIV | UFCR_DCEDTE);
+    reg |= TXTL_DEFAULT << UFCR_TXTL_SHF | RXTL_DEFAULT;
+    imx_uart_write(uart, UFCR, reg);
+
+    /* Enable UART and disable interrupts/DMA */
+    reg = imx_uart_read(uart, UCR1);
+    reg |= UCR1_UARTEN;
+    reg &= ~(UCR1_TRDYEN | UCR1_TXMPTYEN | UCR1_RTSDEN | UCR1_RRDYEN |
+             UCR1_RXDMAEN | UCR1_TXDMAEN | UCR1_ATDMAEN);
+    imx_uart_write(uart, UCR1, reg);
+
+    /* Enable receiver/transmitter and disable Aging Timer */
+    reg = imx_uart_read(uart, UCR2);
+    reg |= UCR2_RXEN | UCR2_TXEN;
+    reg &= ~UCR2_ATEN;
+    imx_uart_write(uart, UCR2, reg);
+
+    /* Disable interrupts */
+    reg = imx_uart_read(uart, UCR4);
+    reg &= ~(UCR4_TCEN | UCR4_DREN);
+    imx_uart_write(uart, UCR4, reg);
+}
+
+static void __init imx_uart_init_postirq(struct serial_port *port)
+{
+    struct imx_uart *uart = port->uart;
+
+    uart->irqaction.handler = imx_uart_interrupt;
+    uart->irqaction.name = "imx_uart";
+    uart->irqaction.dev_id = port;
+
+    if ( setup_irq(uart->irq, 0, &uart->irqaction) != 0 )
+    {
+        dprintk(XENLOG_ERR, "Failed to allocate imx_uart IRQ %d\n", uart->irq);
+        return;
+    }
+
+    /* Clear possible receiver errors */
+    imx_uart_clear_rx_errors(port);
+
+    /* Enable interrupts */
+    imx_uart_write(uart, UCR1, imx_uart_read(uart, UCR1) |
+                   UCR1_RRDYEN | UCR1_TRDYEN);
+    imx_uart_write(uart, UCR2, imx_uart_read(uart, UCR2) | UCR2_ATEN);
+}
+
+static void imx_uart_suspend(struct serial_port *port)
+{
+    BUG();
+}
+
+static void imx_uart_resume(struct serial_port *port)
+{
+    BUG();
+}
+
+static int imx_uart_tx_ready(struct serial_port *port)
+{
+    struct imx_uart *uart = port->uart;
+    uint32_t reg;
+
+    reg = imx_uart_read(uart, IMX21_UTS);
+    if ( reg & UTS_TXEMPTY )
+        return TX_FIFO_SIZE;
+    if ( reg & UTS_TXFULL )
+        return 0;
+
+    /*
+     * If the FIFO is neither full nor empty then there is a space for
+     * one char at least.
+     */
+    return 1;
+}
+
+static void imx_uart_putc(struct serial_port *port, char c)
+{
+    struct imx_uart *uart = port->uart;
+
+    while ( imx_uart_read(uart, IMX21_UTS) & UTS_TXFULL )
+        cpu_relax();
+
+    imx_uart_write(uart, URTX0, c);
+}
+
+static int imx_uart_getc(struct serial_port *port, char *pc)
+{
+    struct imx_uart *uart = port->uart;
+    uint32_t data;
+
+    if ( !(imx_uart_read(uart, USR2) & USR2_RDR) )
+        return 0;
+
+    data = imx_uart_read(uart, URXD0);
+    *pc = data & URXD_RX_DATA;
+
+    if ( unlikely(data & URXD_ERR) )
+        imx_uart_clear_rx_errors(port);
+
+    return 1;
+}
+
+static int __init imx_uart_irq(struct serial_port *port)
+{
+    struct imx_uart *uart = port->uart;
+
+    return ((uart->irq > 0) ? uart->irq : -1);
+}
+
+static const struct vuart_info *imx_uart_vuart_info(struct serial_port *port)
+{
+    struct imx_uart *uart = port->uart;
+
+    return &uart->vuart;
+}
+
+static void imx_uart_start_tx(struct serial_port *port)
+{
+    struct imx_uart *uart = port->uart;
+
+    imx_uart_write(uart, UCR1, imx_uart_read(uart, UCR1) | UCR1_TRDYEN);
+}
+
+static void imx_uart_stop_tx(struct serial_port *port)
+{
+    struct imx_uart *uart = port->uart;
+
+    imx_uart_write(uart, UCR1, imx_uart_read(uart, UCR1) & ~UCR1_TRDYEN);
+}
+
+static struct uart_driver __read_mostly imx_uart_driver = {
+    .init_preirq = imx_uart_init_preirq,
+    .init_postirq = imx_uart_init_postirq,
+    .endboot = NULL,
+    .suspend = imx_uart_suspend,
+    .resume = imx_uart_resume,
+    .tx_ready = imx_uart_tx_ready,
+    .putc = imx_uart_putc,
+    .getc = imx_uart_getc,
+    .irq = imx_uart_irq,
+    .start_tx = imx_uart_start_tx,
+    .stop_tx = imx_uart_stop_tx,
+    .vuart_info = imx_uart_vuart_info,
+};
+
+static int __init imx_uart_init(struct dt_device_node *dev, const void *data)
+{
+    const char *config = data;
+    struct imx_uart *uart;
+    int res;
+    paddr_t addr, size;
+
+    if ( strcmp(config, "") )
+        printk("WARNING: UART configuration is not supported\n");
+
+    uart = &imx_com;
+
+    uart->baud = 115200;
+    uart->data_bits = 8;
+    uart->parity = 0;
+    uart->stop_bits = 1;
+
+    res = dt_device_get_paddr(dev, 0, &addr, &size);
+    if ( res )
+    {
+        printk("imx-uart: Unable to retrieve the base address of the UART\n");
+        return res;
+    }
+
+    res = platform_get_irq(dev, 0);
+    if ( res < 0 )
+    {
+        printk("imx-uart: Unable to retrieve the IRQ\n");
+        return -EINVAL;
+    }
+    uart->irq = res;
+
+    uart->regs = ioremap_nocache(addr, size);
+    if ( !uart->regs )
+    {
+        printk("imx-uart: Unable to map the UART memory\n");
+        return -ENOMEM;
+    }
+
+    uart->vuart.base_addr = addr;
+    uart->vuart.size = size;
+    uart->vuart.data_off = URTX0;
+    uart->vuart.status_off = IMX21_UTS;
+    uart->vuart.status = UTS_TXEMPTY;
+
+    /* Register with generic serial driver */
+    serial_register_uart(SERHND_DTUART, &imx_uart_driver, uart);
+
+    dt_device_set_used_by(dev, DOMID_XEN);
+
+    return 0;
+}
+
+static const struct dt_device_match imx_uart_dt_compat[] __initconst =
+{
+    DT_MATCH_COMPATIBLE("fsl,imx6q-uart"),
+    { /* sentinel */ },
+};
+
+DT_DEVICE_START(imx_uart, "i.MX UART", DEVICE_SERIAL)
+    .dt_match = imx_uart_dt_compat,
+    .init = imx_uart_init,
+DT_DEVICE_END
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */