Mercurial > flash_v2
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
