Merge remote-tracking branch 'tty/tty-next'
[deliverable/linux.git] / drivers / dma / imx-sdma.c
index a6bffbc47ee2e6d7ca3649de7f37af38fb9d7e0d..b9629b2bfc050b3c6cd4db691da49f99c7d85232 100644 (file)
@@ -670,14 +670,11 @@ static void sdma_event_disable(struct sdma_channel *sdmac, unsigned int event)
        writel_relaxed(val, sdma->regs + chnenbl);
 }
 
-static void sdma_handle_channel_loop(struct sdma_channel *sdmac)
-{
-       dmaengine_desc_get_callback_invoke(&sdmac->desc, NULL);
-}
-
 static void sdma_update_channel_loop(struct sdma_channel *sdmac)
 {
        struct sdma_buffer_descriptor *bd;
+       int error = 0;
+       enum dma_status old_status = sdmac->status;
 
        /*
         * loop mode. Iterate over descriptors, re-setup them and
@@ -689,17 +686,41 @@ static void sdma_update_channel_loop(struct sdma_channel *sdmac)
                if (bd->mode.status & BD_DONE)
                        break;
 
-               if (bd->mode.status & BD_RROR)
+               if (bd->mode.status & BD_RROR) {
+                       bd->mode.status &= ~BD_RROR;
                        sdmac->status = DMA_ERROR;
+                       error = -EIO;
+               }
 
+              /*
+               * We use bd->mode.count to calculate the residue, since contains
+               * the number of bytes present in the current buffer descriptor.
+               */
+
+               sdmac->chn_real_count = bd->mode.count;
                bd->mode.status |= BD_DONE;
+               bd->mode.count = sdmac->period_len;
+
+               /*
+                * The callback is called from the interrupt context in order
+                * to reduce latency and to avoid the risk of altering the
+                * SDMA transaction status by the time the client tasklet is
+                * executed.
+                */
+
+               dmaengine_desc_get_callback_invoke(&sdmac->desc, NULL);
+
                sdmac->buf_tail++;
                sdmac->buf_tail %= sdmac->num_bd;
+
+               if (error)
+                       sdmac->status = old_status;
        }
 }
 
-static void mxc_sdma_handle_channel_normal(struct sdma_channel *sdmac)
+static void mxc_sdma_handle_channel_normal(unsigned long data)
 {
+       struct sdma_channel *sdmac = (struct sdma_channel *) data;
        struct sdma_buffer_descriptor *bd;
        int i, error = 0;
 
@@ -726,16 +747,6 @@ static void mxc_sdma_handle_channel_normal(struct sdma_channel *sdmac)
        dmaengine_desc_get_callback_invoke(&sdmac->desc, NULL);
 }
 
-static void sdma_tasklet(unsigned long data)
-{
-       struct sdma_channel *sdmac = (struct sdma_channel *) data;
-
-       if (sdmac->flags & IMX_DMA_SG_LOOP)
-               sdma_handle_channel_loop(sdmac);
-       else
-               mxc_sdma_handle_channel_normal(sdmac);
-}
-
 static irqreturn_t sdma_int_handler(int irq, void *dev_id)
 {
        struct sdma_engine *sdma = dev_id;
@@ -752,8 +763,8 @@ static irqreturn_t sdma_int_handler(int irq, void *dev_id)
 
                if (sdmac->flags & IMX_DMA_SG_LOOP)
                        sdma_update_channel_loop(sdmac);
-
-               tasklet_schedule(&sdmac->tasklet);
+               else
+                       tasklet_schedule(&sdmac->tasklet);
 
                __clear_bit(channel, &stat);
        }
@@ -1374,7 +1385,8 @@ static enum dma_status sdma_tx_status(struct dma_chan *chan,
        u32 residue;
 
        if (sdmac->flags & IMX_DMA_SG_LOOP)
-               residue = (sdmac->num_bd - sdmac->buf_tail) * sdmac->period_len;
+               residue = (sdmac->num_bd - sdmac->buf_tail) *
+                          sdmac->period_len - sdmac->chn_real_count;
        else
                residue = sdmac->chn_count - sdmac->chn_real_count;
 
@@ -1757,7 +1769,7 @@ static int sdma_probe(struct platform_device *pdev)
                dma_cookie_init(&sdmac->chan);
                sdmac->channel = i;
 
-               tasklet_init(&sdmac->tasklet, sdma_tasklet,
+               tasklet_init(&sdmac->tasklet, mxc_sdma_handle_channel_normal,
                             (unsigned long) sdmac);
                /*
                 * Add the channel to the DMAC list. Do not add channel 0 though
This page took 0.026357 seconds and 5 git commands to generate.