Index: ChangeLog =================================================================== RCS file: /cvs/ecos/ecos/packages/devs/flash/amd/am29xxxxxv2/current/ChangeLog,v retrieving revision 1.6 diff -u -r1.6 ChangeLog --- ChangeLog 21 Apr 2009 10:13:26 -0000 1.6 +++ ChangeLog 24 Apr 2009 11:37:18 -0000 @@ -1,3 +1,8 @@ +2009-04-24 Rene Schipp von Branitz Nielsen + + * src/am29xxxxx.c, src/am29xxxxx_aux.c, include/am29xxxxx_dev.h: + Implement buffered writes. + 2009-04-20 Rene Schipp von Branitz Nielsen * src/am29xxxxx.c: Added set of parentheses around macro Index: include/am29xxxxx_dev.h =================================================================== RCS file: /cvs/ecos/ecos/packages/devs/flash/amd/am29xxxxxv2/current/include/am29xxxxx_dev.h,v retrieving revision 1.3 diff -u -r1.3 am29xxxxx_dev.h --- include/am29xxxxx_dev.h 29 Jan 2009 17:48:19 -0000 1.3 +++ include/am29xxxxx_dev.h 24 Apr 2009 11:37:18 -0000 @@ -126,6 +126,9 @@ typedef struct cyg_am29xxxxx_dev { // The device id, mainly for use by the init_check_devid() routines cyg_uint32 devid; + // The write buffer size in units of the data width per flash. + // Can either be statically initialized or dynamically via init_cfi(). + cyg_uint32 wr_buf_sz; // Space for the block_info fields needed for the cyg_flash_dev. // These can be statically initialized, or dynamically via // init_cfi(). Index: src/am29xxxxx.c =================================================================== RCS file: /cvs/ecos/ecos/packages/devs/flash/amd/am29xxxxxv2/current/src/am29xxxxx.c,v retrieving revision 1.4 diff -u -r1.4 am29xxxxx.c --- src/am29xxxxx.c 21 Apr 2009 10:13:26 -0000 1.4 +++ src/am29xxxxx.c 24 Apr 2009 11:37:18 -0000 @@ -126,6 +126,8 @@ #define AM29_COMMAND_ERASE_RESUME AM29_SWAP(AM29_PARALLEL(0x0030)) #define AM29_COMMAND_CFI AM29_SWAP(AM29_PARALLEL(0x0098)) #define AM29_COMMAND_PROGRAM AM29_SWAP(AM29_PARALLEL(0x00A0)) +#define AM29_COMMAND_PROGRAM_BUFFER AM29_SWAP(AM29_PARALLEL(0x0029)) +#define AM29_COMMAND_LOAD_BUFFER AM29_SWAP(AM29_PARALLEL(0x0025)) // Following are specific to AT49 derivatives #define AM29_COMMAND_AT49_SOFTLOCK_BLOCK_0 AM29_SWAP(AM29_PARALLEL(0x0080)) #define AM29_COMMAND_AT49_SOFTLOCK_BLOCK_1 AM29_SWAP(AM29_PARALLEL(0x0040)) @@ -138,6 +140,8 @@ // CFI standard allows that behaviour. #define AM29_OFFSET_CFI_Q AM29_OFFSET_CFI_DATA(0x0010) #define AM29_OFFSET_CFI_SIZE AM29_OFFSET_CFI_DATA(0x0027) +#define AM29_OFFSET_CFI_WR_BUF_SZ_LSB AM29_OFFSET_CFI_DATA(0x002A) +#define AM29_OFFSET_CFI_WR_BUF_SZ_MSB AM29_OFFSET_CFI_DATA(0x002B) #define AM29_OFFSET_CFI_BLOCK_REGIONS AM29_OFFSET_CFI_DATA(0x002C) #define AM29_OFFSET_CFI_BLOCK_COUNT_LSB(_i_) AM29_OFFSET_CFI_DATA(0x002D + (4 * (_i_))) #define AM29_OFFSET_CFI_BLOCK_COUNT_MSB(_i_) AM29_OFFSET_CFI_DATA(0x002E + (4 * (_i_))) Index: src/am29xxxxx_aux.c =================================================================== RCS file: /cvs/ecos/ecos/packages/devs/flash/amd/am29xxxxxv2/current/src/am29xxxxx_aux.c,v retrieving revision 1.3 diff -u -r1.3 am29xxxxx_aux.c --- src/am29xxxxx_aux.c 29 Jan 2009 17:48:19 -0000 1.3 +++ src/am29xxxxx_aux.c 24 Apr 2009 11:37:19 -0000 @@ -144,10 +144,11 @@ 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*)); +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(am29_hw_program_buffered, (volatile AM29_TYPE*, volatile AM29_TYPE*, const cyg_uint8*, cyg_uint32 count, int retries, cyg_uint32 wr_buf_sz)); +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*)); // ---------------------------------------------------------------------------- @@ -253,9 +254,10 @@ 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; + int dev_size; + int i; + int erase_regions; + cyg_uint32 wr_buf_sz_lsb, wr_buf_sz_msb; AM29_2RAM_ENTRY_HOOK(); @@ -292,6 +294,19 @@ dev_size = 0x01 << (AM29_UNSWAP(addr[AM29_OFFSET_CFI_SIZE]) & 0x00FF); dev->end = dev->start + dev_size - 1; + // Write buffer sizes are always a power of 2. + // The following code will cause the wr_buf_sz to become 1 if it's not + // supported, but this is taken care of during the program operations + // so that values 0 and 1 are treated identically. + wr_buf_sz_lsb = AM29_UNSWAP(addr[AM29_OFFSET_CFI_WR_BUF_SZ_LSB]) & 0x00FF; + wr_buf_sz_msb = AM29_UNSWAP(addr[AM29_OFFSET_CFI_WR_BUF_SZ_MSB]) & 0x00FF; + am29_dev->wr_buf_sz = 0x01 << ((wr_buf_sz_msb << 8) | wr_buf_sz_lsb); + + // CFI returns the write buffer size in bytes, but this driver must have + // it in AM29_TYPE quantities, corrected for number of devices in parallel + // (assuming that all devices have the same write buffer size). + am29_dev->wr_buf_sz /= (sizeof(AM29_TYPE) / AM29_DEVCOUNT); + // 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 @@ -584,7 +599,127 @@ return retries; } -// FIXME: implement a separate program routine for buffered writes. +// 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_buffered)(volatile AM29_TYPE* block_start, volatile AM29_TYPE* addr, const cyg_uint8* buf, cyg_uint32 count, int retries, cyg_uint32 wr_buf_sz) +{ + int i; + + // The mask will always be [write buffer size measured in bytes for a particular flash device - 1]. + cyg_uint32 mask = wr_buf_sz * (sizeof(AM29_TYPE) / AM29_DEVCOUNT) - 1; + + AM29_2RAM_ENTRY_HOOK(); + + while (count > 0 && retries > 0) { + volatile AM29_TYPE *last_addr; + AM29_TYPE datum = 0, current, active_dq7s; + + // Compute number of words to write. All writes to the write + // buffer must fall within the same write buffer page. + cyg_uint32 nwords = wr_buf_sz - (((cyg_uint32)(addr) & mask) / (sizeof(AM29_TYPE) / AM29_DEVCOUNT)); + if (nwords > count) + nwords = count; + + block_start[AM29_OFFSET_COMMAND] = AM29_COMMAND_SETUP1; + HAL_MEMORY_BARRIER(); + block_start[AM29_OFFSET_COMMAND2] = AM29_COMMAND_SETUP2; + HAL_MEMORY_BARRIER(); + block_start[0] = AM29_COMMAND_LOAD_BUFFER; + HAL_MEMORY_BARRIER(); + block_start[0] = AM29_SWAP(AM29_PARALLEL((nwords - 1))); // Program same count into all devices + HAL_MEMORY_BARRIER(); + + // Load data into write buffer + for(i = 0; i < nwords; i++) { + *addr++ = datum = AM29_NEXT_DATUM(buf); + } + last_addr = addr - 1; + + // Program buffer to flash + block_start[0] = AM29_COMMAND_PROGRAM_BUFFER; + + // 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 = *last_addr; + 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 = *last_addr; + 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 = *last_addr; + 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 write buffer + + count -= nwords; + } // Next write buffer + + // 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; +} #if 0 // Unused for now, but might be useful later @@ -845,7 +980,6 @@ 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; @@ -853,7 +987,7 @@ const cyg_uint8* data; int retries; int i; - + cyg_am29xxxxx_dev* am29_dev; AM29_INTSCACHE_STATE; CYG_CHECK_DATA_PTR(dev, "valid flash device pointer required"); @@ -881,12 +1015,23 @@ 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_dev = (cyg_am29xxxxx_dev*) dev->priv; + if(am29_dev->wr_buf_sz > 1) { + int (*program_buffered_fn)(volatile AM29_TYPE*, volatile AM29_TYPE*, const cyg_uint8*, cyg_uint32, int, cyg_uint32); + program_buffered_fn = (int (*)(volatile AM29_TYPE*, volatile AM29_TYPE*, const cyg_uint8*, cyg_uint32, int, cyg_uint32)) + cyg_flash_anonymizer( & AM29_FNNAME(am29_hw_program_buffered) ); + AM29_INTSCACHE_BEGIN(); + (*program_buffered_fn)(block, addr, data, len / sizeof(AM29_TYPE), retries, am29_dev->wr_buf_sz); + AM29_INTSCACHE_END(); - AM29_INTSCACHE_BEGIN(); - (*program_fn)(block, addr, data, len / sizeof(AM29_TYPE), retries); - AM29_INTSCACHE_END(); + } else { + int (*program_fn)(volatile AM29_TYPE*, volatile AM29_TYPE*, const cyg_uint8*, cyg_uint32, int); + 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.