Commit 782cfeb2 authored by Ludovic Desroches's avatar Ludovic Desroches Committed by Greg Kroah-Hartman

dmaengine: at_xdmac: fix residue computation

commit 25c5e962 upstream.

When computing the residue we need two pieces of information: the current
descriptor and the remaining data of the current descriptor. To get
that information, we need to read consecutively two registers but we
can't do it in an atomic way. For that reason, we have to check manually
that current descriptor has not changed.
Signed-off-by: default avatarLudovic Desroches <ludovic.desroches@atmel.com>
Suggested-by: default avatarCyrille Pitchen <cyrille.pitchen@atmel.com>
Reported-by: default avatarDavid Engraf <david.engraf@sysgo.com>
Tested-by: default avatarDavid Engraf <david.engraf@sysgo.com>
Fixes: e1f7c9ee ("dmaengine: at_xdmac: creation of the atmel
eXtended DMA Controller driver")
Signed-off-by: default avatarVinod Koul <vinod.koul@intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent f3c83858
...@@ -176,6 +176,7 @@ ...@@ -176,6 +176,7 @@
#define AT_XDMAC_MAX_CHAN 0x20 #define AT_XDMAC_MAX_CHAN 0x20
#define AT_XDMAC_MAX_CSIZE 16 /* 16 data */ #define AT_XDMAC_MAX_CSIZE 16 /* 16 data */
#define AT_XDMAC_MAX_DWIDTH 8 /* 64 bits */ #define AT_XDMAC_MAX_DWIDTH 8 /* 64 bits */
#define AT_XDMAC_RESIDUE_MAX_RETRIES 5
#define AT_XDMAC_DMA_BUSWIDTHS\ #define AT_XDMAC_DMA_BUSWIDTHS\
(BIT(DMA_SLAVE_BUSWIDTH_UNDEFINED) |\ (BIT(DMA_SLAVE_BUSWIDTH_UNDEFINED) |\
...@@ -1383,8 +1384,8 @@ at_xdmac_tx_status(struct dma_chan *chan, dma_cookie_t cookie, ...@@ -1383,8 +1384,8 @@ at_xdmac_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
struct at_xdmac_desc *desc, *_desc; struct at_xdmac_desc *desc, *_desc;
struct list_head *descs_list; struct list_head *descs_list;
enum dma_status ret; enum dma_status ret;
int residue; int residue, retry;
u32 cur_nda, mask, value; u32 cur_nda, check_nda, cur_ubc, mask, value;
u8 dwidth = 0; u8 dwidth = 0;
unsigned long flags; unsigned long flags;
...@@ -1421,7 +1422,42 @@ at_xdmac_tx_status(struct dma_chan *chan, dma_cookie_t cookie, ...@@ -1421,7 +1422,42 @@ at_xdmac_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
cpu_relax(); cpu_relax();
} }
/*
* When processing the residue, we need to read two registers but we
* can't do it in an atomic way. AT_XDMAC_CNDA is used to find where
* we stand in the descriptor list and AT_XDMAC_CUBC is used
* to know how many data are remaining for the current descriptor.
* Since the dma channel is not paused to not loose data, between the
* AT_XDMAC_CNDA and AT_XDMAC_CUBC read, we may have change of
* descriptor.
* For that reason, after reading AT_XDMAC_CUBC, we check if we are
* still using the same descriptor by reading a second time
* AT_XDMAC_CNDA. If AT_XDMAC_CNDA has changed, it means we have to
* read again AT_XDMAC_CUBC.
* Memory barriers are used to ensure the read order of the registers.
* A max number of retries is set because unlikely it can never ends if
* we are transferring a lot of data with small buffers.
*/
cur_nda = at_xdmac_chan_read(atchan, AT_XDMAC_CNDA) & 0xfffffffc; cur_nda = at_xdmac_chan_read(atchan, AT_XDMAC_CNDA) & 0xfffffffc;
rmb();
cur_ubc = at_xdmac_chan_read(atchan, AT_XDMAC_CUBC);
for (retry = 0; retry < AT_XDMAC_RESIDUE_MAX_RETRIES; retry++) {
rmb();
check_nda = at_xdmac_chan_read(atchan, AT_XDMAC_CNDA) & 0xfffffffc;
if (likely(cur_nda == check_nda))
break;
cur_nda = check_nda;
rmb();
cur_ubc = at_xdmac_chan_read(atchan, AT_XDMAC_CUBC);
}
if (unlikely(retry >= AT_XDMAC_RESIDUE_MAX_RETRIES)) {
ret = DMA_ERROR;
goto spin_unlock;
}
/* /*
* Remove size of all microblocks already transferred and the current * Remove size of all microblocks already transferred and the current
* one. Then add the remaining size to transfer of the current * one. Then add the remaining size to transfer of the current
...@@ -1434,7 +1470,7 @@ at_xdmac_tx_status(struct dma_chan *chan, dma_cookie_t cookie, ...@@ -1434,7 +1470,7 @@ at_xdmac_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
if ((desc->lld.mbr_nda & 0xfffffffc) == cur_nda) if ((desc->lld.mbr_nda & 0xfffffffc) == cur_nda)
break; break;
} }
residue += at_xdmac_chan_read(atchan, AT_XDMAC_CUBC) << dwidth; residue += cur_ubc << dwidth;
dma_set_residue(txstate, residue); dma_set_residue(txstate, residue);
......
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