Linux-2.6.12-rc2

Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.

Let it rip!
diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig
new file mode 100644
index 0000000..c4a56a4
--- /dev/null
+++ b/drivers/mtd/devices/Kconfig
@@ -0,0 +1,259 @@
+# drivers/mtd/maps/Kconfig
+# $Id: Kconfig,v 1.15 2004/12/22 17:51:15 joern Exp $
+
+menu "Self-contained MTD device drivers"
+	depends on MTD!=n
+
+config MTD_PMC551
+	tristate "Ramix PMC551 PCI Mezzanine RAM card support"
+	depends on MTD && PCI
+	---help---
+	  This provides a MTD device driver for the Ramix PMC551 RAM PCI card
+	  from Ramix Inc. <http://www.ramix.com/products/memory/pmc551.html>.
+	  These devices come in memory configurations from 32M - 1G.  If you
+	  have one, you probably want to enable this.
+
+	  If this driver is compiled as a module you get the ability to select
+	  the size of the aperture window pointing into the devices memory.
+	  What this means is that if you have a 1G card, normally the kernel
+	  will use a 1G memory map as its view of the device.  As a module,
+	  you can select a 1M window into the memory and the driver will
+	  "slide" the window around the PMC551's memory.  This was
+	  particularly useful on the 2.2 kernels on PPC architectures as there
+	  was limited kernel space to deal with.
+
+config MTD_PMC551_BUGFIX
+	bool "PMC551 256M DRAM Bugfix"
+	depends on MTD_PMC551
+	help
+	  Some of Ramix's PMC551 boards with 256M configurations have invalid
+	  column and row mux values.  This option will fix them, but will
+	  break other memory configurations.  If unsure say N.
+
+config MTD_PMC551_DEBUG
+	bool "PMC551 Debugging"
+	depends on MTD_PMC551
+	help
+	  This option makes the PMC551 more verbose during its operation and
+	  is only really useful if you are developing on this driver or
+	  suspect a possible hardware or driver bug.  If unsure say N.
+
+config MTD_MS02NV
+	tristate "DEC MS02-NV NVRAM module support"
+	depends on MTD && MACH_DECSTATION
+	help
+	  This is an MTD driver for the DEC's MS02-NV (54-20948-01) battery
+	  backed-up NVRAM module.  The module was originally meant as an NFS
+	  accelerator.  Say Y here if you have a DECstation 5000/2x0 or a
+	  DECsystem 5900 equipped with such a module.
+
+config MTD_SLRAM
+	tristate "Uncached system RAM"
+	depends on MTD
+	help
+	  If your CPU cannot cache all of the physical memory in your machine,
+	  you can still use it for storage or swap by using this driver to
+	  present it to the system as a Memory Technology Device.
+
+config MTD_PHRAM
+	tristate "Physical system RAM"
+	depends on MTD
+	help
+	  This is a re-implementation of the slram driver above.
+
+	  Use this driver to access physical memory that the kernel proper
+	  doesn't have access to, memory beyond the mem=xxx limit, nvram,
+	  memory on the video card, etc...
+
+config MTD_LART
+	tristate "28F160xx flash driver for LART"
+	depends on SA1100_LART && MTD
+	help
+	  This enables the flash driver for LART. Please note that you do
+	  not need any mapping/chip driver for LART. This one does it all
+	  for you, so go disable all of those if you enabled some of them (:
+
+config MTD_MTDRAM
+	tristate "Test driver using RAM"
+	depends on MTD
+	help
+	  This enables a test MTD device driver which uses vmalloc() to
+	  provide storage.  You probably want to say 'N' unless you're
+	  testing stuff.
+
+config MTDRAM_TOTAL_SIZE
+	int "MTDRAM device size in KiB"
+	depends on MTD_MTDRAM
+	default "4096"
+	help
+	  This allows you to configure the total size of the MTD device
+	  emulated by the MTDRAM driver.  If the MTDRAM driver is built
+	  as a module, it is also possible to specify this as a parameter when
+	  loading the module.
+
+config MTDRAM_ERASE_SIZE
+	int "MTDRAM erase block size in KiB"
+	depends on MTD_MTDRAM
+	default "128"
+	help
+	  This allows you to configure the size of the erase blocks in the
+	  device emulated by the MTDRAM driver.  If the MTDRAM driver is built
+	  as a module, it is also possible to specify this as a parameter when
+	  loading the module.
+
+#If not a module (I don't want to test it as a module)
+config MTDRAM_ABS_POS
+	hex "SRAM Hexadecimal Absolute position or 0"
+	depends on MTD_MTDRAM=y
+	default "0"
+	help
+	  If you have system RAM accessible by the CPU but not used by Linux
+	  in normal operation, you can give the physical address at which the
+	  available RAM starts, and the MTDRAM driver will use it instead of
+	  allocating space from Linux's available memory. Otherwise, leave 
+	  this set to zero. Most people will want to leave this as zero.
+
+config MTD_BLKMTD
+	tristate "MTD emulation using block device"
+	depends on MTD
+	help
+	  This driver allows a block device to appear as an MTD. It would
+	  generally be used in the following cases:
+
+	  Using Compact Flash as an MTD, these usually present themselves to
+	  the system as an ATA drive.
+	  Testing MTD users (eg JFFS2) on large media and media that might
+	  be removed during a write (using the floppy drive).
+
+config MTD_BLOCK2MTD
+	tristate "MTD using block device (rewrite)"
+	depends on MTD && EXPERIMENTAL
+	help
+	  This driver is basically the same at MTD_BLKMTD above, but
+	  experienced some interface changes plus serious speedups.  In
+	  the long term, it should replace MTD_BLKMTD.  Right now, you
+	  shouldn't entrust important data to it yet.
+
+comment "Disk-On-Chip Device Drivers"
+
+config MTD_DOC2000
+	tristate "M-Systems Disk-On-Chip 2000 and Millennium (DEPRECATED)"
+	depends on MTD
+	select MTD_DOCPROBE
+	select MTD_NAND_IDS
+	---help---
+	  This provides an MTD device driver for the M-Systems DiskOnChip
+	  2000 and Millennium devices.  Originally designed for the DiskOnChip
+	  2000, it also now includes support for the DiskOnChip Millennium.
+	  If you have problems with this driver and the DiskOnChip Millennium,
+	  you may wish to try the alternative Millennium driver below. To use
+	  the alternative driver, you will need to undefine DOC_SINGLE_DRIVER
+	  in the <file:drivers/mtd/devices/docprobe.c> source code.
+
+	  If you use this device, you probably also want to enable the NFTL
+	  'NAND Flash Translation Layer' option below, which is used to
+	  emulate a block device by using a kind of file system on the flash
+	  chips.
+
+	  NOTE: This driver is deprecated and will probably be removed soon.
+	  Please try the new DiskOnChip driver under "NAND Flash Device
+	  Drivers".
+
+config MTD_DOC2001
+	tristate "M-Systems Disk-On-Chip Millennium-only alternative driver (DEPRECATED)"
+	depends on MTD
+	select MTD_DOCPROBE
+	select MTD_NAND_IDS
+	---help---
+	  This provides an alternative MTD device driver for the M-Systems 
+	  DiskOnChip Millennium devices.  Use this if you have problems with
+	  the combined DiskOnChip 2000 and Millennium driver above.  To get
+	  the DiskOnChip probe code to load and use this driver instead of
+	  the other one, you will need to undefine DOC_SINGLE_DRIVER near
+	  the beginning of <file:drivers/mtd/devices/docprobe.c>.
+
+	  If you use this device, you probably also want to enable the NFTL
+	  'NAND Flash Translation Layer' option below, which is used to
+	  emulate a block device by using a kind of file system on the flash
+	  chips.
+
+	  NOTE: This driver is deprecated and will probably be removed soon.
+	  Please try the new DiskOnChip driver under "NAND Flash Device
+	  Drivers".
+
+config MTD_DOC2001PLUS
+	tristate "M-Systems Disk-On-Chip Millennium Plus"
+	depends on MTD
+	select MTD_DOCPROBE
+	select MTD_NAND_IDS
+	---help---
+	  This provides an MTD device driver for the M-Systems DiskOnChip
+	  Millennium Plus devices.
+
+	  If you use this device, you probably also want to enable the INFTL
+	  'Inverse NAND Flash Translation Layer' option below, which is used
+	  to emulate a block device by using a kind of file system on the 
+	  flash chips.
+
+	  NOTE: This driver will soon be replaced by the new DiskOnChip driver
+	  under "NAND Flash Device Drivers" (currently that driver does not
+	  support all Millennium Plus devices).
+
+config MTD_DOCPROBE
+	tristate
+	select MTD_DOCECC
+
+config MTD_DOCECC
+	tristate
+
+config MTD_DOCPROBE_ADVANCED
+	bool "Advanced detection options for DiskOnChip"
+	depends on MTD_DOCPROBE
+	help
+	  This option allows you to specify nonstandard address at which to
+	  probe for a DiskOnChip, or to change the detection options.  You
+	  are unlikely to need any of this unless you are using LinuxBIOS.
+	  Say 'N'.
+
+config MTD_DOCPROBE_ADDRESS
+	hex "Physical address of DiskOnChip" if MTD_DOCPROBE_ADVANCED
+	depends on MTD_DOCPROBE
+	default "0x0000" if MTD_DOCPROBE_ADVANCED
+	default "0" if !MTD_DOCPROBE_ADVANCED
+	---help---
+	  By default, the probe for DiskOnChip devices will look for a
+	  DiskOnChip at every multiple of 0x2000 between 0xC8000 and 0xEE000.
+	  This option allows you to specify a single address at which to probe
+	  for the device, which is useful if you have other devices in that
+	  range which get upset when they are probed.
+
+	  (Note that on PowerPC, the normal probe will only check at
+	  0xE4000000.)
+
+	  Normally, you should leave this set to zero, to allow the probe at
+	  the normal addresses.
+
+config MTD_DOCPROBE_HIGH
+	bool "Probe high addresses"
+	depends on MTD_DOCPROBE_ADVANCED
+	help
+	  By default, the probe for DiskOnChip devices will look for a
+	  DiskOnChip at every multiple of 0x2000 between 0xC8000 and 0xEE000.
+	  This option changes to make it probe between 0xFFFC8000 and
+	  0xFFFEE000.  Unless you are using LinuxBIOS, this is unlikely to be
+	  useful to you.  Say 'N'.
+
+config MTD_DOCPROBE_55AA
+	bool "Probe for 0x55 0xAA BIOS Extension Signature"
+	depends on MTD_DOCPROBE_ADVANCED
+	help
+	  Check for the 0x55 0xAA signature of a DiskOnChip, and do not
+	  continue with probing if it is absent.  The signature will always be
+	  present for a DiskOnChip 2000 or a normal DiskOnChip Millennium.
+	  Only if you have overwritten the first block of a DiskOnChip
+	  Millennium will it be absent.  Enable this option if you are using
+	  LinuxBIOS or if you need to recover a DiskOnChip Millennium on which
+	  you have managed to wipe the first block.
+
+endmenu
+
diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile
new file mode 100644
index 0000000..e38db34
--- /dev/null
+++ b/drivers/mtd/devices/Makefile
@@ -0,0 +1,25 @@
+#
+# linux/drivers/devices/Makefile
+#
+# $Id: Makefile.common,v 1.7 2004/12/22 17:51:15 joern Exp $
+
+#                       *** BIG UGLY NOTE ***
+#
+# The removal of get_module_symbol() and replacement with
+# inter_module_register() et al has introduced a link order dependency
+# here where previously there was none.  We now have to ensure that
+# doc200[01].o are linked before docprobe.o
+
+obj-$(CONFIG_MTD_DOC2000)	+= doc2000.o
+obj-$(CONFIG_MTD_DOC2001)	+= doc2001.o
+obj-$(CONFIG_MTD_DOC2001PLUS)	+= doc2001plus.o
+obj-$(CONFIG_MTD_DOCPROBE)	+= docprobe.o
+obj-$(CONFIG_MTD_DOCECC)	+= docecc.o
+obj-$(CONFIG_MTD_SLRAM)		+= slram.o
+obj-$(CONFIG_MTD_PHRAM)		+= phram.o
+obj-$(CONFIG_MTD_PMC551)	+= pmc551.o
+obj-$(CONFIG_MTD_MS02NV)	+= ms02-nv.o
+obj-$(CONFIG_MTD_MTDRAM)	+= mtdram.o
+obj-$(CONFIG_MTD_LART)		+= lart.o
+obj-$(CONFIG_MTD_BLKMTD)	+= blkmtd.o
+obj-$(CONFIG_MTD_BLOCK2MTD)	+= block2mtd.o
diff --git a/drivers/mtd/devices/blkmtd.c b/drivers/mtd/devices/blkmtd.c
new file mode 100644
index 0000000..662e807
--- /dev/null
+++ b/drivers/mtd/devices/blkmtd.c
@@ -0,0 +1,823 @@
+/*
+ * $Id: blkmtd.c,v 1.24 2004/11/16 18:29:01 dwmw2 Exp $
+ *
+ * blkmtd.c - use a block device as a fake MTD
+ *
+ * Author: Simon Evans <spse@secret.org.uk>
+ *
+ * Copyright (C) 2001,2002 Simon Evans
+ *
+ * Licence: GPL
+ *
+ * How it works:
+ *	The driver uses raw/io to read/write the device and the page
+ *	cache to cache access. Writes update the page cache with the
+ *	new data and mark it dirty and add the page into a BIO which
+ *	is then written out.
+ *
+ *	It can be loaded Read-Only to prevent erases and writes to the
+ *	medium.
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/blkdev.h>
+#include <linux/bio.h>
+#include <linux/pagemap.h>
+#include <linux/list.h>
+#include <linux/init.h>
+#include <linux/mtd/mtd.h>
+
+
+#define err(format, arg...) printk(KERN_ERR "blkmtd: " format "\n" , ## arg)
+#define info(format, arg...) printk(KERN_INFO "blkmtd: " format "\n" , ## arg)
+#define warn(format, arg...) printk(KERN_WARNING "blkmtd: " format "\n" , ## arg)
+#define crit(format, arg...) printk(KERN_CRIT "blkmtd: " format "\n" , ## arg)
+
+
+/* Default erase size in K, always make it a multiple of PAGE_SIZE */
+#define CONFIG_MTD_BLKDEV_ERASESIZE (128 << 10)	/* 128KiB */
+#define VERSION "$Revision: 1.24 $"
+
+/* Info for the block device */
+struct blkmtd_dev {
+	struct list_head list;
+	struct block_device *blkdev;
+	struct mtd_info mtd_info;
+	struct semaphore wrbuf_mutex;
+};
+
+
+/* Static info about the MTD, used in cleanup_module */
+static LIST_HEAD(blkmtd_device_list);
+
+
+static void blkmtd_sync(struct mtd_info *mtd);
+
+#define MAX_DEVICES 4
+
+/* Module parameters passed by insmod/modprobe */
+static char *device[MAX_DEVICES];    /* the block device to use */
+static int erasesz[MAX_DEVICES];     /* optional default erase size */
+static int ro[MAX_DEVICES];          /* optional read only flag */
+static int sync;
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Simon Evans <spse@secret.org.uk>");
+MODULE_DESCRIPTION("Emulate an MTD using a block device");
+module_param_array(device, charp, NULL, 0);
+MODULE_PARM_DESC(device, "block device to use");
+module_param_array(erasesz, int, NULL, 0);
+MODULE_PARM_DESC(erasesz, "optional erase size to use in KiB. eg 4=4KiB.");
+module_param_array(ro, bool, NULL, 0);
+MODULE_PARM_DESC(ro, "1=Read only, writes and erases cause errors");
+module_param(sync, bool, 0);
+MODULE_PARM_DESC(sync, "1=Synchronous writes");
+
+
+/* completion handler for BIO reads */
+static int bi_read_complete(struct bio *bio, unsigned int bytes_done, int error)
+{
+	if (bio->bi_size)
+		return 1;
+
+	complete((struct completion*)bio->bi_private);
+	return 0;
+}
+
+
+/* completion handler for BIO writes */
+static int bi_write_complete(struct bio *bio, unsigned int bytes_done, int error)
+{
+	const int uptodate = test_bit(BIO_UPTODATE, &bio->bi_flags);
+	struct bio_vec *bvec = bio->bi_io_vec + bio->bi_vcnt - 1;
+
+	if (bio->bi_size)
+		return 1;
+
+	if(!uptodate)
+		err("bi_write_complete: not uptodate\n");
+
+	do {
+		struct page *page = bvec->bv_page;
+		DEBUG(3, "Cleaning up page %ld\n", page->index);
+		if (--bvec >= bio->bi_io_vec)
+			prefetchw(&bvec->bv_page->flags);
+
+		if (uptodate) {
+			SetPageUptodate(page);
+		} else {
+			ClearPageUptodate(page);
+			SetPageError(page);
+		}
+		ClearPageDirty(page);
+		unlock_page(page);
+		page_cache_release(page);
+	} while (bvec >= bio->bi_io_vec);
+	
+	complete((struct completion*)bio->bi_private);
+	return 0;
+}
+
+
+/* read one page from the block device */
+static int blkmtd_readpage(struct blkmtd_dev *dev, struct page *page)
+{
+	struct bio *bio;
+	struct completion event;
+	int err = -ENOMEM;
+
+	if(PageUptodate(page)) {
+		DEBUG(2, "blkmtd: readpage page %ld is already upto date\n", page->index);
+		unlock_page(page);
+		return 0;
+	}
+	
+	ClearPageUptodate(page);
+	ClearPageError(page);
+
+	bio = bio_alloc(GFP_KERNEL, 1);
+	if(bio) {
+		init_completion(&event);
+		bio->bi_bdev = dev->blkdev;
+		bio->bi_sector = page->index << (PAGE_SHIFT-9);
+		bio->bi_private = &event;
+		bio->bi_end_io = bi_read_complete;
+		if(bio_add_page(bio, page, PAGE_SIZE, 0) == PAGE_SIZE) {
+			submit_bio(READ_SYNC, bio);
+			wait_for_completion(&event);
+			err = test_bit(BIO_UPTODATE, &bio->bi_flags) ? 0 : -EIO;
+			bio_put(bio);
+		}
+	}
+
+	if(err)
+		SetPageError(page);
+	else
+		SetPageUptodate(page);
+	flush_dcache_page(page);
+	unlock_page(page);
+	return err;
+}
+
+
+/* write out the current BIO and wait for it to finish */
+static int blkmtd_write_out(struct bio *bio)
+{
+	struct completion event;
+	int err;
+
+	if(!bio->bi_vcnt) {
+		bio_put(bio);
+		return 0;
+	}
+
+	init_completion(&event);
+	bio->bi_private = &event;
+	bio->bi_end_io = bi_write_complete;
+	submit_bio(WRITE_SYNC, bio);
+	wait_for_completion(&event);
+	DEBUG(3, "submit_bio completed, bi_vcnt = %d\n", bio->bi_vcnt);
+	err = test_bit(BIO_UPTODATE, &bio->bi_flags) ? 0 : -EIO;
+	bio_put(bio);
+	return err;
+}
+
+
+/**
+ * blkmtd_add_page - add a page to the current BIO
+ * @bio: bio to add to (NULL to alloc initial bio)
+ * @blkdev: block device
+ * @page: page to add
+ * @pagecnt: pages left to add
+ *
+ * Adds a page to the current bio, allocating it if necessary. If it cannot be
+ * added, the current bio is written out and a new one is allocated. Returns
+ * the new bio to add or NULL on error
+ */
+static struct bio *blkmtd_add_page(struct bio *bio, struct block_device *blkdev,
+				   struct page *page, int pagecnt)
+{
+
+ retry:
+	if(!bio) {
+		bio = bio_alloc(GFP_KERNEL, pagecnt);
+		if(!bio)
+			return NULL;
+		bio->bi_sector = page->index << (PAGE_SHIFT-9);
+		bio->bi_bdev = blkdev;
+	}
+
+	if(bio_add_page(bio, page, PAGE_SIZE, 0) != PAGE_SIZE) {
+		blkmtd_write_out(bio);
+		bio = NULL;
+		goto retry;
+	}
+	return bio;
+}
+
+
+/**
+ * write_pages - write block of data to device via the page cache
+ * @dev: device to write to
+ * @buf: data source or NULL if erase (output is set to 0xff)
+ * @to: offset into output device
+ * @len: amount to data to write
+ * @retlen: amount of data written
+ *
+ * Grab pages from the page cache and fill them with the source data.
+ * Non page aligned start and end result in a readin of the page and
+ * part of the page being modified. Pages are added to the bio and then written
+ * out.
+ */
+static int write_pages(struct blkmtd_dev *dev, const u_char *buf, loff_t to,
+		    size_t len, size_t *retlen)
+{
+	int pagenr, offset;
+	size_t start_len = 0, end_len;
+	int pagecnt = 0;
+	int err = 0;
+	struct bio *bio = NULL;
+	size_t thislen = 0;
+
+	pagenr = to >> PAGE_SHIFT;
+	offset = to & ~PAGE_MASK;
+
+	DEBUG(2, "blkmtd: write_pages: buf = %p to = %ld len = %zd pagenr = %d offset = %d\n",
+	      buf, (long)to, len, pagenr, offset);
+
+	/* see if we have to do a partial write at the start */
+	if(offset) {
+		start_len = ((offset + len) > PAGE_SIZE) ? PAGE_SIZE - offset : len;
+		len -= start_len;
+	}
+
+	/* calculate the length of the other two regions */
+	end_len = len & ~PAGE_MASK;
+	len -= end_len;
+
+	if(start_len)
+		pagecnt++;
+
+	if(len)
+		pagecnt += len >> PAGE_SHIFT;
+
+	if(end_len)
+		pagecnt++;
+
+	down(&dev->wrbuf_mutex);
+
+	DEBUG(3, "blkmtd: write: start_len = %zd len = %zd end_len = %zd pagecnt = %d\n",
+	      start_len, len, end_len, pagecnt);
+
+	if(start_len) {
+		/* do partial start region */
+		struct page *page;
+
+		DEBUG(3, "blkmtd: write: doing partial start, page = %d len = %zd offset = %d\n",
+		      pagenr, start_len, offset);
+
+		BUG_ON(!buf);
+		page = read_cache_page(dev->blkdev->bd_inode->i_mapping, pagenr, (filler_t *)blkmtd_readpage, dev);
+		lock_page(page);
+		if(PageDirty(page)) {
+			err("to = %lld start_len = %zd len = %zd end_len = %zd pagenr = %d\n",
+			    to, start_len, len, end_len, pagenr);
+			BUG();
+		}
+		memcpy(page_address(page)+offset, buf, start_len);
+		SetPageDirty(page);
+		SetPageUptodate(page);
+		buf += start_len;
+		thislen = start_len;
+		bio = blkmtd_add_page(bio, dev->blkdev, page, pagecnt);
+		if(!bio) {
+			err = -ENOMEM;
+			err("bio_add_page failed\n");
+			goto write_err;
+		}
+		pagecnt--;
+		pagenr++;
+	}
+
+	/* Now do the main loop to a page aligned, n page sized output */
+	if(len) {
+		int pagesc = len >> PAGE_SHIFT;
+		DEBUG(3, "blkmtd: write: whole pages start = %d, count = %d\n",
+		      pagenr, pagesc);
+		while(pagesc) {
+			struct page *page;
+
+			/* see if page is in the page cache */
+			DEBUG(3, "blkmtd: write: grabbing page %d from page cache\n", pagenr);
+			page = grab_cache_page(dev->blkdev->bd_inode->i_mapping, pagenr);
+			if(PageDirty(page)) {
+				BUG();
+			}
+			if(!page) {
+				warn("write: cannot grab cache page %d", pagenr);
+				err = -ENOMEM;
+				goto write_err;
+			}
+			if(!buf) {
+				memset(page_address(page), 0xff, PAGE_SIZE);
+			} else {
+				memcpy(page_address(page), buf, PAGE_SIZE);
+				buf += PAGE_SIZE;
+			}
+			bio = blkmtd_add_page(bio, dev->blkdev, page, pagecnt);
+			if(!bio) {
+				err = -ENOMEM;
+				err("bio_add_page failed\n");
+				goto write_err;
+			}
+			pagenr++;
+			pagecnt--;
+			SetPageDirty(page);
+			SetPageUptodate(page);
+			pagesc--;
+			thislen += PAGE_SIZE;
+		}
+	}
+
+	if(end_len) {
+		/* do the third region */
+		struct page *page;
+		DEBUG(3, "blkmtd: write: doing partial end, page = %d len = %zd\n",
+		      pagenr, end_len);
+		BUG_ON(!buf);
+		page = read_cache_page(dev->blkdev->bd_inode->i_mapping, pagenr, (filler_t *)blkmtd_readpage, dev);
+		lock_page(page);
+		if(PageDirty(page)) {
+			err("to = %lld start_len = %zd len = %zd end_len = %zd pagenr = %d\n",
+			    to, start_len, len, end_len, pagenr);
+			BUG();
+		}
+		memcpy(page_address(page), buf, end_len);
+		SetPageDirty(page);
+		SetPageUptodate(page);
+		DEBUG(3, "blkmtd: write: writing out partial end\n");
+		thislen += end_len;
+		bio = blkmtd_add_page(bio, dev->blkdev, page, pagecnt);
+		if(!bio) {
+			err = -ENOMEM;
+			err("bio_add_page failed\n");
+			goto write_err;
+		}
+		pagenr++;
+	}
+
+	DEBUG(3, "blkmtd: write: got %d vectors to write\n", bio->bi_vcnt);
+ write_err:
+	if(bio)
+		blkmtd_write_out(bio);
+
+	DEBUG(2, "blkmtd: write: end, retlen = %zd, err = %d\n", *retlen, err);
+	up(&dev->wrbuf_mutex);
+
+	if(retlen)
+		*retlen = thislen;
+	return err;
+}
+
+
+/* erase a specified part of the device */
+static int blkmtd_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+	struct blkmtd_dev *dev = mtd->priv;
+	struct mtd_erase_region_info *einfo = mtd->eraseregions;
+	int numregions = mtd->numeraseregions;
+	size_t from;
+	u_long len;
+	int err = -EIO;
+	size_t retlen;
+
+	instr->state = MTD_ERASING;
+	from = instr->addr;
+	len = instr->len;
+
+	/* check erase region has valid start and length */
+	DEBUG(2, "blkmtd: erase: dev = `%s' from = 0x%zx len = 0x%lx\n",
+	      mtd->name+9, from, len);
+	while(numregions) {
+		DEBUG(3, "blkmtd: checking erase region = 0x%08X size = 0x%X num = 0x%x\n",
+		      einfo->offset, einfo->erasesize, einfo->numblocks);
+		if(from >= einfo->offset
+		   && from < einfo->offset + (einfo->erasesize * einfo->numblocks)) {
+			if(len == einfo->erasesize
+			   && ( (from - einfo->offset) % einfo->erasesize == 0))
+				break;
+		}
+		numregions--;
+		einfo++;
+	}
+
+	if(!numregions) {
+		/* Not a valid erase block */
+		err("erase: invalid erase request 0x%lX @ 0x%08zX", len, from);
+		instr->state = MTD_ERASE_FAILED;
+		err = -EIO;
+	}
+
+	if(instr->state != MTD_ERASE_FAILED) {
+		/* do the erase */
+		DEBUG(3, "Doing erase from = %zd len = %ld\n", from, len);
+		err = write_pages(dev, NULL, from, len, &retlen);
+		if(err || retlen != len) {
+			err("erase failed err = %d", err);
+			instr->state = MTD_ERASE_FAILED;
+		} else {
+			instr->state = MTD_ERASE_DONE;
+		}
+	}
+
+	DEBUG(3, "blkmtd: erase: checking callback\n");
+	mtd_erase_callback(instr);
+	DEBUG(2, "blkmtd: erase: finished (err = %d)\n", err);
+	return err;
+}
+
+
+/* read a range of the data via the page cache */
+static int blkmtd_read(struct mtd_info *mtd, loff_t from, size_t len,
+		       size_t *retlen, u_char *buf)
+{
+	struct blkmtd_dev *dev = mtd->priv;
+	int err = 0;
+	int offset;
+	int pagenr, pages;
+	size_t thislen = 0;
+
+	DEBUG(2, "blkmtd: read: dev = `%s' from = %lld len = %zd buf = %p\n",
+	      mtd->name+9, from, len, buf);
+
+	if(from > mtd->size)
+		return -EINVAL;
+	if(from + len > mtd->size)
+		len = mtd->size - from;
+
+	pagenr = from >> PAGE_SHIFT;
+	offset = from - (pagenr << PAGE_SHIFT);
+
+	pages = (offset+len+PAGE_SIZE-1) >> PAGE_SHIFT;
+	DEBUG(3, "blkmtd: read: pagenr = %d offset = %d, pages = %d\n",
+	      pagenr, offset, pages);
+
+	while(pages) {
+		struct page *page;
+		int cpylen;
+
+		DEBUG(3, "blkmtd: read: looking for page: %d\n", pagenr);
+		page = read_cache_page(dev->blkdev->bd_inode->i_mapping, pagenr, (filler_t *)blkmtd_readpage, dev);
+		if(IS_ERR(page)) {
+			err = -EIO;
+			goto readerr;
+		}
+
+		cpylen = (PAGE_SIZE > len) ? len : PAGE_SIZE;
+		if(offset+cpylen > PAGE_SIZE)
+			cpylen = PAGE_SIZE-offset;
+
+		memcpy(buf + thislen, page_address(page) + offset, cpylen);
+		offset = 0;
+		len -= cpylen;
+		thislen += cpylen;
+		pagenr++;
+		pages--;
+		if(!PageDirty(page))
+			page_cache_release(page);
+	}
+
+ readerr:
+	if(retlen)
+		*retlen = thislen;
+	DEBUG(2, "blkmtd: end read: retlen = %zd, err = %d\n", thislen, err);
+	return err;
+}
+
+
+/* write data to the underlying device */
+static int blkmtd_write(struct mtd_info *mtd, loff_t to, size_t len,
+			size_t *retlen, const u_char *buf)
+{
+	struct blkmtd_dev *dev = mtd->priv;
+	int err;
+
+	if(!len)
+		return 0;
+
+	DEBUG(2, "blkmtd: write: dev = `%s' to = %lld len = %zd buf = %p\n",
+	      mtd->name+9, to, len, buf);
+
+	if(to >= mtd->size) {
+		return -ENOSPC;
+	}
+
+	if(to + len > mtd->size) {
+		len = mtd->size - to;
+	}
+
+	err = write_pages(dev, buf, to, len, retlen);
+	if(err > 0)
+		err = 0;
+	DEBUG(2, "blkmtd: write: end, err = %d\n", err);
+	return err;
+}
+
+
+/* sync the device - wait until the write queue is empty */
+static void blkmtd_sync(struct mtd_info *mtd)
+{
+	/* Currently all writes are synchronous */
+}
+
+
+static void free_device(struct blkmtd_dev *dev)
+{
+	DEBUG(2, "blkmtd: free_device() dev = %p\n", dev);
+	if(dev) {
+		if(dev->mtd_info.eraseregions)
+			kfree(dev->mtd_info.eraseregions);
+		if(dev->mtd_info.name)
+			kfree(dev->mtd_info.name);
+
+		if(dev->blkdev) {
+			invalidate_inode_pages(dev->blkdev->bd_inode->i_mapping);
+			close_bdev_excl(dev->blkdev);
+		}
+		kfree(dev);
+	}
+}
+
+
+/* For a given size and initial erase size, calculate the number
+ * and size of each erase region. Goes round the loop twice,
+ * once to find out how many regions, then allocates space,
+ * then round the loop again to fill it in.
+ */
+static struct mtd_erase_region_info *calc_erase_regions(
+	size_t erase_size, size_t total_size, int *regions)
+{
+	struct mtd_erase_region_info *info = NULL;
+
+	DEBUG(2, "calc_erase_regions, es = %zd size = %zd regions = %d\n",
+	      erase_size, total_size, *regions);
+	/* Make any user specified erasesize be a power of 2
+	   and at least PAGE_SIZE */
+	if(erase_size) {
+		int es = erase_size;
+		erase_size = 1;
+		while(es != 1) {
+			es >>= 1;
+			erase_size <<= 1;
+		}
+		if(erase_size < PAGE_SIZE)
+			erase_size = PAGE_SIZE;
+	} else {
+		erase_size = CONFIG_MTD_BLKDEV_ERASESIZE;
+	}
+
+	*regions = 0;
+
+	do {
+		int tot_size = total_size;
+		int er_size = erase_size;
+		int count = 0, offset = 0, regcnt = 0;
+
+		while(tot_size) {
+			count = tot_size / er_size;
+			if(count) {
+				tot_size = tot_size % er_size;
+				if(info) {
+					DEBUG(2, "adding to erase info off=%d er=%d cnt=%d\n",
+					      offset, er_size, count);
+					(info+regcnt)->offset = offset;
+					(info+regcnt)->erasesize = er_size;
+					(info+regcnt)->numblocks = count;
+					(*regions)++;
+				}
+				regcnt++;
+				offset += (count * er_size);
+			}
+			while(er_size > tot_size)
+				er_size >>= 1;
+		}
+		if(info == NULL) {
+			info = kmalloc(regcnt * sizeof(struct mtd_erase_region_info), GFP_KERNEL);
+			if(!info)
+				break;
+		}
+	} while(!(*regions));
+	DEBUG(2, "calc_erase_regions done, es = %zd size = %zd regions = %d\n",
+	      erase_size, total_size, *regions);
+	return info;
+}
+
+
+extern dev_t __init name_to_dev_t(const char *line);
+
+static struct blkmtd_dev *add_device(char *devname, int readonly, int erase_size)
+{
+	struct block_device *bdev;
+	int mode;
+	struct blkmtd_dev *dev;
+
+	if(!devname)
+		return NULL;
+
+	/* Get a handle on the device */
+
+
+#ifdef MODULE
+	mode = (readonly) ? O_RDONLY : O_RDWR;
+	bdev = open_bdev_excl(devname, mode, NULL);
+#else
+	mode = (readonly) ? FMODE_READ : FMODE_WRITE;
+	bdev = open_by_devnum(name_to_dev_t(devname), mode);
+#endif
+	if(IS_ERR(bdev)) {
+		err("error: cannot open device %s", devname);
+		DEBUG(2, "blkmtd: opening bdev returned %ld\n", PTR_ERR(bdev));
+		return NULL;
+	}
+
+	DEBUG(1, "blkmtd: found a block device major = %d, minor = %d\n",
+	      MAJOR(bdev->bd_dev), MINOR(bdev->bd_dev));
+
+	if(MAJOR(bdev->bd_dev) == MTD_BLOCK_MAJOR) {
+		err("attempting to use an MTD device as a block device");
+		blkdev_put(bdev);
+		return NULL;
+	}
+
+	dev = kmalloc(sizeof(struct blkmtd_dev), GFP_KERNEL);
+	if(dev == NULL) {
+		blkdev_put(bdev);
+		return NULL;
+	}
+
+	memset(dev, 0, sizeof(struct blkmtd_dev));
+	dev->blkdev = bdev;
+	if(!readonly) {
+		init_MUTEX(&dev->wrbuf_mutex);
+	}
+
+	dev->mtd_info.size = dev->blkdev->bd_inode->i_size & PAGE_MASK;
+
+	/* Setup the MTD structure */
+	/* make the name contain the block device in */
+	dev->mtd_info.name = kmalloc(sizeof("blkmtd: ") + strlen(devname), GFP_KERNEL);
+	if(dev->mtd_info.name == NULL)
+		goto devinit_err;
+
+	sprintf(dev->mtd_info.name, "blkmtd: %s", devname);
+	dev->mtd_info.eraseregions = calc_erase_regions(erase_size, dev->mtd_info.size,
+							&dev->mtd_info.numeraseregions);
+	if(dev->mtd_info.eraseregions == NULL)
+		goto devinit_err;
+
+	dev->mtd_info.erasesize = dev->mtd_info.eraseregions->erasesize;
+	DEBUG(1, "blkmtd: init: found %d erase regions\n",
+	      dev->mtd_info.numeraseregions);
+
+	if(readonly) {
+		dev->mtd_info.type = MTD_ROM;
+		dev->mtd_info.flags = MTD_CAP_ROM;
+	} else {
+		dev->mtd_info.type = MTD_RAM;
+		dev->mtd_info.flags = MTD_CAP_RAM;
+		dev->mtd_info.erase = blkmtd_erase;
+		dev->mtd_info.write = blkmtd_write;
+		dev->mtd_info.writev = default_mtd_writev;
+		dev->mtd_info.sync = blkmtd_sync;
+	}
+	dev->mtd_info.read = blkmtd_read;
+	dev->mtd_info.readv = default_mtd_readv;
+	dev->mtd_info.priv = dev;
+	dev->mtd_info.owner = THIS_MODULE;
+
+	list_add(&dev->list, &blkmtd_device_list);
+	if (add_mtd_device(&dev->mtd_info)) {
+		/* Device didnt get added, so free the entry */
+		list_del(&dev->list);
+		goto devinit_err;
+	} else {
+		info("mtd%d: [%s] erase_size = %dKiB %s",
+		     dev->mtd_info.index, dev->mtd_info.name + strlen("blkmtd: "),
+		     dev->mtd_info.erasesize >> 10,
+		     readonly ? "(read-only)" : "");
+	}
+	
+	return dev;
+
+ devinit_err:
+	free_device(dev);
+	return NULL;
+}
+
+
+/* Cleanup and exit - sync the device and kill of the kernel thread */
+static void __devexit cleanup_blkmtd(void)
+{
+	struct list_head *temp1, *temp2;
+
+	/* Remove the MTD devices */
+	list_for_each_safe(temp1, temp2, &blkmtd_device_list) {
+		struct blkmtd_dev *dev = list_entry(temp1, struct blkmtd_dev,
+						    list);
+		blkmtd_sync(&dev->mtd_info);
+		del_mtd_device(&dev->mtd_info);
+		info("mtd%d: [%s] removed", dev->mtd_info.index,
+		     dev->mtd_info.name + strlen("blkmtd: "));
+		list_del(&dev->list);
+		free_device(dev);
+	}
+}
+
+#ifndef MODULE
+
+/* Handle kernel boot params */
+
+
+static int __init param_blkmtd_device(char *str)
+{
+	int i;
+
+	for(i = 0; i < MAX_DEVICES; i++) {
+		device[i] = str;
+		DEBUG(2, "blkmtd: device setup: %d = %s\n", i, device[i]);
+		strsep(&str, ",");
+	}
+	return 1;
+}
+
+
+static int __init param_blkmtd_erasesz(char *str)
+{
+	int i;
+	for(i = 0; i < MAX_DEVICES; i++) {
+		char *val = strsep(&str, ",");
+		if(val)
+			erasesz[i] = simple_strtoul(val, NULL, 0);
+		DEBUG(2, "blkmtd: erasesz setup: %d = %d\n", i, erasesz[i]);
+	}
+
+	return 1;
+}
+
+
+static int __init param_blkmtd_ro(char *str)
+{
+	int i;
+	for(i = 0; i < MAX_DEVICES; i++) {
+		char *val = strsep(&str, ",");
+		if(val)
+			ro[i] = simple_strtoul(val, NULL, 0);
+		DEBUG(2, "blkmtd: ro setup: %d = %d\n", i, ro[i]);
+	}
+
+	return 1;
+}
+
+
+static int __init param_blkmtd_sync(char *str)
+{
+	if(str[0] == '1')
+		sync = 1;
+	return 1;
+}
+
+__setup("blkmtd_device=", param_blkmtd_device);
+__setup("blkmtd_erasesz=", param_blkmtd_erasesz);
+__setup("blkmtd_ro=", param_blkmtd_ro);
+__setup("blkmtd_sync=", param_blkmtd_sync);
+
+#endif
+
+
+/* Startup */
+static int __init init_blkmtd(void)
+{
+	int i;
+
+	info("version " VERSION);
+	/* Check args - device[0] is the bare minimum*/
+	if(!device[0]) {
+		err("error: missing `device' name\n");
+		return -EINVAL;
+	}
+
+	for(i = 0; i < MAX_DEVICES; i++)
+		add_device(device[i], ro[i], erasesz[i] << 10);
+
+	if(list_empty(&blkmtd_device_list))
+		return -EINVAL;
+
+	return 0;
+}
+
+module_init(init_blkmtd);
+module_exit(cleanup_blkmtd);
diff --git a/drivers/mtd/devices/block2mtd.c b/drivers/mtd/devices/block2mtd.c
new file mode 100644
index 0000000..cfe6ccf
--- /dev/null
+++ b/drivers/mtd/devices/block2mtd.c
@@ -0,0 +1,495 @@
+/*
+ * $Id: block2mtd.c,v 1.23 2005/01/05 17:05:46 dwmw2 Exp $
+ *
+ * block2mtd.c - create an mtd from a block device
+ *
+ * Copyright (C) 2001,2002	Simon Evans <spse@secret.org.uk>
+ * Copyright (C) 2004		Gareth Bult <Gareth@Encryptec.net>
+ * Copyright (C) 2004,2005	Jörn Engel <joern@wh.fh-wedel.de>
+ *
+ * Licence: GPL
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/blkdev.h>
+#include <linux/bio.h>
+#include <linux/pagemap.h>
+#include <linux/list.h>
+#include <linux/init.h>
+#include <linux/mtd/mtd.h>
+#include <linux/buffer_head.h>
+
+#define VERSION "$Revision: 1.23 $"
+
+
+#define ERROR(fmt, args...) printk(KERN_ERR "block2mtd: " fmt "\n" , ## args)
+#define INFO(fmt, args...) printk(KERN_INFO "block2mtd: " fmt "\n" , ## args)
+
+
+/* Info for the block device */
+struct block2mtd_dev {
+	struct list_head list;
+	struct block_device *blkdev;
+	struct mtd_info mtd;
+	struct semaphore write_mutex;
+};
+
+
+/* Static info about the MTD, used in cleanup_module */
+static LIST_HEAD(blkmtd_device_list);
+
+
+#define PAGE_READAHEAD 64
+void cache_readahead(struct address_space *mapping, int index)
+{
+	filler_t *filler = (filler_t*)mapping->a_ops->readpage;
+	int i, pagei;
+	unsigned ret = 0;
+	unsigned long end_index;
+	struct page *page;
+	LIST_HEAD(page_pool);
+	struct inode *inode = mapping->host;
+	loff_t isize = i_size_read(inode);
+
+	if (!isize) {
+		INFO("iSize=0 in cache_readahead\n");
+		return;
+	}
+
+	end_index = ((isize - 1) >> PAGE_CACHE_SHIFT);
+
+	read_lock_irq(&mapping->tree_lock);
+	for (i = 0; i < PAGE_READAHEAD; i++) {
+		pagei = index + i;
+		if (pagei > end_index) {
+			INFO("Overrun end of disk in cache readahead\n");
+			break;
+		}
+		page = radix_tree_lookup(&mapping->page_tree, pagei);
+		if (page && (!i))
+			break;
+		if (page)
+			continue;
+		read_unlock_irq(&mapping->tree_lock);
+		page = page_cache_alloc_cold(mapping);
+		read_lock_irq(&mapping->tree_lock);
+		if (!page)
+			break;
+		page->index = pagei;
+		list_add(&page->lru, &page_pool);
+		ret++;
+	}
+	read_unlock_irq(&mapping->tree_lock);
+	if (ret)
+		read_cache_pages(mapping, &page_pool, filler, NULL);
+}
+
+
+static struct page* page_readahead(struct address_space *mapping, int index)
+{
+	filler_t *filler = (filler_t*)mapping->a_ops->readpage;
+	//do_page_cache_readahead(mapping, index, XXX, 64);
+	cache_readahead(mapping, index);
+	return read_cache_page(mapping, index, filler, NULL);
+}
+
+
+/* erase a specified part of the device */
+static int _block2mtd_erase(struct block2mtd_dev *dev, loff_t to, size_t len)
+{
+	struct address_space *mapping = dev->blkdev->bd_inode->i_mapping;
+	struct page *page;
+	int index = to >> PAGE_SHIFT;	// page index
+	int pages = len >> PAGE_SHIFT;
+	u_long *p;
+	u_long *max;
+
+	while (pages) {
+		page = page_readahead(mapping, index);
+		if (!page)
+			return -ENOMEM;
+		if (IS_ERR(page))
+			return PTR_ERR(page);
+
+		max = (u_long*)page_address(page) + PAGE_SIZE;
+		for (p=(u_long*)page_address(page); p<max; p++) 
+			if (*p != -1UL) {
+				lock_page(page);
+				memset(page_address(page), 0xff, PAGE_SIZE);
+				set_page_dirty(page);
+				unlock_page(page);
+				break;
+			}
+
+		page_cache_release(page);
+		pages--;
+		index++;
+	}
+	return 0;
+}
+static int block2mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+	struct block2mtd_dev *dev = mtd->priv;
+	size_t from = instr->addr;
+	size_t len = instr->len;
+	int err;
+
+	instr->state = MTD_ERASING;
+	down(&dev->write_mutex);
+	err = _block2mtd_erase(dev, from, len);
+	up(&dev->write_mutex);
+	if (err) {
+		ERROR("erase failed err = %d", err);
+		instr->state = MTD_ERASE_FAILED;
+	} else
+		instr->state = MTD_ERASE_DONE;
+
+	instr->state = MTD_ERASE_DONE;
+	mtd_erase_callback(instr);
+	return err;
+}
+
+
+static int block2mtd_read(struct mtd_info *mtd, loff_t from, size_t len,
+		size_t *retlen, u_char *buf)
+{
+	struct block2mtd_dev *dev = mtd->priv;
+	struct page *page;
+	int index = from >> PAGE_SHIFT;
+	int offset = from & (PAGE_SHIFT-1);
+	int cpylen;
+
+	if (from > mtd->size)
+		return -EINVAL;
+	if (from + len > mtd->size)
+		len = mtd->size - from;
+
+	if (retlen)
+		*retlen = 0;
+
+	while (len) {
+		if ((offset + len) > PAGE_SIZE)
+			cpylen = PAGE_SIZE - offset;	// multiple pages
+		else
+			cpylen = len;	// this page
+		len = len - cpylen;
+
+		//      Get page
+		page = page_readahead(dev->blkdev->bd_inode->i_mapping, index);
+		if (!page)
+			return -ENOMEM;
+		if (IS_ERR(page))
+			return PTR_ERR(page);
+
+		memcpy(buf, page_address(page) + offset, cpylen);
+		page_cache_release(page);
+
+		if (retlen)
+			*retlen += cpylen;
+		buf += cpylen;
+		offset = 0;
+		index++;
+	}
+	return 0;
+}
+
+
+/* write data to the underlying device */
+static int _block2mtd_write(struct block2mtd_dev *dev, const u_char *buf,
+		loff_t to, size_t len, size_t *retlen)
+{
+	struct page *page;
+	struct address_space *mapping = dev->blkdev->bd_inode->i_mapping;
+	int index = to >> PAGE_SHIFT;	// page index
+	int offset = to & ~PAGE_MASK;	// page offset
+	int cpylen;
+
+	if (retlen)
+		*retlen = 0;
+	while (len) {
+		if ((offset+len) > PAGE_SIZE) 
+			cpylen = PAGE_SIZE - offset;	// multiple pages
+		else
+			cpylen = len;			// this page
+		len = len - cpylen;
+
+		//	Get page
+		page = page_readahead(mapping, index);
+		if (!page)
+			return -ENOMEM;
+		if (IS_ERR(page))
+			return PTR_ERR(page);
+
+		if (memcmp(page_address(page)+offset, buf, cpylen)) {
+			lock_page(page);
+			memcpy(page_address(page) + offset, buf, cpylen);
+			set_page_dirty(page);
+			unlock_page(page);
+		}
+		page_cache_release(page);
+
+		if (retlen)
+			*retlen += cpylen;
+
+		buf += cpylen;
+		offset = 0;
+		index++;
+	}
+	return 0;
+}
+static int block2mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
+		size_t *retlen, const u_char *buf)
+{
+	struct block2mtd_dev *dev = mtd->priv;
+	int err;
+
+	if (!len)
+		return 0;
+	if (to >= mtd->size)
+		return -ENOSPC;
+	if (to + len > mtd->size)
+		len = mtd->size - to;
+
+	down(&dev->write_mutex);
+	err = _block2mtd_write(dev, buf, to, len, retlen);
+	up(&dev->write_mutex);
+	if (err > 0)
+		err = 0;
+	return err;
+}
+
+
+/* sync the device - wait until the write queue is empty */
+static void block2mtd_sync(struct mtd_info *mtd)
+{
+	struct block2mtd_dev *dev = mtd->priv;
+	sync_blockdev(dev->blkdev);
+	return;
+}
+
+
+static void block2mtd_free_device(struct block2mtd_dev *dev)
+{
+	if (!dev)
+		return;
+
+	kfree(dev->mtd.name);
+
+	if (dev->blkdev) {
+		invalidate_inode_pages(dev->blkdev->bd_inode->i_mapping);
+		close_bdev_excl(dev->blkdev);
+	}
+
+	kfree(dev);
+}
+
+
+/* FIXME: ensure that mtd->size % erase_size == 0 */
+static struct block2mtd_dev *add_device(char *devname, int erase_size)
+{
+	struct block_device *bdev;
+	struct block2mtd_dev *dev;
+
+	if (!devname)
+		return NULL;
+
+	dev = kmalloc(sizeof(struct block2mtd_dev), GFP_KERNEL);
+	if (!dev)
+		return NULL;
+	memset(dev, 0, sizeof(*dev));
+
+	/* Get a handle on the device */
+	bdev = open_bdev_excl(devname, O_RDWR, NULL);
+	if (IS_ERR(bdev)) {
+		ERROR("error: cannot open device %s", devname);
+		goto devinit_err;
+	}
+	dev->blkdev = bdev;
+
+	if (MAJOR(bdev->bd_dev) == MTD_BLOCK_MAJOR) {
+		ERROR("attempting to use an MTD device as a block device");
+		goto devinit_err;
+	}
+
+	init_MUTEX(&dev->write_mutex);
+
+	/* Setup the MTD structure */
+	/* make the name contain the block device in */
+	dev->mtd.name = kmalloc(sizeof("block2mtd: ") + strlen(devname),
+			GFP_KERNEL);
+	if (!dev->mtd.name)
+		goto devinit_err;
+
+	sprintf(dev->mtd.name, "block2mtd: %s", devname);
+
+	dev->mtd.size = dev->blkdev->bd_inode->i_size & PAGE_MASK;
+	dev->mtd.erasesize = erase_size;
+	dev->mtd.type = MTD_RAM;
+	dev->mtd.flags = MTD_CAP_RAM;
+	dev->mtd.erase = block2mtd_erase;
+	dev->mtd.write = block2mtd_write;
+	dev->mtd.writev = default_mtd_writev;
+	dev->mtd.sync = block2mtd_sync;
+	dev->mtd.read = block2mtd_read;
+	dev->mtd.readv = default_mtd_readv;
+	dev->mtd.priv = dev;
+	dev->mtd.owner = THIS_MODULE;
+
+	if (add_mtd_device(&dev->mtd)) {
+		/* Device didnt get added, so free the entry */
+		goto devinit_err;
+	}
+	list_add(&dev->list, &blkmtd_device_list);
+	INFO("mtd%d: [%s] erase_size = %dKiB [%d]", dev->mtd.index,
+			dev->mtd.name + strlen("blkmtd: "),
+			dev->mtd.erasesize >> 10, dev->mtd.erasesize);
+	return dev;
+
+devinit_err:
+	block2mtd_free_device(dev);
+	return NULL;
+}
+
+
+static int ustrtoul(const char *cp, char **endp, unsigned int base)
+{
+	unsigned long result = simple_strtoul(cp, endp, base);
+	switch (**endp) {
+	case 'G' :
+		result *= 1024;
+	case 'M':
+		result *= 1024;
+	case 'k':
+		result *= 1024;
+	/* By dwmw2 editorial decree, "ki", "Mi" or "Gi" are to be used. */
+		if ((*endp)[1] == 'i')
+			(*endp) += 2;
+	}
+	return result;
+}
+
+
+static int parse_num32(u32 *num32, const char *token)
+{
+	char *endp;
+	unsigned long n;
+
+	n = ustrtoul(token, &endp, 0);
+	if (*endp)
+		return -EINVAL;
+
+	*num32 = n;
+	return 0;
+}
+
+
+static int parse_name(char **pname, const char *token, size_t limit)
+{
+	size_t len;
+	char *name;
+
+	len = strlen(token) + 1;
+	if (len > limit)
+		return -ENOSPC;
+
+	name = kmalloc(len, GFP_KERNEL);
+	if (!name)
+		return -ENOMEM;
+
+	strcpy(name, token);
+
+	*pname = name;
+	return 0;
+}
+
+
+static inline void kill_final_newline(char *str)
+{
+	char *newline = strrchr(str, '\n');
+	if (newline && !newline[1])
+		*newline = 0;
+}
+
+
+#define parse_err(fmt, args...) do {		\
+	ERROR("block2mtd: " fmt "\n", ## args);	\
+	return 0;				\
+} while (0)
+
+static int block2mtd_setup(const char *val, struct kernel_param *kp)
+{
+	char buf[80+12], *str=buf; /* 80 for device, 12 for erase size */
+	char *token[2];
+	char *name;
+	u32 erase_size = PAGE_SIZE;
+	int i, ret;
+
+	if (strnlen(val, sizeof(buf)) >= sizeof(buf))
+		parse_err("parameter too long");
+
+	strcpy(str, val);
+	kill_final_newline(str);
+
+	for (i=0; i<2; i++)
+		token[i] = strsep(&str, ",");
+
+	if (str)
+		parse_err("too many arguments");
+
+	if (!token[0])
+		parse_err("no argument");
+
+	ret = parse_name(&name, token[0], 80);
+	if (ret == -ENOMEM)
+		parse_err("out of memory");
+	if (ret == -ENOSPC)
+		parse_err("name too long");
+	if (ret)
+		return 0;
+
+	if (token[1]) {
+		ret = parse_num32(&erase_size, token[1]);
+		if (ret)
+			parse_err("illegal erase size");
+	}
+
+	add_device(name, erase_size);
+
+	return 0;
+}
+
+
+module_param_call(block2mtd, block2mtd_setup, NULL, NULL, 0200);
+MODULE_PARM_DESC(block2mtd, "Device to use. \"block2mtd=<dev>[,<erasesize>]\"");
+
+static int __init block2mtd_init(void)
+{
+	INFO("version " VERSION);
+	return 0;
+}
+
+
+static void __devexit block2mtd_exit(void)
+{
+	struct list_head *pos, *next;
+
+	/* Remove the MTD devices */
+	list_for_each_safe(pos, next, &blkmtd_device_list) {
+		struct block2mtd_dev *dev = list_entry(pos, typeof(*dev), list);
+		block2mtd_sync(&dev->mtd);
+		del_mtd_device(&dev->mtd);
+		INFO("mtd%d: [%s] removed", dev->mtd.index,
+				dev->mtd.name + strlen("blkmtd: "));
+		list_del(&dev->list);
+		block2mtd_free_device(dev);
+	}
+}
+
+
+module_init(block2mtd_init);
+module_exit(block2mtd_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Simon Evans <spse@secret.org.uk> and others");
+MODULE_DESCRIPTION("Emulate an MTD using a block device");
diff --git a/drivers/mtd/devices/doc2000.c b/drivers/mtd/devices/doc2000.c
new file mode 100644
index 0000000..5fc5328
--- /dev/null
+++ b/drivers/mtd/devices/doc2000.c
@@ -0,0 +1,1309 @@
+
+/*
+ * Linux driver for Disk-On-Chip 2000 and Millennium
+ * (c) 1999 Machine Vision Holdings, Inc.
+ * (c) 1999, 2000 David Woodhouse <dwmw2@infradead.org>
+ *
+ * $Id: doc2000.c,v 1.66 2005/01/05 18:05:12 dwmw2 Exp $
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/miscdevice.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/bitops.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/doc2000.h>
+
+#define DOC_SUPPORT_2000
+#define DOC_SUPPORT_2000TSOP
+#define DOC_SUPPORT_MILLENNIUM
+
+#ifdef DOC_SUPPORT_2000
+#define DoC_is_2000(doc) (doc->ChipID == DOC_ChipID_Doc2k)
+#else
+#define DoC_is_2000(doc) (0)
+#endif
+
+#if defined(DOC_SUPPORT_2000TSOP) || defined(DOC_SUPPORT_MILLENNIUM)
+#define DoC_is_Millennium(doc) (doc->ChipID == DOC_ChipID_DocMil)
+#else
+#define DoC_is_Millennium(doc) (0)
+#endif
+
+/* #define ECC_DEBUG */
+
+/* I have no idea why some DoC chips can not use memcpy_from|to_io().
+ * This may be due to the different revisions of the ASIC controller built-in or
+ * simplily a QA/Bug issue. Who knows ?? If you have trouble, please uncomment
+ * this:
+ #undef USE_MEMCPY
+*/
+
+static int doc_read(struct mtd_info *mtd, loff_t from, size_t len,
+		    size_t *retlen, u_char *buf);
+static int doc_write(struct mtd_info *mtd, loff_t to, size_t len,
+		     size_t *retlen, const u_char *buf);
+static int doc_read_ecc(struct mtd_info *mtd, loff_t from, size_t len,
+			size_t *retlen, u_char *buf, u_char *eccbuf, struct nand_oobinfo *oobsel);
+static int doc_write_ecc(struct mtd_info *mtd, loff_t to, size_t len,
+			 size_t *retlen, const u_char *buf, u_char *eccbuf, struct nand_oobinfo *oobsel);
+static int doc_writev_ecc(struct mtd_info *mtd, const struct kvec *vecs, 
+			  unsigned long count, loff_t to, size_t *retlen,
+			  u_char *eccbuf, struct nand_oobinfo *oobsel);
+static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+			size_t *retlen, u_char *buf);
+static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+			 size_t *retlen, const u_char *buf);
+static int doc_write_oob_nolock(struct mtd_info *mtd, loff_t ofs, size_t len,
+			 size_t *retlen, const u_char *buf);
+static int doc_erase (struct mtd_info *mtd, struct erase_info *instr);
+
+static struct mtd_info *doc2klist = NULL;
+
+/* Perform the required delay cycles by reading from the appropriate register */
+static void DoC_Delay(struct DiskOnChip *doc, unsigned short cycles)
+{
+	volatile char dummy;
+	int i;
+	
+	for (i = 0; i < cycles; i++) {
+		if (DoC_is_Millennium(doc))
+			dummy = ReadDOC(doc->virtadr, NOP);
+		else
+			dummy = ReadDOC(doc->virtadr, DOCStatus);
+	}
+	
+}
+
+/* DOC_WaitReady: Wait for RDY line to be asserted by the flash chip */
+static int _DoC_WaitReady(struct DiskOnChip *doc)
+{
+	void __iomem *docptr = doc->virtadr;
+	unsigned long timeo = jiffies + (HZ * 10);
+
+	DEBUG(MTD_DEBUG_LEVEL3,
+	      "_DoC_WaitReady called for out-of-line wait\n");
+
+	/* Out-of-line routine to wait for chip response */
+	while (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B)) {
+		/* issue 2 read from NOP register after reading from CDSNControl register
+	   	see Software Requirement 11.4 item 2. */
+		DoC_Delay(doc, 2);
+
+		if (time_after(jiffies, timeo)) {
+			DEBUG(MTD_DEBUG_LEVEL2, "_DoC_WaitReady timed out.\n");
+			return -EIO;
+		}
+		udelay(1);
+		cond_resched();
+	}
+
+	return 0;
+}
+
+static inline int DoC_WaitReady(struct DiskOnChip *doc)
+{
+	void __iomem *docptr = doc->virtadr;
+
+	/* This is inline, to optimise the common case, where it's ready instantly */
+	int ret = 0;
+
+	/* 4 read form NOP register should be issued in prior to the read from CDSNControl
+	   see Software Requirement 11.4 item 2. */
+	DoC_Delay(doc, 4);
+
+	if (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B))
+		/* Call the out-of-line routine to wait */
+		ret = _DoC_WaitReady(doc);
+
+	/* issue 2 read from NOP register after reading from CDSNControl register
+	   see Software Requirement 11.4 item 2. */
+	DoC_Delay(doc, 2);
+
+	return ret;
+}
+
+/* DoC_Command: Send a flash command to the flash chip through the CDSN Slow IO register to
+   bypass the internal pipeline. Each of 4 delay cycles (read from the NOP register) is
+   required after writing to CDSN Control register, see Software Requirement 11.4 item 3. */
+
+static inline int DoC_Command(struct DiskOnChip *doc, unsigned char command,
+			      unsigned char xtraflags)
+{
+	void __iomem *docptr = doc->virtadr;
+
+	if (DoC_is_2000(doc))
+		xtraflags |= CDSN_CTRL_FLASH_IO;
+
+	/* Assert the CLE (Command Latch Enable) line to the flash chip */
+	WriteDOC(xtraflags | CDSN_CTRL_CLE | CDSN_CTRL_CE, docptr, CDSNControl);
+	DoC_Delay(doc, 4);	/* Software requirement 11.4.3 for Millennium */
+
+	if (DoC_is_Millennium(doc))
+		WriteDOC(command, docptr, CDSNSlowIO);
+
+	/* Send the command */
+	WriteDOC_(command, docptr, doc->ioreg);
+	if (DoC_is_Millennium(doc))
+		WriteDOC(command, docptr, WritePipeTerm);
+
+	/* Lower the CLE line */
+	WriteDOC(xtraflags | CDSN_CTRL_CE, docptr, CDSNControl);
+	DoC_Delay(doc, 4);	/* Software requirement 11.4.3 for Millennium */
+
+	/* Wait for the chip to respond - Software requirement 11.4.1 (extended for any command) */
+	return DoC_WaitReady(doc);
+}
+
+/* DoC_Address: Set the current address for the flash chip through the CDSN Slow IO register to
+   bypass the internal pipeline. Each of 4 delay cycles (read from the NOP register) is
+   required after writing to CDSN Control register, see Software Requirement 11.4 item 3. */
+
+static int DoC_Address(struct DiskOnChip *doc, int numbytes, unsigned long ofs,
+		       unsigned char xtraflags1, unsigned char xtraflags2)
+{
+	int i;
+	void __iomem *docptr = doc->virtadr;
+
+	if (DoC_is_2000(doc))
+		xtraflags1 |= CDSN_CTRL_FLASH_IO;
+
+	/* Assert the ALE (Address Latch Enable) line to the flash chip */
+	WriteDOC(xtraflags1 | CDSN_CTRL_ALE | CDSN_CTRL_CE, docptr, CDSNControl);
+
+	DoC_Delay(doc, 4);	/* Software requirement 11.4.3 for Millennium */
+
+	/* Send the address */
+	/* Devices with 256-byte page are addressed as:
+	   Column (bits 0-7), Page (bits 8-15, 16-23, 24-31)
+	   * there is no device on the market with page256
+	   and more than 24 bits.
+	   Devices with 512-byte page are addressed as:
+	   Column (bits 0-7), Page (bits 9-16, 17-24, 25-31)
+	   * 25-31 is sent only if the chip support it.
+	   * bit 8 changes the read command to be sent
+	   (NAND_CMD_READ0 or NAND_CMD_READ1).
+	 */
+
+	if (numbytes == ADDR_COLUMN || numbytes == ADDR_COLUMN_PAGE) {
+		if (DoC_is_Millennium(doc))
+			WriteDOC(ofs & 0xff, docptr, CDSNSlowIO);
+		WriteDOC_(ofs & 0xff, docptr, doc->ioreg);
+	}
+
+	if (doc->page256) {
+		ofs = ofs >> 8;
+	} else {
+		ofs = ofs >> 9;
+	}
+
+	if (numbytes == ADDR_PAGE || numbytes == ADDR_COLUMN_PAGE) {
+		for (i = 0; i < doc->pageadrlen; i++, ofs = ofs >> 8) {
+			if (DoC_is_Millennium(doc))
+				WriteDOC(ofs & 0xff, docptr, CDSNSlowIO);
+			WriteDOC_(ofs & 0xff, docptr, doc->ioreg);
+		}
+	}
+
+	if (DoC_is_Millennium(doc))
+		WriteDOC(ofs & 0xff, docptr, WritePipeTerm);
+
+	DoC_Delay(doc, 2);	/* Needed for some slow flash chips. mf. */
+	
+	/* FIXME: The SlowIO's for millennium could be replaced by 
+	   a single WritePipeTerm here. mf. */
+
+	/* Lower the ALE line */
+	WriteDOC(xtraflags1 | xtraflags2 | CDSN_CTRL_CE, docptr,
+		 CDSNControl);
+
+	DoC_Delay(doc, 4);	/* Software requirement 11.4.3 for Millennium */
+
+	/* Wait for the chip to respond - Software requirement 11.4.1 */
+	return DoC_WaitReady(doc);
+}
+
+/* Read a buffer from DoC, taking care of Millennium odditys */
+static void DoC_ReadBuf(struct DiskOnChip *doc, u_char * buf, int len)
+{
+	volatile int dummy;
+	int modulus = 0xffff;
+	void __iomem *docptr = doc->virtadr;
+	int i;
+
+	if (len <= 0)
+		return;
+
+	if (DoC_is_Millennium(doc)) {
+		/* Read the data via the internal pipeline through CDSN IO register,
+		   see Pipelined Read Operations 11.3 */
+		dummy = ReadDOC(docptr, ReadPipeInit);
+
+		/* Millennium should use the LastDataRead register - Pipeline Reads */
+		len--;
+
+		/* This is needed for correctly ECC calculation */
+		modulus = 0xff;
+	}
+
+	for (i = 0; i < len; i++)
+		buf[i] = ReadDOC_(docptr, doc->ioreg + (i & modulus));
+
+	if (DoC_is_Millennium(doc)) {
+		buf[i] = ReadDOC(docptr, LastDataRead);
+	}
+}
+
+/* Write a buffer to DoC, taking care of Millennium odditys */
+static void DoC_WriteBuf(struct DiskOnChip *doc, const u_char * buf, int len)
+{
+	void __iomem *docptr = doc->virtadr;
+	int i;
+
+	if (len <= 0)
+		return;
+
+	for (i = 0; i < len; i++)
+		WriteDOC_(buf[i], docptr, doc->ioreg + i);
+
+	if (DoC_is_Millennium(doc)) {
+		WriteDOC(0x00, docptr, WritePipeTerm);
+	}
+}
+
+
+/* DoC_SelectChip: Select a given flash chip within the current floor */
+
+static inline int DoC_SelectChip(struct DiskOnChip *doc, int chip)
+{
+	void __iomem *docptr = doc->virtadr;
+
+	/* Software requirement 11.4.4 before writing DeviceSelect */
+	/* Deassert the CE line to eliminate glitches on the FCE# outputs */
+	WriteDOC(CDSN_CTRL_WP, docptr, CDSNControl);
+	DoC_Delay(doc, 4);	/* Software requirement 11.4.3 for Millennium */
+
+	/* Select the individual flash chip requested */
+	WriteDOC(chip, docptr, CDSNDeviceSelect);
+	DoC_Delay(doc, 4);
+
+	/* Reassert the CE line */
+	WriteDOC(CDSN_CTRL_CE | CDSN_CTRL_FLASH_IO | CDSN_CTRL_WP, docptr,
+		 CDSNControl);
+	DoC_Delay(doc, 4);	/* Software requirement 11.4.3 for Millennium */
+
+	/* Wait for it to be ready */
+	return DoC_WaitReady(doc);
+}
+
+/* DoC_SelectFloor: Select a given floor (bank of flash chips) */
+
+static inline int DoC_SelectFloor(struct DiskOnChip *doc, int floor)
+{
+	void __iomem *docptr = doc->virtadr;
+
+	/* Select the floor (bank) of chips required */
+	WriteDOC(floor, docptr, FloorSelect);
+
+	/* Wait for the chip to be ready */
+	return DoC_WaitReady(doc);
+}
+
+/* DoC_IdentChip: Identify a given NAND chip given {floor,chip} */
+
+static int DoC_IdentChip(struct DiskOnChip *doc, int floor, int chip)
+{
+	int mfr, id, i, j;
+	volatile char dummy;
+
+	/* Page in the required floor/chip */
+	DoC_SelectFloor(doc, floor);
+	DoC_SelectChip(doc, chip);
+
+	/* Reset the chip */
+	if (DoC_Command(doc, NAND_CMD_RESET, CDSN_CTRL_WP)) {
+		DEBUG(MTD_DEBUG_LEVEL2,
+		      "DoC_Command (reset) for %d,%d returned true\n",
+		      floor, chip);
+		return 0;
+	}
+
+
+	/* Read the NAND chip ID: 1. Send ReadID command */
+	if (DoC_Command(doc, NAND_CMD_READID, CDSN_CTRL_WP)) {
+		DEBUG(MTD_DEBUG_LEVEL2,
+		      "DoC_Command (ReadID) for %d,%d returned true\n",
+		      floor, chip);
+		return 0;
+	}
+
+	/* Read the NAND chip ID: 2. Send address byte zero */
+	DoC_Address(doc, ADDR_COLUMN, 0, CDSN_CTRL_WP, 0);
+
+	/* Read the manufacturer and device id codes from the device */
+
+	if (DoC_is_Millennium(doc)) {
+		DoC_Delay(doc, 2);
+		dummy = ReadDOC(doc->virtadr, ReadPipeInit);
+		mfr = ReadDOC(doc->virtadr, LastDataRead);
+
+		DoC_Delay(doc, 2);
+		dummy = ReadDOC(doc->virtadr, ReadPipeInit);
+		id = ReadDOC(doc->virtadr, LastDataRead);
+	} else {
+		/* CDSN Slow IO register see Software Req 11.4 item 5. */
+		dummy = ReadDOC(doc->virtadr, CDSNSlowIO);
+		DoC_Delay(doc, 2);
+		mfr = ReadDOC_(doc->virtadr, doc->ioreg);
+
+		/* CDSN Slow IO register see Software Req 11.4 item 5. */
+		dummy = ReadDOC(doc->virtadr, CDSNSlowIO);
+		DoC_Delay(doc, 2);
+		id = ReadDOC_(doc->virtadr, doc->ioreg);
+	}
+
+	/* No response - return failure */
+	if (mfr == 0xff || mfr == 0)
+		return 0;
+
+	/* Check it's the same as the first chip we identified. 
+	 * M-Systems say that any given DiskOnChip device should only
+	 * contain _one_ type of flash part, although that's not a 
+	 * hardware restriction. */
+	if (doc->mfr) {
+		if (doc->mfr == mfr && doc->id == id)
+			return 1;	/* This is another the same the first */
+		else
+			printk(KERN_WARNING
+			       "Flash chip at floor %d, chip %d is different:\n",
+			       floor, chip);
+	}
+
+	/* Print and store the manufacturer and ID codes. */
+	for (i = 0; nand_flash_ids[i].name != NULL; i++) {
+		if (id == nand_flash_ids[i].id) {
+			/* Try to identify manufacturer */
+			for (j = 0; nand_manuf_ids[j].id != 0x0; j++) {
+				if (nand_manuf_ids[j].id == mfr)
+					break;
+			}	
+			printk(KERN_INFO
+			       "Flash chip found: Manufacturer ID: %2.2X, "
+			       "Chip ID: %2.2X (%s:%s)\n", mfr, id,
+			       nand_manuf_ids[j].name, nand_flash_ids[i].name);
+			if (!doc->mfr) {
+				doc->mfr = mfr;
+				doc->id = id;
+				doc->chipshift = 
+					ffs((nand_flash_ids[i].chipsize << 20)) - 1;
+				doc->page256 = (nand_flash_ids[i].pagesize == 256) ? 1 : 0;
+				doc->pageadrlen = doc->chipshift > 25 ? 3 : 2;
+				doc->erasesize =
+				    nand_flash_ids[i].erasesize;
+				return 1;
+			}
+			return 0;
+		}
+	}
+
+
+	/* We haven't fully identified the chip. Print as much as we know. */
+	printk(KERN_WARNING "Unknown flash chip found: %2.2X %2.2X\n",
+	       id, mfr);
+
+	printk(KERN_WARNING "Please report to dwmw2@infradead.org\n");
+	return 0;
+}
+
+/* DoC_ScanChips: Find all NAND chips present in a DiskOnChip, and identify them */
+
+static void DoC_ScanChips(struct DiskOnChip *this, int maxchips)
+{
+	int floor, chip;
+	int numchips[MAX_FLOORS];
+	int ret = 1;
+
+	this->numchips = 0;
+	this->mfr = 0;
+	this->id = 0;
+
+	/* For each floor, find the number of valid chips it contains */
+	for (floor = 0; floor < MAX_FLOORS; floor++) {
+		ret = 1;
+		numchips[floor] = 0;
+		for (chip = 0; chip < maxchips && ret != 0; chip++) {
+
+			ret = DoC_IdentChip(this, floor, chip);
+			if (ret) {
+				numchips[floor]++;
+				this->numchips++;
+			}
+		}
+	}
+
+	/* If there are none at all that we recognise, bail */
+	if (!this->numchips) {
+		printk(KERN_NOTICE "No flash chips recognised.\n");
+		return;
+	}
+
+	/* Allocate an array to hold the information for each chip */
+	this->chips = kmalloc(sizeof(struct Nand) * this->numchips, GFP_KERNEL);
+	if (!this->chips) {
+		printk(KERN_NOTICE "No memory for allocating chip info structures\n");
+		return;
+	}
+
+	ret = 0;
+
+	/* Fill out the chip array with {floor, chipno} for each 
+	 * detected chip in the device. */
+	for (floor = 0; floor < MAX_FLOORS; floor++) {
+		for (chip = 0; chip < numchips[floor]; chip++) {
+			this->chips[ret].floor = floor;
+			this->chips[ret].chip = chip;
+			this->chips[ret].curadr = 0;
+			this->chips[ret].curmode = 0x50;
+			ret++;
+		}
+	}
+
+	/* Calculate and print the total size of the device */
+	this->totlen = this->numchips * (1 << this->chipshift);
+
+	printk(KERN_INFO "%d flash chips found. Total DiskOnChip size: %ld MiB\n",
+	       this->numchips, this->totlen >> 20);
+}
+
+static int DoC2k_is_alias(struct DiskOnChip *doc1, struct DiskOnChip *doc2)
+{
+	int tmp1, tmp2, retval;
+	if (doc1->physadr == doc2->physadr)
+		return 1;
+
+	/* Use the alias resolution register which was set aside for this
+	 * purpose. If it's value is the same on both chips, they might
+	 * be the same chip, and we write to one and check for a change in
+	 * the other. It's unclear if this register is usuable in the
+	 * DoC 2000 (it's in the Millennium docs), but it seems to work. */
+	tmp1 = ReadDOC(doc1->virtadr, AliasResolution);
+	tmp2 = ReadDOC(doc2->virtadr, AliasResolution);
+	if (tmp1 != tmp2)
+		return 0;
+
+	WriteDOC((tmp1 + 1) % 0xff, doc1->virtadr, AliasResolution);
+	tmp2 = ReadDOC(doc2->virtadr, AliasResolution);
+	if (tmp2 == (tmp1 + 1) % 0xff)
+		retval = 1;
+	else
+		retval = 0;
+
+	/* Restore register contents.  May not be necessary, but do it just to
+	 * be safe. */
+	WriteDOC(tmp1, doc1->virtadr, AliasResolution);
+
+	return retval;
+}
+
+static const char im_name[] = "DoC2k_init";
+
+/* This routine is made available to other mtd code via
+ * inter_module_register.  It must only be accessed through
+ * inter_module_get which will bump the use count of this module.  The
+ * addresses passed back in mtd are valid as long as the use count of
+ * this module is non-zero, i.e. between inter_module_get and
+ * inter_module_put.  Keith Owens <kaos@ocs.com.au> 29 Oct 2000.
+ */
+static void DoC2k_init(struct mtd_info *mtd)
+{
+	struct DiskOnChip *this = mtd->priv;
+	struct DiskOnChip *old = NULL;
+	int maxchips;
+
+	/* We must avoid being called twice for the same device. */
+
+	if (doc2klist)
+		old = doc2klist->priv;
+
+	while (old) {
+		if (DoC2k_is_alias(old, this)) {
+			printk(KERN_NOTICE
+			       "Ignoring DiskOnChip 2000 at 0x%lX - already configured\n",
+			       this->physadr);
+			iounmap(this->virtadr);
+			kfree(mtd);
+			return;
+		}
+		if (old->nextdoc)
+			old = old->nextdoc->priv;
+		else
+			old = NULL;
+	}
+
+
+	switch (this->ChipID) {
+	case DOC_ChipID_Doc2kTSOP:
+		mtd->name = "DiskOnChip 2000 TSOP";
+		this->ioreg = DoC_Mil_CDSN_IO;
+		/* Pretend it's a Millennium */
+		this->ChipID = DOC_ChipID_DocMil;
+		maxchips = MAX_CHIPS;
+		break;
+	case DOC_ChipID_Doc2k:
+		mtd->name = "DiskOnChip 2000";
+		this->ioreg = DoC_2k_CDSN_IO;
+		maxchips = MAX_CHIPS;
+		break;
+	case DOC_ChipID_DocMil:
+		mtd->name = "DiskOnChip Millennium";
+		this->ioreg = DoC_Mil_CDSN_IO;
+		maxchips = MAX_CHIPS_MIL;
+		break;
+	default:
+		printk("Unknown ChipID 0x%02x\n", this->ChipID);
+		kfree(mtd);
+		iounmap(this->virtadr);
+		return;
+	}
+
+	printk(KERN_NOTICE "%s found at address 0x%lX\n", mtd->name,
+	       this->physadr);
+
+	mtd->type = MTD_NANDFLASH;
+	mtd->flags = MTD_CAP_NANDFLASH;
+	mtd->ecctype = MTD_ECC_RS_DiskOnChip;
+	mtd->size = 0;
+	mtd->erasesize = 0;
+	mtd->oobblock = 512;
+	mtd->oobsize = 16;
+	mtd->owner = THIS_MODULE;
+	mtd->erase = doc_erase;
+	mtd->point = NULL;
+	mtd->unpoint = NULL;
+	mtd->read = doc_read;
+	mtd->write = doc_write;
+	mtd->read_ecc = doc_read_ecc;
+	mtd->write_ecc = doc_write_ecc;
+	mtd->writev_ecc = doc_writev_ecc;
+	mtd->read_oob = doc_read_oob;
+	mtd->write_oob = doc_write_oob;
+	mtd->sync = NULL;
+
+	this->totlen = 0;
+	this->numchips = 0;
+
+	this->curfloor = -1;
+	this->curchip = -1;
+	init_MUTEX(&this->lock);
+
+	/* Ident all the chips present. */
+	DoC_ScanChips(this, maxchips);
+
+	if (!this->totlen) {
+		kfree(mtd);
+		iounmap(this->virtadr);
+	} else {
+		this->nextdoc = doc2klist;
+		doc2klist = mtd;
+		mtd->size = this->totlen;
+		mtd->erasesize = this->erasesize;
+		add_mtd_device(mtd);
+		return;
+	}
+}
+
+static int doc_read(struct mtd_info *mtd, loff_t from, size_t len,
+		    size_t * retlen, u_char * buf)
+{
+	/* Just a special case of doc_read_ecc */
+	return doc_read_ecc(mtd, from, len, retlen, buf, NULL, NULL);
+}
+
+static int doc_read_ecc(struct mtd_info *mtd, loff_t from, size_t len,
+			size_t * retlen, u_char * buf, u_char * eccbuf, struct nand_oobinfo *oobsel)
+{
+	struct DiskOnChip *this = mtd->priv;
+	void __iomem *docptr = this->virtadr;
+	struct Nand *mychip;
+	unsigned char syndrome[6];
+	volatile char dummy;
+	int i, len256 = 0, ret=0;
+	size_t left = len;
+
+	/* Don't allow read past end of device */
+	if (from >= this->totlen)
+		return -EINVAL;
+
+	down(&this->lock);
+
+	*retlen = 0;
+	while (left) {
+		len = left;
+
+		/* Don't allow a single read to cross a 512-byte block boundary */
+		if (from + len > ((from | 0x1ff) + 1))
+			len = ((from | 0x1ff) + 1) - from;
+
+		/* The ECC will not be calculated correctly if less than 512 is read */
+		if (len != 0x200 && eccbuf)
+			printk(KERN_WARNING
+			       "ECC needs a full sector read (adr: %lx size %lx)\n",
+			       (long) from, (long) len);
+
+		/* printk("DoC_Read (adr: %lx size %lx)\n", (long) from, (long) len); */
+
+
+		/* Find the chip which is to be used and select it */
+		mychip = &this->chips[from >> (this->chipshift)];
+
+		if (this->curfloor != mychip->floor) {
+			DoC_SelectFloor(this, mychip->floor);
+			DoC_SelectChip(this, mychip->chip);
+		} else if (this->curchip != mychip->chip) {
+			DoC_SelectChip(this, mychip->chip);
+		}
+
+		this->curfloor = mychip->floor;
+		this->curchip = mychip->chip;
+
+		DoC_Command(this,
+			    (!this->page256
+			     && (from & 0x100)) ? NAND_CMD_READ1 : NAND_CMD_READ0,
+			    CDSN_CTRL_WP);
+		DoC_Address(this, ADDR_COLUMN_PAGE, from, CDSN_CTRL_WP,
+			    CDSN_CTRL_ECC_IO);
+
+		if (eccbuf) {
+			/* Prime the ECC engine */
+			WriteDOC(DOC_ECC_RESET, docptr, ECCConf);
+			WriteDOC(DOC_ECC_EN, docptr, ECCConf);
+		} else {
+			/* disable the ECC engine */
+			WriteDOC(DOC_ECC_RESET, docptr, ECCConf);
+			WriteDOC(DOC_ECC_DIS, docptr, ECCConf);
+		}
+
+		/* treat crossing 256-byte sector for 2M x 8bits devices */
+		if (this->page256 && from + len > (from | 0xff) + 1) {
+			len256 = (from | 0xff) + 1 - from;
+			DoC_ReadBuf(this, buf, len256);
+
+			DoC_Command(this, NAND_CMD_READ0, CDSN_CTRL_WP);
+			DoC_Address(this, ADDR_COLUMN_PAGE, from + len256,
+				    CDSN_CTRL_WP, CDSN_CTRL_ECC_IO);
+		}
+
+		DoC_ReadBuf(this, &buf[len256], len - len256);
+
+		/* Let the caller know we completed it */
+		*retlen += len;
+
+		if (eccbuf) {
+			/* Read the ECC data through the DiskOnChip ECC logic */
+			/* Note: this will work even with 2M x 8bit devices as   */
+			/*       they have 8 bytes of OOB per 256 page. mf.      */
+			DoC_ReadBuf(this, eccbuf, 6);
+
+			/* Flush the pipeline */
+			if (DoC_is_Millennium(this)) {
+				dummy = ReadDOC(docptr, ECCConf);
+				dummy = ReadDOC(docptr, ECCConf);
+				i = ReadDOC(docptr, ECCConf);
+			} else {
+				dummy = ReadDOC(docptr, 2k_ECCStatus);
+				dummy = ReadDOC(docptr, 2k_ECCStatus);
+				i = ReadDOC(docptr, 2k_ECCStatus);
+			}
+
+			/* Check the ECC Status */
+			if (i & 0x80) {
+				int nb_errors;
+				/* There was an ECC error */
+#ifdef ECC_DEBUG
+				printk(KERN_ERR "DiskOnChip ECC Error: Read at %lx\n", (long)from);
+#endif
+				/* Read the ECC syndrom through the DiskOnChip ECC logic.
+				   These syndrome will be all ZERO when there is no error */
+				for (i = 0; i < 6; i++) {
+					syndrome[i] =
+					    ReadDOC(docptr, ECCSyndrome0 + i);
+				}
+	                        nb_errors = doc_decode_ecc(buf, syndrome);
+
+#ifdef ECC_DEBUG
+				printk(KERN_ERR "Errors corrected: %x\n", nb_errors);
+#endif
+	                        if (nb_errors < 0) {
+					/* We return error, but have actually done the read. Not that
+					   this can be told to user-space, via sys_read(), but at least
+					   MTD-aware stuff can know about it by checking *retlen */
+					ret = -EIO;
+	                        }
+			}
+
+#ifdef PSYCHO_DEBUG
+			printk(KERN_DEBUG "ECC DATA at %lxB: %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n",
+				     (long)from, eccbuf[0], eccbuf[1], eccbuf[2],
+				     eccbuf[3], eccbuf[4], eccbuf[5]);
+#endif
+		
+			/* disable the ECC engine */
+			WriteDOC(DOC_ECC_DIS, docptr , ECCConf);
+		}
+
+		/* according to 11.4.1, we need to wait for the busy line 
+	         * drop if we read to the end of the page.  */
+		if(0 == ((from + len) & 0x1ff))
+		{
+		    DoC_WaitReady(this);
+		}
+
+		from += len;
+		left -= len;
+		buf += len;
+	}
+
+	up(&this->lock);
+
+	return ret;
+}
+
+static int doc_write(struct mtd_info *mtd, loff_t to, size_t len,
+		     size_t * retlen, const u_char * buf)
+{
+	char eccbuf[6];
+	return doc_write_ecc(mtd, to, len, retlen, buf, eccbuf, NULL);
+}
+
+static int doc_write_ecc(struct mtd_info *mtd, loff_t to, size_t len,
+			 size_t * retlen, const u_char * buf,
+			 u_char * eccbuf, struct nand_oobinfo *oobsel)
+{
+	struct DiskOnChip *this = mtd->priv;
+	int di; /* Yes, DI is a hangover from when I was disassembling the binary driver */
+	void __iomem *docptr = this->virtadr;
+	volatile char dummy;
+	int len256 = 0;
+	struct Nand *mychip;
+	size_t left = len;
+	int status;
+
+	/* Don't allow write past end of device */
+	if (to >= this->totlen)
+		return -EINVAL;
+
+	down(&this->lock);
+
+	*retlen = 0;
+	while (left) {
+		len = left;
+
+		/* Don't allow a single write to cross a 512-byte block boundary */
+		if (to + len > ((to | 0x1ff) + 1))
+			len = ((to | 0x1ff) + 1) - to;
+
+		/* The ECC will not be calculated correctly if less than 512 is written */
+/* DBB-
+		if (len != 0x200 && eccbuf)
+			printk(KERN_WARNING
+			       "ECC needs a full sector write (adr: %lx size %lx)\n",
+			       (long) to, (long) len);
+   -DBB */
+
+		/* printk("DoC_Write (adr: %lx size %lx)\n", (long) to, (long) len); */
+
+		/* Find the chip which is to be used and select it */
+		mychip = &this->chips[to >> (this->chipshift)];
+
+		if (this->curfloor != mychip->floor) {
+			DoC_SelectFloor(this, mychip->floor);
+			DoC_SelectChip(this, mychip->chip);
+		} else if (this->curchip != mychip->chip) {
+			DoC_SelectChip(this, mychip->chip);
+		}
+
+		this->curfloor = mychip->floor;
+		this->curchip = mychip->chip;
+
+		/* Set device to main plane of flash */
+		DoC_Command(this, NAND_CMD_RESET, CDSN_CTRL_WP);
+		DoC_Command(this,
+			    (!this->page256
+			     && (to & 0x100)) ? NAND_CMD_READ1 : NAND_CMD_READ0,
+			    CDSN_CTRL_WP);
+
+		DoC_Command(this, NAND_CMD_SEQIN, 0);
+		DoC_Address(this, ADDR_COLUMN_PAGE, to, 0, CDSN_CTRL_ECC_IO);
+
+		if (eccbuf) {
+			/* Prime the ECC engine */
+			WriteDOC(DOC_ECC_RESET, docptr, ECCConf);
+			WriteDOC(DOC_ECC_EN | DOC_ECC_RW, docptr, ECCConf);
+		} else {
+			/* disable the ECC engine */
+			WriteDOC(DOC_ECC_RESET, docptr, ECCConf);
+			WriteDOC(DOC_ECC_DIS, docptr, ECCConf);
+		}
+
+		/* treat crossing 256-byte sector for 2M x 8bits devices */
+		if (this->page256 && to + len > (to | 0xff) + 1) {
+			len256 = (to | 0xff) + 1 - to;
+			DoC_WriteBuf(this, buf, len256);
+
+			DoC_Command(this, NAND_CMD_PAGEPROG, 0);
+
+			DoC_Command(this, NAND_CMD_STATUS, CDSN_CTRL_WP);
+			/* There's an implicit DoC_WaitReady() in DoC_Command */
+
+			dummy = ReadDOC(docptr, CDSNSlowIO);
+			DoC_Delay(this, 2);
+
+			if (ReadDOC_(docptr, this->ioreg) & 1) {
+				printk(KERN_ERR "Error programming flash\n");
+				/* Error in programming */
+				*retlen = 0;
+				up(&this->lock);
+				return -EIO;
+			}
+
+			DoC_Command(this, NAND_CMD_SEQIN, 0);
+			DoC_Address(this, ADDR_COLUMN_PAGE, to + len256, 0,
+				    CDSN_CTRL_ECC_IO);
+		}
+
+		DoC_WriteBuf(this, &buf[len256], len - len256);
+
+		if (eccbuf) {
+			WriteDOC(CDSN_CTRL_ECC_IO | CDSN_CTRL_CE, docptr,
+				 CDSNControl);
+
+			if (DoC_is_Millennium(this)) {
+				WriteDOC(0, docptr, NOP);
+				WriteDOC(0, docptr, NOP);
+				WriteDOC(0, docptr, NOP);
+			} else {
+				WriteDOC_(0, docptr, this->ioreg);
+				WriteDOC_(0, docptr, this->ioreg);
+				WriteDOC_(0, docptr, this->ioreg);
+			}
+
+			WriteDOC(CDSN_CTRL_ECC_IO | CDSN_CTRL_FLASH_IO | CDSN_CTRL_CE, docptr,
+				 CDSNControl);
+
+			/* Read the ECC data through the DiskOnChip ECC logic */
+			for (di = 0; di < 6; di++) {
+				eccbuf[di] = ReadDOC(docptr, ECCSyndrome0 + di);
+			}
+
+			/* Reset the ECC engine */
+			WriteDOC(DOC_ECC_DIS, docptr, ECCConf);
+
+#ifdef PSYCHO_DEBUG
+			printk
+			    ("OOB data at %lx is %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n",
+			     (long) to, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3],
+			     eccbuf[4], eccbuf[5]);
+#endif
+		}
+
+		DoC_Command(this, NAND_CMD_PAGEPROG, 0);
+
+		DoC_Command(this, NAND_CMD_STATUS, CDSN_CTRL_WP);
+		/* There's an implicit DoC_WaitReady() in DoC_Command */
+
+		if (DoC_is_Millennium(this)) {
+			ReadDOC(docptr, ReadPipeInit);
+			status = ReadDOC(docptr, LastDataRead);
+		} else {
+			dummy = ReadDOC(docptr, CDSNSlowIO);
+			DoC_Delay(this, 2);
+			status = ReadDOC_(docptr, this->ioreg);
+		}
+
+		if (status & 1) {
+			printk(KERN_ERR "Error programming flash\n");
+			/* Error in programming */
+			*retlen = 0;
+			up(&this->lock);
+			return -EIO;
+		}
+
+		/* Let the caller know we completed it */
+		*retlen += len;
+		
+		if (eccbuf) {
+			unsigned char x[8];
+			size_t dummy;
+			int ret;
+
+			/* Write the ECC data to flash */
+			for (di=0; di<6; di++)
+				x[di] = eccbuf[di];
+		
+			x[6]=0x55;
+			x[7]=0x55;
+		
+			ret = doc_write_oob_nolock(mtd, to, 8, &dummy, x);
+			if (ret) {
+				up(&this->lock);
+				return ret;
+			}
+		}
+
+		to += len;
+		left -= len;
+		buf += len;
+	}
+
+	up(&this->lock);
+	return 0;
+}
+
+static int doc_writev_ecc(struct mtd_info *mtd, const struct kvec *vecs, 
+			  unsigned long count, loff_t to, size_t *retlen,
+			  u_char *eccbuf, struct nand_oobinfo *oobsel)
+{
+	static char static_buf[512];
+	static DECLARE_MUTEX(writev_buf_sem);
+
+	size_t totretlen = 0;
+	size_t thisvecofs = 0;
+	int ret= 0;
+
+	down(&writev_buf_sem);
+
+	while(count) {
+		size_t thislen, thisretlen;
+		unsigned char *buf;
+
+		buf = vecs->iov_base + thisvecofs;
+		thislen = vecs->iov_len - thisvecofs;
+
+
+		if (thislen >= 512) {
+			thislen = thislen & ~(512-1);
+			thisvecofs += thislen;
+		} else {
+			/* Not enough to fill a page. Copy into buf */
+			memcpy(static_buf, buf, thislen);
+			buf = &static_buf[thislen];
+
+			while(count && thislen < 512) {
+				vecs++;
+				count--;
+				thisvecofs = min((512-thislen), vecs->iov_len);
+				memcpy(buf, vecs->iov_base, thisvecofs);
+				thislen += thisvecofs;
+				buf += thisvecofs;
+			}
+			buf = static_buf;
+		}
+		if (count && thisvecofs == vecs->iov_len) {
+			thisvecofs = 0;
+			vecs++;
+			count--;
+		}
+		ret = doc_write_ecc(mtd, to, thislen, &thisretlen, buf, eccbuf, oobsel);
+
+		totretlen += thisretlen;
+
+		if (ret || thisretlen != thislen)
+			break;
+
+		to += thislen;
+	}		
+
+	up(&writev_buf_sem);
+	*retlen = totretlen;
+	return ret;
+}
+
+
+static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+			size_t * retlen, u_char * buf)
+{
+	struct DiskOnChip *this = mtd->priv;
+	int len256 = 0, ret;
+	struct Nand *mychip;
+
+	down(&this->lock);
+
+	mychip = &this->chips[ofs >> this->chipshift];
+
+	if (this->curfloor != mychip->floor) {
+		DoC_SelectFloor(this, mychip->floor);
+		DoC_SelectChip(this, mychip->chip);
+	} else if (this->curchip != mychip->chip) {
+		DoC_SelectChip(this, mychip->chip);
+	}
+	this->curfloor = mychip->floor;
+	this->curchip = mychip->chip;
+
+	/* update address for 2M x 8bit devices. OOB starts on the second */
+	/* page to maintain compatibility with doc_read_ecc. */
+	if (this->page256) {
+		if (!(ofs & 0x8))
+			ofs += 0x100;
+		else
+			ofs -= 0x8;
+	}
+
+	DoC_Command(this, NAND_CMD_READOOB, CDSN_CTRL_WP);
+	DoC_Address(this, ADDR_COLUMN_PAGE, ofs, CDSN_CTRL_WP, 0);
+
+	/* treat crossing 8-byte OOB data for 2M x 8bit devices */
+	/* Note: datasheet says it should automaticaly wrap to the */
+	/*       next OOB block, but it didn't work here. mf.      */
+	if (this->page256 && ofs + len > (ofs | 0x7) + 1) {
+		len256 = (ofs | 0x7) + 1 - ofs;
+		DoC_ReadBuf(this, buf, len256);
+
+		DoC_Command(this, NAND_CMD_READOOB, CDSN_CTRL_WP);
+		DoC_Address(this, ADDR_COLUMN_PAGE, ofs & (~0x1ff),
+			    CDSN_CTRL_WP, 0);
+	}
+
+	DoC_ReadBuf(this, &buf[len256], len - len256);
+
+	*retlen = len;
+	/* Reading the full OOB data drops us off of the end of the page,
+         * causing the flash device to go into busy mode, so we need
+         * to wait until ready 11.4.1 and Toshiba TC58256FT docs */
+	
+	ret = DoC_WaitReady(this);
+
+	up(&this->lock);
+	return ret;
+
+}
+
+static int doc_write_oob_nolock(struct mtd_info *mtd, loff_t ofs, size_t len,
+				size_t * retlen, const u_char * buf)
+{
+	struct DiskOnChip *this = mtd->priv;
+	int len256 = 0;
+	void __iomem *docptr = this->virtadr;
+	struct Nand *mychip = &this->chips[ofs >> this->chipshift];
+	volatile int dummy;
+	int status;
+
+	//      printk("doc_write_oob(%lx, %d): %2.2X %2.2X %2.2X %2.2X ... %2.2X %2.2X .. %2.2X %2.2X\n",(long)ofs, len,
+	//   buf[0], buf[1], buf[2], buf[3], buf[8], buf[9], buf[14],buf[15]);
+
+	/* Find the chip which is to be used and select it */
+	if (this->curfloor != mychip->floor) {
+		DoC_SelectFloor(this, mychip->floor);
+		DoC_SelectChip(this, mychip->chip);
+	} else if (this->curchip != mychip->chip) {
+		DoC_SelectChip(this, mychip->chip);
+	}
+	this->curfloor = mychip->floor;
+	this->curchip = mychip->chip;
+
+	/* disable the ECC engine */
+	WriteDOC (DOC_ECC_RESET, docptr, ECCConf);
+	WriteDOC (DOC_ECC_DIS, docptr, ECCConf);
+
+	/* Reset the chip, see Software Requirement 11.4 item 1. */
+	DoC_Command(this, NAND_CMD_RESET, CDSN_CTRL_WP);
+
+	/* issue the Read2 command to set the pointer to the Spare Data Area. */
+	DoC_Command(this, NAND_CMD_READOOB, CDSN_CTRL_WP);
+
+	/* update address for 2M x 8bit devices. OOB starts on the second */
+	/* page to maintain compatibility with doc_read_ecc. */
+	if (this->page256) {
+		if (!(ofs & 0x8))
+			ofs += 0x100;
+		else
+			ofs -= 0x8;
+	}
+
+	/* issue the Serial Data In command to initial the Page Program process */
+	DoC_Command(this, NAND_CMD_SEQIN, 0);
+	DoC_Address(this, ADDR_COLUMN_PAGE, ofs, 0, 0);
+
+	/* treat crossing 8-byte OOB data for 2M x 8bit devices */
+	/* Note: datasheet says it should automaticaly wrap to the */
+	/*       next OOB block, but it didn't work here. mf.      */
+	if (this->page256 && ofs + len > (ofs | 0x7) + 1) {
+		len256 = (ofs | 0x7) + 1 - ofs;
+		DoC_WriteBuf(this, buf, len256);
+
+		DoC_Command(this, NAND_CMD_PAGEPROG, 0);
+		DoC_Command(this, NAND_CMD_STATUS, 0);
+		/* DoC_WaitReady() is implicit in DoC_Command */
+
+		if (DoC_is_Millennium(this)) {
+			ReadDOC(docptr, ReadPipeInit);
+			status = ReadDOC(docptr, LastDataRead);
+		} else {
+			dummy = ReadDOC(docptr, CDSNSlowIO);
+			DoC_Delay(this, 2);
+			status = ReadDOC_(docptr, this->ioreg);
+		}
+
+		if (status & 1) {
+			printk(KERN_ERR "Error programming oob data\n");
+			/* There was an error */
+			*retlen = 0;
+			return -EIO;
+		}
+		DoC_Command(this, NAND_CMD_SEQIN, 0);
+		DoC_Address(this, ADDR_COLUMN_PAGE, ofs & (~0x1ff), 0, 0);
+	}
+
+	DoC_WriteBuf(this, &buf[len256], len - len256);
+
+	DoC_Command(this, NAND_CMD_PAGEPROG, 0);
+	DoC_Command(this, NAND_CMD_STATUS, 0);
+	/* DoC_WaitReady() is implicit in DoC_Command */
+
+	if (DoC_is_Millennium(this)) {
+		ReadDOC(docptr, ReadPipeInit);
+		status = ReadDOC(docptr, LastDataRead);
+	} else {
+		dummy = ReadDOC(docptr, CDSNSlowIO);
+		DoC_Delay(this, 2);
+		status = ReadDOC_(docptr, this->ioreg);
+	}
+
+	if (status & 1) {
+		printk(KERN_ERR "Error programming oob data\n");
+		/* There was an error */
+		*retlen = 0;
+		return -EIO;
+	}
+
+	*retlen = len;
+	return 0;
+
+}
+ 
+static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+ 			 size_t * retlen, const u_char * buf)
+{
+ 	struct DiskOnChip *this = mtd->priv;
+ 	int ret;
+
+ 	down(&this->lock);
+ 	ret = doc_write_oob_nolock(mtd, ofs, len, retlen, buf);
+
+ 	up(&this->lock);
+ 	return ret;
+}
+
+static int doc_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+	struct DiskOnChip *this = mtd->priv;
+	__u32 ofs = instr->addr;
+	__u32 len = instr->len;
+	volatile int dummy;
+	void __iomem *docptr = this->virtadr;
+	struct Nand *mychip;
+	int status;
+
+ 	down(&this->lock);
+
+	if (ofs & (mtd->erasesize-1) || len & (mtd->erasesize-1)) {
+		up(&this->lock);
+		return -EINVAL;
+	}
+
+	instr->state = MTD_ERASING;
+		
+	/* FIXME: Do this in the background. Use timers or schedule_task() */
+	while(len) {
+		mychip = &this->chips[ofs >> this->chipshift];
+
+		if (this->curfloor != mychip->floor) {
+			DoC_SelectFloor(this, mychip->floor);
+			DoC_SelectChip(this, mychip->chip);
+		} else if (this->curchip != mychip->chip) {
+			DoC_SelectChip(this, mychip->chip);
+		}
+		this->curfloor = mychip->floor;
+		this->curchip = mychip->chip;
+
+		DoC_Command(this, NAND_CMD_ERASE1, 0);
+		DoC_Address(this, ADDR_PAGE, ofs, 0, 0);
+		DoC_Command(this, NAND_CMD_ERASE2, 0);
+
+		DoC_Command(this, NAND_CMD_STATUS, CDSN_CTRL_WP);
+
+		if (DoC_is_Millennium(this)) {
+			ReadDOC(docptr, ReadPipeInit);
+			status = ReadDOC(docptr, LastDataRead);
+		} else {
+			dummy = ReadDOC(docptr, CDSNSlowIO);
+			DoC_Delay(this, 2);
+			status = ReadDOC_(docptr, this->ioreg);
+		}
+
+		if (status & 1) {
+			printk(KERN_ERR "Error erasing at 0x%x\n", ofs);
+			/* There was an error */
+			instr->state = MTD_ERASE_FAILED;
+			goto callback;
+		}
+		ofs += mtd->erasesize;
+		len -= mtd->erasesize;
+	}
+	instr->state = MTD_ERASE_DONE;
+
+ callback:
+	mtd_erase_callback(instr);
+
+	up(&this->lock);
+	return 0;
+}
+
+
+/****************************************************************************
+ *
+ * Module stuff
+ *
+ ****************************************************************************/
+
+static int __init init_doc2000(void)
+{
+       inter_module_register(im_name, THIS_MODULE, &DoC2k_init);
+       return 0;
+}
+
+static void __exit cleanup_doc2000(void)
+{
+	struct mtd_info *mtd;
+	struct DiskOnChip *this;
+
+	while ((mtd = doc2klist)) {
+		this = mtd->priv;
+		doc2klist = this->nextdoc;
+
+		del_mtd_device(mtd);
+
+		iounmap(this->virtadr);
+		kfree(this->chips);
+		kfree(mtd);
+	}
+	inter_module_unregister(im_name);
+}
+
+module_exit(cleanup_doc2000);
+module_init(init_doc2000);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org> et al.");
+MODULE_DESCRIPTION("MTD driver for DiskOnChip 2000 and Millennium");
+
diff --git a/drivers/mtd/devices/doc2001.c b/drivers/mtd/devices/doc2001.c
new file mode 100644
index 0000000..1e70491
--- /dev/null
+++ b/drivers/mtd/devices/doc2001.c
@@ -0,0 +1,888 @@
+
+/*
+ * Linux driver for Disk-On-Chip Millennium
+ * (c) 1999 Machine Vision Holdings, Inc.
+ * (c) 1999, 2000 David Woodhouse <dwmw2@infradead.org>
+ *
+ * $Id: doc2001.c,v 1.48 2005/01/05 18:05:12 dwmw2 Exp $
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/miscdevice.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/bitops.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/doc2000.h>
+
+/* #define ECC_DEBUG */
+
+/* I have no idea why some DoC chips can not use memcop_form|to_io().
+ * This may be due to the different revisions of the ASIC controller built-in or
+ * simplily a QA/Bug issue. Who knows ?? If you have trouble, please uncomment
+ * this:*/
+#undef USE_MEMCPY
+
+static int doc_read(struct mtd_info *mtd, loff_t from, size_t len,
+		    size_t *retlen, u_char *buf);
+static int doc_write(struct mtd_info *mtd, loff_t to, size_t len,
+		     size_t *retlen, const u_char *buf);
+static int doc_read_ecc(struct mtd_info *mtd, loff_t from, size_t len,
+			size_t *retlen, u_char *buf, u_char *eccbuf,
+			struct nand_oobinfo *oobsel);
+static int doc_write_ecc(struct mtd_info *mtd, loff_t to, size_t len,
+			 size_t *retlen, const u_char *buf, u_char *eccbuf,
+			 struct nand_oobinfo *oobsel);
+static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+			size_t *retlen, u_char *buf);
+static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+			 size_t *retlen, const u_char *buf);
+static int doc_erase (struct mtd_info *mtd, struct erase_info *instr);
+
+static struct mtd_info *docmillist = NULL;
+
+/* Perform the required delay cycles by reading from the NOP register */
+static void DoC_Delay(void __iomem * docptr, unsigned short cycles)
+{
+	volatile char dummy;
+	int i;
+
+	for (i = 0; i < cycles; i++)
+		dummy = ReadDOC(docptr, NOP);
+}
+
+/* DOC_WaitReady: Wait for RDY line to be asserted by the flash chip */
+static int _DoC_WaitReady(void __iomem * docptr)
+{
+	unsigned short c = 0xffff;
+
+	DEBUG(MTD_DEBUG_LEVEL3,
+	      "_DoC_WaitReady called for out-of-line wait\n");
+
+	/* Out-of-line routine to wait for chip response */
+	while (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B) && --c)
+		;
+
+	if (c == 0)
+		DEBUG(MTD_DEBUG_LEVEL2, "_DoC_WaitReady timed out.\n");
+
+	return (c == 0);
+}
+
+static inline int DoC_WaitReady(void __iomem * docptr)
+{
+	/* This is inline, to optimise the common case, where it's ready instantly */
+	int ret = 0;
+
+	/* 4 read form NOP register should be issued in prior to the read from CDSNControl
+	   see Software Requirement 11.4 item 2. */
+	DoC_Delay(docptr, 4);
+
+	if (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B))
+		/* Call the out-of-line routine to wait */
+		ret = _DoC_WaitReady(docptr);
+
+	/* issue 2 read from NOP register after reading from CDSNControl register
+	   see Software Requirement 11.4 item 2. */
+	DoC_Delay(docptr, 2);
+
+	return ret;
+}
+
+/* DoC_Command: Send a flash command to the flash chip through the CDSN IO register
+   with the internal pipeline. Each of 4 delay cycles (read from the NOP register) is
+   required after writing to CDSN Control register, see Software Requirement 11.4 item 3. */
+
+static inline void DoC_Command(void __iomem * docptr, unsigned char command,
+			       unsigned char xtraflags)
+{
+	/* Assert the CLE (Command Latch Enable) line to the flash chip */
+	WriteDOC(xtraflags | CDSN_CTRL_CLE | CDSN_CTRL_CE, docptr, CDSNControl);
+	DoC_Delay(docptr, 4);
+
+	/* Send the command */
+	WriteDOC(command, docptr, Mil_CDSN_IO);
+	WriteDOC(0x00, docptr, WritePipeTerm);
+
+	/* Lower the CLE line */
+	WriteDOC(xtraflags | CDSN_CTRL_CE, docptr, CDSNControl);
+	DoC_Delay(docptr, 4);
+}
+
+/* DoC_Address: Set the current address for the flash chip through the CDSN IO register
+   with the internal pipeline. Each of 4 delay cycles (read from the NOP register) is
+   required after writing to CDSN Control register, see Software Requirement 11.4 item 3. */
+
+static inline void DoC_Address(void __iomem * docptr, int numbytes, unsigned long ofs,
+			       unsigned char xtraflags1, unsigned char xtraflags2)
+{
+	/* Assert the ALE (Address Latch Enable) line to the flash chip */
+	WriteDOC(xtraflags1 | CDSN_CTRL_ALE | CDSN_CTRL_CE, docptr, CDSNControl);
+	DoC_Delay(docptr, 4);
+
+	/* Send the address */
+	switch (numbytes)
+	    {
+	    case 1:
+		    /* Send single byte, bits 0-7. */
+		    WriteDOC(ofs & 0xff, docptr, Mil_CDSN_IO);
+		    WriteDOC(0x00, docptr, WritePipeTerm);
+		    break;
+	    case 2:
+		    /* Send bits 9-16 followed by 17-23 */
+		    WriteDOC((ofs >> 9)  & 0xff, docptr, Mil_CDSN_IO);
+		    WriteDOC((ofs >> 17) & 0xff, docptr, Mil_CDSN_IO);
+		    WriteDOC(0x00, docptr, WritePipeTerm);
+		break;
+	    case 3:
+		    /* Send 0-7, 9-16, then 17-23 */
+		    WriteDOC(ofs & 0xff, docptr, Mil_CDSN_IO);
+		    WriteDOC((ofs >> 9)  & 0xff, docptr, Mil_CDSN_IO);
+		    WriteDOC((ofs >> 17) & 0xff, docptr, Mil_CDSN_IO);
+		    WriteDOC(0x00, docptr, WritePipeTerm);
+		break;
+	    default:
+		return;
+	    }
+
+	/* Lower the ALE line */
+	WriteDOC(xtraflags1 | xtraflags2 | CDSN_CTRL_CE, docptr, CDSNControl);
+	DoC_Delay(docptr, 4);
+}
+
+/* DoC_SelectChip: Select a given flash chip within the current floor */
+static int DoC_SelectChip(void __iomem * docptr, int chip)
+{
+	/* Select the individual flash chip requested */
+	WriteDOC(chip, docptr, CDSNDeviceSelect);
+	DoC_Delay(docptr, 4);
+
+	/* Wait for it to be ready */
+	return DoC_WaitReady(docptr);
+}
+
+/* DoC_SelectFloor: Select a given floor (bank of flash chips) */
+static int DoC_SelectFloor(void __iomem * docptr, int floor)
+{
+	/* Select the floor (bank) of chips required */
+	WriteDOC(floor, docptr, FloorSelect);
+
+	/* Wait for the chip to be ready */
+	return DoC_WaitReady(docptr);
+}
+
+/* DoC_IdentChip: Identify a given NAND chip given {floor,chip} */
+static int DoC_IdentChip(struct DiskOnChip *doc, int floor, int chip)
+{
+	int mfr, id, i, j;
+	volatile char dummy;
+
+	/* Page in the required floor/chip
+	   FIXME: is this supported by Millennium ?? */
+	DoC_SelectFloor(doc->virtadr, floor);
+	DoC_SelectChip(doc->virtadr, chip);
+
+	/* Reset the chip, see Software Requirement 11.4 item 1. */
+	DoC_Command(doc->virtadr, NAND_CMD_RESET, CDSN_CTRL_WP);
+	DoC_WaitReady(doc->virtadr);
+
+	/* Read the NAND chip ID: 1. Send ReadID command */ 
+	DoC_Command(doc->virtadr, NAND_CMD_READID, CDSN_CTRL_WP);
+
+	/* Read the NAND chip ID: 2. Send address byte zero */ 
+	DoC_Address(doc->virtadr, 1, 0x00, CDSN_CTRL_WP, 0x00);
+
+	/* Read the manufacturer and device id codes of the flash device through
+	   CDSN IO register see Software Requirement 11.4 item 5.*/
+	dummy = ReadDOC(doc->virtadr, ReadPipeInit);
+	DoC_Delay(doc->virtadr, 2);
+	mfr = ReadDOC(doc->virtadr, Mil_CDSN_IO);
+
+	DoC_Delay(doc->virtadr, 2);
+	id  = ReadDOC(doc->virtadr, Mil_CDSN_IO);
+	dummy = ReadDOC(doc->virtadr, LastDataRead);
+
+	/* No response - return failure */
+	if (mfr == 0xff || mfr == 0)
+		return 0;
+
+	/* FIXME: to deal with multi-flash on multi-Millennium case more carefully */
+	for (i = 0; nand_flash_ids[i].name != NULL; i++) {
+		if ( id == nand_flash_ids[i].id) {
+			/* Try to identify manufacturer */
+			for (j = 0; nand_manuf_ids[j].id != 0x0; j++) {
+				if (nand_manuf_ids[j].id == mfr)
+					break;
+			}	
+			printk(KERN_INFO "Flash chip found: Manufacturer ID: %2.2X, "
+			       "Chip ID: %2.2X (%s:%s)\n",
+			       mfr, id, nand_manuf_ids[j].name, nand_flash_ids[i].name);
+			doc->mfr = mfr;
+			doc->id = id;
+			doc->chipshift = ffs((nand_flash_ids[i].chipsize << 20)) - 1;
+			break;
+		}
+	}
+
+	if (nand_flash_ids[i].name == NULL)
+		return 0;
+	else
+		return 1;
+}
+
+/* DoC_ScanChips: Find all NAND chips present in a DiskOnChip, and identify them */
+static void DoC_ScanChips(struct DiskOnChip *this)
+{
+	int floor, chip;
+	int numchips[MAX_FLOORS_MIL];
+	int ret;
+
+	this->numchips = 0;
+	this->mfr = 0;
+	this->id = 0;
+
+	/* For each floor, find the number of valid chips it contains */
+	for (floor = 0,ret = 1; floor < MAX_FLOORS_MIL; floor++) {
+		numchips[floor] = 0;
+		for (chip = 0; chip < MAX_CHIPS_MIL && ret != 0; chip++) {
+			ret = DoC_IdentChip(this, floor, chip);
+			if (ret) {
+				numchips[floor]++;
+				this->numchips++;
+			}
+		}
+	}
+	/* If there are none at all that we recognise, bail */
+	if (!this->numchips) {
+		printk("No flash chips recognised.\n");
+		return;
+	}
+
+	/* Allocate an array to hold the information for each chip */
+	this->chips = kmalloc(sizeof(struct Nand) * this->numchips, GFP_KERNEL);
+	if (!this->chips){
+		printk("No memory for allocating chip info structures\n");
+		return;
+	}
+
+	/* Fill out the chip array with {floor, chipno} for each 
+	 * detected chip in the device. */
+	for (floor = 0, ret = 0; floor < MAX_FLOORS_MIL; floor++) {
+		for (chip = 0 ; chip < numchips[floor] ; chip++) {
+			this->chips[ret].floor = floor;
+			this->chips[ret].chip = chip;
+			this->chips[ret].curadr = 0;
+			this->chips[ret].curmode = 0x50;
+			ret++;
+		}
+	}
+
+	/* Calculate and print the total size of the device */
+	this->totlen = this->numchips * (1 << this->chipshift);
+	printk(KERN_INFO "%d flash chips found. Total DiskOnChip size: %ld MiB\n",
+	       this->numchips ,this->totlen >> 20);
+}
+
+static int DoCMil_is_alias(struct DiskOnChip *doc1, struct DiskOnChip *doc2)
+{
+	int tmp1, tmp2, retval;
+
+	if (doc1->physadr == doc2->physadr)
+		return 1;
+
+	/* Use the alias resolution register which was set aside for this
+	 * purpose. If it's value is the same on both chips, they might
+	 * be the same chip, and we write to one and check for a change in
+	 * the other. It's unclear if this register is usuable in the
+	 * DoC 2000 (it's in the Millenium docs), but it seems to work. */
+	tmp1 = ReadDOC(doc1->virtadr, AliasResolution);
+	tmp2 = ReadDOC(doc2->virtadr, AliasResolution);
+	if (tmp1 != tmp2)
+		return 0;
+	
+	WriteDOC((tmp1+1) % 0xff, doc1->virtadr, AliasResolution);
+	tmp2 = ReadDOC(doc2->virtadr, AliasResolution);
+	if (tmp2 == (tmp1+1) % 0xff)
+		retval = 1;
+	else
+		retval = 0;
+
+	/* Restore register contents.  May not be necessary, but do it just to
+	 * be safe. */
+	WriteDOC(tmp1, doc1->virtadr, AliasResolution);
+
+	return retval;
+}
+
+static const char im_name[] = "DoCMil_init";
+
+/* This routine is made available to other mtd code via
+ * inter_module_register.  It must only be accessed through
+ * inter_module_get which will bump the use count of this module.  The
+ * addresses passed back in mtd are valid as long as the use count of
+ * this module is non-zero, i.e. between inter_module_get and
+ * inter_module_put.  Keith Owens <kaos@ocs.com.au> 29 Oct 2000.
+ */
+static void DoCMil_init(struct mtd_info *mtd)
+{
+	struct DiskOnChip *this = mtd->priv;
+	struct DiskOnChip *old = NULL;
+
+	/* We must avoid being called twice for the same device. */
+	if (docmillist)
+		old = docmillist->priv;
+
+	while (old) {
+		if (DoCMil_is_alias(this, old)) {
+			printk(KERN_NOTICE "Ignoring DiskOnChip Millennium at "
+			       "0x%lX - already configured\n", this->physadr);
+			iounmap(this->virtadr);
+			kfree(mtd);
+			return;
+		}
+		if (old->nextdoc)
+			old = old->nextdoc->priv;
+		else
+			old = NULL;
+	}
+
+	mtd->name = "DiskOnChip Millennium";
+	printk(KERN_NOTICE "DiskOnChip Millennium found at address 0x%lX\n",
+	       this->physadr);
+
+	mtd->type = MTD_NANDFLASH;
+	mtd->flags = MTD_CAP_NANDFLASH;
+	mtd->ecctype = MTD_ECC_RS_DiskOnChip;
+	mtd->size = 0;
+
+	/* FIXME: erase size is not always 8KiB */
+	mtd->erasesize = 0x2000;
+
+	mtd->oobblock = 512;
+	mtd->oobsize = 16;
+	mtd->owner = THIS_MODULE;
+	mtd->erase = doc_erase;
+	mtd->point = NULL;
+	mtd->unpoint = NULL;
+	mtd->read = doc_read;
+	mtd->write = doc_write;
+	mtd->read_ecc = doc_read_ecc;
+	mtd->write_ecc = doc_write_ecc;
+	mtd->read_oob = doc_read_oob;
+	mtd->write_oob = doc_write_oob;
+	mtd->sync = NULL;
+
+	this->totlen = 0;
+	this->numchips = 0;
+	this->curfloor = -1;
+	this->curchip = -1;
+
+	/* Ident all the chips present. */
+	DoC_ScanChips(this);
+
+	if (!this->totlen) {
+		kfree(mtd);
+		iounmap(this->virtadr);
+	} else {
+		this->nextdoc = docmillist;
+		docmillist = mtd;
+		mtd->size  = this->totlen;
+		add_mtd_device(mtd);
+		return;
+	}
+}
+
+static int doc_read (struct mtd_info *mtd, loff_t from, size_t len,
+		     size_t *retlen, u_char *buf)
+{
+	/* Just a special case of doc_read_ecc */
+	return doc_read_ecc(mtd, from, len, retlen, buf, NULL, NULL);
+}
+
+static int doc_read_ecc (struct mtd_info *mtd, loff_t from, size_t len,
+			 size_t *retlen, u_char *buf, u_char *eccbuf,
+			 struct nand_oobinfo *oobsel)
+{
+	int i, ret;
+	volatile char dummy;
+	unsigned char syndrome[6];
+	struct DiskOnChip *this = mtd->priv;
+	void __iomem *docptr = this->virtadr;
+	struct Nand *mychip = &this->chips[from >> (this->chipshift)];
+
+	/* Don't allow read past end of device */
+	if (from >= this->totlen)
+		return -EINVAL;
+
+	/* Don't allow a single read to cross a 512-byte block boundary */
+	if (from + len > ((from | 0x1ff) + 1)) 
+		len = ((from | 0x1ff) + 1) - from;
+
+	/* Find the chip which is to be used and select it */
+	if (this->curfloor != mychip->floor) {
+		DoC_SelectFloor(docptr, mychip->floor);
+		DoC_SelectChip(docptr, mychip->chip);
+	} else if (this->curchip != mychip->chip) {
+		DoC_SelectChip(docptr, mychip->chip);
+	}
+	this->curfloor = mychip->floor;
+	this->curchip = mychip->chip;
+
+	/* issue the Read0 or Read1 command depend on which half of the page
+	   we are accessing. Polling the Flash Ready bit after issue 3 bytes
+	   address in Sequence Read Mode, see Software Requirement 11.4 item 1.*/
+	DoC_Command(docptr, (from >> 8) & 1, CDSN_CTRL_WP);
+	DoC_Address(docptr, 3, from, CDSN_CTRL_WP, 0x00);
+	DoC_WaitReady(docptr);
+
+	if (eccbuf) {
+		/* init the ECC engine, see Reed-Solomon EDC/ECC 11.1 .*/
+		WriteDOC (DOC_ECC_RESET, docptr, ECCConf);
+		WriteDOC (DOC_ECC_EN, docptr, ECCConf);
+	} else {
+		/* disable the ECC engine */
+		WriteDOC (DOC_ECC_RESET, docptr, ECCConf);
+		WriteDOC (DOC_ECC_DIS, docptr, ECCConf);
+	}
+
+	/* Read the data via the internal pipeline through CDSN IO register,
+	   see Pipelined Read Operations 11.3 */
+	dummy = ReadDOC(docptr, ReadPipeInit);
+#ifndef USE_MEMCPY
+	for (i = 0; i < len-1; i++) {
+		/* N.B. you have to increase the source address in this way or the
+		   ECC logic will not work properly */
+		buf[i] = ReadDOC(docptr, Mil_CDSN_IO + (i & 0xff));
+	}
+#else
+	memcpy_fromio(buf, docptr + DoC_Mil_CDSN_IO, len - 1);
+#endif
+	buf[len - 1] = ReadDOC(docptr, LastDataRead);
+
+	/* Let the caller know we completed it */
+	*retlen = len;
+        ret = 0;
+
+	if (eccbuf) {
+		/* Read the ECC data from Spare Data Area,
+		   see Reed-Solomon EDC/ECC 11.1 */
+		dummy = ReadDOC(docptr, ReadPipeInit);
+#ifndef USE_MEMCPY
+		for (i = 0; i < 5; i++) {
+			/* N.B. you have to increase the source address in this way or the
+			   ECC logic will not work properly */
+			eccbuf[i] = ReadDOC(docptr, Mil_CDSN_IO + i);
+		}
+#else
+		memcpy_fromio(eccbuf, docptr + DoC_Mil_CDSN_IO, 5);
+#endif
+		eccbuf[5] = ReadDOC(docptr, LastDataRead);
+
+		/* Flush the pipeline */
+		dummy = ReadDOC(docptr, ECCConf);
+		dummy = ReadDOC(docptr, ECCConf);
+
+		/* Check the ECC Status */
+		if (ReadDOC(docptr, ECCConf) & 0x80) {
+                        int nb_errors;
+			/* There was an ECC error */
+#ifdef ECC_DEBUG
+			printk("DiskOnChip ECC Error: Read at %lx\n", (long)from);
+#endif
+			/* Read the ECC syndrom through the DiskOnChip ECC logic.
+			   These syndrome will be all ZERO when there is no error */
+			for (i = 0; i < 6; i++) {
+				syndrome[i] = ReadDOC(docptr, ECCSyndrome0 + i);
+			}
+                        nb_errors = doc_decode_ecc(buf, syndrome);
+#ifdef ECC_DEBUG
+			printk("ECC Errors corrected: %x\n", nb_errors);
+#endif
+                        if (nb_errors < 0) {
+				/* We return error, but have actually done the read. Not that
+				   this can be told to user-space, via sys_read(), but at least
+				   MTD-aware stuff can know about it by checking *retlen */
+				ret = -EIO;
+                        }
+		}
+
+#ifdef PSYCHO_DEBUG
+		printk("ECC DATA at %lx: %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n",
+		       (long)from, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3],
+		       eccbuf[4], eccbuf[5]);
+#endif
+
+		/* disable the ECC engine */
+		WriteDOC(DOC_ECC_DIS, docptr , ECCConf);
+	}
+
+	return ret;
+}
+
+static int doc_write (struct mtd_info *mtd, loff_t to, size_t len,
+		      size_t *retlen, const u_char *buf)
+{
+	char eccbuf[6];
+	return doc_write_ecc(mtd, to, len, retlen, buf, eccbuf, NULL);
+}
+
+static int doc_write_ecc (struct mtd_info *mtd, loff_t to, size_t len,
+			  size_t *retlen, const u_char *buf, u_char *eccbuf,
+			 struct nand_oobinfo *oobsel)
+{
+	int i,ret = 0;
+	volatile char dummy;
+	struct DiskOnChip *this = mtd->priv;
+	void __iomem *docptr = this->virtadr;
+	struct Nand *mychip = &this->chips[to >> (this->chipshift)];
+
+	/* Don't allow write past end of device */
+	if (to >= this->totlen)
+		return -EINVAL;
+
+#if 0
+	/* Don't allow a single write to cross a 512-byte block boundary */
+	if (to + len > ( (to | 0x1ff) + 1)) 
+		len = ((to | 0x1ff) + 1) - to;
+#else
+	/* Don't allow writes which aren't exactly one block */
+	if (to & 0x1ff || len != 0x200)
+		return -EINVAL;
+#endif
+
+	/* Find the chip which is to be used and select it */
+	if (this->curfloor != mychip->floor) {
+		DoC_SelectFloor(docptr, mychip->floor);
+		DoC_SelectChip(docptr, mychip->chip);
+	} else if (this->curchip != mychip->chip) {
+		DoC_SelectChip(docptr, mychip->chip);
+	}
+	this->curfloor = mychip->floor;
+	this->curchip = mychip->chip;
+
+	/* Reset the chip, see Software Requirement 11.4 item 1. */
+	DoC_Command(docptr, NAND_CMD_RESET, 0x00);
+	DoC_WaitReady(docptr);
+	/* Set device to main plane of flash */
+	DoC_Command(docptr, NAND_CMD_READ0, 0x00);
+
+	/* issue the Serial Data In command to initial the Page Program process */
+	DoC_Command(docptr, NAND_CMD_SEQIN, 0x00);
+	DoC_Address(docptr, 3, to, 0x00, 0x00);
+	DoC_WaitReady(docptr);
+
+	if (eccbuf) {
+		/* init the ECC engine, see Reed-Solomon EDC/ECC 11.1 .*/
+		WriteDOC (DOC_ECC_RESET, docptr, ECCConf);
+		WriteDOC (DOC_ECC_EN | DOC_ECC_RW, docptr, ECCConf);
+	} else {
+		/* disable the ECC engine */
+		WriteDOC (DOC_ECC_RESET, docptr, ECCConf);
+		WriteDOC (DOC_ECC_DIS, docptr, ECCConf);
+	}
+
+	/* Write the data via the internal pipeline through CDSN IO register,
+	   see Pipelined Write Operations 11.2 */
+#ifndef USE_MEMCPY
+	for (i = 0; i < len; i++) {
+		/* N.B. you have to increase the source address in this way or the
+		   ECC logic will not work properly */
+		WriteDOC(buf[i], docptr, Mil_CDSN_IO + i);
+	}
+#else
+	memcpy_toio(docptr + DoC_Mil_CDSN_IO, buf, len);
+#endif
+	WriteDOC(0x00, docptr, WritePipeTerm);
+
+	if (eccbuf) {
+		/* Write ECC data to flash, the ECC info is generated by the DiskOnChip ECC logic
+		   see Reed-Solomon EDC/ECC 11.1 */
+		WriteDOC(0, docptr, NOP);
+		WriteDOC(0, docptr, NOP);
+		WriteDOC(0, docptr, NOP);
+
+		/* Read the ECC data through the DiskOnChip ECC logic */
+		for (i = 0; i < 6; i++) {
+			eccbuf[i] = ReadDOC(docptr, ECCSyndrome0 + i);
+		}
+
+		/* ignore the ECC engine */
+		WriteDOC(DOC_ECC_DIS, docptr , ECCConf);
+
+#ifndef USE_MEMCPY
+		/* Write the ECC data to flash */
+		for (i = 0; i < 6; i++) {
+			/* N.B. you have to increase the source address in this way or the
+			   ECC logic will not work properly */
+			WriteDOC(eccbuf[i], docptr, Mil_CDSN_IO + i);
+		}
+#else
+		memcpy_toio(docptr + DoC_Mil_CDSN_IO, eccbuf, 6);
+#endif
+
+		/* write the block status BLOCK_USED (0x5555) at the end of ECC data
+		   FIXME: this is only a hack for programming the IPL area for LinuxBIOS
+		   and should be replace with proper codes in user space utilities */ 
+		WriteDOC(0x55, docptr, Mil_CDSN_IO);
+		WriteDOC(0x55, docptr, Mil_CDSN_IO + 1);
+
+		WriteDOC(0x00, docptr, WritePipeTerm);
+
+#ifdef PSYCHO_DEBUG
+		printk("OOB data at %lx is %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n",
+		       (long) to, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3],
+		       eccbuf[4], eccbuf[5]);
+#endif
+	}
+
+	/* Commit the Page Program command and wait for ready
+	   see Software Requirement 11.4 item 1.*/
+	DoC_Command(docptr, NAND_CMD_PAGEPROG, 0x00);
+	DoC_WaitReady(docptr);
+
+	/* Read the status of the flash device through CDSN IO register
+	   see Software Requirement 11.4 item 5.*/
+	DoC_Command(docptr, NAND_CMD_STATUS, CDSN_CTRL_WP);
+	dummy = ReadDOC(docptr, ReadPipeInit);
+	DoC_Delay(docptr, 2);
+	if (ReadDOC(docptr, Mil_CDSN_IO) & 1) {
+		printk("Error programming flash\n");
+		/* Error in programming
+		   FIXME: implement Bad Block Replacement (in nftl.c ??) */
+		*retlen = 0;
+		ret = -EIO;
+	}
+	dummy = ReadDOC(docptr, LastDataRead);
+
+	/* Let the caller know we completed it */
+	*retlen = len;
+
+	return ret;
+}
+
+static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+			size_t *retlen, u_char *buf)
+{
+#ifndef USE_MEMCPY
+	int i;
+#endif
+	volatile char dummy;
+	struct DiskOnChip *this = mtd->priv;
+	void __iomem *docptr = this->virtadr;
+	struct Nand *mychip = &this->chips[ofs >> this->chipshift];
+
+	/* Find the chip which is to be used and select it */
+	if (this->curfloor != mychip->floor) {
+		DoC_SelectFloor(docptr, mychip->floor);
+		DoC_SelectChip(docptr, mychip->chip);
+	} else if (this->curchip != mychip->chip) {
+		DoC_SelectChip(docptr, mychip->chip);
+	}
+	this->curfloor = mychip->floor;
+	this->curchip = mychip->chip;
+
+	/* disable the ECC engine */
+	WriteDOC (DOC_ECC_RESET, docptr, ECCConf);
+	WriteDOC (DOC_ECC_DIS, docptr, ECCConf);
+
+	/* issue the Read2 command to set the pointer to the Spare Data Area.
+	   Polling the Flash Ready bit after issue 3 bytes address in
+	   Sequence Read Mode, see Software Requirement 11.4 item 1.*/
+	DoC_Command(docptr, NAND_CMD_READOOB, CDSN_CTRL_WP);
+	DoC_Address(docptr, 3, ofs, CDSN_CTRL_WP, 0x00);
+	DoC_WaitReady(docptr);
+
+	/* Read the data out via the internal pipeline through CDSN IO register,
+	   see Pipelined Read Operations 11.3 */
+	dummy = ReadDOC(docptr, ReadPipeInit);
+#ifndef USE_MEMCPY
+	for (i = 0; i < len-1; i++) {
+		/* N.B. you have to increase the source address in this way or the
+		   ECC logic will not work properly */
+		buf[i] = ReadDOC(docptr, Mil_CDSN_IO + i);
+	}
+#else
+	memcpy_fromio(buf, docptr + DoC_Mil_CDSN_IO, len - 1);
+#endif
+	buf[len - 1] = ReadDOC(docptr, LastDataRead);
+
+	*retlen = len;
+
+	return 0;
+}
+
+static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+			 size_t *retlen, const u_char *buf)
+{
+#ifndef USE_MEMCPY
+	int i;
+#endif
+	volatile char dummy;
+	int ret = 0;
+	struct DiskOnChip *this = mtd->priv;
+	void __iomem *docptr = this->virtadr;
+	struct Nand *mychip = &this->chips[ofs >> this->chipshift];
+
+	/* Find the chip which is to be used and select it */
+	if (this->curfloor != mychip->floor) {
+		DoC_SelectFloor(docptr, mychip->floor);
+		DoC_SelectChip(docptr, mychip->chip);
+	} else if (this->curchip != mychip->chip) {
+		DoC_SelectChip(docptr, mychip->chip);
+	}
+	this->curfloor = mychip->floor;
+	this->curchip = mychip->chip;
+
+	/* disable the ECC engine */
+	WriteDOC (DOC_ECC_RESET, docptr, ECCConf);
+	WriteDOC (DOC_ECC_DIS, docptr, ECCConf);
+
+	/* Reset the chip, see Software Requirement 11.4 item 1. */
+	DoC_Command(docptr, NAND_CMD_RESET, CDSN_CTRL_WP);
+	DoC_WaitReady(docptr);
+	/* issue the Read2 command to set the pointer to the Spare Data Area. */
+	DoC_Command(docptr, NAND_CMD_READOOB, CDSN_CTRL_WP);
+
+	/* issue the Serial Data In command to initial the Page Program process */
+	DoC_Command(docptr, NAND_CMD_SEQIN, 0x00);
+	DoC_Address(docptr, 3, ofs, 0x00, 0x00);
+
+	/* Write the data via the internal pipeline through CDSN IO register,
+	   see Pipelined Write Operations 11.2 */
+#ifndef USE_MEMCPY
+	for (i = 0; i < len; i++) {
+		/* N.B. you have to increase the source address in this way or the
+		   ECC logic will not work properly */
+		WriteDOC(buf[i], docptr, Mil_CDSN_IO + i);
+	}
+#else
+	memcpy_toio(docptr + DoC_Mil_CDSN_IO, buf, len);
+#endif
+	WriteDOC(0x00, docptr, WritePipeTerm);
+
+	/* Commit the Page Program command and wait for ready
+	   see Software Requirement 11.4 item 1.*/
+	DoC_Command(docptr, NAND_CMD_PAGEPROG, 0x00);
+	DoC_WaitReady(docptr);
+
+	/* Read the status of the flash device through CDSN IO register
+	   see Software Requirement 11.4 item 5.*/
+	DoC_Command(docptr, NAND_CMD_STATUS, 0x00);
+	dummy = ReadDOC(docptr, ReadPipeInit);
+	DoC_Delay(docptr, 2);
+	if (ReadDOC(docptr, Mil_CDSN_IO) & 1) {
+		printk("Error programming oob data\n");
+		/* FIXME: implement Bad Block Replacement (in nftl.c ??) */
+		*retlen = 0;
+		ret = -EIO;
+	}
+	dummy = ReadDOC(docptr, LastDataRead);
+
+	*retlen = len;
+
+	return ret;
+}
+
+int doc_erase (struct mtd_info *mtd, struct erase_info *instr)
+{
+	volatile char dummy;
+	struct DiskOnChip *this = mtd->priv;
+	__u32 ofs = instr->addr;
+	__u32 len = instr->len;
+	void __iomem *docptr = this->virtadr;
+	struct Nand *mychip = &this->chips[ofs >> this->chipshift];
+
+	if (len != mtd->erasesize) 
+		printk(KERN_WARNING "Erase not right size (%x != %x)n",
+		       len, mtd->erasesize);
+
+	/* Find the chip which is to be used and select it */
+	if (this->curfloor != mychip->floor) {
+		DoC_SelectFloor(docptr, mychip->floor);
+		DoC_SelectChip(docptr, mychip->chip);
+	} else if (this->curchip != mychip->chip) {
+		DoC_SelectChip(docptr, mychip->chip);
+	}
+	this->curfloor = mychip->floor;
+	this->curchip = mychip->chip;
+
+	instr->state = MTD_ERASE_PENDING;
+
+	/* issue the Erase Setup command */
+	DoC_Command(docptr, NAND_CMD_ERASE1, 0x00);
+	DoC_Address(docptr, 2, ofs, 0x00, 0x00);
+
+	/* Commit the Erase Start command and wait for ready
+	   see Software Requirement 11.4 item 1.*/
+	DoC_Command(docptr, NAND_CMD_ERASE2, 0x00);
+	DoC_WaitReady(docptr);
+
+	instr->state = MTD_ERASING;
+
+	/* Read the status of the flash device through CDSN IO register
+	   see Software Requirement 11.4 item 5.
+	   FIXME: it seems that we are not wait long enough, some blocks are not
+	   erased fully */
+	DoC_Command(docptr, NAND_CMD_STATUS, CDSN_CTRL_WP);
+	dummy = ReadDOC(docptr, ReadPipeInit);
+	DoC_Delay(docptr, 2);
+	if (ReadDOC(docptr, Mil_CDSN_IO) & 1) {
+		printk("Error Erasing at 0x%x\n", ofs);
+		/* There was an error
+		   FIXME: implement Bad Block Replacement (in nftl.c ??) */
+		instr->state = MTD_ERASE_FAILED;
+	} else
+		instr->state = MTD_ERASE_DONE;
+	dummy = ReadDOC(docptr, LastDataRead);
+
+	mtd_erase_callback(instr);
+
+	return 0;
+}
+
+/****************************************************************************
+ *
+ * Module stuff
+ *
+ ****************************************************************************/
+
+static int __init init_doc2001(void)
+{
+	inter_module_register(im_name, THIS_MODULE, &DoCMil_init);
+	return 0;
+}
+
+static void __exit cleanup_doc2001(void)
+{
+	struct mtd_info *mtd;
+	struct DiskOnChip *this;
+
+	while ((mtd=docmillist)) {
+		this = mtd->priv;
+		docmillist = this->nextdoc;
+			
+		del_mtd_device(mtd);
+			
+		iounmap(this->virtadr);
+		kfree(this->chips);
+		kfree(mtd);
+	}
+	inter_module_unregister(im_name);
+}
+
+module_exit(cleanup_doc2001);
+module_init(init_doc2001);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org> et al.");
+MODULE_DESCRIPTION("Alternative driver for DiskOnChip Millennium");
diff --git a/drivers/mtd/devices/doc2001plus.c b/drivers/mtd/devices/doc2001plus.c
new file mode 100644
index 0000000..ed47baf
--- /dev/null
+++ b/drivers/mtd/devices/doc2001plus.c
@@ -0,0 +1,1154 @@
+/*
+ * Linux driver for Disk-On-Chip Millennium Plus
+ *
+ * (c) 2002-2003 Greg Ungerer <gerg@snapgear.com>
+ * (c) 2002-2003 SnapGear Inc
+ * (c) 1999 Machine Vision Holdings, Inc.
+ * (c) 1999, 2000 David Woodhouse <dwmw2@infradead.org>
+ *
+ * $Id: doc2001plus.c,v 1.13 2005/01/05 18:05:12 dwmw2 Exp $
+ *
+ * Released under GPL
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/miscdevice.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/bitops.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/doc2000.h>
+
+/* #define ECC_DEBUG */
+
+/* I have no idea why some DoC chips can not use memcop_form|to_io().
+ * This may be due to the different revisions of the ASIC controller built-in or
+ * simplily a QA/Bug issue. Who knows ?? If you have trouble, please uncomment
+ * this:*/
+#undef USE_MEMCPY
+
+static int doc_read(struct mtd_info *mtd, loff_t from, size_t len,
+		size_t *retlen, u_char *buf);
+static int doc_write(struct mtd_info *mtd, loff_t to, size_t len,
+		size_t *retlen, const u_char *buf);
+static int doc_read_ecc(struct mtd_info *mtd, loff_t from, size_t len,
+		size_t *retlen, u_char *buf, u_char *eccbuf,
+		struct nand_oobinfo *oobsel);
+static int doc_write_ecc(struct mtd_info *mtd, loff_t to, size_t len,
+		size_t *retlen, const u_char *buf, u_char *eccbuf,
+		struct nand_oobinfo *oobsel);
+static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+		size_t *retlen, u_char *buf);
+static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+		size_t *retlen, const u_char *buf);
+static int doc_erase (struct mtd_info *mtd, struct erase_info *instr);
+
+static struct mtd_info *docmilpluslist = NULL;
+
+
+/* Perform the required delay cycles by writing to the NOP register */
+static void DoC_Delay(void __iomem * docptr, int cycles)
+{
+	int i;
+
+	for (i = 0; (i < cycles); i++)
+		WriteDOC(0, docptr, Mplus_NOP);
+}
+
+#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(void __iomem * docptr)
+{
+	unsigned int c = 0xffff;
+
+	DEBUG(MTD_DEBUG_LEVEL3,
+	      "_DoC_WaitReady called for out-of-line wait\n");
+
+	/* Out-of-line routine to wait for chip response */
+	while (((ReadDOC(docptr, Mplus_FlashControl) & CDSN_CTRL_FR_B_MASK) != CDSN_CTRL_FR_B_MASK) && --c)
+		;
+
+	if (c == 0)
+		DEBUG(MTD_DEBUG_LEVEL2, "_DoC_WaitReady timed out.\n");
+
+	return (c == 0);
+}
+
+static inline int DoC_WaitReady(void __iomem * docptr)
+{
+	/* This is inline, to optimise the common case, where it's ready instantly */
+	int ret = 0;
+
+	/* read form NOP register should be issued prior to the read from CDSNControl
+	   see Software Requirement 11.4 item 2. */
+	DoC_Delay(docptr, 4);
+
+	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(docptr);
+
+	return ret;
+}
+
+/* For some reason the Millennium Plus seems to occassionally put itself
+ * into reset mode. For me this happens randomly, with no pattern that I
+ * can detect. M-systems suggest always check this on any block level
+ * operation and setting to normal mode if in reset mode.
+ */
+static inline void DoC_CheckASIC(void __iomem * docptr)
+{
+	/* Make sure the DoC is in normal mode */
+	if ((ReadDOC(docptr, Mplus_DOCControl) & DOC_MODE_NORMAL) == 0) {
+		WriteDOC((DOC_MODE_NORMAL | DOC_MODE_MDWREN), docptr, Mplus_DOCControl);
+		WriteDOC(~(DOC_MODE_NORMAL | DOC_MODE_MDWREN), docptr, Mplus_CtrlConfirm);
+	}
+}
+
+/* DoC_Command: Send a flash command to the flash chip through the Flash
+ * command register. Need 2 Write Pipeline Terminates to complete send.
+ */
+static inline void DoC_Command(void __iomem * docptr, unsigned char command,
+			       unsigned char xtraflags)
+{
+	WriteDOC(command, docptr, Mplus_FlashCmd);
+	WriteDOC(command, docptr, Mplus_WritePipeTerm);
+	WriteDOC(command, docptr, Mplus_WritePipeTerm);
+}
+
+/* DoC_Address: Set the current address for the flash chip through the Flash
+ * Address register. Need 2 Write Pipeline Terminates to complete send.
+ */
+static inline void DoC_Address(struct DiskOnChip *doc, int numbytes,
+			       unsigned long ofs, unsigned char xtraflags1,
+			       unsigned char xtraflags2)
+{
+	void __iomem * docptr = doc->virtadr;
+
+	/* Allow for possible Mill Plus internal flash interleaving */
+	ofs >>= doc->interleave;
+
+	switch (numbytes) {
+	case 1:
+		/* Send single byte, bits 0-7. */
+		WriteDOC(ofs & 0xff, docptr, Mplus_FlashAddress);
+		break;
+	case 2:
+		/* Send bits 9-16 followed by 17-23 */
+		WriteDOC((ofs >> 9)  & 0xff, docptr, Mplus_FlashAddress);
+		WriteDOC((ofs >> 17) & 0xff, docptr, Mplus_FlashAddress);
+		break;
+	case 3:
+		/* Send 0-7, 9-16, then 17-23 */
+		WriteDOC(ofs & 0xff, docptr, Mplus_FlashAddress);
+		WriteDOC((ofs >> 9)  & 0xff, docptr, Mplus_FlashAddress);
+		WriteDOC((ofs >> 17) & 0xff, docptr, Mplus_FlashAddress);
+		break;
+	default:
+		return;
+	}
+
+	WriteDOC(0x00, docptr, Mplus_WritePipeTerm);
+	WriteDOC(0x00, docptr, Mplus_WritePipeTerm);
+}
+
+/* DoC_SelectChip: Select a given flash chip within the current floor */
+static int DoC_SelectChip(void __iomem * docptr, int chip)
+{
+	/* No choice for flash chip on Millennium Plus */
+	return 0;
+}
+
+/* DoC_SelectFloor: Select a given floor (bank of flash chips) */
+static int DoC_SelectFloor(void __iomem * docptr, int floor)
+{
+	WriteDOC((floor & 0x3), docptr, Mplus_DeviceSelect);
+	return 0;
+}
+
+/*
+ * Translate the given offset into the appropriate command and offset.
+ * This does the mapping using the 16bit interleave layout defined by
+ * M-Systems, and looks like this for a sector pair:
+ *  +-----------+-------+-------+-------+--------------+---------+-----------+
+ *  | 0 --- 511 |512-517|518-519|520-521| 522 --- 1033 |1034-1039|1040 - 1055|
+ *  +-----------+-------+-------+-------+--------------+---------+-----------+
+ *  | Data 0    | ECC 0 |Flags0 |Flags1 | Data 1       |ECC 1    | OOB 1 + 2 |
+ *  +-----------+-------+-------+-------+--------------+---------+-----------+
+ */
+/* FIXME: This lives in INFTL not here. Other users of flash devices
+   may not want it */
+static unsigned int DoC_GetDataOffset(struct mtd_info *mtd, loff_t *from)
+{
+	struct DiskOnChip *this = mtd->priv;
+
+	if (this->interleave) {
+		unsigned int ofs = *from & 0x3ff;
+		unsigned int cmd;
+
+		if (ofs < 512) {
+			cmd = NAND_CMD_READ0;
+			ofs &= 0x1ff;
+		} else if (ofs < 1014) {
+			cmd = NAND_CMD_READ1;
+			ofs = (ofs & 0x1ff) + 10;
+		} else {
+			cmd = NAND_CMD_READOOB;
+			ofs = ofs - 1014;
+		}
+
+		*from = (*from & ~0x3ff) | ofs;
+		return cmd;
+	} else {
+		/* No interleave */
+		if ((*from) & 0x100)
+			return NAND_CMD_READ1;
+		return NAND_CMD_READ0;
+	}
+}
+
+static unsigned int DoC_GetECCOffset(struct mtd_info *mtd, loff_t *from)
+{
+	unsigned int ofs, cmd;
+
+	if (*from & 0x200) {
+		cmd = NAND_CMD_READOOB;
+		ofs = 10 + (*from & 0xf);
+	} else {
+		cmd = NAND_CMD_READ1;
+		ofs = (*from & 0xf);
+	}
+
+	*from = (*from & ~0x3ff) | ofs;
+	return cmd;
+}
+
+static unsigned int DoC_GetFlagsOffset(struct mtd_info *mtd, loff_t *from)
+{
+	unsigned int ofs, cmd;
+
+	cmd = NAND_CMD_READ1;
+	ofs = (*from & 0x200) ? 8 : 6;
+	*from = (*from & ~0x3ff) | ofs;
+	return cmd;
+}
+
+static unsigned int DoC_GetHdrOffset(struct mtd_info *mtd, loff_t *from)
+{
+	unsigned int ofs, cmd;
+
+	cmd = NAND_CMD_READOOB;
+	ofs = (*from & 0x200) ? 24 : 16;
+	*from = (*from & ~0x3ff) | ofs;
+	return cmd;
+}
+
+static inline void MemReadDOC(void __iomem * docptr, unsigned char *buf, int len)
+{
+#ifndef USE_MEMCPY
+	int i;
+	for (i = 0; i < len; i++)
+		buf[i] = ReadDOC(docptr, Mil_CDSN_IO + i);
+#else
+	memcpy_fromio(buf, docptr + DoC_Mil_CDSN_IO, len);
+#endif
+}
+
+static inline void MemWriteDOC(void __iomem * docptr, unsigned char *buf, int len)
+{
+#ifndef USE_MEMCPY
+	int i;
+	for (i = 0; i < len; i++)
+		WriteDOC(buf[i], docptr, Mil_CDSN_IO + i);
+#else
+	memcpy_toio(docptr + DoC_Mil_CDSN_IO, buf, len);
+#endif
+}
+
+/* DoC_IdentChip: Identify a given NAND chip given {floor,chip} */
+static int DoC_IdentChip(struct DiskOnChip *doc, int floor, int chip)
+{
+	int mfr, id, i, j;
+	volatile char dummy;
+	void __iomem * docptr = doc->virtadr;
+
+	/* Page in the required floor/chip */
+	DoC_SelectFloor(docptr, floor);
+	DoC_SelectChip(docptr, chip);
+
+	/* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */
+	WriteDOC((DOC_FLASH_CE | DOC_FLASH_WP), docptr, Mplus_FlashSelect);
+
+	/* Reset the chip, see Software Requirement 11.4 item 1. */
+	DoC_Command(docptr, NAND_CMD_RESET, 0);
+	DoC_WaitReady(docptr);
+
+	/* Read the NAND chip ID: 1. Send ReadID command */ 
+	DoC_Command(docptr, NAND_CMD_READID, 0);
+
+	/* Read the NAND chip ID: 2. Send address byte zero */ 
+	DoC_Address(doc, 1, 0x00, 0, 0x00);
+
+	WriteDOC(0, docptr, Mplus_FlashControl);
+	DoC_WaitReady(docptr);
+
+	/* Read the manufacturer and device id codes of the flash device through
+	   CDSN IO register see Software Requirement 11.4 item 5.*/
+	dummy = ReadDOC(docptr, Mplus_ReadPipeInit);
+	dummy = ReadDOC(docptr, Mplus_ReadPipeInit);
+
+	mfr = ReadDOC(docptr, Mil_CDSN_IO);
+	if (doc->interleave)
+		dummy = ReadDOC(docptr, Mil_CDSN_IO); /* 2 way interleave */
+
+	id  = ReadDOC(docptr, Mil_CDSN_IO);
+	if (doc->interleave)
+		dummy = ReadDOC(docptr, Mil_CDSN_IO); /* 2 way interleave */
+
+	dummy = ReadDOC(docptr, Mplus_LastDataRead);
+	dummy = ReadDOC(docptr, Mplus_LastDataRead);
+
+	/* Disable flash internally */
+	WriteDOC(0, docptr, Mplus_FlashSelect);
+
+	/* No response - return failure */
+	if (mfr == 0xff || mfr == 0)
+		return 0;
+
+	for (i = 0; nand_flash_ids[i].name != NULL; i++) {
+		if (id == nand_flash_ids[i].id) {
+			/* Try to identify manufacturer */
+			for (j = 0; nand_manuf_ids[j].id != 0x0; j++) {
+				if (nand_manuf_ids[j].id == mfr)
+					break;
+			}
+			printk(KERN_INFO "Flash chip found: Manufacturer ID: %2.2X, "
+			       "Chip ID: %2.2X (%s:%s)\n", mfr, id,
+			       nand_manuf_ids[j].name, nand_flash_ids[i].name);
+			doc->mfr = mfr;
+			doc->id = id;
+			doc->chipshift = ffs((nand_flash_ids[i].chipsize << 20)) - 1;
+			doc->erasesize = nand_flash_ids[i].erasesize << doc->interleave;
+			break;
+		}
+	}
+
+	if (nand_flash_ids[i].name == NULL)
+		return 0;
+	return 1;
+}
+
+/* DoC_ScanChips: Find all NAND chips present in a DiskOnChip, and identify them */
+static void DoC_ScanChips(struct DiskOnChip *this)
+{
+	int floor, chip;
+	int numchips[MAX_FLOORS_MPLUS];
+	int ret;
+
+	this->numchips = 0;
+	this->mfr = 0;
+	this->id = 0;
+
+	/* Work out the intended interleave setting */
+	this->interleave = 0;
+	if (this->ChipID == DOC_ChipID_DocMilPlus32)
+		this->interleave = 1;
+
+	/* Check the ASIC agrees */
+	if ( (this->interleave << 2) != 
+	     (ReadDOC(this->virtadr, Mplus_Configuration) & 4)) {
+		u_char conf = ReadDOC(this->virtadr, Mplus_Configuration);
+		printk(KERN_NOTICE "Setting DiskOnChip Millennium Plus interleave to %s\n",
+		       this->interleave?"on (16-bit)":"off (8-bit)");
+		conf ^= 4;
+		WriteDOC(conf, this->virtadr, Mplus_Configuration);
+	}
+
+	/* For each floor, find the number of valid chips it contains */
+	for (floor = 0,ret = 1; floor < MAX_FLOORS_MPLUS; floor++) {
+		numchips[floor] = 0;
+		for (chip = 0; chip < MAX_CHIPS_MPLUS && ret != 0; chip++) {
+			ret = DoC_IdentChip(this, floor, chip);
+			if (ret) {
+				numchips[floor]++;
+				this->numchips++;
+			}
+		}
+	}
+	/* If there are none at all that we recognise, bail */
+	if (!this->numchips) {
+		printk("No flash chips recognised.\n");
+		return;
+	}
+
+	/* Allocate an array to hold the information for each chip */
+	this->chips = kmalloc(sizeof(struct Nand) * this->numchips, GFP_KERNEL);
+	if (!this->chips){
+		printk("MTD: No memory for allocating chip info structures\n");
+		return;
+	}
+
+	/* Fill out the chip array with {floor, chipno} for each 
+	 * detected chip in the device. */
+	for (floor = 0, ret = 0; floor < MAX_FLOORS_MPLUS; floor++) {
+		for (chip = 0 ; chip < numchips[floor] ; chip++) {
+			this->chips[ret].floor = floor;
+			this->chips[ret].chip = chip;
+			this->chips[ret].curadr = 0;
+			this->chips[ret].curmode = 0x50;
+			ret++;
+		}
+	}
+
+	/* Calculate and print the total size of the device */
+	this->totlen = this->numchips * (1 << this->chipshift);
+	printk(KERN_INFO "%d flash chips found. Total DiskOnChip size: %ld MiB\n",
+	       this->numchips ,this->totlen >> 20);
+}
+
+static int DoCMilPlus_is_alias(struct DiskOnChip *doc1, struct DiskOnChip *doc2)
+{
+	int tmp1, tmp2, retval;
+
+	if (doc1->physadr == doc2->physadr)
+		return 1;
+
+	/* Use the alias resolution register which was set aside for this
+	 * purpose. If it's value is the same on both chips, they might
+	 * be the same chip, and we write to one and check for a change in
+	 * the other. It's unclear if this register is usuable in the
+	 * DoC 2000 (it's in the Millennium docs), but it seems to work. */
+	tmp1 = ReadDOC(doc1->virtadr, Mplus_AliasResolution);
+	tmp2 = ReadDOC(doc2->virtadr, Mplus_AliasResolution);
+	if (tmp1 != tmp2)
+		return 0;
+	
+	WriteDOC((tmp1+1) % 0xff, doc1->virtadr, Mplus_AliasResolution);
+	tmp2 = ReadDOC(doc2->virtadr, Mplus_AliasResolution);
+	if (tmp2 == (tmp1+1) % 0xff)
+		retval = 1;
+	else
+		retval = 0;
+
+	/* Restore register contents.  May not be necessary, but do it just to
+	 * be safe. */
+	WriteDOC(tmp1, doc1->virtadr, Mplus_AliasResolution);
+
+	return retval;
+}
+
+static const char im_name[] = "DoCMilPlus_init";
+
+/* This routine is made available to other mtd code via
+ * inter_module_register.  It must only be accessed through
+ * inter_module_get which will bump the use count of this module.  The
+ * addresses passed back in mtd are valid as long as the use count of
+ * this module is non-zero, i.e. between inter_module_get and
+ * inter_module_put.  Keith Owens <kaos@ocs.com.au> 29 Oct 2000.
+ */
+static void DoCMilPlus_init(struct mtd_info *mtd)
+{
+	struct DiskOnChip *this = mtd->priv;
+	struct DiskOnChip *old = NULL;
+
+	/* We must avoid being called twice for the same device. */
+	if (docmilpluslist)
+		old = docmilpluslist->priv;
+
+	while (old) {
+		if (DoCMilPlus_is_alias(this, old)) {
+			printk(KERN_NOTICE "Ignoring DiskOnChip Millennium "
+				"Plus at 0x%lX - already configured\n",
+				this->physadr);
+			iounmap(this->virtadr);
+			kfree(mtd);
+			return;
+		}
+		if (old->nextdoc)
+			old = old->nextdoc->priv;
+		else
+			old = NULL;
+	}
+
+	mtd->name = "DiskOnChip Millennium Plus";
+	printk(KERN_NOTICE "DiskOnChip Millennium Plus found at "
+		"address 0x%lX\n", this->physadr);
+
+	mtd->type = MTD_NANDFLASH;
+	mtd->flags = MTD_CAP_NANDFLASH;
+	mtd->ecctype = MTD_ECC_RS_DiskOnChip;
+	mtd->size = 0;
+
+	mtd->erasesize = 0;
+	mtd->oobblock = 512;
+	mtd->oobsize = 16;
+	mtd->owner = THIS_MODULE;
+	mtd->erase = doc_erase;
+	mtd->point = NULL;
+	mtd->unpoint = NULL;
+	mtd->read = doc_read;
+	mtd->write = doc_write;
+	mtd->read_ecc = doc_read_ecc;
+	mtd->write_ecc = doc_write_ecc;
+	mtd->read_oob = doc_read_oob;
+	mtd->write_oob = doc_write_oob;
+	mtd->sync = NULL;
+
+	this->totlen = 0;
+	this->numchips = 0;
+	this->curfloor = -1;
+	this->curchip = -1;
+
+	/* Ident all the chips present. */
+	DoC_ScanChips(this);
+
+	if (!this->totlen) {
+		kfree(mtd);
+		iounmap(this->virtadr);
+	} else {
+		this->nextdoc = docmilpluslist;
+		docmilpluslist = mtd;
+		mtd->size  = this->totlen;
+		mtd->erasesize = this->erasesize;
+		add_mtd_device(mtd);
+		return;
+	}
+}
+
+#if 0
+static int doc_dumpblk(struct mtd_info *mtd, loff_t from)
+{
+	int i;
+	loff_t fofs;
+	struct DiskOnChip *this = mtd->priv;
+	void __iomem * docptr = this->virtadr;
+	struct Nand *mychip = &this->chips[from >> (this->chipshift)];
+	unsigned char *bp, buf[1056];
+	char c[32];
+
+	from &= ~0x3ff;
+
+	/* Don't allow read past end of device */
+	if (from >= this->totlen)
+		return -EINVAL;
+
+	DoC_CheckASIC(docptr);
+
+	/* Find the chip which is to be used and select it */
+	if (this->curfloor != mychip->floor) {
+		DoC_SelectFloor(docptr, mychip->floor);
+		DoC_SelectChip(docptr, mychip->chip);
+	} else if (this->curchip != mychip->chip) {
+		DoC_SelectChip(docptr, mychip->chip);
+	}
+	this->curfloor = mychip->floor;
+	this->curchip = mychip->chip;
+
+	/* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */
+	WriteDOC((DOC_FLASH_CE | DOC_FLASH_WP), docptr, Mplus_FlashSelect);
+
+	/* Reset the chip, see Software Requirement 11.4 item 1. */
+	DoC_Command(docptr, NAND_CMD_RESET, 0);
+	DoC_WaitReady(docptr);
+
+	fofs = from;
+	DoC_Command(docptr, DoC_GetDataOffset(mtd, &fofs), 0);
+	DoC_Address(this, 3, fofs, 0, 0x00);
+	WriteDOC(0, docptr, Mplus_FlashControl);
+	DoC_WaitReady(docptr);
+
+	/* disable the ECC engine */
+	WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf);
+
+	ReadDOC(docptr, Mplus_ReadPipeInit);
+	ReadDOC(docptr, Mplus_ReadPipeInit);
+
+	/* Read the data via the internal pipeline through CDSN IO
+	   register, see Pipelined Read Operations 11.3 */
+	MemReadDOC(docptr, buf, 1054);
+	buf[1054] = ReadDOC(docptr, Mplus_LastDataRead);
+	buf[1055] = ReadDOC(docptr, Mplus_LastDataRead);
+
+	memset(&c[0], 0, sizeof(c));
+	printk("DUMP OFFSET=%x:\n", (int)from);
+
+        for (i = 0, bp = &buf[0]; (i < 1056); i++) {
+                if ((i % 16) == 0)
+                        printk("%08x: ", i);
+                printk(" %02x", *bp);
+                c[(i & 0xf)] = ((*bp >= 0x20) && (*bp <= 0x7f)) ? *bp : '.';
+                bp++;
+                if (((i + 1) % 16) == 0)
+                        printk("    %s\n", c);
+        }
+	printk("\n");
+
+	/* Disable flash internally */
+	WriteDOC(0, docptr, Mplus_FlashSelect);
+
+	return 0;
+}
+#endif
+
+static int doc_read(struct mtd_info *mtd, loff_t from, size_t len,
+		    size_t *retlen, u_char *buf)
+{
+	/* Just a special case of doc_read_ecc */
+	return doc_read_ecc(mtd, from, len, retlen, buf, NULL, NULL);
+}
+
+static int doc_read_ecc(struct mtd_info *mtd, loff_t from, size_t len,
+			size_t *retlen, u_char *buf, u_char *eccbuf,
+			struct nand_oobinfo *oobsel)
+{
+	int ret, i;
+	volatile char dummy;
+	loff_t fofs;
+	unsigned char syndrome[6];
+	struct DiskOnChip *this = mtd->priv;
+	void __iomem * docptr = this->virtadr;
+	struct Nand *mychip = &this->chips[from >> (this->chipshift)];
+
+	/* Don't allow read past end of device */
+	if (from >= this->totlen)
+		return -EINVAL;
+
+	/* Don't allow a single read to cross a 512-byte block boundary */
+	if (from + len > ((from | 0x1ff) + 1)) 
+		len = ((from | 0x1ff) + 1) - from;
+
+	DoC_CheckASIC(docptr);
+
+	/* Find the chip which is to be used and select it */
+	if (this->curfloor != mychip->floor) {
+		DoC_SelectFloor(docptr, mychip->floor);
+		DoC_SelectChip(docptr, mychip->chip);
+	} else if (this->curchip != mychip->chip) {
+		DoC_SelectChip(docptr, mychip->chip);
+	}
+	this->curfloor = mychip->floor;
+	this->curchip = mychip->chip;
+
+	/* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */
+	WriteDOC((DOC_FLASH_CE | DOC_FLASH_WP), docptr, Mplus_FlashSelect);
+
+	/* Reset the chip, see Software Requirement 11.4 item 1. */
+	DoC_Command(docptr, NAND_CMD_RESET, 0);
+	DoC_WaitReady(docptr);
+
+	fofs = from;
+	DoC_Command(docptr, DoC_GetDataOffset(mtd, &fofs), 0);
+	DoC_Address(this, 3, fofs, 0, 0x00);
+	WriteDOC(0, docptr, Mplus_FlashControl);
+	DoC_WaitReady(docptr);
+
+	if (eccbuf) {
+		/* init the ECC engine, see Reed-Solomon EDC/ECC 11.1 .*/
+		WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf);
+		WriteDOC(DOC_ECC_EN, docptr, Mplus_ECCConf);
+	} else {
+		/* disable the ECC engine */
+		WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf);
+	}
+
+	/* Let the caller know we completed it */
+	*retlen = len;
+        ret = 0;
+
+	ReadDOC(docptr, Mplus_ReadPipeInit);
+	ReadDOC(docptr, Mplus_ReadPipeInit);
+
+	if (eccbuf) {
+		/* Read the data via the internal pipeline through CDSN IO
+		   register, see Pipelined Read Operations 11.3 */
+		MemReadDOC(docptr, buf, len);
+
+		/* Read the ECC data following raw data */
+		MemReadDOC(docptr, eccbuf, 4);
+		eccbuf[4] = ReadDOC(docptr, Mplus_LastDataRead);
+		eccbuf[5] = ReadDOC(docptr, Mplus_LastDataRead);
+
+		/* Flush the pipeline */
+		dummy = ReadDOC(docptr, Mplus_ECCConf);
+		dummy = ReadDOC(docptr, Mplus_ECCConf);
+
+		/* Check the ECC Status */
+		if (ReadDOC(docptr, Mplus_ECCConf) & 0x80) {
+                        int nb_errors;
+			/* There was an ECC error */
+#ifdef ECC_DEBUG
+			printk("DiskOnChip ECC Error: Read at %lx\n", (long)from);
+#endif
+			/* Read the ECC syndrom through the DiskOnChip ECC logic.
+			   These syndrome will be all ZERO when there is no error */
+			for (i = 0; i < 6; i++)
+				syndrome[i] = ReadDOC(docptr, Mplus_ECCSyndrome0 + i);
+
+                        nb_errors = doc_decode_ecc(buf, syndrome);
+#ifdef ECC_DEBUG
+			printk("ECC Errors corrected: %x\n", nb_errors);
+#endif
+                        if (nb_errors < 0) {
+				/* We return error, but have actually done the read. Not that
+				   this can be told to user-space, via sys_read(), but at least
+				   MTD-aware stuff can know about it by checking *retlen */
+#ifdef ECC_DEBUG
+			printk("%s(%d): Millennium Plus ECC error (from=0x%x:\n",
+				__FILE__, __LINE__, (int)from);
+			printk("        syndrome= %02x:%02x:%02x:%02x:%02x:"
+				"%02x\n",
+				syndrome[0], syndrome[1], syndrome[2],
+				syndrome[3], syndrome[4], syndrome[5]);
+			printk("          eccbuf= %02x:%02x:%02x:%02x:%02x:"
+				"%02x\n",
+				eccbuf[0], eccbuf[1], eccbuf[2],
+				eccbuf[3], eccbuf[4], eccbuf[5]);
+#endif
+				ret = -EIO;
+                        }
+		}
+
+#ifdef PSYCHO_DEBUG
+		printk("ECC DATA at %lx: %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n",
+		       (long)from, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3],
+		       eccbuf[4], eccbuf[5]);
+#endif
+
+		/* disable the ECC engine */
+		WriteDOC(DOC_ECC_DIS, docptr , Mplus_ECCConf);
+	} else {
+		/* Read the data via the internal pipeline through CDSN IO
+		   register, see Pipelined Read Operations 11.3 */
+		MemReadDOC(docptr, buf, len-2);
+		buf[len-2] = ReadDOC(docptr, Mplus_LastDataRead);
+		buf[len-1] = ReadDOC(docptr, Mplus_LastDataRead);
+	}
+
+	/* Disable flash internally */
+	WriteDOC(0, docptr, Mplus_FlashSelect);
+
+	return ret;
+}
+
+static int doc_write(struct mtd_info *mtd, loff_t to, size_t len,
+		     size_t *retlen, const u_char *buf)
+{
+	char eccbuf[6];
+	return doc_write_ecc(mtd, to, len, retlen, buf, eccbuf, NULL);
+}
+
+static int doc_write_ecc(struct mtd_info *mtd, loff_t to, size_t len,
+			 size_t *retlen, const u_char *buf, u_char *eccbuf,
+			 struct nand_oobinfo *oobsel)
+{
+	int i, before, ret = 0;
+	loff_t fto;
+	volatile char dummy;
+	struct DiskOnChip *this = mtd->priv;
+	void __iomem * docptr = this->virtadr;
+	struct Nand *mychip = &this->chips[to >> (this->chipshift)];
+
+	/* Don't allow write past end of device */
+	if (to >= this->totlen)
+		return -EINVAL;
+
+	/* Don't allow writes which aren't exactly one block (512 bytes) */
+	if ((to & 0x1ff) || (len != 0x200))
+		return -EINVAL;
+
+	/* Determine position of OOB flags, before or after data */
+	before = (this->interleave && (to & 0x200));
+
+	DoC_CheckASIC(docptr);
+
+	/* Find the chip which is to be used and select it */
+	if (this->curfloor != mychip->floor) {
+		DoC_SelectFloor(docptr, mychip->floor);
+		DoC_SelectChip(docptr, mychip->chip);
+	} else if (this->curchip != mychip->chip) {
+		DoC_SelectChip(docptr, mychip->chip);
+	}
+	this->curfloor = mychip->floor;
+	this->curchip = mychip->chip;
+
+	/* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */
+	WriteDOC(DOC_FLASH_CE, docptr, Mplus_FlashSelect);
+
+	/* Reset the chip, see Software Requirement 11.4 item 1. */
+	DoC_Command(docptr, NAND_CMD_RESET, 0);
+	DoC_WaitReady(docptr);
+
+	/* Set device to appropriate plane of flash */
+	fto = to;
+	WriteDOC(DoC_GetDataOffset(mtd, &fto), docptr, Mplus_FlashCmd);
+
+	/* On interleaved devices the flags for 2nd half 512 are before data */
+	if (eccbuf && before)
+		fto -= 2;
+
+	/* issue the Serial Data In command to initial the Page Program process */
+	DoC_Command(docptr, NAND_CMD_SEQIN, 0x00);
+	DoC_Address(this, 3, fto, 0x00, 0x00);
+
+	/* Disable the ECC engine */
+	WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf);
+
+	if (eccbuf) {
+		if (before) {
+			/* Write the block status BLOCK_USED (0x5555) */
+			WriteDOC(0x55, docptr, Mil_CDSN_IO);
+			WriteDOC(0x55, docptr, Mil_CDSN_IO);
+		}
+
+		/* init the ECC engine, see Reed-Solomon EDC/ECC 11.1 .*/
+		WriteDOC(DOC_ECC_EN | DOC_ECC_RW, docptr, Mplus_ECCConf);
+	}
+
+	MemWriteDOC(docptr, (unsigned char *) buf, len);
+
+	if (eccbuf) {
+		/* Write ECC data to flash, the ECC info is generated by
+		   the DiskOnChip ECC logic see Reed-Solomon EDC/ECC 11.1 */
+		DoC_Delay(docptr, 3);
+
+		/* Read the ECC data through the DiskOnChip ECC logic */
+		for (i = 0; i < 6; i++)
+			eccbuf[i] = ReadDOC(docptr, Mplus_ECCSyndrome0 + i);
+
+		/* disable the ECC engine */
+		WriteDOC(DOC_ECC_DIS, docptr, Mplus_ECCConf);
+
+		/* Write the ECC data to flash */
+		MemWriteDOC(docptr, eccbuf, 6);
+
+		if (!before) {
+			/* Write the block status BLOCK_USED (0x5555) */
+			WriteDOC(0x55, docptr, Mil_CDSN_IO+6);
+			WriteDOC(0x55, docptr, Mil_CDSN_IO+7);
+		}
+
+#ifdef PSYCHO_DEBUG
+		printk("OOB data at %lx is %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n",
+		       (long) to, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3],
+		       eccbuf[4], eccbuf[5]);
+#endif
+	}
+
+	WriteDOC(0x00, docptr, Mplus_WritePipeTerm);
+	WriteDOC(0x00, docptr, Mplus_WritePipeTerm);
+
+	/* Commit the Page Program command and wait for ready
+	   see Software Requirement 11.4 item 1.*/
+	DoC_Command(docptr, NAND_CMD_PAGEPROG, 0x00);
+	DoC_WaitReady(docptr);
+
+	/* Read the status of the flash device through CDSN IO register
+	   see Software Requirement 11.4 item 5.*/
+	DoC_Command(docptr, NAND_CMD_STATUS, 0);
+	dummy = ReadDOC(docptr, Mplus_ReadPipeInit);
+	dummy = ReadDOC(docptr, Mplus_ReadPipeInit);
+	DoC_Delay(docptr, 2);
+	if ((dummy = ReadDOC(docptr, Mplus_LastDataRead)) & 1) {
+		printk("MTD: Error 0x%x programming at 0x%x\n", dummy, (int)to);
+		/* Error in programming
+		   FIXME: implement Bad Block Replacement (in nftl.c ??) */
+		*retlen = 0;
+		ret = -EIO;
+	}
+	dummy = ReadDOC(docptr, Mplus_LastDataRead);
+
+	/* Disable flash internally */
+	WriteDOC(0, docptr, Mplus_FlashSelect);
+
+	/* Let the caller know we completed it */
+	*retlen = len;
+
+	return ret;
+}
+
+static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+			size_t *retlen, u_char *buf)
+{
+	loff_t fofs, base;
+	struct DiskOnChip *this = mtd->priv;
+	void __iomem * docptr = this->virtadr;
+	struct Nand *mychip = &this->chips[ofs >> this->chipshift];
+	size_t i, size, got, want;
+
+	DoC_CheckASIC(docptr);
+
+	/* Find the chip which is to be used and select it */
+	if (this->curfloor != mychip->floor) {
+		DoC_SelectFloor(docptr, mychip->floor);
+		DoC_SelectChip(docptr, mychip->chip);
+	} else if (this->curchip != mychip->chip) {
+		DoC_SelectChip(docptr, mychip->chip);
+	}
+	this->curfloor = mychip->floor;
+	this->curchip = mychip->chip;
+
+	/* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */
+	WriteDOC((DOC_FLASH_CE | DOC_FLASH_WP), docptr, Mplus_FlashSelect);
+
+	/* disable the ECC engine */
+	WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf);
+	DoC_WaitReady(docptr);
+
+	/* Maximum of 16 bytes in the OOB region, so limit read to that */
+	if (len > 16)
+		len = 16;
+	got = 0;
+	want = len;
+
+	for (i = 0; ((i < 3) && (want > 0)); i++) {
+		/* Figure out which region we are accessing... */
+		fofs = ofs;
+		base = ofs & 0xf;
+		if (!this->interleave) {
+			DoC_Command(docptr, NAND_CMD_READOOB, 0);
+			size = 16 - base;
+		} else if (base < 6) {
+			DoC_Command(docptr, DoC_GetECCOffset(mtd, &fofs), 0);
+			size = 6 - base;
+		} else if (base < 8) {
+			DoC_Command(docptr, DoC_GetFlagsOffset(mtd, &fofs), 0);
+			size = 8 - base;
+		} else {
+			DoC_Command(docptr, DoC_GetHdrOffset(mtd, &fofs), 0);
+			size = 16 - base;
+		}
+		if (size > want)
+			size = want;
+
+		/* Issue read command */
+		DoC_Address(this, 3, fofs, 0, 0x00);
+		WriteDOC(0, docptr, Mplus_FlashControl);
+		DoC_WaitReady(docptr);
+
+		ReadDOC(docptr, Mplus_ReadPipeInit);
+		ReadDOC(docptr, Mplus_ReadPipeInit);
+		MemReadDOC(docptr, &buf[got], size - 2);
+		buf[got + size - 2] = ReadDOC(docptr, Mplus_LastDataRead);
+		buf[got + size - 1] = ReadDOC(docptr, Mplus_LastDataRead);
+
+		ofs += size;
+		got += size;
+		want -= size;
+	}
+
+	/* Disable flash internally */
+	WriteDOC(0, docptr, Mplus_FlashSelect);
+
+	*retlen = len;
+	return 0;
+}
+
+static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+			 size_t *retlen, const u_char *buf)
+{
+	volatile char dummy;
+	loff_t fofs, base;
+	struct DiskOnChip *this = mtd->priv;
+	void __iomem * docptr = this->virtadr;
+	struct Nand *mychip = &this->chips[ofs >> this->chipshift];
+	size_t i, size, got, want;
+	int ret = 0;
+
+	DoC_CheckASIC(docptr);
+
+	/* Find the chip which is to be used and select it */
+	if (this->curfloor != mychip->floor) {
+		DoC_SelectFloor(docptr, mychip->floor);
+		DoC_SelectChip(docptr, mychip->chip);
+	} else if (this->curchip != mychip->chip) {
+		DoC_SelectChip(docptr, mychip->chip);
+	}
+	this->curfloor = mychip->floor;
+	this->curchip = mychip->chip;
+
+	/* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */
+	WriteDOC(DOC_FLASH_CE, docptr, Mplus_FlashSelect);
+
+
+	/* Maximum of 16 bytes in the OOB region, so limit write to that */
+	if (len > 16)
+		len = 16;
+	got = 0;
+	want = len;
+
+	for (i = 0; ((i < 3) && (want > 0)); i++) {
+		/* Reset the chip, see Software Requirement 11.4 item 1. */
+		DoC_Command(docptr, NAND_CMD_RESET, 0);
+		DoC_WaitReady(docptr);
+
+		/* Figure out which region we are accessing... */
+		fofs = ofs;
+		base = ofs & 0x0f;
+		if (!this->interleave) {
+			WriteDOC(NAND_CMD_READOOB, docptr, Mplus_FlashCmd);
+			size = 16 - base;
+		} else if (base < 6) {
+			WriteDOC(DoC_GetECCOffset(mtd, &fofs), docptr, Mplus_FlashCmd);
+			size = 6 - base;
+		} else if (base < 8) {
+			WriteDOC(DoC_GetFlagsOffset(mtd, &fofs), docptr, Mplus_FlashCmd);
+			size = 8 - base;
+		} else {
+			WriteDOC(DoC_GetHdrOffset(mtd, &fofs), docptr, Mplus_FlashCmd);
+			size = 16 - base;
+		}
+		if (size > want)
+			size = want;
+
+		/* Issue the Serial Data In command to initial the Page Program process */
+		DoC_Command(docptr, NAND_CMD_SEQIN, 0x00);
+		DoC_Address(this, 3, fofs, 0, 0x00);
+
+		/* Disable the ECC engine */
+		WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf);
+
+		/* Write the data via the internal pipeline through CDSN IO
+		   register, see Pipelined Write Operations 11.2 */
+		MemWriteDOC(docptr, (unsigned char *) &buf[got], size);
+		WriteDOC(0x00, docptr, Mplus_WritePipeTerm);
+		WriteDOC(0x00, docptr, Mplus_WritePipeTerm);
+
+		/* Commit the Page Program command and wait for ready
+	 	   see Software Requirement 11.4 item 1.*/
+		DoC_Command(docptr, NAND_CMD_PAGEPROG, 0x00);
+		DoC_WaitReady(docptr);
+
+		/* Read the status of the flash device through CDSN IO register
+		   see Software Requirement 11.4 item 5.*/
+		DoC_Command(docptr, NAND_CMD_STATUS, 0x00);
+		dummy = ReadDOC(docptr, Mplus_ReadPipeInit);
+		dummy = ReadDOC(docptr, Mplus_ReadPipeInit);
+		DoC_Delay(docptr, 2);
+		if ((dummy = ReadDOC(docptr, Mplus_LastDataRead)) & 1) {
+			printk("MTD: Error 0x%x programming oob at 0x%x\n",
+				dummy, (int)ofs);
+			/* FIXME: implement Bad Block Replacement */
+			*retlen = 0;
+			ret = -EIO;
+		}
+		dummy = ReadDOC(docptr, Mplus_LastDataRead);
+
+		ofs += size;
+		got += size;
+		want -= size;
+	}
+
+	/* Disable flash internally */
+	WriteDOC(0, docptr, Mplus_FlashSelect);
+
+	*retlen = len;
+	return ret;
+}
+
+int doc_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+	volatile char dummy;
+	struct DiskOnChip *this = mtd->priv;
+	__u32 ofs = instr->addr;
+	__u32 len = instr->len;
+	void __iomem * docptr = this->virtadr;
+	struct Nand *mychip = &this->chips[ofs >> this->chipshift];
+
+	DoC_CheckASIC(docptr);
+
+	if (len != mtd->erasesize) 
+		printk(KERN_WARNING "MTD: Erase not right size (%x != %x)n",
+		       len, mtd->erasesize);
+
+	/* Find the chip which is to be used and select it */
+	if (this->curfloor != mychip->floor) {
+		DoC_SelectFloor(docptr, mychip->floor);
+		DoC_SelectChip(docptr, mychip->chip);
+	} else if (this->curchip != mychip->chip) {
+		DoC_SelectChip(docptr, mychip->chip);
+	}
+	this->curfloor = mychip->floor;
+	this->curchip = mychip->chip;
+
+	instr->state = MTD_ERASE_PENDING;
+
+	/* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */
+	WriteDOC(DOC_FLASH_CE, docptr, Mplus_FlashSelect);
+
+	DoC_Command(docptr, NAND_CMD_RESET, 0x00);
+	DoC_WaitReady(docptr);
+
+	DoC_Command(docptr, NAND_CMD_ERASE1, 0);
+	DoC_Address(this, 2, ofs, 0, 0x00);
+	DoC_Command(docptr, NAND_CMD_ERASE2, 0);
+	DoC_WaitReady(docptr);
+	instr->state = MTD_ERASING;
+
+	/* Read the status of the flash device through CDSN IO register
+	   see Software Requirement 11.4 item 5. */
+	DoC_Command(docptr, NAND_CMD_STATUS, 0);
+	dummy = ReadDOC(docptr, Mplus_ReadPipeInit);
+	dummy = ReadDOC(docptr, Mplus_ReadPipeInit);
+	if ((dummy = ReadDOC(docptr, Mplus_LastDataRead)) & 1) {
+		printk("MTD: Error 0x%x erasing at 0x%x\n", dummy, ofs);
+		/* FIXME: implement Bad Block Replacement (in nftl.c ??) */
+		instr->state = MTD_ERASE_FAILED;
+	} else {
+		instr->state = MTD_ERASE_DONE;
+	}
+	dummy = ReadDOC(docptr, Mplus_LastDataRead);
+
+	/* Disable flash internally */
+	WriteDOC(0, docptr, Mplus_FlashSelect);
+
+	mtd_erase_callback(instr);
+
+	return 0;
+}
+
+/****************************************************************************
+ *
+ * Module stuff
+ *
+ ****************************************************************************/
+
+static int __init init_doc2001plus(void)
+{
+	inter_module_register(im_name, THIS_MODULE, &DoCMilPlus_init);
+	return 0;
+}
+
+static void __exit cleanup_doc2001plus(void)
+{
+	struct mtd_info *mtd;
+	struct DiskOnChip *this;
+
+	while ((mtd=docmilpluslist)) {
+		this = mtd->priv;
+		docmilpluslist = this->nextdoc;
+			
+		del_mtd_device(mtd);
+			
+		iounmap(this->virtadr);
+		kfree(this->chips);
+		kfree(mtd);
+	}
+	inter_module_unregister(im_name);
+}
+
+module_exit(cleanup_doc2001plus);
+module_init(init_doc2001plus);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Greg Ungerer <gerg@snapgear.com> et al.");
+MODULE_DESCRIPTION("Driver for DiskOnChip Millennium Plus");
diff --git a/drivers/mtd/devices/docecc.c b/drivers/mtd/devices/docecc.c
new file mode 100644
index 0000000..933877f
--- /dev/null
+++ b/drivers/mtd/devices/docecc.c
@@ -0,0 +1,526 @@
+/*
+ * ECC algorithm for M-systems disk on chip. We use the excellent Reed
+ * Solmon code of Phil Karn (karn@ka9q.ampr.org) available under the
+ * GNU GPL License. The rest is simply to convert the disk on chip
+ * syndrom into a standard syndom.
+ *
+ * Author: Fabrice Bellard (fabrice.bellard@netgem.com) 
+ * Copyright (C) 2000 Netgem S.A.
+ *
+ * $Id: docecc.c,v 1.5 2003/05/21 15:15:06 dwmw2 Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/miscdevice.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/types.h>
+
+#include <linux/mtd/compatmac.h> /* for min() in older kernels */
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/doc2000.h>
+
+/* need to undef it (from asm/termbits.h) */
+#undef B0
+
+#define MM 10 /* Symbol size in bits */
+#define KK (1023-4) /* Number of data symbols per block */
+#define B0 510 /* First root of generator polynomial, alpha form */
+#define PRIM 1 /* power of alpha used to generate roots of generator poly */
+#define	NN ((1 << MM) - 1)
+
+typedef unsigned short dtype;
+
+/* 1+x^3+x^10 */
+static const int Pp[MM+1] = { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1 };
+
+/* This defines the type used to store an element of the Galois Field
+ * used by the code. Make sure this is something larger than a char if
+ * if anything larger than GF(256) is used.
+ *
+ * Note: unsigned char will work up to GF(256) but int seems to run
+ * faster on the Pentium.
+ */
+typedef int gf;
+
+/* No legal value in index form represents zero, so
+ * we need a special value for this purpose
+ */
+#define A0	(NN)
+
+/* Compute x % NN, where NN is 2**MM - 1,
+ * without a slow divide
+ */
+static inline gf
+modnn(int x)
+{
+  while (x >= NN) {
+    x -= NN;
+    x = (x >> MM) + (x & NN);
+  }
+  return x;
+}
+
+#define	CLEAR(a,n) {\
+int ci;\
+for(ci=(n)-1;ci >=0;ci--)\
+(a)[ci] = 0;\
+}
+
+#define	COPY(a,b,n) {\
+int ci;\
+for(ci=(n)-1;ci >=0;ci--)\
+(a)[ci] = (b)[ci];\
+}
+
+#define	COPYDOWN(a,b,n) {\
+int ci;\
+for(ci=(n)-1;ci >=0;ci--)\
+(a)[ci] = (b)[ci];\
+}
+
+#define Ldec 1
+
+/* generate GF(2**m) from the irreducible polynomial p(X) in Pp[0]..Pp[m]
+   lookup tables:  index->polynomial form   alpha_to[] contains j=alpha**i;
+                   polynomial form -> index form  index_of[j=alpha**i] = i
+   alpha=2 is the primitive element of GF(2**m)
+   HARI's COMMENT: (4/13/94) alpha_to[] can be used as follows:
+        Let @ represent the primitive element commonly called "alpha" that
+   is the root of the primitive polynomial p(x). Then in GF(2^m), for any
+   0 <= i <= 2^m-2,
+        @^i = a(0) + a(1) @ + a(2) @^2 + ... + a(m-1) @^(m-1)
+   where the binary vector (a(0),a(1),a(2),...,a(m-1)) is the representation
+   of the integer "alpha_to[i]" with a(0) being the LSB and a(m-1) the MSB. Thus for
+   example the polynomial representation of @^5 would be given by the binary
+   representation of the integer "alpha_to[5]".
+                   Similarily, index_of[] can be used as follows:
+        As above, let @ represent the primitive element of GF(2^m) that is
+   the root of the primitive polynomial p(x). In order to find the power
+   of @ (alpha) that has the polynomial representation
+        a(0) + a(1) @ + a(2) @^2 + ... + a(m-1) @^(m-1)
+   we consider the integer "i" whose binary representation with a(0) being LSB
+   and a(m-1) MSB is (a(0),a(1),...,a(m-1)) and locate the entry
+   "index_of[i]". Now, @^index_of[i] is that element whose polynomial 
+    representation is (a(0),a(1),a(2),...,a(m-1)).
+   NOTE:
+        The element alpha_to[2^m-1] = 0 always signifying that the
+   representation of "@^infinity" = 0 is (0,0,0,...,0).
+        Similarily, the element index_of[0] = A0 always signifying
+   that the power of alpha which has the polynomial representation
+   (0,0,...,0) is "infinity".
+ 
+*/
+
+static void
+generate_gf(dtype Alpha_to[NN + 1], dtype Index_of[NN + 1])
+{
+  register int i, mask;
+
+  mask = 1;
+  Alpha_to[MM] = 0;
+  for (i = 0; i < MM; i++) {
+    Alpha_to[i] = mask;
+    Index_of[Alpha_to[i]] = i;
+    /* If Pp[i] == 1 then, term @^i occurs in poly-repr of @^MM */
+    if (Pp[i] != 0)
+      Alpha_to[MM] ^= mask;	/* Bit-wise EXOR operation */
+    mask <<= 1;	/* single left-shift */
+  }
+  Index_of[Alpha_to[MM]] = MM;
+  /*
+   * Have obtained poly-repr of @^MM. Poly-repr of @^(i+1) is given by
+   * poly-repr of @^i shifted left one-bit and accounting for any @^MM
+   * term that may occur when poly-repr of @^i is shifted.
+   */
+  mask >>= 1;
+  for (i = MM + 1; i < NN; i++) {
+    if (Alpha_to[i - 1] >= mask)
+      Alpha_to[i] = Alpha_to[MM] ^ ((Alpha_to[i - 1] ^ mask) << 1);
+    else
+      Alpha_to[i] = Alpha_to[i - 1] << 1;
+    Index_of[Alpha_to[i]] = i;
+  }
+  Index_of[0] = A0;
+  Alpha_to[NN] = 0;
+}
+
+/*
+ * Performs ERRORS+ERASURES decoding of RS codes. bb[] is the content
+ * of the feedback shift register after having processed the data and
+ * the ECC.
+ *
+ * Return number of symbols corrected, or -1 if codeword is illegal
+ * or uncorrectable. If eras_pos is non-null, the detected error locations
+ * are written back. NOTE! This array must be at least NN-KK elements long.
+ * The corrected data are written in eras_val[]. They must be xor with the data
+ * to retrieve the correct data : data[erase_pos[i]] ^= erase_val[i] .
+ * 
+ * First "no_eras" erasures are declared by the calling program. Then, the
+ * maximum # of errors correctable is t_after_eras = floor((NN-KK-no_eras)/2).
+ * If the number of channel errors is not greater than "t_after_eras" the
+ * transmitted codeword will be recovered. Details of algorithm can be found
+ * in R. Blahut's "Theory ... of Error-Correcting Codes".
+
+ * Warning: the eras_pos[] array must not contain duplicate entries; decoder failure
+ * will result. The decoder *could* check for this condition, but it would involve
+ * extra time on every decoding operation.
+ * */
+static int
+eras_dec_rs(dtype Alpha_to[NN + 1], dtype Index_of[NN + 1],
+            gf bb[NN - KK + 1], gf eras_val[NN-KK], int eras_pos[NN-KK], 
+            int no_eras)
+{
+  int deg_lambda, el, deg_omega;
+  int i, j, r,k;
+  gf u,q,tmp,num1,num2,den,discr_r;
+  gf lambda[NN-KK + 1], s[NN-KK + 1];	/* Err+Eras Locator poly
+					 * and syndrome poly */
+  gf b[NN-KK + 1], t[NN-KK + 1], omega[NN-KK + 1];
+  gf root[NN-KK], reg[NN-KK + 1], loc[NN-KK];
+  int syn_error, count;
+
+  syn_error = 0;
+  for(i=0;i<NN-KK;i++)
+      syn_error |= bb[i];
+
+  if (!syn_error) {
+    /* if remainder is zero, data[] is a codeword and there are no
+     * errors to correct. So return data[] unmodified
+     */
+    count = 0;
+    goto finish;
+  }
+  
+  for(i=1;i<=NN-KK;i++){
+    s[i] = bb[0];
+  }
+  for(j=1;j<NN-KK;j++){
+    if(bb[j] == 0)
+      continue;
+    tmp = Index_of[bb[j]];
+    
+    for(i=1;i<=NN-KK;i++)
+      s[i] ^= Alpha_to[modnn(tmp + (B0+i-1)*PRIM*j)];
+  }
+
+  /* undo the feedback register implicit multiplication and convert
+     syndromes to index form */
+
+  for(i=1;i<=NN-KK;i++) {
+      tmp = Index_of[s[i]];
+      if (tmp != A0)
+          tmp = modnn(tmp + 2 * KK * (B0+i-1)*PRIM);
+      s[i] = tmp;
+  }
+  
+  CLEAR(&lambda[1],NN-KK);
+  lambda[0] = 1;
+
+  if (no_eras > 0) {
+    /* Init lambda to be the erasure locator polynomial */
+    lambda[1] = Alpha_to[modnn(PRIM * eras_pos[0])];
+    for (i = 1; i < no_eras; i++) {
+      u = modnn(PRIM*eras_pos[i]);
+      for (j = i+1; j > 0; j--) {
+	tmp = Index_of[lambda[j - 1]];
+	if(tmp != A0)
+	  lambda[j] ^= Alpha_to[modnn(u + tmp)];
+      }
+    }
+#if DEBUG >= 1
+    /* Test code that verifies the erasure locator polynomial just constructed
+       Needed only for decoder debugging. */
+    
+    /* find roots of the erasure location polynomial */
+    for(i=1;i<=no_eras;i++)
+      reg[i] = Index_of[lambda[i]];
+    count = 0;
+    for (i = 1,k=NN-Ldec; i <= NN; i++,k = modnn(NN+k-Ldec)) {
+      q = 1;
+      for (j = 1; j <= no_eras; j++)
+	if (reg[j] != A0) {
+	  reg[j] = modnn(reg[j] + j);
+	  q ^= Alpha_to[reg[j]];
+	}
+      if (q != 0)
+	continue;
+      /* store root and error location number indices */
+      root[count] = i;
+      loc[count] = k;
+      count++;
+    }
+    if (count != no_eras) {
+      printf("\n lambda(x) is WRONG\n");
+      count = -1;
+      goto finish;
+    }
+#if DEBUG >= 2
+    printf("\n Erasure positions as determined by roots of Eras Loc Poly:\n");
+    for (i = 0; i < count; i++)
+      printf("%d ", loc[i]);
+    printf("\n");
+#endif
+#endif
+  }
+  for(i=0;i<NN-KK+1;i++)
+    b[i] = Index_of[lambda[i]];
+  
+  /*
+   * Begin Berlekamp-Massey algorithm to determine error+erasure
+   * locator polynomial
+   */
+  r = no_eras;
+  el = no_eras;
+  while (++r <= NN-KK) {	/* r is the step number */
+    /* Compute discrepancy at the r-th step in poly-form */
+    discr_r = 0;
+    for (i = 0; i < r; i++){
+      if ((lambda[i] != 0) && (s[r - i] != A0)) {
+	discr_r ^= Alpha_to[modnn(Index_of[lambda[i]] + s[r - i])];
+      }
+    }
+    discr_r = Index_of[discr_r];	/* Index form */
+    if (discr_r == A0) {
+      /* 2 lines below: B(x) <-- x*B(x) */
+      COPYDOWN(&b[1],b,NN-KK);
+      b[0] = A0;
+    } else {
+      /* 7 lines below: T(x) <-- lambda(x) - discr_r*x*b(x) */
+      t[0] = lambda[0];
+      for (i = 0 ; i < NN-KK; i++) {
+	if(b[i] != A0)
+	  t[i+1] = lambda[i+1] ^ Alpha_to[modnn(discr_r + b[i])];
+	else
+	  t[i+1] = lambda[i+1];
+      }
+      if (2 * el <= r + no_eras - 1) {
+	el = r + no_eras - el;
+	/*
+	 * 2 lines below: B(x) <-- inv(discr_r) *
+	 * lambda(x)
+	 */
+	for (i = 0; i <= NN-KK; i++)
+	  b[i] = (lambda[i] == 0) ? A0 : modnn(Index_of[lambda[i]] - discr_r + NN);
+      } else {
+	/* 2 lines below: B(x) <-- x*B(x) */
+	COPYDOWN(&b[1],b,NN-KK);
+	b[0] = A0;
+      }
+      COPY(lambda,t,NN-KK+1);
+    }
+  }
+
+  /* Convert lambda to index form and compute deg(lambda(x)) */
+  deg_lambda = 0;
+  for(i=0;i<NN-KK+1;i++){
+    lambda[i] = Index_of[lambda[i]];
+    if(lambda[i] != A0)
+      deg_lambda = i;
+  }
+  /*
+   * Find roots of the error+erasure locator polynomial by Chien
+   * Search
+   */
+  COPY(&reg[1],&lambda[1],NN-KK);
+  count = 0;		/* Number of roots of lambda(x) */
+  for (i = 1,k=NN-Ldec; i <= NN; i++,k = modnn(NN+k-Ldec)) {
+    q = 1;
+    for (j = deg_lambda; j > 0; j--){
+      if (reg[j] != A0) {
+	reg[j] = modnn(reg[j] + j);
+	q ^= Alpha_to[reg[j]];
+      }
+    }
+    if (q != 0)
+      continue;
+    /* store root (index-form) and error location number */
+    root[count] = i;
+    loc[count] = k;
+    /* If we've already found max possible roots,
+     * abort the search to save time
+     */
+    if(++count == deg_lambda)
+      break;
+  }
+  if (deg_lambda != count) {
+    /*
+     * deg(lambda) unequal to number of roots => uncorrectable
+     * error detected
+     */
+    count = -1;
+    goto finish;
+  }
+  /*
+   * Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo
+   * x**(NN-KK)). in index form. Also find deg(omega).
+   */
+  deg_omega = 0;
+  for (i = 0; i < NN-KK;i++){
+    tmp = 0;
+    j = (deg_lambda < i) ? deg_lambda : i;
+    for(;j >= 0; j--){
+      if ((s[i + 1 - j] != A0) && (lambda[j] != A0))
+	tmp ^= Alpha_to[modnn(s[i + 1 - j] + lambda[j])];
+    }
+    if(tmp != 0)
+      deg_omega = i;
+    omega[i] = Index_of[tmp];
+  }
+  omega[NN-KK] = A0;
+  
+  /*
+   * Compute error values in poly-form. num1 = omega(inv(X(l))), num2 =
+   * inv(X(l))**(B0-1) and den = lambda_pr(inv(X(l))) all in poly-form
+   */
+  for (j = count-1; j >=0; j--) {
+    num1 = 0;
+    for (i = deg_omega; i >= 0; i--) {
+      if (omega[i] != A0)
+	num1  ^= Alpha_to[modnn(omega[i] + i * root[j])];
+    }
+    num2 = Alpha_to[modnn(root[j] * (B0 - 1) + NN)];
+    den = 0;
+    
+    /* lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] */
+    for (i = min(deg_lambda,NN-KK-1) & ~1; i >= 0; i -=2) {
+      if(lambda[i+1] != A0)
+	den ^= Alpha_to[modnn(lambda[i+1] + i * root[j])];
+    }
+    if (den == 0) {
+#if DEBUG >= 1
+      printf("\n ERROR: denominator = 0\n");
+#endif
+      /* Convert to dual- basis */
+      count = -1;
+      goto finish;
+    }
+    /* Apply error to data */
+    if (num1 != 0) {
+        eras_val[j] = Alpha_to[modnn(Index_of[num1] + Index_of[num2] + NN - Index_of[den])];
+    } else {
+        eras_val[j] = 0;
+    }
+  }
+ finish:
+  for(i=0;i<count;i++)
+      eras_pos[i] = loc[i];
+  return count;
+}
+
+/***************************************************************************/
+/* The DOC specific code begins here */
+
+#define SECTOR_SIZE 512
+/* The sector bytes are packed into NB_DATA MM bits words */
+#define NB_DATA (((SECTOR_SIZE + 1) * 8 + 6) / MM)
+
+/* 
+ * Correct the errors in 'sector[]' by using 'ecc1[]' which is the
+ * content of the feedback shift register applyied to the sector and
+ * the ECC. Return the number of errors corrected (and correct them in
+ * sector), or -1 if error 
+ */
+int doc_decode_ecc(unsigned char sector[SECTOR_SIZE], unsigned char ecc1[6])
+{
+    int parity, i, nb_errors;
+    gf bb[NN - KK + 1];
+    gf error_val[NN-KK];
+    int error_pos[NN-KK], pos, bitpos, index, val;
+    dtype *Alpha_to, *Index_of;
+
+    /* init log and exp tables here to save memory. However, it is slower */
+    Alpha_to = kmalloc((NN + 1) * sizeof(dtype), GFP_KERNEL);
+    if (!Alpha_to)
+        return -1;
+    
+    Index_of = kmalloc((NN + 1) * sizeof(dtype), GFP_KERNEL);
+    if (!Index_of) {
+        kfree(Alpha_to);
+        return -1;
+    }
+
+    generate_gf(Alpha_to, Index_of);
+
+    parity = ecc1[1];
+
+    bb[0] =  (ecc1[4] & 0xff) | ((ecc1[5] & 0x03) << 8);
+    bb[1] = ((ecc1[5] & 0xfc) >> 2) | ((ecc1[2] & 0x0f) << 6);
+    bb[2] = ((ecc1[2] & 0xf0) >> 4) | ((ecc1[3] & 0x3f) << 4);
+    bb[3] = ((ecc1[3] & 0xc0) >> 6) | ((ecc1[0] & 0xff) << 2);
+
+    nb_errors = eras_dec_rs(Alpha_to, Index_of, bb, 
+                            error_val, error_pos, 0);
+    if (nb_errors <= 0)
+        goto the_end;
+
+    /* correct the errors */
+    for(i=0;i<nb_errors;i++) {
+        pos = error_pos[i];
+        if (pos >= NB_DATA && pos < KK) {
+            nb_errors = -1;
+            goto the_end;
+        }
+        if (pos < NB_DATA) {
+            /* extract bit position (MSB first) */
+            pos = 10 * (NB_DATA - 1 - pos) - 6;
+            /* now correct the following 10 bits. At most two bytes
+               can be modified since pos is even */
+            index = (pos >> 3) ^ 1;
+            bitpos = pos & 7;
+            if ((index >= 0 && index < SECTOR_SIZE) || 
+                index == (SECTOR_SIZE + 1)) {
+                val = error_val[i] >> (2 + bitpos);
+                parity ^= val;
+                if (index < SECTOR_SIZE)
+                    sector[index] ^= val;
+            }
+            index = ((pos >> 3) + 1) ^ 1;
+            bitpos = (bitpos + 10) & 7;
+            if (bitpos == 0)
+                bitpos = 8;
+            if ((index >= 0 && index < SECTOR_SIZE) || 
+                index == (SECTOR_SIZE + 1)) {
+                val = error_val[i] << (8 - bitpos);
+                parity ^= val;
+                if (index < SECTOR_SIZE)
+                    sector[index] ^= val;
+            }
+        }
+    }
+    
+    /* use parity to test extra errors */
+    if ((parity & 0xff) != 0)
+        nb_errors = -1;
+
+ the_end:
+    kfree(Alpha_to);
+    kfree(Index_of);
+    return nb_errors;
+}
+
+EXPORT_SYMBOL_GPL(doc_decode_ecc);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Fabrice Bellard <fabrice.bellard@netgem.com>");
+MODULE_DESCRIPTION("ECC code for correcting errors detected by DiskOnChip 2000 and Millennium ECC hardware");
diff --git a/drivers/mtd/devices/docprobe.c b/drivers/mtd/devices/docprobe.c
new file mode 100644
index 0000000..197d670
--- /dev/null
+++ b/drivers/mtd/devices/docprobe.c
@@ -0,0 +1,355 @@
+
+/* Linux driver for Disk-On-Chip devices			*/
+/* Probe routines common to all DoC devices			*/
+/* (C) 1999 Machine Vision Holdings, Inc.			*/
+/* (C) 1999-2003 David Woodhouse <dwmw2@infradead.org>		*/
+
+/* $Id: docprobe.c,v 1.44 2005/01/05 12:40:36 dwmw2 Exp $	*/
+
+
+
+/* DOC_PASSIVE_PROBE:
+   In order to ensure that the BIOS checksum is correct at boot time, and 
+   hence that the onboard BIOS extension gets executed, the DiskOnChip 
+   goes into reset mode when it is read sequentially: all registers 
+   return 0xff until the chip is woken up again by writing to the 
+   DOCControl register. 
+
+   Unfortunately, this means that the probe for the DiskOnChip is unsafe, 
+   because one of the first things it does is write to where it thinks 
+   the DOCControl register should be - which may well be shared memory 
+   for another device. I've had machines which lock up when this is 
+   attempted. Hence the possibility to do a passive probe, which will fail 
+   to detect a chip in reset mode, but is at least guaranteed not to lock
+   the machine.
+
+   If you have this problem, uncomment the following line:
+#define DOC_PASSIVE_PROBE
+*/
+
+
+/* DOC_SINGLE_DRIVER:
+   Millennium driver has been merged into DOC2000 driver.
+
+   The old Millennium-only driver has been retained just in case there
+   are problems with the new code. If the combined driver doesn't work
+   for you, you can try the old one by undefining DOC_SINGLE_DRIVER 
+   below and also enabling it in your configuration. If this fixes the
+   problems, please send a report to the MTD mailing list at 
+   <linux-mtd@lists.infradead.org>.
+*/
+#define DOC_SINGLE_DRIVER
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/types.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/doc2000.h>
+#include <linux/mtd/compatmac.h>
+
+/* Where to look for the devices? */
+#ifndef CONFIG_MTD_DOCPROBE_ADDRESS
+#define CONFIG_MTD_DOCPROBE_ADDRESS 0
+#endif
+
+
+static unsigned long doc_config_location = CONFIG_MTD_DOCPROBE_ADDRESS;
+module_param(doc_config_location, ulong, 0);
+MODULE_PARM_DESC(doc_config_location, "Physical memory address at which to probe for DiskOnChip");
+
+static unsigned long __initdata doc_locations[] = {
+#if defined (__alpha__) || defined(__i386__) || defined(__x86_64__)
+#ifdef CONFIG_MTD_DOCPROBE_HIGH
+	0xfffc8000, 0xfffca000, 0xfffcc000, 0xfffce000, 
+	0xfffd0000, 0xfffd2000, 0xfffd4000, 0xfffd6000,
+	0xfffd8000, 0xfffda000, 0xfffdc000, 0xfffde000, 
+	0xfffe0000, 0xfffe2000, 0xfffe4000, 0xfffe6000, 
+	0xfffe8000, 0xfffea000, 0xfffec000, 0xfffee000,
+#else /*  CONFIG_MTD_DOCPROBE_HIGH */
+	0xc8000, 0xca000, 0xcc000, 0xce000, 
+	0xd0000, 0xd2000, 0xd4000, 0xd6000,
+	0xd8000, 0xda000, 0xdc000, 0xde000, 
+	0xe0000, 0xe2000, 0xe4000, 0xe6000, 
+	0xe8000, 0xea000, 0xec000, 0xee000,
+#endif /*  CONFIG_MTD_DOCPROBE_HIGH */
+#elif defined(__PPC__)
+	0xe4000000,
+#elif defined(CONFIG_MOMENCO_OCELOT)
+	0x2f000000,
+        0xff000000,
+#elif defined(CONFIG_MOMENCO_OCELOT_G) || defined (CONFIG_MOMENCO_OCELOT_C)
+        0xff000000,
+##else
+#warning Unknown architecture for DiskOnChip. No default probe locations defined
+#endif
+	0xffffffff };
+
+/* doccheck: Probe a given memory window to see if there's a DiskOnChip present */
+
+static inline int __init doccheck(void __iomem *potential, unsigned long physadr)
+{
+	void __iomem *window=potential;
+	unsigned char tmp, tmpb, tmpc, ChipID;
+#ifndef DOC_PASSIVE_PROBE
+	unsigned char tmp2;
+#endif
+
+	/* Routine copied from the Linux DOC driver */
+
+#ifdef CONFIG_MTD_DOCPROBE_55AA
+	/* Check for 0x55 0xAA signature at beginning of window,
+	   this is no longer true once we remove the IPL (for Millennium */
+	if (ReadDOC(window, Sig1) != 0x55 || ReadDOC(window, Sig2) != 0xaa)
+		return 0;
+#endif /* CONFIG_MTD_DOCPROBE_55AA */
+
+#ifndef DOC_PASSIVE_PROBE	
+	/* It's not possible to cleanly detect the DiskOnChip - the
+	 * bootup procedure will put the device into reset mode, and
+	 * it's not possible to talk to it without actually writing
+	 * to the DOCControl register. So we store the current contents
+	 * of the DOCControl register's location, in case we later decide
+	 * that it's not a DiskOnChip, and want to put it back how we
+	 * found it. 
+	 */
+	tmp2 = ReadDOC(window, DOCControl);
+	
+	/* Reset the DiskOnChip ASIC */
+	WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_RESET, 
+		 window, DOCControl);
+	WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_RESET, 
+		 window, DOCControl);
+	
+	/* Enable the DiskOnChip ASIC */
+	WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_NORMAL, 
+		 window, DOCControl);
+	WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_NORMAL, 
+		 window, DOCControl);
+#endif /* !DOC_PASSIVE_PROBE */	
+
+	/* We need to read the ChipID register four times. For some
+	   newer DiskOnChip 2000 units, the first three reads will
+	   return the DiskOnChip Millennium ident. Don't ask. */
+	ChipID = ReadDOC(window, ChipID);
+  
+	switch (ChipID) {
+	case DOC_ChipID_Doc2k:
+		/* Check the TOGGLE bit in the ECC register */
+		tmp  = ReadDOC(window, 2k_ECCStatus) & DOC_TOGGLE_BIT;
+		tmpb = ReadDOC(window, 2k_ECCStatus) & DOC_TOGGLE_BIT;
+		tmpc = ReadDOC(window, 2k_ECCStatus) & DOC_TOGGLE_BIT;
+		if (tmp != tmpb && tmp == tmpc)
+				return ChipID;
+		break;
+		
+	case DOC_ChipID_DocMil:
+		/* Check for the new 2000 with Millennium ASIC */
+		ReadDOC(window, ChipID);
+		ReadDOC(window, ChipID);
+		if (ReadDOC(window, ChipID) != DOC_ChipID_DocMil)
+			ChipID = DOC_ChipID_Doc2kTSOP;
+
+		/* Check the TOGGLE bit in the ECC register */
+		tmp  = ReadDOC(window, ECCConf) & DOC_TOGGLE_BIT;
+		tmpb = ReadDOC(window, ECCConf) & DOC_TOGGLE_BIT;
+		tmpc = ReadDOC(window, ECCConf) & DOC_TOGGLE_BIT;
+		if (tmp != tmpb && tmp == tmpc)
+				return ChipID;
+		break;
+		
+	case DOC_ChipID_DocMilPlus16:
+	case DOC_ChipID_DocMilPlus32:
+	case 0:
+		/* Possible Millennium+, need to do more checks */
+#ifndef DOC_PASSIVE_PROBE
+		/* Possibly release from power down mode */
+		for (tmp = 0; (tmp < 4); tmp++)
+			ReadDOC(window, Mplus_Power);
+
+		/* Reset the DiskOnChip ASIC */
+		tmp = DOC_MODE_RESET | DOC_MODE_MDWREN | DOC_MODE_RST_LAT |
+			DOC_MODE_BDECT;
+		WriteDOC(tmp, window, Mplus_DOCControl);
+		WriteDOC(~tmp, window, Mplus_CtrlConfirm);
+	
+		mdelay(1);
+		/* Enable the DiskOnChip ASIC */
+		tmp = DOC_MODE_NORMAL | DOC_MODE_MDWREN | DOC_MODE_RST_LAT |
+			DOC_MODE_BDECT;
+		WriteDOC(tmp, window, Mplus_DOCControl);
+		WriteDOC(~tmp, window, Mplus_CtrlConfirm);
+		mdelay(1);
+#endif /* !DOC_PASSIVE_PROBE */	
+
+		ChipID = ReadDOC(window, ChipID);
+
+		switch (ChipID) {
+		case DOC_ChipID_DocMilPlus16:
+		case DOC_ChipID_DocMilPlus32:
+			/* Check the TOGGLE bit in the toggle register */
+			tmp  = ReadDOC(window, Mplus_Toggle) & DOC_TOGGLE_BIT;
+			tmpb = ReadDOC(window, Mplus_Toggle) & DOC_TOGGLE_BIT;
+			tmpc = ReadDOC(window, Mplus_Toggle) & DOC_TOGGLE_BIT;
+			if (tmp != tmpb && tmp == tmpc)
+					return ChipID;
+		default:
+			break;
+		}
+		/* FALL TRHU */
+
+	default:
+
+#ifdef CONFIG_MTD_DOCPROBE_55AA
+		printk(KERN_DEBUG "Possible DiskOnChip with unknown ChipID %2.2X found at 0x%lx\n",
+		       ChipID, physadr);
+#endif
+#ifndef DOC_PASSIVE_PROBE
+		/* Put back the contents of the DOCControl register, in case it's not
+		 * actually a DiskOnChip.
+		 */
+		WriteDOC(tmp2, window, DOCControl);
+#endif
+		return 0;
+	}
+
+	printk(KERN_WARNING "DiskOnChip failed TOGGLE test, dropping.\n");
+
+#ifndef DOC_PASSIVE_PROBE
+	/* Put back the contents of the DOCControl register: it's not a DiskOnChip */
+	WriteDOC(tmp2, window, DOCControl);
+#endif
+	return 0;
+}   
+
+static int docfound;
+
+static void __init DoC_Probe(unsigned long physadr)
+{
+	void __iomem *docptr;
+	struct DiskOnChip *this;
+	struct mtd_info *mtd;
+	int ChipID;
+	char namebuf[15];
+	char *name = namebuf;
+	char *im_funcname = NULL;
+	char *im_modname = NULL;
+	void (*initroutine)(struct mtd_info *) = NULL;
+
+	docptr = ioremap(physadr, DOC_IOREMAP_LEN);
+	
+	if (!docptr)
+		return;
+	
+	if ((ChipID = doccheck(docptr, physadr))) {
+		if (ChipID == DOC_ChipID_Doc2kTSOP) {
+			/* Remove this at your own peril. The hardware driver works but nothing prevents you from erasing bad blocks */
+			printk(KERN_NOTICE "Refusing to drive DiskOnChip 2000 TSOP until Bad Block Table is correctly supported by INFTL\n");
+			iounmap(docptr);
+			return;
+		}
+		docfound = 1;
+		mtd = kmalloc(sizeof(struct DiskOnChip) + sizeof(struct mtd_info), GFP_KERNEL);
+
+		if (!mtd) {
+			printk(KERN_WARNING "Cannot allocate memory for data structures. Dropping.\n");
+			iounmap(docptr);
+			return;
+		}
+		
+		this = (struct DiskOnChip *)(&mtd[1]);
+		
+		memset((char *)mtd,0, sizeof(struct mtd_info));
+		memset((char *)this, 0, sizeof(struct DiskOnChip));
+
+		mtd->priv = this;
+		this->virtadr = docptr;
+		this->physadr = physadr;
+		this->ChipID = ChipID;
+		sprintf(namebuf, "with ChipID %2.2X", ChipID);
+
+		switch(ChipID) {
+		case DOC_ChipID_Doc2kTSOP:
+			name="2000 TSOP";
+			im_funcname = "DoC2k_init";
+			im_modname = "doc2000";
+			break;
+			
+		case DOC_ChipID_Doc2k:
+			name="2000";
+			im_funcname = "DoC2k_init";
+			im_modname = "doc2000";
+			break;
+			
+		case DOC_ChipID_DocMil:
+			name="Millennium";
+#ifdef DOC_SINGLE_DRIVER
+			im_funcname = "DoC2k_init";
+			im_modname = "doc2000";
+#else
+			im_funcname = "DoCMil_init";
+			im_modname = "doc2001";
+#endif /* DOC_SINGLE_DRIVER */
+			break;
+
+		case DOC_ChipID_DocMilPlus16:
+		case DOC_ChipID_DocMilPlus32:
+			name="MillenniumPlus";
+			im_funcname = "DoCMilPlus_init";
+			im_modname = "doc2001plus";
+			break;
+		}
+
+		if (im_funcname)
+			initroutine = inter_module_get_request(im_funcname, im_modname);
+
+		if (initroutine) {
+			(*initroutine)(mtd);
+			inter_module_put(im_funcname);
+			return;
+		}
+		printk(KERN_NOTICE "Cannot find driver for DiskOnChip %s at 0x%lX\n", name, physadr);
+		kfree(mtd);
+	}
+	iounmap(docptr);
+}
+
+
+/****************************************************************************
+ *
+ * Module stuff
+ *
+ ****************************************************************************/
+
+static int __init init_doc(void)
+{
+	int i;
+	
+	if (doc_config_location) {
+		printk(KERN_INFO "Using configured DiskOnChip probe address 0x%lx\n", doc_config_location);
+		DoC_Probe(doc_config_location);
+	} else {
+		for (i=0; (doc_locations[i] != 0xffffffff); i++) {
+			DoC_Probe(doc_locations[i]);
+		}
+	}
+	/* No banner message any more. Print a message if no DiskOnChip
+	   found, so the user knows we at least tried. */
+	if (!docfound)
+		printk(KERN_INFO "No recognised DiskOnChip devices found\n");
+	return -EAGAIN;
+}
+
+module_init(init_doc);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_DESCRIPTION("Probe code for DiskOnChip 2000 and Millennium devices");
+
diff --git a/drivers/mtd/devices/lart.c b/drivers/mtd/devices/lart.c
new file mode 100644
index 0000000..dfd335e
--- /dev/null
+++ b/drivers/mtd/devices/lart.c
@@ -0,0 +1,711 @@
+
+/*
+ * MTD driver for the 28F160F3 Flash Memory (non-CFI) on LART.
+ *
+ * $Id: lart.c,v 1.7 2004/08/09 13:19:44 dwmw2 Exp $
+ *
+ * Author: Abraham vd Merwe <abraham@2d3d.co.za>
+ *
+ * Copyright (c) 2001, 2d3D, Inc.
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * References:
+ *
+ *    [1] 3 Volt Fast Boot Block Flash Memory" Intel Datasheet
+ *           - Order Number: 290644-005
+ *           - January 2000
+ *
+ *    [2] MTD internal API documentation
+ *           - http://www.linux-mtd.infradead.org/tech/
+ *
+ * Limitations:
+ *
+ *    Even though this driver is written for 3 Volt Fast Boot
+ *    Block Flash Memory, it is rather specific to LART. With
+ *    Minor modifications, notably the without data/address line
+ *    mangling and different bus settings, etc. it should be
+ *    trivial to adapt to other platforms.
+ *
+ *    If somebody would sponsor me a different board, I'll
+ *    adapt the driver (:
+ */
+
+/* debugging */
+//#define LART_DEBUG
+
+/* partition support */
+#define HAVE_PARTITIONS
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/mtd/mtd.h>
+#ifdef HAVE_PARTITIONS
+#include <linux/mtd/partitions.h>
+#endif
+
+#ifndef CONFIG_SA1100_LART
+#error This is for LART architecture only
+#endif
+
+static char module_name[] = "lart";
+
+/*
+ * These values is specific to 28Fxxxx3 flash memory.
+ * See section 2.3.1 in "3 Volt Fast Boot Block Flash Memory" Intel Datasheet
+ */
+#define FLASH_BLOCKSIZE_PARAM		(4096 * BUSWIDTH)
+#define FLASH_NUMBLOCKS_16m_PARAM	8
+#define FLASH_NUMBLOCKS_8m_PARAM	8
+
+/*
+ * These values is specific to 28Fxxxx3 flash memory.
+ * See section 2.3.2 in "3 Volt Fast Boot Block Flash Memory" Intel Datasheet
+ */
+#define FLASH_BLOCKSIZE_MAIN		(32768 * BUSWIDTH)
+#define FLASH_NUMBLOCKS_16m_MAIN	31
+#define FLASH_NUMBLOCKS_8m_MAIN		15
+
+/*
+ * These values are specific to LART
+ */
+
+/* general */
+#define BUSWIDTH			4				/* don't change this - a lot of the code _will_ break if you change this */
+#define FLASH_OFFSET		0xe8000000		/* see linux/arch/arm/mach-sa1100/lart.c */
+
+/* blob */
+#define NUM_BLOB_BLOCKS		FLASH_NUMBLOCKS_16m_PARAM
+#define BLOB_START			0x00000000
+#define BLOB_LEN			(NUM_BLOB_BLOCKS * FLASH_BLOCKSIZE_PARAM)
+
+/* kernel */
+#define NUM_KERNEL_BLOCKS	7
+#define KERNEL_START		(BLOB_START + BLOB_LEN)
+#define KERNEL_LEN			(NUM_KERNEL_BLOCKS * FLASH_BLOCKSIZE_MAIN)
+
+/* initial ramdisk */
+#define NUM_INITRD_BLOCKS	24
+#define INITRD_START		(KERNEL_START + KERNEL_LEN)
+#define INITRD_LEN			(NUM_INITRD_BLOCKS * FLASH_BLOCKSIZE_MAIN)
+
+/*
+ * See section 4.0 in "3 Volt Fast Boot Block Flash Memory" Intel Datasheet
+ */
+#define READ_ARRAY			0x00FF00FF		/* Read Array/Reset */
+#define READ_ID_CODES		0x00900090		/* Read Identifier Codes */
+#define ERASE_SETUP			0x00200020		/* Block Erase */
+#define ERASE_CONFIRM		0x00D000D0		/* Block Erase and Program Resume */
+#define PGM_SETUP			0x00400040		/* Program */
+#define STATUS_READ			0x00700070		/* Read Status Register */
+#define STATUS_CLEAR		0x00500050		/* Clear Status Register */
+#define STATUS_BUSY			0x00800080		/* Write State Machine Status (WSMS) */
+#define STATUS_ERASE_ERR	0x00200020		/* Erase Status (ES) */
+#define STATUS_PGM_ERR		0x00100010		/* Program Status (PS) */
+
+/*
+ * See section 4.2 in "3 Volt Fast Boot Block Flash Memory" Intel Datasheet
+ */
+#define FLASH_MANUFACTURER			0x00890089
+#define FLASH_DEVICE_8mbit_TOP		0x88f188f1
+#define FLASH_DEVICE_8mbit_BOTTOM	0x88f288f2
+#define FLASH_DEVICE_16mbit_TOP		0x88f388f3
+#define FLASH_DEVICE_16mbit_BOTTOM	0x88f488f4
+
+/***************************************************************************************************/
+
+/*
+ * The data line mapping on LART is as follows:
+ * 
+ *   	 U2  CPU |   U3  CPU
+ *   	 -------------------
+ *   	  0  20  |   0   12
+ *   	  1  22  |   1   14
+ *   	  2  19  |   2   11
+ *   	  3  17  |   3   9
+ *   	  4  24  |   4   0
+ *   	  5  26  |   5   2
+ *   	  6  31  |   6   7
+ *   	  7  29  |   7   5
+ *   	  8  21  |   8   13
+ *   	  9  23  |   9   15
+ *   	  10 18  |   10  10
+ *   	  11 16  |   11  8
+ *   	  12 25  |   12  1
+ *   	  13 27  |   13  3
+ *   	  14 30  |   14  6
+ *   	  15 28  |   15  4
+ */
+
+/* Mangle data (x) */
+#define DATA_TO_FLASH(x)				\
+	(									\
+		(((x) & 0x08009000) >> 11)	+	\
+		(((x) & 0x00002000) >> 10)	+	\
+		(((x) & 0x04004000) >> 8)	+	\
+		(((x) & 0x00000010) >> 4)	+	\
+		(((x) & 0x91000820) >> 3)	+	\
+		(((x) & 0x22080080) >> 2)	+	\
+		((x) & 0x40000400)			+	\
+		(((x) & 0x00040040) << 1)	+	\
+		(((x) & 0x00110000) << 4)	+	\
+		(((x) & 0x00220100) << 5)	+	\
+		(((x) & 0x00800208) << 6)	+	\
+		(((x) & 0x00400004) << 9)	+	\
+		(((x) & 0x00000001) << 12)	+	\
+		(((x) & 0x00000002) << 13)		\
+	)
+
+/* Unmangle data (x) */
+#define FLASH_TO_DATA(x)				\
+	(									\
+		(((x) & 0x00010012) << 11)	+	\
+		(((x) & 0x00000008) << 10)	+	\
+		(((x) & 0x00040040) << 8)	+	\
+		(((x) & 0x00000001) << 4)	+	\
+		(((x) & 0x12200104) << 3)	+	\
+		(((x) & 0x08820020) << 2)	+	\
+		((x) & 0x40000400)			+	\
+		(((x) & 0x00080080) >> 1)	+	\
+		(((x) & 0x01100000) >> 4)	+	\
+		(((x) & 0x04402000) >> 5)	+	\
+		(((x) & 0x20008200) >> 6)	+	\
+		(((x) & 0x80000800) >> 9)	+	\
+		(((x) & 0x00001000) >> 12)	+	\
+		(((x) & 0x00004000) >> 13)		\
+	)
+
+/* 
+ * The address line mapping on LART is as follows:
+ *
+ *   	 U3  CPU |   U2  CPU
+ *   	 -------------------
+ *   	  0  2   |   0   2
+ *   	  1  3   |   1   3
+ *   	  2  9   |   2   9
+ *   	  3  13  |   3   8
+ *   	  4  8   |   4   7
+ *   	  5  12  |   5   6
+ *   	  6  11  |   6   5
+ *   	  7  10  |   7   4
+ *   	  8  4   |   8   10
+ *   	  9  5   |   9   11
+ *   	 10  6   |   10  12
+ *   	 11  7   |   11  13
+ *
+ *   	 BOOT BLOCK BOUNDARY
+ *
+ *   	 12  15  |   12  15
+ *   	 13  14  |   13  14
+ *   	 14  16  |   14  16
+ * 
+ *   	 MAIN BLOCK BOUNDARY
+ *
+ *   	 15  17  |   15  18
+ *   	 16  18  |   16  17
+ *   	 17  20  |   17  20
+ *   	 18  19  |   18  19
+ *   	 19  21  |   19  21
+ *
+ * As we can see from above, the addresses aren't mangled across
+ * block boundaries, so we don't need to worry about address
+ * translations except for sending/reading commands during
+ * initialization
+ */
+
+/* Mangle address (x) on chip U2 */
+#define ADDR_TO_FLASH_U2(x)				\
+	(									\
+		(((x) & 0x00000f00) >> 4)	+	\
+		(((x) & 0x00042000) << 1)	+	\
+		(((x) & 0x0009c003) << 2)	+	\
+		(((x) & 0x00021080) << 3)	+	\
+		(((x) & 0x00000010) << 4)	+	\
+		(((x) & 0x00000040) << 5)	+	\
+		(((x) & 0x00000024) << 7)	+	\
+		(((x) & 0x00000008) << 10)		\
+	)
+
+/* Unmangle address (x) on chip U2 */
+#define FLASH_U2_TO_ADDR(x)				\
+	(									\
+		(((x) << 4) & 0x00000f00)	+	\
+		(((x) >> 1) & 0x00042000)	+	\
+		(((x) >> 2) & 0x0009c003)	+	\
+		(((x) >> 3) & 0x00021080)	+	\
+		(((x) >> 4) & 0x00000010)	+	\
+		(((x) >> 5) & 0x00000040)	+	\
+		(((x) >> 7) & 0x00000024)	+	\
+		(((x) >> 10) & 0x00000008)		\
+	)
+
+/* Mangle address (x) on chip U3 */
+#define ADDR_TO_FLASH_U3(x)				\
+	(									\
+		(((x) & 0x00000080) >> 3)	+	\
+		(((x) & 0x00000040) >> 1)	+	\
+		(((x) & 0x00052020) << 1)	+	\
+		(((x) & 0x00084f03) << 2)	+	\
+		(((x) & 0x00029010) << 3)	+	\
+		(((x) & 0x00000008) << 5)	+	\
+		(((x) & 0x00000004) << 7)		\
+	)
+
+/* Unmangle address (x) on chip U3 */
+#define FLASH_U3_TO_ADDR(x)				\
+	(									\
+		(((x) << 3) & 0x00000080)	+	\
+		(((x) << 1) & 0x00000040)	+	\
+		(((x) >> 1) & 0x00052020)	+	\
+		(((x) >> 2) & 0x00084f03)	+	\
+		(((x) >> 3) & 0x00029010)	+	\
+		(((x) >> 5) & 0x00000008)	+	\
+		(((x) >> 7) & 0x00000004)		\
+	)
+
+/***************************************************************************************************/
+
+static __u8 read8 (__u32 offset)
+{
+   volatile __u8 *data = (__u8 *) (FLASH_OFFSET + offset);
+#ifdef LART_DEBUG
+   printk (KERN_DEBUG "%s(): 0x%.8x -> 0x%.2x\n",__FUNCTION__,offset,*data);
+#endif
+   return (*data);
+}
+
+static __u32 read32 (__u32 offset)
+{
+   volatile __u32 *data = (__u32 *) (FLASH_OFFSET + offset);
+#ifdef LART_DEBUG
+   printk (KERN_DEBUG "%s(): 0x%.8x -> 0x%.8x\n",__FUNCTION__,offset,*data);
+#endif
+   return (*data);
+}
+
+static void write32 (__u32 x,__u32 offset)
+{
+   volatile __u32 *data = (__u32 *) (FLASH_OFFSET + offset);
+   *data = x;
+#ifdef LART_DEBUG
+   printk (KERN_DEBUG "%s(): 0x%.8x <- 0x%.8x\n",__FUNCTION__,offset,*data);
+#endif
+}
+
+/***************************************************************************************************/
+
+/*
+ * Probe for 16mbit flash memory on a LART board without doing
+ * too much damage. Since we need to write 1 dword to memory,
+ * we're f**cked if this happens to be DRAM since we can't
+ * restore the memory (otherwise we might exit Read Array mode).
+ *
+ * Returns 1 if we found 16mbit flash memory on LART, 0 otherwise.
+ */
+static int flash_probe (void)
+{
+   __u32 manufacturer,devtype;
+
+   /* setup "Read Identifier Codes" mode */
+   write32 (DATA_TO_FLASH (READ_ID_CODES),0x00000000);
+
+   /* probe U2. U2/U3 returns the same data since the first 3
+	* address lines is mangled in the same way */
+   manufacturer = FLASH_TO_DATA (read32 (ADDR_TO_FLASH_U2 (0x00000000)));
+   devtype = FLASH_TO_DATA (read32 (ADDR_TO_FLASH_U2 (0x00000001)));
+
+   /* put the flash back into command mode */
+   write32 (DATA_TO_FLASH (READ_ARRAY),0x00000000);
+
+   return (manufacturer == FLASH_MANUFACTURER && (devtype == FLASH_DEVICE_16mbit_TOP || FLASH_DEVICE_16mbit_BOTTOM));
+}
+
+/*
+ * Erase one block of flash memory at offset ``offset'' which is any
+ * address within the block which should be erased.
+ *
+ * Returns 1 if successful, 0 otherwise.
+ */
+static inline int erase_block (__u32 offset)
+{
+   __u32 status;
+
+#ifdef LART_DEBUG
+   printk (KERN_DEBUG "%s(): 0x%.8x\n",__FUNCTION__,offset);
+#endif
+
+   /* erase and confirm */
+   write32 (DATA_TO_FLASH (ERASE_SETUP),offset);
+   write32 (DATA_TO_FLASH (ERASE_CONFIRM),offset);
+
+   /* wait for block erase to finish */
+   do
+	 {
+		write32 (DATA_TO_FLASH (STATUS_READ),offset);
+		status = FLASH_TO_DATA (read32 (offset));
+	 }
+   while ((~status & STATUS_BUSY) != 0);
+
+   /* put the flash back into command mode */
+   write32 (DATA_TO_FLASH (READ_ARRAY),offset);
+
+   /* was the erase successfull? */
+   if ((status & STATUS_ERASE_ERR))
+	 {
+		printk (KERN_WARNING "%s: erase error at address 0x%.8x.\n",module_name,offset);
+		return (0);
+	 }
+
+   return (1);
+}
+
+static int flash_erase (struct mtd_info *mtd,struct erase_info *instr)
+{
+   __u32 addr,len;
+   int i,first;
+
+#ifdef LART_DEBUG
+   printk (KERN_DEBUG "%s(addr = 0x%.8x, len = %d)\n",__FUNCTION__,instr->addr,instr->len);
+#endif
+
+   /* sanity checks */
+   if (instr->addr + instr->len > mtd->size) return (-EINVAL);
+
+   /*
+	* check that both start and end of the requested erase are
+	* aligned with the erasesize at the appropriate addresses.
+	*
+	* skip all erase regions which are ended before the start of
+	* the requested erase. Actually, to save on the calculations,
+	* we skip to the first erase region which starts after the
+	* start of the requested erase, and then go back one.
+	*/
+   for (i = 0; i < mtd->numeraseregions && instr->addr >= mtd->eraseregions[i].offset; i++) ;
+   i--;
+
+   /*
+	* ok, now i is pointing at the erase region in which this
+	* erase request starts. Check the start of the requested
+	* erase range is aligned with the erase size which is in
+	* effect here.
+	*/
+   if (instr->addr & (mtd->eraseregions[i].erasesize - 1)) return (-EINVAL);
+
+   /* Remember the erase region we start on */
+   first = i;
+
+   /*
+	* next, check that the end of the requested erase is aligned
+	* with the erase region at that address.
+	*
+	* as before, drop back one to point at the region in which
+	* the address actually falls
+	*/
+   for (; i < mtd->numeraseregions && instr->addr + instr->len >= mtd->eraseregions[i].offset; i++) ;
+   i--;
+
+   /* is the end aligned on a block boundary? */
+   if ((instr->addr + instr->len) & (mtd->eraseregions[i].erasesize - 1)) return (-EINVAL);
+
+   addr = instr->addr;
+   len = instr->len;
+
+   i = first;
+
+   /* now erase those blocks */
+   while (len)
+	 {
+		if (!erase_block (addr))
+		  {
+			 instr->state = MTD_ERASE_FAILED;
+			 return (-EIO);
+		  }
+
+		addr += mtd->eraseregions[i].erasesize;
+		len -= mtd->eraseregions[i].erasesize;
+
+		if (addr == mtd->eraseregions[i].offset + (mtd->eraseregions[i].erasesize * mtd->eraseregions[i].numblocks)) i++;
+	 }
+
+   instr->state = MTD_ERASE_DONE;
+   mtd_erase_callback(instr);
+
+   return (0);
+}
+
+static int flash_read (struct mtd_info *mtd,loff_t from,size_t len,size_t *retlen,u_char *buf)
+{
+#ifdef LART_DEBUG
+   printk (KERN_DEBUG "%s(from = 0x%.8x, len = %d)\n",__FUNCTION__,(__u32) from,len);
+#endif
+
+   /* sanity checks */
+   if (!len) return (0);
+   if (from + len > mtd->size) return (-EINVAL);
+
+   /* we always read len bytes */
+   *retlen = len;
+
+   /* first, we read bytes until we reach a dword boundary */
+   if (from & (BUSWIDTH - 1))
+	 {
+		int gap = BUSWIDTH - (from & (BUSWIDTH - 1));
+
+		while (len && gap--) *buf++ = read8 (from++), len--;
+	 }
+
+   /* now we read dwords until we reach a non-dword boundary */
+   while (len >= BUSWIDTH)
+	 {
+		*((__u32 *) buf) = read32 (from);
+
+		buf += BUSWIDTH;
+		from += BUSWIDTH;
+		len -= BUSWIDTH;
+	 }
+
+   /* top up the last unaligned bytes */
+   if (len & (BUSWIDTH - 1))
+	 while (len--) *buf++ = read8 (from++);
+
+   return (0);
+}
+
+/*
+ * Write one dword ``x'' to flash memory at offset ``offset''. ``offset''
+ * must be 32 bits, i.e. it must be on a dword boundary.
+ *
+ * Returns 1 if successful, 0 otherwise.
+ */
+static inline int write_dword (__u32 offset,__u32 x)
+{
+   __u32 status;
+
+#ifdef LART_DEBUG
+   printk (KERN_DEBUG "%s(): 0x%.8x <- 0x%.8x\n",__FUNCTION__,offset,x);
+#endif
+
+   /* setup writing */
+   write32 (DATA_TO_FLASH (PGM_SETUP),offset);
+
+   /* write the data */
+   write32 (x,offset);
+
+   /* wait for the write to finish */
+   do
+	 {
+		write32 (DATA_TO_FLASH (STATUS_READ),offset);
+		status = FLASH_TO_DATA (read32 (offset));
+	 }
+   while ((~status & STATUS_BUSY) != 0);
+
+   /* put the flash back into command mode */
+   write32 (DATA_TO_FLASH (READ_ARRAY),offset);
+
+   /* was the write successfull? */
+   if ((status & STATUS_PGM_ERR) || read32 (offset) != x)
+	 {
+		printk (KERN_WARNING "%s: write error at address 0x%.8x.\n",module_name,offset);
+		return (0);
+	 }
+
+   return (1);
+}
+
+static int flash_write (struct mtd_info *mtd,loff_t to,size_t len,size_t *retlen,const u_char *buf)
+{
+   __u8 tmp[4];
+   int i,n;
+
+#ifdef LART_DEBUG
+   printk (KERN_DEBUG "%s(to = 0x%.8x, len = %d)\n",__FUNCTION__,(__u32) to,len);
+#endif
+
+   *retlen = 0;
+
+   /* sanity checks */
+   if (!len) return (0);
+   if (to + len > mtd->size) return (-EINVAL);
+
+   /* first, we write a 0xFF.... padded byte until we reach a dword boundary */
+   if (to & (BUSWIDTH - 1))
+	 {
+		__u32 aligned = to & ~(BUSWIDTH - 1);
+		int gap = to - aligned;
+
+		i = n = 0;
+
+		while (gap--) tmp[i++] = 0xFF;
+		while (len && i < BUSWIDTH) tmp[i++] = buf[n++], len--;
+		while (i < BUSWIDTH) tmp[i++] = 0xFF;
+
+		if (!write_dword (aligned,*((__u32 *) tmp))) return (-EIO);
+
+		to += n;
+		buf += n;
+		*retlen += n;
+	 }
+
+   /* now we write dwords until we reach a non-dword boundary */
+   while (len >= BUSWIDTH)
+	 {
+		if (!write_dword (to,*((__u32 *) buf))) return (-EIO);
+
+		to += BUSWIDTH;
+		buf += BUSWIDTH;
+		*retlen += BUSWIDTH;
+		len -= BUSWIDTH;
+	 }
+
+   /* top up the last unaligned bytes, padded with 0xFF.... */
+   if (len & (BUSWIDTH - 1))
+	 {
+		i = n = 0;
+
+		while (len--) tmp[i++] = buf[n++];
+		while (i < BUSWIDTH) tmp[i++] = 0xFF;
+
+		if (!write_dword (to,*((__u32 *) tmp))) return (-EIO);
+
+		*retlen += n;
+	 }
+
+   return (0);
+}
+
+/***************************************************************************************************/
+
+#define NB_OF(x) (sizeof (x) / sizeof (x[0]))
+
+static struct mtd_info mtd;
+
+static struct mtd_erase_region_info erase_regions[] = {
+	/* parameter blocks */
+	{
+		.offset		= 0x00000000,
+		.erasesize	= FLASH_BLOCKSIZE_PARAM,
+		.numblocks	= FLASH_NUMBLOCKS_16m_PARAM,
+	},
+	/* main blocks */
+	{
+		.offset	 = FLASH_BLOCKSIZE_PARAM * FLASH_NUMBLOCKS_16m_PARAM,
+		.erasesize	= FLASH_BLOCKSIZE_MAIN,
+		.numblocks	= FLASH_NUMBLOCKS_16m_MAIN,
+	}
+};
+
+#ifdef HAVE_PARTITIONS
+static struct mtd_partition lart_partitions[] = {
+	/* blob */
+	{
+		.name	= "blob",
+		.offset	= BLOB_START,
+		.size	= BLOB_LEN,
+	},
+	/* kernel */
+	{
+		.name	= "kernel",
+		.offset	= KERNEL_START,		/* MTDPART_OFS_APPEND */
+		.size	= KERNEL_LEN,
+	},
+	/* initial ramdisk / file system */
+	{
+		.name	= "file system",
+		.offset	= INITRD_START,		/* MTDPART_OFS_APPEND */
+		.size	= INITRD_LEN,		/* MTDPART_SIZ_FULL */
+	}
+};
+#endif
+
+int __init lart_flash_init (void)
+{
+   int result;
+   memset (&mtd,0,sizeof (mtd));
+   printk ("MTD driver for LART. Written by Abraham vd Merwe <abraham@2d3d.co.za>\n");
+   printk ("%s: Probing for 28F160x3 flash on LART...\n",module_name);
+   if (!flash_probe ())
+	 {
+		printk (KERN_WARNING "%s: Found no LART compatible flash device\n",module_name);
+		return (-ENXIO);
+	 }
+   printk ("%s: This looks like a LART board to me.\n",module_name);
+   mtd.name = module_name;
+   mtd.type = MTD_NORFLASH;
+   mtd.flags = MTD_CAP_NORFLASH;
+   mtd.size = FLASH_BLOCKSIZE_PARAM * FLASH_NUMBLOCKS_16m_PARAM + FLASH_BLOCKSIZE_MAIN * FLASH_NUMBLOCKS_16m_MAIN;
+   mtd.erasesize = FLASH_BLOCKSIZE_MAIN;
+   mtd.numeraseregions = NB_OF (erase_regions);
+   mtd.eraseregions = erase_regions;
+   mtd.erase = flash_erase;
+   mtd.read = flash_read;
+   mtd.write = flash_write;
+   mtd.owner = THIS_MODULE;
+
+#ifdef LART_DEBUG
+   printk (KERN_DEBUG
+		   "mtd.name = %s\n"
+		   "mtd.size = 0x%.8x (%uM)\n"
+		   "mtd.erasesize = 0x%.8x (%uK)\n"
+		   "mtd.numeraseregions = %d\n",
+		   mtd.name,
+		   mtd.size,mtd.size / (1024*1024),
+		   mtd.erasesize,mtd.erasesize / 1024,
+		   mtd.numeraseregions);
+
+   if (mtd.numeraseregions)
+	 for (result = 0; result < mtd.numeraseregions; result++)
+	   printk (KERN_DEBUG
+			   "\n\n"
+			   "mtd.eraseregions[%d].offset = 0x%.8x\n"
+			   "mtd.eraseregions[%d].erasesize = 0x%.8x (%uK)\n"
+			   "mtd.eraseregions[%d].numblocks = %d\n",
+			   result,mtd.eraseregions[result].offset,
+			   result,mtd.eraseregions[result].erasesize,mtd.eraseregions[result].erasesize / 1024,
+			   result,mtd.eraseregions[result].numblocks);
+
+#ifdef HAVE_PARTITIONS
+   printk ("\npartitions = %d\n",NB_OF (lart_partitions));
+
+   for (result = 0; result < NB_OF (lart_partitions); result++)
+	 printk (KERN_DEBUG
+			 "\n\n"
+			 "lart_partitions[%d].name = %s\n"
+			 "lart_partitions[%d].offset = 0x%.8x\n"
+			 "lart_partitions[%d].size = 0x%.8x (%uK)\n",
+			 result,lart_partitions[result].name,
+			 result,lart_partitions[result].offset,
+			 result,lart_partitions[result].size,lart_partitions[result].size / 1024);
+#endif
+#endif
+
+#ifndef HAVE_PARTITIONS
+   result = add_mtd_device (&mtd);
+#else
+   result = add_mtd_partitions (&mtd,lart_partitions,NB_OF (lart_partitions));
+#endif
+
+   return (result);
+}
+
+void __exit lart_flash_exit (void)
+{
+#ifndef HAVE_PARTITIONS
+   del_mtd_device (&mtd);
+#else
+   del_mtd_partitions (&mtd);
+#endif
+}
+
+module_init (lart_flash_init);
+module_exit (lart_flash_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Abraham vd Merwe <abraham@2d3d.co.za>");
+MODULE_DESCRIPTION("MTD driver for Intel 28F160F3 on LART board");
+
+
diff --git a/drivers/mtd/devices/ms02-nv.c b/drivers/mtd/devices/ms02-nv.c
new file mode 100644
index 0000000..380ff08
--- /dev/null
+++ b/drivers/mtd/devices/ms02-nv.c
@@ -0,0 +1,326 @@
+/*
+ *	Copyright (c) 2001 Maciej W. Rozycki
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License
+ *	as published by the Free Software Foundation; either version
+ *	2 of the License, or (at your option) any later version.
+ *
+ *	$Id: ms02-nv.c,v 1.8 2005/01/05 18:05:12 dwmw2 Exp $
+ */
+
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include <asm/addrspace.h>
+#include <asm/bootinfo.h>
+#include <asm/dec/ioasic_addrs.h>
+#include <asm/dec/kn02.h>
+#include <asm/dec/kn03.h>
+#include <asm/io.h>
+#include <asm/paccess.h>
+
+#include "ms02-nv.h"
+
+
+static char version[] __initdata =
+	"ms02-nv.c: v.1.0.0  13 Aug 2001  Maciej W. Rozycki.\n";
+
+MODULE_AUTHOR("Maciej W. Rozycki <macro@linux-mips.org>");
+MODULE_DESCRIPTION("DEC MS02-NV NVRAM module driver");
+MODULE_LICENSE("GPL");
+
+
+/*
+ * Addresses we probe for an MS02-NV at.  Modules may be located
+ * at any 8MiB boundary within a 0MiB up to 112MiB range or at any 32MiB
+ * boundary within a 0MiB up to 448MiB range.  We don't support a module
+ * at 0MiB, though.
+ */
+static ulong ms02nv_addrs[] __initdata = {
+	0x07000000, 0x06800000, 0x06000000, 0x05800000, 0x05000000,
+	0x04800000, 0x04000000, 0x03800000, 0x03000000, 0x02800000,
+	0x02000000, 0x01800000, 0x01000000, 0x00800000
+};
+
+static const char ms02nv_name[] = "DEC MS02-NV NVRAM";
+static const char ms02nv_res_diag_ram[] = "Diagnostic RAM";
+static const char ms02nv_res_user_ram[] = "General-purpose RAM";
+static const char ms02nv_res_csr[] = "Control and status register";
+
+static struct mtd_info *root_ms02nv_mtd;
+
+
+static int ms02nv_read(struct mtd_info *mtd, loff_t from,
+			size_t len, size_t *retlen, u_char *buf)
+{
+	struct ms02nv_private *mp = mtd->priv;
+
+	if (from + len > mtd->size)
+		return -EINVAL;
+
+	memcpy(buf, mp->uaddr + from, len);
+	*retlen = len;
+
+	return 0;
+}
+
+static int ms02nv_write(struct mtd_info *mtd, loff_t to,
+			size_t len, size_t *retlen, const u_char *buf)
+{
+	struct ms02nv_private *mp = mtd->priv;
+
+	if (to + len > mtd->size)
+		return -EINVAL;
+
+	memcpy(mp->uaddr + to, buf, len);
+	*retlen = len;
+
+	return 0;
+}
+
+
+static inline uint ms02nv_probe_one(ulong addr)
+{
+	ms02nv_uint *ms02nv_diagp;
+	ms02nv_uint *ms02nv_magicp;
+	uint ms02nv_diag;
+	uint ms02nv_magic;
+	size_t size;
+
+	int err;
+
+	/*
+	 * The firmware writes MS02NV_ID at MS02NV_MAGIC and also
+	 * a diagnostic status at MS02NV_DIAG.
+	 */
+	ms02nv_diagp = (ms02nv_uint *)(KSEG1ADDR(addr + MS02NV_DIAG));
+	ms02nv_magicp = (ms02nv_uint *)(KSEG1ADDR(addr + MS02NV_MAGIC));
+	err = get_dbe(ms02nv_magic, ms02nv_magicp);
+	if (err)
+		return 0;
+	if (ms02nv_magic != MS02NV_ID)
+		return 0;
+
+	ms02nv_diag = *ms02nv_diagp;
+	size = (ms02nv_diag & MS02NV_DIAG_SIZE_MASK) << MS02NV_DIAG_SIZE_SHIFT;
+	if (size > MS02NV_CSR)
+		size = MS02NV_CSR;
+
+	return size;
+}
+
+static int __init ms02nv_init_one(ulong addr)
+{
+	struct mtd_info *mtd;
+	struct ms02nv_private *mp;
+	struct resource *mod_res;
+	struct resource *diag_res;
+	struct resource *user_res;
+	struct resource *csr_res;
+	ulong fixaddr;
+	size_t size, fixsize;
+
+	static int version_printed;
+
+	int ret = -ENODEV;
+
+	/* The module decodes 8MiB of address space. */
+	mod_res = kmalloc(sizeof(*mod_res), GFP_KERNEL);
+	if (!mod_res)
+		return -ENOMEM;
+
+	memset(mod_res, 0, sizeof(*mod_res));
+	mod_res->name = ms02nv_name;
+	mod_res->start = addr;
+	mod_res->end = addr + MS02NV_SLOT_SIZE - 1;
+	mod_res->flags = IORESOURCE_MEM | IORESOURCE_BUSY;
+	if (request_resource(&iomem_resource, mod_res) < 0)
+		goto err_out_mod_res;
+
+	size = ms02nv_probe_one(addr);
+	if (!size)
+		goto err_out_mod_res_rel;
+
+	if (!version_printed) {
+		printk(KERN_INFO "%s", version);
+		version_printed = 1;
+	}
+
+	ret = -ENOMEM;
+	mtd = kmalloc(sizeof(*mtd), GFP_KERNEL);
+	if (!mtd)
+		goto err_out_mod_res_rel;
+	memset(mtd, 0, sizeof(*mtd));
+	mp = kmalloc(sizeof(*mp), GFP_KERNEL);
+	if (!mp)
+		goto err_out_mtd;
+	memset(mp, 0, sizeof(*mp));
+
+	mtd->priv = mp;
+	mp->resource.module = mod_res;
+
+	/* Firmware's diagnostic NVRAM area. */
+	diag_res = kmalloc(sizeof(*diag_res), GFP_KERNEL);
+	if (!diag_res)
+		goto err_out_mp;
+
+	memset(diag_res, 0, sizeof(*diag_res));
+	diag_res->name = ms02nv_res_diag_ram;
+	diag_res->start = addr;
+	diag_res->end = addr + MS02NV_RAM - 1;
+	diag_res->flags = IORESOURCE_BUSY;
+	request_resource(mod_res, diag_res);
+
+	mp->resource.diag_ram = diag_res;
+
+	/* User-available general-purpose NVRAM area. */
+	user_res = kmalloc(sizeof(*user_res), GFP_KERNEL);
+	if (!user_res)
+		goto err_out_diag_res;
+
+	memset(user_res, 0, sizeof(*user_res));
+	user_res->name = ms02nv_res_user_ram;
+	user_res->start = addr + MS02NV_RAM;
+	user_res->end = addr + size - 1;
+	user_res->flags = IORESOURCE_BUSY;
+	request_resource(mod_res, user_res);
+
+	mp->resource.user_ram = user_res;
+
+	/* Control and status register. */
+	csr_res = kmalloc(sizeof(*csr_res), GFP_KERNEL);
+	if (!csr_res)
+		goto err_out_user_res;
+
+	memset(csr_res, 0, sizeof(*csr_res));
+	csr_res->name = ms02nv_res_csr;
+	csr_res->start = addr + MS02NV_CSR;
+	csr_res->end = addr + MS02NV_CSR + 3;
+	csr_res->flags = IORESOURCE_BUSY;
+	request_resource(mod_res, csr_res);
+
+	mp->resource.csr = csr_res;
+
+	mp->addr = phys_to_virt(addr);
+	mp->size = size;
+
+	/*
+	 * Hide the firmware's diagnostic area.  It may get destroyed
+	 * upon a reboot.  Take paging into account for mapping support.
+	 */
+	fixaddr = (addr + MS02NV_RAM + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
+	fixsize = (size - (fixaddr - addr)) & ~(PAGE_SIZE - 1);
+	mp->uaddr = phys_to_virt(fixaddr);
+
+	mtd->type = MTD_RAM;
+	mtd->flags = MTD_CAP_RAM | MTD_XIP;
+	mtd->size = fixsize;
+	mtd->name = (char *)ms02nv_name;
+	mtd->owner = THIS_MODULE;
+	mtd->read = ms02nv_read;
+	mtd->write = ms02nv_write;
+
+	ret = -EIO;
+	if (add_mtd_device(mtd)) {
+		printk(KERN_ERR
+			"ms02-nv: Unable to register MTD device, aborting!\n");
+		goto err_out_csr_res;
+	}
+
+	printk(KERN_INFO "mtd%d: %s at 0x%08lx, size %uMiB.\n",
+		mtd->index, ms02nv_name, addr, size >> 20);
+
+	mp->next = root_ms02nv_mtd;
+	root_ms02nv_mtd = mtd;
+
+	return 0;
+
+
+err_out_csr_res:
+	release_resource(csr_res);
+	kfree(csr_res);
+err_out_user_res:
+	release_resource(user_res);
+	kfree(user_res);
+err_out_diag_res:
+	release_resource(diag_res);
+	kfree(diag_res);
+err_out_mp:
+	kfree(mp);
+err_out_mtd:
+	kfree(mtd);
+err_out_mod_res_rel:
+	release_resource(mod_res);
+err_out_mod_res:
+	kfree(mod_res);
+	return ret;
+}
+
+static void __exit ms02nv_remove_one(void)
+{
+	struct mtd_info *mtd = root_ms02nv_mtd;
+	struct ms02nv_private *mp = mtd->priv;
+
+	root_ms02nv_mtd = mp->next;
+
+	del_mtd_device(mtd);
+
+	release_resource(mp->resource.csr);
+	kfree(mp->resource.csr);
+	release_resource(mp->resource.user_ram);
+	kfree(mp->resource.user_ram);
+	release_resource(mp->resource.diag_ram);
+	kfree(mp->resource.diag_ram);
+	release_resource(mp->resource.module);
+	kfree(mp->resource.module);
+	kfree(mp);
+	kfree(mtd);
+}
+
+
+static int __init ms02nv_init(void)
+{
+	volatile u32 *csr;
+	uint stride = 0;
+	int count = 0;
+	int i;
+
+	switch (mips_machtype) {
+	case MACH_DS5000_200:
+		csr = (volatile u32 *)KN02_CSR_BASE;
+		if (*csr & KN02_CSR_BNK32M)
+			stride = 2;
+		break;
+	case MACH_DS5000_2X0:
+	case MACH_DS5900:
+		csr = (volatile u32 *)KN03_MCR_BASE;
+		if (*csr & KN03_MCR_BNK32M)
+			stride = 2;
+		break;
+	default:
+		return -ENODEV;
+		break;
+	}
+
+	for (i = 0; i < (sizeof(ms02nv_addrs) / sizeof(*ms02nv_addrs)); i++)
+		if (!ms02nv_init_one(ms02nv_addrs[i] << stride))
+			count++;
+
+	return (count > 0) ? 0 : -ENODEV;
+}
+
+static void __exit ms02nv_cleanup(void)
+{
+	while (root_ms02nv_mtd)
+		ms02nv_remove_one();
+}
+
+
+module_init(ms02nv_init);
+module_exit(ms02nv_cleanup);
diff --git a/drivers/mtd/devices/ms02-nv.h b/drivers/mtd/devices/ms02-nv.h
new file mode 100644
index 0000000..8a6eef7
--- /dev/null
+++ b/drivers/mtd/devices/ms02-nv.h
@@ -0,0 +1,107 @@
+/*
+ *	Copyright (c) 2001, 2003  Maciej W. Rozycki
+ *
+ *	DEC MS02-NV (54-20948-01) battery backed-up NVRAM module for
+ *	DECstation/DECsystem 5000/2x0 and DECsystem 5900 and 5900/260
+ *	systems.
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License
+ *	as published by the Free Software Foundation; either version
+ *	2 of the License, or (at your option) any later version.
+ *
+ *	$Id: ms02-nv.h,v 1.3 2003/08/19 09:25:36 dwmw2 Exp $
+ */
+
+#include <linux/ioport.h>
+#include <linux/mtd/mtd.h>
+
+/*
+ * Addresses are decoded as follows:
+ *
+ * 0x000000 - 0x3fffff	SRAM
+ * 0x400000 - 0x7fffff	CSR
+ *
+ * Within the SRAM area the following ranges are forced by the system
+ * firmware:
+ *
+ * 0x000000 - 0x0003ff	diagnostic area, destroyed upon a reboot
+ * 0x000400 - ENDofRAM	storage area, available to operating systems
+ *
+ * but we can't really use the available area right from 0x000400 as
+ * the first word is used by the firmware as a status flag passed
+ * from an operating system.  If anything but the valid data magic
+ * ID value is found, the firmware considers the SRAM clean, i.e.
+ * containing no valid data, and disables the battery resulting in
+ * data being erased as soon as power is switched off.  So the choice
+ * for the start address of the user-available is 0x001000 which is
+ * nicely page aligned.  The area between 0x000404 and 0x000fff may
+ * be used by the driver for own needs.
+ *
+ * The diagnostic area defines two status words to be read by an
+ * operating system, a magic ID to distinguish a MS02-NV board from
+ * anything else and a status information providing results of tests
+ * as well as the size of SRAM available, which can be 1MiB or 2MiB
+ * (that's what the firmware handles; no idea if 2MiB modules ever
+ * existed).
+ *
+ * The firmware only handles the MS02-NV board if installed in the
+ * last (15th) slot, so for any other location the status information
+ * stored in the SRAM cannot be relied upon.  But from the hardware
+ * point of view there is no problem using up to 14 such boards in a
+ * system -- only the 1st slot needs to be filled with a DRAM module.
+ * The MS02-NV board is ECC-protected, like other MS02 memory boards.
+ *
+ * The state of the battery as provided by the CSR is reflected on
+ * the two onboard LEDs.  When facing the battery side of the board,
+ * with the LEDs at the top left and the battery at the bottom right
+ * (i.e. looking from the back side of the system box), their meaning
+ * is as follows (the system has to be powered on):
+ *
+ * left LED		battery disable status: lit = enabled
+ * right LED		battery condition status: lit = OK
+ */
+
+/* MS02-NV iomem register offsets. */
+#define MS02NV_CSR		0x400000	/* control & status register */
+
+/* MS02-NV CSR status bits. */
+#define MS02NV_CSR_BATT_OK	0x01		/* battery OK */
+#define MS02NV_CSR_BATT_OFF	0x02		/* battery disabled */
+
+
+/* MS02-NV memory offsets. */
+#define MS02NV_DIAG		0x0003f8	/* diagnostic status */
+#define MS02NV_MAGIC		0x0003fc	/* MS02-NV magic ID */
+#define MS02NV_VALID		0x000400	/* valid data magic ID */
+#define MS02NV_RAM		0x001000	/* user-exposed RAM start */
+
+/* MS02-NV diagnostic status bits. */
+#define MS02NV_DIAG_TEST	0x01		/* SRAM test done (?) */
+#define MS02NV_DIAG_RO		0x02		/* SRAM r/o test done */
+#define MS02NV_DIAG_RW		0x04		/* SRAM r/w test done */
+#define MS02NV_DIAG_FAIL	0x08		/* SRAM test failed */
+#define MS02NV_DIAG_SIZE_MASK	0xf0		/* SRAM size mask */
+#define MS02NV_DIAG_SIZE_SHIFT	0x10		/* SRAM size shift (left) */
+
+/* MS02-NV general constants. */
+#define MS02NV_ID		0x03021966	/* MS02-NV magic ID value */
+#define MS02NV_VALID_ID		0xbd100248	/* valid data magic ID value */
+#define MS02NV_SLOT_SIZE	0x800000	/* size of the address space
+						   decoded by the module */
+
+
+typedef volatile u32 ms02nv_uint;
+
+struct ms02nv_private {
+	struct mtd_info *next;
+	struct {
+		struct resource *module;
+		struct resource *diag_ram;
+		struct resource *user_ram;
+		struct resource *csr;
+	} resource;
+	u_char *addr;
+	size_t size;
+	u_char *uaddr;
+};
diff --git a/drivers/mtd/devices/mtdram.c b/drivers/mtd/devices/mtdram.c
new file mode 100644
index 0000000..edac415
--- /dev/null
+++ b/drivers/mtd/devices/mtdram.c
@@ -0,0 +1,235 @@
+/*
+ * mtdram - a test mtd device
+ * $Id: mtdram.c,v 1.35 2005/01/05 18:05:12 dwmw2 Exp $
+ * Author: Alexander Larsson <alex@cendio.se>
+ *
+ * Copyright (c) 1999 Alexander Larsson <alex@cendio.se>
+ *
+ * This code is GPL
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/vmalloc.h>
+#include <linux/init.h>
+#include <linux/mtd/compatmac.h>
+#include <linux/mtd/mtd.h>
+
+#ifndef CONFIG_MTDRAM_ABS_POS
+  #define CONFIG_MTDRAM_ABS_POS 0
+#endif
+
+#if CONFIG_MTDRAM_ABS_POS > 0
+  #include <asm/io.h>
+#endif
+
+#ifdef MODULE
+static unsigned long total_size = CONFIG_MTDRAM_TOTAL_SIZE;
+static unsigned long erase_size = CONFIG_MTDRAM_ERASE_SIZE;
+module_param(total_size,ulong,0);
+MODULE_PARM_DESC(total_size, "Total device size in KiB");
+module_param(erase_size,ulong,0);
+MODULE_PARM_DESC(erase_size, "Device erase block size in KiB");
+#define MTDRAM_TOTAL_SIZE (total_size * 1024)
+#define MTDRAM_ERASE_SIZE (erase_size * 1024)
+#else
+#define MTDRAM_TOTAL_SIZE (CONFIG_MTDRAM_TOTAL_SIZE * 1024)
+#define MTDRAM_ERASE_SIZE (CONFIG_MTDRAM_ERASE_SIZE * 1024)
+#endif
+
+
+// We could store these in the mtd structure, but we only support 1 device..
+static struct mtd_info *mtd_info;
+
+
+static int
+ram_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+  DEBUG(MTD_DEBUG_LEVEL2, "ram_erase(pos:%ld, len:%ld)\n", (long)instr->addr, (long)instr->len);
+  if (instr->addr + instr->len > mtd->size) {
+    DEBUG(MTD_DEBUG_LEVEL1, "ram_erase() out of bounds (%ld > %ld)\n", (long)(instr->addr + instr->len), (long)mtd->size);
+    return -EINVAL;
+  }
+	
+  memset((char *)mtd->priv + instr->addr, 0xff, instr->len);
+	
+  instr->state = MTD_ERASE_DONE;
+  mtd_erase_callback(instr);
+
+  return 0;
+}
+
+static int ram_point (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf)
+{
+  if (from + len > mtd->size)
+    return -EINVAL;
+	
+  *mtdbuf = mtd->priv + from;
+  *retlen = len;
+  return 0;
+}
+
+static void ram_unpoint (struct mtd_info *mtd, u_char *addr, loff_t from,
+			 size_t len)
+{
+  DEBUG(MTD_DEBUG_LEVEL2, "ram_unpoint\n");
+}
+
+static int ram_read(struct mtd_info *mtd, loff_t from, size_t len,
+	     size_t *retlen, u_char *buf)
+{
+  DEBUG(MTD_DEBUG_LEVEL2, "ram_read(pos:%ld, len:%ld)\n", (long)from, (long)len);
+  if (from + len > mtd->size) {
+    DEBUG(MTD_DEBUG_LEVEL1, "ram_read() out of bounds (%ld > %ld)\n", (long)(from + len), (long)mtd->size);
+    return -EINVAL;
+  }
+
+  memcpy(buf, mtd->priv + from, len);
+
+  *retlen=len;
+  return 0;
+}
+
+static int ram_write(struct mtd_info *mtd, loff_t to, size_t len,
+	      size_t *retlen, const u_char *buf)
+{
+  DEBUG(MTD_DEBUG_LEVEL2, "ram_write(pos:%ld, len:%ld)\n", (long)to, (long)len);
+  if (to + len > mtd->size) {
+    DEBUG(MTD_DEBUG_LEVEL1, "ram_write() out of bounds (%ld > %ld)\n", (long)(to + len), (long)mtd->size);
+    return -EINVAL;
+  }
+
+  memcpy ((char *)mtd->priv + to, buf, len);
+
+  *retlen=len;
+  return 0;
+}
+
+static void __exit cleanup_mtdram(void)
+{
+  if (mtd_info) {
+    del_mtd_device(mtd_info);
+#if CONFIG_MTDRAM_TOTAL_SIZE > 0
+    if (mtd_info->priv)
+#if CONFIG_MTDRAM_ABS_POS > 0
+      iounmap(mtd_info->priv);
+#else
+      vfree(mtd_info->priv);
+#endif	
+#endif
+    kfree(mtd_info);
+  }
+}
+
+int mtdram_init_device(struct mtd_info *mtd, void *mapped_address, 
+                       unsigned long size, char *name)
+{
+   memset(mtd, 0, sizeof(*mtd));
+
+   /* Setup the MTD structure */
+   mtd->name = name;
+   mtd->type = MTD_RAM;
+   mtd->flags = MTD_CAP_RAM;
+   mtd->size = size;
+   mtd->erasesize = MTDRAM_ERASE_SIZE;
+   mtd->priv = mapped_address;
+
+   mtd->owner = THIS_MODULE;
+   mtd->erase = ram_erase;
+   mtd->point = ram_point;
+   mtd->unpoint = ram_unpoint;
+   mtd->read = ram_read;
+   mtd->write = ram_write;
+
+   if (add_mtd_device(mtd)) {
+     return -EIO;
+   }
+   
+   return 0;
+}
+
+#if CONFIG_MTDRAM_TOTAL_SIZE > 0
+#if CONFIG_MTDRAM_ABS_POS > 0
+static int __init init_mtdram(void)
+{
+  void *addr;
+  int err;
+  /* Allocate some memory */
+   mtd_info = kmalloc(sizeof(struct mtd_info), GFP_KERNEL);
+   if (!mtd_info)
+     return -ENOMEM;
+   
+  addr = ioremap(CONFIG_MTDRAM_ABS_POS, MTDRAM_TOTAL_SIZE);
+  if (!addr) {
+    DEBUG(MTD_DEBUG_LEVEL1, 
+          "Failed to ioremap) memory region of size %ld at ABS_POS:%ld\n", 
+          (long)MTDRAM_TOTAL_SIZE, (long)CONFIG_MTDRAM_ABS_POS);
+    kfree(mtd_info);
+    mtd_info = NULL;
+    return -ENOMEM;
+  }
+  err = mtdram_init_device(mtd_info, addr, 
+                           MTDRAM_TOTAL_SIZE, "mtdram test device");
+  if (err) 
+  {
+    iounmap(addr);
+    kfree(mtd_info);
+    mtd_info = NULL;
+    return err;
+  }
+  memset(mtd_info->priv, 0xff, MTDRAM_TOTAL_SIZE);
+  return err;
+}
+
+#else /* CONFIG_MTDRAM_ABS_POS > 0 */
+
+static int __init init_mtdram(void)
+{
+  void *addr;
+  int err;
+  /* Allocate some memory */
+   mtd_info = kmalloc(sizeof(struct mtd_info), GFP_KERNEL);
+   if (!mtd_info)
+     return -ENOMEM;
+
+  addr = vmalloc(MTDRAM_TOTAL_SIZE);
+  if (!addr) {
+    DEBUG(MTD_DEBUG_LEVEL1, 
+          "Failed to vmalloc memory region of size %ld\n", 
+          (long)MTDRAM_TOTAL_SIZE);
+    kfree(mtd_info);
+    mtd_info = NULL;
+    return -ENOMEM;
+  }
+  err = mtdram_init_device(mtd_info, addr, 
+                           MTDRAM_TOTAL_SIZE, "mtdram test device");
+  if (err) 
+  {
+    vfree(addr);
+    kfree(mtd_info);
+    mtd_info = NULL;
+    return err;
+  }
+  memset(mtd_info->priv, 0xff, MTDRAM_TOTAL_SIZE);
+  return err;
+}
+#endif /* !(CONFIG_MTDRAM_ABS_POS > 0) */
+
+#else /* CONFIG_MTDRAM_TOTAL_SIZE > 0 */
+
+static int __init init_mtdram(void)
+{
+  return 0;
+}
+#endif /* !(CONFIG_MTDRAM_TOTAL_SIZE > 0) */
+
+module_init(init_mtdram);
+module_exit(cleanup_mtdram);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Alexander Larsson <alexl@redhat.com>");
+MODULE_DESCRIPTION("Simulated MTD driver for testing");
+
diff --git a/drivers/mtd/devices/phram.c b/drivers/mtd/devices/phram.c
new file mode 100644
index 0000000..5f8e164
--- /dev/null
+++ b/drivers/mtd/devices/phram.c
@@ -0,0 +1,285 @@
+/**
+ * $Id: phram.c,v 1.11 2005/01/05 18:05:13 dwmw2 Exp $
+ *
+ * Copyright (c) ????		Jochen Schäuble <psionic@psionic.de>
+ * Copyright (c) 2003-2004	Jörn Engel <joern@wh.fh-wedel.de>
+ *
+ * Usage:
+ *
+ * one commend line parameter per device, each in the form:
+ *   phram=<name>,<start>,<len>
+ * <name> may be up to 63 characters.
+ * <start> and <len> can be octal, decimal or hexadecimal.  If followed
+ * by "ki", "Mi" or "Gi", the numbers will be interpreted as kilo, mega or
+ * gigabytes.
+ *
+ * Example:
+ *	phram=swap,64Mi,128Mi phram=test,900Mi,1Mi
+ *
+ */
+
+#include <asm/io.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mtd/mtd.h>
+
+#define ERROR(fmt, args...) printk(KERN_ERR "phram: " fmt , ## args)
+
+struct phram_mtd_list {
+	struct mtd_info mtd;
+	struct list_head list;
+};
+
+static LIST_HEAD(phram_list);
+
+
+
+static int phram_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+	u_char *start = mtd->priv;
+
+	if (instr->addr + instr->len > mtd->size)
+		return -EINVAL;
+	
+	memset(start + instr->addr, 0xff, instr->len);
+
+	/* This'll catch a few races. Free the thing before returning :) 
+	 * I don't feel at all ashamed. This kind of thing is possible anyway
+	 * with flash, but unlikely.
+	 */
+
+	instr->state = MTD_ERASE_DONE;
+
+	mtd_erase_callback(instr);
+
+	return 0;
+}
+
+static int phram_point(struct mtd_info *mtd, loff_t from, size_t len,
+		size_t *retlen, u_char **mtdbuf)
+{
+	u_char *start = mtd->priv;
+
+	if (from + len > mtd->size)
+		return -EINVAL;
+	
+	*mtdbuf = start + from;
+	*retlen = len;
+	return 0;
+}
+
+static void phram_unpoint(struct mtd_info *mtd, u_char *addr, loff_t from, size_t len)
+{
+}
+
+static int phram_read(struct mtd_info *mtd, loff_t from, size_t len,
+		size_t *retlen, u_char *buf)
+{
+	u_char *start = mtd->priv;
+
+	if (from + len > mtd->size)
+		return -EINVAL;
+	
+	memcpy(buf, start + from, len);
+
+	*retlen = len;
+	return 0;
+}
+
+static int phram_write(struct mtd_info *mtd, loff_t to, size_t len,
+		size_t *retlen, const u_char *buf)
+{
+	u_char *start = mtd->priv;
+
+	if (to + len > mtd->size)
+		return -EINVAL;
+	
+	memcpy(start + to, buf, len);
+
+	*retlen = len;
+	return 0;
+}
+
+
+
+static void unregister_devices(void)
+{
+	struct phram_mtd_list *this;
+
+	list_for_each_entry(this, &phram_list, list) {
+		del_mtd_device(&this->mtd);
+		iounmap(this->mtd.priv);
+		kfree(this);
+	}
+}
+
+static int register_device(char *name, unsigned long start, unsigned long len)
+{
+	struct phram_mtd_list *new;
+	int ret = -ENOMEM;
+
+	new = kmalloc(sizeof(*new), GFP_KERNEL);
+	if (!new)
+		goto out0;
+
+	memset(new, 0, sizeof(*new));
+
+	ret = -EIO;
+	new->mtd.priv = ioremap(start, len);
+	if (!new->mtd.priv) {
+		ERROR("ioremap failed\n");
+		goto out1;
+	}
+
+
+	new->mtd.name = name;
+	new->mtd.size = len;
+	new->mtd.flags = MTD_CAP_RAM | MTD_ERASEABLE | MTD_VOLATILE;
+        new->mtd.erase = phram_erase;
+	new->mtd.point = phram_point;
+	new->mtd.unpoint = phram_unpoint;
+	new->mtd.read = phram_read;
+	new->mtd.write = phram_write;
+	new->mtd.owner = THIS_MODULE;
+	new->mtd.type = MTD_RAM;
+	new->mtd.erasesize = 0;
+
+	ret = -EAGAIN;
+	if (add_mtd_device(&new->mtd)) {
+		ERROR("Failed to register new device\n");
+		goto out2;
+	}
+
+	list_add_tail(&new->list, &phram_list);
+	return 0;	
+
+out2:
+	iounmap(new->mtd.priv);
+out1:
+	kfree(new);
+out0:
+	return ret;
+}
+
+static int ustrtoul(const char *cp, char **endp, unsigned int base)
+{
+	unsigned long result = simple_strtoul(cp, endp, base);
+
+	switch (**endp) {
+	case 'G':
+		result *= 1024;
+	case 'M':
+		result *= 1024;
+	case 'k':
+		result *= 1024;
+	/* By dwmw2 editorial decree, "ki", "Mi" or "Gi" are to be used. */
+		if ((*endp)[1] == 'i')
+			(*endp) += 2;
+	}
+	return result;
+}
+
+static int parse_num32(uint32_t *num32, const char *token)
+{
+	char *endp;
+	unsigned long n;
+
+	n = ustrtoul(token, &endp, 0);
+	if (*endp)
+		return -EINVAL;
+
+	*num32 = n;
+	return 0;
+}
+
+static int parse_name(char **pname, const char *token)
+{
+	size_t len;
+	char *name;
+
+	len = strlen(token) + 1;
+	if (len > 64)
+		return -ENOSPC;
+
+	name = kmalloc(len, GFP_KERNEL);
+	if (!name)
+		return -ENOMEM;
+
+	strcpy(name, token);
+
+	*pname = name;
+	return 0;
+}
+
+#define parse_err(fmt, args...) do {	\
+	ERROR(fmt , ## args);	\
+	return 0;		\
+} while (0)
+
+static int phram_setup(const char *val, struct kernel_param *kp)
+{
+	char buf[64+12+12], *str = buf;
+	char *token[3];
+	char *name;
+	uint32_t start;
+	uint32_t len;
+	int i, ret;
+
+	if (strnlen(val, sizeof(buf)) >= sizeof(buf))
+		parse_err("parameter too long\n");
+
+	strcpy(str, val);
+
+	for (i=0; i<3; i++)
+		token[i] = strsep(&str, ",");
+
+	if (str)
+		parse_err("too many arguments\n");
+
+	if (!token[2])
+		parse_err("not enough arguments\n");
+
+	ret = parse_name(&name, token[0]);
+	if (ret == -ENOMEM)
+		parse_err("out of memory\n");
+	if (ret == -ENOSPC)
+		parse_err("name too long\n");
+	if (ret)
+		return 0;
+
+	ret = parse_num32(&start, token[1]);
+	if (ret)
+		parse_err("illegal start address\n");
+
+	ret = parse_num32(&len, token[2]);
+	if (ret)
+		parse_err("illegal device length\n");
+
+	register_device(name, start, len);
+
+	return 0;
+}
+
+module_param_call(phram, phram_setup, NULL, NULL, 000);
+MODULE_PARM_DESC(phram,"Memory region to map. \"map=<name>,<start>,<length>\"");
+
+
+static int __init init_phram(void)
+{
+	return 0;
+}
+
+static void __exit cleanup_phram(void)
+{
+	unregister_devices();
+}
+
+module_init(init_phram);
+module_exit(cleanup_phram);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jörn Engel <joern@wh.fh-wedel.de>");
+MODULE_DESCRIPTION("MTD driver for physical RAM");
diff --git a/drivers/mtd/devices/pmc551.c b/drivers/mtd/devices/pmc551.c
new file mode 100644
index 0000000..5b3defa
--- /dev/null
+++ b/drivers/mtd/devices/pmc551.c
@@ -0,0 +1,843 @@
+/*
+ * $Id: pmc551.c,v 1.30 2005/01/05 18:05:13 dwmw2 Exp $
+ *
+ * PMC551 PCI Mezzanine Ram Device
+ *
+ * Author:
+ *       Mark Ferrell <mferrell@mvista.com>
+ *       Copyright 1999,2000 Nortel Networks
+ *
+ * License:
+ *	 As part of this driver was derived from the slram.c driver it
+ *	 falls under the same license, which is GNU General Public
+ *	 License v2
+ *
+ * Description:
+ *	 This driver is intended to support the PMC551 PCI Ram device
+ *	 from Ramix Inc.  The PMC551 is a PMC Mezzanine module for
+ *	 cPCI embedded systems.  The device contains a single SROM
+ *	 that initially programs the V370PDC chipset onboard the
+ *	 device, and various banks of DRAM/SDRAM onboard.  This driver
+ *	 implements this PCI Ram device as an MTD (Memory Technology
+ *	 Device) so that it can be used to hold a file system, or for
+ *	 added swap space in embedded systems.  Since the memory on
+ *	 this board isn't as fast as main memory we do not try to hook
+ *	 it into main memory as that would simply reduce performance
+ *	 on the system.  Using it as a block device allows us to use
+ *	 it as high speed swap or for a high speed disk device of some
+ *	 sort.  Which becomes very useful on diskless systems in the
+ *	 embedded market I might add.
+ *	 
+ * Notes:
+ *	 Due to what I assume is more buggy SROM, the 64M PMC551 I
+ *	 have available claims that all 4 of it's DRAM banks have 64M
+ *	 of ram configured (making a grand total of 256M onboard).
+ *	 This is slightly annoying since the BAR0 size reflects the
+ *	 aperture size, not the dram size, and the V370PDC supplies no
+ *	 other method for memory size discovery.  This problem is
+ *	 mostly only relevant when compiled as a module, as the
+ *	 unloading of the module with an aperture size smaller then
+ *	 the ram will cause the driver to detect the onboard memory
+ *	 size to be equal to the aperture size when the module is
+ *	 reloaded.  Soooo, to help, the module supports an msize
+ *	 option to allow the specification of the onboard memory, and
+ *	 an asize option, to allow the specification of the aperture
+ *	 size.  The aperture must be equal to or less then the memory
+ *	 size, the driver will correct this if you screw it up.  This
+ *	 problem is not relevant for compiled in drivers as compiled
+ *	 in drivers only init once.
+ *
+ * Credits:
+ *       Saeed Karamooz <saeed@ramix.com> of Ramix INC. for the
+ *       initial example code of how to initialize this device and for
+ *       help with questions I had concerning operation of the device.
+ *
+ *       Most of the MTD code for this driver was originally written
+ *       for the slram.o module in the MTD drivers package which
+ *       allows the mapping of system memory into an MTD device.
+ *       Since the PMC551 memory module is accessed in the same
+ *       fashion as system memory, the slram.c code became a very nice
+ *       fit to the needs of this driver.  All we added was PCI
+ *       detection/initialization to the driver and automatically figure
+ *       out the size via the PCI detection.o, later changes by Corey
+ *       Minyard set up the card to utilize a 1M sliding apature.
+ *
+ *	 Corey Minyard <minyard@nortelnetworks.com>
+ *       * Modified driver to utilize a sliding aperture instead of 
+ *         mapping all memory into kernel space which turned out to
+ *         be very wasteful.
+ *       * Located a bug in the SROM's initialization sequence that 
+ *         made the memory unusable, added a fix to code to touch up
+ *         the DRAM some.
+ *
+ * Bugs/FIXME's:
+ *       * MUST fix the init function to not spin on a register
+ *       waiting for it to set .. this does not safely handle busted
+ *       devices that never reset the register correctly which will
+ *       cause the system to hang w/ a reboot being the only chance at
+ *       recover. [sort of fixed, could be better]
+ *       * Add I2C handling of the SROM so we can read the SROM's information
+ *       about the aperture size.  This should always accurately reflect the
+ *       onboard memory size.
+ *       * Comb the init routine.  It's still a bit cludgy on a few things.
+ */
+
+#include <linux/version.h>
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <asm/uaccess.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/ptrace.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/major.h>
+#include <linux/fs.h>
+#include <linux/ioctl.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <linux/pci.h>
+
+#ifndef CONFIG_PCI
+#error Enable PCI in your kernel config
+#endif
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/pmc551.h>
+#include <linux/mtd/compatmac.h>
+
+static struct mtd_info *pmc551list;
+
+static int pmc551_erase (struct mtd_info *mtd, struct erase_info *instr)
+{
+        struct mypriv *priv = mtd->priv;
+        u32 soff_hi, soff_lo; /* start address offset hi/lo */
+        u32 eoff_hi, eoff_lo; /* end address offset hi/lo */
+        unsigned long end;
+	u_char *ptr;
+	size_t retlen;
+
+#ifdef CONFIG_MTD_PMC551_DEBUG
+	printk(KERN_DEBUG "pmc551_erase(pos:%ld, len:%ld)\n", (long)instr->addr, (long)instr->len);
+#endif
+
+        end = instr->addr + instr->len - 1;
+
+        /* Is it past the end? */
+        if ( end > mtd->size ) {
+#ifdef CONFIG_MTD_PMC551_DEBUG
+	printk(KERN_DEBUG "pmc551_erase() out of bounds (%ld > %ld)\n", (long)end, (long)mtd->size);
+#endif
+                return -EINVAL;
+        }
+
+        eoff_hi = end & ~(priv->asize - 1);
+        soff_hi = instr->addr & ~(priv->asize - 1);
+        eoff_lo = end & (priv->asize - 1);
+        soff_lo = instr->addr & (priv->asize - 1);
+
+	pmc551_point (mtd, instr->addr, instr->len, &retlen, &ptr);
+
+        if ( soff_hi == eoff_hi || mtd->size == priv->asize) {
+                /* The whole thing fits within one access, so just one shot
+                   will do it. */
+                memset(ptr, 0xff, instr->len);
+        } else {
+                /* We have to do multiple writes to get all the data
+                   written. */
+                while (soff_hi != eoff_hi) {
+#ifdef CONFIG_MTD_PMC551_DEBUG
+			printk( KERN_DEBUG "pmc551_erase() soff_hi: %ld, eoff_hi: %ld\n", (long)soff_hi, (long)eoff_hi);
+#endif
+                        memset(ptr, 0xff, priv->asize);
+                        if (soff_hi + priv->asize >= mtd->size) {
+                                goto out;
+                        }
+                        soff_hi += priv->asize;
+			pmc551_point (mtd,(priv->base_map0|soff_hi),
+				      priv->asize, &retlen, &ptr);
+                }
+                memset (ptr, 0xff, eoff_lo);
+        }
+
+out:
+	instr->state = MTD_ERASE_DONE;
+#ifdef CONFIG_MTD_PMC551_DEBUG
+	printk(KERN_DEBUG "pmc551_erase() done\n");
+#endif
+
+        mtd_erase_callback(instr);
+        return 0;
+}
+
+
+static int pmc551_point (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf)
+{
+        struct mypriv *priv = mtd->priv;
+        u32 soff_hi;
+        u32 soff_lo;
+
+#ifdef CONFIG_MTD_PMC551_DEBUG
+	printk(KERN_DEBUG "pmc551_point(%ld, %ld)\n", (long)from, (long)len);
+#endif
+
+	if (from + len > mtd->size) {
+#ifdef CONFIG_MTD_PMC551_DEBUG
+		printk(KERN_DEBUG "pmc551_point() out of bounds (%ld > %ld)\n", (long)from+len, (long)mtd->size);
+#endif
+		return -EINVAL;
+	}
+
+        soff_hi = from & ~(priv->asize - 1);
+        soff_lo = from & (priv->asize - 1);
+
+	/* Cheap hack optimization */
+	if( priv->curr_map0 != from ) {
+        	pci_write_config_dword ( priv->dev, PMC551_PCI_MEM_MAP0,
+                                 	(priv->base_map0 | soff_hi) );
+		priv->curr_map0 = soff_hi;
+	}
+
+	*mtdbuf = priv->start + soff_lo;
+	*retlen = len;
+	return 0;
+}
+
+
+static void pmc551_unpoint (struct mtd_info *mtd, u_char *addr, loff_t from, size_t len)
+{
+#ifdef CONFIG_MTD_PMC551_DEBUG
+	printk(KERN_DEBUG "pmc551_unpoint()\n");
+#endif
+}
+
+
+static int pmc551_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf)
+{
+        struct mypriv *priv = mtd->priv;
+        u32 soff_hi, soff_lo; /* start address offset hi/lo */
+        u32 eoff_hi, eoff_lo; /* end address offset hi/lo */
+        unsigned long end;
+	u_char *ptr;
+        u_char *copyto = buf;
+
+#ifdef CONFIG_MTD_PMC551_DEBUG
+	printk(KERN_DEBUG "pmc551_read(pos:%ld, len:%ld) asize: %ld\n", (long)from, (long)len, (long)priv->asize);
+#endif
+
+        end = from + len - 1;
+
+        /* Is it past the end? */
+        if (end > mtd->size) {
+#ifdef CONFIG_MTD_PMC551_DEBUG
+	printk(KERN_DEBUG "pmc551_read() out of bounds (%ld > %ld)\n", (long) end, (long)mtd->size);
+#endif
+                return -EINVAL;
+        }
+
+        soff_hi = from & ~(priv->asize - 1);
+        eoff_hi = end & ~(priv->asize - 1);
+        soff_lo = from & (priv->asize - 1);
+        eoff_lo = end & (priv->asize - 1);
+
+	pmc551_point (mtd, from, len, retlen, &ptr);
+
+        if (soff_hi == eoff_hi) {
+                /* The whole thing fits within one access, so just one shot
+                   will do it. */
+                memcpy(copyto, ptr, len);
+                copyto += len;
+        } else {
+                /* We have to do multiple writes to get all the data
+                   written. */
+                while (soff_hi != eoff_hi) {
+#ifdef CONFIG_MTD_PMC551_DEBUG
+			printk( KERN_DEBUG "pmc551_read() soff_hi: %ld, eoff_hi: %ld\n", (long)soff_hi, (long)eoff_hi);
+#endif
+                        memcpy(copyto, ptr, priv->asize);
+                        copyto += priv->asize;
+                        if (soff_hi + priv->asize >= mtd->size) {
+                                goto out;
+                        }
+                        soff_hi += priv->asize;
+			pmc551_point (mtd, soff_hi, priv->asize, retlen, &ptr);
+                }
+                memcpy(copyto, ptr, eoff_lo);
+                copyto += eoff_lo;
+        }
+
+out:
+#ifdef CONFIG_MTD_PMC551_DEBUG
+	printk(KERN_DEBUG "pmc551_read() done\n");
+#endif
+        *retlen = copyto - buf;
+        return 0;
+}
+
+static int pmc551_write (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf)
+{
+        struct mypriv *priv = mtd->priv;
+        u32 soff_hi, soff_lo; /* start address offset hi/lo */
+        u32 eoff_hi, eoff_lo; /* end address offset hi/lo */
+        unsigned long end;
+	u_char *ptr;
+        const u_char *copyfrom = buf;
+
+
+#ifdef CONFIG_MTD_PMC551_DEBUG
+	printk(KERN_DEBUG "pmc551_write(pos:%ld, len:%ld) asize:%ld\n", (long)to, (long)len, (long)priv->asize);
+#endif
+
+        end = to + len - 1;
+        /* Is it past the end?  or did the u32 wrap? */
+        if (end > mtd->size ) {
+#ifdef CONFIG_MTD_PMC551_DEBUG
+	printk(KERN_DEBUG "pmc551_write() out of bounds (end: %ld, size: %ld, to: %ld)\n", (long) end, (long)mtd->size, (long)to);
+#endif
+                return -EINVAL;
+        }
+
+        soff_hi = to & ~(priv->asize - 1);
+        eoff_hi = end & ~(priv->asize - 1);
+        soff_lo = to & (priv->asize - 1);
+        eoff_lo = end & (priv->asize - 1);
+
+	pmc551_point (mtd, to, len, retlen, &ptr);
+
+        if (soff_hi == eoff_hi) {
+                /* The whole thing fits within one access, so just one shot
+                   will do it. */
+                memcpy(ptr, copyfrom, len);
+                copyfrom += len;
+        } else {
+                /* We have to do multiple writes to get all the data
+                   written. */
+                while (soff_hi != eoff_hi) {
+#ifdef CONFIG_MTD_PMC551_DEBUG
+			printk( KERN_DEBUG "pmc551_write() soff_hi: %ld, eoff_hi: %ld\n", (long)soff_hi, (long)eoff_hi);
+#endif
+                	memcpy(ptr, copyfrom, priv->asize);
+                	copyfrom += priv->asize;
+                        if (soff_hi >= mtd->size) {
+                                goto out;
+                        }
+                        soff_hi += priv->asize;
+			pmc551_point (mtd, soff_hi, priv->asize, retlen, &ptr);
+                }
+                memcpy(ptr, copyfrom, eoff_lo);
+                copyfrom += eoff_lo;
+        }
+
+out:
+#ifdef CONFIG_MTD_PMC551_DEBUG
+	printk(KERN_DEBUG "pmc551_write() done\n");
+#endif
+        *retlen = copyfrom - buf;
+        return 0;
+}
+
+/*
+ * Fixup routines for the V370PDC
+ * PCI device ID 0x020011b0
+ *
+ * This function basicly kick starts the DRAM oboard the card and gets it
+ * ready to be used.  Before this is done the device reads VERY erratic, so
+ * much that it can crash the Linux 2.2.x series kernels when a user cat's
+ * /proc/pci .. though that is mainly a kernel bug in handling the PCI DEVSEL
+ * register.  FIXME: stop spinning on registers .. must implement a timeout
+ * mechanism
+ * returns the size of the memory region found.
+ */
+static u32 fixup_pmc551 (struct pci_dev *dev)
+{
+#ifdef CONFIG_MTD_PMC551_BUGFIX
+        u32 dram_data;
+#endif
+        u32 size, dcmd, cfg, dtmp;
+        u16 cmd, tmp, i;
+	u8 bcmd, counter;
+
+        /* Sanity Check */
+        if(!dev) {
+                return -ENODEV;
+        }
+
+	/*
+	 * Attempt to reset the card
+	 * FIXME: Stop Spinning registers
+	 */
+	counter=0;
+	/* unlock registers */
+	pci_write_config_byte(dev, PMC551_SYS_CTRL_REG, 0xA5 );
+	/* read in old data */
+	pci_read_config_byte(dev, PMC551_SYS_CTRL_REG, &bcmd );
+	/* bang the reset line up and down for a few */
+	for(i=0;i<10;i++) {
+		counter=0;
+		bcmd &= ~0x80;
+		while(counter++ < 100) {
+			pci_write_config_byte(dev, PMC551_SYS_CTRL_REG, bcmd);
+		}
+		counter=0;
+		bcmd |= 0x80;
+		while(counter++ < 100) {
+			pci_write_config_byte(dev, PMC551_SYS_CTRL_REG, bcmd);
+		}
+	}
+	bcmd |= (0x40|0x20);
+	pci_write_config_byte(dev, PMC551_SYS_CTRL_REG, bcmd);
+
+        /* 
+	 * Take care and turn off the memory on the device while we
+	 * tweak the configurations
+	 */
+        pci_read_config_word(dev, PCI_COMMAND, &cmd);
+        tmp = cmd & ~(PCI_COMMAND_IO|PCI_COMMAND_MEMORY);
+        pci_write_config_word(dev, PCI_COMMAND, tmp);
+
+	/*
+	 * Disable existing aperture before probing memory size
+	 */
+	pci_read_config_dword(dev, PMC551_PCI_MEM_MAP0, &dcmd);
+        dtmp=(dcmd|PMC551_PCI_MEM_MAP_ENABLE|PMC551_PCI_MEM_MAP_REG_EN);
+	pci_write_config_dword(dev, PMC551_PCI_MEM_MAP0, dtmp);
+	/*
+	 * Grab old BAR0 config so that we can figure out memory size
+	 * This is another bit of kludge going on.  The reason for the
+	 * redundancy is I am hoping to retain the original configuration
+	 * previously assigned to the card by the BIOS or some previous 
+	 * fixup routine in the kernel.  So we read the old config into cfg,
+	 * then write all 1's to the memory space, read back the result into
+	 * "size", and then write back all the old config.
+	 */
+	pci_read_config_dword( dev, PCI_BASE_ADDRESS_0, &cfg );
+#ifndef CONFIG_MTD_PMC551_BUGFIX
+	pci_write_config_dword( dev, PCI_BASE_ADDRESS_0, ~0 );
+	pci_read_config_dword( dev, PCI_BASE_ADDRESS_0, &size );
+	size = (size&PCI_BASE_ADDRESS_MEM_MASK);
+	size &= ~(size-1);
+	pci_write_config_dword( dev, PCI_BASE_ADDRESS_0, cfg );
+#else
+        /*
+         * Get the size of the memory by reading all the DRAM size values
+         * and adding them up.
+         *
+         * KLUDGE ALERT: the boards we are using have invalid column and
+         * row mux values.  We fix them here, but this will break other
+         * memory configurations.
+         */
+        pci_read_config_dword(dev, PMC551_DRAM_BLK0, &dram_data);
+        size = PMC551_DRAM_BLK_GET_SIZE(dram_data);
+        dram_data = PMC551_DRAM_BLK_SET_COL_MUX(dram_data, 0x5);
+        dram_data = PMC551_DRAM_BLK_SET_ROW_MUX(dram_data, 0x9);
+        pci_write_config_dword(dev, PMC551_DRAM_BLK0, dram_data);
+
+        pci_read_config_dword(dev, PMC551_DRAM_BLK1, &dram_data);
+        size += PMC551_DRAM_BLK_GET_SIZE(dram_data);
+        dram_data = PMC551_DRAM_BLK_SET_COL_MUX(dram_data, 0x5);
+        dram_data = PMC551_DRAM_BLK_SET_ROW_MUX(dram_data, 0x9);
+        pci_write_config_dword(dev, PMC551_DRAM_BLK1, dram_data);
+
+        pci_read_config_dword(dev, PMC551_DRAM_BLK2, &dram_data);
+        size += PMC551_DRAM_BLK_GET_SIZE(dram_data);
+        dram_data = PMC551_DRAM_BLK_SET_COL_MUX(dram_data, 0x5);
+        dram_data = PMC551_DRAM_BLK_SET_ROW_MUX(dram_data, 0x9);
+        pci_write_config_dword(dev, PMC551_DRAM_BLK2, dram_data);
+
+        pci_read_config_dword(dev, PMC551_DRAM_BLK3, &dram_data);
+        size += PMC551_DRAM_BLK_GET_SIZE(dram_data);
+        dram_data = PMC551_DRAM_BLK_SET_COL_MUX(dram_data, 0x5);
+        dram_data = PMC551_DRAM_BLK_SET_ROW_MUX(dram_data, 0x9);
+        pci_write_config_dword(dev, PMC551_DRAM_BLK3, dram_data);
+
+        /*
+         * Oops .. something went wrong
+         */
+        if( (size &= PCI_BASE_ADDRESS_MEM_MASK) == 0) {
+                return -ENODEV;
+        }
+#endif /* CONFIG_MTD_PMC551_BUGFIX */
+
+	if ((cfg&PCI_BASE_ADDRESS_SPACE) != PCI_BASE_ADDRESS_SPACE_MEMORY) {
+                return -ENODEV;
+	}
+
+        /*
+         * Precharge Dram
+         */
+        pci_write_config_word( dev, PMC551_SDRAM_MA, 0x0400 );
+        pci_write_config_word( dev, PMC551_SDRAM_CMD, 0x00bf );
+
+        /*
+         * Wait until command has gone through
+         * FIXME: register spinning issue
+         */
+        do {	pci_read_config_word( dev, PMC551_SDRAM_CMD, &cmd );
+		if(counter++ > 100)break;
+        } while ( (PCI_COMMAND_IO) & cmd );
+
+        /*
+	 * Turn on auto refresh 
+	 * The loop is taken directly from Ramix's example code.  I assume that
+	 * this must be held high for some duration of time, but I can find no
+	 * documentation refrencing the reasons why.
+         */
+        for ( i = 1; i<=8 ; i++) {
+                pci_write_config_word (dev, PMC551_SDRAM_CMD, 0x0df);
+
+                /*
+                 * Make certain command has gone through
+                 * FIXME: register spinning issue
+                 */
+		counter=0;
+                do {	pci_read_config_word(dev, PMC551_SDRAM_CMD, &cmd);
+			if(counter++ > 100)break;
+                } while ( (PCI_COMMAND_IO) & cmd );
+        }
+
+        pci_write_config_word ( dev, PMC551_SDRAM_MA, 0x0020);
+        pci_write_config_word ( dev, PMC551_SDRAM_CMD, 0x0ff);
+
+        /*
+         * Wait until command completes
+         * FIXME: register spinning issue
+         */
+	counter=0;
+        do {	pci_read_config_word ( dev, PMC551_SDRAM_CMD, &cmd);
+		if(counter++ > 100)break;
+        } while ( (PCI_COMMAND_IO) & cmd );
+
+        pci_read_config_dword ( dev, PMC551_DRAM_CFG, &dcmd);
+        dcmd |= 0x02000000;
+        pci_write_config_dword ( dev, PMC551_DRAM_CFG, dcmd);
+
+        /*
+         * Check to make certain fast back-to-back, if not
+         * then set it so
+         */
+        pci_read_config_word( dev, PCI_STATUS, &cmd);
+        if((cmd&PCI_COMMAND_FAST_BACK) == 0) {
+                cmd |= PCI_COMMAND_FAST_BACK;
+                pci_write_config_word( dev, PCI_STATUS, cmd);
+        }
+
+        /*
+         * Check to make certain the DEVSEL is set correctly, this device
+         * has a tendancy to assert DEVSEL and TRDY when a write is performed
+         * to the memory when memory is read-only
+         */
+        if((cmd&PCI_STATUS_DEVSEL_MASK) != 0x0) {
+                cmd &= ~PCI_STATUS_DEVSEL_MASK;
+                pci_write_config_word( dev, PCI_STATUS, cmd );
+        }
+        /*
+         * Set to be prefetchable and put everything back based on old cfg.
+	 * it's possible that the reset of the V370PDC nuked the original
+	 * setup
+         */
+	/*
+        cfg |= PCI_BASE_ADDRESS_MEM_PREFETCH;
+	pci_write_config_dword( dev, PCI_BASE_ADDRESS_0, cfg );
+	*/
+
+        /*
+         * Turn PCI memory and I/O bus access back on
+         */
+        pci_write_config_word( dev, PCI_COMMAND,
+                               PCI_COMMAND_MEMORY | PCI_COMMAND_IO );
+#ifdef CONFIG_MTD_PMC551_DEBUG
+        /*
+         * Some screen fun
+         */
+        printk(KERN_DEBUG "pmc551: %d%c (0x%x) of %sprefetchable memory at 0x%lx\n",
+	       (size<1024)?size:(size<1048576)?size>>10:size>>20,
+               (size<1024)?'B':(size<1048576)?'K':'M',
+	       size, ((dcmd&(0x1<<3)) == 0)?"non-":"",
+               (dev->resource[0].start)&PCI_BASE_ADDRESS_MEM_MASK );
+
+        /*
+         * Check to see the state of the memory
+         */
+        pci_read_config_dword( dev, PMC551_DRAM_BLK0, &dcmd );
+        printk(KERN_DEBUG "pmc551: DRAM_BLK0 Flags: %s,%s\n"
+			  "pmc551: DRAM_BLK0 Size: %d at %d\n"
+			  "pmc551: DRAM_BLK0 Row MUX: %d, Col MUX: %d\n",
+               (((0x1<<1)&dcmd) == 0)?"RW":"RO",
+               (((0x1<<0)&dcmd) == 0)?"Off":"On",
+	       PMC551_DRAM_BLK_GET_SIZE(dcmd),
+	       ((dcmd>>20)&0x7FF), ((dcmd>>13)&0x7), ((dcmd>>9)&0xF) );
+
+        pci_read_config_dword( dev, PMC551_DRAM_BLK1, &dcmd );
+        printk(KERN_DEBUG "pmc551: DRAM_BLK1 Flags: %s,%s\n"
+			  "pmc551: DRAM_BLK1 Size: %d at %d\n"
+			  "pmc551: DRAM_BLK1 Row MUX: %d, Col MUX: %d\n",
+               (((0x1<<1)&dcmd) == 0)?"RW":"RO",
+               (((0x1<<0)&dcmd) == 0)?"Off":"On",
+	       PMC551_DRAM_BLK_GET_SIZE(dcmd),
+	       ((dcmd>>20)&0x7FF), ((dcmd>>13)&0x7), ((dcmd>>9)&0xF) );
+
+        pci_read_config_dword( dev, PMC551_DRAM_BLK2, &dcmd );
+        printk(KERN_DEBUG "pmc551: DRAM_BLK2 Flags: %s,%s\n"
+			  "pmc551: DRAM_BLK2 Size: %d at %d\n"
+			  "pmc551: DRAM_BLK2 Row MUX: %d, Col MUX: %d\n",
+               (((0x1<<1)&dcmd) == 0)?"RW":"RO",
+               (((0x1<<0)&dcmd) == 0)?"Off":"On",
+	       PMC551_DRAM_BLK_GET_SIZE(dcmd),
+	       ((dcmd>>20)&0x7FF), ((dcmd>>13)&0x7), ((dcmd>>9)&0xF) );
+
+        pci_read_config_dword( dev, PMC551_DRAM_BLK3, &dcmd );
+        printk(KERN_DEBUG "pmc551: DRAM_BLK3 Flags: %s,%s\n"
+			  "pmc551: DRAM_BLK3 Size: %d at %d\n"
+			  "pmc551: DRAM_BLK3 Row MUX: %d, Col MUX: %d\n",
+               (((0x1<<1)&dcmd) == 0)?"RW":"RO",
+               (((0x1<<0)&dcmd) == 0)?"Off":"On",
+	       PMC551_DRAM_BLK_GET_SIZE(dcmd),
+	       ((dcmd>>20)&0x7FF), ((dcmd>>13)&0x7), ((dcmd>>9)&0xF) );
+
+        pci_read_config_word( dev, PCI_COMMAND, &cmd );
+        printk( KERN_DEBUG "pmc551: Memory Access %s\n",
+                (((0x1<<1)&cmd) == 0)?"off":"on" );
+        printk( KERN_DEBUG "pmc551: I/O Access %s\n",
+                (((0x1<<0)&cmd) == 0)?"off":"on" );
+
+        pci_read_config_word( dev, PCI_STATUS, &cmd );
+        printk( KERN_DEBUG "pmc551: Devsel %s\n",
+                ((PCI_STATUS_DEVSEL_MASK&cmd)==0x000)?"Fast":
+                ((PCI_STATUS_DEVSEL_MASK&cmd)==0x200)?"Medium":
+                ((PCI_STATUS_DEVSEL_MASK&cmd)==0x400)?"Slow":"Invalid" );
+
+        printk( KERN_DEBUG "pmc551: %sFast Back-to-Back\n",
+                ((PCI_COMMAND_FAST_BACK&cmd) == 0)?"Not ":"" );
+
+	pci_read_config_byte(dev, PMC551_SYS_CTRL_REG, &bcmd );
+	printk( KERN_DEBUG "pmc551: EEPROM is under %s control\n"
+			   "pmc551: System Control Register is %slocked to PCI access\n"
+			   "pmc551: System Control Register is %slocked to EEPROM access\n", 
+		(bcmd&0x1)?"software":"hardware",
+		(bcmd&0x20)?"":"un", (bcmd&0x40)?"":"un");
+#endif
+        return size;
+}
+
+/*
+ * Kernel version specific module stuffages
+ */
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Mark Ferrell <mferrell@mvista.com>");
+MODULE_DESCRIPTION(PMC551_VERSION);
+
+/*
+ * Stuff these outside the ifdef so as to not bust compiled in driver support
+ */
+static int msize=0;
+#if defined(CONFIG_MTD_PMC551_APERTURE_SIZE)
+static int asize=CONFIG_MTD_PMC551_APERTURE_SIZE
+#else
+static int asize=0;
+#endif
+
+module_param(msize, int, 0);
+MODULE_PARM_DESC(msize, "memory size in Megabytes [1 - 1024]");
+module_param(asize, int, 0);
+MODULE_PARM_DESC(asize, "aperture size, must be <= memsize [1-1024]");
+
+/*
+ * PMC551 Card Initialization
+ */
+static int __init init_pmc551(void)
+{
+        struct pci_dev *PCI_Device = NULL;
+        struct mypriv *priv;
+        int count, found=0;
+        struct mtd_info *mtd;
+        u32 length = 0;
+
+	if(msize) {
+		msize = (1 << (ffs(msize) - 1))<<20;
+		if (msize > (1<<30)) {
+			printk(KERN_NOTICE "pmc551: Invalid memory size [%d]\n", msize);
+			return -EINVAL;
+		}
+	}
+
+	if(asize) {
+		asize = (1 << (ffs(asize) - 1))<<20;
+		if (asize > (1<<30) ) {
+			printk(KERN_NOTICE "pmc551: Invalid aperture size [%d]\n", asize);
+			return -EINVAL;
+		}
+	}
+
+        printk(KERN_INFO PMC551_VERSION);
+
+        /*
+         * PCU-bus chipset probe.
+         */
+        for( count = 0; count < MAX_MTD_DEVICES; count++ ) {
+
+                if ((PCI_Device = pci_find_device(PCI_VENDOR_ID_V3_SEMI,
+                                                  PCI_DEVICE_ID_V3_SEMI_V370PDC,
+						  PCI_Device ) ) == NULL) {
+                        break;
+                }
+
+                printk(KERN_NOTICE "pmc551: Found PCI V370PDC at 0x%lX\n",
+				    PCI_Device->resource[0].start);
+
+                /*
+                 * The PMC551 device acts VERY weird if you don't init it
+                 * first.  i.e. it will not correctly report devsel.  If for
+                 * some reason the sdram is in a wrote-protected state the
+                 * device will DEVSEL when it is written to causing problems
+                 * with the oldproc.c driver in
+                 * some kernels (2.2.*)
+                 */
+                if((length = fixup_pmc551(PCI_Device)) <= 0) {
+                        printk(KERN_NOTICE "pmc551: Cannot init SDRAM\n");
+                        break;
+                }
+
+		/*
+		 * This is needed until the driver is capable of reading the
+		 * onboard I2C SROM to discover the "real" memory size.
+		 */
+		if(msize) {
+			length = msize;
+			printk(KERN_NOTICE "pmc551: Using specified memory size 0x%x\n", length);
+		} else {
+			msize = length;
+		}
+
+                mtd = kmalloc(sizeof(struct mtd_info), GFP_KERNEL);
+                if (!mtd) {
+                        printk(KERN_NOTICE "pmc551: Cannot allocate new MTD device.\n");
+                        break;
+                }
+
+                memset(mtd, 0, sizeof(struct mtd_info));
+
+                priv = kmalloc (sizeof(struct mypriv), GFP_KERNEL);
+                if (!priv) {
+                        printk(KERN_NOTICE "pmc551: Cannot allocate new MTD device.\n");
+                        kfree(mtd);
+                        break;
+                }
+                memset(priv, 0, sizeof(*priv));
+                mtd->priv = priv;
+                priv->dev = PCI_Device;
+
+		if(asize > length) {
+			printk(KERN_NOTICE "pmc551: reducing aperture size to fit %dM\n",length>>20);
+			priv->asize = asize = length;
+		} else if (asize == 0 || asize == length) {
+			printk(KERN_NOTICE "pmc551: Using existing aperture size %dM\n", length>>20);
+			priv->asize = asize = length;
+		} else {
+			printk(KERN_NOTICE "pmc551: Using specified aperture size %dM\n", asize>>20);
+			priv->asize = asize;
+		}
+                priv->start = ioremap(((PCI_Device->resource[0].start)
+                                       & PCI_BASE_ADDRESS_MEM_MASK),
+                                      priv->asize);
+		
+		if (!priv->start) {
+			printk(KERN_NOTICE "pmc551: Unable to map IO space\n");
+                        kfree(mtd->priv);
+                        kfree(mtd);
+			break;
+		}
+
+#ifdef CONFIG_MTD_PMC551_DEBUG
+		printk( KERN_DEBUG "pmc551: setting aperture to %d\n",
+			ffs(priv->asize>>20)-1);
+#endif
+
+                priv->base_map0 = ( PMC551_PCI_MEM_MAP_REG_EN
+				  | PMC551_PCI_MEM_MAP_ENABLE
+				  | (ffs(priv->asize>>20)-1)<<4 );
+                priv->curr_map0 = priv->base_map0;
+                pci_write_config_dword ( priv->dev, PMC551_PCI_MEM_MAP0,
+                                         priv->curr_map0 );
+
+#ifdef CONFIG_MTD_PMC551_DEBUG
+		printk( KERN_DEBUG "pmc551: aperture set to %d\n", 
+			(priv->base_map0 & 0xF0)>>4 );
+#endif
+
+                mtd->size 	= msize;
+                mtd->flags 	= MTD_CAP_RAM;
+                mtd->erase 	= pmc551_erase;
+                mtd->read 	= pmc551_read;
+                mtd->write 	= pmc551_write;
+                mtd->point 	= pmc551_point;
+                mtd->unpoint 	= pmc551_unpoint;
+                mtd->type 	= MTD_RAM;
+                mtd->name 	= "PMC551 RAM board";
+                mtd->erasesize 	= 0x10000;
+		mtd->owner = THIS_MODULE;
+
+                if (add_mtd_device(mtd)) {
+                        printk(KERN_NOTICE "pmc551: Failed to register new device\n");
+			iounmap(priv->start);
+                        kfree(mtd->priv);
+                        kfree(mtd);
+                        break;
+                }
+                printk(KERN_NOTICE "Registered pmc551 memory device.\n");
+                printk(KERN_NOTICE "Mapped %dM of memory from 0x%p to 0x%p\n",
+                       priv->asize>>20,
+                       priv->start,
+                       priv->start + priv->asize);
+                printk(KERN_NOTICE "Total memory is %d%c\n",
+	       		(length<1024)?length:
+				(length<1048576)?length>>10:length>>20,
+               		(length<1024)?'B':(length<1048576)?'K':'M');
+		priv->nextpmc551 = pmc551list;
+		pmc551list = mtd;
+		found++;
+        }
+
+        if( !pmc551list ) {
+                printk(KERN_NOTICE "pmc551: not detected\n");
+                return -ENODEV;
+        } else {
+		printk(KERN_NOTICE "pmc551: %d pmc551 devices loaded\n", found);
+                return 0;
+	}
+}
+
+/*
+ * PMC551 Card Cleanup
+ */
+static void __exit cleanup_pmc551(void)
+{
+        int found=0;
+        struct mtd_info *mtd;
+	struct mypriv *priv;
+
+	while((mtd=pmc551list)) {
+		priv = mtd->priv;
+		pmc551list = priv->nextpmc551;
+		
+		if(priv->start) {
+			printk (KERN_DEBUG "pmc551: unmapping %dM starting at 0x%p\n",
+				priv->asize>>20, priv->start);
+			iounmap (priv->start);
+		}
+		
+		kfree (mtd->priv);
+		del_mtd_device (mtd);
+		kfree (mtd);
+		found++;
+	}
+
+	printk(KERN_NOTICE "pmc551: %d pmc551 devices unloaded\n", found);
+}
+
+module_init(init_pmc551);
+module_exit(cleanup_pmc551);
diff --git a/drivers/mtd/devices/slram.c b/drivers/mtd/devices/slram.c
new file mode 100644
index 0000000..5ab15e6
--- /dev/null
+++ b/drivers/mtd/devices/slram.c
@@ -0,0 +1,357 @@
+/*======================================================================
+
+  $Id: slram.c,v 1.33 2005/01/05 18:05:13 dwmw2 Exp $
+
+  This driver provides a method to access memory not used by the kernel
+  itself (i.e. if the kernel commandline mem=xxx is used). To actually
+  use slram at least mtdblock or mtdchar is required (for block or
+  character device access).
+
+  Usage:
+
+  if compiled as loadable module:
+    modprobe slram map=<name>,<start>,<end/offset>
+  if statically linked into the kernel use the following kernel cmd.line
+    slram=<name>,<start>,<end/offset>
+
+  <name>: name of the device that will be listed in /proc/mtd
+  <start>: start of the memory region, decimal or hex (0xabcdef)
+  <end/offset>: end of the memory region. It's possible to use +0x1234
+                to specify the offset instead of the absolute address
+    
+  NOTE:
+  With slram it's only possible to map a contigous memory region. Therfore
+  if there's a device mapped somewhere in the region specified slram will
+  fail to load (see kernel log if modprobe fails).
+
+  -
+  
+  Jochen Schaeuble <psionic@psionic.de>
+
+======================================================================*/
+
+
+#include <linux/module.h>
+#include <asm/uaccess.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/ptrace.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/major.h>
+#include <linux/fs.h>
+#include <linux/ioctl.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <asm/system.h>
+
+#include <linux/mtd/mtd.h>
+
+#define SLRAM_MAX_DEVICES_PARAMS 6		/* 3 parameters / device */
+
+#define T(fmt, args...) printk(KERN_DEBUG fmt, ## args)
+#define E(fmt, args...) printk(KERN_NOTICE fmt, ## args)
+
+typedef struct slram_priv {
+	u_char *start;
+	u_char *end;
+} slram_priv_t;
+
+typedef struct slram_mtd_list {
+	struct mtd_info *mtdinfo;
+	struct slram_mtd_list *next;
+} slram_mtd_list_t;
+
+#ifdef MODULE
+static char *map[SLRAM_MAX_DEVICES_PARAMS];
+
+module_param_array(map, charp, NULL, 0);
+MODULE_PARM_DESC(map, "List of memory regions to map. \"map=<name>, <start>, <length / end>\"");
+#else
+static char *map;
+#endif
+
+static slram_mtd_list_t *slram_mtdlist = NULL;
+
+static int slram_erase(struct mtd_info *, struct erase_info *);
+static int slram_point(struct mtd_info *, loff_t, size_t, size_t *, u_char **);
+static void slram_unpoint(struct mtd_info *, u_char *, loff_t,	size_t);
+static int slram_read(struct mtd_info *, loff_t, size_t, size_t *, u_char *);
+static int slram_write(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
+
+static int slram_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+	slram_priv_t *priv = mtd->priv;
+
+	if (instr->addr + instr->len > mtd->size) {
+		return(-EINVAL);
+	}
+	
+	memset(priv->start + instr->addr, 0xff, instr->len);
+
+	/* This'll catch a few races. Free the thing before returning :) 
+	 * I don't feel at all ashamed. This kind of thing is possible anyway
+	 * with flash, but unlikely.
+	 */
+
+	instr->state = MTD_ERASE_DONE;
+
+	mtd_erase_callback(instr);
+
+	return(0);
+}
+
+static int slram_point(struct mtd_info *mtd, loff_t from, size_t len,
+		size_t *retlen, u_char **mtdbuf)
+{
+	slram_priv_t *priv = mtd->priv;
+
+	*mtdbuf = priv->start + from;
+	*retlen = len;
+	return(0);
+}
+
+static void slram_unpoint(struct mtd_info *mtd, u_char *addr, loff_t from, size_t len)
+{
+}
+
+static int slram_read(struct mtd_info *mtd, loff_t from, size_t len,
+		size_t *retlen, u_char *buf)
+{
+	slram_priv_t *priv = mtd->priv;
+	
+	memcpy(buf, priv->start + from, len);
+
+	*retlen = len;
+	return(0);
+}
+
+static int slram_write(struct mtd_info *mtd, loff_t to, size_t len,
+		size_t *retlen, const u_char *buf)
+{
+	slram_priv_t *priv = mtd->priv;
+
+	memcpy(priv->start + to, buf, len);
+
+	*retlen = len;
+	return(0);
+}
+
+/*====================================================================*/
+
+static int register_device(char *name, unsigned long start, unsigned long length)
+{
+	slram_mtd_list_t **curmtd;
+
+	curmtd = &slram_mtdlist;
+	while (*curmtd) {
+		curmtd = &(*curmtd)->next;
+	}
+
+	*curmtd = kmalloc(sizeof(slram_mtd_list_t), GFP_KERNEL);
+	if (!(*curmtd)) {
+		E("slram: Cannot allocate new MTD device.\n");
+		return(-ENOMEM);
+	}
+	(*curmtd)->mtdinfo = kmalloc(sizeof(struct mtd_info), GFP_KERNEL);
+	(*curmtd)->next = NULL;
+	
+	if ((*curmtd)->mtdinfo)	{
+		memset((char *)(*curmtd)->mtdinfo, 0, sizeof(struct mtd_info));
+		(*curmtd)->mtdinfo->priv =
+			kmalloc(sizeof(slram_priv_t), GFP_KERNEL);
+		
+		if (!(*curmtd)->mtdinfo->priv) {
+			kfree((*curmtd)->mtdinfo);
+			(*curmtd)->mtdinfo = NULL;
+		} else {
+			memset((*curmtd)->mtdinfo->priv,0,sizeof(slram_priv_t));
+		}
+	}
+
+	if (!(*curmtd)->mtdinfo) {
+		E("slram: Cannot allocate new MTD device.\n");
+		return(-ENOMEM);
+	}
+	
+	if (!(((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start =
+				ioremap(start, length))) {
+		E("slram: ioremap failed\n");
+		return -EIO;
+	}
+	((slram_priv_t *)(*curmtd)->mtdinfo->priv)->end =
+		((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start + length;
+
+
+	(*curmtd)->mtdinfo->name = name;
+	(*curmtd)->mtdinfo->size = length;
+	(*curmtd)->mtdinfo->flags = MTD_CLEAR_BITS | MTD_SET_BITS |
+					MTD_WRITEB_WRITEABLE | MTD_VOLATILE;
+        (*curmtd)->mtdinfo->erase = slram_erase;
+	(*curmtd)->mtdinfo->point = slram_point;
+	(*curmtd)->mtdinfo->unpoint = slram_unpoint;
+	(*curmtd)->mtdinfo->read = slram_read;
+	(*curmtd)->mtdinfo->write = slram_write;
+	(*curmtd)->mtdinfo->owner = THIS_MODULE;
+	(*curmtd)->mtdinfo->type = MTD_RAM;
+	(*curmtd)->mtdinfo->erasesize = 0x0;
+
+	if (add_mtd_device((*curmtd)->mtdinfo))	{
+		E("slram: Failed to register new device\n");
+		iounmap(((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start);
+		kfree((*curmtd)->mtdinfo->priv);
+		kfree((*curmtd)->mtdinfo);
+		return(-EAGAIN);
+	}
+	T("slram: Registered device %s from %luKiB to %luKiB\n", name,
+			(start / 1024), ((start + length) / 1024));
+	T("slram: Mapped from 0x%p to 0x%p\n",
+			((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start,
+			((slram_priv_t *)(*curmtd)->mtdinfo->priv)->end);
+	return(0);	
+}
+
+static void unregister_devices(void)
+{
+	slram_mtd_list_t *nextitem;
+
+	while (slram_mtdlist) {
+		nextitem = slram_mtdlist->next;
+		del_mtd_device(slram_mtdlist->mtdinfo);
+		iounmap(((slram_priv_t *)slram_mtdlist->mtdinfo->priv)->start);
+		kfree(slram_mtdlist->mtdinfo->priv);
+		kfree(slram_mtdlist->mtdinfo);
+		kfree(slram_mtdlist);
+		slram_mtdlist = nextitem;
+	}
+}
+
+static unsigned long handle_unit(unsigned long value, char *unit)
+{
+	if ((*unit == 'M') || (*unit == 'm')) {
+		return(value * 1024 * 1024);
+	} else if ((*unit == 'K') || (*unit == 'k')) {
+		return(value * 1024);
+	}
+	return(value);
+}
+
+static int parse_cmdline(char *devname, char *szstart, char *szlength)
+{
+	char *buffer;
+	unsigned long devstart;
+	unsigned long devlength;
+	
+	if ((!devname) || (!szstart) || (!szlength)) {
+		unregister_devices();
+		return(-EINVAL);
+	}
+
+	devstart = simple_strtoul(szstart, &buffer, 0);
+	devstart = handle_unit(devstart, buffer);
+	
+	if (*(szlength) != '+') {
+		devlength = simple_strtoul(szlength, &buffer, 0);
+		devlength = handle_unit(devlength, buffer) - devstart;
+	} else {
+		devlength = simple_strtoul(szlength + 1, &buffer, 0);
+		devlength = handle_unit(devlength, buffer);
+	}
+	T("slram: devname=%s, devstart=0x%lx, devlength=0x%lx\n",
+			devname, devstart, devlength);
+	if ((devstart < 0) || (devlength < 0)) {
+		E("slram: Illegal start / length parameter.\n");
+		return(-EINVAL);
+	}
+	
+	if ((devstart = register_device(devname, devstart, devlength))){
+		unregister_devices();
+		return((int)devstart);
+	}
+	return(0);
+}
+
+#ifndef MODULE
+
+static int __init mtd_slram_setup(char *str)
+{
+	map = str;
+	return(1);
+}
+
+__setup("slram=", mtd_slram_setup);
+
+#endif
+
+static int init_slram(void)
+{
+	char *devname;
+	int i;
+
+#ifndef MODULE
+	char *devstart;
+	char *devlength;
+
+	i = 0;
+
+	if (!map) {
+		E("slram: not enough parameters.\n");
+		return(-EINVAL);
+	}
+	while (map) {
+		devname = devstart = devlength = NULL;
+
+		if (!(devname = strsep(&map, ","))) {
+			E("slram: No devicename specified.\n");
+			break;
+		}
+		T("slram: devname = %s\n", devname);
+		if ((!map) || (!(devstart = strsep(&map, ",")))) {
+			E("slram: No devicestart specified.\n");
+		}
+		T("slram: devstart = %s\n", devstart);
+		if ((!map) || (!(devlength = strsep(&map, ",")))) {
+			E("slram: No devicelength / -end specified.\n");
+		}
+		T("slram: devlength = %s\n", devlength);
+		if (parse_cmdline(devname, devstart, devlength) != 0) {
+			return(-EINVAL);
+		}
+	}
+#else
+	int count;
+	
+	for (count = 0; (map[count]) && (count < SLRAM_MAX_DEVICES_PARAMS);
+			count++) {
+	}
+
+	if ((count % 3 != 0) || (count == 0)) {
+		E("slram: not enough parameters.\n");
+		return(-EINVAL);
+	}
+	for (i = 0; i < (count / 3); i++) {
+		devname = map[i * 3];
+
+		if (parse_cmdline(devname, map[i * 3 + 1], map[i * 3 + 2])!=0) {
+			return(-EINVAL);
+		}
+		
+	}
+#endif /* !MODULE */
+	
+	return(0);
+}
+
+static void __exit cleanup_slram(void)
+{
+	unregister_devices();
+}
+
+module_init(init_slram);
+module_exit(cleanup_slram);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jochen Schaeuble <psionic@psionic.de>");
+MODULE_DESCRIPTION("MTD driver for uncached system RAM");