[PATCH 1/2] serial/imx: add DMA support
Russell King - ARM Linux
linux at arm.linux.org.uk
Thu Apr 26 07:11:16 EDT 2012
On Thu, Apr 26, 2012 at 06:37:11PM +0800, Huang Shijie wrote:
> Add the DMA support for uart RX and TX.
>
> Signed-off-by: Huang Shijie <b32955 at freescale.com>
> ---
Apart from the comments below,
1. How do you deal with transmitting the high-priority XON/XOFF
characters (port->x_char) which occur with s/w flow control and
the tty buffers fill up?
2. How do you deal with flow control in general? IOW, what happens
when the remote end deasserts your CTS with h/w flow control enabled.
How does your end deal with sending RTS according to flow control
conditions?
> .../bindings/tty/serial/fsl-imx-uart.txt | 7 +
> drivers/tty/serial/imx.c | 386 +++++++++++++++++++-
> 2 files changed, 389 insertions(+), 4 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/tty/serial/fsl-imx-uart.txt b/Documentation/devicetree/bindings/tty/serial/fsl-imx-uart.txt
> index a9c0406..f27489d 100644
> --- a/Documentation/devicetree/bindings/tty/serial/fsl-imx-uart.txt
> +++ b/Documentation/devicetree/bindings/tty/serial/fsl-imx-uart.txt
> @@ -8,6 +8,10 @@ Required properties:
> Optional properties:
> - fsl,uart-has-rtscts : Indicate the uart has rts and cts
> - fsl,irda-mode : Indicate the uart supports irda mode
> +- fsl,enable-dma : Indicate the uart supports DMA
> +- fsl,uart-dma-events : contains the DMA events for RX and TX,
> + The first is the RX event, while the other is TX.
> +- fsl,enable-dte: Indicate the uart works in DTE mode
>
> Example:
>
> @@ -16,4 +20,7 @@ uart at 73fbc000 {
> reg = <0x73fbc000 0x4000>;
> interrupts = <31>;
> fsl,uart-has-rtscts;
> + fsl,enable-dma;
> + fsl,uart-dma-events = <xx xx>;
> + fsl,enable-dte;
> };
> diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c
> index e7fecee..65ba24d 100644
> --- a/drivers/tty/serial/imx.c
> +++ b/drivers/tty/serial/imx.c
> @@ -47,9 +47,11 @@
> #include <linux/slab.h>
> #include <linux/of.h>
> #include <linux/of_device.h>
> +#include <linux/dma-mapping.h>
>
> #include <asm/io.h>
> #include <asm/irq.h>
> +#include <mach/dma.h>
> #include <mach/imx-uart.h>
>
> /* Register definitions */
> @@ -82,6 +84,7 @@
> #define UCR1_ADBR (1<<14) /* Auto detect baud rate */
> #define UCR1_TRDYEN (1<<13) /* Transmitter ready interrupt enable */
> #define UCR1_IDEN (1<<12) /* Idle condition interrupt */
> +#define UCR1_ICD_REG(x) (((x) & 3) << 10) /* idle condition detect */
> #define UCR1_RRDYEN (1<<9) /* Recv ready interrupt enable */
> #define UCR1_RDMAEN (1<<8) /* Recv ready DMA enable */
> #define UCR1_IREN (1<<7) /* Infrared interface enable */
> @@ -125,6 +128,7 @@
> #define UCR4_ENIRI (1<<8) /* Serial infrared interrupt enable */
> #define UCR4_WKEN (1<<7) /* Wake interrupt enable */
> #define UCR4_REF16 (1<<6) /* Ref freq 16 MHz */
> +#define UCR4_IDDMAEN (1<<6) /* DMA IDLE Condition Detected */
> #define UCR4_IRSC (1<<5) /* IR special case */
> #define UCR4_TCEN (1<<3) /* Transmit complete interrupt enable */
> #define UCR4_BKEN (1<<2) /* Break condition interrupt enable */
> @@ -134,6 +138,7 @@
> #define UFCR_RFDIV (7<<7) /* Reference freq divider mask */
> #define UFCR_RFDIV_REG(x) (((x) < 7 ? 6 - (x) : 6) << 7)
> #define UFCR_TXTL_SHF 10 /* Transmitter trigger level shift */
> +#define UFCR_DCEDTE (1<<6)
> #define USR1_PARITYERR (1<<15) /* Parity error interrupt flag */
> #define USR1_RTSS (1<<14) /* RTS pin status */
> #define USR1_TRDY (1<<13) /* Transmitter ready interrupt/dma flag */
> @@ -200,12 +205,27 @@ struct imx_port {
> unsigned int old_status;
> int txirq,rxirq,rtsirq;
> unsigned int have_rtscts:1;
> + unsigned int enable_dte:1;
> + unsigned int enable_dma:1;
> unsigned int use_irda:1;
> unsigned int irda_inv_rx:1;
> unsigned int irda_inv_tx:1;
> unsigned short trcv_delay; /* transceiver delay */
> struct clk *clk;
> struct imx_uart_data *devdata;
> +
> + /* DMA fields */
> + unsigned int dma_req_rx;
> + unsigned int dma_req_tx;
> + struct imx_dma_data dma_data;
> + struct dma_chan *dma_chan_rx, *dma_chan_tx;
> + struct scatterlist rx_sgl, tx_sgl[2];
> + void *rx_buf;
> + unsigned int rx_bytes, tx_bytes;
> + struct work_struct tsk_dma_rx, tsk_dma_tx;
Why do you need a work struct to deal with DMA?
> + unsigned int dma_tx_nents;
> + bool dma_is_rxing;
> + wait_queue_head_t dma_wait;
> };
>
> struct imx_port_ucrs {
> @@ -394,6 +414,13 @@ static void imx_stop_rx(struct uart_port *port)
> struct imx_port *sport = (struct imx_port *)port;
> unsigned long temp;
>
> + /*
> + * We are maybe in the SMP now, so if the DMA RX thread is running,
> + * we have to wait for it to finish.
> + */
> + if (sport->enable_dma && sport->dma_is_rxing)
> + return;
> +
> temp = readl(sport->port.membase + UCR2);
> writel(temp &~ UCR2_RXEN, sport->port.membase + UCR2);
> }
> @@ -429,6 +456,80 @@ static inline void imx_transmit_buffer(struct imx_port *sport)
> imx_stop_tx(&sport->port);
> }
>
> +static void dma_tx_callback(void *data)
> +{
> + struct imx_port *sport = data;
> + struct scatterlist *sgl = &sport->tx_sgl[0];
struct scatterlist *sgl = sport->tx_sgl;
is equivalent, and less typing.
> + struct circ_buf *xmit = &sport->port.state->xmit;
> +
> + dma_unmap_sg(sport->port.dev, sgl, sport->dma_tx_nents, DMA_TO_DEVICE);
> +
> + /* update the stat */
> + spin_lock(&sport->port.lock);
> + xmit->tail = (xmit->tail + sport->tx_bytes) & (UART_XMIT_SIZE - 1);
> + sport->port.icount.tx += sport->tx_bytes;
> + spin_unlock(&sport->port.lock);
Callbacks are called from tasklet context, and will have IRQs enabled.
As the port lock is taken from IRQ context, this is waiting for a deadlock
to happen. Have you run this with lockdep enabled?
> +
> + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
> + uart_write_wakeup(&sport->port);
> + schedule_work(&sport->tsk_dma_tx);
> +}
> +
> +static void dma_tx_work(struct work_struct *w)
> +{
> + struct imx_port *sport = container_of(w, struct imx_port, tsk_dma_tx);
> + struct circ_buf *xmit = &sport->port.state->xmit;
> + struct scatterlist *sgl = &sport->tx_sgl[0];
> + struct dma_async_tx_descriptor *desc;
> + struct dma_chan *chan = sport->dma_chan_tx;
> + enum dma_status status;
> + unsigned long flags;
> + int ret;
> +
> + status = chan->device->device_tx_status(chan, (dma_cookie_t)NULL, NULL);
Cookies aren't pointers. NULL is inappropriate here.
> + if (DMA_IN_PROGRESS == status)
> + return;
> +
> + spin_lock_irqsave(&sport->port.lock, flags);
> + sport->tx_bytes = uart_circ_chars_pending(xmit);
> + if (sport->tx_bytes > 0) {
> + if (xmit->tail > xmit->head) {
> + sport->dma_tx_nents = 2;
> + sg_init_table(sgl, 2);
> + sg_set_buf(sgl, xmit->buf + xmit->tail,
> + UART_XMIT_SIZE - xmit->tail);
> + sg_set_buf(&sgl[1], xmit->buf, xmit->head);
> + } else {
> + sport->dma_tx_nents = 1;
> + sg_init_one(sgl, xmit->buf + xmit->tail,
> + sport->tx_bytes);
> + }
> + spin_unlock_irqrestore(&sport->port.lock, flags);
> +
> + ret = dma_map_sg(sport->port.dev, sgl,
> + sport->dma_tx_nents, DMA_TO_DEVICE);
> + if (ret == 0) {
> + pr_err("DMA mapping error for TX.\n");
> + return;
> + }
> + desc = dmaengine_prep_slave_sg(chan, sgl,
> + sport->dma_tx_nents, DMA_MEM_TO_DEV, 0);
If you're setting a callback, you should set DMA_PREP_INTERRUPT in the
flags too.
> + if (!desc) {
> + pr_err("We cannot prepare for the TX slave dma!\n");
> + return;
> + }
> + desc->callback = dma_tx_callback;
> + desc->callback_param = sport;
> +
> + /* fire it */
> + dmaengine_submit(desc);
> + dma_async_issue_pending(chan);
> + return;
> + }
> + spin_unlock_irqrestore(&sport->port.lock, flags);
> + return;
> +}
> +
> /*
> * interrupts disabled on entry
> */
> @@ -448,8 +549,10 @@ static void imx_start_tx(struct uart_port *port)
> writel(temp, sport->port.membase + UCR1);
> }
>
> - temp = readl(sport->port.membase + UCR1);
> - writel(temp | UCR1_TXMPTYEN, sport->port.membase + UCR1);
> + if (!sport->enable_dma) {
> + temp = readl(sport->port.membase + UCR1);
> + writel(temp | UCR1_TXMPTYEN, sport->port.membase + UCR1);
> + }
>
> if (USE_IRDA(sport)) {
> temp = readl(sport->port.membase + UCR1);
> @@ -461,6 +564,11 @@ static void imx_start_tx(struct uart_port *port)
> writel(temp, sport->port.membase + UCR4);
> }
>
> + if (sport->enable_dma) {
> + schedule_work(&sport->tsk_dma_tx);
> + return;
> + }
> +
> if (readl(sport->port.membase + uts_reg(sport)) & UTS_TXEMPTY)
> imx_transmit_buffer(sport);
> }
> @@ -577,6 +685,28 @@ out:
> return IRQ_HANDLED;
> }
>
> +/*
> + * We wait for the RXFIFO is filled with some data, and then
> + * arise a DMA operation to receive the data.
> + */
> +static void imx_dma_rxint(struct imx_port *sport)
> +{
> + unsigned long temp;
> +
> + temp = readl(sport->port.membase + USR2);
> + if ((temp & USR2_RDR) && !sport->dma_is_rxing) {
> + sport->dma_is_rxing = true;
> +
> + /* disable the `Recerver Ready Interrrupt` */
> + temp = readl(sport->port.membase + UCR1);
> + temp &= ~(UCR1_RRDYEN);
> + writel(temp, sport->port.membase + UCR1);
> +
> + /* tell the DMA to receive the data. */
> + schedule_work(&sport->tsk_dma_rx);
> + }
> +}
> +
> static irqreturn_t imx_int(int irq, void *dev_id)
> {
> struct imx_port *sport = dev_id;
> @@ -584,8 +714,12 @@ static irqreturn_t imx_int(int irq, void *dev_id)
>
> sts = readl(sport->port.membase + USR1);
>
> - if (sts & USR1_RRDY)
> - imx_rxint(irq, dev_id);
> + if (sts & USR1_RRDY) {
> + if (sport->enable_dma)
> + imx_dma_rxint(sport);
> + else
> + imx_rxint(irq, dev_id);
> + }
>
> if (sts & USR1_TRDY &&
> readl(sport->port.membase + UCR1) & UCR1_TXMPTYEN)
> @@ -685,6 +819,195 @@ static int imx_setup_ufcr(struct imx_port *sport, unsigned int mode)
> return 0;
> }
>
> +static bool imx_uart_filter(struct dma_chan *chan, void *param)
> +{
> + struct imx_port *sport = param;
> +
> + if (!imx_dma_is_general_purpose(chan))
> + return false;
> + chan->private = &sport->dma_data;
> + return true;
> +}
> +
> +#define RX_BUF_SIZE (PAGE_SIZE)
> +static int start_rx_dma(struct imx_port *sport);
> +static void dma_rx_work(struct work_struct *w)
> +{
> + struct imx_port *sport = container_of(w, struct imx_port, tsk_dma_rx);
> + struct tty_struct *tty = sport->port.state->port.tty;
> +
> + if (sport->rx_bytes) {
> + tty_insert_flip_string(tty, sport->rx_buf, sport->rx_bytes);
> + tty_flip_buffer_push(tty);
> + sport->rx_bytes = 0;
> + }
> +
> + if (sport->dma_is_rxing)
> + start_rx_dma(sport);
> +}
> +
> +static void imx_finish_dma(struct imx_port *sport)
> +{
> + unsigned long temp;
> +
> + /* Enable the interrupt when the RXFIFO is not empty. */
> + temp = readl(sport->port.membase + UCR1);
> + temp |= UCR1_RRDYEN;
> + writel(temp, sport->port.membase + UCR1);
> +
> + sport->dma_is_rxing = false;
> + if (waitqueue_active(&sport->dma_wait))
> + wake_up(&sport->dma_wait);
> +}
> +
> +/*
> + * There are three kinds of RX DMA interrupts in the MX6Q:
> + * [1] the RX DMA buffer is full.
> + * [2] the Aging timer expires(wait for 8 bytes long)
> + * [3] the Idle Condition Detect(enabled the UCR4_IDDMAEN).
> + *
> + * The [2] and [3] are similar, but [3] is better.
> + * [3] can wait for 32 bytes long, so we do not use [2].
> + */
> +static void dma_rx_callback(void *data)
> +{
> + struct imx_port *sport = data;
> + struct dma_chan *chan = sport->dma_chan_rx;
> + unsigned int count;
> + struct tty_struct *tty;
> + struct scatterlist *sgl;
> + struct dma_tx_state state;
> + enum dma_status status;
> +
> + tty = sport->port.state->port.tty;
> + sgl = &sport->rx_sgl;
> +
> + /* unmap it first */
> + dma_unmap_sg(sport->port.dev, sgl, 1, DMA_FROM_DEVICE);
> +
> + /* If we have finish the reading. we will not accept any more data. */
> + if (tty->closing) {
> + imx_finish_dma(sport);
> + return;
> + }
> +
> + status = chan->device->device_tx_status(chan,
> + (dma_cookie_t)NULL, &state);
> + count = RX_BUF_SIZE - state.residue;
> + if (count) {
> + sport->rx_bytes = count;
> + schedule_work(&sport->tsk_dma_rx);
> + } else
> + imx_finish_dma(sport);
> +}
> +
> +static int start_rx_dma(struct imx_port *sport)
> +{
> + struct scatterlist *sgl = &sport->rx_sgl;
> + struct dma_chan *chan = sport->dma_chan_rx;
> + struct dma_async_tx_descriptor *desc;
> + int ret;
> +
> + sg_init_one(sgl, sport->rx_buf, RX_BUF_SIZE);
> + ret = dma_map_sg(sport->port.dev, sgl, 1, DMA_FROM_DEVICE);
> + if (ret == 0) {
> + pr_err("DMA mapping error for RX.\n");
> + return -EINVAL;
> + }
> + desc = dmaengine_prep_slave_sg(chan, sgl, 1, DMA_DEV_TO_MEM, 0);
> + if (!desc) {
> + pr_err("We cannot prepare for the RX slave dma!\n");
> + return -EINVAL;
> + }
> + desc->callback = dma_rx_callback;
> + desc->callback_param = sport;
> +
> + dmaengine_submit(desc);
> + dma_async_issue_pending(chan);
> + return 0;
> +}
> +
> +static void imx_uart_dma_exit(struct imx_port *sport)
> +{
> + if (sport->dma_chan_rx) {
> + dma_release_channel(sport->dma_chan_rx);
> + sport->dma_chan_rx = NULL;
> +
> + kfree(sport->rx_buf);
> + sport->rx_buf = NULL;
> + }
> +
> + if (sport->dma_chan_tx) {
> + dma_release_channel(sport->dma_chan_tx);
> + sport->dma_chan_tx = NULL;
> + }
> +}
> +
> +/* see the "i.MX61 SDMA Scripts User Manual.doc" for the parameters */
> +static int imx_uart_dma_init(struct imx_port *sport)
> +{
> + struct dma_slave_config slave_config;
> + dma_cap_mask_t mask;
> + int ret;
> +
> + /* prepare for RX : */
> + dma_cap_zero(mask);
> + dma_cap_set(DMA_SLAVE, mask);
> +
> + sport->dma_data.priority = DMA_PRIO_HIGH;
> + sport->dma_data.dma_request = sport->dma_req_rx;
> + sport->dma_data.peripheral_type = IMX_DMATYPE_UART;
> +
> + sport->dma_chan_rx = dma_request_channel(mask, imx_uart_filter, sport);
> + if (!sport->dma_chan_rx) {
> + pr_err("cannot get the DMA channel.\n");
> + ret = -EINVAL;
> + goto err;
> + }
> +
> + slave_config.direction = DMA_DEV_TO_MEM;
> + slave_config.src_addr = sport->port.mapbase + URXD0;
> + slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
> + slave_config.src_maxburst = RXTL; /* fix me */
> + ret = dmaengine_slave_config(sport->dma_chan_rx, &slave_config);
> + if (ret) {
> + pr_err("error in RX dma configuration.\n");
> + goto err;
> + }
> +
> + sport->rx_buf = kzalloc(PAGE_SIZE, GFP_DMA);
> + if (!sport->rx_buf) {
> + pr_err("cannot alloc DMA buffer.\n");
> + ret = -ENOMEM;
> + goto err;
> + }
> + sport->rx_bytes = 0;
> +
> + /* prepare for TX : */
> + sport->dma_data.dma_request = sport->dma_req_tx;
> + sport->dma_chan_tx = dma_request_channel(mask, imx_uart_filter, sport);
> + if (!sport->dma_chan_tx) {
> + pr_err("cannot get the TX DMA channel!\n");
> + ret = -EINVAL;
> + goto err;
> + }
> +
> + slave_config.direction = DMA_MEM_TO_DEV;
> + slave_config.dst_addr = sport->port.mapbase + URTX0;
> + slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
> + slave_config.dst_maxburst = TXTL; /* fix me */
> + ret = dmaengine_slave_config(sport->dma_chan_tx, &slave_config);
> + if (ret) {
> + pr_err("error in TX dma configuration.");
> + goto err;
> + }
> +
> + return 0;
> +err:
> + imx_uart_dma_exit(sport);
> + return ret;
> +}
> +
> /* half the RX buffer size */
> #define CTSTL 16
>
> @@ -756,6 +1079,19 @@ static int imx_startup(struct uart_port *port)
> }
> }
>
> + /* Enable the SDMA for uart. */
> + if (sport->enable_dma) {
> + int ret;
> + ret = imx_uart_dma_init(sport);
> + if (ret)
> + goto error_out3;
> +
> + sport->port.flags |= UPF_LOW_LATENCY;
> + INIT_WORK(&sport->tsk_dma_tx, dma_tx_work);
> + INIT_WORK(&sport->tsk_dma_rx, dma_rx_work);
> + init_waitqueue_head(&sport->dma_wait);
> + }
> +
> /*
> * Finally, clear and enable interrupts
> */
> @@ -763,6 +1099,11 @@ static int imx_startup(struct uart_port *port)
>
> temp = readl(sport->port.membase + UCR1);
> temp |= UCR1_RRDYEN | UCR1_RTSDEN | UCR1_UARTEN;
> + if (sport->enable_dma) {
> + temp |= UCR1_RDMAEN | UCR1_TDMAEN;
> + /* ICD, wait for more than 32 frames, but it still to short. */
> + temp |= UCR1_ICD_REG(3);
> + }
>
> if (USE_IRDA(sport)) {
> temp |= UCR1_IREN;
> @@ -806,6 +1147,12 @@ static int imx_startup(struct uart_port *port)
> writel(temp, sport->port.membase + UCR3);
> }
>
> + if (sport->enable_dma) {
> + temp = readl(sport->port.membase + UCR4);
> + temp |= UCR4_IDDMAEN;
> + writel(temp, sport->port.membase + UCR4);
> + }
> +
> /*
> * Enable modem status interrupts
> */
> @@ -840,6 +1187,13 @@ static void imx_shutdown(struct uart_port *port)
> struct imx_port *sport = (struct imx_port *)port;
> unsigned long temp;
>
> + if (sport->enable_dma) {
> + /* We have to wait for the DMA to finish. */
> + wait_event(sport->dma_wait, !sport->dma_is_rxing);
> + imx_stop_rx(port);
> + imx_uart_dma_exit(sport);
> + }
> +
> temp = readl(sport->port.membase + UCR2);
> temp &= ~(UCR2_TXEN);
> writel(temp, sport->port.membase + UCR2);
> @@ -875,8 +1229,16 @@ static void imx_shutdown(struct uart_port *port)
> temp &= ~(UCR1_TXMPTYEN | UCR1_RRDYEN | UCR1_RTSDEN | UCR1_UARTEN);
> if (USE_IRDA(sport))
> temp &= ~(UCR1_IREN);
> + if (sport->enable_dma)
> + temp &= ~(UCR1_RDMAEN | UCR1_TDMAEN);
>
> writel(temp, sport->port.membase + UCR1);
> +
> + if (sport->enable_dma) {
> + temp = readl(sport->port.membase + UCR4);
> + temp &= ~UCR4_IDDMAEN;
> + writel(temp, sport->port.membase + UCR4);
> + }
> }
>
> static void
> @@ -1013,6 +1375,9 @@ imx_set_termios(struct uart_port *port, struct ktermios *termios,
>
> ufcr = readl(sport->port.membase + UFCR);
> ufcr = (ufcr & (~UFCR_RFDIV)) | UFCR_RFDIV_REG(div);
> + /* set the DTE mode */
> + if (sport->enable_dte)
> + ufcr |= UFCR_DCEDTE;
> writel(ufcr, sport->port.membase + UFCR);
>
> writel(num, sport->port.membase + UBIR);
> @@ -1409,6 +1774,7 @@ static int serial_imx_probe_dt(struct imx_port *sport,
> const struct of_device_id *of_id =
> of_match_device(imx_uart_dt_ids, &pdev->dev);
> int ret;
> + u32 dma_req[2];
>
> if (!np)
> /* no device tree device */
> @@ -1427,6 +1793,18 @@ static int serial_imx_probe_dt(struct imx_port *sport,
> if (of_get_property(np, "fsl,irda-mode", NULL))
> sport->use_irda = 1;
>
> + if (of_get_property(np, "fsl,enable-dma", NULL))
> + sport->enable_dma = 1;
> +
> + if (of_get_property(np, "fsl,enable-dte", NULL))
> + sport->enable_dte = 1;
> +
> + if (of_property_read_u32_array(np, "fsl,uart-dma-events", dma_req,
> + ARRAY_SIZE(dma_req)) == 0) {
> + sport->dma_req_rx = dma_req[0];
> + sport->dma_req_tx = dma_req[1];
> + }
> +
> sport->devdata = of_id->data;
>
> return 0;
> --
> 1.7.0.4
>
>
>
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
More information about the linux-arm-kernel
mailing list