diff mbox series

[RFC,2/2] xen/arm: Add UART driver for i.MX8MQ SoC

Message ID 1559938596-5696-3-git-send-email-amittomer25@gmail.com (mailing list archive)
State New, archived
Headers show
Series [RFC,1/2] xen/arm: Add i.MX8MQ SoCs earlyprintk support | expand

Commit Message

Amit Tomer June 7, 2019, 8:16 p.m. UTC
This patch adds driver for UART controller found on i.MX8MQ SoC
and it has been tested on nitrogen8m board.

Controller register defination and some other references has drawn from Linux
kernel driver[1] v5.2.

[1]:https://github.com/torvalds/linux/blob/master/drivers/tty/serial/imx.c

Signed-off-by: Amit Singh Tomar <amittomer25@gmail.com>
---
 xen/drivers/char/Kconfig       |   8 ++
 xen/drivers/char/Makefile      |   1 +
 xen/drivers/char/imx8mq-uart.c | 306 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 315 insertions(+)
 create mode 100644 xen/drivers/char/imx8mq-uart.c
diff mbox series

Patch

diff --git a/xen/drivers/char/Kconfig b/xen/drivers/char/Kconfig
index b572305..f822af5 100644
--- a/xen/drivers/char/Kconfig
+++ b/xen/drivers/char/Kconfig
@@ -12,6 +12,14 @@  config HAS_CADENCE_UART
 	  This selects the Xilinx Zynq Cadence UART. If you have a Xilinx Zynq
 	  based board, say Y.
 
+config HAS_IMX8MQ
+        bool "i.MX8MQ UART "
+        default y
+        depends on ARM_64
+        help
+          This selects the NXP i.MX UART. If you have a NXP i.MX8MQ
+          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 7c646d7..aa1bb29 100644
--- a/xen/drivers/char/Makefile
+++ b/xen/drivers/char/Makefile
@@ -1,6 +1,7 @@ 
 obj-y += console.o
 obj-$(CONFIG_HAS_NS16550) += ns16550.o
 obj-$(CONFIG_HAS_CADENCE_UART) += cadence-uart.o
+obj-$(CONFIG_HAS_IMX8MQ) += imx8mq-uart.o
 obj-$(CONFIG_HAS_PL011) += pl011.o
 obj-$(CONFIG_HAS_EXYNOS4210) += exynos4210-uart.o
 obj-$(CONFIG_HAS_MESON) += meson-uart.o
diff --git a/xen/drivers/char/imx8mq-uart.c b/xen/drivers/char/imx8mq-uart.c
new file mode 100644
index 0000000..90fd649
--- /dev/null
+++ b/xen/drivers/char/imx8mq-uart.c
@@ -0,0 +1,306 @@ 
+/*
+ * xen/drivers/char/imx8mq-uart.c
+ *
+ * Driver for i.MX8MQ UART.
+ *
+ * Copyright (c) 2019, Amit Singh Tomar <amittomer25@gmail.com>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms and conditions of the GNU General Public
+ * License, version 2, as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <xen/irq.h>
+#include <xen/serial.h>
+#include <xen/vmap.h>
+#include <asm/io.h>
+#include <xen/delay.h>
+#include <xen/console.h>
+
+/* Register definitions */
+#define URXD    0x0  /* Receiver Register */
+#define UTXD    0x40 /* Transmitter Register */
+#define UCR1    0x80 /* Control Register 1 */
+#define UCR2    0x84 /* Control Register 2 */
+#define UCR4    0x8c /* Control Register 4 */
+#define USR1    0x94 /* Status Register 1 */
+#define USR2    0x98 /* Status Register 2 */
+#define UBIR    0xa4 /* BRM Incremental Register */
+#define UBMR    0xa8 /* BRM Modulator Register */
+#define UTS     0xb4 /* UART Test Register */
+
+/* UART Control Register Bit Fields */
+#define  UCR1_ADEN      (1<<15) /* Auto dectect interrupt */
+#define  UCR1_TRDYEN    (1<<13) /* Transmitter ready interrupt enable */
+#define  UCR1_IDEN      (1<<12) /* Idle condition interrupt */
+#define  UCR1_RRDYEN    (1<<9)  /* Recv ready interrupt enable */
+#define  UCR1_TXMPTYEN  (1<<6)  /* Transimitter empty interrupt enable */
+#define  UCR1_RTSDEN    (1<<5)  /* RTS delta interrupt enable */
+#define  UCR1_TDMAEN    (1<<3)  /* Transmitter ready DMA enable */
+#define  UCR1_UARTEN    (1<<0)  /* UART enabled */
+
+#define UCR2_TXEN       (1<<2)  /* Transmitter enabled */
+#define UCR2_RXEN       (1<<1)  /* Receiver enabled */
+#define UCR2_IRTS       (1<<14) /* Ignore RTS pin */
+#define UCR2_SRST       (1<<0)  /* SW reset */
+#define UCR2_WS         (1<<5)  /* Word size */
+#define UCR4_TCEN       (1<<3)  /* Transmit complete interrupt enable */
+#define UCR4_DREN       (1<<0)  /* Recv data ready interrupt enable */
+
+#define UTS_TXEMPTY     (1<<6)  /* TxFIFO empty */
+#define UTS_RXEMPTY     (1<<5)  /* RxFIFO empty */
+#define UTS_TXFULL      (1<<4)  /* TxFIFO full */
+
+#define USR1_TRDY       (1<<13) /* Transmitter ready interrupt/dma flag */
+#define USR1_RRDY       (1<<9)  /* Receiver ready interrupt/dma flag */
+
+#define USR2_TXDC       (1<<3)  /* Transmitter complete */
+
+#define TXFIFO_SIZE     32
+
+#define setbits(addr, set)      writel((readl(addr) | (set)), (addr))
+#define clrbits(addr, clear)    writel((readl(addr) & ~(clear)), (addr))
+
+static struct imx8mq_uart {
+    unsigned int irq;
+    void __iomem *regs;
+    struct irqaction irqaction;
+    struct vuart_info vuart;
+} imx8mq_com;
+
+static void imx8mq_uart_interrupt(int irq, void *data,
+                                     struct cpu_user_regs *regs)
+{
+    struct serial_port *port = data;
+    struct imx8mq_uart *uart = port->uart;
+    uint32_t st1 = readl(uart->regs + USR1);
+    uint32_t st2 = readl(uart->regs + USR2);
+
+    if ( st1 & (USR1_RRDY) )
+        serial_rx_interrupt(port, regs);
+
+    if ( (st1 & USR1_TRDY) || (st2 & USR2_TXDC) )
+        serial_tx_interrupt(port, regs);
+}
+
+static void set_baudrate(struct imx8mq_uart *uart)
+{
+    /* Needed for automatic baud rate detection */
+    writel(0xf, uart->regs + UBIR);
+    writel((25000000 / (2 * 115200)), uart->regs + UBMR);
+    writel((UCR2_WS | UCR2_IRTS | UCR2_RXEN | UCR2_TXEN | UCR2_SRST),
+            uart->regs + UCR2);   
+    writel(UCR1_UARTEN, uart->regs + UCR1);
+}
+
+static void __init imx8mq_uart_init_preirq(struct serial_port *port)
+{
+    struct imx8mq_uart *uart = port->uart;
+
+    clrbits(uart->regs + UCR1,
+            UCR1_ADEN | UCR1_IDEN | UCR1_RRDYEN | UCR1_RTSDEN);
+
+    /* Disable receiver */
+    clrbits(uart->regs + UCR2, UCR2_RXEN);
+}
+
+static void __init imx8mq_uart_init_postirq(struct serial_port *port)
+{
+    struct imx8mq_uart *uart = port->uart;
+
+    uart->irqaction.handler = imx8mq_uart_interrupt;
+    uart->irqaction.name    = "imx8mq_uart";
+    uart->irqaction.dev_id  = port;
+
+    if ( setup_irq(uart->irq, 0, &uart->irqaction) != 0 )
+    {
+        printk("Failed to allocated imx8mq_uart IRQ %d\n", uart->irq);
+        return;
+    }
+
+    setbits(uart->regs + UCR1,
+            UCR1_ADEN | UCR1_TRDYEN | UCR1_IDEN 
+            | UCR1_RRDYEN | UCR1_TXMPTYEN | UCR1_RTSDEN);
+    /* Enable receiver */
+    setbits(uart->regs + UCR2, UCR2_RXEN);
+    /* Generally we do soft reset in preirq stage but here reset does empty the
+       Tx FIFO and triggers Tx interrupt which should be enabled by now
+    */
+    clrbits(uart->regs + UCR2, UCR2_SRST);
+    while (!(readl(uart->regs + UCR2) & UCR2_SRST))
+    ;
+
+    set_baudrate(uart);
+}
+
+static void imx8mq_uart_suspend(struct serial_port *port)
+{
+    BUG();
+}
+
+static void imx8mq_uart_resume(struct serial_port *port)
+{
+    BUG();
+}
+
+static void imx8mq_uart_putc(struct serial_port *port, char c)
+{
+    struct imx8mq_uart *uart = port->uart;
+
+    writel(c, uart->regs + UTXD);
+}
+
+static int imx8mq_uart_tx_ready(struct serial_port *port)
+{
+    struct imx8mq_uart *uart = port->uart;
+    uint32_t reg;
+
+    reg = readl(uart->regs + UTS);
+
+    if( reg & UTS_TXEMPTY)
+        return TXFIFO_SIZE;
+    if ( reg & UTS_TXFULL)
+        return 0;
+
+    return 1;
+}
+
+static void imx8mq_uart_stop_tx(struct serial_port *port)
+{
+
+    struct imx8mq_uart *uart = port->uart;
+    
+    setbits(uart->regs + UCR1, UCR1_RRDYEN);
+    clrbits(uart->regs + UCR1, (UCR1_TXMPTYEN | UCR1_TDMAEN));
+    clrbits(uart->regs + UCR4, UCR4_TCEN);
+}
+
+static void imx8mq_uart_start_tx(struct serial_port *port)
+{
+    struct imx8mq_uart *uart = port->uart;
+
+    clrbits(uart->regs + UCR4, UCR4_DREN);
+    clrbits(uart->regs + UCR1, UCR1_TDMAEN | UCR1_RRDYEN);
+    setbits(uart->regs + UCR2, UCR2_TXEN | UCR2_RXEN);
+    setbits(uart->regs + UCR1, UCR1_TXMPTYEN);
+}
+
+
+static int __init imx8mq_irq(struct serial_port *port)
+{
+    struct imx8mq_uart *uart = port->uart;
+
+    return uart->irq;
+}
+
+static const struct vuart_info *imx8mq_vuart_info(struct serial_port *port)
+{
+    struct imx8mq_uart *uart = port->uart;
+
+    return &uart->vuart;
+}
+
+static int imx8mq_uart_getc(struct serial_port *port, char *c)
+{
+    struct imx8mq_uart *uart = port->uart;
+
+    if ( (readl(uart->regs + UTS) & UTS_RXEMPTY) )
+        return 0;
+
+    *c = readl(uart->regs + URXD) & 0xff;
+
+    return 1;
+}
+
+static struct uart_driver __read_mostly imx8mq_uart_driver = {
+    .init_preirq  = imx8mq_uart_init_preirq,
+    .init_postirq = imx8mq_uart_init_postirq,
+    .endboot      = NULL,
+    .suspend      = imx8mq_uart_suspend,
+    .resume       = imx8mq_uart_resume,
+    .putc         = imx8mq_uart_putc,
+    .getc         = imx8mq_uart_getc,
+    .tx_ready     = imx8mq_uart_tx_ready,
+    .stop_tx      = imx8mq_uart_stop_tx,
+    .start_tx     = imx8mq_uart_start_tx,
+    .irq          = imx8mq_irq,
+    .vuart_info   = imx8mq_vuart_info,
+};
+
+static int __init imx8mq_uart_init(struct dt_device_node *dev, const void *data)
+{
+    const char *config = data;
+    struct imx8mq_uart *uart;
+    int res;
+    u64 addr, size;
+
+    if ( strcmp(config, "") )
+        printk("WARNING: UART configuration is not supported\n");
+
+    uart = &imx8mq_com;
+
+    res = dt_device_get_address(dev, 0, &addr, &size);
+    if ( res )
+    {
+        printk("imx8mq3700: Unable to retrieve the base address of the UART\n");
+        return res;
+    }
+
+    res = platform_get_irq(dev, 0);
+    if ( res < 0 )
+    {
+        printk("imx8mq: Unable to retrieve the IRQ\n");
+        return -EINVAL;
+    }
+
+    uart->irq  = res;
+
+    uart->regs = ioremap_nocache(addr, size);
+    if ( !uart->regs )
+    {
+        printk("imx8mq3700: Unable to map the UART memory\n");
+        return -ENOMEM;
+    }
+   
+    uart->vuart.base_addr = addr;
+    uart->vuart.size = size;
+    uart->vuart.data_off = UCR1;
+    uart->vuart.status_off = UTS;
+    uart->vuart.status = USR1;
+
+    /* Register with generic serial driver. */
+    serial_register_uart(SERHND_DTUART, &imx8mq_uart_driver, uart);
+
+    dt_device_set_used_by(dev, DOMID_XEN);
+
+    return 0;
+}
+
+static const struct dt_device_match imx8mq_dt_match[] __initconst =
+{
+    DT_MATCH_COMPATIBLE("fsl,imx6q-uart"),
+    DT_MATCH_COMPATIBLE("fsl,imx8mq-uart"),
+    { /* sentinel */ },
+};
+
+DT_DEVICE_START(imx8mq, "NXP imx8mq UART", DEVICE_SERIAL)
+    .dt_match = imx8mq_dt_match,
+    .init = imx8mq_uart_init,
+DT_DEVICE_END
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */