All patches automatically rebased. Build system: x86_64 Build-tested: ipq806x/R7800 Run-tested: ipq806x/R7800 Signed-off-by: John Audia <graysky@archlinux.us>
		
			
				
	
	
		
			393 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			393 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
From 4db59ee0d7224e0c8008534c9247480a83889034 Mon Sep 17 00:00:00 2001
 | 
						|
From: Fugang Duan <fugang.duan@nxp.com>
 | 
						|
Date: Wed, 11 Sep 2019 17:01:45 +0800
 | 
						|
Subject: [PATCH] tty: serial: lpuart: enable wakeup source for lpuart
 | 
						|
 | 
						|
When use lpuart with DMA mode as wake up source, it still switch to
 | 
						|
cpu mode in .suspend() that enable cpu interrupts RIE and ILIE as
 | 
						|
wakeup source. Enable the wakeup irq bits in .suspend_noirq() and
 | 
						|
disable the wakeup irq bits in .resume_noirq().
 | 
						|
 | 
						|
For DMA mode, after system resume back, it needs to setup DMA again,
 | 
						|
if DMA setup is failed, it switchs to CPU mode. .resume() will share
 | 
						|
the HW setup code with .startup(), so abstract the same code to the
 | 
						|
api like lpuartx_hw_setup().
 | 
						|
 | 
						|
Signed-off-by: Fugang Duan <fugang.duan@nxp.com>
 | 
						|
---
 | 
						|
 drivers/tty/serial/fsl_lpuart.c | 285 ++++++++++++++++++++++++++++------------
 | 
						|
 1 file changed, 198 insertions(+), 87 deletions(-)
 | 
						|
 | 
						|
--- a/drivers/tty/serial/fsl_lpuart.c
 | 
						|
+++ b/drivers/tty/serial/fsl_lpuart.c
 | 
						|
@@ -21,6 +21,7 @@
 | 
						|
 #include <linux/of.h>
 | 
						|
 #include <linux/of_device.h>
 | 
						|
 #include <linux/of_dma.h>
 | 
						|
+#include <linux/pinctrl/consumer.h>
 | 
						|
 #include <linux/pm_domain.h>
 | 
						|
 #include <linux/pm_runtime.h>
 | 
						|
 #include <linux/reset.h>
 | 
						|
@@ -1722,10 +1723,23 @@ static void lpuart_rx_dma_startup(struct
 | 
						|
 	}
 | 
						|
 }
 | 
						|
 
 | 
						|
+static void lpuart_hw_setup(struct lpuart_port *sport)
 | 
						|
+{
 | 
						|
+	unsigned long flags;
 | 
						|
+
 | 
						|
+	spin_lock_irqsave(&sport->port.lock, flags);
 | 
						|
+
 | 
						|
+	lpuart_setup_watermark_enable(sport);
 | 
						|
+
 | 
						|
+	lpuart_rx_dma_startup(sport);
 | 
						|
+	lpuart_tx_dma_startup(sport);
 | 
						|
+
 | 
						|
+	spin_unlock_irqrestore(&sport->port.lock, flags);
 | 
						|
+}
 | 
						|
+
 | 
						|
 static int lpuart_startup(struct uart_port *port)
 | 
						|
 {
 | 
						|
 	struct lpuart_port *sport = container_of(port, struct lpuart_port, port);
 | 
						|
-	unsigned long flags;
 | 
						|
 	unsigned char temp;
 | 
						|
 
 | 
						|
 	/* determine FIFO size and enable FIFO mode */
 | 
						|
@@ -1738,14 +1752,7 @@ static int lpuart_startup(struct uart_po
 | 
						|
 	sport->rxfifo_size = UARTFIFO_DEPTH((temp >> UARTPFIFO_RXSIZE_OFF) &
 | 
						|
 					    UARTPFIFO_FIFOSIZE_MASK);
 | 
						|
 
 | 
						|
-	spin_lock_irqsave(&sport->port.lock, flags);
 | 
						|
-
 | 
						|
-	lpuart_setup_watermark_enable(sport);
 | 
						|
-
 | 
						|
-	lpuart_rx_dma_startup(sport);
 | 
						|
-	lpuart_tx_dma_startup(sport);
 | 
						|
-
 | 
						|
-	spin_unlock_irqrestore(&sport->port.lock, flags);
 | 
						|
+	lpuart_hw_setup(sport);
 | 
						|
 
 | 
						|
 	return 0;
 | 
						|
 }
 | 
						|
@@ -1772,11 +1779,27 @@ static void lpuart32_configure(struct lp
 | 
						|
 	lpuart32_write(&sport->port, temp, UARTCTRL);
 | 
						|
 }
 | 
						|
 
 | 
						|
+static void lpuart32_hw_setup(struct lpuart_port *sport)
 | 
						|
+{
 | 
						|
+	unsigned long flags;
 | 
						|
+
 | 
						|
+	spin_lock_irqsave(&sport->port.lock, flags);
 | 
						|
+
 | 
						|
+	lpuart32_hw_disable(sport);
 | 
						|
+
 | 
						|
+	lpuart_rx_dma_startup(sport);
 | 
						|
+	lpuart_tx_dma_startup(sport);
 | 
						|
+
 | 
						|
+	lpuart32_setup_watermark_enable(sport);
 | 
						|
+	lpuart32_configure(sport);
 | 
						|
+
 | 
						|
+	spin_unlock_irqrestore(&sport->port.lock, flags);
 | 
						|
+}
 | 
						|
+
 | 
						|
 static int lpuart32_startup(struct uart_port *port)
 | 
						|
 {
 | 
						|
 	struct lpuart_port *sport = container_of(port, struct lpuart_port, port);
 | 
						|
 	struct tty_port *tty_port = &sport->port.state->port;
 | 
						|
-	unsigned long flags;
 | 
						|
 	unsigned long temp;
 | 
						|
 	int ret;
 | 
						|
 
 | 
						|
@@ -1808,17 +1831,8 @@ static int lpuart32_startup(struct uart_
 | 
						|
 		sport->port.fifosize = sport->txfifo_size;
 | 
						|
 	}
 | 
						|
 
 | 
						|
-	spin_lock_irqsave(&sport->port.lock, flags);
 | 
						|
-
 | 
						|
-	lpuart32_hw_disable(sport);
 | 
						|
-
 | 
						|
-	lpuart_rx_dma_startup(sport);
 | 
						|
-	lpuart_tx_dma_startup(sport);
 | 
						|
-
 | 
						|
-	lpuart32_setup_watermark_enable(sport);
 | 
						|
-	lpuart32_configure(sport);
 | 
						|
+	lpuart32_hw_setup(sport);
 | 
						|
 
 | 
						|
-	spin_unlock_irqrestore(&sport->port.lock, flags);
 | 
						|
 	return 0;
 | 
						|
 }
 | 
						|
 
 | 
						|
@@ -2879,108 +2893,205 @@ static int lpuart_runtime_resume(struct
 | 
						|
 	return lpuart_enable_clks(sport);
 | 
						|
 };
 | 
						|
 
 | 
						|
-static int lpuart_suspend(struct device *dev)
 | 
						|
+static void serial_lpuart_enable_wakeup(struct lpuart_port *sport, bool on)
 | 
						|
 {
 | 
						|
-	struct lpuart_port *sport = dev_get_drvdata(dev);
 | 
						|
-	unsigned long temp;
 | 
						|
-	bool irq_wake;
 | 
						|
-	int ret;
 | 
						|
-
 | 
						|
-	ret = clk_prepare_enable(sport->ipg_clk);
 | 
						|
-	if (ret)
 | 
						|
-		return ret;
 | 
						|
+	unsigned int val;
 | 
						|
 
 | 
						|
 	if (lpuart_is_32(sport)) {
 | 
						|
-		/* disable Rx/Tx and interrupts */
 | 
						|
-		temp = lpuart32_read(&sport->port, UARTCTRL);
 | 
						|
-		temp &= ~(UARTCTRL_TE | UARTCTRL_TIE | UARTCTRL_TCIE);
 | 
						|
-		lpuart32_write(&sport->port, temp, UARTCTRL);
 | 
						|
+		val = lpuart32_read(&sport->port, UARTCTRL);
 | 
						|
+		if (on)
 | 
						|
+			val |= (UARTCTRL_RIE | UARTCTRL_ILIE);
 | 
						|
+		else
 | 
						|
+			val &= ~(UARTCTRL_RIE | UARTCTRL_ILIE);
 | 
						|
+		lpuart32_write(&sport->port, val, UARTCTRL);
 | 
						|
 	} else {
 | 
						|
-		/* disable Rx/Tx and interrupts */
 | 
						|
-		temp = readb(sport->port.membase + UARTCR2);
 | 
						|
-		temp &= ~(UARTCR2_TE | UARTCR2_TIE | UARTCR2_TCIE);
 | 
						|
-		writeb(temp, sport->port.membase + UARTCR2);
 | 
						|
+		val = readb(sport->port.membase + UARTCR2);
 | 
						|
+		if (on)
 | 
						|
+			val |= UARTCR2_RIE;
 | 
						|
+		else
 | 
						|
+			val &= ~UARTCR2_RIE;
 | 
						|
+		writeb(val, sport->port.membase + UARTCR2);
 | 
						|
 	}
 | 
						|
+}
 | 
						|
 
 | 
						|
-	clk_disable_unprepare(sport->ipg_clk);
 | 
						|
+static bool lpuart_uport_is_active(struct lpuart_port *sport)
 | 
						|
+{
 | 
						|
+	struct tty_port *port = &sport->port.state->port;
 | 
						|
+	struct tty_struct *tty;
 | 
						|
+	struct device *tty_dev;
 | 
						|
+	int may_wake = 0;
 | 
						|
 
 | 
						|
-	uart_suspend_port(&lpuart_reg, &sport->port);
 | 
						|
+	tty = tty_port_tty_get(port);
 | 
						|
+	if (tty) {
 | 
						|
+		tty_dev = tty->dev;
 | 
						|
+		may_wake = device_may_wakeup(tty_dev);
 | 
						|
+		tty_kref_put(tty);
 | 
						|
+	}
 | 
						|
 
 | 
						|
-	/* uart_suspend_port() might set wakeup flag */
 | 
						|
-	irq_wake = irqd_is_wakeup_set(irq_get_irq_data(sport->port.irq));
 | 
						|
-	if (sport->port.suspended && !irq_wake)
 | 
						|
-		return 0;
 | 
						|
+	if ((tty_port_initialized(port) && may_wake) ||
 | 
						|
+	    (!console_suspend_enabled && uart_console(&sport->port)))
 | 
						|
+		return true;
 | 
						|
 
 | 
						|
-	if (sport->lpuart_dma_rx_use) {
 | 
						|
-		/*
 | 
						|
-		 * EDMA driver during suspend will forcefully release any
 | 
						|
-		 * non-idle DMA channels. If port wakeup is enabled or if port
 | 
						|
-		 * is console port or 'no_console_suspend' is set the Rx DMA
 | 
						|
-		 * cannot resume as as expected, hence gracefully release the
 | 
						|
-		 * Rx DMA path before suspend and start Rx DMA path on resume.
 | 
						|
-		 */
 | 
						|
-		if (irq_wake) {
 | 
						|
-			lpuart_del_timer_sync(sport);
 | 
						|
-			lpuart_dma_rx_free(&sport->port);
 | 
						|
-		}
 | 
						|
+	return false;
 | 
						|
+}
 | 
						|
 
 | 
						|
-		/* Disable Rx DMA to use UART port as wakeup source */
 | 
						|
+static int lpuart_suspend_noirq(struct device *dev)
 | 
						|
+{
 | 
						|
+	struct lpuart_port *sport = dev_get_drvdata(dev);
 | 
						|
+	bool irq_wake = irqd_is_wakeup_set(irq_get_irq_data(sport->port.irq));
 | 
						|
+
 | 
						|
+	if (lpuart_uport_is_active(sport))
 | 
						|
+		serial_lpuart_enable_wakeup(sport, !!irq_wake);
 | 
						|
+
 | 
						|
+	pinctrl_pm_select_sleep_state(dev);
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int lpuart_resume_noirq(struct device *dev)
 | 
						|
+{
 | 
						|
+	struct lpuart_port *sport = dev_get_drvdata(dev);
 | 
						|
+	unsigned int val;
 | 
						|
+
 | 
						|
+	pinctrl_pm_select_default_state(dev);
 | 
						|
+
 | 
						|
+	if (lpuart_uport_is_active(sport)) {
 | 
						|
+		serial_lpuart_enable_wakeup(sport, false);
 | 
						|
+
 | 
						|
+		/* clear the wakeup flags */
 | 
						|
 		if (lpuart_is_32(sport)) {
 | 
						|
-			temp = lpuart32_read(&sport->port, UARTBAUD);
 | 
						|
-			lpuart32_write(&sport->port, temp & ~UARTBAUD_RDMAE,
 | 
						|
-				       UARTBAUD);
 | 
						|
-		} else {
 | 
						|
-			writeb(readb(sport->port.membase + UARTCR5) &
 | 
						|
-			       ~UARTCR5_RDMAS, sport->port.membase + UARTCR5);
 | 
						|
+			val = lpuart32_read(&sport->port, UARTSTAT);
 | 
						|
+			lpuart32_write(&sport->port, val, UARTSTAT);
 | 
						|
 		}
 | 
						|
 	}
 | 
						|
 
 | 
						|
-	if (sport->lpuart_dma_tx_use) {
 | 
						|
-		sport->dma_tx_in_progress = false;
 | 
						|
-		dmaengine_terminate_all(sport->dma_tx_chan);
 | 
						|
-	}
 | 
						|
-
 | 
						|
 	return 0;
 | 
						|
 }
 | 
						|
 
 | 
						|
-static int lpuart_resume(struct device *dev)
 | 
						|
+static int lpuart_suspend(struct device *dev)
 | 
						|
 {
 | 
						|
 	struct lpuart_port *sport = dev_get_drvdata(dev);
 | 
						|
-	bool irq_wake = irqd_is_wakeup_set(irq_get_irq_data(sport->port.irq));
 | 
						|
-	int ret;
 | 
						|
+	unsigned long temp;
 | 
						|
+	unsigned long flags;
 | 
						|
 
 | 
						|
-	ret = clk_prepare_enable(sport->ipg_clk);
 | 
						|
-	if (ret)
 | 
						|
-		return ret;
 | 
						|
+	uart_suspend_port(&lpuart_reg, &sport->port);
 | 
						|
 
 | 
						|
-	if (lpuart_is_32(sport))
 | 
						|
-		lpuart32_setup_watermark_enable(sport);
 | 
						|
-	else
 | 
						|
-		lpuart_setup_watermark_enable(sport);
 | 
						|
+	if (lpuart_uport_is_active(sport)) {
 | 
						|
+		spin_lock_irqsave(&sport->port.lock, flags);
 | 
						|
+		if (lpuart_is_32(sport)) {
 | 
						|
+			temp = lpuart32_read(&sport->port, UARTCTRL);
 | 
						|
+			temp &= ~(UARTCTRL_TE | UARTCTRL_TIE | UARTCTRL_TCIE);
 | 
						|
+			lpuart32_write(&sport->port, temp, UARTCTRL);
 | 
						|
+		} else {
 | 
						|
+			temp = readb(sport->port.membase + UARTCR2);
 | 
						|
+			temp &= ~(UARTCR2_TE | UARTCR2_TIE | UARTCR2_TCIE);
 | 
						|
+			writeb(temp, sport->port.membase + UARTCR2);
 | 
						|
+		}
 | 
						|
+		spin_unlock_irqrestore(&sport->port.lock, flags);
 | 
						|
 
 | 
						|
-	if (sport->lpuart_dma_rx_use) {
 | 
						|
-		if (irq_wake) {
 | 
						|
-			if (!lpuart_start_rx_dma(sport))
 | 
						|
-				rx_dma_timer_init(sport);
 | 
						|
-			else
 | 
						|
-				sport->lpuart_dma_rx_use = false;
 | 
						|
+		if (sport->lpuart_dma_rx_use) {
 | 
						|
+			/*
 | 
						|
+			 * EDMA driver during suspend will forcefully release any
 | 
						|
+			 * non-idle DMA channels. If port wakeup is enabled or if port
 | 
						|
+			 * is console port or 'no_console_suspend' is set the Rx DMA
 | 
						|
+			 * cannot resume as as expected, hence gracefully release the
 | 
						|
+			 * Rx DMA path before suspend and start Rx DMA path on resume.
 | 
						|
+			 */
 | 
						|
+			lpuart_del_timer_sync(sport);
 | 
						|
+			lpuart_dma_rx_free(&sport->port);
 | 
						|
+
 | 
						|
+			/* Disable Rx DMA to use UART port as wakeup source */
 | 
						|
+			spin_lock_irqsave(&sport->port.lock, flags);
 | 
						|
+			if (lpuart_is_32(sport)) {
 | 
						|
+				temp = lpuart32_read(&sport->port, UARTBAUD);
 | 
						|
+				lpuart32_write(&sport->port, temp & ~UARTBAUD_RDMAE,
 | 
						|
+					       UARTBAUD);
 | 
						|
+			} else {
 | 
						|
+				writeb(readb(sport->port.membase + UARTCR5) &
 | 
						|
+				       ~UARTCR5_RDMAS, sport->port.membase + UARTCR5);
 | 
						|
+			}
 | 
						|
+			spin_unlock_irqrestore(&sport->port.lock, flags);
 | 
						|
+		}
 | 
						|
+
 | 
						|
+		if (sport->lpuart_dma_tx_use) {
 | 
						|
+			spin_lock_irqsave(&sport->port.lock, flags);
 | 
						|
+			if (lpuart_is_32(sport)) {
 | 
						|
+				temp = lpuart32_read(&sport->port, UARTBAUD);
 | 
						|
+				temp &= ~UARTBAUD_TDMAE;
 | 
						|
+				lpuart32_write(&sport->port, temp, UARTBAUD);
 | 
						|
+			} else {
 | 
						|
+				temp = readb(sport->port.membase + UARTCR5);
 | 
						|
+				temp &= ~UARTCR5_TDMAS;
 | 
						|
+				writeb(temp, sport->port.membase + UARTCR5);
 | 
						|
+			}
 | 
						|
+			spin_unlock_irqrestore(&sport->port.lock, flags);
 | 
						|
+			sport->dma_tx_in_progress = false;
 | 
						|
+			dmaengine_terminate_all(sport->dma_tx_chan);
 | 
						|
 		}
 | 
						|
+	} else if (pm_runtime_active(sport->port.dev)) {
 | 
						|
+		lpuart_disable_clks(sport);
 | 
						|
+		pm_runtime_disable(sport->port.dev);
 | 
						|
+		pm_runtime_set_suspended(sport->port.dev);
 | 
						|
 	}
 | 
						|
 
 | 
						|
-	lpuart_tx_dma_startup(sport);
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
 
 | 
						|
-	if (lpuart_is_32(sport))
 | 
						|
-		lpuart32_configure(sport);
 | 
						|
+static void lpuart_console_fixup(struct lpuart_port *sport)
 | 
						|
+{
 | 
						|
+	struct tty_port *port = &sport->port.state->port;
 | 
						|
+	struct uart_port *uport = &sport->port;
 | 
						|
+	struct ktermios termios;
 | 
						|
 
 | 
						|
-	clk_disable_unprepare(sport->ipg_clk);
 | 
						|
+	/* i.MX7ULP enter VLLS mode that lpuart module power off and registers
 | 
						|
+	 * all lost no matter the port is wakeup source.
 | 
						|
+	 * For console port, console baud rate setting lost and print messy
 | 
						|
+	 * log when enable the console port as wakeup source. To avoid the
 | 
						|
+	 * issue happen, user should not enable uart port as wakeup source
 | 
						|
+	 * in VLLS mode, or restore console setting here.
 | 
						|
+	 */
 | 
						|
+	if (is_imx7ulp_lpuart(sport) && lpuart_uport_is_active(sport) &&
 | 
						|
+	    console_suspend_enabled && uart_console(&sport->port)) {
 | 
						|
+
 | 
						|
+		mutex_lock(&port->mutex);
 | 
						|
+		memset(&termios, 0, sizeof(struct ktermios));
 | 
						|
+		termios.c_cflag = uport->cons->cflag;
 | 
						|
+		if (port->tty && termios.c_cflag == 0)
 | 
						|
+			termios = port->tty->termios;
 | 
						|
+		uport->ops->set_termios(uport, &termios, NULL);
 | 
						|
+		mutex_unlock(&port->mutex);
 | 
						|
+	}
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int lpuart_resume(struct device *dev)
 | 
						|
+{
 | 
						|
+	struct lpuart_port *sport = dev_get_drvdata(dev);
 | 
						|
+	int ret;
 | 
						|
 
 | 
						|
+	if (lpuart_uport_is_active(sport)) {
 | 
						|
+		if (lpuart_is_32(sport))
 | 
						|
+			lpuart32_hw_setup(sport);
 | 
						|
+		else
 | 
						|
+			lpuart_hw_setup(sport);
 | 
						|
+	} else if (pm_runtime_active(sport->port.dev)) {
 | 
						|
+		ret = lpuart_enable_clks(sport);
 | 
						|
+		if (ret)
 | 
						|
+			return ret;
 | 
						|
+		pm_runtime_set_active(sport->port.dev);
 | 
						|
+		pm_runtime_enable(sport->port.dev);
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	lpuart_console_fixup(sport);
 | 
						|
 	uart_resume_port(&lpuart_reg, &sport->port);
 | 
						|
 
 | 
						|
 	return 0;
 | 
						|
 }
 | 
						|
+
 | 
						|
 static const struct dev_pm_ops lpuart_pm_ops = {
 | 
						|
 	SET_RUNTIME_PM_OPS(lpuart_runtime_suspend,
 | 
						|
 			   lpuart_runtime_resume, NULL)
 | 
						|
+	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(lpuart_suspend_noirq,
 | 
						|
+				      lpuart_resume_noirq)
 | 
						|
 	SET_SYSTEM_SLEEP_PM_OPS(lpuart_suspend, lpuart_resume)
 | 
						|
 };
 | 
						|
 #define SERIAL_LPUART_PM_OPS	(&lpuart_pm_ops)
 |