The Interface Functions

There are five major flash memory interface functions. These functions provide initialization of control structures, retrieval of the device ID, and ability to write to and erase the device.

  • FlashInit() Initializes the data structures used by the flash driver to work with the device(s) on board.

  • Flashid() Returns some indication of the type of flash device that is populated on the system board. This function is useful because it provides a sanity check that the flash memory is writable. (In most cases, you must write to the flash memory to read back its ID properly.) Also, this function allows the firmware to support the possibility of different types of devices installed in the system.

  • Flasherase() Takes a sector number as input and properly erases that sector (or all sectors).

  • FlashWrite() Takes a source pointer, a destination pointer, and a size and writes the source data to the destination address. This function assumes that the destination is flash memory and that it has been erased.

  • FlashEwrite() Combines Flasherase() and FlashWrite() . This function takes the same arguments as FlashWrite() but erases the affected sectors prior to the write. This function is used to re-write a new monitor in place.

Flash Banks

The flash driver supports multiple banks of devices not necessarily in contiguous memory space. A bank in this context is one or more devices that are accessed as a unit. For example, if one 8-bit device is accessible to the CPU, then that device is considered a single bank.

As shown in Figure 6.1, if a second device is wired above the first device providing double the memory but each device is accessed individually as 8-bit data space (note the two distinct chip-selects), the system would be said to contain two banks because at any given time only one device can be accessed.

image from book
Figure 6.1: Two Flash Devices, Two Separate Banks.

It is common in small systems for each memory chip to be treated as a separate bank. In larger systems, separate banks might consist of multiple paralleled chips (see Figure 6.2.) In this example, the processor chip select lines are used to activate the appropriate bank.

If the second device is wired so that the CPU can access both devices as a 16-bit block (see Figure 6.2 note only one chip select), then the result would be a single, two-device bank. Similarly four 8-bit devices or two 16-bit devices wired in parallel to present a 32-bit block of flash memory to the CPU would also be a single bank of flash memory.

image from book
Figure 6.2: Two Flash Devices, One Single Bank.

In this example, two 8-bit memory chips are connected in parallel to create a single 16-bit wide memory bank. Note that the chip select line activates the entire bank as a unit.

Tip 

Operations on flash memory take time. Usually the controlling firmware must poll the device and wait for completion of some internal operation. Depending on design goals and requirements, it is wise to consider putting more devices in parallel to build up to a specified flash density. Many flash devices can be configured to run in 8- or 16-bit mode. If your design calls for an amount of flash memory that requires four of these devices, it is more efficient (from the programming time point of view) to put all four devices in 8-bit mode to form one 32-bit bank instead of building two banks with each device in 16-bit mode. Putting four devices in parallel allows all four to perform internal operations simultaneously ; hence, the programming time-per-byte is reduced.

Relocating Flash Operation Functions to RAM

One of the challenges of writing flash interface functions is that the flash interface function must be executed out of memory space that is not located on the flash device you are manipulating. Some newer devices allow you to get fancy and execute from one section of the device while operating on another, but I assume youre not using these special devices.

The idea behind the flash interface function is to create the function in flash memory, then copy the function to RAM and load a function pointer with the address of that copy space. The function pointer is then used to access the RAM-based copy of the function. The code within the function must be entirely self-relative (i.e., relocatable).

In general, code within a function is almost always self-relative anyway, as long as there are no calls to other functions. Some compilers, however, use intrinsic functions for doing basic math operations, such as multiply and divide; you wont see the call, but its still there. Most tool sets support the ability to write position-independent code. Position-independent code simply means that branches to other functions (from within a function) are not done through PC-relative branching. Instead, the entire address of the function is loaded into a register, and the branch is taken using that register. This approach still doesnt help in the case of a flash interface function because if the function you call is in flash memory, youre executing out of the very same flash memory you are trying to avoid. The point of all of this information is that the flash operation functions that are relocated to RAM should be kept simple and to the point. Just be disciplined and dont put any function calls within this code.

The system should copy the flash code to RAM prior to enabling any of the CPUs caching (because the RAM copy uses data accesses to populate instruction space and you dont want to have to worry about the fact that instructions might be in the data cache). The following is a very basic example of how to copy a function into RAM. Note that the buffer is 32-bit aligned using an array of longs instead of chars . Using longs guarantees that the buffer starts on a modulo-4 address. [1] It assumes that the linker is placing functions within a file in contiguous memory space and that the function fits in the specified buffer.

Listing 6.1: Copying a Flash Function to RAM.
image from book
 int (*RamFlashWrite)(); unsigned long RamFlashWriteBuf[1000]; int FlashWrite() {     /* code to write to flash goes here */ } int EndFlashWrite() {     return(1); } ... /* Copy from flash space to RAM space: */ memcpy((char *)RAMFlashWriteBuf,(char *)FlashWrite, (int)((ulong)EndFlashWrite-(ulong)FlashWrite)); /* Establish a function pointer into the RAM buffer: */ RamFlashWrite = (int(*)())RamFlashWriteBuf; 
image from book
 

After this code completes, the function pointer RamFlashWrite can be used to access the RAM-based version of the FlashWrite() function. The RAM-based function uses the same API as the flash-based version; the only difference is that the CPU fetches it out of RAM instead of flash memory.

Flash Control Structure Initialization

To allow the same API to work across several different devices, making the device type insignificant to the code, I use a few data structures to keep track of device-specific information. The main structure is flashinfo (see Listing 6.2). The flashinfo structure contains a set of function pointers that are initialized to point to the RAM space that contains the flash operation functions. To allow one driver to support several different devices within a family, the ID of the device is retrieved, and, based on the ID, additional sector-specific information about the device can be loaded. This sector-specific information is stored in the sectorinfo structure. There is one sectorinfo structure per sector. The pointer to the sectorinfo structure within the flashinfo structure points to a table of sectorinfo structures whose size is equal to the number of sectors in the device. The table pointed to by the sectors pointer within the flashinfo structure is established in the FlashInit() function (see Listing 6.4).

Listing 6.2: Flash Device Descriptors.
image from book
 struct  sectorinfo {     long    size;           /* size of sector */     int snum;               /* number of sector (amongst possibly */                             /* several devices) */     int protected;          /* if set, sector is protected by window */     unsigned char *begin;   /* base address of sector */     unsigned char *end;     /* end address of sector */ };      struct  flashinfo {     unsigned long   id;     /* manufacturer & device id */     unsigned char   *base;  /* base address of bank */     unsigned char   *end;   /* end address of bank */     int     sectorcnt;      /* number of sectors */     int     width;          /* 1, 2, or 4 */     int     (*fltype)();     int     (*flerase)();     int     (*flwrite)();     int     (*flewrite)();     struct sectorinfo *sectors; }; 
image from book
 

Flash Operations for the 29F040 Family

Breaking from the books general, device-independent philosophy, I will now step through a device-specific example. The 29F040 family of devices has been around for a while, comes in a few different package types, and is manufactured by more than just one silicon vendor.

First, I create several RAM arrays (see Listing 6.3) that are used to store the flash operation functions one for each of the operations: TYPE , ERASE , WRITE , and ERASEandWRITE . Next I define a table of flashinfo structures. The size of the table ( FLASHBANKS ) should equal the number of banks in the system. For this example, the size is just one. The last piece of data is the table of sectorinfo structures. Because the 29F040 and 29F010 both have eight sectors, the same table can be used for either device.

List 6.3: RAM Arrays.
image from book
 ulong    FlashTypeFbuf[FLASHFUNCSIZE]; ulong    FlashEraseFbuf[FLASHFUNCSIZE]; ulong    FlashWriteFbuf[FLASHFUNCSIZE]; ulong    FlashEwriteFbuf[FLASHFUNCSIZE]; struct   flashinfo FlashBank[FLASHBANKS]; struct   sectorinfo sinfo040[8]; 
image from book
 

The FlashInit() function (see Listing 6.4) is called at system startup. The calls to flashopload() (not shown) copy the flash operation functions from flash memory to RAM. If for any reason the copy fails, flashopload() returns 1 and FlashInit() aborts. Remember that it is important to copy the flash operation to RAM while the system cache is disabled to avoid the confusion that is created by the fact that the memory copy can leave some of the instructions in the data cache. [2] The flashopload() function is basically a fancy memcpy() . The flashopload() function verifies that the flash operations function fits in the buffer and then verifies that the writes were successful (by reading all that is written during the copy). Next, the flashinfo structure in the FlashBank[] table is initialized. The base and width of the device is recorded, and each function pointer is loaded with the location of the corresponding RAM array that now contains the flash operation code.

Listing 6.4: Initializing Flash-Related Structures.
image from book
 int FlashInit(void) {     int     snum;     struct  flashinfo *fbnk;     snum = 0;     if (flashopload((ulong *)FlashType040,(ulong *)EndFlashType040,         FlashTypeFbuf,sizeof(FlashTypeFbuf)) < 0)         return(-1);     if (flashopload((ulong *)FlashErase040,(ulong *)EndFlashErase040,         FlashEraseFbuf,sizeof(FlashEraseFbuf)) < 0)         return(-1);     if (flashopload((ulong *)FlashEwrite040,(ulong *)EndFlashEwrite040,         FlashEwriteFbuf,sizeof(FlashEwriteFbuf)) < 0)         return(-1);     if (flashopload((ulong *)FlashWrite040,(ulong *)EndFlashWrite040,         FlashWriteFbuf,sizeof(FlashWriteFbuf)) < 0)         return(-1);     fbnk = &FlashBank[0];     fbnk->base = (unsigned char *)FLASH_BANK0_BASE_ADDR;     fbnk->width = FLASH_BANK0_WIDTH;     fbnk->fltype = (int(*)())FlashTypeFbuf;      fbnk->flerase = (int(*)())FlashEraseFbuf;     fbnk->flwrite = (int(*)())FlashWriteFbuf;     fbnk->flewrite = (int(*)())FlashEwriteFbuf;     fbnk->sectors = sinfo040;     snum += FlashBankInit(fbnk,snum);     sectorProtect(FLASH_PROTECT_RANGE,1);     return(0); } 
image from book
 

To determine the type of device, the flash initialization routine calls FlashBankInit() (see Listing 6.5). The FlashBankInit() function calls the function flashtype () , which is simply a wrapper around the fbnk->fltype function pointer (which points to the RAM array that contains the flash TYPE operation code thanks to flashopload() ). The ID of the device is retrieved, and, depending on whether the installed device is AMD20F040 or AMD29F010, the sector information structures are configured appropriately (the sector size for the 040 is 64K, for the 010, its 16K). The loop at the bottom of FlashBankInit() initializes each of the sectorinfo entries based on characteristics established from the device type.

Listing 6.5: Initializing the Flash Descriptors.
image from book
 int FlashBankInit(struct flashinfo *fbnk, int snum) {     int i, ssize;     flashtype(fbnk);     switch(fbnk->id) {         case AMD29LV040:         case SGS29LV040:         case SGS29F040:         case AMD29F040:             fbnk->sectorcnt = 8;             ssize = 0x10000 * fbnk->width;             fbnk->end = fbnk->base + (0x80000 * fbnk->width) - 1;             break;         case AMD29F010:             fbnk->sectorcnt = 8;             ssize = 0x4000 * fbnk->width;             fbnk->end = fbnk->base + (0x20000 * fbnk->width) - 1;             break;         default:             printf("Flash device id 0x%lx unknown\n", fbnk->id);             return(-1);     }     for(i=0;i<fbnk->sectorcnt;i++) {         fbnk->sectors[i].snum = snum+i;         fbnk->sectors[i].size = ssize;         fbnk->sectors[i].begin = fbnk->base + (i*ssize);         fbnk->sectors[i].end = fbnk->sectors[i].begin + ssize - 1;         fbnk->sectors[i].protected = 0;     }     return(0); } 
image from book
 

The final call in FlashInit() (refer to Listing 6.4) is to sectorProtect() . The sectorProtect() function establishes certain sectors as protected. In this case, the term protected means "software" protected. The flash operations that perform erase or write on the device must first look to see if the affected sector is protected. If the sector is protected, the flash operation is aborted. Note that this methodology offers only a software level of protection. In this case, the protection is quite adequate because two different functions must be called sequentially to bypass it accidentally first unprotect the sector and then perform the operation. Because the " unprotect " is performed through the MicroMonitor CLI, the two necessary steps are not in any one function; this makes it extremely unlikely that an accidental erase or write of a protected sector will occur.

Now that Ive explained how the flash functions get copied to RAM, Ill explain how the flash interface works, beginning with the details of the flash write operation.

The 29F040 has eight 64K sectors, each of which can be erased at least 100,000 times. Unlike RAM and DRAM, which allow you to write as you please , the actual write operation for flash memory requires you to notify the device to which you intend to write. A typical write operation consists of writing some chunk of data to some chunk of space within the flash device. Then the actual unit of data is written. The algorithm then waits for the internal machine of the flash device to indicate write completion. Finally the device is told to go back to standard read mode.

Listing 6.6 shows the code for a flash write operation on the 29F040. The function takes a pointer to a flashinfo structure along with source, destination, and size. Note the three WRITE macros prior to each FWRITE macro. The three-write sequence prepares the device for the actual data write, which is performed by the FWRITE macro. After FWRITE transfers the data, a wait loop polls the device to determine when the operation completes. When the operation completes, another sequence is written to the device to put the device back into standard read mode so that the device is available for read operations.

Listing 6.6: The Flash Write Operation.
image from book
 /* Macros used for flash operations: */ #define ftype               volatile unsigned char #define WRITE_AA_TO_5555()  (*(ftype *)(fdev->base + (0x5555<<0)) = 0xaa) #define WRITE_55_TO_2AAA()  (*(ftype *)(fdev->base + (0x2aaa<<0)) = 0x55) #define WRITE_80_TO_5555()  (*(ftype *)(fdev->base + (0x5555<<0)) = 0x80) #define WRITE_A0_TO_5555()  (*(ftype *)(fdev->base + (0x5555<<0)) = 0xa0) #define WRITE_F0_TO_5555()  (*(ftype *)(fdev->base + (0x5555<<0)) = 0xf0) #define WRITE_90_TO_5555()  (*(ftype *)(fdev->base + (0x5555<<0)) = 0x90) #define WRITE_30_TO_(add)   (*(ftype *)add = 0x30) #define READ_0000()         (*(ftype *)(fdev->base + 0x0000<<0))) #define READ_0001()         (*(ftype *)(fdev->base + (0x0001<<0))) #define READ_5555()         (*(ftype *)(fdev->base + (0x5555<<0))) #define IS_FF(add)          (*(ftype *)add == 0xff) #define IS_NOT_FF(add)      (*(ftype *)add != 0xff) #define D5_TIMEOUT(add)     ((*(ftype *)add & 0xdf) == 0x20) #define FWRITE(to,frm)      (*(ftype *)to = *(ftype *)frm) #define IS_EQUAL(p1,p2)     (*(ftype *)p1 == *(ftype *)p2) #define IS_NOT_EQUAL(p1,p2) (*(ftype *)p1 != *(ftype *)p2) int FlashWrite040(struct flashinfo *fdev, ftype *dest,ftype *src,long bytecnt) {     int i, ret;     ftype   val;     /* Each pass through this loop writes 'fdev->width' bytes... */     ret = 0;     for (i=0;i<bytecnt;i++) {         /* Flash write command */         WRITE_AA_TO_5555();         WRITE_55_TO_2AAA();         WRITE_A0_TO_5555();         /* Write the value */         FWRITE(dest,src);         /* Wait for write to complete or timeout. */         while(1) {             if (IS_EQUAL(dest,src)) {                 if (IS_EQUAL(dest,src))                     break;             }             /* Check D5 for timeout... */             if (D5_TIMEOUT(dest)) {                 if (IS_NOT_EQUAL(dest,src))                     ret = -1;                 goto done;             }         }         dest++; src++;     } done:     /* Read/reset command: */     WRITE_AA_TO_5555();     WRITE_55_TO_2AAA();     WRITE_F0_TO_5555();     val = READ_5555();     return(ret); } /* EndFlashwrite():  *  Function place holder to determine the "end" of the  *  Flashwrite() function.  */ void EndFlashWrite040() {} 
image from book
 

The Flasherase() function (see Listing 6.7) is very similar to FlashWrite() . A sequence of writes notifies the device of the impending erase operation. Next, the command that initiates the sector-specific erasure is completed. The polling loop then waits for completion, and, finally, a sequence of writes tells the device to go back to standard read mode.

Listing 6.7: Flasherase() .
image from book
 /* Flasherase():  * Based on the 'snum' value, erase the appropriate sector(s).  * Return 0 if success, else -1.  */ int FlashErase040(struct flashinfo *fdev,int snum) {     ftype           val;     unsigned long   add;     int         ret, sector;     ret = 0;     add = (unsigned long)(fdev->base);     /* Erase the request sector(s): */     for (sector=0;sector<fdev->sectorcnt;sector++) {         if ((!FlashProtectWindow) &&             (fdev->sectors[sector].protected)) {             add += fdev->sectors[sector].size;             continue;         }         if ((snum == ALL_SECTORS)  (snum == sector)) {             /* Issue the sector erase command sequence: */             WRITE_AA_TO_5555();             WRITE_55_TO_2AAA();             WRITE_80_TO_5555();             WRITE_AA_TO_5555();             WRITE_55_TO_2AAA();             WRITE_30_TO_(add);                  /* Wait for sector erase to complete or timeout..              * DQ7 polling: wait for D7 to be 1.              * DQ6 toggling: wait for D6 to not toggle.              * DQ5 timeout: if DQ7 = 0, and DQ5 = 1, timeout.              */             while(1) {                 if (IS_FF(add)) {                     if (IS_FF(add))                         break;                 }                 if (D5_TIMEOUT(add)) {                     if (IS_NOT_FF(add))                         ret = -1;                     break;                 }             }         }         add += fdev->sectors[sector].size;     }     WRITE_AA_TO_5555();     WRITE_55_TO_2AAA();     WRITE_F0_TO_5555();     val = READ_5555();     return(ret); } /* EndFlasherase():  * Function place holder to determine the "end" of the  * sectorerase() function.  */ void EndFlashErase040() {} 
image from book
 

At the top of the Flasherase() function, the global variable FlashProtectWindow is tested against the protected member of the sectorinfo structure. The FlashProtectWindow variable can only be set by the CLI command flash opw ( open flash protection window), and, as a result of the flash opw command, the variable is set to 2 . At the completion of each CLI command, this variable is tested for non-zero state and, if positive, is decremented by one. Upon completion of the flash opw command itself, the variable is decremented to 1 . The window of time for which the variable is non-zero is, therefore, one CLI command, which provides the "soft" protection mentioned earlier.

Dealing with 16- and 32-bit Banks

At the start of this flash driver section, I mentioned that the devices can be in banks, and any given bank can be 8, 16, or 32 bits. The example code then elaborated on an 8-bit device interface. This approach is fine for demonstrating just how to deal with the flash device, but I must still mention one more sticky issue. If you have multiple devices in parallel forming a 32-bit bank, the call to FlashWrite() must deal with the possibility that the starting address of the write may not be 32-bit aligned. The model of banks requires that the banks are in units of the same width, so if you want to start the flash write on some odd address, you need to back up to the preceding 32-bit aligned address and include some of the data already in flash memory as part of the first unit write. Refer to Figure 6.3.

image from book
Figure 6.3: Adjusting Start Address to 32-bit Alignment.

To write 0x112233 starting at the address X, we convert the 3-byte write on the top to the 4-byte write on the bottom. This process is done by backing up the destination address by one and making that byte be part of the 4-byte write to the memory space. So we write 0xF1112233 at address X-1.

The same principle applies to a write that does not end on a 32-bit alignment. You might have to read ahead a few bytes and include that read data in the final 32-bit write.

[1] Some processors require each instruction to be on a 32-bit boundary, so this satisfies that require ment but does not hurt anything for those CPUs that do not have this requirement.

[2] Optionally, we could just flush/invalidate the caches at the end of this function, but because this process is done early on in the system initialization, it is reasonable to just do this prior to enabling cache.



Embedded Systems Firmware Demystified
Embedded Systems Firmware Demystified (With CD-ROM)
ISBN: 1578200997
EAN: 2147483647
Year: 2002
Pages: 118
Authors: Ed Sutter

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net