| /***************************************************************************** |
| * Copyright 2004 - 2009 Broadcom Corporation. All rights reserved. |
| * |
| * Unless you and Broadcom execute a separate written software license |
| * agreement governing use of this software, this software is licensed to you |
| * under the terms of the GNU General Public License version 2, available at |
| * http://www.broadcom.com/licenses/GPLv2.php (the "GPL"). |
| * |
| * Notwithstanding the above, under no circumstances may you combine this |
| * software in any way with any other Broadcom software provided under a |
| * license other than the GPL, without Broadcom's express prior written |
| * consent. |
| *****************************************************************************/ |
| |
| /* ---- Include Files ---------------------------------------------------- */ |
| #include "nand_bcm_umi.h" |
| |
| /* ---- External Variable Declarations ----------------------------------- */ |
| /* ---- External Function Prototypes ------------------------------------- */ |
| /* ---- Public Variables ------------------------------------------------- */ |
| /* ---- Private Constants and Types -------------------------------------- */ |
| |
| /* ---- Private Function Prototypes -------------------------------------- */ |
| static int bcm_umi_bch_read_page_hwecc(struct mtd_info *mtd, |
| struct nand_chip *chip, uint8_t *buf, int oob_required, int page); |
| static void bcm_umi_bch_write_page_hwecc(struct mtd_info *mtd, |
| struct nand_chip *chip, const uint8_t *buf, int oob_required); |
| |
| /* ---- Private Variables ------------------------------------------------ */ |
| |
| /* |
| ** nand_hw_eccoob |
| ** New oob placement block for use with hardware ecc generation. |
| */ |
| static struct nand_ecclayout nand_hw_eccoob_512 = { |
| /* Reserve 5 for BI indicator */ |
| .oobfree = { |
| #if (NAND_ECC_NUM_BYTES > 3) |
| {.offset = 0, .length = 2} |
| #else |
| {.offset = 0, .length = 5}, |
| {.offset = 6, .length = 7} |
| #endif |
| } |
| }; |
| |
| /* |
| ** We treat the OOB for a 2K page as if it were 4 512 byte oobs, |
| ** except the BI is at byte 0. |
| */ |
| static struct nand_ecclayout nand_hw_eccoob_2048 = { |
| /* Reserve 0 as BI indicator */ |
| .oobfree = { |
| #if (NAND_ECC_NUM_BYTES > 10) |
| {.offset = 1, .length = 2}, |
| #elif (NAND_ECC_NUM_BYTES > 7) |
| {.offset = 1, .length = 5}, |
| {.offset = 16, .length = 6}, |
| {.offset = 32, .length = 6}, |
| {.offset = 48, .length = 6} |
| #else |
| {.offset = 1, .length = 8}, |
| {.offset = 16, .length = 9}, |
| {.offset = 32, .length = 9}, |
| {.offset = 48, .length = 9} |
| #endif |
| } |
| }; |
| |
| /* We treat the OOB for a 4K page as if it were 8 512 byte oobs, |
| * except the BI is at byte 0. */ |
| static struct nand_ecclayout nand_hw_eccoob_4096 = { |
| /* Reserve 0 as BI indicator */ |
| .oobfree = { |
| #if (NAND_ECC_NUM_BYTES > 10) |
| {.offset = 1, .length = 2}, |
| {.offset = 16, .length = 3}, |
| {.offset = 32, .length = 3}, |
| {.offset = 48, .length = 3}, |
| {.offset = 64, .length = 3}, |
| {.offset = 80, .length = 3}, |
| {.offset = 96, .length = 3}, |
| {.offset = 112, .length = 3} |
| #else |
| {.offset = 1, .length = 5}, |
| {.offset = 16, .length = 6}, |
| {.offset = 32, .length = 6}, |
| {.offset = 48, .length = 6}, |
| {.offset = 64, .length = 6}, |
| {.offset = 80, .length = 6}, |
| {.offset = 96, .length = 6}, |
| {.offset = 112, .length = 6} |
| #endif |
| } |
| }; |
| |
| /* ---- Private Functions ------------------------------------------------ */ |
| /* ==== Public Functions ================================================= */ |
| |
| /**************************************************************************** |
| * |
| * bcm_umi_bch_read_page_hwecc - hardware ecc based page read function |
| * @mtd: mtd info structure |
| * @chip: nand chip info structure |
| * @buf: buffer to store read data |
| * @oob_required: caller expects OOB data read to chip->oob_poi |
| * |
| ***************************************************************************/ |
| static int bcm_umi_bch_read_page_hwecc(struct mtd_info *mtd, |
| struct nand_chip *chip, uint8_t * buf, |
| int oob_required, int page) |
| { |
| int sectorIdx = 0; |
| int eccsize = chip->ecc.size; |
| int eccsteps = chip->ecc.steps; |
| uint8_t *datap = buf; |
| uint8_t eccCalc[NAND_ECC_NUM_BYTES]; |
| int sectorOobSize = mtd->oobsize / eccsteps; |
| int stat; |
| unsigned int max_bitflips = 0; |
| |
| for (sectorIdx = 0; sectorIdx < eccsteps; |
| sectorIdx++, datap += eccsize) { |
| if (sectorIdx > 0) { |
| /* Seek to page location within sector */ |
| chip->cmdfunc(mtd, NAND_CMD_RNDOUT, sectorIdx * eccsize, |
| -1); |
| } |
| |
| /* Enable hardware ECC before reading the buf */ |
| nand_bcm_umi_bch_enable_read_hwecc(); |
| |
| /* Read in data */ |
| bcm_umi_nand_read_buf(mtd, datap, eccsize); |
| |
| /* Pause hardware ECC after reading the buf */ |
| nand_bcm_umi_bch_pause_read_ecc_calc(); |
| |
| /* Read the OOB ECC */ |
| chip->cmdfunc(mtd, NAND_CMD_RNDOUT, |
| mtd->writesize + sectorIdx * sectorOobSize, -1); |
| nand_bcm_umi_bch_read_oobEcc(mtd->writesize, eccCalc, |
| NAND_ECC_NUM_BYTES, |
| chip->oob_poi + |
| sectorIdx * sectorOobSize); |
| |
| /* Correct any ECC detected errors */ |
| stat = |
| nand_bcm_umi_bch_correct_page(datap, eccCalc, |
| NAND_ECC_NUM_BYTES); |
| |
| /* Update Stats */ |
| if (stat < 0) { |
| #if defined(NAND_BCM_UMI_DEBUG) |
| printk(KERN_WARNING "%s uncorr_err sectorIdx=%d\n", |
| __func__, sectorIdx); |
| printk(KERN_WARNING |
| "%s data %02x %02x %02x %02x " |
| "%02x %02x %02x %02x\n", |
| __func__, datap[0], datap[1], datap[2], datap[3], |
| datap[4], datap[5], datap[6], datap[7]); |
| printk(KERN_WARNING |
| "%s ecc %02x %02x %02x %02x " |
| "%02x %02x %02x %02x %02x %02x " |
| "%02x %02x %02x\n", |
| __func__, eccCalc[0], eccCalc[1], eccCalc[2], |
| eccCalc[3], eccCalc[4], eccCalc[5], eccCalc[6], |
| eccCalc[7], eccCalc[8], eccCalc[9], eccCalc[10], |
| eccCalc[11], eccCalc[12]); |
| BUG(); |
| #endif |
| mtd->ecc_stats.failed++; |
| } else { |
| #if defined(NAND_BCM_UMI_DEBUG) |
| if (stat > 0) { |
| printk(KERN_INFO |
| "%s %d correctable_errors detected\n", |
| __func__, stat); |
| } |
| #endif |
| mtd->ecc_stats.corrected += stat; |
| max_bitflips = max_t(unsigned int, max_bitflips, stat); |
| } |
| } |
| return max_bitflips; |
| } |
| |
| /**************************************************************************** |
| * |
| * bcm_umi_bch_write_page_hwecc - hardware ecc based page write function |
| * @mtd: mtd info structure |
| * @chip: nand chip info structure |
| * @buf: data buffer |
| * @oob_required: must write chip->oob_poi to OOB |
| * |
| ***************************************************************************/ |
| static void bcm_umi_bch_write_page_hwecc(struct mtd_info *mtd, |
| struct nand_chip *chip, const uint8_t *buf, int oob_required) |
| { |
| int sectorIdx = 0; |
| int eccsize = chip->ecc.size; |
| int eccsteps = chip->ecc.steps; |
| const uint8_t *datap = buf; |
| uint8_t *oobp = chip->oob_poi; |
| int sectorOobSize = mtd->oobsize / eccsteps; |
| |
| for (sectorIdx = 0; sectorIdx < eccsteps; |
| sectorIdx++, datap += eccsize, oobp += sectorOobSize) { |
| /* Enable hardware ECC before writing the buf */ |
| nand_bcm_umi_bch_enable_write_hwecc(); |
| bcm_umi_nand_write_buf(mtd, datap, eccsize); |
| nand_bcm_umi_bch_write_oobEcc(mtd->writesize, oobp, |
| NAND_ECC_NUM_BYTES); |
| } |
| |
| bcm_umi_nand_write_buf(mtd, chip->oob_poi, mtd->oobsize); |
| } |