diff options
Diffstat (limited to 'drivers/mtd/nand/gpmi/gpmi-bbt.c')
-rw-r--r-- | drivers/mtd/nand/gpmi/gpmi-bbt.c | 565 |
1 files changed, 565 insertions, 0 deletions
diff --git a/drivers/mtd/nand/gpmi/gpmi-bbt.c b/drivers/mtd/nand/gpmi/gpmi-bbt.c new file mode 100644 index 000000000000..54755f870440 --- /dev/null +++ b/drivers/mtd/nand/gpmi/gpmi-bbt.c @@ -0,0 +1,565 @@ +/* + * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface) + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/dma-mapping.h> +#include <linux/ctype.h> +#include <mach/dma.h> +#include <mach/unique-id.h> +#include "gpmi.h" + +static int boot_search_count; +static int stride = 64; +static int ncb_version = 3; +module_param(boot_search_count, int, 0400); +module_param(ncb_version, int, 0400); + +void *gpmi_read_page(struct mtd_info *mtd, loff_t start, void *data, int raw) +{ + int ret; + struct mtd_oob_ops ops; + + if (!data) + data = kzalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL); + if (!data) + return NULL; + + if (raw) + ops.mode = MTD_OOB_RAW; + else + ops.mode = MTD_OOB_PLACE; + ops.datbuf = data; + ops.len = mtd->writesize; + ops.oobbuf = data + mtd->writesize; + ops.ooblen = mtd->oobsize; + ops.ooboffs = 0; + ret = nand_do_read_ops(mtd, start, &ops); + + if (ret) + return NULL; + return data; +} + +int gpmi_write_ncb(struct mtd_info *mtd, struct gpmi_bcb_info *b) +{ + struct gpmi_ncb *ncb = NULL, *unencoded_ncb = NULL; + struct nand_chip *chip = mtd->priv; + int err; + loff_t start = 0; + struct mtd_oob_ops ops; + struct erase_info instr; + int ncb_count; + + ncb = kzalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL); + if (!ncb) { + err = -ENOMEM; + goto out; + } + unencoded_ncb = kzalloc(mtd->writesize, GFP_KERNEL); + if (!unencoded_ncb) { + err = -ENOMEM; + goto out; + } + ops.mode = -1; /* if the value is not set in switch below, + this will cause BUG. Take care. */ + if (b && b->pre_ncb) + memcpy(unencoded_ncb, b->pre_ncb, b->pre_ncb_size); + else { + memcpy(&unencoded_ncb->fingerprint1, SIG1, sizeof(u32)); + memcpy(&unencoded_ncb->fingerprint2, SIG_NCB, sizeof(u32)); + if (b) + unencoded_ncb->timing = b->timing; + } + + switch (ncb_version) { + case 0: + ops.mode = MTD_OOB_AUTO; + memcpy(ncb, unencoded_ncb, sizeof(*unencoded_ncb)); + break; +#ifdef CONFIG_MTD_NAND_GPMI_TA1 + case 1: + ops.mode = MTD_OOB_RAW; + gpmi_encode_hamming_ncb_22_16(unencoded_ncb, + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES, + ncb, mtd->writesize + mtd->oobsize); + break; +#endif +#ifdef CONFIG_MTD_NAND_GPMI_TA3 + case 3: + ops.mode = MTD_OOB_RAW; + gpmi_encode_hamming_ncb_13_8(unencoded_ncb, + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES, + ncb, mtd->writesize + mtd->oobsize); + break; +#endif + + default: + printk(KERN_ERR"Incorrect ncb_version = %d\n", ncb_version); + err = -EINVAL; + goto out; + } + + ops.datbuf = (u8 *)ncb; + ops.len = mtd->writesize; + ops.oobbuf = (u8 *)ncb + mtd->writesize; + ops.ooblen = mtd->oobsize; + ops.ooboffs = 0; + + ncb_count = 0; + do { + printk(KERN_NOTICE"GPMI: Trying to store NCB at addr %lx\n", + (unsigned long)start); + memset(&instr, 0, sizeof(instr)); + instr.mtd = mtd; + instr.addr = start; + instr.len = (1 << chip->phys_erase_shift); + err = nand_erase_nand(mtd, &instr, 0); + if (err == 0) { + printk(KERN_NOTICE"GPMI: Erased, storing\n"); + err = nand_do_write_ops(mtd, start, &ops); + printk(KERN_NOTICE"GPMI: NCB update %s (%d).\n", + err ? "failed" : "succeeded", err); + } + start += (1 << chip->phys_erase_shift); + ncb_count++; + } while (err != 0 && ncb_count < 100); + + if (b) + b->ncbblock = start >> chip->bbt_erase_shift; + +out: + kfree(ncb); + kfree(unencoded_ncb); + + return 0; +} + +static int gpmi_redundancy_check_one(u8 *pg, int dsize, int esize, int offset, + int o1, int o2) +{ + int r; + + if (o1 == o2) + return 0; + + r = memcmp(pg + o1 * dsize, pg + o2 * dsize, dsize); + if (r) { + pr_debug("DATA copies %d and %d are different: %d\n", + o1, o2, r); + return r; + } + + r = memcmp(pg + o1 * esize + offset, + pg + o2 * esize + offset, esize); + if (r) { + pr_debug("ECC copies %d and %d are different: %d\n", o1, o2, r); + return r; + } + pr_debug("Both DATA and ECC copies %d and %d are identical\n", o1, o2); + return r; +} + +static int gpmi_redundancy_check(u8 *pg, int dsize, int esize, int ecc_offset) +{ + if (gpmi_redundancy_check_one(pg, dsize, esize, ecc_offset, 0, 1) == 0) + return 0; + if (gpmi_redundancy_check_one(pg, dsize, esize, ecc_offset, 0, 2) == 0) + return 0; + if (gpmi_redundancy_check_one(pg, dsize, esize, ecc_offset, 1, 2) == 0) + return 1; + return -1; +} + +static inline int gpmi_ncb1_redundancy_check(u8 *pg) +{ + return gpmi_redundancy_check(pg, + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES, + NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES, + NAND_HC_ECC_OFFSET_FIRST_PARITY_COPY); +} + +static int gpmi_scan_sigmatel_bbt( + struct mtd_info *mtd, struct gpmi_bcb_info *nfo) +{ + int page, r; + u8 *pg; + struct gpmi_ncb *result = NULL; + + if (boot_search_count == 0) + boot_search_count = 1; + if (nfo == NULL) + return -EINVAL; + + pg = NULL; + printk(KERN_NOTICE"Scanning for NCB...\n"); + for (page = 0; page < (1<<boot_search_count); page += stride) { + pg = gpmi_read_page(mtd, page * mtd->writesize, + pg, ncb_version != 0); + + printk(KERN_NOTICE"GPMI: Checking page 0x%08X\n", page); + + if (ncb_version == 0) { + if (memcmp(pg, SIG1, SIG_SIZE) != 0) + continue; + printk(KERN_NOTICE"GPMI: Signature found at 0x%08X\n", + page); + result = (struct gpmi_ncb *)pg; + } + +#ifdef CONFIG_MTD_NAND_GPMI_TA1 + if (ncb_version == 1) { + void *dptr, *eccptr; + + if (memcmp(pg, SIG1, SIG_SIZE) != 0) + continue; + printk(KERN_NOTICE"GPMI: Signature found at 0x%08X\n", + page); + + r = gpmi_ncb1_redundancy_check(pg); + + if (r < 0) { + printk(KERN_ERR"GPMI: Oops. All three " + "copies of NCB are differrent!\n"); + continue; + } + + dptr = pg + r * NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES; + eccptr = pg + NAND_HC_ECC_OFFSET_FIRST_PARITY_COPY + + r * NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES; + + if (gpmi_verify_hamming_22_16(dptr, eccptr, + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES) < 0) { + printk(KERN_ERR"Verification failed.\n"); + continue; + } + result = (struct gpmi_ncb *)pg; + } +#endif + +#ifdef CONFIG_MTD_NAND_GPMI_TA3 + if (ncb_version == 3) { + + if (memcmp(pg + 12, SIG1, SIG_SIZE) != 0) + continue; + + printk(KERN_NOTICE"GPMI: Signature found at 0x%08X\n", + page); + + if (gpmi_verify_hamming_13_8( + pg + 12, + pg + 524, + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES) < 0) { + printk(KERN_ERR"Verification failed.\n"); + continue; + } + result = (struct gpmi_ncb *)(pg + 12); + } +#endif + if (result) { + printk(KERN_NOTICE"GPMI: Valid NCB found " + "at 0x%08x\n", page); + nfo->timing = result->timing; + nfo->ncbblock = page * mtd->writesize; + break; + } + } + kfree(pg); + + return result != NULL; +} + +int gpmi_scan_bbt(struct mtd_info *mtd) +{ + struct gpmi_bcb_info stmp_bbt; + struct nand_chip *this = mtd->priv; + struct gpmi_nand_data *g = this->priv; + int r; + int numblocks, from, i, ign; + + memset(&stmp_bbt, 0, sizeof(stmp_bbt)); + g->transcribe_bbmark = 0; + + /* + Since NCB uses the full page, including BB pattern bits, + driver has to ignore result of gpmi_block_bad when reading + these pages. + */ + ign = g->ignorebad; + + g->ignorebad = true; /* strictly speaking, I'd have to hide + * the BBT too. + * But we still scanning it :) */ + r = gpmi_scan_sigmatel_bbt(mtd, &stmp_bbt); + + /* and then, driver has to restore the setting */ + g->ignorebad = ign; + + if (r) { + printk(KERN_NOTICE"Setting discovered timings: %d:%d:%d:%d\n", + stmp_bbt.timing.data_setup, + stmp_bbt.timing.data_hold, + stmp_bbt.timing.address_setup, + stmp_bbt.timing.dsample_time); + + gpmi_set_timings(g->dev, &stmp_bbt.timing); + g->timing = stmp_bbt.timing; + + } else { + g->transcribe_bbmark = !0; + numblocks = this->chipsize >> this->bbt_erase_shift; + from = 0; + printk(KERN_NOTICE"Checking BB on common-formatted flash\n"); + for (i = stmp_bbt.ncbblock + 1; i < numblocks; i++) { + /* check the block and transcribe the bb if needed */ + gpmi_block_bad(mtd, from, 0); + from += (1 << this->bbt_erase_shift); + } + } + + r = nand_default_bbt(mtd); + + if (g->transcribe_bbmark) { + /* NCB has been not found, so create NCB now */ + g->transcribe_bbmark = 0; + + stmp_bbt.timing = gpmi_safe_timing; + r = gpmi_write_ncb(mtd, &stmp_bbt); + } else { + /* NCB found, and its block should be marked as "good" */ + gpmi_block_mark_as(this, stmp_bbt.ncbblock, 0x00); + } + + return r; +} + +int gpmi_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip) + +{ + int page, res = 0; + struct nand_chip *chip = mtd->priv; + u16 bad; + struct gpmi_nand_data *g = chip->priv; + int chipnr; + + /* badblockpos is an offset in OOB area */ + int badblockpos = chip->ecc.steps * chip->ecc.bytes; + + if (g->ignorebad) + return 0; + + page = (int)(ofs >> chip->page_shift) & chip->pagemask; + + chipnr = (int)(ofs >> chip->chip_shift); + chip->select_chip(mtd, chipnr); + + if (g->transcribe_bbmark) + /* bad block marks still are on first byte of OOB */ + badblockpos = 0; + + if (chip->options & NAND_BUSWIDTH_16) { + chip->cmdfunc(mtd, NAND_CMD_READOOB, badblockpos & 0xFE, + page); + bad = cpu_to_le16(chip->read_word(mtd)); + if (badblockpos & 0x1) + bad >>= 8; + if ((bad & 0xFF) != 0xff) + res = 1; + } else { + chip->cmdfunc(mtd, NAND_CMD_READOOB, badblockpos, page); + if (chip->read_byte(mtd) != 0xff) + res = 1; + } + + if (g->transcribe_bbmark && res) + chip->block_markbad(mtd, ofs); + + chip->select_chip(mtd, -1); + + return res; +} + +#if defined(CONFIG_STMP3XXX_UNIQUE_ID) +/* + * UID on NAND support + */ +const int uid_size = 256; + +struct gpmi_uid_context { + struct mtd_info *mtd; + struct nand_chip *nand; + u_int32_t start; + u_int32_t size; +}; + +static int gpmi_read_uid(struct gpmi_uid_context *ctx, void *result) +{ + void *pg = NULL; + int page, o; + int status = -ENOENT; + int h_size = gpmi_hamming_ecc_size_22_16(uid_size); + + for (page = ctx->start >> ctx->nand->page_shift; + page < (ctx->start + ctx->size) >> ctx->nand->page_shift;) { + pr_debug("%s: reading page 0x%x\n", __func__, page); + if (gpmi_block_bad(ctx->mtd, page * ctx->mtd->writesize, 0)) { + pr_debug("%s: bad block %x, skipping it\n", + __func__, page * ctx->mtd->writesize); + page += (1 << ctx->nand->phys_erase_shift) + >> ctx->nand->page_shift; + continue; + } + pg = gpmi_read_page(ctx->mtd, page * ctx->mtd->writesize, + pg, 0); + if (pg) + break; + page++; + } + + if (!pg) + return status; + + o = gpmi_redundancy_check(pg, uid_size, h_size, 3 * uid_size); + if (o >= 0) { + if (gpmi_verify_hamming_22_16( + pg + o * uid_size, + pg + 3 * uid_size + h_size, uid_size) >= 0) { + memcpy(result, pg + o * uid_size, uid_size); + status = 0; + } + } + kfree(pg); + return status; +} + +static int gpmi_write_uid(struct gpmi_uid_context *ctx, void *src) +{ + struct mtd_oob_ops ops; + struct erase_info instr; + u8 *data = kzalloc(ctx->mtd->writesize + ctx->mtd->oobsize, GFP_KERNEL); + int h_size = gpmi_hamming_ecc_size_22_16(uid_size); + char ecc[h_size]; + u_int32_t start; + int i; + int err; + + if (!data) + return -ENOMEM; + + gpmi_encode_hamming_22_16(src, uid_size, ecc, h_size); + for (i = 0; i < 3; i++) { + memcpy(data + i * uid_size, src, uid_size); + memcpy(data + 3 * uid_size + i * h_size, ecc, h_size); + } + + ops.mode = MTD_OOB_AUTO; + ops.datbuf = data; + ops.len = ctx->mtd->writesize; + ops.oobbuf = NULL; + ops.ooblen = ctx->mtd->oobsize; + ops.ooboffs = 0; + + start = ctx->start; + + do { + memset(&instr, 0, sizeof(instr)); + instr.mtd = ctx->mtd; + instr.addr = start; + instr.len = (1 << ctx->nand->phys_erase_shift); + err = nand_erase_nand(ctx->mtd, &instr, 0); + if (err == 0) + err = nand_do_write_ops(ctx->mtd, start, &ops); + start += (1 << ctx->nand->phys_erase_shift); + if (start > ctx->start + ctx->size) + break; + } while (err != 0); + + return err; +} + +static ssize_t gpmi_uid_store(void *context, const char *page, + size_t count, int ascii) +{ + u8 data[uid_size]; + + memset(data, 0, sizeof(data)); + memcpy(data, page, uid_size < count ? uid_size : count); + gpmi_write_uid(context, data); + return count; +} + +static ssize_t gpmi_uid_show(void *context, char *page, int ascii) +{ + u8 result[uid_size]; + int i; + char *p = page; + int r; + + r = gpmi_read_uid(context, result); + if (r < 0) + return r; + + if (ascii) { + for (i = 0; i < uid_size; i++) { + if (i % 16 == 0) { + if (i) + *p++ = '\n'; + sprintf(p, "%04X: ", i); + p += strlen(p); + } + sprintf(p, "%02X ", result[i]); + p += strlen(p); + } + *p++ = '\n'; + return p - page; + + } else { + memcpy(page, result, uid_size); + return uid_size; + } +} + +static struct uid_ops gpmi_uid_ops = { + .id_show = gpmi_uid_show, + .id_store = gpmi_uid_store, +}; + +static struct gpmi_uid_context gpmi_uid_context; + +int __init gpmi_uid_init(const char *name, struct mtd_info *mtd, + u_int32_t start, u_int32_t size) +{ + gpmi_uid_context.mtd = mtd; + gpmi_uid_context.nand = mtd->priv; + gpmi_uid_context.start = start; + gpmi_uid_context.size = size; + return uid_provider_init(name, &gpmi_uid_ops, &gpmi_uid_context) ? + 0 : -EFAULT; +} + +void gpmi_uid_remove(const char *name) +{ + uid_provider_remove(name); +} +#endif |