Commit 0c328de7 authored by Damien.Horsley's avatar Damien.Horsley Committed by Vinod Koul

dmaengine: mdc: Correct terminate_all handling

Use of the CANCEL bit in mdc_terminate_all creates an
additional 'command done' to appear in the registers (in
addition to an interrupt).

In addition, there is a potential race between
mdc_terminate_all and the irq handler if a transfer
completes at the same time as the terminate all (presently
this results in an inappropriate warning).

To handle these issues, any outstanding 'command done'
events are cleared during mdc_terminate_all and the irq
handler takes no action when there are no new 'command done'
events.
Signed-off-by: default avatarDamien.Horsley <Damien.Horsley@imgtec.com>
Signed-off-by: default avatarVinod Koul <vinod.koul@intel.com>
parent 4fa2d09c
...@@ -651,6 +651,48 @@ static enum dma_status mdc_tx_status(struct dma_chan *chan, ...@@ -651,6 +651,48 @@ static enum dma_status mdc_tx_status(struct dma_chan *chan,
return ret; return ret;
} }
static unsigned int mdc_get_new_events(struct mdc_chan *mchan)
{
u32 val, processed, done1, done2;
unsigned int ret;
val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED);
processed = (val >> MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT) &
MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK;
/*
* CMDS_DONE may have incremented between reading CMDS_PROCESSED
* and clearing INT_ACTIVE. Re-read CMDS_PROCESSED to ensure we
* didn't miss a command completion.
*/
do {
val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED);
done1 = (val >> MDC_CMDS_PROCESSED_CMDS_DONE_SHIFT) &
MDC_CMDS_PROCESSED_CMDS_DONE_MASK;
val &= ~((MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK <<
MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT) |
MDC_CMDS_PROCESSED_INT_ACTIVE);
val |= done1 << MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT;
mdc_chan_writel(mchan, val, MDC_CMDS_PROCESSED);
val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED);
done2 = (val >> MDC_CMDS_PROCESSED_CMDS_DONE_SHIFT) &
MDC_CMDS_PROCESSED_CMDS_DONE_MASK;
} while (done1 != done2);
if (done1 >= processed)
ret = done1 - processed;
else
ret = ((MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK + 1) -
processed) + done1;
return ret;
}
static int mdc_terminate_all(struct dma_chan *chan) static int mdc_terminate_all(struct dma_chan *chan)
{ {
struct mdc_chan *mchan = to_mdc_chan(chan); struct mdc_chan *mchan = to_mdc_chan(chan);
...@@ -667,6 +709,8 @@ static int mdc_terminate_all(struct dma_chan *chan) ...@@ -667,6 +709,8 @@ static int mdc_terminate_all(struct dma_chan *chan)
mchan->desc = NULL; mchan->desc = NULL;
vchan_get_all_descriptors(&mchan->vc, &head); vchan_get_all_descriptors(&mchan->vc, &head);
mdc_get_new_events(mchan);
spin_unlock_irqrestore(&mchan->vc.lock, flags); spin_unlock_irqrestore(&mchan->vc.lock, flags);
if (mdesc) if (mdesc)
...@@ -703,35 +747,17 @@ static irqreturn_t mdc_chan_irq(int irq, void *dev_id) ...@@ -703,35 +747,17 @@ static irqreturn_t mdc_chan_irq(int irq, void *dev_id)
{ {
struct mdc_chan *mchan = (struct mdc_chan *)dev_id; struct mdc_chan *mchan = (struct mdc_chan *)dev_id;
struct mdc_tx_desc *mdesc; struct mdc_tx_desc *mdesc;
u32 val, processed, done1, done2; unsigned int i, new_events;
unsigned int i;
spin_lock(&mchan->vc.lock); spin_lock(&mchan->vc.lock);
val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED);
processed = (val >> MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT) &
MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK;
/*
* CMDS_DONE may have incremented between reading CMDS_PROCESSED
* and clearing INT_ACTIVE. Re-read CMDS_PROCESSED to ensure we
* didn't miss a command completion.
*/
do {
val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED);
done1 = (val >> MDC_CMDS_PROCESSED_CMDS_DONE_SHIFT) &
MDC_CMDS_PROCESSED_CMDS_DONE_MASK;
val &= ~((MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK <<
MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT) |
MDC_CMDS_PROCESSED_INT_ACTIVE);
val |= done1 << MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT;
mdc_chan_writel(mchan, val, MDC_CMDS_PROCESSED);
val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED);
done2 = (val >> MDC_CMDS_PROCESSED_CMDS_DONE_SHIFT) &
MDC_CMDS_PROCESSED_CMDS_DONE_MASK;
} while (done1 != done2);
dev_dbg(mdma2dev(mchan->mdma), "IRQ on channel %d\n", mchan->chan_nr); dev_dbg(mdma2dev(mchan->mdma), "IRQ on channel %d\n", mchan->chan_nr);
new_events = mdc_get_new_events(mchan);
if (!new_events)
goto out;
mdesc = mchan->desc; mdesc = mchan->desc;
if (!mdesc) { if (!mdesc) {
dev_warn(mdma2dev(mchan->mdma), dev_warn(mdma2dev(mchan->mdma),
...@@ -740,8 +766,7 @@ static irqreturn_t mdc_chan_irq(int irq, void *dev_id) ...@@ -740,8 +766,7 @@ static irqreturn_t mdc_chan_irq(int irq, void *dev_id)
goto out; goto out;
} }
for (i = processed; i != done1; for (i = 0; i < new_events; i++) {
i = (i + 1) % (MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK + 1)) {
/* /*
* The first interrupt in a transfer indicates that the * The first interrupt in a transfer indicates that the
* command list has been loaded, not that a command has * command list has been loaded, not that a command has
......
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