[PATCH v3] mtd: nand: add mrvl-nand driver

Robert Jarzmik robert.jarzmik at free.fr
Thu Jan 8 12:22:01 PST 2015


The driver is taken from the Linux kernel, with the following changes :
 - all DMA removed
 - all asynchronous handling removed, including the interrupt handler,
   and the asynchronous state handling
 - pxa armada support removed

Most the kernel structure was kept, to ease up future fixes integration
from the kernel driver.

The driver is tested on a pxa3xx system development
board (aka. zylonite), and reading, writing, erasing, and bad block
management were tested.

Signed-off-by: Robert Jarzmik <robert.jarzmik at free.fr>
---
Since v1: renamed driver per Ezequiel's suggestion
          removed the detection part, let nand_scan_ident() do the job
Since v2: fix 8 bus width (Sascha)
          improve use_default usage in timings setting code
---
 drivers/mtd/nand/Kconfig              |    7 +
 drivers/mtd/nand/Makefile             |    1 +
 drivers/mtd/nand/nand_mrvl_nfc.c      | 1008 +++++++++++++++++++++++++++++++++
 include/platform_data/mtd-nand-mrvl.h |   79 +++
 4 files changed, 1095 insertions(+)
 create mode 100644 drivers/mtd/nand/nand_mrvl_nfc.c
 create mode 100644 include/platform_data/mtd-nand-mrvl.h

diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index c345847..a75540b 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -97,6 +97,13 @@ config NAND_ORION
 	help
 	  Support for the Orion NAND controller, present in Kirkwood SoCs.
 
+config NAND_MRVL_NFC
+	bool
+	prompt "Marvell NAND driver"
+	depends on ARCH_PXA3XX
+	help
+	  Support for the PXA3xx NAND controller, present in pxa3xx SoCs.
+
 config NAND_ATMEL
 	bool
 	prompt "Atmel (AT91SAM9xxx) NAND driver"
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index 02dacde..a0b3198 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_NAND_IMX)			+= nand_imx.o
 obj-$(CONFIG_NAND_IMX_BBM)		+= nand_imx_bbm.o
 obj-$(CONFIG_NAND_OMAP_GPMC)		+= nand_omap_gpmc.o nand_omap_bch_decoder.o
 obj-$(CONFIG_NAND_ORION)		+= nand_orion.o
+obj-$(CONFIG_NAND_MRVL_NFC)		+= nand_mrvl_nfc.o
 obj-$(CONFIG_NAND_ATMEL)		+= atmel_nand.o
 obj-$(CONFIG_NAND_S3C24XX)		+= nand_s3c24xx.o
 pbl-$(CONFIG_NAND_S3C24XX)		+= nand_s3c24xx.o
diff --git a/drivers/mtd/nand/nand_mrvl_nfc.c b/drivers/mtd/nand/nand_mrvl_nfc.c
new file mode 100644
index 0000000..f312529
--- /dev/null
+++ b/drivers/mtd/nand/nand_mrvl_nfc.c
@@ -0,0 +1,1008 @@
+/*
+ * drivers/mtd/nand/mrvl_nand.c
+ *
+ * Copyright © 2005 Intel Corporation
+ * Copyright © 2006 Marvell International Ltd.
+ * Copyright (C) 2014 Robert Jarzmik
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * See Documentation/mtd/nand/pxa3xx-nand.txt for more details.
+ */
+#include <common.h>
+
+#include <driver.h>
+#include <dma/apbh-dma.h>
+#include <errno.h>
+#include <clock.h>
+#include <init.h>
+#include <io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/types.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <mach/clock.h>
+#include <malloc.h>
+#include <of_mtd.h>
+#include <stmp-device.h>
+
+#include <platform_data/mtd-nand-mrvl.h>
+
+#define	CHIP_DELAY_TIMEOUT_US	500000
+#define PAGE_CHUNK_SIZE		(2048)
+
+/*
+ * Define a buffer size for the initial command that detects the flash device:
+ * STATUS, READID and PARAM. The largest of these is the PARAM command,
+ * needing 256 bytes.
+ */
+#define INIT_BUFFER_SIZE	256
+
+/* registers and bit definitions */
+#define NDCR		(0x00) /* Control register */
+#define NDTR0CS0	(0x04) /* Timing Parameter 0 for CS0 */
+#define NDTR1CS0	(0x0C) /* Timing Parameter 1 for CS0 */
+#define NDSR		(0x14) /* Status Register */
+#define NDPCR		(0x18) /* Page Count Register */
+#define NDBDR0		(0x1C) /* Bad Block Register 0 */
+#define NDBDR1		(0x20) /* Bad Block Register 1 */
+#define NDECCCTRL	(0x28) /* ECC control */
+#define NDDB		(0x40) /* Data Buffer */
+#define NDCB0		(0x48) /* Command Buffer0 */
+#define NDCB1		(0x4C) /* Command Buffer1 */
+#define NDCB2		(0x50) /* Command Buffer2 */
+
+#define NDCR_SPARE_EN		(0x1 << 31)
+#define NDCR_ECC_EN		(0x1 << 30)
+#define NDCR_DMA_EN		(0x1 << 29)
+#define NDCR_ND_RUN		(0x1 << 28)
+#define NDCR_DWIDTH_C		(0x1 << 27)
+#define NDCR_DWIDTH_M		(0x1 << 26)
+#define NDCR_PAGE_SZ		(0x1 << 24)
+#define NDCR_NCSX		(0x1 << 23)
+#define NDCR_ND_MODE		(0x3 << 21)
+#define NDCR_NAND_MODE		(0x0)
+#define NDCR_CLR_PG_CNT		(0x1 << 20)
+#define NDCR_STOP_ON_UNCOR	(0x1 << 19)
+#define NDCR_RD_ID_CNT_MASK	(0x7 << 16)
+#define NDCR_RD_ID_CNT(x)	(((x) << 16) & NDCR_RD_ID_CNT_MASK)
+
+#define NDCR_RA_START		(0x1 << 15)
+#define NDCR_PG_PER_BLK		(0x1 << 14)
+#define NDCR_ND_ARB_EN		(0x1 << 12)
+#define NDCR_INT_MASK           (0xFFF)
+
+#define NDSR_MASK		(0xfff)
+#define NDSR_ERR_CNT_OFF	(16)
+#define NDSR_ERR_CNT_MASK       (0x1f)
+#define NDSR_ERR_CNT(sr)	((sr >> NDSR_ERR_CNT_OFF) & NDSR_ERR_CNT_MASK)
+#define NDSR_RDY                (0x1 << 12)
+#define NDSR_FLASH_RDY          (0x1 << 11)
+#define NDSR_CS0_PAGED		(0x1 << 10)
+#define NDSR_CS1_PAGED		(0x1 << 9)
+#define NDSR_CS0_CMDD		(0x1 << 8)
+#define NDSR_CS1_CMDD		(0x1 << 7)
+#define NDSR_CS0_BBD		(0x1 << 6)
+#define NDSR_CS1_BBD		(0x1 << 5)
+#define NDSR_UNCORERR		(0x1 << 4)
+#define NDSR_CORERR		(0x1 << 3)
+#define NDSR_WRDREQ		(0x1 << 2)
+#define NDSR_RDDREQ		(0x1 << 1)
+#define NDSR_WRCMDREQ		(0x1)
+
+#define NDCB0_LEN_OVRD		(0x1 << 28)
+#define NDCB0_ST_ROW_EN         (0x1 << 26)
+#define NDCB0_AUTO_RS		(0x1 << 25)
+#define NDCB0_CSEL		(0x1 << 24)
+#define NDCB0_EXT_CMD_TYPE_MASK	(0x7 << 29)
+#define NDCB0_EXT_CMD_TYPE(x)	(((x) << 29) & NDCB0_EXT_CMD_TYPE_MASK)
+#define NDCB0_CMD_TYPE_MASK	(0x7 << 21)
+#define NDCB0_CMD_TYPE(x)	(((x) << 21) & NDCB0_CMD_TYPE_MASK)
+#define NDCB0_NC		(0x1 << 20)
+#define NDCB0_DBC		(0x1 << 19)
+#define NDCB0_ADDR_CYC_MASK	(0x7 << 16)
+#define NDCB0_ADDR_CYC(x)	(((x) << 16) & NDCB0_ADDR_CYC_MASK)
+#define NDCB0_CMD2_MASK		(0xff << 8)
+#define NDCB0_CMD1_MASK		(0xff)
+#define NDCB0_ADDR_CYC_SHIFT	(16)
+
+#define EXT_CMD_TYPE_DISPATCH	6 /* Command dispatch */
+#define EXT_CMD_TYPE_NAKED_RW	5 /* Naked read or Naked write */
+#define EXT_CMD_TYPE_READ	4 /* Read */
+#define EXT_CMD_TYPE_DISP_WR	4 /* Command dispatch with write */
+#define EXT_CMD_TYPE_FINAL	3 /* Final command */
+#define EXT_CMD_TYPE_LAST_RW	1 /* Last naked read/write */
+#define EXT_CMD_TYPE_MONO	0 /* Monolithic read/write */
+
+/* macros for registers read/write */
+#define nand_writel(host, off, val) \
+	_nand_writel(__func__, __LINE__, (host), (off), (val))
+
+#define nand_writesl(host, off, buf, nbbytes)		\
+	writesl((host)->mmio_base + (off), buf, nbbytes)
+
+#define nand_readl(host, off)	\
+	_nand_readl(__func__, __LINE__, (host), (off))
+
+#define nand_readsl(host, off, buf, nbbytes)		\
+	readsl((host)->mmio_base + (off), buf, nbbytes)
+
+struct mrvl_nand_host {
+	struct mtd_info		mtd;
+	struct nand_chip	chip;
+	struct mtd_partition	*parts;
+	struct device_d		*dev;
+
+	/* calculated from mrvl_nand_flash data */
+	unsigned int		col_addr_cycles;
+	unsigned int		row_addr_cycles;
+	size_t			read_id_bytes;
+
+	void __iomem		*mmio_base;
+
+	unsigned int		buf_start;
+	unsigned int		buf_count;
+	unsigned int		buf_size;
+
+	unsigned char		*data_buff;
+
+	int			keep_config;
+	int			ecc_strength;
+	int			ecc_step;
+
+	int			cs;		/* selected chip 0/1 */
+	int			use_ecc;	/* use HW ECC ? */
+	int			use_spare;	/* use spare ? */
+	int			flash_bbt;
+
+	unsigned int		data_size;	/* data to be read from FIFO */
+	unsigned int		chunk_size;	/* split commands chunk size */
+	unsigned int		oob_size;
+	unsigned int		spare_size;
+	unsigned int		ecc_size;
+	unsigned int		max_bitflips;
+	int			cmd_ongoing;
+
+	/* cached register value */
+	uint32_t		reg_ndcr;
+	uint32_t		ndtr0cs0_chip0;
+	uint32_t		ndtr1cs0_chip0;
+	uint32_t		ndtr0cs0_chip1;
+	uint32_t		ndtr1cs0_chip1;
+
+	/* generated NDCBx register values */
+	uint32_t		ndcb0;
+	uint32_t		ndcb1;
+	uint32_t		ndcb2;
+	uint32_t		ndcb3;
+};
+
+static u8 bbt_pattern[] = {'M', 'V', 'B', 'b', 't', '0' };
+static u8 bbt_mirror_pattern[] = {'1', 't', 'b', 'B', 'V', 'M' };
+
+static struct nand_bbt_descr bbt_main_descr = {
+	.options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE
+		| NAND_BBT_2BIT | NAND_BBT_VERSION,
+	.offs =	8,
+	.len = 6,
+	.veroffs = 14,
+	.maxblocks = 8,		/* Last 8 blocks in each chip */
+	.pattern = bbt_pattern,
+};
+
+static struct nand_bbt_descr bbt_mirror_descr = {
+	.options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE
+		| NAND_BBT_2BIT | NAND_BBT_VERSION,
+	.offs =	8,
+	.len = 6,
+	.veroffs = 14,
+	.maxblocks = 8,		/* Last 8 blocks in each chip */
+	.pattern = bbt_mirror_pattern,
+};
+
+static struct nand_ecclayout ecc_layout_512B_hwecc = {
+	.eccbytes = 6,
+	.eccpos = {
+		8, 9, 10, 11, 12, 13, 14, 15 },
+	.oobfree = { {0, 8} }
+};
+
+static struct nand_ecclayout ecc_layout_2KB_hwecc = {
+	.eccbytes = 24,
+	.eccpos = {
+		40, 41, 42, 43, 44, 45, 46, 47,
+		48, 49, 50, 51, 52, 53, 54, 55,
+		56, 57, 58, 59, 60, 61, 62, 63 },
+	.oobfree = { {0, 40} }
+};
+
+#define NDTR0_tCH(c)	(min((c), 7) << 19)
+#define NDTR0_tCS(c)	(min((c), 7) << 16)
+#define NDTR0_tWH(c)	(min((c), 7) << 11)
+#define NDTR0_tWP(c)	(min((c), 7) << 8)
+#define NDTR0_tRH(c)	(min((c), 7) << 3)
+#define NDTR0_tRP(c)	(min((c), 7) << 0)
+
+#define NDTR1_tR(c)	(min((c), 65535) << 16)
+#define NDTR1_tWHR(c)	(min((c), 15) << 4)
+#define NDTR1_tAR(c)	(min((c), 15) << 0)
+
+/* convert nano-seconds to nand flash controller clock cycles */
+#define ns2cycle(ns, clk)	(int)((ns) * (clk / 1000000) / 1000)
+
+#define mtd_info_to_host(mtd) ((struct mrvl_nand_host *) \
+			       (((struct nand_chip *)((mtd)->priv))->priv))
+
+static struct of_device_id mrvl_nand_dt_ids[] = {
+	{
+		.compatible = "marvell,pxa3xx-nand",
+	},
+	{}
+};
+
+static volatile u32 _nand_readl(const char *func, const int line,
+		       struct mrvl_nand_host *host, int off)
+{
+	volatile u32 val = readl((host)->mmio_base + (off));
+
+	dev_vdbg(host->dev, "\treadl %s:%d reg=0x%08x => 0x%08x\n",
+		func, line, off, val);
+	return val;
+}
+
+static void _nand_writel(const char *func, const int line,
+			 struct mrvl_nand_host *host, int off, u32 val)
+{
+	dev_vdbg(host->dev, "\twritel %s:%d reg=0x%08x val=0x%08x\n",
+		 func, line, off, val);
+	writel(val, (host)->mmio_base + off);
+}
+
+static struct mrvl_nand_timing timings[] = {
+	{ 0x46ec, 10,  0, 20,  40, 30,  40, 11123, 110, 10, },
+	{ 0xdaec, 10,  0, 20,  40, 30,  40, 11123, 110, 10, },
+	{ 0xd7ec, 10,  0, 20,  40, 30,  40, 11123, 110, 10, },
+	{ 0xa12c, 10, 25, 15,  25, 15,  30, 25000,  60, 10, },
+	{ 0xb12c, 10, 25, 15,  25, 15,  30, 25000,  60, 10, },
+	{ 0xdc2c, 10, 25, 15,  25, 15,  30, 25000,  60, 10, },
+	{ 0xcc2c, 10, 25, 15,  25, 15,  30, 25000,  60, 10, },
+	{ 0xba20, 10, 35, 15,  25, 15,  25, 25000,  60, 10, },
+	{ 0x0000, 40, 80, 60, 100, 80, 100, 90000, 400, 40, },
+};
+
+static void mrvl_nand_set_timing(struct mrvl_nand_host *host, bool use_default)
+{
+	struct mtd_info *mtd = &host->mtd;
+	struct mrvl_nand_timing *t;
+	uint32_t ndtr0, ndtr1;
+	u16 id;
+	unsigned long nand_clk = pxa_get_nandclk();
+
+	if (use_default) {
+		id = 0;
+	} else {
+		host->chip.cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
+		host->chip.read_buf(mtd, (unsigned char *)&id, sizeof(id));
+	}
+	for (t = &timings[0]; t->id; t++)
+		if (t->id == id)
+			break;
+	ndtr0 = NDTR0_tCH(ns2cycle(t->tCH, nand_clk)) |
+		NDTR0_tCS(ns2cycle(t->tCS, nand_clk)) |
+		NDTR0_tWH(ns2cycle(t->tWH, nand_clk)) |
+		NDTR0_tWP(ns2cycle(t->tWP, nand_clk)) |
+		NDTR0_tRH(ns2cycle(t->tRH, nand_clk)) |
+		NDTR0_tRP(ns2cycle(t->tRP, nand_clk));
+
+	ndtr1 = NDTR1_tR(ns2cycle(t->tR, nand_clk)) |
+		NDTR1_tWHR(ns2cycle(t->tWHR, nand_clk)) |
+		NDTR1_tAR(ns2cycle(t->tAR, nand_clk));
+	nand_writel(host, NDTR0CS0, ndtr0);
+	nand_writel(host, NDTR1CS0, ndtr1);
+}
+
+static int mrvl_nand_ready(struct mtd_info *mtd)
+{
+	struct mrvl_nand_host *host = mtd_info_to_host(mtd);
+	u32 ndcr;
+
+	ndcr = nand_readl(host, NDSR);
+	if (host->cmd_ongoing == NAND_CMD_RESET)
+		if (host->cs == 0)
+			return ndcr & NDSR_FLASH_RDY;
+		if (host->cs == 1)
+			return ndcr & NDSR_RDY;
+	if (host->cs == 0)
+		return ndcr & NDSR_CS0_CMDD;
+	if (host->cs == 1)
+		return ndcr & NDSR_CS1_CMDD;
+	return 1;
+}
+
+/*
+ * Claims all blocks are good.
+ *
+ * In principle, this function is *only* called when the NAND Flash MTD system
+ * isn't allowed to keep an in-memory bad block table, so it is forced to ask
+ * the driver for bad block information.
+ *
+ * In fact, we permit the NAND Flash MTD system to have an in-memory BBT, so
+ * this function is *only* called when we take it away.
+ *
+ * Thus, this function is only called when we want *all* blocks to look good,
+ * so it *always* return success.
+ */
+static int mrvl_nand_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip)
+{
+	return 0;
+}
+
+static void mrvl_nand_select_chip(struct mtd_info *mtd, int chipnr)
+{
+	struct mrvl_nand_host *host = mtd_info_to_host(mtd);
+
+	if (chipnr <= 0 || chipnr >= 3 || chipnr == host->cs)
+		return;
+	host->cs = chipnr - 1;
+}
+
+/*
+ * Set the data and OOB size, depending on the selected
+ * spare and ECC configuration.
+ * Only applicable to READ0, READOOB and PAGEPROG commands.
+ */
+static unsigned int mrvl_datasize(struct mrvl_nand_host *host)
+{
+	unsigned int datasize;
+
+	datasize = host->mtd.writesize;
+	if (host->use_spare) {
+		datasize += host->spare_size;
+		if (!host->use_ecc)
+			datasize += host->ecc_size;
+	}
+	return datasize;
+}
+
+/**
+ * NOTE: it is a must to set ND_RUN firstly, then write
+ * command buffer, otherwise, it does not work.
+ * We enable all the interrupt at the same time, and
+ * let mrvl_nand_irq to handle all logic.
+ */
+static void mrvl_nand_start(struct mrvl_nand_host *host)
+{
+	uint32_t ndcr;
+
+	ndcr = host->reg_ndcr;
+	if (host->use_ecc)
+		ndcr |= NDCR_ECC_EN;
+	else
+		ndcr &= ~NDCR_ECC_EN;
+
+	ndcr &= ~NDCR_DMA_EN;
+
+	if (host->use_spare)
+		ndcr |= NDCR_SPARE_EN;
+	else
+		ndcr &= ~NDCR_SPARE_EN;
+
+	ndcr |= NDCR_ND_RUN;
+
+	/* clear status bits and run */
+	nand_writel(host, NDCR, 0);
+	nand_writel(host, NDSR, NDSR_MASK);
+	nand_writel(host, NDCR, ndcr);
+
+	/*
+	 * Writing 12 bytes to NDBC0 sets NDBC0, NDBC1 and NDBC2 !
+	 */
+	nand_writel(host, NDCB0, host->ndcb0);
+	nand_writel(host, NDCB0, host->ndcb1);
+	nand_writel(host, NDCB0, host->ndcb2);
+}
+
+static void disable_int(struct mrvl_nand_host *host, uint32_t int_mask)
+{
+	uint32_t ndcr;
+
+	ndcr = nand_readl(host, NDCR);
+	nand_writel(host, NDCR, ndcr | int_mask);
+}
+
+static inline int is_buf_blank(uint8_t *buf, size_t len)
+{
+	for (; len > 0; len--)
+		if (*buf++ != 0xff)
+			return 0;
+	return 1;
+}
+
+static void set_command_address(struct mrvl_nand_host *host,
+		unsigned int page_size, uint16_t column, int page_addr)
+{
+	/* small page addr setting */
+	if (page_size < PAGE_CHUNK_SIZE) {
+		host->ndcb1 = ((page_addr & 0xFFFFFF) << 8)
+				| (column & 0xFF);
+
+		host->ndcb2 = 0;
+	} else {
+		host->ndcb1 = ((page_addr & 0xFFFF) << 16)
+				| (column & 0xFFFF);
+
+		if (page_addr & 0xFF0000)
+			host->ndcb2 = (page_addr & 0xFF0000) >> 16;
+		else
+			host->ndcb2 = 0;
+	}
+}
+
+static void prepare_start_command(struct mrvl_nand_host *host, int command)
+{
+	/* reset data and oob column point to handle data */
+	host->buf_start		= 0;
+	host->buf_count		= 0;
+	host->oob_size		= 0;
+	host->use_ecc		= 0;
+	host->use_spare		= 1;
+	host->ndcb3		= 0;
+	host->cmd_ongoing	= command;
+
+	switch (command) {
+	case NAND_CMD_SEQIN:
+		/*
+		 * This command is a no-op, as merged with PROGPAGE.
+		 */
+		break;
+	case NAND_CMD_READOOB:
+		host->data_size = mrvl_datasize(host);
+		break;
+	case NAND_CMD_READ0:
+		host->use_ecc = 1;
+		host->data_size = mrvl_datasize(host);
+		break;
+	case NAND_CMD_PAGEPROG:
+		host->use_ecc = 1;
+		host->data_size = mrvl_datasize(host);
+		break;
+	case NAND_CMD_PARAM:
+		host->use_spare = 0;
+		break;
+	default:
+		host->ndcb1 = 0;
+		host->ndcb2 = 0;
+		break;
+	}
+
+	/*
+	 * If we are about to issue a read command, or about to set
+	 * the write address, then clean the data buffer.
+	 */
+	if (command == NAND_CMD_READ0 ||
+	    command == NAND_CMD_READOOB ||
+	    command == NAND_CMD_SEQIN) {
+		host->buf_count = host->mtd.writesize + host->mtd.oobsize;
+		memset(host->data_buff, 0xFF, host->buf_count);
+	}
+
+}
+
+/**
+ * prepare_set_command - Prepare a NAND command
+ *
+ * Prepare data for a NAND command. If the command will not be executed, but
+ * instead merged into a "bi-command", returns 0.
+ *
+ * Returns if the command should be launched on the NFC
+ */
+static int prepare_set_command(struct mrvl_nand_host *host, int command,
+		int ext_cmd_type, uint16_t column, int page_addr)
+{
+	int addr_cycle, exec_cmd;
+	struct mtd_info *mtd;
+
+	mtd = &host->mtd;
+	exec_cmd = 1;
+
+	if (host->cs != 0)
+		host->ndcb0 = NDCB0_CSEL;
+	else
+		host->ndcb0 = 0;
+
+	addr_cycle = NDCB0_ADDR_CYC(host->row_addr_cycles
+				    + host->col_addr_cycles);
+	switch (command) {
+	case NAND_CMD_READOOB:
+	case NAND_CMD_READ0:
+		host->ndcb0 |= NDCB0_CMD_TYPE(0)
+				| addr_cycle
+				| NAND_CMD_READ0;
+
+		if (command == NAND_CMD_READOOB)
+			host->buf_start = column + mtd->writesize;
+		else
+			host->buf_start = column;
+
+		/*
+		 * Multiple page read needs an 'extended command type' field,
+		 * which is either naked-read or last-read according to the
+		 * state.
+		 */
+		if (mtd->writesize == PAGE_CHUNK_SIZE) {
+			host->ndcb0 |= NDCB0_DBC | (NAND_CMD_READSTART << 8);
+		} else if (mtd->writesize > PAGE_CHUNK_SIZE) {
+			host->ndcb0 |= NDCB0_DBC | (NAND_CMD_READSTART << 8)
+					| NDCB0_LEN_OVRD
+					| NDCB0_EXT_CMD_TYPE(ext_cmd_type);
+			host->ndcb3 = host->chunk_size +
+				      host->oob_size;
+		}
+
+		set_command_address(host, mtd->writesize, column, page_addr);
+		break;
+
+	case NAND_CMD_SEQIN:
+		host->buf_start = column;
+		set_command_address(host, mtd->writesize, 0, page_addr);
+		/* Data transfer will occur in write_page */
+		host->data_size = 0;
+		exec_cmd = 0;
+		break;
+
+	case NAND_CMD_PAGEPROG:
+		host->ndcb0 |= NDCB0_CMD_TYPE(0x1)
+				| NDCB0_DBC
+				| (NAND_CMD_PAGEPROG << 8)
+				| NAND_CMD_SEQIN
+				| addr_cycle;
+		break;
+
+	case NAND_CMD_PARAM:
+		host->buf_count = 256;
+		host->ndcb0 |= NDCB0_CMD_TYPE(0)
+				| NDCB0_ADDR_CYC(1)
+				| NDCB0_LEN_OVRD
+				| command;
+		host->ndcb1 = (column & 0xFF);
+		host->ndcb3 = 256;
+		host->data_size = 256;
+		break;
+
+	case NAND_CMD_READID:
+		host->buf_count = host->read_id_bytes;
+		host->ndcb0 |= NDCB0_CMD_TYPE(3)
+				| NDCB0_ADDR_CYC(1)
+				| command;
+		host->ndcb1 = (column & 0xFF);
+
+		host->data_size = 8;
+		break;
+	case NAND_CMD_STATUS:
+		host->buf_count = 1;
+		host->ndcb0 |= NDCB0_CMD_TYPE(4)
+				| NDCB0_ADDR_CYC(1)
+				| command;
+
+		host->data_size = 8;
+		break;
+
+	case NAND_CMD_ERASE1:
+		host->ndcb0 |= NDCB0_CMD_TYPE(2)
+				| NDCB0_ADDR_CYC(3)
+				| NDCB0_DBC
+				| (NAND_CMD_ERASE2 << 8)
+				| NAND_CMD_ERASE1;
+		host->ndcb1 = page_addr;
+		host->ndcb2 = 0;
+
+		break;
+	case NAND_CMD_RESET:
+		host->ndcb0 |= NDCB0_CMD_TYPE(5)
+				| command;
+		break;
+
+	case NAND_CMD_ERASE2:
+		exec_cmd = 0;
+		break;
+
+	default:
+		exec_cmd = 0;
+		dev_err(host->dev, "non-supported command %x\n",
+				command);
+		break;
+	}
+
+	return exec_cmd;
+}
+
+static void mrvl_data_stage(struct mrvl_nand_host *host)
+{
+	unsigned int i, mask = NDSR_RDDREQ | NDSR_WRDREQ;
+	u32 *src, ndsr;
+
+	dev_dbg(host->dev, "%s() ndsr=0x%08x\n",  __func__,
+		nand_readl(host, NDSR));
+	if (!host->data_size)
+		return;
+
+	wait_on_timeout(host->chip.chip_delay * USECOND,
+			nand_readl(host, NDSR) & mask);
+	if (!(nand_readl(host, NDSR) & mask)) {
+		dev_err(host->dev, "Timeout waiting for data ndsr=0x%08x\n",
+			nand_readl(host, NDSR));
+		return;
+	}
+
+	ndsr = nand_readl(host, NDSR);
+	mask &= ndsr;
+	src = (u32 *)host->data_buff;
+
+	for (i = 0; i < host->data_size; i += 4) {
+		if (ndsr & NDSR_RDDREQ)
+			*src++ = nand_readl(host, NDDB);
+		if (ndsr & NDSR_WRDREQ)
+			nand_writel(host, NDDB, *src++);
+	}
+
+	host->data_size = 0;
+	nand_writel(host, NDSR, mask);
+}
+
+static void mrvl_nand_wait_cmd_done(struct mrvl_nand_host *host)
+{
+	unsigned int mask;
+
+	if (host->cs == 0)
+		mask = NDSR_CS0_CMDD | NDSR_WRCMDREQ;
+	else
+		mask = NDSR_CS1_CMDD | NDSR_WRCMDREQ;
+	wait_on_timeout(host->chip.chip_delay * USECOND,
+			(nand_readl(host, NDSR) & mask) == mask);
+	if ((nand_readl(host, NDSR) & mask) != mask)
+		dev_err(host->dev, "Waiting end of command timeout, ndsr=0x%08x\n",
+			nand_readl(host, NDSR) & mask);
+}
+
+static void mrvl_nand_cmdfunc(struct mtd_info *mtd, unsigned command,
+			       int column, int page_addr)
+{
+	struct mrvl_nand_host *host = mtd_info_to_host(mtd);
+
+	/*
+	 * if this is a x16 device ,then convert the input
+	 * "byte" address into a "word" address appropriate
+	 * for indexing a word-oriented device
+	 */
+	dev_dbg(host->dev, "%s(cmd=%d, col=%d, page=%d)\n", __func__,
+		command, column, page_addr);
+	if (host->reg_ndcr & NDCR_DWIDTH_M)
+		column /= 2;
+
+	prepare_start_command(host, command);
+	if (prepare_set_command(host, command, 0, column, page_addr)) {
+		mrvl_nand_start(host);
+		mrvl_data_stage(host);
+		mrvl_nand_wait_cmd_done(host);
+	}
+}
+
+/**
+ * mrvl_nand_write_page_hwecc - prepare page for write
+ *
+ * Fills in the host->data_buff. The actual write will be done by the PAGEPROG
+ * command, which will trigger a mrvl_data_stage().
+ *
+ * Returns 0
+ */
+static int mrvl_nand_write_page_hwecc(struct mtd_info *mtd,
+		struct nand_chip *chip, const uint8_t *buf, int oob_required)
+{
+	struct mrvl_nand_host *host = mtd_info_to_host(mtd);
+
+	memcpy(host->data_buff, buf, mtd->writesize);
+	if (oob_required)
+		memcpy(host->data_buff + mtd->writesize, chip->oob_poi,
+		       mtd->oobsize);
+	else
+		memset(host->data_buff + mtd->writesize, 0, mtd->oobsize);
+	dev_dbg(host->dev, "%s(buf=%p, oob_required=%d) => 0\n",
+		__func__, buf, oob_required);
+	return 0;
+}
+
+static int mrvl_nand_read_page_hwecc(struct mtd_info *mtd,
+		struct nand_chip *chip, uint8_t *buf, int oob_required,
+		int page)
+{
+	struct mrvl_nand_host *host = mtd_info_to_host(mtd);
+	u32 ndsr;
+	int ret = 0;
+
+	chip->read_buf(mtd, buf, mtd->writesize);
+	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+	ndsr = nand_readl(host, NDSR);
+
+	if (ndsr & NDSR_UNCORERR) {
+		if (is_buf_blank(buf, mtd->writesize))
+			ret = 0;
+		else
+			ret = -EBADMSG;
+	}
+	if (ndsr & NDSR_CORERR)
+		ret = 1;
+	dev_dbg(host->dev, "%s(buf=%p, page=%d, oob_required=%d) => %d\n",
+		__func__, buf, page, oob_required, ret);
+	return ret;
+}
+
+static void mrvl_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
+{
+	struct mrvl_nand_host *host = mtd_info_to_host(mtd);
+	int xfer;
+
+	xfer = min_t(int, len, host->buf_count);
+	memcpy(buf, host->data_buff + host->buf_start, xfer);
+	host->buf_start += xfer;
+	host->buf_count -= xfer;
+}
+
+static uint8_t mrvl_nand_read_byte(struct mtd_info *mtd)
+{
+	uint8_t ret;
+
+	mrvl_nand_read_buf(mtd, (uint8_t *)&ret, sizeof(ret));
+	return ret;
+}
+
+static u16 mrvl_nand_read_word(struct mtd_info *mtd)
+{
+	u16 ret;
+
+	mrvl_nand_read_buf(mtd, (uint8_t *)&ret, sizeof(ret));
+	return ret;
+}
+
+static void mrvl_nand_write_buf(struct mtd_info *mtd,
+		const uint8_t *buf, int len)
+{
+	struct mrvl_nand_host *host = mtd_info_to_host(mtd);
+
+	memcpy(host->data_buff + host->buf_start, buf, len);
+	host->buf_start += len;
+	host->buf_count -= len;
+}
+
+static void mrvl_nand_config_flash(struct mrvl_nand_host *host)
+{
+	struct nand_chip *chip = &host->chip;
+	struct mtd_info *mtd = &host->mtd;
+	uint32_t ndcr = host->reg_ndcr;
+
+	/* calculate flash information */
+	host->read_id_bytes = (mtd->writesize == 2048) ? 4 : 2;
+
+	/* calculate addressing information */
+	host->col_addr_cycles = (mtd->writesize == 2048) ? 2 : 1;
+	if ((mtd->size >> chip->page_shift) > 65536)
+		host->row_addr_cycles = 3;
+	else
+		host->row_addr_cycles = 2;
+
+	ndcr |= NDCR_ND_ARB_EN;
+	ndcr |= (host->col_addr_cycles == 2) ? NDCR_RA_START : 0;
+	ndcr |= ((mtd->erasesize / mtd->writesize) == 64) ? NDCR_PG_PER_BLK : 0;
+	ndcr |= (mtd->writesize == 2048) ? NDCR_PAGE_SZ : 0;
+
+	ndcr |= NDCR_RD_ID_CNT(host->read_id_bytes);
+	ndcr |= NDCR_SPARE_EN; /* enable spare by default */
+	ndcr &= ~NDCR_DMA_EN;
+
+	if (chip->options & NAND_BUSWIDTH_16)
+		ndcr |= NDCR_DWIDTH_M | NDCR_DWIDTH_C;
+	else
+		ndcr &= ~(NDCR_DWIDTH_M | NDCR_DWIDTH_C);
+
+	host->reg_ndcr = ndcr;
+}
+
+static int pxa_ecc_init(struct mrvl_nand_host *host,
+			struct nand_ecc_ctrl *ecc,
+			int strength, int ecc_stepsize, int page_size)
+{
+	if (strength == 1 && ecc_stepsize == 512 && page_size == 2048) {
+		host->chunk_size = 2048;
+		host->spare_size = 40;
+		host->ecc_size = 24;
+		ecc->mode = NAND_ECC_HW;
+		ecc->size = 512;
+		ecc->strength = 1;
+		ecc->layout = &ecc_layout_2KB_hwecc;
+
+	} else if (strength == 1 && ecc_stepsize == 512 && page_size == 512) {
+		host->chunk_size = 512;
+		host->spare_size = 8;
+		host->ecc_size = 8;
+		ecc->mode = NAND_ECC_HW;
+		ecc->size = 512;
+		ecc->layout = &ecc_layout_512B_hwecc;
+		ecc->strength = 1;
+	} else {
+		dev_err(host->dev,
+			"ECC strength %d at page size %d is not supported\n",
+			strength, page_size);
+		return -ENODEV;
+	}
+
+	dev_info(host->dev, "ECC strength %d, ECC step size %d\n",
+		 ecc->strength, ecc->size);
+	return 0;
+}
+
+static int mrvl_nand_scan(struct mtd_info *mtd)
+{
+	struct nand_chip *chip = mtd->priv;
+	struct mrvl_nand_host *host = chip->priv;
+	int ret;
+	unsigned int ndcr;
+	uint16_t ecc_strength = host->ecc_strength;
+	uint16_t ecc_step = host->ecc_step;
+
+	host->read_id_bytes = 4;
+	ndcr = NDCR_ND_ARB_EN | NDCR_SPARE_EN;
+	ndcr |= NDCR_RD_ID_CNT(host->read_id_bytes);
+	host->reg_ndcr = ndcr;
+
+	mrvl_nand_set_timing(host, true);
+	if (nand_scan_ident(mtd, 1, NULL)) {
+		host->reg_ndcr |= NDCR_DWIDTH_M | NDCR_DWIDTH_C;
+		if (nand_scan_ident(mtd, 1, NULL))
+			return -ENODEV;
+	}
+	mrvl_nand_config_flash(host);
+	mrvl_nand_set_timing(host, false);
+	if (host->flash_bbt) {
+		/*
+		 * We'll use a bad block table stored in-flash and don't
+		 * allow writing the bad block marker to the flash.
+		 */
+		chip->bbt_options |= NAND_BBT_USE_FLASH |
+				     NAND_BBT_NO_OOB_BBM;
+		chip->bbt_td = &bbt_main_descr;
+		chip->bbt_md = &bbt_mirror_descr;
+	}
+
+	/*
+	 * If the page size is bigger than the FIFO size, let's check
+	 * we are given the right variant and then switch to the extended
+	 * (aka split) command handling,
+	 */
+	if (mtd->writesize > PAGE_CHUNK_SIZE) {
+		dev_err(host->dev,
+			"unsupported page size on this variant\n");
+		return -ENODEV;
+	}
+
+	/* Set default ECC strength requirements on non-ONFI devices */
+	if (ecc_strength < 1 && ecc_step < 1) {
+		ecc_strength = 1;
+		ecc_step = 512;
+	}
+
+	ret = pxa_ecc_init(host, &chip->ecc, ecc_strength,
+			   ecc_step, mtd->writesize);
+	if (ret)
+		return ret;
+	mtd->oobsize = host->spare_size + host->ecc_size;
+
+	/* allocate the real data + oob buffer */
+	host->buf_size = mtd->writesize + mtd->oobsize;
+	host->data_buff = xmalloc(host->buf_size);
+
+	return nand_scan_tail(mtd);
+}
+
+static struct mrvl_nand_host *alloc_nand_resource(struct device_d *dev)
+{
+	struct mrvl_nand_platform_data *pdata;
+	struct mrvl_nand_host *host;
+	struct nand_chip *chip = NULL;
+	struct mtd_info *mtd;
+
+	pdata = dev->platform_data;
+	host = xzalloc(sizeof(*host));
+	host->cs = 0;
+	mtd = &host->mtd;
+	mtd->priv = &host->chip;
+	mtd->parent = dev;
+	mtd->name = "mrvl_nand";
+
+	chip = &host->chip;
+	chip->read_byte		= mrvl_nand_read_byte;
+	chip->read_word		= mrvl_nand_read_word;
+	chip->ecc.read_page	= mrvl_nand_read_page_hwecc;
+	chip->ecc.write_page	= mrvl_nand_write_page_hwecc;
+	chip->dev_ready		= mrvl_nand_ready;
+	chip->select_chip	= mrvl_nand_select_chip;
+	chip->block_bad		= mrvl_nand_block_bad;
+	chip->read_buf		= mrvl_nand_read_buf;
+	chip->write_buf		= mrvl_nand_write_buf;
+	chip->options		|= NAND_NO_SUBPAGE_WRITE;
+	chip->cmdfunc		= mrvl_nand_cmdfunc;
+	chip->priv		= host;
+	chip->chip_delay	= CHIP_DELAY_TIMEOUT_US;
+
+	host->dev = dev;
+	host->mmio_base = dev_request_mem_region(dev, 0);
+	if (IS_ERR(host->mmio_base)) {
+		free(host);
+		return host->mmio_base;
+	}
+	if (pdata) {
+		host->keep_config = pdata->keep_config;
+		host->flash_bbt = pdata->flash_bbt;
+		host->ecc_strength = pdata->ecc_strength;
+		host->ecc_step = pdata->ecc_step_size;
+	}
+
+	/* Allocate a buffer to allow flash detection */
+	host->buf_size = INIT_BUFFER_SIZE;
+	host->data_buff = xmalloc(host->buf_size);
+
+	/* initialize all interrupts to be disabled */
+	disable_int(host, NDSR_MASK);
+	return host;
+}
+
+static int mrvl_nand_probe_dt(struct mrvl_nand_host *host)
+{
+	struct device_node *np = host->dev->device_node;
+
+	if (of_get_property(np, "marvell,nand-keep-config", NULL))
+		host->keep_config = 1;
+	of_property_read_u32(np, "num-cs", &host->cs);
+	if (of_get_nand_on_flash_bbt(np))
+		host->flash_bbt = 1;
+
+	return 0;
+}
+
+static int mrvl_nand_probe(struct device_d *dev)
+{
+	struct mrvl_nand_host *host;
+	int ret;
+
+	host = alloc_nand_resource(dev);
+	if (IS_ERR(host)) {
+		dev_err(dev, "alloc nand resource failed\n");
+		return PTR_ERR(host);
+	}
+
+	ret = mrvl_nand_probe_dt(host);
+	if (ret)
+		return ret;
+
+	host->chip.controller = &host->chip.hwcontrol;
+	ret = mrvl_nand_scan(&host->mtd);
+	if (ret) {
+		dev_warn(dev, "failed to scan nand at cs %d\n",
+			 host->cs);
+		return -ENODEV;
+	}
+
+	ret = add_mtd_nand_device(&host->mtd, "nand");
+	return ret;
+}
+
+static struct driver_d mrvl_nand_driver = {
+	.name		= "mrvl_nand",
+	.probe		= mrvl_nand_probe,
+	.of_compatible	= DRV_OF_COMPAT(mrvl_nand_dt_ids),
+};
+device_platform_driver(mrvl_nand_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Marvell NAND controller driver");
diff --git a/include/platform_data/mtd-nand-mrvl.h b/include/platform_data/mtd-nand-mrvl.h
new file mode 100644
index 0000000..c8ef6a1
--- /dev/null
+++ b/include/platform_data/mtd-nand-mrvl.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 Robert Jarzmik
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Taken from linux kernel mostly.
+ */
+#ifndef __MRVL_NAND_H
+#define __MRVL_NAND_H
+
+struct mrvl_nand_timing {
+	uint16_t	id;   /* NAND id code (READID) */
+	unsigned int	tCH;  /* Enable signal hold time */
+	unsigned int	tCS;  /* Enable signal setup time */
+	unsigned int	tWH;  /* ND_nWE high duration */
+	unsigned int	tWP;  /* ND_nWE pulse time */
+	unsigned int	tRH;  /* ND_nRE high duration */
+	unsigned int	tRP;  /* ND_nRE pulse width */
+	unsigned int	tR;   /* ND_nWE high to ND_nRE low for read */
+	unsigned int	tWHR; /* ND_nWE high to ND_nRE low for status read */
+	unsigned int	tAR;  /* ND_ALE low to ND_nRE low delay */
+};
+
+struct mrvl_nand_flash {
+	char		*name;
+	uint32_t	chip_id;
+	unsigned int	page_per_block; /* Pages per block (PG_PER_BLK) */
+	unsigned int	page_size;	/* Page size in bytes (PAGE_SZ) */
+	unsigned int	flash_width;	/* Flash memory width (DWIDTH_M) */
+	unsigned int	dfc_width;	/* Flash controller width (DWIDTH_C) */
+	unsigned int	num_blocks;	/* Number of physical blocks in Flash */
+
+	struct mrvl_nand_timing *timing;	/* NAND Flash timing */
+};
+
+/*
+ * Current pxa3xx_nand controller has two chip select which
+ * both be workable.
+ *
+ * Notice should be taken that:
+ * When you want to use this feature, you should not enable the
+ * keep configuration feature, for two chip select could be
+ * attached with different nand chip. The different page size
+ * and timing requirement make the keep configuration impossible.
+ */
+
+/* The max num of chip select current support */
+#define NUM_CHIP_SELECT		(2)
+struct mrvl_nand_platform_data {
+	/* the data flash bus is shared between the Static Memory
+	 * Controller and the Data Flash Controller,  the arbiter
+	 * controls the ownership of the bus
+	 */
+	int	dwidth_c;
+	int	dwidth_m;
+
+	/* allow platform code to keep OBM/bootloader defined NFC config */
+	int	keep_config;
+
+	/* indicate how many chip selects will be used */
+	int	num_cs;
+
+	/* use an flash-based bad block table */
+	bool	flash_bbt;
+
+	/* requested ECC strength and ECC step size */
+	int ecc_strength, ecc_step_size;
+
+	const struct mtd_partition		*parts[NUM_CHIP_SELECT];
+	unsigned int				nr_parts[NUM_CHIP_SELECT];
+
+	const struct mrvl_nand_flash		*flash;
+	size_t					num_flash;
+};
+
+extern void mrvl_set_nand_info(struct mrvl_nand_platform_data *info);
+#endif /* __MRVL_NAND_H */
-- 
2.1.0




More information about the barebox mailing list