|
@@ -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");
|
|
|
}
|