Linux Serial subsystem development
 help / color / mirror / Atom feed
* [PATCH 10/13] serial: linflexuart: Add support for changing baudrate
From: Larisa Grigore @ 2026-02-16 15:02 UTC (permalink / raw)
  To: gregkh, jirislaby, robh, krzk+dt, conor+dt, sumit.semwal,
	christian.koenig, chester62515, cosmin.stoica, adrian.nitu,
	stefan-gabriel.mirea, Mihaela.Martinas
  Cc: linux-kernel, linux-serial, devicetree, linux-media, dri-devel,
	linaro-mm-sig, s32, imx, clizzi, aruizrui, eballetb, echanude,
	jkangas, Radu Pirea, Larisa Grigore
In-Reply-To: <20260216150205.212318-1-larisa.grigore@oss.nxp.com>

From: Radu Pirea <radu-nicolae.pirea@nxp.com>

This patch adds support for dynamically configuring the baudrate of the
LINFlexD UART.
It introduces clock handling via clk and clk_ipg, and updates the
linflex_set_termios() function to compute and update the baudrate
related registers (LINIBRR and LINFBRR) based on the selected baudrate
and clock rate.
Baudrate is calculated with the following equation:
- When UARTCR[ROSE] = 1 (reduced oversampling), baudrate = LIN_CLK ÷
(OSR × LDIV).
- When UARTCR[ROSE] = 0, baudrate = LIN_CLK ÷ (16 × LDIV),
where LIN_CLK is the frequency of the baud clock.
LDIV is an unsigned fixed-point number:
- LINIBRR[IBR] stores the mantissa.
- LINFBRR[FBR] stores the fraction. This register isn't used in reduced
oversampling case.

This feature is supported only if the clock properties are present in
the device tree.

Signed-off-by: Radu Pirea <radu-nicolae.pirea@nxp.com>
Co-developed-by: Stefan-Gabriel Mirea <stefan-gabriel.mirea@nxp.com>
Signed-off-by: Stefan-Gabriel Mirea <stefan-gabriel.mirea@nxp.com>
Co-developed-by: Adrian.Nitu <adrian.nitu@freescale.com>
Signed-off-by: Adrian.Nitu <adrian.nitu@freescale.com>
Co-developed-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
Signed-off-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
---
 drivers/tty/serial/fsl_linflexuart.c | 124 +++++++++++++++++++++++++--
 1 file changed, 116 insertions(+), 8 deletions(-)

diff --git a/drivers/tty/serial/fsl_linflexuart.c b/drivers/tty/serial/fsl_linflexuart.c
index fb5f325416c0..36c8f90d975d 100644
--- a/drivers/tty/serial/fsl_linflexuart.c
+++ b/drivers/tty/serial/fsl_linflexuart.c
@@ -3,9 +3,10 @@
  * Freescale LINFlexD UART serial port driver
  *
  * Copyright 2012-2016 Freescale Semiconductor, Inc.
- * Copyright 2017-2019, 2021 NXP
+ * Copyright 2017-2019, 2021-2022 NXP
  */
 
+#include <linux/clk.h>
 #include <linux/console.h>
 #include <linux/io.h>
 #include <linux/irq.h>
@@ -131,6 +132,22 @@
 
 #define PREINIT_DELAY			2000 /* us */
 
+enum linflex_clk {
+	LINFLEX_CLK_LIN,
+	LINFLEX_CLK_IPG,
+	LINFLEX_CLK_NUM,
+};
+
+static const char * const linflex_clks_id[] = {
+	"lin",
+	"ipg",
+};
+
+struct linflex_port {
+	struct uart_port	port;
+	struct clk_bulk_data	clks[LINFLEX_CLK_NUM];
+};
+
 static const struct of_device_id linflex_dt_ids[] = {
 	{
 		.compatible = "fsl,s32v234-linflexuart",
@@ -421,6 +438,19 @@ static void linflex_shutdown(struct uart_port *port)
 	devm_free_irq(port->dev, port->irq, port);
 }
 
+static unsigned char
+linflex_ldiv_multiplier(struct uart_port *port)
+{
+	unsigned char mul = LINFLEX_LDIV_MULTIPLIER;
+	unsigned long cr;
+
+	cr = readl(port->membase + UARTCR);
+	if (cr & LINFLEXD_UARTCR_ROSE)
+		mul = LINFLEXD_UARTCR_OSR(cr);
+
+	return mul;
+}
+
 static void
 linflex_set_termios(struct uart_port *port, struct ktermios *termios,
 		    const struct ktermios *old)
@@ -428,6 +458,9 @@ linflex_set_termios(struct uart_port *port, struct ktermios *termios,
 	unsigned long flags;
 	unsigned long cr, old_cr, cr1;
 	unsigned int old_csize = old ? old->c_cflag & CSIZE : CS8;
+	unsigned long ibr, fbr, divisr, dividr;
+	unsigned char ldiv_mul;
+	unsigned int baud;
 
 	uart_port_lock_irqsave(port, &flags);
 
@@ -532,6 +565,24 @@ linflex_set_termios(struct uart_port *port, struct ktermios *termios,
 			port->ignore_status_mask |= LINFLEXD_UARTSR_BOF;
 	}
 
+	if (port->uartclk) {
+		ldiv_mul = linflex_ldiv_multiplier(port);
+		baud = uart_get_baud_rate(port, termios, old, 0,
+					  port->uartclk / ldiv_mul);
+
+		/* update the per-port timeout */
+		uart_update_timeout(port, termios->c_cflag, baud);
+
+		divisr = port->uartclk;
+		dividr = ((unsigned long)baud * ldiv_mul);
+
+		ibr = divisr / dividr;
+		fbr = ((divisr % dividr) * 16 / dividr) & 0xF;
+
+		writel(ibr, port->membase + LINIBRR);
+		writel(fbr, port->membase + LINFBRR);
+	}
+
 	writel(cr, port->membase + UARTCR);
 
 	cr1 &= ~(LINFLEXD_LINCR1_INIT);
@@ -760,17 +811,52 @@ static struct uart_driver linflex_reg = {
 	.cons		= LINFLEX_CONSOLE,
 };
 
+static int linflex_init_clk(struct linflex_port *lfport)
+{
+	int i, ret;
+
+	for (i = 0; i < LINFLEX_CLK_NUM; i++) {
+		lfport->clks[i].id = linflex_clks_id[i];
+		lfport->clks[i].clk = NULL;
+	}
+
+	ret = devm_clk_bulk_get(lfport->port.dev, LINFLEX_CLK_NUM,
+				lfport->clks);
+	if (ret) {
+		if (ret == -EPROBE_DEFER)
+			return ret;
+
+		lfport->port.uartclk = 0;
+		dev_info(lfport->port.dev,
+			 "uart clock is missing, err = %d. Skipping clock setup.\n",
+			 ret);
+		return 0;
+	}
+
+	ret = clk_bulk_prepare_enable(LINFLEX_CLK_NUM, lfport->clks);
+	if (ret)
+		return dev_err_probe(lfport->port.dev, ret,
+				     "Failed to enable LINFlexD clocks.\n");
+
+	lfport->port.uartclk = clk_get_rate(lfport->clks[LINFLEX_CLK_LIN].clk);
+
+	return 0;
+}
+
 static int linflex_probe(struct platform_device *pdev)
 {
 	struct device_node *np = pdev->dev.of_node;
+	struct linflex_port *lfport;
 	struct uart_port *sport;
 	struct resource *res;
 	int ret;
 
-	sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
-	if (!sport)
+	lfport = devm_kzalloc(&pdev->dev, sizeof(*lfport), GFP_KERNEL);
+	if (!lfport)
 		return -ENOMEM;
 
+	sport = &lfport->port;
+
 	ret = of_alias_get_id(np, "serial");
 	if (ret < 0) {
 		dev_err(&pdev->dev, "failed to get alias id, errno %d\n", ret);
@@ -800,33 +886,55 @@ static int linflex_probe(struct platform_device *pdev)
 	sport->flags = UPF_BOOT_AUTOCONF;
 	sport->has_sysrq = IS_ENABLED(CONFIG_SERIAL_FSL_LINFLEXUART_CONSOLE);
 
+	ret = linflex_init_clk(lfport);
+	if (ret)
+		return ret;
+
 	linflex_ports[sport->line] = sport;
 
-	platform_set_drvdata(pdev, sport);
+	platform_set_drvdata(pdev, lfport);
+
+	ret = uart_add_one_port(&linflex_reg, sport);
+	if (ret)
+		clk_bulk_disable_unprepare(LINFLEX_CLK_NUM, lfport->clks);
 
-	return uart_add_one_port(&linflex_reg, sport);
+	return ret;
 }
 
 static void linflex_remove(struct platform_device *pdev)
 {
-	struct uart_port *sport = platform_get_drvdata(pdev);
+	struct linflex_port *lfport = platform_get_drvdata(pdev);
+	struct uart_port *sport = &lfport->port;
 
 	uart_remove_one_port(&linflex_reg, sport);
+	clk_bulk_disable_unprepare(LINFLEX_CLK_NUM, lfport->clks);
 }
 
 #ifdef CONFIG_PM_SLEEP
 static int linflex_suspend(struct device *dev)
 {
-	struct uart_port *sport = dev_get_drvdata(dev);
+	struct linflex_port *lfport = dev_get_drvdata(dev);
+	struct uart_port *sport = &lfport->port;
 
 	uart_suspend_port(&linflex_reg, sport);
+	clk_bulk_disable_unprepare(LINFLEX_CLK_NUM, lfport->clks);
 
 	return 0;
 }
 
 static int linflex_resume(struct device *dev)
 {
-	struct uart_port *sport = dev_get_drvdata(dev);
+	struct linflex_port *lfport = dev_get_drvdata(dev);
+	struct uart_port *sport = &lfport->port;
+	int ret;
+
+	if (lfport->clks[LINFLEX_CLK_LIN].clk) {
+		ret = clk_bulk_prepare_enable(LINFLEX_CLK_NUM, lfport->clks);
+		if (ret) {
+			dev_err(dev, "Failed to enable LINFlexD clocks: %d\n", ret);
+			return ret;
+		}
+	}
 
 	uart_resume_port(&linflex_reg, sport);
 
-- 
2.47.0


^ permalink raw reply related

* [PATCH 09/13] dt-bindings: serial: fsl-linflexuart: add dma properties
From: Larisa Grigore @ 2026-02-16 15:02 UTC (permalink / raw)
  To: gregkh, jirislaby, robh, krzk+dt, conor+dt, sumit.semwal,
	christian.koenig, chester62515, cosmin.stoica, adrian.nitu,
	stefan-gabriel.mirea, Mihaela.Martinas
  Cc: linux-kernel, linux-serial, devicetree, linux-media, dri-devel,
	linaro-mm-sig, s32, imx, clizzi, aruizrui, eballetb, echanude,
	jkangas, Radu Pirea, Larisa Grigore
In-Reply-To: <20260216150205.212318-1-larisa.grigore@oss.nxp.com>

From: Radu Pirea <radu-nicolae.pirea@nxp.com>

Add 'dmas' and 'dma-names' properties to describe optional DMA support
for RX and TX channels in the LINFlexD UART controller.

This allows the device tree to specify DMA channels used for UART data
transfers. If not specified, the driver will fall to interrupt-based
operations.

Signed-off-by: Radu Pirea <radu-nicolae.pirea@nxp.com>
Co-developed-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
Signed-off-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
---
 .../bindings/serial/fsl,s32-linflexuart.yaml        | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/Documentation/devicetree/bindings/serial/fsl,s32-linflexuart.yaml b/Documentation/devicetree/bindings/serial/fsl,s32-linflexuart.yaml
index 885f0b1b3492..317f9ba41c06 100644
--- a/Documentation/devicetree/bindings/serial/fsl,s32-linflexuart.yaml
+++ b/Documentation/devicetree/bindings/serial/fsl,s32-linflexuart.yaml
@@ -42,6 +42,16 @@ properties:
       - const: lin
       - const: ipg
 
+  dmas:
+    items:
+      - description: DMA controller phandle and request line for RX
+      - description: DMA controller phandle and request line for TX
+
+  dma-names:
+    items:
+      - const: rx
+      - const: tx
+
 required:
   - compatible
   - reg
@@ -65,4 +75,7 @@ examples:
         interrupts = <0 82 1>;
         clocks = <&clks 14>, <&clks 13>;
         clock-names = "lin", "ipg";
+        dmas = <&edma0 0 4>,
+               <&edma0 0 3>;
+        dma-names = "rx", "tx";
     };
-- 
2.47.0


^ permalink raw reply related

* [PATCH 08/13] dt-bindings: serial: fsl-linflexuart: add clock input properties
From: Larisa Grigore @ 2026-02-16 15:02 UTC (permalink / raw)
  To: gregkh, jirislaby, robh, krzk+dt, conor+dt, sumit.semwal,
	christian.koenig, chester62515, cosmin.stoica, adrian.nitu,
	stefan-gabriel.mirea, Mihaela.Martinas
  Cc: linux-kernel, linux-serial, devicetree, linux-media, dri-devel,
	linaro-mm-sig, s32, imx, clizzi, aruizrui, eballetb, echanude,
	jkangas, Radu Pirea, Larisa Grigore
In-Reply-To: <20260216150205.212318-1-larisa.grigore@oss.nxp.com>

From: Radu Pirea <radu-nicolae.pirea@nxp.com>

Add optional support for the two clock inputs used by the LINFlexD UART
controller:
- "lin": LIN_BAUD_CLK
- "ipg": LINFLEXD_CLK

The clock inputs are kept optional to maintain compatibility with the
S32V234 platform.

Signed-off-by: Radu Pirea <radu-nicolae.pirea@nxp.com>
Co-developed-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
Signed-off-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
---
 .../bindings/serial/fsl,s32-linflexuart.yaml   | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/Documentation/devicetree/bindings/serial/fsl,s32-linflexuart.yaml b/Documentation/devicetree/bindings/serial/fsl,s32-linflexuart.yaml
index 4171f524a928..885f0b1b3492 100644
--- a/Documentation/devicetree/bindings/serial/fsl,s32-linflexuart.yaml
+++ b/Documentation/devicetree/bindings/serial/fsl,s32-linflexuart.yaml
@@ -34,6 +34,14 @@ properties:
   interrupts:
     maxItems: 1
 
+  clocks:
+    maxItems: 2
+
+  clock-names:
+    items:
+      - const: lin
+      - const: ipg
+
 required:
   - compatible
   - reg
@@ -48,3 +56,13 @@ examples:
         reg = <0x40053000 0x1000>;
         interrupts = <0 59 4>;
     };
+
+  - |
+    serial@401c8000 {
+        compatible = "nxp,s32g2-linflexuart",
+                     "fsl,s32v234-linflexuart";
+        reg = <0x401C8000 0x3000>;
+        interrupts = <0 82 1>;
+        clocks = <&clks 14>, <&clks 13>;
+        clock-names = "lin", "ipg";
+    };
-- 
2.47.0


^ permalink raw reply related

* [PATCH 07/13] serial: linflexuart: Revert earlycon workaround
From: Larisa Grigore @ 2026-02-16 15:01 UTC (permalink / raw)
  To: gregkh, jirislaby, robh, krzk+dt, conor+dt, sumit.semwal,
	christian.koenig, chester62515, cosmin.stoica, adrian.nitu,
	stefan-gabriel.mirea, Mihaela.Martinas
  Cc: linux-kernel, linux-serial, devicetree, linux-media, dri-devel,
	linaro-mm-sig, s32, imx, clizzi, aruizrui, eballetb, echanude,
	jkangas, Larisa Grigore
In-Reply-To: <20260216150205.212318-1-larisa.grigore@oss.nxp.com>

The workaround is no longer needed, as we now wait for the TX FIFO to be
empty before entering INITM mode.
This ensures proper behavior without requiring the previous earlycon
workaround added in commit
09864c1cdf5c ("tty: serial: Add linflexuart driver for S32V234").

Fixes: 09864c1cdf5c ("tty: serial: Add linflexuart driver for S32V234")
Signed-off-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
---
 drivers/tty/serial/fsl_linflexuart.c | 87 +---------------------------
 1 file changed, 1 insertion(+), 86 deletions(-)

diff --git a/drivers/tty/serial/fsl_linflexuart.c b/drivers/tty/serial/fsl_linflexuart.c
index c1d069dc8089..fb5f325416c0 100644
--- a/drivers/tty/serial/fsl_linflexuart.c
+++ b/drivers/tty/serial/fsl_linflexuart.c
@@ -141,14 +141,6 @@ MODULE_DEVICE_TABLE(of, linflex_dt_ids);
 
 #ifdef CONFIG_SERIAL_FSL_LINFLEXUART_CONSOLE
 static struct uart_port *earlycon_port;
-static bool linflex_earlycon_same_instance;
-static DEFINE_SPINLOCK(init_lock);
-static bool during_init;
-
-static struct {
-	char *content;
-	unsigned int len, cap;
-} earlycon_buf;
 #endif
 
 static inline void linflex_wait_tx_fifo_empty(struct uart_port *port)
@@ -619,48 +611,6 @@ static void linflex_console_putchar(struct uart_port *port, unsigned char ch)
 	}
 }
 
-static void linflex_earlycon_putchar(struct uart_port *port, unsigned char ch)
-{
-	unsigned long flags;
-	char *ret;
-
-	if (!linflex_earlycon_same_instance) {
-		linflex_console_putchar(port, ch);
-		return;
-	}
-
-	spin_lock_irqsave(&init_lock, flags);
-	if (!during_init)
-		goto outside_init;
-
-	if (earlycon_buf.len >= 1 << CONFIG_LOG_BUF_SHIFT)
-		goto init_release;
-
-	if (!earlycon_buf.cap) {
-		earlycon_buf.content = kmalloc(EARLYCON_BUFFER_INITIAL_CAP,
-					       GFP_ATOMIC);
-		earlycon_buf.cap = earlycon_buf.content ?
-				   EARLYCON_BUFFER_INITIAL_CAP : 0;
-	} else if (earlycon_buf.len == earlycon_buf.cap) {
-		ret = krealloc(earlycon_buf.content, earlycon_buf.cap << 1,
-			       GFP_ATOMIC);
-		if (ret) {
-			earlycon_buf.content = ret;
-			earlycon_buf.cap <<= 1;
-		}
-	}
-
-	if (earlycon_buf.len < earlycon_buf.cap)
-		earlycon_buf.content[earlycon_buf.len++] = ch;
-
-	goto init_release;
-
-outside_init:
-	linflex_console_putchar(port, ch);
-init_release:
-	spin_unlock_irqrestore(&init_lock, flags);
-}
-
 static void linflex_string_write(struct uart_port *sport, const char *s,
 				 unsigned int count)
 {
@@ -739,8 +689,6 @@ static int __init linflex_console_setup(struct console *co, char *options)
 	int parity = 'n';
 	int flow = 'n';
 	int ret;
-	int i;
-	unsigned long flags;
 	/*
 	 * check whether an invalid uart number has been specified, and
 	 * if so, search for the first available port that does have
@@ -758,43 +706,10 @@ static int __init linflex_console_setup(struct console *co, char *options)
 	else
 		linflex_console_get_options(sport, &parity, &bits);
 
-	if (earlycon_port && sport->mapbase == earlycon_port->mapbase) {
-		linflex_earlycon_same_instance = true;
-
-		spin_lock_irqsave(&init_lock, flags);
-		during_init = true;
-		spin_unlock_irqrestore(&init_lock, flags);
-
-		/* Workaround for character loss or output of many invalid
-		 * characters, when INIT mode is entered shortly after a
-		 * character has just been printed.
-		 */
-		udelay(PREINIT_DELAY);
-	}
-
 	linflex_setup_watermark(sport);
 
 	ret = uart_set_options(sport, co, baud, parity, bits, flow);
 
-	if (!linflex_earlycon_same_instance)
-		goto done;
-
-	spin_lock_irqsave(&init_lock, flags);
-
-	/* Emptying buffer */
-	if (earlycon_buf.len) {
-		for (i = 0; i < earlycon_buf.len; i++)
-			linflex_console_putchar(earlycon_port,
-				earlycon_buf.content[i]);
-
-		kfree(earlycon_buf.content);
-		earlycon_buf.len = 0;
-	}
-
-	during_init = false;
-	spin_unlock_irqrestore(&init_lock, flags);
-
-done:
 	return ret;
 }
 
@@ -814,7 +729,7 @@ static void linflex_earlycon_write(struct console *con, const char *s,
 {
 	struct earlycon_device *dev = con->data;
 
-	uart_console_write(&dev->port, s, n, linflex_earlycon_putchar);
+	uart_console_write(&dev->port, s, n, linflex_console_putchar);
 }
 
 static int __init linflex_early_console_setup(struct earlycon_device *device,
-- 
2.47.0


^ permalink raw reply related

* [PATCH 06/13] serial: linflexuart: Ensure FIFO is empty when entering INITM
From: Larisa Grigore @ 2026-02-16 15:01 UTC (permalink / raw)
  To: gregkh, jirislaby, robh, krzk+dt, conor+dt, sumit.semwal,
	christian.koenig, chester62515, cosmin.stoica, adrian.nitu,
	stefan-gabriel.mirea, Mihaela.Martinas
  Cc: linux-kernel, linux-serial, devicetree, linux-media, dri-devel,
	linaro-mm-sig, s32, imx, clizzi, aruizrui, eballetb, echanude,
	jkangas, Larisa Grigore
In-Reply-To: <20260216150205.212318-1-larisa.grigore@oss.nxp.com>

In FIFO mode, wait until UARTCR.TDFL_TFC(number Tx FIFO) entries reach 0
before entering INITM mode.
Failing to do so may lead to undefined behavior, such as:
- corrupted characters being printed.
- the device is not able to receive or transmit any character.

In linflex_set_termios, transmission and reception should be disabled
before entering INITM mode, as already done in linflex_setup_watermark.

This patch corrects the behavior that was previously addressed by the
earlycon workaround, making that workaround no longer necessary. The
next patch will remove it.

Fixes: 09864c1cdf5c ("tty: serial: Add linflexuart driver for S32V234")
Signed-off-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
---
 drivers/tty/serial/fsl_linflexuart.c | 45 ++++++++++++++++++++++++++--
 1 file changed, 42 insertions(+), 3 deletions(-)

diff --git a/drivers/tty/serial/fsl_linflexuart.c b/drivers/tty/serial/fsl_linflexuart.c
index 768b3c67a614..c1d069dc8089 100644
--- a/drivers/tty/serial/fsl_linflexuart.c
+++ b/drivers/tty/serial/fsl_linflexuart.c
@@ -3,7 +3,7 @@
  * Freescale LINFlexD UART serial port driver
  *
  * Copyright 2012-2016 Freescale Semiconductor, Inc.
- * Copyright 2017-2019 NXP
+ * Copyright 2017-2019, 2021 NXP
  */
 
 #include <linux/console.h>
@@ -74,6 +74,17 @@
 
 #define LINFLEXD_UARTCR_ROSE		BIT(23)
 
+#define LINFLEXD_UARTCR_RDFLRFC_OFFSET	10
+#define LINFLEXD_UARTCR_RDFLRFC_MASK	(0x7 << LINFLEXD_UARTCR_RDFLRFC_OFFSET)
+#define LINFLEXD_UARTCR_RDFLRFC(uartcr)	(((uartcr) \
+					& LINFLEXD_UARTCR_RDFLRFC_MASK) >> \
+					LINFLEXD_UARTCR_RDFLRFC_OFFSET)
+#define LINFLEXD_UARTCR_TDFLTFC_OFFSET	13
+#define LINFLEXD_UARTCR_TDFLTFC_MASK	(0x7 << LINFLEXD_UARTCR_TDFLTFC_OFFSET)
+#define LINFLEXD_UARTCR_TDFLTFC(uartcr)	(((uartcr) \
+					& LINFLEXD_UARTCR_TDFLTFC_MASK) >> \
+					LINFLEXD_UARTCR_TDFLTFC_OFFSET)
+
 #define LINFLEXD_UARTCR_RFBM		BIT(9)
 #define LINFLEXD_UARTCR_TFBM		BIT(8)
 #define LINFLEXD_UARTCR_WL1		BIT(7)
@@ -140,6 +151,17 @@ static struct {
 } earlycon_buf;
 #endif
 
+static inline void linflex_wait_tx_fifo_empty(struct uart_port *port)
+{
+	unsigned long cr = readl(port->membase + UARTCR);
+
+	if (!(cr & LINFLEXD_UARTCR_TFBM))
+		return;
+
+	while (LINFLEXD_UARTCR_TDFLTFC(readl(port->membase + UARTCR)))
+		;
+}
+
 static void linflex_stop_tx(struct uart_port *port)
 {
 	unsigned long ier;
@@ -326,6 +348,11 @@ static void linflex_setup_watermark(struct uart_port *sport)
 	cr &= ~(LINFLEXD_UARTCR_RXEN | LINFLEXD_UARTCR_TXEN);
 	writel(cr, sport->membase + UARTCR);
 
+	/* In FIFO mode, we should make sure the fifo is empty
+	 * before entering INITM.
+	 */
+	linflex_wait_tx_fifo_empty(sport);
+
 	/* Enter initialization mode by setting INIT bit */
 
 	/* set the Linflex in master mode and activate by-pass filter */
@@ -412,8 +439,17 @@ linflex_set_termios(struct uart_port *port, struct ktermios *termios,
 
 	uart_port_lock_irqsave(port, &flags);
 
-	cr = readl(port->membase + UARTCR);
-	old_cr = cr;
+	old_cr = readl(port->membase + UARTCR) &
+		~(LINFLEXD_UARTCR_RXEN | LINFLEXD_UARTCR_TXEN);
+	cr = old_cr;
+
+	/* In FIFO mode, we should make sure the fifo is empty
+	 * before entering INITM.
+	 */
+	linflex_wait_tx_fifo_empty(port);
+
+	/* disable transmit and receive */
+	writel(old_cr, port->membase + UARTCR);
 
 	/* Enter initialization mode by setting INIT bit */
 	cr1 = LINFLEXD_LINCR1_INIT | LINFLEXD_LINCR1_MME;
@@ -510,6 +546,9 @@ linflex_set_termios(struct uart_port *port, struct ktermios *termios,
 
 	writel(cr1, port->membase + LINCR1);
 
+	cr |= (LINFLEXD_UARTCR_TXEN) | (LINFLEXD_UARTCR_RXEN);
+	writel(cr, port->membase + UARTCR);
+
 	uart_port_unlock_irqrestore(port, flags);
 }
 
-- 
2.47.0


^ permalink raw reply related

* [PATCH 05/13] serial: linflexuart: Update RXEN/TXEN outside INITM mode
From: Larisa Grigore @ 2026-02-16 15:01 UTC (permalink / raw)
  To: gregkh, jirislaby, robh, krzk+dt, conor+dt, sumit.semwal,
	christian.koenig, chester62515, cosmin.stoica, adrian.nitu,
	stefan-gabriel.mirea, Mihaela.Martinas
  Cc: linux-kernel, linux-serial, devicetree, linux-media, dri-devel,
	linaro-mm-sig, s32, imx, clizzi, aruizrui, eballetb, echanude,
	jkangas, Larisa Grigore
In-Reply-To: <20260216150205.212318-1-larisa.grigore@oss.nxp.com>

Reception and transmission should not be enabled or disabled while in
INITM mode.
Although the manual does not explicitly forbid modifying RXEN/TXEN
during INITM, this mode is intended for other types of settings.

Fixes: 09864c1cdf5c ("tty: serial: Add linflexuart driver for S32V234")
Signed-off-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
---
 drivers/tty/serial/fsl_linflexuart.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/drivers/tty/serial/fsl_linflexuart.c b/drivers/tty/serial/fsl_linflexuart.c
index a48240b0a5f2..768b3c67a614 100644
--- a/drivers/tty/serial/fsl_linflexuart.c
+++ b/drivers/tty/serial/fsl_linflexuart.c
@@ -350,8 +350,7 @@ static void linflex_setup_watermark(struct uart_port *sport)
 	/* set UART bit to allow writing other bits */
 	writel(LINFLEXD_UARTCR_UART, sport->membase + UARTCR);
 
-	cr = (LINFLEXD_UARTCR_RXEN | LINFLEXD_UARTCR_TXEN |
-	      LINFLEXD_UARTCR_WL0 | LINFLEXD_UARTCR_UART);
+	cr = (LINFLEXD_UARTCR_WL0 | LINFLEXD_UARTCR_UART);
 
 	writel(cr, sport->membase + UARTCR);
 
@@ -359,6 +358,9 @@ static void linflex_setup_watermark(struct uart_port *sport)
 
 	writel(cr1, sport->membase + LINCR1);
 
+	cr |= (LINFLEXD_UARTCR_RXEN | LINFLEXD_UARTCR_TXEN);
+	writel(cr, sport->membase + UARTCR);
+
 	ier = readl(sport->membase + LINIER);
 	ier |= LINFLEXD_LINIER_DRIE;
 	ier |= LINFLEXD_LINIER_DTIE;
-- 
2.47.0


^ permalink raw reply related

* [PATCH 04/13] serial: linflexuart: Correctly clear UARTSR in buffer mode
From: Larisa Grigore @ 2026-02-16 15:01 UTC (permalink / raw)
  To: gregkh, jirislaby, robh, krzk+dt, conor+dt, sumit.semwal,
	christian.koenig, chester62515, cosmin.stoica, adrian.nitu,
	stefan-gabriel.mirea, Mihaela.Martinas
  Cc: linux-kernel, linux-serial, devicetree, linux-media, dri-devel,
	linaro-mm-sig, s32, imx, clizzi, aruizrui, eballetb, echanude,
	jkangas, Larisa Grigore
In-Reply-To: <20260216150205.212318-1-larisa.grigore@oss.nxp.com>

The TX interrupt handler should not clear RX-related fields in UARTSR,
and vice versa.
The handler checks UARTSR.DRFRFE before invoking linflex_rxint, and
UARTSR.DTFTFF before invoking linflex_txint.
Incorrectly clearing these bits may cause the interrupt handler to miss
characters.
Same applies to linflex_console_putchar which should clear only
UARTSR.DTFTFF.

Fixes: 09864c1cdf5c ("tty: serial: Add linflexuart driver for S32V234")
Signed-off-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
---
 drivers/tty/serial/fsl_linflexuart.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/drivers/tty/serial/fsl_linflexuart.c b/drivers/tty/serial/fsl_linflexuart.c
index 9111e7af62ea..a48240b0a5f2 100644
--- a/drivers/tty/serial/fsl_linflexuart.c
+++ b/drivers/tty/serial/fsl_linflexuart.c
@@ -169,7 +169,7 @@ static void linflex_put_char(struct uart_port *sport, unsigned char c)
 				LINFLEXD_UARTSR_DTFTFF)
 		;
 
-	writel(status | LINFLEXD_UARTSR_DTFTFF, sport->membase + UARTSR);
+	writel(LINFLEXD_UARTSR_DTFTFF, sport->membase + UARTSR);
 }
 
 static inline void linflex_transmit_buffer(struct uart_port *sport)
@@ -255,7 +255,8 @@ static irqreturn_t linflex_rxint(int irq, void *dev_id)
 				sport->icount.parity++;
 		}
 
-		writel(status, sport->membase + UARTSR);
+
+		writel(~(u32)LINFLEXD_UARTSR_DTFTFF, sport->membase + UARTSR);
 		status = readl(sport->membase + UARTSR);
 
 		if (brk) {
@@ -573,9 +574,7 @@ static void linflex_console_putchar(struct uart_port *port, unsigned char ch)
 				!= LINFLEXD_UARTSR_DTFTFF)
 			;
 
-		writel((readl(port->membase + UARTSR) |
-					LINFLEXD_UARTSR_DTFTFF),
-					port->membase + UARTSR);
+		writel(LINFLEXD_UARTSR_DTFTFF, port->membase + UARTSR);
 	}
 }
 
-- 
2.47.0


^ permalink raw reply related

* [PATCH 03/13] serial: linflexuart: Check FIFO full before writing
From: Larisa Grigore @ 2026-02-16 15:01 UTC (permalink / raw)
  To: gregkh, jirislaby, robh, krzk+dt, conor+dt, sumit.semwal,
	christian.koenig, chester62515, cosmin.stoica, adrian.nitu,
	stefan-gabriel.mirea, Mihaela.Martinas
  Cc: linux-kernel, linux-serial, devicetree, linux-media, dri-devel,
	linaro-mm-sig, s32, imx, clizzi, aruizrui, eballetb, echanude,
	jkangas, Larisa Grigore
In-Reply-To: <20260216150205.212318-1-larisa.grigore@oss.nxp.com>

In FIFO mode, the transmitter FIFO should be checked to ensure it is not
full before writing a character.

Fixes: 09864c1cdf5c ("tty: serial: Add linflexuart driver for S32V234")
Signed-off-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
---
 drivers/tty/serial/fsl_linflexuart.c | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/drivers/tty/serial/fsl_linflexuart.c b/drivers/tty/serial/fsl_linflexuart.c
index 016011fd8760..9111e7af62ea 100644
--- a/drivers/tty/serial/fsl_linflexuart.c
+++ b/drivers/tty/serial/fsl_linflexuart.c
@@ -555,22 +555,24 @@ static struct uart_port *linflex_ports[UART_NR];
 static void linflex_console_putchar(struct uart_port *port, unsigned char ch)
 {
 	unsigned long cr;
+	bool fifo_mode;
 
 	cr = readl(port->membase + UARTCR);
+	fifo_mode = cr & LINFLEXD_UARTCR_TFBM;
+
+	if (fifo_mode)
+		while (readl(port->membase + UARTSR) &
+					LINFLEXD_UARTSR_DTFTFF)
+			;
 
 	writeb(ch, port->membase + BDRL);
 
-	if (!(cr & LINFLEXD_UARTCR_TFBM))
+	if (!fifo_mode) {
 		while ((readl(port->membase + UARTSR) &
 					LINFLEXD_UARTSR_DTFTFF)
 				!= LINFLEXD_UARTSR_DTFTFF)
 			;
-	else
-		while (readl(port->membase + UARTSR) &
-					LINFLEXD_UARTSR_DTFTFF)
-			;
 
-	if (!(cr & LINFLEXD_UARTCR_TFBM)) {
 		writel((readl(port->membase + UARTSR) |
 					LINFLEXD_UARTSR_DTFTFF),
 					port->membase + UARTSR);
-- 
2.47.0


^ permalink raw reply related

* [PATCH 02/13] serial: linflexuart: Clean SLEEP bit in LINCR1 after suspend
From: Larisa Grigore @ 2026-02-16 15:01 UTC (permalink / raw)
  To: gregkh, jirislaby, robh, krzk+dt, conor+dt, sumit.semwal,
	christian.koenig, chester62515, cosmin.stoica, adrian.nitu,
	stefan-gabriel.mirea, Mihaela.Martinas
  Cc: linux-kernel, linux-serial, devicetree, linux-media, dri-devel,
	linaro-mm-sig, s32, imx, clizzi, aruizrui, eballetb, echanude,
	jkangas, Larisa Grigore
In-Reply-To: <20260216150205.212318-1-larisa.grigore@oss.nxp.com>

When coming back from reset, we need to re-initialize LINCR1 register.
SLEEP bit should be cleared, otherwise we can't enter INITM mode.

Fixes: 09864c1cdf5c ("tty: serial: Add linflexuart driver for S32V234")
Signed-off-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
---
 drivers/tty/serial/fsl_linflexuart.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/drivers/tty/serial/fsl_linflexuart.c b/drivers/tty/serial/fsl_linflexuart.c
index 5a410e2d56ac..016011fd8760 100644
--- a/drivers/tty/serial/fsl_linflexuart.c
+++ b/drivers/tty/serial/fsl_linflexuart.c
@@ -413,8 +413,7 @@ linflex_set_termios(struct uart_port *port, struct ktermios *termios,
 	old_cr = cr;
 
 	/* Enter initialization mode by setting INIT bit */
-	cr1 = readl(port->membase + LINCR1);
-	cr1 |= LINFLEXD_LINCR1_INIT;
+	cr1 = LINFLEXD_LINCR1_INIT | LINFLEXD_LINCR1_MME;
 	writel(cr1, port->membase + LINCR1);
 
 	/* wait for init mode entry */
-- 
2.47.0


^ permalink raw reply related

* [PATCH 01/13] serial: linflexuart: Fix locking in set_termios
From: Larisa Grigore @ 2026-02-16 15:01 UTC (permalink / raw)
  To: gregkh, jirislaby, robh, krzk+dt, conor+dt, sumit.semwal,
	christian.koenig, chester62515, cosmin.stoica, adrian.nitu,
	stefan-gabriel.mirea, Mihaela.Martinas
  Cc: linux-kernel, linux-serial, devicetree, linux-media, dri-devel,
	linaro-mm-sig, s32, imx, clizzi, aruizrui, eballetb, echanude,
	jkangas, Radu Pirea, Larisa Grigore
In-Reply-To: <20260216150205.212318-1-larisa.grigore@oss.nxp.com>

From: Radu Pirea <radu-nicolae.pirea@nxp.com>

Take the port->lock when set_termios is called, otherwise if characters
are sent while IP is in init mode, the IP will hang in an uncertain
state.

Fixes: 09864c1cdf5c ("tty: serial: Add linflexuart driver for S32V234")
Signed-off-by: Radu Pirea <radu-nicolae.pirea@nxp.com>
Signed-off-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
---
 drivers/tty/serial/fsl_linflexuart.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/tty/serial/fsl_linflexuart.c b/drivers/tty/serial/fsl_linflexuart.c
index e70a56de1fce..5a410e2d56ac 100644
--- a/drivers/tty/serial/fsl_linflexuart.c
+++ b/drivers/tty/serial/fsl_linflexuart.c
@@ -407,6 +407,8 @@ linflex_set_termios(struct uart_port *port, struct ktermios *termios,
 	unsigned long cr, old_cr, cr1;
 	unsigned int old_csize = old ? old->c_cflag & CSIZE : CS8;
 
+	uart_port_lock_irqsave(port, &flags);
+
 	cr = readl(port->membase + UARTCR);
 	old_cr = cr;
 
@@ -475,8 +477,6 @@ linflex_set_termios(struct uart_port *port, struct ktermios *termios,
 		cr &= ~LINFLEXD_UARTCR_PCE;
 	}
 
-	uart_port_lock_irqsave(port, &flags);
-
 	port->read_status_mask = 0;
 
 	if (termios->c_iflag & INPCK)
-- 
2.47.0


^ permalink raw reply related

* [PATCH 00/13] Add DMA support for LINFlexD UART driver
From: Larisa Grigore @ 2026-02-16 15:01 UTC (permalink / raw)
  To: gregkh, jirislaby, robh, krzk+dt, conor+dt, sumit.semwal,
	christian.koenig, chester62515, cosmin.stoica, adrian.nitu,
	stefan-gabriel.mirea, Mihaela.Martinas
  Cc: linux-kernel, linux-serial, devicetree, linux-media, dri-devel,
	linaro-mm-sig, s32, imx, clizzi, aruizrui, eballetb, echanude,
	jkangas, Larisa Grigore

This patchset enhances the LINFlexD UART driver and its device tree bindings to
support DMA transfers, configurable clock inputs, dynamic baudrate changes, and
termios features. It also includes a series of fixes and improvements to ensure
reliable operation across various modes and configurations.

The changes added can be summarized as follows:
1. Fixes with respect to FIFO handling, locking, interrupt related registers and
INITM mode transition.
2. Removal of the earlycon workaround, as proper FIFO handling and INITM
transitions now ensure stable behavior.
3. Support for configurable stop bits and dynamic baudrate changes based on
clock inputs and termios settings.
4. Optional DMA support for RX and TX paths, preventing character loss during
high-throughput operations like copy-paste. Cyclic DMA is used for RX to avoid
gaps between transactions.

Larisa Grigore (8):
  serial: linflexuart: Clean SLEEP bit in LINCR1 after suspend
  serial: linflexuart: Check FIFO full before writing
  serial: linflexuart: Correctly clear UARTSR in buffer mode
  serial: linflexuart: Update RXEN/TXEN outside INITM mode
  serial: linflexuart: Ensure FIFO is empty when entering INITM
  serial: linflexuart: Revert earlycon workaround
  serial: linflexuart: Add support for configurable stop bits
  serial: linflexuart: Add DMA support

Radu Pirea (5):
  serial: linflexuart: Fix locking in set_termios
  dt-bindings: serial: fsl-linflexuart: add clock input properties
  dt-bindings: serial: fsl-linflexuart: add dma properties
  serial: linflexuart: Add support for changing baudrate
  serial: linflexuart: Avoid stopping DMA during receive operations

 .../bindings/serial/fsl,s32-linflexuart.yaml  |  31 +
 drivers/tty/serial/fsl_linflexuart.c          | 972 +++++++++++++++---
 2 files changed, 846 insertions(+), 157 deletions(-)

-- 
2.47.0


^ permalink raw reply

* Re: [PATCH v1 2/2] LRW UART: serial: add driver for the LRW UART
From: kernel test robot @ 2026-02-13 17:50 UTC (permalink / raw)
  To: LiuQingtao, gregkh
  Cc: llvm, oe-kbuild-all, jirislaby, robh, krzk+dt, conor+dt,
	neil.armstrong, bjorn.andersson, marex, dev, mani,
	prabhakar.mahadev-lad.rj, linux-serial, linux-riscv,
	liu.wenhong35, liu.qingtao2, hu.yuye, dai.hualiang, deng.weixian,
	jia.yunxiang, bai.lu5, yang.susheng, shen.lin1, tan.hu, zuo.jiang
In-Reply-To: <20260213093334.9217-3-qtliu@mail.ustc.edu.cn>

Hi LiuQingtao,

kernel test robot noticed the following build warnings:

[auto build test WARNING on tty/tty-testing]
[also build test WARNING on tty/tty-next tty/tty-linus robh/for-next usb/usb-testing usb/usb-next usb/usb-linus linus/master v6.19 next-20260212]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/LiuQingtao/LRW-UART-dt-bindings-Add-binding-for-LRW-UART/20260213-173610
base:   https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty.git tty-testing
patch link:    https://lore.kernel.org/r/20260213093334.9217-3-qtliu%40mail.ustc.edu.cn
patch subject: [PATCH v1 2/2] LRW UART: serial: add driver for the LRW UART
config: riscv-allyesconfig (https://download.01.org/0day-ci/archive/20260214/202602140108.kLMOYbwS-lkp@intel.com/config)
compiler: clang version 16.0.6 (https://github.com/llvm/llvm-project 7cbf1a2591520c2491aa35339f227775f4d3adf6)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260214/202602140108.kLMOYbwS-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202602140108.kLMOYbwS-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> drivers/tty/serial/lrw_uart.c:2266:3: warning: variable 'new_mcfg' is uninitialized when used here [-Wuninitialized]
                   new_mcfg |= UARTMCFG_UARTEN | UARTMCFG_TXE;
                   ^~~~~~~~
   drivers/tty/serial/lrw_uart.c:2246:37: note: initialize the variable 'new_mcfg' to silence this warning
           unsigned int old_mcfg = 0, new_mcfg;
                                              ^
                                               = 0
   1 warning generated.


vim +/new_mcfg +2266 drivers/tty/serial/lrw_uart.c

  2240	
  2241	static void
  2242	lrw_uart_console_write(struct console *co, const char *s, unsigned int count)
  2243	{
  2244		struct lrw_uart_port *sup = lrw_uart_console_ports[co->index];
  2245		unsigned int old_fccr = 0, new_fccr;
  2246		unsigned int old_mcfg = 0, new_mcfg;
  2247		unsigned long flags;
  2248		int locked = 1;
  2249	
  2250		clk_enable(sup->clk);
  2251	
  2252		if (oops_in_progress)
  2253			locked = uart_port_trylock_irqsave(&sup->port, &flags);
  2254		else
  2255			uart_port_lock_irqsave(&sup->port, &flags);
  2256	
  2257		/*
  2258		 *	First save the FCCR then disable the interrupts
  2259		 */
  2260		if (!sup->vendor->always_enabled) {
  2261			old_fccr = lrw_uart_read(sup, REG_FCCR);
  2262			new_fccr = old_fccr & ~UARTFCCR_CTSEN;
  2263			lrw_uart_write(new_fccr, sup, REG_FCCR);
  2264	
  2265			old_mcfg = lrw_uart_read(sup, REG_MCFG);
> 2266			new_mcfg |= UARTMCFG_UARTEN | UARTMCFG_TXE;
  2267			lrw_uart_write(new_mcfg, sup, REG_MCFG);
  2268		}
  2269	
  2270		uart_console_write(&sup->port, s, count, lrw_uart_console_putchar);
  2271	
  2272		/*
  2273		 *	Finally, wait for transmitter to become empty and restore the
  2274		 *	TCR. Allow feature register bits to be inverted to work around
  2275		 *	errata.
  2276		 */
  2277		while ((lrw_uart_read(sup, REG_FR) ^ sup->vendor->inv_fr)
  2278							& sup->vendor->fr_busy)
  2279			cpu_relax();
  2280		if (!sup->vendor->always_enabled) {
  2281			lrw_uart_write(old_fccr, sup, REG_FCCR);
  2282			lrw_uart_write(old_mcfg, sup, REG_MCFG);
  2283		}
  2284	
  2285		if (locked)
  2286			uart_port_unlock_irqrestore(&sup->port, flags);
  2287	
  2288		clk_disable(sup->clk);
  2289	}
  2290	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* Re: [PATCH v1 2/2] LRW UART: serial: add driver for the LRW UART
From: kernel test robot @ 2026-02-13 16:27 UTC (permalink / raw)
  To: LiuQingtao, gregkh
  Cc: oe-kbuild-all, jirislaby, robh, krzk+dt, conor+dt, neil.armstrong,
	bjorn.andersson, marex, dev, mani, prabhakar.mahadev-lad.rj,
	linux-serial, linux-riscv, liu.wenhong35, liu.qingtao2, hu.yuye,
	dai.hualiang, deng.weixian, jia.yunxiang, bai.lu5, yang.susheng,
	shen.lin1, tan.hu, zuo.jiang
In-Reply-To: <20260213093334.9217-3-qtliu@mail.ustc.edu.cn>

Hi LiuQingtao,

kernel test robot noticed the following build warnings:

[auto build test WARNING on tty/tty-testing]
[also build test WARNING on tty/tty-next tty/tty-linus robh/for-next usb/usb-testing usb/usb-next usb/usb-linus linus/master v6.19 next-20260212]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/LiuQingtao/LRW-UART-dt-bindings-Add-binding-for-LRW-UART/20260213-173610
base:   https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty.git tty-testing
patch link:    https://lore.kernel.org/r/20260213093334.9217-3-qtliu%40mail.ustc.edu.cn
patch subject: [PATCH v1 2/2] LRW UART: serial: add driver for the LRW UART
config: m68k-allmodconfig (https://download.01.org/0day-ci/archive/20260214/202602140029.NXkDToZ7-lkp@intel.com/config)
compiler: m68k-linux-gcc (GCC) 15.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260214/202602140029.NXkDToZ7-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202602140029.NXkDToZ7-lkp@intel.com/

All warnings (new ones prefixed by >>):

   In file included from arch/m68k/include/asm/bug.h:32,
                    from include/linux/bug.h:5,
                    from include/linux/thread_info.h:13,
                    from include/asm-generic/preempt.h:5,
                    from ./arch/m68k/include/generated/asm/preempt.h:1,
                    from include/linux/preempt.h:79,
                    from include/linux/spinlock.h:56,
                    from include/linux/mmzone.h:8,
                    from include/linux/gfp.h:7,
                    from include/linux/umh.h:4,
                    from include/linux/kmod.h:9,
                    from include/linux/module.h:18,
                    from drivers/tty/serial/lrw_uart.c:8:
   drivers/tty/serial/lrw_uart.c: In function 'lrw_uart_dma_rx_irq':
>> drivers/tty/serial/lrw_uart.c:973:23: warning: format '%zu' expects argument of type 'size_t', but argument 6 has type 'long unsigned int' [-Wformat=]
     973 |                       "pending %zu exceeds DMA buffer size %zu\n",
         |                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/asm-generic/bug.h:142:62: note: in definition of macro '__WARN_printf'
     142 |                 warn_slowpath_fmt(__FILE__, __LINE__, taint, arg);      \
         |                                                              ^~~
   include/linux/once_lite.h:31:25: note: in expansion of macro 'WARN'
      31 |                         func(__VA_ARGS__);                              \
         |                         ^~~~
   include/asm-generic/bug.h:185:9: note: in expansion of macro 'DO_ONCE_LITE_IF'
     185 |         DO_ONCE_LITE_IF(condition, WARN, 1, format)
         |         ^~~~~~~~~~~~~~~
   drivers/tty/serial/lrw_uart.c:972:13: note: in expansion of macro 'WARN_ONCE'
     972 |         if (WARN_ONCE(pending > LRW_UART_DMA_BUFFER_SIZE,
         |             ^~~~~~~~~
   drivers/tty/serial/lrw_uart.c:973:62: note: format string is defined here
     973 |                       "pending %zu exceeds DMA buffer size %zu\n",
         |                                                            ~~^
         |                                                              |
         |                                                              unsigned int
         |                                                            %lu
   drivers/tty/serial/lrw_uart.c: In function 'lrw_uart_dma_rx_callback':
   drivers/tty/serial/lrw_uart.c:1022:23: warning: format '%zu' expects argument of type 'size_t', but argument 6 has type 'long unsigned int' [-Wformat=]
    1022 |                       "pending %zu exceeds DMA buffer size %zu\n",
         |                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/asm-generic/bug.h:142:62: note: in definition of macro '__WARN_printf'
     142 |                 warn_slowpath_fmt(__FILE__, __LINE__, taint, arg);      \
         |                                                              ^~~
   include/linux/once_lite.h:31:25: note: in expansion of macro 'WARN'
      31 |                         func(__VA_ARGS__);                              \
         |                         ^~~~
   include/asm-generic/bug.h:185:9: note: in expansion of macro 'DO_ONCE_LITE_IF'
     185 |         DO_ONCE_LITE_IF(condition, WARN, 1, format)
         |         ^~~~~~~~~~~~~~~
   drivers/tty/serial/lrw_uart.c:1021:13: note: in expansion of macro 'WARN_ONCE'
    1021 |         if (WARN_ONCE(pending > LRW_UART_DMA_BUFFER_SIZE,
         |             ^~~~~~~~~
   drivers/tty/serial/lrw_uart.c:1022:62: note: format string is defined here
    1022 |                       "pending %zu exceeds DMA buffer size %zu\n",
         |                                                            ~~^
         |                                                              |
         |                                                              unsigned int
         |                                                            %lu


vim +973 drivers/tty/serial/lrw_uart.c

   943	
   944	static void lrw_uart_dma_rx_irq(struct lrw_uart_port *sup)
   945	{
   946		struct lrw_uart_dmarx_data *dmarx = &sup->dmarx;
   947		struct dma_chan *rxchan = dmarx->chan;
   948		struct lrw_uart_dmabuf *dbuf = dmarx->use_buf_b ?
   949			&dmarx->dbuf_b : &dmarx->dbuf_a;
   950		size_t pending;
   951		struct dma_tx_state state;
   952		enum dma_status dmastat;
   953	
   954		/*
   955		 * Pause the transfer so we can trust the current counter,
   956		 * do this before we pause the LRW UART block, else we may
   957		 * overflow the FIFO.
   958		 */
   959		if (dmaengine_pause(rxchan))
   960			dev_err(sup->port.dev, "unable to pause DMA transfer\n");
   961		dmastat = rxchan->device->device_tx_status(rxchan,
   962							   dmarx->cookie, &state);
   963		if (dmastat != DMA_PAUSED)
   964			dev_err(sup->port.dev, "unable to pause DMA transfer\n");
   965	
   966		/* Disable RX DMA - incoming data will wait in the FIFO */
   967		sup->dmacr &= ~UARTFCCR_RXDMAE;
   968		lrw_uart_write(sup->dmacr, sup, REG_FCCR);
   969		sup->dmarx.running = false;
   970	
   971		pending = dbuf->len - state.residue;
   972		if (WARN_ONCE(pending > LRW_UART_DMA_BUFFER_SIZE,
 > 973			      "pending %zu exceeds DMA buffer size %zu\n",
   974			      pending, LRW_UART_DMA_BUFFER_SIZE))
   975			pending = LRW_UART_DMA_BUFFER_SIZE;
   976		/* Then we terminate the transfer - we now know our residue */
   977		dmaengine_terminate_all(rxchan);
   978	
   979		/*
   980		 * This will take the chars we have so far and insert
   981		 * into the framework.
   982		 */
   983		lrw_uart_dma_rx_chars(sup, pending, dmarx->use_buf_b, true);
   984	
   985		/* Switch buffer & re-trigger DMA job */
   986		dmarx->use_buf_b = !dmarx->use_buf_b;
   987		if (lrw_uart_dma_rx_trigger_dma(sup)) {
   988			dev_dbg(sup->port.dev,
   989				"could not retrigger RX DMA job fall back to interrupt mode\n");
   990			sup->im |= UARTIMSC_RXIM;
   991			lrw_uart_write(sup->im, sup, REG_IMSC);
   992		}
   993	}
   994	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* Re: [PATCH v1 1/2] LRW UART: dt-bindings: Add binding for LRW UART
From: Krzysztof Kozlowski @ 2026-02-13 14:24 UTC (permalink / raw)
  To: LiuQingtao, gregkh
  Cc: jirislaby, robh, krzk+dt, conor+dt, neil.armstrong,
	bjorn.andersson, marex, dev, mani, prabhakar.mahadev-lad.rj,
	linux-serial, linux-riscv, liu.wenhong35, liu.qingtao2, hu.yuye,
	dai.hualiang, deng.weixian, jia.yunxiang, bai.lu5, yang.susheng,
	shen.lin1, tan.hu, zuo.jiang
In-Reply-To: <20260213093334.9217-2-qtliu@mail.ustc.edu.cn>

On 13/02/2026 10:33, LiuQingtao wrote:
> From: Wenhong Liu <liu.wenhong35@zte.com.cn>
> 
> Add documentation for LRW UART devicetree bindings.
> 
> Signed-off-by: Wenhong Liu <liu.wenhong35@zte.com.cn>
> Signed-off-by: Qingtao Liu <liu.qingtao2@zte.com.cn>

Please use scripts/get_maintainers.pl to get a list of necessary people
and lists to CC. It might happen, that command when run on an older
kernel, gives you outdated entries. Therefore please be sure you base
your patches on recent Linux kernel.

Tools like b4 or scripts/get_maintainer.pl provide you proper list of
people, so fix your workflow. Tools might also fail if you work on some
ancient tree (don't, instead use mainline) or work on fork of kernel
(don't, instead use mainline). Just use b4 and everything should be
fine, although remember about `b4 prep --auto-to-cc` if you added new
patches to the patchset.

You missed at least devicetree list (maybe more), so this won't be
tested by automated tooling. Performing review on untested code might be
a waste of time.

Please kindly resend and include all necessary To/Cc entries.

Best regards,
Krzysztof

^ permalink raw reply

* Re: [PATCH v1 2/2] LRW UART: serial: add driver for the LRW UART
From: Krzysztof Kozlowski @ 2026-02-13 10:02 UTC (permalink / raw)
  To: LiuQingtao, gregkh
  Cc: jirislaby, robh, krzk+dt, conor+dt, neil.armstrong,
	bjorn.andersson, marex, dev, mani, prabhakar.mahadev-lad.rj,
	linux-serial, linux-riscv, liu.wenhong35, liu.qingtao2, hu.yuye,
	dai.hualiang, deng.weixian, jia.yunxiang, bai.lu5, yang.susheng,
	shen.lin1, tan.hu, zuo.jiang
In-Reply-To: <20260213093334.9217-3-qtliu@mail.ustc.edu.cn>

On 13/02/2026 10:33, LiuQingtao wrote:
> From: Wenhong Liu <liu.wenhong35@zte.com.cn>
> 
> This commit introduces a serial driver for the LRW UART controller


Please do not use "This commit/patch/change", but imperative mood. See
longer explanation here:
https://elixir.bootlin.com/linux/v6.16/source/Documentation/process/submitting-patches.rst#L94

> 
> Key features implemented:
> - Support for FIFO mode (16-byte depth)
> - Baud rate configuration
> - Standard asynchronous communication formats:
>   * Data bits: 5, 6, 7, 8, 9 bits
>   * Parity: odd, even, fixed, none
>   * Stop bits: 1 or 2 bits
> - Hardware flow control (RTS/CTS)
> - Multiple interrupt reporting mechanisms
> 
> Signed-off-by: Wenhong Liu <liu.wenhong35@zte.com.cn>
> Signed-off-by: Qingtao Liu <liu.qingtao2@zte.com.cn>

Mismatched DCO/SoB.

Run checkpatch on your code.

> ---
>  MAINTAINERS                      |    3 +
>  drivers/tty/serial/Kconfig       |   33 +
>  drivers/tty/serial/Makefile      |    1 +
>  drivers/tty/serial/lrw_uart.c    | 2822 ++++++++++++++++++++++++++++++
>  include/uapi/linux/serial_core.h |    3 +
>  5 files changed, 2862 insertions(+)
>  create mode 100644 drivers/tty/serial/lrw_uart.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index ad6acbe24544..a97fbd205f75 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15041,6 +15041,9 @@ R:	Qingtao Liu <liu.qingtao2@zte.com.cn>
>  L:	linux-serial@vger.kernel.org
>  S:	Maintained
>  F:	Documentation/devicetree/bindings/serial/lrw,lrw-uart.yaml
> +F:	drivers/tty/serial/Kconfig
> +F:	drivers/tty/serial/Makefile

Why do you claim you maintain these files?

> +F:	drivers/tty/serial/lrw_uart.c
>  


...


> +
> +static int lrw_uart_probe(struct platform_device *pdev)
> +{
> +	struct lrw_uart_port *sup;
> +	struct resource *r;
> +	int portnr, ret;
> +	unsigned int clk;
> +	unsigned int baudrate;
> +
> +	/*
> +	 * Check the mandatory baud rate parameter in the DT node early
> +	 * so that we can easily exit with the error.
> +	 */
> +	if (pdev->dev.of_node) {
> +		struct device_node *np = pdev->dev.of_node;
> +
> +		ret = of_property_read_u32(np, "current-speed", &baudrate);

Test your code/DTS - there is no such property allowed and you would see
warnings on DTS.


Best regards,
Krzysztof

^ permalink raw reply

* Re: [PATCH v1 1/2] LRW UART: dt-bindings: Add binding for LRW UART
From: Krzysztof Kozlowski @ 2026-02-13 10:00 UTC (permalink / raw)
  To: LiuQingtao, gregkh
  Cc: jirislaby, robh, krzk+dt, conor+dt, neil.armstrong,
	bjorn.andersson, marex, dev, mani, prabhakar.mahadev-lad.rj,
	linux-serial, linux-riscv, liu.wenhong35, liu.qingtao2, hu.yuye,
	dai.hualiang, deng.weixian, jia.yunxiang, bai.lu5, yang.susheng,
	shen.lin1, tan.hu, zuo.jiang
In-Reply-To: <20260213093334.9217-2-qtliu@mail.ustc.edu.cn>

On 13/02/2026 10:33, LiuQingtao wrote:
> From: Wenhong Liu <liu.wenhong35@zte.com.cn>
> 
> Add documentation for LRW UART devicetree bindings.
> 
> Signed-off-by: Wenhong Liu <liu.wenhong35@zte.com.cn>
> Signed-off-by: Qingtao Liu <liu.qingtao2@zte.com.cn>

Please use subject prefixes matching the subsystem. You can get them for
example with `git log --oneline -- DIRECTORY_OR_FILE` on the directory
your patch is touching. For bindings, the preferred subjects are
explained here:
https://www.kernel.org/doc/html/latest/devicetree/bindings/submitting-patches.html#i-for-patch-submitters


A nit, subject: drop second/last, redundant "bindings". The
"dt-bindings" prefix is already stating that these are bindings.
See also:
https://elixir.bootlin.com/linux/v6.17-rc3/source/Documentation/devicetree/bindings/submitting-patches.rst#L18

> ---
>  .../bindings/serial/lrw,lrw-uart.yaml         | 49 +++++++++++++++++++
>  .../devicetree/bindings/vendor-prefixes.yaml  |  2 +
>  MAINTAINERS                                   |  7 +++
>  3 files changed, 58 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/serial/lrw,lrw-uart.yaml
> 
> diff --git a/Documentation/devicetree/bindings/serial/lrw,lrw-uart.yaml b/Documentation/devicetree/bindings/serial/lrw,lrw-uart.yaml
> new file mode 100644
> index 000000000000..a2d41c278c4f
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/serial/lrw,lrw-uart.yaml
> @@ -0,0 +1,49 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/serial/lrw-uart.yaml#

Never tested, NAK. There are several other issues here, but I am not
going through rest of review if you did not bother to even build test
it. Please open any other recent binding and apply same style here
(filename, descriptions etc), so you won't be repeating SAME mistakes.


> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: LRW serial UART
> +
> +maintainers:
> +  - Wenhong Liu <liu.wenhong35@zte.com.cn>
> +  - Qingtao Liu <liu.qingtao2@zte.com.cn>
> +
> +description: |
> +  Should be something similar to "lrw,<chip>-uart"
> +  for the UART as integrated on a particular chip, It supports
> +  multiple CPU architectures, currently including e.g. RISC-V and ARM.
> +
> +properties:
> +  compatible:
> +    const: lrw,lrw-uart

No way lrw is a chip if this is a company.

> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  clocks:
> +    maxItems: 1
> +
> +required:
> +  - compatible
> +  - reg
> +  - current-speed
> +  - clocks
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    uart0: serial@e0001800 {
> +      compatible = "lrw,lrw-uart";
> +      interrupt-parent = <&aplic0>;
> +      interrupts = <0x12 0x4>;
> +      reg = <0xe0001800 0x100>;
> +      clocks = <&bar_clk>;
> +      current-speed = <115200>;
> +    };
> diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
> index ee7fd3cfe203..ec9bf262f466 100644
> --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
> +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
> @@ -961,6 +961,8 @@ patternProperties:
>      description: Loongson Technology Corporation Limited
>    "^loongmasses,.*":
>      description: Nanjing Loongmasses Ltd.
> +  "^lrw,.*":
> +    description: LRW Corp.

What is the website/domain address?


Best regards,
Krzysztof

^ permalink raw reply

* [PATCH v1 2/2] LRW UART: serial: add driver for the LRW UART
From: LiuQingtao @ 2026-02-13  9:33 UTC (permalink / raw)
  To: gregkh
  Cc: jirislaby, robh, krzk+dt, conor+dt, neil.armstrong,
	bjorn.andersson, marex, dev, mani, prabhakar.mahadev-lad.rj,
	linux-serial, linux-riscv, liu.wenhong35, liu.qingtao2, hu.yuye,
	dai.hualiang, deng.weixian, jia.yunxiang, bai.lu5, yang.susheng,
	shen.lin1, tan.hu, zuo.jiang
In-Reply-To: <20260213093334.9217-1-qtliu@mail.ustc.edu.cn>

From: Wenhong Liu <liu.wenhong35@zte.com.cn>

This commit introduces a serial driver for the LRW UART controller

Key features implemented:
- Support for FIFO mode (16-byte depth)
- Baud rate configuration
- Standard asynchronous communication formats:
  * Data bits: 5, 6, 7, 8, 9 bits
  * Parity: odd, even, fixed, none
  * Stop bits: 1 or 2 bits
- Hardware flow control (RTS/CTS)
- Multiple interrupt reporting mechanisms

Signed-off-by: Wenhong Liu <liu.wenhong35@zte.com.cn>
Signed-off-by: Qingtao Liu <liu.qingtao2@zte.com.cn>
---
 MAINTAINERS                      |    3 +
 drivers/tty/serial/Kconfig       |   33 +
 drivers/tty/serial/Makefile      |    1 +
 drivers/tty/serial/lrw_uart.c    | 2822 ++++++++++++++++++++++++++++++
 include/uapi/linux/serial_core.h |    3 +
 5 files changed, 2862 insertions(+)
 create mode 100644 drivers/tty/serial/lrw_uart.c

diff --git a/MAINTAINERS b/MAINTAINERS
index ad6acbe24544..a97fbd205f75 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15041,6 +15041,9 @@ R:	Qingtao Liu <liu.qingtao2@zte.com.cn>
 L:	linux-serial@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/serial/lrw,lrw-uart.yaml
+F:	drivers/tty/serial/Kconfig
+F:	drivers/tty/serial/Makefile
+F:	drivers/tty/serial/lrw_uart.c
 
 LSILOGIC MPT FUSION DRIVERS (FC/SAS/SPI)
 M:	Sathya Prakash <sathya.prakash@broadcom.com>
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index f86775cfdcc9..a8f2d750c5b4 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1619,6 +1619,39 @@ config SERIAL_ESP32_ACM
 	  snippet may be used:
 	    earlycon=esp32s3acm,mmio32,0x60038000
 
+config SERIAL_LRW_UART
+	tristate "LRW UART support"
+	select SERIAL_CORE
+	help
+	  This option enables support for the LRW Universal Asynchronous
+	  Receiver/Transmitter (UART) serial controller.
+
+	  Select this option if you are building a kernel for a device that
+	  contains a LRW UART IP block.
+
+	  This driver can be built as a module; if so, the module will be
+	  called lrw_uart.
+
+	  If you are using a system with an LRW UART controller, say Y or M here.
+	  If unsure, say N.
+
+config SERIAL_LRW_UART_CONSOLE
+	bool "Console on LRW UART"
+	depends on SERIAL_LRW_UART=y
+	select SERIAL_CORE_CONSOLE
+	select SERIAL_EARLYCON
+	help
+	  Say Y here if you wish to use an LRW UART as the system
+	  console (the system console is the device which receives all kernel
+	  messages and warnings and which allows logins in single user mode).
+
+	  Even if you say Y here, the currently visible framebuffer console
+	  (/dev/tty0) will still be used as the system console by default, but
+	  you can alter that using a kernel command line option such as
+	  "console=ttyLRW0". (Try "man bootparam" or see the documentation of
+	  your boot loader (lilo or loadlin) about how to pass options to the
+	  kernel at boot time.)
+
 endmenu
 
 config SERIAL_MCTRL_GPIO
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index a2ccbc508ec5..0f694c4a4e22 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -94,6 +94,7 @@ obj-$(CONFIG_SERIAL_UARTLITE)		+= uartlite.o
 obj-$(CONFIG_SERIAL_VT8500)		+= vt8500_serial.o
 obj-$(CONFIG_SERIAL_XILINX_PS_UART)	+= xilinx_uartps.o
 obj-$(CONFIG_SERIAL_ZS)			+= zs.o
+obj-$(CONFIG_SERIAL_LRW_UART) 		+= lrw_uart.o
 
 # GPIOLIB helpers for modem control lines
 obj-$(CONFIG_SERIAL_MCTRL_GPIO)	+= serial_mctrl_gpio.o
diff --git a/drivers/tty/serial/lrw_uart.c b/drivers/tty/serial/lrw_uart.c
new file mode 100644
index 000000000000..e2399bef203f
--- /dev/null
+++ b/drivers/tty/serial/lrw_uart.c
@@ -0,0 +1,2822 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  Serial Port driver for LRW
+ *
+ *  Copyright (c) 2025, LRW CORPORATION. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/console.h>
+#include <linux/platform_device.h>
+#include <linux/sysrq.h>
+#include <linux/device.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial_core.h>
+#include <linux/serial.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/scatterlist.h>
+#include <linux/delay.h>
+#include <linux/types.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/sizes.h>
+#include <linux/io.h>
+#include <linux/acpi.h>
+
+#define UART_NR			14
+
+#define ISR_PASS_LIMIT		256
+
+#define LRW_UART_NAME		"lrw-uart"
+
+#define LRW_UART_TTY_PREFIX	"ttyLRW"
+
+/* LRW_UART_TX_FIFO_DEPTH: depth of the TX FIFO (in bytes) */
+#define LRW_UART_TX_FIFO_DEPTH	16
+
+/* LRW_UART_RX_FIFO_DEPTH: depth of the RX FIFO (in bytes) */
+#define LRW_UART_RX_FIFO_DEPTH	16
+
+/* LRW UART register offsets */
+#define UARTDR			0x00	/* Data register */
+#define UARTRSR			0x04	/* Receive status register */
+#define UARTECR			0x04	/* Error clear register */
+#define UARTSC			0x08	/* Special character register */
+#define UARTMDR			0x0C	/* RS485 Muti-drop register */
+#define UARTTAT			0x10	/* RS485 turn-around time register */
+#define UARTFCR			0x14	/* FIFO control register */
+#define UARTFR			0x18	/* Flag register */
+#define UARTIND			0x1C	/* Integer baud rate register */
+#define UARTFD			0x20	/* Fractional baud rate register */
+#define UARTBSR			0x24	/* Baud sample rate register */
+#define	UARTFRCR		0x28	/* Frame control register */
+#define UARTMCFG		0x2C	/* config register */
+#define	UARTMCR			0x30	/* Modem control register */
+#define	UARTIRCR		0x34	/* IrDA mode control register */
+#define UARTIMSC		0x38	/* Interrupt mask set/clear register */
+#define UARTRIS			0x3C	/* Raw interrupt status register */
+#define UARTMIS			0x40	/* Masked interrupt states register */
+#define UARTICR			0x44	/* Interrupt clear register */
+#define UARTFCCR		0x48	/* Flow control register */
+#define UARTRVS			0x58	/* Version register */
+
+#define UARTDR_OE		BIT(11)
+#define UARTDR_BE		BIT(10)
+#define UARTDR_PE		BIT(9)
+#define UARTDR_FE		BIT(8)
+
+#define UARTRSR_OE		BIT(3)
+#define UARTRSR_BE		BIT(2)
+#define UARTRSR_PE		BIT(1)
+#define UARTRSR_FE		BIT(0)
+
+#define UARTFCR_RXFTRS		GENMASK(7, 5)
+#define UARTFCR_RXFTRS_RX1_8	FIELD_PREP_CONST(UARTFCR_RXFTRS, 0)
+#define UARTFCR_RXFTRS_RX2_8	FIELD_PREP_CONST(UARTFCR_RXFTRS, 1)
+#define UARTFCR_RXFTRS_RX4_8	FIELD_PREP_CONST(UARTFCR_RXFTRS, 2)
+#define UARTFCR_RXFTRS_RX6_8	FIELD_PREP_CONST(UARTFCR_RXFTRS, 3)
+#define UARTFCR_RXFTRS_RX7_8	FIELD_PREP_CONST(UARTFCR_RXFTRS, 4)
+#define UARTFCR_TXFTRS		GENMASK(4, 2)
+#define UARTFCR_TXFTRS_TX1_8	FIELD_PREP_CONST(UARTFCR_TXFTRS, 0)
+#define UARTFCR_TXFTRS_TX2_8	FIELD_PREP_CONST(UARTFCR_TXFTRS, 1)
+#define UARTFCR_TXFTRS_TX4_8	FIELD_PREP_CONST(UARTFCR_TXFTRS, 2)
+#define UARTFCR_TXFTRS_TX6_8	FIELD_PREP_CONST(UARTFCR_TXFTRS, 3)
+#define UARTFCR_TXFTRS_TX7_8	FIELD_PREP_CONST(UARTFCR_TXFTRS, 4)
+#define UARTFCR_FEN		BIT(0)
+
+#define UARTFR_RI		BIT(8)
+#define UARTFR_TXFE		BIT(7)
+#define UARTFR_RXFF		BIT(6)
+#define UARTFR_TXFF		(1 << 5)	/* used in ASM */
+#define UARTFR_RXFE		BIT(4)
+#define UARTFR_BUSY		(1 << 3)	/* used in ASM */
+#define UARTFR_DCD		BIT(2)
+#define UARTFR_DSR		BIT(1)
+#define UARTFR_CTS		BIT(0)
+#define UARTFR_TMSK		(UARTFR_TXFF + UARTFR_BUSY)
+
+#define UARTFRCR_STP2		BIT(5)
+#define UARTFRCR_SPS		BIT(4)
+#define UARTFRCR_EOP		BIT(3)
+#define UARTFRCR_PEN		BIT(2)
+#define UARTFRCR_WLEN_8		0x3
+#define UARTFRCR_WLEN_7		0x2
+#define UARTFRCR_WLEN_6		0x1
+#define UARTFRCR_WLEN_5		0x0
+
+#define UARTMCFG_LBE		BIT(7)	/* loopback enable */
+#define UARTMCFG_RXE		BIT(3)	/* receive enable */
+#define UARTMCFG_TXE		BIT(2)	/* transmit enable */
+#define UARTMCFG_BRK		BIT(1)	/* send break */
+#define UARTMCFG_UARTEN		BIT(0)	/* UART enable */
+
+#define UARTMCR_OUT2		BIT(3)	/* OUT2 */
+#define UARTMCR_OUT1		BIT(2)	/* OUT1 */
+#define UARTMCR_RTS		BIT(1)	/* RTS */
+#define UARTMCR_DTR		BIT(0)	/* DTR */
+
+#define UARTIMSC_OEIM		BIT(10)	/* overrun error interrupt mask */
+#define UARTIMSC_BEIM		BIT(9)	/* break error interrupt mask */
+#define UARTIMSC_PEIM		BIT(8)	/* parity error interrupt mask */
+#define UARTIMSC_FEIM		BIT(7)	/* framing error interrupt mask */
+#define UARTIMSC_RTIM		BIT(6)	/* receive timeout interrupt mask */
+#define UARTIMSC_TXIM		BIT(5)	/* transmit interrupt mask */
+#define UARTIMSC_RXIM		BIT(4)	/* receive interrupt mask */
+#define UARTIMSC_DSRMIM		BIT(3)	/* DSR interrupt mask */
+#define UARTIMSC_DCDMIM		BIT(2)	/* DCD interrupt mask */
+#define UARTIMSC_CTSMIM		BIT(1)	/* CTS interrupt mask */
+#define UARTIMSC_RIMIM		BIT(0)	/* RI interrupt mask */
+
+#define UARTICR_OEIC		BIT(10)	/* overrun error interrupt clear */
+#define UARTICR_BEIC		BIT(9)	/* break error interrupt clear */
+#define UARTICR_PEIC		BIT(8)	/* parity error interrupt clear */
+#define UARTICR_FEIC		BIT(7)	/* framing error interrupt clear */
+#define UARTICR_RTIC		BIT(6)	/* receive timeout interrupt clear */
+#define UARTICR_TXIC		BIT(5)	/* transmit interrupt clear */
+#define UARTICR_RXIC		BIT(4)	/* receive interrupt clear */
+#define UARTICR_DSRMIC		BIT(3)	/* DSR interrupt clear */
+#define UARTICR_DCDMIC		BIT(2)	/* DCD interrupt clear */
+#define UARTICR_CTSMIC		BIT(1)	/* CTS interrupt clear */
+#define UARTICR_RIMIC		BIT(0)	/* RI interrupt clear */
+
+#define UARTFCCR_CTSEN		BIT(5)	/* CTS hardware flow control */
+#define UARTFCCR_RTSEN		BIT(4)	/* RTS hardware flow control */
+#define UARTFCCR_DMAONERR	BIT(2)	/* disable dma on error */
+#define UARTFCCR_TXDMAE		BIT(1)	/* enable transmit dma */
+#define UARTFCCR_RXDMAE		BIT(0)	/* enable receive dma */
+
+#define UARTRSR_ANY		(UARTRSR_OE | UARTRSR_BE | UARTRSR_PE | UARTRSR_FE)
+#define UARTFR_MODEM_ANY	(UARTFR_DCD | UARTFR_DSR | UARTFR_CTS)
+
+#define UART_DR_ERROR		(UARTDR_OE | UARTDR_BE | UARTDR_PE | UARTDR_FE)
+#define UART_DUMMY_DR_RX	BIT(16)
+
+enum {
+	REG_DR,
+	REG_FCR,
+	REG_FR,
+	REG_IND,
+	REG_FD,
+	REG_BSR,
+	REG_FRCR,
+	REG_MCFG,
+	REG_MCR,
+	REG_IMSC,
+	REG_RIS,
+	REG_MIS,
+	REG_ICR,
+	REG_FCCR,
+
+	/* The size of the array - must be last */
+	REG_ARRAY_SIZE,
+};
+
+static u16 lrw_uart_std_offsets[REG_ARRAY_SIZE] = {
+	[REG_DR] = UARTDR,
+	[REG_FCR] = UARTFCR,
+	[REG_FR] = UARTFR,
+	[REG_IND] = UARTIND,
+	[REG_FD] = UARTFD,
+	[REG_BSR] = UARTBSR,
+	[REG_FRCR] = UARTFRCR,
+	[REG_MCFG] = UARTMCFG,
+	[REG_MCR] = UARTMCR,
+	[REG_IMSC] = UARTIMSC,
+	[REG_RIS] = UARTRIS,
+	[REG_MIS] = UARTMIS,
+	[REG_ICR] = UARTICR,
+	[REG_FCCR] = UARTFCCR,
+};
+
+/* There is by now at least one vendor with differing details, so handle it */
+struct vendor_data {
+	const u16		*reg_offset;
+	unsigned int		fcr;
+	unsigned int		fr_busy;
+	unsigned int		fr_dsr;
+	unsigned int		fr_cts;
+	unsigned int		fr_ri;
+	unsigned int		inv_fr;
+	bool			access_32b;
+	bool			oversampling;
+	bool			dma_threshold;
+	bool			cts_event_workaround;
+	bool			always_enabled;
+	bool			fixed_options;
+};
+
+static struct vendor_data vendor_lrw = {
+	.reg_offset		= lrw_uart_std_offsets,
+	.fcr			= UARTFCR_RXFTRS_RX4_8 | UARTFCR_TXFTRS_TX4_8 | UARTFCR_FEN,
+	.fr_busy		= UARTFR_BUSY,
+	.fr_dsr			= UARTFR_DSR,
+	.fr_cts			= UARTFR_CTS,
+	.fr_ri			= UARTFR_RI,
+	.access_32b		= true,
+	.oversampling		= false,
+	.dma_threshold		= false,
+	.cts_event_workaround	= false,
+	.always_enabled		= false,
+	.fixed_options		= true,
+};
+
+/* Deals with DMA transactions */
+
+struct lrw_uart_dmabuf {
+	dma_addr_t		dma;
+	size_t			len;
+	char			*buf;
+};
+
+struct lrw_uart_dmarx_data {
+	struct dma_chan		*chan;
+	struct completion	complete;
+	bool			use_buf_b;
+	struct lrw_uart_dmabuf	dbuf_a;
+	struct lrw_uart_dmabuf	dbuf_b;
+	dma_cookie_t		cookie;
+	bool			running;
+	struct timer_list	timer;
+	unsigned int last_residue;
+	unsigned long last_jiffies;
+	bool auto_poll_rate;
+	unsigned int poll_rate;
+	unsigned int poll_timeout;
+};
+
+struct lrw_uart_dmatx_data {
+	struct dma_chan		*chan;
+	dma_addr_t		dma;
+	size_t			len;
+	char			*buf;
+	bool			queued;
+};
+
+struct lrw_uart_data {
+	bool (*dma_filter)(struct dma_chan *chan, void *filter_param);
+	void *dma_rx_param;
+	void *dma_tx_param;
+	bool dma_rx_poll_enable;
+	unsigned int dma_rx_poll_rate;
+	unsigned int dma_rx_poll_timeout;
+	void (*init)(void);
+	void (*exit)(void);
+};
+
+/*
+ * We wrap our port structure around the generic uart_port.
+ */
+struct lrw_uart_port {
+	struct uart_port	port;
+	const u16		*reg_offset;
+	struct clk		*clk;
+	const struct vendor_data *vendor;
+	unsigned int		im;		/* interrupt mask */
+	unsigned int		old_status;
+	unsigned int		fifosize;	/* vendor-specific */
+	unsigned int		fixed_baud;	/* vendor-set fixed baud rate */
+	char			type[12];
+	bool			rs485_tx_started;
+	unsigned int		rs485_tx_drain_interval; /* usecs */
+#ifdef CONFIG_DMA_ENGINE
+	/* DMA stuff */
+	unsigned int		dmacr;		/* dma control reg */
+	bool			using_tx_dma;
+	bool			using_rx_dma;
+	struct lrw_uart_dmarx_data dmarx;
+	struct lrw_uart_dmatx_data dmatx;
+	bool			dma_probed;
+#endif
+};
+
+static unsigned int lrw_uart_tx_empty(struct uart_port *port);
+
+static unsigned int lrw_uart_reg_to_offset(const struct lrw_uart_port *sup,
+	unsigned int reg)
+{
+	return sup->reg_offset[reg];
+}
+
+static unsigned int lrw_uart_read(const struct lrw_uart_port *sup,
+	unsigned int reg)
+{
+	void __iomem *addr = sup->port.membase + lrw_uart_reg_to_offset(sup, reg);
+
+	return (sup->port.iotype == UPIO_MEM32) ?
+		readl_relaxed(addr) : readw_relaxed(addr);
+}
+
+static void lrw_uart_write(unsigned int val, const struct lrw_uart_port *sup,
+	unsigned int reg)
+{
+	void __iomem *addr = sup->port.membase + lrw_uart_reg_to_offset(sup, reg);
+
+	if (sup->port.iotype == UPIO_MEM32)
+		writel_relaxed(val, addr);
+	else
+		writew_relaxed(val, addr);
+}
+
+/*
+ * Reads up to 256 characters from the FIFO or until it's empty and
+ * inserts them into the TTY layer. Returns the number of characters
+ * read from the FIFO.
+ */
+static int lrw_uart_fifo_to_tty(struct lrw_uart_port *sup)
+{
+	unsigned int ch, fifotaken;
+	int sysrq;
+	u16 status;
+	u8 flag;
+
+	for (fifotaken = 0; fifotaken != 256; fifotaken++) {
+		status = lrw_uart_read(sup, REG_FR);
+		if (status & UARTFR_RXFE)
+			break;
+
+		/* Take chars from the FIFO and update status */
+		ch = lrw_uart_read(sup, REG_DR) | UART_DUMMY_DR_RX;
+		flag = TTY_NORMAL;
+		sup->port.icount.rx++;
+
+		if (unlikely(ch & UART_DR_ERROR)) {
+			if (ch & UARTDR_BE) {
+				ch &= ~(UARTDR_FE | UARTDR_PE);
+				sup->port.icount.brk++;
+				if (uart_handle_break(&sup->port))
+					continue;
+			} else if (ch & UARTDR_PE) {
+				sup->port.icount.parity++;
+			} else if (ch & UARTDR_FE) {
+				sup->port.icount.frame++;
+			}
+			if (ch & UARTDR_OE)
+				sup->port.icount.overrun++;
+
+			ch &= sup->port.read_status_mask;
+
+			if (ch & UARTDR_BE)
+				flag = TTY_BREAK;
+			else if (ch & UARTDR_PE)
+				flag = TTY_PARITY;
+			else if (ch & UARTDR_FE)
+				flag = TTY_FRAME;
+		}
+
+		sysrq = uart_prepare_sysrq_char(&sup->port, ch & 255);
+		if (!sysrq)
+			uart_insert_char(&sup->port, ch, UARTDR_OE, ch, flag);
+	}
+
+	return fifotaken;
+}
+
+/*
+ * All the DMA operation mode stuff goes inside this ifdef.
+ * This assumes that you have a generic DMA device interface,
+ * no custom DMA interfaces are supported.
+ */
+#ifdef CONFIG_DMA_ENGINE
+
+#define LRW_UART_DMA_BUFFER_SIZE PAGE_SIZE
+
+static int lrw_uart_dmabuf_init(struct dma_chan *chan, struct lrw_uart_dmabuf *db,
+	enum dma_data_direction dir)
+{
+	db->buf = dma_alloc_coherent(chan->device->dev, LRW_UART_DMA_BUFFER_SIZE,
+				     &db->dma, GFP_KERNEL);
+	if (!db->buf)
+		return -ENOMEM;
+	db->len = LRW_UART_DMA_BUFFER_SIZE;
+
+	return 0;
+}
+
+static void lrw_uart_dmabuf_free(struct dma_chan *chan, struct lrw_uart_dmabuf *db,
+	enum dma_data_direction dir)
+{
+	if (db->buf) {
+		dma_free_coherent(chan->device->dev,
+				  LRW_UART_DMA_BUFFER_SIZE, db->buf, db->dma);
+	}
+}
+
+static void lrw_uart_dma_probe(struct lrw_uart_port *sup)
+{
+	/* DMA is the sole user of the platform data right now */
+	struct lrw_uart_data *plat = dev_get_platdata(sup->port.dev);
+	struct device *dev = sup->port.dev;
+	struct dma_slave_config tx_conf = {
+		.dst_addr = sup->port.mapbase +
+				 lrw_uart_reg_to_offset(sup, REG_DR),
+		.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE,
+		.direction = DMA_MEM_TO_DEV,
+		.dst_maxburst = sup->fifosize >> 1,
+		.device_fc = false,
+	};
+	struct dma_chan *chan;
+	dma_cap_mask_t mask;
+
+	sup->dma_probed = true;
+	chan = dma_request_chan(dev, "tx");
+	if (IS_ERR(chan)) {
+		if (PTR_ERR(chan) == -EPROBE_DEFER) {
+			sup->dma_probed = false;
+			return;
+		}
+
+		/* We need platform data */
+		if (!plat || !plat->dma_filter) {
+			dev_dbg(sup->port.dev, "no DMA platform data\n");
+			return;
+		}
+
+		/* Try to acquire a generic DMA engine slave TX channel */
+		dma_cap_zero(mask);
+		dma_cap_set(DMA_SLAVE, mask);
+
+		chan = dma_request_channel(mask, plat->dma_filter,
+					   plat->dma_tx_param);
+		if (!chan) {
+			dev_err(sup->port.dev, "no TX DMA channel!\n");
+			return;
+		}
+	}
+
+	dmaengine_slave_config(chan, &tx_conf);
+	sup->dmatx.chan = chan;
+
+	dev_info(sup->port.dev, "DMA channel TX %s\n",
+		 dma_chan_name(sup->dmatx.chan));
+
+	/* Optionally make use of an RX channel as well */
+	chan = dma_request_chan(dev, "rx");
+
+	if (IS_ERR(chan) && plat && plat->dma_rx_param) {
+		chan = dma_request_channel(mask, plat->dma_filter, plat->dma_rx_param);
+
+		if (!chan) {
+			dev_err(sup->port.dev, "no RX DMA channel!\n");
+			return;
+		}
+	}
+
+	if (!IS_ERR(chan)) {
+		struct dma_slave_config rx_conf = {
+			.src_addr = sup->port.mapbase +
+				lrw_uart_reg_to_offset(sup, REG_DR),
+			.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE,
+			.direction = DMA_DEV_TO_MEM,
+			.src_maxburst = sup->fifosize >> 2,
+			.device_fc = false,
+		};
+		struct dma_slave_caps caps;
+
+		/*
+		 * Some DMA controllers provide information on their capabilities.
+		 * If the controller does, check for suitable residue processing
+		 * otherwise assime all is well.
+		 */
+		if (dma_get_slave_caps(chan, &caps) == 0) {
+			if (caps.residue_granularity ==
+					DMA_RESIDUE_GRANULARITY_DESCRIPTOR) {
+				dma_release_channel(chan);
+				dev_info(sup->port.dev,
+					 "RX DMA disabled - no residue processing\n");
+				return;
+			}
+		}
+		dmaengine_slave_config(chan, &rx_conf);
+		sup->dmarx.chan = chan;
+
+		sup->dmarx.auto_poll_rate = false;
+		if (plat && plat->dma_rx_poll_enable) {
+			/* Set poll rate if specified. */
+			if (plat->dma_rx_poll_rate) {
+				sup->dmarx.auto_poll_rate = false;
+				sup->dmarx.poll_rate = plat->dma_rx_poll_rate;
+			} else {
+				/*
+				 * 100 ms defaults to poll rate if not
+				 * specified. This will be adjusted with
+				 * the baud rate at set_termios.
+				 */
+				sup->dmarx.auto_poll_rate = true;
+				sup->dmarx.poll_rate =  100;
+			}
+			/* 3 secs defaults poll_timeout if not specified. */
+			if (plat->dma_rx_poll_timeout)
+				sup->dmarx.poll_timeout =
+					plat->dma_rx_poll_timeout;
+			else
+				sup->dmarx.poll_timeout = 3000;
+		} else if (!plat && dev->of_node) {
+			sup->dmarx.auto_poll_rate =
+					of_property_read_bool(dev->of_node, "auto-poll");
+			if (sup->dmarx.auto_poll_rate) {
+				u32 x;
+
+				if (of_property_read_u32(dev->of_node, "poll-rate-ms", &x) == 0)
+					sup->dmarx.poll_rate = x;
+				else
+					sup->dmarx.poll_rate = 100;
+				if (of_property_read_u32(dev->of_node, "poll-timeout-ms", &x) == 0)
+					sup->dmarx.poll_timeout = x;
+				else
+					sup->dmarx.poll_timeout = 3000;
+			}
+		}
+		dev_info(sup->port.dev, "DMA channel RX %s\n",
+			 dma_chan_name(sup->dmarx.chan));
+	}
+}
+
+static void lrw_uart_dma_remove(struct lrw_uart_port *sup)
+{
+	if (sup->dmatx.chan)
+		dma_release_channel(sup->dmatx.chan);
+	if (sup->dmarx.chan)
+		dma_release_channel(sup->dmarx.chan);
+}
+
+/* Forward declare these for the refill routine */
+static int lrw_uart_dma_tx_refill(struct lrw_uart_port *sup);
+static void lrw_uart_start_tx_pio(struct lrw_uart_port *sup);
+
+/*
+ * The current DMA TX buffer has been sent.
+ * Try to queue up another DMA buffer.
+ */
+static void lrw_uart_dma_tx_callback(void *data)
+{
+	struct lrw_uart_port *sup = data;
+	struct tty_port *tport = &sup->port.state->port;
+	struct lrw_uart_dmatx_data *dmatx = &sup->dmatx;
+	unsigned long flags;
+	u16 dmacr;
+
+	uart_port_lock_irqsave(&sup->port, &flags);
+	if (sup->dmatx.queued)
+		dma_unmap_single(dmatx->chan->device->dev, dmatx->dma,
+				 dmatx->len, DMA_TO_DEVICE);
+
+	dmacr = sup->dmacr;
+	sup->dmacr = dmacr & ~UARTFCCR_TXDMAE;
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+
+	/*
+	 * If TX DMA was disabled, it means that we've stopped the DMA for
+	 * some reason (eg, XOFF received, or we want to send an X-char.)
+	 *
+	 * Note: we need to be careful here of a potential race between DMA
+	 * and the rest of the driver - if the driver disables TX DMA while
+	 * a TX buffer completing, we must update the tx queued status to
+	 * get further refills (hence we check dmacr).
+	 */
+	if (!(dmacr & UARTFCCR_TXDMAE) || uart_tx_stopped(&sup->port) ||
+	    kfifo_is_empty(&tport->xmit_fifo)) {
+		sup->dmatx.queued = false;
+		uart_port_unlock_irqrestore(&sup->port, flags);
+		return;
+	}
+
+	if (lrw_uart_dma_tx_refill(sup) <= 0)
+		/*
+		 * We didn't queue a DMA buffer for some reason, but we
+		 * have data pending to be sent.  Re-enable the TX IRQ.
+		 */
+		lrw_uart_start_tx_pio(sup);
+
+	uart_port_unlock_irqrestore(&sup->port, flags);
+}
+
+/*
+ * Try to refill the TX DMA buffer.
+ * Locking: called with port lock held and IRQs disabled.
+ * Returns:
+ *   1 if we queued up a TX DMA buffer.
+ *   0 if we didn't want to handle this by DMA
+ *  <0 on error
+ */
+static int lrw_uart_dma_tx_refill(struct lrw_uart_port *sup)
+{
+	struct lrw_uart_dmatx_data *dmatx = &sup->dmatx;
+	struct dma_chan *chan = dmatx->chan;
+	struct dma_device *dma_dev = chan->device;
+	struct dma_async_tx_descriptor *desc;
+	struct tty_port *tport = &sup->port.state->port;
+	unsigned int count;
+
+	/*
+	 * Try to avoid the overhead involved in using DMA if the
+	 * transaction fits in the first half of the FIFO, by using
+	 * the standard interrupt handling.  This ensures that we
+	 * issue a uart_write_wakeup() at the appropriate time.
+	 */
+	count = kfifo_len(&tport->xmit_fifo);
+	if (count < (sup->fifosize >> 1)) {
+		sup->dmatx.queued = false;
+		return 0;
+	}
+
+	/*
+	 * Bodge: don't send the last character by DMA, as this
+	 * will prevent XON from notifying us to restart DMA.
+	 */
+	count -= 1;
+
+	/* Else proceed to copy the TX chars to the DMA buffer and fire DMA */
+	if (count > LRW_UART_DMA_BUFFER_SIZE)
+		count = LRW_UART_DMA_BUFFER_SIZE;
+
+	count = kfifo_out_peek(&tport->xmit_fifo, dmatx->buf, count);
+	dmatx->len = count;
+	dmatx->dma = dma_map_single(dma_dev->dev, dmatx->buf, count,
+				    DMA_TO_DEVICE);
+	if (dmatx->dma == DMA_MAPPING_ERROR) {
+		sup->dmatx.queued = false;
+		dev_dbg(sup->port.dev, "unable to map TX DMA\n");
+		return -EBUSY;
+	}
+
+	desc = dmaengine_prep_slave_single(chan, dmatx->dma, dmatx->len, DMA_MEM_TO_DEV,
+					   DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+	if (!desc) {
+		dma_unmap_single(dma_dev->dev, dmatx->dma, dmatx->len, DMA_TO_DEVICE);
+		sup->dmatx.queued = false;
+		/*
+		 * If DMA cannot be used right now, we complete this
+		 * transaction via IRQ and let the TTY layer retry.
+		 */
+		dev_dbg(sup->port.dev, "TX DMA busy\n");
+		return -EBUSY;
+	}
+
+	/* Some data to go along to the callback */
+	desc->callback = lrw_uart_dma_tx_callback;
+	desc->callback_param = sup;
+
+	/* All errors should happen at prepare time */
+	dmaengine_submit(desc);
+
+	/* Fire the DMA transaction */
+	dma_dev->device_issue_pending(chan);
+
+	sup->dmacr |= UARTFCCR_TXDMAE;
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+	sup->dmatx.queued = true;
+
+	/*
+	 * Now we know that DMA will fire, so advance the ring buffer
+	 * with the stuff we just dispatched.
+	 */
+	uart_xmit_advance(&sup->port, count);
+
+	if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
+		uart_write_wakeup(&sup->port);
+
+	return 1;
+}
+
+/*
+ * We received a transmit interrupt without a pending X-char but with
+ * pending characters.
+ * Locking: called with port lock held and IRQs disabled.
+ * Returns:
+ *   false if we want to use PIO to transmit
+ *   true if we queued a DMA buffer
+ */
+static bool lrw_uart_dma_tx_irq(struct lrw_uart_port *sup)
+{
+	if (!sup->using_tx_dma)
+		return false;
+
+	/*
+	 * If we already have a TX buffer queued, but received a
+	 * TX interrupt, it will be because we've just sent an X-char.
+	 * Ensure the TX DMA is enabled and the TX IRQ is disabled.
+	 */
+	if (sup->dmatx.queued) {
+		sup->dmacr |= UARTFCCR_TXDMAE;
+		lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+		sup->im &= ~UARTIMSC_TXIM;
+		lrw_uart_write(sup->im, sup, REG_IMSC);
+		return true;
+	}
+
+	/*
+	 * We don't have a TX buffer queued, so try to queue one.
+	 * If we successfully queued a buffer, mask the TX IRQ.
+	 */
+	if (lrw_uart_dma_tx_refill(sup) > 0) {
+		sup->im &= ~UARTIMSC_TXIM;
+		lrw_uart_write(sup->im, sup, REG_IMSC);
+		return true;
+	}
+	return false;
+}
+
+/*
+ * Stop the DMA transmit (eg, due to received XOFF).
+ * Locking: called with port lock held and IRQs disabled.
+ */
+static inline void lrw_uart_dma_tx_stop(struct lrw_uart_port *sup)
+{
+	if (sup->dmatx.queued) {
+		sup->dmacr &= ~UARTFCCR_TXDMAE;
+		lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+	}
+}
+
+/*
+ * Try to start a DMA transmit, or in the case of an XON/OFF
+ * character queued for send, try to get that character out ASAP.
+ * Locking: called with port lock held and IRQs disabled.
+ * Returns:
+ *   false if we want the TX IRQ to be enabled
+ *   true if we have a buffer queued
+ */
+static inline bool lrw_uart_dma_tx_start(struct lrw_uart_port *sup)
+{
+	u16 dmacr;
+
+	if (!sup->using_tx_dma)
+		return false;
+
+	if (!sup->port.x_char) {
+		/* no X-char, try to push chars out in DMA mode */
+		bool ret = true;
+
+		if (!sup->dmatx.queued) {
+			if (lrw_uart_dma_tx_refill(sup) > 0) {
+				sup->im &= ~UARTIMSC_TXIM;
+				lrw_uart_write(sup->im, sup, REG_IMSC);
+			} else {
+				ret = false;
+			}
+		} else if (!(sup->dmacr & UARTFCCR_TXDMAE)) {
+			sup->dmacr |= UARTFCCR_TXDMAE;
+			lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+		}
+		return ret;
+	}
+
+	/*
+	 * We have an X-char to send.  Disable DMA to prevent it loading
+	 * the TX fifo, and then see if we can stuff it into the FIFO.
+	 */
+	dmacr = sup->dmacr;
+	sup->dmacr &= ~UARTFCCR_TXDMAE;
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+
+	if (lrw_uart_read(sup, REG_FR) & UARTFR_TXFF) {
+		/*
+		 * No space in the FIFO, so enable the transmit interrupt
+		 * so we know when there is space.  Note that once we've
+		 * loaded the character, we should just re-enable DMA.
+		 */
+		return false;
+	}
+
+	lrw_uart_write(sup->port.x_char, sup, REG_DR);
+	sup->port.icount.tx++;
+	sup->port.x_char = 0;
+
+	/* Success - restore the DMA state */
+	sup->dmacr = dmacr;
+	lrw_uart_write(dmacr, sup, REG_FCCR);
+
+	return true;
+}
+
+/*
+ * Flush the transmit buffer.
+ * Locking: called with port lock held and IRQs disabled.
+ */
+static void lrw_uart_dma_flush_buffer(struct uart_port *port)
+__releases(&sup->port.lock)
+__acquires(&sup->port.lock)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	if (!sup->using_tx_dma)
+		return;
+
+	dmaengine_terminate_async(sup->dmatx.chan);
+
+	if (sup->dmatx.queued) {
+		dma_unmap_single(sup->dmatx.chan->device->dev, sup->dmatx.dma,
+				 sup->dmatx.len, DMA_TO_DEVICE);
+		sup->dmatx.queued = false;
+		sup->dmacr &= ~UARTFCCR_TXDMAE;
+		lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+	}
+}
+
+static void lrw_uart_dma_rx_callback(void *data);
+
+static int lrw_uart_dma_rx_trigger_dma(struct lrw_uart_port *sup)
+{
+	struct dma_chan *rxchan = sup->dmarx.chan;
+	struct lrw_uart_dmarx_data *dmarx = &sup->dmarx;
+	struct dma_async_tx_descriptor *desc;
+	struct lrw_uart_dmabuf *dbuf;
+
+	if (!rxchan)
+		return -EIO;
+
+	/* Start the RX DMA job */
+	dbuf = sup->dmarx.use_buf_b ?
+		&sup->dmarx.dbuf_b : &sup->dmarx.dbuf_a;
+	desc = dmaengine_prep_slave_single(rxchan, dbuf->dma, dbuf->len,
+					   DMA_DEV_TO_MEM,
+					   DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+	/*
+	 * If the DMA engine is busy and cannot prepare a
+	 * channel, no big deal, the driver will fall back
+	 * to interrupt mode as a result of this error code.
+	 */
+	if (!desc) {
+		sup->dmarx.running = false;
+		dmaengine_terminate_all(rxchan);
+		return -EBUSY;
+	}
+
+	/* Some data to go along to the callback */
+	desc->callback = lrw_uart_dma_rx_callback;
+	desc->callback_param = sup;
+	dmarx->cookie = dmaengine_submit(desc);
+	dma_async_issue_pending(rxchan);
+
+	sup->dmacr |= UARTFCCR_RXDMAE;
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+	sup->dmarx.running = true;
+
+	sup->im &= ~UARTIMSC_RXIM;
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+
+	return 0;
+}
+
+/*
+ * This is called when either the DMA job is complete, or
+ * the FIFO timeout interrupt occurred. This must be called
+ * with the port spinlock sup->port.lock held.
+ */
+static void lrw_uart_dma_rx_chars(struct lrw_uart_port *sup,
+				  u32 pending, bool use_buf_b,
+				  bool readfifo)
+{
+	struct tty_port *port = &sup->port.state->port;
+	struct lrw_uart_dmabuf *dbuf = use_buf_b ?
+		&sup->dmarx.dbuf_b : &sup->dmarx.dbuf_a;
+	int dma_count = 0;
+	u32 fifotaken = 0; /* only used for vdbg() */
+
+	struct lrw_uart_dmarx_data *dmarx = &sup->dmarx;
+	int dmataken = 0;
+
+	if (sup->dmarx.poll_rate) {
+		/* The data can be taken by polling */
+		dmataken = dbuf->len - dmarx->last_residue;
+		/* Recalculate the pending size */
+		if (pending >= dmataken)
+			pending -= dmataken;
+	}
+
+	/* Pick the remain data from the DMA */
+	if (pending) {
+		/*
+		 * First take all chars in the DMA pipe, then look in the FIFO.
+		 * Note that tty_insert_flip_buf() tries to take as many chars
+		 * as it can.
+		 */
+		dma_count = tty_insert_flip_string(port, dbuf->buf + dmataken, pending);
+
+		sup->port.icount.rx += dma_count;
+		if (dma_count < pending)
+			dev_warn(sup->port.dev,
+				 "couldn't insert all characters (TTY is full?)\n");
+	}
+
+	/* Reset the last_residue for Rx DMA poll */
+	if (sup->dmarx.poll_rate)
+		dmarx->last_residue = dbuf->len;
+
+	/*
+	 * Only continue with trying to read the FIFO if all DMA chars have
+	 * been taken first.
+	 */
+	if (dma_count == pending && readfifo) {
+		/* Clear any error flags */
+		lrw_uart_write(UARTICR_OEIC | UARTICR_BEIC | UARTICR_PEIC |
+			      UARTICR_FEIC, sup, REG_ICR);
+
+		/*
+		 * If we read all the DMA'd characters, and we had an
+		 * incomplete buffer, that could be due to an rx error, or
+		 * maybe we just timed out. Read any pending chars and check
+		 * the error status.
+		 *
+		 * Error conditions will only occur in the FIFO, these will
+		 * trigger an immediate interrupt and stop the DMA job, so we
+		 * will always find the error in the FIFO, never in the DMA
+		 * buffer.
+		 */
+		fifotaken = lrw_uart_fifo_to_tty(sup);
+	}
+
+	dev_vdbg(sup->port.dev,
+		 "Took %d chars from DMA buffer and %d chars from the FIFO\n",
+		 dma_count, fifotaken);
+	tty_flip_buffer_push(port);
+}
+
+static void lrw_uart_dma_rx_irq(struct lrw_uart_port *sup)
+{
+	struct lrw_uart_dmarx_data *dmarx = &sup->dmarx;
+	struct dma_chan *rxchan = dmarx->chan;
+	struct lrw_uart_dmabuf *dbuf = dmarx->use_buf_b ?
+		&dmarx->dbuf_b : &dmarx->dbuf_a;
+	size_t pending;
+	struct dma_tx_state state;
+	enum dma_status dmastat;
+
+	/*
+	 * Pause the transfer so we can trust the current counter,
+	 * do this before we pause the LRW UART block, else we may
+	 * overflow the FIFO.
+	 */
+	if (dmaengine_pause(rxchan))
+		dev_err(sup->port.dev, "unable to pause DMA transfer\n");
+	dmastat = rxchan->device->device_tx_status(rxchan,
+						   dmarx->cookie, &state);
+	if (dmastat != DMA_PAUSED)
+		dev_err(sup->port.dev, "unable to pause DMA transfer\n");
+
+	/* Disable RX DMA - incoming data will wait in the FIFO */
+	sup->dmacr &= ~UARTFCCR_RXDMAE;
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+	sup->dmarx.running = false;
+
+	pending = dbuf->len - state.residue;
+	if (WARN_ONCE(pending > LRW_UART_DMA_BUFFER_SIZE,
+		      "pending %zu exceeds DMA buffer size %zu\n",
+		      pending, LRW_UART_DMA_BUFFER_SIZE))
+		pending = LRW_UART_DMA_BUFFER_SIZE;
+	/* Then we terminate the transfer - we now know our residue */
+	dmaengine_terminate_all(rxchan);
+
+	/*
+	 * This will take the chars we have so far and insert
+	 * into the framework.
+	 */
+	lrw_uart_dma_rx_chars(sup, pending, dmarx->use_buf_b, true);
+
+	/* Switch buffer & re-trigger DMA job */
+	dmarx->use_buf_b = !dmarx->use_buf_b;
+	if (lrw_uart_dma_rx_trigger_dma(sup)) {
+		dev_dbg(sup->port.dev,
+			"could not retrigger RX DMA job fall back to interrupt mode\n");
+		sup->im |= UARTIMSC_RXIM;
+		lrw_uart_write(sup->im, sup, REG_IMSC);
+	}
+}
+
+static void lrw_uart_dma_rx_callback(void *data)
+{
+	struct lrw_uart_port *sup = data;
+	struct lrw_uart_dmarx_data *dmarx = &sup->dmarx;
+	struct dma_chan *rxchan = dmarx->chan;
+	bool lastbuf = dmarx->use_buf_b;
+	struct lrw_uart_dmabuf *dbuf = dmarx->use_buf_b ?
+		&dmarx->dbuf_b : &dmarx->dbuf_a;
+	size_t pending;
+	struct dma_tx_state state;
+	int ret;
+
+	/*
+	 * This completion interrupt occurs typically when the
+	 * RX buffer is totally stuffed but no timeout has yet
+	 * occurred. When that happens, we just want the RX
+	 * routine to flush out the secondary DMA buffer while
+	 * we immediately trigger the next DMA job.
+	 */
+	uart_port_lock_irq(&sup->port);
+	/*
+	 * Rx data can be taken by the UART interrupts during
+	 * the DMA irq handler. So we check the residue here.
+	 */
+	rxchan->device->device_tx_status(rxchan, dmarx->cookie, &state);
+	pending = dbuf->len - state.residue;
+	if (WARN_ONCE(pending > LRW_UART_DMA_BUFFER_SIZE,
+		      "pending %zu exceeds DMA buffer size %zu\n",
+		      pending, LRW_UART_DMA_BUFFER_SIZE))
+		pending = LRW_UART_DMA_BUFFER_SIZE;
+	/* Then we terminate the transfer - we now know our residue */
+	dmaengine_terminate_all(rxchan);
+
+	sup->dmarx.running = false;
+	dmarx->use_buf_b = !lastbuf;
+	ret = lrw_uart_dma_rx_trigger_dma(sup);
+
+	lrw_uart_dma_rx_chars(sup, pending, lastbuf, false);
+	uart_unlock_and_check_sysrq(&sup->port);
+	/*
+	 * Do this check after we picked the DMA chars so we don't
+	 * get some IRQ immediately from RX.
+	 */
+	if (ret) {
+		dev_dbg(sup->port.dev,
+			"could not retrigger RX DMA job fall back to interrupt mode\n");
+		sup->im |= UARTIMSC_RXIM;
+		lrw_uart_write(sup->im, sup, REG_IMSC);
+	}
+}
+
+/*
+ * Stop accepting received characters, when we're shutting down or
+ * suspending this port.
+ * Locking: called with port lock held and IRQs disabled.
+ */
+static inline void lrw_uart_dma_rx_stop(struct lrw_uart_port *sup)
+{
+	if (!sup->using_rx_dma)
+		return;
+
+	/* FIXME.  Just disable the DMA enable */
+	sup->dmacr &= ~UARTFCCR_RXDMAE;
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+}
+
+/*
+ * Timer handler for Rx DMA polling.
+ * Every polling, It checks the residue in the dma buffer and transfer
+ * data to the tty. Also, last_residue is updated for the next polling.
+ */
+static void lrw_uart_dma_rx_poll(struct timer_list *t)
+{
+	struct lrw_uart_port *sup = timer_container_of(sup, t, dmarx.timer);
+	struct tty_port *port = &sup->port.state->port;
+	struct lrw_uart_dmarx_data *dmarx = &sup->dmarx;
+	struct dma_chan *rxchan = sup->dmarx.chan;
+	unsigned long flags;
+	unsigned int dmataken = 0;
+	unsigned int size = 0;
+	struct lrw_uart_dmabuf *dbuf;
+	int dma_count;
+	struct dma_tx_state state;
+
+	dbuf = dmarx->use_buf_b ? &sup->dmarx.dbuf_b : &sup->dmarx.dbuf_a;
+	rxchan->device->device_tx_status(rxchan, dmarx->cookie, &state);
+	if (likely(state.residue < dmarx->last_residue)) {
+		dmataken = dbuf->len - dmarx->last_residue;
+		size = dmarx->last_residue - state.residue;
+		dma_count = tty_insert_flip_string(port, dbuf->buf + dmataken,
+						   size);
+		if (dma_count == size)
+			dmarx->last_residue =  state.residue;
+		dmarx->last_jiffies = jiffies;
+	}
+	tty_flip_buffer_push(port);
+
+	/*
+	 * If no data is received in poll_timeout, the driver will fall back
+	 * to interrupt mode. We will retrigger DMA at the first interrupt.
+	 */
+	if (jiffies_to_msecs(jiffies - dmarx->last_jiffies)
+			> sup->dmarx.poll_timeout) {
+		uart_port_lock_irqsave(&sup->port, &flags);
+		lrw_uart_dma_rx_stop(sup);
+		sup->im |= UARTIMSC_RXIM;
+		lrw_uart_write(sup->im, sup, REG_IMSC);
+		uart_port_unlock_irqrestore(&sup->port, flags);
+
+		sup->dmarx.running = false;
+		dmaengine_terminate_all(rxchan);
+		timer_delete(&sup->dmarx.timer);
+	} else {
+		mod_timer(&sup->dmarx.timer,
+			  jiffies + msecs_to_jiffies(sup->dmarx.poll_rate));
+	}
+}
+
+static void lrw_uart_dma_startup(struct lrw_uart_port *sup)
+{
+	int ret;
+
+	if (!sup->dma_probed)
+		lrw_uart_dma_probe(sup);
+
+	if (!sup->dmatx.chan)
+		return;
+
+	sup->dmatx.buf = kmalloc(LRW_UART_DMA_BUFFER_SIZE, GFP_KERNEL | __GFP_DMA);
+	if (!sup->dmatx.buf) {
+		sup->port.fifosize = sup->fifosize;
+		return;
+	}
+
+	sup->dmatx.len = LRW_UART_DMA_BUFFER_SIZE;
+
+	/* The DMA buffer is now the FIFO the TTY subsystem can use */
+	sup->port.fifosize = LRW_UART_DMA_BUFFER_SIZE;
+	sup->using_tx_dma = true;
+
+	if (!sup->dmarx.chan)
+		goto skip_rx;
+
+	/* Allocate and map DMA RX buffers */
+	ret = lrw_uart_dmabuf_init(sup->dmarx.chan, &sup->dmarx.dbuf_a,
+				  DMA_FROM_DEVICE);
+	if (ret) {
+		dev_err(sup->port.dev, "failed to init DMA %s: %d\n",
+			"RX buffer A", ret);
+		goto skip_rx;
+	}
+
+	ret = lrw_uart_dmabuf_init(sup->dmarx.chan, &sup->dmarx.dbuf_b,
+				  DMA_FROM_DEVICE);
+	if (ret) {
+		dev_err(sup->port.dev, "failed to init DMA %s: %d\n",
+			"RX buffer B", ret);
+		lrw_uart_dmabuf_free(sup->dmarx.chan, &sup->dmarx.dbuf_a,
+				    DMA_FROM_DEVICE);
+		goto skip_rx;
+	}
+
+	sup->using_rx_dma = true;
+
+skip_rx:
+	/* Turn on DMA error (RX/TX will be enabled on demand) */
+	sup->dmacr |= UARTFCCR_DMAONERR;
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+
+	if (sup->using_rx_dma) {
+		if (lrw_uart_dma_rx_trigger_dma(sup))
+			dev_dbg(sup->port.dev,
+				"could not trigger initial RX DMA job, fall back to interrupt mode\n");
+		if (sup->dmarx.poll_rate) {
+			timer_setup(&sup->dmarx.timer, lrw_uart_dma_rx_poll, 0);
+			mod_timer(&sup->dmarx.timer,
+				  jiffies + msecs_to_jiffies(sup->dmarx.poll_rate));
+			sup->dmarx.last_residue = LRW_UART_DMA_BUFFER_SIZE;
+			sup->dmarx.last_jiffies = jiffies;
+		}
+	}
+}
+
+static void lrw_uart_dma_shutdown(struct lrw_uart_port *sup)
+{
+	if (!(sup->using_tx_dma || sup->using_rx_dma))
+		return;
+
+	/* Disable RX and TX DMA */
+	while (lrw_uart_read(sup, REG_FR) & sup->vendor->fr_busy)
+		cpu_relax();
+
+	uart_port_lock_irq(&sup->port);
+	sup->dmacr &= ~(UARTFCCR_DMAONERR | UARTFCCR_RXDMAE | UARTFCCR_TXDMAE);
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+	uart_port_unlock_irq(&sup->port);
+
+	if (sup->using_tx_dma) {
+		/* In theory, this should already be done by lrw_uart_dma_flush_buffer */
+		dmaengine_terminate_all(sup->dmatx.chan);
+		if (sup->dmatx.queued) {
+			dma_unmap_single(sup->dmatx.chan->device->dev,
+					 sup->dmatx.dma, sup->dmatx.len,
+					 DMA_TO_DEVICE);
+			sup->dmatx.queued = false;
+		}
+
+		kfree(sup->dmatx.buf);
+		sup->using_tx_dma = false;
+	}
+
+	if (sup->using_rx_dma) {
+		dmaengine_terminate_all(sup->dmarx.chan);
+		/* Clean up the RX DMA */
+		lrw_uart_dmabuf_free(sup->dmarx.chan, &sup->dmarx.dbuf_a, DMA_FROM_DEVICE);
+		lrw_uart_dmabuf_free(sup->dmarx.chan, &sup->dmarx.dbuf_b, DMA_FROM_DEVICE);
+		if (sup->dmarx.poll_rate)
+			timer_delete_sync(&sup->dmarx.timer);
+		sup->using_rx_dma = false;
+	}
+}
+
+static inline bool lrw_uart_dma_rx_available(struct lrw_uart_port *sup)
+{
+	return sup->using_rx_dma;
+}
+
+static inline bool lrw_uart_dma_rx_running(struct lrw_uart_port *sup)
+{
+	return sup->using_rx_dma && sup->dmarx.running;
+}
+
+#else
+/* Blank functions if the DMA engine is not available */
+static inline void lrw_uart_dma_remove(struct lrw_uart_port *sup)
+{
+}
+
+static inline void lrw_uart_dma_startup(struct lrw_uart_port *sup)
+{
+}
+
+static inline void lrw_uart_dma_shutdown(struct lrw_uart_port *sup)
+{
+}
+
+static inline bool lrw_uart_dma_tx_irq(struct lrw_uart_port *sup)
+{
+	return false;
+}
+
+static inline void lrw_uart_dma_tx_stop(struct lrw_uart_port *sup)
+{
+}
+
+static inline bool lrw_uart_dma_tx_start(struct lrw_uart_port *sup)
+{
+	return false;
+}
+
+static inline void lrw_uart_dma_rx_irq(struct lrw_uart_port *sup)
+{
+}
+
+static inline void lrw_uart_dma_rx_stop(struct lrw_uart_port *sup)
+{
+}
+
+static inline int lrw_uart_dma_rx_trigger_dma(struct lrw_uart_port *sup)
+{
+	return -EIO;
+}
+
+static inline bool lrw_uart_dma_rx_available(struct lrw_uart_port *sup)
+{
+	return false;
+}
+
+static inline bool lrw_uart_dma_rx_running(struct lrw_uart_port *sup)
+{
+	return false;
+}
+
+#define lrw_uart_dma_flush_buffer	NULL
+#endif
+
+static void lrw_uart_rs485_tx_stop(struct lrw_uart_port *sup)
+{
+	/*
+	 * To be on the safe side only time out after twice as many iterations
+	 * as fifo size.
+	 */
+	const int MAX_TX_DRAIN_ITERS = sup->port.fifosize * 2;
+	struct uart_port *port = &sup->port;
+	int i = 0;
+	u32 mcr, mcfg;
+
+	/* Wait until hardware tx queue is empty */
+	while (!lrw_uart_tx_empty(port)) {
+		if (i > MAX_TX_DRAIN_ITERS) {
+			dev_warn(port->dev,
+				 "timeout while draining hardware tx queue\n");
+			break;
+		}
+
+		udelay(sup->rs485_tx_drain_interval);
+		i++;
+	}
+
+	if (port->rs485.delay_rts_after_send)
+		mdelay(port->rs485.delay_rts_after_send);
+
+	mcr = lrw_uart_read(sup, REG_MCR);
+
+	if (port->rs485.flags & SER_RS485_RTS_AFTER_SEND)
+		mcr &= ~UARTMCR_RTS;
+	else
+		mcr |= UARTMCR_RTS;
+
+	lrw_uart_write(mcr, sup, REG_MCR);
+
+	/* Disable the transmitter and reenable the transceiver */
+	mcfg = lrw_uart_read(sup, REG_MCFG);
+	mcfg &= ~UARTMCFG_TXE;
+	mcfg |= UARTMCFG_RXE;
+	lrw_uart_write(mcfg, sup, REG_MCFG);
+
+	sup->rs485_tx_started = false;
+}
+
+static void lrw_uart_stop_tx(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	sup->im &= ~UARTIMSC_TXIM;
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+	lrw_uart_dma_tx_stop(sup);
+
+	if ((port->rs485.flags & SER_RS485_ENABLED) && sup->rs485_tx_started)
+		lrw_uart_rs485_tx_stop(sup);
+}
+
+static bool lrw_uart_tx_chars(struct lrw_uart_port *sup, bool from_irq);
+
+/* Start TX with programmed I/O only (no DMA) */
+static void lrw_uart_start_tx_pio(struct lrw_uart_port *sup)
+{
+	if (lrw_uart_tx_chars(sup, false)) {
+		sup->im |= UARTIMSC_TXIM;
+		lrw_uart_write(sup->im, sup, REG_IMSC);
+	}
+}
+
+static void lrw_uart_rs485_tx_start(struct lrw_uart_port *sup)
+{
+	struct uart_port *port = &sup->port;
+	u32 mcr, mcfg;
+
+	/* Enable transmitter */
+	mcfg = lrw_uart_read(sup, REG_MCFG);
+	mcfg |= UARTMCFG_TXE;
+
+	/* Disable receiver if half-duplex */
+	if (!(port->rs485.flags & SER_RS485_RX_DURING_TX))
+		mcfg &= ~UARTMCFG_RXE;
+
+	lrw_uart_write(mcfg, sup, REG_MCFG);
+
+	mcr = lrw_uart_read(sup, REG_MCR);
+	if (port->rs485.flags & SER_RS485_RTS_ON_SEND)
+		mcr &= ~UARTMCR_RTS;
+	else
+		mcr |= UARTMCR_RTS;
+
+	lrw_uart_write(mcr, sup, REG_MCR);
+
+	if (port->rs485.delay_rts_before_send)
+		mdelay(port->rs485.delay_rts_before_send);
+
+	sup->rs485_tx_started = true;
+}
+
+static void lrw_uart_start_tx(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	if ((sup->port.rs485.flags & SER_RS485_ENABLED) &&
+	    !sup->rs485_tx_started)
+		lrw_uart_rs485_tx_start(sup);
+
+	if (!lrw_uart_dma_tx_start(sup))
+		lrw_uart_start_tx_pio(sup);
+}
+
+static void lrw_uart_stop_rx(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	sup->im &= ~(UARTIMSC_RXIM | UARTIMSC_RTIM | UARTIMSC_FEIM |
+		     UARTIMSC_PEIM | UARTIMSC_BEIM | UARTIMSC_OEIM);
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+
+	lrw_uart_dma_rx_stop(sup);
+}
+
+static void lrw_uart_throttle_rx(struct uart_port *port)
+{
+	unsigned long flags;
+
+	uart_port_lock_irqsave(port, &flags);
+	lrw_uart_stop_rx(port);
+	uart_port_unlock_irqrestore(port, flags);
+}
+
+static void lrw_uart_enable_ms(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	sup->im |= UARTIMSC_RIMIM | UARTIMSC_CTSMIM | UARTIMSC_DCDMIM | UARTIMSC_DSRMIM;
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+}
+
+static void lrw_uart_rx_chars(struct lrw_uart_port *sup)
+__releases(&sup->port.lock)
+__acquires(&sup->port.lock)
+{
+	lrw_uart_fifo_to_tty(sup);
+
+	uart_port_unlock(&sup->port);
+	tty_flip_buffer_push(&sup->port.state->port);
+	/*
+	 * If we were temporarily out of DMA mode for a while,
+	 * attempt to switch back to DMA mode again.
+	 */
+	if (lrw_uart_dma_rx_available(sup)) {
+		if (lrw_uart_dma_rx_trigger_dma(sup)) {
+			dev_dbg(sup->port.dev,
+				"could not trigger RX DMA job fall back to interrupt mode again\n");
+			sup->im |= UARTIMSC_RXIM;
+			lrw_uart_write(sup->im, sup, REG_IMSC);
+		} else {
+#ifdef CONFIG_DMA_ENGINE
+			/* Start Rx DMA poll */
+			if (sup->dmarx.poll_rate) {
+				sup->dmarx.last_jiffies = jiffies;
+				sup->dmarx.last_residue	= LRW_UART_DMA_BUFFER_SIZE;
+				mod_timer(&sup->dmarx.timer,
+					  jiffies + msecs_to_jiffies(sup->dmarx.poll_rate));
+			}
+#endif
+		}
+	}
+	uart_port_lock(&sup->port);
+}
+
+static bool lrw_uart_tx_char(struct lrw_uart_port *sup, unsigned char c,
+			    bool from_irq)
+{
+	if (unlikely(!from_irq) &&
+	    lrw_uart_read(sup, REG_FR) & UARTFR_TXFF)
+		return false; /* unable to transmit character */
+
+	lrw_uart_write(c, sup, REG_DR);
+	sup->port.icount.tx++;
+
+	return true;
+}
+
+/* Returns true if tx interrupts have to be (kept) enabled  */
+static bool lrw_uart_tx_chars(struct lrw_uart_port *sup, bool from_irq)
+{
+	struct tty_port *tport = &sup->port.state->port;
+	int count = sup->fifosize >> 1;
+
+	if (sup->port.x_char) {
+		if (!lrw_uart_tx_char(sup, sup->port.x_char, from_irq))
+			return true;
+		sup->port.x_char = 0;
+		--count;
+	}
+	if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(&sup->port)) {
+		lrw_uart_stop_tx(&sup->port);
+		return false;
+	}
+
+	/* If we are using DMA mode, try to send some characters. */
+	if (lrw_uart_dma_tx_irq(sup))
+		return true;
+
+	while (1) {
+		unsigned char c;
+
+		if (likely(from_irq) && count-- == 0)
+			break;
+
+		if (!kfifo_peek(&tport->xmit_fifo, &c))
+			break;
+
+		if (!lrw_uart_tx_char(sup, c, from_irq))
+			break;
+
+		kfifo_skip(&tport->xmit_fifo);
+	}
+
+	if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
+		uart_write_wakeup(&sup->port);
+
+	if (kfifo_is_empty(&tport->xmit_fifo)) {
+		lrw_uart_stop_tx(&sup->port);
+		return false;
+	}
+	return true;
+}
+
+static void lrw_uart_modem_status(struct lrw_uart_port *sup)
+{
+	unsigned int status, delta;
+
+	status = lrw_uart_read(sup, REG_FR) & UARTFR_MODEM_ANY;
+
+	delta = status ^ sup->old_status;
+	sup->old_status = status;
+
+	if (!delta)
+		return;
+
+	if (delta & UARTFR_DCD)
+		uart_handle_dcd_change(&sup->port, status & UARTFR_DCD);
+
+	if (delta & sup->vendor->fr_dsr)
+		sup->port.icount.dsr++;
+
+	if (delta & sup->vendor->fr_cts)
+		uart_handle_cts_change(&sup->port,
+				       status & sup->vendor->fr_cts);
+
+	wake_up_interruptible(&sup->port.state->port.delta_msr_wait);
+}
+
+static void check_apply_cts_event_workaround(struct lrw_uart_port *sup)
+{
+	if (!sup->vendor->cts_event_workaround)
+		return;
+
+	/* workaround to make sure that all bits are unlocked.. */
+	lrw_uart_write(0x00, sup, REG_ICR);
+
+	/*
+	 * WA: introduce 26ns(1 uart clk) delay before W1C;
+	 * single apb access will incur 2 pclk(133.12Mhz) delay,
+	 * so add 2 dummy reads
+	 */
+	lrw_uart_read(sup, REG_ICR);
+	lrw_uart_read(sup, REG_ICR);
+}
+
+static irqreturn_t lrw_uart_int(int irq, void *dev_id)
+{
+	struct lrw_uart_port *sup = dev_id;
+	unsigned int status, pass_counter = ISR_PASS_LIMIT;
+	int handled = 0;
+
+	uart_port_lock(&sup->port);
+	status = lrw_uart_read(sup, REG_RIS) & sup->im;
+	if (status) {
+		do {
+			check_apply_cts_event_workaround(sup);
+
+			lrw_uart_write(status & ~(UARTICR_TXIC | UARTICR_RTIC | UARTICR_RXIC),
+				      sup, REG_ICR);
+
+			if (status & (UARTICR_RTIC | UARTICR_RXIC)) {
+				if (lrw_uart_dma_rx_running(sup))
+					lrw_uart_dma_rx_irq(sup);
+				else
+					lrw_uart_rx_chars(sup);
+			}
+			if (status & (UARTICR_DSRMIC | UARTICR_DCDMIC |
+				      UARTICR_CTSMIC | UARTICR_RIMIC))
+				lrw_uart_modem_status(sup);
+			if (status & UARTICR_TXIC)
+				lrw_uart_tx_chars(sup, true);
+
+			if (pass_counter-- == 0)
+				break;
+
+			status = lrw_uart_read(sup, REG_RIS) & sup->im;
+		} while (status != 0);
+		handled = 1;
+	}
+
+	uart_unlock_and_check_sysrq(&sup->port);
+
+	return IRQ_RETVAL(handled);
+}
+
+static unsigned int lrw_uart_tx_empty(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	/* Allow feature register bits to be inverted to work around errata */
+	unsigned int status = lrw_uart_read(sup, REG_FR) ^ sup->vendor->inv_fr;
+
+	return status & (sup->vendor->fr_busy | UARTFR_TXFF) ?
+							0 : TIOCSER_TEMT;
+}
+
+static void lrw_uart_maybe_set_bit(bool cond, unsigned int *ptr, unsigned int mask)
+{
+	if (cond)
+		*ptr |= mask;
+}
+
+static unsigned int lrw_uart_get_mctrl(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	unsigned int result = 0;
+	unsigned int status = lrw_uart_read(sup, REG_FR);
+
+	lrw_uart_maybe_set_bit(status & UARTFR_DCD, &result, TIOCM_CAR);
+	lrw_uart_maybe_set_bit(status & sup->vendor->fr_dsr, &result, TIOCM_DSR);
+	lrw_uart_maybe_set_bit(status & sup->vendor->fr_cts, &result, TIOCM_CTS);
+	lrw_uart_maybe_set_bit(status & sup->vendor->fr_ri, &result, TIOCM_RNG);
+
+	return result;
+}
+
+static void lrw_uart_assign_bit(bool cond, unsigned int *ptr, unsigned int mask)
+{
+	if (cond)
+		*ptr |= mask;
+	else
+		*ptr &= ~mask;
+}
+
+static void lrw_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	unsigned int mcr;
+	unsigned int mcfg;
+	unsigned int fccr;
+
+	mcr = lrw_uart_read(sup, REG_MCR);
+	mcfg = lrw_uart_read(sup, REG_MCFG);
+	fccr = lrw_uart_read(sup, REG_FCCR);
+
+	lrw_uart_assign_bit(mctrl & TIOCM_RTS, &mcr, UARTMCR_RTS);
+	lrw_uart_assign_bit(mctrl & TIOCM_DTR, &mcr, UARTMCR_DTR);
+	lrw_uart_assign_bit(mctrl & TIOCM_OUT1, &mcr, UARTMCR_OUT1);
+	lrw_uart_assign_bit(mctrl & TIOCM_OUT2, &mcr, UARTMCR_OUT2);
+	lrw_uart_assign_bit(mctrl & TIOCM_LOOP, &mcfg, UARTMCFG_LBE);
+
+	if (port->status & UPSTAT_AUTORTS) {
+		/* We need to disable auto-RTS if we want to turn RTS off */
+		lrw_uart_assign_bit(mctrl & TIOCM_RTS, &fccr, UARTFCCR_RTSEN);
+	}
+
+	lrw_uart_write(mcr, sup, REG_MCR);
+	lrw_uart_write(mcfg, sup, REG_MCFG);
+	lrw_uart_write(fccr, sup, REG_FCCR);
+}
+
+static void lrw_uart_break_ctl(struct uart_port *port, int break_state)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	unsigned long flags;
+	unsigned int mcfg;
+
+	uart_port_lock_irqsave(&sup->port, &flags);
+	mcfg = lrw_uart_read(sup, REG_MCFG);
+	if (break_state == -1)
+		mcfg |= UARTMCFG_BRK;
+	else
+		mcfg &= ~UARTMCFG_BRK;
+	lrw_uart_write(mcfg, sup, REG_MCFG);
+	uart_port_unlock_irqrestore(&sup->port, flags);
+}
+
+#ifdef CONFIG_CONSOLE_POLL
+
+static void lrw_uart_quiesce_irqs(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	lrw_uart_write(lrw_uart_read(sup, REG_MIS), sup, REG_ICR);
+	/*
+	 * There is no way to clear TXIM as this is "ready to transmit IRQ", so
+	 * we simply mask it. start_tx() will unmask it.
+	 *
+	 * Note we can race with start_tx(), and if the race happens, the
+	 * polling user might get another interrupt just after we clear it.
+	 * But it should be OK and can happen even w/o the race, e.g.
+	 * controller immediately got some new data and raised the IRQ.
+	 *
+	 * And whoever uses polling routines assumes that it manages the device
+	 * (including tx queue), so we're also fine with start_tx()'s caller
+	 * side.
+	 */
+	lrw_uart_write(lrw_uart_read(sup, REG_IMSC) & ~UARTIMSC_TXIM, sup,
+		    REG_IMSC);
+}
+
+static int lrw_uart_get_poll_char(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	unsigned int status;
+
+	/*
+	 * The caller might need IRQs lowered, e.g. if used with KDB NMI
+	 * debugger.
+	 */
+	lrw_uart_quiesce_irqs(port);
+
+	status = lrw_uart_read(sup, REG_FR);
+	if (status & UARTFR_RXFE)
+		return NO_POLL_CHAR;
+
+	return lrw_uart_read(sup, REG_DR);
+}
+
+static void lrw_uart_put_poll_char(struct uart_port *port, unsigned char ch)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	while (lrw_uart_read(sup, REG_FR) & UARTFR_TXFF)
+		cpu_relax();
+
+	lrw_uart_write(ch, sup, REG_DR);
+}
+
+#endif /* CONFIG_CONSOLE_POLL */
+
+static int lrw_uart_hwinit(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	int retval;
+	unsigned int clk;
+
+	/* Optionaly enable pins to be muxed in and configured */
+	pinctrl_pm_select_default_state(port->dev);
+
+	/*
+	 * Try to enable the clock producer.
+	 */
+	retval = clk_prepare_enable(sup->clk);
+	if (retval)
+		return retval;
+
+	if (has_acpi_companion(sup->port.dev)) {
+		device_property_read_u32(sup->port.dev, "clock-frequency", &clk);
+		sup->port.uartclk = clk;
+	} else {
+		sup->port.uartclk = clk_get_rate(sup->clk);
+	}
+
+	/* Clear pending error and receive interrupts */
+	lrw_uart_write(UARTICR_OEIC | UARTICR_BEIC | UARTICR_PEIC |
+		      UARTICR_FEIC | UARTICR_RTIC | UARTICR_RXIC,
+		      sup, REG_ICR);
+
+	/*
+	 * Save interrupts enable mask, and enable RX interrupts in case if
+	 * the interrupt is used for NMI entry.
+	 */
+	sup->im = lrw_uart_read(sup, REG_IMSC);
+	lrw_uart_write(UARTIMSC_RTIM | UARTIMSC_RXIM, sup, REG_IMSC);
+
+	if (dev_get_platdata(sup->port.dev)) {
+		struct lrw_uart_data *plat;
+
+		plat = dev_get_platdata(sup->port.dev);
+		if (plat->init)
+			plat->init();
+	}
+	return 0;
+}
+
+static int lrw_uart_allocate_irq(struct lrw_uart_port *sup)
+{
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+
+	return request_irq(sup->port.irq, lrw_uart_int, IRQF_SHARED, "lrw-uart", sup);
+}
+
+/*
+ * Enable interrupts, only timeouts when using DMA
+ * if initial RX DMA job failed, start in interrupt mode
+ * as well.
+ */
+static void lrw_uart_enable_interrupts(struct lrw_uart_port *sup)
+{
+	unsigned long flags;
+	unsigned int i;
+
+	uart_port_lock_irqsave(&sup->port, &flags);
+
+	/* Clear out any spuriously appearing RX interrupts */
+	lrw_uart_write(UARTICR_RTIC | UARTICR_RXIC, sup, REG_ICR);
+
+	/*
+	 * RXIS is asserted only when the RX FIFO transitions from below
+	 * to above the trigger threshold.  If the RX FIFO is already
+	 * full to the threshold this can't happen and RXIS will now be
+	 * stuck off.  Drain the RX FIFO explicitly to fix this:
+	 */
+	for (i = 0; i < sup->fifosize * 2; ++i) {
+		if (lrw_uart_read(sup, REG_FR) & UARTFR_RXFE)
+			break;
+
+		lrw_uart_read(sup, REG_DR);
+	}
+
+	sup->im = UARTIMSC_RTIM;
+	if (!lrw_uart_dma_rx_running(sup))
+		sup->im |= UARTIMSC_RXIM;
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+	uart_port_unlock_irqrestore(&sup->port, flags);
+}
+
+static void lrw_uart_unthrottle_rx(struct uart_port *port)
+{
+	struct lrw_uart_port *sup = container_of(port, struct lrw_uart_port, port);
+	unsigned long flags;
+
+	uart_port_lock_irqsave(&sup->port, &flags);
+
+	sup->im = UARTIMSC_RTIM;
+	if (!lrw_uart_dma_rx_running(sup))
+		sup->im |= UARTIMSC_RXIM;
+
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+
+	uart_port_unlock_irqrestore(&sup->port, flags);
+}
+
+static int lrw_uart_startup(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	unsigned int mcr;
+	unsigned int mcfg;
+	int retval;
+
+	retval = lrw_uart_hwinit(port);
+	if (retval)
+		goto clk_dis;
+
+	retval = lrw_uart_allocate_irq(sup);
+	if (retval)
+		goto clk_dis;
+
+	lrw_uart_write(sup->vendor->fcr, sup, REG_FCR);
+
+	uart_port_lock_irq(&sup->port);
+
+	mcr = lrw_uart_read(sup, REG_MCR);
+	mcr &= UARTMCR_RTS | UARTMCR_DTR;
+
+	lrw_uart_write(mcr, sup, REG_MCR);
+
+	mcfg = lrw_uart_read(sup, REG_MCFG);
+
+	mcfg |= UARTMCFG_UARTEN | UARTMCFG_RXE;
+
+	if (!(port->rs485.flags & SER_RS485_ENABLED))
+		mcfg |= UARTMCFG_TXE;
+
+	lrw_uart_write(mcfg, sup, REG_MCFG);
+
+	uart_port_unlock_irq(&sup->port);
+
+	/*
+	 * initialise the old status of the modem signals
+	 */
+	sup->old_status = lrw_uart_read(sup, REG_FR) & UARTFR_MODEM_ANY;
+
+	/* Startup DMA */
+	lrw_uart_dma_startup(sup);
+
+	lrw_uart_enable_interrupts(sup);
+
+	return 0;
+
+ clk_dis:
+	clk_disable_unprepare(sup->clk);
+	return retval;
+}
+
+static void lrw_uart_shutdown_channel(struct lrw_uart_port *sup,
+	unsigned int mcfg, unsigned int fcr)
+{
+	unsigned long val;
+
+	val = lrw_uart_read(sup, mcfg);
+	val &= ~(UARTMCFG_BRK);
+	lrw_uart_write(val, sup, mcfg);
+
+	val = lrw_uart_read(sup, fcr);
+	val &= ~(UARTFCR_FEN);
+	lrw_uart_write(val, sup, fcr);
+}
+
+/*
+ * disable the port. It should not disable RTS and DTR.
+ * Also RTS and DTR state should be preserved to restore
+ * it during startup().
+ */
+static void lrw_uart_disable_uart(struct lrw_uart_port *sup)
+{
+	unsigned int mcr;
+	unsigned int mcfg;
+
+	sup->port.status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS);
+	uart_port_lock_irq(&sup->port);
+	mcr = lrw_uart_read(sup, REG_MCR);
+	mcr &= UARTMCR_RTS | UARTMCR_DTR;
+	lrw_uart_write(mcr, sup, REG_MCR);
+
+	mcfg = lrw_uart_read(sup, REG_MCFG);
+	mcfg |= UARTMCFG_UARTEN | UARTMCFG_TXE;
+	lrw_uart_write(mcfg, sup, REG_MCFG);
+	uart_port_unlock_irq(&sup->port);
+
+	/*
+	 * disable break condition and fifos
+	 */
+	lrw_uart_shutdown_channel(sup, REG_MCFG, REG_FCR);
+}
+
+static void lrw_uart_disable_interrupts(struct lrw_uart_port *sup)
+{
+	uart_port_lock_irq(&sup->port);
+
+	/* mask all interrupts and clear all pending ones */
+	sup->im = 0;
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+	lrw_uart_write(0xffff, sup, REG_ICR);
+
+	uart_port_unlock_irq(&sup->port);
+}
+
+static void lrw_uart_shutdown(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+		container_of(port, struct lrw_uart_port, port);
+
+	lrw_uart_disable_interrupts(sup);
+
+	lrw_uart_dma_shutdown(sup);
+
+	if ((port->rs485.flags & SER_RS485_ENABLED) && sup->rs485_tx_started)
+		lrw_uart_rs485_tx_stop(sup);
+
+	free_irq(sup->port.irq, sup);
+
+	lrw_uart_disable_uart(sup);
+
+	/*
+	 * Shut down the clock producer
+	 */
+	clk_disable_unprepare(sup->clk);
+	/* Optionally let pins go into sleep states */
+	pinctrl_pm_select_sleep_state(port->dev);
+
+	if (dev_get_platdata(sup->port.dev)) {
+		struct lrw_uart_data *plat;
+
+		plat = dev_get_platdata(sup->port.dev);
+		if (plat->exit)
+			plat->exit();
+	}
+
+	if (sup->port.ops->flush_buffer)
+		sup->port.ops->flush_buffer(port);
+}
+
+static void
+lrw_uart_setup_status_masks(struct uart_port *port, struct ktermios *termios)
+{
+	port->read_status_mask = UARTDR_OE | 255;
+	if (termios->c_iflag & INPCK)
+		port->read_status_mask |= UARTDR_FE | UARTDR_PE;
+	if (termios->c_iflag & (IGNBRK | BRKINT | PARMRK))
+		port->read_status_mask |= UARTDR_BE;
+
+	/*
+	 * Characters to ignore
+	 */
+	port->ignore_status_mask = 0;
+	if (termios->c_iflag & IGNPAR)
+		port->ignore_status_mask |= UARTDR_FE | UARTDR_PE;
+	if (termios->c_iflag & IGNBRK) {
+		port->ignore_status_mask |= UARTDR_BE;
+		/*
+		 * If we're ignoring parity and break indicators,
+		 * ignore overruns too (for real raw support).
+		 */
+		if (termios->c_iflag & IGNPAR)
+			port->ignore_status_mask |= UARTDR_OE;
+	}
+
+	/*
+	 * Ignore all characters if CREAD is not set.
+	 */
+	if ((termios->c_cflag & CREAD) == 0)
+		port->ignore_status_mask |= UART_DUMMY_DR_RX;
+}
+
+static void
+lrw_uart_set_termios(struct uart_port *port, struct ktermios *termios,
+		     const struct ktermios *old)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	unsigned int frcr;
+	unsigned int mcr, fccr;
+	unsigned int mcfg;
+	unsigned long flags;
+	unsigned int baud, quot, clkdiv;
+	unsigned int bits;
+	unsigned int clk;
+
+	if (sup->vendor->oversampling)
+		clkdiv = 8;
+	else
+		clkdiv = 16;
+
+	if (has_acpi_companion(sup->port.dev)) {
+		device_property_read_u32(sup->port.dev, "clock-frequency", &clk);
+		sup->port.uartclk = clk;
+	}
+
+	/*
+	 * Ask the core to calculate the divisor for us.
+	 */
+	baud = uart_get_baud_rate(port, termios, old, 0,
+				  port->uartclk / clkdiv);
+
+#ifdef CONFIG_DMA_ENGINE
+	/*
+	 * Adjust RX DMA polling rate with baud rate if not specified.
+	 */
+	if (sup->dmarx.auto_poll_rate)
+		sup->dmarx.poll_rate = DIV_ROUND_UP(10000000, baud);
+#endif
+
+	if (baud > port->uartclk / 16)
+		quot = DIV_ROUND_CLOSEST(port->uartclk * 8, baud);
+	else
+		quot = DIV_ROUND_CLOSEST(port->uartclk * 4, baud);
+
+	switch (termios->c_cflag & CSIZE) {
+	case CS5:
+		frcr = UARTFRCR_WLEN_5;
+		break;
+	case CS6:
+		frcr = UARTFRCR_WLEN_6;
+		break;
+	case CS7:
+		frcr = UARTFRCR_WLEN_7;
+		break;
+	default: // CS8
+		frcr = UARTFRCR_WLEN_8;
+		break;
+	}
+
+	if (termios->c_cflag & CSTOPB)
+		frcr |= UARTFRCR_STP2;
+	if (termios->c_cflag & PARENB) {
+		frcr |= UARTFRCR_PEN;
+		if (!(termios->c_cflag & PARODD))
+			frcr |= UARTFRCR_EOP;
+		if (termios->c_cflag & CMSPAR)
+			frcr |= UARTFRCR_SPS;
+	}
+
+
+	bits = tty_get_frame_size(termios->c_cflag);
+
+	uart_port_lock_irqsave(port, &flags);
+
+	/*
+	 * Update the per-port timeout.
+	 */
+	uart_update_timeout(port, termios->c_cflag, baud);
+
+	/*
+	 * Calculate the approximated time it takes to transmit one character
+	 * with the given baud rate. We use this as the poll interval when we
+	 * wait for the tx queue to empty.
+	 */
+	sup->rs485_tx_drain_interval = DIV_ROUND_UP(bits * 1000 * 1000, baud);
+
+	lrw_uart_setup_status_masks(port, termios);
+
+	if (UART_ENABLE_MS(port, termios->c_cflag))
+		lrw_uart_enable_ms(port);
+
+	if (port->rs485.flags & SER_RS485_ENABLED)
+		termios->c_cflag &= ~CRTSCTS;
+
+	mcr = lrw_uart_read(sup, REG_MCR);
+	mcfg = lrw_uart_read(sup, REG_MCFG);
+	fccr = lrw_uart_read(sup, REG_FCCR);
+
+	if (termios->c_cflag & CRTSCTS) {
+		if (mcr & UARTMCR_RTS)
+			fccr |= UARTFCCR_RTSEN;
+
+		fccr |= UARTFCCR_CTSEN;
+		port->status |= UPSTAT_AUTOCTS | UPSTAT_AUTORTS;
+	} else {
+		fccr &= ~(UARTFCCR_CTSEN | UARTFCCR_RTSEN);
+		port->status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS);
+	}
+
+	/* Set baud rate */
+	lrw_uart_write(quot & 0x3f, sup, REG_FD);
+	lrw_uart_write(quot >> 6, sup, REG_IND);
+
+	/*
+	 * ----------v----------v----------v----------v-----
+	 * NOTE: REG_FRCR MUST BE WRITTEN AFTER REG_FD & REG_IND.
+	 * ----------^----------^----------^----------^-----
+	 */
+	lrw_uart_write(frcr, sup, REG_FRCR);
+
+	lrw_uart_write(fccr, sup, REG_FCCR);
+
+	/*
+	 * Receive was disabled by lrw_uart_disable_uart during shutdown.
+	 * Need to reenable receive if you need to use a tty_driver
+	 * returns from tty_find_polling_driver() after a port shutdown.
+	 */
+	mcfg |= UARTMCFG_RXE;
+	lrw_uart_write(mcfg, sup, REG_MCFG);
+
+	uart_port_unlock_irqrestore(port, flags);
+}
+
+static const char *lrw_uart_type(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	return sup->port.type == PORT_LRW ? sup->type : NULL;
+}
+
+/*
+ * Configure/autoconfigure the port.
+ */
+static void lrw_uart_config_port(struct uart_port *port, int flags)
+{
+	if (flags & UART_CONFIG_TYPE)
+		port->type = PORT_LRW;
+}
+
+/*
+ * verify the new serial_struct (for TIOCSSERIAL).
+ */
+static int lrw_uart_verify_port(struct uart_port *port, struct serial_struct *ser)
+{
+	int ret = 0;
+
+	if (ser->type != PORT_UNKNOWN && ser->type != PORT_LRW)
+		ret = -EINVAL;
+	if (ser->irq < 0 || ser->irq >= irq_get_nr_irqs())
+		ret = -EINVAL;
+	if (ser->baud_base < 9600)
+		ret = -EINVAL;
+	if (port->mapbase != (unsigned long)ser->iomem_base)
+		ret = -EINVAL;
+	return ret;
+}
+
+static int lrw_uart_rs485_config(struct uart_port *port, struct ktermios *termios,
+			      struct serial_rs485 *rs485)
+{
+	struct lrw_uart_port *sup =
+		container_of(port, struct lrw_uart_port, port);
+
+	if (port->rs485.flags & SER_RS485_ENABLED)
+		lrw_uart_rs485_tx_stop(sup);
+
+	/* Make sure auto RTS is disabled */
+	if (rs485->flags & SER_RS485_ENABLED) {
+		u32 fccr = lrw_uart_read(sup, REG_FCCR);
+
+		fccr &= ~UARTFCCR_RTSEN;
+		lrw_uart_write(fccr, sup, REG_FCCR);
+		port->status &= ~UPSTAT_AUTORTS;
+	}
+
+	return 0;
+}
+
+static const struct uart_ops lrw_uart_pops = {
+	.tx_empty	= lrw_uart_tx_empty,
+	.set_mctrl	= lrw_uart_set_mctrl,
+	.get_mctrl	= lrw_uart_get_mctrl,
+	.stop_tx	= lrw_uart_stop_tx,
+	.start_tx	= lrw_uart_start_tx,
+	.stop_rx	= lrw_uart_stop_rx,
+	.throttle	= lrw_uart_throttle_rx,
+	.unthrottle	= lrw_uart_unthrottle_rx,
+	.enable_ms	= lrw_uart_enable_ms,
+	.break_ctl	= lrw_uart_break_ctl,
+	.startup	= lrw_uart_startup,
+	.shutdown	= lrw_uart_shutdown,
+	.flush_buffer	= lrw_uart_dma_flush_buffer,
+	.set_termios	= lrw_uart_set_termios,
+	.type		= lrw_uart_type,
+	.config_port	= lrw_uart_config_port,
+	.verify_port	= lrw_uart_verify_port,
+#ifdef CONFIG_CONSOLE_POLL
+	.poll_init     = lrw_uart_hwinit,
+	.poll_get_char = lrw_uart_get_poll_char,
+	.poll_put_char = lrw_uart_put_poll_char,
+#endif
+};
+
+static struct lrw_uart_port *lrw_uart_console_ports[UART_NR];
+
+#ifdef CONFIG_SERIAL_LRW_UART_CONSOLE
+
+static void lrw_uart_console_putchar(struct uart_port *port, unsigned char ch)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	while (lrw_uart_read(sup, REG_FR) & UARTFR_TXFF)
+		cpu_relax();
+	lrw_uart_write(ch, sup, REG_DR);
+}
+
+static void
+lrw_uart_console_write(struct console *co, const char *s, unsigned int count)
+{
+	struct lrw_uart_port *sup = lrw_uart_console_ports[co->index];
+	unsigned int old_fccr = 0, new_fccr;
+	unsigned int old_mcfg = 0, new_mcfg;
+	unsigned long flags;
+	int locked = 1;
+
+	clk_enable(sup->clk);
+
+	if (oops_in_progress)
+		locked = uart_port_trylock_irqsave(&sup->port, &flags);
+	else
+		uart_port_lock_irqsave(&sup->port, &flags);
+
+	/*
+	 *	First save the FCCR then disable the interrupts
+	 */
+	if (!sup->vendor->always_enabled) {
+		old_fccr = lrw_uart_read(sup, REG_FCCR);
+		new_fccr = old_fccr & ~UARTFCCR_CTSEN;
+		lrw_uart_write(new_fccr, sup, REG_FCCR);
+
+		old_mcfg = lrw_uart_read(sup, REG_MCFG);
+		new_mcfg |= UARTMCFG_UARTEN | UARTMCFG_TXE;
+		lrw_uart_write(new_mcfg, sup, REG_MCFG);
+	}
+
+	uart_console_write(&sup->port, s, count, lrw_uart_console_putchar);
+
+	/*
+	 *	Finally, wait for transmitter to become empty and restore the
+	 *	TCR. Allow feature register bits to be inverted to work around
+	 *	errata.
+	 */
+	while ((lrw_uart_read(sup, REG_FR) ^ sup->vendor->inv_fr)
+						& sup->vendor->fr_busy)
+		cpu_relax();
+	if (!sup->vendor->always_enabled) {
+		lrw_uart_write(old_fccr, sup, REG_FCCR);
+		lrw_uart_write(old_mcfg, sup, REG_MCFG);
+	}
+
+	if (locked)
+		uart_port_unlock_irqrestore(&sup->port, flags);
+
+	clk_disable(sup->clk);
+}
+
+static void lrw_uart_console_get_options(struct lrw_uart_port *sup, int *baud,
+					 int *parity, int *bits)
+{
+	unsigned int frcr, ind, fd;
+
+	if (!(lrw_uart_read(sup, REG_MCFG) & UARTMCFG_UARTEN))
+		return;
+
+	frcr = lrw_uart_read(sup, REG_FRCR);
+
+	*parity = 'n';
+	if (frcr & UARTFRCR_PEN) {
+		if (frcr & UARTFRCR_EOP)
+			*parity = 'e';
+		else
+			*parity = 'o';
+	}
+
+	if ((frcr & 0x3) == UARTFRCR_WLEN_7)
+		*bits = 7;
+	else
+		*bits = 8;
+
+	ind = lrw_uart_read(sup, REG_IND);
+	fd = lrw_uart_read(sup, REG_FD);
+
+	*baud = sup->port.uartclk * 4 / (64 * ind + fd);
+}
+
+static int lrw_uart_console_setup(struct console *co, char *options)
+{
+	struct lrw_uart_port *sup;
+	int baud = 38400;
+	int bits = 8;
+	int parity = 'n';
+	int flow = 'n';
+	int ret;
+	unsigned int clk;
+
+	/*
+	 * Check whether an invalid uart number has been specified, and
+	 * if so, search for the first available port that does have
+	 * console support.
+	 */
+	if (co->index >= UART_NR)
+		co->index = 0;
+	sup = lrw_uart_console_ports[co->index];
+	if (!sup)
+		return -ENODEV;
+
+	/* Allow pins to be muxed in and configured */
+	pinctrl_pm_select_default_state(sup->port.dev);
+
+	ret = clk_prepare(sup->clk);
+	if (ret)
+		return ret;
+
+	if (dev_get_platdata(sup->port.dev)) {
+		struct lrw_uart_data *plat;
+
+		plat = dev_get_platdata(sup->port.dev);
+		if (plat->init)
+			plat->init();
+	}
+
+	if (has_acpi_companion(sup->port.dev)) {
+		device_property_read_u32(sup->port.dev, "clock-frequency", &clk);
+		sup->port.uartclk = clk;
+	} else {
+		sup->port.uartclk = clk_get_rate(sup->clk);
+	}
+
+	if (sup->vendor->fixed_options) {
+		baud = sup->fixed_baud;
+	} else {
+		if (options)
+			uart_parse_options(options,
+					   &baud, &parity, &bits, &flow);
+		else
+			lrw_uart_console_get_options(sup, &baud, &parity, &bits);
+	}
+
+	return uart_set_options(&sup->port, co, baud, parity, bits, flow);
+}
+
+/**
+ *	lrw_uart_console_match - non-standard console matching
+ *	@co:	  registering console
+ *	@name:	  name from console command line
+ *	@idx:	  index from console command line
+ *	@options: ptr to option string from console command line
+ *
+ *	Only attempts to match console command lines of the form:
+ *	    console=lrw_uart,mmio|mmio32,<addr>[,<options>]
+ *	    console=lrw_uart,0x<addr>[,<options>]
+ *	This form is used to register an initial earlycon boot console and
+ *	replace it with the lrw_uart_console at lrw_uart driver init.
+ *
+ *	Performs console setup for a match (as required by interface)
+ *	If no <options> are specified, then assume the h/w is already setup.
+ *
+ *	Returns 0 if console matches; otherwise non-zero to use default matching
+ */
+static int lrw_uart_console_match(struct console *co, char *name, int idx,
+				  char *options)
+{
+	enum uart_iotype iotype;
+	resource_size_t addr;
+	int i;
+
+	if (strcmp(name, "lrw_uart") != 0)
+		return -ENODEV;
+
+	if (uart_parse_earlycon(options, &iotype, &addr, &options))
+		return -ENODEV;
+
+	if (iotype != UPIO_MEM && iotype != UPIO_MEM32)
+		return -ENODEV;
+
+	/* try to match the port specified on the command line */
+	for (i = 0; i < ARRAY_SIZE(lrw_uart_console_ports); i++) {
+		struct uart_port *port;
+
+		if (!lrw_uart_console_ports[i])
+			continue;
+
+		port = &lrw_uart_console_ports[i]->port;
+
+		if (port->mapbase != addr)
+			continue;
+
+		co->index = i;
+		uart_port_set_cons(port, co);
+		return lrw_uart_console_setup(co, options);
+	}
+
+	return -ENODEV;
+}
+
+static struct uart_driver lrw_uart_driver;
+
+static struct console lrw_uart_console = {
+	.name		= LRW_UART_TTY_PREFIX,
+	.write		= lrw_uart_console_write,
+	.device		= uart_console_device,
+	.setup		= lrw_uart_console_setup,
+	.match		= lrw_uart_console_match,
+	.flags		= CON_PRINTBUFFER | CON_ANYTIME,
+	.index		= -1,
+	.data		= &lrw_uart_driver,
+};
+
+#define LRW_UART_CONSOLE	(&lrw_uart_console)
+
+static void lrw_uart_putc(struct uart_port *port, unsigned char c)
+{
+	while (readl(port->membase + UARTFR) & UARTFR_TXFF)
+		cpu_relax();
+	if (port->iotype == UPIO_MEM32)
+		writel(c, port->membase + UARTDR);
+	else
+		writeb(c, port->membase + UARTDR);
+	while (readl(port->membase + UARTFR) & UARTFR_BUSY)
+		cpu_relax();
+}
+
+static void lrw_uart_early_write(struct console *con, const char *s, unsigned int n)
+{
+	struct earlycon_device *dev = con->data;
+
+	uart_console_write(&dev->port, s, n, lrw_uart_putc);
+}
+
+#ifdef CONFIG_CONSOLE_POLL
+static int lrw_uart_getc(struct uart_port *port)
+{
+	if (readl(port->membase + UARTFR) & UARTFR_RXFE)
+		return NO_POLL_CHAR;
+
+	if (port->iotype == UPIO_MEM32)
+		return readl(port->membase + UARTDR);
+	else
+		return readb(port->membase + UARTDR);
+}
+
+static int lrw_uart_early_read(struct console *con, char *s, unsigned int n)
+{
+	struct earlycon_device *dev = con->data;
+	int ch, num_read = 0;
+
+	while (num_read < n) {
+		ch = lrw_uart_getc(&dev->port);
+		if (ch == NO_POLL_CHAR)
+			break;
+
+		s[num_read++] = ch;
+	}
+
+	return num_read;
+}
+#else
+#define lrw_uart_early_read NULL
+#endif
+
+/*
+ * On non-ACPI systems, earlycon is enabled by specifying
+ * "earlycon=lrw_uart,<address>" on the kernel command line.
+ *
+ * On ACPI ARM64 systems, an "early" console is enabled via the SPCR table,
+ * by specifying only "earlycon" on the command line.  Because it requires
+ * SPCR, the console starts after ACPI is parsed, which is later than a
+ * traditional early console.
+ *
+ * To get the traditional early console that starts before ACPI is parsed,
+ * specify the full "earlycon=lrw_uart,<address>" option.
+ */
+static int __init lrw_uart_early_console_setup(struct earlycon_device *device,
+					      const char *opt)
+{
+	if (!device->port.membase)
+		return -ENODEV;
+
+	device->con->write = lrw_uart_early_write;
+	device->con->read = lrw_uart_early_read;
+
+	return 0;
+}
+
+OF_EARLYCON_DECLARE(lrw_uart, "lrw-uart", lrw_uart_early_console_setup);
+
+#else
+#define LRW_UART_CONSOLE	NULL
+#endif
+
+static struct uart_driver lrw_uart_driver = {
+	.owner			= THIS_MODULE,
+	.driver_name		= LRW_UART_NAME,
+	.dev_name		= LRW_UART_TTY_PREFIX,
+	.nr			= UART_NR,
+	.cons			= LRW_UART_CONSOLE,
+};
+
+static int lrw_uart_probe_dt_alias(int index, struct device *dev)
+{
+	struct device_node *np;
+	static bool seen_dev_with_alias;
+	static bool seen_dev_without_alias;
+	int ret = index;
+
+	if (!IS_ENABLED(CONFIG_OF))
+		return ret;
+
+	np = dev->of_node;
+	if (!np)
+		return ret;
+
+	ret = of_alias_get_id(np, "serial");
+	if (ret < 0) {
+		seen_dev_without_alias = true;
+		ret = index;
+	} else {
+		seen_dev_with_alias = true;
+		if (ret >= ARRAY_SIZE(lrw_uart_console_ports) || lrw_uart_console_ports[ret]) {
+			dev_warn(dev, "requested serial port %d  not available.\n", ret);
+			ret = index;
+		}
+	}
+
+	if (seen_dev_with_alias && seen_dev_without_alias)
+		dev_warn(dev, "aliased and non-aliased serial devices found in device tree. Serial port enumeration may be unpredictable.\n");
+
+	return ret;
+}
+
+/* unregisters the driver also if no more ports are left */
+static void lrw_uart_unregister_port(struct lrw_uart_port *sup)
+{
+	int i;
+	bool busy = false;
+
+	for (i = 0; i < ARRAY_SIZE(lrw_uart_console_ports); i++) {
+		if (lrw_uart_console_ports[i] == sup)
+			lrw_uart_console_ports[i] = NULL;
+		else if (lrw_uart_console_ports[i])
+			busy = true;
+	}
+	lrw_uart_dma_remove(sup);
+	if (!busy)
+		uart_unregister_driver(&lrw_uart_driver);
+}
+
+static int lrw_uart_find_free_port(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(lrw_uart_console_ports); i++)
+		if (!lrw_uart_console_ports[i])
+			return i;
+
+	return -EBUSY;
+}
+
+static int lrw_uart_setup_port(struct device *dev, struct lrw_uart_port *sup,
+			      struct resource *mmiobase, int index)
+{
+	void __iomem *base;
+	int ret;
+
+	base = devm_ioremap_resource(dev, mmiobase);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	index = lrw_uart_probe_dt_alias(index, dev);
+
+	sup->port.dev = dev;
+	sup->port.mapbase = mmiobase->start;
+	sup->port.membase = base;
+	sup->port.fifosize = sup->fifosize;
+	sup->port.has_sysrq = IS_ENABLED(CONFIG_SERIAL_LRW_UART_CONSOLE);
+	sup->port.flags = UPF_BOOT_AUTOCONF;
+	sup->port.line = index;
+
+	ret = uart_get_rs485_mode(&sup->port);
+	if (ret)
+		return ret;
+
+	lrw_uart_console_ports[index] = sup;
+
+	return 0;
+}
+
+static int lrw_uart_register_port(struct lrw_uart_port *sup)
+{
+	int ret, i;
+
+	/* Ensure interrupts from this UART are masked and cleared */
+	lrw_uart_write(0, sup, REG_IMSC);
+	lrw_uart_write(0xffff, sup, REG_ICR);
+
+	if (!lrw_uart_driver.state) {
+		ret = uart_register_driver(&lrw_uart_driver);
+		if (ret < 0) {
+			dev_err(sup->port.dev,
+				"Failed to register LRW UART driver\n");
+			for (i = 0; i < ARRAY_SIZE(lrw_uart_console_ports); i++)
+				if (lrw_uart_console_ports[i] == sup)
+					lrw_uart_console_ports[i] = NULL;
+			return ret;
+		}
+	}
+
+	ret = uart_add_one_port(&lrw_uart_driver, &sup->port);
+	if (ret)
+		lrw_uart_unregister_port(sup);
+
+	return ret;
+}
+
+static const struct serial_rs485 lrw_uart_rs485_supported = {
+	.flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | SER_RS485_RTS_AFTER_SEND |
+		 SER_RS485_RX_DURING_TX,
+	.delay_rts_before_send = 1,
+	.delay_rts_after_send = 1,
+};
+
+static int lrw_uart_probe(struct platform_device *pdev)
+{
+	struct lrw_uart_port *sup;
+	struct resource *r;
+	int portnr, ret;
+	unsigned int clk;
+	unsigned int baudrate;
+
+	/*
+	 * Check the mandatory baud rate parameter in the DT node early
+	 * so that we can easily exit with the error.
+	 */
+	if (pdev->dev.of_node) {
+		struct device_node *np = pdev->dev.of_node;
+
+		ret = of_property_read_u32(np, "current-speed", &baudrate);
+		if (ret)
+			return ret;
+	} else if (has_acpi_companion(&pdev->dev)) {
+		ret = device_property_read_u32(&pdev->dev, "current-speed", &baudrate);
+		if (ret)
+			return ret;
+	} else {
+		baudrate = 115200;
+	}
+
+	portnr = lrw_uart_find_free_port();
+	if (portnr < 0)
+		return portnr;
+
+	sup = devm_kzalloc(&pdev->dev, sizeof(struct lrw_uart_port),
+			   GFP_KERNEL);
+	if (!sup)
+		return -ENOMEM;
+
+	if (has_acpi_companion(&pdev->dev)) {
+		device_property_read_u32(&pdev->dev, "clock-frequency", &clk);
+		sup->port.uartclk = clk;
+	} else {
+		sup->clk = devm_clk_get(&pdev->dev, NULL);
+		if (IS_ERR(sup->clk))
+			return PTR_ERR(sup->clk);
+	}
+
+	ret = platform_get_irq(pdev, 0);
+	if (ret < 0)
+		return ret;
+	sup->port.irq	= ret;
+
+	sup->vendor = &vendor_lrw;
+
+	sup->reg_offset = sup->vendor->reg_offset;
+	sup->fifosize	= LRW_UART_TX_FIFO_DEPTH;
+	sup->port.iotype = sup->vendor->access_32b ? UPIO_MEM32 : UPIO_MEM;
+	sup->port.ops = &lrw_uart_pops;
+	sup->port.rs485_config = lrw_uart_rs485_config;
+	sup->port.rs485_supported = lrw_uart_rs485_supported;
+	sup->fixed_baud = baudrate;
+
+	snprintf(sup->type, sizeof(sup->type), "LRW UART");
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	ret = lrw_uart_setup_port(&pdev->dev, sup, r, portnr);
+	if (ret)
+		return ret;
+
+	platform_set_drvdata(pdev, sup);
+
+	return lrw_uart_register_port(sup);
+}
+
+static void lrw_uart_remove(struct platform_device *dev)
+{
+	struct lrw_uart_port *sup = platform_get_drvdata(dev);
+
+	uart_remove_one_port(&lrw_uart_driver, &sup->port);
+	lrw_uart_unregister_port(sup);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int lrw_uart_suspend(struct device *dev)
+{
+	struct lrw_uart_port *sup = dev_get_drvdata(dev);
+
+	if (!sup)
+		return -EINVAL;
+
+	return uart_suspend_port(&lrw_uart_driver, &sup->port);
+}
+
+static int lrw_uart_resume(struct device *dev)
+{
+	struct lrw_uart_port *sup = dev_get_drvdata(dev);
+
+	if (!sup)
+		return -EINVAL;
+
+	return uart_resume_port(&lrw_uart_driver, &sup->port);
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(lrw_uart_pm_ops, lrw_uart_suspend, lrw_uart_resume);
+
+static const struct of_device_id lrw_uart_of_match[] = {
+	{ .compatible = "lrw,lrw-uart" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, lrw_uart_of_match);
+
+static const struct acpi_device_id __maybe_unused lrw_uart_acpi_match[] = {
+	{ "LRWX0000", 0 },
+	{},
+};
+MODULE_DEVICE_TABLE(acpi, lrw_uart_acpi_match);
+
+static struct platform_driver lrw_uart_platform_driver = {
+	.probe		= lrw_uart_probe,
+	.remove		= lrw_uart_remove,
+	.driver		= {
+		.name	= LRW_UART_NAME,
+		.pm = pm_sleep_ptr(&lrw_uart_pm_ops),
+		.of_match_table = of_match_ptr(lrw_uart_of_match),
+		.acpi_match_table = ACPI_PTR(lrw_uart_acpi_match),
+		.suppress_bind_attrs = IS_BUILTIN(CONFIG_SERIAL_LRW_UART),
+	},
+};
+
+static int __init lrw_uart_init(void)
+{
+	pr_info("Serial: LRW UART driver\n");
+
+	int ret;
+
+	ret = uart_register_driver(&lrw_uart_driver);
+	if (ret < 0) {
+		pr_err("Could not register %s driver\n",
+			lrw_uart_driver.driver_name);
+		return ret;
+	}
+
+	ret = platform_driver_register(&lrw_uart_platform_driver);
+	if (ret < 0) {
+		pr_err("LRW UART platform driver register failed, e = %d\n", ret);
+		uart_unregister_driver(&lrw_uart_driver);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void __exit lrw_uart_exit(void)
+{
+	platform_driver_unregister(&lrw_uart_platform_driver);
+	uart_unregister_driver(&lrw_uart_driver);
+}
+
+/*
+ * While this can be a module, if builtin it's most likely the console
+ * So let's leave module_exit but move module_init to an earlier place
+ */
+arch_initcall(lrw_uart_init);
+module_exit(lrw_uart_exit);
+
+MODULE_AUTHOR("Wenhong Liu/Qingtao Liu");
+MODULE_DESCRIPTION("LRW UART serial driver");
+MODULE_LICENSE("GPL");
diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
index 9c007a106330..8e7322067e1f 100644
--- a/include/uapi/linux/serial_core.h
+++ b/include/uapi/linux/serial_core.h
@@ -231,6 +231,9 @@
 /* Sunplus UART */
 #define PORT_SUNPLUS	123
 
+/* LRW UART */
+#define PORT_LRW	124
+
 /* Generic type identifier for ports which type is not important to userspace. */
 #define PORT_GENERIC	(-1)
 
-- 
2.27.0


^ permalink raw reply related

* [PATCH v1 1/2] LRW UART: dt-bindings: Add binding for LRW UART
From: LiuQingtao @ 2026-02-13  9:33 UTC (permalink / raw)
  To: gregkh
  Cc: jirislaby, robh, krzk+dt, conor+dt, neil.armstrong,
	bjorn.andersson, marex, dev, mani, prabhakar.mahadev-lad.rj,
	linux-serial, linux-riscv, liu.wenhong35, liu.qingtao2, hu.yuye,
	dai.hualiang, deng.weixian, jia.yunxiang, bai.lu5, yang.susheng,
	shen.lin1, tan.hu, zuo.jiang
In-Reply-To: <20260213093334.9217-1-qtliu@mail.ustc.edu.cn>

From: Wenhong Liu <liu.wenhong35@zte.com.cn>

Add documentation for LRW UART devicetree bindings.

Signed-off-by: Wenhong Liu <liu.wenhong35@zte.com.cn>
Signed-off-by: Qingtao Liu <liu.qingtao2@zte.com.cn>
---
 .../bindings/serial/lrw,lrw-uart.yaml         | 49 +++++++++++++++++++
 .../devicetree/bindings/vendor-prefixes.yaml  |  2 +
 MAINTAINERS                                   |  7 +++
 3 files changed, 58 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/serial/lrw,lrw-uart.yaml

diff --git a/Documentation/devicetree/bindings/serial/lrw,lrw-uart.yaml b/Documentation/devicetree/bindings/serial/lrw,lrw-uart.yaml
new file mode 100644
index 000000000000..a2d41c278c4f
--- /dev/null
+++ b/Documentation/devicetree/bindings/serial/lrw,lrw-uart.yaml
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/serial/lrw-uart.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: LRW serial UART
+
+maintainers:
+  - Wenhong Liu <liu.wenhong35@zte.com.cn>
+  - Qingtao Liu <liu.qingtao2@zte.com.cn>
+
+description: |
+  Should be something similar to "lrw,<chip>-uart"
+  for the UART as integrated on a particular chip, It supports
+  multiple CPU architectures, currently including e.g. RISC-V and ARM.
+
+properties:
+  compatible:
+    const: lrw,lrw-uart
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+  - current-speed
+  - clocks
+
+additionalProperties: false
+
+examples:
+  - |
+    uart0: serial@e0001800 {
+      compatible = "lrw,lrw-uart";
+      interrupt-parent = <&aplic0>;
+      interrupts = <0x12 0x4>;
+      reg = <0xe0001800 0x100>;
+      clocks = <&bar_clk>;
+      current-speed = <115200>;
+    };
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index ee7fd3cfe203..ec9bf262f466 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -961,6 +961,8 @@ patternProperties:
     description: Loongson Technology Corporation Limited
   "^loongmasses,.*":
     description: Nanjing Loongmasses Ltd.
+  "^lrw,.*":
+    description: LRW Corp.
   "^lsi,.*":
     description: LSI Corp. (LSI Logic)
   "^luckfox,.*":
diff --git a/MAINTAINERS b/MAINTAINERS
index 26898ca27409..ad6acbe24544 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15035,6 +15035,13 @@ L:	linux-edac@vger.kernel.org
 S:	Maintained
 F:	drivers/edac/loongson_edac.c
 
+LRW SERIAL DRIVER
+M:	Wenhong Liu <liu.wenhong35@zte.com.cn>
+R:	Qingtao Liu <liu.qingtao2@zte.com.cn>
+L:	linux-serial@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/serial/lrw,lrw-uart.yaml
+
 LSILOGIC MPT FUSION DRIVERS (FC/SAS/SPI)
 M:	Sathya Prakash <sathya.prakash@broadcom.com>
 M:	Sreekanth Reddy <sreekanth.reddy@broadcom.com>
-- 
2.27.0


^ permalink raw reply related

* [PATCH v1 0/2] LRW UART: Patch series for LRW UART driver
From: LiuQingtao @ 2026-02-13  9:33 UTC (permalink / raw)
  To: gregkh
  Cc: jirislaby, robh, krzk+dt, conor+dt, neil.armstrong,
	bjorn.andersson, marex, dev, mani, prabhakar.mahadev-lad.rj,
	linux-serial, linux-riscv, liu.wenhong35, liu.qingtao2, hu.yuye,
	dai.hualiang, deng.weixian, jia.yunxiang, bai.lu5, yang.susheng,
	shen.lin1, tan.hu, zuo.jiang

From: Wenhong Liu <liu.wenhong35@zte.com.cn>

This patch series introduce the driver for LRW UART
  dt-bindings: serial: Add binding for LRW UART
  tty: serial: add driver for the LRW UART

 .../bindings/serial/lrw,lrw-uart.yaml         |   49 +
 .../devicetree/bindings/vendor-prefixes.yaml  |    2 +
 MAINTAINERS                                   |   10 +
 drivers/tty/serial/Kconfig                    |   33 +
 drivers/tty/serial/Makefile                   |    1 +
 drivers/tty/serial/lrw_uart.c                 | 2822 +++++++++++++++++
 include/uapi/linux/serial_core.h              |    3 +
 7 files changed, 2920 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/serial/lrw,lrw-uart.yaml
 create mode 100644 drivers/tty/serial/lrw_uart.c

-- 
2.27.0


^ permalink raw reply

* [PATCH 2/2] LRW UART: serial: add driver for the LRW UART
From: LiuQingtao @ 2026-02-13  9:26 UTC (permalink / raw)
  To: liu.qingtao2; +Cc: Wenhong Liu, linux-serial, linux-riscv
In-Reply-To: <20260213092615.8584-1-qtliu@mail.ustc.edu.cn>

From: Wenhong Liu <liu.wenhong35@zte.com.cn>

This commit introduces a serial driver for the LRW UART controller

Key features implemented:
- Support for FIFO mode (16-byte depth)
- Baud rate configuration
- Standard asynchronous communication formats:
  * Data bits: 5, 6, 7, 8, 9 bits
  * Parity: odd, even, fixed, none
  * Stop bits: 1 or 2 bits
- Hardware flow control (RTS/CTS)
- Multiple interrupt reporting mechanisms

Signed-off-by: Wenhong Liu <liu.wenhong35@zte.com.cn>
Signed-off-by: Qingtao Liu <liu.qingtao2@zte.com.cn>
Cc: linux-serial@vger.kernel.org
Cc: linux-riscv@lists.infradead.org
---
 MAINTAINERS                      |    3 +
 drivers/tty/serial/Kconfig       |   33 +
 drivers/tty/serial/Makefile      |    1 +
 drivers/tty/serial/lrw_uart.c    | 2822 ++++++++++++++++++++++++++++++
 include/uapi/linux/serial_core.h |    3 +
 5 files changed, 2862 insertions(+)
 create mode 100644 drivers/tty/serial/lrw_uart.c

diff --git a/MAINTAINERS b/MAINTAINERS
index ad6acbe24544..a97fbd205f75 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15041,6 +15041,9 @@ R:	Qingtao Liu <liu.qingtao2@zte.com.cn>
 L:	linux-serial@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/serial/lrw,lrw-uart.yaml
+F:	drivers/tty/serial/Kconfig
+F:	drivers/tty/serial/Makefile
+F:	drivers/tty/serial/lrw_uart.c
 
 LSILOGIC MPT FUSION DRIVERS (FC/SAS/SPI)
 M:	Sathya Prakash <sathya.prakash@broadcom.com>
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index f86775cfdcc9..a8f2d750c5b4 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1619,6 +1619,39 @@ config SERIAL_ESP32_ACM
 	  snippet may be used:
 	    earlycon=esp32s3acm,mmio32,0x60038000
 
+config SERIAL_LRW_UART
+	tristate "LRW UART support"
+	select SERIAL_CORE
+	help
+	  This option enables support for the LRW Universal Asynchronous
+	  Receiver/Transmitter (UART) serial controller.
+
+	  Select this option if you are building a kernel for a device that
+	  contains a LRW UART IP block.
+
+	  This driver can be built as a module; if so, the module will be
+	  called lrw_uart.
+
+	  If you are using a system with an LRW UART controller, say Y or M here.
+	  If unsure, say N.
+
+config SERIAL_LRW_UART_CONSOLE
+	bool "Console on LRW UART"
+	depends on SERIAL_LRW_UART=y
+	select SERIAL_CORE_CONSOLE
+	select SERIAL_EARLYCON
+	help
+	  Say Y here if you wish to use an LRW UART as the system
+	  console (the system console is the device which receives all kernel
+	  messages and warnings and which allows logins in single user mode).
+
+	  Even if you say Y here, the currently visible framebuffer console
+	  (/dev/tty0) will still be used as the system console by default, but
+	  you can alter that using a kernel command line option such as
+	  "console=ttyLRW0". (Try "man bootparam" or see the documentation of
+	  your boot loader (lilo or loadlin) about how to pass options to the
+	  kernel at boot time.)
+
 endmenu
 
 config SERIAL_MCTRL_GPIO
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index a2ccbc508ec5..0f694c4a4e22 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -94,6 +94,7 @@ obj-$(CONFIG_SERIAL_UARTLITE)		+= uartlite.o
 obj-$(CONFIG_SERIAL_VT8500)		+= vt8500_serial.o
 obj-$(CONFIG_SERIAL_XILINX_PS_UART)	+= xilinx_uartps.o
 obj-$(CONFIG_SERIAL_ZS)			+= zs.o
+obj-$(CONFIG_SERIAL_LRW_UART) 		+= lrw_uart.o
 
 # GPIOLIB helpers for modem control lines
 obj-$(CONFIG_SERIAL_MCTRL_GPIO)	+= serial_mctrl_gpio.o
diff --git a/drivers/tty/serial/lrw_uart.c b/drivers/tty/serial/lrw_uart.c
new file mode 100644
index 000000000000..e2399bef203f
--- /dev/null
+++ b/drivers/tty/serial/lrw_uart.c
@@ -0,0 +1,2822 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  Serial Port driver for LRW
+ *
+ *  Copyright (c) 2025, LRW CORPORATION. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/console.h>
+#include <linux/platform_device.h>
+#include <linux/sysrq.h>
+#include <linux/device.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial_core.h>
+#include <linux/serial.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/scatterlist.h>
+#include <linux/delay.h>
+#include <linux/types.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/sizes.h>
+#include <linux/io.h>
+#include <linux/acpi.h>
+
+#define UART_NR			14
+
+#define ISR_PASS_LIMIT		256
+
+#define LRW_UART_NAME		"lrw-uart"
+
+#define LRW_UART_TTY_PREFIX	"ttyLRW"
+
+/* LRW_UART_TX_FIFO_DEPTH: depth of the TX FIFO (in bytes) */
+#define LRW_UART_TX_FIFO_DEPTH	16
+
+/* LRW_UART_RX_FIFO_DEPTH: depth of the RX FIFO (in bytes) */
+#define LRW_UART_RX_FIFO_DEPTH	16
+
+/* LRW UART register offsets */
+#define UARTDR			0x00	/* Data register */
+#define UARTRSR			0x04	/* Receive status register */
+#define UARTECR			0x04	/* Error clear register */
+#define UARTSC			0x08	/* Special character register */
+#define UARTMDR			0x0C	/* RS485 Muti-drop register */
+#define UARTTAT			0x10	/* RS485 turn-around time register */
+#define UARTFCR			0x14	/* FIFO control register */
+#define UARTFR			0x18	/* Flag register */
+#define UARTIND			0x1C	/* Integer baud rate register */
+#define UARTFD			0x20	/* Fractional baud rate register */
+#define UARTBSR			0x24	/* Baud sample rate register */
+#define	UARTFRCR		0x28	/* Frame control register */
+#define UARTMCFG		0x2C	/* config register */
+#define	UARTMCR			0x30	/* Modem control register */
+#define	UARTIRCR		0x34	/* IrDA mode control register */
+#define UARTIMSC		0x38	/* Interrupt mask set/clear register */
+#define UARTRIS			0x3C	/* Raw interrupt status register */
+#define UARTMIS			0x40	/* Masked interrupt states register */
+#define UARTICR			0x44	/* Interrupt clear register */
+#define UARTFCCR		0x48	/* Flow control register */
+#define UARTRVS			0x58	/* Version register */
+
+#define UARTDR_OE		BIT(11)
+#define UARTDR_BE		BIT(10)
+#define UARTDR_PE		BIT(9)
+#define UARTDR_FE		BIT(8)
+
+#define UARTRSR_OE		BIT(3)
+#define UARTRSR_BE		BIT(2)
+#define UARTRSR_PE		BIT(1)
+#define UARTRSR_FE		BIT(0)
+
+#define UARTFCR_RXFTRS		GENMASK(7, 5)
+#define UARTFCR_RXFTRS_RX1_8	FIELD_PREP_CONST(UARTFCR_RXFTRS, 0)
+#define UARTFCR_RXFTRS_RX2_8	FIELD_PREP_CONST(UARTFCR_RXFTRS, 1)
+#define UARTFCR_RXFTRS_RX4_8	FIELD_PREP_CONST(UARTFCR_RXFTRS, 2)
+#define UARTFCR_RXFTRS_RX6_8	FIELD_PREP_CONST(UARTFCR_RXFTRS, 3)
+#define UARTFCR_RXFTRS_RX7_8	FIELD_PREP_CONST(UARTFCR_RXFTRS, 4)
+#define UARTFCR_TXFTRS		GENMASK(4, 2)
+#define UARTFCR_TXFTRS_TX1_8	FIELD_PREP_CONST(UARTFCR_TXFTRS, 0)
+#define UARTFCR_TXFTRS_TX2_8	FIELD_PREP_CONST(UARTFCR_TXFTRS, 1)
+#define UARTFCR_TXFTRS_TX4_8	FIELD_PREP_CONST(UARTFCR_TXFTRS, 2)
+#define UARTFCR_TXFTRS_TX6_8	FIELD_PREP_CONST(UARTFCR_TXFTRS, 3)
+#define UARTFCR_TXFTRS_TX7_8	FIELD_PREP_CONST(UARTFCR_TXFTRS, 4)
+#define UARTFCR_FEN		BIT(0)
+
+#define UARTFR_RI		BIT(8)
+#define UARTFR_TXFE		BIT(7)
+#define UARTFR_RXFF		BIT(6)
+#define UARTFR_TXFF		(1 << 5)	/* used in ASM */
+#define UARTFR_RXFE		BIT(4)
+#define UARTFR_BUSY		(1 << 3)	/* used in ASM */
+#define UARTFR_DCD		BIT(2)
+#define UARTFR_DSR		BIT(1)
+#define UARTFR_CTS		BIT(0)
+#define UARTFR_TMSK		(UARTFR_TXFF + UARTFR_BUSY)
+
+#define UARTFRCR_STP2		BIT(5)
+#define UARTFRCR_SPS		BIT(4)
+#define UARTFRCR_EOP		BIT(3)
+#define UARTFRCR_PEN		BIT(2)
+#define UARTFRCR_WLEN_8		0x3
+#define UARTFRCR_WLEN_7		0x2
+#define UARTFRCR_WLEN_6		0x1
+#define UARTFRCR_WLEN_5		0x0
+
+#define UARTMCFG_LBE		BIT(7)	/* loopback enable */
+#define UARTMCFG_RXE		BIT(3)	/* receive enable */
+#define UARTMCFG_TXE		BIT(2)	/* transmit enable */
+#define UARTMCFG_BRK		BIT(1)	/* send break */
+#define UARTMCFG_UARTEN		BIT(0)	/* UART enable */
+
+#define UARTMCR_OUT2		BIT(3)	/* OUT2 */
+#define UARTMCR_OUT1		BIT(2)	/* OUT1 */
+#define UARTMCR_RTS		BIT(1)	/* RTS */
+#define UARTMCR_DTR		BIT(0)	/* DTR */
+
+#define UARTIMSC_OEIM		BIT(10)	/* overrun error interrupt mask */
+#define UARTIMSC_BEIM		BIT(9)	/* break error interrupt mask */
+#define UARTIMSC_PEIM		BIT(8)	/* parity error interrupt mask */
+#define UARTIMSC_FEIM		BIT(7)	/* framing error interrupt mask */
+#define UARTIMSC_RTIM		BIT(6)	/* receive timeout interrupt mask */
+#define UARTIMSC_TXIM		BIT(5)	/* transmit interrupt mask */
+#define UARTIMSC_RXIM		BIT(4)	/* receive interrupt mask */
+#define UARTIMSC_DSRMIM		BIT(3)	/* DSR interrupt mask */
+#define UARTIMSC_DCDMIM		BIT(2)	/* DCD interrupt mask */
+#define UARTIMSC_CTSMIM		BIT(1)	/* CTS interrupt mask */
+#define UARTIMSC_RIMIM		BIT(0)	/* RI interrupt mask */
+
+#define UARTICR_OEIC		BIT(10)	/* overrun error interrupt clear */
+#define UARTICR_BEIC		BIT(9)	/* break error interrupt clear */
+#define UARTICR_PEIC		BIT(8)	/* parity error interrupt clear */
+#define UARTICR_FEIC		BIT(7)	/* framing error interrupt clear */
+#define UARTICR_RTIC		BIT(6)	/* receive timeout interrupt clear */
+#define UARTICR_TXIC		BIT(5)	/* transmit interrupt clear */
+#define UARTICR_RXIC		BIT(4)	/* receive interrupt clear */
+#define UARTICR_DSRMIC		BIT(3)	/* DSR interrupt clear */
+#define UARTICR_DCDMIC		BIT(2)	/* DCD interrupt clear */
+#define UARTICR_CTSMIC		BIT(1)	/* CTS interrupt clear */
+#define UARTICR_RIMIC		BIT(0)	/* RI interrupt clear */
+
+#define UARTFCCR_CTSEN		BIT(5)	/* CTS hardware flow control */
+#define UARTFCCR_RTSEN		BIT(4)	/* RTS hardware flow control */
+#define UARTFCCR_DMAONERR	BIT(2)	/* disable dma on error */
+#define UARTFCCR_TXDMAE		BIT(1)	/* enable transmit dma */
+#define UARTFCCR_RXDMAE		BIT(0)	/* enable receive dma */
+
+#define UARTRSR_ANY		(UARTRSR_OE | UARTRSR_BE | UARTRSR_PE | UARTRSR_FE)
+#define UARTFR_MODEM_ANY	(UARTFR_DCD | UARTFR_DSR | UARTFR_CTS)
+
+#define UART_DR_ERROR		(UARTDR_OE | UARTDR_BE | UARTDR_PE | UARTDR_FE)
+#define UART_DUMMY_DR_RX	BIT(16)
+
+enum {
+	REG_DR,
+	REG_FCR,
+	REG_FR,
+	REG_IND,
+	REG_FD,
+	REG_BSR,
+	REG_FRCR,
+	REG_MCFG,
+	REG_MCR,
+	REG_IMSC,
+	REG_RIS,
+	REG_MIS,
+	REG_ICR,
+	REG_FCCR,
+
+	/* The size of the array - must be last */
+	REG_ARRAY_SIZE,
+};
+
+static u16 lrw_uart_std_offsets[REG_ARRAY_SIZE] = {
+	[REG_DR] = UARTDR,
+	[REG_FCR] = UARTFCR,
+	[REG_FR] = UARTFR,
+	[REG_IND] = UARTIND,
+	[REG_FD] = UARTFD,
+	[REG_BSR] = UARTBSR,
+	[REG_FRCR] = UARTFRCR,
+	[REG_MCFG] = UARTMCFG,
+	[REG_MCR] = UARTMCR,
+	[REG_IMSC] = UARTIMSC,
+	[REG_RIS] = UARTRIS,
+	[REG_MIS] = UARTMIS,
+	[REG_ICR] = UARTICR,
+	[REG_FCCR] = UARTFCCR,
+};
+
+/* There is by now at least one vendor with differing details, so handle it */
+struct vendor_data {
+	const u16		*reg_offset;
+	unsigned int		fcr;
+	unsigned int		fr_busy;
+	unsigned int		fr_dsr;
+	unsigned int		fr_cts;
+	unsigned int		fr_ri;
+	unsigned int		inv_fr;
+	bool			access_32b;
+	bool			oversampling;
+	bool			dma_threshold;
+	bool			cts_event_workaround;
+	bool			always_enabled;
+	bool			fixed_options;
+};
+
+static struct vendor_data vendor_lrw = {
+	.reg_offset		= lrw_uart_std_offsets,
+	.fcr			= UARTFCR_RXFTRS_RX4_8 | UARTFCR_TXFTRS_TX4_8 | UARTFCR_FEN,
+	.fr_busy		= UARTFR_BUSY,
+	.fr_dsr			= UARTFR_DSR,
+	.fr_cts			= UARTFR_CTS,
+	.fr_ri			= UARTFR_RI,
+	.access_32b		= true,
+	.oversampling		= false,
+	.dma_threshold		= false,
+	.cts_event_workaround	= false,
+	.always_enabled		= false,
+	.fixed_options		= true,
+};
+
+/* Deals with DMA transactions */
+
+struct lrw_uart_dmabuf {
+	dma_addr_t		dma;
+	size_t			len;
+	char			*buf;
+};
+
+struct lrw_uart_dmarx_data {
+	struct dma_chan		*chan;
+	struct completion	complete;
+	bool			use_buf_b;
+	struct lrw_uart_dmabuf	dbuf_a;
+	struct lrw_uart_dmabuf	dbuf_b;
+	dma_cookie_t		cookie;
+	bool			running;
+	struct timer_list	timer;
+	unsigned int last_residue;
+	unsigned long last_jiffies;
+	bool auto_poll_rate;
+	unsigned int poll_rate;
+	unsigned int poll_timeout;
+};
+
+struct lrw_uart_dmatx_data {
+	struct dma_chan		*chan;
+	dma_addr_t		dma;
+	size_t			len;
+	char			*buf;
+	bool			queued;
+};
+
+struct lrw_uart_data {
+	bool (*dma_filter)(struct dma_chan *chan, void *filter_param);
+	void *dma_rx_param;
+	void *dma_tx_param;
+	bool dma_rx_poll_enable;
+	unsigned int dma_rx_poll_rate;
+	unsigned int dma_rx_poll_timeout;
+	void (*init)(void);
+	void (*exit)(void);
+};
+
+/*
+ * We wrap our port structure around the generic uart_port.
+ */
+struct lrw_uart_port {
+	struct uart_port	port;
+	const u16		*reg_offset;
+	struct clk		*clk;
+	const struct vendor_data *vendor;
+	unsigned int		im;		/* interrupt mask */
+	unsigned int		old_status;
+	unsigned int		fifosize;	/* vendor-specific */
+	unsigned int		fixed_baud;	/* vendor-set fixed baud rate */
+	char			type[12];
+	bool			rs485_tx_started;
+	unsigned int		rs485_tx_drain_interval; /* usecs */
+#ifdef CONFIG_DMA_ENGINE
+	/* DMA stuff */
+	unsigned int		dmacr;		/* dma control reg */
+	bool			using_tx_dma;
+	bool			using_rx_dma;
+	struct lrw_uart_dmarx_data dmarx;
+	struct lrw_uart_dmatx_data dmatx;
+	bool			dma_probed;
+#endif
+};
+
+static unsigned int lrw_uart_tx_empty(struct uart_port *port);
+
+static unsigned int lrw_uart_reg_to_offset(const struct lrw_uart_port *sup,
+	unsigned int reg)
+{
+	return sup->reg_offset[reg];
+}
+
+static unsigned int lrw_uart_read(const struct lrw_uart_port *sup,
+	unsigned int reg)
+{
+	void __iomem *addr = sup->port.membase + lrw_uart_reg_to_offset(sup, reg);
+
+	return (sup->port.iotype == UPIO_MEM32) ?
+		readl_relaxed(addr) : readw_relaxed(addr);
+}
+
+static void lrw_uart_write(unsigned int val, const struct lrw_uart_port *sup,
+	unsigned int reg)
+{
+	void __iomem *addr = sup->port.membase + lrw_uart_reg_to_offset(sup, reg);
+
+	if (sup->port.iotype == UPIO_MEM32)
+		writel_relaxed(val, addr);
+	else
+		writew_relaxed(val, addr);
+}
+
+/*
+ * Reads up to 256 characters from the FIFO or until it's empty and
+ * inserts them into the TTY layer. Returns the number of characters
+ * read from the FIFO.
+ */
+static int lrw_uart_fifo_to_tty(struct lrw_uart_port *sup)
+{
+	unsigned int ch, fifotaken;
+	int sysrq;
+	u16 status;
+	u8 flag;
+
+	for (fifotaken = 0; fifotaken != 256; fifotaken++) {
+		status = lrw_uart_read(sup, REG_FR);
+		if (status & UARTFR_RXFE)
+			break;
+
+		/* Take chars from the FIFO and update status */
+		ch = lrw_uart_read(sup, REG_DR) | UART_DUMMY_DR_RX;
+		flag = TTY_NORMAL;
+		sup->port.icount.rx++;
+
+		if (unlikely(ch & UART_DR_ERROR)) {
+			if (ch & UARTDR_BE) {
+				ch &= ~(UARTDR_FE | UARTDR_PE);
+				sup->port.icount.brk++;
+				if (uart_handle_break(&sup->port))
+					continue;
+			} else if (ch & UARTDR_PE) {
+				sup->port.icount.parity++;
+			} else if (ch & UARTDR_FE) {
+				sup->port.icount.frame++;
+			}
+			if (ch & UARTDR_OE)
+				sup->port.icount.overrun++;
+
+			ch &= sup->port.read_status_mask;
+
+			if (ch & UARTDR_BE)
+				flag = TTY_BREAK;
+			else if (ch & UARTDR_PE)
+				flag = TTY_PARITY;
+			else if (ch & UARTDR_FE)
+				flag = TTY_FRAME;
+		}
+
+		sysrq = uart_prepare_sysrq_char(&sup->port, ch & 255);
+		if (!sysrq)
+			uart_insert_char(&sup->port, ch, UARTDR_OE, ch, flag);
+	}
+
+	return fifotaken;
+}
+
+/*
+ * All the DMA operation mode stuff goes inside this ifdef.
+ * This assumes that you have a generic DMA device interface,
+ * no custom DMA interfaces are supported.
+ */
+#ifdef CONFIG_DMA_ENGINE
+
+#define LRW_UART_DMA_BUFFER_SIZE PAGE_SIZE
+
+static int lrw_uart_dmabuf_init(struct dma_chan *chan, struct lrw_uart_dmabuf *db,
+	enum dma_data_direction dir)
+{
+	db->buf = dma_alloc_coherent(chan->device->dev, LRW_UART_DMA_BUFFER_SIZE,
+				     &db->dma, GFP_KERNEL);
+	if (!db->buf)
+		return -ENOMEM;
+	db->len = LRW_UART_DMA_BUFFER_SIZE;
+
+	return 0;
+}
+
+static void lrw_uart_dmabuf_free(struct dma_chan *chan, struct lrw_uart_dmabuf *db,
+	enum dma_data_direction dir)
+{
+	if (db->buf) {
+		dma_free_coherent(chan->device->dev,
+				  LRW_UART_DMA_BUFFER_SIZE, db->buf, db->dma);
+	}
+}
+
+static void lrw_uart_dma_probe(struct lrw_uart_port *sup)
+{
+	/* DMA is the sole user of the platform data right now */
+	struct lrw_uart_data *plat = dev_get_platdata(sup->port.dev);
+	struct device *dev = sup->port.dev;
+	struct dma_slave_config tx_conf = {
+		.dst_addr = sup->port.mapbase +
+				 lrw_uart_reg_to_offset(sup, REG_DR),
+		.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE,
+		.direction = DMA_MEM_TO_DEV,
+		.dst_maxburst = sup->fifosize >> 1,
+		.device_fc = false,
+	};
+	struct dma_chan *chan;
+	dma_cap_mask_t mask;
+
+	sup->dma_probed = true;
+	chan = dma_request_chan(dev, "tx");
+	if (IS_ERR(chan)) {
+		if (PTR_ERR(chan) == -EPROBE_DEFER) {
+			sup->dma_probed = false;
+			return;
+		}
+
+		/* We need platform data */
+		if (!plat || !plat->dma_filter) {
+			dev_dbg(sup->port.dev, "no DMA platform data\n");
+			return;
+		}
+
+		/* Try to acquire a generic DMA engine slave TX channel */
+		dma_cap_zero(mask);
+		dma_cap_set(DMA_SLAVE, mask);
+
+		chan = dma_request_channel(mask, plat->dma_filter,
+					   plat->dma_tx_param);
+		if (!chan) {
+			dev_err(sup->port.dev, "no TX DMA channel!\n");
+			return;
+		}
+	}
+
+	dmaengine_slave_config(chan, &tx_conf);
+	sup->dmatx.chan = chan;
+
+	dev_info(sup->port.dev, "DMA channel TX %s\n",
+		 dma_chan_name(sup->dmatx.chan));
+
+	/* Optionally make use of an RX channel as well */
+	chan = dma_request_chan(dev, "rx");
+
+	if (IS_ERR(chan) && plat && plat->dma_rx_param) {
+		chan = dma_request_channel(mask, plat->dma_filter, plat->dma_rx_param);
+
+		if (!chan) {
+			dev_err(sup->port.dev, "no RX DMA channel!\n");
+			return;
+		}
+	}
+
+	if (!IS_ERR(chan)) {
+		struct dma_slave_config rx_conf = {
+			.src_addr = sup->port.mapbase +
+				lrw_uart_reg_to_offset(sup, REG_DR),
+			.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE,
+			.direction = DMA_DEV_TO_MEM,
+			.src_maxburst = sup->fifosize >> 2,
+			.device_fc = false,
+		};
+		struct dma_slave_caps caps;
+
+		/*
+		 * Some DMA controllers provide information on their capabilities.
+		 * If the controller does, check for suitable residue processing
+		 * otherwise assime all is well.
+		 */
+		if (dma_get_slave_caps(chan, &caps) == 0) {
+			if (caps.residue_granularity ==
+					DMA_RESIDUE_GRANULARITY_DESCRIPTOR) {
+				dma_release_channel(chan);
+				dev_info(sup->port.dev,
+					 "RX DMA disabled - no residue processing\n");
+				return;
+			}
+		}
+		dmaengine_slave_config(chan, &rx_conf);
+		sup->dmarx.chan = chan;
+
+		sup->dmarx.auto_poll_rate = false;
+		if (plat && plat->dma_rx_poll_enable) {
+			/* Set poll rate if specified. */
+			if (plat->dma_rx_poll_rate) {
+				sup->dmarx.auto_poll_rate = false;
+				sup->dmarx.poll_rate = plat->dma_rx_poll_rate;
+			} else {
+				/*
+				 * 100 ms defaults to poll rate if not
+				 * specified. This will be adjusted with
+				 * the baud rate at set_termios.
+				 */
+				sup->dmarx.auto_poll_rate = true;
+				sup->dmarx.poll_rate =  100;
+			}
+			/* 3 secs defaults poll_timeout if not specified. */
+			if (plat->dma_rx_poll_timeout)
+				sup->dmarx.poll_timeout =
+					plat->dma_rx_poll_timeout;
+			else
+				sup->dmarx.poll_timeout = 3000;
+		} else if (!plat && dev->of_node) {
+			sup->dmarx.auto_poll_rate =
+					of_property_read_bool(dev->of_node, "auto-poll");
+			if (sup->dmarx.auto_poll_rate) {
+				u32 x;
+
+				if (of_property_read_u32(dev->of_node, "poll-rate-ms", &x) == 0)
+					sup->dmarx.poll_rate = x;
+				else
+					sup->dmarx.poll_rate = 100;
+				if (of_property_read_u32(dev->of_node, "poll-timeout-ms", &x) == 0)
+					sup->dmarx.poll_timeout = x;
+				else
+					sup->dmarx.poll_timeout = 3000;
+			}
+		}
+		dev_info(sup->port.dev, "DMA channel RX %s\n",
+			 dma_chan_name(sup->dmarx.chan));
+	}
+}
+
+static void lrw_uart_dma_remove(struct lrw_uart_port *sup)
+{
+	if (sup->dmatx.chan)
+		dma_release_channel(sup->dmatx.chan);
+	if (sup->dmarx.chan)
+		dma_release_channel(sup->dmarx.chan);
+}
+
+/* Forward declare these for the refill routine */
+static int lrw_uart_dma_tx_refill(struct lrw_uart_port *sup);
+static void lrw_uart_start_tx_pio(struct lrw_uart_port *sup);
+
+/*
+ * The current DMA TX buffer has been sent.
+ * Try to queue up another DMA buffer.
+ */
+static void lrw_uart_dma_tx_callback(void *data)
+{
+	struct lrw_uart_port *sup = data;
+	struct tty_port *tport = &sup->port.state->port;
+	struct lrw_uart_dmatx_data *dmatx = &sup->dmatx;
+	unsigned long flags;
+	u16 dmacr;
+
+	uart_port_lock_irqsave(&sup->port, &flags);
+	if (sup->dmatx.queued)
+		dma_unmap_single(dmatx->chan->device->dev, dmatx->dma,
+				 dmatx->len, DMA_TO_DEVICE);
+
+	dmacr = sup->dmacr;
+	sup->dmacr = dmacr & ~UARTFCCR_TXDMAE;
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+
+	/*
+	 * If TX DMA was disabled, it means that we've stopped the DMA for
+	 * some reason (eg, XOFF received, or we want to send an X-char.)
+	 *
+	 * Note: we need to be careful here of a potential race between DMA
+	 * and the rest of the driver - if the driver disables TX DMA while
+	 * a TX buffer completing, we must update the tx queued status to
+	 * get further refills (hence we check dmacr).
+	 */
+	if (!(dmacr & UARTFCCR_TXDMAE) || uart_tx_stopped(&sup->port) ||
+	    kfifo_is_empty(&tport->xmit_fifo)) {
+		sup->dmatx.queued = false;
+		uart_port_unlock_irqrestore(&sup->port, flags);
+		return;
+	}
+
+	if (lrw_uart_dma_tx_refill(sup) <= 0)
+		/*
+		 * We didn't queue a DMA buffer for some reason, but we
+		 * have data pending to be sent.  Re-enable the TX IRQ.
+		 */
+		lrw_uart_start_tx_pio(sup);
+
+	uart_port_unlock_irqrestore(&sup->port, flags);
+}
+
+/*
+ * Try to refill the TX DMA buffer.
+ * Locking: called with port lock held and IRQs disabled.
+ * Returns:
+ *   1 if we queued up a TX DMA buffer.
+ *   0 if we didn't want to handle this by DMA
+ *  <0 on error
+ */
+static int lrw_uart_dma_tx_refill(struct lrw_uart_port *sup)
+{
+	struct lrw_uart_dmatx_data *dmatx = &sup->dmatx;
+	struct dma_chan *chan = dmatx->chan;
+	struct dma_device *dma_dev = chan->device;
+	struct dma_async_tx_descriptor *desc;
+	struct tty_port *tport = &sup->port.state->port;
+	unsigned int count;
+
+	/*
+	 * Try to avoid the overhead involved in using DMA if the
+	 * transaction fits in the first half of the FIFO, by using
+	 * the standard interrupt handling.  This ensures that we
+	 * issue a uart_write_wakeup() at the appropriate time.
+	 */
+	count = kfifo_len(&tport->xmit_fifo);
+	if (count < (sup->fifosize >> 1)) {
+		sup->dmatx.queued = false;
+		return 0;
+	}
+
+	/*
+	 * Bodge: don't send the last character by DMA, as this
+	 * will prevent XON from notifying us to restart DMA.
+	 */
+	count -= 1;
+
+	/* Else proceed to copy the TX chars to the DMA buffer and fire DMA */
+	if (count > LRW_UART_DMA_BUFFER_SIZE)
+		count = LRW_UART_DMA_BUFFER_SIZE;
+
+	count = kfifo_out_peek(&tport->xmit_fifo, dmatx->buf, count);
+	dmatx->len = count;
+	dmatx->dma = dma_map_single(dma_dev->dev, dmatx->buf, count,
+				    DMA_TO_DEVICE);
+	if (dmatx->dma == DMA_MAPPING_ERROR) {
+		sup->dmatx.queued = false;
+		dev_dbg(sup->port.dev, "unable to map TX DMA\n");
+		return -EBUSY;
+	}
+
+	desc = dmaengine_prep_slave_single(chan, dmatx->dma, dmatx->len, DMA_MEM_TO_DEV,
+					   DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+	if (!desc) {
+		dma_unmap_single(dma_dev->dev, dmatx->dma, dmatx->len, DMA_TO_DEVICE);
+		sup->dmatx.queued = false;
+		/*
+		 * If DMA cannot be used right now, we complete this
+		 * transaction via IRQ and let the TTY layer retry.
+		 */
+		dev_dbg(sup->port.dev, "TX DMA busy\n");
+		return -EBUSY;
+	}
+
+	/* Some data to go along to the callback */
+	desc->callback = lrw_uart_dma_tx_callback;
+	desc->callback_param = sup;
+
+	/* All errors should happen at prepare time */
+	dmaengine_submit(desc);
+
+	/* Fire the DMA transaction */
+	dma_dev->device_issue_pending(chan);
+
+	sup->dmacr |= UARTFCCR_TXDMAE;
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+	sup->dmatx.queued = true;
+
+	/*
+	 * Now we know that DMA will fire, so advance the ring buffer
+	 * with the stuff we just dispatched.
+	 */
+	uart_xmit_advance(&sup->port, count);
+
+	if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
+		uart_write_wakeup(&sup->port);
+
+	return 1;
+}
+
+/*
+ * We received a transmit interrupt without a pending X-char but with
+ * pending characters.
+ * Locking: called with port lock held and IRQs disabled.
+ * Returns:
+ *   false if we want to use PIO to transmit
+ *   true if we queued a DMA buffer
+ */
+static bool lrw_uart_dma_tx_irq(struct lrw_uart_port *sup)
+{
+	if (!sup->using_tx_dma)
+		return false;
+
+	/*
+	 * If we already have a TX buffer queued, but received a
+	 * TX interrupt, it will be because we've just sent an X-char.
+	 * Ensure the TX DMA is enabled and the TX IRQ is disabled.
+	 */
+	if (sup->dmatx.queued) {
+		sup->dmacr |= UARTFCCR_TXDMAE;
+		lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+		sup->im &= ~UARTIMSC_TXIM;
+		lrw_uart_write(sup->im, sup, REG_IMSC);
+		return true;
+	}
+
+	/*
+	 * We don't have a TX buffer queued, so try to queue one.
+	 * If we successfully queued a buffer, mask the TX IRQ.
+	 */
+	if (lrw_uart_dma_tx_refill(sup) > 0) {
+		sup->im &= ~UARTIMSC_TXIM;
+		lrw_uart_write(sup->im, sup, REG_IMSC);
+		return true;
+	}
+	return false;
+}
+
+/*
+ * Stop the DMA transmit (eg, due to received XOFF).
+ * Locking: called with port lock held and IRQs disabled.
+ */
+static inline void lrw_uart_dma_tx_stop(struct lrw_uart_port *sup)
+{
+	if (sup->dmatx.queued) {
+		sup->dmacr &= ~UARTFCCR_TXDMAE;
+		lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+	}
+}
+
+/*
+ * Try to start a DMA transmit, or in the case of an XON/OFF
+ * character queued for send, try to get that character out ASAP.
+ * Locking: called with port lock held and IRQs disabled.
+ * Returns:
+ *   false if we want the TX IRQ to be enabled
+ *   true if we have a buffer queued
+ */
+static inline bool lrw_uart_dma_tx_start(struct lrw_uart_port *sup)
+{
+	u16 dmacr;
+
+	if (!sup->using_tx_dma)
+		return false;
+
+	if (!sup->port.x_char) {
+		/* no X-char, try to push chars out in DMA mode */
+		bool ret = true;
+
+		if (!sup->dmatx.queued) {
+			if (lrw_uart_dma_tx_refill(sup) > 0) {
+				sup->im &= ~UARTIMSC_TXIM;
+				lrw_uart_write(sup->im, sup, REG_IMSC);
+			} else {
+				ret = false;
+			}
+		} else if (!(sup->dmacr & UARTFCCR_TXDMAE)) {
+			sup->dmacr |= UARTFCCR_TXDMAE;
+			lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+		}
+		return ret;
+	}
+
+	/*
+	 * We have an X-char to send.  Disable DMA to prevent it loading
+	 * the TX fifo, and then see if we can stuff it into the FIFO.
+	 */
+	dmacr = sup->dmacr;
+	sup->dmacr &= ~UARTFCCR_TXDMAE;
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+
+	if (lrw_uart_read(sup, REG_FR) & UARTFR_TXFF) {
+		/*
+		 * No space in the FIFO, so enable the transmit interrupt
+		 * so we know when there is space.  Note that once we've
+		 * loaded the character, we should just re-enable DMA.
+		 */
+		return false;
+	}
+
+	lrw_uart_write(sup->port.x_char, sup, REG_DR);
+	sup->port.icount.tx++;
+	sup->port.x_char = 0;
+
+	/* Success - restore the DMA state */
+	sup->dmacr = dmacr;
+	lrw_uart_write(dmacr, sup, REG_FCCR);
+
+	return true;
+}
+
+/*
+ * Flush the transmit buffer.
+ * Locking: called with port lock held and IRQs disabled.
+ */
+static void lrw_uart_dma_flush_buffer(struct uart_port *port)
+__releases(&sup->port.lock)
+__acquires(&sup->port.lock)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	if (!sup->using_tx_dma)
+		return;
+
+	dmaengine_terminate_async(sup->dmatx.chan);
+
+	if (sup->dmatx.queued) {
+		dma_unmap_single(sup->dmatx.chan->device->dev, sup->dmatx.dma,
+				 sup->dmatx.len, DMA_TO_DEVICE);
+		sup->dmatx.queued = false;
+		sup->dmacr &= ~UARTFCCR_TXDMAE;
+		lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+	}
+}
+
+static void lrw_uart_dma_rx_callback(void *data);
+
+static int lrw_uart_dma_rx_trigger_dma(struct lrw_uart_port *sup)
+{
+	struct dma_chan *rxchan = sup->dmarx.chan;
+	struct lrw_uart_dmarx_data *dmarx = &sup->dmarx;
+	struct dma_async_tx_descriptor *desc;
+	struct lrw_uart_dmabuf *dbuf;
+
+	if (!rxchan)
+		return -EIO;
+
+	/* Start the RX DMA job */
+	dbuf = sup->dmarx.use_buf_b ?
+		&sup->dmarx.dbuf_b : &sup->dmarx.dbuf_a;
+	desc = dmaengine_prep_slave_single(rxchan, dbuf->dma, dbuf->len,
+					   DMA_DEV_TO_MEM,
+					   DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+	/*
+	 * If the DMA engine is busy and cannot prepare a
+	 * channel, no big deal, the driver will fall back
+	 * to interrupt mode as a result of this error code.
+	 */
+	if (!desc) {
+		sup->dmarx.running = false;
+		dmaengine_terminate_all(rxchan);
+		return -EBUSY;
+	}
+
+	/* Some data to go along to the callback */
+	desc->callback = lrw_uart_dma_rx_callback;
+	desc->callback_param = sup;
+	dmarx->cookie = dmaengine_submit(desc);
+	dma_async_issue_pending(rxchan);
+
+	sup->dmacr |= UARTFCCR_RXDMAE;
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+	sup->dmarx.running = true;
+
+	sup->im &= ~UARTIMSC_RXIM;
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+
+	return 0;
+}
+
+/*
+ * This is called when either the DMA job is complete, or
+ * the FIFO timeout interrupt occurred. This must be called
+ * with the port spinlock sup->port.lock held.
+ */
+static void lrw_uart_dma_rx_chars(struct lrw_uart_port *sup,
+				  u32 pending, bool use_buf_b,
+				  bool readfifo)
+{
+	struct tty_port *port = &sup->port.state->port;
+	struct lrw_uart_dmabuf *dbuf = use_buf_b ?
+		&sup->dmarx.dbuf_b : &sup->dmarx.dbuf_a;
+	int dma_count = 0;
+	u32 fifotaken = 0; /* only used for vdbg() */
+
+	struct lrw_uart_dmarx_data *dmarx = &sup->dmarx;
+	int dmataken = 0;
+
+	if (sup->dmarx.poll_rate) {
+		/* The data can be taken by polling */
+		dmataken = dbuf->len - dmarx->last_residue;
+		/* Recalculate the pending size */
+		if (pending >= dmataken)
+			pending -= dmataken;
+	}
+
+	/* Pick the remain data from the DMA */
+	if (pending) {
+		/*
+		 * First take all chars in the DMA pipe, then look in the FIFO.
+		 * Note that tty_insert_flip_buf() tries to take as many chars
+		 * as it can.
+		 */
+		dma_count = tty_insert_flip_string(port, dbuf->buf + dmataken, pending);
+
+		sup->port.icount.rx += dma_count;
+		if (dma_count < pending)
+			dev_warn(sup->port.dev,
+				 "couldn't insert all characters (TTY is full?)\n");
+	}
+
+	/* Reset the last_residue for Rx DMA poll */
+	if (sup->dmarx.poll_rate)
+		dmarx->last_residue = dbuf->len;
+
+	/*
+	 * Only continue with trying to read the FIFO if all DMA chars have
+	 * been taken first.
+	 */
+	if (dma_count == pending && readfifo) {
+		/* Clear any error flags */
+		lrw_uart_write(UARTICR_OEIC | UARTICR_BEIC | UARTICR_PEIC |
+			      UARTICR_FEIC, sup, REG_ICR);
+
+		/*
+		 * If we read all the DMA'd characters, and we had an
+		 * incomplete buffer, that could be due to an rx error, or
+		 * maybe we just timed out. Read any pending chars and check
+		 * the error status.
+		 *
+		 * Error conditions will only occur in the FIFO, these will
+		 * trigger an immediate interrupt and stop the DMA job, so we
+		 * will always find the error in the FIFO, never in the DMA
+		 * buffer.
+		 */
+		fifotaken = lrw_uart_fifo_to_tty(sup);
+	}
+
+	dev_vdbg(sup->port.dev,
+		 "Took %d chars from DMA buffer and %d chars from the FIFO\n",
+		 dma_count, fifotaken);
+	tty_flip_buffer_push(port);
+}
+
+static void lrw_uart_dma_rx_irq(struct lrw_uart_port *sup)
+{
+	struct lrw_uart_dmarx_data *dmarx = &sup->dmarx;
+	struct dma_chan *rxchan = dmarx->chan;
+	struct lrw_uart_dmabuf *dbuf = dmarx->use_buf_b ?
+		&dmarx->dbuf_b : &dmarx->dbuf_a;
+	size_t pending;
+	struct dma_tx_state state;
+	enum dma_status dmastat;
+
+	/*
+	 * Pause the transfer so we can trust the current counter,
+	 * do this before we pause the LRW UART block, else we may
+	 * overflow the FIFO.
+	 */
+	if (dmaengine_pause(rxchan))
+		dev_err(sup->port.dev, "unable to pause DMA transfer\n");
+	dmastat = rxchan->device->device_tx_status(rxchan,
+						   dmarx->cookie, &state);
+	if (dmastat != DMA_PAUSED)
+		dev_err(sup->port.dev, "unable to pause DMA transfer\n");
+
+	/* Disable RX DMA - incoming data will wait in the FIFO */
+	sup->dmacr &= ~UARTFCCR_RXDMAE;
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+	sup->dmarx.running = false;
+
+	pending = dbuf->len - state.residue;
+	if (WARN_ONCE(pending > LRW_UART_DMA_BUFFER_SIZE,
+		      "pending %zu exceeds DMA buffer size %zu\n",
+		      pending, LRW_UART_DMA_BUFFER_SIZE))
+		pending = LRW_UART_DMA_BUFFER_SIZE;
+	/* Then we terminate the transfer - we now know our residue */
+	dmaengine_terminate_all(rxchan);
+
+	/*
+	 * This will take the chars we have so far and insert
+	 * into the framework.
+	 */
+	lrw_uart_dma_rx_chars(sup, pending, dmarx->use_buf_b, true);
+
+	/* Switch buffer & re-trigger DMA job */
+	dmarx->use_buf_b = !dmarx->use_buf_b;
+	if (lrw_uart_dma_rx_trigger_dma(sup)) {
+		dev_dbg(sup->port.dev,
+			"could not retrigger RX DMA job fall back to interrupt mode\n");
+		sup->im |= UARTIMSC_RXIM;
+		lrw_uart_write(sup->im, sup, REG_IMSC);
+	}
+}
+
+static void lrw_uart_dma_rx_callback(void *data)
+{
+	struct lrw_uart_port *sup = data;
+	struct lrw_uart_dmarx_data *dmarx = &sup->dmarx;
+	struct dma_chan *rxchan = dmarx->chan;
+	bool lastbuf = dmarx->use_buf_b;
+	struct lrw_uart_dmabuf *dbuf = dmarx->use_buf_b ?
+		&dmarx->dbuf_b : &dmarx->dbuf_a;
+	size_t pending;
+	struct dma_tx_state state;
+	int ret;
+
+	/*
+	 * This completion interrupt occurs typically when the
+	 * RX buffer is totally stuffed but no timeout has yet
+	 * occurred. When that happens, we just want the RX
+	 * routine to flush out the secondary DMA buffer while
+	 * we immediately trigger the next DMA job.
+	 */
+	uart_port_lock_irq(&sup->port);
+	/*
+	 * Rx data can be taken by the UART interrupts during
+	 * the DMA irq handler. So we check the residue here.
+	 */
+	rxchan->device->device_tx_status(rxchan, dmarx->cookie, &state);
+	pending = dbuf->len - state.residue;
+	if (WARN_ONCE(pending > LRW_UART_DMA_BUFFER_SIZE,
+		      "pending %zu exceeds DMA buffer size %zu\n",
+		      pending, LRW_UART_DMA_BUFFER_SIZE))
+		pending = LRW_UART_DMA_BUFFER_SIZE;
+	/* Then we terminate the transfer - we now know our residue */
+	dmaengine_terminate_all(rxchan);
+
+	sup->dmarx.running = false;
+	dmarx->use_buf_b = !lastbuf;
+	ret = lrw_uart_dma_rx_trigger_dma(sup);
+
+	lrw_uart_dma_rx_chars(sup, pending, lastbuf, false);
+	uart_unlock_and_check_sysrq(&sup->port);
+	/*
+	 * Do this check after we picked the DMA chars so we don't
+	 * get some IRQ immediately from RX.
+	 */
+	if (ret) {
+		dev_dbg(sup->port.dev,
+			"could not retrigger RX DMA job fall back to interrupt mode\n");
+		sup->im |= UARTIMSC_RXIM;
+		lrw_uart_write(sup->im, sup, REG_IMSC);
+	}
+}
+
+/*
+ * Stop accepting received characters, when we're shutting down or
+ * suspending this port.
+ * Locking: called with port lock held and IRQs disabled.
+ */
+static inline void lrw_uart_dma_rx_stop(struct lrw_uart_port *sup)
+{
+	if (!sup->using_rx_dma)
+		return;
+
+	/* FIXME.  Just disable the DMA enable */
+	sup->dmacr &= ~UARTFCCR_RXDMAE;
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+}
+
+/*
+ * Timer handler for Rx DMA polling.
+ * Every polling, It checks the residue in the dma buffer and transfer
+ * data to the tty. Also, last_residue is updated for the next polling.
+ */
+static void lrw_uart_dma_rx_poll(struct timer_list *t)
+{
+	struct lrw_uart_port *sup = timer_container_of(sup, t, dmarx.timer);
+	struct tty_port *port = &sup->port.state->port;
+	struct lrw_uart_dmarx_data *dmarx = &sup->dmarx;
+	struct dma_chan *rxchan = sup->dmarx.chan;
+	unsigned long flags;
+	unsigned int dmataken = 0;
+	unsigned int size = 0;
+	struct lrw_uart_dmabuf *dbuf;
+	int dma_count;
+	struct dma_tx_state state;
+
+	dbuf = dmarx->use_buf_b ? &sup->dmarx.dbuf_b : &sup->dmarx.dbuf_a;
+	rxchan->device->device_tx_status(rxchan, dmarx->cookie, &state);
+	if (likely(state.residue < dmarx->last_residue)) {
+		dmataken = dbuf->len - dmarx->last_residue;
+		size = dmarx->last_residue - state.residue;
+		dma_count = tty_insert_flip_string(port, dbuf->buf + dmataken,
+						   size);
+		if (dma_count == size)
+			dmarx->last_residue =  state.residue;
+		dmarx->last_jiffies = jiffies;
+	}
+	tty_flip_buffer_push(port);
+
+	/*
+	 * If no data is received in poll_timeout, the driver will fall back
+	 * to interrupt mode. We will retrigger DMA at the first interrupt.
+	 */
+	if (jiffies_to_msecs(jiffies - dmarx->last_jiffies)
+			> sup->dmarx.poll_timeout) {
+		uart_port_lock_irqsave(&sup->port, &flags);
+		lrw_uart_dma_rx_stop(sup);
+		sup->im |= UARTIMSC_RXIM;
+		lrw_uart_write(sup->im, sup, REG_IMSC);
+		uart_port_unlock_irqrestore(&sup->port, flags);
+
+		sup->dmarx.running = false;
+		dmaengine_terminate_all(rxchan);
+		timer_delete(&sup->dmarx.timer);
+	} else {
+		mod_timer(&sup->dmarx.timer,
+			  jiffies + msecs_to_jiffies(sup->dmarx.poll_rate));
+	}
+}
+
+static void lrw_uart_dma_startup(struct lrw_uart_port *sup)
+{
+	int ret;
+
+	if (!sup->dma_probed)
+		lrw_uart_dma_probe(sup);
+
+	if (!sup->dmatx.chan)
+		return;
+
+	sup->dmatx.buf = kmalloc(LRW_UART_DMA_BUFFER_SIZE, GFP_KERNEL | __GFP_DMA);
+	if (!sup->dmatx.buf) {
+		sup->port.fifosize = sup->fifosize;
+		return;
+	}
+
+	sup->dmatx.len = LRW_UART_DMA_BUFFER_SIZE;
+
+	/* The DMA buffer is now the FIFO the TTY subsystem can use */
+	sup->port.fifosize = LRW_UART_DMA_BUFFER_SIZE;
+	sup->using_tx_dma = true;
+
+	if (!sup->dmarx.chan)
+		goto skip_rx;
+
+	/* Allocate and map DMA RX buffers */
+	ret = lrw_uart_dmabuf_init(sup->dmarx.chan, &sup->dmarx.dbuf_a,
+				  DMA_FROM_DEVICE);
+	if (ret) {
+		dev_err(sup->port.dev, "failed to init DMA %s: %d\n",
+			"RX buffer A", ret);
+		goto skip_rx;
+	}
+
+	ret = lrw_uart_dmabuf_init(sup->dmarx.chan, &sup->dmarx.dbuf_b,
+				  DMA_FROM_DEVICE);
+	if (ret) {
+		dev_err(sup->port.dev, "failed to init DMA %s: %d\n",
+			"RX buffer B", ret);
+		lrw_uart_dmabuf_free(sup->dmarx.chan, &sup->dmarx.dbuf_a,
+				    DMA_FROM_DEVICE);
+		goto skip_rx;
+	}
+
+	sup->using_rx_dma = true;
+
+skip_rx:
+	/* Turn on DMA error (RX/TX will be enabled on demand) */
+	sup->dmacr |= UARTFCCR_DMAONERR;
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+
+	if (sup->using_rx_dma) {
+		if (lrw_uart_dma_rx_trigger_dma(sup))
+			dev_dbg(sup->port.dev,
+				"could not trigger initial RX DMA job, fall back to interrupt mode\n");
+		if (sup->dmarx.poll_rate) {
+			timer_setup(&sup->dmarx.timer, lrw_uart_dma_rx_poll, 0);
+			mod_timer(&sup->dmarx.timer,
+				  jiffies + msecs_to_jiffies(sup->dmarx.poll_rate));
+			sup->dmarx.last_residue = LRW_UART_DMA_BUFFER_SIZE;
+			sup->dmarx.last_jiffies = jiffies;
+		}
+	}
+}
+
+static void lrw_uart_dma_shutdown(struct lrw_uart_port *sup)
+{
+	if (!(sup->using_tx_dma || sup->using_rx_dma))
+		return;
+
+	/* Disable RX and TX DMA */
+	while (lrw_uart_read(sup, REG_FR) & sup->vendor->fr_busy)
+		cpu_relax();
+
+	uart_port_lock_irq(&sup->port);
+	sup->dmacr &= ~(UARTFCCR_DMAONERR | UARTFCCR_RXDMAE | UARTFCCR_TXDMAE);
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+	uart_port_unlock_irq(&sup->port);
+
+	if (sup->using_tx_dma) {
+		/* In theory, this should already be done by lrw_uart_dma_flush_buffer */
+		dmaengine_terminate_all(sup->dmatx.chan);
+		if (sup->dmatx.queued) {
+			dma_unmap_single(sup->dmatx.chan->device->dev,
+					 sup->dmatx.dma, sup->dmatx.len,
+					 DMA_TO_DEVICE);
+			sup->dmatx.queued = false;
+		}
+
+		kfree(sup->dmatx.buf);
+		sup->using_tx_dma = false;
+	}
+
+	if (sup->using_rx_dma) {
+		dmaengine_terminate_all(sup->dmarx.chan);
+		/* Clean up the RX DMA */
+		lrw_uart_dmabuf_free(sup->dmarx.chan, &sup->dmarx.dbuf_a, DMA_FROM_DEVICE);
+		lrw_uart_dmabuf_free(sup->dmarx.chan, &sup->dmarx.dbuf_b, DMA_FROM_DEVICE);
+		if (sup->dmarx.poll_rate)
+			timer_delete_sync(&sup->dmarx.timer);
+		sup->using_rx_dma = false;
+	}
+}
+
+static inline bool lrw_uart_dma_rx_available(struct lrw_uart_port *sup)
+{
+	return sup->using_rx_dma;
+}
+
+static inline bool lrw_uart_dma_rx_running(struct lrw_uart_port *sup)
+{
+	return sup->using_rx_dma && sup->dmarx.running;
+}
+
+#else
+/* Blank functions if the DMA engine is not available */
+static inline void lrw_uart_dma_remove(struct lrw_uart_port *sup)
+{
+}
+
+static inline void lrw_uart_dma_startup(struct lrw_uart_port *sup)
+{
+}
+
+static inline void lrw_uart_dma_shutdown(struct lrw_uart_port *sup)
+{
+}
+
+static inline bool lrw_uart_dma_tx_irq(struct lrw_uart_port *sup)
+{
+	return false;
+}
+
+static inline void lrw_uart_dma_tx_stop(struct lrw_uart_port *sup)
+{
+}
+
+static inline bool lrw_uart_dma_tx_start(struct lrw_uart_port *sup)
+{
+	return false;
+}
+
+static inline void lrw_uart_dma_rx_irq(struct lrw_uart_port *sup)
+{
+}
+
+static inline void lrw_uart_dma_rx_stop(struct lrw_uart_port *sup)
+{
+}
+
+static inline int lrw_uart_dma_rx_trigger_dma(struct lrw_uart_port *sup)
+{
+	return -EIO;
+}
+
+static inline bool lrw_uart_dma_rx_available(struct lrw_uart_port *sup)
+{
+	return false;
+}
+
+static inline bool lrw_uart_dma_rx_running(struct lrw_uart_port *sup)
+{
+	return false;
+}
+
+#define lrw_uart_dma_flush_buffer	NULL
+#endif
+
+static void lrw_uart_rs485_tx_stop(struct lrw_uart_port *sup)
+{
+	/*
+	 * To be on the safe side only time out after twice as many iterations
+	 * as fifo size.
+	 */
+	const int MAX_TX_DRAIN_ITERS = sup->port.fifosize * 2;
+	struct uart_port *port = &sup->port;
+	int i = 0;
+	u32 mcr, mcfg;
+
+	/* Wait until hardware tx queue is empty */
+	while (!lrw_uart_tx_empty(port)) {
+		if (i > MAX_TX_DRAIN_ITERS) {
+			dev_warn(port->dev,
+				 "timeout while draining hardware tx queue\n");
+			break;
+		}
+
+		udelay(sup->rs485_tx_drain_interval);
+		i++;
+	}
+
+	if (port->rs485.delay_rts_after_send)
+		mdelay(port->rs485.delay_rts_after_send);
+
+	mcr = lrw_uart_read(sup, REG_MCR);
+
+	if (port->rs485.flags & SER_RS485_RTS_AFTER_SEND)
+		mcr &= ~UARTMCR_RTS;
+	else
+		mcr |= UARTMCR_RTS;
+
+	lrw_uart_write(mcr, sup, REG_MCR);
+
+	/* Disable the transmitter and reenable the transceiver */
+	mcfg = lrw_uart_read(sup, REG_MCFG);
+	mcfg &= ~UARTMCFG_TXE;
+	mcfg |= UARTMCFG_RXE;
+	lrw_uart_write(mcfg, sup, REG_MCFG);
+
+	sup->rs485_tx_started = false;
+}
+
+static void lrw_uart_stop_tx(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	sup->im &= ~UARTIMSC_TXIM;
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+	lrw_uart_dma_tx_stop(sup);
+
+	if ((port->rs485.flags & SER_RS485_ENABLED) && sup->rs485_tx_started)
+		lrw_uart_rs485_tx_stop(sup);
+}
+
+static bool lrw_uart_tx_chars(struct lrw_uart_port *sup, bool from_irq);
+
+/* Start TX with programmed I/O only (no DMA) */
+static void lrw_uart_start_tx_pio(struct lrw_uart_port *sup)
+{
+	if (lrw_uart_tx_chars(sup, false)) {
+		sup->im |= UARTIMSC_TXIM;
+		lrw_uart_write(sup->im, sup, REG_IMSC);
+	}
+}
+
+static void lrw_uart_rs485_tx_start(struct lrw_uart_port *sup)
+{
+	struct uart_port *port = &sup->port;
+	u32 mcr, mcfg;
+
+	/* Enable transmitter */
+	mcfg = lrw_uart_read(sup, REG_MCFG);
+	mcfg |= UARTMCFG_TXE;
+
+	/* Disable receiver if half-duplex */
+	if (!(port->rs485.flags & SER_RS485_RX_DURING_TX))
+		mcfg &= ~UARTMCFG_RXE;
+
+	lrw_uart_write(mcfg, sup, REG_MCFG);
+
+	mcr = lrw_uart_read(sup, REG_MCR);
+	if (port->rs485.flags & SER_RS485_RTS_ON_SEND)
+		mcr &= ~UARTMCR_RTS;
+	else
+		mcr |= UARTMCR_RTS;
+
+	lrw_uart_write(mcr, sup, REG_MCR);
+
+	if (port->rs485.delay_rts_before_send)
+		mdelay(port->rs485.delay_rts_before_send);
+
+	sup->rs485_tx_started = true;
+}
+
+static void lrw_uart_start_tx(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	if ((sup->port.rs485.flags & SER_RS485_ENABLED) &&
+	    !sup->rs485_tx_started)
+		lrw_uart_rs485_tx_start(sup);
+
+	if (!lrw_uart_dma_tx_start(sup))
+		lrw_uart_start_tx_pio(sup);
+}
+
+static void lrw_uart_stop_rx(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	sup->im &= ~(UARTIMSC_RXIM | UARTIMSC_RTIM | UARTIMSC_FEIM |
+		     UARTIMSC_PEIM | UARTIMSC_BEIM | UARTIMSC_OEIM);
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+
+	lrw_uart_dma_rx_stop(sup);
+}
+
+static void lrw_uart_throttle_rx(struct uart_port *port)
+{
+	unsigned long flags;
+
+	uart_port_lock_irqsave(port, &flags);
+	lrw_uart_stop_rx(port);
+	uart_port_unlock_irqrestore(port, flags);
+}
+
+static void lrw_uart_enable_ms(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	sup->im |= UARTIMSC_RIMIM | UARTIMSC_CTSMIM | UARTIMSC_DCDMIM | UARTIMSC_DSRMIM;
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+}
+
+static void lrw_uart_rx_chars(struct lrw_uart_port *sup)
+__releases(&sup->port.lock)
+__acquires(&sup->port.lock)
+{
+	lrw_uart_fifo_to_tty(sup);
+
+	uart_port_unlock(&sup->port);
+	tty_flip_buffer_push(&sup->port.state->port);
+	/*
+	 * If we were temporarily out of DMA mode for a while,
+	 * attempt to switch back to DMA mode again.
+	 */
+	if (lrw_uart_dma_rx_available(sup)) {
+		if (lrw_uart_dma_rx_trigger_dma(sup)) {
+			dev_dbg(sup->port.dev,
+				"could not trigger RX DMA job fall back to interrupt mode again\n");
+			sup->im |= UARTIMSC_RXIM;
+			lrw_uart_write(sup->im, sup, REG_IMSC);
+		} else {
+#ifdef CONFIG_DMA_ENGINE
+			/* Start Rx DMA poll */
+			if (sup->dmarx.poll_rate) {
+				sup->dmarx.last_jiffies = jiffies;
+				sup->dmarx.last_residue	= LRW_UART_DMA_BUFFER_SIZE;
+				mod_timer(&sup->dmarx.timer,
+					  jiffies + msecs_to_jiffies(sup->dmarx.poll_rate));
+			}
+#endif
+		}
+	}
+	uart_port_lock(&sup->port);
+}
+
+static bool lrw_uart_tx_char(struct lrw_uart_port *sup, unsigned char c,
+			    bool from_irq)
+{
+	if (unlikely(!from_irq) &&
+	    lrw_uart_read(sup, REG_FR) & UARTFR_TXFF)
+		return false; /* unable to transmit character */
+
+	lrw_uart_write(c, sup, REG_DR);
+	sup->port.icount.tx++;
+
+	return true;
+}
+
+/* Returns true if tx interrupts have to be (kept) enabled  */
+static bool lrw_uart_tx_chars(struct lrw_uart_port *sup, bool from_irq)
+{
+	struct tty_port *tport = &sup->port.state->port;
+	int count = sup->fifosize >> 1;
+
+	if (sup->port.x_char) {
+		if (!lrw_uart_tx_char(sup, sup->port.x_char, from_irq))
+			return true;
+		sup->port.x_char = 0;
+		--count;
+	}
+	if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(&sup->port)) {
+		lrw_uart_stop_tx(&sup->port);
+		return false;
+	}
+
+	/* If we are using DMA mode, try to send some characters. */
+	if (lrw_uart_dma_tx_irq(sup))
+		return true;
+
+	while (1) {
+		unsigned char c;
+
+		if (likely(from_irq) && count-- == 0)
+			break;
+
+		if (!kfifo_peek(&tport->xmit_fifo, &c))
+			break;
+
+		if (!lrw_uart_tx_char(sup, c, from_irq))
+			break;
+
+		kfifo_skip(&tport->xmit_fifo);
+	}
+
+	if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
+		uart_write_wakeup(&sup->port);
+
+	if (kfifo_is_empty(&tport->xmit_fifo)) {
+		lrw_uart_stop_tx(&sup->port);
+		return false;
+	}
+	return true;
+}
+
+static void lrw_uart_modem_status(struct lrw_uart_port *sup)
+{
+	unsigned int status, delta;
+
+	status = lrw_uart_read(sup, REG_FR) & UARTFR_MODEM_ANY;
+
+	delta = status ^ sup->old_status;
+	sup->old_status = status;
+
+	if (!delta)
+		return;
+
+	if (delta & UARTFR_DCD)
+		uart_handle_dcd_change(&sup->port, status & UARTFR_DCD);
+
+	if (delta & sup->vendor->fr_dsr)
+		sup->port.icount.dsr++;
+
+	if (delta & sup->vendor->fr_cts)
+		uart_handle_cts_change(&sup->port,
+				       status & sup->vendor->fr_cts);
+
+	wake_up_interruptible(&sup->port.state->port.delta_msr_wait);
+}
+
+static void check_apply_cts_event_workaround(struct lrw_uart_port *sup)
+{
+	if (!sup->vendor->cts_event_workaround)
+		return;
+
+	/* workaround to make sure that all bits are unlocked.. */
+	lrw_uart_write(0x00, sup, REG_ICR);
+
+	/*
+	 * WA: introduce 26ns(1 uart clk) delay before W1C;
+	 * single apb access will incur 2 pclk(133.12Mhz) delay,
+	 * so add 2 dummy reads
+	 */
+	lrw_uart_read(sup, REG_ICR);
+	lrw_uart_read(sup, REG_ICR);
+}
+
+static irqreturn_t lrw_uart_int(int irq, void *dev_id)
+{
+	struct lrw_uart_port *sup = dev_id;
+	unsigned int status, pass_counter = ISR_PASS_LIMIT;
+	int handled = 0;
+
+	uart_port_lock(&sup->port);
+	status = lrw_uart_read(sup, REG_RIS) & sup->im;
+	if (status) {
+		do {
+			check_apply_cts_event_workaround(sup);
+
+			lrw_uart_write(status & ~(UARTICR_TXIC | UARTICR_RTIC | UARTICR_RXIC),
+				      sup, REG_ICR);
+
+			if (status & (UARTICR_RTIC | UARTICR_RXIC)) {
+				if (lrw_uart_dma_rx_running(sup))
+					lrw_uart_dma_rx_irq(sup);
+				else
+					lrw_uart_rx_chars(sup);
+			}
+			if (status & (UARTICR_DSRMIC | UARTICR_DCDMIC |
+				      UARTICR_CTSMIC | UARTICR_RIMIC))
+				lrw_uart_modem_status(sup);
+			if (status & UARTICR_TXIC)
+				lrw_uart_tx_chars(sup, true);
+
+			if (pass_counter-- == 0)
+				break;
+
+			status = lrw_uart_read(sup, REG_RIS) & sup->im;
+		} while (status != 0);
+		handled = 1;
+	}
+
+	uart_unlock_and_check_sysrq(&sup->port);
+
+	return IRQ_RETVAL(handled);
+}
+
+static unsigned int lrw_uart_tx_empty(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	/* Allow feature register bits to be inverted to work around errata */
+	unsigned int status = lrw_uart_read(sup, REG_FR) ^ sup->vendor->inv_fr;
+
+	return status & (sup->vendor->fr_busy | UARTFR_TXFF) ?
+							0 : TIOCSER_TEMT;
+}
+
+static void lrw_uart_maybe_set_bit(bool cond, unsigned int *ptr, unsigned int mask)
+{
+	if (cond)
+		*ptr |= mask;
+}
+
+static unsigned int lrw_uart_get_mctrl(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	unsigned int result = 0;
+	unsigned int status = lrw_uart_read(sup, REG_FR);
+
+	lrw_uart_maybe_set_bit(status & UARTFR_DCD, &result, TIOCM_CAR);
+	lrw_uart_maybe_set_bit(status & sup->vendor->fr_dsr, &result, TIOCM_DSR);
+	lrw_uart_maybe_set_bit(status & sup->vendor->fr_cts, &result, TIOCM_CTS);
+	lrw_uart_maybe_set_bit(status & sup->vendor->fr_ri, &result, TIOCM_RNG);
+
+	return result;
+}
+
+static void lrw_uart_assign_bit(bool cond, unsigned int *ptr, unsigned int mask)
+{
+	if (cond)
+		*ptr |= mask;
+	else
+		*ptr &= ~mask;
+}
+
+static void lrw_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	unsigned int mcr;
+	unsigned int mcfg;
+	unsigned int fccr;
+
+	mcr = lrw_uart_read(sup, REG_MCR);
+	mcfg = lrw_uart_read(sup, REG_MCFG);
+	fccr = lrw_uart_read(sup, REG_FCCR);
+
+	lrw_uart_assign_bit(mctrl & TIOCM_RTS, &mcr, UARTMCR_RTS);
+	lrw_uart_assign_bit(mctrl & TIOCM_DTR, &mcr, UARTMCR_DTR);
+	lrw_uart_assign_bit(mctrl & TIOCM_OUT1, &mcr, UARTMCR_OUT1);
+	lrw_uart_assign_bit(mctrl & TIOCM_OUT2, &mcr, UARTMCR_OUT2);
+	lrw_uart_assign_bit(mctrl & TIOCM_LOOP, &mcfg, UARTMCFG_LBE);
+
+	if (port->status & UPSTAT_AUTORTS) {
+		/* We need to disable auto-RTS if we want to turn RTS off */
+		lrw_uart_assign_bit(mctrl & TIOCM_RTS, &fccr, UARTFCCR_RTSEN);
+	}
+
+	lrw_uart_write(mcr, sup, REG_MCR);
+	lrw_uart_write(mcfg, sup, REG_MCFG);
+	lrw_uart_write(fccr, sup, REG_FCCR);
+}
+
+static void lrw_uart_break_ctl(struct uart_port *port, int break_state)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	unsigned long flags;
+	unsigned int mcfg;
+
+	uart_port_lock_irqsave(&sup->port, &flags);
+	mcfg = lrw_uart_read(sup, REG_MCFG);
+	if (break_state == -1)
+		mcfg |= UARTMCFG_BRK;
+	else
+		mcfg &= ~UARTMCFG_BRK;
+	lrw_uart_write(mcfg, sup, REG_MCFG);
+	uart_port_unlock_irqrestore(&sup->port, flags);
+}
+
+#ifdef CONFIG_CONSOLE_POLL
+
+static void lrw_uart_quiesce_irqs(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	lrw_uart_write(lrw_uart_read(sup, REG_MIS), sup, REG_ICR);
+	/*
+	 * There is no way to clear TXIM as this is "ready to transmit IRQ", so
+	 * we simply mask it. start_tx() will unmask it.
+	 *
+	 * Note we can race with start_tx(), and if the race happens, the
+	 * polling user might get another interrupt just after we clear it.
+	 * But it should be OK and can happen even w/o the race, e.g.
+	 * controller immediately got some new data and raised the IRQ.
+	 *
+	 * And whoever uses polling routines assumes that it manages the device
+	 * (including tx queue), so we're also fine with start_tx()'s caller
+	 * side.
+	 */
+	lrw_uart_write(lrw_uart_read(sup, REG_IMSC) & ~UARTIMSC_TXIM, sup,
+		    REG_IMSC);
+}
+
+static int lrw_uart_get_poll_char(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	unsigned int status;
+
+	/*
+	 * The caller might need IRQs lowered, e.g. if used with KDB NMI
+	 * debugger.
+	 */
+	lrw_uart_quiesce_irqs(port);
+
+	status = lrw_uart_read(sup, REG_FR);
+	if (status & UARTFR_RXFE)
+		return NO_POLL_CHAR;
+
+	return lrw_uart_read(sup, REG_DR);
+}
+
+static void lrw_uart_put_poll_char(struct uart_port *port, unsigned char ch)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	while (lrw_uart_read(sup, REG_FR) & UARTFR_TXFF)
+		cpu_relax();
+
+	lrw_uart_write(ch, sup, REG_DR);
+}
+
+#endif /* CONFIG_CONSOLE_POLL */
+
+static int lrw_uart_hwinit(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	int retval;
+	unsigned int clk;
+
+	/* Optionaly enable pins to be muxed in and configured */
+	pinctrl_pm_select_default_state(port->dev);
+
+	/*
+	 * Try to enable the clock producer.
+	 */
+	retval = clk_prepare_enable(sup->clk);
+	if (retval)
+		return retval;
+
+	if (has_acpi_companion(sup->port.dev)) {
+		device_property_read_u32(sup->port.dev, "clock-frequency", &clk);
+		sup->port.uartclk = clk;
+	} else {
+		sup->port.uartclk = clk_get_rate(sup->clk);
+	}
+
+	/* Clear pending error and receive interrupts */
+	lrw_uart_write(UARTICR_OEIC | UARTICR_BEIC | UARTICR_PEIC |
+		      UARTICR_FEIC | UARTICR_RTIC | UARTICR_RXIC,
+		      sup, REG_ICR);
+
+	/*
+	 * Save interrupts enable mask, and enable RX interrupts in case if
+	 * the interrupt is used for NMI entry.
+	 */
+	sup->im = lrw_uart_read(sup, REG_IMSC);
+	lrw_uart_write(UARTIMSC_RTIM | UARTIMSC_RXIM, sup, REG_IMSC);
+
+	if (dev_get_platdata(sup->port.dev)) {
+		struct lrw_uart_data *plat;
+
+		plat = dev_get_platdata(sup->port.dev);
+		if (plat->init)
+			plat->init();
+	}
+	return 0;
+}
+
+static int lrw_uart_allocate_irq(struct lrw_uart_port *sup)
+{
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+
+	return request_irq(sup->port.irq, lrw_uart_int, IRQF_SHARED, "lrw-uart", sup);
+}
+
+/*
+ * Enable interrupts, only timeouts when using DMA
+ * if initial RX DMA job failed, start in interrupt mode
+ * as well.
+ */
+static void lrw_uart_enable_interrupts(struct lrw_uart_port *sup)
+{
+	unsigned long flags;
+	unsigned int i;
+
+	uart_port_lock_irqsave(&sup->port, &flags);
+
+	/* Clear out any spuriously appearing RX interrupts */
+	lrw_uart_write(UARTICR_RTIC | UARTICR_RXIC, sup, REG_ICR);
+
+	/*
+	 * RXIS is asserted only when the RX FIFO transitions from below
+	 * to above the trigger threshold.  If the RX FIFO is already
+	 * full to the threshold this can't happen and RXIS will now be
+	 * stuck off.  Drain the RX FIFO explicitly to fix this:
+	 */
+	for (i = 0; i < sup->fifosize * 2; ++i) {
+		if (lrw_uart_read(sup, REG_FR) & UARTFR_RXFE)
+			break;
+
+		lrw_uart_read(sup, REG_DR);
+	}
+
+	sup->im = UARTIMSC_RTIM;
+	if (!lrw_uart_dma_rx_running(sup))
+		sup->im |= UARTIMSC_RXIM;
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+	uart_port_unlock_irqrestore(&sup->port, flags);
+}
+
+static void lrw_uart_unthrottle_rx(struct uart_port *port)
+{
+	struct lrw_uart_port *sup = container_of(port, struct lrw_uart_port, port);
+	unsigned long flags;
+
+	uart_port_lock_irqsave(&sup->port, &flags);
+
+	sup->im = UARTIMSC_RTIM;
+	if (!lrw_uart_dma_rx_running(sup))
+		sup->im |= UARTIMSC_RXIM;
+
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+
+	uart_port_unlock_irqrestore(&sup->port, flags);
+}
+
+static int lrw_uart_startup(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	unsigned int mcr;
+	unsigned int mcfg;
+	int retval;
+
+	retval = lrw_uart_hwinit(port);
+	if (retval)
+		goto clk_dis;
+
+	retval = lrw_uart_allocate_irq(sup);
+	if (retval)
+		goto clk_dis;
+
+	lrw_uart_write(sup->vendor->fcr, sup, REG_FCR);
+
+	uart_port_lock_irq(&sup->port);
+
+	mcr = lrw_uart_read(sup, REG_MCR);
+	mcr &= UARTMCR_RTS | UARTMCR_DTR;
+
+	lrw_uart_write(mcr, sup, REG_MCR);
+
+	mcfg = lrw_uart_read(sup, REG_MCFG);
+
+	mcfg |= UARTMCFG_UARTEN | UARTMCFG_RXE;
+
+	if (!(port->rs485.flags & SER_RS485_ENABLED))
+		mcfg |= UARTMCFG_TXE;
+
+	lrw_uart_write(mcfg, sup, REG_MCFG);
+
+	uart_port_unlock_irq(&sup->port);
+
+	/*
+	 * initialise the old status of the modem signals
+	 */
+	sup->old_status = lrw_uart_read(sup, REG_FR) & UARTFR_MODEM_ANY;
+
+	/* Startup DMA */
+	lrw_uart_dma_startup(sup);
+
+	lrw_uart_enable_interrupts(sup);
+
+	return 0;
+
+ clk_dis:
+	clk_disable_unprepare(sup->clk);
+	return retval;
+}
+
+static void lrw_uart_shutdown_channel(struct lrw_uart_port *sup,
+	unsigned int mcfg, unsigned int fcr)
+{
+	unsigned long val;
+
+	val = lrw_uart_read(sup, mcfg);
+	val &= ~(UARTMCFG_BRK);
+	lrw_uart_write(val, sup, mcfg);
+
+	val = lrw_uart_read(sup, fcr);
+	val &= ~(UARTFCR_FEN);
+	lrw_uart_write(val, sup, fcr);
+}
+
+/*
+ * disable the port. It should not disable RTS and DTR.
+ * Also RTS and DTR state should be preserved to restore
+ * it during startup().
+ */
+static void lrw_uart_disable_uart(struct lrw_uart_port *sup)
+{
+	unsigned int mcr;
+	unsigned int mcfg;
+
+	sup->port.status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS);
+	uart_port_lock_irq(&sup->port);
+	mcr = lrw_uart_read(sup, REG_MCR);
+	mcr &= UARTMCR_RTS | UARTMCR_DTR;
+	lrw_uart_write(mcr, sup, REG_MCR);
+
+	mcfg = lrw_uart_read(sup, REG_MCFG);
+	mcfg |= UARTMCFG_UARTEN | UARTMCFG_TXE;
+	lrw_uart_write(mcfg, sup, REG_MCFG);
+	uart_port_unlock_irq(&sup->port);
+
+	/*
+	 * disable break condition and fifos
+	 */
+	lrw_uart_shutdown_channel(sup, REG_MCFG, REG_FCR);
+}
+
+static void lrw_uart_disable_interrupts(struct lrw_uart_port *sup)
+{
+	uart_port_lock_irq(&sup->port);
+
+	/* mask all interrupts and clear all pending ones */
+	sup->im = 0;
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+	lrw_uart_write(0xffff, sup, REG_ICR);
+
+	uart_port_unlock_irq(&sup->port);
+}
+
+static void lrw_uart_shutdown(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+		container_of(port, struct lrw_uart_port, port);
+
+	lrw_uart_disable_interrupts(sup);
+
+	lrw_uart_dma_shutdown(sup);
+
+	if ((port->rs485.flags & SER_RS485_ENABLED) && sup->rs485_tx_started)
+		lrw_uart_rs485_tx_stop(sup);
+
+	free_irq(sup->port.irq, sup);
+
+	lrw_uart_disable_uart(sup);
+
+	/*
+	 * Shut down the clock producer
+	 */
+	clk_disable_unprepare(sup->clk);
+	/* Optionally let pins go into sleep states */
+	pinctrl_pm_select_sleep_state(port->dev);
+
+	if (dev_get_platdata(sup->port.dev)) {
+		struct lrw_uart_data *plat;
+
+		plat = dev_get_platdata(sup->port.dev);
+		if (plat->exit)
+			plat->exit();
+	}
+
+	if (sup->port.ops->flush_buffer)
+		sup->port.ops->flush_buffer(port);
+}
+
+static void
+lrw_uart_setup_status_masks(struct uart_port *port, struct ktermios *termios)
+{
+	port->read_status_mask = UARTDR_OE | 255;
+	if (termios->c_iflag & INPCK)
+		port->read_status_mask |= UARTDR_FE | UARTDR_PE;
+	if (termios->c_iflag & (IGNBRK | BRKINT | PARMRK))
+		port->read_status_mask |= UARTDR_BE;
+
+	/*
+	 * Characters to ignore
+	 */
+	port->ignore_status_mask = 0;
+	if (termios->c_iflag & IGNPAR)
+		port->ignore_status_mask |= UARTDR_FE | UARTDR_PE;
+	if (termios->c_iflag & IGNBRK) {
+		port->ignore_status_mask |= UARTDR_BE;
+		/*
+		 * If we're ignoring parity and break indicators,
+		 * ignore overruns too (for real raw support).
+		 */
+		if (termios->c_iflag & IGNPAR)
+			port->ignore_status_mask |= UARTDR_OE;
+	}
+
+	/*
+	 * Ignore all characters if CREAD is not set.
+	 */
+	if ((termios->c_cflag & CREAD) == 0)
+		port->ignore_status_mask |= UART_DUMMY_DR_RX;
+}
+
+static void
+lrw_uart_set_termios(struct uart_port *port, struct ktermios *termios,
+		     const struct ktermios *old)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	unsigned int frcr;
+	unsigned int mcr, fccr;
+	unsigned int mcfg;
+	unsigned long flags;
+	unsigned int baud, quot, clkdiv;
+	unsigned int bits;
+	unsigned int clk;
+
+	if (sup->vendor->oversampling)
+		clkdiv = 8;
+	else
+		clkdiv = 16;
+
+	if (has_acpi_companion(sup->port.dev)) {
+		device_property_read_u32(sup->port.dev, "clock-frequency", &clk);
+		sup->port.uartclk = clk;
+	}
+
+	/*
+	 * Ask the core to calculate the divisor for us.
+	 */
+	baud = uart_get_baud_rate(port, termios, old, 0,
+				  port->uartclk / clkdiv);
+
+#ifdef CONFIG_DMA_ENGINE
+	/*
+	 * Adjust RX DMA polling rate with baud rate if not specified.
+	 */
+	if (sup->dmarx.auto_poll_rate)
+		sup->dmarx.poll_rate = DIV_ROUND_UP(10000000, baud);
+#endif
+
+	if (baud > port->uartclk / 16)
+		quot = DIV_ROUND_CLOSEST(port->uartclk * 8, baud);
+	else
+		quot = DIV_ROUND_CLOSEST(port->uartclk * 4, baud);
+
+	switch (termios->c_cflag & CSIZE) {
+	case CS5:
+		frcr = UARTFRCR_WLEN_5;
+		break;
+	case CS6:
+		frcr = UARTFRCR_WLEN_6;
+		break;
+	case CS7:
+		frcr = UARTFRCR_WLEN_7;
+		break;
+	default: // CS8
+		frcr = UARTFRCR_WLEN_8;
+		break;
+	}
+
+	if (termios->c_cflag & CSTOPB)
+		frcr |= UARTFRCR_STP2;
+	if (termios->c_cflag & PARENB) {
+		frcr |= UARTFRCR_PEN;
+		if (!(termios->c_cflag & PARODD))
+			frcr |= UARTFRCR_EOP;
+		if (termios->c_cflag & CMSPAR)
+			frcr |= UARTFRCR_SPS;
+	}
+
+
+	bits = tty_get_frame_size(termios->c_cflag);
+
+	uart_port_lock_irqsave(port, &flags);
+
+	/*
+	 * Update the per-port timeout.
+	 */
+	uart_update_timeout(port, termios->c_cflag, baud);
+
+	/*
+	 * Calculate the approximated time it takes to transmit one character
+	 * with the given baud rate. We use this as the poll interval when we
+	 * wait for the tx queue to empty.
+	 */
+	sup->rs485_tx_drain_interval = DIV_ROUND_UP(bits * 1000 * 1000, baud);
+
+	lrw_uart_setup_status_masks(port, termios);
+
+	if (UART_ENABLE_MS(port, termios->c_cflag))
+		lrw_uart_enable_ms(port);
+
+	if (port->rs485.flags & SER_RS485_ENABLED)
+		termios->c_cflag &= ~CRTSCTS;
+
+	mcr = lrw_uart_read(sup, REG_MCR);
+	mcfg = lrw_uart_read(sup, REG_MCFG);
+	fccr = lrw_uart_read(sup, REG_FCCR);
+
+	if (termios->c_cflag & CRTSCTS) {
+		if (mcr & UARTMCR_RTS)
+			fccr |= UARTFCCR_RTSEN;
+
+		fccr |= UARTFCCR_CTSEN;
+		port->status |= UPSTAT_AUTOCTS | UPSTAT_AUTORTS;
+	} else {
+		fccr &= ~(UARTFCCR_CTSEN | UARTFCCR_RTSEN);
+		port->status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS);
+	}
+
+	/* Set baud rate */
+	lrw_uart_write(quot & 0x3f, sup, REG_FD);
+	lrw_uart_write(quot >> 6, sup, REG_IND);
+
+	/*
+	 * ----------v----------v----------v----------v-----
+	 * NOTE: REG_FRCR MUST BE WRITTEN AFTER REG_FD & REG_IND.
+	 * ----------^----------^----------^----------^-----
+	 */
+	lrw_uart_write(frcr, sup, REG_FRCR);
+
+	lrw_uart_write(fccr, sup, REG_FCCR);
+
+	/*
+	 * Receive was disabled by lrw_uart_disable_uart during shutdown.
+	 * Need to reenable receive if you need to use a tty_driver
+	 * returns from tty_find_polling_driver() after a port shutdown.
+	 */
+	mcfg |= UARTMCFG_RXE;
+	lrw_uart_write(mcfg, sup, REG_MCFG);
+
+	uart_port_unlock_irqrestore(port, flags);
+}
+
+static const char *lrw_uart_type(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	return sup->port.type == PORT_LRW ? sup->type : NULL;
+}
+
+/*
+ * Configure/autoconfigure the port.
+ */
+static void lrw_uart_config_port(struct uart_port *port, int flags)
+{
+	if (flags & UART_CONFIG_TYPE)
+		port->type = PORT_LRW;
+}
+
+/*
+ * verify the new serial_struct (for TIOCSSERIAL).
+ */
+static int lrw_uart_verify_port(struct uart_port *port, struct serial_struct *ser)
+{
+	int ret = 0;
+
+	if (ser->type != PORT_UNKNOWN && ser->type != PORT_LRW)
+		ret = -EINVAL;
+	if (ser->irq < 0 || ser->irq >= irq_get_nr_irqs())
+		ret = -EINVAL;
+	if (ser->baud_base < 9600)
+		ret = -EINVAL;
+	if (port->mapbase != (unsigned long)ser->iomem_base)
+		ret = -EINVAL;
+	return ret;
+}
+
+static int lrw_uart_rs485_config(struct uart_port *port, struct ktermios *termios,
+			      struct serial_rs485 *rs485)
+{
+	struct lrw_uart_port *sup =
+		container_of(port, struct lrw_uart_port, port);
+
+	if (port->rs485.flags & SER_RS485_ENABLED)
+		lrw_uart_rs485_tx_stop(sup);
+
+	/* Make sure auto RTS is disabled */
+	if (rs485->flags & SER_RS485_ENABLED) {
+		u32 fccr = lrw_uart_read(sup, REG_FCCR);
+
+		fccr &= ~UARTFCCR_RTSEN;
+		lrw_uart_write(fccr, sup, REG_FCCR);
+		port->status &= ~UPSTAT_AUTORTS;
+	}
+
+	return 0;
+}
+
+static const struct uart_ops lrw_uart_pops = {
+	.tx_empty	= lrw_uart_tx_empty,
+	.set_mctrl	= lrw_uart_set_mctrl,
+	.get_mctrl	= lrw_uart_get_mctrl,
+	.stop_tx	= lrw_uart_stop_tx,
+	.start_tx	= lrw_uart_start_tx,
+	.stop_rx	= lrw_uart_stop_rx,
+	.throttle	= lrw_uart_throttle_rx,
+	.unthrottle	= lrw_uart_unthrottle_rx,
+	.enable_ms	= lrw_uart_enable_ms,
+	.break_ctl	= lrw_uart_break_ctl,
+	.startup	= lrw_uart_startup,
+	.shutdown	= lrw_uart_shutdown,
+	.flush_buffer	= lrw_uart_dma_flush_buffer,
+	.set_termios	= lrw_uart_set_termios,
+	.type		= lrw_uart_type,
+	.config_port	= lrw_uart_config_port,
+	.verify_port	= lrw_uart_verify_port,
+#ifdef CONFIG_CONSOLE_POLL
+	.poll_init     = lrw_uart_hwinit,
+	.poll_get_char = lrw_uart_get_poll_char,
+	.poll_put_char = lrw_uart_put_poll_char,
+#endif
+};
+
+static struct lrw_uart_port *lrw_uart_console_ports[UART_NR];
+
+#ifdef CONFIG_SERIAL_LRW_UART_CONSOLE
+
+static void lrw_uart_console_putchar(struct uart_port *port, unsigned char ch)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	while (lrw_uart_read(sup, REG_FR) & UARTFR_TXFF)
+		cpu_relax();
+	lrw_uart_write(ch, sup, REG_DR);
+}
+
+static void
+lrw_uart_console_write(struct console *co, const char *s, unsigned int count)
+{
+	struct lrw_uart_port *sup = lrw_uart_console_ports[co->index];
+	unsigned int old_fccr = 0, new_fccr;
+	unsigned int old_mcfg = 0, new_mcfg;
+	unsigned long flags;
+	int locked = 1;
+
+	clk_enable(sup->clk);
+
+	if (oops_in_progress)
+		locked = uart_port_trylock_irqsave(&sup->port, &flags);
+	else
+		uart_port_lock_irqsave(&sup->port, &flags);
+
+	/*
+	 *	First save the FCCR then disable the interrupts
+	 */
+	if (!sup->vendor->always_enabled) {
+		old_fccr = lrw_uart_read(sup, REG_FCCR);
+		new_fccr = old_fccr & ~UARTFCCR_CTSEN;
+		lrw_uart_write(new_fccr, sup, REG_FCCR);
+
+		old_mcfg = lrw_uart_read(sup, REG_MCFG);
+		new_mcfg |= UARTMCFG_UARTEN | UARTMCFG_TXE;
+		lrw_uart_write(new_mcfg, sup, REG_MCFG);
+	}
+
+	uart_console_write(&sup->port, s, count, lrw_uart_console_putchar);
+
+	/*
+	 *	Finally, wait for transmitter to become empty and restore the
+	 *	TCR. Allow feature register bits to be inverted to work around
+	 *	errata.
+	 */
+	while ((lrw_uart_read(sup, REG_FR) ^ sup->vendor->inv_fr)
+						& sup->vendor->fr_busy)
+		cpu_relax();
+	if (!sup->vendor->always_enabled) {
+		lrw_uart_write(old_fccr, sup, REG_FCCR);
+		lrw_uart_write(old_mcfg, sup, REG_MCFG);
+	}
+
+	if (locked)
+		uart_port_unlock_irqrestore(&sup->port, flags);
+
+	clk_disable(sup->clk);
+}
+
+static void lrw_uart_console_get_options(struct lrw_uart_port *sup, int *baud,
+					 int *parity, int *bits)
+{
+	unsigned int frcr, ind, fd;
+
+	if (!(lrw_uart_read(sup, REG_MCFG) & UARTMCFG_UARTEN))
+		return;
+
+	frcr = lrw_uart_read(sup, REG_FRCR);
+
+	*parity = 'n';
+	if (frcr & UARTFRCR_PEN) {
+		if (frcr & UARTFRCR_EOP)
+			*parity = 'e';
+		else
+			*parity = 'o';
+	}
+
+	if ((frcr & 0x3) == UARTFRCR_WLEN_7)
+		*bits = 7;
+	else
+		*bits = 8;
+
+	ind = lrw_uart_read(sup, REG_IND);
+	fd = lrw_uart_read(sup, REG_FD);
+
+	*baud = sup->port.uartclk * 4 / (64 * ind + fd);
+}
+
+static int lrw_uart_console_setup(struct console *co, char *options)
+{
+	struct lrw_uart_port *sup;
+	int baud = 38400;
+	int bits = 8;
+	int parity = 'n';
+	int flow = 'n';
+	int ret;
+	unsigned int clk;
+
+	/*
+	 * Check whether an invalid uart number has been specified, and
+	 * if so, search for the first available port that does have
+	 * console support.
+	 */
+	if (co->index >= UART_NR)
+		co->index = 0;
+	sup = lrw_uart_console_ports[co->index];
+	if (!sup)
+		return -ENODEV;
+
+	/* Allow pins to be muxed in and configured */
+	pinctrl_pm_select_default_state(sup->port.dev);
+
+	ret = clk_prepare(sup->clk);
+	if (ret)
+		return ret;
+
+	if (dev_get_platdata(sup->port.dev)) {
+		struct lrw_uart_data *plat;
+
+		plat = dev_get_platdata(sup->port.dev);
+		if (plat->init)
+			plat->init();
+	}
+
+	if (has_acpi_companion(sup->port.dev)) {
+		device_property_read_u32(sup->port.dev, "clock-frequency", &clk);
+		sup->port.uartclk = clk;
+	} else {
+		sup->port.uartclk = clk_get_rate(sup->clk);
+	}
+
+	if (sup->vendor->fixed_options) {
+		baud = sup->fixed_baud;
+	} else {
+		if (options)
+			uart_parse_options(options,
+					   &baud, &parity, &bits, &flow);
+		else
+			lrw_uart_console_get_options(sup, &baud, &parity, &bits);
+	}
+
+	return uart_set_options(&sup->port, co, baud, parity, bits, flow);
+}
+
+/**
+ *	lrw_uart_console_match - non-standard console matching
+ *	@co:	  registering console
+ *	@name:	  name from console command line
+ *	@idx:	  index from console command line
+ *	@options: ptr to option string from console command line
+ *
+ *	Only attempts to match console command lines of the form:
+ *	    console=lrw_uart,mmio|mmio32,<addr>[,<options>]
+ *	    console=lrw_uart,0x<addr>[,<options>]
+ *	This form is used to register an initial earlycon boot console and
+ *	replace it with the lrw_uart_console at lrw_uart driver init.
+ *
+ *	Performs console setup for a match (as required by interface)
+ *	If no <options> are specified, then assume the h/w is already setup.
+ *
+ *	Returns 0 if console matches; otherwise non-zero to use default matching
+ */
+static int lrw_uart_console_match(struct console *co, char *name, int idx,
+				  char *options)
+{
+	enum uart_iotype iotype;
+	resource_size_t addr;
+	int i;
+
+	if (strcmp(name, "lrw_uart") != 0)
+		return -ENODEV;
+
+	if (uart_parse_earlycon(options, &iotype, &addr, &options))
+		return -ENODEV;
+
+	if (iotype != UPIO_MEM && iotype != UPIO_MEM32)
+		return -ENODEV;
+
+	/* try to match the port specified on the command line */
+	for (i = 0; i < ARRAY_SIZE(lrw_uart_console_ports); i++) {
+		struct uart_port *port;
+
+		if (!lrw_uart_console_ports[i])
+			continue;
+
+		port = &lrw_uart_console_ports[i]->port;
+
+		if (port->mapbase != addr)
+			continue;
+
+		co->index = i;
+		uart_port_set_cons(port, co);
+		return lrw_uart_console_setup(co, options);
+	}
+
+	return -ENODEV;
+}
+
+static struct uart_driver lrw_uart_driver;
+
+static struct console lrw_uart_console = {
+	.name		= LRW_UART_TTY_PREFIX,
+	.write		= lrw_uart_console_write,
+	.device		= uart_console_device,
+	.setup		= lrw_uart_console_setup,
+	.match		= lrw_uart_console_match,
+	.flags		= CON_PRINTBUFFER | CON_ANYTIME,
+	.index		= -1,
+	.data		= &lrw_uart_driver,
+};
+
+#define LRW_UART_CONSOLE	(&lrw_uart_console)
+
+static void lrw_uart_putc(struct uart_port *port, unsigned char c)
+{
+	while (readl(port->membase + UARTFR) & UARTFR_TXFF)
+		cpu_relax();
+	if (port->iotype == UPIO_MEM32)
+		writel(c, port->membase + UARTDR);
+	else
+		writeb(c, port->membase + UARTDR);
+	while (readl(port->membase + UARTFR) & UARTFR_BUSY)
+		cpu_relax();
+}
+
+static void lrw_uart_early_write(struct console *con, const char *s, unsigned int n)
+{
+	struct earlycon_device *dev = con->data;
+
+	uart_console_write(&dev->port, s, n, lrw_uart_putc);
+}
+
+#ifdef CONFIG_CONSOLE_POLL
+static int lrw_uart_getc(struct uart_port *port)
+{
+	if (readl(port->membase + UARTFR) & UARTFR_RXFE)
+		return NO_POLL_CHAR;
+
+	if (port->iotype == UPIO_MEM32)
+		return readl(port->membase + UARTDR);
+	else
+		return readb(port->membase + UARTDR);
+}
+
+static int lrw_uart_early_read(struct console *con, char *s, unsigned int n)
+{
+	struct earlycon_device *dev = con->data;
+	int ch, num_read = 0;
+
+	while (num_read < n) {
+		ch = lrw_uart_getc(&dev->port);
+		if (ch == NO_POLL_CHAR)
+			break;
+
+		s[num_read++] = ch;
+	}
+
+	return num_read;
+}
+#else
+#define lrw_uart_early_read NULL
+#endif
+
+/*
+ * On non-ACPI systems, earlycon is enabled by specifying
+ * "earlycon=lrw_uart,<address>" on the kernel command line.
+ *
+ * On ACPI ARM64 systems, an "early" console is enabled via the SPCR table,
+ * by specifying only "earlycon" on the command line.  Because it requires
+ * SPCR, the console starts after ACPI is parsed, which is later than a
+ * traditional early console.
+ *
+ * To get the traditional early console that starts before ACPI is parsed,
+ * specify the full "earlycon=lrw_uart,<address>" option.
+ */
+static int __init lrw_uart_early_console_setup(struct earlycon_device *device,
+					      const char *opt)
+{
+	if (!device->port.membase)
+		return -ENODEV;
+
+	device->con->write = lrw_uart_early_write;
+	device->con->read = lrw_uart_early_read;
+
+	return 0;
+}
+
+OF_EARLYCON_DECLARE(lrw_uart, "lrw-uart", lrw_uart_early_console_setup);
+
+#else
+#define LRW_UART_CONSOLE	NULL
+#endif
+
+static struct uart_driver lrw_uart_driver = {
+	.owner			= THIS_MODULE,
+	.driver_name		= LRW_UART_NAME,
+	.dev_name		= LRW_UART_TTY_PREFIX,
+	.nr			= UART_NR,
+	.cons			= LRW_UART_CONSOLE,
+};
+
+static int lrw_uart_probe_dt_alias(int index, struct device *dev)
+{
+	struct device_node *np;
+	static bool seen_dev_with_alias;
+	static bool seen_dev_without_alias;
+	int ret = index;
+
+	if (!IS_ENABLED(CONFIG_OF))
+		return ret;
+
+	np = dev->of_node;
+	if (!np)
+		return ret;
+
+	ret = of_alias_get_id(np, "serial");
+	if (ret < 0) {
+		seen_dev_without_alias = true;
+		ret = index;
+	} else {
+		seen_dev_with_alias = true;
+		if (ret >= ARRAY_SIZE(lrw_uart_console_ports) || lrw_uart_console_ports[ret]) {
+			dev_warn(dev, "requested serial port %d  not available.\n", ret);
+			ret = index;
+		}
+	}
+
+	if (seen_dev_with_alias && seen_dev_without_alias)
+		dev_warn(dev, "aliased and non-aliased serial devices found in device tree. Serial port enumeration may be unpredictable.\n");
+
+	return ret;
+}
+
+/* unregisters the driver also if no more ports are left */
+static void lrw_uart_unregister_port(struct lrw_uart_port *sup)
+{
+	int i;
+	bool busy = false;
+
+	for (i = 0; i < ARRAY_SIZE(lrw_uart_console_ports); i++) {
+		if (lrw_uart_console_ports[i] == sup)
+			lrw_uart_console_ports[i] = NULL;
+		else if (lrw_uart_console_ports[i])
+			busy = true;
+	}
+	lrw_uart_dma_remove(sup);
+	if (!busy)
+		uart_unregister_driver(&lrw_uart_driver);
+}
+
+static int lrw_uart_find_free_port(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(lrw_uart_console_ports); i++)
+		if (!lrw_uart_console_ports[i])
+			return i;
+
+	return -EBUSY;
+}
+
+static int lrw_uart_setup_port(struct device *dev, struct lrw_uart_port *sup,
+			      struct resource *mmiobase, int index)
+{
+	void __iomem *base;
+	int ret;
+
+	base = devm_ioremap_resource(dev, mmiobase);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	index = lrw_uart_probe_dt_alias(index, dev);
+
+	sup->port.dev = dev;
+	sup->port.mapbase = mmiobase->start;
+	sup->port.membase = base;
+	sup->port.fifosize = sup->fifosize;
+	sup->port.has_sysrq = IS_ENABLED(CONFIG_SERIAL_LRW_UART_CONSOLE);
+	sup->port.flags = UPF_BOOT_AUTOCONF;
+	sup->port.line = index;
+
+	ret = uart_get_rs485_mode(&sup->port);
+	if (ret)
+		return ret;
+
+	lrw_uart_console_ports[index] = sup;
+
+	return 0;
+}
+
+static int lrw_uart_register_port(struct lrw_uart_port *sup)
+{
+	int ret, i;
+
+	/* Ensure interrupts from this UART are masked and cleared */
+	lrw_uart_write(0, sup, REG_IMSC);
+	lrw_uart_write(0xffff, sup, REG_ICR);
+
+	if (!lrw_uart_driver.state) {
+		ret = uart_register_driver(&lrw_uart_driver);
+		if (ret < 0) {
+			dev_err(sup->port.dev,
+				"Failed to register LRW UART driver\n");
+			for (i = 0; i < ARRAY_SIZE(lrw_uart_console_ports); i++)
+				if (lrw_uart_console_ports[i] == sup)
+					lrw_uart_console_ports[i] = NULL;
+			return ret;
+		}
+	}
+
+	ret = uart_add_one_port(&lrw_uart_driver, &sup->port);
+	if (ret)
+		lrw_uart_unregister_port(sup);
+
+	return ret;
+}
+
+static const struct serial_rs485 lrw_uart_rs485_supported = {
+	.flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | SER_RS485_RTS_AFTER_SEND |
+		 SER_RS485_RX_DURING_TX,
+	.delay_rts_before_send = 1,
+	.delay_rts_after_send = 1,
+};
+
+static int lrw_uart_probe(struct platform_device *pdev)
+{
+	struct lrw_uart_port *sup;
+	struct resource *r;
+	int portnr, ret;
+	unsigned int clk;
+	unsigned int baudrate;
+
+	/*
+	 * Check the mandatory baud rate parameter in the DT node early
+	 * so that we can easily exit with the error.
+	 */
+	if (pdev->dev.of_node) {
+		struct device_node *np = pdev->dev.of_node;
+
+		ret = of_property_read_u32(np, "current-speed", &baudrate);
+		if (ret)
+			return ret;
+	} else if (has_acpi_companion(&pdev->dev)) {
+		ret = device_property_read_u32(&pdev->dev, "current-speed", &baudrate);
+		if (ret)
+			return ret;
+	} else {
+		baudrate = 115200;
+	}
+
+	portnr = lrw_uart_find_free_port();
+	if (portnr < 0)
+		return portnr;
+
+	sup = devm_kzalloc(&pdev->dev, sizeof(struct lrw_uart_port),
+			   GFP_KERNEL);
+	if (!sup)
+		return -ENOMEM;
+
+	if (has_acpi_companion(&pdev->dev)) {
+		device_property_read_u32(&pdev->dev, "clock-frequency", &clk);
+		sup->port.uartclk = clk;
+	} else {
+		sup->clk = devm_clk_get(&pdev->dev, NULL);
+		if (IS_ERR(sup->clk))
+			return PTR_ERR(sup->clk);
+	}
+
+	ret = platform_get_irq(pdev, 0);
+	if (ret < 0)
+		return ret;
+	sup->port.irq	= ret;
+
+	sup->vendor = &vendor_lrw;
+
+	sup->reg_offset = sup->vendor->reg_offset;
+	sup->fifosize	= LRW_UART_TX_FIFO_DEPTH;
+	sup->port.iotype = sup->vendor->access_32b ? UPIO_MEM32 : UPIO_MEM;
+	sup->port.ops = &lrw_uart_pops;
+	sup->port.rs485_config = lrw_uart_rs485_config;
+	sup->port.rs485_supported = lrw_uart_rs485_supported;
+	sup->fixed_baud = baudrate;
+
+	snprintf(sup->type, sizeof(sup->type), "LRW UART");
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	ret = lrw_uart_setup_port(&pdev->dev, sup, r, portnr);
+	if (ret)
+		return ret;
+
+	platform_set_drvdata(pdev, sup);
+
+	return lrw_uart_register_port(sup);
+}
+
+static void lrw_uart_remove(struct platform_device *dev)
+{
+	struct lrw_uart_port *sup = platform_get_drvdata(dev);
+
+	uart_remove_one_port(&lrw_uart_driver, &sup->port);
+	lrw_uart_unregister_port(sup);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int lrw_uart_suspend(struct device *dev)
+{
+	struct lrw_uart_port *sup = dev_get_drvdata(dev);
+
+	if (!sup)
+		return -EINVAL;
+
+	return uart_suspend_port(&lrw_uart_driver, &sup->port);
+}
+
+static int lrw_uart_resume(struct device *dev)
+{
+	struct lrw_uart_port *sup = dev_get_drvdata(dev);
+
+	if (!sup)
+		return -EINVAL;
+
+	return uart_resume_port(&lrw_uart_driver, &sup->port);
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(lrw_uart_pm_ops, lrw_uart_suspend, lrw_uart_resume);
+
+static const struct of_device_id lrw_uart_of_match[] = {
+	{ .compatible = "lrw,lrw-uart" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, lrw_uart_of_match);
+
+static const struct acpi_device_id __maybe_unused lrw_uart_acpi_match[] = {
+	{ "LRWX0000", 0 },
+	{},
+};
+MODULE_DEVICE_TABLE(acpi, lrw_uart_acpi_match);
+
+static struct platform_driver lrw_uart_platform_driver = {
+	.probe		= lrw_uart_probe,
+	.remove		= lrw_uart_remove,
+	.driver		= {
+		.name	= LRW_UART_NAME,
+		.pm = pm_sleep_ptr(&lrw_uart_pm_ops),
+		.of_match_table = of_match_ptr(lrw_uart_of_match),
+		.acpi_match_table = ACPI_PTR(lrw_uart_acpi_match),
+		.suppress_bind_attrs = IS_BUILTIN(CONFIG_SERIAL_LRW_UART),
+	},
+};
+
+static int __init lrw_uart_init(void)
+{
+	pr_info("Serial: LRW UART driver\n");
+
+	int ret;
+
+	ret = uart_register_driver(&lrw_uart_driver);
+	if (ret < 0) {
+		pr_err("Could not register %s driver\n",
+			lrw_uart_driver.driver_name);
+		return ret;
+	}
+
+	ret = platform_driver_register(&lrw_uart_platform_driver);
+	if (ret < 0) {
+		pr_err("LRW UART platform driver register failed, e = %d\n", ret);
+		uart_unregister_driver(&lrw_uart_driver);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void __exit lrw_uart_exit(void)
+{
+	platform_driver_unregister(&lrw_uart_platform_driver);
+	uart_unregister_driver(&lrw_uart_driver);
+}
+
+/*
+ * While this can be a module, if builtin it's most likely the console
+ * So let's leave module_exit but move module_init to an earlier place
+ */
+arch_initcall(lrw_uart_init);
+module_exit(lrw_uart_exit);
+
+MODULE_AUTHOR("Wenhong Liu/Qingtao Liu");
+MODULE_DESCRIPTION("LRW UART serial driver");
+MODULE_LICENSE("GPL");
diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
index 9c007a106330..8e7322067e1f 100644
--- a/include/uapi/linux/serial_core.h
+++ b/include/uapi/linux/serial_core.h
@@ -231,6 +231,9 @@
 /* Sunplus UART */
 #define PORT_SUNPLUS	123
 
+/* LRW UART */
+#define PORT_LRW	124
+
 /* Generic type identifier for ports which type is not important to userspace. */
 #define PORT_GENERIC	(-1)
 
-- 
2.27.0


^ permalink raw reply related

* [PATCH 2/2] tty: serial: add driver for the LRW UART
From: LiuQingtao @ 2026-02-13  9:22 UTC (permalink / raw)
  To: liu.qingtao2; +Cc: Wenhong Liu, linux-serial, linux-riscv
In-Reply-To: <20260213092252.8244-1-qtliu@mail.ustc.edu.cn>

From: Wenhong Liu <liu.wenhong35@zte.com.cn>

This commit introduces a serial driver for the LRW UART controller

Key features implemented:
- Support for FIFO mode (16-byte depth)
- Baud rate configuration
- Standard asynchronous communication formats:
  * Data bits: 5, 6, 7, 8, 9 bits
  * Parity: odd, even, fixed, none
  * Stop bits: 1 or 2 bits
- Hardware flow control (RTS/CTS)
- Multiple interrupt reporting mechanisms

Signed-off-by: Wenhong Liu <liu.wenhong35@zte.com.cn>
Signed-off-by: Qingtao Liu <liu.qingtao2@zte.com.cn>
Cc: linux-serial@vger.kernel.org
Cc: linux-riscv@lists.infradead.org
---
 MAINTAINERS                      |    3 +
 drivers/tty/serial/Kconfig       |   33 +
 drivers/tty/serial/Makefile      |    1 +
 drivers/tty/serial/lrw_uart.c    | 2822 ++++++++++++++++++++++++++++++
 include/uapi/linux/serial_core.h |    3 +
 5 files changed, 2862 insertions(+)
 create mode 100644 drivers/tty/serial/lrw_uart.c

diff --git a/MAINTAINERS b/MAINTAINERS
index ad6acbe24544..a97fbd205f75 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15041,6 +15041,9 @@ R:	Qingtao Liu <liu.qingtao2@zte.com.cn>
 L:	linux-serial@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/serial/lrw,lrw-uart.yaml
+F:	drivers/tty/serial/Kconfig
+F:	drivers/tty/serial/Makefile
+F:	drivers/tty/serial/lrw_uart.c
 
 LSILOGIC MPT FUSION DRIVERS (FC/SAS/SPI)
 M:	Sathya Prakash <sathya.prakash@broadcom.com>
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index f86775cfdcc9..a8f2d750c5b4 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1619,6 +1619,39 @@ config SERIAL_ESP32_ACM
 	  snippet may be used:
 	    earlycon=esp32s3acm,mmio32,0x60038000
 
+config SERIAL_LRW_UART
+	tristate "LRW UART support"
+	select SERIAL_CORE
+	help
+	  This option enables support for the LRW Universal Asynchronous
+	  Receiver/Transmitter (UART) serial controller.
+
+	  Select this option if you are building a kernel for a device that
+	  contains a LRW UART IP block.
+
+	  This driver can be built as a module; if so, the module will be
+	  called lrw_uart.
+
+	  If you are using a system with an LRW UART controller, say Y or M here.
+	  If unsure, say N.
+
+config SERIAL_LRW_UART_CONSOLE
+	bool "Console on LRW UART"
+	depends on SERIAL_LRW_UART=y
+	select SERIAL_CORE_CONSOLE
+	select SERIAL_EARLYCON
+	help
+	  Say Y here if you wish to use an LRW UART as the system
+	  console (the system console is the device which receives all kernel
+	  messages and warnings and which allows logins in single user mode).
+
+	  Even if you say Y here, the currently visible framebuffer console
+	  (/dev/tty0) will still be used as the system console by default, but
+	  you can alter that using a kernel command line option such as
+	  "console=ttyLRW0". (Try "man bootparam" or see the documentation of
+	  your boot loader (lilo or loadlin) about how to pass options to the
+	  kernel at boot time.)
+
 endmenu
 
 config SERIAL_MCTRL_GPIO
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index a2ccbc508ec5..0f694c4a4e22 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -94,6 +94,7 @@ obj-$(CONFIG_SERIAL_UARTLITE)		+= uartlite.o
 obj-$(CONFIG_SERIAL_VT8500)		+= vt8500_serial.o
 obj-$(CONFIG_SERIAL_XILINX_PS_UART)	+= xilinx_uartps.o
 obj-$(CONFIG_SERIAL_ZS)			+= zs.o
+obj-$(CONFIG_SERIAL_LRW_UART) 		+= lrw_uart.o
 
 # GPIOLIB helpers for modem control lines
 obj-$(CONFIG_SERIAL_MCTRL_GPIO)	+= serial_mctrl_gpio.o
diff --git a/drivers/tty/serial/lrw_uart.c b/drivers/tty/serial/lrw_uart.c
new file mode 100644
index 000000000000..e2399bef203f
--- /dev/null
+++ b/drivers/tty/serial/lrw_uart.c
@@ -0,0 +1,2822 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  Serial Port driver for LRW
+ *
+ *  Copyright (c) 2025, LRW CORPORATION. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/console.h>
+#include <linux/platform_device.h>
+#include <linux/sysrq.h>
+#include <linux/device.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial_core.h>
+#include <linux/serial.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/scatterlist.h>
+#include <linux/delay.h>
+#include <linux/types.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/sizes.h>
+#include <linux/io.h>
+#include <linux/acpi.h>
+
+#define UART_NR			14
+
+#define ISR_PASS_LIMIT		256
+
+#define LRW_UART_NAME		"lrw-uart"
+
+#define LRW_UART_TTY_PREFIX	"ttyLRW"
+
+/* LRW_UART_TX_FIFO_DEPTH: depth of the TX FIFO (in bytes) */
+#define LRW_UART_TX_FIFO_DEPTH	16
+
+/* LRW_UART_RX_FIFO_DEPTH: depth of the RX FIFO (in bytes) */
+#define LRW_UART_RX_FIFO_DEPTH	16
+
+/* LRW UART register offsets */
+#define UARTDR			0x00	/* Data register */
+#define UARTRSR			0x04	/* Receive status register */
+#define UARTECR			0x04	/* Error clear register */
+#define UARTSC			0x08	/* Special character register */
+#define UARTMDR			0x0C	/* RS485 Muti-drop register */
+#define UARTTAT			0x10	/* RS485 turn-around time register */
+#define UARTFCR			0x14	/* FIFO control register */
+#define UARTFR			0x18	/* Flag register */
+#define UARTIND			0x1C	/* Integer baud rate register */
+#define UARTFD			0x20	/* Fractional baud rate register */
+#define UARTBSR			0x24	/* Baud sample rate register */
+#define	UARTFRCR		0x28	/* Frame control register */
+#define UARTMCFG		0x2C	/* config register */
+#define	UARTMCR			0x30	/* Modem control register */
+#define	UARTIRCR		0x34	/* IrDA mode control register */
+#define UARTIMSC		0x38	/* Interrupt mask set/clear register */
+#define UARTRIS			0x3C	/* Raw interrupt status register */
+#define UARTMIS			0x40	/* Masked interrupt states register */
+#define UARTICR			0x44	/* Interrupt clear register */
+#define UARTFCCR		0x48	/* Flow control register */
+#define UARTRVS			0x58	/* Version register */
+
+#define UARTDR_OE		BIT(11)
+#define UARTDR_BE		BIT(10)
+#define UARTDR_PE		BIT(9)
+#define UARTDR_FE		BIT(8)
+
+#define UARTRSR_OE		BIT(3)
+#define UARTRSR_BE		BIT(2)
+#define UARTRSR_PE		BIT(1)
+#define UARTRSR_FE		BIT(0)
+
+#define UARTFCR_RXFTRS		GENMASK(7, 5)
+#define UARTFCR_RXFTRS_RX1_8	FIELD_PREP_CONST(UARTFCR_RXFTRS, 0)
+#define UARTFCR_RXFTRS_RX2_8	FIELD_PREP_CONST(UARTFCR_RXFTRS, 1)
+#define UARTFCR_RXFTRS_RX4_8	FIELD_PREP_CONST(UARTFCR_RXFTRS, 2)
+#define UARTFCR_RXFTRS_RX6_8	FIELD_PREP_CONST(UARTFCR_RXFTRS, 3)
+#define UARTFCR_RXFTRS_RX7_8	FIELD_PREP_CONST(UARTFCR_RXFTRS, 4)
+#define UARTFCR_TXFTRS		GENMASK(4, 2)
+#define UARTFCR_TXFTRS_TX1_8	FIELD_PREP_CONST(UARTFCR_TXFTRS, 0)
+#define UARTFCR_TXFTRS_TX2_8	FIELD_PREP_CONST(UARTFCR_TXFTRS, 1)
+#define UARTFCR_TXFTRS_TX4_8	FIELD_PREP_CONST(UARTFCR_TXFTRS, 2)
+#define UARTFCR_TXFTRS_TX6_8	FIELD_PREP_CONST(UARTFCR_TXFTRS, 3)
+#define UARTFCR_TXFTRS_TX7_8	FIELD_PREP_CONST(UARTFCR_TXFTRS, 4)
+#define UARTFCR_FEN		BIT(0)
+
+#define UARTFR_RI		BIT(8)
+#define UARTFR_TXFE		BIT(7)
+#define UARTFR_RXFF		BIT(6)
+#define UARTFR_TXFF		(1 << 5)	/* used in ASM */
+#define UARTFR_RXFE		BIT(4)
+#define UARTFR_BUSY		(1 << 3)	/* used in ASM */
+#define UARTFR_DCD		BIT(2)
+#define UARTFR_DSR		BIT(1)
+#define UARTFR_CTS		BIT(0)
+#define UARTFR_TMSK		(UARTFR_TXFF + UARTFR_BUSY)
+
+#define UARTFRCR_STP2		BIT(5)
+#define UARTFRCR_SPS		BIT(4)
+#define UARTFRCR_EOP		BIT(3)
+#define UARTFRCR_PEN		BIT(2)
+#define UARTFRCR_WLEN_8		0x3
+#define UARTFRCR_WLEN_7		0x2
+#define UARTFRCR_WLEN_6		0x1
+#define UARTFRCR_WLEN_5		0x0
+
+#define UARTMCFG_LBE		BIT(7)	/* loopback enable */
+#define UARTMCFG_RXE		BIT(3)	/* receive enable */
+#define UARTMCFG_TXE		BIT(2)	/* transmit enable */
+#define UARTMCFG_BRK		BIT(1)	/* send break */
+#define UARTMCFG_UARTEN		BIT(0)	/* UART enable */
+
+#define UARTMCR_OUT2		BIT(3)	/* OUT2 */
+#define UARTMCR_OUT1		BIT(2)	/* OUT1 */
+#define UARTMCR_RTS		BIT(1)	/* RTS */
+#define UARTMCR_DTR		BIT(0)	/* DTR */
+
+#define UARTIMSC_OEIM		BIT(10)	/* overrun error interrupt mask */
+#define UARTIMSC_BEIM		BIT(9)	/* break error interrupt mask */
+#define UARTIMSC_PEIM		BIT(8)	/* parity error interrupt mask */
+#define UARTIMSC_FEIM		BIT(7)	/* framing error interrupt mask */
+#define UARTIMSC_RTIM		BIT(6)	/* receive timeout interrupt mask */
+#define UARTIMSC_TXIM		BIT(5)	/* transmit interrupt mask */
+#define UARTIMSC_RXIM		BIT(4)	/* receive interrupt mask */
+#define UARTIMSC_DSRMIM		BIT(3)	/* DSR interrupt mask */
+#define UARTIMSC_DCDMIM		BIT(2)	/* DCD interrupt mask */
+#define UARTIMSC_CTSMIM		BIT(1)	/* CTS interrupt mask */
+#define UARTIMSC_RIMIM		BIT(0)	/* RI interrupt mask */
+
+#define UARTICR_OEIC		BIT(10)	/* overrun error interrupt clear */
+#define UARTICR_BEIC		BIT(9)	/* break error interrupt clear */
+#define UARTICR_PEIC		BIT(8)	/* parity error interrupt clear */
+#define UARTICR_FEIC		BIT(7)	/* framing error interrupt clear */
+#define UARTICR_RTIC		BIT(6)	/* receive timeout interrupt clear */
+#define UARTICR_TXIC		BIT(5)	/* transmit interrupt clear */
+#define UARTICR_RXIC		BIT(4)	/* receive interrupt clear */
+#define UARTICR_DSRMIC		BIT(3)	/* DSR interrupt clear */
+#define UARTICR_DCDMIC		BIT(2)	/* DCD interrupt clear */
+#define UARTICR_CTSMIC		BIT(1)	/* CTS interrupt clear */
+#define UARTICR_RIMIC		BIT(0)	/* RI interrupt clear */
+
+#define UARTFCCR_CTSEN		BIT(5)	/* CTS hardware flow control */
+#define UARTFCCR_RTSEN		BIT(4)	/* RTS hardware flow control */
+#define UARTFCCR_DMAONERR	BIT(2)	/* disable dma on error */
+#define UARTFCCR_TXDMAE		BIT(1)	/* enable transmit dma */
+#define UARTFCCR_RXDMAE		BIT(0)	/* enable receive dma */
+
+#define UARTRSR_ANY		(UARTRSR_OE | UARTRSR_BE | UARTRSR_PE | UARTRSR_FE)
+#define UARTFR_MODEM_ANY	(UARTFR_DCD | UARTFR_DSR | UARTFR_CTS)
+
+#define UART_DR_ERROR		(UARTDR_OE | UARTDR_BE | UARTDR_PE | UARTDR_FE)
+#define UART_DUMMY_DR_RX	BIT(16)
+
+enum {
+	REG_DR,
+	REG_FCR,
+	REG_FR,
+	REG_IND,
+	REG_FD,
+	REG_BSR,
+	REG_FRCR,
+	REG_MCFG,
+	REG_MCR,
+	REG_IMSC,
+	REG_RIS,
+	REG_MIS,
+	REG_ICR,
+	REG_FCCR,
+
+	/* The size of the array - must be last */
+	REG_ARRAY_SIZE,
+};
+
+static u16 lrw_uart_std_offsets[REG_ARRAY_SIZE] = {
+	[REG_DR] = UARTDR,
+	[REG_FCR] = UARTFCR,
+	[REG_FR] = UARTFR,
+	[REG_IND] = UARTIND,
+	[REG_FD] = UARTFD,
+	[REG_BSR] = UARTBSR,
+	[REG_FRCR] = UARTFRCR,
+	[REG_MCFG] = UARTMCFG,
+	[REG_MCR] = UARTMCR,
+	[REG_IMSC] = UARTIMSC,
+	[REG_RIS] = UARTRIS,
+	[REG_MIS] = UARTMIS,
+	[REG_ICR] = UARTICR,
+	[REG_FCCR] = UARTFCCR,
+};
+
+/* There is by now at least one vendor with differing details, so handle it */
+struct vendor_data {
+	const u16		*reg_offset;
+	unsigned int		fcr;
+	unsigned int		fr_busy;
+	unsigned int		fr_dsr;
+	unsigned int		fr_cts;
+	unsigned int		fr_ri;
+	unsigned int		inv_fr;
+	bool			access_32b;
+	bool			oversampling;
+	bool			dma_threshold;
+	bool			cts_event_workaround;
+	bool			always_enabled;
+	bool			fixed_options;
+};
+
+static struct vendor_data vendor_lrw = {
+	.reg_offset		= lrw_uart_std_offsets,
+	.fcr			= UARTFCR_RXFTRS_RX4_8 | UARTFCR_TXFTRS_TX4_8 | UARTFCR_FEN,
+	.fr_busy		= UARTFR_BUSY,
+	.fr_dsr			= UARTFR_DSR,
+	.fr_cts			= UARTFR_CTS,
+	.fr_ri			= UARTFR_RI,
+	.access_32b		= true,
+	.oversampling		= false,
+	.dma_threshold		= false,
+	.cts_event_workaround	= false,
+	.always_enabled		= false,
+	.fixed_options		= true,
+};
+
+/* Deals with DMA transactions */
+
+struct lrw_uart_dmabuf {
+	dma_addr_t		dma;
+	size_t			len;
+	char			*buf;
+};
+
+struct lrw_uart_dmarx_data {
+	struct dma_chan		*chan;
+	struct completion	complete;
+	bool			use_buf_b;
+	struct lrw_uart_dmabuf	dbuf_a;
+	struct lrw_uart_dmabuf	dbuf_b;
+	dma_cookie_t		cookie;
+	bool			running;
+	struct timer_list	timer;
+	unsigned int last_residue;
+	unsigned long last_jiffies;
+	bool auto_poll_rate;
+	unsigned int poll_rate;
+	unsigned int poll_timeout;
+};
+
+struct lrw_uart_dmatx_data {
+	struct dma_chan		*chan;
+	dma_addr_t		dma;
+	size_t			len;
+	char			*buf;
+	bool			queued;
+};
+
+struct lrw_uart_data {
+	bool (*dma_filter)(struct dma_chan *chan, void *filter_param);
+	void *dma_rx_param;
+	void *dma_tx_param;
+	bool dma_rx_poll_enable;
+	unsigned int dma_rx_poll_rate;
+	unsigned int dma_rx_poll_timeout;
+	void (*init)(void);
+	void (*exit)(void);
+};
+
+/*
+ * We wrap our port structure around the generic uart_port.
+ */
+struct lrw_uart_port {
+	struct uart_port	port;
+	const u16		*reg_offset;
+	struct clk		*clk;
+	const struct vendor_data *vendor;
+	unsigned int		im;		/* interrupt mask */
+	unsigned int		old_status;
+	unsigned int		fifosize;	/* vendor-specific */
+	unsigned int		fixed_baud;	/* vendor-set fixed baud rate */
+	char			type[12];
+	bool			rs485_tx_started;
+	unsigned int		rs485_tx_drain_interval; /* usecs */
+#ifdef CONFIG_DMA_ENGINE
+	/* DMA stuff */
+	unsigned int		dmacr;		/* dma control reg */
+	bool			using_tx_dma;
+	bool			using_rx_dma;
+	struct lrw_uart_dmarx_data dmarx;
+	struct lrw_uart_dmatx_data dmatx;
+	bool			dma_probed;
+#endif
+};
+
+static unsigned int lrw_uart_tx_empty(struct uart_port *port);
+
+static unsigned int lrw_uart_reg_to_offset(const struct lrw_uart_port *sup,
+	unsigned int reg)
+{
+	return sup->reg_offset[reg];
+}
+
+static unsigned int lrw_uart_read(const struct lrw_uart_port *sup,
+	unsigned int reg)
+{
+	void __iomem *addr = sup->port.membase + lrw_uart_reg_to_offset(sup, reg);
+
+	return (sup->port.iotype == UPIO_MEM32) ?
+		readl_relaxed(addr) : readw_relaxed(addr);
+}
+
+static void lrw_uart_write(unsigned int val, const struct lrw_uart_port *sup,
+	unsigned int reg)
+{
+	void __iomem *addr = sup->port.membase + lrw_uart_reg_to_offset(sup, reg);
+
+	if (sup->port.iotype == UPIO_MEM32)
+		writel_relaxed(val, addr);
+	else
+		writew_relaxed(val, addr);
+}
+
+/*
+ * Reads up to 256 characters from the FIFO or until it's empty and
+ * inserts them into the TTY layer. Returns the number of characters
+ * read from the FIFO.
+ */
+static int lrw_uart_fifo_to_tty(struct lrw_uart_port *sup)
+{
+	unsigned int ch, fifotaken;
+	int sysrq;
+	u16 status;
+	u8 flag;
+
+	for (fifotaken = 0; fifotaken != 256; fifotaken++) {
+		status = lrw_uart_read(sup, REG_FR);
+		if (status & UARTFR_RXFE)
+			break;
+
+		/* Take chars from the FIFO and update status */
+		ch = lrw_uart_read(sup, REG_DR) | UART_DUMMY_DR_RX;
+		flag = TTY_NORMAL;
+		sup->port.icount.rx++;
+
+		if (unlikely(ch & UART_DR_ERROR)) {
+			if (ch & UARTDR_BE) {
+				ch &= ~(UARTDR_FE | UARTDR_PE);
+				sup->port.icount.brk++;
+				if (uart_handle_break(&sup->port))
+					continue;
+			} else if (ch & UARTDR_PE) {
+				sup->port.icount.parity++;
+			} else if (ch & UARTDR_FE) {
+				sup->port.icount.frame++;
+			}
+			if (ch & UARTDR_OE)
+				sup->port.icount.overrun++;
+
+			ch &= sup->port.read_status_mask;
+
+			if (ch & UARTDR_BE)
+				flag = TTY_BREAK;
+			else if (ch & UARTDR_PE)
+				flag = TTY_PARITY;
+			else if (ch & UARTDR_FE)
+				flag = TTY_FRAME;
+		}
+
+		sysrq = uart_prepare_sysrq_char(&sup->port, ch & 255);
+		if (!sysrq)
+			uart_insert_char(&sup->port, ch, UARTDR_OE, ch, flag);
+	}
+
+	return fifotaken;
+}
+
+/*
+ * All the DMA operation mode stuff goes inside this ifdef.
+ * This assumes that you have a generic DMA device interface,
+ * no custom DMA interfaces are supported.
+ */
+#ifdef CONFIG_DMA_ENGINE
+
+#define LRW_UART_DMA_BUFFER_SIZE PAGE_SIZE
+
+static int lrw_uart_dmabuf_init(struct dma_chan *chan, struct lrw_uart_dmabuf *db,
+	enum dma_data_direction dir)
+{
+	db->buf = dma_alloc_coherent(chan->device->dev, LRW_UART_DMA_BUFFER_SIZE,
+				     &db->dma, GFP_KERNEL);
+	if (!db->buf)
+		return -ENOMEM;
+	db->len = LRW_UART_DMA_BUFFER_SIZE;
+
+	return 0;
+}
+
+static void lrw_uart_dmabuf_free(struct dma_chan *chan, struct lrw_uart_dmabuf *db,
+	enum dma_data_direction dir)
+{
+	if (db->buf) {
+		dma_free_coherent(chan->device->dev,
+				  LRW_UART_DMA_BUFFER_SIZE, db->buf, db->dma);
+	}
+}
+
+static void lrw_uart_dma_probe(struct lrw_uart_port *sup)
+{
+	/* DMA is the sole user of the platform data right now */
+	struct lrw_uart_data *plat = dev_get_platdata(sup->port.dev);
+	struct device *dev = sup->port.dev;
+	struct dma_slave_config tx_conf = {
+		.dst_addr = sup->port.mapbase +
+				 lrw_uart_reg_to_offset(sup, REG_DR),
+		.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE,
+		.direction = DMA_MEM_TO_DEV,
+		.dst_maxburst = sup->fifosize >> 1,
+		.device_fc = false,
+	};
+	struct dma_chan *chan;
+	dma_cap_mask_t mask;
+
+	sup->dma_probed = true;
+	chan = dma_request_chan(dev, "tx");
+	if (IS_ERR(chan)) {
+		if (PTR_ERR(chan) == -EPROBE_DEFER) {
+			sup->dma_probed = false;
+			return;
+		}
+
+		/* We need platform data */
+		if (!plat || !plat->dma_filter) {
+			dev_dbg(sup->port.dev, "no DMA platform data\n");
+			return;
+		}
+
+		/* Try to acquire a generic DMA engine slave TX channel */
+		dma_cap_zero(mask);
+		dma_cap_set(DMA_SLAVE, mask);
+
+		chan = dma_request_channel(mask, plat->dma_filter,
+					   plat->dma_tx_param);
+		if (!chan) {
+			dev_err(sup->port.dev, "no TX DMA channel!\n");
+			return;
+		}
+	}
+
+	dmaengine_slave_config(chan, &tx_conf);
+	sup->dmatx.chan = chan;
+
+	dev_info(sup->port.dev, "DMA channel TX %s\n",
+		 dma_chan_name(sup->dmatx.chan));
+
+	/* Optionally make use of an RX channel as well */
+	chan = dma_request_chan(dev, "rx");
+
+	if (IS_ERR(chan) && plat && plat->dma_rx_param) {
+		chan = dma_request_channel(mask, plat->dma_filter, plat->dma_rx_param);
+
+		if (!chan) {
+			dev_err(sup->port.dev, "no RX DMA channel!\n");
+			return;
+		}
+	}
+
+	if (!IS_ERR(chan)) {
+		struct dma_slave_config rx_conf = {
+			.src_addr = sup->port.mapbase +
+				lrw_uart_reg_to_offset(sup, REG_DR),
+			.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE,
+			.direction = DMA_DEV_TO_MEM,
+			.src_maxburst = sup->fifosize >> 2,
+			.device_fc = false,
+		};
+		struct dma_slave_caps caps;
+
+		/*
+		 * Some DMA controllers provide information on their capabilities.
+		 * If the controller does, check for suitable residue processing
+		 * otherwise assime all is well.
+		 */
+		if (dma_get_slave_caps(chan, &caps) == 0) {
+			if (caps.residue_granularity ==
+					DMA_RESIDUE_GRANULARITY_DESCRIPTOR) {
+				dma_release_channel(chan);
+				dev_info(sup->port.dev,
+					 "RX DMA disabled - no residue processing\n");
+				return;
+			}
+		}
+		dmaengine_slave_config(chan, &rx_conf);
+		sup->dmarx.chan = chan;
+
+		sup->dmarx.auto_poll_rate = false;
+		if (plat && plat->dma_rx_poll_enable) {
+			/* Set poll rate if specified. */
+			if (plat->dma_rx_poll_rate) {
+				sup->dmarx.auto_poll_rate = false;
+				sup->dmarx.poll_rate = plat->dma_rx_poll_rate;
+			} else {
+				/*
+				 * 100 ms defaults to poll rate if not
+				 * specified. This will be adjusted with
+				 * the baud rate at set_termios.
+				 */
+				sup->dmarx.auto_poll_rate = true;
+				sup->dmarx.poll_rate =  100;
+			}
+			/* 3 secs defaults poll_timeout if not specified. */
+			if (plat->dma_rx_poll_timeout)
+				sup->dmarx.poll_timeout =
+					plat->dma_rx_poll_timeout;
+			else
+				sup->dmarx.poll_timeout = 3000;
+		} else if (!plat && dev->of_node) {
+			sup->dmarx.auto_poll_rate =
+					of_property_read_bool(dev->of_node, "auto-poll");
+			if (sup->dmarx.auto_poll_rate) {
+				u32 x;
+
+				if (of_property_read_u32(dev->of_node, "poll-rate-ms", &x) == 0)
+					sup->dmarx.poll_rate = x;
+				else
+					sup->dmarx.poll_rate = 100;
+				if (of_property_read_u32(dev->of_node, "poll-timeout-ms", &x) == 0)
+					sup->dmarx.poll_timeout = x;
+				else
+					sup->dmarx.poll_timeout = 3000;
+			}
+		}
+		dev_info(sup->port.dev, "DMA channel RX %s\n",
+			 dma_chan_name(sup->dmarx.chan));
+	}
+}
+
+static void lrw_uart_dma_remove(struct lrw_uart_port *sup)
+{
+	if (sup->dmatx.chan)
+		dma_release_channel(sup->dmatx.chan);
+	if (sup->dmarx.chan)
+		dma_release_channel(sup->dmarx.chan);
+}
+
+/* Forward declare these for the refill routine */
+static int lrw_uart_dma_tx_refill(struct lrw_uart_port *sup);
+static void lrw_uart_start_tx_pio(struct lrw_uart_port *sup);
+
+/*
+ * The current DMA TX buffer has been sent.
+ * Try to queue up another DMA buffer.
+ */
+static void lrw_uart_dma_tx_callback(void *data)
+{
+	struct lrw_uart_port *sup = data;
+	struct tty_port *tport = &sup->port.state->port;
+	struct lrw_uart_dmatx_data *dmatx = &sup->dmatx;
+	unsigned long flags;
+	u16 dmacr;
+
+	uart_port_lock_irqsave(&sup->port, &flags);
+	if (sup->dmatx.queued)
+		dma_unmap_single(dmatx->chan->device->dev, dmatx->dma,
+				 dmatx->len, DMA_TO_DEVICE);
+
+	dmacr = sup->dmacr;
+	sup->dmacr = dmacr & ~UARTFCCR_TXDMAE;
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+
+	/*
+	 * If TX DMA was disabled, it means that we've stopped the DMA for
+	 * some reason (eg, XOFF received, or we want to send an X-char.)
+	 *
+	 * Note: we need to be careful here of a potential race between DMA
+	 * and the rest of the driver - if the driver disables TX DMA while
+	 * a TX buffer completing, we must update the tx queued status to
+	 * get further refills (hence we check dmacr).
+	 */
+	if (!(dmacr & UARTFCCR_TXDMAE) || uart_tx_stopped(&sup->port) ||
+	    kfifo_is_empty(&tport->xmit_fifo)) {
+		sup->dmatx.queued = false;
+		uart_port_unlock_irqrestore(&sup->port, flags);
+		return;
+	}
+
+	if (lrw_uart_dma_tx_refill(sup) <= 0)
+		/*
+		 * We didn't queue a DMA buffer for some reason, but we
+		 * have data pending to be sent.  Re-enable the TX IRQ.
+		 */
+		lrw_uart_start_tx_pio(sup);
+
+	uart_port_unlock_irqrestore(&sup->port, flags);
+}
+
+/*
+ * Try to refill the TX DMA buffer.
+ * Locking: called with port lock held and IRQs disabled.
+ * Returns:
+ *   1 if we queued up a TX DMA buffer.
+ *   0 if we didn't want to handle this by DMA
+ *  <0 on error
+ */
+static int lrw_uart_dma_tx_refill(struct lrw_uart_port *sup)
+{
+	struct lrw_uart_dmatx_data *dmatx = &sup->dmatx;
+	struct dma_chan *chan = dmatx->chan;
+	struct dma_device *dma_dev = chan->device;
+	struct dma_async_tx_descriptor *desc;
+	struct tty_port *tport = &sup->port.state->port;
+	unsigned int count;
+
+	/*
+	 * Try to avoid the overhead involved in using DMA if the
+	 * transaction fits in the first half of the FIFO, by using
+	 * the standard interrupt handling.  This ensures that we
+	 * issue a uart_write_wakeup() at the appropriate time.
+	 */
+	count = kfifo_len(&tport->xmit_fifo);
+	if (count < (sup->fifosize >> 1)) {
+		sup->dmatx.queued = false;
+		return 0;
+	}
+
+	/*
+	 * Bodge: don't send the last character by DMA, as this
+	 * will prevent XON from notifying us to restart DMA.
+	 */
+	count -= 1;
+
+	/* Else proceed to copy the TX chars to the DMA buffer and fire DMA */
+	if (count > LRW_UART_DMA_BUFFER_SIZE)
+		count = LRW_UART_DMA_BUFFER_SIZE;
+
+	count = kfifo_out_peek(&tport->xmit_fifo, dmatx->buf, count);
+	dmatx->len = count;
+	dmatx->dma = dma_map_single(dma_dev->dev, dmatx->buf, count,
+				    DMA_TO_DEVICE);
+	if (dmatx->dma == DMA_MAPPING_ERROR) {
+		sup->dmatx.queued = false;
+		dev_dbg(sup->port.dev, "unable to map TX DMA\n");
+		return -EBUSY;
+	}
+
+	desc = dmaengine_prep_slave_single(chan, dmatx->dma, dmatx->len, DMA_MEM_TO_DEV,
+					   DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+	if (!desc) {
+		dma_unmap_single(dma_dev->dev, dmatx->dma, dmatx->len, DMA_TO_DEVICE);
+		sup->dmatx.queued = false;
+		/*
+		 * If DMA cannot be used right now, we complete this
+		 * transaction via IRQ and let the TTY layer retry.
+		 */
+		dev_dbg(sup->port.dev, "TX DMA busy\n");
+		return -EBUSY;
+	}
+
+	/* Some data to go along to the callback */
+	desc->callback = lrw_uart_dma_tx_callback;
+	desc->callback_param = sup;
+
+	/* All errors should happen at prepare time */
+	dmaengine_submit(desc);
+
+	/* Fire the DMA transaction */
+	dma_dev->device_issue_pending(chan);
+
+	sup->dmacr |= UARTFCCR_TXDMAE;
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+	sup->dmatx.queued = true;
+
+	/*
+	 * Now we know that DMA will fire, so advance the ring buffer
+	 * with the stuff we just dispatched.
+	 */
+	uart_xmit_advance(&sup->port, count);
+
+	if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
+		uart_write_wakeup(&sup->port);
+
+	return 1;
+}
+
+/*
+ * We received a transmit interrupt without a pending X-char but with
+ * pending characters.
+ * Locking: called with port lock held and IRQs disabled.
+ * Returns:
+ *   false if we want to use PIO to transmit
+ *   true if we queued a DMA buffer
+ */
+static bool lrw_uart_dma_tx_irq(struct lrw_uart_port *sup)
+{
+	if (!sup->using_tx_dma)
+		return false;
+
+	/*
+	 * If we already have a TX buffer queued, but received a
+	 * TX interrupt, it will be because we've just sent an X-char.
+	 * Ensure the TX DMA is enabled and the TX IRQ is disabled.
+	 */
+	if (sup->dmatx.queued) {
+		sup->dmacr |= UARTFCCR_TXDMAE;
+		lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+		sup->im &= ~UARTIMSC_TXIM;
+		lrw_uart_write(sup->im, sup, REG_IMSC);
+		return true;
+	}
+
+	/*
+	 * We don't have a TX buffer queued, so try to queue one.
+	 * If we successfully queued a buffer, mask the TX IRQ.
+	 */
+	if (lrw_uart_dma_tx_refill(sup) > 0) {
+		sup->im &= ~UARTIMSC_TXIM;
+		lrw_uart_write(sup->im, sup, REG_IMSC);
+		return true;
+	}
+	return false;
+}
+
+/*
+ * Stop the DMA transmit (eg, due to received XOFF).
+ * Locking: called with port lock held and IRQs disabled.
+ */
+static inline void lrw_uart_dma_tx_stop(struct lrw_uart_port *sup)
+{
+	if (sup->dmatx.queued) {
+		sup->dmacr &= ~UARTFCCR_TXDMAE;
+		lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+	}
+}
+
+/*
+ * Try to start a DMA transmit, or in the case of an XON/OFF
+ * character queued for send, try to get that character out ASAP.
+ * Locking: called with port lock held and IRQs disabled.
+ * Returns:
+ *   false if we want the TX IRQ to be enabled
+ *   true if we have a buffer queued
+ */
+static inline bool lrw_uart_dma_tx_start(struct lrw_uart_port *sup)
+{
+	u16 dmacr;
+
+	if (!sup->using_tx_dma)
+		return false;
+
+	if (!sup->port.x_char) {
+		/* no X-char, try to push chars out in DMA mode */
+		bool ret = true;
+
+		if (!sup->dmatx.queued) {
+			if (lrw_uart_dma_tx_refill(sup) > 0) {
+				sup->im &= ~UARTIMSC_TXIM;
+				lrw_uart_write(sup->im, sup, REG_IMSC);
+			} else {
+				ret = false;
+			}
+		} else if (!(sup->dmacr & UARTFCCR_TXDMAE)) {
+			sup->dmacr |= UARTFCCR_TXDMAE;
+			lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+		}
+		return ret;
+	}
+
+	/*
+	 * We have an X-char to send.  Disable DMA to prevent it loading
+	 * the TX fifo, and then see if we can stuff it into the FIFO.
+	 */
+	dmacr = sup->dmacr;
+	sup->dmacr &= ~UARTFCCR_TXDMAE;
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+
+	if (lrw_uart_read(sup, REG_FR) & UARTFR_TXFF) {
+		/*
+		 * No space in the FIFO, so enable the transmit interrupt
+		 * so we know when there is space.  Note that once we've
+		 * loaded the character, we should just re-enable DMA.
+		 */
+		return false;
+	}
+
+	lrw_uart_write(sup->port.x_char, sup, REG_DR);
+	sup->port.icount.tx++;
+	sup->port.x_char = 0;
+
+	/* Success - restore the DMA state */
+	sup->dmacr = dmacr;
+	lrw_uart_write(dmacr, sup, REG_FCCR);
+
+	return true;
+}
+
+/*
+ * Flush the transmit buffer.
+ * Locking: called with port lock held and IRQs disabled.
+ */
+static void lrw_uart_dma_flush_buffer(struct uart_port *port)
+__releases(&sup->port.lock)
+__acquires(&sup->port.lock)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	if (!sup->using_tx_dma)
+		return;
+
+	dmaengine_terminate_async(sup->dmatx.chan);
+
+	if (sup->dmatx.queued) {
+		dma_unmap_single(sup->dmatx.chan->device->dev, sup->dmatx.dma,
+				 sup->dmatx.len, DMA_TO_DEVICE);
+		sup->dmatx.queued = false;
+		sup->dmacr &= ~UARTFCCR_TXDMAE;
+		lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+	}
+}
+
+static void lrw_uart_dma_rx_callback(void *data);
+
+static int lrw_uart_dma_rx_trigger_dma(struct lrw_uart_port *sup)
+{
+	struct dma_chan *rxchan = sup->dmarx.chan;
+	struct lrw_uart_dmarx_data *dmarx = &sup->dmarx;
+	struct dma_async_tx_descriptor *desc;
+	struct lrw_uart_dmabuf *dbuf;
+
+	if (!rxchan)
+		return -EIO;
+
+	/* Start the RX DMA job */
+	dbuf = sup->dmarx.use_buf_b ?
+		&sup->dmarx.dbuf_b : &sup->dmarx.dbuf_a;
+	desc = dmaengine_prep_slave_single(rxchan, dbuf->dma, dbuf->len,
+					   DMA_DEV_TO_MEM,
+					   DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+	/*
+	 * If the DMA engine is busy and cannot prepare a
+	 * channel, no big deal, the driver will fall back
+	 * to interrupt mode as a result of this error code.
+	 */
+	if (!desc) {
+		sup->dmarx.running = false;
+		dmaengine_terminate_all(rxchan);
+		return -EBUSY;
+	}
+
+	/* Some data to go along to the callback */
+	desc->callback = lrw_uart_dma_rx_callback;
+	desc->callback_param = sup;
+	dmarx->cookie = dmaengine_submit(desc);
+	dma_async_issue_pending(rxchan);
+
+	sup->dmacr |= UARTFCCR_RXDMAE;
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+	sup->dmarx.running = true;
+
+	sup->im &= ~UARTIMSC_RXIM;
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+
+	return 0;
+}
+
+/*
+ * This is called when either the DMA job is complete, or
+ * the FIFO timeout interrupt occurred. This must be called
+ * with the port spinlock sup->port.lock held.
+ */
+static void lrw_uart_dma_rx_chars(struct lrw_uart_port *sup,
+				  u32 pending, bool use_buf_b,
+				  bool readfifo)
+{
+	struct tty_port *port = &sup->port.state->port;
+	struct lrw_uart_dmabuf *dbuf = use_buf_b ?
+		&sup->dmarx.dbuf_b : &sup->dmarx.dbuf_a;
+	int dma_count = 0;
+	u32 fifotaken = 0; /* only used for vdbg() */
+
+	struct lrw_uart_dmarx_data *dmarx = &sup->dmarx;
+	int dmataken = 0;
+
+	if (sup->dmarx.poll_rate) {
+		/* The data can be taken by polling */
+		dmataken = dbuf->len - dmarx->last_residue;
+		/* Recalculate the pending size */
+		if (pending >= dmataken)
+			pending -= dmataken;
+	}
+
+	/* Pick the remain data from the DMA */
+	if (pending) {
+		/*
+		 * First take all chars in the DMA pipe, then look in the FIFO.
+		 * Note that tty_insert_flip_buf() tries to take as many chars
+		 * as it can.
+		 */
+		dma_count = tty_insert_flip_string(port, dbuf->buf + dmataken, pending);
+
+		sup->port.icount.rx += dma_count;
+		if (dma_count < pending)
+			dev_warn(sup->port.dev,
+				 "couldn't insert all characters (TTY is full?)\n");
+	}
+
+	/* Reset the last_residue for Rx DMA poll */
+	if (sup->dmarx.poll_rate)
+		dmarx->last_residue = dbuf->len;
+
+	/*
+	 * Only continue with trying to read the FIFO if all DMA chars have
+	 * been taken first.
+	 */
+	if (dma_count == pending && readfifo) {
+		/* Clear any error flags */
+		lrw_uart_write(UARTICR_OEIC | UARTICR_BEIC | UARTICR_PEIC |
+			      UARTICR_FEIC, sup, REG_ICR);
+
+		/*
+		 * If we read all the DMA'd characters, and we had an
+		 * incomplete buffer, that could be due to an rx error, or
+		 * maybe we just timed out. Read any pending chars and check
+		 * the error status.
+		 *
+		 * Error conditions will only occur in the FIFO, these will
+		 * trigger an immediate interrupt and stop the DMA job, so we
+		 * will always find the error in the FIFO, never in the DMA
+		 * buffer.
+		 */
+		fifotaken = lrw_uart_fifo_to_tty(sup);
+	}
+
+	dev_vdbg(sup->port.dev,
+		 "Took %d chars from DMA buffer and %d chars from the FIFO\n",
+		 dma_count, fifotaken);
+	tty_flip_buffer_push(port);
+}
+
+static void lrw_uart_dma_rx_irq(struct lrw_uart_port *sup)
+{
+	struct lrw_uart_dmarx_data *dmarx = &sup->dmarx;
+	struct dma_chan *rxchan = dmarx->chan;
+	struct lrw_uart_dmabuf *dbuf = dmarx->use_buf_b ?
+		&dmarx->dbuf_b : &dmarx->dbuf_a;
+	size_t pending;
+	struct dma_tx_state state;
+	enum dma_status dmastat;
+
+	/*
+	 * Pause the transfer so we can trust the current counter,
+	 * do this before we pause the LRW UART block, else we may
+	 * overflow the FIFO.
+	 */
+	if (dmaengine_pause(rxchan))
+		dev_err(sup->port.dev, "unable to pause DMA transfer\n");
+	dmastat = rxchan->device->device_tx_status(rxchan,
+						   dmarx->cookie, &state);
+	if (dmastat != DMA_PAUSED)
+		dev_err(sup->port.dev, "unable to pause DMA transfer\n");
+
+	/* Disable RX DMA - incoming data will wait in the FIFO */
+	sup->dmacr &= ~UARTFCCR_RXDMAE;
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+	sup->dmarx.running = false;
+
+	pending = dbuf->len - state.residue;
+	if (WARN_ONCE(pending > LRW_UART_DMA_BUFFER_SIZE,
+		      "pending %zu exceeds DMA buffer size %zu\n",
+		      pending, LRW_UART_DMA_BUFFER_SIZE))
+		pending = LRW_UART_DMA_BUFFER_SIZE;
+	/* Then we terminate the transfer - we now know our residue */
+	dmaengine_terminate_all(rxchan);
+
+	/*
+	 * This will take the chars we have so far and insert
+	 * into the framework.
+	 */
+	lrw_uart_dma_rx_chars(sup, pending, dmarx->use_buf_b, true);
+
+	/* Switch buffer & re-trigger DMA job */
+	dmarx->use_buf_b = !dmarx->use_buf_b;
+	if (lrw_uart_dma_rx_trigger_dma(sup)) {
+		dev_dbg(sup->port.dev,
+			"could not retrigger RX DMA job fall back to interrupt mode\n");
+		sup->im |= UARTIMSC_RXIM;
+		lrw_uart_write(sup->im, sup, REG_IMSC);
+	}
+}
+
+static void lrw_uart_dma_rx_callback(void *data)
+{
+	struct lrw_uart_port *sup = data;
+	struct lrw_uart_dmarx_data *dmarx = &sup->dmarx;
+	struct dma_chan *rxchan = dmarx->chan;
+	bool lastbuf = dmarx->use_buf_b;
+	struct lrw_uart_dmabuf *dbuf = dmarx->use_buf_b ?
+		&dmarx->dbuf_b : &dmarx->dbuf_a;
+	size_t pending;
+	struct dma_tx_state state;
+	int ret;
+
+	/*
+	 * This completion interrupt occurs typically when the
+	 * RX buffer is totally stuffed but no timeout has yet
+	 * occurred. When that happens, we just want the RX
+	 * routine to flush out the secondary DMA buffer while
+	 * we immediately trigger the next DMA job.
+	 */
+	uart_port_lock_irq(&sup->port);
+	/*
+	 * Rx data can be taken by the UART interrupts during
+	 * the DMA irq handler. So we check the residue here.
+	 */
+	rxchan->device->device_tx_status(rxchan, dmarx->cookie, &state);
+	pending = dbuf->len - state.residue;
+	if (WARN_ONCE(pending > LRW_UART_DMA_BUFFER_SIZE,
+		      "pending %zu exceeds DMA buffer size %zu\n",
+		      pending, LRW_UART_DMA_BUFFER_SIZE))
+		pending = LRW_UART_DMA_BUFFER_SIZE;
+	/* Then we terminate the transfer - we now know our residue */
+	dmaengine_terminate_all(rxchan);
+
+	sup->dmarx.running = false;
+	dmarx->use_buf_b = !lastbuf;
+	ret = lrw_uart_dma_rx_trigger_dma(sup);
+
+	lrw_uart_dma_rx_chars(sup, pending, lastbuf, false);
+	uart_unlock_and_check_sysrq(&sup->port);
+	/*
+	 * Do this check after we picked the DMA chars so we don't
+	 * get some IRQ immediately from RX.
+	 */
+	if (ret) {
+		dev_dbg(sup->port.dev,
+			"could not retrigger RX DMA job fall back to interrupt mode\n");
+		sup->im |= UARTIMSC_RXIM;
+		lrw_uart_write(sup->im, sup, REG_IMSC);
+	}
+}
+
+/*
+ * Stop accepting received characters, when we're shutting down or
+ * suspending this port.
+ * Locking: called with port lock held and IRQs disabled.
+ */
+static inline void lrw_uart_dma_rx_stop(struct lrw_uart_port *sup)
+{
+	if (!sup->using_rx_dma)
+		return;
+
+	/* FIXME.  Just disable the DMA enable */
+	sup->dmacr &= ~UARTFCCR_RXDMAE;
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+}
+
+/*
+ * Timer handler for Rx DMA polling.
+ * Every polling, It checks the residue in the dma buffer and transfer
+ * data to the tty. Also, last_residue is updated for the next polling.
+ */
+static void lrw_uart_dma_rx_poll(struct timer_list *t)
+{
+	struct lrw_uart_port *sup = timer_container_of(sup, t, dmarx.timer);
+	struct tty_port *port = &sup->port.state->port;
+	struct lrw_uart_dmarx_data *dmarx = &sup->dmarx;
+	struct dma_chan *rxchan = sup->dmarx.chan;
+	unsigned long flags;
+	unsigned int dmataken = 0;
+	unsigned int size = 0;
+	struct lrw_uart_dmabuf *dbuf;
+	int dma_count;
+	struct dma_tx_state state;
+
+	dbuf = dmarx->use_buf_b ? &sup->dmarx.dbuf_b : &sup->dmarx.dbuf_a;
+	rxchan->device->device_tx_status(rxchan, dmarx->cookie, &state);
+	if (likely(state.residue < dmarx->last_residue)) {
+		dmataken = dbuf->len - dmarx->last_residue;
+		size = dmarx->last_residue - state.residue;
+		dma_count = tty_insert_flip_string(port, dbuf->buf + dmataken,
+						   size);
+		if (dma_count == size)
+			dmarx->last_residue =  state.residue;
+		dmarx->last_jiffies = jiffies;
+	}
+	tty_flip_buffer_push(port);
+
+	/*
+	 * If no data is received in poll_timeout, the driver will fall back
+	 * to interrupt mode. We will retrigger DMA at the first interrupt.
+	 */
+	if (jiffies_to_msecs(jiffies - dmarx->last_jiffies)
+			> sup->dmarx.poll_timeout) {
+		uart_port_lock_irqsave(&sup->port, &flags);
+		lrw_uart_dma_rx_stop(sup);
+		sup->im |= UARTIMSC_RXIM;
+		lrw_uart_write(sup->im, sup, REG_IMSC);
+		uart_port_unlock_irqrestore(&sup->port, flags);
+
+		sup->dmarx.running = false;
+		dmaengine_terminate_all(rxchan);
+		timer_delete(&sup->dmarx.timer);
+	} else {
+		mod_timer(&sup->dmarx.timer,
+			  jiffies + msecs_to_jiffies(sup->dmarx.poll_rate));
+	}
+}
+
+static void lrw_uart_dma_startup(struct lrw_uart_port *sup)
+{
+	int ret;
+
+	if (!sup->dma_probed)
+		lrw_uart_dma_probe(sup);
+
+	if (!sup->dmatx.chan)
+		return;
+
+	sup->dmatx.buf = kmalloc(LRW_UART_DMA_BUFFER_SIZE, GFP_KERNEL | __GFP_DMA);
+	if (!sup->dmatx.buf) {
+		sup->port.fifosize = sup->fifosize;
+		return;
+	}
+
+	sup->dmatx.len = LRW_UART_DMA_BUFFER_SIZE;
+
+	/* The DMA buffer is now the FIFO the TTY subsystem can use */
+	sup->port.fifosize = LRW_UART_DMA_BUFFER_SIZE;
+	sup->using_tx_dma = true;
+
+	if (!sup->dmarx.chan)
+		goto skip_rx;
+
+	/* Allocate and map DMA RX buffers */
+	ret = lrw_uart_dmabuf_init(sup->dmarx.chan, &sup->dmarx.dbuf_a,
+				  DMA_FROM_DEVICE);
+	if (ret) {
+		dev_err(sup->port.dev, "failed to init DMA %s: %d\n",
+			"RX buffer A", ret);
+		goto skip_rx;
+	}
+
+	ret = lrw_uart_dmabuf_init(sup->dmarx.chan, &sup->dmarx.dbuf_b,
+				  DMA_FROM_DEVICE);
+	if (ret) {
+		dev_err(sup->port.dev, "failed to init DMA %s: %d\n",
+			"RX buffer B", ret);
+		lrw_uart_dmabuf_free(sup->dmarx.chan, &sup->dmarx.dbuf_a,
+				    DMA_FROM_DEVICE);
+		goto skip_rx;
+	}
+
+	sup->using_rx_dma = true;
+
+skip_rx:
+	/* Turn on DMA error (RX/TX will be enabled on demand) */
+	sup->dmacr |= UARTFCCR_DMAONERR;
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+
+	if (sup->using_rx_dma) {
+		if (lrw_uart_dma_rx_trigger_dma(sup))
+			dev_dbg(sup->port.dev,
+				"could not trigger initial RX DMA job, fall back to interrupt mode\n");
+		if (sup->dmarx.poll_rate) {
+			timer_setup(&sup->dmarx.timer, lrw_uart_dma_rx_poll, 0);
+			mod_timer(&sup->dmarx.timer,
+				  jiffies + msecs_to_jiffies(sup->dmarx.poll_rate));
+			sup->dmarx.last_residue = LRW_UART_DMA_BUFFER_SIZE;
+			sup->dmarx.last_jiffies = jiffies;
+		}
+	}
+}
+
+static void lrw_uart_dma_shutdown(struct lrw_uart_port *sup)
+{
+	if (!(sup->using_tx_dma || sup->using_rx_dma))
+		return;
+
+	/* Disable RX and TX DMA */
+	while (lrw_uart_read(sup, REG_FR) & sup->vendor->fr_busy)
+		cpu_relax();
+
+	uart_port_lock_irq(&sup->port);
+	sup->dmacr &= ~(UARTFCCR_DMAONERR | UARTFCCR_RXDMAE | UARTFCCR_TXDMAE);
+	lrw_uart_write(sup->dmacr, sup, REG_FCCR);
+	uart_port_unlock_irq(&sup->port);
+
+	if (sup->using_tx_dma) {
+		/* In theory, this should already be done by lrw_uart_dma_flush_buffer */
+		dmaengine_terminate_all(sup->dmatx.chan);
+		if (sup->dmatx.queued) {
+			dma_unmap_single(sup->dmatx.chan->device->dev,
+					 sup->dmatx.dma, sup->dmatx.len,
+					 DMA_TO_DEVICE);
+			sup->dmatx.queued = false;
+		}
+
+		kfree(sup->dmatx.buf);
+		sup->using_tx_dma = false;
+	}
+
+	if (sup->using_rx_dma) {
+		dmaengine_terminate_all(sup->dmarx.chan);
+		/* Clean up the RX DMA */
+		lrw_uart_dmabuf_free(sup->dmarx.chan, &sup->dmarx.dbuf_a, DMA_FROM_DEVICE);
+		lrw_uart_dmabuf_free(sup->dmarx.chan, &sup->dmarx.dbuf_b, DMA_FROM_DEVICE);
+		if (sup->dmarx.poll_rate)
+			timer_delete_sync(&sup->dmarx.timer);
+		sup->using_rx_dma = false;
+	}
+}
+
+static inline bool lrw_uart_dma_rx_available(struct lrw_uart_port *sup)
+{
+	return sup->using_rx_dma;
+}
+
+static inline bool lrw_uart_dma_rx_running(struct lrw_uart_port *sup)
+{
+	return sup->using_rx_dma && sup->dmarx.running;
+}
+
+#else
+/* Blank functions if the DMA engine is not available */
+static inline void lrw_uart_dma_remove(struct lrw_uart_port *sup)
+{
+}
+
+static inline void lrw_uart_dma_startup(struct lrw_uart_port *sup)
+{
+}
+
+static inline void lrw_uart_dma_shutdown(struct lrw_uart_port *sup)
+{
+}
+
+static inline bool lrw_uart_dma_tx_irq(struct lrw_uart_port *sup)
+{
+	return false;
+}
+
+static inline void lrw_uart_dma_tx_stop(struct lrw_uart_port *sup)
+{
+}
+
+static inline bool lrw_uart_dma_tx_start(struct lrw_uart_port *sup)
+{
+	return false;
+}
+
+static inline void lrw_uart_dma_rx_irq(struct lrw_uart_port *sup)
+{
+}
+
+static inline void lrw_uart_dma_rx_stop(struct lrw_uart_port *sup)
+{
+}
+
+static inline int lrw_uart_dma_rx_trigger_dma(struct lrw_uart_port *sup)
+{
+	return -EIO;
+}
+
+static inline bool lrw_uart_dma_rx_available(struct lrw_uart_port *sup)
+{
+	return false;
+}
+
+static inline bool lrw_uart_dma_rx_running(struct lrw_uart_port *sup)
+{
+	return false;
+}
+
+#define lrw_uart_dma_flush_buffer	NULL
+#endif
+
+static void lrw_uart_rs485_tx_stop(struct lrw_uart_port *sup)
+{
+	/*
+	 * To be on the safe side only time out after twice as many iterations
+	 * as fifo size.
+	 */
+	const int MAX_TX_DRAIN_ITERS = sup->port.fifosize * 2;
+	struct uart_port *port = &sup->port;
+	int i = 0;
+	u32 mcr, mcfg;
+
+	/* Wait until hardware tx queue is empty */
+	while (!lrw_uart_tx_empty(port)) {
+		if (i > MAX_TX_DRAIN_ITERS) {
+			dev_warn(port->dev,
+				 "timeout while draining hardware tx queue\n");
+			break;
+		}
+
+		udelay(sup->rs485_tx_drain_interval);
+		i++;
+	}
+
+	if (port->rs485.delay_rts_after_send)
+		mdelay(port->rs485.delay_rts_after_send);
+
+	mcr = lrw_uart_read(sup, REG_MCR);
+
+	if (port->rs485.flags & SER_RS485_RTS_AFTER_SEND)
+		mcr &= ~UARTMCR_RTS;
+	else
+		mcr |= UARTMCR_RTS;
+
+	lrw_uart_write(mcr, sup, REG_MCR);
+
+	/* Disable the transmitter and reenable the transceiver */
+	mcfg = lrw_uart_read(sup, REG_MCFG);
+	mcfg &= ~UARTMCFG_TXE;
+	mcfg |= UARTMCFG_RXE;
+	lrw_uart_write(mcfg, sup, REG_MCFG);
+
+	sup->rs485_tx_started = false;
+}
+
+static void lrw_uart_stop_tx(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	sup->im &= ~UARTIMSC_TXIM;
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+	lrw_uart_dma_tx_stop(sup);
+
+	if ((port->rs485.flags & SER_RS485_ENABLED) && sup->rs485_tx_started)
+		lrw_uart_rs485_tx_stop(sup);
+}
+
+static bool lrw_uart_tx_chars(struct lrw_uart_port *sup, bool from_irq);
+
+/* Start TX with programmed I/O only (no DMA) */
+static void lrw_uart_start_tx_pio(struct lrw_uart_port *sup)
+{
+	if (lrw_uart_tx_chars(sup, false)) {
+		sup->im |= UARTIMSC_TXIM;
+		lrw_uart_write(sup->im, sup, REG_IMSC);
+	}
+}
+
+static void lrw_uart_rs485_tx_start(struct lrw_uart_port *sup)
+{
+	struct uart_port *port = &sup->port;
+	u32 mcr, mcfg;
+
+	/* Enable transmitter */
+	mcfg = lrw_uart_read(sup, REG_MCFG);
+	mcfg |= UARTMCFG_TXE;
+
+	/* Disable receiver if half-duplex */
+	if (!(port->rs485.flags & SER_RS485_RX_DURING_TX))
+		mcfg &= ~UARTMCFG_RXE;
+
+	lrw_uart_write(mcfg, sup, REG_MCFG);
+
+	mcr = lrw_uart_read(sup, REG_MCR);
+	if (port->rs485.flags & SER_RS485_RTS_ON_SEND)
+		mcr &= ~UARTMCR_RTS;
+	else
+		mcr |= UARTMCR_RTS;
+
+	lrw_uart_write(mcr, sup, REG_MCR);
+
+	if (port->rs485.delay_rts_before_send)
+		mdelay(port->rs485.delay_rts_before_send);
+
+	sup->rs485_tx_started = true;
+}
+
+static void lrw_uart_start_tx(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	if ((sup->port.rs485.flags & SER_RS485_ENABLED) &&
+	    !sup->rs485_tx_started)
+		lrw_uart_rs485_tx_start(sup);
+
+	if (!lrw_uart_dma_tx_start(sup))
+		lrw_uart_start_tx_pio(sup);
+}
+
+static void lrw_uart_stop_rx(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	sup->im &= ~(UARTIMSC_RXIM | UARTIMSC_RTIM | UARTIMSC_FEIM |
+		     UARTIMSC_PEIM | UARTIMSC_BEIM | UARTIMSC_OEIM);
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+
+	lrw_uart_dma_rx_stop(sup);
+}
+
+static void lrw_uart_throttle_rx(struct uart_port *port)
+{
+	unsigned long flags;
+
+	uart_port_lock_irqsave(port, &flags);
+	lrw_uart_stop_rx(port);
+	uart_port_unlock_irqrestore(port, flags);
+}
+
+static void lrw_uart_enable_ms(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	sup->im |= UARTIMSC_RIMIM | UARTIMSC_CTSMIM | UARTIMSC_DCDMIM | UARTIMSC_DSRMIM;
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+}
+
+static void lrw_uart_rx_chars(struct lrw_uart_port *sup)
+__releases(&sup->port.lock)
+__acquires(&sup->port.lock)
+{
+	lrw_uart_fifo_to_tty(sup);
+
+	uart_port_unlock(&sup->port);
+	tty_flip_buffer_push(&sup->port.state->port);
+	/*
+	 * If we were temporarily out of DMA mode for a while,
+	 * attempt to switch back to DMA mode again.
+	 */
+	if (lrw_uart_dma_rx_available(sup)) {
+		if (lrw_uart_dma_rx_trigger_dma(sup)) {
+			dev_dbg(sup->port.dev,
+				"could not trigger RX DMA job fall back to interrupt mode again\n");
+			sup->im |= UARTIMSC_RXIM;
+			lrw_uart_write(sup->im, sup, REG_IMSC);
+		} else {
+#ifdef CONFIG_DMA_ENGINE
+			/* Start Rx DMA poll */
+			if (sup->dmarx.poll_rate) {
+				sup->dmarx.last_jiffies = jiffies;
+				sup->dmarx.last_residue	= LRW_UART_DMA_BUFFER_SIZE;
+				mod_timer(&sup->dmarx.timer,
+					  jiffies + msecs_to_jiffies(sup->dmarx.poll_rate));
+			}
+#endif
+		}
+	}
+	uart_port_lock(&sup->port);
+}
+
+static bool lrw_uart_tx_char(struct lrw_uart_port *sup, unsigned char c,
+			    bool from_irq)
+{
+	if (unlikely(!from_irq) &&
+	    lrw_uart_read(sup, REG_FR) & UARTFR_TXFF)
+		return false; /* unable to transmit character */
+
+	lrw_uart_write(c, sup, REG_DR);
+	sup->port.icount.tx++;
+
+	return true;
+}
+
+/* Returns true if tx interrupts have to be (kept) enabled  */
+static bool lrw_uart_tx_chars(struct lrw_uart_port *sup, bool from_irq)
+{
+	struct tty_port *tport = &sup->port.state->port;
+	int count = sup->fifosize >> 1;
+
+	if (sup->port.x_char) {
+		if (!lrw_uart_tx_char(sup, sup->port.x_char, from_irq))
+			return true;
+		sup->port.x_char = 0;
+		--count;
+	}
+	if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(&sup->port)) {
+		lrw_uart_stop_tx(&sup->port);
+		return false;
+	}
+
+	/* If we are using DMA mode, try to send some characters. */
+	if (lrw_uart_dma_tx_irq(sup))
+		return true;
+
+	while (1) {
+		unsigned char c;
+
+		if (likely(from_irq) && count-- == 0)
+			break;
+
+		if (!kfifo_peek(&tport->xmit_fifo, &c))
+			break;
+
+		if (!lrw_uart_tx_char(sup, c, from_irq))
+			break;
+
+		kfifo_skip(&tport->xmit_fifo);
+	}
+
+	if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
+		uart_write_wakeup(&sup->port);
+
+	if (kfifo_is_empty(&tport->xmit_fifo)) {
+		lrw_uart_stop_tx(&sup->port);
+		return false;
+	}
+	return true;
+}
+
+static void lrw_uart_modem_status(struct lrw_uart_port *sup)
+{
+	unsigned int status, delta;
+
+	status = lrw_uart_read(sup, REG_FR) & UARTFR_MODEM_ANY;
+
+	delta = status ^ sup->old_status;
+	sup->old_status = status;
+
+	if (!delta)
+		return;
+
+	if (delta & UARTFR_DCD)
+		uart_handle_dcd_change(&sup->port, status & UARTFR_DCD);
+
+	if (delta & sup->vendor->fr_dsr)
+		sup->port.icount.dsr++;
+
+	if (delta & sup->vendor->fr_cts)
+		uart_handle_cts_change(&sup->port,
+				       status & sup->vendor->fr_cts);
+
+	wake_up_interruptible(&sup->port.state->port.delta_msr_wait);
+}
+
+static void check_apply_cts_event_workaround(struct lrw_uart_port *sup)
+{
+	if (!sup->vendor->cts_event_workaround)
+		return;
+
+	/* workaround to make sure that all bits are unlocked.. */
+	lrw_uart_write(0x00, sup, REG_ICR);
+
+	/*
+	 * WA: introduce 26ns(1 uart clk) delay before W1C;
+	 * single apb access will incur 2 pclk(133.12Mhz) delay,
+	 * so add 2 dummy reads
+	 */
+	lrw_uart_read(sup, REG_ICR);
+	lrw_uart_read(sup, REG_ICR);
+}
+
+static irqreturn_t lrw_uart_int(int irq, void *dev_id)
+{
+	struct lrw_uart_port *sup = dev_id;
+	unsigned int status, pass_counter = ISR_PASS_LIMIT;
+	int handled = 0;
+
+	uart_port_lock(&sup->port);
+	status = lrw_uart_read(sup, REG_RIS) & sup->im;
+	if (status) {
+		do {
+			check_apply_cts_event_workaround(sup);
+
+			lrw_uart_write(status & ~(UARTICR_TXIC | UARTICR_RTIC | UARTICR_RXIC),
+				      sup, REG_ICR);
+
+			if (status & (UARTICR_RTIC | UARTICR_RXIC)) {
+				if (lrw_uart_dma_rx_running(sup))
+					lrw_uart_dma_rx_irq(sup);
+				else
+					lrw_uart_rx_chars(sup);
+			}
+			if (status & (UARTICR_DSRMIC | UARTICR_DCDMIC |
+				      UARTICR_CTSMIC | UARTICR_RIMIC))
+				lrw_uart_modem_status(sup);
+			if (status & UARTICR_TXIC)
+				lrw_uart_tx_chars(sup, true);
+
+			if (pass_counter-- == 0)
+				break;
+
+			status = lrw_uart_read(sup, REG_RIS) & sup->im;
+		} while (status != 0);
+		handled = 1;
+	}
+
+	uart_unlock_and_check_sysrq(&sup->port);
+
+	return IRQ_RETVAL(handled);
+}
+
+static unsigned int lrw_uart_tx_empty(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	/* Allow feature register bits to be inverted to work around errata */
+	unsigned int status = lrw_uart_read(sup, REG_FR) ^ sup->vendor->inv_fr;
+
+	return status & (sup->vendor->fr_busy | UARTFR_TXFF) ?
+							0 : TIOCSER_TEMT;
+}
+
+static void lrw_uart_maybe_set_bit(bool cond, unsigned int *ptr, unsigned int mask)
+{
+	if (cond)
+		*ptr |= mask;
+}
+
+static unsigned int lrw_uart_get_mctrl(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	unsigned int result = 0;
+	unsigned int status = lrw_uart_read(sup, REG_FR);
+
+	lrw_uart_maybe_set_bit(status & UARTFR_DCD, &result, TIOCM_CAR);
+	lrw_uart_maybe_set_bit(status & sup->vendor->fr_dsr, &result, TIOCM_DSR);
+	lrw_uart_maybe_set_bit(status & sup->vendor->fr_cts, &result, TIOCM_CTS);
+	lrw_uart_maybe_set_bit(status & sup->vendor->fr_ri, &result, TIOCM_RNG);
+
+	return result;
+}
+
+static void lrw_uart_assign_bit(bool cond, unsigned int *ptr, unsigned int mask)
+{
+	if (cond)
+		*ptr |= mask;
+	else
+		*ptr &= ~mask;
+}
+
+static void lrw_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	unsigned int mcr;
+	unsigned int mcfg;
+	unsigned int fccr;
+
+	mcr = lrw_uart_read(sup, REG_MCR);
+	mcfg = lrw_uart_read(sup, REG_MCFG);
+	fccr = lrw_uart_read(sup, REG_FCCR);
+
+	lrw_uart_assign_bit(mctrl & TIOCM_RTS, &mcr, UARTMCR_RTS);
+	lrw_uart_assign_bit(mctrl & TIOCM_DTR, &mcr, UARTMCR_DTR);
+	lrw_uart_assign_bit(mctrl & TIOCM_OUT1, &mcr, UARTMCR_OUT1);
+	lrw_uart_assign_bit(mctrl & TIOCM_OUT2, &mcr, UARTMCR_OUT2);
+	lrw_uart_assign_bit(mctrl & TIOCM_LOOP, &mcfg, UARTMCFG_LBE);
+
+	if (port->status & UPSTAT_AUTORTS) {
+		/* We need to disable auto-RTS if we want to turn RTS off */
+		lrw_uart_assign_bit(mctrl & TIOCM_RTS, &fccr, UARTFCCR_RTSEN);
+	}
+
+	lrw_uart_write(mcr, sup, REG_MCR);
+	lrw_uart_write(mcfg, sup, REG_MCFG);
+	lrw_uart_write(fccr, sup, REG_FCCR);
+}
+
+static void lrw_uart_break_ctl(struct uart_port *port, int break_state)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	unsigned long flags;
+	unsigned int mcfg;
+
+	uart_port_lock_irqsave(&sup->port, &flags);
+	mcfg = lrw_uart_read(sup, REG_MCFG);
+	if (break_state == -1)
+		mcfg |= UARTMCFG_BRK;
+	else
+		mcfg &= ~UARTMCFG_BRK;
+	lrw_uart_write(mcfg, sup, REG_MCFG);
+	uart_port_unlock_irqrestore(&sup->port, flags);
+}
+
+#ifdef CONFIG_CONSOLE_POLL
+
+static void lrw_uart_quiesce_irqs(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	lrw_uart_write(lrw_uart_read(sup, REG_MIS), sup, REG_ICR);
+	/*
+	 * There is no way to clear TXIM as this is "ready to transmit IRQ", so
+	 * we simply mask it. start_tx() will unmask it.
+	 *
+	 * Note we can race with start_tx(), and if the race happens, the
+	 * polling user might get another interrupt just after we clear it.
+	 * But it should be OK and can happen even w/o the race, e.g.
+	 * controller immediately got some new data and raised the IRQ.
+	 *
+	 * And whoever uses polling routines assumes that it manages the device
+	 * (including tx queue), so we're also fine with start_tx()'s caller
+	 * side.
+	 */
+	lrw_uart_write(lrw_uart_read(sup, REG_IMSC) & ~UARTIMSC_TXIM, sup,
+		    REG_IMSC);
+}
+
+static int lrw_uart_get_poll_char(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	unsigned int status;
+
+	/*
+	 * The caller might need IRQs lowered, e.g. if used with KDB NMI
+	 * debugger.
+	 */
+	lrw_uart_quiesce_irqs(port);
+
+	status = lrw_uart_read(sup, REG_FR);
+	if (status & UARTFR_RXFE)
+		return NO_POLL_CHAR;
+
+	return lrw_uart_read(sup, REG_DR);
+}
+
+static void lrw_uart_put_poll_char(struct uart_port *port, unsigned char ch)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	while (lrw_uart_read(sup, REG_FR) & UARTFR_TXFF)
+		cpu_relax();
+
+	lrw_uart_write(ch, sup, REG_DR);
+}
+
+#endif /* CONFIG_CONSOLE_POLL */
+
+static int lrw_uart_hwinit(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	int retval;
+	unsigned int clk;
+
+	/* Optionaly enable pins to be muxed in and configured */
+	pinctrl_pm_select_default_state(port->dev);
+
+	/*
+	 * Try to enable the clock producer.
+	 */
+	retval = clk_prepare_enable(sup->clk);
+	if (retval)
+		return retval;
+
+	if (has_acpi_companion(sup->port.dev)) {
+		device_property_read_u32(sup->port.dev, "clock-frequency", &clk);
+		sup->port.uartclk = clk;
+	} else {
+		sup->port.uartclk = clk_get_rate(sup->clk);
+	}
+
+	/* Clear pending error and receive interrupts */
+	lrw_uart_write(UARTICR_OEIC | UARTICR_BEIC | UARTICR_PEIC |
+		      UARTICR_FEIC | UARTICR_RTIC | UARTICR_RXIC,
+		      sup, REG_ICR);
+
+	/*
+	 * Save interrupts enable mask, and enable RX interrupts in case if
+	 * the interrupt is used for NMI entry.
+	 */
+	sup->im = lrw_uart_read(sup, REG_IMSC);
+	lrw_uart_write(UARTIMSC_RTIM | UARTIMSC_RXIM, sup, REG_IMSC);
+
+	if (dev_get_platdata(sup->port.dev)) {
+		struct lrw_uart_data *plat;
+
+		plat = dev_get_platdata(sup->port.dev);
+		if (plat->init)
+			plat->init();
+	}
+	return 0;
+}
+
+static int lrw_uart_allocate_irq(struct lrw_uart_port *sup)
+{
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+
+	return request_irq(sup->port.irq, lrw_uart_int, IRQF_SHARED, "lrw-uart", sup);
+}
+
+/*
+ * Enable interrupts, only timeouts when using DMA
+ * if initial RX DMA job failed, start in interrupt mode
+ * as well.
+ */
+static void lrw_uart_enable_interrupts(struct lrw_uart_port *sup)
+{
+	unsigned long flags;
+	unsigned int i;
+
+	uart_port_lock_irqsave(&sup->port, &flags);
+
+	/* Clear out any spuriously appearing RX interrupts */
+	lrw_uart_write(UARTICR_RTIC | UARTICR_RXIC, sup, REG_ICR);
+
+	/*
+	 * RXIS is asserted only when the RX FIFO transitions from below
+	 * to above the trigger threshold.  If the RX FIFO is already
+	 * full to the threshold this can't happen and RXIS will now be
+	 * stuck off.  Drain the RX FIFO explicitly to fix this:
+	 */
+	for (i = 0; i < sup->fifosize * 2; ++i) {
+		if (lrw_uart_read(sup, REG_FR) & UARTFR_RXFE)
+			break;
+
+		lrw_uart_read(sup, REG_DR);
+	}
+
+	sup->im = UARTIMSC_RTIM;
+	if (!lrw_uart_dma_rx_running(sup))
+		sup->im |= UARTIMSC_RXIM;
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+	uart_port_unlock_irqrestore(&sup->port, flags);
+}
+
+static void lrw_uart_unthrottle_rx(struct uart_port *port)
+{
+	struct lrw_uart_port *sup = container_of(port, struct lrw_uart_port, port);
+	unsigned long flags;
+
+	uart_port_lock_irqsave(&sup->port, &flags);
+
+	sup->im = UARTIMSC_RTIM;
+	if (!lrw_uart_dma_rx_running(sup))
+		sup->im |= UARTIMSC_RXIM;
+
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+
+	uart_port_unlock_irqrestore(&sup->port, flags);
+}
+
+static int lrw_uart_startup(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	unsigned int mcr;
+	unsigned int mcfg;
+	int retval;
+
+	retval = lrw_uart_hwinit(port);
+	if (retval)
+		goto clk_dis;
+
+	retval = lrw_uart_allocate_irq(sup);
+	if (retval)
+		goto clk_dis;
+
+	lrw_uart_write(sup->vendor->fcr, sup, REG_FCR);
+
+	uart_port_lock_irq(&sup->port);
+
+	mcr = lrw_uart_read(sup, REG_MCR);
+	mcr &= UARTMCR_RTS | UARTMCR_DTR;
+
+	lrw_uart_write(mcr, sup, REG_MCR);
+
+	mcfg = lrw_uart_read(sup, REG_MCFG);
+
+	mcfg |= UARTMCFG_UARTEN | UARTMCFG_RXE;
+
+	if (!(port->rs485.flags & SER_RS485_ENABLED))
+		mcfg |= UARTMCFG_TXE;
+
+	lrw_uart_write(mcfg, sup, REG_MCFG);
+
+	uart_port_unlock_irq(&sup->port);
+
+	/*
+	 * initialise the old status of the modem signals
+	 */
+	sup->old_status = lrw_uart_read(sup, REG_FR) & UARTFR_MODEM_ANY;
+
+	/* Startup DMA */
+	lrw_uart_dma_startup(sup);
+
+	lrw_uart_enable_interrupts(sup);
+
+	return 0;
+
+ clk_dis:
+	clk_disable_unprepare(sup->clk);
+	return retval;
+}
+
+static void lrw_uart_shutdown_channel(struct lrw_uart_port *sup,
+	unsigned int mcfg, unsigned int fcr)
+{
+	unsigned long val;
+
+	val = lrw_uart_read(sup, mcfg);
+	val &= ~(UARTMCFG_BRK);
+	lrw_uart_write(val, sup, mcfg);
+
+	val = lrw_uart_read(sup, fcr);
+	val &= ~(UARTFCR_FEN);
+	lrw_uart_write(val, sup, fcr);
+}
+
+/*
+ * disable the port. It should not disable RTS and DTR.
+ * Also RTS and DTR state should be preserved to restore
+ * it during startup().
+ */
+static void lrw_uart_disable_uart(struct lrw_uart_port *sup)
+{
+	unsigned int mcr;
+	unsigned int mcfg;
+
+	sup->port.status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS);
+	uart_port_lock_irq(&sup->port);
+	mcr = lrw_uart_read(sup, REG_MCR);
+	mcr &= UARTMCR_RTS | UARTMCR_DTR;
+	lrw_uart_write(mcr, sup, REG_MCR);
+
+	mcfg = lrw_uart_read(sup, REG_MCFG);
+	mcfg |= UARTMCFG_UARTEN | UARTMCFG_TXE;
+	lrw_uart_write(mcfg, sup, REG_MCFG);
+	uart_port_unlock_irq(&sup->port);
+
+	/*
+	 * disable break condition and fifos
+	 */
+	lrw_uart_shutdown_channel(sup, REG_MCFG, REG_FCR);
+}
+
+static void lrw_uart_disable_interrupts(struct lrw_uart_port *sup)
+{
+	uart_port_lock_irq(&sup->port);
+
+	/* mask all interrupts and clear all pending ones */
+	sup->im = 0;
+	lrw_uart_write(sup->im, sup, REG_IMSC);
+	lrw_uart_write(0xffff, sup, REG_ICR);
+
+	uart_port_unlock_irq(&sup->port);
+}
+
+static void lrw_uart_shutdown(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+		container_of(port, struct lrw_uart_port, port);
+
+	lrw_uart_disable_interrupts(sup);
+
+	lrw_uart_dma_shutdown(sup);
+
+	if ((port->rs485.flags & SER_RS485_ENABLED) && sup->rs485_tx_started)
+		lrw_uart_rs485_tx_stop(sup);
+
+	free_irq(sup->port.irq, sup);
+
+	lrw_uart_disable_uart(sup);
+
+	/*
+	 * Shut down the clock producer
+	 */
+	clk_disable_unprepare(sup->clk);
+	/* Optionally let pins go into sleep states */
+	pinctrl_pm_select_sleep_state(port->dev);
+
+	if (dev_get_platdata(sup->port.dev)) {
+		struct lrw_uart_data *plat;
+
+		plat = dev_get_platdata(sup->port.dev);
+		if (plat->exit)
+			plat->exit();
+	}
+
+	if (sup->port.ops->flush_buffer)
+		sup->port.ops->flush_buffer(port);
+}
+
+static void
+lrw_uart_setup_status_masks(struct uart_port *port, struct ktermios *termios)
+{
+	port->read_status_mask = UARTDR_OE | 255;
+	if (termios->c_iflag & INPCK)
+		port->read_status_mask |= UARTDR_FE | UARTDR_PE;
+	if (termios->c_iflag & (IGNBRK | BRKINT | PARMRK))
+		port->read_status_mask |= UARTDR_BE;
+
+	/*
+	 * Characters to ignore
+	 */
+	port->ignore_status_mask = 0;
+	if (termios->c_iflag & IGNPAR)
+		port->ignore_status_mask |= UARTDR_FE | UARTDR_PE;
+	if (termios->c_iflag & IGNBRK) {
+		port->ignore_status_mask |= UARTDR_BE;
+		/*
+		 * If we're ignoring parity and break indicators,
+		 * ignore overruns too (for real raw support).
+		 */
+		if (termios->c_iflag & IGNPAR)
+			port->ignore_status_mask |= UARTDR_OE;
+	}
+
+	/*
+	 * Ignore all characters if CREAD is not set.
+	 */
+	if ((termios->c_cflag & CREAD) == 0)
+		port->ignore_status_mask |= UART_DUMMY_DR_RX;
+}
+
+static void
+lrw_uart_set_termios(struct uart_port *port, struct ktermios *termios,
+		     const struct ktermios *old)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	unsigned int frcr;
+	unsigned int mcr, fccr;
+	unsigned int mcfg;
+	unsigned long flags;
+	unsigned int baud, quot, clkdiv;
+	unsigned int bits;
+	unsigned int clk;
+
+	if (sup->vendor->oversampling)
+		clkdiv = 8;
+	else
+		clkdiv = 16;
+
+	if (has_acpi_companion(sup->port.dev)) {
+		device_property_read_u32(sup->port.dev, "clock-frequency", &clk);
+		sup->port.uartclk = clk;
+	}
+
+	/*
+	 * Ask the core to calculate the divisor for us.
+	 */
+	baud = uart_get_baud_rate(port, termios, old, 0,
+				  port->uartclk / clkdiv);
+
+#ifdef CONFIG_DMA_ENGINE
+	/*
+	 * Adjust RX DMA polling rate with baud rate if not specified.
+	 */
+	if (sup->dmarx.auto_poll_rate)
+		sup->dmarx.poll_rate = DIV_ROUND_UP(10000000, baud);
+#endif
+
+	if (baud > port->uartclk / 16)
+		quot = DIV_ROUND_CLOSEST(port->uartclk * 8, baud);
+	else
+		quot = DIV_ROUND_CLOSEST(port->uartclk * 4, baud);
+
+	switch (termios->c_cflag & CSIZE) {
+	case CS5:
+		frcr = UARTFRCR_WLEN_5;
+		break;
+	case CS6:
+		frcr = UARTFRCR_WLEN_6;
+		break;
+	case CS7:
+		frcr = UARTFRCR_WLEN_7;
+		break;
+	default: // CS8
+		frcr = UARTFRCR_WLEN_8;
+		break;
+	}
+
+	if (termios->c_cflag & CSTOPB)
+		frcr |= UARTFRCR_STP2;
+	if (termios->c_cflag & PARENB) {
+		frcr |= UARTFRCR_PEN;
+		if (!(termios->c_cflag & PARODD))
+			frcr |= UARTFRCR_EOP;
+		if (termios->c_cflag & CMSPAR)
+			frcr |= UARTFRCR_SPS;
+	}
+
+
+	bits = tty_get_frame_size(termios->c_cflag);
+
+	uart_port_lock_irqsave(port, &flags);
+
+	/*
+	 * Update the per-port timeout.
+	 */
+	uart_update_timeout(port, termios->c_cflag, baud);
+
+	/*
+	 * Calculate the approximated time it takes to transmit one character
+	 * with the given baud rate. We use this as the poll interval when we
+	 * wait for the tx queue to empty.
+	 */
+	sup->rs485_tx_drain_interval = DIV_ROUND_UP(bits * 1000 * 1000, baud);
+
+	lrw_uart_setup_status_masks(port, termios);
+
+	if (UART_ENABLE_MS(port, termios->c_cflag))
+		lrw_uart_enable_ms(port);
+
+	if (port->rs485.flags & SER_RS485_ENABLED)
+		termios->c_cflag &= ~CRTSCTS;
+
+	mcr = lrw_uart_read(sup, REG_MCR);
+	mcfg = lrw_uart_read(sup, REG_MCFG);
+	fccr = lrw_uart_read(sup, REG_FCCR);
+
+	if (termios->c_cflag & CRTSCTS) {
+		if (mcr & UARTMCR_RTS)
+			fccr |= UARTFCCR_RTSEN;
+
+		fccr |= UARTFCCR_CTSEN;
+		port->status |= UPSTAT_AUTOCTS | UPSTAT_AUTORTS;
+	} else {
+		fccr &= ~(UARTFCCR_CTSEN | UARTFCCR_RTSEN);
+		port->status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS);
+	}
+
+	/* Set baud rate */
+	lrw_uart_write(quot & 0x3f, sup, REG_FD);
+	lrw_uart_write(quot >> 6, sup, REG_IND);
+
+	/*
+	 * ----------v----------v----------v----------v-----
+	 * NOTE: REG_FRCR MUST BE WRITTEN AFTER REG_FD & REG_IND.
+	 * ----------^----------^----------^----------^-----
+	 */
+	lrw_uart_write(frcr, sup, REG_FRCR);
+
+	lrw_uart_write(fccr, sup, REG_FCCR);
+
+	/*
+	 * Receive was disabled by lrw_uart_disable_uart during shutdown.
+	 * Need to reenable receive if you need to use a tty_driver
+	 * returns from tty_find_polling_driver() after a port shutdown.
+	 */
+	mcfg |= UARTMCFG_RXE;
+	lrw_uart_write(mcfg, sup, REG_MCFG);
+
+	uart_port_unlock_irqrestore(port, flags);
+}
+
+static const char *lrw_uart_type(struct uart_port *port)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+	return sup->port.type == PORT_LRW ? sup->type : NULL;
+}
+
+/*
+ * Configure/autoconfigure the port.
+ */
+static void lrw_uart_config_port(struct uart_port *port, int flags)
+{
+	if (flags & UART_CONFIG_TYPE)
+		port->type = PORT_LRW;
+}
+
+/*
+ * verify the new serial_struct (for TIOCSSERIAL).
+ */
+static int lrw_uart_verify_port(struct uart_port *port, struct serial_struct *ser)
+{
+	int ret = 0;
+
+	if (ser->type != PORT_UNKNOWN && ser->type != PORT_LRW)
+		ret = -EINVAL;
+	if (ser->irq < 0 || ser->irq >= irq_get_nr_irqs())
+		ret = -EINVAL;
+	if (ser->baud_base < 9600)
+		ret = -EINVAL;
+	if (port->mapbase != (unsigned long)ser->iomem_base)
+		ret = -EINVAL;
+	return ret;
+}
+
+static int lrw_uart_rs485_config(struct uart_port *port, struct ktermios *termios,
+			      struct serial_rs485 *rs485)
+{
+	struct lrw_uart_port *sup =
+		container_of(port, struct lrw_uart_port, port);
+
+	if (port->rs485.flags & SER_RS485_ENABLED)
+		lrw_uart_rs485_tx_stop(sup);
+
+	/* Make sure auto RTS is disabled */
+	if (rs485->flags & SER_RS485_ENABLED) {
+		u32 fccr = lrw_uart_read(sup, REG_FCCR);
+
+		fccr &= ~UARTFCCR_RTSEN;
+		lrw_uart_write(fccr, sup, REG_FCCR);
+		port->status &= ~UPSTAT_AUTORTS;
+	}
+
+	return 0;
+}
+
+static const struct uart_ops lrw_uart_pops = {
+	.tx_empty	= lrw_uart_tx_empty,
+	.set_mctrl	= lrw_uart_set_mctrl,
+	.get_mctrl	= lrw_uart_get_mctrl,
+	.stop_tx	= lrw_uart_stop_tx,
+	.start_tx	= lrw_uart_start_tx,
+	.stop_rx	= lrw_uart_stop_rx,
+	.throttle	= lrw_uart_throttle_rx,
+	.unthrottle	= lrw_uart_unthrottle_rx,
+	.enable_ms	= lrw_uart_enable_ms,
+	.break_ctl	= lrw_uart_break_ctl,
+	.startup	= lrw_uart_startup,
+	.shutdown	= lrw_uart_shutdown,
+	.flush_buffer	= lrw_uart_dma_flush_buffer,
+	.set_termios	= lrw_uart_set_termios,
+	.type		= lrw_uart_type,
+	.config_port	= lrw_uart_config_port,
+	.verify_port	= lrw_uart_verify_port,
+#ifdef CONFIG_CONSOLE_POLL
+	.poll_init     = lrw_uart_hwinit,
+	.poll_get_char = lrw_uart_get_poll_char,
+	.poll_put_char = lrw_uart_put_poll_char,
+#endif
+};
+
+static struct lrw_uart_port *lrw_uart_console_ports[UART_NR];
+
+#ifdef CONFIG_SERIAL_LRW_UART_CONSOLE
+
+static void lrw_uart_console_putchar(struct uart_port *port, unsigned char ch)
+{
+	struct lrw_uart_port *sup =
+	    container_of(port, struct lrw_uart_port, port);
+
+	while (lrw_uart_read(sup, REG_FR) & UARTFR_TXFF)
+		cpu_relax();
+	lrw_uart_write(ch, sup, REG_DR);
+}
+
+static void
+lrw_uart_console_write(struct console *co, const char *s, unsigned int count)
+{
+	struct lrw_uart_port *sup = lrw_uart_console_ports[co->index];
+	unsigned int old_fccr = 0, new_fccr;
+	unsigned int old_mcfg = 0, new_mcfg;
+	unsigned long flags;
+	int locked = 1;
+
+	clk_enable(sup->clk);
+
+	if (oops_in_progress)
+		locked = uart_port_trylock_irqsave(&sup->port, &flags);
+	else
+		uart_port_lock_irqsave(&sup->port, &flags);
+
+	/*
+	 *	First save the FCCR then disable the interrupts
+	 */
+	if (!sup->vendor->always_enabled) {
+		old_fccr = lrw_uart_read(sup, REG_FCCR);
+		new_fccr = old_fccr & ~UARTFCCR_CTSEN;
+		lrw_uart_write(new_fccr, sup, REG_FCCR);
+
+		old_mcfg = lrw_uart_read(sup, REG_MCFG);
+		new_mcfg |= UARTMCFG_UARTEN | UARTMCFG_TXE;
+		lrw_uart_write(new_mcfg, sup, REG_MCFG);
+	}
+
+	uart_console_write(&sup->port, s, count, lrw_uart_console_putchar);
+
+	/*
+	 *	Finally, wait for transmitter to become empty and restore the
+	 *	TCR. Allow feature register bits to be inverted to work around
+	 *	errata.
+	 */
+	while ((lrw_uart_read(sup, REG_FR) ^ sup->vendor->inv_fr)
+						& sup->vendor->fr_busy)
+		cpu_relax();
+	if (!sup->vendor->always_enabled) {
+		lrw_uart_write(old_fccr, sup, REG_FCCR);
+		lrw_uart_write(old_mcfg, sup, REG_MCFG);
+	}
+
+	if (locked)
+		uart_port_unlock_irqrestore(&sup->port, flags);
+
+	clk_disable(sup->clk);
+}
+
+static void lrw_uart_console_get_options(struct lrw_uart_port *sup, int *baud,
+					 int *parity, int *bits)
+{
+	unsigned int frcr, ind, fd;
+
+	if (!(lrw_uart_read(sup, REG_MCFG) & UARTMCFG_UARTEN))
+		return;
+
+	frcr = lrw_uart_read(sup, REG_FRCR);
+
+	*parity = 'n';
+	if (frcr & UARTFRCR_PEN) {
+		if (frcr & UARTFRCR_EOP)
+			*parity = 'e';
+		else
+			*parity = 'o';
+	}
+
+	if ((frcr & 0x3) == UARTFRCR_WLEN_7)
+		*bits = 7;
+	else
+		*bits = 8;
+
+	ind = lrw_uart_read(sup, REG_IND);
+	fd = lrw_uart_read(sup, REG_FD);
+
+	*baud = sup->port.uartclk * 4 / (64 * ind + fd);
+}
+
+static int lrw_uart_console_setup(struct console *co, char *options)
+{
+	struct lrw_uart_port *sup;
+	int baud = 38400;
+	int bits = 8;
+	int parity = 'n';
+	int flow = 'n';
+	int ret;
+	unsigned int clk;
+
+	/*
+	 * Check whether an invalid uart number has been specified, and
+	 * if so, search for the first available port that does have
+	 * console support.
+	 */
+	if (co->index >= UART_NR)
+		co->index = 0;
+	sup = lrw_uart_console_ports[co->index];
+	if (!sup)
+		return -ENODEV;
+
+	/* Allow pins to be muxed in and configured */
+	pinctrl_pm_select_default_state(sup->port.dev);
+
+	ret = clk_prepare(sup->clk);
+	if (ret)
+		return ret;
+
+	if (dev_get_platdata(sup->port.dev)) {
+		struct lrw_uart_data *plat;
+
+		plat = dev_get_platdata(sup->port.dev);
+		if (plat->init)
+			plat->init();
+	}
+
+	if (has_acpi_companion(sup->port.dev)) {
+		device_property_read_u32(sup->port.dev, "clock-frequency", &clk);
+		sup->port.uartclk = clk;
+	} else {
+		sup->port.uartclk = clk_get_rate(sup->clk);
+	}
+
+	if (sup->vendor->fixed_options) {
+		baud = sup->fixed_baud;
+	} else {
+		if (options)
+			uart_parse_options(options,
+					   &baud, &parity, &bits, &flow);
+		else
+			lrw_uart_console_get_options(sup, &baud, &parity, &bits);
+	}
+
+	return uart_set_options(&sup->port, co, baud, parity, bits, flow);
+}
+
+/**
+ *	lrw_uart_console_match - non-standard console matching
+ *	@co:	  registering console
+ *	@name:	  name from console command line
+ *	@idx:	  index from console command line
+ *	@options: ptr to option string from console command line
+ *
+ *	Only attempts to match console command lines of the form:
+ *	    console=lrw_uart,mmio|mmio32,<addr>[,<options>]
+ *	    console=lrw_uart,0x<addr>[,<options>]
+ *	This form is used to register an initial earlycon boot console and
+ *	replace it with the lrw_uart_console at lrw_uart driver init.
+ *
+ *	Performs console setup for a match (as required by interface)
+ *	If no <options> are specified, then assume the h/w is already setup.
+ *
+ *	Returns 0 if console matches; otherwise non-zero to use default matching
+ */
+static int lrw_uart_console_match(struct console *co, char *name, int idx,
+				  char *options)
+{
+	enum uart_iotype iotype;
+	resource_size_t addr;
+	int i;
+
+	if (strcmp(name, "lrw_uart") != 0)
+		return -ENODEV;
+
+	if (uart_parse_earlycon(options, &iotype, &addr, &options))
+		return -ENODEV;
+
+	if (iotype != UPIO_MEM && iotype != UPIO_MEM32)
+		return -ENODEV;
+
+	/* try to match the port specified on the command line */
+	for (i = 0; i < ARRAY_SIZE(lrw_uart_console_ports); i++) {
+		struct uart_port *port;
+
+		if (!lrw_uart_console_ports[i])
+			continue;
+
+		port = &lrw_uart_console_ports[i]->port;
+
+		if (port->mapbase != addr)
+			continue;
+
+		co->index = i;
+		uart_port_set_cons(port, co);
+		return lrw_uart_console_setup(co, options);
+	}
+
+	return -ENODEV;
+}
+
+static struct uart_driver lrw_uart_driver;
+
+static struct console lrw_uart_console = {
+	.name		= LRW_UART_TTY_PREFIX,
+	.write		= lrw_uart_console_write,
+	.device		= uart_console_device,
+	.setup		= lrw_uart_console_setup,
+	.match		= lrw_uart_console_match,
+	.flags		= CON_PRINTBUFFER | CON_ANYTIME,
+	.index		= -1,
+	.data		= &lrw_uart_driver,
+};
+
+#define LRW_UART_CONSOLE	(&lrw_uart_console)
+
+static void lrw_uart_putc(struct uart_port *port, unsigned char c)
+{
+	while (readl(port->membase + UARTFR) & UARTFR_TXFF)
+		cpu_relax();
+	if (port->iotype == UPIO_MEM32)
+		writel(c, port->membase + UARTDR);
+	else
+		writeb(c, port->membase + UARTDR);
+	while (readl(port->membase + UARTFR) & UARTFR_BUSY)
+		cpu_relax();
+}
+
+static void lrw_uart_early_write(struct console *con, const char *s, unsigned int n)
+{
+	struct earlycon_device *dev = con->data;
+
+	uart_console_write(&dev->port, s, n, lrw_uart_putc);
+}
+
+#ifdef CONFIG_CONSOLE_POLL
+static int lrw_uart_getc(struct uart_port *port)
+{
+	if (readl(port->membase + UARTFR) & UARTFR_RXFE)
+		return NO_POLL_CHAR;
+
+	if (port->iotype == UPIO_MEM32)
+		return readl(port->membase + UARTDR);
+	else
+		return readb(port->membase + UARTDR);
+}
+
+static int lrw_uart_early_read(struct console *con, char *s, unsigned int n)
+{
+	struct earlycon_device *dev = con->data;
+	int ch, num_read = 0;
+
+	while (num_read < n) {
+		ch = lrw_uart_getc(&dev->port);
+		if (ch == NO_POLL_CHAR)
+			break;
+
+		s[num_read++] = ch;
+	}
+
+	return num_read;
+}
+#else
+#define lrw_uart_early_read NULL
+#endif
+
+/*
+ * On non-ACPI systems, earlycon is enabled by specifying
+ * "earlycon=lrw_uart,<address>" on the kernel command line.
+ *
+ * On ACPI ARM64 systems, an "early" console is enabled via the SPCR table,
+ * by specifying only "earlycon" on the command line.  Because it requires
+ * SPCR, the console starts after ACPI is parsed, which is later than a
+ * traditional early console.
+ *
+ * To get the traditional early console that starts before ACPI is parsed,
+ * specify the full "earlycon=lrw_uart,<address>" option.
+ */
+static int __init lrw_uart_early_console_setup(struct earlycon_device *device,
+					      const char *opt)
+{
+	if (!device->port.membase)
+		return -ENODEV;
+
+	device->con->write = lrw_uart_early_write;
+	device->con->read = lrw_uart_early_read;
+
+	return 0;
+}
+
+OF_EARLYCON_DECLARE(lrw_uart, "lrw-uart", lrw_uart_early_console_setup);
+
+#else
+#define LRW_UART_CONSOLE	NULL
+#endif
+
+static struct uart_driver lrw_uart_driver = {
+	.owner			= THIS_MODULE,
+	.driver_name		= LRW_UART_NAME,
+	.dev_name		= LRW_UART_TTY_PREFIX,
+	.nr			= UART_NR,
+	.cons			= LRW_UART_CONSOLE,
+};
+
+static int lrw_uart_probe_dt_alias(int index, struct device *dev)
+{
+	struct device_node *np;
+	static bool seen_dev_with_alias;
+	static bool seen_dev_without_alias;
+	int ret = index;
+
+	if (!IS_ENABLED(CONFIG_OF))
+		return ret;
+
+	np = dev->of_node;
+	if (!np)
+		return ret;
+
+	ret = of_alias_get_id(np, "serial");
+	if (ret < 0) {
+		seen_dev_without_alias = true;
+		ret = index;
+	} else {
+		seen_dev_with_alias = true;
+		if (ret >= ARRAY_SIZE(lrw_uart_console_ports) || lrw_uart_console_ports[ret]) {
+			dev_warn(dev, "requested serial port %d  not available.\n", ret);
+			ret = index;
+		}
+	}
+
+	if (seen_dev_with_alias && seen_dev_without_alias)
+		dev_warn(dev, "aliased and non-aliased serial devices found in device tree. Serial port enumeration may be unpredictable.\n");
+
+	return ret;
+}
+
+/* unregisters the driver also if no more ports are left */
+static void lrw_uart_unregister_port(struct lrw_uart_port *sup)
+{
+	int i;
+	bool busy = false;
+
+	for (i = 0; i < ARRAY_SIZE(lrw_uart_console_ports); i++) {
+		if (lrw_uart_console_ports[i] == sup)
+			lrw_uart_console_ports[i] = NULL;
+		else if (lrw_uart_console_ports[i])
+			busy = true;
+	}
+	lrw_uart_dma_remove(sup);
+	if (!busy)
+		uart_unregister_driver(&lrw_uart_driver);
+}
+
+static int lrw_uart_find_free_port(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(lrw_uart_console_ports); i++)
+		if (!lrw_uart_console_ports[i])
+			return i;
+
+	return -EBUSY;
+}
+
+static int lrw_uart_setup_port(struct device *dev, struct lrw_uart_port *sup,
+			      struct resource *mmiobase, int index)
+{
+	void __iomem *base;
+	int ret;
+
+	base = devm_ioremap_resource(dev, mmiobase);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	index = lrw_uart_probe_dt_alias(index, dev);
+
+	sup->port.dev = dev;
+	sup->port.mapbase = mmiobase->start;
+	sup->port.membase = base;
+	sup->port.fifosize = sup->fifosize;
+	sup->port.has_sysrq = IS_ENABLED(CONFIG_SERIAL_LRW_UART_CONSOLE);
+	sup->port.flags = UPF_BOOT_AUTOCONF;
+	sup->port.line = index;
+
+	ret = uart_get_rs485_mode(&sup->port);
+	if (ret)
+		return ret;
+
+	lrw_uart_console_ports[index] = sup;
+
+	return 0;
+}
+
+static int lrw_uart_register_port(struct lrw_uart_port *sup)
+{
+	int ret, i;
+
+	/* Ensure interrupts from this UART are masked and cleared */
+	lrw_uart_write(0, sup, REG_IMSC);
+	lrw_uart_write(0xffff, sup, REG_ICR);
+
+	if (!lrw_uart_driver.state) {
+		ret = uart_register_driver(&lrw_uart_driver);
+		if (ret < 0) {
+			dev_err(sup->port.dev,
+				"Failed to register LRW UART driver\n");
+			for (i = 0; i < ARRAY_SIZE(lrw_uart_console_ports); i++)
+				if (lrw_uart_console_ports[i] == sup)
+					lrw_uart_console_ports[i] = NULL;
+			return ret;
+		}
+	}
+
+	ret = uart_add_one_port(&lrw_uart_driver, &sup->port);
+	if (ret)
+		lrw_uart_unregister_port(sup);
+
+	return ret;
+}
+
+static const struct serial_rs485 lrw_uart_rs485_supported = {
+	.flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | SER_RS485_RTS_AFTER_SEND |
+		 SER_RS485_RX_DURING_TX,
+	.delay_rts_before_send = 1,
+	.delay_rts_after_send = 1,
+};
+
+static int lrw_uart_probe(struct platform_device *pdev)
+{
+	struct lrw_uart_port *sup;
+	struct resource *r;
+	int portnr, ret;
+	unsigned int clk;
+	unsigned int baudrate;
+
+	/*
+	 * Check the mandatory baud rate parameter in the DT node early
+	 * so that we can easily exit with the error.
+	 */
+	if (pdev->dev.of_node) {
+		struct device_node *np = pdev->dev.of_node;
+
+		ret = of_property_read_u32(np, "current-speed", &baudrate);
+		if (ret)
+			return ret;
+	} else if (has_acpi_companion(&pdev->dev)) {
+		ret = device_property_read_u32(&pdev->dev, "current-speed", &baudrate);
+		if (ret)
+			return ret;
+	} else {
+		baudrate = 115200;
+	}
+
+	portnr = lrw_uart_find_free_port();
+	if (portnr < 0)
+		return portnr;
+
+	sup = devm_kzalloc(&pdev->dev, sizeof(struct lrw_uart_port),
+			   GFP_KERNEL);
+	if (!sup)
+		return -ENOMEM;
+
+	if (has_acpi_companion(&pdev->dev)) {
+		device_property_read_u32(&pdev->dev, "clock-frequency", &clk);
+		sup->port.uartclk = clk;
+	} else {
+		sup->clk = devm_clk_get(&pdev->dev, NULL);
+		if (IS_ERR(sup->clk))
+			return PTR_ERR(sup->clk);
+	}
+
+	ret = platform_get_irq(pdev, 0);
+	if (ret < 0)
+		return ret;
+	sup->port.irq	= ret;
+
+	sup->vendor = &vendor_lrw;
+
+	sup->reg_offset = sup->vendor->reg_offset;
+	sup->fifosize	= LRW_UART_TX_FIFO_DEPTH;
+	sup->port.iotype = sup->vendor->access_32b ? UPIO_MEM32 : UPIO_MEM;
+	sup->port.ops = &lrw_uart_pops;
+	sup->port.rs485_config = lrw_uart_rs485_config;
+	sup->port.rs485_supported = lrw_uart_rs485_supported;
+	sup->fixed_baud = baudrate;
+
+	snprintf(sup->type, sizeof(sup->type), "LRW UART");
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	ret = lrw_uart_setup_port(&pdev->dev, sup, r, portnr);
+	if (ret)
+		return ret;
+
+	platform_set_drvdata(pdev, sup);
+
+	return lrw_uart_register_port(sup);
+}
+
+static void lrw_uart_remove(struct platform_device *dev)
+{
+	struct lrw_uart_port *sup = platform_get_drvdata(dev);
+
+	uart_remove_one_port(&lrw_uart_driver, &sup->port);
+	lrw_uart_unregister_port(sup);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int lrw_uart_suspend(struct device *dev)
+{
+	struct lrw_uart_port *sup = dev_get_drvdata(dev);
+
+	if (!sup)
+		return -EINVAL;
+
+	return uart_suspend_port(&lrw_uart_driver, &sup->port);
+}
+
+static int lrw_uart_resume(struct device *dev)
+{
+	struct lrw_uart_port *sup = dev_get_drvdata(dev);
+
+	if (!sup)
+		return -EINVAL;
+
+	return uart_resume_port(&lrw_uart_driver, &sup->port);
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(lrw_uart_pm_ops, lrw_uart_suspend, lrw_uart_resume);
+
+static const struct of_device_id lrw_uart_of_match[] = {
+	{ .compatible = "lrw,lrw-uart" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, lrw_uart_of_match);
+
+static const struct acpi_device_id __maybe_unused lrw_uart_acpi_match[] = {
+	{ "LRWX0000", 0 },
+	{},
+};
+MODULE_DEVICE_TABLE(acpi, lrw_uart_acpi_match);
+
+static struct platform_driver lrw_uart_platform_driver = {
+	.probe		= lrw_uart_probe,
+	.remove		= lrw_uart_remove,
+	.driver		= {
+		.name	= LRW_UART_NAME,
+		.pm = pm_sleep_ptr(&lrw_uart_pm_ops),
+		.of_match_table = of_match_ptr(lrw_uart_of_match),
+		.acpi_match_table = ACPI_PTR(lrw_uart_acpi_match),
+		.suppress_bind_attrs = IS_BUILTIN(CONFIG_SERIAL_LRW_UART),
+	},
+};
+
+static int __init lrw_uart_init(void)
+{
+	pr_info("Serial: LRW UART driver\n");
+
+	int ret;
+
+	ret = uart_register_driver(&lrw_uart_driver);
+	if (ret < 0) {
+		pr_err("Could not register %s driver\n",
+			lrw_uart_driver.driver_name);
+		return ret;
+	}
+
+	ret = platform_driver_register(&lrw_uart_platform_driver);
+	if (ret < 0) {
+		pr_err("LRW UART platform driver register failed, e = %d\n", ret);
+		uart_unregister_driver(&lrw_uart_driver);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void __exit lrw_uart_exit(void)
+{
+	platform_driver_unregister(&lrw_uart_platform_driver);
+	uart_unregister_driver(&lrw_uart_driver);
+}
+
+/*
+ * While this can be a module, if builtin it's most likely the console
+ * So let's leave module_exit but move module_init to an earlier place
+ */
+arch_initcall(lrw_uart_init);
+module_exit(lrw_uart_exit);
+
+MODULE_AUTHOR("Wenhong Liu/Qingtao Liu");
+MODULE_DESCRIPTION("LRW UART serial driver");
+MODULE_LICENSE("GPL");
diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
index 9c007a106330..8e7322067e1f 100644
--- a/include/uapi/linux/serial_core.h
+++ b/include/uapi/linux/serial_core.h
@@ -231,6 +231,9 @@
 /* Sunplus UART */
 #define PORT_SUNPLUS	123
 
+/* LRW UART */
+#define PORT_LRW	124
+
 /* Generic type identifier for ports which type is not important to userspace. */
 #define PORT_GENERIC	(-1)
 
-- 
2.27.0


^ permalink raw reply related

* [PATCH v10] tty: tty_port: add workqueue to flip TTY buffer
From: Xin Zhao @ 2026-02-13  8:50 UTC (permalink / raw)
  To: gregkh, jirislaby, m.szyprowski, tommaso.merciai.xr, geert
  Cc: hch, tj, jackzxcui1989, linux-kernel, linux-serial

On the embedded platform, certain critical data, such as IMU data, is
transmitted through UART. The tty_flip_buffer_push() interface in the TTY
layer uses system_dfl_wq to handle the flipping of the TTY buffer.
Although the unbound workqueue can create new threads on demand and wake
up the kworker thread on an idle CPU, it may be preempted by real-time
tasks or other high-prio tasks.

flush_to_ldisc() needs to wake up the relevant data handle thread. When
executing __wake_up_common_lock(), it calls spin_lock_irqsave(), which
does not disable preemption but disables migration in RT-Linux. This
prevents the kworker thread from being migrated to other cores by CPU's
balancing logic, resulting in long delays. The call trace is as follows:
    __wake_up_common_lock
    __wake_up
    ep_poll_callback
    __wake_up_common
    __wake_up_common_lock
    __wake_up
    n_tty_receive_buf_common
    n_tty_receive_buf2
    tty_ldisc_receive_buf
    tty_port_default_receive_buf
    flush_to_ldisc

In our system, the processing interval for each frame of IMU data
transmitted via UART can experience significant jitter due to this issue.
Instead of the expected 10 to 15 ms frame processing interval, we see
spikes up to 30 to 35 ms. Moreover, in just one or two hours, there can
be 2 to 3 occurrences of such high jitter, which is quite frequent. This
jitter exceeds the software's tolerable limit of 20 ms.

Introduce flip_wq in tty_port which can be set by tty_port_link_wq() or as
default linked to default workqueue allocated when tty_register_driver().
The default workqueue is allocated with flag WQ_SYSFS, so that cpumask and
nice can be set dynamically. The execution timing of tty_port_link_wq() is
not clearly restricted. The newly added function tty_port_link_driver_wq()
checks whether the flip_wq of the tty_port has already been assigned when
linking the default tty_driver's workqueue to the port. After the user has
set a custom workqueue for a certain tty_port using tty_port_link_wq(), the
system will only use this custom workqueue, even if tty_driver does not
have %TTY_DRIVER_NO_WORKQUEUE flag. When tty_port register device, flip_wq
link operation is done by tty_port_link_driver_wq(), but for in-memory
devices the link operation cannot cover all the cases. Although
tty_port_install() is dedicated for in-memory devices lik PTY to link port
allocated on demand, the logic of tty_port_install() is so simple that
people may not call it, vc_cons[0].d->port is one such case. We check the
buf.flip_wq when flip TTY buffer, if buf.flip_wq of TTY port is NULL, use
system_dfl_wq as a backup.

To avoid naming conflict of the default tty_driver's workqueue, using
'"%s-%s", driver->name, driver->driver_name' as the workqueue name. In
cases where driver_name is not specified and therefore is NULL, the
workqueue is not created. Drivers that do not define driver_name are
potentially in-memory devices like vty, which generally do not require
special workqueue settings. Even with the combination of name and
driver_name, the workqueue names can still be duplicated, as many tty
serial drivers use "ttyS" as dev_name and "serial" as driver_name. I
modified the conflicting driver_name of these drivers by appending a
suffix of _xx based on the corresponding .c file. If this modification is
not made, it could not only lead to duplicate workqueue names but also
result in duplicate entries for the /proc/tty/driver/<driver_name> nodes.

Introduce %TTY_DRIVER_NO_WORKQUEUE flag meaning not to create the
default single tty_driver workqueue. Two reasons why need to introduce the
%TTY_DRIVER_NO_WORKQUEUE flag:
1. If the WQ_SYSFS parameter is enabled, workqueue_sysfs_register() will
fail when trying to create a workqueue with the same name. The pty is an
example of this; if both CONFIG_LEGACY_PTYS and CONFIG_UNIX98_PTYS are
enabled, the call to tty_register_driver() in unix98_pty_init() will fail.
2. Different TTY ports may be used for different tasks, which may require
separate core binding control via workqueues. In this case, the workqueue
created by default in the TTY driver is unnecessary. Enabling this flag
prevents the creation of this redundant workqueue.

After applying this patch, we can set the related UART TTY flip buffer
workqueue by sysfs. We set the cpumask to CPU cores associated with the
IMU tasks, and set the nice to -20. Testing has shown significant
improvement in the previously described issue, with almost no stuttering
occurring anymore.

Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Signed-off-by: Xin Zhao <jackzxcui1989@163.com>
---

Change in v10:
- Modify the driver_name of several tty serial drivers to avoid duplicate
  workqueue names.

Change in v9:
- Fix 'cannot create duplicate filename' problem, when create flip_wq
  workqueue, use '"%s-%s", ... driver->name, driver->driver_name' as
  flip_wq workqueue name, as suggested by Marek Szyprowski.
  driver_name maybe NULL, do not create driver flip_wq workqueue  when
  driver_name is NULL. Drivers that do not define driver_name are
  potentially in-memory devices like vty, which generally do not require
  special workqueue settings.
- Fix 'NULL pointer panic' problem, tty_flip_buffer_push() now check
  whether buf.flip_wq is NULL, use system_dfl_wq instead if buf.flip_wq
  is NULL. Therefore, no longer need to call tty_port_link_wq() to link
  system_dfl_wq to pty ports.
- Set buf.flip_wq to NULL in tty_port_destroy() and
  tty_port_unregister_device().
- Link to v9: https://lore.kernel.org/linux-serial/20260205101948.594577-1-jackzxcui1989@163.com/

Change in v8:
- Rebase code, use system_dfl_wq instead of system_unbound_wq.
- Link to v8: https://lore.kernel.org/linux-serial/20260129103129.2928955-1-jackzxcui1989@163.com/T/#m8697be62ae18c8c7bcb677cbd96599c23b3dab4d

Change in v7:
- Pty simply link to system_unbound_wq instead of allocating a custom one,
  as suggested by Jiri Slaby.
- Modify some inappropriate expressions in the code comments,
  as suggested by Jiri Slaby.
- Link to v7: https://lore.kernel.org/all/20251210125028.4174917-1-jackzxcui1989@163.com/T/#u

Change in v6:
- Modify many inappropriate expressions in the commit log and code comments,
  as suggested by Jiri Slaby.
- Add reasons why need to introduce the %TTY_DRIVER_CUSTOM_WORKQUEUE in
  commit log.
- Modify the error handling related to the allocation failure of workqueue in
  tty_register_driver(), as suggested by Jiri Slaby.
- Add description of tty_port_link_driver_wq() in the commit log,
  as suggested by Jiri Slaby.
- Link to v6: https://lore.kernel.org/all/20251210031827.3771327-1-jackzxcui1989@163.com/

Change in v5:
- Do not allocate workqueue twice when CONFIG_UNIX98_PTYS and
  CONFIG_LEGACY_PTYS are all enabled.
- Link to v5: https://lore.kernel.org/all/20251205030829.1829987-1-jackzxcui1989@163.com/

Change in v4:
- Simplify the logic for creating and releasing the workqueue,
  as suggested by Tejun Heo.
- Allocate single workqueue of one tty_driver as default, link it to
  port when tty_port register device or tty_driver.
- Introduce tty_port_link_wq() to link specific workqueue to port.
- Add driver flag %TTY_DRIVER_CUSTOM_WORKQUEUE meaning not to create the
  default single tty_driver workqueue.
- Link to v4: https://lore.kernel.org/all/202512041303.7192024b-lkp@intel.com/T/#t

Change in v3:
- Add tty flip workqueue for all tty ports, as suggested by Greg KH.
  Every tty port use an individual flip workqueue, while all pty ports
  share the same workqueue created in pty_flip_wq_init().
- Modify the commit log to describe the reason for latency spikes in
  RT-Linux.
- Link to v3: https://lore.kernel.org/all/20251027060929.394053-1-jackzxcui1989@163.com/

Change in v2:
- Do not add new module parameters
  as suggested by Greg KH
- Set WQ_SYSFS to allow properties changes from userspace
  as suggested by Tejun Heo
- Link to v2: https://lore.kernel.org/all/20251024155534.2302590-1-jackzxcui1989@163.com
---
 drivers/tty/pty.c                   | 12 ++++++++----
 drivers/tty/serial/8250/8250_core.c |  2 +-
 drivers/tty/serial/apbuart.c        |  2 +-
 drivers/tty/serial/dz.c             |  2 +-
 drivers/tty/serial/ip22zilog.c      |  2 +-
 drivers/tty/serial/zs.c             |  2 +-
 drivers/tty/tty_buffer.c            | 15 +++++++++++----
 drivers/tty/tty_io.c                | 25 ++++++++++++++++++++++++-
 drivers/tty/tty_port.c              | 22 ++++++++++++++++++++++
 include/linux/tty_buffer.h          |  1 +
 include/linux/tty_driver.h          |  7 +++++++
 include/linux/tty_port.h            | 13 +++++++++++++
 12 files changed, 91 insertions(+), 14 deletions(-)

diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c
index 6120d827a..6c406c741 100644
--- a/drivers/tty/pty.c
+++ b/drivers/tty/pty.c
@@ -532,14 +532,16 @@ static void __init legacy_pty_init(void)
 	pty_driver = tty_alloc_driver(legacy_count,
 			TTY_DRIVER_RESET_TERMIOS |
 			TTY_DRIVER_REAL_RAW |
-			TTY_DRIVER_DYNAMIC_ALLOC);
+			TTY_DRIVER_DYNAMIC_ALLOC |
+			TTY_DRIVER_NO_WORKQUEUE);
 	if (IS_ERR(pty_driver))
 		panic("Couldn't allocate pty driver");
 
 	pty_slave_driver = tty_alloc_driver(legacy_count,
 			TTY_DRIVER_RESET_TERMIOS |
 			TTY_DRIVER_REAL_RAW |
-			TTY_DRIVER_DYNAMIC_ALLOC);
+			TTY_DRIVER_DYNAMIC_ALLOC |
+			TTY_DRIVER_NO_WORKQUEUE);
 	if (IS_ERR(pty_slave_driver))
 		panic("Couldn't allocate pty slave driver");
 
@@ -849,7 +851,8 @@ static void __init unix98_pty_init(void)
 			TTY_DRIVER_REAL_RAW |
 			TTY_DRIVER_DYNAMIC_DEV |
 			TTY_DRIVER_DEVPTS_MEM |
-			TTY_DRIVER_DYNAMIC_ALLOC);
+			TTY_DRIVER_DYNAMIC_ALLOC |
+			TTY_DRIVER_NO_WORKQUEUE);
 	if (IS_ERR(ptm_driver))
 		panic("Couldn't allocate Unix98 ptm driver");
 	pts_driver = tty_alloc_driver(NR_UNIX98_PTY_MAX,
@@ -857,7 +860,8 @@ static void __init unix98_pty_init(void)
 			TTY_DRIVER_REAL_RAW |
 			TTY_DRIVER_DYNAMIC_DEV |
 			TTY_DRIVER_DEVPTS_MEM |
-			TTY_DRIVER_DYNAMIC_ALLOC);
+			TTY_DRIVER_DYNAMIC_ALLOC |
+			TTY_DRIVER_NO_WORKQUEUE);
 	if (IS_ERR(pts_driver))
 		panic("Couldn't allocate Unix98 pts driver");
 
diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c
index 0e81f78c6..7a9b7005b 100644
--- a/drivers/tty/serial/8250/8250_core.c
+++ b/drivers/tty/serial/8250/8250_core.c
@@ -524,7 +524,7 @@ console_initcall(univ8250_console_init);
 
 struct uart_driver serial8250_reg = {
 	.owner			= THIS_MODULE,
-	.driver_name		= "serial",
+	.driver_name		= "serial_8250",
 	.dev_name		= "ttyS",
 	.major			= TTY_MAJOR,
 	.minor			= 64,
diff --git a/drivers/tty/serial/apbuart.c b/drivers/tty/serial/apbuart.c
index 364599f25..3e46341cf 100644
--- a/drivers/tty/serial/apbuart.c
+++ b/drivers/tty/serial/apbuart.c
@@ -505,7 +505,7 @@ console_initcall(apbuart_console_init);
 
 static struct uart_driver grlib_apbuart_driver = {
 	.owner = THIS_MODULE,
-	.driver_name = "serial",
+	.driver_name = "serial_apbuart",
 	.dev_name = "ttyS",
 	.major = SERIAL_APBUART_MAJOR,
 	.minor = SERIAL_APBUART_MINOR,
diff --git a/drivers/tty/serial/dz.c b/drivers/tty/serial/dz.c
index eba91daed..e53c54353 100644
--- a/drivers/tty/serial/dz.c
+++ b/drivers/tty/serial/dz.c
@@ -914,7 +914,7 @@ console_initcall(dz_serial_console_init);
 
 static struct uart_driver dz_reg = {
 	.owner			= THIS_MODULE,
-	.driver_name		= "serial",
+	.driver_name		= "serial_dz",
 	.dev_name		= "ttyS",
 	.major			= TTY_MAJOR,
 	.minor			= 64,
diff --git a/drivers/tty/serial/ip22zilog.c b/drivers/tty/serial/ip22zilog.c
index 6e19c6713..a69b06893 100644
--- a/drivers/tty/serial/ip22zilog.c
+++ b/drivers/tty/serial/ip22zilog.c
@@ -1015,7 +1015,7 @@ static struct console ip22zilog_console = {
 
 static struct uart_driver ip22zilog_reg = {
 	.owner		= THIS_MODULE,
-	.driver_name	= "serial",
+	.driver_name	= "serial_ip22zilog",
 	.dev_name	= "ttyS",
 	.major		= TTY_MAJOR,
 	.minor		= 64,
diff --git a/drivers/tty/serial/zs.c b/drivers/tty/serial/zs.c
index 79ea7108a..72a3c0d90 100644
--- a/drivers/tty/serial/zs.c
+++ b/drivers/tty/serial/zs.c
@@ -1252,7 +1252,7 @@ console_initcall(zs_serial_console_init);
 
 static struct uart_driver zs_reg = {
 	.owner			= THIS_MODULE,
-	.driver_name		= "serial",
+	.driver_name		= "serial_zs",
 	.dev_name		= "ttyS",
 	.major			= TTY_MAJOR,
 	.minor			= 64,
diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c
index 1a5673acd..1a2b29135 100644
--- a/drivers/tty/tty_buffer.c
+++ b/drivers/tty/tty_buffer.c
@@ -59,6 +59,13 @@ void tty_buffer_lock_exclusive(struct tty_port *port)
 }
 EXPORT_SYMBOL_GPL(tty_buffer_lock_exclusive);
 
+static bool tty_buffer_queue_work(struct tty_bufhead *buf)
+{
+	struct workqueue_struct *flip_wq = READ_ONCE(buf->flip_wq);
+
+	return queue_work(flip_wq ?: system_dfl_wq, &buf->work);
+}
+
 /**
  * tty_buffer_unlock_exclusive	-	release exclusive access
  * @port: tty port owning the flip buffer
@@ -76,7 +83,7 @@ void tty_buffer_unlock_exclusive(struct tty_port *port)
 	mutex_unlock(&buf->lock);
 
 	if (restart)
-		queue_work(system_dfl_wq, &buf->work);
+		tty_buffer_queue_work(buf);
 }
 EXPORT_SYMBOL_GPL(tty_buffer_unlock_exclusive);
 
@@ -530,7 +537,7 @@ void tty_flip_buffer_push(struct tty_port *port)
 	struct tty_bufhead *buf = &port->buf;
 
 	tty_flip_buffer_commit(buf->tail);
-	queue_work(system_dfl_wq, &buf->work);
+	tty_buffer_queue_work(buf);
 }
 EXPORT_SYMBOL(tty_flip_buffer_push);
 
@@ -560,7 +567,7 @@ int tty_insert_flip_string_and_push_buffer(struct tty_port *port,
 		tty_flip_buffer_commit(buf->tail);
 	spin_unlock_irqrestore(&port->lock, flags);
 
-	queue_work(system_dfl_wq, &buf->work);
+	tty_buffer_queue_work(buf);
 
 	return size;
 }
@@ -613,7 +620,7 @@ void tty_buffer_set_lock_subclass(struct tty_port *port)
 
 bool tty_buffer_restart_work(struct tty_port *port)
 {
-	return queue_work(system_dfl_wq, &port->buf.work);
+	return tty_buffer_queue_work(&port->buf);
 }
 
 bool tty_buffer_cancel_work(struct tty_port *port)
diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
index e2d92cf70..a8674a20a 100644
--- a/drivers/tty/tty_io.c
+++ b/drivers/tty/tty_io.c
@@ -3446,10 +3446,27 @@ int tty_register_driver(struct tty_driver *driver)
 	if (error < 0)
 		goto err;
 
+	/*
+	 * Drivers that do not define driver_name are potentially in-memory devices
+	 * like vty, which generally do not require special workqueue settings.
+	 */
+	if (!(driver->flags & TTY_DRIVER_NO_WORKQUEUE) && driver->driver_name) {
+		driver->flip_wq = alloc_workqueue("%s-%s", WQ_UNBOUND | WQ_SYSFS,
+						  0, driver->name, driver->driver_name);
+		if (!driver->flip_wq) {
+			error = -ENOMEM;
+			goto err_unreg_char;
+		}
+		for (i = 0; i < driver->num; i++) {
+			if (driver->ports[i])
+				tty_port_link_driver_wq(driver->ports[i], driver);
+		}
+	}
+
 	if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {
 		error = tty_cdev_add(driver, dev, 0, driver->num);
 		if (error)
-			goto err_unreg_char;
+			goto err_destroy_wq;
 	}
 
 	scoped_guard(mutex, &tty_mutex)
@@ -3475,6 +3492,10 @@ int tty_register_driver(struct tty_driver *driver)
 	scoped_guard(mutex, &tty_mutex)
 		list_del(&driver->tty_drivers);
 
+err_destroy_wq:
+	if (driver->flip_wq)
+		destroy_workqueue(driver->flip_wq);
+
 err_unreg_char:
 	unregister_chrdev_region(dev, driver->num);
 err:
@@ -3494,6 +3515,8 @@ void tty_unregister_driver(struct tty_driver *driver)
 				driver->num);
 	scoped_guard(mutex, &tty_mutex)
 		list_del(&driver->tty_drivers);
+	if (driver->flip_wq)
+		destroy_workqueue(driver->flip_wq);
 }
 EXPORT_SYMBOL(tty_unregister_driver);
 
diff --git a/drivers/tty/tty_port.c b/drivers/tty/tty_port.c
index fe67c5cb0..54359310e 100644
--- a/drivers/tty/tty_port.c
+++ b/drivers/tty/tty_port.c
@@ -99,6 +99,23 @@ void tty_port_init(struct tty_port *port)
 }
 EXPORT_SYMBOL(tty_port_init);
 
+/**
+ * tty_port_link_wq - link tty_port and flip workqueue
+ * @port: tty_port of the device
+ * @flip_wq: workqueue to queue flip buffer work on
+ *
+ * Whenever %TTY_DRIVER_NO_WORKQUEUE is used, every tty_port can be linked to
+ * a workqueue manually by this function.
+ * tty_port will use system_dfl_wq when buf.flip_wq is NULL.
+ *
+ * Note that tty_port API will NOT destroy the workqueue.
+ */
+void tty_port_link_wq(struct tty_port *port, struct workqueue_struct *flip_wq)
+{
+	port->buf.flip_wq = flip_wq;
+}
+EXPORT_SYMBOL_GPL(tty_port_link_wq);
+
 /**
  * tty_port_link_device - link tty and tty_port
  * @port: tty_port of the device
@@ -157,6 +174,7 @@ struct device *tty_port_register_device_attr(struct tty_port *port,
 		const struct attribute_group **attr_grp)
 {
 	tty_port_link_device(port, driver, index);
+	tty_port_link_driver_wq(port, driver);
 	return tty_register_device_attr(driver, index, device, drvdata,
 			attr_grp);
 }
@@ -183,6 +201,7 @@ struct device *tty_port_register_device_attr_serdev(struct tty_port *port,
 	struct device *dev;
 
 	tty_port_link_device(port, driver, index);
+	tty_port_link_driver_wq(port, driver);
 
 	dev = serdev_tty_port_register(port, host, parent, driver, index);
 	if (PTR_ERR(dev) != -ENODEV) {
@@ -210,6 +229,7 @@ void tty_port_unregister_device(struct tty_port *port,
 {
 	int ret;
 
+	WRITE_ONCE(port->buf.flip_wq, NULL);
 	ret = serdev_tty_port_unregister(port);
 	if (ret == 0)
 		return;
@@ -257,6 +277,7 @@ void tty_port_destroy(struct tty_port *port)
 {
 	tty_buffer_cancel_work(port);
 	tty_buffer_free_all(port);
+	WRITE_ONCE(port->buf.flip_wq, NULL);
 }
 EXPORT_SYMBOL(tty_port_destroy);
 
@@ -703,6 +724,7 @@ int tty_port_install(struct tty_port *port, struct tty_driver *driver,
 		struct tty_struct *tty)
 {
 	tty->port = port;
+	tty_port_link_driver_wq(port, driver);
 	return tty_standard_install(driver, tty);
 }
 EXPORT_SYMBOL_GPL(tty_port_install);
diff --git a/include/linux/tty_buffer.h b/include/linux/tty_buffer.h
index 31125e3be..48adcb0e8 100644
--- a/include/linux/tty_buffer.h
+++ b/include/linux/tty_buffer.h
@@ -34,6 +34,7 @@ static inline u8 *flag_buf_ptr(struct tty_buffer *b, unsigned int ofs)
 
 struct tty_bufhead {
 	struct tty_buffer *head;	/* Queue head */
+	struct workqueue_struct *flip_wq;
 	struct work_struct work;
 	struct mutex	   lock;
 	atomic_t	   priority;
diff --git a/include/linux/tty_driver.h b/include/linux/tty_driver.h
index 188ee9b76..1f2896e56 100644
--- a/include/linux/tty_driver.h
+++ b/include/linux/tty_driver.h
@@ -69,6 +69,10 @@ struct serial_struct;
  *	Do not create numbered ``/dev`` nodes. For example, create
  *	``/dev/ttyprintk`` and not ``/dev/ttyprintk0``. Applicable only when a
  *	driver for a single tty device is being allocated.
+ *
+ * @TTY_DRIVER_NO_WORKQUEUE:
+ *	Do not create workqueue when tty_register_driver(). Whenever set, flip
+ *	buffer workqueue can be set by tty_port_link_wq() for every port.
  */
 enum tty_driver_flag {
 	TTY_DRIVER_INSTALLED		= BIT(0),
@@ -79,6 +83,7 @@ enum tty_driver_flag {
 	TTY_DRIVER_HARDWARE_BREAK	= BIT(5),
 	TTY_DRIVER_DYNAMIC_ALLOC	= BIT(6),
 	TTY_DRIVER_UNNUMBERED_NODE	= BIT(7),
+	TTY_DRIVER_NO_WORKQUEUE		= BIT(8),
 };
 
 enum tty_driver_type {
@@ -506,6 +511,7 @@ struct tty_operations {
  * @flags: tty driver flags (%TTY_DRIVER_)
  * @proc_entry: proc fs entry, used internally
  * @other: driver of the linked tty; only used for the PTY driver
+ * @flip_wq: workqueue to queue flip buffer work on
  * @ttys: array of active &struct tty_struct, set by tty_standard_install()
  * @ports: array of &struct tty_port; can be set during initialization by
  *	   tty_port_link_device() and similar
@@ -539,6 +545,7 @@ struct tty_driver {
 	unsigned long	flags;
 	struct proc_dir_entry *proc_entry;
 	struct tty_driver *other;
+	struct workqueue_struct *flip_wq;
 
 	/*
 	 * Pointer to the tty data structures
diff --git a/include/linux/tty_port.h b/include/linux/tty_port.h
index 660c254f1..d2a7882c0 100644
--- a/include/linux/tty_port.h
+++ b/include/linux/tty_port.h
@@ -138,6 +138,7 @@ struct tty_port {
 					   kernel */
 
 void tty_port_init(struct tty_port *port);
+void tty_port_link_wq(struct tty_port *port, struct workqueue_struct *flip_wq);
 void tty_port_link_device(struct tty_port *port, struct tty_driver *driver,
 		unsigned index);
 struct device *tty_port_register_device(struct tty_port *port,
@@ -165,6 +166,18 @@ static inline struct tty_port *tty_port_get(struct tty_port *port)
 	return NULL;
 }
 
+/*
+ * Never overwrite the workqueue set by tty_port_link_wq().
+ * No effect when %TTY_DRIVER_NO_WORKQUEUE is set, as driver->flip_wq is
+ * %NULL.
+ */
+static inline void tty_port_link_driver_wq(struct tty_port *port,
+					   struct tty_driver *driver)
+{
+	if (!port->buf.flip_wq)
+		tty_port_link_wq(port, driver->flip_wq);
+}
+
 /* If the cts flow control is enabled, return true. */
 static inline bool tty_port_cts_enabled(const struct tty_port *port)
 {
-- 
2.34.1


^ permalink raw reply related

* Re: [PATCH v3] serial: 8250: always disable IRQ during THRE test
From: Andy Shevchenko @ 2026-02-12 10:15 UTC (permalink / raw)
  To: Jiri Slaby
  Cc: Alban Bedel, linux-serial, linux-kernel, Nam Cao,
	Ilpo Järvinen, Greg Kroah-Hartman, Lennert Buytenhek,
	Peng Zhang, stable, Muchun Song, Maximilian Lueer
In-Reply-To: <5964c41f-ddd3-406d-91b1-62fa33364163@kernel.org>

On Thu, Feb 12, 2026 at 09:41:08AM +0100, Jiri Slaby wrote:
> On 09. 02. 26, 12:32, Alban Bedel wrote:

> > commit 039d4926379b ("serial: 8250: Toggle IER bits on only after irq
> > has been set up") moved IRQ setup before the THRE test, so the interrupt
> > handler can run during the test and race with its IIR reads. This can
> > produce wrong THRE test results and cause spurious registration of the
> > serial8250_backup_timeout timer. Unconditionally disable the IRQ for the
> > short duration of the test and re-enable it afterwards to avoid the race.

...

> > v2: Replaced disable_irq_nosync() with disable_irq() to prevent interrupts
> >      that are currently being handled
> 
> This made me to check, why/how this is possible. It appears to be, but only
> thanks to:
>   205d300aea75 serial: 8250: change lock order in serial8250_do_startup()
> 
> And the change should be documented in the commit log. Especially to avoid
> stable backports to trees without 205d300aea75.

There is a de facto tag Depends-on: which might be used here.

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* Re: [PATCH v3] serial: 8250: always disable IRQ during THRE test
From: Jiri Slaby @ 2026-02-12  8:41 UTC (permalink / raw)
  To: Alban Bedel, linux-serial
  Cc: linux-kernel, Nam Cao, Andy Shevchenko, Ilpo Järvinen,
	Greg Kroah-Hartman, Lennert Buytenhek, Peng Zhang, stable,
	Muchun Song, Maximilian Lueer
In-Reply-To: <20260209113207.2118445-1-alban.bedel@lht.dlh.de>

On 09. 02. 26, 12:32, Alban Bedel wrote:
> From: Peng Zhang <zhangpeng.00@bytedance.com>
> 
> commit 039d4926379b ("serial: 8250: Toggle IER bits on only after irq
> has been set up") moved IRQ setup before the THRE test, so the interrupt
> handler can run during the test and race with its IIR reads. This can
> produce wrong THRE test results and cause spurious registration of the
> serial8250_backup_timeout timer. Unconditionally disable the IRQ for the
> short duration of the test and re-enable it afterwards to avoid the race.
> 
> Cc: stable@vger.kernel.org
> Fixes: 039d4926379b ("serial: 8250: Toggle IER bits on only after irq has been set up")
> Signed-off-by: Peng Zhang <zhangpeng.00@bytedance.com>
> Reviewed-by: Muchun Song <songmuchun@bytedance.com>
> Signed-off-by: Alban Bedel <alban.bedel@lht.dlh.de>
> Tested-by: Maximilian Lueer <maximilian.lueer@lht.dlh.de>
> ---
> Changelog:
> 
> v2: Replaced disable_irq_nosync() with disable_irq() to prevent interrupts
>      that are currently being handled

This made me to check, why/how this is possible. It appears to be, but 
only thanks to:
   205d300aea75 serial: 8250: change lock order in serial8250_do_startup()

And the change should be documented in the commit log. Especially to 
avoid stable backports to trees without 205d300aea75.

thanks,
-- 
js
suse labs

^ permalink raw reply

* [PATCH] tty: n_tty: fix KCSAN data-race in n_tty_flush_buffer / n_tty_lookahead_flow_ctrl
From: Osama Abdelkader @ 2026-02-11 21:08 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko,
	Ilpo Järvinen, linux-kernel, linux-serial
  Cc: Osama Abdelkader, syzbot+80806cf7508e92c7cc86

n_tty_lookahead_flow_ctrl() accesses ldata->lookahead_count without
holding termios_rwsem, while reset_buffer_flags() in n_tty_flush_buffer()
resets it with exclusive termios_rwsem held. This causes a data race
reported by KCSAN when a PTY is closed while flush_to_ldisc is still
processing lookahead data.

Fix by taking termios_rwsem (read) in n_tty_lookahead_flow_ctrl(),
consistent with __receive_buf() which also modifies lookahead_count
under the read lock.

Reported-by: syzbot+80806cf7508e92c7cc86@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=80806cf7508e92c7cc86
Fixes: 6bb6fa6908eb ("tty: Implement lookahead to process XON/XOFF timely")
Signed-off-by: Osama Abdelkader <osama.abdelkader@gmail.com>
---
 drivers/tty/n_tty.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c
index e6a0f5b40d0a..725d6ed2b863 100644
--- a/drivers/tty/n_tty.c
+++ b/drivers/tty/n_tty.c
@@ -1480,6 +1480,8 @@ static void n_tty_lookahead_flow_ctrl(struct tty_struct *tty, const u8 *cp,
 	struct n_tty_data *ldata = tty->disc_data;
 	u8 flag = TTY_NORMAL;
 
+	guard(rwsem_read)(&tty->termios_rwsem);
+
 	ldata->lookahead_count += count;
 
 	if (!I_IXON(tty))
-- 
2.43.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox