From 8525412a4dd4ea7da2c9d7c32852cc8bafc3c896 Mon Sep 17 00:00:00 2001 From: Naveen Mamindlapalli Date: Fri, 3 Jun 2011 18:41:05 +0530 Subject: [PATCH] Xilinx: ARM: Added ClearNAND ECC support --- board/xilinx/dfe/xilinx_nandpss.c | 26 ++++-- drivers/mtd/nand/nand_base.c | 142 +++++++++++++++++++++++++----- include/linux/mtd/nand.h | 68 ++++++++++++++ include/linux/string.h | 1 + lib/string.c | 39 ++++++++ 5 files changed, 247 insertions(+), 29 deletions(-) diff --git a/board/xilinx/dfe/xilinx_nandpss.c b/board/xilinx/dfe/xilinx_nandpss.c index c085691cb79..5d349abb38b 100644 --- a/board/xilinx/dfe/xilinx_nandpss.c +++ b/board/xilinx/dfe/xilinx_nandpss.c @@ -204,6 +204,7 @@ static struct xnandpss_command_format xnandpss_commands[] __devinitdata = { {NAND_CMD_RNDIN, NAND_CMD_NONE, 2, NAND_CMD_NONE}, {NAND_CMD_ERASE1, NAND_CMD_ERASE2, 3, XNANDPSS_CMD_PHASE}, {NAND_CMD_RESET, NAND_CMD_NONE, 0, NAND_CMD_NONE}, + {NAND_CMD_PARAM, NAND_CMD_NONE, 1, NAND_CMD_NONE}, {NAND_CMD_GET_FEATURES, NAND_CMD_NONE, 1, NAND_CMD_NONE}, {NAND_CMD_SET_FEATURES, NAND_CMD_NONE, 1, NAND_CMD_NONE}, {NAND_CMD_NONE, NAND_CMD_NONE, 0, 0}, @@ -220,10 +221,10 @@ static struct xnandpss_command_format xnandpss_commands[] __devinitdata = { /* Define default oob placement schemes for large and small page devices */ static struct nand_ecclayout nand_oob_16 = { .eccbytes = 3, - .eccpos = {0, 1, 2}, + .eccpos = {13, 14, 15}, .oobfree = { - {.offset = 8, - . length = 8} } + {.offset = 0, + . length = 12} } }; static struct nand_ecclayout nand_oob_64 = { @@ -1006,6 +1007,7 @@ int board_nand_init(struct nand_chip *nand_chip) u8 set_feature[4] = {0x08, 0x00, 0x00, 0x00}; unsigned long ecc_cfg; int ondie_ecc_enabled = 0; + int ez_nand_supported = 0; #ifdef LINUX_ONLY_NOT_UBOOT xnand = kzalloc(sizeof(struct xnandpss_info), GFP_KERNEL); @@ -1143,9 +1145,13 @@ int board_nand_init(struct nand_chip *nand_chip) if (get_feature[0] & 0x08) ondie_ecc_enabled = 1; + } else if ((nand_chip->onfi_version == 23) && + (nand_chip->onfi_params.features & (1 << 9))) { + printk(KERN_INFO "\nClear NAND flash detected\n"); + ez_nand_supported = 1; } - if (ondie_ecc_enabled) { + if (ondie_ecc_enabled || ez_nand_supported) { /* bypass the controller ECC block */ ecc_cfg = xnandpss_read32(xnand->smc_regs + XSMCPSS_ECC_MEMCFG_OFFSET(XSMCPSS_ECC_IF1_OFFSET)); @@ -1166,11 +1172,13 @@ int board_nand_init(struct nand_chip *nand_chip) nand_chip->ecc.size = mtd->writesize; nand_chip->ecc.bytes = 0; - nand_chip->ecc.layout = &ondie_nand_oob_64; - - /* Use the BBT pattern descriptors */ - nand_chip->bbt_td = &bbt_main_descr; - nand_chip->bbt_md = &bbt_mirror_descr; + /* On-Die ECC spare bytes offset 8 is used for ECC codes */ + if (ondie_ecc_enabled) { + nand_chip->ecc.layout = &ondie_nand_oob_64; + /* Use the BBT pattern descriptors */ + nand_chip->bbt_td = &bbt_main_descr; + nand_chip->bbt_md = &bbt_mirror_descr; + } } else { /* Hardware ECC generates 3 bytes ECC code for each 512 bytes */ nand_chip->ecc.mode = NAND_ECC_HW; diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 96bbbcafcfa..b1687b3ab12 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -2602,6 +2602,104 @@ static void nand_set_defaults(struct nand_chip *chip, int busw) } +/* + * sanitize ONFI strings so we can safely print them + */ +static void sanitize_string(uint8_t *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; + + 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)) { + break; + } + } + + if (i == 3) { + return 0; + } + + /* check version */ + val = le16_to_cpu(p->revision); + if (val == 1 || val > (1 << 5)) { + printk(KERN_INFO "%s: unsupported ONFI version: %d\n", + __func__, val); + return 0; + } + + 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 + chip->onfi_version = 10; + + 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 = 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_NO_AUTOINCR) & NAND_CHIPOPTIONS_MSK; + + return 1; +} + /* * Get the flash and manufacturer id and lookup if the type is supported */ @@ -2612,6 +2710,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); @@ -2649,22 +2748,24 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd, return ERR_PTR(-ENODEV); } + type = nand_flash_ids; /* Lookup the flash id */ - for (i = 0; nand_flash_ids[i].name != NULL; i++) { - if (dev_id == nand_flash_ids[i].id) { - type = &nand_flash_ids[i]; + for (; type->name != NULL; type++) + if (dev_id == type->id) break; - } - } - if (!type) { - /* supress warning if there is no nand */ - if (*maf_id != 0x00 && *maf_id != 0xff && - dev_id != 0x00 && dev_id != 0xff) + chip->onfi_version = 0; + if(!type->name && !type->pagesize) { + /* Check is chip is ONFI compliant */ + ret = nand_flash_detect_onfi(mtd, chip, busw); + if (ret) + goto ident_done; + else { printk(KERN_INFO "%s: unknown NAND device: " "Manufacturer ID: 0x%02x, Chip ID: 0x%02x\n", __func__, *maf_id, dev_id); - return ERR_PTR(-ENODEV); + return ERR_PTR(-ENODEV); + } } if (!mtd->name) @@ -2701,6 +2802,17 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd, busw = type->options & NAND_BUSWIDTH_16; } + /* 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: /* 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) @@ -2737,21 +2849,11 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd, 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; - /* Check for AND chips with 4 page planes */ if (chip->options & NAND_4PAGE_ARRAY) chip->erase_cmd = multi_erase_cmd; diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 94ad0c0e36a..0295f87de88 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -85,6 +85,7 @@ extern void nand_wait_ready(struct mtd_info *mtd); #define NAND_CMD_RNDIN 0x85 #define NAND_CMD_READID 0x90 #define NAND_CMD_ERASE2 0xd0 +#define NAND_CMD_PARAM 0xec #define NAND_CMD_RESET 0xff /* Extended commands for large page devices */ @@ -214,6 +215,70 @@ typedef enum { /* Keep gcc happy */ struct nand_chip; +struct nand_onfi_params { + /* rev info and features block */ + /* 'O' 'N' 'F' 'I' */ + u8 sig[4]; + __le16 revision; + __le16 features; + __le16 opt_cmd; + u8 reserved[22]; + + /* manufacturer information block */ + char manufacturer[12]; + char model[20]; + u8 jedec_id; + __le16 date_code; + u8 reserved2[13]; + + /* memory organization block */ + __le32 byte_per_page; + __le16 spare_bytes_per_page; + __le32 data_bytes_per_ppage; + __le16 spare_bytes_per_ppage; + __le32 pages_per_block; + __le32 blocks_per_lun; + u8 lun_count; + u8 addr_cycles; + u8 bits_per_cell; + __le16 bb_per_lun; + __le16 block_endurance; + u8 guaranteed_good_blocks; + __le16 guaranteed_block_endurance; + u8 programs_per_page; + u8 ppage_attr; + u8 ecc_bits; + u8 interleaved_bits; + u8 interleaved_ops; + u8 reserved3[13]; + + /* electrical parameter block */ + u8 io_pin_capacitance_max; + __le16 async_timing_mode; + __le16 program_cache_timing_mode; + __le16 t_prog; + __le16 t_bers; + __le16 t_r; + __le16 t_ccs; + __le16 src_sync_timing_mode; + __le16 src_ssync_features; + __le16 clk_pin_capacitance_typ; + __le16 io_pin_capacitance_typ; + __le16 input_pin_capacitance_typ; + u8 input_pin_capacitance_max; + u8 driver_strenght_support; + __le16 t_int_r; + __le16 t_ald; + u8 reserved4[7]; + + /* vendor */ + u8 reserved5[90]; + + __le16 crc; +} __attribute__((packed)); + +#define ONFI_CRC_BASE 0x4F4E + /** * struct nand_hw_control - Control structure for hardware controller (e.g ECC generator) shared among independent devices * @lock: protection lock @@ -404,6 +469,9 @@ struct nand_chip { uint8_t cellinfo; int badblockpos; + int onfi_version; + struct nand_onfi_params onfi_params; + int state; uint8_t *oob_poi; diff --git a/include/linux/string.h b/include/linux/string.h index 62390399b0b..249f61362a4 100644 --- a/include/linux/string.h +++ b/include/linux/string.h @@ -50,6 +50,7 @@ extern char * strrchr(const char *,int); #ifndef __HAVE_ARCH_STRSTR extern char * strstr(const char *,const char *); #endif +extern char *strim(char *s); #ifndef __HAVE_ARCH_STRLEN extern __kernel_size_t strlen(const char *); #endif diff --git a/lib/string.c b/lib/string.c index b375b8124a9..78df4cc4f80 100644 --- a/lib/string.c +++ b/lib/string.c @@ -214,6 +214,45 @@ char * strrchr(const char * s, int c) } #endif +/** + * skip_spaces - Removes leading whitespace from @str. + * @str: The string to be stripped. + * + * Returns a pointer to the first non-whitespace character in @str. + */ +char *skip_spaces(const char *str) +{ + while (isspace(*str)) + ++str; + return (char *)str; +} + +/** + * strim - Removes leading and trailing whitespace from @s. + * @s: The string to be stripped. + * + * Note that the first trailing whitespace is replaced with a %NUL-terminator + * in the given string @s. Returns a pointer to the first non-whitespace + * character in @s. + */ +char *strim(char *s) +{ + size_t size; + char *end; + + s = skip_spaces(s); + size = strlen(s); + if (!size) + return s; + + end = s + size - 1; + while (end >= s && isspace(*end)) + end--; + *(end + 1) = '\0'; + + return s; +} + #ifndef __HAVE_ARCH_STRLEN /** * strlen - Find the length of a string -- 2.47.3