| /* |
| * drivers/mtd/nand/comcerto-nand.c |
| * |
| * Copyright (C) Mindspeed Technologies, Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * Overview: |
| * Device driver for the NAND flash device found on the |
| * Comcerto board. |
| * |
| */ |
| #include <common.h> |
| #include <errno.h> |
| #include <malloc.h> |
| #include <init.h> |
| #include <types.h> |
| #include <linux/mtd/mtd.h> |
| #include <linux/mtd/nand.h> |
| #include <asm/hardware.h> |
| #include <asm/io.h> |
| #include <asm/unaligned.h> |
| #include <clock.h> |
| |
| #include <mach/gpio.h> |
| #include <mach/comcerto-2000.h> |
| #include <mach/comcerto-common.h> |
| #include <mach/exp-bus.h> |
| #include <mach/ecc.h> |
| |
| #include "nand.h" |
| |
| #define BBD_ID_0 0xDEADBEEF |
| #define BBD_ID_1 0xECC0ECC0 |
| #define MAX_BLOCKS_WITH_BBT 12 |
| #define MAX_NO_SBL_COPIES 6 |
| #define MAX_INHERITED_BAD_BLK 16 /* for boot sector */ |
| |
| #define BBT_STRUCT_SIZE_EXCLUDING_BLK_MARK (9 * 4 + sizeof(struct boot_sector_layout)) |
| #define ECC_BLK_MARK_SET_0_SIZE (1024 - BBT_STRUCT_SIZE_EXCLUDING_BLK_MARK) |
| |
| #define IRAM_PAYLOAD_SEGMENT_SIZE 0xC000 |
| |
| |
| #if defined (CONFIG_NAND_COMCERTO_ECC_24_HW_BCH) |
| /* |
| * spare area layout for BCH ECC bytes calculated over 512-Bytes ECC block size |
| */ |
| static struct nand_ecclayout comcerto_ecc_info_512_bch = { |
| .eccbytes = 42, |
| .eccpos = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, |
| 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, |
| 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, |
| 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41}, |
| .oobfree = { |
| {.offset = 43, .length = 13} |
| } |
| }; |
| |
| /* |
| * spare area layout for BCH ECC bytes calculated over 1024-Bytes ECC block size |
| */ |
| static struct nand_ecclayout comcerto_ecc_info_1024_bch = { |
| .eccbytes = 42, |
| .eccpos = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, |
| 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, |
| 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, |
| 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41}, |
| .oobfree = { |
| {.offset = 43, .length = 13} |
| } |
| }; |
| |
| #elif defined (CONFIG_NAND_COMCERTO_ECC_8_HW_BCH) |
| |
| /* |
| * spare area layout for BCH ECC bytes calculated over 512-Bytes ECC block size |
| */ |
| static struct nand_ecclayout comcerto_ecc_info_512_bch = { |
| .eccbytes = 14, |
| .eccpos = {0, 1, 2, 3, 4, 5, 6, |
| 7, 8, 9, 10, 11, 12, 13}, |
| .oobfree = { |
| {.offset = 14, .length = 2} |
| } |
| }; |
| |
| /* |
| * spare area layout for BCH ECC bytes calculated over 1024-Bytes ECC block size |
| */ |
| static struct nand_ecclayout comcerto_ecc_info_1024_bch = { |
| .eccbytes = 14, |
| .eccpos = {0, 1, 2, 3, 4, 5, 6, |
| 7, 8, 9, 10, 11, 12, 13}, |
| .oobfree = { |
| {.offset = 14, .length = 18} |
| } |
| }; |
| #else /* Hamming */ |
| /* |
| * spare area layout for Hamming ECC bytes calculated over 512-Bytes ECC block |
| * size |
| */ |
| static struct nand_ecclayout comcerto_ecc_info_512_hamm = { |
| .eccbytes = 4, |
| .eccpos = {0, 1, 2, 3}, |
| .oobfree = { |
| {.offset = 4, .length = 12} |
| } |
| }; |
| |
| /* |
| * spare area layout for Hamming ECC bytes calculated over 1024-Bytes ECC block |
| * size |
| */ |
| static struct nand_ecclayout comcerto_ecc_info_1024_hamm = { |
| .eccbytes = 4, |
| .eccpos = {0, 1, 2, 3}, |
| .oobfree = { |
| {.offset = 4, .length = 28} |
| } |
| }; |
| #endif |
| |
| static uint8_t bbt_pattern[] = { 'B', 'b', 't', '0' }; |
| static uint8_t mirror_pattern[] = { '1', 't', 'b', 'B' }; |
| |
| static struct nand_bbt_descr bbt_main_descr = { |
| .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE |
| | NAND_BBT_8BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, |
| .offs = 44, |
| .len = 4, |
| .veroffs = 48, |
| .maxblocks = 8, |
| .pattern = bbt_pattern, |
| }; |
| |
| static struct nand_bbt_descr bbt_mirror_descr = { |
| .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE |
| | NAND_BBT_8BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, |
| .offs = 44, |
| .len = 4, |
| .veroffs = 48, |
| .maxblocks = 8, |
| .pattern = mirror_pattern, |
| }; |
| |
| static uint8_t scan_ff_pattern[] = {0xff}; |
| |
| #if defined (CONFIG_NAND_COMCERTO_ECC_24_HW_BCH) |
| static struct nand_bbt_descr c2000_badblock_pattern = { |
| .offs = 42, |
| .len = 1, |
| .pattern = scan_ff_pattern |
| }; |
| #elif defined (CONFIG_NAND_COMCERTO_ECC_8_HW_BCH) |
| static struct nand_bbt_descr c2000_badblock_pattern = { |
| .offs = 14, |
| .len = 1, |
| .pattern = scan_ff_pattern |
| }; |
| #else /* Hamming */ |
| static struct nand_bbt_descr c2000_badblock_pattern = { |
| .offs = 4, |
| .len = 1, |
| .pattern = scan_ff_pattern |
| }; |
| #endif |
| |
| |
| struct boot_sector_layout { |
| uint32_t nand_block_size; /* get from nand_init */ |
| uint32_t ecc_block_size; /* default : 512 */ |
| uint32_t oob_size; /* default : 16 */ |
| uint32_t pri_boot_block_start_0; /* default : 0 */ |
| uint32_t boot_block_size; /* Multiple of ECC Block size (512) */ |
| uint32_t no_of_copies; /* default : 6 */ |
| uint32_t bbt_start_0; /* default : 2 */ |
| uint32_t bbt_start_1; /* default : 3 */ |
| uint32_t complete_bbt_size; /* should not exceed size of NAND block */ |
| uint32_t sec_boot_block_start; /* default : 4 */ |
| uint32_t sec_boot_sector_size; /* Multiple of NAND Block size */ |
| }; |
| |
| typedef struct boot_sector_layout boot_sector_layout_t; |
| |
| /* |
| * Comcerto NAND flash bbt decriptors |
| */ |
| struct bbt_descriptor { |
| uint32_t bbt_id_0; /* unique identifier to identify BBT 0xDEADBEEF */ |
| uint32_t bbt_id_1; /* unique identifier to identify BBT 0xECC0ECC0 */ |
| uint32_t options; /* implementation dependent */ |
| uint32_t bbt_max_blocks; /* maximum no of blocks to search for BBT */ |
| uint32_t bbt_max_resv_blocks; /* maximum no of reserved blocks to search for BBT */ |
| uint32_t bbt_trail_location; /* */ |
| uint8_t bbt_set_id; |
| uint32_t pri_boot_block_location; /* */ |
| uint8_t inherited_bb_flag; /* Will be set when inherited bad block is found */ |
| }; |
| |
| typedef struct bbt_descriptor bbt_descriptor_t; |
| typedef struct bad_block_table bad_block_table_t; |
| |
| bad_block_table_t bbt_temp; |
| bbt_descriptor_t bbt_desc; |
| boot_sector_layout_t boot_sector; |
| |
| struct bad_block_table { |
| uint32_t bbt_id_0; /* unique identifier to identify BBT 0xDEADBEEF */ |
| uint32_t bbt_id_1; /* unique identifier to identify BBT 0xECC0ECC0 */ |
| uint8_t inherited_blk[MAX_INHERITED_BAD_BLK]; /* Stors upto 8 locations of Inherited Bad Blocks */ |
| uint8_t boot_blk_split[8]; |
| boot_sector_layout_t layout; /* current flash layout structure */ |
| uint32_t resrved_bbt_sec_start; /* start location of reserved BBT sector */ |
| uint8_t ecc_block_mark[ECC_BLK_MARK_SET_0_SIZE];/* size of complete BBT should be |
| equal to the NAND ECC block size */ |
| }; |
| |
| /* |
| * MTD structure for Comcerto board |
| */ |
| struct comcerto_nand_info { |
| struct mtd_info *mtd; |
| }; |
| |
| #ifndef CONFIG_COMCERTO_NAND_ULOADER |
| #ifdef CONFIG_COMCERTO_NAND_BBT /* Default: Turned OFF */ |
| /* code under this flag is intended to be used only when |
| IBR NAND Boot is functional. */ |
| |
| /*Scan the nand flash to find the stored BBT and copy it into RAM. |
| * |
| */ |
| int comcerto_scan_bbt(struct mtd_info *mtd) |
| { |
| struct nand_chip *nand_device = (struct nand_chip *)(mtd->priv); |
| uint8_t bbt_blks_count; |
| uint32_t bbt_location; |
| uint32_t spare_area_size = 0; |
| uint32_t num_ecc_blks = 0; |
| size_t retlen; |
| uint32_t bbt_start, bbt_start_1; |
| |
| |
| bbt_start = IRAM_PAYLOAD_SEGMENT_SIZE * MAX_NO_SBL_COPIES; |
| |
| if (bbt_start <= mtd->erasesize) { |
| bbt_location = 1; |
| } else { |
| bbt_start_1 = bbt_start / mtd->erasesize; |
| if ((bbt_start_1 * mtd->erasesize) < bbt_start) |
| bbt_start_1++; |
| bbt_location = bbt_start_1; |
| } |
| |
| bbt_desc.bbt_id_0 = BBD_ID_0; |
| bbt_desc.bbt_id_1 = BBD_ID_1; |
| bbt_desc.bbt_max_blocks = MAX_BLOCKS_WITH_BBT; |
| |
| bbt_desc.bbt_set_id = 0; |
| |
| if (nand_device->ecc.size < 512) |
| spare_area_size = 32; |
| |
| num_ecc_blks = mtd->size / nand_device->ecc.size; |
| |
| if (num_ecc_blks > mtd->erasesize) { |
| printk(KERN_ERR "comcerto_scan_bbt: Number of ECC blocks exceeds \ |
| single erase block size \n"); |
| return 0; |
| } |
| |
| /* Allocate memory (1 byte per block) and clear the memory bad block table */ |
| nand_device->bbt = kzalloc(num_ecc_blks, GFP_KERNEL); |
| if (!nand_device->bbt) { |
| printk(KERN_ERR "comcerto_scan_bbt: Out of memory\n"); |
| return -ENOMEM; |
| } |
| |
| for (bbt_blks_count = 0; bbt_blks_count < bbt_desc.bbt_max_blocks; bbt_blks_count++) { |
| if (mtd->read(mtd, bbt_location * mtd->erasesize, nand_device->ecc.size, &retlen, (uint8_t *)&bbt_temp) > 0) { |
| if ((bbt_temp.bbt_id_0 == bbt_desc.bbt_id_0) && (bbt_temp.bbt_id_1 == bbt_desc.bbt_id_1)) { |
| /* Got good BBT, now download remaining */ |
| memcpy(nand_device->bbt, (&bbt_temp + BBT_STRUCT_SIZE_EXCLUDING_BLK_MARK), ECC_BLK_MARK_SET_0_SIZE); |
| if (mtd->read(mtd, bbt_location * mtd->erasesize + nand_device->ecc.size + spare_area_size, \ |
| num_ecc_blks - ECC_BLK_MARK_SET_0_SIZE, &retlen, (uint8_t *) (nand_device->bbt + ECC_BLK_MARK_SET_0_SIZE)) < 0) |
| continue; |
| return 0; |
| }else { |
| /* Got a good block, but BBT IDs don't match.Assuming |
| this to be a microloader and next location BBT */ |
| boot_sector.bbt_start_0 = ++bbt_location; |
| continue; |
| } |
| } else { |
| /* Got bad block, jump to next block */ |
| bbt_location++; |
| } |
| } |
| |
| printk(KERN_WARNING "Bad block table not found\n"); |
| return 0; |
| } |
| |
| /*comcerto_block_isbad |
| * |
| */ |
| static int comcerto_block_isbad(struct mtd_info *mtd, loff_t offs) |
| { |
| struct nand_chip *nand_device = (struct nand_chip *)(mtd->priv); |
| uint32_t block; |
| |
| |
| /* Check for invalid offset */ |
| if (offs > mtd->size) |
| return -EINVAL; |
| |
| block = (uint32_t) offs / mtd->erasesize; |
| if (nand_device->bbt[block] != 0xFF) |
| return 1; |
| return 0; |
| } |
| |
| #endif /* CONFIG_COMCERTO_NAND_BBT */ |
| #endif /* CONFIG_COMCERTO_NAND_ULOADER */ |
| /** Disable/Enable shifting of data to parity module |
| * |
| * @param[in] en_dis_shift Enable or disable shift to parity module. |
| * |
| */ |
| static void comcerto_ecc_shift(uint8_t en_dis_shift) |
| { |
| writel(en_dis_shift, ECC_SHIFT_EN_CFG); |
| } |
| |
| /** Initializes h/w ECC with proper configuration values. |
| * |
| * @param[in] mtd MTD device structure |
| * @param[in] mode Select between BCH and Hamming |
| * |
| */ |
| static void comcerto_enable_hw_ecc(struct mtd_info *mtd, int mode) |
| { |
| struct nand_chip *nand_device = (struct nand_chip *)(mtd->priv); |
| uint32_t ecc_gen_cfg_val = 0; |
| |
| |
| /* CS4 will have the option for ECC calculation */ |
| writel(ECC_CS4_SEL, ECC_CS_SEL_CFG); |
| |
| /* parity calculation for write, syndrome calculation for read.*/ |
| (mode == NAND_ECC_WRITE) ? (ecc_gen_cfg_val |= PRTY_CALC) : (ecc_gen_cfg_val &= SYNDROME_CALC); |
| |
| #if defined (CONFIG_NAND_COMCERTO_ECC_8_HW_BCH) || defined (CONFIG_NAND_COMCERTO_ECC_24_HW_BCH) |
| ecc_gen_cfg_val &= BCH_MODE; |
| ecc_gen_cfg_val = (ecc_gen_cfg_val & ~(ECC_LVL_MASK)) | (ECC_LVL_VAL << ECC_LVL_SHIFT); |
| #else |
| ecc_gen_cfg_val |= HAMM_MODE; |
| #endif |
| |
| ecc_gen_cfg_val = (ecc_gen_cfg_val & ~(BLK_SIZE_MASK)) | nand_device->ecc.size; ; |
| |
| writel(ecc_gen_cfg_val, ECC_GEN_CFG); |
| /* Reset parity module and latch configured values */ |
| writel(ECC_INIT, ECC_INIT_CFG); |
| comcerto_ecc_shift(ECC_SHIFT_ENABLE); |
| return; |
| } |
| |
| /** writes ECC bytes generated by the parity module into the flash |
| * |
| * @param[in] mtd MTD device structure |
| * @param[in] dat raw data |
| * @param[in] ecc_code buffer for ECC |
| * |
| */ |
| static int comcerto_calculate_ecc(struct mtd_info *mtd, |
| const uint8_t *dat, |
| uint8_t *ecc_code) |
| { |
| struct nand_chip *nand_device = mtd->priv; |
| uint32_t ecc_bytes = nand_device->ecc.bytes; |
| uint8_t dummy_var = 0xFF; |
| |
| while (!(readl(ECC_IDLE_STAT))) |
| ; |
| |
| writel(ECC_PARITY_OUT_EN, ECC_PRTY_OUT_SEL_CFG); |
| |
| /* Even though we do a dummy write to NAND flash, actual ECC bytes are |
| * written to the ECC location in the flash. */ |
| for ( ; ecc_bytes; ecc_bytes--) |
| writeb(dummy_var, nand_device->IO_ADDR_W); |
| |
| comcerto_ecc_shift(ECC_SHIFT_DISABLE); |
| writel(ECC_PARITY_OUT_DISABLE, ECC_PRTY_OUT_SEL_CFG); |
| |
| return 0; |
| } |
| |
| /** Checks ECC registers for errors and will correct them, if correctable |
| * |
| * @param[in] mtd MTD device structure |
| * @param[in] dat raw data |
| * @param[in] read_ecc ECC read out from flash |
| * @param[in] calc_ecc ECC calculated over the raw data |
| * |
| */ |
| static int comcerto_correct_ecc(struct mtd_info *mtd, uint8_t *dat, |
| uint8_t *read_ecc, uint8_t *calc_ecc) |
| { |
| #if !defined (CONFIG_NAND_COMCERTO_ECC_8_HW_BCH) && !defined (CONFIG_NAND_COMCERTO_ECC_24_HW_BCH) |
| struct nand_chip *nand_device = mtd->priv; |
| #else |
| uint8_t err_count = 0; |
| uint32_t err_corr_data_prev; |
| #endif |
| uint32_t err_corr_data; |
| uint16_t mask, index; |
| uint64_t start = get_time_ns(); |
| |
| /* Wait for syndrome calculation to complete */ |
| do { |
| if ((readl(ECC_IDLE_STAT)) & ECC_IDLE) |
| break; |
| |
| } while (!is_timeout(start, SECOND * 2)); |
| |
| /* If no correction is required */ |
| if (likely(!(readl(ECC_POLY_STAT) & ECC_CORR_REQ))) { |
| return 0; |
| } |
| |
| /* Error found! Correction required */ |
| #if defined (CONFIG_NAND_COMCERTO_ECC_8_HW_BCH) || defined (CONFIG_NAND_COMCERTO_ECC_24_HW_BCH) |
| dev_dbg(mtd->dev, "ECC BCH Errors found!\n"); |
| |
| /* Initiate correction operation */ |
| writel(ECC_POLY_START, ECC_POLY_START_CFG); |
| |
| err_corr_data_prev = 0; |
| |
| /* Read Correction data status register till header is 0x7FD */ |
| do { |
| err_corr_data_prev = readl(ECC_CORR_DATA_STAT); |
| if ((err_corr_data_prev >> ECC_BCH_INDEX_SHIFT) == 0x87FD) |
| break; |
| // This delay seems to be necessary. Without it, error |
| // correction fails sometimes when running out of IRAM, which |
| // I guess is faster than normal RAM. |
| udelay(15); |
| // udelay(1); |
| dev_dbg(mtd->dev, "Polling ECC_CORR_DATA_STAT!!!! \n"); |
| } while (!is_timeout(start, SECOND * 2)); |
| |
| /* start reading error locations */ |
| err_corr_data = 0x0; |
| |
| while (((err_corr_data >> 16) != 0x87FE)) { |
| uint16_t *p; |
| err_corr_data = readl(ECC_CORR_DATA_STAT); |
| if ((err_corr_data >> 16) == 0x87FE) |
| break; |
| if (err_corr_data == err_corr_data_prev) |
| continue; |
| err_corr_data_prev = err_corr_data; |
| index = (err_corr_data >> 16) & 0x7FF; |
| mask = err_corr_data & 0xFFFF; |
| p = (uint16_t *) (dat + (index * 2)); |
| put_unaligned(get_unaligned(p) ^ mask, p); |
| while (mask) { |
| if (mask & 1) |
| err_count++; |
| mask >>= 1; |
| } |
| } |
| |
| if (!((readl(ECC_CORR_DONE_STAT)) & ECC_DONE)) { |
| dev_dbg(mtd->dev, "ECC Correction Not done!!!! \n"); |
| dev_dbg(mtd->dev, "No. of errors %d \n", err_count); |
| return -1; |
| } |
| |
| if ((readl(ECC_CORR_STAT)) & ECC_UNCORR) { |
| dev_dbg(mtd->dev, "ECC Uncorrectable Errors found!!!! \n"); |
| return -EIO; |
| } |
| |
| return 0; |
| |
| #else /* Hamming Mode */ |
| if (readl(ECC_POLY_STAT) == ECC_UNCORR_ERR_HAMM) { |
| dev_dbg(mtd->dev, "ECC Uncorrectable Errors found!!!! \n"); |
| /* 2 or more errors detected and hence cannot |
| be corrected */ |
| return -1; /* uncorrectable */ |
| } else { /* 1-bit correctable error */ |
| dev_dbg(mtd->dev, "ECC Correctable Errors found!!!! \n"); |
| err_corr_data = readl(ECC_CORR_DATA_STAT); |
| index = (err_corr_data >> 16) & 0x1FF; |
| |
| if (nand_device->options & NAND_BUSWIDTH_16) { |
| mask = 1 << (err_corr_data & 0xF); |
| *((uint16_t *)(dat + index)) ^= mask; |
| } else { |
| mask = 1 << (err_corr_data & 0x7); |
| dev_dbg(mtd->dev, "index = x%x , mask = x%x \n", index, mask); |
| *(dat + index) ^= mask; |
| } |
| |
| return 1; |
| |
| } |
| #endif |
| return 0; |
| } |
| |
| /** writes single page to the NAND device along with the ECC bytes |
| * |
| * @param[in] mtd MTD device structure |
| * @param[in] chip nand chip info structure |
| * @param[in] buf data buffer |
| * |
| */ |
| static void comcerto_nand_write_page_hwecc(struct mtd_info *mtd, |
| struct nand_chip *chip, |
| const uint8_t *buf) |
| { |
| int i, eccsize = chip->ecc.size; |
| int eccbytes = chip->ecc.bytes; |
| int eccsteps = chip->ecc.steps; |
| const uint8_t *p = buf; |
| uint8_t *oob = chip->oob_poi; |
| |
| /* CS4 will have the option for ECC calculation */ |
| writel(ECC_CS4_SEL, ECC_CS_SEL_CFG); |
| |
| for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { |
| |
| chip->ecc.hwctl(mtd, NAND_ECC_WRITE); |
| chip->write_buf(mtd, p, eccsize); |
| |
| chip->ecc.calculate(mtd, p, oob); |
| oob += eccbytes; |
| |
| if (chip->ecc.postpad) { |
| chip->write_buf(mtd, oob, chip->ecc.postpad); |
| oob += chip->ecc.postpad; |
| } |
| } |
| |
| /* Calculate remaining oob bytes */ |
| i = mtd->oobsize - (oob - chip->oob_poi); |
| if (i) |
| chip->write_buf(mtd, oob, i); |
| } |
| |
| /** reads single page from the NAND device and will read ECC bytes from flash. A |
| * function call to comcerto_correct_ecc() will be used to validate the data. |
| * |
| * @param[in] mtd MTD device structure |
| * @param[in] chip nand chip info structure |
| * @param[in] buf data buffer |
| * |
| */ |
| static int comcerto_nand_read_page_hwecc(struct mtd_info *mtd, |
| struct nand_chip *chip, uint8_t *buf) |
| { |
| struct nand_chip *nand_device = mtd->priv; |
| int i, eccsize = nand_device->ecc.size; |
| int eccbytes = nand_device->ecc.bytes; |
| int eccsteps = nand_device->ecc.steps; |
| uint8_t *p = buf; |
| uint8_t *ecc_code = nand_device->buffers->ecccode; |
| uint8_t ecc_bytes = nand_device->ecc.bytes; |
| uint8_t stat; |
| uint8_t *oob = nand_device->oob_poi; |
| |
| for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { |
| chip->ecc.hwctl(mtd, NAND_ECC_READ); |
| chip->read_buf(mtd, p, eccsize); |
| chip->read_buf(mtd, ecc_code, ecc_bytes); |
| |
| stat = chip->ecc.correct(mtd, p, oob, NULL); |
| if (stat < 0) |
| mtd->ecc_stats.failed++; |
| else |
| mtd->ecc_stats.corrected += stat; |
| |
| comcerto_ecc_shift(ECC_SHIFT_DISABLE); |
| |
| if (chip->ecc.postpad) { |
| chip->read_buf(mtd, oob, chip->ecc.postpad); |
| oob += chip->ecc.postpad; |
| } |
| } |
| /* Calculate remaining oob bytes */ |
| i = mtd->oobsize - (oob - chip->oob_poi); |
| if (i) |
| chip->read_buf(mtd, oob, i); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_COMCERTO_NAND_ERASE_FBB |
| /* Erase False Bad Blocks created due to the difference in ECC layouts */ |
| int erase_old_nand_fmt(struct mtd_info *mtd, u8 bb_old_layout) |
| { |
| |
| struct nand_chip *nand_device = mtd->priv; |
| |
| printf("erase_old_nand_fmt: Erasing old nand format\n"); |
| |
| if (bb_old_layout == 0) { |
| |
| static struct nand_bbt_descr c2000_badblock_pattern_old = { |
| .offs = 14, |
| .len = 1, |
| .pattern = scan_ff_pattern |
| }; |
| |
| nand_device->badblock_pattern = &c2000_badblock_pattern_old; |
| } else { |
| |
| memset(&nand_device->ecc, 0, sizeof(struct nand_ecc_ctrl)); |
| nand_device->ecc.mode = NAND_ECC_SOFT_BCH; |
| |
| /* Scan to find existence of the device */ |
| if (nand_scan_ident(mtd, 1)) { |
| printf("nand_scan_ident failed \n"); |
| return -1; |
| } |
| } |
| |
| /* second phase scan */ |
| if (nand_scan_tail(mtd)) { |
| printf("nand_scan_tail failed \n"); |
| } |
| return 0; |
| } |
| #endif /* CONFIG_COMCERTO_NAND_ERASE_FBB */ |
| |
| |
| /** Hardware specific access to NAND control-lines |
| * |
| * @param[in] mtd MTD device structure |
| * @param[in] cmd NAND command to the controller |
| * @param[in] ctrl control address/command latch |
| * |
| */ |
| static void comcerto_nand_hwcontrol(struct mtd_info *mtd, int cmd, |
| unsigned int ctrl) |
| { |
| struct nand_chip *chip = mtd->priv; |
| |
| |
| if (ctrl & NAND_CTRL_CHANGE) { |
| if (ctrl & NAND_NCE) |
| comcerto_gpio_set_0(COMCERTO_NAND_CE); |
| else |
| comcerto_gpio_set_1(COMCERTO_NAND_CE); |
| } |
| |
| if (cmd == NAND_CMD_NONE) |
| return; |
| |
| if (ctrl & NAND_CLE) |
| writeb(cmd, chip->IO_ADDR_W + COMCERTO_NAND_CLE); |
| else if (ctrl & NAND_ALE) |
| writeb(cmd, chip->IO_ADDR_W + COMCERTO_NAND_ALE); |
| else |
| return; |
| } |
| |
| /** Checks whether the NAND flash is ready or not |
| * |
| * @param[in] mtd MTD device structure |
| * |
| */ |
| static int comcerto_nand_ready(struct mtd_info *mtd) |
| { |
| return comcerto_gpio_read(COMCERTO_NAND_RDY) ? 1 : 0; |
| } |
| |
| /** |
| * nand_read_page_raw_syndrome - [INTERN] read raw page data without ecc |
| * @mtd: mtd info structure |
| * @chip: nand chip info structure |
| * @buf: buffer to store read data |
| * @page: page number to read |
| * |
| * We need a special oob layout and handling even when OOB isn't used. |
| */ |
| static int nand_read_page_raw_syndrome(struct mtd_info *mtd, |
| struct nand_chip *chip, |
| uint8_t *buf) |
| { |
| int eccsize = chip->ecc.size; |
| int eccbytes = chip->ecc.bytes; |
| uint8_t *oob = chip->oob_poi; |
| int steps, size; |
| |
| for (steps = chip->ecc.steps; steps > 0; steps--) { |
| chip->read_buf(mtd, buf, eccsize); |
| buf += eccsize; |
| |
| if (chip->ecc.prepad) { |
| chip->read_buf(mtd, oob, chip->ecc.prepad); |
| oob += chip->ecc.prepad; |
| } |
| |
| chip->read_buf(mtd, oob, eccbytes); |
| oob += eccbytes; |
| |
| if (chip->ecc.postpad) { |
| chip->read_buf(mtd, oob, chip->ecc.postpad); |
| oob += chip->ecc.postpad; |
| } |
| } |
| |
| size = mtd->oobsize - (oob - chip->oob_poi); |
| if (size) |
| chip->read_buf(mtd, oob, size); |
| |
| return 0; |
| } |
| |
| /** Probes for NAND device on comcerto board. |
| * |
| * @param[in,out] pdev describes the NAND device in the system |
| * |
| */ |
| static int comcerto_nand_probe(struct device_d *pdev) |
| { |
| struct comcerto_nand_info *info; |
| struct mtd_info *mtd; |
| struct nand_chip *nand_device; |
| int err = 0; |
| |
| printf("nand_probe: %s base: 0x%08x size: 0x%s\n", pdev->name, pdev->map_base, size_human_readable(pdev->size)); |
| |
| /* Allocate memory for info structure */ |
| info = malloc(sizeof(struct comcerto_nand_info)); |
| if (!info) { |
| err = -ENOMEM; |
| goto out; |
| } |
| memset(info, 0, sizeof(struct comcerto_nand_info)); |
| |
| /* Allocate memory for MTD device structure */ |
| mtd = malloc(sizeof(struct mtd_info)); |
| if (!mtd) { |
| err = -ENOMEM; |
| goto out_info; |
| } |
| memset(mtd, 0, sizeof(struct mtd_info)); |
| |
| /* Link info's mtd data with the initialized MTD structure */ |
| info->mtd = mtd; |
| |
| /* Allocate pointer to nand_device data */ |
| nand_device = malloc(sizeof(struct nand_chip)); |
| if (!nand_device) { |
| err = -ENOMEM; |
| goto out_mtd; |
| } |
| memset(nand_device, 0, sizeof(struct nand_chip)); |
| |
| /* Link the private data with the MTD structure */ |
| mtd->priv = nand_device; |
| |
| nand_device->IO_ADDR_R = (void __iomem *) pdev->map_base; |
| if (nand_device->IO_ADDR_R == NULL) { |
| err = -EIO; |
| goto out_nand; |
| } |
| |
| /* This is the same address to read and write */ |
| nand_device->IO_ADDR_W = nand_device->IO_ADDR_R; |
| |
| |
| /* Set address of hardware control function */ |
| nand_device->cmd_ctrl = comcerto_nand_hwcontrol; |
| nand_device->dev_ready = comcerto_nand_ready; |
| |
| /* 20 us command delay time */ |
| nand_device->chip_delay = 20; |
| nand_device->ecc.mode = NAND_ECC_HW_SYNDROME; |
| |
| #if defined(CONFIG_MACH_COMCERTO_C2K_ASIC) && defined(CONFIG_NAND_TYPE_SLC) |
| nand_device->options = NAND_BUSWIDTH_16; |
| #else |
| nand_device->options = 0; |
| #endif |
| /* Scan to find existence of the device */ |
| if (nand_scan_ident(mtd, 1)) { |
| err = -ENXIO; |
| goto out_nand; |
| } |
| |
| if (nand_device->ecc.mode == NAND_ECC_HW_SYNDROME) { |
| |
| nand_device->ecc.hwctl = comcerto_enable_hw_ecc; |
| nand_device->ecc.write_page = comcerto_nand_write_page_hwecc; |
| nand_device->ecc.read_page = comcerto_nand_read_page_hwecc; |
| nand_device->ecc.calculate = comcerto_calculate_ecc; |
| nand_device->ecc.correct = comcerto_correct_ecc; |
| nand_device->ecc.read_page_raw = nand_read_page_raw_syndrome; |
| |
| switch (mtd->writesize) { |
| case 512: |
| nand_device->ecc.size = mtd->writesize; |
| #if defined (CONFIG_NAND_COMCERTO_ECC_24_HW_BCH) |
| nand_device->ecc.layout = &comcerto_ecc_info_512_bch; |
| nand_device->ecc.bytes = 42; |
| nand_device->ecc.prepad = 0; |
| nand_device->ecc.postpad = 14; |
| #elif defined (CONFIG_NAND_COMCERTO_ECC_8_HW_BCH) |
| nand_device->ecc.layout = &comcerto_ecc_info_512_bch; |
| nand_device->ecc.bytes = 14; |
| nand_device->ecc.prepad = 0; |
| nand_device->ecc.postpad = 2; |
| #else /* Hamming */ |
| nand_device->ecc.layout = &comcerto_ecc_info_512_hamm; |
| nand_device->ecc.bytes = 4; |
| nand_device->ecc.prepad = 0; |
| nand_device->ecc.postpad = 2; |
| #endif |
| break; |
| case 1024: |
| nand_device->ecc.size = mtd->writesize; |
| #if defined (CONFIG_NAND_COMCERTO_ECC_24_HW_BCH) |
| nand_device->ecc.layout = &comcerto_ecc_info_1024_bch; |
| nand_device->ecc.bytes = 42; |
| nand_device->ecc.prepad = 0; |
| nand_device->ecc.postpad = 14; |
| #elif defined (CONFIG_NAND_COMCERTO_ECC_8_HW_BCH) |
| nand_device->ecc.layout = &comcerto_ecc_info_1024_bch; |
| nand_device->ecc.bytes = 14; |
| nand_device->ecc.prepad = 0; |
| nand_device->ecc.postpad = 18; |
| #else /* Hamming */ |
| nand_device->ecc.layout = &comcerto_ecc_info_1024_hamm; |
| nand_device->ecc.bytes = 4; |
| nand_device->ecc.prepad = 0; |
| nand_device->ecc.postpad = 18; |
| #endif |
| break; |
| default: |
| printf("Using default values \n"); |
| nand_device->ecc.size = 1024; |
| #if defined (CONFIG_NAND_COMCERTO_ECC_24_HW_BCH) |
| nand_device->ecc.layout = &comcerto_ecc_info_1024_bch; |
| nand_device->ecc.bytes = 42; |
| nand_device->ecc.prepad = 0; |
| nand_device->ecc.postpad = 14; |
| #elif defined (CONFIG_NAND_COMCERTO_ECC_8_HW_BCH) |
| nand_device->ecc.layout = &comcerto_ecc_info_1024_bch; |
| nand_device->ecc.bytes = 14; |
| nand_device->ecc.prepad = 0; |
| nand_device->ecc.postpad = 18; |
| #else /* Hamming */ |
| nand_device->ecc.layout = &comcerto_ecc_info_1024_hamm; |
| nand_device->ecc.bytes = 4; |
| nand_device->ecc.prepad = 0; |
| nand_device->ecc.postpad = 18; |
| #endif |
| break; |
| } |
| |
| nand_device->ecc.steps = mtd->writesize / nand_device->ecc.size; |
| |
| if(nand_device->ecc.steps * nand_device->ecc.size != mtd->writesize) { |
| printk(KERN_WARNING "Invalid ecc parameters\n"); |
| BUG(); |
| } |
| |
| nand_device->ecc.total = nand_device->ecc.steps * nand_device->ecc.bytes; |
| |
| nand_device->badblock_pattern = &c2000_badblock_pattern; |
| |
| nand_device->bbt_td = &bbt_main_descr; |
| nand_device->bbt_md = &bbt_mirror_descr; |
| /* update flash based bbt */ |
| nand_device->options |= NAND_USE_FLASH_BBT; |
| } |
| |
| #ifdef CONFIG_COMCERTO_NAND_BBT /* Default: Turned OFF */ |
| /* code under this flag is intended to be used only when |
| IBR NAND Boot is functional. */ |
| nand_device->scan_bbt = comcerto_scan_bbt; |
| #endif /* CONFIG_COMCERTO_NAND_BBT */ |
| |
| #ifdef CONFIG_NAND_ECC_HW_SYNDROME |
| nand_init_ecc_hw_syndrome(nand_device); |
| #endif |
| |
| nand_device->options |= NAND_NO_SUBPAGE_WRITE; |
| |
| /* second phase scan */ |
| if (nand_scan_tail(mtd)) { |
| err = -ENXIO; |
| goto out_nand; |
| } |
| |
| #ifdef CONFIG_COMCERTO_NAND_BBT /* Default: Turned OFF */ |
| /* code under this flag is intended to be used only when |
| IBR NAND Boot is functional. */ |
| mtd->block_isbad = comcerto_block_isbad; |
| #endif /* CONFIG_COMCERTO_NAND_BBT */ |
| |
| /*Name of the mtd device */ |
| mtd->name = pdev->name; |
| mtd->dev = pdev; |
| |
| add_mtd_device(mtd); |
| |
| err = 0; |
| |
| goto out; |
| |
| out_nand: |
| kfree(nand_device); |
| out_mtd: |
| kfree(mtd); |
| out_info: |
| kfree(info); |
| out: |
| return err; |
| |
| } |
| |
| /* |
| * Driver Registration |
| */ |
| |
| static struct driver_d comcerto_nand_driver = { |
| .name = "comcerto_nand", |
| .probe = comcerto_nand_probe, |
| }; |
| |
| static int comcerto_nand_drv_init(void) |
| { |
| return register_driver(&comcerto_nand_driver); |
| } |
| |
| device_initcall(comcerto_nand_drv_init); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_DESCRIPTION("Board-specific glue layer for NAND flash on Comcerto \ |
| board"); |