[RFC PATCH 7/7] tty: serial: 8250: omap: add dma support

From: Sebastian Andrzej Siewior
Date: Tue Jul 29 2014 - 15:01:10 EST


This patch adds the required pieces to OMAP uart for DMA support. The TX
burst size is set to 1 so we can send an arbitrary amout of bytes.

The RX burst is currently set to 8 which means we receive an interrupt
every eight bytes and have to reprogramm everything. Seven bytes in the
RX-fifo mean that no transfer will happen and the UART will send a
RX-timeout event at which point the FIFO will be manually purged.
I played around with the trigger value and burst and burst of one, trigger
eight isn't really better. We receive a DMA-complete event once we
received eight. Should we receive only 7 bytes then the DMA will fetch
them and wait for the last one and there will be RX-timeout from the
UART because the RX-FIFO is empty.
Since this makes most sense for high baudrates I think I will increment
the RX size to something larger.
This works fine on DRA7. AM33xx is a diffent story and I run into two
problems:
- TX, after ptogramming the TX transfer there has to be a byte written
into the FIFO to trigger the transfer. After that, the transfer
continues and reloads as expected. I had a workaround for this but
wasn't working perfectly.
- RX, after the DMA engines fetches the data from the FIFO there is not
only a dma-complete interrupt but also an UART interrupt which
reports nothing (because the FIFO is already empty). I managed a few
to receive wrong data if the UART interrupt was delayed and I am not
yet sure if this is a real problem, a theoretical one or just a bug
somewhere in the driver(s)

Signed-off-by: Sebastian Andrzej Siewior <bigeasy@xxxxxxxxxxxxx>
---
drivers/tty/serial/8250/8250_omap.c | 92 +++++++++++++++++++++++++++++++------
1 file changed, 78 insertions(+), 14 deletions(-)

diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c
index 4d01f45..6cc8174 100644
--- a/drivers/tty/serial/8250/8250_omap.c
+++ b/drivers/tty/serial/8250/8250_omap.c
@@ -20,6 +20,7 @@
#include <linux/pm_runtime.h>
#include <linux/console.h>
#include <linux/pm_qos.h>
+#include <linux/edma.h>

#include "8250.h"

@@ -31,10 +32,16 @@
#define UART_ERRATA_i202_MDR1_ACCESS (1 << 0)
#define OMAP_UART_WER_HAS_TX_WAKEUP (1 << 1)

+#define OMAP_UART_FCR_RX_TRIG 6
+#define OMAP_UART_FCR_TX_TRIG 4
+
/* SCR register bitmasks */
#define OMAP_UART_SCR_RX_TRIG_GRANU1_MASK (1 << 7)
#define OMAP_UART_SCR_TX_TRIG_GRANU1_MASK (1 << 6)
#define OMAP_UART_SCR_TX_EMPTY (1 << 3)
+#define OMAP_UART_SCR_DMAMODE_MASK (3 << 1)
+#define OMAP_UART_SCR_DMAMODE_1 (1 << 1)
+#define OMAP_UART_SCR_DMAMODE_CTL (1 << 0)

/* MVR register bitmasks */
#define OMAP_UART_MVR_SCHEME_SHIFT 30
@@ -45,6 +52,12 @@
#define OMAP_UART_MVR_MAJ_SHIFT 8
#define OMAP_UART_MVR_MIN_MASK 0x3f

+#define UART_TI752_TLR_TX 0
+#define UART_TI752_TLR_RX 4
+
+#define TRIGGER_TLR_MASK(x) ((x & 0x3c) >> 2)
+#define TRIGGER_FCR_MASK(x) (x & 3)
+
/* Enable XON/XOFF flow control on output */
#define OMAP_UART_SW_TX 0x08
/* Enable XON/XOFF flow control on input */
@@ -78,6 +91,7 @@ struct omap8250_priv {
u32 calc_latency;
struct pm_qos_request pm_qos_request;
struct work_struct qos_work;
+ struct uart_8250_dma omap8250_dma;
};

static u32 uart_read(struct uart_8250_port *up, u32 reg)
@@ -158,6 +172,20 @@ static void omap_8250_get_divisor(struct uart_port *port, unsigned int baud,
}
}

+static void omap8250_update_scr(struct uart_8250_port *up,
+ struct omap8250_priv *priv)
+{
+ /*
+ * As it turns out the DMA part in the UART strikes if the CONTROL bit
+ * and the DMA-MODE is set at the same time. Therefore we first set the
+ * control bit with DMA off and then we switch to the required DMA mode.
+ */
+ if (priv->scr & OMAP_UART_SCR_DMAMODE_MASK)
+ serial_out(up, UART_OMAP_SCR,
+ priv->scr & ~OMAP_UART_SCR_DMAMODE_MASK);
+ serial_out(up, UART_OMAP_SCR, priv->scr);
+}
+
/*
* OMAP can use "CLK / (16 or 13) / div" for baud rate. And then we have have
* some differences in how we want to handle flow control.
@@ -273,7 +301,19 @@ static void omap_8250_set_termios(struct uart_port *port,
serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A);
serial_out(up, UART_MCR, up->mcr | UART_MCR_TCRTLR);

- priv->scr = OMAP_UART_SCR_RX_TRIG_GRANU1_MASK | OMAP_UART_SCR_TX_EMPTY;
+ serial_out(up, UART_TI752_TCR, OMAP_UART_TCR_TRIG);
+
+ serial_out(up, UART_TI752_TLR,
+ TRIGGER_TLR_MASK(1) << UART_TI752_TLR_TX |
+ TRIGGER_TLR_MASK(8) << UART_TI752_TLR_RX);
+
+ priv->scr = OMAP_UART_SCR_RX_TRIG_GRANU1_MASK | OMAP_UART_SCR_TX_EMPTY;
+ if (up->dma) {
+ priv->scr |= OMAP_UART_SCR_DMAMODE_1 |
+ OMAP_UART_SCR_DMAMODE_CTL;
+ priv->scr |= OMAP_UART_SCR_TX_TRIG_GRANU1_MASK;
+ }
+
/*
* NOTE: Setting OMAP_UART_SCR_RX_TRIG_GRANU1_MASK sets Enables the
* granularity of 1 for TRIGGER RX level. Along with setting RX FIFO
@@ -283,13 +323,14 @@ static void omap_8250_set_termios(struct uart_port *port,
* we receive an interrupt once TX FIFO (and shift) is empty as this is
* what The irq routine currently expects to happen.
*/
- priv->fcr = UART_FCR6_R_TRIGGER_16 | UART_FCR6_T_TRIGGER_24 |
- UART_FCR_ENABLE_FIFO;
+ priv->fcr |= UART_FCR_ENABLE_FIFO;
+ priv->fcr |= TRIGGER_FCR_MASK(1) << OMAP_UART_FCR_TX_TRIG;
+ priv->fcr |= TRIGGER_FCR_MASK(8) << OMAP_UART_FCR_RX_TRIG;

serial_out(up, UART_FCR, priv->fcr);
serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B);

- serial_out(up, UART_OMAP_SCR, priv->scr);
+ omap8250_update_scr(up, priv);

/* Reset UART_MCR_TCRTLR: this must be done with the EFR_ECB bit set */
serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A);
@@ -333,13 +374,6 @@ static void omap_8250_set_termios(struct uart_port *port,
serial_out(up, UART_XON1, termios->c_cc[VSTART]);
serial_out(up, UART_XOFF1, termios->c_cc[VSTOP]);

- /* Enable access to TCR/TLR */
- serial_out(up, UART_EFR, UART_EFR_ECB);
- serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A);
- serial_out(up, UART_MCR, up->mcr | UART_MCR_TCRTLR);
-
- serial_out(up, UART_TI752_TCR, OMAP_UART_TCR_TRIG);
-
priv->efr = 0;
if (termios->c_cflag & CRTSCTS && up->port.flags & UPF_HARD_FLOW) {
/* Enable AUTORTS and AUTOCTS */
@@ -377,9 +411,8 @@ static void omap_8250_set_termios(struct uart_port *port,
else
up->mcr &= ~UART_MCR_XONANY;
}
- serial_out(up, UART_MCR, up->mcr);
serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B);
- serial_out(up, UART_EFR, 0);
+ serial_out(up, UART_EFR, priv->efr);
serial_out(up, UART_LCR, up->lcr);

port->ops->set_mctrl(port, port->mctrl);
@@ -519,6 +552,9 @@ static int omap_8250_startup(struct uart_port *port)
priv->wer |= OMAP_UART_TX_WAKEUP_EN;
serial_out(up, UART_OMAP_WER, priv->wer);

+ if (up->dma)
+ serial8250_rx_dma(up, 0);
+
pm_runtime_mark_last_busy(port->dev);
pm_runtime_put_autosuspend(port->dev);
return 0;
@@ -580,6 +616,13 @@ static void omap_8250_unthrottle(struct uart_port *port)
pm_runtime_put_autosuspend(port->dev);
}

+#ifdef CONFIG_SERIAL_8250_DMA
+static bool the_no_dma_filter_fn(struct dma_chan *chan, void *param)
+{
+ return false;
+}
+#endif
+
static int omap8250_probe(struct platform_device *pdev)
{
struct resource *regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
@@ -667,7 +710,27 @@ static int omap8250_probe(struct platform_device *pdev)
pm_runtime_get_sync(&pdev->dev);

omap_serial_fill_features_erratas(&up, priv);
+#ifdef CONFIG_SERIAL_8250_DMA
+ if (pdev->dev.of_node) {
+ /*
+ * Oh DMA support. If there are no DMA properties in the DT then
+ * we will fall back to a generic DMA channel which does not
+ * really work here. To ensure that we do not get a generic DMA
+ * channel assigned, we have the the_no_dma_filter_fn() here.
+ * To avoid "failed to request DMA" messages we check for DMA
+ * properties in DT.
+ */
+ ret = of_property_count_strings(pdev->dev.of_node, "dma-names");
+ if (ret == 2) {
+ up.dma = &priv->omap8250_dma;
+ priv->omap8250_dma.fn = the_no_dma_filter_fn;
+ priv->omap8250_dma.rx_size = 8;
+ priv->omap8250_dma.rxconf.src_maxburst = 8;
+ priv->omap8250_dma.txconf.dst_maxburst = 1;

+ }
+ }
+#endif
ret = serial8250_register_8250_port(&up);
if (ret < 0) {
dev_err(&pdev->dev, "unable to register 8250 port\n");
@@ -838,7 +901,8 @@ static void omap8250_restore_context(struct omap8250_priv *priv)
serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A);
serial_out(up, UART_MCR, up->mcr);
serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); /* Config B mode */
- serial_out(up, UART_OMAP_SCR, priv->scr);
+ omap8250_update_scr(up, priv);
+
serial_out(up, UART_EFR, priv->efr);
serial_out(up, UART_LCR, up->lcr);
if (priv->habit & UART_ERRATA_i202_MDR1_ACCESS)
--
2.0.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/