/******************************************************************************
 * 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 char *flash = 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[0xAAAA] = 0xAA; // unlock cycle 1
    flash[0x5555] = 0x55; // unlock cycle 2
    flash[0xAAAA] = 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 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 = 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)


