summaryrefslogtreecommitdiffstats
path: root/sound/soc/sof/imx
diff options
context:
space:
mode:
authorTejun Heo <tj@kernel.org>2026-09-14 22:12:34 -1000
committerTejun Heo <tj@kernel.org>2026-09-15 06:57:20 -1000
commita9e3760b0838299649c0d57cca44daaf40ba3c33 (patch)
tree95807ff0e511c05602ef34808cfc660303309057 /sound/soc/sof/imx
downloadlinux-stable-a9e3760b0838299649c0d57cca44daaf40ba3c33.tar.gz
linux-stable-a9e3760b0838299649c0d57cca44daaf40ba3c33.zip
sched_ext: Maintain an online cid mask in the scheduler arenagrafted
Schedulers on the default cid mapping treat [0, nr_online_cids) as the online set and restart on hotplug. Schedulers that install their own mapping with scx_bpf_cid_override() have no way to learn which cids are online: the count no longer identifies members and the CPU-form cpumask is unusable from cid programs. This is an obvious hole in the cid API. Add scx_bpf_online_cmask(), a kernel-maintained cmask in the scheduler's arena, allocated alongside the per-CPU scratch masks and populated after the cid mapping is finalized and before ops.init(), for child schedulers too. The pointer stays valid through ops.exit() with no reference to take. It is the arena offset as a void pointer, the same form struct_ops arena arguments arrive in. The verifier types the void return as a scalar for the program's arena cast. The mask follows the SCX hotplug notifications: seeded from cpu_active_mask and updated before ops.cid_online/offline() runs, so it lags cpu_online_mask only inside a hotplug transition. Updates walk the scheduler list under the lock that also serializes unlinking. Reads are live, not atomic snapshots. Root initialization excludes hotplug. v2: Reworded the getter kerneldoc (Andrea Righi). Signed-off-by: Tejun Heo <tj@kernel.org> Reviewed-by: Andrea Righi <arighi@nvidia.com>
Diffstat (limited to 'sound/soc/sof/imx')
-rw-r--r--sound/soc/sof/imx/Kconfig45
-rw-r--r--sound/soc/sof/imx/Makefile9
-rw-r--r--sound/soc/sof/imx/imx-common.c480
-rw-r--r--sound/soc/sof/imx/imx-common.h169
-rw-r--r--sound/soc/sof/imx/imx8.c473
-rw-r--r--sound/soc/sof/imx/imx9.c117
6 files changed, 1293 insertions, 0 deletions
diff --git a/sound/soc/sof/imx/Kconfig b/sound/soc/sof/imx/Kconfig
new file mode 100644
index 000000000..09d88ce5b
--- /dev/null
+++ b/sound/soc/sof/imx/Kconfig
@@ -0,0 +1,45 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+
+config SND_SOC_SOF_IMX_TOPLEVEL
+ bool "SOF support for NXP i.MX audio DSPs"
+ depends on ARM64|| COMPILE_TEST
+ depends on SND_SOC_SOF_OF
+ help
+ This adds support for Sound Open Firmware for NXP i.MX platforms.
+ Say Y if you have such a device.
+ If unsure select "N".
+
+if SND_SOC_SOF_IMX_TOPLEVEL
+
+config SND_SOC_SOF_IMX_COMMON
+ tristate
+ select SND_SOC_SOF_OF_DEV
+ select SND_SOC_SOF
+ select SND_SOC_SOF_IPC3
+ select SND_SOC_SOF_XTENSA
+ select SND_SOC_SOF_COMPRESS
+ help
+ This option is not user-selectable but automagically handled by
+ 'select' statements at a higher level.
+
+config SND_SOC_SOF_IMX8
+ tristate "SOF support for i.MX8"
+ depends on IMX_SCU
+ depends on IMX_DSP
+ select SND_SOC_SOF_IMX_COMMON
+ help
+ This adds support for Sound Open Firmware for NXP i.MX8 platforms.
+ Say Y if you have such a device.
+ If unsure select "N".
+
+config SND_SOC_SOF_IMX9
+ tristate "SOF support for i.MX9"
+ depends on IMX_DSP
+ depends on IMX_SCMI_LMM_DRV
+ select SND_SOC_SOF_IMX_COMMON
+ help
+ This adds support for Sound Open Firmware for NXP i.MX9 platforms.
+ Say Y if you need such a device.
+ If unsure select "N".
+
+endif ## SND_SOC_SOF_IMX_TOPLEVEL
diff --git a/sound/soc/sof/imx/Makefile b/sound/soc/sof/imx/Makefile
new file mode 100644
index 000000000..74b5ecad8
--- /dev/null
+++ b/sound/soc/sof/imx/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+snd-sof-imx8-y := imx8.o
+snd-sof-imx9-y := imx9.o
+
+snd-sof-imx-common-y := imx-common.o
+
+obj-$(CONFIG_SND_SOC_SOF_IMX8) += snd-sof-imx8.o
+obj-$(CONFIG_SND_SOC_SOF_IMX9) += snd-sof-imx9.o
+obj-$(CONFIG_SND_SOC_SOF_IMX_COMMON) += imx-common.o
diff --git a/sound/soc/sof/imx/imx-common.c b/sound/soc/sof/imx/imx-common.c
new file mode 100644
index 000000000..7a03c8cc5
--- /dev/null
+++ b/sound/soc/sof/imx/imx-common.c
@@ -0,0 +1,480 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// Copyright 2020-2025 NXP
+//
+// Common helpers for the audio DSP on i.MX8
+
+#include <linux/firmware/imx/dsp.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/pm_domain.h>
+#include <sound/sof/xtensa.h>
+
+#include "../ops.h"
+
+#include "imx-common.h"
+
+/**
+ * imx8_get_registers() - This function is called in case of DSP oops
+ * in order to gather information about the registers, filename and
+ * linenumber and stack.
+ * @sdev: SOF device
+ * @xoops: Stores information about registers.
+ * @panic_info: Stores information about filename and line number.
+ * @stack: Stores the stack dump.
+ * @stack_words: Size of the stack dump.
+ */
+void imx8_get_registers(struct snd_sof_dev *sdev,
+ struct sof_ipc_dsp_oops_xtensa *xoops,
+ struct sof_ipc_panic_info *panic_info,
+ u32 *stack, size_t stack_words)
+{
+ u32 offset = sdev->dsp_oops_offset;
+
+ /* first read registers */
+ sof_mailbox_read(sdev, offset, xoops, sizeof(*xoops));
+
+ /* then get panic info */
+ if (xoops->arch_hdr.totalsize > EXCEPT_MAX_HDR_SIZE) {
+ dev_err(sdev->dev, "invalid header size 0x%x. FW oops is bogus\n",
+ xoops->arch_hdr.totalsize);
+ return;
+ }
+ offset += xoops->arch_hdr.totalsize;
+ sof_mailbox_read(sdev, offset, panic_info, sizeof(*panic_info));
+
+ /* then get the stack */
+ offset += sizeof(*panic_info);
+ sof_mailbox_read(sdev, offset, stack, stack_words * sizeof(u32));
+}
+
+/**
+ * imx8_dump() - This function is called when a panic message is
+ * received from the firmware.
+ * @sdev: SOF device
+ * @flags: parameter not used but required by ops prototype
+ */
+void imx8_dump(struct snd_sof_dev *sdev, u32 flags)
+{
+ struct sof_ipc_dsp_oops_xtensa xoops;
+ struct sof_ipc_panic_info panic_info;
+ u32 stack[IMX8_STACK_DUMP_SIZE];
+ u32 status;
+
+ /* Get information about the panic status from the debug box area.
+ * Compute the trace point based on the status.
+ */
+ sof_mailbox_read(sdev, sdev->debug_box.offset + 0x4, &status, 4);
+
+ /* Get information about the registers, the filename and line
+ * number and the stack.
+ */
+ imx8_get_registers(sdev, &xoops, &panic_info, stack,
+ IMX8_STACK_DUMP_SIZE);
+
+ /* Print the information to the console */
+ sof_print_oops_and_stack(sdev, KERN_ERR, status, status, &xoops,
+ &panic_info, stack, IMX8_STACK_DUMP_SIZE);
+}
+EXPORT_SYMBOL(imx8_dump);
+
+static void imx_handle_reply(struct imx_dsp_ipc *ipc)
+{
+ struct snd_sof_dev *sdev = imx_dsp_get_data(ipc);
+
+ guard(spinlock_irqsave)(&sdev->ipc_lock);
+ snd_sof_ipc_process_reply(sdev, 0);
+}
+
+static void imx_handle_request(struct imx_dsp_ipc *ipc)
+{
+ struct snd_sof_dev *sdev;
+ u32 panic_code;
+
+ sdev = imx_dsp_get_data(ipc);
+
+ if (get_chip_info(sdev)->ipc_info.has_panic_code) {
+ sof_mailbox_read(sdev, sdev->debug_box.offset + 0x4,
+ &panic_code,
+ sizeof(panic_code));
+
+ if ((panic_code & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) {
+ snd_sof_dsp_panic(sdev, panic_code, true);
+ return;
+ }
+ }
+
+ snd_sof_ipc_msgs_rx(sdev);
+}
+
+static struct imx_dsp_ops imx_ipc_ops = {
+ .handle_reply = imx_handle_reply,
+ .handle_request = imx_handle_request,
+};
+
+static int imx_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
+{
+ struct imx_common_data *common = sdev->pdata->hw_pdata;
+
+ sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, msg->msg_size);
+ imx_dsp_ring_doorbell(common->ipc_handle, 0x0);
+
+ return 0;
+}
+
+static int imx_get_bar_index(struct snd_sof_dev *sdev, u32 type)
+{
+ switch (type) {
+ case SOF_FW_BLK_TYPE_IRAM:
+ case SOF_FW_BLK_TYPE_SRAM:
+ return type;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int imx_get_mailbox_offset(struct snd_sof_dev *sdev)
+{
+ return get_chip_info(sdev)->ipc_info.boot_mbox_offset;
+}
+
+static int imx_get_window_offset(struct snd_sof_dev *sdev, u32 id)
+{
+ return get_chip_info(sdev)->ipc_info.window_offset;
+}
+
+static int imx_set_power_state(struct snd_sof_dev *sdev,
+ const struct sof_dsp_power_state *target)
+{
+ sdev->dsp_power_state = *target;
+
+ return 0;
+}
+
+static int imx_common_resume(struct snd_sof_dev *sdev)
+{
+ struct imx_common_data *common;
+ int ret, i;
+
+ common = sdev->pdata->hw_pdata;
+
+ ret = clk_bulk_prepare_enable(common->clk_num, common->clks);
+ if (ret)
+ dev_err(sdev->dev, "failed to enable clocks: %d\n", ret);
+
+ for (i = 0; i < DSP_MU_CHAN_NUM; i++)
+ imx_dsp_request_channel(common->ipc_handle, i);
+
+ /* done. If need be, core will be started by SOF core immediately after */
+ return 0;
+}
+
+static int imx_common_suspend(struct snd_sof_dev *sdev)
+{
+ struct imx_common_data *common;
+ int i, ret;
+
+ common = sdev->pdata->hw_pdata;
+
+ ret = imx_chip_core_shutdown(sdev);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to shutdown core: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < DSP_MU_CHAN_NUM; i++)
+ imx_dsp_free_channel(common->ipc_handle, i);
+
+ clk_bulk_disable_unprepare(common->clk_num, common->clks);
+
+ return 0;
+}
+
+static int imx_runtime_resume(struct snd_sof_dev *sdev)
+{
+ const struct sof_dsp_power_state target_state = {
+ .state = SOF_DSP_PM_D0,
+ };
+ int ret;
+
+ ret = imx_common_resume(sdev);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to runtime common resume: %d\n", ret);
+ return ret;
+ }
+
+ return snd_sof_dsp_set_power_state(sdev, &target_state);
+}
+
+static int imx_resume(struct snd_sof_dev *sdev)
+{
+ const struct sof_dsp_power_state target_state = {
+ .state = SOF_DSP_PM_D0,
+ };
+ int ret;
+
+ ret = imx_common_resume(sdev);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to common resume: %d\n", ret);
+ return ret;
+ }
+
+ if (pm_runtime_suspended(sdev->dev)) {
+ pm_runtime_disable(sdev->dev);
+ pm_runtime_set_active(sdev->dev);
+ pm_runtime_mark_last_busy(sdev->dev);
+ pm_runtime_enable(sdev->dev);
+ pm_runtime_idle(sdev->dev);
+ }
+
+ return snd_sof_dsp_set_power_state(sdev, &target_state);
+}
+
+static int imx_runtime_suspend(struct snd_sof_dev *sdev)
+{
+ const struct sof_dsp_power_state target_state = {
+ .state = SOF_DSP_PM_D3,
+ };
+ int ret;
+
+ ret = imx_common_suspend(sdev);
+ if (ret < 0)
+ dev_err(sdev->dev, "failed to runtime common suspend: %d\n", ret);
+
+ return snd_sof_dsp_set_power_state(sdev, &target_state);
+}
+
+static int imx_suspend(struct snd_sof_dev *sdev, unsigned int target_state)
+{
+ const struct sof_dsp_power_state target_power_state = {
+ .state = target_state,
+ };
+ int ret;
+
+ if (!pm_runtime_suspended(sdev->dev)) {
+ ret = imx_common_suspend(sdev);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to common suspend: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return snd_sof_dsp_set_power_state(sdev, &target_power_state);
+}
+
+static int imx_region_name_to_blk_type(const char *region_name)
+{
+ if (!strcmp(region_name, "iram"))
+ return SOF_FW_BLK_TYPE_IRAM;
+ else if (!strcmp(region_name, "dram"))
+ return SOF_FW_BLK_TYPE_DRAM;
+ else if (!strcmp(region_name, "sram"))
+ return SOF_FW_BLK_TYPE_SRAM;
+ else
+ return -EINVAL;
+}
+
+static int imx_parse_ioremap_memory(struct snd_sof_dev *sdev)
+{
+ const struct imx_chip_info *chip_info;
+ struct platform_device *pdev;
+ struct resource *res, _res;
+ int i, blk_type, ret;
+
+ pdev = to_platform_device(sdev->dev);
+ chip_info = get_chip_info(sdev);
+
+ for (i = 0; chip_info->memory[i].name; i++) {
+ blk_type = imx_region_name_to_blk_type(chip_info->memory[i].name);
+ if (blk_type < 0)
+ return dev_err_probe(sdev->dev, blk_type,
+ "no blk type for region %s\n",
+ chip_info->memory[i].name);
+
+ if (!chip_info->memory[i].reserved) {
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ chip_info->memory[i].name);
+ if (!res)
+ return dev_err_probe(sdev->dev, -ENODEV,
+ "failed to fetch %s resource\n",
+ chip_info->memory[i].name);
+
+ } else {
+ ret = of_reserved_mem_region_to_resource_byname(pdev->dev.of_node,
+ chip_info->memory[i].name,
+ &_res);
+ if (ret < 0)
+ return dev_err_probe(sdev->dev, ret,
+ "no valid entry for %s\n",
+ chip_info->memory[i].name);
+ res = &_res;
+ }
+
+ sdev->bar[blk_type] = devm_ioremap_resource(sdev->dev, res);
+ if (IS_ERR(sdev->bar[blk_type]))
+ return dev_err_probe(sdev->dev,
+ PTR_ERR(sdev->bar[blk_type]),
+ "failed to ioremap %s region\n",
+ chip_info->memory[i].name);
+ }
+
+ return 0;
+}
+
+static void imx_unregister_action(void *data)
+{
+ struct imx_common_data *common;
+ struct snd_sof_dev *sdev;
+
+ sdev = data;
+ common = sdev->pdata->hw_pdata;
+
+ if (get_chip_info(sdev)->has_dma_reserved)
+ of_reserved_mem_device_release(sdev->dev);
+
+ platform_device_unregister(common->ipc_dev);
+}
+
+static int imx_probe(struct snd_sof_dev *sdev)
+{
+ struct dev_pm_domain_attach_data domain_data = {
+ .pd_names = NULL, /* no filtering */
+ .pd_flags = PD_FLAG_DEV_LINK_ON,
+ };
+ struct imx_common_data *common;
+ struct platform_device *pdev;
+ int ret;
+
+ pdev = to_platform_device(sdev->dev);
+
+ common = devm_kzalloc(sdev->dev, sizeof(*common), GFP_KERNEL);
+ if (!common)
+ return -ENOMEM;
+
+ sdev->pdata->hw_pdata = common;
+
+ common->ipc_dev = platform_device_register_data(sdev->dev, "imx-dsp",
+ PLATFORM_DEVID_NONE,
+ pdev, sizeof(*pdev));
+ if (IS_ERR(common->ipc_dev))
+ return dev_err_probe(sdev->dev, PTR_ERR(common->ipc_dev),
+ "failed to create IPC device\n");
+
+ if (get_chip_info(sdev)->has_dma_reserved) {
+ ret = of_reserved_mem_device_init_by_name(sdev->dev,
+ pdev->dev.of_node,
+ "dma");
+ if (ret) {
+ platform_device_unregister(common->ipc_dev);
+
+ return dev_err_probe(sdev->dev, ret,
+ "failed to bind DMA region\n");
+ }
+ }
+
+ /* let the devres API take care of the cleanup */
+ ret = devm_add_action_or_reset(sdev->dev,
+ imx_unregister_action,
+ sdev);
+ if (ret)
+ return ret;
+
+ common->ipc_handle = dev_get_drvdata(&common->ipc_dev->dev);
+ if (!common->ipc_handle)
+ return dev_err_probe(sdev->dev, -EPROBE_DEFER,
+ "failed to fetch IPC handle\n");
+
+ ret = imx_parse_ioremap_memory(sdev);
+ if (ret < 0)
+ return dev_err_probe(sdev->dev, ret,
+ "failed to parse/ioremap memory regions\n");
+
+ if (!sdev->dev->pm_domain) {
+ ret = devm_pm_domain_attach_list(sdev->dev,
+ &domain_data, &common->pd_list);
+ if (ret < 0)
+ return dev_err_probe(sdev->dev, ret, "failed to attach PDs\n");
+ }
+
+ ret = devm_clk_bulk_get_all(sdev->dev, &common->clks);
+ if (ret < 0)
+ return dev_err_probe(sdev->dev, ret, "failed to fetch clocks\n");
+ common->clk_num = ret;
+
+ ret = clk_bulk_prepare_enable(common->clk_num, common->clks);
+ if (ret < 0)
+ return dev_err_probe(sdev->dev, ret, "failed to enable clocks\n");
+
+ common->ipc_handle->ops = &imx_ipc_ops;
+ imx_dsp_set_data(common->ipc_handle, sdev);
+
+ sdev->num_cores = 1;
+ sdev->mailbox_bar = SOF_FW_BLK_TYPE_SRAM;
+ sdev->dsp_box.offset = get_chip_info(sdev)->ipc_info.boot_mbox_offset;
+
+ return imx_chip_probe(sdev);
+}
+
+static void imx_remove(struct snd_sof_dev *sdev)
+{
+ struct imx_common_data *common;
+ int ret;
+
+ common = sdev->pdata->hw_pdata;
+
+ if (!pm_runtime_suspended(sdev->dev)) {
+ ret = imx_chip_core_shutdown(sdev);
+ if (ret < 0)
+ dev_err(sdev->dev, "failed to shutdown core: %d\n", ret);
+
+ clk_bulk_disable_unprepare(common->clk_num, common->clks);
+ }
+}
+
+const struct snd_sof_dsp_ops sof_imx_ops = {
+ .probe = imx_probe,
+ .remove = imx_remove,
+
+ .run = imx_chip_core_kick,
+ .reset = imx_chip_core_reset,
+
+ .block_read = sof_block_read,
+ .block_write = sof_block_write,
+
+ .mailbox_read = sof_mailbox_read,
+ .mailbox_write = sof_mailbox_write,
+
+ .send_msg = imx_send_msg,
+ .get_mailbox_offset = imx_get_mailbox_offset,
+ .get_window_offset = imx_get_window_offset,
+
+ .ipc_msg_data = sof_ipc_msg_data,
+ .set_stream_data_offset = sof_set_stream_data_offset,
+
+ .get_bar_index = imx_get_bar_index,
+ .load_firmware = snd_sof_load_firmware_memcpy,
+
+ .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem,
+
+ .pcm_open = sof_stream_pcm_open,
+ .pcm_close = sof_stream_pcm_close,
+
+ .runtime_suspend = imx_runtime_suspend,
+ .runtime_resume = imx_runtime_resume,
+ .suspend = imx_suspend,
+ .resume = imx_resume,
+
+ .set_power_state = imx_set_power_state,
+
+ .hw_info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_BATCH |
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
+};
+EXPORT_SYMBOL(sof_imx_ops);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("SOF helpers for IMX platforms");
diff --git a/sound/soc/sof/imx/imx-common.h b/sound/soc/sof/imx/imx-common.h
new file mode 100644
index 000000000..9bd711dbb
--- /dev/null
+++ b/sound/soc/sof/imx/imx-common.h
@@ -0,0 +1,169 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+
+#ifndef __IMX_COMMON_H__
+#define __IMX_COMMON_H__
+
+#include <linux/clk.h>
+#include <linux/of_platform.h>
+#include <sound/sof/xtensa.h>
+
+#include "../sof-of-dev.h"
+#include "../ops.h"
+
+#define EXCEPT_MAX_HDR_SIZE 0x400
+#define IMX8_STACK_DUMP_SIZE 32
+
+/* chip_info refers to the data stored in struct sof_dev_desc's chip_info */
+#define get_chip_info(sdev)\
+ ((const struct imx_chip_info *)((sdev)->pdata->desc->chip_info))
+
+/* chip_pdata refers to the data stored in struct imx_common_data's chip_pdata */
+#define get_chip_pdata(sdev)\
+ (((struct imx_common_data *)((sdev)->pdata->hw_pdata))->chip_pdata)
+
+/* can be used if:
+ * 1) The only supported IPC version is IPC3.
+ * 2) The default paths/FW name match values below.
+ *
+ * otherwise, just explicitly declare the structure
+ */
+#define IMX_SOF_DEV_DESC(mach_name, of_machs, \
+ mach_chip_info, mach_ops, mach_ops_init) \
+static struct sof_dev_desc sof_of_##mach_name##_desc = { \
+ .of_machines = of_machs, \
+ .chip_info = mach_chip_info, \
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3), \
+ .ipc_default = SOF_IPC_TYPE_3, \
+ .default_fw_path = { \
+ [SOF_IPC_TYPE_3] = "imx/sof", \
+ }, \
+ .default_tplg_path = { \
+ [SOF_IPC_TYPE_3] = "imx/sof-tplg", \
+ }, \
+ .default_fw_filename = { \
+ [SOF_IPC_TYPE_3] = "sof-" #mach_name ".ri", \
+ }, \
+ .ops = mach_ops, \
+ .ops_init = mach_ops_init, \
+}
+
+/* to be used alongside IMX_SOF_DEV_DESC() */
+#define IMX_SOF_DEV_DESC_NAME(mach_name) sof_of_##mach_name##_desc
+
+/* dai driver entry w/ playback and capture caps. If one direction is missing
+ * then set the channels to 0.
+ */
+#define IMX_SOF_DAI_DRV_ENTRY(dai_name, pb_cmin, pb_cmax, cap_cmin, cap_cmax) \
+{ \
+ .name = dai_name, \
+ .playback = { \
+ .channels_min = pb_cmin, \
+ .channels_max = pb_cmax, \
+ }, \
+ .capture = { \
+ .channels_min = cap_cmin, \
+ .channels_max = cap_cmax, \
+ }, \
+}
+
+/* use if playback and capture have the same min/max channel count */
+#define IMX_SOF_DAI_DRV_ENTRY_BIDIR(dai_name, cmin, cmax)\
+ IMX_SOF_DAI_DRV_ENTRY(dai_name, cmin, cmax, cmin, cmax)
+
+struct imx_ipc_info {
+ /* true if core is able to write a panic code to the debug box */
+ bool has_panic_code;
+ /* offset to mailbox in which firmware initially writes FW_READY */
+ int boot_mbox_offset;
+ /* offset to region at which the mailboxes start */
+ int window_offset;
+};
+
+struct imx_chip_ops {
+ /* called after clocks and PDs are enabled */
+ int (*probe)(struct snd_sof_dev *sdev);
+ /* used directly by the SOF core */
+ int (*core_kick)(struct snd_sof_dev *sdev);
+ /* called during suspend()/remove() before clocks are disabled */
+ int (*core_shutdown)(struct snd_sof_dev *sdev);
+ /* used directly by the SOF core */
+ int (*core_reset)(struct snd_sof_dev *sdev);
+};
+
+struct imx_memory_info {
+ const char *name;
+ bool reserved;
+};
+
+struct imx_chip_info {
+ struct imx_ipc_info ipc_info;
+ /* does the chip have a reserved memory region for DMA? */
+ bool has_dma_reserved;
+ struct imx_memory_info *memory;
+ struct snd_soc_dai_driver *drv;
+ int num_drv;
+ /* optional */
+ const struct imx_chip_ops *ops;
+};
+
+struct imx_common_data {
+ struct platform_device *ipc_dev;
+ struct imx_dsp_ipc *ipc_handle;
+ /* core may have no clocks */
+ struct clk_bulk_data *clks;
+ int clk_num;
+ /* core may have no PDs */
+ struct dev_pm_domain_list *pd_list;
+ void *chip_pdata;
+};
+
+static inline int imx_chip_core_kick(struct snd_sof_dev *sdev)
+{
+ const struct imx_chip_ops *ops = get_chip_info(sdev)->ops;
+
+ if (ops && ops->core_kick)
+ return ops->core_kick(sdev);
+
+ return 0;
+}
+
+static inline int imx_chip_core_shutdown(struct snd_sof_dev *sdev)
+{
+ const struct imx_chip_ops *ops = get_chip_info(sdev)->ops;
+
+ if (ops && ops->core_shutdown)
+ return ops->core_shutdown(sdev);
+
+ return 0;
+}
+
+static inline int imx_chip_core_reset(struct snd_sof_dev *sdev)
+{
+ const struct imx_chip_ops *ops = get_chip_info(sdev)->ops;
+
+ if (ops && ops->core_reset)
+ return ops->core_reset(sdev);
+
+ return 0;
+}
+
+static inline int imx_chip_probe(struct snd_sof_dev *sdev)
+{
+ const struct imx_chip_ops *ops = get_chip_info(sdev)->ops;
+
+ if (ops && ops->probe)
+ return ops->probe(sdev);
+
+ return 0;
+}
+
+void imx8_get_registers(struct snd_sof_dev *sdev,
+ struct sof_ipc_dsp_oops_xtensa *xoops,
+ struct sof_ipc_panic_info *panic_info,
+ u32 *stack, size_t stack_words);
+
+void imx8_dump(struct snd_sof_dev *sdev, u32 flags);
+
+extern const struct snd_sof_dsp_ops sof_imx_ops;
+
+#endif
diff --git a/sound/soc/sof/imx/imx8.c b/sound/soc/sof/imx/imx8.c
new file mode 100644
index 000000000..7e9eab2e3
--- /dev/null
+++ b/sound/soc/sof/imx/imx8.c
@@ -0,0 +1,473 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// Copyright 2019-2025 NXP
+//
+// Author: Daniel Baluta <daniel.baluta@nxp.com>
+//
+// Hardware interface for audio DSP on i.MX8
+
+#include <dt-bindings/firmware/imx/rsrc.h>
+
+#include <linux/arm-smccc.h>
+#include <linux/firmware/imx/svc/misc.h>
+#include <linux/mfd/syscon.h>
+#include <linux/reset.h>
+
+#include "imx-common.h"
+
+/* imx8/imx8x macros */
+#define RESET_VECTOR_VADDR 0x596f8000
+
+/* imx8m macros */
+#define IMX8M_DAP_DEBUG 0x28800000
+#define IMX8M_DAP_DEBUG_SIZE (64 * 1024)
+#define IMX8M_DAP_PWRCTL (0x4000 + 0x3020)
+#define IMX8M_PWRCTL_CORERESET BIT(16)
+
+/* imx8ulp macros */
+#define FSL_SIP_HIFI_XRDC 0xc200000e
+#define SYSCTRL0 0x8
+#define EXECUTE_BIT BIT(13)
+#define RESET_BIT BIT(16)
+#define HIFI4_CLK_BIT BIT(17)
+#define PB_CLK_BIT BIT(18)
+#define PLAT_CLK_BIT BIT(19)
+#define DEBUG_LOGIC_BIT BIT(25)
+
+struct imx8m_chip_data {
+ void __iomem *dap;
+ struct regmap *regmap;
+ struct reset_control *run_stall;
+};
+
+static int imx8_shutdown(struct snd_sof_dev *sdev)
+{
+ /*
+ * Force the DSP to stall. After the firmware image is loaded,
+ * the stall will be removed during run() by a matching
+ * imx_sc_pm_cpu_start() call.
+ */
+ imx_sc_pm_cpu_start(get_chip_pdata(sdev), IMX_SC_R_DSP, false,
+ RESET_VECTOR_VADDR);
+
+ return 0;
+}
+
+/*
+ * DSP control.
+ */
+static int imx8x_run(struct snd_sof_dev *sdev)
+{
+ int ret;
+
+ ret = imx_sc_misc_set_control(get_chip_pdata(sdev), IMX_SC_R_DSP,
+ IMX_SC_C_OFS_SEL, 1);
+ if (ret < 0) {
+ dev_err(sdev->dev, "Error system address offset source select\n");
+ return ret;
+ }
+
+ ret = imx_sc_misc_set_control(get_chip_pdata(sdev), IMX_SC_R_DSP,
+ IMX_SC_C_OFS_AUDIO, 0x80);
+ if (ret < 0) {
+ dev_err(sdev->dev, "Error system address offset of AUDIO\n");
+ return ret;
+ }
+
+ ret = imx_sc_misc_set_control(get_chip_pdata(sdev), IMX_SC_R_DSP,
+ IMX_SC_C_OFS_PERIPH, 0x5A);
+ if (ret < 0) {
+ dev_err(sdev->dev, "Error system address offset of PERIPH %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = imx_sc_misc_set_control(get_chip_pdata(sdev), IMX_SC_R_DSP,
+ IMX_SC_C_OFS_IRQ, 0x51);
+ if (ret < 0) {
+ dev_err(sdev->dev, "Error system address offset of IRQ\n");
+ return ret;
+ }
+
+ imx_sc_pm_cpu_start(get_chip_pdata(sdev), IMX_SC_R_DSP, true,
+ RESET_VECTOR_VADDR);
+
+ return 0;
+}
+
+static int imx8_run(struct snd_sof_dev *sdev)
+{
+ int ret;
+
+ ret = imx_sc_misc_set_control(get_chip_pdata(sdev), IMX_SC_R_DSP,
+ IMX_SC_C_OFS_SEL, 0);
+ if (ret < 0) {
+ dev_err(sdev->dev, "Error system address offset source select\n");
+ return ret;
+ }
+
+ imx_sc_pm_cpu_start(get_chip_pdata(sdev), IMX_SC_R_DSP, true,
+ RESET_VECTOR_VADDR);
+
+ return 0;
+}
+
+static int imx8_probe(struct snd_sof_dev *sdev)
+{
+ struct imx_sc_ipc *sc_ipc_handle;
+ struct imx_common_data *common;
+ int ret;
+
+ common = sdev->pdata->hw_pdata;
+
+ ret = imx_scu_get_handle(&sc_ipc_handle);
+ if (ret < 0)
+ return dev_err_probe(sdev->dev, ret,
+ "failed to fetch SC IPC handle\n");
+
+ common->chip_pdata = sc_ipc_handle;
+
+ return 0;
+}
+
+static int imx8m_reset(struct snd_sof_dev *sdev)
+{
+ struct imx8m_chip_data *chip;
+ u32 pwrctl;
+
+ chip = get_chip_pdata(sdev);
+
+ /* put DSP into reset and stall */
+ pwrctl = readl(chip->dap + IMX8M_DAP_PWRCTL);
+ pwrctl |= IMX8M_PWRCTL_CORERESET;
+ writel(pwrctl, chip->dap + IMX8M_DAP_PWRCTL);
+
+ /* keep reset asserted for 10 cycles */
+ usleep_range(1, 2);
+
+ reset_control_assert(chip->run_stall);
+
+ /* take the DSP out of reset and keep stalled for FW loading */
+ pwrctl = readl(chip->dap + IMX8M_DAP_PWRCTL);
+ pwrctl &= ~IMX8M_PWRCTL_CORERESET;
+ writel(pwrctl, chip->dap + IMX8M_DAP_PWRCTL);
+
+ return 0;
+}
+
+static int imx8m_run(struct snd_sof_dev *sdev)
+{
+ struct imx8m_chip_data *chip = get_chip_pdata(sdev);
+
+ return reset_control_deassert(chip->run_stall);
+}
+
+static int imx8m_probe(struct snd_sof_dev *sdev)
+{
+ struct imx_common_data *common;
+ struct imx8m_chip_data *chip;
+
+ common = sdev->pdata->hw_pdata;
+
+ chip = devm_kzalloc(sdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->dap = devm_ioremap(sdev->dev, IMX8M_DAP_DEBUG, IMX8M_DAP_DEBUG_SIZE);
+ if (!chip->dap)
+ return dev_err_probe(sdev->dev, -ENODEV,
+ "failed to ioremap DAP\n");
+
+ chip->run_stall = devm_reset_control_get_exclusive(sdev->dev, "runstall");
+ if (IS_ERR(chip->run_stall))
+ return dev_err_probe(sdev->dev, PTR_ERR(chip->run_stall),
+ "failed to get dsp runstall reset control\n");
+
+ common->chip_pdata = chip;
+
+ return 0;
+}
+
+static int imx8ulp_run(struct snd_sof_dev *sdev)
+{
+ struct regmap *regmap = get_chip_pdata(sdev);
+
+ /* Controls the HiFi4 DSP Reset: 1 in reset, 0 out of reset */
+ regmap_update_bits(regmap, SYSCTRL0, RESET_BIT, 0);
+
+ /* Reset HiFi4 DSP Debug logic: 1 debug reset, 0 out of reset*/
+ regmap_update_bits(regmap, SYSCTRL0, DEBUG_LOGIC_BIT, 0);
+
+ /* Stall HIFI4 DSP Execution: 1 stall, 0 run */
+ regmap_update_bits(regmap, SYSCTRL0, EXECUTE_BIT, 0);
+
+ return 0;
+}
+
+static int imx8ulp_reset(struct snd_sof_dev *sdev)
+{
+ struct arm_smccc_res smc_res;
+ struct regmap *regmap;
+
+ regmap = get_chip_pdata(sdev);
+
+ /* HiFi4 Platform Clock Enable: 1 enabled, 0 disabled */
+ regmap_update_bits(regmap, SYSCTRL0, PLAT_CLK_BIT, PLAT_CLK_BIT);
+
+ /* HiFi4 PBCLK clock enable: 1 enabled, 0 disabled */
+ regmap_update_bits(regmap, SYSCTRL0, PB_CLK_BIT, PB_CLK_BIT);
+
+ /* HiFi4 Clock Enable: 1 enabled, 0 disabled */
+ regmap_update_bits(regmap, SYSCTRL0, HIFI4_CLK_BIT, HIFI4_CLK_BIT);
+
+ regmap_update_bits(regmap, SYSCTRL0, RESET_BIT, RESET_BIT);
+
+ usleep_range(1, 2);
+
+ /* Stall HIFI4 DSP Execution: 1 stall, 0 not stall */
+ regmap_update_bits(regmap, SYSCTRL0, EXECUTE_BIT, EXECUTE_BIT);
+ usleep_range(1, 2);
+
+ arm_smccc_smc(FSL_SIP_HIFI_XRDC, 0, 0, 0, 0, 0, 0, 0, &smc_res);
+
+ return smc_res.a0;
+}
+
+static int imx8ulp_probe(struct snd_sof_dev *sdev)
+{
+ struct imx_common_data *common;
+ struct regmap *regmap;
+
+ common = sdev->pdata->hw_pdata;
+
+ regmap = syscon_regmap_lookup_by_phandle(sdev->dev->of_node, "fsl,dsp-ctrl");
+ if (IS_ERR(regmap))
+ return dev_err_probe(sdev->dev, PTR_ERR(regmap),
+ "failed to fetch dsp ctrl regmap\n");
+
+ common->chip_pdata = regmap;
+
+ return 0;
+}
+
+static struct snd_soc_dai_driver imx8_dai[] = {
+ IMX_SOF_DAI_DRV_ENTRY_BIDIR("esai0", 1, 8),
+ IMX_SOF_DAI_DRV_ENTRY_BIDIR("sai1", 1, 32),
+};
+
+static struct snd_soc_dai_driver imx8m_dai[] = {
+ IMX_SOF_DAI_DRV_ENTRY_BIDIR("sai1", 1, 32),
+ IMX_SOF_DAI_DRV_ENTRY_BIDIR("sai2", 1, 32),
+ IMX_SOF_DAI_DRV_ENTRY_BIDIR("sai3", 1, 32),
+ IMX_SOF_DAI_DRV_ENTRY_BIDIR("sai5", 1, 32),
+ IMX_SOF_DAI_DRV_ENTRY_BIDIR("sai6", 1, 32),
+ IMX_SOF_DAI_DRV_ENTRY_BIDIR("sai7", 1, 32),
+ IMX_SOF_DAI_DRV_ENTRY("micfil", 0, 0, 1, 8),
+};
+
+static struct snd_soc_dai_driver imx8ulp_dai[] = {
+ IMX_SOF_DAI_DRV_ENTRY_BIDIR("sai5", 1, 32),
+ IMX_SOF_DAI_DRV_ENTRY_BIDIR("sai6", 1, 32),
+};
+
+static struct snd_sof_dsp_ops sof_imx8_ops;
+
+static int imx8_ops_init(struct snd_sof_dev *sdev)
+{
+ /* first copy from template */
+ memcpy(&sof_imx8_ops, &sof_imx_ops, sizeof(sof_imx_ops));
+
+ /* then set common imx8 ops */
+ sof_imx8_ops.dbg_dump = imx8_dump;
+ sof_imx8_ops.dsp_arch_ops = &sof_xtensa_arch_ops;
+ sof_imx8_ops.debugfs_add_region_item =
+ snd_sof_debugfs_add_region_item_iomem;
+
+ /* ... and finally set DAI driver */
+ sof_imx8_ops.drv = get_chip_info(sdev)->drv;
+ sof_imx8_ops.num_drv = get_chip_info(sdev)->num_drv;
+
+ return 0;
+}
+
+static const struct imx_chip_ops imx8_chip_ops = {
+ .probe = imx8_probe,
+ .core_kick = imx8_run,
+ .core_shutdown = imx8_shutdown,
+};
+
+static const struct imx_chip_ops imx8x_chip_ops = {
+ .probe = imx8_probe,
+ .core_kick = imx8x_run,
+ .core_shutdown = imx8_shutdown,
+};
+
+static const struct imx_chip_ops imx8m_chip_ops = {
+ .probe = imx8m_probe,
+ .core_kick = imx8m_run,
+ .core_reset = imx8m_reset,
+};
+
+static const struct imx_chip_ops imx8ulp_chip_ops = {
+ .probe = imx8ulp_probe,
+ .core_kick = imx8ulp_run,
+ .core_reset = imx8ulp_reset,
+};
+
+static struct imx_memory_info imx8_memory_regions[] = {
+ { .name = "iram", .reserved = false },
+ { .name = "sram", .reserved = true },
+ { }
+};
+
+static struct imx_memory_info imx8m_memory_regions[] = {
+ { .name = "iram", .reserved = false },
+ { .name = "sram", .reserved = true },
+ { }
+};
+
+static struct imx_memory_info imx8ulp_memory_regions[] = {
+ { .name = "iram", .reserved = false },
+ { .name = "sram", .reserved = true },
+ { }
+};
+
+static const struct imx_chip_info imx8_chip_info = {
+ .ipc_info = {
+ .has_panic_code = true,
+ .boot_mbox_offset = 0x800000,
+ .window_offset = 0x800000,
+ },
+ .memory = imx8_memory_regions,
+ .drv = imx8_dai,
+ .num_drv = ARRAY_SIZE(imx8_dai),
+ .ops = &imx8_chip_ops,
+};
+
+static const struct imx_chip_info imx8x_chip_info = {
+ .ipc_info = {
+ .has_panic_code = true,
+ .boot_mbox_offset = 0x800000,
+ .window_offset = 0x800000,
+ },
+ .memory = imx8_memory_regions,
+ .drv = imx8_dai,
+ .num_drv = ARRAY_SIZE(imx8_dai),
+ .ops = &imx8x_chip_ops,
+};
+
+static const struct imx_chip_info imx8m_chip_info = {
+ .ipc_info = {
+ .has_panic_code = true,
+ .boot_mbox_offset = 0x800000,
+ .window_offset = 0x800000,
+ },
+ .memory = imx8m_memory_regions,
+ .drv = imx8m_dai,
+ .num_drv = ARRAY_SIZE(imx8m_dai),
+ .ops = &imx8m_chip_ops,
+};
+
+static const struct imx_chip_info imx8ulp_chip_info = {
+ .ipc_info = {
+ .has_panic_code = true,
+ .boot_mbox_offset = 0x800000,
+ .window_offset = 0x800000,
+ },
+ .has_dma_reserved = true,
+ .memory = imx8ulp_memory_regions,
+ .drv = imx8ulp_dai,
+ .num_drv = ARRAY_SIZE(imx8ulp_dai),
+ .ops = &imx8ulp_chip_ops,
+};
+
+static struct snd_sof_of_mach sof_imx8_machs[] = {
+ {
+ .compatible = "fsl,imx8qxp-mek",
+ .sof_tplg_filename = "sof-imx8-wm8960.tplg",
+ .drv_name = "asoc-audio-graph-card2",
+ },
+ {
+ .compatible = "fsl,imx8qxp-mek-wcpu",
+ .sof_tplg_filename = "sof-imx8-wm8962.tplg",
+ .drv_name = "asoc-audio-graph-card2",
+ },
+ {
+ .compatible = "fsl,imx8qm-mek",
+ .sof_tplg_filename = "sof-imx8-wm8960.tplg",
+ .drv_name = "asoc-audio-graph-card2",
+ },
+ {
+ .compatible = "fsl,imx8qm-mek-revd",
+ .sof_tplg_filename = "sof-imx8-wm8962.tplg",
+ .drv_name = "asoc-audio-graph-card2",
+ },
+ {
+ .compatible = "fsl,imx8qxp-mek-bb",
+ .sof_tplg_filename = "sof-imx8-cs42888.tplg",
+ .drv_name = "asoc-audio-graph-card2",
+ },
+ {
+ .compatible = "fsl,imx8qm-mek-bb",
+ .sof_tplg_filename = "sof-imx8-cs42888.tplg",
+ .drv_name = "asoc-audio-graph-card2",
+ },
+ {
+ .compatible = "fsl,imx8mp-evk",
+ .sof_tplg_filename = "sof-imx8mp-wm8960.tplg",
+ .drv_name = "asoc-audio-graph-card2",
+ },
+ {
+ .compatible = "fsl,imx8mp-evk-revb4",
+ .sof_tplg_filename = "sof-imx8mp-wm8962.tplg",
+ .drv_name = "asoc-audio-graph-card2",
+ },
+ {
+ .compatible = "fsl,imx8ulp-evk",
+ .sof_tplg_filename = "sof-imx8ulp-btsco.tplg",
+ .drv_name = "asoc-audio-graph-card2",
+ },
+ {}
+};
+
+IMX_SOF_DEV_DESC(imx8, sof_imx8_machs, &imx8_chip_info, &sof_imx8_ops, imx8_ops_init);
+IMX_SOF_DEV_DESC(imx8x, sof_imx8_machs, &imx8x_chip_info, &sof_imx8_ops, imx8_ops_init);
+IMX_SOF_DEV_DESC(imx8m, sof_imx8_machs, &imx8m_chip_info, &sof_imx8_ops, imx8_ops_init);
+IMX_SOF_DEV_DESC(imx8ulp, sof_imx8_machs, &imx8ulp_chip_info, &sof_imx8_ops, imx8_ops_init);
+
+static const struct of_device_id sof_of_imx8_ids[] = {
+ {
+ .compatible = "fsl,imx8qxp-dsp",
+ .data = &IMX_SOF_DEV_DESC_NAME(imx8x),
+ },
+ {
+ .compatible = "fsl,imx8qm-dsp",
+ .data = &IMX_SOF_DEV_DESC_NAME(imx8),
+ },
+ {
+ .compatible = "fsl,imx8mp-dsp",
+ .data = &IMX_SOF_DEV_DESC_NAME(imx8m),
+ },
+ {
+ .compatible = "fsl,imx8ulp-dsp",
+ .data = &IMX_SOF_DEV_DESC_NAME(imx8ulp),
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sof_of_imx8_ids);
+
+/* DT driver definition */
+static struct platform_driver snd_sof_of_imx8_driver = {
+ .probe = sof_of_probe,
+ .remove = sof_of_remove,
+ .driver = {
+ .name = "sof-audio-of-imx8",
+ .pm = pm_ptr(&sof_of_pm),
+ .of_match_table = sof_of_imx8_ids,
+ },
+};
+module_platform_driver(snd_sof_of_imx8_driver);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("SOF support for IMX8 platforms");
+MODULE_IMPORT_NS("SND_SOC_SOF_XTENSA");
diff --git a/sound/soc/sof/imx/imx9.c b/sound/soc/sof/imx/imx9.c
new file mode 100644
index 000000000..e56e8a1c8
--- /dev/null
+++ b/sound/soc/sof/imx/imx9.c
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+/*
+ * Copyright 2025 NXP
+ */
+
+#include <linux/firmware/imx/sm.h>
+
+#include "imx-common.h"
+
+#define IMX95_M7_CPU_ID 0x1
+#define IMX95_M7_LM_ID 0x1
+
+static struct snd_soc_dai_driver imx95_dai[] = {
+ IMX_SOF_DAI_DRV_ENTRY_BIDIR("sai3", 1, 32),
+};
+
+static struct snd_sof_dsp_ops sof_imx9_ops;
+
+static int imx95_ops_init(struct snd_sof_dev *sdev)
+{
+ /* first copy from template */
+ memcpy(&sof_imx9_ops, &sof_imx_ops, sizeof(sof_imx_ops));
+
+ /* ... and finally set DAI driver */
+ sof_imx9_ops.drv = get_chip_info(sdev)->drv;
+ sof_imx9_ops.num_drv = get_chip_info(sdev)->num_drv;
+
+ return 0;
+}
+
+static int imx95_chip_probe(struct snd_sof_dev *sdev)
+{
+ struct platform_device *pdev;
+ struct resource *res;
+
+ pdev = to_platform_device(sdev->dev);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram");
+ if (!res)
+ return dev_err_probe(sdev->dev, -ENODEV,
+ "failed to fetch SRAM region\n");
+
+ return scmi_imx_lmm_reset_vector_set(IMX95_M7_LM_ID, IMX95_M7_CPU_ID,
+ 0, res->start);
+}
+
+static int imx95_core_kick(struct snd_sof_dev *sdev)
+{
+ return scmi_imx_lmm_operation(IMX95_M7_LM_ID, SCMI_IMX_LMM_BOOT, 0);
+}
+
+static int imx95_core_shutdown(struct snd_sof_dev *sdev)
+{
+ return scmi_imx_lmm_operation(IMX95_M7_LM_ID,
+ SCMI_IMX_LMM_SHUTDOWN,
+ SCMI_IMX_LMM_OP_FORCEFUL);
+}
+
+static const struct imx_chip_ops imx95_chip_ops = {
+ .probe = imx95_chip_probe,
+ .core_kick = imx95_core_kick,
+ .core_shutdown = imx95_core_shutdown,
+};
+
+static struct imx_memory_info imx95_memory_regions[] = {
+ { .name = "sram", .reserved = false },
+ { }
+};
+
+static const struct imx_chip_info imx95_chip_info = {
+ .ipc_info = {
+ .boot_mbox_offset = 0x6001000,
+ .window_offset = 0x6000000,
+ },
+ .has_dma_reserved = true,
+ .memory = imx95_memory_regions,
+ .drv = imx95_dai,
+ .num_drv = ARRAY_SIZE(imx95_dai),
+ .ops = &imx95_chip_ops,
+};
+
+static struct snd_sof_of_mach sof_imx9_machs[] = {
+ {
+ .compatible = "fsl,imx95-19x19-evk",
+ .sof_tplg_filename = "sof-imx95-wm8962.tplg",
+ .drv_name = "asoc-audio-graph-card2",
+ },
+ {
+ }
+};
+
+IMX_SOF_DEV_DESC(imx95, sof_imx9_machs, &imx95_chip_info, &sof_imx9_ops, imx95_ops_init);
+
+static const struct of_device_id sof_of_imx9_ids[] = {
+ {
+ .compatible = "fsl,imx95-cm7-sof",
+ .data = &IMX_SOF_DEV_DESC_NAME(imx95),
+ },
+ {
+ },
+};
+MODULE_DEVICE_TABLE(of, sof_of_imx9_ids);
+
+static struct platform_driver snd_sof_of_imx9_driver = {
+ .probe = sof_of_probe,
+ .remove = sof_of_remove,
+ .driver = {
+ .name = "sof-audio-of-imx9",
+ .pm = pm_ptr(&sof_of_pm),
+ .of_match_table = sof_of_imx9_ids,
+ },
+};
+module_platform_driver(snd_sof_of_imx9_driver);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("SOF driver for imx9 platforms");
+MODULE_AUTHOR("Laurentiu Mihalcea <laurentiu.mihalcea@nxp.com>");