patch-2.4.0-test3 linux/drivers/mtd/mapped.c

Next file: linux/drivers/mtd/mixmem.c
Previous file: linux/drivers/mtd/map_rom.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.0-test2/linux/drivers/mtd/mapped.c linux/drivers/mtd/mapped.c
@@ -0,0 +1,674 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                          /*{{{*/
+// $Id: mapped.c,v 1.8 2000/03/31 14:40:42 dwmw2 Exp $
+/* ######################################################################
+
+   Flash MTD Routines
+
+   These routine support IDing and manipulating flash. Currently the 
+   older JEDEC ID mechanism and a table is used for determining the
+   flash characterisitics, but it is trivial to add support for the
+   CFI specification:
+     http://www.pentium.com/design/flash/ in the technote section.
+   
+   ##################################################################### */
+									/*}}}*/
+#include <linux/mtd/mapped.h>
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <asm/io.h>
+#include <asm/delay.h>
+
+struct JEDECTable mtd_JEDEC_table[] = 
+  {{0x01AD,"AMD Am29F016",2*1024*1024,64*1024,MTD_CAP_NORFLASH},
+   {0x01D5,"AMD Am29F080",1*1024*1024,64*1024,MTD_CAP_NORFLASH},
+   {}};
+
+// flash_setup - Setup the mapped_mtd_info structure for normal flash	/*{{{*/
+// ---------------------------------------------------------------------
+/* There is a set of commands that flash manufactures follow for getting the
+   JEDEC id, erasing and writing. So long as your flash device supports 
+   getting the JEDEC ID in this (standard?) way it will be supported as flash,
+   otherwise it is converted to ROM. Upon completion the structure is 
+   registered with the MTD layer */
+int mtd_mapped_setup(struct mapped_mtd_info *map)
+{
+   DEBUG(1, "\n");
+   // Must define a page function to use the defaults!
+   if (map->page == 0)
+      return -1;
+   
+   if (map->jedec_sense == 0)
+      map->jedec_sense = flash_jedec;
+
+   if (map->jedec_sense(map) != 0)
+      return -1;
+
+   if (map->mtd.erase == 0 && map->mtd.type == MTD_NORFLASH)
+      map->mtd.erase = flash_erase;
+   if (map->mtd.write == 0)
+   {
+      if (map->mtd.type == MTD_NORFLASH)
+	 map->mtd.write = flash_write;
+      if (map->mtd.type == MTD_RAM)
+	 map->mtd.write = ram_write;      
+   }   
+   if (map->mtd.read == 0)
+      map->mtd.read = rom_read;   
+   
+   return add_mtd_device(&map->mtd);
+}
+									/*}}}*/
+// flash_remove - Remove the flash device from the MTD layer		/*{{{*/
+// ---------------------------------------------------------------------
+/* Free any memory allocated for the device here */
+int mtd_mapped_remove(struct mapped_mtd_info *map)
+{
+   return del_mtd_device(&map->mtd);
+}
+									/*}}}*/
+
+// checkparity - Checks a number for odd parity				/*{{{*/
+// ---------------------------------------------------------------------
+/* Helper for the JEDEC function, JEDEC numbers all have odd parity */
+static int checkparity(u_char C)
+{
+   u_char parity = 0;
+   while (C != 0)
+   {
+      parity ^= C & 1;
+      C >>= 1;
+   }
+   
+   return parity == 1;
+}
+									/*}}}*/
+// SetJedec - Set the jedec information for a chip			/*{{{*/
+// ---------------------------------------------------------------------
+/* We track the configuration of each chip separately in the chip list, 
+   each chip can have a different type and configuration to allow for 
+   maximum flexability. */
+void set_jedec(struct mapped_mtd_info *map,unsigned chip,unsigned char mfr,
+	      unsigned char id)
+{
+   unsigned long longID = (mfr << 8) + id;
+   unsigned int I;
+   
+   map->mtd.type = MTD_NORFLASH;
+   map->mfr = mfr;
+   map->id = id;
+
+   // Locate the chip in the jedec table
+   for (I = 0; mtd_JEDEC_table[I].jedec != 0; I++)
+   {      
+      if (mtd_JEDEC_table[I].jedec == longID)
+	 break;
+   }
+
+   if (mtd_JEDEC_table[I].jedec != longID || longID == 0)
+   {
+      printk("Unknown JEDEC number %x-%x, treating as ROM\n",map->mfr,
+	     map->id);
+      map->mtd.type = MTD_ROM;
+      return;
+   }
+      
+   // Setup the MTD from the JEDEC information
+//   map->mtd.size = mtd_JEDEC_table[I].size;
+//   map->mtd.erasesize = mtd_JEDEC_table[I].sectorsize;
+//   map->mtd.capabilities = mtd_JEDEC_table[I].capabilities;
+//   strncpy(map->mtd.part,mtd_JEDEC_table[I].name,sizeof(map->mtd.part)-1);
+   
+   map->chips[chip].jedec = longID;
+   map->chips[chip].size = mtd_JEDEC_table[I].size;
+   map->chips[chip].sectorsize = mtd_JEDEC_table[I].sectorsize;
+   map->chips[chip].capabilities = mtd_JEDEC_table[I].capabilities;
+   map->chips[chip].base = 0; 
+}
+									/*}}}*/
+// isjedec - Check if reading from the memory location gives jedec #s	/*{{{*/
+// ---------------------------------------------------------------------
+/* This is ment to be called on the flash window once it is in jedec mode */
+int isjedec(unsigned long base)
+{
+   // Test #1, JEDEC numbers are readable from 0x??00/0x??01
+   if (readb(base + 0) != readb(base + 0x100) || 
+       readb(base + 1) != readb(base + 0x101))
+      return 0;
+   
+   // Test #2 JEDEC numbers exhibit odd parity
+   if (checkparity(readb(base + 0)) == 0 || checkparity(readb(base + 1)) == 0)
+      return 0;
+   return 1;
+}
+									/*}}}*/
+// flash_jedec - JEDEC ID sensor					/*{{{*/
+// ---------------------------------------------------------------------
+/* The mysterious jedec flash probe sequence writes a specific pattern of
+   bytes to the flash. This should be general enough to work with any MTD
+   structure that may contain a flash chip, but note that it will corrupt
+   address 0x5555 on SRAM cards if the machine dies between the two 
+   critical operations. */
+int flash_jedec(struct mapped_mtd_info *map)
+{
+   unsigned I;
+   u_char OldVal;
+   unsigned long base;
+   unsigned long baseaddr = 0;
+   unsigned chip = 0;
+   unsigned count;
+   
+   // Who has a page size this small? :>
+   if (map->pagesize < 0x555)
+      return 1;
+   
+   base = map->page(map,0);
+   
+   // Wait for any write/erase operation to settle
+   OldVal = readb(base);
+   for (I = 0; OldVal != readb(base) && I < 10000; I++)
+      OldVal = readb(base);
+   
+   /* Check for sram by writing to it, the write also happens to be part 
+      of the flash reset sequence.. */
+   OldVal = readb(base + 0x555);
+   writeb(OldVal,base + 0x555);
+   writeb(0xF0,base + 0x555);
+   if (OldVal != readb(base + 0x555))
+   {
+      udelay(100);
+      
+      // Set it back and make sure..
+      writeb(OldVal,base + 0x555);
+      if (OldVal == readb(base + 0x555))
+      {
+	 map->mtd.type = MTD_RAM;
+	 return 0;
+      }
+      
+      writeb(0xF0,base + 0x555);
+   }
+   
+   // Probe for chips
+   while (chip < sizeof(map->chips)/sizeof(map->chips[0]))
+   {
+      // Already in jedec mode, we might be doing some address wrap around
+      if (chip != 0 && isjedec(base) != 0)
+      {
+	 /* Try to reset this page and check if that resets the first page
+	    to confirm */
+	 writeb(0xF0,base + 0x555);
+	 if (isjedec(base) != 0)
+	    break;
+	 base = map->page(map,0);
+	 if (isjedec(base) == 0)
+	    break;
+	 base = map->page(map,baseaddr/map->pagesize);
+      }
+      
+      // Send the sequence
+      writeb(0xAA,base + 0x555);
+      writeb(0x55,base + 0x2AA);
+      writeb(0x90,base + 0x555);
+
+      // Check the jedec number
+      if (isjedec(base) == 0)
+      {
+	 /* If this is the first chip it must be rom, otherwise it is the
+	    end of the flash region */
+	 if (chip == 0)
+	 {
+	    map->mtd.type = MTD_ROM;
+	    return 0;
+	 }
+	 break;
+      }      
+      
+      // Store the jdec info
+      set_jedec(map,chip,readb(base + 0),readb(base + 1));
+      map->chips[chip].base = baseaddr;
+      
+      // Jump to the next chip
+      baseaddr += map->chips[chip].size;
+      if (baseaddr/map->pagesize > map->maxsize)
+	 break;
+      base = map->page(map,baseaddr/map->pagesize);
+      if (base == 0)
+	 return -EIO;
+      
+      chip++;
+   }
+
+   // Reset all of the chips
+   map->mtd.size = 0;
+   baseaddr = 0;
+   map->mtd.flags = 0xFFFF;
+   for (I = 0; map->chips[I].jedec != 0; I++)
+   {
+      // Fill in the various MTD structures
+      map->mtd.size += map->chips[I].size;
+      if (map->mtd.erasesize < map->chips[I].sectorsize)
+	 map->mtd.erasesize = map->chips[I].sectorsize;
+      map->mtd.flags &= map->chips[I].capabilities;
+      
+      base = map->page(map,baseaddr/map->pagesize);
+      baseaddr += map->chips[chip].size;
+      writeb(0xF0,base + 0);  // Reset      
+   }   
+   
+   /* Generate a part name that includes the number of different chips and
+      other configuration information */
+   count = 1;
+   map->part[0] = 0;
+   for (I = 0; map->chips[I].jedec != 0; I++)
+   {
+      unsigned J;
+      if (map->chips[I+1].jedec == map->chips[I].jedec)
+      {
+	 count++;
+	 continue;
+      }
+      
+      // Locate the chip in the jedec table
+      for (J = 0; mtd_JEDEC_table[J].jedec != 0; J++)
+      {      
+	 if (mtd_JEDEC_table[J].jedec == map->chips[I].jedec)
+	    break;
+      }
+      
+      if (map->part[0] != 0)
+	 strcat(map->part,",");
+      
+      if (count != 1)
+	 sprintf(map->part+strlen(map->part),"%u*[%s]",count,
+		 mtd_JEDEC_table[J].name);
+      else
+	 sprintf(map->part+strlen(map->part),"%s",
+		 mtd_JEDEC_table[J].name);
+      count = 1;
+   }   
+   return 0;
+}
+									/*}}}*/
+
+// flash_failed - Print a console message about why the failure		/*{{{*/
+// ---------------------------------------------------------------------
+/* Pass the flags value that the flash return before it re-entered read 
+   mode. */
+static void flash_failed(unsigned char code)
+{
+   /* Bit 5 being high indicates that there was an internal device
+      failure, erasure time limits exceeded or something */
+   if ((code & (1 << 5)) != 0)
+   {
+      printk("mtd: Internal Flash failure\n");
+      return;
+   }
+   printk("mtd: Programming didn't take\n");
+}
+									/*}}}*/
+// flash_erase - Generic erase function					/*{{{*/
+// ---------------------------------------------------------------------
+/* This uses the erasure function described in the AMD Flash Handbook, 
+   it will work for flashes with a fixed sector size only. Flashes with
+   a selection of sector sizes (ie the AMD Am29F800B) will need a different
+   routine. This routine tries to parallize erasing multiple chips/sectors 
+   where possible */
+int flash_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+   unsigned long Time = 0;
+   unsigned long NoTime = 0;
+   unsigned long start = instr->addr, len = instr->len;
+   unsigned int I;
+   struct mapped_mtd_info *map = (struct mapped_mtd_info *)mtd;
+
+   // Verify the arguments..
+   if (start + len > map->mtd.size ||
+       (start % map->mtd.erasesize) != 0 ||
+       (len % map->mtd.erasesize) != 0 ||
+       (len/map->mtd.erasesize) == 0)
+      return -EINVAL;
+   
+   flash_chip_scan(map,start,len);
+
+   // Start the erase sequence on each chip
+   for (I = 0; map->chips[I].jedec != 0; I++)
+   {
+      unsigned long off;
+      struct flash_chip *chip = map->chips + I;
+      unsigned long base;
+      unsigned long flags;
+      
+      if (chip->length == 0)
+	 continue;
+      
+      if (page_jump(map,chip->base + chip->start,0x555,&base,0) != 0)
+	 return -EIO;
+      
+      // Send the erase setup code
+      writeb(0xF0,base + 0x555);
+      writeb(0xAA,base + 0x555);
+      writeb(0x55,base + 0x2AA);
+      writeb(0x80,base + 0x555);
+      writeb(0xAA,base + 0x555);
+      writeb(0x55,base + 0x2AA);
+
+      // Use chip erase if possible
+      if (chip->start == 0 && chip->length == chip->size)
+      {
+	 writeb(0x10,base+0x555);
+	 continue;
+      }
+            
+      /* Once we start selecting the erase sectors the delay between each 
+         command must not exceed 50us or it will immediately start erasing 
+         and ignore the other sectors */
+      save_flags(flags);
+      cli();
+      for (off = 0; off < chip->length; off += chip->sectorsize)
+      {
+	 if (page_jump(map,chip->base + chip->start + off,1,&base,0) != 0)
+	    return -EIO;
+	 
+	 // Check to make sure we didn't timeout
+	 writeb(0x30,base);
+	 if ((readb(base) & (1 << 3)) != 0)
+	 {
+	    printk("mtd: Ack! We timed out the erase timer!\n");
+	    return -EIO;
+	 }       	 
+      }
+      restore_flags(flags);
+   }   
+
+   /* We could split this into a timer routine and return early, performing
+      background erasure.. Maybe later if the need warrents */
+   
+   /* Poll the flash for erasure completion, specs say this can take as long
+      as 480 seconds to do all the sectors (for a 2 meg flash). 
+      Erasure time is dependant on chip age, temp and wear.. */
+   Time = 0;
+   NoTime = 0;
+   for (I = 0; map->chips[I].jedec != 0; I++)
+   {
+      struct flash_chip *chip = map->chips + I;
+      unsigned long base;
+      unsigned long off = 0;
+      if (chip->length == 0)
+	 continue;
+      
+      if (page_jump(map,chip->base + chip->start,1,&base,0) != 0)
+	 return -EIO;
+      
+      while (1)
+      {
+	 unsigned char Last[4];
+	 unsigned long Count = 0;
+	 
+	 /* During erase bit 7 is held low and bit 6 toggles, we watch this,
+	    should it stop toggling or go high then the erase is completed,
+  	    or this is not really flash ;> */
+	 Last[0] = readb(base);
+	 Last[1] = readb(base);
+	 Last[2] = readb(base);
+	 for (Count = 3; (Last[(Count - 1) % 4] & (1 << 7)) == 0 && 
+	      Last[(Count - 1) % 4] != Last[(Count - 2) % 4]; Count++)
+	 {
+	    if (NoTime == 0)
+	       Time += HZ/10 - schedule_timeout(HZ/10);
+	    NoTime = 0;
+	    
+	    Last[Count % 4] = readb(base);
+	 
+	    // Count time, max of 15s per sector (according to AMD)
+	    if (Time > 15*len/mtd->erasesize*HZ)
+	    {
+	       printk("mtd: Flash Erase Timed out\n");
+	       return -EIO;
+	    }	    
+	 }
+	 
+	 if (Last[(Count - 1) % 4] == Last[(Count - 2) % 4])
+	 {
+	    flash_failed(Last[(Count - 3) % 4]);
+	    return -EIO;
+	 }
+	 
+	 // Skip to the next chip if we used chip erase
+	 if (chip->length == chip->size)
+	    off = chip->size;
+	 else
+	    off += chip->sectorsize;
+	 
+	 if (off >= chip->length)
+	    break;
+	 if (page_jump(map,chip->base + chip->start + off,1,&base,0) != 0)
+	    return -EIO;	 
+	 NoTime = 1;
+      }      
+   }
+       	 
+   // Paranoid verify of erasure
+   {
+      unsigned long base;
+      unsigned long buflen;
+      while (len > 0)
+      {
+	 unsigned long step;
+	 
+	 if (page_jump(map,start,len,&base,&buflen) != 0)
+	    return -EIO;
+	 start += buflen;
+	 len -= buflen;
+	 step = buflen/128;
+	 for (;buflen != 0; buflen -= step)
+	 {
+	    if (readb(base+buflen-1) != 0xFF)
+	    {
+	       printk("mtd: Flash Erase didn't take %lu %lu %lu\n",buflen,len,start);
+	       return -EIO;
+	    }
+	 }	 
+      }      
+   }   
+   
+   return 0;
+}
+#if 1
+									/*}}}*/
+// flash_write - Generic writing function				/*{{{*/
+// ---------------------------------------------------------------------
+/* This could do parallel writes on multiple chips but doesnt, memory 
+   constraints make that infeasable. This should work with any sort of 
+   linear flash that is not interleved */
+extern int flash_write(struct mtd_info *mtd, loff_t start, size_t len,
+		       size_t *retlen, const u_char *buf)
+{
+   struct mapped_mtd_info *map = (struct mapped_mtd_info *)mtd;
+   unsigned long base;
+   unsigned long off;
+   DEBUG(1,"\n");
+   if (start + len > mtd->size)
+      return -EIO;
+   
+   while (len != 0)
+   {
+      // Compute the page offset and reposition
+      base = map->page(map,(u_long)start/map->pagesize);
+      off = (u_long)start %  map->pagesize;
+
+      // Loop over this page
+      for (; off != map->pagesize && len != 0; start++, len--, off++,buf++)
+      {
+	 unsigned char oldbyte = readb(base+off);
+	 unsigned char Last[4];
+	 unsigned long Count = 0;
+
+	 if (oldbyte == *buf)
+	    continue;
+	 if (((~oldbyte) & *buf) != 0)
+	    printk("mtd: warn: Trying to set a 0 to a 1\n");
+	     
+	 // Write
+	 writeb(0xAA,base + 0x555);
+	 writeb(0x55,base + 0x2AA);
+	 writeb(0xA0,base + 0x555);
+	 writeb(*buf,base + off);
+	 Last[0] = readb(base + off);
+	 Last[1] = readb(base + off);
+	 Last[2] = readb(base + off);
+	 
+	 /* Wait for the flash to finish the operation. We store the last 4
+	    status bytes that have been retrieved so we can determine why
+	    it failed. The toggle bits keep toggling when there is a 
+	    failure */
+	 for (Count = 3; Last[(Count - 1) % 4] != Last[(Count - 2) % 4] &&
+	      Count < 10000; Count++)
+	    Last[Count % 4] = readb(base + off);
+	 if (Last[(Count - 1) % 4] != *buf)
+	 {
+	    flash_failed(Last[(Count - 3) % 4]);
+	    return -EIO;
+	 }	 
+      }
+   }
+   *retlen = len;
+   return 0;
+}
+#endif
+
+// ram_write - Generic writing function	for ram				/*{{{*/
+// ---------------------------------------------------------------------
+/* */
+extern int ram_write(struct mtd_info *mtd, loff_t start, size_t len,
+		       size_t *retlen, const u_char *buf)
+{
+   struct mapped_mtd_info *map = (struct mapped_mtd_info *)mtd;
+   unsigned long base;
+   size_t origlen = len;
+   unsigned long buflen;
+   DEBUG(1,"\n");
+   if (start + len > mtd->size)
+      return -EIO;
+   
+   while (len != 0)
+   {
+      // Reposition..
+      if (page_jump(map,start,len,&base,&buflen) != 0)
+	 return -EIO;
+      
+      // Copy
+      memcpy_toio(base,buf,buflen);
+      len -= buflen;
+      start += buflen;
+   }
+   *retlen = origlen;
+   return 0;
+}
+
+// rom_read - Read handler for any sort of device			/*{{{*/
+// ---------------------------------------------------------------------
+/* This is a generic read function that should work with any device in the
+   mapped region. */
+extern int rom_read(struct mtd_info *mtd, loff_t start, size_t len,
+		    size_t *retlen, u_char *buf)
+{
+   struct mapped_mtd_info *map = (struct mapped_mtd_info *)mtd;
+   size_t origlen = len;
+   unsigned long base;
+   unsigned long buflen;
+
+   printk("Rom_Read\n");
+   if (start + len > mtd->size)
+      return -EIO;
+   
+   while (len != 0)
+   {
+      // Reposition..
+      if (page_jump(map,start,len,&base,&buflen) != 0)
+	 return -EIO;
+      
+      // Copy
+      memcpy_fromio(buf,base,buflen);
+      len -= buflen;
+      start += buflen;
+   }
+   *retlen = origlen;
+   return 0;
+}
+
+// page_jump - Move the window and return the buffer			/*{{{*/
+// ---------------------------------------------------------------------
+/* Unlike the page function this returns a buffer and length adjusted for
+   the page dimensions and the reading offset into the page, simplifies
+   many of the other routines */
+int page_jump(struct mapped_mtd_info *map,unsigned long start,
+	      unsigned long len,unsigned long *base,
+	      unsigned long *retlen)
+{
+   DEBUG(1,"Page Jump\n");
+   if (start > map->mtd.size || start + len > map->mtd.size)
+      return -EINVAL;
+      
+   *base = map->page(map,start/map->pagesize);
+   if (*base == 0)
+      return -EIO;
+
+   *base += start % map->pagesize;
+
+   // If retlen is 0 that mean the caller requires len bytes, no quibbling.
+   if (retlen == 0)
+   {
+      if (len > map->pagesize  - (start % map->pagesize))
+	 return -EIO;
+      return 0;
+   }
+   
+   // Compute the buffer paramaters and return
+   if (len > map->pagesize - (start % map->pagesize))
+      *retlen = map->pagesize - (start % map->pagesize);
+   else
+      *retlen = len;
+   return 0;
+}
+									/*}}}*/
+// flash_chip_scan - Intersect a region with the flash chip structure	/*{{{*/
+// ---------------------------------------------------------------------
+/* This is used to enhance the speed of the erase routine,
+   when things are being done to multiple chips it is possible to
+   parallize the operations, particularly full memory erases of multi
+   chip memories benifit */
+
+void flash_chip_scan(struct mapped_mtd_info *map,unsigned long start,
+		     unsigned long len)
+{
+   unsigned int I = 0;
+
+   DEBUG(1,"\n");
+   // Zero the records
+   for (I = 0; map->chips[I].jedec != 0; I++)
+      map->chips[I].start = map->chips[I].length = 0;
+   
+   // Intesect our region with the chip structures
+   for (I = 0; map->chips[I].jedec != 0 && len != 0; I++)
+   {
+      // Havent found the start yet
+      if (start >= map->chips[I].base + map->chips[I].size)
+	 continue;
+
+      // Store the portion of this chip that is being effected
+      map->chips[I].start = start - map->chips[I].base;
+      if (len <= map->chips[I].size - map->chips[I].start)
+	 map->chips[I].length = len;
+      else
+	 map->chips[I].length = map->chips[I].size - map->chips[I].start;
+      len -= map->chips[I].length;
+      start = map->chips[I].base + map->chips[I].size;
+   }
+}
+									/*}}}*/
+

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)