//==========================================================================
//
//      am29xxxxx_aux.c
//
//      Flash driver for the AMD family - implementation. 
//
//==========================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
//
// eCos is free software; you can redistribute it and/or modify it under    
// the terms of the GNU General Public License as published by the Free     
// Software Foundation; either version 2 or (at your option) any later      
// version.                                                                 
//
// eCos is distributed in the hope that it will be useful, but WITHOUT      
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or    
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License    
// for more details.                                                        
//
// You should have received a copy of the GNU General Public License        
// along with eCos; if not, write to the Free Software Foundation, Inc.,    
// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.            
//
// As a special exception, if other files instantiate templates or use      
// macros or inline functions from this file, or you compile this file      
// and link it with other works to produce a work based on this file,       
// this file does not by itself cause the resulting work to be covered by   
// the GNU General Public License. However the source code for this file    
// must still be made available in accordance with section (3) of the GNU   
// General Public License v2.                                               
//
// This exception does not invalidate any other reasons why a work based    
// on this file might be covered by the GNU General Public License.         
// -------------------------------------------                              
// ####ECOSGPLCOPYRIGHTEND####                                              
//==========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):    bartv
// Contributors:
// Date:         2004-11-05
//              
//####DESCRIPTIONEND####
//
//==========================================================================

// This file is #include'd multiple times from the main am29xxxxx.c file,
// It serves to instantiate the various hardware operations in ways
// appropriate for all the bus configurations.

// The following macros are used to construct suitable function names
// for the current bus configuration. AM29_SUFFIX is #define'd before
// each #include of am29xxxxx_aux.c

#ifndef AM29_STR
# define AM29_STR1(_a_) # _a_
# define AM29_STR(_a_) AM29_STR1(_a_)
# define AM29_CONCAT3_AUX(_a_, _b_, _c_) _a_##_b_##_c_
# define AM29_CONCAT3(_a_, _b_, _c_) AM29_CONCAT3_AUX(_a_, _b_, _c_)
#endif

#define AM29_FNNAME(_base_) AM29_CONCAT3(_base_, _,  AM29_SUFFIX)

// Similarly construct a forward declaration, placing the function in
// the .2ram section. Each function must still be in a separate section
// for linker garbage collection.

# define AM29_RAMFNDECL(_base_, _args_) \
  AM29_FNNAME(_base_) _args_ __attribute__((section (".2ram." AM29_STR(_base_) "_" AM29_STR(AM29_SUFFIX))))

// Calculate the various offsets, based on the device count.
// The main code may override these settings for specific
// configurations, e.g. 16as8
#ifndef AM29_OFFSET_COMMAND
# define AM29_OFFSET_COMMAND            0x0555
#endif
#ifndef AM29_OFFSET_COMMAND2
# define AM29_OFFSET_COMMAND2           0x02AA
#endif
#ifndef AM29_OFFSET_MANUFACTURER_ID
# define AM29_OFFSET_MANUFACTURER_ID    0x0000
#endif
#ifndef AM29_OFFSET_DEVID
# define AM29_OFFSET_DEVID              0x0001
#endif
#ifndef AM29_OFFSET_DEVID2
# define AM29_OFFSET_DEVID2             0x000E
#endif
#ifndef AM29_OFFSET_DEVID3
# define AM29_OFFSET_DEVID3             0x000F
#endif
#ifndef AM29_OFFSET_CFI
# define AM29_OFFSET_CFI                0x0055
#endif
#ifndef AM29_OFFSET_CFI_DATA
# define AM29_OFFSET_CFI_DATA(_idx_)    _idx_
#endif
#ifndef AM29_OFFSET_AT49_LOCK_STATUS
# define AM29_OFFSET_AT49_LOCK_STATUS   0x02
#endif

// For parallel operation commands are issued in parallel and status
// bits are checked in parallel.
#ifndef AM29_PARALLEL
# define AM29_PARALLEL(_cmd_)    (_cmd_)
#endif

// ----------------------------------------------------------------------------
// Diagnostic routines.

#if 0
#define amd_diag( __fmt, ... ) diag_printf("AMD: %s[%d]: " __fmt, __FUNCTION__, __LINE__, ## __VA_ARGS__ );
#define amd_dump_buf( __addr, __size ) diag_dump_buf( __addr, __size )
#else
#define amd_diag( __fmt, ... )
#define amd_dump_buf( __addr, __size )
#endif

// ----------------------------------------------------------------------------
// When performing the various low-level operations like erase the flash
// chip can no longer support ordinary data reads. Obviously this is a
// problem if the current code is executing out of flash. The solution is
// to store the key functions in RAM rather than flash, via a special
// linker section .2ram which usually gets placed in the same area as
// .data.
//
// In a ROM startup application anything in .2ram will consume space
// in both the flash and RAM. Hence it is desirable to keep the .2ram
// functions as small as possible, responsible only for the actual
// hardware manipulation.
//
// All these .2ram functions should be invoked with interrupts
// disabled. Depending on the hardware it may also be necessary to
// have the data cache disabled. The .2ram functions must be
// self-contained, even macro invocations like HAL_DELAY_US() are
// banned because on some platforms those could be implemented as
// function calls.

// gcc requires forward declarations with the attributes, then the actual
// definitions.
static int  AM29_RAMFNDECL(am29_hw_query, (volatile AM29_TYPE*));
static int  AM29_RAMFNDECL(am29_hw_cfi, (struct cyg_flash_dev*, cyg_am29xxxxx_dev*, volatile AM29_TYPE*));
static int  AM29_RAMFNDECL(am29_hw_erase, (volatile AM29_TYPE*));
static int  AM29_RAMFNDECL(am29_hw_program, (volatile AM29_TYPE*, volatile AM29_TYPE*, const cyg_uint8*, cyg_uint32 count, int retries));
static int  AM29_RAMFNDECL(at49_hw_softlock,        (volatile AM29_TYPE*));
static int  AM29_RAMFNDECL(at49_hw_hardlock,        (volatile AM29_TYPE*));
static int  AM29_RAMFNDECL(at49_hw_unlock,          (volatile AM29_TYPE*));


// ----------------------------------------------------------------------------

#ifdef CYGHWR_DEVS_FLASH_AMD_AM29XXXXX_V2_RESET_NEEDS_RESUME
// With this flash component (e.g. AT49xxxxx), the reset does not
// cause a suspended erase/program to be aborted. Instead all we
// can do is resume any suspended operations. We do this on each
// block as some parts have different granularity.

static void
AM29_FNNAME(am29_hw_force_all_suspended_resume)(struct cyg_flash_dev* dev, cyg_am29xxxxx_dev* am29_dev, volatile AM29_TYPE* addr)
{
    cyg_ucount16 i,j;
    AM29_TYPE datum1, datum2;

    AM29_2RAM_ENTRY_HOOK();
    
    for (i=0; i<dev->num_block_infos; i++)
    {
        for (j=0; j<am29_dev->block_info[i].blocks; j++)
        {
            addr[AM29_OFFSET_COMMAND] = AM29_COMMAND_ERASE_RESUME;
            HAL_MEMORY_BARRIER();
            // We don't know if the suspended operation was an erase or
            // program, so just compare the whole word to spot _any_ toggling.
            do {
                datum1  = addr[AM29_OFFSET_COMMAND];
                datum2  = addr[AM29_OFFSET_COMMAND];
            } while (datum1 != datum2);

            addr += am29_dev->block_info[i].block_size/sizeof(AM29_TYPE);
        }
    }
    
    AM29_2RAM_EXIT_HOOK();
}
#endif // ifdef CYGHWR_DEVS_FLASH_AMD_AM29XXXXX_V2_RESET_NEEDS_RESUME

// Read the device id. This involves a straightforward command
// sequence, followed by a reset to get back into array mode.
// All chips are accessed in parallel, but only the response
// from the least significant is used.
static int
AM29_FNNAME(am29_hw_query)(volatile AM29_TYPE* addr)
{
    int devid;
    cyg_uint32 onedevmask;

    AM29_2RAM_ENTRY_HOOK();

    // Fortunately the compiler should optimise the below
    // tests such that onedevmask is a constant.
    if ( 1 == (sizeof(AM29_TYPE) / AM29_DEVCOUNT) )
        onedevmask = 0xFF;
    else if ( 2 == (sizeof(AM29_TYPE) / AM29_DEVCOUNT) )
        onedevmask = 0xFFFF;
    else {
        CYG_ASSERT( 4 == (sizeof(AM29_TYPE) / AM29_DEVCOUNT), 
                    "Unexpected flash width per device" );
        onedevmask = 0xFFFFFFFF;
    }
    
    addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_SETUP1;
    HAL_MEMORY_BARRIER();
    addr[AM29_OFFSET_COMMAND2]  = AM29_COMMAND_SETUP2;
    HAL_MEMORY_BARRIER();
    addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_AUTOSELECT;
    HAL_MEMORY_BARRIER();

    devid                       = AM29_UNSWAP(addr[AM29_OFFSET_DEVID]) & onedevmask;

//    amd_diag("devid %x\n", devid );
//    amd_dump_buf(addr, 64 );
    
    // The original AMD chips only used a single-byte device id, but
    // all codes have now been used up. Newer devices use a 3-byte
    // devid. The above devid read will have returned 0x007E. The
    // test allows for boards with a mixture of old and new chips.
    // The amount of code involved is too small to warrant a config
    // option.
    // FIXME by jifl: What happens when a single device is connected 16-bits
    // (or 32-bits) wide per device? Is the code still 0x7E, and all the
    // other devids are 8-bits only?
    if (0x007E == devid) {
        devid <<= 16;
        devid  |= ((AM29_UNSWAP(addr[AM29_OFFSET_DEVID2]) & 0x00FF) << 8);
        devid  |=  (AM29_UNSWAP(addr[AM29_OFFSET_DEVID3]) & 0x00FF);
    }
    addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_RESET;
    HAL_MEMORY_BARRIER();
    
//    amd_diag("devid %x\n", devid );
    
    AM29_2RAM_EXIT_HOOK();
    return devid;
}

// Perform a CFI query. This involves placing the device(s) into CFI
// mode, checking that this has really happened, and then reading the
// size and block info. The address corresponds to the start of the
// flash.
static int
AM29_FNNAME(am29_hw_cfi)(struct cyg_flash_dev* dev, cyg_am29xxxxx_dev* am29_dev, volatile AM29_TYPE* addr)
{
    int     dev_size;
    int     i;
    int     erase_regions;

    AM29_2RAM_ENTRY_HOOK();
    
#ifdef CYGHWR_DEVS_FLASH_AMD_AM29XXXXX_V2_CFI_BOGOSITY
    int     manufacturer_id;
    addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_SETUP1;
    HAL_MEMORY_BARRIER();
    addr[AM29_OFFSET_COMMAND2]  = AM29_COMMAND_SETUP2;
    HAL_MEMORY_BARRIER();
    addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_AUTOSELECT;
    HAL_MEMORY_BARRIER();

    manufacturer_id             = AM29_UNSWAP(addr[AM29_OFFSET_MANUFACTURER_ID]) & 0x00FF;
    addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_RESET;
    HAL_MEMORY_BARRIER();
#endif
    
    // Just a single write is needed to put the device into CFI mode
    addr[AM29_OFFSET_CFI]   = AM29_COMMAND_CFI;
    HAL_MEMORY_BARRIER();
//    amd_diag("CFI data:\n");
//    amd_dump_buf( addr, 256 );
    // Now check that we really are in CFI mode. There should be a 'Q'
    // at a specific address. This test is not 100% reliable, but should
    // be good enough.
    if ('Q' != (AM29_UNSWAP(addr[AM29_OFFSET_CFI_Q]) & 0x00FF)) {
        addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_RESET;
        HAL_MEMORY_BARRIER();
        AM29_2RAM_EXIT_HOOK();
        return CYG_FLASH_ERR_PROTOCOL;
    }
    // Device sizes are always a power of 2, and the shift is encoded
    // in a single byte
    dev_size = 0x01 << (AM29_UNSWAP(addr[AM29_OFFSET_CFI_SIZE]) & 0x00FF);
    dev->end = dev->start + dev_size - 1;

    // The number of erase regions is also encoded in a single byte.
    // Usually this is no more than 4. A value of 0 indicates that
    // only chip erase is supported, but the driver does not cope
    // with that.
    erase_regions   = AM29_UNSWAP(addr[AM29_OFFSET_CFI_BLOCK_REGIONS]) & 0x00FF;
    if (erase_regions > CYGNUM_DEVS_FLASH_AMD_AM29XXXXX_V2_ERASE_REGIONS) {
        addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_RESET;
        HAL_MEMORY_BARRIER();
        AM29_2RAM_EXIT_HOOK();
        return CYG_FLASH_ERR_PROTOCOL;
    }
    dev->num_block_infos    = erase_regions;

    for (i = 0; i < erase_regions; i++) {
        cyg_uint32 count, size;
        cyg_uint32 count_lsb   = AM29_UNSWAP(addr[AM29_OFFSET_CFI_BLOCK_COUNT_LSB(i)]) & 0x00FF;
        cyg_uint32 count_msb   = AM29_UNSWAP(addr[AM29_OFFSET_CFI_BLOCK_COUNT_MSB(i)]) & 0x00FF;
        cyg_uint32 size_lsb    = AM29_UNSWAP(addr[AM29_OFFSET_CFI_BLOCK_SIZE_LSB(i)]) & 0x00FF;
        cyg_uint32 size_msb    = AM29_UNSWAP(addr[AM29_OFFSET_CFI_BLOCK_SIZE_MSB(i)]) & 0x00FF;

        count = ((count_msb << 8) | count_lsb) + 1;
        size  = (size_msb << 16) | (size_lsb << 8);
        am29_dev->block_info[i].block_size  = (size_t) size * AM29_DEVCOUNT;
        am29_dev->block_info[i].blocks      = count;
    }

#ifdef CYGHWR_DEVS_FLASH_AMD_AM29XXXXX_V2_CFI_BOGOSITY

    // Some flash parts have a peculiar implementation of CFI. The
    // device erase regions may not be in the order specified in the
    // main CFI area. Instead the erase regions are given in a
    // manufacturer dependent fixed order, regardless of whether this
    // is a top or bottom boot block device. A vendor-specific
    // extended query block has an entry saying whether the boot
    // blocks are at the top or bottom. This code works out whether
    // the erase regions appear to be specified in the wrong order,
    // and then swaps them over.

    {
        enum { bottom, symmetric, top } boot_type = symmetric;
        cyg_uint32 vspec = AM29_SWAP(addr[AM29_OFFSET_CFI_DATA(0x15)]) & 0x00FF;

        // Take a look at the vendor specific area for the boot block
        // order.
        
        switch( manufacturer_id )
        {
            // Atmel appear to have their own layout for the vendor
            // specific area. Offset 0x06 of the vendor specific area
            // contains a single bit: 0x00 = top boot, 0x01 = bottom
            // boot. There appears to be no way of specifying
            // symmetric formats.
        case 0x1F:
            if( (addr[AM29_OFFSET_CFI_DATA(vspec+0x06)] & AM29_SWAP(0x1)) == AM29_SWAP(0x1) )
                boot_type = bottom;
            else boot_type = top;
            break;

            // Most other manufacturers seem to follow the same layout
            // and encoding. Offset 0xF of the vendor specific area
            // contains the boot sector layout: 0x00 = uniform, 0x01 =
            // 8x8k top and bottom, 0x02 = bottom boot, 0x03 = top
            // boot, 0x04 = both top and bottom.
            //
            // The following manufacturers support this layout:
            // AMD, Spansion, ST, Macronix.
        default:
            if( (addr[AM29_OFFSET_CFI_DATA(vspec+0xF)] == AM29_SWAP(0x2)) )
                boot_type = bottom;                
            else if( (addr[AM29_OFFSET_CFI_DATA(vspec+0xF)] == AM29_SWAP(0x3)) )
                boot_type = top;
            // All other options are symmetric
            break;
        }

        // If the device is marked as top boot, but the erase region
        // list appears to be in bottom boot order, then reverse the
        // list. Also swap, if it is marked as bottom boot but the
        // erase regions appear to be in top boot order. This code
        // assumes that the first boot block is always smaller than
        // regular blocks; it is possible to imagine flash layouts for
        // which that is not true.
        
        if( ((boot_type == top) &&
             (am29_dev->block_info[0].block_size < am29_dev->block_info[erase_regions-1].block_size)) ||
            ((boot_type == bottom) &&
             (am29_dev->block_info[0].block_size > am29_dev->block_info[erase_regions-1].block_size)))
        {
            int lo, hi;

            for( lo = 0, hi = erase_regions-1 ; lo < hi ; lo++, hi-- )
            {
                size_t size                          = am29_dev->block_info[lo].block_size;
                cyg_uint32 count                     = am29_dev->block_info[lo].blocks;
                am29_dev->block_info[lo].block_size  = am29_dev->block_info[hi].block_size;
                am29_dev->block_info[lo].blocks      = am29_dev->block_info[hi].blocks;
                am29_dev->block_info[hi].block_size  = size;
                am29_dev->block_info[hi].blocks      = count;
            }
        }
    }
#endif
        
    // Get out of CFI mode
    addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_RESET;
    HAL_MEMORY_BARRIER();

    AM29_2RAM_EXIT_HOOK();
    return CYG_FLASH_ERR_OK;
}

// Erase a single sector. There is no API support for chip-erase. The
// generic code operates one sector at a time, invoking the driver for
// each sector, so there is no opportunity inside the driver for
// erasing multiple sectors in a single call. The address argument
// points at the start of the sector.
static int
AM29_FNNAME(am29_hw_erase)(volatile AM29_TYPE* addr)
{
    int         retries;
    AM29_TYPE   datum;

    AM29_2RAM_ENTRY_HOOK();
    
    // Start the erase operation
    addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_SETUP1;
    HAL_MEMORY_BARRIER();
    addr[AM29_OFFSET_COMMAND2]  = AM29_COMMAND_SETUP2;
    HAL_MEMORY_BARRIER();
    addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_ERASE;
    HAL_MEMORY_BARRIER();
    addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_SETUP1;
    HAL_MEMORY_BARRIER();
    addr[AM29_OFFSET_COMMAND2]  = AM29_COMMAND_SETUP2;
    HAL_MEMORY_BARRIER();
    addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_ERASE_SECTOR;
    HAL_MEMORY_BARRIER();
    // There is now a 50us window in which we could send additional
    // ERASE_SECTOR commands, but the driver API does not allow this

    // All chips are now erasing in parallel. Loop until all have
    // completed. This can be detected in a number of ways. The DQ7
    // bit will be 0 until the erase is complete, but there is a
    // problem if something went wrong (e.g. the sector is locked),
    // the erase has not actually started, and the relevant bit was 0
    // already. More useful is DQ6. This will toggle during the 50us
    // window and while the erase is in progress, then stop toggling.
    // If the erase does not actually start then the bit won't toggle
    // at all so the operation completes rather quickly.
    //
    // If at any time DQ5 is set (indicating a timeout inside the
    // chip) then a reset command must be issued and the erase is
    // aborted. It is not clear this can actually happen during an
    // erase, but just in case.
    for (retries = CYGNUM_DEVS_FLASH_AMD_AM29XXXXX_V2_ERASE_TIMEOUT;
         retries > 0;
         retries--) {
        
        datum  = addr[AM29_OFFSET_COMMAND];
        // The operation completes when all DQ7 bits are set.
        if ((datum & AM29_STATUS_DQ7) == AM29_STATUS_DQ7) {
            break;
        }
        // Otherwise, for any flash chips where DQ7 is still clear, it is
        // necessary to test DQ5.
        if (((datum ^ AM29_STATUS_DQ7) >> 2) & datum & AM29_STATUS_DQ5) {
            // DQ5 is set, indicating a hardware error. The calling code
            // will always verify that the erase really was successful
            // so we do not need to distinguish between error conditions.
            addr[AM29_OFFSET_COMMAND] = AM29_COMMAND_RESET;
            HAL_MEMORY_BARRIER();
            break;
        }
    }

    // A result of 0 indicates a timeout.
    // A non-zero result indicates
    // that the erase completed or there has been a fatal error.
    AM29_2RAM_EXIT_HOOK();
    return retries;
}

// Write data to flash. At most one block will be processed at a time,
// but the write may be for a subset of the write. The destination
// address will be aligned in a way suitable for the bus. The source
// address need not be aligned. The count is in AM29_TYPE's, i.e.
// as per the bus alignment, not in bytes.
static int
AM29_FNNAME(am29_hw_program)(volatile AM29_TYPE* block_start, volatile AM29_TYPE* addr, const cyg_uint8* buf, cyg_uint32 count, int retries)
{
    int     i;

    AM29_2RAM_ENTRY_HOOK();
    
    for (i = 0; (i < count) && (retries > 0); i++) {
        AM29_TYPE   datum, current, active_dq7s;
        
        // We can only clear bits, not set them, so any bits that were
        // already clear need to be preserved.
        current = addr[i];
        datum   = AM29_NEXT_DATUM(buf) & current;
        if (datum == current) {
            // No change, so just move on.
            continue;
        }
        
        block_start[AM29_OFFSET_COMMAND]    = AM29_COMMAND_SETUP1;
        HAL_MEMORY_BARRIER();
        block_start[AM29_OFFSET_COMMAND2]   = AM29_COMMAND_SETUP2;
        HAL_MEMORY_BARRIER();
        block_start[AM29_OFFSET_COMMAND]    = AM29_COMMAND_PROGRAM;
        HAL_MEMORY_BARRIER();
        addr[i] = datum;
        HAL_MEMORY_BARRIER();

        // The data is now being written. The official algorithm is
        // to poll either DQ7 or DQ6, checking DQ5 along the way for
        // error conditions. This gets complicated with parallel
        // flash chips because they may finish at different times.
        // The alternative approach is to ignore the status bits
        // completely and just look for current==datum until the
        // retry count is exceeded. However that does not cope
        // cleanly with cases where the flash chip reports an error
        // early on, e.g. because a flash block is locked.

        while (--retries > 0) {
#if CYGNUM_DEVS_FLASH_AMD_AM29XXXXX_V2_PROGRAM_DELAY > 0
            // Some chips want a delay between polling
            { int j; for( j = 0; j < CYGNUM_DEVS_FLASH_AMD_AM29XXXXX_V2_PROGRAM_DELAY; j++ ); }
#endif
            // While the program operation is in progress DQ7 will read
            // back inverted from datum.
            current = addr[i];
            if ((current & AM29_STATUS_DQ7) == (datum & AM29_STATUS_DQ7)) {
                // All DQ7 bits now match datum, so the operation has completed.
                // But not necessarily successfully. On some chips DQ7 may
                // toggle before DQ0-6 are valid, so we need to read the
                // data one more time.
                current = addr[i];
                if (current != datum) {
                    retries = 0;    // Abort this burst.
                }
                break;
            }

            // Now we want to check the DQ5 status bits, but only for those
            // chips which are still programming. ((current^datum) & DQ7) gives
            // ones for chips which are still programming, zeroes for chips when
            // the programming is complete.
            active_dq7s = (current ^ datum) & AM29_STATUS_DQ7;
            
            if (0 != (current & (active_dq7s >> 2))) {
                // Unfortunately this is not sufficient to prove an error. On
                // some chips DQ0-6 switch to the data while DQ7 is still a
                // status flag, so the set DQ5 bit detected above may be data
                // instead of an error. Check again, this time DQ7 may
                // indicate completion.
                //
                // Next problem. Suppose chip A gave a bogus DQ5 result earlier
                // because it was just finishing. For this read chip A gives
                // back datum, but now chip B is finishing and has reported a
                // bogus DQ5.
                //
                // Solution: if any of the DQ7 lines have changed since the last
                // time, go around the loop again. When an error occurs DQ5
                // remains set and DQ7 remains toggled, so there is no harm
                // in one more polling loop.
                
                current = addr[i];
                if (((current ^ datum) & AM29_STATUS_DQ7) != active_dq7s) {
                    continue;
                }

                // DQ5 has been set in a chip where DQ7 indicates an ongoing
                // program operation for two successive reads. That is an error.
                // The hardware is in a strange state so must be reset.
                block_start[AM29_OFFSET_COMMAND]    = AM29_COMMAND_RESET;
                HAL_MEMORY_BARRIER();
                retries = 0;
                break;
            }
            // No DQ5 bits set in chips which are still programming. Poll again.
        }   // Retry for current word
    }       // Next word

    // At this point retries holds the total number of retries left.
    //  0 indicates a timeout or fatal error.
    // >0 indicates success.
    AM29_2RAM_EXIT_HOOK();
    return retries;
}

// FIXME: implement a separate program routine for buffered writes. 

#if 0
// Unused for now, but might be useful later
static int
AM29_FNNAME(at49_hw_is_locked)(volatile AM29_TYPE* addr)
{
    int result;
    AM29_TYPE plane;

    AM29_2RAM_ENTRY_HOOK();
    
    // Plane is bits A21-A20 for AT49BV6416
    // A more generic formula is needed.
    plane = AM29_PARALLEL( ((((CYG_ADDRESS)addr)>>21) & 0x3) );
    addr[AM29_OFFSET_COMMAND]         = AM29_COMMAND_SETUP1;
    HAL_MEMORY_BARRIER();
    addr[AM29_OFFSET_COMMAND2]        = AM29_COMMAND_SETUP2;
    HAL_MEMORY_BARRIER();
    addr[AM29_OFFSET_COMMAND + plane] = AM29_COMMAND_AUTOSELECT;
    HAL_MEMORY_BARRIER();
    result          = addr[AM29_OFFSET_AT49_LOCK_STATUS];
    addr[0]         = AM29_COMMAND_RESET;
    HAL_MEMORY_BARRIER();
    // The bottom two bits hold the lock status, LSB indicates
    // soft lock, next bit indicates hard lock. We don't distinguish
    // in this function.
    AM29_2RAM_EXIT_HOOK();
    return (0 != (result & AM29_ID_LOCKED));
}
#endif

static int
AM29_FNNAME(at49_hw_softlock)(volatile AM29_TYPE* addr)
{
    int result  = CYG_FLASH_ERR_OK;

    AM29_2RAM_ENTRY_HOOK();
    
    addr[AM29_OFFSET_COMMAND]         = AM29_COMMAND_SETUP1;
    HAL_MEMORY_BARRIER();
    addr[AM29_OFFSET_COMMAND2]        = AM29_COMMAND_SETUP2;
    HAL_MEMORY_BARRIER();
    addr[AM29_OFFSET_COMMAND]         = AM29_COMMAND_AT49_SOFTLOCK_BLOCK_0;
    HAL_MEMORY_BARRIER();
    addr[AM29_OFFSET_COMMAND]         = AM29_COMMAND_SETUP1;
    HAL_MEMORY_BARRIER();
    addr[AM29_OFFSET_COMMAND2]        = AM29_COMMAND_SETUP2;
    HAL_MEMORY_BARRIER();
    addr[0]                           = AM29_COMMAND_AT49_SOFTLOCK_BLOCK_1;
    HAL_MEMORY_BARRIER();
    // not sure if this is required:
    addr[0]                           = AM29_COMMAND_RESET;
    HAL_MEMORY_BARRIER();
    AM29_2RAM_EXIT_HOOK();
    return result;
}

static int
AM29_FNNAME(at49_hw_hardlock)(volatile AM29_TYPE* addr)
{
    int result  = CYG_FLASH_ERR_OK;

    AM29_2RAM_ENTRY_HOOK();
    
    addr[AM29_OFFSET_COMMAND]         = AM29_COMMAND_SETUP1;
    HAL_MEMORY_BARRIER();
    addr[AM29_OFFSET_COMMAND2]        = AM29_COMMAND_SETUP2;
    HAL_MEMORY_BARRIER();
    addr[AM29_OFFSET_COMMAND]         = AM29_COMMAND_AT49_HARDLOCK_BLOCK_0;
    HAL_MEMORY_BARRIER();
    addr[AM29_OFFSET_COMMAND]         = AM29_COMMAND_SETUP1;
    HAL_MEMORY_BARRIER();
    addr[AM29_OFFSET_COMMAND2]        = AM29_COMMAND_SETUP2;
    HAL_MEMORY_BARRIER();
    addr[0]                           = AM29_COMMAND_AT49_HARDLOCK_BLOCK_1;
    HAL_MEMORY_BARRIER();
    // not sure if this is required:
    addr[0]                           = AM29_COMMAND_RESET;
    HAL_MEMORY_BARRIER();
    AM29_2RAM_EXIT_HOOK();
    return result;
}

static int
AM29_FNNAME(at49_hw_unlock)(volatile AM29_TYPE* addr)
{
    int result  = CYG_FLASH_ERR_OK;

    AM29_2RAM_ENTRY_HOOK();
    
    addr[AM29_OFFSET_COMMAND]         = AM29_COMMAND_SETUP1;
    HAL_MEMORY_BARRIER();
    addr[0]                           = AM29_COMMAND_AT49_UNLOCK_BLOCK;
    HAL_MEMORY_BARRIER();
    // not sure if this is required:
    addr[0]                           = AM29_COMMAND_RESET;
    HAL_MEMORY_BARRIER();
    AM29_2RAM_EXIT_HOOK();
    return result;
}


// ----------------------------------------------------------------------------
// Exported code, mostly for placing in a cyg_flash_dev_funs structure.

// Just read the device id, either for sanity checking that the system
// has been configured for the right device, or for filling in the
// block info by a platform-specific init routine if the platform may
// be manufactured with one of several different chips.
int
AM29_FNNAME(cyg_am29xxxxx_read_devid) (struct cyg_flash_dev* dev)
{
    int                 (*query_fn)(volatile AM29_TYPE*);
    int                 devid;
    volatile AM29_TYPE* addr;
    AM29_INTSCACHE_STATE;

    CYG_CHECK_DATA_PTR(dev, "valid flash device pointer required");

    amd_diag("\n");
    
    addr     = AM29_UNCACHED_ADDRESS(dev->start);
    query_fn = (int (*)(volatile AM29_TYPE*)) cyg_flash_anonymizer( & AM29_FNNAME(am29_hw_query) );
    AM29_INTSCACHE_BEGIN();
    devid    = (*query_fn)(addr);
    AM29_INTSCACHE_END();
    return devid;
}

// Validate that the device statically configured is the one on the
// board.
int
AM29_FNNAME(cyg_am29xxxxx_init_check_devid)(struct cyg_flash_dev* dev)
{
    cyg_am29xxxxx_dev*  am29_dev;
    int                 devid;

    amd_diag("\n");
    
    am29_dev = (cyg_am29xxxxx_dev*) dev->priv;
    devid    = AM29_FNNAME(cyg_am29xxxxx_read_devid)(dev);
    if (devid != am29_dev->devid) {
        return CYG_FLASH_ERR_DRV_WRONG_PART;
    }

#ifdef CYGHWR_DEVS_FLASH_AMD_AM29XXXXX_V2_RESET_NEEDS_RESUME
    {
        volatile AM29_TYPE *addr = AM29_UNCACHED_ADDRESS(dev->start);
        void (*resume_fn)(struct cyg_flash_dev*, cyg_am29xxxxx_dev*, volatile AM29_TYPE*);
        resume_fn = (void (*)(struct cyg_flash_dev*, cyg_am29xxxxx_dev*, volatile AM29_TYPE*))
            cyg_flash_anonymizer( &AM29_FNNAME(am29_hw_force_all_suspended_resume) );
        AM29_INTSCACHE_STATE;

        AM29_INTSCACHE_BEGIN();
        (*resume_fn)(dev, am29_dev, addr);
        AM29_INTSCACHE_END();
    }
#endif

    // Successfully queried the device, and the id's match. That
    // should be a good enough indication that the flash is working.
    return CYG_FLASH_ERR_OK;
}

// Initialize via a CFI query, instead of statically specifying the
// boot block layout.
int
AM29_FNNAME(cyg_am29xxxxx_init_cfi)(struct cyg_flash_dev* dev)
{
    int                 (*cfi_fn)(struct cyg_flash_dev*, cyg_am29xxxxx_dev*, volatile AM29_TYPE*);
    volatile AM29_TYPE* addr;
    cyg_am29xxxxx_dev*  am29_dev;
    int                 result;
    AM29_INTSCACHE_STATE;
    
    amd_diag("\n");
    
    CYG_CHECK_DATA_PTR(dev, "valid flash device pointer required");
    am29_dev    = (cyg_am29xxxxx_dev*) dev->priv;    // Remove const, only place where this is needed.
    addr        = AM29_UNCACHED_ADDRESS(dev->start);
    cfi_fn      = (int (*)(struct cyg_flash_dev*, cyg_am29xxxxx_dev*, volatile AM29_TYPE*))
        cyg_flash_anonymizer( & AM29_FNNAME(am29_hw_cfi));

    AM29_INTSCACHE_BEGIN();
    result      = (*cfi_fn)(dev, am29_dev, addr);
    AM29_INTSCACHE_END();

    // Now calculate the device size, and hence the end field.
    if (CYG_FLASH_ERR_OK == result) {
        int i;
        int size    = 0;
        for (i = 0; i < dev->num_block_infos; i++) {
            amd_diag("region %d: 0x%08x * %d\n", i, (int)dev->block_info[i].block_size, dev->block_info[i].blocks );
            size += (dev->block_info[i].block_size * dev->block_info[i].blocks);
        }
        dev->end = dev->start + size - 1;

#ifdef CYGHWR_DEVS_FLASH_AMD_AM29XXXXX_V2_RESET_NEEDS_RESUME
    {
        void (*resume_fn)(struct cyg_flash_dev*, cyg_am29xxxxx_dev*, volatile AM29_TYPE*);
        resume_fn = (void (*)(struct cyg_flash_dev*, cyg_am29xxxxx_dev*, volatile AM29_TYPE*))
            cyg_flash_anonymizer( &AM29_FNNAME(am29_hw_force_all_suspended_resume) );

        AM29_INTSCACHE_BEGIN();
        (*resume_fn)(dev, am29_dev, addr);
        AM29_INTSCACHE_END();
    }
#endif
    }
    return result;
}

// Erase a single block. The calling code will have supplied a pointer
// aligned to a block boundary.
int
AM29_FNNAME(cyg_am29xxxxx_erase)(struct cyg_flash_dev* dev, cyg_flashaddr_t addr)
{
    int                 (*erase_fn)(volatile AM29_TYPE*);
    volatile AM29_TYPE* block;
    cyg_flashaddr_t     block_start;
    size_t              block_size;
    int                 i;
    int                 result;
    AM29_INTSCACHE_STATE;

    CYG_CHECK_DATA_PTR(dev, "valid flash device pointer required");
    CYG_ASSERT((addr >= dev->start) && (addr <= dev->end), "flash address out of device range");

    am29_get_block_info(dev, addr, &block_start, &block_size);
    CYG_ASSERT(addr == block_start, "erase address should be the start of a flash block");
    
    amd_diag("addr %p block %p[%d]\n", (void*)addr, (void*)block_start, (int)block_size );
    
    block       = AM29_UNCACHED_ADDRESS(addr);
    erase_fn    = (int (*)(volatile AM29_TYPE*)) cyg_flash_anonymizer( & AM29_FNNAME(am29_hw_erase) );

    AM29_INTSCACHE_BEGIN();
    result = (*erase_fn)(block);
    AM29_INTSCACHE_END();

    // The erase may have failed for a number of reasons, e.g. because
    // of a locked sector. The best thing to do here is to check that the
    // erase has succeeded.
    block = (AM29_TYPE*) addr;
    for (i = 0; i < (block_size / sizeof(AM29_TYPE)); i++) {
        if (block[i] != (AM29_TYPE)~0) {
            // There is no easy way of detecting the specific error,
            // e.g. locked flash block, timeout, ... So return a
            // useless catch-all error.
            return CYG_FLASH_ERR_ERASE;
        }
    }
    return CYG_FLASH_ERR_OK;
}

// Write some data to the flash. The destination must be aligned
// appropriately for the bus width (not the device width).
int
AM29_FNNAME(cyg_am29xxxxx_program)(struct cyg_flash_dev* dev, cyg_flashaddr_t dest, const void* src, size_t len)
{
    int                 (*program_fn)(volatile AM29_TYPE*, volatile AM29_TYPE*, const cyg_uint8*, cyg_uint32, int);
    volatile AM29_TYPE* block;
    volatile AM29_TYPE* addr; 
    cyg_flashaddr_t     block_start;
    size_t              block_size;
    const cyg_uint8*    data;
    int                 retries;
    int                 i;

    AM29_INTSCACHE_STATE;

    CYG_CHECK_DATA_PTR(dev, "valid flash device pointer required");

    amd_diag("dest %p src %p len %d\n", (void*)dest, (void*)src, (int)len );
    
    // Only support writes that are aligned to the bus boundary. This
    // may be more restrictive than what the hardware is capable of.
    // However it ensures that the hw_program routine can write as
    // much data as possible each iteration, and hence significantly
    // improves performance. The length had better be a multiple of
    // the bus width as well
    if ((0 != ((CYG_ADDRWORD)dest & (sizeof(AM29_TYPE) - 1))) ||
        (0 != (len & (sizeof(AM29_TYPE) - 1)))) {
        return CYG_FLASH_ERR_INVALID;
    }

    addr        = AM29_UNCACHED_ADDRESS(dest);
    CYG_ASSERT((dest >= dev->start) && (dest <= dev->end), "flash address out of device range");

    am29_get_block_info(dev, dest, &block_start, &block_size);
    CYG_ASSERT(((dest - block_start) + len) <= block_size, "write cannot cross block boundary");
    
    block       = AM29_UNCACHED_ADDRESS(block_start);
    data        = (const cyg_uint8*) src;
    retries     = CYGNUM_DEVS_FLASH_AMD_AM29XXXXX_V2_PROGRAM_TIMEOUT;

    program_fn  = (int (*)(volatile AM29_TYPE*, volatile AM29_TYPE*, const cyg_uint8*, cyg_uint32, int))
        cyg_flash_anonymizer( & AM29_FNNAME(am29_hw_program) );

    AM29_INTSCACHE_BEGIN();
    (*program_fn)(block, addr, data, len / sizeof(AM29_TYPE), retries);
    AM29_INTSCACHE_END();

    // Too many things can go wrong when manipulating the h/w, so
    // verify the operation by actually checking the data.
    addr = (volatile AM29_TYPE*) dest;
    data = (const cyg_uint8*) src;
    for (i = 0; i < (len / sizeof(AM29_TYPE)); i++) {
        AM29_TYPE   datum   = AM29_NEXT_DATUM(data);
        AM29_TYPE   current = addr[i];
        if ((datum & current) != current) {
            amd_diag("data %p addr[i] %p datum %08x current %08x\n", data-sizeof(AM29_TYPE), &addr[i], datum, current );
            return CYG_FLASH_ERR_PROGRAM;
        }
    }
    return CYG_FLASH_ERR_OK;
}

int
AM29_FNNAME(cyg_at49xxxx_softlock)(struct cyg_flash_dev* dev, const cyg_flashaddr_t dest)
{
    volatile AM29_TYPE*   uncached;
    int                     result;
    int (*lock_fn)(volatile AM29_TYPE*);
    AM29_INTSCACHE_STATE;

    amd_diag("\n");
    
    CYG_CHECK_DATA_PTR(dev, "valid flash device pointer required");
    CYG_ASSERT((dest >= (cyg_flashaddr_t)dev->start) &&
               ((CYG_ADDRESS)dest <= dev->end), "flash address out of device range");

    uncached    = AM29_UNCACHED_ADDRESS(dest);
    lock_fn     = (int (*)(volatile AM29_TYPE*)) cyg_flash_anonymizer( & AM29_FNNAME(at49_hw_softlock) );
    AM29_INTSCACHE_BEGIN();
    result  = (*lock_fn)(uncached);
    AM29_INTSCACHE_END();
    return result;
}

int
AM29_FNNAME(cyg_at49xxxx_hardlock)(struct cyg_flash_dev* dev, const cyg_flashaddr_t dest)
{
    volatile AM29_TYPE*   uncached;
    int                     result;
    int (*lock_fn)(volatile AM29_TYPE*);
    AM29_INTSCACHE_STATE;

    amd_diag("\n");
    
    CYG_CHECK_DATA_PTR(dev, "valid flash device pointer required");
    CYG_ASSERT((dest >= (cyg_flashaddr_t)dev->start) &&
               ((CYG_ADDRESS)dest <= dev->end), "flash address out of device range");

    uncached    = AM29_UNCACHED_ADDRESS(dest);
    lock_fn     = (int (*)(volatile AM29_TYPE*)) cyg_flash_anonymizer( & AM29_FNNAME(at49_hw_hardlock) );
    AM29_INTSCACHE_BEGIN();
    result  = (*lock_fn)(uncached);
    AM29_INTSCACHE_END();
    return result;
}

int
AM29_FNNAME(cyg_at49xxxx_unlock)(struct cyg_flash_dev* dev, const cyg_flashaddr_t dest)
{
    volatile AM29_TYPE* uncached;
    int                 result;
    int (*unlock_fn)(volatile AM29_TYPE*);
    AM29_INTSCACHE_STATE;

    amd_diag("\n");
    
    CYG_CHECK_DATA_PTR(dev, "valid flash device pointer required");
    CYG_ASSERT((dest >= (cyg_flashaddr_t)dev->start) &&
               ((CYG_ADDRESS)dest <= dev->end), "flash address out of device range");

    uncached = AM29_UNCACHED_ADDRESS(dest);
    unlock_fn = (int (*)(volatile AM29_TYPE*)) cyg_flash_anonymizer( & AM29_FNNAME(at49_hw_unlock) );

    AM29_INTSCACHE_BEGIN();
    result  = (*unlock_fn)(uncached);
    AM29_INTSCACHE_END();
    return result;
}

// ----------------------------------------------------------------------------
// Clean up the various #define's so this file can be #include'd again
#undef AM29_FNNAME
#undef AM29_RAMFNDECL
#undef AM29_OFFSET_COMMAND
#undef AM29_OFFSET_COMMAND2
#undef AM29_OFFSET_DEVID
#undef AM29_OFFSET_DEVID2
#undef AM29_OFFSET_DEVID3
#undef AM29_OFFSET_CFI
#undef AM29_OFFSET_CFI_DATA
#undef AM29_OFFSET_AT49_LOCK_STATUS
#undef AM29_PARALLEL
