Commit 008913db authored by Lars-Peter Clausen's avatar Lars-Peter Clausen Committed by Vinod Koul

dmaengine: axi-dmac: Fix software cyclic mode

When running in software cyclic mode the driver currently does not go back
to the first segment once the last segment has been reached. Effectively
making the transfer non-cyclic.

Fix this by going back to the first segment once the last segment has been
reached for cyclic transfers.

Special care need to be taken to avoid a segment from being submitted
multiple times concurrently, which could happen for transfers with a number
of segments that is smaller than the DMA controller's internal queue.
Signed-off-by: default avatarLars-Peter Clausen <lars@metafoo.de>
Signed-off-by: default avatarVinod Koul <vinod.koul@intel.com>
parent 63ab76db
...@@ -72,6 +72,9 @@ ...@@ -72,6 +72,9 @@
#define AXI_DMAC_FLAG_CYCLIC BIT(0) #define AXI_DMAC_FLAG_CYCLIC BIT(0)
/* The maximum ID allocated by the hardware is 31 */
#define AXI_DMAC_SG_UNUSED 32U
struct axi_dmac_sg { struct axi_dmac_sg {
dma_addr_t src_addr; dma_addr_t src_addr;
dma_addr_t dest_addr; dma_addr_t dest_addr;
...@@ -80,6 +83,7 @@ struct axi_dmac_sg { ...@@ -80,6 +83,7 @@ struct axi_dmac_sg {
unsigned int dest_stride; unsigned int dest_stride;
unsigned int src_stride; unsigned int src_stride;
unsigned int id; unsigned int id;
bool schedule_when_free;
}; };
struct axi_dmac_desc { struct axi_dmac_desc {
...@@ -200,11 +204,21 @@ static void axi_dmac_start_transfer(struct axi_dmac_chan *chan) ...@@ -200,11 +204,21 @@ static void axi_dmac_start_transfer(struct axi_dmac_chan *chan)
} }
sg = &desc->sg[desc->num_submitted]; sg = &desc->sg[desc->num_submitted];
/* Already queued in cyclic mode. Wait for it to finish */
if (sg->id != AXI_DMAC_SG_UNUSED) {
sg->schedule_when_free = true;
return;
}
desc->num_submitted++; desc->num_submitted++;
if (desc->num_submitted == desc->num_sgs) if (desc->num_submitted == desc->num_sgs) {
chan->next_desc = NULL; if (desc->cyclic)
else desc->num_submitted = 0; /* Start again */
else
chan->next_desc = NULL;
} else {
chan->next_desc = desc; chan->next_desc = desc;
}
sg->id = axi_dmac_read(dmac, AXI_DMAC_REG_TRANSFER_ID); sg->id = axi_dmac_read(dmac, AXI_DMAC_REG_TRANSFER_ID);
...@@ -239,37 +253,52 @@ static struct axi_dmac_desc *axi_dmac_active_desc(struct axi_dmac_chan *chan) ...@@ -239,37 +253,52 @@ static struct axi_dmac_desc *axi_dmac_active_desc(struct axi_dmac_chan *chan)
struct axi_dmac_desc, vdesc.node); struct axi_dmac_desc, vdesc.node);
} }
static void axi_dmac_transfer_done(struct axi_dmac_chan *chan, static bool axi_dmac_transfer_done(struct axi_dmac_chan *chan,
unsigned int completed_transfers) unsigned int completed_transfers)
{ {
struct axi_dmac_desc *active; struct axi_dmac_desc *active;
struct axi_dmac_sg *sg; struct axi_dmac_sg *sg;
bool start_next = false;
active = axi_dmac_active_desc(chan); active = axi_dmac_active_desc(chan);
if (!active) if (!active)
return; return false;
if (active->cyclic) { do {
vchan_cyclic_callback(&active->vdesc); sg = &active->sg[active->num_completed];
} else { if (sg->id == AXI_DMAC_SG_UNUSED) /* Not yet submitted */
do { break;
sg = &active->sg[active->num_completed]; if (!(BIT(sg->id) & completed_transfers))
if (!(BIT(sg->id) & completed_transfers)) break;
break; active->num_completed++;
active->num_completed++; sg->id = AXI_DMAC_SG_UNUSED;
if (active->num_completed == active->num_sgs) { if (sg->schedule_when_free) {
sg->schedule_when_free = false;
start_next = true;
}
if (active->cyclic)
vchan_cyclic_callback(&active->vdesc);
if (active->num_completed == active->num_sgs) {
if (active->cyclic) {
active->num_completed = 0; /* wrap around */
} else {
list_del(&active->vdesc.node); list_del(&active->vdesc.node);
vchan_cookie_complete(&active->vdesc); vchan_cookie_complete(&active->vdesc);
active = axi_dmac_active_desc(chan); active = axi_dmac_active_desc(chan);
} }
} while (active); }
} } while (active);
return start_next;
} }
static irqreturn_t axi_dmac_interrupt_handler(int irq, void *devid) static irqreturn_t axi_dmac_interrupt_handler(int irq, void *devid)
{ {
struct axi_dmac *dmac = devid; struct axi_dmac *dmac = devid;
unsigned int pending; unsigned int pending;
bool start_next = false;
pending = axi_dmac_read(dmac, AXI_DMAC_REG_IRQ_PENDING); pending = axi_dmac_read(dmac, AXI_DMAC_REG_IRQ_PENDING);
if (!pending) if (!pending)
...@@ -283,10 +312,10 @@ static irqreturn_t axi_dmac_interrupt_handler(int irq, void *devid) ...@@ -283,10 +312,10 @@ static irqreturn_t axi_dmac_interrupt_handler(int irq, void *devid)
unsigned int completed; unsigned int completed;
completed = axi_dmac_read(dmac, AXI_DMAC_REG_TRANSFER_DONE); completed = axi_dmac_read(dmac, AXI_DMAC_REG_TRANSFER_DONE);
axi_dmac_transfer_done(&dmac->chan, completed); start_next = axi_dmac_transfer_done(&dmac->chan, completed);
} }
/* Space has become available in the descriptor queue */ /* Space has become available in the descriptor queue */
if (pending & AXI_DMAC_IRQ_SOT) if ((pending & AXI_DMAC_IRQ_SOT) || start_next)
axi_dmac_start_transfer(&dmac->chan); axi_dmac_start_transfer(&dmac->chan);
spin_unlock(&dmac->chan.vchan.lock); spin_unlock(&dmac->chan.vchan.lock);
...@@ -336,12 +365,16 @@ static void axi_dmac_issue_pending(struct dma_chan *c) ...@@ -336,12 +365,16 @@ static void axi_dmac_issue_pending(struct dma_chan *c)
static struct axi_dmac_desc *axi_dmac_alloc_desc(unsigned int num_sgs) static struct axi_dmac_desc *axi_dmac_alloc_desc(unsigned int num_sgs)
{ {
struct axi_dmac_desc *desc; struct axi_dmac_desc *desc;
unsigned int i;
desc = kzalloc(sizeof(struct axi_dmac_desc) + desc = kzalloc(sizeof(struct axi_dmac_desc) +
sizeof(struct axi_dmac_sg) * num_sgs, GFP_NOWAIT); sizeof(struct axi_dmac_sg) * num_sgs, GFP_NOWAIT);
if (!desc) if (!desc)
return NULL; return NULL;
for (i = 0; i < num_sgs; i++)
desc->sg[i].id = AXI_DMAC_SG_UNUSED;
desc->num_sgs = num_sgs; desc->num_sgs = num_sgs;
return desc; return desc;
......
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