From a3b2d374f0c7ae9bab251cac0101a9e0a8e8d636 Mon Sep 17 00:00:00 2001 From: VinTheGodfather Date: Sun, 8 Jan 2017 05:10:51 +0700 Subject: [PATCH] JFFS for WNR3500Lv2 thx VinTheGodfather --- .../arch/mips/brcm-boards/bcm947xx/setup.c | 848 +++---- release/src-rt/linux/linux-2.6/config_base | 1 + .../linux/linux-2.6/drivers/mtd/47xxnand/Kconfig | 12 + .../linux/linux-2.6/drivers/mtd/47xxnand/Makefile | 7 + .../linux-2.6/drivers/mtd/47xxnand/brcmnand_47xx.c | 2570 ++++++++++++++++++++ .../linux-2.6/drivers/mtd/47xxnand/brcmnand_bbt.c | 1406 +++++++++++ .../linux-2.6/drivers/mtd/47xxnand/brcmnand_priv.h | 103 + release/src-rt/linux/linux-2.6/drivers/mtd/Kconfig | 2 + .../src-rt/linux/linux-2.6/drivers/mtd/Makefile | 2 +- .../linux-2.6/drivers/mtd/devices/bcm_nflash.c | 256 +- .../linux/linux-2.6/drivers/mtd/devices/sflash.c | 46 +- .../linux-2.6/drivers/mtd/maps/bcm947xx-flash.c | 3 + .../src-rt/linux/linux-2.6/include/linux/mtd/mtd.h | 4 + .../linux/linux-2.6/include/linux/mtd/nand.h | 40 + release/src/Makefile | 22 +- release/src/router/rc/jffs2.c | 42 +- 16 files changed, 4844 insertions(+), 520 deletions(-) mode change 100644 => 100755 release/src-rt/linux/linux-2.6/arch/mips/brcm-boards/bcm947xx/setup.c create mode 100755 release/src-rt/linux/linux-2.6/drivers/mtd/47xxnand/Kconfig create mode 100755 release/src-rt/linux/linux-2.6/drivers/mtd/47xxnand/Makefile create mode 100755 release/src-rt/linux/linux-2.6/drivers/mtd/47xxnand/brcmnand_47xx.c create mode 100755 release/src-rt/linux/linux-2.6/drivers/mtd/47xxnand/brcmnand_bbt.c create mode 100755 release/src-rt/linux/linux-2.6/drivers/mtd/47xxnand/brcmnand_priv.h mode change 100644 => 100755 release/src-rt/linux/linux-2.6/drivers/mtd/devices/bcm_nflash.c mode change 100644 => 100755 release/src-rt/linux/linux-2.6/drivers/mtd/devices/sflash.c diff --git a/release/src-rt/linux/linux-2.6/arch/mips/brcm-boards/bcm947xx/setup.c b/release/src-rt/linux/linux-2.6/arch/mips/brcm-boards/bcm947xx/setup.c old mode 100644 new mode 100755 index 98308fc55f..f3858f7568 --- a/release/src-rt/linux/linux-2.6/arch/mips/brcm-boards/bcm947xx/setup.c +++ b/release/src-rt/linux/linux-2.6/arch/mips/brcm-boards/bcm947xx/setup.c @@ -1,15 +1,21 @@ /* * HND MIPS boards setup routines * - * Copyright (C) 2009, Broadcom Corporation - * All Rights Reserved. + * Copyright (C) 2010, Broadcom Corporation. All Rights Reserved. * - * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY - * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM - * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE. + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - * $Id: setup.c,v 1.14 2010/02/26 04:43:25 Exp $ + * $Id: setup.c,v 1.23 2010-10-20 08:26:12 Exp $ */ #include @@ -35,7 +41,9 @@ #include #include #endif - +#ifdef CONFIG_BLK_DEV_INITRD +#include +#endif #include #include #include @@ -52,9 +60,10 @@ #include #endif /* HNDCTF */ #include "bcm947xx.h" -#ifdef NFLASH_SUPPORT +#ifdef CONFIG_MTD_NFLASH #include "nflash.h" #endif +#include "bcmdevs.h" extern void bcm947xx_time_init(void); extern void bcm947xx_timer_setup(struct irqaction *irq); @@ -95,11 +104,12 @@ extern char arcs_cmdline[CL_SIZE]; static int lanports_enable = 0; static int wombo_reset = GPIO_PIN_NOTDEFINED; -static void +static void bcm947xx_reboot_handler(void) { if (lanports_enable) { uint lp = 1 << lanports_enable; + si_gpioout(sih, lp, 0, GPIO_DRV_PRIORITY); si_gpioouten(sih, lp, lp, GPIO_DRV_PRIORITY); bcm_mdelay(1); @@ -133,34 +143,30 @@ bcm947xx_machine_halt(void) /* Disable interrupts and watchdog and spin forever */ local_irq_disable(); - bcm947xx_reboot_handler(); si_watchdog(sih, 0); + bcm947xx_reboot_handler(); while (1); } #ifdef CONFIG_SERIAL_CORE -static int num_ports = 0; +static struct uart_port rs = { + line: 0, + flags: ASYNC_BOOT_AUTOCONF, + iotype: SERIAL_IO_MEM, +}; static void __init serial_add(void *regs, uint irq, uint baud_base, uint reg_shift) { - struct uart_port rs; - - memset(&rs, 0, sizeof(rs)); - rs.line = num_ports++; - rs.flags = UPF_BOOT_AUTOCONF | UPF_SHARE_IRQ; - rs.iotype = UPIO_MEM; - - rs.mapbase = (unsigned int) regs; rs.membase = regs; rs.irq = irq + 2; rs.uartclk = baud_base; rs.regshift = reg_shift; - if (early_serial_setup(&rs) != 0) { - printk(KERN_ERR "Serial setup failed!\n"); - } + early_serial_setup(&rs); + + rs.line++; } static void __init @@ -177,6 +183,49 @@ serial_setup(si_t *sih) #endif /* CONFIG_SERIAL_CORE */ +static int boot_flags(void) +{ + int bootflags = 0; + char *val; + + /* Only support chipcommon revision == 38 or BCM4706 for now */ + if ((CHIPID(sih->chip) == BCM4706_CHIP_ID) || sih->ccrev == 38) { + if (sih->ccrev == 38 && (sih->chipst & (1 << 4)) != 0) { + /* This is NANDBOOT */ + bootflags = FLASH_BOOT_NFLASH | FLASH_KERNEL_NFLASH; + } + else if ((val = nvram_get("bootflags"))) { + bootflags = simple_strtol(val, NULL, 0); + bootflags &= FLASH_KERNEL_NFLASH; + } + } + + return bootflags; +} + +static int rootfs_mtdblock(void) +{ + int bootflags; + int block = 0; + + bootflags = boot_flags(); + + /* NANDBOOT */ + if ((bootflags & (FLASH_BOOT_NFLASH | FLASH_KERNEL_NFLASH)) == + (FLASH_BOOT_NFLASH | FLASH_KERNEL_NFLASH)) + return 15; + + /* SFLASH/PFLASH only */ + if ((bootflags & (FLASH_BOOT_NFLASH | FLASH_KERNEL_NFLASH)) == 0) + return 2; + +#ifdef BCMCONFMTD + block++; +#endif + /* Boot from norflash and kernel in nandflash */ + return block+3; +} + void __init brcm_setup(void) { @@ -207,17 +256,14 @@ brcm_setup(void) ide_ops = &std_ide_ops; #endif -#ifdef NFLASH_SUPPORT - if ((sih->ccrev >= 38) && ((sih->chipst & (1 << 4)) != 0)) { - if (strncmp(arcs_cmdline, "root=/dev/mtdblock", strlen("root=/dev/mtdblock")) == 0) - strcpy(arcs_cmdline, "root=/dev/mtdblock15 console=ttyS0,115200"); - } -#endif + sprintf(arcs_cmdline, "root=/dev/mtdblock%d console=ttyS0,115200 init=/sbin/preinit", rootfs_mtdblock()); + /* Override default command line arguments */ value = nvram_get("kernel_args"); if (value && strlen(value) && strncmp(value, "empty", 5)) strncpy(arcs_cmdline, value, sizeof(arcs_cmdline)); + if ((lanports_enable = getgpiopin(NULL, "lanports_enable", GPIO_PIN_NOTDEFINED)) == GPIO_PIN_NOTDEFINED) lanports_enable = 0; @@ -249,12 +295,10 @@ const char * get_system_type(void) { static char s[32]; - char cn[8]; if (bcm947xx_sih) { - bcm_chipname(bcm947xx_sih->chip, cn, 8); - sprintf(s, "Broadcom BCM%s chip rev %d pkg %d", cn, - bcm947xx_sih->chiprev, bcm947xx_sih->chippkg); + sprintf(s, "Broadcom BCM%X chip rev %d", bcm947xx_sih->chip, + bcm947xx_sih->chiprev); return s; } else @@ -275,334 +319,245 @@ plat_mem_setup(void) #ifdef CONFIG_MTD_PARTITIONS -enum { - RT_UNKNOWN, - RT_DIR320, // D-Link DIR-320 - RT_WNR3500L, // Netgear WNR3500v2/U/L - RT_WNR2000V2, // Netgear WNR2000v2 - RT_WNDR, // Netgear WNDR4000, WNDR3700v3, WNDR3400, WNDR3400v2 - RT_BELKIN_F7D // Belkin F7D3301, F7D3302, F7D4302, F7D8235V3 -}; +static struct mutex *mtd_mutex = NULL; -static int get_router(void) +struct mutex *partitions_mutex_init(void) { - uint boardnum = bcm_strtoul(nvram_safe_get("boardnum"), NULL, 0); - uint boardrev = bcm_strtoul(nvram_safe_get("boardrev"), NULL, 0); - uint boardtype = bcm_strtoul(nvram_safe_get("boardtype"), NULL, 0); - - if ((boardnum == 1 || boardnum == 3500) && boardtype == 0x4CF && (boardrev == 0x1213 || boardrev == 2)) { - return RT_WNR3500L; - } - else if (boardnum == 1 && boardtype == 0xE4CD && boardrev == 0x1700) { - return RT_WNR2000V2; + if (!mtd_mutex) { + mtd_mutex = (struct mutex *)kzalloc(sizeof(struct mutex), GFP_KERNEL); + if (!mtd_mutex) + return NULL; + mutex_init(mtd_mutex); } - else if (boardtype == 0xF52C && boardrev == 0x1101 && boardnum == 01) { - // Netgear WNDR4000m WNDR3700v3 - return RT_WNDR; - } - else if (boardtype == 0xB4CF && boardrev == 0x1100 && boardnum == 01) { - // Netgear WNDR3400 - return RT_WNDR; - } - else if (boardtype == 0x0550 && boardrev == 0x1400 && boardnum == 01) { - // Netgear WNDR3400v2 - return RT_WNDR; - } -#ifdef DIR320_BOARD - else if (boardnum == 0 && boardtype == 0x48E && boardrev == 0x35) { - return RT_DIR320; - } -#endif - else if (boardtype == 0xA4CF && (boardrev == 0x1102 || boardrev == 0x1100)) { - return RT_BELKIN_F7D; - } - return RT_UNKNOWN; + return mtd_mutex; } +EXPORT_SYMBOL(partitions_mutex_init); -#ifdef DIR320_BOARD -static size_t get_erasesize(struct mtd_info *mtd, size_t offset, size_t size) -{ - int i; - struct mtd_erase_region_info *regions; - size_t erasesize = 0; +/* Find out prom size */ +static uint32 boot_partition_size(uint32 flash_phys) { + uint32 bootsz, *bisz; - if (mtd->numeraseregions > 1) { - regions = mtd->eraseregions; + /* Default is 256K boot partition */ + bootsz = 256 * 1024; - // Find the first erase regions which is part of this partition - for (i = 0; i < mtd->numeraseregions && offset >= regions[i].offset; i++); + /* Do we have a self-describing binary image? */ + bisz = (uint32 *)KSEG1ADDR(flash_phys + BISZ_OFFSET); + if (bisz[BISZ_MAGIC_IDX] == BISZ_MAGIC) { + int isz = bisz[BISZ_DATAEND_IDX] - bisz[BISZ_TXTST_IDX]; - for (i--; i < mtd->numeraseregions && offset + size > regions[i].offset; i++) { - if (erasesize < regions[i].erasesize) - erasesize = regions[i].erasesize; - } - } - else { - erasesize = mtd->erasesize; + if (isz > (1024 * 1024)) + bootsz = 2048 * 1024; + else if (isz > (512 * 1024)) + bootsz = 1024 * 1024; + else if (isz > (256 * 1024)) + bootsz = 512 * 1024; + else if (isz <= (128 * 1024)) + bootsz = 128 * 1024; } - - return erasesize; + return bootsz; } + +#if defined(BCMCONFMTD) +#define FLASH_PARTS_NUM 17 +#else +#define FLASH_PARTS_NUM 16 #endif -/* - new layout -- zzz 04/2006 - - +--------------+ - | boot | - +---+----------+ < search for HDR0 - | | | - | | (kernel) | - | l | | - | i +----------+ < + trx->offset[1] - | n | | - | u | rootfs | - | x | | - + +----------+ < + trx->len - | | jffs2 | - +---+----------+ < size - NVRAM_SPACE - board_data_size() - | board_data | - +--------------+ < size - NVRAM_SPACE - | nvram | - +--------------+ < size -*/ - -static struct mtd_partition bcm947xx_parts[] = { - { name: "pmon", offset: 0, size: 0, mask_flags: MTD_WRITEABLE, }, - { name: "linux", offset: 0, size: 0, }, - { name: "rootfs", offset: 0, size: 0, mask_flags: MTD_WRITEABLE, }, - { name: "jffs2", offset: 0, size: 0, }, - { name: "nvram", offset: 0, size: 0, }, - { name: "board_data", offset: 0, size: 0, }, - { name: NULL, }, +static struct mtd_partition bcm947xx_flash_parts[FLASH_PARTS_NUM] = { + {.name = "boot"}, + {.name = "linux"}, + {.name = "rootfs"}, + {.name = "ML1", .size = 0x20000}, + {.name = "ML2", .size = 0x20000}, + {.name = "ML3", .size = 0x20000}, + {.name = "ML4", .size = 0x20000}, + {.name = "ML5", .size = 0x20000}, + {.name = "ML6", .size = 0x20000}, + {.name = "ML7", .size = 0x20000}, + {.name = "T_Meter1", .size = 0x10000}, + {.name = "T_Meter2", .size = 0x10000}, + {.name = "POT", .size = 0x10000}, + {.name = "board_data", .size = 0x10000}, +#if defined(BCMCONFMTD) + {.name = "confmtd"}, +#endif + {.name = "nvram"}, + {0} }; -#define PART_BOOT 0 -#define PART_LINUX 1 -#define PART_ROOTFS 2 -#define PART_JFFS2 3 -#define PART_NVRAM 4 -#define PART_BOARD 5 - -struct mtd_partition * -init_mtd_partitions(struct mtd_info *mtd, size_t size) +static uint lookup_flash_rootfs_offset(struct mtd_info *mtd, int offset, size_t size) { - int router; + struct romfs_super_block *romfsb; + struct cramfs_super *cramfsb; + struct squashfs_super_block *squashfsb; struct trx_header *trx; unsigned char buf[512]; - size_t off, trxoff, boardoff; - size_t crclen; + int off, trx_off; size_t len; - size_t trxsize; - - crclen = 0; - - /* Find and size nvram */ - bcm947xx_parts[PART_NVRAM].size = ROUNDUP(NVRAM_SPACE, mtd->erasesize); - bcm947xx_parts[PART_NVRAM].offset = size - bcm947xx_parts[PART_NVRAM].size; - - /* Size board_data */ - boardoff = bcm947xx_parts[PART_NVRAM].offset; - router = get_router(); - switch (router) { -#ifdef DIR320_BOARD - case RT_DIR320: - if (get_erasesize(mtd, bcm947xx_parts[PART_NVRAM].offset, bcm947xx_parts[PART_NVRAM].size) == 0x2000) { - bcm947xx_parts[PART_NVRAM].size = ROUNDUP(NVRAM_SPACE, 0x2000); - bcm947xx_parts[PART_NVRAM].offset = size - bcm947xx_parts[PART_NVRAM].size; - bcm947xx_parts[PART_BOARD].size = 0x2000; // 8 KB - bcm947xx_parts[PART_BOARD].offset = bcm947xx_parts[PART_NVRAM].offset - bcm947xx_parts[PART_BOARD].size; - } - else bcm947xx_parts[PART_BOARD].name = NULL; - break; -#endif - case RT_WNR3500L: - case RT_WNR2000V2: - case RT_WNDR: - bcm947xx_parts[PART_BOARD].size = mtd->erasesize; - boardoff -= bcm947xx_parts[PART_BOARD].size; - bcm947xx_parts[PART_BOARD].offset = boardoff; - boardoff -= 5 * mtd->erasesize; - if (size <= 4 * 1024 * 1024) { - // 4MB flash - bcm947xx_parts[PART_JFFS2].offset = boardoff; - bcm947xx_parts[PART_JFFS2].size = bcm947xx_parts[PART_BOARD].offset - bcm947xx_parts[PART_JFFS2].offset; - } - else { - // 8MB or larger flash, exclude 1 block for Netgear CRC - crclen = mtd->erasesize; - } - break; - default: - bcm947xx_parts[PART_BOARD].name = NULL; - break; - } - trxsize = 0; + romfsb = (struct romfs_super_block *) buf; + cramfsb = (struct cramfs_super *) buf; + squashfsb = (struct squashfs_super_block *) buf; trx = (struct trx_header *) buf; - for (off = 0; off < size; off += mtd->erasesize) { + + /* Look at every 64 KB boundary */ + for (off = offset; off < size; off += (64 * 1024)) { + memset(buf, 0xe5, sizeof(buf)); + /* - * Read block 0 to test for rootfs + * Read block 0 to test for romfs and cramfs superblock */ - if ((mtd->read(mtd, off, sizeof(buf), &len, buf)) || (len != sizeof(buf))) + if (mtd->read(mtd, off, sizeof(buf), &len, buf) || + len != sizeof(buf)) continue; /* Try looking at TRX header for rootfs offset */ - switch (le32_to_cpu(trx->magic)) { - case TRX_MAGIC_F7D3301: - case TRX_MAGIC_F7D3302: - case TRX_MAGIC_F7D4302: - case TRX_MAGIC_F5D8235V3: - case TRX_MAGIC_QA: - if (router != RT_BELKIN_F7D) + if (le32_to_cpu(trx->magic) == TRX_MAGIC) { + trx_off = off; + if (trx->offsets[1] == 0) + continue; + /* + * Read to test for romfs and cramfs superblock + */ + off += le32_to_cpu(trx->offsets[1]); + memset(buf, 0xe5, sizeof(buf)); + if (mtd->read(mtd, off, sizeof(buf), &len, buf) || len != sizeof(buf)) continue; - // fall through - case TRX_MAGIC: - trxsize = ROUNDUP(le32_to_cpu(trx->len), mtd->erasesize); // kernel + rootfs + } + + /* romfs is at block zero too */ + if (romfsb->word0 == ROMSB_WORD0 && + romfsb->word1 == ROMSB_WORD1) { + printk(KERN_NOTICE + "%s: romfs filesystem found at block %d\n", + mtd->name, off / mtd->erasesize); break; - } - if (trxsize) { - /* Size pmon */ - bcm947xx_parts[PART_BOOT].size = off; - - /* Size linux (kernel and rootfs) */ - bcm947xx_parts[PART_LINUX].offset = off; - bcm947xx_parts[PART_LINUX].size = boardoff - off; - - /* Find and size rootfs */ - trxoff = (le32_to_cpu(trx->offsets[2]) > off) ? trx->offsets[2] : trx->offsets[1]; - bcm947xx_parts[PART_ROOTFS].offset = trxoff + off; - bcm947xx_parts[PART_ROOTFS].size = trxsize - trxoff; - - /* Find and size jffs2 */ - if (bcm947xx_parts[PART_JFFS2].size == 0) { - bcm947xx_parts[PART_JFFS2].offset = off + trxsize; - if ((boardoff - crclen) > bcm947xx_parts[PART_JFFS2].offset) { - bcm947xx_parts[PART_JFFS2].size = boardoff - crclen - bcm947xx_parts[PART_JFFS2].offset; - } - } + /* so is cramfs */ + if (cramfsb->magic == CRAMFS_MAGIC) { + printk(KERN_NOTICE + "%s: cramfs filesystem found at block %d\n", + mtd->name, off / mtd->erasesize); + break; + } + + if (squashfsb->s_magic == SQUASHFS_MAGIC) { + printk(KERN_NOTICE + "%s: squash filesystem with lzma found at block %d\n", + mtd->name, off / mtd->erasesize); break; } } - if (trxsize == 0) { - // uh, now what... - printk(KERN_NOTICE "%s: Unable to find a valid linux partition\n", mtd->name); + return off; +} +struct mtd_partition * +init_mtd_partitions(struct mtd_info *mtd, size_t size) +{ + int bootflags; + int nparts = 0; + uint32 offset = 0; + uint rfs_off = 0; + uint vmlz_off, knl_size; + uint32 top = 0; + uint32 bootsz; + int parts_idx = 0; + + bootflags = boot_flags(); + + bootsz = boot_partition_size(SI_FLASH2); + printk("Boot partition size = %d(0x%x)\n", bootsz, bootsz); + + /* Size pmon */ + bcm947xx_flash_parts[nparts].name = "boot"; + bcm947xx_flash_parts[nparts].size = bootsz; + bcm947xx_flash_parts[nparts].offset = top; + bcm947xx_flash_parts[nparts].mask_flags = MTD_WRITEABLE; /* forces on read only */ + offset = bcm947xx_flash_parts[nparts].size; + nparts++; + + if ((bootflags & FLASH_KERNEL_NFLASH) != FLASH_KERNEL_NFLASH) { + rfs_off = lookup_flash_rootfs_offset(mtd, offset, size); + vmlz_off = offset; + + /* Setup kernel MTD partition */ + bcm947xx_flash_parts[nparts].name = "linux"; + bcm947xx_flash_parts[nparts].size = mtd->size - offset; + + /* Reserve for NVRAM */ + bcm947xx_flash_parts[nparts].size -= ROUNDUP(NVRAM_SPACE, mtd->erasesize); + +#ifdef BCMCONFMTD + bcm947xx_flash_parts[nparts].size -= (mtd->erasesize *4); +#endif + + for (parts_idx = 3; parts_idx <= 13; parts_idx++) + bcm947xx_flash_parts[nparts].size -= bcm947xx_flash_parts[parts_idx].size; + + bcm947xx_flash_parts[nparts].offset = offset; + knl_size = bcm947xx_flash_parts[nparts].size; + offset = bcm947xx_flash_parts[nparts].offset + knl_size; + nparts++; + + /* Setup rootfs MTD partition */ + bcm947xx_flash_parts[nparts].name = "rootfs"; + bcm947xx_flash_parts[nparts].size = knl_size - (rfs_off - vmlz_off); + bcm947xx_flash_parts[nparts].offset = rfs_off; + bcm947xx_flash_parts[nparts].mask_flags = MTD_WRITEABLE; /* forces on read only */ + nparts++; } -#if 0 - int i; - for (i = 0; bcm947xx_parts[i].name; ++i) { - printk(KERN_NOTICE "%8x %8x (%8x) %s\n", - bcm947xx_parts[i].offset, - (bcm947xx_parts[i].offset + bcm947xx_parts[i].size) - 1, - bcm947xx_parts[i].size, - bcm947xx_parts[i].name); + for (parts_idx = 3; parts_idx <= 13; parts_idx++) { + bcm947xx_flash_parts[parts_idx].offset = offset; + offset += bcm947xx_flash_parts[parts_idx].size; + nparts++; } -#endif - return bcm947xx_parts; +#ifdef BCMCONFMTD + /* Setup CONF MTD partition */ + bcm947xx_flash_parts[nparts].name = "confmtd"; + bcm947xx_flash_parts[nparts].size = mtd->erasesize * 4; + bcm947xx_flash_parts[nparts].offset = offset; + offset = bcm947xx_flash_parts[nparts].offset + bcm947xx_flash_parts[nparts].size; + nparts++; +#endif /* BCMCONFMTD */ + + /* Setup nvram MTD partition */ + bcm947xx_flash_parts[nparts].name = "nvram"; + bcm947xx_flash_parts[nparts].size = ROUNDUP(NVRAM_SPACE, mtd->erasesize); + bcm947xx_flash_parts[nparts].offset = size - bcm947xx_flash_parts[nparts].size; + nparts++; + + return bcm947xx_flash_parts; } EXPORT_SYMBOL(init_mtd_partitions); -#ifdef NFLASH_SUPPORT -static struct mtd_partition bcm947xx_nflash_parts[] = -{ - { - .name = "boot", - .size = 0, - .offset = 0, - .mask_flags = MTD_WRITEABLE - }, - { - .name = "nvram", - .size = 0, - .offset = 0 - }, -#if 1 - { - .name = "board_data", - .offset = 0, - .size = 0, - }, - { - .name = "POT1", - .offset = 0, - .size = 0, - }, - { - .name = "POT2", - .offset = 0, - .size = 0, - }, - { - .name = "T_Meter1", - .offset = 0, - .size = 0, - }, - { - .name = "T_Meter2", - .offset = 0, - .size = 0, - }, - { - .name = "ML1", - .offset = 0, - .size = 0, - }, - { - .name = "ML2", - .offset = 0, - .size = 0, - }, - { - .name = "ML3", - .offset = 0, - .size = 0, - }, - { - .name = "ML4", - .offset = 0, - .size = 0, - }, - { - .name = "ML5", - .offset = 0, - .size = 0, - }, - { - .name = "ML6", - .offset = 0, - .size = 0, - }, - { - .name = "ML7", - .offset = 0, - .size = 0, - }, -#endif - { - .name = "linux", - .size = 0, - .offset = 0 - }, - { - .name = "rootfs", - .size = 0, - .offset = 0, - .mask_flags = MTD_WRITEABLE - }, - { - .name = 0, - .size = 0, - .offset = 0 - } +#ifdef CONFIG_MTD_NFLASH +#define NFLASH_PARTS_NUM 18 +static struct mtd_partition bcm947xx_nflash_parts[NFLASH_PARTS_NUM] = { + {.name = "boot"}, + {.name = "nvram"}, + {.name = "board_data", .size = 0x40000}, + {.name = "POT1", .size = 0x40000}, + {.name = "POT2", .size = 0x40000}, + {.name = "T_Meter1", .size = 0x40000}, + {.name = "T_Meter2", .size = 0x40000}, + {.name = "ML1", .size = 0x40000}, + {.name = "ML2", .size = 0x40000}, + {.name = "ML3", .size = 0x40000}, + {.name = "ML4", .size = 0x40000}, + {.name = "ML5", .size = 0x40000}, + {.name = "ML6", .size = 0x40000}, + {.name = "ML7", .size = 0x40000}, + {.name = "linux"}, + {.name = "rootfs"}, + {0}, + {0} }; -struct mtd_partition * init_nflash_mtd_partitions(struct mtd_info *mtd, size_t size) +static uint lookup_nflash_rootfs_offset(struct mtd_info *mtd, int offset, size_t size) { struct romfs_super_block *romfsb; struct cramfs_super *cramfsb; @@ -611,22 +566,20 @@ struct mtd_partition * init_nflash_mtd_partitions(struct mtd_info *mtd, size_t s unsigned char buf[NFL_SECTOR_SIZE]; uint blocksize, mask, blk_offset, off, shift = 0; chipcregs_t *cc; - uint32 bootsz, *bisz; - int ret, i; - uint32 top; - int idx; - + int ret; + romfsb = (struct romfs_super_block *) buf; cramfsb = (struct cramfs_super *) buf; squashfsb = (struct squashfs_super_block *) buf; trx = (struct trx_header *) buf; if ((cc = (chipcregs_t *)si_setcoreidx(sih, SI_CC_IDX)) == NULL) - return NULL; + return 0; /* Look at every block boundary till 16MB; higher space is reserved for application data. */ blocksize = mtd->erasesize; - for (off = NFL_BOOT_SIZE; off < NFL_BOOT_OS_SIZE; off += blocksize) { + printk("lookup_nflash_rootfs_offset: offset = 0x%x\n", offset); + for (off = offset; off < NFL_BOOT_OS_SIZE; off += blocksize) { mask = blocksize - 1; blk_offset = off & ~mask; if (nflash_checkbadb(sih, cc, blk_offset) != 0) @@ -637,15 +590,15 @@ struct mtd_partition * init_nflash_mtd_partitions(struct mtd_info *mtd, size_t s "%s: nflash_read return %d\n", mtd->name, ret); continue; } - + /* Try looking at TRX header for rootfs offset */ if (le32_to_cpu(trx->magic) == TRX_MAGIC && off >= 0x500000) { mask = NFL_SECTOR_SIZE - 1; - off = off + (le32_to_cpu(trx->offsets[1]) & ~mask) - blocksize; + off = offset + (le32_to_cpu(trx->offsets[1]) & ~mask) - blocksize; shift = (le32_to_cpu(trx->offsets[1]) & mask); - romfsb = (unsigned char *)romfsb + shift; - cramfsb = (unsigned char *)cramfsb + shift; - squashfsb = (unsigned char *)squashfsb + shift; + romfsb = (struct romfs_super_block *)((unsigned char *)romfsb + shift); + cramfsb = (struct cramfs_super *)((unsigned char *)cramfsb + shift); + squashfsb = (struct squashfs_super_block *)((unsigned char *)squashfsb + shift); continue; } @@ -654,114 +607,187 @@ struct mtd_partition * init_nflash_mtd_partitions(struct mtd_info *mtd, size_t s romfsb->word1 == ROMSB_WORD1) { printk(KERN_NOTICE "%s: romfs filesystem found at block %d\n", - mtd->name, off / BLOCK_SIZE); - goto done; + mtd->name, off / blocksize); + break; } /* so is cramfs */ if (cramfsb->magic == CRAMFS_MAGIC) { printk(KERN_NOTICE "%s: cramfs filesystem found at block %d\n", - mtd->name, off / BLOCK_SIZE); - goto done; + mtd->name, off / blocksize); + break; } if (squashfsb->s_magic == SQUASHFS_MAGIC) { printk(KERN_NOTICE "%s: squash filesystem with lzma found at block %d\n", - mtd->name, off / BLOCK_SIZE); - goto done; + mtd->name, off / blocksize); + break; + } + + } + return shift + off; +} + +struct mtd_partition * init_nflash_mtd_partitions(struct mtd_info *mtd, size_t size) +{ + int bootflags; + int nparts = 0; + uint32 offset = 0; + uint shift = 0; + uint32 top = 0; + uint32 bootsz; + int parts_idx = 0; + + bootflags = boot_flags(); + if ((bootflags & FLASH_BOOT_NFLASH) == FLASH_BOOT_NFLASH) { + bootsz = boot_partition_size(SI_FLASH1); + if (bootsz > mtd->erasesize) { + /* Prepare double space in case of bad blocks */ + bootsz = (bootsz << 1); + } else { + /* CFE occupies at least one block */ + bootsz = mtd->erasesize; } + printk("Boot partition size = %d(0x%x)\n", bootsz, bootsz); + + /* Size pmon */ + bcm947xx_nflash_parts[nparts].name = "boot"; + bcm947xx_nflash_parts[nparts].size = bootsz; + bcm947xx_nflash_parts[nparts].offset = top; + bcm947xx_nflash_parts[nparts].mask_flags = MTD_WRITEABLE; /* forces on read only */ + offset = bcm947xx_nflash_parts[nparts].size; + nparts++; + + /* Setup NVRAM MTD partition */ + bcm947xx_nflash_parts[nparts].name = "nvram"; + bcm947xx_nflash_parts[nparts].size = NFL_BOOT_SIZE - offset; + bcm947xx_nflash_parts[nparts].offset = offset; + + offset = NFL_BOOT_SIZE; + nparts++; } + - printk(KERN_NOTICE - "%s: Couldn't find valid ROM disk image\n", - mtd->name); + for (parts_idx = 2; parts_idx <= 13; parts_idx++) { + bcm947xx_nflash_parts[parts_idx].offset = offset; + offset += bcm947xx_nflash_parts[parts_idx].size; + nparts++; + } -done: + if ((bootflags & FLASH_KERNEL_NFLASH) == FLASH_KERNEL_NFLASH) { + /* Setup kernel MTD partition */ + bcm947xx_nflash_parts[nparts].name = "linux"; + //bcm947xx_nflash_parts[nparts].size = nparts ? (NFL_BOOT_OS_SIZE - NFL_BOOT_SIZE) : NFL_BOOT_OS_SIZE; + bcm947xx_nflash_parts[nparts].size = nparts ? (NFL_BOOT_OS_SIZE - offset) : NFL_BOOT_OS_SIZE; + bcm947xx_nflash_parts[nparts].offset = offset; + + shift = lookup_nflash_rootfs_offset(mtd, offset, size); + + offset = NFL_BOOT_OS_SIZE; + nparts++; + + /* Setup rootfs MTD partition */ + bcm947xx_nflash_parts[nparts].name = "rootfs"; + bcm947xx_nflash_parts[nparts].size = NFL_BOOT_OS_SIZE - shift; + bcm947xx_nflash_parts[nparts].offset = shift; + bcm947xx_nflash_parts[nparts].mask_flags = MTD_WRITEABLE; + + nparts++; + } - /* Default is 256K boot partition */ - bootsz = 256 * 1024; + return bcm947xx_nflash_parts; +} - /* Do we have a self-describing binary image? */ - bisz = (uint32 *)KSEG1ADDR(SI_FLASH1 + BISZ_OFFSET); - if (bisz[BISZ_MAGIC_IDX] == BISZ_MAGIC) { - int isz = bisz[BISZ_DATAEND_IDX] - bisz[BISZ_TXTST_IDX]; +EXPORT_SYMBOL(init_nflash_mtd_partitions); +#endif /* CONFIG_MTD_NFLASH */ - if (isz > (1024 * 1024)) - bootsz = 2048 * 1024; - else if (isz > (512 * 1024)) - bootsz = 1024 * 1024; - else if (isz > (256 * 1024)) - bootsz = 512 * 1024; - else if (isz <= (128 * 1024)) - bootsz = 128 * 1024; - } - if (bootsz > mtd->erasesize) { - /* Prepare double space in case of bad blocks */ - bootsz = (bootsz << 1); - } else { - /* CFE occupies at least one block */ - bootsz = mtd->erasesize; - } +#ifdef CONFIG_BLK_DEV_INITRD +extern char _end; - printf("Boot partition size = %d(0x%x)\n", bootsz, bootsz); +/* The check_ramdisk_trx has more exact qualification to look at TRX header from end of linux */ +static __init int +check_ramdisk_trx(unsigned long offset, unsigned long ram_size) +{ + struct trx_header *trx; + uint32 crc; + unsigned int len; + uint8 *ptr = (uint8 *)offset; - /* Size pmon */ - bcm947xx_nflash_parts[0].size = bootsz; - - /* Setup NVRAM MTD partition */ - bcm947xx_nflash_parts[1].offset = bootsz; - bcm947xx_nflash_parts[1].size = NFL_BOOT_SIZE - bootsz; - - i = (sizeof(bcm947xx_nflash_parts)/sizeof(struct mtd_partition)) - 2; - top = NFL_BOOT_OS_SIZE; - -#if 1 - for (idx = 2; idx <= i - 2; idx++) - { - if (strncmp(bcm947xx_nflash_parts[idx].name, "board_data", 10) == 0) - bcm947xx_nflash_parts[idx].size = 0x40000; /* 256K */ - else if (strncmp(bcm947xx_nflash_parts[idx].name, "POT", 3) == 0) - bcm947xx_nflash_parts[idx].size = 0x40000; /* 256K */ - else if (strncmp(bcm947xx_nflash_parts[idx].name, "T_Meter", 7) == 0) - bcm947xx_nflash_parts[idx].size = 0x40000; /* 256K */ - else if (strncmp(bcm947xx_nflash_parts[idx].name, "ML", 2) == 0) - bcm947xx_nflash_parts[idx].size = 0x40000; /* 256K */ - else if (strncmp(bcm947xx_nflash_parts[idx].name, "linux", 5) == 0) - break; - else if (strncmp(bcm947xx_nflash_parts[idx].name, "rootfs", 6) == 0) - break; - else - { - printk(KERN_ERR "%s: Unknow MTD name %s\n", - __FUNCTION__, bcm947xx_nflash_parts[idx].name); - break; - } + trx = (struct trx_header *)ptr; - bcm947xx_nflash_parts[idx].offset = bcm947xx_nflash_parts[idx - 1].offset - + bcm947xx_nflash_parts[idx - 1].size; + /* Not a TRX_MAGIC */ + if (le32_to_cpu(trx->magic) != TRX_MAGIC) { + printk("check_ramdisk_trx: not a valid TRX magic\n"); + return -1; } -#endif - /* Find and size rootfs */ - if (off < size) { - bcm947xx_nflash_parts[i].offset = off + shift; - bcm947xx_nflash_parts[i].size = - top - bcm947xx_nflash_parts[i].offset; + /* TRX len invalid */ + len = le32_to_cpu(trx->len); + if (offset + len > ram_size) { + printk("check_ramdisk_trx: not a valid TRX length\n"); + return -1; } - /* Size linux (kernel and rootfs) */ - bcm947xx_nflash_parts[i - 1].offset = - bcm947xx_nflash_parts[i - 2].offset + bcm947xx_nflash_parts[i - 2].size; - bcm947xx_nflash_parts[i - 1].size = - top - bcm947xx_nflash_parts[i - 1].offset; + /* Checksum over header */ + crc = hndcrc32((uint8 *) &trx->flag_version, + sizeof(struct trx_header) - OFFSETOF(struct trx_header, flag_version), + CRC32_INIT_VALUE); - return bcm947xx_nflash_parts; -} + /* Move ptr to data */ + ptr += sizeof(struct trx_header); + len -= sizeof(struct trx_header); -EXPORT_SYMBOL(init_nflash_mtd_partitions); -#endif /* NFLASH_SUPPORT */ + /* Checksum over data */ + crc = hndcrc32(ptr, len, crc); + /* Verify checksum */ + if (le32_to_cpu(trx->crc32) != crc) { + printk("check_ramdisk_trx: checksum invalid\n"); + return -1; + } + + return 0; +} +void __init init_ramdisk(unsigned long mem_end) +{ + struct trx_header *trx = NULL; + char *from_rootfs, *to_rootfs; + unsigned long rootfs_size = 0; + unsigned long ram_size = mem_end + 0x80000000; + unsigned long offset; + char *root_cmd = "root=/dev/ram0 console=ttyS0,115200 rdinit=/sbin/preinit"; + + to_rootfs = (char *)(((unsigned long)&_end + PAGE_SIZE-1) & PAGE_MASK); + offset = ((unsigned long)&_end +0xffff) & ~0xffff; + + /* Look at TRX header from end of linux */ + for (; offset < ram_size; offset += 0x10000) { + trx = (struct trx_header *)offset; + if (le32_to_cpu(trx->magic) == TRX_MAGIC && + check_ramdisk_trx(offset, ram_size) == 0) { + printk(KERN_NOTICE + "Found TRX image at %08lx\n", offset); + from_rootfs = (char *)((unsigned long)trx + le32_to_cpu(trx->offsets[1])); + rootfs_size = le32_to_cpu(trx->len) - le32_to_cpu(trx->offsets[1]); + rootfs_size = (rootfs_size + 0xffff) & ~0xffff; + printk("rootfs size is %ld bytes at 0x%p, copying to 0x%p\n", rootfs_size, from_rootfs, to_rootfs); + memmove(to_rootfs, from_rootfs, rootfs_size); + + initrd_start = (int)to_rootfs; + initrd_end = initrd_start + rootfs_size; + strncpy(arcs_cmdline, root_cmd, sizeof(arcs_cmdline)); + /* + * In case the system warm boot, the memory won't be zeroed. + * So we have to erase trx magic. + */ + if (initrd_end < (unsigned long)trx) + trx->magic = 0; + break; + } + } +} +#endif #endif diff --git a/release/src-rt/linux/linux-2.6/config_base b/release/src-rt/linux/linux-2.6/config_base index 3a402a498c..a524e6a43a 100644 --- a/release/src-rt/linux/linux-2.6/config_base +++ b/release/src-rt/linux/linux-2.6/config_base @@ -737,6 +737,7 @@ CONFIG_MTD_SFLASH=y # CONFIG_MTD_DOC2001PLUS is not set # CONFIG_MTD_NAND is not set # CONFIG_MTD_ONENAND is not set +# CONFIG_MTD_BRCMNAND is not set # # UBI - Unsorted block images diff --git a/release/src-rt/linux/linux-2.6/drivers/mtd/47xxnand/Kconfig b/release/src-rt/linux/linux-2.6/drivers/mtd/47xxnand/Kconfig new file mode 100755 index 0000000000..485e027fd4 --- /dev/null +++ b/release/src-rt/linux/linux-2.6/drivers/mtd/47xxnand/Kconfig @@ -0,0 +1,12 @@ +# +# linux/drivers/mtd/47xxnand/Kconfig +# + +config MTD_BRCMNAND + tristate "Broadcom NAND Device Support" + default n + depends on MTD + help + This enables support for accessing selected types of NAND flash + devices through the Broadcom NAND controller. + diff --git a/release/src-rt/linux/linux-2.6/drivers/mtd/47xxnand/Makefile b/release/src-rt/linux/linux-2.6/drivers/mtd/47xxnand/Makefile new file mode 100755 index 0000000000..23f5ce3bc9 --- /dev/null +++ b/release/src-rt/linux/linux-2.6/drivers/mtd/47xxnand/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the Broadcom NAND MTD +# + +obj-$(CONFIG_MTD_BRCMNAND) += brcmnand_47xx.o brcmnand_bbt.o + +obj-$(CONFIG_MTD_BRCMNAND_CORRECTABLE_ERR_HANDLING) += brcmnand_cet.o diff --git a/release/src-rt/linux/linux-2.6/drivers/mtd/47xxnand/brcmnand_47xx.c b/release/src-rt/linux/linux-2.6/drivers/mtd/47xxnand/brcmnand_47xx.c new file mode 100755 index 0000000000..04ef70562f --- /dev/null +++ b/release/src-rt/linux/linux-2.6/drivers/mtd/47xxnand/brcmnand_47xx.c @@ -0,0 +1,2570 @@ +/* + * Broadcom NAND flash controller interface + * + * Copyright (C) 2010, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "brcmnand_priv.h" + +struct mutex *partitions_mutex_init(void); +#ifdef CONFIG_MTD_PARTITIONS +#include + +extern struct mtd_partition * init_brcmnand_mtd_partitions(struct mtd_info *mtd, size_t size); +#endif + +static int nflash_lock = 0; + +#define PLATFORM_IOFLUSH_WAR() __sync() + +#define BRCMNAND_POLL_TIMEOUT 3000 + +#define BRCMNAND_CORRECTABLE_ECC_ERROR (1) +#define BRCMNAND_SUCCESS (0) +#define BRCMNAND_UNCORRECTABLE_ECC_ERROR (-1) +#define BRCMNAND_FLASH_STATUS_ERROR (-2) +#define BRCMNAND_TIMED_OUT (-3) + +#define BRCMNAND_OOBBUF(pbuf) (&((pbuf)->databuf[NAND_MAX_PAGESIZE])) + +/* + * Number of required ECC bytes per 512B slice + */ +static const unsigned int brcmnand_eccbytes[16] = { + [BRCMNAND_ECC_DISABLE] = 0, + [BRCMNAND_ECC_BCH_1] = 2, + [BRCMNAND_ECC_BCH_2] = 4, + [BRCMNAND_ECC_BCH_3] = 5, + [BRCMNAND_ECC_BCH_4] = 7, + [BRCMNAND_ECC_BCH_5] = 9, + [BRCMNAND_ECC_BCH_6] = 10, + [BRCMNAND_ECC_BCH_7] = 12, + [BRCMNAND_ECC_BCH_8] = 13, + [BRCMNAND_ECC_BCH_9] = 15, + [BRCMNAND_ECC_BCH_10] = 17, + [BRCMNAND_ECC_BCH_11] = 18, + [BRCMNAND_ECC_BCH_12] = 20, + [BRCMNAND_ECC_RESVD_1] = 0, + [BRCMNAND_ECC_RESVD_2] = 0, + [BRCMNAND_ECC_HAMMING] = 3, +}; + +static const unsigned char ffchars[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 16 */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 32 */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 48 */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 64 */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 80 */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 96 */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 112 */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 128 */ +}; + +static struct nand_ecclayout brcmnand_oob_128 = { + .eccbytes = 24, + .eccpos = + { + 6, 7, 8, + 22, 23, 24, + 38, 39, 40, + 54, 55, 56, + 70, 71, 72, + 86, 87, 88, + 102, 103, 104, + 118, 119, 120 + }, + .oobfree = + { + /* 0-1 used for BBT and/or manufacturer bad block marker, + * first slice loses 2 bytes for BBT + */ + {.offset = 2, .length = 4}, + {.offset = 9, .length = 13}, + /* First slice {9,7} 2nd slice {16,6}are combined */ + /* ST uses 6th byte (offset=5) as Bad Block Indicator, + * in addition to the 1st byte, and will be adjusted at run time + */ + {.offset = 25, .length = 13}, /* 2nd slice */ + {.offset = 41, .length = 13}, /* 4th slice */ + {.offset = 57, .length = 13}, /* 5th slice */ + {.offset = 73, .length = 13}, /* 6th slice */ + {.offset = 89, .length = 13}, /* 7th slice */ + {.offset = 105, .length = 13}, /* 8th slice */ + {.offset = 121, .length = 7}, /* 9th slice */ + {.offset = 0, .length = 0} /* End marker */ + } +}; + +static struct nand_ecclayout brcmnand_oob_64 = { + .eccbytes = 12, + .eccpos = + { + 6, 7, 8, + 22, 23, 24, + 38, 39, 40, + 54, 55, 56 + }, + .oobfree = + { + /* 0-1 used for BBT and/or manufacturer bad block marker, + * first slice loses 2 bytes for BBT + */ + {.offset = 2, .length = 4}, + {.offset = 9, .length = 13}, + /* First slice {9,7} 2nd slice {16,6}are combined */ + /* ST uses 6th byte (offset=5) as Bad Block Indicator, + * in addition to the 1st byte, and will be adjusted at run time + */ + {.offset = 25, .length = 13}, /* 2nd slice */ + {.offset = 41, .length = 13}, /* 3rd slice */ + {.offset = 57, .length = 7}, /* 4th slice */ + {.offset = 0, .length = 0} /* End marker */ + } +}; + +/** + * brcmnand_oob oob info for 512 page + */ +static struct nand_ecclayout brcmnand_oob_16 = { + .eccbytes = 3, + .eccpos = {6, 7, 8}, + .oobfree = { + {.offset = 0, .length = 5}, + {.offset = 9, .length = 7}, /* Byte 5 (6th byte) used for BI */ + {.offset = 0, .length = 0}} /* End marker */ + /* Bytes offset 4&5 are used by BBT. Actually only byte 5 is used, + * but in order to accomodate for 16 bit bus width, byte 4 is also not used. + * If we only use byte-width chip, (We did) + * then we can also use byte 4 as free bytes. + */ +}; + +/* Small page with BCH-4 */ +static struct nand_ecclayout brcmnand_oob_bch4_512 = { + .eccbytes = 7, + .eccpos = {9, 10, 11, 12, 13, 14, 15}, + .oobfree = { + {.offset = 0, .length = 5}, + {.offset = 7, .length = 2}, /* Byte 5 (6th byte) used for BI */ + {.offset = 0, .length = 0}} /* End marker */ +}; + +/* + * 2K page SLC/MLC with BCH-4 ECC, uses 7 ECC bytes per 512B ECC step + */ +static struct nand_ecclayout brcmnand_oob_bch4_2k = { + .eccbytes = 7 * 8, /* 7 * 8 = 56 bytes */ + .eccpos = + { + 9, 10, 11, 12, 13, 14, 15, + 25, 26, 27, 28, 29, 30, 31, + 41, 42, 43, 44, 45, 46, 47, + 57, 58, 59, 60, 61, 62, 63 + }, + .oobfree = + { + /* 0 used for BBT and/or manufacturer bad block marker, + * first slice loses 1 byte for BBT + */ + {.offset = 1, .length = 8}, /* 1st slice loses byte 0 */ + {.offset = 16, .length = 9}, /* 2nd slice */ + {.offset = 32, .length = 9}, /* 3rd slice */ + {.offset = 48, .length = 9}, /* 4th slice */ + {.offset = 0, .length = 0} /* End marker */ + } +}; + + +static void *page_buffer = NULL; + +/* Private global state */ +struct brcmnand_mtd brcmnand_info; + +static INLINE void +brcmnand_cmd(osl_t *osh, chipcregs_t *cc, uint opcode) +{ + W_REG(osh, &cc->nand_cmd_start, opcode); + /* read after write to flush the command */ + R_REG(osh, &cc->nand_cmd_start); +} + +int brcmnand_ctrl_verify_ecc(struct nand_chip *chip, int state) +{ + si_t *sih = brcmnand_info.sih; + chipcregs_t *cc = brcmnand_info.cc; + osl_t *osh; + uint32_t addr, ext_addr; + int err = 0; + + if (state != FL_READING) + return BRCMNAND_SUCCESS; + osh = si_osh(sih); + addr = R_REG(osh, &cc->nand_ecc_corr_addr); + if (addr) { + ext_addr = R_REG(osh, &cc->nand_ecc_corr_addr_x); + /* clear */ + W_REG(osh, &cc->nand_ecc_corr_addr, 0); + W_REG(osh, &cc->nand_ecc_corr_addr_x, 0); + err = BRCMNAND_CORRECTABLE_ECC_ERROR; + } + addr = R_REG(osh, &cc->nand_ecc_unc_addr); + if (addr) { + ext_addr = R_REG(osh, &cc->nand_ecc_unc_addr_x); + /* clear */ + W_REG(osh, &cc->nand_ecc_unc_addr, 0); + W_REG(osh, &cc->nand_ecc_unc_addr_x, 0); + /* If the block was just erased, and have not yet been written to, + * this will be flagged, so this could be a false alarm + */ + err = BRCMNAND_UNCORRECTABLE_ECC_ERROR; + } + return (err); +} + +uint32 brcmnand_poll(uint32 pollmask) +{ + si_t *sih = brcmnand_info.sih; + chipcregs_t *cc = brcmnand_info.cc; + osl_t *osh; + uint32 status; + + osh = si_osh(sih); + status = R_REG(osh, &cc->nand_intfc_status); + status &= pollmask; + + return status; +} + +int brcmnand_cache_is_valid(struct mtd_info *mtd, struct nand_chip *chip, int state) +{ + uint32 pollmask = NIST_CTRL_READY | 0x1; + unsigned long timeout = msecs_to_jiffies(BRCMNAND_POLL_TIMEOUT); + unsigned long now = jiffies; + uint32 status = 0; + int ret; + + for (;;) { + if ((status = brcmnand_poll(pollmask)) != 0) { + break; + } + if (time_after(jiffies, now + timeout)) { + status = brcmnand_poll(pollmask); + break; + } + udelay(1); + } + + if (status == 0) + ret = BRCMNAND_TIMED_OUT; + else if (status & 0x1) + ret = BRCMNAND_FLASH_STATUS_ERROR; + else + ret = brcmnand_ctrl_verify_ecc(chip, state); + + return ret; +} + +int brcmnand_spare_is_valid(struct mtd_info *mtd, struct nand_chip *chip, int state) +{ + uint32 pollmask = NIST_CTRL_READY; + unsigned long timeout = msecs_to_jiffies(BRCMNAND_POLL_TIMEOUT); + unsigned long now = jiffies; + uint32 status = 0; + int ret; + + for (;;) { + if ((status = brcmnand_poll(pollmask)) != 0) { + break; + } + if (time_after(jiffies, now + timeout)) { + status = brcmnand_poll(pollmask); + break; + } + udelay(1); + } + + if (status == 0) + ret = 0 /* timed out */; + else + ret = 1; + + return ret; +} + +/** + * nand_release_device - [GENERIC] release chip + * @mtd: MTD device structure + * + * Deselect, release chip lock and wake up anyone waiting on the device + */ +static void brcmnand_release_device(struct mtd_info *mtd) +{ + if (nflash_lock == 1) { + nflash_enable(brcmnand_info.sih, 0); + mutex_unlock(mtd->mutex); + } + nflash_lock --; +} + +/** + * brcmnand_get_device - [GENERIC] Get chip for selected access + * @param chip the nand chip descriptor + * @param mtd MTD device structure + * @param new_state the state which is requested + * + * Get the device and lock it for exclusive access + */ +static int brcmnand_get_device(struct nand_chip *chip, struct mtd_info *mtd, int new_state) +{ + if (nflash_lock == 0) { + mutex_lock(mtd->mutex); + nflash_enable(brcmnand_info.sih, 1); + } + nflash_lock ++; + return 0; +} + +/** + * brcmnand_release_device_bcm4706 - [GENERIC] release chip + * @mtd: MTD device structure + * + * Deselect, release chip lock and wake up anyone waiting on the device + */ +static void +brcmnand_release_device_bcm4706(struct mtd_info *mtd) +{ + mutex_unlock(mtd->mutex); +} + +/** + * brcmnand_get_device_bcm4706 - [GENERIC] Get chip for selected access + * @param chip the nand chip descriptor + * @param mtd MTD device structure + * @param new_state the state which is requested + * + * Get the device and lock it for exclusive access + */ +static int +brcmnand_get_device_bcm4706(struct nand_chip *chip, struct mtd_info *mtd, int new_state) +{ + mutex_lock(mtd->mutex); + return 0; +} + +/** + * brcmnand_block_checkbad - [GENERIC] Check if a block is marked bad + * @mtd: MTD device structure + * @ofs: offset from device start + * @getchip: 0, if the chip is already selected + * @allowbbt: 1, if its allowed to access the bbt area + * + * Check, if the block is bad. Either by reading the bad block table or + * calling of the scan function. + */ +static int brcmnand_block_checkbad(struct mtd_info *mtd, loff_t ofs, int getchip, + int allowbbt) +{ + struct nand_chip *chip = mtd->priv; + int ret; + + if (getchip) + brcmnand_get_device(chip, mtd, FL_READING); + + if (!chip->bbt) + ret = chip->block_bad(mtd, ofs, getchip); + else + ret = brcmnand_isbad_bbt(mtd, ofs, allowbbt); + + if (getchip) + brcmnand_release_device(mtd); + return (ret); +} + +/* + * Returns 0 on success + */ +static int brcmnand_handle_false_read_ecc_unc_errors(struct mtd_info *mtd, + struct nand_chip *chip, uint8_t *buf, uint8_t *oob, uint32_t offset) +{ + static uint32_t oobbuf[4]; + uint32_t *p32 = (oob ? (uint32_t *)oob : (uint32_t *)&oobbuf[0]); + int ret = 0; + uint8_t *oobarea; + int erased = 0, allFF = 0; + int i; + si_t *sih = brcmnand_info.sih; + chipcregs_t *cc = brcmnand_info.cc; + osl_t *osh; + + osh = si_osh(sih); + oobarea = (uint8_t *)p32; + for (i = 0; i < 4; i++) { + p32[i] = R_REG(osh, (uint32_t *)((uint32_t)&cc->nand_spare_rd0 + (i * 4))); + } + if (chip->ecc.level == BRCMNAND_ECC_HAMMING) { + erased = + (oobarea[6] == 0xff && oobarea[7] == 0xff && oobarea[8] == 0xff); + allFF = + (oobarea[6] == 0x00 && oobarea[7] == 0x00 && oobarea[8] == 0x00); + } else if (chip->ecc.level >= BRCMNAND_ECC_BCH_1 && + chip->ecc.level <= BRCMNAND_ECC_BCH_12) { + erased = allFF = 1; + /* For BCH-n, the ECC bytes are at the end of the OOB area */ + for (i = chip->ecc.oobsize - chip->ecc.bytes; i < chip->ecc.oobsize; i++) { + erased = erased && (oobarea[i] == 0xff); + allFF = allFF && (oobarea[i] == 0x00); + } + } else { + printk("BUG: Unsupported ECC level %d\n", chip->ecc.level); + BUG(); + } + + if (erased || allFF) { + /* + * For the first case, the slice is an erased block, and the ECC bytes + * are all 0xFF, for the 2nd, all bytes are 0xFF, so the Hamming Codes + * for it are all zeroes. The current version of the BrcmNAND + * controller treats these as un-correctable errors. For either case, + * fill data buffer with 0xff and return success. The error has + * already been cleared inside brcmnand_verify_ecc. Both case will be + * handled correctly by the BrcmNand controller in later releases. + */ + p32 = (uint32_t *)buf; + for (i = 0; i < chip->ecc.size/4; i++) { + p32[i] = 0xFFFFFFFF; + } + ret = 0; /* Success */ + } else { + /* Real error: Disturb read returns uncorrectable errors */ + ret = -EBADMSG; + printk("<-- %s: ret -EBADMSG\n", __FUNCTION__); + } + return ret; +} + +static int brcmnand_posted_read_cache(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf, uint8_t *oob, uint32_t offset) +{ + uint32_t mask = chip->ecc.size - 1; + si_t *sih = brcmnand_info.sih; + chipcregs_t *cc = brcmnand_info.cc; + osl_t *osh; + int valid; + uint32_t *to; + int ret = 0, i; + + if (offset & mask) + return -EINVAL; + + osh = si_osh(sih); + W_REG(osh, &cc->nand_cmd_addr, offset); + PLATFORM_IOFLUSH_WAR(); + brcmnand_cmd(osh, cc, NCMD_PAGE_RD); + valid = brcmnand_cache_is_valid(mtd, chip, FL_READING); + + switch (valid) { + case BRCMNAND_CORRECTABLE_ECC_ERROR: + case BRCMNAND_SUCCESS: + if (buf) { + to = (uint32_t *)buf; + PLATFORM_IOFLUSH_WAR(); + for (i = 0; i < chip->ecc.size; i += 4, to++) { + *to = R_REG(osh, &cc->nand_cache_data); + } + } + if (oob) { + to = (uint32_t *)oob; + PLATFORM_IOFLUSH_WAR(); + for (i = 0; i < chip->ecc.oobsize; i += 4, to++) { + *to = R_REG(osh, (uint32_t *)((uint32_t)&cc->nand_spare_rd0 + i)); + } + } + break; + case BRCMNAND_UNCORRECTABLE_ECC_ERROR: + ret = brcmnand_handle_false_read_ecc_unc_errors(mtd, chip, buf, oob, offset); + break; + case BRCMNAND_FLASH_STATUS_ERROR: + ret = -EBADMSG; + break; + case BRCMNAND_TIMED_OUT: + ret = -ETIMEDOUT; + break; + default: + ret = -EFAULT; + break; + } + + return (ret); +} + +/** + * nand_read_page_hwecc - [REPLACABLE] hardware ecc based page read function + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: buffer to store read data + * + * Not for syndrome calculating ecc controllers which need a special oob layout + */ +static int brcmnand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf) +{ + int eccsteps; + int data_read = 0; + int oob_read = 0; + int corrected = 0; + int ret = 0; + uint32_t offset = chip->pageidx << chip->page_shift; + uint8_t *oob = chip->oob_poi; + + brcmnand_get_device(chip, mtd, FL_READING); + for (eccsteps = 0; eccsteps < chip->ecc.steps; eccsteps++) { + ret = brcmnand_posted_read_cache(mtd, chip, &buf[data_read], + oob ? &oob[oob_read]: NULL, offset + data_read); + if (ret == BRCMNAND_CORRECTABLE_ECC_ERROR && !corrected) { + mtd->ecc_stats.corrected++; + corrected = 1; + ret = 0; + } else { + if (ret < 0) + break; + } + data_read += chip->ecc.size; + oob_read += chip->ecc.oobsize; + } + brcmnand_release_device(mtd); + return (ret); +} + +/** + * brcmnand_transfer_oob - [Internal] Transfer oob to client buffer + * @chip: nand chip structure + * @oob: oob destination address + * @ops: oob ops structure + * @len: size of oob to transfer + */ +static uint8_t *brcmnand_transfer_oob(struct nand_chip *chip, uint8_t *oob, + struct mtd_oob_ops *ops, size_t len) +{ + switch (ops->mode) { + + case MTD_OOB_PLACE: + case MTD_OOB_RAW: + memcpy(oob, chip->oob_poi + ops->ooboffs, len); + return oob + len; + + case MTD_OOB_AUTO: { + struct nand_oobfree *free = chip->ecc.layout->oobfree; + uint32_t boffs = 0, roffs = ops->ooboffs; + size_t bytes = 0; + + for (; free->length && len; free++, len -= bytes) { + /* Read request not from offset 0 ? */ + if (unlikely(roffs)) { + if (roffs >= free->length) { + roffs -= free->length; + continue; + } + boffs = free->offset + roffs; + bytes = min_t(size_t, len, + (free->length - roffs)); + roffs = 0; + } else { + bytes = min_t(size_t, len, free->length); + boffs = free->offset; + } + memcpy(oob, chip->oob_poi + boffs, bytes); + oob += bytes; + } + return oob; + } + default: + BUG(); + } + return NULL; +} + +/** + * brcmnand_do_read_ops - [Internal] Read data with ECC + * + * @mtd: MTD device structure + * @from: offset to read from + * @ops: oob ops structure + * + * Internal function. Called with chip held. + */ +static int brcmnand_do_read_ops(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + int page, realpage, col, bytes, aligned; + struct nand_chip *chip = mtd->priv; + struct mtd_ecc_stats stats; + int ret = 0; + uint32_t readlen = ops->len; + uint32_t oobreadlen = ops->ooblen; + uint8_t *bufpoi, *oob, *buf; + + stats = mtd->ecc_stats; + + realpage = (int)(from >> chip->page_shift); + page = realpage & chip->pagemask; + + col = (int)(from & (mtd->writesize - 1)); + + buf = ops->datbuf; + oob = ops->oobbuf; + + while (1) { + bytes = min(mtd->writesize - col, readlen); + aligned = (bytes == mtd->writesize); + + /* Is the current page in the buffer ? */ + if (realpage != chip->pagebuf || oob) { + bufpoi = aligned ? buf : chip->buffers->databuf; + chip->pageidx = page; + /* Now read the page into the buffer */ + ret = chip->ecc.read_page(mtd, chip, bufpoi); + if (ret < 0) + break; + + /* Transfer not aligned data */ + if (!aligned) { + chip->pagebuf = realpage; + memcpy(buf, chip->buffers->databuf + col, bytes); + } + + buf += bytes; + + if (unlikely(oob)) { + if (ops->mode != MTD_OOB_RAW) { + int toread = min(oobreadlen, + chip->ecc.layout->oobavail); + if (toread) { + oob = brcmnand_transfer_oob(chip, + oob, ops, toread); + oobreadlen -= toread; + } + } else + buf = brcmnand_transfer_oob(chip, + buf, ops, mtd->oobsize); + } + } else { + memcpy(buf, chip->buffers->databuf + col, bytes); + buf += bytes; + } + + readlen -= bytes; + + if (!readlen) + break; + + /* For subsequent reads align to page boundary. */ + col = 0; + /* Increment page address */ + realpage++; + + page = realpage & chip->pagemask; + } + + ops->retlen = ops->len - (size_t) readlen; + if (oob) + ops->oobretlen = ops->ooblen - oobreadlen; + + if (ret) + return ret; + + if (mtd->ecc_stats.failed - stats.failed) + return -EBADMSG; + + return mtd->ecc_stats.corrected - stats.corrected ? -EUCLEAN : 0; +} + +/** + * brcmnand_read - [MTD Interface] MTD compability function for nand_do_read_ecc + * @mtd: MTD device structure + * @from: offset to read from + * @len: number of bytes to read + * @retlen: pointer to variable to store the number of read bytes + * @buf: the databuffer to put data + * + * Get hold of the chip and call nand_do_read + */ +static int +brcmnand_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) +{ + struct nand_chip *chip = mtd->priv; + int ret; + + if ((from + len) > mtd->size) + return -EINVAL; + if (!len) + return 0; + + brcmnand_get_device(chip, mtd, FL_READING); + chip->ops.len = len; + chip->ops.datbuf = buf; + chip->ops.oobbuf = NULL; + + ret = brcmnand_do_read_ops(mtd, from, &chip->ops); + + *retlen = chip->ops.retlen; + + brcmnand_release_device(mtd); + + return ret; +} + +static int brcmnand_posted_read_oob(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *oob, uint32_t offset) +{ + uint32_t mask = chip->ecc.size - 1; + si_t *sih = brcmnand_info.sih; + chipcregs_t *cc = brcmnand_info.cc; + osl_t *osh; + int valid; + uint32 *to; + int ret = 0, i; + + if (offset & mask) + return -EINVAL; + + osh = si_osh(sih); + W_REG(osh, &cc->nand_cmd_addr, offset); + PLATFORM_IOFLUSH_WAR(); + brcmnand_cmd(osh, cc, NCMD_SPARE_RD); + valid = brcmnand_spare_is_valid(mtd, chip, FL_READING); + + switch (valid) { + case 1: + if (oob) { + to = (uint32 *)oob; + for (i = 0; i < chip->ecc.oobsize; i += 4, to++) { + *to = R_REG(osh, (uint32_t *)((uint32_t)&cc->nand_spare_rd0 + i)); + } + } + break; + case 0: + ret = -ETIMEDOUT; + break; + default: + ret = -EFAULT; + break; + } + return (ret); +} + +/** + * brcmnand_read_oob_hwecc - [REPLACABLE] the most common OOB data read function + * @mtd: mtd info structure + * @chip: nand chip info structure + * @page: page number to read + * @sndcmd: flag whether to issue read command or not + */ +static int brcmnand_read_oob_hwecc(struct mtd_info *mtd, struct nand_chip *chip, + int page, int sndcmd) +{ + int eccsteps; + int data_read = 0; + int oob_read = 0; + int corrected = 0; + int ret = 0; + uint32_t offset = page << chip->page_shift; + uint8_t *oob = chip->oob_poi; + + for (eccsteps = 0; eccsteps < chip->ecc.steps; eccsteps++) { + ret = brcmnand_posted_read_oob(mtd, chip, &oob[oob_read], offset + data_read); + if (ret == BRCMNAND_CORRECTABLE_ECC_ERROR && !corrected) { + mtd->ecc_stats.corrected++; + /* Only update stats once per page */ + corrected = 1; + ret = 0; + } else { + if (ret < 0) + break; + } + data_read += chip->ecc.size; + oob_read += chip->ecc.oobsize; + } + + return (ret); +} + +/** + * brcmnand_do_read_oob - [Intern] NAND read out-of-band + * @mtd: MTD device structure + * @from: offset to read from + * @ops: oob operations description structure + * + * NAND read out-of-band data from the spare area + */ +static int brcmnand_do_read_oob(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + int page, realpage; + struct nand_chip *chip = mtd->priv; + int readlen = ops->ooblen; + int len; + uint8_t *buf = ops->oobbuf; + int ret; + + DEBUG(MTD_DEBUG_LEVEL3, "nand_read_oob: from = 0x%08Lx, len = %i\n", + (unsigned long long)from, readlen); + + if (ops->mode == MTD_OOB_AUTO) + len = chip->ecc.layout->oobavail; + else + len = mtd->oobsize; + + if (unlikely(ops->ooboffs >= len)) { + DEBUG(MTD_DEBUG_LEVEL0, "nand_read_oob: " + "Attempt to start read outside oob\n"); + return -EINVAL; + } + + /* Do not allow reads past end of device */ + if (unlikely(from >= mtd->size || + ops->ooboffs + readlen > ((mtd->size >> chip->page_shift) - + (from >> chip->page_shift)) * len)) { + DEBUG(MTD_DEBUG_LEVEL0, "nand_read_oob: " + "Attempt read beyond end of device\n"); + return -EINVAL; + } + /* Shift to get page */ + realpage = (int)(from >> chip->page_shift); + page = realpage & chip->pagemask; + + while (1) { + ret = chip->ecc.read_oob(mtd, chip, page, 0); + if (ret) + break; + len = min(len, readlen); + buf = brcmnand_transfer_oob(chip, buf, ops, len); + + readlen -= len; + if (!readlen) + break; + + /* Increment page address */ + realpage++; + + page = realpage & chip->pagemask; + } + + ops->oobretlen = ops->ooblen; + return (ret); +} + +/** + * brcmnand_read_oob - [MTD Interface] NAND read data and/or out-of-band + * @mtd: MTD device structure + * @from: offset to read from + * @ops: oob operation description structure + * + * NAND read data and/or out-of-band data + */ +static int brcmnand_read_oob(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct nand_chip *chip = mtd->priv; + int ret = -ENOTSUPP; + + ops->retlen = 0; + + /* Do not allow reads past end of device */ + if (ops->datbuf && (from + ops->len) > mtd->size) { + DEBUG(MTD_DEBUG_LEVEL0, "brcmnand_read_oob: " + "Attempt read beyond end of device\n"); + return -EINVAL; + } + + brcmnand_get_device(chip, mtd, FL_READING); + + switch (ops->mode) { + case MTD_OOB_PLACE: + case MTD_OOB_AUTO: + case MTD_OOB_RAW: + break; + + default: + goto out; + } + + if (!ops->datbuf) + ret = brcmnand_do_read_oob(mtd, from, ops); + else + ret = brcmnand_do_read_ops(mtd, from, ops); + +out: + brcmnand_release_device(mtd); + return ret; +} + +static int brcmnand_ctrl_write_is_complete(struct mtd_info *mtd, struct nand_chip *chip, + int *need_bbt) +{ + uint32 pollmask = NIST_CTRL_READY | 0x1; + unsigned long timeout = msecs_to_jiffies(BRCMNAND_POLL_TIMEOUT); + unsigned long now = jiffies; + uint32 status = 0; + int ret; + + for (;;) { + if ((status = brcmnand_poll(pollmask)) != 0) { + break; + } + if (time_after(jiffies, now + timeout)) { + status = brcmnand_poll(pollmask); + break; + } + udelay(1); + } + + *need_bbt = 0; + if (status == 0) + ret = 0; /* timed out */ + else { + ret = 1; + if (status & 0x1) + *need_bbt = 1; + } + + return ret; +} + +/** + * brcmnand_posted_write - [BrcmNAND Interface] Write a buffer to the flash + * cache + * Assuming brcmnand_get_device() has been called to obtain exclusive lock + * + * @param mtd MTD data structure + * @param chip nand chip info structure + * @param buf the databuffer to put/get data + * @param oob Spare area, pass NULL if not interested + * @param offset offset to write to, and must be 512B aligned + * + */ +static int brcmnand_posted_write_cache(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf, uint8_t *oob, uint32_t offset) +{ + uint32_t mask = chip->ecc.size - 1; + si_t *sih = brcmnand_info.sih; + chipcregs_t *cc = brcmnand_info.cc; + osl_t *osh; + int i, ret = 0; + uint32_t *from; + + if (offset & mask) { + ret = -EINVAL; + goto out; + } + + osh = si_osh(sih); + from = (uint32_t *)buf; + for (i = 0; i < chip->ecc.size; i += 4, from++) { + W_REG(osh, &cc->nand_cache_data, *from); + } +out: + return (ret); +} + +/** + * brcmnand_write_page_hwecc - [REPLACABLE] hardware ecc based page write function + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: data buffer + */ +static void brcmnand_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf) +{ + int eccsize = chip->ecc.size; + int eccsteps; + int data_written = 0; + int oob_written = 0; + si_t *sih = brcmnand_info.sih; + chipcregs_t *cc = brcmnand_info.cc; + osl_t *osh; + uint32_t reg; + int ret = 0, need_bbt = 0; + uint32_t offset = chip->pageidx << chip->page_shift; + + uint8_t oob_buf[NAND_MAX_OOBSIZE]; + int *eccpos = chip->ecc.layout->eccpos; + int i; + uint8_t *oob = chip->oob_poi; + + brcmnand_get_device(chip, mtd, FL_READING); + osh = si_osh(sih); + /* full page write */ + /* disable partial page enable */ + reg = R_REG(osh, &cc->nand_acc_control); + reg &= ~NAC_PARTIAL_PAGE_EN; + W_REG(osh, &cc->nand_acc_control, reg); + + for (eccsteps = 0; eccsteps < chip->ecc.steps; eccsteps++) { + W_REG(osh, &cc->nand_cache_addr, 0); + W_REG(osh, &cc->nand_cmd_addr, data_written); + ret = brcmnand_posted_write_cache(mtd, chip, &buf[data_written], + oob ? &oob[oob_written]: NULL, offset + data_written); + if (ret < 0) { + goto out; + } + data_written += eccsize; + oob_written += chip->ecc.oobsize; + } + + W_REG(osh, &cc->nand_cmd_addr, offset + mtd->writesize - NFL_SECTOR_SIZE); + brcmnand_cmd(osh, cc, NCMD_PAGE_PROG); + if (brcmnand_ctrl_write_is_complete(mtd, chip, &need_bbt)) { + if (!need_bbt) { + /* write the oob */ + if (oob) { + /* Enable partial page program so that we can + * overwrite the spare area + */ + reg = R_REG(osh, &cc->nand_acc_control); + reg |= NAC_PARTIAL_PAGE_EN; + W_REG(osh, &cc->nand_acc_control, reg); + + memcpy(oob_buf, oob, NAND_MAX_OOBSIZE); + /* read from the spare area first */ + ret = chip->ecc.read_oob(mtd, chip, chip->pageidx, 0); + if (ret != 0) + goto out; + /* merge the oob */ + for (i = 0; i < chip->ecc.total; i++) + oob_buf[eccpos[i]] = chip->oob_poi[eccpos[i]]; + memcpy(chip->oob_poi, oob_buf, NAND_MAX_OOBSIZE); + /* write back to the spare area */ + ret = chip->ecc.write_oob(mtd, chip, chip->pageidx); + } + goto out; + } else { + ret = chip->block_markbad(mtd, offset); + goto out; + } + } + /* timed out */ + ret = -ETIMEDOUT; + +out: + if (ret != 0) + printk(KERN_ERR "brcmnand_write_page_hwecc failed\n"); + brcmnand_release_device(mtd); + return; +} + +/* + * brcmnand_posted_write_oob - [BrcmNAND Interface] Write the spare area + * @mtd: MTD data structure + * @chip: nand chip info structure + * @oob: Spare area, pass NULL if not interested. Must be able to + * hold mtd->oobsize (16) bytes. + * @offset: offset to write to, and must be 512B aligned + * + */ +static int brcmnand_posted_write_oob(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *oob, uint32_t offset) +{ + uint32_t mask = chip->ecc.size - 1; + si_t *sih = brcmnand_info.sih; + chipcregs_t *cc = brcmnand_info.cc; + osl_t *osh; + int i, ret = 0, need_bbt = 0; + uint32_t *from; + uint32_t reg; + uint8_t oob_buf0[16]; + + if (offset & mask) { + ret = -EINVAL; + goto out; + } + + osh = si_osh(sih); + /* Make sure we are in partial page program mode */ + reg = R_REG(osh, &cc->nand_acc_control); + reg |= NAC_PARTIAL_PAGE_EN; + W_REG(osh, &cc->nand_acc_control, reg); + + W_REG(osh, &cc->nand_cmd_addr, offset); + if (!oob) { + ret = -EINVAL; + goto out; + } + memcpy(oob_buf0, oob, chip->ecc.oobsize); + from = (uint32_t *)oob_buf0; + for (i = 0; i < chip->ecc.oobsize; i += 4, from++) { + W_REG(osh, (uint32_t *)((uint32_t)&cc->nand_spare_wr0 + i), *from); + } + PLATFORM_IOFLUSH_WAR(); + brcmnand_cmd(osh, cc, NCMD_SPARE_PROG); + if (brcmnand_ctrl_write_is_complete(mtd, chip, &need_bbt)) { + if (!need_bbt) { + ret = 0; + goto out; + } else { + ret = chip->block_markbad(mtd, offset); + goto out; + } + } + /* timed out */ + ret = -ETIMEDOUT; +out: + return ret; +} + + +/** + * brcmnand_write_page - [REPLACEABLE] write one page + * @mtd: MTD device structure + * @chip: NAND chip descriptor + * @buf: the data to write + * @page: page number to write + * @cached: cached programming + * @raw: use _raw version of write_page + */ +static int brcmnand_write_page(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf, int page, int cached, int raw) +{ + chip->pageidx = page; + chip->ecc.write_page(mtd, chip, buf); + + return 0; +} + +/** + * brcmnand_fill_oob - [Internal] Transfer client buffer to oob + * @chip: nand chip structure + * @oob: oob data buffer + * @ops: oob ops structure + */ +static uint8_t *brcmnand_fill_oob(struct nand_chip *chip, uint8_t *oob, + struct mtd_oob_ops *ops) +{ + size_t len = ops->ooblen; + + switch (ops->mode) { + + case MTD_OOB_PLACE: + case MTD_OOB_RAW: + memcpy(chip->oob_poi + ops->ooboffs, oob, len); + return oob + len; + + case MTD_OOB_AUTO: { + struct nand_oobfree *free = chip->ecc.layout->oobfree; + uint32_t boffs = 0, woffs = ops->ooboffs; + size_t bytes = 0; + + for (; free->length && len; free++, len -= bytes) { + /* Write request not from offset 0 ? */ + if (unlikely(woffs)) { + if (woffs >= free->length) { + woffs -= free->length; + continue; + } + boffs = free->offset + woffs; + bytes = min_t(size_t, len, + (free->length - woffs)); + woffs = 0; + } else { + bytes = min_t(size_t, len, free->length); + boffs = free->offset; + } + memcpy(chip->oob_poi + boffs, oob, bytes); + oob += bytes; + } + return oob; + } + default: + BUG(); + } + return NULL; +} + +#define NOTALIGNED(x) (x & (chip->subpagesize - 1)) != 0 + +/** + * brcmnand_do_write_ops - [Internal] NAND write with ECC + * @mtd: MTD device structure + * @to: offset to write to + * @ops: oob operations description structure + * + * NAND write with ECC + */ +static int brcmnand_do_write_ops(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + int realpage, page, blockmask; + struct nand_chip *chip = mtd->priv; + uint32_t writelen = ops->len; + uint8_t *oob = ops->oobbuf; + uint8_t *buf = ops->datbuf; + int ret; + + ops->retlen = 0; + if (!writelen) + return 0; + + /* reject writes, which are not page aligned */ + if (NOTALIGNED(to) || NOTALIGNED(ops->len)) { + printk(KERN_NOTICE "nand_write: " + "Attempt to write not page aligned data\n"); + return -EINVAL; + } + + + realpage = (int)(to >> chip->page_shift); + page = realpage & chip->pagemask; + blockmask = (1 << (chip->phys_erase_shift - chip->page_shift)) - 1; + + /* Invalidate the page cache, when we write to the cached page */ + if (to <= (chip->pagebuf << chip->page_shift) && + (chip->pagebuf << chip->page_shift) < (to + ops->len)) + chip->pagebuf = -1; + + /* If we're not given explicit OOB data, let it be 0xFF */ + if (likely(!oob)) + memset(chip->oob_poi, 0xff, mtd->oobsize); + + while (1) { + int bytes = mtd->writesize; + int cached = writelen > bytes && page != blockmask; + uint8_t *wbuf = buf; + + if (unlikely(oob)) + oob = brcmnand_fill_oob(chip, oob, ops); + + ret = chip->write_page(mtd, chip, wbuf, page, cached, + (ops->mode == MTD_OOB_RAW)); + if (ret) + break; + + writelen -= bytes; + if (!writelen) + break; + + buf += bytes; + realpage++; + + page = realpage & chip->pagemask; + } + + ops->retlen = ops->len - writelen; + if (unlikely(oob)) + ops->oobretlen = ops->ooblen; + return ret; +} + +/** + * brcmnand_write - [MTD Interface] NAND write with ECC + * @mtd: MTD device structure + * @to: offset to write to + * @len: number of bytes to write + * @retlen: pointer to variable to store the number of written bytes + * @buf: the data to write + * + * NAND write with ECC + */ +static int brcmnand_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const uint8_t *buf) +{ + struct nand_chip *chip = mtd->priv; + int ret; + + /* Do not allow reads past end of device */ + if ((to + len) > mtd->size) + return -EINVAL; + if (!len) + return 0; + + brcmnand_get_device(chip, mtd, FL_WRITING); + + chip->ops.len = len; + chip->ops.datbuf = (uint8_t *)buf; + chip->ops.oobbuf = NULL; + + ret = brcmnand_do_write_ops(mtd, to, &chip->ops); + + *retlen = chip->ops.retlen; + + brcmnand_release_device(mtd); + + return ret; +} + +/** + * brcmnand_write_oob_hwecc - [INTERNAL] write one page + * @mtd: MTD device structure + * @chip: NAND chip descriptor. The oob_poi ptr points to the OOB buffer. + * @page: page number to write + */ +static int brcmnand_write_oob_hwecc(struct mtd_info *mtd, struct nand_chip *chip, int page) +{ + int eccsteps; + int oob_written = 0, data_written = 0; + uint32_t offset = page << chip->page_shift; + uint8_t *oob = chip->oob_poi; + int ret = 0; + + for (eccsteps = 0; eccsteps < chip->ecc.steps; eccsteps++) { + ret = brcmnand_posted_write_oob(mtd, chip, oob + oob_written, + offset + data_written); + if (ret < 0) + break; + data_written += chip->ecc.size; + oob_written += chip->ecc.oobsize; + } + return (ret); +} + +/** + * brcmnand_do_write_oob - [MTD Interface] NAND write out-of-band + * @mtd: MTD device structure + * @to: offset to write to + * @ops: oob operation description structure + * + * NAND write out-of-band + */ +static int brcmnand_do_write_oob(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + int page, status, len; + struct nand_chip *chip = mtd->priv; + + DEBUG(MTD_DEBUG_LEVEL3, "nand_write_oob: to = 0x%08x, len = %i\n", + (unsigned int)to, (int)ops->ooblen); + + if (ops->mode == MTD_OOB_AUTO) + len = chip->ecc.layout->oobavail; + else + len = mtd->oobsize; + + /* Do not allow write past end of page */ + if ((ops->ooboffs + ops->ooblen) > len) { + DEBUG(MTD_DEBUG_LEVEL0, "nand_write_oob: " + "Attempt to write past end of page\n"); + return -EINVAL; + } + + if (unlikely(ops->ooboffs >= len)) { + DEBUG(MTD_DEBUG_LEVEL0, "nand_write_oob: " + "Attempt to start write outside oob\n"); + return -EINVAL; + } + + /* Do not allow reads past end of device */ + if (unlikely(to >= mtd->size || + ops->ooboffs + ops->ooblen > + ((mtd->size >> chip->page_shift) - + (to >> chip->page_shift)) * len)) { + DEBUG(MTD_DEBUG_LEVEL0, "nand_read_oob: " + "Attempt write beyond end of device\n"); + return -EINVAL; + } + + + /* Shift to get page */ + page = (int)(to >> chip->page_shift); + + /* Invalidate the page cache, if we write to the cached page */ + if (page == chip->pagebuf) + chip->pagebuf = -1; + + memset(chip->oob_poi, 0xff, mtd->oobsize); + brcmnand_fill_oob(chip, ops->oobbuf, ops); + status = chip->ecc.write_oob(mtd, chip, page & chip->pagemask); + memset(chip->oob_poi, 0xff, mtd->oobsize); + + if (status) + return status; + + ops->oobretlen = ops->ooblen; + + return 0; +} + +/** + * brcmnand_write_oob - [MTD Interface] NAND write data and/or out-of-band + * @mtd: MTD device structure + * @to: offset to write to + * @ops: oob operation description structure + */ +static int brcmnand_write_oob(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + struct nand_chip *chip = mtd->priv; + int ret = -ENOTSUPP; + + ops->retlen = 0; + + /* Do not allow writes past end of device */ + if (ops->datbuf && (to + ops->len) > mtd->size) { + DEBUG(MTD_DEBUG_LEVEL0, "brcmnand_write_oob: " + "Attempt write beyond end of device\n"); + return -EINVAL; + } + + brcmnand_get_device(chip, mtd, FL_WRITING); + + switch (ops->mode) { + case MTD_OOB_PLACE: + case MTD_OOB_AUTO: + case MTD_OOB_RAW: + break; + + default: + goto out; + } + + if (!ops->datbuf) + ret = brcmnand_do_write_oob(mtd, to, ops); + else + ret = brcmnand_do_write_ops(mtd, to, ops); + +out: + brcmnand_release_device(mtd); + return ret; +} + +static int brcmnand_erase_bbt(struct mtd_info *mtd, struct erase_info *instr, int allowbbt) +{ + struct nand_chip * chip = mtd->priv; + int page, len, pages_per_block, block_size; + loff_t addr; + int ret = 0; + int need_bbt = 0; + si_t *sih = brcmnand_info.sih; + chipcregs_t *cc = brcmnand_info.cc; + osl_t *osh; + + DEBUG(MTD_DEBUG_LEVEL3, "nand_erase: start = 0x%08x, len = %i\n", + (unsigned int)instr->addr, (unsigned int)instr->len); + + block_size = 1 << chip->phys_erase_shift; + + /* Start address must align on block boundary */ + if (instr->addr & (block_size - 1)) { + DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: Unaligned address\n"); + return -EINVAL; + } + + /* Length must align on block boundary */ + if (instr->len & (block_size - 1)) { + DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: " + "Length not block aligned\n"); + return -EINVAL; + } + + /* Do not allow erase past end of device */ + if ((instr->len + instr->addr) > mtd->size) { + DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: " + "Erase past end of device\n"); + return -EINVAL; + } + + instr->fail_addr = 0xffffffff; + + /* Grab the lock and see if the device is available */ + brcmnand_get_device(chip, mtd, FL_ERASING); + + /* Shift to get first page */ + page = (int)(instr->addr >> chip->page_shift); + + /* Calculate pages in each block */ + pages_per_block = 1 << (chip->phys_erase_shift - chip->page_shift); + + osh = si_osh(sih); + /* Clear ECC registers */ + W_REG(osh, &cc->nand_ecc_corr_addr, 0); + W_REG(osh, &cc->nand_ecc_corr_addr_x, 0); + W_REG(osh, &cc->nand_ecc_unc_addr, 0); + W_REG(osh, &cc->nand_ecc_unc_addr_x, 0); + + /* Loop throught the pages */ + len = instr->len; + addr = instr->addr; + instr->state = MTD_ERASING; + + while (len) { + /* + * heck if we have a bad block, we do not erase bad blocks ! + */ + if (brcmnand_block_checkbad(mtd, ((loff_t) page) << + chip->page_shift, 0, allowbbt)) { + printk(KERN_WARNING "nand_erase: attempt to erase a " + "bad block at page 0x%08x\n", page); + instr->state = MTD_ERASE_FAILED; + goto erase_exit; + } + + /* + * Invalidate the page cache, if we erase the block which + * contains the current cached page + */ + if (page <= chip->pagebuf && chip->pagebuf < + (page + pages_per_block)) + chip->pagebuf = -1; + + W_REG(osh, &cc->nand_cmd_addr, (page << chip->page_shift)); + brcmnand_cmd(osh, cc, NCMD_BLOCK_ERASE); + + /* Wait until flash is ready */ + ret = brcmnand_ctrl_write_is_complete(mtd, chip, &need_bbt); + + if (need_bbt) { + if (!allowbbt) { + DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: " + "Failed erase, page 0x%08x\n", page); + instr->state = MTD_ERASE_FAILED; + instr->fail_addr = (page << chip->page_shift); + chip->block_markbad(mtd, addr); + goto erase_exit; + } + } + + /* Increment page address and decrement length */ + len -= (1 << chip->phys_erase_shift); + page += pages_per_block; + } + instr->state = MTD_ERASE_DONE; + +erase_exit: + + ret = instr->state == MTD_ERASE_DONE ? 0 : -EIO; + /* Do call back function */ + if (!ret) + mtd_erase_callback(instr); + + /* Deselect and wake up anyone waiting on the device */ + brcmnand_release_device(mtd); + + return ret; +} + +static int +brcmnand_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + int allowbbt = 0; + int ret = 0; + + /* do not allow erase of bbt */ + ret = brcmnand_erase_bbt(mtd, instr, allowbbt); + + return ret; +} + +/** + * brcmnand_sync - [MTD Interface] sync + * @mtd: MTD device structure + * + * Sync is actually a wait for chip ready function + */ +static void brcmnand_sync(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + + DEBUG(MTD_DEBUG_LEVEL3, "nand_sync: called\n"); + + /* Grab the lock and see if the device is available */ + brcmnand_get_device(chip, mtd, FL_SYNCING); + PLATFORM_IOFLUSH_WAR(); + + /* Release it and go back */ + brcmnand_release_device(mtd); +} + +/** + * brcmnand_block_isbad - [MTD Interface] Check if block at offset is bad + * @mtd: MTD device structure + * @offs: offset relative to mtd start + */ +static int brcmnand_block_isbad(struct mtd_info *mtd, loff_t offs) +{ + /* Check for invalid offset */ + if (offs > mtd->size) + return -EINVAL; + + return brcmnand_block_checkbad(mtd, offs, 1, 0); +} + +/** + * brcmnand_default_block_markbad - [DEFAULT] mark a block bad + * @mtd: MTD device structure + * @ofs: offset from device start + * + * This is the default implementation, which can be overridden by + * a hardware specific driver. +*/ +static int brcmnand_default_block_markbad(struct mtd_info *mtd, loff_t ofs) +{ + struct nand_chip *chip = mtd->priv; + uint8_t bbmarker[1] = {0}; + uint8_t *buf = chip->oob_poi; + int block, ret; + int page, dir; + + /* Get block number */ + block = (int)(ofs >> chip->bbt_erase_shift); + /* Get page number */ + page = block << (chip->bbt_erase_shift - chip->page_shift); + dir = 1; + + if (chip->bbt) + chip->bbt[block >> 2] |= 0x01 << ((block & 0x03) << 1); + memcpy(buf, ffchars, NAND_MAX_OOBSIZE); + memcpy(buf + chip->badblockpos, bbmarker, sizeof(bbmarker)); + ret = chip->ecc.write_oob(mtd, chip, page); + page += dir; + ret = chip->ecc.write_oob(mtd, chip, page); + + /* According to the HW guy, even if the write fails, the controller have + * written a 0 pattern that certainly would have written a non 0xFF value + * into the BI marker. + * + * Ignoring ret. Even if we fail to write the BI bytes, just ignore it, + * and mark the block as bad in the BBT + */ + ret = brcmnand_update_bbt(mtd, ofs); + mtd->ecc_stats.badblocks++; + return ret; +} + +/** + * brcmnand_block_markbad - [MTD Interface] Mark block at the given offset as bad + * @mtd: MTD device structure + * @ofs: offset relative to mtd start + */ +static int brcmnand_block_markbad(struct mtd_info *mtd, loff_t ofs) +{ + struct nand_chip *chip = mtd->priv; + int ret; + + if ((ret = brcmnand_block_isbad(mtd, ofs))) { + /* If it was bad already, return success and do nothing. */ + if (ret > 0) + return 0; + return ret; + } + + return chip->block_markbad(mtd, ofs); +} + +/** + * brcmnand_suspend - [MTD Interface] Suspend the NAND flash + * @mtd: MTD device structure + */ +static int brcmnand_suspend(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + + return brcmnand_get_device(chip, mtd, FL_PM_SUSPENDED); +} + +/** + * brcmnand_resume - [MTD Interface] Resume the NAND flash + * @mtd: MTD device structure + */ +static void brcmnand_resume(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + + if (chip->state == FL_PM_SUSPENDED) + brcmnand_release_device(mtd); + else + printk(KERN_ERR "brcmnand_resume() called for a chip which is not " + "in suspended state\n"); +} + +struct mtd_partition brcmnand_parts[] = { + { + .name = "brcmnand", + .size = 0, + .offset = 0 + }, + { + .name = 0, + .size = 0, + .offset = 0 + } +}; + +struct mtd_partition *init_brcmnand_mtd_partitions(struct mtd_info *mtd, size_t size) +{ + brcmnand_parts[0].offset = NFL_BOOT_OS_SIZE; + brcmnand_parts[0].size = size - NFL_BOOT_OS_SIZE - NFL_BBT_SIZE; + + return brcmnand_parts; +} + +/** + * brcmnand_check_command_done - [DEFAULT] check if command is done + * @mtd: MTD device structure + * + * Return 0 to process next command + */ +static int +brcmnand_check_command_done(void) +{ + si_t *sih = brcmnand_info.sih; + chipcregs_t *cc = brcmnand_info.cc; + osl_t *osh; + int count = 0; + + osh = si_osh(sih); + + while (R_REG(osh, &cc->nflashctrl) & NFC_START) { + if (++count > BRCMNAND_POLL_TIMEOUT) { + printk("brcmnand_check_command_done: command timeout\n"); + return -1; + } + } + + return 0; +} + +/** + * brcmnand_hwcontrol - [DEFAULT] Issue command and address cycles to the chip + * @mtd: MTD device structure + * @cmd: the command to be sent + * @ctrl: the control code to be sent + * + * Issue command and address cycles to the chip + */ +static void +brcmnand_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl) +{ + si_t *sih = brcmnand_info.sih; + chipcregs_t *cc = brcmnand_info.cc; + osl_t *osh; + unsigned int val = 0; + + osh = si_osh(sih); + + if (cmd == NAND_CMD_NONE) + return; + + if (ctrl & NAND_CLE) { + val = cmd | NFC_CMD0; + } + else { + switch (ctrl & (NAND_ALE_COL | NAND_ALE_ROW)) { + case NAND_ALE_COL: + W_REG(osh, &cc->nflashcoladdr, cmd); + val = NFC_COL; + break; + case NAND_ALE_ROW: + W_REG(osh, &cc->nflashrowaddr, cmd); + val = NFC_ROW; + break; + default: + BUG(); + } + } + + /* nCS is not needed for reset command */ + if (cmd != NAND_CMD_RESET) + val |= NFC_CSA; + + val |= NFC_START; + W_REG(osh, &cc->nflashctrl, val); + + brcmnand_check_command_done(); +} + +/** + * brcmnand_command_lp - [DEFAULT] Send command to NAND large page device + * @mtd: MTD device structure + * @command: the command to be sent + * @column: the column address for this command, -1 if none + * @page_addr: the page address for this command, -1 if none + * + * Send command to NAND device. This is the version for the new large page + * devices We dont have the separate regions as we have in the small page + * devices. We must emulate NAND_CMD_READOOB to keep the code compatible. + */ +static void +brcmnand_command_lp(struct mtd_info *mtd, unsigned int command, int column, int page_addr) +{ + register struct nand_chip *chip = mtd->priv; + + /* Emulate NAND_CMD_READOOB */ + if (command == NAND_CMD_READOOB) { + column += mtd->writesize; + command = NAND_CMD_READ0; + } + + /* Command latch cycle */ + chip->cmd_ctrl(mtd, command & 0xff, NAND_NCE | NAND_CLE); + + if (column != -1 || page_addr != -1) { + int ctrl = NAND_NCE | NAND_ALE; + + /* Serially input address */ + if (column != -1) { + ctrl |= NAND_ALE_COL; + + /* Adjust columns for 16 bit buswidth */ + if (chip->options & NAND_BUSWIDTH_16) + column >>= 1; + + chip->cmd_ctrl(mtd, column, ctrl); + } + + if (page_addr != -1) { + ctrl &= ~NAND_ALE_COL; + ctrl |= NAND_ALE_ROW; + + chip->cmd_ctrl(mtd, page_addr, ctrl); + } + } + + chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE); + + /* + * program and erase have their own busy handlers + * status, sequential in, and deplete1 need no delay + */ + switch (command) { + + case NAND_CMD_CACHEDPROG: + case NAND_CMD_PAGEPROG: + case NAND_CMD_ERASE1: + case NAND_CMD_ERASE2: + case NAND_CMD_SEQIN: + case NAND_CMD_RNDIN: + case NAND_CMD_STATUS: + case NAND_CMD_DEPLETE1: + return; + + /* + * read error status commands require only a short delay + */ + case NAND_CMD_STATUS_ERROR: + case NAND_CMD_STATUS_ERROR0: + case NAND_CMD_STATUS_ERROR1: + case NAND_CMD_STATUS_ERROR2: + case NAND_CMD_STATUS_ERROR3: + udelay(chip->chip_delay); + return; + + case NAND_CMD_RESET: + if (chip->dev_ready) + break; + + udelay(chip->chip_delay); + + chip->cmd_ctrl(mtd, NAND_CMD_STATUS, NAND_NCE | NAND_CLE); + chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE); + + while (!(chip->read_byte(mtd) & NAND_STATUS_READY)); + return; + + case NAND_CMD_RNDOUT: + /* No ready / busy check necessary */ + chip->cmd_ctrl(mtd, NAND_CMD_RNDOUTSTART, NAND_NCE | NAND_CLE); + chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE); + return; + + case NAND_CMD_READ0: + chip->cmd_ctrl(mtd, NAND_CMD_READSTART, NAND_NCE | NAND_CLE); + chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE); + + /* This applies to read commands */ + default: + /* + * If we don't have access to the busy pin, we apply the given + * command delay + */ + if (!chip->dev_ready) { + udelay(chip->chip_delay); + return; + } + } + + /* Apply this short delay always to ensure that we do wait tWB in + * any case on any machine. + */ + ndelay(100); + + nand_wait_ready(mtd); +} + +/** + * brcmnand_command - [DEFAULT] Send command to NAND device + * @mtd: MTD device structure + * @command: the command to be sent + * @column: the column address for this command, -1 if none + * @page_addr: the page address for this command, -1 if none + * + * Send command to NAND device. This function is used for small page + * devices (256/512 Bytes per page) + */ +static void +brcmnand_command(struct mtd_info *mtd, unsigned int command, int column, int page_addr) +{ + register struct nand_chip *chip = mtd->priv; + int ctrl = NAND_CTRL_CLE; + + /* Invoke large page command function */ + if (mtd->writesize > 512) { + brcmnand_command_lp(mtd, command, column, page_addr); + return; + } + + /* + * Write out the command to the device. + */ + if (command == NAND_CMD_SEQIN) { + int readcmd; + + if (column >= mtd->writesize) { + /* OOB area */ + column -= mtd->writesize; + readcmd = NAND_CMD_READOOB; + } else if (column < 256) { + /* First 256 bytes --> READ0 */ + readcmd = NAND_CMD_READ0; + } else { + column -= 256; + readcmd = NAND_CMD_READ1; + } + + chip->cmd_ctrl(mtd, readcmd, ctrl); + } + + chip->cmd_ctrl(mtd, command, ctrl); + + /* + * Address cycle, when necessary + */ + ctrl = NAND_CTRL_ALE; + + /* Serially input address */ + if (column != -1) { + ctrl |= NAND_ALE_COL; + + /* Adjust columns for 16 bit buswidth */ + if (chip->options & NAND_BUSWIDTH_16) + column >>= 1; + + chip->cmd_ctrl(mtd, column, ctrl); + } + + if (page_addr != -1) { + ctrl &= ~NAND_ALE_COL; + ctrl |= NAND_ALE_ROW; + + chip->cmd_ctrl(mtd, page_addr, ctrl); + } + + chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE); + + /* + * program and erase have their own busy handlers + * status and sequential in needs no delay + */ + switch (command) { + + case NAND_CMD_PAGEPROG: + case NAND_CMD_ERASE1: + case NAND_CMD_ERASE2: + case NAND_CMD_SEQIN: + case NAND_CMD_STATUS: + return; + + case NAND_CMD_RESET: + if (chip->dev_ready) + break; + + udelay(chip->chip_delay); + + chip->cmd_ctrl(mtd, NAND_CMD_STATUS, NAND_CTRL_CLE); + + chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE); + + while (!(chip->read_byte(mtd) & NAND_STATUS_READY)); + + return; + + /* This applies to read commands */ + default: + /* + * If we don't have access to the busy pin, we apply the given + * command delay + */ + if (!chip->dev_ready) { + udelay(chip->chip_delay); + return; + } + } + + /* Apply this short delay always to ensure that we do wait tWB in + * any case on any machine. + */ + ndelay(100); + + nand_wait_ready(mtd); +} + +/** + * brcmnand_read_byte - [DEFAULT] read one byte from the chip + * @mtd: MTD device structure + * + * Default read function for 8bit bus width + */ +static uint8_t +brcmnand_read_byte(struct mtd_info *mtd) +{ + si_t *sih = brcmnand_info.sih; + chipcregs_t *cc = brcmnand_info.cc; + osl_t *osh; + register struct nand_chip *chip = mtd->priv; + unsigned int val; + + osh = si_osh(sih); + + val = NFC_DREAD | NFC_CSA | NFC_START; + W_REG(osh, &cc->nflashctrl, val); + + brcmnand_check_command_done(); + + return readb(chip->IO_ADDR_R); +} + +/** + * brcmnand_write_byte - [DEFAULT] write one byte from the chip + * @mtd: MTD device structure + * + * Default write function for 8bit bus width + */ +static int +brcmnand_write_byte(struct mtd_info *mtd, u_char ch) +{ + si_t *sih = brcmnand_info.sih; + chipcregs_t *cc = brcmnand_info.cc; + osl_t *osh; + unsigned int val; + + osh = si_osh(sih); + + W_REG(osh, &cc->nflashdata, (unsigned int)ch); + + val = NFC_DWRITE | NFC_CSA | NFC_START; + W_REG(osh, &cc->nflashctrl, val); + + brcmnand_check_command_done(); + + return 0; +} + +/** + * brcmnand_read_buf - [DEFAULT] read data from chip into buf + * @mtd: MTD device structure + * @buf: data buffer + * @len: number of bytes to read + * + * Default read function for 8bit bus width + */ +static void +brcmnand_read_buf(struct mtd_info *mtd, u_char *buf, int len) +{ + int count = 0; + + while (len > 0) { + buf[count++] = brcmnand_read_byte(mtd); + len--; + } +} + +/** + * brcmnand_write_buf - [DEFAULT] write buffer to chip + * @mtd: MTD device structure + * @buf: data buffer + * @len: number of bytes to write + * + * Default write function for 8bit bus width + */ +static void +brcmnand_write_buf(struct mtd_info *mtd, const u_char *buf, int len) +{ + int count = 0; + + while (len > 0) { + brcmnand_write_byte(mtd, buf[count++]); + len--; + } +} + +/** + * nand_verify_buf - [DEFAULT] Verify chip data against buffer + * @mtd: MTD device structure + * @buf: buffer containing the data to compare + * @len: number of bytes to compare + * + * Default verify function for 8bit buswith + */ +static int +brcmnand_verify_buf(struct mtd_info *mtd, const uint8_t *buf, int len) +{ + int i; + struct nand_chip *chip = mtd->priv; + uint8_t chbuf; + + for (i = 0; i < len; i++) { + chbuf = chip->read_byte(mtd); + if (buf[i] != chbuf) { + return -EFAULT; + } + } + + return 0; +} + +/** + * brcmnand_devready - [DEFAULT] Check if nand flash device is ready + * @mtd: MTD device structure + * + * Return 0 if nand flash device is busy + */ +static int +brcmnand_devready(struct mtd_info *mtd) +{ + si_t *sih = brcmnand_info.sih; + chipcregs_t *cc = brcmnand_info.cc; + osl_t *osh; + int status; + + osh = si_osh(sih); + + status = (R_REG(osh, &cc->nflashctrl) & NFC_RDYBUSY) ? 1 : 0; + + return status; +} + +/** + * brcmnand_select_chip - [DEFAULT] select chip + * @mtd: MTD device structure + * @chip: chip to be selected + * + * For BCM4706 just return because of only one chip is used + */ +static void +brcmnand_select_chip(struct mtd_info *mtd, int chip) +{ + return; +} + +/** + * brcmnand_init_nandchip - [DEFAULT] init mtd_info and nand_chip + * @mtd: MTD device structure + * @chip: chip to be selected + * + */ +static int +brcmnand_init_nandchip(struct mtd_info *mtd, struct nand_chip *chip) +{ + chipcregs_t *cc = brcmnand_info.cc; + int ret = 0; + + chip->cmdfunc = brcmnand_command; + chip->read_byte = brcmnand_read_byte; + chip->write_buf = brcmnand_write_buf; + chip->read_buf = brcmnand_read_buf; + chip->verify_buf = brcmnand_verify_buf; + chip->select_chip = brcmnand_select_chip; + chip->cmd_ctrl = brcmnand_hwcontrol; + chip->dev_ready = brcmnand_devready; + chip->get_device = brcmnand_get_device_bcm4706; + chip->release_device = brcmnand_release_device_bcm4706; + + chip->numchips = 1; + chip->chip_shift = 0; + chip->chip_delay = 50; + chip->priv = mtd; + chip->options = NAND_USE_FLASH_BBT; + + chip->controller = &chip->hwcontrol; + spin_lock_init(&chip->controller->lock); + init_waitqueue_head(&chip->controller->wq); + + chip->IO_ADDR_W = (void __iomem *)&cc->nflashdata; + chip->IO_ADDR_R = chip->IO_ADDR_W; + + /* BCM4706 only support software ECC mode */ + chip->ecc.mode = NAND_ECC_SOFT; + chip->ecc.layout = NULL; + + mtd->name = "brcmnand"; + mtd->priv = chip; + mtd->owner = THIS_MODULE; + + mtd->mutex = partitions_mutex_init(); + if (!mtd->mutex) + ret = -ENOMEM; + + return ret; +} + +static int __init +brcmnand_mtd_init(void) +{ + int ret = 0; + struct nflash *info; + struct pci_dev *dev = NULL; + struct nand_chip *chip; + struct mtd_info *mtd; +#ifdef CONFIG_MTD_PARTITIONS + struct mtd_partition *parts; + int i; +#endif + + list_for_each_entry(dev, &((pci_find_bus(0, 0))->devices), bus_list) { + if ((dev != NULL) && (dev->device == CC_CORE_ID)) + break; + } + + if (dev == NULL) { + printk(KERN_ERR "brcmnand: chipcommon not found\n"); + return -ENODEV; + } + + memset(&brcmnand_info, 0, sizeof(struct brcmnand_mtd)); + + /* attach to the backplane */ + if (!(brcmnand_info.sih = si_kattach(SI_OSH))) { + printk(KERN_ERR "brcmnand: error attaching to backplane\n"); + ret = -EIO; + goto fail; + } + + /* Map registers and flash base */ + if (!(brcmnand_info.cc = ioremap_nocache( + pci_resource_start(dev, 0), + pci_resource_len(dev, 0)))) { + printk(KERN_ERR "brcmnand: error mapping registers\n"); + ret = -EIO; + goto fail; + } + + /* Initialize serial flash access */ + if (!(info = nflash_init(brcmnand_info.sih, brcmnand_info.cc))) { + printk(KERN_ERR "brcmnand: found no supported devices\n"); + ret = -ENODEV; + goto fail; + } + + if (CHIPID(brcmnand_info.sih->chip) == BCM4706_CHIP_ID) { + mtd = &brcmnand_info.mtd; + chip = &brcmnand_info.chip; + + if ((ret = brcmnand_init_nandchip(mtd, chip)) != 0) { + printk(KERN_ERR "brcmnand_mtd_init: brcmnand_init_nandchip failed\n"); + goto fail; + } + + if ((ret = nand_scan(mtd, chip->numchips)) != 0) { + printk(KERN_ERR "brcmnand_mtd_init: nand_scan failed\n"); + goto fail; + } + + goto init_partitions; + } + + page_buffer = kmalloc(sizeof(struct nand_buffers), GFP_KERNEL); + if (!page_buffer) { + printk(KERN_ERR "brcmnand: cannot allocate memory for page buffer\n"); + return -ENOMEM; + } + memset(page_buffer, 0, sizeof(struct nand_buffers)); + + chip = &brcmnand_info.chip; + mtd = &brcmnand_info.mtd; + + chip->ecc.mode = NAND_ECC_HW; + + chip->buffers = (struct nand_buffers *)page_buffer; + chip->numchips = 1; + chip->chip_shift = 0; + chip->priv = mtd; + chip->options |= NAND_USE_FLASH_BBT; + /* At most 2GB is supported */ + chip->chipsize = (info->size >= (1 << 11)) ? (1 << 31) : (info->size << 20); + chip->ecc.level = info->ecclevel; + + /* Register with MTD */ + mtd->name = "brcmnand"; + mtd->priv = &brcmnand_info.chip; + mtd->owner = THIS_MODULE; + mtd->mutex = partitions_mutex_init(); + if (!mtd->mutex) { + ret = -ENOMEM; + goto fail; + } + + mtd->size = chip->chipsize; + mtd->erasesize = info->blocksize; + mtd->writesize = info->pagesize; + /* 16B oob for 512B page, 64B for 2KB page, etc.. */ + mtd->oobsize = (info->pagesize >> 5); + + /* Calculate the address shift from the page size */ + chip->page_shift = ffs(mtd->writesize) - 1; + /* Convert chipsize to number of pages per chip -1. */ + chip->pagemask = (chip->chipsize >> chip->page_shift) - 1; + + chip->bbt_erase_shift = chip->phys_erase_shift = + ffs(mtd->erasesize) - 1; + chip->chip_shift = ffs(chip->chipsize) - 1; + + /* Set the bad block position */ + chip->badblockpos = (mtd->writesize > 512) ? + NAND_LARGE_BADBLOCK_POS : NAND_SMALL_BADBLOCK_POS; + + if (!chip->controller) { + chip->controller = &chip->hwcontrol; + spin_lock_init(&chip->controller->lock); + init_waitqueue_head(&chip->controller->wq); + } + + /* Preset the internal oob write buffer */ + memset(BRCMNAND_OOBBUF(chip->buffers), 0xff, mtd->oobsize); + + /* Set the internal oob buffer location, just after the page data */ + chip->oob_poi = BRCMNAND_OOBBUF(chip->buffers); + + /* + * If no default placement scheme is given, select an appropriate one + */ + if (!chip->ecc.layout) { + switch (mtd->oobsize) { + case 16: + if (chip->ecc.level == BRCMNAND_ECC_HAMMING) + chip->ecc.layout = &brcmnand_oob_16; + else + chip->ecc.layout = &brcmnand_oob_bch4_512; + break; + case 64: + if (chip->ecc.level == BRCMNAND_ECC_HAMMING) + chip->ecc.layout = &brcmnand_oob_64; + else if (chip->ecc.level == BRCMNAND_ECC_BCH_4) { + if (mtd->writesize == 2048) + chip->ecc.layout = &brcmnand_oob_bch4_2k; + else { + printk(KERN_ERR "Unsupported page size of %d\n", + mtd->writesize); + BUG(); + } + } + break; + case 128: + if (chip->ecc.level == BRCMNAND_ECC_HAMMING) + chip->ecc.layout = &brcmnand_oob_128; + else { + printk(KERN_ERR "Unsupported page size of %d\n", + mtd->writesize); + BUG(); + } + break; + default: + printk(KERN_WARNING "No oob scheme defined for " + "oobsize %d\n", mtd->oobsize); + BUG(); + } + } + + if (!chip->write_page) + chip->write_page = brcmnand_write_page; + + switch (chip->ecc.mode) { + case NAND_ECC_HW: + if (!chip->ecc.read_page) + chip->ecc.read_page = brcmnand_read_page_hwecc; + if (!chip->ecc.write_page) + chip->ecc.write_page = brcmnand_write_page_hwecc; + if (!chip->ecc.read_oob) + chip->ecc.read_oob = brcmnand_read_oob_hwecc; + if (!chip->ecc.write_oob) + chip->ecc.write_oob = brcmnand_write_oob_hwecc; + break; + case NAND_ECC_SOFT: + break; + case NAND_ECC_NONE: + break; + default: + printk(KERN_WARNING "Invalid NAND_ECC_MODE %d\n", + chip->ecc.mode); + BUG(); + break; + } + + /* + * The number of bytes available for a client to place data into + * the out of band area + */ + chip->ecc.layout->oobavail = 0; + for (i = 0; chip->ecc.layout->oobfree[i].length; i++) + chip->ecc.layout->oobavail += + chip->ecc.layout->oobfree[i].length; + mtd->oobavail = chip->ecc.layout->oobavail; + + /* + * Set the number of read / write steps for one page + */ + chip->ecc.size = NFL_SECTOR_SIZE; /* Fixed for Broadcom controller. */ + chip->ecc.oobsize = 16; /* Fixed for Hamming code or 4-bit BCH for now. */ + chip->ecc.bytes = brcmnand_eccbytes[chip->ecc.level]; + chip->ecc.steps = mtd->writesize / chip->ecc.size; + if (chip->ecc.steps * chip->ecc.size != mtd->writesize) { + printk(KERN_WARNING "Invalid ecc parameters\n"); + BUG(); + } + chip->ecc.total = chip->ecc.steps * chip->ecc.bytes; + + /* + * Allow subpage writes up to ecc.steps. Not possible for MLC + * FLASH. + */ + if (!(chip->options & NAND_NO_SUBPAGE_WRITE) && + !(chip->cellinfo & NAND_CI_CELLTYPE_MSK)) { + switch (chip->ecc.steps) { + case 2: + mtd->subpage_sft = 1; + break; + case 4: + mtd->subpage_sft = 2; + break; + case 8: + mtd->subpage_sft = 3; + break; + } + } + chip->subpagesize = mtd->writesize >> mtd->subpage_sft; + + /* Initialize state */ + chip->state = FL_READY; + + /* Invalidate the pagebuffer reference */ + chip->pagebuf = -1; + + if (!chip->block_markbad) + chip->block_markbad = brcmnand_default_block_markbad; + if (!chip->scan_bbt) + chip->scan_bbt = brcmnand_default_bbt; + if (!chip->erase_bbt) + chip->erase_bbt = brcmnand_erase_bbt; + + if (!chip->get_device) + chip->get_device = brcmnand_get_device; + if (!chip->release_device) + chip->release_device = brcmnand_release_device; + + mtd->type = MTD_NANDFLASH; + mtd->flags = MTD_CAP_NANDFLASH; + mtd->erase = brcmnand_erase; + mtd->point = NULL; + mtd->unpoint = NULL; + mtd->read = brcmnand_read; + mtd->write = brcmnand_write; + mtd->read_oob = brcmnand_read_oob; + mtd->write_oob = brcmnand_write_oob; + mtd->sync = brcmnand_sync; + mtd->lock = NULL; + mtd->unlock = NULL; + mtd->suspend = brcmnand_suspend; + mtd->resume = brcmnand_resume; + mtd->block_isbad = brcmnand_block_isbad; + mtd->block_markbad = brcmnand_block_markbad; + + /* propagate ecc.layout to mtd_info */ + mtd->ecclayout = chip->ecc.layout; + + ret = chip->scan_bbt(mtd); + if (ret) { + printk(KERN_ERR "brcmnand: scan_bbt failed\n"); + goto fail; + } + +init_partitions: +#ifdef CONFIG_MTD_PARTITIONS + parts = init_brcmnand_mtd_partitions(mtd, mtd->size); + if (!parts) + goto fail; + for (i = 0; parts[i].name; i++); + ret = add_mtd_partitions(mtd, parts, i); + if (ret) { + printk(KERN_ERR "brcmnand: add_mtd failed\n"); + goto fail; + } + brcmnand_info.parts = parts; +#endif + return 0; + +fail: + if (brcmnand_info.cc) + iounmap((void *) brcmnand_info.cc); + if (brcmnand_info.sih) + si_detach(brcmnand_info.sih); + if (page_buffer) + kfree(page_buffer); + return ret; +} + +static void __exit +brcmnand_mtd_exit(void) +{ +#ifdef CONFIG_MTD_PARTITIONS + del_mtd_partitions(&brcmnand_info.mtd); +#else + del_mtd_device(&brcmnand_info.mtd); +#endif + iounmap((void *) brcmnand_info.cc); + si_detach(brcmnand_info.sih); +} + +module_init(brcmnand_mtd_init); +module_exit(brcmnand_mtd_exit); diff --git a/release/src-rt/linux/linux-2.6/drivers/mtd/47xxnand/brcmnand_bbt.c b/release/src-rt/linux/linux-2.6/drivers/mtd/47xxnand/brcmnand_bbt.c new file mode 100755 index 0000000000..4592588964 --- /dev/null +++ b/release/src-rt/linux/linux-2.6/drivers/mtd/47xxnand/brcmnand_bbt.c @@ -0,0 +1,1406 @@ +/* + * Broadcom NAND flash BBT interface + * + * Copyright (C) 2010, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id $ + */ +/* + * When brcmnand_scan_bbt is called, then it tries to find the bad block table + * depending on the options in the bbt descriptor(s). If a bbt is found + * then the contents are read and the memory based bbt is created. If a + * mirrored bbt is selected then the mirror is searched too and the + * versions are compared. If the mirror has a greater version number + * than the mirror bbt is used to build the memory based bbt. + * If the tables are not versioned, then we "or" the bad block information. + * If one of the bbt's is out of date or does not exist it is (re)created. + * If no bbt exists at all then the device is scanned for factory marked + * good / bad blocks and the bad block tables are created. + * + * For manufacturer created bbts like the one found on M-SYS DOC devices + * the bbt is searched and read but never created + * + * The autogenerated bad block table is located in the last good blocks + * of the device. The table is mirrored, so it can be updated eventually. + * The table is marked in the oob area with an ident pattern and a version + * number which indicates which of both tables is more up to date. + * + * The table uses 2 bits per block + * 11b: block is good + * 00b: block is factory marked bad + * 01b, 10b: block is marked bad due to wear + * + * The memory bad block table uses the following scheme: + * 00b: block is good + * 01b: block is marked bad due to wear + * 10b: block is reserved (to protect the bbt area) + * 11b: block is factory marked bad + * + * Multichip devices like DOC store the bad block info per floor. + * + * Following assumptions are made: + * - bbts start at a page boundary, if autolocated on a block boundary + * - the space necessary for a bbt in FLASH does not exceed a block boundary + * + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "brcmnand_priv.h" + +#define PRINTK(...) + +extern int gClearBBT; +extern int gdebug; + +char brcmNandBBTMsg[1024]; + +/* brcmnand= + * rescan: 1. Rescan for bad blocks, and update existing BBT + * showbbt: 2. Print out the contents of the BBT on boot up. + * + * The following commands are implemented but should be removed for + * production builds. + * Use userspace flash_eraseall instead. + * These were intended for development debugging only. + * erase: 7. Erase entire flash, except CFE, and rescan for bad blocks + * eraseall: 8. Erase entire flash, and rescan for bad blocks + * clearbbt: 9. Erase BBT and rescan for bad blocks. + * (DANGEROUS, may lose Mfg's BIs). + */ +#define NANDCMD_RESCAN 1 +#define NANDCMD_SHOWBBT 2 + +#define NANDCMD_ERASE 7 +#define NANDCMD_ERASEALL 8 +#define NANDCMD_CLEARBBT 9 + +int brcmnand_update_bbt(struct mtd_info *mtd, loff_t offs); + +extern struct brcmnand_mtd brcmnand_info; + +static inline uint32_t device_size(struct mtd_info *mtd) +{ + return mtd->size; +} + +/** + * check_pattern - [GENERIC] check if a pattern is in the buffer + * @buf: the buffer to search + * @len: the length of buffer to search + * @paglen: the pagelength + * @td: search pattern descriptor + * + * Check for a pattern at the given place. Used to search bad block + * tables and good / bad block identifiers. + * If the SCAN_EMPTY option is set then check, if all bytes except the + * pattern area contain 0xff + * +*/ +static int check_pattern(uint8_t *buf, int len, int paglen, struct nand_bbt_descr *td) +{ + int i, end = 0; + uint8_t *p = buf; + + end = paglen + td->offs; + if (td->options & NAND_BBT_SCANEMPTY) { + for (i = 0; i < end; i++) { + if (p[i] != 0xff) + return -1; + } + } + p += end; + + /* Compare the pattern */ + for (i = 0; i < td->len; i++) { + if (p[i] != td->pattern[i]) + return -1; + } + + if (td->options & NAND_BBT_SCANEMPTY) { + p += td->len; + end += td->len; + for (i = end; i < len; i++) { + if (*p++ != 0xff) + return -1; + } + } + return 0; +} + +/** + * check_short_pattern - [GENERIC] check if a pattern is in the buffer + * @buf: the buffer to search + * @td: search pattern descriptor + * + * Check for a pattern at the given place. Used to search bad block + * tables and good / bad block identifiers. Same as check_pattern, but + * no optional empty check + * +*/ +static int check_short_pattern(uint8_t *buf, struct nand_bbt_descr *td) +{ + int i; + uint8_t *p = buf; + + /* Compare the pattern */ + for (i = 0; i < td->len; i++) { + if (p[td->offs + i] != td->pattern[i]) + return -1; + } + return 0; +} + +/** + * brcmnand_read_bbt - [GENERIC] Read the bad block table starting from page + * @mtd: MTD device structure + * @buf: temporary buffer + * @page: the starting page + * @num: the number of bbt descriptors to read + * @bits: number of bits per block + * @offs: offset in the memory table + * @reserved_block_code: Pattern to identify reserved blocks + * + * Read the bad block table starting from page. + * + */ +static int brcmnand_read_bbt(struct mtd_info *mtd, uint8_t *buf, int page, int num, + int bits, int offs, int reserved_block_code) +{ + int res, i, j, act = 0; + struct nand_chip *this = mtd->priv; + size_t retlen, len, totlen; + loff_t from; + uint8_t msk = (uint8_t) ((1 << bits) - 1); + si_t *sih = brcmnand_info.sih; + chipcregs_t *cc = brcmnand_info.cc; + osl_t *osh; + + totlen = (num * bits) >> 3; + from = ((loff_t) page) << this->page_shift; + + osh = si_osh(sih); + /* Clear ECC registers */ + W_REG(osh, &cc->nand_ecc_corr_addr, 0); + W_REG(osh, &cc->nand_ecc_corr_addr_x, 0); + W_REG(osh, &cc->nand_ecc_unc_addr, 0); + W_REG(osh, &cc->nand_ecc_unc_addr_x, 0); + + while (totlen) { + len = min(totlen, (size_t) (1 << this->bbt_erase_shift)); + res = mtd->read(mtd, from, len, &retlen, buf); + if (res < 0) { + if (retlen != len) { + printk(KERN_INFO "nand_bbt: Error reading bad block table\n"); + return res; + } + printk(KERN_WARNING "nand_bbt: ECC error while reading bad block table\n"); + } + + /* Analyse data */ + for (i = 0; i < len; i++) { + uint8_t dat = buf[i]; + for (j = 0; j < 8; j += bits, act += 2) { + uint8_t tmp = (dat >> j) & msk; + if (tmp == msk) + continue; + if (reserved_block_code && (tmp == reserved_block_code)) { + printk(KERN_DEBUG + "nand_read_bbt: Reserved block at 0x%08x\n", + ((offs << 2) + (act >> 1)) + << this->bbt_erase_shift); + this->bbt[offs + (act >> 3)] |= 0x2 << (act & 0x06); + mtd->ecc_stats.bbtblocks++; + continue; + } + /* Leave it for now, if its matured we can move this + * message to MTD_DEBUG_LEVEL0 + */ + printk(KERN_DEBUG "nand_read_bbt: Bad block at 0x%08x\n", + ((offs << 2) + (act >> 1)) << this->bbt_erase_shift); + printk("nand_read_bbt: Bad block at 0x%08x\n", + ((offs << 2) + (act >> 1)) << this->bbt_erase_shift); + /* Factory marked bad or worn out ? */ + if (tmp == 0) + this->bbt[offs + (act >> 3)] |= 0x3 << (act & 0x06); + else + this->bbt[offs + (act >> 3)] |= 0x1 << (act & 0x06); + mtd->ecc_stats.badblocks++; + } + } + totlen -= len; + from += len; + } + return 0; +} + +/** + * brcmnand_read_abs_bbt - [GENERIC] Read the bad block table starting at a given page + * @mtd: MTD device structure + * @buf: temporary buffer + * @td: descriptor for the bad block table + * @chip: read the table for a specific chip, -1 read all chips. + * Applies only if NAND_BBT_PERCHIP option is set + * + * Read the bad block table for all chips starting at a given page + * We assume that the bbt bits are in consecutive order. +*/ +static int brcmnand_read_abs_bbt(struct mtd_info *mtd, uint8_t *buf, + struct nand_bbt_descr *td, int chip) +{ + struct nand_chip *this = mtd->priv; + int res = 0, i; + int bits; + + bits = td->options & NAND_BBT_NRBITS_MSK; + if (td->options & NAND_BBT_PERCHIP) { + int offs = 0; + for (i = 0; i < this->numchips; i++) { + if (chip == -1 || chip == i) + res = brcmnand_read_bbt(mtd, buf, td->pages[i], + this->chipsize >> this->bbt_erase_shift, + bits, offs, td->reserved_block_code); + if (res) + return res; + offs += this->chipsize >> (this->bbt_erase_shift + 2); + } + } else { + res = brcmnand_read_bbt(mtd, buf, td->pages[0], + mtd->size >> this->bbt_erase_shift, bits, 0, td->reserved_block_code); + if (res) + return res; + } + return 0; +} + +/* + * Scan read raw data from flash + */ +static int brcmnand_scan_read_raw(struct mtd_info *mtd, uint8_t *buf, loff_t offs, + size_t len) +{ + struct mtd_oob_ops ops; + + ops.mode = MTD_OOB_RAW; + ops.ooboffs = 0; + ops.ooblen = mtd->oobsize; + ops.oobbuf = buf; + ops.datbuf = buf; + ops.len = len; + + return mtd->read_oob(mtd, offs, &ops); +} + +/* + * Scan write data with oob to flash + */ +static int brcmnand_scan_write_bbt(struct mtd_info *mtd, loff_t offs, size_t len, + uint8_t *buf, uint8_t *oob) +{ + struct mtd_oob_ops ops; + + ops.mode = MTD_OOB_PLACE; + ops.ooboffs = 0; + ops.ooblen = mtd->oobsize; + ops.datbuf = buf; + ops.oobbuf = oob; + ops.len = len; + + return mtd->write_oob(mtd, offs, &ops); +} + +/** + * brcmnand_read_abs_bbts - [GENERIC] Read the bad block table(s) for all chips + * starting at a given page + * @mtd: MTD device structure + * @buf: temporary buffer + * @td: descriptor for the bad block table + * @md: descriptor for the bad block table mirror + * + * Read the bad block table(s) for all chips starting at a given page + * We assume that the bbt bits are in consecutive order. + * +*/ +static int brcmnand_read_abs_bbts(struct mtd_info *mtd, uint8_t *buf, + struct nand_bbt_descr *td, struct nand_bbt_descr *md) +{ + struct nand_chip *this = mtd->priv; + + /* Read the primary version, if available */ + if (td->options & NAND_BBT_VERSION) { + brcmnand_scan_read_raw(mtd, buf, td->pages[0] << this->page_shift, + mtd->writesize); + td->version[0] = buf[mtd->writesize + td->veroffs]; + printk(KERN_DEBUG "Bad block table at page %d, version 0x%02X\n", + td->pages[0], td->version[0]); + } + + /* Read the mirror version, if available */ + if (md && (md->options & NAND_BBT_VERSION)) { + brcmnand_scan_read_raw(mtd, buf, md->pages[0] << this->page_shift, + mtd->writesize); + md->version[0] = buf[mtd->writesize + md->veroffs]; + printk(KERN_DEBUG "Bad block table at page %d, version 0x%02X\n", + md->pages[0], md->version[0]); + } + return 1; +} + +/* + * Scan a given block full + */ +static int brcmnand_scan_block_full(struct mtd_info *mtd, struct nand_bbt_descr *bd, + loff_t offs, uint8_t *buf, size_t readlen, int scanlen, int len) +{ + int ret, j; + + ret = brcmnand_scan_read_raw(mtd, buf, offs, readlen); + if (ret) + return ret; + + for (j = 0; j < len; j++, buf += scanlen) { + if (check_pattern(buf, scanlen, mtd->writesize, bd)) + return 1; + } + return 0; +} + +/* + * Scan a given block partially + */ +static int brcmnand_scan_block_fast(struct mtd_info *mtd, struct nand_bbt_descr *bd, + loff_t offs, uint8_t *buf, int len) +{ + struct mtd_oob_ops ops; + int j, ret; + int dir; + + /* SLC: First and 2nd page */ + dir = 1; + ops.ooblen = mtd->oobsize; + ops.oobbuf = buf; + ops.ooboffs = 0; + ops.datbuf = NULL; + ops.mode = MTD_OOB_PLACE; + + for (j = 0; j < len; j++) { + /* + * Read the full oob until read_oob is fixed to + * handle single byte reads for 16 bit + * buswidth + */ + ret = mtd->read_oob(mtd, offs, &ops); + if (ret) + return ret; + + if (check_short_pattern(buf, bd)) + return 1; + + offs += (dir * mtd->writesize); + } + return 0; +} + +/** + * brcmnand_create_bbt - [GENERIC] Create a bad block table by scanning the device + * @mtd: MTD device structure + * @buf: temporary buffer + * @bd: descriptor for the good/bad block search pattern + * @chip: create the table for a specific chip, -1 read all chips. + * 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 + */ +static int brcmnand_create_bbt(struct mtd_info *mtd, uint8_t *buf, + struct nand_bbt_descr *bd, int chip) +{ + struct nand_chip *this = mtd->priv; + int i, numblocks, len, scanlen; + int startblock; + loff_t from; + size_t readlen; + + printk(KERN_INFO "Scanning device for bad blocks\n"); + + if (bd->options & NAND_BBT_SCANALLPAGES) + len = 1 << (this->bbt_erase_shift - this->page_shift); + else { + if (bd->options & NAND_BBT_SCAN2NDPAGE) + len = 2; + else + len = 1; + } + + if (!(bd->options & NAND_BBT_SCANEMPTY)) { + /* We need only read few bytes from the OOB area */ + scanlen = 0; + readlen = bd->len; + } else { + /* Full page content should be read */ + scanlen = mtd->writesize + mtd->oobsize; + readlen = len * mtd->writesize; + } + + if (chip == -1) { + /* Note that numblocks is 2 * (real numblocks) here, see i+=2 + * below as it makes shifting and masking less painful + */ + numblocks = mtd->size >> (this->bbt_erase_shift - 1); + startblock = 0; + from = 0; + } else { + if (chip >= this->numchips) { + printk(KERN_WARNING "create_bbt(): chipnr (%d) > available chips (%d)\n", + chip + 1, this->numchips); + return -EINVAL; + } + numblocks = this->chipsize >> (this->bbt_erase_shift - 1); + startblock = chip * numblocks; + numblocks += startblock; + from = startblock << (this->bbt_erase_shift - 1); + } + + for (i = startblock; i < numblocks;) { + int ret; + + if (bd->options & NAND_BBT_SCANALLPAGES) + ret = brcmnand_scan_block_full(mtd, bd, from, buf, readlen, + scanlen, len); + else + ret = brcmnand_scan_block_fast(mtd, bd, from, buf, len); + + if (ret < 0) + return ret; + + if (ret) { + this->bbt[i >> 3] |= 0x03 << (i & 0x6); + printk(KERN_WARNING "Bad eraseblock %d at 0x%08x\n", + i >> 1, (unsigned int)from); + mtd->ecc_stats.badblocks++; + } + + i += 2; + from += (1 << this->bbt_erase_shift); + } + return 0; +} + +/** + * brcmnand_search_bbt - [GENERIC] scan the device for a specific bad block table + * @mtd: MTD device structure + * @buf: temporary buffer + * @td: descriptor for the bad block table + * + * Read the bad block table by searching for a given ident pattern. + * Search is preformed either from the beginning up or from the end of + * the device downwards. The search starts always at the start of a + * block. + * If the option NAND_BBT_PERCHIP is given, each chip is searched + * for a bbt, which contains the bad block information of this chip. + * This is necessary to provide support for certain DOC devices. + * + * The bbt ident pattern resides in the oob area of the first page + * in a block. + */ +static int brcmnand_search_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *td) +{ + struct nand_chip *this = mtd->priv; + int i, chips; + int bits, startblock, block, dir; + int scanlen = mtd->writesize + mtd->oobsize; + int bbtblocks; + int blocktopage = this->bbt_erase_shift - this->page_shift; + + /* Search direction top -> down ? */ + if (td->options & NAND_BBT_LASTBLOCK) { + startblock = (mtd->size >> this->bbt_erase_shift) - 1; + dir = -1; + } else { + startblock = 0; + dir = 1; + } + + /* Do we have a bbt per chip ? */ + if (td->options & NAND_BBT_PERCHIP) { + chips = this->numchips; + bbtblocks = this->chipsize >> this->bbt_erase_shift; + startblock &= bbtblocks - 1; + } else { + chips = 1; + bbtblocks = mtd->size >> this->bbt_erase_shift; + } + + /* Number of bits for each erase block in the bbt */ + bits = td->options & NAND_BBT_NRBITS_MSK; + + for (i = 0; i < chips; i++) { + /* Reset version information */ + td->version[i] = 0; + td->pages[i] = -1; + /* Scan the maximum number of blocks */ + for (block = 0; block < td->maxblocks; block++) { + + int actblock = startblock + dir * block; + loff_t offs = actblock << this->bbt_erase_shift; + + /* Read first page */ + brcmnand_scan_read_raw(mtd, buf, offs, mtd->writesize); + if (!check_pattern(buf, scanlen, mtd->writesize, td)) { + td->pages[i] = actblock << blocktopage; + if (td->options & NAND_BBT_VERSION) { + td->version[i] = buf[mtd->writesize + td->veroffs]; + } + break; + } + } + startblock += this->chipsize >> this->bbt_erase_shift; + } + /* Check, if we found a bbt for each requested chip */ + for (i = 0; i < chips; i++) { + if (td->pages[i] == -1) + printk(KERN_WARNING "Bad block table not found for chip %d\n", i); + else + printk(KERN_DEBUG "Bad block table found at page %d, version 0x%02X\n", + td->pages[i], td->version[i]); + } + return 0; +} + +/** + * brcmnand_search_read_bbts - [GENERIC] scan the device for bad block table(s) + * @mtd: MTD device structure + * @buf: temporary buffer + * @td: descriptor for the bad block table + * @md: descriptor for the bad block table mirror + * + * Search and read the bad block table(s) +*/ +static int brcmnand_search_read_bbts(struct mtd_info *mtd, uint8_t *buf, + struct nand_bbt_descr *td, struct nand_bbt_descr *md) +{ + /* Search the primary table */ + brcmnand_search_bbt(mtd, buf, td); + + /* Search the mirror table */ + if (md) + brcmnand_search_bbt(mtd, buf, md); + + /* Force result check */ + return 1; +} + +/** + * brcmnand_write_bbt - [GENERIC] (Re)write the bad block table + * + * @mtd: MTD device structure + * @buf: temporary buffer + * @td: descriptor for the bad block table + * @md: descriptor for the bad block table mirror + * @chipsel: selector for a specific chip, -1 for all + * + * (Re)write the bad block table + * +*/ +static int brcmnand_write_bbt(struct mtd_info *mtd, uint8_t *buf, + struct nand_bbt_descr *td, struct nand_bbt_descr *md, int chipsel) +{ + struct nand_chip *this = mtd->priv; + struct erase_info einfo; + int i, j, res, chip = 0; + int bits, startblock, dir, page, offs, numblocks, sft, sftmsk; + int nrchips, bbtoffs, pageoffs, ooboffs; + uint8_t msk[4]; + uint8_t rcode = td->reserved_block_code; + size_t retlen, len = 0; + loff_t to; + struct mtd_oob_ops ops; + + ops.ooblen = mtd->oobsize; + ops.ooboffs = 0; + ops.datbuf = NULL; + ops.mode = MTD_OOB_PLACE; + + if (!rcode) + rcode = 0xff; + /* Write bad block table per chip rather than per device ? */ + if (td->options & NAND_BBT_PERCHIP) { + numblocks = (int)(this->chipsize >> this->bbt_erase_shift); + /* Full device write or specific chip ? */ + if (chipsel == -1) { + nrchips = this->numchips; + } else { + nrchips = chipsel + 1; + chip = chipsel; + } + } else { + numblocks = (int)(mtd->size >> this->bbt_erase_shift); + nrchips = 1; + } + + /* Loop through the chips */ + for (; chip < nrchips; chip++) { + + /* There was already a version of the table, reuse the page + * This applies for absolute placement too, as we have the + * page nr. in td->pages. + */ + if (td->pages[chip] != -1) { + page = td->pages[chip]; + goto write; + } + + /* Automatic placement of the bad block table */ + /* Search direction top -> down ? */ + if (td->options & NAND_BBT_LASTBLOCK) { + startblock = numblocks * (chip + 1) - 1; + dir = -1; + } else { + startblock = chip * numblocks; + dir = 1; + } + + for (i = 0; i < td->maxblocks; i++) { + int block = startblock + dir * i; + /* Check, if the block is bad */ + switch ((this->bbt[block >> 2] >> + (2 * (block & 0x03))) & 0x03) { + case 0x01: + case 0x03: + continue; + } + page = block << + (this->bbt_erase_shift - this->page_shift); + /* Check, if the block is used by the mirror table */ + if (!md || md->pages[chip] != page) + goto write; + } + printk(KERN_ERR "No space left to write bad block table\n"); + return -ENOSPC; + write: + + /* Set up shift count and masks for the flash table */ + bits = td->options & NAND_BBT_NRBITS_MSK; + msk[2] = ~rcode; + switch (bits) { + case 1: sft = 3; sftmsk = 0x07; msk[0] = 0x00; msk[1] = 0x01; + msk[3] = 0x01; + break; + case 2: sft = 2; sftmsk = 0x06; msk[0] = 0x00; msk[1] = 0x01; + msk[3] = 0x03; + break; + case 4: sft = 1; sftmsk = 0x04; msk[0] = 0x00; msk[1] = 0x0C; + msk[3] = 0x0f; + break; + case 8: sft = 0; sftmsk = 0x00; msk[0] = 0x00; msk[1] = 0x0F; + msk[3] = 0xff; + break; + default: return -EINVAL; + } + + bbtoffs = chip * (numblocks >> 2); + + to = ((loff_t) page) << this->page_shift; + + /* Must we save the block contents ? */ + if (td->options & NAND_BBT_SAVECONTENT) { + /* Make it block aligned */ + to &= ~((loff_t) ((1 << this->bbt_erase_shift) - 1)); + len = 1 << this->bbt_erase_shift; + res = mtd->read(mtd, to, len, &retlen, buf); + if (res < 0) { + if (retlen != len) { + printk(KERN_INFO "nand_bbt: Error " + "reading block for writing " + "the bad block table\n"); + return res; + } + printk(KERN_WARNING "nand_bbt: ECC error " + "while reading block for writing " + "bad block table\n"); + } + /* Read oob data */ + ops.ooblen = (len >> this->page_shift) * mtd->oobsize; + ops.oobbuf = &buf[len]; + res = mtd->read_oob(mtd, to + mtd->writesize, &ops); + if (res < 0 || ops.oobretlen != ops.ooblen) + goto outerr; + + /* Calc the byte offset in the buffer */ + pageoffs = page - (int)(to >> this->page_shift); + offs = pageoffs << this->page_shift; + /* Preset the bbt area with 0xff */ + memset(&buf[offs], 0xff, (size_t) (numblocks >> sft)); + ooboffs = len + (pageoffs * mtd->oobsize); + + } else { + /* Calc length */ + len = (size_t) (numblocks >> sft); + /* Make it page aligned ! */ + len = (len + (mtd->writesize - 1)) & + ~(mtd->writesize - 1); + /* Preset the buffer with 0xff */ + memset(buf, 0xff, len + + (len >> this->page_shift)* mtd->oobsize); + offs = 0; + ooboffs = len; + /* Pattern is located in oob area of first page */ + memcpy(&buf[ooboffs + td->offs], td->pattern, td->len); + } + + if (td->options & NAND_BBT_VERSION) + buf[ooboffs + td->veroffs] = td->version[chip]; + + /* walk through the memory table */ + for (i = 0; i < numblocks;) { + uint8_t dat; + dat = this->bbt[bbtoffs + (i >> 2)]; + for (j = 0; j < 4; j++, i++) { + int sftcnt = (i << (3 - sft)) & sftmsk; + /* Do not store the reserved bbt blocks ! */ + buf[offs + (i >> sft)] &= + ~(msk[dat & 0x03] << sftcnt); + dat >>= 2; + } + } + + memset(&einfo, 0, sizeof(einfo)); + einfo.mtd = mtd; + einfo.addr = (unsigned long)to; + einfo.len = 1 << this->bbt_erase_shift; + res = this->erase_bbt(mtd, &einfo, 1); + if (res < 0) + goto outerr; + + res = brcmnand_scan_write_bbt(mtd, to, len, buf, &buf[len]); + if (res < 0) + goto outerr; + + printk(KERN_DEBUG "Bad block table written to 0x%08x, version " + "0x%02X\n", (unsigned int)to, td->version[chip]); + + /* Mark it as used */ + td->pages[chip] = page; + } + return 0; + +outerr: + printk(KERN_WARNING + "nand_bbt: Error while writing bad block table %d\n", res); + return res; +} + +/** + * brcmnand_memory_bbt - [GENERIC] create a memory based bad block table + * @mtd: MTD device structure + * @bd: descriptor for the good/bad block search pattern + * + * The function creates a memory based bbt by scanning the device + * for manufacturer / software marked good / bad blocks +*/ +static inline int brcmnand_memory_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd) +{ + struct nand_chip *this = mtd->priv; + + bd->options &= ~NAND_BBT_SCANEMPTY; + return brcmnand_create_bbt(mtd, this->buffers->databuf, bd, -1); +} + +/** + * brcmnand_check_create - [GENERIC] create and write bbt(s) if necessary + * @mtd: MTD device structure + * @buf: temporary buffer + * @bd: descriptor for the good/bad block search pattern + * + * The function checks the results of the previous call to read_bbt + * and creates / updates the bbt(s) if necessary + * Creation is necessary if no bbt was found for the chip/device + * Update is necessary if one of the tables is missing or the + * version nr. of one table is less than the other +*/ +static int brcmnand_check_create(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *bd) +{ + int i, chips, writeops, chipsel, res; + struct nand_chip *this = mtd->priv; + struct nand_bbt_descr *td = this->bbt_td; + struct nand_bbt_descr *md = this->bbt_md; + struct nand_bbt_descr *rd, *rd2; + + /* Do we have a bbt per chip ? */ + if (td->options & NAND_BBT_PERCHIP) + chips = this->numchips; + else + chips = 1; + + for (i = 0; i < chips; i++) { + writeops = 0; + rd = NULL; + rd2 = NULL; + /* Per chip or per device ? */ + chipsel = (td->options & NAND_BBT_PERCHIP) ? i : -1; + /* Mirrored table avilable ? */ + if (md) { + if (td->pages[i] == -1 && md->pages[i] == -1) { + writeops = 0x03; + goto create; + } + + if (td->pages[i] == -1) { + rd = md; + td->version[i] = md->version[i]; + writeops = 1; + goto writecheck; + } + + if (md->pages[i] == -1) { + rd = td; + md->version[i] = td->version[i]; + writeops = 2; + goto writecheck; + } + + if (td->version[i] == md->version[i]) { + rd = td; + if (!(td->options & NAND_BBT_VERSION)) + rd2 = md; + goto writecheck; + } + + if (((int8_t) (td->version[i] - md->version[i])) > 0) { + rd = td; + md->version[i] = td->version[i]; + writeops = 2; + } else { + rd = md; + td->version[i] = md->version[i]; + writeops = 1; + } + + goto writecheck; + + } else { + if (td->pages[i] == -1) { + writeops = 0x01; + goto create; + } + rd = td; + goto writecheck; + } + create: + /* Create the bad block table by scanning the device ? */ + if (!(td->options & NAND_BBT_CREATE)) + continue; + + /* Create the table in memory by scanning the chip(s) */ + brcmnand_create_bbt(mtd, buf, bd, chipsel); + + td->version[i] = 1; + if (md) + md->version[i] = 1; + writecheck: + /* read back first ? */ + if (rd) + brcmnand_read_abs_bbt(mtd, buf, rd, chipsel); + /* If they weren't versioned, read both. */ + if (rd2) + brcmnand_read_abs_bbt(mtd, buf, rd2, chipsel); + + /* Write the bad block table to the device ? */ + if ((writeops & 0x01) && (td->options & NAND_BBT_WRITE)) { + res = brcmnand_write_bbt(mtd, buf, td, md, chipsel); + if (res < 0) + return res; + } + + /* Write the mirror bad block table to the device ? */ + if ((writeops & 0x02) && md && (md->options & NAND_BBT_WRITE)) { + res = brcmnand_write_bbt(mtd, buf, md, td, chipsel); + if (res < 0) + return res; + } + } + return 0; +} + +/** + * mark_bbt_regions - [GENERIC] mark the bad block table regions + * @mtd: MTD device structure + * @td: bad block table descriptor + * + * The bad block table regions are marked as "bad" to prevent + * accidental erasures / writes. The regions are identified by + * the mark 0x02. +*/ +static void mark_bbt_region(struct mtd_info *mtd, struct nand_bbt_descr *td) +{ + struct nand_chip *this = mtd->priv; + int i, j, chips, block, nrblocks, update; + uint8_t oldval, newval; + + /* Do we have a bbt per chip ? */ + if (td->options & NAND_BBT_PERCHIP) { + chips = this->numchips; + nrblocks = (int)(this->chipsize >> this->bbt_erase_shift); + } else { + chips = 1; + nrblocks = (int)(mtd->size >> this->bbt_erase_shift); + } + + for (i = 0; i < chips; i++) { + if ((td->options & NAND_BBT_ABSPAGE) || + !(td->options & NAND_BBT_WRITE)) { + if (td->pages[i] == -1) + continue; + block = td->pages[i] >> (this->bbt_erase_shift - this->page_shift); + block <<= 1; + oldval = this->bbt[(block >> 3)]; + newval = oldval | (0x2 << (block & 0x06)); + this->bbt[(block >> 3)] = newval; + if ((oldval != newval) && td->reserved_block_code) + brcmnand_update_bbt(mtd, block << (this->bbt_erase_shift - 1)); + continue; + } + update = 0; + if (td->options & NAND_BBT_LASTBLOCK) + block = ((i + 1) * nrblocks) - td->maxblocks; + else + block = i * nrblocks; + block <<= 1; + for (j = 0; j < td->maxblocks; j++) { + oldval = this->bbt[(block >> 3)]; + newval = oldval | (0x2 << (block & 0x06)); + this->bbt[(block >> 3)] = newval; + if (oldval != newval) + update = 1; + block += 2; + } + /* If we want reserved blocks to be recorded to flash, and some + * new ones have been marked, then we need to update the stored + * bbts. This should only happen once. + */ + if (update && td->reserved_block_code) + brcmnand_update_bbt(mtd, (block - 2) << (this->bbt_erase_shift - 1)); + } +} + +/** + * brcmnand_scan_bbt - [NAND Interface] scan, find, read and maybe create bad block table(s) + * @mtd: MTD device structure + * @bd: descriptor for the good/bad block search pattern + * + * The function checks, if a bad block table(s) is/are already + * available. If not it scans the device for manufacturer + * marked good / bad blocks and writes the bad block table(s) to + * the selected place. + * + * The bad block table memory is allocated here. It must be freed + * by calling the nand_free_bbt function. + * +*/ +int brcmnand_scan_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd) +{ + struct nand_chip *this = mtd->priv; + int len, res = 0; + uint8_t *buf; + struct nand_bbt_descr *td = this->bbt_td; + struct nand_bbt_descr *md = this->bbt_md; + + len = mtd->size >> (this->bbt_erase_shift + 2); + /* Allocate memory (2bit per block) and clear the memory bad block table */ + this->bbt = kzalloc(len, GFP_KERNEL); + if (!this->bbt) { + printk(KERN_ERR "nand_scan_bbt: Out of memory\n"); + return -ENOMEM; + } + + /* If no primary table decriptor is given, scan the device + * to build a memory based bad block table + */ + if (!td) { + if ((res = brcmnand_memory_bbt(mtd, bd))) { + printk(KERN_ERR "nand_bbt: Can't scan flash and build the RAM-based BBT\n"); + kfree(this->bbt); + this->bbt = NULL; + } + return res; + } + + /* Allocate a temporary buffer for one eraseblock incl. oob */ + len = (1 << this->bbt_erase_shift); + len += (len >> this->page_shift) * mtd->oobsize; + buf = vmalloc(len); + if (!buf) { + printk(KERN_ERR "nand_bbt: Out of memory\n"); + kfree(this->bbt); + this->bbt = NULL; + return -ENOMEM; + } + + /* Is the bbt at a given page ? */ + if (td->options & NAND_BBT_ABSPAGE) { + res = brcmnand_read_abs_bbts(mtd, buf, td, md); + } else { + /* Search the bad block table using a pattern in oob */ + res = brcmnand_search_read_bbts(mtd, buf, td, md); + } + + if (res) + res = brcmnand_check_create(mtd, buf, bd); + + /* Prevent the bbt regions from erasing / writing */ + mark_bbt_region(mtd, td); + if (md) + mark_bbt_region(mtd, md); + + vfree(buf); + return res; +} + + +/** + * brcmnand_update_bbt - [NAND Interface] update bad block table(s) + * @mtd: MTD device structure + * @offs: the offset of the newly marked block + * + * The function updates the bad block table(s) +*/ +int brcmnand_update_bbt(struct mtd_info *mtd, loff_t offs) +{ + struct nand_chip *this = mtd->priv; + int len, res = 0, writeops = 0; + int chip, chipsel; + uint8_t *buf; + struct nand_bbt_descr *td = this->bbt_td; + struct nand_bbt_descr *md = this->bbt_md; + + if (!this->bbt || !td) + return -EINVAL; + + len = mtd->size >> (this->bbt_erase_shift + 2); + /* Allocate a temporary buffer for one eraseblock incl. oob */ + len = (1 << this->bbt_erase_shift); + len += (len >> this->page_shift) * mtd->oobsize; + buf = kmalloc(len, GFP_KERNEL); + if (!buf) { + printk(KERN_ERR "nand_update_bbt: Out of memory\n"); + return -ENOMEM; + } + + writeops = md != NULL ? 0x03 : 0x01; + + /* Do we have a bbt per chip ? */ + if (td->options & NAND_BBT_PERCHIP) { + chip = (int)(offs >> this->chip_shift); + chipsel = chip; + } else { + chip = 0; + chipsel = -1; + } + + td->version[chip]++; + if (md) + md->version[chip]++; + + /* Write the bad block table to the device ? */ + if ((writeops & 0x01) && (td->options & NAND_BBT_WRITE)) { + res = brcmnand_write_bbt(mtd, buf, td, md, chipsel); + if (res < 0) + goto out; + } + /* Write the mirror bad block table to the device ? */ + if ((writeops & 0x02) && md && (md->options & NAND_BBT_WRITE)) { + res = brcmnand_write_bbt(mtd, buf, md, td, chipsel); + } + +out: + kfree(buf); + return res; +} + +/* Define some generic bad / good block scan pattern which are used + * while scanning a device for factory marked good / bad blocks. + */ +static uint8_t scan_ff_pattern[] = { 0xff, 0xff }; + +static struct nand_bbt_descr smallpage_memorybased = { + .options = NAND_BBT_SCAN2NDPAGE, + .offs = 5, + .len = 1, + .pattern = scan_ff_pattern +}; + +static struct nand_bbt_descr largepage_memorybased = { + .options = 0, + .offs = 0, + .len = 2, + .pattern = scan_ff_pattern +}; + +static struct nand_bbt_descr smallpage_flashbased = { + .options = NAND_BBT_SCAN2NDPAGE, + .offs = 5, + .len = 1, + .pattern = scan_ff_pattern +}; + +static struct nand_bbt_descr largepage_flashbased = { + .options = NAND_BBT_SCAN2NDPAGE, + .offs = 0, + .len = 2, + .pattern = scan_ff_pattern +}; + +/* 2K & 4K page MLC NAND use same pattern */ +static struct nand_bbt_descr bch4_flashbased = { + .options = NAND_BBT_SCAN2NDPAGE, + .offs = 0, + .len = 1, + .pattern = scan_ff_pattern +}; + +static uint8_t scan_agand_pattern[] = { 0x1C, 0x71, 0xC7, 0x1C, 0x71, 0xC7 }; + +static struct nand_bbt_descr agand_flashbased = { + .options = NAND_BBT_SCANEMPTY | NAND_BBT_SCANALLPAGES, + .offs = 0x20, + .len = 6, + .pattern = scan_agand_pattern +}; + +/* Generic flash bbt decriptors +*/ +static uint8_t bbt_pattern[] = {'B', 'b', 't', '0' }; +static uint8_t mirror_pattern[] = {'1', 't', 'b', 'B' }; + +/* + * THT: We only have 1 chip per device + */ +static struct nand_bbt_descr bbt_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION /* | NAND_BBT_PERCHIP */, + .offs = 9, /* THT: Changed from 8 */ + .len = 4, + .veroffs = 13, /* THT: Changed from 12 */ + .maxblocks = 4, /* THT: Will update later, based on 1MB partition for BBT */ + .pattern = bbt_pattern +}; + +static struct nand_bbt_descr bbt_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION /* | NAND_BBT_PERCHIP */, + .offs = 9, /* THT: Changed from 8 */ + .len = 4, + .veroffs = 13, /* THT: Changed from 12 */ + .maxblocks = 4, + .pattern = mirror_pattern +}; + +/* SLC flash using BCH-4 ECC, SM & Large page use same descriptor template */ +static struct nand_bbt_descr bbt_slc_bch4_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION /* | NAND_BBT_PERCHIP */, + .offs = 1, /* THT: Changed from 8 */ + .len = 4, + .veroffs = 6, /* THT: Changed from 12 */ + .maxblocks = 8, + .pattern = bbt_pattern +}; + +static struct nand_bbt_descr bbt_slc_bch4_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION /* | NAND_BBT_PERCHIP */, + .offs = 1, + .len = 4, + .veroffs = 6, + .maxblocks = 8, + .pattern = mirror_pattern +}; + +static struct nand_bbt_descr bbt_bch4_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION /* | NAND_BBT_PERCHIP */, + .offs = 1, + .len = 4, + .veroffs = 5, /* THT: Changed from 12 */ + .maxblocks = 8, /* THT: Will update later, based on 4MB partition for BBT */ + .pattern = bbt_pattern +}; + +static struct nand_bbt_descr bbt_bch4_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION /* | NAND_BBT_PERCHIP */, + .offs = 1, /* THT: Changed from 8 */ + .len = 4, + .veroffs = 5, /* THT: Changed from 12 */ + .maxblocks = 8, + .pattern = mirror_pattern +}; + +/** + * brcmnand_isbad_bbt - [NAND Interface] Check if a block is bad + * @mtd: MTD device structure + * @offs: offset in the device + * @allowbbt: allow access to bad block table region + * + * Each byte in the BBT contains 4 entries, 2 bits each per block. + * So the entry for the block b is: + * bbt[b >> 2] & (0x3 << ((b & 0x3) << 1))) + * +*/ +int brcmnand_isbad_bbt(struct mtd_info *mtd, loff_t offs, int allowbbt) +{ + struct nand_chip *this = mtd->priv; + uint32_t block; /* Used as an index, so 32bit. */ + uint8_t res; + + /* THT 03/20/07: Get block number. It's more convenient to do it in the + * following way but is actually the same thing as in comment the above + */ + block = (uint32_t) (offs >> (this->bbt_erase_shift - 1)); + res = (this->bbt[block >> 3] >> (block & 0x06)) & 0x03; + DEBUG(MTD_DEBUG_LEVEL3, "brcmnand_isbad_bbt(): bbt info for offs " + "0x%08x: (block %d) 0x%02x\n", (unsigned int)offs, block >> 1, res); + if (res) + printk("brcmnand_isbad_bbt(): bbt info for offs " + "0x%08x: (block %d) 0x%02x\n", (unsigned int)offs, block >> 1, res); + + switch ((int)res) { + case 0x00: + /* Good block */ + return 0; + case 0x01: + /* Marked bad due to wear */ + return 1; + case 0x02: + /* Reserved blocks */ + return allowbbt ? 0 : 1; + } + return 1; +} + +/** + * brcmnand_default_bbt - [NAND Interface] Select a default bad block table for the device + * @mtd: MTD device structure + * + * This function selects the default bad block table + * support for the device and calls the nand_scan_bbt function + * +*/ +int brcmnand_default_bbt(struct mtd_info *mtd) +{ + struct nand_chip *this = mtd->priv; + int ret; + + this->get_device(this, mtd, FL_WRITING); + /* Default for AG-AND. We must use a flash based + * bad block table as the devices have factory marked + * _good_ blocks. Erasing those blocks leads to loss + * of the good / bad information, so we _must_ store + * this information in a good / bad table during + * startup + */ + if (this->options & NAND_IS_AND) { + /* Use the default pattern descriptors */ + if (!this->bbt_td) { + this->bbt_td = &bbt_main_descr; + this->bbt_md = &bbt_mirror_descr; + } + this->options |= NAND_USE_FLASH_BBT; + ret = brcmnand_scan_bbt(mtd, &agand_flashbased); + this->release_device(mtd); + return ret; + } + + /* Is a flash based bad block table requested ? */ + if (this->options & NAND_USE_FLASH_BBT) { + if (this->ecc.level == BRCMNAND_ECC_HAMMING) { + /* Use the default pattern descriptors */ + if (!this->bbt_td) { + this->bbt_td = &bbt_main_descr; + this->bbt_md = &bbt_mirror_descr; + } + if (!this->badblock_pattern) { + this->badblock_pattern = (mtd->writesize > 512) ? + &largepage_flashbased : &smallpage_flashbased; + } + } else { + if (NAND_IS_MLC(this)) { + if (!this->bbt_td) { + this->bbt_td = &bbt_bch4_main_descr; + this->bbt_md = &bbt_bch4_mirror_descr; + } + if (!this->badblock_pattern) { + /* 2K and 4K MLC NAND use the same pattern */ + this->badblock_pattern = &bch4_flashbased; + } + } else { + /* SLC using BCH-4 ECC */ + if (!this->bbt_td) { + this->bbt_td = &bbt_slc_bch4_main_descr; + this->bbt_md = &bbt_slc_bch4_mirror_descr; + } + if (!this->badblock_pattern) { + /* 2K and 4K MLC NAND use the same pattern */ + this->badblock_pattern = (mtd->writesize > 512) ? + &bch4_flashbased : &smallpage_flashbased; + } + } + } + } else { + this->bbt_td = NULL; + this->bbt_md = NULL; + if (!this->badblock_pattern) { + this->badblock_pattern = (mtd->writesize > 512) ? + &largepage_memorybased : &smallpage_memorybased; + } + } + + /* BBT partition occupies 1 MB at the end of the useable flash, so adjust + * maxblocks accordingly. Only applies to flash with 512MB or less, since + * we don't have the extra reserved space at the end of the flash + * (1FF0_0000 - 1FFF_FFFF). + */ + if (mtd->size <= (512 << 20)) { + this->bbt_td->maxblocks = this->bbt_md->maxblocks = + (1 << (20 - this->bbt_erase_shift)); + } + ret = brcmnand_scan_bbt(mtd, this->badblock_pattern); + this->release_device(mtd); + return ret; +} + +EXPORT_SYMBOL(brcmnand_scan_bbt); +EXPORT_SYMBOL(brcmnand_default_bbt); diff --git a/release/src-rt/linux/linux-2.6/drivers/mtd/47xxnand/brcmnand_priv.h b/release/src-rt/linux/linux-2.6/drivers/mtd/47xxnand/brcmnand_priv.h new file mode 100755 index 0000000000..d053680e8f --- /dev/null +++ b/release/src-rt/linux/linux-2.6/drivers/mtd/47xxnand/brcmnand_priv.h @@ -0,0 +1,103 @@ +/* + * Broadcom NAND flash controller interface + * + * Copyright (C) 2010, Broadcom Corporation. All Rights Reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $Id $ + */ + + +#ifndef _BRCMNAND_PRIV_H_ +#define _BRCMNAND_PRIV_H_ + +#include +#include + +#include + +#define BRCMNAND_malloc(size) vmalloc(size) +#define BRCMNAND_free(addr) vfree(addr) + +typedef enum +{ + BRCMNAND_ECC_DISABLE = 0u, + BRCMNAND_ECC_BCH_1 = 1u, + BRCMNAND_ECC_BCH_2 = 2u, + BRCMNAND_ECC_BCH_3 = 3u, + BRCMNAND_ECC_BCH_4 = 4u, + BRCMNAND_ECC_BCH_5 = 5u, + BRCMNAND_ECC_BCH_6 = 6u, + BRCMNAND_ECC_BCH_7 = 7u, + BRCMNAND_ECC_BCH_8 = 8u, + BRCMNAND_ECC_BCH_9 = 9u, + BRCMNAND_ECC_BCH_10 = 10u, + BRCMNAND_ECC_BCH_11 = 11u, + BRCMNAND_ECC_BCH_12 = 12u, + BRCMNAND_ECC_RESVD_1 = 13u, + BRCMNAND_ECC_RESVD_2 = 14u, + BRCMNAND_ECC_HAMMING = 15u +} brcmnand_ecc_level_t; + +struct brcmnand_mtd { + si_t *sih; + chipcregs_t *cc; + struct mtd_info mtd; + struct nand_chip chip; +#ifdef CONFIG_MTD_PARTITIONS + struct mtd_partition *parts; +#endif +}; + + +/** + * brcmnand_scan - [BrcmNAND Interface] Scan for the BrcmNAND device + * @param mtd MTD device structure + * @param maxchips Number of chips to scan for + * + * This fills out all the not initialized function pointers + * with the defaults. + * The flash ID is read and the mtd/chip structures are + * filled with the appropriate values. + * + * THT: For now, maxchips should always be 1. + */ +extern int brcmnand_scan(struct mtd_info *mtd, int maxchips); + +/** + * brcmnand_release - [BrcmNAND Interface] Free resources held by the + * BrcmNAND device + * @param mtd MTD device structure + */ +extern void brcmnand_release(struct mtd_info *mtd); + +extern int brcmnand_scan_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd); +extern int brcmnand_default_bbt(struct mtd_info *mtd); +extern int brcmnand_isbad_bbt(struct mtd_info *mtd, loff_t offs, int allowbbt); + +extern int brcmnand_update_bbt(struct mtd_info *mtd, loff_t offs); + +extern void* get_brcmnand_handle(void); + +extern void print_oobbuf(const unsigned char* buf, int len); +extern void print_databuf(const unsigned char* buf, int len); + +#ifdef CONFIG_MTD_BRCMNAND_CORRECTABLE_ERR_HANDLING +extern int brcmnand_cet_update(struct mtd_info *mtd, loff_t from, int *status); +extern int brcmnand_cet_prepare_reboot(struct mtd_info *mtd); +extern int brcmnand_cet_erasecallback(struct mtd_info *mtd, u_int32_t addr); +extern int brcmnand_create_cet(struct mtd_info *mtd); +#endif /* CONFIG_MTD_BRCMNAND_CORRECTABLE_ERR_HANDLING */ + +#endif /* _BRCMNAND_PRIV_H_ */ diff --git a/release/src-rt/linux/linux-2.6/drivers/mtd/Kconfig b/release/src-rt/linux/linux-2.6/drivers/mtd/Kconfig index fbec8cd55e..093c7638d3 100644 --- a/release/src-rt/linux/linux-2.6/drivers/mtd/Kconfig +++ b/release/src-rt/linux/linux-2.6/drivers/mtd/Kconfig @@ -288,6 +288,8 @@ source "drivers/mtd/nand/Kconfig" source "drivers/mtd/onenand/Kconfig" +source "drivers/mtd/47xxnand/Kconfig" + source "drivers/mtd/ubi/Kconfig" endif # MTD diff --git a/release/src-rt/linux/linux-2.6/drivers/mtd/Makefile b/release/src-rt/linux/linux-2.6/drivers/mtd/Makefile index 6d958a4566..5c111c7d48 100644 --- a/release/src-rt/linux/linux-2.6/drivers/mtd/Makefile +++ b/release/src-rt/linux/linux-2.6/drivers/mtd/Makefile @@ -26,6 +26,6 @@ obj-$(CONFIG_SSFDC) += ssfdc.o nftl-objs := nftlcore.o nftlmount.o inftl-objs := inftlcore.o inftlmount.o -obj-y += chips/ maps/ devices/ nand/ onenand/ +obj-y += chips/ maps/ devices/ nand/ onenand/ 47xxnand/ obj-$(CONFIG_MTD_UBI) += ubi/ diff --git a/release/src-rt/linux/linux-2.6/drivers/mtd/devices/bcm_nflash.c b/release/src-rt/linux/linux-2.6/drivers/mtd/devices/bcm_nflash.c old mode 100644 new mode 100755 index a964bf5108..72b71ab958 --- a/release/src-rt/linux/linux-2.6/drivers/mtd/devices/bcm_nflash.c +++ b/release/src-rt/linux/linux-2.6/drivers/mtd/devices/bcm_nflash.c @@ -1,13 +1,19 @@ /* * Broadcom SiliconBackplane chipcommon serial flash interface * - * Copyright (C) 2009, Broadcom Corporation - * All Rights Reserved. + * Copyright (C) 2010, Broadcom Corporation. All Rights Reserved. * - * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY - * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM - * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE. + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * $Id $ */ @@ -42,6 +48,7 @@ extern struct mtd_partition * init_nflash_mtd_partitions(struct mtd_info *mtd, s struct mtd_partition *nflash_parts; #endif +extern struct mutex *partitions_mutex_init(void); struct nflash_mtd { si_t *sih; @@ -55,11 +62,11 @@ struct nflash_mtd { static struct nflash_mtd nflash; static int -nflash_mtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) +_nflash_mtd_read(struct mtd_info *mtd, struct mtd_partition *part, + loff_t from, size_t len, size_t *retlen, u_char *buf) { struct nflash_mtd *nflash = (struct nflash_mtd *) mtd->priv; int bytes, ret = 0; - struct mtd_partition *part = NULL; uint extra = 0; uchar *tmpbuf = NULL; int size; @@ -70,15 +77,17 @@ nflash_mtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u uchar *ptr = NULL; /* Locate the part */ - for (i = 0; nflash_parts[i].name; i++) { - if (from >= nflash_parts[i].offset && - ((nflash_parts[i+1].name == NULL) || (from < nflash_parts[i+1].offset))) { - part = &nflash_parts[i]; - break; + if (!part) { + for (i = 0; nflash_parts[i].name; i++) { + if (from >= nflash_parts[i].offset && + ((nflash_parts[i+1].name == NULL) || (from < nflash_parts[i+1].offset))) { + part = &nflash_parts[i]; + break; + } } + if (!part) + return -EINVAL; } - if (!part) - return -EINVAL; /* Check address range */ if (!len) return 0; @@ -167,24 +176,38 @@ done: } static int +nflash_mtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) +{ + int ret; + + mutex_lock(mtd->mutex); + ret = _nflash_mtd_read(mtd, NULL, from, len, retlen, buf); + mutex_unlock(mtd->mutex); + + return ret; +} + +static int nflash_mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { struct nflash_mtd *nflash = (struct nflash_mtd *) mtd->priv; int bytes, ret = 0; struct mtd_partition *part = NULL; u_char *block = NULL; - u_char *ptr = buf; + u_char *ptr = (u_char *)buf; uint offset, blocksize, mask, blk_offset, off; uint skip_bytes = 0, good_bytes = 0; int blk_idx, i; - int read_len, write_len, copy_len; - loff_t from; + int read_len, write_len, copy_len = 0; + loff_t from = to; u_char *write_ptr; + int docopy = 1; /* Locate the part */ for (i = 0; nflash_parts[i].name; i++) { if (to >= nflash_parts[i].offset && - ((nflash_parts[i+1].name == NULL) || (to < nflash_parts[i+1].offset))) { + ((nflash_parts[i+1].name == NULL) || + (to < (nflash_parts[i].offset + nflash_parts[i].size)))) { part = &nflash_parts[i]; break; } @@ -200,11 +223,15 @@ nflash_mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, co blocksize = mtd->erasesize; if (!(block = kmalloc(blocksize, GFP_KERNEL))) return -ENOMEM; + + mutex_lock(mtd->mutex); + mask = blocksize - 1; /* Check and skip bad blocks */ blk_offset = offset & ~mask; good_bytes = part->offset & ~mask; - for (blk_idx = good_bytes/blocksize; blk_idx < (part->offset+part->size)/blocksize; blk_idx++) { + for (blk_idx = good_bytes/blocksize; blk_idx < (part->offset+part->size)/blocksize; + blk_idx++) { if ((nflash->map[blk_idx] != 0) || (nflash_checkbadb(nflash->sih, nflash->cc, (blocksize*blk_idx)) != 0)) { skip_bytes += blocksize; @@ -223,44 +250,58 @@ nflash_mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, co /* Backup and erase one block at a time */ *retlen = 0; while (len) { - /* Align offset */ - from = offset & ~mask; - /* Copy existing data into holding block if necessary */ - if (((offset & (blocksize-1)) != 0) || (len < blocksize)) { - if ((ret = nflash_mtd_read(mtd, from, blocksize, &read_len, block))) - goto done; - if (read_len != blocksize) { - ret = -EINVAL; - goto done; + if (docopy) { + /* Align offset */ + from = offset & ~mask; + /* Copy existing data into holding block if necessary */ + if (((offset & (blocksize-1)) != 0) || (len < blocksize)) { + ret = _nflash_mtd_read(mtd, part, from, blocksize, + &read_len, block); + if (ret) + goto done; + if (read_len != blocksize) { + ret = -EINVAL; + goto done; + } } + /* Copy input data into holding block */ + copy_len = min(len, blocksize - (offset & mask)); + memcpy(block + (offset & mask), ptr, copy_len); } - /* Copy input data into holding block */ - copy_len = min(len, blocksize - (offset & mask)); - memcpy(block + (offset & mask), ptr, copy_len); off = (uint) from + skip_bytes; /* Erase block */ if ((ret = nflash_erase(nflash->sih, nflash->cc, off)) < 0) { - goto done; + nflash_mark_badb(nflash->sih, nflash->cc, off); + nflash->map[blk_idx] = 1; + skip_bytes += blocksize; + docopy = 0; } - /* Write holding block */ - write_ptr = block; - write_len = blocksize; - while (write_len) { - if ((bytes = nflash_write(nflash->sih, nflash->cc, - (uint) from + skip_bytes, - (uint) write_len, - (uchar *) write_ptr)) < 0) { - ret = bytes; - goto done; + else { + /* Write holding block */ + write_ptr = block; + write_len = blocksize; + while (write_len) { + if ((bytes = nflash_write(nflash->sih, nflash->cc, + (uint) from + skip_bytes, (uint) write_len, + (uchar *) write_ptr)) < 0) { + nflash_mark_badb(nflash->sih, nflash->cc, off); + nflash->map[blk_idx] = 1; + skip_bytes += blocksize; + docopy = 0; + break; + } + from += bytes; + write_len -= bytes; + write_ptr += bytes; + docopy = 1; + } + if (docopy) { + offset += copy_len; + len -= copy_len; + ptr += copy_len; + *retlen += copy_len; } - from += bytes; - write_len -= bytes; - write_ptr += bytes; } - offset += copy_len; - len -= copy_len; - ptr += copy_len; - *retlen += copy_len; /* Check and skip bad blocks */ if (len) { blk_offset += blocksize; @@ -280,6 +321,8 @@ nflash_mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, co } } done: + mutex_unlock(mtd->mutex); + if (block) kfree(block); return ret; @@ -289,40 +332,108 @@ static int nflash_mtd_erase(struct mtd_info *mtd, struct erase_info *erase) { struct nflash_mtd *nflash = (struct nflash_mtd *) mtd->priv; - int i, j, ret = 0; - unsigned int addr, len; + struct mtd_partition *part = NULL; + int i, ret = 0; + uint addr, len, blocksize; + uint part_start_blk, part_end_blk; + uint blknum, new_addr, erase_blknum; + + addr = erase->addr; + len = erase->len; + + blocksize = mtd->erasesize; /* Check address range */ - if (!erase->len) + if (!len) return 0; - if ((erase->addr + erase->len) > mtd->size) + + if ((addr + len) > mtd->size) return -EINVAL; - addr = erase->addr; - len = erase->len; + if (addr & (blocksize - 1)) + return -EINVAL; - /* Ensure that requested region is aligned */ - for (i = 0; i < mtd->numeraseregions; i++) { - for (j = 0; j < mtd->eraseregions[i].numblocks; j++) { - if (addr == mtd->eraseregions[i].offset + - mtd->eraseregions[i].erasesize * j && - len >= mtd->eraseregions[i].erasesize) { - if ((ret = nflash_erase(nflash->sih, nflash->cc, addr)) < 0) - break; - addr += mtd->eraseregions[i].erasesize; - len -= mtd->eraseregions[i].erasesize; + /* Locate the part */ + for (i = 0; nflash_parts[i].name; i++) { + if (addr >= nflash_parts[i].offset && + ((addr + len) <= (nflash_parts[i].offset + nflash_parts[i].size))) { + part = &nflash_parts[i]; + break; + } + } + + if (!part) + return -EINVAL; + + mutex_lock(mtd->mutex); + + /* Find the effective start block address to erase */ + part_start_blk = (part->offset & ~(blocksize - 1)) / blocksize; + part_end_blk = ROUNDUP((part->offset + part->size), blocksize) / blocksize; + + new_addr = part_start_blk * blocksize; + blknum = (addr / blocksize) - part_start_blk; + + for (i = part_start_blk; i < part_end_blk; i++) { + if ((nflash->map[i] != 0) || + (nflash_checkbadb(nflash->sih, nflash->cc, i * blocksize) != 0)) { + nflash->map[i] = 1; + new_addr += blocksize; + continue; + } else { + if (blknum) { + new_addr += blocksize; + blknum--; } } - if (ret) + + if (blknum == 0) break; } + /* Erase the blocks from the new block address */ + erase_blknum = ROUNDUP(len, blocksize) / blocksize; + + #if 0 + if ((new_addr + (erase_blknum * blocksize)) > (part->offset + part->size)) { + ret = -EINVAL; + goto done; + } + #endif + + for (i = new_addr; erase_blknum; i += blocksize) { + if (i >= (part->offset + part->size)) { + /* It indicates bad block, but do NOT treat it as fail */ + printk(KERN_WARNING "%s(%d):end of part %s (0x%x)!\n", + __FUNCTION__, __LINE__, part->name, i); + ret = 0; + goto done; + } + + /* Skip bad block erase */ + if ((nflash->map[i / blocksize] != 0) || + (nflash_checkbadb(nflash->sih, nflash->cc, i) != 0)) { + nflash->map[i / blocksize] = 1; + continue; + } + + if ((ret = nflash_erase(nflash->sih, nflash->cc, i)) < 0) { + nflash_mark_badb(nflash->sih, nflash->cc, i); + nflash->map[i / blocksize] = 1; + } else { + erase_blknum--; + } + } + +done: /* Set erase status */ if (ret) erase->state = MTD_ERASE_FAILED; else erase->state = MTD_ERASE_DONE; + mutex_unlock(mtd->mutex); + /* Call erase callback */ if (erase->callback) erase->callback(erase); @@ -405,7 +516,18 @@ nflash_mtd_init(void) nflash.mtd.writesize = NFL_SECTOR_SIZE; nflash.mtd.priv = &nflash; nflash.mtd.owner = THIS_MODULE; + nflash.mtd.mutex = partitions_mutex_init(); + if (!nflash.mtd.mutex) + return -ENOMEM; + /* Scan bad block */ + mutex_lock(nflash.mtd.mutex); + for (i = 0; i < info->numblocks; i++) { + if (nflash_checkbadb(nflash.sih, nflash.cc, (i * info->blocksize)) != 0) { + nflash.map[i] = 1; + } + } + mutex_unlock(nflash.mtd.mutex); #ifdef CONFIG_MTD_PARTITIONS parts = init_nflash_mtd_partitions(&nflash.mtd, nflash.mtd.size); diff --git a/release/src-rt/linux/linux-2.6/drivers/mtd/devices/sflash.c b/release/src-rt/linux/linux-2.6/drivers/mtd/devices/sflash.c old mode 100644 new mode 100755 index afa8a66e2a..1523114519 --- a/release/src-rt/linux/linux-2.6/drivers/mtd/devices/sflash.c +++ b/release/src-rt/linux/linux-2.6/drivers/mtd/devices/sflash.c @@ -1,15 +1,21 @@ /* * Broadcom SiliconBackplane chipcommon serial flash interface * - * Copyright (C) 2009, Broadcom Corporation - * All Rights Reserved. + * Copyright (C) 2010, Broadcom Corporation. All Rights Reserved. * - * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY - * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM - * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE. + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - * $Id: sflash.c,v 1.2 2009/11/06 09:55:49 Exp $ + * $Id: sflash.c,v 1.6 2011-02-10 10:55:57 Exp $ */ #include @@ -40,6 +46,7 @@ extern struct mtd_partition * init_mtd_partitions(struct mtd_info *mtd, size_t size); #endif +extern struct mutex *partitions_mutex_init(void); struct sflash_mtd { si_t *sih; @@ -58,11 +65,13 @@ sflash_mtd_poll(struct sflash_mtd *sflash, unsigned int offset, int timeout) int ret = 0; for (;;) { - if (!sflash_poll(sflash->sih, sflash->cc, offset)) { - ret = 0; + if (!sflash_poll(sflash->sih, sflash->cc, offset)) break; - } + if (time_after((unsigned long)jiffies, (unsigned long)now + timeout)) { + if (!sflash_poll(sflash->sih, sflash->cc, offset)) + break; + printk(KERN_ERR "sflash: timeout\n"); ret = -ETIMEDOUT; break; @@ -84,6 +93,8 @@ sflash_mtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u return 0; if ((from + len) > mtd->size) return -EINVAL; + + mutex_lock(mtd->mutex); *retlen = 0; while (len) { @@ -96,7 +107,8 @@ sflash_mtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u buf += bytes; *retlen += bytes; } - + + mutex_unlock(mtd->mutex); return ret; } @@ -112,13 +124,14 @@ sflash_mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, co if ((to + len) > mtd->size) return -EINVAL; + mutex_lock(mtd->mutex); *retlen = 0; while (len) { if ((bytes = sflash_write(sflash->sih, sflash->cc, (uint) to, len, buf)) < 0) { ret = bytes; break; } - if ((ret = sflash_mtd_poll(sflash, (unsigned int) to, HZ / 10))) + if ((ret = sflash_mtd_poll(sflash, (unsigned int) to, HZ))) break; to += (loff_t) bytes; len -= bytes; @@ -126,6 +139,7 @@ sflash_mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, co *retlen += bytes; } + mutex_unlock(mtd->mutex); return ret; } @@ -142,6 +156,7 @@ sflash_mtd_erase(struct mtd_info *mtd, struct erase_info *erase) if ((erase->addr + erase->len) > mtd->size) return -EINVAL; + mutex_lock(mtd->mutex); addr = erase->addr; len = erase->len; @@ -169,6 +184,8 @@ sflash_mtd_erase(struct mtd_info *mtd, struct erase_info *erase) else erase->state = MTD_ERASE_DONE; + mutex_unlock(mtd->mutex); + /* Call erase callback */ if (erase->callback) erase->callback(erase); @@ -247,7 +264,9 @@ sflash_mtd_init(void) sflash.mtd.writesize = 1; sflash.mtd.priv = &sflash; sflash.mtd.owner = THIS_MODULE; - + sflash.mtd.mutex = partitions_mutex_init(); + if (!sflash.mtd.mutex) + return -ENOMEM; #ifdef CONFIG_MTD_PARTITIONS parts = init_mtd_partitions(&sflash.mtd, sflash.mtd.size); @@ -258,6 +277,7 @@ sflash_mtd_init(void) goto fail; } #endif + return 0; fail: diff --git a/release/src-rt/linux/linux-2.6/drivers/mtd/maps/bcm947xx-flash.c b/release/src-rt/linux/linux-2.6/drivers/mtd/maps/bcm947xx-flash.c index e1b2a4135f..b88c7f6a9d 100644 --- a/release/src-rt/linux/linux-2.6/drivers/mtd/maps/bcm947xx-flash.c +++ b/release/src-rt/linux/linux-2.6/drivers/mtd/maps/bcm947xx-flash.c @@ -42,6 +42,8 @@ extern spinlock_t bcm947xx_sih_lock; extern struct mtd_partition * init_mtd_partitions(struct mtd_info *mtd, size_t size); #endif +extern struct mutex *partitions_mutex_init(void); + #define WINDOW_ADDR 0x1fc00000 #define WINDOW_SIZE 0x400000 #define BUSWIDTH 2 @@ -129,6 +131,7 @@ init_bcm947xx_map(void) } bcm947xx_mtd->owner = THIS_MODULE; + bcm947xx_mtd->mutex = partitions_mutex_init(); /* Allow size override for testing */ size = flash ? : bcm947xx_mtd->size; diff --git a/release/src-rt/linux/linux-2.6/include/linux/mtd/mtd.h b/release/src-rt/linux/linux-2.6/include/linux/mtd/mtd.h index 38a6889803..5d825b3b7d 100644 --- a/release/src-rt/linux/linux-2.6/include/linux/mtd/mtd.h +++ b/release/src-rt/linux/linux-2.6/include/linux/mtd/mtd.h @@ -128,6 +128,10 @@ struct mtd_info { char *name; int index; +#ifdef CONFIG_BCM47XX + struct mutex *mutex; +#endif + /* ecc layout structure pointer - read only ! */ struct nand_ecclayout *ecclayout; diff --git a/release/src-rt/linux/linux-2.6/include/linux/mtd/nand.h b/release/src-rt/linux/linux-2.6/include/linux/mtd/nand.h index d2365c8dca..4440398b6d 100644 --- a/release/src-rt/linux/linux-2.6/include/linux/mtd/nand.h +++ b/release/src-rt/linux/linux-2.6/include/linux/mtd/nand.h @@ -45,8 +45,14 @@ extern void nand_wait_ready(struct mtd_info *mtd); * is supported now. If you add a chip with bigger oobsize/page * adjust this accordingly. */ + +#ifdef CONFIG_MTD_BRCMNAND +#define NAND_MAX_OOBSIZE 128 +#define NAND_MAX_PAGESIZE 4096 +#else #define NAND_MAX_OOBSIZE 64 #define NAND_MAX_PAGESIZE 2048 +#endif /* CONFIG_MTD_BRCMNAND */ /* * Constants for hardware specific CLE/ALE/NCE function @@ -61,6 +67,12 @@ extern void nand_wait_ready(struct mtd_info *mtd); /* Select the address latch by setting ALE to high */ #define NAND_ALE 0x04 +#ifdef CONFIG_MTD_BRCMNAND +# define NAND_ALE_COL 0x08 +# define NAND_ALE_ROW 0x10 +#endif /* CONFIG_MTD_BRCMNAND */ + + #define NAND_CTRL_CLE (NAND_NCE | NAND_CLE) #define NAND_CTRL_ALE (NAND_NCE | NAND_ALE) #define NAND_CTRL_CHANGE 0x80 @@ -180,6 +192,12 @@ typedef enum { #define NAND_HAS_CACHEPROG(chip) ((chip->options & NAND_CACHEPRG)) #define NAND_HAS_COPYBACK(chip) ((chip->options & NAND_COPYBACK)) +/* Large page NAND with SOFT_ECC should support subpage reads */ +/* It's patched from Linux 2.6.27.57 */ +#define NAND_SUBPAGE_READ(chip) ((chip->ecc.mode == NAND_ECC_SOFT) \ + && (chip->page_shift > 9)) + + /* Mask to zero out the chip options, which come from the id table */ #define NAND_CHIPOPTIONS_MSK (0x0000ffff & ~NAND_NO_AUTOINCR) @@ -200,6 +218,11 @@ typedef enum { #define NAND_CI_CHIPNR_MSK 0x03 #define NAND_CI_CELLTYPE_MSK 0x0C +#ifdef CONFIG_MTD_BRCMNAND +#define NAND_IS_MLC(chip) ((chip)->cellinfo & NAND_CI_CELLTYPE_MSK) +#endif /* CONFIG_MTD_BRCMNAND */ + + /* * nand_state_t - chip states * Enumeration for NAND flash chip state @@ -253,6 +276,10 @@ struct nand_hw_control { */ struct nand_ecc_ctrl { nand_ecc_modes_t mode; +#ifdef CONFIG_MTD_BRCMNAND + int level; + int oobsize; +#endif /* CONFIG_MTD_BRCMNAND */ int steps; int size; int bytes; @@ -276,6 +303,11 @@ struct nand_ecc_ctrl { int (*read_page)(struct mtd_info *mtd, struct nand_chip *chip, uint8_t *buf); + /* Patched from Linux 2.6.27.57 */ + int (*read_subpage)(struct mtd_info *mtd, + struct nand_chip *chip, + uint32_t offs, uint32_t len, + uint8_t *buf); void (*write_page)(struct mtd_info *mtd, struct nand_chip *chip, const uint8_t *buf); @@ -381,6 +413,11 @@ struct nand_chip { int (*waitfunc)(struct mtd_info *mtd, struct nand_chip *this); void (*erase_cmd)(struct mtd_info *mtd, int page); int (*scan_bbt)(struct mtd_info *mtd); +#ifdef CONFIG_MTD_BRCMNAND + int (*erase_bbt)(struct mtd_info *mtd, struct erase_info *instr, int allowbbt); + int (*get_device)(struct nand_chip *chip, struct mtd_info *mtd, int new_state); + void (*release_device)(struct mtd_info *mtd); +#endif /* CONFIG_MTD_BRCMNAND */ int (*errstat)(struct mtd_info *mtd, struct nand_chip *this, int state, int status, int page); int (*write_page)(struct mtd_info *mtd, struct nand_chip *chip, const uint8_t *buf, int page, int cached, int raw); @@ -399,6 +436,9 @@ struct nand_chip { int subpagesize; uint8_t cellinfo; int badblockpos; +#ifdef CONFIG_MTD_BRCMNAND + int pageidx; +#endif /* CONFIG_MTD_BRCMNAND */ nand_state_t state; diff --git a/release/src/Makefile b/release/src/Makefile index 1cbf32b576..f27054a0e8 100644 --- a/release/src/Makefile +++ b/release/src/Makefile @@ -1225,9 +1225,6 @@ n60n: n60nc: @$(MAKE) r2nc NVRAM_SIZE=60 LINKSYS_E=y -n60e: - @$(MAKE) r2e NVRAM_SIZE=60 LINKSYS_E=y - n60r: @$(MAKE) r2r NVRAM_SIZE=60 LINKSYS_E=y @@ -1271,20 +1268,6 @@ n64v: @$(MAKE) r2v NVRAM_SIZE=64 LINKSYS_E_64k=y -#Linksys E-Series with 4MB Flash - -e1000v2i: - @$(MAKE) r2i NVRAM_SIZE=32 LINKSYS_E1000v2=y NO_JFFS=y - -e1000v2f: - @$(MAKE) r2f NVRAM_SIZE=32 LINKSYS_E1000v2=y NO_JFFS=y - -e1200v1i: - @$(MAKE) r2i NVRAM_SIZE=64 LINKSYS_E1200v1=y NO_JFFS=y - -e1200v1f: - @$(MAKE) r2f NVRAM_SIZE=64 LINKSYS_E1200v1=y NO_JFFS=y - #Linksys E-Series with 4MB Flash (Max Flashsize=3866624) @@ -1300,10 +1283,9 @@ e1200v1f: e1200v1i: @$(MAKE) bin MIPS32=r2 NVRAM_SIZE=64 LINKSYS_E1200v1=y NO_JFFS=y NO_CIFS=y NO_ZEBRA=y IPV6SUPP=y B=I USB="" NO_HTTPS=y KERN_SIZE_OPT=y BUILD_DESC="MiniIPv6" -#Builds for WNR3500L v2 - +#Build for WNR3500L v2 v2e: - @$(MAKE) e MIPS32=r2 WNR3500LV2=1 NO_JFFS=y + @$(MAKE) e MIPS32=r2 WNR3500LV2=1 NAND=y #Builds with wl_high module diff --git a/release/src/router/rc/jffs2.c b/release/src/router/rc/jffs2.c index e216e9f7d1..8f95e8e44d 100644 --- a/release/src/router/rc/jffs2.c +++ b/release/src/router/rc/jffs2.c @@ -44,19 +44,36 @@ void start_jffs2(void) int part; const char *p; struct statfs sf; + int model; + + model = get_model(); if (!wait_action_idle(10)) return; - if (!mtd_getinfo("jffs2", &part, &size)) return; + // new partition name "brcmnand" for WNR3500Lv2 + if (model == MODEL_WNR3500LV2) { + if (!mtd_getinfo("brcmnand", &part, &size)) return; + } + else { + if (!mtd_getinfo("jffs2", &part, &size)) return; + } if (nvram_match("jffs2_format", "1")) { nvram_set("jffs2_format", "0"); nvram_commit_x(); - if (!mtd_erase("jffs2")) { - error("formatting"); - return; + // new partition name "brcmnand" for WNR3500Lv2 + if (model == MODEL_WNR3500LV2) { + if (!mtd_erase("brcmnand")) { + error("formatting"); + return; + } + } + else { + if (!mtd_erase("jffs2")) { + error("formatting"); + return; + } } - format = 1; } @@ -79,9 +96,18 @@ void start_jffs2(void) return; } - if (!mtd_unlock("jffs2")) { - error("unlocking"); - return; + // new partition name "brcmnand" for WNR3500Lv2 + if (model == MODEL_WNR3500LV2) { + if (!mtd_unlock("brcmnand")) { + error("unlocking"); + return; + } + } + else { + if (!mtd_unlock("jffs2")) { + error("unlocking"); + return; + } } modprobe(JFFS_NAME); -- 2.11.4.GIT