Browse Source

Merge git://git.denx.de/u-boot-nand-flash

Tom Rini 9 years ago
parent
commit
2313d48445

+ 35 - 6
cmd/nand.c

@@ -647,6 +647,9 @@ static int do_nand(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
 
 
 #ifdef CONFIG_CMD_NAND_TORTURE
 #ifdef CONFIG_CMD_NAND_TORTURE
 	if (strcmp(cmd, "torture") == 0) {
 	if (strcmp(cmd, "torture") == 0) {
+		loff_t endoff;
+		unsigned int failed = 0, passed = 0;
+
 		if (argc < 3)
 		if (argc < 3)
 			goto usage;
 			goto usage;
 
 
@@ -655,12 +658,37 @@ static int do_nand(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
 			return 1;
 			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
 #endif
 
 
@@ -775,7 +803,8 @@ static char nand_help_text[] =
 	"nand bad - show bad blocks\n"
 	"nand bad - show bad blocks\n"
 	"nand dump[.oob] off - dump page\n"
 	"nand dump[.oob] off - dump page\n"
 #ifdef CONFIG_CMD_NAND_TORTURE
 #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
 #endif
 	"nand scrub [-y] off size | scrub.part partition | scrub.chip\n"
 	"nand scrub [-y] off size | scrub.part partition | scrub.chip\n"
 	"    really clean NAND erasing bad blocks (UNSAFE)\n"
 	"    really clean NAND erasing bad blocks (UNSAFE)\n"

+ 1 - 1
common/fb_nand.c

@@ -34,7 +34,7 @@ __weak int board_fastboot_write_partition_setup(char *name)
 }
 }
 
 
 static int fb_nand_lookup(const char *partname, char *response,
 static int fb_nand_lookup(const char *partname, char *response,
-			  struct mtd_info **nand,
+			  struct mtd_info **mtd,
 			  struct part_info **part)
 			  struct part_info **part)
 {
 {
 	struct mtd_device *dev;
 	struct mtd_device *dev;

+ 7 - 0
common/spl/spl_nand.c

@@ -134,6 +134,13 @@ int spl_nand_load_image(void)
 #endif
 #endif
 	/* Load u-boot */
 	/* Load u-boot */
 	err = spl_nand_load_element(CONFIG_SYS_NAND_U_BOOT_OFFS, header);
 	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();
 	nand_deselect();
 	return err;
 	return err;
 }
 }

+ 7 - 3
doc/README.nand

@@ -137,7 +137,7 @@ Configuration Options:
       init:
       init:
 
 
 	/* chip is struct nand_chip, and is now provided by the driver. */
 	/* 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,
 	 * Fill in appropriate values if this driver uses these fields,
@@ -271,7 +271,7 @@ Platform specific options
 
 
 		However, for 4K pagesize NAND
 		However, for 4K pagesize NAND
 		NAND_PAGESIZE = 4096
 		NAND_PAGESIZE = 4096
-		NAND_OOBSIZE = 64
+		NAND_OOBSIZE = 224
 		ECC_BYTES = 26
 		ECC_BYTES = 26
 		2 + (4096 / 512) * 26 = 210 < NAND_OOBSIZE
 		2 + (4096 / 512) * 26 = 210 < NAND_OOBSIZE
 		Thus BCH16 can be supported on 4K page NAND.
 		Thus BCH16 can be supported on 4K page NAND.
@@ -307,7 +307,7 @@ Miscellaneous and testing commands:
   DANGEROUS!!! Factory set bad blocks will be lost. Use only
   DANGEROUS!!! Factory set bad blocks will be lost. Use only
   to remove artificial bad blocks created with the "markbad" command.
   to remove artificial bad blocks created with the "markbad" command.
 
 
-  "torture offset"
+  "torture offset [size]"
   Torture block to determine if it is still reliable.
   Torture block to determine if it is still reliable.
   Enabled by the CONFIG_CMD_NAND_TORTURE configuration option.
   Enabled by the CONFIG_CMD_NAND_TORTURE configuration option.
   This command returns 0 if the block is still reliable, else 1.
   This command returns 0 if the block is still reliable, else 1.
@@ -324,6 +324,10 @@ Miscellaneous and testing commands:
   automate actions following a nand->write() error. This would e.g. be required
   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
   in order to program or update safely firmware to NAND, especially for the UBI
   part of such firmware.
   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)
 NAND locking command (for chips with active LOCKPRE pin)

+ 18 - 3
drivers/mtd/nand/Kconfig

@@ -99,16 +99,31 @@ config SYS_NAND_BUSWIDTH_16BIT
 	    not available while configuring controller. So a static CONFIG_NAND_xx
 	    not available while configuring controller. So a static CONFIG_NAND_xx
 	    is needed to know the device's bus-width in advance.
 	    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
 config SYS_NAND_U_BOOT_OFFS
 	hex "Location in NAND to read U-Boot from"
 	hex "Location in NAND to read U-Boot from"
 	default 0x8000 if NAND_SUNXI
 	default 0x8000 if NAND_SUNXI
-	depends on NAND_SUNXI
+	depends on SYS_NAND_U_BOOT_LOCATIONS
 	help
 	help
 	Set the offset from the start of the nand where u-boot should be
 	Set the offset from the start of the nand where u-boot should be
 	loaded from.
 	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
 config SPL_NAND_DENALI
 	bool "Support Denali NAND controller for SPL"
 	bool "Support Denali NAND controller for SPL"

+ 1 - 1
drivers/mtd/nand/am335x_spl_bch.c

@@ -223,7 +223,7 @@ void nand_init(void)
 	/*
 	/*
 	 * Init board specific nand support
 	 * 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 =
 	nand_chip.IO_ADDR_R = nand_chip.IO_ADDR_W =
 		(void  __iomem *)CONFIG_SYS_NAND_BASE;
 		(void  __iomem *)CONFIG_SYS_NAND_BASE;
 	board_nand_init(&nand_chip);
 	board_nand_init(&nand_chip);

+ 1 - 1
drivers/mtd/nand/atmel_nand.c

@@ -1449,7 +1449,7 @@ int board_nand_init(struct nand_chip *nand)
 
 
 void nand_init(void)
 void nand_init(void)
 {
 {
-	mtd = &nand_chip.mtd;
+	mtd = nand_to_mtd(&nand_chip);
 	mtd->writesize = CONFIG_SYS_NAND_PAGE_SIZE;
 	mtd->writesize = CONFIG_SYS_NAND_PAGE_SIZE;
 	mtd->oobsize = CONFIG_SYS_NAND_OOBSIZE;
 	mtd->oobsize = CONFIG_SYS_NAND_OOBSIZE;
 	nand_chip.IO_ADDR_R = (void __iomem *)CONFIG_SYS_NAND_BASE;
 	nand_chip.IO_ADDR_R = (void __iomem *)CONFIG_SYS_NAND_BASE;

+ 1 - 1
drivers/mtd/nand/lpc32xx_nand_mlc.c

@@ -541,7 +541,7 @@ static struct nand_chip lpc32xx_chip;
 
 
 void board_nand_init(void)
 void board_nand_init(void)
 {
 {
-	struct mtd_info *mtd = &lpc32xx_chip.mtd;
+	struct mtd_info *mtd = nand_to_mtd(&lpc32xx_chip);
 	int ret;
 	int ret;
 
 
 	/* Set all BOARDSPECIFIC (actually core-specific) fields  */
 	/* Set all BOARDSPECIFIC (actually core-specific) fields  */

+ 1 - 1
drivers/mtd/nand/mxs_nand_spl.c

@@ -147,7 +147,7 @@ static int mxs_nand_init(void)
 
 
 	/* init mxs nand driver */
 	/* init mxs nand driver */
 	board_nand_init(&nand_chip);
 	board_nand_init(&nand_chip);
-	mtd = &nand_chip.mtd;
+	mtd = nand_to_mtd(&nand_chip);
 	/* set mtd functions */
 	/* set mtd functions */
 	nand_chip.cmdfunc = mxs_nand_command;
 	nand_chip.cmdfunc = mxs_nand_command;
 	nand_chip.numchips = 1;
 	nand_chip.numchips = 1;

+ 1 - 1
drivers/mtd/nand/nand_spl_simple.c

@@ -249,7 +249,7 @@ void nand_init(void)
 	/*
 	/*
 	 * Init board specific nand support
 	 * 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 =
 	nand_chip.IO_ADDR_R = nand_chip.IO_ADDR_W =
 		(void  __iomem *)CONFIG_SYS_NAND_BASE;
 		(void  __iomem *)CONFIG_SYS_NAND_BASE;
 	board_nand_init(&nand_chip);
 	board_nand_init(&nand_chip);

+ 1 - 1
drivers/mtd/nand/nand_util.c

@@ -820,7 +820,7 @@ int nand_torture(struct mtd_info *mtd, loff_t offset)
 {
 {
 	u_char patterns[] = {0xa5, 0x5a, 0x00};
 	u_char patterns[] = {0xa5, 0x5a, 0x00};
 	struct erase_info instr = {
 	struct erase_info instr = {
-		.mtd = nand,
+		.mtd = mtd,
 		.addr = offset,
 		.addr = offset,
 		.len = mtd->erasesize,
 		.len = mtd->erasesize,
 	};
 	};

+ 309 - 171
drivers/mtd/nand/sunxi_nand_spl.c

@@ -66,6 +66,8 @@
 #define NFC_ROW_AUTO_INC           (1 << 27)
 #define NFC_ROW_AUTO_INC           (1 << 27)
 #define NFC_SEND_CMD3              (1 << 28)
 #define NFC_SEND_CMD3              (1 << 28)
 #define NFC_SEND_CMD4              (1 << 29)
 #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_CMD_INT_FLAG        (1 << 1)
 #define NFC_ST_DMA_INT_FLAG        (1 << 2)
 #define NFC_ST_DMA_INT_FLAG        (1 << 2)
@@ -78,9 +80,6 @@
 #define NFC_CMD_RNDOUT             0x05
 #define NFC_CMD_RNDOUT             0x05
 #define NFC_CMD_READSTART          0x30
 #define NFC_CMD_READSTART          0x30
 
 
-
-#define NFC_PAGE_CMD               (2 << 30)
-
 #define SUNXI_DMA_CFG_REG0              0x300
 #define SUNXI_DMA_CFG_REG0              0x300
 #define SUNXI_DMA_SRC_START_ADDR_REG0   0x304
 #define SUNXI_DMA_SRC_START_ADDR_REG0   0x304
 #define SUNXI_DMA_DEST_START_ADDRR_REG0 0x308
 #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_WAIT_CYC (0x0F << 0)
 #define SUNXI_DMA_DDMA_PARA_REG_SRC_BLK_SIZE (0x7F << 8)
 #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 */
 /* minimal "boot0" style NAND support for Allwinner A20 */
 
 
 /* random seed used by linux */
 /* random seed used by linux */
@@ -119,38 +128,31 @@ const uint16_t random_seed[128] = {
 	0x7c57, 0x0fbe, 0x46ce, 0x4939, 0x6b17, 0x37bb, 0x3e91, 0x76db,
 	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,
 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 {
 	do {
 		int val = readl(offset) & expected_bits;
 		int val = readl(offset) & expected_bits;
 		if (negation ? !val : val)
 		if (negation ? !val : val)
 			return 1;
 			return 1;
-		mdelay(1);
-		retries++;
-	} while (retries < max_number_of_retries);
+		udelay(1);
+	} while (--timeout_us);
 
 
 	return 0;
 	return 0;
 }
 }
 
 
 static inline int check_value(int offset, int expected_bits,
 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,
 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)
 void nand_init(void)
@@ -165,7 +167,7 @@ void nand_init(void)
 	       SUNXI_NFC_BASE + NFC_CTL);
 	       SUNXI_NFC_BASE + NFC_CTL);
 
 
 	if (!check_value_negated(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");
 		printf("Couldn't initialize nand\n");
 	}
 	}
 
 
@@ -175,64 +177,97 @@ void nand_init(void)
 	       SUNXI_NFC_BASE + NFC_CMD);
 	       SUNXI_NFC_BASE + NFC_CMD);
 
 
 	if (!check_value(SUNXI_NFC_BASE + NFC_ST, NFC_ST_CMD_INT_FLAG,
 	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");
 		printf("Error timeout waiting for nand reset\n");
 		return;
 		return;
 	}
 	}
 	writel(NFC_ST_CMD_INT_FLAG, SUNXI_NFC_BASE + NFC_ST);
 	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 };
-
-	for (i = 0; i < ARRAY_SIZE(strengths); i++) {
-		if (ecc_strength == strengths[i]) {
-			ecc_mode = i;
-			break;
-		}
+	u32 val;
+
+	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;
+}
+
+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);
 
 
-	page = real_addr / page_size;
-	column = real_addr % page_size;
+	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;
+	}
 
 
-	if (syndrome)
-		column += (column / ecc_page_size) * ecc_off;
+	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 */
 	/* clear ecc status */
 	writel(0, SUNXI_NFC_BASE + NFC_ECC_ST);
 	writel(0, SUNXI_NFC_BASE + NFC_ECC_ST);
 
 
 	/* Choose correct seed */
 	/* 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);
 		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 */
 	/* SUNXI_DMA */
 	writel(0x0, SUNXI_DMA_BASE + SUNXI_DMA_CFG_REG0); /* clr dma cmd */
 	writel(0x0, SUNXI_DMA_BASE + SUNXI_DMA_CFG_REG0); /* clr dma cmd */
@@ -241,158 +276,261 @@ static int nand_read_page(int page_size, int ecc_strength, int ecc_page_size,
 	       SUNXI_DMA_BASE + SUNXI_DMA_SRC_START_ADDR_REG0);
 	       SUNXI_DMA_BASE + SUNXI_DMA_SRC_START_ADDR_REG0);
 	/* read to RAM */
 	/* read to RAM */
 	writel(dst, SUNXI_DMA_BASE + SUNXI_DMA_DEST_START_ADDRR_REG0);
 	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((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(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(nsectors, SUNXI_NFC_BASE + NFC_SECTOR_NUM);
 	writel(NFC_ST_DMA_INT_FLAG, SUNXI_NFC_BASE + NFC_ST);
 	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,
 	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");
 		printf("Error while initializing dma interrupt\n");
-		return -1;
+		return -EIO;
 	}
 	}
 	writel(NFC_ST_DMA_INT_FLAG, SUNXI_NFC_BASE + NFC_ST);
 	writel(NFC_ST_DMA_INT_FLAG, SUNXI_NFC_BASE + NFC_ST);
 
 
 	if (!check_value_negated(SUNXI_DMA_BASE + SUNXI_DMA_CFG_REG0,
 	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");
 		printf("Error while waiting for dma transfer to finish\n");
-		return -1;
+		return -EIO;
 	}
 	}
 
 
 	invalidate_dcache_range(dst,
 	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;
+	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;
+
+	/*
+	 * 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;
+	}
 
 
-	clrsetbits_le32(SUNXI_NFC_BASE + NFC_CTL, NFC_CTL_PAGE_SIZE_MASK,
-			NFC_CTL_PAGE_SIZE(page_size));
+	max_ecc_bytes = max_oobsize / nsectors;
 
 
-	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;
+	for (i = 0; i < ARRAY_SIZE(ecc_bytes); i++) {
+		if (ecc_bytes[i] > max_ecc_bytes)
+			break;
 	}
 	}
 
 
-	return 0;
+	if (!i)
+		return -EINVAL;
+
+	return i - 1;
 }
 }
 
 
-static int nand_read_buffer(uint32_t offs, unsigned int size, void *dest,
-			    int syndrome)
+static int nand_detect_ecc_config(struct nfc_config *conf, u32 offs,
+				  void *dest)
 {
 {
-	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;
-	int i;
+	/* 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;
 
 
-	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;
+	/*
+	 * 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;
 				return 0;
+			} else if (ret > 0) {
+				/*
+				 * If page is empty we can't deduce anything
+				 * about the ECC config => stop the detection.
+				 */
+				return -EINVAL;
 			}
 			}
-			debug("failed\n");
+
+			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 -1;
 	}
 	}
 
 
-	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);
+	return -EINVAL;
 }
 }
 
 
-int nand_spl_load_image(uint32_t offs, unsigned int size, void *dest)
+static int nand_detect_config(struct nfc_config *conf, u32 offs, 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
+	if (conf->valid)
+		return 0;
+
 	/*
 	/*
-	 * 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.
+	 * Modern NANDs are more likely than legacy ones, so we start testing
+	 * with 5 address cycles.
 	 */
 	 */
-	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;
-
-	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)
+	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 0;
+			}
 		}
 		}
-		return -1;
 	}
 	}
 
 
-	return nand_read_buffer(offs, size, dest, syndrome);
+	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)
+{
+	static struct nfc_config conf = { };
+	int ret;
+
+	ret = nand_detect_config(&conf, offs, dest);
+	if (ret)
+		return ret;
+
+	return nand_read_buffer(&conf, offs, size, dest);
 }
 }
 
 
 void nand_deselect(void)
 void nand_deselect(void)