Merge git://git.denx.de/u-boot-nand-flash
diff --git a/cmd/nand.c b/cmd/nand.c
index 583a18f..ffdeea4 100644
--- a/cmd/nand.c
+++ b/cmd/nand.c
@@ -647,6 +647,9 @@
 
 #ifdef CONFIG_CMD_NAND_TORTURE
 	if (strcmp(cmd, "torture") == 0) {
+		loff_t endoff;
+		unsigned int failed = 0, passed = 0;
+
 		if (argc < 3)
 			goto usage;
 
@@ -655,12 +658,37 @@
 			return 1;
 		}
 
-		printf("\nNAND torture: device %d offset 0x%llx size 0x%x\n",
-			dev, off, mtd->erasesize);
-		ret = nand_torture(mtd, off);
-		printf(" %s\n", ret ? "Failed" : "Passed");
+		size = mtd->erasesize;
+		if (argc > 3) {
+			if (!str2off(argv[3], &size)) {
+				puts("Size is not a valid number\n");
+				return 1;
+			}
+		}
 
-		return ret == 0 ? 0 : 1;
+		endoff = off + size;
+		if (endoff > mtd->size) {
+			puts("Arguments beyond end of NAND\n");
+			return 1;
+		}
+
+		off = round_down(off, mtd->erasesize);
+		endoff = round_up(endoff, mtd->erasesize);
+		size = endoff - off;
+		printf("\nNAND torture: device %d offset 0x%llx size 0x%llx (block size 0x%x)\n",
+		       dev, off, size, mtd->erasesize);
+		while (off < endoff) {
+			ret = nand_torture(mtd, off);
+			if (ret) {
+				failed++;
+				printf("  block at 0x%llx failed\n", off);
+			} else {
+				passed++;
+			}
+			off += mtd->erasesize;
+		}
+		printf(" Passed: %u, failed: %u\n", passed, failed);
+		return failed != 0;
 	}
 #endif
 
@@ -775,7 +803,8 @@
 	"nand bad - show bad blocks\n"
 	"nand dump[.oob] off - dump page\n"
 #ifdef CONFIG_CMD_NAND_TORTURE
-	"nand torture off - torture block at offset\n"
+	"nand torture off - torture one block at offset\n"
+	"nand torture off [size] - torture blocks from off to off+size\n"
 #endif
 	"nand scrub [-y] off size | scrub.part partition | scrub.chip\n"
 	"    really clean NAND erasing bad blocks (UNSAFE)\n"
diff --git a/common/fb_nand.c b/common/fb_nand.c
index e55ea38..ae34f48 100644
--- a/common/fb_nand.c
+++ b/common/fb_nand.c
@@ -34,7 +34,7 @@
 }
 
 static int fb_nand_lookup(const char *partname, char *response,
-			  struct mtd_info **nand,
+			  struct mtd_info **mtd,
 			  struct part_info **part)
 {
 	struct mtd_device *dev;
diff --git a/common/spl/spl_nand.c b/common/spl/spl_nand.c
index 7cf0d1b..0e35e0f 100644
--- a/common/spl/spl_nand.c
+++ b/common/spl/spl_nand.c
@@ -134,6 +134,13 @@
 #endif
 	/* Load u-boot */
 	err = spl_nand_load_element(CONFIG_SYS_NAND_U_BOOT_OFFS, header);
+#ifdef CONFIG_SYS_NAND_U_BOOT_OFFS_REDUND
+#if CONFIG_SYS_NAND_U_BOOT_OFFS != CONFIG_SYS_NAND_U_BOOT_OFFS_REDUND
+	if (err)
+		err = spl_nand_load_element(CONFIG_SYS_NAND_U_BOOT_OFFS_REDUND,
+					    header);
+#endif
+#endif
 	nand_deselect();
 	return err;
 }
diff --git a/doc/README.nand b/doc/README.nand
index 96ffc48..f1c20ff 100644
--- a/doc/README.nand
+++ b/doc/README.nand
@@ -137,7 +137,7 @@
       init:
 
 	/* chip is struct nand_chip, and is now provided by the driver. */
-	mtd = &chip.mtd;
+	mtd = nand_to_mtd(&chip);
 
 	/*
 	 * Fill in appropriate values if this driver uses these fields,
@@ -271,7 +271,7 @@
 
 		However, for 4K pagesize NAND
 		NAND_PAGESIZE = 4096
-		NAND_OOBSIZE = 64
+		NAND_OOBSIZE = 224
 		ECC_BYTES = 26
 		2 + (4096 / 512) * 26 = 210 < NAND_OOBSIZE
 		Thus BCH16 can be supported on 4K page NAND.
@@ -307,7 +307,7 @@
   DANGEROUS!!! Factory set bad blocks will be lost. Use only
   to remove artificial bad blocks created with the "markbad" command.
 
-  "torture offset"
+  "torture offset [size]"
   Torture block to determine if it is still reliable.
   Enabled by the CONFIG_CMD_NAND_TORTURE configuration option.
   This command returns 0 if the block is still reliable, else 1.
@@ -324,6 +324,10 @@
   automate actions following a nand->write() error. This would e.g. be required
   in order to program or update safely firmware to NAND, especially for the UBI
   part of such firmware.
+  Optionally, a second parameter size can be given to test multiple blocks with
+  one call. If size is not a multiple of the NAND's erase size, then the block
+  that contains offset + size will be tested in full. If used with size, this
+  command returns 0 if all tested blocks have been found reliable, else 1.
 
 
 NAND locking command (for chips with active LOCKPRE pin)
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index 2fc73ef..8c46a2f 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -99,16 +99,31 @@
 	    not available while configuring controller. So a static CONFIG_NAND_xx
 	    is needed to know the device's bus-width in advance.
 
-# Enhance depends when converting drivers to Kconfig which use this config
+if SPL
+
+config SYS_NAND_U_BOOT_LOCATIONS
+	bool "Define U-boot binaries locations in NAND"
+	help
+	Enable CONFIG_SYS_NAND_U_BOOT_OFFS though Kconfig.
+	This option should not be enabled when compiling U-boot for boards
+	defining CONFIG_SYS_NAND_U_BOOT_OFFS in their include/configs/<board>.h
+	file.
+
 config SYS_NAND_U_BOOT_OFFS
 	hex "Location in NAND to read U-Boot from"
 	default 0x8000 if NAND_SUNXI
-	depends on NAND_SUNXI
+	depends on SYS_NAND_U_BOOT_LOCATIONS
 	help
 	Set the offset from the start of the nand where u-boot should be
 	loaded from.
 
-if SPL
+config SYS_NAND_U_BOOT_OFFS_REDUND
+	hex "Location in NAND to read U-Boot from"
+	default SYS_NAND_U_BOOT_OFFS
+	depends on SYS_NAND_U_BOOT_LOCATIONS
+	help
+	Set the offset from the start of the nand where the redundant u-boot
+	should be loaded from.
 
 config SPL_NAND_DENALI
 	bool "Support Denali NAND controller for SPL"
diff --git a/drivers/mtd/nand/am335x_spl_bch.c b/drivers/mtd/nand/am335x_spl_bch.c
index f8770e0..a8a7a66 100644
--- a/drivers/mtd/nand/am335x_spl_bch.c
+++ b/drivers/mtd/nand/am335x_spl_bch.c
@@ -223,7 +223,7 @@
 	/*
 	 * Init board specific nand support
 	 */
-	mtd = &nand_chip.mtd;
+	mtd = nand_to_mtd(&nand_chip);
 	nand_chip.IO_ADDR_R = nand_chip.IO_ADDR_W =
 		(void  __iomem *)CONFIG_SYS_NAND_BASE;
 	board_nand_init(&nand_chip);
diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c
index ad5ded3..8669432 100644
--- a/drivers/mtd/nand/atmel_nand.c
+++ b/drivers/mtd/nand/atmel_nand.c
@@ -1449,7 +1449,7 @@
 
 void nand_init(void)
 {
-	mtd = &nand_chip.mtd;
+	mtd = nand_to_mtd(&nand_chip);
 	mtd->writesize = CONFIG_SYS_NAND_PAGE_SIZE;
 	mtd->oobsize = CONFIG_SYS_NAND_OOBSIZE;
 	nand_chip.IO_ADDR_R = (void __iomem *)CONFIG_SYS_NAND_BASE;
diff --git a/drivers/mtd/nand/lpc32xx_nand_mlc.c b/drivers/mtd/nand/lpc32xx_nand_mlc.c
index 4262029..a793115 100644
--- a/drivers/mtd/nand/lpc32xx_nand_mlc.c
+++ b/drivers/mtd/nand/lpc32xx_nand_mlc.c
@@ -541,7 +541,7 @@
 
 void board_nand_init(void)
 {
-	struct mtd_info *mtd = &lpc32xx_chip.mtd;
+	struct mtd_info *mtd = nand_to_mtd(&lpc32xx_chip);
 	int ret;
 
 	/* Set all BOARDSPECIFIC (actually core-specific) fields  */
diff --git a/drivers/mtd/nand/mxs_nand_spl.c b/drivers/mtd/nand/mxs_nand_spl.c
index a8a3084..ff28df4 100644
--- a/drivers/mtd/nand/mxs_nand_spl.c
+++ b/drivers/mtd/nand/mxs_nand_spl.c
@@ -147,7 +147,7 @@
 
 	/* init mxs nand driver */
 	board_nand_init(&nand_chip);
-	mtd = &nand_chip.mtd;
+	mtd = nand_to_mtd(&nand_chip);
 	/* set mtd functions */
 	nand_chip.cmdfunc = mxs_nand_command;
 	nand_chip.numchips = 1;
diff --git a/drivers/mtd/nand/nand_spl_simple.c b/drivers/mtd/nand/nand_spl_simple.c
index b023e00..60a7607 100644
--- a/drivers/mtd/nand/nand_spl_simple.c
+++ b/drivers/mtd/nand/nand_spl_simple.c
@@ -249,7 +249,7 @@
 	/*
 	 * Init board specific nand support
 	 */
-	mtd = &nand_chip.mtd;
+	mtd = nand_to_mtd(&nand_chip);
 	nand_chip.IO_ADDR_R = nand_chip.IO_ADDR_W =
 		(void  __iomem *)CONFIG_SYS_NAND_BASE;
 	board_nand_init(&nand_chip);
diff --git a/drivers/mtd/nand/nand_util.c b/drivers/mtd/nand/nand_util.c
index 5bba66a..e8bcc34 100644
--- a/drivers/mtd/nand/nand_util.c
+++ b/drivers/mtd/nand/nand_util.c
@@ -820,7 +820,7 @@
 {
 	u_char patterns[] = {0xa5, 0x5a, 0x00};
 	struct erase_info instr = {
-		.mtd = nand,
+		.mtd = mtd,
 		.addr = offset,
 		.len = mtd->erasesize,
 	};
diff --git a/drivers/mtd/nand/sunxi_nand_spl.c b/drivers/mtd/nand/sunxi_nand_spl.c
index b0e07aa..1ef7366 100644
--- a/drivers/mtd/nand/sunxi_nand_spl.c
+++ b/drivers/mtd/nand/sunxi_nand_spl.c
@@ -66,6 +66,8 @@
 #define NFC_ROW_AUTO_INC           (1 << 27)
 #define NFC_SEND_CMD3              (1 << 28)
 #define NFC_SEND_CMD4              (1 << 29)
+#define NFC_RAW_CMD                (0 << 30)
+#define NFC_PAGE_CMD               (2 << 30)
 
 #define NFC_ST_CMD_INT_FLAG        (1 << 1)
 #define NFC_ST_DMA_INT_FLAG        (1 << 2)
@@ -78,9 +80,6 @@
 #define NFC_CMD_RNDOUT             0x05
 #define NFC_CMD_READSTART          0x30
 
-
-#define NFC_PAGE_CMD               (2 << 30)
-
 #define SUNXI_DMA_CFG_REG0              0x300
 #define SUNXI_DMA_SRC_START_ADDR_REG0   0x304
 #define SUNXI_DMA_DEST_START_ADDRR_REG0 0x308
@@ -97,6 +96,16 @@
 #define SUNXI_DMA_DDMA_PARA_REG_SRC_WAIT_CYC (0x0F << 0)
 #define SUNXI_DMA_DDMA_PARA_REG_SRC_BLK_SIZE (0x7F << 8)
 
+struct nfc_config {
+	int page_size;
+	int ecc_strength;
+	int ecc_size;
+	int addr_cycles;
+	int nseeds;
+	bool randomize;
+	bool valid;
+};
+
 /* minimal "boot0" style NAND support for Allwinner A20 */
 
 /* random seed used by linux */
@@ -119,38 +128,31 @@
 	0x7c57, 0x0fbe, 0x46ce, 0x4939, 0x6b17, 0x37bb, 0x3e91, 0x76db,
 };
 
-/* random seed used for syndrome calls */
-const uint16_t random_seed_syndrome = 0x4a80;
-
-#define MAX_RETRIES 10
+#define DEFAULT_TIMEOUT_US	100000
 
 static int check_value_inner(int offset, int expected_bits,
-				int max_number_of_retries, int negation)
+			     int timeout_us, int negation)
 {
-	int retries = 0;
 	do {
 		int val = readl(offset) & expected_bits;
 		if (negation ? !val : val)
 			return 1;
-		mdelay(1);
-		retries++;
-	} while (retries < max_number_of_retries);
+		udelay(1);
+	} while (--timeout_us);
 
 	return 0;
 }
 
 static inline int check_value(int offset, int expected_bits,
-				int max_number_of_retries)
+			      int timeout_us)
 {
-	return check_value_inner(offset, expected_bits,
-					max_number_of_retries, 0);
+	return check_value_inner(offset, expected_bits, timeout_us, 0);
 }
 
 static inline int check_value_negated(int offset, int unexpected_bits,
-					int max_number_of_retries)
+				      int timeout_us)
 {
-	return check_value_inner(offset, unexpected_bits,
-					max_number_of_retries, 1);
+	return check_value_inner(offset, unexpected_bits, timeout_us, 1);
 }
 
 void nand_init(void)
@@ -165,7 +167,7 @@
 	       SUNXI_NFC_BASE + NFC_CTL);
 
 	if (!check_value_negated(SUNXI_NFC_BASE + NFC_CTL,
-				 NFC_CTL_RESET, MAX_RETRIES)) {
+				 NFC_CTL_RESET, DEFAULT_TIMEOUT_US)) {
 		printf("Couldn't initialize nand\n");
 	}
 
@@ -175,64 +177,97 @@
 	       SUNXI_NFC_BASE + NFC_CMD);
 
 	if (!check_value(SUNXI_NFC_BASE + NFC_ST, NFC_ST_CMD_INT_FLAG,
-			 MAX_RETRIES)) {
+			 DEFAULT_TIMEOUT_US)) {
 		printf("Error timeout waiting for nand reset\n");
 		return;
 	}
 	writel(NFC_ST_CMD_INT_FLAG, SUNXI_NFC_BASE + NFC_ST);
 }
 
-static int nand_read_page(int page_size, int ecc_strength, int ecc_page_size,
-	int addr_cycles, uint32_t real_addr, dma_addr_t dst, int syndrome)
+static void nand_apply_config(const struct nfc_config *conf)
 {
-	uint32_t val;
-	int i, ecc_off = 0;
-	uint16_t ecc_mode = 0;
-	uint16_t rand_seed;
-	uint32_t page;
-	uint16_t column;
-	static const u8 strengths[] = { 16, 24, 28, 32, 40, 48, 56, 60, 64 };
+	u32 val;
 
-	for (i = 0; i < ARRAY_SIZE(strengths); i++) {
-		if (ecc_strength == strengths[i]) {
-			ecc_mode = i;
-			break;
-		}
+	val = readl(SUNXI_NFC_BASE + NFC_CTL);
+	val &= ~NFC_CTL_PAGE_SIZE_MASK;
+	writel(val | NFC_CTL_RAM_METHOD | NFC_CTL_PAGE_SIZE(conf->page_size),
+	       SUNXI_NFC_BASE + NFC_CTL);
+	writel(conf->ecc_size, SUNXI_NFC_BASE + NFC_CNT);
+	writel(conf->page_size, SUNXI_NFC_BASE + NFC_SPARE_AREA);
+}
+
+static int nand_load_page(const struct nfc_config *conf, u32 offs)
+{
+	int page = offs / conf->page_size;
+
+	writel((NFC_CMD_RNDOUTSTART << NFC_RANDOM_READ_CMD1_OFFSET) |
+	       (NFC_CMD_RNDOUT << NFC_RANDOM_READ_CMD0_OFFSET) |
+	       (NFC_CMD_READSTART << NFC_READ_CMD_OFFSET),
+	       SUNXI_NFC_BASE + NFC_RCMD_SET);
+	writel(((page & 0xFFFF) << 16), SUNXI_NFC_BASE + NFC_ADDR_LOW);
+	writel((page >> 16) & 0xFF, SUNXI_NFC_BASE + NFC_ADDR_HIGH);
+	writel(NFC_ST_CMD_INT_FLAG, SUNXI_NFC_BASE + NFC_ST);
+	writel(NFC_SEND_CMD1 | NFC_SEND_CMD2 | NFC_RAW_CMD | NFC_WAIT_FLAG |
+	       ((conf->addr_cycles - 1) << NFC_ADDR_NUM_OFFSET) | NFC_SEND_ADR,
+	       SUNXI_NFC_BASE + NFC_CMD);
+
+	if (!check_value(SUNXI_NFC_BASE + NFC_ST, NFC_ST_CMD_INT_FLAG,
+			 DEFAULT_TIMEOUT_US)) {
+		printf("Error while initializing dma interrupt\n");
+		return -EIO;
 	}
 
-	/* HW ECC always request ECC bytes for 1024 bytes blocks */
-	ecc_off = DIV_ROUND_UP(ecc_strength * fls(8 * 1024), 8);
-	/* HW ECC always work with even numbers of ECC bytes */
-	ecc_off += (ecc_off & 1);
-	ecc_off += 4; /* prepad */
+	return 0;
+}
 
-	page = real_addr / page_size;
-	column = real_addr % page_size;
+static int nand_reset_column(void)
+{
+	writel((NFC_CMD_RNDOUTSTART << NFC_RANDOM_READ_CMD1_OFFSET) |
+	       (NFC_CMD_RNDOUT << NFC_RANDOM_READ_CMD0_OFFSET) |
+	       (NFC_CMD_RNDOUTSTART << NFC_READ_CMD_OFFSET),
+	       SUNXI_NFC_BASE + NFC_RCMD_SET);
+	writel(0, SUNXI_NFC_BASE + NFC_ADDR_LOW);
+	writel(NFC_SEND_CMD1 | NFC_SEND_CMD2 | NFC_RAW_CMD |
+	       (1 << NFC_ADDR_NUM_OFFSET) | NFC_SEND_ADR | NFC_CMD_RNDOUT,
+	       SUNXI_NFC_BASE + NFC_CMD);
 
-	if (syndrome)
-		column += (column / ecc_page_size) * ecc_off;
+	if (!check_value(SUNXI_NFC_BASE + NFC_ST, NFC_ST_CMD_INT_FLAG,
+			 DEFAULT_TIMEOUT_US)) {
+		printf("Error while initializing dma interrupt\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int nand_read_page(const struct nfc_config *conf, u32 offs,
+			  void *dest, int len)
+{
+	dma_addr_t dst = (dma_addr_t)dest;
+	int nsectors = len / conf->ecc_size;
+	u16 rand_seed;
+	u32 val;
+	int page;
+
+	page = offs / conf->page_size;
+
+	if (offs % conf->page_size || len % conf->ecc_size ||
+	    len > conf->page_size || len < 0)
+		return -EINVAL;
 
 	/* clear ecc status */
 	writel(0, SUNXI_NFC_BASE + NFC_ECC_ST);
 
 	/* Choose correct seed */
-	if (syndrome)
-		rand_seed = random_seed_syndrome;
-	else
-		rand_seed = random_seed[page % 128];
+	rand_seed = random_seed[page % conf->nseeds];
 
-	writel((rand_seed << 16) | NFC_ECC_RANDOM_EN | NFC_ECC_EN
-		| NFC_ECC_PIPELINE | (ecc_mode << 12),
+	writel((rand_seed << 16) | (conf->ecc_strength << 12) |
+		(conf->randomize ? NFC_ECC_RANDOM_EN : 0) |
+		(conf->ecc_size == 512 ? NFC_ECC_BLOCK_SIZE : 0) |
+		NFC_ECC_EN | NFC_ECC_PIPELINE | NFC_ECC_EXCEPTION,
 		SUNXI_NFC_BASE + NFC_ECC_CTL);
 
-	val = readl(SUNXI_NFC_BASE + NFC_CTL);
-	writel(val | NFC_CTL_RAM_METHOD, SUNXI_NFC_BASE + NFC_CTL);
-
-	if (!syndrome)
-		writel(page_size + (column / ecc_page_size) * ecc_off,
-		       SUNXI_NFC_BASE + NFC_SPARE_AREA);
-
-	flush_dcache_range(dst, ALIGN(dst + ecc_page_size, ARCH_DMA_MINALIGN));
+	flush_dcache_range(dst, ALIGN(dst + conf->ecc_size, ARCH_DMA_MINALIGN));
 
 	/* SUNXI_DMA */
 	writel(0x0, SUNXI_DMA_BASE + SUNXI_DMA_CFG_REG0); /* clr dma cmd */
@@ -241,158 +276,261 @@
 	       SUNXI_DMA_BASE + SUNXI_DMA_SRC_START_ADDR_REG0);
 	/* read to RAM */
 	writel(dst, SUNXI_DMA_BASE + SUNXI_DMA_DEST_START_ADDRR_REG0);
-	writel(SUNXI_DMA_DDMA_PARA_REG_SRC_WAIT_CYC
-			| SUNXI_DMA_DDMA_PARA_REG_SRC_BLK_SIZE,
-			SUNXI_DMA_BASE + SUNXI_DMA_DDMA_PARA_REG0);
-	writel(ecc_page_size,
-	       SUNXI_DMA_BASE + SUNXI_DMA_DDMA_BC_REG0); /* 1kB */
-	writel(SUNXI_DMA_DDMA_CFG_REG_LOADING
-		| SUNXI_DMA_DDMA_CFG_REG_DMA_DEST_DATA_WIDTH_32
-		| SUNXI_DMA_DDMA_CFG_REG_DDMA_DST_DRQ_TYPE_DRAM
-		| SUNXI_DMA_DDMA_CFG_REG_DMA_SRC_DATA_WIDTH_32
-		| SUNXI_DMA_DDMA_CFG_REG_DMA_SRC_ADDR_MODE_IO
-		| SUNXI_DMA_DDMA_CFG_REG_DDMA_SRC_DRQ_TYPE_NFC,
-		SUNXI_DMA_BASE + SUNXI_DMA_CFG_REG0);
+	writel(SUNXI_DMA_DDMA_PARA_REG_SRC_WAIT_CYC |
+	       SUNXI_DMA_DDMA_PARA_REG_SRC_BLK_SIZE,
+	       SUNXI_DMA_BASE + SUNXI_DMA_DDMA_PARA_REG0);
+	writel(len, SUNXI_DMA_BASE + SUNXI_DMA_DDMA_BC_REG0);
+	writel(SUNXI_DMA_DDMA_CFG_REG_LOADING |
+	       SUNXI_DMA_DDMA_CFG_REG_DMA_DEST_DATA_WIDTH_32 |
+	       SUNXI_DMA_DDMA_CFG_REG_DDMA_DST_DRQ_TYPE_DRAM |
+	       SUNXI_DMA_DDMA_CFG_REG_DMA_SRC_DATA_WIDTH_32 |
+	       SUNXI_DMA_DDMA_CFG_REG_DMA_SRC_ADDR_MODE_IO |
+	       SUNXI_DMA_DDMA_CFG_REG_DDMA_SRC_DRQ_TYPE_NFC,
+	       SUNXI_DMA_BASE + SUNXI_DMA_CFG_REG0);
 
-	writel((NFC_CMD_RNDOUTSTART << NFC_RANDOM_READ_CMD1_OFFSET)
-		| (NFC_CMD_RNDOUT << NFC_RANDOM_READ_CMD0_OFFSET)
-		| (NFC_CMD_READSTART | NFC_READ_CMD_OFFSET), SUNXI_NFC_BASE
-			+ NFC_RCMD_SET);
-	writel(1, SUNXI_NFC_BASE + NFC_SECTOR_NUM);
-	writel(((page & 0xFFFF) << 16) | column,
-	       SUNXI_NFC_BASE + NFC_ADDR_LOW);
-	writel((page >> 16) & 0xFF, SUNXI_NFC_BASE + NFC_ADDR_HIGH);
+	writel(nsectors, SUNXI_NFC_BASE + NFC_SECTOR_NUM);
 	writel(NFC_ST_DMA_INT_FLAG, SUNXI_NFC_BASE + NFC_ST);
-	writel(NFC_SEND_CMD1 | NFC_SEND_CMD2 | NFC_DATA_TRANS |
-		NFC_PAGE_CMD | NFC_WAIT_FLAG |
-		((addr_cycles - 1) << NFC_ADDR_NUM_OFFSET) |
-		NFC_SEND_ADR | NFC_DATA_SWAP_METHOD | (syndrome ? NFC_SEQ : 0),
-		SUNXI_NFC_BASE + NFC_CMD);
+	writel(NFC_DATA_TRANS |	NFC_PAGE_CMD | NFC_DATA_SWAP_METHOD,
+	       SUNXI_NFC_BASE + NFC_CMD);
 
 	if (!check_value(SUNXI_NFC_BASE + NFC_ST, NFC_ST_DMA_INT_FLAG,
-			 MAX_RETRIES)) {
+			 DEFAULT_TIMEOUT_US)) {
 		printf("Error while initializing dma interrupt\n");
-		return -1;
+		return -EIO;
 	}
 	writel(NFC_ST_DMA_INT_FLAG, SUNXI_NFC_BASE + NFC_ST);
 
 	if (!check_value_negated(SUNXI_DMA_BASE + SUNXI_DMA_CFG_REG0,
-				 SUNXI_DMA_DDMA_CFG_REG_LOADING, MAX_RETRIES)) {
+				 SUNXI_DMA_DDMA_CFG_REG_LOADING,
+				 DEFAULT_TIMEOUT_US)) {
 		printf("Error while waiting for dma transfer to finish\n");
-		return -1;
+		return -EIO;
 	}
 
 	invalidate_dcache_range(dst,
-			        ALIGN(dst + ecc_page_size, ARCH_DMA_MINALIGN));
+				ALIGN(dst + conf->ecc_size, ARCH_DMA_MINALIGN));
 
-	if (readl(SUNXI_NFC_BASE + NFC_ECC_ST))
-		return -1;
+	val = readl(SUNXI_NFC_BASE + NFC_ECC_ST);
 
-	return 0;
+	/* ECC error detected. */
+	if (val & 0xffff)
+		return -EIO;
+
+	/*
+	 * Return 1 if the page is empty.
+	 * We consider the page as empty if the first ECC block is marked
+	 * empty.
+	 */
+	return (val & 0x10000) ? 1 : 0;
 }
 
-static int nand_read_ecc(int page_size, int ecc_strength, int ecc_page_size,
-	int addr_cycles, uint32_t offs, uint32_t size, void *dest, int syndrome)
+static int nand_max_ecc_strength(struct nfc_config *conf)
 {
-	void *end = dest + size;
-
-	clrsetbits_le32(SUNXI_NFC_BASE + NFC_CTL, NFC_CTL_PAGE_SIZE_MASK,
-			NFC_CTL_PAGE_SIZE(page_size));
-
-	for ( ;dest < end; dest += ecc_page_size, offs += ecc_page_size) {
-		if (nand_read_page(page_size, ecc_strength, ecc_page_size,
-				   addr_cycles, offs, (dma_addr_t)dest,
-				   syndrome))
-			return -1;
-	}
-
-	return 0;
-}
-
-static int nand_read_buffer(uint32_t offs, unsigned int size, void *dest,
-			    int syndrome)
-{
-	const struct {
-		int page_size;
-		int ecc_strength;
-		int ecc_page_size;
-		int addr_cycles;
-	} nand_configs[] = {
-		{  8192, 40, 1024, 5 },
-		{ 16384, 56, 1024, 5 },
-		{  8192, 24, 1024, 5 },
-		{  4096, 24, 1024, 5 },
-	};
-	static int nand_config = -1;
+	static const int ecc_bytes[] = { 32, 46, 54, 60, 74, 88, 102, 110, 116 };
+	int max_oobsize, max_ecc_bytes;
+	int nsectors = conf->page_size / conf->ecc_size;
 	int i;
 
-	if (nand_config == -1) {
-		for (i = 0; i < ARRAY_SIZE(nand_configs); i++) {
-			debug("nand: trying page %d ecc %d / %d addr %d: ",
-			      nand_configs[i].page_size,
-			      nand_configs[i].ecc_strength,
-			      nand_configs[i].ecc_page_size,
-			      nand_configs[i].addr_cycles);
-			if (nand_read_ecc(nand_configs[i].page_size,
-					  nand_configs[i].ecc_strength,
-					  nand_configs[i].ecc_page_size,
-					  nand_configs[i].addr_cycles,
-					  offs, size, dest, syndrome) == 0) {
-				debug("success\n");
-				nand_config = i;
-				return 0;
-			}
-			debug("failed\n");
-		}
-		return -1;
+	/*
+	 * ECC strength is limited by the size of the OOB area which is
+	 * correlated with the page size.
+	 */
+	switch (conf->page_size) {
+	case 2048:
+		max_oobsize = 64;
+		break;
+	case 4096:
+		max_oobsize = 256;
+		break;
+	case 8192:
+		max_oobsize = 640;
+		break;
+	case 16384:
+		max_oobsize = 1664;
+		break;
+	default:
+		return -EINVAL;
 	}
 
-	return nand_read_ecc(nand_configs[nand_config].page_size,
-			     nand_configs[nand_config].ecc_strength,
-			     nand_configs[nand_config].ecc_page_size,
-			     nand_configs[nand_config].addr_cycles,
-			     offs, size, dest, syndrome);
+	max_ecc_bytes = max_oobsize / nsectors;
+
+	for (i = 0; i < ARRAY_SIZE(ecc_bytes); i++) {
+		if (ecc_bytes[i] > max_ecc_bytes)
+			break;
+	}
+
+	if (!i)
+		return -EINVAL;
+
+	return i - 1;
+}
+
+static int nand_detect_ecc_config(struct nfc_config *conf, u32 offs,
+				  void *dest)
+{
+	/* NAND with pages > 4k will likely require 1k sector size. */
+	int min_ecc_size = conf->page_size > 4096 ? 1024 : 512;
+	int page = offs / conf->page_size;
+	int ret;
+
+	/*
+	 * In most cases, 1k sectors are preferred over 512b ones, start
+	 * testing this config first.
+	 */
+	for (conf->ecc_size = 1024; conf->ecc_size >= min_ecc_size;
+	     conf->ecc_size >>= 1) {
+		int max_ecc_strength = nand_max_ecc_strength(conf);
+
+		nand_apply_config(conf);
+
+		/*
+		 * We are starting from the maximum ECC strength because
+		 * most of the time NAND vendors provide an OOB area that
+		 * barely meets the ECC requirements.
+		 */
+		for (conf->ecc_strength = max_ecc_strength;
+		     conf->ecc_strength >= 0;
+		     conf->ecc_strength--) {
+			conf->randomize = false;
+			if (nand_reset_column())
+				return -EIO;
+
+			/*
+			 * Only read the first sector to speedup detection.
+			 */
+			ret = nand_read_page(conf, offs, dest, conf->ecc_size);
+			if (!ret) {
+				return 0;
+			} else if (ret > 0) {
+				/*
+				 * If page is empty we can't deduce anything
+				 * about the ECC config => stop the detection.
+				 */
+				return -EINVAL;
+			}
+
+			conf->randomize = true;
+			conf->nseeds = ARRAY_SIZE(random_seed);
+			do {
+				if (nand_reset_column())
+					return -EIO;
+
+				if (!nand_read_page(conf, offs, dest,
+						    conf->ecc_size))
+					return 0;
+
+				/*
+				 * Find the next ->nseeds value that would
+				 * change the randomizer seed for the page
+				 * we're trying to read.
+				 */
+				while (conf->nseeds >= 16) {
+					int seed = page % conf->nseeds;
+
+					conf->nseeds >>= 1;
+					if (seed != page % conf->nseeds)
+						break;
+				}
+			} while (conf->nseeds >= 16);
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int nand_detect_config(struct nfc_config *conf, u32 offs, void *dest)
+{
+	if (conf->valid)
+		return 0;
+
+	/*
+	 * Modern NANDs are more likely than legacy ones, so we start testing
+	 * with 5 address cycles.
+	 */
+	for (conf->addr_cycles = 5;
+	     conf->addr_cycles >= 4;
+	     conf->addr_cycles--) {
+		int max_page_size = conf->addr_cycles == 4 ? 2048 : 16384;
+
+		/*
+		 * Ignoring 1k pages cause I'm not even sure this case exist
+		 * in the real world.
+		 */
+		for (conf->page_size = 2048; conf->page_size <= max_page_size;
+		     conf->page_size <<= 1) {
+			if (nand_load_page(conf, offs))
+				return -1;
+
+			if (!nand_detect_ecc_config(conf, offs, dest)) {
+				conf->valid = true;
+				return 0;
+			}
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int nand_read_buffer(struct nfc_config *conf, uint32_t offs,
+			    unsigned int size, void *dest)
+{
+	int first_seed, page, ret;
+
+	size = ALIGN(size, conf->page_size);
+	page = offs / conf->page_size;
+	first_seed = page % conf->nseeds;
+
+	for (; size; size -= conf->page_size) {
+		if (nand_load_page(conf, offs))
+			return -1;
+
+		ret = nand_read_page(conf, offs, dest, conf->page_size);
+		/*
+		 * The ->nseeds value should be equal to the number of pages
+		 * in an eraseblock. Since we don't know this information in
+		 * advance we might have picked a wrong value.
+		 */
+		if (ret < 0 && conf->randomize) {
+			int cur_seed = page % conf->nseeds;
+
+			/*
+			 * We already tried all the seed values => we are
+			 * facing a real corruption.
+			 */
+			if (cur_seed < first_seed)
+				return -EIO;
+
+			/* Try to adjust ->nseeds and read the page again... */
+			conf->nseeds = cur_seed;
+
+			if (nand_reset_column())
+				return -EIO;
+
+			/* ... it still fails => it's a real corruption. */
+			if (nand_read_page(conf, offs, dest, conf->page_size))
+				return -EIO;
+		} else if (ret && conf->randomize) {
+			memset(dest, 0xff, conf->page_size);
+		}
+
+		page++;
+		offs += conf->page_size;
+		dest += conf->page_size;
+	}
+
+	return 0;
 }
 
 int nand_spl_load_image(uint32_t offs, unsigned int size, void *dest)
 {
-#if CONFIG_SYS_NAND_U_BOOT_OFFS == CONFIG_SPL_PAD_TO
-	/*
-	 * u-boot-dtb.bin appended to SPL, use syndrome (like the BROM does)
-	 * and try different erase block sizes to find the backup.
-	 */
-	const uint32_t boot_offsets[] = {
-		0 * 1024 * 1024 + CONFIG_SYS_NAND_U_BOOT_OFFS,
-		1 * 1024 * 1024 + CONFIG_SYS_NAND_U_BOOT_OFFS,
-		2 * 1024 * 1024 + CONFIG_SYS_NAND_U_BOOT_OFFS,
-		4 * 1024 * 1024 + CONFIG_SYS_NAND_U_BOOT_OFFS,
-	};
-	const int syndrome = 1;
-#else
-	/*
-	 * u-boot-dtb.bin on its own partition, do not use syndrome, u-boot
-	 * partition sits after 2 eraseblocks (spl, spl-backup), look for
-	 * backup u-boot 1 erase block further.
-	 */
-	const uint32_t eraseblock_size = CONFIG_SYS_NAND_U_BOOT_OFFS / 2;
-	const uint32_t boot_offsets[] = {
-		CONFIG_SYS_NAND_U_BOOT_OFFS,
-		CONFIG_SYS_NAND_U_BOOT_OFFS + eraseblock_size,
-	};
-	const int syndrome = 0;
-#endif
-	int i;
+	static struct nfc_config conf = { };
+	int ret;
 
-	if (offs == CONFIG_SYS_NAND_U_BOOT_OFFS) {
-		for (i = 0; i < ARRAY_SIZE(boot_offsets); i++) {
-			if (nand_read_buffer(boot_offsets[i], size,
-					     dest, syndrome) == 0)
-				return 0;
-		}
-		return -1;
-	}
+	ret = nand_detect_config(&conf, offs, dest);
+	if (ret)
+		return ret;
 
-	return nand_read_buffer(offs, size, dest, syndrome);
+	return nand_read_buffer(&conf, offs, size, dest);
 }
 
 void nand_deselect(void)