From 3be6ab7d7cc5b396d2aa80bfbc76ec9d5ce9c6ca Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Wed, 26 Aug 2026 13:58:52 +0300 Subject: [PATCH 01/18] ipc4: fw_reg: add per-gateway host DMA byte counter window A host without a DMA position register (i.MX) needs to publish its byte counter so the driver can derive the ALSA PCM pointer. Add host_byte_cnt[] to struct ipc4_fw_registers, indexed by host gateway id, carved out of reserved space to preserve the following fields' offsets. Signed-off-by: Daniel Baluta --- src/include/ipc4/fw_reg.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/include/ipc4/fw_reg.h b/src/include/ipc4/fw_reg.h index 744c5e457686..52690591a56d 100644 --- a/src/include/ipc4/fw_reg.h +++ b/src/include/ipc4/fw_reg.h @@ -159,6 +159,9 @@ union ipc4_rom_info { /** \brief Number of GPDMA LLP reading slots in firmware register table. */ #define IPC4_MAX_LLP_GPDMA_READING_SLOTS 24 +/** \brief Number of host DMA byte counter slots in firmware register table. */ +#define IPC4_MAX_HOST_BYTE_CNT_SLOTS 2 + /** \brief Number of Aggregated SoundWire reading slots in firmware register table. */ #define IPC4_MAX_LLP_SNDW_READING_SLOTS 16 @@ -179,7 +182,8 @@ struct ipc4_fw_registers { union ipc4_rom_info rom_info; /**< ROM version and load capabilities (at offset 0x18). */ uint32_t abi_ver; /**< Layout version (IPC4_FW_REGS_ABI_VER at offset 0x1C). */ uint8_t slave_core_sts[IPC4_MAX_SUPPORTED_ADSP_CORES]; /**< Status byte per secondary core. */ - uint32_t rsvd2[6]; /**< Reserved padding. */ + uint32_t rsvd2[2]; /**< Reserved padding. */ + uint64_t host_byte_cnt[IPC4_MAX_HOST_BYTE_CNT_SLOTS]; /**< Host DMA byte counter per host gateway. */ struct ipc4_pipeline_registers pipeline_regs[IPC4_MAX_PIPELINE_REG_SLOTS]; /**< Pipeline LPIB offsets. */ struct ipc4_peak_volume_regs peak_vol_regs[IPC4_MAX_PEAK_VOL_REG_SLOTS]; /**< Peak meter telemetry. */ struct ipc4_llp_reading_slot llp_gpdma_reading_slots[IPC4_MAX_LLP_GPDMA_READING_SLOTS]; /**< GPDMA LLP readings. */ From e86f0d5479c3c067205527e4905fd096b13420f5 Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Wed, 26 Aug 2026 14:01:10 +0300 Subject: [PATCH 02/18] ipc4: host copier: emulate host DMA for host-DMA-less i.MX DSPs i.MX has no host DMA like Intel HDA; the transfer is driven by a software DMA over the ALSA buffer. In copier_host_create() read the PCM buffer physical address from a GTW_IMX_HOST_CONFIG_ID TLV in gtw_cfg.config_data, install it as a single element and use one-shot copy, as the IPC3 path does. host_common_params() keeps one period per element for that case. Gated on CONFIG_IMX. Signed-off-by: Daniel Baluta --- src/audio/copier/copier_host.c | 52 ++++++++++++++++++++++++++++++++++ src/audio/host-zephyr.c | 7 +++-- src/include/ipc4/gateway.h | 17 +++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/audio/copier/copier_host.c b/src/audio/copier/copier_host.c index c3ab0208a5d0..ac5d16632bd0 100644 --- a/src/audio/copier/copier_host.c +++ b/src/audio/copier/copier_host.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "copier.h" #include "host_copier.h" @@ -131,6 +132,43 @@ __cold static int init_pipeline_reg(struct comp_dev *dev) return 0; } +#if CONFIG_IMX +static int copier_host_imx_dma_setup(struct host_data *hd, + const struct ipc4_copier_module_cfg *copier_cfg) +{ + uint32_t total = copier_cfg->gtw_cfg.config_length << 2; + uint32_t prefix = sizeof(struct sof_ipc4_gtw_attributes); + struct sof_ipc4_imx_host_config *host_cfg = NULL; + uint32_t host_cfg_len = 0; + + if (total > prefix) + tlv_value_get((const uint8_t *)copier_cfg->gtw_cfg.config_data + prefix, + total - prefix, GTW_IMX_HOST_CONFIG_ID, + (void **)&host_cfg, &host_cfg_len); + + if (host_cfg && host_cfg_len >= sizeof(*host_cfg) && host_cfg->phy_addr) { + struct dma_sg_elem *host_elem; + + host_elem = sof_heap_alloc(hd->alloc_ctx.heap, SOF_MEM_FLAG_USER, + sizeof(*host_elem), 0); + if (!host_elem) + return -ENOMEM; + + host_elem->src = host_cfg->phy_addr; + host_elem->dest = host_cfg->phy_addr; + host_elem->size = host_cfg->dma_buffer_size; + + hd->host.elem_array.count = 1; + hd->host.elem_array.elems = host_elem; + hd->host_period_bytes = host_cfg->host_period_bytes; + } + + hd->copy_type = COMP_COPY_ONE_SHOT; + + return 0; +} +#endif + /* if copier is linked to host gateway, it will manage host dma. * Sof host component can support this case so copier reuses host * component to support host gateway. @@ -191,6 +229,13 @@ __cold int copier_host_create(struct processing_module *mod, comp_err(dev, "copier: host new failed with exit"); goto e_data; } + +#if CONFIG_IMX + ret = copier_host_imx_dma_setup(hd, copier_cfg); + if (ret < 0) + goto e_conv; +#endif + #if CONFIG_HOST_DMA_STREAM_SYNCHRONIZATION /* Size of a configuration without optional parameters. */ const uint32_t basic_size = sizeof(*copier_cfg) + @@ -323,11 +368,18 @@ static void copier_notifier_cb(void *arg, enum notify_id type, void *data) int copier_host_params(struct copier_data *cd, struct comp_dev *dev, struct sof_ipc_stream_params *params) { +#if CONFIG_IMX + /* keep host_period_bytes from the TLV; IPC4 params lack it */ + uint32_t host_period_bytes = cd->hd->host_period_bytes; +#endif int ret; component_set_nearest_period_frames(dev, params->rate); ret = host_common_params(cd->hd, dev, params, copier_notifier_cb); +#if CONFIG_IMX + cd->hd->host_period_bytes = host_period_bytes; +#endif cd->hd->process = cd->converter[IPC4_COPIER_GATEWAY_PIN]; diff --git a/src/audio/host-zephyr.c b/src/audio/host-zephyr.c index 0884fd834d8b..367efa343a3c 100644 --- a/src/audio/host-zephyr.c +++ b/src/audio/host-zephyr.c @@ -1027,8 +1027,11 @@ int host_common_params(struct host_data *hd, struct comp_dev *dev, /* TODO: should be taken from DMA */ if (hd->host.elem_array.count) { - period_bytes *= period_count; - period_count = 1; + /* i.MX software DMA copies one element per LL tick; keep the ring */ + if (!(IS_ENABLED(CONFIG_IMX) && hd->host.elem_array.count == 1)) { + period_bytes *= period_count; + period_count = 1; + } } /* calculate DMA buffer size */ diff --git a/src/include/ipc4/gateway.h b/src/include/ipc4/gateway.h index 81fa389e2a09..712481976bc1 100644 --- a/src/include/ipc4/gateway.h +++ b/src/include/ipc4/gateway.h @@ -135,6 +135,23 @@ union ipc4_gateway_attributes { } bits; /**< Bitfield access structure. */ } __attribute__((packed, aligned(4))); +/* Generic default-gateway attributes prefix. Mirrors the kernel struct. */ +struct sof_ipc4_gtw_attributes { + uint32_t lp_buffer_alloc : 1; + uint32_t alloc_from_reg_file : 1; + uint32_t rsvd : 30; +} __attribute__((packed, aligned(4))); + +/* i.MX host PCM buffer TLV; matches kernel SOF_IPC4_GTW_IMX_HOST_CONFIG_ID */ +#define GTW_IMX_HOST_CONFIG_ID 0x2001 + +struct sof_ipc4_imx_host_config { + uint32_t phy_addr; + uint32_t dma_buffer_size; + /* ALSA period size; one period-elapsed notification per this many bytes */ + uint32_t host_period_bytes; +} __attribute__((packed, aligned(4))); + /** * \brief Generic gateway configuration blob container. * \ingroup ipc4_gateway_api From e298e4c366adf3922dd100cfac17249fa70c400b Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Mon, 24 Aug 2026 18:22:54 +0300 Subject: [PATCH 03/18] ipc4: notification: add i.MX host period-elapsed notification i.MX has no host DMA to raise a period interrupt, so the DSP host copier emits a vendor notification (type 200) once per host period carrying the host gateway id. The kernel maps it to the substream and calls snd_sof_pcm_period_elapsed(). Signed-off-by: Daniel Baluta --- src/include/ipc4/notification.h | 4 ++++ src/kernel/ipc/ipc4/notification.c | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/include/ipc4/notification.h b/src/include/ipc4/notification.h index c2a55dd32284..ced27482402e 100644 --- a/src/include/ipc4/notification.h +++ b/src/include/ipc4/notification.h @@ -46,6 +46,7 @@ enum sof_ipc4_notification_type { SOF_IPC4_PROBE_DATA_AVAILABLE = 14, /**< Probe extraction buffer contains recorded audio. */ SOF_IPC4_WATCHDOG_TIMEOUT = 15, /**< Watchdog timer timeout interrupt on DSP core. */ SOF_IPC4_MANAGEMENT_SERVICE = 16, /**< System management service event notification. */ + SOF_IPC4_IMX_HOST_PERIOD_ELAPSED = 200, /**< Vendor (NXP i.MX): host period elapsed. */ }; /** @@ -333,4 +334,7 @@ bool z_impl_send_resource_notif(uint32_t resource_id, uint32_t event_type, #endif +/* Vendor (NXP i.MX): notify the host that one host period has elapsed. */ +bool send_host_period_elapsed_notif_msg(uint32_t gtw_id); + #endif /* __IPC4_NOTIFICATION_H__ */ diff --git a/src/kernel/ipc/ipc4/notification.c b/src/kernel/ipc/ipc4/notification.c index 86fb55e4d12f..f86c35709abd 100644 --- a/src/kernel/ipc/ipc4/notification.c +++ b/src/kernel/ipc/ipc4/notification.c @@ -114,3 +114,26 @@ static inline bool z_vrfy_send_resource_notif(uint32_t resource_id, uint32_t eve } #include #endif /* CONFIG_USERSPACE */ + +bool send_host_period_elapsed_notif_msg(uint32_t gtw_id) +{ + union ipc4_notification_header header; + struct ipc_msg *msg; + + msg = ipc_notification_pool_get(0); + if (!msg) + return false; + + header.dat = 0; + header.r.notif_type = SOF_IPC4_IMX_HOST_PERIOD_ELAPSED; + header.r.type = SOF_IPC4_GLB_NOTIFICATION; + header.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; + header.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG; + + msg->header = header.dat; + msg->extension = gtw_id; + + ipc_msg_send(msg, NULL, false); + + return true; +} From 5fee2cdd23a21b49bd410b6f6dd1077aa19587bb Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Wed, 26 Aug 2026 14:01:49 +0300 Subject: [PATCH 04/18] ipc4: host copier: publish host byte counter and period notification i.MX has no host DMA and no HDA link position registers. On each host DMA callback, write the host byte counter into the FW registers window (host_byte_cnt[gateway_id]) so the host driver can derive the PCM pointer, and send the vendor period-elapsed notification once per ALSA period so the driver runs snd_sof_pcm_period_elapsed(). Both are i.MX only; a gateway id beyond the byte counter slots is rejected at copier creation. Signed-off-by: Daniel Baluta --- src/audio/copier/copier_host.c | 37 ++++++++++++++++++++++++++++++++++ src/audio/copier/host_copier.h | 4 +++- src/audio/host-zephyr.c | 2 +- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/audio/copier/copier_host.c b/src/audio/copier/copier_host.c index ac5d16632bd0..412f8bbb8b74 100644 --- a/src/audio/copier/copier_host.c +++ b/src/audio/copier/copier_host.c @@ -10,9 +10,13 @@ #include #include #include +#include +#include #include "copier.h" #include "host_copier.h" +#include + LOG_MODULE_DECLARE(copier, CONFIG_SOF_LOG_LEVEL); #if CONFIG_HOST_DMA_STREAM_SYNCHRONIZATION @@ -231,6 +235,13 @@ __cold int copier_host_create(struct processing_module *mod, } #if CONFIG_IMX + if (copier_cfg->gtw_cfg.node_id.f.v_index >= IPC4_MAX_HOST_BYTE_CNT_SLOTS) { + comp_err(dev, "host gateway id %u exceeds byte counter slots", + copier_cfg->gtw_cfg.node_id.f.v_index); + ret = -EINVAL; + goto e_conv; + } + ret = copier_host_imx_dma_setup(hd, copier_cfg); if (ret < 0) goto e_conv; @@ -320,6 +331,28 @@ __cold void copier_host_free(struct processing_module *mod) mod_free(mod, cd->hd); } +#if CONFIG_IMX +/* publish the host byte count for the PCM pointer, notify once per ALSA period */ +static void copier_host_imx_update_pos(struct copier_data *cd, size_t bytes) +{ + uint32_t gtw_id = cd->config.gtw_cfg.node_id.f.v_index; + uint32_t off = offsetof(struct ipc4_fw_registers, host_byte_cnt) + + gtw_id * sizeof(uint64_t); + + mailbox_sw_regs_write(off, &cd->hd->total_data_processed, + sizeof(cd->hd->total_data_processed)); + + if (!cd->hd->host_period_bytes) + return; + + cd->hd->report_pos += bytes; + if (cd->hd->report_pos >= cd->hd->host_period_bytes) { + cd->hd->report_pos -= cd->hd->host_period_bytes; + send_host_period_elapsed_notif_msg(gtw_id); + } +} +#endif + /* This is called by DMA driver every time when DMA completes its current * transfer between host and DSP. */ @@ -334,6 +367,10 @@ void copier_host_dma_cb(struct comp_dev *dev, size_t bytes) /* update position */ host_common_update(cd->hd, dev, bytes); +#if CONFIG_IMX + copier_host_imx_update_pos(cd, bytes); +#endif + /* callback for one shot copy */ if (cd->hd->copy_type == COMP_COPY_ONE_SHOT) host_common_one_shot(cd->hd, bytes); diff --git a/src/audio/copier/host_copier.h b/src/audio/copier/host_copier.h index 309ddaeae24b..65229d4d0cda 100644 --- a/src/audio/copier/host_copier.h +++ b/src/audio/copier/host_copier.h @@ -105,8 +105,10 @@ struct host_data { /* stream info */ struct sof_ipc_stream_posn posn; /* TODO: update this */ -#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES || CONFIG_IMX uint32_t report_pos; /**< Position in current report period */ +#endif +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES struct ipc_msg *msg; /**< host notification */ #endif #if CONFIG_XRUN_NOTIFICATIONS_ENABLE diff --git a/src/audio/host-zephyr.c b/src/audio/host-zephyr.c index 367efa343a3c..cfc40c2c029b 100644 --- a/src/audio/host-zephyr.c +++ b/src/audio/host-zephyr.c @@ -1293,7 +1293,7 @@ void host_common_reset(struct host_data *hd, uint16_t state) /* reset buffer pointers */ hd->local_pos = 0; -#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES +#if CONFIG_HOST_DMA_IPC_POSITION_UPDATES || CONFIG_IMX hd->report_pos = 0; #endif hd->total_data_processed = 0; From aa6b66524471a01dfcae6efaf79d6b1530e49a71 Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Wed, 26 Aug 2026 14:02:37 +0300 Subject: [PATCH 05/18] ipc4: dai: set SDMA channel and burst for i.MX SAI/ESAI/MICFIL i.MX SAI/ESAI/MICFIL feed a Zephyr SDMA channel, unlike the Intel gateways. Set the two DMA parameters the IPC3 path already sets: * dai_config_dma_channel(): arm the channel on the DAI handshake low byte (the SDMA event source), or the FIFO request never triggers it; * ipc_dai_data_config(): set the DMA burst to the DAI FIFO depth. Signed-off-by: Daniel Baluta --- src/ipc/ipc4/dai.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/ipc/ipc4/dai.c b/src/ipc/ipc4/dai.c index b0737e3ac09b..722caec40360 100644 --- a/src/ipc/ipc4/dai.c +++ b/src/ipc/ipc4/dai.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -105,6 +106,13 @@ int dai_config_dma_channel(struct dai_data *dd, struct comp_dev *dev, const void int channel; switch (dai->type) { + case SOF_DAI_IMX_SAI: + case SOF_DAI_IMX_ESAI: + case SOF_DAI_IMX_MICFIL: + /* arm SDMA on the DAI handshake low byte (event source), like IPC3 */ + channel = dai_get_handshake(dd->dai, dev->direction, + dd->stream_id) & GENMASK(7, 0); + break; case SOF_DAI_INTEL_SSP: COMPILER_FALLTHROUGH; case SOF_DAI_INTEL_DMIC: @@ -216,6 +224,12 @@ int ipc_dai_data_config(struct dai_data *dd, struct comp_dev *dev) break; } #endif + case SOF_DAI_IMX_SAI: + case SOF_DAI_IMX_ESAI: + case SOF_DAI_IMX_MICFIL: + /* SDMA burst must match the DAI FIFO depth, like IPC3 */ + dd->config.burst_elems = dai_get_fifo_depth(dd->dai, dai->direction); + break; default: /* other types of DAIs not handled for now */ comp_warn(dev, "Unknown dai type %d", dai->type); From 60df9499ad2390458b7ec49993eba629690eed2e Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Mon, 9 Nov 2026 12:31:43 +0200 Subject: [PATCH 06/18] ipc4: gateway: add i.MX SAI link node types Add IPC4 connector node types for the i.MX SAI link: output (28) and input (29). Direction comes from the node type parity, so 27 is unused. Add the matching topology2 constants and dai-copier node_type values. Signed-off-by: Daniel Baluta --- src/include/ipc4/gateway.h | 5 +++++ .../topology2/include/common/common_definitions.conf | 2 ++ tools/topology/topology2/include/components/dai-copier.conf | 2 ++ 3 files changed, 9 insertions(+) diff --git a/src/include/ipc4/gateway.h b/src/include/ipc4/gateway.h index 712481976bc1..cd403602805a 100644 --- a/src/include/ipc4/gateway.h +++ b/src/include/ipc4/gateway.h @@ -85,6 +85,11 @@ enum ipc4_connector_node_id_type { ipc4_spi_output_class = 25, /** SPI hardware gateway input. */ ipc4_spi_input_class = 26, + + /** i.MX SAI link output */ + ipc4_sai_link_output_class = 28, + /** i.MX SAI link input */ + ipc4_sai_link_input_class = 29, /** Sentinel marking maximum connector node ID type. */ ipc4_max_connector_node_id_type }; diff --git a/tools/topology/topology2/include/common/common_definitions.conf b/tools/topology/topology2/include/common/common_definitions.conf index 06f0f425c5e2..fc3d10edbf10 100644 --- a/tools/topology/topology2/include/common/common_definitions.conf +++ b/tools/topology/topology2/include/common/common_definitions.conf @@ -60,6 +60,8 @@ Define { GPIO_CLASS 24 # GPIO SPI_OUTPUT_CLASS 25 # SPI Output (DSP ->) SPI_INPUT_CLASS 26 # SPI Input (DSP <-) + SAI_LINK_OUTPUT_CLASS 28 # i.MX SAI link output (DSP ->) + SAI_LINK_INPUT_CLASS 29 # i.MX SAI link input (DSP <-) DEEPBUFFER_FW_DMA_MS 100 # 100 ms copier dma size DEEPBUFFER_D0I3_COMPATIBLE true # Deep buffer PCM is D0I3 compatible diff --git a/tools/topology/topology2/include/components/dai-copier.conf b/tools/topology/topology2/include/components/dai-copier.conf index 3b97ff0b4adf..b16cd703db3b 100644 --- a/tools/topology/topology2/include/components/dai-copier.conf +++ b/tools/topology/topology2/include/components/dai-copier.conf @@ -114,6 +114,8 @@ Class.Widget."dai-copier" { $GPIO_CLASS # GPIO $SPI_OUTPUT_CLASS # SPI Output (DSP ->) $SPI_INPUT_CLASS # SPI Input (DSP <-) + $SAI_LINK_OUTPUT_CLASS # i.MX SAI link output (DSP ->) + $SAI_LINK_INPUT_CLASS # i.MX SAI link input (DSP <-) ] } } From 310e8094c70a7f665f8d2c6f4fa98156848cbde0 Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Wed, 26 Aug 2026 14:03:28 +0300 Subject: [PATCH 07/18] ipc4: dai: support i.MX SAI DAI configuration On i.MX the kernel passes the DAI configuration from topology as a GTW_IMX_DAI_CONFIG_ID TLV (struct sof_ipc_dai_config) in the DAI copier gateway config, after the gateway attributes. Create SAI link DAI copiers as SOF_DAI_IMX_SAI, taking dai.format from the TLV, and pass the TLV to dai_set_config() for SAI, ESAI and MICFIL. Requires the matching kernel change. Signed-off-by: Daniel Baluta --- src/audio/copier/copier.c | 2 ++ src/audio/copier/copier_dai.c | 22 ++++++++++++++++++++++ src/include/ipc4/gateway.h | 3 +++ src/ipc/ipc4/dai.c | 31 +++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+) diff --git a/src/audio/copier/copier.c b/src/audio/copier/copier.c index e8976ec9add6..9fabe480dc27 100644 --- a/src/audio/copier/copier.c +++ b/src/audio/copier/copier.c @@ -234,6 +234,8 @@ __cold static int copier_init(struct processing_module *mod) case ipc4_dmic_link_input_class: case ipc4_i2s_link_output_class: case ipc4_i2s_link_input_class: + case ipc4_sai_link_output_class: + case ipc4_sai_link_input_class: case ipc4_alh_link_output_class: case ipc4_alh_link_input_class: case ipc4_alh_uaol_stream_link_output_class: diff --git a/src/audio/copier/copier_dai.c b/src/audio/copier/copier_dai.c index 508972b4773c..bac7f6abaa33 100644 --- a/src/audio/copier/copier_dai.c +++ b/src/audio/copier/copier_dai.c @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include "copier.h" #include "dai_copier.h" @@ -335,6 +337,26 @@ __cold int copier_dai_create(struct comp_dev *dev, struct copier_data *cd, return -EINVAL; } break; +#if CONFIG_IMX + case ipc4_sai_link_output_class: + case ipc4_sai_link_input_class: + /* take dai.format from the i.MX DAI config TLV */ + { + uint32_t prefix = sizeof(struct sof_ipc4_gtw_attributes); + void *dcfg = NULL; + uint32_t dlen = 0; + + if (gtw_cfg_size > prefix) + tlv_value_get(gtw_cfg_data + prefix, + gtw_cfg_size - prefix, + GTW_IMX_DAI_CONFIG_ID, &dcfg, &dlen); + if (dcfg && dlen >= sizeof(struct sof_ipc_dai_config)) + dai.format = ((struct sof_ipc_dai_config *)dcfg)->format; + } + dai.type = SOF_DAI_IMX_SAI; + cd->gtw_type = ipc4_gtw_link; + break; +#endif case ipc4_alh_link_output_class: case ipc4_alh_link_input_class: #if CONFIG_ACE_VERSION_2_0_OR_LATER diff --git a/src/include/ipc4/gateway.h b/src/include/ipc4/gateway.h index cd403602805a..ab3ee2731ee1 100644 --- a/src/include/ipc4/gateway.h +++ b/src/include/ipc4/gateway.h @@ -157,6 +157,9 @@ struct sof_ipc4_imx_host_config { uint32_t host_period_bytes; } __attribute__((packed, aligned(4))); +/* i.MX SAI config TLV (struct sof_ipc_dai_config) */ +#define GTW_IMX_DAI_CONFIG_ID 0x2000 + /** * \brief Generic gateway configuration blob container. * \ingroup ipc4_gateway_api diff --git a/src/ipc/ipc4/dai.c b/src/ipc/ipc4/dai.c index 722caec40360..f36e9af4c2c0 100644 --- a/src/ipc/ipc4/dai.c +++ b/src/ipc/ipc4/dai.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -460,6 +461,36 @@ __cold int dai_config(struct dai_data *dd, struct comp_dev *dev, if (ret < 0) return ret; +#if CONFIG_IMX + /* pass the DAI config TLV; dai_spec_config must stay the copier cfg */ + switch (common_config->type) { + case SOF_DAI_IMX_SAI: + case SOF_DAI_IMX_ESAI: + case SOF_DAI_IMX_MICFIL: { + uint32_t total = copier_cfg->gtw_cfg.config_length << 2; + uint32_t prefix = sizeof(struct sof_ipc4_gtw_attributes); + void *dai_cfg = NULL; + uint32_t dai_cfg_len = 0; + + if (total > prefix) + tlv_value_get((const uint8_t *)copier_cfg->gtw_cfg.config_data + + prefix, + total - prefix, GTW_IMX_DAI_CONFIG_ID, + &dai_cfg, &dai_cfg_len); + + if (!dai_cfg || dai_cfg_len < sizeof(struct sof_ipc_dai_config)) { + comp_err(dev, "missing i.MX DAI config TLV (dai type %d)", + common_config->type); + return -EINVAL; + } + + return dai_set_config(dd->dai, common_config, dai_cfg, dai_cfg_len); + } + default: + break; + } +#endif + /* gtw_cfg.config_length is in words */ size = copier_cfg->gtw_cfg.config_length << 2; From 9f7371d014657114b6357f26affdf666ca1b7c89 Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Tue, 25 Aug 2026 17:09:15 +0300 Subject: [PATCH 08/18] rimage: imx: add IPC4 ext-manifest support The Linux SOF IPC4 loader reads an extended manifest at the start of the firmware file to discover the firmware modules: it builds its module ID to UUID table from the module entries and uses it to instantiate the copier, gain and other modules. Intel images get these entries from the signed manifest; i.MX images are not signed and have no such manifest. Add an i.MX IPC4 ext-manifest writer, written to .xman and placed in front of the image: - an "$AE1" extended manifest header (struct fw_ext_man_cavs_header, version 4.0) covering only itself; - a 48-byte gap: the kernel finds the fw header at the ext-manifest length plus its manifest_fw_hdr_offset, which must not be zero; - an "$AM1" fw binary header (struct sof_man_fw_header) with the firmware version and module count; - one struct sof_man_module per record of the base firmware ".module" ELF section, with the module ID set to the table index. i.MX uses no module config blocks or segments, so those fields are zero. Select it with a new rimage config version 1.1: the v1.0 simple image plus this ext-manifest. v1.0 and v1.1 share one parser. Per-SoC configs are added with each platform's IPC4 support. Signed-off-by: Daniel Baluta --- tools/rimage/src/adsp_config.c | 17 ++- tools/rimage/src/ext_manifest.c | 108 ++++++++++++++++++ .../src/include/rimage/ext_manifest_gen.h | 3 + 3 files changed, 127 insertions(+), 1 deletion(-) diff --git a/tools/rimage/src/adsp_config.c b/tools/rimage/src/adsp_config.c index cbafc4c58b4c..c51ceba00a5a 100644 --- a/tools/rimage/src/adsp_config.c +++ b/tools/rimage/src/adsp_config.c @@ -2011,7 +2011,9 @@ static int parse_module(const toml_table_t *toml, struct parse_ctx *pctx, return 0; } -static int parse_adsp_config_v1_0(const toml_table_t *toml, struct image *image) +/* v1.0 and v1.1: simple image, v1.1 adds an ext-manifest writer */ +static int parse_adsp_config_simple(const toml_table_t *toml, struct image *image, + int (*write_ext_man)(struct image *image)) { struct adsp *out = image->adsp; bool verbose = image->verbose; @@ -2029,6 +2031,7 @@ static int parse_adsp_config_v1_0(const toml_table_t *toml, struct image *image) /* assign correct write functions */ out->write_firmware = simple_write_firmware; + out->write_firmware_ext_man = write_ext_man; out->write_firmware_meu = NULL; /* check everything parsed */ @@ -2039,6 +2042,17 @@ static int parse_adsp_config_v1_0(const toml_table_t *toml, struct image *image) return 0; } +static int parse_adsp_config_v1_0(const toml_table_t *toml, struct image *image) +{ + return parse_adsp_config_simple(toml, image, NULL); +} + +/* i.MX IPC4: v1.0 plus an unsigned ext-manifest v4 prefix */ +static int parse_adsp_config_v1_1(const toml_table_t *toml, struct image *image) +{ + return parse_adsp_config_simple(toml, image, ext_man_write_imx_ipc4); +} + static int parse_adsp_config_v1_5(const toml_table_t *toml, struct image *image) { struct adsp *out = image->adsp; @@ -2310,6 +2324,7 @@ static const struct config_parser *find_config_parser(int64_t version[2]) /* list of supported configuration version with handler to parser */ static const struct config_parser parsers[] = { {1, 0, parse_adsp_config_v1_0}, + {1, 1, parse_adsp_config_v1_1}, {1, 5, parse_adsp_config_v1_5}, {1, 8, parse_adsp_config_v1_8}, {2, 5, parse_adsp_config_v2_5}, diff --git a/tools/rimage/src/ext_manifest.c b/tools/rimage/src/ext_manifest.c index 8beaa4943a7b..e28ec7b33ec2 100644 --- a/tools/rimage/src/ext_manifest.c +++ b/tools/rimage/src/ext_manifest.c @@ -253,3 +253,111 @@ int ext_man_write_cavs_25(struct image *image) fclose(image->out_ext_man_fd); return ret; } + +/* gap before the fw header; must match kernel manifest_fw_hdr_offset (!= 0) */ +#define IMX_EXT_MAN4_FW_HDR_OFFSET 48 + +/* + * i.MX IPC4 extended manifest: the cAVS ext-manifest header, a gap, the fw + * binary header and one module entry per ".module" record of the base FW. + */ +int ext_man_write_imx_ipc4(struct image *image) +{ + static const uint8_t fw_hdr_id[] = SOF_MAN_FW_HDR_ID; + const struct sof_man_module_manifest *sof_mod; + const struct manifest_module *module = NULL; + struct fw_ext_man_cavs_header *ext_hdr; + struct elf_section section = { 0 }; + struct sof_man_fw_header *fw_hdr; + struct sof_man_module *mods; + unsigned int n_mod, i; + size_t ext_man_len; + uint8_t *buf = NULL; + int ret; + + for (i = 0; i < (unsigned int)image->num_modules; i++) { + if (!image->module[i].is_bootloader) { + module = &image->module[i]; + break; + } + } + + if (!module) { + fprintf(stderr, "error: no base firmware module for IPC4 manifest\n"); + return -EINVAL; + } + + ret = elf_section_read_by_name(&module->file.elf, ".module", §ion); + if (ret) { + fprintf(stderr, "error: can't read '.module' section for IPC4 manifest\n"); + return ret; + } + + n_mod = section.header.data.size / sizeof(*sof_mod); + if (!n_mod || n_mod * sizeof(*sof_mod) != section.header.data.size) { + fprintf(stderr, "error: invalid '.module' section for IPC4 manifest\n"); + ret = -EINVAL; + goto out; + } + + ext_man_len = sizeof(*ext_hdr) + IMX_EXT_MAN4_FW_HDR_OFFSET + + sizeof(*fw_hdr) + n_mod * sizeof(*mods); + buf = calloc(1, ext_man_len); + if (!buf) { + ret = -ENOMEM; + goto out; + } + + /* the kernel finds the fw header at ext_hdr->len + manifest_fw_hdr_offset */ + ext_hdr = (struct fw_ext_man_cavs_header *)buf; + ext_hdr->id = EXTENDED_MANIFEST_MAGIC_HEADER_ID; + ext_hdr->len = sizeof(*ext_hdr); + ext_hdr->version_major = 4; + ext_hdr->version_minor = 0; + ext_hdr->num_module_entries = n_mod; + + fw_hdr = (struct sof_man_fw_header *)(buf + sizeof(*ext_hdr) + + IMX_EXT_MAN4_FW_HDR_OFFSET); + memcpy(fw_hdr->header_id, fw_hdr_id, sizeof(fw_hdr->header_id)); + fw_hdr->header_len = sizeof(*fw_hdr); + memcpy(fw_hdr->name, SOF_MAN_FW_HDR_NAME, sizeof(SOF_MAN_FW_HDR_NAME) - 1); + fw_hdr->major_version = image->fw_ver_major; + fw_hdr->minor_version = image->fw_ver_minor; + fw_hdr->hotfix_version = image->fw_ver_micro; + fw_hdr->build_version = image->fw_ver_build; + fw_hdr->num_module_entries = n_mod; + + /* module id == table index; no config blocks or segments on i.MX */ + mods = (struct sof_man_module *)(fw_hdr + 1); + sof_mod = section.data; + for (i = 0; i < n_mod; i++) { + memcpy(&mods[i], &sof_mod[i].module, sizeof(mods[i])); + mods[i].runtime_info.module_id = i; + mods[i].runtime_info.state_flags = 0; + mods[i].cfg_offset = 0; + mods[i].cfg_count = 0; + mods[i].instance_bss_size = 0; + memset(mods[i].segment, 0, sizeof(mods[i].segment)); + } + + ret = ext_man_open_file(image); + if (ret) + goto out; + + if (fwrite(buf, ext_man_len, 1, image->out_ext_man_fd) != 1) { + ret = file_error("can't write IPC4 extended manifest", + image->out_ext_man_file); + goto out; + } + + fprintf(stdout, "imx: IPC4 extended manifest: %zu bytes, %u module(s)\n", + ext_man_len, n_mod); +out: + if (image->out_ext_man_fd) { + fclose(image->out_ext_man_fd); + image->out_ext_man_fd = NULL; + } + free(buf); + elf_section_free(§ion); + return ret; +} diff --git a/tools/rimage/src/include/rimage/ext_manifest_gen.h b/tools/rimage/src/include/rimage/ext_manifest_gen.h index 7f106834a0ca..f5795bea79fc 100644 --- a/tools/rimage/src/include/rimage/ext_manifest_gen.h +++ b/tools/rimage/src/include/rimage/ext_manifest_gen.h @@ -34,4 +34,7 @@ int ext_man_write(struct image *image); int ext_man_write_cavs_25(struct image *image); +/* i.MX IPC4 ext-manifest (v4) generator */ +int ext_man_write_imx_ipc4(struct image *image); + #endif /* __EXT_MAN_H__ */ From 4bdde5ddc153df748db443f8c6ed42eec4070f47 Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Wed, 26 Aug 2026 13:58:52 +0300 Subject: [PATCH 09/18] ipc4: imx: resolve module IDs via a platform manifest table i.MX has no IMR-resident rimage manifest for ipc4_get_comp_drv() to walk. Each i.MX platform links its module manifest table into the image, with the module ID equal to the table index (base FW is entry 0). Add platform_ipc4_get_module() for the platforms to provide, and resolve module IDs through it on i.MX, the same way the fuzzer build resolves them through its own lookup. Signed-off-by: Daniel Baluta --- src/include/sof/platform.h | 6 ++++++ src/ipc/ipc4/helper.c | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/include/sof/platform.h b/src/include/sof/platform.h index 070809ad38b1..10b9e4635460 100644 --- a/src/include/sof/platform.h +++ b/src/include/sof/platform.h @@ -52,6 +52,12 @@ int platform_init(struct sof *sof); */ int platform_context_save(struct sof *sof); +#if CONFIG_IPC_MAJOR_4 && CONFIG_IMX +struct sof_man_module; +/** \brief Map an IPC4 module ID to its manifest entry. */ +const struct sof_man_module *platform_ipc4_get_module(uint32_t module_id); +#endif + /** @}*/ #endif diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index d5a0e095b3f4..ae0ff94a607f 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -49,6 +49,7 @@ #include #include #include +#include #include #include @@ -1434,6 +1435,21 @@ static const struct comp_driver *ipc4_get_fuzzer_drv(uint32_t module_id) } #endif /* defined(CONFIG_ARCH_POSIX_LIBFUZZER) && !defined(RIMAGE_MANIFEST) */ +#if CONFIG_IMX && !defined(RIMAGE_MANIFEST) +/* i.MX: the module table is linked into the image, module ID == table index */ +static const struct comp_driver *ipc4_get_imx_drv(uint32_t module_id) +{ + const struct sof_man_module *mod = platform_ipc4_get_module(module_id); + + if (!mod) { + tr_err(&comp_tr, "Error: module id %u out of bounds.", module_id); + return NULL; + } + + return ipc4_get_drv(&mod->uuid); +} +#endif /* CONFIG_IMX && !defined(RIMAGE_MANIFEST) */ + /* * Called from * - ipc4_get_large_config_module_instance() @@ -1453,6 +1469,8 @@ __cold const struct comp_driver *ipc4_get_comp_drv(uint32_t module_id) desc = (const struct sof_man_fw_desc *)IMR_BOOT_LDR_MANIFEST_BASE; #elif defined(CONFIG_ARCH_POSIX_LIBFUZZER) return ipc4_get_fuzzer_drv(module_id); +#elif CONFIG_IMX + return ipc4_get_imx_drv(module_id); #else /* Non-rimage platforms have no component facility yet. * This needs to move to the platform layer. From d109c3bb35c713480573a8e50cc24b98ad9c06f0 Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Mon, 9 Nov 2026 16:55:01 +0200 Subject: [PATCH 10/18] scripts: xtensa-build-zephyr: support a per-platform extra_conf overlay Add an optional extra_conf field to PlatformConfig. When set, the file is appended to EXTRA_CONF_FILE for that platform, so a build target can apply a Kconfig overlay on top of the board defaults, e.g. to select a different IPC version. Signed-off-by: Daniel Baluta --- scripts/xtensa-build-zephyr.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/xtensa-build-zephyr.py b/scripts/xtensa-build-zephyr.py index f20c95f7262a..62a09a1ed6b3 100755 --- a/scripts/xtensa-build-zephyr.py +++ b/scripts/xtensa-build-zephyr.py @@ -88,6 +88,7 @@ class PlatformConfig: RIMAGE_KEY: pathlib.Path = pathlib.Path(SOF_TOP, "keys", "otc_private_key_3k.pem") aliases: list = dataclasses.field(default_factory=list) ipc4: bool = False + extra_conf: pathlib.Path = None # These cannot be built by everyone out of the box yet. # For instance: there's no open-source toolchain available for them yet. @@ -953,6 +954,9 @@ def build_platforms(): if platf_build_environ.get("ZEPHYR_TOOLCHAIN_VARIANT") == 'xt-clang': extra_conf_files.append(str(pathlib.Path(SOF_TOP, "app", "llext_relocatable.conf"))) + if platform_dict.get("extra_conf"): + extra_conf_files.append(str(platform_dict["extra_conf"])) + if extra_conf_files: extra_conf_files = ";".join(extra_conf_files) build_cmd.append(f"-DEXTRA_CONF_FILE={extra_conf_files}") From 362eb6db573ffafa5bce00ac72996ade460ac51e Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Mon, 9 Nov 2026 16:58:08 +0200 Subject: [PATCH 11/18] drivers: imx: ipc: add IPC4 transport The 8-byte IPC4 primary/extension header does not fit the 4-byte MU data register. Carry it in an SRAM slot in front of each mailbox payload (MAILBOX_HOSTBOX_HDR_BASE / MAILBOX_DSPBOX_HDR_BASE, provided by each platform) and keep using the MU only as a doorbell: - read the host header from the hostbox slot via ipc_platform_compact_read_msg(); - write the DSP header to the dspbox slot before sending, followed by the payload, if any; - imx_ipc4_boot_complete() sends SOF_IPC4_FW_READY as a notification. Also add the IPC4 code that is the same on all i.MX platforms: - the module manifest table, emitted into the ".module" section for rimage and resolved at runtime via platform_ipc4_get_module(); - clear the SW register (LLP) window in platform_ipc_init(): it is not part of the loaded image, so it keeps stale slots across reloads. Signed-off-by: Daniel Baluta --- src/include/sof/platform.h | 2 + src/kernel/drivers/imx/ipc.c | 126 ++++++++++++++++++++++++++++++++++- 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/src/include/sof/platform.h b/src/include/sof/platform.h index 10b9e4635460..223d6255b904 100644 --- a/src/include/sof/platform.h +++ b/src/include/sof/platform.h @@ -56,6 +56,8 @@ int platform_context_save(struct sof *sof); struct sof_man_module; /** \brief Map an IPC4 module ID to its manifest entry. */ const struct sof_man_module *platform_ipc4_get_module(uint32_t module_id); +/** \brief Send SOF_IPC4_FW_READY to the host. */ +int imx_ipc4_boot_complete(uint32_t boot_message); #endif /** @}*/ diff --git a/src/kernel/drivers/imx/ipc.c b/src/kernel/drivers/imx/ipc.c index a124d20a9028..ccd178db4590 100644 --- a/src/kernel/drivers/imx/ipc.c +++ b/src/kernel/drivers/imx/ipc.c @@ -26,6 +26,12 @@ #include #include +#if CONFIG_IPC_MAJOR_4 +#include +#include +#include +#endif + #include #include #include @@ -47,6 +53,96 @@ struct ipc_data { struct ipc_data_host_buffer dh_buffer; }; +#if CONFIG_IPC_MAJOR_4 +/* + * IPC4 module manifest, emitted into the ".module" ELF section and re-emitted + * by rimage into the ext-manifest module table. Module id == table index, so + * BASEFW must stay first; UUIDs must match uuid-registry.txt. + */ +#define IMX_MAN_MODULE(mname, uuid_a, uuid_b, uuid_c, uuid_d...) \ + { \ + .module = { \ + .name = mname, \ + .uuid = { \ + .a = uuid_a, \ + .b = uuid_b, \ + .c = uuid_c, \ + .d = { uuid_d }, \ + }, \ + .type = { \ + .load_type = SOF_MAN_MOD_TYPE_BUILTIN, \ + .domain_ll = 1, \ + }, \ + .affinity_mask = 1, \ + .instance_max_count = 8, \ + }, \ + } + +static const struct sof_man_module_manifest imx_man_modules[] + __section(".module") __used = { + IMX_MAN_MODULE("BASEFW", 0x0e398c32, 0x5ade, 0xba4b, + 0x93, 0xb1, 0xc5, 0x04, 0x32, 0x28, 0x0e, 0xe4), + IMX_MAN_MODULE("COPIER", 0x9ba00c83, 0xca12, 0x4a83, + 0x94, 0x3c, 0x1f, 0xa2, 0xe8, 0x2f, 0x9d, 0xda), + IMX_MAN_MODULE("GAIN", 0x61bca9a8, 0x18d0, 0x4a18, + 0x8e, 0x7b, 0x26, 0x39, 0x21, 0x98, 0x04, 0xb7), + IMX_MAN_MODULE("MIXIN", 0x39656eb2, 0x3b71, 0x4049, + 0x8d, 0x3f, 0xf9, 0x2c, 0xd5, 0xc4, 0x3c, 0x09), + IMX_MAN_MODULE("MIXOUT", 0x3c56505a, 0x24d7, 0x418f, + 0xbd, 0xdc, 0xc1, 0xf5, 0xa3, 0xac, 0x2a, 0xe0), +}; + +const struct sof_man_module *platform_ipc4_get_module(uint32_t module_id) +{ + if (module_id >= ARRAY_SIZE(imx_man_modules)) + return NULL; + + return &imx_man_modules[module_id].module; +} + +/* + * The 8-byte IPC4 header does not fit the 4-byte MU data register, so it + * travels in the SRAM slot in front of each mailbox payload. + */ +static void imx_ipc4_read_hdr(struct ipc_cmd_hdr *hdr) +{ + volatile struct ipc_cmd_hdr *slot = (void *)MAILBOX_HOSTBOX_HDR_BASE; + + dcache_invalidate_region((__sparse_force void __sparse_cache *)MAILBOX_HOSTBOX_HDR_BASE, + sizeof(*hdr)); + hdr->pri = slot->pri; + hdr->ext = slot->ext; + + tr_dbg(&ipc_tr, "ipc4: msg rx <- pri 0x%08x ext 0x%08x", hdr->pri, hdr->ext); +} + +static void imx_ipc4_write_hdr(const struct ipc_msg *msg) +{ + volatile struct ipc_cmd_hdr *slot = (void *)MAILBOX_DSPBOX_HDR_BASE; + + slot->pri = msg->header; + slot->ext = msg->extension; + dcache_writeback_region((__sparse_force void __sparse_cache *)MAILBOX_DSPBOX_HDR_BASE, + sizeof(*slot)); + + tr_dbg(&ipc_tr, "ipc4: msg tx -> pri 0x%08x ext 0x%08x size %u", + msg->header, msg->extension, msg->tx_size); +} + +/* send SOF_IPC4_FW_READY as a notification (no payload) */ +int imx_ipc4_boot_complete(uint32_t boot_message) +{ + struct ipc_cmd_hdr header; + struct ipc_msg msg = { 0 }; + + ipc_boot_complete_msg(&header, 0); + msg.header = header.pri | boot_message; + msg.extension = header.ext; + + return ipc_platform_send_msg(&msg); +} +#endif /* CONFIG_IPC_MAJOR_4 */ + static void irq_handler(void *arg) { struct ipc *ipc = arg; @@ -87,7 +183,16 @@ int ipc_platform_compact_write_msg(struct ipc_cmd_hdr *hdr, int words) int ipc_platform_compact_read_msg(struct ipc_cmd_hdr *hdr, int words) { +#if CONFIG_IPC_MAJOR_4 + if (words != 2) + return 0; + + imx_ipc4_read_hdr(hdr); + + return 2; /* number of words read */ +#else return 0; /* number of words read - not currently used on this platform */ +#endif } enum task_state ipc_platform_do_cmd(struct ipc *ipc) @@ -95,8 +200,13 @@ enum task_state ipc_platform_do_cmd(struct ipc *ipc) struct ipc_cmd_hdr *hdr; /* Use struct ipc_data *iipc = ipc_get_drvdata(ipc); if needed */ - /* perform command */ +#if CONFIG_IPC_MAJOR_4 + hdr = ipc_compact_read_msg(); +#else hdr = mailbox_validate(); +#endif + + /* perform command */ ipc_cmd(hdr); return SOF_TASK_STATE_COMPLETED; @@ -153,8 +263,13 @@ int ipc_platform_send_msg(const struct ipc_msg *msg) if (ipc->is_notification_pending || gir0_set || gir1_set) return -EBUSY; +#if CONFIG_IPC_MAJOR_4 + imx_ipc4_write_hdr(msg); +#endif + /* now send the message */ - mailbox_dspbox_write(0, msg->tx_data, msg->tx_size); + if (msg->tx_size) + mailbox_dspbox_write(0, msg->tx_data, msg->tx_size); tr_dbg(&ipc_tr, "ipc: msg tx -> 0x%x", msg->header); @@ -194,6 +309,13 @@ int platform_ipc_init(struct ipc *ipc) ipc_set_drvdata(ipc, NULL); #endif +#if CONFIG_IPC_MAJOR_4 + /* SW regs survive a reload; clear stale LLP slots */ + bzero((void *)MAILBOX_SW_REG_BASE, MAILBOX_SW_REG_SIZE); + dcache_writeback_region((__sparse_force void __sparse_cache *)MAILBOX_SW_REG_BASE, + MAILBOX_SW_REG_SIZE); +#endif + /* schedule */ schedule_task_init_edf(&ipc->ipc_task, SOF_UUID(ipc_task_uuid), &ipc_task_ops, ipc, 0, 0); From 0a491cc8d13a6809177b341ab64360187eabdcd3 Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Mon, 9 Nov 2026 17:00:53 +0200 Subject: [PATCH 12/18] platform: imx8m: add IPC4 firmware support Add IPC4 support for the i.MX8MP HiFi4 DSP on top of the shared i.MX IPC4 transport: - memory.h/mailbox.h: an 8-byte IPC4 header slot at the start of the inbox and outbox (MAILBOX_*_HDR_BASE), and the SW register (LLP) window with its accessors. The SW registers reuse the exception window, which only IPC3 uses. The IPC3 layout is unchanged. - clk.h: CPU_LOWEST_FREQ_IDX and HW_CFG_VERSION for the IPC4 base FW. - platform.c: send FW_READY via imx_ipc4_boot_complete(); the IPC3 fw_ready and memory window descriptors are IPC3 only. - keep the ".module" manifest table in a loaded section and select the imx8m_ipc4 rimage config, which adds the IPC4 ext-manifest. - an IPC4 board overlay and an opt-in 'imx8m-ipc4' build target. The default 'imx8m' target stays IPC3. Signed-off-by: Daniel Baluta --- app/boards/imx8mp_evk_mimx8ml8_adsp_ipc4.conf | 19 +++++++ scripts/xtensa-build-zephyr.py | 12 +++- src/platform/imx8m/include/platform/lib/clk.h | 6 ++ .../imx8m/include/platform/lib/mailbox.h | 57 +++++++++++++++++++ .../imx8m/include/platform/lib/memory.h | 29 +++++++--- src/platform/imx8m/linker/module-section.ld | 18 ++++++ src/platform/imx8m/platform.c | 6 ++ tools/rimage/config/imx8m_ipc4.toml | 20 +++++++ zephyr/CMakeLists.txt | 11 ++++ 9 files changed, 170 insertions(+), 8 deletions(-) create mode 100644 app/boards/imx8mp_evk_mimx8ml8_adsp_ipc4.conf create mode 100644 src/platform/imx8m/linker/module-section.ld create mode 100644 tools/rimage/config/imx8m_ipc4.toml diff --git a/app/boards/imx8mp_evk_mimx8ml8_adsp_ipc4.conf b/app/boards/imx8mp_evk_mimx8ml8_adsp_ipc4.conf new file mode 100644 index 000000000000..d1057caef667 --- /dev/null +++ b/app/boards/imx8mp_evk_mimx8ml8_adsp_ipc4.conf @@ -0,0 +1,19 @@ +CONFIG_IMX8M=y +CONFIG_HAVE_AGENT=n +CONFIG_FORMAT_CONVERT_HIFI3=n +CONFIG_KPB_FORCE_COPY_TYPE_NORMAL=n +CONFIG_DMA=y +CONFIG_DMA_NXP_SDMA=y +CONFIG_DAI_NXP_MICFIL=y +CONFIG_TRACE=n +CONFIG_SHARED_INTERRUPTS=y +CONFIG_ZEPHYR_NATIVE_DRIVERS=y +CONFIG_SAI_HAS_MCLK_CONFIG_OPTION=y +CONFIG_CLOCK_CONTROL_FIXED_RATE_CLOCK=y + +# IPC4 overlay for i.MX8MP +CONFIG_IPC_MAJOR_4=y + +# single core +CONFIG_CROSS_CORE_STREAM=n +CONFIG_COMP_PEAK_VOL=n diff --git a/scripts/xtensa-build-zephyr.py b/scripts/xtensa-build-zephyr.py index 62a09a1ed6b3..5eeffd6fd550 100755 --- a/scripts/xtensa-build-zephyr.py +++ b/scripts/xtensa-build-zephyr.py @@ -230,6 +230,16 @@ class PlatformConfig: "hifi4_mscale_v2_0_2_prod", RIMAGE_KEY = "key param ignored by imx8m" ), + # i.MX8MP IPC4 build. Opt-in target; the default 'imx8m' stays IPC3. + "imx8m-ipc4" : PlatformConfig( + "imx", "imx8mp_evk/mimx8ml8/adsp", + f"RI-2023.11{xtensa_tools_version_postfix}", + "hifi4_mscale_v2_0_2_prod", + RIMAGE_KEY = "key param ignored by imx8m", + ipc4 = True, + extra_conf = pathlib.Path(SOF_TOP, "app", "boards", + "imx8mp_evk_mimx8ml8_adsp_ipc4.conf") + ), "imx8m_cm7" : PlatformConfig( "imx", "imx8mp_evk/mimx8ml8/m7/ddr", "", "", "", "" @@ -1354,7 +1364,7 @@ def gzip_compress(fname, gzdst=None): # Don't run sof_ri_info and ignore silently .ri files that don't have one. RI_INFO_UNSUPPORTED = [] -RI_INFO_UNSUPPORTED += ['imx8', 'imx8x', 'imx8m', 'imx8m_cm7', 'imx8ulp', 'imx95'] +RI_INFO_UNSUPPORTED += ['imx8', 'imx8x', 'imx8m', 'imx8m-ipc4', 'imx8m_cm7', 'imx8ulp', 'imx95'] RI_INFO_UNSUPPORTED += ['rn', 'acp_6_0', 'acp_7_0', 'acp_7_x'] RI_INFO_UNSUPPORTED += ['mt8186', 'mt8188', 'mt8195', 'mt8196', 'mt8365'] RI_INFO_UNSUPPORTED += ['qemu_xtensa', 'qemu_xtensa_mmu', 'native_sim'] diff --git a/src/platform/imx8m/include/platform/lib/clk.h b/src/platform/imx8m/include/platform/lib/clk.h index 99e262bdbe5a..1b640700cb92 100644 --- a/src/platform/imx8m/include/platform/lib/clk.h +++ b/src/platform/imx8m/include/platform/lib/clk.h @@ -18,6 +18,9 @@ struct sof; #define CPU_DEFAULT_IDX 0 +/* single fixed frequency, so lowest == default */ +#define CPU_LOWEST_FREQ_IDX CPU_DEFAULT_IDX + #define CLK_DEFAULT_CPU_HZ 800000000 #define CLK_MAX_CPU_HZ 800000000 @@ -25,6 +28,9 @@ struct sof; #define NUM_CPU_FREQ 1 +/* i.MX is not a CAVS platform; report 0 (unspecified) */ +#define HW_CFG_VERSION 0 + void platform_clock_init(struct sof *sof); #endif /* __PLATFORM_LIB_CLK_H__ */ diff --git a/src/platform/imx8m/include/platform/lib/mailbox.h b/src/platform/imx8m/include/platform/lib/mailbox.h index 7a7e0d643fd0..13f3cfe7fdf5 100644 --- a/src/platform/imx8m/include/platform/lib/mailbox.h +++ b/src/platform/imx8m/include/platform/lib/mailbox.h @@ -37,6 +37,10 @@ #define MAILBOX_HOSTBOX_BASE SRAM_INBOX_BASE #define MAILBOX_HOSTBOX_OFFSET SRAM_INBOX_OFFSET +/* IPC4 compact-header slots preceding each payload box */ +#define MAILBOX_DSPBOX_HDR_BASE SRAM_OUTBOX_HDR_BASE +#define MAILBOX_HOSTBOX_HDR_BASE SRAM_INBOX_HDR_BASE + #define MAILBOX_DEBUG_SIZE SRAM_DEBUG_SIZE #define MAILBOX_DEBUG_BASE SRAM_DEBUG_BASE #define MAILBOX_DEBUG_OFFSET SRAM_DEBUG_OFFSET @@ -61,6 +65,59 @@ static inline void mailbox_sw_reg_write(size_t offset, uint32_t src) *ptr = src; } +/* IPC4 SW registers (copier LLP); carved out of mailbox SRAM */ +#define MAILBOX_SW_REG_BASE SRAM_SW_REG_BASE +#define MAILBOX_SW_REG_SIZE SRAM_SW_REG_SIZE + +static inline void mailbox_sw_reg_write64(size_t offset, uint64_t src) +{ + volatile uint64_t *ptr; + + ptr = (volatile uint64_t *)(MAILBOX_SW_REG_BASE + offset); + *ptr = src; + dcache_writeback_region((__sparse_force void __sparse_cache *)(MAILBOX_SW_REG_BASE + + offset), + sizeof(src)); +} + +static inline uint32_t mailbox_sw_reg_read(size_t offset) +{ + volatile uint32_t *ptr; + + ptr = (volatile uint32_t *)(MAILBOX_SW_REG_BASE + offset); + dcache_invalidate_region((__sparse_force void __sparse_cache *)(MAILBOX_SW_REG_BASE + + offset), + sizeof(uint32_t)); + + return *ptr; +} + +static inline uint64_t mailbox_sw_reg_read64(size_t offset) +{ + volatile uint64_t *ptr; + + ptr = (volatile uint64_t *)(MAILBOX_SW_REG_BASE + offset); + dcache_invalidate_region((__sparse_force void __sparse_cache *)(MAILBOX_SW_REG_BASE + + offset), + sizeof(uint64_t)); + + return *ptr; +} + +static inline void mailbox_sw_regs_write(size_t offset, const void *src, size_t bytes) +{ + volatile uint8_t *dst = (volatile uint8_t *)(MAILBOX_SW_REG_BASE + offset); + const uint8_t *s = src; + size_t i; + + for (i = 0; i < bytes; i++) + dst[i] = s[i]; + + dcache_writeback_region((__sparse_force void __sparse_cache *)(MAILBOX_SW_REG_BASE + + offset), + bytes); +} + #endif /* __PLATFORM_LIB_MAILBOX_H__ */ #else diff --git a/src/platform/imx8m/include/platform/lib/memory.h b/src/platform/imx8m/include/platform/lib/memory.h index fd0034a7fba3..112a69492190 100644 --- a/src/platform/imx8m/include/platform/lib/memory.h +++ b/src/platform/imx8m/include/platform/lib/memory.h @@ -91,13 +91,23 @@ */ /* Mailbox configuration */ -#define SRAM_OUTBOX_BASE SDRAM1_BASE -#define SRAM_OUTBOX_SIZE 0x1000 -#define SRAM_OUTBOX_OFFSET 0 -#define SRAM_INBOX_BASE (SRAM_OUTBOX_BASE + SRAM_OUTBOX_SIZE) -#define SRAM_INBOX_SIZE 0x1000 -#define SRAM_INBOX_OFFSET SRAM_OUTBOX_SIZE +/* IPC4: 8-byte header slot at the start of each box, before the payload */ +#if CONFIG_IPC_MAJOR_4 +#define SRAM_IPC4_HDR_SIZE 8 +#else +#define SRAM_IPC4_HDR_SIZE 0 +#endif + +#define SRAM_OUTBOX_HDR_BASE SDRAM1_BASE +#define SRAM_OUTBOX_BASE (SRAM_OUTBOX_HDR_BASE + SRAM_IPC4_HDR_SIZE) +#define SRAM_OUTBOX_SIZE (0x1000 - SRAM_IPC4_HDR_SIZE) +#define SRAM_OUTBOX_OFFSET SRAM_IPC4_HDR_SIZE + +#define SRAM_INBOX_HDR_BASE (SRAM_OUTBOX_BASE + SRAM_OUTBOX_SIZE) +#define SRAM_INBOX_BASE (SRAM_INBOX_HDR_BASE + SRAM_IPC4_HDR_SIZE) +#define SRAM_INBOX_SIZE (0x1000 - SRAM_IPC4_HDR_SIZE) +#define SRAM_INBOX_OFFSET (SRAM_OUTBOX_OFFSET + SRAM_OUTBOX_SIZE + SRAM_IPC4_HDR_SIZE) #define SRAM_DEBUG_BASE (SRAM_INBOX_BASE + SRAM_INBOX_SIZE) #define SRAM_DEBUG_SIZE 0x800 @@ -107,6 +117,11 @@ #define SRAM_EXCEPT_SIZE 0x800 #define SRAM_EXCEPT_OFFSET (SRAM_DEBUG_OFFSET + SRAM_DEBUG_SIZE) +/* IPC4 SW registers (copier LLP) reuse the IPC3-only exception window */ +#define SRAM_SW_REG_BASE SRAM_EXCEPT_BASE +#define SRAM_SW_REG_SIZE SRAM_EXCEPT_SIZE +#define SRAM_SW_REG_OFFSET SRAM_EXCEPT_OFFSET + #define SRAM_STREAM_BASE (SRAM_EXCEPT_BASE + SRAM_EXCEPT_SIZE) #define SRAM_STREAM_SIZE 0x1000 #define SRAM_STREAM_OFFSET (SRAM_EXCEPT_OFFSET + SRAM_EXCEPT_SIZE) @@ -115,7 +130,7 @@ #define SRAM_TRACE_SIZE 0x1000 #define SRAM_TRACE_OFFSET (SRAM_STREAM_OFFSET + SRAM_STREAM_SIZE) -#define SOF_MAILBOX_SIZE (SRAM_INBOX_SIZE + SRAM_OUTBOX_SIZE \ +#define SOF_MAILBOX_SIZE (SRAM_INBOX_SIZE + SRAM_OUTBOX_SIZE + 2 * SRAM_IPC4_HDR_SIZE \ + SRAM_DEBUG_SIZE + SRAM_EXCEPT_SIZE \ + SRAM_STREAM_SIZE + SRAM_TRACE_SIZE) diff --git a/src/platform/imx8m/linker/module-section.ld b/src/platform/imx8m/linker/module-section.ld new file mode 100644 index 000000000000..87790c2e5ccb --- /dev/null +++ b/src/platform/imx8m/linker/module-section.ld @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright 2025 NXP + * + * Keep the IPC4 module manifest entries (struct sof_man_module_manifest, + * see platform.c) in the final ELF and load them with the image. The table + * serves two consumers: + * - rimage's i.MX ext-manifest v4 generator reads the ".module" section by + * name to emit the "$AE1" module table for the Linux IPC4 driver; + * - the firmware itself resolves IPC4 module IDs to component drivers from + * it at runtime (platform_ipc4_get_module()), since i.MX has no + * rimage/IMR manifest in memory like Intel platforms. + * Without an explicit KEEP the linker's --gc-sections would drop it. + */ +.module : ALIGN(4) +{ + KEEP(*(.module)) +} >sdram0 :sdram0_phdr diff --git a/src/platform/imx8m/platform.c b/src/platform/imx8m/platform.c index 46e60a7e8608..66797c2b10cb 100644 --- a/src/platform/imx8m/platform.c +++ b/src/platform/imx8m/platform.c @@ -36,6 +36,7 @@ struct sof; +#if !CONFIG_IPC_MAJOR_4 static const struct sof_ipc_fw_ready ready __section(".fw_ready") = { .hdr = { @@ -126,9 +127,13 @@ const struct ext_man_windows xsram_window }, }, }; +#endif /* !CONFIG_IPC_MAJOR_4 */ int platform_boot_complete(uint32_t boot_message) { +#if CONFIG_IPC_MAJOR_4 + return imx_ipc4_boot_complete(boot_message); +#else mailbox_dspbox_write(0, &ready, sizeof(ready)); /* now interrupt host to tell it we are done booting */ @@ -141,6 +146,7 @@ int platform_boot_complete(uint32_t boot_message) /* clock_set_freq(CLK_CPU, CLK_DEFAULT_CPU_HZ); */ return 0; +#endif } int platform_init(struct sof *sof) diff --git a/tools/rimage/config/imx8m_ipc4.toml b/tools/rimage/config/imx8m_ipc4.toml new file mode 100644 index 000000000000..82212eddc1ca --- /dev/null +++ b/tools/rimage/config/imx8m_ipc4.toml @@ -0,0 +1,20 @@ +version = [1, 1] # simple file write prefixed with an unsigned IPC4 ext-manifest + +[adsp] +name = "imx8m" + +[[adsp.mem_zone]] +type = "IRAM" +base = "0x3b6F8000" +size = "0x800" +host_offset = "0x10000" +[[adsp.mem_zone]] +type = "DRAM" +base = "0x3B6E8000" +size = "0x8000" +host_offset = "0x0" +[[adsp.mem_zone]] +type = "SRAM" +base = "0x92400000" +size = "0x800000" +host_offset = "0x0" diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 8da2dc47fdc5..0675057b45ef 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -350,6 +350,17 @@ if (CONFIG_SOC_MIMX8ML8_ADSP) ) set(PLATFORM "imx8m") + + # IPC4 rimage target; FORCE since board.cmake already set it + if (CONFIG_IPC_MAJOR_4) + set(RIMAGE_TARGET imx8m_ipc4 CACHE STRING "rimage target" FORCE) + set(CACHED_RIMAGE_TARGET imx8m_ipc4 CACHE STRING + "Selected rimage target" FORCE) + + # keep .module from --gc-sections; ROM_SECTIONS so it is loaded + zephyr_linker_sources(ROM_SECTIONS + ${sof_top_dir}/src/platform/imx8m/linker/module-section.ld) + endif() endif() if (CONFIG_SOC_MIMX8ML8_M7) From 6902bf0eeee86b30f6849e9e923cb405ebdbb454 Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Tue, 25 Aug 2026 17:28:07 +0300 Subject: [PATCH 13/18] platform: imx8: add IPC4 support for i.MX8QM and i.MX8QXP Add IPC4 support for the i.MX8QM/i.MX8QXP HiFi4 DSPs on top of the shared i.MX IPC4 transport, as for imx8m: - memory.h/mailbox.h: an 8-byte IPC4 header slot at the start of the inbox and outbox, and the SW register (LLP) window with its accessors, reusing the IPC3-only exception window. The IPC3 layout is unchanged. - platform.h/clk.h: skip the IPC3 fw_status box under IPC4; add CPU_LOWEST_FREQ_IDX and HW_CFG_VERSION for the IPC4 base FW. - platform.c: send FW_READY via imx_ipc4_boot_complete(). - keep the ".module" manifest table in a loaded section and select the imx8_ipc4 / imx8x_ipc4 rimage configs. - IPC4 board overlays and opt-in 'imx8-ipc4' / 'imx8x-ipc4' build targets. The default 'imx8' / 'imx8x' targets stay IPC3. Signed-off-by: Daniel Baluta --- app/boards/imx8qm_mek_mimx8qm6_adsp_ipc4.conf | 11 ++++ .../imx8qxp_mek_mimx8qx6_adsp_ipc4.conf | 11 ++++ scripts/xtensa-build-zephyr.py | 22 ++++++- src/platform/imx8/include/platform/lib/clk.h | 6 ++ .../imx8/include/platform/lib/mailbox.h | 58 +++++++++++++++++++ .../imx8/include/platform/lib/memory.h | 29 +++++++--- src/platform/imx8/include/platform/platform.h | 5 ++ src/platform/imx8/linker/module-section.ld | 24 ++++++++ src/platform/imx8/platform.c | 6 ++ tools/rimage/config/imx8_ipc4.toml | 20 +++++++ tools/rimage/config/imx8x_ipc4.toml | 20 +++++++ zephyr/CMakeLists.txt | 17 ++++++ 12 files changed, 221 insertions(+), 8 deletions(-) create mode 100644 app/boards/imx8qm_mek_mimx8qm6_adsp_ipc4.conf create mode 100644 app/boards/imx8qxp_mek_mimx8qx6_adsp_ipc4.conf create mode 100644 src/platform/imx8/linker/module-section.ld create mode 100644 tools/rimage/config/imx8_ipc4.toml create mode 100644 tools/rimage/config/imx8x_ipc4.toml diff --git a/app/boards/imx8qm_mek_mimx8qm6_adsp_ipc4.conf b/app/boards/imx8qm_mek_mimx8qm6_adsp_ipc4.conf new file mode 100644 index 000000000000..aa004398b0cb --- /dev/null +++ b/app/boards/imx8qm_mek_mimx8qm6_adsp_ipc4.conf @@ -0,0 +1,11 @@ +# IPC4 overlay for i.MX8QM +CONFIG_IPC_MAJOR_4=y + +# single core +CONFIG_CROSS_CORE_STREAM=n +CONFIG_COMP_PEAK_VOL=n + +# no IPC4 trace window on i.MX +CONFIG_TRACE=n + +# no SAI MCLK config here: codec MCLK comes from the SCU audio clock diff --git a/app/boards/imx8qxp_mek_mimx8qx6_adsp_ipc4.conf b/app/boards/imx8qxp_mek_mimx8qx6_adsp_ipc4.conf new file mode 100644 index 000000000000..6b1f5b4348b6 --- /dev/null +++ b/app/boards/imx8qxp_mek_mimx8qx6_adsp_ipc4.conf @@ -0,0 +1,11 @@ +# IPC4 overlay for i.MX8QXP +CONFIG_IPC_MAJOR_4=y + +# single core +CONFIG_CROSS_CORE_STREAM=n +CONFIG_COMP_PEAK_VOL=n + +# no IPC4 trace window on i.MX +CONFIG_TRACE=n + +# no SAI MCLK config here: codec MCLK comes from the SCU audio clock diff --git a/scripts/xtensa-build-zephyr.py b/scripts/xtensa-build-zephyr.py index 5eeffd6fd550..744c19f4cdd7 100755 --- a/scripts/xtensa-build-zephyr.py +++ b/scripts/xtensa-build-zephyr.py @@ -218,12 +218,32 @@ class PlatformConfig: "hifi4_nxp_v5_3_1_prod", RIMAGE_KEY = "key param ignored by imx8", ), + # i.MX8QM IPC4 build. Opt-in target; the default 'imx8' stays IPC3. + "imx8-ipc4" : PlatformConfig( + "imx", "imx8qm_mek/mimx8qm6/adsp", + f"RI-2023.11{xtensa_tools_version_postfix}", + "hifi4_nxp_v5_3_1_prod", + RIMAGE_KEY = "key param ignored by imx8", + ipc4 = True, + extra_conf = pathlib.Path(SOF_TOP, "app", "boards", + "imx8qm_mek_mimx8qm6_adsp_ipc4.conf") + ), "imx8x" : PlatformConfig( "imx", "imx8qxp_mek/mimx8qx6/adsp", f"RI-2023.11{xtensa_tools_version_postfix}", "hifi4_nxp_v5_3_1_prod", RIMAGE_KEY = "key param ignored by imx8x" ), + # i.MX8QXP IPC4 build. Opt-in target; the default 'imx8x' stays IPC3. + "imx8x-ipc4" : PlatformConfig( + "imx", "imx8qxp_mek/mimx8qx6/adsp", + f"RI-2023.11{xtensa_tools_version_postfix}", + "hifi4_nxp_v5_3_1_prod", + RIMAGE_KEY = "key param ignored by imx8x", + ipc4 = True, + extra_conf = pathlib.Path(SOF_TOP, "app", "boards", + "imx8qxp_mek_mimx8qx6_adsp_ipc4.conf") + ), "imx8m" : PlatformConfig( "imx", "imx8mp_evk/mimx8ml8/adsp", f"RI-2023.11{xtensa_tools_version_postfix}", @@ -1364,7 +1384,7 @@ def gzip_compress(fname, gzdst=None): # Don't run sof_ri_info and ignore silently .ri files that don't have one. RI_INFO_UNSUPPORTED = [] -RI_INFO_UNSUPPORTED += ['imx8', 'imx8x', 'imx8m', 'imx8m-ipc4', 'imx8m_cm7', 'imx8ulp', 'imx95'] +RI_INFO_UNSUPPORTED += ['imx8', 'imx8-ipc4', 'imx8x', 'imx8x-ipc4', 'imx8m', 'imx8m-ipc4', 'imx8m_cm7', 'imx8ulp', 'imx95', 'imx95-ipc4'] RI_INFO_UNSUPPORTED += ['rn', 'acp_6_0', 'acp_7_0', 'acp_7_x'] RI_INFO_UNSUPPORTED += ['mt8186', 'mt8188', 'mt8195', 'mt8196', 'mt8365'] RI_INFO_UNSUPPORTED += ['qemu_xtensa', 'qemu_xtensa_mmu', 'native_sim'] diff --git a/src/platform/imx8/include/platform/lib/clk.h b/src/platform/imx8/include/platform/lib/clk.h index 6d52972da9e5..0b1fbab379c4 100644 --- a/src/platform/imx8/include/platform/lib/clk.h +++ b/src/platform/imx8/include/platform/lib/clk.h @@ -18,6 +18,12 @@ struct sof; #define CPU_DEFAULT_IDX 0 +/* single fixed frequency, so lowest == default */ +#define CPU_LOWEST_FREQ_IDX CPU_DEFAULT_IDX + +/* i.MX is not a CAVS platform; report 0 (unspecified) */ +#define HW_CFG_VERSION 0 + #ifdef CONFIG_IMX8 #define CLK_DEFAULT_CPU_HZ 666000000 #define CLK_MAX_CPU_HZ 666000000 diff --git a/src/platform/imx8/include/platform/lib/mailbox.h b/src/platform/imx8/include/platform/lib/mailbox.h index 1b7498cf08d6..6bdb839bc657 100644 --- a/src/platform/imx8/include/platform/lib/mailbox.h +++ b/src/platform/imx8/include/platform/lib/mailbox.h @@ -10,6 +10,7 @@ #ifndef __PLATFORM_LIB_MAILBOX_H__ #define __PLATFORM_LIB_MAILBOX_H__ +#include #include #include #include @@ -37,6 +38,10 @@ #define MAILBOX_HOSTBOX_BASE SRAM_INBOX_BASE #define MAILBOX_HOSTBOX_OFFSET SRAM_INBOX_OFFSET +/* IPC4 compact-header slots preceding each payload box */ +#define MAILBOX_DSPBOX_HDR_BASE SRAM_OUTBOX_HDR_BASE +#define MAILBOX_HOSTBOX_HDR_BASE SRAM_INBOX_HDR_BASE + #define MAILBOX_DEBUG_SIZE SRAM_DEBUG_SIZE #define MAILBOX_DEBUG_BASE SRAM_DEBUG_BASE #define MAILBOX_DEBUG_OFFSET SRAM_DEBUG_OFFSET @@ -61,6 +66,59 @@ static inline void mailbox_sw_reg_write(size_t offset, uint32_t src) *ptr = src; } +/* IPC4 SW registers (copier LLP); carved out of mailbox SRAM */ +#define MAILBOX_SW_REG_BASE SRAM_SW_REG_BASE +#define MAILBOX_SW_REG_SIZE SRAM_SW_REG_SIZE + +static inline void mailbox_sw_reg_write64(size_t offset, uint64_t src) +{ + volatile uint64_t *ptr; + + ptr = (volatile uint64_t *)(MAILBOX_SW_REG_BASE + offset); + *ptr = src; + dcache_writeback_region((__sparse_force void __sparse_cache *)(MAILBOX_SW_REG_BASE + + offset), + sizeof(src)); +} + +static inline uint32_t mailbox_sw_reg_read(size_t offset) +{ + volatile uint32_t *ptr; + + ptr = (volatile uint32_t *)(MAILBOX_SW_REG_BASE + offset); + dcache_invalidate_region((__sparse_force void __sparse_cache *)(MAILBOX_SW_REG_BASE + + offset), + sizeof(uint32_t)); + + return *ptr; +} + +static inline uint64_t mailbox_sw_reg_read64(size_t offset) +{ + volatile uint64_t *ptr; + + ptr = (volatile uint64_t *)(MAILBOX_SW_REG_BASE + offset); + dcache_invalidate_region((__sparse_force void __sparse_cache *)(MAILBOX_SW_REG_BASE + + offset), + sizeof(uint64_t)); + + return *ptr; +} + +static inline void mailbox_sw_regs_write(size_t offset, const void *src, size_t bytes) +{ + volatile uint8_t *dst = (volatile uint8_t *)(MAILBOX_SW_REG_BASE + offset); + const uint8_t *s = src; + size_t i; + + for (i = 0; i < bytes; i++) + dst[i] = s[i]; + + dcache_writeback_region((__sparse_force void __sparse_cache *)(MAILBOX_SW_REG_BASE + + offset), + bytes); +} + #endif /* __PLATFORM_LIB_MAILBOX_H__ */ #else diff --git a/src/platform/imx8/include/platform/lib/memory.h b/src/platform/imx8/include/platform/lib/memory.h index 0388e2481c6b..cb54bc92dabd 100644 --- a/src/platform/imx8/include/platform/lib/memory.h +++ b/src/platform/imx8/include/platform/lib/memory.h @@ -73,13 +73,23 @@ */ /* Mailbox configuration */ -#define SRAM_OUTBOX_BASE SDRAM1_BASE -#define SRAM_OUTBOX_SIZE 0x1000 -#define SRAM_OUTBOX_OFFSET 0 -#define SRAM_INBOX_BASE (SRAM_OUTBOX_BASE + SRAM_OUTBOX_SIZE) -#define SRAM_INBOX_SIZE 0x1000 -#define SRAM_INBOX_OFFSET SRAM_OUTBOX_SIZE +/* IPC4: 8-byte header slot at the start of each box, before the payload */ +#if CONFIG_IPC_MAJOR_4 +#define SRAM_IPC4_HDR_SIZE 8 +#else +#define SRAM_IPC4_HDR_SIZE 0 +#endif + +#define SRAM_OUTBOX_HDR_BASE SDRAM1_BASE +#define SRAM_OUTBOX_BASE (SRAM_OUTBOX_HDR_BASE + SRAM_IPC4_HDR_SIZE) +#define SRAM_OUTBOX_SIZE (0x1000 - SRAM_IPC4_HDR_SIZE) +#define SRAM_OUTBOX_OFFSET SRAM_IPC4_HDR_SIZE + +#define SRAM_INBOX_HDR_BASE (SRAM_OUTBOX_BASE + SRAM_OUTBOX_SIZE) +#define SRAM_INBOX_BASE (SRAM_INBOX_HDR_BASE + SRAM_IPC4_HDR_SIZE) +#define SRAM_INBOX_SIZE (0x1000 - SRAM_IPC4_HDR_SIZE) +#define SRAM_INBOX_OFFSET (SRAM_OUTBOX_OFFSET + SRAM_OUTBOX_SIZE + SRAM_IPC4_HDR_SIZE) #define SRAM_DEBUG_BASE (SRAM_INBOX_BASE + SRAM_INBOX_SIZE) #define SRAM_DEBUG_SIZE 0x800 @@ -89,6 +99,11 @@ #define SRAM_EXCEPT_SIZE 0x800 #define SRAM_EXCEPT_OFFSET (SRAM_DEBUG_OFFSET + SRAM_DEBUG_SIZE) +/* IPC4 SW registers (copier LLP) reuse the IPC3-only exception window */ +#define SRAM_SW_REG_BASE SRAM_EXCEPT_BASE +#define SRAM_SW_REG_SIZE SRAM_EXCEPT_SIZE +#define SRAM_SW_REG_OFFSET SRAM_EXCEPT_OFFSET + #define SRAM_STREAM_BASE (SRAM_EXCEPT_BASE + SRAM_EXCEPT_SIZE) #define SRAM_STREAM_SIZE 0x1000 #define SRAM_STREAM_OFFSET (SRAM_EXCEPT_OFFSET + SRAM_EXCEPT_SIZE) @@ -97,7 +112,7 @@ #define SRAM_TRACE_SIZE 0x1000 #define SRAM_TRACE_OFFSET (SRAM_STREAM_OFFSET + SRAM_STREAM_SIZE) -#define SOF_MAILBOX_SIZE (SRAM_INBOX_SIZE + SRAM_OUTBOX_SIZE \ +#define SOF_MAILBOX_SIZE (SRAM_INBOX_SIZE + SRAM_OUTBOX_SIZE + 2 * SRAM_IPC4_HDR_SIZE \ + SRAM_DEBUG_SIZE + SRAM_EXCEPT_SIZE \ + SRAM_STREAM_SIZE + SRAM_TRACE_SIZE) diff --git a/src/platform/imx8/include/platform/platform.h b/src/platform/imx8/include/platform/platform.h index 8bc1cbdff495..6ffec48aeff0 100644 --- a/src/platform/imx8/include/platform/platform.h +++ b/src/platform/imx8/include/platform/platform.h @@ -53,11 +53,15 @@ struct timer; /* DSP default delay in cycles */ #define PLATFORM_DEFAULT_DELAY 12 +#if !CONFIG_IPC_MAJOR_4 +/* IPC4 defines SRAM_REG_FW_STATUS elsewhere; only IPC3 uses this fixed offset */ #define SRAM_REG_FW_STATUS 0x4 +#endif /* Platform defined panic code */ static inline void platform_panic(uint32_t p) { +#if !CONFIG_IPC_MAJOR_4 /* Store the error code in the debug box so the * application processor can pick it up. Takes up 4 bytes * from the debug box. @@ -66,6 +70,7 @@ static inline void platform_panic(uint32_t p) /* Notify application processor */ imx_mu_xcr_rmw(IMX_MU_VERSION, IMX_MU_GCR, IMX_MU_xCR_GIRn(IMX_MU_VERSION, 1), 0); +#endif } /** diff --git a/src/platform/imx8/linker/module-section.ld b/src/platform/imx8/linker/module-section.ld new file mode 100644 index 000000000000..0138a38fc33b --- /dev/null +++ b/src/platform/imx8/linker/module-section.ld @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright 2025 NXP + * + * Keep the IPC4 module manifest entries (struct sof_man_module_manifest, + * see platform.c) in the final ELF and load them with the image. The table + * serves two consumers: + * - rimage's i.MX ext-manifest v4 generator reads the ".module" section by + * name to emit the "$AE1" module table for the Linux IPC4 driver; + * - the firmware itself resolves IPC4 module IDs to component drivers from + * it at runtime (platform_ipc4_get_module()), since i.MX has no + * rimage/IMR manifest in memory like Intel platforms. + * Without an explicit KEEP the linker's --gc-sections would drop it. + */ +SECTION_PROLOGUE(.module,,) +{ + /* + * No trailing ALIGN() padding: rimage's IPC4 ext-manifest generator + * requires the ".module" section size to be an exact multiple of + * sizeof(struct sof_man_module_manifest), so the section must contain + * only the KEEP'd table entries. + */ + KEEP (*(.module)) +} GROUP_ROM_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) diff --git a/src/platform/imx8/platform.c b/src/platform/imx8/platform.c index b07039832c2b..6ec98727c579 100644 --- a/src/platform/imx8/platform.c +++ b/src/platform/imx8/platform.c @@ -18,6 +18,7 @@ struct sof; +#if !CONFIG_IPC_MAJOR_4 static const struct sof_ipc_fw_ready ready __section(".fw_ready") = { .hdr = { @@ -108,9 +109,13 @@ const struct ext_man_windows xsram_window }, } }; +#endif /* !CONFIG_IPC_MAJOR_4 */ int platform_boot_complete(uint32_t boot_message) { +#if CONFIG_IPC_MAJOR_4 + return imx_ipc4_boot_complete(boot_message); +#else mailbox_dspbox_write(0, &ready, sizeof(ready)); /* now interrupt host to tell it we are done booting */ @@ -123,6 +128,7 @@ int platform_boot_complete(uint32_t boot_message) /* clock_set_freq(CLK_CPU, CLK_DEFAULT_CPU_HZ); */ return 0; +#endif } int platform_init(struct sof *sof) diff --git a/tools/rimage/config/imx8_ipc4.toml b/tools/rimage/config/imx8_ipc4.toml new file mode 100644 index 000000000000..87eddd7eb4be --- /dev/null +++ b/tools/rimage/config/imx8_ipc4.toml @@ -0,0 +1,20 @@ +version = [1, 1] # simple file write prefixed with an unsigned IPC4 ext-manifest + +[adsp] +name = "imx8" + +[[adsp.mem_zone]] +type = "IRAM" +base = "0x596F8000" +size = "0x800" +host_offset = "0x10000" +[[adsp.mem_zone]] +type = "DRAM" +base = "0x596E8000" +size = "0x8000" +host_offset = "0x0" +[[adsp.mem_zone]] +type = "SRAM" +base = "0x92400000" +size = "0x800000" +host_offset = "0x0" diff --git a/tools/rimage/config/imx8x_ipc4.toml b/tools/rimage/config/imx8x_ipc4.toml new file mode 100644 index 000000000000..28a995baace0 --- /dev/null +++ b/tools/rimage/config/imx8x_ipc4.toml @@ -0,0 +1,20 @@ +version = [1, 1] # simple file write prefixed with an unsigned IPC4 ext-manifest + +[adsp] +name = "imx8x" + +[[adsp.mem_zone]] +type = "IRAM" +base = "0x596F8000" +size = "0x800" +host_offset = "0x10000" +[[adsp.mem_zone]] +type = "DRAM" +base = "0x596e8000" +size = "0x8000" +host_offset = "0x0" +[[adsp.mem_zone]] +type = "SRAM" +base = "0x92400000" +size = "0x800000" +host_offset = "0x0" diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 0675057b45ef..c7b49c0dbdf5 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -331,6 +331,23 @@ if (CONFIG_SOC_MIMX8QM6_ADSP OR CONFIG_SOC_MIMX8QX6_ADSP) ) set(PLATFORM "imx8") + + # IPC4 rimage target; FORCE since board.cmake already set it. + # imx8 (QM) and imx8x (QXP) use distinct targets. + if (CONFIG_IPC_MAJOR_4) + if (CONFIG_SOC_MIMX8QX6_ADSP) + set(rimage_ipc4_target imx8x_ipc4) + else() + set(rimage_ipc4_target imx8_ipc4) + endif() + set(RIMAGE_TARGET ${rimage_ipc4_target} CACHE STRING "rimage target" FORCE) + set(CACHED_RIMAGE_TARGET ${rimage_ipc4_target} CACHE STRING + "Selected rimage target" FORCE) + + # keep .module from --gc-sections + zephyr_linker_sources(DATA_SECTIONS + ${sof_top_dir}/src/platform/imx8/linker/module-section.ld) + endif() endif() if (CONFIG_SOC_MIMX8ML8_ADSP) From be36f8b5b9b5437aab6ef10c7146219714b9d35f Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Tue, 25 Aug 2026 17:27:14 +0300 Subject: [PATCH 14/18] platform: imx95: add IPC4 firmware support Add IPC4 support for the i.MX95 Cortex-M7 on top of the shared i.MX IPC4 transport: - mailbox.h: an 8-byte IPC4 header slot at the start of the inbox and outbox, and the SW register (LLP) window with its accessors. - platform.h/clk.h: the DMA trace buffer size, and CPU_LOWEST_FREQ_IDX and HW_CFG_VERSION for the IPC4 base FW. - platform.c: send FW_READY via imx_ipc4_boot_complete(). - keep the ".module" manifest table in a loaded section and select the imx95_ipc4 rimage config. - an IPC4 board overlay and an opt-in 'imx95-ipc4' build target. The default 'imx95' target stays IPC3. Signed-off-by: Daniel Baluta --- .../imx95_evk_mimx9596_m7_ddr_ipc4.conf | 9 ++ scripts/xtensa-build-zephyr.py | 8 ++ src/platform/imx95/include/platform/lib/clk.h | 6 ++ .../imx95/include/platform/lib/mailbox.h | 101 ++++++++++++++++-- .../imx95/include/platform/platform.h | 3 + src/platform/imx95/linker/module-section.ld | 24 +++++ src/platform/imx95/platform.c | 6 ++ tools/rimage/config/imx95_ipc4.toml | 9 ++ zephyr/CMakeLists.txt | 11 ++ 9 files changed, 168 insertions(+), 9 deletions(-) create mode 100644 app/boards/imx95_evk_mimx9596_m7_ddr_ipc4.conf create mode 100644 src/platform/imx95/linker/module-section.ld create mode 100644 tools/rimage/config/imx95_ipc4.toml diff --git a/app/boards/imx95_evk_mimx9596_m7_ddr_ipc4.conf b/app/boards/imx95_evk_mimx9596_m7_ddr_ipc4.conf new file mode 100644 index 000000000000..d2b5b154d073 --- /dev/null +++ b/app/boards/imx95_evk_mimx9596_m7_ddr_ipc4.conf @@ -0,0 +1,9 @@ +# IPC4 overlay for i.MX95 +CONFIG_IPC_MAJOR_4=y + +# single core +CONFIG_CROSS_CORE_STREAM=n +CONFIG_COMP_PEAK_VOL=n + +# no IPC4 trace window on i.MX +CONFIG_TRACE=n diff --git a/scripts/xtensa-build-zephyr.py b/scripts/xtensa-build-zephyr.py index 744c19f4cdd7..914183e62e6e 100755 --- a/scripts/xtensa-build-zephyr.py +++ b/scripts/xtensa-build-zephyr.py @@ -274,6 +274,14 @@ class PlatformConfig: "imx", "imx95_evk/mimx9596/m7/ddr", "", "", "", "" ), + # i.MX95 IPC4 build. Opt-in target; the default 'imx95' stays IPC3. + "imx95-ipc4" : PlatformConfig( + "imx", "imx95_evk/mimx9596/m7/ddr", + "", "", "", "", + ipc4 = True, + extra_conf = pathlib.Path(SOF_TOP, "app", "boards", + "imx95_evk_mimx9596_m7_ddr_ipc4.conf") + ), "qemu_xtensa" : PlatformConfig( "zephyr", "qemu_xtensa/dc233c", "", "", "zephyr" diff --git a/src/platform/imx95/include/platform/lib/clk.h b/src/platform/imx95/include/platform/lib/clk.h index ad8b193fab1d..4d434cac16e8 100644 --- a/src/platform/imx95/include/platform/lib/clk.h +++ b/src/platform/imx95/include/platform/lib/clk.h @@ -13,6 +13,12 @@ #define NUM_CPU_FREQ 1 #define NUM_CLOCKS 1 +/* single fixed frequency, so lowest == default */ +#define CPU_LOWEST_FREQ_IDX CPU_DEFAULT_IDX + +/* i.MX is not a CAVS platform; report 0 (unspecified) */ +#define HW_CFG_VERSION 0 + struct sof; void platform_clock_init(struct sof *sof); diff --git a/src/platform/imx95/include/platform/lib/mailbox.h b/src/platform/imx95/include/platform/lib/mailbox.h index ffdb39b34398..f24bda8de013 100644 --- a/src/platform/imx95/include/platform/lib/mailbox.h +++ b/src/platform/imx95/include/platform/lib/mailbox.h @@ -8,6 +8,10 @@ #ifndef __PLATFORM_LIB_MAILBOX_H__ #define __PLATFORM_LIB_MAILBOX_H__ +#include +#include +#include + /* i.MX95's mailbox is organized as follows: * * +---------------+-------------------------+ @@ -25,20 +29,99 @@ * be performed on them. */ -/* inbox */ -#define MAILBOX_HOSTBOX_SIZE 0x1000 -#define MAILBOX_HOSTBOX_BASE 0x86000000 -#define MAILBOX_HOSTBOX_OFFSET 0 +/* IPC4: 8-byte header slot at the start of each box, before the payload */ +#if CONFIG_IPC_MAJOR_4 +#define SRAM_IPC4_HDR_SIZE 8 +#else +#define SRAM_IPC4_HDR_SIZE 0 +#endif + +/* inbox (host -> DSP / hostbox) */ +#define MAILBOX_HOSTBOX_HDR_BASE 0x86000000 +#define MAILBOX_HOSTBOX_SIZE (0x1000 - SRAM_IPC4_HDR_SIZE) +#define MAILBOX_HOSTBOX_BASE (0x86000000 + SRAM_IPC4_HDR_SIZE) +#define MAILBOX_HOSTBOX_OFFSET SRAM_IPC4_HDR_SIZE -/* outbox */ -#define MAILBOX_DSPBOX_SIZE 0x1000 -#define MAILBOX_DSPBOX_BASE 0x86001000 -#define MAILBOX_DSPBOX_OFFSET (MAILBOX_HOSTBOX_OFFSET + MAILBOX_HOSTBOX_SIZE) +/* outbox (DSP -> host / dspbox) */ +#define MAILBOX_DSPBOX_HDR_BASE 0x86001000 +#define MAILBOX_DSPBOX_SIZE (0x1000 - SRAM_IPC4_HDR_SIZE) +#define MAILBOX_DSPBOX_BASE (0x86001000 + SRAM_IPC4_HDR_SIZE) +#define MAILBOX_DSPBOX_OFFSET (0x1000 + SRAM_IPC4_HDR_SIZE) /* stream */ #define MAILBOX_STREAM_SIZE 0x1000 #define MAILBOX_STREAM_BASE 0x86002000 -#define MAILBOX_STREAM_OFFSET (MAILBOX_DSPBOX_OFFSET + MAILBOX_DSPBOX_SIZE) +#define MAILBOX_STREAM_OFFSET 0x2000 + +/* debug */ +#define MAILBOX_DEBUG_SIZE 0x800 +#define MAILBOX_DEBUG_BASE 0x86003000 +#define MAILBOX_DEBUG_OFFSET 0x3000 + +/* IPC4 SW registers (copier LLP); carved out of mailbox SRAM */ +#define MAILBOX_SW_REG_SIZE 0x800 +#define MAILBOX_SW_REG_BASE 0x86003800 +#define MAILBOX_SW_REG_OFFSET 0x3800 + +static inline void mailbox_sw_reg_write(size_t offset, uint32_t src) +{ + volatile uint32_t *ptr; + + ptr = (volatile uint32_t *)(MAILBOX_SW_REG_BASE + offset); + *ptr = src; + dcache_writeback_region((__sparse_force void __sparse_cache *)(MAILBOX_SW_REG_BASE + + offset), + sizeof(src)); +} + +static inline void mailbox_sw_reg_write64(size_t offset, uint64_t src) +{ + volatile uint64_t *ptr; + + ptr = (volatile uint64_t *)(MAILBOX_SW_REG_BASE + offset); + *ptr = src; + dcache_writeback_region((__sparse_force void __sparse_cache *)(MAILBOX_SW_REG_BASE + + offset), + sizeof(src)); +} + +static inline uint32_t mailbox_sw_reg_read(size_t offset) +{ + volatile uint32_t *ptr; + + ptr = (volatile uint32_t *)(MAILBOX_SW_REG_BASE + offset); + dcache_invalidate_region((__sparse_force void __sparse_cache *)(MAILBOX_SW_REG_BASE + + offset), + sizeof(uint32_t)); + + return *ptr; +} + +static inline uint64_t mailbox_sw_reg_read64(size_t offset) +{ + volatile uint64_t *ptr; + + ptr = (volatile uint64_t *)(MAILBOX_SW_REG_BASE + offset); + dcache_invalidate_region((__sparse_force void __sparse_cache *)(MAILBOX_SW_REG_BASE + + offset), + sizeof(uint64_t)); + + return *ptr; +} + +static inline void mailbox_sw_regs_write(size_t offset, const void *src, size_t bytes) +{ + volatile uint8_t *dst = (volatile uint8_t *)(MAILBOX_SW_REG_BASE + offset); + const uint8_t *s = src; + size_t i; + + for (i = 0; i < bytes; i++) + dst[i] = s[i]; + + dcache_writeback_region((__sparse_force void __sparse_cache *)(MAILBOX_SW_REG_BASE + + offset), + bytes); +} #endif /* __PLATFORM_LIB_MAILBOX_H__ */ diff --git a/src/platform/imx95/include/platform/platform.h b/src/platform/imx95/include/platform/platform.h index 91c4f00cd3fd..7538e9b15e44 100644 --- a/src/platform/imx95/include/platform/platform.h +++ b/src/platform/imx95/include/platform/platform.h @@ -13,6 +13,9 @@ #define HOST_PAGE_SIZE 4096 +/* local buffer size of DMA tracing (also used by the IPC4 trace-xfer helper) */ +#define DMA_TRACE_LOCAL_SIZE HOST_PAGE_SIZE + #define PLATFORM_PAGE_TABLE_SIZE 256 /* WAKEUP domain MU7 side B */ diff --git a/src/platform/imx95/linker/module-section.ld b/src/platform/imx95/linker/module-section.ld new file mode 100644 index 000000000000..0138a38fc33b --- /dev/null +++ b/src/platform/imx95/linker/module-section.ld @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright 2025 NXP + * + * Keep the IPC4 module manifest entries (struct sof_man_module_manifest, + * see platform.c) in the final ELF and load them with the image. The table + * serves two consumers: + * - rimage's i.MX ext-manifest v4 generator reads the ".module" section by + * name to emit the "$AE1" module table for the Linux IPC4 driver; + * - the firmware itself resolves IPC4 module IDs to component drivers from + * it at runtime (platform_ipc4_get_module()), since i.MX has no + * rimage/IMR manifest in memory like Intel platforms. + * Without an explicit KEEP the linker's --gc-sections would drop it. + */ +SECTION_PROLOGUE(.module,,) +{ + /* + * No trailing ALIGN() padding: rimage's IPC4 ext-manifest generator + * requires the ".module" section size to be an exact multiple of + * sizeof(struct sof_man_module_manifest), so the section must contain + * only the KEEP'd table entries. + */ + KEEP (*(.module)) +} GROUP_ROM_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) diff --git a/src/platform/imx95/platform.c b/src/platform/imx95/platform.c index b7d4bed96b24..a13142c682c8 100644 --- a/src/platform/imx95/platform.c +++ b/src/platform/imx95/platform.c @@ -18,6 +18,7 @@ #include #include +#if !CONFIG_IPC_MAJOR_4 static const struct sof_ipc_fw_ready ready = { .hdr = { .cmd = SOF_IPC_FW_READY, @@ -70,15 +71,20 @@ const struct ext_man_windows windows }, }, }; +#endif /* !CONFIG_IPC_MAJOR_4 */ int platform_boot_complete(uint32_t boot_message) { +#if CONFIG_IPC_MAJOR_4 + return imx_ipc4_boot_complete(boot_message); +#else mailbox_dspbox_write(0, &ready, sizeof(ready)); imx_mu_xcr_rmw(IMX_MU_VERSION, IMX_MU_GCR, IMX_MU_xCR_GIRn(IMX_MU_VERSION, 1), 0); return 0; +#endif } int platform_context_save(struct sof *sof) diff --git a/tools/rimage/config/imx95_ipc4.toml b/tools/rimage/config/imx95_ipc4.toml new file mode 100644 index 000000000000..83fbf2f8b1a1 --- /dev/null +++ b/tools/rimage/config/imx95_ipc4.toml @@ -0,0 +1,9 @@ +version = [1, 1] # simple file write prefixed with an unsigned IPC4 ext-manifest + +[adsp] +name = "imx95" + +[[adsp.mem_zone]] +type = "SRAM" +base = "0x80000000" +size = "0x100000" diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index c7b49c0dbdf5..9e90374a652c 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -475,6 +475,17 @@ if (CONFIG_SOC_MIMX9596_M7) DEPENDS ${CMAKE_BINARY_DIR}/zephyr/${KERNEL_ELF_NAME}) board_set_rimage_target(imx95) + + # IPC4 rimage target; FORCE since board.cmake already set it + if (CONFIG_IPC_MAJOR_4) + set(RIMAGE_TARGET imx95_ipc4 CACHE STRING "rimage target" FORCE) + set(CACHED_RIMAGE_TARGET imx95_ipc4 CACHE STRING + "Selected rimage target" FORCE) + + # keep .module from --gc-sections + zephyr_linker_sources(DATA_SECTIONS + ${sof_top_dir}/src/platform/imx95/linker/module-section.ld) + endif() endif() # AMD RMB platforms From cd9fdb50204bd601fb14b1f4470346af85cb9ed0 Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Tue, 25 Aug 2026 17:11:13 +0300 Subject: [PATCH 15/18] platform: imx8ulp: add IPC4 support Add IPC4 support for the i.MX8ULP HiFi4 DSP on top of the shared i.MX IPC4 transport, as for imx8/imx8m: - memory.h/mailbox.h: an 8-byte IPC4 header slot at the start of the inbox and outbox, and the SW register (LLP) window with its accessors, reusing the IPC3-only exception window. i.MX8ULP has a larger 0x2800 debug window, so the SW register window is at offset 0x4800; the kernel imx8ulp_chip_info must match it. - platform.h/clk.h: skip the IPC3 fw_status box under IPC4; add CPU_LOWEST_FREQ_IDX and HW_CFG_VERSION for the IPC4 base FW. - platform.c: send FW_READY via imx_ipc4_boot_complete(). - keep the ".module" manifest table in a loaded section and select the imx8ulp_ipc4 rimage config. - an IPC4 board overlay and an opt-in 'imx8ulp-ipc4' build target. The default 'imx8ulp' target stays IPC3. Signed-off-by: Daniel Baluta --- .../imx8ulp_evk_mimx8ud7_adsp_ipc4.conf | 9 +++ scripts/xtensa-build-zephyr.py | 12 +++- .../imx8ulp/include/platform/lib/clk.h | 6 ++ .../imx8ulp/include/platform/lib/mailbox.h | 58 +++++++++++++++++++ .../imx8ulp/include/platform/lib/memory.h | 32 +++++++--- .../imx8ulp/include/platform/platform.h | 5 ++ src/platform/imx8ulp/linker/module-section.ld | 24 ++++++++ src/platform/imx8ulp/platform.c | 6 ++ tools/rimage/config/imx8ulp_ipc4.toml | 20 +++++++ zephyr/CMakeLists.txt | 11 ++++ 10 files changed, 175 insertions(+), 8 deletions(-) create mode 100644 app/boards/imx8ulp_evk_mimx8ud7_adsp_ipc4.conf create mode 100644 src/platform/imx8ulp/linker/module-section.ld create mode 100644 tools/rimage/config/imx8ulp_ipc4.toml diff --git a/app/boards/imx8ulp_evk_mimx8ud7_adsp_ipc4.conf b/app/boards/imx8ulp_evk_mimx8ud7_adsp_ipc4.conf new file mode 100644 index 000000000000..c01b516fec04 --- /dev/null +++ b/app/boards/imx8ulp_evk_mimx8ud7_adsp_ipc4.conf @@ -0,0 +1,9 @@ +# IPC4 overlay for i.MX8ULP +CONFIG_IPC_MAJOR_4=y + +# single core +CONFIG_CROSS_CORE_STREAM=n +CONFIG_COMP_PEAK_VOL=n + +# no IPC4 trace window on i.MX +CONFIG_TRACE=n diff --git a/scripts/xtensa-build-zephyr.py b/scripts/xtensa-build-zephyr.py index 914183e62e6e..423f2e0a60fa 100755 --- a/scripts/xtensa-build-zephyr.py +++ b/scripts/xtensa-build-zephyr.py @@ -270,6 +270,16 @@ class PlatformConfig: "hifi4_nxp2_s7_v2_1a_prod", RIMAGE_KEY = "key param ignored by imx8ulp" ), + # i.MX8ULP IPC4 build. Opt-in target; the default 'imx8ulp' stays IPC3. + "imx8ulp-ipc4" : PlatformConfig( + "imx", "imx8ulp_evk/mimx8ud7/adsp", + f"RI-2023.11{xtensa_tools_version_postfix}", + "hifi4_nxp2_s7_v2_1a_prod", + RIMAGE_KEY = "key param ignored by imx8ulp", + ipc4 = True, + extra_conf = pathlib.Path(SOF_TOP, "app", "boards", + "imx8ulp_evk_mimx8ud7_adsp_ipc4.conf") + ), "imx95" : PlatformConfig( "imx", "imx95_evk/mimx9596/m7/ddr", "", "", "", "" @@ -1392,7 +1402,7 @@ def gzip_compress(fname, gzdst=None): # Don't run sof_ri_info and ignore silently .ri files that don't have one. RI_INFO_UNSUPPORTED = [] -RI_INFO_UNSUPPORTED += ['imx8', 'imx8-ipc4', 'imx8x', 'imx8x-ipc4', 'imx8m', 'imx8m-ipc4', 'imx8m_cm7', 'imx8ulp', 'imx95', 'imx95-ipc4'] +RI_INFO_UNSUPPORTED += ['imx8', 'imx8-ipc4', 'imx8x', 'imx8x-ipc4', 'imx8m', 'imx8m-ipc4', 'imx8m_cm7', 'imx8ulp', 'imx8ulp-ipc4', 'imx95', 'imx95-ipc4'] RI_INFO_UNSUPPORTED += ['rn', 'acp_6_0', 'acp_7_0', 'acp_7_x'] RI_INFO_UNSUPPORTED += ['mt8186', 'mt8188', 'mt8195', 'mt8196', 'mt8365'] RI_INFO_UNSUPPORTED += ['qemu_xtensa', 'qemu_xtensa_mmu', 'native_sim'] diff --git a/src/platform/imx8ulp/include/platform/lib/clk.h b/src/platform/imx8ulp/include/platform/lib/clk.h index fbbc34bf812b..4a20394f03e0 100644 --- a/src/platform/imx8ulp/include/platform/lib/clk.h +++ b/src/platform/imx8ulp/include/platform/lib/clk.h @@ -18,6 +18,12 @@ struct sof; #define CPU_DEFAULT_IDX 0 +/* single fixed frequency, so lowest == default */ +#define CPU_LOWEST_FREQ_IDX CPU_DEFAULT_IDX + +/* i.MX is not a CAVS platform; report 0 (unspecified) */ +#define HW_CFG_VERSION 0 + #define CLK_DEFAULT_CPU_HZ 528000000 #define CLK_MAX_CPU_HZ 600000000 diff --git a/src/platform/imx8ulp/include/platform/lib/mailbox.h b/src/platform/imx8ulp/include/platform/lib/mailbox.h index f06560ba41c1..e41df353bbe6 100644 --- a/src/platform/imx8ulp/include/platform/lib/mailbox.h +++ b/src/platform/imx8ulp/include/platform/lib/mailbox.h @@ -10,6 +10,7 @@ #ifndef __PLATFORM_LIB_MAILBOX_H__ #define __PLATFORM_LIB_MAILBOX_H__ +#include #include #include #include @@ -37,6 +38,10 @@ #define MAILBOX_HOSTBOX_BASE SRAM_INBOX_BASE #define MAILBOX_HOSTBOX_OFFSET SRAM_INBOX_OFFSET +/* IPC4 compact-header slots preceding each payload box */ +#define MAILBOX_DSPBOX_HDR_BASE SRAM_OUTBOX_HDR_BASE +#define MAILBOX_HOSTBOX_HDR_BASE SRAM_INBOX_HDR_BASE + #define MAILBOX_DEBUG_SIZE SRAM_DEBUG_SIZE #define MAILBOX_DEBUG_BASE SRAM_DEBUG_BASE #define MAILBOX_DEBUG_OFFSET SRAM_DEBUG_OFFSET @@ -61,6 +66,59 @@ static inline void mailbox_sw_reg_write(size_t offset, uint32_t src) *ptr = src; } +/* IPC4 SW registers (copier LLP); carved out of mailbox SRAM */ +#define MAILBOX_SW_REG_BASE SRAM_SW_REG_BASE +#define MAILBOX_SW_REG_SIZE SRAM_SW_REG_SIZE + +static inline void mailbox_sw_reg_write64(size_t offset, uint64_t src) +{ + volatile uint64_t *ptr; + + ptr = (volatile uint64_t *)(MAILBOX_SW_REG_BASE + offset); + *ptr = src; + dcache_writeback_region((__sparse_force void __sparse_cache *)(MAILBOX_SW_REG_BASE + + offset), + sizeof(src)); +} + +static inline uint32_t mailbox_sw_reg_read(size_t offset) +{ + volatile uint32_t *ptr; + + ptr = (volatile uint32_t *)(MAILBOX_SW_REG_BASE + offset); + dcache_invalidate_region((__sparse_force void __sparse_cache *)(MAILBOX_SW_REG_BASE + + offset), + sizeof(uint32_t)); + + return *ptr; +} + +static inline uint64_t mailbox_sw_reg_read64(size_t offset) +{ + volatile uint64_t *ptr; + + ptr = (volatile uint64_t *)(MAILBOX_SW_REG_BASE + offset); + dcache_invalidate_region((__sparse_force void __sparse_cache *)(MAILBOX_SW_REG_BASE + + offset), + sizeof(uint64_t)); + + return *ptr; +} + +static inline void mailbox_sw_regs_write(size_t offset, const void *src, size_t bytes) +{ + volatile uint8_t *dst = (volatile uint8_t *)(MAILBOX_SW_REG_BASE + offset); + const uint8_t *s = src; + size_t i; + + for (i = 0; i < bytes; i++) + dst[i] = s[i]; + + dcache_writeback_region((__sparse_force void __sparse_cache *)(MAILBOX_SW_REG_BASE + + offset), + bytes); +} + #endif /* __PLATFORM_LIB_MAILBOX_H__ */ #else diff --git a/src/platform/imx8ulp/include/platform/lib/memory.h b/src/platform/imx8ulp/include/platform/lib/memory.h index e8c18b00c58c..a080c42c90fa 100644 --- a/src/platform/imx8ulp/include/platform/lib/memory.h +++ b/src/platform/imx8ulp/include/platform/lib/memory.h @@ -75,13 +75,23 @@ */ /* Mailbox configuration */ -#define SRAM_OUTBOX_BASE SDRAM1_BASE -#define SRAM_OUTBOX_SIZE 0x1000 -#define SRAM_OUTBOX_OFFSET 0 -#define SRAM_INBOX_BASE (SRAM_OUTBOX_BASE + SRAM_OUTBOX_SIZE) -#define SRAM_INBOX_SIZE 0x1000 -#define SRAM_INBOX_OFFSET SRAM_OUTBOX_SIZE +/* IPC4: 8-byte header slot at the start of each box, before the payload */ +#if CONFIG_IPC_MAJOR_4 +#define SRAM_IPC4_HDR_SIZE 8 +#else +#define SRAM_IPC4_HDR_SIZE 0 +#endif + +#define SRAM_OUTBOX_HDR_BASE SDRAM1_BASE +#define SRAM_OUTBOX_BASE (SRAM_OUTBOX_HDR_BASE + SRAM_IPC4_HDR_SIZE) +#define SRAM_OUTBOX_SIZE (0x1000 - SRAM_IPC4_HDR_SIZE) +#define SRAM_OUTBOX_OFFSET SRAM_IPC4_HDR_SIZE + +#define SRAM_INBOX_HDR_BASE (SRAM_OUTBOX_BASE + SRAM_OUTBOX_SIZE) +#define SRAM_INBOX_BASE (SRAM_INBOX_HDR_BASE + SRAM_IPC4_HDR_SIZE) +#define SRAM_INBOX_SIZE (0x1000 - SRAM_IPC4_HDR_SIZE) +#define SRAM_INBOX_OFFSET (SRAM_OUTBOX_OFFSET + SRAM_OUTBOX_SIZE + SRAM_IPC4_HDR_SIZE) #define SRAM_DEBUG_BASE (SRAM_INBOX_BASE + SRAM_INBOX_SIZE) #define SRAM_DEBUG_SIZE 0x2800 @@ -91,6 +101,14 @@ #define SRAM_EXCEPT_SIZE 0x800 #define SRAM_EXCEPT_OFFSET (SRAM_DEBUG_OFFSET + SRAM_DEBUG_SIZE) +/* + * IPC4 SW registers (copier LLP) reuse the IPC3-only exception window, at + * offset 0x4800; must match the kernel imx8ulp ipc4_fw_regs_offset. + */ +#define SRAM_SW_REG_BASE SRAM_EXCEPT_BASE +#define SRAM_SW_REG_SIZE SRAM_EXCEPT_SIZE +#define SRAM_SW_REG_OFFSET SRAM_EXCEPT_OFFSET + #define SRAM_STREAM_BASE (SRAM_EXCEPT_BASE + SRAM_EXCEPT_SIZE) #define SRAM_STREAM_SIZE 0x1000 #define SRAM_STREAM_OFFSET (SRAM_EXCEPT_OFFSET + SRAM_EXCEPT_SIZE) @@ -99,7 +117,7 @@ #define SRAM_TRACE_SIZE 0x1000 #define SRAM_TRACE_OFFSET (SRAM_STREAM_OFFSET + SRAM_STREAM_SIZE) -#define SOF_MAILBOX_SIZE (SRAM_INBOX_SIZE + SRAM_OUTBOX_SIZE \ +#define SOF_MAILBOX_SIZE (SRAM_INBOX_SIZE + SRAM_OUTBOX_SIZE + 2 * SRAM_IPC4_HDR_SIZE \ + SRAM_DEBUG_SIZE + SRAM_EXCEPT_SIZE \ + SRAM_STREAM_SIZE + SRAM_TRACE_SIZE) diff --git a/src/platform/imx8ulp/include/platform/platform.h b/src/platform/imx8ulp/include/platform/platform.h index f2f89a5632d8..4d1f00ed92b0 100644 --- a/src/platform/imx8ulp/include/platform/platform.h +++ b/src/platform/imx8ulp/include/platform/platform.h @@ -53,11 +53,15 @@ struct timer; /* DSP default delay in cycles */ #define PLATFORM_DEFAULT_DELAY 12 +#if !CONFIG_IPC_MAJOR_4 +/* IPC4 defines SRAM_REG_FW_STATUS elsewhere; only IPC3 uses this fixed offset */ #define SRAM_REG_FW_STATUS 0x4 +#endif /* Platform defined panic code */ static inline void platform_panic(uint32_t p) { +#if !CONFIG_IPC_MAJOR_4 /* Store the error code in the debug box so the * application processor can pick it up. Takes up 4 bytes * from the debug box. @@ -66,6 +70,7 @@ static inline void platform_panic(uint32_t p) /* Notify application processor */ imx_mu_xcr_rmw(IMX_MU_VERSION, IMX_MU_GCR, IMX_MU_xCR_GIRn(IMX_MU_VERSION, 1), 0); +#endif } /** diff --git a/src/platform/imx8ulp/linker/module-section.ld b/src/platform/imx8ulp/linker/module-section.ld new file mode 100644 index 000000000000..0138a38fc33b --- /dev/null +++ b/src/platform/imx8ulp/linker/module-section.ld @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright 2025 NXP + * + * Keep the IPC4 module manifest entries (struct sof_man_module_manifest, + * see platform.c) in the final ELF and load them with the image. The table + * serves two consumers: + * - rimage's i.MX ext-manifest v4 generator reads the ".module" section by + * name to emit the "$AE1" module table for the Linux IPC4 driver; + * - the firmware itself resolves IPC4 module IDs to component drivers from + * it at runtime (platform_ipc4_get_module()), since i.MX has no + * rimage/IMR manifest in memory like Intel platforms. + * Without an explicit KEEP the linker's --gc-sections would drop it. + */ +SECTION_PROLOGUE(.module,,) +{ + /* + * No trailing ALIGN() padding: rimage's IPC4 ext-manifest generator + * requires the ".module" section size to be an exact multiple of + * sizeof(struct sof_man_module_manifest), so the section must contain + * only the KEEP'd table entries. + */ + KEEP (*(.module)) +} GROUP_ROM_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) diff --git a/src/platform/imx8ulp/platform.c b/src/platform/imx8ulp/platform.c index 88e6dfb8d56d..7cb49363841e 100644 --- a/src/platform/imx8ulp/platform.c +++ b/src/platform/imx8ulp/platform.c @@ -39,6 +39,7 @@ struct sof; +#if !CONFIG_IPC_MAJOR_4 static const struct sof_ipc_fw_ready ready __section(".fw_ready") = { .hdr = { @@ -125,9 +126,13 @@ const struct ext_man_windows xsram_window }, } }; +#endif /* !CONFIG_IPC_MAJOR_4 */ int platform_boot_complete(uint32_t boot_message) { +#if CONFIG_IPC_MAJOR_4 + return imx_ipc4_boot_complete(boot_message); +#else mailbox_dspbox_write(0, &ready, sizeof(ready)); /* now interrupt host to tell it we are done booting */ @@ -140,6 +145,7 @@ int platform_boot_complete(uint32_t boot_message) /* clock_set_freq(CLK_CPU, CLK_DEFAULT_CPU_HZ); */ return 0; +#endif } int platform_init(struct sof *sof) diff --git a/tools/rimage/config/imx8ulp_ipc4.toml b/tools/rimage/config/imx8ulp_ipc4.toml new file mode 100644 index 000000000000..792cd21e4b68 --- /dev/null +++ b/tools/rimage/config/imx8ulp_ipc4.toml @@ -0,0 +1,20 @@ +version = [1, 1] # simple file write prefixed with an unsigned IPC4 ext-manifest + +[adsp] +name = "imx8ulp" + +[[adsp.mem_zone]] +type = "IRAM" +base = "0x21170000" +size = "0x10000" +host_offset = "0x0" +[[adsp.mem_zone]] +type = "DRAM" +base = "0x21180000" +size = "0x10000" +host_offset = "0x10000" +[[adsp.mem_zone]] +type = "SRAM" +base = "0x1a000000" +size = "0x800000" +host_offset = "0x0" diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 9e90374a652c..92c8afc2f73b 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -425,6 +425,17 @@ if (CONFIG_SOC_MIMX8UD7_ADSP) ) set(PLATFORM "imx8ulp") + + # IPC4 rimage target; FORCE since board.cmake already set it + if (CONFIG_IPC_MAJOR_4) + set(RIMAGE_TARGET imx8ulp_ipc4 CACHE STRING "rimage target" FORCE) + set(CACHED_RIMAGE_TARGET imx8ulp_ipc4 CACHE STRING + "Selected rimage target" FORCE) + + # keep .module from --gc-sections + zephyr_linker_sources(DATA_SECTIONS + ${sof_top_dir}/src/platform/imx8ulp/linker/module-section.ld) + endif() endif() if (CONFIG_SOC_MIMX9352_A55) From 1048ad9a7257dfe3817a671231f5a1d7e453589a Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Tue, 25 Aug 2026 17:12:04 +0300 Subject: [PATCH 16/18] topology2: imx8mp: add wm8960 IPC4 topologies and SAI hw_config Add the IPC4 WM8960 topologies for the i.MX8MP EVK (WM8960 on SAI3), playback and duplex, plus the shared i.MX topology infrastructure: the hw_config_imx SAI helper and the dai-copier-io pipeline include. Register the sof-imx8mp-wm8960[-duplex] targets. The imx8-wm8960 sources are reused by the i.MX8QM/8QXP targets on SAI1. Signed-off-by: Daniel Baluta --- .../topology2/imx8-wm8960-duplex.conf | 209 ++++++++++++++++++ tools/topology/topology2/imx8-wm8960.conf | 76 ++++--- .../topology2/include/dais/hw_config_imx.conf | 84 +++++++ .../include/pipelines/dai-copier-io.conf | 113 ++++++++++ .../production/tplg-targets-imx8.cmake | 4 +- 5 files changed, 457 insertions(+), 29 deletions(-) create mode 100644 tools/topology/topology2/imx8-wm8960-duplex.conf create mode 100644 tools/topology/topology2/include/dais/hw_config_imx.conf create mode 100644 tools/topology/topology2/include/pipelines/dai-copier-io.conf diff --git a/tools/topology/topology2/imx8-wm8960-duplex.conf b/tools/topology/topology2/imx8-wm8960-duplex.conf new file mode 100644 index 000000000000..ab7e4db1b3c0 --- /dev/null +++ b/tools/topology/topology2/imx8-wm8960-duplex.conf @@ -0,0 +1,209 @@ +## +## IPC4 topology for i.MX8MP with WM8960 codec - duplex (playback + capture) +## +## Pipeline graph: +## +## Playback: +## host-copier.0.playback -> gain.1.1 -> dai-copier.SAI.saiN-codec.playback +## +## Capture: +## dai-copier.SAI.saiN-codec.capture -> gain.2.1 -> host-copier.1.capture +## + +# Include paths + + + + + + + +# Class definitions + + + + + + + + + + + + + + + + + + + + +Define { + # Playback PCM + HEADSET_PLAYBACK_PCM_STREAM_NAME "Headset Playback" + # Capture PCM + HEADSET_CAPTURE_PCM_STREAM_NAME "Headset Capture" + HEADSET_PCM_NAME "Headset" + HEADSET_PCM_ID 0 +} + +# +# Playback pipeline: host-copier.0.playback -> gain.1.1 +# +Object.Pipeline { + gain-playback [ + { + index 1 + + Object.Widget.host-copier.1 { + stream_name $HEADSET_PLAYBACK_PCM_STREAM_NAME + pcm_id $HEADSET_PCM_ID + } + + Object.Widget.gain.1 { + curve_type "windows_fade" + Object.Control.mixer.1 { + name 'PGA Playback Volume' + } + } + + time_domain "dma" + format "s32le" + num_input_audio_formats 3 + num_output_audio_formats 1 + } + ] + + # + # Capture pipeline: gain.2.1 -> host-copier.1.capture + # + gain-capture [ + { + index 2 + + Object.Widget.host-copier.1 { + stream_name $HEADSET_CAPTURE_PCM_STREAM_NAME + pcm_id $HEADSET_PCM_ID + } + + Object.Widget.gain.1 { + curve_type "windows_fade" + Object.Control.mixer.1 { + name 'Capture Volume' + } + } + + time_domain "dma" + format "s32le" + num_input_audio_formats 1 + num_output_audio_formats 2 + } + ] +} + +# +# DAI pipelines - one SAI DAI for playback, one for capture. Each dai-copier +# needs its own pipeline/scheduler (dai-copier-io-playback/-capture) so +# sof_complete() actually visits it and populates the widget's IPC struct - +# a bare Object.Widget.dai-copier has no pipeline_id and is silently +# skipped. +# +Object.Pipeline.dai-copier-io-playback [ + { + index 3 + + Object.Widget.dai-copier.1 { + dai_index $SAI_DAI_INDEX + dai_type "SAI" + copier_type "SAI" + stream_name $STREAM_CODEC_NAME + node_type $SAI_LINK_OUTPUT_CLASS + } + } +] +Object.Pipeline.dai-copier-io-capture [ + { + index 4 + + Object.Widget.dai-copier.1 { + dai_index $SAI_DAI_INDEX + dai_type "SAI" + copier_type "SAI" + stream_name $STREAM_CODEC_NAME + node_type $SAI_LINK_INPUT_CLASS + } + } +] + +# PCM definitions - single PCM with both playback and capture directions +# BE DAI: hw_config makes the codec the BCLK/FSYNC provider (as in IPC3) +Object.Dai.SAI [ + { + id 0 + dai_index $SAI_DAI_INDEX + direction "duplex" + name $STREAM_CODEC_NAME + sample_bits 32 + + Object.Base.hw_config.1 { + name "SAI3" + id 0 + format "I2S" + mclk "codec_mclk_in" + mclk_freq 12288000 + bclk "codec_provider" + bclk_freq 3072000 + fsync "codec_provider" + fsync_freq 48000 + tdm_slots 2 + tdm_slot_width 32 + tx_slots 3 + rx_slots 3 + } + } +] + +Object.PCM.pcm [ + { + name $HEADSET_PCM_NAME + id $HEADSET_PCM_ID + direction "duplex" + + Object.Base.fe_dai.1 { + name "$HEADSET_PCM_NAME" + } + + Object.PCM.pcm_caps.playback { + name $HEADSET_PLAYBACK_PCM_STREAM_NAME + formats 'S32_LE,S24_LE,S16_LE' + } + + Object.PCM.pcm_caps.capture { + name $HEADSET_CAPTURE_PCM_STREAM_NAME + formats 'S32_LE,S24_LE,S16_LE' + } + } +] + +# Widget routing +Object.Base.route [ + # Playback: host -> gain -> SAI DAI + { + source "host-copier.$HEADSET_PCM_ID.playback" + sink "gain.1.1" + } + { + source "gain.1.1" + sink "dai-copier.SAI.$STREAM_CODEC_NAME.playback" + } + # Capture: SAI DAI -> gain -> host + { + source "dai-copier.SAI.$STREAM_CODEC_NAME.capture" + sink "gain.2.1" + } + { + source "gain.2.1" + sink "host-copier.$HEADSET_PCM_ID.capture" + } +] diff --git a/tools/topology/topology2/imx8-wm8960.conf b/tools/topology/topology2/imx8-wm8960.conf index ea6ccf92853b..ef3936937c9d 100644 --- a/tools/topology/topology2/imx8-wm8960.conf +++ b/tools/topology/topology2/imx8-wm8960.conf @@ -14,7 +14,7 @@ # Include class definitions - + @@ -28,6 +28,7 @@ + Define { @@ -54,7 +55,7 @@ Object.Pipeline { Object.Widget.gain.1 { curve_type "windows_fade" Object.Control.mixer.1 { - name 'Playback Volume' + name 'PGA Playback Volume' } } @@ -66,34 +67,53 @@ Object.Pipeline { ] } -Object.Widget { - dai-copier."1" { - dai_index $SAI_DAI_INDEX - dai_type "SAI" - copier_type "SAI" - stream_name $STREAM_CODEC_NAME - node_type $I2S_LINK_OUTPUT_CLASS - type dai_in - num_input_audio_formats 1 - num_output_audio_formats 1 - num_input_pins 1 - direction "playback" +# +# DAI pipeline - the dai-copier needs its own pipeline/scheduler +# (dai-copier-io-playback) so sof_complete() actually visits it and +# populates the widget's IPC struct. A bare Object.Widget.dai-copier has no +# pipeline_id and is silently skipped, leaving struct snd_sof_dai::private +# NULL (seen as "No private data found for DAI ..." at PCM hw_params time). +# +Object.Pipeline.dai-copier-io-playback [ + { + index 2 - # copier only supports one format based on mixin/mixout requirements: 32-bit 48KHz 2ch - Object.Base.input_audio_format [ - { - in_bit_depth 32 - in_valid_bit_depth 32 - } - ] - Object.Base.output_audio_format [ - { - out_bit_depth 32 - out_valid_bit_depth 32 - } - ] + Object.Widget.dai-copier.1 { + dai_index $SAI_DAI_INDEX + dai_type "SAI" + copier_type "SAI" + stream_name $STREAM_CODEC_NAME + node_type $SAI_LINK_OUTPUT_CLASS + } } -} +] + +# BE DAI: hw_config makes the codec the BCLK/FSYNC provider (as in IPC3) +Object.Dai.SAI [ + { + id 0 + dai_index $SAI_DAI_INDEX + direction "playback" + name $STREAM_CODEC_NAME + sample_bits 32 + + Object.Base.hw_config.1 { + name "SAI3" + id 0 + format "I2S" + mclk "codec_mclk_in" + mclk_freq 12288000 + bclk "codec_provider" + bclk_freq 3072000 + fsync "codec_provider" + fsync_freq 48000 + tdm_slots 2 + tdm_slot_width 32 + tx_slots 3 + rx_slots 3 + } + } +] # PCM Definitions Object.PCM.pcm [ diff --git a/tools/topology/topology2/include/dais/hw_config_imx.conf b/tools/topology/topology2/include/dais/hw_config_imx.conf new file mode 100644 index 000000000000..eb2d7b92750b --- /dev/null +++ b/tools/topology/topology2/include/dais/hw_config_imx.conf @@ -0,0 +1,84 @@ +# hw_config for i.MX DAIs (SAI/ESAI/MICFIL): carries the DAI format and +# clock providers, which the kernel applies as the BE link dai_fmt. +# +# Object.Base.hw_config.1 { +# name "SAI3" +# id 0 +# format "I2S" +# mclk "codec_mclk_in" +# mclk_freq 12288000 +# bclk "codec_provider" +# bclk_freq 3072000 +# fsync "codec_provider" +# fsync_freq 48000 +# tdm_slots 2 +# tdm_slot_width 32 +# tx_slots 3 +# rx_slots 3 +# } + +Class.Base."hw_config" { + # + # Argument used to construct hw config (hw config ID) + # + DefineAttribute."id" {} + DefineAttribute."instance" {} + + DefineAttribute."name" { + type "string" + } + + DefineAttribute."format" { + type "string" + constraints { + !valid_values [ + "I2S" + "DSP_A" + "DSP_B" + ] + } + } + + # mclk direction: "codec_mclk_in" (SoC outputs MCLK to codec) or + # "codec_mclk_out" + DefineAttribute."mclk" { + type "string" + } + + DefineAttribute."mclk_freq" {} + + # bclk provider: "codec_provider" or "codec_consumer" + DefineAttribute."bclk" { + type "string" + } + + DefineAttribute."bclk_freq" {} + + # fsync provider: "codec_provider" or "codec_consumer" + DefineAttribute."fsync" { + type "string" + } + + DefineAttribute."fsync_freq" {} + + DefineAttribute."tdm_slots" {} + + DefineAttribute."tdm_slot_width" {} + + DefineAttribute."tx_slots" {} + + DefineAttribute."rx_slots" {} + + attributes { + !constructor [ + "id" + ] + !mandatory [ + "name" + ] + # + # hw_config objects instantiated within the same alsaconf node must + # have unique 'instance' attribute + unique "instance" + } +} diff --git a/tools/topology/topology2/include/pipelines/dai-copier-io.conf b/tools/topology/topology2/include/pipelines/dai-copier-io.conf new file mode 100644 index 000000000000..43949b4e0b5d --- /dev/null +++ b/tools/topology/topology2/include/pipelines/dai-copier-io.conf @@ -0,0 +1,113 @@ +# +# DAI-only pipeline: a dai-copier with its own scheduler (e.g. i.MX SAI). +# +# Set dai_index/dai_type/copier_type/stream_name/node_type only at the +# instantiation site; anything else makes alsatplg drop the uuid tuple. +# +# Usage: +# +# Object.Pipeline.dai-copier-io-playback."N" { +# Object.Widget.dai-copier.1 { +# dai_index $SAI_DAI_INDEX +# dai_type "SAI" +# copier_type "SAI" +# stream_name $STREAM_CODEC_NAME +# node_type $SAI_LINK_OUTPUT_CLASS +# } +# } +# +# Where N is the unique pipeline ID within the same alsaconf node. +# + + + + + + +Class.Pipeline."dai-copier-io-playback" { + + + + attributes { + !constructor [ + "index" + ] + + unique "instance" + } + + Object.Widget { + dai-copier."1" { + type dai_in + direction "playback" + num_input_audio_formats 1 + num_output_audio_formats 1 + num_input_pins 1 + + Object.Base.input_audio_format [ + { + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] + } + + pipeline."1" { + priority 0 + lp_mode 0 + } + } + + dynamic_pipeline 1 + time_domain "timer" +} + +Class.Pipeline."dai-copier-io-capture" { + + + + attributes { + !constructor [ + "index" + ] + + unique "instance" + } + + Object.Widget { + dai-copier."1" { + type dai_out + direction "capture" + num_input_audio_formats 1 + num_output_audio_formats 1 + num_output_pins 1 + + Object.Base.input_audio_format [ + { + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] + } + + pipeline."1" { + priority 0 + lp_mode 0 + } + } + + dynamic_pipeline 1 + time_domain "timer" +} diff --git a/tools/topology/topology2/production/tplg-targets-imx8.cmake b/tools/topology/topology2/production/tplg-targets-imx8.cmake index 34977808d962..e075d3954cce 100644 --- a/tools/topology/topology2/production/tplg-targets-imx8.cmake +++ b/tools/topology/topology2/production/tplg-targets-imx8.cmake @@ -2,6 +2,8 @@ # Array of "input-file-name;output-file-name;comma separated pre-processor variables" list(APPEND TPLGS -# IPC4 topology for i.MX8MP + WM8960 Codec +# IPC4 topology for i.MX8MP + WM8960 Codec - playback only "imx8-wm8960\;sof-imx8mp-wm8960\;SAI_DAI_INDEX=3,STREAM_CODEC_NAME=sai3-wm8960-hifi" +# IPC4 topology for i.MX8MP + WM8960 Codec - duplex (playback + capture) +"imx8-wm8960-duplex\;sof-imx8mp-wm8960-duplex\;SAI_DAI_INDEX=3,STREAM_CODEC_NAME=sai3-wm8960-hifi" ) From 0bbd42ffd9fd4a99c045b233d6c12d8a1c6239c0 Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Tue, 25 Aug 2026 17:12:37 +0300 Subject: [PATCH 17/18] topology2: imx8: add wm8960 topologies for i.MX8QM and i.MX8QXP Wire up the WM8960 IPC4 topologies for the i.MX8QM/i.MX8QXP MEK boards (codec on SAI1): register the sof-imx8-wm8960[-duplex] targets reusing the imx8-wm8960 sources, and give the duplex capture path an explicit indexed pcm_caps direction so the module adapter accepts it. Signed-off-by: Daniel Baluta --- tools/topology/topology2/imx8-wm8960-duplex.conf | 6 ++++-- .../topology/topology2/production/tplg-targets-imx8.cmake | 8 ++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/tools/topology/topology2/imx8-wm8960-duplex.conf b/tools/topology/topology2/imx8-wm8960-duplex.conf index ab7e4db1b3c0..a0440d8600c9 100644 --- a/tools/topology/topology2/imx8-wm8960-duplex.conf +++ b/tools/topology/topology2/imx8-wm8960-duplex.conf @@ -174,12 +174,14 @@ Object.PCM.pcm [ name "$HEADSET_PCM_NAME" } - Object.PCM.pcm_caps.playback { + Object.PCM.pcm_caps.1 { + direction "playback" name $HEADSET_PLAYBACK_PCM_STREAM_NAME formats 'S32_LE,S24_LE,S16_LE' } - Object.PCM.pcm_caps.capture { + Object.PCM.pcm_caps.2 { + direction "capture" name $HEADSET_CAPTURE_PCM_STREAM_NAME formats 'S32_LE,S24_LE,S16_LE' } diff --git a/tools/topology/topology2/production/tplg-targets-imx8.cmake b/tools/topology/topology2/production/tplg-targets-imx8.cmake index e075d3954cce..23ee2ea9c01e 100644 --- a/tools/topology/topology2/production/tplg-targets-imx8.cmake +++ b/tools/topology/topology2/production/tplg-targets-imx8.cmake @@ -6,4 +6,12 @@ list(APPEND TPLGS "imx8-wm8960\;sof-imx8mp-wm8960\;SAI_DAI_INDEX=3,STREAM_CODEC_NAME=sai3-wm8960-hifi" # IPC4 topology for i.MX8MP + WM8960 Codec - duplex (playback + capture) "imx8-wm8960-duplex\;sof-imx8mp-wm8960-duplex\;SAI_DAI_INDEX=3,STREAM_CODEC_NAME=sai3-wm8960-hifi" +# IPC4 topology for i.MX95 + WM8962 Codec - playback only +"imx95-wm8962\;sof-imx95-wm8962\;SAI_DAI_INDEX=3,STREAM_CODEC_NAME=sai3-wm8962" +# IPC4 topology for i.MX95 + WM8962 Codec - duplex (playback + capture) +"imx95-wm8962-duplex\;sof-imx95-wm8962-duplex\;SAI_DAI_INDEX=3,STREAM_CODEC_NAME=sai3-wm8962" +# IPC4 topology for i.MX8QM/i.MX8QXP + WM8960 Codec on SAI1 - playback only +"imx8-wm8960\;sof-imx8-wm8960\;SAI_DAI_INDEX=1,STREAM_CODEC_NAME=sai1-wm8960-hifi" +# IPC4 topology for i.MX8QM/i.MX8QXP + WM8960 Codec on SAI1 - duplex +"imx8-wm8960-duplex\;sof-imx8-wm8960-duplex\;SAI_DAI_INDEX=1,STREAM_CODEC_NAME=sai1-wm8960-hifi" ) From aae837feffe6693fe9ccd8676ea9d103ac90acc7 Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Tue, 18 Aug 2026 11:46:20 +0300 Subject: [PATCH 18/18] topology2: imx95: add wm8962 IPC4 topologies Add the i.MX95 + WM8962 IPC4 topologies (playback and duplex) and register them in the imx8 production tplg targets. The SAI back-end hw_config declares the WM8962 as bit-clock/frame-sync provider (SAI as consumer), matching the board wiring. Signed-off-by: Daniel Baluta --- .../topology2/imx95-wm8962-duplex.conf | 211 ++++++++++++++++++ tools/topology/topology2/imx95-wm8962.conf | 147 ++++++++++++ 2 files changed, 358 insertions(+) create mode 100644 tools/topology/topology2/imx95-wm8962-duplex.conf create mode 100644 tools/topology/topology2/imx95-wm8962.conf diff --git a/tools/topology/topology2/imx95-wm8962-duplex.conf b/tools/topology/topology2/imx95-wm8962-duplex.conf new file mode 100644 index 000000000000..b9d538dfee54 --- /dev/null +++ b/tools/topology/topology2/imx95-wm8962-duplex.conf @@ -0,0 +1,211 @@ +## +## IPC4 topology for i.MX95 (Cortex-M7) with WM8962 codec - duplex +## (playback + capture) +## +## Pipeline graph: +## +## Playback: +## host-copier.0.playback -> gain.1.1 -> dai-copier.SAI.saiN-codec.playback +## +## Capture: +## dai-copier.SAI.saiN-codec.capture -> gain.2.1 -> host-copier.0.capture +## + +# Include paths + + + + + + + +# Class definitions + + + + + + + + + + + + + + + + + + + + +Define { + # Playback PCM + HEADSET_PLAYBACK_PCM_STREAM_NAME "Headset Playback" + # Capture PCM + HEADSET_CAPTURE_PCM_STREAM_NAME "Headset Capture" + HEADSET_PCM_NAME "Headset" + HEADSET_PCM_ID 0 +} + +# +# Playback pipeline: host-copier.0.playback -> gain.1.1 +# +Object.Pipeline { + gain-playback [ + { + index 1 + + Object.Widget.host-copier.1 { + stream_name $HEADSET_PLAYBACK_PCM_STREAM_NAME + pcm_id $HEADSET_PCM_ID + } + + Object.Widget.gain.1 { + curve_type "windows_fade" + Object.Control.mixer.1 { + name 'PGA Playback Volume' + } + } + + time_domain "dma" + format "s32le" + num_input_audio_formats 3 + num_output_audio_formats 1 + } + ] + + # + # Capture pipeline: gain.2.1 -> host-copier.0.capture + # + gain-capture [ + { + index 2 + + Object.Widget.host-copier.1 { + stream_name $HEADSET_CAPTURE_PCM_STREAM_NAME + pcm_id $HEADSET_PCM_ID + } + + Object.Widget.gain.1 { + curve_type "windows_fade" + Object.Control.mixer.1 { + name 'Capture Volume' + } + } + + time_domain "dma" + format "s32le" + num_input_audio_formats 1 + num_output_audio_formats 2 + } + ] +} + +# +# DAI pipelines - one SAI DAI for playback, one for capture. Each dai-copier +# needs its own pipeline/scheduler (dai-copier-io-playback/-capture) so +# sof_complete() actually visits it and populates the widget's IPC struct - +# a bare Object.Widget.dai-copier has no pipeline_id and is silently +# skipped. +# +Object.Pipeline.dai-copier-io-playback [ + { + index 3 + + Object.Widget.dai-copier.1 { + dai_index $SAI_DAI_INDEX + dai_type "SAI" + copier_type "SAI" + stream_name $STREAM_CODEC_NAME + node_type $SAI_LINK_OUTPUT_CLASS + } + } +] +Object.Pipeline.dai-copier-io-capture [ + { + index 4 + + Object.Widget.dai-copier.1 { + dai_index $SAI_DAI_INDEX + dai_type "SAI" + copier_type "SAI" + stream_name $STREAM_CODEC_NAME + node_type $SAI_LINK_INPUT_CLASS + } + } +] + +# BE DAI: hw_config makes the codec the BCLK/FSYNC provider (as in IPC3) +Object.Dai.SAI [ + { + id 0 + dai_index $SAI_DAI_INDEX + direction "duplex" + name $STREAM_CODEC_NAME + sample_bits 32 + + Object.Base.hw_config.1 { + name "SAI3" + id 0 + format "I2S" + mclk "codec_mclk_in" + mclk_freq 12288000 + bclk "codec_provider" + bclk_freq 3072000 + fsync "codec_provider" + fsync_freq 48000 + tdm_slots 2 + tdm_slot_width 32 + tx_slots 3 + rx_slots 3 + } + } +] + +Object.PCM.pcm [ + { + name $HEADSET_PCM_NAME + id $HEADSET_PCM_ID + direction "duplex" + + Object.Base.fe_dai.1 { + name "$HEADSET_PCM_NAME" + } + + Object.PCM.pcm_caps.1 { + direction "playback" + name $HEADSET_PLAYBACK_PCM_STREAM_NAME + formats 'S32_LE,S24_LE,S16_LE' + } + + Object.PCM.pcm_caps.2 { + direction "capture" + name $HEADSET_CAPTURE_PCM_STREAM_NAME + formats 'S32_LE,S24_LE,S16_LE' + } + } +] + +# Widget routing +Object.Base.route [ + # Playback: host -> gain -> SAI DAI + { + source "host-copier.$HEADSET_PCM_ID.playback" + sink "gain.1.1" + } + { + source "gain.1.1" + sink "dai-copier.SAI.$STREAM_CODEC_NAME.playback" + } + # Capture: SAI DAI -> gain -> host + { + source "dai-copier.SAI.$STREAM_CODEC_NAME.capture" + sink "gain.2.1" + } + { + source "gain.2.1" + sink "host-copier.$HEADSET_PCM_ID.capture" + } +] diff --git a/tools/topology/topology2/imx95-wm8962.conf b/tools/topology/topology2/imx95-wm8962.conf new file mode 100644 index 000000000000..812cf872c080 --- /dev/null +++ b/tools/topology/topology2/imx95-wm8962.conf @@ -0,0 +1,147 @@ +## +## IPC4 topology for i.MX95 (Cortex-M7) with WM8962 codec - playback only +## +## (source) host-copier.N.playback -> gain.N.1 -> dai-copier.SAI(sink endpoint) +## + +# Include paths + + + + + + + +# Include class definitions + + + + + + + + + + + + + + + + + + +Define { + HEADSET_PLAYBACK_PCM_STREAM_NAME "Headset Playback" + HEADSET_PCM_NAME "Headset" + HEADSET_PCM_ID 0 +} + +# +# Pipeline definitions +# +# (source) host.N.playback -> gain.N.1 +# +Object.Pipeline { + gain-playback [ + { + index 1 + + Object.Widget.host-copier.1 { + stream_name $HEADSET_PLAYBACK_PCM_STREAM_NAME + pcm_id $HEADSET_PCM_ID + } + + Object.Widget.gain.1 { + curve_type "windows_fade" + Object.Control.mixer.1 { + name 'PGA Playback Volume' + } + } + + time_domain "dma" + format "s32le" + num_input_audio_formats 3 + num_output_audio_formats 1 + } + ] +} + +# +# DAI pipeline - the dai-copier needs its own pipeline/scheduler +# (dai-copier-io-playback) so sof_complete() actually visits it and +# populates the widget's IPC struct. A bare Object.Widget.dai-copier has no +# pipeline_id and is silently skipped, leaving struct snd_sof_dai::private +# NULL (seen as "No private data found for DAI ..." at PCM hw_params time). +# +Object.Pipeline.dai-copier-io-playback [ + { + index 2 + + Object.Widget.dai-copier.1 { + dai_index $SAI_DAI_INDEX + dai_type "SAI" + copier_type "SAI" + stream_name $STREAM_CODEC_NAME + node_type $SAI_LINK_OUTPUT_CLASS + } + } +] + +# BE DAI: hw_config makes the codec the BCLK/FSYNC provider (as in IPC3) +Object.Dai.SAI [ + { + id 0 + dai_index $SAI_DAI_INDEX + direction "playback" + name $STREAM_CODEC_NAME + sample_bits 32 + + Object.Base.hw_config.1 { + name "SAI3" + id 0 + format "I2S" + mclk "codec_mclk_in" + mclk_freq 12288000 + bclk "codec_provider" + bclk_freq 3072000 + fsync "codec_provider" + fsync_freq 48000 + tdm_slots 2 + tdm_slot_width 32 + tx_slots 3 + rx_slots 3 + } + } +] + +# PCM Definitions +Object.PCM.pcm [ + { + name $HEADSET_PCM_NAME + direction "playback" + id $HEADSET_PCM_ID + + Object.Base.fe_dai.1 { + name "$HEADSET_PCM_NAME" + } + + Object.PCM.pcm_caps.playback { + name $HEADSET_PLAYBACK_PCM_STREAM_NAME + formats 'S32_LE,S24_LE,S16_LE' + } + } +] + +# Top-level pipeline connection +# gain.1. -> dai.SAI.1.playback +Object.Base.route [ + { + source "host-copier.$HEADSET_PCM_ID.playback" + sink "gain.1.1" + } + { + source "gain.1.1" + sink "dai-copier.SAI.$STREAM_CODEC_NAME.playback" + } +]