spl: nand: sunxi: add support for NAND config auto-detection

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>
This commit is contained in:
Boris Brezillon 2016-06-06 10:17:02 +02:00 committed by Scott Wood
parent 4e7d1b3beb
commit 7748b41482

View file

@ -103,6 +103,7 @@ struct nfc_config {
int addr_cycles; int addr_cycles;
int nseeds; int nseeds;
bool randomize; bool randomize;
bool valid;
}; };
/* minimal "boot0" style NAND support for Allwinner A20 */ /* 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; 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, static int nand_read_page(const struct nfc_config *conf, u32 offs,
void *dest, int len) 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; return (val & 0x10000) ? 1 : 0;
} }
static int nand_read_ecc(int page_size, int ecc_strength, int ecc_page_size, static int nand_max_ecc_strength(struct nfc_config *conf)
int addr_cycles, uint32_t offs, uint32_t size, void *dest)
{ {
void *end = dest + size; static const int ecc_bytes[] = { 32, 46, 54, 60, 74, 88, 102, 110, 116 };
static const u8 strengths[] = { 16, 24, 28, 32, 40, 48, 56, 60, 64 }; int max_oobsize, max_ecc_bytes;
struct nfc_config conf = { int nsectors = conf->page_size / conf->ecc_size;
.page_size = page_size,
.ecc_size = ecc_page_size,
.addr_cycles = addr_cycles,
.nseeds = ARRAY_SIZE(random_seed),
.randomize = true,
};
int i; int i;
for (i = 0; i < ARRAY_SIZE(strengths); i++) { /*
if (ecc_strength == strengths[i]) { * ECC strength is limited by the size of the OOB area which is
conf.ecc_strength = i; * 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; 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;
}
nand_apply_config(&conf); static int nand_detect_config(struct nfc_config *conf, u32 offs, void *dest)
{
if (conf->valid)
return 0;
for ( ;dest < end; dest += ecc_page_size, offs += page_size) { /*
if (nand_load_page(&conf, offs)) * 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; return -1;
if (nand_read_page(&conf, offs, dest, page_size)) ret = nand_read_page(conf, offs, dest, conf->page_size);
return -1; /*
* 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; return 0;
} }
static int nand_read_buffer(uint32_t offs, unsigned int size, 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 (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;
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);
}
int nand_spl_load_image(uint32_t offs, unsigned int size, void *dest) 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) void nand_deselect(void)