This adds a lpuart32 driver. This is a variant of the lpuart driver
that is found on i.MX9 and other SoCs.

Signed-off-by: Sascha Hauer <s.ha...@pengutronix.de>
---
 drivers/serial/Kconfig           |   4 +
 drivers/serial/Makefile          |   1 +
 drivers/serial/serial_lpuart32.c | 185 +++++++++++++++++++++++++++++++
 include/serial/lpuart32.h        | 158 ++++++++++++++++++++++++++
 4 files changed, 348 insertions(+)
 create mode 100644 drivers/serial/serial_lpuart32.c
 create mode 100644 include/serial/lpuart32.h

diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index 77c827e436..803f6b6aee 100644
--- a/drivers/serial/Kconfig
+++ b/drivers/serial/Kconfig
@@ -122,6 +122,10 @@ config DRIVER_SERIAL_LPUART
        default y
        bool "LPUART serial driver"
 
+config DRIVER_SERIAL_LPUART32
+       depends on ARCH_IMX
+       bool "LPUART32 serial driver"
+
 config VIRTIO_CONSOLE
        tristate "Virtio console"
        depends on VIRTIO
diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile
index bbc517f521..4887e24ee1 100644
--- a/drivers/serial/Makefile
+++ b/drivers/serial/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_DRIVER_SERIAL_CADENCE)           += 
serial_cadence.o
 obj-$(CONFIG_DRIVER_SERIAL_EFI_STDIO)          += efi-stdio.o
 obj-$(CONFIG_DRIVER_SERIAL_DIGIC)              += serial_digic.o
 obj-$(CONFIG_DRIVER_SERIAL_LPUART)             += serial_lpuart.o
+obj-$(CONFIG_DRIVER_SERIAL_LPUART32)           += serial_lpuart32.o
 obj-$(CONFIG_VIRTIO_CONSOLE)                   += virtio_console.o
 obj-$(CONFIG_SERIAL_SIFIVE)                    += serial_sifive.o
 obj-$(CONFIG_SERIAL_SBI)                       += serial_sbi.o
diff --git a/drivers/serial/serial_lpuart32.c b/drivers/serial/serial_lpuart32.c
new file mode 100644
index 0000000000..0f3e7c7a04
--- /dev/null
+++ b/drivers/serial/serial_lpuart32.c
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023 Pengutronix
+ */
+
+#include <common.h>
+#include <driver.h>
+#include <init.h>
+#include <malloc.h>
+#include <notifier.h>
+#include <io.h>
+#include <of.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <serial/lpuart32.h>
+
+struct lpuart32_devtype_data {
+       unsigned int reg_offs;
+};
+
+struct lpuart32 {
+       struct console_device cdev;
+       int baudrate;
+       int dte_mode;
+       struct notifier_block notify;
+       struct resource *io;
+       void __iomem *base;
+       struct clk *clk;
+};
+
+static struct lpuart32 *cdev_to_lpuart32(struct console_device *cdev)
+{
+       return container_of(cdev, struct lpuart32, cdev);
+}
+
+static void lpuart32_enable(struct lpuart32 *lpuart32)
+{
+       writel(LPUART32_UARTCTRL_TE | LPUART32_UARTCTRL_RE,
+              lpuart32->base + LPUART32_UARTCTRL);
+}
+
+static void lpuart32_disable(struct lpuart32 *lpuart32)
+{
+       writel(0, lpuart32->base + LPUART32_UARTCTRL);
+}
+
+static int lpuart32_serial_setbaudrate(struct console_device *cdev,
+                                    int baudrate)
+{
+       struct lpuart32 *lpuart32 = cdev_to_lpuart32(cdev);
+
+       lpuart32_disable(lpuart32);
+
+       /*
+        * We treat baudrate of 0 as a request to disable UART
+        */
+       if (baudrate) {
+               lpuart32_setbrg(lpuart32->base, clk_get_rate(lpuart32->clk),
+                             baudrate);
+               lpuart32_enable(lpuart32);
+       }
+
+       lpuart32->baudrate = baudrate;
+
+       return 0;
+}
+
+static int lpuart32_serial_getc(struct console_device *cdev)
+{
+       struct lpuart32 *lpuart32 = cdev_to_lpuart32(cdev);
+
+       while (!(readl(lpuart32->base + LPUART32_UARTSTAT) & 
LPUART32_UARTSTAT_RDRF));
+
+       return readl(lpuart32->base + LPUART32_UARTDATA) & 0xff;
+}
+
+static void lpuart32_serial_putc(struct console_device *cdev, char c)
+{
+       struct lpuart32 *lpuart32 = cdev_to_lpuart32(cdev);
+
+       lpuart32_putc(lpuart32->base, c);
+}
+
+/* Test whether a character is in the RX buffer */
+static int lpuart32_serial_tstc(struct console_device *cdev)
+{
+       struct lpuart32 *lpuart32 = cdev_to_lpuart32(cdev);
+
+       return readl(lpuart32->base + LPUART32_UARTSTAT) & 
LPUART32_UARTSTAT_RDRF;
+}
+
+static void lpuart32_serial_flush(struct console_device *cdev)
+{
+}
+
+static int lpuart32_serial_probe(struct device *dev)
+{
+       int ret;
+       struct console_device *cdev;
+       struct lpuart32 *lpuart32;
+       const char *devname;
+       struct lpuart32_devtype_data *devtype;
+
+       ret = dev_get_drvdata(dev, (const void **)&devtype);
+       if (ret)
+               return ret;
+
+       lpuart32 = xzalloc(sizeof(*lpuart32));
+       cdev = &lpuart32->cdev;
+       dev->priv = lpuart32;
+
+       lpuart32->io = dev_request_mem_resource(dev, 0);
+       if (IS_ERR(lpuart32->io)) {
+               ret = PTR_ERR(lpuart32->io);
+               goto err_free;
+       }
+       lpuart32->base = IOMEM(lpuart32->io->start) + devtype->reg_offs;
+
+       lpuart32->clk = clk_get(dev, NULL);
+       if (IS_ERR(lpuart32->clk)) {
+               ret = PTR_ERR(lpuart32->clk);
+               dev_err(dev, "Failed to get UART clock %d\n", ret);
+               goto io_release;
+       }
+
+       ret = clk_enable(lpuart32->clk);
+       if (ret) {
+               dev_err(dev, "Failed to enable UART clock %d\n", ret);
+               goto io_release;
+       }
+
+       cdev->dev    = dev;
+       cdev->tstc   = lpuart32_serial_tstc;
+       cdev->putc   = lpuart32_serial_putc;
+       cdev->getc   = lpuart32_serial_getc;
+       cdev->flush  = lpuart32_serial_flush;
+       cdev->setbrg = lpuart32_serial_setbaudrate;
+
+       if (dev->of_node) {
+               devname = of_alias_get(dev->of_node);
+               if (devname) {
+                       cdev->devname = xstrdup(devname);
+                       cdev->devid   = DEVICE_ID_SINGLE;
+               }
+       }
+
+       cdev->linux_console_name = "ttyLP";
+       cdev->linux_earlycon_name = "lpuart";
+       cdev->phys_base = lpuart32->base;
+
+       lpuart32_setup(lpuart32->base, clk_get_rate(lpuart32->clk));
+
+       ret = console_register(cdev);
+       if (!ret)
+               return 0;
+
+       clk_put(lpuart32->clk);
+io_release:
+       release_region(lpuart32->io);
+err_free:
+       free(lpuart32);
+
+       return ret;
+}
+
+static struct lpuart32_devtype_data imx7ulp_data = {
+       .reg_offs = 0x10,
+};
+
+static struct of_device_id lpuart32_serial_dt_ids[] = {
+       {
+               .compatible = "fsl,imx7ulp-lpuart",
+               .data = &imx7ulp_data,
+       }, {
+               /* sentinel */
+       }
+};
+MODULE_DEVICE_TABLE(of, lpuart32_serial_dt_ids);
+
+static struct driver lpuart32_serial_driver = {
+       .name   = "lpuart32-serial",
+       .probe  = lpuart32_serial_probe,
+       .of_compatible = DRV_OF_COMPAT(lpuart32_serial_dt_ids),
+};
+console_platform_driver(lpuart32_serial_driver);
diff --git a/include/serial/lpuart32.h b/include/serial/lpuart32.h
new file mode 100644
index 0000000000..bcfd067113
--- /dev/null
+++ b/include/serial/lpuart32.h
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2016 Zodiac Inflight Innovation
+ * Author: Andrey Smirnov <andrew.smir...@gmail.com>
+ *
+ * Based on code found in Linux kernel and U-Boot.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms 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.
+ *
+ */
+
+#ifndef __lpuart32_H__
+#define __lpuart32_H__
+
+
+/* 32-bit register definition */
+#define LPUART32_UARTBAUD              0x00
+#define LPUART32_UARTSTAT              0x04
+#define LPUART32_UARTCTRL              0x08
+#define LPUART32_UARTDATA              0x0C
+#define LPUART32_UARTMATCH             0x10
+#define LPUART32_UARTMODIR             0x14
+#define LPUART32_UARTFIFO              0x18
+#define LPUART32_UARTWATER             0x1c
+
+#define LPUART32_UARTBAUD_MAEN1                0x80000000
+#define LPUART32_UARTBAUD_MAEN2                0x40000000
+#define LPUART32_UARTBAUD_M10          0x20000000
+#define LPUART32_UARTBAUD_TDMAE                0x00800000
+#define LPUART32_UARTBAUD_RDMAE                0x00200000
+#define LPUART32_UARTBAUD_MATCFG       0x00400000
+#define LPUART32_UARTBAUD_BOTHEDGE     0x00020000
+#define LPUART32_UARTBAUD_RESYNCDIS    0x00010000
+#define LPUART32_UARTBAUD_LBKDIE       0x00008000
+#define LPUART32_UARTBAUD_RXEDGIE      0x00004000
+#define LPUART32_UARTBAUD_SBNS         0x00002000
+#define LPUART32_UARTBAUD_SBR          0x00000000
+#define LPUART32_UARTBAUD_SBR_MASK     0x1fff
+#define LPUART32_UARTBAUD_OSR_MASK     0x1f
+#define LPUART32_UARTBAUD_OSR_SHIFT    24
+
+#define LPUART32_UARTSTAT_LBKDIF       0x80000000
+#define LPUART32_UARTSTAT_RXEDGIF      0x40000000
+#define LPUART32_UARTSTAT_MSBF         0x20000000
+#define LPUART32_UARTSTAT_RXINV                0x10000000
+#define LPUART32_UARTSTAT_RWUID                0x08000000
+#define LPUART32_UARTSTAT_BRK13                0x04000000
+#define LPUART32_UARTSTAT_LBKDE                0x02000000
+#define LPUART32_UARTSTAT_RAF          0x01000000
+#define LPUART32_UARTSTAT_TDRE         0x00800000
+#define LPUART32_UARTSTAT_TC           0x00400000
+#define LPUART32_UARTSTAT_RDRF         0x00200000
+#define LPUART32_UARTSTAT_IDLE         0x00100000
+#define LPUART32_UARTSTAT_OR           0x00080000
+#define LPUART32_UARTSTAT_NF           0x00040000
+#define LPUART32_UARTSTAT_FE           0x00020000
+#define LPUART32_UARTSTAT_PE           0x00010000
+#define LPUART32_UARTSTAT_MA1F         0x00008000
+#define LPUART32_UARTSTAT_M21F         0x00004000
+
+#define LPUART32_UARTCTRL_R8T9         0x80000000
+#define LPUART32_UARTCTRL_R9T8         0x40000000
+#define LPUART32_UARTCTRL_TXDIR                0x20000000
+#define LPUART32_UARTCTRL_TXINV                0x10000000
+#define LPUART32_UARTCTRL_ORIE         0x08000000
+#define LPUART32_UARTCTRL_NEIE         0x04000000
+#define LPUART32_UARTCTRL_FEIE         0x02000000
+#define LPUART32_UARTCTRL_PEIE         0x01000000
+#define LPUART32_UARTCTRL_TIE          0x00800000
+#define LPUART32_UARTCTRL_TCIE         0x00400000
+#define LPUART32_UARTCTRL_RIE          0x00200000
+#define LPUART32_UARTCTRL_ILIE         0x00100000
+#define LPUART32_UARTCTRL_TE           0x00080000
+#define LPUART32_UARTCTRL_RE           0x00040000
+#define LPUART32_UARTCTRL_RWU          0x00020000
+#define LPUART32_UARTCTRL_SBK          0x00010000
+#define LPUART32_UARTCTRL_MA1IE                0x00008000
+#define LPUART32_UARTCTRL_MA2IE                0x00004000
+#define LPUART32_UARTCTRL_IDLECFG      GENMASK(10, 8)
+#define LPUART32_UARTCTRL_LOOPS                0x00000080
+#define LPUART32_UARTCTRL_DOZEEN       0x00000040
+#define LPUART32_UARTCTRL_RSRC         0x00000020
+#define LPUART32_UARTCTRL_M            0x00000010
+#define LPUART32_UARTCTRL_WAKE         0x00000008
+#define LPUART32_UARTCTRL_ILT          0x00000004
+#define LPUART32_UARTCTRL_PE           0x00000002
+#define LPUART32_UARTCTRL_PT           0x00000001
+
+#define LPUART32_UARTDATA_NOISY                0x00008000
+#define LPUART32_UARTDATA_PARITYE      0x00004000
+#define LPUART32_UARTDATA_FRETSC       0x00002000
+#define LPUART32_UARTDATA_RXEMPT       0x00001000
+#define LPUART32_UARTDATA_IDLINE       0x00000800
+#define LPUART32_UARTDATA_MASK         0x3ff
+
+#define LPUART32_UARTMODIR_IREN                0x00020000
+#define LPUART32_UARTMODIR_RTSWATER    GENMASK(10, 8)
+#define LPUART32_UARTMODIR_TXCTSSRC    0x00000020
+#define LPUART32_UARTMODIR_TXCTSC      0x00000010
+#define LPUART32_UARTMODIR_RXRTSE      0x00000008
+#define LPUART32_UARTMODIR_TXRTSPOL    0x00000004
+#define LPUART32_UARTMODIR_TXRTSE      0x00000002
+#define LPUART32_UARTMODIR_TXCTSE      0x00000001
+
+#define LPUART32_UARTFIFO_TXEMPT       0x00800000
+#define LPUART32_UARTFIFO_RXEMPT       0x00400000
+#define LPUART32_UARTFIFO_TXOF         0x00020000
+#define LPUART32_UARTFIFO_RXUF         0x00010000
+#define LPUART32_UARTFIFO_TXFLUSH      0x00008000
+#define LPUART32_UARTFIFO_RXFLUSH      0x00004000
+#define LPUART32_UARTFIFO_RXIDEN       GENMASK(12, 10)
+#define LPUART32_UARTFIFO_TXOFE                0x00000200
+#define LPUART32_UARTFIFO_RXUFE                0x00000100
+#define LPUART32_UARTFIFO_TXFE         0x00000080
+#define LPUART32_UARTFIFO_FIFOSIZE_MASK        0x7
+#define LPUART32_UARTFIFO_TXSIZE_OFF   4
+#define LPUART32_UARTFIFO_RXFE         0x00000008
+#define LPUART32_UARTFIFO_RXSIZE_OFF   0
+#define LPUART32_UARTFIFO_DEPTH(x)     (0x1 << ((x) ? ((x) + 1) : 0))
+
+#define LPUART32_UARTWATER_COUNT_MASK  0xff
+#define LPUART32_UARTWATER_TXCNT_OFF   8
+#define LPUART32_UARTWATER_RXCNT_OFF   24
+#define LPUART32_UARTWATER_WATER_MASK  0xff
+#define LPUART32_UARTWATER_TXWATER_OFF 0
+#define LPUART32_UARTWATER_RXWATER_OFF 16
+
+static inline void lpuart32_setbrg(void __iomem *base,
+                                unsigned int refclock,
+                                unsigned int baudrate)
+{
+       u32 sbr;
+
+       sbr = (refclock / (16 * baudrate));
+       writel(sbr, base + LPUART32_UARTBAUD);
+}
+
+static inline void lpuart32_setup(void __iomem *base,
+                               unsigned int refclock)
+{
+       lpuart32_setbrg(base, refclock, CONFIG_BAUDRATE);
+       writel(LPUART32_UARTCTRL_TE | LPUART32_UARTCTRL_RE, base + 
LPUART32_UARTCTRL);
+}
+
+static inline void lpuart32_putc(void __iomem *base, int c)
+{
+       while (!(readl(base + LPUART32_UARTSTAT) & LPUART32_UARTSTAT_TDRE));
+
+       writel(c, base + LPUART32_UARTDATA);
+}
+
+#endif
-- 
2.39.2


Reply via email to