add initial support for the crisarchitecture used on foxboards to openwrt
[openwrt/svn-archive/archive.git] / target / linux / etrax-2.6 / image / e100boot / src / cbl / src / flash.c
diff --git a/target/linux/etrax-2.6/image/e100boot/src/cbl/src/flash.c b/target/linux/etrax-2.6/image/e100boot/src/cbl/src/flash.c
new file mode 100644 (file)
index 0000000..892e98a
--- /dev/null
@@ -0,0 +1,1125 @@
+/* $Id: flash.c,v 1.39 2004/04/20 07:57:57 jonashg Exp $
+ *
+ * Stolen from the eLinux kernel and stripped down.
+ *
+ * HISTORY:
+ *
+ * $Log: flash.c,v $
+ * Revision 1.39  2004/04/20 07:57:57  jonashg
+ * Clear flash_status fields to make it possible to flash several images
+ * sequentially.
+ *
+ * Revision 1.38  2003/12/16 09:04:07  magnusmn
+ * Removed FLASHFILL command
+ *
+ * Revision 1.37  2003/12/16 08:49:01  magnusmn
+ * Merging change_branch--fast_flash
+ *
+ * Revision 1.36.2.6  2003/12/15 17:21:27  magnusmn
+ * Reset counter when continuing with operations the next sector.
+ *
+ * Revision 1.36.2.5  2003/12/15 11:35:57  magnusmn
+ * Bail out if we try to erase the same sector more that 10 times
+ *
+ * Revision 1.36.2.4  2003/12/12 12:07:10  magnusmn
+ * FIX for ST M29W320DT
+ * Some chip need a reset to bring them back to read mode again.
+ *
+ * Revision 1.36.2.3  2003/11/10 16:38:04  orjanf
+ * Unified Erasing/Writing messages
+ *
+ * Revision 1.36.2.2  2003/11/10 15:52:34  magnusmn
+ * More info on a sector basis
+ *
+ * Revision 1.36.2.1  2003/11/07 16:23:20  magnusmn
+ * o Only erase a flash sector if we need to, that is if the source content isn't already is in place.
+ * o Don't erase a flash sector that already contain ones.
+ * o Don't write ones to a (d)word that already contain ones.
+ * o If there are two flashes, switch flash after an erase operation is started on one of them.
+ * o Flash fill doesn't work yet.
+ * o No timeout implemented, we will continue to erase/program until we succeed.
+ * o Interleave not tested.
+ *
+ * Revision 1.36  2003/10/16 17:08:51  jonashg
+ * Bugfix: reversed CFI-tables wasn't handled correctly since regions support was
+ * merged.
+ *
+ * Revision 1.35  2003/10/14 13:43:41  pkj
+ * Fixed compiler warnings.
+ *
+ * Revision 1.34  2003/10/14 10:48:13  magnusmn
+ * No need to write ones to a (d)word where there already are ones. This will save time during flash programming.
+ *
+ * Revision 1.33  2003/10/10 11:46:25  jonashg
+ * Merged change_branch--regions_support.
+ *
+ * Revision 1.32.2.3  2003/10/10 09:38:13  jonashg
+ * Corrected calculation of current region and sector before erase.
+ *
+ * Revision 1.32.2.2  2003/10/09 16:31:26  jonashg
+ * Regions support in JEDEC probe.
+ *
+ * Revision 1.32.2.1  2003/09/19 15:28:22  jonashg
+ * Support for unusual region layouts. It only works for CFI compliant chips (yet).
+ *
+ * Revision 1.32  2002/12/13 15:55:54  jonashg
+ * Fix for ST M29W160ET. It seems to need a reset before erase (even though the
+ * probe functions did reset it).
+ *
+ * Revision 1.31  2002/07/01 14:37:25  pkj
+ * Merged with the ASIC version of e100boot. Main difference is that
+ * information about the executed commands are sent back to e100boot
+ * instead of being sent to the debug port. This means there is no
+ * longer any need to use different boot loaders for different
+ * debug ports.
+ *
+ * Revision 1.30  2002/06/26 13:28:29  pkj
+ * flash_write() can now be used to erase an area (by specifying
+ * source as NULL), and to fill an area with the first udword of
+ * source by setting do_fill to TRUE).
+ *
+ * Revision 1.29  2002/06/26 13:19:37  pkj
+ * * flash_write() now returns a status code.
+ * * timeout is now decremented correctly in flash_write_part() to
+ *   actually be able to trigger the timeout message.
+ * * Fixed all compiler warnings.
+ *
+ * Revision 1.28  2002/06/20 12:58:18  pkj
+ * Changed svinto_boot.h to e100boot.h
+ *
+ * Revision 1.27  2002/06/19 14:00:29  pkj
+ * * Broke out the probing of the flash chips from  flash_write()
+ *   into flash_probe_chips().
+ * * flash_probe_chips() is not limited to two chips or that the
+ *   first chip exists.
+ *
+ * Revision 1.26  2002/02/21 14:37:52  jonashg
+ * Optimized away my sanity. It's back now I think.
+ *
+ * Revision 1.25  2002/02/21 14:28:24  jonashg
+ * Added support for Atmel AT49?V16?T (had to optimize a bit to make room).
+ *
+ * Revision 1.24  2002/01/31 14:36:14  jonashg
+ * * Added support for Atmel AT49[BL]V16[01] (the chip used in the ETRAX MCM).
+ * * Replaced concurrent sector erase with sequential (we have found three
+ *   different chips that cannot erase multiple sectors at the same time,
+ *   one of the is the chip in the MCM). I haven't noticed any performance
+ *   loss on chips (CFI and non-CFI) that can erase all sectors at the same
+ *   time either (maybe they don't really erase them at the same time in
+ *   hardware).
+ * * Added check for manufacturer id as well as device id (should have been
+ *   done a long time ago).
+ *
+ * Revision 1.23  2001/11/21 15:52:44  jonashg
+ * Almost readable.
+ *
+ * Revision 1.22  2001/11/21 15:24:38  jonashg
+ * Increased readability and decreased size some 40bytes.
+ *
+ * Revision 1.21  2001/11/20 13:40:12  starvik
+ * Corrected handling for CFI capable bottom boot flashes
+ * Shorted some strings to make more space available
+ *
+ * Revision 1.20  2001/08/08 17:51:28  pkj
+ * Made it possible to flash at a start offset other than zero when
+ * there are more than one physical flash chip available. Previously
+ * it always started flashing from the start of the first flash if
+ * there were more than one, even though the start offset was set to
+ * something else...
+ *
+ * Revision 1.19  2001/06/19 14:51:17  jonashg
+ * Added support for non-CFI flash Toshiba TC58FVT800.
+ *
+ * Revision 1.18  2001/04/05 06:32:39  starvik
+ * Works with flashes with multiple banks
+ *
+ * Revision 1.17  2001/03/06 15:21:16  jonashg
+ * More output to user.
+ *
+ * Revision 1.16  2001/03/06 14:11:16  jonashg
+ * * Switch to second device correctly when flashing images that extend past the
+ *   first device.
+ * * Only enter autoselect mode once saves a few bytes (not needed before reading
+ *   device id, since it was done before reading manufacturer id).
+ * * A few unnecessary resets removed to save another few bytes.
+ *
+ * Revision 1.15  2001/02/28 14:52:43  jonashg
+ * * Reverted to old sector erase sequence (that was correct).
+ * * A bit of executable size optimization (a few hundred bytes).
+ * * Cleanup.
+ *
+ * Revision 1.14  2001/02/27 14:18:59  jonashg
+ * * Write full erase command sequence to all sectors that should be erased.
+ * * Write 16bit erase command to non-interleaved chips.
+ *
+ * Revision 1.13  2001/02/23 11:03:41  jonashg
+ * Added support for 2 x 16Mb flashes (32-bits buswidth).
+ * The CFI probe does not detect two parallel flash devices, but the normal
+ * probe does (it should be easy to add that in the CFI-probe, but I didn't
+ * have any hardware to try it on and the size of the executable is getting
+ * pretty close to the size of the ETRAX cache).
+ *
+ * Revision 1.12  2001/02/12 13:59:00  jonashg
+ * Bugfix: pointer arithmetics made bootsector calculation go wrong.
+ *
+ * Revision 1.11  2000/11/10 08:02:23  starvik
+ * Added CFI support
+ *
+ * Revision 1.10  2000/10/26 13:47:32  johana
+ * Added support for Fujitsu flash 16MBit (2MByte) MBM29LV160BE and MBM29LV160TE.
+ * NOT VERIFIED YET!
+ *
+ * Revision 1.9  2000/06/28 13:02:50  bjornw
+ * * Added support for SST39LF800 and SST39LF160 flashes
+ * * Fixed some indentation issues
+ *
+ * Revision 1.8  2000/06/13 11:51:11  starvik
+ * Support for two flashes. Second flash is erased and programmed if program
+ * is larger than first flash.
+ *
+ * Revision 1.7  2000/04/13 16:06:15  macce
+ * See if flash is empty before erasing it. Might save some production time.
+ *
+ * Revision 1.6  2000/01/27 17:52:07  bjornw
+ * * Added Toshiba flashes
+ * * Added proper bootblock erase for the different flashes
+ *   (this caused the verify errors when trying to do ./flashitall before)
+ *
+ * Revision 1.5  2000/01/20 11:41:28  finn
+ * Improved the verify error printouts in flash_write.
+ *
+ * Revision 1.4  1999/12/21 19:32:53  bjornw
+ * Dont choke on full chip erases even though we dont implement it efficiently.
+ *
+ * Revision 1.3  1999/11/12 01:30:04  bjornw
+ * Added wait for busy to be ready. Removed some warnings.
+ *
+ * Revision 1.2  1999/10/27 07:42:42  johana
+ * Added support for ST M29W800T flash used in 5600
+ *
+ * Revision 1.1  1999/10/27 01:37:12  bjornw
+ * Wrote routines to erase and flash data into a flash ROM.
+ *
+ */
+
+#include "e100boot.h"
+
+//#define DEBUG
+
+#ifdef DEBUG
+#define FDEBUG(x) x
+#else
+#define FDEBUG(x)
+#endif
+
+/* Try turning of some of these if you run into space problems. */
+#define CFI_PROBE
+#define JEDEC_PROBE
+#define INTERLEAVE
+
+#define TYPE_X16       (16 / 8)
+
+#define nop() __asm__("nop")
+
+#define safe_printk send_string
+
+static char *message_bottom_boot_8 = "8Mb BB";
+static char *message_top_boot_8 = "8Mb TB";
+static char *message_bottom_boot_16 = "16Mb BB";
+static char *message_top_boot_16 = "16Mb TB";
+static char *message_top_boot_32 = "32Mb TB";
+
+enum {
+       /* Addresses */
+       ADDR_UNLOCK_1                   = 0x0555,
+       ADDR_UNLOCK_2                   = 0x02AA,
+       ADDR_MANUFACTURER               = 0x0000,
+       ADDR_DEVICE_ID                  = 0x0001,
+       ADDR_CFI_QUERY                  = 0x0055,
+
+       /* Commands */
+       CMD_UNLOCK_DATA_1               = 0x00AA,
+       CMD_UNLOCK_DATA_2               = 0x0055,
+       CMD_MANUFACTURER_UNLOCK_DATA    = 0x0090,
+       CMD_PROGRAM_UNLOCK_DATA         = 0x00A0,
+       CMD_RESET_DATA                  = 0x00F0,
+       CMD_SECTOR_ERASE_UNLOCK_DATA_1  = 0x0080,
+       CMD_SECTOR_ERASE_UNLOCK_DATA_2  = 0x0030,
+       CMD_CFI_QUERY_DATA              = 0x0098,
+
+       /* Offsets */
+       OFFSET_CFI_ID                   = 0x10,
+       OFFSET_CFI_SIZE                 = 0x27,
+       OFFSET_CFI_BLOCK_COUNT          = 0x2C,
+       OFFSET_CFI_BLOCK                = 0x2D,
+
+       /* Manufacturers */
+       MANUFACTURER_AMD                = 0x01,
+       MANUFACTURER_ATMEL              = 0x1F,
+       MANUFACTURER_FUJITSU            = 0x04,
+       MANUFACTURER_SST                = 0xBF,
+       MANUFACTURER_ST                 = 0x20,
+       MANUFACTURER_TOSHIBA            = 0x98,
+
+
+       /* To save precious space we store mfr and dev id together */
+
+       /* AMD devices */
+       AM29F800BB                      = 0x00012258,
+       AM29F800BT                      = 0x000122D6,
+       AM29LV800BB                     = 0x0001225B,
+       AM29LV800BT                     = 0x000122DA,
+       AM29LV160BT                     = 0x000122C4,
+
+       /* Atmel devices */
+       AT49xV16x                       = 0x001F00C0,
+       AT49xV16xT                      = 0x001F00C2,
+       AT49BV32xAT                     = 0x001F00C9,
+
+       /* Fujitsu devices */
+       MBM29LV160TE                    = 0x000422C4,
+       MBM29LV160BE                    = 0x00042249,
+
+       /* SST devices */
+       SST39LF800                      = 0x00BF2781,
+       SST39LF160                      = 0x00BF2782,
+
+       /* ST devices */
+       M29W800T                        = 0x002000D7, /* Used in 5600, similar
+                                                      * to AM29LV800, but no
+                                                      * unlock bypass
+                                                      */
+       /* Toshiba devices */
+       TC58FVT160                      = 0x009800C2,
+       TC58FVB160                      = 0x00980043,
+       TC58FVT800                      = 0x0098004F,
+
+       /* Toggle bit mask */
+       D6_MASK                         = 0x40
+};
+
+struct region {
+       unsigned long offset;
+       unsigned int sector_size;
+       unsigned int numsectors;
+};
+
+#define MAXREGIONS 8
+
+struct chip {
+       volatile unsigned char *base;
+#ifdef INTERLEAVE
+       byte interleave;
+       byte buswidth;
+#endif
+       unsigned int size;
+       unsigned short numregions;
+       struct region regions[MAXREGIONS];
+};
+
+/* Allocate flash structures and initialize base. */
+static struct chip chips[2] = {
+       { (unsigned char *)0x80000000,
+#ifdef INTERLEAVE
+               0, 0,
+#endif
+               0, 0, { } },
+       { (unsigned char *)0x84000000,
+#ifdef INTERLEAVE
+               0, 0,
+#endif
+               0, 0, { } }
+};
+
+
+
+static unsigned int
+wide_read(struct chip *flash, unsigned long offset)
+{
+#ifdef INTERLEAVE
+       switch (flash->buswidth) {
+       case 2:
+#endif
+               return *((uword *)(flash->base + offset));
+
+#ifdef INTERLEAVE
+       case 4:
+               return *((udword *)(flash->base + offset));
+       }
+
+       return 0;
+#endif
+}
+
+static int
+wide_write_chunk(struct chip *flash, unsigned long offset, const void *chunk)
+{
+#ifdef INTERLEAVE
+       switch (flash->buswidth) {
+       case 2:
+#endif
+               *((uword *)(flash->base + offset)) = *((uword *)chunk);
+               return 2;
+
+#ifdef INTERLEAVE
+       case 4:
+               *((udword *)(flash->base + offset)) = *((udword *)chunk);
+               return 4;
+       }
+
+       return 0;
+#endif
+}
+
+static void
+wide_cmd(struct chip *flash, udword cmd, unsigned long offset)
+{
+#ifdef INTERLEAVE
+       if (flash->interleave == 1) {
+#endif
+               offset <<= 1;
+#ifdef INTERLEAVE
+       } else if (flash->interleave == 2) {
+               cmd |= (cmd << 16);
+               offset <<= 2;
+       } else {
+               safe_printk("Unsupported interleave!\n");
+               return;
+       }
+#endif
+
+       wide_write_chunk(flash, offset, &cmd);
+}
+
+static void
+flash_unlock(struct chip *flash)
+{
+       wide_cmd(flash, CMD_UNLOCK_DATA_1, ADDR_UNLOCK_1);
+       wide_cmd(flash, CMD_UNLOCK_DATA_2, ADDR_UNLOCK_2);
+}
+
+static int
+flash_is_busy(struct chip *flash, unsigned long offset)
+{
+#ifdef INTERLEAVE
+       if (flash->interleave == 2) {
+               udword read1, read2;
+
+               read1 = wide_read(flash, offset);
+               read2 = wide_read(flash, offset);
+               return (((read1 >> 16) & D6_MASK) !=
+                       ((read2 >> 16) & D6_MASK)) ||
+                      (((read1 & 0xffff) & D6_MASK) !=
+                       ((read2 & 0xffff) & D6_MASK));
+       }
+#endif
+
+       return ((wide_read(flash, offset) & D6_MASK) !=
+               (wide_read(flash, offset) & D6_MASK));
+}
+
+
+
+#ifdef CFI_PROBE
+static int
+try_cfi(struct chip *flash)
+{
+       int offset_shift = 1;
+
+#ifdef INTERLEAVE
+       if (flash->interleave == 2) {
+               offset_shift = 2;
+       }
+#endif
+
+       /* Enter CFI mode */
+       wide_cmd(flash, CMD_CFI_QUERY_DATA, ADDR_CFI_QUERY);
+
+       /* Check if flash responds correctly */
+       if ((byte)wide_read(flash, (OFFSET_CFI_ID+0) << offset_shift) == 'Q' &&
+           (byte)wide_read(flash, (OFFSET_CFI_ID+1) << offset_shift) == 'R' &&
+           (byte)wide_read(flash, (OFFSET_CFI_ID+2) << offset_shift) == 'Y') {
+               int block;               /* Current block */
+               int block_count;         /* Number of blocks */
+               unsigned int offset = 0; /* Offset into flash */
+               int reverse = 0;         /* Reverse block table */
+               int primary;             /* Offset to vendor specific table */
+
+               safe_printk("Found 1 x CFI at ");
+               send_hex((udword)flash->base, NL);
+
+               flash->size =
+                       1 << wide_read(flash, OFFSET_CFI_SIZE << offset_shift);
+
+               /* CFI stores flash organization in blocks. Each block contains
+                * a number of sectors with the same size
+                */
+               block_count = wide_read(flash, OFFSET_CFI_BLOCK_COUNT <<
+                                              offset_shift);
+
+               /* Check if table is reversed */
+               primary = wide_read(flash, (OFFSET_CFI_ID+5) << offset_shift);
+               /* For CFI version 1.0 we don't know. Assume that id & 0x80 */
+               /* indicates top boot */
+               if ((byte)wide_read(flash, (primary+4) << offset_shift) == 0x30)
+               {
+                       /* read device id */
+                       wide_cmd(flash, CMD_RESET_DATA, ADDR_UNLOCK_1);
+                       flash_unlock(flash);
+                       wide_cmd(flash, CMD_MANUFACTURER_UNLOCK_DATA,
+                                ADDR_UNLOCK_1);
+                       reverse = wide_read(flash, ADDR_DEVICE_ID * TYPE_X16
+#ifdef INTERLEAVE
+                                           * flash->interleave
+#endif
+                                          ) & 0x80;
+                       wide_cmd(flash, CMD_CFI_QUERY_DATA, ADDR_CFI_QUERY);
+               } else {
+                       reverse = ((byte)wide_read(flash,
+                                       (primary+15) << offset_shift) == 3);
+               }
+
+               flash->numregions = block_count;
+               if (block_count > MAXREGIONS) {
+                       safe_printk("Too many regions on chip!\n");
+                       return 0;
+               }
+
+               /* Blocks are stored backwards compared to flash organization */
+               for (block = reverse ? block_count - 1 : 0;
+                    reverse ? block >= 0 : block < block_count;
+                    reverse ? block-- : block++) {
+                       int region;
+
+                       /* Size of each sector in block. Size is stored as
+                        * sector_size / 256.
+                        */
+                       int sector_size =
+                           (wide_read(flash, (OFFSET_CFI_BLOCK+block * 4+2) <<
+                                             offset_shift)
+                               |
+                           (wide_read(flash, (OFFSET_CFI_BLOCK+block * 4+3) <<
+                                             offset_shift) << 8)
+                           ) << 8;
+
+                       /* Number of sectors */
+                       int sector_count =
+                           (wide_read(flash, (OFFSET_CFI_BLOCK+block * 4+0) <<
+                                             offset_shift)
+                               |
+                           (wide_read(flash, (OFFSET_CFI_BLOCK+block * 4+1) <<
+                                             offset_shift) << 8)
+                           ) + 1;
+
+                       region = reverse? block_count - 1 - block : block;
+                       flash->regions[region].offset = offset;
+                       flash->regions[region].sector_size = sector_size;
+                       flash->regions[region].numsectors = sector_count;
+
+                       /* Can't use multiplication (we have no lib). */
+                       {
+                               int temp;
+                               for (temp = 0 ; temp < sector_count ; temp++) {
+                                       offset += sector_size;
+                               }
+                       }
+
+FDEBUG(
+       if (reverse) {
+               safe_printk("NOTE! reversed table:\n");
+       }
+       safe_printk("region: ");
+       send_hex((udword)region, NL);
+       safe_printk("   offset: ");
+       send_hex((udword)flash->regions[region].offset, NL);
+       safe_printk("   sector_size: ");
+       send_hex((udword)flash->regions[region].sector_size, NL);
+       safe_printk("   numsectors: ");
+       send_hex((udword)flash->regions[region].numsectors, NL);
+)
+
+               /* Some flashes (SST) store information about alternate
+                        * block sizes. Ignore those by breaking when the sum
+                        * of the sector sizes == flash size.
+                        */
+                       if (offset == flash->size) {
+                               break;
+                       }
+               }
+
+               /* reset */
+               wide_cmd(flash, CMD_RESET_DATA, ADDR_UNLOCK_1);
+
+               return 1;
+       }
+
+       /* reset */
+       wide_cmd(flash, CMD_RESET_DATA, ADDR_UNLOCK_1);
+
+       return 0;
+}
+#endif
+
+
+
+static int
+flash_probe(struct chip *flash)
+{
+       char *message;
+       udword dev_id;
+       udword mfr_id;
+       udword id;
+
+       if (flash->size
+#ifdef CFI_PROBE
+           || try_cfi(flash)
+#endif
+          ) {
+               return 1;
+       }
+
+#ifdef JEDEC_PROBE
+       /* Read manufacturer ID. */
+       flash_unlock(flash);
+       wide_cmd(flash, CMD_MANUFACTURER_UNLOCK_DATA, ADDR_UNLOCK_1);
+       mfr_id = wide_read(flash, ADDR_MANUFACTURER * TYPE_X16
+#ifdef INTERLEAVE
+                          * flash->interleave
+#endif
+                         );
+       /* Read device ID. */
+       dev_id = wide_read(flash, ADDR_DEVICE_ID * TYPE_X16
+#ifdef INTERLEAVE
+                          * flash->interleave
+#endif
+                         );
+FDEBUG(
+       safe_printk("mfr_id: ");
+       send_hex(mfr_id, NL);
+       safe_printk("dev_id: ");
+       send_hex(dev_id, NL);
+)
+
+#ifdef INTERLEAVE
+       if ((flash->interleave == 2) &&
+           ((mfr_id >> 16) == (mfr_id & 0xffff)) &&
+           ((dev_id >> 16) == (dev_id & 0xffff))) {
+               mfr_id &= 0xffff;
+               dev_id &= 0xffff;
+       }
+#endif
+
+       id = (mfr_id << 16) | dev_id;
+
+       /* reset */
+       wide_cmd(flash, CMD_RESET_DATA, ADDR_UNLOCK_1);
+
+       /* Check device type and fill in correct sizes. */
+       switch (id) {
+               case AM29LV160BT:
+               case TC58FVT160:
+               // case MBM29LV160TE: /* This is same id as AM29LV160BT */
+                       message = message_top_boot_16;
+
+                       flash->size = 0x00200000;
+
+                       flash->regions[0].offset = 0x00000000;
+                       flash->regions[0].sector_size = 0x10000;
+                       flash->regions[0].numsectors = 31;
+
+                       flash->regions[1].offset = 0x001F0000;
+                       flash->regions[1].sector_size = 0x08000;
+                       flash->regions[1].numsectors = 1;
+
+                       flash->regions[2].offset = 0x001F8000;
+                       flash->regions[2].sector_size = 0x02000;
+                       flash->regions[2].numsectors = 2;
+
+                       flash->regions[3].offset = 0x001FC000;
+                       flash->regions[3].sector_size = 0x04000;
+                       flash->regions[3].numsectors = 1;
+                               break;
+
+               // case AM29LV160BB:
+               case TC58FVB160:
+               case MBM29LV160BE:
+                       message = message_bottom_boot_16;
+
+                       flash->size = 0x00200000;
+
+                       flash->regions[0].offset = 0x00000000;
+                       flash->regions[0].sector_size = 0x04000;
+                       flash->regions[0].numsectors = 1;
+
+                       flash->regions[1].offset = 0x00004000;
+                       flash->regions[1].sector_size = 0x02000;
+                       flash->regions[1].numsectors = 2;
+
+                       flash->regions[2].offset = 0x00008000;
+                       flash->regions[2].sector_size = 0x08000;
+                       flash->regions[2].numsectors = 1;
+
+                       flash->regions[3].offset = 0x00010000;
+                       flash->regions[3].sector_size = 0x10000;
+                       flash->regions[3].numsectors = 31;
+                       break;
+
+               case AM29LV800BB:
+               case AM29F800BB:
+                       message = message_bottom_boot_8;
+
+                       flash->size = 0x00100000;
+
+                       flash->regions[0].offset = 0x00000000;
+                       flash->regions[0].sector_size = 0x04000;
+                       flash->regions[0].numsectors = 1;
+
+                       flash->regions[1].offset = 0x00004000;
+                       flash->regions[1].sector_size = 0x02000;
+                       flash->regions[1].numsectors = 2;
+
+                       flash->regions[2].offset = 0x00008000;
+                       flash->regions[2].sector_size = 0x08000;
+                       flash->regions[2].numsectors = 1;
+
+                       flash->regions[3].offset = 0x00010000;
+                       flash->regions[3].sector_size = 0x10000;
+                       flash->regions[3].numsectors = 15;
+                       break;
+
+               case M29W800T:
+               case AM29LV800BT:
+               case AM29F800BT:
+               case TC58FVT800:
+                       message = message_top_boot_8;
+
+                       flash->size = 0x00100000;
+
+                       flash->regions[0].offset = 0x00000000;
+                       flash->regions[0].sector_size = 0x10000;
+                       flash->regions[0].numsectors = 15;
+
+                       flash->regions[1].offset = 0x000F0000;
+                       flash->regions[1].sector_size = 0x08000;
+                       flash->regions[1].numsectors = 1;
+
+                       flash->regions[2].offset = 0x000F8000;
+                       flash->regions[2].sector_size = 0x02000;
+                       flash->regions[2].numsectors = 2;
+
+                       flash->regions[3].offset = 0x000FC000;
+                       flash->regions[3].sector_size = 0x04000;
+                       flash->regions[3].numsectors = 1;
+
+                       break;
+
+               case AT49xV16x:
+                       message = message_bottom_boot_16;
+
+                       flash->size = 0x00200000;
+
+                       flash->regions[0].offset = 0x00000000;
+                       flash->regions[0].sector_size = 0x02000;
+                       flash->regions[0].numsectors = 8;
+
+                       flash->regions[1].offset = 0x00010000;
+                       flash->regions[1].sector_size = 0x10000;
+                       flash->regions[1].numsectors = 31;
+
+                       break;
+
+               case AT49xV16xT:
+                       message = message_top_boot_16;
+
+                       flash->size = 0x00200000;
+
+                       flash->regions[0].offset = 0x00000000;
+                       flash->regions[0].sector_size = 0x10000;
+                       flash->regions[0].numsectors = 31;
+
+                       flash->regions[1].offset = 0x001F0000;
+                       flash->regions[1].sector_size = 0x02000;
+                       flash->regions[1].numsectors = 8;
+
+                       break;
+
+               case AT49BV32xAT:
+                       message = message_top_boot_32;
+
+                       flash->size = 0x00400000;
+
+                       flash->regions[0].offset = 0x00000000;
+                       flash->regions[0].sector_size = 0x10000;
+                       flash->regions[0].numsectors = 63;
+
+                       flash->regions[1].offset = 0x001F0000;
+                       flash->regions[1].sector_size = 0x02000;
+                       flash->regions[1].numsectors = 8;
+
+                       break;
+
+               default:
+#endif
+#ifdef INTERLEAVE
+                       if (flash->interleave == 1) {
+#endif
+                               safe_printk("No single x16 at ");
+#ifdef INTERLEAVE
+                       } else {
+                               safe_printk("No interleaved x16 at ");
+                       }
+#endif
+                       send_hex((udword)flash->base, NL);
+
+                       return 0;
+#ifdef JEDEC_PROBE
+       }
+
+       safe_printk("Found ");
+#ifdef INTERLEAVE
+       if (flash->interleave == 1) {
+#endif
+               safe_printk("1");
+#ifdef INTERLEAVE
+       }
+       if (flash->interleave == 2) {
+       int count = 0;
+
+               flash->size <<= 1;
+       while (count < MAXREGIONS) {
+               flash->regions[count].offset <<= 1;
+               flash->regions[count].sector_size <<= 1;
+               count++;
+       }
+               safe_printk("2");
+       }
+#endif
+       safe_printk(" x ");
+       safe_printk(message);
+       safe_printk(" at ");
+       send_hex((udword)flash->base, NL);
+
+       return 1;
+#endif
+}
+
+/* Start erase of a sector but do no wait for completion */
+static void
+start_sector_erase(struct chip *flash, unsigned long offset)
+{
+       flash_unlock(flash);
+       wide_cmd(flash, CMD_SECTOR_ERASE_UNLOCK_DATA_1, ADDR_UNLOCK_1);
+       flash_unlock(flash);
+
+#ifdef INTERLEAVE
+       if (flash->interleave == 2) {
+               *(udword *)(flash->base+offset) = (CMD_SECTOR_ERASE_UNLOCK_DATA_2 << 16) |
+                                                  CMD_SECTOR_ERASE_UNLOCK_DATA_2;
+       } else {
+#endif
+               *(uword *)(flash->base+offset) = CMD_SECTOR_ERASE_UNLOCK_DATA_2;
+#ifdef INTERLEAVE
+       }
+#endif
+}
+
+/* Return the size of the sector at the given offset */
+static int
+find_sector_size(struct chip *flash, unsigned long offset)
+{
+       unsigned int i, j;
+       int region_size;
+       /* Sanity check */
+       if (offset >= flash->size)
+               return 0;
+
+       for(i=0; i < MAXREGIONS; i++) 
+               if (offset >= flash->regions[i].offset) {
+                       region_size=0;
+                       for (j=0; j < flash->regions[i].numsectors; j++)
+                               region_size += flash->regions[i].sector_size;
+                       if (offset < flash->regions[i].offset + region_size)
+                               return flash->regions[i].sector_size;
+               }
+
+       /* Should not happen */
+       return 0;
+}
+
+/* Check and see if we need to erase the sector  */
+/* The return values mean */
+/* 0: The source and destination are the same. */
+/* 1: The source and destination are not the same, but flash sector already contains only ones. */
+/* 2: The source and destination are not the same and the flash sector is tainted by some zeroes. */
+static char
+need_to_erase(struct chip *flash, unsigned long offset, const unsigned char *source, int size)
+{
+       int i;
+       unsigned long j;
+               
+       for (i = 0; i < size; i+=2)
+               if (*(uword*)(flash->base + i + offset) != *(uword*)(source + i)) {
+                       /* Check if the sector only contain zeroes */
+                       for (j = offset; j < (size + offset); j+=2) {
+                               if (*(uword*)(flash->base + j) != 0xffff)
+                                       return 2;
+                       }
+                       return 1;
+               }
+                       
+       /* The source is equal to the destination */
+       return 0;
+}
+
+static unsigned int
+flash_probe_chips(void)
+{
+       unsigned int tot_size = 0;
+       unsigned int i = 0;
+
+       for (; i < sizeof chips/sizeof *chips; i++) {
+#ifdef INTERLEAVE
+               byte interleave;
+
+               for (interleave = 1; interleave < 4; interleave *= 2) {
+                       chips[i].interleave = interleave;
+                       if (interleave == 1) {
+                               chips[i].buswidth = sizeof(uword);
+                       } else {
+                               chips[i].buswidth = sizeof(udword);
+                       }
+
+                       if (flash_probe(&chips[i])) {
+                               break;
+                       }
+               }
+#else
+               flash_probe(&chips[i]);
+#endif
+
+               tot_size += chips[i].size;
+       }
+
+       return tot_size;
+}
+
+/* Program a sector (given by size) at the given offset. Do not write only ones. */
+static void
+program_sector(struct chip *flash, unsigned long offset, const unsigned char *source, int size)
+{
+       int chunk_size = 0;
+       int bytes_written = 0;
+
+       
+       while (bytes_written < size) {
+               if (
+#ifdef INTERLEAVE
+                   (flash->buswidth == 2) && 
+#endif
+                   *(uword*)(source + bytes_written) == 0xffff) {
+                       chunk_size=2;   
+               }
+#ifdef INTERLEAVE
+               else if ((flash->buswidth == 4) && *(udword*)(source + bytes_written) == 0xffffffff) {
+                       chunk_size=4;   
+               }
+#endif
+               else {
+                       flash_unlock(flash);
+                       wide_cmd(flash, CMD_PROGRAM_UNLOCK_DATA, ADDR_UNLOCK_1);
+                       chunk_size = wide_write_chunk(flash, offset + bytes_written, source + bytes_written);
+                       while(flash_is_busy(flash, offset + bytes_written))
+                       /* Nothing */  
+                       ;
+               }
+               
+               bytes_written += chunk_size;
+       }
+}
+
+int
+flash_write(const unsigned char *source, unsigned int offset, unsigned int size)
+{
+       struct flash_status {
+               unsigned char busy;             /* Indicates if the flash is busy */
+               const unsigned char *src;       /* From where to get the source info */
+               unsigned long offset;           /* Start operations in flash at this offset */
+               unsigned int size;              /* Size to erase/program (if needed) */
+               unsigned int bytes_done;        /* Bytes written (if needed) */
+               unsigned int erase_attempts;    /* Keep track how many times we try to erase the same sector */
+       };
+       
+       unsigned int tot_size = flash_probe_chips();
+       unsigned int i, j;
+       unsigned int current_sector_size;
+       unsigned long current_offset;
+       const unsigned char *current_src;
+       char need_erase;
+       struct flash_status *current_flash = NULL;
+       
+       static struct flash_status flash_status[2] = {
+               { 0, NULL, 0, 0, 0, 0 },
+               { 0, NULL, 0, 0, 0, 0 }
+       };
+
+       if (!tot_size) {
+               /* No chips found, bail out. */
+               return ERR_FLASH_NONE;
+       }
+
+       if (offset + size > tot_size) {
+               safe_printk("Fatal: flash is too small.\n");
+               return ERR_FLASH_TOO_SMALL;
+       }
+
+       /* Initiate the flash_status structs so that we can keep track of what needs to be done
+          on the different flash chips */
+       
+       /* Operations only on flash chip 1 */
+       if (offset >= (&chips[0])->size) {
+               flash_status[0].size = 0;
+               flash_status[1].src = source;
+               flash_status[1].offset = offset - (&chips[0])->size;
+               flash_status[1].size = size;
+       }
+       /* Operations on both flash chips */
+       else if ((offset < (&chips[0])->size) && ((offset+size) > (&chips[0])->size)) {
+               flash_status[0].src = source;
+               flash_status[0].offset = offset;
+               flash_status[0].size = (&chips[0])->size - offset;
+               flash_status[1].src = source + flash_status[0].size;
+               flash_status[1].offset = 0;
+               flash_status[1].size = size - flash_status[0].size;
+       } 
+       /* Operations only on flash chip 0 */
+       else {
+               flash_status[0].src = source;
+               flash_status[0].offset = offset;
+               flash_status[0].size = size;
+               flash_status[1].size = 0;
+       }
+       flash_status[0].busy = 0;
+       flash_status[0].bytes_done = 0;
+       flash_status[0].erase_attempts = 0;
+       flash_status[1].busy = 0;
+       flash_status[1].bytes_done = 0;
+       flash_status[1].erase_attempts = 0;
+#if 0
+        for (i = 0; i < 2; i++) { 
+                safe_printk("\nFlash ");
+                send_hex(i, NL);
+                safe_printk("src:\t");
+                send_hex((int)flash_status[i].src, NL);
+                safe_printk("offset:\t");
+                send_hex(flash_status[i].offset, NL);
+                safe_printk("size:\t");
+                send_hex(flash_status[i].size, NL);
+                safe_printk("\n");
+        }
+#endif
+
+       /* Erase and write */
+
+       i = 0;  /* Start operations on flash 0 */       
+
+#define CHANGE_FLASH
+
+       while (((&flash_status[0])->bytes_done + (&flash_status[1])->bytes_done) < size) {
+       
+               struct flash_status *previous_flash = &flash_status[i ? 0 : 1];
+               current_flash = &flash_status[i];
+
+#ifdef CHANGE_FLASH
+               /* Change flash only if:
+                  - There is a flash to change to and operations should be made on that flash *AND*
+                  - There is more to write to the previous flash *AND*
+                  - Operations should be made on the current flash *OR*
+                  - The current flash is busy *OR*
+                  - All has been written to the current flash */
+       
+               if (previous_flash->size && (previous_flash->bytes_done < previous_flash->size) &&
+                       (!current_flash->size || current_flash->busy || 
+                         current_flash->bytes_done == current_flash->size))    
+                               i = i ? 0 : 1;  /* Change flash chip */ 
+#else
+               /* Finish one flash chip before continuing on the next one */
+               
+               if ((&flash_status[i])->bytes_done == (&flash_status[i])->size)
+                       i = i ? 0 : 1;  /* Change flash chip */ 
+#endif
+               /* Bail out if we have tried to erase the same sector more that 10 times. */
+               if(current_flash->erase_attempts > 10) {
+                       safe_printk("Sector erase error\n");
+                       return ERR_FLASH_ERASE;
+               }
+
+               /* Get the current status from the chip we are about to access */
+               current_flash = &flash_status[i];
+               current_offset = current_flash->offset + current_flash->bytes_done;
+               current_src = current_flash->src + current_flash->bytes_done;
+               current_sector_size = find_sector_size(&chips[i], current_offset);
+       
+               /* Make sure that the chip we are about to access has finished erasing */
+               if (current_flash->busy) {
+                       while (flash_is_busy(&chips[i], current_offset))
+                               /* nothing */
+                               ;
+                       current_flash->busy = 0;
+               }
+               
+               /* Some flash chip need a reset to bring them back to read mode again. */
+               wide_cmd(&chips[i], CMD_RESET_DATA, ADDR_UNLOCK_1);
+       
+               /* Find out if we need to erase the sector or not */
+               need_erase = need_to_erase(&chips[i], current_offset, current_src, current_sector_size);
+               
+               if (need_erase == 0) {
+                       current_flash->bytes_done += current_sector_size;
+                       current_flash->erase_attempts = 0;
+                       send_hex((int)(&chips[i])->base + current_offset, 0);
+                       safe_printk(": No need to write\n");
+                       continue;
+               } else if (need_erase == 1) {
+                       /* Erased, not worth printing. */
+               }
+               else if (need_erase == 2) {
+                       send_hex((int)(&chips[i])->base + current_offset, 0);
+                       safe_printk(": Erasing ");
+                       send_hex(current_sector_size, 0);
+                       safe_printk(" bytes\n");
+                       start_sector_erase(&chips[i], current_offset);          
+                       current_flash->busy=1;
+                       current_flash->erase_attempts++;
+                       continue;
+               }
+                       
+               /* The sector is ready to be programmed */      
+               send_hex((int)(&chips[i])->base + current_offset, 0);
+               safe_printk(": Writing ");
+               send_hex(current_sector_size, 0);
+               safe_printk(" bytes\n");
+               program_sector(&chips[i], current_offset, current_src, current_sector_size);
+               current_flash->bytes_done += current_sector_size;
+               current_flash->erase_attempts = 0;
+       }
+       
+       /* Verify that the flash chip(s) have the correct content */
+       for (i = 0; i < 2; i++) {
+               current_flash = &flash_status[i]; 
+               if (!current_flash->size)
+                       continue;
+               send_hex((int)(&chips[i])->base, 0);
+               safe_printk(": Verifying...");
+               for (j = 0; j < current_flash->size; j+=2) {
+                       if (*(uword*)(current_flash->offset + j + (&chips[i])->base) != 
+                           *(uword*)(current_flash->src + j)) {
+                               safe_printk("Error at ");
+                               send_hex(j, NL);
+                               return ERR_FLASH_VERIFY;
+                       }
+               }
+               safe_printk("OK\n");
+       }
+
+       return ERR_FLASH_OK;
+}