diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2015-04-24 09:49:37 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2015-04-24 09:49:37 -0700 |
commit | d6a4c0e5d3d433ef296f8f417e835329a834a256 (patch) | |
tree | a65de394ea1afeeccee9f84dc7e6cd21e2660639 /drivers/dma/sh | |
parent | 474095e46cd14421821da3201a9fd6a4c070996b (diff) | |
parent | cdde0e61cf2d41123604c6c9d8f09ef5c23464ee (diff) |
Merge branch 'for-linus' of git://git.infradead.org/users/vkoul/slave-dma
Pull slave-dmaengine updates from Vinod Koul:
- new drivers for:
- Ingenic JZ4780 controller
- APM X-Gene controller
- Freescale RaidEngine device
- Renesas USB Controller
- remove device_alloc_chan_resources dummy handlers
- sh driver cleanups for peri peri and related emmc and asoc patches
as well
- fixes and enhancements spread over the drivers
* 'for-linus' of git://git.infradead.org/users/vkoul/slave-dma: (59 commits)
dmaengine: dw: don't prompt for DW_DMAC_CORE
dmaengine: shdmac: avoid unused variable warnings
dmaengine: fix platform_no_drv_owner.cocci warnings
dmaengine: pch_dma: fix memory leak on failure path in pch_dma_probe()
dmaengine: at_xdmac: unlock spin lock before return
dmaengine: xgene: devm_ioremap() returns NULL on error
dmaengine: xgene: buffer overflow in xgene_dma_init_channels()
dmaengine: usb-dmac: Fix dereferencing freed memory 'desc'
dmaengine: sa11x0: report slave capabilities to upper layers
dmaengine: vdma: Fix compilation warnings
dmaengine: fsl_raid: statify fsl_re_chan_probe
dmaengine: Driver support for FSL RaidEngine device.
dmaengine: xgene_dma_init_ring_mngr() can be static
Documentation: dma: Add documentation for the APM X-Gene SoC DMA device DTS binding
arm64: dts: Add APM X-Gene SoC DMA device and DMA clock DTS nodes
dmaengine: Add support for APM X-Gene SoC DMA engine driver
dmaengine: usb-dmac: Add Renesas USB DMA Controller (USB-DMAC) driver
dmaengine: renesas,usb-dmac: Add device tree bindings documentation
dmaengine: edma: fixed wrongly initialized data parameter to the edma callback
dmaengine: ste_dma40: fix implicit conversion
...
Diffstat (limited to 'drivers/dma/sh')
-rw-r--r-- | drivers/dma/sh/Kconfig | 15 | ||||
-rw-r--r-- | drivers/dma/sh/Makefile | 2 | ||||
-rw-r--r-- | drivers/dma/sh/rcar-audmapp.c | 376 | ||||
-rw-r--r-- | drivers/dma/sh/shdma-base.c | 73 | ||||
-rw-r--r-- | drivers/dma/sh/shdmac.c | 4 | ||||
-rw-r--r-- | drivers/dma/sh/usb-dmac.c | 910 |
6 files changed, 975 insertions, 405 deletions
diff --git a/drivers/dma/sh/Kconfig b/drivers/dma/sh/Kconfig index 8190ad225a1b..0f371524a4d9 100644 --- a/drivers/dma/sh/Kconfig +++ b/drivers/dma/sh/Kconfig @@ -51,12 +51,6 @@ config RCAR_HPB_DMAE help Enable support for the Renesas R-Car series DMA controllers. -config RCAR_AUDMAC_PP - tristate "Renesas R-Car Audio DMAC Peripheral Peripheral support" - depends on SH_DMAE_BASE - help - Enable support for the Renesas R-Car Audio DMAC Peripheral Peripheral controllers. - config RCAR_DMAC tristate "Renesas R-Car Gen2 DMA Controller" depends on ARCH_SHMOBILE || COMPILE_TEST @@ -64,3 +58,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. diff --git a/drivers/dma/sh/Makefile b/drivers/dma/sh/Makefile index 2852f9db61a4..b8a598066ce2 100644 --- a/drivers/dma/sh/Makefile +++ b/drivers/dma/sh/Makefile @@ -15,5 +15,5 @@ obj-$(CONFIG_SH_DMAE) += shdma.o 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/rcar-audmapp.c b/drivers/dma/sh/rcar-audmapp.c deleted file mode 100644 index d95bbdd721f4..000000000000 --- a/drivers/dma/sh/rcar-audmapp.c +++ /dev/null @@ -1,376 +0,0 @@ -/* - * This is for Renesas R-Car Audio-DMAC-peri-peri. - * - * Copyright (C) 2014 Renesas Electronics Corporation - * Copyright (C) 2014 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> - * - * based on the drivers/dma/sh/shdma.c - * - * Copyright (C) 2011-2012 Guennadi Liakhovetski <g.liakhovetski@gmx.de> - * Copyright (C) 2009 Nobuhiro Iwamatsu <iwamatsu.nobuhiro@renesas.com> - * Copyright (C) 2009 Renesas Solutions, Inc. All rights reserved. - * Copyright (C) 2007 Freescale Semiconductor, Inc. All rights reserved. - * - * This 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. - * - */ -#include <linux/delay.h> -#include <linux/init.h> -#include <linux/module.h> -#include <linux/slab.h> -#include <linux/dmaengine.h> -#include <linux/of_dma.h> -#include <linux/platform_data/dma-rcar-audmapp.h> -#include <linux/platform_device.h> -#include <linux/shdma-base.h> - -/* - * DMA register - */ -#define PDMASAR 0x00 -#define PDMADAR 0x04 -#define PDMACHCR 0x0c - -/* PDMACHCR */ -#define PDMACHCR_DE (1 << 0) - -#define AUDMAPP_MAX_CHANNELS 29 - -/* Default MEMCPY transfer size = 2^2 = 4 bytes */ -#define LOG2_DEFAULT_XFER_SIZE 2 -#define AUDMAPP_SLAVE_NUMBER 256 -#define AUDMAPP_LEN_MAX (16 * 1024 * 1024) - -struct audmapp_chan { - struct shdma_chan shdma_chan; - void __iomem *base; - dma_addr_t slave_addr; - u32 chcr; -}; - -struct audmapp_device { - struct shdma_dev shdma_dev; - struct audmapp_pdata *pdata; - struct device *dev; - void __iomem *chan_reg; -}; - -struct audmapp_desc { - struct shdma_desc shdma_desc; - dma_addr_t src; - dma_addr_t dst; -}; - -#define to_shdma_chan(c) container_of(c, struct shdma_chan, dma_chan) - -#define to_chan(chan) container_of(chan, struct audmapp_chan, shdma_chan) -#define to_desc(sdesc) container_of(sdesc, struct audmapp_desc, shdma_desc) -#define to_dev(chan) container_of(chan->shdma_chan.dma_chan.device, \ - struct audmapp_device, shdma_dev.dma_dev) - -static void audmapp_write(struct audmapp_chan *auchan, u32 data, u32 reg) -{ - struct audmapp_device *audev = to_dev(auchan); - struct device *dev = audev->dev; - - dev_dbg(dev, "w %p : %08x\n", auchan->base + reg, data); - - iowrite32(data, auchan->base + reg); -} - -static u32 audmapp_read(struct audmapp_chan *auchan, u32 reg) -{ - return ioread32(auchan->base + reg); -} - -static void audmapp_halt(struct shdma_chan *schan) -{ - struct audmapp_chan *auchan = to_chan(schan); - int i; - - audmapp_write(auchan, 0, PDMACHCR); - - for (i = 0; i < 1024; i++) { - if (0 == audmapp_read(auchan, PDMACHCR)) - return; - udelay(1); - } -} - -static void audmapp_start_xfer(struct shdma_chan *schan, - struct shdma_desc *sdesc) -{ - struct audmapp_chan *auchan = to_chan(schan); - struct audmapp_device *audev = to_dev(auchan); - struct audmapp_desc *desc = to_desc(sdesc); - struct device *dev = audev->dev; - u32 chcr = auchan->chcr | PDMACHCR_DE; - - dev_dbg(dev, "src/dst/chcr = %pad/%pad/%08x\n", - &desc->src, &desc->dst, chcr); - - audmapp_write(auchan, desc->src, PDMASAR); - audmapp_write(auchan, desc->dst, PDMADAR); - audmapp_write(auchan, chcr, PDMACHCR); -} - -static int audmapp_get_config(struct audmapp_chan *auchan, int slave_id, - u32 *chcr, dma_addr_t *dst) -{ - struct audmapp_device *audev = to_dev(auchan); - struct audmapp_pdata *pdata = audev->pdata; - struct audmapp_slave_config *cfg; - int i; - - *chcr = 0; - *dst = 0; - - if (!pdata) { /* DT */ - *chcr = ((u32)slave_id) << 16; - auchan->shdma_chan.slave_id = (slave_id) >> 8; - return 0; - } - - /* non-DT */ - - if (slave_id >= AUDMAPP_SLAVE_NUMBER) - return -ENXIO; - - for (i = 0, cfg = pdata->slave; i < pdata->slave_num; i++, cfg++) - if (cfg->slave_id == slave_id) { - *chcr = cfg->chcr; - *dst = cfg->dst; - return 0; - } - - return -ENXIO; -} - -static int audmapp_set_slave(struct shdma_chan *schan, int slave_id, - dma_addr_t slave_addr, bool try) -{ - struct audmapp_chan *auchan = to_chan(schan); - u32 chcr; - dma_addr_t dst; - int ret; - - ret = audmapp_get_config(auchan, slave_id, &chcr, &dst); - if (ret < 0) - return ret; - - if (try) - return 0; - - auchan->chcr = chcr; - auchan->slave_addr = slave_addr ? : dst; - - return 0; -} - -static int audmapp_desc_setup(struct shdma_chan *schan, - struct shdma_desc *sdesc, - dma_addr_t src, dma_addr_t dst, size_t *len) -{ - struct audmapp_desc *desc = to_desc(sdesc); - - if (*len > (size_t)AUDMAPP_LEN_MAX) - *len = (size_t)AUDMAPP_LEN_MAX; - - desc->src = src; - desc->dst = dst; - - return 0; -} - -static void audmapp_setup_xfer(struct shdma_chan *schan, - int slave_id) -{ -} - -static dma_addr_t audmapp_slave_addr(struct shdma_chan *schan) -{ - struct audmapp_chan *auchan = to_chan(schan); - - return auchan->slave_addr; -} - -static bool audmapp_channel_busy(struct shdma_chan *schan) -{ - struct audmapp_chan *auchan = to_chan(schan); - u32 chcr = audmapp_read(auchan, PDMACHCR); - - return chcr & ~PDMACHCR_DE; -} - -static bool audmapp_desc_completed(struct shdma_chan *schan, - struct shdma_desc *sdesc) -{ - return true; -} - -static struct shdma_desc *audmapp_embedded_desc(void *buf, int i) -{ - return &((struct audmapp_desc *)buf)[i].shdma_desc; -} - -static const struct shdma_ops audmapp_shdma_ops = { - .halt_channel = audmapp_halt, - .desc_setup = audmapp_desc_setup, - .set_slave = audmapp_set_slave, - .start_xfer = audmapp_start_xfer, - .embedded_desc = audmapp_embedded_desc, - .setup_xfer = audmapp_setup_xfer, - .slave_addr = audmapp_slave_addr, - .channel_busy = audmapp_channel_busy, - .desc_completed = audmapp_desc_completed, -}; - -static int audmapp_chan_probe(struct platform_device *pdev, - struct audmapp_device *audev, int id) -{ - struct shdma_dev *sdev = &audev->shdma_dev; - struct audmapp_chan *auchan; - struct shdma_chan *schan; - struct device *dev = audev->dev; - - auchan = devm_kzalloc(dev, sizeof(*auchan), GFP_KERNEL); - if (!auchan) - return -ENOMEM; - - schan = &auchan->shdma_chan; - schan->max_xfer_len = AUDMAPP_LEN_MAX; - - shdma_chan_probe(sdev, schan, id); - - auchan->base = audev->chan_reg + 0x20 + (0x10 * id); - dev_dbg(dev, "%02d : %p / %p", id, auchan->base, audev->chan_reg); - - return 0; -} - -static void audmapp_chan_remove(struct audmapp_device *audev) -{ - struct shdma_chan *schan; - int i; - - shdma_for_each_chan(schan, &audev->shdma_dev, i) { - BUG_ON(!schan); - shdma_chan_remove(schan); - } -} - -static struct dma_chan *audmapp_of_xlate(struct of_phandle_args *dma_spec, - struct of_dma *ofdma) -{ - dma_cap_mask_t mask; - struct dma_chan *chan; - u32 chcr = dma_spec->args[0]; - - if (dma_spec->args_count != 1) - return NULL; - - dma_cap_zero(mask); - dma_cap_set(DMA_SLAVE, mask); - - chan = dma_request_channel(mask, shdma_chan_filter, NULL); - if (chan) - to_shdma_chan(chan)->hw_req = chcr; - - return chan; -} - -static int audmapp_probe(struct platform_device *pdev) -{ - struct audmapp_pdata *pdata = pdev->dev.platform_data; - struct device_node *np = pdev->dev.of_node; - struct audmapp_device *audev; - struct shdma_dev *sdev; - struct dma_device *dma_dev; - struct resource *res; - int err, i; - - if (np) - of_dma_controller_register(np, audmapp_of_xlate, pdev); - else if (!pdata) - return -ENODEV; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - - audev = devm_kzalloc(&pdev->dev, sizeof(*audev), GFP_KERNEL); - if (!audev) - return -ENOMEM; - - audev->dev = &pdev->dev; - audev->pdata = pdata; - audev->chan_reg = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(audev->chan_reg)) - return PTR_ERR(audev->chan_reg); - - sdev = &audev->shdma_dev; - sdev->ops = &audmapp_shdma_ops; - sdev->desc_size = sizeof(struct audmapp_desc); - - dma_dev = &sdev->dma_dev; - dma_dev->copy_align = LOG2_DEFAULT_XFER_SIZE; - dma_cap_set(DMA_SLAVE, dma_dev->cap_mask); - - err = shdma_init(&pdev->dev, sdev, AUDMAPP_MAX_CHANNELS); - if (err < 0) - return err; - - platform_set_drvdata(pdev, audev); - - /* Create DMA Channel */ - for (i = 0; i < AUDMAPP_MAX_CHANNELS; i++) { - err = audmapp_chan_probe(pdev, audev, i); - if (err) - goto chan_probe_err; - } - - err = dma_async_device_register(dma_dev); - if (err < 0) - goto chan_probe_err; - - return err; - -chan_probe_err: - audmapp_chan_remove(audev); - shdma_cleanup(sdev); - - return err; -} - -static int audmapp_remove(struct platform_device *pdev) -{ - struct audmapp_device *audev = platform_get_drvdata(pdev); - struct dma_device *dma_dev = &audev->shdma_dev.dma_dev; - - dma_async_device_unregister(dma_dev); - - audmapp_chan_remove(audev); - shdma_cleanup(&audev->shdma_dev); - - return 0; -} - -static const struct of_device_id audmapp_of_match[] = { - { .compatible = "renesas,rcar-audmapp", }, - {}, -}; - -static struct platform_driver audmapp_driver = { - .probe = audmapp_probe, - .remove = audmapp_remove, - .driver = { - .name = "rcar-audmapp-engine", - .of_match_table = audmapp_of_match, - }, -}; -module_platform_driver(audmapp_driver); - -MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); -MODULE_DESCRIPTION("Renesas R-Car Audio DMAC peri-peri driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/dma/sh/shdma-base.c b/drivers/dma/sh/shdma-base.c index 8ee383d339a5..10fcabad80f3 100644 --- a/drivers/dma/sh/shdma-base.c +++ b/drivers/dma/sh/shdma-base.c @@ -171,8 +171,7 @@ static struct shdma_desc *shdma_get_desc(struct shdma_chan *schan) return NULL; } -static int shdma_setup_slave(struct shdma_chan *schan, int slave_id, - dma_addr_t slave_addr) +static int shdma_setup_slave(struct shdma_chan *schan, dma_addr_t slave_addr) { struct shdma_dev *sdev = to_shdma_dev(schan->dma_chan.device); const struct shdma_ops *ops = sdev->ops; @@ -183,25 +182,23 @@ static int shdma_setup_slave(struct shdma_chan *schan, int slave_id, ret = ops->set_slave(schan, match, slave_addr, true); if (ret < 0) return ret; - - slave_id = schan->slave_id; } else { - match = slave_id; + match = schan->real_slave_id; } - if (slave_id < 0 || slave_id >= slave_num) + if (schan->real_slave_id < 0 || schan->real_slave_id >= slave_num) return -EINVAL; - if (test_and_set_bit(slave_id, shdma_slave_used)) + if (test_and_set_bit(schan->real_slave_id, shdma_slave_used)) return -EBUSY; ret = ops->set_slave(schan, match, slave_addr, false); if (ret < 0) { - clear_bit(slave_id, shdma_slave_used); + clear_bit(schan->real_slave_id, shdma_slave_used); return ret; } - schan->slave_id = slave_id; + schan->slave_id = schan->real_slave_id; return 0; } @@ -221,10 +218,12 @@ static int shdma_alloc_chan_resources(struct dma_chan *chan) */ if (slave) { /* Legacy mode: .private is set in filter */ - ret = shdma_setup_slave(schan, slave->slave_id, 0); + schan->real_slave_id = slave->slave_id; + ret = shdma_setup_slave(schan, 0); if (ret < 0) goto esetslave; } else { + /* Normal mode: real_slave_id was set by filter */ schan->slave_id = -EINVAL; } @@ -258,11 +257,14 @@ esetslave: /* * This is the standard shdma filter function to be used as a replacement to the - * "old" method, using the .private pointer. If for some reason you allocate a - * channel without slave data, use something like ERR_PTR(-EINVAL) as a filter + * "old" method, using the .private pointer. + * You always have to pass a valid slave id as the argument, old drivers that + * pass ERR_PTR(-EINVAL) as a filter parameter and set it up in dma_slave_config + * need to be updated so we can remove the slave_id field from dma_slave_config. * parameter. If this filter is used, the slave driver, after calling * dma_request_channel(), will also have to call dmaengine_slave_config() with - * .slave_id, .direction, and either .src_addr or .dst_addr set. + * .direction, and either .src_addr or .dst_addr set. + * * NOTE: this filter doesn't support multiple DMAC drivers with the DMA_SLAVE * capability! If this becomes a requirement, hardware glue drivers, using this * services would have to provide their own filters, which first would check @@ -276,7 +278,7 @@ bool shdma_chan_filter(struct dma_chan *chan, void *arg) { struct shdma_chan *schan; struct shdma_dev *sdev; - int match = (long)arg; + int slave_id = (long)arg; int ret; /* Only support channels handled by this driver. */ @@ -284,19 +286,39 @@ bool shdma_chan_filter(struct dma_chan *chan, void *arg) shdma_alloc_chan_resources) return false; - if (match < 0) + schan = to_shdma_chan(chan); + sdev = to_shdma_dev(chan->device); + + /* + * For DT, the schan->slave_id field is generated by the + * set_slave function from the slave ID that is passed in + * from xlate. For the non-DT case, the slave ID is + * directly passed into the filter function by the driver + */ + if (schan->dev->of_node) { + ret = sdev->ops->set_slave(schan, slave_id, 0, true); + if (ret < 0) + return false; + + schan->real_slave_id = schan->slave_id; + return true; + } + + if (slave_id < 0) { /* No slave requested - arbitrary channel */ + dev_warn(sdev->dma_dev.dev, "invalid slave ID passed to dma_request_slave\n"); return true; + } - schan = to_shdma_chan(chan); - if (!schan->dev->of_node && match >= slave_num) + if (slave_id >= slave_num) return false; - sdev = to_shdma_dev(schan->dma_chan.device); - ret = sdev->ops->set_slave(schan, match, 0, true); + ret = sdev->ops->set_slave(schan, slave_id, 0, true); if (ret < 0) return false; + schan->real_slave_id = slave_id; + return true; } EXPORT_SYMBOL(shdma_chan_filter); @@ -452,6 +474,8 @@ static void shdma_free_chan_resources(struct dma_chan *chan) chan->private = NULL; } + schan->real_slave_id = 0; + spin_lock_irq(&schan->chan_lock); list_splice_init(&schan->ld_free, &list); @@ -764,11 +788,20 @@ static int shdma_config(struct dma_chan *chan, */ if (!config) return -EINVAL; + + /* + * overriding the slave_id through dma_slave_config is deprecated, + * but possibly some out-of-tree drivers still do it. + */ + if (WARN_ON_ONCE(config->slave_id && + config->slave_id != schan->real_slave_id)) + schan->real_slave_id = config->slave_id; + /* * We could lock this, but you shouldn't be configuring the * channel, while using it... */ - return shdma_setup_slave(schan, config->slave_id, + return shdma_setup_slave(schan, config->direction == DMA_DEV_TO_MEM ? config->src_addr : config->dst_addr); } diff --git a/drivers/dma/sh/shdmac.c b/drivers/dma/sh/shdmac.c index 9f1d4c7dbab8..11707df1a689 100644 --- a/drivers/dma/sh/shdmac.c +++ b/drivers/dma/sh/shdmac.c @@ -443,7 +443,7 @@ static bool sh_dmae_reset(struct sh_dmae_device *shdev) return ret; } -#if defined(CONFIG_CPU_SH4) || defined(CONFIG_ARM) +#if defined(CONFIG_CPU_SH4) || defined(CONFIG_ARCH_SHMOBILE) static irqreturn_t sh_dmae_err(int irq, void *data) { struct sh_dmae_device *shdev = data; @@ -689,7 +689,7 @@ static int sh_dmae_probe(struct platform_device *pdev) const struct sh_dmae_pdata *pdata; unsigned long chan_flag[SH_DMAE_MAX_CHANNELS] = {}; int chan_irq[SH_DMAE_MAX_CHANNELS]; -#if defined(CONFIG_CPU_SH4) || defined(CONFIG_ARM) +#if defined(CONFIG_CPU_SH4) || defined(CONFIG_ARCH_SHMOBILE) unsigned long irqflags = 0; int errirq; #endif diff --git a/drivers/dma/sh/usb-dmac.c b/drivers/dma/sh/usb-dmac.c new file mode 100644 index 000000000000..f705798ce3eb --- /dev/null +++ b/drivers/dma/sh/usb-dmac.c @@ -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, *_desc; + LIST_HEAD(list); + + list_splice_init(&chan->desc_freed, &list); + list_splice_init(&chan->desc_got, &list); + + list_for_each_entry_safe(desc, _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; + L |