serial: fsl_lpuart: DMA support for 32-bit variant
authorAtsushi Nemoto <atsushi.nemoto@sord.co.jp>
Wed, 23 Jan 2019 03:20:17 +0000 (12:20 +0900)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 30 Jan 2019 08:31:30 +0000 (09:31 +0100)
Add DMA support for 32-bit variant of the LPUART, such as LS1021A.

Signed-off-by: Tomonori Sakita <tomonori.sakita@sord.co.jp>
Signed-off-by: Atsushi Nemoto <atsushi.nemoto@sord.co.jp>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/serial/fsl_lpuart.c

index d0a98ec..ea1c85e 100644 (file)
@@ -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;