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/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/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/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 f20c95f7262a..423f2e0a60fa 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. @@ -217,18 +218,48 @@ 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}", "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", "", "", "", "" @@ -239,10 +270,28 @@ 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", "", "", "", "" ), + # 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" @@ -953,6 +1002,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}") @@ -1350,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', 'imx8x', 'imx8m', 'imx8m_cm7', 'imx8ulp', 'imx95'] +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/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/audio/copier/copier_host.c b/src/audio/copier/copier_host.c index c3ab0208a5d0..412f8bbb8b74 100644 --- a/src/audio/copier/copier_host.c +++ b/src/audio/copier/copier_host.c @@ -9,9 +9,14 @@ #include #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 @@ -131,6 +136,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 +233,20 @@ __cold int copier_host_create(struct processing_module *mod, comp_err(dev, "copier: host new failed with exit"); goto e_data; } + +#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; +#endif + #if CONFIG_HOST_DMA_STREAM_SYNCHRONIZATION /* Size of a configuration without optional parameters. */ const uint32_t basic_size = sizeof(*copier_cfg) + @@ -275,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. */ @@ -289,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); @@ -323,11 +405,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/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 0884fd834d8b..cfc40c2c029b 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 */ @@ -1290,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; 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. */ diff --git a/src/include/ipc4/gateway.h b/src/include/ipc4/gateway.h index 81fa389e2a09..ab3ee2731ee1 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 }; @@ -135,6 +140,26 @@ 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))); + +/* 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/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/include/sof/platform.h b/src/include/sof/platform.h index 070809ad38b1..223d6255b904 100644 --- a/src/include/sof/platform.h +++ b/src/include/sof/platform.h @@ -52,6 +52,14 @@ 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); +/** \brief Send SOF_IPC4_FW_READY to the host. */ +int imx_ipc4_boot_complete(uint32_t boot_message); +#endif + /** @}*/ #endif diff --git a/src/ipc/ipc4/dai.c b/src/ipc/ipc4/dai.c index b0737e3ac09b..f36e9af4c2c0 100644 --- a/src/ipc/ipc4/dai.c +++ b/src/ipc/ipc4/dai.c @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include #include #include @@ -105,6 +107,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 +225,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); @@ -446,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; 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. 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); 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; +} 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/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/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/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/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/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/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/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/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/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__ */ diff --git a/tools/topology/topology2/imx8-wm8960-duplex.conf b/tools/topology/topology2/imx8-wm8960-duplex.conf new file mode 100644 index 000000000000..a0440d8600c9 --- /dev/null +++ b/tools/topology/topology2/imx8-wm8960-duplex.conf @@ -0,0 +1,211 @@ +## +## 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.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/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/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" + } +] 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 <-) ] } } 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..23ee2ea9c01e 100644 --- a/tools/topology/topology2/production/tplg-targets-imx8.cmake +++ b/tools/topology/topology2/production/tplg-targets-imx8.cmake @@ -2,6 +2,16 @@ # 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" +# 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" ) diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 8da2dc47fdc5..92c8afc2f73b 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) @@ -350,6 +367,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) @@ -397,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) @@ -447,6 +486,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