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.hauer@xxxxxxxxxxxxxx> --- 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.smirnov@xxxxxxxxx> + * + * 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