Merge tag 'dmaengine-4.16-rc1' of git://git.infradead.org/users/vkoul/slave-dma
[linux-2.6-microblaze.git] / drivers / dma / sh / rcar-dmac.c
index 35c3936..e3ff162 100644 (file)
@@ -10,6 +10,7 @@
  * published by the Free Software Foundation.
  */
 
+#include <linux/delay.h>
 #include <linux/dma-mapping.h>
 #include <linux/dmaengine.h>
 #include <linux/interrupt.h>
@@ -741,6 +742,41 @@ static int rcar_dmac_fill_hwdesc(struct rcar_dmac_chan *chan,
 /* -----------------------------------------------------------------------------
  * Stop and reset
  */
+static void rcar_dmac_chcr_de_barrier(struct rcar_dmac_chan *chan)
+{
+       u32 chcr;
+       unsigned int i;
+
+       /*
+        * Ensure that the setting of the DE bit is actually 0 after
+        * clearing it.
+        */
+       for (i = 0; i < 1024; i++) {
+               chcr = rcar_dmac_chan_read(chan, RCAR_DMACHCR);
+               if (!(chcr & RCAR_DMACHCR_DE))
+                       return;
+               udelay(1);
+       }
+
+       dev_err(chan->chan.device->dev, "CHCR DE check error\n");
+}
+
+static void rcar_dmac_sync_tcr(struct rcar_dmac_chan *chan)
+{
+       u32 chcr = rcar_dmac_chan_read(chan, RCAR_DMACHCR);
+
+       if (!(chcr & RCAR_DMACHCR_DE))
+               return;
+
+       /* set DE=0 and flush remaining data */
+       rcar_dmac_chan_write(chan, RCAR_DMACHCR, (chcr & ~RCAR_DMACHCR_DE));
+
+       /* make sure all remaining data was flushed */
+       rcar_dmac_chcr_de_barrier(chan);
+
+       /* back DE */
+       rcar_dmac_chan_write(chan, RCAR_DMACHCR, chcr);
+}
 
 static void rcar_dmac_chan_halt(struct rcar_dmac_chan *chan)
 {
@@ -749,6 +785,7 @@ static void rcar_dmac_chan_halt(struct rcar_dmac_chan *chan)
        chcr &= ~(RCAR_DMACHCR_DSE | RCAR_DMACHCR_DSIE | RCAR_DMACHCR_IE |
                  RCAR_DMACHCR_TE | RCAR_DMACHCR_DE);
        rcar_dmac_chan_write(chan, RCAR_DMACHCR, chcr);
+       rcar_dmac_chcr_de_barrier(chan);
 }
 
 static void rcar_dmac_chan_reinit(struct rcar_dmac_chan *chan)
@@ -1309,8 +1346,11 @@ static unsigned int rcar_dmac_chan_get_residue(struct rcar_dmac_chan *chan,
                residue += chunk->size;
        }
 
+       if (desc->direction == DMA_DEV_TO_MEM)
+               rcar_dmac_sync_tcr(chan);
+
        /* Add the residue for the current chunk. */
-       residue += rcar_dmac_chan_read(chan, RCAR_DMATCR) << desc->xfer_shift;
+       residue += rcar_dmac_chan_read(chan, RCAR_DMATCRB) << desc->xfer_shift;
 
        return residue;
 }
@@ -1481,6 +1521,8 @@ static irqreturn_t rcar_dmac_isr_channel(int irq, void *dev)
        if (chcr & RCAR_DMACHCR_TE)
                mask |= RCAR_DMACHCR_DE;
        rcar_dmac_chan_write(chan, RCAR_DMACHCR, chcr & ~mask);
+       if (mask & RCAR_DMACHCR_DE)
+               rcar_dmac_chcr_de_barrier(chan);
 
        if (chcr & RCAR_DMACHCR_DSE)
                ret |= rcar_dmac_isr_desc_stage_end(chan);