summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Agner <stefan.agner@toradex.com>2015-06-09 00:43:28 +0200
committerStefan Agner <stefan.agner@toradex.com>2015-06-09 13:04:39 +0200
commit7ed3ad7562ccdd9dca296b30a556865c772d99e6 (patch)
tree20b0a2cd99569cb43f4b6c887ff6b58ccfc7d1e4
parent8e6453ae0b58330e7171a3bc6bdece035b34bbe2 (diff)
tty: serial: fsl_lpuart: avoid TX FIFO overflow
In some rare cases, the TX DMA gets started while there are still data in the TX FIFO and the Transmit Data Register Empty Flag (TDRE) is asserted. In this case, the DMA writes data and overflows the TX FIFO. It is not quite clear why the TDRE is asserted in this case, reading the status register which should clear the flag did not alleviate the problem. It seems that the switching between DMA and interrupt mode leads to this glitches. Avoid TX FIFO overrun by using a burst size of 1, which allows to transfer all data using DMA and avoid switching between DMA and interrupt mode entirely.
-rw-r--r--drivers/tty/serial/fsl_lpuart.c66
1 files changed, 13 insertions, 53 deletions
diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c
index b276813965a2..b3bc175d70e4 100644
--- a/drivers/tty/serial/fsl_lpuart.c
+++ b/drivers/tty/serial/fsl_lpuart.c
@@ -342,38 +342,19 @@ static void lpuart_copy_rx_to_tty(struct lpuart_port *sport,
FSL_UART_RX_DMA_BUFFER_SIZE, DMA_TO_DEVICE);
}
-static void lpuart_pio_tx(struct lpuart_port *sport)
-{
- struct circ_buf *xmit = &sport->port.state->xmit;
- unsigned long flags;
-
- spin_lock_irqsave(&sport->port.lock, flags);
-
- while (!uart_circ_empty(xmit) &&
- readb(sport->port.membase + UARTTCFIFO) < sport->txfifo_size) {
- writeb(xmit->buf[xmit->tail], sport->port.membase + UARTDR);
- xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
- sport->port.icount.tx++;
- }
-
- if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
- uart_write_wakeup(&sport->port);
-
- if (uart_circ_empty(xmit))
- writeb(readb(sport->port.membase + UARTCR5) | UARTCR5_TDMAS,
- sport->port.membase + UARTCR5);
-
- spin_unlock_irqrestore(&sport->port.lock, flags);
-}
-
-static int lpuart_dma_tx(struct lpuart_port *sport, unsigned long count)
+static int lpuart_dma_tx(struct lpuart_port *sport)
{
struct circ_buf *xmit = &sport->port.state->xmit;
+ unsigned long count = CIRC_CNT_TO_END(xmit->head,
+ xmit->tail, UART_XMIT_SIZE);
dma_addr_t tx_bus_addr;
+ if (!count)
+ return 0;
+
dma_sync_single_for_device(sport->port.dev, sport->dma_tx_buf_bus,
UART_XMIT_SIZE, DMA_TO_DEVICE);
- sport->dma_tx_bytes = count & ~(sport->txfifo_size - 1);
+ sport->dma_tx_bytes = count;
tx_bus_addr = sport->dma_tx_buf_bus + xmit->tail;
sport->dma_tx_desc = dmaengine_prep_slave_single(sport->dma_tx_chan,
tx_bus_addr, sport->dma_tx_bytes,
@@ -393,25 +374,6 @@ static int lpuart_dma_tx(struct lpuart_port *sport, unsigned long count)
return 0;
}
-static void lpuart_prepare_tx(struct lpuart_port *sport)
-{
- struct circ_buf *xmit = &sport->port.state->xmit;
- unsigned long count = CIRC_CNT_TO_END(xmit->head,
- xmit->tail, UART_XMIT_SIZE);
-
- if (!count)
- return;
-
- if (count < sport->txfifo_size)
- writeb(readb(sport->port.membase + UARTCR5) & ~UARTCR5_TDMAS,
- sport->port.membase + UARTCR5);
- else {
- writeb(readb(sport->port.membase + UARTCR5) | UARTCR5_TDMAS,
- sport->port.membase + UARTCR5);
- lpuart_dma_tx(sport, count);
- }
-}
-
static void lpuart_dma_tx_complete(void *arg)
{
struct lpuart_port *sport = arg;
@@ -428,7 +390,7 @@ static void lpuart_dma_tx_complete(void *arg)
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
uart_write_wakeup(&sport->port);
- lpuart_prepare_tx(sport);
+ lpuart_dma_tx(sport);
spin_unlock_irqrestore(&sport->port.lock, flags);
}
@@ -581,7 +543,7 @@ static void lpuart_start_tx(struct uart_port *port)
if (sport->lpuart_dma_tx_use) {
if (!uart_circ_empty(xmit) && !sport->dma_tx_in_progress)
- lpuart_prepare_tx(sport);
+ lpuart_dma_tx(sport);
} else {
if (readb(port->membase + UARTSR1) & UARTSR1_TDRE)
lpuart_transmit_buffer(sport);
@@ -778,10 +740,8 @@ static irqreturn_t lpuart_int(int irq, void *dev_id)
lpuart_rxint(irq, dev_id);
}
if (sts & UARTSR1_TDRE && !(crdma & UARTCR5_TDMAS)) {
- if (sport->lpuart_dma_tx_use)
- lpuart_pio_tx(sport);
- else
- lpuart_txint(irq, dev_id);
+ BUG_ON(sport->lpuart_dma_tx_use);
+ lpuart_txint(irq, dev_id);
}
return IRQ_HANDLED;
@@ -936,7 +896,7 @@ static void lpuart_setup_watermark(struct lpuart_port *sport)
writeb(UARTCFIFO_TXFLUSH | UARTCFIFO_RXFLUSH,
sport->port.membase + UARTCFIFO);
- writeb(0, sport->port.membase + UARTTWFIFO);
+ writeb(sport->txfifo_size / 2, sport->port.membase + UARTTWFIFO);
writeb(1, sport->port.membase + UARTRWFIFO);
/* Restore cr2 */
@@ -989,7 +949,7 @@ static int lpuart_dma_tx_request(struct uart_port *port)
dma_buf = sport->port.state->xmit.buf;
dma_tx_sconfig.dst_addr = sport->port.mapbase + UARTDR;
dma_tx_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
- dma_tx_sconfig.dst_maxburst = sport->txfifo_size;
+ dma_tx_sconfig.dst_maxburst = 1;
dma_tx_sconfig.direction = DMA_MEM_TO_DEV;
ret = dmaengine_slave_config(sport->dma_tx_chan, &dma_tx_sconfig);