[U-Boot] [RFC 07/11] mtd/nand Add Sunxi NAND driver

Roy Spliet r.spliet at ultimaker.com
Fri Jun 5 13:52:40 CEST 2015


Heavily based on BBrezillon's (downstream) driver. Most noticable
differences
- No per-partition ECC settings. Partitions in U-boot are quite
  different from Linux
- U-boot register definitions, shared with sunxi_nand_spl
- FDT parsing in-line, there's no framework method yet

Signed-off-by: Roy Spliet <r.spliet at ultimaker.com>
---
 arch/arm/include/asm/arch-sunxi/nand.h |   46 +-
 board/sunxi/board.c                    |    5 +
 drivers/mtd/nand/Makefile              |    1 +
 drivers/mtd/nand/sunxi_nand.c          | 1887 ++++++++++++++++++++++++++++++++
 include/fdtdec.h                       |   13 +
 lib/fdtdec.c                           |   17 +
 6 files changed, 1966 insertions(+), 3 deletions(-)
 create mode 100644 drivers/mtd/nand/sunxi_nand.c

diff --git a/arch/arm/include/asm/arch-sunxi/nand.h b/arch/arm/include/asm/arch-sunxi/nand.h
index 22844d8..d0fae80 100644
--- a/arch/arm/include/asm/arch-sunxi/nand.h
+++ b/arch/arm/include/asm/arch-sunxi/nand.h
@@ -1,5 +1,5 @@
 /*
- * (C) Copyright 2015 Roy Spliet <rspliet at ultimaker.com>
+ * (C) Copyright 2015 Roy Spliet <r.spliet at ultimaker.com>
  *
  * SPDX-License-Identifier:	GPL-2.0+
  */
@@ -27,8 +27,7 @@ struct sunxi_nand
 	u32 ecc_ctl;		/* 0x034 ECC configure and control */
 	u32 ecc_st;		/* 0x038 ECC status and operation info */
 	u32 efr;		/* 0x03C Enhanced feature */
-	u32 err_cnt0;		/* 0x040 Corrected error bit counter 0 */
-	u32 err_cnt1;		/* 0x044 Corrected error bit counter 1 */
+	u32 err_cnt[4];		/* 0x040[4] Corrected error bit counter 0 */
 	u32 user_data[16];	/* 0x050[16] User data field */
 	u32 efnand_st;		/* 0x090 EFNAND status */
 	u32 res0[3];
@@ -40,28 +39,69 @@ struct sunxi_nand
 	u32 res1[3];
 	u32 mdma_addr;		/* 0x0C0 MBUS DMA Address */
 	u32 mdma_cnt;		/* 0x0C4 MBUS DMA data counter */
+	u32 res2[206];
+	u32 ram0_base;
 };
 
 #define SUNXI_NAND_CTL_EN			(1 << 0)
 #define SUNXI_NAND_CTL_RST			(1 << 1)
+#define SUNXI_NAND_BUS_WIDTH			(1 << 2)
+#define SUNXI_NAND_CTL_RB_SEL_MASK		(0x3 << 3)
+#define SUNXI_NAND_CTL_RB_SEL(a)		((a) << 3)
+#define SUNXI_NAND_CTL_CE_ACT			(1 << 6)
+#define SUNXI_NAND_CTL_PAGE_SIZE_MASK		(0xf << 8)
 #define SUNXI_NAND_CTL_PAGE_SIZE(a)		((fls(a) - 11) << 8)
 #define SUNXI_NAND_CTL_RAM_METHOD_DMA		(1 << 14)
+#define SUNXI_NAND_CTL_CE_SEL_MASK		(0xf << 24)
+#define SUNXI_NAND_CTL_CE_SEL(a)		(a << 24)
+#define SUNXI_NAND_CTL_DEBUG			(1 << 31)
 
+#define SUNXI_NAND_ST_RB_B2R			(1 << 0)
 #define SUNXI_NAND_ST_CMD_INT			(1 << 1)
 #define SUNXI_NAND_ST_DMA_INT			(1 << 2)
 #define SUNXI_NAND_ST_FIFO_FULL			(1 << 3)
+#define SUNXI_NAND_ST_BUSY			(1 << 4)
+#define SUNXI_NAND_ST_RB_STATE0			(1 << 8)
+#define SUNXI_NAND_ST_RB_STATE1			(1 << 9)
+#define SUNXI_NAND_ST_RB_STATE2			(1 << 10)
+#define SUNXI_NAND_ST_RB_STATE3			(1 << 11)
 
+
+#define SUNXI_NAND_INT_B2R_ENABLE		(1 << 0)
+#define SUNXI_NAND_INT_CMD_ENABLE		(1 << 1)
+#define SUNXI_NAND_INT_DMA_ENABLE		(1 << 2)
+#define SUNXI_NAND_INT_MASK	(SUNXI_NAND_INT_B2R_ENABLE | \
+				 SUNXI_NAND_INT_CMD_ENABLE | \
+				 SUNXI_NAND_INT_DMA_ENABLE)
+
+#define SUNXI_NAND_CMD_LOW_BYTE(a)		(a & 0xff)
+#define SUNXI_NAND_CMD_HIGH_BYTE(a)		((a & 0xff) << 8)
 #define SUNXI_NAND_CMD_ADDR_CYCLES(a)		((a - 1) << 16);
+#define SUNXI_NAND_CMD_SEND_ADR			(1 << 19)
+#define SUNXI_NAND_CMD_ACCESS_RD		0
+#define SUNXI_NAND_CMD_ACCESS_WR		(1 << 20)
+#define SUNXI_NAND_CMD_DATA_TRANS		(1 << 21)
 #define SUNXI_NAND_CMD_SEND_CMD1		(1 << 22)
 #define SUNXI_NAND_CMD_WAIT_FLAG		(1 << 23)
+#define SUNXI_NAND_CMD_SEND_CMD2		(1 << 24)
 #define SUNXI_NAND_CMD_ORDER_INTERLEAVE		0
 #define SUNXI_NAND_CMD_ORDER_SEQ		(1 << 25)
+#define SUNXI_NAND_CMD_DATA_SWAP_METHOD		(1 << 26)
+#define SUNXI_NAND_CMD_ROW_AUTO_INC		(1 << 27)
+#define SUNXI_NAND_CMD_SEND_CMD3		(1 << 28)
+#define SUNXI_NAND_CMD_SEND_CMD4		(1 << 29)
 
 #define SUNXI_NAND_ECC_CTL_ECC_EN		(1 << 0)
 #define SUNXI_NAND_ECC_CTL_PIPELINE		(1 << 3)
+#define SUNXI_NAND_ECC_CTL_EXCEPTION		(1 << 4)
 #define SUNXI_NAND_ECC_CTL_BS_512B		(1 << 5)
 #define SUNXI_NAND_ECC_CTL_RND_EN		(1 << 9)
+#define SUNXI_NAND_ECC_CTL_RND_DIRECTION	(1 << 10)
+#define SUNXI_NAND_ECC_CTL_MODE_MASK		(0xf << 12)
 #define SUNXI_NAND_ECC_CTL_MODE(a)		((a) << 12)
+#define SUNXI_NAND_ECC_CTL_RND_SEED_MASK	(0xffff << 16)
 #define SUNXI_NAND_ECC_CTL_RND_SEED(a)		((a) << 16)
 
+extern void sunxi_nand_init(void);
+
 #endif /* _SUNXI_NAND_H */
diff --git a/board/sunxi/board.c b/board/sunxi/board.c
index d5bed30..e37209d 100644
--- a/board/sunxi/board.c
+++ b/board/sunxi/board.c
@@ -31,6 +31,7 @@
 #include <asm/arch/dram.h>
 #include <asm/arch/gpio.h>
 #include <asm/arch/mmc.h>
+#include <asm/arch/nand.h>
 #include <asm/arch/usb_phy.h>
 #include <asm/gpio.h>
 #include <asm/io.h>
@@ -329,6 +330,10 @@ void board_nand_init(void)
 
 	for (pin = 0; pin < ARRAY_SIZE(ports); pin++)
 		sunxi_gpio_set_cfgpin(SUNXI_GPC(ports[pin]), SUNXI_GPC_NAND);
+
+#ifndef CONFIG_SPL_BUILD
+	sunxi_nand_init();
+#endif
 }
 
 void i2c_init_board(void)
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index f194493..c2368bc 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -66,6 +66,7 @@ obj-$(CONFIG_NAND_OMAP_GPMC) += omap_gpmc.o
 obj-$(CONFIG_NAND_OMAP_ELM) += omap_elm.o
 obj-$(CONFIG_NAND_PLAT) += nand_plat.o
 obj-$(CONFIG_NAND_DOCG4) += docg4.o
+obj-$(CONFIG_NAND_SUNXI) += sunxi_nand.o
 
 else  # minimal SPL drivers
 
diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/sunxi_nand.c
new file mode 100644
index 0000000..42f45e3
--- /dev/null
+++ b/drivers/mtd/nand/sunxi_nand.c
@@ -0,0 +1,1887 @@
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon.dev at gmail.com>
+ * Copyright (C) 2015 Roy Spliet <r.spliet at ultimaker.com>
+ *
+ * Derived from:
+ *	https://github.com/yuq/sunxi-nfc-mtd
+ *	Copyright (C) 2013 Qiang Yu <yuq825 at gmail.com>
+ *
+ *	https://github.com/hno/Allwinner-Info
+ *	Copyright (C) 2013 Henrik Nordström <Henrik Nordström>
+ *
+ *	Copyright (C) 2013 Dmitriy B. <rzk333 at gmail.com>
+ *	Copyright (C) 2013 Sergey Lapin <slapin at ossfans.org>
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <asm/io.h>
+#include <asm/gpio.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/nand.h>
+#include <asm/io.h>
+#include <asm/errno.h>
+
+#include <fdtdec.h>
+#include <fdt_support.h>
+#include <nand.h>
+#include <errno.h>
+#include <malloc.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+
+/* Declare global data pointer */
+DECLARE_GLOBAL_DATA_PTR;
+
+#define NAND_MAX_CLOCK (10 * 1000000)
+#define DEFAULT_NAME_FORMAT	"nand@%d"
+#define MAX_NAME_SIZE		(sizeof("nand@") + 2)
+
+#define SUNXI_NAND_DEFAULT_TIMEOUT_MS	1000
+
+/*
+ * Ready/Busy detection type: describes the Ready/Busy detection modes
+ *
+ * @RB_NONE:	no external detection available, rely on STATUS command
+ *		and software timeouts
+ * @RB_NATIVE:	use sunxi NAND controller Ready/Busy support. The Ready/Busy
+ *		pin of the NAND flash chip must be connected to one of the
+ *		native NAND R/B pins (those which can be muxed to the NAND
+ *		Controller)
+ * @RB_GPIO:	use a simple GPIO to handle Ready/Busy status. The Ready/Busy
+ *		pin of the NAND flash chip must be connected to a GPIO capable
+ *		pin.
+ */
+enum sunxi_nand_rb_type {
+	RB_NONE,
+	RB_NATIVE,
+	RB_GPIO,
+};
+
+/*
+ * Ready/Busy structure: stores informations related to Ready/Busy detection
+ *
+ * @type:	the Ready/Busy detection mode
+ * @info:	information related to the R/B detection mode. Either a gpio
+ *		id or a native R/B id (those supported by the NAND controller).
+ */
+struct sunxi_nand_rb {
+	enum sunxi_nand_rb_type type;
+	union {
+		int gpio;
+		int nativeid;
+	} info;
+};
+
+/*
+ * Chip Select structure: stores informations related to NAND Chip Select
+ *
+ * @cs:		the NAND CS id used to communicate with a NAND Chip
+ * @rb:		the Ready/Busy description
+ */
+struct sunxi_nand_chip_sel {
+	u8 cs;
+	struct sunxi_nand_rb rb;
+};
+
+/*
+ * sunxi HW ECC infos: stores informations related to HW ECC support
+ *
+ * @mode:	the sunxi ECC mode field deduced from ECC requirements
+ * @layout:	the OOB layout depending on the ECC requirements and the
+ *		selected ECC mode
+ */
+struct sunxi_nand_hw_ecc {
+	int mode;
+	struct nand_ecclayout layout;
+};
+
+/*
+ * sunxi NAND randomizer structure: stores NAND randomizer informations
+ *
+ * @page: current page
+ * @column: current column
+ * @nseeds: seed table size
+ * @seeds: seed table
+ * @subseeds: pre computed sub seeds
+ * @step: step function
+ * @left: number of remaining bytes in the page
+ * @state: current randomizer state
+ */
+struct sunxi_nand_hw_rnd {
+	int page;
+	int column;
+	int nseeds;
+	u16 *seeds;
+	u16 *subseeds;
+	u16 (*step)(struct mtd_info *mtd, u16 state, int column, int *left);
+	int left;
+	u16 state;
+};
+
+/*
+ * NAND chip structure: stores NAND chip device related informations
+ *
+ * @node:		used to store NAND chips into a list
+ * @nand:		base NAND chip structure
+ * @mtd:		base MTD structure
+ * @default_name:	name used if no name was provided by the DT
+ * @clk_rate:		clk_rate required for this NAND chip
+ * @selected:		current active CS
+ * @nsels:		number of CS lines required by the NAND chip
+ * @sels:		array of CS lines descriptions
+ */
+struct sunxi_nand_chip {
+	struct list_head node;
+	struct nand_chip nand;
+	struct mtd_info mtd;
+	char default_name[MAX_NAME_SIZE];
+	void *buffer;
+	unsigned long clk_rate;
+	int selected;
+	int nsels;
+	struct sunxi_nand_chip_sel sels[0];
+};
+
+static inline struct sunxi_nand_chip *to_sunxi_nand(struct nand_chip *nand)
+{
+	return container_of(nand, struct sunxi_nand_chip, nand);
+}
+
+/*
+ * NAND Controller structure: stores sunxi NAND controller informations
+ *
+ * @controller:		base controller structure
+ * @regs:		NAND controller registers
+ * @ahb_clk:		NAND Controller AHB clock
+ * @mod_clk:		NAND Controller mod clock
+ * @assigned_cs:	bitmask describing already assigned CS lines
+ * @clk_rate:		NAND controller current clock rate
+ * @chips:		a list containing all the NAND chips attached to
+ *			this NAND controller
+ * @complete:		a completion object used to wait for NAND
+ *			controller events
+ */
+struct sunxi_nfc {
+	struct nand_hw_control controller;
+	const struct sunxi_nand * regs;
+	struct clk *ahb_clk;
+	struct clk *mod_clk;
+	unsigned long assigned_cs;
+	unsigned long clk_rate;
+	struct list_head chips;
+};
+
+static inline struct sunxi_nfc *to_sunxi_nfc(struct nand_hw_control *ctrl)
+{
+	return container_of(ctrl, struct sunxi_nfc, controller);
+}
+
+static void sunxi_set_clk_rate(unsigned long hz)
+{
+	struct sunxi_ccm_reg *const ccm =
+		(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
+
+	uint32_t rval = readl(&ccm->pll5_cfg);
+	int n = (rval >> 8) & 0x1F;
+	int k = ((rval >> 4) & 0x3) + 1;
+	int p = (rval >> 16) & 0x3;
+	
+	unsigned long clk_rate = 24000000 * n * k >> p;
+
+	unsigned long edo_clk = hz *2;
+	int div_n = 0, div_m;
+
+	unsigned long nand_clk_divid_ratio = clk_rate / edo_clk;
+
+	if (clk_rate % edo_clk)
+		nand_clk_divid_ratio++;
+
+	for (div_m = nand_clk_divid_ratio; div_m > 16 && div_n < 3; div_n++) {
+		if (div_m % 2)
+			div_m++;
+		div_m >>= 1;
+	}
+	div_m--;
+	if (div_m > 15)
+		div_m = 15;	/* Overflow */
+
+	/* config mod clock */
+	clrsetbits_le32(&ccm->nand0_clk_cfg, 3 << 24, 2 << 24);      /* 0 = OSC24M, 1 = PLL6, 2 = PLL5 */
+	clrsetbits_le32(&ccm->nand0_clk_cfg, 3 << 16, div_n << 16);
+	clrsetbits_le32(&ccm->nand0_clk_cfg, 0xf << 0, div_m << 0);
+	
+	/*gate on nand clock*/
+	setbits_le32(&ccm->ahb_gate0, (1 << AHB_GATE_OFFSET_NAND0));
+#ifdef CONFIG_MACH_SUN9I
+	setbits_le32(&ccm->ahb_gate1, (1 << AHB_GATE_OFFSET_DMA));
+#else
+	setbits_le32(&ccm->ahb_gate0, (1 << AHB_GATE_OFFSET_DMA));
+#endif
+	setbits_le32(&ccm->nand0_clk_cfg, 0x80000000);
+}
+
+static int sunxi_nfc_wait_int(struct sunxi_nfc *nfc, u32 flags,
+                          unsigned int timeout_ms)
+{
+       u32 time_start;
+
+	if (!timeout_ms)
+		timeout_ms = (CONFIG_SYS_HZ * SUNXI_NAND_DEFAULT_TIMEOUT_MS) / 1000;
+
+	time_start = get_timer(0);
+	
+	do {
+		if ((readl(&nfc->regs->st) & flags) == flags) {
+			setbits_le32(&nfc->regs->st, flags);
+			return 0;
+		}
+	} while (get_timer(time_start) < timeout_ms);
+
+        pr_err("Timeout waiting for interrupt\n");
+	return -ETIMEDOUT;
+}
+
+static void sunxi_nfc_wait_cmd_fifo_empty(struct sunxi_nfc *nfc)
+{
+	u32 timeout = (CONFIG_SYS_HZ * SUNXI_NAND_DEFAULT_TIMEOUT_MS) / 1000;
+	u32 time_start;
+
+	time_start = get_timer(0);
+
+	do {
+		if (!(readl(&nfc->regs->st) & SUNXI_NAND_ST_FIFO_FULL))
+			return;
+	} while (get_timer(time_start) < timeout);
+
+	pr_err("Timeout waiting for empty fifo\n");
+}
+
+static void sunxi_nfc_rst(struct sunxi_nfc *nfc)
+{
+	u32 timeout = (CONFIG_SYS_HZ * SUNXI_NAND_DEFAULT_TIMEOUT_MS) / 1000;
+	u32 time_start;
+
+	time_start = get_timer(0);
+
+	writel(0, &nfc->regs->ecc_ctl);
+	writel(SUNXI_NAND_CTL_RST, &nfc->regs->ctl);
+
+	do {
+		if (!(readl(&nfc->regs->ctl) & SUNXI_NAND_CTL_RST))
+			return;
+	} while (get_timer(time_start) < timeout);
+
+	pr_err("Timeout waiting for reset\n");
+}
+
+static int sunxi_nfc_dev_ready(struct mtd_info *mtd)
+{
+	struct nand_chip *nand = mtd->priv;
+	struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+	struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
+	struct sunxi_nand_rb *rb;
+	unsigned long timeo = (sunxi_nand->nand.state == FL_ERASING ? 400 : 20);
+	int ret;
+
+	if (sunxi_nand->selected < 0)
+		return 0;
+
+	rb = &sunxi_nand->sels[sunxi_nand->selected].rb;
+
+	switch (rb->type) {
+	case RB_NATIVE:
+		ret = !!(readl(&nfc->regs->st) &
+			 (SUNXI_NAND_ST_RB_STATE0 << rb->info.nativeid));
+		if (ret)
+			break;
+
+		sunxi_nfc_wait_int(nfc, SUNXI_NAND_ST_RB_B2R, timeo);
+		ret = !!(readl(&nfc->regs->st) &
+			 (SUNXI_NAND_ST_RB_STATE0 << rb->info.nativeid));
+		break;
+	case RB_GPIO:
+		/*check this*/
+		ret = gpio_get_value(rb->info.gpio);
+		break;
+	case RB_NONE:
+	default:
+		ret = 0;
+		pr_err("cannot check R/B NAND status!");
+		break;
+	}
+
+	return ret;
+}
+
+static void sunxi_nfc_select_chip(struct mtd_info *mtd, int chip)
+{
+	struct nand_chip *nand = mtd->priv;
+	struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+	struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
+	struct sunxi_nand_chip_sel *sel;
+	u32 ctl;
+
+	if (chip > 0 && chip >= sunxi_nand->nsels)
+		return;
+
+	if (chip == sunxi_nand->selected)
+		return;
+
+	ctl = readl(&nfc->regs->ctl) &
+	      ~(SUNXI_NAND_CTL_CE_SEL_MASK | SUNXI_NAND_CTL_RB_SEL_MASK |
+		SUNXI_NAND_CTL_EN | SUNXI_NAND_CTL_PAGE_SIZE_MASK);
+
+	if (chip >= 0) {
+		sel = &sunxi_nand->sels[chip];
+
+		ctl |= SUNXI_NAND_CTL_CE_SEL(sel->cs) | SUNXI_NAND_CTL_EN |
+		       (((nand->page_shift - 10) & 0xf) << 8);
+		if (sel->rb.type == RB_NONE) {
+			nand->dev_ready = NULL;
+		} else {
+			nand->dev_ready = sunxi_nfc_dev_ready;
+			if (sel->rb.type == RB_NATIVE)
+				ctl |= SUNXI_NAND_CTL_RB_SEL(sel->rb.info.nativeid);
+		}
+
+		writel(mtd->writesize, &nfc->regs->spare_area);
+
+		if (nfc->clk_rate != sunxi_nand->clk_rate) {
+			sunxi_set_clk_rate(sunxi_nand->clk_rate);
+			nfc->clk_rate = sunxi_nand->clk_rate;
+		}
+	}
+
+	writel(ctl, &nfc->regs->ctl);
+
+	sunxi_nand->selected = chip;
+}
+
+static void sunxi_nfc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
+{
+	struct nand_chip *nand = mtd->priv;
+	struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+	struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
+	int cnt;
+	int offs = 0;
+	u32 tmp;
+
+	while (len > offs) {
+		cnt = len - offs;
+		if (cnt > 1024)
+			cnt = 1024;
+
+		sunxi_nfc_wait_cmd_fifo_empty(nfc);
+		writel(cnt & 0x3ff, &nfc->regs->data_cnt);
+		tmp = SUNXI_NAND_CMD_DATA_TRANS | SUNXI_NAND_CMD_DATA_SWAP_METHOD;
+		writel(tmp, &nfc->regs->cmd);
+		sunxi_nfc_wait_int(nfc, SUNXI_NAND_ST_CMD_INT, 0);
+		if (buf)
+			memcpy_fromio(buf + offs, &nfc->regs->ram0_base, cnt);
+		offs += cnt;
+	}
+}
+
+static void sunxi_nfc_write_buf(struct mtd_info *mtd, const uint8_t *buf,
+				int len)
+{
+	struct nand_chip *nand = mtd->priv;
+	struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+	struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
+	int cnt;
+	int offs = 0;
+	u32 tmp;
+
+	while (len > offs) {
+		cnt = len - offs;
+		if (cnt > 1024)
+			cnt = 1024;
+
+		sunxi_nfc_wait_cmd_fifo_empty(nfc);
+		writel(cnt, &nfc->regs->data_cnt);
+		memcpy_toio(&nfc->regs->ram0_base, buf + offs, cnt);
+		tmp = SUNXI_NAND_CMD_DATA_TRANS | SUNXI_NAND_CMD_DATA_SWAP_METHOD |
+		      SUNXI_NAND_CMD_ACCESS_WR;
+		writel(tmp, &nfc->regs->cmd);
+		sunxi_nfc_wait_int(nfc, SUNXI_NAND_ST_CMD_INT, 0);
+		offs += cnt;
+	}
+}
+
+static u16 sunxi_nfc_hwrnd_step(struct sunxi_nand_hw_rnd *rnd, u16 state, int count)
+{
+	state &= 0x7fff;
+	count *= 8;
+	while (count--)
+		state = ((state >> 1) |
+			 ((((state >> 0) ^ (state >> 1)) & 1) << 14)) & 0x7fff;
+
+	return state;
+}
+
+static u16 sunxi_nfc_hwrnd_single_step(u16 state, int count)
+{
+	state &= 0x7fff;
+	while (count--)
+		state = ((state >> 1) |
+			 ((((state >> 0) ^ (state >> 1)) & 1) << 14)) & 0x7fff;
+
+	return state;
+}
+
+static int sunxi_nfc_hwrnd_config(struct mtd_info *mtd, int page, int column,
+				  enum nand_rnd_action action)
+{
+	struct nand_chip *nand = mtd->priv;
+	struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+	struct sunxi_nand_hw_rnd *rnd = nand->rnd.priv;
+	u16 state;
+
+	if (page < 0 && column < 0) {
+		rnd->page = -1;
+		rnd->column = -1;
+		return 0;
+	}
+
+	if (column < 0)
+		column = 0;
+	if (page < 0)
+		page = rnd->page;
+
+	if (page < 0)
+		return -EINVAL;
+
+	if (page != rnd->page && action == NAND_RND_READ) {
+		int status;
+
+		status = nand_page_get_status(mtd, page);
+		if (status == NAND_PAGE_STATUS_UNKNOWN) {
+			nand->cmdfunc(mtd, NAND_CMD_RNDOUT, 0, -1);
+			sunxi_nfc_read_buf(mtd, sunxi_nand->buffer,
+					   mtd->writesize + mtd->oobsize);
+
+			if (nand_page_is_empty(mtd, sunxi_nand->buffer,
+					       sunxi_nand->buffer +
+					       mtd->writesize))
+				status = NAND_PAGE_EMPTY;
+			else
+				status = NAND_PAGE_FILLED;
+
+			nand_page_set_status(mtd, page, status);
+			nand->cmdfunc(mtd, NAND_CMD_RNDOUT, column, -1);
+		}
+	}
+
+	state = rnd->seeds[page % rnd->nseeds];
+	rnd->page = page;
+	rnd->column = column;
+
+	if (rnd->step) {
+		rnd->state = rnd->step(mtd, state, column, &rnd->left);
+	} else {
+		rnd->state = sunxi_nfc_hwrnd_step(rnd, state, column % 4096);
+		rnd->left = mtd->oobsize + mtd->writesize - column;
+	}
+
+	return 0;
+}
+
+static void sunxi_nfc_hwrnd_write_buf(struct mtd_info *mtd, const uint8_t *buf,
+				      int len)
+{
+	struct nand_chip *nand = mtd->priv;
+	struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+	struct sunxi_nand_hw_rnd *rnd = nand->rnd.priv;
+	u32 tmp = readl(&nfc->regs->ecc_ctl);
+	int cnt;
+	int offs = 0;
+	int rndactiv;
+
+	tmp &= ~(SUNXI_NAND_ECC_CTL_RND_DIRECTION |
+			SUNXI_NAND_ECC_CTL_RND_SEED_MASK |
+			SUNXI_NAND_ECC_CTL_RND_EN);
+	writel(tmp, &nfc->regs->ecc_ctl);
+
+	if (rnd->page < 0) {
+		sunxi_nfc_write_buf(mtd, buf, len);
+		return;
+	}
+
+	while (len > offs) {
+		cnt = len - offs;
+		if (cnt > 1024)
+			cnt = 1024;
+
+		rndactiv = nand_rnd_is_activ(mtd, rnd->page, rnd->column,
+					     &cnt);
+		if (rndactiv > 0) {
+			writel(tmp | SUNXI_NAND_ECC_CTL_RND_EN |
+					SUNXI_NAND_ECC_CTL_RND_SEED(rnd->state),
+			       &nfc->regs->ecc_ctl);
+			if (rnd->left < cnt)
+				cnt = rnd->left;
+		}
+
+		sunxi_nfc_write_buf(mtd, buf + offs, cnt);
+
+		if (rndactiv > 0)
+			writel(tmp & ~SUNXI_NAND_ECC_CTL_RND_EN,
+			       &nfc->regs->ecc_ctl);
+
+		offs += cnt;
+		if (len <= offs)
+			break;
+
+		sunxi_nfc_hwrnd_config(mtd, -1, rnd->column + cnt, NAND_RND_WRITE);
+	}
+}
+
+static void sunxi_nfc_hwrnd_read_buf(struct mtd_info *mtd, uint8_t *buf,
+				     int len)
+{
+	struct nand_chip *nand = mtd->priv;
+	struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+	struct sunxi_nand_hw_rnd *rnd = nand->rnd.priv;
+	u32 tmp = readl(&nfc->regs->ecc_ctl);
+	int cnt;
+	int offs = 0;
+	int rndactiv;
+
+	tmp &= ~(SUNXI_NAND_ECC_CTL_RND_DIRECTION |
+			SUNXI_NAND_ECC_CTL_RND_SEED_MASK |
+			SUNXI_NAND_ECC_CTL_RND_EN);
+	writel(tmp, &nfc->regs->ecc_ctl);
+
+	if (rnd->page < 0) {
+		sunxi_nfc_read_buf(mtd, buf, len);
+		return;
+	}
+
+	while (len > offs) {
+		cnt = len - offs;
+		if (cnt > 1024)
+			cnt = 1024;
+
+		if (nand_page_get_status(mtd, rnd->page) != NAND_PAGE_EMPTY &&
+		    nand_rnd_is_activ(mtd, rnd->page, rnd->column, &cnt) > 0)
+			rndactiv = 1;
+		else
+			rndactiv = 0;
+
+		if (rndactiv > 0) {
+			writel(tmp | SUNXI_NAND_ECC_CTL_RND_EN |
+					SUNXI_NAND_ECC_CTL_RND_SEED(rnd->state),
+			       &nfc->regs->ecc_ctl);
+			if (rnd->left < cnt)
+				cnt = rnd->left;
+		}
+
+		if (buf)
+			sunxi_nfc_read_buf(mtd, buf + offs, cnt);
+		else
+			sunxi_nfc_read_buf(mtd, NULL, cnt);
+
+		if (rndactiv > 0)
+			writel(tmp & ~SUNXI_NAND_ECC_CTL_RND_EN,
+			       &nfc->regs->ecc_ctl);
+
+		offs += cnt;
+		if (len <= offs)
+			break;
+
+		sunxi_nfc_hwrnd_config(mtd, -1, rnd->column + cnt, NAND_RND_READ);
+	}
+}
+static uint8_t sunxi_nfc_read_byte(struct mtd_info *mtd)
+{
+	uint8_t ret;
+
+	sunxi_nfc_read_buf(mtd, &ret, 1);
+
+	return ret;
+}
+
+static void sunxi_nfc_cmd_ctrl(struct mtd_info *mtd, int dat,
+			       unsigned int ctrl)
+{
+	struct nand_chip *nand = mtd->priv;
+	struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+	struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
+	u32 tmp;
+
+	sunxi_nfc_wait_cmd_fifo_empty(nfc);
+
+	if (ctrl & NAND_CTRL_CHANGE) {
+		tmp = readl(&nfc->regs->ctl);
+		if (ctrl & NAND_NCE)
+			tmp |= SUNXI_NAND_CTL_CE_ACT;
+		else
+			tmp &= ~SUNXI_NAND_CTL_CE_ACT;
+		writel(tmp, &nfc->regs->ctl);
+	}
+
+	if (dat == NAND_CMD_NONE)
+		return;
+
+	if (ctrl & NAND_CLE) {
+		writel(SUNXI_NAND_CMD_SEND_CMD1 | dat, &nfc->regs->cmd);
+	} else {
+		writel(dat, &nfc->regs->addr_low);
+		writel(SUNXI_NAND_CMD_SEND_ADR, &nfc->regs->cmd);
+	}
+
+	sunxi_nfc_wait_int(nfc, SUNXI_NAND_ST_CMD_INT, 0);
+}
+
+static int sunxi_nfc_hw_ecc_read_page(struct mtd_info *mtd,
+				      struct nand_chip *chip, uint8_t *buf,
+				      int oob_required, int page)
+{
+	struct sunxi_nfc *nfc = to_sunxi_nfc(chip->controller);
+	struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(chip);
+	struct nand_ecc_ctrl *ecc = &chip->ecc;
+	struct nand_ecclayout *layout = ecc->layout;
+	struct sunxi_nand_hw_ecc *data = ecc->priv;
+	unsigned int max_bitflips = 0;
+	int status;
+	int offset;
+	u32 tmp;
+	int i;
+	int cnt;
+
+	status = nand_page_get_status(mtd, page);
+	if (status == NAND_PAGE_STATUS_UNKNOWN) {
+		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, 0, -1);
+		sunxi_nfc_read_buf(mtd, sunxi_nand->buffer,
+				   mtd->writesize + mtd->oobsize);
+
+		if (nand_page_is_empty(mtd, sunxi_nand->buffer,
+				       sunxi_nand->buffer +
+				       mtd->writesize)) {
+			status = NAND_PAGE_EMPTY;
+		} else {
+			status = NAND_PAGE_FILLED;
+			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, 0, -1);
+		}
+
+		nand_page_set_status(mtd, page, status);
+	}
+
+	if (status == NAND_PAGE_EMPTY) {
+		memset(buf, 0xff, mtd->writesize);
+		if (oob_required)
+			memset(chip->oob_poi, 0xff, mtd->oobsize);
+		return 0;
+	}
+
+	tmp = readl(&nfc->regs->ecc_ctl);
+	tmp &= ~(SUNXI_NAND_ECC_CTL_MODE_MASK | SUNXI_NAND_ECC_CTL_PIPELINE | SUNXI_NAND_ECC_CTL_BS_512B);
+	tmp |= SUNXI_NAND_ECC_CTL_ECC_EN | SUNXI_NAND_ECC_CTL_MODE(data->mode) |
+	       SUNXI_NAND_ECC_CTL_EXCEPTION;
+
+	writel(tmp, &nfc->regs->ecc_ctl);
+
+	for (i = 0; i < ecc->steps; i++) {
+		bool rndactiv = false;
+
+		if (i)
+			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, i * ecc->size, -1);
+
+		offset = mtd->writesize + layout->eccpos[i * ecc->bytes] - 4;
+
+		nand_rnd_config(mtd, page, i * ecc->size, NAND_RND_READ);
+		nand_rnd_read_buf(mtd, NULL, ecc->size);
+
+		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, offset, -1);
+		sunxi_nfc_wait_cmd_fifo_empty(nfc);
+
+		if (i) {
+			cnt = ecc->bytes + 4;
+			if (nand_rnd_is_activ(mtd, page, offset, &cnt) > 0 &&
+			    cnt == ecc->bytes + 4)
+				rndactiv = true;
+		} else {
+			cnt = ecc->bytes + 2;
+			if (nand_rnd_is_activ(mtd, page, offset + 2, &cnt) > 0 &&
+			    cnt == ecc->bytes + 2)
+				rndactiv = true;
+		}
+
+		if (rndactiv) {
+			tmp = readl(&nfc->regs->ecc_ctl);
+			tmp &= ~(SUNXI_NAND_ECC_CTL_RND_DIRECTION | SUNXI_NAND_ECC_CTL_EXCEPTION);
+			tmp |= SUNXI_NAND_ECC_CTL_RND_EN;
+			writel(tmp, &nfc->regs->ecc_ctl);
+		}
+
+		tmp = SUNXI_NAND_CMD_DATA_TRANS | SUNXI_NAND_CMD_DATA_SWAP_METHOD | (1 << 30);
+		writel(tmp, &nfc->regs->cmd);
+
+		sunxi_nfc_wait_int(nfc, SUNXI_NAND_ST_CMD_INT, 0);
+		memcpy_fromio(buf + (i * ecc->size),
+			      &nfc->regs->ram0_base, ecc->size);
+
+		writel(readl(&nfc->regs->ecc_ctl) & ~SUNXI_NAND_ECC_CTL_RND_EN,
+		       &nfc->regs->ecc_ctl);
+
+		if (readl(&nfc->regs->ecc_st) & 0x1) {
+			mtd->ecc_stats.failed++;
+		} else {
+			tmp = readl(&nfc->regs->err_cnt[0]) & 0xff;
+			mtd->ecc_stats.corrected += tmp;
+			max_bitflips = max_t(unsigned int, max_bitflips, tmp);
+		}
+
+		if (oob_required) {
+			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, offset, -1);
+			sunxi_nfc_wait_cmd_fifo_empty(nfc);
+			nand_rnd_config(mtd, -1, offset, NAND_RND_READ);
+			offset -= mtd->writesize;
+			nand_rnd_read_buf(mtd, chip->oob_poi + offset,
+					  ecc->bytes + 4);
+		}
+	}
+
+	if (oob_required) {
+		cnt = ecc->layout->oobfree[ecc->steps].length;
+		if (cnt > 0) {
+			offset = mtd->writesize +
+				 ecc->layout->oobfree[ecc->steps].offset;
+			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, offset, -1);
+			nand_rnd_config(mtd, -1, offset, NAND_RND_READ);
+			offset -= mtd->writesize;
+			nand_rnd_read_buf(mtd, chip->oob_poi + offset, cnt);
+		}
+	}
+
+	nand_rnd_config(mtd, -1, -1, NAND_RND_READ);
+
+	tmp = readl(&nfc->regs->ecc_ctl);
+	tmp &= ~SUNXI_NAND_ECC_CTL_ECC_EN;
+
+	writel(tmp, &nfc->regs->ecc_ctl);
+
+	return max_bitflips;
+}
+
+static int sunxi_nfc_hw_ecc_write_page(struct mtd_info *mtd,
+				       struct nand_chip *chip,
+				       const uint8_t *buf, int oob_required)
+{
+	struct sunxi_nfc *nfc = to_sunxi_nfc(chip->controller);
+	struct nand_ecc_ctrl *ecc = &chip->ecc;
+	struct nand_ecclayout *layout = ecc->layout;
+	struct sunxi_nand_hw_ecc *data = ecc->priv;
+	struct sunxi_nand_hw_rnd *rnd = chip->rnd.priv;
+	int offset;
+	u32 tmp;
+	int i;
+	int cnt;
+
+	tmp = readl(&nfc->regs->ecc_ctl);
+	tmp &= ~(SUNXI_NAND_ECC_CTL_MODE_MASK | SUNXI_NAND_ECC_CTL_PIPELINE | SUNXI_NAND_ECC_CTL_BS_512B);
+	tmp |= SUNXI_NAND_ECC_CTL_ECC_EN | SUNXI_NAND_ECC_CTL_MODE(data->mode) |
+	       SUNXI_NAND_ECC_CTL_EXCEPTION;
+
+	writel(tmp, &nfc->regs->ecc_ctl);
+
+	for (i = 0; i < mtd->writesize / ecc->size; i++) {
+		bool rndactiv = false;
+		u8 oob_buf[4];
+
+		if (i)
+			chip->cmdfunc(mtd, NAND_CMD_RNDIN, i * ecc->size, -1);
+
+		nand_rnd_config(mtd, -1, i * ecc->size, NAND_RND_WRITE);
+		nand_rnd_write_buf(mtd, buf + (i * ecc->size), ecc->size);
+
+		offset = layout->eccpos[i * ecc->bytes] - 4 + mtd->writesize;
+
+		/* Fill OOB data in */
+		if (!oob_required)
+			memset(oob_buf, 0xff, 4);
+		else
+			memcpy(oob_buf,
+			       chip->oob_poi + layout->oobfree[i].offset,
+			       4);
+
+
+		memcpy_toio(nfc->regs->user_data, oob_buf, 4);
+
+		if (i) {
+			cnt = ecc->bytes + 4;
+			if (rnd &&
+			    nand_rnd_is_activ(mtd, -1, offset, &cnt) > 0 &&
+			    cnt == ecc->bytes + 4)
+				rndactiv = true;
+		} else {
+			cnt = ecc->bytes + 2;
+			if (rnd &&
+			    nand_rnd_is_activ(mtd, -1, offset + 2, &cnt) > 0 &&
+			    cnt == ecc->bytes + 2)
+				rndactiv = true;
+		}
+
+		if (rndactiv) {
+			/* pre randomize to generate FF patterns on the NAND */
+			if (!i) {
+				u16 state = rnd->subseeds[rnd->page % rnd->nseeds];
+				state = sunxi_nfc_hwrnd_single_step(state, 15);
+				oob_buf[0] ^= state;
+				state = sunxi_nfc_hwrnd_step(rnd, state, 1);
+				oob_buf[1] ^= state;
+				memcpy_toio(nfc->regs->user_data, oob_buf, 4);
+			}
+			tmp = readl(&nfc->regs->ecc_ctl);
+			tmp &= ~(SUNXI_NAND_ECC_CTL_RND_DIRECTION | SUNXI_NAND_ECC_CTL_EXCEPTION);
+			tmp |= SUNXI_NAND_ECC_CTL_RND_EN;
+			writel(tmp, &nfc->regs->ecc_ctl);
+		}
+
+		chip->cmdfunc(mtd, NAND_CMD_RNDIN, offset, -1);
+		sunxi_nfc_wait_cmd_fifo_empty(nfc);
+
+		tmp = SUNXI_NAND_CMD_DATA_TRANS | SUNXI_NAND_CMD_DATA_SWAP_METHOD | SUNXI_NAND_CMD_ACCESS_WR |
+		      (1 << 30);
+		writel(tmp, &nfc->regs->cmd);
+		sunxi_nfc_wait_int(nfc, SUNXI_NAND_ST_CMD_INT, 0);
+
+		writel(readl(&nfc->regs->ecc_ctl) & ~SUNXI_NAND_ECC_CTL_RND_EN,
+		       &nfc->regs->ecc_ctl);
+	}
+
+	if (oob_required) {
+		cnt = ecc->layout->oobfree[i].length;
+		if (cnt > 0) {
+			offset = mtd->writesize +
+				 ecc->layout->oobfree[i].offset;
+			chip->cmdfunc(mtd, NAND_CMD_RNDIN, offset, -1);
+			nand_rnd_config(mtd, -1, offset, NAND_RND_WRITE);
+			offset -= mtd->writesize;
+			nand_rnd_write_buf(mtd, chip->oob_poi + offset, cnt);
+		}
+	}
+
+	nand_rnd_config(mtd, -1, -1, NAND_RND_WRITE);
+
+	tmp = readl(&nfc->regs->ecc_ctl);
+	tmp &= ~SUNXI_NAND_ECC_CTL_ECC_EN;
+
+	writel(tmp, &nfc->regs->ecc_ctl);
+
+	return 0;
+}
+
+static u16 sunxi_nfc_hw_ecc_rnd_steps(struct mtd_info *mtd, u16 state,
+				      int column, int *left)
+{
+	struct nand_chip *chip = mtd->priv;
+	struct nand_ecc_ctrl *ecc = &chip->ecc;
+	struct sunxi_nand_hw_rnd *rnd = chip->rnd.priv;
+	int nblks = mtd->writesize / ecc->size;
+	int modsize = ecc->size;
+	int steps;
+
+	if (column < mtd->writesize) {
+		steps = column % modsize;
+		*left = modsize - steps;
+	} else if (column < mtd->writesize +
+			    (nblks * (ecc->bytes + 4))) {
+		column -= mtd->writesize;
+		steps = column % (ecc->bytes + 4);
+		*left = ecc->bytes + 4 - steps;
+		state = rnd->subseeds[rnd->page % rnd->nseeds];
+	} else {
+		steps = column % 4096;
+		*left = mtd->writesize + mtd->oobsize - column;
+	}
+
+	return sunxi_nfc_hwrnd_step(rnd, state, steps);
+}
+
+static int sunxi_nfc_hw_syndrome_ecc_read_page(struct mtd_info *mtd,
+					       struct nand_chip *chip,
+					       uint8_t *buf, int oob_required,
+					       int page)
+{
+	struct sunxi_nfc *nfc = to_sunxi_nfc(chip->controller);
+	struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(chip);
+	struct nand_ecc_ctrl *ecc = &chip->ecc;
+	struct sunxi_nand_hw_ecc *data = ecc->priv;
+	int steps = mtd->writesize / ecc->size;
+	unsigned int max_bitflips = 0;
+	uint8_t *oob = chip->oob_poi;
+	int offset = 0;
+	int status;
+	int cnt;
+	u32 tmp;
+	int i;
+
+	status = nand_page_get_status(mtd, page);
+	if (status == NAND_PAGE_STATUS_UNKNOWN) {
+		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, 0, -1);
+		sunxi_nfc_read_buf(mtd, sunxi_nand->buffer,
+				   mtd->writesize + mtd->oobsize);
+
+		if (nand_page_is_empty(mtd, sunxi_nand->buffer,
+				       sunxi_nand->buffer +
+				       mtd->writesize)) {
+			status = NAND_PAGE_EMPTY;
+		} else {
+			status = NAND_PAGE_FILLED;
+			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, 0, -1);
+		}
+
+		nand_page_set_status(mtd, page, status);
+	}
+
+	if (status == NAND_PAGE_EMPTY) {
+		memset(buf, 0xff, mtd->writesize);
+		if (oob_required)
+			memset(chip->oob_poi, 0xff, mtd->oobsize);
+		return 0;
+	}
+
+	tmp = readl(&nfc->regs->ecc_ctl);
+	tmp &= ~(SUNXI_NAND_ECC_CTL_MODE_MASK | SUNXI_NAND_ECC_CTL_PIPELINE | SUNXI_NAND_ECC_CTL_BS_512B);
+	tmp |= SUNXI_NAND_ECC_CTL_ECC_EN | SUNXI_NAND_ECC_CTL_MODE(data->mode) |
+	       SUNXI_NAND_ECC_CTL_EXCEPTION;
+
+	writel(tmp, &nfc->regs->ecc_ctl);
+
+	for (i = 0; i < steps; i++) {
+		nand_rnd_config(mtd, page, offset, NAND_RND_READ);
+		nand_rnd_read_buf(mtd, NULL, ecc->size);
+
+		cnt = ecc->bytes + 4;
+		if (nand_rnd_is_activ(mtd, page, offset, &cnt) > 0 &&
+		    cnt == ecc->bytes + 4) {
+			tmp = readl(&nfc->regs->ecc_ctl);
+			tmp &= ~(SUNXI_NAND_ECC_CTL_RND_DIRECTION | SUNXI_NAND_ECC_CTL_EXCEPTION);
+			tmp |= SUNXI_NAND_ECC_CTL_RND_EN;
+			writel(tmp, &nfc->regs->ecc_ctl);
+		}
+
+		tmp = SUNXI_NAND_CMD_DATA_TRANS | SUNXI_NAND_CMD_DATA_SWAP_METHOD | (1 << 30);
+		writel(tmp, &nfc->regs->cmd);
+		sunxi_nfc_wait_int(nfc, SUNXI_NAND_ST_CMD_INT, 0);
+		memcpy_fromio(buf, &nfc->regs->ram0_base, ecc->size);
+		buf += ecc->size;
+		offset += ecc->size;
+
+		writel(readl(&nfc->regs->ecc_ctl) & ~SUNXI_NAND_ECC_CTL_RND_EN,
+		       &nfc->regs->ecc_ctl);
+
+		if (readl(&nfc->regs->ecc_st) & 0x1) {
+			mtd->ecc_stats.failed++;
+		} else {
+			tmp = readl(&nfc->regs->err_cnt[0]) & 0xff;
+			mtd->ecc_stats.corrected += tmp;
+			max_bitflips = max_t(unsigned int, max_bitflips, tmp);
+		}
+
+		if (oob_required) {
+			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, offset, -1);
+			nand_rnd_config(mtd, -1, offset, NAND_RND_READ);
+			nand_rnd_read_buf(mtd, oob, ecc->bytes + ecc->prepad);
+			oob += ecc->bytes + ecc->prepad;
+		}
+
+		offset += ecc->bytes + ecc->prepad;
+	}
+
+	if (oob_required) {
+		cnt = mtd->oobsize - (oob - chip->oob_poi);
+		if (cnt > 0) {
+			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, offset, -1);
+			nand_rnd_config(mtd, page, offset, NAND_RND_READ);
+			nand_rnd_read_buf(mtd, oob, cnt);
+		}
+	}
+
+	nand_rnd_config(mtd, -1, -1, NAND_RND_READ);
+
+	writel(readl(&nfc->regs->ecc_ctl) & ~SUNXI_NAND_ECC_CTL_ECC_EN,
+	       &nfc->regs->ecc_ctl);
+
+	return max_bitflips;
+}
+
+static int sunxi_nfc_hw_syndrome_ecc_write_page(struct mtd_info *mtd,
+						struct nand_chip *chip,
+						const uint8_t *buf,
+						int oob_required)
+{
+	struct sunxi_nfc *nfc = to_sunxi_nfc(chip->controller);
+	struct nand_ecc_ctrl *ecc = &chip->ecc;
+	struct sunxi_nand_hw_ecc *data = ecc->priv;
+	struct sunxi_nand_hw_rnd *rnd = chip->rnd.priv;
+	int steps = mtd->writesize / ecc->size;
+	uint8_t *oob = chip->oob_poi;
+	int offset = 0;
+	int cnt;
+	u32 tmp;
+	int i;
+
+	tmp = readl(&nfc->regs->ecc_ctl);
+	tmp &= ~(SUNXI_NAND_ECC_CTL_MODE_MASK | SUNXI_NAND_ECC_CTL_PIPELINE | SUNXI_NAND_ECC_CTL_BS_512B);
+	tmp |= SUNXI_NAND_ECC_CTL_ECC_EN | SUNXI_NAND_ECC_CTL_MODE(data->mode) |
+	       SUNXI_NAND_ECC_CTL_EXCEPTION;
+
+	writel(tmp, &nfc->regs->ecc_ctl);
+
+	for (i = 0; i < steps; i++) {
+		nand_rnd_config(mtd, -1, offset, NAND_RND_WRITE);
+		nand_rnd_write_buf(mtd, buf + (i * ecc->size), ecc->size);
+		offset += ecc->size;
+
+		/* Fill OOB data in */
+		if (oob_required) {
+			tmp = 0xffffffff;
+			memcpy_toio(nfc->regs->user_data, &tmp,
+				    4);
+		} else {
+			memcpy_toio(nfc->regs->user_data, oob ,
+				    4);
+		}
+
+		cnt = ecc->bytes + 4;
+		if (rnd &&
+		    nand_rnd_is_activ(mtd, rnd->page, offset, &cnt) > 0 &&
+		    cnt == ecc->bytes + 4) {
+			tmp = readl(&nfc->regs->ecc_ctl);
+			tmp &= ~(SUNXI_NAND_ECC_CTL_RND_DIRECTION | SUNXI_NAND_ECC_CTL_EXCEPTION);
+			tmp |= SUNXI_NAND_ECC_CTL_RND_EN;
+			writel(tmp, &nfc->regs->ecc_ctl);
+		}
+
+		tmp = SUNXI_NAND_CMD_DATA_TRANS | SUNXI_NAND_CMD_DATA_SWAP_METHOD | SUNXI_NAND_CMD_ACCESS_WR |
+		      (1 << 30);
+		writel(tmp, &nfc->regs->cmd);
+		sunxi_nfc_wait_int(nfc, SUNXI_NAND_ST_CMD_INT, 0);
+
+		writel(readl(&nfc->regs->ecc_ctl) & ~SUNXI_NAND_ECC_CTL_RND_EN,
+		       &nfc->regs->ecc_ctl);
+
+		offset += ecc->bytes + ecc->prepad;
+		oob += ecc->bytes + ecc->prepad;
+	}
+
+	if (oob_required) {
+		cnt = mtd->oobsize - (oob - chip->oob_poi);
+		if (cnt > 0) {
+			chip->cmdfunc(mtd, NAND_CMD_RNDIN, offset, -1);
+			nand_rnd_config(mtd, -1, offset, NAND_RND_WRITE);
+			nand_rnd_write_buf(mtd, oob, cnt);
+		}
+	}
+	nand_rnd_config(mtd, -1, -1, NAND_RND_WRITE);
+
+	tmp = readl(&nfc->regs->ecc_ctl);
+	tmp &= ~SUNXI_NAND_ECC_CTL_ECC_EN;
+
+	writel(tmp, &nfc->regs->ecc_ctl);
+
+	return 0;
+}
+
+static u16 sunxi_nfc_hw_syndrome_ecc_rnd_steps(struct mtd_info *mtd, u16 state,
+					       int column, int *left)
+{
+	struct nand_chip *chip = mtd->priv;
+	struct nand_ecc_ctrl *ecc = &chip->ecc;
+	struct sunxi_nand_hw_rnd *rnd = chip->rnd.priv;
+	int eccsteps = mtd->writesize / ecc->size;
+	int modsize = ecc->size + ecc->prepad + ecc->bytes;
+	int steps;
+
+	if (column < (eccsteps * modsize)) {
+		steps = column % modsize;
+		*left = modsize - steps;
+		if (steps >= ecc->size) {
+			steps -= ecc->size;
+			state = rnd->subseeds[rnd->page % rnd->nseeds];
+		}
+	} else {
+		steps = column % 4096;
+		*left = mtd->writesize + mtd->oobsize - column;
+	}
+
+	return sunxi_nfc_hwrnd_step(rnd, state, steps);
+}
+
+static u16 default_seeds[] = {0x4a80};
+#ifndef __UBOOT__
+static void sunxi_nand_rnd_ctrl_cleanup(struct nand_rnd_ctrl *rnd)
+{
+	struct sunxi_nand_hw_rnd *hwrnd = rnd->priv;
+
+	if (hwrnd->seeds != default_seeds)
+		kfree(hwrnd->seeds);
+	kfree(hwrnd->subseeds);
+	kfree(rnd->layout);
+	kfree(hwrnd);
+}
+#endif
+
+static int sunxi_nand_rnd_ctrl_init(int node, struct mtd_info *mtd,
+				    struct nand_rnd_ctrl *rnd,
+				    struct nand_ecc_ctrl *ecc)
+{
+	struct sunxi_nand_hw_rnd *hwrnd;
+	struct nand_rnd_layout *layout = NULL;
+	int ret;
+
+	hwrnd = kzalloc(sizeof(*hwrnd), GFP_KERNEL);
+	if (!hwrnd)
+		return -ENOMEM;
+
+	hwrnd->seeds = default_seeds;
+	hwrnd->nseeds = ARRAY_SIZE(default_seeds);
+	
+	if(fdt_getprop(gd->fdt_blob, node, "nand-randomizer-seeds", &ret)){
+		hwrnd->nseeds = ret / sizeof(*hwrnd->seeds);
+		hwrnd->seeds = kzalloc(hwrnd->nseeds * sizeof(*hwrnd->seeds),
+				       GFP_KERNEL);
+		if (!hwrnd->seeds) {
+			ret = -ENOMEM;
+			goto err;
+		}
+
+		ret = fdtdec_get_u16_array(gd->fdt_blob, node, "nand-randomizer-seeds",
+						 hwrnd->seeds, hwrnd->nseeds);
+		if (ret)
+			goto err;
+	}
+
+	switch (ecc->mode) {
+	case NAND_ECC_HW_SYNDROME:
+		hwrnd->step = sunxi_nfc_hw_syndrome_ecc_rnd_steps;
+		break;
+
+	case NAND_ECC_HW:
+		hwrnd->step = sunxi_nfc_hw_ecc_rnd_steps;
+
+	default:
+		layout = kzalloc(sizeof(*layout) + sizeof(struct nand_rndfree),
+				 GFP_KERNEL);
+		if (!layout) {
+			ret = -ENOMEM;
+			goto err;
+		}
+		layout->nranges = 1;
+		layout->ranges[0].offset = mtd->writesize;
+		layout->ranges[0].length = 2;
+		rnd->layout = layout;
+		break;
+	}
+
+	if (ecc->mode == NAND_ECC_HW_SYNDROME || ecc->mode == NAND_ECC_HW) {
+		int i;
+
+		hwrnd->subseeds = kzalloc(hwrnd->nseeds *
+					  sizeof(*hwrnd->subseeds),
+					  GFP_KERNEL);
+		if (!hwrnd->subseeds) {
+			ret = -ENOMEM;
+			goto err;
+		}
+
+		for (i = 0; i < hwrnd->nseeds; i++)
+			hwrnd->subseeds[i] = sunxi_nfc_hwrnd_step(hwrnd,
+							hwrnd->seeds[i],
+							ecc->size);
+	}
+
+	rnd->config = sunxi_nfc_hwrnd_config;
+	rnd->read_buf = sunxi_nfc_hwrnd_read_buf;
+	rnd->write_buf = sunxi_nfc_hwrnd_write_buf;
+	rnd->priv = hwrnd;
+
+	return 0;
+
+err:
+	kfree(hwrnd);
+	kfree(layout);
+
+	return ret;
+}
+
+static int sunxi_nand_chip_set_timings(struct sunxi_nand_chip *chip,
+				       const struct nand_sdr_timings *timings)
+{
+	u32 min_clk_period = 0;
+
+	/* T1 <=> tCLS */
+	if (timings->tCLS_min > min_clk_period)
+		min_clk_period = timings->tCLS_min;
+
+	/* T2 <=> tCLH */
+	if (timings->tCLH_min > min_clk_period)
+		min_clk_period = timings->tCLH_min;
+
+	/* T3 <=> tCS */
+	if (timings->tCS_min > min_clk_period)
+		min_clk_period = timings->tCS_min;
+
+	/* T4 <=> tCH */
+	if (timings->tCH_min > min_clk_period)
+		min_clk_period = timings->tCH_min;
+
+	/* T5 <=> tWP */
+	if (timings->tWP_min > min_clk_period)
+		min_clk_period = timings->tWP_min;
+
+	/* T6 <=> tWH */
+	if (timings->tWH_min > min_clk_period)
+		min_clk_period = timings->tWH_min;
+
+	/* T7 <=> tALS */
+	if (timings->tALS_min > min_clk_period)
+		min_clk_period = timings->tALS_min;
+
+	/* T8 <=> tDS */
+	if (timings->tDS_min > min_clk_period)
+		min_clk_period = timings->tDS_min;
+
+	/* T9 <=> tDH */
+	if (timings->tDH_min > min_clk_period)
+		min_clk_period = timings->tDH_min;
+
+	/* T10 <=> tRR */
+	if (timings->tRR_min > (min_clk_period * 3))
+		min_clk_period = (timings->tRR_min + 2) / 3;
+
+	/* T11 <=> tALH */
+	if (timings->tALH_min > min_clk_period)
+		min_clk_period = timings->tALH_min;
+
+	/* T12 <=> tRP */
+	if (timings->tRP_min > min_clk_period)
+		min_clk_period = timings->tRP_min;
+
+	/* T13 <=> tREH */
+	if (timings->tREH_min > min_clk_period)
+		min_clk_period = timings->tREH_min;
+
+	/* T14 <=> tRC */
+	if (timings->tRC_min > (min_clk_period * 2))
+		min_clk_period = (timings->tRC_min + 1) / 2;
+
+	/* T15 <=> tWC */
+	if (timings->tWC_min > (min_clk_period * 2))
+		min_clk_period = (timings->tWC_min + 1) / 2;
+
+
+	/* min_clk_period = (NAND-clk-period * 2) */
+	if (min_clk_period < 1000)
+		min_clk_period = 1000;
+
+	min_clk_period /= 1000;
+	chip->clk_rate = (2 * 1000000000) / min_clk_period;
+
+	/* TODO: configure T16-T19 */
+
+	return 0;
+}
+
+static int sunxi_nand_chip_init_timings(struct sunxi_nand_chip *chip)
+{
+	const struct nand_sdr_timings *timings;
+	int ret;
+	int mode;
+
+	mode = onfi_get_async_timing_mode(&chip->nand);
+	if (mode == ONFI_TIMING_MODE_UNKNOWN) {
+		mode = chip->nand.onfi_timing_mode_ds;
+	} else {
+		uint8_t feature[ONFI_SUBFEATURE_PARAM_LEN] = {};
+
+		mode = fls(mode) - 1;
+		if (mode < 0)
+			mode = 0;
+
+		feature[0] = mode;
+		ret = chip->nand.onfi_set_features(&chip->mtd, &chip->nand,
+						ONFI_FEATURE_ADDR_TIMING_MODE,
+						feature);
+		if (ret)
+			return ret;
+	}
+
+	timings = onfi_async_timing_mode_to_sdr_timings(mode);
+	if (IS_ERR(timings))
+		return PTR_ERR(timings);
+
+	return sunxi_nand_chip_set_timings(chip, timings);
+}
+
+static int sunxi_nand_hw_common_ecc_ctrl_init(struct mtd_info *mtd,
+					      struct nand_ecc_ctrl *ecc)
+{
+	struct sunxi_nand_hw_ecc *data;
+	struct nand_ecclayout *layout;
+	int nsectors;
+	int ret;
+
+	if (!ecc->strength || !ecc->size)
+		return -EINVAL;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	/* Add ECC info retrieval from DT */
+	if (ecc->strength <= 16) {
+		ecc->strength = 16;
+		data->mode = 0;
+	} else if (ecc->strength <= 24) {
+		ecc->strength = 24;
+		data->mode = 1;
+	} else if (ecc->strength <= 28) {
+		ecc->strength = 28;
+		data->mode = 2;
+	} else if (ecc->strength <= 32) {
+		ecc->strength = 32;
+		data->mode = 3;
+	} else if (ecc->strength <= 40) {
+		ecc->strength = 40;
+		data->mode = 4;
+	} else if (ecc->strength <= 48) {
+		ecc->strength = 48;
+		data->mode = 5;
+	} else if (ecc->strength <= 56) {
+		ecc->strength = 56;
+		data->mode = 6;
+	} else if (ecc->strength <= 60) {
+		ecc->strength = 60;
+		data->mode = 7;
+	} else if (ecc->strength <= 64) {
+		ecc->strength = 64;
+		data->mode = 8;
+	} else {
+		pr_err("unsupported strength\n");
+		ret = -ENOTSUPP;
+		goto err;
+	}
+
+	/* HW ECC always request ECC bytes for 1024 bytes blocks */
+	ecc->bytes = ((ecc->strength * fls(8 * 1024)) + 7) / 8;
+
+	/* HW ECC always work with even numbers of ECC bytes */
+	if (ecc->bytes % 2)
+		ecc->bytes++;
+
+	layout = &data->layout;
+	nsectors = mtd->writesize / ecc->size;
+
+	if (mtd->oobsize < ((ecc->bytes + 4) * nsectors)) {
+		ret = -EINVAL;
+		goto err;
+	}
+
+	layout->eccbytes = (ecc->bytes * nsectors);
+
+	ecc->layout = layout;
+	ecc->priv = data;
+
+	return 0;
+
+err:
+	kfree(data);
+
+	return ret;
+}
+
+#ifndef __UBOOT__
+static void sunxi_nand_hw_common_ecc_ctrl_cleanup(struct nand_ecc_ctrl *ecc)
+{
+	kfree(ecc->priv);
+}
+#endif
+
+static int sunxi_nand_hw_ecc_ctrl_init(struct mtd_info *mtd,
+				       struct nand_ecc_ctrl *ecc)
+{
+	struct nand_ecclayout *layout;
+	int nsectors;
+	int i, j;
+	int ret;
+
+	ret = sunxi_nand_hw_common_ecc_ctrl_init(mtd, ecc);
+	if (ret)
+		return ret;
+
+	ecc->read_page = sunxi_nfc_hw_ecc_read_page;
+	ecc->write_page = sunxi_nfc_hw_ecc_write_page;
+	layout = ecc->layout;
+	nsectors = mtd->writesize / ecc->size;
+
+	for (i = 0; i < nsectors; i++) {
+		if (i) {
+			layout->oobfree[i].offset =
+				layout->oobfree[i - 1].offset +
+				layout->oobfree[i - 1].length +
+				ecc->bytes;
+			layout->oobfree[i].length = 4;
+		} else {
+			/*
+			 * The first 2 bytes are used for BB markers, hence we
+			 * only have 2 bytes available in the first user data
+			 * section.
+			 */
+			layout->oobfree[i].length = 2;
+			layout->oobfree[i].offset = 2;
+		}
+
+		for (j = 0; j < ecc->bytes; j++)
+			layout->eccpos[(ecc->bytes * i) + j] =
+					layout->oobfree[i].offset +
+					layout->oobfree[i].length + j;
+	}
+
+	if (mtd->oobsize > (ecc->bytes + 4) * nsectors) {
+		layout->oobfree[nsectors].offset =
+				layout->oobfree[nsectors - 1].offset +
+				layout->oobfree[nsectors - 1].length +
+				ecc->bytes;
+		layout->oobfree[nsectors].length = mtd->oobsize -
+				((ecc->bytes + 4) * nsectors);
+	}
+
+	return 0;
+}
+
+static int sunxi_nand_hw_syndrome_ecc_ctrl_init(struct mtd_info *mtd,
+						struct nand_ecc_ctrl *ecc)
+{
+	struct nand_ecclayout *layout;
+	int nsectors;
+	int i;
+	int ret;
+
+	ret = sunxi_nand_hw_common_ecc_ctrl_init(mtd, ecc);
+	if (ret)
+		return ret;
+
+	ecc->prepad = 4;
+	ecc->read_page = sunxi_nfc_hw_syndrome_ecc_read_page;
+	ecc->write_page = sunxi_nfc_hw_syndrome_ecc_write_page;
+
+	layout = ecc->layout;
+	nsectors = mtd->writesize / ecc->size;
+
+	for (i = 0; i < (ecc->bytes * nsectors); i++)
+		layout->eccpos[i] = i;
+
+	layout->oobfree[0].length = mtd->oobsize - i;
+	layout->oobfree[0].offset = i;
+
+	return 0;
+}
+
+/**
+ * It maps 'enum nand_ecc_modes_t' found in include/linux/mtd/nand.h
+ * into the device tree binding of 'nand-ecc', so that MTD
+ * device driver can get nand ecc from device tree.
+ */
+static const char *nand_ecc_modes[] = {
+	[NAND_ECC_NONE]		= "none",
+	[NAND_ECC_SOFT]		= "soft",
+	[NAND_ECC_HW]			= "hw",
+	[NAND_ECC_HW_SYNDROME]	= "hw_syndrome",
+	[NAND_ECC_HW_OOB_FIRST]	= "hw_oob_first",
+	[NAND_ECC_SOFT_BCH]		= "soft_bch",
+};
+
+/**
+ * of_get_nand_ecc_mode - Get nand ecc mode for given device_node
+ * @np:	Pointer to the given device_node
+ *
+ * The function gets ecc mode string from property 'nand-ecc-mode',
+ * and return its index in nand_ecc_modes table, or errno in error case.
+ */
+static inline int of_get_nand_ecc_mode(int node)
+{
+	const char *pm;
+	int len, i;
+	
+	pm = fdt_getprop(gd->fdt_blob, node,  "nand-ecc-mode", &len);
+	if (!pm)
+		return -1;
+	for (i = 0; i < ARRAY_SIZE(nand_ecc_modes); i++)
+		if (!strcasecmp(pm, nand_ecc_modes[i]))
+			return i;
+	return -1;
+}
+
+ /**
+ * It maps 'enum nand_rnd_modes_t' found in include/linux/mtd/nand.h
+ * into the device tree binding of 'nand-rnd', so that MTD
+ * device driver can get nand rnd from device tree.
+ */
+static const char *nand_rnd_modes[] = {
+	[NAND_RND_NONE]	= "none",
+	[NAND_RND_SOFT]	= "soft",
+	[NAND_RND_HW]		= "hw",
+};
+
+/**
+ * of_get_nand_rnd_mode - Get nand randomizer mode for given device_node
+ * @np:	Pointer to the given device_node
+ *
+ * The function gets randomizer mode string from property 'nand-rnd-mode',
+ * and return its index in nand_rnd_modes table, or errno in error case.
+*/
+static inline int of_get_nand_rnd_mode(int node)
+{	
+	const char *pm;
+	int len, i;
+	
+	pm = fdt_getprop(gd->fdt_blob, node, "nand-rnd-mode", &len);
+	if (!pm)
+		return -1;
+	for (i = 0; i < ARRAY_SIZE(nand_rnd_modes); i++)
+		if (!strcasecmp(pm, nand_rnd_modes[i]))
+			return i;
+	return -1;
+}
+
+#ifndef __UBOOT__
+static void sunxi_nand_rnd_cleanup(struct nand_rnd_ctrl *rnd)
+{
+	switch (rnd->mode) {
+	case NAND_RND_HW:
+		sunxi_nand_rnd_ctrl_cleanup(rnd);
+		break;
+	default:
+		break;
+	}
+}
+#endif
+
+static int sunxi_nand_rnd_init(int node, struct mtd_info *mtd,
+			       struct nand_rnd_ctrl *rnd,
+			       struct nand_ecc_ctrl *ecc)
+{
+	int ret;
+
+	rnd->mode = NAND_RND_NONE;
+
+	ret = of_get_nand_rnd_mode(node);
+	if (ret >= 0)
+		rnd->mode = ret;
+
+	switch (rnd->mode) {
+	case NAND_RND_HW:
+		return sunxi_nand_rnd_ctrl_init(node, mtd, rnd, ecc);
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+#ifndef __UBOOT__
+static void sunxi_nand_ecc_cleanup(struct nand_ecc_ctrl *ecc)
+{
+	switch (ecc->mode) {
+	case NAND_ECC_HW:
+	case NAND_ECC_HW_SYNDROME:
+		sunxi_nand_hw_common_ecc_ctrl_cleanup(ecc);
+		break;
+	case NAND_ECC_NONE:
+		kfree(ecc->layout);
+	default:
+		break;
+	}
+}
+#endif
+
+static int sunxi_nand_ecc_init(int node, struct mtd_info *mtd, 
+				struct nand_ecc_ctrl *ecc)
+{
+	struct nand_chip *nand = mtd->priv;
+	s32 strength;
+	s32 blk_size;
+	int ret;
+	
+	blk_size = fdtdec_get_int(gd->fdt_blob, node, "nand-ecc-step-size", -1);
+	strength = fdtdec_get_int(gd->fdt_blob, node, "nand-ecc-strength", -1);
+	if ((blk_size | strength) > -1){
+		ecc->size = blk_size;
+		ecc->strength = strength;
+	} else {
+		ecc->size = nand->ecc_step_ds;
+		ecc->strength = nand->ecc_strength_ds;
+	}
+
+	ecc->mode = NAND_ECC_HW;
+
+	ret = of_get_nand_ecc_mode(node);
+	if (ret >= 0)
+		ecc->mode = ret;
+
+	switch (ecc->mode) {
+	case NAND_ECC_SOFT_BCH:
+		if (!ecc->size || !ecc->strength)
+			return -EINVAL;
+		ecc->bytes = ((ecc->strength * fls(8 * ecc->size)) + 7) / 8;
+		break;
+	case NAND_ECC_HW:
+		ret = sunxi_nand_hw_ecc_ctrl_init(mtd, ecc);
+		if (ret)
+			return ret;
+		break;
+	case NAND_ECC_HW_SYNDROME:
+		ret = sunxi_nand_hw_syndrome_ecc_ctrl_init(mtd, ecc);
+		if (ret)
+			return ret;
+		break;
+	case NAND_ECC_NONE:
+		ecc->layout = kzalloc(sizeof(*ecc->layout), GFP_KERNEL);
+		if (!ecc->layout)
+			return -ENOMEM;
+		ecc->layout->oobfree[0].length = mtd->oobsize;
+	case NAND_ECC_SOFT:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int sunxi_nand_chip_init(int node, struct sunxi_nfc *nfc, int devnum)
+{
+	const struct nand_sdr_timings *timings;
+	struct sunxi_nand_chip *chip;
+	struct mtd_info *mtd;
+	struct nand_chip *nand;
+	int nsels;
+	int ret;
+	int i;
+	u32 tmp[8];
+	
+	if(!fdt_getprop(gd->fdt_blob, node, "reg", &nsels))
+		return -EINVAL;
+
+	nsels /= sizeof(u32);
+	if (!nsels)
+		return -EINVAL;
+
+	chip = kzalloc(sizeof(*chip) +
+			(nsels * sizeof(struct sunxi_nand_chip_sel)),
+			GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->nsels = nsels;
+	chip->selected = -1;
+	
+	for (i = 0; i < nsels; i++) {	
+		ret = fdtdec_get_int_array(gd->fdt_blob, node, "reg", tmp,
+				   nsels);
+		if(ret)
+			return ret;
+
+		if (tmp[i] > 7)
+			return -EINVAL;
+
+ 		if (test_and_set_bit(tmp[i], &nfc->assigned_cs))
+			return -EINVAL;
+		
+		chip->sels[i].cs = tmp[i];
+		
+		if(fdtdec_get_int_array(gd->fdt_blob, node, "allwinner,rb", tmp,
+			nsels) && tmp[i] < 2){
+			chip->sels[i].rb.type = RB_NATIVE;
+			chip->sels[i].rb.info.nativeid = tmp[i];
+		} else {
+			chip->sels[i].rb.type = RB_NONE;
+		}
+	}
+
+	timings = onfi_async_timing_mode_to_sdr_timings(0);
+	if (IS_ERR(timings))
+		return PTR_ERR(timings);
+
+	ret = sunxi_nand_chip_set_timings(chip, timings);
+
+	nand = &chip->nand;
+
+	nand->chip_delay = 200;
+	nand->controller = &nfc->controller;
+	nand->select_chip = sunxi_nfc_select_chip;
+	nand->cmd_ctrl = sunxi_nfc_cmd_ctrl;
+	nand->read_buf = sunxi_nfc_read_buf;
+	nand->write_buf = sunxi_nfc_write_buf;
+	nand->read_byte = sunxi_nfc_read_byte;
+
+	if(fdtdec_get_bool(gd->fdt_blob, node, "nand-on-flash-bbt"))
+		nand->bbt_options |= NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB;
+
+	mtd =  &nand_info[devnum];
+	mtd->priv = nand;
+
+
+	ret = nand_scan_ident(mtd, nsels, NULL);
+	if (ret){
+		return ret;
+	}
+
+	chip->buffer = kzalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL);
+	if (!chip->buffer)
+		return -ENOMEM;
+
+	ret = sunxi_nand_chip_init_timings(chip);
+	if (ret)
+		return ret;
+
+	ret = nand_pst_create(mtd);
+	if (ret)
+		return ret;
+
+	ret = sunxi_nand_ecc_init(node, mtd, &nand->ecc);
+	printf("%d\n", ret);
+	if (ret)
+		return ret;
+
+	ret = sunxi_nand_rnd_init(node, mtd, &nand->rnd, &nand->ecc);
+	if (ret)
+		return ret;
+	
+	ret = nand_scan_tail(mtd);
+	if (ret)
+		return ret;
+
+	/*if(fdt_getprop(gd->fdt_blob, node, "nand-name", NULL)){
+		snprintf(chip->default_name, MAX_NAME_SIZE,
+			 DEFAULT_NAME_FORMAT, chip->sels[i].cs);
+		mtd->name = chip->default_name;
+	}*/
+
+	ret = nand_register(devnum);
+
+	if (ret){
+		pr_err("%s:failed to register\n", __func__);
+		return ret;
+	}
+
+	list_add_tail(&chip->node, &nfc->chips);
+
+	return 0;
+}
+
+#ifndef __UBOOT__
+static void sunxi_nand_chips_cleanup(struct sunxi_nfc *nfc)
+{
+	struct sunxi_nand_chip *chip;
+
+	while (!list_empty(&nfc->chips)) {
+		chip = list_first_entry(&nfc->chips, struct sunxi_nand_chip,
+					node);
+		nand_release(&chip->mtd);
+		sunxi_nand_ecc_cleanup(&chip->nand.ecc);
+		sunxi_nand_rnd_cleanup(&chip->nand.rnd);
+		kfree(chip->buffer);
+	}
+}
+#endif /* !__UBOOT__ */
+
+static int sunxi_nand_chips_init(int node, struct sunxi_nfc *nfc)
+{
+	int ret, offset;
+	/*TODO  check maximum chips*/
+	for (offset = fdt_first_subnode(gd->fdt_blob, node);
+					offset >= 0;
+     					offset = fdt_next_subnode(gd->fdt_blob, offset)) {
+		ret = sunxi_nand_chip_init(offset, nfc, 0 );
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int sunxi_nfc_init(struct sunxi_nfc *nfc)
+{
+	int node; 
+	int ret;
+	
+	spin_lock_init(&nfc->controller.lock);
+	init_waitqueue_head(&nfc->controller.wq);
+	INIT_LIST_HEAD(&nfc->chips);
+	
+	node = fdtdec_next_compatible(gd->fdt_blob, 0,
+				      COMPAT_SUNXI_NAND);
+	if (node < 0){
+		pr_err("%s:unable to find nfc in device tree\n", __func__);
+		goto err;
+	}
+
+	if(!fdtdec_get_is_enabled(gd->fdt_blob, node)){
+		pr_err("%s:nfc disabled in device tree\n", __func__);
+		goto err;
+	}
+	
+	nfc->regs = (struct sunxi_nand * const)fdtdec_get_addr(gd->fdt_blob,
+							node, "reg");
+	if ((fdt_addr_t)nfc->regs == FDT_ADDR_T_NONE) {	
+		pr_err("%s:unable to find nfc address in device tree\n", __func__);
+		goto err;
+	}
+	
+	/* clock enable*/
+	sunxi_set_clk_rate(NAND_MAX_CLOCK);
+	sunxi_nfc_rst(nfc);
+
+	writel(0, &nfc->regs->intr);
+
+	/*
+	 * TODO: replace these magic values with proper flags as soon as we
+	 * know what they are encoding.
+	 */
+	writel(0x100, &nfc->regs->timing_ctl);
+	writel(0x7ff, &nfc->regs->timing_cfg);
+
+	ret = sunxi_nand_chips_init(node, nfc);
+	if (ret) {
+		pr_err("%s:failed to init nand chips\n", __func__);
+		goto err;
+	}
+
+	return 0;	
+
+err:
+	kfree(nfc);
+	return ret;
+}
+
+void sunxi_nand_init(void)
+{
+	struct sunxi_nfc *nfc;
+
+	nfc = kzalloc(sizeof(*nfc), GFP_KERNEL);
+	if (!nfc)
+		return;
+
+	sunxi_nfc_init(nfc);			
+}
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Boris BREZILLON");
+MODULE_DESCRIPTION("Allwinner NAND Flash Controller driver");
diff --git a/include/fdtdec.h b/include/fdtdec.h
index 6590470..c0662d9 100644
--- a/include/fdtdec.h
+++ b/include/fdtdec.h
@@ -177,6 +177,7 @@ enum fdt_compat_id {
 	COMPAT_INTEL_QRK_MRC,		/* Intel Quark MRC */
 	COMPAT_SOCIONEXT_XHCI,		/* Socionext UniPhier xHCI */
 	COMPAT_INTEL_PCH,		/* Intel PCH */
+	COMPAT_SUNXI_NAND,		/* SUNXI NAND controller */
 
 	COMPAT_COUNT,
 };
@@ -567,6 +568,18 @@ const char *fdtdec_get_compatible(enum fdt_compat_id id);
 int fdtdec_lookup_phandle(const void *blob, int node, const char *prop_name);
 
 /**
+ * @param blob		FDT blob
+ * @param node		node to examine
+ * @param prop_name	name of property to find
+ * @param array		array to fill with data
+ * @param count		number of array elements
+ * @return 0 if ok, or -FDT_ERR_NOTFOUND if the property is not found,
+ *		or -FDT_ERR_BADLAYOUT if not enough data
+ */
+int fdtdec_get_u16_array(const void *blob, int node, const char *prop_name,
+		u16 *array, int count);
+
+/**
  * Look up a property in a node and return its contents in an integer
  * array of given length. The property must have at least enough data for
  * the array (4*count bytes). It may have more, but this will be ignored.
diff --git a/lib/fdtdec.c b/lib/fdtdec.c
index 80b897a..45dc9fd 100644
--- a/lib/fdtdec.c
+++ b/lib/fdtdec.c
@@ -76,6 +76,7 @@ static const char * const compat_names[COMPAT_COUNT] = {
 	COMPAT(INTEL_QRK_MRC, "intel,quark-mrc"),
 	COMPAT(SOCIONEXT_XHCI, "socionext,uniphier-xhci"),
 	COMPAT(COMPAT_INTEL_PCH, "intel,bd82x6x"),
+	COMPAT(COMPAT_SUNXI_NAND, "allwinner,sun4i-nand"),
 };
 
 const char *fdtdec_get_compatible(enum fdt_compat_id id)
@@ -618,6 +619,22 @@ static const void *get_prop_check_min_len(const void *blob, int node,
 	return cell;
 }
 
+int fdtdec_get_u16_array(const void *blob, int node, const char *prop_name,
+		u16 *array, int count)
+{
+	const u16 *cell;
+	int i, err = 0;
+
+	debug("%s: %s\n", __func__, prop_name);
+	cell = get_prop_check_min_len(blob, node, prop_name,
+				      sizeof(u16) * count, &err);
+	if (!err) {
+		for (i = 0; i < count; i++)
+			array[i] =  be16_to_cpu(cell[i]);
+	}
+	return err;
+}
+
 int fdtdec_get_int_array(const void *blob, int node, const char *prop_name,
 		u32 *array, int count)
 {
-- 
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 U-Boot mailing list