diff mbox

[U-Boot,v2,7/7] spl: nand: sunxi: add support for NAND config auto-detection

Message ID 1464780204-17737-8-git-send-email-boris.brezillon@free-electrons.com
State Accepted
Commit 7748b4148207d6e0dca8c0b5b95975e8e64179db
Delegated to: Scott Wood
Headers show

Commit Message

Boris Brezillon June 1, 2016, 11:23 a.m. UTC
NAND chips are supposed to expose their capabilities through advanced
mechanisms like READID, ONFI or JEDEC parameter tables. While those
methods are appropriate for the bootloader itself, it's way to
complicated and takes too much space to fit in the SPL.

Replace those mechanisms by a dumb 'trial and error' mechanism.

With this new approach we can get rid of the fixed config list that was
used in the sunxi NAND SPL driver.

Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
Acked-by: Hans de Goede <hdegoede@redhat.com>
---
Changes since v1:
- fix the nand_max_ecc_strength()
---
 drivers/mtd/nand/sunxi_nand_spl.c | 262 +++++++++++++++++++++++++++++---------
 1 file changed, 204 insertions(+), 58 deletions(-)

Comments

Siarhei Siamashka June 1, 2016, 12:35 p.m. UTC | #1
On Wed,  1 Jun 2016 13:23:24 +0200
Boris Brezillon <boris.brezillon@free-electrons.com> wrote:

> NAND chips are supposed to expose their capabilities through advanced
> mechanisms like READID, ONFI or JEDEC parameter tables. While those
> methods are appropriate for the bootloader itself, it's way to
> complicated and takes too much space to fit in the SPL.
> 
> Replace those mechanisms by a dumb 'trial and error' mechanism.
> 
> With this new approach we can get rid of the fixed config list that was
> used in the sunxi NAND SPL driver.
> 
> Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
> Acked-by: Hans de Goede <hdegoede@redhat.com>

We can also have these NAND parameters stored in the SPL header and
added there by a NAND image builder tool. This may save some precious
space in the SPL and also improve the reliability of detection.

Yes, this brings the necessity of the image builder tool into the
spotlight (something that converts the "u-boot-sunxi-with-spl.bin"
to a NAND image) but this has always been a problem. We have some
ongoing discussion about this in the linux-sunxi mailing list:
    https://groups.google.com/forum/#!topic/linux-sunxi/HsWRG-nuV-w

It also makes a lot of sense to have the NAND support functionality
enabled in the SPL for all sunxi boards by default, so the code size
does matter. We still do have the runtime decompression opportunity
as the strategic reserve [1], which should provide additional 4 or
5 KiB of space for the code. Still we need to be very careful about
using up this reserve, to ensure that it is well spent on something
useful (such as NAND support) instead of being just wasted by the
bloatware cultists :-)

[1] http://lists.denx.de/pipermail/u-boot/2015-September/226963.html
Boris Brezillon June 1, 2016, 1:22 p.m. UTC | #2
On Wed, 1 Jun 2016 15:35:07 +0300
Siarhei Siamashka <siarhei.siamashka@gmail.com> wrote:

> On Wed,  1 Jun 2016 13:23:24 +0200
> Boris Brezillon <boris.brezillon@free-electrons.com> wrote:
> 
> > NAND chips are supposed to expose their capabilities through advanced
> > mechanisms like READID, ONFI or JEDEC parameter tables. While those
> > methods are appropriate for the bootloader itself, it's way to
> > complicated and takes too much space to fit in the SPL.
> > 
> > Replace those mechanisms by a dumb 'trial and error' mechanism.
> > 
> > With this new approach we can get rid of the fixed config list that was
> > used in the sunxi NAND SPL driver.
> > 
> > Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
> > Acked-by: Hans de Goede <hdegoede@redhat.com>  
> 
> We can also have these NAND parameters stored in the SPL header and
> added there by a NAND image builder tool. This may save some precious
> space in the SPL and also improve the reliability of detection.
> 
> Yes, this brings the necessity of the image builder tool into the
> spotlight (something that converts the "u-boot-sunxi-with-spl.bin"
> to a NAND image) but this has always been a problem. We have some
> ongoing discussion about this in the linux-sunxi mailing list:
>     https://groups.google.com/forum/#!topic/linux-sunxi/HsWRG-nuV-w
> 
> It also makes a lot of sense to have the NAND support functionality
> enabled in the SPL for all sunxi boards by default, so the code size
> does matter. We still do have the runtime decompression opportunity
> as the strategic reserve [1], which should provide additional 4 or
> 5 KiB of space for the code. Still we need to be very careful about
> using up this reserve, to ensure that it is well spent on something
> useful (such as NAND support) instead of being just wasted by the
> bloatware cultists :-)

Oh, come one! I just did the test, and we save 352 bytes when dropping
the auto-detection code. Do we really want to delay the NAND support
just because you want the perfect solution (which as I already said,
will not be trivial to implement).

I'm not telling that your approach is wrong, just that it's not a
priority right now, so let's move on and improve it iteratively.
Maxime Ripard June 1, 2016, 1:22 p.m. UTC | #3
Hi,

On Wed, Jun 01, 2016 at 03:35:07PM +0300, Siarhei Siamashka wrote:
> On Wed,  1 Jun 2016 13:23:24 +0200
> Boris Brezillon <boris.brezillon@free-electrons.com> wrote:
> 
> > NAND chips are supposed to expose their capabilities through advanced
> > mechanisms like READID, ONFI or JEDEC parameter tables. While those
> > methods are appropriate for the bootloader itself, it's way to
> > complicated and takes too much space to fit in the SPL.
> > 
> > Replace those mechanisms by a dumb 'trial and error' mechanism.
> > 
> > With this new approach we can get rid of the fixed config list that was
> > used in the sunxi NAND SPL driver.
> > 
> > Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
> > Acked-by: Hans de Goede <hdegoede@redhat.com>
> 
> We can also have these NAND parameters stored in the SPL header and
> added there by a NAND image builder tool. This may save some precious
> space in the SPL and also improve the reliability of detection.

So you want to favour a solution that hardcodes the NAND configuration
over a solution that runtime-detects what it needs?

I think we are aiming for two very different use-cases: we want to
have everything working ahead of time on various NAND chips during
production, you want to have the end-users being able to reflash
something after the production.

Boris' solution address both use-cases, yours address only the latter.

> Yes, this brings the necessity of the image builder tool into the
> spotlight (something that converts the "u-boot-sunxi-with-spl.bin"
> to a NAND image) but this has always been a problem.

I know you like it a lot, but I don't see how
u-boot-sunxi-with-spl.bin is relevant here. It's an image that has
always been built for the MMC.

We are talking about the NAND here, that will use a different image
format anyway to deal with the randomizer and the ECC, that will have
to use redundancy for the SPL, U-Boot and probably environment images,
that will have to be padded with random data, and doesn't want to
waste the useless (on NAND) padding that is used in that image.

> We have some
> ongoing discussion about this in the linux-sunxi mailing list:
>     https://groups.google.com/forum/#!topic/linux-sunxi/HsWRG-nuV-w
> 
> It also makes a lot of sense to have the NAND support functionality
> enabled in the SPL for all sunxi boards by default

On all the sunxi boards that have NAND at least.

> so the code size does matter. We still do have the runtime
> decompression opportunity as the strategic reserve [1], which should
> provide additional 4 or 5 KiB of space for the code. Still we need
> to be very careful about using up this reserve, to ensure that it is
> well spent on something useful (such as NAND support) instead of
> being just wasted by the bloatware cultists :-)

Good thing we are talking about NAND support and not some bloat then :)

Maxime
diff mbox

Patch

diff --git a/drivers/mtd/nand/sunxi_nand_spl.c b/drivers/mtd/nand/sunxi_nand_spl.c
index b43f2af..1ef7366 100644
--- a/drivers/mtd/nand/sunxi_nand_spl.c
+++ b/drivers/mtd/nand/sunxi_nand_spl.c
@@ -103,6 +103,7 @@  struct nfc_config {
 	int addr_cycles;
 	int nseeds;
 	bool randomize;
+	bool valid;
 };
 
 /* minimal "boot0" style NAND support for Allwinner A20 */
@@ -219,6 +220,26 @@  static int nand_load_page(const struct nfc_config *conf, u32 offs)
 	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);
+
+	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)
 {
@@ -303,88 +324,213 @@  static int nand_read_page(const struct nfc_config *conf, u32 offs,
 	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)
+static int nand_max_ecc_strength(struct nfc_config *conf)
 {
-	void *end = dest + size;
-	static const u8 strengths[] = { 16, 24, 28, 32, 40, 48, 56, 60, 64 };
-	struct nfc_config conf = {
-		.page_size = page_size,
-		.ecc_size = ecc_page_size,
-		.addr_cycles = addr_cycles,
-		.nseeds = ARRAY_SIZE(random_seed),
-		.randomize = true,
-	};
+	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;
 
-	for (i = 0; i < ARRAY_SIZE(strengths); i++) {
-		if (ecc_strength == strengths[i]) {
-			conf.ecc_strength = 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;
+	}
+
+	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;
 
-	nand_apply_config(&conf);
+	return i - 1;
+}
 
-	for ( ;dest < end; dest += ecc_page_size, offs += page_size) {
-		if (nand_load_page(&conf, offs))
-			return -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;
 
-		if (nand_read_page(&conf, offs, dest, page_size))
-			return -1;
+	/*
+	 * 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 0;
+	return -EINVAL;
 }
 
-static int nand_read_buffer(uint32_t offs, unsigned int size, void *dest)
+static int nand_detect_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;
+	if (conf->valid)
+		return 0;
 
-	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) == 0) {
-				debug("success\n");
-				nand_config = i;
+	/*
+	 * 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;
 			}
-			debug("failed\n");
 		}
-		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);
+	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)
 {
-	return nand_read_buffer(offs, size, 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)