[PATCH 2/2] dmaengine: mtk-hsdma: Add Mediatek High-Speed DMA controller on MT7623 SoC

sean.wang at mediatek.com sean.wang at mediatek.com
Sun May 14 23:59:27 PDT 2017


From: Sean Wang <sean.wang at mediatek.com>

Add dmaengine driver for Mediatek High-Speed DMA based on the feature
DMA_VIRTUAL_CHANNELS.

Mediatek High-Speed DMA controller (HSDMA) on MT7623 SoC has the single
channel which is dedicated to memory-to-memory transfer through
ring-based descriptor management. Even though there is only one physical
channel available inside HSDMA, the driver is extended to the support for
multiple virtual channels processing simultaneously in round-round way
by means of the feature DMA_VIRTUAL_CHANNELS.

Signed-off-by: Sean Wang <sean.wang at mediatek.com>
---
 drivers/dma/Kconfig     |  14 +
 drivers/dma/Makefile    |   1 +
 drivers/dma/mtk-hsdma.c | 890 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 905 insertions(+)
 create mode 100644 drivers/dma/mtk-hsdma.c

diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index fc3435c..656beac 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -329,6 +329,20 @@ config MPC512X_DMA
 	---help---
 	  Enable support for the Freescale MPC512x built-in DMA engine.
 
+config MTK_HSDMA
+	tristate "Mediatek High-Speed DMA controller support"
+	depends on ARCH_MEDIATEK || COMPILE_TEST
+	select DMA_ENGINE
+	select DMA_VIRTUAL_CHANNELS
+	---help---
+	  Enable support for High-Speed DMA controller on Mediatek
+	  SoCs.
+
+	  This controller provides the channels which is dedicated to
+	  memory-to-memory transfer to offload from CPU through ring-
+	  based descriptor management which could be found on MT7623
+	  platform.
+
 config MV_XOR
 	bool "Marvell XOR engine support"
 	depends on PLAT_ORION || ARCH_MVEBU || COMPILE_TEST
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index 0b723e9..40ef3a4 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -68,6 +68,7 @@ obj-$(CONFIG_TI_EDMA) += edma.o
 obj-$(CONFIG_XGENE_DMA) += xgene-dma.o
 obj-$(CONFIG_ZX_DMA) += zx_dma.o
 obj-$(CONFIG_ST_FDMA) += st_fdma.o
+obj-$(CONFIG_MTK_HSDMA) += mtk-hsdma.o
 
 obj-y += qcom/
 obj-y += xilinx/
diff --git a/drivers/dma/mtk-hsdma.c b/drivers/dma/mtk-hsdma.c
new file mode 100644
index 0000000..2adc1bc
--- /dev/null
+++ b/drivers/dma/mtk-hsdma.c
@@ -0,0 +1,890 @@
+/*
+ * Driver for Mediatek High-Speed DMA Controller
+ *
+ * Copyright (C) 2017 Sean Wang <sean.wang at mediatek.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; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * 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/clk.h>
+#include <linux/delay.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/iopoll.h>
+#include <linux/jiffies.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_dma.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include "virt-dma.h"
+
+#define MTK_DMA_DEV KBUILD_MODNAME
+
+#define MTK_HSDMA_USEC_POLL		20
+#define MTK_HSDMA_TIMEOUT_POLL		200000
+
+#define MTK_HSDMA_DMA_BUSWIDTHS (BIT(DMA_SLAVE_BUSWIDTH_UNDEFINED) | \
+				 BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | \
+				 BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | \
+				 BIT(DMA_SLAVE_BUSWIDTH_4_BYTES))
+
+/* Max size of data one descriptor can move */
+#define MTK_DMA_MAX_DATA_ITEMS		0x3fff
+
+/* The default number of virtual channel */
+#define MTK_DMA_MAX_VCHANNELS		3
+
+/* MTK_DMA_SIZE must be 2 of power and 4 for the minimal */
+#define MTK_DMA_SIZE			256
+#define MTK_HSDMA_NEXT_DESP_IDX(x, y)	(((x) + 1) & ((y) - 1))
+#define MTK_HSDMA_PREV_DESP_IDX(x, y)	(((x) - 1) & ((y) - 1))
+#define MTK_HSDMA_MAX_LEN		0x3f80
+#define MTK_HSDMA_ALIGN_SIZE		4
+#define MTK_HSDMA_TIMEOUT		HZ
+
+/* Registers and related fields definition */
+#define MTK_HSDMA_TX_BASE		0x0
+#define MTK_HSDMA_TX_CNT		0x4
+#define MTK_HSDMA_TX_CPU		0x8
+#define MTK_HSDMA_TX_DMA		0xc
+#define MTK_HSDMA_RX_BASE		0x100
+#define MTK_HSDMA_RX_CNT		0x104
+#define MTK_HSDMA_RX_CPU		0x108
+#define MTK_HSDMA_RX_DMA		0x10c
+#define MTK_HSDMA_INFO			0x200
+#define MTK_HSDMA_GLO			0x204
+#define MTK_HSDMA_GLO_TX2B_OFFSET	BIT(31)
+#define MTK_HSDMA_GLO_MULTI_DMA		BIT(10)
+#define MTK_HSDMA_TX_WB_DDONE		BIT(6)
+#define MTK_HSDMA_BURST_64BYTES		(0x2 << 4)
+#define MTK_HSDMA_BURST_32BYTES		(0x1 << 4)
+#define MTK_HSDMA_BURST_16BYTES		(0x0 << 4)
+#define MTK_HSDMA_GLO_RX_BUSY		BIT(3)
+#define MTK_HSDMA_GLO_RX_DMA		BIT(2)
+#define MTK_HSDMA_GLO_TX_BUSY		BIT(1)
+#define MTK_HSDMA_GLO_TX_DMA		BIT(0)
+#define MTK_HSDMA_GLO_DMA		(MTK_HSDMA_GLO_TX_DMA |\
+					 MTK_HSDMA_GLO_RX_DMA)
+#define MTK_HSDMA_GLO_BUSY		(MTK_HSDMA_GLO_RX_BUSY |\
+					 MTK_HSDMA_GLO_TX_BUSY)
+#define MTK_HSDMA_GLO_DEFAULT		(MTK_HSDMA_GLO_TX_DMA | \
+					 MTK_HSDMA_GLO_RX_DMA | \
+					 MTK_HSDMA_TX_WB_DDONE | \
+					 MTK_HSDMA_BURST_64BYTES | \
+					 MTK_HSDMA_GLO_MULTI_DMA)
+#define MTK_HSDMA_RESET			0x208
+#define MTK_HSDMA_RST_TX		BIT(0)
+#define MTK_HSDMA_RST_RX		BIT(16)
+#define MTK_HSDMA_DLYINT		0x20c
+#define MTK_HSDMA_RXDLY_INT_EN		BIT(15)
+#define MTK_HSDMA_RXMAX_PINT(x)		(((x) & 0x7f) << 8)
+#define MTK_HSDMA_RXMAX_PTIME(x)	(((x) & 0xff))
+#define MTK_HSDMA_DLYINT_DEFAULT	(MTK_HSDMA_RXDLY_INT_EN |\
+					 MTK_HSDMA_RXMAX_PINT(30) |\
+					 MTK_HSDMA_RXMAX_PINT(50))
+#define MTK_HSDMA_FREEQ_THR		0x210
+#define MTK_HSDMA_INT_STATUS		0x220
+#define MTK_HSDMA_INT_ENABLE		0x228
+#define MTK_HSDMA_INT_RXDONE		BIT(16)
+#define MTK_HSDMA_PLEN_MASK		0x3fff
+#define MTK_HSDMA_DESC_DDONE		BIT(31)
+#define MTK_HSDMA_DESC_LS0		BIT(30)
+#define MTK_HSDMA_DESC_PLEN(x)		(((x) & MTK_HSDMA_PLEN_MASK) << 16)
+
+enum mtk_hsdma_cb_flags {
+	VDESC_FINISHED	= 0x01,
+};
+
+#define IS_VDESC_FINISHED(x) ((x) == VDESC_FINISHED)
+
+struct mtk_hsdma_device;
+
+/* The placement of descriptors should be kept at 4-bytes alignment */
+struct mtk_hsdma_pdesc {
+	__le32 des1;
+	__le32 des2;
+	__le32 des3;
+	__le32 des4;
+} __packed __aligned(4);
+
+struct mtk_hsdma_cb {
+	struct virt_dma_desc *vd;
+	enum mtk_hsdma_cb_flags flags;
+};
+
+struct mtk_hsdma_vdesc {
+	struct virt_dma_desc vd;
+	size_t len;
+	dma_addr_t dest;
+	dma_addr_t src;
+	u32 num_sgs;
+};
+
+struct mtk_hsdma_ring {
+	struct mtk_hsdma_pdesc *txd;
+	struct mtk_hsdma_pdesc *rxd;
+	struct mtk_hsdma_cb *cb;
+	dma_addr_t tphys;
+	dma_addr_t rphys;
+	u16 cur_tptr;
+	u16 cur_rptr;
+};
+
+struct mtk_hsdma_pchan {
+	u32 sz_ring;
+	atomic_t free_count;
+	struct mtk_hsdma_ring ring;
+	struct mtk_hsdma_device *hsdma;
+};
+
+struct mtk_hsdma_vchan {
+	struct virt_dma_chan vc;
+	struct virt_dma_desc *vd_uncompleted;
+	struct mtk_hsdma_pchan *pc;
+	struct list_head node;
+	atomic_t refcnt;
+};
+
+struct mtk_hsdma_device {
+	struct dma_device ddev;
+	void __iomem *base;
+	struct clk *clk;
+	u32 irq;
+	bool busy;
+
+	struct mtk_hsdma_vchan *vc;
+	struct mtk_hsdma_pchan pc;
+	struct list_head vc_pending;
+	struct mtk_hsdma_vchan *vc_uncompleted;
+
+	struct tasklet_struct housekeeping;
+	struct tasklet_struct scheduler;
+	atomic_t pc_refcnt;
+	u32 dma_requests;
+	/* Lock used to protect the list vc_pending */
+	spinlock_t lock;
+};
+
+static struct device *chan2dev(struct dma_chan *chan)
+{
+	return &chan->dev->device;
+}
+
+static struct mtk_hsdma_device *to_hsdma_dev(struct dma_chan *chan)
+{
+	return container_of(chan->device, struct mtk_hsdma_device,
+			    ddev);
+}
+
+static inline struct mtk_hsdma_vchan *to_hsdma_vchan(struct dma_chan *chan)
+{
+	return container_of(chan, struct mtk_hsdma_vchan, vc.chan);
+}
+
+static struct mtk_hsdma_vdesc *to_hsdma_vdesc(struct virt_dma_desc *vd)
+{
+	return container_of(vd, struct mtk_hsdma_vdesc, vd);
+}
+
+static struct device *hsdma2dev(struct mtk_hsdma_device *hsdma)
+{
+	return hsdma->ddev.dev;
+}
+
+static u32 mtk_dma_read(struct mtk_hsdma_device *hsdma, u32 reg)
+{
+	return readl(hsdma->base + reg);
+}
+
+static void mtk_dma_write(struct mtk_hsdma_device *hsdma, u32 reg, u32 val)
+{
+	writel(val, hsdma->base + reg);
+}
+
+static void mtk_dma_rmw(struct mtk_hsdma_device *hsdma, u32 reg,
+			u32 mask, u32 set)
+{
+	u32 val;
+
+	val = mtk_dma_read(hsdma, reg);
+	val &= ~mask;
+	val |= set;
+	mtk_dma_write(hsdma, reg, val);
+}
+
+static void mtk_dma_set(struct mtk_hsdma_device *hsdma, u32 reg, u32 val)
+{
+	mtk_dma_rmw(hsdma, reg, 0, val);
+}
+
+static void mtk_dma_clr(struct mtk_hsdma_device *hsdma, u32 reg, u32 val)
+{
+	mtk_dma_rmw(hsdma, reg, val, 0);
+}
+
+static void mtk_hsdma_vdesc_free(struct virt_dma_desc *vd)
+{
+	kfree(container_of(vd, struct mtk_hsdma_vdesc, vd));
+}
+
+static int mtk_hsdma_busy_wait(struct mtk_hsdma_device *hsdma)
+{
+	u32 status = 0;
+
+	return readl_poll_timeout(hsdma->base + MTK_HSDMA_GLO, status,
+				  !(status & MTK_HSDMA_GLO_BUSY),
+				  MTK_HSDMA_USEC_POLL,
+				  MTK_HSDMA_TIMEOUT_POLL);
+}
+
+static int mtk_hsdma_alloc_pchan(struct mtk_hsdma_device *hsdma,
+				 struct mtk_hsdma_pchan *pc)
+{
+	int i, ret;
+	struct mtk_hsdma_ring *ring = &pc->ring;
+
+	dev_dbg(hsdma2dev(hsdma), "Allocating pchannel\n");
+
+	memset(pc, 0, sizeof(*pc));
+	pc->hsdma = hsdma;
+	atomic_set(&pc->free_count, MTK_DMA_SIZE - 1);
+	pc->sz_ring = 2 * MTK_DMA_SIZE * sizeof(*ring->txd);
+	ring->txd = dma_alloc_coherent(hsdma2dev(hsdma),
+				       pc->sz_ring, &ring->tphys,
+				       GFP_ATOMIC | __GFP_ZERO);
+	if (!ring->txd)
+		return -ENOMEM;
+
+	memset(ring->txd, 0, pc->sz_ring);
+	for (i = 0; i < MTK_DMA_SIZE; i++)
+		ring->txd[i].des2 = MTK_HSDMA_DESC_LS0 | MTK_HSDMA_DESC_DDONE;
+
+	ring->cb = kcalloc(MTK_DMA_SIZE, sizeof(*ring->cb), GFP_KERNEL);
+	if (!ring->cb) {
+		ret = -ENOMEM;
+		goto err_free_dma;
+	}
+
+	ring->rxd = &ring->txd[MTK_DMA_SIZE];
+	ring->rphys = ring->tphys + MTK_DMA_SIZE * sizeof(*ring->txd);
+	ring->cur_rptr = MTK_DMA_SIZE - 1;
+
+	mtk_dma_clr(hsdma, MTK_HSDMA_GLO, MTK_HSDMA_GLO_DMA);
+	ret = mtk_hsdma_busy_wait(hsdma);
+	if (ret < 0)
+		goto err_free_cb;
+
+	mtk_dma_write(hsdma, MTK_HSDMA_TX_BASE, ring->tphys);
+	mtk_dma_write(hsdma, MTK_HSDMA_TX_CNT, MTK_DMA_SIZE);
+	mtk_dma_write(hsdma, MTK_HSDMA_TX_CPU, ring->cur_tptr);
+	mtk_dma_write(hsdma, MTK_HSDMA_RX_BASE, ring->rphys);
+	mtk_dma_write(hsdma, MTK_HSDMA_RX_CNT, MTK_DMA_SIZE);
+	mtk_dma_write(hsdma, MTK_HSDMA_RX_CPU, ring->cur_rptr);
+	mtk_dma_set(hsdma, MTK_HSDMA_RESET,
+		    MTK_HSDMA_RST_TX | MTK_HSDMA_RST_RX);
+	mtk_dma_clr(hsdma, MTK_HSDMA_RESET,
+		    MTK_HSDMA_RST_TX | MTK_HSDMA_RST_RX);
+	mtk_dma_set(hsdma, MTK_HSDMA_GLO, MTK_HSDMA_GLO_DMA);
+	mtk_dma_set(hsdma, MTK_HSDMA_INT_ENABLE, MTK_HSDMA_INT_RXDONE);
+	mtk_dma_write(hsdma, MTK_HSDMA_DLYINT, MTK_HSDMA_DLYINT_DEFAULT);
+
+	dev_dbg(hsdma2dev(hsdma), "Allocating pchannel done\n");
+
+	return 0;
+
+err_free_cb:
+	kfree(ring->cb);
+
+err_free_dma:
+	dma_free_coherent(hsdma2dev(hsdma),
+			  pc->sz_ring, ring->txd, ring->tphys);
+	return ret;
+}
+
+static void mtk_hsdma_free_pchan(struct mtk_hsdma_device *hsdma,
+				 struct mtk_hsdma_pchan *pc)
+{
+	struct mtk_hsdma_ring *ring = &pc->ring;
+
+	dev_dbg(hsdma2dev(hsdma), "Freeing pchannel\n");
+
+	mtk_dma_clr(hsdma, MTK_HSDMA_GLO, MTK_HSDMA_GLO_DMA);
+
+	mtk_hsdma_busy_wait(hsdma);
+
+	mtk_dma_clr(hsdma, MTK_HSDMA_INT_ENABLE, MTK_HSDMA_INT_RXDONE);
+	mtk_dma_write(hsdma, MTK_HSDMA_TX_BASE, 0);
+	mtk_dma_write(hsdma, MTK_HSDMA_TX_CNT, 0);
+	mtk_dma_write(hsdma, MTK_HSDMA_TX_CPU, 0);
+	mtk_dma_write(hsdma, MTK_HSDMA_RX_BASE, 0);
+	mtk_dma_write(hsdma, MTK_HSDMA_RX_CNT, 0);
+	mtk_dma_write(hsdma, MTK_HSDMA_RX_CPU, MTK_DMA_SIZE - 1);
+
+	mtk_dma_set(hsdma, MTK_HSDMA_RESET,
+		    MTK_HSDMA_RST_TX | MTK_HSDMA_RST_RX);
+	mtk_dma_clr(hsdma, MTK_HSDMA_RESET,
+		    MTK_HSDMA_RST_TX | MTK_HSDMA_RST_RX);
+
+	mtk_dma_set(hsdma, MTK_HSDMA_GLO, MTK_HSDMA_GLO_DMA);
+
+	kfree(ring->cb);
+
+	dma_free_coherent(hsdma2dev(hsdma),
+			  pc->sz_ring, ring->txd, ring->tphys);
+
+	dev_dbg(hsdma2dev(hsdma), "Freeing pchannel done\n");
+}
+
+static int mtk_hsdma_alloc_chan_resources(struct dma_chan *c)
+{
+	struct mtk_hsdma_device *hsdma = to_hsdma_dev(c);
+	struct mtk_hsdma_vchan  *vc = to_hsdma_vchan(c);
+	int ret = 0;
+
+	if (!atomic_read(&hsdma->pc_refcnt))
+		ret = mtk_hsdma_alloc_pchan(hsdma, &hsdma->pc);
+
+	vc->pc = &hsdma->pc;
+	atomic_inc(&hsdma->pc_refcnt);
+	atomic_set(&vc->refcnt, 0);
+
+	return ret;
+}
+
+static void mtk_hsdma_free_chan_resources(struct dma_chan *c)
+{
+	struct mtk_hsdma_device *hsdma = to_hsdma_dev(c);
+	struct mtk_hsdma_vchan  *vc = to_hsdma_vchan(c);
+
+	spin_lock_bh(&hsdma->lock);
+	list_del_init(&vc->node);
+	spin_unlock_bh(&hsdma->lock);
+
+	if (!atomic_dec_and_test(&hsdma->pc_refcnt))
+		return;
+
+	mtk_hsdma_free_pchan(hsdma, vc->pc);
+	vchan_free_chan_resources(to_virt_chan(c));
+}
+
+static int mtk_hsdma_consume_one_vdesc(struct mtk_hsdma_pchan *pc,
+				       struct mtk_hsdma_vdesc *hvd)
+{
+	struct mtk_hsdma_device *hsdma = pc->hsdma;
+	struct mtk_hsdma_ring *ring = &pc->ring;
+	struct mtk_hsdma_pdesc *txd, *rxd;
+	u32 i, tlen;
+	u16 maxfills, prev, old_ptr, handled;
+
+	maxfills = min_t(u32, hvd->num_sgs, atomic_read(&pc->free_count));
+	if (!maxfills)
+		return -ENOSPC;
+
+	hsdma->busy = true;
+	old_ptr = ring->cur_tptr;
+	for (i = 0; i < maxfills ; i++) {
+		tlen = (hvd->len > MTK_HSDMA_MAX_LEN) ?
+		       MTK_HSDMA_MAX_LEN : hvd->len;
+		txd = &ring->txd[ring->cur_tptr];
+		WRITE_ONCE(txd->des1, hvd->src);
+		WRITE_ONCE(txd->des2,
+			   MTK_HSDMA_DESC_LS0 | MTK_HSDMA_DESC_PLEN(tlen));
+		rxd = &ring->rxd[ring->cur_tptr];
+		WRITE_ONCE(rxd->des1, hvd->dest);
+		WRITE_ONCE(rxd->des2, MTK_HSDMA_DESC_PLEN(tlen));
+		ring->cur_tptr = MTK_HSDMA_NEXT_DESP_IDX(ring->cur_tptr,
+							 MTK_DMA_SIZE);
+		hvd->src  += tlen;
+		hvd->dest += tlen;
+		hvd->len  -= tlen;
+		hvd->num_sgs--;
+	}
+
+	prev = MTK_HSDMA_PREV_DESP_IDX(ring->cur_tptr, MTK_DMA_SIZE);
+
+	if (!hvd->len) {
+		ring->cb[prev].vd = &hvd->vd;
+		ring->cb[prev].flags = VDESC_FINISHED;
+	}
+
+	handled = (ring->cur_tptr - old_ptr) & (MTK_DMA_SIZE - 1);
+	atomic_sub(handled, &pc->free_count);
+
+	/*
+	 * Ensue all changes to the ring space flushed before we
+	 * continue.
+	 */
+	wmb();
+	mtk_dma_write(hsdma, MTK_HSDMA_TX_CPU, ring->cur_tptr);
+	return !hvd->len ? 0 : -ENOSPC;
+}
+
+static struct mtk_hsdma_vchan *
+mtk_hsdma_pick_vchan(struct mtk_hsdma_device *hsdma)
+{
+	struct mtk_hsdma_vchan *vc;
+
+	if (hsdma->vc_uncompleted)
+		return hsdma->vc_uncompleted;
+
+	spin_lock(&hsdma->lock);
+	if (list_empty(&hsdma->vc_pending)) {
+		vc = 0;
+	} else {
+		vc = list_first_entry(&hsdma->vc_pending,
+				      struct mtk_hsdma_vchan,
+				      node);
+	}
+	spin_unlock(&hsdma->lock);
+
+	return vc;
+}
+
+static int mtk_hsdma_vc_vd(struct mtk_hsdma_device *hsdma,
+			   struct mtk_hsdma_vchan *vc,
+			   struct virt_dma_desc *vd) {
+	struct mtk_hsdma_vdesc *hvd;
+	int ret;
+
+	hvd = to_hsdma_vdesc(vd);
+
+	spin_lock(&vc->vc.lock);
+	if (!list_empty(&vd->node))
+		list_del_init(&vd->node);
+	spin_unlock(&vc->vc.lock);
+
+	/* Mapping the descriptor into the ring space of HSDMA */
+	ret = mtk_hsdma_consume_one_vdesc(vc->pc, hvd);
+
+	/*
+	 * Remember vc and vd if out of space in the ring happened
+	 * which will be handled firstly in the next schedule.
+	 */
+	if (ret < 0) {
+		hsdma->vc_uncompleted = vc;
+		vc->vd_uncompleted = vd;
+		return ret;
+	}
+
+	spin_lock(&vc->vc.lock);
+	vd = vchan_next_desc(&vc->vc);
+	spin_unlock(&vc->vc.lock);
+
+	/*
+	 * Re-queue the current channel to the pending list if pending
+	 * descriptors on the current channel are still available.
+	 */
+	spin_lock(&hsdma->lock);
+	if (!list_empty(&vc->node)) {
+		if (!vd)
+			list_del_init(&vc->node);
+		else
+			list_move_tail(&vc->node, &hsdma->vc_pending);
+	}
+	spin_unlock(&hsdma->lock);
+
+	return 0;
+}
+
+static void mtk_hsdma_schedule(unsigned long data)
+{
+	struct mtk_hsdma_device *hsdma = (struct mtk_hsdma_device *)data;
+	struct mtk_hsdma_vchan *vc;
+	struct virt_dma_desc *vd;
+	bool vc_removed;
+
+	vc = mtk_hsdma_pick_vchan(hsdma);
+	if (!vc)
+		return;
+
+	if (!vc->vd_uncompleted) {
+		spin_lock(&vc->vc.lock);
+		vd = vchan_next_desc(&vc->vc);
+		spin_unlock(&vc->vc.lock);
+	} else {
+		vd = vc->vd_uncompleted;
+		atomic_dec(&vc->refcnt);
+	}
+
+	hsdma->vc_uncompleted = 0;
+	vc->vd_uncompleted = 0;
+
+	while (vc && vd) {
+		spin_lock(&hsdma->lock);
+		vc_removed = list_empty(&vc->node);
+		/*
+		 * Refcnt increases for the indication that one more descriptor
+		 * is ready for the process if the corresponding channel is
+		 * active.
+		 */
+		if (!vc_removed)
+			atomic_inc(&vc->refcnt);
+		spin_unlock(&hsdma->lock);
+
+		/*
+		 * One descriptor is the unit for each round consuming and the
+		 * returned negative value for mtk_hsdma_vc_vd occurs if it's
+		 * out of space in the ring of HSDMA.
+		 */
+		if (!vc_removed && mtk_hsdma_vc_vd(hsdma, vc, vd) < 0)
+			break;
+
+		/* Switch to the next channel waiting on the pending list */
+		vc = mtk_hsdma_pick_vchan(hsdma);
+		if (vc) {
+			spin_lock(&vc->vc.lock);
+			vd = vchan_next_desc(&vc->vc);
+			spin_unlock(&vc->vc.lock);
+		}
+	}
+}
+
+static void mtk_hsdma_housekeeping(unsigned long data)
+{
+	struct mtk_hsdma_device *hsdma = (struct mtk_hsdma_device *)data;
+	struct mtk_hsdma_vchan *hvc;
+	struct mtk_hsdma_pchan *pc;
+	struct mtk_hsdma_pdesc *rxd;
+	struct mtk_hsdma_cb *cb;
+	struct virt_dma_chan *vc;
+	struct virt_dma_desc *vd, *tmp;
+	u16 next;
+	u32 status;
+	LIST_HEAD(comp);
+
+	pc = &hsdma->pc;
+
+	status = mtk_dma_read(hsdma, MTK_HSDMA_INT_STATUS);
+	mtk_dma_write(hsdma, MTK_HSDMA_INT_STATUS, status);
+
+	while (1) {
+		next = MTK_HSDMA_NEXT_DESP_IDX(pc->ring.cur_rptr,
+					       MTK_DMA_SIZE);
+		rxd = &pc->ring.rxd[next];
+		cb = &pc->ring.cb[next];
+
+		/*
+		 * If no MTK_HSDMA_DESC_DDONE is specified in rxd->des2, that
+		 * means 1) the hardware doesn't finish the data moving yet
+		 * for the corresponding descriptor or 2) the hardware meets
+		 * the end of data moved.
+		 */
+		if (!(rxd->des2 & MTK_HSDMA_DESC_DDONE))
+			break;
+
+		if (IS_VDESC_FINISHED(cb->flags))
+			list_add_tail(&cb->vd->node, &comp);
+
+		WRITE_ONCE(rxd->des1, 0);
+		WRITE_ONCE(rxd->des2, 0);
+		cb->flags = 0;
+		pc->ring.cur_rptr = next;
+		atomic_inc(&pc->free_count);
+	}
+
+	/*
+	 * Ensure all changes to all the descriptors in ring space being
+	 * flushed before we continue.
+	 */
+	wmb();
+	mtk_dma_write(hsdma, MTK_HSDMA_RX_CPU, pc->ring.cur_rptr);
+	mtk_dma_set(hsdma, MTK_HSDMA_INT_ENABLE, MTK_HSDMA_INT_RXDONE);
+
+	list_for_each_entry_safe(vd, tmp, &comp, node) {
+		vc = to_virt_chan(vd->tx.chan);
+		spin_lock(&vc->lock);
+		vchan_cookie_complete(vd);
+		spin_unlock(&vc->lock);
+
+		hvc = to_hsdma_vchan(vd->tx.chan);
+		atomic_dec(&hvc->refcnt);
+	}
+
+	/*
+	 * An indication to HSDMA as not busy allows the user context to start
+	 * the next HSDMA scheduler.
+	 */
+	if (atomic_read(&pc->free_count) == MTK_DMA_SIZE - 1)
+		hsdma->busy = false;
+
+	tasklet_schedule(&hsdma->scheduler);
+}
+
+static irqreturn_t mtk_hsdma_chan_irq(int irq, void *devid)
+{
+	struct mtk_hsdma_device *hsdma = devid;
+
+	tasklet_schedule(&hsdma->housekeeping);
+
+	/* Interrupt is enabled until the housekeeping tasklet is completed */
+	mtk_dma_clr(hsdma, MTK_HSDMA_INT_ENABLE,
+		    MTK_HSDMA_INT_RXDONE);
+
+	return IRQ_HANDLED;
+}
+
+static void mtk_hsdma_issue_pending(struct dma_chan *c)
+{
+	struct mtk_hsdma_device *hsdma = to_hsdma_dev(c);
+	struct mtk_hsdma_vchan *vc = to_hsdma_vchan(c);
+	bool issued;
+
+	spin_lock_bh(&vc->vc.lock);
+	issued = vchan_issue_pending(&vc->vc);
+	spin_unlock_bh(&vc->vc.lock);
+
+	spin_lock_bh(&hsdma->lock);
+	if (list_empty(&vc->node))
+		list_add_tail(&vc->node, &hsdma->vc_pending);
+	spin_unlock_bh(&hsdma->lock);
+
+	if (issued && !hsdma->busy)
+		tasklet_schedule(&hsdma->scheduler);
+}
+
+static struct dma_async_tx_descriptor *mtk_hsdma_prep_dma_memcpy(
+	struct dma_chan *c, dma_addr_t dest,
+	dma_addr_t src, size_t len, unsigned long flags)
+{
+	struct mtk_hsdma_vdesc *hvd;
+
+	hvd = kzalloc(sizeof(*hvd), GFP_NOWAIT);
+	if (!hvd)
+		return NULL;
+
+	hvd->len = len;
+	hvd->src = src;
+	hvd->dest = dest;
+	hvd->num_sgs = DIV_ROUND_UP(len, MTK_HSDMA_MAX_LEN);
+
+	return vchan_tx_prep(to_virt_chan(c), &hvd->vd, flags);
+}
+
+static int mtk_hsdma_terminate_all(struct dma_chan *c)
+{
+	struct mtk_hsdma_device *hsdma = to_hsdma_dev(c);
+	struct virt_dma_chan *vc = to_virt_chan(c);
+	struct mtk_hsdma_vchan *hvc = to_hsdma_vchan(c);
+	LIST_HEAD(head);
+
+	/*
+	 * Hardware doesn't support abort, so remove the channel from the
+	 * pendling list and wait for those data for the channel already in
+	 * the ring space of HSDMA all transferred done.
+	 */
+	spin_lock_bh(&hsdma->lock);
+	list_del_init(&hvc->node);
+	spin_unlock_bh(&hsdma->lock);
+
+	while (atomic_read(&hvc->refcnt)) {
+		dev_dbg_ratelimited(chan2dev(c), "%s %d %d\n",
+				    __func__, __LINE__,
+				    (u32)atomic_read(&hvc->refcnt));
+		usleep_range(100, 200);
+	}
+
+	spin_lock_bh(&vc->lock);
+	vchan_get_all_descriptors(vc, &head);
+	spin_unlock_bh(&vc->lock);
+	vchan_dma_desc_free_list(vc, &head);
+
+	return 0;
+}
+
+static void mtk_hsdma_synchronize(struct dma_chan *c)
+{
+	struct virt_dma_chan *vc = to_virt_chan(c);
+
+	vchan_synchronize(vc);
+}
+
+static int mtk_hsdma_hw_init(struct mtk_hsdma_device *hsdma)
+{
+	int ret;
+
+	ret = clk_prepare_enable(hsdma->clk);
+	if (ret < 0) {
+		dev_err(hsdma2dev(hsdma),
+			"clk_prepare_enable failed: %d\n", ret);
+		return ret;
+	}
+
+	mtk_dma_write(hsdma, MTK_HSDMA_INT_ENABLE, 0);
+	mtk_dma_write(hsdma, MTK_HSDMA_GLO, MTK_HSDMA_GLO_DEFAULT);
+
+	return 0;
+}
+
+static int mtk_hsdma_hw_deinit(struct mtk_hsdma_device *hsdma)
+{
+	mtk_dma_write(hsdma, MTK_HSDMA_INT_ENABLE, 0);
+	mtk_dma_write(hsdma, MTK_HSDMA_GLO, 0);
+
+	clk_disable_unprepare(hsdma->clk);
+
+	return 0;
+}
+
+static const struct of_device_id mtk_dma_match[] = {
+	{ .compatible = "mediatek,mt7623-hsdma" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mtk_dma_match);
+
+static int mtk_dma_probe(struct platform_device *pdev)
+{
+	struct mtk_hsdma_device *hsdma;
+	struct mtk_hsdma_vchan *vc;
+	struct dma_device *dd;
+	struct resource *res;
+	int i, ret;
+
+	hsdma = devm_kzalloc(&pdev->dev, sizeof(*hsdma), GFP_KERNEL);
+	if (!hsdma)
+		return -ENOMEM;
+
+	dd = &hsdma->ddev;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	hsdma->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(hsdma->base))
+		return PTR_ERR(hsdma->base);
+
+	hsdma->clk = devm_clk_get(&pdev->dev, "hsdma");
+	if (IS_ERR(hsdma->clk)) {
+		dev_err(&pdev->dev, "Error: Missing controller clock\n");
+		return PTR_ERR(hsdma->clk);
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "No irq resource for %s\n",
+			dev_name(&pdev->dev));
+		return -EINVAL;
+	}
+	hsdma->irq = res->start;
+
+	INIT_LIST_HEAD(&hsdma->vc_pending);
+	spin_lock_init(&hsdma->lock);
+	atomic_set(&hsdma->pc_refcnt, 0);
+	dma_cap_set(DMA_MEMCPY, dd->cap_mask);
+
+	dd->copy_align = MTK_HSDMA_ALIGN_SIZE;
+	dd->device_alloc_chan_resources = mtk_hsdma_alloc_chan_resources;
+	dd->device_free_chan_resources = mtk_hsdma_free_chan_resources;
+	dd->device_tx_status = dma_cookie_status;
+	dd->device_issue_pending = mtk_hsdma_issue_pending;
+	dd->device_prep_dma_memcpy = mtk_hsdma_prep_dma_memcpy;
+	dd->device_terminate_all = mtk_hsdma_terminate_all;
+	dd->device_synchronize = mtk_hsdma_synchronize;
+	dd->src_addr_widths = MTK_HSDMA_DMA_BUSWIDTHS;
+	dd->dst_addr_widths = MTK_HSDMA_DMA_BUSWIDTHS;
+	dd->directions = BIT(DMA_MEM_TO_MEM);
+	dd->residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR;
+	dd->dev = &pdev->dev;
+	INIT_LIST_HEAD(&dd->channels);
+
+	hsdma->dma_requests = MTK_DMA_MAX_VCHANNELS;
+	if (pdev->dev.of_node && of_property_read_u32(pdev->dev.of_node,
+						      "dma-requests",
+						      &hsdma->dma_requests)) {
+		dev_info(&pdev->dev,
+			 "Using %u as missing dma-requests property\n",
+			 MTK_DMA_MAX_VCHANNELS);
+	}
+
+	hsdma->vc = devm_kcalloc(&pdev->dev, hsdma->dma_requests,
+				 sizeof(*hsdma->vc), GFP_KERNEL);
+	if (!hsdma->vc)
+		return -ENOMEM;
+
+	for (i = 0; i < hsdma->dma_requests; i++) {
+		vc = &hsdma->vc[i];
+		vc->vc.desc_free = mtk_hsdma_vdesc_free;
+		vchan_init(&vc->vc, dd);
+		INIT_LIST_HEAD(&vc->node);
+	}
+
+	ret = dma_async_device_register(dd);
+	if (ret)
+		return ret;
+
+	ret = of_dma_controller_register(pdev->dev.of_node,
+					 of_dma_xlate_by_chan_id, hsdma);
+	if (ret < 0) {
+		dev_err(&pdev->dev,
+			"Mediatek HSDMA OF registration failed %d\n", ret);
+		goto err_unregister;
+	}
+
+	mtk_hsdma_hw_init(hsdma);
+
+	tasklet_init(&hsdma->housekeeping, mtk_hsdma_housekeeping,
+		     (unsigned long)hsdma);
+	tasklet_init(&hsdma->scheduler, mtk_hsdma_schedule,
+		     (unsigned long)hsdma);
+
+	ret = devm_request_irq(&pdev->dev, hsdma->irq,
+			       mtk_hsdma_chan_irq, 0,
+			       dev_name(&pdev->dev), hsdma);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"request_irq failed with err %d channel %d\n",
+			ret, i);
+		goto err_unregister;
+	}
+
+	platform_set_drvdata(pdev, hsdma);
+
+	dev_info(&pdev->dev, "Mediatek HSDMA driver registered\n");
+
+	return 0;
+
+err_unregister:
+	dma_async_device_unregister(dd);
+
+	return ret;
+}
+
+static int mtk_dma_remove(struct platform_device *pdev)
+{
+	struct mtk_hsdma_device *hsdma = platform_get_drvdata(pdev);
+
+	of_dma_controller_free(pdev->dev.of_node);
+	dma_async_device_unregister(&hsdma->ddev);
+
+	tasklet_kill(&hsdma->scheduler);
+	tasklet_kill(&hsdma->housekeeping);
+
+	mtk_hsdma_hw_deinit(hsdma);
+
+	return 0;
+}
+
+static struct platform_driver mtk_dma_driver = {
+	.probe		= mtk_dma_probe,
+	.remove		= mtk_dma_remove,
+	.driver = {
+		.name		= MTK_DMA_DEV,
+		.of_match_table	= mtk_dma_match,
+	},
+};
+module_platform_driver(mtk_dma_driver);
+
+MODULE_DESCRIPTION("Mediatek High-Speed DMA Controller Driver");
+MODULE_AUTHOR("Sean Wang <sean.wang at mediatek.com>");
+MODULE_LICENSE("GPL");
-- 
2.7.4




More information about the linux-arm-kernel mailing list