diff packages/devs/flash/amd/am29xxxxxv2/current/src/am29xxxxx_aux.c @ 1739:1773ef378c6f

Add new AM29xxxxx V2 flash driver
author bartv
date Sat, 20 Nov 2004 18:09:53 +0000
parents
children a15c7003d1da
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/packages/devs/flash/amd/am29xxxxxv2/current/src/am29xxxxx_aux.c
@@ -0,0 +1,497 @@
+//==========================================================================
+//
+//      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 eCosCentric Ltd
+//
+// 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.,
+// 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
+//
+// 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_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
+
+// For parallel operation commands are issued in parallel and status
+// bits are checked in parallel.
+#ifndef AM29_PARALLEL
+# define AM29_PARALLEL(_cmd_)    (_cmd_)
+#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 void AM29_RAMFNDECL(am29_hw_erase, (volatile AM29_TYPE*));
+static void AM29_RAMFNDECL(am29_hw_program, (volatile AM29_TYPE*, volatile AM29_TYPE*, const cyg_uint8*, cyg_uint32 count));
+
+// 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;
+    
+    addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_SETUP1;
+    addr[AM29_OFFSET_COMMAND2]  = AM29_COMMAND_SETUP2;
+    addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_AUTOSELECT;
+    devid                       = addr[AM29_OFFSET_DEVID] & 0x00FF;
+
+    // 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.
+    if (0x007E == devid) {
+        devid <<= 16;
+        devid  |= ((addr[AM29_OFFSET_DEVID2] & 0x00FF) << 8);
+        devid  |=  (addr[AM29_OFFSET_DEVID3] & 0x00FF);
+    }
+    addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_RESET;
+    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;
+
+    // Just a single write is needed to put the device into CFI mode
+    addr[AM29_OFFSET_CFI]   = AM29_COMMAND_CFI;
+    // 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' != (addr[AM29_OFFSET_CFI_Q] & 0x00FF)) {
+        addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_RESET;
+        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 << (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   = 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;
+        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   = addr[AM29_OFFSET_CFI_BLOCK_COUNT_LSB(i)] & 0x00FF;
+        cyg_uint32 count_msb   = addr[AM29_OFFSET_CFI_BLOCK_COUNT_MSB(i)] & 0x00FF;
+        cyg_uint32 size_lsb    = addr[AM29_OFFSET_CFI_BLOCK_SIZE_LSB(i)] & 0x00FF;
+        cyg_uint32 size_msb    = 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;
+    }
+
+    // Get out of CFI mode
+    addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_RESET;
+
+    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 void
+AM29_FNNAME(am29_hw_erase)(volatile AM29_TYPE* addr)
+{
+    int     retries = CYGNUM_DEVS_FLASH_AMD_AM29XXXXX_V2_ERASE_TIMEOUT;
+
+    addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_SETUP1;
+    addr[AM29_OFFSET_COMMAND2]  = AM29_COMMAND_SETUP2;
+    addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_ERASE;
+    addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_SETUP1;
+    addr[AM29_OFFSET_COMMAND2]  = AM29_COMMAND_SETUP2;
+    addr[AM29_OFFSET_COMMAND]   = AM29_COMMAND_ERASE_SECTOR;
+    // 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.
+    do {
+        AM29_TYPE   datum1, datum2;
+        datum1  = addr[AM29_OFFSET_COMMAND];
+        datum2  = addr[AM29_OFFSET_COMMAND];
+        if ((datum1 & AM29_STATUS_DQ6) == (datum2 & AM29_STATUS_DQ6)) {
+            // The bits have stopped toggling, so finished.
+            break;
+        }
+        if (0 != ((datum1 | datum2) & AM29_STATUS_DQ5)) {
+            addr[AM29_OFFSET_COMMAND] = AM29_COMMAND_RESET;
+            break;
+        }
+    } while (retries-- > 0);
+
+    // The calling code will verify that the erase was successful,
+    // and generate an error code.
+}
+
+// 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 void
+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;
+    
+    for (i = 0; i < count; i++) {
+        AM29_TYPE   datum;
+        AM29_TYPE   current, masked_datum;
+        
+        // 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;
+        block_start[AM29_OFFSET_COMMAND2]   = AM29_COMMAND_SETUP2;
+        block_start[AM29_OFFSET_COMMAND]    = AM29_COMMAND_PROGRAM;
+        addr[i] = datum;
+
+        // The data is now being written. While the write is in progress
+        // DQ7 will have an inverted value from what was written, so we
+        // can poll, comparing just this bit. Again, if DQ5 is set then
+        // an error has occurred.
+        masked_datum = datum & AM29_STATUS_DQ7;
+        retries = CYGNUM_DEVS_FLASH_AMD_AM29XXXXX_V2_PROGRAM_TIMEOUT;
+        do {
+            current = addr[i];
+            if ((current & AM29_STATUS_DQ7) == masked_datum) {
+                break;
+            }
+            if (0 != (current & AM29_STATUS_DQ5)) {
+                current = addr[i];
+                if (current == datum) {
+                    // Race condition, but the operation did succeed.
+                    break;
+                } else {
+                    // A timeout has occurred inside the hardware and
+                    // the system is in a strange state. Reset but don't
+                    // try to write any more of the data.
+                    block_start[AM29_OFFSET_COMMAND]    = AM29_COMMAND_RESET;
+                    return;
+                }
+            }
+        } while (retries-- > 0);
+
+        if (0 == retries) {
+            // Failed to write this word, no point in trying to write the rest.
+            return;
+        }
+    }
+}
+
+// FIXME: implement a separate program routine for buffered writes. 
+
+// ----------------------------------------------------------------------------
+// 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;
+
+    CYG_CHECK_DATA_PTR(dev, "valid flash device pointer required");
+    
+    addr     = AM29_P2V(dev->start);
+    query_fn = (int (*)(volatile AM29_TYPE*)) am29_anonymizer( & AM29_FNNAME(am29_hw_query) );
+    devid    = (*query_fn)(addr);
+    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;
+
+    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;
+    }
+    // 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;
+    
+    CYG_CHECK_DATA_PTR(dev, "valid flash device pointer required");
+    am29_dev    = (cyg_am29xxxxx_dev*) dev->priv;
+    addr        = AM29_P2V(dev->start);
+    cfi_fn      = (int (*)(struct cyg_flash_dev*, cyg_am29xxxxx_dev*, volatile AM29_TYPE*))
+        am29_anonymizer( & AM29_FNNAME(am29_hw_cfi));
+
+    result      = (*cfi_fn)(dev, am29_dev, addr);
+
+    // 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++) {
+            size += (dev->block_info[i].block_size * dev->block_info[i].blocks);
+        }
+        dev->end = dev->start + size - 1;
+    }
+    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, const cyg_flashaddr_t addr)
+{
+    void                (*erase_fn)(volatile AM29_TYPE*);
+    volatile AM29_TYPE* block;
+    cyg_flashaddr_t     block_start;
+    size_t              block_size;
+    int                 i;
+
+    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");
+    
+    block       = AM29_P2V(addr);
+    erase_fn    = (void (*)(volatile AM29_TYPE*)) am29_anonymizer( & AM29_FNNAME(am29_hw_erase) );
+    (*erase_fn)(block);
+
+    // 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, const cyg_flashaddr_t dest, const void* src, const size_t len)
+{
+    void                (*program_fn)(volatile AM29_TYPE*, volatile AM29_TYPE*, const cyg_uint8*, cyg_uint32);
+    volatile AM29_TYPE* block;
+    volatile AM29_TYPE* addr; 
+    cyg_flashaddr_t     block_start;
+    size_t              block_size;
+    const cyg_uint8*    data;
+    int                 i;
+
+    CYG_CHECK_DATA_PTR(dev, "valid flash device pointer required");
+    CYG_ASSERT((dest >= dev->start) && (addr <= dev->end), "flash address out of device range");
+
+    // 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;
+    }
+
+    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_P2V(block_start);
+    addr        = AM29_P2V(dest);
+    data        = (const cyg_uint8*) src;
+
+    program_fn  = (void (*)(volatile AM29_TYPE*, volatile AM29_TYPE*, const cyg_uint8*, cyg_uint32))
+        am29_anonymizer( & AM29_FNNAME(am29_hw_program) );
+    (*program_fn)(block, addr, (const cyg_uint8*)src, len / sizeof(AM29_TYPE));
+    
+    // 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;
+    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) {
+            return CYG_FLASH_ERR_PROGRAM;
+        }
+    }
+    return CYG_FLASH_ERR_OK;
+}
+
+// ----------------------------------------------------------------------------
+// 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_PARALLEL