Commit 156a599b authored by Dmitry Osipenko's avatar Dmitry Osipenko Committed by Vinod Koul

dmaengine: tegra-apb: Support per-burst residue granularity

Tegra's APB DMA engine updates words counter after each transferred burst
of data, hence it can report transfer's residual with more fidelity which
may be required in cases like audio playback. In particular this fixes
audio stuttering during playback in a chromium web browser. The patch is
based on the original work that was made by Ben Dooks and a patch from
downstream kernel. It was tested on Tegra20 and Tegra30 devices.

Link: https://lore.kernel.org/lkml/20190424162348.23692-1-ben.dooks@codethink.co.uk/
Link: https://nv-tegra.nvidia.com/gitweb/?p=linux-4.4.git;a=commit;h=c7bba40c6846fbf3eaad35c4472dcc7d8bbc02e5Inspired-by: default avatarBen Dooks <ben.dooks@codethink.co.uk>
Reviewed-by: default avatarJon Hunter <jonathanh@nvidia.com>
Signed-off-by: default avatarDmitry Osipenko <digetx@gmail.com>
Link: https://lore.kernel.org/r/20190705150519.18171-1-digetx@gmail.comSigned-off-by: default avatarVinod Koul <vkoul@kernel.org>
parent 72503b25
...@@ -152,6 +152,7 @@ struct tegra_dma_sg_req { ...@@ -152,6 +152,7 @@ struct tegra_dma_sg_req {
bool last_sg; bool last_sg;
struct list_head node; struct list_head node;
struct tegra_dma_desc *dma_desc; struct tegra_dma_desc *dma_desc;
unsigned int words_xferred;
}; };
/* /*
...@@ -496,6 +497,7 @@ static void tegra_dma_configure_for_next(struct tegra_dma_channel *tdc, ...@@ -496,6 +497,7 @@ static void tegra_dma_configure_for_next(struct tegra_dma_channel *tdc,
tdc_write(tdc, TEGRA_APBDMA_CHAN_CSR, tdc_write(tdc, TEGRA_APBDMA_CHAN_CSR,
nsg_req->ch_regs.csr | TEGRA_APBDMA_CSR_ENB); nsg_req->ch_regs.csr | TEGRA_APBDMA_CSR_ENB);
nsg_req->configured = true; nsg_req->configured = true;
nsg_req->words_xferred = 0;
tegra_dma_resume(tdc); tegra_dma_resume(tdc);
} }
...@@ -511,6 +513,7 @@ static void tdc_start_head_req(struct tegra_dma_channel *tdc) ...@@ -511,6 +513,7 @@ static void tdc_start_head_req(struct tegra_dma_channel *tdc)
typeof(*sg_req), node); typeof(*sg_req), node);
tegra_dma_start(tdc, sg_req); tegra_dma_start(tdc, sg_req);
sg_req->configured = true; sg_req->configured = true;
sg_req->words_xferred = 0;
tdc->busy = true; tdc->busy = true;
} }
...@@ -638,6 +641,8 @@ static void handle_cont_sngl_cycle_dma_done(struct tegra_dma_channel *tdc, ...@@ -638,6 +641,8 @@ static void handle_cont_sngl_cycle_dma_done(struct tegra_dma_channel *tdc,
list_add_tail(&dma_desc->cb_node, &tdc->cb_desc); list_add_tail(&dma_desc->cb_node, &tdc->cb_desc);
dma_desc->cb_count++; dma_desc->cb_count++;
sgreq->words_xferred = 0;
/* If not last req then put at end of pending list */ /* If not last req then put at end of pending list */
if (!list_is_last(&sgreq->node, &tdc->pending_sg_req)) { if (!list_is_last(&sgreq->node, &tdc->pending_sg_req)) {
list_move_tail(&sgreq->node, &tdc->pending_sg_req); list_move_tail(&sgreq->node, &tdc->pending_sg_req);
...@@ -797,6 +802,65 @@ static int tegra_dma_terminate_all(struct dma_chan *dc) ...@@ -797,6 +802,65 @@ static int tegra_dma_terminate_all(struct dma_chan *dc)
return 0; return 0;
} }
static unsigned int tegra_dma_sg_bytes_xferred(struct tegra_dma_channel *tdc,
struct tegra_dma_sg_req *sg_req)
{
unsigned long status, wcount = 0;
if (!list_is_first(&sg_req->node, &tdc->pending_sg_req))
return 0;
if (tdc->tdma->chip_data->support_separate_wcount_reg)
wcount = tdc_read(tdc, TEGRA_APBDMA_CHAN_WORD_TRANSFER);
status = tdc_read(tdc, TEGRA_APBDMA_CHAN_STATUS);
if (!tdc->tdma->chip_data->support_separate_wcount_reg)
wcount = status;
if (status & TEGRA_APBDMA_STATUS_ISE_EOC)
return sg_req->req_len;
wcount = get_current_xferred_count(tdc, sg_req, wcount);
if (!wcount) {
/*
* If wcount wasn't ever polled for this SG before, then
* simply assume that transfer hasn't started yet.
*
* Otherwise it's the end of the transfer.
*
* The alternative would be to poll the status register
* until EOC bit is set or wcount goes UP. That's so
* because EOC bit is getting set only after the last
* burst's completion and counter is less than the actual
* transfer size by 4 bytes. The counter value wraps around
* in a cyclic mode before EOC is set(!), so we can't easily
* distinguish start of transfer from its end.
*/
if (sg_req->words_xferred)
wcount = sg_req->req_len - 4;
} else if (wcount < sg_req->words_xferred) {
/*
* This case will never happen for a non-cyclic transfer.
*
* For a cyclic transfer, although it is possible for the
* next transfer to have already started (resetting the word
* count), this case should still not happen because we should
* have detected that the EOC bit is set and hence the transfer
* was completed.
*/
WARN_ON_ONCE(1);
wcount = sg_req->req_len - 4;
} else {
sg_req->words_xferred = wcount;
}
return wcount;
}
static enum dma_status tegra_dma_tx_status(struct dma_chan *dc, static enum dma_status tegra_dma_tx_status(struct dma_chan *dc,
dma_cookie_t cookie, struct dma_tx_state *txstate) dma_cookie_t cookie, struct dma_tx_state *txstate)
{ {
...@@ -806,6 +870,7 @@ static enum dma_status tegra_dma_tx_status(struct dma_chan *dc, ...@@ -806,6 +870,7 @@ static enum dma_status tegra_dma_tx_status(struct dma_chan *dc,
enum dma_status ret; enum dma_status ret;
unsigned long flags; unsigned long flags;
unsigned int residual; unsigned int residual;
unsigned int bytes = 0;
ret = dma_cookie_status(dc, cookie, txstate); ret = dma_cookie_status(dc, cookie, txstate);
if (ret == DMA_COMPLETE) if (ret == DMA_COMPLETE)
...@@ -825,6 +890,7 @@ static enum dma_status tegra_dma_tx_status(struct dma_chan *dc, ...@@ -825,6 +890,7 @@ static enum dma_status tegra_dma_tx_status(struct dma_chan *dc,
list_for_each_entry(sg_req, &tdc->pending_sg_req, node) { list_for_each_entry(sg_req, &tdc->pending_sg_req, node) {
dma_desc = sg_req->dma_desc; dma_desc = sg_req->dma_desc;
if (dma_desc->txd.cookie == cookie) { if (dma_desc->txd.cookie == cookie) {
bytes = tegra_dma_sg_bytes_xferred(tdc, sg_req);
ret = dma_desc->dma_status; ret = dma_desc->dma_status;
goto found; goto found;
} }
...@@ -836,7 +902,7 @@ static enum dma_status tegra_dma_tx_status(struct dma_chan *dc, ...@@ -836,7 +902,7 @@ static enum dma_status tegra_dma_tx_status(struct dma_chan *dc,
found: found:
if (dma_desc && txstate) { if (dma_desc && txstate) {
residual = dma_desc->bytes_requested - residual = dma_desc->bytes_requested -
(dma_desc->bytes_transferred % ((dma_desc->bytes_transferred + bytes) %
dma_desc->bytes_requested); dma_desc->bytes_requested);
dma_set_residue(txstate, residual); dma_set_residue(txstate, residual);
} }
...@@ -1441,12 +1507,7 @@ static int tegra_dma_probe(struct platform_device *pdev) ...@@ -1441,12 +1507,7 @@ static int tegra_dma_probe(struct platform_device *pdev)
BIT(DMA_SLAVE_BUSWIDTH_4_BYTES) | BIT(DMA_SLAVE_BUSWIDTH_4_BYTES) |
BIT(DMA_SLAVE_BUSWIDTH_8_BYTES); BIT(DMA_SLAVE_BUSWIDTH_8_BYTES);
tdma->dma_dev.directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV); tdma->dma_dev.directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
/* tdma->dma_dev.residue_granularity = DMA_RESIDUE_GRANULARITY_BURST;
* XXX The hardware appears to support
* DMA_RESIDUE_GRANULARITY_BURST-level reporting, but it's
* only used by this driver during tegra_dma_terminate_all()
*/
tdma->dma_dev.residue_granularity = DMA_RESIDUE_GRANULARITY_SEGMENT;
tdma->dma_dev.device_config = tegra_dma_slave_config; tdma->dma_dev.device_config = tegra_dma_slave_config;
tdma->dma_dev.device_terminate_all = tegra_dma_terminate_all; tdma->dma_dev.device_terminate_all = tegra_dma_terminate_all;
tdma->dma_dev.device_tx_status = tegra_dma_tx_status; tdma->dma_dev.device_tx_status = tegra_dma_tx_status;
......
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