[PATCH 4/5] ARM: add PrimeCell generic DMA to PL011
Linus Walleij
linus.walleij at stericsson.com
Mon Mar 8 08:52:45 EST 2010
This extends the PL011 UART driver with generic DMA engine support
using the PrimeCell DMA engine interface.
Signed-off-by: Linus Walleij <linus.walleij at stericsson.com>
---
drivers/serial/amba-pl011.c | 649 ++++++++++++++++++++++++++++++++++++++++++-
include/linux/amba/serial.h | 6 +
2 files changed, 653 insertions(+), 2 deletions(-)
diff --git a/drivers/serial/amba-pl011.c b/drivers/serial/amba-pl011.c
index aad86ec..41ed380 100644
--- a/drivers/serial/amba-pl011.c
+++ b/drivers/serial/amba-pl011.c
@@ -7,6 +7,7 @@
*
* Copyright 1999 ARM Limited
* Copyright (C) 2000 Deep Blue Solutions Ltd.
+ * Copyright (C) 2010 ST-Ericsson SA
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -47,10 +48,17 @@
#include <linux/amba/bus.h>
#include <linux/amba/serial.h>
#include <linux/clk.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/scatterlist.h>
+#include <linux/completion.h>
+#include <linux/amba/dma.h>
#include <asm/io.h>
#include <asm/sizes.h>
+#include <mach/coh901318.h>
+
#define UART_NR 14
#define SERIAL_AMBA_MAJOR 204
@@ -62,6 +70,22 @@
#define UART_DR_ERROR (UART011_DR_OE|UART011_DR_BE|UART011_DR_PE|UART011_DR_FE)
#define UART_DUMMY_DR_RX (1 << 16)
+/* Deals with DMA transactions */
+struct pl011_dma_rx_transaction {
+ struct completion complete;
+ bool use_buffer_b;
+ struct scatterlist scatter_a;
+ struct scatterlist scatter_b;
+ char *rx_dma_buf_a;
+ char *rx_dma_buf_b;
+};
+
+struct pl011_dma_tx_transaction {
+ struct completion complete;
+ struct scatterlist scatter;
+ char *tx_dma_buf;
+};
+
/*
* We wrap our port structure around the generic uart_port.
*/
@@ -72,6 +96,15 @@ struct uart_amba_port {
unsigned int old_status;
unsigned int ifls; /* vendor-specific */
bool autorts;
+ unsigned int fifosize;
+ /* DMA stuff */
+ bool use_dma;
+#ifdef CONFIG_DMADEVICES
+ struct dma_chan *dma_rx_channel;
+ struct dma_chan *dma_tx_channel;
+ struct pl011_dma_rx_transaction dmarx;
+ struct pl011_dma_tx_transaction dmatx;
+#endif
};
/* There is by now at least one vendor with differing details, so handle it */
@@ -90,18 +123,602 @@ static struct vendor_data vendor_st = {
.fifosize = 64,
};
+
+/*
+ * All the DMA operation mode stuff goes inside this ifdef.
+ * This assumes that you have a generic DMA device interface,
+ * no custom DMA interfaces are supported.
+ */
+#ifdef CONFIG_DMADEVICES
+
+#define PL011_DMA_BUFFER_SIZE UART_XMIT_SIZE
+
+static void __init pl011_dma_probe_initcall(struct uart_amba_port *uap)
+{
+ /* DMA is the sole user of the platform data right now */
+ struct amba_pl011_data *plat = uap->port.dev->platform_data;
+ struct pl011_dma_rx_transaction *dmarx = &uap->dmarx;
+ struct pl011_dma_tx_transaction *dmatx = &uap->dmatx;
+ struct amba_dma_channel_config rx_conf = {
+ .addr = uap->port.mapbase + UART01x_DR,
+ .addr_width = 2,
+ };
+ struct amba_dma_channel_config tx_conf = {
+ .addr = uap->port.mapbase + UART01x_DR,
+ .addr_width = 2,
+ };
+ dma_cap_mask_t mask;
+ int sglen;
+
+ /* We need platform data */
+ if (!plat) {
+ dev_err(uap->port.dev, "no platform data!\n");
+ return;
+ }
+ dev_info(uap->port.dev, "RX param = %p, TX param = %p\n",
+ plat->dma_rx_param, plat->dma_tx_param);
+
+ /* Try to acquire a generic DMA engine slave channel */
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ /*
+ * We need both RX and TX channels to do DMA, else do none
+ * of them.
+ */
+ uap->dma_rx_channel = dma_request_channel(mask,
+ plat->dma_filter,
+ plat->dma_rx_param);
+ if (!uap->dma_rx_channel) {
+ dev_err(uap->port.dev, "no RX DMA channel!\n");
+ return;
+ }
+ dma_set_ambaconfig(uap->dma_rx_channel, &rx_conf);
+
+ uap->dma_tx_channel = dma_request_channel(mask,
+ plat->dma_filter,
+ plat->dma_tx_param);
+ if (!uap->dma_tx_channel) {
+ dev_err(uap->port.dev, "no TX DMA channel!\n");
+ goto err_no_txchan;
+ }
+ dma_set_ambaconfig(uap->dma_tx_channel, &tx_conf);
+
+ /* Allocate DMA RX and TX buffers */
+ dmarx->rx_dma_buf_a = kmalloc(PL011_DMA_BUFFER_SIZE, GFP_KERNEL);
+ if (!dmarx->rx_dma_buf_a) {
+ dev_info(uap->port.dev, "failed to allocate DMA RX buffer A\n");
+ goto err_no_rxbuf_a;
+ }
+
+ dmarx->rx_dma_buf_b = kmalloc(PL011_DMA_BUFFER_SIZE, GFP_KERNEL);
+ if (!dmarx->rx_dma_buf_b) {
+ dev_info(uap->port.dev, "failed to allocate DMA RX buffer B\n");
+ goto err_no_rxbuf_b;
+ }
+
+ dmatx->tx_dma_buf = kmalloc(PL011_DMA_BUFFER_SIZE, GFP_KERNEL);
+ if (!dmatx->tx_dma_buf) {
+ dev_info(uap->port.dev, "failed to allocate DMA TX buffer\n");
+ goto err_no_txbuf;
+ }
+
+ /* Provide single SG list with one item to the buffers */
+ sg_init_one(&dmarx->scatter_a, dmarx->rx_dma_buf_a,
+ PL011_DMA_BUFFER_SIZE);
+ sg_init_one(&dmarx->scatter_b, dmarx->rx_dma_buf_b,
+ PL011_DMA_BUFFER_SIZE);
+ sg_init_one(&dmatx->scatter, dmatx->tx_dma_buf, PL011_DMA_BUFFER_SIZE);
+
+ /* Map DMA buffers */
+ sglen = dma_map_sg(uap->port.dev, &dmarx->scatter_a,
+ 1, DMA_FROM_DEVICE);
+ if (sglen != 1)
+ goto err_rx_sgmap_a;
+
+ sglen = dma_map_sg(uap->port.dev, &dmarx->scatter_b,
+ 1, DMA_FROM_DEVICE);
+ if (sglen != 1)
+ goto err_rx_sgmap_b;
+
+ sglen = dma_map_sg(uap->port.dev, &dmatx->scatter,
+ 1, DMA_TO_DEVICE);
+ if (sglen != 1)
+ goto err_tx_sgmap;
+
+ /* Initially we say the transfers are incomplete */
+ init_completion(&uap->dmatx.complete);
+ complete(&uap->dmatx.complete);
+
+ /* The DMA buffer is now the FIFO the TTY subsystem can use */
+ uap->port.fifosize = PL011_DMA_BUFFER_SIZE;
+
+ uap->use_dma = true;
+ dev_info(uap->port.dev, "setup for DMA on RX %s, TX %s\n",
+ dma_chan_name(uap->dma_rx_channel),
+ dma_chan_name(uap->dma_tx_channel));
+ return;
+
+err_tx_sgmap:
+ dma_unmap_sg(uap->port.dev, &dmarx->scatter_b,
+ 1, DMA_FROM_DEVICE);
+err_rx_sgmap_b:
+ dma_unmap_sg(uap->port.dev, &dmarx->scatter_a,
+ 1, DMA_FROM_DEVICE);
+err_rx_sgmap_a:
+ kfree(dmatx->tx_dma_buf);
+err_no_txbuf:
+ kfree(dmarx->rx_dma_buf_b);
+err_no_rxbuf_b:
+ kfree(dmarx->rx_dma_buf_a);
+err_no_rxbuf_a:
+ dma_release_channel(uap->dma_tx_channel);
+ uap->dma_tx_channel = NULL;
+err_no_txchan:
+ dma_release_channel(uap->dma_rx_channel);
+ uap->dma_rx_channel = NULL;
+ return;
+}
+
+/*
+ * Stack up the UARTs and let the above initcall be done at
+ * device initcall time, because the serial driver is called as
+ * an arch initcall, and at this time the DMA subsystem is not yet
+ * registered. At this point the driver will switch over to using
+ * DMA where desired.
+ */
+
+struct dma_uap {
+ struct list_head node;
+ struct uart_amba_port *uap;
+};
+
+struct list_head __initdata pl011_dma_uarts = LIST_HEAD_INIT(pl011_dma_uarts);
+
+static int __init pl011_dma_initcall(void)
+{
+ struct list_head *node, *tmp;
+
+ list_for_each_safe(node, tmp, &pl011_dma_uarts) {
+ struct dma_uap *dmau = list_entry(node, struct dma_uap, node);
+ pl011_dma_probe_initcall(dmau->uap);
+ list_del(node);
+ kfree(dmau);
+ }
+ return 0;
+}
+
+device_initcall(pl011_dma_initcall);
+
+static void pl011_dma_probe(struct uart_amba_port *uap)
+{
+ struct dma_uap *dmau = kzalloc(sizeof(struct dma_uap), GFP_KERNEL);
+
+ if (dmau == NULL)
+ return;
+ dmau->uap = uap;
+ list_add_tail(&dmau->node, &pl011_dma_uarts);
+}
+
+static void pl011_dma_remove(struct uart_amba_port *uap)
+{
+ struct pl011_dma_rx_transaction *dmarx = &uap->dmarx;
+ struct pl011_dma_tx_transaction *dmatx = &uap->dmatx;
+
+ /* TODO: remove the initcall if it has not yet executed */
+ /* Unmap and free DMA buffers */
+ if (uap->dma_rx_channel)
+ dma_release_channel(uap->dma_rx_channel);
+ if (uap->dma_tx_channel)
+ dma_release_channel(uap->dma_tx_channel);
+ if (dmatx->tx_dma_buf) {
+ dma_unmap_sg(uap->port.dev, &dmatx->scatter,
+ 1, DMA_TO_DEVICE);
+ kfree(dmatx->tx_dma_buf);
+ }
+ if (dmarx->rx_dma_buf_b) {
+ dma_unmap_sg(uap->port.dev, &dmarx->scatter_b,
+ 1, DMA_FROM_DEVICE);
+ kfree(dmarx->rx_dma_buf_b);
+ }
+ if (dmarx->rx_dma_buf_a) {
+ dma_unmap_sg(uap->port.dev, &dmarx->scatter_a,
+ 1, DMA_FROM_DEVICE);
+ kfree(dmarx->rx_dma_buf_a);
+ }
+}
+
+/* Forward declare this for the refill routine */
+static void pl011_dma_tx_refill(struct uart_amba_port *uap);
+
+/*
+ * Move the tail when this IRQ occurs, if not empty refill and
+ * fire another transaction
+ */
+static void pl011_dma_tx_int(void *data)
+{
+ struct uart_amba_port *uap = data;
+ struct pl011_dma_tx_transaction *dmatx = &uap->dmatx;
+ struct circ_buf *xmit = &uap->port.state->xmit;
+
+ /* Refill the TX if the buffer is not empty */
+ if (!uart_circ_empty(xmit))
+ pl011_dma_tx_refill(uap);
+ else
+ complete(&dmatx->complete);
+}
+
+static void pl011_dma_tx_refill(struct uart_amba_port *uap)
+{
+ struct pl011_dma_tx_transaction *dmatx = &uap->dmatx;
+ struct dma_chan *chan = uap->dma_tx_channel;
+ struct dma_async_tx_descriptor *desc;
+ struct circ_buf *xmit = &uap->port.state->xmit;
+ unsigned int count;
+
+ /* Don't bother about using DMA on XON/XOFF */
+ if (uap->port.x_char) {
+ /* If we can't get it into the FIFO, retry later */
+ if (readw(uap->port.membase + UART01x_FR) &
+ UART01x_FR_TXFF) {
+ complete(&dmatx->complete);
+ return;
+ }
+ writew(uap->port.x_char, uap->port.membase + UART01x_DR);
+ uap->port.icount.tx++;
+ uap->port.x_char = 0;
+ complete(&dmatx->complete);
+ return;
+ }
+
+ /*
+ * Try to avoid the overhead involved in using DMA if the
+ * transaction fits in the first half of the FIFO and it's not
+ * full. Unfortunately there is only one single bit in the
+ * hardware to tell whether the FIFO is full or not, so
+ * we don't know exactly how many chars we can fit in.
+ */
+ if (!(readw(uap->port.membase + UART01x_FR) &
+ UART01x_FR_TXFF) &&
+ uart_circ_chars_pending(xmit) < (uap->fifosize >> 1)) {
+ while (uart_circ_chars_pending(xmit)) {
+ if (readw(uap->port.membase + UART01x_FR) &
+ UART01x_FR_TXFF)
+ /*
+ * Ooops TX FIFO is full, we'd better stop this
+ */
+ break;
+ writew(xmit->buf[xmit->tail],
+ uap->port.membase + UART01x_DR);
+ uap->port.icount.tx++;
+ xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
+ }
+ complete(&dmatx->complete);
+ return;
+ };
+
+ /* Else proceed to copy the TX chars to the DMA buffer and fire DMA */
+ count = uart_circ_chars_pending(xmit);
+ if (count > PL011_DMA_BUFFER_SIZE)
+ count = PL011_DMA_BUFFER_SIZE;
+
+ if (xmit->tail < xmit->head)
+ memcpy(&dmatx->tx_dma_buf[0], &xmit->buf[xmit->tail], count);
+ else {
+ size_t first = UART_XMIT_SIZE - xmit->tail;
+ size_t second = xmit->head;
+
+ memcpy(&dmatx->tx_dma_buf[0], &xmit->buf[xmit->tail], first);
+ memcpy(&dmatx->tx_dma_buf[first], &xmit->buf[0], second);
+ }
+
+ /* Advance the ring buffer with the stuff we just dispatched */
+ xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1);
+ uap->port.icount.tx += count;
+ dmatx->scatter.length = count;
+
+ if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+ uart_write_wakeup(&uap->port);
+
+ /* Synchronize the scatterlist, invalidate buffers, caches etc */
+ dma_sync_sg_for_device(uap->port.dev,
+ &dmatx->scatter,
+ 1,
+ DMA_TO_DEVICE);
+
+ /* Send the scatterlist */
+ desc = chan->device->device_prep_slave_sg(chan,
+ &dmatx->scatter,
+ 1,
+ DMA_TO_DEVICE,
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (!desc) {
+ /* "Complete" DMA (errorpath) */
+ complete(&dmatx->complete);
+ return;
+ }
+
+ /* Some data to go along to the callback */
+ desc->callback = pl011_dma_tx_int;
+ desc->callback_param = uap;
+ desc->tx_submit(desc);
+ chan->device->device_issue_pending(chan);
+}
+
+static void pl011_dma_rx_int(void *data);
+
+static int pl011_dma_rx_trigger_dma(struct uart_amba_port *uap)
+{
+ struct dma_chan *rxchan = uap->dma_rx_channel;
+ struct pl011_dma_rx_transaction *dmarx = &uap->dmarx;
+ struct dma_async_tx_descriptor *desc;
+ struct scatterlist *scatter = dmarx->use_buffer_b ?
+ &dmarx->scatter_b : &dmarx->scatter_a;
+
+ /* Start the RX DMA job */
+ desc = rxchan->device->device_prep_slave_sg(rxchan,
+ scatter,
+ 1,
+ DMA_FROM_DEVICE,
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (!desc)
+ return -EIO;
+
+ /* Some data to go along to the callback */
+ desc->callback = pl011_dma_rx_int;
+ desc->callback_param = uap;
+ desc->tx_submit(desc);
+ rxchan->device->device_issue_pending(rxchan);
+ return 0;
+}
+
+/*
+ * This is called when either the DMA job is complete, or
+ * the FIFO timeout interrupt occurred.
+ */
+static void pl011_dma_rx_chars(struct uart_amba_port *uap,
+ u32 pending, bool use_buffer_b,
+ bool readfifo)
+{
+ struct pl011_dma_rx_transaction *dmarx = &uap->dmarx;
+ struct tty_struct *tty = uap->port.state->port.tty;
+ char *buf = use_buffer_b ? dmarx->rx_dma_buf_b : dmarx->rx_dma_buf_a;
+ struct scatterlist *scatter = use_buffer_b ?
+ &dmarx->scatter_b : &dmarx->scatter_a;
+ unsigned int status, ch, flag;
+ u32 count = pending;
+ u32 bufp = 0;
+ u32 fifotaken = 0; /* only used for vdbg() */
+
+ /* Sync in buffer */
+ dma_sync_sg_for_cpu(uap->port.dev,
+ scatter,
+ 1,
+ DMA_FROM_DEVICE);
+
+ status = readw(uap->port.membase + UART01x_FR);
+
+ /*
+ * First take all chars in the DMA pipe, then look
+ * in the FIFO. So loop while we have chars in the
+ * DMA buffer or the FIFO. If we came here from a
+ * DMA buffer full interrupt, there is already another
+ * DMA job triggered to read the FIFO, so don't look
+ * at it.
+ */
+ while (count ||
+ (readfifo && (status & UART01x_FR_RXFE) == 0)) {
+
+ flag = TTY_NORMAL;
+ uap->port.icount.rx++;
+
+ if (count) {
+ /* Take chars from the DMA buffer */
+ ch = (unsigned int) buf[bufp];
+ bufp++;
+ count--;
+ } else {
+ /* Take chars from the FIFO and update status */
+ ch = readw(uap->port.membase + UART01x_DR);
+ status = readw(uap->port.membase + UART01x_FR);
+ fifotaken++;
+
+ /*
+ * Error conditions will only occur in the FIFO,
+ * these will trigger an immediate interrupt and
+ * stop the DMA job, so we will always find the
+ * error in the FIFO, never in the DMA buffer.
+ */
+ if (unlikely(ch & UART_DR_ERROR)) {
+ if (ch & UART011_DR_BE) {
+ ch &= ~(UART011_DR_FE | UART011_DR_PE);
+ uap->port.icount.brk++;
+ if (uart_handle_break(&uap->port))
+ continue;
+ } else if (ch & UART011_DR_PE)
+ uap->port.icount.parity++;
+ else if (ch & UART011_DR_FE)
+ uap->port.icount.frame++;
+ if (ch & UART011_DR_OE)
+ uap->port.icount.overrun++;
+
+ ch &= uap->port.read_status_mask;
+
+ if (ch & UART011_DR_BE)
+ flag = TTY_BREAK;
+ else if (ch & UART011_DR_PE)
+ flag = TTY_PARITY;
+ else if (ch & UART011_DR_FE)
+ flag = TTY_FRAME;
+ }
+ }
+
+ if (uart_handle_sysrq_char(&uap->port, ch & 255))
+ continue;
+
+ uart_insert_char(&uap->port, ch, UART011_DR_OE, ch, flag);
+ }
+ dev_vdbg(uap->port.dev,
+ "Took %d chars from DMA buffer and %d chars from the FIFO\n",
+ bufp, fifotaken);
+ spin_unlock(&uap->port.lock);
+ tty_flip_buffer_push(tty);
+ spin_lock(&uap->port.lock);
+}
+
+static void pl011_dma_rx_irq(struct uart_amba_port *uap)
+{
+ struct dma_chan *rxchan = uap->dma_rx_channel;
+ struct pl011_dma_rx_transaction *dmarx = &uap->dmarx;
+ struct scatterlist *scatter = dmarx->use_buffer_b ?
+ &dmarx->scatter_b : &dmarx->scatter_a;
+ u32 pending;
+ int ret;
+
+ /*
+ * FIXME: replace this COH 901 318 call with some
+ * generic solution in the DMA engine slave
+ */
+ dma_stop_channel(rxchan);
+ pending = scatter->length
+ - dma_get_channel_bytes_left(rxchan);
+ rxchan->device->device_terminate_all(rxchan);
+
+ /*
+ * This will take the chars we have so far and insert
+ * into the framework.
+ */
+ pl011_dma_rx_chars(uap, pending, dmarx->use_buffer_b, true);
+
+ /* Switch buffer & re-trigger DMA job */
+ dmarx->use_buffer_b = !dmarx->use_buffer_b;
+ ret = pl011_dma_rx_trigger_dma(uap);
+ if (ret)
+ dev_err(uap->port.dev, "could not retrigger RX DMA job\n");
+}
+
+static void pl011_dma_rx_int(void *data)
+{
+ struct uart_amba_port *uap = data;
+ struct pl011_dma_rx_transaction *dmarx = &uap->dmarx;
+ bool lastbuf = dmarx->use_buffer_b;
+ int ret;
+
+ /*
+ * This completion interrupt occurs typically when the
+ * RX buffer is totally stuffed but no timeout has yet
+ * occurred. When that happens, we just want the RX
+ * routine to flush out the secondary DMA buffer while
+ * we immediately trigger the next DMA job.
+ */
+ dmarx->use_buffer_b = !lastbuf;
+ ret = pl011_dma_rx_trigger_dma(uap);
+ if (ret)
+ dev_err(uap->port.dev, "could not retrigger RX DMA job\n");
+ pl011_dma_rx_chars(uap, PL011_DMA_BUFFER_SIZE, lastbuf, false);
+}
+
+static void pl011_dma_startup(struct uart_amba_port *uap)
+{
+ u16 val;
+ int ret;
+
+ ret = pl011_dma_rx_trigger_dma(uap);
+ if (ret) {
+ uap->use_dma = false;
+ return;
+ }
+
+ /* Turn on DMA for RX and TX */
+ val = readw(uap->port.membase + UART011_DMACR);
+ val |= (UART011_RXDMAE | UART011_TXDMAE | UART011_DMAONERR);
+ writew(val, uap->port.membase + UART011_DMACR);
+}
+
+static void pl011_dma_shutdown(struct uart_amba_port *uap)
+{
+ struct dma_chan *rxchan = uap->dma_rx_channel;
+ struct dma_chan *txchan = uap->dma_tx_channel;
+ u16 val;
+
+ /* Disable RX and TX DMA */
+ while (readw(uap->port.membase + UART01x_FR) & UART01x_FR_BUSY)
+ barrier();
+ val = readw(uap->port.membase + UART011_DMACR);
+ val &= ~(UART011_RXDMAE | UART011_TXDMAE);
+ writew(val, uap->port.membase + UART011_DMACR);
+ /* Terminate any RX and TX DMA jobs */
+ rxchan->device->device_terminate_all(rxchan);
+ txchan->device->device_terminate_all(txchan);
+}
+
+static int pl011_dma_tx_chars(struct uart_amba_port *uap)
+{
+ struct pl011_dma_tx_transaction *dmatx = &uap->dmatx;
+
+ /* Try to wait for completion, return if something is in progress */
+ if (!try_wait_for_completion(&dmatx->complete))
+ return -EINPROGRESS;
+
+ /* Set up and fire the DMA job */
+ init_completion(&dmatx->complete);
+ pl011_dma_tx_refill(uap);
+
+ return 0;
+}
+
+#else
+/* Blank functions if the DMA engine is not available */
+static inline void pl011_dma_probe(struct uart_amba_port *uap)
+{
+}
+
+static inline void pl011_dma_remove(struct uart_amba_port *uap)
+{
+}
+
+static void pl011_dma_rx_irq(struct uart_amba_port *uap)
+{
+}
+
+static inline void pl011_dma_startup(struct uart_amba_port *uap)
+{
+}
+
+static inline void pl011_dma_shutdown(struct uart_amba_port *uap)
+{
+}
+
+static inline int pl011_dma_tx_chars(struct uart_amba_port *uap)
+{
+ return -EIO;
+}
+
+static inline void pl011_dma_rx_timeout(struct uart_amba_port *uap)
+{
+}
+#endif
+
+
static void pl011_stop_tx(struct uart_port *port)
{
struct uart_amba_port *uap = (struct uart_amba_port *)port;
+ if (uap->use_dma)
+ return;
uap->im &= ~UART011_TXIM;
writew(uap->im, uap->port.membase + UART011_IMSC);
}
+static void pl011_tx_chars(struct uart_amba_port *uap);
+
static void pl011_start_tx(struct uart_port *port)
{
struct uart_amba_port *uap = (struct uart_amba_port *)port;
+ if (uap->use_dma) {
+ pl011_tx_chars(uap);
+ return;
+ }
uap->im |= UART011_TXIM;
writew(uap->im, uap->port.membase + UART011_IMSC);
}
@@ -179,6 +796,22 @@ static void pl011_tx_chars(struct uart_amba_port *uap)
struct circ_buf *xmit = &uap->port.state->xmit;
int count;
+ if (uap->use_dma) {
+ int ret;
+
+ ret = pl011_dma_tx_chars(uap);
+ if (!ret)
+ return;
+ if (ret == -EINPROGRESS)
+ return;
+
+ /* On error we fall through to interrupt mode */
+ dev_err(uap->port.dev, "error %d using TX DMA\n", ret);
+ uap->use_dma = false;
+ uap->im |= UART011_TXIM;
+ writew(uap->im, uap->port.membase + UART011_IMSC);
+ }
+
if (uap->port.x_char) {
writew(uap->port.x_char, uap->port.membase + UART01x_DR);
uap->port.icount.tx++;
@@ -245,8 +878,12 @@ static irqreturn_t pl011_int(int irq, void *dev_id)
UART011_RXIS),
uap->port.membase + UART011_ICR);
- if (status & (UART011_RTIS|UART011_RXIS))
- pl011_rx_chars(uap);
+ if (status & (UART011_RTIS|UART011_RXIS)) {
+ if (uap->use_dma)
+ pl011_dma_rx_irq(uap);
+ else
+ pl011_rx_chars(uap);
+ }
if (status & (UART011_DSRMIS|UART011_DCDMIS|
UART011_CTSMIS|UART011_RIMIS))
pl011_modem_status(uap);
@@ -413,6 +1050,8 @@ static int pl011_startup(struct uart_port *port)
writew(uap->im, uap->port.membase + UART011_IMSC);
spin_unlock_irq(&uap->port.lock);
+ pl011_dma_startup(uap);
+
return 0;
clk_dis:
@@ -426,6 +1065,8 @@ static void pl011_shutdown(struct uart_port *port)
struct uart_amba_port *uap = (struct uart_amba_port *)port;
unsigned long val;
+ pl011_dma_shutdown(uap);
+
/*
* disable all interrupts
*/
@@ -808,6 +1449,8 @@ static int pl011_probe(struct amba_device *dev, struct amba_id *id)
uap->port.ops = &amba_pl011_pops;
uap->port.flags = UPF_BOOT_AUTOCONF;
uap->port.line = i;
+ uap->fifosize = vendor->fifosize;
+ pl011_dma_probe(uap);
amba_ports[i] = uap;
@@ -816,6 +1459,7 @@ static int pl011_probe(struct amba_device *dev, struct amba_id *id)
if (ret) {
amba_set_drvdata(dev, NULL);
amba_ports[i] = NULL;
+ pl011_dma_remove(uap);
clk_put(uap->clk);
unmap:
iounmap(base);
@@ -839,6 +1483,7 @@ static int pl011_remove(struct amba_device *dev)
if (amba_ports[i] == uap)
amba_ports[i] = NULL;
+ pl011_dma_remove(uap);
iounmap(uap->port.membase);
clk_put(uap->clk);
kfree(uap);
diff --git a/include/linux/amba/serial.h b/include/linux/amba/serial.h
index 5a5a7fd..2ce6a75 100644
--- a/include/linux/amba/serial.h
+++ b/include/linux/amba/serial.h
@@ -166,6 +166,12 @@ struct amba_device; /* in uncompress this is included but amba/bus.h is not */
struct amba_pl010_data {
void (*set_mctrl)(struct amba_device *dev, void __iomem *base, unsigned int mctrl);
};
+struct dma_chan;
+struct amba_pl011_data {
+ bool (*dma_filter)(struct dma_chan *chan, void *filter_param);
+ void *dma_rx_param;
+ void *dma_tx_param;
+};
#endif
#endif
--
1.6.2.5
More information about the linux-arm-kernel
mailing list