]> git.proxmox.com Git - mirror_ubuntu-eoan-kernel.git/commitdiff
DMA: AT91: Get residual bytes in dma buffer
authorElen Song <elen.song@atmel.com>
Fri, 10 May 2013 03:01:46 +0000 (11:01 +0800)
committerVinod Koul <vinod.koul@intel.com>
Fri, 5 Jul 2013 06:10:31 +0000 (11:40 +0530)
Add support for returning the residue for current transfer cookie by
reading the transfered buffer size(BTSIZE) in CTRLA register.

For a single buffer cookie, the descriptor length minus BTSIZE
can get the residue.

For a lli cookie, remain_desc will record remain descriptor length
when last descriptor finish, the remain_desc minus BTSIZE can get the
current residue.

If the cookie has completed successfully, the residue will be zero.
If the cookie is in progress, it will be the number of bytes yet to be transferred.
If get residue error, the cookie will be turn into error status.

Check dma fifo to see if data remain, let issue pending finish remain work if there is.
Signed-off-by: Elen Song <elen.song@atmel.com>
Signed-off-by: Vinod Koul <vinod.koul@intel.com>
drivers/dma/at_hdmac.c
drivers/dma/at_hdmac_regs.h

index 4c101a9dd3cb9268a2994f7011e0e3f86d5016fb..5ce89368a8db28a76d1b1e593298da6b9bfbdf23 100644 (file)
@@ -54,6 +54,7 @@ MODULE_PARM_DESC(init_nr_desc_per_channel,
 
 /* prototypes */
 static dma_cookie_t atc_tx_submit(struct dma_async_tx_descriptor *tx);
+static void atc_issue_pending(struct dma_chan *chan);
 
 
 /*----------------------------------------------------------------------*/
@@ -230,6 +231,94 @@ static void atc_dostart(struct at_dma_chan *atchan, struct at_desc *first)
        vdbg_dump_regs(atchan);
 }
 
+/*
+ * atc_get_current_descriptors -
+ * locate the descriptor which equal to physical address in DSCR
+ * @atchan: the channel we want to start
+ * @dscr_addr: physical descriptor address in DSCR
+ */
+static struct at_desc *atc_get_current_descriptors(struct at_dma_chan *atchan,
+                                                       u32 dscr_addr)
+{
+       struct at_desc  *desc, *_desc, *child, *desc_cur = NULL;
+
+       list_for_each_entry_safe(desc, _desc, &atchan->active_list, desc_node) {
+               if (desc->lli.dscr == dscr_addr) {
+                       desc_cur = desc;
+                       break;
+               }
+
+               list_for_each_entry(child, &desc->tx_list, desc_node) {
+                       if (child->lli.dscr == dscr_addr) {
+                               desc_cur = child;
+                               break;
+                       }
+               }
+       }
+
+       return desc_cur;
+}
+
+/*
+ * atc_get_bytes_left -
+ * Get the number of bytes residue in dma buffer,
+ * @chan: the channel we want to start
+ */
+static int atc_get_bytes_left(struct dma_chan *chan)
+{
+       struct at_dma_chan      *atchan = to_at_dma_chan(chan);
+       struct at_dma           *atdma = to_at_dma(chan->device);
+       int     chan_id = atchan->chan_common.chan_id;
+       struct at_desc *desc_first = atc_first_active(atchan);
+       struct at_desc *desc_cur;
+       int ret = 0, count = 0;
+
+       /*
+        * Initialize necessary values in the first time.
+        * remain_desc record remain desc length.
+        */
+       if (atchan->remain_desc == 0)
+               /* First descriptor embedds the transaction length */
+               atchan->remain_desc = desc_first->len;
+
+       /*
+        * This happens when current descriptor transfer complete.
+        * The residual buffer size should reduce current descriptor length.
+        */
+       if (unlikely(test_bit(ATC_IS_BTC, &atchan->status))) {
+               clear_bit(ATC_IS_BTC, &atchan->status);
+               desc_cur = atc_get_current_descriptors(atchan,
+                                               channel_readl(atchan, DSCR));
+               if (!desc_cur) {
+                       ret = -EINVAL;
+                       goto out;
+               }
+               atchan->remain_desc -= (desc_cur->lli.ctrla & ATC_BTSIZE_MAX)
+                                               << (desc_first->tx_width);
+               if (atchan->remain_desc < 0) {
+                       ret = -EINVAL;
+                       goto out;
+               } else
+                       ret = atchan->remain_desc;
+       } else {
+               /*
+                * Get residual bytes when current
+                * descriptor transfer in progress.
+                */
+               count = (channel_readl(atchan, CTRLA) & ATC_BTSIZE_MAX)
+                               << (desc_first->tx_width);
+               ret = atchan->remain_desc - count;
+       }
+       /*
+        * Check fifo empty.
+        */
+       if (!(dma_readl(atdma, CHSR) & AT_DMA_EMPT(chan_id)))
+               atc_issue_pending(chan);
+
+out:
+       return ret;
+}
+
 /**
  * atc_chain_complete - finish work for one transaction chain
  * @atchan: channel we work on
@@ -496,6 +585,8 @@ static irqreturn_t at_dma_interrupt(int irq, void *dev_id)
                                        /* Give information to tasklet */
                                        set_bit(ATC_IS_ERROR, &atchan->status);
                                }
+                               if (pending & AT_DMA_BTC(i))
+                                       set_bit(ATC_IS_BTC, &atchan->status);
                                tasklet_schedule(&atchan->tasklet);
                                ret = IRQ_HANDLED;
                        }
@@ -1035,34 +1126,35 @@ atc_tx_status(struct dma_chan *chan,
                struct dma_tx_state *txstate)
 {
        struct at_dma_chan      *atchan = to_at_dma_chan(chan);
-       dma_cookie_t            last_used;
-       dma_cookie_t            last_complete;
        unsigned long           flags;
        enum dma_status         ret;
-
-       spin_lock_irqsave(&atchan->lock, flags);
+       int bytes = 0;
 
        ret = dma_cookie_status(chan, cookie, txstate);
-       if (ret != DMA_SUCCESS) {
-               atc_cleanup_descriptors(atchan);
+       if (ret == DMA_SUCCESS)
+               return ret;
+       /*
+        * There's no point calculating the residue if there's
+        * no txstate to store the value.
+        */
+       if (!txstate)
+               return DMA_ERROR;
 
-               ret = dma_cookie_status(chan, cookie, txstate);
-       }
+       spin_lock_irqsave(&atchan->lock, flags);
 
-       last_complete = chan->completed_cookie;
-       last_used = chan->cookie;
+       /*  Get number of bytes left in the active transactions */
+       bytes = atc_get_bytes_left(chan);
 
        spin_unlock_irqrestore(&atchan->lock, flags);
 
-       if (ret != DMA_SUCCESS)
-               dma_set_residue(txstate, atc_first_active(atchan)->len);
-
-       if (atc_chan_is_paused(atchan))
-               ret = DMA_PAUSED;
+       if (unlikely(bytes < 0)) {
+               dev_vdbg(chan2dev(chan), "get residual bytes error\n");
+               return DMA_ERROR;
+       } else
+               dma_set_residue(txstate, bytes);
 
-       dev_vdbg(chan2dev(chan), "tx_status %d: cookie = %d (d%d, u%d)\n",
-                ret, cookie, last_complete ? last_complete : 0,
-                last_used ? last_used : 0);
+       dev_vdbg(chan2dev(chan), "tx_status %d: cookie = %d residue = %d\n",
+                ret, cookie, bytes);
 
        return ret;
 }
@@ -1146,6 +1238,7 @@ static int atc_alloc_chan_resources(struct dma_chan *chan)
 
        spin_lock_irqsave(&atchan->lock, flags);
        atchan->descs_allocated = i;
+       atchan->remain_desc = 0;
        list_splice(&tmp_list, &atchan->free_list);
        dma_cookie_init(chan);
        spin_unlock_irqrestore(&atchan->lock, flags);
@@ -1188,6 +1281,7 @@ static void atc_free_chan_resources(struct dma_chan *chan)
        list_splice_init(&atchan->free_list, &list);
        atchan->descs_allocated = 0;
        atchan->status = 0;
+       atchan->remain_desc = 0;
 
        dev_vdbg(chan2dev(chan), "free_chan_resources: done\n");
 }
index 3679933fb646e1a718ab7044f8d1b5cf912952e1..f31d647acdfaac3730371e1366ff964e245435ac 100644 (file)
@@ -213,6 +213,7 @@ txd_to_at_desc(struct dma_async_tx_descriptor *txd)
 enum atc_status {
        ATC_IS_ERROR = 0,
        ATC_IS_PAUSED = 1,
+       ATC_IS_BTC = 2,
        ATC_IS_CYCLIC = 24,
 };
 
@@ -230,6 +231,7 @@ enum atc_status {
  * @save_cfg: configuration register that is saved on suspend/resume cycle
  * @save_dscr: for cyclic operations, preserve next descriptor address in
  *             the cyclic list on suspend/resume cycle
+ * @remain_desc: to save remain desc length
  * @dma_sconfig: configuration for slave transfers, passed via DMA_SLAVE_CONFIG
  * @lock: serializes enqueue/dequeue operations to descriptors lists
  * @active_list: list of descriptors dmaengine is being running on
@@ -248,6 +250,7 @@ struct at_dma_chan {
        struct tasklet_struct   tasklet;
        u32                     save_cfg;
        u32                     save_dscr;
+       u32                     remain_desc;
        struct dma_slave_config dma_sconfig;
 
        spinlock_t              lock;