An MTD is a software module that provides the basic read, write, erase, and map functionality that TrueFFS needs in order to program flash memory. This module also provides an identification utility, a routine that probes a flash device for its type. If the flash type is appropriate to the MTD, the MTD provides TrueFFS with data and pointers to the functions (map, read, write, and erase) that it needs to program that flash device.
In the process of creating a logical block device for a flash memory array, TrueFFS tries to match an MTD to the flash device. To do this, TrueFFS calls the identification routine from each MTD until one reports a match. The first reported match is the one taken. If no MTD reports a match, TrueFFS falls back on a default read-only MTD that reads from the flash device by copying from the socket window.
The MTD identification routine is guaranteed to be called prior to any other routine in the MTD. An MTD identification routine is of the following format:
FLStatus xxxIdentify(FLFlash vol)
Within an MTD identify routine, you must probe the device to determine its type. How you do this depends on the hardware. If the type is not appropriate to this MTD, return failure. Otherwise, set the members of the FLFlash structure listed below:
For most NOR flash arrays, you can use the standard routines flIntelIdentify( ) and flIntelSize( ) to automatically detect the JEDEC id, the interleaving, and the size of the flash array. Call the flIntelIdentify( ) routine first. Its interface is:1
FLStatus flIntelIdentify( FLFlash vol, void (*amdCmdRoutine) (FLFlash *, long int, unsigned char), long int chipOffset)
flIntelIdentify( ) uses the chip's READ_ID command to detect the device and retrieve its JEDEC id and chip interleaving. Set amdCmdRoutine to NULL for Intel flash devices. For AMD & Fujitsu flash devices, set amdCmdRoutine to the address of a routine that writes a command to the flash device through the special "unlock sequence" that is necessary for this kind of flash. Use chipOffSet to pass in the offset on the chip that is used for reading the chip id. A value of 0 (offset zero) is almost always appropriate.
If flIntelIdentify( ) fails, your MTD identification routine should immediately return failure. If flIntelIdentify( ) succeeds, it sets vol.type and vol.interleaving. Your MTD identification routine should then check the JEDEC id stored in vol.type. If your MTD can handle this type of flash, it should set vol.chipSize to its correct value, and then call flIntelSize( ), defined as:
FLStatus flIntelSize( FLFLash vol, void (*amdCmdRoutine) (FLFlash *, long int, unsigned char), long int chipOffset)
This function is able to detect the array size correctly even when an address wraparound exists at the end of the flash array, although all chips in the flash array must be of the same type. If this function succeeds, it sets vol.noOfChips correctly. If this routine fails, your MTD identification routine must return failure.
Often an MTD needs to identify a PCMCIA flash card through its CIS or Attribute Memory. To access Attribute Memory, use the flMap( ) function, as described in 3.4.2 Writing a Map Function for an MTD. The address parameter is the Attribute Memory address plus 128 M. For example:
void far *cis = flMap(vol, 0x8000000l);
Note that both the socket hardware and the card must recognize a separate Attribute Memory Space. If one of them does not, you will find yourself mapped to the equivalent address in common memory.
On many systems, the flash array is connected to the system bus through an intelligent controller (PCMCIA, for example). From the programmer's perspective, this connection makes it appear as if there is a host address space window that is sliding along the flash array. This feature is used extensively by TrueFFS to optimize read operations on NOR flash array when TrueFFS needs to access system meta data.
Because system meta data is distributed evenly across the region of the flash array, TrueFFS needs to issue many calls to the MTD read routine in order to collect the data. Thus it is more efficient to map an entire region of the flash array into the host address space (using the MTD map routine) and then read directly from the memory window.
Initially, TrueFFS assigns FLFlash.map to point to a default mapping function called flashMap( ). This default function is appropriate for flash devices that can be mapped directly into host memory. Internally, this function makes only one call, a call to flMap( ):
void FAR0 *flMap ( FLSocket vol, CardAddress address )
This function maps the flash window (described in the submitted FLSocket structure) to a specified card address and returns a pointer to that location, which is at some offset within the window.
After calling flMap( ), the specified part of the flash array is mapped to the socket window and is effectively a part of the local host memory. Thus, it is accessible through normal addressing. To address subsequent bytes on the card, increment the address pointer--but only so far as the socket window extends in host memory. At some offset you are no longer addressing memory on the card. Because the minimum window size is 4 Kilobytes, a safe practice is to never cross a 4 KByte boundary when adding offsets to a window location.
Of course, if the entire flash array is visible in the host address space, the map routine need do nothing more than return a pointer to the specified address in flash memory.
Unfortunately, not all flash devices allow this direct mapping of flash memory into host memory. For some devices, it is necessary to map through a buffer. For example, the MTD defined in target/src/drv/tffs/nfdc2048.c takes this approach. The identification routine of this MTD assigns FLFlash.map to cdsnMap, a pointer to cdsnMap( ), which is defined as follows:
static void FAR0 * cdsnMap ( FLFlash vol, CardAddress address, int length ) { cdsnRead(&vol,address,thisBuffer,length, 0); vol.socket->remapped = TRUE; return (void FAR0 *)thisBuffer; }
There is a common case of RFA fully visible in the host address space. In this marginal case, the only thing the map routine needs to do is to return a pointer to the specified address in flash memory.
If the flash device can be mapped directly into flash memory, it is generally a simple matter to read from it. TrueFFS supplies a default function that does a remap and simple memory copy to retrieve the data from the specified area. If the mapping is done through a buffer, you must provide your own read routine.
Typically, you should write your read routine as generically as possible. That is, it should read only a character or a word or a long word at a time, and that it should be able to handle an unaligned read as well as a read that crosses chip boundaries.
For an example of an MTD that provides its own read routine, see the cdsnRead( ) function defined in target/src/drv/tffs/nfdc2048.c.
Your write and erase functions should be as generic as possible. That is, the write should write only a character or a word or a long word at a time, and it should be able to handle an unaligned write as well as a write that crosses chip boundaries. Similarly, the erase function should be able to handle an erase that crosses chip boundaries. As input, the erase can expect a block number. Use the value of the erasableBlockSize member of the FLFlash structure to translate this block number to the offset within the flash array.
When writing these functions, you probably want to use the MTD helper functions flNeedVpp( ), flDontNeedVpp( ), and flWriteProtected( ). The interfaces for these routines are as follows:
FLStatus flNeedVpp(FLSocket vol) void flDontNeedVpp(FLSocket vol) FLBoolean flWriteProtected(FLSocket vol)
Use flNeedVpp( )if you need to turn on the Vpp (programming voltage) for the chip. Internally, flNeedVpp( ) bumps a counter, FLSocket.VppUsers, and then calls the function referenced in FLSocket.VppOn. After calling flNeedVpp( ), check its return status to verify that it succeeded in turning on Vpp.2
When done with the write or erase that required Vpp, call flDontNeedVpp( )to decrement the FLSocket.VppUsers counter. This FLSocket.VppUsers counter is part of a delayed-off system. While the chip is busy, TrueFFS keeps the chip continuously powered. When the chip is idle, TrueFFS turns off the voltage to conserve power.
Use flWriteProtected( )to test that the flash device is not write protected. MTD write and MTD erase routines must not do any flash programming before checking that writing to the card is allowed. The Boolean function flWriteProtected( ) returns TRUE if the card is write-protected and FALSE otherwise.
When TrueFFS tries to match an MTD to a flash device, it executes the functions referenced in mtdTable[ ], which is defined in target/src/drv/tffs/tffsConfig.c as follows:
MTDidentifyRoutine mtdTable[] = /* MTD tables */ { #ifdef INCLUDE_MTD_I28F016 i28f016Identify, #endif /* INCLUDE_MTD_I28F016 */ #ifdef INCLUDE_MTD_I28F008 i28f008Identify, #endif /* INCLUDE_MTD_I28F008 */ #ifdef INCLUDE_MTD_AMD amdMTDIdentify, #endif /* INCLUDE_MTD_AMD */ #ifdef INCLUDE_MTD_CDSN cdsnIdentify, #endif /* INCLUDE_MTD_CDSN */ #ifdef INCLUDE_MTD_DOC2 doc2Identify, #endif /* INCLUDE_MTD_DOC2 */ #ifdef INCLUDE_MTD_CFISCS cfiscsIdentify, #endif /* INCLUDE_MTD_CFISCS */ };
If you write a new MTD and want to make it available to TrueFFS, you must add its registration routine to this table. When doing so, you should probably surround the function name with conditional include statements. The symbolic constants that control these conditional includes are defined in the BSPs config.h. Using these constants, your end users can conditionally include specific MTDs. When you add your MTDs identification routine to this table, you should also add a new constant to the BSP's config.h.
1: Both flIntelIdentify( ) and flIntelSize( ) assume 8-bit access to the flash array. If this is inappropriate for your particular flash device, you could directly set the values of the relevant members of the vol structure.
2: An MTD does not need to touch Vcc. TrueFFS turns Vcc on before calling an MTD function.