|  | /* | 
|  | * Copyright (c) 2024, Ambiq Micro Inc. <www.ambiq.com> | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | * | 
|  | * This driver creates fake MSPI buses which can contain emulated devices, | 
|  | * implemented by separate emulation drivers. | 
|  | * The API between this driver and its emulators is defined by | 
|  | * struct mspi_emul_driver_api. | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT zephyr_mspi_emul_controller | 
|  |  | 
|  | #define LOG_LEVEL CONFIG_MSPI_LOG_LEVEL | 
|  | #include <zephyr/logging/log.h> | 
|  | LOG_MODULE_REGISTER(mspi_emul_controller); | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/device.h> | 
|  | #include <zephyr/drivers/emul.h> | 
|  | #include <zephyr/drivers/mspi.h> | 
|  | #include <zephyr/drivers/mspi_emul.h> | 
|  |  | 
|  | #define MSPI_MAX_FREQ        250000000 | 
|  | #define MSPI_MAX_DEVICE      2 | 
|  | #define MSPI_TIMEOUT_US      1000000 | 
|  | #define EMUL_MSPI_INST_ID    0 | 
|  |  | 
|  | struct mspi_emul_context { | 
|  | /* the request entity currently owns the lock */ | 
|  | const struct mspi_dev_id      *owner; | 
|  | /* the current transfer context */ | 
|  | struct mspi_xfer              xfer; | 
|  | /* the transfer controls */ | 
|  | bool                          asynchronous; | 
|  | int                           packets_done; | 
|  | /* the transfer callback and callback context */ | 
|  | mspi_callback_handler_t       callback; | 
|  | struct mspi_callback_context  *callback_ctx; | 
|  | /** the transfer lock */ | 
|  | struct k_sem                  lock; | 
|  | }; | 
|  |  | 
|  | struct mspi_emul_data { | 
|  | /* List of struct mspi_emul associated with the device */ | 
|  | sys_slist_t                   emuls; | 
|  | /* common mspi hardware configurations */ | 
|  | struct mspi_cfg               mspicfg; | 
|  | /* device id of the current device occupied the bus */ | 
|  | const struct mspi_dev_id      *dev_id; | 
|  | /* controller access mutex */ | 
|  | struct k_mutex                lock; | 
|  | /* device specific hardware settings */ | 
|  | struct mspi_dev_cfg           dev_cfg; | 
|  | /* XIP configurations */ | 
|  | struct mspi_xip_cfg           xip_cfg; | 
|  | /* scrambling configurations */ | 
|  | struct mspi_scramble_cfg      scramble_cfg; | 
|  | /* Timing configurations */ | 
|  | struct mspi_timing_cfg        timing_cfg; | 
|  | /* local storage of mspi callback hanlder */ | 
|  | mspi_callback_handler_t       cbs[MSPI_BUS_EVENT_MAX]; | 
|  | /* local storage of mspi callback context */ | 
|  | struct mspi_callback_context  *cb_ctxs[MSPI_BUS_EVENT_MAX]; | 
|  | /* local mspi context */ | 
|  | struct mspi_emul_context      ctx; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Verify if the device with dev_id is on this MSPI bus. | 
|  | * | 
|  | * @param controller Pointer to the device structure for the driver instance. | 
|  | * @param dev_id Pointer to the device ID structure from a device. | 
|  | * @return 0 The device is on this MSPI bus. | 
|  | * @return -ENODEV The device is not on this MSPI bus. | 
|  | */ | 
|  | static inline int mspi_verify_device(const struct device *controller, | 
|  | const struct mspi_dev_id *dev_id) | 
|  | { | 
|  | const struct mspi_emul_data *data = controller->data; | 
|  | int device_index = data->mspicfg.num_periph; | 
|  | int ret = 0; | 
|  |  | 
|  | if (data->mspicfg.num_ce_gpios != 0) { | 
|  | for (int i = 0; i < data->mspicfg.num_periph; i++) { | 
|  | if (dev_id->ce.port == data->mspicfg.ce_group[i].port && | 
|  | dev_id->ce.pin == data->mspicfg.ce_group[i].pin && | 
|  | dev_id->ce.dt_flags == data->mspicfg.ce_group[i].dt_flags) { | 
|  | device_index = i; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (device_index >= data->mspicfg.num_periph || | 
|  | device_index != dev_id->dev_idx) { | 
|  | LOG_ERR("%u, invalid device ID.", __LINE__); | 
|  | return -ENODEV; | 
|  | } | 
|  | } else { | 
|  | if (dev_id->dev_idx >= data->mspicfg.num_periph) { | 
|  | LOG_ERR("%u, invalid device ID.", __LINE__); | 
|  | return -ENODEV; | 
|  | } | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Check if the MSPI bus is busy. | 
|  | * | 
|  | * @param controller MSPI emulation controller device. | 
|  | * @return true The MSPI bus is busy. | 
|  | * @return false The MSPI bus is idle. | 
|  | */ | 
|  | static inline bool mspi_is_inp(const struct device *controller) | 
|  | { | 
|  | struct mspi_emul_data *data = controller->data; | 
|  |  | 
|  | return (k_sem_count_get(&data->ctx.lock) == 0); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Lock MSPI context. | 
|  | * | 
|  | * @param ctx Pointer to the MSPI context. | 
|  | * @param req Pointer to the request entity represented by mspi_dev_id. | 
|  | * @param xfer Pointer to the MSPI transfer started by req. | 
|  | * @param callback MSPI call back function pointer. | 
|  | * @param callback_ctx Pointer to the mspi callback context. | 
|  | * @return 0 if allowed for hardware configuration. | 
|  | * @return 1 if not allowed for hardware configuration. | 
|  | */ | 
|  | static inline int mspi_context_lock(struct mspi_emul_context *ctx, | 
|  | const struct mspi_dev_id *req, | 
|  | const struct mspi_xfer *xfer, | 
|  | mspi_callback_handler_t callback, | 
|  | struct mspi_callback_context *callback_ctx) | 
|  | { | 
|  | int ret = 0; | 
|  |  | 
|  | if (k_sem_take(&ctx->lock, K_MSEC(xfer->timeout))) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | if (ctx->callback) { | 
|  |  | 
|  | if ((xfer->tx_dummy == ctx->xfer.tx_dummy) && | 
|  | (xfer->rx_dummy == ctx->xfer.rx_dummy) && | 
|  | (xfer->cmd_length == ctx->xfer.cmd_length) && | 
|  | (xfer->addr_length == ctx->xfer.addr_length)) { | 
|  | ret = 1; | 
|  | } else { | 
|  | ret = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | ctx->owner = req; | 
|  | ctx->xfer = *xfer; | 
|  | ctx->packets_done = 0; | 
|  | ctx->asynchronous = ctx->xfer.async; | 
|  | ctx->callback = callback; | 
|  | ctx->callback_ctx = callback_ctx; | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * release MSPI context. | 
|  | * | 
|  | * @param ctx Pointer to the MSPI context. | 
|  | */ | 
|  | static inline void mspi_context_release(struct mspi_emul_context *ctx) | 
|  | { | 
|  | ctx->owner = NULL; | 
|  | k_sem_give(&ctx->lock); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Configure hardware before a transfer. | 
|  | * | 
|  | * @param controller Pointer to the MSPI controller instance. | 
|  | * @param xfer Pointer to the MSPI transfer started by the request entity. | 
|  | * @return 0 if successful. | 
|  | */ | 
|  | static int mspi_xfer_config(const struct device *controller, | 
|  | const struct mspi_xfer *xfer) | 
|  | { | 
|  | struct mspi_emul_data *data = controller->data; | 
|  |  | 
|  | data->dev_cfg.cmd_length      = xfer->cmd_length; | 
|  | data->dev_cfg.addr_length     = xfer->addr_length; | 
|  | data->dev_cfg.tx_dummy        = xfer->tx_dummy; | 
|  | data->dev_cfg.rx_dummy        = xfer->rx_dummy; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Check and save dev_cfg to controller data->dev_cfg. | 
|  | * | 
|  | * @param controller Pointer to the device structure for the driver instance. | 
|  | * @param param_mask Macro definition of what to be configured in cfg. | 
|  | * @param dev_cfg The device runtime configuration for the MSPI controller. | 
|  | * @return 0 MSPI device configuration successful. | 
|  | * @return -Error MSPI device configuration fail. | 
|  | */ | 
|  | static inline int mspi_dev_cfg_check_save(const struct device *controller, | 
|  | const enum mspi_dev_cfg_mask param_mask, | 
|  | const struct mspi_dev_cfg *dev_cfg) | 
|  | { | 
|  | struct mspi_emul_data *data = controller->data; | 
|  |  | 
|  | if (param_mask & MSPI_DEVICE_CONFIG_CE_NUM) { | 
|  | data->dev_cfg.ce_num = dev_cfg->ce_num; | 
|  | } | 
|  |  | 
|  | if (param_mask & MSPI_DEVICE_CONFIG_FREQUENCY) { | 
|  | if (dev_cfg->freq > MSPI_MAX_FREQ) { | 
|  | LOG_ERR("%u, freq is too large.", __LINE__); | 
|  | return -ENOTSUP; | 
|  | } | 
|  | data->dev_cfg.freq = dev_cfg->freq; | 
|  | } | 
|  |  | 
|  | if (param_mask & MSPI_DEVICE_CONFIG_IO_MODE) { | 
|  | if (dev_cfg->io_mode >= MSPI_IO_MODE_MAX) { | 
|  | LOG_ERR("%u, Invalid io_mode.", __LINE__); | 
|  | return -EINVAL; | 
|  | } | 
|  | data->dev_cfg.io_mode = dev_cfg->io_mode; | 
|  | } | 
|  |  | 
|  | if (param_mask & MSPI_DEVICE_CONFIG_DATA_RATE) { | 
|  | if (dev_cfg->data_rate >= MSPI_DATA_RATE_MAX) { | 
|  | LOG_ERR("%u, Invalid data_rate.", __LINE__); | 
|  | return -EINVAL; | 
|  | } | 
|  | data->dev_cfg.data_rate = dev_cfg->data_rate; | 
|  | } | 
|  |  | 
|  | if (param_mask & MSPI_DEVICE_CONFIG_CPP) { | 
|  | if (dev_cfg->cpp > MSPI_CPP_MODE_3) { | 
|  | LOG_ERR("%u, Invalid cpp.", __LINE__); | 
|  | return -EINVAL; | 
|  | } | 
|  | data->dev_cfg.cpp = dev_cfg->cpp; | 
|  | } | 
|  |  | 
|  | if (param_mask & MSPI_DEVICE_CONFIG_ENDIAN) { | 
|  | if (dev_cfg->endian > MSPI_XFER_BIG_ENDIAN) { | 
|  | LOG_ERR("%u, Invalid endian.", __LINE__); | 
|  | return -EINVAL; | 
|  | } | 
|  | data->dev_cfg.endian = dev_cfg->endian; | 
|  | } | 
|  |  | 
|  | if (param_mask & MSPI_DEVICE_CONFIG_CE_POL) { | 
|  | if (dev_cfg->ce_polarity > MSPI_CE_ACTIVE_HIGH) { | 
|  | LOG_ERR("%u, Invalid ce_polarity.", __LINE__); | 
|  | return -EINVAL; | 
|  | } | 
|  | data->dev_cfg.ce_polarity = dev_cfg->ce_polarity; | 
|  | } | 
|  |  | 
|  | if (param_mask & MSPI_DEVICE_CONFIG_DQS) { | 
|  | if (dev_cfg->dqs_enable && !data->mspicfg.dqs_support) { | 
|  | LOG_ERR("%u, DQS mode not supported.", __LINE__); | 
|  | return -ENOTSUP; | 
|  | } | 
|  | data->dev_cfg.dqs_enable = dev_cfg->dqs_enable; | 
|  | } | 
|  |  | 
|  | if (param_mask & MSPI_DEVICE_CONFIG_RX_DUMMY) { | 
|  | data->dev_cfg.rx_dummy = dev_cfg->rx_dummy; | 
|  | } | 
|  |  | 
|  | if (param_mask & MSPI_DEVICE_CONFIG_TX_DUMMY) { | 
|  | data->dev_cfg.tx_dummy = dev_cfg->tx_dummy; | 
|  | } | 
|  |  | 
|  | if (param_mask & MSPI_DEVICE_CONFIG_READ_CMD) { | 
|  | data->dev_cfg.read_cmd = dev_cfg->read_cmd; | 
|  | } | 
|  |  | 
|  | if (param_mask & MSPI_DEVICE_CONFIG_WRITE_CMD) { | 
|  | data->dev_cfg.write_cmd = dev_cfg->write_cmd; | 
|  | } | 
|  |  | 
|  | if (param_mask & MSPI_DEVICE_CONFIG_CMD_LEN) { | 
|  | data->dev_cfg.cmd_length = dev_cfg->cmd_length; | 
|  | } | 
|  |  | 
|  | if (param_mask & MSPI_DEVICE_CONFIG_ADDR_LEN) { | 
|  | data->dev_cfg.addr_length = dev_cfg->addr_length; | 
|  | } | 
|  |  | 
|  | if (param_mask & MSPI_DEVICE_CONFIG_MEM_BOUND) { | 
|  | data->dev_cfg.mem_boundary = dev_cfg->mem_boundary; | 
|  | } | 
|  |  | 
|  | if (param_mask & MSPI_DEVICE_CONFIG_BREAK_TIME) { | 
|  | data->dev_cfg.time_to_break = dev_cfg->time_to_break; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Check the transfer context from the request entity. | 
|  | * | 
|  | * @param xfer Pointer to the MSPI transfer started by the request entity. | 
|  | * @return 0 if successful. | 
|  | * @return -EINVAL invalid parameter detected. | 
|  | */ | 
|  | static inline int mspi_xfer_check(const struct mspi_xfer *xfer) | 
|  | { | 
|  | if (xfer->xfer_mode > MSPI_DMA) { | 
|  | LOG_ERR("%u, Invalid xfer xfer_mode.", __LINE__); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (!xfer->packets || !xfer->num_packet) { | 
|  | LOG_ERR("%u, Invalid xfer payload.", __LINE__); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | for (int i = 0; i < xfer->num_packet; ++i) { | 
|  |  | 
|  | if (!xfer->packets[i].data_buf || | 
|  | !xfer->packets[i].num_bytes) { | 
|  | LOG_ERR("%u, Invalid xfer payload num: %u.", __LINE__, i); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (xfer->packets[i].dir > MSPI_TX) { | 
|  | LOG_ERR("%u, Invalid xfer direction.", __LINE__); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (xfer->packets[i].cb_mask > MSPI_BUS_XFER_COMPLETE_CB) { | 
|  | LOG_ERR("%u, Invalid xfer cb_mask.", __LINE__); | 
|  | return -EINVAL; | 
|  | } | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * find_emul API implementation. | 
|  | * | 
|  | * @param controller Pointer to MSPI controller instance. | 
|  | * @param dev_idx The device index of a mspi_emul. | 
|  | * @return Pointer to a mspi_emul entity if successful. | 
|  | * @return NULL if mspi_emul entity not found. | 
|  | */ | 
|  | static struct mspi_emul *mspi_emul_find(const struct device *controller, | 
|  | uint16_t dev_idx) | 
|  | { | 
|  | struct mspi_emul_data *data = controller->data; | 
|  | sys_snode_t *node; | 
|  |  | 
|  | SYS_SLIST_FOR_EACH_NODE(&data->emuls, node) { | 
|  | struct mspi_emul *emul; | 
|  |  | 
|  | emul = CONTAINER_OF(node, struct mspi_emul, node); | 
|  | if (emul->dev_idx == dev_idx) { | 
|  | return emul; | 
|  | } | 
|  | } | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * trigger_event API implementation. | 
|  | * | 
|  | * @param controller Pointer to MSPI controller instance. | 
|  | * @param evt_type The bus event to trigger | 
|  | * @return 0 if successful. | 
|  | */ | 
|  | static int emul_mspi_trigger_event(const struct device *controller, | 
|  | enum mspi_bus_event evt_type) | 
|  | { | 
|  | struct mspi_emul_data *data = controller->data; | 
|  | struct mspi_emul_context *ctx = &data->ctx; | 
|  |  | 
|  | mspi_callback_handler_t cb; | 
|  | struct mspi_callback_context *cb_context; | 
|  |  | 
|  | if (evt_type == MSPI_BUS_XFER_COMPLETE) { | 
|  |  | 
|  | if (ctx->callback && ctx->callback_ctx) { | 
|  |  | 
|  | struct mspi_event *evt = &ctx->callback_ctx->mspi_evt; | 
|  | const struct mspi_xfer_packet *packet; | 
|  |  | 
|  | packet = &ctx->xfer.packets[ctx->packets_done]; | 
|  |  | 
|  | evt->evt_type = MSPI_BUS_XFER_COMPLETE; | 
|  | evt->evt_data.controller = controller; | 
|  | evt->evt_data.dev_id = ctx->owner; | 
|  | evt->evt_data.packet = packet; | 
|  | evt->evt_data.packet_idx = ctx->packets_done; | 
|  | ctx->packets_done++; | 
|  |  | 
|  | if (packet->cb_mask == MSPI_BUS_XFER_COMPLETE_CB) { | 
|  | cb = ctx->callback; | 
|  | cb_context = ctx->callback_ctx; | 
|  | cb(cb_context); | 
|  | } | 
|  |  | 
|  | } else { | 
|  | LOG_WRN("%u, MSPI_BUS_XFER_COMPLETE callback not registered.", __LINE__); | 
|  | } | 
|  |  | 
|  | } else { | 
|  |  | 
|  | cb = data->cbs[evt_type]; | 
|  | cb_context = data->cb_ctxs[evt_type]; | 
|  | if (cb) { | 
|  | cb(cb_context); | 
|  | } else { | 
|  | LOG_ERR("%u, mspi callback type %u not registered.", __LINE__, evt_type); | 
|  | return -EINVAL; | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * API implementation of mspi_config. | 
|  | * | 
|  | * @param spec Pointer to MSPI device tree spec. | 
|  | * @return 0 if successful. | 
|  | * @return -Error if fail. | 
|  | */ | 
|  | static int mspi_emul_config(const struct mspi_dt_spec *spec) | 
|  | { | 
|  | const struct mspi_cfg *config = &spec->config; | 
|  | struct mspi_emul_data *data = spec->bus->data; | 
|  |  | 
|  | int ret = 0; | 
|  |  | 
|  | if (config->op_mode > MSPI_OP_MODE_PERIPHERAL) { | 
|  | LOG_ERR("%u, Invalid MSPI OP mode.", __LINE__); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (config->max_freq > MSPI_MAX_FREQ) { | 
|  | LOG_ERR("%u, Invalid MSPI Frequency", __LINE__); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | if (config->duplex > MSPI_FULL_DUPLEX) { | 
|  | LOG_ERR("%u, Invalid MSPI duplexity.", __LINE__); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (config->num_periph > MSPI_MAX_DEVICE) { | 
|  | LOG_ERR("%u, Invalid MSPI peripheral number.", __LINE__); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | if (config->num_ce_gpios != 0 && | 
|  | config->num_ce_gpios != config->num_periph) { | 
|  | LOG_ERR("%u, Invalid number of ce_gpios.", __LINE__); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (config->re_init) { | 
|  | if (k_mutex_lock(&data->lock, K_MSEC(CONFIG_MSPI_COMPLETION_TIMEOUT_TOLERANCE))) { | 
|  | LOG_ERR("%u, Failed to access controller.", __LINE__); | 
|  | return -EBUSY; | 
|  | } | 
|  | while (mspi_is_inp(spec->bus)) { | 
|  | } | 
|  | } | 
|  |  | 
|  | /* emulate controller hardware initialization */ | 
|  | k_busy_wait(10); | 
|  |  | 
|  | if (!k_sem_count_get(&data->ctx.lock)) { | 
|  | data->ctx.owner = NULL; | 
|  | k_sem_give(&data->ctx.lock); | 
|  | } | 
|  |  | 
|  | if (config->re_init) { | 
|  | k_mutex_unlock(&data->lock); | 
|  | } | 
|  |  | 
|  | data->mspicfg = *config; | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * API implementation of 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 dev_cfg The device runtime configuration for the MSPI controller. | 
|  | * | 
|  | * @retval 0 if successful. | 
|  | * @retval -EINVAL invalid capabilities, failed to configure device. | 
|  | * @retval -ENOTSUP capability not supported by MSPI peripheral. | 
|  | */ | 
|  | static int mspi_emul_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 *dev_cfg) | 
|  | { | 
|  | struct mspi_emul_data *data = controller->data; | 
|  | int ret = 0; | 
|  |  | 
|  | if (data->dev_id != dev_id) { | 
|  | if (k_mutex_lock(&data->lock, K_MSEC(CONFIG_MSPI_COMPLETION_TIMEOUT_TOLERANCE))) { | 
|  | LOG_ERR("%u, Failed to access controller.", __LINE__); | 
|  | return -EBUSY; | 
|  | } | 
|  |  | 
|  | ret = mspi_verify_device(controller, dev_id); | 
|  | if (ret) { | 
|  | goto e_return; | 
|  | } | 
|  | } | 
|  |  | 
|  | while (mspi_is_inp(controller)) { | 
|  | } | 
|  |  | 
|  | if (param_mask == MSPI_DEVICE_CONFIG_NONE && | 
|  | !data->mspicfg.sw_multi_periph) { | 
|  | /* Do nothing except obtaining the controller lock */ | 
|  | } else if (param_mask < MSPI_DEVICE_CONFIG_ALL) { | 
|  | if (data->dev_id != dev_id) { | 
|  | /* MSPI_DEVICE_CONFIG_ALL should be used */ | 
|  | LOG_ERR("%u, config failed, must be the same device.", __LINE__); | 
|  | ret = -ENOTSUP; | 
|  | goto e_return; | 
|  | } | 
|  | ret = mspi_dev_cfg_check_save(controller, param_mask, dev_cfg); | 
|  | if (ret) { | 
|  | goto e_return; | 
|  | } | 
|  | } else if (param_mask == MSPI_DEVICE_CONFIG_ALL) { | 
|  | ret = mspi_dev_cfg_check_save(controller, param_mask, dev_cfg); | 
|  | if (ret) { | 
|  | goto e_return; | 
|  | } | 
|  | if (data->dev_id != dev_id) { | 
|  | /* Conduct device switching */ | 
|  | } | 
|  | } else { | 
|  | LOG_ERR("%u, Invalid param_mask.", __LINE__); | 
|  | ret = -EINVAL; | 
|  | goto e_return; | 
|  | } | 
|  |  | 
|  | data->dev_id = dev_id; | 
|  | return ret; | 
|  |  | 
|  | e_return: | 
|  | k_mutex_unlock(&data->lock); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * API implementation of mspi_xip_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 xip_cfg The controller XIP configuration for MSPI. | 
|  | * | 
|  | * @retval 0 if successful. | 
|  | * @retval -ESTALE device ID don't match, need to call mspi_dev_config first. | 
|  | */ | 
|  | static int mspi_emul_xip_config(const struct device *controller, | 
|  | const struct mspi_dev_id *dev_id, | 
|  | const struct mspi_xip_cfg *xip_cfg) | 
|  | { | 
|  | struct mspi_emul_data *data = controller->data; | 
|  | int ret = 0; | 
|  |  | 
|  | if (dev_id != data->dev_id) { | 
|  | LOG_ERR("%u, dev_id don't match.", __LINE__); | 
|  | return -ESTALE; | 
|  | } | 
|  |  | 
|  | data->xip_cfg = *xip_cfg; | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * API implementation of 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 scramble_cfg The controller scramble configuration for MSPI. | 
|  | * | 
|  | * @retval 0 if successful. | 
|  | * @retval -ESTALE device ID don't match, need to call mspi_dev_config first. | 
|  | */ | 
|  | static int mspi_emul_scramble_config(const struct device *controller, | 
|  | const struct mspi_dev_id *dev_id, | 
|  | const struct mspi_scramble_cfg *scramble_cfg) | 
|  | { | 
|  | struct mspi_emul_data *data = controller->data; | 
|  | int ret = 0; | 
|  |  | 
|  | while (mspi_is_inp(controller)) { | 
|  | } | 
|  |  | 
|  | if (dev_id != data->dev_id) { | 
|  | LOG_ERR("%u, dev_id don't match.", __LINE__); | 
|  | return -ESTALE; | 
|  | } | 
|  |  | 
|  | data->scramble_cfg = *scramble_cfg; | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * API implementation of 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 timing_cfg The controller timing configuration for MSPI. | 
|  | * | 
|  | * @retval 0 if successful. | 
|  | * @retval -ESTALE device ID don't match, need to call mspi_dev_config first. | 
|  | * @retval -ENOTSUP param_mask value is not supported. | 
|  | */ | 
|  | static int mspi_emul_timing_config(const struct device *controller, | 
|  | const struct mspi_dev_id *dev_id, | 
|  | const uint32_t param_mask, | 
|  | void *timing_cfg) | 
|  | { | 
|  | struct mspi_emul_data *data = controller->data; | 
|  | int ret = 0; | 
|  |  | 
|  | while (mspi_is_inp(controller)) { | 
|  | } | 
|  |  | 
|  | if (dev_id != data->dev_id) { | 
|  | LOG_ERR("%u, dev_id don't match.", __LINE__); | 
|  | return -ESTALE; | 
|  | } | 
|  |  | 
|  | if (param_mask == MSPI_TIMING_PARAM_DUMMY) { | 
|  | data->timing_cfg = *(struct mspi_timing_cfg *)timing_cfg; | 
|  | } else { | 
|  | LOG_ERR("%u, param_mask not supported.", __LINE__); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * API implementation of mspi_get_channel_status. | 
|  | * | 
|  | * @param controller Pointer to the device structure for the driver instance. | 
|  | * @param ch Not used. | 
|  | * | 
|  | * @retval 0 if successful. | 
|  | * @retval -EBUSY MSPI bus is busy | 
|  | */ | 
|  | static int mspi_emul_get_channel_status(const struct device *controller, uint8_t ch) | 
|  | { | 
|  | struct mspi_emul_data *data = controller->data; | 
|  |  | 
|  | ARG_UNUSED(ch); | 
|  |  | 
|  | if (mspi_is_inp(controller)) { | 
|  | return -EBUSY; | 
|  | } | 
|  |  | 
|  | k_mutex_unlock(&data->lock); | 
|  | data->dev_id = NULL; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * API implementation of 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 callback context. | 
|  | * | 
|  | * @retval 0 if successful. | 
|  | * @retval -ESTALE device ID don't match, need to call mspi_dev_config first. | 
|  | * @retval -ENOTSUP evt_type not supported. | 
|  | */ | 
|  | static int mspi_emul_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) | 
|  | { | 
|  | struct mspi_emul_data *data = controller->data; | 
|  |  | 
|  | while (mspi_is_inp(controller)) { | 
|  | } | 
|  |  | 
|  | if (dev_id != data->dev_id) { | 
|  | LOG_ERR("%u, dev_id don't match.", __LINE__); | 
|  | return -ESTALE; | 
|  | } | 
|  |  | 
|  | if (evt_type >= MSPI_BUS_EVENT_MAX) { | 
|  | LOG_ERR("%u, callback types not supported.", __LINE__); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | data->cbs[evt_type] = cb; | 
|  | data->cb_ctxs[evt_type] = ctx; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * API implementation of mspi_transceive. | 
|  | * | 
|  | * @param controller Pointer to the device structure for the driver instance. | 
|  | * @param dev_id Pointer to the device ID structure from a device. | 
|  | * @param xfer Pointer to the MSPI transfer started by dev_id. | 
|  | * | 
|  | * @retval 0 if successful. | 
|  | * @retval -ESTALE device ID don't match, need to call mspi_dev_config first. | 
|  | * @retval -Error transfer failed. | 
|  | */ | 
|  | static int mspi_emul_transceive(const struct device *controller, | 
|  | const struct mspi_dev_id *dev_id, | 
|  | const struct mspi_xfer *xfer) | 
|  | { | 
|  | struct mspi_emul_data *data = controller->data; | 
|  | struct mspi_emul_context *ctx = &data->ctx; | 
|  | struct mspi_emul *emul; | 
|  | mspi_callback_handler_t cb = NULL; | 
|  | struct mspi_callback_context *cb_ctx = NULL; | 
|  | int ret = 0; | 
|  | int cfg_flag = 0; | 
|  |  | 
|  | emul = mspi_emul_find(controller, dev_id->dev_idx); | 
|  | if (!emul) { | 
|  | LOG_ERR("%u, mspi_emul not found.", __LINE__); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | if (dev_id != data->dev_id) { | 
|  | LOG_ERR("%u, dev_id don't match.", __LINE__); | 
|  | return -ESTALE; | 
|  | } | 
|  |  | 
|  | ret = mspi_xfer_check(xfer); | 
|  | if (ret) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | __ASSERT_NO_MSG(emul->api); | 
|  | __ASSERT_NO_MSG(emul->api->transceive); | 
|  |  | 
|  | if (xfer->async) { | 
|  | cb = data->cbs[MSPI_BUS_XFER_COMPLETE]; | 
|  | cb_ctx = data->cb_ctxs[MSPI_BUS_XFER_COMPLETE]; | 
|  | } | 
|  |  | 
|  | cfg_flag = mspi_context_lock(ctx, dev_id, xfer, cb, cb_ctx); | 
|  |  | 
|  | if (cfg_flag) { | 
|  | if (cfg_flag == 1) { | 
|  | ret = mspi_xfer_config(controller, xfer); | 
|  | if (ret) { | 
|  | LOG_ERR("%u, xfer config fail.", __LINE__); | 
|  | goto trans_err; | 
|  | } | 
|  | } else { | 
|  | ret = cfg_flag; | 
|  | LOG_ERR("%u, xfer fail.", __LINE__); | 
|  | goto trans_err; | 
|  | } | 
|  | } | 
|  |  | 
|  | ret = emul->api->transceive(emul->target, | 
|  | ctx->xfer.packets, | 
|  | ctx->xfer.num_packet, | 
|  | ctx->asynchronous, MSPI_TIMEOUT_US); | 
|  |  | 
|  | trans_err: | 
|  | mspi_context_release(ctx); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Set up a new emulator and add its child to the list. | 
|  | * | 
|  | * @param dev MSPI emulation controller. | 
|  | * | 
|  | * @retval 0 if successful. | 
|  | */ | 
|  | static int mspi_emul_init(const struct device *dev) | 
|  | { | 
|  | struct mspi_emul_data *data = dev->data; | 
|  | const struct mspi_dt_spec spec = { | 
|  | .bus    = dev, | 
|  | .config = data->mspicfg, | 
|  | }; | 
|  | int ret = 0; | 
|  |  | 
|  | ret = mspi_emul_config(&spec); | 
|  | if (ret) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | sys_slist_init(&data->emuls); | 
|  |  | 
|  | return emul_init_for_bus(dev); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * add its child to the list. | 
|  | * | 
|  | * @param dev MSPI emulation controller. | 
|  | * @param emul MSPI emulation device. | 
|  | * | 
|  | * @retval 0 if successful. | 
|  | */ | 
|  | int mspi_emul_register(const struct device *dev, struct mspi_emul *emul) | 
|  | { | 
|  | struct mspi_emul_data *data = dev->data; | 
|  | const char *name = emul->target->dev->name; | 
|  |  | 
|  | sys_slist_append(&data->emuls, &emul->node); | 
|  |  | 
|  | LOG_INF("Register emulator '%s', id:%x\n", name, emul->dev_idx); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Device instantiation */ | 
|  | static struct emul_mspi_driver_api emul_mspi_driver_api = { | 
|  | .mspi_api = { | 
|  | .config                = mspi_emul_config, | 
|  | .dev_config            = mspi_emul_dev_config, | 
|  | .xip_config            = mspi_emul_xip_config, | 
|  | .scramble_config       = mspi_emul_scramble_config, | 
|  | .timing_config         = mspi_emul_timing_config, | 
|  | .get_channel_status    = mspi_emul_get_channel_status, | 
|  | .register_callback     = mspi_emul_register_callback, | 
|  | .transceive            = mspi_emul_transceive, | 
|  | }, | 
|  | .trigger_event                         = emul_mspi_trigger_event, | 
|  | .find_emul                             = mspi_emul_find, | 
|  | }; | 
|  |  | 
|  | #define MSPI_CONFIG(n)                                                                            \ | 
|  | {                                                                                         \ | 
|  | .channel_num           = EMUL_MSPI_INST_ID,                                       \ | 
|  | .op_mode               = DT_INST_ENUM_IDX_OR(n, op_mode, MSPI_OP_MODE_CONTROLLER),\ | 
|  | .duplex                = DT_INST_ENUM_IDX_OR(n, duplex, MSPI_HALF_DUPLEX),        \ | 
|  | .max_freq              = DT_INST_PROP(n, clock_frequency),                        \ | 
|  | .dqs_support           = DT_INST_PROP_OR(n, dqs_support, false),                  \ | 
|  | .sw_multi_periph       = DT_INST_PROP(n, software_multiperipheral),               \ | 
|  | } | 
|  |  | 
|  | #define EMUL_LINK_AND_COMMA(node_id)                                                              \ | 
|  | {                                                                                         \ | 
|  | .dev = DEVICE_DT_GET(node_id),                                                    \ | 
|  | }, | 
|  |  | 
|  | #define MSPI_EMUL_INIT(n)                                                                         \ | 
|  | static const struct emul_link_for_bus emuls_##n[] = {                                     \ | 
|  | DT_FOREACH_CHILD_STATUS_OKAY(DT_DRV_INST(n), EMUL_LINK_AND_COMMA)};               \ | 
|  | static struct emul_list_for_bus mspi_emul_cfg_##n = {                                     \ | 
|  | .children = emuls_##n,                                                            \ | 
|  | .num_children = ARRAY_SIZE(emuls_##n),                                            \ | 
|  | };                                                                                        \ | 
|  | static struct gpio_dt_spec ce_gpios##n[] = MSPI_CE_GPIOS_DT_SPEC_INST_GET(n);             \ | 
|  | static struct mspi_emul_data mspi_emul_data_##n = {                                       \ | 
|  | .mspicfg               = MSPI_CONFIG(n),                                          \ | 
|  | .mspicfg.ce_group      = (struct gpio_dt_spec *)ce_gpios##n,                      \ | 
|  | .mspicfg.num_ce_gpios  = ARRAY_SIZE(ce_gpios##n),                                 \ | 
|  | .mspicfg.num_periph    = DT_INST_CHILD_NUM(n),                                    \ | 
|  | .mspicfg.re_init       = false,                                                   \ | 
|  | .dev_id                = 0,                                                       \ | 
|  | .lock                  = Z_MUTEX_INITIALIZER(mspi_emul_data_##n.lock),            \ | 
|  | .dev_cfg               = {0},                                                     \ | 
|  | .xip_cfg               = {0},                                                     \ | 
|  | .scramble_cfg          = {0},                                                     \ | 
|  | .cbs                   = {0},                                                     \ | 
|  | .cb_ctxs               = {0},                                                     \ | 
|  | .ctx.lock              = Z_SEM_INITIALIZER(mspi_emul_data_##n.ctx.lock, 0, 1),    \ | 
|  | .ctx.callback          = 0,                                                       \ | 
|  | .ctx.callback_ctx      = 0,                                                       \ | 
|  | };                                                                                        \ | 
|  | DEVICE_DT_INST_DEFINE(n,                                                                  \ | 
|  | &mspi_emul_init,                                                    \ | 
|  | NULL,                                                               \ | 
|  | &mspi_emul_data_##n,                                                \ | 
|  | &mspi_emul_cfg_##n,                                                 \ | 
|  | POST_KERNEL,                                                        \ | 
|  | CONFIG_MSPI_INIT_PRIORITY,                                          \ | 
|  | &emul_mspi_driver_api); | 
|  |  | 
|  | DT_INST_FOREACH_STATUS_OKAY(MSPI_EMUL_INIT) |