Skip to content

[RFC] Introduction to MSPI (Multi-bit SPI) driver API  #70723

@swift-tk

Description

@swift-tk

Introduction

Rather than attempting to overhaul the existing SPI interface, adding a new MSPI API may be a better option for both new and existing users.

Problem description

The existing Zephyr SPI has many limitations including but not limited to

  • no support for SDR mixed mode configuration such as 1-4-4, 1-1-4, 1-8-8, 1-1-8. (cmd-addr-data line numbers)
  • no support for HEX line mode.
  • no support for DDR mode configuration.
  • no support for dynamically managing dummy cycles before data phase.
  • no support for dynamically managing cmd and address phase length.
  • no support for configuring the controller hardware other than before the transfer happens. (Such as timing configurations)
  • supports only transfer related callback.
  • no support for DQS mode configuration.

Proposed change

The MSPI interface should contain a controller driver that is SoC platform specific and implements the following APIs.
The device driver should then reference these APIs so that a unified device driver can be achieved.

__syscall int mspi_config(const struct mspi_dt_spec *spec);
 
__syscall int mspi_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);
 
__syscall int mspi_get_channel_status(const struct device *controller,
                                         uint8_t ch);
 
__syscall int mspi_transceive(const struct device *controller,
                                const struct mspi_dev_id *dev_id,
                                const struct mspi_xfer_packet *req);
 
__syscall int mspi_transceive_async(const struct device *controller,
                                      const struct mspi_dev_id *dev_id,
                                      const struct mspi_xfer_packet *req);
 
__syscall int mspi_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);
 
__syscall int mspi_xip_config(const struct device *controller,
                                const struct mspi_dev_id *dev_id,
                                const struct mspi_xip_cfg *cfg);
 
__syscall int mspi_scramble_config(const struct device *controller,
                                      const struct mspi_dev_id *dev_id,
                                      const struct mspi_scramble_cfg *cfg);
 
__syscall int mspi_timing_config(const struct device *controller,
                                   const struct mspi_dev_id *dev_id,
                                   const uint32_t param_mask,
                                   void *cfg);

Note: mspi_register_callback to removed from the list of syscalls.

Detailed RFC

Methodology

To better serve the modern-day memory devices, I have divided configurations into three categories:

  • Controller hardware configurations: master/slave, duplexity. These common configurations are defined in struct mspi_cfg which is part of struct mspi_dt_spec. They are to be used for instance initialization or run-time re-initialization of the controller hardware.
    The common configurations are also defined in mspi-controller.yaml as a standard and should be referenced by all MSPI controller drivers’ bindings. For example, the ambiq,mspi-controller.yaml includes mspi-controller.yaml and has additional properties exclusive to Ambiq MSPI hardware/hal.
struct mspi_dt_spec {
    /** @brief MSPI bus */
    const struct device     *bus;
    /** @brief MSPI hardware specific configuration */
    struct mspi_cfg         config;
};
 
struct mspi_cfg {
    /** @brief mspi channel number */
    uint8_t                 ui8MSPIChannel;
    /** @brief Configure operaton mode */
    enum mspi_op_mode       eOPMode;
    /** @brief Configure duplex mode */
    enum mspi_duplex        eDuplex;
    /** @brief DQS support flag */
    bool                    bDQS;
    /** @brief GPIO chip-select line (optional) */
    struct gpio_dt_spec     *pCE;
    /** @brief Slave number from 0 to host controller slave limit. */
    uint32_t                ui32SlaveNum;
    /** @brief Maximum supported frequency in MHz */
    uint32_t                ui32MaxFreq;
    /** @brief Whether to re-initialize controller */
    bool                    bReinit;
};
  • Device specific configurations: These common settings are to be derived from device datasheet and should not change from one controller to another. They are defined in mspi_dev_cfg which is typically used in device driver initialization and run-time re-configuration.
    The common settings are also defined in mspi-device.yaml as a standard and should be referenced by all MSPI device drivers’ bindings. For example, the ambiq,mspi-device.yaml includes mspi-device.yaml and has additional timing properties only for Ambiq MSPI.
enum mspi_io_mode {
    MSPI_IO_MODE_SINGLE         = 0,
    MSPI_IO_MODE_DUAL           = 1,
    MSPI_IO_MODE_DUAL_1_1_2     = 2,
    MSPI_IO_MODE_DUAL_1_2_2     = 3,
    MSPI_IO_MODE_QUAD           = 4,
    MSPI_IO_MODE_QUAD_1_1_4     = 5,
    MSPI_IO_MODE_QUAD_1_4_4     = 6,
    MSPI_IO_MODE_OCTAL          = 7,
    MSPI_IO_MODE_OCTAL_1_1_8    = 8,
    MSPI_IO_MODE_OCTAL_1_8_8    = 9,
    MSPI_IO_MODE_HEX            = 10,
};
 
struct mspi_dev_cfg {
    /** @brief Configure CE0 or CE1 */
    uint32_t                ui32CENum;
    /** @brief Configure frequency */
    uint32_t                ui32Freq;
    /** @brief Configure I/O mode */
    enum mspi_io_mode       eIOMode;
    /** @brief Configure data rate SDR/DDR */
    enum mspi_data_rate     eDataRate;
    /** @brief Configure clock polarity and phase*/
    enum mspi_cpp_mode      eCPP;
    /** @brief Configure transfer endian */
    enum mspi_endian        eEndian;
    /** @brief Configure chip enable polarity */
    enum mspi_ce_polarity   eCEPolarity;
    /** @brief Configure DQS mode */
    bool                    bDQSEnable;
    /** @brief Configure number of clock cycles between
     * addr and data in RX direction
     */
    uint32_t                ui32RXDummy;
    /** @brief Configure number of clock cycles between
     * addr and data in TX direction
     */
    uint32_t                ui32TXDummy;
    /** @brief Configure read instruction */
    uint32_t                ui32ReadInstr;
    /** @brief Configure write instruction */
    uint32_t                ui32WriteInstr;
    /** @brief Configure instruction length */
    uint16_t                ui16InstrLength;
    /** @brief Configure address length */
    uint16_t                ui16AddrLength;
    /** @brief Configure memory boundary */
    uint32_t                ui32MemBoundary;
    /** @brief Configure break time */
    uint32_t                ui32BreakTimeLimit;
};
  • additional hardware features: XIP and scrambling. These may be special features that are not shared among device or controller hardware. In that case, they can be left aside.
struct mspi_xip_cfg {
    /** @brief XIP enable */
    bool                    bEnable;
    /** @brief XIP region start address =
     * hardware default + address offset
    */
    uint32_t                ui32AddrOffset;
    /** @brief XIP region size */
    uint32_t                ui32Size;
    /** @brief XIP access permission */
    enum mspi_xip_permit    ePermission;
};
/**
 * @brief MSPI controller scramble configuration
 */
struct mspi_scramble_cfg {
    /** @brief scramble enable */
    bool                    bEnable;
    /** @brief scramble region start address =
     * hardware default + address offset
    */
    uint32_t                ui32AddrOffset;
    /** @brief scramble region size */
    uint32_t                ui32Size;
};

API Detail

Now let's dive deeper and checkout what each API should do.

mspi_config
 
 * @param controller Pointer to the device structure for the driver instance.
 * @param cfg The controller configuration for MSPI.

This routine provides a generic interface to override MSPI controller capabilities. In the controller driver, one may implement this API to initialize or re-initialize their controller hardware. Additional SoC platform specific settings that are not in struct mspi_cfg may be added to one's own binding(xxx,mspi-controller.yaml) so that one may derive the settings from DTS and configure it in this API. In general, these settings should not change during run-time.

mspi_dev_config
 
 * @param controller Pointer to the device structure for the driver instance.
 * @param dev_id Pointer to the device ID structure from a device.
 * @param param_mask Macro definition of what to be configured in cfg.
 * @param cfg The device runtime configuration for the MSPI controller.

This routine provides a generic interface to override MSPI controller device specific settings. With struct mspi_dev_id defined as the device index and CE GPIO from device tree, the API supports multiple devices on the same controller instance. It is up to the controller driver implementation whether to support device switching either by software or by hardware. The implementation may also support individual parameter configurations specified by enum mspi_dev_cfg_mask.
The settings within struct mspi_dev_cfg don't typically change once the mode of operation is determined after the device initialization.

An example of the DTS.

&mspi1 {
    compatible = "ambiq,mspi-controller";
    pinctrl-0 = <&mspi1_default>;
    pinctrl-1 = <&mspi1_sleep>;
    pinctrl-2 = <&mspi1_psram>;
    pinctrl-3 = <&mspi1_flash>;
    pinctrl-names = "default","sleep","psram","flash";
    status = "okay";
 
    ce-gpios = <&gpio64_95 5 GPIO_ACTIVE_LOW>,
               <&gpio32_63 18 GPIO_ACTIVE_LOW>;
 
    cmdq-buffer-location = ".mspi_buff";
    cmdq-buffer-size = <256>;
 
    aps6404l: aps6404l@0 {
        compatible = "mspi-aps6404l";
        size = <DT_SIZE_M(64)>;
        reg = <0>;
        status = "okay";
        mspi-max-frequency = <48000000>;
        mspi-io-mode = "MSPI_IO_MODE_QUAD";
        mspi-data-rate = "MSPI_SINGLE_DATA_RATE";
        hardware-ce-num = <0>;
        read-instruction = <0xEB>;
        write-instruction = <0x38>;
        instruction-length = "INSTR_1_BYTE";
        address-length = "ADDR_3_BYTE";
        rx-dummy = <6>;
        tx-dummy = <0>;
        xip-config = <1 0 0 0>;
        ce-break-config = <0x6 30>;
        ambiq,timing-config-mask = <3>;
        ambiq,timing-config = <0 6 0 0 0 0 0 0>;
    };
    aps6408l: aps6408l@1 { /** dummy device */
        compatible = "mspi-aps6404l";
        size = <DT_SIZE_M(64)>;
        reg = <1>;
        status = "disabled";
        mspi-max-frequency = <48000000>;
        mspi-io-mode = "MSPI_IO_MODE_OCTAL";
        mspi-data-rate = "MSPI_SINGLE_DATA_RATE";
        hardware-ce-num = <0>;
        read-instruction = <0xEB>;
        write-instruction = <0x38>;
        instruction-length = "INSTR_1_BYTE";
        address-length = "ADDR_3_BYTE";
        rx-dummy = <6>;
        tx-dummy = <0>;
        xip-config = <1 0 0 0>;
        ce-break-config = <0x6 30>;
        ambiq,timing-config-mask = <3>;
        ambiq,timing-config = <0 6 0 0 0 0 0 0>;
    };
};
mspi_get_channel_status
 
 * @param controller Pointer to the device structure for the driver instance.
 * @param ch the MSPI channel for which status is to be retrieved.

This routine provides a generic interface to check whether the hardware is busy. This is useful in the multiple slave devices scheme.

mspi_register_callback
 
 * @param controller Pointer to the device structure for the driver instance.
 * @param dev_id Pointer to the device ID structure from a device.
 * @param evt_type The event type associated the callback.
 * @param cb Pointer to the user implemented callback function.
 * @param ctx Pointer to the user callback context.

This routine provides a generic interface to register different types of bus events.
The dev_id is provided so that the controller can identify its device and determine whether the access is allowed in a multiple device scheme.
The enum mspi_bus_event is a preliminary list of bus events. There are XIP events that can be added. I encourage the community to come up with more events that they would use.

mspi_transceive/mspi_transceive_async
 
 * @param controller Pointer to the device structure for the driver instance.
 * @param dev_id Pointer to the device ID structure from a device.
 * @param req Content of the request and request specific settings.

This routine provides a generic interface to transfer a request synchronously/asynchronously.
The dev_id is provided so that the controller can identify its device and determine whether the access is allowed in a multiple device scheme.
The req is of type mspi_xfer_packet which allows for dynamically changing the transfer related settings once the mode of operation is determined and configured by mspi_dev_config.
The API supports bulk transfers with different starting addresses and sizes with struct mspi_buf. 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 enum 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 the full advantage of it in terms of performance if scatter IO and callback management are supported.

struct mspi_buf {
    /** @brief  Device Instruction           */
    uint16_t                    ui16DeviceInstr;
    /** @brief  Device Address               */
    uint32_t                    ui32DeviceAddr;
    /** @brief  Number of bytes to transfer  */
    uint32_t                    ui32NumBytes;
    /** @brief  Buffer                       */
    uint32_t                    *pui32Buffer;
    /** @brief  Bus event callback masks     */
    enum mspi_bus_event_cb_mask eCBMask;
};
 
struct mspi_xfer_packet {
    /** @brief  Transfer Mode                */
    enum eTransMode             eMode;
    /** @brief  Direction (Transmit/Receive) */
    enum eTransDirection        eDirection;
    /** @brief  Configure TX dummy cycles    */
    uint32_t                    ui32TXDummy;
    /** @brief  Configure RX dummy cycles    */
    uint32_t                    ui32RXDummy;
    /** @brief Configure instruction length  */
    uint16_t                    ui16InstrLength;
    /** @brief Configure address length      */
    uint16_t                    ui16AddrLength;
    /** @brief  Hold CE active after xfer    */
    bool                        bHoldCE;
    /** @brief  Software CE control          */
    struct mspi_ce_control      sCE;
    /** @brief  Priority 0 = Low (best effort)
     *                   1 = High (service immediately)
    */
    uint8_t                     ui8Priority;
    /** @brief  Transfer buffers             */
    struct mspi_buf             *pPayload;
    /** @brief  Number of transfer buffers   */
    uint32_t                    ui32NumPayload;
};
mspi_xip_config/mspi_scramble_config
 
 * @param controller Pointer to the device structure for the driver instance.
 * @param dev_id Pointer to the device ID structure from a device.
 * @param cfg The controller configuration for MSPI.

This routine provides a generic interface to configure the XIP and scrambling feature. Typically, the cfg parameter includes an enable and the range of address to take effect. I also wouldn't expect these settings to change often.

mspi_timing_config
 
 * @param controller Pointer to the device structure for the driver instance.
 * @param dev_id Pointer to the device ID structure from a device.
 * @param param_mask The macro definition of what should be configured in cfg.
 * @param cfg The controller timing configuration for MSPI.

This routine provides a generic interface to configure timing parameters that are SoC platform specific.
If it is used, there should be one's own definition for param_mask and cfg type in one's own *.h file.

Dependencies

To compile, one needs to checkout “apollo3p-dev-mspi” at https://github.com/AmbiqMicro/ambiqhal_ambiq.git and "RFC-MSPI" at https://github.com/AmbiqMicro/ambiqzephyr.git

The branch is based of a PR that has yet to be merged to Zephyr main. #67815.
Please look at these commits for the example implementations.
image

The API prototype is at https://github.com/AmbiqMicro/ambiqzephyr/blob/RFC-MSPI/include/zephyr/drivers/mspi.h

Example code

Example implementation of the MSPI API can be found in this path zephyr\drivers\mspi\mspi_ambiq_ap3.c
Example usage of the MSPI API can be found in the following files.

zephyr\drivers\memc\memc_mspi_aps6404l.c PSRAM APS6404L device driver
zephyr\drivers\flash\flash_mspi_atxp032.c NOR FLASH ATXP032 device driver

zephyr\samples\drivers\memc\src\main.c demo the usage of mspi_transceive_async

Metadata

Metadata

Assignees

Labels

Architecture ReviewDiscussion in the Architecture WG requiredRFCRequest For Comments: want input from the communityarea: SPISPI bus

Type

Projects

Status

Done

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions