This is sample implementation of a driver that uses "avalon-dma"
driver interface to perform data transfers between on-chip and
system memory in devices using Avalon-MM DMA Interface for PCIe
design. Companion user-level tool could be found at
https://github.com/a-gordeev/avalon-tool.git

CC: Michael Chen <micc...@altera.com>
CC: de...@driverdev.osuosl.org
CC: dmaeng...@vger.kernel.org

Signed-off-by: Alexander Gordeev <a.gordeev....@gmail.com>
---
 drivers/dma/Kconfig                     |   1 +
 drivers/dma/Makefile                    |   1 +
 drivers/dma/avalon-test/Kconfig         |  23 +
 drivers/dma/avalon-test/Makefile        |  14 +
 drivers/dma/avalon-test/avalon-dev.c    |  65 +++
 drivers/dma/avalon-test/avalon-dev.h    |  33 ++
 drivers/dma/avalon-test/avalon-ioctl.c  | 128 +++++
 drivers/dma/avalon-test/avalon-ioctl.h  |  12 +
 drivers/dma/avalon-test/avalon-mmap.c   |  93 ++++
 drivers/dma/avalon-test/avalon-mmap.h   |  12 +
 drivers/dma/avalon-test/avalon-sg-buf.c | 132 +++++
 drivers/dma/avalon-test/avalon-sg-buf.h |  26 +
 drivers/dma/avalon-test/avalon-util.c   |  54 ++
 drivers/dma/avalon-test/avalon-util.h   |  12 +
 drivers/dma/avalon-test/avalon-xfer.c   | 697 ++++++++++++++++++++++++
 drivers/dma/avalon-test/avalon-xfer.h   |  28 +
 include/uapi/linux/avalon-ioctl.h       |  32 ++
 17 files changed, 1363 insertions(+)
 create mode 100644 drivers/dma/avalon-test/Kconfig
 create mode 100644 drivers/dma/avalon-test/Makefile
 create mode 100644 drivers/dma/avalon-test/avalon-dev.c
 create mode 100644 drivers/dma/avalon-test/avalon-dev.h
 create mode 100644 drivers/dma/avalon-test/avalon-ioctl.c
 create mode 100644 drivers/dma/avalon-test/avalon-ioctl.h
 create mode 100644 drivers/dma/avalon-test/avalon-mmap.c
 create mode 100644 drivers/dma/avalon-test/avalon-mmap.h
 create mode 100644 drivers/dma/avalon-test/avalon-sg-buf.c
 create mode 100644 drivers/dma/avalon-test/avalon-sg-buf.h
 create mode 100644 drivers/dma/avalon-test/avalon-util.c
 create mode 100644 drivers/dma/avalon-test/avalon-util.h
 create mode 100644 drivers/dma/avalon-test/avalon-xfer.c
 create mode 100644 drivers/dma/avalon-test/avalon-xfer.h
 create mode 100644 include/uapi/linux/avalon-ioctl.h

diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index f6f43480a4a4..4b3c6a6baf4c 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -670,6 +670,7 @@ source "drivers/dma/sh/Kconfig"
 source "drivers/dma/ti/Kconfig"
 
 source "drivers/dma/avalon/Kconfig"
+source "drivers/dma/avalon-test/Kconfig"
 
 # clients
 comment "DMA Clients"
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index fd7e11417b73..eb3ee7f6cac6 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -76,6 +76,7 @@ obj-$(CONFIG_XGENE_DMA) += xgene-dma.o
 obj-$(CONFIG_ZX_DMA) += zx_dma.o
 obj-$(CONFIG_ST_FDMA) += st_fdma.o
 obj-$(CONFIG_AVALON_DMA) += avalon/
+obj-$(CONFIG_AVALON_TEST) += avalon-test/
 
 obj-y += mediatek/
 obj-y += qcom/
diff --git a/drivers/dma/avalon-test/Kconfig b/drivers/dma/avalon-test/Kconfig
new file mode 100644
index 000000000000..021c28fe77a6
--- /dev/null
+++ b/drivers/dma/avalon-test/Kconfig
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Avalon DMA engine
+#
+# Author: Alexander Gordeev <a.gordeev....@gmail.com>
+#
+config AVALON_TEST
+       select AVALON_DMA
+       tristate "Intel Avalon-MM DMA Interface for PCIe test driver"
+       help
+         This selects a test driver for Avalon-MM DMA Interface for PCI
+
+if AVALON_TEST
+
+config AVALON_TEST_TARGET_BASE
+       hex "Target device base address"
+       default "0x70000000"
+
+config AVALON_TEST_TARGET_SIZE
+       hex "Target device memory size"
+       default "0x10000000"
+
+endif
diff --git a/drivers/dma/avalon-test/Makefile b/drivers/dma/avalon-test/Makefile
new file mode 100644
index 000000000000..63351c52478a
--- /dev/null
+++ b/drivers/dma/avalon-test/Makefile
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Avalon DMA driver
+#
+# Author: Alexander Gordeev <a.gordeev....@gmail.com>
+#
+obj-$(CONFIG_AVALON_TEST)      += avalon-test.o
+
+avalon-test-objs :=    avalon-dev.o \
+                       avalon-ioctl.o \
+                       avalon-mmap.o \
+                       avalon-sg-buf.o \
+                       avalon-xfer.o \
+                       avalon-util.o
diff --git a/drivers/dma/avalon-test/avalon-dev.c 
b/drivers/dma/avalon-test/avalon-dev.c
new file mode 100644
index 000000000000..9e83f777f937
--- /dev/null
+++ b/drivers/dma/avalon-test/avalon-dev.c
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Avalon DMA driver
+ *
+ * Author: Alexander Gordeev <a.gordeev....@gmail.com>
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+
+#include "avalon-dev.h"
+#include "avalon-ioctl.h"
+#include "avalon-mmap.h"
+
+const struct file_operations avalon_dev_fops = {
+       .llseek         = generic_file_llseek,
+       .unlocked_ioctl = avalon_dev_ioctl,
+       .mmap           = avalon_dev_mmap,
+};
+
+static struct avalon_dev avalon_dev;
+
+static int __init avalon_drv_init(void)
+{
+       struct avalon_dev *adev = &avalon_dev;
+       struct dma_chan *chan;
+       dma_cap_mask_t mask;
+       int ret;
+
+       dma_cap_zero(mask);
+       dma_cap_set(DMA_SLAVE, mask);
+
+       chan = dma_request_channel(mask, NULL, NULL);
+       if (!chan)
+               return -ENODEV;
+
+       adev->dma_chan          = chan;
+
+       adev->misc_dev.minor    = MISC_DYNAMIC_MINOR;
+       adev->misc_dev.name     = DEVICE_NAME;
+       adev->misc_dev.nodename = DEVICE_NAME;
+       adev->misc_dev.fops     = &avalon_dev_fops;
+       adev->misc_dev.mode     = 0644;
+
+       ret = misc_register(&adev->misc_dev);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static void __exit avalon_drv_exit(void)
+{
+       struct avalon_dev *adev = &avalon_dev;
+
+       misc_deregister(&adev->misc_dev);
+       dma_release_channel(adev->dma_chan);
+}
+
+module_init(avalon_drv_init);
+module_exit(avalon_drv_exit);
+
+MODULE_AUTHOR("Alexander Gordeev <a.gordeev....@gmail.com>");
+MODULE_DESCRIPTION("Avalon DMA control driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/dma/avalon-test/avalon-dev.h 
b/drivers/dma/avalon-test/avalon-dev.h
new file mode 100644
index 000000000000..f06362ebf110
--- /dev/null
+++ b/drivers/dma/avalon-test/avalon-dev.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Avalon DMA driver
+ *
+ * Author: Alexander Gordeev <a.gordeev....@gmail.com>
+ */
+#ifndef __AVALON_DEV_H__
+#define __AVALON_DEV_H__
+
+#include <linux/dmaengine.h>
+#include <linux/miscdevice.h>
+
+#include "../avalon/avalon-hw.h"
+
+#define TARGET_MEM_BASE                CONFIG_AVALON_TEST_TARGET_BASE
+#define TARGET_MEM_SIZE                CONFIG_AVALON_TEST_TARGET_SIZE
+
+#define TARGET_DMA_SIZE         (2 * AVALON_DMA_MAX_TANSFER_SIZE)
+#define TARGET_DMA_SIZE_SG      TARGET_MEM_SIZE
+
+#define DEVICE_NAME            "avalon-dev"
+
+struct avalon_dev {
+       struct dma_chan *dma_chan;
+       struct miscdevice misc_dev;
+};
+
+static inline struct device *chan_to_dev(struct dma_chan *chan)
+{
+       return chan->device->dev;
+}
+
+#endif
diff --git a/drivers/dma/avalon-test/avalon-ioctl.c 
b/drivers/dma/avalon-test/avalon-ioctl.c
new file mode 100644
index 000000000000..b90cdedf4400
--- /dev/null
+++ b/drivers/dma/avalon-test/avalon-ioctl.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Avalon DMA driver
+ *
+ * Author: Alexander Gordeev <a.gordeev....@gmail.com>
+ */
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/uio.h>
+
+#include <uapi/linux/avalon-ioctl.h>
+
+#include "avalon-xfer.h"
+
+long avalon_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+       struct avalon_dev *adev = container_of(file->private_data,
+               struct avalon_dev, misc_dev);
+       struct dma_chan *chan = adev->dma_chan;
+       struct device *dev = chan_to_dev(chan);
+       struct iovec iovec[2];
+       void __user *buf = NULL, __user *buf_rd = NULL, __user *buf_wr = NULL;
+       size_t len = 0, len_rd = 0, len_wr = 0;
+       int ret = 0;
+
+       dev_dbg(dev, "%s(%d) { cmd %x", __FUNCTION__, __LINE__, cmd);
+
+       switch (cmd) {
+       case IOCTL_AVALON_DMA_GET_INFO: {
+               struct avalon_dma_info info = {
+                       .mem_addr       = TARGET_MEM_BASE,
+                       .mem_size       = TARGET_MEM_SIZE,
+                       .dma_size       = TARGET_DMA_SIZE,
+                       .dma_size_sg    = TARGET_DMA_SIZE_SG,
+               };
+
+               if (copy_to_user((void *)arg, &info, sizeof(info))) {
+                       ret = -EFAULT;
+                       goto done;
+               }
+
+               goto done;
+       }
+       case IOCTL_AVALON_DMA_SET_INFO:
+               ret = -EINVAL;
+               goto done;
+
+       case IOCTL_AVALON_DMA_READ:
+       case IOCTL_AVALON_DMA_WRITE:
+       case IOCTL_AVALON_DMA_READ_SG:
+       case IOCTL_AVALON_DMA_WRITE_SG:
+       case IOCTL_AVALON_DMA_READ_SG_SMP:
+       case IOCTL_AVALON_DMA_WRITE_SG_SMP:
+               if (copy_from_user(iovec, (void *)arg, sizeof(iovec[0]))) {
+                       ret = -EFAULT;
+                       goto done;
+               }
+
+               buf = iovec[0].iov_base;
+               len = iovec[0].iov_len;
+
+               break;
+
+       case IOCTL_AVALON_DMA_RDWR:
+       case IOCTL_AVALON_DMA_RDWR_SG:
+               if (copy_from_user(iovec, (void *)arg, sizeof(iovec))) {
+                       ret = -EFAULT;
+                       goto done;
+               }
+
+               buf_rd = iovec[0].iov_base;
+               len_rd = iovec[0].iov_len;
+
+               buf_wr = iovec[1].iov_base;
+               len_wr = iovec[1].iov_len;
+
+               break;
+
+       default:
+               ret = -EINVAL;
+               goto done;
+       };
+
+       dev_dbg(dev,
+               "%s(%d) buf %px len %ld\nbuf_rd %px len_rd %ld\nbuf_wr %px 
len_wr %ld\n",
+               __FUNCTION__, __LINE__, buf, len, buf_rd, len_rd, buf_wr, 
len_wr);
+
+       switch (cmd) {
+       case IOCTL_AVALON_DMA_READ:
+               ret = xfer_rw(chan, DMA_FROM_DEVICE, buf, len);
+               break;
+       case IOCTL_AVALON_DMA_WRITE:
+               ret = xfer_rw(chan, DMA_TO_DEVICE, buf, len);
+               break;
+       case IOCTL_AVALON_DMA_RDWR:
+               ret = xfer_simultaneous(chan,
+                                       buf_rd, len_rd,
+                                       buf_wr, len_wr);
+               break;
+
+       case IOCTL_AVALON_DMA_READ_SG:
+               ret = xfer_rw_sg(chan, DMA_FROM_DEVICE, buf, len, false);
+               break;
+       case IOCTL_AVALON_DMA_WRITE_SG:
+               ret = xfer_rw_sg(chan, DMA_TO_DEVICE, buf, len, false);
+               break;
+       case IOCTL_AVALON_DMA_READ_SG_SMP:
+               ret = xfer_rw_sg(chan, DMA_FROM_DEVICE, buf, len, true);
+               break;
+       case IOCTL_AVALON_DMA_WRITE_SG_SMP:
+               ret = xfer_rw_sg(chan, DMA_TO_DEVICE, buf, len, true);
+               break;
+       case IOCTL_AVALON_DMA_RDWR_SG:
+               ret = xfer_simultaneous_sg(chan,
+                                          buf_rd, len_rd,
+                                          buf_wr, len_wr);
+               break;
+
+       default:
+               BUG();
+               ret = -EINVAL;
+       };
+
+done:
+       dev_dbg(dev, "%s(%d) } = %d", __FUNCTION__, __LINE__, ret);
+
+       return ret;
+}
diff --git a/drivers/dma/avalon-test/avalon-ioctl.h 
b/drivers/dma/avalon-test/avalon-ioctl.h
new file mode 100644
index 000000000000..14a5952f1e20
--- /dev/null
+++ b/drivers/dma/avalon-test/avalon-ioctl.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Avalon DMA driver
+ *
+ * Author: Alexander Gordeev <a.gordeev....@gmail.com>
+ */
+#ifndef __AVALON_IOCTL_H__
+#define __AVALON_IOCTL_H__
+
+long avalon_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+
+#endif
diff --git a/drivers/dma/avalon-test/avalon-mmap.c 
b/drivers/dma/avalon-test/avalon-mmap.c
new file mode 100644
index 000000000000..1309db9bceeb
--- /dev/null
+++ b/drivers/dma/avalon-test/avalon-mmap.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Avalon DMA driver
+ *
+ * Author: Alexander Gordeev <a.gordeev....@gmail.com>
+ */
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/dma-direction.h>
+
+#include "avalon-dev.h"
+#include "avalon-sg-buf.h"
+
+const gfp_t gfp_flags = GFP_KERNEL;
+
+static void avalon_drv_vm_close(struct vm_area_struct *vma)
+{
+       struct dma_sg_buf *sg_buf = vma->vm_private_data;
+       struct device *dev = sg_buf->dev;
+
+       dev_dbg(dev, "%s(%d) vma %px sg_buf %px",
+               __FUNCTION__, __LINE__, vma, sg_buf);
+
+       dma_sg_buf_free(sg_buf);
+}
+
+static const struct vm_operations_struct avalon_drv_vm_ops = {
+       .close  = avalon_drv_vm_close,
+};
+
+int avalon_dev_mmap(struct file *file, struct vm_area_struct *vma)
+{
+       struct avalon_dev *adev = container_of(file->private_data,
+               struct avalon_dev, misc_dev);
+       struct device *dev = chan_to_dev(adev->dma_chan);
+       unsigned long addr = vma->vm_start;
+       unsigned long size = vma->vm_end - vma->vm_start;
+       enum dma_data_direction dir;
+       struct dma_sg_buf *sg_buf;
+       int ret;
+       int i;
+
+       dev_dbg(dev, "%s(%d) { vm_pgoff %08lx vm_flags %08lx, size %lu",
+               __FUNCTION__, __LINE__, vma->vm_pgoff, vma->vm_flags, size);
+
+       if (!(IS_ALIGNED(addr, PAGE_SIZE) && IS_ALIGNED(size, PAGE_SIZE)))
+               return -EINVAL;
+       if ((vma->vm_pgoff * PAGE_SIZE + size) > TARGET_MEM_SIZE)
+               return -EINVAL;
+       if (!(((vma->vm_flags & (VM_READ | VM_WRITE)) == VM_READ) ||
+             ((vma->vm_flags & (VM_READ | VM_WRITE)) == VM_WRITE)))
+               return -EINVAL;
+       if (!(vma->vm_flags & VM_SHARED))
+               return -EINVAL;
+
+       vma->vm_ops = &avalon_drv_vm_ops;
+
+       if (vma->vm_flags & VM_WRITE)
+               dir = DMA_TO_DEVICE;
+       else
+               dir = DMA_FROM_DEVICE;
+
+       sg_buf = dma_sg_buf_alloc(dev, size, dir, gfp_flags);
+       if (IS_ERR(sg_buf)) {
+               ret = PTR_ERR(sg_buf);
+               goto sg_buf_alloc_err;
+       }
+
+       for (i = 0; size > 0; i++) {
+               ret = vm_insert_page(vma, addr, sg_buf->pages[i]);
+               if (ret)
+                       goto ins_page_err;
+
+               addr += PAGE_SIZE;
+               size -= PAGE_SIZE;
+       };
+
+       vma->vm_private_data = sg_buf;
+
+       dev_dbg(dev, "%s(%d) } vma %px sg_buf %px",
+               __FUNCTION__, __LINE__, vma, sg_buf);
+
+       return 0;
+
+ins_page_err:
+       dma_sg_buf_free(sg_buf);
+
+sg_buf_alloc_err:
+       dev_dbg(dev, "%s(%d) } vma %px err %d",
+               __FUNCTION__, __LINE__, vma, ret);
+
+       return ret;
+}
diff --git a/drivers/dma/avalon-test/avalon-mmap.h 
b/drivers/dma/avalon-test/avalon-mmap.h
new file mode 100644
index 000000000000..494be31c9f54
--- /dev/null
+++ b/drivers/dma/avalon-test/avalon-mmap.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Avalon DMA driver
+ *
+ * Author: Alexander Gordeev <a.gordeev....@gmail.com>
+ */
+#ifndef __AVALON_MMAP_H__
+#define __AVALON_MMAP_H__
+
+int avalon_dev_mmap(struct file *file, struct vm_area_struct *vma);
+
+#endif
diff --git a/drivers/dma/avalon-test/avalon-sg-buf.c 
b/drivers/dma/avalon-test/avalon-sg-buf.c
new file mode 100644
index 000000000000..620b03c42ec5
--- /dev/null
+++ b/drivers/dma/avalon-test/avalon-sg-buf.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Avalon DMA driver
+ *
+ * Author: Alexander Gordeev <a.gordeev....@gmail.com>
+ */
+#include <linux/kernel.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+
+#include "avalon-sg-buf.h"
+
+static int dma_sg_alloc_compacted(struct dma_sg_buf *buf, gfp_t gfp_flags)
+{
+       unsigned int last_page = 0;
+       int size = buf->size;
+
+       while (size > 0) {
+               struct page *pages;
+               int order;
+               int i;
+
+               order = get_order(size);
+               /* Dont over allocate*/
+               if ((PAGE_SIZE << order) > size)
+                       order--;
+
+               pages = NULL;
+               while (!pages) {
+                       pages = alloc_pages(gfp_flags | __GFP_NOWARN, order);
+                       if (pages)
+                               break;
+
+                       if (order == 0) {
+                               while (last_page--)
+                                       __free_page(buf->pages[last_page]);
+                               return -ENOMEM;
+                       }
+                       order--;
+               }
+
+               split_page(pages, order);
+               for (i = 0; i < (1 << order); i++)
+                       buf->pages[last_page++] = &pages[i];
+
+               size -= PAGE_SIZE << order;
+       }
+
+       return 0;
+}
+
+struct dma_sg_buf *dma_sg_buf_alloc(struct device *dev,
+                                   unsigned long size,
+                                   enum dma_data_direction dma_dir,
+                                   gfp_t gfp_flags)
+{
+       struct dma_sg_buf *buf;
+       struct sg_table *sgt;
+       int ret;
+       int num_pages;
+
+       buf = kzalloc(sizeof(*buf), GFP_KERNEL);
+       if (!buf)
+               return ERR_PTR(-ENOMEM);
+
+       buf->dma_dir = dma_dir;
+       buf->size = size;
+       /* size is already page aligned */
+       buf->num_pages = size >> PAGE_SHIFT;
+
+       buf->pages = kvmalloc_array(buf->num_pages, sizeof(struct page *),
+                                   GFP_KERNEL | __GFP_ZERO);
+       if (!buf->pages)
+               goto fail_pages_array_alloc;
+
+       ret = dma_sg_alloc_compacted(buf, gfp_flags);
+       if (ret)
+               goto fail_pages_alloc;
+
+       ret = sg_alloc_table_from_pages(&buf->sgt, buf->pages,
+                                       buf->num_pages, 0, size,
+                                       GFP_KERNEL);
+       if (ret)
+               goto fail_table_alloc;
+
+       buf->dev = get_device(dev);
+
+       sgt = &buf->sgt;
+
+       sgt->nents = dma_map_sg_attrs(buf->dev, sgt->sgl, sgt->orig_nents,
+                                     buf->dma_dir, DMA_ATTR_SKIP_CPU_SYNC);
+       if (!sgt->nents)
+               goto fail_map;
+
+       buf->vaddr = vm_map_ram(buf->pages, buf->num_pages, -1, PAGE_KERNEL);
+       if (!buf->vaddr)
+               goto fail_vm_map;
+
+       return buf;
+
+fail_vm_map:
+       dma_unmap_sg_attrs(buf->dev, sgt->sgl, sgt->orig_nents,
+                          buf->dma_dir, DMA_ATTR_SKIP_CPU_SYNC);
+fail_map:
+       put_device(buf->dev);
+       sg_free_table(&buf->sgt);
+fail_table_alloc:
+       num_pages = buf->num_pages;
+       while (num_pages--)
+               __free_page(buf->pages[num_pages]);
+fail_pages_alloc:
+       kvfree(buf->pages);
+fail_pages_array_alloc:
+       kfree(buf);
+       return ERR_PTR(-ENOMEM);
+}
+
+void dma_sg_buf_free(struct dma_sg_buf *buf)
+{
+       struct sg_table *sgt = &buf->sgt;
+       int i = buf->num_pages;
+
+       dma_unmap_sg_attrs(buf->dev, sgt->sgl, sgt->orig_nents,
+                          buf->dma_dir, DMA_ATTR_SKIP_CPU_SYNC);
+       vm_unmap_ram(buf->vaddr, buf->num_pages);
+       sg_free_table(&buf->sgt);
+       while (--i >= 0)
+               __free_page(buf->pages[i]);
+       kvfree(buf->pages);
+       put_device(buf->dev);
+       kfree(buf);
+}
diff --git a/drivers/dma/avalon-test/avalon-sg-buf.h 
b/drivers/dma/avalon-test/avalon-sg-buf.h
new file mode 100644
index 000000000000..c3a94bc1c664
--- /dev/null
+++ b/drivers/dma/avalon-test/avalon-sg-buf.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Avalon DMA driver
+ *
+ * Author: Alexander Gordeev <a.gordeev....@gmail.com>
+ */
+#ifndef __AVALON_SG_BUF_H__
+#define __AVALON_SG_BUF_H__
+
+struct dma_sg_buf {
+       struct device                   *dev;
+       void                            *vaddr;
+       struct page                     **pages;
+       enum dma_data_direction         dma_dir;
+       struct sg_table                 sgt;
+       size_t                          size;
+       unsigned int                    num_pages;
+};
+
+struct dma_sg_buf *dma_sg_buf_alloc(struct device *dev,
+                                   unsigned long size,
+                                   enum dma_data_direction dma_dir,
+                                   gfp_t gfp_flags);
+void dma_sg_buf_free(struct dma_sg_buf *buf);
+
+#endif
diff --git a/drivers/dma/avalon-test/avalon-util.c 
b/drivers/dma/avalon-test/avalon-util.c
new file mode 100644
index 000000000000..b7ca5aa495d2
--- /dev/null
+++ b/drivers/dma/avalon-test/avalon-util.c
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Avalon DMA driver
+ *
+ * Author: Alexander Gordeev <a.gordeev....@gmail.com>
+ */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/device.h>
+
+#if defined(CONFIG_DYNAMIC_DEBUG)
+static int print_mem(char *buf, size_t buf_len,
+                    const void *mem, size_t mem_len)
+{
+       int ret, i, total = 0;
+
+       if (buf_len < 3)
+               return -EINVAL;
+
+       mem_len = min_t(size_t, mem_len, buf_len / 3);
+       for (i = 0; i < mem_len; i++) {
+               ret = snprintf(buf + total, buf_len - total,
+                              "%02X ", ((const unsigned char *)mem)[i]);
+               if (ret < 0) {
+                       strcpy(buf, "--");
+                       return ret;
+               }
+               total += ret;
+       }
+
+       buf[total] = 0;
+
+       return total;
+}
+
+void dump_mem(struct device *dev, void *data, size_t len)
+{
+       char buf[256];
+       int n;
+
+       n = snprintf(buf, sizeof(buf),
+                    "%s(%d): %px [ ",
+                    __FUNCTION__, __LINE__, data);
+
+       print_mem(buf + n, sizeof(buf) - n, data, len);
+
+       dev_dbg(dev, "%s(%d): %s]\n", __FUNCTION__, __LINE__, buf);
+}
+#else
+void dump_mem(struct device *dev, void *data, size_t len)
+{
+}
+#endif
diff --git a/drivers/dma/avalon-test/avalon-util.h 
b/drivers/dma/avalon-test/avalon-util.h
new file mode 100644
index 000000000000..0d30dd6ecdf7
--- /dev/null
+++ b/drivers/dma/avalon-test/avalon-util.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Avalon DMA driver
+ *
+ * Author: Alexander Gordeev <a.gordeev....@gmail.com>
+ */
+#ifndef __AVALON_UTIL_H__
+#define __AVALON_UTIL_H__
+
+void dump_mem(struct device *dev, void *data, size_t len);
+
+#endif
diff --git a/drivers/dma/avalon-test/avalon-xfer.c 
b/drivers/dma/avalon-test/avalon-xfer.c
new file mode 100644
index 000000000000..96924c2159b2
--- /dev/null
+++ b/drivers/dma/avalon-test/avalon-xfer.c
@@ -0,0 +1,697 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Avalon DMA driver
+ *
+ * Author: Alexander Gordeev <a.gordeev....@gmail.com>
+ */
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/uaccess.h>
+#include <linux/kthread.h>
+#include <linux/sched/signal.h>
+#include <linux/dmaengine.h>
+
+#include "avalon-xfer.h"
+#include "avalon-sg-buf.h"
+#include "avalon-util.h"
+
+static const size_t dma_size   = TARGET_DMA_SIZE;
+static const int nr_dma_reps   = 2;
+static const int dmas_per_cpu  = 8;
+
+char *__dir_str[] = {
+       [DMA_BIDIRECTIONAL]     = "DMA_BIDIRECTIONAL",
+       [DMA_TO_DEVICE]         = "DMA_TO_DEVICE",
+       [DMA_FROM_DEVICE]       = "DMA_FROM_DEVICE",
+       [DMA_NONE]              = "DMA_NONE",
+};
+
+struct xfer_callback_info {
+       struct completion completion;
+       atomic_t counter;
+       ktime_t kt_start;
+       ktime_t kt_end;
+};
+
+static inline struct dma_async_tx_descriptor *__dmaengine_prep_slave_single(
+       struct dma_chan *chan, dma_addr_t buf, size_t len,
+       enum dma_transfer_direction dir, unsigned long flags)
+{
+       struct scatterlist *sg;
+
+       sg = kmalloc(sizeof(*sg), GFP_KERNEL);
+
+       sg_init_table(sg, 1);
+       sg_dma_address(sg) = buf;
+       sg_dma_len(sg) = len;
+
+       if (!chan || !chan->device || !chan->device->device_prep_slave_sg)
+               return NULL;
+
+       return chan->device->device_prep_slave_sg(chan, sg, 1,
+                                                 dir, flags, NULL);
+}
+
+static void init_callback_info(struct xfer_callback_info *info, int value)
+{
+       init_completion(&info->completion);
+
+       atomic_set(&info->counter, value);
+       smp_wmb();
+
+       info->kt_start = ktime_get();
+}
+
+static int xfer_callback(struct xfer_callback_info *info, const char *pfx)
+{
+       int ret;
+
+       info->kt_end = ktime_get();
+
+       smp_rmb();
+       if (atomic_dec_and_test(&info->counter)) {
+               complete(&info->completion);
+               ret = 0;
+       } else {
+               ret = 1;
+       }
+
+
+       return ret;
+}
+
+static void rd_xfer_callback(void *dma_async_param)
+{
+       struct xfer_callback_info *info = dma_async_param;
+
+       xfer_callback(info, "rd");
+
+}
+
+static void wr_xfer_callback(void *dma_async_param)
+{
+       struct xfer_callback_info *info = dma_async_param;
+
+       xfer_callback(info, "wr");
+}
+
+static int
+submit_xfer_single(struct dma_chan *chan,
+                  enum dma_data_direction dir,
+                  dma_addr_t dev_addr,
+                  dma_addr_t host_addr, unsigned int size,
+                  dma_async_tx_callback callback, void *callback_param)
+{
+       struct dma_async_tx_descriptor *tx;
+       struct dma_slave_config config = {
+               .direction      = dir,
+               .src_addr       = dev_addr,
+               .dst_addr       = dev_addr,
+       };
+       int ret;
+
+       ret = dmaengine_slave_config(chan, &config);
+       if (ret)
+               return ret;
+
+       tx = __dmaengine_prep_slave_single(chan, host_addr, size, dir, 0);
+       if (!tx)
+               return -ENOMEM;
+
+       tx->callback = callback;
+       tx->callback_param = callback_param;
+
+       ret = dmaengine_submit(tx);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+static int
+submit_xfer_sg(struct dma_chan *chan,
+              enum dma_data_direction dir,
+              dma_addr_t dev_addr,
+              struct scatterlist *sg, unsigned int sg_len,
+              dma_async_tx_callback callback, void *callback_param)
+{
+       struct dma_async_tx_descriptor *tx;
+       struct dma_slave_config config = {
+               .direction      = dir,
+               .src_addr       = dev_addr,
+               .dst_addr       = dev_addr,
+       };
+       int ret;
+
+       ret = dmaengine_slave_config(chan, &config);
+       if (ret)
+               return ret;
+
+       tx = dmaengine_prep_slave_sg(chan, sg, sg_len, dir, 0);
+       if (!tx)
+               return -ENOMEM;
+
+       tx->callback = callback;
+       tx->callback_param = callback_param;
+
+       ret = dmaengine_submit(tx);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+int xfer_rw(struct dma_chan *chan,
+           enum dma_data_direction dir,
+           void __user *user_buf, size_t user_len)
+{
+       struct device *dev = chan_to_dev(chan);
+       dma_addr_t dma_addr;
+       void *buf;
+       struct xfer_callback_info info;
+       void (*xfer_callback)(void *dma_async_param);
+       int ret;
+       int i;
+
+       const size_t size = dma_size;
+       const int nr_reps = nr_dma_reps;
+
+       dev_dbg(dev, "%s(%d) { dir %s",
+               __FUNCTION__, __LINE__, __dir_str[dir]);
+
+       if (user_len < size) {
+               ret = -EINVAL;
+               goto mem_len_err;
+       } else {
+               user_len = size;
+       }
+
+       switch (dir) {
+       case DMA_TO_DEVICE:
+               xfer_callback = wr_xfer_callback;
+               break;
+       case DMA_FROM_DEVICE:
+               xfer_callback = rd_xfer_callback;
+               break;
+       default:
+               BUG();
+               ret = -EINVAL;
+               goto dma_dir_err;
+       }
+
+       buf = kmalloc(size, GFP_KERNEL);
+       if (!buf) {
+               ret = -ENOMEM;
+               goto mem_alloc_err;
+       }
+
+       memset(buf, 0, size);
+
+       if (dir == DMA_TO_DEVICE) {
+               if (copy_from_user(buf, user_buf, user_len)) {
+                       ret = -EFAULT;
+                       goto cp_from_user_err;
+               }
+       }
+
+       dma_addr = dma_map_single(dev, buf, size, dir);
+       if (dma_mapping_error(dev, dma_addr)) {
+               ret = -ENOMEM;
+               goto dma_alloc_err;
+       }
+
+       init_callback_info(&info, nr_reps);
+
+       dev_dbg(dev, "%s(%d) dma_addr %08llx size %lu dir %d reps = %d",
+               __FUNCTION__, __LINE__, dma_addr, size, dir, nr_reps);
+
+       for (i = 0; i < nr_reps; i++) {
+               ret = submit_xfer_single(chan, dir,
+                                        TARGET_MEM_BASE, dma_addr, size,
+                                        xfer_callback, &info);
+               if (ret)
+                       goto dma_submit_err;
+       }
+
+       dma_async_issue_pending(chan);
+
+       ret = wait_for_completion_interruptible(&info.completion);
+       if (ret)
+               goto wait_err;
+
+       if (dir == DMA_FROM_DEVICE) {
+               if (copy_to_user(user_buf, buf, user_len))
+                       ret = -EFAULT;
+       }
+
+wait_err:
+dma_submit_err:
+       dma_unmap_single(dev, dma_addr, size, dir);
+
+dma_alloc_err:
+cp_from_user_err:
+       kfree(buf);
+
+mem_alloc_err:
+dma_dir_err:
+mem_len_err:
+       dev_dbg(dev, "%s(%d) } = %d", __FUNCTION__, __LINE__, ret);
+
+       return ret;
+}
+
+int xfer_simultaneous(struct dma_chan *chan,
+                     void __user *user_buf_rd, size_t user_len_rd,
+                     void __user *user_buf_wr, size_t user_len_wr)
+{
+       struct device *dev = chan_to_dev(chan);
+       dma_addr_t dma_addr_rd, dma_addr_wr;
+       void *buf_rd, *buf_wr;
+       struct xfer_callback_info info;
+       int ret;
+       int i;
+
+       const size_t size = dma_size;
+       const dma_addr_t target_rd = TARGET_MEM_BASE;
+       const dma_addr_t target_wr = target_rd + size;
+       const int nr_reps = nr_dma_reps;
+
+       dev_dbg(dev, "%s(%d) {", __FUNCTION__, __LINE__);
+
+       if (user_len_rd < size) {
+               ret = -EINVAL;
+               goto mem_len_err;
+       } else {
+               user_len_rd = size;
+       }
+
+       if (user_len_wr < size) {
+               ret = -EINVAL;
+               goto mem_len_err;
+       } else {
+               user_len_wr = size;
+       }
+
+       buf_rd = kmalloc(size, GFP_KERNEL);
+       if (!buf_rd) {
+               ret = -ENOMEM;
+               goto rd_mem_alloc_err;
+       }
+
+       buf_wr = kmalloc(size, GFP_KERNEL);
+       if (!buf_wr) {
+               ret = -ENOMEM;
+               goto wr_mem_alloc_err;
+       }
+
+       memset(buf_rd, 0, size);
+       memset(buf_wr, 0, size);
+
+       if (copy_from_user(buf_wr, user_buf_wr, user_len_wr)) {
+               ret = -EFAULT;
+               goto cp_from_user_err;
+       }
+
+       dma_addr_rd = dma_map_single(dev, buf_rd, size, DMA_FROM_DEVICE);
+       if (dma_mapping_error(dev, dma_addr_rd)) {
+               ret = -ENOMEM;
+               goto rd_dma_map_err;
+       }
+
+       dma_addr_wr = dma_map_single(dev, buf_wr, size, DMA_TO_DEVICE);
+       if (dma_mapping_error(dev, dma_addr_rd)) {
+               ret = -ENOMEM;
+               goto wr_dma_map_err;
+       }
+
+       init_callback_info(&info, 2 * nr_reps);
+
+       for (i = 0; i < nr_reps; i++) {
+               ret = submit_xfer_single(chan, DMA_TO_DEVICE,
+                                        target_wr, dma_addr_wr, size,
+                                        wr_xfer_callback, &info);
+               if (ret < 0)
+                       goto rd_dma_submit_err;
+
+               ret = submit_xfer_single(chan, DMA_FROM_DEVICE,
+                                        target_rd, dma_addr_rd, size,
+                                        rd_xfer_callback, &info);
+               if (ret < 0)
+                       goto wr_dma_submit_err;
+       }
+
+       dma_async_issue_pending(chan);
+
+       dev_dbg(dev,
+               "%s(%d) dma_addr %08llx/%08llx rd_size %lu wr_size %lu",
+               __FUNCTION__, __LINE__,
+               dma_addr_rd, dma_addr_wr, size, size);
+
+       ret = wait_for_completion_interruptible(&info.completion);
+       if (ret)
+               goto wait_err;
+
+       if (copy_to_user(user_buf_rd, buf_rd, user_len_rd))
+               ret = -EFAULT;
+
+wait_err:
+wr_dma_submit_err:
+rd_dma_submit_err:
+       dma_unmap_single(dev, dma_addr_wr, size, DMA_TO_DEVICE);
+
+wr_dma_map_err:
+       dma_unmap_single(dev, dma_addr_rd, size, DMA_FROM_DEVICE);
+
+rd_dma_map_err:
+cp_from_user_err:
+       kfree(buf_wr);
+
+wr_mem_alloc_err:
+       kfree(buf_rd);
+
+rd_mem_alloc_err:
+mem_len_err:
+       dev_dbg(dev, "%s(%d) } = %d", __FUNCTION__, __LINE__, ret);
+
+       return ret;
+}
+
+static int kthread_xfer_rw_sg(struct dma_chan *chan,
+                             enum dma_data_direction dir,
+                             dma_addr_t dev_addr,
+                             struct scatterlist *sg, unsigned int sg_len,
+                             void (*xfer_callback)(void *dma_async_param))
+{
+       struct xfer_callback_info info;
+       int ret;
+       int i;
+
+       const int nr_reps = nr_dma_reps;
+
+       while (!kthread_should_stop()) {
+               init_callback_info(&info, nr_reps);
+
+               for (i = 0; i < nr_reps; i++) {
+                       ret = submit_xfer_sg(chan, dir,
+                                           dev_addr, sg, sg_len,
+                                           xfer_callback, &info);
+                       if (ret < 0)
+                               goto err;
+               }
+
+               dma_async_issue_pending(chan);
+
+               ret = wait_for_completion_interruptible(&info.completion);
+               if (ret)
+                       goto err;
+       }
+
+       return 0;
+
+err:
+       while (!kthread_should_stop())
+               cond_resched();
+
+       return ret;
+}
+
+struct kthread_xfer_rw_sg_data {
+       struct dma_chan *chan;
+       enum dma_data_direction dir;
+       dma_addr_t dev_addr;
+       struct scatterlist *sg;
+       unsigned int sg_len;
+       void (*xfer_callback)(void *dma_async_param);
+};
+
+static int __kthread_xfer_rw_sg(void *_data)
+{
+       struct kthread_xfer_rw_sg_data *data = _data;
+
+       return kthread_xfer_rw_sg(data->chan, data->dir,
+                                 data->dev_addr, data->sg, data->sg_len,
+                                 data->xfer_callback);
+}
+
+static int __xfer_rw_sg_smp(struct dma_chan *chan,
+                           enum dma_data_direction dir,
+                           dma_addr_t dev_addr,
+                           struct scatterlist *sg, unsigned int sg_len,
+                           void (*xfer_callback)(void *dma_async_param))
+{
+       struct kthread_xfer_rw_sg_data data = {
+               chan, dir,
+               dev_addr, sg, sg_len,
+               xfer_callback
+       };
+       struct task_struct *task;
+       struct task_struct **tasks;
+       int nr_tasks = dmas_per_cpu * num_online_cpus();
+       int n, cpu;
+       int ret = 0;
+       int i = 0;
+
+       tasks = kmalloc(sizeof(tasks[0]) * nr_tasks, GFP_KERNEL);
+       if (!tasks)
+               return -ENOMEM;
+
+       for (n = 0; n < dmas_per_cpu; n++) {
+               for_each_online_cpu(cpu) {
+                       if (i >= nr_tasks) {
+                               ret = -ENOMEM;
+                               goto kthread_err;
+                       }
+
+                       task = kthread_create(__kthread_xfer_rw_sg,
+                                             &data, "av-dma-sg-%d-%d", cpu, n);
+                       if (IS_ERR(task)) {
+                               ret = PTR_ERR(task);
+                               goto kthread_err;
+                       }
+
+                       kthread_bind(task, cpu);
+
+                       tasks[i] = task;
+                       i++;
+               }
+       }
+
+       for (i = 0; i < nr_tasks; i++)
+               wake_up_process(tasks[i]);
+
+       /*
+        * Run child kthreads until user sent a signal (i.e Ctrl+C)
+        * and clear the signal to avid user program from being killed.
+        */
+       schedule_timeout_interruptible(MAX_SCHEDULE_TIMEOUT);
+       flush_signals(current);
+
+kthread_err:
+       for (i = 0; i < nr_tasks; i++)
+               kthread_stop(tasks[i]);
+
+       kfree(tasks);
+
+       return ret;
+}
+
+static int __xfer_rw_sg(struct dma_chan *chan,
+                       enum dma_data_direction dir,
+                       dma_addr_t dev_addr,
+                       struct scatterlist *sg, unsigned int sg_len,
+                       void (*xfer_callback)(void *dma_async_param))
+{
+       struct xfer_callback_info info;
+       int ret;
+       int i;
+
+       const int nr_reps = nr_dma_reps;
+
+       init_callback_info(&info, nr_reps);
+
+       for (i = 0; i < nr_reps; i++) {
+               ret = submit_xfer_sg(chan, dir,
+                                    dev_addr, sg, sg_len,
+                                    xfer_callback, &info);
+               if (ret < 0)
+                       return ret;
+       }
+
+       dma_async_issue_pending(chan);
+
+       ret = wait_for_completion_interruptible(&info.completion);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static struct vm_area_struct *get_vma(unsigned long addr,
+                                     unsigned long size)
+{
+       struct vm_area_struct *vma;
+       unsigned long vm_size;
+
+       vma = find_vma(current->mm, addr);
+       if (!vma || (vma->vm_start != addr))
+               return ERR_PTR(-ENXIO);
+
+       vm_size = vma->vm_end - vma->vm_start;
+       if (size > vm_size)
+               return ERR_PTR(-EINVAL);
+
+       return vma;
+}
+
+int xfer_rw_sg(struct dma_chan *chan,
+              enum dma_data_direction dir,
+              void __user *user_buf, size_t user_len,
+              bool is_smp)
+{
+       struct device *dev = chan_to_dev(chan);
+       int (*xfer)(struct dma_chan *chan,
+                   enum dma_data_direction dir,
+                   dma_addr_t dev_addr,
+                   struct scatterlist *sg, unsigned int sg_len,
+                   void (*xfer_callback)(void *dma_async_param));
+       void (*xfer_callback)(void *dma_async_param);
+       struct vm_area_struct *vma;
+       struct dma_sg_buf *sg_buf;
+       dma_addr_t dma_addr;
+       int ret;
+
+       dev_dbg(dev, "%s(%d) { dir %s smp %d",
+               __FUNCTION__, __LINE__, __dir_str[dir], is_smp);
+
+       vma = get_vma((unsigned long)user_buf, user_len);
+       if (IS_ERR(vma))
+               return PTR_ERR(vma);
+
+       sg_buf = vma->vm_private_data;
+       if (dir != sg_buf->dma_dir)
+               return -EINVAL;
+
+       if (is_smp)
+               xfer = __xfer_rw_sg_smp;
+       else
+               xfer = __xfer_rw_sg;
+
+       if (dir == DMA_FROM_DEVICE)
+               xfer_callback = rd_xfer_callback;
+       else
+               xfer_callback = wr_xfer_callback;
+
+       dma_addr = TARGET_MEM_BASE + vma->vm_pgoff * PAGE_SIZE;
+
+       if (dir == DMA_TO_DEVICE)
+               dump_mem(dev, sg_buf->vaddr, 16);
+
+       dma_sync_sg_for_device(dev,
+                              sg_buf->sgt.sgl, sg_buf->sgt.nents,
+                              sg_buf->dma_dir);
+
+       ret = xfer(chan, dir,
+                  dma_addr, sg_buf->sgt.sgl, sg_buf->sgt.nents,
+                  xfer_callback);
+       if (ret)
+               goto xfer_err;
+
+       dma_sync_sg_for_cpu(dev,
+                           sg_buf->sgt.sgl, sg_buf->sgt.nents,
+                           sg_buf->dma_dir);
+
+       if (dir == DMA_FROM_DEVICE)
+               dump_mem(dev, sg_buf->vaddr, 16);
+
+xfer_err:
+       dev_dbg(dev, "%s(%d) } = %d", __FUNCTION__, __LINE__, ret);
+
+       return ret;
+}
+
+int xfer_simultaneous_sg(struct dma_chan *chan,
+                        void __user *user_buf_rd, size_t user_len_rd,
+                        void __user *user_buf_wr, size_t user_len_wr)
+{
+       struct device *dev = chan_to_dev(chan);
+       dma_addr_t dma_addr_rd, dma_addr_wr;
+       struct xfer_callback_info info;
+       struct vm_area_struct *vma_rd, *vma_wr;
+       struct dma_sg_buf *sg_buf_rd, *sg_buf_wr;
+       int ret;
+       int i;
+
+       const int nr_reps = nr_dma_reps;
+
+       dev_dbg(dev, "%s(%d) {", __FUNCTION__, __LINE__);
+
+       vma_rd = get_vma((unsigned long)user_buf_rd, user_len_rd);
+       if (IS_ERR(vma_rd))
+               return PTR_ERR(vma_rd);
+
+       vma_wr = get_vma((unsigned long)user_buf_wr, user_len_wr);
+       if (IS_ERR(vma_wr))
+               return PTR_ERR(vma_wr);
+
+       sg_buf_rd = vma_rd->vm_private_data;
+       sg_buf_wr = vma_wr->vm_private_data;
+
+       if ((sg_buf_rd->dma_dir != DMA_FROM_DEVICE) ||
+           (sg_buf_wr->dma_dir != DMA_TO_DEVICE))
+               return -EINVAL;
+
+       dma_addr_rd = TARGET_MEM_BASE + vma_rd->vm_pgoff * PAGE_SIZE;
+       dma_addr_wr = TARGET_MEM_BASE + vma_wr->vm_pgoff * PAGE_SIZE;
+
+       init_callback_info(&info, 2 * nr_reps);
+
+       dma_sync_sg_for_device(dev,
+                              sg_buf_rd->sgt.sgl,
+                              sg_buf_rd->sgt.nents,
+                              DMA_FROM_DEVICE);
+       dma_sync_sg_for_device(dev,
+                              sg_buf_wr->sgt.sgl,
+                              sg_buf_wr->sgt.nents,
+                              DMA_TO_DEVICE);
+
+       for (i = 0; i < nr_reps; i++) {
+               ret = submit_xfer_sg(chan, DMA_TO_DEVICE,
+                                    dma_addr_wr,
+                                    sg_buf_wr->sgt.sgl,
+                                    sg_buf_wr->sgt.nents,
+                                    wr_xfer_callback, &info);
+               if (ret < 0)
+                       goto dma_submit_rd_err;
+
+               ret = submit_xfer_sg(chan, DMA_FROM_DEVICE,
+                                    dma_addr_rd,
+                                    sg_buf_wr->sgt.sgl,
+                                    sg_buf_wr->sgt.nents,
+                                    rd_xfer_callback, &info);
+               if (ret < 0)
+                       goto dma_submit_wr_err;
+       }
+
+       dma_async_issue_pending(chan);
+
+       ret = wait_for_completion_interruptible(&info.completion);
+       if (ret)
+               goto wait_err;
+
+       dma_sync_sg_for_cpu(dev,
+                           sg_buf_rd->sgt.sgl,
+                           sg_buf_rd->sgt.nents,
+                           DMA_FROM_DEVICE);
+       dma_sync_sg_for_cpu(dev,
+                           sg_buf_wr->sgt.sgl,
+                           sg_buf_wr->sgt.nents,
+                           DMA_TO_DEVICE);
+
+wait_err:
+dma_submit_wr_err:
+dma_submit_rd_err:
+       dev_dbg(dev, "%s(%d) } = %d", __FUNCTION__, __LINE__, ret);
+
+       return ret;
+}
diff --git a/drivers/dma/avalon-test/avalon-xfer.h 
b/drivers/dma/avalon-test/avalon-xfer.h
new file mode 100644
index 000000000000..7cf515543284
--- /dev/null
+++ b/drivers/dma/avalon-test/avalon-xfer.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Avalon DMA driver
+ *
+ * Author: Alexander Gordeev <a.gordeev....@gmail.com>
+ */
+#ifndef __AVALON_XFER_H__
+#define __AVALON_XFER_H__
+
+#include <linux/dma-direction.h>
+
+#include "avalon-dev.h"
+
+int xfer_rw(struct dma_chan *chan,
+           enum dma_data_direction dir,
+           void __user *user_buf, size_t user_len);
+int xfer_simultaneous(struct dma_chan *chan,
+                     void __user *user_buf_rd, size_t user_len_rd,
+                     void __user *user_buf_wr, size_t user_len_wr);
+int xfer_rw_sg(struct dma_chan *chan,
+              enum dma_data_direction dir,
+              void __user *user_buf, size_t user_len,
+              bool is_smp);
+int xfer_simultaneous_sg(struct dma_chan *chan,
+                        void __user *user_buf_rd, size_t user_len_rd,
+                        void __user *user_buf_wr, size_t user_len_wr);
+
+#endif
diff --git a/include/uapi/linux/avalon-ioctl.h 
b/include/uapi/linux/avalon-ioctl.h
new file mode 100644
index 000000000000..b929c2dd46ea
--- /dev/null
+++ b/include/uapi/linux/avalon-ioctl.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Avalon DMA driver
+ *
+ * Author: Alexander Gordeev <a.gordeev....@gmail.com>
+ */
+#ifndef _UAPI_LINUX_AVALON_IOCTL_H__
+#define _UAPI_LINUX_AVALON_IOCTL_H__
+
+#define AVALON_DEVICE_NAME             "avalon-dev"
+
+struct avalon_dma_info {
+       size_t mem_addr;
+       size_t mem_size;
+       size_t dma_size;
+       size_t dma_size_sg;
+} __attribute((packed));
+
+#define AVALON_SIG 'V'
+
+#define IOCTL_AVALON_DMA_GET_INFO      _IOR(AVALON_SIG, 0, struct 
avalon_dma_info)
+#define IOCTL_AVALON_DMA_SET_INFO      _IOW(AVALON_SIG, 1, struct 
avalon_dma_info)
+#define IOCTL_AVALON_DMA_READ          _IOR(AVALON_SIG, 2, struct iovec)
+#define IOCTL_AVALON_DMA_WRITE         _IOW(AVALON_SIG, 3, struct iovec)
+#define IOCTL_AVALON_DMA_RDWR          _IOWR(AVALON_SIG, 4, struct iovec[2])
+#define IOCTL_AVALON_DMA_READ_SG       _IOR(AVALON_SIG, 5, struct iovec)
+#define IOCTL_AVALON_DMA_WRITE_SG      _IOW(AVALON_SIG, 6, struct iovec)
+#define IOCTL_AVALON_DMA_RDWR_SG       _IOWR(AVALON_SIG, 7, struct iovec[2])
+#define IOCTL_AVALON_DMA_READ_SG_SMP   _IOR(AVALON_SIG, 8, struct iovec)
+#define IOCTL_AVALON_DMA_WRITE_SG_SMP  _IOW(AVALON_SIG, 9, struct iovec)
+
+#endif
-- 
2.23.0

Reply via email to