ARM: tegra: dma: Update actual bytes_transferred in dma cancel
Laxman Dewangan [Wed, 21 Mar 2012 12:24:21 +0000 (17:24 +0530)]
When canceling dma, updating actual bytes transferred by dma,
making all requests status to aborted and deleting from channel
request queue.

Change-Id: I860780814340d54465de5b2ae11a6895319f428c
Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
Reviewed-on: http://git-master/r/90815
Reviewed-by: Automatic_Commit_Validation_User

arch/arm/mach-tegra/dma.c
arch/arm/mach-tegra/include/mach/dma.h

index c9c0cda..98e7644 100644 (file)
@@ -159,6 +159,7 @@ static bool tegra_dma_update_hw_partial(struct tegra_dma_channel *ch,
 static void handle_oneshot_dma(struct tegra_dma_channel *ch);
 static void handle_continuous_dbl_dma(struct tegra_dma_channel *ch);
 static void handle_continuous_sngl_dma(struct tegra_dma_channel *ch);
+static void handle_dma_isr_locked(struct tegra_dma_channel *ch);
 
 void tegra_dma_flush(struct tegra_dma_channel *ch)
 {
@@ -182,21 +183,6 @@ static void tegra_dma_stop(struct tegra_dma_channel *ch)
                writel(status, ch->addr + APB_DMA_CHAN_STA);
 }
 
-int tegra_dma_cancel(struct tegra_dma_channel *ch)
-{
-       unsigned long irq_flags;
-
-       spin_lock_irqsave(&ch->lock, irq_flags);
-       while (!list_empty(&ch->list))
-               list_del(ch->list.next);
-
-       tegra_dma_stop(ch);
-
-       spin_unlock_irqrestore(&ch->lock, irq_flags);
-       return 0;
-}
-EXPORT_SYMBOL(tegra_dma_cancel);
-
 static void pause_dma(bool wait_for_burst_complete)
 {
        spin_lock(&enable_lock);
@@ -416,6 +402,74 @@ skip_stop_dma:
 }
 EXPORT_SYMBOL(tegra_dma_dequeue_req);
 
+int tegra_dma_cancel(struct tegra_dma_channel *ch)
+{
+       struct tegra_dma_req *hreq = NULL;
+       unsigned long status;
+       unsigned long irq_flags;
+       struct tegra_dma_req *cb_req = NULL;
+       dma_callback callback = NULL;
+       struct list_head new_list;
+
+       INIT_LIST_HEAD(&new_list);
+
+       if (ch->mode & TEGRA_DMA_SHARED) {
+               pr_err("Can not abort requests from shared channel %d\n",
+                       ch->id);
+               return -EPERM;
+       }
+
+       spin_lock_irqsave(&ch->lock, irq_flags);
+
+       /* If list is empty, return with error*/
+       if (list_empty(&ch->list)) {
+               spin_unlock_irqrestore(&ch->lock, irq_flags);
+               return 0;
+       }
+
+       /* Pause dma before checking the queue status */
+       pause_dma(true);
+       status = readl(ch->addr + APB_DMA_CHAN_STA);
+       if (status & STA_ISE_EOC) {
+               handle_dma_isr_locked(ch);
+               cb_req = ch->cb_req;
+               callback = ch->callback;
+               ch->cb_req = NULL;
+               ch->callback = NULL;
+               /* Read status because it may get changed */
+               status = readl(ch->addr + APB_DMA_CHAN_STA);
+       }
+
+       /* Abort head requests, stop dma and dequeue all requests */
+       if (!list_empty(&ch->list)) {
+               tegra_dma_stop(ch);
+               hreq = list_entry(ch->list.next, typeof(*hreq), node);
+               hreq->bytes_transferred +=
+                               get_current_xferred_count(ch, hreq, status);
+
+               /* copy the list into new list. */
+               list_replace_init(&ch->list, &new_list);
+       }
+
+       resume_dma();
+
+       spin_unlock_irqrestore(&ch->lock, irq_flags);
+
+       /* Call callback if it is due from interrupts */
+       if (callback)
+               callback(cb_req);
+
+       /* Abort all requests on list. */
+       while (!list_empty(&new_list)) {
+               hreq = list_entry(new_list.next, typeof(*hreq), node);
+               hreq->status = -TEGRA_DMA_REQ_ERROR_ABORTED;
+               list_del(&hreq->node);
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(tegra_dma_cancel);
+
 bool tegra_dma_is_empty(struct tegra_dma_channel *ch)
 {
        unsigned long irq_flags;
index 948538a..5e092d3 100644 (file)
@@ -189,6 +189,15 @@ bool tegra_dma_is_empty(struct tegra_dma_channel *ch);
 struct tegra_dma_channel *tegra_dma_allocate_channel(int mode,
                const char namefmt[], ...);
 void tegra_dma_free_channel(struct tegra_dma_channel *ch);
+
+/*
+ * tegra_dma_cancel: Stop the dma and remove all request from pending request
+ * queue for transfer.
+ * The pending list for data transfer will become empty after this callback.
+ * The status of each request will be marked as ABORTED.
+ * bytes_transferred in each requests shows the actual bytes transferred by dma.
+ * Callbacks will not be called when cancel the requests.
+*/
 int tegra_dma_cancel(struct tegra_dma_channel *ch);
 
 int __init tegra_dma_init(void);