Commit d48de6f1 authored by Elen Song's avatar Elen Song Committed by Vinod Koul

DMA: AT91: Get residual bytes in dma buffer

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: default avatarElen Song <elen.song@atmel.com>
Signed-off-by: default avatarVinod Koul <vinod.koul@intel.com>
parent d088c33b
...@@ -54,6 +54,7 @@ MODULE_PARM_DESC(init_nr_desc_per_channel, ...@@ -54,6 +54,7 @@ MODULE_PARM_DESC(init_nr_desc_per_channel,
/* prototypes */ /* prototypes */
static dma_cookie_t atc_tx_submit(struct dma_async_tx_descriptor *tx); 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) ...@@ -230,6 +231,94 @@ static void atc_dostart(struct at_dma_chan *atchan, struct at_desc *first)
vdbg_dump_regs(atchan); 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 * atc_chain_complete - finish work for one transaction chain
* @atchan: channel we work on * @atchan: channel we work on
...@@ -496,6 +585,8 @@ static irqreturn_t at_dma_interrupt(int irq, void *dev_id) ...@@ -496,6 +585,8 @@ static irqreturn_t at_dma_interrupt(int irq, void *dev_id)
/* Give information to tasklet */ /* Give information to tasklet */
set_bit(ATC_IS_ERROR, &atchan->status); set_bit(ATC_IS_ERROR, &atchan->status);
} }
if (pending & AT_DMA_BTC(i))
set_bit(ATC_IS_BTC, &atchan->status);
tasklet_schedule(&atchan->tasklet); tasklet_schedule(&atchan->tasklet);
ret = IRQ_HANDLED; ret = IRQ_HANDLED;
} }
...@@ -1035,34 +1126,35 @@ atc_tx_status(struct dma_chan *chan, ...@@ -1035,34 +1126,35 @@ atc_tx_status(struct dma_chan *chan,
struct dma_tx_state *txstate) struct dma_tx_state *txstate)
{ {
struct at_dma_chan *atchan = to_at_dma_chan(chan); struct at_dma_chan *atchan = to_at_dma_chan(chan);
dma_cookie_t last_used;
dma_cookie_t last_complete;
unsigned long flags; unsigned long flags;
enum dma_status ret; enum dma_status ret;
int bytes = 0;
spin_lock_irqsave(&atchan->lock, flags);
ret = dma_cookie_status(chan, cookie, txstate); ret = dma_cookie_status(chan, cookie, txstate);
if (ret != DMA_SUCCESS) { if (ret == DMA_SUCCESS)
atc_cleanup_descriptors(atchan); 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; /* Get number of bytes left in the active transactions */
last_used = chan->cookie; bytes = atc_get_bytes_left(chan);
spin_unlock_irqrestore(&atchan->lock, flags); spin_unlock_irqrestore(&atchan->lock, flags);
if (ret != DMA_SUCCESS) if (unlikely(bytes < 0)) {
dma_set_residue(txstate, atc_first_active(atchan)->len); dev_vdbg(chan2dev(chan), "get residual bytes error\n");
return DMA_ERROR;
if (atc_chan_is_paused(atchan)) } else
ret = DMA_PAUSED; dma_set_residue(txstate, bytes);
dev_vdbg(chan2dev(chan), "tx_status %d: cookie = %d (d%d, u%d)\n", dev_vdbg(chan2dev(chan), "tx_status %d: cookie = %d residue = %d\n",
ret, cookie, last_complete ? last_complete : 0, ret, cookie, bytes);
last_used ? last_used : 0);
return ret; return ret;
} }
...@@ -1146,6 +1238,7 @@ static int atc_alloc_chan_resources(struct dma_chan *chan) ...@@ -1146,6 +1238,7 @@ static int atc_alloc_chan_resources(struct dma_chan *chan)
spin_lock_irqsave(&atchan->lock, flags); spin_lock_irqsave(&atchan->lock, flags);
atchan->descs_allocated = i; atchan->descs_allocated = i;
atchan->remain_desc = 0;
list_splice(&tmp_list, &atchan->free_list); list_splice(&tmp_list, &atchan->free_list);
dma_cookie_init(chan); dma_cookie_init(chan);
spin_unlock_irqrestore(&atchan->lock, flags); spin_unlock_irqrestore(&atchan->lock, flags);
...@@ -1188,6 +1281,7 @@ static void atc_free_chan_resources(struct dma_chan *chan) ...@@ -1188,6 +1281,7 @@ static void atc_free_chan_resources(struct dma_chan *chan)
list_splice_init(&atchan->free_list, &list); list_splice_init(&atchan->free_list, &list);
atchan->descs_allocated = 0; atchan->descs_allocated = 0;
atchan->status = 0; atchan->status = 0;
atchan->remain_desc = 0;
dev_vdbg(chan2dev(chan), "free_chan_resources: done\n"); dev_vdbg(chan2dev(chan), "free_chan_resources: done\n");
} }
......
...@@ -213,6 +213,7 @@ txd_to_at_desc(struct dma_async_tx_descriptor *txd) ...@@ -213,6 +213,7 @@ txd_to_at_desc(struct dma_async_tx_descriptor *txd)
enum atc_status { enum atc_status {
ATC_IS_ERROR = 0, ATC_IS_ERROR = 0,
ATC_IS_PAUSED = 1, ATC_IS_PAUSED = 1,
ATC_IS_BTC = 2,
ATC_IS_CYCLIC = 24, ATC_IS_CYCLIC = 24,
}; };
...@@ -230,6 +231,7 @@ enum atc_status { ...@@ -230,6 +231,7 @@ enum atc_status {
* @save_cfg: configuration register that is saved on suspend/resume cycle * @save_cfg: configuration register that is saved on suspend/resume cycle
* @save_dscr: for cyclic operations, preserve next descriptor address in * @save_dscr: for cyclic operations, preserve next descriptor address in
* the cyclic list on suspend/resume cycle * 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 * @dma_sconfig: configuration for slave transfers, passed via DMA_SLAVE_CONFIG
* @lock: serializes enqueue/dequeue operations to descriptors lists * @lock: serializes enqueue/dequeue operations to descriptors lists
* @active_list: list of descriptors dmaengine is being running on * @active_list: list of descriptors dmaengine is being running on
...@@ -248,6 +250,7 @@ struct at_dma_chan { ...@@ -248,6 +250,7 @@ struct at_dma_chan {
struct tasklet_struct tasklet; struct tasklet_struct tasklet;
u32 save_cfg; u32 save_cfg;
u32 save_dscr; u32 save_dscr;
u32 remain_desc;
struct dma_slave_config dma_sconfig; struct dma_slave_config dma_sconfig;
spinlock_t lock; spinlock_t lock;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment