[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [PATCH 2/3] drivers: serial: add Qualcomm GENI-based serial driver
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@xxxxxxxx> --- 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@xxxxxxxx> + * 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@xxxxxxxx> + * 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@xxxxxxxx> + * + * 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
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |