From 419200f349faa870cc13eceaa373e76905f97935 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Tue, 21 Oct 2014 17:36:41 +0200 Subject: serial: fsl_lpuart: delete timer on shutdown If the serial port gets closed while a RX transfer is in progress, the timer might fire after the serial port shutdown finished. Setup the timer on UART startup which allows to delete the timer unconditionally on shutdown. This also saves the initialization on each transfer too. --- drivers/tty/serial/fsl_lpuart.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c index 6dd53af546a3..b1905a93fac9 100644 --- a/drivers/tty/serial/fsl_lpuart.c +++ b/drivers/tty/serial/fsl_lpuart.c @@ -506,9 +506,6 @@ static inline void lpuart_prepare_rx(struct lpuart_port *sport) spin_lock_irqsave(&sport->port.lock, flags); - init_timer(&sport->lpuart_timer); - sport->lpuart_timer.function = lpuart_timer_func; - sport->lpuart_timer.data = (unsigned long)sport; sport->lpuart_timer.expires = jiffies + sport->dma_rx_timeout; add_timer(&sport->lpuart_timer); @@ -1106,6 +1103,8 @@ static int lpuart_startup(struct uart_port *port) sport->lpuart_dma_use = false; } else { sport->lpuart_dma_use = true; + setup_timer(&sport->lpuart_timer, lpuart_timer_func, + (unsigned long)sport); temp = readb(port->membase + UARTCR5); writeb(temp | UARTCR5_TDMAS, port->membase + UARTCR5); } @@ -1180,6 +1179,8 @@ static void lpuart_shutdown(struct uart_port *port) devm_free_irq(port->dev, port->irq, sport); if (sport->lpuart_dma_use) { + del_timer_sync(&sport->lpuart_timer); + lpuart_dma_tx_free(port); lpuart_dma_rx_free(port); } -- cgit v1.2.3 From ed2406b4d4ea455df37c9feee244169795bf0ac0 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Fri, 31 Oct 2014 13:35:20 +0100 Subject: serial: fsl_lpuart: move DMA channel request to probe Move the DMA channel request to probe to avoid requesting the DMA channel on each opening of the ttyLPx device. This also fixes a potential issue that TX channel is not freed when only RX channel allocation fails. The DMA channels are now handled independently, so one could use UART with DMA only in TX direction for instance. --- drivers/tty/serial/fsl_lpuart.c | 96 +++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 52 deletions(-) diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c index b1905a93fac9..629291d20d6c 100644 --- a/drivers/tty/serial/fsl_lpuart.c +++ b/drivers/tty/serial/fsl_lpuart.c @@ -237,7 +237,8 @@ struct lpuart_port { unsigned int rxfifo_size; bool lpuart32; - bool lpuart_dma_use; + bool lpuart_dma_tx_use; + bool lpuart_dma_rx_use; struct dma_chan *dma_tx_chan; struct dma_chan *dma_rx_chan; struct dma_async_tx_descriptor *dma_tx_desc; @@ -568,7 +569,7 @@ static void lpuart_start_tx(struct uart_port *port) temp = readb(port->membase + UARTCR2); writeb(temp | UARTCR2_TIE, port->membase + UARTCR2); - if (sport->lpuart_dma_use) { + if (sport->lpuart_dma_tx_use) { if (!uart_circ_empty(xmit) && !sport->dma_tx_in_progress) lpuart_prepare_tx(sport); } else { @@ -760,14 +761,14 @@ static irqreturn_t lpuart_int(int irq, void *dev_id) sts = readb(sport->port.membase + UARTSR1); if (sts & UARTSR1_RDRF) { - if (sport->lpuart_dma_use) + if (sport->lpuart_dma_rx_use) lpuart_prepare_rx(sport); else lpuart_rxint(irq, dev_id); } if (sts & UARTSR1_TDRE && !(readb(sport->port.membase + UARTCR5) & UARTCR5_TDMAS)) { - if (sport->lpuart_dma_use) + if (sport->lpuart_dma_tx_use) lpuart_pio_tx(sport); else lpuart_txint(irq, dev_id); @@ -950,26 +951,17 @@ static int lpuart_dma_tx_request(struct uart_port *port) { struct lpuart_port *sport = container_of(port, struct lpuart_port, port); - struct dma_chan *tx_chan; struct dma_slave_config dma_tx_sconfig; dma_addr_t dma_bus; unsigned char *dma_buf; int ret; - tx_chan = dma_request_slave_channel(sport->port.dev, "tx"); - - if (!tx_chan) { - dev_err(sport->port.dev, "Dma tx channel request failed!\n"); - return -ENODEV; - } - - dma_bus = dma_map_single(tx_chan->device->dev, + dma_bus = dma_map_single(sport->dma_tx_chan->device->dev, sport->port.state->xmit.buf, UART_XMIT_SIZE, DMA_TO_DEVICE); - if (dma_mapping_error(tx_chan->device->dev, dma_bus)) { + if (dma_mapping_error(sport->dma_tx_chan->device->dev, dma_bus)) { dev_err(sport->port.dev, "dma_map_single tx failed\n"); - dma_release_channel(tx_chan); return -ENOMEM; } @@ -978,16 +970,14 @@ static int lpuart_dma_tx_request(struct uart_port *port) dma_tx_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; dma_tx_sconfig.dst_maxburst = sport->txfifo_size; dma_tx_sconfig.direction = DMA_MEM_TO_DEV; - ret = dmaengine_slave_config(tx_chan, &dma_tx_sconfig); + ret = dmaengine_slave_config(sport->dma_tx_chan, &dma_tx_sconfig); if (ret < 0) { dev_err(sport->port.dev, "Dma slave config failed, err = %d\n", ret); - dma_release_channel(tx_chan); return ret; } - sport->dma_tx_chan = tx_chan; sport->dma_tx_buf_virt = dma_buf; sport->dma_tx_buf_bus = dma_bus; sport->dma_tx_in_progress = 0; @@ -999,34 +989,24 @@ static int lpuart_dma_rx_request(struct uart_port *port) { struct lpuart_port *sport = container_of(port, struct lpuart_port, port); - struct dma_chan *rx_chan; struct dma_slave_config dma_rx_sconfig; dma_addr_t dma_bus; unsigned char *dma_buf; int ret; - rx_chan = dma_request_slave_channel(sport->port.dev, "rx"); - - if (!rx_chan) { - dev_err(sport->port.dev, "Dma rx channel request failed!\n"); - return -ENODEV; - } - dma_buf = devm_kzalloc(sport->port.dev, FSL_UART_RX_DMA_BUFFER_SIZE, GFP_KERNEL); if (!dma_buf) { dev_err(sport->port.dev, "Dma rx alloc failed\n"); - dma_release_channel(rx_chan); return -ENOMEM; } - dma_bus = dma_map_single(rx_chan->device->dev, dma_buf, + dma_bus = dma_map_single(sport->dma_rx_chan->device->dev, dma_buf, FSL_UART_RX_DMA_BUFFER_SIZE, DMA_FROM_DEVICE); - if (dma_mapping_error(rx_chan->device->dev, dma_bus)) { + if (dma_mapping_error(sport->dma_rx_chan->device->dev, dma_bus)) { dev_err(sport->port.dev, "dma_map_single rx failed\n"); - dma_release_channel(rx_chan); return -ENOMEM; } @@ -1034,16 +1014,14 @@ static int lpuart_dma_rx_request(struct uart_port *port) 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; - ret = dmaengine_slave_config(rx_chan, &dma_rx_sconfig); + ret = dmaengine_slave_config(sport->dma_rx_chan, &dma_rx_sconfig); if (ret < 0) { dev_err(sport->port.dev, "Dma slave config failed, err = %d\n", ret); - dma_release_channel(rx_chan); return ret; } - sport->dma_rx_chan = rx_chan; sport->dma_rx_buf_virt = dma_buf; sport->dma_rx_buf_bus = dma_bus; sport->dma_rx_in_progress = 0; @@ -1055,31 +1033,24 @@ static void lpuart_dma_tx_free(struct uart_port *port) { struct lpuart_port *sport = container_of(port, struct lpuart_port, port); - struct dma_chan *dma_chan; dma_unmap_single(sport->port.dev, sport->dma_tx_buf_bus, UART_XMIT_SIZE, DMA_TO_DEVICE); - dma_chan = sport->dma_tx_chan; - sport->dma_tx_chan = NULL; + sport->dma_tx_buf_bus = 0; sport->dma_tx_buf_virt = NULL; - dma_release_channel(dma_chan); } static void lpuart_dma_rx_free(struct uart_port *port) { struct lpuart_port *sport = container_of(port, struct lpuart_port, port); - struct dma_chan *dma_chan; dma_unmap_single(sport->port.dev, sport->dma_rx_buf_bus, FSL_UART_RX_DMA_BUFFER_SIZE, DMA_FROM_DEVICE); - dma_chan = sport->dma_rx_chan; - sport->dma_rx_chan = NULL; sport->dma_rx_buf_bus = 0; sport->dma_rx_buf_virt = NULL; - dma_release_channel(dma_chan); } static int lpuart_startup(struct uart_port *port) @@ -1098,16 +1069,20 @@ static int lpuart_startup(struct uart_port *port) sport->rxfifo_size = 0x1 << (((temp >> UARTPFIFO_RXSIZE_OFF) & UARTPFIFO_FIFOSIZE_MASK) + 1); - /* Whether use dma support by dma request results */ - if (lpuart_dma_tx_request(port) || lpuart_dma_rx_request(port)) { - sport->lpuart_dma_use = false; - } else { - sport->lpuart_dma_use = true; + if (sport->dma_rx_chan && !lpuart_dma_rx_request(port)) { + sport->lpuart_dma_rx_use = true; setup_timer(&sport->lpuart_timer, lpuart_timer_func, (unsigned long)sport); + } else + sport->lpuart_dma_rx_use = false; + + + if (sport->dma_tx_chan && !lpuart_dma_tx_request(port)) { + sport->lpuart_dma_tx_use = true; temp = readb(port->membase + UARTCR5); writeb(temp | UARTCR5_TDMAS, port->membase + UARTCR5); - } + } else + sport->lpuart_dma_tx_use = false; ret = devm_request_irq(port->dev, port->irq, lpuart_int, 0, DRIVER_NAME, sport); @@ -1178,12 +1153,13 @@ static void lpuart_shutdown(struct uart_port *port) devm_free_irq(port->dev, port->irq, sport); - if (sport->lpuart_dma_use) { + if (sport->lpuart_dma_rx_use) { + lpuart_dma_rx_free(&sport->port); del_timer_sync(&sport->lpuart_timer); - - lpuart_dma_tx_free(port); - lpuart_dma_rx_free(port); } + + if (sport->lpuart_dma_tx_use) + lpuart_dma_tx_free(&sport->port); } static void lpuart32_shutdown(struct uart_port *port) @@ -1305,7 +1281,7 @@ lpuart_set_termios(struct uart_port *port, struct ktermios *termios, /* update the per-port timeout */ uart_update_timeout(port, termios->c_cflag, baud); - if (sport->lpuart_dma_use) { + if (sport->lpuart_dma_rx_use) { /* Calculate delay for 1.5 DMA buffers */ sport->dma_rx_timeout = (sport->port.timeout - HZ / 50) * FSL_UART_RX_DMA_BUFFER_SIZE * 3 / @@ -1836,6 +1812,16 @@ static int lpuart_probe(struct platform_device *pdev) return ret; } + sport->dma_tx_chan = dma_request_slave_channel(sport->port.dev, "tx"); + if (!sport->dma_tx_chan) + dev_info(sport->port.dev, "DMA tx channel request failed, " + "operating without tx DMA\n"); + + sport->dma_rx_chan = dma_request_slave_channel(sport->port.dev, "rx"); + if (!sport->dma_rx_chan) + dev_info(sport->port.dev, "DMA rx channel request failed, " + "operating without rx DMA\n"); + return 0; } @@ -1847,6 +1833,12 @@ static int lpuart_remove(struct platform_device *pdev) clk_disable_unprepare(sport->clk); + if (sport->dma_tx_chan) + dma_release_channel(sport->dma_tx_chan); + + if (sport->dma_rx_chan) + dma_release_channel(sport->dma_rx_chan); + return 0; } -- cgit v1.2.3 From 561d54d077c2b0493e3002cf6b1a7e6362957028 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Fri, 28 Nov 2014 01:00:57 +0100 Subject: serial: fsl_lpuart: avoid new transfer while DMA is running When the UART is DMA receive mode (RDMAS set) and a character just arrived, while another interrupt is handled, the RDRF (receiver data register full flag) is set. But since the DMA will take care of it there is no need to handle it by calling lpuart_prepare_rx. Handling it leads in adding the RX timeout timer twice: [ 43.528029] Kernel BUG at 8004ee7c [verbose debug info unavailable] [ 43.534329] Internal error: Oops - BUG: 0 [#1] ARM [ 43.539145] Modules linked in: [ 43.542242] CPU: 0 PID: 0 Comm: swapper Not tainted 3.18.0-rc5-00014-ge956e1a-dirty #1293 [ 43.550448] task: 80795630 ti: 8078a000 task.ti: 8078a000 [ 43.555886] PC is at add_timer+0x24/0x28 [ 43.559833] LR is at lpuart_int+0x188/0x3b0 [ 43.564039] pc : [<8004ee7c>] lr : [<802cbd3c>] psr: a0000193 [ 43.564039] sp : 8078be18 ip : 8078be28 fp : 8078be24 [ 43.575560] r10: 8e80b840 r9 : 807d23ff r8 : 20000193 [ 43.580812] r7 : 00000020 r6 : 00000001 r5 : 000000a0 r4 : 8e994e10 [ 43.587368] r3 : ffff9bd1 r2 : 80815360 r1 : 8e994e10 r0 : 8e994f20 [ 43.593923] Flags: NzCv IRQs off FIQs on Mode SVC_32 ISA ARM Segment kernel [ 43.601352] Control: 10c5387d Table: 8c424059 DAC: 00000015 [ 43.607125] Process swapper (pid: 0, stack limit = 0x8078a238) ... --- drivers/tty/serial/fsl_lpuart.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c index 629291d20d6c..be9ccdf44060 100644 --- a/drivers/tty/serial/fsl_lpuart.c +++ b/drivers/tty/serial/fsl_lpuart.c @@ -756,18 +756,18 @@ out: static irqreturn_t lpuart_int(int irq, void *dev_id) { struct lpuart_port *sport = dev_id; - unsigned char sts; + unsigned char sts, crdma; sts = readb(sport->port.membase + UARTSR1); + crdma = readb(sport->port.membase + UARTCR5); - if (sts & UARTSR1_RDRF) { + if (sts & UARTSR1_RDRF && !(crdma & UARTCR5_RDMAS)) { if (sport->lpuart_dma_rx_use) lpuart_prepare_rx(sport); else lpuart_rxint(irq, dev_id); } - if (sts & UARTSR1_TDRE && - !(readb(sport->port.membase + UARTCR5) & UARTCR5_TDMAS)) { + if (sts & UARTSR1_TDRE && !(crdma & UARTCR5_TDMAS)) { if (sport->lpuart_dma_tx_use) lpuart_pio_tx(sport); else -- cgit v1.2.3 From ba147f8257a5f340f9d75ee1b0724848edb0f0be Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Fri, 28 Nov 2014 01:01:07 +0100 Subject: serial: fsl_lpuart: update RX timer on successful DMA transfer To end a DMA transfer which did not consume a whole buffer (e.g. one character only), a RX timer is used. When lots of data are received the DMA transfer will complete and setup another DMA transfer, which in turn might complete again. In this cases, it is not necessary to abort the DMA transfers using the RX timer. This change pushes the RX timer timeout into the future each time a DMA transfer completed. Aborting the DMA was not very harmful, since the next received character lead to setup of another RX DMA. --- drivers/tty/serial/fsl_lpuart.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c index be9ccdf44060..b1c1589c48be 100644 --- a/drivers/tty/serial/fsl_lpuart.c +++ b/drivers/tty/serial/fsl_lpuart.c @@ -462,6 +462,7 @@ static void lpuart_dma_rx_complete(void *arg) unsigned long flags; async_tx_ack(sport->dma_rx_desc); + mod_timer(&sport->lpuart_timer, jiffies + sport->dma_rx_timeout); spin_lock_irqsave(&sport->port.lock, flags); -- cgit v1.2.3