Generic Interface (GENI) is a newer interface for low speed interfaces
like UART, I2C or SPI. This patch adds the simple driver for the UART
instance of GENI. Code is based on similar drivers in U-Boot and Linux
kernel.
This driver implements only simple synchronous mode, because although
GENI supports FIFO mode, it needs to know number of
characters **before** starting TX transaction. This is a stark
contrast when compared to other UART peripherals, which allow adding
characters to a FIFO while TX operation is running.
The patch adds both normal UART driver and earlyprintk version.
Signed-off-by: Volodymyr Babchuk <volodymyr_babchuk@epam.com>
---
xen/arch/arm/Kconfig.debug | 19 +-
xen/arch/arm/arm64/debug-qcom.inc | 76 +++++++
xen/arch/arm/include/asm/qcom-uart.h | 48 +++++
xen/drivers/char/Kconfig | 8 +
xen/drivers/char/Makefile | 1 +
xen/drivers/char/qcom-uart.c | 288 +++++++++++++++++++++++++++
6 files changed, 439 insertions(+), 1 deletion(-)
create mode 100644 xen/arch/arm/arm64/debug-qcom.inc
create mode 100644 xen/arch/arm/include/asm/qcom-uart.h
create mode 100644 xen/drivers/char/qcom-uart.c
diff --git a/xen/arch/arm/Kconfig.debug b/xen/arch/arm/Kconfig.debug
index eec860e88e..f6ab0bb30e 100644
--- a/xen/arch/arm/Kconfig.debug
+++ b/xen/arch/arm/Kconfig.debug
@@ -119,6 +119,19 @@ choice
selecting one of the platform specific options below if
you know the parameters for the port.
+ This option is preferred over the platform specific
+ options; the platform specific options are deprecated
+ and will soon be removed.
+ config EARLY_UART_CHOICE_QCOM
+ select EARLY_UART_QCOM
+ bool "Early printk via Qualcomm debug UART"
+ help
+ Say Y here if you wish the early printk to direct their
+ output to a Qualcomm debug UART. You can use this option to
+ provide the parameters for the debug UART rather than
+ selecting one of the platform specific options below if
+ you know the parameters for the port.
+
This option is preferred over the platform specific
options; the platform specific options are deprecated
and will soon be removed.
@@ -211,6 +224,9 @@ config EARLY_UART_PL011
config EARLY_UART_SCIF
select EARLY_PRINTK
bool
+config EARLY_UART_QCOM
+ select EARLY_PRINTK
+ bool
config EARLY_PRINTK
bool
@@ -261,7 +277,7 @@ config EARLY_UART_PL011_MMIO32
will be done using 32-bit only accessors.
config EARLY_UART_INIT
- depends on EARLY_UART_PL011 && EARLY_UART_PL011_BAUD_RATE != 0
+ depends on (EARLY_UART_PL011 && EARLY_UART_PL011_BAUD_RATE != 0) || EARLY_UART_QCOM
def_bool y
config EARLY_UART_8250_REG_SHIFT
@@ -308,3 +324,4 @@ config EARLY_PRINTK_INC
default "debug-mvebu.inc" if EARLY_UART_MVEBU
default "debug-pl011.inc" if EARLY_UART_PL011
default "debug-scif.inc" if EARLY_UART_SCIF
+ default "debug-qcom.inc" if EARLY_UART_QCOM
diff --git a/xen/arch/arm/arm64/debug-qcom.inc b/xen/arch/arm/arm64/debug-qcom.inc
new file mode 100644
index 0000000000..130d08d6e9
--- /dev/null
+++ b/xen/arch/arm/arm64/debug-qcom.inc
@@ -0,0 +1,76 @@
+/*
+ * xen/arch/arm/arm64/debug-qcom.inc
+ *
+ * Qualcomm debug UART specific debug code
+ *
+ * Volodymyr Babchuk <volodymyr_babchuk@epam.com>
+ * Copyright (C) 2024, EPAM Systems.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <asm/qcom-uart.h>
+
+.macro early_uart_init xb c
+ mov w\c, #M_GENI_CMD_ABORT
+ str w\c, [\xb, #SE_GENI_M_CMD_CTRL_REG]
+1:
+ ldr w\c, [\xb, #SE_GENI_M_IRQ_STATUS] /* Load IRQ status */
+ tst w\c, #M_CMD_ABORT_EN /* Check TX_FIFI_WATERMARK_EN bit */
+ beq 1b /* Wait for the UART to be ready */
+ mov w\c, #M_CMD_ABORT_EN
+ orr w\c, w\c, #M_CMD_DONE_EN
+ str w\c, [\xb, #SE_GENI_M_IRQ_CLEAR]
+
+ mov w\c, #1
+ str w\c, [\xb, #SE_UART_TX_TRANS_LEN] /* write len */
+
+ mov w\c, #(UART_START_TX << M_OPCODE_SHFT) /* Prepare cmd */
+ str w\c, [\xb, #SE_GENI_M_CMD0] /* write cmd */
+.endm
+/*
+ * wait for UART to be ready to transmit
+ * xb: register which contains the UART base address
+ * c: scratch register
+ */
+.macro early_uart_ready xb c
+1:
+ ldr w\c, [\xb, #SE_GENI_M_IRQ_STATUS] /* Load IRQ status */
+ tst w\c, #M_TX_FIFO_WATERMARK_EN /* Check TX_FIFI_WATERMARK_EN bit */
+ beq 1b /* Wait for the UART to be ready */
+.endm
+
+/*
+ * UART transmit character
+ * xb: register which contains the UART base address
+ * wt: register which contains the character to transmit
+ */
+.macro early_uart_transmit xb wt
+ str \wt, [\xb, #SE_GENI_TX_FIFOn] /* Put char to FIFO */
+ mov \wt, #M_TX_FIFO_WATERMARK_EN /* Prepare to FIFO */
+ str \wt, [\xb, #SE_GENI_M_IRQ_CLEAR] /* Kick FIFO */
+95:
+ ldr \wt, [\xb, #SE_GENI_M_IRQ_STATUS] /* Load IRQ status */
+ tst \wt, #M_CMD_DONE_EN /* Check TX_FIFO_WATERMARK_EN bit */
+ beq 95b /* Wait for the UART to be ready */
+ mov \wt, #M_CMD_DONE_EN
+ str \wt, [\xb, #SE_GENI_M_IRQ_CLEAR]
+
+ mov \wt, #(UART_START_TX << M_OPCODE_SHFT) /* Prepare next cmd */
+ str \wt, [\xb, #SE_GENI_M_CMD0] /* write cmd */
+.endm
+
+/*
+ * Local variables:
+ * mode: ASM
+ * indent-tabs-mode: nil
+ * End:
+ */
diff --git a/xen/arch/arm/include/asm/qcom-uart.h b/xen/arch/arm/include/asm/qcom-uart.h
new file mode 100644
index 0000000000..dc9579374c
--- /dev/null
+++ b/xen/arch/arm/include/asm/qcom-uart.h
@@ -0,0 +1,48 @@
+/*
+ * xen/include/asm-arm/qcom-uart.h
+ *
+ * Common constant definition between early printk and the UART driver
+ * for the Qualcomm debug UART
+ *
+ * Volodymyr Babchuk <volodymyr_babchuk@epam.com>
+ * Copyright (C) 2024, EPAM Systems.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __ASM_ARM_QCOM_UART_H
+#define __ASM_ARM_QCOM_UART_H
+
+#define SE_UART_TX_TRANS_LEN 0x270
+#define SE_GENI_M_CMD0 0x600
+#define UART_START_TX 0x1
+#define M_OPCODE_SHFT 27
+
+#define SE_GENI_M_CMD_CTRL_REG 0x604
+#define M_GENI_CMD_ABORT BIT(1, U)
+#define SE_GENI_M_IRQ_STATUS 0x610
+#define M_CMD_DONE_EN BIT(0, U)
+#define M_CMD_ABORT_EN BIT(5, U)
+#define M_TX_FIFO_WATERMARK_EN BIT(30, U)
+#define SE_GENI_M_IRQ_CLEAR 0x618
+#define SE_GENI_TX_FIFOn 0x700
+#define SE_GENI_TX_WATERMARK_REG 0x80c
+
+#endif /* __ASM_ARM_QCOM_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 e18ec3788c..52c3934d06 100644
--- a/xen/drivers/char/Kconfig
+++ b/xen/drivers/char/Kconfig
@@ -68,6 +68,14 @@ config HAS_SCIF
This selects the SuperH SCI(F) UART. If you have a SuperH based board,
or Renesas R-Car Gen 2/3 based board say Y.
+config HAS_QCOM_UART
+ bool "Qualcomm GENI UART driver"
+ default y
+ depends on ARM
+ help
+ This selects the Qualcomm GENI-based UART driver. If you
+ have a Qualcomm-based board board say Y here.
+
config HAS_EHCI
bool
depends on X86
diff --git a/xen/drivers/char/Makefile b/xen/drivers/char/Makefile
index e7e374775d..698ad0578c 100644
--- a/xen/drivers/char/Makefile
+++ b/xen/drivers/char/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_HAS_MESON) += meson-uart.o
obj-$(CONFIG_HAS_MVEBU) += mvebu-uart.o
obj-$(CONFIG_HAS_OMAP) += omap-uart.o
obj-$(CONFIG_HAS_SCIF) += scif-uart.o
+obj-$(CONFIG_HAS_QCOM_UART) += qcom-uart.o
obj-$(CONFIG_HAS_EHCI) += ehci-dbgp.o
obj-$(CONFIG_XHCI) += xhci-dbc.o
obj-$(CONFIG_HAS_IMX_LPUART) += imx-lpuart.o
diff --git a/xen/drivers/char/qcom-uart.c b/xen/drivers/char/qcom-uart.c
new file mode 100644
index 0000000000..2614051ca0
--- /dev/null
+++ b/xen/drivers/char/qcom-uart.c
@@ -0,0 +1,288 @@
+/*
+ * xen/drivers/char/qcom-uart.c
+ *
+ * Driver for Qualcomm GENI-based UART interface
+ *
+ * Volodymyr Babchuk <volodymyr_babchuk@epam.com>
+ *
+ * Copyright (C) EPAM Systems 2024
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <xen/console.h>
+#include <xen/const.h>
+#include <xen/errno.h>
+#include <xen/serial.h>
+#include <xen/init.h>
+#include <xen/irq.h>
+#include <xen/mm.h>
+#include <xen/delay.h>
+#include <asm/device.h>
+#include <asm/qcom-uart.h>
+#include <asm/io.h>
+
+#define GENI_FORCE_DEFAULT_REG 0x20
+#define FORCE_DEFAULT BIT(0, U)
+#define DEF_TX_WM 2
+#define SE_GENI_TX_PACKING_CFG0 0x260
+#define SE_GENI_TX_PACKING_CFG1 0x264
+#define SE_GENI_RX_PACKING_CFG0 0x284
+#define SE_GENI_RX_PACKING_CFG1 0x288
+#define SE_GENI_M_IRQ_EN 0x614
+#define M_SEC_IRQ_EN BIT(31, U)
+#define M_RX_FIFO_WATERMARK_EN BIT(26, U)
+#define M_RX_FIFO_LAST_EN BIT(27, U)
+#define SE_GENI_S_CMD0 0x630
+#define UART_START_READ 0x1
+#define S_OPCODE_SHFT 27
+#define SE_GENI_S_CMD_CTRL_REG 0x634
+#define S_GENI_CMD_ABORT BIT(1, U)
+#define SE_GENI_S_IRQ_STATUS 0x640
+#define SE_GENI_S_IRQ_EN 0x644
+#define S_RX_FIFO_LAST_EN BIT(27, U)
+#define S_RX_FIFO_WATERMARK_EN BIT(26, U)
+#define S_CMD_ABORT_EN BIT(5, U)
+#define S_CMD_DONE_EN BIT(0, U)
+#define SE_GENI_S_IRQ_CLEAR 0x648
+#define SE_GENI_RX_FIFOn 0x780
+#define SE_GENI_TX_FIFO_STATUS 0x800
+#define TX_FIFO_WC GENMASK(27, 0)
+#define SE_GENI_RX_FIFO_STATUS 0x804
+#define RX_LAST BIT(31, U)
+#define RX_LAST_BYTE_VALID_MSK GENMASK(30, 28)
+#define RX_LAST_BYTE_VALID_SHFT 28
+#define RX_FIFO_WC_MSK GENMASK(24, 0)
+#define SE_GENI_TX_WATERMARK_REG 0x80c
+
+static struct qcom_uart {
+ unsigned int irq;
+ char __iomem *regs;
+ struct irqaction irqaction;
+} qcom_com = {0};
+
+static bool qcom_uart_poll_bit(void *addr, uint32_t mask, bool set)
+{
+ unsigned long timeout_us = 20000;
+ uint32_t reg;
+
+ while ( timeout_us ) {
+ reg = readl(addr);
+ if ( (bool)(reg & mask) == set )
+ return true;
+ udelay(10);
+ timeout_us -= 10;
+ }
+
+ return false;
+}
+
+static void __init qcom_uart_init_preirq(struct serial_port *port)
+{
+ struct qcom_uart *uart = port->uart;
+
+ /* Stop anything in TX that earlyprintk configured and clear all errors */
+ writel(M_GENI_CMD_ABORT, uart->regs + SE_GENI_M_CMD_CTRL_REG);
+ qcom_uart_poll_bit(uart->regs + SE_GENI_M_IRQ_STATUS, M_CMD_ABORT_EN,
+ true);
+ writel(M_CMD_ABORT_EN, uart->regs + SE_GENI_M_IRQ_CLEAR);
+
+ /*
+ * Configure FIFO length: 1 byte per FIFO entry. This is terribly
+ * ineffective, as it is possible to cram 4 bytes per FIFO word,
+ * like Linux does. But using one byte per FIFO entry makes this
+ * driver much simpler.
+ */
+ writel(0xf, uart->regs + SE_GENI_TX_PACKING_CFG0);
+ writel(0x0, uart->regs + SE_GENI_TX_PACKING_CFG1);
+ writel(0xf, uart->regs + SE_GENI_RX_PACKING_CFG0);
+ writel(0x0, uart->regs + SE_GENI_RX_PACKING_CFG1);
+
+ /* Reset RX state machine */
+ writel(S_GENI_CMD_ABORT, uart->regs + SE_GENI_S_CMD_CTRL_REG);
+ qcom_uart_poll_bit(uart->regs + SE_GENI_S_CMD_CTRL_REG,
+ S_GENI_CMD_ABORT, false);
+ writel(S_CMD_DONE_EN | S_CMD_ABORT_EN, uart->regs + SE_GENI_S_IRQ_CLEAR);
+ writel(FORCE_DEFAULT, uart->regs + GENI_FORCE_DEFAULT_REG);
+}
+
+static void qcom_uart_interrupt(int irq, void *data, struct cpu_user_regs *regs)
+{
+ struct serial_port *port = data;
+ struct qcom_uart *uart = port->uart;
+ uint32_t m_irq_status, s_irq_status;
+
+ m_irq_status = readl(uart->regs + SE_GENI_M_IRQ_STATUS);
+ s_irq_status = readl(uart->regs + SE_GENI_S_IRQ_STATUS);
+ writel(m_irq_status, uart->regs + SE_GENI_M_IRQ_CLEAR);
+ writel(s_irq_status, uart->regs + SE_GENI_S_IRQ_CLEAR);
+
+ if ( s_irq_status & (S_RX_FIFO_WATERMARK_EN | S_RX_FIFO_LAST_EN) )
+ serial_rx_interrupt(port, regs);
+}
+
+static void __init qcom_uart_init_postirq(struct serial_port *port)
+{
+ struct qcom_uart *uart = port->uart;
+ int rc;
+ uint32_t val;
+
+ uart->irqaction.handler = qcom_uart_interrupt;
+ uart->irqaction.name = "qcom_uart";
+ uart->irqaction.dev_id = port;
+
+ if ( (rc = setup_irq(uart->irq, 0, &uart->irqaction)) != 0 )
+ dprintk(XENLOG_ERR, "Failed to allocated qcom_uart IRQ %d\n",
+ uart->irq);
+
+ /* Enable TX/RX and Error Interrupts */
+ writel(S_GENI_CMD_ABORT, uart->regs + SE_GENI_S_CMD_CTRL_REG);
+ qcom_uart_poll_bit(uart->regs + SE_GENI_S_CMD_CTRL_REG,
+ S_GENI_CMD_ABORT, false);
+ writel(S_CMD_DONE_EN | S_CMD_ABORT_EN, uart->regs + SE_GENI_S_IRQ_CLEAR);
+ writel(FORCE_DEFAULT, uart->regs + GENI_FORCE_DEFAULT_REG);
+
+ val = readl(uart->regs + SE_GENI_S_IRQ_EN);
+ val = S_RX_FIFO_WATERMARK_EN | S_RX_FIFO_LAST_EN;
+ writel(val, uart->regs + SE_GENI_S_IRQ_EN);
+
+ val = readl(uart->regs + SE_GENI_M_IRQ_EN);
+ val = M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN;
+ writel(val, uart->regs + SE_GENI_M_IRQ_EN);
+
+ /* Send RX command */
+ writel(UART_START_READ << S_OPCODE_SHFT, uart->regs + SE_GENI_S_CMD0);
+ qcom_uart_poll_bit(uart->regs + SE_GENI_M_IRQ_STATUS, M_SEC_IRQ_EN,
+ true);
+}
+
+static void qcom_uart_putc(struct serial_port *port, char c)
+{
+ struct qcom_uart *uart = port->uart;
+ uint32_t irq_clear = M_CMD_DONE_EN;
+ uint32_t m_cmd;
+ bool done;
+
+ /* Setup TX */
+ writel(1, uart->regs + SE_UART_TX_TRANS_LEN);
+
+ writel(DEF_TX_WM, uart->regs + SE_GENI_TX_WATERMARK_REG);
+
+ m_cmd = UART_START_TX << M_OPCODE_SHFT;
+ writel(m_cmd, uart->regs + SE_GENI_M_CMD0);
+
+ qcom_uart_poll_bit(uart->regs + SE_GENI_M_IRQ_STATUS,
+ M_TX_FIFO_WATERMARK_EN, true);
+
+ writel(c, uart->regs + SE_GENI_TX_FIFOn);
+ writel(M_TX_FIFO_WATERMARK_EN, uart->regs + SE_GENI_M_IRQ_CLEAR);
+
+ /* Check for TX done */
+ done = qcom_uart_poll_bit(uart->regs + SE_GENI_M_IRQ_STATUS, M_CMD_DONE_EN,
+ true);
+ if ( !done )
+ {
+ writel(M_GENI_CMD_ABORT, uart->regs + SE_GENI_M_CMD_CTRL_REG);
+ irq_clear |= M_CMD_ABORT_EN;
+ qcom_uart_poll_bit(uart->regs + SE_GENI_M_IRQ_STATUS, M_CMD_ABORT_EN,
+ true);
+
+ }
+ writel(irq_clear, uart->regs + SE_GENI_M_IRQ_CLEAR);
+}
+
+static int qcom_uart_getc(struct serial_port *port, char *pc)
+{
+ struct qcom_uart *uart = port->uart;
+
+ if ( !readl(uart->regs + SE_GENI_RX_FIFO_STATUS) )
+ return 0;
+
+ *pc = readl(uart->regs + SE_GENI_RX_FIFOn) & 0xFF;
+
+ writel(UART_START_READ << S_OPCODE_SHFT, uart->regs + SE_GENI_S_CMD0);
+ qcom_uart_poll_bit(uart->regs + SE_GENI_M_IRQ_STATUS, M_SEC_IRQ_EN,
+ true);
+
+ return 1;
+
+}
+
+static struct uart_driver __read_mostly qcom_uart_driver = {
+ .init_preirq = qcom_uart_init_preirq,
+ .init_postirq = qcom_uart_init_postirq,
+ .putc = qcom_uart_putc,
+ .getc = qcom_uart_getc,
+};
+
+static const struct dt_device_match qcom_uart_dt_match[] __initconst =
+{
+ { .compatible = "qcom,geni-debug-uart"},
+ { /* sentinel */ },
+};
+
+static int __init qcom_uart_init(struct dt_device_node *dev,
+ const void *data)
+{
+ const char *config = data;
+ struct qcom_uart *uart;
+ int res;
+ paddr_t addr, size;
+
+ if ( strcmp(config, "") )
+ printk("WARNING: UART configuration is not supported\n");
+
+ uart = &qcom_com;
+
+ res = dt_device_get_paddr(dev, 0, &addr, &size);
+ if ( res )
+ {
+ printk("qcom-uart: Unable to retrieve the base"
+ " address of the UART\n");
+ return res;
+ }
+
+ res = platform_get_irq(dev, 0);
+ if ( res < 0 )
+ {
+ printk("qcom-uart: Unable to retrieve the IRQ\n");
+ return res;
+ }
+ uart->irq = res;
+
+ uart->regs = ioremap_nocache(addr, size);
+ if ( !uart->regs )
+ {
+ printk("qcom-uart: Unable to map the UART memory\n");
+ return -ENOMEM;
+ }
+
+ /* Register with generic serial driver */
+ serial_register_uart(SERHND_DTUART, &qcom_uart_driver, uart);
+
+ dt_device_set_used_by(dev, DOMID_XEN);
+
+ return 0;
+}
+
+DT_DEVICE_START(qcom_uart, "QCOM UART", DEVICE_SERIAL)
+ .dt_match = qcom_uart_dt_match,
+ .init = qcom_uart_init,
+DT_DEVICE_END
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
--
2.43.0
like UART, I2C or SPI. This patch adds the simple driver for the UART
instance of GENI. Code is based on similar drivers in U-Boot and Linux
kernel.
This driver implements only simple synchronous mode, because although
GENI supports FIFO mode, it needs to know number of
characters **before** starting TX transaction. This is a stark
contrast when compared to other UART peripherals, which allow adding
characters to a FIFO while TX operation is running.
The patch adds both normal UART driver and earlyprintk version.
Signed-off-by: Volodymyr Babchuk <volodymyr_babchuk@epam.com>
---
xen/arch/arm/Kconfig.debug | 19 +-
xen/arch/arm/arm64/debug-qcom.inc | 76 +++++++
xen/arch/arm/include/asm/qcom-uart.h | 48 +++++
xen/drivers/char/Kconfig | 8 +
xen/drivers/char/Makefile | 1 +
xen/drivers/char/qcom-uart.c | 288 +++++++++++++++++++++++++++
6 files changed, 439 insertions(+), 1 deletion(-)
create mode 100644 xen/arch/arm/arm64/debug-qcom.inc
create mode 100644 xen/arch/arm/include/asm/qcom-uart.h
create mode 100644 xen/drivers/char/qcom-uart.c
diff --git a/xen/arch/arm/Kconfig.debug b/xen/arch/arm/Kconfig.debug
index eec860e88e..f6ab0bb30e 100644
--- a/xen/arch/arm/Kconfig.debug
+++ b/xen/arch/arm/Kconfig.debug
@@ -119,6 +119,19 @@ choice
selecting one of the platform specific options below if
you know the parameters for the port.
+ This option is preferred over the platform specific
+ options; the platform specific options are deprecated
+ and will soon be removed.
+ config EARLY_UART_CHOICE_QCOM
+ select EARLY_UART_QCOM
+ bool "Early printk via Qualcomm debug UART"
+ help
+ Say Y here if you wish the early printk to direct their
+ output to a Qualcomm debug UART. You can use this option to
+ provide the parameters for the debug UART rather than
+ selecting one of the platform specific options below if
+ you know the parameters for the port.
+
This option is preferred over the platform specific
options; the platform specific options are deprecated
and will soon be removed.
@@ -211,6 +224,9 @@ config EARLY_UART_PL011
config EARLY_UART_SCIF
select EARLY_PRINTK
bool
+config EARLY_UART_QCOM
+ select EARLY_PRINTK
+ bool
config EARLY_PRINTK
bool
@@ -261,7 +277,7 @@ config EARLY_UART_PL011_MMIO32
will be done using 32-bit only accessors.
config EARLY_UART_INIT
- depends on EARLY_UART_PL011 && EARLY_UART_PL011_BAUD_RATE != 0
+ depends on (EARLY_UART_PL011 && EARLY_UART_PL011_BAUD_RATE != 0) || EARLY_UART_QCOM
def_bool y
config EARLY_UART_8250_REG_SHIFT
@@ -308,3 +324,4 @@ config EARLY_PRINTK_INC
default "debug-mvebu.inc" if EARLY_UART_MVEBU
default "debug-pl011.inc" if EARLY_UART_PL011
default "debug-scif.inc" if EARLY_UART_SCIF
+ default "debug-qcom.inc" if EARLY_UART_QCOM
diff --git a/xen/arch/arm/arm64/debug-qcom.inc b/xen/arch/arm/arm64/debug-qcom.inc
new file mode 100644
index 0000000000..130d08d6e9
--- /dev/null
+++ b/xen/arch/arm/arm64/debug-qcom.inc
@@ -0,0 +1,76 @@
+/*
+ * xen/arch/arm/arm64/debug-qcom.inc
+ *
+ * Qualcomm debug UART specific debug code
+ *
+ * Volodymyr Babchuk <volodymyr_babchuk@epam.com>
+ * Copyright (C) 2024, EPAM Systems.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <asm/qcom-uart.h>
+
+.macro early_uart_init xb c
+ mov w\c, #M_GENI_CMD_ABORT
+ str w\c, [\xb, #SE_GENI_M_CMD_CTRL_REG]
+1:
+ ldr w\c, [\xb, #SE_GENI_M_IRQ_STATUS] /* Load IRQ status */
+ tst w\c, #M_CMD_ABORT_EN /* Check TX_FIFI_WATERMARK_EN bit */
+ beq 1b /* Wait for the UART to be ready */
+ mov w\c, #M_CMD_ABORT_EN
+ orr w\c, w\c, #M_CMD_DONE_EN
+ str w\c, [\xb, #SE_GENI_M_IRQ_CLEAR]
+
+ mov w\c, #1
+ str w\c, [\xb, #SE_UART_TX_TRANS_LEN] /* write len */
+
+ mov w\c, #(UART_START_TX << M_OPCODE_SHFT) /* Prepare cmd */
+ str w\c, [\xb, #SE_GENI_M_CMD0] /* write cmd */
+.endm
+/*
+ * wait for UART to be ready to transmit
+ * xb: register which contains the UART base address
+ * c: scratch register
+ */
+.macro early_uart_ready xb c
+1:
+ ldr w\c, [\xb, #SE_GENI_M_IRQ_STATUS] /* Load IRQ status */
+ tst w\c, #M_TX_FIFO_WATERMARK_EN /* Check TX_FIFI_WATERMARK_EN bit */
+ beq 1b /* Wait for the UART to be ready */
+.endm
+
+/*
+ * UART transmit character
+ * xb: register which contains the UART base address
+ * wt: register which contains the character to transmit
+ */
+.macro early_uart_transmit xb wt
+ str \wt, [\xb, #SE_GENI_TX_FIFOn] /* Put char to FIFO */
+ mov \wt, #M_TX_FIFO_WATERMARK_EN /* Prepare to FIFO */
+ str \wt, [\xb, #SE_GENI_M_IRQ_CLEAR] /* Kick FIFO */
+95:
+ ldr \wt, [\xb, #SE_GENI_M_IRQ_STATUS] /* Load IRQ status */
+ tst \wt, #M_CMD_DONE_EN /* Check TX_FIFO_WATERMARK_EN bit */
+ beq 95b /* Wait for the UART to be ready */
+ mov \wt, #M_CMD_DONE_EN
+ str \wt, [\xb, #SE_GENI_M_IRQ_CLEAR]
+
+ mov \wt, #(UART_START_TX << M_OPCODE_SHFT) /* Prepare next cmd */
+ str \wt, [\xb, #SE_GENI_M_CMD0] /* write cmd */
+.endm
+
+/*
+ * Local variables:
+ * mode: ASM
+ * indent-tabs-mode: nil
+ * End:
+ */
diff --git a/xen/arch/arm/include/asm/qcom-uart.h b/xen/arch/arm/include/asm/qcom-uart.h
new file mode 100644
index 0000000000..dc9579374c
--- /dev/null
+++ b/xen/arch/arm/include/asm/qcom-uart.h
@@ -0,0 +1,48 @@
+/*
+ * xen/include/asm-arm/qcom-uart.h
+ *
+ * Common constant definition between early printk and the UART driver
+ * for the Qualcomm debug UART
+ *
+ * Volodymyr Babchuk <volodymyr_babchuk@epam.com>
+ * Copyright (C) 2024, EPAM Systems.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __ASM_ARM_QCOM_UART_H
+#define __ASM_ARM_QCOM_UART_H
+
+#define SE_UART_TX_TRANS_LEN 0x270
+#define SE_GENI_M_CMD0 0x600
+#define UART_START_TX 0x1
+#define M_OPCODE_SHFT 27
+
+#define SE_GENI_M_CMD_CTRL_REG 0x604
+#define M_GENI_CMD_ABORT BIT(1, U)
+#define SE_GENI_M_IRQ_STATUS 0x610
+#define M_CMD_DONE_EN BIT(0, U)
+#define M_CMD_ABORT_EN BIT(5, U)
+#define M_TX_FIFO_WATERMARK_EN BIT(30, U)
+#define SE_GENI_M_IRQ_CLEAR 0x618
+#define SE_GENI_TX_FIFOn 0x700
+#define SE_GENI_TX_WATERMARK_REG 0x80c
+
+#endif /* __ASM_ARM_QCOM_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 e18ec3788c..52c3934d06 100644
--- a/xen/drivers/char/Kconfig
+++ b/xen/drivers/char/Kconfig
@@ -68,6 +68,14 @@ config HAS_SCIF
This selects the SuperH SCI(F) UART. If you have a SuperH based board,
or Renesas R-Car Gen 2/3 based board say Y.
+config HAS_QCOM_UART
+ bool "Qualcomm GENI UART driver"
+ default y
+ depends on ARM
+ help
+ This selects the Qualcomm GENI-based UART driver. If you
+ have a Qualcomm-based board board say Y here.
+
config HAS_EHCI
bool
depends on X86
diff --git a/xen/drivers/char/Makefile b/xen/drivers/char/Makefile
index e7e374775d..698ad0578c 100644
--- a/xen/drivers/char/Makefile
+++ b/xen/drivers/char/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_HAS_MESON) += meson-uart.o
obj-$(CONFIG_HAS_MVEBU) += mvebu-uart.o
obj-$(CONFIG_HAS_OMAP) += omap-uart.o
obj-$(CONFIG_HAS_SCIF) += scif-uart.o
+obj-$(CONFIG_HAS_QCOM_UART) += qcom-uart.o
obj-$(CONFIG_HAS_EHCI) += ehci-dbgp.o
obj-$(CONFIG_XHCI) += xhci-dbc.o
obj-$(CONFIG_HAS_IMX_LPUART) += imx-lpuart.o
diff --git a/xen/drivers/char/qcom-uart.c b/xen/drivers/char/qcom-uart.c
new file mode 100644
index 0000000000..2614051ca0
--- /dev/null
+++ b/xen/drivers/char/qcom-uart.c
@@ -0,0 +1,288 @@
+/*
+ * xen/drivers/char/qcom-uart.c
+ *
+ * Driver for Qualcomm GENI-based UART interface
+ *
+ * Volodymyr Babchuk <volodymyr_babchuk@epam.com>
+ *
+ * Copyright (C) EPAM Systems 2024
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <xen/console.h>
+#include <xen/const.h>
+#include <xen/errno.h>
+#include <xen/serial.h>
+#include <xen/init.h>
+#include <xen/irq.h>
+#include <xen/mm.h>
+#include <xen/delay.h>
+#include <asm/device.h>
+#include <asm/qcom-uart.h>
+#include <asm/io.h>
+
+#define GENI_FORCE_DEFAULT_REG 0x20
+#define FORCE_DEFAULT BIT(0, U)
+#define DEF_TX_WM 2
+#define SE_GENI_TX_PACKING_CFG0 0x260
+#define SE_GENI_TX_PACKING_CFG1 0x264
+#define SE_GENI_RX_PACKING_CFG0 0x284
+#define SE_GENI_RX_PACKING_CFG1 0x288
+#define SE_GENI_M_IRQ_EN 0x614
+#define M_SEC_IRQ_EN BIT(31, U)
+#define M_RX_FIFO_WATERMARK_EN BIT(26, U)
+#define M_RX_FIFO_LAST_EN BIT(27, U)
+#define SE_GENI_S_CMD0 0x630
+#define UART_START_READ 0x1
+#define S_OPCODE_SHFT 27
+#define SE_GENI_S_CMD_CTRL_REG 0x634
+#define S_GENI_CMD_ABORT BIT(1, U)
+#define SE_GENI_S_IRQ_STATUS 0x640
+#define SE_GENI_S_IRQ_EN 0x644
+#define S_RX_FIFO_LAST_EN BIT(27, U)
+#define S_RX_FIFO_WATERMARK_EN BIT(26, U)
+#define S_CMD_ABORT_EN BIT(5, U)
+#define S_CMD_DONE_EN BIT(0, U)
+#define SE_GENI_S_IRQ_CLEAR 0x648
+#define SE_GENI_RX_FIFOn 0x780
+#define SE_GENI_TX_FIFO_STATUS 0x800
+#define TX_FIFO_WC GENMASK(27, 0)
+#define SE_GENI_RX_FIFO_STATUS 0x804
+#define RX_LAST BIT(31, U)
+#define RX_LAST_BYTE_VALID_MSK GENMASK(30, 28)
+#define RX_LAST_BYTE_VALID_SHFT 28
+#define RX_FIFO_WC_MSK GENMASK(24, 0)
+#define SE_GENI_TX_WATERMARK_REG 0x80c
+
+static struct qcom_uart {
+ unsigned int irq;
+ char __iomem *regs;
+ struct irqaction irqaction;
+} qcom_com = {0};
+
+static bool qcom_uart_poll_bit(void *addr, uint32_t mask, bool set)
+{
+ unsigned long timeout_us = 20000;
+ uint32_t reg;
+
+ while ( timeout_us ) {
+ reg = readl(addr);
+ if ( (bool)(reg & mask) == set )
+ return true;
+ udelay(10);
+ timeout_us -= 10;
+ }
+
+ return false;
+}
+
+static void __init qcom_uart_init_preirq(struct serial_port *port)
+{
+ struct qcom_uart *uart = port->uart;
+
+ /* Stop anything in TX that earlyprintk configured and clear all errors */
+ writel(M_GENI_CMD_ABORT, uart->regs + SE_GENI_M_CMD_CTRL_REG);
+ qcom_uart_poll_bit(uart->regs + SE_GENI_M_IRQ_STATUS, M_CMD_ABORT_EN,
+ true);
+ writel(M_CMD_ABORT_EN, uart->regs + SE_GENI_M_IRQ_CLEAR);
+
+ /*
+ * Configure FIFO length: 1 byte per FIFO entry. This is terribly
+ * ineffective, as it is possible to cram 4 bytes per FIFO word,
+ * like Linux does. But using one byte per FIFO entry makes this
+ * driver much simpler.
+ */
+ writel(0xf, uart->regs + SE_GENI_TX_PACKING_CFG0);
+ writel(0x0, uart->regs + SE_GENI_TX_PACKING_CFG1);
+ writel(0xf, uart->regs + SE_GENI_RX_PACKING_CFG0);
+ writel(0x0, uart->regs + SE_GENI_RX_PACKING_CFG1);
+
+ /* Reset RX state machine */
+ writel(S_GENI_CMD_ABORT, uart->regs + SE_GENI_S_CMD_CTRL_REG);
+ qcom_uart_poll_bit(uart->regs + SE_GENI_S_CMD_CTRL_REG,
+ S_GENI_CMD_ABORT, false);
+ writel(S_CMD_DONE_EN | S_CMD_ABORT_EN, uart->regs + SE_GENI_S_IRQ_CLEAR);
+ writel(FORCE_DEFAULT, uart->regs + GENI_FORCE_DEFAULT_REG);
+}
+
+static void qcom_uart_interrupt(int irq, void *data, struct cpu_user_regs *regs)
+{
+ struct serial_port *port = data;
+ struct qcom_uart *uart = port->uart;
+ uint32_t m_irq_status, s_irq_status;
+
+ m_irq_status = readl(uart->regs + SE_GENI_M_IRQ_STATUS);
+ s_irq_status = readl(uart->regs + SE_GENI_S_IRQ_STATUS);
+ writel(m_irq_status, uart->regs + SE_GENI_M_IRQ_CLEAR);
+ writel(s_irq_status, uart->regs + SE_GENI_S_IRQ_CLEAR);
+
+ if ( s_irq_status & (S_RX_FIFO_WATERMARK_EN | S_RX_FIFO_LAST_EN) )
+ serial_rx_interrupt(port, regs);
+}
+
+static void __init qcom_uart_init_postirq(struct serial_port *port)
+{
+ struct qcom_uart *uart = port->uart;
+ int rc;
+ uint32_t val;
+
+ uart->irqaction.handler = qcom_uart_interrupt;
+ uart->irqaction.name = "qcom_uart";
+ uart->irqaction.dev_id = port;
+
+ if ( (rc = setup_irq(uart->irq, 0, &uart->irqaction)) != 0 )
+ dprintk(XENLOG_ERR, "Failed to allocated qcom_uart IRQ %d\n",
+ uart->irq);
+
+ /* Enable TX/RX and Error Interrupts */
+ writel(S_GENI_CMD_ABORT, uart->regs + SE_GENI_S_CMD_CTRL_REG);
+ qcom_uart_poll_bit(uart->regs + SE_GENI_S_CMD_CTRL_REG,
+ S_GENI_CMD_ABORT, false);
+ writel(S_CMD_DONE_EN | S_CMD_ABORT_EN, uart->regs + SE_GENI_S_IRQ_CLEAR);
+ writel(FORCE_DEFAULT, uart->regs + GENI_FORCE_DEFAULT_REG);
+
+ val = readl(uart->regs + SE_GENI_S_IRQ_EN);
+ val = S_RX_FIFO_WATERMARK_EN | S_RX_FIFO_LAST_EN;
+ writel(val, uart->regs + SE_GENI_S_IRQ_EN);
+
+ val = readl(uart->regs + SE_GENI_M_IRQ_EN);
+ val = M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN;
+ writel(val, uart->regs + SE_GENI_M_IRQ_EN);
+
+ /* Send RX command */
+ writel(UART_START_READ << S_OPCODE_SHFT, uart->regs + SE_GENI_S_CMD0);
+ qcom_uart_poll_bit(uart->regs + SE_GENI_M_IRQ_STATUS, M_SEC_IRQ_EN,
+ true);
+}
+
+static void qcom_uart_putc(struct serial_port *port, char c)
+{
+ struct qcom_uart *uart = port->uart;
+ uint32_t irq_clear = M_CMD_DONE_EN;
+ uint32_t m_cmd;
+ bool done;
+
+ /* Setup TX */
+ writel(1, uart->regs + SE_UART_TX_TRANS_LEN);
+
+ writel(DEF_TX_WM, uart->regs + SE_GENI_TX_WATERMARK_REG);
+
+ m_cmd = UART_START_TX << M_OPCODE_SHFT;
+ writel(m_cmd, uart->regs + SE_GENI_M_CMD0);
+
+ qcom_uart_poll_bit(uart->regs + SE_GENI_M_IRQ_STATUS,
+ M_TX_FIFO_WATERMARK_EN, true);
+
+ writel(c, uart->regs + SE_GENI_TX_FIFOn);
+ writel(M_TX_FIFO_WATERMARK_EN, uart->regs + SE_GENI_M_IRQ_CLEAR);
+
+ /* Check for TX done */
+ done = qcom_uart_poll_bit(uart->regs + SE_GENI_M_IRQ_STATUS, M_CMD_DONE_EN,
+ true);
+ if ( !done )
+ {
+ writel(M_GENI_CMD_ABORT, uart->regs + SE_GENI_M_CMD_CTRL_REG);
+ irq_clear |= M_CMD_ABORT_EN;
+ qcom_uart_poll_bit(uart->regs + SE_GENI_M_IRQ_STATUS, M_CMD_ABORT_EN,
+ true);
+
+ }
+ writel(irq_clear, uart->regs + SE_GENI_M_IRQ_CLEAR);
+}
+
+static int qcom_uart_getc(struct serial_port *port, char *pc)
+{
+ struct qcom_uart *uart = port->uart;
+
+ if ( !readl(uart->regs + SE_GENI_RX_FIFO_STATUS) )
+ return 0;
+
+ *pc = readl(uart->regs + SE_GENI_RX_FIFOn) & 0xFF;
+
+ writel(UART_START_READ << S_OPCODE_SHFT, uart->regs + SE_GENI_S_CMD0);
+ qcom_uart_poll_bit(uart->regs + SE_GENI_M_IRQ_STATUS, M_SEC_IRQ_EN,
+ true);
+
+ return 1;
+
+}
+
+static struct uart_driver __read_mostly qcom_uart_driver = {
+ .init_preirq = qcom_uart_init_preirq,
+ .init_postirq = qcom_uart_init_postirq,
+ .putc = qcom_uart_putc,
+ .getc = qcom_uart_getc,
+};
+
+static const struct dt_device_match qcom_uart_dt_match[] __initconst =
+{
+ { .compatible = "qcom,geni-debug-uart"},
+ { /* sentinel */ },
+};
+
+static int __init qcom_uart_init(struct dt_device_node *dev,
+ const void *data)
+{
+ const char *config = data;
+ struct qcom_uart *uart;
+ int res;
+ paddr_t addr, size;
+
+ if ( strcmp(config, "") )
+ printk("WARNING: UART configuration is not supported\n");
+
+ uart = &qcom_com;
+
+ res = dt_device_get_paddr(dev, 0, &addr, &size);
+ if ( res )
+ {
+ printk("qcom-uart: Unable to retrieve the base"
+ " address of the UART\n");
+ return res;
+ }
+
+ res = platform_get_irq(dev, 0);
+ if ( res < 0 )
+ {
+ printk("qcom-uart: Unable to retrieve the IRQ\n");
+ return res;
+ }
+ uart->irq = res;
+
+ uart->regs = ioremap_nocache(addr, size);
+ if ( !uart->regs )
+ {
+ printk("qcom-uart: Unable to map the UART memory\n");
+ return -ENOMEM;
+ }
+
+ /* Register with generic serial driver */
+ serial_register_uart(SERHND_DTUART, &qcom_uart_driver, uart);
+
+ dt_device_set_used_by(dev, DOMID_XEN);
+
+ return 0;
+}
+
+DT_DEVICE_START(qcom_uart, "QCOM UART", DEVICE_SERIAL)
+ .dt_match = qcom_uart_dt_match,
+ .init = qcom_uart_init,
+DT_DEVICE_END
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
--
2.43.0