[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