diff options
Diffstat (limited to 'drivers/mtd')
| -rw-r--r-- | drivers/mtd/cfi_flash.c | 105 | ||||
| -rw-r--r-- | drivers/mtd/jedec_flash.c | 28 | ||||
| -rw-r--r-- | drivers/mtd/nand/Makefile | 3 | ||||
| -rw-r--r-- | drivers/mtd/nand/bfin_nand.c | 385 | ||||
| -rw-r--r-- | drivers/mtd/nand/fsl_elbc_nand.c | 6 | ||||
| -rw-r--r-- | drivers/mtd/nand/nand.c | 6 | ||||
| -rw-r--r-- | drivers/mtd/nand/nand_base.c | 25 | ||||
| -rw-r--r-- | drivers/mtd/nand/nand_util.c | 91 | ||||
| -rw-r--r-- | drivers/mtd/nand/nomadik.c | 221 | ||||
| -rw-r--r-- | drivers/mtd/nand/omap_gpmc.c | 353 | ||||
| -rw-r--r-- | drivers/mtd/nand_legacy/nand_legacy.c | 2 | ||||
| -rw-r--r-- | drivers/mtd/onenand/onenand_base.c | 640 | ||||
| -rw-r--r-- | drivers/mtd/onenand/onenand_bbt.c | 8 | ||||
| -rw-r--r-- | drivers/mtd/onenand/onenand_uboot.c | 10 | ||||
| -rw-r--r-- | drivers/mtd/spi/atmel.c | 3 | ||||
| -rw-r--r-- | drivers/mtd/spi/stmicro.c | 3 |
16 files changed, 1630 insertions, 259 deletions
diff --git a/drivers/mtd/cfi_flash.c b/drivers/mtd/cfi_flash.c index e8afe9985..a66feac14 100644 --- a/drivers/mtd/cfi_flash.c +++ b/drivers/mtd/cfi_flash.c @@ -273,7 +273,7 @@ u64 flash_read64(void *addr)__attribute__((weak, alias("__flash_read64"))); /*----------------------------------------------------------------------- */ #if defined(CONFIG_ENV_IS_IN_FLASH) || defined(CONFIG_ENV_ADDR_REDUND) || (CONFIG_SYS_MONITOR_BASE >= CONFIG_SYS_FLASH_BASE) -static flash_info_t *flash_get_info(ulong base) +flash_info_t *flash_get_info(ulong base) { int i; flash_info_t * info = 0; @@ -305,17 +305,12 @@ flash_map (flash_info_t * info, flash_sect_t sect, uint offset) { unsigned int byte_offset = offset * info->portwidth; - return map_physmem(info->start[sect] + byte_offset, - flash_sector_size(info, sect) - byte_offset, - MAP_NOCACHE); + return (void *)(info->start[sect] + byte_offset); } static inline void flash_unmap(flash_info_t *info, flash_sect_t sect, unsigned int offset, void *addr) { - unsigned int byte_offset = offset * info->portwidth; - - unmap_physmem(addr, flash_sector_size(info, sect) - byte_offset); } /*----------------------------------------------------------------------- @@ -354,7 +349,7 @@ static void print_longlong (char *str, unsigned long long data) int i; char *cp; - cp = (unsigned char *) &data; + cp = (char *) &data; for (i = 0; i < 8; i++) sprintf (&str[i * 2], "%2.2x", *cp++); } @@ -774,17 +769,26 @@ static void flash_add_byte (flash_info_t * info, cfiword_t * cword, uchar c) } } -/* loop through the sectors from the highest address when the passed - * address is greater or equal to the sector address we have a match +/* + * Loop through the sector table starting from the previously found sector. + * Searches forwards or backwards, dependent on the passed address. */ static flash_sect_t find_sector (flash_info_t * info, ulong addr) { - flash_sect_t sector; + static flash_sect_t saved_sector = 0; /* previously found sector */ + flash_sect_t sector = saved_sector; - for (sector = info->sector_count - 1; sector >= 0; sector--) { - if (addr >= info->start[sector]) - break; - } + while ((info->start[sector] < addr) + && (sector < info->sector_count - 1)) + sector++; + while ((info->start[sector] > addr) && (sector > 0)) + /* + * also decrements the sector in case of an overshot + * in the first loop + */ + sector--; + + saved_sector = sector; return sector; } @@ -793,11 +797,10 @@ static flash_sect_t find_sector (flash_info_t * info, ulong addr) static int flash_write_cfiword (flash_info_t * info, ulong dest, cfiword_t cword) { - void *dstaddr; + void *dstaddr = (void *)dest; int flag; - flash_sect_t sect; - - dstaddr = map_physmem(dest, info->portwidth, MAP_NOCACHE); + flash_sect_t sect = 0; + char sect_found = 0; /* Check if Flash is (sufficiently) erased */ switch (info->portwidth) { @@ -817,10 +820,8 @@ static int flash_write_cfiword (flash_info_t * info, ulong dest, flag = 0; break; } - if (!flag) { - unmap_physmem(dstaddr, info->portwidth); + if (!flag) return ERR_NOT_ERASED; - } /* Disable interrupts which might cause a timeout here */ flag = disable_interrupts (); @@ -840,6 +841,7 @@ static int flash_write_cfiword (flash_info_t * info, ulong dest, sect = find_sector(info, dest); flash_unlock_seq (info, sect); flash_write_cmd (info, sect, info->addr_unlock1, AMD_CMD_WRITE); + sect_found = 1; break; } @@ -862,10 +864,10 @@ static int flash_write_cfiword (flash_info_t * info, ulong dest, if (flag) enable_interrupts (); - unmap_physmem(dstaddr, info->portwidth); + if (!sect_found) + sect = find_sector (info, dest); - return flash_full_status_check (info, find_sector (info, dest), - info->write_tout, "write"); + return flash_full_status_check (info, sect, info->write_tout, "write"); } #ifdef CONFIG_SYS_FLASH_USE_BUFFER_WRITE @@ -877,7 +879,7 @@ static int flash_write_cfibuffer (flash_info_t * info, ulong dest, uchar * cp, int cnt; int retcode; void *src = cp; - void *dst = map_physmem(dest, len, MAP_NOCACHE); + void *dst = (void *)dest; void *dst2 = dst; int flag = 0; uint offset = 0; @@ -1039,7 +1041,6 @@ static int flash_write_cfibuffer (flash_info_t * info, ulong dest, uchar * cp, } out_unmap: - unmap_physmem(dst, len); return retcode; } #endif /* CONFIG_SYS_FLASH_USE_BUFFER_WRITE */ @@ -1288,7 +1289,7 @@ int write_buff (flash_info_t * info, uchar * src, ulong addr, ulong cnt) /* handle unaligned start */ if ((aln = addr - wp) != 0) { cword.l = 0; - p = map_physmem(wp, info->portwidth, MAP_NOCACHE); + p = (uchar *)wp; for (i = 0; i < aln; ++i) flash_add_byte (info, &cword, flash_read8(p + i)); @@ -1300,7 +1301,6 @@ int write_buff (flash_info_t * info, uchar * src, ulong addr, ulong cnt) flash_add_byte (info, &cword, flash_read8(p + i)); rc = flash_write_cfiword (info, wp, cword); - unmap_physmem(p, info->portwidth); if (rc != 0) return rc; @@ -1359,14 +1359,13 @@ int write_buff (flash_info_t * info, uchar * src, ulong addr, ulong cnt) * handle unaligned tail bytes */ cword.l = 0; - p = map_physmem(wp, info->portwidth, MAP_NOCACHE); + p = (uchar *)wp; for (i = 0; (i < info->portwidth) && (cnt > 0); ++i) { flash_add_byte (info, &cword, *src++); --cnt; } for (; i < info->portwidth; ++i) flash_add_byte (info, &cword, flash_read8(p + i)); - unmap_physmem(p, info->portwidth); return flash_write_cfiword (info, wp, cword); } @@ -1605,7 +1604,7 @@ static void flash_read_jedec_ids (flash_info_t * info) * board_flash_get_legacy needs to fill in at least: * info->portwidth, info->chipwidth and info->interface for Jedec probing. */ -static int flash_detect_legacy(ulong base, int banknum) +static int flash_detect_legacy(phys_addr_t base, int banknum) { flash_info_t *info = &flash_info[banknum]; @@ -1621,7 +1620,10 @@ static int flash_detect_legacy(ulong base, int banknum) for (i = 0; i < sizeof(modes) / sizeof(modes[0]); i++) { info->vendor = modes[i]; - info->start[0] = base; + info->start[0] = + (ulong)map_physmem(base, + info->portwidth, + MAP_NOCACHE); if (info->portwidth == FLASH_CFI_8BIT && info->interface == FLASH_CFI_X8X16) { info->addr_unlock1 = 0x2AAA; @@ -1635,8 +1637,11 @@ static int flash_detect_legacy(ulong base, int banknum) info->manufacturer_id, info->device_id, info->device_id2); - if (jedec_flash_match(info, base)) + if (jedec_flash_match(info, info->start[0])) break; + else + unmap_physmem((void *)info->start[0], + MAP_NOCACHE); } } @@ -1658,7 +1663,7 @@ static int flash_detect_legacy(ulong base, int banknum) return 0; /* use CFI */ } #else -static inline int flash_detect_legacy(ulong base, int banknum) +static inline int flash_detect_legacy(phys_addr_t base, int banknum) { return 0; /* use CFI */ } @@ -1795,16 +1800,30 @@ static void flash_fixup_atmel(flash_info_t *info, struct cfi_qry *qry) cfi_reverse_geometry(qry); } +static void flash_fixup_stm(flash_info_t *info, struct cfi_qry *qry) +{ + /* check if flash geometry needs reversal */ + if (qry->num_erase_regions > 1) { + /* reverse geometry if top boot part */ + if (info->cfi_version < 0x3131) { + /* CFI < 1.1, guess by device id (only M29W320ET now) */ + if (info->device_id == 0x2256) { + cfi_reverse_geometry(qry); + } + } + } +} + /* * The following code cannot be run from FLASH! * */ -ulong flash_get_size (ulong base, int banknum) +ulong flash_get_size (phys_addr_t base, int banknum) { flash_info_t *info = &flash_info[banknum]; int i, j; flash_sect_t sect_cnt; - unsigned long sector; + phys_addr_t sector; unsigned long tmp; int size_ratio; uchar num_erase_regions; @@ -1820,7 +1839,7 @@ ulong flash_get_size (ulong base, int banknum) info->legacy_unlock = 0; #endif - info->start[0] = base; + info->start[0] = (ulong)map_physmem(base, info->portwidth, MAP_NOCACHE); if (flash_detect_cfi (info, &qry)) { info->vendor = le16_to_cpu(qry.p_id); @@ -1868,6 +1887,9 @@ ulong flash_get_size (ulong base, int banknum) case 0x001f: flash_fixup_atmel(info, &qry); break; + case 0x0020: + flash_fixup_stm(info, &qry); + break; } debug ("manufacturer is %d\n", info->vendor); @@ -1909,7 +1931,10 @@ ulong flash_get_size (ulong base, int banknum) printf("ERROR: too many flash sectors\n"); break; } - info->start[sect_cnt] = sector; + info->start[sect_cnt] = + (ulong)map_physmem(sector, + info->portwidth, + MAP_NOCACHE); sector += (erase_region_size * size_ratio); /* @@ -1986,7 +2011,7 @@ unsigned long flash_init (void) char *s = getenv("unlock"); #endif -#define BANK_BASE(i) (((unsigned long [CFI_MAX_FLASH_BANKS])CONFIG_SYS_FLASH_BANKS_LIST)[i]) +#define BANK_BASE(i) (((phys_addr_t [CFI_MAX_FLASH_BANKS])CONFIG_SYS_FLASH_BANKS_LIST)[i]) /* Init: no FLASHes known */ for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; ++i) { diff --git a/drivers/mtd/jedec_flash.c b/drivers/mtd/jedec_flash.c index 226e1e418..e48acecea 100644 --- a/drivers/mtd/jedec_flash.c +++ b/drivers/mtd/jedec_flash.c @@ -37,10 +37,6 @@ #define P_ID_AMD_STD CFI_CMDSET_AMD_LEGACY -/* Manufacturers */ -#define MANUFACTURER_AMD 0x0001 -#define MANUFACTURER_SST 0x00BF - /* AMD */ #define AM29DL800BB 0x22CB #define AM29DL800BT 0x224A @@ -172,7 +168,7 @@ struct amd_flash_info { static const struct amd_flash_info jedec_table[] = { #ifdef CONFIG_SYS_FLASH_LEGACY_256Kx8 { - .mfr_id = MANUFACTURER_SST, + .mfr_id = (u16)SST_MANUFACT, .dev_id = SST39LF020, .name = "SST 39LF020", .uaddr = { @@ -188,7 +184,7 @@ static const struct amd_flash_info jedec_table[] = { #endif #ifdef CONFIG_SYS_FLASH_LEGACY_512Kx8 { - .mfr_id = MANUFACTURER_AMD, + .mfr_id = (u16)AMD_MANUFACT, .dev_id = AM29LV040B, .name = "AMD AM29LV040B", .uaddr = { @@ -202,7 +198,7 @@ static const struct amd_flash_info jedec_table[] = { } }, { - .mfr_id = MANUFACTURER_SST, + .mfr_id = (u16)SST_MANUFACT, .dev_id = SST39LF040, .name = "SST 39LF040", .uaddr = { @@ -215,10 +211,24 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x01000,128), } }, + { + .mfr_id = (u16)STM_MANUFACT, + .dev_id = STM_ID_M29W040B, + .name = "ST Micro M29W040B", + .uaddr = { + [0] = MTD_UADDR_0x0555_0x02AA /* x8 */ + }, + .DevSize = SIZE_512KiB, + .CmdSet = P_ID_AMD_STD, + .NumEraseRegions= 1, + .regions = { + ERASEINFO(0x10000,8), + } + }, #endif #ifdef CONFIG_SYS_FLASH_LEGACY_512Kx16 { - .mfr_id = MANUFACTURER_AMD, + .mfr_id = (u16)AMD_MANUFACT, .dev_id = AM29LV400BB, .name = "AMD AM29LV400BB", .uaddr = { @@ -235,7 +245,7 @@ static const struct amd_flash_info jedec_table[] = { } }, { - .mfr_id = MANUFACTURER_AMD, + .mfr_id = (u16)AMD_MANUFACT, .dev_id = AM29LV800BB, .name = "AMD AM29LV800BB", .uaddr = { diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index b0abe6e52..5974d7768 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -35,9 +35,12 @@ COBJS-y += nand_ids.o COBJS-y += nand_util.o endif +COBJS-$(CONFIG_DRIVER_NAND_BFIN) += bfin_nand.o COBJS-$(CONFIG_NAND_FSL_ELBC) += fsl_elbc_nand.o COBJS-$(CONFIG_NAND_FSL_UPM) += fsl_upm.o +COBJS-$(CONFIG_NAND_NOMADIK) += nomadik.o COBJS-$(CONFIG_NAND_S3C64XX) += s3c64xx.o +COBJS-$(CONFIG_NAND_OMAP_GPMC) += omap_gpmc.o endif COBJS := $(COBJS-y) diff --git a/drivers/mtd/nand/bfin_nand.c b/drivers/mtd/nand/bfin_nand.c new file mode 100644 index 000000000..f6a0835b4 --- /dev/null +++ b/drivers/mtd/nand/bfin_nand.c @@ -0,0 +1,385 @@ +/* + * Driver for Blackfin on-chip NAND controller. + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Copyright (c) 2007-2008 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +/* TODO: + * - move bit defines into mach-common/bits/nand.h + * - try and replace all IRQSTAT usage with STAT polling + * - have software ecc mode use same algo as hw ecc ? + */ + +#include <common.h> +#include <asm/io.h> + +#ifdef DEBUG +# define pr_stamp() printf("%s:%s:%i: here i am\n", __FILE__, __func__, __LINE__) +#else +# define pr_stamp() +#endif + +#include <nand.h> + +#include <asm/blackfin.h> + +/* Bit masks for NFC_CTL */ + +#define WR_DLY 0xf /* Write Strobe Delay */ +#define RD_DLY 0xf0 /* Read Strobe Delay */ +#define NWIDTH 0x100 /* NAND Data Width */ +#define PG_SIZE 0x200 /* Page Size */ + +/* Bit masks for NFC_STAT */ + +#define NBUSY 0x1 /* Not Busy */ +#define WB_FULL 0x2 /* Write Buffer Full */ +#define PG_WR_STAT 0x4 /* Page Write Pending */ +#define PG_RD_STAT 0x8 /* Page Read Pending */ +#define WB_EMPTY 0x10 /* Write Buffer Empty */ + +/* Bit masks for NFC_IRQSTAT */ + +#define NBUSYIRQ 0x1 /* Not Busy IRQ */ +#define WB_OVF 0x2 /* Write Buffer Overflow */ +#define WB_EDGE 0x4 /* Write Buffer Edge Detect */ +#define RD_RDY 0x8 /* Read Data Ready */ +#define WR_DONE 0x10 /* Page Write Done */ + +#define NAND_IS_512() (CONFIG_BFIN_NFC_CTL_VAL & 0x200) + +/* + * hardware specific access to control-lines + */ +static void bfin_nfc_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl) +{ + pr_stamp(); + + if (cmd == NAND_CMD_NONE) + return; + + while (bfin_read_NFC_STAT() & WB_FULL) + continue; + + if (ctrl & NAND_CLE) + bfin_write_NFC_CMD(cmd); + else + bfin_write_NFC_ADDR(cmd); + SSYNC(); +} + +int bfin_nfc_devready(struct mtd_info *mtd) +{ + pr_stamp(); + return (bfin_read_NFC_STAT() & NBUSY ? 1 : 0); +} + +/* + * PIO mode for buffer writing and reading + */ +static void bfin_nfc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + pr_stamp(); + + int i; + + /* + * Data reads are requested by first writing to NFC_DATA_RD + * and then reading back from NFC_READ. + */ + for (i = 0; i < len; ++i) { + while (bfin_read_NFC_STAT() & WB_FULL) + if (ctrlc()) + return; + + /* Contents do not matter */ + bfin_write_NFC_DATA_RD(0x0000); + + while (!(bfin_read_NFC_IRQSTAT() & RD_RDY)) + if (ctrlc()) + return; + + buf[i] = bfin_read_NFC_READ(); + + bfin_write_NFC_IRQSTAT(RD_RDY); + } +} + +static uint8_t bfin_nfc_read_byte(struct mtd_info *mtd) +{ + pr_stamp(); + + uint8_t val; + bfin_nfc_read_buf(mtd, &val, 1); + return val; +} + +static void bfin_nfc_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len) +{ + pr_stamp(); + + int i; + + for (i = 0; i < len; ++i) { + while (bfin_read_NFC_STAT() & WB_FULL) + if (ctrlc()) + return; + + bfin_write_NFC_DATA_WR(buf[i]); + } +} + +/* + * ECC functions + * These allow the bfin to use the controller's ECC + * generator block to ECC the data as it passes through + */ + +/* + * ECC error correction function + */ +static int bfin_nfc_correct_data_256(struct mtd_info *mtd, u_char *dat, + u_char *read_ecc, u_char *calc_ecc) +{ + u32 syndrome[5]; + u32 calced, stored; + unsigned short failing_bit, failing_byte; + u_char data; + + pr_stamp(); + + calced = calc_ecc[0] | (calc_ecc[1] << 8) | (calc_ecc[2] << 16); + stored = read_ecc[0] | (read_ecc[1] << 8) | (read_ecc[2] << 16); + + syndrome[0] = (calced ^ stored); + + /* + * syndrome 0: all zero + * No error in data + * No action + */ + if (!syndrome[0] || !calced || !stored) + return 0; + + /* + * sysdrome 0: only one bit is one + * ECC data was incorrect + * No action + */ + if (hweight32(syndrome[0]) == 1) + return 1; + + syndrome[1] = (calced & 0x7FF) ^ (stored & 0x7FF); + syndrome[2] = (calced & 0x7FF) ^ ((calced >> 11) & 0x7FF); + syndrome[3] = (stored & 0x7FF) ^ ((stored >> 11) & 0x7FF); + syndrome[4] = syndrome[2] ^ syndrome[3]; + + /* + * sysdrome 0: exactly 11 bits are one, each parity + * and parity' pair is 1 & 0 or 0 & 1. + * 1-bit correctable error + * Correct the error + */ + if (hweight32(syndrome[0]) == 11 && syndrome[4] == 0x7FF) { + failing_bit = syndrome[1] & 0x7; + failing_byte = syndrome[1] >> 0x3; + data = *(dat + failing_byte); + data = data ^ (0x1 << failing_bit); + *(dat + failing_byte) = data; + + return 0; + } + + /* + * sysdrome 0: random data + * More than 1-bit error, non-correctable error + * Discard data, mark bad block + */ + + return 1; +} + +static int bfin_nfc_correct_data(struct mtd_info *mtd, u_char *dat, + u_char *read_ecc, u_char *calc_ecc) +{ + int ret; + + pr_stamp(); + + ret = bfin_nfc_correct_data_256(mtd, dat, read_ecc, calc_ecc); + + /* If page size is 512, correct second 256 bytes */ + if (NAND_IS_512()) { + dat += 256; + read_ecc += 8; + calc_ecc += 8; + ret |= bfin_nfc_correct_data_256(mtd, dat, read_ecc, calc_ecc); + } + + return ret; +} + +static void reset_ecc(void) +{ + bfin_write_NFC_RST(0x1); + while (bfin_read_NFC_RST() & 1) + continue; +} + +static void bfin_nfc_enable_hwecc(struct mtd_info *mtd, int mode) +{ + reset_ecc(); +} + +static int bfin_nfc_calculate_ecc(struct mtd_info *mtd, + const u_char *dat, u_char *ecc_code) +{ + u16 ecc0, ecc1; + u32 code[2]; + u8 *p; + + pr_stamp(); + + /* first 4 bytes ECC code for 256 page size */ + ecc0 = bfin_read_NFC_ECC0(); + ecc1 = bfin_read_NFC_ECC1(); + + code[0] = (ecc0 & 0x7FF) | ((ecc1 & 0x7FF) << 11); + + /* first 3 bytes in ecc_code for 256 page size */ + p = (u8 *) code; + memcpy(ecc_code, p, 3); + + /* second 4 bytes ECC code for 512 page size */ + if (NAND_IS_512()) { + ecc0 = bfin_read_NFC_ECC2(); + ecc1 = bfin_read_NFC_ECC3(); + code[1] = (ecc0 & 0x7FF) | ((ecc1 & 0x7FF) << 11); + + /* second 3 bytes in ecc_code for second 256 + * bytes of 512 page size + */ + p = (u8 *) (code + 1); + memcpy((ecc_code + 3), p, 3); + } + + reset_ecc(); + + return 0; +} + +#ifdef CONFIG_BFIN_NFC_BOOTROM_ECC +# define BOOTROM_ECC 1 +#else +# define BOOTROM_ECC 0 +#endif + +static uint8_t bbt_pattern[] = { 0xff }; + +static struct nand_bbt_descr bootrom_bbt = { + .options = 0, + .offs = 63, + .len = 1, + .pattern = bbt_pattern, +}; + +static struct nand_ecclayout bootrom_ecclayout = { + .eccbytes = 24, + .eccpos = { + 0x8 * 0, 0x8 * 0 + 1, 0x8 * 0 + 2, + 0x8 * 1, 0x8 * 1 + 1, 0x8 * 1 + 2, + 0x8 * 2, 0x8 * 2 + 1, 0x8 * 2 + 2, + 0x8 * 3, 0x8 * 3 + 1, 0x8 * 3 + 2, + 0x8 * 4, 0x8 * 4 + 1, 0x8 * 4 + 2, + 0x8 * 5, 0x8 * 5 + 1, 0x8 * 5 + 2, + 0x8 * 6, 0x8 * 6 + 1, 0x8 * 6 + 2, + 0x8 * 7, 0x8 * 7 + 1, 0x8 * 7 + 2 + }, + .oobfree = { + { 0x8 * 0 + 3, 5 }, + { 0x8 * 1 + 3, 5 }, + { 0x8 * 2 + 3, 5 }, + { 0x8 * 3 + 3, 5 }, + { 0x8 * 4 + 3, 5 }, + { 0x8 * 5 + 3, 5 }, + { 0x8 * 6 + 3, 5 }, + { 0x8 * 7 + 3, 5 }, + } +}; + +/* + * Board-specific NAND initialization. The following members of the + * argument are board-specific (per include/linux/mtd/nand.h): + * - IO_ADDR_R?: address to read the 8 I/O lines of the flash device + * - IO_ADDR_W?: address to write the 8 I/O lines of the flash device + * - cmd_ctrl: hardwarespecific function for accesing control-lines + * - dev_ready: hardwarespecific function for accesing device ready/busy line + * - enable_hwecc?: function to enable (reset) hardware ecc generator. Must + * only be provided if a hardware ECC is available + * - ecc.mode: mode of ecc, see defines + * - chip_delay: chip dependent delay for transfering data from array to + * read regs (tR) + * - options: various chip options. They can partly be set to inform + * nand_scan about special functionality. See the defines for further + * explanation + * Members with a "?" were not set in the merged testing-NAND branch, + * so they are not set here either. + */ +int board_nand_init(struct nand_chip *chip) +{ + pr_stamp(); + + /* set width/ecc/timings/etc... */ + bfin_write_NFC_CTL(CONFIG_BFIN_NFC_CTL_VAL); + + /* clear interrupt status */ + bfin_write_NFC_IRQMASK(0x0); + bfin_write_NFC_IRQSTAT(0xffff); + + /* enable GPIO function enable register */ +#ifdef __ADSPBF54x__ + bfin_write_PORTJ_FER(bfin_read_PORTJ_FER() | 6); +#elif defined(__ADSPBF52x__) + bfin_write_PORTH_FER(bfin_read_PORTH_FER() | 0xFCFF); + bfin_write_PORTH_MUX(0); +#else +# error no support for this variant +#endif + + chip->cmd_ctrl = bfin_nfc_cmd_ctrl; + chip->read_buf = bfin_nfc_read_buf; + chip->write_buf = bfin_nfc_write_buf; + chip->read_byte = bfin_nfc_read_byte; + +#ifdef CONFIG_BFIN_NFC_NO_HW_ECC +# define ECC_HW 0 +#else +# define ECC_HW 1 +#endif + if (ECC_HW) { + if (BOOTROM_ECC) { + chip->badblock_pattern = &bootrom_bbt; + chip->ecc.layout = &bootrom_ecclayout; + } + if (!NAND_IS_512()) { + chip->ecc.bytes = 3; + chip->ecc.size = 256; + } else { + chip->ecc.bytes = 6; + chip->ecc.size = 512; + } + chip->ecc.mode = NAND_ECC_HW; + chip->ecc.calculate = bfin_nfc_calculate_ecc; + chip->ecc.correct = bfin_nfc_correct_data; + chip->ecc.hwctl = bfin_nfc_enable_hwecc; + } else + chip->ecc.mode = NAND_ECC_SOFT; + chip->dev_ready = bfin_nfc_devready; + chip->chip_delay = 0; + + return 0; +} diff --git a/drivers/mtd/nand/fsl_elbc_nand.c b/drivers/mtd/nand/fsl_elbc_nand.c index 367c7d7fc..3f318e02e 100644 --- a/drivers/mtd/nand/fsl_elbc_nand.c +++ b/drivers/mtd/nand/fsl_elbc_nand.c @@ -718,7 +718,7 @@ static void fsl_elbc_ctrl_init(void) int board_nand_init(struct nand_chip *nand) { struct fsl_elbc_mtd *priv; - uint32_t br, or; + uint32_t br = 0, or = 0; if (!elbc_ctrl) { fsl_elbc_ctrl_init(); @@ -737,11 +737,13 @@ int board_nand_init(struct nand_chip *nand) * if we could pass more than one datum to the NAND driver... */ for (priv->bank = 0; priv->bank < MAX_BANKS; priv->bank++) { + phys_addr_t base_addr = virt_to_phys(nand->IO_ADDR_R); + br = in_be32(&elbc_ctrl->regs->bank[priv->bank].br); or = in_be32(&elbc_ctrl->regs->bank[priv->bank].or); if ((br & BR_V) && (br & BR_MSEL) == BR_MS_FCM && - (br & or & BR_BA) == (phys_addr_t)nand->IO_ADDR_R) + (br & or & BR_BA) == BR_PHYS_ADDR(base_addr)) break; } diff --git a/drivers/mtd/nand/nand.c b/drivers/mtd/nand/nand.c index eeb19ff1b..70b605f9d 100644 --- a/drivers/mtd/nand/nand.c +++ b/drivers/mtd/nand/nand.c @@ -28,6 +28,8 @@ #define CONFIG_SYS_NAND_BASE_LIST { CONFIG_SYS_NAND_BASE } #endif +DECLARE_GLOBAL_DATA_PTR; + int nand_curr_device = -1; nand_info_t nand_info[CONFIG_SYS_MAX_NAND_DEVICE]; @@ -36,8 +38,6 @@ static ulong base_address[CONFIG_SYS_MAX_NAND_DEVICE] = CONFIG_SYS_NAND_BASE_LIS static const char default_nand_name[] = "nand"; -extern int board_nand_init(struct nand_chip *nand); - static void nand_init_chip(struct mtd_info *mtd, struct nand_chip *nand, ulong base_addr) { @@ -48,6 +48,8 @@ static void nand_init_chip(struct mtd_info *mtd, struct nand_chip *nand, if (nand_scan(mtd, 1) == 0) { if (!mtd->name) mtd->name = (char *)default_nand_name; + else + mtd->name += gd->reloc_off; } else mtd->name = NULL; } else { diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index ba05b762e..d33fee242 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -75,6 +75,17 @@ #include <jffs2/jffs2.h> #endif +/* + * CONFIG_SYS_NAND_RESET_CNT is used as a timeout mechanism when resetting + * a flash. NAND flash is initialized prior to interrupts so standard timers + * can't be used. CONFIG_SYS_NAND_RESET_CNT should be set to a value + * which is greater than (max NAND reset time / NAND status read time). + * A conservative default of 200000 (500 us / 25 ns) is used as a default. + */ +#ifndef CONFIG_SYS_NAND_RESET_CNT +#define CONFIG_SYS_NAND_RESET_CNT 200000 +#endif + /* Define default oob placement schemes for large and small page devices */ static struct nand_ecclayout nand_oob_8 = { .eccbytes = 3, @@ -460,8 +471,8 @@ static int nand_block_checkbad(struct mtd_info *mtd, loff_t ofs, int getchip, struct nand_chip *chip = mtd->priv; if (!(chip->options & NAND_BBT_SCANNED)) { - chip->scan_bbt(mtd); chip->options |= NAND_BBT_SCANNED; + chip->scan_bbt(mtd); } if (!chip->bbt) @@ -524,6 +535,7 @@ static void nand_command(struct mtd_info *mtd, unsigned int command, { register struct nand_chip *chip = mtd->priv; int ctrl = NAND_CTRL_CLE | NAND_CTRL_CHANGE; + uint32_t rst_sts_cnt = CONFIG_SYS_NAND_RESET_CNT; /* * Write out the command to the device. @@ -590,7 +602,8 @@ static void nand_command(struct mtd_info *mtd, unsigned int command, NAND_CTRL_CLE | NAND_CTRL_CHANGE); chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE); - while (!(chip->read_byte(mtd) & NAND_STATUS_READY)) ; + while (!(chip->read_byte(mtd) & NAND_STATUS_READY) && + (rst_sts_cnt--)); return; /* This applies to read commands */ @@ -626,6 +639,7 @@ static void nand_command_lp(struct mtd_info *mtd, unsigned int command, int column, int page_addr) { register struct nand_chip *chip = mtd->priv; + uint32_t rst_sts_cnt = CONFIG_SYS_NAND_RESET_CNT; /* Emulate NAND_CMD_READOOB */ if (command == NAND_CMD_READOOB) { @@ -696,7 +710,8 @@ static void nand_command_lp(struct mtd_info *mtd, unsigned int command, NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE); chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE); - while (!(chip->read_byte(mtd) & NAND_STATUS_READY)) ; + while (!(chip->read_byte(mtd) & NAND_STATUS_READY) && + (rst_sts_cnt--)); return; case NAND_CMD_RNDOUT: @@ -2144,7 +2159,7 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr, { int page, len, status, pages_per_block, ret, chipnr; struct nand_chip *chip = mtd->priv; - int rewrite_bbt[NAND_MAX_CHIPS]={0}; + int rewrite_bbt[CONFIG_SYS_NAND_MAX_CHIPS]={0}; unsigned int bbt_masked_page = 0xffffffff; MTDDEBUG (MTD_DEBUG_LEVEL3, "nand_erase: start = 0x%08x, len = %i\n", @@ -2618,7 +2633,9 @@ int nand_scan_ident(struct mtd_info *mtd, int maxchips) type = nand_get_flash_type(mtd, chip, busw, &nand_maf_id); if (IS_ERR(type)) { +#ifndef CONFIG_SYS_NAND_QUIET_TEST printk(KERN_WARNING "No NAND device found!!!\n"); +#endif chip->select_chip(mtd, -1); return PTR_ERR(type); } diff --git a/drivers/mtd/nand/nand_util.c b/drivers/mtd/nand/nand_util.c index d86c98737..6ba52b30c 100644 --- a/drivers/mtd/nand/nand_util.c +++ b/drivers/mtd/nand/nand_util.c @@ -238,7 +238,8 @@ static struct nand_ecclayout autoplace_ecclayout = { #endif /* XXX U-BOOT XXX */ -#if 0 +#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK + /****************************************************************************** * Support for locking / unlocking operations of some NAND devices *****************************************************************************/ @@ -253,7 +254,7 @@ static struct nand_ecclayout autoplace_ecclayout = { * nand_lock: Set all pages of NAND flash chip to the LOCK or LOCK-TIGHT * state * - * @param meminfo nand mtd instance + * @param mtd nand mtd instance * @param tight bring device in lock tight mode * * @return 0 on success, -1 in case of error @@ -270,21 +271,21 @@ static struct nand_ecclayout autoplace_ecclayout = { * calls will fail. It is only posible to leave lock-tight state by * an hardware signal (low pulse on _WP pin) or by power down. */ -int nand_lock(nand_info_t *meminfo, int tight) +int nand_lock(struct mtd_info *mtd, int tight) { int ret = 0; int status; - struct nand_chip *this = meminfo->priv; + struct nand_chip *chip = mtd->priv; /* select the NAND device */ - this->select_chip(meminfo, 0); + chip->select_chip(mtd, 0); - this->cmdfunc(meminfo, + chip->cmdfunc(mtd, (tight ? NAND_CMD_LOCK_TIGHT : NAND_CMD_LOCK), -1, -1); /* call wait ready function */ - status = this->waitfunc(meminfo, this, FL_WRITING); + status = chip->waitfunc(mtd, chip); /* see if device thinks it succeeded */ if (status & 0x01) { @@ -292,7 +293,7 @@ int nand_lock(nand_info_t *meminfo, int tight) } /* de-select the NAND device */ - this->select_chip(meminfo, -1); + chip->select_chip(mtd, -1); return ret; } @@ -300,7 +301,7 @@ int nand_lock(nand_info_t *meminfo, int tight) * nand_get_lock_status: - query current lock state from one page of NAND * flash * - * @param meminfo nand mtd instance + * @param mtd nand mtd instance * @param offset page address to query (muss be page aligned!) * * @return -1 in case of error @@ -311,19 +312,19 @@ int nand_lock(nand_info_t *meminfo, int tight) * NAND_LOCK_STATUS_UNLOCK: page unlocked * */ -int nand_get_lock_status(nand_info_t *meminfo, ulong offset) +int nand_get_lock_status(struct mtd_info *mtd, ulong offset) { int ret = 0; int chipnr; int page; - struct nand_chip *this = meminfo->priv; + struct nand_chip *chip = mtd->priv; /* select the NAND device */ - chipnr = (int)(offset >> this->chip_shift); - this->select_chip(meminfo, chipnr); + chipnr = (int)(offset >> chip->chip_shift); + chip->select_chip(mtd, chipnr); - if ((offset & (meminfo->writesize - 1)) != 0) { + if ((offset & (mtd->writesize - 1)) != 0) { printf ("nand_get_lock_status: " "Start address must be beginning of " "nand page!\n"); @@ -332,16 +333,16 @@ int nand_get_lock_status(nand_info_t *meminfo, ulong offset) } /* check the Lock Status */ - page = (int)(offset >> this->page_shift); - this->cmdfunc(meminfo, NAND_CMD_LOCK_STATUS, -1, page & this->pagemask); + page = (int)(offset >> chip->page_shift); + chip->cmdfunc(mtd, NAND_CMD_LOCK_STATUS, -1, page & chip->pagemask); - ret = this->read_byte(meminfo) & (NAND_LOCK_STATUS_TIGHT + ret = chip->read_byte(mtd) & (NAND_LOCK_STATUS_TIGHT | NAND_LOCK_STATUS_LOCK | NAND_LOCK_STATUS_UNLOCK); out: /* de-select the NAND device */ - this->select_chip(meminfo, -1); + chip->select_chip(mtd, -1); return ret; } @@ -349,59 +350,65 @@ int nand_get_lock_status(nand_info_t *meminfo, ulong offset) * nand_unlock: - Unlock area of NAND pages * only one consecutive area can be unlocked at one time! * - * @param meminfo nand mtd instance + * @param mtd nand mtd instance * @param start start byte address * @param length number of bytes to unlock (must be a multiple of * page size nand->writesize) * * @return 0 on success, -1 in case of error */ -int nand_unlock(nand_info_t *meminfo, ulong start, ulong length) +int nand_unlock(struct mtd_info *mtd, ulong start, ulong length) { int ret = 0; int chipnr; int status; int page; - struct nand_chip *this = meminfo->priv; + struct nand_chip *chip = mtd->priv; printf ("nand_unlock: start: %08x, length: %d!\n", (int)start, (int)length); /* select the NAND device */ - chipnr = (int)(start >> this->chip_shift); - this->select_chip(meminfo, chipnr); + chipnr = (int)(start >> chip->chip_shift); + chip->select_chip(mtd, chipnr); /* check the WP bit */ - this->cmdfunc(meminfo, NAND_CMD_STATUS, -1, -1); - if ((this->read_byte(meminfo) & 0x80) == 0) { + chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1); + if (!(chip->read_byte(mtd) & NAND_STATUS_WP)) { printf ("nand_unlock: Device is write protected!\n"); ret = -1; goto out; } - if ((start & (meminfo->writesize - 1)) != 0) { + if ((start & (mtd->erasesize - 1)) != 0) { printf ("nand_unlock: Start address must be beginning of " - "nand page!\n"); + "nand block!\n"); ret = -1; goto out; } - if (length == 0 || (length & (meminfo->writesize - 1)) != 0) { - printf ("nand_unlock: Length must be a multiple of nand page " - "size!\n"); + if (length == 0 || (length & (mtd->erasesize - 1)) != 0) { + printf ("nand_unlock: Length must be a multiple of nand block " + "size %08x!\n", mtd->erasesize); ret = -1; goto out; } + /* + * Set length so that the last address is set to the + * starting address of the last block + */ + length -= mtd->erasesize; + /* submit address of first page to unlock */ - page = (int)(start >> this->page_shift); - this->cmdfunc(meminfo, NAND_CMD_UNLOCK1, -1, page & this->pagemask); + page = (int)(start >> chip->page_shift); + chip->cmdfunc(mtd, NAND_CMD_UNLOCK1, -1, page & chip->pagemask); /* submit ADDRESS of LAST page to unlock */ - page += (int)(length >> this->page_shift) - 1; - this->cmdfunc(meminfo, NAND_CMD_UNLOCK2, -1, page & this->pagemask); + page += (int)(length >> chip->page_shift); + chip->cmdfunc(mtd, NAND_CMD_UNLOCK2, -1, page & chip->pagemask); /* call wait ready function */ - status = this->waitfunc(meminfo, this, FL_WRITING); + status = chip->waitfunc(mtd, chip); /* see if device thinks it succeeded */ if (status & 0x01) { /* there was an error */ @@ -411,7 +418,7 @@ int nand_unlock(nand_info_t *meminfo, ulong start, ulong length) out: /* de-select the NAND device */ - this->select_chip(meminfo, -1); + chip->select_chip(mtd, -1); return ret; } #endif @@ -488,7 +495,7 @@ int nand_write_skip_bad(nand_info_t *nand, size_t offset, size_t *length, if (len_incl_bad == *length) { rval = nand_write (nand, offset, length, buffer); if (rval != 0) - printf ("NAND write to offset %x failed %d\n", + printf ("NAND write to offset %zx failed %d\n", offset, rval); return rval; @@ -499,7 +506,7 @@ int nand_write_skip_bad(nand_info_t *nand, size_t offset, size_t *length, size_t write_size; if (nand_block_isbad (nand, offset & ~(nand->erasesize - 1))) { - printf ("Skip bad block 0x%08x\n", + printf ("Skip bad block 0x%08zx\n", offset & ~(nand->erasesize - 1)); offset += nand->erasesize - block_offset; continue; @@ -512,7 +519,7 @@ int nand_write_skip_bad(nand_info_t *nand, size_t offset, size_t *length, rval = nand_write (nand, offset, &write_size, p_buffer); if (rval != 0) { - printf ("NAND write to offset %x failed %d\n", + printf ("NAND write to offset %zx failed %d\n", offset, rval); *length -= left_to_write; return rval; @@ -558,7 +565,7 @@ int nand_read_skip_bad(nand_info_t *nand, size_t offset, size_t *length, if (len_incl_bad == *length) { rval = nand_read (nand, offset, length, buffer); if (rval != 0) - printf ("NAND read from offset %x failed %d\n", + printf ("NAND read from offset %zx failed %d\n", offset, rval); return rval; @@ -569,7 +576,7 @@ int nand_read_skip_bad(nand_info_t *nand, size_t offset, size_t *length, size_t read_length; if (nand_block_isbad (nand, offset & ~(nand->erasesize - 1))) { - printf ("Skipping bad block 0x%08x\n", + printf ("Skipping bad block 0x%08zx\n", offset & ~(nand->erasesize - 1)); offset += nand->erasesize - block_offset; continue; @@ -582,7 +589,7 @@ int nand_read_skip_bad(nand_info_t *nand, size_t offset, size_t *length, rval = nand_read (nand, offset, &read_length, p_buffer); if (rval != 0) { - printf ("NAND read from offset %x failed %d\n", + printf ("NAND read from offset %zx failed %d\n", offset, rval); *length -= left_to_read; return rval; diff --git a/drivers/mtd/nand/nomadik.c b/drivers/mtd/nand/nomadik.c new file mode 100644 index 000000000..b76f4cbb5 --- /dev/null +++ b/drivers/mtd/nand/nomadik.c @@ -0,0 +1,221 @@ +/* + * (C) Copyright 2007 STMicroelectronics, <www.st.com> + * (C) Copyright 2009 Alessandro Rubini <rubini@unipv.it> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <nand.h> +#include <asm/io.h> + +static inline int parity(int b) /* b is really a byte; returns 0 or ~0 */ +{ + __asm__ __volatile__( + "eor %0, %0, %0, lsr #4\n\t" + "eor %0, %0, %0, lsr #2\n\t" + "eor %0, %0, %0, lsr #1\n\t" + "ands %0, %0, #1\n\t" + "subne %0, %0, #2\t" + : "=r" (b) : "0" (b)); + return b; +} + +/* + * This is the ECC routine used in hardware, according to the manual. + * HW claims to make the calculation but not the correction; so we must + * recalculate the bytes for a comparison. + */ +static int ecc512(const unsigned char *data, unsigned char *ecc) +{ + int gpar = 0; + int i, val, par; + int pbits = 0; /* P8, P16, ... P2048 */ + int pprime = 0; /* P8', P16', ... P2048' */ + int lowbits; /* P1, P2, P4 and primes */ + + for (i = 0; i < 512; i++) { + par = parity((val = data[i])); + gpar ^= val; + pbits ^= (i & par); + } + /* + * Ok, now gpar is global parity (xor of all bytes) + * pbits are all the parity bits (non-prime ones) + */ + par = parity(gpar); + pprime = pbits ^ par; + /* Put low bits in the right position for ecc[2] (bits 7..2) */ + lowbits = 0 + | (parity(gpar & 0xf0) & 0x80) /* P4 */ + | (parity(gpar & 0x0f) & 0x40) /* P4' */ + | (parity(gpar & 0xcc) & 0x20) /* P2 */ + | (parity(gpar & 0x33) & 0x10) /* P2' */ + | (parity(gpar & 0xaa) & 0x08) /* P1 */ + | (parity(gpar & 0x55) & 0x04); /* P1' */ + + ecc[2] = ~(lowbits | ((pbits & 0x100) >> 7) | ((pprime & 0x100) >> 8)); + /* now intermix bits for ecc[1] (P1024..P128') and ecc[0] (P64..P8') */ + ecc[1] = ~( (pbits & 0x80) >> 0 | ((pprime & 0x80) >> 1) + | ((pbits & 0x40) >> 1) | ((pprime & 0x40) >> 2) + | ((pbits & 0x20) >> 2) | ((pprime & 0x20) >> 3) + | ((pbits & 0x10) >> 3) | ((pprime & 0x10) >> 4)); + + ecc[0] = ~( (pbits & 0x8) << 4 | ((pprime & 0x8) << 3) + | ((pbits & 0x4) << 3) | ((pprime & 0x4) << 2) + | ((pbits & 0x2) << 2) | ((pprime & 0x2) << 1) + | ((pbits & 0x1) << 1) | ((pprime & 0x1) << 0)); + return 0; +} + +/* This is the method in the chip->ecc field */ +static int nomadik_ecc_calculate(struct mtd_info *mtd, const uint8_t *dat, + uint8_t *ecc_code) +{ + return ecc512(dat, ecc_code); +} + +static int nomadik_ecc_correct(struct mtd_info *mtd, uint8_t *dat, + uint8_t *r_ecc, uint8_t *c_ecc) +{ + struct nand_chip *chip = mtd->priv; + uint32_t r, c, d, diff; /*read, calculated, xor of them */ + + if (!memcmp(r_ecc, c_ecc, chip->ecc.bytes)) + return 0; + + /* Reorder the bytes into ascending-order 24 bits -- see manual */ + r = r_ecc[2] << 22 | r_ecc[1] << 14 | r_ecc[0] << 6 | r_ecc[2] >> 2; + c = c_ecc[2] << 22 | c_ecc[1] << 14 | c_ecc[0] << 6 | c_ecc[2] >> 2; + diff = (r ^ c) & ((1<<24)-1); /* use 24 bits only */ + + /* If 12 bits are different, one per pair, it's correctable */ + if (((diff | (diff>>1)) & 0x555555) == 0x555555) { + int bit = ((diff & 2) >> 1) + | ((diff & 0x8) >> 2) | ((diff & 0x20) >> 3); + int byte; + + d = diff >> 6; /* remove bit-order info */ + byte = ((d & 2) >> 1) + | ((d & 0x8) >> 2) | ((d & 0x20) >> 3) + | ((d & 0x80) >> 4) | ((d & 0x200) >> 5) + | ((d & 0x800) >> 6) | ((d & 0x2000) >> 7) + | ((d & 0x8000) >> 8) | ((d & 0x20000) >> 9); + /* correct the single bit */ + dat[byte] ^= 1<<bit; + return 0; + } + /* If 1 bit only differs, it's one bit error in ECC, ignore */ + if ((diff ^ (1 << (ffs(diff) - 1))) == 0) + return 0; + /* Otherwise, uncorrectable */ + return -1; +} + +static void nomadik_ecc_hwctl(struct mtd_info *mtd, int mode) +{ /* mandatory in the structure but not used here */ } + + +/* This is the layout used by older installations, we keep compatible */ +struct nand_ecclayout nomadik_ecc_layout = { + .eccbytes = 3 * 4, + .eccpos = { /* each subpage has 16 bytes: pos 2,3,4 hosts ECC */ + 0x02, 0x03, 0x04, + 0x12, 0x13, 0x14, + 0x22, 0x23, 0x24, + 0x32, 0x33, 0x34}, + .oobfree = { {0x08, 0x08}, {0x18, 0x08}, {0x28, 0x08}, {0x38, 0x08} }, +}; + +#define MASK_ALE (1 << 24) /* our ALE is AD21 */ +#define MASK_CLE (1 << 23) /* our CLE is AD22 */ + +/* This is copied from the AT91SAM9 devices (Stelian Pop, Lead Tech Design) */ +static void nomadik_nand_hwcontrol(struct mtd_info *mtd, + int cmd, unsigned int ctrl) +{ + struct nand_chip *this = mtd->priv; + u32 pcr0 = readl(REG_FSMC_PCR0); + + if (ctrl & NAND_CTRL_CHANGE) { + ulong IO_ADDR_W = (ulong) this->IO_ADDR_W; + IO_ADDR_W &= ~(MASK_ALE | MASK_CLE); + + if (ctrl & NAND_CLE) + IO_ADDR_W |= MASK_CLE; + if (ctrl & NAND_ALE) + IO_ADDR_W |= MASK_ALE; + + if (ctrl & NAND_NCE) + writel(pcr0 | 0x4, REG_FSMC_PCR0); + else + writel(pcr0 & ~0x4, REG_FSMC_PCR0); + + this->IO_ADDR_W = (void *) IO_ADDR_W; + this->IO_ADDR_R = (void *) IO_ADDR_W; + } + + if (cmd != NAND_CMD_NONE) + writeb(cmd, this->IO_ADDR_W); +} + +/* Returns 1 when ready; upper layers timeout at 20ms with timer routines */ +static int nomadik_nand_ready(struct mtd_info *mtd) +{ + return 1; /* The ready bit is handled in hardware */ +} + +/* Copy a buffer 32bits at a time: faster than defualt method which is 8bit */ +static void nomadik_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + int i; + struct nand_chip *chip = mtd->priv; + u32 *p = (u32 *) buf; + + len >>= 2; + writel(0, REG_FSMC_ECCR0); + for (i = 0; i < len; i++) + p[i] = readl(chip->IO_ADDR_R); +} + +int board_nand_init(struct nand_chip *chip) +{ + /* Set up the FSMC_PCR0 for nand access*/ + writel(0x0000004a, REG_FSMC_PCR0); + /* Set up FSMC_PMEM0, FSMC_PATT0 with timing data for access */ + writel(0x00020401, REG_FSMC_PMEM0); + writel(0x00020404, REG_FSMC_PATT0); + + chip->options = NAND_COPYBACK | NAND_CACHEPRG | NAND_NO_PADDING; + chip->cmd_ctrl = nomadik_nand_hwcontrol; + chip->dev_ready = nomadik_nand_ready; + /* The chip allows 32bit reads, so avoid the default 8bit copy */ + chip->read_buf = nomadik_nand_read_buf; + + /* ECC: follow the hardware-defined rulse, but do it in sw */ + chip->ecc.mode = NAND_ECC_HW; + chip->ecc.bytes = 3; + chip->ecc.size = 512; + chip->ecc.layout = &nomadik_ecc_layout; + chip->ecc.calculate = nomadik_ecc_calculate; + chip->ecc.hwctl = nomadik_ecc_hwctl; + chip->ecc.correct = nomadik_ecc_correct; + + return 0; +} diff --git a/drivers/mtd/nand/omap_gpmc.c b/drivers/mtd/nand/omap_gpmc.c new file mode 100644 index 000000000..5f8ed3984 --- /dev/null +++ b/drivers/mtd/nand/omap_gpmc.c @@ -0,0 +1,353 @@ +/* + * (C) Copyright 2004-2008 Texas Instruments, <www.ti.com> + * Rohit Choraria <rohitkc@ti.com> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <asm/io.h> +#include <asm/errno.h> +#include <asm/arch/mem.h> +#include <asm/arch/omap_gpmc.h> +#include <linux/mtd/nand_ecc.h> +#include <nand.h> + +static uint8_t cs; +static gpmc_t *gpmc_base = (gpmc_t *)GPMC_BASE; +static gpmc_csx_t *gpmc_cs_base; +static struct nand_ecclayout hw_nand_oob = GPMC_NAND_HW_ECC_LAYOUT; + +/* + * omap_nand_hwcontrol - Set the address pointers corretly for the + * following address/data/command operation + */ +static void omap_nand_hwcontrol(struct mtd_info *mtd, int32_t cmd, + uint32_t ctrl) +{ + register struct nand_chip *this = mtd->priv; + + /* + * Point the IO_ADDR to DATA and ADDRESS registers instead + * of chip address + */ + switch (ctrl) { + case NAND_CTRL_CHANGE | NAND_CTRL_CLE: + this->IO_ADDR_W = (void __iomem *)&gpmc_cs_base->nand_cmd; + break; + case NAND_CTRL_CHANGE | NAND_CTRL_ALE: + this->IO_ADDR_W = (void __iomem *)&gpmc_cs_base->nand_adr; + break; + case NAND_CTRL_CHANGE | NAND_NCE: + this->IO_ADDR_W = (void __iomem *)&gpmc_cs_base->nand_dat; + break; + } + + if (cmd != NAND_CMD_NONE) + writeb(cmd, this->IO_ADDR_W); +} + +/* + * omap_hwecc_init - Initialize the Hardware ECC for NAND flash in + * GPMC controller + * @mtd: MTD device structure + * + */ +static void omap_hwecc_init(struct nand_chip *chip) +{ + /* + * Init ECC Control Register + * Clear all ECC | Enable Reg1 + */ + writel(ECCCLEAR | ECCRESULTREG1, &gpmc_base->ecc_control); + writel(ECCSIZE1 | ECCSIZE0 | ECCSIZE0SEL, &gpmc_base->ecc_size_config); +} + +/* + * gen_true_ecc - This function will generate true ECC value, which + * can be used when correcting data read from NAND flash memory core + * + * @ecc_buf: buffer to store ecc code + * + * @return: re-formatted ECC value + */ +static uint32_t gen_true_ecc(uint8_t *ecc_buf) +{ + return ecc_buf[0] | (ecc_buf[1] << 16) | ((ecc_buf[2] & 0xF0) << 20) | + ((ecc_buf[2] & 0x0F) << 8); +} + +/* + * omap_correct_data - Compares the ecc read from nand spare area with ECC + * registers values and corrects one bit error if it has occured + * Further details can be had from OMAP TRM and the following selected links: + * http://en.wikipedia.org/wiki/Hamming_code + * http://www.cs.utexas.edu/users/plaxton/c/337/05f/slides/ErrorCorrection-4.pdf + * + * @mtd: MTD device structure + * @dat: page data + * @read_ecc: ecc read from nand flash + * @calc_ecc: ecc read from ECC registers + * + * @return 0 if data is OK or corrected, else returns -1 + */ +static int omap_correct_data(struct mtd_info *mtd, uint8_t *dat, + uint8_t *read_ecc, uint8_t *calc_ecc) +{ + uint32_t orig_ecc, new_ecc, res, hm; + uint16_t parity_bits, byte; + uint8_t bit; + + /* Regenerate the orginal ECC */ + orig_ecc = gen_true_ecc(read_ecc); + new_ecc = gen_true_ecc(calc_ecc); + /* Get the XOR of real ecc */ + res = orig_ecc ^ new_ecc; + if (res) { + /* Get the hamming width */ + hm = hweight32(res); + /* Single bit errors can be corrected! */ + if (hm == 12) { + /* Correctable data! */ + parity_bits = res >> 16; + bit = (parity_bits & 0x7); + byte = (parity_bits >> 3) & 0x1FF; + /* Flip the bit to correct */ + dat[byte] ^= (0x1 << bit); + } else if (hm == 1) { + printf("Error: Ecc is wrong\n"); + /* ECC itself is corrupted */ + return 2; + } else { + /* + * hm distance != parity pairs OR one, could mean 2 bit + * error OR potentially be on a blank page.. + * orig_ecc: contains spare area data from nand flash. + * new_ecc: generated ecc while reading data area. + * Note: if the ecc = 0, all data bits from which it was + * generated are 0xFF. + * The 3 byte(24 bits) ecc is generated per 512byte + * chunk of a page. If orig_ecc(from spare area) + * is 0xFF && new_ecc(computed now from data area)=0x0, + * this means that data area is 0xFF and spare area is + * 0xFF. A sure sign of a erased page! + */ + if ((orig_ecc == 0x0FFF0FFF) && (new_ecc == 0x00000000)) + return 0; + printf("Error: Bad compare! failed\n"); + /* detected 2 bit error */ + return -1; + } + } + return 0; +} + +/* + * omap_calculate_ecc - Generate non-inverted ECC bytes. + * + * Using noninverted ECC can be considered ugly since writing a blank + * page ie. padding will clear the ECC bytes. This is no problem as + * long nobody is trying to write data on the seemingly unused page. + * Reading an erased page will produce an ECC mismatch between + * generated and read ECC bytes that has to be dealt with separately. + * E.g. if page is 0xFF (fresh erased), and if HW ECC engine within GPMC + * is used, the result of read will be 0x0 while the ECC offsets of the + * spare area will be 0xFF which will result in an ECC mismatch. + * @mtd: MTD structure + * @dat: unused + * @ecc_code: ecc_code buffer + */ +static int omap_calculate_ecc(struct mtd_info *mtd, const uint8_t *dat, + uint8_t *ecc_code) +{ + u_int32_t val; + + /* Start Reading from HW ECC1_Result = 0x200 */ + val = readl(&gpmc_base->ecc1_result); + + ecc_code[0] = val & 0xFF; + ecc_code[1] = (val >> 16) & 0xFF; + ecc_code[2] = ((val >> 8) & 0x0F) | ((val >> 20) & 0xF0); + + /* + * Stop reading anymore ECC vals and clear old results + * enable will be called if more reads are required + */ + writel(0x000, &gpmc_base->ecc_config); + + return 0; +} + +/* + * omap_enable_ecc - This function enables the hardware ecc functionality + * @mtd: MTD device structure + * @mode: Read/Write mode + */ +static void omap_enable_hwecc(struct mtd_info *mtd, int32_t mode) +{ + struct nand_chip *chip = mtd->priv; + uint32_t val, dev_width = (chip->options & NAND_BUSWIDTH_16) >> 1; + + switch (mode) { + case NAND_ECC_READ: + case NAND_ECC_WRITE: + /* Clear the ecc result registers, select ecc reg as 1 */ + writel(ECCCLEAR | ECCRESULTREG1, &gpmc_base->ecc_control); + + /* + * Size 0 = 0xFF, Size1 is 0xFF - both are 512 bytes + * tell all regs to generate size0 sized regs + * we just have a single ECC engine for all CS + */ + writel(ECCSIZE1 | ECCSIZE0 | ECCSIZE0SEL, + &gpmc_base->ecc_size_config); + val = (dev_width << 7) | (cs << 1) | (0x1); + writel(val, &gpmc_base->ecc_config); + break; + default: + printf("Error: Unrecognized Mode[%d]!\n", mode); + break; + } +} + +/* + * omap_nand_switch_ecc - switch the ECC operation b/w h/w ecc and s/w ecc. + * The default is to come up on s/w ecc + * + * @hardware - 1 -switch to h/w ecc, 0 - s/w ecc + * + */ +void omap_nand_switch_ecc(int32_t hardware) +{ + struct nand_chip *nand; + struct mtd_info *mtd; + + if (nand_curr_device < 0 || + nand_curr_device >= CONFIG_SYS_MAX_NAND_DEVICE || + !nand_info[nand_curr_device].name) { + printf("Error: Can't switch ecc, no devices available\n"); + return; + } + + mtd = &nand_info[nand_curr_device]; + nand = mtd->priv; + + nand->options |= NAND_OWN_BUFFERS; + + /* Reset ecc interface */ + nand->ecc.read_page = NULL; + nand->ecc.write_page = NULL; + nand->ecc.read_oob = NULL; + nand->ecc.write_oob = NULL; + nand->ecc.hwctl = NULL; + nand->ecc.correct = NULL; + nand->ecc.calculate = NULL; + + /* Setup the ecc configurations again */ + if (hardware) { + nand->ecc.mode = NAND_ECC_HW; + nand->ecc.layout = &hw_nand_oob; + nand->ecc.size = 512; + nand->ecc.bytes = 3; + nand->ecc.hwctl = omap_enable_hwecc; + nand->ecc.correct = omap_correct_data; + nand->ecc.calculate = omap_calculate_ecc; + omap_hwecc_init(nand); + printf("HW ECC selected\n"); + } else { + nand->ecc.mode = NAND_ECC_SOFT; + /* Use mtd default settings */ + nand->ecc.layout = NULL; + printf("SW ECC selected\n"); + } + + /* Update NAND handling after ECC mode switch */ + nand_scan_tail(mtd); + + nand->options &= ~NAND_OWN_BUFFERS; +} + +/* + * Board-specific NAND initialization. The following members of the + * argument are board-specific: + * - IO_ADDR_R: address to read the 8 I/O lines of the flash device + * - IO_ADDR_W: address to write the 8 I/O lines of the flash device + * - cmd_ctrl: hardwarespecific function for accesing control-lines + * - waitfunc: hardwarespecific function for accesing device ready/busy line + * - ecc.hwctl: function to enable (reset) hardware ecc generator + * - ecc.mode: mode of ecc, see defines + * - chip_delay: chip dependent delay for transfering data from array to + * read regs (tR) + * - options: various chip options. They can partly be set to inform + * nand_scan about special functionality. See the defines for further + * explanation + */ +int board_nand_init(struct nand_chip *nand) +{ + int32_t gpmc_config = 0; + cs = 0; + + /* + * xloader/Uboot's gpmc configuration would have configured GPMC for + * nand type of memory. The following logic scans and latches on to the + * first CS with NAND type memory. + * TBD: need to make this logic generic to handle multiple CS NAND + * devices. + */ + while (cs < GPMC_MAX_CS) { + /* + * Each GPMC set for a single CS is at offset 0x30 + * - already remapped for us + */ + gpmc_cs_base = (gpmc_csx_t *)(GPMC_CONFIG_CS0_BASE + + (cs * GPMC_CONFIG_WIDTH)); + /* Check if NAND type is set */ + if ((readl(&gpmc_cs_base->config1) & 0xC00) == + 0x800) { + /* Found it!! */ + break; + } + cs++; + } + if (cs >= GPMC_MAX_CS) { + printf("NAND: Unable to find NAND settings in " + "GPMC Configuration - quitting\n"); + return -ENODEV; + } + + gpmc_config = readl(&gpmc_base->config); + /* Disable Write protect */ + gpmc_config |= 0x10; + writel(gpmc_config, &gpmc_base->config); + + nand->IO_ADDR_R = (void __iomem *)&gpmc_cs_base->nand_dat; + nand->IO_ADDR_W = (void __iomem *)&gpmc_cs_base->nand_cmd; + + nand->cmd_ctrl = omap_nand_hwcontrol; + nand->options = NAND_NO_PADDING | NAND_CACHEPRG | NAND_NO_AUTOINCR; + /* If we are 16 bit dev, our gpmc config tells us that */ + if ((readl(gpmc_cs_base) & 0x3000) == 0x1000) + nand->options |= NAND_BUSWIDTH_16; + + nand->chip_delay = 100; + /* Default ECC mode */ + nand->ecc.mode = NAND_ECC_SOFT; + + return 0; +} diff --git a/drivers/mtd/nand_legacy/nand_legacy.c b/drivers/mtd/nand_legacy/nand_legacy.c index 407e901a3..441780ac2 100644 --- a/drivers/mtd/nand_legacy/nand_legacy.c +++ b/drivers/mtd/nand_legacy/nand_legacy.c @@ -457,7 +457,7 @@ static void NanD_ScanChips(struct nand_chip *nand) { int floor, chip; int numchips[NAND_MAX_FLOORS]; - int maxchips = NAND_MAX_CHIPS; + int maxchips = CONFIG_SYS_NAND_MAX_CHIPS; int ret = 1; nand->numchips = 0; diff --git a/drivers/mtd/onenand/onenand_base.c b/drivers/mtd/onenand/onenand_base.c index 9b7bf3aa3..d482437a4 100644 --- a/drivers/mtd/onenand/onenand_base.c +++ b/drivers/mtd/onenand/onenand_base.c @@ -36,6 +36,35 @@ static inline void *memcpy_16(void *dst, const void *src, unsigned int len) return ret; } +/** + * onenand_oob_64 - oob info for large (2KB) page + */ +static struct nand_ecclayout onenand_oob_64 = { + .eccbytes = 20, + .eccpos = { + 8, 9, 10, 11, 12, + 24, 25, 26, 27, 28, + 40, 41, 42, 43, 44, + 56, 57, 58, 59, 60, + }, + .oobfree = { + {2, 3}, {14, 2}, {18, 3}, {30, 2}, + {34, 3}, {46, 2}, {50, 3}, {62, 2} + } +}; + +/** + * onenand_oob_32 - oob info for middle (1KB) page + */ +static struct nand_ecclayout onenand_oob_32 = { + .eccbytes = 10, + .eccpos = { + 8, 9, 10, 11, 12, + 24, 25, 26, 27, 28, + }, + .oobfree = { {2, 3}, {14, 2}, {18, 3}, {30, 2} } +}; + static const unsigned char ffchars[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 16 */ @@ -78,20 +107,11 @@ static void onenand_writew(unsigned short value, void __iomem * addr) * * Setup Start Address 1 Register (F100h) */ -static int onenand_block_address(int device, int block) +static int onenand_block_address(struct onenand_chip *this, int block) { - if (device & ONENAND_DEVICE_IS_DDP) { - /* Device Flash Core select, NAND Flash Block Address */ - int dfs = 0, density, mask; - - density = device >> ONENAND_DEVICE_DENSITY_SHIFT; - mask = (1 << (density + 6)); - - if (block & mask) - dfs = 1; - - return (dfs << ONENAND_DDP_SHIFT) | (block & (mask - 1)); - } + /* Device Flash Core select, NAND Flash Block Address */ + if (block & this->density_mask) + return ONENAND_DDP_CHIP1 | (block ^ this->density_mask); return block; } @@ -104,22 +124,13 @@ static int onenand_block_address(int device, int block) * * Setup Start Address 2 Register (F101h) for DDP */ -static int onenand_bufferram_address(int device, int block) +static int onenand_bufferram_address(struct onenand_chip *this, int block) { - if (device & ONENAND_DEVICE_IS_DDP) { - /* Device BufferRAM Select */ - int dbs = 0, density, mask; - - density = device >> ONENAND_DEVICE_DENSITY_SHIFT; - mask = (1 << (density + 6)); - - if (block & mask) - dbs = 1; + /* Device BufferRAM Select */ + if (block & this->density_mask) + return ONENAND_DDP_CHIP1; - return (dbs << ONENAND_DDP_SHIFT); - } - - return 0; + return ONENAND_DDP_CHIP0; } /** @@ -169,6 +180,18 @@ static int onenand_buffer_address(int dataram1, int sectors, int count) } /** + * onenand_get_density - [DEFAULT] Get OneNAND density + * @param dev_id OneNAND device ID + * + * Get OneNAND density from device ID + */ +static inline int onenand_get_density(int dev_id) +{ + int density = dev_id >> ONENAND_DEVICE_DENSITY_SHIFT; + return (density & ONENAND_DEVICE_DENSITY_MASK); +} + +/** * onenand_command - [DEFAULT] Send command to OneNAND device * @param mtd MTD device structure * @param cmd the command to be sent @@ -192,6 +215,7 @@ static int onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, case ONENAND_CMD_UNLOCK: case ONENAND_CMD_LOCK: case ONENAND_CMD_LOCK_TIGHT: + case ONENAND_CMD_UNLOCK_ALL: block = -1; page = -1; break; @@ -212,7 +236,7 @@ static int onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, /* NOTE: The setting order of the registers is very important! */ if (cmd == ONENAND_CMD_BUFFERRAM) { /* Select DataRAM for DDP */ - value = onenand_bufferram_address(this->device_id, block); + value = onenand_bufferram_address(this, block); this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); @@ -224,9 +248,14 @@ static int onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, if (block != -1) { /* Write 'DFS, FBA' of Flash */ - value = onenand_block_address(this->device_id, block); + value = onenand_block_address(this, block); this->write_word(value, this->base + ONENAND_REG_START_ADDRESS1); + + /* Write 'DFS, FBA' of Flash */ + value = onenand_bufferram_address(this, block); + this->write_word(value, + this->base + ONENAND_REG_START_ADDRESS2); } if (page != -1) { @@ -252,15 +281,6 @@ static int onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, /* Write 'BSA, BSC' of DataRAM */ value = onenand_buffer_address(dataram, sectors, count); this->write_word(value, this->base + ONENAND_REG_START_BUFFER); - - if (readcmd) { - /* Select DataRAM for DDP */ - value = - onenand_bufferram_address(this->device_id, block); - this->write_word(value, - this->base + - ONENAND_REG_START_ADDRESS2); - } } /* Interrupt clear */ @@ -296,14 +316,11 @@ static int onenand_wait(struct mtd_info *mtd, int state) ctrl = this->read_word(this->base + ONENAND_REG_CTRL_STATUS); if (ctrl & ONENAND_CTRL_ERROR) { - MTDDEBUG (MTD_DEBUG_LEVEL0, - "onenand_wait: controller error = 0x%04x\n", ctrl); - return -EAGAIN; - } + printk("onenand_wait: controller error = 0x%04x\n", ctrl); + if (ctrl & ONENAND_CTRL_LOCK) + printk("onenand_wait: it's locked error = 0x%04x\n", + ctrl); - if (ctrl & ONENAND_CTRL_LOCK) { - MTDDEBUG (MTD_DEBUG_LEVEL0, - "onenand_wait: it's locked error = 0x%04x\n", ctrl); return -EIO; } @@ -351,7 +368,7 @@ static inline int onenand_bufferram_offset(struct mtd_info *mtd, int area) * * Read the BufferRAM area */ -static int onenand_read_bufferram(struct mtd_info *mtd, int area, +static int onenand_read_bufferram(struct mtd_info *mtd, loff_t addr, int area, unsigned char *buffer, int offset, size_t count) { @@ -376,7 +393,7 @@ static int onenand_read_bufferram(struct mtd_info *mtd, int area, * * Read the BufferRAM area with Sync. Burst Mode */ -static int onenand_sync_read_bufferram(struct mtd_info *mtd, int area, +static int onenand_sync_read_bufferram(struct mtd_info *mtd, loff_t addr, int area, unsigned char *buffer, int offset, size_t count) { @@ -405,7 +422,7 @@ static int onenand_sync_read_bufferram(struct mtd_info *mtd, int area, * * Write the BufferRAM area */ -static int onenand_write_bufferram(struct mtd_info *mtd, int area, +static int onenand_write_bufferram(struct mtd_info *mtd, loff_t addr, int area, const unsigned char *buffer, int offset, size_t count) { @@ -421,6 +438,30 @@ static int onenand_write_bufferram(struct mtd_info *mtd, int area, } /** + * onenand_get_2x_blockpage - [GENERIC] Get blockpage at 2x program mode + * @param mtd MTD data structure + * @param addr address to check + * @return blockpage address + * + * Get blockpage address at 2x program mode + */ +static int onenand_get_2x_blockpage(struct mtd_info *mtd, loff_t addr) +{ + struct onenand_chip *this = mtd->priv; + int blockpage, block, page; + + /* Calculate the even block number */ + block = (int) (addr >> this->erase_shift) & ~1; + /* Is it the odd plane? */ + if (addr & this->writesize) + block++; + page = (int) (addr >> (this->page_shift + 1)) & this->page_mask; + blockpage = (block << 7) | page; + + return blockpage; +} + +/** * onenand_check_bufferram - [GENERIC] Check BufferRAM information * @param mtd MTD data structure * @param addr address to check @@ -431,21 +472,39 @@ static int onenand_write_bufferram(struct mtd_info *mtd, int area, static int onenand_check_bufferram(struct mtd_info *mtd, loff_t addr) { struct onenand_chip *this = mtd->priv; - int block, page; - int i; + int blockpage, found = 0; + unsigned int i; - block = (int)(addr >> this->erase_shift); - page = (int)(addr >> this->page_shift); - page &= this->page_mask; +#ifdef CONFIG_S3C64XX + return 0; +#endif - i = ONENAND_CURRENT_BUFFERRAM(this); + if (ONENAND_IS_2PLANE(this)) + blockpage = onenand_get_2x_blockpage(mtd, addr); + else + blockpage = (int) (addr >> this->page_shift); /* Is there valid data? */ - if (this->bufferram[i].block == block && - this->bufferram[i].page == page && this->bufferram[i].valid) - return 1; + i = ONENAND_CURRENT_BUFFERRAM(this); + if (this->bufferram[i].blockpage == blockpage) + found = 1; + else { + /* Check another BufferRAM */ + i = ONENAND_NEXT_BUFFERRAM(this); + if (this->bufferram[i].blockpage == blockpage) { + ONENAND_SET_NEXT_BUFFERRAM(this); + found = 1; + } + } - return 0; + if (found && ONENAND_IS_DDP(this)) { + /* Select DataRAM for DDP */ + int block = (int) (addr >> this->erase_shift); + int value = onenand_bufferram_address(this, block); + this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); + } + + return found; } /** @@ -460,25 +519,25 @@ static int onenand_update_bufferram(struct mtd_info *mtd, loff_t addr, int valid) { struct onenand_chip *this = mtd->priv; - int block, page; - int i; + int blockpage; + unsigned int i; - block = (int)(addr >> this->erase_shift); - page = (int)(addr >> this->page_shift); - page &= this->page_mask; + if (ONENAND_IS_2PLANE(this)) + blockpage = onenand_get_2x_blockpage(mtd, addr); + else + blockpage = (int)(addr >> this->page_shift); - /* Invalidate BufferRAM */ - for (i = 0; i < MAX_BUFFERRAM; i++) { - if (this->bufferram[i].block == block && - this->bufferram[i].page == page) - this->bufferram[i].valid = 0; - } + /* Invalidate another BufferRAM */ + i = ONENAND_NEXT_BUFFERRAM(this); + if (this->bufferram[i].blockpage == blockpage) + this->bufferram[i].blockpage = -1; /* Update BufferRAM */ i = ONENAND_CURRENT_BUFFERRAM(this); - this->bufferram[i].block = block; - this->bufferram[i].page = page; - this->bufferram[i].valid = valid; + if (valid) + this->bufferram[i].blockpage = blockpage; + else + this->bufferram[i].blockpage = -1; return 0; } @@ -500,10 +559,10 @@ static void onenand_invalidate_bufferram(struct mtd_info *mtd, loff_t addr, /* Invalidate BufferRAM */ for (i = 0; i < MAX_BUFFERRAM; i++) { - loff_t buf_addr = this->bufferram[i].block << this->erase_shift; + loff_t buf_addr = this->bufferram[i].blockpage << this->page_shift; if (buf_addr >= addr && buf_addr < end_addr) - this->bufferram[i].valid = 0; + this->bufferram[i].blockpage = -1; } } @@ -556,7 +615,7 @@ static int onenand_transfer_auto_oob(struct mtd_info *mtd, uint8_t *buf, readend += free->offset - lastgap; lastgap = free->offset + free->length; } - this->read_bufferram(mtd, ONENAND_SPARERAM, oob_buf, 0, mtd->oobsize); + this->read_bufferram(mtd, 0, ONENAND_SPARERAM, oob_buf, 0, mtd->oobsize); free = this->ecclayout->oobfree; for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES && free->length; i++, free++) { int free_end = free->offset + free->length; @@ -594,9 +653,7 @@ static int onenand_read_ops_nolock(struct mtd_info *mtd, loff_t from, int ret = 0, boundary = 0; int writesize = this->writesize; - MTDDEBUG(MTD_DEBUG_LEVEL3, - "onenand_read_ops_nolock: from = 0x%08x, len = %i\n", - (unsigned int) from, (int) len); + MTDDEBUG(MTD_DEBUG_LEVEL3, "onenand_read_ops_nolock: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len); if (ops->mode == MTD_OOB_AUTO) oobsize = this->ecclayout->oobavail; @@ -620,6 +677,7 @@ static int onenand_read_ops_nolock(struct mtd_info *mtd, loff_t from, /* Do first load to bufferRAM */ if (read < len) { if (!onenand_check_bufferram(mtd, from)) { + this->main_buf = buf; this->command(mtd, ONENAND_CMD_READ, from, writesize); ret = this->wait(mtd, FL_READING); onenand_update_bufferram(mtd, from, !ret); @@ -637,6 +695,7 @@ static int onenand_read_ops_nolock(struct mtd_info *mtd, loff_t from, /* If there is more to load then start next load */ from += thislen; if (read + thislen < len) { + this->main_buf = buf + thislen; this->command(mtd, ONENAND_CMD_READ, from, writesize); /* * Chip boundary handling in DDP @@ -653,7 +712,7 @@ static int onenand_read_ops_nolock(struct mtd_info *mtd, loff_t from, } /* While load is going, read from last bufferRAM */ - this->read_bufferram(mtd, ONENAND_DATARAM, buf, column, thislen); + this->read_bufferram(mtd, from - thislen, ONENAND_DATARAM, buf, column, thislen); /* Read oob area if needed */ if (oobbuf) { @@ -663,7 +722,7 @@ static int onenand_read_ops_nolock(struct mtd_info *mtd, loff_t from, if (ops->mode == MTD_OOB_AUTO) onenand_transfer_auto_oob(mtd, oobbuf, oobcolumn, thisooblen); else - this->read_bufferram(mtd, ONENAND_SPARERAM, oobbuf, oobcolumn, thisooblen); + this->read_bufferram(mtd, 0, ONENAND_SPARERAM, oobbuf, oobcolumn, thisooblen); oobread += thisooblen; oobbuf += thisooblen; oobcolumn = 0; @@ -726,9 +785,7 @@ static int onenand_read_oob_nolock(struct mtd_info *mtd, loff_t from, from += ops->ooboffs; - MTDDEBUG(MTD_DEBUG_LEVEL3, - "onenand_read_oob_nolock: from = 0x%08x, len = %i\n", - (unsigned int) from, (int) len); + MTDDEBUG(MTD_DEBUG_LEVEL3, "onenand_read_oob_nolock: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len); /* Initialize return length value */ ops->oobretlen = 0; @@ -759,6 +816,7 @@ static int onenand_read_oob_nolock(struct mtd_info *mtd, loff_t from, thislen = oobsize - column; thislen = min_t(int, thislen, len); + this->spare_buf = buf; this->command(mtd, ONENAND_CMD_READOOB, from, mtd->oobsize); onenand_update_bufferram(mtd, from, 0); @@ -772,7 +830,7 @@ static int onenand_read_oob_nolock(struct mtd_info *mtd, loff_t from, if (mode == MTD_OOB_AUTO) onenand_transfer_auto_oob(mtd, buf, column, thislen); else - this->read_bufferram(mtd, ONENAND_SPARERAM, buf, column, thislen); + this->read_bufferram(mtd, 0, ONENAND_SPARERAM, buf, column, thislen); read += thislen; @@ -886,12 +944,6 @@ static int onenand_bbt_wait(struct mtd_info *mtd, int state) interrupt = this->read_word(this->base + ONENAND_REG_INTERRUPT); ctrl = this->read_word(this->base + ONENAND_REG_CTRL_STATUS); - /* Initial bad block case: 0x2400 or 0x0400 */ - if (ctrl & ONENAND_CTRL_ERROR) { - printk(KERN_DEBUG "onenand_bbt_wait: controller error = 0x%04x\n", ctrl); - return ONENAND_BBT_READ_ERROR; - } - if (interrupt & ONENAND_INT_READ) { int ecc = this->read_word(this->base + ONENAND_REG_ECC_STATUS); if (ecc & ONENAND_ECC_2BIT_ALL) @@ -902,6 +954,12 @@ static int onenand_bbt_wait(struct mtd_info *mtd, int state) return ONENAND_BBT_READ_FATAL_ERROR; } + /* Initial bad block case: 0x2400 or 0x0400 */ + if (ctrl & ONENAND_CTRL_ERROR) { + printk(KERN_DEBUG "onenand_bbt_wait: controller error = 0x%04x\n", ctrl); + return ONENAND_BBT_READ_ERROR; + } + return 0; } @@ -922,9 +980,7 @@ int onenand_bbt_read_oob(struct mtd_info *mtd, loff_t from, size_t len = ops->ooblen; u_char *buf = ops->oobbuf; - MTDDEBUG(MTD_DEBUG_LEVEL3, - "onenand_bbt_read_oob: from = 0x%08x, len = %zi\n", - (unsigned int) from, len); + MTDDEBUG(MTD_DEBUG_LEVEL3, "onenand_bbt_read_oob: from = 0x%08x, len = %zi\n", (unsigned int) from, len); /* Initialize return value */ ops->oobretlen = 0; @@ -945,15 +1001,16 @@ int onenand_bbt_read_oob(struct mtd_info *mtd, loff_t from, thislen = mtd->oobsize - column; thislen = min_t(int, thislen, len); + this->spare_buf = buf; this->command(mtd, ONENAND_CMD_READOOB, from, mtd->oobsize); onenand_update_bufferram(mtd, from, 0); - ret = onenand_bbt_wait(mtd, FL_READING); + ret = this->bbt_wait(mtd, FL_READING); if (ret) break; - this->read_bufferram(mtd, ONENAND_SPARERAM, buf, column, thislen); + this->read_spareram(mtd, 0, ONENAND_SPARERAM, buf, column, thislen); read += thislen; if (read == len) break; @@ -995,7 +1052,7 @@ static int onenand_verify_oob(struct mtd_info *mtd, const u_char *buf, loff_t to if (status) return status; - this->read_bufferram(mtd, ONENAND_SPARERAM, oob_buf, 0, mtd->oobsize); + this->read_bufferram(mtd, 0, ONENAND_SPARERAM, oob_buf, 0, mtd->oobsize); for (i = 0; i < mtd->oobsize; i++) if (buf[i] != 0xFF && buf[i] != oob_buf[i]) return -EBADMSG; @@ -1051,7 +1108,7 @@ static int onenand_verify(struct mtd_info *mtd, const u_char *buf, loff_t addr, #define onenand_verify_oob(...) (0) #endif -#define NOTALIGNED(x) ((x & (mtd->writesize - 1)) != 0) +#define NOTALIGNED(x) ((x & (this->subpagesize - 1)) != 0) /** * onenand_fill_auto_oob - [Internal] oob auto-placement transfer @@ -1115,9 +1172,7 @@ static int onenand_write_ops_nolock(struct mtd_info *mtd, loff_t to, u_char *oobbuf; int ret = 0; - MTDDEBUG(MTD_DEBUG_LEVEL3, - "onenand_write_ops_nolock: to = 0x%08x, len = %i\n", - (unsigned int) to, (int) len); + MTDDEBUG(MTD_DEBUG_LEVEL3, "onenand_write_ops_nolock: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len); /* Initialize retlen, in case of early exit */ ops->retlen = 0; @@ -1161,7 +1216,7 @@ static int onenand_write_ops_nolock(struct mtd_info *mtd, loff_t to, wbuf = this->page_buf; } - this->write_bufferram(mtd, ONENAND_DATARAM, wbuf, 0, mtd->writesize); + this->write_bufferram(mtd, to, ONENAND_DATARAM, wbuf, 0, mtd->writesize); if (oob) { oobbuf = this->oob_buf; @@ -1180,7 +1235,7 @@ static int onenand_write_ops_nolock(struct mtd_info *mtd, loff_t to, } else oobbuf = (u_char *) ffchars; - this->write_bufferram(mtd, ONENAND_SPARERAM, oobbuf, 0, mtd->oobsize); + this->write_bufferram(mtd, 0, ONENAND_SPARERAM, oobbuf, 0, mtd->oobsize); this->command(mtd, ONENAND_CMD_PROG, to, mtd->writesize); @@ -1244,9 +1299,7 @@ static int onenand_write_oob_nolock(struct mtd_info *mtd, loff_t to, to += ops->ooboffs; - MTDDEBUG(MTD_DEBUG_LEVEL3, - "onenand_write_oob_nolock: to = 0x%08x, len = %i\n", - (unsigned int) to, (int) len); + MTDDEBUG(MTD_DEBUG_LEVEL3, "onenand_write_oob_nolock: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len); /* Initialize retlen, in case of early exit */ ops->oobretlen = 0; @@ -1293,7 +1346,7 @@ static int onenand_write_oob_nolock(struct mtd_info *mtd, loff_t to, onenand_fill_auto_oob(mtd, oobbuf, buf, column, thislen); else memcpy(oobbuf + column, buf, thislen); - this->write_bufferram(mtd, ONENAND_SPARERAM, oobbuf, 0, mtd->oobsize); + this->write_bufferram(mtd, 0, ONENAND_SPARERAM, oobbuf, 0, mtd->oobsize); this->command(mtd, ONENAND_CMD_PROGOOB, to, mtd->oobsize); @@ -1466,7 +1519,14 @@ int onenand_erase(struct mtd_info *mtd, struct erase_info *instr) while (len) { - /* TODO Check badblock */ + /* Check if we have a bad block, we do not erase bad blocks */ + if (instr->priv == 0 && onenand_block_isbad_nolock(mtd, addr, 0)) { + printk(KERN_WARNING "onenand_erase: attempt to erase" + " a bad block at addr 0x%08x\n", + (unsigned int) addr); + instr->state = MTD_ERASE_FAILED; + goto erase_exit; + } this->command(mtd, ONENAND_CMD_ERASE, addr, block_size); @@ -1482,8 +1542,16 @@ int onenand_erase(struct mtd_info *mtd, struct erase_info *instr) MTDDEBUG (MTD_DEBUG_LEVEL0, "onenand_erase: " "Failed erase, block %d\n", (unsigned)(addr >> this->erase_shift)); + if (ret == -EPERM) + printk("onenand_erase: " + "Device is write protected!!!\n"); + else + printk("onenand_erase: " + "Failed erase, block %d\n", + (unsigned)(addr >> this->erase_shift)); instr->state = MTD_ERASE_FAILED; instr->fail_addr = addr; + goto erase_exit; } @@ -1493,7 +1561,7 @@ int onenand_erase(struct mtd_info *mtd, struct erase_info *instr) instr->state = MTD_ERASE_DONE; - erase_exit: +erase_exit: ret = instr->state == MTD_ERASE_DONE ? 0 : -EIO; /* Do call back function */ @@ -1545,6 +1613,37 @@ int onenand_block_isbad(struct mtd_info *mtd, loff_t ofs) } /** + * onenand_default_block_markbad - [DEFAULT] mark a block bad + * @param mtd MTD device structure + * @param ofs offset from device start + * + * This is the default implementation, which can be overridden by + * a hardware specific driver. + */ +static int onenand_default_block_markbad(struct mtd_info *mtd, loff_t ofs) +{ + struct onenand_chip *this = mtd->priv; + struct bbm_info *bbm = this->bbm; + u_char buf[2] = {0, 0}; + struct mtd_oob_ops ops = { + .mode = MTD_OOB_PLACE, + .ooblen = 2, + .oobbuf = buf, + .ooboffs = 0, + }; + int block; + + /* Get block number */ + block = ((int) ofs) >> bbm->bbt_erase_shift; + if (bbm->bbt) + bbm->bbt[block >> 2] |= 0x01 << ((block & 0x03) << 1); + + /* We write two bytes, so we dont have to mess with 16 bit access */ + ofs += mtd->oobsize + (bbm->badblockpos & ~0x01); + return onenand_write_oob_nolock(mtd, ofs, &ops); +} + +/** * onenand_block_markbad - [MTD Interface] Mark the block at the given offset as bad * @param mtd MTD device structure * @param ofs offset relative to mtd start @@ -1569,23 +1668,30 @@ int onenand_block_markbad(struct mtd_info *mtd, loff_t ofs) } /** - * onenand_unlock - [MTD Interface] Unlock block(s) - * @param mtd MTD device structure - * @param ofs offset relative to mtd start - * @param len number of bytes to unlock + * onenand_do_lock_cmd - [OneNAND Interface] Lock or unlock block(s) + * @param mtd MTD device structure + * @param ofs offset relative to mtd start + * @param len number of bytes to lock or unlock + * @param cmd lock or unlock command * - * Unlock one or more blocks + * Lock or unlock one or more blocks */ -int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) +static int onenand_do_lock_cmd(struct mtd_info *mtd, loff_t ofs, size_t len, int cmd) { struct onenand_chip *this = mtd->priv; int start, end, block, value, status; + int wp_status_mask; start = ofs >> this->erase_shift; end = len >> this->erase_shift; + if (cmd == ONENAND_CMD_LOCK) + wp_status_mask = ONENAND_WP_LS; + else + wp_status_mask = ONENAND_WP_US; + /* Continuous lock scheme */ - if (this->options & ONENAND_CONT_LOCK) { + if (this->options & ONENAND_HAS_CONT_LOCK) { /* Set start block address */ this->write_word(start, this->base + ONENAND_REG_START_BLOCK_ADDRESS); @@ -1593,7 +1699,7 @@ int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) this->write_word(end - 1, this->base + ONENAND_REG_END_BLOCK_ADDRESS); /* Write unlock command */ - this->command(mtd, ONENAND_CMD_UNLOCK, 0, 0); + this->command(mtd, cmd, 0, 0); /* There's no return value */ this->wait(mtd, FL_UNLOCKING); @@ -1612,7 +1718,14 @@ int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) } /* Block lock scheme */ - for (block = start; block < end; block++) { + for (block = start; block < start + end; block++) { + /* Set block address */ + value = onenand_block_address(this, block); + this->write_word(value, this->base + ONENAND_REG_START_ADDRESS1); + /* Select DataRAM for DDP */ + value = onenand_bufferram_address(this, block); + this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); + /* Set start block address */ this->write_word(block, this->base + ONENAND_REG_START_BLOCK_ADDRESS); @@ -1627,11 +1740,6 @@ int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) & ONENAND_CTRL_ONGO) continue; - /* Set block address for read block status */ - value = onenand_block_address(this->device_id, block); - this->write_word(value, - this->base + ONENAND_REG_START_ADDRESS1); - /* Check lock status */ status = this->read_word(this->base + ONENAND_REG_WP_STATUS); if (!(status & ONENAND_WP_US)) @@ -1642,32 +1750,199 @@ int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) return 0; } +#ifdef ONENAND_LINUX +/** + * onenand_lock - [MTD Interface] Lock block(s) + * @param mtd MTD device structure + * @param ofs offset relative to mtd start + * @param len number of bytes to unlock + * + * Lock one or more blocks + */ +static int onenand_lock(struct mtd_info *mtd, loff_t ofs, size_t len) +{ + int ret; + + onenand_get_device(mtd, FL_LOCKING); + ret = onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_LOCK); + onenand_release_device(mtd); + return ret; +} + +/** + * onenand_unlock - [MTD Interface] Unlock block(s) + * @param mtd MTD device structure + * @param ofs offset relative to mtd start + * @param len number of bytes to unlock + * + * Unlock one or more blocks + */ +static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) +{ + int ret; + + onenand_get_device(mtd, FL_LOCKING); + ret = onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_UNLOCK); + onenand_release_device(mtd); + return ret; +} +#endif + +/** + * onenand_check_lock_status - [OneNAND Interface] Check lock status + * @param this onenand chip data structure + * + * Check lock status + */ +static int onenand_check_lock_status(struct onenand_chip *this) +{ + unsigned int value, block, status; + unsigned int end; + + end = this->chipsize >> this->erase_shift; + for (block = 0; block < end; block++) { + /* Set block address */ + value = onenand_block_address(this, block); + this->write_word(value, this->base + ONENAND_REG_START_ADDRESS1); + /* Select DataRAM for DDP */ + value = onenand_bufferram_address(this, block); + this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); + /* Set start block address */ + this->write_word(block, this->base + ONENAND_REG_START_BLOCK_ADDRESS); + + /* Check lock status */ + status = this->read_word(this->base + ONENAND_REG_WP_STATUS); + if (!(status & ONENAND_WP_US)) { + printk(KERN_ERR "block = %d, wp status = 0x%x\n", block, status); + return 0; + } + } + + return 1; +} + +/** + * onenand_unlock_all - [OneNAND Interface] unlock all blocks + * @param mtd MTD device structure + * + * Unlock all blocks + */ +static void onenand_unlock_all(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + loff_t ofs = 0; + size_t len = this->chipsize; + + if (this->options & ONENAND_HAS_UNLOCK_ALL) { + /* Set start block address */ + this->write_word(0, this->base + ONENAND_REG_START_BLOCK_ADDRESS); + /* Write unlock command */ + this->command(mtd, ONENAND_CMD_UNLOCK_ALL, 0, 0); + + /* There's no return value */ + this->wait(mtd, FL_LOCKING); + + /* Sanity check */ + while (this->read_word(this->base + ONENAND_REG_CTRL_STATUS) + & ONENAND_CTRL_ONGO) + continue; + + return; + + /* Check lock status */ + if (onenand_check_lock_status(this)) + return; + + /* Workaround for all block unlock in DDP */ + if (ONENAND_IS_DDP(this)) { + /* All blocks on another chip */ + ofs = this->chipsize >> 1; + len = this->chipsize >> 1; + } + } + + onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_UNLOCK); +} + + +/** + * onenand_check_features - Check and set OneNAND features + * @param mtd MTD data structure + * + * Check and set OneNAND features + * - lock scheme + * - two plane + */ +static void onenand_check_features(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + unsigned int density, process; + + /* Lock scheme depends on density and process */ + density = onenand_get_density(this->device_id); + process = this->version_id >> ONENAND_VERSION_PROCESS_SHIFT; + + /* Lock scheme */ + switch (density) { + case ONENAND_DEVICE_DENSITY_4Gb: + this->options |= ONENAND_HAS_2PLANE; + + case ONENAND_DEVICE_DENSITY_2Gb: + /* 2Gb DDP don't have 2 plane */ + if (!ONENAND_IS_DDP(this)) + this->options |= ONENAND_HAS_2PLANE; + this->options |= ONENAND_HAS_UNLOCK_ALL; + + case ONENAND_DEVICE_DENSITY_1Gb: + /* A-Die has all block unlock */ + if (process) + this->options |= ONENAND_HAS_UNLOCK_ALL; + break; + + default: + /* Some OneNAND has continuous lock scheme */ + if (!process) + this->options |= ONENAND_HAS_CONT_LOCK; + break; + } + + if (this->options & ONENAND_HAS_CONT_LOCK) + printk(KERN_DEBUG "Lock scheme is Continuous Lock\n"); + if (this->options & ONENAND_HAS_UNLOCK_ALL) + printk(KERN_DEBUG "Chip support all block unlock\n"); + if (this->options & ONENAND_HAS_2PLANE) + printk(KERN_DEBUG "Chip has 2 plane\n"); +} + /** * onenand_print_device_info - Print device ID * @param device device ID * * Print device ID */ -char * onenand_print_device_info(int device) +char *onenand_print_device_info(int device, int version) { int vcc, demuxed, ddp, density; char *dev_info = malloc(80); + char *p = dev_info; vcc = device & ONENAND_DEVICE_VCC_MASK; demuxed = device & ONENAND_DEVICE_IS_DEMUX; ddp = device & ONENAND_DEVICE_IS_DDP; density = device >> ONENAND_DEVICE_DENSITY_SHIFT; - sprintf(dev_info, "%sOneNAND%s %dMB %sV 16-bit (0x%02x)", + p += sprintf(dev_info, "%sOneNAND%s %dMB %sV 16-bit (0x%02x)", demuxed ? "" : "Muxed ", ddp ? "(DDP)" : "", (16 << density), vcc ? "2.65/3.3" : "1.8", device); + sprintf(p, "\nOneNAND version = 0x%04x", version); + printk("%s\n", dev_info); + return dev_info; } static const struct onenand_manufacturers onenand_manuf_ids[] = { {ONENAND_MFR_SAMSUNG, "Samsung"}, - {ONENAND_MFR_UNKNOWN, "Unknown"} }; /** @@ -1678,19 +1953,24 @@ static const struct onenand_manufacturers onenand_manuf_ids[] = { */ static int onenand_check_maf(int manuf) { + int size = ARRAY_SIZE(onenand_manuf_ids); + char *name; int i; - for (i = 0; onenand_manuf_ids[i].id; i++) { + for (i = 0; size; i++) if (manuf == onenand_manuf_ids[i].id) break; - } + + if (i < size) + name = onenand_manuf_ids[i].name; + else + name = "Unknown"; #ifdef ONENAND_DEBUG - printk(KERN_DEBUG "OneNAND Manufacturer: %s (0x%0x)\n", - onenand_manuf_ids[i].name, manuf); + printk(KERN_DEBUG "OneNAND Manufacturer: %s (0x%0x)\n", name, manuf); #endif - return (i != ONENAND_MFR_UNKNOWN); + return i == size; } /** @@ -1703,9 +1983,14 @@ static int onenand_check_maf(int manuf) static int onenand_probe(struct mtd_info *mtd) { struct onenand_chip *this = mtd->priv; - int bram_maf_id, bram_dev_id, maf_id, dev_id; - int version_id; + int bram_maf_id, bram_dev_id, maf_id, dev_id, ver_id; int density; + int syscfg; + + /* Save system configuration 1 */ + syscfg = this->read_word(this->base + ONENAND_REG_SYS_CFG1); + /* Clear Sync. Burst Read mode to read BootRAM */ + this->write_word((syscfg & ~ONENAND_SYS_CFG1_SYNC_READ), this->base + ONENAND_REG_SYS_CFG1); /* Send the command for reading device ID from BootRAM */ this->write_word(ONENAND_CMD_READID, this->base + ONENAND_BOOTRAM); @@ -1714,19 +1999,23 @@ static int onenand_probe(struct mtd_info *mtd) bram_maf_id = this->read_word(this->base + ONENAND_BOOTRAM + 0x0); bram_dev_id = this->read_word(this->base + ONENAND_BOOTRAM + 0x2); - /* Check manufacturer ID */ - if (onenand_check_maf(bram_maf_id)) - return -ENXIO; - /* Reset OneNAND to read default register values */ this->write_word(ONENAND_CMD_RESET, this->base + ONENAND_BOOTRAM); /* Wait reset */ this->wait(mtd, FL_RESETING); + /* Restore system configuration 1 */ + this->write_word(syscfg, this->base + ONENAND_REG_SYS_CFG1); + + /* Check manufacturer ID */ + if (onenand_check_maf(bram_maf_id)) + return -ENXIO; + /* Read manufacturer and device IDs from Register */ maf_id = this->read_word(this->base + ONENAND_REG_MANUFACTURER_ID); dev_id = this->read_word(this->base + ONENAND_REG_DEVICE_ID); + ver_id = this->read_word(this->base + ONENAND_REG_VERSION_ID); /* Check OneNAND device */ if (maf_id != bram_maf_id || dev_id != bram_dev_id) @@ -1739,11 +2028,17 @@ static int onenand_probe(struct mtd_info *mtd) } /* Flash device information */ - mtd->name = onenand_print_device_info(dev_id); + mtd->name = onenand_print_device_info(dev_id, ver_id); this->device_id = dev_id; + this->version_id = ver_id; - density = dev_id >> ONENAND_DEVICE_DENSITY_SHIFT; + density = onenand_get_density(dev_id); this->chipsize = (16 << density) << 20; + /* Set density mask. it is used for DDP */ + if (ONENAND_IS_DDP(this)) + this->density_mask = (1 << (density + 6)); + else + this->density_mask = 0; /* OneNAND page size & block size */ /* The data buffer size is equal to page size */ @@ -1764,18 +2059,8 @@ static int onenand_probe(struct mtd_info *mtd) mtd->size = this->chipsize; - /* Version ID */ - version_id = this->read_word(this->base + ONENAND_REG_VERSION_ID); -#ifdef ONENAND_DEBUG - printk(KERN_DEBUG "OneNAND version = 0x%04x\n", version_id); -#endif - - /* Lock scheme */ - if (density <= ONENAND_DEVICE_DENSITY_512Mb && - !(version_id >> ONENAND_VERSION_PROCESS_SHIFT)) { - printk(KERN_INFO "Lock scheme is Continues Lock\n"); - this->options |= ONENAND_CONT_LOCK; - } + /* Check OneNAND features */ + onenand_check_features(mtd); mtd->flags = MTD_CAP_NANDFLASH; mtd->erase = onenand_erase; @@ -1802,6 +2087,7 @@ static int onenand_probe(struct mtd_info *mtd) */ int onenand_scan(struct mtd_info *mtd, int maxchips) { + int i; struct onenand_chip *this = mtd->priv; if (!this->read_word) @@ -1813,12 +2099,21 @@ int onenand_scan(struct mtd_info *mtd, int maxchips) this->command = onenand_command; if (!this->wait) this->wait = onenand_wait; + if (!this->bbt_wait) + this->bbt_wait = onenand_bbt_wait; if (!this->read_bufferram) this->read_bufferram = onenand_read_bufferram; + if (!this->read_spareram) + this->read_spareram = onenand_read_bufferram; if (!this->write_bufferram) this->write_bufferram = onenand_write_bufferram; + if (!this->block_markbad) + this->block_markbad = onenand_default_block_markbad; + if (!this->scan_bbt) + this->scan_bbt = onenand_default_bbt; + if (onenand_probe(mtd)) return -ENXIO; @@ -1850,9 +2145,50 @@ int onenand_scan(struct mtd_info *mtd, int maxchips) this->options |= ONENAND_OOBBUF_ALLOC; } - onenand_unlock(mtd, 0, mtd->size); + this->state = FL_READY; + + /* + * Allow subpage writes up to oobsize. + */ + switch (mtd->oobsize) { + case 64: + this->ecclayout = &onenand_oob_64; + mtd->subpage_sft = 2; + break; + + case 32: + this->ecclayout = &onenand_oob_32; + mtd->subpage_sft = 1; + break; + + default: + printk(KERN_WARNING "No OOB scheme defined for oobsize %d\n", + mtd->oobsize); + mtd->subpage_sft = 0; + /* To prevent kernel oops */ + this->ecclayout = &onenand_oob_32; + break; + } + + this->subpagesize = mtd->writesize >> mtd->subpage_sft; + + /* + * The number of bytes available for a client to place data into + * the out of band area + */ + this->ecclayout->oobavail = 0; + for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES && + this->ecclayout->oobfree[i].length; i++) + this->ecclayout->oobavail += + this->ecclayout->oobfree[i].length; + mtd->oobavail = this->ecclayout->oobavail; + + mtd->ecclayout = this->ecclayout; + + /* Unlock whole block */ + onenand_unlock_all(mtd); - return onenand_default_bbt(mtd); + return this->scan_bbt(mtd); } /** diff --git a/drivers/mtd/onenand/onenand_bbt.c b/drivers/mtd/onenand/onenand_bbt.c index f6092b9be..d538f9582 100644 --- a/drivers/mtd/onenand/onenand_bbt.c +++ b/drivers/mtd/onenand/onenand_bbt.c @@ -3,7 +3,7 @@ * * Bad Block Table support for the OneNAND driver * - * Copyright(c) 2005-2007 Samsung Electronics + * Copyright(c) 2005-2008 Samsung Electronics * Kyungmin Park <kyungmin.park@samsung.com> * * TODO: @@ -54,7 +54,7 @@ static int check_short_pattern(uint8_t * buf, int len, int paglen, * @param buf temporary buffer * @param bd descriptor for the good/bad block search pattern * @param chip create the table for a specific chip, -1 read all chips. - * Applies only if NAND_BBT_PERCHIP option is set + * Applies only if NAND_BBT_PERCHIP option is set * * Create a bad block table by scanning the device * for the given good/bad block identify pattern @@ -156,8 +156,8 @@ static int onenand_isbad_bbt(struct mtd_info *mtd, loff_t offs, int allowbbt) res = (bbm->bbt[block >> 3] >> (block & 0x06)) & 0x03; MTDDEBUG (MTD_DEBUG_LEVEL2, - "onenand_isbad_bbt: bbt info for offs 0x%08x: (block %d) 0x%02x\n", - (unsigned int)offs, block >> 1, res); + "onenand_isbad_bbt: bbt info for offs 0x%08x: (block %d) 0x%02x\n", + (unsigned int)offs, block >> 1, res); switch ((int)res) { case 0x00: diff --git a/drivers/mtd/onenand/onenand_uboot.c b/drivers/mtd/onenand/onenand_uboot.c index 08082f3ed..4541b2217 100644 --- a/drivers/mtd/onenand/onenand_uboot.c +++ b/drivers/mtd/onenand/onenand_uboot.c @@ -26,9 +26,17 @@ void onenand_init(void) memset(&onenand_mtd, 0, sizeof(struct mtd_info)); memset(&onenand_chip, 0, sizeof(struct onenand_chip)); - onenand_chip.base = (void *) CONFIG_SYS_ONENAND_BASE; onenand_mtd.priv = &onenand_chip; +#ifdef CONFIG_USE_ONENAND_BOARD_INIT + /* + * It's used for some board init required + */ + onenand_board_init(&onenand_mtd); +#else + onenand_chip.base = (void *) CONFIG_SYS_ONENAND_BASE; +#endif + onenand_scan(&onenand_mtd, 1); puts("OneNAND: "); diff --git a/drivers/mtd/spi/atmel.c b/drivers/mtd/spi/atmel.c index 10fcf0cdd..a5f51caf4 100644 --- a/drivers/mtd/spi/atmel.c +++ b/drivers/mtd/spi/atmel.c @@ -39,9 +39,10 @@ struct atmel_spi_flash_params { const char *name; }; +/* spi_flash needs to be first so upper layers can free() it */ struct atmel_spi_flash { - const struct atmel_spi_flash_params *params; struct spi_flash flash; + const struct atmel_spi_flash_params *params; }; static inline struct atmel_spi_flash * diff --git a/drivers/mtd/spi/stmicro.c b/drivers/mtd/spi/stmicro.c index 86324e4e1..e7dda91a4 100644 --- a/drivers/mtd/spi/stmicro.c +++ b/drivers/mtd/spi/stmicro.c @@ -64,9 +64,10 @@ struct stmicro_spi_flash_params { const char *name; }; +/* spi_flash needs to be first so upper layers can free() it */ struct stmicro_spi_flash { - const struct stmicro_spi_flash_params *params; struct spi_flash flash; + const struct stmicro_spi_flash_params *params; }; static inline struct stmicro_spi_flash *to_stmicro_spi_flash(struct spi_flash |