From: Angelo Dureghello <angelo@sysam.it>
To: vinod.koul@intel.com, dmaengine@vger.kernel.org
Cc: gerg@linux-m68k.org, linux-m68k@vger.kernel.org
Subject: dmaengine: mcf-edma: add ColdFire mcf5441x eDMA support
Date: Wed, 25 Apr 2018 22:08:17 +0200 [thread overview]
Message-ID: <20180425200816.GA4662@jerusalem> (raw)
This patch adds dma support for NXP mcf5441x (ColdFire) family.
ColdFire mcf5441x implements an edma hw module similar to the
one implemented in Vybrid VFxxx controllers, but with a slightly
different register set, more dma channels (64 instead of 32),
a different interrupt mechanism and some other minor differences.
For the above reasons, modfying fsl-edma.c was too complex and
likely too ugly. From here, the decision to create a different
driver, but starting from fsl-edma.
The driver has been tested with mcf5441x (stmark2 board) and
dspi driver, it worked fine and seems reliable at least as a
first initial version.
Signed-off-by: Angelo Dureghello <angelo@sysam.it>
---
arch/m68k/configs/stmark2_defconfig | 2 +
drivers/dma/Kconfig | 11 +
drivers/dma/Makefile | 1 +
drivers/dma/mcf-edma.c | 847 +++++++++++++++++++++
include/linux/platform_data/dma-mcf-edma.h | 38 +
5 files changed, 899 insertions(+)
create mode 100644 drivers/dma/mcf-edma.c
create mode 100644 include/linux/platform_data/dma-mcf-edma.h
diff --git a/arch/m68k/configs/stmark2_defconfig b/arch/m68k/configs/stmark2_defconfig
index bf2bfd4ebd2a..2d111e0aeb48 100644
--- a/arch/m68k/configs/stmark2_defconfig
+++ b/arch/m68k/configs/stmark2_defconfig
@@ -71,6 +71,8 @@ CONFIG_GPIO_GENERIC_PLATFORM=y
# CONFIG_HWMON is not set
# CONFIG_HID is not set
# CONFIG_USB_SUPPORT is not set
+CONFIG_DMADEVICES=y
+CONFIG_MCF_EDMA=y
# CONFIG_FILE_LOCKING is not set
# CONFIG_DNOTIFY is not set
# CONFIG_INOTIFY_USER is not set
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index 6d61cd023633..139626c01ba4 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -225,6 +225,17 @@ config FSL_EDMA
multiplexing capability for DMA request sources(slot).
This module can be found on Freescale Vybrid and LS-1 SoCs.
+config MCF_EDMA
+ tristate "Freescale eDMA engine support, ColdFire mcf5441x SoCs"
+ depends on M5441x
+ select DMA_ENGINE
+ select DMA_VIRTUAL_CHANNELS
+ help
+ Support the Freescale ColdFire eDMA engine, 64-channel
+ implementation that performs complex data transfers with
+ minimal intervention from a host processor.
+ This module can be found on Freescale ColdFire mcf5441x SoCs.
+
config FSL_RAID
tristate "Freescale RAID engine Support"
depends on FSL_SOC && !ASYNC_TX_ENABLE_CHANNEL_SWITCH
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index 0f62a4d49aab..db93824441aa 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_DW_DMAC_CORE) += dw/
obj-$(CONFIG_EP93XX_DMA) += ep93xx_dma.o
obj-$(CONFIG_FSL_DMA) += fsldma.o
obj-$(CONFIG_FSL_EDMA) += fsl-edma.o
+obj-$(CONFIG_MCF_EDMA) += mcf-edma.o
obj-$(CONFIG_FSL_RAID) += fsl_raid.o
obj-$(CONFIG_HSU_DMA) += hsu/
obj-$(CONFIG_IMG_MDC_DMA) += img-mdc-dma.o
diff --git a/drivers/dma/mcf-edma.c b/drivers/dma/mcf-edma.c
new file mode 100644
index 000000000000..8fb6282e922a
--- /dev/null
+++ b/drivers/dma/mcf-edma.c
@@ -0,0 +1,847 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * drivers/dma/mcf-edma.c
+ *
+ * Driver for the Freescale ColdFire 64-ch eDMA implementation,
+ * derived from drivers/dma/fsl-edma.c.
+ *
+ * Copyright 2013-2014 Freescale Semiconductor, Inc
+ *
+ * Copyright 2017 Sysam, Angelo Dureghello <angelo@sysam.it>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/dmaengine.h>
+#include <linux/dmapool.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/dma-mcf-edma.h>
+
+#include "virt-dma.h"
+
+#define EDMA_CHANNELS 64
+#define EDMA_MASK_CH(x) ((x) & 0x3F)
+#define EDMA_MASK_ITER(x) ((x) & 0x7FFF)
+#define EDMA_TCD_MEM_ALIGN 32
+
+#define EDMA_TCD_ATTR_DSZ_8b (0x0000)
+#define EDMA_TCD_ATTR_DSZ_16b (0x0001)
+#define EDMA_TCD_ATTR_DSZ_32b (0x0002)
+#define EDMA_TCD_ATTR_DSZ_16B (0x0004)
+#define EDMA_TCD_ATTR_SSZ_8b (EDMA_TCD_ATTR_DSZ_8b << 8)
+#define EDMA_TCD_ATTR_SSZ_16b (EDMA_TCD_ATTR_DSZ_16b << 8)
+#define EDMA_TCD_ATTR_SSZ_32b (EDMA_TCD_ATTR_DSZ_32b << 8)
+#define EDMA_TCD_ATTR_SSZ_16B (EDMA_TCD_ATTR_DSZ_16B << 8)
+
+#define MCF_EDMA_BUSWIDTHS (BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | \
+ BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | \
+ BIT(DMA_SLAVE_BUSWIDTH_4_BYTES) | \
+ BIT(DMA_SLAVE_BUSWIDTH_8_BYTES))
+
+#define EDMA_CR_ERCA BIT(2)
+#define EDMA_CR_ERGA BIT(3)
+
+#define EDMA_TCD_CSR_INT_MAJOR BIT(1)
+#define EDMA_TCD_CSR_D_REQ BIT(3)
+#define EDMA_TCD_CSR_E_SG BIT(4)
+
+#define EDMA_CERR_CERR(x) ((x) & 0x1F)
+
+struct edma_tcd {
+ u32 saddr;
+ u16 attr;
+ u16 soff;
+ u32 nbytes;
+ u32 slast;
+ u32 daddr;
+ u16 citer;
+ u16 doff;
+ u32 dlast_sga;
+ u16 biter;
+ u16 csr;
+};
+
+struct edma_regs {
+ u32 cr;
+ u32 es;
+ u32 erqh;
+ u32 erql;
+ u32 eeih;
+ u32 eeil;
+ u8 serq;
+ u8 cerq;
+ u8 seei;
+ u8 ceei;
+ u8 cint;
+ u8 cerr;
+ u8 ssrt;
+ u8 cdne;
+ u32 inth;
+ u32 intl;
+ u32 errh;
+ u32 errl;
+ u32 rsh;
+ u32 rsl;
+ u8 res[200];
+ u8 dch_pri[EDMA_CHANNELS];
+ u8 res2[3776];
+ struct edma_tcd tcd[EDMA_CHANNELS];
+};
+
+struct mcf_edma_sw_tcd {
+ dma_addr_t ptcd;
+ struct edma_tcd *vtcd;
+};
+
+struct mcf_edma_desc {
+ struct virt_dma_desc vdesc;
+ struct mcf_edma_chan *echan;
+ bool iscyclic;
+ unsigned int n_tcds;
+ struct mcf_edma_sw_tcd tcd[];
+};
+
+struct mcf_edma_slave_config {
+ enum dma_transfer_direction dir;
+ enum dma_slave_buswidth addr_width;
+ u32 dev_addr;
+ u32 burst;
+ u32 attr;
+};
+
+struct mcf_edma_chan {
+ struct virt_dma_chan vchan;
+ enum dma_status status;
+ bool idle;
+ struct mcf_edma_engine *edma;
+ struct dma_pool *tcd_pool;
+ struct mcf_edma_desc *edesc;
+ struct mcf_edma_slave_config esc;
+ u32 slave_id;
+};
+
+struct mcf_edma_engine {
+ struct dma_device dma_dev;
+ int n_chans;
+ void __iomem *membase;
+ struct mutex mcf_edma_mutex;
+ struct mcf_edma_chan chans[];
+};
+
+static struct mcf_edma_chan *to_mcf_edma_chan(struct dma_chan *chan)
+{
+ return container_of(chan, struct mcf_edma_chan, vchan.chan);
+}
+
+static struct mcf_edma_desc *to_mcf_edma_desc(struct virt_dma_desc *vd)
+{
+ return container_of(vd, struct mcf_edma_desc, vdesc);
+}
+
+static unsigned int mcf_edma_get_tcd_attr(enum dma_slave_buswidth addr_width)
+{
+ switch (addr_width) {
+ case 1:
+ return EDMA_TCD_ATTR_SSZ_8b | EDMA_TCD_ATTR_DSZ_8b;
+ case 2:
+ return EDMA_TCD_ATTR_SSZ_16b | EDMA_TCD_ATTR_DSZ_16b;
+ case 4:
+ default:
+ return EDMA_TCD_ATTR_SSZ_32b | EDMA_TCD_ATTR_DSZ_32b;
+ }
+}
+
+static void mcf_edma_enable_request(struct mcf_edma_chan *mcf_chan)
+{
+ struct edma_regs *regs = mcf_chan->edma->membase;
+ u32 ch = mcf_chan->vchan.chan.chan_id;
+
+ iowrite8(EDMA_MASK_CH(ch), ®s->seei);
+ iowrite8(EDMA_MASK_CH(ch), ®s->serq);
+}
+
+static void mcf_edma_disable_request(struct mcf_edma_chan *mcf_chan)
+{
+ struct edma_regs *regs = mcf_chan->edma->membase;
+ u32 ch = mcf_chan->vchan.chan.chan_id;
+
+ iowrite8(EDMA_MASK_CH(ch), ®s->cerq);
+ iowrite8(EDMA_MASK_CH(ch), ®s->ceei);
+}
+
+static void mcf_edma_set_tcd_regs(struct mcf_edma_chan *mcf_chan,
+ struct edma_tcd *tcd)
+{
+ struct edma_regs *regs = mcf_chan->edma->membase;
+ u32 ch = mcf_chan->vchan.chan.chan_id;
+
+ iowrite16(0, ®s->tcd[ch].csr);
+ iowrite32(tcd->saddr, ®s->tcd[ch].saddr);
+ iowrite16(tcd->attr, ®s->tcd[ch].attr);
+ iowrite16(tcd->soff, ®s->tcd[ch].soff);
+ iowrite32(tcd->nbytes, ®s->tcd[ch].nbytes);
+ iowrite32(tcd->slast, ®s->tcd[ch].slast);
+ iowrite32(tcd->daddr, ®s->tcd[ch].daddr);
+ iowrite16(tcd->citer, ®s->tcd[ch].citer);
+ iowrite16(tcd->doff, ®s->tcd[ch].doff);
+ iowrite32(tcd->dlast_sga, ®s->tcd[ch].dlast_sga);
+ iowrite16(tcd->biter, ®s->tcd[ch].biter);
+ iowrite16(tcd->csr, ®s->tcd[ch].csr);
+}
+
+static void mcf_edma_xfer_desc(struct mcf_edma_chan *mcf_chan)
+{
+ struct virt_dma_desc *vdesc;
+
+ vdesc = vchan_next_desc(&mcf_chan->vchan);
+ if (!vdesc)
+ return;
+
+ mcf_chan->edesc = to_mcf_edma_desc(vdesc);
+
+ mcf_edma_set_tcd_regs(mcf_chan, mcf_chan->edesc->tcd[0].vtcd);
+ mcf_edma_enable_request(mcf_chan);
+
+ mcf_chan->status = DMA_IN_PROGRESS;
+ mcf_chan->idle = false;
+}
+
+static irqreturn_t mcf_edma_tx_handler(int irq, void *dev_id)
+{
+ struct mcf_edma_engine *mcf_edma = dev_id;
+ struct edma_regs *regs = mcf_edma->membase;
+ unsigned int ch;
+ struct mcf_edma_chan *mcf_chan;
+ u64 intmap;
+
+ intmap = ioread32(®s->inth);
+ intmap <<= 32;
+ intmap |= ioread32(®s->intl);
+ if (!intmap)
+ return IRQ_NONE;
+
+ for (ch = 0; ch < mcf_edma->n_chans; ch++) {
+ if (intmap & (0x1 << ch)) {
+ iowrite8(EDMA_MASK_CH(ch), ®s->cint);
+
+ mcf_chan = &mcf_edma->chans[ch];
+
+ spin_lock(&mcf_chan->vchan.lock);
+ if (!mcf_chan->edesc->iscyclic) {
+ list_del(&mcf_chan->edesc->vdesc.node);
+ vchan_cookie_complete(&mcf_chan->edesc->vdesc);
+ mcf_chan->edesc = NULL;
+ mcf_chan->status = DMA_COMPLETE;
+ mcf_chan->idle = true;
+ } else {
+ vchan_cyclic_callback(&mcf_chan->edesc->vdesc);
+ }
+
+ if (!mcf_chan->edesc)
+ mcf_edma_xfer_desc(mcf_chan);
+
+ spin_unlock(&mcf_chan->vchan.lock);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t mcf_edma_err_handler(int irq, void *dev_id)
+{
+ struct mcf_edma_engine *mcf_edma = dev_id;
+ struct edma_regs *regs = mcf_edma->membase;
+ unsigned int err, ch;
+
+ err = ioread32(®s->errl);
+ if (!err)
+ return IRQ_NONE;
+
+ for (ch = 0; ch < (EDMA_CHANNELS / 2); ch++) {
+ if (err & (0x1 << ch)) {
+ mcf_edma_disable_request(&mcf_edma->chans[ch]);
+ iowrite8(EDMA_CERR_CERR(ch), ®s->cerr);
+ mcf_edma->chans[ch].status = DMA_ERROR;
+ mcf_edma->chans[ch].idle = true;
+ }
+ }
+
+ err = ioread32(®s->errh);
+ if (!err)
+ return IRQ_NONE;
+
+ for (ch = (EDMA_CHANNELS / 2); ch < EDMA_CHANNELS; ch++) {
+ if (err & (0x1 << (ch - (EDMA_CHANNELS / 2)))) {
+ mcf_edma_disable_request(&mcf_edma->chans[ch]);
+ iowrite8(EDMA_CERR_CERR(ch), ®s->cerr);
+ mcf_edma->chans[ch].status = DMA_ERROR;
+ mcf_edma->chans[ch].idle = true;
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static inline void mcf_edma_fill_tcd(struct edma_tcd *tcd,
+ u32 src, u32 dst, u16 attr, u16 soff, u32 nbytes,
+ u32 slast, u16 citer, u16 biter, u16 doff,
+ u32 dlast_sga, bool major_int,
+ bool disable_req, bool enable_sg)
+{
+ u16 csr = 0;
+
+ tcd->saddr = src;
+ tcd->daddr = dst;
+ tcd->attr = attr;
+ tcd->soff = soff;
+ tcd->nbytes = nbytes;
+ tcd->slast = slast;
+ tcd->citer = EDMA_MASK_ITER(citer);
+ tcd->doff = doff;
+ tcd->dlast_sga = dlast_sga;
+ tcd->biter = EDMA_MASK_ITER(biter);
+
+ if (major_int)
+ csr |= EDMA_TCD_CSR_INT_MAJOR;
+
+ if (disable_req)
+ csr |= EDMA_TCD_CSR_D_REQ;
+
+ if (enable_sg)
+ csr |= EDMA_TCD_CSR_E_SG;
+
+ tcd->csr = csr;
+}
+
+static int mcf_edma_slave_config(struct dma_chan *chan,
+ struct dma_slave_config *cfg)
+{
+ struct mcf_edma_chan *mcf_chan = to_mcf_edma_chan(chan);
+
+ mcf_chan->esc.dir = cfg->direction;
+ if (cfg->direction == DMA_DEV_TO_MEM) {
+ mcf_chan->esc.dev_addr = cfg->src_addr;
+ mcf_chan->esc.addr_width = cfg->src_addr_width;
+ mcf_chan->esc.burst = cfg->src_maxburst;
+ mcf_chan->esc.attr = mcf_edma_get_tcd_attr(cfg->src_addr_width);
+ } else if (cfg->direction == DMA_MEM_TO_DEV) {
+ mcf_chan->esc.dev_addr = cfg->dst_addr;
+ mcf_chan->esc.addr_width = cfg->dst_addr_width;
+ mcf_chan->esc.burst = cfg->dst_maxburst;
+ mcf_chan->esc.attr = mcf_edma_get_tcd_attr(cfg->dst_addr_width);
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct mcf_edma_desc *mcf_edma_alloc_desc(
+ struct mcf_edma_chan *mcf_chan, int sg_len)
+{
+ struct mcf_edma_desc *mcf_desc;
+ int i;
+
+ mcf_desc = kzalloc(sizeof(*mcf_desc) + sizeof(struct mcf_edma_sw_tcd)
+ * sg_len, GFP_NOWAIT);
+ if (!mcf_desc)
+ return NULL;
+
+ mcf_desc->echan = mcf_chan;
+ mcf_desc->n_tcds = sg_len;
+ for (i = 0; i < sg_len; i++) {
+ mcf_desc->tcd[i].vtcd = dma_pool_alloc(mcf_chan->tcd_pool,
+ GFP_NOWAIT, &mcf_desc->tcd[i].ptcd);
+ if (!mcf_desc->tcd[i].vtcd)
+ goto err;
+ }
+
+ return mcf_desc;
+
+err:
+ while (--i >= 0)
+ dma_pool_free(mcf_chan->tcd_pool, mcf_desc->tcd[i].vtcd,
+ mcf_desc->tcd[i].ptcd);
+ kfree(mcf_desc);
+
+ return NULL;
+}
+
+static struct dma_async_tx_descriptor *mcf_edma_prep_slave_sg(
+ struct dma_chan *chan, struct scatterlist *sgl,
+ unsigned int sg_len, enum dma_transfer_direction direction,
+ unsigned long flags, void *context)
+{
+ struct mcf_edma_chan *mcf_chan = to_mcf_edma_chan(chan);
+ struct mcf_edma_desc *mcf_desc;
+ struct scatterlist *sg;
+ u32 src_addr, dst_addr, last_sg, nbytes;
+ u16 soff, doff, iter;
+ int i;
+
+ if (!is_slave_direction(mcf_chan->esc.dir))
+ return NULL;
+
+ mcf_desc = mcf_edma_alloc_desc(mcf_chan, sg_len);
+ if (!mcf_desc)
+ return NULL;
+
+ mcf_desc->iscyclic = false;
+
+ nbytes = mcf_chan->esc.addr_width * mcf_chan->esc.burst;
+ for_each_sg(sgl, sg, sg_len, i) {
+ /* get next sg's physical address */
+ last_sg = mcf_desc->tcd[(i + 1) % sg_len].ptcd;
+
+ if (mcf_chan->esc.dir == DMA_MEM_TO_DEV) {
+ src_addr = sg_dma_address(sg);
+ dst_addr = mcf_chan->esc.dev_addr;
+ soff = mcf_chan->esc.addr_width;
+ doff = 0;
+ } else {
+ src_addr = mcf_chan->esc.dev_addr;
+ dst_addr = sg_dma_address(sg);
+ soff = 0;
+ doff = mcf_chan->esc.addr_width;
+ }
+
+ iter = sg_dma_len(sg) / nbytes;
+ if (i < sg_len - 1) {
+ last_sg = mcf_desc->tcd[(i + 1)].ptcd;
+ mcf_edma_fill_tcd(mcf_desc->tcd[i].vtcd, src_addr,
+ dst_addr, mcf_chan->esc.attr, soff,
+ nbytes, 0, iter, iter, doff, last_sg,
+ false, false, true);
+ } else {
+ last_sg = 0;
+ mcf_edma_fill_tcd(mcf_desc->tcd[i].vtcd, src_addr,
+ dst_addr, mcf_chan->esc.attr, soff,
+ nbytes, 0, iter, iter, doff, last_sg,
+ true, true, false);
+ }
+ }
+
+ return vchan_tx_prep(&mcf_chan->vchan, &mcf_desc->vdesc, flags);
+}
+
+static struct dma_async_tx_descriptor *mcf_edma_prep_dma_cyclic(
+ struct dma_chan *chan, dma_addr_t dma_addr, size_t buf_len,
+ size_t period_len, enum dma_transfer_direction direction,
+ unsigned long flags)
+{
+ struct mcf_edma_chan *mcf_chan = to_mcf_edma_chan(chan);
+ struct mcf_edma_desc *mcf_desc;
+ dma_addr_t dma_buf_next;
+ int sg_len, i;
+ u32 src_addr, dst_addr, last_sg, nbytes;
+ u16 soff, doff, iter;
+
+ if (!is_slave_direction(mcf_chan->esc.dir))
+ return NULL;
+
+ sg_len = buf_len / period_len;
+ mcf_desc = mcf_edma_alloc_desc(mcf_chan, sg_len);
+ if (!mcf_desc)
+ return NULL;
+ mcf_desc->iscyclic = true;
+
+ dma_buf_next = dma_addr;
+ nbytes = mcf_chan->esc.addr_width * mcf_chan->esc.burst;
+ iter = period_len / nbytes;
+
+ for (i = 0; i < sg_len; i++) {
+ if (dma_buf_next >= dma_addr + buf_len)
+ dma_buf_next = dma_addr;
+
+ /* get next sg's physical address */
+ last_sg = mcf_desc->tcd[(i + 1) % sg_len].ptcd;
+
+ if (mcf_chan->esc.dir == DMA_MEM_TO_DEV) {
+ src_addr = dma_buf_next;
+ dst_addr = mcf_chan->esc.dev_addr;
+ soff = mcf_chan->esc.addr_width;
+ doff = 0;
+ } else {
+ src_addr = mcf_chan->esc.dev_addr;
+ dst_addr = dma_buf_next;
+ soff = 0;
+ doff = mcf_chan->esc.addr_width;
+ }
+
+ mcf_edma_fill_tcd(mcf_desc->tcd[i].vtcd, src_addr, dst_addr,
+ mcf_chan->esc.attr, soff, nbytes, 0, iter,
+ iter, doff, last_sg, true, false, true);
+ dma_buf_next += period_len;
+ }
+
+ return vchan_tx_prep(&mcf_chan->vchan, &mcf_desc->vdesc, flags);
+}
+
+static int mcf_edma_alloc_chan_resources(struct dma_chan *chan)
+{
+ struct mcf_edma_chan *mcf_chan = to_mcf_edma_chan(chan);
+
+ mcf_chan->tcd_pool = dma_pool_create("tcd_pool", chan->device->dev,
+ sizeof(struct edma_tcd), EDMA_TCD_MEM_ALIGN, 0);
+
+ return 0;
+}
+
+static void mcf_edma_free_chan_resources(struct dma_chan *chan)
+{
+ struct mcf_edma_chan *mcf_chan = to_mcf_edma_chan(chan);
+ unsigned long flags;
+ LIST_HEAD(head);
+
+ spin_lock_irqsave(&mcf_chan->vchan.lock, flags);
+ mcf_edma_disable_request(mcf_chan);
+ mcf_chan->edesc = NULL;
+ vchan_get_all_descriptors(&mcf_chan->vchan, &head);
+ spin_unlock_irqrestore(&mcf_chan->vchan.lock, flags);
+
+ vchan_dma_desc_free_list(&mcf_chan->vchan, &head);
+ dma_pool_destroy(mcf_chan->tcd_pool);
+ mcf_chan->tcd_pool = NULL;
+}
+
+static size_t mcf_edma_desc_residue(struct mcf_edma_chan *mcf_chan,
+ struct virt_dma_desc *vdesc, bool in_progress)
+{
+ struct mcf_edma_desc *edesc = mcf_chan->edesc;
+ struct edma_regs *regs = mcf_chan->edma->membase;
+ u32 ch = mcf_chan->vchan.chan.chan_id;
+ enum dma_transfer_direction dir = mcf_chan->esc.dir;
+ dma_addr_t cur_addr, dma_addr;
+ size_t len, size;
+ int i;
+
+ /* calculate the total size in this desc */
+ for (len = i = 0; i < mcf_chan->edesc->n_tcds; i++)
+ len += edesc->tcd[i].vtcd->nbytes * edesc->tcd[i].vtcd->biter;
+
+ if (!in_progress)
+ return len;
+
+ cur_addr = (dir == DMA_MEM_TO_DEV) ?
+ ioread32(®s->tcd[ch].saddr) :
+ ioread32(®s->tcd[ch].daddr);
+
+ /* figure out the finished and calculate the residue */
+ for (i = 0; i < mcf_chan->edesc->n_tcds; i++) {
+ size = edesc->tcd[i].vtcd->nbytes * edesc->tcd[i].vtcd->biter;
+ if (dir == DMA_MEM_TO_DEV)
+ dma_addr = edesc->tcd[i].vtcd->saddr;
+ else
+ dma_addr = edesc->tcd[i].vtcd->daddr;
+
+ len -= size;
+ if (cur_addr >= dma_addr && cur_addr < dma_addr + size) {
+ len += dma_addr + size - cur_addr;
+ break;
+ }
+ }
+
+ return len;
+}
+
+static enum dma_status mcf_edma_tx_status(struct dma_chan *chan,
+ dma_cookie_t cookie, struct dma_tx_state *txstate)
+{
+ struct mcf_edma_chan *mcf_chan = to_mcf_edma_chan(chan);
+ struct virt_dma_desc *vdesc;
+ enum dma_status status;
+ unsigned long flags;
+
+ status = dma_cookie_status(chan, cookie, txstate);
+ if (status == DMA_COMPLETE)
+ return status;
+
+ if (!txstate)
+ return mcf_chan->status;
+
+ spin_lock_irqsave(&mcf_chan->vchan.lock, flags);
+ vdesc = vchan_find_desc(&mcf_chan->vchan, cookie);
+ if (mcf_chan->edesc && cookie == mcf_chan->edesc->vdesc.tx.cookie)
+ txstate->residue =
+ mcf_edma_desc_residue(mcf_chan, vdesc, true);
+ else if (vdesc)
+ txstate->residue =
+ mcf_edma_desc_residue(mcf_chan, vdesc, false);
+ else
+ txstate->residue = 0;
+
+ spin_unlock_irqrestore(&mcf_chan->vchan.lock, flags);
+
+ return mcf_chan->status;
+}
+
+static void mcf_edma_issue_pending(struct dma_chan *chan)
+{
+ struct mcf_edma_chan *mcf_chan = to_mcf_edma_chan(chan);
+ unsigned long flags;
+
+ spin_lock_irqsave(&mcf_chan->vchan.lock, flags);
+
+ if (vchan_issue_pending(&mcf_chan->vchan) && !mcf_chan->edesc)
+ mcf_edma_xfer_desc(mcf_chan);
+
+ spin_unlock_irqrestore(&mcf_chan->vchan.lock, flags);
+}
+
+static void mcf_edma_free_desc(struct virt_dma_desc *vdesc)
+{
+ struct mcf_edma_desc *mcf_desc;
+ int i;
+ struct edma_regs *regs;
+
+ mcf_desc = to_mcf_edma_desc(vdesc);
+ regs = mcf_desc->echan->edma->membase;
+
+ //trace_tcd(®s->tcd[mcf_desc->echan->slave_id]);
+ //trace_regs(regs);
+
+ for (i = 0; i < mcf_desc->n_tcds; i++)
+ dma_pool_free(mcf_desc->echan->tcd_pool, mcf_desc->tcd[i].vtcd,
+ mcf_desc->tcd[i].ptcd);
+ kfree(mcf_desc);
+}
+
+static int mcf_edma_irq_init(struct platform_device *pdev,
+ struct mcf_edma_engine *mcf_edma)
+{
+ int ret = 0, i;
+ struct resource *res;
+
+ res = platform_get_resource_byname(pdev,
+ IORESOURCE_IRQ, "edma-tx-00-15");
+ if (!res)
+ return -1;
+
+ for (ret = 0, i = res->start; i <= res->end; ++i) {
+ ret |= devm_request_irq(&pdev->dev, i,
+ mcf_edma_tx_handler, 0, "eDMA", mcf_edma);
+ }
+ if (ret)
+ return ret;
+
+ res = platform_get_resource_byname(pdev,
+ IORESOURCE_IRQ, "edma-tx-16-55");
+ if (!res)
+ return -1;
+
+ for (ret = 0, i = res->start; i <= res->end; ++i) {
+ ret |= devm_request_irq(&pdev->dev, i,
+ mcf_edma_tx_handler, 0, "eDMA", mcf_edma);
+ }
+ if (ret)
+ return ret;
+
+ ret = platform_get_irq_byname(pdev, "edma-tx-56-63");
+ if (ret != -ENXIO) {
+ ret = devm_request_irq(&pdev->dev, ret,
+ mcf_edma_tx_handler, 0, "eDMA", mcf_edma);
+ if (ret)
+ return ret;
+ }
+
+ ret = platform_get_irq_byname(pdev, "edma-err");
+ if (ret != -ENXIO) {
+ ret = devm_request_irq(&pdev->dev, ret,
+ mcf_edma_err_handler, 0, "eDMA", mcf_edma);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void mcf_edma_irq_free(struct platform_device *pdev,
+ struct mcf_edma_engine *mcf_edma)
+{
+ int irq;
+ struct resource *res;
+
+ res = platform_get_resource_byname(pdev,
+ IORESOURCE_IRQ, "edma-tx-00-15");
+ if (res) {
+ for (irq = res->start; irq <= res->end; irq++)
+ devm_free_irq(&pdev->dev, irq, mcf_edma);
+ }
+
+ res = platform_get_resource_byname(pdev,
+ IORESOURCE_IRQ, "edma-tx-16-55");
+ if (res) {
+ for (irq = res->start; irq <= res->end; irq++)
+ devm_free_irq(&pdev->dev, irq, mcf_edma);
+ }
+
+ irq = platform_get_irq_byname(pdev, "edma-tx-56-63");
+ if (irq != -ENXIO)
+ devm_free_irq(&pdev->dev, irq, mcf_edma);
+
+ irq = platform_get_irq_byname(pdev, "edma-err");
+ if (irq != -ENXIO)
+ devm_free_irq(&pdev->dev, irq, mcf_edma);
+}
+
+static int mcf_edma_probe(struct platform_device *pdev)
+{
+ struct mcf_edma_platform_data *pdata;
+ struct mcf_edma_engine *mcf_edma;
+ struct mcf_edma_chan *mcf_chan;
+ struct edma_regs *regs;
+ struct resource *res;
+ int ret, i, len, chans;
+
+ pdata = dev_get_platdata(&pdev->dev);
+ if (!pdata)
+ return PTR_ERR(pdata);
+
+ chans = pdata->dma_channels;
+ len = sizeof(*mcf_edma) + sizeof(*mcf_chan) * chans;
+ mcf_edma = devm_kzalloc(&pdev->dev, len, GFP_KERNEL);
+ if (!mcf_edma)
+ return -ENOMEM;
+
+ mcf_edma->n_chans = chans;
+
+ if (!mcf_edma->n_chans) {
+ pr_info("setting default channel number to 64");
+ mcf_edma->n_chans = 64;
+ }
+
+ mutex_init(&mcf_edma->mcf_edma_mutex);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ mcf_edma->membase = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(mcf_edma->membase))
+ return PTR_ERR(mcf_edma->membase);
+
+ regs = mcf_edma->membase;
+
+ INIT_LIST_HEAD(&mcf_edma->dma_dev.channels);
+ for (i = 0; i < mcf_edma->n_chans; i++) {
+ struct mcf_edma_chan *mcf_chan = &mcf_edma->chans[i];
+
+ mcf_chan->edma = mcf_edma;
+ mcf_chan->slave_id = i;
+ mcf_chan->idle = true;
+ mcf_chan->vchan.desc_free = mcf_edma_free_desc;
+ vchan_init(&mcf_chan->vchan, &mcf_edma->dma_dev);
+ iowrite32(0x0, ®s->tcd[i].csr);
+ }
+
+ iowrite32(~0, ®s->inth);
+ iowrite32(~0, ®s->intl);
+
+ ret = mcf_edma_irq_init(pdev, mcf_edma);
+ if (ret)
+ return ret;
+
+ dma_cap_set(DMA_PRIVATE, mcf_edma->dma_dev.cap_mask);
+ dma_cap_set(DMA_SLAVE, mcf_edma->dma_dev.cap_mask);
+ dma_cap_set(DMA_CYCLIC, mcf_edma->dma_dev.cap_mask);
+
+ mcf_edma->dma_dev.dev = &pdev->dev;
+ mcf_edma->dma_dev.device_alloc_chan_resources =
+ mcf_edma_alloc_chan_resources;
+ mcf_edma->dma_dev.device_free_chan_resources =
+ mcf_edma_free_chan_resources;
+ mcf_edma->dma_dev.device_config = mcf_edma_slave_config;
+ mcf_edma->dma_dev.device_prep_dma_cyclic =
+ mcf_edma_prep_dma_cyclic;
+ mcf_edma->dma_dev.device_prep_slave_sg = mcf_edma_prep_slave_sg;
+ mcf_edma->dma_dev.device_tx_status = mcf_edma_tx_status;
+ mcf_edma->dma_dev.device_issue_pending = mcf_edma_issue_pending;
+
+ mcf_edma->dma_dev.src_addr_widths = MCF_EDMA_BUSWIDTHS;
+ mcf_edma->dma_dev.dst_addr_widths = MCF_EDMA_BUSWIDTHS;
+ mcf_edma->dma_dev.directions =
+ BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
+
+ mcf_edma->dma_dev.filter.fn = mcf_edma_filter_fn;
+ mcf_edma->dma_dev.filter.map = pdata->slave_map;
+ mcf_edma->dma_dev.filter.mapcnt = pdata->slavecnt;
+
+ platform_set_drvdata(pdev, mcf_edma);
+
+ ret = dma_async_device_register(&mcf_edma->dma_dev);
+ if (ret) {
+ pr_err("Can't register Freescale eDMA engine. (%d)\n", ret);
+ return ret;
+ }
+
+ /* Enable round robin arbitration */
+ iowrite32(EDMA_CR_ERGA | EDMA_CR_ERCA, ®s->cr);
+
+ return 0;
+}
+
+static void mcf_edma_cleanup_vchan(struct dma_device *dmadev)
+{
+ struct mcf_edma_chan *chan, *_chan;
+
+ list_for_each_entry_safe(chan, _chan,
+ &dmadev->channels, vchan.chan.device_node) {
+ list_del(&chan->vchan.chan.device_node);
+ tasklet_kill(&chan->vchan.task);
+ }
+}
+
+static int mcf_edma_remove(struct platform_device *pdev)
+{
+ struct mcf_edma_engine *mcf_edma = platform_get_drvdata(pdev);
+
+ mcf_edma_irq_free(pdev, mcf_edma);
+ mcf_edma_cleanup_vchan(&mcf_edma->dma_dev);
+ dma_async_device_unregister(&mcf_edma->dma_dev);
+
+ return 0;
+}
+
+static struct platform_driver mcf_edma_driver = {
+ .driver = {
+ .name = "mcf-edma",
+ },
+ .probe = mcf_edma_probe,
+ .remove = mcf_edma_remove,
+};
+
+bool mcf_edma_filter_fn(struct dma_chan *chan, void *param)
+{
+ if (chan->device->dev->driver == &mcf_edma_driver.driver) {
+ struct mcf_edma_chan *mcf_chan = to_mcf_edma_chan(chan);
+
+ return (mcf_chan->slave_id == (int)param);
+ }
+
+ return false;
+}
+EXPORT_SYMBOL(mcf_edma_filter_fn);
+
+static int __init mcf_edma_init(void)
+{
+ return platform_driver_register(&mcf_edma_driver);
+}
+subsys_initcall(mcf_edma_init);
+
+static void __exit mcf_edma_exit(void)
+{
+ platform_driver_unregister(&mcf_edma_driver);
+}
+module_exit(mcf_edma_exit);
+
+MODULE_ALIAS("platform:mcf-edma");
+MODULE_DESCRIPTION("Freescale eDMA engine driver, ColdFire family");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/platform_data/dma-mcf-edma.h b/include/linux/platform_data/dma-mcf-edma.h
new file mode 100644
index 000000000000..9a1819acb28f
--- /dev/null
+++ b/include/linux/platform_data/dma-mcf-edma.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Freescale eDMA platform data, ColdFire SoC's family.
+ *
+ * Copyright (c) 2017 Angelo Dureghello <angelo@sysam.it>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __MACH_MCF_EDMA_H__
+#define __MACH_MCF_EDMA_H__
+
+struct dma_slave_map;
+
+bool mcf_edma_filter_fn(struct dma_chan *chan, void *param);
+
+#define MCF_EDMA_FILTER_PARAM(ch) ((void *)ch)
+
+/**
+ * struct mcf_edma_platform_data - platform specific data for eDMA engine
+ *
+ * @ver The eDMA module version.
+ * @dma_channels The number of eDMA channels.
+ */
+struct mcf_edma_platform_data {
+ int dma_channels;
+ const struct dma_slave_map *slave_map;
+ int slavecnt;
+};
+
+#endif /* __MACH_MCF_EDMA_H__ */
next reply other threads:[~2018-04-25 20:08 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2018-04-25 20:08 Angelo Dureghello [this message]
-- strict thread matches above, loose matches on Subject: below --
2018-05-03 16:48 dmaengine: mcf-edma: add ColdFire mcf5441x eDMA support Vinod Koul
2018-05-04 19:18 Angelo Dureghello
2018-05-07 14:15 Vinod Koul
2018-05-22 21:28 Angelo Dureghello
2018-05-23 5:37 Vinod Koul
2018-05-26 20:50 Angelo Dureghello
2018-05-28 4:01 Vinod Koul
2018-05-30 20:59 Angelo Dureghello
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20180425200816.GA4662@jerusalem \
--to=angelo@sysam.it \
--cc=dmaengine@vger.kernel.org \
--cc=gerg@linux-m68k.org \
--cc=linux-m68k@vger.kernel.org \
--cc=vinod.koul@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.