diff options
author | Ralf Baechle <ralf@linux-mips.org> | 2001-01-10 05:27:25 +0000 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2001-01-10 05:27:25 +0000 |
commit | c9c06167e7933d93a6e396174c68abf242294abb (patch) | |
tree | d9a8bb30663e9a3405a1ef37ffb62bc14b9f019f /drivers/mtd | |
parent | f79e8cc3c34e4192a3e5ef4cc9c6542fdef703c0 (diff) |
Merge with Linux 2.4.0-test12.
Diffstat (limited to 'drivers/mtd')
-rw-r--r-- | drivers/mtd/Config.in | 45 | ||||
-rw-r--r-- | drivers/mtd/Makefile | 8 | ||||
-rw-r--r-- | drivers/mtd/cfi_cmdset_0002.c | 2 | ||||
-rw-r--r-- | drivers/mtd/doc1000.c | 71 | ||||
-rw-r--r-- | drivers/mtd/doc2000.c | 1101 | ||||
-rw-r--r-- | drivers/mtd/doc2001.c | 356 | ||||
-rw-r--r-- | drivers/mtd/docecc.c | 522 | ||||
-rw-r--r-- | drivers/mtd/docprobe.c | 97 | ||||
-rw-r--r-- | drivers/mtd/ftl.c | 24 | ||||
-rw-r--r-- | drivers/mtd/map_ram.c | 13 | ||||
-rw-r--r-- | drivers/mtd/map_rom.c | 46 | ||||
-rw-r--r-- | drivers/mtd/mtdblock.c | 677 | ||||
-rw-r--r-- | drivers/mtd/mtdchar.c | 195 | ||||
-rw-r--r-- | drivers/mtd/mtdcore.c | 158 | ||||
-rw-r--r-- | drivers/mtd/mtdpart.c | 228 | ||||
-rw-r--r-- | drivers/mtd/nftl.c | 1263 | ||||
-rw-r--r-- | drivers/mtd/nftlmount.c | 678 | ||||
-rw-r--r-- | drivers/mtd/nora.c | 50 | ||||
-rw-r--r-- | drivers/mtd/octagon-5066.c | 57 | ||||
-rw-r--r-- | drivers/mtd/physmap.c | 38 | ||||
-rw-r--r-- | drivers/mtd/pmc551.c | 344 | ||||
-rw-r--r-- | drivers/mtd/pnc2000.c | 140 | ||||
-rw-r--r-- | drivers/mtd/rpxlite.c | 105 | ||||
-rw-r--r-- | drivers/mtd/vmax301.c | 61 |
24 files changed, 4128 insertions, 2151 deletions
diff --git a/drivers/mtd/Config.in b/drivers/mtd/Config.in index 9023f0a81..87bf2e9b6 100644 --- a/drivers/mtd/Config.in +++ b/drivers/mtd/Config.in @@ -1,15 +1,21 @@ -# $Id: Config.in,v 1.20 2000/07/13 12:40:46 scote1 Exp $ +# $Id: No. :) $ mainmenu_option next_comment comment 'Memory Technology Devices (MTD)' tristate 'Memory Technology Device (MTD) support' CONFIG_MTD -if [ "$CONFIG_MTD" != "n" ]; then - dep_tristate ' M-Systems Disk-On-Chip 1000 support' CONFIG_MTD_DOC1000 $CONFIG_MTD - dep_tristate ' M-Systems Disk-On-Chip 2000' CONFIG_MTD_DOC2000 $CONFIG_MTD - dep_tristate ' M-Systems Disk-On-Chip Millennium' CONFIG_MTD_DOC2001 $CONFIG_MTD +if [ "$CONFIG_MTD" = "y" -o "$CONFIG_MTD" = "m" ]; then + bool 'Debugging' CONFIG_MTD_DEBUG + if [ "$CONFIG_MTD_DEBUG" = "y" ]; then + int ' Debugging verbosity (0 = quiet, 3 = noisy)' CONFIG_MTD_DEBUG_VERBOSE 0 + fi + +comment 'Disk-On-Chip Device Drivers' + dep_tristate ' M-Systems Disk-On-Chip 1000' CONFIG_MTD_DOC1000 $CONFIG_MTD + dep_tristate ' M-Systems Disk-On-Chip 2000 and Millennium' CONFIG_MTD_DOC2000 $CONFIG_MTD + dep_tristate ' M-Systems Disk-On-Chip Millennium-only alternative driver' CONFIG_MTD_DOC2001 $CONFIG_MTD if [ "$CONFIG_MTD_DOC2001" = "y" -o "$CONFIG_MTD_DOC2000" = "y" ]; then define_tristate CONFIG_MTD_DOCPROBE y else @@ -19,10 +25,18 @@ if [ "$CONFIG_MTD" != "n" ]; then define_tristate CONFIG_MTD_DOCPROBE n fi fi + if [ "$CONFIG_MTD_DOCPROBE" = "y" -o "$CONFIG_MTD_DOCPROBE" = "m" ]; then + hex ' Physical address of DiskOnChip' CONFIG_MTD_DOCPROBE_ADDRESS 0x0000 + bool ' Probe high addresses' CONFIG_MTD_DOCPROBE_HIGH + bool ' Probe for 0x55 0xAA BIOS Extension Signature' CONFIG_MTD_DOCPROBE_55AA + fi + +comment 'RAM/ROM Device Drivers' dep_tristate ' Use extra onboard system memory as MTD device' CONFIG_MTD_SLRAM $CONFIG_MTD dep_tristate ' Ramix PMC551 PCI Mezzanine ram card support' CONFIG_MTD_PMC551 $CONFIG_MTD $CONFIG_PCI if [ "$CONFIG_MTD_PMC551" != "n" ]; then bool ' PMC551 256M DRAM Bugfix' CONFIG_MTD_PMC551_BUGFIX + bool ' PMC551 Debugging' CONFIG_MTD_PMC551_DEBUG fi dep_tristate ' Debugging RAM test driver' CONFIG_MTD_MTDRAM $CONFIG_MTD if [ "$CONFIG_MTD_MTDRAM" != "n" ]; then @@ -30,20 +44,21 @@ if [ "$CONFIG_MTD" != "n" ]; then int 'Size of the erase sectors in kB' CONFIG_MTDRAM_ERASE_SIZE 128 fi -comment 'MTD drivers for mapped chips' +comment 'Linearly Mapped Flash Device Drivers' dep_tristate ' Common Flash Interface (CFI) support' CONFIG_MTD_CFI $CONFIG_MTD dep_tristate ' CFI support for Intel/Sharp Extended Command Set chips' CONFIG_MTD_CFI_INTELEXT $CONFIG_MTD_CFI dep_tristate ' CFI support for AMD/Fujitsu Standard Command Set chips' CONFIG_MTD_CFI_AMDSTD $CONFIG_MTD_CFI + dep_tristate ' Support for RAM chips in bus mapping' CONFIG_MTD_RAM $CONFIG_MTD + dep_tristate ' Support for ROM chips in bus mapping' CONFIG_MTD_ROM $CONFIG_MTD # These will later become config-options define_bool CONFIG_MTD_JEDEC n -define_bool CONFIG_MTD_RAM n -define_bool CONFIG_MTD_ROM n - dep_tristate ' Flash chip mapping in physical memory' CONFIG_MTD_PHYSMAP $CONFIG_MTD_CFI - if [ "$CONFIG_MTD_PHYSMAP" != "n" ]; then - hex 'Physical start location of flash chip mapping' CONFIG_MTD_PHYSMAP_START 0x8000000 - hex 'Physical length of flash chip mapping' CONFIG_MTD_PHYSMAP_LEN 0x4000000 + dep_tristate ' Flash chip mapping in physical memory' CONFIG_MTD_PHYSMAP $CONFIG_MTD_CFI + if [ "$CONFIG_MTD_PHYSMAP" = "y" -o "$CONFIG_MTD_PHYSMAP" = "m" ]; then + hex ' Physical start location of flash chip mapping' CONFIG_MTD_PHYSMAP_START 0x8000000 + hex ' Physical length of flash chip mapping' CONFIG_MTD_PHYSMAP_LEN 0x4000000 + int ' Bus width in octets' CONFIG_MTD_PHYSMAP_BUSWIDTH 2 fi comment 'Drivers for chip mappings' @@ -56,11 +71,11 @@ comment 'Drivers for chip mappings' comment 'User modules and translation layers for MTD devices' dep_tristate ' Direct chardevice access to MTD devices' CONFIG_MTD_CHAR $CONFIG_MTD - dep_tristate ' Pseudo-blockdevice access to MTD devices' CONFIG_MTD_BLOCK $CONFIG_MTD + dep_tristate ' Caching blockdevice access to MTD devices' CONFIG_MTD_BLOCK $CONFIG_MTD dep_tristate ' FTL (Flash Translation Layer) support' CONFIG_FTL $CONFIG_MTD dep_tristate ' NFTL (NAND Flash Translation Layer) support' CONFIG_NFTL $CONFIG_MTD - if [ "$CONFIG_EXPERIMENTAL" = "y" -a "$CONFIG_NFTL" != "n" ]; then - bool ' Write support for NFTL (EXPERIMENTAL)' CONFIG_NFTL_RW + if [ "$CONFIG_NFTL" = "y" -o "$CONFIG_NFTL" = "m" ]; then + bool ' Write support for NFTL (BETA)' CONFIG_NFTL_RW fi fi diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile index 3f28868e3..7fdbf65a4 100644 --- a/drivers/mtd/Makefile +++ b/drivers/mtd/Makefile @@ -44,7 +44,7 @@ SUB_DIRS := ALL_SUB_DIRS := MOD_SUB_DIRS := -export-objs := mtdcore.o +export-objs := mtdcore.o mtdpart.o list-multi := # MTD devices @@ -52,7 +52,7 @@ obj-$(CONFIG_MTD) += mtdcore.o obj-$(CONFIG_MTD_DOC1000) += doc1000.o obj-$(CONFIG_MTD_DOC2000) += doc2000.o obj-$(CONFIG_MTD_DOC2001) += doc2001.o -obj-$(CONFIG_MTD_DOCPROBE) += docprobe.o +obj-$(CONFIG_MTD_DOCPROBE) += docprobe.o docecc.o obj-$(CONFIG_MTD_SLRAM) += slram.o obj-$(CONFIG_MTD_PMC551) += pmc551.o obj-$(CONFIG_MTD_MTDRAM) += mtdram.o @@ -70,7 +70,7 @@ obj-$(CONFIG_MTD_PHYSMAP) += physmap.o obj-$(CONFIG_MTD_MIXMEM) += mixmem.o obj-$(CONFIG_MTD_NORA) += nora.o obj-$(CONFIG_MTD_OCTAGON) += octagon-5066.o -obj-$(CONFIG_MTD_PNC2000) += pnc2000.o +obj-$(CONFIG_MTD_PNC2000) += pnc2000.o mtdpart.o obj-$(CONFIG_MTD_RPXLITE) += rpxlite.o obj-$(CONFIG_MTD_VMAX) += vmax301.o @@ -78,7 +78,7 @@ obj-$(CONFIG_MTD_VMAX) += vmax301.o obj-$(CONFIG_MTD_CHAR) += mtdchar.o obj-$(CONFIG_MTD_BLOCK) += mtdblock.o obj-$(CONFIG_FTL) += ftl.o -obj-$(CONFIG_NFTL) += nftl.o +obj-$(CONFIG_NFTL) += nftl.o nftlmount.o # Extract lists of the multi-part drivers. # The 'int-*' lists are the intermediate files used to build the multi's. diff --git a/drivers/mtd/cfi_cmdset_0002.c b/drivers/mtd/cfi_cmdset_0002.c index d6cce6474..6cb0de4e9 100644 --- a/drivers/mtd/cfi_cmdset_0002.c +++ b/drivers/mtd/cfi_cmdset_0002.c @@ -437,7 +437,7 @@ static int cfi_amdext_erase_2_by_16 (struct mtd_info *mtd, struct erase_info *in adr = instr->addr - (chipnum << cfi->chipshift) * (cfi->interleave); len = instr->len; -printk("erase : 0x%lx 0x%lx 0x%x 0x%x\n", adr, len, chipnum, mtd->size); + printk("erase : 0x%lx 0x%lx 0x%x 0x%lx\n", adr, len, chipnum, mtd->size); while(len) { //printk("erase : 0x%x 0x%x 0x%x 0x%x\n", chipnum, adr, len, cfi->chipshift); diff --git a/drivers/mtd/doc1000.c b/drivers/mtd/doc1000.c index ce1b6fdd8..584745359 100644 --- a/drivers/mtd/doc1000.c +++ b/drivers/mtd/doc1000.c @@ -1,16 +1,7 @@ /*====================================================================== - $Id: doc1000.c,v 1.8 2000/07/03 10:01:38 dwmw2 Exp $ + $Id: doc1000.c,v 1.11 2000/11/24 13:43:16 dwmw2 Exp $ - A general driver for accessing PCMCIA card memory via Bulk - Memory Services. - - This driver provides the equivalent of /dev/mem for a PCMCIA - card's attribute and common memory. It includes character - and block devices. - - Written by David Hinds, dhinds@allegro.stanford.edu - ======================================================================*/ @@ -295,10 +286,9 @@ int flashcard_write (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen static inline int byte_write (volatile u_char *addr, u_char byte) { register u_char status; - register u_short i; - - for (i = 0; i < max_tries; i++) - { + register u_short i = 0; + + do { status = readb(addr); if (status & CSR_WR_READY) { @@ -306,7 +296,9 @@ static inline int byte_write (volatile u_char *addr, u_char byte) writeb(byte, addr); return 0; } - } + i++; + } while(i < max_tries); + printk(KERN_NOTICE "flashcard: byte_write timed out, status 0x%x\n",status); return -EIO; @@ -314,11 +306,10 @@ static inline int byte_write (volatile u_char *addr, u_char byte) static inline int word_write (volatile u_char *addr, __u16 word) { - register u_short status = 0; - register u_short i; + register u_short status; + register u_short i = 0; - for (i = 0; i < max_tries; i++) - { + do { status = readw(addr); if ((status & CSR_WR_READY) == CSR_WR_READY) { @@ -326,7 +317,8 @@ static inline int word_write (volatile u_char *addr, __u16 word) writew(word, addr); return 0; } - } + i++; + } while(i < max_tries); printk(KERN_NOTICE "flashcard: word_write timed out at %p, status 0x%x\n", addr, status); return -EIO; @@ -362,24 +354,22 @@ static inline int check_erase(volatile u_char *addr) static inline int suspend_erase(volatile u_char *addr) { - __u16 status = 0; - u_long i; + __u16 status; + u_long i = 0; writew(IF_ERASE_SUSPEND, addr); writew(IF_READ_CSR, addr); - for (i = 0; i < max_tries; i++) - { + do { status = readw(addr); - if ((status & CSR_WR_READY) == CSR_WR_READY) break; - } - if (i == max_tries) - { - printk(KERN_NOTICE "flashcard: suspend_erase timed out, status 0x%x\n", status); - return -EIO; - } - - return 0; + if ((status & CSR_WR_READY) == CSR_WR_READY) + return 0; + i++; + } while(i < max_tries); + + printk(KERN_NOTICE "flashcard: suspend_erase timed out, status 0x%x\n", status); + return -EIO; + } static inline void resume_erase(volatile u_char *addr) @@ -413,12 +403,11 @@ static inline void reset_block(volatile u_char *addr) static inline int check_write(volatile u_char *addr) { - u_short status = 0, i; + u_short status, i = 0; writew(IF_READ_CSR, addr); - for (i=0; i < max_tries; i++) - { + do { status = readw(addr); if (status & (CSR_WR_ERR | CSR_VPP_LOW)) { @@ -428,7 +417,9 @@ static inline int check_write(volatile u_char *addr) } if ((status & CSR_WR_READY) == CSR_WR_READY) return 0; - } + i++; + } while (i < max_tries); + printk(KERN_NOTICE "flashcard: write timed out at %p, status 0x%x\n", addr, status); return -EIO; } @@ -519,7 +510,7 @@ static void flashcard_periodic(unsigned long data) } -#if defined (MODULE) && LINUX_VERSION_CODE < 0x20300 +#if defined (MODULE) && LINUX_VERSION_CODE < 0x20211 #define init_doc1000 init_module #define cleanup_doc1000 cleanup_module #endif @@ -600,3 +591,7 @@ static void __init cleanup_doc1000(void) kfree(mymtd); } +#if LINUX_VERSION_CODE >= 0x20211 +module_init(init_doc1000); +module_exit(cleanup_doc1000); +#endif diff --git a/drivers/mtd/doc2000.c b/drivers/mtd/doc2000.c index cef67512b..22ef68e62 100644 --- a/drivers/mtd/doc2000.c +++ b/drivers/mtd/doc2000.c @@ -1,8 +1,11 @@ -/* Linux driver for Disk-On-Chip 2000 */ -/* (c) 1999 Machine Vision Holdings, Inc. */ -/* Author: David Woodhouse <dwmw2@mvhi.com> */ -/* $Id: doc2000.c,v 1.24 2000/07/13 10:03:31 dwmw2 Exp $ */ +/* + * 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.39 2000/12/01 17:34:29 dwmw2 Exp $ + */ #include <linux/kernel.h> #include <linux/module.h> @@ -19,286 +22,383 @@ #include <linux/mtd/mtd.h> #include <linux/mtd/nand.h> +#include <linux/mtd/nand_ids.h> #include <linux/mtd/doc2000.h> -//#define PRERELEASE +#define DOC_SUPPORT_2000 +#define DOC_SUPPORT_MILLENNIUM -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 *eecbuf); -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); -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); +#ifdef DOC_SUPPORT_2000 +#define DoC_is_2000(doc) (doc->ChipID == DOC_ChipID_Doc2k) +#else +#define DoC_is_2000(doc) (0) +#endif +#ifdef 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); +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); +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 *doc2klist = NULL; -/* DOC_WaitReady: Wait for RDY line to be asserted by the flash chip */ +/* 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); + } + +} -static int _DoC_WaitReady (unsigned long docptr) +/* DOC_WaitReady: Wait for RDY line to be asserted by the flash chip */ +static int _DoC_WaitReady(struct DiskOnChip *doc) { - //long c=-1; - short c=-1; + unsigned long docptr = doc->virtadr; + unsigned short c = 0xffff; - DEBUG(2,"_DoC_WaitReady called for out-of-line wait\n"); + 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(2, "_DoC_WaitReady timed out.\n"); - - return (c==0); + DEBUG(MTD_DEBUG_LEVEL2, "_DoC_WaitReady timed out.\n"); + + return (c == 0); } -static inline int DoC_WaitReady(unsigned long docptr) +static inline int DoC_WaitReady(struct DiskOnChip *doc) { + unsigned long docptr = doc->virtadr; /* This is inline, to optimise the common case, where it's ready instantly */ - volatile char dummy; int ret = 0; - /* Out-of-line routine to wait for chip response */ - /* TPW: Add 4 reads - see Software Requirement 2.3.2 */ - dummy = ReadDOC(docptr, CDSNControl); - dummy = ReadDOC(docptr, CDSNControl); - dummy = ReadDOC(docptr, CDSNControl); - dummy = ReadDOC(docptr, CDSNControl); - + /* 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)) - ret = _DoC_WaitReady(docptr); /* Call the out-of-line routine to wait */ - - /* TPW: Add 2 reads - see Software Requirement 2.3.2 */ - dummy = ReadDOC(docptr, CDSNControl); - dummy = ReadDOC(docptr, CDSNControl); + /* 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. */ -/* DoC_Command: Send a flash command to the flash chip */ - -static inline int DoC_Command(unsigned long docptr, unsigned char command, unsigned char xtraflags) +static inline int DoC_Command(struct DiskOnChip *doc, unsigned char command, + unsigned char xtraflags) { + unsigned long 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( CDSN_CTRL_FLASH_IO | xtraflags | CDSN_CTRL_CLE | CDSN_CTRL_CE, - docptr, CDSNControl); + 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, 2k_CDSN_IO); - + WriteDOC_(command, docptr, doc->ioreg); + /* Lower the CLE line */ - WriteDOC( CDSN_CTRL_FLASH_IO | xtraflags | CDSN_CTRL_CE, docptr, CDSNControl); + WriteDOC(xtraflags | CDSN_CTRL_CE, docptr, CDSNControl); + DoC_Delay(doc, 4); /* Software requirement 11.4.3 for Millennium */ - /* Wait for the chip to respond */ - return DoC_WaitReady(docptr); + /* 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 */ +/* 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 inline int DoC_Address (unsigned long docptr, int numbytes, unsigned long ofs, - unsigned char xtraflags1, unsigned char xtraflags2) +static int DoC_Address(struct DiskOnChip *doc, int numbytes, unsigned long ofs, + unsigned char xtraflags1, unsigned char xtraflags2) { - /* Assert the ALE (Address Latch Enable line to the flash chip */ - WriteDOC( CDSN_CTRL_FLASH_IO | xtraflags1 | CDSN_CTRL_ALE | CDSN_CTRL_CE, - docptr, CDSNControl); + unsigned long docptr; + int i; + + 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 */ - /* Three cases: - numbytes == 1: Send single byte, bits 0-7. - numbytes == 2: Send bits 9-16 followed by 17-23 - numbytes == 3: Send 0-7, 9-16, then 17-23 - */ - if (numbytes != 2) - WriteDOC(ofs & 0xff, docptr, 2k_CDSN_IO); - - if (numbytes != 1) { - WriteDOC((ofs >> 9) & 0xff, docptr, 2k_CDSN_IO); - WriteDOC((ofs >> 17) & 0xff, docptr, 2k_CDSN_IO); + /* 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); } - /* Lower the ALE line */ - WriteDOC( CDSN_CTRL_FLASH_IO | xtraflags1 | xtraflags2 | CDSN_CTRL_CE, docptr, CDSNControl); + + 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); + } + } + + DoC_Delay(doc, 2); /* Needed for some slow flash chips. mf. */ - /* Wait for the chip to respond */ - return DoC_WaitReady(docptr); + /* 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) +{ + int dummy; + int modulus = 0xffff; + unsigned long docptr; + int i; + + docptr = doc->virtadr; + + 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) +{ + unsigned long docptr; + int i; + + docptr = doc->virtadr; + + 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(unsigned long docptr, int chip) +static inline int DoC_SelectChip(struct DiskOnChip *doc, int chip) { + unsigned long 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); - + 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(docptr); + return DoC_WaitReady(doc); } /* DoC_SelectFloor: Select a given floor (bank of flash chips) */ -static inline int DoC_SelectFloor(unsigned long docptr, int floor) +static inline int DoC_SelectFloor(struct DiskOnChip *doc, int floor) { + unsigned long docptr = doc->virtadr; + /* Select the floor (bank) of chips required */ - WriteDOC( floor, docptr, FloorSelect); + WriteDOC(floor, docptr, FloorSelect); /* Wait for the chip to be ready */ - return DoC_WaitReady(docptr); + 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, chipshift=0; - char *mfrname=NULL, *idname=NULL; + int mfr, id, i; + volatile char dummy; /* Page in the required floor/chip */ - DoC_SelectFloor(doc->virtadr, floor); - DoC_SelectChip(doc->virtadr, chip); + DoC_SelectFloor(doc, floor); + DoC_SelectChip(doc, chip); /* Reset the chip */ - if (DoC_Command(doc->virtadr, NAND_CMD_RESET, CDSN_CTRL_WP)) { - DEBUG(2, "DoC_Command (reset) for %d,%d returned true\n", floor,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->virtadr, NAND_CMD_READID, CDSN_CTRL_WP)) { - DEBUG(2,"DoC_Command (ReadID) for %d,%d returned true\n", floor,chip); + + + /* 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->virtadr, 1, 0, CDSN_CTRL_WP, 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 */ - mfr = ReadDOC(doc->virtadr, 2k_CDSN_IO); - id = ReadDOC(doc->virtadr, 2k_CDSN_IO); - + + /* CDSN Slow IO register see Software Requirement 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 Requirement 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 */ + return 1; /* This is another the same the first */ else - printk(KERN_WARNING "Flash chip at floor %d, chip %d is different:\n", + printk(KERN_WARNING + "Flash chip at floor %d, chip %d is different:\n", floor, chip); } - - /* Print (and store if first time) the manufacturer and ID codes. */ - - switch(mfr) { - case NAND_MFR_TOSHIBA: /* Toshiba */ - mfrname = "Toshiba"; - - switch(id) { - case 0x64: - idname = "TC5816BDC"; - chipshift = 21; - break; - - case 0x6b: - idname = "TC5832DC"; - chipshift = 22; - break; - - case 0x73: - idname = "TH58V128DC"; - chipshift = 24; - break; - - case 0x75: - idname = "TC58256FT/DC"; - chipshift = 25; - break; - - case 0xe5: - idname = "TC58V32DC"; - chipshift = 22; - break; - - case 0xe6: - idname = "TC58V64DC"; - chipshift = 23; - break; - - case 0xea: - idname = "TC58V16BDC"; - chipshift = 21; - break; - } - break; /* End of Toshiba parts */ - - case NAND_MFR_SAMSUNG: /* Samsung */ - mfrname = "Samsung"; - - switch(id) { - case 0x64: - idname = "KM29N16000"; - chipshift = 21; - - case 0x73: - idname = "KM29U128T"; - chipshift = 24; - break; - - case 0x75: - idname = "KM29U256T"; - chipshift = 25; - break; - - case 0xe3: - idname = "KM29W32000"; - chipshift = 22; - break; - - case 0xe6: - idname = "KM29U64000"; - chipshift = 23; - break; - - case 0xea: - idname = "KM29W16000"; - chipshift = 21; - break; - } - break; /* End of Samsung parts */ - } - - /* If we've identified it fully, print the full names */ - if (idname) { -#ifdef PRERELEASE - DEBUG(1, "Flash chip found: %2.2X %2.2X (%s %s)\n", - mfr,id,mfrname,idname); -#endif - /* If this is the first chip, store the id codes */ - if (!doc->mfr) { - doc->mfr = mfr; - doc->id = id; - doc->chipshift = chipshift; - return 1; + + /* Print and store the manufacturer and ID codes. */ + for (i = 0; nand_flash_ids[i].name != NULL; i++) { + if (mfr == nand_flash_ids[i].manufacture_id && + id == nand_flash_ids[i].model_id) { + printk(KERN_INFO + "Flash chip found: Manufacturer ID: %2.2X, " + "Chip ID: %2.2X (%s)\n", mfr, id, + nand_flash_ids[i].name); + if (!doc->mfr) { + doc->mfr = mfr; + doc->id = id; + doc->chipshift = + nand_flash_ids[i].chipshift; + doc->page256 = nand_flash_ids[i].page256; + doc->pageadrlen = + nand_flash_ids[i].pageadrlen; + doc->erasesize = + nand_flash_ids[i].erasesize; + return 1; + } + return 0; } - return 0; } + /* We haven't fully identified the chip. Print as much as we know. */ - if (mfrname) - printk(KERN_WARNING "Unknown %s flash chip found: %2.2X %2.2X\n", mfrname, - id, mfr); - else - printk(KERN_WARNING "Unknown flash chip found: %2.2X %2.2X\n", id, mfr); - - printk(KERN_WARNING "Please report to David.Woodhouse@mvhi.com\n"); + 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 */ @@ -306,18 +406,22 @@ static void DoC_ScanChips(struct DiskOnChip *this) { int floor, chip; int numchips[MAX_FLOORS]; + int maxchips = MAX_CHIPS; int ret = 1; - + this->numchips = 0; this->mfr = 0; this->id = 0; - + + if (DoC_is_Millennium(this)) + maxchips = MAX_CHIPS_MIL; + /* For each floor, find the number of valid chips it contains */ - for (floor = 0 ; floor < MAX_FLOORS ; floor++) { + for (floor = 0; floor < MAX_FLOORS; floor++) { ret = 1; - numchips[floor]=0; - for (chip = 0 ; chip < MAX_CHIPS && ret != 0; chip++ ) { - + numchips[floor] = 0; + for (chip = 0; chip < maxchips && ret != 0; chip++) { + ret = DoC_IdentChip(this, floor, chip); if (ret) { numchips[floor]++; @@ -325,26 +429,26 @@ static void DoC_ScanChips(struct DiskOnChip *this) } } } - + /* 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){ + if (!this->chips) { printk("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++) { + for (chip = 0; chip < numchips[floor]; chip++) { this->chips[ret].floor = floor; this->chips[ret].chip = chip; this->chips[ret].curadr = 0; @@ -356,8 +460,9 @@ static void DoC_ScanChips(struct DiskOnChip *this) /* 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 Mb\n", this->numchips , - this->totlen >> 20); + printk(KERN_INFO + "%d flash chips found. Total DiskOnChip size: %ld Mb\n", + this->numchips, this->totlen >> 20); } @@ -371,15 +476,15 @@ static int DoC2k_is_alias(struct DiskOnChip *doc1, struct DiskOnChip *doc2) * 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. */ + * 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); + + WriteDOC((tmp1 + 1) % 0xff, doc1->virtadr, AliasResolution); tmp2 = ReadDOC(doc2->virtadr, AliasResolution); - if (tmp2 == (tmp1+1) % 0xff) + if (tmp2 == (tmp1 + 1) % 0xff) retval = 1; else retval = 0; @@ -387,11 +492,10 @@ static int DoC2k_is_alias(struct DiskOnChip *doc1, struct DiskOnChip *doc2) /* 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 @@ -403,36 +507,48 @@ static const char im_name[] = "DoC2k_init"; */ static void DoC2k_init(struct mtd_info *mtd) { - struct DiskOnChip *this = (struct DiskOnChip *)mtd->priv; + struct DiskOnChip *this = (struct DiskOnChip *) mtd->priv; struct DiskOnChip *old = NULL; /* We must avoid being called twice for the same device. */ if (doc2klist) - old = (struct DiskOnChip *)doc2klist->priv; + old = (struct DiskOnChip *) doc2klist->priv; while (old) { if (DoC2k_is_alias(old, this)) { - printk(KERN_NOTICE "Ignoring DiskOnChip 2000 at 0x%lX - already configured\n", + printk(KERN_NOTICE + "Ignoring DiskOnChip 2000 at 0x%lX - already configured\n", this->physadr); - iounmap((void *)this->virtadr); + iounmap((void *) this->virtadr); kfree(mtd); return; } if (old->nextdoc) - old = (struct DiskOnChip *)old->nextdoc->priv; + old = (struct DiskOnChip *) old->nextdoc->priv; else old = NULL; } - - - mtd->name = "DiskOnChip 2000"; - printk(KERN_NOTICE "DiskOnChip 2000 found at address 0x%lX\n",this->physadr); + + + switch (this->ChipID) { + case DOC_ChipID_Doc2k: + mtd->name = "DiskOnChip 2000"; + this->ioreg = DoC_2k_CDSN_IO; + break; + case DOC_ChipID_DocMil: + mtd->name = "DiskOnChip Millennium"; + this->ioreg = DoC_Mil_CDSN_IO; + break; + } + + printk(KERN_NOTICE "%s found at address 0x%lX\n", mtd->name, + this->physadr); mtd->type = MTD_NANDFLASH; mtd->flags = MTD_CAP_NANDFLASH; mtd->size = 0; - mtd->erasesize = 0x2000; + mtd->erasesize = 0; mtd->oobblock = 512; mtd->oobsize = 16; mtd->module = THIS_MODULE; @@ -446,131 +562,182 @@ static void DoC2k_init(struct mtd_info *mtd) 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((void *)this->virtadr); + iounmap((void *) this->virtadr); } else { this->nextdoc = doc2klist; doc2klist = mtd; - mtd->size = this->totlen; + 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) +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); } -static int doc_read_ecc (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf, u_char *eccbuf) +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 DiskOnChip *this = (struct DiskOnChip *)mtd->priv; - int di=0; /* Yes, DI is a hangover from when I was disassembling the binary driver */ + struct DiskOnChip *this = (struct DiskOnChip *) mtd->priv; unsigned long docptr; struct Nand *mychip; + unsigned char syndrome[6]; + volatile char dummy; + int i, len256 = 0, ret=0; docptr = this->virtadr; /* 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)) + 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(docptr, mychip->floor); - DoC_SelectChip(docptr, mychip->chip); + DoC_SelectFloor(this, mychip->floor); + DoC_SelectChip(this, mychip->chip); + } else if (this->curchip != mychip->chip) { + DoC_SelectChip(this, mychip->chip); } - else if (this->curchip != mychip->chip) { - DoC_SelectChip(docptr, 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); + 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); } - DoC_Command(docptr, (from >> 8) & 1, CDSN_CTRL_WP); - DoC_Address(docptr, 3, from, CDSN_CTRL_WP , CDSN_CTRL_ECC_IO); + /* 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); - for (di=0; di < len ; di++) { - buf[di] = ReadDOC(docptr, 2k_CDSN_IO); + 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 */ - for (di=0; di<6; di++) { - eccbuf[di] = ReadDOC(docptr, 2k_CDSN_IO); - } - + /* 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 */ - (void) ReadDOC(docptr, 2k_ECCStatus); - (void) ReadDOC(docptr, 2k_ECCStatus); - + 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 (ReadDOC(docptr, 2k_ECCStatus) & 0x80) { + if (i & 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); - /* FIXME: Implement ECC error correction, don't just whinge */ - - /* 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 */ - return -EIO; +#ifdef ECC_DEBUG + printk("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 - else - printk("ECC OK 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]); + printk("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 - /* Reset the ECC engine */ - WriteDOC(DOC_ECC_RESV, docptr , ECCConf); - + /* disable the ECC engine */ + WriteDOC(DOC_ECC_DIS, docptr , ECCConf); } - return 0; + return ret; } -static int doc_write (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const 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 char as[6]; - return doc_write_ecc(mtd, to, len, retlen, buf, as); + char eccbuf[6]; + return doc_write_ecc(mtd, to, len, retlen, buf, eccbuf); } -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) +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 DiskOnChip *this = (struct DiskOnChip *)mtd->priv; - int di=0; + struct DiskOnChip *this = (struct DiskOnChip *) mtd->priv; + int di; /* Yes, DI is a hangover from when I was disassembling the binary driver */ unsigned long docptr; + volatile char dummy; + int len256 = 0; struct Nand *mychip; docptr = this->virtadr; @@ -578,82 +745,118 @@ static int doc_write_ecc (struct mtd_info *mtd, loff_t to, size_t len, size_t *r /* 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)) + 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 + /* The ECC will not be calculated correctly if less than 512 is written */ + if (len != 0x200 && eccbuf) + printk(KERN_WARNING + "ECC needs a full sector write (adr: %lx size %lx)\n", + (long) to, (long) len); + + /* 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(docptr, mychip->floor); - DoC_SelectChip(docptr, mychip->chip); + DoC_SelectFloor(this, mychip->floor); + DoC_SelectChip(this, mychip->chip); + } else if (this->curchip != mychip->chip) { + DoC_SelectChip(this, mychip->chip); } - else if (this->curchip != mychip->chip) { - DoC_SelectChip(docptr, mychip->chip); - } - + this->curfloor = mychip->floor; this->curchip = mychip->chip; - + /* Set device to main plane of flash */ - DoC_Command(docptr, NAND_CMD_RESET, CDSN_CTRL_WP); - DoC_Command(docptr, NAND_CMD_READ0, CDSN_CTRL_WP); + 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); + 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); } - DoC_Command(docptr, NAND_CMD_SEQIN, 0); - DoC_Address(docptr, 3, to, 0, CDSN_CTRL_ECC_IO); + /* 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); - for (di=0; di < len ; di++) { - WriteDOC(buf[di], docptr, 2k_CDSN_IO); + if (ReadDOC_(docptr, this->ioreg) & 1) { + printk("Error programming flash\n"); + /* Error in programming */ + *retlen = 0; + 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 1 - /* eduardp@m-sys.com says this shouldn't be necessary, - * but it doesn't actually work without it, so I've - * left it in for now. dwmw2. - */ - - WriteDOC( 0, docptr, 2k_CDSN_IO); - WriteDOC( 0, docptr, 2k_CDSN_IO); - WriteDOC( 0, docptr, 2k_CDSN_IO); -#endif + 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); + } + /* Read the ECC data through the DiskOnChip ECC logic */ - for (di=0; di<6; di++) { + 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] ); + 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 - /* Reset the ECC engine */ - WriteDOC(DOC_ECC_RESV, docptr , ECCConf); - } - DoC_Command(docptr, NAND_CMD_PAGEPROG, 0); + DoC_Command(this, NAND_CMD_PAGEPROG, 0); - DoC_Command(docptr, NAND_CMD_STATUS, CDSN_CTRL_WP); + DoC_Command(this, NAND_CMD_STATUS, CDSN_CTRL_WP); /* There's an implicit DoC_WaitReady() in DoC_Command */ - if (ReadDOC(docptr, 2k_CDSN_IO) & 1) { + dummy = ReadDOC(docptr, CDSNSlowIO); + DoC_Delay(this, 2); + + if (ReadDOC_(docptr, this->ioreg) & 1) { printk("Error programming flash\n"); /* Error in programming */ *retlen = 0; @@ -662,84 +865,155 @@ static int doc_write_ecc (struct mtd_info *mtd, loff_t to, size_t len, size_t *r /* Let the caller know we completed it */ *retlen = len; + + if (eccbuf) { + unsigned char x[8]; + size_t dummy; + + /* Write the ECC data to flash */ + for (di=0; di<6; di++) + x[di] = eccbuf[di]; + + x[6]=0x55; + x[7]=0x55; + + return doc_write_oob(mtd, to, 8, &dummy, x); + } return 0; } - - -static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len, size_t *retlen, u_char *buf) +static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len, + size_t * retlen, u_char * buf) { - struct DiskOnChip *this = (struct DiskOnChip *)mtd->priv; - int i; + struct DiskOnChip *this = (struct DiskOnChip *) mtd->priv; + int len256 = 0; unsigned long docptr; struct Nand *mychip; - + docptr = this->virtadr; - + mychip = &this->chips[ofs >> this->chipshift]; - + 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); + 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(docptr, NAND_CMD_READOOB, CDSN_CTRL_WP); - DoC_Address(docptr, 3, ofs, CDSN_CTRL_WP, 0); - - for (i=0; i<len; i++) - buf[i] = ReadDOC(docptr, 2k_CDSN_IO); - + + /* 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; return 0; } -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(struct mtd_info *mtd, loff_t ofs, size_t len, + size_t * retlen, const u_char * buf) { - struct DiskOnChip *this = (struct DiskOnChip *)mtd->priv; - int i; - unsigned long docptr; - struct Nand *mychip; + struct DiskOnChip *this = (struct DiskOnChip *) mtd->priv; + int len256 = 0; + unsigned long docptr = this->virtadr; + struct Nand *mychip = &this->chips[ofs >> this->chipshift]; + int dummy; - // 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, + // 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]); - docptr = this->virtadr; - - 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); + 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(docptr, NAND_CMD_RESET, CDSN_CTRL_WP); - DoC_Command(docptr, NAND_CMD_READOOB, CDSN_CTRL_WP); - DoC_Command(docptr, NAND_CMD_SEQIN, 0); - DoC_Address(docptr, 3, ofs, 0, 0); - - for (i=0; i<len; i++) - WriteDOC(buf[i], docptr, 2k_CDSN_IO); + /* 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); - DoC_Command(docptr, NAND_CMD_PAGEPROG, 0); - DoC_Command(docptr, NAND_CMD_STATUS, 0); + /* 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 */ + + dummy = ReadDOC(docptr, CDSNSlowIO); + DoC_Delay(this, 2); + + if (ReadDOC_(docptr, this->ioreg) & 1) { + printk("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 (ReadDOC(docptr, 2k_CDSN_IO) & 1) { + dummy = ReadDOC(docptr, CDSNSlowIO); + DoC_Delay(this, 2); + + if (ReadDOC_(docptr, this->ioreg) & 1) { printk("Error programming oob data\n"); /* There was an error */ *retlen = 0; @@ -751,102 +1025,89 @@ static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len, size_t *r } - -int doc_erase (struct mtd_info *mtd, struct erase_info *instr) +int doc_erase(struct mtd_info *mtd, struct erase_info *instr) { - struct DiskOnChip *this = (struct DiskOnChip *)mtd->priv; + struct DiskOnChip *this = (struct DiskOnChip *) mtd->priv; unsigned long ofs = instr->addr; unsigned long len = instr->len; unsigned long docptr; struct Nand *mychip; - - if(len != mtd->erasesize) - printk(KERN_WARNING "Erase not right size (%lx != %lx)n", len, mtd->erasesize); - + + if (len != mtd->erasesize) + printk(KERN_WARNING "Erase not right size (%lx != %lx)n", + len, mtd->erasesize); docptr = this->virtadr; - + mychip = &this->chips[ofs >> this->chipshift]; - + 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); + 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; - + instr->state = MTD_ERASE_PENDING; - DoC_Command(docptr, NAND_CMD_ERASE1, 0); - DoC_Address(docptr, 2, ofs, 0, 0); - DoC_Command(docptr, NAND_CMD_ERASE2, 0); + DoC_Command(this, NAND_CMD_ERASE1, 0); + DoC_Address(this, ADDR_PAGE, ofs, 0, 0); + DoC_Command(this, NAND_CMD_ERASE2, 0); instr->state = MTD_ERASING; - DoC_Command(docptr, NAND_CMD_STATUS, CDSN_CTRL_WP); + DoC_Command(this, NAND_CMD_STATUS, CDSN_CTRL_WP); - if (ReadDOC(docptr, 2k_CDSN_IO) & 1) { + if (ReadDOC_(docptr, this->ioreg) & 1) { printk("Error writing\n"); /* There was an error */ instr->state = MTD_ERASE_FAILED; - } - else + } else instr->state = MTD_ERASE_DONE; - if (instr->callback) + if (instr->callback) instr->callback(instr); - + return 0; } - - - /**************************************************************************** * * Module stuff * ****************************************************************************/ -static int __init init_doc2000(void) -{ - inter_module_register(im_name, THIS_MODULE, &DoC2k_init); - return 0; -} - -#if LINUX_VERSION_CODE < 0x20300 -#ifdef MODULE +#if LINUX_VERSION_CODE < 0x20212 && defined(MODULE) #define cleanup_doc2000 cleanup_module -#endif -#define __exit +#define init_doc2000 init_module #endif +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 = (struct DiskOnChip *)mtd->priv; + while ((mtd = doc2klist)) { + this = (struct DiskOnChip *) mtd->priv; doc2klist = this->nextdoc; - + del_mtd_device(mtd); - - iounmap((void *)this->virtadr); + + iounmap((void *) this->virtadr); kfree(this->chips); kfree(mtd); } inter_module_unregister(im_name); - } -module_init(init_doc2000); - -#if LINUX_VERSION_CODE > 0x20300 module_exit(cleanup_doc2000); -#endif +module_init(init_doc2000); diff --git a/drivers/mtd/doc2001.c b/drivers/mtd/doc2001.c index 8a9f03235..79aa3630d 100644 --- a/drivers/mtd/doc2001.c +++ b/drivers/mtd/doc2001.c @@ -1,7 +1,11 @@ -/* Linux driver for Disk-On-Chip Millennium */ -/* (c) 1999 Machine Vision Holdings, Inc. */ -/* Author: David Woodhouse <dwmw2@mvhi.com> */ -/* $Id: doc2001.c,v 1.7 2000/07/13 10:41:39 dwmw2 Exp $ */ + +/* + * 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.24 2000/12/01 13:11:02 dwmw2 Exp $ + */ #include <linux/kernel.h> #include <linux/module.h> @@ -18,38 +22,26 @@ #include <linux/mtd/mtd.h> #include <linux/mtd/nand.h> +#include <linux/mtd/nand_ids.h> #include <linux/mtd/doc2000.h> -static struct { - char * name; - int manufacture_id; - int model_id; - int chipshift; -} nand_flash_ids[] = { - {"Toshiba TC5816BDC", NAND_MFR_TOSHIBA, 0x64, 21}, - {"Toshiba TC5832DC", NAND_MFR_TOSHIBA, 0x6b, 22}, - {"Toshiba TH58V128DC", NAND_MFR_TOSHIBA, 0x73, 24}, - {"Toshiba TC58256FT/DC", NAND_MFR_TOSHIBA, 0x75, 25}, - {"Toshiba TC58V32DC", NAND_MFR_TOSHIBA, 0xe5, 22}, - {"Toshiba TC58V64DC", NAND_MFR_TOSHIBA, 0xe6, 23}, - {"Toshiba TC58V16BDC", NAND_MFR_TOSHIBA, 0xea, 21}, - {"Samsung KM29N16000", NAND_MFR_SAMSUNG, 0x64, 21}, - {"Samsung KM29U128T", NAND_MFR_SAMSUNG, 0x73, 24}, - {"Samsung KM29U256T", NAND_MFR_SAMSUNG, 0x75, 25}, - {"Samsung KM29W32000", NAND_MFR_SAMSUNG, 0xe3, 22}, - {"Samsung KM29U64000", NAND_MFR_SAMSUNG, 0xe6, 23}, - {"Samsung KM29W16000", NAND_MFR_SAMSUNG, 0xea, 21}, - {NULL,} -}; - -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 *eecbuf); -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); +/* #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); +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); 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, @@ -58,6 +50,7 @@ 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(unsigned long docptr, unsigned short cycles) { volatile char dummy; @@ -72,14 +65,20 @@ static int _DoC_WaitReady(unsigned long 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(unsigned long docptr) +static inline int DoC_WaitReady(unsigned long docptr) { /* This is inline, to optimise the common case, where it's ready instantly */ int ret = 0; @@ -99,33 +98,35 @@ static __inline__ int DoC_WaitReady(unsigned long docptr) 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__ void DoC_Command(unsigned long docptr, unsigned char command, - unsigned char xtraflags) +/* 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 void DoC_Command(unsigned long 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); + WriteDOC(xtraflags | CDSN_CTRL_CLE | CDSN_CTRL_CE, docptr, CDSNControl); DoC_Delay(docptr, 4); /* Send the command */ WriteDOC(command, docptr, CDSNSlowIO); WriteDOC(command, docptr, Mil_CDSN_IO); - + /* 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 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__ void DoC_Address (unsigned long docptr, int numbytes, unsigned long ofs, +/* 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 inline void DoC_Address(unsigned long 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); + /* 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 */ @@ -217,11 +218,11 @@ static int DoC_IdentChip(struct DiskOnChip *doc, int floor, int chip) if (mfr == 0xff || mfr == 0) return 0; - /* FIXME: to deal with mulit-flash on multi-Millennium case more carefully */ + /* FIXME: to deal with multi-flash on multi-Millennium case more carefully */ for (i = 0; nand_flash_ids[i].name != NULL; i++) { if (mfr == nand_flash_ids[i].manufacture_id && id == nand_flash_ids[i].model_id) { - printk(KERN_INFO "Flash chip found: Manufacture ID: %2.2X, " + printk(KERN_INFO "Flash chip found: Manufacturer ID: %2.2X, " "Chip ID: %2.2X (%s)\n", mfr, id, nand_flash_ids[i].name); doc->mfr = mfr; @@ -235,7 +236,7 @@ static int DoC_IdentChip(struct DiskOnChip *doc, int floor, int chip) 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) @@ -243,11 +244,11 @@ 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; @@ -264,14 +265,14 @@ static void DoC_ScanChips(struct DiskOnChip *this) 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++) { @@ -286,7 +287,7 @@ static void DoC_ScanChips(struct DiskOnChip *this) /* 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 Mbytes\n", + printk(KERN_NOTICE "%d flash chips found. Total DiskOnChip size: %ld Mbytes\n", this->numchips ,this->totlen >> 20); } @@ -317,7 +318,7 @@ static int DoCMil_is_alias(struct DiskOnChip *doc1, struct DiskOnChip *doc2) /* Restore register contents. May not be necessary, but do it just to * be safe. */ WriteDOC(tmp1, doc1->virtadr, AliasResolution); - + return retval; } @@ -330,7 +331,7 @@ static const char im_name[] = "DoCMil_init"; * this module is non-zero, i.e. between inter_module_get and * inter_module_put. Keith Owens <kaos@ocs.com.au> 29 Oct 2000. */ -void DoCMil_init(struct mtd_info *mtd) +static void DoCMil_init(struct mtd_info *mtd) { struct DiskOnChip *this = (struct DiskOnChip *)mtd->priv; struct DiskOnChip *old = NULL; @@ -355,12 +356,15 @@ void DoCMil_init(struct mtd_info *mtd) mtd->name = "DiskOnChip Millennium"; printk(KERN_NOTICE "DiskOnChip Millennium found at address 0x%lX\n", - this->physadr); + this->physadr); mtd->type = MTD_NANDFLASH; mtd->flags = MTD_CAP_NANDFLASH; mtd->size = 0; + + /* FIXME: erase size is not always 8kB */ mtd->erasesize = 0x2000; + mtd->oobblock = 512; mtd->oobsize = 16; mtd->module = THIS_MODULE; @@ -374,15 +378,15 @@ void DoCMil_init(struct mtd_info *mtd) mtd->read_oob = doc_read_oob; mtd->write_oob = doc_write_oob; mtd->sync = NULL; - + this->totlen = 0; - this->numchips = 0; + this->numchips = 0; this->curfloor = -1; this->curchip = -1; - + /* Ident all the chips present. */ DoC_ScanChips(this); - + if (!this->totlen) { kfree(mtd); iounmap((void *)this->virtadr); @@ -405,8 +409,9 @@ static int doc_read (struct mtd_info *mtd, loff_t from, size_t len, static int doc_read_ecc (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf, u_char *eccbuf) { - int i; + int i, ret; volatile char dummy; + unsigned char syndrome[6]; struct DiskOnChip *this = (struct DiskOnChip *)mtd->priv; unsigned long docptr = this->virtadr; struct Nand *mychip = &this->chips[from >> (this->chipshift)]; @@ -429,45 +434,55 @@ static int doc_read_ecc (struct mtd_info *mtd, loff_t from, size_t len, 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, FIXME: is this correct ?? */ + /* disable the ECC engine */ WriteDOC (DOC_ECC_RESET, docptr, ECCConf); - WriteDOC (DOC_ECC_DIS, docptr, ECCConf); + WriteDOC (DOC_ECC_DIS, docptr, ECCConf); } - /* 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); - /* 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++) { - buf[i] = ReadDOC(docptr, Mil_CDSN_IO); + /* 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)); } - buf[i] = ReadDOC(docptr, LastDataRead); +#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) { - /* FIXME: are we reading the ECC from the ECC logic of DOC or - the spare data space on the flash chip i.e. How do we - control the Spare Area Enable bit of the flash ?? */ - /* Read the ECC data through the DiskOnChip ECC logic + /* 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++) { - eccbuf[i] = ReadDOC(docptr, Mil_CDSN_IO); + /* 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); } - eccbuf[i] = ReadDOC(docptr, LastDataRead); +#else + memcpy_fromio(eccbuf, docptr + DoC_Mil_CDSN_IO, 5); +#endif + eccbuf[5] = ReadDOC(docptr, LastDataRead); /* Flush the pipeline */ dummy = ReadDOC(docptr, ECCConf); @@ -475,34 +490,45 @@ static int doc_read_ecc (struct mtd_info *mtd, loff_t from, size_t len, /* 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); - - /* FIXME: Implement ECC error correction, don't just whinge */ - - /* 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 */ - return -EIO; +#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("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 - else - printk("ECC OK 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]); + 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 - /* Reset the ECC engine */ - WriteDOC(DOC_ECC_RESV, docptr , ECCConf); + /* disable the ECC engine */ + WriteDOC(DOC_ECC_DIS, docptr , ECCConf); } - return 0; + return ret; } static int doc_write (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { - static char as[6]; - return doc_write_ecc(mtd, to, len, retlen, buf, as); + char eccbuf[6]; + return doc_write_ecc(mtd, to, len, retlen, buf, eccbuf); } static int doc_write_ecc (struct mtd_info *mtd, loff_t to, size_t len, @@ -532,42 +558,48 @@ static int doc_write_ecc (struct mtd_info *mtd, loff_t to, size_t len, if (this->curfloor != mychip->floor) { DoC_SelectFloor(docptr, mychip->floor); DoC_SelectChip(docptr, mychip->chip); - } - else if (this->curchip != 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, CDSN_CTRL_WP); + DoC_Command(docptr, NAND_CMD_RESET, 0x00); DoC_WaitReady(docptr); /* Set device to main plane of flash */ - DoC_Command(docptr, NAND_CMD_READ0, CDSN_CTRL_WP); + 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, FIXME: is this correct ?? */ + /* disable the ECC engine */ WriteDOC (DOC_ECC_RESET, docptr, ECCConf); WriteDOC (DOC_ECC_DIS, docptr, ECCConf); } - /* 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); - /* 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++) { - WriteDOC(buf[i], docptr, Mil_CDSN_IO); + /* 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 DECC logic + /* 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); @@ -578,10 +610,26 @@ static int doc_write_ecc (struct mtd_info *mtd, loff_t to, size_t len, 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++) { - WriteDOC(eccbuf[i], docptr, Mil_CDSN_IO); + /* 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 @@ -589,9 +637,6 @@ static int doc_write_ecc (struct mtd_info *mtd, loff_t to, size_t len, (long) to, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3], eccbuf[4], eccbuf[5]); #endif - - /* Reset the ECC engine */ - WriteDOC(DOC_ECC_RESV, docptr , ECCConf); } /* Commit the Page Program command and wait for ready @@ -601,12 +646,13 @@ static int doc_write_ecc (struct mtd_info *mtd, loff_t to, size_t len, /* Read the status of the flash device through CDSN Slow IO register see Software Requirement 11.4 item 5.*/ - DoC_Command(docptr, NAND_CMD_STATUS, 0x00); + DoC_Command(docptr, NAND_CMD_STATUS, CDSN_CTRL_WP); dummy = ReadDOC(docptr, CDSNSlowIO); DoC_Delay(docptr, 2); if (ReadDOC(docptr, Mil_CDSN_IO) & 1) { printk("Error programming flash\n"); - /* Error in programming */ + /* Error in programming + FIXME: implement Bad Block Replacement (in nftl.c ??) */ *retlen = 0; return -EIO; } @@ -620,31 +666,29 @@ static int doc_write_ecc (struct mtd_info *mtd, loff_t to, size_t len, static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len, size_t *retlen, u_char *buf) { - volatile char dummy; +#ifndef USE_MEMCPY int i; +#endif + volatile char dummy; struct DiskOnChip *this = (struct DiskOnChip *)mtd->priv; unsigned long docptr = this->virtadr; struct Nand *mychip = &this->chips[ofs >> this->chipshift]; - /* FIXME: should we restrict the access between 512 to 527 ?? */ - /* 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) { + } else if (this->curchip != mychip->chip) { DoC_SelectChip(docptr, mychip->chip); } this->curfloor = mychip->floor; this->curchip = mychip->chip; - /* FIXME: should we disable ECC engine in this way ?? */ - /* disable the ECC engine, FIXME: is this correct ?? */ + /* disable the ECC engine */ WriteDOC (DOC_ECC_RESET, docptr, ECCConf); WriteDOC (DOC_ECC_DIS, docptr, ECCConf); - /* issue the Read2 command to read the Spare Data Area. + /* 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); @@ -654,10 +698,17 @@ static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len, /* 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++) { - buf[i] = ReadDOC(docptr, Mil_CDSN_IO); + /* 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); } buf[i] = ReadDOC(docptr, LastDataRead); +#else + memcpy_fromio(buf, docptr + DoC_Mil_CDSN_IO, len - 1); +#endif + buf[len - 1] = ReadDOC(docptr, LastDataRead); *retlen = len; @@ -667,32 +718,32 @@ static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len, 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; struct DiskOnChip *this = (struct DiskOnChip *)mtd->priv; unsigned long 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) { + if (this->curfloor != mychip->floor) { DoC_SelectFloor(docptr, mychip->floor); DoC_SelectChip(docptr, mychip->chip); - } - else if (this->curchip != mychip->chip) { + } else if (this->curchip != mychip->chip) { DoC_SelectChip(docptr, mychip->chip); } this->curfloor = mychip->floor; this->curchip = mychip->chip; - /* FIXME: should we disable ECC engine in this way ?? */ - /* disable the ECC engine, FIXME: is this correct ?? */ + /* 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 read the Spare Data Area. */ + /* 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 */ @@ -701,8 +752,15 @@ static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len, /* Write the data via the internal pipeline through CDSN IO register, see Pipelined Write Operations 11.2 */ - for (i = 0; i < len; i++) - WriteDOC(buf[i], docptr, Mil_CDSN_IO); +#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 @@ -717,6 +775,7 @@ static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len, 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; return -EIO; } @@ -743,13 +802,12 @@ int doc_erase (struct mtd_info *mtd, struct erase_info *instr) if (this->curfloor != mychip->floor) { DoC_SelectFloor(docptr, mychip->floor); DoC_SelectChip(docptr, mychip->chip); - } - else if (this->curchip != 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 */ @@ -764,13 +822,16 @@ int doc_erase (struct mtd_info *mtd, struct erase_info *instr) instr->state = MTD_ERASING; /* Read the status of the flash device through CDSN Slow IO register - see Software Requirement 11.4 item 5.*/ + 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, CDSNSlowIO); DoC_Delay(docptr, 2); if (ReadDOC(docptr, Mil_CDSN_IO) & 1) { - printk("Error Erasing\n"); - /* There was an error */ + printk("Error Erasing at 0x%lx\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; @@ -787,20 +848,17 @@ int doc_erase (struct mtd_info *mtd, struct erase_info *instr) * ****************************************************************************/ -static int __init init_doc2001(void) +#if LINUX_VERSION_CODE < 0x20212 && defined(MODULE) +#define cleanup_doc2001 cleanup_module +#define init_doc2001 init_module +#endif + +int __init init_doc2001(void) { inter_module_register(im_name, THIS_MODULE, &DoCMil_init); return 0; } -#if LINUX_VERSION_CODE < 0x20300 -#ifdef MODULE -#define cleanup_doc2001 cleanup_module -#endif -#define __exit -#endif - - static void __exit cleanup_doc2001(void) { struct mtd_info *mtd; @@ -817,11 +875,9 @@ static void __exit cleanup_doc2001(void) kfree(mtd); } inter_module_unregister(im_name); - } +module_exit(cleanup_doc2001); module_init(init_doc2001); -#if LINUX_VERSION_CODE > 0x20300 -module_exit(cleanup_doc2001); -#endif + diff --git a/drivers/mtd/docecc.c b/drivers/mtd/docecc.c new file mode 100644 index 000000000..cddc968c0 --- /dev/null +++ b/drivers/mtd/docecc.c @@ -0,0 +1,522 @@ +/* + * 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.1 2000/11/03 12:43:43 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/malloc.h> +#include <linux/sched.h> +#include <linux/init.h> +#include <linux/types.h> + +#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 min(a,b) ((a) < (b) ? (a) : (b)) + +#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; +} + diff --git a/drivers/mtd/docprobe.c b/drivers/mtd/docprobe.c index b38b4352d..c67a3489a 100644 --- a/drivers/mtd/docprobe.c +++ b/drivers/mtd/docprobe.c @@ -3,7 +3,7 @@ /* Probe routines common to all DoC devices */ /* (c) 1999 Machine Vision Holdings, Inc. */ /* Author: David Woodhouse <dwmw2@mvhi.com> */ -/* $Id: docprobe.c,v 1.10 2000/07/13 14:23:20 dwmw2 Exp $ */ +/* $Id: docprobe.c,v 1.21 2000/12/03 19:32:34 dwmw2 Exp $ */ @@ -26,6 +26,21 @@ #define DOC_PASSIVE_PROBE */ + +/* DOC_SINGLE_DRIVER: + Millennium driver has been merged into DOC2000 driver. + + The newly-merged driver doesn't appear to work for writing. It's the + same with the DiskOnChip 2000 and the Millennium. If you have a + Millennium and you want write support to work, remove the definition + of DOC_SINGLE_DRIVER below to use the old doc2001-specific driver. + + Otherwise, it's left on in the hope that it'll annoy someone with + a Millennium enough that they go through and work out what the + difference is :) +*/ +#define DOC_SINGLE_DRIVER + #include <linux/kernel.h> #include <linux/module.h> #include <asm/errno.h> @@ -44,21 +59,36 @@ #include <linux/mtd/doc2000.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_PARM(doc_config_location, "l"); + +static unsigned long __initdata doc_locations[] = { #if defined (__alpha__) || defined(__i386__) -static unsigned long __initdata doc_locations[] = { - 0xc8000, 0xca000, 0xcc000, 0xce000, - 0xd0000, 0xd2000, 0xd4000, 0xd6000, - 0xd8000, 0xda000, 0xdc000, 0xde000, - 0xe0000, 0xe2000, 0xe4000, 0xe6000, - 0xe8000, 0xea000, 0xec000, 0xee000, 0 }; +#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__) -static unsigned long __initdata doc_locations[] = { - 0xe4000000, 0}; + 0xe4000000, #else #warning Unknown architecture for DiskOnChip. No default probe locations defined #endif - + 0 }; /* doccheck: Probe a given memory window to see if there's a DiskOnChip present */ @@ -71,10 +101,13 @@ static inline int __init doccheck(unsigned long potential, unsigned long physadr #endif /* Routine copied from the Linux DOC driver */ - - /* Check for 0x55 0xAA signature at beginning of window */ + +#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 @@ -118,9 +151,10 @@ static inline int __init doccheck(unsigned long potential, unsigned long physadr break; default: +#ifndef CONFIG_MTD_DOCPROBE_55AA printk(KERN_WARNING "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. @@ -177,28 +211,35 @@ static void DoC_Probe(unsigned long physadr) this->physadr = physadr; this->ChipID = ChipID; sprintf(namebuf, "with ChipID %2.2X", ChipID); - + switch(ChipID) { 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; } + if (im_funcname) initroutine = inter_module_get_request(im_funcname, im_modname); + if (initroutine) { (*initroutine)(mtd); inter_module_put(im_funcname); return; } - printk("Cannot find driver for DiskOnChip %s at 0x%X\n", name, physadr); + printk("Cannot find driver for DiskOnChip %s at 0x%lX\n", name, physadr); } iounmap((void *)docptr); } @@ -210,12 +251,9 @@ static void DoC_Probe(unsigned long physadr) * ****************************************************************************/ -#if LINUX_VERSION_CODE < 0x20300 -#ifdef MODULE +#if LINUX_VERSION_CODE < 0x20212 && defined(MODULE) #define init_doc init_module #endif -#define __exit -#endif int __init init_doc(void) { @@ -223,19 +261,22 @@ int __init init_doc(void) printk(KERN_NOTICE "M-Systems DiskOnChip driver. (C) 1999 Machine Vision Holdings, Inc.\n"); #ifdef PRERELEASE - printk(KERN_INFO "$Id: docprobe.c,v 1.10 2000/07/13 14:23:20 dwmw2 Exp $\n"); + printk(KERN_INFO "$Id: docprobe.c,v 1.21 2000/12/03 19:32:34 dwmw2 Exp $\n"); #endif - - for (i=0; doc_locations[i]; i++) { - DoC_Probe(doc_locations[i]); + if (doc_config_location) { + printk("Using configured probe address 0x%lx\n", doc_config_location); + DoC_Probe(doc_config_location); + } else { + for (i=0; doc_locations[i]; i++) { + DoC_Probe(doc_locations[i]); + } } - + /* So it looks like we've been used and we get unloaded */ + MOD_INC_USE_COUNT; + MOD_DEC_USE_COUNT; return 0; } - -#if LINUX_VERSION_CODE > 0x20300 module_init(init_doc); -#endif diff --git a/drivers/mtd/ftl.c b/drivers/mtd/ftl.c index 1cf4a5f16..21ed0cecd 100644 --- a/drivers/mtd/ftl.c +++ b/drivers/mtd/ftl.c @@ -1,6 +1,10 @@ /* This version ported to the Linux-MTD system by dwmw2@infradead.org - * $Id: ftl.c,v 1.20 2000/06/23 15:17:53 dwmw2 Exp $ - * Based on: + * + * - Based on Id: ftl.c,v 1.21 2000/08/01 13:07:49 dwmw2 Exp + * - With the Franz Galiana's set_bam_entry fix from v1.23 + * - Perhaps it's about time I made a branch for the 2.4 series. + + * Originally based on: */ /*====================================================================== @@ -263,12 +267,13 @@ static struct block_device_operations ftl_blk_fops = { static int scan_header(partition_t *part) { erase_unit_header_t header; - loff_t offset; + loff_t offset, max_offset; int ret; part->header.FormattedSize = 0; + max_offset = (0x100000<part->mtd->size)?0x100000:part->mtd->size; /* Search first megabyte for a valid FTL header */ for (offset = 0; - offset < 0x100000; + offset < max_offset; offset += part->mtd->erasesize?part->mtd->erasesize:0x2000) { ret = part->mtd->read(part->mtd, offset, sizeof(header), &ret, @@ -280,7 +285,7 @@ static int scan_header(partition_t *part) if (strcmp(header.DataOrgTuple+3, "FTL100") == 0) break; } - if (offset == 0x100000) { + if (offset == max_offset) { printk(KERN_NOTICE "ftl_cs: FTL header not found.\n"); return -ENOENT; } @@ -998,7 +1003,7 @@ static int ftl_read(partition_t *part, caddr_t buffer, static int set_bam_entry(partition_t *part, u_int32_t log_addr, u_int32_t virt_addr) { - u_int32_t bsize, blk; + u_int32_t bsize, blk, le_virt_addr; #ifdef PSYCHO_DEBUG u_int32_t old_addr; #endif @@ -1035,6 +1040,7 @@ static int set_bam_entry(partition_t *part, u_int32_t log_addr, return -EIO; } #endif + le_virt_addr = cpu_to_le32(virt_addr); if (part->bam_index == eun) { #ifdef PSYCHO_DEBUG if (le32_to_cpu(part->bam_cache[blk]) != old_addr) { @@ -1049,10 +1055,10 @@ static int set_bam_entry(partition_t *part, u_int32_t log_addr, return -EIO; } #endif - part->bam_cache[blk] = cpu_to_le32(virt_addr); + part->bam_cache[blk] = le_virt_addr; } ret = part->mtd->write(part->mtd, offset, sizeof(u_int32_t), - &retlen, (u_char *)&part->bam_cache[blk]); + &retlen, (u_char *)&le_virt_addr); if (ret) { printk(KERN_NOTICE "ftl_cs: set_bam_entry() failed!\n"); @@ -1410,8 +1416,6 @@ mod_init_t init_ftl(void) memset(myparts, 0, sizeof(myparts)); - DEBUG(0, "$Id: ftl.c,v 1.20 2000/06/23 15:17:53 dwmw2 Exp $\n"); - if (register_blkdev(FTL_MAJOR, "ftl", &ftl_blk_fops)) { printk(KERN_NOTICE "ftl_cs: unable to grab major " "device number!\n"); diff --git a/drivers/mtd/map_ram.c b/drivers/mtd/map_ram.c index c62515406..00c009d7f 100644 --- a/drivers/mtd/map_ram.c +++ b/drivers/mtd/map_ram.c @@ -1,7 +1,7 @@ /* * Common code to handle map devices which are simple RAM * (C) 2000 Red Hat. GPL'd. - * $Id: map_ram.c,v 1.2 2000/07/03 10:01:38 dwmw2 Exp $ + * $Id: map_ram.c,v 1.7 2000/12/10 01:39:13 dwmw2 Exp $ */ #include <linux/module.h> @@ -29,6 +29,7 @@ static const char im_name[] = "map_ram_probe"; * 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 struct mtd_info *map_ram_probe(struct map_info *map) { struct mtd_info *mtd; @@ -59,6 +60,7 @@ static struct mtd_info *map_ram_probe(struct map_info *map) memset(mtd, 0, sizeof(*mtd)); + map->im_name = im_name; map->fldrv_destroy = mapram_nop; mtd->priv = map; mtd->name = map->name; @@ -69,9 +71,9 @@ static struct mtd_info *map_ram_probe(struct map_info *map) mtd->read = mapram_read; mtd->write = mapram_write; mtd->sync = mapram_nop; - mtd->im_name = im_name; mtd->flags = MTD_CAP_RAM | MTD_VOLATILE; - + mtd->erasesize = PAGE_SIZE; + return mtd; } @@ -115,6 +117,11 @@ static void mapram_nop(struct mtd_info *mtd) /* Nothing to see here */ } +#if LINUX_VERSION_CODE < 0x20212 && defined(MODULE) +#define map_ram_init init_module +#define map_ram_exit cleanup_module +#endif + static int __init map_ram_init(void) { inter_module_register(im_name, THIS_MODULE, &map_ram_probe); diff --git a/drivers/mtd/map_rom.c b/drivers/mtd/map_rom.c index d353938e9..c976c7ecf 100644 --- a/drivers/mtd/map_rom.c +++ b/drivers/mtd/map_rom.c @@ -1,7 +1,7 @@ /* * Common code to handle map devices which are simple ROM * (C) 2000 Red Hat. GPL'd. - * $Id: map_rom.c,v 1.2 2000/07/03 10:01:38 dwmw2 Exp $ + * $Id: map_rom.c,v 1.10 2000/12/10 01:39:13 dwmw2 Exp $ */ #include <linux/module.h> @@ -14,12 +14,19 @@ #include <linux/mtd/map.h> - static int maprom_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *); +static int maprom_write (struct mtd_info *, loff_t, size_t, size_t *, const u_char *); static void maprom_nop (struct mtd_info *); -struct mtd_info *map_rom_probe(struct map_info *); -EXPORT_SYMBOL(map_rom_probe); +static const char im_name[] = "map_rom_probe"; + +/* 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. + */ struct mtd_info *map_rom_probe(struct map_info *map) { @@ -31,16 +38,18 @@ struct mtd_info *map_rom_probe(struct map_info *map) memset(mtd, 0, sizeof(*mtd)); + map->im_name = im_name; map->fldrv_destroy = maprom_nop; mtd->priv = map; mtd->name = map->name; mtd->type = MTD_ROM; mtd->size = map->size; mtd->read = maprom_read; + mtd->write = maprom_write; mtd->sync = maprom_nop; mtd->flags = MTD_CAP_ROM; - - MOD_INC_USE_COUNT; + mtd->erasesize = 131072; + return mtd; } @@ -58,3 +67,28 @@ static void maprom_nop(struct mtd_info *mtd) { /* Nothing to see here */ } + +static int maprom_write (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) +{ + printk(KERN_NOTICE "maprom_write called\n"); + return -EIO; +} + +#if LINUX_VERSION_CODE < 0x20212 && defined(MODULE) +#define map_rom_init init_module +#define map_rom_exit cleanup_module +#endif + +static int __init map_rom_init(void) +{ + inter_module_register(im_name, THIS_MODULE, &map_rom_probe); + return 0; +} + +static void __exit map_rom_exit(void) +{ + inter_module_unregister(im_name); +} + +module_init(map_rom_init); +module_exit(map_rom_exit); diff --git a/drivers/mtd/mtdblock.c b/drivers/mtd/mtdblock.c index b728ddcd2..0a33ed1cf 100644 --- a/drivers/mtd/mtdblock.c +++ b/drivers/mtd/mtdblock.c @@ -1,16 +1,15 @@ /* * Direct MTD block device access * - * $Id: mtdblock.c,v 1.17 2000/07/13 14:25:54 dwmw2 Exp $ + * $Id: mtdblock.c,v 1.38 2000/11/27 08:50:22 dwmw2 Exp $ + * + * 02-nov-2000 Nicolas Pitre Added read-modify-write with cache */ -#ifdef MTDBLOCK_DEBUG -#define DEBUGLVL debug -#endif - #include <linux/types.h> #include <linux/module.h> - +#include <linux/kernel.h> +#include <linux/malloc.h> #include <linux/mtd/mtd.h> #define MAJOR_NR MTD_BLOCK_MAJOR @@ -21,62 +20,332 @@ #define DEVICE_OFF(device) #define DEVICE_NO_RANDOM #include <linux/blk.h> - +/* for old kernels... */ +#ifndef QUEUE_EMPTY +#define QUEUE_EMPTY (!CURRENT) +#endif #if LINUX_VERSION_CODE < 0x20300 -#define RQFUNC_ARG void -#define blkdev_dequeue_request(req) do {CURRENT = req->next;} while (0) +#define QUEUE_PLUGGED (blk_dev[MAJOR_NR].plug_tq.sync) #else -#define RQFUNC_ARG request_queue_t *q +#define QUEUE_PLUGGED (blk_dev[MAJOR_NR].request_queue.plugged) #endif -#ifdef MTDBLOCK_DEBUG -static int debug = MTDBLOCK_DEBUG; -MODULE_PARM(debug, "i"); +#ifdef CONFIG_DEVFS_FS +#include <linux/devfs_fs_kernel.h> +static void mtd_notify_add(struct mtd_info* mtd); +static void mtd_notify_remove(struct mtd_info* mtd); +static struct mtd_notifier notifier = { + mtd_notify_add, + mtd_notify_remove, + NULL +}; +static devfs_handle_t devfs_dir_handle = NULL; +static devfs_handle_t devfs_rw_handle[MAX_MTD_DEVICES]; #endif -#if 1 -static void mtdblock_end_request(struct request *req, int res) +static struct mtdblk_dev { + struct mtd_info *mtd; /* Locked */ + int count; + struct semaphore cache_sem; + unsigned char *cache_data; + unsigned long cache_offset; + unsigned int cache_size; + enum { STATE_EMPTY, STATE_CLEAN, STATE_DIRTY } cache_state; +} *mtdblks[MAX_MTD_DEVICES]; + +static spinlock_t mtdblks_lock; + +static int mtd_sizes[MAX_MTD_DEVICES]; +static int mtd_blksizes[MAX_MTD_DEVICES]; + + +/* + * Cache stuff... + * + * Since typical flash erasable sectors are much larger than what Linux's + * buffer cache can handle, we must implement read-modify-write on flash + * sectors for each block write requests. To avoid over-erasing flash sectors + * and to speed things up, we locally cache a whole flash sector while it is + * being written to until a different sector is required. + */ + +static void erase_callback(struct erase_info *done) { - if (end_that_request_first( req, res, "mtdblock" )) - return; - end_that_request_last( req ); + wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv; + wake_up(wait_q); } -#endif -static int mtd_sizes[MAX_MTD_DEVICES]; +static int erase_write (struct mtd_info *mtd, unsigned long pos, + int len, const char *buf) +{ + struct erase_info erase; + DECLARE_WAITQUEUE(wait, current); + wait_queue_head_t wait_q; + size_t retlen; + int ret; + + /* + * First, let's erase the flash block. + */ + + init_waitqueue_head(&wait_q); + erase.mtd = mtd; + erase.callback = erase_callback; + erase.addr = pos; + erase.len = len; + erase.priv = (u_long)&wait_q; + + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&wait_q, &wait); + + ret = MTD_ERASE(mtd, &erase); + if (ret) { + set_current_state(TASK_RUNNING); + remove_wait_queue(&wait_q, &wait); + printk (KERN_WARNING "mtdblock: erase of region [0x%lx, 0x%x] " + "on \"%s\" failed\n", + pos, len, mtd->name); + return ret; + } + schedule(); /* Wait for erase to finish. */ + remove_wait_queue(&wait_q, &wait); -/* Keeping a separate list rather than just getting stuff directly out of - the MTD core's mtd_table is perhaps not very nice, but I happen - to dislike the idea of directly accessing mtd_table even more. - dwmw2 31/3/0 -*/ + /* + * Next, writhe data to flash. + */ -static int mtdblock_open(struct inode *inode, struct file *file) + ret = MTD_WRITE (mtd, pos, len, &retlen, buf); + if (ret) + return ret; + if (retlen != len) + return -EIO; + return 0; +} + + +static int write_cached_data (struct mtdblk_dev *mtdblk) +{ + struct mtd_info *mtd = mtdblk->mtd; + int ret; + + if (mtdblk->cache_state != STATE_DIRTY) + return 0; + + DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: writing cached data for \"%s\" " + "at 0x%lx, size 0x%x\n", mtd->name, + mtdblk->cache_offset, mtdblk->cache_size); + + ret = erase_write (mtd, mtdblk->cache_offset, + mtdblk->cache_size, mtdblk->cache_data); + if (ret) + return ret; + + /* + * Here we could argably set the cache state to STATE_CLEAN. + * However this could lead to inconsistency since we will not + * be notified if this content is altered on the flash by other + * means. Let's declare it empty and leave buffering tasks to + * the buffer cache instead. + */ + mtdblk->cache_state = STATE_EMPTY; + return 0; +} + + +static int do_cached_write (struct mtdblk_dev *mtdblk, unsigned long pos, + int len, const char *buf) { - struct mtd_info *mtd = NULL; + struct mtd_info *mtd = mtdblk->mtd; + unsigned int sect_size = mtd->erasesize; + size_t retlen; + int ret; + + DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: write on \"%s\" at 0x%lx, size 0x%x\n", + mtd->name, pos, len); + + while (len > 0) { + unsigned long sect_start = (pos/sect_size)*sect_size; + unsigned int offset = pos - sect_start; + unsigned int size = sect_size - offset; + if( size > len ) + size = len; + + if (size == sect_size) { + /* + * We are covering a whole sector. Thus there is no + * need to bother with the cache while it may still be + * useful for other partial writes. + */ + ret = erase_write (mtd, pos, size, buf); + if (ret) + return ret; + } else { + /* Partial sector: need to use the cache */ + + if (mtdblk->cache_state == STATE_DIRTY && + mtdblk->cache_offset != sect_start) { + ret = write_cached_data(mtdblk); + if (ret) + return ret; + } + + if (mtdblk->cache_state == STATE_EMPTY || + mtdblk->cache_offset != sect_start) { + /* fill the cache with the current sector */ + mtdblk->cache_state = STATE_EMPTY; + ret = MTD_READ(mtd, sect_start, sect_size, &retlen, mtdblk->cache_data); + if (ret) + return ret; + if (retlen != sect_size) + return -EIO; + + mtdblk->cache_offset = sect_start; + mtdblk->cache_size = sect_size; + mtdblk->cache_state = STATE_CLEAN; + } + + /* write data to our local cache */ + memcpy (mtdblk->cache_data + offset, buf, size); + mtdblk->cache_state = STATE_DIRTY; + } + + buf += size; + pos += size; + len -= size; + } + + return 0; +} + + +static int do_cached_read (struct mtdblk_dev *mtdblk, unsigned long pos, + int len, char *buf) +{ + struct mtd_info *mtd = mtdblk->mtd; + unsigned int sect_size = mtd->erasesize; + size_t retlen; + int ret; + + DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: read on \"%s\" at 0x%lx, size 0x%x\n", + mtd->name, pos, len); + + while (len > 0) { + unsigned long sect_start = (pos/sect_size)*sect_size; + unsigned int offset = pos - sect_start; + unsigned int size = sect_size - offset; + if (size > len) + size = len; + + /* + * Check if the requested data is already cached + * Read the requested amount of data from our internal cache if it + * contains what we want, otherwise we read the data directly + * from flash. + */ + if (mtdblk->cache_state != STATE_EMPTY && + mtdblk->cache_offset == sect_start) { + memcpy (buf, mtdblk->cache_data + offset, size); + } else { + ret = MTD_READ (mtd, pos, size, &retlen, buf); + if (ret) + return ret; + if (retlen != size) + return -EIO; + } + + buf += size; + pos += size; + len -= size; + } + + return 0; +} + + +static int mtdblock_open(struct inode *inode, struct file *file) +{ + struct mtdblk_dev *mtdblk; int dev; - DEBUG(1,"mtdblock_open\n"); + DEBUG(MTD_DEBUG_LEVEL1,"mtdblock_open\n"); - if (inode == 0) + if (!inode) return -EINVAL; dev = MINOR(inode->i_rdev); + if (dev >= MAX_MTD_DEVICES) + return -EINVAL; MOD_INC_USE_COUNT; - mtd = get_mtd_device(NULL, dev); + spin_lock(&mtdblks_lock); + + /* If it's already open, no need to piss about. */ + if (mtdblks[dev]) { + mtdblks[dev]->count++; + spin_unlock(&mtdblks_lock); + return 0; + } + + /* OK, it's not open. Try to find it */ + + /* First we have to drop the lock, because we have to + to things which might sleep. + */ + spin_unlock(&mtdblks_lock); + + mtdblk = kmalloc(sizeof(struct mtdblk_dev), GFP_KERNEL); + if (!mtdblk) { + MOD_DEC_USE_COUNT; + return -ENOMEM; + } + memset(mtdblk, 0, sizeof(*mtdblk)); + mtdblk->count = 1; + mtdblk->mtd = get_mtd_device(NULL, dev); - if (!mtd) { + if (!mtdblk->mtd) { + kfree(mtdblk); MOD_DEC_USE_COUNT; return -ENODEV; } - mtd_sizes[dev] = mtd->size>>9; + init_MUTEX (&mtdblk->cache_sem); + mtdblk->cache_state = STATE_EMPTY; + mtdblk->cache_size = mtdblk->mtd->erasesize; + mtdblk->cache_data = vmalloc(mtdblk->mtd->erasesize); + if (!mtdblk->cache_data) { + put_mtd_device(mtdblk->mtd); + kfree(mtdblk); + MOD_DEC_USE_COUNT; + return -ENOMEM; + } + + /* OK, we've created a new one. Add it to the list. */ + + spin_lock(&mtdblks_lock); - DEBUG(1, "ok\n"); + if (mtdblks[dev]) { + /* Another CPU made one at the same time as us. */ + mtdblks[dev]->count++; + spin_unlock(&mtdblks_lock); + put_mtd_device(mtdblk->mtd); + vfree(mtdblk->cache_data); + kfree(mtdblk); + return 0; + } + + mtdblks[dev] = mtdblk; + mtd_sizes[dev] = mtdblk->mtd->size/1024; + mtd_blksizes[dev] = mtdblk->mtd->erasesize; + if (mtd_blksizes[dev] > PAGE_SIZE) + mtd_blksizes[dev] = PAGE_SIZE; + set_device_ro (inode->i_rdev, !(mtdblk->mtd->flags & MTD_WRITEABLE)); + + spin_unlock(&mtdblks_lock); + + DEBUG(MTD_DEBUG_LEVEL1, "ok\n"); return 0; } @@ -84,11 +353,11 @@ static int mtdblock_open(struct inode *inode, struct file *file) static release_t mtdblock_release(struct inode *inode, struct file *file) { int dev; - struct mtd_info *mtd; + struct mtdblk_dev *mtdblk; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) struct super_block * sb = get_super(inode->i_rdev); #endif - DEBUG(1, "mtdblock_release\n"); + DEBUG(MTD_DEBUG_LEVEL1, "mtdblock_release\n"); if (inode == NULL) release_return(-ENODEV); @@ -100,150 +369,189 @@ static release_t mtdblock_release(struct inode *inode, struct file *file) invalidate_buffers(inode->i_rdev); dev = MINOR(inode->i_rdev); - mtd = __get_mtd_device(NULL, dev); - - if (!mtd) { - printk(KERN_WARNING "MTD device is absent on mtd_release!\n"); - MOD_DEC_USE_COUNT; - release_return(-ENODEV); - + mtdblk = mtdblks[dev]; + + down(&mtdblk->cache_sem); + write_cached_data(mtdblk); + up(&mtdblk->cache_sem); + + spin_lock(&mtdblks_lock); + if (!--mtdblk->count) { + /* It was the last usage. Free the device */ + mtdblks[dev] = NULL; + spin_unlock(&mtdblks_lock); + if (mtdblk->mtd->sync) + mtdblk->mtd->sync(mtdblk->mtd); + put_mtd_device(mtdblk->mtd); + vfree(mtdblk->cache_data); + kfree(mtdblk); + } else { + spin_unlock(&mtdblks_lock); } - - if (mtd->sync) - mtd->sync(mtd); - - put_mtd_device(mtd); - DEBUG(1, "ok\n"); + DEBUG(MTD_DEBUG_LEVEL1, "ok\n"); MOD_DEC_USE_COUNT; release_return(0); } -static void mtdblock_request(RQFUNC_ARG) +/* + * This is a special request_fn because it is executed in a process context + * to be able to sleep independently of the caller. The io_request_lock + * is held upon entry and exit. + * The head of our request queue is considered active so there is no need + * to dequeue requests before we are done. + */ +static void handle_mtdblock_request(void) { - struct request *current_request; - unsigned int res = 0; - struct mtd_info *mtd; - - while (1) - { - /* Grab the Request and unlink it from the request list, INIT_REQUEST - will execute a return if we are done. */ - INIT_REQUEST; - current_request = CURRENT; - - if (MINOR(current_request->rq_dev) >= MAX_MTD_DEVICES) - { - printk("mtd: Unsupported device!\n"); - end_request(0); - continue; - } - - // Grab our MTD structure - - mtd = __get_mtd_device(NULL, MINOR(current_request->rq_dev)); - if (!mtd) { - printk("MTD device %d doesn't appear to exist any more\n", CURRENT_DEV); - end_request(0); - } - - if (current_request->sector << 9 > mtd->size || - (current_request->sector + current_request->nr_sectors) << 9 > mtd->size) - { - printk("mtd: Attempt to read past end of device!\n"); - printk("size: %lx, sector: %lx, nr_sectors %lx\n", mtd->size, current_request->sector, current_request->nr_sectors); - end_request(0); - continue; - } - - /* Remove the request we are handling from the request list so nobody messes - with it */ -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) - blkdev_dequeue_request(current_request); - - /* Now drop the lock that the ll_rw_blk functions grabbed for us - and process the request. This is necessary due to the extreme time - we spend processing it. */ - spin_unlock_irq(&io_request_lock); -#endif + struct request *req; + struct mtdblk_dev *mtdblk; + unsigned int res; + + for (;;) { + INIT_REQUEST; + req = CURRENT; + spin_unlock_irq(&io_request_lock); + mtdblk = mtdblks[MINOR(req->rq_dev)]; + res = 0; + + if (MINOR(req->rq_dev) >= MAX_MTD_DEVICES) + panic(__FUNCTION__": minor out of bound"); + + if ((req->sector + req->current_nr_sectors) > (mtdblk->mtd->size >> 9)) + goto end_req; + + // Handle the request + switch (req->cmd) + { + int err; + + case READ: + down(&mtdblk->cache_sem); + err = do_cached_read (mtdblk, req->sector << 9, + req->current_nr_sectors << 9, + req->buffer); + up(&mtdblk->cache_sem); + if (!err) + res = 1; + break; + + case WRITE: + // Read only device + if ( !(mtdblk->mtd->flags & MTD_WRITEABLE) ) + break; + + // Do the write + down(&mtdblk->cache_sem); + err = do_cached_write (mtdblk, req->sector << 9, + req->current_nr_sectors << 9, + req->buffer); + up(&mtdblk->cache_sem); + if (!err) + res = 1; + break; + } + +end_req: + spin_lock_irq(&io_request_lock); + end_request(res); + } +} - // Handle the request - switch (current_request->cmd) - { - size_t retlen; - - case READ: - if (mtd->read(mtd,current_request->sector<<9, - current_request->nr_sectors << 9, - &retlen, current_request->buffer) == 0) - res = 1; - else - res = 0; - break; - - case WRITE: -//printk("mtdblock_request WRITE sector=%d(%d)\n",current_request->sector, -// current_request->nr_sectors); - - // Read only device - if ((mtd->flags & MTD_CAP_RAM) == 0) - { - res = 0; - break; - } - - // Do the write - if (mtd->write(mtd,current_request->sector<<9, - current_request->nr_sectors << 9, - &retlen, current_request->buffer) == 0) - res = 1; - else - res = 0; - break; - - // Shouldn't happen - default: - printk("mtd: unknown request\n"); - break; - } - - // Grab the lock and re-thread the item onto the linked list -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) - spin_lock_irq(&io_request_lock); - mtdblock_end_request(current_request, res); +static volatile int leaving = 0; +#if LINUX_VERSION_CODE > 0x020300 +static DECLARE_MUTEX_LOCKED(thread_sem); +static DECLARE_WAIT_QUEUE_HEAD(thr_wq); #else - end_request(res); +static struct semaphore thread_sem = MUTEX_LOCKED; +DECLARE_WAIT_QUEUE_HEAD(thr_wq); #endif - } + +int mtdblock_thread(void *dummy) +{ + struct task_struct *tsk = current; + DECLARE_WAITQUEUE(wait, tsk); + + tsk->session = 1; + tsk->pgrp = 1; + /* we might get involved when memory gets low, so use PF_MEMALLOC */ + tsk->flags |= PF_MEMALLOC; + strcpy(tsk->comm, "mtdblockd"); + tsk->tty = NULL; + spin_lock_irq(&tsk->sigmask_lock); + sigfillset(&tsk->blocked); + recalc_sigpending(tsk); + spin_unlock_irq(&tsk->sigmask_lock); + exit_mm(tsk); + exit_files(tsk); + exit_sighand(tsk); + exit_fs(tsk); + + while (!leaving) { + add_wait_queue(&thr_wq, &wait); + set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irq(&io_request_lock); + if (QUEUE_EMPTY || QUEUE_PLUGGED) { + spin_unlock_irq(&io_request_lock); + schedule(); + remove_wait_queue(&thr_wq, &wait); + } else { + remove_wait_queue(&thr_wq, &wait); + set_current_state(TASK_RUNNING); + handle_mtdblock_request(); + spin_unlock_irq(&io_request_lock); + } + } + + up(&thread_sem); + return 0; } +#if LINUX_VERSION_CODE < 0x20300 +#define RQFUNC_ARG void +#else +#define RQFUNC_ARG request_queue_t *q +#endif + +static void mtdblock_request(RQFUNC_ARG) +{ + /* Don't do anything, except wake the thread if necessary */ + wake_up(&thr_wq); +} static int mtdblock_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg) { - struct mtd_info *mtd; + struct mtdblk_dev *mtdblk; - mtd = __get_mtd_device(NULL, MINOR(inode->i_rdev)); + mtdblk = mtdblks[MINOR(inode->i_rdev)]; - if (!mtd) return -EINVAL; +#ifdef PARANOIA + if (!mtdblk) + BUG(); +#endif switch (cmd) { case BLKGETSIZE: /* Return device size */ - if (!arg) return -EFAULT; - return put_user((mtd->size >> 9), - (long *) arg); + if (!arg) + return -EFAULT; + return put_user((mtdblk->mtd->size >> 9), + (long *) arg)?-EFAULT:0; case BLKFLSBUF: #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) - if(!capable(CAP_SYS_ADMIN)) return -EACCES; + if(!capable(CAP_SYS_ADMIN)) + return -EACCES; #endif fsync_dev(inode->i_rdev); invalidate_buffers(inode->i_rdev); - if (mtd->sync) - mtd->sync(mtd); + down(&mtdblk->cache_sem); + write_cached_data(mtdblk); + up(&mtdblk->cache_sem); + if (mtdblk->mtd->sync) + mtdblk->mtd->sync(mtdblk->mtd); return 0; default: @@ -251,7 +559,6 @@ static int mtdblock_ioctl(struct inode * inode, struct file * file, } } - /*}}}*/ #if LINUX_VERSION_CODE < 0x20326 static struct file_operations mtd_fops = { @@ -270,32 +577,69 @@ static struct block_device_operations mtd_fops = }; #endif -#if LINUX_VERSION_CODE < 0x20300 -#ifdef MODULE +#ifdef CONFIG_DEVFS_FS +/* Notification that a new device has been added. Create the devfs entry for + * it. */ + +static void mtd_notify_add(struct mtd_info* mtd) +{ + char name[8]; + + if (!mtd) + return; + + sprintf(name, "%d", mtd->index); + devfs_rw_handle[mtd->index] = devfs_register(devfs_dir_handle, name, + DEVFS_FL_DEFAULT, MTD_BLOCK_MAJOR, mtd->index, + S_IFBLK | S_IRUGO | S_IWUGO, + &mtd_fops, NULL); +} + +static void mtd_notify_remove(struct mtd_info* mtd) +{ + if (!mtd) + return; + + devfs_unregister(devfs_rw_handle[mtd->index]); +} +#endif + +#if LINUX_VERSION_CODE < 0x20212 && defined(MODULE) #define init_mtdblock init_module #define cleanup_mtdblock cleanup_module #endif -#define __exit -#endif - int __init init_mtdblock(void) { int i; + spin_lock_init(&mtdblks_lock); +#ifdef CONFIG_DEVFS_FS + if (devfs_register_blkdev(MTD_BLOCK_MAJOR, DEVICE_NAME, &mtd_fops)) + { + printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n", + MTD_BLOCK_MAJOR); + return -EAGAIN; + } + + devfs_dir_handle = devfs_mk_dir(NULL, DEVICE_NAME, NULL); + register_mtd_user(¬ifier); +#else if (register_blkdev(MAJOR_NR,DEVICE_NAME,&mtd_fops)) { printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n", MTD_BLOCK_MAJOR); return -EAGAIN; } +#endif /* We fill it in at open() time. */ for (i=0; i< MAX_MTD_DEVICES; i++) { mtd_sizes[i] = 0; + mtd_blksizes[i] = BLOCK_SIZE; } - + init_waitqueue_head(&thr_wq); /* Allow the block size to default to BLOCK_SIZE. */ - blksize_size[MAJOR_NR] = NULL; + blksize_size[MAJOR_NR] = mtd_blksizes; blk_size[MAJOR_NR] = mtd_sizes; #if LINUX_VERSION_CODE < 0x20320 @@ -303,15 +647,30 @@ int __init init_mtdblock(void) #else blk_init_queue(BLK_DEFAULT_QUEUE(MAJOR_NR), &mtdblock_request); #endif + kernel_thread (mtdblock_thread, NULL, CLONE_FS|CLONE_FILES|CLONE_SIGHAND); return 0; } static void __exit cleanup_mtdblock(void) { + leaving = 1; + wake_up(&thr_wq); + down(&thread_sem); +#ifdef CONFIG_DEVFS_FS + unregister_mtd_user(¬ifier); + devfs_unregister(devfs_dir_handle); + devfs_unregister_blkdev(MTD_BLOCK_MAJOR, DEVICE_NAME); +#else unregister_blkdev(MAJOR_NR,DEVICE_NAME); +#endif +#if LINUX_VERSION_CODE < 0x20320 + blk_dev[MAJOR_NR].request_fn = NULL; +#else + blk_cleanup_queue(BLK_DEFAULT_QUEUE(MAJOR_NR)); +#endif + blksize_size[MAJOR_NR] = NULL; + blk_size[MAJOR_NR] = NULL; } -#if LINUX_VERSION_CODE > 0x20300 module_init(init_mtdblock); module_exit(cleanup_mtdblock); -#endif diff --git a/drivers/mtd/mtdchar.c b/drivers/mtd/mtdchar.c index 0bb15a8fc..bccb687c0 100644 --- a/drivers/mtd/mtdchar.c +++ b/drivers/mtd/mtdchar.c @@ -1,5 +1,6 @@ /* - * $Id: mtdchar.c,v 1.7 2000/06/30 15:54:19 dwmw2 Exp $ + * Almost: $Id: mtdchar.c,v 1.21 2000/12/09 21:15:12 dwmw2 Exp $ + * (With some of the compatibility for previous kernels taken out) * * Character-device access to raw MTD devices. * @@ -13,6 +14,20 @@ #include <linux/mtd/mtd.h> #include <linux/malloc.h> +#ifdef CONFIG_DEVFS_FS +#include <linux/devfs_fs_kernel.h> +static void mtd_notify_add(struct mtd_info* mtd); +static void mtd_notify_remove(struct mtd_info* mtd); +static struct mtd_notifier notifier = { + mtd_notify_add, + mtd_notify_remove, + NULL +}; +static devfs_handle_t devfs_dir_handle = NULL; +static devfs_handle_t devfs_rw_handle[MAX_MTD_DEVICES]; +static devfs_handle_t devfs_ro_handle[MAX_MTD_DEVICES]; +#endif + #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) static loff_t mtd_lseek (struct file *file, loff_t offset, int orig) #else @@ -22,11 +37,11 @@ static int mtd_lseek (struct inode *inode, struct file *file, off_t offset, int struct mtd_info *mtd=(struct mtd_info *)file->private_data; switch (orig) { - case 0: + case 0: /* SEEK_SET */ file->f_pos = offset; break; - case 1: + case 1: /* SEEK_CUR */ file->f_pos += offset; break; @@ -34,11 +49,11 @@ static int mtd_lseek (struct inode *inode, struct file *file, off_t offset, int /* SEEK_END */ file->f_pos =mtd->size + offset; break; - default: + default: return -EINVAL; } - if (file->f_pos < 0) + if (file->f_pos < 0) file->f_pos = 0; else if (file->f_pos >= mtd->size) file->f_pos = mtd->size - 1; @@ -54,7 +69,7 @@ static int mtd_open(struct inode *inode, struct file *file) int devnum = minor >> 1; struct mtd_info *mtd; - DEBUG(0, "MTD_open\n"); + DEBUG(MTD_DEBUG_LEVEL0, "MTD_open\n"); if (devnum >= MAX_MTD_DEVICES) return -ENODEV; @@ -86,7 +101,7 @@ static release_t mtd_close(struct inode *inode, { struct mtd_info *mtd; - DEBUG(0, "MTD_close\n"); + DEBUG(MTD_DEBUG_LEVEL0, "MTD_close\n"); mtd = (struct mtd_info *)file->private_data; @@ -115,7 +130,7 @@ static int mtd_read(struct inode *inode,struct file *file, char *buf, int count) int ret=0; char *kbuf; - DEBUG(0,"MTD_read\n"); + DEBUG(MTD_DEBUG_LEVEL0,"MTD_read\n"); if (FILE_POS + count > mtd->size) count = mtd->size - FILE_POS; @@ -124,7 +139,7 @@ static int mtd_read(struct inode *inode,struct file *file, char *buf, int count) return 0; /* FIXME: Use kiovec in 2.3 or 2.2+rawio, or at - * least split the IO into smaller chunks. + * least split the IO into smaller chunks. */ kbuf = vmalloc(count); @@ -157,7 +172,7 @@ static read_write_t mtd_write(struct inode *inode,struct file *file, const char size_t retlen; int ret=0; - DEBUG(0,"MTD_write\n"); + DEBUG(MTD_DEBUG_LEVEL0,"MTD_write\n"); if (FILE_POS == mtd->size) return -ENOSPC; @@ -208,7 +223,7 @@ static int mtd_ioctl(struct inode *inode, struct file *file, int ret = 0; u_long size; - DEBUG(0, "MTD_ioctl\n"); + DEBUG(MTD_DEBUG_LEVEL0, "MTD_ioctl\n"); size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT; if (cmd & IOC_IN) { @@ -222,8 +237,9 @@ static int mtd_ioctl(struct inode *inode, struct file *file, switch (cmd) { case MEMGETINFO: - copy_to_user((struct mtd_info *)arg, mtd, - sizeof(struct mtd_info_user)); + if (copy_to_user((struct mtd_info *)arg, mtd, + sizeof(struct mtd_info_user))) + return -EFAULT; break; case MEMERASE: @@ -238,28 +254,23 @@ static int mtd_ioctl(struct inode *inode, struct file *file, init_waitqueue_head(&waitq); memset (erase,0,sizeof(struct erase_info)); - copy_from_user(&erase->addr, (u_long *)arg, - 2 * sizeof(u_long)); + if (copy_from_user(&erase->addr, (u_long *)arg, + 2 * sizeof(u_long))) { + kfree(erase); + return -EFAULT; + } erase->mtd = mtd; erase->callback = mtd_erase_callback; erase->priv = (unsigned long)&waitq; - /* FIXME: Allow INTERRUPTIBLE. Which means - not having the wait_queue head on the stack - - Does it? Why? Who wrote this? Was it my alter - ago - the intelligent one? Or was it the stupid - one, and now I'm being clever I don't know what - it was on about? - - dwmw2. - - It was the intelligent one. If the wq_head is - on the stack, and we leave because we got - interrupted, then the wq_head is no longer - there when the callback routine tries to - wake us up --> BOOM!. - + /* + FIXME: Allow INTERRUPTIBLE. Which means + not having the wait_queue head on the stack. + + If the wq_head is on the stack, and we + leave because we got interrupted, then the + wq_head is no longer there when the + callback routine tries to wake us up. */ current->state = TASK_UNINTERRUPTIBLE; add_wait_queue(&waitq, &wait); @@ -281,7 +292,8 @@ static int mtd_ioctl(struct inode *inode, struct file *file, void *databuf; ssize_t retlen; - copy_from_user(&buf, (struct mtd_oob_buf *)arg, sizeof(struct mtd_oob_buf)); + if (copy_from_user(&buf, (struct mtd_oob_buf *)arg, sizeof(struct mtd_oob_buf))) + return -EFAULT; if (buf.length > 0x4096) return -EINVAL; @@ -298,11 +310,13 @@ static int mtd_ioctl(struct inode *inode, struct file *file, if (!databuf) return -ENOMEM; - copy_from_user(databuf, buf.ptr, buf.length); + if (copy_from_user(databuf, buf.ptr, buf.length)) + return -EFAULT; ret = (mtd->write_oob)(mtd, buf.start, buf.length, &retlen, databuf); - copy_to_user((void *)arg + sizeof(loff_t), &retlen, sizeof(ssize_t)); + if (copy_to_user((void *)arg + sizeof(loff_t), &retlen, sizeof(ssize_t))) + ret = -EFAULT; kfree(databuf); break; @@ -315,7 +329,8 @@ static int mtd_ioctl(struct inode *inode, struct file *file, void *databuf; ssize_t retlen; - copy_from_user(&buf, (struct mtd_oob_buf *)arg, sizeof(struct mtd_oob_buf)); + if (copy_from_user(&buf, (struct mtd_oob_buf *)arg, sizeof(struct mtd_oob_buf))) + return -EFAULT; if (buf.length > 0x4096) return -EINVAL; @@ -334,19 +349,42 @@ static int mtd_ioctl(struct inode *inode, struct file *file, ret = (mtd->read_oob)(mtd, buf.start, buf.length, &retlen, databuf); - copy_to_user((void *)arg + sizeof(loff_t), &retlen, sizeof(ssize_t)); - - if (retlen) - copy_to_user(buf.ptr, databuf, retlen); - + if (copy_to_user((void *)arg + sizeof(loff_t), &retlen, sizeof(ssize_t))) + ret = -EFAULT; + else if (retlen && copy_to_user(buf.ptr, databuf, retlen)) + ret = -EFAULT; + kfree(databuf); break; } - - - - + case MEMLOCK: + { + unsigned long adrs[2]; + + if (copy_from_user(adrs ,(void *)arg, 2* sizeof(unsigned long))) + return -EFAULT; + + if (!mtd->lock) + ret = -EOPNOTSUPP; + else + ret = mtd->lock(mtd, adrs[0], adrs[1]); + } + + case MEMUNLOCK: + { + unsigned long adrs[2]; + + if (copy_from_user(adrs, (void *)arg, 2* sizeof(unsigned long))) + return -EFAULT; + + if (!mtd->unlock) + ret = -EOPNOTSUPP; + else + ret = mtd->unlock(mtd, adrs[0], adrs[1]); + } + + default: printk("Invalid ioctl %x (MEMGETINFO = %x)\n",cmd, MEMGETINFO); ret = -EINVAL; @@ -366,31 +404,84 @@ static struct file_operations mtd_fops = { }; -#if LINUX_VERSION_CODE < 0x20300 -#ifdef MODULE +#ifdef CONFIG_DEVFS_FS +/* Notification that a new device has been added. Create the devfs entry for + * it. */ + +static void mtd_notify_add(struct mtd_info* mtd) +{ + char name[8]; + + if (!mtd) + return; + + sprintf(name, "%d", mtd->index); + devfs_rw_handle[mtd->index] = devfs_register(devfs_dir_handle, name, + DEVFS_FL_DEFAULT, MTD_CHAR_MAJOR, mtd->index*2, + S_IFCHR | S_IRUGO | S_IWUGO, + &mtd_fops, NULL); + + sprintf(name, "%dro", mtd->index); + devfs_ro_handle[mtd->index] = devfs_register(devfs_dir_handle, name, + DEVFS_FL_DEFAULT, MTD_CHAR_MAJOR, mtd->index*2+1, + S_IFCHR | S_IRUGO | S_IWUGO, + &mtd_fops, NULL); +} + +static void mtd_notify_remove(struct mtd_info* mtd) +{ + if (!mtd) + return; + + devfs_unregister(devfs_rw_handle[mtd->index]); + devfs_unregister(devfs_ro_handle[mtd->index]); +} +#endif + +#if LINUX_VERSION_CODE < 0x20212 && defined(MODULE) #define init_mtdchar init_module #define cleanup_mtdchar cleanup_module #endif -#endif mod_init_t init_mtdchar(void) { - - if (register_chrdev(MTD_CHAR_MAJOR,"mtd",&mtd_fops)) { +#ifdef CONFIG_DEVFS_FS + int i; + char name[8]; + struct mtd_info* mtd; + + if (devfs_register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops)) + { + printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n", + MTD_CHAR_MAJOR); + return -EAGAIN; + } + + devfs_dir_handle = devfs_mk_dir(NULL, "mtd", NULL); + + register_mtd_user(¬ifier); +#else + if (register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops)) + { printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n", MTD_CHAR_MAJOR); return -EAGAIN; } +#endif return 0; } mod_exit_t cleanup_mtdchar(void) { - unregister_chrdev(MTD_CHAR_MAJOR,"mtd"); +#ifdef CONFIG_DEVFS_FS + unregister_mtd_user(¬ifier); + devfs_unregister(devfs_dir_handle); + devfs_unregister_chrdev(MTD_CHAR_MAJOR, "mtd"); +#else + unregister_chrdev(MTD_CHAR_MAJOR, "mtd"); +#endif } -#if LINUX_VERSION_CODE > 0x20300 module_init(init_mtdchar); module_exit(cleanup_mtdchar); -#endif diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index b3f0157eb..8c30838ba 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -1,15 +1,11 @@ /* - * $Id: mtdcore.c,v 1.13 2000/07/13 14:27:37 dwmw2 Exp $ + * $Id: mtdcore.c,v 1.27 2000/12/10 01:10:09 dwmw2 Exp $ * - * Core registration and callback routines for MTD + * Core registration and callback routines for MTD * drivers and users. * */ -#ifdef MTD_DEBUG -#define DEBUGLVL debug -#endif - #include <linux/config.h> #include <linux/module.h> #include <linux/kernel.h> @@ -29,67 +25,12 @@ #include <linux/mtd/mtd.h> -#ifdef MTD_DEBUG -static int debug = MTD_DEBUG; -MODULE_PARM(debug, "i"); -#endif - -/* Init code required for 2.2 kernels */ - -#if LINUX_VERSION_CODE < 0x20300 - -#ifdef CONFIG_MTD_DOC1000 -extern int init_doc1000(void); -#endif -#ifdef CONFIG_MTD_DOCPROBE -extern int init_doc(void); -#endif -#ifdef CONFIG_MTD_PHYSMAP -extern int init_physmap(void); -#endif -#ifdef CONFIG_MTD_RPXLITE -extern int init_rpxlite(void); -#endif -#ifdef CONFIG_MTD_OCTAGON -extern int init_octagon5066(void); -#endif -#ifdef CONFIG_MTD_PNC2000 -extern int init_pnc2000(void); -#endif -#ifdef CONFIG_MTD_VMAX -extern int init_vmax301(void); -#endif -#ifdef CONFIG_MTD_MIXMEM -extern int init_mixmem(void); -#endif -#ifdef CONFIG_MTD_PMC551 -extern int init_pmc551(void); -#endif -#ifdef CONFIG_MTD_NORA -extern int init_nora(void); -#endif -#ifdef CONFIG_FTL -extern int init_ftl(void); -#endif -#ifdef CONFIG_NFTL -extern int init_nftl(void); -#endif -#ifdef CONFIG_MTD_BLOCK -extern int init_mtdblock(void); -#endif -#ifdef CONFIG_MTD_CHAR -extern int init_mtdchar(void); -#endif - -#endif /* LINUX_VERSION_CODE < 0x20300 */ - - static DECLARE_MUTEX(mtd_table_mutex); static struct mtd_info *mtd_table[MAX_MTD_DEVICES]; static struct mtd_notifier *mtd_notifiers = NULL; /** - * add_mtd_device - register an MTD device + * add_mtd_device - register an MTD device * @mtd: pointer to new MTD device info structure * * Add a device to the list of MTD devices present in the system, and @@ -110,6 +51,7 @@ int add_mtd_device(struct mtd_info *mtd) struct mtd_notifier *not=mtd_notifiers; mtd_table[i] = mtd; + mtd->index = i; DEBUG(0, "mtd: Giving out device %d to %s\n",i, mtd->name); while (not) { @@ -126,7 +68,7 @@ int add_mtd_device(struct mtd_info *mtd) } /** - * del_mtd_device - unregister an MTD device + * del_mtd_device - unregister an MTD device * @mtd: pointer to MTD device info structure * * Remove a device from the list of MTD devices present in the system, @@ -194,7 +136,7 @@ void register_mtd_user (struct mtd_notifier *new) * @new: pointer to notifier info structure * * Removes a callback function pair from the list of 'users' to be - * notified upon addition or removal of MTD devices. Causes the + * notified upon addition or removal of MTD devices. Causes the * 'remove' callback to be immediately invoked for each MTD device * currently present in the system. */ @@ -232,7 +174,7 @@ int unregister_mtd_user (struct mtd_notifier *old) * @mtd: last known address of the required MTD device * @num: internal device number of the required MTD device * - * Given a number and NULL address, return the num'th entry in the device + * Given a number and NULL address, return the num'th entry in the device * table, if any. Given an address and num == -1, search the device table * for a device with that address and return if it's still present. Given * both, return the num'th driver only if its address matches. Return NULL @@ -313,10 +255,10 @@ static inline int mtd_proc_info (char *buf, int i) { struct mtd_info *this = mtd_table[i]; - if (!this) + if (!this) return 0; - return sprintf(buf, "mtd%d: %8.8lx \"%s\"\n", i, this->size, + return sprintf(buf, "mtd%d: %8.8lx \"%s\"\n", i, this->size, this->name); } @@ -330,7 +272,7 @@ static int mtd_read_proc ( char *page, char **start, off_t off,int count { int len = 0, l, i; off_t begin = 0; - + down(&mtd_table_mutex); for (i=0; i< MAX_MTD_DEVICES; i++) { @@ -374,79 +316,13 @@ struct proc_dir_entry mtd_proc_entry = { /*====================================================================*/ /* Init code */ -#if LINUX_VERSION_CODE < 0x20300 - -static inline void init_others(void) -{ - /* Shedloads of calls to init functions of all the - * other drivers and users of MTD, which we can - * ditch in 2.3 because of the sexy new way of - * finding init routines. - */ -#ifdef CONFIG_MTD_DOC1000 - init_doc1000(); -#endif -#ifdef CONFIG_MTD_DOCPROBE - init_doc(); /* This covers both the DiskOnChip 2000 - * and the DiskOnChip Millennium. - * Theoretically all other DiskOnChip - * devices too. */ -#endif -#ifdef CONFIG_MTD_PHYSMAP - init_physmap(); -#endif -#ifdef CONFIG_MTD_RPXLITE - init_rpxlite(); -#endif -#ifdef CONFIG_MTD_OCTAGON - init_octagon5066(); -#endif -#ifdef CONFIG_MTD_PNC2000 - init_pnc2000(); -#endif -#ifdef CONFIG_MTD_VMAX - init_vmax301(); -#endif -#ifdef CONFIGF_MTD_MIXMEM - init_mixmem(); -#endif -#ifdef CONFIG_MTD_PMC551 - init_pmc551(); -#endif -#ifdef CONFIG_MTD_NORA - init_nora(); -#endif -#ifdef CONFIG_MTD_MTDRAM - init_mtdram(); -#endif -#ifdef CONFIG_FTL - init_ftl(); -#endif -#ifdef CONFIG_NFTL - init_nftl(); -#endif -#ifdef CONFIG_MTD_BLOCK - init_mtdblock(); -#endif -#ifdef CONFIG_MTD_CHAR - init_mtdchar(); -#endif -} - -#ifdef MODULE +#if LINUX_VERSION_CODE < 0x20212 && defined(MODULE) #define init_mtd init_module #define cleanup_mtd cleanup_module #endif -#endif /* LINUX_VERSION_CODE < 0x20300 */ - mod_init_t init_mtd(void) { - int i; - DEBUG(1, "INIT_MTD:\n"); - for (i=0; i<MAX_MTD_DEVICES; i++) - mtd_table[i]=NULL; - #ifdef CONFIG_PROC_FS #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) if ((proc_mtd = create_proc_entry( "mtd", 0, 0 ))) @@ -454,12 +330,12 @@ mod_init_t init_mtd(void) #else proc_register_dynamic(&proc_root,&mtd_proc_entry); #endif - #endif -#if LINUX_VERSION_CODE < 0x20300 - init_others(); +#if LINUX_VERSION_CODE < 0x20212 + init_mtd_devices(); #endif + #ifdef CONFIG_PM mtd_pm_dev = pm_register(PM_UNKNOWN_DEV, 0, mtd_pm_callback); #endif @@ -468,13 +344,13 @@ mod_init_t init_mtd(void) mod_exit_t cleanup_mtd(void) { - unregister_chrdev(MTD_CHAR_MAJOR, "mtd"); #ifdef CONFIG_PM if (mtd_pm_dev) { pm_unregister(mtd_pm_dev); mtd_pm_dev = NULL; } #endif + #ifdef CONFIG_PROC_FS #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) if (proc_mtd) @@ -484,10 +360,8 @@ mod_exit_t cleanup_mtd(void) #endif #endif } - -#if LINUX_VERSION_CODE > 0x20300 + module_init(init_mtd); module_exit(cleanup_mtd); -#endif diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c new file mode 100644 index 000000000..0135be39e --- /dev/null +++ b/drivers/mtd/mtdpart.c @@ -0,0 +1,228 @@ +/* + * Simple MTD partitioning layer + * + * (C) 2000 Nicolas Pitre <nico@cam.org> + * + * This code is GPL + * + * $Id: mtdpart.c,v 1.7 2000/12/09 23:29:47 dwmw2 Exp $ + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/malloc.h> +#include <linux/list.h> + +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> + + +/* Our partition linked list */ +static LIST_HEAD(mtd_partitions); + +/* Our partition node structure */ +struct mtd_part { + struct mtd_info mtd; + struct mtd_info *master; + loff_t offset; + int index; + struct list_head list; +}; + +/* + * Given a pointer to the MTD object in the mtd_part structure, we can retrieve + * the pointer to that structure with this macro. + */ +#define PART(x) ((struct mtd_part *)(x)) + + +/* + * MTD methods which simply translate the effective address and pass through + * to the _real_ device. + */ + +static int part_read (struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct mtd_part *part = PART(mtd); + if (from >= mtd->size) + len = 0; + else if (from + len > mtd->size) + len = mtd->size - from; + return part->master->read (part->master, from + part->offset, + len, retlen, buf); +} + +static int part_write (struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct mtd_part *part = PART(mtd); + if (!(mtd->flags & MTD_WRITEABLE)) + return -EROFS; + if (to >= mtd->size) + len = 0; + else if (to + len > mtd->size) + len = mtd->size - to; + return part->master->write (part->master, to + part->offset, + len, retlen, buf); +} + +static int part_writev (struct mtd_info *mtd, const struct iovec *vecs, + unsigned long count, loff_t to, size_t *retlen) +{ + struct mtd_part *part = PART(mtd); + if (!(mtd->flags & MTD_WRITEABLE)) + return -EROFS; + return part->master->writev (part->master, vecs, count, + to + part->offset, retlen); +} + +static int part_readv (struct mtd_info *mtd, struct iovec *vecs, + unsigned long count, loff_t from, size_t *retlen) +{ + struct mtd_part *part = PART(mtd); + return part->master->readv (part->master, vecs, count, + from + part->offset, retlen); +} + +static int part_erase (struct mtd_info *mtd, struct erase_info *instr) +{ + struct mtd_part *part = PART(mtd); + if (!(mtd->flags & MTD_WRITEABLE)) + return -EROFS; + if (instr->addr >= mtd->size) + return -EINVAL; + instr->addr += part->offset; + return part->master->erase(part->master, instr); +} + +static int part_lock (struct mtd_info *mtd, loff_t ofs, size_t len) +{ + struct mtd_part *part = PART(mtd); + return part->master->lock(part->master, ofs + part->offset, len); +} + +static int part_unlock (struct mtd_info *mtd, loff_t ofs, size_t len) +{ + struct mtd_part *part = PART(mtd); + return part->master->unlock(part->master, ofs + part->offset, len); +} + + +/* + * This function unregisters and destroy all slave MTD objects which are + * attached to the given master MTD object. + */ + +int del_mtd_partitions(struct mtd_info *master) +{ + struct list_head *node; + struct mtd_part *slave; + + for (node = mtd_partitions.next; + node != &mtd_partitions; + node = node->next) { + slave = list_entry(node, struct mtd_part, list); + if (slave->master == master) { + struct list_head *prev = node->prev; + __list_del(prev, node->next); + del_mtd_device(&slave->mtd); + kfree(slave); + node = prev; + MOD_DEC_USE_COUNT; + } + } + + return 0; +} + + +/* + * This function, given a master MTD object and a partition table, creates + * and registers slave MTD objects which are bound to the master according to + * the partition definitions. + * (Q: should we register the master MTD object as well?) + */ + +int add_mtd_partitions(struct mtd_info *master, + struct mtd_partition *parts, + int nbparts) +{ + struct mtd_part *slave; + u_long cur_offset = 0; + int i; + + for (i = 0; i < nbparts; i++) { + /* allocate the partition structure */ + slave = kmalloc (sizeof(*slave), GFP_KERNEL); + if (!slave) { + printk ("memory allocation error while creating partitions for \"%s\"\n", + master->name); + del_mtd_partitions(master); + return -ENOMEM; + } + list_add(&slave->list, &mtd_partitions); + + /* set up the MTD object for this partition */ + slave->mtd = *master; + slave->mtd.name = parts[i].name; + slave->mtd.size = parts[i].size; + slave->mtd.flags &= ~parts[i].mask_flags; + slave->mtd.read = part_read; + slave->mtd.write = part_write; + if (slave->mtd.writev) + slave->mtd.writev = part_writev; + if (slave->mtd.readv) + slave->mtd.readv = part_readv; + if (slave->mtd.lock) + slave->mtd.lock = part_lock; + if (slave->mtd.unlock) + slave->mtd.unlock = part_unlock; + slave->mtd.erase = part_erase; + slave->master = master; + slave->offset = parts[i].offset; + slave->index = i; + + if (slave->offset == 0) + slave->offset = cur_offset; + if (slave->mtd.size == 0) + slave->mtd.size = master->size - slave->offset; + cur_offset = slave->offset + slave->mtd.size; + + /* let's do some sanity checks */ + if ((slave->mtd.flags & MTD_WRITEABLE) && + (parts[i].offset % master->erasesize)) { + slave->mtd.flags &= ~MTD_WRITEABLE; + printk ("mtd: partition \"%s\" doesn't start on an erase block boundary -- force read-only\n", + parts[i].name); + } + if ((slave->mtd.flags & MTD_WRITEABLE) && + (parts[i].size % master->erasesize)) { + slave->mtd.flags &= ~MTD_WRITEABLE; + printk ("mtd: partition \"%s\" doesn't end on an erase block -- force read-only\n", + parts[i].name); + } + if (parts[i].offset >= master->size) { + /* let's register it anyway to preserve ordering */ + slave->offset = 0; + slave->mtd.size = 0; + printk ("mtd: partition \"%s\" is out of reach -- disabled\n", + parts[i].name); + } + if (parts[i].offset + parts[i].size > master->size) { + slave->mtd.size = master->size - parts[i].offset; + printk ("mtd: partition \"%s\" extends beyond the end of device \"%s\" -- size truncated to %#lx\n", + parts[i].name, master->name, slave->mtd.size); + } + + /* register our partition */ + add_mtd_device(&slave->mtd); + MOD_INC_USE_COUNT; + } + + return 0; +} + +EXPORT_SYMBOL(add_mtd_partitions); +EXPORT_SYMBOL(del_mtd_partitions); diff --git a/drivers/mtd/nftl.c b/drivers/mtd/nftl.c index e7040863f..d7e2aeaad 100644 --- a/drivers/mtd/nftl.c +++ b/drivers/mtd/nftl.c @@ -1,32 +1,35 @@ - /* Linux driver for NAND Flash Translation Layer */ /* (c) 1999 Machine Vision Holdings, Inc. */ /* Author: David Woodhouse <dwmw2@infradead.org> */ -/* $Id: nftl.c,v 1.36 2000/07/13 14:14:20 dwmw2 Exp $ */ +/* $Id: nftl.c,v 1.57 2000/12/01 17:51:54 dwmw2 Exp $ */ /* - The contents of this file are distributed under the GNU Public - Licence version 2 ("GPL"). The legal note below refers only to the - _use_ of the code in some jurisdictions, and does not in any way - affect the copying, distribution and modification of this code, - which is permitted under the terms of the GPL. + The contents of this file are distributed under the GNU General + Public License version 2 ("GPL"). The author places no additional + restrictions of any kind on it. However, local legislation in some + countries may restrict the use of the algorithms implemented by this + code in certain circumstances. - Section 0 of the GPL says: + The legal note below refers only to the _use_ of the code in the + affected jurisdictions, and does not in any way affect the copying, + distribution and modification of this code, which are permitted, and + indeed required, under the terms of the GPL. + Section 0 of the GPL says: "Activities other than copying, distribution and modification are not covered by this License; they are outside its scope." You may copy, distribute and modify this code to your hearts' content - it's just that in some jurisdictions, you may only _use_ - it under the terms of the licence below. This puts it in a similar - situation to the ISDN code, which you may need telco approval to - use, and indeed any code which has uses that may be restricted in - law. For example, certain malicious uses of the networking stack - may be illegal, but that doesn't prevent the networking code from - being under GPL. + it under the terms of the patent grant below. This puts it in a + similar situation to the ISDN code, which you may need telco + approval to use, and indeed any code which has uses that may be + restricted in law. For example, certain malicious uses of the + networking stack may be illegal, but that doesn't prevent the + networking code from being under GPL. In fact the ISDN case is worse than this, because modification of - the code automatically invalidates its approval. Modificiation, + the code automatically invalidates its approval. Modification, unlike usage, _is_ one of the rights which is protected by the GPL. Happily, the law in those places where approval is required doesn't actually prevent you from modifying the code - it's just @@ -34,7 +37,7 @@ because usage isn't addressed by the GPL, that's just fine. dwmw2@infradead.org - 6/7/0 + 30/10/0 LEGAL NOTE: The NFTL format is patented by M-Systems. They have granted a licence for its use with their DiskOnChip products: @@ -51,10 +54,6 @@ #define PRERELEASE -#ifdef NFTL_DEBUG -#define DEBUGLVL debug -#endif - #include <linux/config.h> #include <linux/kernel.h> #include <linux/module.h> @@ -67,42 +66,28 @@ #include <linux/malloc.h> #include <linux/sched.h> #include <linux/init.h> +#include <linux/blkpg.h> +#ifdef CONFIG_KMOD +#include <linux/kmod.h> +#endif #include <linux/mtd/mtd.h> #include <linux/mtd/nftl.h> #include <linux/mtd/compatmac.h> -#undef WE_KNOW_WTF_THIS_DOES_NOT_WORK +/* maximum number of loops while examining next block, to have a + chance to detect consistency problems (they should never happen + because of the checks done in the mounting */ + +#define MAX_LOOPS 10000 /* NFTL block device stuff */ #define MAJOR_NR NFTL_MAJOR #define DEVICE_REQUEST nftl_request #define DEVICE_OFF(device) -#ifdef WE_KNOW_WTF_THIS_DOES_NOT_WORK -#define LOCAL_END_REQUEST -#endif -#include <linux/blk.h> -#include <linux/hdreg.h> - - -#ifdef WE_KNOW_WTF_THIS_DOES_NOT_WORK - -static void nftl_end_request(struct request *req, int res) -{ - req->sector += req->current_nr_sectors; - req->nr_sectors -= req->current_nr_sectors; - - if (end_that_request_first( req, res, "nftl" )) - return; - end_that_request_last( req ); -} -#endif - -#ifdef NFTL_DEBUG -static int debug = NFTL_DEBUG; -MODULE_PARM(debug, "i"); -#endif +#include <linux/blk.h> +#include <linux/hdreg.h> /* Linux-specific block device functions */ @@ -110,11 +95,10 @@ MODULE_PARM(debug, "i"); * encountered, except ... */ -static int nftl_sizes[256]={0,}; +static int nftl_sizes[256] = {0,}; static int nftl_blocksizes[256] = {0,}; /* .. for the Linux partition table handling. */ - struct hd_struct part_table[256] = {{0,0},}; #if LINUX_VERSION_CODE < 0x20328 @@ -123,8 +107,8 @@ static void dummy_init (struct gendisk *crap) #endif static struct gendisk nftl_gendisk = { - NFTL_MAJOR, /* Major number */ - "nftl", /* Major name */ + MAJOR_NR, /* Major number */ + "nftl", /* Major name */ 4, /* Bits to shift to get real from partition */ 15, /* Number of partitions per real */ #if LINUX_VERSION_CODE < 0x20328 @@ -138,281 +122,129 @@ static struct gendisk nftl_gendisk = { NULL /* next */ }; - struct NFTLrecord *NFTLs[MAX_NFTLS] = {NULL}; -static void NFTL_setup(struct mtd_info *mtd, unsigned long ofs, - struct NFTLMediaHeader *hdr) +static void NFTL_setup(struct mtd_info *mtd) { int i; - struct NFTLrecord *thisNFTL; + struct NFTLrecord *nftl; unsigned long temp; int firstfree = -1; - DEBUG(1,"NFTL_setup\n"); + DEBUG(MTD_DEBUG_LEVEL1,"NFTL_setup\n"); - for (i=0; i < MAX_NFTLS; i++) { - if (!NFTLs[i] && firstfree==-1) + for (i = 0; i < MAX_NFTLS; i++) { + if (!NFTLs[i] && firstfree == -1) firstfree = i; - else if (NFTLs[i] && NFTLs[i]->mtd == mtd && - NFTLs[i]->MediaHdr.FirstPhysicalEUN == hdr->FirstPhysicalEUN) { + else if (NFTLs[i] && NFTLs[i]->mtd == mtd) { /* This is a Spare Media Header for an NFTL we've already found */ - DEBUG(1, "Spare Media Header for NFTL %d found at %lx\n",i, ofs); - NFTLs[i]->SpareMediaUnit = ofs / mtd->erasesize; + DEBUG(MTD_DEBUG_LEVEL1, "MTD already mounted as NFTL\n"); return; } } - - - /* OK, it's a new one. Set up all the data structures. */ -#ifdef PSYCHO_DEBUG - printk("Found new NFTL nftl%c at offset %lx\n",firstfree + 'a', ofs); -#endif - if (hdr->UnitSizeFactor != 0xff) { - printk("Sorry, we don't support UnitSizeFactor of != 1 yet\n"); + if (firstfree == -1) { + printk(KERN_WARNING "No more NFTL slot available\n"); return; - } - - thisNFTL = kmalloc(sizeof(struct NFTLrecord), GFP_KERNEL); - if (!thisNFTL) { + } + + nftl = kmalloc(sizeof(struct NFTLrecord), GFP_KERNEL); + if (!nftl) { printk(KERN_WARNING "Out of memory for NFTL data structures\n"); return; } - init_MUTEX(&thisNFTL->mutex); - thisNFTL->EraseSize = mtd->erasesize; - memcpy(&thisNFTL->MediaHdr, hdr, sizeof(*hdr)); - thisNFTL->mtd = mtd; - thisNFTL->MediaUnit = ofs / mtd->erasesize; - thisNFTL->SpareMediaUnit = 0xffff; - thisNFTL->numvunits = le32_to_cpu(thisNFTL->MediaHdr.FormattedSize) / 8192; - thisNFTL->nr_sects = thisNFTL->numvunits * (thisNFTL->EraseSize / 512); - thisNFTL->usecount = 0; - thisNFTL->cylinders = 1024; - thisNFTL->heads = 16; + init_MUTEX(&nftl->mutex); - temp = thisNFTL->cylinders * thisNFTL->heads; - thisNFTL->sectors = thisNFTL->nr_sects / temp; + /* get physical parameters */ + nftl->EraseSize = mtd->erasesize; + nftl->nb_blocks = mtd->size / mtd->erasesize; + nftl->mtd = mtd; - if (thisNFTL->nr_sects % temp) { - - thisNFTL->sectors++; - temp = thisNFTL->cylinders * thisNFTL->sectors; - thisNFTL->heads = thisNFTL->nr_sects / temp; - - if (thisNFTL->nr_sects & temp) { - thisNFTL->heads++; - temp = thisNFTL->heads * thisNFTL->sectors; - - thisNFTL->cylinders = thisNFTL->nr_sects / temp; - } - } - if (thisNFTL->nr_sects != thisNFTL->heads * thisNFTL->cylinders * - thisNFTL->sectors) { - printk(KERN_WARNING "Cannot calculate an NFTL geometry to match size of 0x%lx.\n", thisNFTL->nr_sects); - printk(KERN_WARNING "Using C:%d H:%d S:%d (== %lx sects)\n", - thisNFTL->cylinders, thisNFTL->heads , - thisNFTL->sectors, - (long)thisNFTL->cylinders * (long)thisNFTL->heads * - (long)thisNFTL->sectors ); - - /* Oh no we don't - * thisNFTL->nr_sects = thisNFTL->heads * thisNFTL->cylinders * thisNFTL->sectors; - */ - } - - - thisNFTL->EUNtable = kmalloc( 2 * thisNFTL->numvunits, - GFP_KERNEL); - if (!thisNFTL->EUNtable) { - printk("ENOMEM\n"); - kfree(thisNFTL); + if (NFTL_mount(nftl) < 0) { + printk(KERN_WARNING "Could not mount NFTL device\n"); + kfree(nftl); return; - } - memset(thisNFTL->EUNtable, 0xff, 2 * thisNFTL->numvunits); - - thisNFTL->VirtualUnitTable = kmalloc( 2 * le16_to_cpu(thisNFTL->MediaHdr.NumEraseUnits) , GFP_KERNEL); - if (!thisNFTL->VirtualUnitTable) { - printk("ENOMEM\n"); - kfree(thisNFTL->EUNtable); - kfree(thisNFTL); - return; - } - memset(thisNFTL->VirtualUnitTable, 0xff, 2 * le16_to_cpu(thisNFTL->MediaHdr.NumEraseUnits)); - - thisNFTL->ReplUnitTable = kmalloc( 2 * le16_to_cpu(thisNFTL->MediaHdr.NumEraseUnits) , GFP_KERNEL); - if (!thisNFTL->ReplUnitTable) { - printk("ENOMEM\n"); - kfree(thisNFTL->VirtualUnitTable); - kfree(thisNFTL->EUNtable); - kfree(thisNFTL); - return; - } - memset(thisNFTL->ReplUnitTable, 0xff, 2 *le16_to_cpu(thisNFTL->MediaHdr.NumEraseUnits) ); - - /* Ought to check the media header for bad blocks */ - thisNFTL->lastEUN = le16_to_cpu(thisNFTL->MediaHdr.NumEraseUnits) + - le16_to_cpu(thisNFTL->MediaHdr.FirstPhysicalEUN) - 1; - thisNFTL->numfreeEUNs = 0; + } - /* Scan each physical Erase Unit for validity and to find the - Virtual Erase Unit Chain to which it belongs */ - - for (i=le16_to_cpu(thisNFTL->MediaHdr.FirstPhysicalEUN); - i <= thisNFTL->lastEUN; i++) { - - union nftl_uci uci; - unsigned long ofs; - size_t retlen; - ofs = i * thisNFTL->EraseSize; - - MTD_READOOB(mtd, (i * thisNFTL->EraseSize) + 512 + 8, 8, &retlen, (char *)&uci); - - if (uci.b.EraseMark != cpu_to_le16(0x3c69) || - uci.b.EraseMark1 != cpu_to_le16(0x3c69)) { - printk("EUN %d: EraseMark not 0x3c69 (0x%4.4x 0x%4.4x instead)\n", - i, le16_to_cpu(uci.b.EraseMark), le16_to_cpu(uci.b.EraseMark1)); - thisNFTL->VirtualUnitTable[i] = 0x7fff; - thisNFTL->ReplUnitTable[i] = 0xffff; - continue; - } - - MTD_READOOB(mtd, (i * thisNFTL->EraseSize) + 8, 8, &retlen, (u_char *)&uci); - - if (uci.a.VirtUnitNum != uci.a.SpareVirtUnitNum) - printk("EUN %d: VirtualUnitNumber (%x) != SpareVirtualUnitNumber (%x)\n", - i, le16_to_cpu(uci.a.VirtUnitNum), - le16_to_cpu(uci.a.SpareVirtUnitNum)); - - if (uci.a.ReplUnitNum != uci.a.SpareReplUnitNum) - printk("EUN %d: ReplacementUnitNumber (%x) != SpareReplacementUnitNumber (%x)\n", - i, le16_to_cpu(uci.a.ReplUnitNum), - le16_to_cpu(uci.a.SpareReplUnitNum)); - - /* We don't actually _do_ anything about the above, just whinge */ - - thisNFTL->VirtualUnitTable[i] = le16_to_cpu(uci.a.VirtUnitNum); - thisNFTL->ReplUnitTable[i] = le16_to_cpu(uci.a.ReplUnitNum); - - /* if (!(VUN & 0x8000) && VUN < (arraybounds)).. optimises to: */ - if (le16_to_cpu(uci.a.VirtUnitNum) < thisNFTL->numvunits) - thisNFTL->EUNtable[le16_to_cpu(uci.a.VirtUnitNum) & 0x7fff] = i; - - if (uci.a.VirtUnitNum == 0xffff) { - /* Free block */ - thisNFTL->LastFreeEUN = i; - thisNFTL->numfreeEUNs++; - } - - } - NFTLs[firstfree] = thisNFTL; - thisNFTL->LastFreeEUN = le16_to_cpu(thisNFTL->MediaHdr.FirstPhysicalEUN); - - //#define PSYCHO_DEBUG + /* OK, it's a new one. Set up all the data structures. */ #ifdef PSYCHO_DEBUG - for (i=0; i < 10/* thisNFTL->numvunits*/; i++) { - u16 curEUN = thisNFTL->EUNtable[i]; - int sillycount=100; - - printk("Virtual Unit #%d: ",i); - if (!curEUN || curEUN == 0xffff) { - printk("Not present\n"); - continue; - } - printk("%d", curEUN); - - while ((curEUN = thisNFTL->ReplUnitTable[curEUN]) != 0xffff && --sillycount) { - printk(", %d", curEUN & 0xffff); - + printk("Found new NFTL nftl%c\n", firstfree + 'a'); +#endif + + /* linux stuff */ + nftl->usecount = 0; + nftl->cylinders = 1024; + nftl->heads = 16; + + temp = nftl->cylinders * nftl->heads; + nftl->sectors = nftl->nr_sects / temp; + if (nftl->nr_sects % temp) { + nftl->sectors++; + temp = nftl->cylinders * nftl->sectors; + nftl->heads = nftl->nr_sects / temp; + + if (nftl->nr_sects % temp) { + nftl->heads++; + temp = nftl->heads * nftl->sectors; + nftl->cylinders = nftl->nr_sects / temp; } - printk("\n"); } -#endif - /* OK. Now we deal with the fact that we're in the real world. Sometimes - things don't actually happen the way they're supposed to. Find, fix, - and whinge about the most common deviations from spec that we have - been known to encounter. - */ - /* Except that I haven't implemented that bit yet :) */ + if (nftl->nr_sects != nftl->heads * nftl->cylinders * nftl->sectors) { + printk(KERN_WARNING "Cannot calculate an NFTL geometry to " + "match size of 0x%lx.\n", nftl->nr_sects); + printk(KERN_WARNING "Using C:%d H:%d S:%d (== 0x%lx sects)\n", + nftl->cylinders, nftl->heads , nftl->sectors, + (long)nftl->cylinders * (long)nftl->heads * (long)nftl->sectors ); + /* Oh no we don't have nftl->nr_sects = nftl->heads * nftl->cylinders * nftl->sectors; */ + } + NFTLs[firstfree] = nftl; /* Finally, set up the block device sizes */ - nftl_sizes[firstfree * 16]=thisNFTL->nr_sects; -// nftl_blocksizes[firstfree*16] = 512; - part_table[firstfree * 16].nr_sects = thisNFTL->nr_sects; + nftl_sizes[firstfree * 16] = nftl->nr_sects; + //nftl_blocksizes[firstfree*16] = 512; + part_table[firstfree * 16].nr_sects = nftl->nr_sects; + + /* partition check ... */ #if LINUX_VERSION_CODE < 0x20328 resetup_one_dev(&nftl_gendisk, firstfree); #else - grok_partitions(&nftl_gendisk, firstfree, 1<<4, thisNFTL->nr_sects); + grok_partitions(&nftl_gendisk, firstfree, 1<<4, nftl->nr_sects); #endif - } - static void NFTL_unsetup(int i) { - struct NFTLrecord *thisNFTL = NFTLs[i]; + struct NFTLrecord *nftl = NFTLs[i]; - DEBUG(1, "NFTL_unsetup %d\n", i); + DEBUG(MTD_DEBUG_LEVEL1, "NFTL_unsetup %d\n", i); NFTLs[i] = NULL; - if (thisNFTL->VirtualUnitTable) - kfree(thisNFTL->VirtualUnitTable); - if (thisNFTL->ReplUnitTable) - kfree(thisNFTL->ReplUnitTable); - if (thisNFTL->EUNtable) - kfree(thisNFTL->EUNtable); + if (nftl->ReplUnitTable) + kfree(nftl->ReplUnitTable); + if (nftl->EUNtable) + kfree(nftl->EUNtable); - kfree(thisNFTL); + kfree(nftl); } - - - /* Search the MTD device for NFTL partitions */ static void NFTL_notify_add(struct mtd_info *mtd) { - int i; - unsigned long ofs; - struct NFTLMediaHeader hdr; - - DEBUG(1, "NFTL_notify_add for %s\n", mtd->name); + DEBUG(MTD_DEBUG_LEVEL1, "NFTL_notify_add for %s\n", mtd->name); if (mtd) { - if (!mtd->read_oob) /* If this MTD doesn't have out-of-band data, - then there's no point continuing */ - { - DEBUG(1, "No OOB data, quitting\n"); + if (!mtd->read_oob) { + /* If this MTD doesn't have out-of-band data, + then there's no point continuing */ + DEBUG(MTD_DEBUG_LEVEL1, "No OOB data, quitting\n"); return; } - DEBUG(3, "mtd->read = %p,size = %d, erasesize = %d\n", - mtd->read, mtd->size, mtd->erasesize); - for (ofs = 0; ofs < mtd->size ; ofs += mtd->erasesize) { - size_t retlen = 0; - MTD_READ(mtd, ofs, sizeof(hdr), &retlen, (u_char *)&hdr); - - if (retlen < sizeof(hdr)) - { - continue; - } - - if (!strncmp(hdr.DataOrgID, "ANAND", 6)) { - DEBUG(2, "Valid NFTL partition at ofs %ld\n", ofs); - NFTL_setup(mtd, ofs, &hdr); - } - else { - DEBUG(3,"No valid NFTL Partition at ofs %d\n", ofs); - for(i = 0; i < 6; i++) { - DEBUG(3,"%x, ", hdr.DataOrgID[i]); - } - DEBUG(3," = %s\n", hdr.DataOrgID); - DEBUG(3,"%d, %d, %d, %d\n", hdr.NumEraseUnits, hdr.FirstPhysicalEUN, - hdr.FormattedSize, hdr.UnitSizeFactor); + DEBUG(MTD_DEBUG_LEVEL3, "mtd->read = %p, size = %d, erasesize = %d\n", + mtd->read, mtd->size, mtd->erasesize); - } - } - return; + NFTL_setup(mtd); } } @@ -420,100 +252,101 @@ static void NFTL_notify_remove(struct mtd_info *mtd) { int i; - for (i=0; i< MAX_NFTLS; i++) { + for (i = 0; i < MAX_NFTLS; i++) { if (NFTLs[i] && NFTLs[i]->mtd == mtd) NFTL_unsetup(i); } } - #ifdef CONFIG_NFTL_RW /* Actual NFTL access routines */ - - -static u16 NFTL_findfreeblock( struct NFTLrecord *thisNFTL, int desperate ) +/* NFTL_findfreeblock: Find a free Erase Unit on the NFTL partition. This function is used + * when the give Virtual Unit Chain + */ +static u16 NFTL_findfreeblock(struct NFTLrecord *nftl, int desperate ) { /* For a given Virtual Unit Chain: find or create a free block and add it to the chain */ /* We're passed the number of the last EUN in the chain, to save us from having to look it up again */ - - u16 pot = thisNFTL->LastFreeEUN; + u16 pot = nftl->LastFreeEUN; int silly = -1; /* Normally, we force a fold to happen before we run out of free blocks completely */ - - if (!desperate && thisNFTL->numfreeEUNs < 2) { - // printk("NFTL_findfreeblock: there are too few free EUNs\n"); + if (!desperate && nftl->numfreeEUNs < 2) { + DEBUG(MTD_DEBUG_LEVEL1, "NFTL_findfreeblock: there are too few free EUNs\n"); return 0xffff; } /* Scan for a free block */ - do { - if (thisNFTL->VirtualUnitTable[pot] == 0xffff) { - thisNFTL->LastFreeEUN = pot; - thisNFTL->numfreeEUNs--; + if (nftl->ReplUnitTable[pot] == BLOCK_FREE) { + nftl->LastFreeEUN = pot; + nftl->numfreeEUNs--; return pot; } - if (++pot > thisNFTL->lastEUN) - pot = le16_to_cpu(thisNFTL->MediaHdr.FirstPhysicalEUN); + /* This will probably point to the MediaHdr unit itself, + right at the beginning of the partition. But that unit + (and the backup unit too) should have the UCI set + up so that it's not selected for overwriting */ + if (++pot > nftl->lastEUN) + pot = le16_to_cpu(nftl->MediaHdr.FirstPhysicalEUN); if (!silly--) { - printk("Tell Dave he fucked up. LastFreeEUN = %d, FirstEUN = %d\n", - thisNFTL->LastFreeEUN, le16_to_cpu(thisNFTL->MediaHdr.FirstPhysicalEUN)); + printk("Argh! No free blocks found! LastFreeEUN = %d, " + "FirstEUN = %d\n", nftl->LastFreeEUN, + le16_to_cpu(nftl->MediaHdr.FirstPhysicalEUN)); return 0xffff; } - - } while (pot != thisNFTL->LastFreeEUN); + } while (pot != nftl->LastFreeEUN); return 0xffff; } - - - - -static u16 NFTL_foldchain (struct NFTLrecord *thisNFTL, u16 thisVUC, unsigned pendingblock ) +static u16 NFTL_foldchain (struct NFTLrecord *nftl, unsigned thisVUC, unsigned pendingblock ) { - u16 BlockMap[thisNFTL->EraseSize / 512]; - unsigned char BlockLastState[thisNFTL->EraseSize / 512]; - unsigned char BlockFreeFound[thisNFTL->EraseSize / 512]; - u16 thisEUN; + u16 BlockMap[MAX_SECTORS_PER_UNIT]; + unsigned char BlockLastState[MAX_SECTORS_PER_UNIT]; + unsigned char BlockFreeFound[MAX_SECTORS_PER_UNIT]; + unsigned int thisEUN; int block; - int silly = -1; - u16 targetEUN = 0xffff; + int silly; + unsigned int targetEUN; struct nftl_oob oob; int inplace = 1; + size_t retlen; memset(BlockMap, 0xff, sizeof(BlockMap)); memset(BlockFreeFound, 0, sizeof(BlockFreeFound)); - thisEUN = thisNFTL->EUNtable[thisVUC]; + thisEUN = nftl->EUNtable[thisVUC]; - if (thisEUN == 0xffff) { - printk(KERN_WARNING "Trying to fold non-existent Virtual Unit Chain %d!\n", thisVUC); - return 0xffff; + if (thisEUN == BLOCK_NIL) { + printk(KERN_WARNING "Trying to fold non-existent " + "Virtual Unit Chain %d!\n", thisVUC); + return BLOCK_NIL; } /* Scan to find the Erase Unit which holds the actual data for each 512-byte block within the Chain. */ + silly = MAX_LOOPS; + targetEUN = BLOCK_NIL; + while (thisEUN <= nftl->lastEUN ) { + unsigned int status, foldmark; - while( thisEUN <= thisNFTL->lastEUN ) { - size_t retlen; - targetEUN = thisEUN; - - for (block = 0 ; block < thisNFTL->EraseSize / 512; block ++) { - - MTD_READOOB(thisNFTL->mtd, (thisEUN * thisNFTL->EraseSize) + (block * 512),16 , &retlen, (char *)&oob); - + for (block = 0; block < nftl->EraseSize / 512; block ++) { + MTD_READOOB(nftl->mtd, + (thisEUN * nftl->EraseSize) + (block * 512), + 16 , &retlen, (char *)&oob); if (block == 2) { - if (oob.u.c.WriteInh != 0xffffffff) { - printk("Write Inhibited on EUN %d\n", thisEUN); + foldmark = oob.u.c.FoldMark | oob.u.c.FoldMark1; + if (foldmark == FOLD_MARK_IN_PROGRESS) { + DEBUG(MTD_DEBUG_LEVEL1, + "Write Inhibited on EUN %d\n", thisEUN); inplace = 0; } else { /* There's no other reason not to do inplace, @@ -522,153 +355,139 @@ static u16 NFTL_foldchain (struct NFTLrecord *thisNFTL, u16 thisVUC, unsigned pe inplace = 1; } } + status = oob.b.Status | oob.b.Status1; + BlockLastState[block] = status; - BlockLastState[block] = (unsigned char) oob.b.Status & 0xff; - - switch(oob.b.Status) { - case __constant_cpu_to_le16(BLOCK_FREE): - BlockFreeFound[block]=1; + switch(status) { + case SECTOR_FREE: + BlockFreeFound[block] = 1; break; - case __constant_cpu_to_le16(BLOCK_USED): + case SECTOR_USED: if (!BlockFreeFound[block]) BlockMap[block] = thisEUN; else - printk(KERN_WARNING "BLOCK_USED found after BLOCK_FREE in Virtual Unit Chain %d for block %d\n", thisVUC, block); + printk(KERN_WARNING + "SECTOR_USED found after SECTOR_FREE " + "in Virtual Unit Chain %d for block %d\n", + thisVUC, block); break; - case __constant_cpu_to_le16(BLOCK_IGNORE): - case __constant_cpu_to_le16(BLOCK_DELETED): + case SECTOR_IGNORE: + case SECTOR_DELETED: break; default: - printk("Unknown status for block %d in EUN %d: %x\n",block,thisEUN, oob.b.Status); + printk("Unknown status for block %d in EUN %d: %x\n", + block, thisEUN, status); } } if (!silly--) { - printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%x\n", thisVUC); - return 0xffff; + printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%x\n", + thisVUC); + return BLOCK_NIL; } - thisEUN = thisNFTL->ReplUnitTable[thisEUN] & 0x7fff; + thisEUN = nftl->ReplUnitTable[thisEUN]; } if (inplace) { /* We're being asked to be a fold-in-place. Check - that all blocks are either present or BLOCK_FREE + that all blocks are either present or SECTOR_FREE in the target block. If not, we're going to have to fold out-of-place anyway. */ - - for (block = 0; block < thisNFTL->EraseSize / 512 ; block++) { - - if (BlockLastState[block] != (unsigned char) (cpu_to_le16(BLOCK_FREE) & 0xff) && + for (block = 0; block < nftl->EraseSize / 512 ; block++) { + if (BlockLastState[block] != SECTOR_FREE && BlockMap[block] != targetEUN) { - DEBUG(1, "Setting inplace to 0. VUC %d, block %d was %x lastEUN, and is in EUN %d (%s) %d\n", - thisVUC, block, BlockLastState[block], BlockMap[block] , BlockMap[block]==targetEUN?"==":"!=", targetEUN); - + DEBUG(MTD_DEBUG_LEVEL1, "Setting inplace to 0. VUC %d, " + "block %d was %x lastEUN, " + "and is in EUN %d (%s) %d\n", + thisVUC, block, BlockLastState[block], + BlockMap[block], + BlockMap[block]== targetEUN ? "==" : "!=", + targetEUN); inplace = 0; break; } } - if ( pendingblock >= (thisVUC * (thisNFTL->EraseSize / 512)) && - pendingblock < ((thisVUC + 1)* (thisNFTL->EraseSize / 512)) && - BlockLastState[ pendingblock - (thisVUC * (thisNFTL->EraseSize / 512))] != - (unsigned char) (cpu_to_le16(BLOCK_FREE) & 0xff)) { - DEBUG(1, "Pending write not free in EUN %d. Folding out of place.\n", targetEUN); + if (pendingblock >= (thisVUC * (nftl->EraseSize / 512)) && + pendingblock < ((thisVUC + 1)* (nftl->EraseSize / 512)) && + BlockLastState[pendingblock - (thisVUC * (nftl->EraseSize / 512))] != + SECTOR_FREE) { + DEBUG(MTD_DEBUG_LEVEL1, "Pending write not free in EUN %d. " + "Folding out of place.\n", targetEUN); inplace = 0; } - } if (!inplace) { - DEBUG(1, "Cannot fold Virtual Unit Chain %d in place. Trying out-of-place\n", thisVUC); + DEBUG(MTD_DEBUG_LEVEL1, "Cannot fold Virtual Unit Chain %d in place. " + "Trying out-of-place\n", thisVUC); /* We need to find a targetEUN to fold into. */ - targetEUN = NFTL_findfreeblock(thisNFTL, 1); - if (targetEUN == 0xffff) { - /* Ouch. Now we're screwed. We need to do a - fold-in-place of another chain to make room - for this one. We need a better way of selecting - which chain to fold, because makefreeblock will - only ask us to fold the same one again. - */ - printk(KERN_WARNING"NFTL_findfreeblock(desperate) returns 0xffff.\n"); - return 0xffff; + targetEUN = NFTL_findfreeblock(nftl, 1); + if (targetEUN == BLOCK_NIL) { + /* Ouch. Now we're screwed. We need to do a + fold-in-place of another chain to make room + for this one. We need a better way of selecting + which chain to fold, because makefreeblock will + only ask us to fold the same one again. + */ + printk(KERN_WARNING + "NFTL_findfreeblock(desperate) returns 0xffff.\n"); + return BLOCK_NIL; } - - } - + } else { + /* We put a fold mark in the chain we are folding only if + we fold in place to help the mount check code. If we do + not fold in place, it is possible to find the valid + chain by selecting the longer one */ + oob.u.c.FoldMark = oob.u.c.FoldMark1 = cpu_to_le16(FOLD_MARK_IN_PROGRESS); + oob.u.c.unused = 0xffffffff; + MTD_WRITEOOB(nftl->mtd, (nftl->EraseSize * targetEUN) + 2 * 512 + 8, + 8, &retlen, (char *)&oob.u); + } /* OK. We now know the location of every block in the Virtual Unit Chain, and the Erase Unit into which we are supposed to be copying. Go for it. */ - - DEBUG(1,"Folding chain %d into unit %d\n", thisVUC, targetEUN); - - for (block = 0; block < thisNFTL->EraseSize / 512 ; block++) { + DEBUG(MTD_DEBUG_LEVEL1,"Folding chain %d into unit %d\n", thisVUC, targetEUN); + for (block = 0; block < nftl->EraseSize / 512 ; block++) { unsigned char movebuf[512]; - struct nftl_oob oob; - size_t retlen; - - memset(&oob, 0xff, sizeof(oob)); + int ret; /* If it's in the target EUN already, or if it's pending write, do nothing */ - if (BlockMap[block] == targetEUN ||(pendingblock == (thisVUC * (thisNFTL->EraseSize / 512) + block))) { - /* Except if it's the first block, in which case we have to - set the UnitNumbers */ - if (block == 0) { - - thisNFTL->mtd->read_oob(thisNFTL->mtd, (thisNFTL->EraseSize * targetEUN) , - 16, &retlen, (char *)&oob); - - // printk("Setting VirtUnitNum on EUN %d to %x, was %x\n", targetEUN, thisVUC, - // le16_to_cpu(oob.u.a.VirtUnitNum)); - - oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum = cpu_to_le16(thisVUC & 0x7fff); - - thisNFTL->mtd->write_oob(thisNFTL->mtd, (thisNFTL->EraseSize * targetEUN) , - 16, &retlen, (char *)&oob); - } + if (BlockMap[block] == targetEUN || + (pendingblock == (thisVUC * (nftl->EraseSize / 512) + block))) { continue; } - oob.b.Status = BLOCK_USED; - - switch(block) { - case 0: - oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum = cpu_to_le16(thisVUC & 0x7fff); - // printk("Setting VirtUnitNum on EUN %d to %x\n", targetEUN, thisVUC); - - oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum = 0xffff; - break; - - case 1: - oob.u.b.WearInfo = cpu_to_le32(3); // We don't use this, but M-Systems' drivers do - oob.u.b.EraseMark = oob.u.b.EraseMark1 = cpu_to_le16(0x3c69); - break; - - case 2: - default: - oob.u.c.WriteInh = 0xffffffff; - oob.u.c.unused = 0xffffffff; - } - if (thisNFTL->mtd->read_ecc(thisNFTL->mtd, (thisNFTL->EraseSize * BlockMap[block]) + (block * 512), - 512, &retlen, movebuf, (char *)&oob) == -EIO) { - if (thisNFTL->mtd->read_ecc(thisNFTL->mtd, (thisNFTL->EraseSize * BlockMap[block]) + (block * 512), - 512, &retlen, movebuf, (char *)&oob) != -EIO) - printk("Error went away on retry.\n"); - } - - thisNFTL->mtd->write_ecc(thisNFTL->mtd, (thisNFTL->EraseSize * targetEUN) + (block * 512), - 512, &retlen, movebuf, (char *)&oob); - - - /* FIXME: Add some error checking.... */ - thisNFTL->mtd->write_oob(thisNFTL->mtd, (thisNFTL->EraseSize * targetEUN) + (block * 512), - 16, &retlen, (char *)&oob); - + /* copy only in non free block (free blocks can only + happen in case of media errors or deleted blocks) */ + if (BlockMap[block] == BLOCK_NIL) + continue; + + ret = MTD_READECC(nftl->mtd, (nftl->EraseSize * BlockMap[block]) + + (block * 512), 512, &retlen, movebuf, (char *)&oob); + if (ret < 0) { + ret = MTD_READECC(nftl->mtd, (nftl->EraseSize * BlockMap[block]) + + (block * 512), 512, &retlen, + movebuf, (char *)&oob); + if (ret != -EIO) + printk("Error went away on retry.\n"); + } + MTD_WRITEECC(nftl->mtd, (nftl->EraseSize * targetEUN) + (block * 512), + 512, &retlen, movebuf, (char *)&oob); } + + /* add the header so that it is now a valid chain */ + oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum + = cpu_to_le16(thisVUC); + oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum = 0xffff; + + MTD_WRITEOOB(nftl->mtd, (nftl->EraseSize * targetEUN) + 8, + 8, &retlen, (char *)&oob.u); /* OK. We've moved the whole lot into the new block. Now we have to free the original blocks. */ @@ -677,78 +496,37 @@ static u16 NFTL_foldchain (struct NFTLrecord *thisNFTL, u16 thisVUC, unsigned pe shouldn't actually lose data in this case. It's just that when we load up on a medium which has duplicate chains, we need to free one of the chains because it's not necessary any more. */ - - - thisEUN = thisNFTL->EUNtable[thisVUC]; + thisEUN = nftl->EUNtable[thisVUC]; + DEBUG(MTD_DEBUG_LEVEL1,"Want to erase\n"); - DEBUG(1,"Want to erase\n"); /* For each block in the old chain (except the targetEUN of course), free it and make it available for future use */ - - while( thisEUN <= thisNFTL->lastEUN && thisEUN != targetEUN) { - size_t retlen; - struct erase_info *instr; - u16 EUNtmp; - - instr = kmalloc(sizeof(struct erase_info), GFP_KERNEL); - if (!instr) { - printk(KERN_WARNING "Out of memory for struct erase_info\n"); - - EUNtmp = thisEUN; - - thisEUN = thisNFTL->ReplUnitTable[EUNtmp] & 0x7fff; - thisNFTL->VirtualUnitTable[EUNtmp] = 0x7fff; - thisNFTL->ReplUnitTable[EUNtmp] = 0xffff; - } else { - memset(instr, 0, sizeof(struct erase_info)); - instr->addr = thisEUN * thisNFTL->EraseSize; - instr->len = thisNFTL->EraseSize; - - MTD_ERASE(thisNFTL->mtd, instr); - /* This is an async interface. Or will be. At which point - this code will break. */ - -#if 0 - MTD_READOOB(thisNFTL->mtd, (thisEUN * thisNFTL->EraseSize) + 512, 16, &retlen, (char *)&oob); - - printk("After erasing, EUN %d contains: %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n", - thisEUN, oob.b.ECCSig[0], - oob.b.ECCSig[1], - oob.b.ECCSig[2], - oob.b.ECCSig[3], - oob.b.ECCSig[4], - oob.b.ECCSig[5]); -#endif - memset(&oob, 0xff, sizeof(oob)); - oob.u.b.WearInfo = cpu_to_le32(3); - oob.u.b.EraseMark = oob.u.b.EraseMark1 = cpu_to_le16(0x3c69); - - MTD_WRITEOOB(thisNFTL->mtd, (thisEUN * thisNFTL->EraseSize) + 512, 16, &retlen, (char *)&oob); - - EUNtmp = thisEUN; - - thisEUN = thisNFTL->ReplUnitTable[EUNtmp] & 0x7fff; - thisNFTL->VirtualUnitTable[EUNtmp] = 0xffff; - thisNFTL->ReplUnitTable[EUNtmp] = 0xffff; - - thisNFTL->numfreeEUNs++; - - } - - // shifted upwards: thisEUN = thisNFTL->ReplUnitTable[thisEUN] & 0x7fff; - + while (thisEUN <= nftl->lastEUN && thisEUN != targetEUN) { + unsigned int EUNtmp; + + EUNtmp = nftl->ReplUnitTable[thisEUN]; + + if (NFTL_formatblock(nftl, thisEUN) < 0) { + /* could not erase : mark block as reserved + * FixMe: Update Bad Unit Table on disk + */ + nftl->ReplUnitTable[thisEUN] = BLOCK_RESERVED; + } else { + /* correctly erased : mark it as free */ + nftl->ReplUnitTable[thisEUN] = BLOCK_FREE; + nftl->numfreeEUNs++; + } + thisEUN = EUNtmp; } /* Make this the new start of chain for thisVUC */ - thisNFTL->VirtualUnitTable[targetEUN] = thisVUC; - thisNFTL->ReplUnitTable[targetEUN] = 0xffff; + nftl->ReplUnitTable[targetEUN] = BLOCK_NIL; + nftl->EUNtable[thisVUC] = targetEUN; - thisNFTL->EUNtable[thisVUC] = targetEUN; return targetEUN; - } -u16 NFTL_makefreeblock( struct NFTLrecord *thisNFTL , unsigned pendingblock) +u16 NFTL_makefreeblock( struct NFTLrecord *nftl , unsigned pendingblock) { /* This is the part that needs some cleverness applied. For now, I'm doing the minimum applicable to actually @@ -757,23 +535,21 @@ u16 NFTL_makefreeblock( struct NFTLrecord *thisNFTL , unsigned pendingblock) and we also need to do some assessment of the results when the system loses power half-way through the routine. */ - u16 LongestChain = 0; u16 ChainLength = 0, thislen; u16 chain, EUN; - - for (chain=0; chain < thisNFTL->MediaHdr.FormattedSize / thisNFTL->EraseSize; chain++) { - EUN = thisNFTL->EUNtable[chain]; - + for (chain = 0; chain < nftl->MediaHdr.FormattedSize / nftl->EraseSize; chain++) { + EUN = nftl->EUNtable[chain]; thislen = 0; - while (EUN <= thisNFTL->lastEUN) { + while (EUN <= nftl->lastEUN) { thislen++; - // printk("VUC %d reaches len %d with EUN %d\n", chain, thislen, EUN); - EUN = thisNFTL->ReplUnitTable[EUN] & 0x7fff; + //printk("VUC %d reaches len %d with EUN %d\n", chain, thislen, EUN); + EUN = nftl->ReplUnitTable[EUN] & 0x7fff; if (thislen > 0xff00) { - printk("Endless loop in Virtual Chain %d: Unit %x\n", chain, EUN); + printk("Endless loop in Virtual Chain %d: Unit %x\n", + chain, EUN); } if (thislen > 0xff10) { /* Actually, don't return failure. Just ignore this chain and @@ -781,42 +557,38 @@ u16 NFTL_makefreeblock( struct NFTLrecord *thisNFTL , unsigned pendingblock) thislen = 0; break; } - } - if (thislen > ChainLength) { - // printk("New longest chain is %d with length %d\n", chain, thislen); + //printk("New longest chain is %d with length %d\n", chain, thislen); ChainLength = thislen; LongestChain = chain; } - } + } if (ChainLength < 2) { - printk(KERN_WARNING "No Virtual Unit Chains available for folding. Failing request\n"); + printk(KERN_WARNING "No Virtual Unit Chains available for folding. " + "Failing request\n"); return 0xffff; } - - return NFTL_foldchain (thisNFTL, LongestChain, pendingblock); + + return NFTL_foldchain (nftl, LongestChain, pendingblock); } /* NFTL_findwriteunit: Return the unit number into which we can write for this block. Make it available if it isn't already */ - -static inline u16 NFTL_findwriteunit(struct NFTLrecord *thisNFTL, unsigned block) +static inline u16 NFTL_findwriteunit(struct NFTLrecord *nftl, unsigned block) { u16 lastEUN; - u16 thisVUC = block / (thisNFTL->EraseSize / 512); - u16 writeEUN; - unsigned long blockofs = (block * 512) & (thisNFTL->EraseSize -1); + u16 thisVUC = block / (nftl->EraseSize / 512); + unsigned int writeEUN; + unsigned long blockofs = (block * 512) & (nftl->EraseSize -1); size_t retlen; - int silly = 0x10000, silly2 = 3; + int silly, silly2 = 3; struct nftl_oob oob; - int debug=0; do { - /* Scan the media to find a unit in the VUC which has a free space for the block in question. */ @@ -824,28 +596,30 @@ static inline u16 NFTL_findwriteunit(struct NFTLrecord *thisNFTL, unsigned block /* This condition catches the 0x[7f]fff cases, as well as being a sanity check for past-end-of-media access */ - lastEUN = 0xffff; - writeEUN = thisNFTL->EUNtable[thisVUC]; - - while(writeEUN <= thisNFTL->lastEUN) { + lastEUN = BLOCK_NIL; + writeEUN = nftl->EUNtable[thisVUC]; + silly = MAX_LOOPS; + while (writeEUN <= nftl->lastEUN) { struct nftl_bci bci; size_t retlen; - + unsigned int status; + lastEUN = writeEUN; + + MTD_READOOB(nftl->mtd, (writeEUN * nftl->EraseSize) + blockofs, + 8, &retlen, (char *)&bci); - MTD_READOOB(thisNFTL->mtd, (writeEUN * thisNFTL->EraseSize) - + blockofs,8, &retlen, (char *)&bci); - - if (debug) - printk("Status of block %d in EUN %d is %x\n", block , writeEUN, le16_to_cpu(bci.Status)); + DEBUG(MTD_DEBUG_LEVEL2, "Status of block %d in EUN %d is %x\n", + block , writeEUN, le16_to_cpu(bci.Status)); - switch(bci.Status) { - case __constant_cpu_to_le16(BLOCK_FREE): + status = bci.Status | bci.Status1; + switch(status) { + case SECTOR_FREE: return writeEUN; - case __constant_cpu_to_le16(BLOCK_DELETED): - case __constant_cpu_to_le16(BLOCK_USED): - case __constant_cpu_to_le16(BLOCK_IGNORE): + case SECTOR_DELETED: + case SECTOR_USED: + case SECTOR_IGNORE: break; default: // Invalid block. Don't use it any more. Must implement. @@ -853,35 +627,35 @@ static inline u16 NFTL_findwriteunit(struct NFTLrecord *thisNFTL, unsigned block } if (!silly--) { - printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%x\n", thisVUC); + printk(KERN_WARNING + "Infinite loop in Virtual Unit Chain 0x%x\n", + thisVUC); return 0xffff; } /* Skip to next block in chain */ - - writeEUN = thisNFTL->ReplUnitTable[writeEUN] & 0x7fff; + writeEUN = nftl->ReplUnitTable[writeEUN]; } /* OK. We didn't find one in the existing chain, or there is no existing chain. */ /* Try to find an already-free block */ + writeEUN = NFTL_findfreeblock(nftl, 0); - writeEUN = NFTL_findfreeblock(thisNFTL, 0); - - if (writeEUN == 0xffff) { + if (writeEUN == BLOCK_NIL) { /* That didn't work - there were no free blocks just waiting to be picked up. We're going to have to fold a chain to make room. */ /* First remember the start of this chain */ - // u16 startEUN = thisNFTL->EUNtable[thisVUC]; + //u16 startEUN = nftl->EUNtable[thisVUC]; //printk("Write to VirtualUnitChain %d, calling makefreeblock()\n", thisVUC); - writeEUN = NFTL_makefreeblock(thisNFTL, block); + writeEUN = NFTL_makefreeblock(nftl, 0xffff); - if (writeEUN == 0xffff) { + if (writeEUN == BLOCK_NIL) { /* Ouch. This should never happen - we should always be able to make some room somehow. If we get here, we've allocated more storage @@ -889,235 +663,223 @@ static inline u16 NFTL_findwriteunit(struct NFTLrecord *thisNFTL, unsigned block routine is missing something. */ printk(KERN_WARNING "Cannot make free space.\n"); - return 0xffff; + return BLOCK_NIL; } - // printk("Restarting scan\n"); - lastEUN = 0xffff; - // debug = 1; + //printk("Restarting scan\n"); + lastEUN = BLOCK_NIL; continue; -#if 0 - if (startEUN != thisNFTL->EUNtable[thisVUC]) { - /* The fold operation has moved the chain - that we're looking at. Start the scan again. - */ - continue; - } -#endif } /* We've found a free block. Insert it into the chain. */ - if (lastEUN != 0xffff) { - /* Addition to an existing chain. Make the previous - last block in the chain point to this one. - */ - - //printk("Linking EUN %d to EUN %d in VUC %d\n", - // lastEUN, writeEUN, thisVUC); - /* Both in our cache... */ - thisNFTL->ReplUnitTable[lastEUN] = writeEUN; - - - /* ... and on the flash itself */ - MTD_READOOB(thisNFTL->mtd, (lastEUN * thisNFTL->EraseSize), 16, &retlen, - (char *)&oob); + if (lastEUN != BLOCK_NIL) { + thisVUC |= 0x8000; /* It's a replacement block */ + } else { + /* The first block in a new chain */ + nftl->EUNtable[thisVUC] = writeEUN; + } - oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum = cpu_to_le16(writeEUN); + /* set up the actual EUN we're writing into */ + /* Both in our cache... */ + nftl->ReplUnitTable[writeEUN] = BLOCK_NIL; - MTD_WRITEOOB(thisNFTL->mtd, (lastEUN * thisNFTL->EraseSize), 16, &retlen, - (char *)&oob); + /* ... and on the flash itself */ + MTD_READOOB(nftl->mtd, writeEUN * nftl->EraseSize + 8, 8, + &retlen, (char *)&oob.u); - thisVUC |= 0x8000; /* It's a replacement block */ - } else { - /* The first block in a new chain */ - thisNFTL->EUNtable[thisVUC] = writeEUN; - } + oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum = cpu_to_le16(thisVUC); - /* Now set up the actual EUN we're writing into */ + MTD_WRITEOOB(nftl->mtd, writeEUN * nftl->EraseSize + 8, 8, + &retlen, (char *)&oob.u); + /* we link the new block to the chain only after the + block is ready. It avoids the case where the chain + could point to a free block */ + if (lastEUN != BLOCK_NIL) { /* Both in our cache... */ - thisNFTL->VirtualUnitTable[writeEUN] = thisVUC; - thisNFTL->ReplUnitTable[writeEUN] = 0xffff; - + nftl->ReplUnitTable[lastEUN] = writeEUN; /* ... and on the flash itself */ - MTD_READOOB(thisNFTL->mtd, writeEUN * thisNFTL->EraseSize, 16, - &retlen, (char *)&oob); + MTD_READOOB(nftl->mtd, (lastEUN * nftl->EraseSize) + 8, + 8, &retlen, (char *)&oob.u); - oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum = cpu_to_le16(thisVUC); + oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum + = cpu_to_le16(writeEUN); - MTD_WRITEOOB(thisNFTL->mtd, writeEUN * thisNFTL->EraseSize, 16, - &retlen, (char *)&oob); + MTD_WRITEOOB(nftl->mtd, (lastEUN * nftl->EraseSize) + 8, + 8, &retlen, (char *)&oob.u); + } return writeEUN; } while (silly2--); - printk(KERN_WARNING "Error folding to make room for Virtual Unit Chain 0x%x\n", thisVUC); + printk(KERN_WARNING "Error folding to make room for Virtual Unit Chain 0x%x\n", + thisVUC); return 0xffff; } -static int NFTL_writeblock(struct NFTLrecord *thisNFTL, unsigned block, - char *buffer) +static int NFTL_writeblock(struct NFTLrecord *nftl, unsigned block, char *buffer) { u16 writeEUN; - unsigned long blockofs = (block * 512) & (thisNFTL->EraseSize -1); + unsigned long blockofs = (block * 512) & (nftl->EraseSize - 1); size_t retlen; - u16 eccbuf[8]; - - // if (thisEUN == 0xffff) thisEUN = 0; - - writeEUN = NFTL_findwriteunit(thisNFTL, block); + u8 eccbuf[6]; -// printk("writeblock(%d): Write to Unit %d\n", block, writeEUN); + writeEUN = NFTL_findwriteunit(nftl, block); - if (writeEUN == 0xffff) { - printk(KERN_WARNING "NFTL_writeblock(): Cannot find block to write to\n"); + if (writeEUN == BLOCK_NIL) { + printk(KERN_WARNING + "NFTL_writeblock(): Cannot find block to write to\n"); /* If we _still_ haven't got a block to use, we're screwed */ return 1; } -// printk("Writing block %lx to EUN %x\n",block, writeEUN); - - thisNFTL->mtd->write_ecc(thisNFTL->mtd, - (writeEUN * thisNFTL->EraseSize) + blockofs, - 512, &retlen, (char *)buffer, (char *)eccbuf); - eccbuf[3] = BLOCK_USED; - eccbuf[4] = eccbuf[5] = eccbuf[6] = eccbuf[7] = 0xffff; - - thisNFTL->mtd->write_oob(thisNFTL->mtd, - (writeEUN * thisNFTL->EraseSize) + blockofs, - 16, &retlen, (char *)eccbuf); + MTD_WRITEECC(nftl->mtd, (writeEUN * nftl->EraseSize) + blockofs, + 512, &retlen, (char *)buffer, (char *)eccbuf); + /* no need to write SECTOR_USED flags since they are written in mtd_writeecc */ return 0; } - #endif /* CONFIG_NFTL_RW */ -static int NFTL_readblock(struct NFTLrecord *thisNFTL, - unsigned block, char *buffer) +static int NFTL_readblock(struct NFTLrecord *nftl, unsigned block, char *buffer) { - u16 lastgoodEUN = 0xffff; - u16 thisEUN = thisNFTL->EUNtable[block / (thisNFTL->EraseSize / 512)]; - unsigned long blockofs = (block * 512) & (thisNFTL->EraseSize -1); - - int silly = -1; + u16 lastgoodEUN; + u16 thisEUN = nftl->EUNtable[block / (nftl->EraseSize / 512)]; + unsigned long blockofs = (block * 512) & (nftl->EraseSize - 1); + unsigned int status; + int silly = MAX_LOOPS; + size_t retlen; + struct nftl_bci bci; + + lastgoodEUN = BLOCK_NIL; + + if (thisEUN != BLOCK_NIL) { + while (thisEUN < nftl->nb_blocks) { + if (MTD_READOOB(nftl->mtd, (thisEUN * nftl->EraseSize) + blockofs, + 8, &retlen, (char *)&bci) < 0) + status = SECTOR_IGNORE; + else + status = bci.Status | bci.Status1; + + switch (status) { + case SECTOR_FREE: + /* no modification of a sector should follow a free sector */ + goto the_end; + case SECTOR_DELETED: + lastgoodEUN = BLOCK_NIL; + break; + case SECTOR_USED: + lastgoodEUN = thisEUN; + break; + case SECTOR_IGNORE: + break; + default: + printk("Unknown status for block %d in EUN %d: %x\n", + block, thisEUN, status); + break; + } - if (thisEUN == 0xffff) thisEUN = 0; - - while(thisEUN && (thisEUN & 0x7fff) != 0x7fff) { - struct nftl_bci bci; - size_t retlen; - - MTD_READOOB(thisNFTL->mtd, (thisEUN * thisNFTL->EraseSize) + blockofs,8, &retlen, (char *)&bci); - - switch(bci.Status) { - case __constant_cpu_to_le16(BLOCK_FREE): - thisEUN = 0; - break; - case __constant_cpu_to_le16(BLOCK_USED): - lastgoodEUN = thisEUN; - break; - case __constant_cpu_to_le16(BLOCK_IGNORE): - case __constant_cpu_to_le16(BLOCK_DELETED): - break; - default: - printk("Unknown status for block %d in EUN %d: %x\n",block,thisEUN, bci.Status); + if (!silly--) { + printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%x\n", + block / (nftl->EraseSize / 512)); + return 1; + } + thisEUN = nftl->ReplUnitTable[thisEUN]; } + } - if (!silly--) { - printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%x\n",block / (thisNFTL->EraseSize / 512)); - return 1; - } - if (thisEUN) - thisEUN = thisNFTL->ReplUnitTable[thisEUN] & 0x7fff; - } - if (lastgoodEUN == 0xffff) { + the_end: + if (lastgoodEUN == BLOCK_NIL) { + /* the requested block is not on the media, return all 0x00 */ memset(buffer, 0, 512); } else { - loff_t ptr = (lastgoodEUN * thisNFTL->EraseSize) + blockofs; + loff_t ptr = (lastgoodEUN * nftl->EraseSize) + blockofs; size_t retlen; u_char eccbuf[6]; - thisNFTL->mtd->read_ecc(thisNFTL->mtd, ptr, 512, &retlen, buffer, eccbuf); + if (MTD_READECC(nftl->mtd, ptr, 512, &retlen, buffer, eccbuf)) + return -EIO; } return 0; } - -static int nftl_ioctl(struct inode * inode, struct file * file, - unsigned int cmd, unsigned long arg) +static int nftl_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg) { - struct NFTLrecord *thisNFTL; - - thisNFTL = NFTLs[MINOR(inode->i_rdev) / 16]; + struct NFTLrecord *nftl; - if (!thisNFTL) return -EINVAL; + nftl = NFTLs[MINOR(inode->i_rdev) / 16]; + if (!nftl) return -EINVAL; switch (cmd) { case HDIO_GETGEO: { struct hd_geometry g; - g.heads = thisNFTL->heads; - g.sectors = thisNFTL->sectors; - g.cylinders = thisNFTL->cylinders; + g.heads = nftl->heads; + g.sectors = nftl->sectors; + g.cylinders = nftl->cylinders; g.start = part_table[MINOR(inode->i_rdev)].start_sect; return copy_to_user((void *)arg, &g, sizeof g) ? -EFAULT : 0; } case BLKGETSIZE: /* Return device size */ - if (!arg) return -EINVAL; + if (!arg) return -EINVAL; return put_user(part_table[MINOR(inode->i_rdev)].nr_sects, (long *) arg); case BLKFLSBUF: - if(!capable(CAP_SYS_ADMIN)) return -EACCES; + if (!capable(CAP_SYS_ADMIN)) return -EACCES; fsync_dev(inode->i_rdev); invalidate_buffers(inode->i_rdev); - if (thisNFTL->mtd->sync) - thisNFTL->mtd->sync(thisNFTL->mtd); + if (nftl->mtd->sync) + nftl->mtd->sync(nftl->mtd); return 0; case BLKRRPART: if (!capable(CAP_SYS_ADMIN)) return -EACCES; - if (thisNFTL->usecount > 1) { - // printk("Use count %d\n", thisNFTL->usecount); - return -EBUSY; - } + if (nftl->usecount > 1) return -EBUSY; #if LINUX_VERSION_CODE < 0x20328 - resetup_one_dev(&nftl_gendisk, MINOR(inode->i_dev) / 16); + resetup_one_dev(&nftl_gendisk, MINOR(inode->i_rdev) / 16); #else - grok_partitions(&nftl_gendisk, MINOR(inode->i_dev) / 16, 1<<4, thisNFTL->nr_sects); + grok_partitions(&nftl_gendisk, MINOR(inode->i_rdev) / 16, + 1<<4, nftl->nr_sects); #endif return 0; - - // RO_IOCTLS(inode->i_rdev, arg); /* ref. linux/blk.h */ + +#if (LINUX_VERSION_CODE < 0x20303) + RO_IOCTLS(inode->i_rdev, arg); /* ref. linux/blk.h */ +#else + case BLKROSET: + case BLKROGET: + case BLKSSZGET: + return blk_ioctl(inode->i_rdev, cmd, arg); +#endif + default: return -EINVAL; } } - void nftl_request(RQFUNC_ARG) { unsigned int dev, block, nsect; - struct NFTLrecord *thisNFTL; + struct NFTLrecord *nftl; char *buffer; struct request *req; int res; while (1) { INIT_REQUEST; /* blk.h */ - - req = CURRENT; -#ifdef WE_KNOW_WTF_THIS_DOES_NOT_WORK - blkdev_dequeue_request(req); - spin_unlock_irq(&io_request_lock); -#else req = CURRENT; -#endif - DEBUG(2,"NFTL_request\n"); - DEBUG(3,"NFTL %d request, %lx, %lx", req->cmd, - req->sector, req->current_nr_sectors); + /* We can do this because the generic code knows not to + touch the request at the head of the queue */ + spin_unlock_irq(&io_request_lock); + + DEBUG(MTD_DEBUG_LEVEL2, "NFTL_request\n"); + DEBUG(MTD_DEBUG_LEVEL3, "NFTL %s request, from sector 0x%04lx for 0x%04lx sectors\n", + (req->cmd == READ) ? "Read " : "Write", + req->sector, req->current_nr_sectors); + dev = MINOR(req->rq_dev); block = req->sector; nsect = req->current_nr_sectors; @@ -1125,21 +887,23 @@ void nftl_request(RQFUNC_ARG) res = 1; /* succeed */ if (dev >= MAX_NFTLS * 16) { - printk("fl: bad minor number: device=%s\n", + /* there is no such partition */ + printk("nftl: bad minor number: device = %s\n", kdevname(req->rq_dev)); res = 0; /* fail */ goto repeat; } - thisNFTL = NFTLs[dev / 16]; - DEBUG(3,"Waiting for mutex\n"); - down(&thisNFTL->mutex); - DEBUG(3,"Got mutex\n"); - - if (block + nsect >= part_table[dev].nr_sects) { - printk("nftl%c%d: bad access: block=%d, count=%d\n", + nftl = NFTLs[dev / 16]; + DEBUG(MTD_DEBUG_LEVEL3, "Waiting for mutex\n"); + down(&nftl->mutex); + DEBUG(MTD_DEBUG_LEVEL3, "Got mutex\n"); + + if (block + nsect > part_table[dev].nr_sects) { + /* access past the end of device */ + printk("nftl%c%d: bad access: block = %d, count = %d\n", (MINOR(req->rq_dev)>>6)+'a', dev & 0xf, block, nsect); - up(&thisNFTL->mutex); + up(&nftl->mutex); res = 0; /* fail */ goto repeat; } @@ -1147,75 +911,80 @@ void nftl_request(RQFUNC_ARG) block += part_table[dev].start_sect; if (req->cmd == READ) { - DEBUG(2,"NFTL read\n"); - for ( ; nsect > 0; nsect-- , block++, buffer+= 512) { + DEBUG(MTD_DEBUG_LEVEL2, "NFTL read request of 0x%x sectors @ %x " + "(req->nr_sectors == %lx)\n", nsect, block, req->nr_sectors); + + for ( ; nsect > 0; nsect-- , block++, buffer += 512) { /* Read a single sector to req->buffer + (512 * i) */ - - if (NFTL_readblock(thisNFTL, block, buffer)) { - DEBUG(2,"NFTL read request failed\n"); - up(&thisNFTL->mutex); + if (NFTL_readblock(nftl, block, buffer)) { + DEBUG(MTD_DEBUG_LEVEL2, "NFTL read request failed\n"); + up(&nftl->mutex); res = 0; goto repeat; } } - DEBUG(2,"NFTL read request completed OK\n"); - up(&thisNFTL->mutex); + + DEBUG(MTD_DEBUG_LEVEL2,"NFTL read request completed OK\n"); + up(&nftl->mutex); goto repeat; - } - else if (req->cmd == WRITE) { - DEBUG(2,"NFTL write request of 0x%x sectors @ %x (req->nr_sectors == %lx\n",nsect, block, req->nr_sectors); + } else if (req->cmd == WRITE) { + DEBUG(MTD_DEBUG_LEVEL2, "NFTL write request of 0x%x sectors @ %x " + "(req->nr_sectors == %lx)\n", nsect, block, + req->nr_sectors); #ifdef CONFIG_NFTL_RW - for ( ; nsect > 0; nsect-- , block++, buffer+= 512) { + for ( ; nsect > 0; nsect-- , block++, buffer += 512) { /* Read a single sector to req->buffer + (512 * i) */ - - if (NFTL_writeblock(thisNFTL, block, buffer)) { - DEBUG(1,"NFTL write request failed\n"); - - up(&thisNFTL->mutex); + if (NFTL_writeblock(nftl, block, buffer)) { + DEBUG(MTD_DEBUG_LEVEL1,"NFTL write request failed\n"); + up(&nftl->mutex); res = 0; goto repeat; } } - DEBUG(2,"NFTL write request completed OK\n"); + DEBUG(MTD_DEBUG_LEVEL2,"NFTL write request completed OK\n"); #else - res=0; /* Writes always fail */ + res = 0; /* Writes always fail */ #endif /* CONFIG_NFTL_RW */ - up(&thisNFTL->mutex); + up(&nftl->mutex); goto repeat; - } - else { - DEBUG(0,"NFTL ??? request\n"); - up(&thisNFTL->mutex); + } else { + DEBUG(MTD_DEBUG_LEVEL0, "NFTL unknown request\n"); + up(&nftl->mutex); res = 0; goto repeat; } repeat: - DEBUG(3,"end_request(%d)\n", res); -#ifdef WE_KNOW_WTF_THIS_DOES_NOT_WORK + DEBUG(MTD_DEBUG_LEVEL3, "end_request(%d)\n", res); spin_lock_irq(&io_request_lock); - nftl_end_request(req, res); -#else end_request(res); -#endif } } static int nftl_open(struct inode *ip, struct file *fp) { + int nftlnum = MINOR(ip->i_rdev) / 16; struct NFTLrecord *thisNFTL; - thisNFTL = NFTLs[MINOR(ip->i_rdev) / 16]; + thisNFTL = NFTLs[nftlnum]; - DEBUG(2,"NFTL_open\n"); + DEBUG(MTD_DEBUG_LEVEL2,"NFTL_open\n"); +#ifdef CONFIG_KMOD + if (!thisNFTL && nftlnum == 0) { + request_module("docprobe"); + thisNFTL = NFTLs[nftlnum]; + } +#endif if (!thisNFTL) { - DEBUG(2,"ENODEV: thisNFTL = %d, minor = %d, ip = %p, fp = %p\n", - MINOR(ip->i_rdev) / 16,ip->i_rdev,ip, fp); + DEBUG(MTD_DEBUG_LEVEL2,"ENODEV: thisNFTL = %d, minor = %d, ip = %p, fp = %p\n", + nftlnum, ip->i_rdev, ip, fp); return -ENODEV; } + #ifndef CONFIG_NFTL_RW if (fp->f_mode & FMODE_WRITE) - return -EROFS; + return -EROFS; #endif /* !CONFIG_NFTL_RW */ + thisNFTL->usecount++; MOD_INC_USE_COUNT; if (!get_mtd_device(thisNFTL->mtd, -1)) { @@ -1233,8 +1002,8 @@ static int nftl_release(struct inode *inode, struct file *fp) thisNFTL = NFTLs[MINOR(inode->i_rdev) / 16]; - DEBUG(2, "NFTL_release\n"); - + DEBUG(MTD_DEBUG_LEVEL2, "NFTL_release\n"); + fsync_dev(inode->i_rdev); if (sb) invalidate_inodes(sb); @@ -1251,19 +1020,19 @@ static int nftl_release(struct inode *inode, struct file *fp) } #if LINUX_VERSION_CODE < 0x20326 static struct file_operations nftl_fops = { - read: block_read, - write: block_write, - ioctl: nftl_ioctl, - open: nftl_open, - release: nftl_release, - fsync: block_fsync, + read: block_read, + write: block_write, + ioctl: nftl_ioctl, + open: nftl_open, + release: nftl_release, + fsync: block_fsync, }; #else static struct block_device_operations nftl_fops = { - open: nftl_open, - release: nftl_release, - ioctl: nftl_ioctl + open: nftl_open, + release: nftl_release, + ioctl: nftl_ioctl }; #endif @@ -1275,39 +1044,39 @@ static struct block_device_operations nftl_fops = * ****************************************************************************/ -#if LINUX_VERSION_CODE < 0x20300 -#ifdef MODULE +#if LINUX_VERSION_CODE < 0x20212 && defined(MODULE) #define init_nftl init_module #define cleanup_nftl cleanup_module #endif -#define __exit -#endif static struct mtd_notifier nftl_notifier = {NFTL_notify_add, NFTL_notify_remove, NULL}; - /* static int __init init_nftl(void) */ int __init init_nftl(void) { int i; - printk(KERN_NOTICE "M-Systems NAND Flash Translation Layer driver. (C) 1999 MVHI\n"); + printk(KERN_NOTICE + "M-Systems NAND Flash Translation Layer driver. (C) 1999 MVHI\n"); #ifdef PRERELEASE - printk(KERN_INFO"$Id: nftl.c,v 1.36 2000/07/13 14:14:20 dwmw2 Exp $\n"); + printk(KERN_INFO"$Id: nftl.c,v 1.57 2000/12/01 17:51:54 dwmw2 Exp $\n"); #endif - if (register_blkdev(NFTL_MAJOR, "nftl", &nftl_fops)){ - printk("unable to register NFTL block device\n"); + if (register_blkdev(MAJOR_NR, "nftl", &nftl_fops)){ + printk("unable to register NFTL block device on major %d\n", MAJOR_NR); + return -EBUSY; } else { #if LINUX_VERSION_CODE < 0x20320 - blk_dev[MAJOR_NR].request_fn = nftl_request; + blk_dev[MAJOR_NR].request_fn = nftl_request; #else - blk_init_queue(BLK_DEFAULT_QUEUE(MAJOR_NR), &nftl_request); + blk_init_queue(BLK_DEFAULT_QUEUE(MAJOR_NR), &nftl_request); #endif - for (i=0; i < 256 ; i++) { + /* set block size to 1kB each */ + for (i = 0; i < 256; i++) { nftl_blocksizes[i] = 1024; } - blksize_size[NFTL_MAJOR] = nftl_blocksizes; + blksize_size[MAJOR_NR] = nftl_blocksizes; + nftl_gendisk.next = gendisk_head; gendisk_head = &nftl_gendisk; } @@ -1319,25 +1088,25 @@ int __init init_nftl(void) static void __exit cleanup_nftl(void) { - struct gendisk *gd, **gdp; + struct gendisk *gd, **gdp; - unregister_mtd_user(&nftl_notifier); - - unregister_blkdev(NFTL_MAJOR, "nftl"); + unregister_mtd_user(&nftl_notifier); + unregister_blkdev(MAJOR_NR, "nftl"); + #if LINUX_VERSION_CODE < 0x20320 - blk_dev[MAJOR_NR].request_fn = 0; + blk_dev[MAJOR_NR].request_fn = 0; #else - blk_cleanup_queue(BLK_DEFAULT_QUEUE(MAJOR_NR)); + blk_cleanup_queue(BLK_DEFAULT_QUEUE(MAJOR_NR)); #endif - for (gdp = &gendisk_head; *gdp; gdp = &((*gdp)->next)) - if (*gdp == &nftl_gendisk) { - gd = *gdp; *gdp = gd->next; - break; - } - + + /* remove ourself from generic harddisk list + FIXME: why can't I found this partition on /proc/partition */ + for (gdp = &gendisk_head; *gdp; gdp = &((*gdp)->next)) + if (*gdp == &nftl_gendisk) { + gd = *gdp; *gdp = gd->next; + break; + } } -#if LINUX_VERSION_CODE > 0x20300 module_init(init_nftl); module_exit(cleanup_nftl); -#endif diff --git a/drivers/mtd/nftlmount.c b/drivers/mtd/nftlmount.c new file mode 100644 index 000000000..d1f05930f --- /dev/null +++ b/drivers/mtd/nftlmount.c @@ -0,0 +1,678 @@ +/* + * NFTL mount code with extensive checks + * + * Author: Fabrice Bellard (fabrice.bellard@netgem.com) + * Copyright (C) 2000 Netgem S.A. + * + * $Id: nftlmount.c,v 1.11 2000/11/17 12:24:09 ollie 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/config.h> +#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/malloc.h> +#include <linux/sched.h> +#include <linux/init.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nftl.h> +#include <linux/mtd/compatmac.h> + +#define SECTORSIZE 512 + +/* find_boot_record: Find the NFTL Media Header and its Spare copy which contains the + * various device information of the NFTL partition and Bad Unit Table. Update + * the ReplUnitTable[] table accroding to the Bad Unit Table. ReplUnitTable[] + * is used for management of Erase Unit in other routines in nftl.c and nftlmount.c + */ +static int find_boot_record(struct NFTLrecord *nftl) +{ + struct nftl_uci1 h1; + struct nftl_oob oob; + unsigned int block, boot_record_count; + int retlen; + u8 buf[SECTORSIZE]; + struct NFTLMediaHeader *mh = &nftl->MediaHdr; + + nftl->MediaUnit = BLOCK_NIL; + nftl->SpareMediaUnit = BLOCK_NIL; + boot_record_count = 0; + + /* search for a valid boot record */ + for (block = 0; block < nftl->nb_blocks; block++) { + unsigned int erase_mark; + + /* read ANAND header. To be safer with BIOS, also use erase mark as discriminant */ + if (MTD_READOOB(nftl->mtd, block * nftl->EraseSize + SECTORSIZE + 8, + 8, &retlen, (char *)&h1) < 0) + continue; + + erase_mark = le16_to_cpu ((h1.EraseMark | h1.EraseMark1)); + if (erase_mark != ERASE_MARK) + continue; + + if (MTD_READECC(nftl->mtd, block * nftl->EraseSize, SECTORSIZE, + &retlen, buf, (char *)&oob) < 0) + continue; + + memcpy(mh, buf, sizeof(struct NFTLMediaHeader)); + if (memcmp(mh->DataOrgID, "ANAND", 6) == 0) { + /* first boot record */ + if (boot_record_count == 0) { + unsigned int i; + /* header found : read the bad block table data */ + if (mh->UnitSizeFactor != 0xff) { + printk("Sorry, we don't support UnitSizeFactor " + "of != 1 yet\n"); + goto ReplUnitTable; + } + + nftl->nb_boot_blocks = le16_to_cpu(mh->FirstPhysicalEUN); + if ((nftl->nb_boot_blocks + 2) >= nftl->nb_blocks) + goto ReplUnitTable; /* small consistency check */ + + nftl->numvunits = le32_to_cpu(mh->FormattedSize) / nftl->EraseSize; + if (nftl->numvunits > (nftl->nb_blocks - nftl->nb_boot_blocks - 2)) + goto ReplUnitTable; /* small consistency check */ + + /* FixMe: with bad blocks, the total size available is not FormattedSize any + more !!! */ + nftl->nr_sects = nftl->numvunits * (nftl->EraseSize / SECTORSIZE); + nftl->MediaUnit = block; + + /* read the Bad Erase Unit Table and modify ReplUnitTable[] accordingly */ + for (i = 0; i < nftl->nb_blocks; i++) { + if ((i & (SECTORSIZE - 1)) == 0) { + /* read one sector for every SECTORSIZE of blocks */ + if (MTD_READECC(nftl->mtd, block * nftl->EraseSize + + i + SECTORSIZE, SECTORSIZE, + &retlen, buf, (char *)&oob) < 0) + goto ReplUnitTable; + } + /* mark the Bad Erase Unit as RESERVED in ReplUnitTable */ + if (buf[i & (SECTORSIZE - 1)] != 0xff) + nftl->ReplUnitTable[i] = BLOCK_RESERVED; + } + + boot_record_count++; + } else if (boot_record_count == 1) { + nftl->SpareMediaUnit = block; + boot_record_count++; + break; + } + } + ReplUnitTable: + } + + if (boot_record_count == 0) { + /* no boot record found */ + return -1; + } else { + return 0; + } +} + +static int memcmpb(void *a, int c, int n) +{ + int i; + for (i = 0; i < n; i++) { + if (c != ((unsigned char *)a)[i]) + return 1; + } + return 0; +} + +/* check_free_sector: check if a free sector is actually FREE, i.e. All 0xff in data and oob area */ +static int check_free_sectors(struct NFTLrecord *nftl, unsigned int address, int len, + int check_oob) +{ + int i, retlen; + u8 buf[SECTORSIZE]; + + for (i = 0; i < len; i += SECTORSIZE) { + /* we want to read the sector without ECC check here since a free + sector does not have ECC syndrome on it yet */ + if (MTD_READ(nftl->mtd, address, SECTORSIZE, &retlen, buf) < 0) + return -1; + if (memcmpb(buf, 0xff, SECTORSIZE) != 0) + return -1; + + if (check_oob) { + if (MTD_READOOB(nftl->mtd, address, nftl->mtd->oobsize, + &retlen, buf) < 0) + return -1; + if (memcmpb(buf, 0xff, nftl->mtd->oobsize) != 0) + return -1; + } + address += SECTORSIZE; + } + + return 0; +} + +/* NFTL_format: format a Erase Unit by erasing ALL Erase Zones in the Erase Unit and + * Update NFTL metadata. Each erase operation is checked with check_free_sectors + * + * Return: 0 when succeed, -1 on error. + * + * ToDo: 1. Is it neceressary to check_free_sector after erasing ?? + * 2. UnitSizeFactor != 0xFF + */ +int NFTL_formatblock(struct NFTLrecord *nftl, int block) +{ + int retlen; + unsigned int nb_erases, erase_mark; + struct nftl_uci1 uci; + struct erase_info *instr = &nftl->instr; + + /* Read the Unit Control Information #1 for Wear-Leveling */ + if (MTD_READOOB(nftl->mtd, block * nftl->EraseSize + SECTORSIZE + 8, + 8, &retlen, (char *)&uci) < 0) + goto default_uci1; + + erase_mark = le16_to_cpu ((uci.EraseMark | uci.EraseMark1)); + if (erase_mark != ERASE_MARK) { + default_uci1: + uci.EraseMark = cpu_to_le16(ERASE_MARK); + uci.EraseMark1 = cpu_to_le16(ERASE_MARK); + uci.WearInfo = cpu_to_le32(0); + } + + memset(instr, 0, sizeof(struct erase_info)); + + /* XXX: use async erase interface, XXX: test return code */ + instr->addr = block * nftl->EraseSize; + instr->len = nftl->EraseSize; + MTD_ERASE(nftl->mtd, instr); + + if (instr->state == MTD_ERASE_FAILED) { + /* could not format, FixMe: We should update the BadUnitTable + both in memory and on disk */ + printk("Error while formatting block %d\n", block); + return -1; + } else { + /* increase and write Wear-Leveling info */ + nb_erases = le32_to_cpu(uci.WearInfo); + nb_erases++; + + /* wrap (almost impossible with current flashs) or free block */ + if (nb_erases == 0) + nb_erases = 1; + + /* check the "freeness" of Erase Unit before updating metadata + * FixMe: is this check really necessary ? since we have check the + * return code after the erase operation. */ + if (check_free_sectors(nftl, instr->addr, nftl->EraseSize, 1) != 0) + return -1; + + uci.WearInfo = le32_to_cpu(nb_erases); + if (MTD_WRITEOOB(nftl->mtd, block * nftl->EraseSize + SECTORSIZE + 8, 8, + &retlen, (char *)&uci) < 0) + return -1; + return 0; + } +} + +/* check_sectors_in_chain: Check that each sector of a Virtual Unit Chain is correct. + * Mark as 'IGNORE' each incorrect sector. This check is only done if the chain + * was being folded when NFTL was interrupted. + * + * The check_free_sectors in this function is neceressary. There is a possible + * situation that after writing the Data area, the Block Control Information is + * not updated according (due to power failure or something) which leaves the block + * in an umconsistent state. So we have to check if a block is really FREE in this + * case. */ +static void check_sectors_in_chain(struct NFTLrecord *nftl, unsigned int first_block) +{ + unsigned int block, i, status; + struct nftl_bci bci; + int sectors_per_block, retlen; + + sectors_per_block = nftl->EraseSize / SECTORSIZE; + block = first_block; + for (;;) { + for (i = 0; i < sectors_per_block; i++) { + if (MTD_READOOB(nftl->mtd, block * nftl->EraseSize + i * SECTORSIZE, + 8, &retlen, (char *)&bci) < 0) + status = SECTOR_IGNORE; + else + status = bci.Status | bci.Status1; + + switch(status) { + case SECTOR_FREE: + /* verify that the sector is really free. If not, mark + as ignore */ + if (memcmpb(&bci, 0xff, 8) != 0 || + check_free_sectors(nftl, block * nftl->EraseSize + i * SECTORSIZE, + SECTORSIZE, 0) != 0) { + printk("Incorrect free sector %d in block %d: " + "marking it as ignored\n", + i, block); + + /* sector not free actually : mark it as SECTOR_IGNORE */ + bci.Status = SECTOR_IGNORE; + bci.Status1 = SECTOR_IGNORE; + MTD_WRITEOOB(nftl->mtd, + block * nftl->EraseSize + i * SECTORSIZE, + 8, &retlen, (char *)&bci); + } + break; + default: + break; + } + } + + /* proceed to next Erase Unit on the chain */ + block = nftl->ReplUnitTable[block]; + if (!(block == BLOCK_NIL || block < nftl->nb_blocks)) + printk("incorrect ReplUnitTable[] : %d\n", block); + if (block == BLOCK_NIL || block >= nftl->nb_blocks) + break; + } +} + +/* calc_chain_lenght: Walk through a Virtual Unit Chain and estimate chain length */ +static int calc_chain_length(struct NFTLrecord *nftl, unsigned int first_block) +{ + unsigned int length = 0, block = first_block; + + for (;;) { + length++; + /* avoid infinite loops, although this is guaranted not to + happen because of the previous checks */ + if (length >= nftl->nb_blocks) { + printk("nftl: length too long %d !\n", length); + break; + } + + block = nftl->ReplUnitTable[block]; + if (!(block == BLOCK_NIL || block < nftl->nb_blocks)) + printk("incorrect ReplUnitTable[] : %d\n", block); + if (block == BLOCK_NIL || block >= nftl->nb_blocks) + break; + } + return length; +} + +/* format_chain: Format an invalid Virtual Unit chain. It frees all the Erase Units in a + * Virtual Unit Chain, i.e. all the units are disconnected. + * + * It is not stricly correct to begin from the first block of the chain because + * if we stop the code, we may see again a valid chain if there was a first_block + * flag in a block inside it. But is it really a problem ? + * + * FixMe: Figure out what the last statesment means. What if power failure when we are + * in the for (;;) loop formatting blocks ?? + */ +static void format_chain(struct NFTLrecord *nftl, unsigned int first_block) +{ + unsigned int block = first_block, block1; + + printk("Formatting chain at block %d\n", first_block); + + for (;;) { + block1 = nftl->ReplUnitTable[block]; + + printk("Formatting block %d\n", block); + if (NFTL_formatblock(nftl, block) < 0) { + /* cannot format !!!! Mark it as Bad Unit, + FixMe: update the BadUnitTable on disk */ + nftl->ReplUnitTable[block] = BLOCK_RESERVED; + } else { + nftl->ReplUnitTable[block] = BLOCK_FREE; + } + + /* goto next block on the chain */ + block = block1; + + if (!(block == BLOCK_NIL || block < nftl->nb_blocks)) + printk("incorrect ReplUnitTable[] : %d\n", block); + if (block == BLOCK_NIL || block >= nftl->nb_blocks) + break; + } +} + +/* check_and_mark_free_block: Verify that a block is free in the NFTL sense (valid erase mark) or + * totally free (only 0xff). + * + * Definition: Free Erase Unit -- A properly erased/formatted Free Erase Unit should have meet the + * following critia: + * 1. */ +static int check_and_mark_free_block(struct NFTLrecord *nftl, int block) +{ + struct nftl_uci1 h1; + unsigned int erase_mark; + int i, retlen; + unsigned char buf[SECTORSIZE]; + + /* check erase mark. */ + if (MTD_READOOB(nftl->mtd, block * nftl->EraseSize + SECTORSIZE + 8, 8, + &retlen, (char *)&h1) < 0) + return -1; + + erase_mark = le16_to_cpu ((h1.EraseMark | h1.EraseMark1)); + if (erase_mark != ERASE_MARK) { + /* if no erase mark, the block must be totally free. This is + possible in two cases : empty filsystem or interrupted erase (very unlikely) */ + if (check_free_sectors (nftl, block * nftl->EraseSize, nftl->EraseSize, 1) != 0) + return -1; + + /* free block : write erase mark */ + h1.EraseMark = cpu_to_le16(ERASE_MARK); + h1.EraseMark1 = cpu_to_le16(ERASE_MARK); + h1.WearInfo = cpu_to_le32(0); + if (MTD_WRITEOOB(nftl->mtd, block * nftl->EraseSize + SECTORSIZE + 8, 8, + &retlen, (char *)&h1) < 0) + return -1; + } else { +#if 0 + /* if erase mark present, need to skip it when doing check */ + for (i = 0; i < nftl->EraseSize; i += SECTORSIZE) { + /* check free sector */ + if (check_free_sectors (nftl, block * nftl->EraseSize + i, + SECTORSIZE, 0) != 0) + return -1; + + if (MTD_READOOB(nftl->mtd, block * nftl->EraseSize + i, + 16, &retlen, buf) < 0) + return -1; + if (i == SECTORSIZE) { + /* skip erase mark */ + if (memcmpb(buf, 0xff, 8)) + return -1; + } else { + if (memcmpb(buf, 0xff, 16)) + return -1; + } + } +#endif + } + + return 0; +} + +/* get_fold_mark: Read fold mark from Unit Control Information #2, we use FOLD_MARK_IN_PROGRESS + * to indicate that we are in the progression of a Virtual Unit Chain folding. If the UCI #2 + * is FOLD_MARK_IN_PROGRESS when mounting the NFTL, the (previous) folding process is interrupted + * for some reason. A clean up/check of the VUC is neceressary in this case. + * + * WARNING: return 0 if read error + */ +static int get_fold_mark(struct NFTLrecord *nftl, unsigned int block) +{ + struct nftl_uci2 uci; + int retlen; + + if (MTD_READOOB(nftl->mtd, block * nftl->EraseSize + 2 * SECTORSIZE + 8, + 8, &retlen, (char *)&uci) < 0) + return 0; + + return le16_to_cpu((uci.FoldMark | uci.FoldMark1)); +} + +int NFTL_mount(struct NFTLrecord *s) +{ + int i; + unsigned int first_logical_block, logical_block, rep_block, nb_erases, erase_mark; + unsigned int block, first_block, is_first_block; + int chain_length, do_format_chain; + struct nftl_uci0 h0; + struct nftl_uci1 h1; + int retlen; + + /* XXX: will be suppressed */ + s->lastEUN = s->nb_blocks - 1; + + /* memory alloc */ + s->EUNtable = kmalloc(s->nb_blocks * sizeof(u16), GFP_KERNEL); + s->ReplUnitTable = kmalloc(s->nb_blocks * sizeof(u16), GFP_KERNEL); + if (!s->EUNtable || !s->ReplUnitTable) { + fail: + if (s->EUNtable) + kfree(s->EUNtable); + if (s->ReplUnitTable) + kfree(s->ReplUnitTable); + return -1; + } + + /* mark all blocks as potentially containing data */ + for (i = 0; i < s->nb_blocks; i++) { + s->ReplUnitTable[i] = BLOCK_NOTEXPLORED; + } + + /* search for NFTL MediaHeader and Spare NFTL Media Header */ + if (find_boot_record(s) < 0) { + printk("Could not find valid boot record\n"); + goto fail; + } + + /* mark the bios blocks (blocks before NFTL MediaHeader) as reserved */ + for (i = 0; i < s->nb_boot_blocks; i++) + s->ReplUnitTable[i] = BLOCK_RESERVED; + + /* also mark the boot records (NFTL MediaHeader) blocks as reserved */ + if (s->MediaUnit != BLOCK_NIL) + s->ReplUnitTable[s->MediaUnit] = BLOCK_RESERVED; + if (s->SpareMediaUnit != BLOCK_NIL) + s->ReplUnitTable[s->SpareMediaUnit] = BLOCK_RESERVED; + + /* init the logical to physical table */ + for (i = 0; i < s->nb_blocks; i++) { + s->EUNtable[i] = BLOCK_NIL; + } + + /* first pass : explore each block chain */ + first_logical_block = 0; + for (first_block = 0; first_block < s->nb_blocks; first_block++) { + /* if the block was not already explored, we can look at it */ + if (s->ReplUnitTable[first_block] == BLOCK_NOTEXPLORED) { + block = first_block; + chain_length = 0; + do_format_chain = 0; + + for (;;) { + /* read the block header. If error, we format the chain */ + if (MTD_READOOB(s->mtd, block * s->EraseSize + 8, 8, + &retlen, (char *)&h0) < 0 || + MTD_READOOB(s->mtd, block * s->EraseSize + SECTORSIZE + 8, 8, + &retlen, (char *)&h1) < 0) { + s->ReplUnitTable[block] = BLOCK_NIL; + do_format_chain = 1; + break; + } + + logical_block = le16_to_cpu ((h0.VirtUnitNum | h0.SpareVirtUnitNum)); + rep_block = le16_to_cpu ((h0.ReplUnitNum | h0.SpareReplUnitNum)); + nb_erases = le32_to_cpu (h1.WearInfo); + erase_mark = le16_to_cpu ((h1.EraseMark | h1.EraseMark1)); + + is_first_block = !(logical_block >> 15); + logical_block = logical_block & 0x7fff; + + /* invalid/free block test */ + if (erase_mark != ERASE_MARK || logical_block >= s->nb_blocks) { + if (chain_length == 0) { + /* if not currently in a chain, we can handle it safely */ + if (check_and_mark_free_block(s, block) < 0) { + /* not really free: format it */ + printk("Formatting block %d\n", block); + if (NFTL_formatblock(s, block) < 0) { + /* could not format: reserve the block */ + s->ReplUnitTable[block] = BLOCK_RESERVED; + } else { + s->ReplUnitTable[block] = BLOCK_FREE; + } + } else { + /* free block: mark it */ + s->ReplUnitTable[block] = BLOCK_FREE; + } + /* directly examine the next block. */ + goto examine_ReplUnitTable; + } else { + /* the block was in a chain : this is bad. We + must format all the chain */ + printk("Block %d: free but referenced in chain %d\n", + block, first_block); + s->ReplUnitTable[block] = BLOCK_NIL; + do_format_chain = 1; + break; + } + } + + /* we accept only first blocks here */ + if (chain_length == 0) { + /* this block is not the first block in chain : + ignore it, it will be included in a chain + later, or marked as not explored */ + if (!is_first_block) + goto examine_ReplUnitTable; + first_logical_block = logical_block; + } else { + if (logical_block != first_logical_block) { + printk("Block %d: incorrect logical block: %d expected: %d\n", + block, logical_block, first_logical_block); + /* the chain is incorrect : we must format it, + but we need to read it completly */ + do_format_chain = 1; + } + if (is_first_block) { + /* we accept that a block is marked as first + block while being last block in a chain + only if the chain is being folded */ + if (get_fold_mark(s, block) != FOLD_MARK_IN_PROGRESS || + rep_block != 0xffff) { + printk("Block %d: incorrectly marked as first block in chain\n", + block); + /* the chain is incorrect : we must format it, + but we need to read it completly */ + do_format_chain = 1; + } else { + printk("Block %d: folding in progress - ignoring first block flag\n", + block); + } + } + } + chain_length++; + if (rep_block == 0xffff) { + /* no more blocks after */ + s->ReplUnitTable[block] = BLOCK_NIL; + break; + } else if (rep_block >= s->nb_blocks) { + printk("Block %d: referencing invalid block %d\n", + block, rep_block); + do_format_chain = 1; + s->ReplUnitTable[block] = BLOCK_NIL; + break; + } else if (s->ReplUnitTable[rep_block] != BLOCK_NOTEXPLORED) { + /* same problem as previous 'is_first_block' test: + we accept that the last block of a chain has + the first_block flag set if folding is in + progress. We handle here the case where the + last block appeared first */ + if (s->ReplUnitTable[rep_block] == BLOCK_NIL && + s->EUNtable[first_logical_block] == rep_block && + get_fold_mark(s, first_block) == FOLD_MARK_IN_PROGRESS) { + /* EUNtable[] will be set after */ + printk("Block %d: folding in progress - ignoring first block flag\n", + rep_block); + s->ReplUnitTable[block] = rep_block; + s->EUNtable[first_logical_block] = BLOCK_NIL; + } else { + printk("Block %d: referencing block %d already in another chain\n", + block, rep_block); + /* XXX: should handle correctly fold in progress chains */ + do_format_chain = 1; + s->ReplUnitTable[block] = BLOCK_NIL; + } + break; + } else { + /* this is OK */ + s->ReplUnitTable[block] = rep_block; + block = rep_block; + } + } + + /* the chain was completely explored. Now we can decide + what to do with it */ + if (do_format_chain) { + /* invalid chain : format it */ + format_chain(s, first_block); + } else { + unsigned int first_block1, chain_to_format, chain_length1; + int fold_mark; + + /* valid chain : get foldmark */ + fold_mark = get_fold_mark(s, first_block); + if (fold_mark == 0) { + /* cannot get foldmark : format the chain */ + printk("Could read foldmark at block %d\n", first_block); + format_chain(s, first_block); + } else { + if (fold_mark == FOLD_MARK_IN_PROGRESS) + check_sectors_in_chain(s, first_block); + + /* now handle the case where we find two chains at the + same virtual address : we select the longer one, + because the shorter one is the one which was being + folded if the folding was not done in place */ + first_block1 = s->EUNtable[first_logical_block]; + if (first_block1 != BLOCK_NIL) { + /* XXX: what to do if same length ? */ + chain_length1 = calc_chain_length(s, first_block1); + printk("Two chains at blocks %d (len=%d) and %d (len=%d)\n", + first_block1, chain_length1, first_block, chain_length); + + if (chain_length >= chain_length1) { + chain_to_format = first_block1; + s->EUNtable[first_logical_block] = first_block; + } else { + chain_to_format = first_block; + } + format_chain(s, chain_to_format); + } else { + s->EUNtable[first_logical_block] = first_block; + } + } + } + } + examine_ReplUnitTable: + } + + /* second pass to format unreferenced blocks and init free block count */ + s->numfreeEUNs = 0; + s->LastFreeEUN = BLOCK_NIL; + + for (block = 0; block < s->nb_blocks; block++) { + if (s->ReplUnitTable[block] == BLOCK_NOTEXPLORED) { + printk("Unreferenced block %d, formatting it\n", block); + if (NFTL_formatblock(s, block) < 0) + s->ReplUnitTable[block] = BLOCK_RESERVED; + else + s->ReplUnitTable[block] = BLOCK_FREE; + } + if (s->ReplUnitTable[block] == BLOCK_FREE) { + s->numfreeEUNs++; + s->LastFreeEUN = block; + } + } + + return 0; +} diff --git a/drivers/mtd/nora.c b/drivers/mtd/nora.c index 9304d7e62..48da299fc 100644 --- a/drivers/mtd/nora.c +++ b/drivers/mtd/nora.c @@ -1,5 +1,5 @@ /* - * $Id: nora.c,v 1.12 2000/07/13 10:32:33 dwmw2 Exp $ + * $Id: nora.c,v 1.17 2000/12/03 19:32:21 dwmw2 Exp $ * * This is so simple I love it. */ @@ -58,19 +58,17 @@ void nora_copy_to(struct map_info *map, unsigned long to, const void *from, ssiz } struct map_info nora_map = { - "NORA", - WINDOW_SIZE, - 2, - nora_read8, - nora_read16, - nora_read32, - nora_copy_from, - nora_write8, - nora_write16, - nora_write32, - nora_copy_to, - 0, - 0 + name: "NORA", + size: WINDOW_SIZE, + buswidth: 2, + read8: nora_read8, + read16: nora_read16, + read32: nora_read32, + copy_from: nora_copy_from, + write8: nora_write8, + write16: nora_write16, + write32: nora_write32, + copy_to: nora_copy_to }; @@ -140,7 +138,7 @@ static struct mtd_info nora_mtds[4] = { /* boot, kernel, ramdisk, fs */ { type: MTD_NORFLASH, flags: MTD_CAP_NORFLASH, - size: 0xf00000, + size: 0x900000, erasesize: 0x20000, name: "NORA root filesystem", module: THIS_MODULE, @@ -155,9 +153,9 @@ static struct mtd_info nora_mtds[4] = { /* boot, kernel, ramdisk, fs */ { type: MTD_NORFLASH, flags: MTD_CAP_NORFLASH, - size: 0x1000000, + size: 0x1600000, erasesize: 0x20000, - name: "NORA main filesystem", + name: "NORA second filesystem", module: THIS_MODULE, erase: nora_mtd_erase, read: nora_mtd_read, @@ -165,16 +163,15 @@ static struct mtd_info nora_mtds[4] = { /* boot, kernel, ramdisk, fs */ suspend: nora_mtd_suspend, resume: nora_mtd_resume, sync: nora_mtd_sync, - priv: (void *)0x1000000 + priv: (void *)0xa00000 } }; -#if LINUX_VERSION_CODE < 0x20300 -#ifdef MODULE + +#if LINUX_VERSION_CODE < 0x20212 && defined(MODULE) #define init_nora init_module #define cleanup_nora cleanup_module #endif -#endif int __init init_nora(void) { @@ -186,10 +183,10 @@ int __init init_nora(void) mymtd->module = &__this_module; #endif - add_mtd_device(&nora_mtds[3]); + add_mtd_device(&nora_mtds[2]); add_mtd_device(&nora_mtds[0]); add_mtd_device(&nora_mtds[1]); - add_mtd_device(&nora_mtds[2]); + add_mtd_device(&nora_mtds[3]); return 0; } @@ -199,10 +196,13 @@ int __init init_nora(void) static void __exit cleanup_nora(void) { if (mymtd) { - del_mtd_device(&nora_mtds[2]); + del_mtd_device(&nora_mtds[3]); del_mtd_device(&nora_mtds[1]); del_mtd_device(&nora_mtds[0]); - del_mtd_device(&nora_mtds[3]); + del_mtd_device(&nora_mtds[2]); map_destroy(mymtd); } } + +module_init(init_nora); +module_exit(cleanup_nora); diff --git a/drivers/mtd/octagon-5066.c b/drivers/mtd/octagon-5066.c index b184cd0e7..70e78d7e9 100644 --- a/drivers/mtd/octagon-5066.c +++ b/drivers/mtd/octagon-5066.c @@ -1,4 +1,4 @@ -// $Id: octagon-5066.c,v 1.10 2000/07/13 14:04:23 dwmw2 Exp $ +// $Id: octagon-5066.c,v 1.12 2000/11/27 08:50:22 dwmw2 Exp $ /* ###################################################################### Octagon 5066 MTD Driver. @@ -151,32 +151,32 @@ static void oct5066_copy_to(struct map_info *map, unsigned long to, const void * static struct map_info oct5066_map[2] = { { - "Octagon 5066 Socket", - 512 * 1024, - 1, - oct5066_read8, - oct5066_read16, - oct5066_read32, - oct5066_copy_from, - oct5066_write8, - oct5066_write16, - oct5066_write32, - oct5066_copy_to, - 1<<6 + name: "Octagon 5066 Socket", + size: 512 * 1024, + buswidth: 1, + read8: oct5066_read8, + read16: oct5066_read16, + read32: oct5066_read32, + copy_from: oct5066_copy_from, + write8: oct5066_write8, + write16: oct5066_write16, + write32: oct5066_write32, + copy_to: oct5066_copy_to, + map_priv_1: 1<<6 }, { - "Octagon 5066 Internal Flash", - 2 * 1024 * 1024, - 1, - oct5066_read8, - oct5066_read16, - oct5066_read32, - oct5066_copy_from, - oct5066_write8, - oct5066_write16, - oct5066_write32, - oct5066_copy_to, - 2<<6 + name: "Octagon 5066 Internal Flash", + size: 2 * 1024 * 1024, + buswidth: 1, + read8: oct5066_read8, + read16: oct5066_read16, + read32: oct5066_read32, + copy_from: oct5066_copy_from, + write8: oct5066_write8, + write16: oct5066_write16, + write32: oct5066_write32, + copy_to: oct5066_copy_to, + map_priv_1: 2<<6 } }; @@ -213,13 +213,10 @@ static int __init OctProbe() return 0; } -#if LINUX_VERSION_CODE < 0x20300 -#ifdef MODULE +#if LINUX_VERSION_CODE < 0x20212 && defined(MODULE) #define init_oct5066 init_module #define cleanup_oct5066 cleanup_module #endif -#define __exit -#endif void cleanup_oct5066(void) { @@ -284,7 +281,5 @@ int __init init_oct5066(void) return 0; } -#if LINUX_VERSION_CODE > 0x20300 module_init(init_oct5066); module_exit(cleanup_oct5066); -#endif diff --git a/drivers/mtd/physmap.c b/drivers/mtd/physmap.c index 658b8bf47..31ac39310 100644 --- a/drivers/mtd/physmap.c +++ b/drivers/mtd/physmap.c @@ -1,5 +1,5 @@ /* - * $Id: physmap.c,v 1.2 2000/07/11 09:42:32 dwmw2 Exp $ + * $Id: physmap.c,v 1.8 2000/11/27 08:50:22 dwmw2 Exp $ * * Normal mappings of chips in physical memory */ @@ -15,6 +15,7 @@ #define WINDOW_ADDR CONFIG_MTD_PHYSMAP_START #define WINDOW_SIZE CONFIG_MTD_PHYSMAP_LEN +#define BUSWIDTH CONFIG_MTD_PHYSMAP_BUSWIDTH static struct mtd_info *mymtd; @@ -59,32 +60,28 @@ void physmap_copy_to(struct map_info *map, unsigned long to, const void *from, s } struct map_info physmap_map = { - "Physically mapped flash", - WINDOW_SIZE, - 2, - physmap_read8, - physmap_read16, - physmap_read32, - physmap_copy_from, - physmap_write8, - physmap_write16, - physmap_write32, - physmap_copy_to, - 0, - 0 + name: "Physically mapped flash", + size: WINDOW_SIZE, + buswidth: BUSWIDTH, + read8: physmap_read8, + read16: physmap_read16, + read32: physmap_read32, + copy_from: physmap_copy_from, + write8: physmap_write8, + write16: physmap_write16, + write32: physmap_write32, + copy_to: physmap_copy_to }; -#if LINUX_VERSION_CODE < 0x20300 -#ifdef MODULE +#if LINUX_VERSION_CODE < 0x20212 && defined(MODULE) #define init_physmap init_module #define cleanup_physmap cleanup_module #endif -#endif int __init init_physmap(void) { printk(KERN_NOTICE "physmap flash device: %x at %x\n", WINDOW_SIZE, WINDOW_ADDR); - physmap_map.map_priv_1 = (unsigned long)ioremap(WINDOW_SIZE, WINDOW_ADDR); + physmap_map.map_priv_1 = (unsigned long)ioremap(WINDOW_ADDR, WINDOW_SIZE); if (!physmap_map.map_priv_1) { printk("Failed to ioremap\n"); @@ -99,6 +96,7 @@ int __init init_physmap(void) return 0; } + iounmap((void *)physmap_map.map_priv_1); return -ENXIO; } @@ -113,3 +111,7 @@ static void __exit cleanup_physmap(void) physmap_map.map_priv_1 = 0; } } + +module_init(init_physmap); +module_exit(cleanup_physmap); + diff --git a/drivers/mtd/pmc551.c b/drivers/mtd/pmc551.c index fdc80cbcf..ab9049356 100644 --- a/drivers/mtd/pmc551.c +++ b/drivers/mtd/pmc551.c @@ -1,17 +1,17 @@ /* - * $Id: pmc551.c,v 1.8 2000/07/14 07:53:31 dwmw2 Exp $ + * $Id: pmc551.c,v 1.11 2000/11/23 13:40:12 dwmw2 Exp $ * * PMC551 PCI Mezzanine Ram Device * * Author: - * Mark Ferrell + * Mark Ferrell <mferrell@mvista.com> * Copyright 1999,2000 Nortel Networks * - * License: + * License: * As part of this driver was derrived from the slram.c driver it falls * under the same license, which is GNU General Public License v2 * - * Description: + * Description: * This driver is intended to support the PMC551 PCI Ram device from * Ramix Inc. The PMC551 is a PMC Mezzanine module for cPCI embeded * systems. The device contains a single SROM that initally programs the @@ -24,6 +24,23 @@ * 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 usefull on * diskless systems in the embeded 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 relivant 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 relivant for compiled in drivers as + * compiled in drivers only init once. * * Credits: * Saeed Karamooz <saeed@ramix.com> of Ramix INC. for the initial @@ -31,7 +48,7 @@ * 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 written by David Hinds + * slram.o module in the MTD drivers package written by David Hinds * <dhinds@allegro.stanford.edu> 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 @@ -44,9 +61,9 @@ * * Modified driver to utilize a sliding apature instead of mapping all * memory into kernel space which turned out to be very wastefull. * * Located a bug in the SROM's initialization sequence that made the - * memory unussable, added a fix to code to touch up the DRAM some. + * memory unusable, added a fix to code to touch up the DRAM some. * - * Bugs/FIXME's: + * 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 @@ -73,6 +90,10 @@ #include <stdarg.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> @@ -347,14 +368,70 @@ static u32 fixup_pmc551 (struct pci_dev *dev) #ifdef CONFIG_MTD_PMC551_BUGFIX u32 dram_data; #endif - u32 size, dcmd; - u16 cmd, i; + 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 ); + pci_write_config_dword( dev, PCI_BASE_ADDRESS_0, cfg ); + size=~(size&PCI_BASE_ADDRESS_MEM_MASK)+1; +#else /* * Get the size of the memory by reading all the DRAM size values * and adding them up. @@ -363,7 +440,6 @@ static u32 fixup_pmc551 (struct pci_dev *dev) * row mux values. We fix them here, but this will break other * memory configurations. */ -#ifdef CONFIG_MTD_PMC551_BUGFIX 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); @@ -387,7 +463,6 @@ static u32 fixup_pmc551 (struct pci_dev *dev) 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); -#endif /* CONFIG_MTD_PMC551_BUGFIX */ /* * Oops .. something went wrong @@ -395,34 +470,14 @@ static u32 fixup_pmc551 (struct pci_dev *dev) if( (size &= PCI_BASE_ADDRESS_MEM_MASK) == 0) { return -ENODEV; } +#endif /* CONFIG_MTD_PMC551_BUGFIX */ - /* - * Set to be prefetchable - */ - pci_read_config_dword(dev, PCI_BASE_ADDRESS_0, &dcmd ); - dcmd |= 0x8; - - /* - * Put it back the way it was - */ - pci_write_config_dword(dev, PCI_BASE_ADDRESS_0, dcmd ); - pci_read_config_dword(dev, PCI_BASE_ADDRESS_0, &dcmd ); - - /* - * Some screen fun - */ - printk(KERN_NOTICE "pmc551: %dM (0x%x) of %sprefetchable memory at 0x%lx\n", - size/1024/1024, size, ((dcmd&0x8) == 0)?"non-":"", - PCI_BASE_ADDRESS(dev)&PCI_BASE_ADDRESS_MEM_MASK ); - - /* - * Turn on PCI memory and I/O bus access just for kicks - */ - pci_write_config_word( dev, PCI_COMMAND, - PCI_COMMAND_MEMORY | PCI_COMMAND_IO ); + if ((cfg&PCI_BASE_ADDRESS_SPACE) != PCI_BASE_ADDRESS_SPACE_MEMORY) { + return -ENODEV; + } /* - * Config DRAM + * Precharge Dram */ pci_write_config_word( dev, PMC551_SDRAM_MA, 0x0400 ); pci_write_config_word( dev, PMC551_SDRAM_CMD, 0x00bf ); @@ -431,11 +486,16 @@ static u32 fixup_pmc551 (struct pci_dev *dev) * Wait untill command has gone through * FIXME: register spinning issue */ - do { pci_read_config_word( dev, PMC551_SDRAM_CMD, &cmd ); + do { pci_read_config_word( dev, PMC551_SDRAM_CMD, &cmd ); + if(counter++ > 100)break; } while ( (PCI_COMMAND_IO) & cmd ); /* - * Must be held high for some duration of time to take effect?? + * 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); @@ -444,7 +504,9 @@ static u32 fixup_pmc551 (struct pci_dev *dev) * Make certain command has gone through * FIXME: register spinning issue */ - do { pci_read_config_word(dev, PMC551_SDRAM_CMD, &cmd); + counter=0; + do { pci_read_config_word(dev, PMC551_SDRAM_CMD, &cmd); + if(counter++ > 100)break; } while ( (PCI_COMMAND_IO) & cmd ); } @@ -455,7 +517,9 @@ static u32 fixup_pmc551 (struct pci_dev *dev) * Wait until command completes * FIXME: register spinning issue */ - do { pci_read_config_word ( dev, PMC551_SDRAM_CMD, &cmd); + 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); @@ -481,54 +545,97 @@ static u32 fixup_pmc551 (struct pci_dev *dev) 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 + * settup + */ + 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/1024:size/1024/1024, + (size<1024)?'B':(size<1048576)?'K':'M', + size, ((dcmd&(0x1<<3)) == 0)?"non-":"", + PCI_BASE_ADDRESS(dev)&PCI_BASE_ADDRESS_MEM_MASK ); /* * Check to see the state of the memory - * FIXME: perhaps hide some of this around an #ifdef DEBUG as - * it doesn't effect or enhance cards functionality */ - pci_read_config_dword( dev, 0x74, &dcmd ); - printk(KERN_NOTICE "pmc551: DRAM_BLK3 Flags: %s,%s\n", - ((0x2&dcmd) == 0)?"RW":"RO", - ((0x1&dcmd) == 0)?"Off":"On" ); - - pci_read_config_dword( dev, 0x70, &dcmd ); - printk(KERN_NOTICE "pmc551: DRAM_BLK2 Flags: %s,%s\n", - ((0x2&dcmd) == 0)?"RW":"RO", - ((0x1&dcmd) == 0)?"Off":"On" ); - - pci_read_config_dword( dev, 0x6C, &dcmd ); - printk(KERN_NOTICE "pmc551: DRAM_BLK1 Flags: %s,%s\n", - ((0x2&dcmd) == 0)?"RW":"RO", - ((0x1&dcmd) == 0)?"Off":"On" ); - - pci_read_config_dword( dev, 0x68, &dcmd ); - printk(KERN_NOTICE "pmc551: DRAM_BLK0 Flags: %s,%s\n", - ((0x2&dcmd) == 0)?"RW":"RO", - ((0x1&dcmd) == 0)?"Off":"On" ); - - pci_read_config_word( dev, 0x4, &cmd ); - printk( KERN_NOTICE "pmc551: Memory Access %s\n", - ((0x2&cmd) == 0)?"off":"on" ); - printk( KERN_NOTICE "pmc551: I/O Access %s\n", - ((0x1&cmd) == 0)?"off":"on" ); - - pci_read_config_word( dev, 0x6, &cmd ); - printk( KERN_NOTICE "pmc551: Devsel %s\n", + 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_NOTICE "pmc551: %sFast Back-to-Back\n", + 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 */ -#if LINUX_VERSION_CODE < 0x20300 +#if LINUX_VERSION_CODE < 0x20211 #ifdef MODULE #define init_pmc551 init_module #define cleanup_pmc551 cleanup_module @@ -536,11 +643,27 @@ static u32 fixup_pmc551 (struct pci_dev *dev) #define __exit #endif +#if defined(MODULE) +MODULE_AUTHOR("Mark Ferrell <mferrell@mvista.com>"); +MODULE_DESCRIPTION(PMC551_VERSION); +MODULE_PARM(msize, "i"); +MODULE_PARM_DESC(msize, "memory size, 6=32M, 7=64M, 8=128M, ect.. [32M-1024M]"); +MODULE_PARM(asize, "i"); +MODULE_PARM_DESC(asize, "aperture size, must be <= memsize [1M-1024M]"); +#endif +/* + * 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 /* * PMC551 Card Initialization */ -//static int __init init_pmc551(void) int __init init_pmc551(void) { struct pci_dev *PCI_Device = NULL; @@ -549,9 +672,23 @@ int __init init_pmc551(void) struct mtd_info *mtd; u32 length = 0; + if(msize) { + if (msize < 6 || msize > 11 ) { + printk(KERN_NOTICE "pmc551: Invalid memory size\n"); + return -ENODEV; + } + msize = (512*1024)<<msize; + } - printk(KERN_NOTICE "Ramix PMC551 PCI Mezzanine Ram Driver. (C) 1999,2000 Nortel Networks.\n"); - printk(KERN_INFO "$Id: pmc551.c,v 1.8 2000/07/14 07:53:31 dwmw2 Exp $\n"); + if(asize) { + if (asize < 1 || asize > 11 ) { + printk(KERN_NOTICE "pmc551: Invalid aperture size\n"); + return -ENODEV; + } + asize = (512*1024)<<asize; + } + + printk(KERN_INFO PMC551_VERSION); if(!pci_present()) { printk(KERN_NOTICE "pmc551: PCI not enabled.\n"); @@ -583,6 +720,10 @@ int __init init_pmc551(void) printk(KERN_NOTICE "pmc551: Cannot init SDRAM\n"); break; } + if(msize) { + length = msize; + printk(KERN_NOTICE "pmc551: Using specified memory size 0x%x\n", length); + } mtd = kmalloc(sizeof(struct mtd_info), GFP_KERNEL); if (!mtd) { @@ -602,13 +743,44 @@ int __init init_pmc551(void) mtd->priv = priv; priv->dev = PCI_Device; - priv->aperture_size = PMC551_APERTURE_SIZE; + if(asize) { + if(asize > length) { + asize=length; + printk(KERN_NOTICE "pmc551: reducing aperture size to fit memory [0x%x]\n",asize); + } else { + printk(KERN_NOTICE "pmc551: Using specified aperture size 0x%x\n", asize); + } + priv->aperture_size = asize; + } else { + priv->aperture_size = length; + } priv->start = ioremap((PCI_BASE_ADDRESS(PCI_Device) & PCI_BASE_ADDRESS_MEM_MASK), priv->aperture_size); - priv->mem_map0_base_val = (PMC551_APERTURE_VAL - | PMC551_PCI_MEM_MAP_REG_EN - | PMC551_PCI_MEM_MAP_ENABLE); + + /* + * Due to the dynamic nature of the code, we need to figure + * this out in order to stuff the register to set the proper + * aperture size. If you know of an easier way to do this then + * PLEASE help yourself. + * + * Not with bloody floating point, you don't. Consider yourself + * duly LARTed. dwmw2. + */ + { + u32 size; + u16 bits; + size = priv->aperture_size>>20; + for(bits=0;!(size&0x01)&&size>0;bits++,size=size>>1); + //size=((u32)((log10(priv->aperture_size)/.30103)-19)<<4); + priv->mem_map0_base_val = (PMC551_PCI_MEM_MAP_REG_EN + | PMC551_PCI_MEM_MAP_ENABLE + | size); +#ifdef CONFIG_MTD_PMC551_DEBUG + printk(KERN_NOTICE "pmc551: aperture set to %d[%d]\n", + size, size>>4); +#endif + } priv->curr_mem_map0_val = priv->mem_map0_base_val; pci_write_config_dword ( priv->dev, @@ -641,7 +813,10 @@ int __init init_pmc551(void) priv->aperture_size/1024/1024, priv->start, priv->start + priv->aperture_size); - printk(KERN_NOTICE "Total memory is %dM\n", length/1024/1024); + printk(KERN_NOTICE "Total memory is %d%c\n", + (length<1024)?length: + (length<1048576)?length/1024:length/1024/1024, + (length<1024)?'B':(length<1048576)?'K':'M'); priv->nextpmc551 = pmc551list; pmc551list = mtd; found++; @@ -651,8 +826,8 @@ int __init init_pmc551(void) printk(KERN_NOTICE "pmc551: not detected,\n"); return -ENODEV; } else { - return 0; printk(KERN_NOTICE "pmc551: %d pmc551 devices loaded\n", found); + return 0; } } @@ -669,7 +844,7 @@ static void __exit cleanup_pmc551(void) priv = (struct mypriv *)mtd->priv; pmc551list = priv->nextpmc551; - if(priv->start) + if(priv->start) iounmap(((struct mypriv *)mtd->priv)->start); kfree (mtd->priv); @@ -681,10 +856,7 @@ static void __exit cleanup_pmc551(void) printk(KERN_NOTICE "pmc551: %d pmc551 devices unloaded\n", found); } -#if LINUX_VERSION_CODE > 0x20300 +#if LINUX_VERSION_CODE >= 0x20211 module_init(init_pmc551); module_exit(cleanup_pmc551); #endif - - - diff --git a/drivers/mtd/pnc2000.c b/drivers/mtd/pnc2000.c index 4e4b052a7..a93dd3548 100644 --- a/drivers/mtd/pnc2000.c +++ b/drivers/mtd/pnc2000.c @@ -5,7 +5,7 @@ * * This code is GPL * - * $Id: pnc2000.c,v 1.1 2000/07/12 09:34:32 dwmw2 Exp $ + * $Id: pnc2000.c,v 1.4 2000/11/27 08:50:22 dwmw2 Exp $ */ #include <linux/module.h> @@ -14,6 +14,7 @@ #include <linux/mtd/mtd.h> #include <linux/mtd/map.h> +#include <linux/mtd/partitions.h> #define WINDOW_ADDR 0xbf000000 @@ -64,136 +65,60 @@ void pnc_copy_to(struct map_info *map, unsigned long to, const void *from, ssize } struct map_info pnc_map = { - "PNC-2000", - WINDOW_SIZE, - 4, - pnc_read8, - pnc_read16, - pnc_read32, - pnc_copy_from, - pnc_write8, - pnc_write16, - pnc_write32, - pnc_copy_to, - 0, - 0 + name: "PNC-2000", + size: WINDOW_SIZE, + buswidth: 4, + read8: pnc_read8, + read16: pnc_read16, + read32: pnc_read32, + copy_from: pnc_copy_from, + write8: pnc_write8, + write16: pnc_write16, + write32: pnc_write32, + copy_to: pnc_copy_to }; /* * MTD 'PARTITIONING' STUFF */ - -/* - * This is the _real_ MTD device for which all the others are just - * auto-relocating aliases. - */ -static struct mtd_info *mymtd; - -/* - * MTD methods which simply translate the effective address and pass through - * to the _real_ device. - */ - -static int pnc_mtd_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) -{ - return mymtd->read(mymtd, from + (unsigned long)mtd->priv, len, retlen, buf); -} - -static int pnc_mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) -{ - return mymtd->write(mymtd, to + (unsigned long)mtd->priv, len, retlen, buf); -} - -static int pnc_mtd_erase (struct mtd_info *mtd, struct erase_info *instr) -{ - instr->addr += (unsigned long)mtd->priv; - return mymtd->erase(mymtd, instr); -} - -static void pnc_mtd_sync (struct mtd_info *mtd) -{ - mymtd->sync(mymtd); -} - -static int pnc_mtd_suspend (struct mtd_info *mtd) -{ - return mymtd->suspend(mymtd); -} - -static void pnc_mtd_resume (struct mtd_info *mtd) -{ - mymtd->resume(mymtd); -} - - -static struct mtd_info pnc_mtds[3] = { /* boot, kernel, fs */ +static struct mtd_partition pnc_partitions[3] = { { - type: MTD_NORFLASH, - flags: MTD_CAP_NORFLASH, - size: 0x20000, - erasesize: 0x20000, name: "PNC-2000 boot firmware", - module: THIS_MODULE, - erase: pnc_mtd_erase, - read: pnc_mtd_read, - write: pnc_mtd_write, - suspend: pnc_mtd_suspend, - resume: pnc_mtd_resume, - sync: pnc_mtd_sync, - priv: (void *)0 + size: 0x20000, + offset: 0 }, { - type: MTD_NORFLASH, - flags: MTD_CAP_NORFLASH, - size: 0x1a0000, - erasesize: 0x20000, name: "PNC-2000 kernel", - module: THIS_MODULE, - erase: pnc_mtd_erase, - read: pnc_mtd_read, - write: pnc_mtd_write, - suspend: pnc_mtd_suspend, - resume: pnc_mtd_resume, - sync: pnc_mtd_sync, - priv: (void *)0x20000 + size: 0x1a0000, + offset: 0x20000 }, { - type: MTD_NORFLASH, - flags: MTD_CAP_NORFLASH, - size: 0x240000, - erasesize: 0x20000, name: "PNC-2000 filesystem", - module: THIS_MODULE, - erase: pnc_mtd_erase, - read: pnc_mtd_read, - write: pnc_mtd_write, - suspend: pnc_mtd_suspend, - resume: pnc_mtd_resume, - sync: pnc_mtd_sync, - priv: (void *)0x1c0000 + size: 0x240000, + offset: 0x1c0000 } }; -#if LINUX_VERSION_CODE < 0x20300 -#ifdef MODULE +/* + * This is the master MTD device for which all the others are just + * auto-relocating aliases. + */ +static struct mtd_info *mymtd; + +#if LINUX_VERSION_CODE < 0x20212 && defined(MODULE) #define init_pnc init_module #define cleanup_pnc cleanup_module #endif -#endif int __init init_pnc(void) { - printk(KERN_NOTICE "Photron PNC-2000 flash mapping: %x at %x\n", WINDOW_SIZE, WINDOW_ADDR); + printk(KERN_NOTICE "Photron PNC-2000 flash mapping: %x at %x\n", WINDOW_SIZE, WINDOW_ADDR); mymtd = do_cfi_probe(&pnc_map); if (mymtd) { mymtd->module = THIS_MODULE; - - add_mtd_device(&pnc_mtds[0]); /* boot */ - add_mtd_device(&pnc_mtds[1]); /* kernel */ - add_mtd_device(&pnc_mtds[2]); /* file system */ - return 0; + return add_mtd_partitions(mymtd, pnc_partitions, 3); } return -ENXIO; @@ -202,9 +127,10 @@ int __init init_pnc(void) static void __exit cleanup_pnc(void) { if (mymtd) { - del_mtd_device(&pnc_mtds[2]); - del_mtd_device(&pnc_mtds[1]); - del_mtd_device(&pnc_mtds[0]); + del_mtd_partitions(mymtd); map_destroy(mymtd); } } + +module_init(init_pnc); +module_exit(cleanup_pnc); diff --git a/drivers/mtd/rpxlite.c b/drivers/mtd/rpxlite.c index 783c863ac..51bcaf8a9 100644 --- a/drivers/mtd/rpxlite.c +++ b/drivers/mtd/rpxlite.c @@ -1,7 +1,7 @@ /* - * $Id: rpxlite.c,v 1.2 2000/07/04 12:16:26 dwmw2 Exp $ + * $Id: rpxlite.c,v 1.8 2000/12/09 22:00:31 dwmw2 Exp $ * - * Handle the strange 16-in-32-bit mapping on the RPXLite board + * Handle mapping of the flash on the RPX Lite and CLLF boards */ #include <linux/module.h> @@ -12,124 +12,74 @@ #include <linux/mtd/map.h> -#define WINDOW_ADDR 0x8000000 -#define WINDOW_SIZE 0x2000000 - -#define MAP_TO_ADR(x) ( ( ( x & ~1 ) << 1 ) | (x&1) ) +#define WINDOW_ADDR 0xfe000000 +#define WINDOW_SIZE 0x800000 static struct mtd_info *mymtd; __u8 rpxlite_read8(struct map_info *map, unsigned long ofs) { - return readb(map->map_priv_1 + MAP_TO_ADR(ofs)); + return readb(map->map_priv_1 * ofs); } __u16 rpxlite_read16(struct map_info *map, unsigned long ofs) { - return readw(map->map_priv_1 + MAP_TO_ADR(ofs)); + return readw(map->map_priv_1 + ofs); } __u32 rpxlite_read32(struct map_info *map, unsigned long ofs) { - return readl(map->map_priv_1 + MAP_TO_ADR(ofs)); + return readl(map->map_priv_1 + ofs); } void rpxlite_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len) { - if (from & 1) { - *(__u8 *)to = readb(map->map_priv_1 + MAP_TO_ADR(from)); - from++; - len--; - } - /* Can't do this if it's not aligned */ - if (!((unsigned long)to & 1)) { - unsigned long fromadr = MAP_TO_ADR(from); - - while (len > 1) { - *(__u16 *)to = readw(map->map_priv_1 + fromadr); - to += 2; - fromadr += 4; - from += 2; - len -= 2; - } - } - while(len) { - *(__u8 *)to = readb(map->map_priv_1 + MAP_TO_ADR(from)); - to++; - from++; - len--; - } + memcpy_fromio(to, (void *)(map->map_priv_1 + from), len); } void rpxlite_write8(struct map_info *map, __u8 d, unsigned long adr) { - writeb(d, map->map_priv_1 + MAP_TO_ADR(adr)); + writeb(d, map->map_priv_1 + adr); } void rpxlite_write16(struct map_info *map, __u16 d, unsigned long adr) { - writew(d, map->map_priv_1 + MAP_TO_ADR(adr)); + writew(d, map->map_priv_1 + adr); } void rpxlite_write32(struct map_info *map, __u32 d, unsigned long adr) { - writel(d, map->map_priv_1 + MAP_TO_ADR(adr)); + writel(d, map->map_priv_1 + adr); } void rpxlite_copy_to(struct map_info *map, unsigned long to, const void *from, ssize_t len) { - if (to & 1) { - writeb(*(__u8 *)from, map->map_priv_1 + MAP_TO_ADR(to)); - from++; - len--; - } - /* Can't do this if it's not aligned */ - if (!((unsigned long)from & 1)) { - unsigned long toadr = map->map_priv_1 + MAP_TO_ADR(to); - - while (len > 1) { - writew(*(__u16 *)from, toadr); - from += 2; - toadr += 4; - to += 2; - len -= 2; - } - } - while(len) { - writeb(*(__u8 *)from, map->map_priv_1 + MAP_TO_ADR(to)); - to++; - from++; - len--; - } + memcpy_toio((void *)(map->map_priv_1 + to), from, len); } struct map_info rpxlite_map = { - "RPXLITE", - WINDOW_SIZE, - 2, - rpxlite_read8, - rpxlite_read16, - rpxlite_read32, - rpxlite_copy_from, - rpxlite_write8, - rpxlite_write16, - rpxlite_write32, - rpxlite_copy_to, - 0, - 0 + name: "RPX", + size: WINDOW_SIZE, + buswidth: 4, + read8: rpxlite_read8, + read16: rpxlite_read16, + read32: rpxlite_read32, + copy_from: rpxlite_copy_from, + write8: rpxlite_write8, + write16: rpxlite_write16, + write32: rpxlite_write32, + copy_to: rpxlite_copy_to }; -#if LINUX_VERSION_CODE < 0x20300 -#ifdef MODULE +#if LINUX_VERSION_CODE < 0x20212 && defined(MODULE) #define init_rpxlite init_module #define cleanup_rpxlite cleanup_module #endif -#endif int __init init_rpxlite(void) { - printk(KERN_NOTICE "rpxlite flash device: %x at %x\n", WINDOW_SIZE, WINDOW_ADDR); - rpxlite_map.map_priv_1 = (unsigned long)ioremap(WINDOW_ADDR, WINDOW_SIZE * 2); + printk(KERN_NOTICE "RPX Lite or CLLF flash device: %x at %x\n", WINDOW_SIZE*4, WINDOW_ADDR); + rpxlite_map.map_priv_1 = (unsigned long)ioremap(WINDOW_ADDR, WINDOW_SIZE * 4); if (!rpxlite_map.map_priv_1) { printk("Failed to ioremap\n"); @@ -158,3 +108,6 @@ static void __exit cleanup_rpxlite(void) rpxlite_map.map_priv_1 = 0; } } + +module_init(init_rpxlite); +module_exit(cleanup_rpxlite); diff --git a/drivers/mtd/vmax301.c b/drivers/mtd/vmax301.c index 553beaad6..e694604e7 100644 --- a/drivers/mtd/vmax301.c +++ b/drivers/mtd/vmax301.c @@ -1,4 +1,4 @@ -// $Id: vmax301.c,v 1.13 2000/07/03 10:01:38 dwmw2 Exp $ +// $Id: vmax301.c,v 1.15 2000/11/27 08:50:22 dwmw2 Exp $ /* ###################################################################### Tempustech VMAX SBC301 MTD Driver. @@ -142,46 +142,43 @@ static void vmax301_copy_to(struct map_info *map, unsigned long to, const void * static struct map_info vmax_map[2] = { { - "VMAX301 Internal Flash", - 3*2*1024*1024, - 1, - vmax301_read8, - vmax301_read16, - vmax301_read32, - vmax301_copy_from, - vmax301_write8, - vmax301_write16, - vmax301_write32, - vmax301_copy_to, - WINDOW_START + WINDOW_LENGTH, - 0xFFFFFFFF + name: "VMAX301 Internal Flash", + size: 3*2*1024*1024, + buswidth: 1, + read8: vmax301_read8, + read16: vmax301_read16, + read32: vmax301_read32, + copy_from: vmax301_copy_from, + write8: vmax301_write8, + write16: vmax301_write16, + write32: vmax301_write32, + copy_to: vmax301_copy_to, + map_priv_1: WINDOW_START + WINDOW_LENGTH, + map_priv_2: 0xFFFFFFFF }, { - "VMAX301 Socket", - 0, - 1, - vmax301_read8, - vmax301_read16, - vmax301_read32, - vmax301_copy_from, - vmax301_write8, - vmax301_write16, - vmax301_write32, - vmax301_copy_to, - WINDOW_START + (3*WINDOW_LENGTH), - 0xFFFFFFFF + name: "VMAX301 Socket", + size: 0, + buswidth: 1, + read8: vmax301_read8, + read16: vmax301_read16, + read32: vmax301_read32, + copy_from: vmax301_copy_from, + write8: vmax301_write8, + write16: vmax301_write16, + write32: vmax301_write32, + copy_to: vmax301_copy_to, + map_priv_1: WINDOW_START + (3*WINDOW_LENGTH), + map_priv_2: 0xFFFFFFFF } }; static struct mtd_info *vmax_mtd[2] = {NULL, NULL}; -#if LINUX_VERSION_CODE < 0x20300 -#ifdef MODULE +#if LINUX_VERSION_CODE < 0x20212 && defined(MODULE) #define init_vmax301 init_module #define cleanup_vmax301 cleanup_module #endif -#define __exit -#endif static void __exit cleanup_vmax301(void) { @@ -237,7 +234,5 @@ int __init init_vmax301(void) return 0; } -#if LINUX_VERSION_CODE > 0x20300 module_init(init_vmax301); module_exit(cleanup_vmax301); -#endif |