[PATCH] DMA: AT91: Get residual bytes in dma buffer
Song, Elen
Elen.Song at atmel.com
Mon Oct 22 23:32:32 EDT 2012
Ping....
>-----Original Message-----
>From: Song, Elen
>Sent: 2012年10月15日 17:19
>To: Ferre, Nicolas
>Cc: 'Russell King - ARM Linux'; 'Jean-Christophe PLAGNIOL-VILLARD';
>linux-arm-kernel at lists.infradead.org
>Subject: [PATCH] DMA: AT91: Get residual bytes in dma buffer
>
>The main purpose of atc_tx_status is to get residual bytes when dma transfer in
>progress or complete.
>
>Residual bytes is the number of bytes left to read from DMA source.
>During transfer, residual bytes is changing as DMA read from its source.
>When transfer complete, the residual bytes is zero.
>
>atc_get_bytes_left will dynamically get bytes left in dma buffer.
>It reads count register bit btsize to get residue, the btsize only indicate current
>descriptor receive data.
>So we should let atc_get_bytes_left know when descriptor switch or dma
>transfer done.
>DMA_CTRL_ACK is a useful ack, it means current descriptor can not be reused
>when clear.
>The atc_get_bytes_left will find out whether move to next descriptor or transfer
>done.
>
>So in dma handler, we get residue like below:
>dma_handler() {
> async_tx_clear_ack; //clear ack, tell driver it is time to switch
>descriptor
> read residue;
> async_tx_ack; //move to next descriptor
> // no need to set ack when dma transfer done }
>
>DMA should be pause so that we can trust data count.
>
>Signed-off-by: Elen Song <elen.song at atmel.com>
>---
> drivers/dma/at_hdmac.c | 127
>+++++++++++++++++++++++++++++++++++++++----
> drivers/dma/at_hdmac_regs.h | 4 ++
> 2 files changed, 120 insertions(+), 11 deletions(-)
>
>diff --git a/drivers/dma/at_hdmac.c b/drivers/dma/at_hdmac.c index
>13a02f4..b0aa783 100644
>--- a/drivers/dma/at_hdmac.c
>+++ b/drivers/dma/at_hdmac.c
>@@ -229,6 +229,101 @@ static void atc_dostart(struct at_dma_chan *atchan,
>struct at_desc *first)
> vdbg_dump_regs(atchan);
> }
>
>+/*
>+ * atc_get_current_descriptors - get 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,
>+ * it is unwise to call this before stopping the channel for
>+ * absolute measures.
>+ * @atchan: the channel we want to start */ static int
>+atc_get_bytes_left(struct at_dma_chan *atchan) {
>+ struct at_desc *desc_first;
>+ int left = 0, count = 0;
>+ u32 dscr_addr;
>+
>+ /* First descriptor embedds additional information */
>+ desc_first = atc_first_active(atchan);
>+ if (!desc_first)
>+ return -EINVAL;
>+
>+ /* Initialize necessary value in the first time.
>+ * We use desc_cur to save current descriptor,
>+ * save_len record residual dma buffer length.
>+ */
>+ if (!atchan->desc_cur &&
>+ !atchan->save_len) {
>+ atchan->desc_cur = desc_first;
>+ atchan->save_len = desc_first->len;
>+ }
>+
>+ /* This happend in the end of dma transfer or descriptor switch,
>+ * during the dma interrupt handler, DMA_CTRL_ACK should be cleared.
>+ * We trigger this flag to get residual values.
>+ * To be aware of, two conditions should be taken into consideration:
>+ * at the end of a dma transfer, or descriptors switch.
>+ */
>+ if (!(desc_first->txd.flags & DMA_CTRL_ACK)) {
>+ dscr_addr = channel_readl(atchan, DSCR);
>+ /* End of a single or cyclic transfer */
>+ if (dscr_addr == 0 || dscr_addr == desc_first->lli.dscr) {
>+ atchan->desc_cur = NULL;
>+ atchan->save_len = 0;
>+ /* Switch between two descriptor.
>+ * Current descriptor transfer done.
>+ * The residual buffer length should reduce current descriptor.
>+ */
>+ } else {
>+ atchan->desc_cur = atc_get_current_descriptors(atchan,
>+ dscr_addr);
>+ if (!atchan->desc_cur)
>+ return -EINVAL;
>+ atchan->save_len -= (atchan->desc_cur->lli.ctrla
>+ & ATC_BTSIZE_MAX);
>+ }
>+
>+ left = atchan->save_len;
>+ }
>+
>+ /* Get residual value during a descriptor transfer */
>+ if (atchan->desc_cur) {
>+ count = channel_readl(atchan, CTRLA) & ATC_BTSIZE_MAX;
>+ left = atchan->save_len - count;
>+ }
>+
>+ return left;
>+}
>+
> /**
> * atc_chain_complete - finish work for one transaction chain
> * @atchan: channel we work on
>@@ -1034,23 +1129,33 @@ atc_tx_status(struct dma_chan *chan,
> 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);
>-
>- ret = dma_cookie_status(chan, cookie, txstate);
>+ /* Residue is zero while transactions complete */
>+ if (ret == DMA_SUCCESS) {
>+ dma_set_residue(txstate, 0);
>+ return ret;
>+ }
>+ /*
>+ * There's no point calculating the residue if there's
>+ * no txstate to store the value.
>+ */
>+ if (!txstate) {
>+ if (atc_chan_is_paused(atchan))
>+ ret = DMA_PAUSED;
>+ return ret;
> }
>
>- last_complete = chan->completed_cookie;
>- last_used = chan->cookie;
>+ spin_lock_irqsave(&atchan->lock, flags);
>
>- spin_unlock_irqrestore(&atchan->lock, flags);
>+ /* Get number of bytes left in the active transactions */
>+ bytes = atc_get_bytes_left(atchan);
>+ if (bytes < 0)
>+ dev_err(chan2dev(chan), "get residue value error\n");
>+ dma_set_residue(txstate, bytes);
>
>- if (ret != DMA_SUCCESS)
>- dma_set_residue(txstate, atc_first_active(atchan)->len);
>+ spin_unlock_irqrestore(&atchan->lock, flags);
>
> if (atc_chan_is_paused(atchan))
> ret = DMA_PAUSED;
>diff --git a/drivers/dma/at_hdmac_regs.h b/drivers/dma/at_hdmac_regs.h index
>116e4ad..ea8d747 100644
>--- a/drivers/dma/at_hdmac_regs.h
>+++ b/drivers/dma/at_hdmac_regs.h
>@@ -226,12 +226,14 @@ 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
>+ * @save_len: to record dma buffer 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
> * @queue: list of descriptors ready to be submitted to engine
> * @free_list: list of descriptors usable by the channel
> * @descs_allocated: records the actual size of the descriptor pool
>+ * @desc_cur: current descriptor
> */
> struct at_dma_chan {
> struct dma_chan chan_common;
>@@ -242,6 +244,7 @@ struct at_dma_chan {
> struct tasklet_struct tasklet;
> u32 save_cfg;
> u32 save_dscr;
>+ u32 save_len;
> struct dma_slave_config dma_sconfig;
>
> spinlock_t lock;
>@@ -251,6 +254,7 @@ struct at_dma_chan {
> struct list_head queue;
> struct list_head free_list;
> unsigned int descs_allocated;
>+ struct at_desc *desc_cur;
> };
>
> #define channel_readl(atchan, name) \
>--
>1.7.9.5
More information about the linux-arm-kernel
mailing list