| 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> |
| @@ -1739,10 +1740,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 */ |
| @@ -1755,14 +1769,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; |
| } |
| @@ -1789,11 +1796,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; |
| |
| @@ -1825,17 +1848,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_hw_setup(sport); |
| |
| - lpuart32_setup_watermark_enable(sport); |
| - lpuart32_configure(sport); |
| - |
| - spin_unlock_irqrestore(&sport->port.lock, flags); |
| return 0; |
| } |
| |
| @@ -2893,108 +2907,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; |
| +} |
| + |
| +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); |
| |
| - /* Disable Rx DMA to use UART port as wakeup source */ |
| + 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) |