Multi-bit SPI Bus

The MSPI (multi-bit SPI) is provided as a generic API to accommodate advanced SPI peripherals and devices that typically require command, address and data phases, and multiple signal lines during these phases. While the API supports advanced features such as XIP and scrambling, it is also compatible with generic SPI.

MSPI Controller API

Zephyr’s MSPI controller API may be used when a multi-bit SPI controller is present. E.g. Ambiq MSPI, QSPI, OSPI, Flexspi, etc. The API supports single to hex SDR/DDR IO with variable latency and advanced features such as XIP and scrambling. Applicable devices include but not limited to high-speed, high density flash/psram memory devices, displays and sensors.

The MSPI interface contains controller drivers that are SoC platform specific and implement the MSPI APIs, and device drivers that reference these APIs. The relationship between the controller and device drivers is many-to-many to allow for easy switching between platforms.

Here is a list of generic steps for initializing the MSPI controller and the MSPI bus inside the device driver initialization function:

  1. Initialize the data structure of the MSPI controller driver instance. The usual device defining macros such as DEVICE_DT_INST_DEFINE can be used, and the initialization function, config and data provided as a parameter to the macro.

  2. Initialize the hardware, including but not limited to:

    • Check mspi_cfg against hardware’s own capabilities to prevent incorrect usages.

    • Setup default pinmux.

    • Setup the clock for the controller.

    • Power on the hardware.

    • Configure the hardware using mspi_cfg and possibly more platform specific settings.

    • Usually, the mspi_cfg is filled from device tree and contains static, boot time parameters. However, if needed, one can use mspi_config() to re-initialize the hardware with new parameters during runtime.

    • Release any lock if applicable.

  3. Perform device driver initialization. As usually, DEVICE_DT_INST_DEFINE can be used. Inside device driver initialization function, perform the following required steps.

    1. Call mspi_dev_config() with device specific hardware settings obtained from device datasheets.

      • The mspi_dev_cfg should be filled by device tree and helper macro MSPI_DEVICE_CONFIG_DT can be used.

      • The controller driver should then validate the members of mspi_dev_cfg to prevent incorrect usage.

      • The controller driver should implement a mutex to protect from accidental access.

      • The controller driver may also switch between different devices based on mspi_dev_id.

    2. Call API for additional setups if supported by hardware

      • mspi_xip_config() for XIP feature

      • mspi_scramble_config() for scrambling feature

      • mspi_timing_config() for platform specific timing setup.

    3. Register any callback with mspi_register_callback() if needed.

    4. Release the controller mutex lock.

Transceive

The transceive request is of type mspi_xfer which allows dynamic change to the transfer related settings once the mode of operation is determined and configured by mspi_dev_config().

The API also supports bulk transfers with different starting addresses and sizes with mspi_xfer_packet. However, it is up to the controller implementation whether to support scatter IO and callback management. The controller can determine which user callback to trigger based on mspi_bus_event_cb_mask upon completion of each async/sync transfer if the callback had been registered using mspi_register_callback(). Or not to trigger any callback at all with MSPI_BUS_NO_CB even if the callbacks are already registered. In which case that a controller supports hardware command queue, user could take full advantage of the hardware performance if scatter IO and callback management are supported by the driver implementation.

Device Tree

Here is an example for defining an MSPI controller in device tree: The mspi controller’s bindings should reference mspi-controller.yaml as one of the base.

mspi0: mspi@400 {
         status = "okay";
         compatible = "zephyr,mspi-emul-controller";

         reg = < 0x400 0x4 >;
         #address-cells = < 0x1 >;
         #size-cells = < 0x0 >;

         clock-frequency = < 0x17d7840 >;
         op-mode = "MSPI_CONTROLLER";
         duplex = "MSPI_HALF_DUPLEX";
         ce-gpios = < &gpio0 0x5 0x1 >, < &gpio0 0x12 0x1 >;
         dqs-support;

         pinctrl-0 = < &pinmux-mspi0 >;
         pinctrl-names = "default";
};

Here is an example for defining an MSPI device in device tree: The mspi device’s bindings should reference mspi-device.yaml as one of the base.

&mspi0 {

         mspi_dev0: mspi_dev0@0 {
                  status = "okay";
                  compatible = "zephyr,mspi-emul-device";

                  reg = < 0x0 >;
                  size = < 0x10000 >;

                  mspi-max-frequency = < 0x2dc6c00 >;
                  mspi-io-mode = "MSPI_IO_MODE_QUAD";
                  mspi-data-rate = "MSPI_DATA_RATE_SINGLE";
                  mspi-hardware-ce-num = < 0x0 >;
                  read-instruction = < 0xb >;
                  write-instruction = < 0x2 >;
                  instruction-length = "INSTR_1_BYTE";
                  address-length = "ADDR_4_BYTE";
                  rx-dummy = < 0x8 >;
                  tx-dummy = < 0x0 >;
                  xip-config = < 0x0 0x0 0x0 0x0 >;
                  ce-break-config = < 0x0 0x0 >;
         };

};

User should specify target operating parameters in the DTS such as mspi-max-frequency, mspi-io-mode and mspi-data-rate even though they may subject to change during runtime. It should represent the typical configuration of the device during normal operations.

Multi Peripheral

With mspi_dev_id defined as collection of the device index and CE GPIO from device tree, the API supports multiple devices on the same controller instance. The controller driver implementation may or may not support device switching, which can be performed either by software or by hardware. If the switching is handled by software, it should be performed in mspi_dev_config() call.

The device driver should record the current operating conditions of the device to support software controlled device switching by saving and updating mspi_dev_cfg and other relevant mspi struct or private data structures. In particular, mspi_dev_id which contains the identity of the device needs to be used for every API call.

Configuration Options

Related configuration options:

API Reference

group mspi_interface

MSPI Driver APIs.

Typedefs

typedef int (*mspi_api_config)(const struct mspi_dt_spec *spec)

MSPI driver API definition and system call entry points.

typedef int (*mspi_api_dev_config)(const struct device *controller, const struct mspi_dev_id *dev_id, const enum mspi_dev_cfg_mask param_mask, const struct mspi_dev_cfg *cfg)
typedef int (*mspi_api_get_channel_status)(const struct device *controller, uint8_t ch)
typedef int (*mspi_api_transceive)(const struct device *controller, const struct mspi_dev_id *dev_id, const struct mspi_xfer *req)
typedef int (*mspi_api_register_callback)(const struct device *controller, const struct mspi_dev_id *dev_id, const enum mspi_bus_event evt_type, mspi_callback_handler_t cb, struct mspi_callback_context *ctx)
typedef int (*mspi_api_xip_config)(const struct device *controller, const struct mspi_dev_id *dev_id, const struct mspi_xip_cfg *xip_cfg)
typedef int (*mspi_api_scramble_config)(const struct device *controller, const struct mspi_dev_id *dev_id, const struct mspi_scramble_cfg *scramble_cfg)
typedef int (*mspi_api_timing_config)(const struct device *controller, const struct mspi_dev_id *dev_id, const uint32_t param_mask, void *timing_cfg)

Enums

enum mspi_op_mode

MSPI operational mode.

Values:

enumerator MSPI_OP_MODE_CONTROLLER = 0
enumerator MSPI_OP_MODE_PERIPHERAL = 1
enum mspi_duplex

MSPI duplex mode.

Values:

enumerator MSPI_HALF_DUPLEX = 0
enumerator MSPI_FULL_DUPLEX = 1
enum mspi_io_mode

MSPI I/O mode capabilities Postfix like 1_4_4 stands for the number of lines used for command, address and data phases.

Mode with no postfix has the same number of lines for all phases.

Values:

enumerator MSPI_IO_MODE_SINGLE = 0
enumerator MSPI_IO_MODE_DUAL = 1
enumerator MSPI_IO_MODE_DUAL_1_1_2 = 2
enumerator MSPI_IO_MODE_DUAL_1_2_2 = 3
enumerator MSPI_IO_MODE_QUAD = 4
enumerator MSPI_IO_MODE_QUAD_1_1_4 = 5
enumerator MSPI_IO_MODE_QUAD_1_4_4 = 6
enumerator MSPI_IO_MODE_OCTAL = 7
enumerator MSPI_IO_MODE_OCTAL_1_1_8 = 8
enumerator MSPI_IO_MODE_OCTAL_1_8_8 = 9
enumerator MSPI_IO_MODE_HEX = 10
enumerator MSPI_IO_MODE_HEX_8_8_16 = 11
enumerator MSPI_IO_MODE_HEX_8_16_16 = 12
enumerator MSPI_IO_MODE_MAX
enum mspi_data_rate

MSPI data rate capabilities SINGLE stands for single data rate for all phases.

DUAL stands for dual data rate for all phases. S_S_D stands for single data rate for command and address phases but dual data rate for data phase. S_D_D stands for single data rate for command phase but dual data rate for address and data phases.

Values:

enumerator MSPI_DATA_RATE_SINGLE = 0
enumerator MSPI_DATA_RATE_S_S_D = 1
enumerator MSPI_DATA_RATE_S_D_D = 2
enumerator MSPI_DATA_RATE_DUAL = 3
enumerator MSPI_DATA_RATE_MAX
enum mspi_cpp_mode

MSPI Polarity & Phase Modes.

Values:

enumerator MSPI_CPP_MODE_0 = 0
enumerator MSPI_CPP_MODE_1 = 1
enumerator MSPI_CPP_MODE_2 = 2
enumerator MSPI_CPP_MODE_3 = 3
enum mspi_endian

MSPI Endian.

Values:

enumerator MSPI_XFER_LITTLE_ENDIAN = 0
enumerator MSPI_XFER_BIG_ENDIAN = 1
enum mspi_ce_polarity

MSPI chip enable polarity.

Values:

enumerator MSPI_CE_ACTIVE_LOW = 0
enumerator MSPI_CE_ACTIVE_HIGH = 1
enum mspi_bus_event

MSPI bus event.

This is a preliminary list of events. I encourage the community to fill it up.

Values:

enumerator MSPI_BUS_RESET = 0
enumerator MSPI_BUS_ERROR = 1
enumerator MSPI_BUS_XFER_COMPLETE = 2
enumerator MSPI_BUS_EVENT_MAX
enum mspi_bus_event_cb_mask

MSPI bus event callback mask This is a preliminary list same as mspi_bus_event.

I encourage the community to fill it up.

Values:

enumerator MSPI_BUS_NO_CB = 0
enumerator MSPI_BUS_RESET_CB = BIT(0)
enumerator MSPI_BUS_ERROR_CB = BIT(1)
enumerator MSPI_BUS_XFER_COMPLETE_CB = BIT(2)
enum mspi_xfer_mode

MSPI transfer modes.

Values:

enumerator MSPI_PIO
enumerator MSPI_DMA
enum mspi_xfer_direction

MSPI transfer directions.

Values:

enumerator MSPI_RX
enumerator MSPI_TX
enum mspi_dev_cfg_mask

MSPI controller device specific configuration mask.

Values:

enumerator MSPI_DEVICE_CONFIG_NONE = 0
enumerator MSPI_DEVICE_CONFIG_CE_NUM = BIT(0)
enumerator MSPI_DEVICE_CONFIG_FREQUENCY = BIT(1)
enumerator MSPI_DEVICE_CONFIG_IO_MODE = BIT(2)
enumerator MSPI_DEVICE_CONFIG_DATA_RATE = BIT(3)
enumerator MSPI_DEVICE_CONFIG_CPP = BIT(4)
enumerator MSPI_DEVICE_CONFIG_ENDIAN = BIT(5)
enumerator MSPI_DEVICE_CONFIG_CE_POL = BIT(6)
enumerator MSPI_DEVICE_CONFIG_DQS = BIT(7)
enumerator MSPI_DEVICE_CONFIG_RX_DUMMY = BIT(8)
enumerator MSPI_DEVICE_CONFIG_TX_DUMMY = BIT(9)
enumerator MSPI_DEVICE_CONFIG_READ_CMD = BIT(10)
enumerator MSPI_DEVICE_CONFIG_WRITE_CMD = BIT(11)
enumerator MSPI_DEVICE_CONFIG_CMD_LEN = BIT(12)
enumerator MSPI_DEVICE_CONFIG_ADDR_LEN = BIT(13)
enumerator MSPI_DEVICE_CONFIG_MEM_BOUND = BIT(14)
enumerator MSPI_DEVICE_CONFIG_BREAK_TIME = BIT(15)
enumerator MSPI_DEVICE_CONFIG_ALL = BIT_MASK(16)
enum mspi_xip_permit

MSPI XIP access permissions.

Values:

enumerator MSPI_XIP_READ_WRITE = 0
enumerator MSPI_XIP_READ_ONLY = 1
struct mspi_driver_api
#include <mspi.h>