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 <[email protected]>
---
 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-omap" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to