summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-07-23 15:40:27 +0100
committerMark Brown <broonie@kernel.org>2026-07-23 15:40:27 +0100
commit719fd2fe250a20940099aa08ff0bc6a72fe6aac8 (patch)
tree4556bf119cf6d3c6c342c58e6e143ad2006f7e4c /drivers
parented9b79b61e9248ec87c34c0d2f7f2109e7f3a651 (diff)
parent0613e7934ee233d726af8d8c89f251a1c4df738d (diff)
downloadlinux-next-719fd2fe250a20940099aa08ff0bc6a72fe6aac8.tar.gz
linux-next-719fd2fe250a20940099aa08ff0bc6a72fe6aac8.zip
Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/vkoul/dmaengine.git
Diffstat (limited to 'drivers')
-rw-r--r--drivers/crypto/atmel-aes.c10
-rw-r--r--drivers/dma/arm-dma350.c9
-rw-r--r--drivers/dma/dmaengine.c9
-rw-r--r--drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c2
-rw-r--r--drivers/dma/dw-edma/dw-edma-core.c564
-rw-r--r--drivers/dma/dw-edma/dw-edma-core.h111
-rw-r--r--drivers/dma/dw-edma/dw-edma-pcie.c264
-rw-r--r--drivers/dma/dw-edma/dw-edma-v0-core.c390
-rw-r--r--drivers/dma/dw-edma/dw-hdma-v0-core.c265
-rw-r--r--drivers/dma/dw-edma/dw-hdma-v0-regs.h3
-rw-r--r--drivers/dma/fsl-edma-main.c2
-rw-r--r--drivers/dma/fsl_raid.c58
-rw-r--r--drivers/dma/fsl_raid.h4
-rw-r--r--drivers/dma/hisi_dma.c2
-rw-r--r--drivers/dma/mediatek/mtk-uart-apdma.c2
-rw-r--r--drivers/dma/pl330.c19
-rw-r--r--drivers/dma/tegra210-adma.c12
-rw-r--r--drivers/dma/ti/k3-udma.c2
-rw-r--r--drivers/dma/xilinx/xilinx_dma.c66
-rw-r--r--drivers/dma/xilinx/zynqmp_dma.c6
-rw-r--r--drivers/nvme/target/pci-epf.c33
-rw-r--r--drivers/pci/endpoint/functions/pci-epf-mhi.c52
-rw-r--r--drivers/pci/endpoint/functions/pci-epf-test.c8
23 files changed, 1157 insertions, 736 deletions
diff --git a/drivers/crypto/atmel-aes.c b/drivers/crypto/atmel-aes.c
index f79d7c456546..30acfd937d54 100644
--- a/drivers/crypto/atmel-aes.c
+++ b/drivers/crypto/atmel-aes.c
@@ -794,7 +794,6 @@ static int atmel_aes_dma_transfer_start(struct atmel_aes_dev *dd,
struct dma_slave_config config;
dma_async_tx_callback callback;
struct atmel_aes_dma *dma;
- int err;
memset(&config, 0, sizeof(config));
config.src_addr_width = addr_width;
@@ -819,12 +818,9 @@ static int atmel_aes_dma_transfer_start(struct atmel_aes_dev *dd,
return -EINVAL;
}
- err = dmaengine_slave_config(dma->chan, &config);
- if (err)
- return err;
-
- desc = dmaengine_prep_slave_sg(dma->chan, dma->sg, dma->sg_len, dir,
- DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ desc = dmaengine_prep_config_sg(dma->chan, dma->sg, dma->sg_len, dir,
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK,
+ &config);
if (!desc)
return -ENOMEM;
diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
index 84220fa83029..09403aca8bb0 100644
--- a/drivers/dma/arm-dma350.c
+++ b/drivers/dma/arm-dma350.c
@@ -13,6 +13,11 @@
#include "dmaengine.h"
#include "virt-dma.h"
+#define DMANSECCTRL 0x200
+
+#define NSEC_CTRL 0x0c
+#define INTREN_ANYCHINTR_EN BIT(0)
+
#define DMAINFO 0x0f00
#define DMA_BUILDCFG0 0xb0
@@ -582,6 +587,10 @@ static int d350_probe(struct platform_device *pdev)
dmac->dma.device_issue_pending = d350_issue_pending;
INIT_LIST_HEAD(&dmac->dma.channels);
+ reg = readl_relaxed(base + DMANSECCTRL + NSEC_CTRL);
+ writel_relaxed(reg | INTREN_ANYCHINTR_EN,
+ base + DMANSECCTRL + NSEC_CTRL);
+
/* Would be nice to have per-channel caps for this... */
memset = true;
for (int i = 0; i < nchan; i++) {
diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c
index 9049171df857..6ffd8bd82154 100644
--- a/drivers/dma/dmaengine.c
+++ b/drivers/dma/dmaengine.c
@@ -814,10 +814,15 @@ static const struct dma_slave_map *dma_filter_match(struct dma_device *device,
*/
struct dma_chan *dma_request_chan(struct device *dev, const char *name)
{
- struct fwnode_handle *fwnode = dev_fwnode(dev);
+ struct fwnode_handle *fwnode;
struct dma_device *d, *_d;
struct dma_chan *chan = NULL;
+ if (WARN_ON(!dev || !name))
+ return ERR_PTR(-EINVAL);
+
+ fwnode = dev_fwnode(dev);
+
if (is_of_node(fwnode))
chan = of_dma_request_slave_channel(to_of_node(fwnode), name);
else if (is_acpi_device_node(fwnode))
@@ -1100,6 +1105,8 @@ static int __dma_async_device_channel_register(struct dma_device *device,
chan->dev->device.parent = device->dev;
chan->dev->chan = chan;
chan->dev->dev_id = device->dev_id;
+ spin_lock_init(&chan->lock);
+
if (!name)
dev_set_name(&chan->dev->device, "dma%dchan%d", device->dev_id, chan->chan_id);
else
diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
index bcefaff03b5c..eebed2474210 100644
--- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
+++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
@@ -1126,7 +1126,7 @@ static void axi_chan_block_xfer_complete(struct axi_dma_chan *chan)
hw_desc = &desc->hw_desc[i];
if (hw_desc->llp == llp) {
axi_chan_irq_clear(chan, hw_desc->lli->status_lo);
- hw_desc->lli->ctl_hi |= CH_CTL_H_LLI_VALID;
+ hw_desc->lli->ctl_hi |= cpu_to_le32(CH_CTL_H_LLI_VALID);
desc->completed_blocks = i;
if (((hw_desc->len * (i + 1)) % desc->period_len) == 0)
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index 89a4c498a17b..1f893dc54c79 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -7,6 +7,7 @@
*/
#include <linux/module.h>
+#include <linux/delay.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/dmaengine.h>
@@ -29,6 +30,11 @@ struct dw_edma_desc *vd2dw_edma_desc(struct virt_dma_desc *vd)
return container_of(vd, struct dw_edma_desc, vd);
}
+enum dw_edma_irq_event {
+ DW_EDMA_IRQ_DONE = BIT(0),
+ DW_EDMA_IRQ_ABORT = BIT(1),
+};
+
static inline
u64 dw_edma_get_pci_address(struct dw_edma_chan *chan, phys_addr_t cpu_addr)
{
@@ -40,141 +46,63 @@ u64 dw_edma_get_pci_address(struct dw_edma_chan *chan, phys_addr_t cpu_addr)
return cpu_addr;
}
-static struct dw_edma_burst *dw_edma_alloc_burst(struct dw_edma_chunk *chunk)
-{
- struct dw_edma_burst *burst;
-
- burst = kzalloc_obj(*burst, GFP_NOWAIT);
- if (unlikely(!burst))
- return NULL;
-
- INIT_LIST_HEAD(&burst->list);
- if (chunk->burst) {
- /* Create and add new element into the linked list */
- chunk->bursts_alloc++;
- list_add_tail(&burst->list, &chunk->burst->list);
- } else {
- /* List head */
- chunk->bursts_alloc = 0;
- chunk->burst = burst;
- }
-
- return burst;
-}
-
-static struct dw_edma_chunk *dw_edma_alloc_chunk(struct dw_edma_desc *desc)
-{
- struct dw_edma_chip *chip = desc->chan->dw->chip;
- struct dw_edma_chan *chan = desc->chan;
- struct dw_edma_chunk *chunk;
-
- chunk = kzalloc_obj(*chunk, GFP_NOWAIT);
- if (unlikely(!chunk))
- return NULL;
-
- INIT_LIST_HEAD(&chunk->list);
- chunk->chan = chan;
- /* Toggling change bit (CB) in each chunk, this is a mechanism to
- * inform the eDMA HW block that this is a new linked list ready
- * to be consumed.
- * - Odd chunks originate CB equal to 0
- * - Even chunks originate CB equal to 1
- */
- chunk->cb = !(desc->chunks_alloc % 2);
- if (chan->dir == EDMA_DIR_WRITE) {
- chunk->ll_region.paddr = chip->ll_region_wr[chan->id].paddr;
- chunk->ll_region.vaddr = chip->ll_region_wr[chan->id].vaddr;
- } else {
- chunk->ll_region.paddr = chip->ll_region_rd[chan->id].paddr;
- chunk->ll_region.vaddr = chip->ll_region_rd[chan->id].vaddr;
- }
-
- if (desc->chunk) {
- /* Create and add new element into the linked list */
- if (!dw_edma_alloc_burst(chunk)) {
- kfree(chunk);
- return NULL;
- }
- desc->chunks_alloc++;
- list_add_tail(&chunk->list, &desc->chunk->list);
- } else {
- /* List head */
- chunk->burst = NULL;
- desc->chunks_alloc = 0;
- desc->chunk = chunk;
- }
-
- return chunk;
-}
-
-static struct dw_edma_desc *dw_edma_alloc_desc(struct dw_edma_chan *chan)
+static struct dw_edma_desc *
+dw_edma_alloc_desc(struct dw_edma_chan *chan, size_t nburst)
{
struct dw_edma_desc *desc;
- desc = kzalloc_obj(*desc, GFP_NOWAIT);
+ desc = kzalloc_flex(*desc, burst, nburst, GFP_NOWAIT);
if (unlikely(!desc))
return NULL;
desc->chan = chan;
- if (!dw_edma_alloc_chunk(desc)) {
- kfree(desc);
- return NULL;
- }
+ desc->nburst = nburst;
+ desc->cb = true;
return desc;
}
-static void dw_edma_free_burst(struct dw_edma_chunk *chunk)
+static void vchan_free_desc(struct virt_dma_desc *vdesc)
{
- struct dw_edma_burst *child, *_next;
-
- /* Remove all the list elements */
- list_for_each_entry_safe(child, _next, &chunk->burst->list, list) {
- list_del(&child->list);
- kfree(child);
- chunk->bursts_alloc--;
- }
-
- /* Remove the list head */
- kfree(child);
- chunk->burst = NULL;
+ kfree(vd2dw_edma_desc(vdesc));
}
-static void dw_edma_free_chunk(struct dw_edma_desc *desc)
+static void dw_edma_core_start(struct dw_edma_desc *desc, bool first)
{
- struct dw_edma_chunk *child, *_next;
+ struct dw_edma_chan *chan = desc->chan;
+ size_t i = 0;
- if (!desc->chunk)
+ if (chan->non_ll) {
+ chan->dw->core->non_ll_start(chan, &desc->burst[desc->start_burst]);
+ desc->done_burst = desc->start_burst;
+ desc->start_burst += 1;
return;
+ }
- /* Remove all the list elements */
- list_for_each_entry_safe(child, _next, &desc->chunk->list, list) {
- dw_edma_free_burst(child);
- list_del(&child->list);
- kfree(child);
- desc->chunks_alloc--;
+ for (i = 0; i + desc->start_burst < desc->nburst; i++) {
+ u32 idx = i + desc->start_burst;
+
+ if (i == chan->ll_max)
+ break;
+
+ dw_edma_core_ll_data(chan, &desc->burst[idx],
+ i, desc->cb,
+ idx == desc->nburst - 1 || i == chan->ll_max - 1);
}
- /* Remove the list head */
- kfree(child);
- desc->chunk = NULL;
-}
+ desc->done_burst = desc->start_burst;
+ desc->start_burst += i;
-static void dw_edma_free_desc(struct dw_edma_desc *desc)
-{
- dw_edma_free_chunk(desc);
- kfree(desc);
-}
+ dw_edma_core_ll_link(chan, i, desc->cb, chan->ll_region.paddr);
-static void vchan_free_desc(struct virt_dma_desc *vdesc)
-{
- dw_edma_free_desc(vd2dw_edma_desc(vdesc));
+ if (first)
+ dw_edma_core_ch_enable(chan);
+
+ dw_edma_core_ch_doorbell(chan);
}
static int dw_edma_start_transfer(struct dw_edma_chan *chan)
{
- struct dw_edma *dw = chan->dw;
- struct dw_edma_chunk *child;
struct dw_edma_desc *desc;
struct virt_dma_desc *vd;
@@ -186,21 +114,42 @@ static int dw_edma_start_transfer(struct dw_edma_chan *chan)
if (!desc)
return 0;
- child = list_first_entry_or_null(&desc->chunk->list,
- struct dw_edma_chunk, list);
- if (!child)
- return 0;
+ dw_edma_core_start(desc, !desc->start_burst);
- dw_edma_core_start(dw, child, !desc->xfer_sz);
- desc->xfer_sz += child->ll_region.sz;
- dw_edma_free_burst(child);
- list_del(&child->list);
- kfree(child);
- desc->chunks_alloc--;
+ desc->cb = !desc->cb;
return 1;
}
+static void dw_edma_terminate_vdesc(struct virt_dma_desc *vd)
+{
+ list_del(&vd->node);
+ dma_cookie_complete(&vd->tx);
+ vchan_terminate_vdesc(vd);
+}
+
+static void dw_edma_terminate_vdesc_list(struct list_head *head)
+{
+ struct virt_dma_desc *vd, *_vd;
+
+ list_for_each_entry_safe(vd, _vd, head, node)
+ dw_edma_terminate_vdesc(vd);
+}
+
+/* Must be called with vc.lock held. */
+static void dw_edma_terminate_all_descs(struct dw_edma_chan *chan)
+{
+ /*
+ * This order must not be reversed. Cookies are assigned when
+ * descriptors are submitted, so desc_issued contains older cookies
+ * than desc_submitted. Completing desc_submitted first could move
+ * chan->vc.chan.completed_cookie backwards when desc_issued is
+ * terminated afterwards.
+ */
+ dw_edma_terminate_vdesc_list(&chan->vc.desc_issued);
+ dw_edma_terminate_vdesc_list(&chan->vc.desc_submitted);
+}
+
static void dw_edma_device_caps(struct dma_chan *dchan,
struct dma_slave_caps *caps)
{
@@ -219,6 +168,15 @@ static void dw_edma_device_caps(struct dma_chan *dchan,
}
}
+static enum dw_edma_ch_irq_mode
+dw_edma_get_default_irq_mode(struct dw_edma_chan *chan)
+{
+ struct dw_edma_chip *chip = chan->dw->chip;
+
+ return chip->flags & DW_EDMA_CHIP_LOCAL ? DW_EDMA_CH_IRQ_LOCAL :
+ DW_EDMA_CH_IRQ_REMOTE;
+}
+
static int dw_edma_device_config(struct dma_chan *dchan,
struct dma_slave_config *config)
{
@@ -267,11 +225,27 @@ static int dw_edma_device_config(struct dma_chan *dchan,
return 0;
}
+static struct dma_slave_config *
+dw_edma_device_get_config(struct dma_chan *dchan,
+ struct dma_slave_config *config)
+{
+ struct dw_edma_chan *chan;
+
+ if (config)
+ return config;
+
+ chan = dchan2dw_edma_chan(dchan);
+
+ return &chan->config;
+}
+
static int dw_edma_device_pause(struct dma_chan *dchan)
{
struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
int err = 0;
+ guard(spinlock_irqsave)(&chan->vc.lock);
+
if (!chan->configured)
err = -EPERM;
else if (chan->status != EDMA_ST_BUSY)
@@ -289,6 +263,8 @@ static int dw_edma_device_resume(struct dma_chan *dchan)
struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
int err = 0;
+ guard(spinlock_irqsave)(&chan->vc.lock);
+
if (!chan->configured) {
err = -EPERM;
} else if (chan->status != EDMA_ST_PAUSE) {
@@ -297,7 +273,8 @@ static int dw_edma_device_resume(struct dma_chan *dchan)
err = -EPERM;
} else {
chan->status = EDMA_ST_BUSY;
- dw_edma_start_transfer(chan);
+ if (!dw_edma_start_transfer(chan))
+ chan->status = EDMA_ST_IDLE;
}
return err;
@@ -308,25 +285,29 @@ static int dw_edma_device_terminate_all(struct dma_chan *dchan)
struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
int err = 0;
+ guard(spinlock_irqsave)(&chan->vc.lock);
+
if (!chan->configured) {
- /* Do nothing */
+ dw_edma_terminate_all_descs(chan);
} else if (chan->status == EDMA_ST_PAUSE) {
+ dw_edma_terminate_all_descs(chan);
chan->status = EDMA_ST_IDLE;
- chan->configured = false;
} else if (chan->status == EDMA_ST_IDLE) {
- chan->configured = false;
+ dw_edma_terminate_all_descs(chan);
} else if (dw_edma_core_ch_status(chan) == DMA_COMPLETE) {
/*
* The channel is in a false BUSY state, probably didn't
* receive or lost an interrupt
*/
+ dw_edma_terminate_all_descs(chan);
chan->status = EDMA_ST_IDLE;
- chan->configured = false;
} else if (chan->request > EDMA_REQ_PAUSE) {
err = -EPERM;
} else {
chan->request = EDMA_REQ_STOP;
}
+ if (chan->status == EDMA_ST_IDLE)
+ chan->request = EDMA_REQ_NONE;
return err;
}
@@ -336,11 +317,9 @@ static void dw_edma_device_issue_pending(struct dma_chan *dchan)
struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
unsigned long flags;
- if (!chan->configured)
- return;
-
spin_lock_irqsave(&chan->vc.lock, flags);
- if (vchan_issue_pending(&chan->vc) && chan->request == EDMA_REQ_NONE &&
+ if (chan->configured && vchan_issue_pending(&chan->vc) &&
+ chan->request == EDMA_REQ_NONE &&
chan->status == EDMA_ST_IDLE) {
chan->status = EDMA_ST_BUSY;
dw_edma_start_transfer(chan);
@@ -373,8 +352,10 @@ dw_edma_device_tx_status(struct dma_chan *dchan, dma_cookie_t cookie,
vd = vchan_find_desc(&chan->vc, cookie);
if (vd) {
desc = vd2dw_edma_desc(vd);
- if (desc)
- residue = desc->alloc_sz - desc->xfer_sz;
+
+ residue = desc->alloc_sz;
+ if (desc && desc->done_burst)
+ residue -= desc->burst[desc->done_burst - 1].xfer_sz;
}
spin_unlock_irqrestore(&chan->vc.lock, flags);
@@ -385,19 +366,18 @@ ret_residue:
}
static struct dma_async_tx_descriptor *
-dw_edma_device_transfer(struct dw_edma_transfer *xfer)
+dw_edma_device_transfer(struct dw_edma_transfer *xfer,
+ struct dma_slave_config *config)
{
struct dw_edma_chan *chan = dchan2dw_edma_chan(xfer->dchan);
enum dma_transfer_direction dir = xfer->direction;
struct scatterlist *sg = NULL;
- struct dw_edma_chunk *chunk;
struct dw_edma_burst *burst;
struct dw_edma_desc *desc;
u64 src_addr, dst_addr;
size_t fsz = 0;
- u32 bursts_max;
- u32 cnt = 0;
- int i;
+ size_t cnt = 0;
+ u32 i;
if (!chan->configured)
return NULL;
@@ -453,27 +433,12 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer)
return NULL;
}
- /*
- * For non-LL mode, only a single burst can be handled
- * in a single chunk unlike LL mode where multiple bursts
- * can be configured in a single chunk.
- */
- bursts_max = chan->non_ll ? 1 : chan->ll_max;
-
- desc = dw_edma_alloc_desc(chan);
- if (unlikely(!desc))
- goto err_alloc;
-
- chunk = dw_edma_alloc_chunk(desc);
- if (unlikely(!chunk))
- goto err_alloc;
-
if (xfer->type == EDMA_XFER_INTERLEAVED) {
src_addr = xfer->xfer.il->src_start;
dst_addr = xfer->xfer.il->dst_start;
} else {
- src_addr = chan->config.src_addr;
- dst_addr = chan->config.dst_addr;
+ src_addr = config->src_addr;
+ dst_addr = config->dst_addr;
}
if (dir == DMA_DEV_TO_MEM)
@@ -491,19 +456,15 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer)
fsz = xfer->xfer.il->frame_size;
}
+ desc = dw_edma_alloc_desc(chan, cnt);
+ if (unlikely(!desc))
+ return NULL;
+
for (i = 0; i < cnt; i++) {
if (xfer->type == EDMA_XFER_SCATTER_GATHER && !sg)
break;
- if (chunk->bursts_alloc == bursts_max) {
- chunk = dw_edma_alloc_chunk(desc);
- if (unlikely(!chunk))
- goto err_alloc;
- }
-
- burst = dw_edma_alloc_burst(chunk);
- if (unlikely(!burst))
- goto err_alloc;
+ burst = desc->burst + i;
if (xfer->type == EDMA_XFER_CYCLIC)
burst->sz = xfer->xfer.cyclic.len;
@@ -512,8 +473,8 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer)
else if (xfer->type == EDMA_XFER_INTERLEAVED)
burst->sz = xfer->xfer.il->sgl[i % fsz].size;
- chunk->ll_region.sz += burst->sz;
desc->alloc_sz += burst->sz;
+ burst->xfer_sz = desc->alloc_sz;
if (dir == DMA_DEV_TO_MEM) {
burst->sar = src_addr;
@@ -568,19 +529,14 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer)
}
return vchan_tx_prep(&chan->vc, &desc->vd, xfer->flags);
-
-err_alloc:
- if (desc)
- dw_edma_free_desc(desc);
-
- return NULL;
}
static struct dma_async_tx_descriptor *
-dw_edma_device_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl,
- unsigned int len,
- enum dma_transfer_direction direction,
- unsigned long flags, void *context)
+dw_edma_device_prep_config_sg(struct dma_chan *dchan, struct scatterlist *sgl,
+ unsigned int len,
+ enum dma_transfer_direction direction,
+ unsigned long flags,
+ struct dma_slave_config *config)
{
struct dw_edma_transfer xfer;
@@ -591,7 +547,10 @@ dw_edma_device_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl,
xfer.flags = flags;
xfer.type = EDMA_XFER_SCATTER_GATHER;
- return dw_edma_device_transfer(&xfer);
+ if (config && dw_edma_device_config(dchan, config))
+ return NULL;
+
+ return dw_edma_device_transfer(&xfer, dw_edma_device_get_config(dchan, config));
}
static struct dma_async_tx_descriptor *
@@ -610,7 +569,7 @@ dw_edma_device_prep_dma_cyclic(struct dma_chan *dchan, dma_addr_t paddr,
xfer.flags = flags;
xfer.type = EDMA_XFER_CYCLIC;
- return dw_edma_device_transfer(&xfer);
+ return dw_edma_device_transfer(&xfer, dw_edma_device_get_config(dchan, NULL));
}
static struct dma_async_tx_descriptor *
@@ -626,7 +585,7 @@ dw_edma_device_prep_interleaved_dma(struct dma_chan *dchan,
xfer.flags = flags;
xfer.type = EDMA_XFER_INTERLEAVED;
- return dw_edma_device_transfer(&xfer);
+ return dw_edma_device_transfer(&xfer, dw_edma_device_get_config(dchan, NULL));
}
static void dw_hdma_set_callback_result(struct virt_dma_desc *vd,
@@ -640,8 +599,14 @@ static void dw_hdma_set_callback_result(struct virt_dma_desc *vd,
return;
desc = vd2dw_edma_desc(vd);
- if (desc)
- residue = desc->alloc_sz - desc->xfer_sz;
+ if (desc) {
+ residue = desc->alloc_sz;
+
+ if (result == DMA_TRANS_NOERROR)
+ residue -= desc->burst[desc->start_burst - 1].xfer_sz;
+ else if (desc->done_burst)
+ residue -= desc->burst[desc->done_burst - 1].xfer_sz;
+ }
res = &vd->tx_result;
res->result = result;
@@ -655,35 +620,41 @@ static void dw_edma_done_interrupt(struct dw_edma_chan *chan)
unsigned long flags;
spin_lock_irqsave(&chan->vc.lock, flags);
+ if (chan->status == EDMA_ST_PAUSE) {
+ spin_unlock_irqrestore(&chan->vc.lock, flags);
+ return;
+ }
+
vd = vchan_next_desc(&chan->vc);
if (vd) {
switch (chan->request) {
case EDMA_REQ_NONE:
+ case EDMA_REQ_PAUSE:
desc = vd2dw_edma_desc(vd);
- if (!desc->chunks_alloc) {
+ if (desc->start_burst >= desc->nburst) {
dw_hdma_set_callback_result(vd,
DMA_TRANS_NOERROR);
list_del(&vd->node);
vchan_cookie_complete(vd);
}
+ if (chan->request == EDMA_REQ_PAUSE) {
+ chan->request = EDMA_REQ_NONE;
+ chan->status = EDMA_ST_PAUSE;
+ break;
+ }
+
/* Continue transferring if there are remaining chunks or issued requests.
*/
chan->status = dw_edma_start_transfer(chan) ? EDMA_ST_BUSY : EDMA_ST_IDLE;
break;
case EDMA_REQ_STOP:
- list_del(&vd->node);
- vchan_cookie_complete(vd);
+ dw_edma_terminate_all_descs(chan);
chan->request = EDMA_REQ_NONE;
chan->status = EDMA_ST_IDLE;
break;
- case EDMA_REQ_PAUSE:
- chan->request = EDMA_REQ_NONE;
- chan->status = EDMA_ST_PAUSE;
- break;
-
default:
break;
}
@@ -698,14 +669,49 @@ static void dw_edma_abort_interrupt(struct dw_edma_chan *chan)
spin_lock_irqsave(&chan->vc.lock, flags);
vd = vchan_next_desc(&chan->vc);
- if (vd) {
+ if (vd && chan->request == EDMA_REQ_STOP) {
+ dw_edma_terminate_all_descs(chan);
+ } else if (vd) {
dw_hdma_set_callback_result(vd, DMA_TRANS_ABORTED);
list_del(&vd->node);
vchan_cookie_complete(vd);
}
- spin_unlock_irqrestore(&chan->vc.lock, flags);
chan->request = EDMA_REQ_NONE;
chan->status = EDMA_ST_IDLE;
+ spin_unlock_irqrestore(&chan->vc.lock, flags);
+}
+
+static void dw_edma_irq_work(struct work_struct *work)
+{
+ struct dw_edma_chan *chan = container_of(work, struct dw_edma_chan,
+ irq_work);
+ unsigned int events;
+
+ do {
+ events = atomic_xchg(&chan->irq_pending, 0);
+
+ if (events & DW_EDMA_IRQ_DONE)
+ dw_edma_done_interrupt(chan);
+ if (events & DW_EDMA_IRQ_ABORT)
+ dw_edma_abort_interrupt(chan);
+ } while (atomic_read(&chan->irq_pending));
+}
+
+static void dw_edma_queue_irq_work(struct dw_edma_chan *chan,
+ enum dw_edma_irq_event event)
+{
+ atomic_or(event, &chan->irq_pending);
+ queue_work(chan->dw->wq, &chan->irq_work);
+}
+
+static void dw_edma_done_interrupt_deferred(struct dw_edma_chan *chan)
+{
+ dw_edma_queue_irq_work(chan, DW_EDMA_IRQ_DONE);
+}
+
+static void dw_edma_abort_interrupt_deferred(struct dw_edma_chan *chan)
+{
+ dw_edma_queue_irq_work(chan, DW_EDMA_IRQ_ABORT);
}
static void dw_edma_emul_irq_ack(struct irq_data *d)
@@ -739,6 +745,9 @@ static int dw_edma_emul_irq_alloc(struct dw_edma *dw)
chip->db_irq = 0;
chip->db_offset = ~0;
+ if (chip->flags & DW_EDMA_CHIP_PARTIAL)
+ return 0;
+
/*
* Only meaningful when the core provides the deassert sequence
* for interrupt emulation.
@@ -802,8 +811,8 @@ static inline irqreturn_t dw_edma_interrupt_write_inner(int irq, void *data)
struct dw_edma_irq *dw_irq = data;
return dw_edma_core_handle_int(dw_irq, EDMA_DIR_WRITE,
- dw_edma_done_interrupt,
- dw_edma_abort_interrupt);
+ dw_edma_done_interrupt_deferred,
+ dw_edma_abort_interrupt_deferred);
}
static inline irqreturn_t dw_edma_interrupt_read_inner(int irq, void *data)
@@ -811,8 +820,8 @@ static inline irqreturn_t dw_edma_interrupt_read_inner(int irq, void *data)
struct dw_edma_irq *dw_irq = data;
return dw_edma_core_handle_int(dw_irq, EDMA_DIR_READ,
- dw_edma_done_interrupt,
- dw_edma_abort_interrupt);
+ dw_edma_done_interrupt_deferred,
+ dw_edma_abort_interrupt_deferred);
}
static inline irqreturn_t dw_edma_interrupt_write(int irq, void *data)
@@ -856,21 +865,51 @@ static int dw_edma_alloc_chan_resources(struct dma_chan *dchan)
return 0;
}
-static void dw_edma_free_chan_resources(struct dma_chan *dchan)
+static void dw_edma_wait_termination(struct dma_chan *dchan)
{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
unsigned long timeout = jiffies + msecs_to_jiffies(5000);
- int ret;
+ bool stopping;
+ /*
+ * A STOP may be deferred to a later interrupt while the channel is still
+ * running. Wait until that handler completes the termination.
+ */
while (time_before(jiffies, timeout)) {
- ret = dw_edma_device_terminate_all(dchan);
- if (!ret)
- break;
+ scoped_guard(spinlock_irqsave, &chan->vc.lock)
+ stopping = chan->request == EDMA_REQ_STOP;
- if (time_after_eq(jiffies, timeout))
+ if (!stopping)
return;
- cpu_relax();
+ fsleep(1000);
}
+
+ dev_warn(chan->dw->chip->dev,
+ "timeout waiting for channel termination\n");
+}
+
+static void dw_edma_device_synchronize(struct dma_chan *dchan)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+
+ dw_edma_wait_termination(dchan);
+ cancel_work_sync(&chan->irq_work);
+ atomic_set(&chan->irq_pending, 0);
+ vchan_synchronize(&chan->vc);
+}
+
+static void dw_edma_free_chan_resources(struct dma_chan *dchan)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+
+ dw_edma_device_terminate_all(dchan);
+ dw_edma_device_synchronize(dchan);
+
+ scoped_guard(spinlock_irqsave, &chan->vc.lock)
+ chan->configured = false;
+
+ vchan_free_chan_resources(&chan->vc);
}
static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc)
@@ -892,6 +931,7 @@ static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc)
chan = &dw->chan[i];
chan->dw = dw;
+ chan->func_no = chip->func_no;
if (i < dw->wr_ch_cnt) {
chan->id = i;
@@ -904,12 +944,16 @@ static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc)
chan->configured = false;
chan->request = EDMA_REQ_NONE;
chan->status = EDMA_ST_IDLE;
+ chan->irq_mode = dw_edma_get_default_irq_mode(chan);
+ INIT_WORK(&chan->irq_work, dw_edma_irq_work);
+ atomic_set(&chan->irq_pending, 0);
if (chan->dir == EDMA_DIR_WRITE)
- chan->ll_max = (chip->ll_region_wr[chan->id].sz / EDMA_LL_SZ);
+ chan->ll_region = chip->ll_region_wr[chan->id];
else
- chan->ll_max = (chip->ll_region_rd[chan->id].sz / EDMA_LL_SZ);
- chan->ll_max -= 1;
+ chan->ll_region = chip->ll_region_rd[chan->id];
+
+ chan->ll_max = chan->ll_region.sz / EDMA_LL_SZ - 1;
dev_vdbg(dev, "L. List:\tChannel %s[%u] max_cnt=%u\n",
str_write_read(chan->dir == EDMA_DIR_WRITE),
@@ -925,11 +969,10 @@ static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc)
irq = &dw->irq[pos];
if (chan->dir == EDMA_DIR_WRITE)
- irq->wr_mask |= BIT(chan->id);
+ bitmap_set(irq->wr_mask, chan->id, 1);
else
- irq->rd_mask |= BIT(chan->id);
+ bitmap_set(irq->rd_mask, chan->id, 1);
- irq->dw = dw;
memcpy(&chan->msi, &irq->msi, sizeof(chan->msi));
dev_vdbg(dev, "MSI:\t\tChannel %s[%u] addr=0x%.8x%.8x, data=0x%.8x\n",
@@ -968,9 +1011,10 @@ static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc)
dma->device_pause = dw_edma_device_pause;
dma->device_resume = dw_edma_device_resume;
dma->device_terminate_all = dw_edma_device_terminate_all;
+ dma->device_synchronize = dw_edma_device_synchronize;
dma->device_issue_pending = dw_edma_device_issue_pending;
dma->device_tx_status = dw_edma_device_tx_status;
- dma->device_prep_slave_sg = dw_edma_device_prep_slave_sg;
+ dma->device_prep_config_sg = dw_edma_device_prep_config_sg;
dma->device_prep_dma_cyclic = dw_edma_device_prep_dma_cyclic;
dma->device_prep_interleaved_dma = dw_edma_device_prep_interleaved_dma;
@@ -1010,6 +1054,7 @@ static int dw_edma_irq_request(struct dw_edma *dw,
if (chip->nr_irqs == 1) {
/* Common IRQ shared among all channels */
irq = chip->ops->irq_vector(dev, 0);
+ dw->irq[0].dw = dw;
err = request_irq(irq, dw_edma_interrupt_common,
IRQF_SHARED, dw->name, &dw->irq[0]);
if (err) {
@@ -1032,6 +1077,7 @@ static int dw_edma_irq_request(struct dw_edma *dw,
for (i = 0; i < (*wr_alloc + *rd_alloc); i++) {
irq = chip->ops->irq_vector(dev, i);
+ dw->irq[i].dw = dw;
err = request_irq(irq,
i < *wr_alloc ?
dw_edma_interrupt_write :
@@ -1062,12 +1108,37 @@ err_irq_free:
return err;
}
+static int dw_edma_check_partial(struct dw_edma_chip *chip,
+ u16 hw_wr_ch_cnt, u16 hw_rd_ch_cnt)
+{
+ if (!(chip->flags & DW_EDMA_CHIP_PARTIAL))
+ return 0;
+
+ if (chip->mf != EDMA_MF_EDMA_UNROLL &&
+ chip->mf != EDMA_MF_HDMA_COMPAT)
+ return 0;
+
+ /*
+ * Direction-wide registers are shared by all channels in that
+ * direction, so a direction must have a single owner.
+ */
+ if ((chip->ll_wr_cnt && chip->ll_wr_cnt != hw_wr_ch_cnt) ||
+ (chip->ll_rd_cnt && chip->ll_rd_cnt != hw_rd_ch_cnt))
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
int dw_edma_probe(struct dw_edma_chip *chip)
{
struct device *dev;
struct dw_edma *dw;
+ u16 hw_wr_ch_cnt;
+ u16 hw_rd_ch_cnt;
u32 wr_alloc = 0;
u32 rd_alloc = 0;
+ u16 max_wr_cnt;
+ u16 max_rd_cnt;
int i, err;
if (!chip)
@@ -1077,26 +1148,50 @@ int dw_edma_probe(struct dw_edma_chip *chip)
if (!dev || !chip->ops)
return -EINVAL;
+ if (chip->flags & DW_EDMA_CHIP_PARTIAL) {
+ switch (chip->mf) {
+ case EDMA_MF_EDMA_UNROLL:
+ case EDMA_MF_HDMA_COMPAT:
+ case EDMA_MF_HDMA_NATIVE:
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ }
+
dw = devm_kzalloc(dev, sizeof(*dw), GFP_KERNEL);
if (!dw)
return -ENOMEM;
dw->chip = chip;
- if (dw->chip->mf == EDMA_MF_HDMA_NATIVE)
+ if (dw->chip->mf == EDMA_MF_HDMA_NATIVE) {
dw_hdma_v0_core_register(dw);
- else
+ max_wr_cnt = HDMA_MAX_WR_CH;
+ max_rd_cnt = HDMA_MAX_RD_CH;
+ } else {
dw_edma_v0_core_register(dw);
+ max_wr_cnt = EDMA_MAX_WR_CH;
+ max_rd_cnt = EDMA_MAX_RD_CH;
+ }
raw_spin_lock_init(&dw->lock);
- dw->wr_ch_cnt = min_t(u16, chip->ll_wr_cnt,
- dw_edma_core_ch_count(dw, EDMA_DIR_WRITE));
- dw->wr_ch_cnt = min_t(u16, dw->wr_ch_cnt, EDMA_MAX_WR_CH);
+ /*
+ * chip->ll_*_cnt describes the channels exposed by this instance. Keep
+ * the usable hardware counts separate for partial ownership checks.
+ */
+ hw_wr_ch_cnt = min(dw_edma_core_ch_count(dw, EDMA_DIR_WRITE),
+ max_wr_cnt);
+ hw_rd_ch_cnt = min(dw_edma_core_ch_count(dw, EDMA_DIR_READ),
+ max_rd_cnt);
+
+ err = dw_edma_check_partial(chip, hw_wr_ch_cnt, hw_rd_ch_cnt);
+ if (err)
+ return err;
- dw->rd_ch_cnt = min_t(u16, chip->ll_rd_cnt,
- dw_edma_core_ch_count(dw, EDMA_DIR_READ));
- dw->rd_ch_cnt = min_t(u16, dw->rd_ch_cnt, EDMA_MAX_RD_CH);
+ dw->wr_ch_cnt = min(chip->ll_wr_cnt, hw_wr_ch_cnt);
+ dw->rd_ch_cnt = min(chip->ll_rd_cnt, hw_rd_ch_cnt);
if (!dw->wr_ch_cnt && !dw->rd_ch_cnt)
return -EINVAL;
@@ -1113,13 +1208,34 @@ int dw_edma_probe(struct dw_edma_chip *chip)
snprintf(dw->name, sizeof(dw->name), "dw-edma-core:%s",
dev_name(chip->dev));
- /* Disable eDMA, only to establish the ideal initial conditions */
- dw_edma_core_off(dw);
+ if (chip->flags & DW_EDMA_CHIP_PARTIAL) {
+ /*
+ * Do not reset the shared controller, but drain stale state
+ * from resources represented by this instance.
+ */
+ err = dw_edma_core_quiesce(dw);
+ if (err)
+ return err;
+ } else {
+ /* Disable eDMA only when this instance owns the controller. */
+ dw_edma_core_off(dw);
+ }
+
+ /*
+ * Deferred IRQ works are queued from the hard IRQ handlers, so the
+ * workqueue must exist before any IRQ is requested.
+ */
+ dw->wq = alloc_workqueue("dw-edma:%s", WQ_UNBOUND | WQ_HIGHPRI, 0,
+ dev_name(chip->dev));
+ if (!dw->wq)
+ return -ENOMEM;
/* Request IRQs */
err = dw_edma_irq_request(dw, &wr_alloc, &rd_alloc);
- if (err)
+ if (err) {
+ destroy_workqueue(dw->wq);
return err;
+ }
/* Allocate a dedicated virtual IRQ for interrupt-emulation doorbells */
err = dw_edma_emul_irq_alloc(dw);
@@ -1142,6 +1258,7 @@ err_irq_free:
for (i = (dw->nr_irqs - 1); i >= 0; i--)
free_irq(chip->ops->irq_vector(dev, i), &dw->irq[i]);
dw_edma_emul_irq_free(dw);
+ destroy_workqueue(dw->wq);
return err;
}
@@ -1152,20 +1269,27 @@ int dw_edma_remove(struct dw_edma_chip *chip)
struct dw_edma_chan *chan, *_chan;
struct device *dev = chip->dev;
struct dw_edma *dw = chip->dw;
- int i;
+ int i, err = 0;
/* Skip removal if no private data found */
if (!dw)
return -ENODEV;
- /* Disable eDMA */
- dw_edma_core_off(dw);
+ if (chip->flags & DW_EDMA_CHIP_PARTIAL)
+ err = dw_edma_core_quiesce(dw);
+ else
+ dw_edma_core_off(dw);
/* Free irqs */
for (i = (dw->nr_irqs - 1); i >= 0; i--)
free_irq(chip->ops->irq_vector(dev, i), &dw->irq[i]);
dw_edma_emul_irq_free(dw);
+ for (i = 0; i < dw->wr_ch_cnt + dw->rd_ch_cnt; i++)
+ cancel_work_sync(&dw->chan[i].irq_work);
+
+ destroy_workqueue(dw->wq);
+
/* Deregister eDMA device */
dma_async_device_unregister(&dw->dma);
list_for_each_entry_safe(chan, _chan, &dw->dma.channels,
@@ -1174,7 +1298,7 @@ int dw_edma_remove(struct dw_edma_chip *chip)
list_del(&chan->vc.chan.device_node);
}
- return 0;
+ return err;
}
EXPORT_SYMBOL_GPL(dw_edma_remove);
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index 6474cacf7195..f6a5ad317567 100644
--- a/drivers/dma/dw-edma/dw-edma-core.h
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -9,8 +9,10 @@
#ifndef _DW_EDMA_CORE_H
#define _DW_EDMA_CORE_H
+#include <linux/atomic.h>
#include <linux/msi.h>
#include <linux/dma/edma.h>
+#include <linux/workqueue.h>
#include "../virt-dma.h"
@@ -43,32 +45,24 @@ struct dw_edma_chan;
struct dw_edma_chunk;
struct dw_edma_burst {
- struct list_head list;
u64 sar;
u64 dar;
u32 sz;
-};
-
-struct dw_edma_chunk {
- struct list_head list;
- struct dw_edma_chan *chan;
- struct dw_edma_burst *burst;
-
- u32 bursts_alloc;
-
- u8 cb;
- struct dw_edma_region ll_region; /* Linked list */
+ /* precalulate summary of previous burst total size */
+ u32 xfer_sz;
};
struct dw_edma_desc {
struct virt_dma_desc vd;
struct dw_edma_chan *chan;
- struct dw_edma_chunk *chunk;
-
- u32 chunks_alloc;
u32 alloc_sz;
- u32 xfer_sz;
+
+ size_t done_burst;
+ size_t start_burst;
+ u8 cb;
+ size_t nburst;
+ struct dw_edma_burst burst[] __counted_by(nburst);
};
struct dw_edma_chan {
@@ -76,24 +70,32 @@ struct dw_edma_chan {
struct dw_edma *dw;
int id;
enum dw_edma_dir dir;
+ u8 func_no;
u32 ll_max;
+ struct dw_edma_region ll_region; /* Linked list */
struct msi_msg msi;
+ enum dw_edma_ch_irq_mode irq_mode;
+
enum dw_edma_request request;
enum dw_edma_status status;
u8 configured;
struct dma_slave_config config;
bool non_ll;
+
+ struct work_struct irq_work;
+ atomic_t irq_pending;
};
struct dw_edma_irq {
struct msi_msg msi;
- u32 wr_mask;
- u32 rd_mask;
struct dw_edma *dw;
+
+ DECLARE_BITMAP(wr_mask, HDMA_MAX_WR_CH);
+ DECLARE_BITMAP(rd_mask, HDMA_MAX_RD_CH);
};
struct dw_edma {
@@ -109,6 +111,12 @@ struct dw_edma {
struct dw_edma_chan *chan;
+ /*
+ * WQ_HIGHPRI keeps completion processing responsive under heavy load;
+ * WQ_UNBOUND lets different channels run on different CPUs.
+ */
+ struct workqueue_struct *wq;
+
raw_spinlock_t lock; /* Protect v0 shared registers */
struct dw_edma_chip *chip;
@@ -120,11 +128,18 @@ typedef void (*dw_edma_handler_t)(struct dw_edma_chan *);
struct dw_edma_core_ops {
void (*off)(struct dw_edma *dw);
+ int (*quiesce)(struct dw_edma *dw);
+ int (*ch_quiesce)(struct dw_edma_chan *chan);
u16 (*ch_count)(struct dw_edma *dw, enum dw_edma_dir dir);
enum dma_status (*ch_status)(struct dw_edma_chan *chan);
irqreturn_t (*handle_int)(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
dw_edma_handler_t done, dw_edma_handler_t abort);
- void (*start)(struct dw_edma_chunk *chunk, bool first);
+ void (*non_ll_start)(struct dw_edma_chan *chan, struct dw_edma_burst *child);
+ void (*ll_data)(struct dw_edma_chan *chan, struct dw_edma_burst *burst,
+ u32 idx, bool cb, bool irq);
+ void (*ll_link)(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr);
+ void (*ch_doorbell)(struct dw_edma_chan *chan);
+ void (*ch_enable)(struct dw_edma_chan *chan);
void (*ch_config)(struct dw_edma_chan *chan);
void (*debugfs_on)(struct dw_edma *dw);
void (*ack_emulated_irq)(struct dw_edma *dw);
@@ -166,6 +181,14 @@ struct dw_edma_chan *dchan2dw_edma_chan(struct dma_chan *dchan)
return vc2dw_edma_chan(to_virt_chan(dchan));
}
+static inline u64 dw_edma_core_get_ll_paddr(struct dw_edma_chan *chan)
+{
+ if (chan->dir == EDMA_DIR_WRITE)
+ return chan->dw->chip->ll_region_wr[chan->id].paddr;
+
+ return chan->dw->chip->ll_region_rd[chan->id].paddr;
+}
+
static inline
void dw_edma_core_off(struct dw_edma *dw)
{
@@ -173,6 +196,18 @@ void dw_edma_core_off(struct dw_edma *dw)
}
static inline
+int dw_edma_core_quiesce(struct dw_edma *dw)
+{
+ return dw->core->quiesce(dw);
+}
+
+static inline
+int dw_edma_core_ch_quiesce(struct dw_edma_chan *chan)
+{
+ return chan->dw->core->ch_quiesce(chan);
+}
+
+static inline
u16 dw_edma_core_ch_count(struct dw_edma *dw, enum dw_edma_dir dir)
{
return dw->core->ch_count(dw, dir);
@@ -192,15 +227,32 @@ dw_edma_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
}
static inline
-void dw_edma_core_start(struct dw_edma *dw, struct dw_edma_chunk *chunk, bool first)
+void dw_edma_core_ch_config(struct dw_edma_chan *chan)
{
- dw->core->start(chunk, first);
+ chan->dw->core->ch_config(chan);
}
-static inline
-void dw_edma_core_ch_config(struct dw_edma_chan *chan)
+static inline void
+dw_edma_core_ll_data(struct dw_edma_chan *chan, struct dw_edma_burst *burst,
+ u32 idx, bool cb, bool irq)
{
- chan->dw->core->ch_config(chan);
+ chan->dw->core->ll_data(chan, burst, idx, cb, irq);
+}
+
+static inline void
+dw_edma_core_ll_link(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr)
+{
+ chan->dw->core->ll_link(chan, idx, cb, addr);
+}
+
+static inline void dw_edma_core_ch_doorbell(struct dw_edma_chan *chan)
+{
+ chan->dw->core->ch_doorbell(chan);
+}
+
+static inline void dw_edma_core_ch_enable(struct dw_edma_chan *chan)
+{
+ chan->dw->core->ch_enable(chan);
}
static inline
@@ -224,4 +276,15 @@ dw_edma_core_db_offset(struct dw_edma *dw)
return dw->core->db_offset(dw);
}
+static inline bool
+dw_edma_core_ch_ignore_irq(struct dw_edma_chan *chan)
+{
+ struct dw_edma *dw = chan->dw;
+
+ if (dw->chip->flags & DW_EDMA_CHIP_LOCAL)
+ return chan->irq_mode == DW_EDMA_CH_IRQ_REMOTE;
+ else
+ return chan->irq_mode == DW_EDMA_CH_IRQ_LOCAL;
+}
+
#endif /* _DW_EDMA_CORE_H */
diff --git a/drivers/dma/dw-edma/dw-edma-pcie.c b/drivers/dma/dw-edma/dw-edma-pcie.c
index 791c46e8ae4c..9f237ba916de 100644
--- a/drivers/dma/dw-edma/dw-edma-pcie.c
+++ b/drivers/dma/dw-edma/dw-edma-pcie.c
@@ -55,6 +55,8 @@
struct dw_edma_block {
enum pci_barno bar;
off_t off;
+ u64 paddr;
+ bool paddr_valid;
size_t sz;
};
@@ -62,19 +64,36 @@ struct dw_edma_pcie_data {
/* eDMA registers location */
struct dw_edma_block rg;
/* eDMA memory linked list location */
- struct dw_edma_block ll_wr[EDMA_MAX_WR_CH];
- struct dw_edma_block ll_rd[EDMA_MAX_RD_CH];
+ struct dw_edma_block ll_wr[HDMA_MAX_WR_CH];
+ struct dw_edma_block ll_rd[HDMA_MAX_RD_CH];
/* eDMA memory data location */
- struct dw_edma_block dt_wr[EDMA_MAX_WR_CH];
- struct dw_edma_block dt_rd[EDMA_MAX_RD_CH];
+ struct dw_edma_block dt_wr[HDMA_MAX_WR_CH];
+ struct dw_edma_block dt_rd[HDMA_MAX_RD_CH];
/* Other */
enum dw_edma_map_format mf;
u8 irqs;
u16 wr_ch_cnt;
u16 rd_ch_cnt;
u64 devmem_phys_off;
+ bool cfg_non_ll;
};
+struct dw_edma_pcie_match_data {
+ const struct dw_edma_pcie_data *data;
+ const struct dw_edma_plat_ops *plat_ops;
+ /*
+ * Mandatory callback. It may leave @pdata unchanged when the static
+ * template already describes the device.
+ */
+ int (*parse_caps)(struct pci_dev *pdev,
+ struct dw_edma_pcie_data *pdata);
+ unsigned long flags;
+ u32 chip_flags;
+};
+
+#define DW_EDMA_PCIE_F_DEVMEM_PHYS_OFF BIT(0)
+#define DW_EDMA_PCIE_F_REG_OFFSET BIT(1)
+
static const struct dw_edma_pcie_data snps_edda_data = {
/* eDMA registers location */
.rg.bar = BAR_0,
@@ -309,31 +328,88 @@ static void dw_edma_pcie_get_xilinx_dma_data(struct pci_dev *pdev,
pdata->devmem_phys_off = off;
}
+static int
+dw_edma_pcie_parse_synopsys_caps(struct pci_dev *pdev,
+ struct dw_edma_pcie_data *pdata)
+{
+ dw_edma_pcie_get_synopsys_dma_data(pdev, pdata);
+
+ return 0;
+}
+
+static int
+dw_edma_pcie_parse_xilinx_caps(struct pci_dev *pdev,
+ struct dw_edma_pcie_data *pdata)
+{
+ dw_edma_pcie_get_xilinx_dma_data(pdev, pdata);
+
+ /*
+ * There is no valid address found for the LL memory space on the
+ * device side. In the absence of LL base address use the non-LL mode or
+ * simple mode supported by the HDMA IP.
+ */
+ if (pdata->devmem_phys_off == DW_PCIE_XILINX_MDB_INVALID_ADDR) {
+ pdata->cfg_non_ll = true;
+ return 0;
+ }
+
+ /*
+ * Configure the channel LL and data blocks if number of channels
+ * enabled in VSEC capability are more than the channels configured in
+ * xilinx_mdb_data.
+ */
+ dw_edma_set_chan_region_offset(pdata, BAR_2, 0,
+ DW_PCIE_XILINX_MDB_LL_OFF_GAP,
+ DW_PCIE_XILINX_MDB_LL_SIZE,
+ DW_PCIE_XILINX_MDB_DT_OFF_GAP,
+ DW_PCIE_XILINX_MDB_DT_SIZE);
+
+ return 0;
+}
+
static u64 dw_edma_get_phys_addr(struct pci_dev *pdev,
+ const struct dw_edma_pcie_match_data *match,
struct dw_edma_pcie_data *pdata,
enum pci_barno bar)
{
- if (pdev->vendor == PCI_VENDOR_ID_XILINX)
+ if (match->flags & DW_EDMA_PCIE_F_DEVMEM_PHYS_OFF)
return pdata->devmem_phys_off;
+
return pci_bus_address(pdev, bar);
}
+static u64 dw_edma_get_block_addr(struct pci_dev *pdev,
+ const struct dw_edma_pcie_match_data *match,
+ struct dw_edma_pcie_data *pdata,
+ const struct dw_edma_block *block)
+{
+ if (block->paddr_valid)
+ return block->paddr;
+
+ return dw_edma_get_phys_addr(pdev, match, pdata, block->bar) +
+ block->off;
+}
+
static int dw_edma_pcie_probe(struct pci_dev *pdev,
const struct pci_device_id *pid)
{
- struct dw_edma_pcie_data *pdata = (void *)pid->driver_data;
+ const struct dw_edma_pcie_match_data *match = (void *)pid->driver_data;
+ const struct dw_edma_pcie_data *pdata;
struct device *dev = &pdev->dev;
struct dw_edma_chip *chip;
int err, nr_irqs;
int i, mask;
- bool non_ll = false;
+
+ if (!match)
+ return -ENODEV;
+ pdata = match->data;
if (!pdata)
return -ENODEV;
- struct dw_edma_pcie_data *vsec_data __free(kfree) =
- kmalloc_obj(*vsec_data);
- if (!vsec_data)
+ struct dw_edma_pcie_data *dma_data __free(kfree) =
+ kmemdup(pdata, sizeof(*dma_data), GFP_KERNEL);
+ if (!dma_data)
return -ENOMEM;
/* Enable PCI device */
@@ -343,48 +419,25 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev,
return err;
}
- memcpy(vsec_data, pdata, sizeof(struct dw_edma_pcie_data));
+ /* Let device-specific discovery override the static template data. */
+ if (!match->parse_caps || !match->plat_ops)
+ return -EINVAL;
- /*
- * Tries to find if exists a PCIe Vendor-Specific Extended Capability
- * for the DMA, if one exists, then reconfigures it.
- */
- dw_edma_pcie_get_synopsys_dma_data(pdev, vsec_data);
-
- if (pdev->vendor == PCI_VENDOR_ID_XILINX) {
- dw_edma_pcie_get_xilinx_dma_data(pdev, vsec_data);
-
- /*
- * There is no valid address found for the LL memory
- * space on the device side. In the absence of LL base
- * address use the non-LL mode or simple mode supported by
- * the HDMA IP.
- */
- if (vsec_data->devmem_phys_off == DW_PCIE_XILINX_MDB_INVALID_ADDR)
- non_ll = true;
-
- /*
- * Configure the channel LL and data blocks if number of
- * channels enabled in VSEC capability are more than the
- * channels configured in xilinx_mdb_data.
- */
- if (!non_ll)
- dw_edma_set_chan_region_offset(vsec_data, BAR_2, 0,
- DW_PCIE_XILINX_MDB_LL_OFF_GAP,
- DW_PCIE_XILINX_MDB_LL_SIZE,
- DW_PCIE_XILINX_MDB_DT_OFF_GAP,
- DW_PCIE_XILINX_MDB_DT_SIZE);
- }
+ err = match->parse_caps(pdev, dma_data);
+ if (err)
+ return err;
/* Mapping PCI BAR regions */
- mask = BIT(vsec_data->rg.bar);
- for (i = 0; i < vsec_data->wr_ch_cnt; i++) {
- mask |= BIT(vsec_data->ll_wr[i].bar);
- mask |= BIT(vsec_data->dt_wr[i].bar);
+ mask = BIT(dma_data->rg.bar);
+ for (i = 0; i < dma_data->wr_ch_cnt; i++) {
+ mask |= BIT(dma_data->ll_wr[i].bar);
+ if (dma_data->dt_wr[i].sz)
+ mask |= BIT(dma_data->dt_wr[i].bar);
}
- for (i = 0; i < vsec_data->rd_ch_cnt; i++) {
- mask |= BIT(vsec_data->ll_rd[i].bar);
- mask |= BIT(vsec_data->dt_rd[i].bar);
+ for (i = 0; i < dma_data->rd_ch_cnt; i++) {
+ mask |= BIT(dma_data->ll_rd[i].bar);
+ if (dma_data->dt_rd[i].sz)
+ mask |= BIT(dma_data->dt_rd[i].bar);
}
err = pcim_iomap_regions(pdev, mask, pci_name(pdev));
if (err) {
@@ -407,7 +460,7 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev,
return -ENOMEM;
/* IRQs allocation */
- nr_irqs = pci_alloc_irq_vectors(pdev, 1, vsec_data->irqs,
+ nr_irqs = pci_alloc_irq_vectors(pdev, 1, dma_data->irqs,
PCI_IRQ_MSI | PCI_IRQ_MSIX);
if (nr_irqs < 1) {
pci_err(pdev, "fail to alloc IRQ vector (number of IRQs=%u)\n",
@@ -418,69 +471,75 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev,
/* Data structure initialization */
chip->dev = dev;
- chip->mf = vsec_data->mf;
+ chip->mf = dma_data->mf;
+ chip->flags = match->chip_flags;
+ chip->func_no = PCI_FUNC(pdev->devfn);
chip->nr_irqs = nr_irqs;
- chip->ops = &dw_edma_pcie_plat_ops;
- chip->cfg_non_ll = non_ll;
+ chip->ops = match->plat_ops;
+ chip->cfg_non_ll = dma_data->cfg_non_ll;
- chip->ll_wr_cnt = vsec_data->wr_ch_cnt;
- chip->ll_rd_cnt = vsec_data->rd_ch_cnt;
+ chip->ll_wr_cnt = dma_data->wr_ch_cnt;
+ chip->ll_rd_cnt = dma_data->rd_ch_cnt;
- chip->reg_base = pcim_iomap_table(pdev)[vsec_data->rg.bar];
+ chip->reg_base = pcim_iomap_table(pdev)[dma_data->rg.bar];
if (!chip->reg_base)
return -ENOMEM;
+ if (match->flags & DW_EDMA_PCIE_F_REG_OFFSET)
+ chip->reg_base += dma_data->rg.off;
- for (i = 0; i < chip->ll_wr_cnt && !non_ll; i++) {
+ for (i = 0; i < chip->ll_wr_cnt && !dma_data->cfg_non_ll; i++) {
struct dw_edma_region *ll_region = &chip->ll_region_wr[i];
struct dw_edma_region *dt_region = &chip->dt_region_wr[i];
- struct dw_edma_block *ll_block = &vsec_data->ll_wr[i];
- struct dw_edma_block *dt_block = &vsec_data->dt_wr[i];
+ struct dw_edma_block *ll_block = &dma_data->ll_wr[i];
+ struct dw_edma_block *dt_block = &dma_data->dt_wr[i];
ll_region->vaddr.io = pcim_iomap_table(pdev)[ll_block->bar];
if (!ll_region->vaddr.io)
return -ENOMEM;
ll_region->vaddr.io += ll_block->off;
- ll_region->paddr = dw_edma_get_phys_addr(pdev, vsec_data,
- ll_block->bar);
- ll_region->paddr += ll_block->off;
+ ll_region->paddr = dw_edma_get_block_addr(pdev, match, dma_data,
+ ll_block);
ll_region->sz = ll_block->sz;
+ if (!dt_block->sz)
+ continue;
+
dt_region->vaddr.io = pcim_iomap_table(pdev)[dt_block->bar];
if (!dt_region->vaddr.io)
return -ENOMEM;
dt_region->vaddr.io += dt_block->off;
- dt_region->paddr = dw_edma_get_phys_addr(pdev, vsec_data,
- dt_block->bar);
- dt_region->paddr += dt_block->off;
+ dt_region->paddr = dw_edma_get_block_addr(pdev, match, dma_data,
+ dt_block);
dt_region->sz = dt_block->sz;
}
- for (i = 0; i < chip->ll_rd_cnt && !non_ll; i++) {
+ for (i = 0; i < chip->ll_rd_cnt && !dma_data->cfg_non_ll; i++) {
struct dw_edma_region *ll_region = &chip->ll_region_rd[i];
struct dw_edma_region *dt_region = &chip->dt_region_rd[i];
- struct dw_edma_block *ll_block = &vsec_data->ll_rd[i];
- struct dw_edma_block *dt_block = &vsec_data->dt_rd[i];
+ struct dw_edma_block *ll_block = &dma_data->ll_rd[i];
+ struct dw_edma_block *dt_block = &dma_data->dt_rd[i];
ll_region->vaddr.io = pcim_iomap_table(pdev)[ll_block->bar];
if (!ll_region->vaddr.io)
return -ENOMEM;
ll_region->vaddr.io += ll_block->off;
- ll_region->paddr = dw_edma_get_phys_addr(pdev, vsec_data,
- ll_block->bar);
- ll_region->paddr += ll_block->off;
+ ll_region->paddr = dw_edma_get_block_addr(pdev, match, dma_data,
+ ll_block);
ll_region->sz = ll_block->sz;
+ if (!dt_block->sz)
+ continue;
+
dt_region->vaddr.io = pcim_iomap_table(pdev)[dt_block->bar];
if (!dt_region->vaddr.io)
return -ENOMEM;
dt_region->vaddr.io += dt_block->off;
- dt_region->paddr = dw_edma_get_phys_addr(pdev, vsec_data,
- dt_block->bar);
- dt_region->paddr += dt_block->off;
+ dt_region->paddr = dw_edma_get_block_addr(pdev, match, dma_data,
+ dt_block);
dt_region->sz = dt_block->sz;
}
@@ -497,32 +556,40 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev,
pci_dbg(pdev, "Version:\tUnknown (0x%x)\n", chip->mf);
pci_dbg(pdev, "Registers:\tBAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p)\n",
- vsec_data->rg.bar, vsec_data->rg.off, vsec_data->rg.sz,
+ dma_data->rg.bar, dma_data->rg.off, dma_data->rg.sz,
chip->reg_base);
for (i = 0; i < chip->ll_wr_cnt; i++) {
pci_dbg(pdev, "L. List:\tWRITE CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n",
- i, vsec_data->ll_wr[i].bar,
- vsec_data->ll_wr[i].off, chip->ll_region_wr[i].sz,
+ i, dma_data->ll_wr[i].bar,
+ dma_data->ll_wr[i].off, chip->ll_region_wr[i].sz,
chip->ll_region_wr[i].vaddr.io, &chip->ll_region_wr[i].paddr);
+ if (!dma_data->dt_wr[i].sz)
+ continue;
+
pci_dbg(pdev, "Data:\tWRITE CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n",
- i, vsec_data->dt_wr[i].bar,
- vsec_data->dt_wr[i].off, chip->dt_region_wr[i].sz,
- chip->dt_region_wr[i].vaddr.io, &chip->dt_region_wr[i].paddr);
+ i, dma_data->dt_wr[i].bar,
+ dma_data->dt_wr[i].off, chip->dt_region_wr[i].sz,
+ chip->dt_region_wr[i].vaddr.io,
+ &chip->dt_region_wr[i].paddr);
}
for (i = 0; i < chip->ll_rd_cnt; i++) {
pci_dbg(pdev, "L. List:\tREAD CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n",
- i, vsec_data->ll_rd[i].bar,
- vsec_data->ll_rd[i].off, chip->ll_region_rd[i].sz,
+ i, dma_data->ll_rd[i].bar,
+ dma_data->ll_rd[i].off, chip->ll_region_rd[i].sz,
chip->ll_region_rd[i].vaddr.io, &chip->ll_region_rd[i].paddr);
+ if (!dma_data->dt_rd[i].sz)
+ continue;
+
pci_dbg(pdev, "Data:\tREAD CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n",
- i, vsec_data->dt_rd[i].bar,
- vsec_data->dt_rd[i].off, chip->dt_region_rd[i].sz,
- chip->dt_region_rd[i].vaddr.io, &chip->dt_region_rd[i].paddr);
+ i, dma_data->dt_rd[i].bar,
+ dma_data->dt_rd[i].off, chip->dt_region_rd[i].sz,
+ chip->dt_region_rd[i].vaddr.io,
+ &chip->dt_region_rd[i].paddr);
}
pci_dbg(pdev, "Nr. IRQs:\t%u\n", chip->nr_irqs);
@@ -555,17 +622,34 @@ static void dw_edma_pcie_remove(struct pci_dev *pdev)
err = dw_edma_remove(chip);
if (err)
pci_warn(pdev, "can't remove device properly: %d\n", err);
-
- /* Freeing IRQs */
- pci_free_irq_vectors(pdev);
}
+static const struct dw_edma_pcie_match_data snps_edda_match_data = {
+ .data = &snps_edda_data,
+ .plat_ops = &dw_edma_pcie_plat_ops,
+ .parse_caps = dw_edma_pcie_parse_synopsys_caps,
+};
+
+static const struct dw_edma_pcie_match_data xilinx_mdb_match_data = {
+ .data = &xilinx_mdb_data,
+ .plat_ops = &dw_edma_pcie_plat_ops,
+ .parse_caps = dw_edma_pcie_parse_xilinx_caps,
+ .flags = DW_EDMA_PCIE_F_DEVMEM_PHYS_OFF,
+};
+
+static const struct dw_edma_pcie_match_data xilinx_cpm6_dma_match_data = {
+ .data = &xilinx_cpm6_dma_data,
+ .plat_ops = &dw_edma_pcie_plat_ops,
+ .parse_caps = dw_edma_pcie_parse_xilinx_caps,
+ .flags = DW_EDMA_PCIE_F_DEVMEM_PHYS_OFF,
+};
+
static const struct pci_device_id dw_edma_pcie_id_table[] = {
- { PCI_DEVICE_DATA(SYNOPSYS, EDDA, &snps_edda_data) },
+ { PCI_DEVICE_DATA(SYNOPSYS, EDDA, &snps_edda_match_data) },
{ PCI_VDEVICE(XILINX, PCI_DEVICE_ID_XILINX_B054),
- (kernel_ulong_t)&xilinx_mdb_data },
+ .driver_data = (kernel_ulong_t)&xilinx_mdb_match_data },
{ PCI_VDEVICE(XILINX, PCI_DEVICE_ID_XILINX_B00F),
- .driver_data = (kernel_ulong_t)&xilinx_cpm6_dma_data },
+ .driver_data = (kernel_ulong_t)&xilinx_cpm6_dma_match_data },
{ }
};
MODULE_DEVICE_TABLE(pci, dw_edma_pcie_id_table);
diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
index cfdd6463252e..abc1bd4c0be2 100644
--- a/drivers/dma/dw-edma/dw-edma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
@@ -7,6 +7,7 @@
*/
#include <linux/bitfield.h>
+#include <linux/iopoll.h>
#include <linux/irqreturn.h>
#include <linux/io-64-nonatomic-lo-hi.h>
@@ -25,6 +26,8 @@ enum dw_edma_control {
DW_EDMA_V0_LLE = BIT(9),
};
+#define EDMA_V0_FUNC_NUM_MASK GENMASK(16, 12)
+
static inline struct dw_edma_v0_regs __iomem *__dw_regs(struct dw_edma *dw)
{
return dw->chip->reg_base;
@@ -159,7 +162,93 @@ static inline u32 readl_ch(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch,
#define GET_CH_32(dw, dir, ch, name) \
readl_ch(dw, dir, ch, &(__dw_ch_regs(dw, dir, ch)->name))
+static u32 dw_edma_v0_func_num(struct dw_edma_chan *chan)
+{
+ return FIELD_PREP(EDMA_V0_FUNC_NUM_MASK, chan->func_no);
+}
+
/* eDMA management callbacks */
+static void dw_edma_v0_core_ch_power(struct dw_edma *dw,
+ enum dw_edma_dir dir, u16 id, bool enable)
+{
+ u32 value = enable ? BIT(0) : 0;
+
+ if (WARN_ON_ONCE(id >= EDMA_V0_MAX_NR_CH))
+ return;
+
+ switch (id) {
+ case 0:
+ SET_RW_COMPAT(dw, dir, ch0_pwr_en, value);
+ break;
+ case 1:
+ SET_RW_COMPAT(dw, dir, ch1_pwr_en, value);
+ break;
+ case 2:
+ SET_RW_COMPAT(dw, dir, ch2_pwr_en, value);
+ break;
+ case 3:
+ SET_RW_COMPAT(dw, dir, ch3_pwr_en, value);
+ break;
+ case 4:
+ SET_RW_COMPAT(dw, dir, ch4_pwr_en, value);
+ break;
+ case 5:
+ SET_RW_COMPAT(dw, dir, ch5_pwr_en, value);
+ break;
+ case 6:
+ SET_RW_COMPAT(dw, dir, ch6_pwr_en, value);
+ break;
+ case 7:
+ SET_RW_COMPAT(dw, dir, ch7_pwr_en, value);
+ break;
+ }
+}
+
+static int dw_edma_v0_core_engine_disable(struct dw_edma *dw,
+ enum dw_edma_dir dir)
+{
+ u32 value;
+ int ret;
+
+ SET_RW_32(dw, dir, engine_en, 0);
+ ret = read_poll_timeout(GET_RW_32, value, !(value & BIT(0)), 100,
+ 200000, false, dw, dir, engine_en);
+ if (ret)
+ dev_warn(dw->chip->dev, "%s engine did not stop within 200ms\n",
+ dir == EDMA_DIR_WRITE ? "write" : "read");
+
+ return ret;
+}
+
+static int dw_edma_v0_core_dir_off(struct dw_edma *dw, enum dw_edma_dir dir)
+{
+ u16 count, id;
+ int ret = 0;
+
+ scoped_guard(raw_spinlock_irqsave, &dw->lock)
+ SET_RW_32(dw, dir, int_mask,
+ EDMA_V0_DONE_INT_MASK | EDMA_V0_ABORT_INT_MASK);
+
+ if (dw->chip->mf == EDMA_MF_HDMA_COMPAT) {
+ /*
+ * DWC PCIe Controller Databook 6.10a-lca06, "Legacy DMA
+ * and HDMA Software Compatibility": HDMA compatibility mode
+ * does not implement ENGINE_EN, but retains CHi_PWR_EN for
+ * per-channel enable and disable.
+ */
+ count = dir == EDMA_DIR_WRITE ? dw->wr_ch_cnt : dw->rd_ch_cnt;
+ for (id = 0; id < count; id++)
+ dw_edma_v0_core_ch_power(dw, dir, id, false);
+ } else {
+ ret = dw_edma_v0_core_engine_disable(dw, dir);
+ }
+
+ SET_RW_32(dw, dir, int_clear,
+ EDMA_V0_DONE_INT_MASK | EDMA_V0_ABORT_INT_MASK);
+
+ return ret;
+}
+
static void dw_edma_v0_core_off(struct dw_edma *dw)
{
SET_BOTH_32(dw, int_mask,
@@ -169,6 +258,33 @@ static void dw_edma_v0_core_off(struct dw_edma *dw)
SET_BOTH_32(dw, engine_en, 0);
}
+static int dw_edma_v0_core_quiesce(struct dw_edma *dw)
+{
+ int ret = 0;
+ int err;
+
+ if (dw->wr_ch_cnt)
+ ret = dw_edma_v0_core_dir_off(dw, EDMA_DIR_WRITE);
+ if (dw->rd_ch_cnt) {
+ err = dw_edma_v0_core_dir_off(dw, EDMA_DIR_READ);
+ if (!ret)
+ ret = err;
+ }
+
+ return ret;
+}
+
+/*
+ * The unrolled eDMA and HDMA compatibility register maps share interrupt
+ * control per direction, so the whole direction is quiesced. Callers must
+ * own the direction entirely and prevent the peer from programming it after
+ * this point. Partial ownership mode validates direction granularity.
+ */
+static int dw_edma_v0_core_ch_quiesce(struct dw_edma_chan *chan)
+{
+ return dw_edma_v0_core_dir_off(chan->dw, chan->dir);
+}
+
static u16 dw_edma_v0_core_ch_count(struct dw_edma *dw, enum dw_edma_dir dir)
{
u32 num_ch;
@@ -218,18 +334,6 @@ static void dw_edma_v0_core_clear_abort_int(struct dw_edma_chan *chan)
FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id)));
}
-static u32 dw_edma_v0_core_status_done_int(struct dw_edma *dw, enum dw_edma_dir dir)
-{
- return FIELD_GET(EDMA_V0_DONE_INT_MASK,
- GET_RW_32(dw, dir, int_status));
-}
-
-static u32 dw_edma_v0_core_status_abort_int(struct dw_edma *dw, enum dw_edma_dir dir)
-{
- return FIELD_GET(EDMA_V0_ABORT_INT_MASK,
- GET_RW_32(dw, dir, int_status));
-}
-
static irqreturn_t
dw_edma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
dw_edma_handler_t done, dw_edma_handler_t abort)
@@ -239,7 +343,8 @@ dw_edma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
irqreturn_t ret = IRQ_NONE;
struct dw_edma_chan *chan;
unsigned long off;
- u32 mask;
+ unsigned long *mask;
+ u32 sts;
if (dir == EDMA_DIR_WRITE) {
total = dw->wr_ch_cnt;
@@ -251,22 +356,38 @@ dw_edma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
mask = dw_irq->rd_mask;
}
- val = dw_edma_v0_core_status_done_int(dw, dir);
- val &= mask;
+ /*
+ * DONE and ABORT status share one register, and on remote setups
+ * every read is a non-posted round trip across the PCIe link. Take
+ * one snapshot and derive both views from it. An abort raised
+ * after the snapshot is deferred, not lost: only bits observed in
+ * the snapshot are ever cleared below, so its status remains set and
+ * triggers another handler pass.
+ */
+ sts = GET_RW_32(dw, dir, int_status);
+
+ val = FIELD_GET(EDMA_V0_DONE_INT_MASK, sts);
+ val &= *mask;
for_each_set_bit(pos, &val, total) {
chan = &dw->chan[pos + off];
+ if (unlikely(dw_edma_core_ch_ignore_irq(chan)))
+ continue;
+
dw_edma_v0_core_clear_done_int(chan);
done(chan);
ret = IRQ_HANDLED;
}
- val = dw_edma_v0_core_status_abort_int(dw, dir);
- val &= mask;
+ val = FIELD_GET(EDMA_V0_ABORT_INT_MASK, sts);
+ val &= *mask;
for_each_set_bit(pos, &val, total) {
chan = &dw->chan[pos + off];
+ if (unlikely(dw_edma_core_ch_ignore_irq(chan)))
+ continue;
+
dw_edma_v0_core_clear_abort_int(chan);
abort(chan);
@@ -276,77 +397,90 @@ dw_edma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
return ret;
}
-static void dw_edma_v0_write_ll_data(struct dw_edma_chunk *chunk, int i,
+static void dw_edma_v0_write_ll_data(struct dw_edma_chan *chan, int i,
u32 control, u32 size, u64 sar, u64 dar)
{
ptrdiff_t ofs = i * sizeof(struct dw_edma_v0_lli);
- if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
- struct dw_edma_v0_lli *lli = chunk->ll_region.vaddr.mem + ofs;
+ if (chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
+ struct dw_edma_v0_lli *lli = chan->ll_region.vaddr.mem + ofs;
- lli->control = control;
lli->transfer_size = size;
lli->sar.reg = sar;
lli->dar.reg = dar;
+ dma_wmb();
+ lli->control = control;
} else {
- struct dw_edma_v0_lli __iomem *lli = chunk->ll_region.vaddr.io + ofs;
+ struct dw_edma_v0_lli __iomem *lli = chan->ll_region.vaddr.io + ofs;
- writel(control, &lli->control);
writel(size, &lli->transfer_size);
writeq(sar, &lli->sar.reg);
writeq(dar, &lli->dar.reg);
+ writel(control, &lli->control);
}
}
-static void dw_edma_v0_write_ll_link(struct dw_edma_chunk *chunk,
+static void dw_edma_v0_write_ll_link(struct dw_edma_chan *chan,
int i, u32 control, u64 pointer)
{
ptrdiff_t ofs = i * sizeof(struct dw_edma_v0_lli);
- if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
- struct dw_edma_v0_llp *llp = chunk->ll_region.vaddr.mem + ofs;
+ if (chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
+ struct dw_edma_v0_llp *llp = chan->ll_region.vaddr.mem + ofs;
- llp->control = control;
llp->llp.reg = pointer;
+ dma_wmb();
+ llp->control = control;
} else {
- struct dw_edma_v0_llp __iomem *llp = chunk->ll_region.vaddr.io + ofs;
+ struct dw_edma_v0_llp __iomem *llp = chan->ll_region.vaddr.io + ofs;
- writel(control, &llp->control);
writeq(pointer, &llp->llp.reg);
+ writel(control, &llp->control);
}
}
-static void dw_edma_v0_core_write_chunk(struct dw_edma_chunk *chunk)
+static void dw_edma_v0_core_ch_enable(struct dw_edma_chan *chan)
{
- struct dw_edma_burst *child;
- struct dw_edma_chan *chan = chunk->chan;
- u32 control = 0, i = 0;
- int j;
-
- if (chunk->cb)
- control = DW_EDMA_V0_CB;
-
- j = chunk->bursts_alloc;
- list_for_each_entry(child, &chunk->burst->list, list) {
- j--;
- if (!j) {
- control |= DW_EDMA_V0_LIE;
- if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
- control |= DW_EDMA_V0_RIE;
- }
-
- dw_edma_v0_write_ll_data(chunk, i++, control, child->sz,
- child->sar, child->dar);
- }
-
- control = DW_EDMA_V0_LLP | DW_EDMA_V0_TCB;
- if (!chunk->cb)
- control |= DW_EDMA_V0_CB;
+ struct dw_edma *dw = chan->dw;
+ unsigned long flags;
+ u32 tmp;
- dw_edma_v0_write_ll_link(chunk, i, control, chunk->ll_region.paddr);
+ /* Enable engine */
+ SET_RW_32(dw, chan->dir, engine_en, BIT(0));
+ if (dw->chip->mf == EDMA_MF_HDMA_COMPAT)
+ dw_edma_v0_core_ch_power(dw, chan->dir, chan->id, true);
+ /* Interrupt mask/unmask - done, abort */
+ raw_spin_lock_irqsave(&dw->lock, flags);
+
+ tmp = GET_RW_32(dw, chan->dir, int_mask);
+ if (chan->irq_mode == DW_EDMA_CH_IRQ_REMOTE) {
+ tmp |= FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id));
+ tmp |= FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id));
+ } else {
+ tmp &= ~FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id));
+ tmp &= ~FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id));
+ }
+ SET_RW_32(dw, chan->dir, int_mask, tmp);
+ /* Linked list error */
+ tmp = GET_RW_32(dw, chan->dir, linked_list_err_en);
+ tmp |= FIELD_PREP(EDMA_V0_LINKED_LIST_ERR_MASK, BIT(chan->id));
+ SET_RW_32(dw, chan->dir, linked_list_err_en, tmp);
+
+ raw_spin_unlock_irqrestore(&dw->lock, flags);
+
+ /* Channel control */
+ SET_CH_32(dw, chan->dir, chan->id, ch_control1,
+ DW_EDMA_V0_CCS | DW_EDMA_V0_LLE |
+ dw_edma_v0_func_num(chan));
+ /* Linked list */
+ /* llp is not aligned on 64bit -> keep 32bit accesses */
+ SET_CH_32(dw, chan->dir, chan->id, llp.lsb,
+ lower_32_bits(chan->ll_region.paddr));
+ SET_CH_32(dw, chan->dir, chan->id, llp.msb,
+ upper_32_bits(chan->ll_region.paddr));
}
-static void dw_edma_v0_sync_ll_data(struct dw_edma_chunk *chunk)
+static void dw_edma_v0_sync_ll_data(struct dw_edma_chan *chan)
{
/*
* In case of remote eDMA engine setup, the DW PCIe RP/EP internal
@@ -356,88 +490,8 @@ static void dw_edma_v0_sync_ll_data(struct dw_edma_chunk *chunk)
* LL memory in a hope that the MRd TLP will return only after the
* last MWr TLP is completed
*/
- if (!(chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
- readl(chunk->ll_region.vaddr.io);
-}
-
-static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
-{
- struct dw_edma_chan *chan = chunk->chan;
- struct dw_edma *dw = chan->dw;
- unsigned long flags;
- u32 tmp;
-
- dw_edma_v0_core_write_chunk(chunk);
-
- if (first) {
- /* Enable engine */
- SET_RW_32(dw, chan->dir, engine_en, BIT(0));
- if (dw->chip->mf == EDMA_MF_HDMA_COMPAT) {
- switch (chan->id) {
- case 0:
- SET_RW_COMPAT(dw, chan->dir, ch0_pwr_en,
- BIT(0));
- break;
- case 1:
- SET_RW_COMPAT(dw, chan->dir, ch1_pwr_en,
- BIT(0));
- break;
- case 2:
- SET_RW_COMPAT(dw, chan->dir, ch2_pwr_en,
- BIT(0));
- break;
- case 3:
- SET_RW_COMPAT(dw, chan->dir, ch3_pwr_en,
- BIT(0));
- break;
- case 4:
- SET_RW_COMPAT(dw, chan->dir, ch4_pwr_en,
- BIT(0));
- break;
- case 5:
- SET_RW_COMPAT(dw, chan->dir, ch5_pwr_en,
- BIT(0));
- break;
- case 6:
- SET_RW_COMPAT(dw, chan->dir, ch6_pwr_en,
- BIT(0));
- break;
- case 7:
- SET_RW_COMPAT(dw, chan->dir, ch7_pwr_en,
- BIT(0));
- break;
- }
- }
- /* Interrupt unmask - done, abort */
- raw_spin_lock_irqsave(&dw->lock, flags);
-
- tmp = GET_RW_32(dw, chan->dir, int_mask);
- tmp &= ~FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id));
- tmp &= ~FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id));
- SET_RW_32(dw, chan->dir, int_mask, tmp);
- /* Linked list error */
- tmp = GET_RW_32(dw, chan->dir, linked_list_err_en);
- tmp |= FIELD_PREP(EDMA_V0_LINKED_LIST_ERR_MASK, BIT(chan->id));
- SET_RW_32(dw, chan->dir, linked_list_err_en, tmp);
-
- raw_spin_unlock_irqrestore(&dw->lock, flags);
-
- /* Channel control */
- SET_CH_32(dw, chan->dir, chan->id, ch_control1,
- (DW_EDMA_V0_CCS | DW_EDMA_V0_LLE));
- /* Linked list */
- /* llp is not aligned on 64bit -> keep 32bit accesses */
- SET_CH_32(dw, chan->dir, chan->id, llp.lsb,
- lower_32_bits(chunk->ll_region.paddr));
- SET_CH_32(dw, chan->dir, chan->id, llp.msb,
- upper_32_bits(chunk->ll_region.paddr));
- }
-
- dw_edma_v0_sync_ll_data(chunk);
-
- /* Doorbell */
- SET_RW_32(dw, chan->dir, doorbell,
- FIELD_PREP(EDMA_V0_DOORBELL_CH_MASK, chan->id));
+ if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
+ readl(chan->ll_region.vaddr.io);
}
static void dw_edma_v0_core_ch_config(struct dw_edma_chan *chan)
@@ -509,6 +563,59 @@ static void dw_edma_v0_core_ch_config(struct dw_edma_chan *chan)
}
}
+static void
+dw_edma_v0_core_ll_data(struct dw_edma_chan *chan, struct dw_edma_burst *burst,
+ u32 idx, bool cb, bool irq)
+{
+ u32 control = 0;
+
+ if (cb)
+ control |= DW_EDMA_V0_CB;
+
+ if (irq) {
+ control |= DW_EDMA_V0_LIE;
+
+ /*
+ * A local instance never issues transfers on a remote-routed
+ * channel: on CHIP_LOCAL instances, REMOTE routing denotes a
+ * channel handed over to the remote side, which programs the
+ * linked list through its own instance. The remote-only
+ * recipe (LIE|RIE with the local interrupt masked) is thus
+ * applied by the instance that owns the transfer, and the
+ * LIE-only write below never executes for a remote-routed
+ * channel.
+ */
+ if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) &&
+ chan->irq_mode == DW_EDMA_CH_IRQ_REMOTE)
+ control |= DW_EDMA_V0_RIE;
+ }
+
+ dw_edma_v0_write_ll_data(chan, idx, control, burst->sz, burst->sar,
+ burst->dar);
+}
+
+static void
+dw_edma_v0_core_ll_link(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr)
+{
+ u32 control = DW_EDMA_V0_LLP | DW_EDMA_V0_TCB;
+
+ if (!cb)
+ control |= DW_EDMA_V0_CB;
+
+ dw_edma_v0_write_ll_link(chan, idx, control, addr);
+}
+
+static void dw_edma_v0_core_ch_doorbell(struct dw_edma_chan *chan)
+{
+ struct dw_edma *dw = chan->dw;
+
+ dw_edma_v0_sync_ll_data(chan);
+
+ /* Doorbell */
+ SET_RW_32(dw, chan->dir, doorbell,
+ FIELD_PREP(EDMA_V0_DOORBELL_CH_MASK, chan->id));
+}
+
/* eDMA debugfs callbacks */
static void dw_edma_v0_core_debugfs_on(struct dw_edma *dw)
{
@@ -536,10 +643,15 @@ static resource_size_t dw_edma_v0_core_db_offset(struct dw_edma *dw)
static const struct dw_edma_core_ops dw_edma_v0_core = {
.off = dw_edma_v0_core_off,
+ .quiesce = dw_edma_v0_core_quiesce,
+ .ch_quiesce = dw_edma_v0_core_ch_quiesce,
.ch_count = dw_edma_v0_core_ch_count,
.ch_status = dw_edma_v0_core_ch_status,
.handle_int = dw_edma_v0_core_handle_int,
- .start = dw_edma_v0_core_start,
+ .ll_data = dw_edma_v0_core_ll_data,
+ .ll_link = dw_edma_v0_core_ll_link,
+ .ch_doorbell = dw_edma_v0_core_ch_doorbell,
+ .ch_enable = dw_edma_v0_core_ch_enable,
.ch_config = dw_edma_v0_core_ch_config,
.debugfs_on = dw_edma_v0_core_debugfs_on,
.ack_emulated_irq = dw_edma_v0_core_ack_emulated_irq,
diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c
index 632abb8b481c..36ee72efcd31 100644
--- a/drivers/dma/dw-edma/dw-hdma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c
@@ -49,20 +49,82 @@ __dw_ch_regs(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch)
writel(value, &(__dw_ch_regs(dw, EDMA_DIR_READ, ch)->name)); \
} while (0)
+static u32 dw_hdma_v0_core_int_setup(struct dw_edma_chan *chan, u32 val)
+{
+ val &= ~(HDMA_V0_LOCAL_ABORT_INT_EN | HDMA_V0_REMOTE_ABORT_INT_EN |
+ HDMA_V0_LOCAL_STOP_INT_EN | HDMA_V0_REMOTE_STOP_INT_EN |
+ HDMA_V0_ABORT_INT_MASK | HDMA_V0_STOP_INT_MASK);
+
+ /*
+ * HDMA_INT_STATUS.STOP and .ABORT are latched only when LSIE and
+ * LAIE are enabled. A remote handler needs those status bits to
+ * identify the source of the IMWr, so keep local generation enabled
+ * and mask the local interrupt pins instead.
+ */
+ val |= HDMA_V0_LOCAL_ABORT_INT_EN | HDMA_V0_LOCAL_STOP_INT_EN;
+
+ if (chan->irq_mode == DW_EDMA_CH_IRQ_REMOTE)
+ val |= HDMA_V0_REMOTE_ABORT_INT_EN |
+ HDMA_V0_REMOTE_STOP_INT_EN |
+ HDMA_V0_ABORT_INT_MASK | HDMA_V0_STOP_INT_MASK;
+
+ return val;
+}
+
/* HDMA management callbacks */
+static void dw_hdma_v0_core_ch_off(struct dw_edma *dw, enum dw_edma_dir dir,
+ u16 id)
+{
+ SET_CH_32(dw, dir, id, int_setup,
+ HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK);
+ SET_CH_32(dw, dir, id, ch_en, 0);
+ SET_CH_32(dw, dir, id, int_clear,
+ HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK);
+}
+
static void dw_hdma_v0_core_off(struct dw_edma *dw)
{
int id;
+ enum dw_edma_dir dir;
+
+ dir = EDMA_DIR_WRITE;
+ for (id = 0; id < dw->wr_ch_cnt; id++) {
+ SET_CH_32(dw, dir, id, int_setup,
+ HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK);
+ SET_CH_32(dw, dir, id, int_clear,
+ HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK);
+ SET_CH_32(dw, dir, id, ch_en, 0);
+ }
- for (id = 0; id < HDMA_V0_MAX_NR_CH; id++) {
- SET_BOTH_CH_32(dw, id, int_setup,
- HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK);
- SET_BOTH_CH_32(dw, id, int_clear,
- HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK);
- SET_BOTH_CH_32(dw, id, ch_en, 0);
+ dir = EDMA_DIR_READ;
+ for (id = 0; id < dw->rd_ch_cnt; id++) {
+ SET_CH_32(dw, dir, id, int_setup,
+ HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK);
+ SET_CH_32(dw, dir, id, int_clear,
+ HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK);
+ SET_CH_32(dw, dir, id, ch_en, 0);
}
}
+static int dw_hdma_v0_core_quiesce(struct dw_edma *dw)
+{
+ int id;
+
+ for (id = 0; id < dw->wr_ch_cnt; id++)
+ dw_hdma_v0_core_ch_off(dw, EDMA_DIR_WRITE, id);
+ for (id = 0; id < dw->rd_ch_cnt; id++)
+ dw_hdma_v0_core_ch_off(dw, EDMA_DIR_READ, id);
+
+ return 0;
+}
+
+static int dw_hdma_v0_core_ch_quiesce(struct dw_edma_chan *chan)
+{
+ dw_hdma_v0_core_ch_off(chan->dw, chan->dir, chan->id);
+
+ return 0;
+}
+
static u16 dw_hdma_v0_core_ch_count(struct dw_edma *dw, enum dw_edma_dir dir)
{
/*
@@ -79,7 +141,7 @@ static enum dma_status dw_hdma_v0_core_ch_status(struct dw_edma_chan *chan)
u32 tmp;
tmp = FIELD_GET(HDMA_V0_CH_STATUS_MASK,
- GET_CH_32(dw, chan->id, chan->dir, ch_stat));
+ GET_CH_32(dw, chan->dir, chan->id, ch_stat));
if (tmp == 1)
return DMA_IN_PROGRESS;
@@ -118,7 +180,7 @@ dw_hdma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
unsigned long total, pos, val;
irqreturn_t ret = IRQ_NONE;
struct dw_edma_chan *chan;
- unsigned long off, mask;
+ unsigned long off, *mask;
if (dir == EDMA_DIR_WRITE) {
total = dw->wr_ch_cnt;
@@ -130,8 +192,10 @@ dw_hdma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
mask = dw_irq->rd_mask;
}
- for_each_set_bit(pos, &mask, total) {
+ for_each_set_bit(pos, mask, total) {
chan = &dw->chan[pos + off];
+ if (unlikely(dw_edma_core_ch_ignore_irq(chan)))
+ continue;
val = dw_hdma_v0_core_status_int(chan);
if (FIELD_GET(HDMA_V0_STOP_INT_MASK, val)) {
@@ -152,66 +216,73 @@ dw_hdma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
return ret;
}
-static void dw_hdma_v0_write_ll_data(struct dw_edma_chunk *chunk, int i,
+static void dw_hdma_v0_write_ll_data(struct dw_edma_chan *chan, int i,
u32 control, u32 size, u64 sar, u64 dar)
{
ptrdiff_t ofs = i * sizeof(struct dw_hdma_v0_lli);
- if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
- struct dw_hdma_v0_lli *lli = chunk->ll_region.vaddr.mem + ofs;
+ if (chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
+ struct dw_hdma_v0_lli *lli = chan->ll_region.vaddr.mem + ofs;
- lli->control = control;
lli->transfer_size = size;
lli->sar.reg = sar;
lli->dar.reg = dar;
+ dma_wmb();
+ lli->control = control;
} else {
- struct dw_hdma_v0_lli __iomem *lli = chunk->ll_region.vaddr.io + ofs;
+ struct dw_hdma_v0_lli __iomem *lli = chan->ll_region.vaddr.io + ofs;
- writel(control, &lli->control);
writel(size, &lli->transfer_size);
writeq(sar, &lli->sar.reg);
writeq(dar, &lli->dar.reg);
+ writel(control, &lli->control);
}
}
-static void dw_hdma_v0_write_ll_link(struct dw_edma_chunk *chunk,
+static void dw_hdma_v0_write_ll_link(struct dw_edma_chan *chan,
int i, u32 control, u64 pointer)
{
ptrdiff_t ofs = i * sizeof(struct dw_hdma_v0_lli);
- if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
- struct dw_hdma_v0_llp *llp = chunk->ll_region.vaddr.mem + ofs;
+ if (chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
+ struct dw_hdma_v0_llp *llp = chan->ll_region.vaddr.mem + ofs;
- llp->control = control;
llp->llp.reg = pointer;
+ dma_wmb();
+ llp->control = control;
} else {
- struct dw_hdma_v0_llp __iomem *llp = chunk->ll_region.vaddr.io + ofs;
+ struct dw_hdma_v0_llp __iomem *llp = chan->ll_region.vaddr.io + ofs;
- writel(control, &llp->control);
writeq(pointer, &llp->llp.reg);
+ writel(control, &llp->control);
}
}
-static void dw_hdma_v0_core_write_chunk(struct dw_edma_chunk *chunk)
+static void dw_hdma_v0_core_ch_enable(struct dw_edma_chan *chan)
{
- struct dw_edma_burst *child;
- u32 control = 0, i = 0;
-
- if (chunk->cb)
- control = DW_HDMA_V0_CB;
-
- list_for_each_entry(child, &chunk->burst->list, list)
- dw_hdma_v0_write_ll_data(chunk, i++, control, child->sz,
- child->sar, child->dar);
-
- control = DW_HDMA_V0_LLP | DW_HDMA_V0_TCB;
- if (!chunk->cb)
- control |= DW_HDMA_V0_CB;
+ struct dw_edma *dw = chan->dw;
+ u32 tmp;
- dw_hdma_v0_write_ll_link(chunk, i, control, chunk->ll_region.paddr);
+ /* Enable engine */
+ SET_CH_32(dw, chan->dir, chan->id, ch_en, BIT(0));
+ /* Interrupt unmask - stop, abort */
+ tmp = GET_CH_32(dw, chan->dir, chan->id, int_setup);
+ tmp = dw_hdma_v0_core_int_setup(chan, tmp);
+ SET_CH_32(dw, chan->dir, chan->id, int_setup, tmp);
+ /* Channel control */
+ SET_CH_32(dw, chan->dir, chan->id, control1, HDMA_V0_LINKLIST_EN);
+ /* Linked list */
+ /* llp is not aligned on 64bit -> keep 32bit accesses */
+ SET_CH_32(dw, chan->dir, chan->id, llp.lsb,
+ lower_32_bits(chan->ll_region.paddr));
+ SET_CH_32(dw, chan->dir, chan->id, llp.msb,
+ upper_32_bits(chan->ll_region.paddr));
+ /* Set consumer cycle */
+ SET_CH_32(dw, chan->dir, chan->id, cycle_sync,
+ HDMA_V0_CONSUMER_CYCLE_STAT | HDMA_V0_CONSUMER_CYCLE_BIT);
}
-static void dw_hdma_v0_sync_ll_data(struct dw_edma_chunk *chunk)
+static void dw_hdma_v0_sync_ll_data(struct dw_edma_chan *chan)
{
/*
* In case of remote HDMA engine setup, the DW PCIe RP/EP internal
@@ -221,60 +292,16 @@ static void dw_hdma_v0_sync_ll_data(struct dw_edma_chunk *chunk)
* LL memory in a hope that the MRd TLP will return only after the
* last MWr TLP is completed
*/
- if (!(chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
- readl(chunk->ll_region.vaddr.io);
+ if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
+ readl(chan->ll_region.vaddr.io);
}
-static void dw_hdma_v0_core_ll_start(struct dw_edma_chunk *chunk, bool first)
+static void dw_hdma_v0_core_non_ll_start(struct dw_edma_chan *chan,
+ struct dw_edma_burst *child)
{
- struct dw_edma_chan *chan = chunk->chan;
struct dw_edma *dw = chan->dw;
- u32 tmp;
-
- dw_hdma_v0_core_write_chunk(chunk);
-
- if (first) {
- /* Enable engine */
- SET_CH_32(dw, chan->dir, chan->id, ch_en, BIT(0));
- /* Interrupt unmask - stop, abort */
- tmp = GET_CH_32(dw, chan->dir, chan->id, int_setup);
- tmp &= ~(HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK);
- /* Interrupt enable - stop, abort */
- tmp |= HDMA_V0_LOCAL_STOP_INT_EN | HDMA_V0_LOCAL_ABORT_INT_EN;
- if (!(dw->chip->flags & DW_EDMA_CHIP_LOCAL))
- tmp |= HDMA_V0_REMOTE_STOP_INT_EN | HDMA_V0_REMOTE_ABORT_INT_EN;
- SET_CH_32(dw, chan->dir, chan->id, int_setup, tmp);
- /* Channel control */
- SET_CH_32(dw, chan->dir, chan->id, control1, HDMA_V0_LINKLIST_EN);
- /* Linked list */
- /* llp is not aligned on 64bit -> keep 32bit accesses */
- SET_CH_32(dw, chan->dir, chan->id, llp.lsb,
- lower_32_bits(chunk->ll_region.paddr));
- SET_CH_32(dw, chan->dir, chan->id, llp.msb,
- upper_32_bits(chunk->ll_region.paddr));
- /* Set consumer cycle */
- SET_CH_32(dw, chan->dir, chan->id, cycle_sync,
- HDMA_V0_CONSUMER_CYCLE_STAT | HDMA_V0_CONSUMER_CYCLE_BIT);
- }
-
- dw_hdma_v0_sync_ll_data(chunk);
-
- /* Doorbell */
- SET_CH_32(dw, chan->dir, chan->id, doorbell, HDMA_V0_DOORBELL_START);
-}
-
-static void dw_hdma_v0_core_non_ll_start(struct dw_edma_chunk *chunk)
-{
- struct dw_edma_chan *chan = chunk->chan;
- struct dw_edma *dw = chan->dw;
- struct dw_edma_burst *child;
u32 val;
- child = list_first_entry_or_null(&chunk->burst->list,
- struct dw_edma_burst, list);
- if (!child)
- return;
-
SET_CH_32(dw, chan->dir, chan->id, ch_en, HDMA_V0_CH_EN);
/* Source address */
@@ -293,17 +320,8 @@ static void dw_hdma_v0_core_non_ll_start(struct dw_edma_chunk *chunk)
SET_CH_32(dw, chan->dir, chan->id, transfer_size, child->sz);
/* Interrupt setup */
- val = GET_CH_32(dw, chan->dir, chan->id, int_setup) |
- HDMA_V0_STOP_INT_MASK |
- HDMA_V0_ABORT_INT_MASK |
- HDMA_V0_LOCAL_STOP_INT_EN |
- HDMA_V0_LOCAL_ABORT_INT_EN;
-
- if (!(dw->chip->flags & DW_EDMA_CHIP_LOCAL)) {
- val |= HDMA_V0_REMOTE_STOP_INT_EN |
- HDMA_V0_REMOTE_ABORT_INT_EN;
- }
-
+ val = GET_CH_32(dw, chan->dir, chan->id, int_setup);
+ val = dw_hdma_v0_core_int_setup(chan, val);
SET_CH_32(dw, chan->dir, chan->id, int_setup, val);
/* Channel control setup */
@@ -315,16 +333,6 @@ static void dw_hdma_v0_core_non_ll_start(struct dw_edma_chunk *chunk)
HDMA_V0_DOORBELL_START);
}
-static void dw_hdma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
-{
- struct dw_edma_chan *chan = chunk->chan;
-
- if (chan->non_ll)
- dw_hdma_v0_core_non_ll_start(chunk);
- else
- dw_hdma_v0_core_ll_start(chunk, first);
-}
-
static void dw_hdma_v0_core_ch_config(struct dw_edma_chan *chan)
{
struct dw_edma *dw = chan->dw;
@@ -337,6 +345,43 @@ static void dw_hdma_v0_core_ch_config(struct dw_edma_chan *chan)
SET_CH_32(dw, chan->dir, chan->id, msi_abort.msb, chan->msi.address_hi);
/* config MSI data */
SET_CH_32(dw, chan->dir, chan->id, msi_msgdata, chan->msi.data);
+ /* Configure the requester function number used by outbound TLPs. */
+ SET_CH_32(dw, chan->dir, chan->id, func_num,
+ FIELD_PREP(HDMA_V0_FUNC_NUM_PF_MASK, chan->func_no));
+}
+
+static void
+dw_hdma_v0_core_ll_data(struct dw_edma_chan *chan, struct dw_edma_burst *burst,
+ u32 idx, bool cb, bool irq)
+{
+ u32 control = 0;
+
+ if (cb)
+ control |= DW_HDMA_V0_CB;
+
+ dw_hdma_v0_write_ll_data(chan, idx, control, burst->sz, burst->sar,
+ burst->dar);
+}
+
+static void
+dw_hdma_v0_core_ll_link(struct dw_edma_chan *chan, u32 idx, bool cb, u64 addr)
+{
+ u32 control = DW_HDMA_V0_LLP | DW_HDMA_V0_TCB;
+
+ if (!cb)
+ control |= DW_HDMA_V0_CB;
+
+ dw_hdma_v0_write_ll_link(chan, idx, control, addr);
+}
+
+static void dw_hdma_v0_core_ch_doorbell(struct dw_edma_chan *chan)
+{
+ struct dw_edma *dw = chan->dw;
+
+ dw_hdma_v0_sync_ll_data(chan);
+
+ /* Doorbell */
+ SET_CH_32(dw, chan->dir, chan->id, doorbell, HDMA_V0_DOORBELL_START);
}
/* HDMA debugfs callbacks */
@@ -353,10 +398,16 @@ static resource_size_t dw_hdma_v0_core_db_offset(struct dw_edma *dw)
static const struct dw_edma_core_ops dw_hdma_v0_core = {
.off = dw_hdma_v0_core_off,
+ .quiesce = dw_hdma_v0_core_quiesce,
+ .ch_quiesce = dw_hdma_v0_core_ch_quiesce,
.ch_count = dw_hdma_v0_core_ch_count,
.ch_status = dw_hdma_v0_core_ch_status,
.handle_int = dw_hdma_v0_core_handle_int,
- .start = dw_hdma_v0_core_start,
+ .non_ll_start = dw_hdma_v0_core_non_ll_start,
+ .ll_data = dw_hdma_v0_core_ll_data,
+ .ll_link = dw_hdma_v0_core_ll_link,
+ .ch_doorbell = dw_hdma_v0_core_ch_doorbell,
+ .ch_enable = dw_hdma_v0_core_ch_enable,
.ch_config = dw_hdma_v0_core_ch_config,
.debugfs_on = dw_hdma_v0_core_debugfs_on,
.db_offset = dw_hdma_v0_core_db_offset,
diff --git a/drivers/dma/dw-edma/dw-hdma-v0-regs.h b/drivers/dma/dw-edma/dw-hdma-v0-regs.h
index 7759ba9b4850..2124c162a62f 100644
--- a/drivers/dma/dw-edma/dw-hdma-v0-regs.h
+++ b/drivers/dma/dw-edma/dw-hdma-v0-regs.h
@@ -11,7 +11,7 @@
#include <linux/dmaengine.h>
-#define HDMA_V0_MAX_NR_CH 8
+#define HDMA_V0_MAX_NR_CH 64
#define HDMA_V0_CH_EN BIT(0)
#define HDMA_V0_LOCAL_ABORT_INT_EN BIT(6)
#define HDMA_V0_REMOTE_ABORT_INT_EN BIT(5)
@@ -24,6 +24,7 @@
#define HDMA_V0_CONSUMER_CYCLE_BIT BIT(0)
#define HDMA_V0_DOORBELL_START BIT(0)
#define HDMA_V0_CH_STATUS_MASK GENMASK(1, 0)
+#define HDMA_V0_FUNC_NUM_PF_MASK GENMASK(7, 0)
struct dw_hdma_v0_ch_regs {
u32 ch_en; /* 0x0000 */
diff --git a/drivers/dma/fsl-edma-main.c b/drivers/dma/fsl-edma-main.c
index 36155ab1602a..d9fb717b5b53 100644
--- a/drivers/dma/fsl-edma-main.c
+++ b/drivers/dma/fsl-edma-main.c
@@ -414,6 +414,8 @@ static int fsl_edma3_irq_init(struct platform_device *pdev, struct fsl_edma_engi
errirq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s-err",
dev_name(&pdev->dev));
+ if (!errirq_name)
+ return -ENOMEM;
ret = devm_request_irq(&pdev->dev, fsl_edma->errirq, fsl_edma3_err_handler_shared,
0, errirq_name, fsl_edma);
diff --git a/drivers/dma/fsl_raid.c b/drivers/dma/fsl_raid.c
index 99945845d8b5..2d86f61105e5 100644
--- a/drivers/dma/fsl_raid.c
+++ b/drivers/dma/fsl_raid.c
@@ -242,9 +242,9 @@ static void fill_cfd_frame(struct fsl_re_cmpnd_frame *cf, u8 index,
u32 efrl = length & FSL_RE_CF_LENGTH_MASK;
efrl |= final << FSL_RE_CF_FINAL_SHIFT;
- cf[index].efrl32 = efrl;
- cf[index].addr_high = upper_32_bits(addr);
- cf[index].addr_low = lower_32_bits(addr);
+ cf[index].efrl32 = cpu_to_be32(efrl);
+ cf[index].addr_high = cpu_to_be32(upper_32_bits(addr));
+ cf[index].addr_low = cpu_to_be32(lower_32_bits(addr));
}
static struct fsl_re_desc *fsl_re_init_desc(struct fsl_re_chan *re_chan,
@@ -256,9 +256,10 @@ static struct fsl_re_desc *fsl_re_init_desc(struct fsl_re_chan *re_chan,
dma_async_tx_descriptor_init(&desc->async_tx, &re_chan->chan);
INIT_LIST_HEAD(&desc->node);
- desc->hwdesc.fmt32 = FSL_RE_FRAME_FORMAT << FSL_RE_HWDESC_FMT_SHIFT;
- desc->hwdesc.lbea32 = upper_32_bits(paddr);
- desc->hwdesc.addr_low = lower_32_bits(paddr);
+ desc->hwdesc.fmt32 = cpu_to_be32(FSL_RE_FRAME_FORMAT <<
+ FSL_RE_HWDESC_FMT_SHIFT);
+ desc->hwdesc.lbea32 = cpu_to_be32(upper_32_bits(paddr));
+ desc->hwdesc.addr_low = cpu_to_be32(lower_32_bits(paddr));
desc->cf_addr = cf;
desc->cf_paddr = paddr;
@@ -374,11 +375,11 @@ static struct dma_async_tx_descriptor *fsl_re_prep_dma_genq(
for (i = 2, j = 0; j < save_src_cnt; i++, j++)
fill_cfd_frame(cf, i, len, src[j], 0);
+ /* Fill the last frame and mark it final */
if (cont_q)
- fill_cfd_frame(cf, i++, len, dest, 0);
-
- /* Setting the final bit in the last source buffer frame in CFD */
- cf[i - 1].efrl32 |= 1 << FSL_RE_CF_FINAL_SHIFT;
+ fill_cfd_frame(cf, i, len, dest, 1);
+ else
+ fill_cfd_frame(cf, i - 1, len, src[j - 1], 1);
return &desc->async_tx;
}
@@ -504,16 +505,16 @@ static struct dma_async_tx_descriptor *fsl_re_prep_dma_pq(
p[save_src_cnt + 2] = 1;
fill_cfd_frame(cf, i++, len, dest[0], 0);
fill_cfd_frame(cf, i++, len, dest[1], 0);
- fill_cfd_frame(cf, i++, len, dest[1], 0);
+ fill_cfd_frame(cf, i++, len, dest[1], 1);
} else {
dev_err(re_chan->dev, "PQ tx continuation error!\n");
return NULL;
}
+ } else {
+ /* Mark the last source buffer frame final */
+ fill_cfd_frame(cf, i - 1, len, src[j - 1], 1);
}
- /* Setting the final bit in the last source buffer frame in CFD */
- cf[i - 1].efrl32 |= 1 << FSL_RE_CF_FINAL_SHIFT;
-
return &desc->async_tx;
}
@@ -656,8 +657,7 @@ static int fsl_re_chan_probe(struct platform_device *ofdev,
goto err_free;
}
- chan->jrregs = (struct fsl_re_chan_cfg *)((u8 *)re_priv->re_regs +
- off + ptr);
+ chan->jrregs = re_priv->base + off + ptr;
/* read irq property from dts */
chan->irq = irq_of_parse_and_map(np, 0);
@@ -745,38 +745,36 @@ err_free:
/* Probe function for RAID Engine */
static int fsl_re_probe(struct platform_device *ofdev)
{
+ struct fsl_re_ctrl __iomem *re_regs;
struct fsl_re_drv_private *re_priv;
struct device_node *child;
u32 off;
u8 ridx = 0;
struct dma_device *dma_dev;
- struct resource *res;
int rc;
struct device *dev = &ofdev->dev;
+ /* IOMAP the entire RAID Engine region */
+ re_regs = devm_platform_ioremap_resource(ofdev, 0);
+ if (IS_ERR(re_regs))
+ return PTR_ERR(re_regs);
+
re_priv = devm_kzalloc(dev, sizeof(*re_priv), GFP_KERNEL);
if (!re_priv)
return -ENOMEM;
- res = platform_get_resource(ofdev, IORESOURCE_MEM, 0);
- if (!res)
- return -ENODEV;
-
- /* IOMAP the entire RAID Engine region */
- re_priv->re_regs = devm_ioremap(dev, res->start, resource_size(res));
- if (!re_priv->re_regs)
- return -EBUSY;
+ re_priv->base = re_regs;
/* Program the RE mode */
- out_be32(&re_priv->re_regs->global_config, FSL_RE_NON_DPAA_MODE);
+ out_be32(&re_regs->global_config, FSL_RE_NON_DPAA_MODE);
/* Program Galois Field polynomial */
- out_be32(&re_priv->re_regs->galois_field_config, FSL_RE_GFM_POLY);
+ out_be32(&re_regs->galois_field_config, FSL_RE_GFM_POLY);
dev_info(dev, "version %x, mode %x, gfp %x\n",
- in_be32(&re_priv->re_regs->re_version_id),
- in_be32(&re_priv->re_regs->global_config),
- in_be32(&re_priv->re_regs->galois_field_config));
+ in_be32(&re_regs->re_version_id),
+ in_be32(&re_regs->global_config),
+ in_be32(&re_regs->galois_field_config));
dma_dev = &re_priv->dma_dev;
dma_dev->dev = dev;
diff --git a/drivers/dma/fsl_raid.h b/drivers/dma/fsl_raid.h
index 69d743c04973..adbfede330a7 100644
--- a/drivers/dma/fsl_raid.h
+++ b/drivers/dma/fsl_raid.h
@@ -256,7 +256,7 @@ struct fsl_re_hw_desc {
struct fsl_re_drv_private {
u8 total_chans;
struct dma_device dma_dev;
- struct fsl_re_ctrl *re_regs;
+ void __iomem *base;
struct fsl_re_chan *re_jrs[FSL_RE_MAX_CHANS];
struct dma_pool *cf_desc_pool;
struct dma_pool *hw_desc_pool;
@@ -273,7 +273,7 @@ struct fsl_re_chan {
struct device *dev;
struct fsl_re_drv_private *re_dev;
struct dma_chan chan;
- struct fsl_re_chan_cfg *jrregs;
+ struct fsl_re_chan_cfg __iomem *jrregs;
int irq;
struct tasklet_struct irqtask;
u32 alloc_count;
diff --git a/drivers/dma/hisi_dma.c b/drivers/dma/hisi_dma.c
index 28bf818f9aa6..c751a2e49e6d 100644
--- a/drivers/dma/hisi_dma.c
+++ b/drivers/dma/hisi_dma.c
@@ -983,7 +983,7 @@ static int hisi_dma_probe(struct pci_dev *pdev, const struct pci_device_id *id)
hdma_dev = devm_kzalloc(dev, struct_size(hdma_dev, chan, chan_num),
GFP_KERNEL);
if (!hdma_dev)
- return -EINVAL;
+ return -ENOMEM;
hdma_dev->base = pcim_iomap_table(pdev)[PCI_BAR_2];
hdma_dev->pdev = pdev;
diff --git a/drivers/dma/mediatek/mtk-uart-apdma.c b/drivers/dma/mediatek/mtk-uart-apdma.c
index c269d84d7bd2..f74e9a328588 100644
--- a/drivers/dma/mediatek/mtk-uart-apdma.c
+++ b/drivers/dma/mediatek/mtk-uart-apdma.c
@@ -531,7 +531,7 @@ static int mtk_uart_apdma_probe(struct platform_device *pdev)
for (i = 0; i < mtkd->dma_requests; i++) {
c = devm_kzalloc(mtkd->ddev.dev, sizeof(*c), GFP_KERNEL);
if (!c) {
- rc = -ENODEV;
+ rc = -ENOMEM;
goto err_no_dma;
}
diff --git a/drivers/dma/pl330.c b/drivers/dma/pl330.c
index 25ba84b18704..6214d9000db8 100644
--- a/drivers/dma/pl330.c
+++ b/drivers/dma/pl330.c
@@ -502,6 +502,7 @@ struct pl330_dmac {
struct dma_pl330_chan *peripherals; /* keep at end */
int quirks;
+ struct dentry *dbgfs;
struct reset_control *rstc;
struct reset_control *rstc_ocp;
};
@@ -2952,14 +2953,24 @@ DEFINE_SHOW_ATTRIBUTE(pl330_debugfs);
static inline void init_pl330_debugfs(struct pl330_dmac *pl330)
{
- debugfs_create_file(dev_name(pl330->ddma.dev),
- S_IFREG | 0444, NULL, pl330,
- &pl330_debugfs_fops);
+ pl330->dbgfs = debugfs_create_file(dev_name(pl330->ddma.dev),
+ S_IFREG | 0444, NULL, pl330,
+ &pl330_debugfs_fops);
+}
+
+static inline void deinit_pl330_debugfs(struct pl330_dmac *pl330)
+{
+ debugfs_remove(pl330->dbgfs);
+ pl330->dbgfs = NULL;
}
#else
static inline void init_pl330_debugfs(struct pl330_dmac *pl330)
{
}
+
+static inline void deinit_pl330_debugfs(struct pl330_dmac *pl330)
+{
+}
#endif
/*
@@ -3204,6 +3215,8 @@ static void pl330_remove(struct amba_device *adev)
struct dma_pl330_chan *pch, *_p;
int i, irq;
+ deinit_pl330_debugfs(pl330);
+
pm_runtime_get_noresume(pl330->ddma.dev);
if (adev->dev.of_node)
diff --git a/drivers/dma/tegra210-adma.c b/drivers/dma/tegra210-adma.c
index ceaee1e33e68..21a381d022cf 100644
--- a/drivers/dma/tegra210-adma.c
+++ b/drivers/dma/tegra210-adma.c
@@ -1087,15 +1087,9 @@ static int tegra_adma_probe(struct platform_device *pdev)
}
} else {
/* If no 'page' property found, then reg DT binding would be legacy */
- res_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (res_base) {
- tdma->base_addr = devm_ioremap_resource(&pdev->dev, res_base);
- if (IS_ERR(tdma->base_addr))
- return PTR_ERR(tdma->base_addr);
- } else {
- return dev_err_probe(&pdev->dev, -ENODEV,
- "failed to get memory resource\n");
- }
+ tdma->base_addr = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(tdma->base_addr))
+ return PTR_ERR(tdma->base_addr);
tdma->ch_base_addr = tdma->base_addr + cdata->ch_base_offset;
}
diff --git a/drivers/dma/ti/k3-udma.c b/drivers/dma/ti/k3-udma.c
index 1cf158eb7bdb..fb21e0df5ab7 100644
--- a/drivers/dma/ti/k3-udma.c
+++ b/drivers/dma/ti/k3-udma.c
@@ -3408,7 +3408,7 @@ static int udma_set_metadata_len(struct dma_async_tx_descriptor *desc,
return 0;
}
-static struct dma_descriptor_metadata_ops metadata_ops = {
+static const struct dma_descriptor_metadata_ops metadata_ops = {
.attach = udma_attach_metadata,
.get_ptr = udma_get_metadata_ptr,
.set_len = udma_set_metadata_len,
diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c
index 404235c17353..bef2b031dba1 100644
--- a/drivers/dma/xilinx/xilinx_dma.c
+++ b/drivers/dma/xilinx/xilinx_dma.c
@@ -165,8 +165,10 @@
#define XILINX_DMA_FLUSH_MM2S 2
#define XILINX_DMA_FLUSH_BOTH 1
-/* Delay loop counter to prevent hardware failure */
-#define XILINX_DMA_LOOP_COUNT 1000000
+/* Timeout for polling various registers */
+#define XILINX_DMA_POLL_TIMEOUT_US 1000000
+/* Delay between polls (avoid a delay of 0 to prevent CPU stalls) */
+#define XILINX_DMA_POLL_DELAY_US 10
/* AXI DMA Specific Registers/Offsets */
#define XILINX_DMA_REG_SRCDSTADDR 0x18
@@ -653,7 +655,7 @@ static void *xilinx_dma_get_metadata_ptr(struct dma_async_tx_descriptor *tx,
return seg->hw.app;
}
-static struct dma_descriptor_metadata_ops xilinx_dma_metadata_ops = {
+static const struct dma_descriptor_metadata_ops xilinx_dma_metadata_ops = {
.get_ptr = xilinx_dma_get_metadata_ptr,
};
@@ -1324,8 +1326,9 @@ static int xilinx_dma_stop_transfer(struct xilinx_dma_chan *chan)
/* Wait for the hardware to halt */
return xilinx_dma_poll_timeout(chan, XILINX_DMA_REG_DMASR, val,
- val & XILINX_DMA_DMASR_HALTED, 0,
- XILINX_DMA_LOOP_COUNT);
+ val & XILINX_DMA_DMASR_HALTED,
+ XILINX_DMA_POLL_DELAY_US,
+ XILINX_DMA_POLL_TIMEOUT_US);
}
/**
@@ -1339,8 +1342,9 @@ static int xilinx_cdma_stop_transfer(struct xilinx_dma_chan *chan)
u32 val;
return xilinx_dma_poll_timeout(chan, XILINX_DMA_REG_DMASR, val,
- val & XILINX_DMA_DMASR_IDLE, 0,
- XILINX_DMA_LOOP_COUNT);
+ val & XILINX_DMA_DMASR_IDLE,
+ XILINX_DMA_POLL_DELAY_US,
+ XILINX_DMA_POLL_TIMEOUT_US);
}
/**
@@ -1356,8 +1360,9 @@ static void xilinx_dma_start(struct xilinx_dma_chan *chan)
/* Wait for the hardware to start */
err = xilinx_dma_poll_timeout(chan, XILINX_DMA_REG_DMASR, val,
- !(val & XILINX_DMA_DMASR_HALTED), 0,
- XILINX_DMA_LOOP_COUNT);
+ !(val & XILINX_DMA_DMASR_HALTED),
+ XILINX_DMA_POLL_DELAY_US,
+ XILINX_DMA_POLL_TIMEOUT_US);
if (err) {
dev_err(chan->dev, "Cannot start channel %p: %x\n",
@@ -1580,7 +1585,14 @@ static void xilinx_dma_start_transfer(struct xilinx_dma_chan *chan)
return;
}
- if (!chan->idle)
+ /*
+ * Direct (non-SG) mode has no descriptor queue: writing the BTT
+ * register launches a transfer immediately, so a new transfer must
+ * not be programmed while one is in flight. Keep such transfers
+ * serialized. SG mode supports chaining onto a running transfer via
+ * tail-pointer extension, so it is allowed to proceed when busy.
+ */
+ if (!chan->has_sg && !chan->idle)
return;
head_desc = list_first_entry(&chan->pending_list,
@@ -1596,10 +1608,9 @@ static void xilinx_dma_start_transfer(struct xilinx_dma_chan *chan)
reg &= ~XILINX_DMA_CR_COALESCE_MAX;
reg |= chan->desc_pendingcount <<
XILINX_DMA_CR_COALESCE_SHIFT;
- dma_ctrl_write(chan, XILINX_DMA_REG_DMACR, reg);
}
- if (chan->has_sg)
+ if (chan->has_sg && list_empty(&chan->active_list))
xilinx_write(chan, XILINX_DMA_REG_CURDESC,
head_desc->async_tx.phys);
reg &= ~XILINX_DMA_CR_DELAY_MAX;
@@ -1607,7 +1618,8 @@ static void xilinx_dma_start_transfer(struct xilinx_dma_chan *chan)
reg |= XILINX_DMA_DMAXR_ALL_IRQ_MASK;
dma_ctrl_write(chan, XILINX_DMA_REG_DMACR, reg);
- xilinx_dma_start(chan);
+ if (chan->idle)
+ xilinx_dma_start(chan);
if (chan->err)
return;
@@ -1660,9 +1672,6 @@ static void xilinx_mcdma_start_transfer(struct xilinx_dma_chan *chan)
if (chan->err)
return;
- if (!chan->idle)
- return;
-
if (list_empty(&chan->pending_list))
return;
@@ -1685,8 +1694,9 @@ static void xilinx_mcdma_start_transfer(struct xilinx_dma_chan *chan)
dma_ctrl_write(chan, XILINX_MCDMA_CHAN_CR_OFFSET(chan->tdest), reg);
/* Program current descriptor */
- xilinx_write(chan, XILINX_MCDMA_CHAN_CDESC_OFFSET(chan->tdest),
- head_desc->async_tx.phys);
+ if (chan->has_sg && list_empty(&chan->active_list))
+ xilinx_write(chan, XILINX_MCDMA_CHAN_CDESC_OFFSET(chan->tdest),
+ head_desc->async_tx.phys);
/* Program channel enable register */
reg = dma_ctrl_read(chan, XILINX_MCDMA_CHEN_OFFSET);
@@ -1698,7 +1708,8 @@ static void xilinx_mcdma_start_transfer(struct xilinx_dma_chan *chan)
reg |= XILINX_MCDMA_CR_RUNSTOP_MASK;
dma_ctrl_write(chan, XILINX_MCDMA_CHAN_CR_OFFSET(chan->tdest), reg);
- xilinx_dma_start(chan);
+ if (chan->idle)
+ xilinx_dma_start(chan);
if (chan->err)
return;
@@ -1794,8 +1805,9 @@ static int xilinx_dma_reset(struct xilinx_dma_chan *chan)
/* Wait for the hardware to finish reset */
err = xilinx_dma_poll_timeout(chan, XILINX_DMA_REG_DMACR, tmp,
- !(tmp & XILINX_DMA_DMACR_RESET), 0,
- XILINX_DMA_LOOP_COUNT);
+ !(tmp & XILINX_DMA_DMACR_RESET),
+ XILINX_DMA_POLL_DELAY_US,
+ XILINX_DMA_POLL_TIMEOUT_US);
if (err) {
dev_err(chan->dev, "reset timeout, cr %x, sr %x\n",
@@ -1893,8 +1905,10 @@ static irqreturn_t xilinx_mcdma_irq_handler(int irq, void *data)
if (status & XILINX_MCDMA_IRQ_IOC_MASK) {
spin_lock(&chan->lock);
xilinx_dma_complete_descriptor(chan);
- chan->idle = true;
- chan->start_transfer(chan);
+ if (list_empty(&chan->active_list)) {
+ chan->idle = true;
+ chan->start_transfer(chan);
+ }
spin_unlock(&chan->lock);
}
@@ -1950,8 +1964,10 @@ static irqreturn_t xilinx_dma_irq_handler(int irq, void *data)
XILINX_DMA_DMASR_DLY_CNT_IRQ)) {
spin_lock(&chan->lock);
xilinx_dma_complete_descriptor(chan);
- chan->idle = true;
- chan->start_transfer(chan);
+ if (list_empty(&chan->active_list)) {
+ chan->idle = true;
+ chan->start_transfer(chan);
+ }
spin_unlock(&chan->lock);
}
diff --git a/drivers/dma/xilinx/zynqmp_dma.c b/drivers/dma/xilinx/zynqmp_dma.c
index f6a812e49ddc..2b03584102ac 100644
--- a/drivers/dma/xilinx/zynqmp_dma.c
+++ b/drivers/dma/xilinx/zynqmp_dma.c
@@ -1159,8 +1159,6 @@ err_disable_pm:
/**
* zynqmp_dma_remove - Driver remove function
* @pdev: Pointer to the platform_device structure
- *
- * Return: Always '0'
*/
static void zynqmp_dma_remove(struct platform_device *pdev)
{
@@ -1170,9 +1168,9 @@ static void zynqmp_dma_remove(struct platform_device *pdev)
dma_async_device_unregister(&zdev->common);
zynqmp_dma_chan_remove(zdev->chan);
- if (pm_runtime_active(zdev->dev))
- zynqmp_dma_runtime_suspend(zdev->dev);
pm_runtime_disable(zdev->dev);
+ if (!pm_runtime_status_suspended(zdev->dev))
+ zynqmp_dma_runtime_suspend(zdev->dev);
}
static const struct of_device_id zynqmp_dma_of_match[] = {
diff --git a/drivers/nvme/target/pci-epf.c b/drivers/nvme/target/pci-epf.c
index 4e9db96ebfec..b1ba2d0bea6d 100644
--- a/drivers/nvme/target/pci-epf.c
+++ b/drivers/nvme/target/pci-epf.c
@@ -210,9 +210,7 @@ struct nvmet_pci_epf {
bool dma_enabled;
struct dma_chan *dma_tx_chan;
- struct mutex dma_tx_lock;
struct dma_chan *dma_rx_chan;
- struct mutex dma_rx_lock;
struct mutex mmio_lock;
@@ -295,9 +293,6 @@ static void nvmet_pci_epf_init_dma(struct nvmet_pci_epf *nvme_epf)
struct dma_chan *chan;
dma_cap_mask_t mask;
- mutex_init(&nvme_epf->dma_rx_lock);
- mutex_init(&nvme_epf->dma_tx_lock);
-
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
@@ -336,8 +331,6 @@ out_dma_no_tx:
nvme_epf->dma_rx_chan = NULL;
out_dma_no_rx:
- mutex_destroy(&nvme_epf->dma_rx_lock);
- mutex_destroy(&nvme_epf->dma_tx_lock);
nvme_epf->dma_enabled = false;
dev_info(&epf->dev, "DMA not supported, falling back to MMIO\n");
@@ -352,8 +345,6 @@ static void nvmet_pci_epf_deinit_dma(struct nvmet_pci_epf *nvme_epf)
nvme_epf->dma_tx_chan = NULL;
dma_release_channel(nvme_epf->dma_rx_chan);
nvme_epf->dma_rx_chan = NULL;
- mutex_destroy(&nvme_epf->dma_rx_lock);
- mutex_destroy(&nvme_epf->dma_tx_lock);
nvme_epf->dma_enabled = false;
}
@@ -368,18 +359,15 @@ static int nvmet_pci_epf_dma_transfer(struct nvmet_pci_epf *nvme_epf,
struct dma_chan *chan;
dma_cookie_t cookie;
dma_addr_t dma_addr;
- struct mutex *lock;
int ret;
switch (dir) {
case DMA_FROM_DEVICE:
- lock = &nvme_epf->dma_rx_lock;
chan = nvme_epf->dma_rx_chan;
sconf.direction = DMA_DEV_TO_MEM;
sconf.src_addr = seg->pci_addr;
break;
case DMA_TO_DEVICE:
- lock = &nvme_epf->dma_tx_lock;
chan = nvme_epf->dma_tx_chan;
sconf.direction = DMA_MEM_TO_DEV;
sconf.dst_addr = seg->pci_addr;
@@ -388,22 +376,15 @@ static int nvmet_pci_epf_dma_transfer(struct nvmet_pci_epf *nvme_epf,
return -EINVAL;
}
- mutex_lock(lock);
-
dma_dev = dmaengine_get_dma_device(chan);
dma_addr = dma_map_single(dma_dev, seg->buf, seg->length, dir);
ret = dma_mapping_error(dma_dev, dma_addr);
if (ret)
- goto unlock;
-
- ret = dmaengine_slave_config(chan, &sconf);
- if (ret) {
- dev_err(dev, "Failed to configure DMA channel\n");
- goto unmap;
- }
+ return ret;
- desc = dmaengine_prep_slave_single(chan, dma_addr, seg->length,
- sconf.direction, DMA_CTRL_ACK);
+ desc = dmaengine_prep_config_single_safe(chan, dma_addr, seg->length,
+ sconf.direction,
+ DMA_CTRL_ACK, &sconf);
if (!desc) {
dev_err(dev, "Failed to prepare DMA\n");
ret = -EIO;
@@ -420,16 +401,12 @@ static int nvmet_pci_epf_dma_transfer(struct nvmet_pci_epf *nvme_epf,
if (dma_sync_wait(chan, cookie) != DMA_COMPLETE) {
dev_err(dev, "DMA transfer failed\n");
ret = -EIO;
+ dmaengine_terminate_sync(chan);
}
- dmaengine_terminate_sync(chan);
-
unmap:
dma_unmap_single(dma_dev, dma_addr, seg->length, dir);
-unlock:
- mutex_unlock(lock);
-
return ret;
}
diff --git a/drivers/pci/endpoint/functions/pci-epf-mhi.c b/drivers/pci/endpoint/functions/pci-epf-mhi.c
index 7f5326925ed5..c3e3b58fb86c 100644
--- a/drivers/pci/endpoint/functions/pci-epf-mhi.c
+++ b/drivers/pci/endpoint/functions/pci-epf-mhi.c
@@ -328,12 +328,6 @@ static int pci_epf_mhi_edma_read(struct mhi_ep_cntrl *mhi_cntrl,
config.direction = DMA_DEV_TO_MEM;
config.src_addr = buf_info->host_addr;
- ret = dmaengine_slave_config(chan, &config);
- if (ret) {
- dev_err(dev, "Failed to configure DMA channel\n");
- goto err_unlock;
- }
-
dst_addr = dma_map_single(dma_dev, buf_info->dev_addr, buf_info->size,
DMA_FROM_DEVICE);
ret = dma_mapping_error(dma_dev, dst_addr);
@@ -342,9 +336,10 @@ static int pci_epf_mhi_edma_read(struct mhi_ep_cntrl *mhi_cntrl,
goto err_unlock;
}
- desc = dmaengine_prep_slave_single(chan, dst_addr, buf_info->size,
- DMA_DEV_TO_MEM,
- DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
+ desc = dmaengine_prep_config_single(chan, dst_addr, buf_info->size,
+ DMA_DEV_TO_MEM,
+ DMA_CTRL_ACK | DMA_PREP_INTERRUPT,
+ &config);
if (!desc) {
dev_err(dev, "Failed to prepare DMA\n");
ret = -EIO;
@@ -401,12 +396,6 @@ static int pci_epf_mhi_edma_write(struct mhi_ep_cntrl *mhi_cntrl,
config.direction = DMA_MEM_TO_DEV;
config.dst_addr = buf_info->host_addr;
- ret = dmaengine_slave_config(chan, &config);
- if (ret) {
- dev_err(dev, "Failed to configure DMA channel\n");
- goto err_unlock;
- }
-
src_addr = dma_map_single(dma_dev, buf_info->dev_addr, buf_info->size,
DMA_TO_DEVICE);
ret = dma_mapping_error(dma_dev, src_addr);
@@ -415,9 +404,10 @@ static int pci_epf_mhi_edma_write(struct mhi_ep_cntrl *mhi_cntrl,
goto err_unlock;
}
- desc = dmaengine_prep_slave_single(chan, src_addr, buf_info->size,
- DMA_MEM_TO_DEV,
- DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
+ desc = dmaengine_prep_config_single(chan, src_addr, buf_info->size,
+ DMA_MEM_TO_DEV,
+ DMA_CTRL_ACK | DMA_PREP_INTERRUPT,
+ &config);
if (!desc) {
dev_err(dev, "Failed to prepare DMA\n");
ret = -EIO;
@@ -506,12 +496,6 @@ static int pci_epf_mhi_edma_read_async(struct mhi_ep_cntrl *mhi_cntrl,
config.direction = DMA_DEV_TO_MEM;
config.src_addr = buf_info->host_addr;
- ret = dmaengine_slave_config(chan, &config);
- if (ret) {
- dev_err(dev, "Failed to configure DMA channel\n");
- goto err_unlock;
- }
-
dst_addr = dma_map_single(dma_dev, buf_info->dev_addr, buf_info->size,
DMA_FROM_DEVICE);
ret = dma_mapping_error(dma_dev, dst_addr);
@@ -520,9 +504,10 @@ static int pci_epf_mhi_edma_read_async(struct mhi_ep_cntrl *mhi_cntrl,
goto err_unlock;
}
- desc = dmaengine_prep_slave_single(chan, dst_addr, buf_info->size,
- DMA_DEV_TO_MEM,
- DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
+ desc = dmaengine_prep_config_single(chan, dst_addr, buf_info->size,
+ DMA_DEV_TO_MEM,
+ DMA_CTRL_ACK | DMA_PREP_INTERRUPT,
+ &config);
if (!desc) {
dev_err(dev, "Failed to prepare DMA\n");
ret = -EIO;
@@ -585,12 +570,6 @@ static int pci_epf_mhi_edma_write_async(struct mhi_ep_cntrl *mhi_cntrl,
config.direction = DMA_MEM_TO_DEV;
config.dst_addr = buf_info->host_addr;
- ret = dmaengine_slave_config(chan, &config);
- if (ret) {
- dev_err(dev, "Failed to configure DMA channel\n");
- goto err_unlock;
- }
-
src_addr = dma_map_single(dma_dev, buf_info->dev_addr, buf_info->size,
DMA_TO_DEVICE);
ret = dma_mapping_error(dma_dev, src_addr);
@@ -599,9 +578,10 @@ static int pci_epf_mhi_edma_write_async(struct mhi_ep_cntrl *mhi_cntrl,
goto err_unlock;
}
- desc = dmaengine_prep_slave_single(chan, src_addr, buf_info->size,
- DMA_MEM_TO_DEV,
- DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
+ desc = dmaengine_prep_config_single(chan, src_addr, buf_info->size,
+ DMA_MEM_TO_DEV,
+ DMA_CTRL_ACK | DMA_PREP_INTERRUPT,
+ &config);
if (!desc) {
dev_err(dev, "Failed to prepare DMA\n");
ret = -EIO;
diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c b/drivers/pci/endpoint/functions/pci-epf-test.c
index 4802d4f80f78..d4905aa8e4c0 100644
--- a/drivers/pci/endpoint/functions/pci-epf-test.c
+++ b/drivers/pci/endpoint/functions/pci-epf-test.c
@@ -183,12 +183,8 @@ static int pci_epf_test_data_transfer(struct pci_epf_test *epf_test,
else
sconf.src_addr = dma_remote;
- if (dmaengine_slave_config(chan, &sconf)) {
- dev_err(dev, "DMA slave config fail\n");
- return -EIO;
- }
- tx = dmaengine_prep_slave_single(chan, dma_local, len, dir,
- flags);
+ tx = dmaengine_prep_config_single(chan, dma_local, len,
+ dir, flags, &sconf);
} else {
tx = dmaengine_prep_dma_memcpy(chan, dma_dst, dma_src, len,
flags);