dmaengine: usb-dmac: Add Renesas USB DMA Controller (USB-DMAC) driver
authorYoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
Wed, 1 Apr 2015 06:22:45 +0000 (15:22 +0900)
committerVinod Koul <vinod.koul@intel.com>
Thu, 2 Apr 2015 07:27:32 +0000 (12:57 +0530)
This DMAC is Renesas USB high-speed module DMA controller that
supports slave transfer.

This USB-DMAC has similar register sets with R-Car Gen2 DMAC, but
the USB-DMAC has specific registers to control the USB transactions.
If this code is added into the rcar-dmac driver, it will become
unreadable. So, this driver is independent from the rcar-dmac.

And, this USB-DMAC uses virt-dma infrastructure.

Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
Signed-off-by: Vinod Koul <vinod.koul@intel.com>
drivers/dma/sh/Kconfig
drivers/dma/sh/Makefile
drivers/dma/sh/usb-dmac.c [new file with mode: 0644]

index 8190ad225a1b80bdad6daab9d43904421b8481f7..f6ca002cb7c59378172b08e7f3a945be108ae0e7 100644 (file)
@@ -64,3 +64,12 @@ config RCAR_DMAC
        help
          This driver supports the general purpose DMA controller found in the
          Renesas R-Car second generation SoCs.
+
+config RENESAS_USB_DMAC
+       tristate "Renesas USB-DMA Controller"
+       depends on ARCH_SHMOBILE || COMPILE_TEST
+       select RENESAS_DMA
+       select DMA_VIRTUAL_CHANNELS
+       help
+         This driver supports the USB-DMA controller found in the Renesas
+         SoCs.
index 2852f9db61a40a668f3140299c6a2760226aea13..221ab19b8f773fba251347964042f6614a27ff1a 100644 (file)
@@ -17,3 +17,4 @@ obj-$(CONFIG_SUDMAC) += sudmac.o
 obj-$(CONFIG_RCAR_HPB_DMAE) += rcar-hpbdma.o
 obj-$(CONFIG_RCAR_AUDMAC_PP) += rcar-audmapp.o
 obj-$(CONFIG_RCAR_DMAC) += rcar-dmac.o
+obj-$(CONFIG_RENESAS_USB_DMAC) += usb-dmac.o
diff --git a/drivers/dma/sh/usb-dmac.c b/drivers/dma/sh/usb-dmac.c
new file mode 100644 (file)
index 0000000..d5dad98
--- /dev/null
@@ -0,0 +1,910 @@
+/*
+ * Renesas USB DMA Controller Driver
+ *
+ * Copyright (C) 2015 Renesas Electronics Corporation
+ *
+ * based on rcar-dmac.c
+ * Copyright (C) 2014 Renesas Electronics Inc.
+ * Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/interrupt.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_dma.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include "../dmaengine.h"
+#include "../virt-dma.h"
+
+/*
+ * struct usb_dmac_sg - Descriptor for a hardware transfer
+ * @mem_addr: memory address
+ * @size: transfer size in bytes
+ */
+struct usb_dmac_sg {
+       dma_addr_t mem_addr;
+       u32 size;
+};
+
+/*
+ * struct usb_dmac_desc - USB DMA Transfer Descriptor
+ * @vd: base virtual channel DMA transaction descriptor
+ * @direction: direction of the DMA transfer
+ * @sg_allocated_len: length of allocated sg
+ * @sg_len: length of sg
+ * @sg_index: index of sg
+ * @residue: residue after the DMAC completed a transfer
+ * @node: node for desc_got and desc_freed
+ * @done_cookie: cookie after the DMAC completed a transfer
+ * @sg: information for the transfer
+ */
+struct usb_dmac_desc {
+       struct virt_dma_desc vd;
+       enum dma_transfer_direction direction;
+       unsigned int sg_allocated_len;
+       unsigned int sg_len;
+       unsigned int sg_index;
+       u32 residue;
+       struct list_head node;
+       dma_cookie_t done_cookie;
+       struct usb_dmac_sg sg[0];
+};
+
+#define to_usb_dmac_desc(vd)   container_of(vd, struct usb_dmac_desc, vd)
+
+/*
+ * struct usb_dmac_chan - USB DMA Controller Channel
+ * @vc: base virtual DMA channel object
+ * @iomem: channel I/O memory base
+ * @index: index of this channel in the controller
+ * @irq: irq number of this channel
+ * @desc: the current descriptor
+ * @descs_allocated: number of descriptors allocated
+ * @desc_got: got descriptors
+ * @desc_freed: freed descriptors after the DMAC completed a transfer
+ */
+struct usb_dmac_chan {
+       struct virt_dma_chan vc;
+       void __iomem *iomem;
+       unsigned int index;
+       int irq;
+       struct usb_dmac_desc *desc;
+       int descs_allocated;
+       struct list_head desc_got;
+       struct list_head desc_freed;
+};
+
+#define to_usb_dmac_chan(c) container_of(c, struct usb_dmac_chan, vc.chan)
+
+/*
+ * struct usb_dmac - USB DMA Controller
+ * @engine: base DMA engine object
+ * @dev: the hardware device
+ * @iomem: remapped I/O memory base
+ * @n_channels: number of available channels
+ * @channels: array of DMAC channels
+ */
+struct usb_dmac {
+       struct dma_device engine;
+       struct device *dev;
+       void __iomem *iomem;
+
+       unsigned int n_channels;
+       struct usb_dmac_chan *channels;
+};
+
+#define to_usb_dmac(d)         container_of(d, struct usb_dmac, engine)
+
+/* -----------------------------------------------------------------------------
+ * Registers
+ */
+
+#define USB_DMAC_CHAN_OFFSET(i)                (0x20 + 0x20 * (i))
+
+#define USB_DMASWR                     0x0008
+#define USB_DMASWR_SWR                 (1 << 0)
+#define USB_DMAOR                      0x0060
+#define USB_DMAOR_AE                   (1 << 2)
+#define USB_DMAOR_DME                  (1 << 0)
+
+#define USB_DMASAR                     0x0000
+#define USB_DMADAR                     0x0004
+#define USB_DMATCR                     0x0008
+#define USB_DMATCR_MASK                        0x00ffffff
+#define USB_DMACHCR                    0x0014
+#define USB_DMACHCR_FTE                        (1 << 24)
+#define USB_DMACHCR_NULLE              (1 << 16)
+#define USB_DMACHCR_NULL               (1 << 12)
+#define USB_DMACHCR_TS_8B              ((0 << 7) | (0 << 6))
+#define USB_DMACHCR_TS_16B             ((0 << 7) | (1 << 6))
+#define USB_DMACHCR_TS_32B             ((1 << 7) | (0 << 6))
+#define USB_DMACHCR_IE                 (1 << 5)
+#define USB_DMACHCR_SP                 (1 << 2)
+#define USB_DMACHCR_TE                 (1 << 1)
+#define USB_DMACHCR_DE                 (1 << 0)
+#define USB_DMATEND                    0x0018
+
+/* Hardcode the xfer_shift to 5 (32bytes) */
+#define USB_DMAC_XFER_SHIFT    5
+#define USB_DMAC_XFER_SIZE     (1 << USB_DMAC_XFER_SHIFT)
+#define USB_DMAC_CHCR_TS       USB_DMACHCR_TS_32B
+#define USB_DMAC_SLAVE_BUSWIDTH        DMA_SLAVE_BUSWIDTH_32_BYTES
+
+/* for descriptors */
+#define USB_DMAC_INITIAL_NR_DESC       16
+#define USB_DMAC_INITIAL_NR_SG         8
+
+/* -----------------------------------------------------------------------------
+ * Device access
+ */
+
+static void usb_dmac_write(struct usb_dmac *dmac, u32 reg, u32 data)
+{
+       writel(data, dmac->iomem + reg);
+}
+
+static u32 usb_dmac_read(struct usb_dmac *dmac, u32 reg)
+{
+       return readl(dmac->iomem + reg);
+}
+
+static u32 usb_dmac_chan_read(struct usb_dmac_chan *chan, u32 reg)
+{
+       return readl(chan->iomem + reg);
+}
+
+static void usb_dmac_chan_write(struct usb_dmac_chan *chan, u32 reg, u32 data)
+{
+       writel(data, chan->iomem + reg);
+}
+
+/* -----------------------------------------------------------------------------
+ * Initialization and configuration
+ */
+
+static bool usb_dmac_chan_is_busy(struct usb_dmac_chan *chan)
+{
+       u32 chcr = usb_dmac_chan_read(chan, USB_DMACHCR);
+
+       return (chcr & (USB_DMACHCR_DE | USB_DMACHCR_TE)) == USB_DMACHCR_DE;
+}
+
+static u32 usb_dmac_calc_tend(u32 size)
+{
+       /*
+        * Please refer to the Figure "Example of Final Transaction Valid
+        * Data Transfer Enable (EDTEN) Setting" in the data sheet.
+        */
+       return 0xffffffff << (32 - (size % USB_DMAC_XFER_SIZE ? :
+                                               USB_DMAC_XFER_SIZE));
+}
+
+/* This function is already held by vc.lock */
+static void usb_dmac_chan_start_sg(struct usb_dmac_chan *chan,
+                                  unsigned int index)
+{
+       struct usb_dmac_desc *desc = chan->desc;
+       struct usb_dmac_sg *sg = desc->sg + index;
+       dma_addr_t src_addr = 0, dst_addr = 0;
+
+       WARN_ON_ONCE(usb_dmac_chan_is_busy(chan));
+
+       if (desc->direction == DMA_DEV_TO_MEM)
+               dst_addr = sg->mem_addr;
+       else
+               src_addr = sg->mem_addr;
+
+       dev_dbg(chan->vc.chan.device->dev,
+               "chan%u: queue sg %p: %u@%pad -> %pad\n",
+               chan->index, sg, sg->size, &src_addr, &dst_addr);
+
+       usb_dmac_chan_write(chan, USB_DMASAR, src_addr & 0xffffffff);
+       usb_dmac_chan_write(chan, USB_DMADAR, dst_addr & 0xffffffff);
+       usb_dmac_chan_write(chan, USB_DMATCR,
+                           DIV_ROUND_UP(sg->size, USB_DMAC_XFER_SIZE));
+       usb_dmac_chan_write(chan, USB_DMATEND, usb_dmac_calc_tend(sg->size));
+
+       usb_dmac_chan_write(chan, USB_DMACHCR, USB_DMAC_CHCR_TS |
+                       USB_DMACHCR_NULLE | USB_DMACHCR_IE | USB_DMACHCR_DE);
+}
+
+/* This function is already held by vc.lock */
+static void usb_dmac_chan_start_desc(struct usb_dmac_chan *chan)
+{
+       struct virt_dma_desc *vd;
+
+       vd = vchan_next_desc(&chan->vc);
+       if (!vd) {
+               chan->desc = NULL;
+               return;
+       }
+
+       /*
+        * Remove this request from vc->desc_issued. Otherwise, this driver
+        * will get the previous value from vchan_next_desc() after a transfer
+        * was completed.
+        */
+       list_del(&vd->node);
+
+       chan->desc = to_usb_dmac_desc(vd);
+       chan->desc->sg_index = 0;
+       usb_dmac_chan_start_sg(chan, 0);
+}
+
+static int usb_dmac_init(struct usb_dmac *dmac)
+{
+       u16 dmaor;
+
+       /* Clear all channels and enable the DMAC globally. */
+       usb_dmac_write(dmac, USB_DMAOR, USB_DMAOR_DME);
+
+       dmaor = usb_dmac_read(dmac, USB_DMAOR);
+       if ((dmaor & (USB_DMAOR_AE | USB_DMAOR_DME)) != USB_DMAOR_DME) {
+               dev_warn(dmac->dev, "DMAOR initialization failed.\n");
+               return -EIO;
+       }
+
+       return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Descriptors allocation and free
+ */
+static int usb_dmac_desc_alloc(struct usb_dmac_chan *chan, unsigned int sg_len,
+                              gfp_t gfp)
+{
+       struct usb_dmac_desc *desc;
+       unsigned long flags;
+
+       desc = kzalloc(sizeof(*desc) + sg_len * sizeof(desc->sg[0]), gfp);
+       if (!desc)
+               return -ENOMEM;
+
+       desc->sg_allocated_len = sg_len;
+       INIT_LIST_HEAD(&desc->node);
+
+       spin_lock_irqsave(&chan->vc.lock, flags);
+       list_add_tail(&desc->node, &chan->desc_freed);
+       spin_unlock_irqrestore(&chan->vc.lock, flags);
+
+       return 0;
+}
+
+static void usb_dmac_desc_free(struct usb_dmac_chan *chan)
+{
+       struct usb_dmac_desc *desc;
+       LIST_HEAD(list);
+
+       list_splice_init(&chan->desc_freed, &list);
+       list_splice_init(&chan->desc_got, &list);
+
+       list_for_each_entry(desc, &list, node) {
+               list_del(&desc->node);
+               kfree(desc);
+       }
+       chan->descs_allocated = 0;
+}
+
+static struct usb_dmac_desc *usb_dmac_desc_get(struct usb_dmac_chan *chan,
+                                              unsigned int sg_len, gfp_t gfp)
+{
+       struct usb_dmac_desc *desc = NULL;
+       unsigned long flags;
+
+       /* Get a freed descritpor */
+       spin_lock_irqsave(&chan->vc.lock, flags);
+       list_for_each_entry(desc, &chan->desc_freed, node) {
+               if (sg_len <= desc->sg_allocated_len) {
+                       list_move_tail(&desc->node, &chan->desc_got);
+                       spin_unlock_irqrestore(&chan->vc.lock, flags);
+                       return desc;
+               }
+       }
+       spin_unlock_irqrestore(&chan->vc.lock, flags);
+
+       /* Allocate a new descriptor */
+       if (!usb_dmac_desc_alloc(chan, sg_len, gfp)) {
+               /* If allocated the desc, it was added to tail of the list */
+               spin_lock_irqsave(&chan->vc.lock, flags);
+               desc = list_last_entry(&chan->desc_freed, struct usb_dmac_desc,
+                                      node);
+               list_move_tail(&desc->node, &chan->desc_got);
+               spin_unlock_irqrestore(&chan->vc.lock, flags);
+               return desc;
+       }
+
+       return NULL;
+}
+
+static void usb_dmac_desc_put(struct usb_dmac_chan *chan,
+                             struct usb_dmac_desc *desc)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&chan->vc.lock, flags);
+       list_move_tail(&desc->node, &chan->desc_freed);
+       spin_unlock_irqrestore(&chan->vc.lock, flags);
+}
+
+/* -----------------------------------------------------------------------------
+ * Stop and reset
+ */
+
+static void usb_dmac_soft_reset(struct usb_dmac_chan *uchan)
+{
+       struct dma_chan *chan = &uchan->vc.chan;
+       struct usb_dmac *dmac = to_usb_dmac(chan->device);
+       int i;
+
+       /* Don't issue soft reset if any one of channels is busy */
+       for (i = 0; i < dmac->n_channels; ++i) {
+               if (usb_dmac_chan_is_busy(uchan))
+                       return;
+       }
+
+       usb_dmac_write(dmac, USB_DMAOR, 0);
+       usb_dmac_write(dmac, USB_DMASWR, USB_DMASWR_SWR);
+       udelay(100);
+       usb_dmac_write(dmac, USB_DMASWR, 0);
+       usb_dmac_write(dmac, USB_DMAOR, 1);
+}
+
+static void usb_dmac_chan_halt(struct usb_dmac_chan *chan)
+{
+       u32 chcr = usb_dmac_chan_read(chan, USB_DMACHCR);
+
+       chcr &= ~(USB_DMACHCR_IE | USB_DMACHCR_TE | USB_DMACHCR_DE);
+       usb_dmac_chan_write(chan, USB_DMACHCR, chcr);
+
+       usb_dmac_soft_reset(chan);
+}
+
+static void usb_dmac_stop(struct usb_dmac *dmac)
+{
+       usb_dmac_write(dmac, USB_DMAOR, 0);
+}
+
+/* -----------------------------------------------------------------------------
+ * DMA engine operations
+ */
+
+static int usb_dmac_alloc_chan_resources(struct dma_chan *chan)
+{
+       struct usb_dmac_chan *uchan = to_usb_dmac_chan(chan);
+       int ret;
+
+       while (uchan->descs_allocated < USB_DMAC_INITIAL_NR_DESC) {
+               ret = usb_dmac_desc_alloc(uchan, USB_DMAC_INITIAL_NR_SG,
+                                         GFP_KERNEL);
+               if (ret < 0) {
+                       usb_dmac_desc_free(uchan);
+                       return ret;
+               }
+               uchan->descs_allocated++;
+       }
+
+       return pm_runtime_get_sync(chan->device->dev);
+}
+
+static void usb_dmac_free_chan_resources(struct dma_chan *chan)
+{
+       struct usb_dmac_chan *uchan = to_usb_dmac_chan(chan);
+       unsigned long flags;
+
+       /* Protect against ISR */
+       spin_lock_irqsave(&uchan->vc.lock, flags);
+       usb_dmac_chan_halt(uchan);
+       spin_unlock_irqrestore(&uchan->vc.lock, flags);
+
+       usb_dmac_desc_free(uchan);
+       vchan_free_chan_resources(&uchan->vc);
+
+       pm_runtime_put(chan->device->dev);
+}
+
+static struct dma_async_tx_descriptor *
+usb_dmac_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
+                      unsigned int sg_len, enum dma_transfer_direction dir,
+                      unsigned long dma_flags, void *context)
+{
+       struct usb_dmac_chan *uchan = to_usb_dmac_chan(chan);
+       struct usb_dmac_desc *desc;
+       struct scatterlist *sg;
+       int i;
+
+       if (!sg_len) {
+               dev_warn(chan->device->dev,
+                        "%s: bad parameter: len=%d\n", __func__, sg_len);
+               return NULL;
+       }
+
+       desc = usb_dmac_desc_get(uchan, sg_len, GFP_NOWAIT);
+       if (!desc)
+               return NULL;
+
+       desc->direction = dir;
+       desc->sg_len = sg_len;
+       for_each_sg(sgl, sg, sg_len, i) {
+               desc->sg[i].mem_addr = sg_dma_address(sg);
+               desc->sg[i].size = sg_dma_len(sg);
+       }
+
+       return vchan_tx_prep(&uchan->vc, &desc->vd, dma_flags);
+}
+
+static int usb_dmac_chan_terminate_all(struct dma_chan *chan)
+{
+       struct usb_dmac_chan *uchan = to_usb_dmac_chan(chan);
+       struct usb_dmac_desc *desc;
+       unsigned long flags;
+       LIST_HEAD(head);
+       LIST_HEAD(list);
+
+       spin_lock_irqsave(&uchan->vc.lock, flags);
+       usb_dmac_chan_halt(uchan);
+       vchan_get_all_descriptors(&uchan->vc, &head);
+       if (uchan->desc)
+               uchan->desc = NULL;
+       list_splice_init(&uchan->desc_got, &list);
+       list_for_each_entry(desc, &list, node)
+               list_move_tail(&desc->node, &uchan->desc_freed);
+       spin_unlock_irqrestore(&uchan->vc.lock, flags);
+       vchan_dma_desc_free_list(&uchan->vc, &head);
+
+       return 0;
+}
+
+static unsigned int usb_dmac_get_current_residue(struct usb_dmac_chan *chan,
+                                                struct usb_dmac_desc *desc,
+                                                int sg_index)
+{
+       struct usb_dmac_sg *sg = desc->sg + sg_index;
+       u32 mem_addr = sg->mem_addr & 0xffffffff;
+       unsigned int residue = sg->size;
+
+       /*
+        * We cannot use USB_DMATCR to calculate residue because USB_DMATCR
+        * has unsuited value to calculate.
+        */
+       if (desc->direction == DMA_DEV_TO_MEM)
+               residue -= usb_dmac_chan_read(chan, USB_DMADAR) - mem_addr;
+       else
+               residue -= usb_dmac_chan_read(chan, USB_DMASAR) - mem_addr;
+
+       return residue;
+}
+
+static u32 usb_dmac_chan_get_residue_if_complete(struct usb_dmac_chan *chan,
+                                                dma_cookie_t cookie)
+{
+       struct usb_dmac_desc *desc;
+       u32 residue = 0;
+
+       list_for_each_entry_reverse(desc, &chan->desc_freed, node) {
+               if (desc->done_cookie == cookie) {
+                       residue = desc->residue;
+                       break;
+               }
+       }
+
+       return residue;
+}
+
+static u32 usb_dmac_chan_get_residue(struct usb_dmac_chan *chan,
+                                    dma_cookie_t cookie)
+{
+       u32 residue = 0;
+       struct virt_dma_desc *vd;
+       struct usb_dmac_desc *desc = chan->desc;
+       int i;
+
+       if (!desc) {
+               vd = vchan_find_desc(&chan->vc, cookie);
+               if (!vd)
+                       return 0;
+               desc = to_usb_dmac_desc(vd);
+       }
+
+       /* Compute the size of all usb_dmac_sg still to be transferred */
+       for (i = desc->sg_index + 1; i < desc->sg_len; i++)
+               residue += desc->sg[i].size;
+
+       /* Add the residue for the current sg */
+       residue += usb_dmac_get_current_residue(chan, desc, desc->sg_index);
+
+       return residue;
+}
+
+static enum dma_status usb_dmac_tx_status(struct dma_chan *chan,
+                                         dma_cookie_t cookie,
+                                         struct dma_tx_state *txstate)
+{
+       struct usb_dmac_chan *uchan = to_usb_dmac_chan(chan);
+       enum dma_status status;
+       unsigned int residue = 0;
+       unsigned long flags;
+
+       status = dma_cookie_status(chan, cookie, txstate);
+       /* a client driver will get residue after DMA_COMPLETE */
+       if (!txstate)
+               return status;
+
+       spin_lock_irqsave(&uchan->vc.lock, flags);
+       if (status == DMA_COMPLETE)
+               residue = usb_dmac_chan_get_residue_if_complete(uchan, cookie);
+       else
+               residue = usb_dmac_chan_get_residue(uchan, cookie);
+       spin_unlock_irqrestore(&uchan->vc.lock, flags);
+
+       dma_set_residue(txstate, residue);
+
+       return status;
+}
+
+static void usb_dmac_issue_pending(struct dma_chan *chan)
+{
+       struct usb_dmac_chan *uchan = to_usb_dmac_chan(chan);
+       unsigned long flags;
+
+       spin_lock_irqsave(&uchan->vc.lock, flags);
+       if (vchan_issue_pending(&uchan->vc) && !uchan->desc)
+               usb_dmac_chan_start_desc(uchan);
+       spin_unlock_irqrestore(&uchan->vc.lock, flags);
+}
+
+static void usb_dmac_virt_desc_free(struct virt_dma_desc *vd)
+{
+       struct usb_dmac_desc *desc = to_usb_dmac_desc(vd);
+       struct usb_dmac_chan *chan = to_usb_dmac_chan(vd->tx.chan);
+
+       usb_dmac_desc_put(chan, desc);
+}
+
+/* -----------------------------------------------------------------------------
+ * IRQ handling
+ */
+
+static void usb_dmac_isr_transfer_end(struct usb_dmac_chan *chan)
+{
+       struct usb_dmac_desc *desc = chan->desc;
+
+       BUG_ON(!desc);
+
+       if (++desc->sg_index < desc->sg_len) {
+               usb_dmac_chan_start_sg(chan, desc->sg_index);
+       } else {
+               desc->residue = usb_dmac_get_current_residue(chan, desc,
+                                                       desc->sg_index - 1);
+               desc->done_cookie = desc->vd.tx.cookie;
+               vchan_cookie_complete(&desc->vd);
+
+               /* Restart the next transfer if this driver has a next desc */
+               usb_dmac_chan_start_desc(chan);
+       }
+}
+
+static irqreturn_t usb_dmac_isr_channel(int irq, void *dev)
+{
+       struct usb_dmac_chan *chan = dev;
+       irqreturn_t ret = IRQ_NONE;
+       u32 mask = USB_DMACHCR_TE;
+       u32 check_bits = USB_DMACHCR_TE | USB_DMACHCR_SP;
+       u32 chcr;
+
+       spin_lock(&chan->vc.lock);
+
+       chcr = usb_dmac_chan_read(chan, USB_DMACHCR);
+       if (chcr & check_bits)
+               mask |= USB_DMACHCR_DE | check_bits;
+       if (chcr & USB_DMACHCR_NULL) {
+               /* An interruption of TE will happen after we set FTE */
+               mask |= USB_DMACHCR_NULL;
+               chcr |= USB_DMACHCR_FTE;
+               ret |= IRQ_HANDLED;
+       }
+       usb_dmac_chan_write(chan, USB_DMACHCR, chcr & ~mask);
+
+       if (chcr & check_bits) {
+               usb_dmac_isr_transfer_end(chan);
+               ret |= IRQ_HANDLED;
+       }
+
+       spin_unlock(&chan->vc.lock);
+
+       return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * OF xlate and channel filter
+ */
+
+static bool usb_dmac_chan_filter(struct dma_chan *chan, void *arg)
+{
+       struct usb_dmac_chan *uchan = to_usb_dmac_chan(chan);
+       struct of_phandle_args *dma_spec = arg;
+
+       if (dma_spec->np != chan->device->dev->of_node)
+               return false;
+
+       /* USB-DMAC should be used with fixed usb controller's FIFO */
+       if (uchan->index != dma_spec->args[0])
+               return false;
+
+       return true;
+}
+
+static struct dma_chan *usb_dmac_of_xlate(struct of_phandle_args *dma_spec,
+                                         struct of_dma *ofdma)
+{
+       struct usb_dmac_chan *uchan;
+       struct dma_chan *chan;
+       dma_cap_mask_t mask;
+
+       if (dma_spec->args_count != 1)
+               return NULL;
+
+       /* Only slave DMA channels can be allocated via DT */
+       dma_cap_zero(mask);
+       dma_cap_set(DMA_SLAVE, mask);
+
+       chan = dma_request_channel(mask, usb_dmac_chan_filter, dma_spec);
+       if (!chan)
+               return NULL;
+
+       uchan = to_usb_dmac_chan(chan);
+
+       return chan;
+}
+
+/* -----------------------------------------------------------------------------
+ * Power management
+ */
+
+static int usb_dmac_runtime_suspend(struct device *dev)
+{
+       struct usb_dmac *dmac = dev_get_drvdata(dev);
+       int i;
+
+       for (i = 0; i < dmac->n_channels; ++i)
+               usb_dmac_chan_halt(&dmac->channels[i]);
+
+       return 0;
+}
+
+static int usb_dmac_runtime_resume(struct device *dev)
+{
+       struct usb_dmac *dmac = dev_get_drvdata(dev);
+
+       return usb_dmac_init(dmac);
+}
+
+static const struct dev_pm_ops usb_dmac_pm = {
+       SET_RUNTIME_PM_OPS(usb_dmac_runtime_suspend, usb_dmac_runtime_resume,
+                          NULL)
+};
+
+/* -----------------------------------------------------------------------------
+ * Probe and remove
+ */
+
+static int usb_dmac_chan_probe(struct usb_dmac *dmac,
+                              struct usb_dmac_chan *uchan,
+                              unsigned int index)
+{
+       struct platform_device *pdev = to_platform_device(dmac->dev);
+       char pdev_irqname[5];
+       char *irqname;
+       int ret;
+
+       uchan->index = index;
+       uchan->iomem = dmac->iomem + USB_DMAC_CHAN_OFFSET(index);
+
+       /* Request the channel interrupt. */
+       sprintf(pdev_irqname, "ch%u", index);
+       uchan->irq = platform_get_irq_byname(pdev, pdev_irqname);
+       if (uchan->irq < 0) {
+               dev_err(dmac->dev, "no IRQ specified for channel %u\n", index);
+               return -ENODEV;
+       }
+
+       irqname = devm_kasprintf(dmac->dev, GFP_KERNEL, "%s:%u",
+                                dev_name(dmac->dev), index);
+       if (!irqname)
+               return -ENOMEM;
+
+       ret = devm_request_irq(dmac->dev, uchan->irq, usb_dmac_isr_channel,
+                              IRQF_SHARED, irqname, uchan);
+       if (ret) {
+               dev_err(dmac->dev, "failed to request IRQ %u (%d)\n",
+                       uchan->irq, ret);
+               return ret;
+       }
+
+       uchan->vc.desc_free = usb_dmac_virt_desc_free;
+       vchan_init(&uchan->vc, &dmac->engine);
+       INIT_LIST_HEAD(&uchan->desc_freed);
+       INIT_LIST_HEAD(&uchan->desc_got);
+
+       return 0;
+}
+
+static int usb_dmac_parse_of(struct device *dev, struct usb_dmac *dmac)
+{
+       struct device_node *np = dev->of_node;
+       int ret;
+
+       ret = of_property_read_u32(np, "dma-channels", &dmac->n_channels);
+       if (ret < 0) {
+               dev_err(dev, "unable to read dma-channels property\n");
+               return ret;
+       }
+
+       if (dmac->n_channels <= 0 || dmac->n_channels >= 100) {
+               dev_err(dev, "invalid number of channels %u\n",
+                       dmac->n_channels);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int usb_dmac_probe(struct platform_device *pdev)
+{
+       const enum dma_slave_buswidth widths = USB_DMAC_SLAVE_BUSWIDTH;
+       struct dma_device *engine;
+       struct usb_dmac *dmac;
+       struct resource *mem;
+       unsigned int i;
+       int ret;
+
+       dmac = devm_kzalloc(&pdev->dev, sizeof(*dmac), GFP_KERNEL);
+       if (!dmac)
+               return -ENOMEM;
+
+       dmac->dev = &pdev->dev;
+       platform_set_drvdata(pdev, dmac);
+
+       ret = usb_dmac_parse_of(&pdev->dev, dmac);
+       if (ret < 0)
+               return ret;
+
+       dmac->channels = devm_kcalloc(&pdev->dev, dmac->n_channels,
+                                     sizeof(*dmac->channels), GFP_KERNEL);
+       if (!dmac->channels)
+               return -ENOMEM;
+
+       /* Request resources. */
+       mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       dmac->iomem = devm_ioremap_resource(&pdev->dev, mem);
+       if (IS_ERR(dmac->iomem))
+               return PTR_ERR(dmac->iomem);
+
+       /* Enable runtime PM and initialize the device. */
+       pm_runtime_enable(&pdev->dev);
+       ret = pm_runtime_get_sync(&pdev->dev);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "runtime PM get sync failed (%d)\n", ret);
+               return ret;
+       }
+
+       ret = usb_dmac_init(dmac);
+       pm_runtime_put(&pdev->dev);
+
+       if (ret) {
+               dev_err(&pdev->dev, "failed to reset device\n");
+               goto error;
+       }
+
+       /* Initialize the channels. */
+       INIT_LIST_HEAD(&dmac->engine.channels);
+
+       for (i = 0; i < dmac->n_channels; ++i) {
+               ret = usb_dmac_chan_probe(dmac, &dmac->channels[i], i);
+               if (ret < 0)
+                       goto error;
+       }
+
+       /* Register the DMAC as a DMA provider for DT. */
+       ret = of_dma_controller_register(pdev->dev.of_node, usb_dmac_of_xlate,
+                                        NULL);
+       if (ret < 0)
+               goto error;
+
+       /*
+        * Register the DMA engine device.
+        *
+        * Default transfer size of 32 bytes requires 32-byte alignment.
+        */
+       engine = &dmac->engine;
+       dma_cap_set(DMA_SLAVE, engine->cap_mask);
+
+       engine->dev = &pdev->dev;
+
+       engine->src_addr_widths = widths;
+       engine->dst_addr_widths = widths;
+       engine->directions = BIT(DMA_MEM_TO_DEV) | BIT(DMA_DEV_TO_MEM);
+       engine->residue_granularity = DMA_RESIDUE_GRANULARITY_BURST;
+
+       engine->device_alloc_chan_resources = usb_dmac_alloc_chan_resources;
+       engine->device_free_chan_resources = usb_dmac_free_chan_resources;
+       engine->device_prep_slave_sg = usb_dmac_prep_slave_sg;
+       engine->device_terminate_all = usb_dmac_chan_terminate_all;
+       engine->device_tx_status = usb_dmac_tx_status;
+       engine->device_issue_pending = usb_dmac_issue_pending;
+
+       ret = dma_async_device_register(engine);
+       if (ret < 0)
+               goto error;
+
+       return 0;
+
+error:
+       of_dma_controller_free(pdev->dev.of_node);
+       pm_runtime_disable(&pdev->dev);
+       return ret;
+}
+
+static void usb_dmac_chan_remove(struct usb_dmac *dmac,
+                                struct usb_dmac_chan *uchan)
+{
+       usb_dmac_chan_halt(uchan);
+       devm_free_irq(dmac->dev, uchan->irq, uchan);
+}
+
+static int usb_dmac_remove(struct platform_device *pdev)
+{
+       struct usb_dmac *dmac = platform_get_drvdata(pdev);
+       int i;
+
+       for (i = 0; i < dmac->n_channels; ++i)
+               usb_dmac_chan_remove(dmac, &dmac->channels[i]);
+       of_dma_controller_free(pdev->dev.of_node);
+       dma_async_device_unregister(&dmac->engine);
+
+       pm_runtime_disable(&pdev->dev);
+
+       return 0;
+}
+
+static void usb_dmac_shutdown(struct platform_device *pdev)
+{
+       struct usb_dmac *dmac = platform_get_drvdata(pdev);
+
+       usb_dmac_stop(dmac);
+}
+
+static const struct of_device_id usb_dmac_of_ids[] = {
+       { .compatible = "renesas,usb-dmac", },
+       { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, usb_dmac_of_ids);
+
+static struct platform_driver usb_dmac_driver = {
+       .driver         = {
+               .pm     = &usb_dmac_pm,
+               .name   = "usb-dmac",
+               .of_match_table = usb_dmac_of_ids,
+       },
+       .probe          = usb_dmac_probe,
+       .remove         = usb_dmac_remove,
+       .shutdown       = usb_dmac_shutdown,
+};
+
+module_platform_driver(usb_dmac_driver);
+
+MODULE_DESCRIPTION("Renesas USB DMA Controller Driver");
+MODULE_AUTHOR("Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>");
+MODULE_LICENSE("GPL v2");