X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fmtd%2Fnand%2Fdiskonchip.c;h=0f7dedd6b88ecd95fd30d3ac761c11574478488d;hb=9bf4aaab3e101692164d49b7ca357651eb691cb6;hp=677c21685218eb3f2b760029764bfc7524b6ebd6;hpb=db216c3d5e4c040e557a50f8f5d35d5c415e8c1c;p=linux-2.6.git diff --git a/drivers/mtd/nand/diskonchip.c b/drivers/mtd/nand/diskonchip.c index 677c21685..0f7dedd6b 100644 --- a/drivers/mtd/nand/diskonchip.c +++ b/drivers/mtd/nand/diskonchip.c @@ -2,12 +2,16 @@ * drivers/mtd/nand/diskonchip.c * * (C) 2003 Red Hat, Inc. + * (C) 2004 Dan Brown + * (C) 2004 Kalev Lember * * Author: David Woodhouse + * Additional Diskonchip 2000 and Millennium support by Dan Brown + * Diskonchip Millennium Plus support by Kalev Lember * * Interface to generic NAND code for M-Systems DiskOnChip devices * - * $Id: diskonchip.c,v 1.23 2004/07/13 00:14:35 dbrown Exp $ + * $Id: diskonchip.c,v 1.34 2004/08/09 19:41:12 dbrown Exp $ */ #include @@ -24,13 +28,13 @@ #include /* Where to look for the devices? */ -#ifndef CONFIG_MTD_DOCPROBE_ADDRESS -#define CONFIG_MTD_DOCPROBE_ADDRESS 0 +#ifndef CONFIG_MTD_DISKONCHIP_PROBE_ADDRESS +#define CONFIG_MTD_DISKONCHIP_PROBE_ADDRESS 0 #endif static unsigned long __initdata doc_locations[] = { #if defined (__alpha__) || defined(__i386__) || defined(__x86_64__) -#ifdef CONFIG_MTD_DOCPROBE_HIGH +#ifdef CONFIG_MTD_DISKONCHIP_PROBE_HIGH 0xfffc8000, 0xfffca000, 0xfffcc000, 0xfffce000, 0xfffd0000, 0xfffd2000, 0xfffd4000, 0xfffd6000, 0xfffd8000, 0xfffda000, 0xfffdc000, 0xfffde000, @@ -84,6 +88,7 @@ static u_char empty_write_ecc[6] = { 0x4b, 0x00, 0xe2, 0x0e, 0x93, 0xf7 }; #define INFTL_BBT_RESERVED_BLOCKS 4 +#define DoC_is_MillenniumPlus(doc) ((doc)->ChipID == DOC_ChipID_DocMilPlus16 || (doc)->ChipID == DOC_ChipID_DocMilPlus32) #define DoC_is_Millennium(doc) ((doc)->ChipID == DOC_ChipID_DocMil) #define DoC_is_2000(doc) ((doc)->ChipID == DOC_ChipID_Doc2k) @@ -109,7 +114,7 @@ static int inftl_bbt_write=0; #endif MODULE_PARM(inftl_bbt_write, "i"); -static unsigned long doc_config_location = CONFIG_MTD_DOCPROBE_ADDRESS; +static unsigned long doc_config_location = CONFIG_MTD_DISKONCHIP_PROBE_ADDRESS; MODULE_PARM(doc_config_location, "l"); MODULE_PARM_DESC(doc_config_location, "Physical memory address at which to probe for DiskOnChip"); @@ -121,11 +126,16 @@ static void DoC_Delay(struct doc_priv *doc, unsigned short cycles) for (i = 0; i < cycles; i++) { if (DoC_is_Millennium(doc)) dummy = ReadDOC(doc->virtadr, NOP); + else if (DoC_is_MillenniumPlus(doc)) + dummy = ReadDOC(doc->virtadr, Mplus_NOP); else dummy = ReadDOC(doc->virtadr, DOCStatus); } } + +#define CDSN_CTRL_FR_B_MASK (CDSN_CTRL_FR_B0 | CDSN_CTRL_FR_B1) + /* DOC_WaitReady: Wait for RDY line to be asserted by the flash chip */ static int _DoC_WaitReady(struct doc_priv *doc) { @@ -134,13 +144,24 @@ static int _DoC_WaitReady(struct doc_priv *doc) if(debug) printk("_DoC_WaitReady...\n"); /* Out-of-line routine to wait for chip response */ - while (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B)) { - if (time_after(jiffies, timeo)) { - printk("_DoC_WaitReady timed out.\n"); - return -EIO; + if (DoC_is_MillenniumPlus(doc)) { + while ((ReadDOC(docptr, Mplus_FlashControl) & CDSN_CTRL_FR_B_MASK) != CDSN_CTRL_FR_B_MASK) { + if (time_after(jiffies, timeo)) { + printk("_DoC_WaitReady timed out.\n"); + return -EIO; + } + udelay(1); + cond_resched(); + } + } else { + while (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B)) { + if (time_after(jiffies, timeo)) { + printk("_DoC_WaitReady timed out.\n"); + return -EIO; + } + udelay(1); + cond_resched(); } - udelay(1); - cond_resched(); } return 0; @@ -151,13 +172,21 @@ static inline int DoC_WaitReady(struct doc_priv *doc) unsigned long docptr = doc->virtadr; int ret = 0; - DoC_Delay(doc, 4); + if (DoC_is_MillenniumPlus(doc)) { + DoC_Delay(doc, 4); - if (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B)) - /* Call the out-of-line routine to wait */ - ret = _DoC_WaitReady(doc); + if ((ReadDOC(docptr, Mplus_FlashControl) & CDSN_CTRL_FR_B_MASK) != CDSN_CTRL_FR_B_MASK) + /* Call the out-of-line routine to wait */ + ret = _DoC_WaitReady(doc); + } else { + DoC_Delay(doc, 4); + + if (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B)) + /* Call the out-of-line routine to wait */ + ret = _DoC_WaitReady(doc); + DoC_Delay(doc, 2); + } - DoC_Delay(doc, 2); if(debug) printk("DoC_WaitReady OK\n"); return ret; } @@ -382,7 +411,7 @@ static void doc2001_readbuf(struct mtd_info *mtd, ReadDOC(docptr, ReadPipeInit); for (i=0; i < len-1; i++) - buf[i] = ReadDOC(docptr, Mil_CDSN_IO); + buf[i] = ReadDOC(docptr, Mil_CDSN_IO + (i & 0xff)); /* Terminate read pipeline */ buf[i] = ReadDOC(docptr, LastDataRead); @@ -409,15 +438,126 @@ static int doc2001_verifybuf(struct mtd_info *mtd, return 0; } -static void doc200x_select_chip(struct mtd_info *mtd, int chip) +static u_char doc2001plus_read_byte(struct mtd_info *mtd) +{ + struct nand_chip *this = mtd->priv; + struct doc_priv *doc = (void *)this->priv; + unsigned long docptr = doc->virtadr; + u_char ret; + + ReadDOC(docptr, Mplus_ReadPipeInit); + ReadDOC(docptr, Mplus_ReadPipeInit); + ret = ReadDOC(docptr, Mplus_LastDataRead); + if (debug) printk("read_byte returns %02x\n", ret); + return ret; +} + +static void doc2001plus_writebuf(struct mtd_info *mtd, + const u_char *buf, int len) +{ + struct nand_chip *this = mtd->priv; + struct doc_priv *doc = (void *)this->priv; + unsigned long docptr = doc->virtadr; + int i; + + if (debug)printk("writebuf of %d bytes: ", len); + for (i=0; i < len; i++) { + WriteDOC_(buf[i], docptr, DoC_Mil_CDSN_IO + i); + if (debug && i < 16) + printk("%02x ", buf[i]); + } + if (debug) printk("\n"); +} + +static void doc2001plus_readbuf(struct mtd_info *mtd, + u_char *buf, int len) +{ + struct nand_chip *this = mtd->priv; + struct doc_priv *doc = (void *)this->priv; + unsigned long docptr = doc->virtadr; + int i; + + if (debug)printk("readbuf of %d bytes: ", len); + + /* Start read pipeline */ + ReadDOC(docptr, Mplus_ReadPipeInit); + ReadDOC(docptr, Mplus_ReadPipeInit); + + for (i=0; i < len-2; i++) { + buf[i] = ReadDOC(docptr, Mil_CDSN_IO); + if (debug && i < 16) + printk("%02x ", buf[i]); + } + + /* Terminate read pipeline */ + buf[len-2] = ReadDOC(docptr, Mplus_LastDataRead); + if (debug && i < 16) + printk("%02x ", buf[len-2]); + buf[len-1] = ReadDOC(docptr, Mplus_LastDataRead); + if (debug && i < 16) + printk("%02x ", buf[len-1]); + if (debug) printk("\n"); +} + +static int doc2001plus_verifybuf(struct mtd_info *mtd, + const u_char *buf, int len) +{ + struct nand_chip *this = mtd->priv; + struct doc_priv *doc = (void *)this->priv; + unsigned long docptr = doc->virtadr; + int i; + + if (debug)printk("verifybuf of %d bytes: ", len); + + /* Start read pipeline */ + ReadDOC(docptr, Mplus_ReadPipeInit); + ReadDOC(docptr, Mplus_ReadPipeInit); + + for (i=0; i < len-2; i++) + if (buf[i] != ReadDOC(docptr, Mil_CDSN_IO)) { + ReadDOC(docptr, Mplus_LastDataRead); + ReadDOC(docptr, Mplus_LastDataRead); + return i; + } + if (buf[len-2] != ReadDOC(docptr, Mplus_LastDataRead)) + return len-2; + if (buf[len-1] != ReadDOC(docptr, Mplus_LastDataRead)) + return len-1; + return 0; +} + +static void doc2001plus_select_chip(struct mtd_info *mtd, int chip) { struct nand_chip *this = mtd->priv; struct doc_priv *doc = (void *)this->priv; unsigned long docptr = doc->virtadr; int floor = 0; - /* 11.4.4 -- deassert CE before changing chip */ - doc200x_hwcontrol(mtd, NAND_CTL_CLRNCE); + if(debug)printk("select chip (%d)\n", chip); + + if (chip == -1) { + /* Disable flash internally */ + WriteDOC(0, docptr, Mplus_FlashSelect); + return; + } + + floor = chip / doc->chips_per_floor; + chip -= (floor * doc->chips_per_floor); + + /* Assert ChipEnable and deassert WriteProtect */ + WriteDOC((DOC_FLASH_CE), docptr, Mplus_FlashSelect); + this->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + + doc->curchip = chip; + doc->curfloor = floor; +} + +static void doc200x_select_chip(struct mtd_info *mtd, int chip) +{ + struct nand_chip *this = mtd->priv; + struct doc_priv *doc = (void *)this->priv; + unsigned long docptr = doc->virtadr; + int floor = 0; if(debug)printk("select chip (%d)\n", chip); @@ -427,6 +567,9 @@ static void doc200x_select_chip(struct mtd_info *mtd, int chip) floor = chip / doc->chips_per_floor; chip -= (floor * doc->chips_per_floor); + /* 11.4.4 -- deassert CE before changing chip */ + doc200x_hwcontrol(mtd, NAND_CTL_CLRNCE); + WriteDOC(floor, docptr, FloorSelect); WriteDOC(chip, docptr, CDSNDeviceSelect); @@ -474,24 +617,140 @@ static void doc200x_hwcontrol(struct mtd_info *mtd, int cmd) DoC_Delay(doc, 4); } +static void doc2001plus_command (struct mtd_info *mtd, unsigned command, int column, int page_addr) +{ + struct nand_chip *this = mtd->priv; + struct doc_priv *doc = (void *)this->priv; + unsigned long docptr = doc->virtadr; + + /* + * Must terminate write pipeline before sending any commands + * to the device. + */ + if (command == NAND_CMD_PAGEPROG) { + WriteDOC(0x00, docptr, Mplus_WritePipeTerm); + WriteDOC(0x00, docptr, Mplus_WritePipeTerm); + } + + /* + * Write out the command to the device. + */ + if (command == NAND_CMD_SEQIN) { + int readcmd; + + if (column >= mtd->oobblock) { + /* OOB area */ + column -= mtd->oobblock; + readcmd = NAND_CMD_READOOB; + } else if (column < 256) { + /* First 256 bytes --> READ0 */ + readcmd = NAND_CMD_READ0; + } else { + column -= 256; + readcmd = NAND_CMD_READ1; + } + WriteDOC(readcmd, docptr, Mplus_FlashCmd); + } + WriteDOC(command, docptr, Mplus_FlashCmd); + WriteDOC(0, docptr, Mplus_WritePipeTerm); + WriteDOC(0, docptr, Mplus_WritePipeTerm); + + if (column != -1 || page_addr != -1) { + /* Serially input address */ + if (column != -1) { + /* Adjust columns for 16 bit buswidth */ + if (this->options & NAND_BUSWIDTH_16) + column >>= 1; + WriteDOC(column, docptr, Mplus_FlashAddress); + } + if (page_addr != -1) { + WriteDOC((unsigned char) (page_addr & 0xff), docptr, Mplus_FlashAddress); + WriteDOC((unsigned char) ((page_addr >> 8) & 0xff), docptr, Mplus_FlashAddress); + /* One more address cycle for higher density devices */ + if (this->chipsize & 0x0c000000) { + WriteDOC((unsigned char) ((page_addr >> 16) & 0x0f), docptr, Mplus_FlashAddress); + printk("high density\n"); + } + } + WriteDOC(0, docptr, Mplus_WritePipeTerm); + WriteDOC(0, docptr, Mplus_WritePipeTerm); + /* deassert ALE */ + if (command == NAND_CMD_READ0 || command == NAND_CMD_READ1 || command == NAND_CMD_READOOB || command == NAND_CMD_READID) + WriteDOC(0, docptr, Mplus_FlashControl); + } + + /* + * 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 (this->dev_ready) + break; + udelay(this->chip_delay); + WriteDOC(NAND_CMD_STATUS, docptr, Mplus_FlashCmd); + WriteDOC(0, docptr, Mplus_WritePipeTerm); + WriteDOC(0, docptr, Mplus_WritePipeTerm); + while ( !(this->read_byte(mtd) & 0x40)); + return; + + /* This applies to read commands */ + default: + /* + * If we don't have access to the busy pin, we apply the given + * command delay + */ + if (!this->dev_ready) { + udelay (this->chip_delay); + return; + } + } + + /* Apply this short delay always to ensure that we do wait tWB in + * any case on any machine. */ + ndelay (100); + /* wait until command is processed */ + while (!this->dev_ready(mtd)); +} + static int doc200x_dev_ready(struct mtd_info *mtd) { struct nand_chip *this = mtd->priv; struct doc_priv *doc = (void *)this->priv; unsigned long docptr = doc->virtadr; - /* 11.4.2 -- must NOP four times before checking FR/B# */ - DoC_Delay(doc, 4); - if (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B)) { - if(debug) - printk("not ready\n"); - return 0; + if (DoC_is_MillenniumPlus(doc)) { + /* 11.4.2 -- must NOP four times before checking FR/B# */ + DoC_Delay(doc, 4); + if ((ReadDOC(docptr, Mplus_FlashControl) & CDSN_CTRL_FR_B_MASK) != CDSN_CTRL_FR_B_MASK) { + if(debug) + printk("not ready\n"); + return 0; + } + if (debug)printk("was ready\n"); + return 1; + } else { + /* 11.4.2 -- must NOP four times before checking FR/B# */ + DoC_Delay(doc, 4); + if (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B)) { + if(debug) + printk("not ready\n"); + return 0; + } + /* 11.4.2 -- Must NOP twice if it's ready */ + DoC_Delay(doc, 2); + if (debug)printk("was ready\n"); + return 1; } - /* 11.4.2 -- Must NOP twice if it's ready */ - DoC_Delay(doc, 2); - if (debug)printk("was ready\n"); - return 1; -} +} static int doc200x_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip) { @@ -516,7 +775,26 @@ static void doc200x_enable_hwecc(struct mtd_info *mtd, int mode) WriteDOC(DOC_ECC_RESET, docptr, ECCConf); WriteDOC(DOC_ECC_EN | DOC_ECC_RW, docptr, ECCConf); break; - } + } +} + +static void doc2001plus_enable_hwecc(struct mtd_info *mtd, int mode) +{ + struct nand_chip *this = mtd->priv; + struct doc_priv *doc = (void *)this->priv; + unsigned long docptr = doc->virtadr; + + /* Prime the ECC engine */ + switch(mode) { + case NAND_ECC_READ: + WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf); + WriteDOC(DOC_ECC_EN, docptr, Mplus_ECCConf); + break; + case NAND_ECC_WRITE: + WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf); + WriteDOC(DOC_ECC_EN | DOC_ECC_RW, docptr, Mplus_ECCConf); + break; + } } /* This code is only called on write */ @@ -536,6 +814,10 @@ static int doc200x_calculate_ecc(struct mtd_info *mtd, const u_char *dat, WriteDOC(0, docptr, 2k_CDSN_IO); WriteDOC(0, docptr, 2k_CDSN_IO); WriteDOC(doc->CDSNControl, docptr, CDSNControl); + } else if (DoC_is_MillenniumPlus(doc)) { + WriteDOC(0, docptr, Mplus_NOP); + WriteDOC(0, docptr, Mplus_NOP); + WriteDOC(0, docptr, Mplus_NOP); } else { WriteDOC(0, docptr, NOP); WriteDOC(0, docptr, NOP); @@ -543,11 +825,17 @@ static int doc200x_calculate_ecc(struct mtd_info *mtd, const u_char *dat, } for (i = 0; i < 6; i++) { - ecc_code[i] = ReadDOC_(docptr, DoC_ECCSyndrome0 + i); + if (DoC_is_MillenniumPlus(doc)) + ecc_code[i] = ReadDOC_(docptr, DoC_Mplus_ECCSyndrome0 + i); + else + ecc_code[i] = ReadDOC_(docptr, DoC_ECCSyndrome0 + i); if (ecc_code[i] != empty_write_ecc[i]) emptymatch = 0; } - WriteDOC(DOC_ECC_DIS, docptr, ECCConf); + if (DoC_is_MillenniumPlus(doc)) + WriteDOC(DOC_ECC_DIS, docptr, Mplus_ECCConf); + else + WriteDOC(DOC_ECC_DIS, docptr, ECCConf); #if 0 /* If emptymatch=1, we might have an all-0xff data buffer. Check. */ if (emptymatch) { @@ -582,6 +870,10 @@ static int doc200x_correct_data(struct mtd_info *mtd, u_char *dat, u_char *read_ dummy = ReadDOC(docptr, 2k_ECCStatus); dummy = ReadDOC(docptr, 2k_ECCStatus); dummy = ReadDOC(docptr, 2k_ECCStatus); + } else if (DoC_is_MillenniumPlus(doc)) { + dummy = ReadDOC(docptr, Mplus_ECCConf); + dummy = ReadDOC(docptr, Mplus_ECCConf); + dummy = ReadDOC(docptr, Mplus_ECCConf); } else { dummy = ReadDOC(docptr, ECCConf); dummy = ReadDOC(docptr, ECCConf); @@ -591,7 +883,10 @@ static int doc200x_correct_data(struct mtd_info *mtd, u_char *dat, u_char *read_ /* Error occured ? */ if (dummy & 0x80) { for (i = 0; i < 6; i++) { - calc_ecc[i] = ReadDOC_(docptr, DoC_ECCSyndrome0 + i); + if (DoC_is_MillenniumPlus(doc)) + calc_ecc[i] = ReadDOC_(docptr, DoC_Mplus_ECCSyndrome0 + i); + else + calc_ecc[i] = ReadDOC_(docptr, DoC_ECCSyndrome0 + i); if (calc_ecc[i] != empty_read_syndrome[i]) emptymatch = 0; } @@ -623,7 +918,10 @@ static int doc200x_correct_data(struct mtd_info *mtd, u_char *dat, u_char *read_ if (ret > 0) printk(KERN_ERR "doc200x_correct_data corrected %d errors\n", ret); } - WriteDOC(DOC_ECC_DIS, docptr, ECCConf); + if (DoC_is_MillenniumPlus(doc)) + WriteDOC(DOC_ECC_DIS, docptr, Mplus_ECCConf); + else + WriteDOC(DOC_ECC_DIS, docptr, ECCConf); if (no_ecc_failures && (ret == -1)) { printk(KERN_ERR "suppressing ECC failure\n"); ret = 0; @@ -651,8 +949,9 @@ static int __init find_media_headers(struct mtd_info *mtd, u_char *buf, { struct nand_chip *this = mtd->priv; struct doc_priv *doc = (void *)this->priv; - int offs, end = (MAX_MEDIAHEADER_SCAN << this->phys_erase_shift); - int ret, retlen; + unsigned offs, end = (MAX_MEDIAHEADER_SCAN << this->phys_erase_shift); + int ret; + size_t retlen; end = min(end, mtd->size); // paranoia for (offs = 0; offs < end; offs += mtd->erasesize) { @@ -693,13 +992,20 @@ static inline int __init nftl_partscan(struct mtd_info *mtd, { struct nand_chip *this = mtd->priv; struct doc_priv *doc = (void *)this->priv; - u_char *buf = this->data_buf; - struct NFTLMediaHeader *mh = (struct NFTLMediaHeader *) buf; - const int psize = 1 << this->page_shift; - int blocks, maxblocks; + int ret = 0; + u_char *buf; + struct NFTLMediaHeader *mh; + const unsigned psize = 1 << this->page_shift; + unsigned blocks, maxblocks; int offs, numheaders; - if (!(numheaders=find_media_headers(mtd, buf, "ANAND", 1))) return 0; + buf = kmalloc(mtd->oobblock, GFP_KERNEL); + if (!buf) { + printk(KERN_ERR "DiskOnChip mediaheader kmalloc failed!\n"); + return 0; + } + if (!(numheaders=find_media_headers(mtd, buf, "ANAND", 1))) goto out; + mh = (struct NFTLMediaHeader *) buf; //#ifdef CONFIG_MTD_DEBUG_VERBOSE // if (CONFIG_MTD_DEBUG_VERBOSE >= 2) @@ -714,7 +1020,7 @@ static inline int __init nftl_partscan(struct mtd_info *mtd, //#endif blocks = mtd->size >> this->phys_erase_shift; - maxblocks = min(32768, mtd->erasesize - psize); + maxblocks = min(32768U, mtd->erasesize - psize); if (mh->UnitSizeFactor == 0x00) { /* Auto-determine UnitSizeFactor. The constraints are: @@ -725,7 +1031,7 @@ static inline int __init nftl_partscan(struct mtd_info *mtd, mh->UnitSizeFactor = 0xff; while (blocks > maxblocks) { blocks >>= 1; - maxblocks = min(32768, (maxblocks << 1) + psize); + maxblocks = min(32768U, (maxblocks << 1) + psize); mh->UnitSizeFactor--; } printk(KERN_WARNING "UnitSizeFactor=0x00 detected. Correct value is assumed to be 0x%02x.\n", mh->UnitSizeFactor); @@ -741,12 +1047,12 @@ static inline int __init nftl_partscan(struct mtd_info *mtd, mtd->erasesize <<= (0xff - mh->UnitSizeFactor); printk(KERN_INFO "Setting virtual erase size to %d\n", mtd->erasesize); blocks = mtd->size >> this->bbt_erase_shift; - maxblocks = min(32768, mtd->erasesize - psize); + maxblocks = min(32768U, mtd->erasesize - psize); } if (blocks > maxblocks) { printk(KERN_ERR "UnitSizeFactor of 0x%02x is inconsistent with device size. Aborting.\n", mh->UnitSizeFactor); - return 0; + goto out; } /* Skip past the media headers. */ @@ -767,9 +1073,13 @@ static inline int __init nftl_partscan(struct mtd_info *mtd, parts[1].name = " DiskOnChip Remainder partition"; parts[1].offset = offs; parts[1].size = mtd->size - offs; - return 2; + ret = 2; + goto out; } - return 1; + ret = 1; +out: + kfree(buf); + return ret; } /* This is a stripped-down copy of the code in inftlmount.c */ @@ -778,8 +1088,9 @@ static inline int __init inftl_partscan(struct mtd_info *mtd, { struct nand_chip *this = mtd->priv; struct doc_priv *doc = (void *)this->priv; - u_char *buf = this->data_buf; - struct INFTLMediaHeader *mh = (struct INFTLMediaHeader *) buf; + int ret = 0; + u_char *buf; + struct INFTLMediaHeader *mh; struct INFTLPartition *ip; int numparts = 0; int blocks; @@ -790,8 +1101,15 @@ static inline int __init inftl_partscan(struct mtd_info *mtd, if (inftl_bbt_write) end -= (INFTL_BBT_RESERVED_BLOCKS << this->phys_erase_shift); - if (!find_media_headers(mtd, buf, "BNAND", 0)) return 0; + buf = kmalloc(mtd->oobblock, GFP_KERNEL); + if (!buf) { + printk(KERN_ERR "DiskOnChip mediaheader kmalloc failed!\n"); + return 0; + } + + if (!find_media_headers(mtd, buf, "BNAND", 0)) goto out; doc->mh1_page = doc->mh0_page + (4096 >> this->page_shift); + mh = (struct INFTLMediaHeader *) buf; mh->NoOfBootImageBlocks = le32_to_cpu(mh->NoOfBootImageBlocks); mh->NoOfBinaryPartitions = le32_to_cpu(mh->NoOfBinaryPartitions); @@ -808,13 +1126,17 @@ static inline int __init inftl_partscan(struct mtd_info *mtd, " NoOfBDTLPartitions = %d\n" " BlockMultiplerBits = %d\n" " FormatFlgs = %d\n" - " OsakVersion = 0x%x\n" + " OsakVersion = %d.%d.%d.%d\n" " PercentUsed = %d\n", mh->bootRecordID, mh->NoOfBootImageBlocks, mh->NoOfBinaryPartitions, mh->NoOfBDTLPartitions, mh->BlockMultiplierBits, mh->FormatFlags, - mh->OsakVersion, mh->PercentUsed); + ((unsigned char *) &mh->OsakVersion)[0] & 0xf, + ((unsigned char *) &mh->OsakVersion)[1] & 0xf, + ((unsigned char *) &mh->OsakVersion)[2] & 0xf, + ((unsigned char *) &mh->OsakVersion)[3] & 0xf, + mh->PercentUsed); //#endif vshift = this->phys_erase_shift + mh->BlockMultiplierBits; @@ -822,13 +1144,13 @@ static inline int __init inftl_partscan(struct mtd_info *mtd, blocks = mtd->size >> vshift; if (blocks > 32768) { printk(KERN_ERR "BlockMultiplierBits=%d is inconsistent with device size. Aborting.\n", mh->BlockMultiplierBits); - return 0; + goto out; } blocks = doc->chips_per_floor << (this->chip_shift - this->phys_erase_shift); if (inftl_bbt_write && (blocks > mtd->erasesize)) { printk(KERN_ERR "Writeable BBTs spanning more than one erase block are not yet supported. FIX ME!\n"); - return 0; + goto out; } /* Scan the partitions */ @@ -880,7 +1202,10 @@ static inline int __init inftl_partscan(struct mtd_info *mtd, parts[numparts].size = end - parts[numparts].offset; numparts++; } - return numparts; + ret = numparts; +out: + kfree(buf); + return ret; } static int __init nftl_scan_bbt(struct mtd_info *mtd) @@ -915,8 +1240,9 @@ static int __init nftl_scan_bbt(struct mtd_info *mtd) if ((ret = nand_scan_bbt(mtd, NULL))) return ret; add_mtd_device(mtd); -#if defined(CONFIG_MTD_PARTITIONS) || defined(CONFIG_MTD_PARTITIONS_MODULE) - if (!no_autopart) add_mtd_partitions(mtd, parts, numparts); +#ifdef CONFIG_MTD_PARTITIONS + if (!no_autopart) + add_mtd_partitions(mtd, parts, numparts); #endif return 0; } @@ -933,39 +1259,36 @@ static int __init inftl_scan_bbt(struct mtd_info *mtd) return -EIO; } - if (mtd->size == (8<<20)) { -#if 0 -/* This doesn't seem to work for me. I get ECC errors on every page. */ - /* The Millennium 8MiB is actually an NFTL device! */ - mtd->name = "DiskOnChip Millennium 8MiB (NFTL)"; - return nftl_scan_bbt(mtd); -#endif - printk(KERN_ERR "DiskOnChip Millennium 8MiB is not supported.\n"); - return -EIO; + if (DoC_is_MillenniumPlus(doc)) { + this->bbt_td->options = NAND_BBT_2BIT | NAND_BBT_ABSPAGE; + if (inftl_bbt_write) + this->bbt_td->options |= NAND_BBT_WRITE; + this->bbt_td->pages[0] = 2; + this->bbt_md = NULL; + } else { + this->bbt_td->options = NAND_BBT_LASTBLOCK | NAND_BBT_8BIT | + NAND_BBT_VERSION; + if (inftl_bbt_write) + this->bbt_td->options |= NAND_BBT_WRITE; + this->bbt_td->offs = 8; + this->bbt_td->len = 8; + this->bbt_td->veroffs = 7; + this->bbt_td->maxblocks = INFTL_BBT_RESERVED_BLOCKS; + this->bbt_td->reserved_block_code = 0x01; + this->bbt_td->pattern = "MSYS_BBT"; + + this->bbt_md->options = NAND_BBT_LASTBLOCK | NAND_BBT_8BIT | + NAND_BBT_VERSION; + if (inftl_bbt_write) + this->bbt_md->options |= NAND_BBT_WRITE; + this->bbt_md->offs = 8; + this->bbt_md->len = 8; + this->bbt_md->veroffs = 7; + this->bbt_md->maxblocks = INFTL_BBT_RESERVED_BLOCKS; + this->bbt_md->reserved_block_code = 0x01; + this->bbt_md->pattern = "TBB_SYSM"; } - this->bbt_td->options = NAND_BBT_LASTBLOCK | NAND_BBT_8BIT | - NAND_BBT_VERSION; - if (inftl_bbt_write) - this->bbt_td->options |= NAND_BBT_WRITE; - this->bbt_td->offs = 8; - this->bbt_td->len = 8; - this->bbt_td->veroffs = 7; - this->bbt_td->maxblocks = INFTL_BBT_RESERVED_BLOCKS; - this->bbt_td->reserved_block_code = 0x01; - this->bbt_td->pattern = "MSYS_BBT"; - - this->bbt_md->options = NAND_BBT_LASTBLOCK | NAND_BBT_8BIT | - NAND_BBT_VERSION; - if (inftl_bbt_write) - this->bbt_md->options |= NAND_BBT_WRITE; - this->bbt_md->offs = 8; - this->bbt_md->len = 8; - this->bbt_md->veroffs = 7; - this->bbt_md->maxblocks = INFTL_BBT_RESERVED_BLOCKS; - this->bbt_md->reserved_block_code = 0x01; - this->bbt_md->pattern = "TBB_SYSM"; - /* It's safe to set bd=NULL below because NAND_BBT_CREATE is not set. At least as nand_bbt.c is currently written. */ if ((ret = nand_scan_bbt(mtd, NULL))) @@ -977,8 +1300,9 @@ static int __init inftl_scan_bbt(struct mtd_info *mtd) autopartitioning, but I want to give it more thought. */ if (!numparts) return -EIO; add_mtd_device(mtd); -#if defined(CONFIG_MTD_PARTITIONS) || defined(CONFIG_MTD_PARTITIONS_MODULE) - if (!no_autopart) add_mtd_partitions(mtd, parts, numparts); +#ifdef CONFIG_MTD_PARTITIONS + if (!no_autopart) + add_mtd_partitions(mtd, parts, numparts); #endif return 0; } @@ -1011,7 +1335,6 @@ static inline int __init doc2001_init(struct mtd_info *mtd) this->write_buf = doc2001_writebuf; this->read_buf = doc2001_readbuf; this->verify_buf = doc2001_verifybuf; - this->scan_bbt = inftl_scan_bbt; ReadDOC(doc->virtadr, ChipID); ReadDOC(doc->virtadr, ChipID); @@ -1023,15 +1346,39 @@ static inline int __init doc2001_init(struct mtd_info *mtd) can have multiple chips. */ doc2000_count_chips(mtd); mtd->name = "DiskOnChip 2000 (INFTL Model)"; + this->scan_bbt = inftl_scan_bbt; return (4 * doc->chips_per_floor); } else { /* Bog-standard Millennium */ doc->chips_per_floor = 1; mtd->name = "DiskOnChip Millennium"; + this->scan_bbt = nftl_scan_bbt; return 1; } } +static inline int __init doc2001plus_init(struct mtd_info *mtd) +{ + struct nand_chip *this = mtd->priv; + struct doc_priv *doc = (void *)this->priv; + + this->write_byte = NULL; + this->read_byte = doc2001plus_read_byte; + this->write_buf = doc2001plus_writebuf; + this->read_buf = doc2001plus_readbuf; + this->verify_buf = doc2001plus_verifybuf; + this->scan_bbt = inftl_scan_bbt; + this->hwcontrol = NULL; + this->select_chip = doc2001plus_select_chip; + this->cmdfunc = doc2001plus_command; + this->enable_hwecc = doc2001plus_enable_hwecc; + + doc->chips_per_floor = 1; + mtd->name = "DiskOnChip Millennium Plus"; + + return 1; +} + static inline int __init doc_probe(unsigned long physadr) { unsigned char ChipID; @@ -1081,6 +1428,42 @@ static inline int __init doc_probe(unsigned long physadr) case DOC_ChipID_DocMil: reg = DoC_ECCConf; break; + case DOC_ChipID_DocMilPlus16: + case DOC_ChipID_DocMilPlus32: + case 0: + /* Possible Millennium Plus, need to do more checks */ + /* Possibly release from power down mode */ + for (tmp = 0; (tmp < 4); tmp++) + ReadDOC(virtadr, Mplus_Power); + + /* Reset the Millennium Plus ASIC */ + tmp = DOC_MODE_RESET | DOC_MODE_MDWREN | DOC_MODE_RST_LAT | + DOC_MODE_BDECT; + WriteDOC(tmp, virtadr, Mplus_DOCControl); + WriteDOC(~tmp, virtadr, Mplus_CtrlConfirm); + + mdelay(1); + /* Enable the Millennium Plus ASIC */ + tmp = DOC_MODE_NORMAL | DOC_MODE_MDWREN | DOC_MODE_RST_LAT | + DOC_MODE_BDECT; + WriteDOC(tmp, virtadr, Mplus_DOCControl); + WriteDOC(~tmp, virtadr, Mplus_CtrlConfirm); + mdelay(1); + + ChipID = ReadDOC(virtadr, ChipID); + + switch (ChipID) { + case DOC_ChipID_DocMilPlus16: + reg = DoC_Mplus_Toggle; + break; + case DOC_ChipID_DocMilPlus32: + printk(KERN_ERR "DiskOnChip Millennium Plus 32MB is not supported, ignoring.\n"); + default: + ret = -ENODEV; + goto notfound; + } + break; + default: ret = -ENODEV; goto notfound; @@ -1096,22 +1479,35 @@ static inline int __init doc_probe(unsigned long physadr) } for (mtd = doclist; mtd; mtd = doc->nextdoc) { + unsigned char oldval; + unsigned char newval; nand = mtd->priv; doc = (void *)nand->priv; /* Use the alias resolution register to determine if this is in fact the same DOC aliased to a new address. If writes to one chip's alias resolution register change the value on the other chip, they're the same chip. */ - unsigned char oldval = ReadDOC(doc->virtadr, AliasResolution); - unsigned char newval = ReadDOC(virtadr, AliasResolution); + if (ChipID == DOC_ChipID_DocMilPlus16) { + oldval = ReadDOC(doc->virtadr, Mplus_AliasResolution); + newval = ReadDOC(virtadr, Mplus_AliasResolution); + } else { + oldval = ReadDOC(doc->virtadr, AliasResolution); + newval = ReadDOC(virtadr, AliasResolution); + } if (oldval != newval) continue; - WriteDOC(~newval, virtadr, AliasResolution); - oldval = ReadDOC(doc->virtadr, AliasResolution); - WriteDOC(newval, virtadr, AliasResolution); // restore it + if (ChipID == DOC_ChipID_DocMilPlus16) { + WriteDOC(~newval, virtadr, Mplus_AliasResolution); + oldval = ReadDOC(doc->virtadr, Mplus_AliasResolution); + WriteDOC(newval, virtadr, Mplus_AliasResolution); // restore it + } else { + WriteDOC(~newval, virtadr, AliasResolution); + oldval = ReadDOC(doc->virtadr, AliasResolution); + WriteDOC(newval, virtadr, AliasResolution); // restore it + } newval = ~newval; if (oldval == newval) { - //printk(KERN_DEBUG "Found alias of DOC at 0x%lx to 0x%lx\n", doc->physadr, physadr); + printk(KERN_DEBUG "Found alias of DOC at 0x%lx to 0x%lx\n", doc->physadr, physadr); goto notfound; } } @@ -1163,6 +1559,8 @@ static inline int __init doc_probe(unsigned long physadr) if (ChipID == DOC_ChipID_Doc2k) numchips = doc2000_init(mtd); + else if (ChipID == DOC_ChipID_DocMilPlus16) + numchips = doc2001plus_init(mtd); else numchips = doc2001_init(mtd); @@ -1228,10 +1626,10 @@ void __exit cleanup_nanddoc(void) kfree(mtd); } } - + module_init(init_nanddoc); module_exit(cleanup_nanddoc); MODULE_LICENSE("GPL"); MODULE_AUTHOR("David Woodhouse "); -MODULE_DESCRIPTION("M-Systems DiskOnChip 2000 and Millennium device driver\n"); +MODULE_DESCRIPTION("M-Systems DiskOnChip 2000, Millennium and Millennium Plus device driver\n");