Common SPI Memory Device Hardware Driver — Interface to a hardware device driver



Application developers should not normally need to concern themselves with the internal API between this common layer and the H/W specific device drivers. The following information is primarily for H/W device driver developers.

In most cases the platform (PLF) declares the individual flash driver instances. The top-level descriptor as used with the flash API (CYGPKG_IO_FLASH) should reference the flash API functions provided by this package (cyg_devs_flash_common_funs) as well as provide a per-instance cyg_flash_csm_context_t structure initialised with a reference to the instance-specific hardware driver descriptor in the p_hwdriver field.

The driver instance specific cyg_spi_common_hwdriver_t descriptor is used to describe the hardware driver specific features to this common layer.

Flash API                      Common                                  H/W Driver
(struct cyg_flash_dev).priv -> (cyg_flash_csm_context_t).p_hwdriver -> (cyg_spi_common_hwdriver_t)

All architecture/platform/HAL eCos xSPI device drivers using the CYGPKG_DEVS_FLASH_SPI_COMMON package must implement a standard interface defined by the header <cyg/io/flash_csm_dev.h>. The interface descriptor structure includes a private pointer for the H/W driver context, a “features” set and a set of function pointers for various operations: initialization, memory operation, memory-mapped access and general configuration.

struct cyg_spi_common_hwdriver {
    // H/W driver private (opaque) context:
    const void *p_io; // H/W driver specific I/O information

    // H/W driver feature set descriptor:
    const cyg_flash_csm_features_t * const p_features;

    // Common H/W driver API
    cyg_spi_common_hwdriver_init *init; // initialisation function
    cyg_spi_common_hwdriver_op *op; // single command operation function
    cyg_spi_common_hwdriver_mm_start *mm_start; // memory-mapped start/enable
    cyg_spi_common_hwdriver_mm_stop *mm_stop; // memory-mapped stop/disable
    cyg_spi_common_hwdriver_config *config; // get/set config+control

Hardware Driver Features

The p_features structure provides fixed information used to describe to this common layer the features and settings of the H/W driver instance:

typedef struct cyg_flash_csm_features {
    cyg_uint32 avail; // bitmask of H/W driver available features
    cyg_uint32 mmaddr; // if MM capable, base address for MM region
    // Since early JESD216 standards do not provide a mechanism for the device
    // to report its maximum frequency we allow the platform/variant HAL to be
    // configured with maximum rates.
    cyg_uint32 max_sdr; // if non-zero platform/variant HAL provided maximum SDR baudrate
    cyg_uint32 max_ddr; // if non-zero platform/variant HAL provided maximum DDR baudrate
    cyg_uint32 nmodes; // number of modes present in modes vector
    const cyg_uint32 * const p_modes; // pointer to vector of FLASH_CSM_OP_MODE_MASK
                                      // covered bitmasks for available modes
} cyg_flash_csm_features_t;

The p_features structure allows the H/W driver to report the SPI modes capable by the device driver. This can, for example, be used in conjunction with information gathered from the device using SFDP to select the common subset of supported access methods: e.g. Quad (QSPI) vs Octal (OSPI).

The current set of available feature flags indicating H/W driver support is:

FLASH_CSM_FEATURE_ADDR33-byte addressing supported
FLASH_CSM_FEATURE_ADDR44-byte addressing supported
FLASH_CSM_FEATURE_ADDR55-byte addressing supported
FLASH_CSM_FEATURE_MODEBITSDriver supports writing mode bits (sometimes referred to as OPT or Alternate) bits
FLASH_CSM_FEATURE_CMD88-bit commands supported
FLASH_CSM_FEATURE_CMD1616-bit commands supported
FLASH_CSM_FEATURE_MMMemory mapped access supported
FLASH_CSM_FEATURE_MM_XIPeXecute-In-Place supported
FLASH_CSM_FEATURE_MM_SMDriver can memory-map serial-memory as well as data-memory
FLASH_CSM_FEATURE_MM_SM_RARandom Access supported for memory mapped serial-memory
FLASH_CSM_FEATURE_CRContinuous Read supported
FLASH_CSM_FEATURE_DSData Strobe signalling available

The p_modes pointer references the nmodes deep vector of access modes supported, encoded using the same OP bitmask encoding as used for the individual memory operations. For example an Octal (OSPI) capable driver might define:

// List of possible modes for this driver:
static const cyg_uint32 cyg_hwdriver_modes[] = {
    // 8-line Octal (OSPI)
    FLASH_CSM_OP_MODE_1S1S8S, //  0
    FLASH_CSM_OP_MODE_1S8S8S, //  1
    FLASH_CSM_OP_MODE_8S8S8S, //  2
    FLASH_CSM_OP_MODE_8D8D8D, //  3
    // 4-line QSPI
    FLASH_CSM_OP_MODE_1S1S4S, //  4
    FLASH_CSM_OP_MODE_1S4S4S, //  5
    FLASH_CSM_OP_MODE_4S4S4S, //  6
    FLASH_CSM_OP_MODE_4S4D4D, //  7
    // 2-line
    FLASH_CSM_OP_MODE_1S1S2S, //  8
    FLASH_CSM_OP_MODE_1S2S2S, //  9
    FLASH_CSM_OP_MODE_2S2S2S, // 10
    // 1-line
    FLASH_CSM_OP_MODE_1S1S1S, // 11

Referencing the vector in its feature descriptor:

static const cyg_flash_csm_features_t hwdriver1_features = {
    .nmodes = NUMOF_(cyg_hwdriver_modes),
    .p_modes = &cyg_hwdriver_modes[0]

Hardware Driver-Specific Structure

The p_io pointer allows the H/W device driver to hold per-instance private data as needed for the operation of the driver.

Normally the driver context would be split into read-only, constant, data that could be held in the code with only the truly dynamic context occupying RAM space. See the Hardware Example below for an outline.


The H/W driver provides its common driver API via the cyg_spi_common_hwdriver_t descriptor. For the function pointers the NULL value can be used to indicate that the relevant support is not required. Only the op function must be provided, though it is unlikely that the driver would not require a init function to be called at startup.


typedef int cyg_spi_common_hwdriver_init(const void *p_info, cyg_bool do_reset, cyg_uint32 baudrate);

This function allows the H/W driver to complete the run-time initialisation of any dynamic context needed, along with setting up the controller in preperation for the first operation.

This can consist of attaching any ISR/DSR or DMA handlers needed, setting up I/O pin configurations (if the platform/architecture uses pin multiplexing), etc.

The reset parameter indicates whether the upper layer requires the hardware to be “reset” back to a known state.

The baudrate is the clock frequency that will be used for the initial operations. Normally (for SFDP devices) this will be 50MHz.

The function call should return standard flash API status code. e.g. CYG_FLASH_ERR_OK to indicate success.

Memory Operation

typedef cyg_bool cyg_spi_common_hwdriver_op(const void *p_info, const cyg_flash_csm_op_t *p_op);

This is the core operation of the H/W driver interface. The referenced cyg_flash_csm_op_t pointer p_op describes the basic operation to be performed on the serial memory device.

The p_info pointer is a reference to the H/W driver specific context supplied when declaring the flash descriptor.

The function should return a simple boolean true success indication, or false if an error occurred.

The <cyg/io/flash_csm_dev.h> header defines the referenced p_op structure:

typedef struct cyg_flash_csm_op {
    cyg_uint32 mode; // Encoded bus information and control
    cyg_uint32 cmdflags; // Instruction CMD and extra control flags
    cyg_uint32 address; // Device relative address
    cyg_uint32 opt; // Upto 32-bits of OPT (mode-bits; alternate) data
    cyg_uint32 timeout; // Millisecond timeout for operation
    cyg_uint32 nbytes; // Non-zero is number of valid bytes from p_buff
    cyg_uint8 *p_buff; // Pointer to data buffer for transfer
} cyg_flash_csm_op_t;

The mode and cmdflags fields define whether the other fields are used/required. For example, a simple device operation to enable the write-latch will not normally require any data to be written (or read) and so the nbytes and p_buff fields would not be referenced for that operation.

The <cyg/io/flash_csm_dev.h> header is the definitive source for the bitmask use for the mode and cmdflags fields and should be examined by the developer writing a H/W driver. The header contains helper manifests and macros to aid the decoding of the fields.


The mode bitmask encodes the operation. It contains some single-bit boolean flags as well as some multi-bit values with specific encodings.

The value 0x00000000 (CSM_OP_INVALID) is never a valid descriptor since we should always have at least one of the instruction, address, opt or data phases defined.

An operation compromises one or more phases in the order: Instruction, Address, Mode and Data. The mode bitmask encodes which phases are enabled, and hence their associated bitmask flags and values are valid, as well as some general operation control flags.

  1. Instruction phase

    If an instruction phase is required then the FLASH_CSM_OP_IP mask will have the value FLASH_CSM_OP_IP_ACTIVE, otherwise the flag will have the value FLASH_CSM_OP_IP_NONE.

    If FLASH_CSM_OP_IP_ACTIVE then the FLASH_CSM_OP_IW_MASK bits encode the number of lines to be used for the instruction phase:

    FLASH_CSM_OP_IW_LINE22-lines (Dual)
    FLASH_CSM_OP_IW_LINE44-lines (Quad)
    FLASH_CSM_OP_IW_LINE88-lines (Octal)

    If FLASH_CSM_OP_IP_ACTIVE then the p_op field cmdflags holds the command instruction.

    The command length is encoded by the FLASH_CSM_OP_CL mask. The value should be FLASH_CSM_OP_CL_8BIT for 8-bit commands, and FLASH_CSM_OP_CL_16BIT for 16-bit commands.

    If FLASH_CSM_OP_IP_ACTIVE then the FLASH_CSM_OP_IP_IR mask encodes whether the instruction phase is Single-Data-Rate (FLASH_CSM_OP_IR_SDR) or Dual-Data-Rate (FLASH_CSM_OP_IR_DDR).

  2. Address phase

    If an address phase is required then the FLASH_CSM_OP_AP mask will have the value FLASH_CSM_OP_AP_ACTIVE, otherwise the flag will have the value FLASH_CSM_OP_AP_NONE.

    If FLASH_CSM_OP_AP_ACTIVE then the FLASH_CSM_OP_AB_MASK bits encode the number of bytes used for an address:

    FLASH_CSM_OP_AB_3BYTE3-byte (24-bit) address
    FLASH_CSM_OP_AB_4BYTE4-byte (32-bit) address
    FLASH_CSM_OP_AB_5BYTE5-byte (40-bit) address (not currently supported)

    If FLASH_CSM_OP_AP_ACTIVE then the FLASH_CSM_OP_AW_MASK bits encode the number of lines to be used for the address phase:

    FLASH_CSM_OP_AW_LINE22-lines (Dual)
    FLASH_CSM_OP_AW_LINE44-lines (Quad)
    FLASH_CSM_OP_AW_LINE88-lines (Octal)

    If FLASH_CSM_OP_AP_ACTIVE then the p_op field address should define the device relative address for the operation.

    If FLASH_CSM_OP_AP_ACTIVE then the FLASH_CSM_OP_AR mask encodes whether the address phase is Single-Data-Rate (FLASH_CSM_OP_AR_SDR) or Dual-Data-Rate (FLASH_CSM_OP_AR_DDR).

  3. Mode phase

    Different hardware implementations support OPT/Alternate bytes of differing sizes and limitations. The JESD216D.01 standard describes these as “mode bits” and are sent after the address phase.

    If a mode phase is required then the FLASH_CSM_OP_MP mask will have the value FLASH_CSM_OP_MP_ACTIVE, otherwise the flag will have the value FLASH_CSM_OP_MP_NONE.

    If FLASH_CSM_OP_MP_ACTIVE then the FLASH_CSM_OP_MB_MASK bits encode the number of bits (range 1..32). The mode bits are signalled on the same number of SPI lines as the address phase.

    If FLASH_CSM_OP_MP_ACTIVE then the p_op field opt holds the mode bits value.

    If FLASH_CSM_OP_MP_ACTIVE then the FLASH_CSM_OP_MR mask encodes whether the mode bits phase is Single-Data-Rate (FLASH_CSM_OP_MR_SDR) or Dual-Data-Rate (FLASH_CSM_OP_MR_DDR).

  4. Data phase

    If an data phase is required then the FLASH_CSM_OP_DP mask will have the value FLASH_CSM_OP_DP_ACTIVE, otherwise the flag will have the value FLASH_CSM_OP_DP_NONE.

    If FLASH_CSM_OP_DP_ACTIVE then the FLASH_CSM_OP_DW_MASK bits encode the number of lines to be used for the data phase:

    FLASH_CSM_OP_DW_LINE22-lines (Dual)
    FLASH_CSM_OP_DW_LINE44-lines (Quad)
    FLASH_CSM_OP_DW_LINE88-lines (Octal)

    If FLASH_CSM_OP_DP_ACTIVE then the p_op field nbytes should define the number of valid memory bytes referenced by the p_buff pointer.

    If FLASH_CSM_OP_DP_ACTIVE then the FLASH_CSM_OP_DR mask encodes whether the address phase is Single-Data-Rate (FLASH_CSM_OP_DR_SDR) or Dual-Data-Rate (FLASH_CSM_OP_DR_DDR).

The mode field also encodes other information that may be required by the H/W driver.

The FLASH_CSM_OP_SD mask has the value FLASH_CSM_OP_SD_DATA when the operation is accessing the data-memory of the flash device, and the value FLASH_CSM_OP_SD_DEVICE if accessing device-internal “memory” (e.g. SFDP tables, unique IDs, etc.).

The FLASH_CSM_OP_TD mask encodes the Transfer Direction, whether the operation is a read (FLASH_CSM_OP_TD_READ) or a write (FLASH_CSM_OP_TD_WRITE).

The FLASH_CSM_OP_DA indicates whether the transfer should be undertaken by the operation call directly (FLASH_CSM_OP_DA_XFER) or should be deferred for subsequent memory-mapped access (FLASH_CSM_OP_DA_DEFER).

When reading the FLASH_CSM_OP_CR mask indicates that the device is configured for Continuous Read. If FLASH_CSM_OP_CR_NONE then continuous read is not configured. If FLASH_CSM_OP_CR_ACTIVE then it indicates that the controller can setup access for continuous read mode.

The FLASH_CSM_OP_DS encodes whether the operation requires the Data Strobe signal (FLASH_CSM_OP_DS_ACTIVE) or the signal is not required (FLASH_CSM_OP_DS_NONE).


When required by the mode bitmask the cmdflags field encodes the command code (8- or 16-bit), the number of Delay Cycles and whether the timeout is valid.


The timeout value is only valid if the cmdflag flag FLASH_CSM_CMD_TO_VALID is set, otherwise the field is ignored.

The timeout field is a millisecond operation timeout, or one of the special values: CYG_FLASH_CSM_TO_NOWAIT or CYG_FLASH_CSM_TO_INFINITY. The ...NOWAIT value is for an immediate, polled, return without waiting, operation. The ...INFINITY value is for when the operation should block indefinately until completion (success or error indicated).

Memory Mapped

typedef cyg_bool cyg_spi_common_hwdriver_mm_start(const void *p_info);
typedef cyg_bool cyg_spi_common_hwdriver_mm_stop(const void *p_info);

The optional mm_start and mm_stop functions are used to notify the H/W driver when memory-mapped state is being changed.

Since most devices cannot continue to provide memory-mapped access whilst being erased or programmed the common driver layer allows the H/W driver to perform any controller operations needed to ensure the hardware is in the correct mode. For example, this may include changing the cached/uncached state for the memory covered by flash device, or require specific controller operations to abort any active memory-mapped pre-fetching that may be occurring.


typedef cyg_bool cyg_spi_common_hwdriver_config(const void *p_info,
                                                cyg_uint32 key, void *p_buff, cyg_uint32 *p_len);

The optional H/W driver supplied config function is used with specific configuration key values to interact with the H/W driver:


Used by the common layer to control the clock frequency (normally named SCK) of the H/W driver instance. The max_sdr and max_ddr fields of the H/W driver supplied (cyg_flash_csm_features_t) descriptor allow the H/W driver to limit the upper frequency to that supported by the specific controller, with the common layer device support being used for the actual flash memory device maximum rates possible.

The frequency setting is a simple 32-bit unsigned integer (e.g. cyg_uint32).


If required, for OCTOSPI devices, these options provide common layer control of the memory type as used by the H/W driver.

The memory type setting is currently a simple 32-bit unsigned integer (e.g. cyg_uint32):

This option indicates Micron style byte-ordering.
This option indicates Macronix style byte-ordering.
Indicates normal SPI access.
This value indicates that no specific memory type has been set.

For configurarions that use a data strobe signal (DQS) these config options provide the mechanism for informing the H/W driver of the device data strobe timing.

The data strobe settings are a simple 32-bit unsigned integer (e.g. cyg_uint32):

Start of first data bit aligned with the first rising edge of DQS.
First rising edge of DQS in the middle of the first data bit.
First rising edge of DQS is half a clock cycle before the start of the first data bit.
This setting is used to indicate that the data strobe timing is not defined or unknown.


The following section provides an example skeleton of how a H/W driver instance can be declared.

Since device drivers normally have a requirement for some fixed (constant) information describing the hardware configuration as well as possibly some dynamic state to hold run-time information (e.g. ISR or DSR state) the example below shows a simple framework. The example hw_driver_ctx_t structure used for the dynamic context and the hw_driver_io_t structure holding the constant/fixed information are specific to the H/W driver implementation and the underlying H/W controller requirements.

For our example instance in the H/W driver source we can provide a RAM based private context for the dynamic state:

static hw_driver_ctx_t hw_dynamic1 = {};

This can then be referenced from a constant (normally placed in read-only memory by the linker) structure with the fixed information for the driver along with a pointer to the RAM based dynamic run-time context structure:

static const hw_driver_io_t hw_context1 {
    .p_ctx = &hw_driver_dynamic1, // dynamic H/W driver state
    // The fixed information needed by the H/W driver
    .intr_vec = <HAL_INTERRUPT_NUMBER>

The driver can then provide a per-instance common H/W driver API structure referencing the H/W driver context and the features and functions provided by the driver:

const cyg_spi_common_hwdriver_t cyg_dev_flash_csm_example1= {
    .p_io = &hw_context1,
    .p_features = &hw_features,
    .init = hw_init,
    .op = hw_op,
    .mm_start = NULL, // op interface sufficient for this driver
    .mm_stop = NULL, // op interface sufficient for this driver
    .config = hw_config

With the H/W driver providing the cyg_spi_common_hwdriver_t structure the platform specific sources would then reference the H/W driver instance when declaring the flash object in the platform/HAL specific source file:

static struct cyg_flash_block_info cyg_flash_common_block_info;

static cyg_flash_csm_context_t cyg_flash_common_ctx = {
    .p_hwdriver = &cyg_dev_flash_csm_example1, // reference H/W instance

                 <BASE_ADDR>, // start (normally same as p_features.mmaddr field
                 <BASE_ADDR>, // end (depends on detected device, so filled in at run-time)
                 1, // number of flash block info structures

After successful flash device initialisation the struct cyg_flash_dev field end will hold the end address for the flash area. The start and end addresses are used by the flash API to select the relevant device descriptor when accessing a flashg area.

When called, the common layer code can use the p_hwdriver field to access the specific H/W instance used to access the actual flash device for the area, with the H/W driver subsequently de-referencing its own structures to access the fixed and dynamic portions of its context.