diff src/cs/drivers/drv_app/ffs/board/drv.c @ 0:b6a5e36de839

src/cs: initial import from Magnetite
author Mychaela Falconia <falcon@freecalypso.org>
date Sun, 15 Jul 2018 04:39:26 +0000
parents
children 559a8b3ef10b
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cs/drivers/drv_app/ffs/board/drv.c	Sun Jul 15 04:39:26 2018 +0000
@@ -0,0 +1,1452 @@
+/******************************************************************************
+ * Flash File System (ffs)
+ * Idea, design and coding by Mads Meisner-Jensen, mmj@ti.com
+ *
+ * ffs low level flash driver
+ *
+ * $Id: drv.c 1.30.1.6.1.51.1.1.1.13.1.11 Tue, 06 Jan 2004 14:36:52 +0100 tsj $
+ *
+ ******************************************************************************/
+
+#ifndef TARGET
+#include "ffs.cfg"
+#endif
+
+#include "ffs/ffs.h"
+#include "ffs/board/drv.h"
+#include "ffs/board/ffstrace.h"
+
+#if (TARGET == 0)
+
+#ifdef WIN32
+#include "windows.h"
+#else //WIN32
+#include "sys/mman.h"
+#include "unistd.h"
+#endif //WIN32
+
+#include "stdio.h"
+#include "sys/types.h"
+#include "sys/stat.h"
+#include "fcntl.h"
+#else
+#include "nucleus.h"
+
+#endif
+
+
+// Note that all code notes and comments pertaining to single-bank flash
+// drivers are located in the AMD SB driver (amdsbdrv.c). Consider this as
+// the reference.
+
+
+/******************************************************************************
+ * Globals
+ ******************************************************************************/
+
+#if (TARGET == 1)
+// NOTE: This is the size in bytes of the single-bank driver code that is
+// copied to RAM. The only way to determine the amount of memory needed is
+// to look into the linker output file (.map) or the assembler output of
+// both amdsbdrv.obj and intelsbdrv.obj files.
+#define FFSDRV_CODE_SIZE     (0x200)
+
+uint8 ffsdrv_code[FFSDRV_CODE_SIZE];
+
+#endif
+
+#define INTEL_UNLOCK_SLOW 1
+
+struct dev_s dev;
+struct ffsdrv_s ffsdrv;
+
+uint32 int_disable(void);
+void int_enable(uint32 tmp);
+
+/******************************************************************************
+ * Macros
+ ******************************************************************************/
+
+#define addr2offset(address) ( (int) (address) - (int) dev.base )
+
+
+/******************************************************************************
+ * Generic Driver Functions
+ ******************************************************************************/
+
+void ffsdrv_generic_write(void *dst, const void *src, uint16 size)
+{
+    uint8 *mydst = dst;
+    const uint8 *mysrc = src;
+
+    if (size > 0)
+    {
+        if ((unsigned int) mydst & 1) {
+            ffsdrv_write_byte(mydst++, *mysrc++);
+            size--;
+        }
+        while (size >= 2) {
+            ffsdrv.write_halfword((uint16 *) mydst,
+                                  mysrc[0] | (mysrc[1] << 8));
+            size -= 2;
+            mysrc += 2;
+            mydst += 2;
+        }
+        if (size == 1)
+            ffsdrv_write_byte(mydst++, *mysrc++);
+    }
+}
+
+
+/******************************************************************************
+ * AMD Single Bank Driver Functions
+ ******************************************************************************/
+
+#if (TARGET == 1)
+
+// Forward declaration of functions in file amdsbdrv.c
+void ffsdrv_ram_amd_sb_write_halfword(volatile uint16 *addr, uint16 value);
+void ffsdrv_ram_amd_sb_erase(uint8 block);
+
+#else // (TARGET == 0)
+
+// On PC these functions are empty
+void ffsdrv_ram_amd_sb_write_halfword(volatile uint16 *addr, uint16 value) {}
+void ffsdrv_ram_amd_sb_erase(uint8 block) {}
+
+#endif // (TARGET == 1)
+
+
+/******************************************************************************
+ * AMD Pseudo Single Bank Driver Functions
+ ******************************************************************************/
+
+// This is a pseudo single-bank flash driver. It simulates a single-bank
+// flash device on a dual-bank device.
+
+#if (TARGET == 1)
+
+void ffsdrv_amd_pseudo_sb_write_halfword(volatile uint16 *addr, uint16 value)
+{
+    volatile char *flash = dev.base;
+    uint32 cpsr, i, x;
+
+    ttw(ttr(TTrDrvWrite, "wh(%x,%x)" NL, addr, value));
+
+    if (~*addr & value) {
+        ttw(ttr(TTrFatal, "wh(%x,%x->%x) fatal" NL, addr, *addr, value));
+        return;
+    }
+
+    cpsr = int_disable();
+    tlw(led_on(LED_WRITE));
+
+    flash[0xAAAA] = 0xAA; // unlock cycle 1
+    flash[0x5555] = 0x55; // unlock cycle 2
+    flash[0xAAAA] = 0xA0;
+    *addr         = value;
+
+    while ((*dev.addr ^ dev.data) & 0x80)
+        ;
+
+    tlw(led_off(LED_WRITE));
+    int_enable(cpsr);
+}
+
+// This VERY simple way of erase suspending only works because we run under
+// a pre-emptive operating system, so whenever an interrupt occurs, another
+// task takes the CPU, and at the end of the interrupt, FFS gets the CPU
+// again.
+void ffsdrv_amd_pseudo_sb_erase(uint8 block)
+{
+    volatile char *flash = dev.base;
+    volatile char *addr;
+    uint32 cpsr;
+    uint16 flashpoll;
+
+    addr = block2addr(block);
+
+    ttw(ttr(TTrDrvErase, "e(%d)" NL, block));
+
+    cpsr = int_disable();
+    tlw(led_on(LED_ERASE));
+
+    flash[0xAAAA] = 0xAA; // unlock cycle 1
+    flash[0x5555] = 0x55; // unlock cycle 2
+    flash[0xAAAA] = 0x80; 
+    flash[0xAAAA] = 0xAA; // unlock cycle 1
+    flash[0x5555] = 0x55; // unlock cycle 2
+    *addr         = 0x30; // AMD erase sector command
+
+    // Wait for erase to finish.
+    while ((*addr & 0x80) == 0) {
+        tlw(led_toggle(LED_ERASE));
+        // Poll interrupts, taking interrupt mask into account.
+        if (INT_REQUESTED)
+        {
+            // 1. suspend erase
+            // 2. enable interrupts
+            // .. now the interrupt code executes
+            // 3. disable interrupts
+            // 4. resume erase
+
+            tlw(led_on(LED_ERASE_SUSPEND));
+            *addr = 0xB0;
+
+            // wait for erase suspend to finish
+            while ((*addr & 0x80) == 0)
+                ;
+
+            tlw(led_off(LED_ERASE_SUSPEND));
+            int_enable(cpsr);
+
+            // Other interrupts and tasks run now...
+
+            cpsr = int_disable();
+            tlw(led_on(LED_ERASE_SUSPEND));
+
+            // Before resuming erase we must? check if the erase is really
+            // suspended or if it did finish
+            flashpoll = *addr;
+            *addr = 0x30;
+
+            tlw(led_off(LED_ERASE_SUSPEND));
+        }
+    }
+
+    tlw(led_on(LED_ERASE));
+    tlw(led_off(LED_ERASE));
+    int_enable(cpsr);
+}
+
+#else // (TARGET == 0)
+
+void ffsdrv_amd_pseudo_sb_write_halfword(volatile uint16 *addr, uint16 value) {}
+void ffsdrv_amd_pseudo_sb_erase(uint8 block) {}
+
+#endif // (TARGET == 1)
+
+
+/******************************************************************************
+ * AMD Dual/Multi Bank Driver Functions
+ ******************************************************************************/
+
+// All erase and write operations are performed atomically (interrupts
+// disabled). Otherwise we cannot trust the value of dev.state and we cannot
+// determine exactly how many of the command words have already been
+// written.
+
+// in ffs_end() when we resume an erasure that was previously suspended, how
+// does that affect multiple tasks doing that simultaneously?
+
+void ffsdrv_amd_write_end(void);
+void ffsdrv_amd_erase_end(void);
+
+void ffsdrv_amd_write_halfword(volatile uint16 *addr, uint16 value)
+{
+    volatile uint16 *flash = (volatile uint16 *)dev.base;
+    uint32 cpsr;
+
+    tlw(led_on(LED_WRITE));
+    ttw(ttr(TTrDrvWrite, "wh(%x,%x)" NL, addr, value));
+
+    dev.addr = addr;
+    dev.data = value;
+
+    if (~*addr & value) {
+        ttw(ttr(TTrFatal, "wh(%x,%x->%x) fatal" NL, addr, *addr, value));
+        return;
+    }
+
+    cpsr = int_disable();
+    tlw(led_toggle(LED_WRITE_SUSPEND));
+    dev.state = DEV_WRITE;
+    flash[0x555] = 0xAA; // unlock cycle 1
+    flash[0x2AA] = 0x55; // unlock cycle 2
+    flash[0x555] = 0xA0;
+    *addr         = value;
+    int_enable(cpsr);
+    tlw(led_toggle(LED_WRITE_SUSPEND));
+
+    ffsdrv_amd_write_end();
+}
+
+void ffsdrv_amd_write(void *dst, const void *src, uint16 size)
+{
+    uint8 *mydst = dst;
+    const uint8 *mysrc = src;
+
+    if (size > 0)
+    {
+        if ((unsigned int) mydst & 1) {
+            ffsdrv_write_byte(mydst++, *mysrc++);
+            size--;
+        }
+        while (size >= 2) {
+            ffsdrv_amd_write_halfword((uint16 *) mydst,
+                                      mysrc[0] | (mysrc[1] << 8));
+            size -= 2;
+            mysrc += 2;
+            mydst += 2;
+        }
+        if (size == 1)
+            ffsdrv_write_byte(mydst++, *mysrc++);
+    }
+}
+
+void ffsdrv_amd_write_end(void)
+{
+    while ((*dev.addr ^ dev.data) & 0x80)
+        tlw(led_toggle(LED_WRITE_SUSPEND));
+
+    dev.state = DEV_READ;
+
+    tlw(led_off(LED_WRITE));
+}
+
+void ffsdrv_amd_erase(uint8 block)
+{
+    volatile uint16 *flash = (volatile uint16 *)dev.base;
+    uint32 cpsr;
+
+    tlw(led_on(LED_ERASE));
+    ttw(ttr(TTrDrvErase, "e(%d)" NL, block));
+
+    dev.addr = (uint16 *) block2addr(block);
+
+    cpsr = int_disable();
+    dev.state = DEV_ERASE;
+    flash[0x555] = 0xAA; // unlock cycle 1
+    flash[0x2AA] = 0x55; // unlock cycle 2
+    flash[0x555] = 0x80; 
+    flash[0x555] = 0xAA; // unlock cycle 1
+    flash[0x2AA] = 0x55; // unlock cycle 2
+    *dev.addr = 0x30; // AMD erase sector command
+    int_enable(cpsr);
+
+    ffsdrv_amd_erase_end();
+}
+
+void ffsdrv_amd_erase_end(void)
+{
+    while ((*dev.addr & 0x80) == 0)
+        ;
+
+    dev.state = DEV_READ;
+
+    tlw(led_off(LED_ERASE));
+}
+
+void ffsdrv_amd_erase_suspend(void)
+{
+    uint32 cpsr;
+
+    tlw(led_on(LED_ERASE_SUSPEND));
+    ttw(str(TTrDrvErase, "es" NL));
+
+    // if erase has finished then all is ok
+    if (*dev.addr & 0x80) {
+        ffsdrv_amd_erase_end();
+        tlw(led_off(LED_ERASE_SUSPEND));
+        return;
+    }
+
+    // NOTEME: As there is no way to be absolutely certain that erase
+    // doesn't finish between last poll and the following erase suspend
+    // command, we assume that the erase suspend is safe even though the
+    // erase IS actually already finished.
+
+    cpsr = int_disable();
+    dev.state = DEV_ERASE_SUSPEND;
+    *dev.addr = 0xB0;
+
+    // Wait for erase suspend to finish
+    while ((*dev.addr & 0x80) == 0)
+        ;
+
+    int_enable(cpsr);
+}
+
+void ffsdrv_amd_erase_resume(void)
+{
+    uint32 cpsr;
+
+    ttw(str(TTrDrvErase, "er" NL));
+
+    // NOTEME: See note in erase_suspend()... We assume that the erase
+    // resume is safe even though the erase IS actually already finished.
+    cpsr = int_disable();
+    dev.state = DEV_ERASE;
+    *dev.addr = 0x30;
+    int_enable(cpsr);
+
+    tlw(led_off(LED_ERASE_SUSPEND));
+}
+
+
+/******************************************************************************
+ * SST Dual/Multi Bank Driver Functions
+ ******************************************************************************/
+
+// SST flashes use almost same command set as AMD flashes. Only the command
+// addresses (4 more bits) and erase command data (0x50 instead of 0x30) are
+// different. SST flashes have no erase suspend/resume commands because they
+// are so fast at erasing!
+
+void ffsdrv_sst_write_end(void);
+void ffsdrv_sst_erase_end(void);
+
+void ffsdrv_sst_write(void *dst, const void *src, uint16 size)
+{
+    uint8 *mydst = dst;
+    const uint8 *mysrc = src;
+
+    if (size > 0)
+    {
+        if ((unsigned int) mydst & 1) {
+            ffsdrv_write_byte(mydst++, *mysrc++);
+            size--;
+        }
+        while (size >= 2) {
+            ffsdrv_amd_write_halfword((uint16 *) mydst,
+                                      mysrc[0] | (mysrc[1] << 8));
+            size -= 2;
+            mysrc += 2;
+            mydst += 2;
+        }
+        if (size == 1)
+            ffsdrv_write_byte(mydst++, *mysrc++);
+    }
+}
+
+// Note that SST flashes have smaller sectors than other flash families.
+// Fortunately they support erasure of several of these sectors in a logical
+// unit called a "block".
+void ffsdrv_sst_erase(uint8 block)
+{
+    volatile char *flash = dev.base;
+    uint32 cpsr;
+
+    tlw(led_on(LED_ERASE));
+    ttw(ttr(TTrDrvErase, "e(%d)" NL, block));
+
+    dev.addr = (uint16 *) block2addr(block);
+
+    cpsr = int_disable();
+    dev.state = DEV_ERASE;
+    flash[0xAAAA] = 0xAA; // unlock cycle 1
+    flash[0x5555] = 0x55; // unlock cycle 2
+    flash[0xAAAA] = 0x80; 
+    flash[0xAAAA] = 0xAA; // unlock cycle 1
+    flash[0x5555] = 0x55; // unlock cycle 2
+    *dev.addr = 0x50; // SST erase block command
+    int_enable(cpsr);
+
+    ffsdrv_sst_erase_end();
+}
+
+void ffsdrv_sst_erase_end(void)
+{
+    // Wait for erase end
+    while ((*dev.addr & 0x80) == 0)
+        ;
+
+    dev.state = DEV_READ;
+
+    tlw(led_off(LED_ERASE));
+}
+
+// Erase suspend/resume commands do not exist for SST flashes, so we just
+// poll for the end of the erase operation...
+
+void ffsdrv_sst_erase_suspend(void)
+{
+    ttw(str(TTrDrvErase, "es" NL));
+
+    ffsdrv_sst_erase_end();
+}
+
+
+/******************************************************************************
+ * Intel Single Bank Driver Functions
+ ******************************************************************************/
+
+#if (TARGET == 1)
+
+// Forward declaration of functions in file intelsbdrv.c
+int  ffsdrv_ram_intel_sb_init(void);
+void ffsdrv_ram_intel_sb_write_halfword(volatile uint16 *addr, uint16 value);
+void ffsdrv_ram_intel_sb_erase(uint8 block);
+void ffsdrv_ram_intel_erase(uint8 block);
+
+#else // (TARGET == 0)
+
+// On PC these functions are empty
+void ffsdrv_ram_intel_sb_write_halfword(volatile uint16 *addr, uint16 value) {}
+void ffsdrv_ram_intel_sb_erase(uint8 block) {}
+void ffsdrv_ram_intel_erase(uint8 block) {}
+
+#endif // (TARGET == 1)
+
+
+/******************************************************************************
+ * Intel Dual/Multi Bank Driver Functions
+ ******************************************************************************/
+
+void ffsdrv_intel_write_end(void);
+void ffsdrv_intel_erase_end(void);
+
+
+// ffsdrv_intel_write_halfword and ffsdrv_intel_write_end is not used
+// because of the bug in the intel flash device. Instead is the functions
+// ffsdrv_ram_intel_sb_write_halfword and ffsdrv_ram_intel_erase used.
+void ffsdrv_intel_write_halfword(volatile uint16 *addr, uint16 value)
+{
+    uint32 cpsr;
+
+    tlw(led_on(LED_WRITE));
+    ttw(ttr(TTrDrvWrite, "wh(%x,%x)" NL, addr, value));
+
+    dev.addr = addr;
+
+    if (~*addr & value) {
+        ttw(ttr(TTrFatal, "wh(%x,%x->%x) fatal" NL, addr, *addr, value));
+        return;
+    }
+
+    cpsr = int_disable();
+    dev.state = DEV_WRITE;
+
+#if (INTEL_UNLOCK_SLOW == 1)
+    *addr = 0x60; // Intel Config setup
+    *addr = 0xD0; // Intel Unlock block
+    *addr = 0x50; // Intel Clear Status Register
+#endif
+
+    *addr = 0x40; // Intel program byte/word
+    *addr = value;
+
+    int_enable(cpsr);
+
+    ffsdrv_intel_write_end();
+}
+
+void ffsdrv_intel_write(void *dst, const void *src, uint16 size)
+{
+    uint8 *mydst = dst;
+    const uint8 *mysrc = src;
+
+    if (size > 0)
+    {
+        if ((unsigned int) mydst & 1) {
+            ffsdrv_write_byte(mydst++, *mysrc++);
+            size--;
+        }
+        while (size >= 2) {
+            ffsdrv_intel_write_halfword((uint16 *) mydst,
+                                        mysrc[0] | (mysrc[1] << 8));
+            size -= 2;
+            mysrc += 2;
+            mydst += 2;
+        }
+        if (size == 1)
+            ffsdrv_write_byte(mydst++, *mysrc++);
+    }
+}
+
+void ffsdrv_intel_write_end(void)
+{
+    uint32 cpsr;
+	// We can be interrupted and reentered thus the state can have been changed.
+    while ((*dev.addr & 0x80) == 0 && dev.state == DEV_WRITE)
+        ;
+
+    // The flash state and dev.state must be in sync thus the stat changes
+    // must be protect from being interrupted
+    cpsr = int_disable();
+    *dev.addr = 0xFF; // Intel read array
+    dev.state = DEV_READ;
+    int_enable(cpsr);
+
+    tlw(led_off(LED_WRITE));
+}
+
+
+void ffsdrv_intel_erase(uint8 block) 
+{
+    uint32 cpsr;
+
+    ttw(ttr(TTrDrvErase, "e(%d)" NL, block));
+    tlw(led_on(LED_ERASE));
+
+    dev.addr = (uint16 *) block2addr(block);
+
+    cpsr = int_disable();
+    dev.state = DEV_ERASE;
+
+#if (INTEL_UNLOCK_SLOW == 1)
+    *dev.addr = 0x60; // Intel Config setup
+    *dev.addr = 0xD0; // Intel Unlock block
+#endif
+
+    *dev.addr = 0x50; // Intel clear status register (not really necessary)
+    *dev.addr = 0x20; // Intel erase setup
+    *dev.addr = 0xD0; // Intel erase confirm
+
+    int_enable(cpsr);
+
+    ffsdrv_intel_erase_end();
+}
+
+void ffsdrv_intel_erase_end(void)
+{
+    while ((*dev.addr & 0x80) == 0 && dev.state == DEV_ERASE)
+        ;
+
+    *dev.addr = 0xFF; // Intel read array
+
+    dev.state = DEV_READ;
+    tlw(led_off(LED_ERASE));
+}
+
+void ffsdrv_intel_erase_suspend(void)
+{
+    uint32 cpsr;
+    uint16 poll;
+
+    ttw(str(TTrDrvErase, "es" NL));
+    tlw(led_on(LED_ERASE_SUSPEND));
+
+    cpsr = int_disable();
+    dev.state = DEV_ERASE_SUSPEND;
+    *dev.addr = 0xB0; // Intel Erase Suspend
+    *dev.addr = 0x70; // Intel Read Status Register
+    while (((poll = *dev.addr) & 0x80) == 0)
+        ;
+
+    if ((poll & 0x40) == 0) {
+        // Block erase has completed
+        tlw(led_off(LED_ERASE_SUSPEND));
+        dev.state = DEV_READ;
+        tlw(led_off(LED_ERASE));
+    }
+    *dev.addr = 0xFF; // Intel read array
+    int_enable(cpsr);
+}
+
+void ffsdrv_intel_erase_resume(void)
+{
+    uint32 cpsr;
+
+    ttw(str(TTrDrvErase, "er" NL));
+
+    cpsr = int_disable();
+    dev.state = DEV_ERASE;
+    *dev.addr = 0xD0; // Intel erase resume
+
+    // The following "extra" Read Status command is required because Intel
+    // has changed the specification of the W30 flash! (See "1.8 Volt Intel®
+    // Wireless Flash Memory with 3 Volt I/O 28F6408W30, 28F640W30,
+    // 28F320W30 Specification Update")
+	*dev.addr = 0x70; // Intel Read Status Register
+
+    int_enable(cpsr);
+
+    tlw(led_off(LED_ERASE_SUSPEND));
+}
+
+
+/******************************************************************************
+ * RAM Family Functions
+ ******************************************************************************/
+
+void ffsdrv_ram_write_halfword(volatile uint16 *dst, uint16 value)
+{
+  *dst = value;
+}
+
+void ffsdrv_ram_write(void *dst, const void *src, uint16 size)
+{
+    uint8 *mydst = dst;
+    const uint8 *mysrc = src;
+  
+    if (size == 0)
+        return;
+    else if (size == 1)
+        ffsdrv_write_byte(mydst, *mysrc);
+    else {
+        if ((int) mydst & 1) {
+            ffsdrv_write_byte(mydst++, *mysrc++);
+            size--;
+        }
+        while (size >= 2) {
+            ffsdrv_ram_write_halfword((uint16 *) mydst, mysrc[0]|(mysrc[1] << 8));
+            size -= 2;
+            mysrc += 2;
+            mydst += 2;
+        }
+        if (size == 1)
+            ffsdrv_write_byte(mydst++, *mysrc++);
+    }
+}
+
+void ffsdrv_ram_erase(uint8 block) 
+{
+    int i;
+    char *addr;
+
+    addr = block2addr(block);
+    
+    for (i = 0; i < (1 << dev.binfo[block].size_ld); i++) {
+        *addr++ = 0xFF;
+    }
+}
+
+/******************************************************************************
+ * Void Functions
+ ******************************************************************************/
+
+int ffsdrv_null_init(void)
+{
+    ttw(ttr(TTrDrvOther, "ffsdrv_null_init()" NL));
+
+    return 0;
+}
+
+void ffsdrv_null_erase(uint8 block)
+{
+    ttw(ttr(TTrDrvErase, "ffsdrv_null_erase(%d)" NL, block));
+}
+
+void ffsdrv_null_write_halfword(volatile uint16 *addr, uint16 value)
+{
+    ttw(ttr(TTrDrvWrite, "ffsdrv_null_write_halfword(0x%x, 0x%x)" NL, addr, value));
+}
+
+void ffsdrv_null_write(void *dst, const void *src, uint16 size)
+{
+    ttw(ttr(TTrDrvWrite, "ffsdrv_null_write(0x%x, 0x%x, %d)" NL, dst, src, size));
+}
+
+void ffsdrv_null_erase_suspend(void)
+{
+    ttw(str(TTrDrvErase, "ffsdrv_null_erase_suspend()" NL));
+}
+
+void ffsdrv_null_erase_resume(void)
+{
+    ttw(str(TTrDrvErase, "ffsdrv_null_erase_resume()" NL));
+}
+
+void ffsdrv_null_write_end(void)
+{
+    ttw(str(TTrDrvWrite, "ffsdrv_null_write_end()" NL));
+}
+
+void ffsdrv_null_erase_end(void)
+{
+    ttw(str(TTrDrvErase, "ffsdrv_null_erase_end()" NL));
+}
+
+
+/******************************************************************************
+ * Test Driver Functions
+ ******************************************************************************/
+
+#if (TARGET == 0)
+
+static char *image_addr = 0;
+static int image_size = 0;
+#ifdef WIN32
+HANDLE image_fd, map_fd;
+#else //WIN32
+static int image_fd = 0;
+#endif //WIN32
+
+extern int   arg_removeimage;
+extern char *arg_imagename;
+
+extern void test_fatal_printf(char *format, ...);
+
+void ffsdrv_write_check(char *addr, int size)
+{
+    offset_t offset, last;
+
+    offset = addr2offset(addr);
+    last = dev.binfo[dev.numblocks-1].offset
+        + (1 << dev.binfo[dev.numblocks-1].size_ld);
+
+    if (offset < 0 || (offset + size) > last) {
+        fprintf(stderr, "ffsdrv_write_check() failed (addr = 0x%x, size = %d)\n",
+                (int) addr, size);
+        fprintf(stdout, "ffsdrv_write_check() failed (addr = 0x%x, size = %d)\n",
+                (int) addr, size);
+        exit (1);
+    }
+}
+
+
+void ffsdrv_write_error(uint16 old, uint16 new)
+{
+    test_fatal_printf("FATAL: Attempt to rewrite 0 to 1 bit "
+                      "(old:0x%x/%c new:0x%x/%c)\n",
+                      old, (old < ' ' ? '?' : old),
+                      new, (new < ' ' ? '?' : new));
+}
+
+void ffsdrv_test_write_halfword(volatile uint16 *addr, uint16 value)
+{
+    tw(tr(TR_FUNC, TrDrvWrite, "test_write_halfword(0x%05x, 0x%x)\n",
+          addr2offset(addr), value));
+
+    ffsdrv_write_check((uint8 *) addr, 2);
+
+    if (~*addr & value)
+        ffsdrv_write_error(*addr, value);
+
+    *addr = value;
+}
+
+void ffsdrv_test_write(void *dst, const void *src, uint16 size)
+{
+    uint8 *mydst = dst;
+    const uint8 *mysrc = src;
+
+    tw(tr(TR_FUNC, TrDrvWrite, "test_write(0x%05x, 0x%x, %d)\n",
+          addr2offset(mydst), mysrc, size));
+
+    if (size > 0)
+    {
+        if ((int) mydst & 1) {
+            ffsdrv_write_byte(mydst++, *mysrc++);
+            size--;
+        }
+        while (size >= 2) {
+            ffsdrv_test_write_halfword((uint16 *) mydst, mysrc[0]|(mysrc[1] << 8));
+            size -= 2;
+            mysrc += 2;
+            mydst += 2;
+        }
+        if (size == 1)
+            ffsdrv_write_byte(mydst++, *mysrc++);
+    }
+}
+
+void ffsdrv_test_erase(uint8 block) 
+{
+    int i;
+    uint8 *addr;
+
+    addr = block2addr(block);
+
+    tw(tr(TR_FUNC, TrDrvErase, "ffsdrv_test_erase(%d)\n", block));
+
+    for (i = 0; i < 1 << dev.binfo[block].size_ld; i++) {
+        *addr++ = 0xFF;
+    }
+}
+
+char *ffsdrv_test_create(void)
+{
+    // If flash image file already exists, open the file, and mmap it.
+    // Otherwise, create file, fill file with 1's, then mmap it.
+    
+    int i;
+    struct stat statbuf;
+#ifdef WIN32
+    OFSTRUCT lpReOpenBuff;
+    DWORD last_error;
+  	SECURITY_ATTRIBUTES	lpAttributes;	
+
+  	lpAttributes.nLength				= sizeof (lpAttributes);
+    lpAttributes.lpSecurityDescriptor	= NULL;
+    lpAttributes.bInheritHandle			= TRUE;
+#endif
+    image_size = (int) dev.binfo[dev.numblocks - 1].offset +
+        (1 << dev.binfo[dev.numblocks - 1].size_ld);
+
+    tw(tr(TR_BEGIN, TrDrvInit, "ffsdrv_test_create() {\n"));
+    tw(tr(TR_FUNC, TrDrvInit, "%s image: '%s', size = %d\n",
+          arg_removeimage ? "new" : "current", arg_imagename, image_size));
+
+    // create file if it does not exist
+#ifdef WIN32
+    if( arg_removeimage || OpenFile( arg_imagename, &lpReOpenBuff, OF_EXIST) == HFILE_ERROR )
+#else 
+        if (arg_removeimage || lstat(arg_imagename, &statbuf) == -1)
+#endif
+        {
+            char data[64];
+#ifdef WIN32
+            DWORD bwritten;
+#endif
+            // only the first run should remove the flash image file
+            arg_removeimage = 0;
+
+            tw(tr(TR_FUNC, TrDrvInit, "creating new flash image file '%s'\n",
+                  arg_imagename));
+#ifdef WIN32
+            image_fd = CreateFile(arg_imagename,
+                                  GENERIC_WRITE | GENERIC_READ,
+                                  0,
+                                  NULL,
+                                  OPEN_ALWAYS,
+                                  FILE_ATTRIBUTE_NORMAL,
+                                  NULL );
+#else 
+            image_fd = open(arg_imagename , O_RDWR|O_CREAT,
+                            (S_IRWXU & ~S_IXUSR) |
+                            ( S_IRWXG & ~S_IXGRP) | (S_IRWXO & ~S_IXOTH));
+#endif 
+            if (image_fd == -1) {
+                perror("Failed to create flash image");
+                exit(1);
+            }
+        
+            // write 1's to the file.
+            for (i = 0; i < 64; i++)
+                data[i] = 0xff;
+        
+#ifdef WIN32
+            for (i = 0; i < image_size/64; i++)
+                WriteFile(image_fd, data, 64, &bwritten, NULL);
+            CloseHandle(image_fd);
+#else
+            for (i = 0; i < image_size/64; i++)
+                write(image_fd, data, 64);
+        
+            close(image_fd);
+#endif
+            image_fd = 0;
+            tw(tr(TR_FUNC, TrDrvInit, "flash image file created\n"));
+        }
+
+    // only open image file if this is the first initialization.
+    if (image_fd > 0) {
+        tw(tr(TR_FUNC, TrDrvInit, "re-opening '%s' file of size %d\n",
+              arg_imagename, image_size));
+    }
+    else {
+        tw(tr(TR_FUNC, TrDrvInit, "opening '%s' file of size %d\n",
+              arg_imagename, image_size));
+
+#ifdef WIN32
+        image_fd = OpenFile( arg_imagename, &lpReOpenBuff, OF_READWRITE);
+        map_fd = CreateFileMapping (image_fd,
+                                    &lpAttributes,
+                                    PAGE_READWRITE,
+                                    0,
+                                    0,
+                                    arg_imagename);
+#else 
+        image_fd = open(arg_imagename, O_RDWR, S_IRWXU|S_IRWXG|S_IRWXO);
+#endif
+        if (image_fd == -1) {
+            perror("Failed to open flash image");
+            exit(1);
+        }
+
+        // memory map the file and update block addresses of binfo array
+#ifdef WIN32
+        image_addr = MapViewOfFile( map_fd,
+                                    FILE_MAP_ALL_ACCESS,
+                                    0,
+                                    0,
+                                    0);
+#else 
+        image_addr = mmap(0, image_size, PROT_READ|PROT_WRITE,
+                          MAP_FILE|MAP_SHARED, image_fd, 0);
+#endif
+    }
+
+    tw(tr(TR_END, TrDrvInit, "}\n"));
+
+    return image_addr;
+}
+
+#endif // (TARGET == 0)
+
+
+/******************************************************************************
+ * Device Detection and Copying of Driver to RAM
+ ******************************************************************************/
+
+#if (TARGET == 1)
+
+// Note that this function reads device code of any of the three known flash
+// families; Intel, AMD and SST. This works because Intel and AMD use
+// the same command data for entering READ_IDENTIFIER mode (0x90).
+// The function should be copied and executed from RAM!
+void ffsdrv_device_id_read(uint16 *manufact, uint16 *device)
+{
+    int addr, i;
+
+    // This silly looking code has one purpose; to set addr = 0xAAAA. It is
+    // necessary in order to force the compiler NOT to produce code that
+    // uses LDR opcode(s) with PC-relative addressing. The assember code
+    // produced from this C code is completely relocatable!
+    for (i = 0, addr = 0; i < 2; i++)
+        addr |= addr << 8 | 0xAA;
+
+    FLASH_WRITE_HALFWORD (addr,      0xAA);
+    FLASH_WRITE_HALFWORD (addr >> 1, 0x55);
+    FLASH_WRITE_HALFWORD (addr,      0x90); // Intel/AMD read id command
+
+    *manufact = FLASH_READ_HALFWORD (0); // flash a0 = 0
+    *device   = FLASH_READ_HALFWORD (2); // flash a0 = 1
+
+    // Read extended id
+    device[1] = FLASH_READ_HALFWORD (0xE << 1); 
+    device[2] = FLASH_READ_HALFWORD (0xF << 1); 
+    FLASH_WRITE_HALFWORD (0, 0xFF); // Intel read-array command
+
+    // AMD devices do not need the two unlock cycles but SST devices do,
+    // even though the SST datasheets states otherwise ;-)
+    FLASH_WRITE_HALFWORD (addr,      0xAA);
+    FLASH_WRITE_HALFWORD (addr >> 1, 0x55);
+    FLASH_WRITE_HALFWORD (addr,      0xF0); // AMD read-array/reset command
+}
+
+// Copy ffsdrv_device_id_read() function code to RAM. The only known way to
+// determine the size of the code is to look either in the linker-generated
+// map file or in the assember output file.
+void ffsdrv_device_id_read_copy_to_ram(uint16 *dst, int size)
+{
+    uint16 *src = (uint16 *) &ffsdrv_device_id_read;
+
+    // The ARM7TDMI compiler sets bit 0 for thumb mode function pointers, so
+    // we need to clear this in order to copy *all* bytes. Otherwise we
+    // exclude first byte and the resulting copy becomes garbage
+    src = (uint16 *) (~1 & (int) src);
+    size /= 2;
+
+    while (size--)
+        *dst++ = *src++;
+}
+
+// Copy ffsdrv_xxx_sb_erase() and ffsdrv_xxx_sb_write_halfword() functions
+// to RAM. The only known way to determine the size of the code is to look
+// either in the linker-generated map file or in the assember output file.
+int ffsdrv_driver_copy_to_ram(int type)
+{
+    int size;
+    uint16 *src, *dst;
+    extern uint16 ffsdrv_ram_amd_begin[];
+    extern uint16 ffsdrv_ram_intel_begin[];
+    uint32 offset_of_init;
+    uint32 offset_of_erase;
+    uint32 offset_of_write_halfword;
+    
+    ttw(ttr(TTrDrvOther, "ffsdrv_driver_copy_to_ram() {" NL));
+
+    switch (type) {
+    case FFS_DRIVER_AMD:
+    case FFS_DRIVER_AMD_SB:
+        src = ffsdrv_ram_amd_begin;
+        offset_of_erase =
+            (uint32) ffsdrv_ram_amd_sb_erase - (uint32) src;
+        offset_of_write_halfword =
+            (uint32) ffsdrv_ram_amd_sb_write_halfword - (uint32) src;
+        break;
+    case FFS_DRIVER_INTEL_SB:
+        src = ffsdrv_ram_intel_begin;
+        offset_of_init =
+            (uint32) ffsdrv_ram_intel_sb_init - (uint32) src;
+        offset_of_erase =
+            (uint32) ffsdrv_ram_intel_sb_erase - (uint32) src;
+        offset_of_write_halfword =
+            (uint32) ffsdrv_ram_intel_sb_write_halfword - (uint32) src;
+        break;
+    case FFS_DRIVER_INTEL:
+        src = ffsdrv_ram_intel_begin;
+        offset_of_init =
+            (uint32) ffsdrv_ram_intel_sb_init - (uint32) src;
+        offset_of_erase =
+            (uint32) ffsdrv_ram_intel_erase - (uint32) src;
+        offset_of_write_halfword =
+            (uint32) ffsdrv_ram_intel_sb_write_halfword - (uint32) src;
+        break;
+    default:
+        ttw(ttr(TTrDrvOther, "}" NL));
+        return 0;
+    }
+
+    // Make sure we are handling a half-word aligned address (Thumb mode
+    // function pointers have lsb set!)
+    src = (uint16 *) (~1 & (int) src);
+
+    // If we detect that the linker allocated the driver to RUN in RAM, the
+    // user has obviously NOT removed those linker lines and we bail out!
+    if (offset_of_erase > FFSDRV_CODE_SIZE)
+        return EFFS_DRIVER;
+
+    dst = (uint16 *) &ffsdrv_code;
+
+    // Code size in halfwords
+    size = FFSDRV_CODE_SIZE / 2;
+
+    // Rebind the two changed driver functions
+    if (type == FFS_DRIVER_AMD_SB || type == FFS_DRIVER_INTEL_SB) {
+        ffsdrv.erase =
+            (void (*)(uint8))
+            (offset_of_erase + (uint32) dst);
+        ffsdrv.write_halfword =
+            (void (*)(volatile uint16 *, uint16))
+            (offset_of_write_halfword + (uint32) dst);
+    }
+    if (type == FFS_DRIVER_INTEL_SB) {
+        ffsdrv.init =
+            (int (*)(void))
+            (offset_of_init + (uint32) dst);
+    }
+
+    ttw(ttr(TTrDrvOther, "ffsdrv_code, init, write, erase = 0x%07x, 0x%07x, 0x%07x, 0x%07x" NL,
+            dst, (uint32) ffsdrv.init,
+            (uint32) ffsdrv.write_halfword, (uint32) ffsdrv.erase));
+
+    ttw(ttr(TTrDrvOther, "amd_begin,   init, write, erase = 0x%07x, 0x%07x, 0x%07x, 0x%07x" NL,
+            ffsdrv_ram_amd_begin, ffsdrv_null_init,
+            ffsdrv_ram_amd_sb_write_halfword, ffsdrv_ram_amd_sb_erase));
+
+    ttw(ttr(TTrDrvOther, "intel_begin, init, write, erase = 0x%07x, 0x%07x, 0x%07x, 0x%07x" NL,
+            ffsdrv_ram_intel_begin, ffsdrv_ram_intel_sb_init,
+            ffsdrv_ram_intel_sb_write_halfword, ffsdrv_ram_intel_sb_erase));
+    
+    // Copy the code to RAM
+    while (size--)
+        *dst++ = *src++;
+
+    ttw(ttr(TTrDrvOther, "}" NL));
+
+    return 0;
+}
+
+#else // (TARGET == 0)
+
+void ffsdrv_device_id_read(uint16 *manufact, uint16 *device) {}
+int  ffsdrv_driver_copy_to_ram(int type) { return 0; }
+
+#endif // (TARGET == 1)
+
+
+/******************************************************************************
+ * Initialization
+ ******************************************************************************/
+
+const struct ffsdrv_s ffsdrv_amd = {
+    ffsdrv_null_init,
+    ffsdrv_amd_erase,
+    ffsdrv_amd_write_halfword,
+    ffsdrv_amd_write,
+    ffsdrv_amd_write_end,
+    ffsdrv_amd_erase_suspend,
+    ffsdrv_amd_erase_resume
+};
+
+const struct ffsdrv_s ffsdrv_amd_sb = {
+    ffsdrv_null_init,
+    ffsdrv_ram_amd_sb_erase,
+    ffsdrv_ram_amd_sb_write_halfword,
+    ffsdrv_generic_write,
+    ffsdrv_null_write_end,
+    ffsdrv_null_erase_suspend,
+    ffsdrv_null_erase_resume
+};
+
+const struct ffsdrv_s ffsdrv_sst = {
+    ffsdrv_null_init,
+    ffsdrv_sst_erase,
+    ffsdrv_amd_write_halfword, // Use AMD driver function
+    ffsdrv_sst_write,
+    ffsdrv_amd_write_end,      // Use AMD driver function
+    ffsdrv_sst_erase_suspend,
+    ffsdrv_null_erase_resume
+};
+
+const struct ffsdrv_s ffsdrv_sst_sb = {
+    ffsdrv_null_init,
+    ffsdrv_null_erase,
+    ffsdrv_null_write_halfword,
+    ffsdrv_null_write,
+    ffsdrv_null_write_end,
+    ffsdrv_null_erase_suspend,
+    ffsdrv_null_erase_resume
+};
+
+// We use the functions ffsdrv_ram_intel_sb_write_halfword and
+// ffsdrv_ram_intel_erase due to the bug in the intel wireless flash
+// device. See 28F640W30.pdf specification Errata 5.
+const struct ffsdrv_s ffsdrv_intel = {
+    ffsdrv_null_init,
+	ffsdrv_intel_erase,
+    ffsdrv_intel_write_halfword,
+    ffsdrv_generic_write,
+    ffsdrv_intel_write_end,
+    ffsdrv_intel_erase_suspend,
+    ffsdrv_intel_erase_resume
+};
+
+const struct ffsdrv_s ffsdrv_intel_sb = {
+    ffsdrv_null_init,
+    ffsdrv_ram_intel_sb_erase,
+    ffsdrv_ram_intel_sb_write_halfword,
+    ffsdrv_generic_write,
+    ffsdrv_null_write_end,
+    ffsdrv_null_erase_suspend,
+    ffsdrv_null_erase_resume
+};
+
+const struct ffsdrv_s ffsdrv_null = {
+    ffsdrv_null_init,
+    ffsdrv_null_erase,
+    ffsdrv_null_write_halfword,
+    ffsdrv_null_write,
+    ffsdrv_null_write_end,
+    ffsdrv_null_erase_suspend,
+    ffsdrv_null_erase_resume
+};
+
+const struct ffsdrv_s ffsdrv_amd_pseudo_sb = {
+    ffsdrv_null_init,
+    ffsdrv_amd_pseudo_sb_erase,
+    ffsdrv_amd_pseudo_sb_write_halfword,
+    ffsdrv_generic_write,
+    ffsdrv_null_write_end,
+    ffsdrv_null_erase_suspend,
+    ffsdrv_null_erase_resume
+};
+
+const struct ffsdrv_s ffsdrv_ram = {
+    ffsdrv_null_init,
+    ffsdrv_ram_erase,
+    ffsdrv_ram_write_halfword,
+    ffsdrv_ram_write,
+    ffsdrv_null_write_end,
+    ffsdrv_null_erase_suspend,
+    ffsdrv_null_erase_resume
+};
+
+#if (TARGET == 0)
+const struct ffsdrv_s ffsdrv_test = {
+    ffsdrv_null_init,
+    ffsdrv_test_erase,
+    ffsdrv_test_write_halfword,
+    ffsdrv_test_write,
+    ffsdrv_null_write_end,
+    ffsdrv_null_erase_suspend,
+    ffsdrv_null_erase_resume
+};
+#endif
+
+// Note: This function is designed for little-endian memory addressing!
+void ffsdrv_write_byte(void *dst, uint8 value)
+{
+    uint16 halfword;
+
+    tw(tr(TR_FUNC, TrDrvWrite, "ffsdrv_write_byte(0x%05x, 0x%x)\n",
+       (int) (addr2offset(dst)), value));
+    ttw(str(TTrDrvWrite, "wb" NL));
+
+    if ((int) dst & 1)
+        halfword =                (value << 8) | *((uint8 *) dst - 1);
+    else
+        halfword = (*((uint8 *) dst + 1) << 8) | (value);
+
+    ffsdrv.write_halfword((uint16 *) ((int) dst & ~1), halfword);
+}
+
+
+extern uint16 ffs_flash_manufact;
+extern uint16 ffs_flash_device;
+
+effs_t ffsdrv_init(void)
+{
+    const struct ffsdrv_s *p;
+    const struct flash_info_s *flash = &flash_info[0];
+    int error;
+
+    tw(tr(TR_BEGIN, TrDrvInit, "drv_init() {\n"));
+    ttw(str(TTrDrvOther, "ffsdrv_init() {" NL));
+
+    dev.state = DEV_READ;
+    dev.binfo = 0;
+    dev.base = 0;
+    dev.numblocks = 0;
+
+    // If ffs_flash_device is zero, detect device automatically by copying
+    // the detect function into RAM and execute it from there...
+    if (ffs_flash_manufact == 0 && ffs_flash_device == 0)
+    {
+#if (TARGET == 1)
+        char detect_code[80];
+        typedef (*pf_t)(uint16 *, uint16 *);
+        pf_t myfp;
+        uint16 device_id[3];
+
+        ffsdrv_device_id_read_copy_to_ram((uint16 *) detect_code, 
+                                          sizeof(detect_code));
+        // Combine bit 0 of the thumb mode function pointer with the address
+        // of the code in RAM. Then call the detect function in RAM.
+        myfp = (pf_t) (((int) &ffsdrv_device_id_read & 1) | (int) detect_code);
+        (*myfp)(&dev.manufact, device_id);
+
+        if ((dev.manufact == MANUFACT_AMD || dev.manufact == MANUFACT_FUJITSU) && 
+            device_id[0] == 0x227E) {
+            // This is a multi-id device
+            dev.device = (device_id[1] << 8) | (device_id[2] & 0xFF);
+        }
+        else 
+            dev.device = device_id[0];
+#endif
+    }
+    else {
+        dev.manufact = ffs_flash_manufact;
+        dev.device   = ffs_flash_device;
+    }
+
+    tw(tr(TR_FUNC, TrDrvInit, "TARGET = %d\n", TARGET));
+    tw(tr(TR_FUNC, TrDrvInit, "Looking up device (0x%2x,0x%4x): ",
+          dev.manufact, dev.device));
+    while (flash->manufact) {
+        tw(tr(TR_NULL, TrDrvInit, "(0x%02x,0x%04x) ",
+              flash->manufact, flash->device));
+        if (dev.manufact == flash->manufact && dev.device == flash->device) {
+            tw(tr(TR_NULL, TrDrvInit, "FOUND "));
+            break;
+        }
+        flash++;
+    }
+    tw(tr(TR_NULL, TrDrvInit, "\n"));
+
+    if (flash->manufact == 0) {
+        tw(tr(TR_END, TrDrvInit, "} (%d)\n", EFFS_NODEVICE));
+        return EFFS_NODEVICE;
+    }
+
+    dev.binfo     = (struct block_info_s *) flash->binfo;
+
+    if (flash->driver == FFS_DRIVER_RAM && flash->base == 0) {
+        if (ffs_ram_image_address <= 0) {
+            tw(tr(TR_END, TrDrvInit, "} (%d)\n", EFFS_DRIVER));
+            return EFFS_DRIVER;
+        }
+        dev.base = (char *) ffs_ram_image_address;
+    }
+    else 
+        dev.base = (char *) flash->base;
+
+    dev.numblocks = flash->numblocks;
+    dev.driver    = flash->driver;
+
+    // We assume that ALL blocks are of equal size
+    dev.blocksize_ld = dev.binfo[0].size_ld;
+    dev.blocksize    = (1 << dev.blocksize_ld);
+
+    dev.atomlog2 = FFS_ATOM_LOG2;
+    dev.atomsize = 1 << dev.atomlog2;
+    dev.atomnotmask = dev.atomsize - 1;
+
+#if (TARGET == 0)
+    if (dev.manufact == MANUFACT_TEST)
+        dev.base = ffsdrv_test_create();
+
+    p = &ffsdrv_test;
+
+#else // (TARGET == 1)
+
+    // Initialize hardware independent driver functions array
+    switch (dev.driver) {
+    case FFS_DRIVER_AMD:           p = &ffsdrv_amd; break;
+    case FFS_DRIVER_AMD_SB:        p = &ffsdrv_amd_sb; break;
+    case FFS_DRIVER_SST:           p = &ffsdrv_sst; break;
+    case FFS_DRIVER_SST_SB:        p = &ffsdrv_sst_sb; break;
+    case FFS_DRIVER_INTEL:         p = &ffsdrv_intel; break;
+    case FFS_DRIVER_INTEL_SB:      p = &ffsdrv_intel_sb; break;
+    case FFS_DRIVER_AMD_PSEUDO_SB: p = &ffsdrv_amd_pseudo_sb; break;
+    case FFS_DRIVER_RAM:           p = &ffsdrv_ram; break;
+    default:                       p = &ffsdrv_null; break;
+    }
+
+#endif // (TARGET == 0)
+
+    // Bind the driver functions
+    ffsdrv.init           = p->init;
+    ffsdrv.erase          = p->erase;
+    ffsdrv.write_halfword = p->write_halfword;
+    ffsdrv.write          = p->write;
+    ffsdrv.write_end      = p->write_end;
+    ffsdrv.erase_suspend  = p->erase_suspend;
+    ffsdrv.erase_resume   = p->erase_resume;
+
+    // Copy single bank driver code to RAM (and possibly re-bind some of the
+    // driver functions)
+    error = ffsdrv_driver_copy_to_ram(dev.driver);
+
+    if (error >= 0)
+        error = ffsdrv.init();
+
+    tw(tr(TR_FUNC, TrDrvInit, "dev.binfo     = 0x%x\n", (unsigned int) dev.binfo));
+    tw(tr(TR_FUNC, TrDrvInit, "dev.base      = 0x%x\n", (unsigned int) dev.base));
+    tw(tr(TR_FUNC, TrDrvInit, "dev.numblocks = %d\n", dev.numblocks));
+    tw(tr(TR_FUNC, TrDrvInit, "dev.blocksize = %d\n", dev.blocksize));
+    tw(tr(TR_FUNC, TrDrvInit, "dev.atomlog2/atomsize/atomnotmask = %d/%d/%x\n",
+          dev.atomlog2, dev.atomsize, dev.atomnotmask));
+    tw(tr(TR_END, TrDrvInit, "} %d\n", error));
+    ttw(ttr(TTrDrvOther, "} %d" NL, error));
+
+    return error;
+}
+
+
+/******************************************************************************
+ * Interrupt Enable/Disable
+ ******************************************************************************/
+
+// IMPORTANT NOTE! Apparently, locating this ARM assembly code at the top of
+// this file will make the compiler trash the A1 register between the calls
+// of arm_int_disable and arm_int_enable() thus crashing the whole system.
+// If the code is placed AFTER the usage of the functions, the compiler
+// saves the A1 register. Strange but true.
+
+// IMPORTANT NOTE! Apparently, another strange thing is that if the
+// functions are declared static, they don't work!
+
+// Executing code from RAM is NOT trivial when we need to jump between ROM
+// (flash) and RAM memory. The ARM only supports 26-bit relative branch
+// offsets. This is the reason why we have a local copy of the
+// arm_int_disable/enable() functions in this file plus each of the
+// single-bank drivers.
+
+#if (TARGET == 1)
+// Note that we use our own interrupt disable/enable function because
+// Nucleus allegedly should have a bug in its implementation for this.
+
+uint32 int_disable(void)
+{
+    asm("        .state16");
+    asm("        mov       A1, #0xC0");
+    asm("        ldr       A2, tct_disable");
+    asm("        bx        A2      ");
+
+    asm("tct_disable    .field     _TCT_Control_Interrupts+0,32");
+    asm("	            .global	   _TCT_Control_Interrupts");
+}
+
+void int_enable(uint32 cpsr)
+{
+    asm("        .state16");
+    asm("        ldr       A2, tct_enable");
+    asm("        bx        A2      ");
+
+    asm("tct_enable 	.field     _TCT_Control_Interrupts+0,32");
+    asm("	            .global	   _TCT_Control_Interrupts");
+}
+
+#else
+
+uint32 int_disable(void) { return 0; }
+void int_enable(uint32 tmp) {}
+
+#endif // (TARGET == 1)
+
+