[PATCH 1/6] dmaengine: Import tango MBUS driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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



[Index of Archives]     [Linux Kernel]     [Linux ARM (vger)]     [Linux ARM MSM]     [Linux Omap]     [Linux Arm]     [Linux Tegra]     [Fedora ARM]     [Linux for Samsung SOC]     [eCos]     [Linux PCI]     [Linux Fastboot]     [Gcc Help]     [Git]     [DCCP]     [IETF Announce]     [Security]     [Linux MIPS]     [Yosemite Campsites]

  Powered by Linux