High-level (chip) functions

The high-level functions provided by the chip driver are typically created as an inline file providing a fully-populated cyg_nand_dev_fns_v1 struct, instantiated by the CYG_NAND_FUNS macro. The high-level driver should not directly read or write to the hardware itself, but instead call into functions in the low-level driver.

The form the low-level functions should take is not prescribed; typically functions will be required to write commands to the device, to read and write data, and to query any status line which may be present. The high-level driver should normally provide a header file containing prototypes for the functions it requires from the low-level driver. (The low-level source file would provide the low-level functions required, include the high-level include, then instantiate the combined driver using the CYG_NAND_DEVICE macro.)

This source code layout is not intended as a prescription. It would for example be entirely in order to store pointers to the low-level functions in a struct and set priv to point to that struct, which could be useful in some cases.

Note: The device driver must not call malloc or otherwise allocate memory; all data should be in the stack or set as globals. This is because the driver may be required to run within a minimal eCos configuration.

These functions should all return 0 on success, or a negative eCos error code. In the event of an error, do not call back into the NAND library; use the NAND_CHATTER macro to report, in case a human is watching, and return an error code. The library will take care of ensuring the correct response to the application and updating the BBT as necessary.

Device initialisation

static int my_devinit (cyg_nand_device *dev);

The devinit function is the most complex, and logically one to write first. It is responsible for:

  • initialising the device, typically by sending a reset command;

  • interrogating the device to confirm its presence and properties;

  • setting up the partition table list (see "Planning a port" above);

  • setting up mutexes as necessary (see "Locking against concurrent access" above);

  • populating the other members of the cyg_nand_device struct (see below).

Interrogating the device is normally performed by sending a Read ID command and examining the result, which typically encodes some or all of the chip parameters.

Given the similarity between many NAND parts, it may be possible to write a generic driver to cover all of one or more manufacturer's parts, or indeed for all ONFI-compliant parts. At the time of writing, this has not yet been attempted.

The devinit function must set up the following struct members:


The size of the regular (non-spare) part of a page, expressed as the logarithm in base 2 of the number of bytes. For example, if pages are 2048 bytes long, page_bits would be 11. Obviously, the size of a page must be an exact power of two.


The number of bytes of spare area available in each page.


The base-2 log of the number of pages per eraseblock.


The total number of erase blocks in the device, expressed as a base-2 log.


The total size of the chip, not counting the spare areas. This is required so that the library can double-check that the given parameters make sense by comparing with the preceding fields. Again, this field is itself a base-2 logarithm.


Space for the in-memory Bad Block Table for this device.


This is the size of bbt.data, in bytes. At present, this should be two bits times the number of blocks in the device; in other words, 1<<(blockcount_bits-2) bytes.

The cyg_nand_device struct has two further members ecc and oob which must be set up to point to the ECC and OOB descriptors to use for the device. This is normally done by the CYG_NAND_DEVICE low-level instantiation macro, so will be better described in that section, but at this level you should be aware that it is also safe to set up the descriptor block during devinit. for example if multiple semantics might be you had included logic to detect what semantics to use.

The Bad Block Table itself is implemented in a way which intends to be compatible with the Linux MTD layer. A full parameter struct is not currently provided, though one may be in future.

Reading, writing and erasing data

The read and write operations are divided into three phases, with the following flow:

  • Begin. This is called once; the driver should lock any platform-level mutex and send the command and address.

  • Stride. This is called one or more times to read the page data from the device.

    Note: The reason for this is if the platform provides a NAND controller with hardware ECC: it is often necessary to read out the ECC registers every so often.

  • Finish. This is called once; it should read or write the spare area, (on programming) send a "program confirm" command and check its status, and unlock any platform-level mutex.

Erasing is a single-shot call which should lock any platform-specific mutex, send the command, check its status and unlock the mutex.

static int my_read_begin(cyg_nand_device *dev, cyg_nand_page_addr page);
static int my_read_stride(cyg_nand_device *dev, void * dest, size_t size);
static int my_read_finish(cyg_nand_device *dev, void * spare, size_t spare_size);

static int my_write_begin(cyg_nand_device *dev, cyg_nand_page_addr page);
static int my_write_stride(cyg_nand_device *dev, const void * src, size_t size);
static int my_write_finish(cyg_nand_device *dev, const void * spare, size_t spare_size);

static int my_erase_block(cyg_nand_device *dev, cyg_nand_block_addr blk);

Searching for factory-bad blocks

static int my_is_factory_bad(cyg_nand_device *dev, cyg_nand_block_addr blk);

The very first time a NAND chip is used, the library has to scan it to check for factory-bad eraseblocks and build up the Bad Block Table. This function is called repeatedly to do so, one block at a time; it should return 1 if the block is marked bad, or 0 if the block appears to be OK.

Typically this function will invoke read_page; blocks are usually marked factory-bad by the presence of a particular signature in the out-of-band area of the first or second page of that block.


It is extremely important that you get this function right; after an eraseblock has been written to, it is no longer possible to reliably determine whether the block was factory-bad. It is never safe to assume that the factory-bad signature for a chip is the same as that of a similarly-sized chip or another by the same manufacturer; always check the correct spec sheet for the actual part or part-family in use!

Tip: Because this function is critical and a subtle error could cripple your application some time later in the field when it runs across undetected factory-bad blocks, you might find it handy to have a double-check before proceeding. If you enable CYGSEM_IO_NAND_READONLY in your eCos configuration during early development, you can safely fire up a test application (which calls cyg_nand_lookup) whilst watching the chatter output: the scan will be performed, but no BBT will be written. You can then compare the number of bad blocks reported against the manufacturer's specification of the maximum. Double-check that your is_factory_bad function is correct before enabling read-write mode!

Declaring the function set

CYG_NAND_FUNS_V2(mydev_funs, my_devinit,
        my_read_begin, my_read_stride, my_read_finish,
        my_write_begin, my_write_stride, my_write_finish,
        my_erase_block, my_is_factory_bad);

This macro ties the above functions together into a struct whose name is given as its first argument. The name of the resulting struct must be quoted when the driver is formally instantiated, which is normally done by the low-level functions.

Note: Earlier versions of this library used a slightly different device interface, keyed off the macro CYG_NAND_FUNS. This interface has been retired.

Documentation license for this page: eCosPro License