summaryrefslogtreecommitdiffstats
path: root/drivers/mtd
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2012-08-01 17:49:27 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2012-08-01 17:49:27 +0200
commitb1a4e722c3754ea11fc621e938196e8db57b1e39 (patch)
tree4706b4b99f1391e4db39a6e95673044d0c282828 /drivers/mtd
parent7db4b8a16a634ba1c225b918294305606f05f5e2 (diff)
parent632c45795065e6a7471ab82be38e808eb6204341 (diff)
downloadbarebox-b1a4e722c3754ea11fc621e938196e8db57b1e39.tar.gz
barebox-b1a4e722c3754ea11fc621e938196e8db57b1e39.tar.xz
Merge branch 'for-next/onfi'
Diffstat (limited to 'drivers/mtd')
-rw-r--r--drivers/mtd/nand/nand_base.c174
-rw-r--r--drivers/mtd/nand/nand_imx.c98
2 files changed, 242 insertions, 30 deletions
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index c4eca0d2a3..a5bf757e47 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -1027,6 +1027,108 @@ static void nand_set_defaults(struct nand_chip *chip, int busw)
}
/*
+ * sanitize ONFI strings so we can safely print them
+ */
+static void sanitize_string(char *s, size_t len)
+{
+ ssize_t i;
+
+ /* null terminate */
+ s[len - 1] = 0;
+
+ /* remove non printable chars */
+ for (i = 0; i < len - 1; i++) {
+ if (s[i] < ' ' || s[i] > 127)
+ s[i] = '?';
+ }
+
+ /* remove trailing spaces */
+ strim(s);
+}
+
+static u16 onfi_crc16(u16 crc, u8 const *p, size_t len)
+{
+ int i;
+ while (len--) {
+ crc ^= *p++ << 8;
+ for (i = 0; i < 8; i++)
+ crc = (crc << 1) ^ ((crc & 0x8000) ? 0x8005 : 0);
+ }
+
+ return crc;
+}
+
+/*
+ * Check if the NAND chip is ONFI compliant, returns 1 if it is, 0 otherwise
+ */
+static int nand_flash_detect_onfi(struct mtd_info *mtd, struct nand_chip *chip,
+ int *busw)
+{
+ struct nand_onfi_params *p = &chip->onfi_params;
+ int i;
+ int val;
+
+ /* try ONFI for unknow chip or LP */
+ chip->cmdfunc(mtd, NAND_CMD_READID, 0x20, -1);
+ if (chip->read_byte(mtd) != 'O' || chip->read_byte(mtd) != 'N' ||
+ chip->read_byte(mtd) != 'F' || chip->read_byte(mtd) != 'I')
+ return 0;
+
+ printk(KERN_INFO "ONFI flash detected ... ");
+ chip->cmdfunc(mtd, NAND_CMD_PARAM, 0, -1);
+ for (i = 0; i < 3; i++) {
+ chip->read_buf(mtd, (uint8_t *)p, sizeof(*p));
+ if (onfi_crc16(ONFI_CRC_BASE, (uint8_t *)p, 254) ==
+ le16_to_cpu(p->crc)) {
+ printk(KERN_INFO "ONFI param page %d valid\n", i);
+ break;
+ }
+ }
+
+ if (i == 3) {
+ printk(KERN_INFO "no valid ONFI param page found\n");
+ return 0;
+ }
+
+ /* check version */
+ val = le16_to_cpu(p->revision);
+ if (val & (1 << 5))
+ chip->onfi_version = 23;
+ else if (val & (1 << 4))
+ chip->onfi_version = 22;
+ else if (val & (1 << 3))
+ chip->onfi_version = 21;
+ else if (val & (1 << 2))
+ chip->onfi_version = 20;
+ else if (val & (1 << 1))
+ chip->onfi_version = 10;
+ else
+ chip->onfi_version = 0;
+
+ if (!chip->onfi_version) {
+ printk(KERN_INFO "unsupported ONFI version: %d\n", val);
+ return 0;
+ }
+
+ sanitize_string(p->manufacturer, sizeof(p->manufacturer));
+ sanitize_string(p->model, sizeof(p->model));
+ if (!mtd->name)
+ mtd->name = p->model;
+ mtd->writesize = le32_to_cpu(p->byte_per_page);
+ mtd->erasesize = le32_to_cpu(p->pages_per_block) * mtd->writesize;
+ mtd->oobsize = le16_to_cpu(p->spare_bytes_per_page);
+ chip->chipsize = (uint64_t)le32_to_cpu(p->blocks_per_lun) * mtd->erasesize;
+ *busw = 0;
+ if (le16_to_cpu(p->features) & 1)
+ *busw = NAND_BUSWIDTH_16;
+
+ chip->options &= ~NAND_CHIPOPTIONS_MSK;
+ chip->options |= NAND_NO_READRDY & NAND_CHIPOPTIONS_MSK;
+
+ return 1;
+}
+
+/*
* Get the flash and manufacturer id and lookup if the type is supported
*/
static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
@@ -1036,6 +1138,7 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
struct nand_flash_dev *type = NULL;
int i, dev_id, maf_idx;
int tmp_id, tmp_manf;
+ int ret;
/* Select the device */
chip->select_chip(mtd, 0);
@@ -1081,9 +1184,16 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
}
}
+ chip->onfi_version = 0;
if (!type) {
- printk(KERN_ERR "NAND type unknown: %02x,%02x\n", *maf_id, dev_id);
- return ERR_PTR(-ENODEV);
+ /* Check is chip is ONFI compliant */
+ ret = nand_flash_detect_onfi(mtd, chip, &busw);
+ if (ret)
+ goto ident_done;
+ else {
+ printk(KERN_ERR "NAND type unknown: %02x,%02x\n", *maf_id, dev_id);
+ return ERR_PTR(-ENODEV);
+ }
}
if (!mtd->name)
@@ -1126,17 +1236,39 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
break;
}
+ /* Get chip options, preserve non chip based options */
+ chip->options &= ~NAND_CHIPOPTIONS_MSK;
+ chip->options |= type->options & NAND_CHIPOPTIONS_MSK;
+
+ /* Check if chip is a not a samsung device. Do not clear the
+ * options for chips which are not having an extended id.
+ */
+ if (*maf_id != NAND_MFR_SAMSUNG && !type->pagesize)
+ chip->options &= ~NAND_SAMSUNG_LP_OPTIONS;
+
+ident_done:
+ /*
+ * Set chip as a default. Board drivers can override it, if necessary
+ */
+ chip->options |= NAND_NO_AUTOINCR;
+
+ /* Try to identify manufacturer */
+ for (maf_idx = 0; nand_manuf_ids[maf_idx].id != 0x0; maf_idx++) {
+ if (nand_manuf_ids[maf_idx].id == *maf_id)
+ break;
+ }
+
/*
* Check, if buswidth is correct. Hardware drivers should set
* chip correct !
*/
if (busw != (chip->options & NAND_BUSWIDTH_16)) {
printk(KERN_INFO "NAND device: Manufacturer ID:"
- " 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id,
- dev_id, nand_manuf_ids[maf_idx].name, mtd->name);
+ " 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id,
+ dev_id, nand_manuf_ids[maf_idx].name, mtd->name);
printk(KERN_WARNING "NAND bus width %d instead %d bit\n",
- (chip->options & NAND_BUSWIDTH_16) ? 16 : 8,
- busw ? 16 : 8);
+ (chip->options & NAND_BUSWIDTH_16) ? 16 : 8,
+ busw ? 16 : 8);
return ERR_PTR(-EINVAL);
}
@@ -1147,27 +1279,17 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
chip->bbt_erase_shift = chip->phys_erase_shift =
ffs(mtd->erasesize) - 1;
- chip->chip_shift = ffs(chip->chipsize) - 1;
+ if (chip->chipsize & 0xffffffff)
+ chip->chip_shift = ffs((unsigned)chip->chipsize) - 1;
+ else {
+ chip->chip_shift = ffs((unsigned)(chip->chipsize >> 32));
+ chip->chip_shift += 32 - 1;
+ }
/* Set the bad block position */
chip->badblockpos = mtd->writesize > 512 ?
NAND_LARGE_BADBLOCK_POS : NAND_SMALL_BADBLOCK_POS;
- /* Get chip options, preserve non chip based options */
- chip->options &= ~NAND_CHIPOPTIONS_MSK;
- chip->options |= type->options & NAND_CHIPOPTIONS_MSK;
-
- /*
- * Set chip as a default. Board drivers can override it, if necessary
- */
- chip->options |= NAND_NO_AUTOINCR;
-
- /* Check if chip is a not a samsung device. Do not clear the
- * options for chips which are not having an extended id.
- */
- if (*maf_id != NAND_MFR_SAMSUNG && !type->pagesize)
- chip->options &= ~NAND_SAMSUNG_LP_OPTIONS;
-
#ifdef CONFIG_MTD_WRITE
/* Check for AND chips with 4 page planes */
if (chip->options & NAND_4PAGE_ARRAY)
@@ -1179,9 +1301,11 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
if (mtd->writesize > 512 && chip->cmdfunc == nand_command)
chip->cmdfunc = nand_command_lp;
- printk(KERN_INFO "NAND device: Manufacturer ID:"
- " 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id, dev_id,
- nand_manuf_ids[maf_idx].name, type->name);
+ printk("NAND device: Manufacturer ID: 0x%02x, Chip ID: 0x%02x (%s %s),"
+ " page size: %d, OOB size: %d\n",
+ *maf_id, dev_id, nand_manuf_ids[maf_idx].name,
+ chip->onfi_version ? chip->onfi_params.model : type->name,
+ mtd->writesize, mtd->oobsize);
return type;
}
diff --git a/drivers/mtd/nand/nand_imx.c b/drivers/mtd/nand/nand_imx.c
index e75ffbc819..83b49e3f29 100644
--- a/drivers/mtd/nand/nand_imx.c
+++ b/drivers/mtd/nand/nand_imx.c
@@ -106,6 +106,7 @@ struct imx_nand_host {
void (*send_addr)(struct imx_nand_host *, uint16_t);
void (*send_page)(struct imx_nand_host *, unsigned int);
void (*send_read_id)(struct imx_nand_host *);
+ void (*send_read_param)(struct imx_nand_host *);
uint16_t (*get_dev_status)(struct imx_nand_host *);
int (*check_int)(struct imx_nand_host *);
};
@@ -154,6 +155,31 @@ static struct nand_ecclayout nandv2_hw_eccoob_largepage = {
}
};
+/* OOB description for 4096 byte pages with 128 byte OOB */
+static struct nand_ecclayout nandv2_hw_eccoob_4k = {
+ .eccbytes = 8 * 9,
+ .eccpos = {
+ 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ 71, 72, 73, 74, 75, 76, 77, 78, 79,
+ 87, 88, 89, 90, 91, 92, 93, 94, 95,
+ 103, 104, 105, 106, 107, 108, 109, 110, 111,
+ 119, 120, 121, 122, 123, 124, 125, 126, 127,
+ },
+ .oobfree = {
+ {.offset = 2, .length = 4},
+ {.offset = 16, .length = 7},
+ {.offset = 32, .length = 7},
+ {.offset = 48, .length = 7},
+ {.offset = 64, .length = 7},
+ {.offset = 80, .length = 7},
+ {.offset = 96, .length = 7},
+ {.offset = 112, .length = 7},
+ }
+};
+
static void memcpy32(void *trg, const void *src, int size)
{
int i;
@@ -335,6 +361,16 @@ static void send_read_id_v3(struct imx_nand_host *host)
memcpy(host->data_buf, host->main_area0, 16);
}
+static void send_read_param_v3(struct imx_nand_host *host)
+{
+ /* Read ID into main buffer */
+ writel(NFC_OUTPUT, NFC_V3_LAUNCH);
+
+ wait_op_done(host);
+
+ memcpy(host->data_buf, host->main_area0, 1024);
+}
+
static void send_read_id_v1_v2(struct imx_nand_host *host)
{
struct nand_chip *this = &host->nand;
@@ -363,6 +399,34 @@ static void send_read_id_v1_v2(struct imx_nand_host *host)
memcpy32(host->data_buf, host->main_area0, 16);
}
+/* FIXME : to check on real HW */
+static void send_read_param_v1_v2(struct imx_nand_host *host)
+{
+ struct nand_chip *this = &host->nand;
+
+ /* NANDFC buffer 0 is used for device ID output */
+ writew(0x0, host->regs + NFC_V1_V2_BUF_ADDR);
+
+ writew(NFC_OUTPUT, host->regs + NFC_V1_V2_CONFIG2);
+
+ /* Wait for operation to complete */
+ wait_op_done(host);
+
+ if (this->options & NAND_BUSWIDTH_16) {
+ volatile u16 *mainbuf = host->main_area0;
+
+ /*
+ * Pack the every-other-byte result for 16-bit ID reads
+ * into every-byte as the generic code expects and various
+ * chips implement.
+ */
+
+ mainbuf[0] = (mainbuf[0] & 0xff) | ((mainbuf[1] & 0xff) << 8);
+ mainbuf[1] = (mainbuf[2] & 0xff) | ((mainbuf[3] & 0xff) << 8);
+ mainbuf[2] = (mainbuf[4] & 0xff) | ((mainbuf[5] & 0xff) << 8);
+ }
+ memcpy32(host->data_buf, host->main_area0, 1024);
+}
/*
* This function requests the NANDFC to perform a read of the
* NAND device status and returns the current status.
@@ -579,6 +643,10 @@ static void imx_nand_read_buf(struct mtd_info *mtd, u_char * buf, int len)
n = min(n, len);
+ /* handle the read param special case */
+ if ((mtd->writesize == 0) && (len != 0))
+ n = len;
+
memcpy(buf, host->data_buf + col, n);
host->buf_start += n;
@@ -677,8 +745,11 @@ static void mxc_do_addr_cycle(struct mtd_info *mtd, int column, int page_addr)
* layers perform a read/write buf operation,
* we will used the saved column adress to index into
* the full page.
+ *
+ * The colum address must be sent to the flash in
+ * order to get the ONFI header (0x20)
*/
- host->send_addr(host, 0);
+ host->send_addr(host, column);
if (host->pagesize_2k)
/* another col addr cycle for 2k page */
host->send_addr(host, 0);
@@ -790,9 +861,11 @@ static void preset_v3(struct mtd_info *mtd)
writel(0, NFC_V3_IPC);
+ /* if the flash has a 224 oob, the NFC must be configured to 218 */
config2 = NFC_V3_CONFIG2_ONE_CYCLE |
NFC_V3_CONFIG2_2CMD_PHASES |
- NFC_V3_CONFIG2_SPAS(mtd->oobsize >> 1) |
+ NFC_V3_CONFIG2_SPAS(((mtd->oobsize > 218) ?
+ 218 : mtd->oobsize) >> 1) |
NFC_V3_CONFIG2_ST_CMD(0x70) |
NFC_V3_CONFIG2_NUM_ADDR_PHASE0;
@@ -944,8 +1017,15 @@ static void imx_nand_command(struct mtd_info *mtd, unsigned command,
case NAND_CMD_READID:
host->send_cmd(host, command);
mxc_do_addr_cycle(mtd, column, page_addr);
- host->buf_start = 0;
host->send_read_id(host);
+ host->buf_start = 0;
+ break;
+
+ case NAND_CMD_PARAM:
+ host->send_cmd(host, command);
+ mxc_do_addr_cycle(mtd, column, page_addr);
+ host->send_read_param(host);
+ host->buf_start = 0;
break;
case NAND_CMD_ERASE1:
@@ -1024,7 +1104,7 @@ static int __init imxnd_probe(struct device_d *dev)
struct mtd_info *mtd;
struct imx_nand_platform_data *pdata = dev->platform_data;
struct imx_nand_host *host;
- struct nand_ecclayout *oob_smallpage, *oob_largepage;
+ struct nand_ecclayout *oob_smallpage, *oob_largepage, *oob_4kpage;
int err = 0;
#ifdef CONFIG_ARCH_IMX27
@@ -1047,6 +1127,7 @@ static int __init imxnd_probe(struct device_d *dev)
host->send_addr = send_addr_v1_v2;
host->send_page = send_page_v1_v2;
host->send_read_id = send_read_id_v1_v2;
+ host->send_read_param = send_read_param_v1_v2; /* FIXME : to check */
host->get_dev_status = get_dev_status_v1_v2;
host->check_int = check_int_v1_v2;
}
@@ -1059,6 +1140,7 @@ static int __init imxnd_probe(struct device_d *dev)
host->spare_len = 64;
oob_smallpage = &nandv2_hw_eccoob_smallpage;
oob_largepage = &nandv2_hw_eccoob_largepage;
+ oob_4kpage = &nandv2_hw_eccoob_4k; /* FIXME : to check */
} else if (nfc_is_v1()) {
host->base = dev_request_mem_region(dev, 0);
host->main_area0 = host->base;
@@ -1067,6 +1149,7 @@ static int __init imxnd_probe(struct device_d *dev)
host->spare_len = 16;
oob_smallpage = &nandv1_hw_eccoob_smallpage;
oob_largepage = &nandv1_hw_eccoob_largepage;
+ oob_4kpage = &nandv1_hw_eccoob_smallpage; /* FIXME : to check */
} else if (nfc_is_v3_2()) {
host->regs_ip = dev_request_mem_region(dev, 0);
host->base = dev_request_mem_region(dev, 1);
@@ -1086,10 +1169,12 @@ static int __init imxnd_probe(struct device_d *dev)
host->send_addr = send_addr_v3;
host->send_page = send_page_v3;
host->send_read_id = send_read_id_v3;
+ host->send_read_param = send_read_param_v3;
host->get_dev_status = get_dev_status_v3;
host->check_int = check_int_v3;
oob_smallpage = &nandv2_hw_eccoob_smallpage;
oob_largepage = &nandv2_hw_eccoob_largepage;
+ oob_4kpage = &nandv2_hw_eccoob_4k;
}
host->dev = dev;
@@ -1161,7 +1246,10 @@ static int __init imxnd_probe(struct device_d *dev)
imx_nand_set_layout(mtd->writesize, pdata->width == 2 ? 16 : 8);
if (mtd->writesize >= 2048) {
- this->ecc.layout = oob_largepage;
+ if (mtd->writesize == 2048)
+ this->ecc.layout = oob_largepage;
+ else
+ this->ecc.layout = oob_4kpage;
host->pagesize_2k = 1;
if (nfc_is_v21())
writew(NFC_V2_SPAS_SPARESIZE(64), host->regs + NFC_V2_SPAS);