From: Mans Rullgard <mans@xxxxxxxxx> https://github.com/mansr/linux-tangox/blob/master/drivers/dma/tangox-dma.c --- drivers/dma/Kconfig | 6 + drivers/dma/Makefile | 1 + drivers/dma/tango-dma.c | 583 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 590 insertions(+) create mode 100644 drivers/dma/tango-dma.c diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index af63a6bcf564..1382cf31b68e 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -466,6 +466,12 @@ config TXX9_DMAC Support the TXx9 SoC internal DMA controller. This can be integrated in chips such as the Toshiba TX4927/38/39. +config TANGO_DMA + tristate "Sigma Designs SMP86xx DMA support" + depends on ARCH_TANGO + select DMA_ENGINE + select DMA_VIRTUAL_CHANNELS + config TEGRA20_APB_DMA bool "NVIDIA Tegra20 APB DMA support" depends on ARCH_TEGRA diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index e4dc9cac7ee8..5f14c5b71a72 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_STE_DMA40) += ste_dma40.o ste_dma40_ll.o obj-$(CONFIG_STM32_DMA) += stm32-dma.o obj-$(CONFIG_S3C24XX_DMAC) += s3c24xx-dma.o obj-$(CONFIG_TXX9_DMAC) += txx9dmac.o +obj-$(CONFIG_TANGO_DMA) += tango-dma.o obj-$(CONFIG_TEGRA20_APB_DMA) += tegra20-apb-dma.o obj-$(CONFIG_TEGRA210_ADMA) += tegra210-adma.o obj-$(CONFIG_TIMB_DMA) += timb_dma.o diff --git a/drivers/dma/tango-dma.c b/drivers/dma/tango-dma.c new file mode 100644 index 000000000000..53f6c7f61599 --- /dev/null +++ b/drivers/dma/tango-dma.c @@ -0,0 +1,583 @@ +/* + * Copyright (C) 2014 Mans Rullgard <mans@xxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/dmaengine.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of_address.h> +#include <linux/of_dma.h> +#include <linux/of_irq.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/irq.h> +#include <linux/delay.h> + +#include "virt-dma.h" + +#define TANGOX_DMA_MAX_LEN 0x1fff + +#define TANGOX_DMA_MAX_CHANS 6 +#define TANGOX_DMA_MAX_PCHANS 6 + +#define DMA_ADDR 0 +#define DMA_COUNT 4 +#define DMA_ADDR2 8 +#define DMA_STRIDE DMA_ADDR2 +#define DMA_CMD 12 + +#define DMA_MODE_SINGLE 1 +#define DMA_MODE_DOUBLE 2 +#define DMA_MODE_RECT 3 + +struct tangox_dma_sg { + dma_addr_t addr; + unsigned int len; +}; + +struct tangox_dma_desc { + struct virt_dma_desc vd; + enum dma_transfer_direction direction; + unsigned int num_sgs; + struct tangox_dma_sg sg[]; +}; + +struct tangox_dma_chan { + struct virt_dma_chan vc; + u32 id; +}; + +struct tangox_dma_pchan { + struct tangox_dma_device *dev; + enum dma_transfer_direction direction; + u32 sbox_id; + int slave_id; + void __iomem *base; + spinlock_t lock; + struct tangox_dma_desc *desc; + unsigned int next_sg; + unsigned long issued_len; +}; + +struct tangox_dma_device { + struct dma_device ddev; + void __iomem *sbox_base; + spinlock_t lock; + struct list_head desc_memtodev; + struct list_head desc_devtomem; + int nr_pchans; + struct tangox_dma_pchan pchan[TANGOX_DMA_MAX_PCHANS]; + struct tangox_dma_chan chan[TANGOX_DMA_MAX_CHANS]; +}; + +static inline struct tangox_dma_device *to_tangox_dma_device( + struct dma_device *ddev) +{ + return container_of(ddev, struct tangox_dma_device, ddev); +} + +static inline struct tangox_dma_chan *to_tangox_dma_chan(struct dma_chan *c) +{ + return container_of(c, struct tangox_dma_chan, vc.chan); +} + +static inline struct tangox_dma_desc *to_tangox_dma_desc( + struct virt_dma_desc *vdesc) +{ + return container_of(vdesc, struct tangox_dma_desc, vd); +} + +static struct tangox_dma_desc *tangox_dma_alloc_desc(unsigned int num_sgs) +{ + return kzalloc(sizeof(struct tangox_dma_desc) + + sizeof(struct tangox_dma_sg) * num_sgs, GFP_ATOMIC); +} + +static void tangox_dma_sbox_map(struct tangox_dma_device *dev, int src, int dst) +{ + void __iomem *addr = dev->sbox_base + 8; + int shift = (dst - 1) * 4; + + if (shift > 31) { + addr += 4; + shift -= 32; + } + + writel(src << shift, addr); + wmb(); +} + +static void tangox_dma_pchan_setup(struct tangox_dma_pchan *pchan, + struct tangox_dma_desc *desc) +{ + struct tangox_dma_chan *chan = to_tangox_dma_chan(desc->vd.tx.chan); + struct tangox_dma_device *dev = pchan->dev; + + BUG_ON(desc->direction != pchan->direction); + + if (pchan->direction == DMA_DEV_TO_MEM) + tangox_dma_sbox_map(dev, chan->id, pchan->sbox_id); + else + tangox_dma_sbox_map(dev, pchan->sbox_id, chan->id); + + pchan->slave_id = chan->id; +} + +static void tangox_dma_pchan_detach(struct tangox_dma_pchan *pchan) +{ + struct tangox_dma_device *dev = pchan->dev; + + BUG_ON(pchan->slave_id < 0); + + if (pchan->direction == DMA_DEV_TO_MEM) + tangox_dma_sbox_map(dev, 0xf, pchan->sbox_id); + else + tangox_dma_sbox_map(dev, 0xf, pchan->slave_id); + + pchan->slave_id = -1; +} + +static int tangox_dma_issue_single(struct tangox_dma_pchan *pchan, + struct tangox_dma_sg *sg, int flags) +{ + writel(sg->addr, pchan->base + DMA_ADDR); + writel(sg->len, pchan->base + DMA_COUNT); + wmb(); + writel(flags << 2 | DMA_MODE_SINGLE, pchan->base + DMA_CMD); + wmb(); + + return sg->len; +} + +static int tangox_dma_issue_double(struct tangox_dma_pchan *pchan, + struct tangox_dma_sg *sg, int flags) +{ + unsigned int len1 = sg->len - TANGOX_DMA_MAX_LEN; + + writel(sg->addr, pchan->base + DMA_ADDR); + writel(sg->addr + TANGOX_DMA_MAX_LEN, pchan->base + DMA_ADDR2); + writel(TANGOX_DMA_MAX_LEN | len1 << 16, pchan->base + DMA_COUNT); + wmb(); + writel(flags << 2 | DMA_MODE_DOUBLE, pchan->base + DMA_CMD); + wmb(); + + return sg->len; +} + +static int tangox_dma_issue_rect(struct tangox_dma_pchan *pchan, + struct tangox_dma_sg *sg, int flags) +{ + int shift = min(__ffs(sg->len), 12ul); + int count = sg->len >> shift; + int width = 1 << shift; + + if (count > TANGOX_DMA_MAX_LEN) { + count = TANGOX_DMA_MAX_LEN; + flags &= ~1; + } + + writel(sg->addr, pchan->base + DMA_ADDR); + writel(width, pchan->base + DMA_STRIDE); + writel(width | count << 16, pchan->base + DMA_COUNT); + wmb(); + writel(flags << 2 | DMA_MODE_RECT, pchan->base + DMA_CMD); + wmb(); + + return count << shift; +} + +static int tangox_dma_pchan_issue(struct tangox_dma_pchan *pchan, + struct tangox_dma_sg *sg) +{ + int flags; + + if (pchan->next_sg == pchan->desc->num_sgs - 1) + flags = 1; + else + flags = 0; + + if (sg->len <= TANGOX_DMA_MAX_LEN) + return tangox_dma_issue_single(pchan, sg, flags); + + if (sg->len <= TANGOX_DMA_MAX_LEN * 2) + return tangox_dma_issue_double(pchan, sg, flags); + + return tangox_dma_issue_rect(pchan, sg, flags); +} + +static struct tangox_dma_desc *tangox_dma_next_desc( + struct tangox_dma_device *dev, enum dma_transfer_direction dir) +{ + struct tangox_dma_desc *desc; + struct list_head *list; + unsigned long flags; + + if (dir == DMA_MEM_TO_DEV) + list = &dev->desc_memtodev; + else + list = &dev->desc_devtomem; + + spin_lock_irqsave(&dev->lock, flags); + + desc = list_first_entry_or_null(list, struct tangox_dma_desc, vd.node); + if (desc) + list_del(&desc->vd.node); + + spin_unlock_irqrestore(&dev->lock, flags); + + return desc; +} + +static int tangox_dma_pchan_start(struct tangox_dma_pchan *pchan) +{ + struct tangox_dma_device *dev = pchan->dev; + struct tangox_dma_sg *sg; + int len; + + if (!pchan->desc) { + pchan->desc = tangox_dma_next_desc(dev, pchan->direction); + + if (!pchan->desc) { + tangox_dma_pchan_detach(pchan); + return 0; + } + + pchan->next_sg = 0; + tangox_dma_pchan_setup(pchan, pchan->desc); + } + + sg = &pchan->desc->sg[pchan->next_sg]; + + len = tangox_dma_pchan_issue(pchan, sg); + + sg->addr += len; + sg->len -= len; + + if (!sg->len) + pchan->next_sg++; + + pchan->issued_len = len; + + return 0; +} + +static void tangox_dma_queue_desc(struct tangox_dma_device *dev, + struct tangox_dma_desc *desc) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (desc->direction == DMA_MEM_TO_DEV) + list_add_tail(&desc->vd.node, &dev->desc_memtodev); + else + list_add_tail(&desc->vd.node, &dev->desc_devtomem); + spin_unlock_irqrestore(&dev->lock, flags); +} + +static irqreturn_t tangox_dma_irq(int irq, void *irq_data) +{ + struct tangox_dma_pchan *pchan = irq_data; + struct tangox_dma_chan *chan; + struct tangox_dma_desc *desc; + struct virt_dma_desc *vdesc; + + spin_lock(&pchan->lock); + + if (pchan->desc) { + desc = pchan->desc; + chan = to_tangox_dma_chan(desc->vd.tx.chan); + this_cpu_ptr(chan->vc.chan.local)->bytes_transferred += + pchan->issued_len; + if (pchan->next_sg == desc->num_sgs) { + spin_lock(&chan->vc.lock); + vchan_cookie_complete(&desc->vd); + vdesc = vchan_next_desc(&chan->vc); + if (vdesc) { + list_del(&vdesc->node); + desc = to_tangox_dma_desc(vdesc); + tangox_dma_queue_desc(pchan->dev, desc); + } + spin_unlock(&chan->vc.lock); + pchan->desc = NULL; + } + } + + tangox_dma_pchan_start(pchan); + + spin_unlock(&pchan->lock); + + return IRQ_HANDLED; +} + +static void tangox_dma_start(struct tangox_dma_device *dev, + enum dma_transfer_direction dir) +{ + struct tangox_dma_pchan *pchan = NULL; + unsigned long flags; + int i; + + for (i = 0; i < dev->nr_pchans; i++) { + pchan = &dev->pchan[i]; + if (pchan->direction == dir && !pchan->desc) + break; + } + + if (i == dev->nr_pchans) + return; + + spin_lock_irqsave(&pchan->lock, flags); + if (!pchan->desc) + tangox_dma_pchan_start(pchan); + spin_unlock_irqrestore(&pchan->lock, flags); +} + +static void tangox_dma_issue_pending(struct dma_chan *c) +{ + struct tangox_dma_device *dev = to_tangox_dma_device(c->device); + struct tangox_dma_chan *chan = to_tangox_dma_chan(c); + struct tangox_dma_desc *desc = NULL; + struct virt_dma_desc *vdesc; + unsigned long flags; + + spin_lock_irqsave(&chan->vc.lock, flags); + if (vchan_issue_pending(&chan->vc)) { + vdesc = vchan_next_desc(&chan->vc); + list_del(&vdesc->node); + desc = to_tangox_dma_desc(vdesc); + } + spin_unlock_irqrestore(&chan->vc.lock, flags); + + if (desc) { + tangox_dma_queue_desc(dev, desc); + tangox_dma_start(dev, desc->direction); + } +} + +static struct dma_async_tx_descriptor *tangox_dma_prep_slave_sg( + struct dma_chan *c, struct scatterlist *sgl, unsigned int sg_len, + enum dma_transfer_direction direction, + unsigned long flags, void *context) +{ + struct tangox_dma_chan *chan = to_tangox_dma_chan(c); + struct tangox_dma_desc *desc; + struct scatterlist *sg; + unsigned int i; + + desc = tangox_dma_alloc_desc(sg_len); + if (!desc) + return NULL; + + for_each_sg(sgl, sg, sg_len, i) { + desc->sg[i].addr = sg_dma_address(sg); + desc->sg[i].len = sg_dma_len(sg); + } + + desc->num_sgs = sg_len; + desc->direction = direction; + + return vchan_tx_prep(&chan->vc, &desc->vd, flags); +} + +static enum dma_status tangox_dma_tx_status(struct dma_chan *c, + dma_cookie_t cookie, struct dma_tx_state *state) +{ + return dma_cookie_status(c, cookie, state); +} + +static int tangox_dma_alloc_chan_resources(struct dma_chan *c) +{ + return 0; +} + +static void tangox_dma_free_chan_resources(struct dma_chan *c) +{ + vchan_free_chan_resources(to_virt_chan(c)); +} + +static void tangox_dma_desc_free(struct virt_dma_desc *vd) +{ + kfree(container_of(vd, struct tangox_dma_desc, vd)); +} + +static void tangox_dma_reset(struct tangox_dma_device *dev) +{ + int i; + + for (i = 0; i < 2; i++) { + writel(0xffffffff, dev->sbox_base); + writel(0xff00ff00, dev->sbox_base); + writel(0xffffffff, dev->sbox_base + 4); + writel(0xff00ff00, dev->sbox_base + 4); + udelay(2); + } + + writel(0xffffffff, dev->sbox_base + 8); + writel(0xffffffff, dev->sbox_base + 12); +} + +static struct dma_chan *tangox_dma_xlate(struct of_phandle_args *dma_spec, + struct of_dma *ofdma) +{ + struct dma_device *dev = ofdma->of_dma_data; + struct tangox_dma_chan *chan; + struct dma_chan *c; + + if (!dev || dma_spec->args_count != 1) + return NULL; + + list_for_each_entry(c, &dev->channels, device_node) { + chan = to_tangox_dma_chan(c); + if (chan->id == dma_spec->args[0]) + return dma_get_slave_channel(c); + } + + return NULL; +} + +static int tangox_dma_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct device_node *cnode; + struct tangox_dma_device *dmadev; + struct tangox_dma_pchan *pchan; + struct tangox_dma_chan *chan; + struct dma_device *dd; + struct resource *res; + struct resource cres; + int irq; + int err; + int i; + + dmadev = devm_kzalloc(&pdev->dev, sizeof(*dmadev), GFP_KERNEL); + if (!dmadev) + return -ENOMEM; + + dd = &dmadev->ddev; + + dma_cap_set(DMA_SLAVE, dd->cap_mask); + + dd->dev = &pdev->dev; + + dd->directions = 1 << DMA_MEM_TO_DEV | 1 << DMA_DEV_TO_MEM; + dd->device_alloc_chan_resources = tangox_dma_alloc_chan_resources; + dd->device_free_chan_resources = tangox_dma_free_chan_resources; + dd->device_prep_slave_sg = tangox_dma_prep_slave_sg; + dd->device_tx_status = tangox_dma_tx_status; + dd->device_issue_pending = tangox_dma_issue_pending; + + INIT_LIST_HEAD(&dd->channels); + + for (i = 0; i < TANGOX_DMA_MAX_CHANS; i++) { + chan = &dmadev->chan[i]; + + if (of_property_read_u32_index(node, "sigma,slave-ids", i, + &chan->id)) + break; + + chan->vc.desc_free = tangox_dma_desc_free; + vchan_init(&chan->vc, dd); + } + + dd->chancnt = i; + + spin_lock_init(&dmadev->lock); + INIT_LIST_HEAD(&dmadev->desc_memtodev); + INIT_LIST_HEAD(&dmadev->desc_devtomem); + + for_each_child_of_node(node, cnode) { + pchan = &dmadev->pchan[dmadev->nr_pchans]; + pchan->dev = dmadev; + spin_lock_init(&pchan->lock); + + if (of_property_read_bool(cnode, "sigma,mem-to-dev")) + pchan->direction = DMA_MEM_TO_DEV; + else + pchan->direction = DMA_DEV_TO_MEM; + + of_property_read_u32(cnode, "sigma,sbox-id", &pchan->sbox_id); + + err = of_address_to_resource(cnode, 0, &cres); + if (err) + return err; + + pchan->base = devm_ioremap_resource(&pdev->dev, &cres); + if (IS_ERR(pchan->base)) + return PTR_ERR(pchan->base); + + irq = irq_of_parse_and_map(cnode, 0); + if (!irq) + return -EINVAL; + + err = devm_request_irq(&pdev->dev, irq, tangox_dma_irq, 0, + dev_name(&pdev->dev), pchan); + if (err) + return err; + + if (++dmadev->nr_pchans == TANGOX_DMA_MAX_PCHANS) + break; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + dmadev->sbox_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dmadev->sbox_base)) + return PTR_ERR(dmadev->sbox_base); + + tangox_dma_reset(dmadev); + + err = dma_async_device_register(dd); + if (err) + return err; + + err = of_dma_controller_register(node, tangox_dma_xlate, dd); + if (err) { + dma_async_device_unregister(dd); + return err; + } + + platform_set_drvdata(pdev, dmadev); + + dev_info(&pdev->dev, "SMP86xx DMA with %d channels, %d slaves\n", + dmadev->nr_pchans, dd->chancnt); + + return 0; +} + +static int tangox_dma_remove(struct platform_device *pdev) +{ + struct tangox_dma_device *dmadev = platform_get_drvdata(pdev); + + of_dma_controller_free(pdev->dev.of_node); + dma_async_device_unregister(&dmadev->ddev); + + return 0; +} + +static struct of_device_id tangox_dma_dt_ids[] = { + { .compatible = "sigma,smp8640-dma" }, + { } +}; + +static struct platform_driver tangox_dma_driver = { + .probe = tangox_dma_probe, + .remove = tangox_dma_remove, + .driver = { + .name = "tangox-dma", + .of_match_table = tangox_dma_dt_ids, + }, +}; +module_platform_driver(tangox_dma_driver); + +MODULE_AUTHOR("Mans Rullgard <mans@xxxxxxxxx>"); +MODULE_DESCRIPTION("SMP86xx DMA driver"); +MODULE_LICENSE("GPL"); -- 2.9.0 -- To unsubscribe from this list: send the line "unsubscribe dmaengine" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html