Hi Oleksandr,
> Subject: [PATCH 2/2] xen/arm: Add i.MX UART driver
>
> From: Oleksandr Tyshchenko <[email protected]>
>
> 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 <[email protected]>
> ---
> 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