@@ -198,7 +198,6 @@ struct sun6i_pchan {
void __iomem *base;
struct sun6i_vchan *vchan;
struct sun6i_desc *desc;
- struct sun6i_desc *done;
};
struct sun6i_vchan {
@@ -217,6 +216,7 @@ struct sun6i_dma_dev {
struct clk *clk;
struct clk *clk_mbus;
int irq;
+ /* Channel assignment and register access; nests outside vc.lock. */
spinlock_t lock;
struct reset_control *rstc;
struct tasklet_struct task;
@@ -486,22 +486,23 @@ static int sun6i_dma_start_desc(struct sun6i_vchan *vchan)
if (!pchan)
return -EAGAIN;
- if (!desc) {
- pchan->desc = NULL;
- pchan->done = NULL;
+ if (!desc)
return -EAGAIN;
- }
list_del(&desc->node);
pchan->desc = to_sun6i_desc(&desc->tx);
- pchan->done = NULL;
sun6i_dma_dump_lli(vchan, pchan->desc->v_lli, pchan->desc->p_lli);
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);
@@ -524,15 +525,15 @@ static void sun6i_dma_tasklet(struct tasklet_struct *t)
struct sun6i_dma_dev *sdev = from_tasklet(sdev, t, task);
struct sun6i_vchan *vchan;
struct sun6i_pchan *pchan;
- unsigned int pchan_alloc = 0;
unsigned int pchan_idx;
+ spin_lock_irq(&sdev->lock);
list_for_each_entry(vchan, &sdev->slave.channels, vc.chan.device_node) {
- spin_lock_irq(&vchan->vc.lock);
+ spin_lock(&vchan->vc.lock);
pchan = vchan->phy;
- if (pchan && pchan->done) {
+ if (pchan && !pchan->desc) {
if (sun6i_dma_start_desc(vchan)) {
/*
* No current txd associated with this channel
@@ -545,10 +546,9 @@ static void sun6i_dma_tasklet(struct tasklet_struct *t)
pchan->vchan = NULL;
}
}
- spin_unlock_irq(&vchan->vc.lock);
+ spin_unlock(&vchan->vc.lock);
}
- spin_lock_irq(&sdev->lock);
for (pchan_idx = 0; pchan_idx < sdev->num_pchans; pchan_idx++) {
pchan = &sdev->pchans[pchan_idx];
@@ -557,31 +557,20 @@ static void sun6i_dma_tasklet(struct tasklet_struct *t)
vchan = list_first_entry(&sdev->pending,
struct sun6i_vchan, node);
+ spin_lock(&vchan->vc.lock);
/* Remove from pending channels */
list_del_init(&vchan->node);
- pchan_alloc |= BIT(pchan_idx);
/* Mark this channel allocated */
pchan->vchan = vchan;
vchan->phy = pchan;
dev_dbg(sdev->slave.dev, "pchan %u: alloc vchan %p\n",
pchan->idx, &vchan->vc);
+ sun6i_dma_start_desc(vchan);
+ spin_unlock(&vchan->vc.lock);
}
spin_unlock_irq(&sdev->lock);
-
- for (pchan_idx = 0; pchan_idx < sdev->num_pchans; pchan_idx++) {
- if (!(pchan_alloc & BIT(pchan_idx)))
- continue;
-
- pchan = sdev->pchans + pchan_idx;
- vchan = pchan->vchan;
- if (vchan) {
- spin_lock_irq(&vchan->vc.lock);
- sun6i_dma_start_desc(vchan);
- spin_unlock_irq(&vchan->vc.lock);
- }
- }
}
static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
@@ -590,9 +579,12 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
u32 channels_per_reg = sdev->cfg->num_channels_per_reg;
struct sun6i_vchan *vchan;
struct sun6i_pchan *pchan;
+ struct sun6i_desc *desc;
+ unsigned long flags;
int i, j, ret = IRQ_NONE;
u32 status;
+ spin_lock_irqsave(&sdev->lock, flags);
for (i = 0; i < DIV_ROUND_UP(sdev->num_pchans, channels_per_reg); i++) {
status = sun6i_read_irq_stat(sdev, i);
if (!status)
@@ -607,15 +599,17 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
i * channels_per_reg + j < sdev->num_pchans && status; j++) {
pchan = sdev->pchans + i * channels_per_reg + j;
vchan = pchan->vchan;
- if (vchan && (status & vchan->irq_type)) {
+ desc = pchan->desc;
+ if (vchan && desc && (status & vchan->irq_type)) {
+ spin_lock(&vchan->vc.lock);
if (vchan->cyclic) {
- vchan_cyclic_callback(&pchan->desc->vd);
+ vchan_cyclic_callback(&desc->vd);
} else {
- spin_lock(&vchan->vc.lock);
- vchan_cookie_complete(&pchan->desc->vd);
- pchan->done = pchan->desc;
- spin_unlock(&vchan->vc.lock);
+ /* Completion transfers ownership to virt-dma. */
+ pchan->desc = NULL;
+ vchan_cookie_complete(&desc->vd);
}
+ spin_unlock(&vchan->vc.lock);
}
status = status >> DMA_IRQ_CHAN_WIDTH;
@@ -625,6 +619,7 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
tasklet_schedule(&sdev->task);
ret = IRQ_HANDLED;
}
+ spin_unlock_irqrestore(&sdev->lock, flags);
return ret;
}
@@ -931,19 +926,20 @@ static int sun6i_dma_pause(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 = vchan->phy;
+ struct sun6i_pchan *pchan;
unsigned long flags;
dev_dbg(dmaengine_chan_dev(chan), "vchan %p: pause\n", &vchan->vc);
+ spin_lock_irqsave(&sdev->lock, flags);
+ pchan = vchan->phy;
if (pchan) {
writel(DMA_CHAN_PAUSE_PAUSE,
pchan->base + DMA_CHAN_PAUSE);
} else {
- spin_lock_irqsave(&sdev->lock, flags);
list_del_init(&vchan->node);
- spin_unlock_irqrestore(&sdev->lock, flags);
}
+ spin_unlock_irqrestore(&sdev->lock, flags);
return 0;
}
@@ -952,23 +948,24 @@ static int sun6i_dma_resume(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 = vchan->phy;
+ struct sun6i_pchan *pchan;
unsigned long flags;
dev_dbg(dmaengine_chan_dev(chan), "vchan %p: resume\n", &vchan->vc);
- spin_lock_irqsave(&vchan->vc.lock, flags);
+ spin_lock_irqsave(&sdev->lock, flags);
+ spin_lock(&vchan->vc.lock);
+ pchan = vchan->phy;
if (pchan) {
writel(DMA_CHAN_PAUSE_RESUME,
pchan->base + DMA_CHAN_PAUSE);
} else if (!list_empty(&vchan->vc.desc_issued)) {
- spin_lock(&sdev->lock);
list_add_tail(&vchan->node, &sdev->pending);
- spin_unlock(&sdev->lock);
}
- spin_unlock_irqrestore(&vchan->vc.lock, flags);
+ spin_unlock(&vchan->vc.lock);
+ spin_unlock_irqrestore(&sdev->lock, flags);
return 0;
}
@@ -977,17 +974,17 @@ 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 = vchan->phy;
+ struct sun6i_pchan *pchan;
+ u32 irq_reg, irq_shift, irq_mask;
unsigned long flags;
LIST_HEAD(head);
spin_lock_irqsave(&sdev->lock, flags);
+ spin_lock(&vchan->vc.lock);
+ pchan = vchan->phy;
list_del_init(&vchan->node);
- spin_unlock_irqrestore(&sdev->lock, flags);
- spin_lock_irqsave(&vchan->vc.lock, flags);
-
- if (pchan && pchan->desc && pchan->desc != pchan->done) {
+ if (pchan && pchan->desc) {
struct virt_dma_desc *vd = &pchan->desc->vd;
vchan_terminate_vdesc(vd);
@@ -1000,15 +997,25 @@ 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;
- pchan->done = NULL;
}
- spin_unlock_irqrestore(&vchan->vc.lock, flags);
+ spin_unlock(&vchan->vc.lock);
+ 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;
}
@@ -1017,8 +1024,9 @@ static enum dma_status sun6i_dma_tx_status(struct dma_chan *chan,
dma_cookie_t cookie,
struct dma_tx_state *state)
{
+ struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
- struct sun6i_pchan *pchan = vchan->phy;
+ struct sun6i_pchan *pchan;
struct sun6i_dma_lli *lli;
struct virt_dma_desc *vd;
struct sun6i_desc *txd;
@@ -1026,11 +1034,12 @@ static enum dma_status sun6i_dma_tx_status(struct dma_chan *chan,
unsigned long flags;
size_t bytes = 0;
+ spin_lock_irqsave(&sdev->lock, flags);
+ spin_lock(&vchan->vc.lock);
ret = dma_cookie_status(chan, cookie, state);
if (ret == DMA_COMPLETE || !state)
- return ret;
-
- spin_lock_irqsave(&vchan->vc.lock, flags);
+ goto out;
+ pchan = vchan->phy;
vd = vchan_find_desc(&vchan->vc, cookie);
txd = to_sun6i_desc(&vd->tx);
@@ -1044,9 +1053,10 @@ static enum dma_status sun6i_dma_tx_status(struct dma_chan *chan,
bytes = sun6i_get_chan_size(pchan);
}
- spin_unlock_irqrestore(&vchan->vc.lock, flags);
-
dma_set_residue(state, bytes);
+out:
+ spin_unlock(&vchan->vc.lock);
+ spin_unlock_irqrestore(&sdev->lock, flags);
return ret;
}
@@ -1057,25 +1067,23 @@ static void sun6i_dma_issue_pending(struct dma_chan *chan)
struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
unsigned long flags;
- spin_lock_irqsave(&vchan->vc.lock, flags);
+ spin_lock_irqsave(&sdev->lock, flags);
+ spin_lock(&vchan->vc.lock);
if (vchan_issue_pending(&vchan->vc)) {
- spin_lock(&sdev->lock);
-
if (!vchan->phy && list_empty(&vchan->node)) {
list_add_tail(&vchan->node, &sdev->pending);
tasklet_schedule(&sdev->task);
dev_dbg(dmaengine_chan_dev(chan), "vchan %p: issued\n",
&vchan->vc);
}
-
- spin_unlock(&sdev->lock);
} else {
dev_dbg(dmaengine_chan_dev(chan), "vchan %p: nothing to issue\n",
&vchan->vc);
}
- spin_unlock_irqrestore(&vchan->vc.lock, flags);
+ spin_unlock(&vchan->vc.lock);
+ spin_unlock_irqrestore(&sdev->lock, flags);
}
static void sun6i_dma_free_chan_resources(struct dma_chan *chan)