diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c index c028011cc61c..30eeb7bffad8 100644 --- a/drivers/dma/dw-edma/dw-edma-core.c +++ b/drivers/dma/dw-edma/dw-edma-core.c @@ -40,82 +40,54 @@ u64 dw_edma_get_pci_address(struct dw_edma_chan *chan, phys_addr_t cpu_addr) return cpu_addr; } -static struct dw_edma_chunk *dw_edma_alloc_chunk(struct dw_edma_desc *desc, u32 nburst) -{ - struct dw_edma_chan *chan = desc->chan; - struct dw_edma_chunk *chunk; - - chunk = kzalloc_flex(*chunk, burst, nburst, GFP_NOWAIT); - if (unlikely(!chunk)) - return NULL; - - chunk->chan = chan; - /* Toggling change bit (CB) in each chunk, this is a mechanism to - * inform the eDMA HW block that this is a new linked list ready - * to be consumed. - * - Odd chunks originate CB equal to 0 - * - Even chunks originate CB equal to 1 - */ - chunk->cb = !(desc->chunks_alloc % 2); - - chunk->nburst = nburst; - - list_add_tail(&chunk->list, &desc->chunk_list); - desc->chunks_alloc++; - - return chunk; -} - -static struct dw_edma_desc *dw_edma_alloc_desc(struct dw_edma_chan *chan) +static struct dw_edma_desc * +dw_edma_alloc_desc(struct dw_edma_chan *chan, size_t nburst) { struct dw_edma_desc *desc; - desc = kzalloc_obj(*desc, GFP_NOWAIT); + desc = kzalloc_flex(*desc, burst, nburst, GFP_NOWAIT); if (unlikely(!desc)) return NULL; desc->chan = chan; - - INIT_LIST_HEAD(&desc->chunk_list); + desc->nburst = nburst; + desc->cb = true; return desc; } -static void dw_edma_free_desc(struct dw_edma_desc *desc) -{ - struct dw_edma_chunk *child, *_next; - - /* Remove all the list elements */ - list_for_each_entry_safe(child, _next, &desc->chunk_list, list) { - list_del(&child->list); - kfree(child); - desc->chunks_alloc--; - } - - kfree(desc); -} - static void vchan_free_desc(struct virt_dma_desc *vdesc) { - dw_edma_free_desc(vd2dw_edma_desc(vdesc)); + kfree(vd2dw_edma_desc(vdesc)); } -static void dw_edma_core_start(struct dw_edma_chunk *chunk, bool first) +static void dw_edma_core_start(struct dw_edma_desc *desc, bool first) { - struct dw_edma_chan *chan = chunk->chan; - u32 i = 0; + struct dw_edma_chan *chan = desc->chan; + size_t i = 0; if (chan->non_ll) { - if (chunk->nburst == 1) - chan->dw->core->non_ll_start(chunk->chan, &chunk->burst[0]); + chan->dw->core->non_ll_start(chan, &desc->burst[desc->start_burst]); + desc->done_burst = desc->start_burst; + desc->start_burst += 1; return; } - for (i = 0; i < chunk->nburst; i++) - dw_edma_core_ll_data(chan, &chunk->burst[i], i, chunk->cb, - i == chunk->nburst - 1); + for (i = 0; i + desc->start_burst < desc->nburst; i++) { + u32 idx = i + desc->start_burst; - dw_edma_core_ll_link(chan, i, chunk->cb, chan->ll_region.paddr); + if (i == chan->ll_max) + break; + + dw_edma_core_ll_data(chan, &desc->burst[idx], + i, desc->cb, + idx == desc->nburst - 1 || i == chan->ll_max - 1); + } + + desc->done_burst = desc->start_burst; + desc->start_burst += i; + + dw_edma_core_ll_link(chan, i, desc->cb, chan->ll_region.paddr); if (first) dw_edma_core_ch_enable(chan); @@ -125,7 +97,6 @@ static void dw_edma_core_start(struct dw_edma_chunk *chunk, bool first) static int dw_edma_start_transfer(struct dw_edma_chan *chan) { - struct dw_edma_chunk *child; struct dw_edma_desc *desc; struct virt_dma_desc *vd; @@ -137,16 +108,9 @@ static int dw_edma_start_transfer(struct dw_edma_chan *chan) if (!desc) return 0; - child = list_first_entry_or_null(&desc->chunk_list, - struct dw_edma_chunk, list); - if (!child) - return 0; + dw_edma_core_start(desc, !desc->start_burst); - dw_edma_core_start(child, !desc->xfer_sz); - desc->xfer_sz += child->xfer_sz; - list_del(&child->list); - kfree(child); - desc->chunks_alloc--; + desc->cb = !desc->cb; return 1; } @@ -337,8 +301,10 @@ dw_edma_device_tx_status(struct dma_chan *dchan, dma_cookie_t cookie, vd = vchan_find_desc(&chan->vc, cookie); if (vd) { desc = vd2dw_edma_desc(vd); - if (desc) - residue = desc->alloc_sz - desc->xfer_sz; + + residue = desc->alloc_sz; + if (desc && desc->done_burst) + residue -= desc->burst[desc->done_burst - 1].xfer_sz; } spin_unlock_irqrestore(&chan->vc.lock, flags); @@ -355,13 +321,11 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer, struct dw_edma_chan *chan = dchan2dw_edma_chan(xfer->dchan); enum dma_transfer_direction dir = xfer->direction; struct scatterlist *sg = NULL; - struct dw_edma_chunk *chunk = NULL; struct dw_edma_burst *burst; struct dw_edma_desc *desc; u64 src_addr, dst_addr; size_t fsz = 0; - u32 bursts_max; - u32 cnt = 0; + size_t cnt = 0; u32 i; if (!chan->configured) @@ -418,17 +382,6 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer, return NULL; } - /* - * For non-LL mode, only a single burst can be handled - * in a single chunk unlike LL mode where multiple bursts - * can be configured in a single chunk. - */ - bursts_max = chan->non_ll ? 1 : chan->ll_max; - - desc = dw_edma_alloc_desc(chan); - if (unlikely(!desc)) - goto err_alloc; - if (xfer->type == EDMA_XFER_INTERLEAVED) { src_addr = xfer->xfer.il->src_start; dst_addr = xfer->xfer.il->dst_start; @@ -452,19 +405,15 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer, fsz = xfer->xfer.il->frame_size; } + desc = dw_edma_alloc_desc(chan, cnt); + if (unlikely(!desc)) + return NULL; + for (i = 0; i < cnt; i++) { if (xfer->type == EDMA_XFER_SCATTER_GATHER && !sg) break; - if (!(i % bursts_max)) { - u32 n = min(cnt - i, bursts_max); - - chunk = dw_edma_alloc_chunk(desc, n); - if (unlikely(!chunk)) - goto err_alloc; - } - - burst = chunk->burst + (i % bursts_max); + burst = desc->burst + i; if (xfer->type == EDMA_XFER_CYCLIC) burst->sz = xfer->xfer.cyclic.len; @@ -473,8 +422,8 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer, else if (xfer->type == EDMA_XFER_INTERLEAVED) burst->sz = xfer->xfer.il->sgl[i % fsz].size; - chunk->xfer_sz += burst->sz; desc->alloc_sz += burst->sz; + burst->xfer_sz = desc->alloc_sz; if (dir == DMA_DEV_TO_MEM) { burst->sar = src_addr; @@ -529,12 +478,6 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer, } return vchan_tx_prep(&chan->vc, &desc->vd, xfer->flags); - -err_alloc: - if (desc) - dw_edma_free_desc(desc); - - return NULL; } static struct dma_async_tx_descriptor * @@ -605,8 +548,14 @@ static void dw_hdma_set_callback_result(struct virt_dma_desc *vd, return; desc = vd2dw_edma_desc(vd); - if (desc) - residue = desc->alloc_sz - desc->xfer_sz; + if (desc) { + residue = desc->alloc_sz; + + if (result == DMA_TRANS_NOERROR) + residue -= desc->burst[desc->start_burst - 1].xfer_sz; + else if (desc->done_burst) + residue -= desc->burst[desc->done_burst - 1].xfer_sz; + } res = &vd->tx_result; res->result = result; @@ -625,7 +574,7 @@ static void dw_edma_done_interrupt(struct dw_edma_chan *chan) switch (chan->request) { case EDMA_REQ_NONE: desc = vd2dw_edma_desc(vd); - if (!desc->chunks_alloc) { + if (desc->start_burst >= desc->nburst) { dw_hdma_set_callback_result(vd, DMA_TRANS_NOERROR); list_del(&vd->node); diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h index 4950c57fca34..3c958ca05144 100644 --- a/drivers/dma/dw-edma/dw-edma-core.h +++ b/drivers/dma/dw-edma/dw-edma-core.h @@ -46,26 +46,21 @@ struct dw_edma_burst { u64 sar; u64 dar; u32 sz; -}; - -struct dw_edma_chunk { - struct list_head list; - struct dw_edma_chan *chan; - u8 cb; + /* precalulate summary of previous burst total size */ u32 xfer_sz; - u32 nburst; - struct dw_edma_burst burst[] __counted_by(nburst); }; struct dw_edma_desc { struct virt_dma_desc vd; struct dw_edma_chan *chan; - struct list_head chunk_list; - - u32 chunks_alloc; u32 alloc_sz; - u32 xfer_sz; + + size_t done_burst; + size_t start_burst; + u8 cb; + size_t nburst; + struct dw_edma_burst burst[] __counted_by(nburst); }; struct dw_edma_chan { @@ -128,7 +123,6 @@ struct dw_edma_core_ops { void (*ll_link)(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr); void (*ch_doorbell)(struct dw_edma_chan *chan); void (*ch_enable)(struct dw_edma_chan *chan); - void (*ch_config)(struct dw_edma_chan *chan); void (*debugfs_on)(struct dw_edma *dw); void (*ack_emulated_irq)(struct dw_edma *dw); @@ -170,6 +164,14 @@ struct dw_edma_chan *dchan2dw_edma_chan(struct dma_chan *dchan) return vc2dw_edma_chan(to_virt_chan(dchan)); } +static inline u64 dw_edma_core_get_ll_paddr(struct dw_edma_chan *chan) +{ + if (chan->dir == EDMA_DIR_WRITE) + return chan->dw->chip->ll_region_wr[chan->id].paddr; + + return chan->dw->chip->ll_region_rd[chan->id].paddr; +} + static inline void dw_edma_core_off(struct dw_edma *dw) {