Low-level (board) functions

The set and prototypes of the functions required here will necessarily depend on the board and to a lesser extent on the NAND part itself. The following functionality is typically required:

  • Very low-level hardware initialisation - for example, GPIO pin direction and interrupt config - if this has not already been done by the platform HAL

  • Set up the chip partition table (see below)

  • Runtime hardware config as required, such as commanding an FPGA or CPLD to route lines to the NAND part

  • Write a command (byte)

  • Write an address (handful of bytes)

  • Write data, usually at the chip's full bus width (typically 8 or 16 bits)

  • Read data at full bus width

  • Read data at 8-bit width (if the chip has a 16 bit data bus, some commands - commonly ReadID - may return 8-bit data)

  • Poll any status lines required or - if supported - set them up as interrupts to allow sleeping-wait

Talking to the chip

It is impossible to prescribe how to achieve this, as it depends entirely on how the NAND part is wired up on the board.

The ideal situation is that the NAND part is wired in via the CPU's memory controller and that the controller is set up to do most of the hard work for you. In that case, reading and writing the device is as simple as accessing the correct memory-mapped I/O address; usually different address ranges connect to the device's command, address and data registers respectively.

Tip: The HAL provides a number of macros in <cyg/hal/hal_io.h> to read and write memory-mapped I/O.

Note: On platforms with an MMU, MMIO may be rerouted to different addresses to those on the board spec sheet. Check the MMU setup in the platform HAL.

On some platforms, you may have to invoke an FPGA or CPLD to be able to talk to the NAND chip. This might typically take the form of a handful of MMIO accesses, but should hopefully be fairly straightforward once you've figured out how the components interrelate.

The worst case is where you have no support from any sort of controller hardware and have to bit-bang GPIO lines to talk to the chip. This is a much more involved process; you have to take great care to get the timings right with carefully tuned delays. The result is usually quite CPU intensive, and could be clock speed sensitive too; you should check for and take account of any CDL settings in the architecture and variant HAL which allow the CPU clock frequency to be changed.

Tip: If your low-level functions take a cyg_nand_device pointer as an argument, you can use its priv member to hold or point to some relevant data like the MMIO addresses to use, which is preferable to hard-coding them. Indeed, if you wish your board port to support more than one chip, you should use the priv member to distinguish between them.

Setting up the chip partition table

It is the responsibility of the high-level devinit function to set up the device's partition table. (It may be appropriate for it to invoke a low-level function to do this.)

The partition definition is an array of cyg_nand_partition entries in the cyg_nand_device.

struct _cyg_nand_partition_t {
cyg_nand_device *dev;
cyg_nand_block_addr first;
cyg_nand_block_addr last;
};
typedef struct _cyg_nand_partition_t cyg_nand_partition;

struct _cyg_nand_device_t {
...
cyg_nand_partition partition[CYGNUM_NAND_MAX_PARTITIONS];
...
};

Application-visible partition numbers are simply indexes into this array.

  • On a live partition, dev must point back to the cyg_nand_device containing it. If NULL, the partition is inactive.

  • first is the number of the first block of the partition.

  • last is the number of the last block of the partition (not the number of blocks, unless the partition starts at block 0).

Putting it all together...

Finally, with everything else in place, we turn to the CYG_NAND_DEVICE macro to instantiate it.

CYG_NAND_DEVICE(my_nand, "onboard", &mydev_funs, &my_priv_struct, &linux_mtd_ecc, &nand_mtd_oob_64);

In order, the arguments to this macro are:

  • The name to give the resultant cyg_nand_device struct;

  • the device identifier string, application-visible to be used in cyg_nand_lookup() ;

  • a pointer to the device high-level function set to use, normally set up by the CYG_NAND_FUNS macro;

  • the priv member to include in the struct;

  • a pointer to the ECC semantics block to use. linux_mtd_ecc provides software ECC compatible with the Linux MTD layer, but it is strongly recommended to use onboard hardware ecc support if this is present as it gives a huge speed boost. See the Section called ECC implementation for more details.

  • a pointer to the OOB-area layout descriptor to use (see nand_oob.h : nand_mtd_oob_16 and nand_mtd_oob_64 are Linux-compatible layouts for devices with 16 and 64 bytes of spare area per page respectively).

The macro invokes the appropriate linker magic to pull all the compiled NAND device structs into one section so the NAND library can find them.

2017-02-09
Documentation license for this page: eCosPro License