Name
Instantiating — including the driver in an eCos target
Synopsis
#include <cyg/io/am29xxxxx_dev.h>
int cyg_am29xxxxx_init_check_devid_XX(
struct cyg_flash_dev* device)
;
int cyg_am29xxxxx_init_cfi_XX(
struct cyg_flash_dev* device)
;
int cyg_am29xxxxx_erase_XX(
struct cyg_flash_dev* device, cyg_flashaddr_t addr)
;
int cyg_am29xxxxx_program_XX(
struct cyg_flash_dev* device, cyg_flashaddr_t addr, const void* data, size_t len)
;
int cyg_at49xxxx_softlock(
struct cyg_flash_dev* device, const cyg_flashaddr_t addr)
;
int cyg_at49xxxx_hardlock(
struct cyg_flash_dev* device, const cyg_flashaddr_t addr)
;
int cyg_at49xxxx_unlock(
struct cyg_flash_dev* device, const cyg_flashaddr_t addr)
;
int cyg_am29xxxxx_read_devid_XX(
struct cyg_flash_dev* device)
;
Description
The AM29xxxxx family contains some hundreds of different flash devices, all supporting the same basic set of operations but with various common or uncommon extensions. The devices vary in capacity, performance, boot block layout, and width. There are also platform-specific issues such as how many devices are actually present on the board and where they are mapped in the address space. The AM29xxxxx driver package cannot know the details of every chip and every platform. Instead it is the responsibility of another package, usually the platform HAL, to supply the necessary information by instantiating some data structures. Two pieces of information are especially important: the bus configuration and the boot block layout.
Flash devices are typically 8-bits, 16-bits, or 32-bits wide (64-bit devices are not yet in common use). Most 16-bit devices will also support 8-bit accesses, but not all. Similarly 32-bit devices can be accessed 16-bits at a time or 8-bits at a time. A board will have one or more of these devices on the bus. For example there may be a single 16-bit device on a 16-bit bus, or two 16-bit devices on a 32-bit bus. The processor's bus logic determines which combinations are possible, and there will be a trade off between cost and performance: two 16-bit devices in parallel can provide twice the memory bandwidth of a single device. The driver supports the following combinations:
- 8
- A single 8-bit flash device on an 8-bit bus.
- 16
- A single 16-bit flash device on a 16-bit bus.
- 32
- A single 32-bit flash device on an 32-bit bus.
- 88
- Two parallel 8-bit devices on an 16-bit bus.
- 8888
- Four parallel 8-bit devices on a 32-bit bus.
- 1616
- Two parallel 16-bit devices on a 32-bit bus, with one device providing the bottom two bytes of each 32-bit datum and the other device providing the top two bytes.
- 16as8
- A single 16-bit flash device connected to an 8-bit bus.
- 32as16
- A single 32-bit flash device connected to a 16-bit bus.
These configuration all require slightly different code to manipulate
the hardware. The AM29xxxxx driver package provides separate functions
for each configuration, for example
cyg_am29xxxxx_erase_16
and
cyg_am29xxxxx_program_1616
.
Caution | |
---|---|
At the time of writing not all the configurations have been tested. |
The second piece of information is the boot block layout. Flash devices are subdivided into blocks (also known as sectors - both terms are in common use). Some operations such as erase work on a whole block at a time, and for most applications a block is the smallest unit that gets updated. A typical block size is 64K. It is inefficient to use an entire 64K block for small bits of configuration data and similar information, so many flash devices also support a number of smaller boot blocks. A typical 2MB flash device could have a single 16K block, followed by two 8K blocks, then a 32K block, and finally 31 full-size 64K blocks. The boot blocks may appear at the bottom or the top of the device. So-called uniform devices do not have boot blocks, just full-size ones. The driver needs to know the boot block layout. With modern devices it can work this out at run-time, but often it is better to provide the information statically.
Example
In most cases flash support is specific to a platform. Even if two platforms happen to use the same flash device there are likely to be differences such as the location in the address map. Hence there is little possibility of re-using the platform-specific code, and this code should be placed in the platform HAL rather than in a separate package. Typically this involves a separate file and a corresponding compile property in the platform HAL's CDL:
cdl_package CYGPKG_HAL_M68K_ALAIA { … compile -library=libextras.a alaia_flash.c … }
The contents of this file will not be accessed directly, only
indirectly via the generic flash API, so normally it would be removed
by link-time garbage collection. To avoid this the object file has to
go into libextras.a
.
The actual file alaia_flash.c
will look something like:
#include <pkgconf/system.h> #ifdef CYGPKG_DEVS_FLASH_AMD_AM29XXXXX_V2 #include <cyg/io/flash.h> #include <cyg/io/flash_dev.h> #include <cyg/io/am29xxxxx_dev.h> static const CYG_FLASH_FUNS(hal_alaia_flash_amd_funs, &cyg_am29xxxxx_init_check_devid_16, &cyg_flash_devfn_query_nop, &cyg_am29xxxxx_erase_16, &cyg_am29xxxxx_program_16, (int (*)(struct cyg_flash_dev*, const cyg_flashaddr_t, void*, size_t))0, &cyg_flash_devfn_lock_nop, &cyg_flash_devfn_unlock_nop); static const cyg_am29xxxxx_dev hal_alaia_flash_priv = { .devid = 0x45, .block_info = { { 0x00004000, 1 }, { 0x00002000, 2 }, { 0x00008000, 1 }, { 0x00010000, 63 } } }; CYG_FLASH_DRIVER(hal_alaia_flash, &hal_alaia_flash_amd_funs, 0, 0xFFC00000, 0xFFFFFFFF, 4, hal_alaia_flash_priv.block_info, &hal_alaia_flash_priv ); #endif
The bulk of the file is protected by an #ifdef
for
the AM29xxxxx flash driver. That driver will only be active if the
generic flash support is enabled. Without that support there will be
no way of accessing the device so instantiating the data structures
would serve no purpose. The rest of the file is split into three
structure definitions. The first supplies the functions which will be
used to perform the actual flash accesses, using a macro provided by
the generic flash code in cyg/io/flash_dev.h
. The relevant ones
have an _16
suffix, indicating that on this board
there is a single 16-bit flash device on a 16-bit bus. The second
provides information specific to AM29xxxxx flash devices.
The third provides the cyg_flash_dev
structure needed by the generic flash code, which contains pointers to
the previous two.
Functions
All eCos flash device drivers must implement a standard interface,
defined by the generic flash code CYGPKG_IO_FLASH
.
This interface includes a table of seven function pointers for various
operations: initialization, query, erase, program, read, locking and
unlocking. The query operation is optional and the generic flash
support provides a dummy implementation
cyg_flash_devfn_query_nop
. AM29xxxxx flash
devices are always directly accessible so there is no need for a
separate read function. The remaining functions are more complicated.
Usually the table can be declared const
. In a ROM
startup application this avoids both ROM and RAM copies of the table,
saving a small amount of memory. const
should not
be used if the table may be modified by a platform-specific
initialization routine.
Initialization
There is a choice of three main initialization functions. The simplest
is cyg_flash_devfn_init_nop
, which does nothing.
It can be used if the cyg_am29xxxxx_dev and
cyg_flash_dev structures are fully
initialized statically and the flash will just work without special
effort. This is useful if it is guaranteed that the board will always
be manufactured using the same flash chip, since the nop function
involves the smallest code size and run-time overheads.
The next step up is
cyg_am29xxxxx_init_check_devid_XX
, where
XX
will be replaced by the suffix appropriate for
the bus configuration. It is still necessary to provide all the device
information statically, including the devid
field in the cyg_am29xxxxx_dev structure.
This initialization function will attempt to query the flash device
and check that the provided device id matches the actual hardware. If
there is a mismatch the device will be marked uninitialized and
subsequent attempts to manipulate the flash will fail.
If the board may end up being manufactured with any of a number of
different flash chips then the driver can perform run-time
initialization, using a cyg_am29xxxxx_init_cfi_XX
function. This queries the flash device as per the Common Flash Memory
Interface Specification, supported by all current devices (although
not necessarily by older devices). The
block_info
field in the
cyg_am29xxxxx_dev structure and the
end
and
num_block_infos
fields in the
cyg_flash_dev structure will be filled in.
It is still necessary to supply the start
field statically since otherwise the driver will not know how to
access the flash device. The main disadvantage of using CFI is that it
increases the code size.
Caution | |
---|---|
If CFI is used then the cyg_am29xxxxx_dev
structure must not be declared |
A final option is to use a platform-specific initialization function.
This may be useful if the board may be manufactured with one of a
small number of different flash devices and the platform HAL needs to
adapt to this. The AM29xxxxx driver provides a utility function to
read the device id, cyg_am29xxxxx_read_devid_XX
:
static int alaia_flash_init(struct cyg_flash_dev* dev) { int devid = cyg_am29xxxxx_read_devid_1616(dev); switch(devid) { case 0x0042 : … case 0x0084 : … default: return CYG_FLASH_ERR_DRV_WRONG_PART; } }
There are many other possible uses for a platform-specific
initialization function. For example initial prototype boards might
have only supported 8-bit access to a 16-bit flash device rather than
16-bit access, but this problem was fixed in the next revision. The
platform-specific initialization function can figure out which model
board it is running on and replace the default
16as8
functions with faster 16
ones.
Erase and Program
The AM29xxxxx driver provides erase and program functions appropriate for the various bus configurations. On most targets these can be used directly. On some targets it may be necessary to do some extra work before and after the erase and program operations. For example if the hardware has an MMU then the part of the address map containing the flash may have been set to read-only, in an attempt to catch spurious memory accesses. Erasing or programming the flash requires write-access, so the MMU settings have to be changed temporarily. As another example some flash device may require a higher voltage to be applied during an erase or program operation. or a higher voltage may be desirable to make the operation proceed faster. A typical platform-specific erase function would look like this:
static int alaia_flash_erase(struct cyg_flash_dev* dev, cyg_flashaddr_t addr) { int result; … // Set up the hardware for an erase result = cyg_am29xxxxx_erase_32(dev, addr); … // Revert the hardware change return result; }
There are two configurations which affect the erase and program
functions, and which a platform HAL may wish to change:
CYGNUM_DEVS_FLASH_AMD_AM29XXXXX_V2_ERASE_TIMEOUT
and
CYGNUM_DEVS_FLASH_AMD_AM29XXXXX_V2_PROGRAM_TIMEOUT
.
The erase and program operations both involve polling for completion,
and these timeout impose an upper bound on the polling loop. Normally
these operations should never take anywhere close to the timeout
period, so a timeout indicates a catastrophic failure that should
really be handled by a watchdog reset. A reset is particularly
appropriate because there will be no clean way of aborting the flash
operation. The main reason for the timeouts is to help with debugging
when porting to new hardware. If there is a valid reason why a
particular platform needs different timeouts then the platform HAL's
CDL can require appropriate values for these options.
Locking
There is no single way of implementing the block lock and unlock
operations on all AM29xxxxx devices. If these operations are supported at
all then usually they involve manipulating the voltages on certain
pins. This would not be able to be handled by generic driver code since it requires
knowing how these pins can be manipulated via the processor's GPIO
lines. Therefore the AM29xxxxx driver does not usually provide lock and unlock
functions, and instead the generic dummy functions
cyg_flash_devfn_lock_nop
and
cyg_flash_devfn_unlock_nop
should be used. An exception exists for
the AT49xxxx family of devices which are sufficiently AMD
compatible in other respects. Otherwise, if a
platform does provide a way of implementing the locking then this can
be handled by platform-specific functions.
static int alaia_lock(struct cyg_flash_dev* dev, const cyg_flashaddr_t addr) { … } static int alaia_unlock(struct cyg_flash_dev* dev, const cyg_flashaddr_t addr) { … }
If real locking functions are implemented then the platform HAL's CDL
script should implement the CDL interface
CYGHWR_IO_FLASH_BLOCK_LOCKING
. Otherwise the
generic flash package may believe that none of the flash drivers in the
system provide locking functionality and disable the interface functions.
AT49xxxx locking
As locking is standardised across the AT49xxxx family of AMD AM29xxxxx
compatible Flash parts, a method supporting this is included within this
driver. cyg_at49xxxx_softlock_XX
provides a means of
locking a Flash sector such that it may be subsequently unlocked.
cyg_at49xxxx_hardlock_XX
locks a sector such that
it cannot be unlocked until after reset or a power cycle.
cyg_at49xxxx_unlock_XX
unlocks a sector that has
previously been softlocked. At power on or Flash device reset, all sectors
default to being softlocked.
Other
The driver provides a set of functions
cyg_am29xxxxx_read_devid_XX
, one per supported
bus configuration. These functions take a single argument, a pointer
to the cyg_flash_dev structure, and return
the chip's device id. For older devices this id is a single byte. For
more recent devices the id is a 3-byte value, 0x7E followed by a
further two bytes that actually identify the device.
cyg_am29xxxxx_read_devid_XX
is usually called
only from inside a platform-specific driver initialization routine,
allowing the platform HAL to adapt to the actual device present on the
board.
Device-Specific Structure
The cyg_am29xxxxx_dev structure provides
information specific to AM29xxxxx flash devices, as opposed to the
more generic flash information which goes into the
cyg_flash_dev structure. There are only two
fields: devid
and
block_info
.
devid
is only needed if the driver's
initialization function is set to
cyg_am29xxxxx_init_check_devid_XX
. That function
will extract the actual device info from the flash chip and compare it
with the devid
field. If there is a
mismatch then subsequent operations on the device will fail.
The block_info
field consists of one or
more pairs of the block size in bytes and the number of blocks of that
size. The order must match the actual hardware device since the flash
code will use the table to determine the start and end locations of
each block. The table can be initialized in one of three ways:
-
If the driver initialization function is set to
cyg_flash_devfn_init_nop
orcyg_am29xxxxx_init_check_devid_XX
then the block information should be provided statically. This is appropriate if the board will also be manufactured using the same flash chip. -
If
cyg_am29xxxxx_init_cfi_XX
is used then this will fill in the block info table. Hence there is no need for static initialization. - If a platform-specific initialization function is used then either this should fill in the block info table, or the info should be provided statically.
The size of the block_info
table is
determined by the configuration option
CYGNUM_DEVS_FLASH_AMD_AM29XXXXX_V2_ERASE_REGIONS
.
This has a default value of 4, which should suffice for nearly all
AM29xxxxx flash devices. If more entries are needed then the platform
HAL's CDL script should require a larger value.
If the cyg_am29xxxxx_dev structure is
statically initialized then it can be const
. This
saves a small amount of memory in ROM startup applications. If the
structure is updated at run-time, either by
cyg_am29xxxxx_init_cfi_XX
or by a
platform-specific initialization routine, then it cannot be
const
.
Flash Structure
Internally the generic flash code works in terms of
cyg_flash_dev structures, and the platform
HAL should define one of these. The structure should be placed in the
cyg_flashdev
table. The following fields need to be
provided:
-
funs
- This should point at the table of functions.
-
start
-
The base address of the flash in the address map. On
some board the flash may be mapped into memory several times, for
example it may appear in both cached and uncached parts of the address
space. The
start
field should correspond to the cached address. -
end
-
The address of the last byte in the flash. It can
either be statically initialized, or
cyg_am29xxxxx_init_cfi_XX
will calculate its value at run-time. -
num_block_infos
-
This should be the number of entries in the
block_info
table. It can either be statically initialized or it will be filled in bycyg_am29xxxxx_init_cfi_XX
. -
block_info
- The table with the block information is held in the cyg_am29xxxxx_dev structure, so this field should just point into that structure.
-
priv
- This field is reserved for use by the device driver. For the AM29xxxxx driver it should point at the appropriate cyg_am29xxxxx_dev structure.
The cyg_flash_dev structure contains a number
of other fields which are manipulated only by the generic flash code.
Some of these fields will be updated at run-time so the structure
cannot be declared const
.
Multiple Devices
A board may have several flash devices in parallel, for example two
16-bit devices on a 32-bit bus. It may also have several such banks
to increase the total amount of flash. If each device provides 2MB,
there could be one bank of 2 parallel flash devices at 0xFF800000 and
another bank at 0xFFC00000, giving a total of 8MB. This setup can be
described in several ways. One approach is to define two
cyg_flash_dev structures. The table of
function pointers can usually be shared, as can the
cyg_am29xxxxx_dev structure. Another approach
is to define a single cyg_flash_dev
structure but with a larger block_info
table, covering the blocks in both banks of devices. The second
approach makes more efficient use of memory.
Many variations are possible, for example a small slow flash device may be used for initial bootstrap and holding the configuration data, while there is also a much larger and faster device to hold a file system. Such variations are usually best described by separate cyg_flash_dev structures.
If more than one cyg_flash_dev structure is
instantiated then the platform HAL's CDL script should implement the
CDL interface CYGHWR_IO_FLASH_DEVICE
once for every
device past the first. Otherwise the generic code may default to the
case of a single flash device and optimize for that.
Platform-Specific Macros
The AM29xxxxx driver source code includes the header files
cyg/hal/hal_arch.h
and
cyg/hal/hal_io.h
, and hence
indirectly the corresponding platform header files (if defined).
Optionally these headers can define macros which are used inside the
driver, thus giving the HAL limited control over how the driver works.
Cache Management
By default the AM29xxxxx driver assumes that the flash can be accessed
uncached, and it will use the HAL
CYGARC_UNCACHED_ADDRESS
macro to map the cached
address in the start
field of the
cyg_flash_dev structure into an uncached
address. If for any reason this HAL macro is inappropriate for the
flash then an alternative macro
HAL_AM29XXXXX_UNCACHED_ADDRESS
can be defined
instead. However fixing the
CYGARC_UNCACHED_ADDRESS
macro is normally the
better solution.
If there is no way of bypassing the cache then the platform HAL should
implement the CDL interface
CYGHWR_DEVS_FLASH_AMD_AM29XXXXX_V2_CACHED_ONLY
. The flash
driver will now disable and re-enable the cache as required. For
example a program operation will involve the following:
AM29_INTSCACHE_STATE; AM29_INTSCACHE_BEGIN(); while ( ! finished ) { write a burst of CYGNUM_DEVS_FLASH_AMD_AM29XXXXX_V2_PROGRAM_BURST_SIZE AM29_INTSCACHE_SUSPEND(); AM29_INTSCACHE_RESUME(); } AM29_INTSCACHE_END();
The default implementations of these INTSCACHE macros are as follows:
STATE
defines any local variables that may be
needed, e.g. to save the current interrupt state;
BEGIN
disables interrupts, synchronizes the data
caches, disables it, and invalidates the current contents;
SUSPEND
re-enables the data cache and then
interrupts; RESUME
disables interrupts and the
data cache; END
re-enables the cache and then
interrupts. The cache is only disabled when interrupts are disabled,
so there is no possibility of an interrupt handler running or a
context switch occurring while the cache is disabled, potentially
leaving the system running very slowly. The data cache synchronization
ensures that there are no dirty cache lines, so when the cache is
disabled the low-level flash write code will not see stale data in
memory. The invalidate ensures that at the end of the operation
higher-level code will not pick up stale cache contents instead of the
newly written flash data. The SUSPEND
and
RESUME
macros only re-enable and disable the data
cache. An interrupt and possibly a context switch may occur between
these macros and use the cache normally. It is assumed that any code
which runs at this time will not touch the memory being used by the
flash operation, so as far as the low-level program code is concerned
it can just continue to use the uncached memory contents as set up by
the BEGIN
macro. If any code modifies the const
data currently being written to a flash block or tries to read the
flash block being modified then the system's behaviour is undefined.
Theoretically a more robust approach is possible, synchronizing and
invalidating the cache again in every RESUME
.
However these cache operations can be expensive and
RESUME
may get invoked some thousands of times
for every flash block, so this alternative approach would cripple the
driver's performance.
Some implementations of the HAL cache macros may not provide the exact
semantics required by the flash driver. For example
HAL_DCACHE_DISABLE
may have an unwanted side
effect, or it may do more work than is needed here. The driver will
check for alternative macros
HAL_AM29XXXXX_INTSCACHE_STATE
,
HAL_AM29XXXXX_INTSCACHE_BEGIN
,
HAL_AM29XXXXX_INTSCACHE_SUSPEND
,
HAL_AM29XXXXX_INTSCACHE_RESUME
and
HAL_AM29XXXXX_INTSCACHE_END
, using these instead of
the defaults.
2024-03-18 | Open Publication License |