[PATCH 4/4] dma: X-Gene PCIE DMA Driver
Mayuresh Chitale
mchitale at apm.com
Tue Jan 6 21:28:57 PST 2015
This patch implements DMA engine API for DMA controller on APM
X-Gene PCIe controller. DMA engine can support up to 4 channels per port
and up to 2048 outstanding requests per channel. This is intended
to be used on ports that are configured in EP mode or to transfer
data from a RC port that is connected to a X-Gene EP port.
Signed-off-by: Mayuresh Chitale <mchitale at apm.com>
Signed-off-by: Tanmay Inamdar <tinamdar at apm.com>
---
drivers/dma/Kconfig | 11 +
drivers/dma/Makefile | 1 +
drivers/dma/xgene-pcie-dma.c | 709 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 721 insertions(+)
create mode 100644 drivers/dma/xgene-pcie-dma.c
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index f2b2c4e..9f50759 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -464,4 +464,15 @@ config QCOM_BAM_DMA
Enable support for the QCOM BAM DMA controller. This controller
provides DMA capabilities for a variety of on-chip devices.
+config XGENE_PCIE_DMA
+ tristate "X-Gene PCIe DMA support for PCIe Devices"
+ depends on PCI_XGENE
+ select DMA_ENGINE
+ default n
+ help
+ Enable support for the X-Gene PCIe DMA engine. This can be used
+ to transfer data between PCIe RC and PCIe endpoints only.
+
+ If unsure, say N.
+
endif
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index 2022b54..6a75698 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -50,3 +50,4 @@ obj-y += xilinx/
obj-$(CONFIG_INTEL_MIC_X100_DMA) += mic_x100_dma.o
obj-$(CONFIG_NBPFAXI_DMA) += nbpfaxi.o
obj-$(CONFIG_DMA_SUN6I) += sun6i-dma.o
+obj-$(CONFIG_XGENE_PCIE_DMA) += xgene-pcie-dma.o
diff --git a/drivers/dma/xgene-pcie-dma.c b/drivers/dma/xgene-pcie-dma.c
new file mode 100644
index 0000000..f5dfcad
--- /dev/null
+++ b/drivers/dma/xgene-pcie-dma.c
@@ -0,0 +1,709 @@
+/*
+ * Copyright (c) 2014, 2015 Applied Micro Circuits Corporation.
+ * Author: Tanmay Inamdar <tinamdar at apm.com>
+ * Mayuresh Chitale <mchitale at apm.com>
+ *
+ * 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; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ */
+#include <linux/circ_buf.h>
+#include <linux/delay.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include "dmaengine.h"
+
+#define CHAN_REG_BASE 0x20000
+#define CHAN_REG_SIZE 0x40
+#define MAX_DMA_CHAN 4
+#define MAX_DMA_REQ 2048
+#define SRC_Q_PTR_LO 0x00
+#define SRC_Q_PTR_HI 0x04
+#define SRC_Q_SIZE 0x08
+#define SRC_Q_LIMIT 0x0C
+#define DST_Q_PTR_LO 0x10
+#define DST_Q_PTR_HI 0x14
+#define DST_Q_SIZE 0x18
+#define DST_Q_LIMIT 0x1C
+#define STA_Q_PTR_LO 0x20
+#define STA_Q_PTR_HI 0x24
+#define STA_Q_SIZE 0x28
+#define STA_Q_LIMIT 0x2C
+#define SRC_Q_NEXT 0x30
+#define DST_Q_NEXT 0x34
+#define STA_Q_NEXT 0x38
+#define DMA_CONTROL 0x3C
+#define AXI_DESC_COHERENCY (1U << 3)
+#define PCI_DESC_COHERENCY (1U << 2)
+#define DMA_READ_ATTR 0x20000000
+#define DMA_ENABLE 0x00000001
+#define DMA_RESET 0x00000004
+#define DMA_PRESENT 0x00008000
+#define PCIE_INTERRUPT 0x00010000
+#define AXI_INTERRUPT 0x01000000
+#define AXI_INTERRUPT_STATUS 0x02000000
+#define BUS_MASTER_EN_INT 0x4
+#define BUS_MASTER_DIS_INT 0x8
+#define BYTE_CNT_MASK 0xFFFFFF
+#define PORT_CFGCTL 0x8
+#define PORT_CFG_HI 0x4
+#define INTR_MASK 0x4
+#define DMA_ERROR 0xE
+#define DMA_COMPLETE 0x1
+#define PCIE_DMA_INT 0x100
+#define PCI_MEM 0
+#define AXI_MEM 1
+#define MAKE_U64(h, l) ((((u64) (h)) << 32) | (l))
+
+struct xpdma_qdesc {
+ u64 addr;
+ u64 metadata;
+};
+
+struct xpdma_desc {
+ atomic_t busy;
+ int id;
+ int src;
+ int slen;
+ int dst;
+ int dlen;
+ struct dma_async_tx_descriptor txd;
+};
+
+struct xpdma_chan {
+ struct dma_chan chan;
+ struct dma_slave_config cfg;
+ struct device *dev;
+ short id;
+ void __iomem *base;
+ spinlock_t lock;
+ u32 max_elems;
+ /* Queue descriptor addresses and circ_bufs*/
+ dma_addr_t src_elem_addr_phys;
+ struct circ_buf src;
+ dma_addr_t dst_elem_addr_phys;
+ struct circ_buf dst;
+ dma_addr_t sts_elem_addr_phys;
+ struct circ_buf sts;
+ struct circ_buf desc;
+};
+
+struct xpdma_port {
+ struct dma_device dma_dev;
+ void __iomem *dma;
+ void __iomem *cfg;
+ void __iomem *intr;
+ struct tasklet_struct completion_tasklet;
+};
+
+static void xpdma_chan_set(void __iomem *base, u32 bits)
+{
+ u32 val;
+
+ val = readl(base + DMA_CONTROL);
+ val |= bits;
+ writel(val, base + DMA_CONTROL);
+ readl(base + DMA_CONTROL);
+}
+
+static void xpdma_chan_clr(void __iomem *base, u32 bits)
+{
+ u32 val;
+
+ val = readl(base + DMA_CONTROL);
+ val &= ~bits;
+ writel(val, base + DMA_CONTROL);
+ readl(base + DMA_CONTROL);
+}
+
+static struct xpdma_chan *to_xpdma_chan(struct dma_chan *chan)
+{
+ return container_of(chan, struct xpdma_chan, chan);
+}
+
+static int xpdma_chan_present(struct xpdma_chan *chan)
+{
+ return readl(chan->base + DMA_CONTROL) & DMA_PRESENT;
+}
+
+static void xpdma_reset_chan(void *base)
+{
+ xpdma_chan_set(base, DMA_RESET);
+ xpdma_chan_clr(base, DMA_RESET);
+}
+
+static void xpdma_disable_chan(void *base)
+{
+ xpdma_chan_clr(base, DMA_ENABLE);
+}
+
+static void xpdma_enable_chan(void *base)
+{
+ xpdma_chan_set(base, DMA_ENABLE);
+}
+
+static void xpdma_disable_axi_int(void *base)
+{
+ xpdma_chan_clr(base, AXI_INTERRUPT);
+}
+
+static void xpdma_enable_axi_int(void *base)
+{
+ xpdma_chan_set(base, AXI_INTERRUPT);
+}
+
+static void xpdma_disable_pci_int(void *base)
+{
+ xpdma_chan_clr(base, PCIE_INTERRUPT);
+}
+
+static void xpdma_setup_src_q_desc(struct xpdma_chan *chan, int pos, u64 addr,
+ u32 byte_cnt, bool eop, bool intr, bool location)
+{
+ struct xpdma_qdesc *src_desc = (struct xpdma_qdesc *)
+ chan->src.buf + pos;
+
+ src_desc->addr = addr;
+ src_desc->metadata = (byte_cnt & BYTE_CNT_MASK) | location << 24 |
+ eop << 25 | intr << 26 | DMA_READ_ATTR;
+}
+
+static void xpdma_setup_dst_q_desc(struct xpdma_chan *chan, int pos, u64 addr,
+ u32 byte_cnt, bool location)
+{
+ struct xpdma_qdesc *dst_desc = (struct xpdma_qdesc *)
+ chan->dst.buf + pos;
+
+ dst_desc->addr = addr;
+ dst_desc->metadata = (byte_cnt & BYTE_CNT_MASK) | location << 24 |
+ DMA_READ_ATTR;
+}
+
+static struct xpdma_desc *xpdma_desc_get(struct xpdma_chan *chan,
+ int src_elems, int dst_elems)
+{
+ struct xpdma_desc *desc = NULL;
+
+ spin_lock_bh(&chan->lock);
+ desc = (struct xpdma_desc *) chan->desc.buf + chan->desc.head;
+ if (!CIRC_SPACE(chan->desc.head, chan->desc.tail, chan->max_elems) ||
+ atomic_read(&desc->busy)) {
+ dev_err(chan->dev, "No free descriptors found.\n");
+ goto out_error;
+ }
+
+ if (CIRC_SPACE(chan->src.head, chan->src.tail, chan->max_elems)
+ < src_elems) {
+ dev_err(chan->dev, "No free source elems src elements found.\n");
+ goto out_error;
+ }
+
+ if (CIRC_SPACE(chan->dst.head, chan->dst.tail, chan->max_elems)
+ < dst_elems) {
+ dev_err(chan->dev, "No free dst elements found.\n");
+ goto out_error;
+ }
+
+ atomic_set(&desc->busy, 1);
+ desc->src = chan->src.head;
+ desc->slen = src_elems;
+ desc->dst = chan->dst.head;
+ desc->dlen = dst_elems;
+ chan->desc.head = (chan->desc.head + 1) & (chan->max_elems - 1);
+ chan->src.head = (chan->src.head + src_elems) & (chan->max_elems - 1);
+ chan->dst.head = (chan->dst.head + dst_elems) & (chan->max_elems - 1);
+ chan->sts.head = (chan->sts.head + 1) & (chan->max_elems - 1);
+ spin_unlock_bh(&chan->lock);
+
+ return desc;
+
+out_error:
+ spin_unlock_bh(&chan->lock);
+ dev_err(chan->dev, "Failed to get desc\n");
+
+ return NULL;
+}
+
+static void xpdma_desc_put(struct xpdma_chan *chan, struct xpdma_desc *desc)
+{
+ spin_lock_bh(&chan->lock);
+ chan->src.tail = (chan->src.tail + desc->slen) & (chan->max_elems - 1);
+ chan->dst.tail = (chan->dst.tail + desc->dlen) & (chan->max_elems - 1);
+ chan->sts.tail = (chan->sts.tail + 1) & (chan->max_elems - 1);
+ chan->desc.tail = (chan->desc.tail + 1) & (chan->max_elems - 1);
+ atomic_set(&desc->busy, 0);
+ spin_unlock_bh(&chan->lock);
+}
+
+static int xpdma_desc_complete(struct xpdma_chan *chan, struct xpdma_desc *desc)
+{
+ u32 *sts_desc, status;
+ struct dma_async_tx_descriptor *txd = NULL;
+
+ sts_desc = (u32 *) chan->sts.buf + chan->sts.tail;
+ status = *sts_desc;
+ txd = &desc->txd;
+ if (!txd) {
+ dev_err(chan->dev, "Chan %d, Desc %d, txd %p\n",
+ chan->id, desc->id, txd);
+ return -EINVAL;
+ }
+
+ if (!(status & DMA_COMPLETE)) {
+ dev_dbg(chan->dev,
+ "Chan %d, Desc %d, DMA pending\n",
+ chan->id, desc->id);
+ return -EAGAIN;
+ }
+
+ if (status & DMA_ERROR)
+ dev_err(chan->dev, "Chan %d, Desc %d, DMA error 0x%08X\n",
+ chan->id, desc->id, status);
+ else {
+ dma_cookie_complete(txd);
+ dma_descriptor_unmap(txd);
+ }
+
+ if (txd->callback)
+ txd->callback(txd->callback_param);
+
+ /* Clear the status descriptor and mark elements as free */
+ *sts_desc = 0;
+ xpdma_desc_put(chan, desc);
+
+ return 0;
+}
+
+static dma_cookie_t xpdma_tx_submit(struct dma_async_tx_descriptor *tx)
+{
+ return dma_cookie_assign(tx);
+}
+
+static int xpdma_alloc_chan_resources(struct dma_chan *dchan)
+{
+ int i;
+ struct xpdma_desc *desc;
+ struct xpdma_chan *chan = to_xpdma_chan(dchan);
+
+ chan->desc.buf = devm_kzalloc(chan->dev, chan->max_elems *
+ sizeof(struct xpdma_desc), GFP_KERNEL);
+ if (!chan->desc.buf)
+ return -ENOMEM;
+
+ for (i = 0; i < chan->max_elems; i++) {
+ desc = (struct xpdma_desc *) chan->desc.buf + i;
+ dma_async_tx_descriptor_init(&desc->txd, dchan);
+ desc->txd.tx_submit = xpdma_tx_submit;
+ desc->id = i;
+ atomic_set(&desc->busy, 0);
+ }
+
+ dma_cookie_init(dchan);
+ xpdma_enable_axi_int(chan->base);
+
+ return i;
+}
+
+static void xpdma_free_chan_resources(struct dma_chan *dchan)
+{
+ struct xpdma_chan *chan = to_xpdma_chan(dchan);
+
+ devm_kfree(chan->dev, chan->desc.buf);
+ xpdma_disable_axi_int(chan);
+}
+
+static int xpdma_device_control(struct dma_chan *dchan,
+ enum dma_ctrl_cmd cmd, unsigned long arg)
+{
+ struct xpdma_chan *chan = to_xpdma_chan(dchan);
+ struct dma_slave_config *cfg = (struct dma_slave_config *) arg;
+
+ if (cmd == DMA_SLAVE_CONFIG) {
+ memcpy(&chan->cfg, cfg, sizeof(chan->cfg));
+ return 0;
+ }
+
+ return -ENXIO;
+}
+
+static struct dma_async_tx_descriptor *xpdma_prep_slave_sg(
+ struct dma_chan *dchan, struct scatterlist *sgl,
+ u32 sg_len, enum dma_transfer_direction dir,
+ unsigned long flags, void *context)
+{
+ int i, len = 0;
+ struct scatterlist *sg;
+ struct xpdma_desc *desc;
+ struct xpdma_chan *chan = to_xpdma_chan(dchan);
+ struct dma_slave_config cfg = chan->cfg;
+ u8 eop_intr = 0;
+
+ if (!is_slave_direction(dir)) {
+ dev_err(chan->dev, "Incorrect DMA Transfer direction %d\n",
+ dir);
+ return NULL;
+ }
+
+ if (dir == DMA_MEM_TO_DEV)
+ desc = xpdma_desc_get(chan, sg_len, 1);
+ else
+ desc = xpdma_desc_get(chan, 1, sg_len);
+
+ if (!desc)
+ return NULL;
+
+ for_each_sg(sgl, sg, sg_len, i) {
+ if (dir == DMA_MEM_TO_DEV) {
+ if (i == (sg_len - 1))
+ eop_intr = 1;
+ xpdma_setup_src_q_desc(chan, desc->src + i,
+ sg_dma_address(sg), sg_dma_len(sg),
+ eop_intr, eop_intr, AXI_MEM);
+ len += sg_dma_len(sg);
+ } else {
+ xpdma_setup_dst_q_desc(chan, desc->dst + i,
+ sg_dma_address(sg), sg_dma_len(sg),
+ AXI_MEM);
+ len += sg_dma_len(sg);
+ }
+ }
+
+ if (dir == DMA_MEM_TO_DEV)
+ xpdma_setup_dst_q_desc(chan, desc->dst, cfg.dst_addr, len,
+ PCI_MEM);
+ else
+ xpdma_setup_src_q_desc(chan, desc->src, cfg.src_addr, len,
+ 1, 1, PCI_MEM);
+ return &desc->txd;
+
+}
+
+static enum dma_status xpdma_tx_status(struct dma_chan *dchan,
+ dma_cookie_t cookie,
+ struct dma_tx_state *txstate)
+{
+ return dma_cookie_status(dchan, cookie, txstate);
+}
+
+static void xpdma_issue_pending(struct dma_chan *dchan)
+{
+ struct xpdma_chan *chan = to_xpdma_chan(dchan);
+
+ spin_lock_bh(&chan->lock);
+ writel(chan->src.head, chan->base + SRC_Q_LIMIT);
+ writel(chan->dst.head, chan->base + DST_Q_LIMIT);
+ writel(chan->sts.head, chan->base + STA_Q_LIMIT);
+ spin_unlock_bh(&chan->lock);
+}
+
+static void xpdma_setup_dma_dev(struct dma_device *dev)
+{
+ dma_cap_zero(dev->cap_mask);
+ dma_cap_set(DMA_SLAVE, dev->cap_mask);
+ dma_cap_set(DMA_PRIVATE, dev->cap_mask);
+
+ dev->device_alloc_chan_resources = xpdma_alloc_chan_resources;
+ dev->device_free_chan_resources = xpdma_free_chan_resources;
+ dev->device_tx_status = xpdma_tx_status;
+ dev->device_issue_pending = xpdma_issue_pending;
+ dev->device_prep_slave_sg = xpdma_prep_slave_sg;
+ dev->device_control = xpdma_device_control;
+}
+
+static void xpdma_init_channel(struct xpdma_chan *chan)
+{
+ xpdma_disable_axi_int(chan->base);
+ xpdma_disable_pci_int(chan->base);
+ xpdma_disable_chan(chan->base);
+ xpdma_reset_chan(chan->base);
+ /*
+ * Setup queue management registers
+ */
+ writel(0, chan->base + SRC_Q_NEXT);
+ writel(0, chan->base + DST_Q_NEXT);
+ writel(0, chan->base + STA_Q_NEXT);
+ writel(0, chan->base + SRC_Q_LIMIT);
+ writel(0, chan->base + DST_Q_LIMIT);
+ writel(0, chan->base + STA_Q_LIMIT);
+ writel(lower_32_bits(chan->src_elem_addr_phys) | AXI_MEM |
+ AXI_DESC_COHERENCY, chan->base + SRC_Q_PTR_LO);
+ writel(upper_32_bits(chan->src_elem_addr_phys),
+ chan->base + SRC_Q_PTR_HI);
+ writel(chan->max_elems, chan->base + SRC_Q_SIZE);
+
+ writel(lower_32_bits(chan->dst_elem_addr_phys) | AXI_MEM |
+ AXI_DESC_COHERENCY, chan->base + DST_Q_PTR_LO);
+ writel(upper_32_bits(chan->dst_elem_addr_phys),
+ chan->base + DST_Q_PTR_HI);
+ writel(chan->max_elems, chan->base + DST_Q_SIZE);
+
+ writel(lower_32_bits(chan->sts_elem_addr_phys) | AXI_MEM |
+ AXI_DESC_COHERENCY, chan->base + STA_Q_PTR_LO);
+ writel(upper_32_bits(chan->sts_elem_addr_phys),
+ chan->base + STA_Q_PTR_HI);
+ writel(chan->max_elems, chan->base + STA_Q_SIZE);
+ xpdma_enable_chan(chan->base);
+}
+
+static irqreturn_t xpdma_isr(int irq, void *data)
+{
+ u32 imask, status;
+ struct xpdma_chan *chan;
+ struct dma_chan *dchan;
+ struct xpdma_port *port = (struct xpdma_port *) data;
+
+ status = readl(port->intr);
+ imask = readl(port->intr + INTR_MASK);
+ if ((status & BUS_MASTER_DIS_INT) && !(imask & BUS_MASTER_DIS_INT)) {
+ imask |= BUS_MASTER_DIS_INT;
+ imask &= ~BUS_MASTER_EN_INT;
+ writel(imask, port->intr + INTR_MASK);
+ }
+
+ if ((status & BUS_MASTER_EN_INT) && !(imask & BUS_MASTER_EN_INT)) {
+ /*
+ * As per spec few registers should be programmed only
+ * after bus master enable.
+ */
+ list_for_each_entry(dchan, &port->dma_dev.channels,
+ device_node) {
+ chan = to_xpdma_chan(dchan);
+ xpdma_init_channel(chan);
+ }
+ imask |= BUS_MASTER_EN_INT;
+ imask &= ~BUS_MASTER_DIS_INT;
+ writel(imask, port->intr + INTR_MASK);
+ }
+
+ if (status & PCIE_DMA_INT) {
+ imask |= PCIE_DMA_INT;
+ writel(imask, port->intr + INTR_MASK);
+ tasklet_schedule(&port->completion_tasklet);
+ }
+ return IRQ_HANDLED;
+}
+
+static void xpdma_tasklet(unsigned long data)
+{
+ u32 status, imask;
+ struct xpdma_desc *desc;
+ struct xpdma_port *port = (struct xpdma_port *) data;
+ struct xpdma_chan *chan;
+ struct dma_chan *dchan;
+
+ list_for_each_entry(dchan, &port->dma_dev.channels, device_node) {
+ chan = to_xpdma_chan(dchan);
+ status = readl(chan->base + DMA_CONTROL);
+ if (!(status & AXI_INTERRUPT_STATUS))
+ continue;
+ status |= AXI_INTERRUPT_STATUS;
+ writel(status, chan->base + DMA_CONTROL);
+ do {
+ desc = (struct xpdma_desc *) chan->desc.buf +
+ chan->desc.tail;
+ } while (atomic_read(&desc->busy) &&
+ !xpdma_desc_complete(chan, desc));
+ }
+ imask = readl(port->intr + INTR_MASK);
+ imask &= ~PCIE_DMA_INT;
+ writel(imask, port->intr + INTR_MASK);
+}
+
+static int xpdma_alloc_sglists(struct xpdma_chan *chan)
+{
+ unsigned long len;
+ void *addr;
+
+ len = chan->max_elems * sizeof(struct xpdma_qdesc);
+ chan->src.buf = addr = dmam_alloc_coherent(chan->dev, len,
+ &chan->src_elem_addr_phys, GFP_KERNEL);
+ if (!chan->src.buf) {
+ dev_err(chan->dev, "Failed to allocate source sg descriptors\n");
+ return -ENOMEM;
+ }
+
+ chan->dst.buf = dmam_alloc_coherent(chan->dev, len,
+ &chan->dst_elem_addr_phys, GFP_KERNEL);
+ if (!chan->dst.buf) {
+ dev_err(chan->dev, "Failed to allocate destination sg descriptors\n");
+ return -ENOMEM;
+ }
+
+ chan->sts.buf = dmam_alloc_coherent(chan->dev, chan->max_elems,
+ &chan->sts_elem_addr_phys, GFP_KERNEL);
+ if (!chan->sts.buf) {
+ dev_err(chan->dev, "Failed to allocate source sg descriptors\n");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int xpdma_setup_dma_channel(struct platform_device *pdev,
+ struct xpdma_port *port)
+{
+ int i, ret = 0;
+ struct xpdma_chan *chan;
+ resource_size_t dma_base;
+
+ dma_base = MAKE_U64(readl(port->cfg + PORT_CFG_HI), readl(port->cfg)) +
+ CHAN_REG_BASE;
+ port->dma = devm_ioremap(&pdev->dev, dma_base,
+ CHAN_REG_SIZE * MAX_DMA_CHAN);
+ if (port->dma == NULL) {
+ dev_err(&pdev->dev, "Could not get base addressc$\n");
+ return -ENOMEM;
+ }
+
+ INIT_LIST_HEAD(&port->dma_dev.channels);
+ for (i = 0; i < MAX_DMA_CHAN; i++) {
+ chan = devm_kzalloc(&pdev->dev, sizeof(struct xpdma_chan),
+ GFP_KERNEL);
+ if (!chan)
+ return -ENOMEM;
+ memset(chan, 0, sizeof(*chan));
+ chan->id = i;
+ chan->dev = port->dma_dev.dev;
+ chan->base = port->dma + (i * CHAN_REG_SIZE);
+ chan->chan.device = &port->dma_dev;
+ if (!xpdma_chan_present(chan)) {
+ dev_err(chan->dev, "DMA Chan %d is disabled\n",
+ chan->id);
+ continue;
+ }
+
+ chan->max_elems = MAX_DMA_REQ;
+ ret = xpdma_alloc_sglists(chan);
+ if (ret)
+ return -ENOMEM;
+ spin_lock_init(&chan->lock);
+ list_add_tail(&chan->chan.device_node,
+ &port->dma_dev.channels);
+ }
+ return 0;
+}
+
+static int xpdma_probe(struct platform_device *pdev)
+{
+ int err;
+ u32 mask;
+ struct resource *res;
+ struct xpdma_port *port;
+
+ port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);
+ if (!port)
+ return -ENOMEM;
+
+ port->dma_dev.dev = &pdev->dev;
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "No cfg resource\n");
+ return -EINVAL;
+ }
+
+ port->cfg = devm_ioremap(&pdev->dev, res->start, resource_size(res));
+ if (IS_ERR(port->cfg))
+ return PTR_ERR(port->cfg);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (!res) {
+ dev_err(&pdev->dev, "No intr resource\n");
+ return -EINVAL;
+ }
+
+ port->intr = devm_ioremap(&pdev->dev, res->start, resource_size(res));
+ if (IS_ERR(port->intr))
+ return PTR_ERR(port->intr);
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "No irq resource\n");
+ return -EINVAL;
+ }
+
+ if (!readl(port->cfg + PORT_CFGCTL)) {
+ dev_err(&pdev->dev, "Port not enabled\n");
+ return -EINVAL;
+ }
+
+ err = xpdma_setup_dma_channel(pdev, port);
+ if (err) {
+ dev_err(&pdev->dev, "Setup channel failed\n");
+ return -EINVAL;
+ }
+
+ tasklet_init(&port->completion_tasklet, xpdma_tasklet,
+ (unsigned long)port);
+
+ err = devm_request_irq(&pdev->dev, res->start, xpdma_isr,
+ IRQF_SHARED, "PCIEDMA", port);
+ if (err) {
+ dev_err(&pdev->dev, "Request IRQ failed for XGENE PCIe DMA\n");
+ return -EINVAL;
+ }
+
+ xpdma_setup_dma_dev(&port->dma_dev);
+ /* Setup DMA mask - 32 for 32-bit system and 64 for 64-bit system */
+ err = dma_set_mask_and_coherent(&pdev->dev,
+ DMA_BIT_MASK(8*sizeof(void *)));
+ if (err) {
+ dev_err(&pdev->dev, "Unable to set dma mask\n");
+ return err;
+ }
+
+ err = dma_async_device_register(&port->dma_dev);
+ if (err) {
+ dev_err(&pdev->dev,
+ "XGENE PCIe DMA device_register failed: %d\n",
+ err);
+ return -EINVAL;
+ }
+
+ platform_set_drvdata(pdev, port);
+ mask = readl(port->intr + INTR_MASK);
+ mask &= ~(BUS_MASTER_EN_INT | PCIE_DMA_INT);
+ writel(mask, port->intr + INTR_MASK);
+ dev_info(&pdev->dev, "X-Gene PCIE DMA driver v1.0");
+ return 0;
+}
+
+static int xpdma_remove(struct platform_device *pdev)
+{
+ struct xpdma_port *port = platform_get_drvdata(pdev);
+
+ dma_async_device_unregister(&port->dma_dev);
+ return 0;
+}
+
+static const struct of_device_id xpdma_match_table[] = {
+ {.compatible = "apm,xgene-pciedma",},
+ {},
+};
+
+static struct platform_driver xpdma_driver = {
+ .driver = {
+ .name = "xgene-pciedma",
+ .of_match_table = of_match_ptr(xpdma_match_table),
+ },
+ .probe = xpdma_probe,
+ .remove = xpdma_remove,
+};
+module_platform_driver(xpdma_driver);
+
+MODULE_AUTHOR("Tanmay Inamdar <tinamdar at apm.com>");
+MODULE_AUTHOR("Mayuresh Chitale <mchitale at apm.com>");
+MODULE_DESCRIPTION("XGENE X-Gene PCIe DMA Driver");
+MODULE_LICENSE("GPL v2");
--
2.2.1.212.gc5b9256
More information about the linux-arm-kernel
mailing list