* [PATCH 4/5] ARM: add PrimeCell generic DMA to PL011
@ 2010-03-08 13:52 Linus Walleij
[not found] ` <b328b2051003252142o1c142ca0h66135eb9129d1c99@mail.gmail.com>
0 siblings, 1 reply; 3+ messages in thread
From: Linus Walleij @ 2010-03-08 13:52 UTC (permalink / raw)
To: linux-arm-kernel
This extends the PL011 UART driver with generic DMA engine support
using the PrimeCell DMA engine interface.
Signed-off-by: Linus Walleij <linus.walleij@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@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
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH 4/5] ARM: add PrimeCell generic DMA to PL011
[not found] ` <b328b2051003252142o1c142ca0h66135eb9129d1c99@mail.gmail.com>
@ 2010-03-26 4:57 ` srinidhi
2010-03-26 9:47 ` Linus WALLEIJ
0 siblings, 1 reply; 3+ messages in thread
From: srinidhi @ 2010-03-26 4:57 UTC (permalink / raw)
To: linux-arm-kernel
Linus,
> This extends the PL011 UART driver with generic DMA engine support
> using the PrimeCell DMA engine interface.
>
> Signed-off-by: Linus Walleij <linus.walleij@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>
Is this accidentally added? Please update 5983/1 patch in Russell's
tracking system.
regards,
Srinidhi
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH 4/5] ARM: add PrimeCell generic DMA to PL011
2010-03-26 4:57 ` srinidhi
@ 2010-03-26 9:47 ` Linus WALLEIJ
0 siblings, 0 replies; 3+ messages in thread
From: Linus WALLEIJ @ 2010-03-26 9:47 UTC (permalink / raw)
To: linux-arm-kernel
[Srinidhi]
> >
> > +#include <mach/coh901318.h>
> Is this accidentally added? Please update 5983/1 patch in Russell's
> tracking system.
OK I'll update the entire series when I'm finished with porting
it on top of Dan's requested changes to DMAengine.
Russell cannot commit them until the required changes from DMAengine
are in actually, unless I push the PrimeCell DMA API through
Dan's DMA tree.
Linus
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2010-03-26 9:47 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-03-08 13:52 [PATCH 4/5] ARM: add PrimeCell generic DMA to PL011 Linus Walleij
[not found] ` <b328b2051003252142o1c142ca0h66135eb9129d1c99@mail.gmail.com>
2010-03-26 4:57 ` srinidhi
2010-03-26 9:47 ` Linus WALLEIJ
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox