Skip to content

Commit

Permalink
DMA: AT91: Get residual bytes in dma buffer
Browse files Browse the repository at this point in the history
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 <[email protected]>
Signed-off-by: Vinod Koul <[email protected]>
  • Loading branch information
ElenSong authored and Vinod Koul committed Jul 5, 2013
1 parent d088c33 commit d48de6f
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 18 deletions.
130 changes: 112 additions & 18 deletions drivers/dma/at_hdmac.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);


/*----------------------------------------------------------------------*/
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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");
}
Expand Down
3 changes: 3 additions & 0 deletions drivers/dma/at_hdmac_regs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand All @@ -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
Expand All @@ -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;
Expand Down

0 comments on commit d48de6f

Please sign in to comment.