/* * Copyright (c) 2024, Ambiq Micro Inc. * * 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 LOG_MODULE_REGISTER(mspi_emul_controller); #include #include #include #include #include #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)