[U-Boot] [PATCH 7/7] spl: nand: sunxi: add support for NAND config auto-detection
Boris Brezillon
boris.brezillon at free-electrons.com
Wed Jun 1 13:04:04 CEST 2016
On Fri, 20 May 2016 15:55:51 +0200
Boris Brezillon <boris.brezillon at 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 at 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
More information about the U-Boot
mailing list