diff options
Diffstat (limited to 'drivers/tty/serial/fsl_lpuart.c')
-rw-r--r-- | drivers/tty/serial/fsl_lpuart.c | 715 |
1 files changed, 539 insertions, 176 deletions
diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c index ac5112def40d..de1475ed947d 100644 --- a/drivers/tty/serial/fsl_lpuart.c +++ b/drivers/tty/serial/fsl_lpuart.c @@ -17,6 +17,10 @@ #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> #include <linux/serial_core.h> #include <linux/slab.h> #include <linux/tty_flip.h> @@ -130,6 +134,7 @@ #define UARTBAUD_M10 0x20000000 #define UARTBAUD_TDMAE 0x00800000 #define UARTBAUD_RDMAE 0x00200000 +#define UARTBAUD_RIDMAE 0x00100000 #define UARTBAUD_MATCFG 0x00400000 #define UARTBAUD_BOTHEDGE 0x00020000 #define UARTBAUD_RESYNCDIS 0x00010000 @@ -178,7 +183,7 @@ #define UARTCTRL_SBK 0x00010000 #define UARTCTRL_MA1IE 0x00008000 #define UARTCTRL_MA2IE 0x00004000 -#define UARTCTRL_IDLECFG 0x00000100 +#define UARTCTRL_IDLECFG_OFF 0x8 #define UARTCTRL_LOOPS 0x00000080 #define UARTCTRL_DOZEEN 0x00000040 #define UARTCTRL_RSRC 0x00000020 @@ -196,6 +201,7 @@ #define UARTDATA_MASK 0x3ff #define UARTMODIR_IREN 0x00020000 +#define UARTMODIR_RTSWATER_S 0x8 #define UARTMODIR_TXCTSSRC 0x00000020 #define UARTMODIR_TXCTSC 0x00000010 #define UARTMODIR_RXRTSE 0x00000008 @@ -209,6 +215,8 @@ #define UARTFIFO_RXUF 0x00010000 #define UARTFIFO_TXFLUSH 0x00008000 #define UARTFIFO_RXFLUSH 0x00004000 +#define UARTFIFO_RXIDEN_MASK 0x7 +#define UARTFIFO_RXIDEN_OFF 10 #define UARTFIFO_TXOFE 0x00000200 #define UARTFIFO_RXUFE 0x00000100 #define UARTFIFO_TXFE 0x00000080 @@ -229,8 +237,12 @@ #define GLOBAL_RST_MIN_US 20 #define GLOBAL_RST_MAX_US 40 +#define UARTFIFO_RXIDEN_RDRF 0x3 +#define UARTCTRL_IDLECFG 0x7 + /* Rx DMA timeout in ms, which is used to calculate Rx ring buffer size */ #define DMA_RX_TIMEOUT (10) +#define UART_AUTOSUSPEND_TIMEOUT 3000 #define DRIVER_NAME "fsl-lpuart" #define DEV_NAME "ttyLP" @@ -239,13 +251,12 @@ /* IMX lpuart has four extra unused regs located at the beginning */ #define IMX_REG_OFF 0x10 -static DEFINE_IDA(fsl_lpuart_ida); - enum lpuart_type { VF610_LPUART, LS1021A_LPUART, LS1028A_LPUART, IMX7ULP_LPUART, + IMX8ULP_LPUART, IMX8QXP_LPUART, }; @@ -257,8 +268,11 @@ struct lpuart_port { unsigned int txfifo_size; unsigned int rxfifo_size; + u8 rx_watermark; + bool dma_eeop; bool lpuart_dma_tx_use; bool lpuart_dma_rx_use; + bool dma_rx_chan_active; struct dma_chan *dma_tx_chan; struct dma_chan *dma_rx_chan; struct dma_async_tx_descriptor *dma_tx_desc; @@ -273,42 +287,57 @@ struct lpuart_port { struct scatterlist rx_sgl, tx_sgl[2]; struct circ_buf rx_ring; int rx_dma_rng_buf_len; + int rx_dma_periods; unsigned int dma_tx_nents; wait_queue_head_t dma_wait; - bool id_allocated; + bool is_cs7; /* Set to true when character size is 7 */ + /* and the parity is enabled */ }; struct lpuart_soc_data { enum lpuart_type devtype; char iotype; u8 reg_off; + u8 rx_watermark; }; static const struct lpuart_soc_data vf_data = { .devtype = VF610_LPUART, .iotype = UPIO_MEM, + .rx_watermark = 1, }; static const struct lpuart_soc_data ls1021a_data = { .devtype = LS1021A_LPUART, .iotype = UPIO_MEM32BE, + .rx_watermark = 0, }; static const struct lpuart_soc_data ls1028a_data = { .devtype = LS1028A_LPUART, .iotype = UPIO_MEM32, + .rx_watermark = 0, }; static struct lpuart_soc_data imx7ulp_data = { .devtype = IMX7ULP_LPUART, .iotype = UPIO_MEM32, .reg_off = IMX_REG_OFF, + .rx_watermark = 0, +}; + +static struct lpuart_soc_data imx8ulp_data = { + .devtype = IMX8ULP_LPUART, + .iotype = UPIO_MEM32, + .reg_off = IMX_REG_OFF, + .rx_watermark = 3, }; static struct lpuart_soc_data imx8qxp_data = { .devtype = IMX8QXP_LPUART, .iotype = UPIO_MEM32, .reg_off = IMX_REG_OFF, + .rx_watermark = 31, }; static const struct of_device_id lpuart_dt_ids[] = { @@ -316,6 +345,7 @@ static const struct of_device_id lpuart_dt_ids[] = { { .compatible = "fsl,ls1021a-lpuart", .data = &ls1021a_data, }, { .compatible = "fsl,ls1028a-lpuart", .data = &ls1028a_data, }, { .compatible = "fsl,imx7ulp-lpuart", .data = &imx7ulp_data, }, + { .compatible = "fsl,imx8ulp-lpuart", .data = &imx8ulp_data, }, { .compatible = "fsl,imx8qxp-lpuart", .data = &imx8qxp_data, }, { /* sentinel */ } }; @@ -323,6 +353,7 @@ MODULE_DEVICE_TABLE(of, lpuart_dt_ids); /* Forward declare this for the dma callbacks*/ static void lpuart_dma_tx_complete(void *arg); +static void lpuart_dma_rx_free(struct uart_port *port, bool dma_terminate); static inline bool is_layerscape_lpuart(struct lpuart_port *sport) { @@ -335,6 +366,11 @@ static inline bool is_imx7ulp_lpuart(struct lpuart_port *sport) return sport->devtype == IMX7ULP_LPUART; } +static inline bool is_imx8ulp_lpuart(struct lpuart_port *sport) +{ + return sport->devtype == IMX8ULP_LPUART; +} + static inline bool is_imx8qxp_lpuart(struct lpuart_port *sport) { return sport->devtype == IMX8QXP_LPUART; @@ -413,7 +449,7 @@ static int lpuart_global_reset(struct lpuart_port *sport) return ret; } - if (is_imx7ulp_lpuart(sport) || is_imx8qxp_lpuart(sport)) { + if (is_imx7ulp_lpuart(sport) || is_imx8ulp_lpuart(sport) || is_imx8qxp_lpuart(sport)) { global_addr = port->membase + UART_GLOBAL - IMX_REG_OFF; writel(UART_GLOBAL_RST, global_addr); usleep_range(GLOBAL_RST_MIN_US, GLOBAL_RST_MAX_US); @@ -835,6 +871,20 @@ static void lpuart32_start_tx(struct uart_port *port) } } +static void +lpuart_uart_pm(struct uart_port *port, unsigned int state, unsigned int oldstate) +{ + switch (state) { + case UART_PM_STATE_OFF: + pm_runtime_mark_last_busy(port->dev); + pm_runtime_put_autosuspend(port->dev); + break; + default: + pm_runtime_get_sync(port->dev); + break; + } +} + /* return TIOCSER_TEMT when transmitter is not busy */ static unsigned int lpuart_tx_empty(struct uart_port *port) { @@ -924,7 +974,8 @@ static void lpuart_rxint(struct lpuart_port *sport) sport->port.sysrq = 0; } - tty_insert_flip_char(port, rx, flg); + if (tty_insert_flip_char(port, rx, flg) == 0) + sport->port.icount.buf_overrun++; } out: @@ -1017,7 +1068,11 @@ static void lpuart32_rxint(struct lpuart_port *sport) flg = TTY_OVERRUN; } - tty_insert_flip_char(port, rx, flg); + if (sport->is_cs7) + rx &= 0x7F; + + if (tty_insert_flip_char(port, rx, flg) == 0) + sport->port.icount.buf_overrun++; } out: @@ -1066,11 +1121,13 @@ static irqreturn_t lpuart32_int(int irq, void *dev_id) if ((sts & UARTSTAT_TDRE) && !sport->lpuart_dma_tx_use) lpuart32_txint(sport); + if (sport->lpuart_dma_rx_use && sport->dma_eeop) + sts &= ~UARTSTAT_IDLE; + lpuart32_write(&sport->port, sts, UARTSTAT); return IRQ_HANDLED; } - static inline void lpuart_handle_sysrq_chars(struct uart_port *port, unsigned char *p, int count) { @@ -1101,16 +1158,8 @@ static void lpuart_handle_sysrq(struct lpuart_port *sport) } } -static void lpuart_copy_rx_to_tty(struct lpuart_port *sport) +static void lpuart_rx_error_stat(struct lpuart_port *sport) { - struct tty_port *port = &sport->port.state->port; - struct dma_tx_state state; - enum dma_status dmastat; - struct dma_chan *chan = sport->dma_rx_chan; - struct circ_buf *ring = &sport->rx_ring; - unsigned long flags; - int count = 0; - if (lpuart_is_32(sport)) { unsigned long sr = lpuart32_read(&sport->port, UARTSTAT); @@ -1162,8 +1211,34 @@ static void lpuart_copy_rx_to_tty(struct lpuart_port *sport) writeb(cr2, sport->port.membase + UARTCR2); } } +} + +static inline int lpuart_tty_insert_flip_string(struct tty_port *port, + unsigned char *chars, size_t size, bool is_cs7) +{ + int i; + + if (is_cs7) + for (i = 0; i < size; i++) + chars[i] &= 0x7F; + + return tty_insert_flip_string(port, chars, size); +} + +static void lpuart_copy_rx_to_tty(struct lpuart_port *sport) +{ + struct tty_port *port = &sport->port.state->port; + struct dma_tx_state state; + enum dma_status dmastat; + struct dma_chan *chan = sport->dma_rx_chan; + struct circ_buf *ring = &sport->rx_ring; + unsigned long flags; + int count = 0, copied; - async_tx_ack(sport->dma_rx_desc); + if (!is_imx8qxp_lpuart(sport) && !is_imx8ulp_lpuart(sport)) { + lpuart_rx_error_stat(sport); + async_tx_ack(sport->dma_rx_desc); + } spin_lock_irqsave(&sport->port.lock, flags); @@ -1188,6 +1263,13 @@ static void lpuart_copy_rx_to_tty(struct lpuart_port *sport) ring->head = sport->rx_sgl.length - state.residue; BUG_ON(ring->head > sport->rx_sgl.length); + if (sport->dma_eeop) { + unsigned int tcd_size; + + /* Calculate the tail. */ + tcd_size = sg_dma_len(&sport->rx_sgl) / sport->rx_dma_periods; + ring->tail = ((ring->head - 1) / tcd_size) * tcd_size; + } /* * Silent handling of keys pressed in the sysrq timeframe */ @@ -1211,20 +1293,26 @@ static void lpuart_copy_rx_to_tty(struct lpuart_port *sport) if (ring->head < ring->tail) { count = sport->rx_sgl.length - ring->tail; - tty_insert_flip_string(port, ring->buf + ring->tail, count); + copied = lpuart_tty_insert_flip_string(port, ring->buf + ring->tail, + count, sport->is_cs7); + if (copied != count) + sport->port.icount.buf_overrun++; ring->tail = 0; - sport->port.icount.rx += count; + sport->port.icount.rx += copied; } /* Finally we read data from tail to head */ if (ring->tail < ring->head) { count = ring->head - ring->tail; - tty_insert_flip_string(port, ring->buf + ring->tail, count); + copied = lpuart_tty_insert_flip_string(port, ring->buf + ring->tail, + count, sport->is_cs7); + if (copied != count) + sport->port.icount.buf_overrun++; /* Wrap ring->head if needed */ if (ring->head >= sport->rx_sgl.length) ring->head = 0; ring->tail = ring->head; - sport->port.icount.rx += count; + sport->port.icount.rx += copied; } exit: @@ -1234,7 +1322,10 @@ exit: spin_unlock_irqrestore(&sport->port.lock, flags); tty_flip_buffer_push(port); - mod_timer(&sport->lpuart_timer, jiffies + sport->dma_rx_timeout); + + if (!sport->dma_eeop) + mod_timer(&sport->lpuart_timer, + jiffies + sport->dma_rx_timeout); } static void lpuart_dma_rx_complete(void *arg) @@ -1251,16 +1342,50 @@ static void lpuart_timer_func(struct timer_list *t) lpuart_copy_rx_to_tty(sport); } -static inline int lpuart_start_rx_dma(struct lpuart_port *sport) +static int lpuart_sched_rx_dma(struct lpuart_port *sport) +{ + unsigned long temp; + + if (!sport->dma_rx_chan_active) + return -EINVAL; + + sport->rx_dma_periods = 2; + sport->dma_rx_desc = dmaengine_prep_dma_cyclic(sport->dma_rx_chan, + sg_dma_address(&sport->rx_sgl), + sport->rx_sgl.length, + sport->rx_sgl.length / sport->rx_dma_periods, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + if (!sport->dma_rx_desc) { + dev_err(sport->port.dev, "Cannot prepare cyclic DMA\n"); + return -EFAULT; + } + + sport->dma_rx_desc->callback = lpuart_dma_rx_complete; + sport->dma_rx_desc->callback_param = sport; + sport->dma_rx_cookie = dmaengine_submit(sport->dma_rx_desc); + dma_async_issue_pending(sport->dma_rx_chan); + + if (lpuart_is_32(sport)) { + temp = lpuart32_read(&sport->port, UARTBAUD); + if (sport->dma_eeop) + temp |= UARTBAUD_RIDMAE; + temp |= UARTBAUD_RDMAE; + lpuart32_write(&sport->port, temp, UARTBAUD); + } else { + writeb(readb(sport->port.membase + UARTCR5) | UARTCR5_RDMAS, + sport->port.membase + UARTCR5); + } + + return 0; +} + +static void lpuart_get_rx_dma_rng_len(struct lpuart_port *sport) { - struct dma_slave_config dma_rx_sconfig = {}; - struct circ_buf *ring = &sport->rx_ring; - int ret, nent; int bits, baud; struct tty_port *port = &sport->port.state->port; struct tty_struct *tty = port->tty; struct ktermios *termios = &tty->termios; - struct dma_chan *chan = sport->dma_rx_chan; baud = tty_get_baud_rate(tty); @@ -1276,6 +1401,19 @@ static inline int lpuart_start_rx_dma(struct lpuart_port *sport) sport->rx_dma_rng_buf_len = (1 << (fls(sport->rx_dma_rng_buf_len) - 1)); if (sport->rx_dma_rng_buf_len < 16) sport->rx_dma_rng_buf_len = 16; +} + +static inline int lpuart_start_rx_dma(struct lpuart_port *sport) +{ + struct dma_slave_config dma_rx_sconfig = {}; + struct circ_buf *ring = &sport->rx_ring; + struct dma_chan *chan = sport->dma_rx_chan; + int ret, nent; + + if (!sport->dma_eeop) + lpuart_get_rx_dma_rng_len(sport); + else + sport->rx_dma_rng_buf_len = PAGE_SIZE; ring->buf = kzalloc(sport->rx_dma_rng_buf_len, GFP_ATOMIC); if (!ring->buf) @@ -1302,41 +1440,25 @@ static inline int lpuart_start_rx_dma(struct lpuart_port *sport) return ret; } - sport->dma_rx_desc = dmaengine_prep_dma_cyclic(chan, - sg_dma_address(&sport->rx_sgl), - sport->rx_sgl.length, - sport->rx_sgl.length / 2, - DMA_DEV_TO_MEM, - DMA_PREP_INTERRUPT); - if (!sport->dma_rx_desc) { - dev_err(sport->port.dev, "Cannot prepare cyclic DMA\n"); - return -EFAULT; - } - - sport->dma_rx_desc->callback = lpuart_dma_rx_complete; - sport->dma_rx_desc->callback_param = sport; - sport->dma_rx_cookie = dmaengine_submit(sport->dma_rx_desc); - dma_async_issue_pending(chan); - - if (lpuart_is_32(sport)) { - unsigned long 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); + sport->dma_rx_chan_active = true; + ret = lpuart_sched_rx_dma(sport); + if (ret) { + sport->dma_rx_chan_active = false; + lpuart_dma_rx_free(&sport->port, false); } - return 0; + return ret; } -static void lpuart_dma_rx_free(struct uart_port *port) +static void lpuart_dma_rx_free(struct uart_port *port, bool dma_terminate) { struct lpuart_port *sport = container_of(port, struct lpuart_port, port); struct dma_chan *chan = sport->dma_rx_chan; - dmaengine_terminate_all(chan); + if (dma_terminate) + dmaengine_terminate_sync(sport->dma_rx_chan); + dma_unmap_sg(chan->device->dev, &sport->rx_sgl, 1, DMA_FROM_DEVICE); kfree(sport->rx_ring.buf); sport->rx_ring.tail = 0; @@ -1435,9 +1557,9 @@ static int lpuart32_config_rs485(struct uart_port *port, * Note: UART is assumed to be active high. */ if (rs485->flags & SER_RS485_RTS_ON_SEND) - modem &= ~UARTMODEM_TXRTSPOL; - else if (rs485->flags & SER_RS485_RTS_AFTER_SEND) modem |= UARTMODEM_TXRTSPOL; + else if (rs485->flags & SER_RS485_RTS_AFTER_SEND) + modem &= ~UARTMODEM_TXRTSPOL; } /* Store the new configuration */ @@ -1548,8 +1670,10 @@ static void lpuart_setup_watermark(struct lpuart_port *sport) writeb(UARTSFIFO_RXUF, sport->port.membase + UARTSFIFO); } + if (uart_console(&sport->port)) + sport->rx_watermark = 1; writeb(0, sport->port.membase + UARTTWFIFO); - writeb(1, sport->port.membase + UARTRWFIFO); + writeb(sport->rx_watermark, sport->port.membase + UARTRWFIFO); /* Restore cr2 */ writeb(cr2_saved, sport->port.membase + UARTCR2); @@ -1570,6 +1694,7 @@ static void lpuart32_setup_watermark(struct lpuart_port *sport) { unsigned long val, ctrl; unsigned long ctrl_saved; + unsigned long rxiden_cnt; ctrl = lpuart32_read(&sport->port, UARTCTRL); ctrl_saved = ctrl; @@ -1581,12 +1706,26 @@ static void lpuart32_setup_watermark(struct lpuart_port *sport) val = lpuart32_read(&sport->port, UARTFIFO); val |= UARTFIFO_TXFE | UARTFIFO_RXFE; val |= UARTFIFO_TXFLUSH | UARTFIFO_RXFLUSH; + val &= ~(UARTFIFO_RXIDEN_MASK << UARTFIFO_RXIDEN_OFF); + rxiden_cnt = sport->dma_eeop ? 0 : UARTFIFO_RXIDEN_RDRF; + val |= ((rxiden_cnt & UARTFIFO_RXIDEN_MASK) << + UARTFIFO_RXIDEN_OFF); lpuart32_write(&sport->port, val, UARTFIFO); /* set the watermark */ - val = (0x1 << UARTWATER_RXWATER_OFF) | (0x0 << UARTWATER_TXWATER_OFF); + if (uart_console(&sport->port)) + sport->rx_watermark = 1; + val = (sport->rx_watermark << UARTWATER_RXWATER_OFF) | + (0x0 << UARTWATER_TXWATER_OFF); lpuart32_write(&sport->port, val, UARTWATER); + /* set RTS watermark */ + if (!uart_console(&sport->port)) { + val = lpuart32_read(&sport->port, UARTMODIR); + val = (sport->rxfifo_size >> 1) << UARTMODIR_RTSWATER_S; + lpuart32_write(&sport->port, val, UARTMODIR); + } + /* Restore cr2 */ lpuart32_write(&sport->port, ctrl_saved, UARTCTRL); } @@ -1598,17 +1737,29 @@ static void lpuart32_setup_watermark_enable(struct lpuart_port *sport) lpuart32_setup_watermark(sport); temp = lpuart32_read(&sport->port, UARTCTRL); - temp |= UARTCTRL_RE | UARTCTRL_TE | UARTCTRL_ILIE; + temp |= UARTCTRL_RE | UARTCTRL_TE; + temp |= UARTCTRL_IDLECFG << UARTCTRL_IDLECFG_OFF; lpuart32_write(&sport->port, temp, UARTCTRL); } static void rx_dma_timer_init(struct lpuart_port *sport) { + if (sport->dma_eeop) + return; + timer_setup(&sport->lpuart_timer, lpuart_timer_func, 0); sport->lpuart_timer.expires = jiffies + sport->dma_rx_timeout; add_timer(&sport->lpuart_timer); } +static void lpuart_del_timer_sync(struct lpuart_port *sport) +{ + if (sport->dma_eeop) + return; + + del_timer_sync(&sport->lpuart_timer); +} + static void lpuart_request_dma(struct lpuart_port *sport) { sport->dma_tx_chan = dma_request_chan(sport->port.dev, "tx"); @@ -1695,10 +1846,23 @@ err: sport->lpuart_dma_rx_use = false; } +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 */ @@ -1712,42 +1876,63 @@ static int lpuart_startup(struct uart_port *port) UARTPFIFO_FIFOSIZE_MASK); lpuart_request_dma(sport); + lpuart_hw_setup(sport); - spin_lock_irqsave(&sport->port.lock, flags); - - lpuart_setup_watermark_enable(sport); - - lpuart_rx_dma_startup(sport); - lpuart_tx_dma_startup(sport); + return 0; +} - spin_unlock_irqrestore(&sport->port.lock, flags); +static void lpuart32_hw_disable(struct lpuart_port *sport) +{ + unsigned long temp; - return 0; + temp = lpuart32_read(&sport->port, UARTCTRL); + temp &= ~(UARTCTRL_RIE | UARTCTRL_ILIE | UARTCTRL_RE | + UARTCTRL_TIE | UARTCTRL_TE); + lpuart32_write(&sport->port, temp, UARTCTRL); } static void lpuart32_configure(struct lpuart_port *sport) { unsigned long temp; - if (sport->lpuart_dma_rx_use) { - /* RXWATER must be 0 */ - temp = lpuart32_read(&sport->port, UARTWATER); - temp &= ~(UARTWATER_WATER_MASK << UARTWATER_RXWATER_OFF); - lpuart32_write(&sport->port, temp, UARTWATER); - } temp = lpuart32_read(&sport->port, UARTCTRL); if (!sport->lpuart_dma_rx_use) - temp |= UARTCTRL_RIE; + temp |= UARTCTRL_RIE | UARTCTRL_ILIE; if (!sport->lpuart_dma_tx_use) temp |= UARTCTRL_TIE; 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); - unsigned long flags; + struct tty_port *tty_port = &sport->port.state->port; unsigned long temp; + int ret; + + /* some modem may need reset */ + if (!tty_port_suspended(tty_port)) { + ret = device_reset(sport->port.dev); + if (ret && ret != -ENOENT) + return ret; + } /* determine FIFO size */ temp = lpuart32_read(&sport->port, UARTFIFO); @@ -1771,32 +1956,23 @@ static int lpuart32_startup(struct uart_port *port) } lpuart_request_dma(sport); + lpuart32_hw_setup(sport); - spin_lock_irqsave(&sport->port.lock, flags); - - lpuart32_setup_watermark_enable(sport); - - lpuart_rx_dma_startup(sport); - lpuart_tx_dma_startup(sport); - - lpuart32_configure(sport); - - spin_unlock_irqrestore(&sport->port.lock, flags); return 0; } static void lpuart_dma_shutdown(struct lpuart_port *sport) { if (sport->lpuart_dma_rx_use) { - del_timer_sync(&sport->lpuart_timer); - lpuart_dma_rx_free(&sport->port); + lpuart_del_timer_sync(sport); + lpuart_dma_rx_free(&sport->port, true); } if (sport->lpuart_dma_tx_use) { - if (wait_event_interruptible(sport->dma_wait, - !sport->dma_tx_in_progress) != false) { + if (wait_event_interruptible_timeout(sport->dma_wait, + !sport->dma_tx_in_progress, msecs_to_jiffies(300)) <= 0) { sport->dma_tx_in_progress = false; - dmaengine_terminate_all(sport->dma_tx_chan); + dmaengine_terminate_sync(sport->dma_tx_chan); } } @@ -1820,6 +1996,8 @@ static void lpuart_shutdown(struct uart_port *port) UARTCR2_TIE | UARTCR2_TCIE | UARTCR2_RIE); writeb(temp, port->membase + UARTCR2); + if (sport->lpuart_dma_rx_use) + sport->dma_rx_chan_active = false; spin_unlock_irqrestore(&port->lock, flags); lpuart_dma_shutdown(sport); @@ -1834,12 +2012,25 @@ static void lpuart32_shutdown(struct uart_port *port) spin_lock_irqsave(&port->lock, flags); + /* clear statue */ + temp = lpuart32_read(&sport->port, UARTSTAT); + lpuart32_write(&sport->port, temp, UARTSTAT); + + /* disable Rx/Tx DMA */ + temp = lpuart32_read(port, UARTBAUD); + temp &= ~(UARTBAUD_TDMAE | UARTBAUD_RDMAE | UARTBAUD_RIDMAE); + lpuart32_write(port, temp, UARTBAUD); + /* disable Rx/Tx and interrupts */ temp = lpuart32_read(port, UARTCTRL); - temp &= ~(UARTCTRL_TE | UARTCTRL_RE | - UARTCTRL_TIE | UARTCTRL_TCIE | UARTCTRL_RIE); + temp &= ~(UARTCTRL_TE | UARTCTRL_RE | UARTCTRL_TIE | + UARTCTRL_TCIE | UARTCTRL_RIE | UARTCTRL_ILIE | + UARTCTRL_LOOPS); lpuart32_write(port, temp, UARTCTRL); + lpuart32_write(port, 0, UARTMODIR); + if (sport->lpuart_dma_rx_use) + sport->dma_rx_chan_active = false; spin_unlock_irqrestore(&port->lock, flags); lpuart_dma_shutdown(sport); @@ -1935,11 +2126,12 @@ lpuart_set_termios(struct uart_port *port, struct ktermios *termios, * baud rate and restart Rx DMA path. * * Since timer function acqures sport->port.lock, need to stop before - * acquring same lock because otherwise del_timer_sync() can deadlock. + * acquring same lock because otherwise lpuart_del_timer_sync() can deadlock. */ if (old && sport->lpuart_dma_rx_use) { - del_timer_sync(&sport->lpuart_timer); - lpuart_dma_rx_free(&sport->port); + sport->dma_rx_chan_active = false; + lpuart_del_timer_sync(sport); + lpuart_dma_rx_free(&sport->port, true); } spin_lock_irqsave(&sport->port.lock, flags); @@ -2098,6 +2290,8 @@ lpuart32_set_termios(struct uart_port *port, struct ktermios *termios, ctrl = old_ctrl = lpuart32_read(&sport->port, UARTCTRL); bd = lpuart32_read(&sport->port, UARTBAUD); modem = lpuart32_read(&sport->port, UARTMODIR); + sport->is_cs7 = false; + /* * only support CS8 and CS7, and for CS7 must enable PE. * supported mode: @@ -2173,11 +2367,12 @@ lpuart32_set_termios(struct uart_port *port, struct ktermios *termios, * baud rate and restart Rx DMA path. * * Since timer function acqures sport->port.lock, need to stop before - * acquring same lock because otherwise del_timer_sync() can deadlock. + * acquring same lock because otherwise lpuart_del_timer_sync() can deadlock. */ if (old && sport->lpuart_dma_rx_use) { - del_timer_sync(&sport->lpuart_timer); - lpuart_dma_rx_free(&sport->port); + sport->dma_rx_chan_active = false; + lpuart_del_timer_sync(sport); + lpuart_dma_rx_free(&sport->port, true); } spin_lock_irqsave(&sport->port.lock, flags); @@ -2206,6 +2401,7 @@ lpuart32_set_termios(struct uart_port *port, struct ktermios *termios, uart_update_timeout(port, termios->c_cflag, baud); /* wait transmit engin complete */ + lpuart32_write(&sport->port, 0, UARTMODIR); lpuart32_wait_bit_set(&sport->port, UARTSTAT, UARTSTAT_TC); /* disable transmit and receive */ @@ -2218,6 +2414,9 @@ lpuart32_set_termios(struct uart_port *port, struct ktermios *termios, lpuart32_write(&sport->port, ctrl, UARTCTRL); /* restore control register */ + if ((ctrl & (UARTCTRL_PE | UARTCTRL_M)) == UARTCTRL_PE) + sport->is_cs7 = true; + if (old && sport->lpuart_dma_rx_use) { if (!lpuart_start_rx_dma(sport)) rx_dma_timer_init(sport); @@ -2279,6 +2478,7 @@ static const struct uart_ops lpuart_pops = { .break_ctl = lpuart_break_ctl, .startup = lpuart_startup, .shutdown = lpuart_shutdown, + .pm = lpuart_uart_pm, .set_termios = lpuart_set_termios, .type = lpuart_type, .request_port = lpuart_request_port, @@ -2303,6 +2503,7 @@ static const struct uart_ops lpuart32_pops = { .break_ctl = lpuart32_break_ctl, .startup = lpuart32_startup, .shutdown = lpuart32_shutdown, + .pm = lpuart_uart_pm, .set_termios = lpuart32_set_termios, .type = lpuart_type, .request_port = lpuart_request_port, @@ -2644,6 +2845,29 @@ static struct uart_driver lpuart_reg = { .cons = LPUART_CONSOLE, }; +static int lpuart_attach_pd(struct device *dev) +{ + struct device *pd_uart; + struct device_link *link; + + if (dev->pm_domain) + return 0; + + pd_uart = dev_pm_domain_attach_by_name(dev, "uart"); + if (IS_ERR(pd_uart)) + return PTR_ERR(pd_uart); + link = device_link_add(dev, pd_uart, DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (IS_ERR(link)) { + dev_err(dev, "Failed to add device_link to uart pd: %ld\n", + PTR_ERR(link)); + return PTR_ERR(link); + } + + return 0; +} + static int lpuart_probe(struct platform_device *pdev) { const struct lpuart_soc_data *sdata = of_device_get_match_data(&pdev->dev); @@ -2666,6 +2890,9 @@ static int lpuart_probe(struct platform_device *pdev) sport->port.dev = &pdev->dev; sport->port.type = PORT_LPUART; sport->devtype = sdata->devtype; + sport->rx_watermark = sdata->rx_watermark; + sport->dma_eeop = is_imx8qxp_lpuart(sport) || is_imx8ulp_lpuart(sport); + ret = platform_get_irq(pdev, 0); if (ret < 0) return ret; @@ -2683,6 +2910,10 @@ static int lpuart_probe(struct platform_device *pdev) else sport->port.rs485_config = lpuart_config_rs485; + ret = lpuart_attach_pd(&pdev->dev); + if (ret) + return ret; + sport->ipg_clk = devm_clk_get(&pdev->dev, "ipg"); if (IS_ERR(sport->ipg_clk)) { ret = PTR_ERR(sport->ipg_clk); @@ -2702,23 +2933,18 @@ static int lpuart_probe(struct platform_device *pdev) ret = of_alias_get_id(np, "serial"); if (ret < 0) { - ret = ida_simple_get(&fsl_lpuart_ida, 0, UART_NR, GFP_KERNEL); - if (ret < 0) { - dev_err(&pdev->dev, "port line is full, add device failed\n"); - return ret; - } - sport->id_allocated = true; + dev_err(&pdev->dev, "failed to get alias id, errno %d\n", ret); + return ret; } if (ret >= ARRAY_SIZE(lpuart_ports)) { dev_err(&pdev->dev, "serial%d out of range\n", ret); - ret = -EINVAL; - goto failed_out_of_range; + return -EINVAL; } sport->port.line = ret; ret = lpuart_enable_clks(sport); if (ret) - goto failed_clock_enable; + return ret; sport->port.uartclk = lpuart_get_baud_clk_rate(sport); lpuart_ports[sport->port.line] = sport; @@ -2738,6 +2964,11 @@ static int lpuart_probe(struct platform_device *pdev) if (ret) goto failed_irq_request; + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_set_autosuspend_delay(&pdev->dev, UART_AUTOSUSPEND_TIMEOUT); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + ret = uart_add_one_port(&lpuart_reg, &sport->port); if (ret) goto failed_attach_port; @@ -2765,12 +2996,11 @@ failed_get_rs485: failed_reset: uart_remove_one_port(&lpuart_reg, &sport->port); failed_attach_port: + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); failed_irq_request: lpuart_disable_clks(sport); -failed_clock_enable: -failed_out_of_range: - if (sport->id_allocated) - ida_simple_remove(&fsl_lpuart_ida, sport->port.line); return ret; } @@ -2780,9 +3010,6 @@ static int lpuart_remove(struct platform_device *pdev) uart_remove_one_port(&lpuart_reg, &sport->port); - if (sport->id_allocated) - ida_simple_remove(&fsl_lpuart_ida, sport->port.line); - lpuart_disable_clks(sport); if (sport->dma_tx_chan) @@ -2791,100 +3018,237 @@ static int lpuart_remove(struct platform_device *pdev) if (sport->dma_rx_chan) dma_release_channel(sport->dma_rx_chan); + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); return 0; } -static int __maybe_unused lpuart_suspend(struct device *dev) +static int __maybe_unused lpuart_runtime_suspend(struct device *dev) { - struct lpuart_port *sport = dev_get_drvdata(dev); - unsigned long temp; - bool irq_wake; + struct platform_device *pdev = to_platform_device(dev); + struct lpuart_port *sport = platform_get_drvdata(pdev); - 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); - } 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); - } + lpuart_disable_clks(sport); - uart_suspend_port(&lpuart_reg, &sport->port); + return 0; +}; - /* uart_suspend_port() might set wakeup flag */ - irq_wake = irqd_is_wakeup_set(irq_get_irq_data(sport->port.irq)); +static int __maybe_unused lpuart_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct lpuart_port *sport = platform_get_drvdata(pdev); - 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) { - del_timer_sync(&sport->lpuart_timer); - lpuart_dma_rx_free(&sport->port); - } + return lpuart_enable_clks(sport); +}; - /* Disable Rx DMA to use UART port as wakeup source */ - if (lpuart_is_32(sport)) { - temp = lpuart32_read(&sport->port, UARTBAUD); - lpuart32_write(&sport->port, temp & ~UARTBAUD_RDMAE, - UARTBAUD); +static void serial_lpuart_enable_wakeup(struct lpuart_port *sport, bool on) +{ + unsigned int val; + + if (lpuart_is_32(sport)) { + val = lpuart32_read(&sport->port, UARTCTRL); + if (on) { + /* set rx_watermark to 0 in wakeup source mode */ + lpuart32_write(&sport->port, 0, UARTWATER); + val |= UARTCTRL_RIE; } else { - writeb(readb(sport->port.membase + UARTCR5) & - ~UARTCR5_RDMAS, sport->port.membase + UARTCR5); + val &= ~UARTCTRL_RIE; } + lpuart32_write(&sport->port, val, UARTCTRL); + } else { + val = readb(sport->port.membase + UARTCR2); + if (on) + val |= UARTCR2_RIE; + else + val &= ~UARTCR2_RIE; + writeb(val, sport->port.membase + UARTCR2); } +} - if (sport->lpuart_dma_tx_use) { - sport->dma_tx_in_progress = false; - dmaengine_terminate_all(sport->dma_tx_chan); +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; + + tty = tty_port_tty_get(port); + if (tty) { + tty_dev = tty->dev; + may_wake = device_may_wakeup(tty_dev); + tty_kref_put(tty); } - if (sport->port.suspended && !irq_wake) - lpuart_disable_clks(sport); + if ((tty_port_initialized(port) && may_wake) || + (!console_suspend_enabled && uart_console(&sport->port))) + return true; - return 0; + return false; } -static int __maybe_unused lpuart_resume(struct device *dev) +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 (sport->port.suspended && !irq_wake) - lpuart_enable_clks(sport); + if (lpuart_uport_is_active(sport)) + serial_lpuart_enable_wakeup(sport, !!irq_wake); - if (lpuart_is_32(sport)) - lpuart32_setup_watermark_enable(sport); - else - lpuart_setup_watermark_enable(sport); + pinctrl_pm_select_sleep_state(dev); - 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; + 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)) { + val = lpuart32_read(&sport->port, UARTSTAT); + lpuart32_write(&sport->port, val, UARTSTAT); } } - lpuart_tx_dma_startup(sport); + return 0; +} - if (lpuart_is_32(sport)) - lpuart32_configure(sport); +static int __maybe_unused lpuart_suspend(struct device *dev) +{ + struct lpuart_port *sport = dev_get_drvdata(dev); + unsigned long temp; + unsigned long flags; + uart_suspend_port(&lpuart_reg, &sport->port); + + 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); + } + + if (sport->lpuart_dma_rx_use) + sport->dma_rx_chan_active = false; + spin_unlock_irqrestore(&sport->port.lock, flags); + + 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, true); + + /* 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_sync(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); + } + + return 0; +} + +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; + + /* 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 __maybe_unused 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 SIMPLE_DEV_PM_OPS(lpuart_pm_ops, lpuart_suspend, lpuart_resume); +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) +}; static struct platform_driver lpuart_driver = { .probe = lpuart_probe, @@ -2912,7 +3276,6 @@ static int __init lpuart_serial_init(void) static void __exit lpuart_serial_exit(void) { - ida_destroy(&fsl_lpuart_ida); platform_driver_unregister(&lpuart_driver); uart_unregister_driver(&lpuart_reg); } |