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
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.
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.
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.