From 50437bff7f7374f86837986f66e15e73a364f894 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 13 Apr 2012 12:07:23 +0100 Subject: dmaengine: split out virtual channel DMA support from sa11x0 driver Split the virtual slave channel DMA support from the sa11x0 driver so this code can be shared with other slave DMA engine drivers. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/Kconfig | 4 + drivers/dma/Makefile | 1 + drivers/dma/sa11x0-dma.c | 249 +++++++++++++++-------------------------------- drivers/dma/virt-dma.c | 99 +++++++++++++++++++ drivers/dma/virt-dma.h | 138 ++++++++++++++++++++++++++ 5 files changed, 320 insertions(+), 171 deletions(-) create mode 100644 drivers/dma/virt-dma.c create mode 100644 drivers/dma/virt-dma.h (limited to 'drivers/dma') diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index aadeb5be9db..eb2b60e8e1c 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -255,6 +255,7 @@ config DMA_SA11X0 tristate "SA-11x0 DMA support" depends on ARCH_SA1100 select DMA_ENGINE + select DMA_VIRTUAL_CHANNELS help Support the DMA engine found on Intel StrongARM SA-1100 and SA-1110 SoCs. This DMA engine can only be used with on-chip @@ -263,6 +264,9 @@ config DMA_SA11X0 config DMA_ENGINE bool +config DMA_VIRTUAL_CHANNELS + tristate + comment "DMA Clients" depends on DMA_ENGINE diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index 86b795baba9..fc05f7ddac7 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -2,6 +2,7 @@ ccflags-$(CONFIG_DMADEVICES_DEBUG) := -DDEBUG ccflags-$(CONFIG_DMADEVICES_VDEBUG) += -DVERBOSE_DEBUG obj-$(CONFIG_DMA_ENGINE) += dmaengine.o +obj-$(CONFIG_DMA_VIRTUAL_CHANNELS) += virt-dma.o obj-$(CONFIG_NET_DMA) += iovlock.o obj-$(CONFIG_INTEL_MID_DMAC) += intel_mid_dma.o obj-$(CONFIG_DMATEST) += dmatest.o diff --git a/drivers/dma/sa11x0-dma.c b/drivers/dma/sa11x0-dma.c index ec78ccef913..5f1d2e67083 100644 --- a/drivers/dma/sa11x0-dma.c +++ b/drivers/dma/sa11x0-dma.c @@ -21,6 +21,8 @@ #include #include +#include "virt-dma.h" + #define NR_PHY_CHAN 6 #define DMA_ALIGN 3 #define DMA_MAX_SIZE 0x1fff @@ -72,12 +74,11 @@ struct sa11x0_dma_sg { }; struct sa11x0_dma_desc { - struct dma_async_tx_descriptor tx; + struct virt_dma_desc vd; + u32 ddar; size_t size; - /* maybe protected by c->lock */ - struct list_head node; unsigned sglen; struct sa11x0_dma_sg sg[0]; }; @@ -85,15 +86,11 @@ struct sa11x0_dma_desc { struct sa11x0_dma_phy; struct sa11x0_dma_chan { - struct dma_chan chan; - spinlock_t lock; - dma_cookie_t lc; + struct virt_dma_chan vc; - /* protected by c->lock */ + /* protected by c->vc.lock */ struct sa11x0_dma_phy *phy; enum dma_status status; - struct list_head desc_submitted; - struct list_head desc_issued; /* protected by d->lock */ struct list_head node; @@ -109,7 +106,7 @@ struct sa11x0_dma_phy { struct sa11x0_dma_chan *vchan; - /* Protected by c->lock */ + /* Protected by c->vc.lock */ unsigned sg_load; struct sa11x0_dma_desc *txd_load; unsigned sg_done; @@ -127,13 +124,12 @@ struct sa11x0_dma_dev { spinlock_t lock; struct tasklet_struct task; struct list_head chan_pending; - struct list_head desc_complete; struct sa11x0_dma_phy phy[NR_PHY_CHAN]; }; static struct sa11x0_dma_chan *to_sa11x0_dma_chan(struct dma_chan *chan) { - return container_of(chan, struct sa11x0_dma_chan, chan); + return container_of(chan, struct sa11x0_dma_chan, vc.chan); } static struct sa11x0_dma_dev *to_sa11x0_dma(struct dma_device *dmadev) @@ -141,27 +137,26 @@ static struct sa11x0_dma_dev *to_sa11x0_dma(struct dma_device *dmadev) return container_of(dmadev, struct sa11x0_dma_dev, slave); } -static struct sa11x0_dma_desc *to_sa11x0_dma_tx(struct dma_async_tx_descriptor *tx) +static struct sa11x0_dma_desc *sa11x0_dma_next_desc(struct sa11x0_dma_chan *c) { - return container_of(tx, struct sa11x0_dma_desc, tx); + struct virt_dma_desc *vd = vchan_next_desc(&c->vc); + + return vd ? container_of(vd, struct sa11x0_dma_desc, vd) : NULL; } -static struct sa11x0_dma_desc *sa11x0_dma_next_desc(struct sa11x0_dma_chan *c) +static void sa11x0_dma_free_desc(struct virt_dma_desc *vd) { - if (list_empty(&c->desc_issued)) - return NULL; - - return list_first_entry(&c->desc_issued, struct sa11x0_dma_desc, node); + kfree(container_of(vd, struct sa11x0_dma_desc, vd)); } static void sa11x0_dma_start_desc(struct sa11x0_dma_phy *p, struct sa11x0_dma_desc *txd) { - list_del(&txd->node); + list_del(&txd->vd.node); p->txd_load = txd; p->sg_load = 0; dev_vdbg(p->dev->slave.dev, "pchan %u: txd %p[%x]: starting: DDAR:%x\n", - p->num, txd, txd->tx.cookie, txd->ddar); + p->num, &txd->vd, txd->vd.tx.cookie, txd->ddar); } static void noinline sa11x0_dma_start_sg(struct sa11x0_dma_phy *p, @@ -229,21 +224,13 @@ static void noinline sa11x0_dma_complete(struct sa11x0_dma_phy *p, struct sa11x0_dma_desc *txd = p->txd_done; if (++p->sg_done == txd->sglen) { - struct sa11x0_dma_dev *d = p->dev; - - dev_vdbg(d->slave.dev, "pchan %u: txd %p[%x]: completed\n", - p->num, p->txd_done, p->txd_done->tx.cookie); - - c->lc = txd->tx.cookie; - - spin_lock(&d->lock); - list_add_tail(&txd->node, &d->desc_complete); - spin_unlock(&d->lock); + vchan_cookie_complete(&txd->vd); p->sg_done = 0; p->txd_done = p->txd_load; - tasklet_schedule(&d->task); + if (!p->txd_done) + tasklet_schedule(&p->dev->task); } sa11x0_dma_start_sg(p, c); @@ -280,7 +267,7 @@ static irqreturn_t sa11x0_dma_irq(int irq, void *dev_id) if (c) { unsigned long flags; - spin_lock_irqsave(&c->lock, flags); + spin_lock_irqsave(&c->vc.lock, flags); /* * Now that we're holding the lock, check that the vchan * really is associated with this pchan before touching the @@ -294,7 +281,7 @@ static irqreturn_t sa11x0_dma_irq(int irq, void *dev_id) if (dcsr & DCSR_DONEB) sa11x0_dma_complete(p, c); } - spin_unlock_irqrestore(&c->lock, flags); + spin_unlock_irqrestore(&c->vc.lock, flags); } return IRQ_HANDLED; @@ -332,28 +319,15 @@ static void sa11x0_dma_tasklet(unsigned long arg) struct sa11x0_dma_dev *d = (struct sa11x0_dma_dev *)arg; struct sa11x0_dma_phy *p; struct sa11x0_dma_chan *c; - struct sa11x0_dma_desc *txd, *txn; - LIST_HEAD(head); unsigned pch, pch_alloc = 0; dev_dbg(d->slave.dev, "tasklet enter\n"); - /* Get the completed tx descriptors */ - spin_lock_irq(&d->lock); - list_splice_init(&d->desc_complete, &head); - spin_unlock_irq(&d->lock); - - list_for_each_entry(txd, &head, node) { - c = to_sa11x0_dma_chan(txd->tx.chan); - - dev_dbg(d->slave.dev, "vchan %p: txd %p[%x] completed\n", - c, txd, txd->tx.cookie); - - spin_lock_irq(&c->lock); + list_for_each_entry(c, &d->slave.channels, vc.chan.device_node) { + spin_lock_irq(&c->vc.lock); p = c->phy; - if (p) { - if (!p->txd_done) - sa11x0_dma_start_txd(c); + if (p && !p->txd_done) { + sa11x0_dma_start_txd(c); if (!p->txd_done) { /* No current txd associated with this channel */ dev_dbg(d->slave.dev, "pchan %u: free\n", p->num); @@ -363,7 +337,7 @@ static void sa11x0_dma_tasklet(unsigned long arg) p->vchan = NULL; } } - spin_unlock_irq(&c->lock); + spin_unlock_irq(&c->vc.lock); } spin_lock_irq(&d->lock); @@ -380,7 +354,7 @@ static void sa11x0_dma_tasklet(unsigned long arg) /* Mark this channel allocated */ p->vchan = c; - dev_dbg(d->slave.dev, "pchan %u: alloc vchan %p\n", pch, c); + dev_dbg(d->slave.dev, "pchan %u: alloc vchan %p\n", pch, &c->vc); } } spin_unlock_irq(&d->lock); @@ -390,42 +364,18 @@ static void sa11x0_dma_tasklet(unsigned long arg) p = &d->phy[pch]; c = p->vchan; - spin_lock_irq(&c->lock); + spin_lock_irq(&c->vc.lock); c->phy = p; sa11x0_dma_start_txd(c); - spin_unlock_irq(&c->lock); + spin_unlock_irq(&c->vc.lock); } } - /* Now free the completed tx descriptor, and call their callbacks */ - list_for_each_entry_safe(txd, txn, &head, node) { - dma_async_tx_callback callback = txd->tx.callback; - void *callback_param = txd->tx.callback_param; - - dev_dbg(d->slave.dev, "txd %p[%x]: callback and free\n", - txd, txd->tx.cookie); - - kfree(txd); - - if (callback) - callback(callback_param); - } - dev_dbg(d->slave.dev, "tasklet exit\n"); } -static void sa11x0_dma_desc_free(struct sa11x0_dma_dev *d, struct list_head *head) -{ - struct sa11x0_dma_desc *txd, *txn; - - list_for_each_entry_safe(txd, txn, head, node) { - dev_dbg(d->slave.dev, "txd %p: freeing\n", txd); - kfree(txd); - } -} - static int sa11x0_dma_alloc_chan_resources(struct dma_chan *chan) { return 0; @@ -436,18 +386,12 @@ static void sa11x0_dma_free_chan_resources(struct dma_chan *chan) struct sa11x0_dma_chan *c = to_sa11x0_dma_chan(chan); struct sa11x0_dma_dev *d = to_sa11x0_dma(chan->device); unsigned long flags; - LIST_HEAD(head); - spin_lock_irqsave(&c->lock, flags); - spin_lock(&d->lock); + spin_lock_irqsave(&d->lock, flags); list_del_init(&c->node); - spin_unlock(&d->lock); - - list_splice_tail_init(&c->desc_submitted, &head); - list_splice_tail_init(&c->desc_issued, &head); - spin_unlock_irqrestore(&c->lock, flags); + spin_unlock_irqrestore(&d->lock, flags); - sa11x0_dma_desc_free(d, &head); + vchan_free_chan_resources(&c->vc); } static dma_addr_t sa11x0_dma_pos(struct sa11x0_dma_phy *p) @@ -473,21 +417,15 @@ static enum dma_status sa11x0_dma_tx_status(struct dma_chan *chan, struct sa11x0_dma_dev *d = to_sa11x0_dma(chan->device); struct sa11x0_dma_phy *p; struct sa11x0_dma_desc *txd; - dma_cookie_t last_used, last_complete; unsigned long flags; enum dma_status ret; size_t bytes = 0; - last_used = c->chan.cookie; - last_complete = c->lc; - - ret = dma_async_is_complete(cookie, last_complete, last_used); - if (ret == DMA_SUCCESS) { - dma_set_tx_state(state, last_complete, last_used, 0); + ret = dma_cookie_status(&c->vc.chan, cookie, state); + if (ret == DMA_SUCCESS) return ret; - } - spin_lock_irqsave(&c->lock, flags); + spin_lock_irqsave(&c->vc.lock, flags); p = c->phy; ret = c->status; if (p) { @@ -524,12 +462,13 @@ static enum dma_status sa11x0_dma_tx_status(struct dma_chan *chan, if (txd != p->txd_load && p->txd_load) bytes += p->txd_load->size; } - list_for_each_entry(txd, &c->desc_issued, node) { + list_for_each_entry(txd, &c->vc.desc_issued, vd.node) { bytes += txd->size; } - spin_unlock_irqrestore(&c->lock, flags); + spin_unlock_irqrestore(&c->vc.lock, flags); - dma_set_tx_state(state, last_complete, last_used, bytes); + if (state) + state->residue = bytes; dev_vdbg(d->slave.dev, "tx_status: bytes 0x%zx\n", bytes); @@ -547,40 +486,20 @@ static void sa11x0_dma_issue_pending(struct dma_chan *chan) struct sa11x0_dma_dev *d = to_sa11x0_dma(chan->device); unsigned long flags; - spin_lock_irqsave(&c->lock, flags); - list_splice_tail_init(&c->desc_submitted, &c->desc_issued); - if (!list_empty(&c->desc_issued)) { - spin_lock(&d->lock); - if (!c->phy && list_empty(&c->node)) { - list_add_tail(&c->node, &d->chan_pending); - tasklet_schedule(&d->task); - dev_dbg(d->slave.dev, "vchan %p: issued\n", c); + spin_lock_irqsave(&c->vc.lock, flags); + if (vchan_issue_pending(&c->vc)) { + if (!c->phy) { + spin_lock(&d->lock); + if (list_empty(&c->node)) { + list_add_tail(&c->node, &d->chan_pending); + tasklet_schedule(&d->task); + dev_dbg(d->slave.dev, "vchan %p: issued\n", &c->vc); + } + spin_unlock(&d->lock); } - spin_unlock(&d->lock); } else - dev_dbg(d->slave.dev, "vchan %p: nothing to issue\n", c); - spin_unlock_irqrestore(&c->lock, flags); -} - -static dma_cookie_t sa11x0_dma_tx_submit(struct dma_async_tx_descriptor *tx) -{ - struct sa11x0_dma_chan *c = to_sa11x0_dma_chan(tx->chan); - struct sa11x0_dma_desc *txd = to_sa11x0_dma_tx(tx); - unsigned long flags; - - spin_lock_irqsave(&c->lock, flags); - c->chan.cookie += 1; - if (c->chan.cookie < 0) - c->chan.cookie = 1; - txd->tx.cookie = c->chan.cookie; - - list_add_tail(&txd->node, &c->desc_submitted); - spin_unlock_irqrestore(&c->lock, flags); - - dev_dbg(tx->chan->device->dev, "vchan %p: txd %p[%x]: submitted\n", - c, txd, txd->tx.cookie); - - return txd->tx.cookie; + dev_dbg(d->slave.dev, "vchan %p: nothing to issue\n", &c->vc); + spin_unlock_irqrestore(&c->vc.lock, flags); } static struct dma_async_tx_descriptor *sa11x0_dma_prep_slave_sg( @@ -596,7 +515,7 @@ static struct dma_async_tx_descriptor *sa11x0_dma_prep_slave_sg( /* SA11x0 channels can only operate in their native direction */ if (dir != (c->ddar & DDAR_RW ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV)) { dev_err(chan->device->dev, "vchan %p: bad DMA direction: DDAR:%08x dir:%u\n", - c, c->ddar, dir); + &c->vc, c->ddar, dir); return NULL; } @@ -612,14 +531,14 @@ static struct dma_async_tx_descriptor *sa11x0_dma_prep_slave_sg( j += DIV_ROUND_UP(len, DMA_MAX_SIZE & ~DMA_ALIGN) - 1; if (addr & DMA_ALIGN) { dev_dbg(chan->device->dev, "vchan %p: bad buffer alignment: %08x\n", - c, addr); + &c->vc, addr); return NULL; } } txd = kzalloc(sizeof(*txd) + j * sizeof(txd->sg[0]), GFP_ATOMIC); if (!txd) { - dev_dbg(chan->device->dev, "vchan %p: kzalloc failed\n", c); + dev_dbg(chan->device->dev, "vchan %p: kzalloc failed\n", &c->vc); return NULL; } @@ -655,17 +574,14 @@ static struct dma_async_tx_descriptor *sa11x0_dma_prep_slave_sg( } while (len); } - dma_async_tx_descriptor_init(&txd->tx, &c->chan); - txd->tx.flags = flags; - txd->tx.tx_submit = sa11x0_dma_tx_submit; txd->ddar = c->ddar; txd->size = size; txd->sglen = j; dev_dbg(chan->device->dev, "vchan %p: txd %p: size %u nr %u\n", - c, txd, txd->size, txd->sglen); + &c->vc, &txd->vd, txd->size, txd->sglen); - return &txd->tx; + return vchan_tx_prep(&c->vc, &txd->vd, flags); } static int sa11x0_dma_slave_config(struct sa11x0_dma_chan *c, struct dma_slave_config *cfg) @@ -695,8 +611,8 @@ static int sa11x0_dma_slave_config(struct sa11x0_dma_chan *c, struct dma_slave_c if (maxburst == 8) ddar |= DDAR_BS; - dev_dbg(c->chan.device->dev, "vchan %p: dma_slave_config addr %x width %u burst %u\n", - c, addr, width, maxburst); + dev_dbg(c->vc.chan.device->dev, "vchan %p: dma_slave_config addr %x width %u burst %u\n", + &c->vc, addr, width, maxburst); c->ddar = ddar | (addr & 0xf0000000) | (addr & 0x003ffffc) << 6; @@ -718,16 +634,13 @@ static int sa11x0_dma_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, return sa11x0_dma_slave_config(c, (struct dma_slave_config *)arg); case DMA_TERMINATE_ALL: - dev_dbg(d->slave.dev, "vchan %p: terminate all\n", c); + dev_dbg(d->slave.dev, "vchan %p: terminate all\n", &c->vc); /* Clear the tx descriptor lists */ - spin_lock_irqsave(&c->lock, flags); - list_splice_tail_init(&c->desc_submitted, &head); - list_splice_tail_init(&c->desc_issued, &head); + spin_lock_irqsave(&c->vc.lock, flags); + vchan_get_all_descriptors(&c->vc, &head); p = c->phy; if (p) { - struct sa11x0_dma_desc *txd, *txn; - dev_dbg(d->slave.dev, "pchan %u: terminating\n", p->num); /* vchan is assigned to a pchan - stop the channel */ writel(DCSR_RUN | DCSR_IE | @@ -735,17 +648,13 @@ static int sa11x0_dma_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, DCSR_STRTB | DCSR_DONEB, p->base + DMA_DCSR_C); - list_for_each_entry_safe(txd, txn, &d->desc_complete, node) - if (txd->tx.chan == &c->chan) - list_move(&txd->node, &head); - if (p->txd_load) { if (p->txd_load != p->txd_done) - list_add_tail(&p->txd_load->node, &head); + list_add_tail(&p->txd_load->vd.node, &head); p->txd_load = NULL; } if (p->txd_done) { - list_add_tail(&p->txd_done->node, &head); + list_add_tail(&p->txd_done->vd.node, &head); p->txd_done = NULL; } c->phy = NULL; @@ -754,14 +663,14 @@ static int sa11x0_dma_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, spin_unlock(&d->lock); tasklet_schedule(&d->task); } - spin_unlock_irqrestore(&c->lock, flags); - sa11x0_dma_desc_free(d, &head); + spin_unlock_irqrestore(&c->vc.lock, flags); + vchan_dma_desc_free_list(&c->vc, &head); ret = 0; break; case DMA_PAUSE: - dev_dbg(d->slave.dev, "vchan %p: pause\n", c); - spin_lock_irqsave(&c->lock, flags); + dev_dbg(d->slave.dev, "vchan %p: pause\n", &c->vc); + spin_lock_irqsave(&c->vc.lock, flags); if (c->status == DMA_IN_PROGRESS) { c->status = DMA_PAUSED; @@ -774,26 +683,26 @@ static int sa11x0_dma_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, spin_unlock(&d->lock); } } - spin_unlock_irqrestore(&c->lock, flags); + spin_unlock_irqrestore(&c->vc.lock, flags); ret = 0; break; case DMA_RESUME: - dev_dbg(d->slave.dev, "vchan %p: resume\n", c); - spin_lock_irqsave(&c->lock, flags); + dev_dbg(d->slave.dev, "vchan %p: resume\n", &c->vc); + spin_lock_irqsave(&c->vc.lock, flags); if (c->status == DMA_PAUSED) { c->status = DMA_IN_PROGRESS; p = c->phy; if (p) { writel(DCSR_RUN | DCSR_IE, p->base + DMA_DCSR_S); - } else if (!list_empty(&c->desc_issued)) { + } else if (!list_empty(&c->vc.desc_issued)) { spin_lock(&d->lock); list_add_tail(&c->node, &d->chan_pending); spin_unlock(&d->lock); } } - spin_unlock_irqrestore(&c->lock, flags); + spin_unlock_irqrestore(&c->vc.lock, flags); ret = 0; break; @@ -853,15 +762,13 @@ static int __devinit sa11x0_dma_init_dmadev(struct dma_device *dmadev, return -ENOMEM; } - c->chan.device = dmadev; c->status = DMA_IN_PROGRESS; c->ddar = chan_desc[i].ddar; c->name = chan_desc[i].name; - spin_lock_init(&c->lock); - INIT_LIST_HEAD(&c->desc_submitted); - INIT_LIST_HEAD(&c->desc_issued); INIT_LIST_HEAD(&c->node); - list_add_tail(&c->chan.device_node, &dmadev->channels); + + c->vc.desc_free = sa11x0_dma_free_desc; + vchan_init(&c->vc, dmadev); } return dma_async_device_register(dmadev); @@ -890,8 +797,9 @@ static void sa11x0_dma_free_channels(struct dma_device *dmadev) { struct sa11x0_dma_chan *c, *cn; - list_for_each_entry_safe(c, cn, &dmadev->channels, chan.device_node) { - list_del(&c->chan.device_node); + list_for_each_entry_safe(c, cn, &dmadev->channels, vc.chan.device_node) { + list_del(&c->vc.chan.device_node); + tasklet_kill(&c->vc.task); kfree(c); } } @@ -915,7 +823,6 @@ static int __devinit sa11x0_dma_probe(struct platform_device *pdev) spin_lock_init(&d->lock); INIT_LIST_HEAD(&d->chan_pending); - INIT_LIST_HEAD(&d->desc_complete); d->base = ioremap(res->start, resource_size(res)); if (!d->base) { diff --git a/drivers/dma/virt-dma.c b/drivers/dma/virt-dma.c new file mode 100644 index 00000000000..bd85b052b48 --- /dev/null +++ b/drivers/dma/virt-dma.c @@ -0,0 +1,99 @@ +/* + * Virtual DMA channel support for DMAengine + * + * Copyright (C) 2012 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include + +#include "virt-dma.h" + +static struct virt_dma_desc *to_virt_desc(struct dma_async_tx_descriptor *tx) +{ + return container_of(tx, struct virt_dma_desc, tx); +} + +dma_cookie_t vchan_tx_submit(struct dma_async_tx_descriptor *tx) +{ + struct virt_dma_chan *vc = to_virt_chan(tx->chan); + struct virt_dma_desc *vd = to_virt_desc(tx); + unsigned long flags; + dma_cookie_t cookie; + + spin_lock_irqsave(&vc->lock, flags); + cookie = dma_cookie_assign(tx); + + list_add_tail(&vd->node, &vc->desc_submitted); + spin_unlock_irqrestore(&vc->lock, flags); + + dev_dbg(vc->chan.device->dev, "vchan %p: txd %p[%x]: submitted\n", + vc, vd, cookie); + + return cookie; +} +EXPORT_SYMBOL_GPL(vchan_tx_submit); + +/* + * This tasklet handles the completion of a DMA descriptor by + * calling its callback and freeing it. + */ +static void vchan_complete(unsigned long arg) +{ + struct virt_dma_chan *vc = (struct virt_dma_chan *)arg; + LIST_HEAD(head); + + spin_lock_irq(&vc->lock); + list_splice_tail_init(&vc->desc_completed, &head); + spin_unlock_irq(&vc->lock); + + while (!list_empty(&head)) { + struct virt_dma_desc *vd = list_first_entry(&head, + struct virt_dma_desc, node); + dma_async_tx_callback cb = vd->tx.callback; + void *cb_data = vd->tx.callback_param; + + list_del(&vd->node); + + vc->desc_free(vd); + + if (cb) + cb(cb_data); + } +} + +void vchan_dma_desc_free_list(struct virt_dma_chan *vc, struct list_head *head) +{ + while (!list_empty(head)) { + struct virt_dma_desc *vd = list_first_entry(head, + struct virt_dma_desc, node); + list_del(&vd->node); + dev_dbg(vc->chan.device->dev, "txd %p: freeing\n", vd); + vc->desc_free(vd); + } +} +EXPORT_SYMBOL_GPL(vchan_dma_desc_free_list); + +void vchan_init(struct virt_dma_chan *vc, struct dma_device *dmadev) +{ + dma_cookie_init(&vc->chan); + + spin_lock_init(&vc->lock); + INIT_LIST_HEAD(&vc->desc_submitted); + INIT_LIST_HEAD(&vc->desc_issued); + INIT_LIST_HEAD(&vc->desc_completed); + + tasklet_init(&vc->task, vchan_complete, (unsigned long)vc); + + vc->chan.device = dmadev; + list_add_tail(&vc->chan.device_node, &dmadev->channels); +} +EXPORT_SYMBOL_GPL(vchan_init); + +MODULE_AUTHOR("Russell King"); +MODULE_LICENSE("GPL"); diff --git a/drivers/dma/virt-dma.h b/drivers/dma/virt-dma.h new file mode 100644 index 00000000000..825bb962317 --- /dev/null +++ b/drivers/dma/virt-dma.h @@ -0,0 +1,138 @@ +/* + * Virtual DMA channel support for DMAengine + * + * Copyright (C) 2012 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef VIRT_DMA_H +#define VIRT_DMA_H + +#include +#include + +#include "dmaengine.h" + +struct virt_dma_desc { + struct dma_async_tx_descriptor tx; + /* protected by vc.lock */ + struct list_head node; +}; + +struct virt_dma_chan { + struct dma_chan chan; + struct tasklet_struct task; + void (*desc_free)(struct virt_dma_desc *); + + spinlock_t lock; + + /* protected by vc.lock */ + struct list_head desc_submitted; + struct list_head desc_issued; + struct list_head desc_completed; +}; + +static inline struct virt_dma_chan *to_virt_chan(struct dma_chan *chan) +{ + return container_of(chan, struct virt_dma_chan, chan); +} + +void vchan_dma_desc_free_list(struct virt_dma_chan *vc, struct list_head *head); + +void vchan_init(struct virt_dma_chan *vc, struct dma_device *dmadev); + +/** + * vchan_tx_prep - prepare a descriptor + * vc: virtual channel allocating this descriptor + * vd: virtual descriptor to prepare + * tx_flags: flags argument passed in to prepare function + */ +static inline struct dma_async_tx_descriptor *vchan_tx_prep(struct virt_dma_chan *vc, + struct virt_dma_desc *vd, unsigned long tx_flags) +{ + extern dma_cookie_t vchan_tx_submit(struct dma_async_tx_descriptor *); + + dma_async_tx_descriptor_init(&vd->tx, &vc->chan); + vd->tx.flags = tx_flags; + vd->tx.tx_submit = vchan_tx_submit; + + return &vd->tx; +} + +/** + * vchan_issue_pending - move submitted descriptors to issued list + * vc: virtual channel to update + * + * vc.lock must be held by caller + */ +static inline bool vchan_issue_pending(struct virt_dma_chan *vc) +{ + list_splice_tail_init(&vc->desc_submitted, &vc->desc_issued); + return !list_empty(&vc->desc_issued); +} + +/** + * vchan_cookie_complete - report completion of a descriptor + * vd: virtual descriptor to update + * + * vc.lock must be held by caller + */ +static inline void vchan_cookie_complete(struct virt_dma_desc *vd) +{ + struct virt_dma_chan *vc = to_virt_chan(vd->tx.chan); + + dma_cookie_complete(&vd->tx); + dev_vdbg(vc->chan.device->dev, "txd %p[%x]: marked complete\n", + vd, vd->tx.cookie); + list_add_tail(&vd->node, &vc->desc_completed); + + tasklet_schedule(&vc->task); +} + +/** + * vchan_next_desc - peek at the next descriptor to be processed + * vc: virtual channel to obtain descriptor from + * + * vc.lock must be held by caller + */ +static inline struct virt_dma_desc *vchan_next_desc(struct virt_dma_chan *vc) +{ + if (list_empty(&vc->desc_issued)) + return NULL; + + return list_first_entry(&vc->desc_issued, struct virt_dma_desc, node); +} + +/** + * vchan_get_all_descriptors - obtain all submitted and issued descriptors + * vc: virtual channel to get descriptors from + * head: list of descriptors found + * + * vc.lock must be held by caller + * + * Removes all submitted and issued descriptors from internal lists, and + * provides a list of all descriptors found + */ +static inline void vchan_get_all_descriptors(struct virt_dma_chan *vc, + struct list_head *head) +{ + list_splice_tail_init(&vc->desc_submitted, head); + list_splice_tail_init(&vc->desc_issued, head); + list_splice_tail_init(&vc->desc_completed, head); +} + +static inline void vchan_free_chan_resources(struct virt_dma_chan *vc) +{ + unsigned long flags; + LIST_HEAD(head); + + spin_lock_irqsave(&vc->lock, flags); + vchan_get_all_descriptors(vc, &head); + spin_unlock_irqrestore(&vc->lock, flags); + + vchan_dma_desc_free_list(vc, &head); +} + +#endif -- cgit v1.2.3-70-g09d2 From fe045874aaf4480386c65baf1acae82af4c5e21f Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 10 May 2012 23:39:27 +0100 Subject: dmaengine: virt-dma: vchan_find_desc() Add a function to find a descriptor within the depths of the virtualized DMA channel support. Needed for tx_status functionality. Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/virt-dma.c | 13 +++++++++++++ drivers/dma/virt-dma.h | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'drivers/dma') diff --git a/drivers/dma/virt-dma.c b/drivers/dma/virt-dma.c index bd85b052b48..a8054fc40ce 100644 --- a/drivers/dma/virt-dma.c +++ b/drivers/dma/virt-dma.c @@ -39,6 +39,19 @@ dma_cookie_t vchan_tx_submit(struct dma_async_tx_descriptor *tx) } EXPORT_SYMBOL_GPL(vchan_tx_submit); +struct virt_dma_desc *vchan_find_desc(struct virt_dma_chan *vc, + dma_cookie_t cookie) +{ + struct virt_dma_desc *vd; + + list_for_each_entry(vd, &vc->desc_issued, node) + if (vd->tx.cookie == cookie) + return vd; + + return NULL; +} +EXPORT_SYMBOL_GPL(vchan_find_desc); + /* * This tasklet handles the completion of a DMA descriptor by * calling its callback and freeing it. diff --git a/drivers/dma/virt-dma.h b/drivers/dma/virt-dma.h index 825bb962317..44ec57e7e41 100644 --- a/drivers/dma/virt-dma.h +++ b/drivers/dma/virt-dma.h @@ -40,8 +40,8 @@ static inline struct virt_dma_chan *to_virt_chan(struct dma_chan *chan) } void vchan_dma_desc_free_list(struct virt_dma_chan *vc, struct list_head *head); - void vchan_init(struct virt_dma_chan *vc, struct dma_device *dmadev); +struct virt_dma_desc *vchan_find_desc(struct virt_dma_chan *, dma_cookie_t); /** * vchan_tx_prep - prepare a descriptor -- cgit v1.2.3-70-g09d2 From 571fa74034701391b1be2ad193f684acfdeb75d1 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 14 May 2012 15:17:20 +0100 Subject: dmaengine: virt-dma: add support for cyclic DMA periodic callbacks Add support for cyclic DMA's periodic callbacks. Drivers are expected to call vchan_cyclic_callback() when a period has completed, which will schedule the tasklet to make the callback into the driver. As callbacks are made from tasklet context, it is important to realise that we don't guarantee a callback for each completed period, but for N completed periods where N may be greater than one. Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/virt-dma.c | 19 +++++++++++++++---- drivers/dma/virt-dma.h | 14 ++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/virt-dma.c b/drivers/dma/virt-dma.c index a8054fc40ce..6f80432a3f0 100644 --- a/drivers/dma/virt-dma.c +++ b/drivers/dma/virt-dma.c @@ -59,17 +59,28 @@ EXPORT_SYMBOL_GPL(vchan_find_desc); static void vchan_complete(unsigned long arg) { struct virt_dma_chan *vc = (struct virt_dma_chan *)arg; + struct virt_dma_desc *vd; + dma_async_tx_callback cb = NULL; + void *cb_data = NULL; LIST_HEAD(head); spin_lock_irq(&vc->lock); list_splice_tail_init(&vc->desc_completed, &head); + vd = vc->cyclic; + if (vd) { + vc->cyclic = NULL; + cb = vd->tx.callback; + cb_data = vd->tx.callback_param; + } spin_unlock_irq(&vc->lock); + if (cb) + cb(cb_data); + while (!list_empty(&head)) { - struct virt_dma_desc *vd = list_first_entry(&head, - struct virt_dma_desc, node); - dma_async_tx_callback cb = vd->tx.callback; - void *cb_data = vd->tx.callback_param; + vd = list_first_entry(&head, struct virt_dma_desc, node); + cb = vd->tx.callback; + cb_data = vd->tx.callback_param; list_del(&vd->node); diff --git a/drivers/dma/virt-dma.h b/drivers/dma/virt-dma.h index 44ec57e7e41..85c19d63f9f 100644 --- a/drivers/dma/virt-dma.h +++ b/drivers/dma/virt-dma.h @@ -32,6 +32,8 @@ struct virt_dma_chan { struct list_head desc_submitted; struct list_head desc_issued; struct list_head desc_completed; + + struct virt_dma_desc *cyclic; }; static inline struct virt_dma_chan *to_virt_chan(struct dma_chan *chan) @@ -91,6 +93,18 @@ static inline void vchan_cookie_complete(struct virt_dma_desc *vd) tasklet_schedule(&vc->task); } +/** + * vchan_cyclic_callback - report the completion of a period + * vd: virtual descriptor + */ +static inline void vchan_cyclic_callback(struct virt_dma_desc *vd) +{ + struct virt_dma_chan *vc = to_virt_chan(vd->tx.chan); + + vc->cyclic = vd; + tasklet_schedule(&vc->task); +} + /** * vchan_next_desc - peek at the next descriptor to be processed * vc: virtual channel to obtain descriptor from -- cgit v1.2.3-70-g09d2 From b843441a4c13281823cbd06128b0265fc80f985c Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 17 May 2012 18:23:38 +0100 Subject: dmaengine: PL08x: remove runtime PM support The runtime PM support conflicts with the generic AMBA bus PM, and also causes a potential deadlock with the PL011 driver as it results in interrupts being enabled beneath a spinlock. I don't presently see any solution to this other than by removing the runtime PM support entirely from the DMA engine driver. Alternative suggestions welcome. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index 49ecbbb8932..5586d9ac4e7 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -404,7 +404,6 @@ pl08x_get_phy_channel(struct pl08x_driver_data *pl08x, return NULL; } - pm_runtime_get_sync(&pl08x->adev->dev); return ch; } @@ -418,8 +417,6 @@ static inline void pl08x_put_phy_channel(struct pl08x_driver_data *pl08x, /* Stop the channel and clear its interrupts */ pl08x_terminate_phy_chan(pl08x, ch); - pm_runtime_put(&pl08x->adev->dev); - /* Mark it as free */ ch->serving = NULL; spin_unlock_irqrestore(&ch->lock, flags); @@ -1851,9 +1848,6 @@ static int pl08x_probe(struct amba_device *adev, const struct amba_id *id) goto out_no_pl08x; } - pm_runtime_set_active(&adev->dev); - pm_runtime_enable(&adev->dev); - /* Initialize memcpy engine */ dma_cap_set(DMA_MEMCPY, pl08x->memcpy.cap_mask); pl08x->memcpy.dev = &adev->dev; @@ -2007,7 +2001,6 @@ static int pl08x_probe(struct amba_device *adev, const struct amba_id *id) amba_part(adev), amba_rev(adev), (unsigned long long)adev->res.start, adev->irq[0]); - pm_runtime_put(&adev->dev); return 0; out_no_slave_reg: @@ -2026,9 +2019,6 @@ out_no_ioremap: dma_pool_destroy(pl08x->pool); out_no_lli_pool: out_no_platdata: - pm_runtime_put(&adev->dev); - pm_runtime_disable(&adev->dev); - kfree(pl08x); out_no_pl08x: amba_release_regions(adev); -- cgit v1.2.3-70-g09d2 From 92d2fd619d143b9559c1d6bca10162da4c1c2d34 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 25 May 2012 14:37:56 +0100 Subject: dmaengine: PL08x: fix missed dma_transfer_direction fixup db8196df4 (dmaengine: move drivers to dma_transfer_direction) missed fixing up the "DMA_NONE" case. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index 5586d9ac4e7..f3ab004509b 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -1287,7 +1287,7 @@ static struct dma_async_tx_descriptor *pl08x_prep_dma_memcpy( } list_add_tail(&dsg->node, &txd->dsg_list); - txd->direction = DMA_NONE; + txd->direction = DMA_MEM_TO_MEM; dsg->src_addr = src; dsg->dst_addr = dest; dsg->len = len; -- cgit v1.2.3-70-g09d2 From c15a6ef6ea3ff06df2d532d314f85da48cb067e8 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sat, 26 May 2012 17:05:40 +0100 Subject: dmaengine: PL08x: remove redundant spinlock The pl08x_driver_data spinlock is only ever initialized. Nothing else uses it. Let's get rid of it. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index f3ab004509b..9c5bae6f85b 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -146,7 +146,6 @@ struct pl08x_driver_data { int pool_ctr; u8 lli_buses; u8 mem_buses; - spinlock_t lock; }; /* @@ -1897,8 +1896,6 @@ static int pl08x_probe(struct amba_device *adev, const struct amba_id *id) goto out_no_lli_pool; } - spin_lock_init(&pl08x->lock); - pl08x->base = ioremap(adev->res.start, resource_size(&adev->res)); if (!pl08x->base) { ret = -ENOMEM; -- cgit v1.2.3-70-g09d2 From 48afb3112e6373a292e54d675e986a5da14c0516 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sat, 26 May 2012 16:58:15 +0100 Subject: dmaengine: PL08x: remove circular_buffer boolean from channel data Circular buffers are not handled in this way; we have a separate API call now to setup circular buffers. So lets not mislead people with this bool. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 7 ------- include/linux/amba/pl08x.h | 4 ---- 2 files changed, 11 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index 9c5bae6f85b..5821125f045 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -1701,13 +1701,6 @@ static int pl08x_dma_init_virtual_channels(struct pl08x_driver_data *pl08x, return -ENOMEM; } } - if (chan->cd->circular_buffer) { - dev_err(&pl08x->adev->dev, - "channel %s: circular buffers not supported\n", - chan->name); - kfree(chan); - continue; - } dev_dbg(&pl08x->adev->dev, "initialize virtual channel \"%s\"\n", chan->name); diff --git a/include/linux/amba/pl08x.h b/include/linux/amba/pl08x.h index 02549017212..0f5b34d668b 100644 --- a/include/linux/amba/pl08x.h +++ b/include/linux/amba/pl08x.h @@ -51,9 +51,6 @@ enum { * can be the address of a FIFO register for burst requests for example. * This can be left undefined if the PrimeCell API is used for configuring * this. - * @circular_buffer: whether the buffer passed in is circular and - * shall simply be looped round round (like a record baby round - * round round round) * @single: the device connected to this channel will request single DMA * transfers, not bursts. (Bursts are default.) * @periph_buses: the device connected to this channel is accessible via @@ -66,7 +63,6 @@ struct pl08x_channel_data { u32 muxval; u32 cctl; dma_addr_t addr; - bool circular_buffer; bool single; u8 periph_buses; }; -- cgit v1.2.3-70-g09d2 From aeea1808fe752e917b966961bde3e9603f206dec Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 17 May 2012 15:01:28 +0100 Subject: dmaengine: PL08x: clean up get_signal/put_signal Try to avoid dereferencing the DMA engine's channel struct in these platform helpers; instead, pass a pointer to the channel data into get_signal(), and the returned signal number to put_signal(). Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 4 ++-- include/linux/amba/pl08x.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index 5821125f045..cc08c8c01a9 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -874,7 +874,7 @@ static int prep_phy_channel(struct pl08x_dma_chan *plchan, * Can the platform allow us to use this channel? */ if (plchan->slave && pl08x->pd->get_signal) { - ret = pl08x->pd->get_signal(plchan); + ret = pl08x->pd->get_signal(plchan->cd); if (ret < 0) { dev_dbg(&pl08x->adev->dev, "unable to use physical channel %d for transfer on %s due to platform restrictions\n", @@ -909,7 +909,7 @@ static void release_phy_channel(struct pl08x_dma_chan *plchan) struct pl08x_driver_data *pl08x = plchan->host; if ((plchan->phychan->signal >= 0) && pl08x->pd->put_signal) { - pl08x->pd->put_signal(plchan); + pl08x->pd->put_signal(plchan->cd, plchan->phychan->signal); plchan->phychan->signal = -1; } pl08x_put_phy_channel(pl08x, plchan->phychan); diff --git a/include/linux/amba/pl08x.h b/include/linux/amba/pl08x.h index 0f5b34d668b..88765a62c8f 100644 --- a/include/linux/amba/pl08x.h +++ b/include/linux/amba/pl08x.h @@ -225,8 +225,8 @@ struct pl08x_platform_data { const struct pl08x_channel_data *slave_channels; unsigned int num_slave_channels; struct pl08x_channel_data memcpy_channel; - int (*get_signal)(struct pl08x_dma_chan *); - void (*put_signal)(struct pl08x_dma_chan *); + int (*get_signal)(const struct pl08x_channel_data *); + void (*put_signal)(const struct pl08x_channel_data *, int); u8 lli_buses; u8 mem_buses; }; -- cgit v1.2.3-70-g09d2 From b23f204c8dbbed8e501442c47d7639aac21a3d84 Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 16 May 2012 10:48:44 +0100 Subject: dmaengine: PL08x: move private data structures into amba-pl08x.c Move the driver private data structures into the driver itself, rather than having them exposed to everyone in a header file. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 136 +++++++++++++++++++++++++++++++++++++++++++ include/linux/amba/pl08x.h | 141 +-------------------------------------------- 2 files changed, 138 insertions(+), 139 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index cc08c8c01a9..94949909e4e 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -90,6 +90,7 @@ #define DRIVER_NAME "pl08xdmac" static struct amba_driver pl08x_amba_driver; +struct pl08x_driver_data; /** * struct vendor_data - vendor-specific config parameters for PL08x derivatives @@ -118,6 +119,141 @@ struct pl08x_lli { u32 cctl; }; +/** + * struct pl08x_bus_data - information of source or destination + * busses for a transfer + * @addr: current address + * @maxwidth: the maximum width of a transfer on this bus + * @buswidth: the width of this bus in bytes: 1, 2 or 4 + */ +struct pl08x_bus_data { + dma_addr_t addr; + u8 maxwidth; + u8 buswidth; +}; + +/** + * struct pl08x_phy_chan - holder for the physical channels + * @id: physical index to this channel + * @lock: a lock to use when altering an instance of this struct + * @signal: the physical signal (aka channel) serving this physical channel + * right now + * @serving: the virtual channel currently being served by this physical + * channel + */ +struct pl08x_phy_chan { + unsigned int id; + void __iomem *base; + spinlock_t lock; + int signal; + struct pl08x_dma_chan *serving; +}; + +/** + * struct pl08x_sg - structure containing data per sg + * @src_addr: src address of sg + * @dst_addr: dst address of sg + * @len: transfer len in bytes + * @node: node for txd's dsg_list + */ +struct pl08x_sg { + dma_addr_t src_addr; + dma_addr_t dst_addr; + size_t len; + struct list_head node; +}; + +/** + * struct pl08x_txd - wrapper for struct dma_async_tx_descriptor + * @tx: async tx descriptor + * @node: node for txd list for channels + * @dsg_list: list of children sg's + * @direction: direction of transfer + * @llis_bus: DMA memory address (physical) start for the LLIs + * @llis_va: virtual memory address start for the LLIs + * @cctl: control reg values for current txd + * @ccfg: config reg values for current txd + */ +struct pl08x_txd { + struct dma_async_tx_descriptor tx; + struct list_head node; + struct list_head dsg_list; + enum dma_transfer_direction direction; + dma_addr_t llis_bus; + struct pl08x_lli *llis_va; + /* Default cctl value for LLIs */ + u32 cctl; + /* + * Settings to be put into the physical channel when we + * trigger this txd. Other registers are in llis_va[0]. + */ + u32 ccfg; +}; + +/** + * struct pl08x_dma_chan_state - holds the PL08x specific virtual channel + * states + * @PL08X_CHAN_IDLE: the channel is idle + * @PL08X_CHAN_RUNNING: the channel has allocated a physical transport + * channel and is running a transfer on it + * @PL08X_CHAN_PAUSED: the channel has allocated a physical transport + * channel, but the transfer is currently paused + * @PL08X_CHAN_WAITING: the channel is waiting for a physical transport + * channel to become available (only pertains to memcpy channels) + */ +enum pl08x_dma_chan_state { + PL08X_CHAN_IDLE, + PL08X_CHAN_RUNNING, + PL08X_CHAN_PAUSED, + PL08X_CHAN_WAITING, +}; + +/** + * struct pl08x_dma_chan - this structure wraps a DMA ENGINE channel + * @chan: wrappped abstract channel + * @phychan: the physical channel utilized by this channel, if there is one + * @phychan_hold: if non-zero, hold on to the physical channel even if we + * have no pending entries + * @tasklet: tasklet scheduled by the IRQ to handle actual work etc + * @name: name of channel + * @cd: channel platform data + * @runtime_addr: address for RX/TX according to the runtime config + * @runtime_direction: current direction of this channel according to + * runtime config + * @pend_list: queued transactions pending on this channel + * @at: active transaction on this channel + * @lock: a lock for this channel data + * @host: a pointer to the host (internal use) + * @state: whether the channel is idle, paused, running etc + * @slave: whether this channel is a device (slave) or for memcpy + * @device_fc: Flow Controller Settings for ccfg register. Only valid for slave + * channels. Fill with 'true' if peripheral should be flow controller. Direction + * will be selected at Runtime. + * @waiting: a TX descriptor on this channel which is waiting for a physical + * channel to become available + */ +struct pl08x_dma_chan { + struct dma_chan chan; + struct pl08x_phy_chan *phychan; + int phychan_hold; + struct tasklet_struct tasklet; + char *name; + const struct pl08x_channel_data *cd; + dma_addr_t src_addr; + dma_addr_t dst_addr; + u32 src_cctl; + u32 dst_cctl; + enum dma_transfer_direction runtime_direction; + struct list_head pend_list; + struct pl08x_txd *at; + spinlock_t lock; + struct pl08x_driver_data *host; + enum pl08x_dma_chan_state state; + bool slave; + bool device_fc; + struct pl08x_txd *waiting; +}; + /** * struct pl08x_driver_data - the local state holder for the PL08x * @slave: slave engine for this instance diff --git a/include/linux/amba/pl08x.h b/include/linux/amba/pl08x.h index 88765a62c8f..48d02bf66ec 100644 --- a/include/linux/amba/pl08x.h +++ b/include/linux/amba/pl08x.h @@ -21,8 +21,9 @@ #include #include -struct pl08x_lli; struct pl08x_driver_data; +struct pl08x_phy_chan; +struct pl08x_txd; /* Bitmasks for selecting AHB ports for DMA transfers */ enum { @@ -67,144 +68,6 @@ struct pl08x_channel_data { u8 periph_buses; }; -/** - * Struct pl08x_bus_data - information of source or destination - * busses for a transfer - * @addr: current address - * @maxwidth: the maximum width of a transfer on this bus - * @buswidth: the width of this bus in bytes: 1, 2 or 4 - */ -struct pl08x_bus_data { - dma_addr_t addr; - u8 maxwidth; - u8 buswidth; -}; - -/** - * struct pl08x_phy_chan - holder for the physical channels - * @id: physical index to this channel - * @lock: a lock to use when altering an instance of this struct - * @signal: the physical signal (aka channel) serving this physical channel - * right now - * @serving: the virtual channel currently being served by this physical - * channel - * @locked: channel unavailable for the system, e.g. dedicated to secure - * world - */ -struct pl08x_phy_chan { - unsigned int id; - void __iomem *base; - spinlock_t lock; - int signal; - struct pl08x_dma_chan *serving; - bool locked; -}; - -/** - * struct pl08x_sg - structure containing data per sg - * @src_addr: src address of sg - * @dst_addr: dst address of sg - * @len: transfer len in bytes - * @node: node for txd's dsg_list - */ -struct pl08x_sg { - dma_addr_t src_addr; - dma_addr_t dst_addr; - size_t len; - struct list_head node; -}; - -/** - * struct pl08x_txd - wrapper for struct dma_async_tx_descriptor - * @tx: async tx descriptor - * @node: node for txd list for channels - * @dsg_list: list of children sg's - * @direction: direction of transfer - * @llis_bus: DMA memory address (physical) start for the LLIs - * @llis_va: virtual memory address start for the LLIs - * @cctl: control reg values for current txd - * @ccfg: config reg values for current txd - */ -struct pl08x_txd { - struct dma_async_tx_descriptor tx; - struct list_head node; - struct list_head dsg_list; - enum dma_transfer_direction direction; - dma_addr_t llis_bus; - struct pl08x_lli *llis_va; - /* Default cctl value for LLIs */ - u32 cctl; - /* - * Settings to be put into the physical channel when we - * trigger this txd. Other registers are in llis_va[0]. - */ - u32 ccfg; -}; - -/** - * struct pl08x_dma_chan_state - holds the PL08x specific virtual channel - * states - * @PL08X_CHAN_IDLE: the channel is idle - * @PL08X_CHAN_RUNNING: the channel has allocated a physical transport - * channel and is running a transfer on it - * @PL08X_CHAN_PAUSED: the channel has allocated a physical transport - * channel, but the transfer is currently paused - * @PL08X_CHAN_WAITING: the channel is waiting for a physical transport - * channel to become available (only pertains to memcpy channels) - */ -enum pl08x_dma_chan_state { - PL08X_CHAN_IDLE, - PL08X_CHAN_RUNNING, - PL08X_CHAN_PAUSED, - PL08X_CHAN_WAITING, -}; - -/** - * struct pl08x_dma_chan - this structure wraps a DMA ENGINE channel - * @chan: wrappped abstract channel - * @phychan: the physical channel utilized by this channel, if there is one - * @phychan_hold: if non-zero, hold on to the physical channel even if we - * have no pending entries - * @tasklet: tasklet scheduled by the IRQ to handle actual work etc - * @name: name of channel - * @cd: channel platform data - * @runtime_addr: address for RX/TX according to the runtime config - * @runtime_direction: current direction of this channel according to - * runtime config - * @pend_list: queued transactions pending on this channel - * @at: active transaction on this channel - * @lock: a lock for this channel data - * @host: a pointer to the host (internal use) - * @state: whether the channel is idle, paused, running etc - * @slave: whether this channel is a device (slave) or for memcpy - * @device_fc: Flow Controller Settings for ccfg register. Only valid for slave - * channels. Fill with 'true' if peripheral should be flow controller. Direction - * will be selected at Runtime. - * @waiting: a TX descriptor on this channel which is waiting for a physical - * channel to become available - */ -struct pl08x_dma_chan { - struct dma_chan chan; - struct pl08x_phy_chan *phychan; - int phychan_hold; - struct tasklet_struct tasklet; - char *name; - const struct pl08x_channel_data *cd; - dma_addr_t src_addr; - dma_addr_t dst_addr; - u32 src_cctl; - u32 dst_cctl; - enum dma_transfer_direction runtime_direction; - struct list_head pend_list; - struct pl08x_txd *at; - spinlock_t lock; - struct pl08x_driver_data *host; - enum pl08x_dma_chan_state state; - bool slave; - bool device_fc; - struct pl08x_txd *waiting; -}; - /** * struct pl08x_platform_data - the platform configuration for the PL08x * PrimeCells. -- cgit v1.2.3-70-g09d2 From 550ec36f507177470a394c4dfffcaf986ca25818 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 28 May 2012 10:18:55 +0100 Subject: dmaengine: PL08x: constify channel names and bus_id strings Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 2 +- include/linux/amba/pl08x.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index 94949909e4e..775efef0349 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -237,7 +237,7 @@ struct pl08x_dma_chan { struct pl08x_phy_chan *phychan; int phychan_hold; struct tasklet_struct tasklet; - char *name; + const char *name; const struct pl08x_channel_data *cd; dma_addr_t src_addr; dma_addr_t dst_addr; diff --git a/include/linux/amba/pl08x.h b/include/linux/amba/pl08x.h index 48d02bf66ec..158ce2634b0 100644 --- a/include/linux/amba/pl08x.h +++ b/include/linux/amba/pl08x.h @@ -58,7 +58,7 @@ enum { * these buses (use PL08X_AHB1 | PL08X_AHB2). */ struct pl08x_channel_data { - char *bus_id; + const char *bus_id; int min_signal; int max_signal; u32 muxval; -- cgit v1.2.3-70-g09d2 From ed91c13d29d1542fddbb053015d62781faca93e1 Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 16 May 2012 11:02:40 +0100 Subject: dmaengine: PL08x: get src/dst addr direct from dma_slave_config struct Add a dma_slave_config struct to struct pl08x_dma_chan, and move the src_addr/dst_addr arguments into this struct. This is a step away from using the dma_slave_config's direction member. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index 775efef0349..31447dbe23a 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -239,8 +239,7 @@ struct pl08x_dma_chan { struct tasklet_struct tasklet; const char *name; const struct pl08x_channel_data *cd; - dma_addr_t src_addr; - dma_addr_t dst_addr; + struct dma_slave_config cfg; u32 src_cctl; u32 dst_cctl; enum dma_transfer_direction runtime_direction; @@ -1245,6 +1244,8 @@ static int dma_set_runtime_config(struct dma_chan *chan, return -EINVAL; } + plchan->cfg = *config; + cctl |= width << PL080_CONTROL_SWIDTH_SHIFT; cctl |= width << PL080_CONTROL_DWIDTH_SHIFT; @@ -1263,12 +1264,10 @@ static int dma_set_runtime_config(struct dma_chan *chan, plchan->device_fc = config->device_fc; if (plchan->runtime_direction == DMA_DEV_TO_MEM) { - plchan->src_addr = config->src_addr; plchan->src_cctl = pl08x_cctl(cctl) | PL080_CONTROL_DST_INCR | pl08x_select_bus(plchan->cd->periph_buses, pl08x->mem_buses); } else { - plchan->dst_addr = config->dst_addr; plchan->dst_cctl = pl08x_cctl(cctl) | PL080_CONTROL_SRC_INCR | pl08x_select_bus(pl08x->mem_buses, plchan->cd->periph_buses); @@ -1482,10 +1481,10 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg( if (direction == DMA_MEM_TO_DEV) { txd->cctl = plchan->dst_cctl; - slave_addr = plchan->dst_addr; + slave_addr = plchan->cfg.dst_addr; } else if (direction == DMA_DEV_TO_MEM) { txd->cctl = plchan->src_cctl; - slave_addr = plchan->src_addr; + slave_addr = plchan->cfg.src_addr; } else { pl08x_free_txd(pl08x, txd); dev_err(&pl08x->adev->dev, @@ -1790,8 +1789,8 @@ static void pl08x_dma_slave_init(struct pl08x_dma_chan *chan) chan->slave = true; chan->name = chan->cd->bus_id; - chan->src_addr = chan->cd->addr; - chan->dst_addr = chan->cd->addr; + chan->cfg.src_addr = chan->cd->addr; + chan->cfg.dst_addr = chan->cd->addr; chan->src_cctl = cctl | PL080_CONTROL_DST_INCR | pl08x_select_bus(chan->cd->periph_buses, chan->host->mem_buses); chan->dst_cctl = cctl | PL080_CONTROL_SRC_INCR | -- cgit v1.2.3-70-g09d2 From 95442b223405dae313e3e273765cc1c905d67717 Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 16 May 2012 11:05:09 +0100 Subject: dmaengine: PL08x: get rid of device_fc in struct pl08x_dma_chan As we now store the dma_slave_config in pl08x_dma_chan, we don't need to store this separately. Use the one in dma_slave_config directly. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index 31447dbe23a..7eb0e8ef6a5 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -226,9 +226,6 @@ enum pl08x_dma_chan_state { * @host: a pointer to the host (internal use) * @state: whether the channel is idle, paused, running etc * @slave: whether this channel is a device (slave) or for memcpy - * @device_fc: Flow Controller Settings for ccfg register. Only valid for slave - * channels. Fill with 'true' if peripheral should be flow controller. Direction - * will be selected at Runtime. * @waiting: a TX descriptor on this channel which is waiting for a physical * channel to become available */ @@ -249,7 +246,6 @@ struct pl08x_dma_chan { struct pl08x_driver_data *host; enum pl08x_dma_chan_state state; bool slave; - bool device_fc; struct pl08x_txd *waiting; }; @@ -1261,8 +1257,6 @@ static int dma_set_runtime_config(struct dma_chan *chan, cctl |= burst << PL080_CONTROL_SB_SIZE_SHIFT; cctl |= burst << PL080_CONTROL_DB_SIZE_SHIFT; - plchan->device_fc = config->device_fc; - if (plchan->runtime_direction == DMA_DEV_TO_MEM) { plchan->src_cctl = pl08x_cctl(cctl) | PL080_CONTROL_DST_INCR | pl08x_select_bus(plchan->cd->periph_buses, @@ -1492,7 +1486,7 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg( return NULL; } - if (plchan->device_fc) + if (plchan->cfg.device_fc) tmp = (direction == DMA_MEM_TO_DEV) ? PL080_FLOW_MEM2PER_PER : PL080_FLOW_PER2MEM_PER; else -- cgit v1.2.3-70-g09d2 From 409ec8db46dc60e5da7169b6bac6b294513d386a Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 16 May 2012 11:08:43 +0100 Subject: dmaengine: PL08x: move the bus and increment selection to dma prepare function Move the bus and transfer increment selection to the DMA prepare function rather than the slave configuration function. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index 7eb0e8ef6a5..bd51a44746b 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -1258,13 +1258,9 @@ static int dma_set_runtime_config(struct dma_chan *chan, cctl |= burst << PL080_CONTROL_DB_SIZE_SHIFT; if (plchan->runtime_direction == DMA_DEV_TO_MEM) { - plchan->src_cctl = pl08x_cctl(cctl) | PL080_CONTROL_DST_INCR | - pl08x_select_bus(plchan->cd->periph_buses, - pl08x->mem_buses); + plchan->src_cctl = pl08x_cctl(cctl); } else { - plchan->dst_cctl = pl08x_cctl(cctl) | PL080_CONTROL_SRC_INCR | - pl08x_select_bus(pl08x->mem_buses, - plchan->cd->periph_buses); + plchan->dst_cctl = pl08x_cctl(cctl); } dev_dbg(&pl08x->adev->dev, @@ -1451,6 +1447,8 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg( struct scatterlist *sg; dma_addr_t slave_addr; int ret, tmp; + u8 src_buses, dst_buses; + u32 cctl; dev_dbg(&pl08x->adev->dev, "%s prepare transaction of %d bytes from %s\n", __func__, sg_dma_len(sgl), plchan->name); @@ -1474,11 +1472,15 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg( txd->direction = direction; if (direction == DMA_MEM_TO_DEV) { - txd->cctl = plchan->dst_cctl; + cctl = plchan->dst_cctl | PL080_CONTROL_SRC_INCR; slave_addr = plchan->cfg.dst_addr; + src_buses = pl08x->mem_buses; + dst_buses = plchan->cd->periph_buses; } else if (direction == DMA_DEV_TO_MEM) { - txd->cctl = plchan->src_cctl; + cctl = plchan->src_cctl | PL080_CONTROL_DST_INCR; slave_addr = plchan->cfg.src_addr; + src_buses = plchan->cd->periph_buses; + dst_buses = pl08x->mem_buses; } else { pl08x_free_txd(pl08x, txd); dev_err(&pl08x->adev->dev, @@ -1486,6 +1488,8 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg( return NULL; } + txd->cctl = cctl | pl08x_select_bus(src_buses, dst_buses); + if (plchan->cfg.device_fc) tmp = (direction == DMA_MEM_TO_DEV) ? PL080_FLOW_MEM2PER_PER : PL080_FLOW_PER2MEM_PER; @@ -1785,10 +1789,8 @@ static void pl08x_dma_slave_init(struct pl08x_dma_chan *chan) chan->name = chan->cd->bus_id; chan->cfg.src_addr = chan->cd->addr; chan->cfg.dst_addr = chan->cd->addr; - chan->src_cctl = cctl | PL080_CONTROL_DST_INCR | - pl08x_select_bus(chan->cd->periph_buses, chan->host->mem_buses); - chan->dst_cctl = cctl | PL080_CONTROL_SRC_INCR | - pl08x_select_bus(chan->host->mem_buses, chan->cd->periph_buses); + chan->src_cctl = cctl; + chan->dst_cctl = cctl; } /* -- cgit v1.2.3-70-g09d2 From 9862ba17b7b6866cbee90547ff1f58d055c13f97 Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 16 May 2012 11:16:03 +0100 Subject: dmaengine: PL08x: extract function to to generate cctl values Extract the functionality from dma_slave_config to generate the cctl values for a given bus width and burst size. This allows us to use this elsewhere in the driver, namely the prepare functions. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 53 +++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 21 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index bd51a44746b..fde801f787c 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -1207,14 +1207,40 @@ static u32 pl08x_burst(u32 maxburst) return burst_sizes[i].reg; } +static u32 pl08x_get_cctl(struct pl08x_dma_chan *plchan, + enum dma_slave_buswidth addr_width, u32 maxburst) +{ + u32 width, burst, cctl = 0; + + width = pl08x_width(addr_width); + if (width == ~0) + return ~0; + + cctl |= width << PL080_CONTROL_SWIDTH_SHIFT; + cctl |= width << PL080_CONTROL_DWIDTH_SHIFT; + + /* + * If this channel will only request single transfers, set this + * down to ONE element. Also select one element if no maxburst + * is specified. + */ + if (plchan->cd->single) + maxburst = 1; + + burst = pl08x_burst(maxburst); + cctl |= burst << PL080_CONTROL_SB_SIZE_SHIFT; + cctl |= burst << PL080_CONTROL_DB_SIZE_SHIFT; + + return pl08x_cctl(cctl); +} + static int dma_set_runtime_config(struct dma_chan *chan, struct dma_slave_config *config) { struct pl08x_dma_chan *plchan = to_pl08x_chan(chan); struct pl08x_driver_data *pl08x = plchan->host; enum dma_slave_buswidth addr_width; - u32 width, burst, maxburst; - u32 cctl = 0; + u32 maxburst, cctl = 0; if (!plchan->slave) return -EINVAL; @@ -1233,8 +1259,8 @@ static int dma_set_runtime_config(struct dma_chan *chan, return -EINVAL; } - width = pl08x_width(addr_width); - if (width == ~0) { + cctl = pl08x_get_cctl(plchan, addr_width, maxburst); + if (cctl == ~0) { dev_err(&pl08x->adev->dev, "bad runtime_config: alien address width\n"); return -EINVAL; @@ -1242,25 +1268,10 @@ static int dma_set_runtime_config(struct dma_chan *chan, plchan->cfg = *config; - cctl |= width << PL080_CONTROL_SWIDTH_SHIFT; - cctl |= width << PL080_CONTROL_DWIDTH_SHIFT; - - /* - * If this channel will only request single transfers, set this - * down to ONE element. Also select one element if no maxburst - * is specified. - */ - if (plchan->cd->single) - maxburst = 1; - - burst = pl08x_burst(maxburst); - cctl |= burst << PL080_CONTROL_SB_SIZE_SHIFT; - cctl |= burst << PL080_CONTROL_DB_SIZE_SHIFT; - if (plchan->runtime_direction == DMA_DEV_TO_MEM) { - plchan->src_cctl = pl08x_cctl(cctl); + plchan->src_cctl = cctl; } else { - plchan->dst_cctl = pl08x_cctl(cctl); + plchan->dst_cctl = cctl; } dev_dbg(&pl08x->adev->dev, -- cgit v1.2.3-70-g09d2 From 800d683e6b8f0ba630470a56b61ff6742ad129ad Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 16 May 2012 11:33:31 +0100 Subject: dmaengine: PL08x: ignore 'direction' argument in dma_slave_config Ignore the direction argument in dma_slave_config, and configure both directions independently. We still check that the configuration for the intended direction is valid; this check will eventually be dropped. This check is just for debugging at present. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 56 ++++++++++++++++-------------------------------- 1 file changed, 18 insertions(+), 38 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index fde801f787c..50b9a839e9c 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -218,8 +218,6 @@ enum pl08x_dma_chan_state { * @name: name of channel * @cd: channel platform data * @runtime_addr: address for RX/TX according to the runtime config - * @runtime_direction: current direction of this channel according to - * runtime config * @pend_list: queued transactions pending on this channel * @at: active transaction on this channel * @lock: a lock for this channel data @@ -239,7 +237,6 @@ struct pl08x_dma_chan { struct dma_slave_config cfg; u32 src_cctl; u32 dst_cctl; - enum dma_transfer_direction runtime_direction; struct list_head pend_list; struct pl08x_txd *at; spinlock_t lock; @@ -1239,50 +1236,31 @@ static int dma_set_runtime_config(struct dma_chan *chan, { struct pl08x_dma_chan *plchan = to_pl08x_chan(chan); struct pl08x_driver_data *pl08x = plchan->host; - enum dma_slave_buswidth addr_width; - u32 maxburst, cctl = 0; + u32 src_cctl, dst_cctl; if (!plchan->slave) return -EINVAL; - /* Transfer direction */ - plchan->runtime_direction = config->direction; - if (config->direction == DMA_MEM_TO_DEV) { - addr_width = config->dst_addr_width; - maxburst = config->dst_maxburst; - } else if (config->direction == DMA_DEV_TO_MEM) { - addr_width = config->src_addr_width; - maxburst = config->src_maxburst; - } else { + dst_cctl = pl08x_get_cctl(plchan, config->dst_addr_width, + config->dst_maxburst); + if (dst_cctl == ~0 && config->direction == DMA_MEM_TO_DEV) { dev_err(&pl08x->adev->dev, - "bad runtime_config: alien transfer direction\n"); + "bad runtime_config: alien address width (M2D)\n"); return -EINVAL; } - cctl = pl08x_get_cctl(plchan, addr_width, maxburst); - if (cctl == ~0) { + src_cctl = pl08x_get_cctl(plchan, config->src_addr_width, + config->src_maxburst); + if (src_cctl == ~0 && config->direction == DMA_DEV_TO_MEM) { dev_err(&pl08x->adev->dev, - "bad runtime_config: alien address width\n"); + "bad runtime_config: alien address width (D2M)\n"); return -EINVAL; } + plchan->dst_cctl = dst_cctl; + plchan->src_cctl = src_cctl; plchan->cfg = *config; - if (plchan->runtime_direction == DMA_DEV_TO_MEM) { - plchan->src_cctl = cctl; - } else { - plchan->dst_cctl = cctl; - } - - dev_dbg(&pl08x->adev->dev, - "configured channel %s (%s) for %s, data width %d, " - "maxburst %d words, LE, CCTL=0x%08x\n", - dma_chan_name(chan), plchan->name, - (config->direction == DMA_DEV_TO_MEM) ? "RX" : "TX", - addr_width, - maxburst, - cctl); - return 0; } @@ -1470,11 +1448,6 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg( return NULL; } - if (direction != plchan->runtime_direction) - dev_err(&pl08x->adev->dev, "%s DMA setup does not match " - "the direction configured for the PrimeCell\n", - __func__); - /* * Set up addresses, the PrimeCell configured address * will take precedence since this may configure the @@ -1499,6 +1472,13 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg( return NULL; } + if (cctl == ~0) { + pl08x_free_txd(pl08x, txd); + dev_err(&pl08x->adev->dev, + "DMA slave configuration botched?\n"); + return NULL; + } + txd->cctl = cctl | pl08x_select_bus(src_buses, dst_buses); if (plchan->cfg.device_fc) -- cgit v1.2.3-70-g09d2 From dc8d5f8de12146c8732d926a30e5f064d76061e0 Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 16 May 2012 12:20:55 +0100 Subject: dmaengine: PL08x: get rid of unnecessary checks in dma_slave_config Get rid of the unnecessary checks in dma_slave_config utilizing the DMA direction. This allows us to move the computation of cctl to the prepare function. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- arch/arm/mach-spear3xx/spear300.c | 26 -------------------- arch/arm/mach-spear3xx/spear310.c | 26 -------------------- arch/arm/mach-spear3xx/spear320.c | 26 -------------------- arch/arm/mach-spear3xx/spear3xx.c | 3 ++- arch/arm/mach-spear6xx/spear6xx.c | 51 ++------------------------------------- drivers/dma/amba-pl08x.c | 41 ++++++++++--------------------- include/linux/amba/pl08x.h | 5 ++-- 7 files changed, 20 insertions(+), 158 deletions(-) (limited to 'drivers/dma') diff --git a/arch/arm/mach-spear3xx/spear300.c b/arch/arm/mach-spear3xx/spear300.c index 0f882ecb7d8..6ec30054996 100644 --- a/arch/arm/mach-spear3xx/spear300.c +++ b/arch/arm/mach-spear3xx/spear300.c @@ -120,182 +120,156 @@ struct pl08x_channel_data spear300_dma_info[] = { .min_signal = 2, .max_signal = 2, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "uart0_tx", .min_signal = 3, .max_signal = 3, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ssp0_rx", .min_signal = 8, .max_signal = 8, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ssp0_tx", .min_signal = 9, .max_signal = 9, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "i2c_rx", .min_signal = 10, .max_signal = 10, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "i2c_tx", .min_signal = 11, .max_signal = 11, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "irda", .min_signal = 12, .max_signal = 12, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "adc", .min_signal = 13, .max_signal = 13, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "to_jpeg", .min_signal = 14, .max_signal = 14, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "from_jpeg", .min_signal = 15, .max_signal = 15, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras0_rx", .min_signal = 0, .max_signal = 0, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras0_tx", .min_signal = 1, .max_signal = 1, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras1_rx", .min_signal = 2, .max_signal = 2, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras1_tx", .min_signal = 3, .max_signal = 3, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras2_rx", .min_signal = 4, .max_signal = 4, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras2_tx", .min_signal = 5, .max_signal = 5, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras3_rx", .min_signal = 6, .max_signal = 6, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras3_tx", .min_signal = 7, .max_signal = 7, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras4_rx", .min_signal = 8, .max_signal = 8, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras4_tx", .min_signal = 9, .max_signal = 9, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras5_rx", .min_signal = 10, .max_signal = 10, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras5_tx", .min_signal = 11, .max_signal = 11, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras6_rx", .min_signal = 12, .max_signal = 12, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras6_tx", .min_signal = 13, .max_signal = 13, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras7_rx", .min_signal = 14, .max_signal = 14, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras7_tx", .min_signal = 15, .max_signal = 15, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, }; diff --git a/arch/arm/mach-spear3xx/spear310.c b/arch/arm/mach-spear3xx/spear310.c index bbcf4571d36..1d0e435b904 100644 --- a/arch/arm/mach-spear3xx/spear310.c +++ b/arch/arm/mach-spear3xx/spear310.c @@ -205,182 +205,156 @@ struct pl08x_channel_data spear310_dma_info[] = { .min_signal = 2, .max_signal = 2, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "uart0_tx", .min_signal = 3, .max_signal = 3, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ssp0_rx", .min_signal = 8, .max_signal = 8, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ssp0_tx", .min_signal = 9, .max_signal = 9, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "i2c_rx", .min_signal = 10, .max_signal = 10, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "i2c_tx", .min_signal = 11, .max_signal = 11, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "irda", .min_signal = 12, .max_signal = 12, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "adc", .min_signal = 13, .max_signal = 13, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "to_jpeg", .min_signal = 14, .max_signal = 14, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "from_jpeg", .min_signal = 15, .max_signal = 15, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "uart1_rx", .min_signal = 0, .max_signal = 0, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "uart1_tx", .min_signal = 1, .max_signal = 1, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "uart2_rx", .min_signal = 2, .max_signal = 2, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "uart2_tx", .min_signal = 3, .max_signal = 3, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "uart3_rx", .min_signal = 4, .max_signal = 4, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "uart3_tx", .min_signal = 5, .max_signal = 5, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "uart4_rx", .min_signal = 6, .max_signal = 6, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "uart4_tx", .min_signal = 7, .max_signal = 7, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "uart5_rx", .min_signal = 8, .max_signal = 8, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "uart5_tx", .min_signal = 9, .max_signal = 9, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras5_rx", .min_signal = 10, .max_signal = 10, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras5_tx", .min_signal = 11, .max_signal = 11, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras6_rx", .min_signal = 12, .max_signal = 12, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras6_tx", .min_signal = 13, .max_signal = 13, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras7_rx", .min_signal = 14, .max_signal = 14, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras7_tx", .min_signal = 15, .max_signal = 15, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, }; diff --git a/arch/arm/mach-spear3xx/spear320.c b/arch/arm/mach-spear3xx/spear320.c index 88d483bcd66..fd823c62457 100644 --- a/arch/arm/mach-spear3xx/spear320.c +++ b/arch/arm/mach-spear3xx/spear320.c @@ -213,182 +213,156 @@ struct pl08x_channel_data spear320_dma_info[] = { .min_signal = 2, .max_signal = 2, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "uart0_tx", .min_signal = 3, .max_signal = 3, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ssp0_rx", .min_signal = 8, .max_signal = 8, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ssp0_tx", .min_signal = 9, .max_signal = 9, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "i2c0_rx", .min_signal = 10, .max_signal = 10, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "i2c0_tx", .min_signal = 11, .max_signal = 11, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "irda", .min_signal = 12, .max_signal = 12, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "adc", .min_signal = 13, .max_signal = 13, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "to_jpeg", .min_signal = 14, .max_signal = 14, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "from_jpeg", .min_signal = 15, .max_signal = 15, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ssp1_rx", .min_signal = 0, .max_signal = 0, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "ssp1_tx", .min_signal = 1, .max_signal = 1, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "ssp2_rx", .min_signal = 2, .max_signal = 2, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "ssp2_tx", .min_signal = 3, .max_signal = 3, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "uart1_rx", .min_signal = 4, .max_signal = 4, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "uart1_tx", .min_signal = 5, .max_signal = 5, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "uart2_rx", .min_signal = 6, .max_signal = 6, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "uart2_tx", .min_signal = 7, .max_signal = 7, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "i2c1_rx", .min_signal = 8, .max_signal = 8, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "i2c1_tx", .min_signal = 9, .max_signal = 9, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "i2c2_rx", .min_signal = 10, .max_signal = 10, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "i2c2_tx", .min_signal = 11, .max_signal = 11, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "i2s_rx", .min_signal = 12, .max_signal = 12, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "i2s_tx", .min_signal = 13, .max_signal = 13, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "rs485_rx", .min_signal = 14, .max_signal = 14, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "rs485_tx", .min_signal = 15, .max_signal = 15, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB2, }, }; diff --git a/arch/arm/mach-spear3xx/spear3xx.c b/arch/arm/mach-spear3xx/spear3xx.c index 0f41bd1c47c..d6cd8403fe6 100644 --- a/arch/arm/mach-spear3xx/spear3xx.c +++ b/arch/arm/mach-spear3xx/spear3xx.c @@ -46,7 +46,8 @@ struct pl022_ssp_controller pl022_plat_data = { struct pl08x_platform_data pl080_plat_data = { .memcpy_channel = { .bus_id = "memcpy", - .cctl = (PL080_BSIZE_16 << PL080_CONTROL_SB_SIZE_SHIFT | \ + .cctl_memcpy = + (PL080_BSIZE_16 << PL080_CONTROL_SB_SIZE_SHIFT | \ PL080_BSIZE_16 << PL080_CONTROL_DB_SIZE_SHIFT | \ PL080_WIDTH_32BIT << PL080_CONTROL_SWIDTH_SHIFT | \ PL080_WIDTH_32BIT << PL080_CONTROL_DWIDTH_SHIFT | \ diff --git a/arch/arm/mach-spear6xx/spear6xx.c b/arch/arm/mach-spear6xx/spear6xx.c index 2e2e3596583..b59ae5369e7 100644 --- a/arch/arm/mach-spear6xx/spear6xx.c +++ b/arch/arm/mach-spear6xx/spear6xx.c @@ -36,336 +36,288 @@ static struct pl08x_channel_data spear600_dma_info[] = { .min_signal = 0, .max_signal = 0, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ssp1_tx", .min_signal = 1, .max_signal = 1, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "uart0_rx", .min_signal = 2, .max_signal = 2, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "uart0_tx", .min_signal = 3, .max_signal = 3, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "uart1_rx", .min_signal = 4, .max_signal = 4, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "uart1_tx", .min_signal = 5, .max_signal = 5, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ssp2_rx", .min_signal = 6, .max_signal = 6, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "ssp2_tx", .min_signal = 7, .max_signal = 7, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "ssp0_rx", .min_signal = 8, .max_signal = 8, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ssp0_tx", .min_signal = 9, .max_signal = 9, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "i2c_rx", .min_signal = 10, .max_signal = 10, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "i2c_tx", .min_signal = 11, .max_signal = 11, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "irda", .min_signal = 12, .max_signal = 12, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "adc", .min_signal = 13, .max_signal = 13, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "to_jpeg", .min_signal = 14, .max_signal = 14, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "from_jpeg", .min_signal = 15, .max_signal = 15, .muxval = 0, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras0_rx", .min_signal = 0, .max_signal = 0, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras0_tx", .min_signal = 1, .max_signal = 1, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras1_rx", .min_signal = 2, .max_signal = 2, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras1_tx", .min_signal = 3, .max_signal = 3, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras2_rx", .min_signal = 4, .max_signal = 4, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras2_tx", .min_signal = 5, .max_signal = 5, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras3_rx", .min_signal = 6, .max_signal = 6, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras3_tx", .min_signal = 7, .max_signal = 7, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras4_rx", .min_signal = 8, .max_signal = 8, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras4_tx", .min_signal = 9, .max_signal = 9, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras5_rx", .min_signal = 10, .max_signal = 10, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras5_tx", .min_signal = 11, .max_signal = 11, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras6_rx", .min_signal = 12, .max_signal = 12, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras6_tx", .min_signal = 13, .max_signal = 13, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras7_rx", .min_signal = 14, .max_signal = 14, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ras7_tx", .min_signal = 15, .max_signal = 15, .muxval = 1, - .cctl = 0, .periph_buses = PL08X_AHB1, }, { .bus_id = "ext0_rx", .min_signal = 0, .max_signal = 0, .muxval = 2, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "ext0_tx", .min_signal = 1, .max_signal = 1, .muxval = 2, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "ext1_rx", .min_signal = 2, .max_signal = 2, .muxval = 2, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "ext1_tx", .min_signal = 3, .max_signal = 3, .muxval = 2, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "ext2_rx", .min_signal = 4, .max_signal = 4, .muxval = 2, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "ext2_tx", .min_signal = 5, .max_signal = 5, .muxval = 2, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "ext3_rx", .min_signal = 6, .max_signal = 6, .muxval = 2, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "ext3_tx", .min_signal = 7, .max_signal = 7, .muxval = 2, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "ext4_rx", .min_signal = 8, .max_signal = 8, .muxval = 2, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "ext4_tx", .min_signal = 9, .max_signal = 9, .muxval = 2, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "ext5_rx", .min_signal = 10, .max_signal = 10, .muxval = 2, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "ext5_tx", .min_signal = 11, .max_signal = 11, .muxval = 2, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "ext6_rx", .min_signal = 12, .max_signal = 12, .muxval = 2, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "ext6_tx", .min_signal = 13, .max_signal = 13, .muxval = 2, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "ext7_rx", .min_signal = 14, .max_signal = 14, .muxval = 2, - .cctl = 0, .periph_buses = PL08X_AHB2, }, { .bus_id = "ext7_tx", .min_signal = 15, .max_signal = 15, .muxval = 2, - .cctl = 0, .periph_buses = PL08X_AHB2, }, }; @@ -373,7 +325,8 @@ static struct pl08x_channel_data spear600_dma_info[] = { struct pl08x_platform_data pl080_plat_data = { .memcpy_channel = { .bus_id = "memcpy", - .cctl = (PL080_BSIZE_16 << PL080_CONTROL_SB_SIZE_SHIFT | \ + .cctl_memcpy = + (PL080_BSIZE_16 << PL080_CONTROL_SB_SIZE_SHIFT | \ PL080_BSIZE_16 << PL080_CONTROL_DB_SIZE_SHIFT | \ PL080_WIDTH_32BIT << PL080_CONTROL_SWIDTH_SHIFT | \ PL080_WIDTH_32BIT << PL080_CONTROL_DWIDTH_SHIFT | \ diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index 50b9a839e9c..f7397789e4e 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -235,8 +235,6 @@ struct pl08x_dma_chan { const char *name; const struct pl08x_channel_data *cd; struct dma_slave_config cfg; - u32 src_cctl; - u32 dst_cctl; struct list_head pend_list; struct pl08x_txd *at; spinlock_t lock; @@ -1235,30 +1233,15 @@ static int dma_set_runtime_config(struct dma_chan *chan, struct dma_slave_config *config) { struct pl08x_dma_chan *plchan = to_pl08x_chan(chan); - struct pl08x_driver_data *pl08x = plchan->host; - u32 src_cctl, dst_cctl; if (!plchan->slave) return -EINVAL; - dst_cctl = pl08x_get_cctl(plchan, config->dst_addr_width, - config->dst_maxburst); - if (dst_cctl == ~0 && config->direction == DMA_MEM_TO_DEV) { - dev_err(&pl08x->adev->dev, - "bad runtime_config: alien address width (M2D)\n"); + /* Reject definitely invalid configurations */ + if (config->src_addr_width == DMA_SLAVE_BUSWIDTH_8_BYTES || + config->dst_addr_width == DMA_SLAVE_BUSWIDTH_8_BYTES) return -EINVAL; - } - src_cctl = pl08x_get_cctl(plchan, config->src_addr_width, - config->src_maxburst); - if (src_cctl == ~0 && config->direction == DMA_DEV_TO_MEM) { - dev_err(&pl08x->adev->dev, - "bad runtime_config: alien address width (D2M)\n"); - return -EINVAL; - } - - plchan->dst_cctl = dst_cctl; - plchan->src_cctl = src_cctl; plchan->cfg = *config; return 0; @@ -1407,7 +1390,7 @@ static struct dma_async_tx_descriptor *pl08x_prep_dma_memcpy( /* Set platform data for m2m */ txd->ccfg |= PL080_FLOW_MEM2MEM << PL080_CONFIG_FLOW_CONTROL_SHIFT; - txd->cctl = pl08x->pd->memcpy_channel.cctl & + txd->cctl = pl08x->pd->memcpy_channel.cctl_memcpy & ~(PL080_CONTROL_DST_AHB2 | PL080_CONTROL_SRC_AHB2); /* Both to be incremented or the code will break */ @@ -1434,10 +1417,11 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg( struct pl08x_txd *txd; struct pl08x_sg *dsg; struct scatterlist *sg; + enum dma_slave_buswidth addr_width; dma_addr_t slave_addr; int ret, tmp; u8 src_buses, dst_buses; - u32 cctl; + u32 maxburst, cctl; dev_dbg(&pl08x->adev->dev, "%s prepare transaction of %d bytes from %s\n", __func__, sg_dma_len(sgl), plchan->name); @@ -1456,13 +1440,17 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg( txd->direction = direction; if (direction == DMA_MEM_TO_DEV) { - cctl = plchan->dst_cctl | PL080_CONTROL_SRC_INCR; + cctl = PL080_CONTROL_SRC_INCR; slave_addr = plchan->cfg.dst_addr; + addr_width = plchan->cfg.dst_addr_width; + maxburst = plchan->cfg.dst_maxburst; src_buses = pl08x->mem_buses; dst_buses = plchan->cd->periph_buses; } else if (direction == DMA_DEV_TO_MEM) { - cctl = plchan->src_cctl | PL080_CONTROL_DST_INCR; + cctl = PL080_CONTROL_DST_INCR; slave_addr = plchan->cfg.src_addr; + addr_width = plchan->cfg.src_addr_width; + maxburst = plchan->cfg.src_maxburst; src_buses = plchan->cd->periph_buses; dst_buses = pl08x->mem_buses; } else { @@ -1472,6 +1460,7 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg( return NULL; } + cctl |= pl08x_get_cctl(plchan, addr_width, maxburst); if (cctl == ~0) { pl08x_free_txd(pl08x, txd); dev_err(&pl08x->adev->dev, @@ -1774,14 +1763,10 @@ static irqreturn_t pl08x_irq(int irq, void *dev) static void pl08x_dma_slave_init(struct pl08x_dma_chan *chan) { - u32 cctl = pl08x_cctl(chan->cd->cctl); - chan->slave = true; chan->name = chan->cd->bus_id; chan->cfg.src_addr = chan->cd->addr; chan->cfg.dst_addr = chan->cd->addr; - chan->src_cctl = cctl; - chan->dst_cctl = cctl; } /* diff --git a/include/linux/amba/pl08x.h b/include/linux/amba/pl08x.h index 158ce2634b0..2a5f64a11b7 100644 --- a/include/linux/amba/pl08x.h +++ b/include/linux/amba/pl08x.h @@ -47,7 +47,8 @@ enum { * devices with static assignments * @muxval: a number usually used to poke into some mux regiser to * mux in the signal to this channel - * @cctl_opt: default options for the channel control register + * @cctl_memcpy: options for the channel control register for memcpy + * *** not used for slave channels *** * @addr: source/target address in physical memory for this DMA channel, * can be the address of a FIFO register for burst requests for example. * This can be left undefined if the PrimeCell API is used for configuring @@ -62,7 +63,7 @@ struct pl08x_channel_data { int min_signal; int max_signal; u32 muxval; - u32 cctl; + u32 cctl_memcpy; dma_addr_t addr; bool single; u8 periph_buses; -- cgit v1.2.3-70-g09d2 From 6b16c8b161a01bf3d6d93ba53c32ebece60fec29 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 25 May 2012 11:10:58 +0100 Subject: dmaengine: PL08x: split DMA signal muxing from channel alloc Split the DMA request mux signal handling from the physical channel allocation code. The physical channel has very little to do with the DMA request input which will be used, so these should be two separate operations. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index f7397789e4e..b579bac5638 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -295,6 +295,39 @@ static inline struct pl08x_txd *to_pl08x_txd(struct dma_async_tx_descriptor *tx) return container_of(tx, struct pl08x_txd, tx); } +/* + * Mux handling. + * + * This gives us the DMA request input to the PL08x primecell which the + * peripheral described by the channel data will be routed to, possibly + * via a board/SoC specific external MUX. One important point to note + * here is that this does not depend on the physical channel. + */ +static int pl08x_request_mux(struct pl08x_dma_chan *plchan, struct pl08x_phy_chan *ch) +{ + const struct pl08x_platform_data *pd = plchan->host->pd; + int ret; + + if (pd->get_signal) { + ret = pd->get_signal(plchan->cd); + if (ret < 0) + return ret; + + ch->signal = ret; + } + return 0; +} + +static void pl08x_release_mux(struct pl08x_dma_chan *plchan) +{ + const struct pl08x_platform_data *pd = plchan->host->pd; + + if (plchan->phychan->signal >= 0 && pd->put_signal) { + pd->put_signal(plchan->cd, plchan->phychan->signal); + plchan->phychan->signal = -1; + } +} + /* * Physical channel handling */ @@ -999,8 +1032,8 @@ static int prep_phy_channel(struct pl08x_dma_chan *plchan, * need, but for slaves the physical signals may be muxed! * Can the platform allow us to use this channel? */ - if (plchan->slave && pl08x->pd->get_signal) { - ret = pl08x->pd->get_signal(plchan->cd); + if (plchan->slave) { + ret = pl08x_request_mux(plchan, ch); if (ret < 0) { dev_dbg(&pl08x->adev->dev, "unable to use physical channel %d for transfer on %s due to platform restrictions\n", @@ -1009,7 +1042,6 @@ static int prep_phy_channel(struct pl08x_dma_chan *plchan, pl08x_put_phy_channel(pl08x, ch); return -EBUSY; } - ch->signal = ret; } plchan->phychan = ch; @@ -1034,10 +1066,7 @@ static void release_phy_channel(struct pl08x_dma_chan *plchan) { struct pl08x_driver_data *pl08x = plchan->host; - if ((plchan->phychan->signal >= 0) && pl08x->pd->put_signal) { - pl08x->pd->put_signal(plchan->cd, plchan->phychan->signal); - plchan->phychan->signal = -1; - } + pl08x_release_mux(plchan); pl08x_put_phy_channel(pl08x, plchan->phychan); plchan->phychan = NULL; } -- cgit v1.2.3-70-g09d2 From ad0de2ac3218d372149d89d9d5c5058aca6aa29b Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 25 May 2012 11:15:15 +0100 Subject: dmaengine: PL08x: move DMA signal muxing into pl08x_dma_chan struct Move the signal handling out of the physical channel structure into the virtual channel structure, where it should belong as it has more to do with the virtual channel than the physical one. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index b579bac5638..c203d2f22bc 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -136,17 +136,17 @@ struct pl08x_bus_data { * struct pl08x_phy_chan - holder for the physical channels * @id: physical index to this channel * @lock: a lock to use when altering an instance of this struct - * @signal: the physical signal (aka channel) serving this physical channel - * right now * @serving: the virtual channel currently being served by this physical * channel + * @locked: channel unavailable for the system, e.g. dedicated to secure + * world */ struct pl08x_phy_chan { unsigned int id; void __iomem *base; spinlock_t lock; - int signal; struct pl08x_dma_chan *serving; + bool locked; }; /** @@ -226,6 +226,7 @@ enum pl08x_dma_chan_state { * @slave: whether this channel is a device (slave) or for memcpy * @waiting: a TX descriptor on this channel which is waiting for a physical * channel to become available + * @signal: the physical DMA request signal which this channel is using */ struct pl08x_dma_chan { struct dma_chan chan; @@ -242,6 +243,7 @@ struct pl08x_dma_chan { enum pl08x_dma_chan_state state; bool slave; struct pl08x_txd *waiting; + int signal; }; /** @@ -303,7 +305,7 @@ static inline struct pl08x_txd *to_pl08x_txd(struct dma_async_tx_descriptor *tx) * via a board/SoC specific external MUX. One important point to note * here is that this does not depend on the physical channel. */ -static int pl08x_request_mux(struct pl08x_dma_chan *plchan, struct pl08x_phy_chan *ch) +static int pl08x_request_mux(struct pl08x_dma_chan *plchan) { const struct pl08x_platform_data *pd = plchan->host->pd; int ret; @@ -313,7 +315,7 @@ static int pl08x_request_mux(struct pl08x_dma_chan *plchan, struct pl08x_phy_cha if (ret < 0) return ret; - ch->signal = ret; + plchan->signal = ret; } return 0; } @@ -322,9 +324,9 @@ static void pl08x_release_mux(struct pl08x_dma_chan *plchan) { const struct pl08x_platform_data *pd = plchan->host->pd; - if (plchan->phychan->signal >= 0 && pd->put_signal) { - pd->put_signal(plchan->cd, plchan->phychan->signal); - plchan->phychan->signal = -1; + if (plchan->signal >= 0 && pd->put_signal) { + pd->put_signal(plchan->cd, plchan->signal); + plchan->signal = -1; } } @@ -549,7 +551,6 @@ pl08x_get_phy_channel(struct pl08x_driver_data *pl08x, if (!ch->locked && !ch->serving) { ch->serving = virt_chan; - ch->signal = -1; spin_unlock_irqrestore(&ch->lock, flags); break; } @@ -1033,7 +1034,7 @@ static int prep_phy_channel(struct pl08x_dma_chan *plchan, * Can the platform allow us to use this channel? */ if (plchan->slave) { - ret = pl08x_request_mux(plchan, ch); + ret = pl08x_request_mux(plchan); if (ret < 0) { dev_dbg(&pl08x->adev->dev, "unable to use physical channel %d for transfer on %s due to platform restrictions\n", @@ -1047,15 +1048,15 @@ static int prep_phy_channel(struct pl08x_dma_chan *plchan, plchan->phychan = ch; dev_dbg(&pl08x->adev->dev, "allocated physical channel %d and signal %d for xfer on %s\n", ch->id, - ch->signal, + plchan->signal, plchan->name); got_channel: /* Assign the flow control signal to this channel */ if (txd->direction == DMA_MEM_TO_DEV) - txd->ccfg |= ch->signal << PL080_CONFIG_DST_SEL_SHIFT; + txd->ccfg |= plchan->signal << PL080_CONFIG_DST_SEL_SHIFT; else if (txd->direction == DMA_DEV_TO_MEM) - txd->ccfg |= ch->signal << PL080_CONFIG_SRC_SEL_SHIFT; + txd->ccfg |= plchan->signal << PL080_CONFIG_SRC_SEL_SHIFT; plchan->phychan_hold++; @@ -1825,6 +1826,7 @@ static int pl08x_dma_init_virtual_channels(struct pl08x_driver_data *pl08x, chan->host = pl08x; chan->state = PL08X_CHAN_IDLE; + chan->signal = -1; if (slave) { chan->cd = &pl08x->pd->slave_channels[i]; @@ -2062,7 +2064,6 @@ static int pl08x_probe(struct amba_device *adev, const struct amba_id *id) ch->id = i; ch->base = pl08x->base + PL080_Cx_BASE(i); spin_lock_init(&ch->lock); - ch->signal = -1; /* * Nomadik variants can have channels that are locked -- cgit v1.2.3-70-g09d2 From 5e2479bd0e0dc41f2b9f6b4345bc5d4557837056 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 25 May 2012 11:32:45 +0100 Subject: dmaengine: PL08x: track mux usage on a per-channel basis. Keep track of the number of descriptors currently using a MUX setting on a per-channel basis. This allows us to know when we have descriptors queued somewhere which have been assigned a DMA request signal. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index c203d2f22bc..ac9fdccb2c1 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -227,6 +227,7 @@ enum pl08x_dma_chan_state { * @waiting: a TX descriptor on this channel which is waiting for a physical * channel to become available * @signal: the physical DMA request signal which this channel is using + * @mux_use: count of descriptors using this DMA request signal setting */ struct pl08x_dma_chan { struct dma_chan chan; @@ -244,6 +245,7 @@ struct pl08x_dma_chan { bool slave; struct pl08x_txd *waiting; int signal; + unsigned mux_use; }; /** @@ -310,10 +312,12 @@ static int pl08x_request_mux(struct pl08x_dma_chan *plchan) const struct pl08x_platform_data *pd = plchan->host->pd; int ret; - if (pd->get_signal) { + if (plchan->mux_use++ == 0 && pd->get_signal) { ret = pd->get_signal(plchan->cd); - if (ret < 0) + if (ret < 0) { + plchan->mux_use = 0; return ret; + } plchan->signal = ret; } @@ -324,9 +328,13 @@ static void pl08x_release_mux(struct pl08x_dma_chan *plchan) { const struct pl08x_platform_data *pd = plchan->host->pd; - if (plchan->signal >= 0 && pd->put_signal) { - pd->put_signal(plchan->cd, plchan->signal); - plchan->signal = -1; + if (plchan->signal >= 0) { + WARN_ON(plchan->mux_use == 0); + + if (--plchan->mux_use == 0 && pd->put_signal) { + pd->put_signal(plchan->cd, plchan->signal); + plchan->signal = -1; + } } } -- cgit v1.2.3-70-g09d2 From a936e793136b4238ef287cfbbdd25ebb78214529 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 25 May 2012 10:51:19 +0100 Subject: dmaengine: PL08x: convert to a list of completed descriptors Convert PL08x to use a list of completed descriptors rather than merely relying upon a single pointer. This makes it possible to schedule the tasklet for other purposes, and makes our behaviour similar to virt-dma. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index ac9fdccb2c1..54e3eb0b372 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -219,6 +219,7 @@ enum pl08x_dma_chan_state { * @cd: channel platform data * @runtime_addr: address for RX/TX according to the runtime config * @pend_list: queued transactions pending on this channel + * @done_list: list of completed transactions * @at: active transaction on this channel * @lock: a lock for this channel data * @host: a pointer to the host (internal use) @@ -238,6 +239,7 @@ struct pl08x_dma_chan { const struct pl08x_channel_data *cd; struct dma_slave_config cfg; struct list_head pend_list; + struct list_head done_list; struct pl08x_txd *at; spinlock_t lock; struct pl08x_driver_data *host; @@ -1673,18 +1675,11 @@ static void pl08x_tasklet(unsigned long data) { struct pl08x_dma_chan *plchan = (struct pl08x_dma_chan *) data; struct pl08x_driver_data *pl08x = plchan->host; - struct pl08x_txd *txd; unsigned long flags; + LIST_HEAD(head); spin_lock_irqsave(&plchan->lock, flags); - - txd = plchan->at; - plchan->at = NULL; - - if (txd) { - /* Update last completed */ - dma_cookie_complete(&txd->tx); - } + list_splice_tail_init(&plchan->done_list, &head); /* If a new descriptor is queued, set it up plchan->at is NULL here */ if (!list_empty(&plchan->pend_list)) { @@ -1739,10 +1734,14 @@ static void pl08x_tasklet(unsigned long data) spin_unlock_irqrestore(&plchan->lock, flags); - if (txd) { + while (!list_empty(&head)) { + struct pl08x_txd *txd = list_first_entry(&head, + struct pl08x_txd, node); dma_async_tx_callback callback = txd->tx.callback; void *callback_param = txd->tx.callback_param; + list_del(&txd->node); + /* Don't try to unmap buffers on slave channels */ if (!plchan->slave) pl08x_unmap_buffers(txd); @@ -1782,6 +1781,7 @@ static irqreturn_t pl08x_irq(int irq, void *dev) /* Locate physical channel */ struct pl08x_phy_chan *phychan = &pl08x->phy_chans[i]; struct pl08x_dma_chan *plchan = phychan->serving; + struct pl08x_txd *tx; if (!plchan) { dev_err(&pl08x->adev->dev, @@ -1790,6 +1790,15 @@ static irqreturn_t pl08x_irq(int irq, void *dev) continue; } + spin_lock(&plchan->lock); + tx = plchan->at; + if (tx) { + plchan->at = NULL; + dma_cookie_complete(&tx->tx); + list_add_tail(&tx->node, &plchan->done_list); + } + spin_unlock(&plchan->lock); + /* Schedule tasklet on this channel */ tasklet_schedule(&plchan->tasklet); mask |= (1 << i); @@ -1856,6 +1865,7 @@ static int pl08x_dma_init_virtual_channels(struct pl08x_driver_data *pl08x, spin_lock_init(&chan->lock); INIT_LIST_HEAD(&chan->pend_list); + INIT_LIST_HEAD(&chan->done_list); tasklet_init(&chan->tasklet, pl08x_tasklet, (unsigned long) chan); -- cgit v1.2.3-70-g09d2 From c48d49632989920a7903c2e14e7a6ddff048d1aa Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 25 May 2012 11:48:51 +0100 Subject: dmaengine: PL08x: move DMA signal muxing into slave prepare code Move the DMA request muxing into the slave prepare code and txd release/completion code. This means we only hold the DMA request mux while there are descriptors waiting to be started or are in progress. This leaves txd->direction as a write-only variable; remove it. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 79 ++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 47 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index 54e3eb0b372..e04ca0b01f9 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -168,7 +168,6 @@ struct pl08x_sg { * @tx: async tx descriptor * @node: node for txd list for channels * @dsg_list: list of children sg's - * @direction: direction of transfer * @llis_bus: DMA memory address (physical) start for the LLIs * @llis_va: virtual memory address start for the LLIs * @cctl: control reg values for current txd @@ -178,7 +177,6 @@ struct pl08x_txd { struct dma_async_tx_descriptor tx; struct list_head node; struct list_head dsg_list; - enum dma_transfer_direction direction; dma_addr_t llis_bus; struct pl08x_lli *llis_va; /* Default cctl value for LLIs */ @@ -997,6 +995,7 @@ static void pl08x_free_txd_list(struct pl08x_driver_data *pl08x, if (!list_empty(&plchan->pend_list)) { list_for_each_entry_safe(txdi, next, &plchan->pend_list, node) { + pl08x_release_mux(plchan); list_del(&txdi->node); pl08x_free_txd(pl08x, txdi); } @@ -1018,12 +1017,10 @@ static void pl08x_free_chan_resources(struct dma_chan *chan) /* * This should be called with the channel plchan->lock held */ -static int prep_phy_channel(struct pl08x_dma_chan *plchan, - struct pl08x_txd *txd) +static int prep_phy_channel(struct pl08x_dma_chan *plchan) { struct pl08x_driver_data *pl08x = plchan->host; struct pl08x_phy_chan *ch; - int ret; /* Check if we already have a channel */ if (plchan->phychan) { @@ -1038,36 +1035,11 @@ static int prep_phy_channel(struct pl08x_dma_chan *plchan, return -EBUSY; } - /* - * OK we have a physical channel: for memcpy() this is all we - * need, but for slaves the physical signals may be muxed! - * Can the platform allow us to use this channel? - */ - if (plchan->slave) { - ret = pl08x_request_mux(plchan); - if (ret < 0) { - dev_dbg(&pl08x->adev->dev, - "unable to use physical channel %d for transfer on %s due to platform restrictions\n", - ch->id, plchan->name); - /* Release physical channel & return */ - pl08x_put_phy_channel(pl08x, ch); - return -EBUSY; - } - } - plchan->phychan = ch; - dev_dbg(&pl08x->adev->dev, "allocated physical channel %d and signal %d for xfer on %s\n", - ch->id, - plchan->signal, - plchan->name); + dev_dbg(&pl08x->adev->dev, "allocated physical channel %d for xfer on %s\n", + ch->id, plchan->name); got_channel: - /* Assign the flow control signal to this channel */ - if (txd->direction == DMA_MEM_TO_DEV) - txd->ccfg |= plchan->signal << PL080_CONFIG_DST_SEL_SHIFT; - else if (txd->direction == DMA_DEV_TO_MEM) - txd->ccfg |= plchan->signal << PL080_CONFIG_SRC_SEL_SHIFT; - plchan->phychan_hold++; return 0; @@ -1077,7 +1049,6 @@ static void release_phy_channel(struct pl08x_dma_chan *plchan) { struct pl08x_driver_data *pl08x = plchan->host; - pl08x_release_mux(plchan); pl08x_put_phy_channel(pl08x, plchan->phychan); plchan->phychan = NULL; } @@ -1340,19 +1311,12 @@ static int pl08x_prep_channel_resources(struct pl08x_dma_chan *plchan, * See if we already have a physical channel allocated, * else this is the time to try to get one. */ - ret = prep_phy_channel(plchan, txd); + ret = prep_phy_channel(plchan); if (ret) { /* * No physical channel was available. * * memcpy transfers can be sorted out at submission time. - * - * Slave transfers may have been denied due to platform - * channel muxing restrictions. Since there is no guarantee - * that this will ever be resolved, and the signal must be - * acquired AFTER acquiring the physical channel, we will let - * them be NACK:ed with -EBUSY here. The drivers can retry - * the prep() call if they are eager on doing this using DMA. */ if (plchan->slave) { pl08x_free_txd_list(pl08x, plchan); @@ -1423,7 +1387,6 @@ static struct dma_async_tx_descriptor *pl08x_prep_dma_memcpy( } list_add_tail(&dsg->node, &txd->dsg_list); - txd->direction = DMA_MEM_TO_MEM; dsg->src_addr = src; dsg->dst_addr = dest; dsg->len = len; @@ -1477,8 +1440,6 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg( * will take precedence since this may configure the * channel target address dynamically at runtime. */ - txd->direction = direction; - if (direction == DMA_MEM_TO_DEV) { cctl = PL080_CONTROL_SRC_INCR; slave_addr = plchan->cfg.dst_addr; @@ -1519,9 +1480,28 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg( txd->ccfg |= tmp << PL080_CONFIG_FLOW_CONTROL_SHIFT; + ret = pl08x_request_mux(plchan); + if (ret < 0) { + pl08x_free_txd(pl08x, txd); + dev_dbg(&pl08x->adev->dev, + "unable to mux for transfer on %s due to platform restrictions\n", + plchan->name); + return NULL; + } + + dev_dbg(&pl08x->adev->dev, "allocated DMA request signal %d for xfer on %s\n", + plchan->signal, plchan->name); + + /* Assign the flow control signal to this channel */ + if (direction == DMA_MEM_TO_DEV) + txd->ccfg |= plchan->signal << PL080_CONFIG_DST_SEL_SHIFT; + else + txd->ccfg |= plchan->signal << PL080_CONFIG_SRC_SEL_SHIFT; + for_each_sg(sgl, sg, sg_len, tmp) { dsg = kzalloc(sizeof(struct pl08x_sg), GFP_NOWAIT); if (!dsg) { + pl08x_release_mux(plchan); pl08x_free_txd(pl08x, txd); dev_err(&pl08x->adev->dev, "%s no mem for pl080 sg\n", __func__); @@ -1586,6 +1566,8 @@ static int pl08x_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, } /* Dequeue jobs and free LLIs */ if (plchan->at) { + /* Killing this one off, release its mux */ + pl08x_release_mux(plchan); pl08x_free_txd(pl08x, plchan->at); plchan->at = NULL; } @@ -1702,7 +1684,6 @@ static void pl08x_tasklet(unsigned long data) /* * No more jobs, so free up the physical channel - * Free any allocated signal on slave transfers too */ release_phy_channel(plchan); plchan->state = PL08X_CHAN_IDLE; @@ -1720,8 +1701,7 @@ static void pl08x_tasklet(unsigned long data) int ret; /* This should REALLY not fail now */ - ret = prep_phy_channel(waiting, - waiting->waiting); + ret = prep_phy_channel(waiting); BUG_ON(ret); waiting->phychan_hold--; waiting->state = PL08X_CHAN_RUNNING; @@ -1794,6 +1774,11 @@ static irqreturn_t pl08x_irq(int irq, void *dev) tx = plchan->at; if (tx) { plchan->at = NULL; + /* + * This descriptor is done, release its mux + * reservation. + */ + pl08x_release_mux(plchan); dma_cookie_complete(&tx->tx); list_add_tail(&tx->node, &plchan->done_list); } -- cgit v1.2.3-70-g09d2 From 7847f6b55e6a5fe0bbcdaf20db291c9b1db890e8 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 25 May 2012 12:08:13 +0100 Subject: dmaengine: PL08x: remove waiting descriptor pointer As we no longer need to pass a descriptor to prep_phy_channel(), we don't need to keep track of the descriptor which is waiting for a channel to become available. So let's get rid of it. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index e04ca0b01f9..88661fa4542 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -223,8 +223,6 @@ enum pl08x_dma_chan_state { * @host: a pointer to the host (internal use) * @state: whether the channel is idle, paused, running etc * @slave: whether this channel is a device (slave) or for memcpy - * @waiting: a TX descriptor on this channel which is waiting for a physical - * channel to become available * @signal: the physical DMA request signal which this channel is using * @mux_use: count of descriptors using this DMA request signal setting */ @@ -243,7 +241,6 @@ struct pl08x_dma_chan { struct pl08x_driver_data *host; enum pl08x_dma_chan_state state; bool slave; - struct pl08x_txd *waiting; int signal; unsigned mux_use; }; @@ -1074,7 +1071,6 @@ static dma_cookie_t pl08x_tx_submit(struct dma_async_tx_descriptor *tx) if (!plchan->slave && !plchan->phychan) { /* Do this memcpy whenever there is a channel ready */ plchan->state = PL08X_CHAN_WAITING; - plchan->waiting = txd; } else { plchan->phychan_hold--; } @@ -1696,8 +1692,7 @@ static void pl08x_tasklet(unsigned long data) */ list_for_each_entry(waiting, &pl08x->memcpy.channels, chan.device_node) { - if (waiting->state == PL08X_CHAN_WAITING && - waiting->waiting != NULL) { + if (waiting->state == PL08X_CHAN_WAITING) { int ret; /* This should REALLY not fail now */ @@ -1705,7 +1700,6 @@ static void pl08x_tasklet(unsigned long data) BUG_ON(ret); waiting->phychan_hold--; waiting->state = PL08X_CHAN_RUNNING; - waiting->waiting = NULL; pl08x_issue_pending(&waiting->chan); break; } -- cgit v1.2.3-70-g09d2 From eab82533c972bf932434b69bbb71c6724d863ef7 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 25 May 2012 12:32:00 +0100 Subject: dmaengine: PL08x: re-jig the starting of txds Rather than code the de-queue of the txd several times, move that into the start_txd function. Rename this to better illustrate what it's now doing, and call this function when starting a delayed memcpy(). Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index 88661fa4542..c278d23adee 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -354,20 +354,25 @@ static int pl08x_phy_channel_busy(struct pl08x_phy_chan *ch) * been set when the LLIs were constructed. Poke them into the hardware * and start the transfer. */ -static void pl08x_start_txd(struct pl08x_dma_chan *plchan, - struct pl08x_txd *txd) +static void pl08x_start_next_txd(struct pl08x_dma_chan *plchan) { struct pl08x_driver_data *pl08x = plchan->host; struct pl08x_phy_chan *phychan = plchan->phychan; - struct pl08x_lli *lli = &txd->llis_va[0]; + struct pl08x_lli *lli; + struct pl08x_txd *txd; u32 val; + txd = list_first_entry(&plchan->pend_list, struct pl08x_txd, node); + list_del(&txd->node); + plchan->at = txd; /* Wait for channel inactive */ while (pl08x_phy_channel_busy(phychan)) cpu_relax(); + lli = &txd->llis_va[0]; + dev_vdbg(&pl08x->adev->dev, "WRITE channel %d: csrc=0x%08x, cdst=0x%08x, " "clli=0x%08x, cctl=0x%08x, ccfg=0x%08x\n", @@ -1272,15 +1277,8 @@ static void pl08x_issue_pending(struct dma_chan *chan) /* Take the first element in the queue and execute it */ if (!list_empty(&plchan->pend_list)) { - struct pl08x_txd *next; - - next = list_first_entry(&plchan->pend_list, - struct pl08x_txd, - node); - list_del(&next->node); plchan->state = PL08X_CHAN_RUNNING; - - pl08x_start_txd(plchan, next); + pl08x_start_next_txd(plchan); } spin_unlock_irqrestore(&plchan->lock, flags); @@ -1661,14 +1659,7 @@ static void pl08x_tasklet(unsigned long data) /* If a new descriptor is queued, set it up plchan->at is NULL here */ if (!list_empty(&plchan->pend_list)) { - struct pl08x_txd *next; - - next = list_first_entry(&plchan->pend_list, - struct pl08x_txd, - node); - list_del(&next->node); - - pl08x_start_txd(plchan, next); + pl08x_start_next_txd(plchan); } else if (plchan->phychan_hold) { /* * This channel is still in use - we have a new txd being @@ -1700,7 +1691,13 @@ static void pl08x_tasklet(unsigned long data) BUG_ON(ret); waiting->phychan_hold--; waiting->state = PL08X_CHAN_RUNNING; - pl08x_issue_pending(&waiting->chan); + /* + * Eww. We know this isn't going to deadlock + * but lockdep probably doens't. + */ + spin_lock(&waiting->lock); + pl08x_start_next_txd(waiting); + spin_unlock(&waiting->lock); break; } } -- cgit v1.2.3-70-g09d2 From ea1605612ca4a8c7936e155da768bb9f4e69e84f Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 25 May 2012 13:10:36 +0100 Subject: dmaengine: PL08x: split the pend_list in two Our behaviour wasn't correct; issue_pending is supposed to be called before any submitted descriptors are available for processing by the DMA engine. Split the pend_list in two, one for submitted descriptors and another list for issued descriptors. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index c278d23adee..b6132845e65 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -217,6 +217,7 @@ enum pl08x_dma_chan_state { * @cd: channel platform data * @runtime_addr: address for RX/TX according to the runtime config * @pend_list: queued transactions pending on this channel + * @issued_list: issued transactions for this channel * @done_list: list of completed transactions * @at: active transaction on this channel * @lock: a lock for this channel data @@ -235,6 +236,7 @@ struct pl08x_dma_chan { const struct pl08x_channel_data *cd; struct dma_slave_config cfg; struct list_head pend_list; + struct list_head issued_list; struct list_head done_list; struct pl08x_txd *at; spinlock_t lock; @@ -362,7 +364,7 @@ static void pl08x_start_next_txd(struct pl08x_dma_chan *plchan) struct pl08x_txd *txd; u32 val; - txd = list_first_entry(&plchan->pend_list, struct pl08x_txd, node); + txd = list_first_entry(&plchan->issued_list, struct pl08x_txd, node); list_del(&txd->node); plchan->at = txd; @@ -525,6 +527,15 @@ static u32 pl08x_getbytes_chan(struct pl08x_dma_chan *plchan) } /* Sum up all queued transactions */ + if (!list_empty(&plchan->issued_list)) { + struct pl08x_txd *txdi; + list_for_each_entry(txdi, &plchan->issued_list, node) { + struct pl08x_sg *dsg; + list_for_each_entry(dsg, &txd->dsg_list, node) + bytes += dsg->len; + } + } + if (!list_empty(&plchan->pend_list)) { struct pl08x_txd *txdi; list_for_each_entry(txdi, &plchan->pend_list, node) { @@ -991,16 +1002,17 @@ static void pl08x_free_txd(struct pl08x_driver_data *pl08x, static void pl08x_free_txd_list(struct pl08x_driver_data *pl08x, struct pl08x_dma_chan *plchan) { - struct pl08x_txd *txdi = NULL; - struct pl08x_txd *next; + LIST_HEAD(head); + struct pl08x_txd *txd; - if (!list_empty(&plchan->pend_list)) { - list_for_each_entry_safe(txdi, - next, &plchan->pend_list, node) { - pl08x_release_mux(plchan); - list_del(&txdi->node); - pl08x_free_txd(pl08x, txdi); - } + list_splice_tail_init(&plchan->issued_list, &head); + list_splice_tail_init(&plchan->pend_list, &head); + + while (!list_empty(&head)) { + txd = list_first_entry(&head, struct pl08x_txd, node); + pl08x_release_mux(plchan); + list_del(&txd->node); + pl08x_free_txd(pl08x, txd); } } @@ -1269,6 +1281,8 @@ static void pl08x_issue_pending(struct dma_chan *chan) unsigned long flags; spin_lock_irqsave(&plchan->lock, flags); + list_splice_tail_init(&plchan->pend_list, &plchan->issued_list); + /* Something is already active, or we're waiting for a channel... */ if (plchan->at || plchan->state == PL08X_CHAN_WAITING) { spin_unlock_irqrestore(&plchan->lock, flags); @@ -1276,7 +1290,7 @@ static void pl08x_issue_pending(struct dma_chan *chan) } /* Take the first element in the queue and execute it */ - if (!list_empty(&plchan->pend_list)) { + if (!list_empty(&plchan->issued_list)) { plchan->state = PL08X_CHAN_RUNNING; pl08x_start_next_txd(plchan); } @@ -1658,9 +1672,9 @@ static void pl08x_tasklet(unsigned long data) list_splice_tail_init(&plchan->done_list, &head); /* If a new descriptor is queued, set it up plchan->at is NULL here */ - if (!list_empty(&plchan->pend_list)) { + if (!list_empty(&plchan->issued_list)) { pl08x_start_next_txd(plchan); - } else if (plchan->phychan_hold) { + } else if (!list_empty(&plchan->pend_list) || plchan->phychan_hold) { /* * This channel is still in use - we have a new txd being * prepared and will soon be queued. Don't give up the @@ -1841,6 +1855,7 @@ static int pl08x_dma_init_virtual_channels(struct pl08x_driver_data *pl08x, spin_lock_init(&chan->lock); INIT_LIST_HEAD(&chan->pend_list); + INIT_LIST_HEAD(&chan->issued_list); INIT_LIST_HEAD(&chan->done_list); tasklet_init(&chan->tasklet, pl08x_tasklet, (unsigned long) chan); -- cgit v1.2.3-70-g09d2 From c33b644cb31899265ec5102a4ed45c44269dde95 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 25 May 2012 15:41:13 +0100 Subject: dmaengine: PL08x: start next descriptor from irq context Rather than waiting for the tasklet to run, we can start the next descriptor from interrupt context, as soon as we know that the previous descriptor has completed. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index b6132845e65..30b6921f094 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -1671,10 +1671,7 @@ static void pl08x_tasklet(unsigned long data) spin_lock_irqsave(&plchan->lock, flags); list_splice_tail_init(&plchan->done_list, &head); - /* If a new descriptor is queued, set it up plchan->at is NULL here */ - if (!list_empty(&plchan->issued_list)) { - pl08x_start_next_txd(plchan); - } else if (!list_empty(&plchan->pend_list) || plchan->phychan_hold) { + if (plchan->at || !list_empty(&plchan->pend_list) || plchan->phychan_hold) { /* * This channel is still in use - we have a new txd being * prepared and will soon be queued. Don't give up the @@ -1786,6 +1783,10 @@ static irqreturn_t pl08x_irq(int irq, void *dev) pl08x_release_mux(plchan); dma_cookie_complete(&tx->tx); list_add_tail(&tx->node, &plchan->done_list); + + /* And start the next descriptor */ + if (!list_empty(&plchan->issued_list)) + pl08x_start_next_txd(plchan); } spin_unlock(&plchan->lock); -- cgit v1.2.3-70-g09d2 From a5a488db427ef1ba637163d1a699b170c20d9789 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sat, 26 May 2012 13:54:15 +0100 Subject: dmaengine: PL08x: rejig physical channel allocation Rework the physical channel allocation mechanism to only allocate physical channels to virtual channels when they're about to be used. This eliminates all the complexity with holding channels while descriptors are being prepared, which is completely unnecessary. This also brings this driver to a state where the generic virtual DMA code can be used with this driver, and opens up the possibility of properly scheduling and prioritorising physical DMA channels to virtual DMA channels. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 268 ++++++++++++++++++++--------------------------- 1 file changed, 112 insertions(+), 156 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index 30b6921f094..bbae30ceb8f 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -210,8 +210,6 @@ enum pl08x_dma_chan_state { * struct pl08x_dma_chan - this structure wraps a DMA ENGINE channel * @chan: wrappped abstract channel * @phychan: the physical channel utilized by this channel, if there is one - * @phychan_hold: if non-zero, hold on to the physical channel even if we - * have no pending entries * @tasklet: tasklet scheduled by the IRQ to handle actual work etc * @name: name of channel * @cd: channel platform data @@ -230,7 +228,6 @@ enum pl08x_dma_chan_state { struct pl08x_dma_chan { struct dma_chan chan; struct pl08x_phy_chan *phychan; - int phychan_hold; struct tasklet_struct tasklet; const char *name; const struct pl08x_channel_data *cd; @@ -587,19 +584,111 @@ pl08x_get_phy_channel(struct pl08x_driver_data *pl08x, return ch; } +/* Mark the physical channel as free. Note, this write is atomic. */ static inline void pl08x_put_phy_channel(struct pl08x_driver_data *pl08x, struct pl08x_phy_chan *ch) { - unsigned long flags; + ch->serving = NULL; +} - spin_lock_irqsave(&ch->lock, flags); +/* + * Try to allocate a physical channel. When successful, assign it to + * this virtual channel, and initiate the next descriptor. The + * virtual channel lock must be held at this point. + */ +static void pl08x_phy_alloc_and_start(struct pl08x_dma_chan *plchan) +{ + struct pl08x_driver_data *pl08x = plchan->host; + struct pl08x_phy_chan *ch; - /* Stop the channel and clear its interrupts */ - pl08x_terminate_phy_chan(pl08x, ch); + ch = pl08x_get_phy_channel(pl08x, plchan); + if (!ch) { + dev_dbg(&pl08x->adev->dev, "no physical channel available for xfer on %s\n", plchan->name); + plchan->state = PL08X_CHAN_WAITING; + return; + } - /* Mark it as free */ - ch->serving = NULL; - spin_unlock_irqrestore(&ch->lock, flags); + dev_dbg(&pl08x->adev->dev, "allocated physical channel %d for xfer on %s\n", + ch->id, plchan->name); + + plchan->phychan = ch; + plchan->state = PL08X_CHAN_RUNNING; + pl08x_start_next_txd(plchan); +} + +static void pl08x_phy_reassign_start(struct pl08x_phy_chan *ch, + struct pl08x_dma_chan *plchan) +{ + struct pl08x_driver_data *pl08x = plchan->host; + + dev_dbg(&pl08x->adev->dev, "reassigned physical channel %d for xfer on %s\n", + ch->id, plchan->name); + + /* + * We do this without taking the lock; we're really only concerned + * about whether this pointer is NULL or not, and we're guaranteed + * that this will only be called when it _already_ is non-NULL. + */ + ch->serving = plchan; + plchan->phychan = ch; + plchan->state = PL08X_CHAN_RUNNING; + pl08x_start_next_txd(plchan); +} + +/* + * Free a physical DMA channel, potentially reallocating it to another + * virtual channel if we have any pending. + */ +static void pl08x_phy_free(struct pl08x_dma_chan *plchan) +{ + struct pl08x_driver_data *pl08x = plchan->host; + struct pl08x_dma_chan *p, *next; + + retry: + next = NULL; + + /* Find a waiting virtual channel for the next transfer. */ + list_for_each_entry(p, &pl08x->memcpy.channels, chan.device_node) + if (p->state == PL08X_CHAN_WAITING) { + next = p; + break; + } + + if (!next) { + list_for_each_entry(p, &pl08x->slave.channels, chan.device_node) + if (p->state == PL08X_CHAN_WAITING) { + next = p; + break; + } + } + + /* Ensure that the physical channel is stopped */ + pl08x_terminate_phy_chan(pl08x, plchan->phychan); + + if (next) { + bool success; + + /* + * Eww. We know this isn't going to deadlock + * but lockdep probably doesn't. + */ + spin_lock(&next->lock); + /* Re-check the state now that we have the lock */ + success = next->state == PL08X_CHAN_WAITING; + if (success) + pl08x_phy_reassign_start(plchan->phychan, next); + spin_unlock(&next->lock); + + /* If the state changed, try to find another channel */ + if (!success) + goto retry; + } else { + /* No more jobs, so free up the physical channel */ + pl08x_put_phy_channel(pl08x, plchan->phychan); + } + + plchan->phychan = NULL; + plchan->state = PL08X_CHAN_IDLE; } /* @@ -1028,45 +1117,6 @@ static void pl08x_free_chan_resources(struct dma_chan *chan) { } -/* - * This should be called with the channel plchan->lock held - */ -static int prep_phy_channel(struct pl08x_dma_chan *plchan) -{ - struct pl08x_driver_data *pl08x = plchan->host; - struct pl08x_phy_chan *ch; - - /* Check if we already have a channel */ - if (plchan->phychan) { - ch = plchan->phychan; - goto got_channel; - } - - ch = pl08x_get_phy_channel(pl08x, plchan); - if (!ch) { - /* No physical channel available, cope with it */ - dev_dbg(&pl08x->adev->dev, "no physical channel available for xfer on %s\n", plchan->name); - return -EBUSY; - } - - plchan->phychan = ch; - dev_dbg(&pl08x->adev->dev, "allocated physical channel %d for xfer on %s\n", - ch->id, plchan->name); - -got_channel: - plchan->phychan_hold++; - - return 0; -} - -static void release_phy_channel(struct pl08x_dma_chan *plchan) -{ - struct pl08x_driver_data *pl08x = plchan->host; - - pl08x_put_phy_channel(pl08x, plchan->phychan); - plchan->phychan = NULL; -} - static dma_cookie_t pl08x_tx_submit(struct dma_async_tx_descriptor *tx) { struct pl08x_dma_chan *plchan = to_pl08x_chan(tx->chan); @@ -1079,19 +1129,6 @@ static dma_cookie_t pl08x_tx_submit(struct dma_async_tx_descriptor *tx) /* Put this onto the pending list */ list_add_tail(&txd->node, &plchan->pend_list); - - /* - * If there was no physical channel available for this memcpy, - * stack the request up and indicate that the channel is waiting - * for a free physical channel. - */ - if (!plchan->slave && !plchan->phychan) { - /* Do this memcpy whenever there is a channel ready */ - plchan->state = PL08X_CHAN_WAITING; - } else { - plchan->phychan_hold--; - } - spin_unlock_irqrestore(&plchan->lock, flags); return cookie; @@ -1282,19 +1319,10 @@ static void pl08x_issue_pending(struct dma_chan *chan) spin_lock_irqsave(&plchan->lock, flags); list_splice_tail_init(&plchan->pend_list, &plchan->issued_list); - - /* Something is already active, or we're waiting for a channel... */ - if (plchan->at || plchan->state == PL08X_CHAN_WAITING) { - spin_unlock_irqrestore(&plchan->lock, flags); - return; - } - - /* Take the first element in the queue and execute it */ if (!list_empty(&plchan->issued_list)) { - plchan->state = PL08X_CHAN_RUNNING; - pl08x_start_next_txd(plchan); + if (!plchan->phychan && plchan->state != PL08X_CHAN_WAITING) + pl08x_phy_alloc_and_start(plchan); } - spin_unlock_irqrestore(&plchan->lock, flags); } @@ -1302,48 +1330,18 @@ static int pl08x_prep_channel_resources(struct pl08x_dma_chan *plchan, struct pl08x_txd *txd) { struct pl08x_driver_data *pl08x = plchan->host; - unsigned long flags; - int num_llis, ret; + int num_llis; num_llis = pl08x_fill_llis_for_desc(pl08x, txd); if (!num_llis) { + unsigned long flags; + spin_lock_irqsave(&plchan->lock, flags); pl08x_free_txd(pl08x, txd); spin_unlock_irqrestore(&plchan->lock, flags); + return -EINVAL; } - - spin_lock_irqsave(&plchan->lock, flags); - - /* - * See if we already have a physical channel allocated, - * else this is the time to try to get one. - */ - ret = prep_phy_channel(plchan); - if (ret) { - /* - * No physical channel was available. - * - * memcpy transfers can be sorted out at submission time. - */ - if (plchan->slave) { - pl08x_free_txd_list(pl08x, plchan); - pl08x_free_txd(pl08x, txd); - spin_unlock_irqrestore(&plchan->lock, flags); - return -EBUSY; - } - } else - /* - * Else we're all set, paused and ready to roll, status - * will switch to PL08X_CHAN_RUNNING when we call - * issue_pending(). If there is something running on the - * channel already we don't change its state. - */ - if (plchan->state == PL08X_CHAN_IDLE) - plchan->state = PL08X_CHAN_PAUSED; - - spin_unlock_irqrestore(&plchan->lock, flags); - return 0; } @@ -1563,14 +1561,11 @@ static int pl08x_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, plchan->state = PL08X_CHAN_IDLE; if (plchan->phychan) { - pl08x_terminate_phy_chan(pl08x, plchan->phychan); - /* * Mark physical channel as free and free any slave * signal */ - release_phy_channel(plchan); - plchan->phychan_hold = 0; + pl08x_phy_free(plchan); } /* Dequeue jobs and free LLIs */ if (plchan->at) { @@ -1670,50 +1665,6 @@ static void pl08x_tasklet(unsigned long data) spin_lock_irqsave(&plchan->lock, flags); list_splice_tail_init(&plchan->done_list, &head); - - if (plchan->at || !list_empty(&plchan->pend_list) || plchan->phychan_hold) { - /* - * This channel is still in use - we have a new txd being - * prepared and will soon be queued. Don't give up the - * physical channel. - */ - } else { - struct pl08x_dma_chan *waiting = NULL; - - /* - * No more jobs, so free up the physical channel - */ - release_phy_channel(plchan); - plchan->state = PL08X_CHAN_IDLE; - - /* - * And NOW before anyone else can grab that free:d up - * physical channel, see if there is some memcpy pending - * that seriously needs to start because of being stacked - * up while we were choking the physical channels with data. - */ - list_for_each_entry(waiting, &pl08x->memcpy.channels, - chan.device_node) { - if (waiting->state == PL08X_CHAN_WAITING) { - int ret; - - /* This should REALLY not fail now */ - ret = prep_phy_channel(waiting); - BUG_ON(ret); - waiting->phychan_hold--; - waiting->state = PL08X_CHAN_RUNNING; - /* - * Eww. We know this isn't going to deadlock - * but lockdep probably doens't. - */ - spin_lock(&waiting->lock); - pl08x_start_next_txd(waiting); - spin_unlock(&waiting->lock); - break; - } - } - } - spin_unlock_irqrestore(&plchan->lock, flags); while (!list_empty(&head)) { @@ -1784,9 +1735,14 @@ static irqreturn_t pl08x_irq(int irq, void *dev) dma_cookie_complete(&tx->tx); list_add_tail(&tx->node, &plchan->done_list); - /* And start the next descriptor */ + /* + * And start the next descriptor (if any), + * otherwise free this channel. + */ if (!list_empty(&plchan->issued_list)) pl08x_start_next_txd(plchan); + else + pl08x_phy_free(plchan); } spin_unlock(&plchan->lock); -- cgit v1.2.3-70-g09d2 From 01d8dc64e92a0abace41028db5b9ca298458543f Mon Sep 17 00:00:00 2001 From: Russell King Date: Sat, 26 May 2012 14:04:29 +0100 Subject: dmaengine: PL08x: convert to use virt-dma structs Convert PL08x to use the virt-dma structures. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 57 ++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 28 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index bbae30ceb8f..9a0642805b8 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -86,6 +86,7 @@ #include #include "dmaengine.h" +#include "virt-dma.h" #define DRIVER_NAME "pl08xdmac" @@ -165,7 +166,7 @@ struct pl08x_sg { /** * struct pl08x_txd - wrapper for struct dma_async_tx_descriptor - * @tx: async tx descriptor + * @vd: virtual DMA descriptor * @node: node for txd list for channels * @dsg_list: list of children sg's * @llis_bus: DMA memory address (physical) start for the LLIs @@ -174,7 +175,7 @@ struct pl08x_sg { * @ccfg: config reg values for current txd */ struct pl08x_txd { - struct dma_async_tx_descriptor tx; + struct virt_dma_desc vd; struct list_head node; struct list_head dsg_list; dma_addr_t llis_bus; @@ -208,7 +209,7 @@ enum pl08x_dma_chan_state { /** * struct pl08x_dma_chan - this structure wraps a DMA ENGINE channel - * @chan: wrappped abstract channel + * @vc: wrappped virtual channel * @phychan: the physical channel utilized by this channel, if there is one * @tasklet: tasklet scheduled by the IRQ to handle actual work etc * @name: name of channel @@ -226,7 +227,7 @@ enum pl08x_dma_chan_state { * @mux_use: count of descriptors using this DMA request signal setting */ struct pl08x_dma_chan { - struct dma_chan chan; + struct virt_dma_chan vc; struct pl08x_phy_chan *phychan; struct tasklet_struct tasklet; const char *name; @@ -287,12 +288,12 @@ struct pl08x_driver_data { static inline struct pl08x_dma_chan *to_pl08x_chan(struct dma_chan *chan) { - return container_of(chan, struct pl08x_dma_chan, chan); + return container_of(chan, struct pl08x_dma_chan, vc.chan); } static inline struct pl08x_txd *to_pl08x_txd(struct dma_async_tx_descriptor *tx) { - return container_of(tx, struct pl08x_txd, tx); + return container_of(tx, struct pl08x_txd, vd.tx); } /* @@ -648,14 +649,14 @@ static void pl08x_phy_free(struct pl08x_dma_chan *plchan) next = NULL; /* Find a waiting virtual channel for the next transfer. */ - list_for_each_entry(p, &pl08x->memcpy.channels, chan.device_node) + list_for_each_entry(p, &pl08x->memcpy.channels, vc.chan.device_node) if (p->state == PL08X_CHAN_WAITING) { next = p; break; } if (!next) { - list_for_each_entry(p, &pl08x->slave.channels, chan.device_node) + list_for_each_entry(p, &pl08x->slave.channels, vc.chan.device_node) if (p->state == PL08X_CHAN_WAITING) { next = p; break; @@ -1351,9 +1352,9 @@ static struct pl08x_txd *pl08x_get_txd(struct pl08x_dma_chan *plchan, struct pl08x_txd *txd = kzalloc(sizeof(*txd), GFP_NOWAIT); if (txd) { - dma_async_tx_descriptor_init(&txd->tx, &plchan->chan); - txd->tx.flags = flags; - txd->tx.tx_submit = pl08x_tx_submit; + dma_async_tx_descriptor_init(&txd->vd.tx, &plchan->vc.chan); + txd->vd.tx.flags = flags; + txd->vd.tx.tx_submit = pl08x_tx_submit; INIT_LIST_HEAD(&txd->node); INIT_LIST_HEAD(&txd->dsg_list); @@ -1413,7 +1414,7 @@ static struct dma_async_tx_descriptor *pl08x_prep_dma_memcpy( if (ret) return NULL; - return &txd->tx; + return &txd->vd.tx; } static struct dma_async_tx_descriptor *pl08x_prep_slave_sg( @@ -1529,7 +1530,7 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg( if (ret) return NULL; - return &txd->tx; + return &txd->vd.tx; } static int pl08x_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, @@ -1630,11 +1631,11 @@ static void pl08x_ensure_on(struct pl08x_driver_data *pl08x) static void pl08x_unmap_buffers(struct pl08x_txd *txd) { - struct device *dev = txd->tx.chan->device->dev; + struct device *dev = txd->vd.tx.chan->device->dev; struct pl08x_sg *dsg; - if (!(txd->tx.flags & DMA_COMPL_SKIP_SRC_UNMAP)) { - if (txd->tx.flags & DMA_COMPL_SRC_UNMAP_SINGLE) + if (!(txd->vd.tx.flags & DMA_COMPL_SKIP_SRC_UNMAP)) { + if (txd->vd.tx.flags & DMA_COMPL_SRC_UNMAP_SINGLE) list_for_each_entry(dsg, &txd->dsg_list, node) dma_unmap_single(dev, dsg->src_addr, dsg->len, DMA_TO_DEVICE); @@ -1644,8 +1645,8 @@ static void pl08x_unmap_buffers(struct pl08x_txd *txd) DMA_TO_DEVICE); } } - if (!(txd->tx.flags & DMA_COMPL_SKIP_DEST_UNMAP)) { - if (txd->tx.flags & DMA_COMPL_DEST_UNMAP_SINGLE) + if (!(txd->vd.tx.flags & DMA_COMPL_SKIP_DEST_UNMAP)) { + if (txd->vd.tx.flags & DMA_COMPL_DEST_UNMAP_SINGLE) list_for_each_entry(dsg, &txd->dsg_list, node) dma_unmap_single(dev, dsg->dst_addr, dsg->len, DMA_FROM_DEVICE); @@ -1670,8 +1671,8 @@ static void pl08x_tasklet(unsigned long data) while (!list_empty(&head)) { struct pl08x_txd *txd = list_first_entry(&head, struct pl08x_txd, node); - dma_async_tx_callback callback = txd->tx.callback; - void *callback_param = txd->tx.callback_param; + dma_async_tx_callback callback = txd->vd.tx.callback; + void *callback_param = txd->vd.tx.callback_param; list_del(&txd->node); @@ -1732,7 +1733,7 @@ static irqreturn_t pl08x_irq(int irq, void *dev) * reservation. */ pl08x_release_mux(plchan); - dma_cookie_complete(&tx->tx); + dma_cookie_complete(&tx->vd.tx); list_add_tail(&tx->node, &plchan->done_list); /* @@ -1807,8 +1808,8 @@ static int pl08x_dma_init_virtual_channels(struct pl08x_driver_data *pl08x, "initialize virtual channel \"%s\"\n", chan->name); - chan->chan.device = dmadev; - dma_cookie_init(&chan->chan); + chan->vc.chan.device = dmadev; + dma_cookie_init(&chan->vc.chan); spin_lock_init(&chan->lock); INIT_LIST_HEAD(&chan->pend_list); @@ -1817,7 +1818,7 @@ static int pl08x_dma_init_virtual_channels(struct pl08x_driver_data *pl08x, tasklet_init(&chan->tasklet, pl08x_tasklet, (unsigned long) chan); - list_add_tail(&chan->chan.device_node, &dmadev->channels); + list_add_tail(&chan->vc.chan.device_node, &dmadev->channels); } dev_info(&pl08x->adev->dev, "initialized %d virtual %s channels\n", i, slave ? "slave" : "memcpy"); @@ -1830,8 +1831,8 @@ static void pl08x_free_virtual_channels(struct dma_device *dmadev) struct pl08x_dma_chan *next; list_for_each_entry_safe(chan, - next, &dmadev->channels, chan.device_node) { - list_del(&chan->chan.device_node); + next, &dmadev->channels, vc.chan.device_node) { + list_del(&chan->vc.chan.device_node); kfree(chan); } } @@ -1884,7 +1885,7 @@ static int pl08x_debugfs_show(struct seq_file *s, void *data) seq_printf(s, "\nPL08x virtual memcpy channels:\n"); seq_printf(s, "CHANNEL:\tSTATE:\n"); seq_printf(s, "--------\t------\n"); - list_for_each_entry(chan, &pl08x->memcpy.channels, chan.device_node) { + list_for_each_entry(chan, &pl08x->memcpy.channels, vc.chan.device_node) { seq_printf(s, "%s\t\t%s\n", chan->name, pl08x_state_str(chan->state)); } @@ -1892,7 +1893,7 @@ static int pl08x_debugfs_show(struct seq_file *s, void *data) seq_printf(s, "\nPL08x virtual slave channels:\n"); seq_printf(s, "CHANNEL:\tSTATE:\n"); seq_printf(s, "--------\t------\n"); - list_for_each_entry(chan, &pl08x->slave.channels, chan.device_node) { + list_for_each_entry(chan, &pl08x->slave.channels, vc.chan.device_node) { seq_printf(s, "%s\t\t%s\n", chan->name, pl08x_state_str(chan->state)); } -- cgit v1.2.3-70-g09d2 From 083be28a1056eaaebdf116126b9d859348160f45 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sat, 26 May 2012 14:09:53 +0100 Subject: dmaengine: PL08x: use vchan's spinlock Initialize the vchan struct, and use the provided spinlock rather than our own. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/Kconfig | 1 + drivers/dma/amba-pl08x.c | 45 ++++++++++++++++++++------------------------- 2 files changed, 21 insertions(+), 25 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index eb2b60e8e1c..be0dc3b7c40 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -53,6 +53,7 @@ config AMBA_PL08X bool "ARM PrimeCell PL080 or PL081 support" depends on ARM_AMBA && EXPERIMENTAL select DMA_ENGINE + select DMA_VIRTUAL_CHANNELS help Platform has a PL08x DMAC device which can provide DMA engine support diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index 9a0642805b8..398a5da6f43 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -237,7 +237,6 @@ struct pl08x_dma_chan { struct list_head issued_list; struct list_head done_list; struct pl08x_txd *at; - spinlock_t lock; struct pl08x_driver_data *host; enum pl08x_dma_chan_state state; bool slave; @@ -484,7 +483,7 @@ static u32 pl08x_getbytes_chan(struct pl08x_dma_chan *plchan) unsigned long flags; size_t bytes = 0; - spin_lock_irqsave(&plchan->lock, flags); + spin_lock_irqsave(&plchan->vc.lock, flags); ch = plchan->phychan; txd = plchan->at; @@ -543,7 +542,7 @@ static u32 pl08x_getbytes_chan(struct pl08x_dma_chan *plchan) } } - spin_unlock_irqrestore(&plchan->lock, flags); + spin_unlock_irqrestore(&plchan->vc.lock, flags); return bytes; } @@ -673,12 +672,12 @@ static void pl08x_phy_free(struct pl08x_dma_chan *plchan) * Eww. We know this isn't going to deadlock * but lockdep probably doesn't. */ - spin_lock(&next->lock); + spin_lock(&next->vc.lock); /* Re-check the state now that we have the lock */ success = next->state == PL08X_CHAN_WAITING; if (success) pl08x_phy_reassign_start(plchan->phychan, next); - spin_unlock(&next->lock); + spin_unlock(&next->vc.lock); /* If the state changed, try to find another channel */ if (!success) @@ -1125,12 +1124,12 @@ static dma_cookie_t pl08x_tx_submit(struct dma_async_tx_descriptor *tx) unsigned long flags; dma_cookie_t cookie; - spin_lock_irqsave(&plchan->lock, flags); + spin_lock_irqsave(&plchan->vc.lock, flags); cookie = dma_cookie_assign(tx); /* Put this onto the pending list */ list_add_tail(&txd->node, &plchan->pend_list); - spin_unlock_irqrestore(&plchan->lock, flags); + spin_unlock_irqrestore(&plchan->vc.lock, flags); return cookie; } @@ -1318,13 +1317,13 @@ static void pl08x_issue_pending(struct dma_chan *chan) struct pl08x_dma_chan *plchan = to_pl08x_chan(chan); unsigned long flags; - spin_lock_irqsave(&plchan->lock, flags); + spin_lock_irqsave(&plchan->vc.lock, flags); list_splice_tail_init(&plchan->pend_list, &plchan->issued_list); if (!list_empty(&plchan->issued_list)) { if (!plchan->phychan && plchan->state != PL08X_CHAN_WAITING) pl08x_phy_alloc_and_start(plchan); } - spin_unlock_irqrestore(&plchan->lock, flags); + spin_unlock_irqrestore(&plchan->vc.lock, flags); } static int pl08x_prep_channel_resources(struct pl08x_dma_chan *plchan, @@ -1337,9 +1336,9 @@ static int pl08x_prep_channel_resources(struct pl08x_dma_chan *plchan, if (!num_llis) { unsigned long flags; - spin_lock_irqsave(&plchan->lock, flags); + spin_lock_irqsave(&plchan->vc.lock, flags); pl08x_free_txd(pl08x, txd); - spin_unlock_irqrestore(&plchan->lock, flags); + spin_unlock_irqrestore(&plchan->vc.lock, flags); return -EINVAL; } @@ -1551,9 +1550,9 @@ static int pl08x_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, * Anything succeeds on channels with no physical allocation and * no queued transfers. */ - spin_lock_irqsave(&plchan->lock, flags); + spin_lock_irqsave(&plchan->vc.lock, flags); if (!plchan->phychan && !plchan->at) { - spin_unlock_irqrestore(&plchan->lock, flags); + spin_unlock_irqrestore(&plchan->vc.lock, flags); return 0; } @@ -1592,7 +1591,7 @@ static int pl08x_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, break; } - spin_unlock_irqrestore(&plchan->lock, flags); + spin_unlock_irqrestore(&plchan->vc.lock, flags); return ret; } @@ -1664,9 +1663,9 @@ static void pl08x_tasklet(unsigned long data) unsigned long flags; LIST_HEAD(head); - spin_lock_irqsave(&plchan->lock, flags); + spin_lock_irqsave(&plchan->vc.lock, flags); list_splice_tail_init(&plchan->done_list, &head); - spin_unlock_irqrestore(&plchan->lock, flags); + spin_unlock_irqrestore(&plchan->vc.lock, flags); while (!list_empty(&head)) { struct pl08x_txd *txd = list_first_entry(&head, @@ -1681,9 +1680,9 @@ static void pl08x_tasklet(unsigned long data) pl08x_unmap_buffers(txd); /* Free the descriptor */ - spin_lock_irqsave(&plchan->lock, flags); + spin_lock_irqsave(&plchan->vc.lock, flags); pl08x_free_txd(pl08x, txd); - spin_unlock_irqrestore(&plchan->lock, flags); + spin_unlock_irqrestore(&plchan->vc.lock, flags); /* Callback to signal completion */ if (callback) @@ -1724,7 +1723,7 @@ static irqreturn_t pl08x_irq(int irq, void *dev) continue; } - spin_lock(&plchan->lock); + spin_lock(&plchan->vc.lock); tx = plchan->at; if (tx) { plchan->at = NULL; @@ -1745,7 +1744,7 @@ static irqreturn_t pl08x_irq(int irq, void *dev) else pl08x_phy_free(plchan); } - spin_unlock(&plchan->lock); + spin_unlock(&plchan->vc.lock); /* Schedule tasklet on this channel */ tasklet_schedule(&plchan->tasklet); @@ -1808,17 +1807,13 @@ static int pl08x_dma_init_virtual_channels(struct pl08x_driver_data *pl08x, "initialize virtual channel \"%s\"\n", chan->name); - chan->vc.chan.device = dmadev; - dma_cookie_init(&chan->vc.chan); - - spin_lock_init(&chan->lock); INIT_LIST_HEAD(&chan->pend_list); INIT_LIST_HEAD(&chan->issued_list); INIT_LIST_HEAD(&chan->done_list); tasklet_init(&chan->tasklet, pl08x_tasklet, (unsigned long) chan); - list_add_tail(&chan->vc.chan.device_node, &dmadev->channels); + vchan_init(&chan->vc, dmadev); } dev_info(&pl08x->adev->dev, "initialized %d virtual %s channels\n", i, slave ? "slave" : "memcpy"); -- cgit v1.2.3-70-g09d2 From 879f127bb2d0b604cf49f7682c0431d47f42f8f9 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sat, 26 May 2012 14:27:40 +0100 Subject: dmaengine: PL08x: convert to use vchan submitted/issued lists Convert to use the virtual dma channel submitted/issued descriptor lists rather than our own private lists, and use the virtual dma channel support functions to manage these lists. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 64 +++++++++++++----------------------------------- 1 file changed, 17 insertions(+), 47 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index 398a5da6f43..5333a91518e 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -215,8 +215,6 @@ enum pl08x_dma_chan_state { * @name: name of channel * @cd: channel platform data * @runtime_addr: address for RX/TX according to the runtime config - * @pend_list: queued transactions pending on this channel - * @issued_list: issued transactions for this channel * @done_list: list of completed transactions * @at: active transaction on this channel * @lock: a lock for this channel data @@ -233,8 +231,6 @@ struct pl08x_dma_chan { const char *name; const struct pl08x_channel_data *cd; struct dma_slave_config cfg; - struct list_head pend_list; - struct list_head issued_list; struct list_head done_list; struct pl08x_txd *at; struct pl08x_driver_data *host; @@ -357,12 +353,12 @@ static void pl08x_start_next_txd(struct pl08x_dma_chan *plchan) { struct pl08x_driver_data *pl08x = plchan->host; struct pl08x_phy_chan *phychan = plchan->phychan; + struct virt_dma_desc *vd = vchan_next_desc(&plchan->vc); + struct pl08x_txd *txd = to_pl08x_txd(&vd->tx); struct pl08x_lli *lli; - struct pl08x_txd *txd; u32 val; - txd = list_first_entry(&plchan->issued_list, struct pl08x_txd, node); - list_del(&txd->node); + list_del(&txd->vd.node); plchan->at = txd; @@ -524,18 +520,18 @@ static u32 pl08x_getbytes_chan(struct pl08x_dma_chan *plchan) } /* Sum up all queued transactions */ - if (!list_empty(&plchan->issued_list)) { + if (!list_empty(&plchan->vc.desc_issued)) { struct pl08x_txd *txdi; - list_for_each_entry(txdi, &plchan->issued_list, node) { + list_for_each_entry(txdi, &plchan->vc.desc_issued, vd.node) { struct pl08x_sg *dsg; list_for_each_entry(dsg, &txd->dsg_list, node) bytes += dsg->len; } } - if (!list_empty(&plchan->pend_list)) { + if (!list_empty(&plchan->vc.desc_submitted)) { struct pl08x_txd *txdi; - list_for_each_entry(txdi, &plchan->pend_list, node) { + list_for_each_entry(txdi, &plchan->vc.desc_submitted, vd.node) { struct pl08x_sg *dsg; list_for_each_entry(dsg, &txd->dsg_list, node) bytes += dsg->len; @@ -1094,13 +1090,12 @@ static void pl08x_free_txd_list(struct pl08x_driver_data *pl08x, LIST_HEAD(head); struct pl08x_txd *txd; - list_splice_tail_init(&plchan->issued_list, &head); - list_splice_tail_init(&plchan->pend_list, &head); + vchan_get_all_descriptors(&plchan->vc, &head); while (!list_empty(&head)) { - txd = list_first_entry(&head, struct pl08x_txd, node); + txd = list_first_entry(&head, struct pl08x_txd, vd.node); pl08x_release_mux(plchan); - list_del(&txd->node); + list_del(&txd->vd.node); pl08x_free_txd(pl08x, txd); } } @@ -1117,23 +1112,6 @@ static void pl08x_free_chan_resources(struct dma_chan *chan) { } -static dma_cookie_t pl08x_tx_submit(struct dma_async_tx_descriptor *tx) -{ - struct pl08x_dma_chan *plchan = to_pl08x_chan(tx->chan); - struct pl08x_txd *txd = to_pl08x_txd(tx); - unsigned long flags; - dma_cookie_t cookie; - - spin_lock_irqsave(&plchan->vc.lock, flags); - cookie = dma_cookie_assign(tx); - - /* Put this onto the pending list */ - list_add_tail(&txd->node, &plchan->pend_list); - spin_unlock_irqrestore(&plchan->vc.lock, flags); - - return cookie; -} - static struct dma_async_tx_descriptor *pl08x_prep_dma_interrupt( struct dma_chan *chan, unsigned long flags) { @@ -1318,8 +1296,7 @@ static void pl08x_issue_pending(struct dma_chan *chan) unsigned long flags; spin_lock_irqsave(&plchan->vc.lock, flags); - list_splice_tail_init(&plchan->pend_list, &plchan->issued_list); - if (!list_empty(&plchan->issued_list)) { + if (vchan_issue_pending(&plchan->vc)) { if (!plchan->phychan && plchan->state != PL08X_CHAN_WAITING) pl08x_phy_alloc_and_start(plchan); } @@ -1345,16 +1322,11 @@ static int pl08x_prep_channel_resources(struct pl08x_dma_chan *plchan, return 0; } -static struct pl08x_txd *pl08x_get_txd(struct pl08x_dma_chan *plchan, - unsigned long flags) +static struct pl08x_txd *pl08x_get_txd(struct pl08x_dma_chan *plchan) { struct pl08x_txd *txd = kzalloc(sizeof(*txd), GFP_NOWAIT); if (txd) { - dma_async_tx_descriptor_init(&txd->vd.tx, &plchan->vc.chan); - txd->vd.tx.flags = flags; - txd->vd.tx.tx_submit = pl08x_tx_submit; - INIT_LIST_HEAD(&txd->node); INIT_LIST_HEAD(&txd->dsg_list); /* Always enable error and terminal interrupts */ @@ -1377,7 +1349,7 @@ static struct dma_async_tx_descriptor *pl08x_prep_dma_memcpy( struct pl08x_sg *dsg; int ret; - txd = pl08x_get_txd(plchan, flags); + txd = pl08x_get_txd(plchan); if (!txd) { dev_err(&pl08x->adev->dev, "%s no memory for descriptor\n", __func__); @@ -1413,7 +1385,7 @@ static struct dma_async_tx_descriptor *pl08x_prep_dma_memcpy( if (ret) return NULL; - return &txd->vd.tx; + return vchan_tx_prep(&plchan->vc, &txd->vd, flags); } static struct dma_async_tx_descriptor *pl08x_prep_slave_sg( @@ -1435,7 +1407,7 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg( dev_dbg(&pl08x->adev->dev, "%s prepare transaction of %d bytes from %s\n", __func__, sg_dma_len(sgl), plchan->name); - txd = pl08x_get_txd(plchan, flags); + txd = pl08x_get_txd(plchan); if (!txd) { dev_err(&pl08x->adev->dev, "%s no txd\n", __func__); return NULL; @@ -1529,7 +1501,7 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg( if (ret) return NULL; - return &txd->vd.tx; + return vchan_tx_prep(&plchan->vc, &txd->vd, flags); } static int pl08x_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, @@ -1739,7 +1711,7 @@ static irqreturn_t pl08x_irq(int irq, void *dev) * And start the next descriptor (if any), * otherwise free this channel. */ - if (!list_empty(&plchan->issued_list)) + if (vchan_next_desc(&plchan->vc)) pl08x_start_next_txd(plchan); else pl08x_phy_free(plchan); @@ -1807,8 +1779,6 @@ static int pl08x_dma_init_virtual_channels(struct pl08x_driver_data *pl08x, "initialize virtual channel \"%s\"\n", chan->name); - INIT_LIST_HEAD(&chan->pend_list); - INIT_LIST_HEAD(&chan->issued_list); INIT_LIST_HEAD(&chan->done_list); tasklet_init(&chan->tasklet, pl08x_tasklet, (unsigned long) chan); -- cgit v1.2.3-70-g09d2 From 18536134abd6b8157d5cb11162606cc264d07232 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sat, 26 May 2012 14:42:23 +0100 Subject: dmaengine: PL08x: convert to use vchan done list Convert to use the virtual dma channel done list, tasklet, and descriptor freeing. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 135 +++++++++++++++++++---------------------------- 1 file changed, 54 insertions(+), 81 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index 5333a91518e..6a35e37d14b 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -167,16 +167,16 @@ struct pl08x_sg { /** * struct pl08x_txd - wrapper for struct dma_async_tx_descriptor * @vd: virtual DMA descriptor - * @node: node for txd list for channels * @dsg_list: list of children sg's * @llis_bus: DMA memory address (physical) start for the LLIs * @llis_va: virtual memory address start for the LLIs * @cctl: control reg values for current txd * @ccfg: config reg values for current txd + * @done: this marks completed descriptors, which should not have their + * mux released. */ struct pl08x_txd { struct virt_dma_desc vd; - struct list_head node; struct list_head dsg_list; dma_addr_t llis_bus; struct pl08x_lli *llis_va; @@ -187,6 +187,7 @@ struct pl08x_txd { * trigger this txd. Other registers are in llis_va[0]. */ u32 ccfg; + bool done; }; /** @@ -211,11 +212,9 @@ enum pl08x_dma_chan_state { * struct pl08x_dma_chan - this structure wraps a DMA ENGINE channel * @vc: wrappped virtual channel * @phychan: the physical channel utilized by this channel, if there is one - * @tasklet: tasklet scheduled by the IRQ to handle actual work etc * @name: name of channel * @cd: channel platform data * @runtime_addr: address for RX/TX according to the runtime config - * @done_list: list of completed transactions * @at: active transaction on this channel * @lock: a lock for this channel data * @host: a pointer to the host (internal use) @@ -227,11 +226,9 @@ enum pl08x_dma_chan_state { struct pl08x_dma_chan { struct virt_dma_chan vc; struct pl08x_phy_chan *phychan; - struct tasklet_struct tasklet; const char *name; const struct pl08x_channel_data *cd; struct dma_slave_config cfg; - struct list_head done_list; struct pl08x_txd *at; struct pl08x_driver_data *host; enum pl08x_dma_chan_state state; @@ -1084,6 +1081,52 @@ static void pl08x_free_txd(struct pl08x_driver_data *pl08x, kfree(txd); } +static void pl08x_unmap_buffers(struct pl08x_txd *txd) +{ + struct device *dev = txd->vd.tx.chan->device->dev; + struct pl08x_sg *dsg; + + if (!(txd->vd.tx.flags & DMA_COMPL_SKIP_SRC_UNMAP)) { + if (txd->vd.tx.flags & DMA_COMPL_SRC_UNMAP_SINGLE) + list_for_each_entry(dsg, &txd->dsg_list, node) + dma_unmap_single(dev, dsg->src_addr, dsg->len, + DMA_TO_DEVICE); + else { + list_for_each_entry(dsg, &txd->dsg_list, node) + dma_unmap_page(dev, dsg->src_addr, dsg->len, + DMA_TO_DEVICE); + } + } + if (!(txd->vd.tx.flags & DMA_COMPL_SKIP_DEST_UNMAP)) { + if (txd->vd.tx.flags & DMA_COMPL_DEST_UNMAP_SINGLE) + list_for_each_entry(dsg, &txd->dsg_list, node) + dma_unmap_single(dev, dsg->dst_addr, dsg->len, + DMA_FROM_DEVICE); + else + list_for_each_entry(dsg, &txd->dsg_list, node) + dma_unmap_page(dev, dsg->dst_addr, dsg->len, + DMA_FROM_DEVICE); + } +} + +static void pl08x_desc_free(struct virt_dma_desc *vd) +{ + struct pl08x_txd *txd = to_pl08x_txd(&vd->tx); + struct pl08x_dma_chan *plchan = to_pl08x_chan(vd->tx.chan); + struct pl08x_driver_data *pl08x = plchan->host; + unsigned long flags; + + if (!plchan->slave) + pl08x_unmap_buffers(txd); + + if (!txd->done) + pl08x_release_mux(plchan); + + spin_lock_irqsave(&pl08x->lock, flags); + pl08x_free_txd(plchan->host, txd); + spin_unlock_irqrestore(&pl08x->lock, flags); +} + static void pl08x_free_txd_list(struct pl08x_driver_data *pl08x, struct pl08x_dma_chan *plchan) { @@ -1094,9 +1137,8 @@ static void pl08x_free_txd_list(struct pl08x_driver_data *pl08x, while (!list_empty(&head)) { txd = list_first_entry(&head, struct pl08x_txd, vd.node); - pl08x_release_mux(plchan); list_del(&txd->vd.node); - pl08x_free_txd(pl08x, txd); + pl08x_desc_free(&txd->vd); } } @@ -1541,9 +1583,7 @@ static int pl08x_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, } /* Dequeue jobs and free LLIs */ if (plchan->at) { - /* Killing this one off, release its mux */ - pl08x_release_mux(plchan); - pl08x_free_txd(pl08x, plchan->at); + pl08x_desc_free(&plchan->at->vd); plchan->at = NULL; } /* Dequeue jobs not yet fired as well */ @@ -1600,68 +1640,6 @@ static void pl08x_ensure_on(struct pl08x_driver_data *pl08x) writel(PL080_CONFIG_ENABLE, pl08x->base + PL080_CONFIG); } -static void pl08x_unmap_buffers(struct pl08x_txd *txd) -{ - struct device *dev = txd->vd.tx.chan->device->dev; - struct pl08x_sg *dsg; - - if (!(txd->vd.tx.flags & DMA_COMPL_SKIP_SRC_UNMAP)) { - if (txd->vd.tx.flags & DMA_COMPL_SRC_UNMAP_SINGLE) - list_for_each_entry(dsg, &txd->dsg_list, node) - dma_unmap_single(dev, dsg->src_addr, dsg->len, - DMA_TO_DEVICE); - else { - list_for_each_entry(dsg, &txd->dsg_list, node) - dma_unmap_page(dev, dsg->src_addr, dsg->len, - DMA_TO_DEVICE); - } - } - if (!(txd->vd.tx.flags & DMA_COMPL_SKIP_DEST_UNMAP)) { - if (txd->vd.tx.flags & DMA_COMPL_DEST_UNMAP_SINGLE) - list_for_each_entry(dsg, &txd->dsg_list, node) - dma_unmap_single(dev, dsg->dst_addr, dsg->len, - DMA_FROM_DEVICE); - else - list_for_each_entry(dsg, &txd->dsg_list, node) - dma_unmap_page(dev, dsg->dst_addr, dsg->len, - DMA_FROM_DEVICE); - } -} - -static void pl08x_tasklet(unsigned long data) -{ - struct pl08x_dma_chan *plchan = (struct pl08x_dma_chan *) data; - struct pl08x_driver_data *pl08x = plchan->host; - unsigned long flags; - LIST_HEAD(head); - - spin_lock_irqsave(&plchan->vc.lock, flags); - list_splice_tail_init(&plchan->done_list, &head); - spin_unlock_irqrestore(&plchan->vc.lock, flags); - - while (!list_empty(&head)) { - struct pl08x_txd *txd = list_first_entry(&head, - struct pl08x_txd, node); - dma_async_tx_callback callback = txd->vd.tx.callback; - void *callback_param = txd->vd.tx.callback_param; - - list_del(&txd->node); - - /* Don't try to unmap buffers on slave channels */ - if (!plchan->slave) - pl08x_unmap_buffers(txd); - - /* Free the descriptor */ - spin_lock_irqsave(&plchan->vc.lock, flags); - pl08x_free_txd(pl08x, txd); - spin_unlock_irqrestore(&plchan->vc.lock, flags); - - /* Callback to signal completion */ - if (callback) - callback(callback_param); - } -} - static irqreturn_t pl08x_irq(int irq, void *dev) { struct pl08x_driver_data *pl08x = dev; @@ -1704,8 +1682,8 @@ static irqreturn_t pl08x_irq(int irq, void *dev) * reservation. */ pl08x_release_mux(plchan); - dma_cookie_complete(&tx->vd.tx); - list_add_tail(&tx->node, &plchan->done_list); + tx->done = true; + vchan_cookie_complete(&tx->vd); /* * And start the next descriptor (if any), @@ -1718,8 +1696,6 @@ static irqreturn_t pl08x_irq(int irq, void *dev) } spin_unlock(&plchan->vc.lock); - /* Schedule tasklet on this channel */ - tasklet_schedule(&plchan->tasklet); mask |= (1 << i); } } @@ -1779,10 +1755,7 @@ static int pl08x_dma_init_virtual_channels(struct pl08x_driver_data *pl08x, "initialize virtual channel \"%s\"\n", chan->name); - INIT_LIST_HEAD(&chan->done_list); - tasklet_init(&chan->tasklet, pl08x_tasklet, - (unsigned long) chan); - + chan->vc.desc_free = pl08x_desc_free; vchan_init(&chan->vc, dmadev); } dev_info(&pl08x->adev->dev, "initialized %d virtual %s channels\n", -- cgit v1.2.3-70-g09d2 From 06e885b735717d1074dec13ae8b8d15edcd63255 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sat, 26 May 2012 15:05:52 +0100 Subject: dmaengine: PL08x: fix tx_status function to return correct residue Now that we're converted to use the generic vchan support, we can fix the residue return from tx_status to be compliant with dmaengine. This returns the number of bytes remaining for the _specified_ cookie, not the number of bytes in all pending transfers on the channel. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 61 +++++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 27 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index 6a35e37d14b..c42c7ef6b93 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -473,10 +473,8 @@ static u32 pl08x_getbytes_chan(struct pl08x_dma_chan *plchan) { struct pl08x_phy_chan *ch; struct pl08x_txd *txd; - unsigned long flags; size_t bytes = 0; - spin_lock_irqsave(&plchan->vc.lock, flags); ch = plchan->phychan; txd = plchan->at; @@ -516,27 +514,6 @@ static u32 pl08x_getbytes_chan(struct pl08x_dma_chan *plchan) } } - /* Sum up all queued transactions */ - if (!list_empty(&plchan->vc.desc_issued)) { - struct pl08x_txd *txdi; - list_for_each_entry(txdi, &plchan->vc.desc_issued, vd.node) { - struct pl08x_sg *dsg; - list_for_each_entry(dsg, &txd->dsg_list, node) - bytes += dsg->len; - } - } - - if (!list_empty(&plchan->vc.desc_submitted)) { - struct pl08x_txd *txdi; - list_for_each_entry(txdi, &plchan->vc.desc_submitted, vd.node) { - struct pl08x_sg *dsg; - list_for_each_entry(dsg, &txd->dsg_list, node) - bytes += dsg->len; - } - } - - spin_unlock_irqrestore(&plchan->vc.lock, flags); - return bytes; } @@ -1171,23 +1148,53 @@ static enum dma_status pl08x_dma_tx_status(struct dma_chan *chan, dma_cookie_t cookie, struct dma_tx_state *txstate) { struct pl08x_dma_chan *plchan = to_pl08x_chan(chan); + struct virt_dma_desc *vd; + unsigned long flags; enum dma_status ret; + size_t bytes = 0; ret = dma_cookie_status(chan, cookie, txstate); if (ret == DMA_SUCCESS) return ret; + /* + * There's no point calculating the residue if there's + * no txstate to store the value. + */ + if (!txstate) { + if (plchan->state == PL08X_CHAN_PAUSED) + ret = DMA_PAUSED; + return ret; + } + + spin_lock_irqsave(&plchan->vc.lock, flags); + ret = dma_cookie_status(chan, cookie, txstate); + if (ret != DMA_SUCCESS) { + vd = vchan_find_desc(&plchan->vc, cookie); + if (vd) { + /* On the issued list, so hasn't been processed yet */ + struct pl08x_txd *txd = to_pl08x_txd(&vd->tx); + struct pl08x_sg *dsg; + + list_for_each_entry(dsg, &txd->dsg_list, node) + bytes += dsg->len; + } else { + bytes = pl08x_getbytes_chan(plchan); + } + } + spin_unlock_irqrestore(&plchan->vc.lock, flags); + /* * This cookie not complete yet * Get number of bytes left in the active transactions and queue */ - dma_set_residue(txstate, pl08x_getbytes_chan(plchan)); + dma_set_residue(txstate, bytes); - if (plchan->state == PL08X_CHAN_PAUSED) - return DMA_PAUSED; + if (plchan->state == PL08X_CHAN_PAUSED && ret == DMA_IN_PROGRESS) + ret = DMA_PAUSED; /* Whether waiting or running, we're in progress */ - return DMA_IN_PROGRESS; + return ret; } /* PrimeCell DMA extension */ -- cgit v1.2.3-70-g09d2 From aa4afb754d42be064ae649b74a599b9d9d04ac57 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sat, 26 May 2012 15:43:00 +0100 Subject: dmaengine: PL08x: get rid of pl08x_prep_channel_resources This function is now unnecessary; we can move its internals inline instead. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index c42c7ef6b93..9297240cae3 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -1352,25 +1352,6 @@ static void pl08x_issue_pending(struct dma_chan *chan) spin_unlock_irqrestore(&plchan->vc.lock, flags); } -static int pl08x_prep_channel_resources(struct pl08x_dma_chan *plchan, - struct pl08x_txd *txd) -{ - struct pl08x_driver_data *pl08x = plchan->host; - int num_llis; - - num_llis = pl08x_fill_llis_for_desc(pl08x, txd); - if (!num_llis) { - unsigned long flags; - - spin_lock_irqsave(&plchan->vc.lock, flags); - pl08x_free_txd(pl08x, txd); - spin_unlock_irqrestore(&plchan->vc.lock, flags); - - return -EINVAL; - } - return 0; -} - static struct pl08x_txd *pl08x_get_txd(struct pl08x_dma_chan *plchan) { struct pl08x_txd *txd = kzalloc(sizeof(*txd), GFP_NOWAIT); @@ -1430,9 +1411,11 @@ static struct dma_async_tx_descriptor *pl08x_prep_dma_memcpy( txd->cctl |= pl08x_select_bus(pl08x->mem_buses, pl08x->mem_buses); - ret = pl08x_prep_channel_resources(plchan, txd); - if (ret) + ret = pl08x_fill_llis_for_desc(plchan->host, txd); + if (!ret) { + pl08x_free_txd(pl08x, txd); return NULL; + } return vchan_tx_prep(&plchan->vc, &txd->vd, flags); } @@ -1546,9 +1529,12 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg( } } - ret = pl08x_prep_channel_resources(plchan, txd); - if (ret) + ret = pl08x_fill_llis_for_desc(plchan->host, txd); + if (!ret) { + pl08x_release_mux(plchan); + pl08x_free_txd(pl08x, txd); return NULL; + } return vchan_tx_prep(&plchan->vc, &txd->vd, flags); } -- cgit v1.2.3-70-g09d2 From 70f3ff434d7fc9a3cea5ebbb8a8d7aaa4c906125 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sat, 26 May 2012 16:33:39 +0100 Subject: dmaengine: PL08x: get rid of write only pool_ctr and free_txd locking The free function says the pl08x lock should be taken before calling it. However, the DMA pool allocation/freeing is already properly locked. The only thing that would need this is pool_ctr, which happens to be a write-only variable. Let's get rid of this, and eliminate any need for additional locking here. Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 12 ------------ 1 file changed, 12 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index 9297240cae3..a5d85b101b8 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -247,7 +247,6 @@ struct pl08x_dma_chan { * @pd: platform data passed in from the platform/machine * @phy_chans: array of data for the physical channels * @pool: a pool for the LLI descriptors - * @pool_ctr: counter of LLIs in the pool * @lli_buses: bitmask to or in to LLI pointer selecting AHB port for LLI * fetches * @mem_buses: set to indicate memory transfers on AHB2. @@ -262,7 +261,6 @@ struct pl08x_driver_data { struct pl08x_platform_data *pd; struct pl08x_phy_chan *phy_chans; struct dma_pool *pool; - int pool_ctr; u8 lli_buses; u8 mem_buses; }; @@ -821,8 +819,6 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x, return 0; } - pl08x->pool_ctr++; - bd.txd = txd; bd.lli_bus = (pl08x->lli_buses & PL08X_AHB2) ? PL080_LLI_LM_AHB2 : 0; cctl = txd->cctl; @@ -1038,18 +1034,14 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x, return num_llis; } -/* You should call this with the struct pl08x lock held */ static void pl08x_free_txd(struct pl08x_driver_data *pl08x, struct pl08x_txd *txd) { struct pl08x_sg *dsg, *_dsg; - /* Free the LLI */ if (txd->llis_va) dma_pool_free(pl08x->pool, txd->llis_va, txd->llis_bus); - pl08x->pool_ctr--; - list_for_each_entry_safe(dsg, _dsg, &txd->dsg_list, node) { list_del(&dsg->node); kfree(dsg); @@ -1090,8 +1082,6 @@ static void pl08x_desc_free(struct virt_dma_desc *vd) { struct pl08x_txd *txd = to_pl08x_txd(&vd->tx); struct pl08x_dma_chan *plchan = to_pl08x_chan(vd->tx.chan); - struct pl08x_driver_data *pl08x = plchan->host; - unsigned long flags; if (!plchan->slave) pl08x_unmap_buffers(txd); @@ -1099,9 +1089,7 @@ static void pl08x_desc_free(struct virt_dma_desc *vd) if (!txd->done) pl08x_release_mux(plchan); - spin_lock_irqsave(&pl08x->lock, flags); pl08x_free_txd(plchan->host, txd); - spin_unlock_irqrestore(&pl08x->lock, flags); } static void pl08x_free_txd_list(struct pl08x_driver_data *pl08x, -- cgit v1.2.3-70-g09d2 From a068682cd69461911407411b4198248a87c583e2 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sat, 26 May 2012 17:00:49 +0100 Subject: dmaengine: PL08x: ensure all descriptors are freed when channel is released Ensure all queued descriptors are freed when the channel is released, ensuring we don't leak memory Acked-by: Linus Walleij Tested-by: Linus Walleij Signed-off-by: Russell King --- drivers/dma/amba-pl08x.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/dma') diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index a5d85b101b8..6fbeebb9486 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -1117,6 +1117,8 @@ static int pl08x_alloc_chan_resources(struct dma_chan *chan) static void pl08x_free_chan_resources(struct dma_chan *chan) { + /* Ensure all queued descriptors are freed */ + vchan_free_chan_resources(to_virt_chan(chan)); } static struct dma_async_tx_descriptor *pl08x_prep_dma_interrupt( -- cgit v1.2.3-70-g09d2 From 63fe23c34e1c18725bd1459897bf3a46335d88b7 Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 10 May 2012 23:45:24 +0100 Subject: dmaengine: sa11x0-dma: fix DMA residue support The semantics now implemented are: - If the cookie has completed successfully, the residue will be zero. - If the cookie is in progress or the channel is paused, it will be the number of bytes yet to be transferred. [*] - If the cookie is queued, it will be the number of bytes in the descriptor. * - where this is the number of bytes yet to be transferred to/from RAM. Signed-off-by: Russell King --- drivers/dma/sa11x0-dma.c | 45 +++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/sa11x0-dma.c b/drivers/dma/sa11x0-dma.c index 5f1d2e67083..db4fcbd80c7 100644 --- a/drivers/dma/sa11x0-dma.c +++ b/drivers/dma/sa11x0-dma.c @@ -416,27 +416,47 @@ static enum dma_status sa11x0_dma_tx_status(struct dma_chan *chan, struct sa11x0_dma_chan *c = to_sa11x0_dma_chan(chan); struct sa11x0_dma_dev *d = to_sa11x0_dma(chan->device); struct sa11x0_dma_phy *p; - struct sa11x0_dma_desc *txd; + struct virt_dma_desc *vd; unsigned long flags; enum dma_status ret; - size_t bytes = 0; ret = dma_cookie_status(&c->vc.chan, cookie, state); if (ret == DMA_SUCCESS) return ret; + if (!state) + return c->status; + spin_lock_irqsave(&c->vc.lock, flags); p = c->phy; - ret = c->status; - if (p) { - dma_addr_t addr = sa11x0_dma_pos(p); - dev_vdbg(d->slave.dev, "tx_status: addr:%x\n", addr); + /* + * If the cookie is on our issue queue, then the residue is + * its total size. + */ + vd = vchan_find_desc(&c->vc, cookie); + if (vd) { + state->residue = container_of(vd, struct sa11x0_dma_desc, vd)->size; + } else if (!p) { + state->residue = 0; + } else { + struct sa11x0_dma_desc *txd; + size_t bytes = 0; + + if (p->txd_done && p->txd_done->vd.tx.cookie == cookie) + txd = p->txd_done; + else if (p->txd_load && p->txd_load->vd.tx.cookie == cookie) + txd = p->txd_load; + else + txd = NULL; - txd = p->txd_done; + ret = c->status; if (txd) { + dma_addr_t addr = sa11x0_dma_pos(p); unsigned i; + dev_vdbg(d->slave.dev, "tx_status: addr:%x\n", addr); + for (i = 0; i < txd->sglen; i++) { dev_vdbg(d->slave.dev, "tx_status: [%u] %x+%x\n", i, txd->sg[i].addr, txd->sg[i].len); @@ -459,18 +479,11 @@ static enum dma_status sa11x0_dma_tx_status(struct dma_chan *chan, bytes += txd->sg[i].len; } } - if (txd != p->txd_load && p->txd_load) - bytes += p->txd_load->size; - } - list_for_each_entry(txd, &c->vc.desc_issued, vd.node) { - bytes += txd->size; + state->residue = bytes; } spin_unlock_irqrestore(&c->vc.lock, flags); - if (state) - state->residue = bytes; - - dev_vdbg(d->slave.dev, "tx_status: bytes 0x%zx\n", bytes); + dev_vdbg(d->slave.dev, "tx_status: bytes 0x%zx\n", state->residue); return ret; } -- cgit v1.2.3-70-g09d2 From d94443256f8f19732f0b95f65ec344c17751aa15 Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 15 May 2012 18:34:33 +0100 Subject: dmaengine: sa11x0-dma: add cyclic DMA support Add support for cyclic DMA on sa11x0 platforms. This follows the discussed behaviour that the callback will be called at some point after period expires, and may coalesce multiple period expiries into one callback (due to the tasklet behaviour.) Signed-off-by: Russell King --- drivers/dma/sa11x0-dma.c | 108 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 92 insertions(+), 16 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/sa11x0-dma.c b/drivers/dma/sa11x0-dma.c index db4fcbd80c7..f5a73606217 100644 --- a/drivers/dma/sa11x0-dma.c +++ b/drivers/dma/sa11x0-dma.c @@ -78,6 +78,8 @@ struct sa11x0_dma_desc { u32 ddar; size_t size; + unsigned period; + bool cyclic; unsigned sglen; struct sa11x0_dma_sg sg[0]; @@ -178,19 +180,24 @@ static void noinline sa11x0_dma_start_sg(struct sa11x0_dma_phy *p, return; if (p->sg_load == txd->sglen) { - struct sa11x0_dma_desc *txn = sa11x0_dma_next_desc(c); + if (!txd->cyclic) { + struct sa11x0_dma_desc *txn = sa11x0_dma_next_desc(c); - /* - * We have reached the end of the current descriptor. - * Peek at the next descriptor, and if compatible with - * the current, start processing it. - */ - if (txn && txn->ddar == txd->ddar) { - txd = txn; - sa11x0_dma_start_desc(p, txn); + /* + * We have reached the end of the current descriptor. + * Peek at the next descriptor, and if compatible with + * the current, start processing it. + */ + if (txn && txn->ddar == txd->ddar) { + txd = txn; + sa11x0_dma_start_desc(p, txn); + } else { + p->txd_load = NULL; + return; + } } else { - p->txd_load = NULL; - return; + /* Cyclic: reset back to beginning */ + p->sg_load = 0; } } @@ -224,13 +231,21 @@ static void noinline sa11x0_dma_complete(struct sa11x0_dma_phy *p, struct sa11x0_dma_desc *txd = p->txd_done; if (++p->sg_done == txd->sglen) { - vchan_cookie_complete(&txd->vd); + if (!txd->cyclic) { + vchan_cookie_complete(&txd->vd); - p->sg_done = 0; - p->txd_done = p->txd_load; + p->sg_done = 0; + p->txd_done = p->txd_load; + + if (!p->txd_done) + tasklet_schedule(&p->dev->task); + } else { + if ((p->sg_done % txd->period) == 0) + vchan_cyclic_callback(&txd->vd); - if (!p->txd_done) - tasklet_schedule(&p->dev->task); + /* Cyclic: reset back to beginning */ + p->sg_done = 0; + } } sa11x0_dma_start_sg(p, c); @@ -597,6 +612,65 @@ static struct dma_async_tx_descriptor *sa11x0_dma_prep_slave_sg( return vchan_tx_prep(&c->vc, &txd->vd, flags); } +static struct dma_async_tx_descriptor *sa11x0_dma_prep_dma_cyclic( + struct dma_chan *chan, dma_addr_t addr, size_t size, size_t period, + enum dma_transfer_direction dir, void *context) +{ + struct sa11x0_dma_chan *c = to_sa11x0_dma_chan(chan); + struct sa11x0_dma_desc *txd; + unsigned i, j, k, sglen, sgperiod; + + /* SA11x0 channels can only operate in their native direction */ + if (dir != (c->ddar & DDAR_RW ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV)) { + dev_err(chan->device->dev, "vchan %p: bad DMA direction: DDAR:%08x dir:%u\n", + &c->vc, c->ddar, dir); + return NULL; + } + + sgperiod = DIV_ROUND_UP(period, DMA_MAX_SIZE & ~DMA_ALIGN); + sglen = size * sgperiod / period; + + /* Do not allow zero-sized txds */ + if (sglen == 0) + return NULL; + + txd = kzalloc(sizeof(*txd) + sglen * sizeof(txd->sg[0]), GFP_ATOMIC); + if (!txd) { + dev_dbg(chan->device->dev, "vchan %p: kzalloc failed\n", &c->vc); + return NULL; + } + + for (i = k = 0; i < size / period; i++) { + size_t tlen, len = period; + + for (j = 0; j < sgperiod; j++, k++) { + tlen = len; + + if (tlen > DMA_MAX_SIZE) { + unsigned mult = DIV_ROUND_UP(tlen, DMA_MAX_SIZE & ~DMA_ALIGN); + tlen = (tlen / mult) & ~DMA_ALIGN; + } + + txd->sg[k].addr = addr; + txd->sg[k].len = tlen; + addr += tlen; + len -= tlen; + } + + WARN_ON(len != 0); + } + + WARN_ON(k != sglen); + + txd->ddar = c->ddar; + txd->size = size; + txd->sglen = sglen; + txd->cyclic = 1; + txd->period = sgperiod; + + return vchan_tx_prep(&c->vc, &txd->vd, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); +} + static int sa11x0_dma_slave_config(struct sa11x0_dma_chan *c, struct dma_slave_config *cfg) { u32 ddar = c->ddar & ((0xf << 4) | DDAR_RW); @@ -867,7 +941,9 @@ static int __devinit sa11x0_dma_probe(struct platform_device *pdev) } dma_cap_set(DMA_SLAVE, d->slave.cap_mask); + dma_cap_set(DMA_CYCLIC, d->slave.cap_mask); d->slave.device_prep_slave_sg = sa11x0_dma_prep_slave_sg; + d->slave.device_prep_dma_cyclic = sa11x0_dma_prep_dma_cyclic; ret = sa11x0_dma_init_dmadev(&d->slave, &pdev->dev); if (ret) { dev_warn(d->slave.dev, "failed to register slave async device: %d\n", -- cgit v1.2.3-70-g09d2 From 7bedaa5537604f34d1d63c5ec7891e559d2a61ed Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 13 Apr 2012 12:10:24 +0100 Subject: dmaengine: add OMAP DMA engine driver Tested-by: Tony Lindgren Signed-off-by: Russell King --- drivers/dma/Kconfig | 6 + drivers/dma/Makefile | 1 + drivers/dma/omap-dma.c | 524 +++++++++++++++++++++++++++++++++++++++++++++++ include/linux/omap-dma.h | 22 ++ 4 files changed, 553 insertions(+) create mode 100644 drivers/dma/omap-dma.c create mode 100644 include/linux/omap-dma.h (limited to 'drivers/dma') diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index eb2b60e8e1c..8be3bf68c0b 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -261,6 +261,12 @@ config DMA_SA11X0 SA-1110 SoCs. This DMA engine can only be used with on-chip devices. +config DMA_OMAP + tristate "OMAP DMA support" + depends on ARCH_OMAP + select DMA_ENGINE + select DMA_VIRTUAL_CHANNELS + config DMA_ENGINE bool diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index fc05f7ddac7..ddc291a2116 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -29,3 +29,4 @@ obj-$(CONFIG_PCH_DMA) += pch_dma.o obj-$(CONFIG_AMBA_PL08X) += amba-pl08x.o obj-$(CONFIG_EP93XX_DMA) += ep93xx_dma.o obj-$(CONFIG_DMA_SA11X0) += sa11x0-dma.o +obj-$(CONFIG_DMA_OMAP) += omap-dma.o diff --git a/drivers/dma/omap-dma.c b/drivers/dma/omap-dma.c new file mode 100644 index 00000000000..fe33ddd8eb0 --- /dev/null +++ b/drivers/dma/omap-dma.c @@ -0,0 +1,524 @@ +/* + * OMAP DMAengine support + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "virt-dma.h" +#include + +struct omap_dmadev { + struct dma_device ddev; + spinlock_t lock; + struct tasklet_struct task; + struct list_head pending; +}; + +struct omap_chan { + struct virt_dma_chan vc; + struct list_head node; + + struct dma_slave_config cfg; + unsigned dma_sig; + + int dma_ch; + struct omap_desc *desc; + unsigned sgidx; +}; + +struct omap_sg { + dma_addr_t addr; + uint32_t en; /* number of elements (24-bit) */ + uint32_t fn; /* number of frames (16-bit) */ +}; + +struct omap_desc { + struct virt_dma_desc vd; + enum dma_transfer_direction dir; + dma_addr_t dev_addr; + + uint8_t es; /* OMAP_DMA_DATA_TYPE_xxx */ + uint8_t sync_mode; /* OMAP_DMA_SYNC_xxx */ + uint8_t sync_type; /* OMAP_DMA_xxx_SYNC* */ + uint8_t periph_port; /* Peripheral port */ + + unsigned sglen; + struct omap_sg sg[0]; +}; + +static const unsigned es_bytes[] = { + [OMAP_DMA_DATA_TYPE_S8] = 1, + [OMAP_DMA_DATA_TYPE_S16] = 2, + [OMAP_DMA_DATA_TYPE_S32] = 4, +}; + +static inline struct omap_dmadev *to_omap_dma_dev(struct dma_device *d) +{ + return container_of(d, struct omap_dmadev, ddev); +} + +static inline struct omap_chan *to_omap_dma_chan(struct dma_chan *c) +{ + return container_of(c, struct omap_chan, vc.chan); +} + +static inline struct omap_desc *to_omap_dma_desc(struct dma_async_tx_descriptor *t) +{ + return container_of(t, struct omap_desc, vd.tx); +} + +static void omap_dma_desc_free(struct virt_dma_desc *vd) +{ + kfree(container_of(vd, struct omap_desc, vd)); +} + +static void omap_dma_start_sg(struct omap_chan *c, struct omap_desc *d, + unsigned idx) +{ + struct omap_sg *sg = d->sg + idx; + + if (d->dir == DMA_DEV_TO_MEM) + omap_set_dma_dest_params(c->dma_ch, OMAP_DMA_PORT_EMIFF, + OMAP_DMA_AMODE_POST_INC, sg->addr, 0, 0); + else + omap_set_dma_src_params(c->dma_ch, OMAP_DMA_PORT_EMIFF, + OMAP_DMA_AMODE_POST_INC, sg->addr, 0, 0); + + omap_set_dma_transfer_params(c->dma_ch, d->es, sg->en, sg->fn, + d->sync_mode, c->dma_sig, d->sync_type); + + omap_start_dma(c->dma_ch); +} + +static void omap_dma_start_desc(struct omap_chan *c) +{ + struct virt_dma_desc *vd = vchan_next_desc(&c->vc); + struct omap_desc *d; + + if (!vd) { + c->desc = NULL; + return; + } + + list_del(&vd->node); + + c->desc = d = to_omap_dma_desc(&vd->tx); + c->sgidx = 0; + + if (d->dir == DMA_DEV_TO_MEM) + omap_set_dma_src_params(c->dma_ch, d->periph_port, + OMAP_DMA_AMODE_CONSTANT, d->dev_addr, 0, 0); + else + omap_set_dma_dest_params(c->dma_ch, d->periph_port, + OMAP_DMA_AMODE_CONSTANT, d->dev_addr, 0, 0); + + omap_dma_start_sg(c, d, 0); +} + +static void omap_dma_callback(int ch, u16 status, void *data) +{ + struct omap_chan *c = data; + struct omap_desc *d; + unsigned long flags; + + spin_lock_irqsave(&c->vc.lock, flags); + d = c->desc; + if (d) { + if (++c->sgidx < d->sglen) { + omap_dma_start_sg(c, d, c->sgidx); + } else { + omap_dma_start_desc(c); + vchan_cookie_complete(&d->vd); + } + } + spin_unlock_irqrestore(&c->vc.lock, flags); +} + +/* + * This callback schedules all pending channels. We could be more + * clever here by postponing allocation of the real DMA channels to + * this point, and freeing them when our virtual channel becomes idle. + * + * We would then need to deal with 'all channels in-use' + */ +static void omap_dma_sched(unsigned long data) +{ + struct omap_dmadev *d = (struct omap_dmadev *)data; + LIST_HEAD(head); + + spin_lock_irq(&d->lock); + list_splice_tail_init(&d->pending, &head); + spin_unlock_irq(&d->lock); + + while (!list_empty(&head)) { + struct omap_chan *c = list_first_entry(&head, + struct omap_chan, node); + + spin_lock_irq(&c->vc.lock); + list_del_init(&c->node); + omap_dma_start_desc(c); + spin_unlock_irq(&c->vc.lock); + } +} + +static int omap_dma_alloc_chan_resources(struct dma_chan *chan) +{ + struct omap_chan *c = to_omap_dma_chan(chan); + + dev_info(c->vc.chan.device->dev, "allocating channel for %u\n", c->dma_sig); + + return omap_request_dma(c->dma_sig, "DMA engine", + omap_dma_callback, c, &c->dma_ch); +} + +static void omap_dma_free_chan_resources(struct dma_chan *chan) +{ + struct omap_chan *c = to_omap_dma_chan(chan); + + vchan_free_chan_resources(&c->vc); + omap_free_dma(c->dma_ch); + + dev_info(c->vc.chan.device->dev, "freeing channel for %u\n", c->dma_sig); +} + +static enum dma_status omap_dma_tx_status(struct dma_chan *chan, + dma_cookie_t cookie, struct dma_tx_state *txstate) +{ + /* + * FIXME: do we need to return pending bytes? + * We have no users of that info at the moment... + */ + return dma_cookie_status(chan, cookie, txstate); +} + +static void omap_dma_issue_pending(struct dma_chan *chan) +{ + struct omap_chan *c = to_omap_dma_chan(chan); + unsigned long flags; + + spin_lock_irqsave(&c->vc.lock, flags); + if (vchan_issue_pending(&c->vc) && !c->desc) { + struct omap_dmadev *d = to_omap_dma_dev(chan->device); + spin_lock(&d->lock); + if (list_empty(&c->node)) + list_add_tail(&c->node, &d->pending); + spin_unlock(&d->lock); + tasklet_schedule(&d->task); + } + spin_unlock_irqrestore(&c->vc.lock, flags); +} + +static struct dma_async_tx_descriptor *omap_dma_prep_slave_sg( + struct dma_chan *chan, struct scatterlist *sgl, unsigned sglen, + enum dma_transfer_direction dir, unsigned long tx_flags, void *context) +{ + struct omap_chan *c = to_omap_dma_chan(chan); + enum dma_slave_buswidth dev_width; + struct scatterlist *sgent; + struct omap_desc *d; + dma_addr_t dev_addr; + unsigned i, j = 0, es, en, frame_bytes, sync_type; + u32 burst; + + if (dir == DMA_DEV_TO_MEM) { + dev_addr = c->cfg.src_addr; + dev_width = c->cfg.src_addr_width; + burst = c->cfg.src_maxburst; + sync_type = OMAP_DMA_SRC_SYNC; + } else if (dir == DMA_MEM_TO_DEV) { + dev_addr = c->cfg.dst_addr; + dev_width = c->cfg.dst_addr_width; + burst = c->cfg.dst_maxburst; + sync_type = OMAP_DMA_DST_SYNC; + } else { + dev_err(chan->device->dev, "%s: bad direction?\n", __func__); + return NULL; + } + + /* Bus width translates to the element size (ES) */ + switch (dev_width) { + case DMA_SLAVE_BUSWIDTH_1_BYTE: + es = OMAP_DMA_DATA_TYPE_S8; + break; + case DMA_SLAVE_BUSWIDTH_2_BYTES: + es = OMAP_DMA_DATA_TYPE_S16; + break; + case DMA_SLAVE_BUSWIDTH_4_BYTES: + es = OMAP_DMA_DATA_TYPE_S32; + break; + default: /* not reached */ + return NULL; + } + + /* Now allocate and setup the descriptor. */ + d = kzalloc(sizeof(*d) + sglen * sizeof(d->sg[0]), GFP_ATOMIC); + if (!d) + return NULL; + + d->dir = dir; + d->dev_addr = dev_addr; + d->es = es; + d->sync_mode = OMAP_DMA_SYNC_FRAME; + d->sync_type = sync_type; + d->periph_port = OMAP_DMA_PORT_TIPB; + + /* + * Build our scatterlist entries: each contains the address, + * the number of elements (EN) in each frame, and the number of + * frames (FN). Number of bytes for this entry = ES * EN * FN. + * + * Burst size translates to number of elements with frame sync. + * Note: DMA engine defines burst to be the number of dev-width + * transfers. + */ + en = burst; + frame_bytes = es_bytes[es] * en; + for_each_sg(sgl, sgent, sglen, i) { + d->sg[j].addr = sg_dma_address(sgent); + d->sg[j].en = en; + d->sg[j].fn = sg_dma_len(sgent) / frame_bytes; + j++; + } + + d->sglen = j; + + return vchan_tx_prep(&c->vc, &d->vd, tx_flags); +} + +static int omap_dma_slave_config(struct omap_chan *c, struct dma_slave_config *cfg) +{ + if (cfg->src_addr_width == DMA_SLAVE_BUSWIDTH_8_BYTES || + cfg->dst_addr_width == DMA_SLAVE_BUSWIDTH_8_BYTES) + return -EINVAL; + + memcpy(&c->cfg, cfg, sizeof(c->cfg)); + + return 0; +} + +static int omap_dma_terminate_all(struct omap_chan *c) +{ + struct omap_dmadev *d = to_omap_dma_dev(c->vc.chan.device); + unsigned long flags; + LIST_HEAD(head); + + spin_lock_irqsave(&c->vc.lock, flags); + + /* Prevent this channel being scheduled */ + spin_lock(&d->lock); + list_del_init(&c->node); + spin_unlock(&d->lock); + + /* + * Stop DMA activity: we assume the callback will not be called + * after omap_stop_dma() returns (even if it does, it will see + * c->desc is NULL and exit.) + */ + if (c->desc) { + c->desc = NULL; + omap_stop_dma(c->dma_ch); + } + + vchan_get_all_descriptors(&c->vc, &head); + spin_unlock_irqrestore(&c->vc.lock, flags); + vchan_dma_desc_free_list(&c->vc, &head); + + return 0; +} + +static int omap_dma_pause(struct omap_chan *c) +{ + /* FIXME: not supported by platform private API */ + return -EINVAL; +} + +static int omap_dma_resume(struct omap_chan *c) +{ + /* FIXME: not supported by platform private API */ + return -EINVAL; +} + +static int omap_dma_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, + unsigned long arg) +{ + struct omap_chan *c = to_omap_dma_chan(chan); + int ret; + + switch (cmd) { + case DMA_SLAVE_CONFIG: + ret = omap_dma_slave_config(c, (struct dma_slave_config *)arg); + break; + + case DMA_TERMINATE_ALL: + ret = omap_dma_terminate_all(c); + break; + + case DMA_PAUSE: + ret = omap_dma_pause(c); + break; + + case DMA_RESUME: + ret = omap_dma_resume(c); + break; + + default: + ret = -ENXIO; + break; + } + + return ret; +} + +static int omap_dma_chan_init(struct omap_dmadev *od, int dma_sig) +{ + struct omap_chan *c; + + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) + return -ENOMEM; + + c->dma_sig = dma_sig; + c->vc.desc_free = omap_dma_desc_free; + vchan_init(&c->vc, &od->ddev); + INIT_LIST_HEAD(&c->node); + + od->ddev.chancnt++; + + return 0; +} + +static void omap_dma_free(struct omap_dmadev *od) +{ + tasklet_kill(&od->task); + while (!list_empty(&od->ddev.channels)) { + struct omap_chan *c = list_first_entry(&od->ddev.channels, + struct omap_chan, vc.chan.device_node); + + list_del(&c->vc.chan.device_node); + tasklet_kill(&c->vc.task); + kfree(c); + } + kfree(od); +} + +static int omap_dma_probe(struct platform_device *pdev) +{ + struct omap_dmadev *od; + int rc, i; + + od = kzalloc(sizeof(*od), GFP_KERNEL); + if (!od) + return -ENOMEM; + + dma_cap_set(DMA_SLAVE, od->ddev.cap_mask); + od->ddev.device_alloc_chan_resources = omap_dma_alloc_chan_resources; + od->ddev.device_free_chan_resources = omap_dma_free_chan_resources; + od->ddev.device_tx_status = omap_dma_tx_status; + od->ddev.device_issue_pending = omap_dma_issue_pending; + od->ddev.device_prep_slave_sg = omap_dma_prep_slave_sg; + od->ddev.device_control = omap_dma_control; + od->ddev.dev = &pdev->dev; + INIT_LIST_HEAD(&od->ddev.channels); + INIT_LIST_HEAD(&od->pending); + spin_lock_init(&od->lock); + + tasklet_init(&od->task, omap_dma_sched, (unsigned long)od); + + for (i = 0; i < 127; i++) { + rc = omap_dma_chan_init(od, i); + if (rc) { + omap_dma_free(od); + return rc; + } + } + + rc = dma_async_device_register(&od->ddev); + if (rc) { + pr_warn("OMAP-DMA: failed to register slave DMA engine device: %d\n", + rc); + omap_dma_free(od); + } else { + platform_set_drvdata(pdev, od); + } + + dev_info(&pdev->dev, "OMAP DMA engine driver\n"); + + return rc; +} + +static int omap_dma_remove(struct platform_device *pdev) +{ + struct omap_dmadev *od = platform_get_drvdata(pdev); + + dma_async_device_unregister(&od->ddev); + omap_dma_free(od); + + return 0; +} + +static struct platform_driver omap_dma_driver = { + .probe = omap_dma_probe, + .remove = omap_dma_remove, + .driver = { + .name = "omap-dma-engine", + .owner = THIS_MODULE, + }, +}; + +bool omap_dma_filter_fn(struct dma_chan *chan, void *param) +{ + if (chan->device->dev->driver == &omap_dma_driver.driver) { + struct omap_chan *c = to_omap_dma_chan(chan); + unsigned req = *(unsigned *)param; + + return req == c->dma_sig; + } + return false; +} +EXPORT_SYMBOL_GPL(omap_dma_filter_fn); + +static struct platform_device *pdev; + +static const struct platform_device_info omap_dma_dev_info = { + .name = "omap-dma-engine", + .id = -1, + .dma_mask = DMA_BIT_MASK(32), +}; + +static int omap_dma_init(void) +{ + int rc = platform_driver_register(&omap_dma_driver); + + if (rc == 0) { + pdev = platform_device_register_full(&omap_dma_dev_info); + if (IS_ERR(pdev)) { + platform_driver_unregister(&omap_dma_driver); + rc = PTR_ERR(pdev); + } + } + return rc; +} +subsys_initcall(omap_dma_init); + +static void __exit omap_dma_exit(void) +{ + platform_device_unregister(pdev); + platform_driver_unregister(&omap_dma_driver); +} +module_exit(omap_dma_exit); + +MODULE_AUTHOR("Russell King"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/omap-dma.h b/include/linux/omap-dma.h new file mode 100644 index 00000000000..eb475a8ea25 --- /dev/null +++ b/include/linux/omap-dma.h @@ -0,0 +1,22 @@ +/* + * OMAP DMA Engine support + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __LINUX_OMAP_DMA_H +#define __LINUX_OMAP_DMA_H + +struct dma_chan; + +#if defined(CONFIG_DMA_OMAP) || defined(CONFIG_DMA_OMAP_MODULE) +bool omap_dma_filter_fn(struct dma_chan *, void *); +#else +static inline bool omap_dma_filter_fn(struct dma_chan *c, void *d) +{ + return false; +} +#endif + +#endif -- cgit v1.2.3-70-g09d2 From 3850e22f5146d2ff5b66f1b7460d4720d5f1b6c7 Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 21 Jun 2012 10:37:35 +0100 Subject: dmaengine: omap: add support for returning residue in tx_state method Add support for returning the residue for a particular descriptor by reading the current DMA address for the source or destination side of the transfer as appropriate, and walking the scatterlist until we find an entry containing the current DMA address. Signed-off-by: Russell King --- drivers/dma/omap-dma.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 5 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/omap-dma.c b/drivers/dma/omap-dma.c index fe33ddd8eb0..82a7ac7c048 100644 --- a/drivers/dma/omap-dma.c +++ b/drivers/dma/omap-dma.c @@ -194,14 +194,73 @@ static void omap_dma_free_chan_resources(struct dma_chan *chan) dev_info(c->vc.chan.device->dev, "freeing channel for %u\n", c->dma_sig); } +static size_t omap_dma_sg_size(struct omap_sg *sg) +{ + return sg->en * sg->fn; +} + +static size_t omap_dma_desc_size(struct omap_desc *d) +{ + unsigned i; + size_t size; + + for (size = i = 0; i < d->sglen; i++) + size += omap_dma_sg_size(&d->sg[i]); + + return size * es_bytes[d->es]; +} + +static size_t omap_dma_desc_size_pos(struct omap_desc *d, dma_addr_t addr) +{ + unsigned i; + size_t size, es_size = es_bytes[d->es]; + + for (size = i = 0; i < d->sglen; i++) { + size_t this_size = omap_dma_sg_size(&d->sg[i]) * es_size; + + if (size) + size += this_size; + else if (addr >= d->sg[i].addr && + addr < d->sg[i].addr + this_size) + size += d->sg[i].addr + this_size - addr; + } + return size; +} + static enum dma_status omap_dma_tx_status(struct dma_chan *chan, dma_cookie_t cookie, struct dma_tx_state *txstate) { - /* - * FIXME: do we need to return pending bytes? - * We have no users of that info at the moment... - */ - return dma_cookie_status(chan, cookie, txstate); + struct omap_chan *c = to_omap_dma_chan(chan); + struct virt_dma_desc *vd; + enum dma_status ret; + unsigned long flags; + + ret = dma_cookie_status(chan, cookie, txstate); + if (ret == DMA_SUCCESS || !txstate) + return ret; + + spin_lock_irqsave(&c->vc.lock, flags); + vd = vchan_find_desc(&c->vc, cookie); + if (vd) { + txstate->residue = omap_dma_desc_size(to_omap_dma_desc(&vd->tx)); + } else if (c->desc && c->desc->vd.tx.cookie == cookie) { + struct omap_desc *d = c->desc; + dma_addr_t pos; + + if (d->dir == DMA_MEM_TO_DEV) + pos = omap_get_dma_src_pos(c->dma_ch); + else if (d->dir == DMA_DEV_TO_MEM) + pos = omap_get_dma_dst_pos(c->dma_ch); + else + pos = 0; + + txstate->residue = omap_dma_desc_size_pos(d, pos); + } else { + txstate->residue = 0; + } + spin_unlock_irqrestore(&c->vc.lock, flags); + + return ret; } static void omap_dma_issue_pending(struct dma_chan *chan) -- cgit v1.2.3-70-g09d2 From 7c836bc7f9f71d62a8dc50712db122a69b405486 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 18 Jun 2012 16:45:19 +0100 Subject: dmaengine: omap: add support for setting fi Signed-off-by: Russell King --- drivers/dma/omap-dma.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/omap-dma.c b/drivers/dma/omap-dma.c index 82a7ac7c048..f2e919fcea3 100644 --- a/drivers/dma/omap-dma.c +++ b/drivers/dma/omap-dma.c @@ -50,6 +50,7 @@ struct omap_desc { enum dma_transfer_direction dir; dma_addr_t dev_addr; + int16_t fi; /* for OMAP_DMA_SYNC_PACKET */ uint8_t es; /* OMAP_DMA_DATA_TYPE_xxx */ uint8_t sync_mode; /* OMAP_DMA_SYNC_xxx */ uint8_t sync_type; /* OMAP_DMA_xxx_SYNC* */ @@ -120,10 +121,10 @@ static void omap_dma_start_desc(struct omap_chan *c) if (d->dir == DMA_DEV_TO_MEM) omap_set_dma_src_params(c->dma_ch, d->periph_port, - OMAP_DMA_AMODE_CONSTANT, d->dev_addr, 0, 0); + OMAP_DMA_AMODE_CONSTANT, d->dev_addr, 0, d->fi); else omap_set_dma_dest_params(c->dma_ch, d->periph_port, - OMAP_DMA_AMODE_CONSTANT, d->dev_addr, 0, 0); + OMAP_DMA_AMODE_CONSTANT, d->dev_addr, 0, d->fi); omap_dma_start_sg(c, d, 0); } -- cgit v1.2.3-70-g09d2 From 3a774ea91a5d05e7af58db6ae1ba298263c4a3d3 Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 21 Jun 2012 10:40:15 +0100 Subject: dmaengine: omap: add support for cyclic DMA Add support for cyclic DMA to the OMAP DMA engine driver. Signed-off-by: Russell King --- drivers/dma/omap-dma.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 4 deletions(-) (limited to 'drivers/dma') diff --git a/drivers/dma/omap-dma.c b/drivers/dma/omap-dma.c index f2e919fcea3..ae056182613 100644 --- a/drivers/dma/omap-dma.c +++ b/drivers/dma/omap-dma.c @@ -33,6 +33,7 @@ struct omap_chan { struct dma_slave_config cfg; unsigned dma_sig; + bool cyclic; int dma_ch; struct omap_desc *desc; @@ -138,11 +139,15 @@ static void omap_dma_callback(int ch, u16 status, void *data) spin_lock_irqsave(&c->vc.lock, flags); d = c->desc; if (d) { - if (++c->sgidx < d->sglen) { - omap_dma_start_sg(c, d, c->sgidx); + if (!c->cyclic) { + if (++c->sgidx < d->sglen) { + omap_dma_start_sg(c, d, c->sgidx); + } else { + omap_dma_start_desc(c); + vchan_cookie_complete(&d->vd); + } } else { - omap_dma_start_desc(c); - vchan_cookie_complete(&d->vd); + vchan_cyclic_callback(&d->vd); } } spin_unlock_irqrestore(&c->vc.lock, flags); @@ -358,6 +363,79 @@ static struct dma_async_tx_descriptor *omap_dma_prep_slave_sg( return vchan_tx_prep(&c->vc, &d->vd, tx_flags); } +static struct dma_async_tx_descriptor *omap_dma_prep_dma_cyclic( + struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len, + size_t period_len, enum dma_transfer_direction dir, void *context) +{ + struct omap_chan *c = to_omap_dma_chan(chan); + enum dma_slave_buswidth dev_width; + struct omap_desc *d; + dma_addr_t dev_addr; + unsigned es, sync_type; + u32 burst; + + if (dir == DMA_DEV_TO_MEM) { + dev_addr = c->cfg.src_addr; + dev_width = c->cfg.src_addr_width; + burst = c->cfg.src_maxburst; + sync_type = OMAP_DMA_SRC_SYNC; + } else if (dir == DMA_MEM_TO_DEV) { + dev_addr = c->cfg.dst_addr; + dev_width = c->cfg.dst_addr_width; + burst = c->cfg.dst_maxburst; + sync_type = OMAP_DMA_DST_SYNC; + } else { + dev_err(chan->device->dev, "%s: bad direction?\n", __func__); + return NULL; + } + + /* Bus width translates to the element size (ES) */ + switch (dev_width) { + case DMA_SLAVE_BUSWIDTH_1_BYTE: + es = OMAP_DMA_DATA_TYPE_S8; + break; + case DMA_SLAVE_BUSWIDTH_2_BYTES: + es = OMAP_DMA_DATA_TYPE_S16; + break; + case DMA_SLAVE_BUSWIDTH_4_BYTES: + es = OMAP_DMA_DATA_TYPE_S32; + break; + default: /* not reached */ + return NULL; + } + + /* Now allocate and setup the descriptor. */ + d = kzalloc(sizeof(*d) + sizeof(d->sg[0]), GFP_ATOMIC); + if (!d) + return NULL; + + d->dir = dir; + d->dev_addr = dev_addr; + d->fi = burst; + d->es = es; + d->sync_mode = OMAP_DMA_SYNC_PACKET; + d->sync_type = sync_type; + d->periph_port = OMAP_DMA_PORT_MPUI; + d->sg[0].addr = buf_addr; + d->sg[0].en = period_len / es_bytes[es]; + d->sg[0].fn = buf_len / period_len; + d->sglen = 1; + + if (!c->cyclic) { + c->cyclic = true; + omap_dma_link_lch(c->dma_ch, c->dma_ch); + omap_enable_dma_irq(c->dma_ch, OMAP_DMA_FRAME_IRQ); + omap_disable_dma_irq(c->dma_ch, OMAP_DMA_BLOCK_IRQ); + } + + if (!cpu_class_is_omap1()) { + omap_set_dma_src_burst_mode(c->dma_ch, OMAP_DMA_DATA_BURST_16); + omap_set_dma_dest_burst_mode(c->dma_ch, OMAP_DMA_DATA_BURST_16); + } + + return vchan_tx_prep(&c->vc, &d->vd, DMA_CTRL_ACK | DMA_PREP_INTERRUPT); +} + static int omap_dma_slave_config(struct omap_chan *c, struct dma_slave_config *cfg) { if (cfg->src_addr_width == DMA_SLAVE_BUSWIDTH_8_BYTES || @@ -392,6 +470,11 @@ static int omap_dma_terminate_all(struct omap_chan *c) omap_stop_dma(c->dma_ch); } + if (c->cyclic) { + c->cyclic = false; + omap_dma_unlink_lch(c->dma_ch, c->dma_ch); + } + vchan_get_all_descriptors(&c->vc, &head); spin_unlock_irqrestore(&c->vc.lock, flags); vchan_dma_desc_free_list(&c->vc, &head); @@ -484,11 +567,13 @@ static int omap_dma_probe(struct platform_device *pdev) return -ENOMEM; dma_cap_set(DMA_SLAVE, od->ddev.cap_mask); + dma_cap_set(DMA_CYCLIC, od->ddev.cap_mask); od->ddev.device_alloc_chan_resources = omap_dma_alloc_chan_resources; od->ddev.device_free_chan_resources = omap_dma_free_chan_resources; od->ddev.device_tx_status = omap_dma_tx_status; od->ddev.device_issue_pending = omap_dma_issue_pending; od->ddev.device_prep_slave_sg = omap_dma_prep_slave_sg; + od->ddev.device_prep_dma_cyclic = omap_dma_prep_dma_cyclic; od->ddev.device_control = omap_dma_control; od->ddev.dev = &pdev->dev; INIT_LIST_HEAD(&od->ddev.channels); -- cgit v1.2.3-70-g09d2