[v3,6/9] dmaengine: sun6i: retire interrupt state before reusing physical channels
Commit Message
Termination stops a physical channel and drops its owner without masking
or clearing its interrupt state. A completion latched by the old transfer
can therefore be delivered to a descriptor installed after reassignment.
Mask and acknowledge the stopped channel's interrupts before releasing
its ownership, and clear pending status before starting a new descriptor.
Keep these register updates under the same device lock used to sample and
acknowledge interrupts in the handler.
Schedule the allocator after termination so pending virtual channels can
use the released physical channel even when no further interrupt occurs.
Fixes: 555859308723 ("dmaengine: sun6i: Add driver for the Allwinner A31 DMA controller")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/dma/sun6i-dma.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
@@ -501,6 +501,11 @@ static int sun6i_dma_start_desc(struct sun6i_vchan *vchan)
irq_reg = pchan->idx / sdev->cfg->num_channels_per_reg;
irq_offset = pchan->idx % sdev->cfg->num_channels_per_reg;
+ /* Do not deliver a previous transfer's interrupt to this descriptor. */
+ sun6i_write_irq_stat(sdev, irq_reg,
+ (DMA_IRQ_HALF | DMA_IRQ_PKG | DMA_IRQ_QUEUE) <<
+ (irq_offset * DMA_IRQ_CHAN_WIDTH));
+
vchan->irq_type = vchan->cyclic ? DMA_IRQ_PKG : DMA_IRQ_QUEUE;
irq_val = sun6i_read_irq_en(sdev, irq_reg);
@@ -973,6 +978,7 @@ static int sun6i_dma_terminate_all(struct dma_chan *chan)
struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
struct sun6i_pchan *pchan;
+ u32 irq_reg, irq_shift, irq_mask;
unsigned long flags;
LIST_HEAD(head);
@@ -994,6 +1000,14 @@ static int sun6i_dma_terminate_all(struct dma_chan *chan)
writel(DMA_CHAN_ENABLE_STOP, pchan->base + DMA_CHAN_ENABLE);
writel(DMA_CHAN_PAUSE_RESUME, pchan->base + DMA_CHAN_PAUSE);
+ irq_reg = pchan->idx / sdev->cfg->num_channels_per_reg;
+ irq_shift = (pchan->idx % sdev->cfg->num_channels_per_reg) *
+ DMA_IRQ_CHAN_WIDTH;
+ irq_mask = (DMA_IRQ_HALF | DMA_IRQ_PKG | DMA_IRQ_QUEUE) << irq_shift;
+ sun6i_write_irq_en(sdev, irq_reg,
+ sun6i_read_irq_en(sdev, irq_reg) & ~irq_mask);
+ sun6i_write_irq_stat(sdev, irq_reg, irq_mask);
+
vchan->phy = NULL;
pchan->vchan = NULL;
pchan->desc = NULL;
@@ -1003,6 +1017,8 @@ static int sun6i_dma_terminate_all(struct dma_chan *chan)
spin_unlock_irqrestore(&sdev->lock, flags);
vchan_dma_desc_free_list(&vchan->vc, &head);
+ if (!atomic_read(&sdev->tasklet_shutdown))
+ tasklet_schedule(&sdev->task);
return 0;
}