dmaengine: mdc: Correct terminate_all handling
[cascardo/linux.git] / drivers / dma / img-mdc-dma.c
index 42ae58d..a4c53be 100644 (file)
@@ -651,6 +651,48 @@ static enum dma_status mdc_tx_status(struct dma_chan *chan,
        return ret;
 }
 
+static unsigned int mdc_get_new_events(struct mdc_chan *mchan)
+{
+       u32 val, processed, done1, done2;
+       unsigned int ret;
+
+       val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED);
+       processed = (val >> MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT) &
+                               MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK;
+       /*
+        * CMDS_DONE may have incremented between reading CMDS_PROCESSED
+        * and clearing INT_ACTIVE.  Re-read CMDS_PROCESSED to ensure we
+        * didn't miss a command completion.
+        */
+       do {
+               val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED);
+
+               done1 = (val >> MDC_CMDS_PROCESSED_CMDS_DONE_SHIFT) &
+                       MDC_CMDS_PROCESSED_CMDS_DONE_MASK;
+
+               val &= ~((MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK <<
+                         MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT) |
+                        MDC_CMDS_PROCESSED_INT_ACTIVE);
+
+               val |= done1 << MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT;
+
+               mdc_chan_writel(mchan, val, MDC_CMDS_PROCESSED);
+
+               val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED);
+
+               done2 = (val >> MDC_CMDS_PROCESSED_CMDS_DONE_SHIFT) &
+                       MDC_CMDS_PROCESSED_CMDS_DONE_MASK;
+       } while (done1 != done2);
+
+       if (done1 >= processed)
+               ret = done1 - processed;
+       else
+               ret = ((MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK + 1) -
+                       processed) + done1;
+
+       return ret;
+}
+
 static int mdc_terminate_all(struct dma_chan *chan)
 {
        struct mdc_chan *mchan = to_mdc_chan(chan);
@@ -667,6 +709,8 @@ static int mdc_terminate_all(struct dma_chan *chan)
        mchan->desc = NULL;
        vchan_get_all_descriptors(&mchan->vc, &head);
 
+       mdc_get_new_events(mchan);
+
        spin_unlock_irqrestore(&mchan->vc.lock, flags);
 
        if (mdesc)
@@ -703,35 +747,17 @@ static irqreturn_t mdc_chan_irq(int irq, void *dev_id)
 {
        struct mdc_chan *mchan = (struct mdc_chan *)dev_id;
        struct mdc_tx_desc *mdesc;
-       u32 val, processed, done1, done2;
-       unsigned int i;
+       unsigned int i, new_events;
 
        spin_lock(&mchan->vc.lock);
 
-       val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED);
-       processed = (val >> MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT) &
-               MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK;
-       /*
-        * CMDS_DONE may have incremented between reading CMDS_PROCESSED
-        * and clearing INT_ACTIVE.  Re-read CMDS_PROCESSED to ensure we
-        * didn't miss a command completion.
-        */
-       do {
-               val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED);
-               done1 = (val >> MDC_CMDS_PROCESSED_CMDS_DONE_SHIFT) &
-                       MDC_CMDS_PROCESSED_CMDS_DONE_MASK;
-               val &= ~((MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK <<
-                         MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT) |
-                        MDC_CMDS_PROCESSED_INT_ACTIVE);
-               val |= done1 << MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT;
-               mdc_chan_writel(mchan, val, MDC_CMDS_PROCESSED);
-               val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED);
-               done2 = (val >> MDC_CMDS_PROCESSED_CMDS_DONE_SHIFT) &
-                       MDC_CMDS_PROCESSED_CMDS_DONE_MASK;
-       } while (done1 != done2);
-
        dev_dbg(mdma2dev(mchan->mdma), "IRQ on channel %d\n", mchan->chan_nr);
 
+       new_events = mdc_get_new_events(mchan);
+
+       if (!new_events)
+               goto out;
+
        mdesc = mchan->desc;
        if (!mdesc) {
                dev_warn(mdma2dev(mchan->mdma),
@@ -740,8 +766,7 @@ static irqreturn_t mdc_chan_irq(int irq, void *dev_id)
                goto out;
        }
 
-       for (i = processed; i != done1;
-            i = (i + 1) % (MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK + 1)) {
+       for (i = 0; i < new_events; i++) {
                /*
                 * The first interrupt in a transfer indicates that the
                 * command list has been loaded, not that a command has