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

-- 
You received this message because you are subscribed to the Google Groups 
"linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to linux-sunxi+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to