VServer 1.9.2 (patch-2.6.8.1-vs1.9.2.diff)
[linux-2.6.git] / drivers / mtd / nand / diskonchip.c
index 677c216..0f7dedd 100644 (file)
@@ -2,12 +2,16 @@
  * drivers/mtd/nand/diskonchip.c
  *
  * (C) 2003 Red Hat, Inc.
+ * (C) 2004 Dan Brown <dan_brown@ieee.org>
+ * (C) 2004 Kalev Lember <kalev@smartlink.ee>
  *
  * Author: David Woodhouse <dwmw2@infradead.org>
+ * Additional Diskonchip 2000 and Millennium support by Dan Brown <dan_brown@ieee.org>
+ * Diskonchip Millennium Plus support by Kalev Lember <kalev@smartlink.ee>
  *
  * 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 <linux/kernel.h>
 #include <linux/mtd/inftl.h>
 
 /* 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 <dwmw2@infradead.org>");
-MODULE_DESCRIPTION("M-Systems DiskOnChip 2000 and Millennium device driver\n");
+MODULE_DESCRIPTION("M-Systems DiskOnChip 2000, Millennium and Millennium Plus device driver\n");