On Fri, 20 May 2016 15:55:51 +0200 Boris Brezillon <boris.brezil...@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.brezil...@free-electrons.com> > --- > drivers/mtd/nand/sunxi_nand_spl.c | 262 > +++++++++++++++++++++++++++++--------- > 1 file changed, 204 insertions(+), 58 deletions(-) > > diff --git a/drivers/mtd/nand/sunxi_nand_spl.c > b/drivers/mtd/nand/sunxi_nand_spl.c > index b43f2af..cc2e69b 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 8196: > + max_oobsize = 620; Should be case 8192: max_oobsize = 640; I'll send a v2 fixing that. > + 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) -- Boris Brezillon, Free Electrons Embedded Linux and Kernel engineering http://free-electrons.com _______________________________________________ U-Boot mailing list U-Boot@lists.denx.de http://lists.denx.de/mailman/listinfo/u-boot