//==========================================================================
//
//      s29gl_n.c
//
//      Flash driver for the Spansion S29GL-N family
//
//==========================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 2004, 2005, 2006, 2007 Free Software Foundation, Inc.      
// Copyright (C) 2004, 2005, 2006, 2007 eCosCentric Limited                 
//
// 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####
//
//==========================================================================

#include <pkgconf/devs_flash_spansion_s29gln_v2.h>
#include <cyg/infra/cyg_type.h>
#include <cyg/infra/cyg_ass.h>
#include <cyg/infra/diag.h>
#include <cyg/io/flash.h>
#include <cyg/io/flash_dev.h>
#include <cyg/io/s29gln_dev.h>
#include <cyg/hal/hal_arch.h>
#include <cyg/hal/hal_intr.h>
#include <cyg/hal/hal_cache.h>
#include <cyg/hal/hal_io.h>

// This driver supports multiple banks of SPANSION s29gln flash devices
// or compatibles. These are NOR-flash devices, requiring explicit
// erase operations with an erase value of 0xff.
//
// The devices may be 8-bit, 16-bit, or 32-bit (64-bit devices are not
// yet supported). Most but not all 16-bit devices can also be
// accessed as 8-bit, in which case the chip may be hooked up to an
// 8-bit bus. A bank of flash may involve just a single chip, or there
// may be several chips in parallel. Typical combinations are 88 to
// get 16-bit, 8888 for 32-bit, and 1616 for 32-bit. It is assumed
// that all chips within a bank are the same device. There may also be
// several banks of flash, and different banks may use different
// devices.
//
// This driver instantiates support for the various bus
// configurations: 8, 16, 16AS8, 32, 88, 8888, and 1616. On any given
// platform only one or two of these combinations will be of interest,
// but the remainder will be eliminated via linker garbage collection.
// To avoid excessive duplication an auxiliary file contains the
// actual implementations. Compiler optimization should eliminate any
// unnecessary code.

// A flash driver is supposed to provide the following functions:
//  int     (*init)(...)
//  size_t  (*query)(...)
//  int     (*erase)(...)
//  int     (*program)(...)
//  int     (*block_lock)(...)
//  int     (*block_unlock)(...)
//
// The devices do not need any special initialization. However a given
// board may be manufactured with any one of several devices, which
// complicates things. The main complication is that there may be
// different bootsector layouts. The primary job of the init function
// is to check the device id, possibly fill in the bootsector info,
// or even to use the CFI support to get the bootsector info from the
// device itself. There may be other complications, e.g. minor variations
// of a given board design. These can be handled by h/w specific init
// functions in the platform HAL.
//
// The query function need not do anything useful, it is
// driver-defined.
//
// No read function need be supplied because the flash memory is
// always directly accessible to the cpu.
//
// Erase, program, and the locking functions need real
// implementations, although locking is not always available.

// ----------------------------------------------------------------------------
// The protocol understood by SPANSION flash chips and compatibles.
// The AM29_PARALLEL() macro is used in bus configurations with multiple
// devices in parallel, to issue commands to all the devices in a single
// write. In theory some of the operations, e.g. READ_DEVID, only need
// to access a single chip but then you get into complications for the
// SETUP commands. The AM29_SWAP() macro deals with endianness issues
// on some targets and can also help with h/w where things are just not
// wired right.
#define S29_COMMAND_SETUP1                  S29_SWAP(S29_PARALLEL(0x00AA))
#define S29_COMMAND_SETUP2                  S29_SWAP(S29_PARALLEL(0x0055))
#define S29_COMMAND_RESET                   S29_SWAP(S29_PARALLEL(0x00F0))
#define S29_COMMAND_AUTOSELECT              S29_SWAP(S29_PARALLEL(0x0090))
#define S29_COMMAND_ERASE                   S29_SWAP(S29_PARALLEL(0x0080))
#define S29_COMMAND_ERASE_SECTOR            S29_SWAP(S29_PARALLEL(0x0030))
#define S29_COMMAND_ERASE_SUSPEND           S29_SWAP(S29_PARALLEL(0x00B0))
#define S29_COMMAND_ERASE_RESUME            S29_SWAP(S29_PARALLEL(0x0030))
#define S29_COMMAND_CFI                     S29_SWAP(S29_PARALLEL(0x0098))
#define S29_COMMAND_PROGRAM                 S29_SWAP(S29_PARALLEL(0x00A0))
// Following are specific to AT49 derivatives
#define S29_COMMAND_AT49_SOFTLOCK_BLOCK_0   S29_SWAP(S29_PARALLEL(0x0080))
#define S29_COMMAND_AT49_SOFTLOCK_BLOCK_1   S29_SWAP(S29_PARALLEL(0x0040))
#define S29_COMMAND_AT49_HARDLOCK_BLOCK_0   S29_SWAP(S29_PARALLEL(0x0080))
#define S29_COMMAND_AT49_HARDLOCK_BLOCK_1   S29_SWAP(S29_PARALLEL(0x0060))
#define S29_COMMAND_AT49_UNLOCK_BLOCK       S29_SWAP(S29_PARALLEL(0x0070))

// CFI offsets of interest. This assumes that the standard query table
// has not been replaced by the extended query table, although the
// CFI standard allows that behaviour.
#define S29_OFFSET_CFI_Q                       S29_OFFSET_CFI_DATA(0x0010)
#define S29_OFFSET_CFI_SIZE                    S29_OFFSET_CFI_DATA(0x0027)
#define S29_OFFSET_CFI_BLOCK_REGIONS           S29_OFFSET_CFI_DATA(0x002C)
#define S29_OFFSET_CFI_BLOCK_COUNT_LSB(_i_)    S29_OFFSET_CFI_DATA(0x002D + (4 * (_i_)))
#define S29_OFFSET_CFI_BLOCK_COUNT_MSB(_i_)    S29_OFFSET_CFI_DATA(0x002E + (4 * (_i_)))
#define S29_OFFSET_CFI_BLOCK_SIZE_LSB(_i_)     S29_OFFSET_CFI_DATA(0x002F + (4 * (_i_)))
#define S29_OFFSET_CFI_BLOCK_SIZE_MSB(_i_)     S29_OFFSET_CFI_DATA(0x0030 + (4 * (_i_)))

#define S29_STATUS_DQ7             S29_SWAP(S29_PARALLEL(0x0080))
#define S29_STATUS_DQ6             S29_SWAP(S29_PARALLEL(0x0040))
#define S29_STATUS_DQ5             S29_SWAP(S29_PARALLEL(0x0020))
#define S29_STATUS_DQ4             S29_SWAP(S29_PARALLEL(0x0010))
#define S29_STATUS_DQ3             S29_SWAP(S29_PARALLEL(0x0008))
#define S29_STATUS_DQ2             S29_SWAP(S29_PARALLEL(0x0004))
#define S29_STATUS_DQ1             S29_SWAP(S29_PARALLEL(0x0002))
#define S29_STATUS_DQ0             S29_SWAP(S29_PARALLEL(0x0001))
#define S29_ID_LOCKED              S29_SWAP(S29_PARALLEL(0x03))

// When programming the flash the source data may not be aligned
// correctly (although usually it will be). Hence it is necessary to
// construct the 16-bit or 32-bit numbers to be written to the flash
// from individual bytes, allowing for endianness.
#define S29_NEXT_DATUM_8(_ptr_) (*_ptr_++)
#if CYG_BYTEORDER == CYG_LSBFIRST
# define S29_NEXT_DATUM_16(_ptr_)                  \
    ({                                              \
        cyg_uint16 _result_;                        \
        _result_  = (_ptr_[1] << 8) | _ptr_[0];     \
        _ptr_    += 2;                              \
        _result_; })

# define S29_NEXT_DATUM_32(_ptr_)                                                      \
    ({                                                                                  \
        cyg_uint32 _result_;                                                            \
        _result_  = (_ptr_[3] << 24) | (_ptr_[2] << 16) | (_ptr_[1] << 8) | _ptr_[0];   \
        _ptr_    += 4;                                                                  \
        _result_; })
#else
# define S29_NEXT_DATUM_16(_ptr_)                  \
    ({                                              \
        cyg_uint16 _result_;                        \
        _result_  = (_ptr_[0] << 8) | _ptr_[1];     \
        _ptr_    += 2;                              \
        _result_; })

# define S29_NEXT_DATUM_32(_ptr_)                                                      \
    ({                                                                                  \
        cyg_uint32 _result_;                                                            \
        _result_  = (_ptr_[0] << 24) | (_ptr_[1] << 16) | (_ptr_[2] << 8) | _ptr_[3];   \
        _ptr_    += 4;                                                                  \
        _result_; })

#endif

// The addresses used for programming the flash may be different from
// the ones used to read the flash. The macro
// HAL_S29XXXXX_UNCACHED_ADDRESS() can be supplied by one of the HAL
// packages. Otherwise if CYGHWR_DEVS_FLASH_SPANSION_S29GL_N_CACHED_ONLY
// is not implemented then the macro CYGARC_UNCACHED_ADDRESS()
// will be used. If there is no way of bypassing the cache then
// the addresses will remain unchanged and instead the INTSCACHE
// macros will disable the cache.

#if defined(HAL_S29GLN_UNCACHED_ADDRESS)
# define S29_UNCACHED_ADDRESS(_addr_)  ((volatile S29_TYPE*)HAL_S29GLN_UNCACHED_ADDRESS(_addr_))
#elif !defined(CYGHWR_DEVS_FLASH_SPANSION_S29GLN_V2_CACHED_ONLY)
# ifndef CYGARC_UNCACHED_ADDRESS
#  error Cache should be bypassed but CYGARC_UNCACHED_ADDRESS is not defined.
# endif
# define S29_UNCACHED_ADDRESS(_addr_)  ((volatile S29_TYPE*)CYGARC_UNCACHED_ADDRESS(_addr_))
#elif defined(HAL_S29GLN_P2V)
// HAL_S29GL_N_P2V is a deprecated macro that is only retained for
// backward compatibility.
# define S29_UNCACHED_ADDRESS(_addr_)  ((volatile S29_TYPE*)HAL_S29GLN_P2V(_addr_))
#else
# define S29_UNCACHED_ADDRESS(_addr_)  ((volatile S29_TYPE*)(_addr_))
#endif

// The bits on the data bus may need swapping, either because of
// endianness issues or because some lines are just wired wrong.
// SWAP is for commands going to the flash chip. UNSWAP is for
// data coming back from the flash chip. The swapping takes
// effect after allowing for S29_PARALLEL(). Data is never
// swapped, it does not matter if bit 5 of a datum is actually
// stored in bit 3 of the flash as long as the data reads back
// right.
#if defined(HAL_S29GLN_SWAP)
# define S29_SWAP(_data_)      HAL_S29GLN_SWAP(_data_)
#else
# define S29_SWAP(_data_)      (_data_)
#endif
#if defined(HAL_S29GLN_UNSWAP)
# define S29_UNSWAP(_data_)    HAL_S29GLN_UNSWAP(_data_)
#else
# define S29_UNSWAP(_data_)    (_data_)
#endif

// On some platforms there may be almost inexplicable failures, caused
// by very subtle effects such as instruction cache lines still being
// filled from flash memory which the _hw routines in .2ram sections are
// already running and have taken the flash out of read-array mode.
// These are very rare effects and not amenable to a generic solution,
// so instead the platform HAL (usually) can define additional hook
// macros that get invoked by the .2ram functions. These can e.g.
// add a short delay or invalidate a couple of instruction cache lines,
// but only if the code is executing from flash. Any such hooks will
// affect interrupt latency so should only be used when absolutely
// necessary. They must also be simple code, e.g. no calls to other
// functions that may be in flash.

#ifdef HAL_S29GLN_2RAM_ENTRY_HOOK
# define S29_2RAM_ENTRY_HOOK() HAL_S29GLN_2RAM_ENTRY_HOOK()
#else
# define S29_2RAM_ENTRY_HOOK() CYG_EMPTY_STATEMENT
#endif
#ifdef HAL_S29GLN_2RAM_EXIT_HOOK
# define S29_2RAM_EXIT_HOOK()  HAL_S29GLN_2RAM_EXIT_HOOK()
#else
# define S29_2RAM_EXIT_HOOK()  CYG_EMPTY_STATEMENT
#endif

// Cache and interrupt manipulation. This driver supports fine-grained
// control over interrupts and the cache, using five macros. These may
// be provided by the platform HAL, or by defaults here. There are
// three variants:
//
// 1) control both interrupts and cache. This is necessary if
//    CYGHWR_DEVS_FLASH_SPANSION_S29GLN_V2_CACHED_ONLY is implemented,
//    i.e. if the cache cannot be bypassed. The cache must be temporarily
//    disabled for flash operations, and interrupts have to be disabled
//    while the cache is disabled to prevent interrupts and context switches.
// 2) control interrupts only, the default if the cache can be bypassed
//    when accessing the flash. The flash is still in an unusable
//    state during flash operations so interrupts and context switches
//    should be avoided.
// 3) only invalidate at the end, if the cache can be bypassed and the
//    application guarantees that the flash will not be accessed by any interrupt
//    handlers or other threads.

#if defined(CYGHWR_DEVS_FLASH_SPANSION_S29GLN_V2_CACHED_ONLY)

// First, the amount of state that should be preserved. By default
// this means the interrupt state and the data cache state.
# define S29_INTSCACHE_DEFAULT_STATE   int _saved_ints_, _saved_dcache_

// Start an operation on the flash. Make sure that interrupts are
// disabled and then save the current state of the data cache. The
// actual flash manipulation should happen with the cache disabled.
// There may still be data in the cache that has not yet been flushed
// to memory, so take care of that first. The invalidate the cache
// lines so that when the cache is re-enabled later on the processor
// gets everything from memory, rather than reusing old data in the
// cache.
# define S29_INTSCACHE_DEFAULT_BEGIN()         \
    CYG_MACRO_START                             \
    HAL_DISABLE_INTERRUPTS(_saved_ints_);       \
    HAL_DCACHE_IS_ENABLED(_saved_dcache_);      \
    HAL_DCACHE_SYNC();                          \
    if (_saved_dcache_) {                       \
        HAL_DCACHE_DISABLE();                   \
    }                                           \
    HAL_DCACHE_INVALIDATE_ALL();                \
    CYG_MACRO_END

// Suspend an operation, allowing interrupt handlers to run and giving
// other threads a chance as well. The flash is currently in normal
// array read mode, although the current block may be in an
// inconsistent state.
//
// The cache must be re-enabled if appropriate, and ditto for
// interrupts. At this point cache lines may get filled again from
// memory. However we assume that no other code will be manipulating
// the flash block or any variables on the stack, so as far as this
// flash operation is concerned there are no inconsistencies.

#define S29_INTSCACHE_DEFAULT_SUSPEND()        \
    CYG_MACRO_START                             \
    if (_saved_dcache_) {                       \
        HAL_DCACHE_ENABLE();                    \
    }                                           \
    HAL_RESTORE_INTERRUPTS(_saved_ints_);       \
    CYG_MACRO_END

// Continue manipulating the flash. The cache may now contain various
// lines because of interrupt handlers and other threads, but we
// assume that no other code is manipulating the data this flash
// operation is interested in (the current stack, the flash block
// being manipulated, or the source data for a program). Hence we
// skip the SYNC() and INVALIDATE() for efficiency reasons.
//
// There is an assumption that no other code has disabled/enabled the
// cache in the meantime, i.e. that _saved_dcache_ is still valid.
// Arguably there should be another call to IS_ENABLED().
//
// There is also an assumption that the hardware can actually work
// like this, i.e. have some lines remain valid in the cache while
// running with the cache disabled. This is a reasonable assumption
// as long as the hardware is not trying to do anything too clever.

#define S29_INTSCACHE_DEFAULT_RESUME()         \
    CYG_MACRO_START                             \
    HAL_DISABLE_INTERRUPTS(_saved_ints_);       \
    if (_saved_dcache_) {                       \
        HAL_DCACHE_DISABLE();                   \
    }                                           \
    CYG_MACRO_END

// A flash operation has completed. Restore the situation to what it
// was before. Because of suspend/resume support interrupt handlers
// and other threads may have run, filling various cache lines with
// useful data. However it is assumed that none of those cache
// lines contain any of the data that has been manipulated by this
// flash operation (the stack and the flash block), so there is
// no need for another sync or invalidate. It is also assumed that
// we have not been executing any code out of the block of flash
// that has just been erased or programmed, so no need to worry
// about the icache.
#define S29_INTSCACHE_DEFAULT_END()            \
    CYG_MACRO_START                             \
    if (_saved_dcache_) {                       \
        HAL_DCACHE_ENABLE();                    \
    }                                           \
    HAL_RESTORE_INTERRUPTS(_saved_ints_);       \
    CYG_MACRO_END

#elif !defined(CYGIMP_DEVS_FLASH_SPANSION_S29GLN_V2_LEAVE_INTERRUPTS_ENABLED)

# define S29_INTSCACHE_DEFAULT_STATE     int _saved_ints_
# define S29_INTSCACHE_DEFAULT_BEGIN()   HAL_DISABLE_INTERRUPTS(_saved_ints_)
# define S29_INTSCACHE_DEFAULT_SUSPEND() HAL_RESTORE_INTERRUPTS(_saved_ints_)
# define S29_INTSCACHE_DEFAULT_RESUME()  HAL_DISABLE_INTERRUPTS(_saved_ints_)

# if defined(HAL_DCACHE_SYNC) && defined(HAL_DCACHE_INVALIDATE_ALL)
// The following blips the interrupt enable to allow pending interrupts
// to run, which will reduce interrupt latency given the dcache sync/invalidate
// may be relatively lengthy.
#  define S29_INTSCACHE_DEFAULT_END()          \
    CYG_MACRO_START                             \
    HAL_RESTORE_INTERRUPTS(_saved_ints_);       \
    HAL_DISABLE_INTERRUPTS(_saved_ints_);       \
    HAL_DCACHE_SYNC();                          \
    HAL_DCACHE_INVALIDATE_ALL();                \
    HAL_RESTORE_INTERRUPTS(_saved_ints_);       \
    CYG_MACRO_END
# else
#  define S29_INTSCACHE_DEFAULT_END()    HAL_RESTORE_INTERRUPTS(_saved_ints_)
# endif
#else

# define S29_INTSCACHE_DEFAULT_STATE     CYG_EMPTY_STATEMENT
# define S29_INTSCACHE_DEFAULT_BEGIN()   CYG_EMPTY_STATEMENT
# define S29_INTSCACHE_DEFAULT_SUSPEND() CYG_EMPTY_STATEMENT
# define S29_INTSCACHE_DEFAULT_RESUME()  CYG_EMPTY_STATEMENT
# if defined(HAL_DCACHE_SYNC) && defined(HAL_DCACHE_INVALIDATE_ALL)
#  define S29_INTSCACHE_DEFAULT_END()          \
    CYG_MACRO_START                             \
    int _saved_ints_;                           \
    HAL_DISABLE_INTERRUPTS(_saved_ints_);       \
    HAL_DCACHE_SYNC();                          \
    HAL_DCACHE_INVALIDATE_ALL();                \
    HAL_RESTORE_INTERRUPTS(_saved_ints_);       \
    CYG_MACRO_END
# else
#  define S29_INTSCACHE_DEFAULT_END()    CYG_EMPTY_STATEMENT
# endif
#endif

#ifdef HAL_S29GLN_INTSCACHE_STATE
# define S29_INTSCACHE_STATE       HAL_S29GLN_INTSCACHE_STATE
#else
# define S29_INTSCACHE_STATE       S29_INTSCACHE_DEFAULT_STATE
#endif
#ifdef HAL_S29GLN_INTSCACHE_BEGIN
# define S29_INTSCACHE_BEGIN       HAL_S29GLN_INTSCACHE_BEGIN
#else
# define S29_INTSCACHE_BEGIN       S29_INTSCACHE_DEFAULT_BEGIN
#endif
#ifdef HAL_S29GLN_INTSCACHE_SUSPEND
# define S29_INTSCACHE_SUSPEND     HAL_S29GLN_INTSCACHE_SUSPEND
#else
# define S29_INTSCACHE_SUSPEND     S29_INTSCACHE_DEFAULT_SUSPEND
#endif
#ifdef HAL_S29GLN_INTSCACHE_RESUME
# define S29_INTSCACHE_RESUME      HAL_S29GLN_INTSCACHE_RESUME
#else
# define S29_INTSCACHE_RESUME      S29_INTSCACHE_DEFAULT_RESUME
#endif
#ifdef HAL_S29GLN_INTSCACHE_END
# define S29_INTSCACHE_END         HAL_S29GLN_INTSCACHE_END
#else
# define S29_INTSCACHE_END         S29_INTSCACHE_DEFAULT_END
#endif

// Some HALs require a special instruction to flush write buffers.
// Not all HALs do though, so we define it empty if it isn't already present.
#ifndef HAL_MEMORY_BARRIER
# define HAL_MEMORY_BARRIER() CYG_EMPTY_STATEMENT
#endif

// ----------------------------------------------------------------------------
// Generic code.

// Get info about the current block, i.e. base and size.
static void
s29_get_block_info(struct cyg_flash_dev* dev, const cyg_flashaddr_t addr, cyg_flashaddr_t* block_start, size_t* block_size)
{
    cyg_uint32      i;
    size_t          offset  = addr - dev->start;
    cyg_flashaddr_t result;

    result  = dev->start;
    
    for (i = 0; i < dev->num_block_infos; i++) {
        if (offset < (dev->block_info[i].blocks * dev->block_info[i].block_size)) {
            offset         -= (offset % dev->block_info[i].block_size);
            *block_start    = result + offset;
            *block_size     = dev->block_info[i].block_size;
            return;
        }
        result  += (dev->block_info[i].blocks * dev->block_info[i].block_size);
        offset  -= (dev->block_info[i].blocks * dev->block_info[i].block_size);
    }
    CYG_FAIL("Address out of range of selected flash device");
}

// ----------------------------------------------------------------------------
// Instantiate all of the h/w functions appropriate for the various
// configurations.
//   The suffix is used to construct the function names.
//   Types for the width of the bus, controlling the granularity of access.
//   devcount specifies the number of devices in parallel, and is used for looping
//   The NEXT_DATUM() macro allows for misaligned source data.
//   The PARALLEL macro, if defined, is used for sending commands and reading
//   status bits from all devices in the bank in one operation.

// A single 8-bit device on an 8-bit bus.
#define S29_SUFFIX             8
#define S29_TYPE               cyg_uint8
#define S29_DEVCOUNT           1
#define S29_NEXT_DATUM(_ptr_)  S29_NEXT_DATUM_8(_ptr_)

#include "s29gl-n_aux.c"

#undef S29_SUFFIX
#undef S29_TYPE
#undef S29_DEVCOUNT
#undef S29_NEXT_DATUM

// A single 16-bit device.
#define S29_SUFFIX             16
#define S29_TYPE               cyg_uint16
#define S29_DEVCOUNT           1
#define S29_NEXT_DATUM(_ptr_)  S29_NEXT_DATUM_16(_ptr_)

#include "s29gl-n_aux.c"

#undef S29_SUFFIX
#undef S29_TYPE
#undef S29_DEVCOUNT
#undef S29_NEXT_DATUM

// A single 32-bit device.
#define S29_SUFFIX             32
#define S29_TYPE               cyg_uint32
#define S29_DEVCOUNT           1
#define S29_NEXT_DATUM(_ptr_)  S29_NEXT_DATUM_32(_ptr_)

#include "s29gl-n_aux.c"

#undef S29_SUFFIX
#undef S29_TYPE
#undef S29_DEVCOUNT
#undef S29_NEXT_DATUM

// Two 8-bit devices, giving a 16-bit bus. 
#define S29_SUFFIX             88
#define S29_TYPE               cyg_uint16
#define S29_DEVCOUNT           2
#define S29_NEXT_DATUM(_ptr_)  S29_NEXT_DATUM_16(_ptr_)
#define S29_PARALLEL(_cmd_)     ((_cmd_ << 8) | _cmd_)

#include "s29gl-n_aux.c"

#undef S29_SUFFIX
#undef S29_TYPE
#undef S29_DEVCOUNT
#undef S29_NEXT_DATUM

// Four 8-bit devices, giving a 32-bit bus. 
#define S29_SUFFIX             8888
#define S29_TYPE               cyg_uint32
#define S29_DEVCOUNT           4
#define S29_NEXT_DATUM(_ptr_)  S29_NEXT_DATUM_32(_ptr_)
#define S29_PARALLEL(_cmd_)    ((_cmd_ << 24) | (_cmd_ << 16) | (_cmd_ << 8) | _cmd_)

#include "s29gl-n_aux.c"

#undef S29_SUFFIX
#undef S29_TYPE
#undef S29_DEVCOUNT
#undef S29_NEXT_DATUM

// Two 16-bit devices, giving a 32-bit bus.
#define S29_SUFFIX             1616
#define S29_TYPE               cyg_uint32
#define S29_DEVCOUNT           2
#define S29_NEXT_DATUM(_ptr_)  S29_NEXT_DATUM_32(_ptr_)
#define S29_PARALLEL(_cmd_)    ((_cmd_ << 16) | _cmd_)

#include "s29gl-n_aux.c"

#undef S29_SUFFIX
#undef S29_TYPE
#undef S29_DEVCOUNT
#undef S29_NEXT_DATUM

// 16AS8. A 16-bit device hooked up so that only byte accesses are
// allowed. This requires unusual offsets
#define S29_SUFFIX                 16as8
#define S29_TYPE                   cyg_uint8
#define S29_DEVCOUNT               1
#define S29_NEXT_DATUM(_ptr_)      S29_NEXT_DATUM_8(_ptr_)
#define S29_OFFSET_COMMAND         0x0AAA
#define S29_OFFSET_COMMAND2        0x0555
#define S29_OFFSET_MANUFACTURER_ID 0x0000
#define S29_OFFSET_DEVID           0x0002
#define S29_OFFSET_DEVID2          0x001C
#define S29_OFFSET_DEVID3          0x001E
#define S29_OFFSET_AT49_LOCK_STATUS 04
#define S29_OFFSET_CFI             0x00AA
#define S29_OFFSET_CFI_DATA(_idx_) (2 * (_idx_))

#include "s29gl-n_aux.c"
