Add DMA support for 32-bit variant of the LPUART, such as LS1021A. Signed-off-by: Tomonori Sakita <tomonori.sakita@xxxxxxxxxx> Signed-off-by: Atsushi Nemoto <atsushi.nemoto@xxxxxxxxxx> --- Changes in v2: Fix build with CONFIG_PM_SLEEP=y. drivers/tty/serial/fsl_lpuart.c | 192 ++++++++++++++++++++++++++++++++++------ 1 file changed, 163 insertions(+), 29 deletions(-) diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c index d0a98ec..ea1c85e3 100644 --- a/drivers/tty/serial/fsl_lpuart.c +++ b/drivers/tty/serial/fsl_lpuart.c @@ -426,6 +426,17 @@ static void lpuart_dma_tx_complete(void *arg) spin_unlock_irqrestore(&sport->port.lock, flags); } +static dma_addr_t lpuart_dma_datareg_addr(struct lpuart_port *sport) +{ + switch (sport->port.iotype) { + case UPIO_MEM32: + return sport->port.mapbase + UARTDATA; + case UPIO_MEM32BE: + return sport->port.mapbase + UARTDATA + sizeof(u32) - 1; + } + return sport->port.mapbase + UARTDR; +} + static int lpuart_dma_tx_request(struct uart_port *port) { struct lpuart_port *sport = container_of(port, @@ -433,7 +444,7 @@ static int lpuart_dma_tx_request(struct uart_port *port) struct dma_slave_config dma_tx_sconfig = {}; int ret; - dma_tx_sconfig.dst_addr = sport->port.mapbase + UARTDR; + dma_tx_sconfig.dst_addr = lpuart_dma_datareg_addr(sport); dma_tx_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; dma_tx_sconfig.dst_maxburst = 1; dma_tx_sconfig.direction = DMA_MEM_TO_DEV; @@ -636,13 +647,19 @@ static void lpuart_start_tx(struct uart_port *port) static void lpuart32_start_tx(struct uart_port *port) { struct lpuart_port *sport = container_of(port, struct lpuart_port, port); + struct circ_buf *xmit = &sport->port.state->xmit; unsigned long temp; - temp = lpuart32_read(port, UARTCTRL); - lpuart32_write(port, temp | UARTCTRL_TIE, UARTCTRL); + if (sport->lpuart_dma_tx_use) { + if (!uart_circ_empty(xmit) && !uart_tx_stopped(port)) + lpuart_dma_tx(sport); + } else { + temp = lpuart32_read(port, UARTCTRL); + lpuart32_write(port, temp | UARTCTRL_TIE, UARTCTRL); - if (lpuart32_read(port, UARTSTAT) & UARTSTAT_TDRE) - lpuart32_transmit_buffer(sport); + if (lpuart32_read(port, UARTSTAT) & UARTSTAT_TDRE) + lpuart32_transmit_buffer(sport); + } } /* return TIOCSER_TEMT when transmitter is not busy */ @@ -872,11 +889,10 @@ static irqreturn_t lpuart32_int(int irq, void *dev_id) rxcount = lpuart32_read(&sport->port, UARTWATER); rxcount = rxcount >> UARTWATER_RXCNT_OFF; - if (sts & UARTSTAT_RDRF || rxcount > 0) + if ((sts & UARTSTAT_RDRF || rxcount > 0) && !sport->lpuart_dma_rx_use) lpuart32_rxint(irq, dev_id); - if ((sts & UARTSTAT_TDRE) && - !(lpuart32_read(&sport->port, UARTBAUD) & UARTBAUD_TDMAE)) + if ((sts & UARTSTAT_TDRE) && !sport->lpuart_dma_tx_use) lpuart_txint(irq, dev_id); lpuart32_write(&sport->port, sts, UARTSTAT); @@ -891,18 +907,31 @@ static void lpuart_copy_rx_to_tty(struct lpuart_port *sport) struct circ_buf *ring = &sport->rx_ring; unsigned long flags; int count = 0; - unsigned char sr; - sr = readb(sport->port.membase + UARTSR1); + if (lpuart_is_32(sport)) { + unsigned long sr = lpuart32_read(&sport->port, UARTSTAT); - if (sr & (UARTSR1_PE | UARTSR1_FE)) { - /* Read DR to clear the error flags */ - readb(sport->port.membase + UARTDR); + if (sr & (UARTSTAT_PE | UARTSTAT_FE)) { + /* Read DR to clear the error flags */ + lpuart32_read(&sport->port, UARTDATA); + + if (sr & UARTSTAT_PE) + sport->port.icount.parity++; + else if (sr & UARTSTAT_FE) + sport->port.icount.frame++; + } + } else { + unsigned char sr = readb(sport->port.membase + UARTSR1); + + if (sr & (UARTSR1_PE | UARTSR1_FE)) { + /* Read DR to clear the error flags */ + readb(sport->port.membase + UARTDR); - if (sr & UARTSR1_PE) - sport->port.icount.parity++; - else if (sr & UARTSR1_FE) - sport->port.icount.frame++; + if (sr & UARTSR1_PE) + sport->port.icount.parity++; + else if (sr & UARTSR1_FE) + sport->port.icount.frame++; + } } async_tx_ack(sport->dma_rx_desc); @@ -1025,7 +1054,7 @@ static inline int lpuart_start_rx_dma(struct lpuart_port *sport) return -EINVAL; } - dma_rx_sconfig.src_addr = sport->port.mapbase + UARTDR; + dma_rx_sconfig.src_addr = lpuart_dma_datareg_addr(sport); dma_rx_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; dma_rx_sconfig.src_maxburst = 1; dma_rx_sconfig.direction = DMA_DEV_TO_MEM; @@ -1053,8 +1082,14 @@ static inline int lpuart_start_rx_dma(struct lpuart_port *sport) sport->dma_rx_cookie = dmaengine_submit(sport->dma_rx_desc); dma_async_issue_pending(sport->dma_rx_chan); - writeb(readb(sport->port.membase + UARTCR5) | UARTCR5_RDMAS, - sport->port.membase + UARTCR5); + 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); + } return 0; } @@ -1354,8 +1389,41 @@ static int lpuart32_startup(struct uart_port *port) lpuart32_setup_watermark(sport); temp = lpuart32_read(&sport->port, UARTCTRL); - temp |= (UARTCTRL_RIE | UARTCTRL_TIE | UARTCTRL_RE | UARTCTRL_TE); - temp |= UARTCTRL_ILIE; + temp |= UARTCTRL_RE | UARTCTRL_TE | UARTCTRL_ILIE; + lpuart32_write(&sport->port, temp, UARTCTRL); + + if (sport->dma_rx_chan && !lpuart_start_rx_dma(sport)) { + /* set Rx DMA timeout */ + sport->dma_rx_timeout = msecs_to_jiffies(DMA_RX_TIMEOUT); + if (!sport->dma_rx_timeout) + sport->dma_rx_timeout = 1; + + sport->lpuart_dma_rx_use = true; + rx_dma_timer_init(sport); + } else { + sport->lpuart_dma_rx_use = false; + } + + if (sport->dma_tx_chan && !lpuart_dma_tx_request(port)) { + init_waitqueue_head(&sport->dma_wait); + sport->lpuart_dma_tx_use = true; + temp = lpuart32_read(&sport->port, UARTBAUD); + lpuart32_write(&sport->port, temp | UARTBAUD_TDMAE, UARTBAUD); + } else { + sport->lpuart_dma_tx_use = false; + } + + 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; + if (!sport->lpuart_dma_tx_use) + temp |= UARTCTRL_TIE; lpuart32_write(&sport->port, temp, UARTCTRL); spin_unlock_irqrestore(&sport->port.lock, flags); @@ -1396,6 +1464,8 @@ static void lpuart_shutdown(struct uart_port *port) static void lpuart32_shutdown(struct uart_port *port) { + struct lpuart_port *sport = + container_of(port, struct lpuart_port, port); unsigned long temp; unsigned long flags; @@ -1408,6 +1478,21 @@ static void lpuart32_shutdown(struct uart_port *port) lpuart32_write(port, temp, UARTCTRL); spin_unlock_irqrestore(&port->lock, flags); + + if (sport->lpuart_dma_rx_use) { + del_timer_sync(&sport->lpuart_timer); + lpuart_dma_rx_free(&sport->port); + } + + if (sport->lpuart_dma_tx_use) { + if (wait_event_interruptible(sport->dma_wait, + !sport->dma_tx_in_progress)) { + sport->dma_tx_in_progress = false; + dmaengine_terminate_all(sport->dma_tx_chan); + } + + lpuart32_stop_tx(port); + } } static void @@ -1633,7 +1718,10 @@ lpuart32_serial_setbrg(struct lpuart_port *sport, unsigned int baudrate) tmp &= ~UARTBAUD_SBR_MASK; tmp |= sbr & UARTBAUD_SBR_MASK; - tmp &= ~(UARTBAUD_TDMAE | UARTBAUD_RDMAE); + if (!sport->lpuart_dma_rx_use) + tmp &= ~UARTBAUD_RDMAE; + if (!sport->lpuart_dma_tx_use) + tmp &= ~UARTBAUD_TDMAE; lpuart32_write(&sport->port, tmp, UARTBAUD); } @@ -1711,6 +1799,18 @@ lpuart32_set_termios(struct uart_port *port, struct ktermios *termios, /* ask the core to calculate the divisor */ baud = uart_get_baud_rate(port, termios, old, 50, port->uartclk / 4); + /* + * Need to update the Ring buffer length according to the selected + * 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. + */ + if (old && sport->lpuart_dma_rx_use) { + del_timer_sync(&sport->lpuart_timer); + lpuart_dma_rx_free(&sport->port); + } + spin_lock_irqsave(&sport->port.lock, flags); sport->port.read_status_mask = 0; @@ -1749,6 +1849,13 @@ lpuart32_set_termios(struct uart_port *port, struct ktermios *termios, lpuart32_write(&sport->port, ctrl, UARTCTRL); /* restore control register */ + if (old && sport->lpuart_dma_rx_use) { + if (!lpuart_start_rx_dma(sport)) + rx_dma_timer_init(sport); + else + sport->lpuart_dma_rx_use = false; + } + spin_unlock_irqrestore(&sport->port.lock, flags); } @@ -2318,8 +2425,14 @@ static int lpuart_suspend(struct device *dev) } /* Disable Rx DMA to use UART port as wakeup source */ - writeb(readb(sport->port.membase + UARTCR5) & ~UARTCR5_RDMAS, - sport->port.membase + UARTCR5); + 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); + } } if (sport->lpuart_dma_tx_use) { @@ -2345,8 +2458,7 @@ static int lpuart_resume(struct device *dev) if (lpuart_is_32(sport)) { lpuart32_setup_watermark(sport); temp = lpuart32_read(&sport->port, UARTCTRL); - temp |= (UARTCTRL_RIE | UARTCTRL_TIE | UARTCTRL_RE | - UARTCTRL_TE | UARTCTRL_ILIE); + temp |= UARTCTRL_RE | UARTCTRL_TE | UARTCTRL_ILIE; lpuart32_write(&sport->port, temp, UARTCTRL); } else { lpuart_setup_watermark(sport); @@ -2365,14 +2477,36 @@ static int lpuart_resume(struct device *dev) } if (sport->dma_tx_chan && !lpuart_dma_tx_request(&sport->port)) { - init_waitqueue_head(&sport->dma_wait); - sport->lpuart_dma_tx_use = true; + init_waitqueue_head(&sport->dma_wait); + sport->lpuart_dma_tx_use = true; + if (lpuart_is_32(sport)) { + temp = lpuart32_read(&sport->port, UARTBAUD); + lpuart32_write(&sport->port, + temp | UARTBAUD_TDMAE, UARTBAUD); + } else { writeb(readb(sport->port.membase + UARTCR5) | UARTCR5_TDMAS, sport->port.membase + UARTCR5); + } } else { sport->lpuart_dma_tx_use = false; } + if (lpuart_is_32(sport)) { + 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; + if (!sport->lpuart_dma_tx_use) + temp |= UARTCTRL_TIE; + lpuart32_write(&sport->port, temp, UARTCTRL); + } + uart_resume_port(&lpuart_reg, &sport->port); return 0; -- 2.1.4