diff options
Diffstat (limited to 'drivers/mtd/devices')
-rw-r--r-- | drivers/mtd/devices/Kconfig | 259 | ||||
-rw-r--r-- | drivers/mtd/devices/Makefile | 25 | ||||
-rw-r--r-- | drivers/mtd/devices/blkmtd.c | 823 | ||||
-rw-r--r-- | drivers/mtd/devices/block2mtd.c | 495 | ||||
-rw-r--r-- | drivers/mtd/devices/doc2000.c | 1309 | ||||
-rw-r--r-- | drivers/mtd/devices/doc2001.c | 888 | ||||
-rw-r--r-- | drivers/mtd/devices/doc2001plus.c | 1154 | ||||
-rw-r--r-- | drivers/mtd/devices/docecc.c | 526 | ||||
-rw-r--r-- | drivers/mtd/devices/docprobe.c | 355 | ||||
-rw-r--r-- | drivers/mtd/devices/lart.c | 711 | ||||
-rw-r--r-- | drivers/mtd/devices/ms02-nv.c | 326 | ||||
-rw-r--r-- | drivers/mtd/devices/ms02-nv.h | 107 | ||||
-rw-r--r-- | drivers/mtd/devices/mtdram.c | 235 | ||||
-rw-r--r-- | drivers/mtd/devices/phram.c | 285 | ||||
-rw-r--r-- | drivers/mtd/devices/pmc551.c | 843 | ||||
-rw-r--r-- | drivers/mtd/devices/slram.c | 357 |
16 files changed, 8698 insertions, 0 deletions
diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig new file mode 100644 index 00000000000..c4a56a4ac5e --- /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 00000000000..e38db348057 --- /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 00000000000..662e807801e --- /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 00000000000..cfe6ccf0797 --- /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 00000000000..5fc532895a2 --- /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 00000000000..1e704915ef0 --- /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 00000000000..ed47bafb2ce --- /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 00000000000..933877ff4d8 --- /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(®[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 00000000000..197d67045e1 --- /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 00000000000..dfd335e4a2a --- /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 00000000000..380ff08d29e --- /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 00000000000..8a6eef7cfee --- /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 00000000000..edac4156d69 --- /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 00000000000..5f8e164ddb7 --- /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 00000000000..5b3defadf88 --- /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 00000000000..5ab15e643be --- /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"); |