[RFC 1/2] mtd: nand: sunxi: Add RX DMA support

Roy Spliet r.spliet at ultimaker.com
Fri Jun 12 04:38:48 PDT 2015


Replace PIO readout with DMA when supported. This contains both a direct
DMA method and one using a bounce buffer. PIO is still the preferred fall-back.
Follow-up patches should implement both tx and the nand-component's
"page access" mode, in which hardware automatically validates the ECC checksum.
---
 drivers/mtd/nand/sunxi_nand.c | 192 +++++++++++++++++++++++++++++++++++-------
 1 file changed, 163 insertions(+), 29 deletions(-)

diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/sunxi_nand.c
index 26df48f..5095a32 100644
--- a/drivers/mtd/nand/sunxi_nand.c
+++ b/drivers/mtd/nand/sunxi_nand.c
@@ -23,6 +23,7 @@
  */
 
 #include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
 #include <linux/slab.h>
 #include <linux/module.h>
 #include <linux/moduleparam.h>
@@ -250,6 +251,8 @@ static inline struct sunxi_nand_chip *to_sunxi_nand(struct nand_chip *nand)
  * @mod_clk:		NAND Controller mod clock
  * @assigned_cs:	bitmask describing already assigned CS lines
  * @clk_rate:		NAND controller current clock rate
+ * @dmach		DMA channel for RX and TX transfers
+ * @dma_complete	Completion object to wait for DMA transfer complete
  * @chips:		a list containing all the NAND chips attached to
  *			this NAND controller
  * @complete:		a completion object used to wait for NAND
@@ -263,8 +266,14 @@ struct sunxi_nfc {
 	struct clk *mod_clk;
 	unsigned long assigned_cs;
 	unsigned long clk_rate;
+	struct dma_chan *dmach;
+	struct completion dma_complete;
+	void *dma_buf;
+	dma_addr_t dma_buf_phy;
 	struct list_head chips;
 	struct completion complete;
+
+	void (*rx)(struct sunxi_nfc *nfc, u32 cmd, void *from, size_t cnt);
 };
 
 static inline struct sunxi_nfc *to_sunxi_nfc(struct nand_hw_control *ctrl)
@@ -378,6 +387,126 @@ static int sunxi_nfc_dev_ready(struct mtd_info *mtd)
 	return ret;
 }
 
+static void sunxi_nfc_dma_irq_callback(void *param)
+{
+	struct sunxi_nfc *nfc = param;
+
+	complete(&nfc->dma_complete);
+}
+
+static void sunxi_nfc_rx_pio(struct sunxi_nfc *nfc, u32 cmd, void *buf,
+		size_t cnt)
+{
+	int ret;
+
+	writel(cmd, nfc->regs + NFC_REG_CMD);
+
+	ret = sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
+	if (ret)
+		return;
+
+	if (buf)
+		memcpy_fromio(buf, nfc->regs + NFC_RAM0_BASE, cnt);
+
+	return;
+}
+
+static int sunxi_nfc_rx_dma_transfer(struct sunxi_nfc *nfc, u32 cmd,
+		dma_addr_t dma_addr, size_t cnt)
+{
+	u32 ctl;
+	int ret = 0;
+	dma_cookie_t dma_cookie;
+	struct dma_async_tx_descriptor *desc = NULL;
+
+	ctl = readl(nfc->regs + NFC_REG_CTL);
+
+	ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
+	if (ret)
+		return -EIO;
+
+	writel(ctl | NFC_RAM_METHOD, nfc->regs + NFC_REG_CTL);
+
+	desc = dmaengine_prep_slave_single(nfc->dmach, dma_addr, cnt,
+					DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);
+
+	desc->callback = sunxi_nfc_dma_irq_callback;
+	desc->callback_param = nfc;
+
+	dma_cookie = dmaengine_submit(desc);
+	if (dma_cookie < 0) {
+		dev_dbg(nfc->dev, "DMA cookie error %d\n", dma_cookie);
+		ret = -EIO;
+		goto exit;
+	}
+
+	dma_async_issue_pending(nfc->dmach);
+
+	cmd |= NFC_WAIT_FLAG;
+	writel(cmd, nfc->regs + NFC_REG_CMD);
+
+	if (sunxi_nfc_wait_int(nfc, NFC_DMA_INT_FLAG, 0)) {
+		dev_dbg(nfc->dev, "DMA transfer timeout - NFC fifo\n");
+		dmaengine_terminate_all(nfc->dmach);
+		ret = -EIO;
+		goto exit;
+	}
+
+	if (!wait_for_completion_timeout(&nfc->dma_complete,
+				 msecs_to_jiffies(NFC_DEFAULT_TIMEOUT_MS))) {
+		dev_dbg(nfc->dev, "DMA transfer timeout\n");
+		dmaengine_terminate_all(nfc->dmach);
+		ret = -EIO;
+	}
+
+exit:
+	writel(ctl, nfc->regs + NFC_REG_CTL);
+	return ret;
+}
+
+static void sunxi_nfc_rx_dma(struct sunxi_nfc *nfc, u32 cmd, void *buf,
+		size_t cnt)
+{
+	dma_addr_t dma_addr;
+
+	/*
+	 * TODO: Sunxi DMA doesn't want to co-operate with transfers of size
+	 * not a multiple of 4B.
+	 */
+	if (!buf || cnt & 0x3) {
+		sunxi_nfc_rx_pio(nfc, cmd, buf, cnt);
+		return;
+	}
+
+	reinit_completion(&nfc->dma_complete);
+
+	if (virt_addr_valid(buf)) {
+		/* Direct DMA */
+		dma_addr = dma_map_single(nfc->dev, buf, cnt, DMA_FROM_DEVICE);
+		if (!dma_mapping_error(nfc->dev, dma_addr)) {
+			if (!sunxi_nfc_rx_dma_transfer(nfc, cmd, dma_addr,
+							cnt)) {
+				dma_unmap_single(nfc->dev, dma_addr, cnt,
+						DMA_FROM_DEVICE);
+				return;
+			}
+		}
+	}
+
+	if (nfc->dma_buf && cnt <= 8192) {
+		/* fall back to intermediate buffer */
+		dma_addr = nfc->dma_buf_phy;
+		if (!sunxi_nfc_rx_dma_transfer(nfc, cmd, dma_addr, cnt)) {
+			memcpy(buf, nfc->dma_buf, cnt);
+			return;
+		}
+	}
+
+	/* Fall back to PIO */
+	sunxi_nfc_rx_pio(nfc, cmd, buf, cnt);
+	return;
+}
+
 static void sunxi_nfc_select_chip(struct mtd_info *mtd, int chip)
 {
 	struct nand_chip *nand = mtd->priv;
@@ -431,7 +560,6 @@ static void sunxi_nfc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
 	int ret;
 	int cnt;
 	int offs = 0;
-	u32 tmp;
 
 	while (len > offs) {
 		cnt = min(len - offs, NFC_SRAM_SIZE);
@@ -441,16 +569,9 @@ static void sunxi_nfc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
 			break;
 
 		writel(cnt, nfc->regs + NFC_REG_CNT);
-		tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD;
-		writel(tmp, nfc->regs + NFC_REG_CMD);
 
-		ret = sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
-		if (ret)
-			break;
-
-		if (buf)
-			memcpy_fromio(buf + offs, nfc->regs + NFC_RAM0_BASE,
-				      cnt);
+		nfc->rx(nfc, NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD,
+			buf ? buf + offs : NULL, cnt);
 		offs += cnt;
 	}
 }
@@ -567,15 +688,8 @@ static int sunxi_nfc_hw_ecc_read_page(struct mtd_info *mtd,
 		if (ret)
 			return ret;
 
-		tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | (1 << 30);
-		writel(tmp, nfc->regs + NFC_REG_CMD);
-
-		ret = sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
-		if (ret)
-			return ret;
-
-		memcpy_fromio(buf + (i * ecc->size),
-			      nfc->regs + NFC_RAM0_BASE, ecc->size);
+		nfc->rx(nfc, NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | (1 << 30),
+			buf + (i * ecc->size), ecc->size);
 
 		if (readl(nfc->regs + NFC_REG_ECC_ST) & 0x1) {
 			mtd->ecc_stats.failed++;
@@ -701,7 +815,6 @@ static int sunxi_nfc_hw_syndrome_ecc_read_page(struct mtd_info *mtd,
 	unsigned int max_bitflips = 0;
 	uint8_t *oob = chip->oob_poi;
 	int offset = 0;
-	int ret;
 	int cnt;
 	u32 tmp;
 	int i;
@@ -716,14 +829,8 @@ static int sunxi_nfc_hw_syndrome_ecc_read_page(struct mtd_info *mtd,
 	for (i = 0; i < ecc->steps; i++) {
 		chip->read_buf(mtd, NULL, ecc->size);
 
-		tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | (1 << 30);
-		writel(tmp, nfc->regs + NFC_REG_CMD);
-
-		ret = sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
-		if (ret)
-			return ret;
-
-		memcpy_fromio(buf, nfc->regs + NFC_RAM0_BASE, ecc->size);
+		nfc->rx(nfc, NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | (1 << 30),
+			buf, ecc->size);
 		buf += ecc->size;
 		offset += ecc->size;
 
@@ -1398,6 +1505,7 @@ static int sunxi_nfc_probe(struct platform_device *pdev)
 	struct device *dev = &pdev->dev;
 	struct resource *r;
 	struct sunxi_nfc *nfc;
+	struct dma_slave_config slave_config = {};
 	int irq;
 	int ret;
 
@@ -1452,16 +1560,39 @@ static int sunxi_nfc_probe(struct platform_device *pdev)
 	if (ret)
 		goto out_mod_clk_unprepare;
 
+	nfc->dmach = dma_request_slave_channel(dev, "rx-tx");
+	if (nfc->dmach) {
+		dev_info(dev, "Using DMA channel %d @ %08x", nfc->dmach->chan_id, r->start + NFC_REG_IO_DATA);
+		nfc->rx = sunxi_nfc_rx_dma;
+
+		slave_config.src_addr = r->start + NFC_REG_IO_DATA;
+		slave_config.dst_addr = r->start + NFC_REG_IO_DATA;
+		slave_config.src_addr_width = 4;
+		slave_config.dst_addr_width = 4;
+		slave_config.src_maxburst = 1;
+		slave_config.dst_maxburst = 1;
+		dmaengine_slave_config(nfc->dmach, &slave_config);
+		nfc->dma_buf = dma_alloc_coherent(dev, 8 * 1024,
+						&nfc->dma_buf_phy, GFP_KERNEL);
+		init_completion(&nfc->dma_complete);
+	} else {
+		dev_info(dev, "Using PIO");
+		nfc->rx = sunxi_nfc_rx_pio;
+	}
+
 	platform_set_drvdata(pdev, nfc);
 
 	ret = sunxi_nand_chips_init(dev, nfc);
 	if (ret) {
 		dev_err(dev, "failed to init nand chips\n");
-		goto out_mod_clk_unprepare;
+		goto out_mod_dma_unprepare;
 	}
 
 	return 0;
 
+out_mod_dma_unprepare:
+	dma_release_channel(nfc->dmach);
+	nfc->dmach = NULL;
 out_mod_clk_unprepare:
 	clk_disable_unprepare(nfc->mod_clk);
 out_ahb_clk_unprepare:
@@ -1476,6 +1607,9 @@ static int sunxi_nfc_remove(struct platform_device *pdev)
 
 	sunxi_nand_chips_cleanup(nfc);
 
+	if (nfc->dmach)
+		dma_release_channel(nfc->dmach);
+
 	return 0;
 }
 
-- 
2.4.2


-- 


IMAGINE IT >> MAKE IT

Meet us online at Twitter <http://twitter.com/ultimaker>, Facebook 
<http://facebook.com/ultimaker>, Google+ <http://google.com/+Ultimaker>

www.ultimaker.com



More information about the linux-mtd mailing list