aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/sof
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/sof')
-rw-r--r--sound/soc/sof/Kconfig7
-rw-r--r--sound/soc/sof/Makefile16
-rw-r--r--sound/soc/sof/amd/Kconfig1
-rw-r--r--sound/soc/sof/amd/acp-dsp-offset.h2
-rw-r--r--sound/soc/sof/amd/acp.c36
-rw-r--r--sound/soc/sof/amd/acp.h4
-rw-r--r--sound/soc/sof/amd/pci-rn.c4
-rw-r--r--sound/soc/sof/amd/renoir.c4
-rw-r--r--sound/soc/sof/compress.c73
-rw-r--r--sound/soc/sof/core.c7
-rw-r--r--sound/soc/sof/debug.c11
-rw-r--r--sound/soc/sof/imx/Kconfig1
-rw-r--r--sound/soc/sof/intel/Kconfig27
-rw-r--r--sound/soc/sof/intel/Makefile4
-rw-r--r--sound/soc/sof/intel/apl.c1
-rw-r--r--sound/soc/sof/intel/atom.c16
-rw-r--r--sound/soc/sof/intel/bdw.c7
-rw-r--r--sound/soc/sof/intel/byt.c5
-rw-r--r--sound/soc/sof/intel/cnl.c39
-rw-r--r--sound/soc/sof/intel/hda-dai.c226
-rw-r--r--sound/soc/sof/intel/hda-dsp.c26
-rw-r--r--sound/soc/sof/intel/hda-ipc.c39
-rw-r--r--sound/soc/sof/intel/hda-loader.c28
-rw-r--r--sound/soc/sof/intel/hda-probes.c16
-rw-r--r--sound/soc/sof/intel/hda-stream.c21
-rw-r--r--sound/soc/sof/intel/hda.c237
-rw-r--r--sound/soc/sof/intel/hda.h70
-rw-r--r--sound/soc/sof/intel/icl.c1
-rw-r--r--sound/soc/sof/intel/mtl.c794
-rw-r--r--sound/soc/sof/intel/mtl.h76
-rw-r--r--sound/soc/sof/intel/pci-apl.c1
-rw-r--r--sound/soc/sof/intel/pci-cnl.c1
-rw-r--r--sound/soc/sof/intel/pci-icl.c1
-rw-r--r--sound/soc/sof/intel/pci-mtl.c71
-rw-r--r--sound/soc/sof/intel/pci-tgl.c1
-rw-r--r--sound/soc/sof/intel/shim.h2
-rw-r--r--sound/soc/sof/intel/tgl.c34
-rw-r--r--sound/soc/sof/ipc.c24
-rw-r--r--sound/soc/sof/ipc3-dtrace.c55
-rw-r--r--sound/soc/sof/ipc3-loader.c17
-rw-r--r--sound/soc/sof/ipc3-pcm.c11
-rw-r--r--sound/soc/sof/ipc3-topology.c73
-rw-r--r--sound/soc/sof/ipc3.c36
-rw-r--r--sound/soc/sof/ipc4-control.c216
-rw-r--r--sound/soc/sof/ipc4-pcm.c234
-rw-r--r--sound/soc/sof/ipc4-priv.h7
-rw-r--r--sound/soc/sof/ipc4-topology.c1921
-rw-r--r--sound/soc/sof/ipc4-topology.h270
-rw-r--r--sound/soc/sof/ipc4.c45
-rw-r--r--sound/soc/sof/mediatek/Kconfig1
-rw-r--r--sound/soc/sof/mediatek/adsp_helper.h1
-rw-r--r--sound/soc/sof/mediatek/mt8186/mt8186-clk.c4
-rw-r--r--sound/soc/sof/mediatek/mt8195/mt8195-clk.c7
-rw-r--r--sound/soc/sof/mediatek/mt8195/mt8195-loader.c13
-rw-r--r--sound/soc/sof/mediatek/mt8195/mt8195.c50
-rw-r--r--sound/soc/sof/mediatek/mt8195/mt8195.h5
-rw-r--r--sound/soc/sof/ops.h6
-rw-r--r--sound/soc/sof/pcm.c13
-rw-r--r--sound/soc/sof/sof-audio.h12
-rw-r--r--sound/soc/sof/sof-client-ipc-msg-injector.c29
-rw-r--r--sound/soc/sof/sof-client-probes.c13
-rw-r--r--sound/soc/sof/sof-client-probes.h8
-rw-r--r--sound/soc/sof/sof-client.c4
-rw-r--r--sound/soc/sof/sof-priv.h15
-rw-r--r--sound/soc/sof/topology.c121
65 files changed, 4760 insertions, 361 deletions
diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig
index 4542868cd730..e90f173d067c 100644
--- a/sound/soc/sof/Kconfig
+++ b/sound/soc/sof/Kconfig
@@ -252,6 +252,13 @@ config SND_SOC_SOF_PROBE_WORK_QUEUE
When selected, the probe is handled in two steps, for example to
avoid lockdeps if request_module is used in the probe.
+# Supported IPC versions
+config SND_SOC_SOF_IPC3
+ bool
+
+config SND_SOC_SOF_INTEL_IPC4
+ bool
+
source "sound/soc/sof/amd/Kconfig"
source "sound/soc/sof/imx/Kconfig"
source "sound/soc/sof/intel/Kconfig"
diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile
index 92b5e83601be..9a74ed116ed9 100644
--- a/sound/soc/sof/Makefile
+++ b/sound/soc/sof/Makefile
@@ -1,10 +1,18 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
snd-sof-objs := core.o ops.o loader.o ipc.o pcm.o pm.o debug.o topology.o\
- control.o trace.o iomem-utils.o sof-audio.o stream-ipc.o\
- ipc3-topology.o ipc3-control.o ipc3.o ipc3-pcm.o ipc3-loader.o\
- ipc3-dtrace.o\
- ipc4.o ipc4-loader.o
+ control.o trace.o iomem-utils.o sof-audio.o stream-ipc.o
+
+# IPC implementations
+ifneq ($(CONFIG_SND_SOC_SOF_IPC3),)
+snd-sof-objs += ipc3.o ipc3-loader.o ipc3-topology.o ipc3-control.o ipc3-pcm.o\
+ ipc3-dtrace.o
+endif
+ifneq ($(CONFIG_SND_SOC_SOF_INTEL_IPC4),)
+snd-sof-objs += ipc4.o ipc4-loader.o ipc4-topology.o ipc4-control.o ipc4-pcm.o
+endif
+
+# SOF client support
ifneq ($(CONFIG_SND_SOC_SOF_CLIENT),)
snd-sof-objs += sof-client.o
endif
diff --git a/sound/soc/sof/amd/Kconfig b/sound/soc/sof/amd/Kconfig
index 085232e04582..190c85d57047 100644
--- a/sound/soc/sof/amd/Kconfig
+++ b/sound/soc/sof/amd/Kconfig
@@ -17,6 +17,7 @@ if SND_SOC_SOF_AMD_TOPLEVEL
config SND_SOC_SOF_AMD_COMMON
tristate
select SND_SOC_SOF
+ select SND_SOC_SOF_IPC3
select SND_SOC_SOF_PCI_DEV
select SND_AMD_ACP_CONFIG
select SND_SOC_ACPI if ACPI
diff --git a/sound/soc/sof/amd/acp-dsp-offset.h b/sound/soc/sof/amd/acp-dsp-offset.h
index 40fbf11facba..56cefd4a84fc 100644
--- a/sound/soc/sof/amd/acp-dsp-offset.h
+++ b/sound/soc/sof/amd/acp-dsp-offset.h
@@ -46,12 +46,14 @@
#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_8 0xC3C
#define ACPAXI2AXI_ATU_CTRL 0xC40
#define ACP_SOFT_RESET 0x1000
+#define ACP_CONTROL 0x1004
#define ACP_I2S_PIN_CONFIG 0x1400
/* Registers from ACP_PGFSM block */
#define ACP_PGFSM_CONTROL 0x141C
#define ACP_PGFSM_STATUS 0x1420
+#define ACP_CLKMUX_SEL 0x1424
/* Registers from ACP_INTR block */
#define ACP_EXTERNAL_INTR_ENB 0x1800
diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c
index 0c272573df97..c40d2900dd36 100644
--- a/sound/soc/sof/amd/acp.c
+++ b/sound/soc/sof/amd/acp.c
@@ -413,10 +413,46 @@ static int acp_init(struct snd_sof_dev *sdev)
dev_err(sdev->dev, "ACP power on failed\n");
return ret;
}
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, 0x01);
/* Reset */
return acp_reset(sdev);
}
+int amd_sof_acp_suspend(struct snd_sof_dev *sdev, u32 target_state)
+{
+ int ret;
+
+ ret = acp_reset(sdev);
+ if (ret) {
+ dev_err(sdev->dev, "ACP Reset failed\n");
+ return ret;
+ }
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, 0x00);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(amd_sof_acp_suspend, SND_SOC_SOF_AMD_COMMON);
+
+int amd_sof_acp_resume(struct snd_sof_dev *sdev)
+{
+ int ret;
+
+ ret = acp_init(sdev);
+ if (ret) {
+ dev_err(sdev->dev, "ACP Init failed\n");
+ return ret;
+ }
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CLKMUX_SEL, 0x03);
+
+ ret = acp_memory_init(sdev);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS(amd_sof_acp_resume, SND_SOC_SOF_AMD_COMMON);
+
int amd_sof_acp_probe(struct snd_sof_dev *sdev)
{
struct pci_dev *pci = to_pci_dev(sdev->dev);
diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h
index 291b44c54bcc..4c42b8fd6abf 100644
--- a/sound/soc/sof/amd/acp.h
+++ b/sound/soc/sof/amd/acp.h
@@ -216,6 +216,10 @@ int acp_sof_trace_init(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab,
struct sof_ipc_dma_trace_params_ext *dtrace_params);
int acp_sof_trace_release(struct snd_sof_dev *sdev);
+/* PM Callbacks */
+int amd_sof_acp_suspend(struct snd_sof_dev *sdev, u32 target_state);
+int amd_sof_acp_resume(struct snd_sof_dev *sdev);
+
struct sof_amd_acp_desc {
unsigned int host_bridge_id;
};
diff --git a/sound/soc/sof/amd/pci-rn.c b/sound/soc/sof/amd/pci-rn.c
index d5d9bcc2c997..3a7fed25a226 100644
--- a/sound/soc/sof/amd/pci-rn.c
+++ b/sound/soc/sof/amd/pci-rn.c
@@ -49,6 +49,7 @@ static const struct sof_amd_acp_desc renoir_chip_info = {
static const struct sof_dev_desc renoir_desc = {
.machines = snd_soc_acpi_amd_sof_machines,
+ .use_acpi_target_states = true,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
@@ -166,6 +167,9 @@ static struct pci_driver snd_sof_pci_amd_rn_driver = {
.id_table = rn_pci_ids,
.probe = acp_pci_rn_probe,
.remove = acp_pci_rn_remove,
+ .driver = {
+ .pm = &sof_pci_pm,
+ },
};
module_pci_driver(snd_sof_pci_amd_rn_driver);
diff --git a/sound/soc/sof/amd/renoir.c b/sound/soc/sof/amd/renoir.c
index 70190365328c..9261c8bc2236 100644
--- a/sound/soc/sof/amd/renoir.c
+++ b/sound/soc/sof/amd/renoir.c
@@ -173,6 +173,10 @@ struct snd_sof_dsp_ops sof_renoir_ops = {
/* Trace Logger */
.trace_init = acp_sof_trace_init,
.trace_release = acp_sof_trace_release,
+
+ /* PM */
+ .suspend = amd_sof_acp_suspend,
+ .resume = amd_sof_acp_resume,
};
EXPORT_SYMBOL(sof_renoir_ops);
diff --git a/sound/soc/sof/compress.c b/sound/soc/sof/compress.c
index 47639b6344c8..67139e15f862 100644
--- a/sound/soc/sof/compress.c
+++ b/sound/soc/sof/compress.c
@@ -167,11 +167,23 @@ static int sof_compr_set_params(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd = cstream->private_data;
struct snd_compr_runtime *crtd = cstream->runtime;
struct sof_ipc_pcm_params_reply ipc_params_reply;
+ struct sof_ipc_fw_ready *ready = &sdev->fw_ready;
+ struct sof_ipc_fw_version *v = &ready->version;
struct snd_compr_tstamp *tstamp;
- struct sof_ipc_pcm_params pcm;
+ struct sof_ipc_pcm_params *pcm;
struct snd_sof_pcm *spcm;
+ size_t ext_data_size;
int ret;
+ if (v->abi_version < SOF_ABI_VER(3, 22, 0)) {
+ dev_err(component->dev,
+ "Compress params not supported with FW ABI version %d:%d:%d\n",
+ SOF_ABI_VERSION_MAJOR(v->abi_version),
+ SOF_ABI_VERSION_MINOR(v->abi_version),
+ SOF_ABI_VERSION_PATCH(v->abi_version));
+ return -EINVAL;
+ }
+
tstamp = crtd->private_data;
spcm = snd_sof_find_spcm_dai(component, rtd);
@@ -179,40 +191,50 @@ static int sof_compr_set_params(struct snd_soc_component *component,
if (!spcm)
return -EINVAL;
+ ext_data_size = sizeof(params->codec);
+
+ if (sizeof(*pcm) + ext_data_size > sdev->ipc->max_payload_size)
+ return -EINVAL;
+
+ pcm = kzalloc(sizeof(*pcm) + ext_data_size, GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
cstream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG;
cstream->dma_buffer.dev.dev = sdev->dev;
ret = snd_compr_malloc_pages(cstream, crtd->buffer_size);
if (ret < 0)
- return ret;
+ goto out;
ret = create_page_table(component, cstream, crtd->dma_area, crtd->dma_bytes);
if (ret < 0)
- return ret;
-
- memset(&pcm, 0, sizeof(pcm));
-
- pcm.params.buffer.pages = PFN_UP(crtd->dma_bytes);
- pcm.hdr.size = sizeof(pcm);
- pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS;
-
- pcm.comp_id = spcm->stream[cstream->direction].comp_id;
- pcm.params.hdr.size = sizeof(pcm.params);
- pcm.params.buffer.phy_addr = spcm->stream[cstream->direction].page_table.addr;
- pcm.params.buffer.size = crtd->dma_bytes;
- pcm.params.direction = cstream->direction;
- pcm.params.channels = params->codec.ch_out;
- pcm.params.rate = params->codec.sample_rate;
- pcm.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED;
- pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE;
- pcm.params.sample_container_bytes =
+ goto out;
+
+ pcm->params.buffer.pages = PFN_UP(crtd->dma_bytes);
+ pcm->hdr.size = sizeof(*pcm) + ext_data_size;
+ pcm->hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS;
+
+ pcm->comp_id = spcm->stream[cstream->direction].comp_id;
+ pcm->params.hdr.size = sizeof(pcm->params) + ext_data_size;
+ pcm->params.buffer.phy_addr = spcm->stream[cstream->direction].page_table.addr;
+ pcm->params.buffer.size = crtd->dma_bytes;
+ pcm->params.direction = cstream->direction;
+ pcm->params.channels = params->codec.ch_out;
+ pcm->params.rate = params->codec.sample_rate;
+ pcm->params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED;
+ pcm->params.frame_fmt = SOF_IPC_FRAME_S32_LE;
+ pcm->params.sample_container_bytes =
snd_pcm_format_physical_width(SNDRV_PCM_FORMAT_S32) >> 3;
- pcm.params.host_period_bytes = params->buffer.fragment_size;
+ pcm->params.host_period_bytes = params->buffer.fragment_size;
+ pcm->params.ext_data_length = ext_data_size;
+
+ memcpy((u8 *)pcm->params.ext_data, &params->codec, ext_data_size);
- ret = sof_ipc_tx_message(sdev->ipc, &pcm, sizeof(pcm),
+ ret = sof_ipc_tx_message(sdev->ipc, pcm, sizeof(*pcm) + ext_data_size,
&ipc_params_reply, sizeof(ipc_params_reply));
if (ret < 0) {
dev_err(component->dev, "error ipc failed\n");
- return ret;
+ goto out;
}
tstamp->byte_offset = sdev->stream_box.offset + ipc_params_reply.posn_offset;
@@ -220,7 +242,10 @@ static int sof_compr_set_params(struct snd_soc_component *component,
spcm->prepared[cstream->direction] = true;
- return 0;
+out:
+ kfree(pcm);
+
+ return ret;
}
static int sof_compr_get_params(struct snd_soc_component *component,
diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c
index 53719c04658f..c99b5e6c026c 100644
--- a/sound/soc/sof/core.c
+++ b/sound/soc/sof/core.c
@@ -189,7 +189,7 @@ static int sof_probe_continue(struct snd_sof_dev *sdev)
ret = snd_sof_probe(sdev);
if (ret < 0) {
dev_err(sdev->dev, "error: failed to probe DSP %d\n", ret);
- return ret;
+ goto probe_err;
}
sof_set_fw_state(sdev, SOF_FW_BOOT_PREPARE);
@@ -317,6 +317,8 @@ dbg_err:
snd_sof_free_debug(sdev);
dsp_err:
snd_sof_remove(sdev);
+probe_err:
+ sof_ops_free(sdev);
/* all resources freed, update state to match */
sof_set_fw_state(sdev, SOF_FW_BOOT_NOT_STARTED);
@@ -374,6 +376,7 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data)
!sof_ops(sdev)->block_read || !sof_ops(sdev)->block_write ||
!sof_ops(sdev)->send_msg || !sof_ops(sdev)->load_firmware ||
!sof_ops(sdev)->ipc_msg_data) {
+ sof_ops_free(sdev);
dev_err(dev, "error: missing mandatory ops\n");
return -EINVAL;
}
@@ -457,6 +460,8 @@ int snd_sof_device_remove(struct device *dev)
snd_sof_remove(sdev);
}
+ sof_ops_free(sdev);
+
/* release firmware */
snd_sof_fw_unload(sdev);
diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c
index cf1271eb29b2..d9a3ce7b69e1 100644
--- a/sound/soc/sof/debug.c
+++ b/sound/soc/sof/debug.c
@@ -252,9 +252,9 @@ static int memory_info_update(struct snd_sof_dev *sdev, char *buf, size_t buff_s
}
for (i = 0, len = 0; i < reply->num_elems; i++) {
- ret = snprintf(buf + len, buff_size - len, "zone %d.%d used %#8x free %#8x\n",
- reply->elems[i].zone, reply->elems[i].id,
- reply->elems[i].used, reply->elems[i].free);
+ ret = scnprintf(buf + len, buff_size - len, "zone %d.%d used %#8x free %#8x\n",
+ reply->elems[i].zone, reply->elems[i].id,
+ reply->elems[i].used, reply->elems[i].free);
if (ret < 0)
goto error;
len += ret;
@@ -428,7 +428,7 @@ static void snd_sof_ipc_dump(struct snd_sof_dev *sdev)
}
}
-void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev)
+void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev, const char *msg)
{
if (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT) ||
sof_debug_check_flag(SOF_DBG_RETAIN_CTX)) {
@@ -441,8 +441,7 @@ void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev)
/* dump vital information to the logs */
snd_sof_ipc_dump(sdev);
- snd_sof_dsp_dbg_dump(sdev, "Firmware exception",
- SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX);
+ snd_sof_dsp_dbg_dump(sdev, msg, SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX);
sof_fw_trace_fw_crashed(sdev);
}
EXPORT_SYMBOL(snd_sof_handle_fw_exception);
diff --git a/sound/soc/sof/imx/Kconfig b/sound/soc/sof/imx/Kconfig
index 9b8d5bb1e449..cc6e695f913a 100644
--- a/sound/soc/sof/imx/Kconfig
+++ b/sound/soc/sof/imx/Kconfig
@@ -15,6 +15,7 @@ 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
diff --git a/sound/soc/sof/intel/Kconfig b/sound/soc/sof/intel/Kconfig
index 0def2aa5581d..3f54678e810b 100644
--- a/sound/soc/sof/intel/Kconfig
+++ b/sound/soc/sof/intel/Kconfig
@@ -40,6 +40,7 @@ if SND_SOC_SOF_ACPI
config SND_SOC_SOF_BAYTRAIL
tristate "SOF support for Baytrail, Braswell and Cherrytrail"
default SND_SOC_SOF_ACPI
+ select SND_SOC_SOF_IPC3
select SND_SOC_SOF_INTEL_COMMON
select SND_SOC_SOF_INTEL_ATOM_HIFI_EP
select SND_SOC_SOF_ACPI_DEV
@@ -60,6 +61,7 @@ config SND_SOC_SOF_BAYTRAIL
config SND_SOC_SOF_BROADWELL
tristate "SOF support for Broadwell"
default SND_SOC_SOF_ACPI
+ select SND_SOC_SOF_IPC3
select SND_SOC_SOF_INTEL_COMMON
select SND_SOC_SOF_INTEL_HIFI_EP_IPC
select SND_SOC_SOF_ACPI_DEV
@@ -85,6 +87,7 @@ config SND_SOC_SOF_MERRIFIELD
tristate "SOF support for Tangier/Merrifield"
default SND_SOC_SOF_PCI
select SND_SOC_SOF_PCI_DEV
+ select SND_SOC_SOF_IPC3
select SND_SOC_SOF_INTEL_ATOM_HIFI_EP
help
This adds support for Sound Open Firmware for Intel(R) platforms
@@ -95,6 +98,8 @@ config SND_SOC_SOF_MERRIFIELD
config SND_SOC_SOF_INTEL_APL
tristate
select SND_SOC_SOF_HDA_COMMON
+ select SND_SOC_SOF_IPC3
+ select SND_SOC_SOF_INTEL_IPC4
config SND_SOC_SOF_APOLLOLAKE
tristate "SOF support for Apollolake"
@@ -120,6 +125,8 @@ config SND_SOC_SOF_INTEL_CNL
tristate
select SND_SOC_SOF_HDA_COMMON
select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
+ select SND_SOC_SOF_IPC3
+ select SND_SOC_SOF_INTEL_IPC4
config SND_SOC_SOF_CANNONLAKE
tristate "SOF support for Cannonlake"
@@ -154,6 +161,8 @@ config SND_SOC_SOF_INTEL_ICL
tristate
select SND_SOC_SOF_HDA_COMMON
select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
+ select SND_SOC_SOF_IPC3
+ select SND_SOC_SOF_INTEL_IPC4
config SND_SOC_SOF_ICELAKE
tristate "SOF support for Icelake"
@@ -179,6 +188,8 @@ config SND_SOC_SOF_INTEL_TGL
tristate
select SND_SOC_SOF_HDA_COMMON
select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
+ select SND_SOC_SOF_IPC3
+ select SND_SOC_SOF_INTEL_IPC4
config SND_SOC_SOF_TIGERLAKE
tristate "SOF support for Tigerlake"
@@ -210,6 +221,22 @@ config SND_SOC_SOF_ALDERLAKE
Say Y if you have such a device.
If unsure select "N".
+config SND_SOC_SOF_INTEL_MTL
+ tristate
+ select SND_SOC_SOF_HDA_COMMON
+ select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
+ select SND_SOC_SOF_INTEL_IPC4
+
+config SND_SOC_SOF_METEORLAKE
+ tristate "SOF support for Meteorlake"
+ default SND_SOC_SOF_PCI
+ select SND_SOC_SOF_INTEL_MTL
+ help
+ This adds support for Sound Open Firmware for Intel(R) platforms
+ using the Meteorlake processors.
+ Say Y if you have such a device.
+ If unsure select "N".
+
config SND_SOC_SOF_HDA_COMMON
tristate
select SND_SOC_SOF_INTEL_COMMON
diff --git a/sound/soc/sof/intel/Makefile b/sound/soc/sof/intel/Makefile
index b9d51dc39ffa..a079159bb2f0 100644
--- a/sound/soc/sof/intel/Makefile
+++ b/sound/soc/sof/intel/Makefile
@@ -6,7 +6,7 @@ snd-sof-acpi-intel-bdw-objs := bdw.o
snd-sof-intel-hda-common-objs := hda.o hda-loader.o hda-stream.o hda-trace.o \
hda-dsp.o hda-ipc.o hda-ctrl.o hda-pcm.o \
hda-dai.o hda-bus.o \
- apl.o cnl.o tgl.o icl.o hda-common-ops.o
+ apl.o cnl.o tgl.o icl.o mtl.o hda-common-ops.o
snd-sof-intel-hda-common-$(CONFIG_SND_SOC_SOF_HDA_PROBES) += hda-probes.o
snd-sof-intel-hda-objs := hda-codec.o
@@ -24,9 +24,11 @@ snd-sof-pci-intel-apl-objs := pci-apl.o
snd-sof-pci-intel-cnl-objs := pci-cnl.o
snd-sof-pci-intel-icl-objs := pci-icl.o
snd-sof-pci-intel-tgl-objs := pci-tgl.o
+snd-sof-pci-intel-mtl-objs := pci-mtl.o
obj-$(CONFIG_SND_SOC_SOF_MERRIFIELD) += snd-sof-pci-intel-tng.o
obj-$(CONFIG_SND_SOC_SOF_INTEL_APL) += snd-sof-pci-intel-apl.o
obj-$(CONFIG_SND_SOC_SOF_INTEL_CNL) += snd-sof-pci-intel-cnl.o
obj-$(CONFIG_SND_SOC_SOF_INTEL_ICL) += snd-sof-pci-intel-icl.o
obj-$(CONFIG_SND_SOC_SOF_INTEL_TGL) += snd-sof-pci-intel-tgl.o
+obj-$(CONFIG_SND_SOC_SOF_INTEL_MTL) += snd-sof-pci-intel-mtl.o
diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c
index 0cea280a6d2d..084c245a9522 100644
--- a/sound/soc/sof/intel/apl.c
+++ b/sound/soc/sof/intel/apl.c
@@ -101,6 +101,7 @@ const struct sof_intel_dsp_desc apl_chip_info = {
.ssp_base_offset = APL_SSP_BASE_OFFSET,
.quirks = SOF_INTEL_PROCEN_FMT_QUIRK,
.check_ipc_irq = hda_dsp_check_ipc_irq,
+ .cl_init = cl_dsp_init,
.hw_ip_version = SOF_INTEL_CAVS_1_5_PLUS,
};
EXPORT_SYMBOL_NS(apl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
diff --git a/sound/soc/sof/intel/atom.c b/sound/soc/sof/intel/atom.c
index ff5900b155dc..bd9789b483b1 100644
--- a/sound/soc/sof/intel/atom.c
+++ b/sound/soc/sof/intel/atom.c
@@ -274,22 +274,22 @@ static const char *fixup_tplg_name(struct snd_sof_dev *sdev,
const char *ssp_str)
{
const char *tplg_filename = NULL;
- char *filename;
- char *split_ext;
+ const char *split_ext;
+ char *filename, *tmp;
- filename = devm_kstrdup(sdev->dev, sof_tplg_filename, GFP_KERNEL);
+ filename = kstrdup(sof_tplg_filename, GFP_KERNEL);
if (!filename)
return NULL;
/* this assumes a .tplg extension */
- split_ext = strsep(&filename, ".");
- if (split_ext) {
+ tmp = filename;
+ split_ext = strsep(&tmp, ".");
+ if (split_ext)
tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL,
"%s-%s.tplg",
split_ext, ssp_str);
- if (!tplg_filename)
- return NULL;
- }
+ kfree(filename);
+
return tplg_filename;
}
diff --git a/sound/soc/sof/intel/bdw.c b/sound/soc/sof/intel/bdw.c
index 26df780c702e..a446154f2803 100644
--- a/sound/soc/sof/intel/bdw.c
+++ b/sound/soc/sof/intel/bdw.c
@@ -681,11 +681,8 @@ static int sof_broadwell_probe(struct platform_device *pdev)
return -ENODEV;
}
- desc = device_get_match_data(dev);
- if (!desc)
- return -ENODEV;
-
- return sof_acpi_probe(pdev, device_get_match_data(dev));
+ desc = (const struct sof_dev_desc *)id->driver_data;
+ return sof_acpi_probe(pdev, desc);
}
/* acpi_driver definition */
diff --git a/sound/soc/sof/intel/byt.c b/sound/soc/sof/intel/byt.c
index 4ed8381eceda..e6dc4ff531c3 100644
--- a/sound/soc/sof/intel/byt.c
+++ b/sound/soc/sof/intel/byt.c
@@ -465,10 +465,7 @@ static int sof_baytrail_probe(struct platform_device *pdev)
return -ENODEV;
}
- desc = device_get_match_data(&pdev->dev);
- if (!desc)
- return -ENODEV;
-
+ desc = (const struct sof_dev_desc *)id->driver_data;
if (desc == &sof_acpi_baytrail_desc && soc_intel_is_byt_cr(pdev))
desc = &sof_acpi_baytrailcr_desc;
diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c
index cd6e5f8a5eb4..a064453f0bc3 100644
--- a/sound/soc/sof/intel/cnl.c
+++ b/sound/soc/sof/intel/cnl.c
@@ -60,17 +60,23 @@ irqreturn_t cnl_ipc4_irq_thread(int irq, void *context)
if (primary & SOF_IPC4_MSG_DIR_MASK) {
/* Reply received */
- struct sof_ipc4_msg *data = sdev->ipc->msg.reply_data;
+ if (likely(sdev->fw_state == SOF_FW_BOOT_COMPLETE)) {
+ struct sof_ipc4_msg *data = sdev->ipc->msg.reply_data;
- data->primary = primary;
- data->extension = extension;
+ data->primary = primary;
+ data->extension = extension;
- spin_lock_irq(&sdev->ipc_lock);
+ spin_lock_irq(&sdev->ipc_lock);
- snd_sof_ipc_get_reply(sdev);
- snd_sof_ipc_reply(sdev, data->primary);
+ snd_sof_ipc_get_reply(sdev);
+ snd_sof_ipc_reply(sdev, data->primary);
- spin_unlock_irq(&sdev->ipc_lock);
+ spin_unlock_irq(&sdev->ipc_lock);
+ } else {
+ dev_dbg_ratelimited(sdev->dev,
+ "IPC reply before FW_READY: %#x|%#x\n",
+ primary, extension);
+ }
} else {
/* Notification received */
notification_data.primary = primary;
@@ -124,15 +130,20 @@ irqreturn_t cnl_ipc_irq_thread(int irq, void *context)
CNL_DSP_REG_HIPCCTL,
CNL_DSP_REG_HIPCCTL_DONE, 0);
- spin_lock_irq(&sdev->ipc_lock);
+ if (likely(sdev->fw_state == SOF_FW_BOOT_COMPLETE)) {
+ spin_lock_irq(&sdev->ipc_lock);
- /* handle immediate reply from DSP core */
- hda_dsp_ipc_get_reply(sdev);
- snd_sof_ipc_reply(sdev, msg);
+ /* handle immediate reply from DSP core */
+ hda_dsp_ipc_get_reply(sdev);
+ snd_sof_ipc_reply(sdev, msg);
- cnl_ipc_dsp_done(sdev);
+ cnl_ipc_dsp_done(sdev);
- spin_unlock_irq(&sdev->ipc_lock);
+ spin_unlock_irq(&sdev->ipc_lock);
+ } else {
+ dev_dbg_ratelimited(sdev->dev, "IPC reply before FW_READY: %#x\n",
+ msg);
+ }
ipc_irq = true;
}
@@ -401,6 +412,7 @@ const struct sof_intel_dsp_desc cnl_chip_info = {
.sdw_alh_base = SDW_ALH_BASE,
.check_sdw_irq = hda_common_check_sdw_irq,
.check_ipc_irq = hda_dsp_check_ipc_irq,
+ .cl_init = cl_dsp_init,
.hw_ip_version = SOF_INTEL_CAVS_1_8,
};
EXPORT_SYMBOL_NS(cnl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
@@ -430,6 +442,7 @@ const struct sof_intel_dsp_desc jsl_chip_info = {
.sdw_alh_base = SDW_ALH_BASE,
.check_sdw_irq = hda_common_check_sdw_irq,
.check_ipc_irq = hda_dsp_check_ipc_irq,
+ .cl_init = cl_dsp_init,
.hw_ip_version = SOF_INTEL_CAVS_2_0,
};
EXPORT_SYMBOL_NS(jsl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c
index 9823230d2ef4..556e883a32ed 100644
--- a/sound/soc/sof/intel/hda-dai.c
+++ b/sound/soc/sof/intel/hda-dai.c
@@ -10,10 +10,23 @@
#include <sound/pcm_params.h>
#include <sound/hdaudio_ext.h>
+#include <sound/intel-nhlt.h>
+#include <sound/sof/ipc4/header.h>
+#include <uapi/sound/sof/header.h>
+#include "../ipc4-priv.h"
+#include "../ipc4-topology.h"
#include "../sof-priv.h"
#include "../sof-audio.h"
#include "hda.h"
+/*
+ * The default method is to fetch NHLT from BIOS. With this parameter set
+ * it is possible to override that with NHLT in the SOF topology manifest.
+ */
+static bool hda_use_tplg_nhlt;
+module_param_named(sof_use_tplg_nhlt, hda_use_tplg_nhlt, bool, 0444);
+MODULE_PARM_DESC(sof_use_tplg_nhlt, "SOF topology nhlt override");
+
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
struct hda_pipe_params {
@@ -113,12 +126,8 @@ hda_link_stream_assign(struct hdac_bus *bus,
}
if (res) {
- /*
- * Decouple host and link DMA. The decoupled flag
- * is updated in snd_hdac_ext_stream_decouple().
- */
- if (!res->decoupled)
- snd_hdac_ext_stream_decouple_locked(bus, res, true);
+ /* Make sure that host and link DMA is decoupled. */
+ snd_hdac_ext_stream_decouple_locked(bus, res, true);
res->link_locked = 1;
res->link_substream = substream;
@@ -171,7 +180,6 @@ static int hda_link_dma_params(struct hdac_ext_stream *hext_stream,
struct hdac_ext_link *link;
unsigned int format_val;
- snd_hdac_ext_stream_decouple(bus, hext_stream, true);
snd_hdac_ext_link_stream_reset(hext_stream);
format_val = snd_hdac_calc_stream_format(params->s_freq, params->ch,
@@ -208,7 +216,6 @@ static int hda_link_dma_hw_params(struct snd_pcm_substream *substream,
struct hdac_bus *bus = hstream->bus;
struct hdac_ext_link *link;
- /* get stored dma data if resuming from system suspend */
hext_stream = snd_soc_dai_get_dma_data(cpu_dai, substream);
if (!hext_stream) {
hext_stream = hda_link_stream_assign(bus, substream);
@@ -257,7 +264,6 @@ static int hda_link_dma_trigger(struct snd_pcm_substream *substream, int cmd)
struct hdac_ext_stream *hext_stream = snd_soc_dai_get_dma_data(cpu_dai, substream);
int ret;
- dev_dbg(cpu_dai->dev, "%s: cmd=%d\n", __func__, cmd);
if (!hext_stream)
return 0;
@@ -369,8 +375,7 @@ static int hda_dai_config_pause_push_ipc(struct snd_soc_dapm_widget *w)
return ret;
}
-static int ipc3_hda_dai_prepare(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
+static int hda_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
struct hdac_ext_stream *hext_stream =
snd_soc_dai_get_dma_data(dai, substream);
@@ -382,7 +387,7 @@ static int ipc3_hda_dai_prepare(struct snd_pcm_substream *substream,
if (hext_stream && hext_stream->link_prepared)
return 0;
- dev_dbg(sdev->dev, "%s: prepare stream dir %d\n", __func__, substream->stream);
+ dev_dbg(sdev->dev, "prepare stream dir %d\n", substream->stream);
ret = hda_link_dma_prepare(substream);
if (ret < 0)
@@ -408,13 +413,15 @@ static int ipc3_hda_dai_trigger(struct snd_pcm_substream *substream,
struct snd_soc_dapm_widget *w;
int ret;
+ dev_dbg(dai->dev, "cmd=%d dai %s direction %d\n", cmd,
+ dai->name, substream->stream);
+
ret = hda_link_dma_trigger(substream, cmd);
if (ret < 0)
return ret;
w = snd_soc_dai_get_widget(dai, substream->stream);
- dev_dbg(dai->dev, "%s: cmd=%d\n", __func__, cmd);
switch (cmd) {
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
@@ -438,6 +445,91 @@ static int ipc3_hda_dai_trigger(struct snd_pcm_substream *substream,
return 0;
}
+/*
+ * In contrast to IPC3, the dai trigger in IPC4 mixes pipeline state changes
+ * (over IPC channel) and DMA state change (direct host register changes).
+ */
+static int ipc4_hda_dai_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ struct hdac_ext_stream *hext_stream = snd_soc_dai_get_dma_data(dai, substream);
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component);
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_sof_widget *swidget;
+ struct snd_soc_dapm_widget *w;
+ struct snd_soc_dai *codec_dai;
+ struct hdac_stream *hstream;
+ struct snd_soc_dai *cpu_dai;
+ int ret;
+
+ dev_dbg(dai->dev, "cmd=%d dai %s direction %d\n", cmd,
+ dai->name, substream->stream);
+
+ hstream = substream->runtime->private_data;
+ rtd = asoc_substream_to_rtd(substream);
+ cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ codec_dai = asoc_rtd_to_codec(rtd, 0);
+
+ w = snd_soc_dai_get_widget(dai, substream->stream);
+ swidget = w->dobj.private;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ snd_hdac_ext_link_stream_start(hext_stream);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ {
+ struct snd_sof_widget *pipe_widget = swidget->pipe_widget;
+ struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+
+ ret = sof_ipc4_set_pipeline_state(sdev, swidget->pipeline_id,
+ SOF_IPC4_PIPE_PAUSED);
+ if (ret < 0)
+ return ret;
+
+ pipeline->state = SOF_IPC4_PIPE_PAUSED;
+
+ snd_hdac_ext_link_stream_clear(hext_stream);
+
+ ret = sof_ipc4_set_pipeline_state(sdev, swidget->pipeline_id,
+ SOF_IPC4_PIPE_RESET);
+ if (ret < 0)
+ return ret;
+
+ pipeline->state = SOF_IPC4_PIPE_RESET;
+
+ ret = hda_link_dma_cleanup(substream, hstream, cpu_dai, codec_dai, false);
+ if (ret < 0) {
+ dev_err(sdev->dev, "%s: failed to clean up link DMA\n", __func__);
+ return ret;
+ }
+ break;
+ }
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ {
+ struct snd_sof_widget *pipe_widget = swidget->pipe_widget;
+ struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+
+ ret = sof_ipc4_set_pipeline_state(sdev, swidget->pipeline_id,
+ SOF_IPC4_PIPE_PAUSED);
+ if (ret < 0)
+ return ret;
+
+ pipeline->state = SOF_IPC4_PIPE_PAUSED;
+
+ snd_hdac_ext_link_stream_clear(hext_stream);
+ break;
+ }
+ default:
+ dev_err(sdev->dev, "%s: unknown trigger command %d\n", __func__, cmd);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int hda_dai_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
@@ -454,7 +546,7 @@ static const struct snd_soc_dai_ops ipc3_hda_dai_ops = {
.hw_params = hda_dai_hw_params,
.hw_free = hda_dai_hw_free,
.trigger = ipc3_hda_dai_trigger,
- .prepare = ipc3_hda_dai_prepare,
+ .prepare = hda_dai_prepare,
};
static int hda_dai_suspend(struct hdac_bus *bus)
@@ -497,6 +589,14 @@ static int hda_dai_suspend(struct hdac_bus *bus)
return 0;
}
+
+static const struct snd_soc_dai_ops ipc4_hda_dai_ops = {
+ .hw_params = hda_dai_hw_params,
+ .hw_free = hda_dai_hw_free,
+ .trigger = ipc4_hda_dai_trigger,
+ .prepare = hda_dai_prepare,
+};
+
#endif
/* only one flag used so far to harden hw_params/hw_free/trigger/prepare */
@@ -608,6 +708,64 @@ static const struct snd_soc_dai_ops ipc3_ssp_dai_ops = {
.shutdown = ssp_dai_shutdown,
};
+static int ipc4_be_dai_common_trigger(struct snd_soc_dai *dai, int cmd, int stream)
+{
+ struct snd_sof_widget *pipe_widget;
+ struct sof_ipc4_pipeline *pipeline;
+ struct snd_sof_widget *swidget;
+ struct snd_soc_dapm_widget *w;
+ struct snd_sof_dev *sdev;
+ int ret;
+
+ w = snd_soc_dai_get_widget(dai, stream);
+ swidget = w->dobj.private;
+ pipe_widget = swidget->pipe_widget;
+ pipeline = pipe_widget->private;
+ sdev = snd_soc_component_get_drvdata(swidget->scomp);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ ret = sof_ipc4_set_pipeline_state(sdev, swidget->pipeline_id,
+ SOF_IPC4_PIPE_PAUSED);
+ if (ret < 0)
+ return ret;
+ pipeline->state = SOF_IPC4_PIPE_PAUSED;
+
+ ret = sof_ipc4_set_pipeline_state(sdev, swidget->pipeline_id,
+ SOF_IPC4_PIPE_RESET);
+ if (ret < 0)
+ return ret;
+ pipeline->state = SOF_IPC4_PIPE_RESET;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = sof_ipc4_set_pipeline_state(sdev, swidget->pipeline_id,
+ SOF_IPC4_PIPE_PAUSED);
+ if (ret < 0)
+ return ret;
+ pipeline->state = SOF_IPC4_PIPE_PAUSED;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int ipc4_be_dai_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ return ipc4_be_dai_common_trigger(dai, cmd, substream->stream);
+}
+
+static const struct snd_soc_dai_ops ipc4_dmic_dai_ops = {
+ .trigger = ipc4_be_dai_trigger,
+};
+
+static const struct snd_soc_dai_ops ipc4_ssp_dai_ops = {
+ .trigger = ipc4_be_dai_trigger,
+};
+
void hda_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops)
{
int i;
@@ -627,11 +785,51 @@ void hda_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops)
#endif
}
break;
+ case SOF_INTEL_IPC4:
+ {
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+
+ for (i = 0; i < ops->num_drv; i++) {
+ if (strstr(ops->drv[i].name, "DMIC")) {
+ ops->drv[i].ops = &ipc4_dmic_dai_ops;
+ continue;
+ }
+ if (strstr(ops->drv[i].name, "SSP")) {
+ ops->drv[i].ops = &ipc4_ssp_dai_ops;
+ continue;
+ }
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
+ if (strstr(ops->drv[i].name, "iDisp") ||
+ strstr(ops->drv[i].name, "Analog") ||
+ strstr(ops->drv[i].name, "Digital"))
+ ops->drv[i].ops = &ipc4_hda_dai_ops;
+#endif
+ }
+
+ if (!hda_use_tplg_nhlt)
+ ipc4_data->nhlt = intel_nhlt_init(sdev->dev);
+
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE))
+ sdw_callback.trigger = ipc4_be_dai_common_trigger;
+
+ break;
+ }
default:
break;
}
}
+void hda_ops_free(struct snd_sof_dev *sdev)
+{
+ if (sdev->pdata->ipc_type == SOF_INTEL_IPC4) {
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+
+ if (!hda_use_tplg_nhlt)
+ intel_nhlt_free(ipc4_data->nhlt);
+ }
+}
+EXPORT_SYMBOL_NS(hda_ops_free, SND_SOC_SOF_INTEL_HDA_COMMON);
+
/*
* common dai driver for skl+ platforms.
* some products who use this DAI array only physically have a subset of
diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c
index e24eea725acb..eddfd77ad90f 100644
--- a/sound/soc/sof/intel/hda-dsp.c
+++ b/sound/soc/sof/intel/hda-dsp.c
@@ -617,6 +617,13 @@ static int hda_suspend(struct snd_sof_dev *sdev, bool runtime_suspend)
#endif
int ret, j;
+ /*
+ * The memory used for IMR boot loses its content in deeper than S3 state
+ * We must not try IMR boot on next power up (as it will fail).
+ */
+ if (sdev->system_suspend_target > SOF_SUSPEND_S3)
+ hda->skip_imr_boot = true;
+
hda_sdw_int_enable(sdev, false);
/* disable IPC interrupts */
@@ -743,7 +750,7 @@ int hda_dsp_resume(struct snd_sof_dev *sdev)
if (hlink->ref_count) {
ret = snd_hdac_ext_bus_link_power_up(hlink);
if (ret < 0) {
- dev_dbg(sdev->dev,
+ dev_err(sdev->dev,
"error %d in %s: failed to power up links",
ret, __func__);
return ret;
@@ -871,7 +878,7 @@ int hda_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state)
/* no link can be powered in s0ix state */
ret = snd_hdac_ext_bus_link_power_down_all(bus);
if (ret < 0) {
- dev_dbg(sdev->dev,
+ dev_err(sdev->dev,
"error %d in %s: failed to power down links",
ret, __func__);
return ret;
@@ -940,13 +947,7 @@ void hda_dsp_d0i3_work(struct work_struct *work)
int hda_dsp_core_get(struct snd_sof_dev *sdev, int core)
{
- struct sof_ipc_pm_core_config pm_core_config = {
- .hdr = {
- .cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CORE_ENABLE,
- .size = sizeof(pm_core_config),
- },
- .enable_mask = sdev->enabled_cores_mask | BIT(core),
- };
+ const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm;
int ret, ret1;
/* power up core */
@@ -961,9 +962,12 @@ int hda_dsp_core_get(struct snd_sof_dev *sdev, int core)
if (sdev->fw_state != SOF_FW_BOOT_COMPLETE || core == SOF_DSP_PRIMARY_CORE)
return 0;
+ /* No need to continue the set_core_state ops is not available */
+ if (!pm_ops->set_core_state)
+ return 0;
+
/* Now notify DSP for secondary cores */
- ret = sof_ipc_tx_message(sdev->ipc, &pm_core_config, sizeof(pm_core_config),
- &pm_core_config, sizeof(pm_core_config));
+ ret = pm_ops->set_core_state(sdev, core, true);
if (ret < 0) {
dev_err(sdev->dev, "failed to enable secondary core '%d' failed with %d\n",
core, ret);
diff --git a/sound/soc/sof/intel/hda-ipc.c b/sound/soc/sof/intel/hda-ipc.c
index f08011249955..65e688f749ea 100644
--- a/sound/soc/sof/intel/hda-ipc.c
+++ b/sound/soc/sof/intel/hda-ipc.c
@@ -148,17 +148,23 @@ irqreturn_t hda_dsp_ipc4_irq_thread(int irq, void *context)
if (primary & SOF_IPC4_MSG_DIR_MASK) {
/* Reply received */
- struct sof_ipc4_msg *data = sdev->ipc->msg.reply_data;
+ if (likely(sdev->fw_state == SOF_FW_BOOT_COMPLETE)) {
+ struct sof_ipc4_msg *data = sdev->ipc->msg.reply_data;
- data->primary = primary;
- data->extension = extension;
+ data->primary = primary;
+ data->extension = extension;
- spin_lock_irq(&sdev->ipc_lock);
+ spin_lock_irq(&sdev->ipc_lock);
- snd_sof_ipc_get_reply(sdev);
- snd_sof_ipc_reply(sdev, data->primary);
+ snd_sof_ipc_get_reply(sdev);
+ snd_sof_ipc_reply(sdev, data->primary);
- spin_unlock_irq(&sdev->ipc_lock);
+ spin_unlock_irq(&sdev->ipc_lock);
+ } else {
+ dev_dbg_ratelimited(sdev->dev,
+ "IPC reply before FW_READY: %#x|%#x\n",
+ primary, extension);
+ }
} else {
/* Notification received */
@@ -225,16 +231,21 @@ irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context)
* place, the message might not yet be marked as expecting a
* reply.
*/
- spin_lock_irq(&sdev->ipc_lock);
+ if (likely(sdev->fw_state == SOF_FW_BOOT_COMPLETE)) {
+ spin_lock_irq(&sdev->ipc_lock);
- /* handle immediate reply from DSP core */
- hda_dsp_ipc_get_reply(sdev);
- snd_sof_ipc_reply(sdev, msg);
+ /* handle immediate reply from DSP core */
+ hda_dsp_ipc_get_reply(sdev);
+ snd_sof_ipc_reply(sdev, msg);
- /* set the done bit */
- hda_dsp_ipc_dsp_done(sdev);
+ /* set the done bit */
+ hda_dsp_ipc_dsp_done(sdev);
- spin_unlock_irq(&sdev->ipc_lock);
+ spin_unlock_irq(&sdev->ipc_lock);
+ } else {
+ dev_dbg_ratelimited(sdev->dev, "IPC reply before FW_READY: %#x\n",
+ msg);
+ }
ipc_irq = true;
}
diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c
index 145d483bd129..eb22eb3f6fee 100644
--- a/sound/soc/sof/intel/hda-loader.c
+++ b/sound/soc/sof/intel/hda-loader.c
@@ -99,7 +99,7 @@ out_put:
* power on all host managed cores and only unstall/run the boot core to boot the
* DSP then turn off all non boot cores (if any) is powered on.
*/
-static int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot)
+int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_intel_dsp_desc *chip = hda->desc;
@@ -369,9 +369,15 @@ int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev)
static int hda_dsp_boot_imr(struct snd_sof_dev *sdev)
{
+ const struct sof_intel_dsp_desc *chip_info;
int ret;
- ret = cl_dsp_init(sdev, 0, true);
+ chip_info = get_chip_info(sdev->pdata);
+ if (chip_info->cl_init)
+ ret = chip_info->cl_init(sdev, 0, true);
+ else
+ ret = -EINVAL;
+
if (!ret)
hda_sdw_process_wakeen(sdev);
@@ -389,8 +395,7 @@ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev)
struct snd_dma_buffer dmab;
int ret, ret1, i;
- if (sdev->system_suspend_target < SOF_SUSPEND_S4 &&
- hda->imrboot_supported && !sdev->first_boot) {
+ if (hda->imrboot_supported && !sdev->first_boot && !hda->skip_imr_boot) {
dev_dbg(sdev->dev, "IMR restore supported, booting from IMR directly\n");
hda->boot_iteration = 0;
ret = hda_dsp_boot_imr(sdev);
@@ -431,7 +436,10 @@ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev)
"Attempting iteration %d of Core En/ROM load...\n", i);
hda->boot_iteration = i + 1;
- ret = cl_dsp_init(sdev, hext_stream->hstream.stream_tag, false);
+ if (chip_info->cl_init)
+ ret = chip_info->cl_init(sdev, hext_stream->hstream.stream_tag, false);
+ else
+ ret = -EINVAL;
/* don't retry anymore if successful */
if (!ret)
@@ -471,11 +479,14 @@ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev)
*/
hda->boot_iteration = HDA_FW_BOOT_ATTEMPTS;
ret = hda_cl_copy_fw(sdev, hext_stream);
- if (!ret)
+ if (!ret) {
dev_dbg(sdev->dev, "Firmware download successful, booting...\n");
- else
+ hda->skip_imr_boot = false;
+ } else {
snd_sof_dsp_dbg_dump(sdev, "Firmware download failed",
SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_MBOX);
+ hda->skip_imr_boot = true;
+ }
cleanup:
/*
@@ -530,7 +541,8 @@ int hda_dsp_post_fw_run(struct snd_sof_dev *sdev)
/* Check if IMR boot is usable */
if (!sof_debug_check_flag(SOF_DBG_IGNORE_D3_PERSISTENT) &&
- sdev->fw_ready.flags & SOF_IPC_INFO_D3_PERSISTENT)
+ (sdev->fw_ready.flags & SOF_IPC_INFO_D3_PERSISTENT ||
+ sdev->pdata->ipc_type == SOF_INTEL_IPC4))
hdev->imrboot_supported = true;
}
diff --git a/sound/soc/sof/intel/hda-probes.c b/sound/soc/sof/intel/hda-probes.c
index 31e85d4aae8c..56a533c63cb0 100644
--- a/sound/soc/sof/intel/hda-probes.c
+++ b/sound/soc/sof/intel/hda-probes.c
@@ -25,9 +25,9 @@ hda_compr_get_stream(struct snd_compr_stream *cstream)
return cstream->runtime->private_data;
}
-static int hda_probes_compr_assign(struct sof_client_dev *cdev,
- struct snd_compr_stream *cstream,
- struct snd_soc_dai *dai, u32 *stream_id)
+static int hda_probes_compr_startup(struct sof_client_dev *cdev,
+ struct snd_compr_stream *cstream,
+ struct snd_soc_dai *dai, u32 *stream_id)
{
struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
struct hdac_ext_stream *hext_stream;
@@ -45,9 +45,9 @@ static int hda_probes_compr_assign(struct sof_client_dev *cdev,
return 0;
}
-static int hda_probes_compr_free(struct sof_client_dev *cdev,
- struct snd_compr_stream *cstream,
- struct snd_soc_dai *dai)
+static int hda_probes_compr_shutdown(struct sof_client_dev *cdev,
+ struct snd_compr_stream *cstream,
+ struct snd_soc_dai *dai)
{
struct hdac_ext_stream *hext_stream = hda_compr_get_stream(cstream);
struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
@@ -127,8 +127,8 @@ static int hda_probes_compr_pointer(struct sof_client_dev *cdev,
/* SOF client implementation */
static const struct sof_probes_host_ops hda_probes_ops = {
- .assign = hda_probes_compr_assign,
- .free = hda_probes_compr_free,
+ .startup = hda_probes_compr_startup,
+ .shutdown = hda_probes_compr_shutdown,
.set_params = hda_probes_compr_set_params,
.trigger = hda_probes_compr_trigger,
.pointer = hda_probes_compr_pointer,
diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c
index d95ae17e81cc..b58662faa4aa 100644
--- a/sound/soc/sof/intel/hda-stream.c
+++ b/sound/soc/sof/intel/hda-stream.c
@@ -116,13 +116,13 @@ int hda_dsp_stream_setup_bdl(struct snd_sof_dev *sdev,
int remain, ioc;
period_bytes = hstream->period_bytes;
- dev_dbg(sdev->dev, "%s: period_bytes:0x%x\n", __func__, period_bytes);
+ dev_dbg(sdev->dev, "period_bytes:0x%x\n", period_bytes);
if (!period_bytes)
period_bytes = hstream->bufsize;
periods = hstream->bufsize / period_bytes;
- dev_dbg(sdev->dev, "%s: periods:%d\n", __func__, periods);
+ dev_dbg(sdev->dev, "periods:%d\n", periods);
remain = hstream->bufsize % period_bytes;
if (remain)
@@ -271,7 +271,7 @@ int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stream_tag)
HDA_VS_INTEL_EM2_L1SEN, HDA_VS_INTEL_EM2_L1SEN);
if (!found) {
- dev_dbg(sdev->dev, "%s: stream_tag %d not opened!\n",
+ dev_err(sdev->dev, "%s: stream_tag %d not opened!\n",
__func__, stream_tag);
return -ENODEV;
}
@@ -411,6 +411,11 @@ int hda_dsp_iccmax_stream_hw_params(struct snd_sof_dev *sdev, struct hdac_ext_st
return -ENODEV;
}
+ if (!dmab) {
+ dev_err(sdev->dev, "error: no dma buffer allocated!\n");
+ return -ENODEV;
+ }
+
if (hstream->posbuf)
*hstream->posbuf = 0;
@@ -485,16 +490,16 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev,
return -ENODEV;
}
- /* decouple host and link DMA */
- mask = 0x1 << hstream->index;
- snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
- mask, mask);
-
if (!dmab) {
dev_err(sdev->dev, "error: no dma buffer allocated!\n");
return -ENODEV;
}
+ /* decouple host and link DMA */
+ mask = 0x1 << hstream->index;
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
+ mask, mask);
+
/* clear stream status */
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset,
SOF_HDA_CL_DMA_SD_INT_MASK |
diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c
index bc07df1fc39f..6d4ecbe14adf 100644
--- a/sound/soc/sof/intel/hda.c
+++ b/sound/soc/sof/intel/hda.c
@@ -147,7 +147,7 @@ static int sdw_free_stream(struct device *dev,
return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE, &data);
}
-static const struct sdw_intel_ops sdw_callback = {
+struct sdw_intel_ops sdw_callback = {
.params_stream = sdw_params_stream,
.free_stream = sdw_free_stream,
};
@@ -353,7 +353,7 @@ static inline bool hda_sdw_check_wakeen_irq(struct snd_sof_dev *sdev)
struct hda_dsp_msg_code {
u32 code;
- const char *msg;
+ const char *text;
};
#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG)
@@ -382,10 +382,7 @@ module_param_named(use_common_hdmi, hda_codec_use_common_hdmi, bool, 0444);
MODULE_PARM_DESC(use_common_hdmi, "SOF HDA use common HDMI codec driver");
#endif
-static const struct hda_dsp_msg_code hda_dsp_rom_msg[] = {
- {HDA_DSP_ROM_FW_MANIFEST_LOADED, "status: manifest loaded"},
- {HDA_DSP_ROM_FW_FW_LOADED, "status: fw loaded"},
- {HDA_DSP_ROM_FW_ENTERED, "status: fw entered"},
+static const struct hda_dsp_msg_code hda_dsp_rom_fw_error_texts[] = {
{HDA_DSP_ROM_CSE_ERROR, "error: cse error"},
{HDA_DSP_ROM_CSE_WRONG_RESPONSE, "error: cse wrong response"},
{HDA_DSP_ROM_IMR_TO_SMALL, "error: IMR too small"},
@@ -404,26 +401,136 @@ static const struct hda_dsp_msg_code hda_dsp_rom_msg[] = {
{HDA_DSP_ROM_NULL_FW_ENTRY, "error: null FW entry point"},
};
-static void hda_dsp_get_status(struct snd_sof_dev *sdev, const char *level)
+#define FSR_ROM_STATE_ENTRY(state) {FSR_STATE_ROM_##state, #state}
+static const struct hda_dsp_msg_code fsr_rom_state_names[] = {
+ FSR_ROM_STATE_ENTRY(INIT),
+ FSR_ROM_STATE_ENTRY(INIT_DONE),
+ FSR_ROM_STATE_ENTRY(CSE_MANIFEST_LOADED),
+ FSR_ROM_STATE_ENTRY(FW_MANIFEST_LOADED),
+ FSR_ROM_STATE_ENTRY(FW_FW_LOADED),
+ FSR_ROM_STATE_ENTRY(FW_ENTERED),
+ FSR_ROM_STATE_ENTRY(VERIFY_FEATURE_MASK),
+ FSR_ROM_STATE_ENTRY(GET_LOAD_OFFSET),
+ FSR_ROM_STATE_ENTRY(FETCH_ROM_EXT),
+ FSR_ROM_STATE_ENTRY(FETCH_ROM_EXT_DONE),
+ /* CSE states */
+ FSR_ROM_STATE_ENTRY(CSE_IMR_REQUEST),
+ FSR_ROM_STATE_ENTRY(CSE_IMR_GRANTED),
+ FSR_ROM_STATE_ENTRY(CSE_VALIDATE_IMAGE_REQUEST),
+ FSR_ROM_STATE_ENTRY(CSE_IMAGE_VALIDATED),
+ FSR_ROM_STATE_ENTRY(CSE_IPC_IFACE_INIT),
+ FSR_ROM_STATE_ENTRY(CSE_IPC_RESET_PHASE_1),
+ FSR_ROM_STATE_ENTRY(CSE_IPC_OPERATIONAL_ENTRY),
+ FSR_ROM_STATE_ENTRY(CSE_IPC_OPERATIONAL),
+ FSR_ROM_STATE_ENTRY(CSE_IPC_DOWN),
+};
+
+#define FSR_BRINGUP_STATE_ENTRY(state) {FSR_STATE_BRINGUP_##state, #state}
+static const struct hda_dsp_msg_code fsr_bringup_state_names[] = {
+ FSR_BRINGUP_STATE_ENTRY(INIT),
+ FSR_BRINGUP_STATE_ENTRY(INIT_DONE),
+ FSR_BRINGUP_STATE_ENTRY(HPSRAM_LOAD),
+ FSR_BRINGUP_STATE_ENTRY(UNPACK_START),
+ FSR_BRINGUP_STATE_ENTRY(IMR_RESTORE),
+ FSR_BRINGUP_STATE_ENTRY(FW_ENTERED),
+};
+
+#define FSR_WAIT_STATE_ENTRY(state) {FSR_WAIT_FOR_##state, #state}
+static const struct hda_dsp_msg_code fsr_wait_state_names[] = {
+ FSR_WAIT_STATE_ENTRY(IPC_BUSY),
+ FSR_WAIT_STATE_ENTRY(IPC_DONE),
+ FSR_WAIT_STATE_ENTRY(CACHE_INVALIDATION),
+ FSR_WAIT_STATE_ENTRY(LP_SRAM_OFF),
+ FSR_WAIT_STATE_ENTRY(DMA_BUFFER_FULL),
+ FSR_WAIT_STATE_ENTRY(CSE_CSR),
+};
+
+#define FSR_MODULE_NAME_ENTRY(mod) [FSR_MOD_##mod] = #mod
+static const char * const fsr_module_names[] = {
+ FSR_MODULE_NAME_ENTRY(ROM),
+ FSR_MODULE_NAME_ENTRY(ROM_BYP),
+ FSR_MODULE_NAME_ENTRY(BASE_FW),
+ FSR_MODULE_NAME_ENTRY(LP_BOOT),
+ FSR_MODULE_NAME_ENTRY(BRNGUP),
+ FSR_MODULE_NAME_ENTRY(ROM_EXT),
+};
+
+static const char *
+hda_dsp_get_state_text(u32 code, const struct hda_dsp_msg_code *msg_code,
+ size_t array_size)
{
- const struct sof_intel_dsp_desc *chip;
- u32 status;
int i;
- chip = get_chip_info(sdev->pdata);
- status = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
- chip->rom_status_reg);
-
- for (i = 0; i < ARRAY_SIZE(hda_dsp_rom_msg); i++) {
- if (status == hda_dsp_rom_msg[i].code) {
- dev_printk(level, sdev->dev, "%s - code %8.8x\n",
- hda_dsp_rom_msg[i].msg, status);
- return;
- }
+ for (i = 0; i < array_size; i++) {
+ if (code == msg_code[i].code)
+ return msg_code[i].text;
}
+ return NULL;
+}
+
+static void hda_dsp_get_state(struct snd_sof_dev *sdev, const char *level)
+{
+ const struct sof_intel_dsp_desc *chip = get_chip_info(sdev->pdata);
+ const char *state_text, *error_text, *module_text;
+ u32 fsr, state, wait_state, module, error_code;
+
+ fsr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, chip->rom_status_reg);
+ state = FSR_TO_STATE_CODE(fsr);
+ wait_state = FSR_TO_WAIT_STATE_CODE(fsr);
+ module = FSR_TO_MODULE_CODE(fsr);
+
+ if (module > FSR_MOD_ROM_EXT)
+ module_text = "unknown";
+ else
+ module_text = fsr_module_names[module];
+
+ if (module == FSR_MOD_BRNGUP)
+ state_text = hda_dsp_get_state_text(state, fsr_bringup_state_names,
+ ARRAY_SIZE(fsr_bringup_state_names));
+ else
+ state_text = hda_dsp_get_state_text(state, fsr_rom_state_names,
+ ARRAY_SIZE(fsr_rom_state_names));
+
/* not for us, must be generic sof message */
- dev_dbg(sdev->dev, "unknown ROM status value %8.8x\n", status);
+ if (!state_text) {
+ dev_printk(level, sdev->dev, "%#010x: unknown ROM status value\n", fsr);
+ return;
+ }
+
+ if (wait_state) {
+ const char *wait_state_text;
+
+ wait_state_text = hda_dsp_get_state_text(wait_state, fsr_wait_state_names,
+ ARRAY_SIZE(fsr_wait_state_names));
+ if (!wait_state_text)
+ wait_state_text = "unknown";
+
+ dev_printk(level, sdev->dev,
+ "%#010x: module: %s, state: %s, waiting for: %s, %s\n",
+ fsr, module_text, state_text, wait_state_text,
+ fsr & FSR_HALTED ? "not running" : "running");
+ } else {
+ dev_printk(level, sdev->dev, "%#010x: module: %s, state: %s, %s\n",
+ fsr, module_text, state_text,
+ fsr & FSR_HALTED ? "not running" : "running");
+ }
+
+ error_code = snd_sof_dsp_read(sdev, HDA_DSP_BAR, chip->rom_status_reg + 4);
+ if (!error_code)
+ return;
+
+ error_text = hda_dsp_get_state_text(error_code, hda_dsp_rom_fw_error_texts,
+ ARRAY_SIZE(hda_dsp_rom_fw_error_texts));
+ if (!error_text)
+ error_text = "unknown";
+
+ if (state == FSR_STATE_FW_ENTERED)
+ dev_printk(level, sdev->dev, "status code: %#x (%s)\n", error_code,
+ error_text);
+ else
+ dev_printk(level, sdev->dev, "error code: %#x (%s)\n", error_code,
+ error_text);
}
static void hda_dsp_get_registers(struct snd_sof_dev *sdev,
@@ -467,7 +574,7 @@ static void hda_dsp_dump_ext_rom_status(struct snd_sof_dev *sdev, const char *le
chip = get_chip_info(sdev->pdata);
for (i = 0; i < HDA_EXT_ROM_STATUS_SIZE; i++) {
value = snd_sof_dsp_read(sdev, HDA_DSP_BAR, chip->rom_status_reg + i * 0x4);
- len += snprintf(msg + len, sizeof(msg) - len, " 0x%x", value);
+ len += scnprintf(msg + len, sizeof(msg) - len, " 0x%x", value);
}
dev_printk(level, sdev->dev, "extended rom status: %s", msg);
@@ -482,7 +589,7 @@ void hda_dsp_dump(struct snd_sof_dev *sdev, u32 flags)
u32 stack[HDA_DSP_STACK_DUMP_SIZE];
/* print ROM/FW status */
- hda_dsp_get_status(sdev, level);
+ hda_dsp_get_state(sdev, level);
if (flags & SOF_DBG_DUMP_REGS) {
u32 status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_FW_STATUS);
@@ -669,13 +776,12 @@ static const char *fixup_tplg_name(struct snd_sof_dev *sdev,
return tplg_filename;
}
-static int dmic_topology_fixup(struct snd_sof_dev *sdev,
- const char **tplg_filename,
- const char *idisp_str,
- int *dmic_found)
+static int dmic_detect_topology_fixup(struct snd_sof_dev *sdev,
+ const char **tplg_filename,
+ const char *idisp_str,
+ int *dmic_found,
+ bool tplg_fixup)
{
- const char *default_tplg_filename = *tplg_filename;
- const char *fixed_tplg_filename;
const char *dmic_str;
int dmic_num;
@@ -701,14 +807,19 @@ static int dmic_topology_fixup(struct snd_sof_dev *sdev,
break;
}
- fixed_tplg_filename = fixup_tplg_name(sdev, default_tplg_filename,
- idisp_str, dmic_str);
- if (!fixed_tplg_filename)
- return -ENOMEM;
+ if (tplg_fixup) {
+ const char *default_tplg_filename = *tplg_filename;
+ const char *fixed_tplg_filename;
+
+ fixed_tplg_filename = fixup_tplg_name(sdev, default_tplg_filename,
+ idisp_str, dmic_str);
+ if (!fixed_tplg_filename)
+ return -ENOMEM;
+ *tplg_filename = fixed_tplg_filename;
+ }
dev_info(sdev->dev, "DMICs detected in NHLT tables: %d\n", dmic_num);
*dmic_found = dmic_num;
- *tplg_filename = fixed_tplg_filename;
return 0;
}
@@ -1114,6 +1225,8 @@ static void hda_generic_machine_select(struct snd_sof_dev *sdev,
* - one external HDAudio codec
*/
if (!*mach && codec_num <= 2) {
+ bool tplg_fixup;
+
hda_mach = snd_soc_acpi_intel_hda_machines;
dev_info(bus->dev, "using HDA machine driver %s now\n",
@@ -1125,8 +1238,15 @@ static void hda_generic_machine_select(struct snd_sof_dev *sdev,
idisp_str = "";
/* topology: use the info from hda_machines */
- tplg_filename = hda_mach->sof_tplg_filename;
- ret = dmic_topology_fixup(sdev, &tplg_filename, idisp_str, &dmic_num);
+ if (pdata->tplg_filename) {
+ tplg_fixup = false;
+ tplg_filename = pdata->tplg_filename;
+ } else {
+ tplg_fixup = true;
+ tplg_filename = hda_mach->sof_tplg_filename;
+ }
+ ret = dmic_detect_topology_fixup(sdev, &tplg_filename, idisp_str, &dmic_num,
+ tplg_fixup);
if (ret < 0)
return;
@@ -1290,30 +1410,37 @@ static struct snd_soc_acpi_mach *hda_sdw_machine_select(struct snd_sof_dev *sdev
}
if (mach && mach->link_mask) {
int dmic_num = 0;
+ bool tplg_fixup;
+ const char *tplg_filename;
mach->mach_params.links = mach->links;
mach->mach_params.link_mask = mach->link_mask;
mach->mach_params.platform = dev_name(sdev->dev);
- pdata->fw_filename = pdata->desc->default_fw_filename[pdata->ipc_type];
- pdata->tplg_filename = mach->sof_tplg_filename;
+
+ if (pdata->tplg_filename) {
+ tplg_fixup = false;
+ } else {
+ tplg_fixup = true;
+ tplg_filename = mach->sof_tplg_filename;
+ }
/*
* DMICs use up to 4 pins and are typically pin-muxed with SoundWire
- * link 2 and 3, thus we only try to enable dmics if all conditions
- * are true:
- * a) link 2 and 3 are not used by SoundWire
+ * link 2 and 3, or link 1 and 2, thus we only try to enable dmics
+ * if all conditions are true:
+ * a) 2 or fewer links are used by SoundWire
* b) the NHLT table reports the presence of microphones
*/
- if (!(mach->link_mask & GENMASK(3, 2))) {
- const char *tplg_filename = mach->sof_tplg_filename;
+ if (hweight_long(mach->link_mask) <= 2) {
int ret;
- ret = dmic_topology_fixup(sdev, &tplg_filename, "", &dmic_num);
+ ret = dmic_detect_topology_fixup(sdev, &tplg_filename, "",
+ &dmic_num, tplg_fixup);
if (ret < 0)
return NULL;
-
- pdata->tplg_filename = tplg_filename;
}
+ if (tplg_fixup)
+ pdata->tplg_filename = tplg_filename;
mach->mach_params.dmic_num = dmic_num;
dev_dbg(sdev->dev,
@@ -1359,18 +1486,22 @@ struct snd_soc_acpi_mach *hda_machine_select(struct snd_sof_dev *sdev)
mach = snd_soc_acpi_find_machine(desc->machines);
if (mach) {
bool add_extension = false;
+ bool tplg_fixup = false;
/*
* If tplg file name is overridden, use it instead of
* the one set in mach table
*/
- if (!sof_pdata->tplg_filename)
+ if (!sof_pdata->tplg_filename) {
sof_pdata->tplg_filename = mach->sof_tplg_filename;
+ tplg_fixup = true;
+ }
/* report to machine driver if any DMICs are found */
mach->mach_params.dmic_num = check_dmic_num(sdev);
- if (mach->tplg_quirk_mask & SND_SOC_ACPI_TPLG_INTEL_DMIC_NUMBER &&
+ if (tplg_fixup &&
+ mach->tplg_quirk_mask & SND_SOC_ACPI_TPLG_INTEL_DMIC_NUMBER &&
mach->mach_params.dmic_num) {
tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL,
"%s%s%d%s",
@@ -1393,8 +1524,10 @@ struct snd_soc_acpi_mach *hda_machine_select(struct snd_sof_dev *sdev)
/* report SSP link mask to machine driver */
mach->mach_params.i2s_link_mask = check_nhlt_ssp_mask(sdev);
- if (mach->tplg_quirk_mask & SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER &&
+ if (tplg_fixup &&
+ mach->tplg_quirk_mask & SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER &&
mach->mach_params.i2s_link_mask) {
+ const struct sof_intel_dsp_desc *chip = get_chip_info(sdev->pdata);
int ssp_num;
if (hweight_long(mach->mach_params.i2s_link_mask) > 1 &&
@@ -1404,6 +1537,12 @@ struct snd_soc_acpi_mach *hda_machine_select(struct snd_sof_dev *sdev)
/* fls returns 1-based results, SSPs indices are 0-based */
ssp_num = fls(mach->mach_params.i2s_link_mask) - 1;
+ if (ssp_num >= chip->ssp_count) {
+ dev_err(sdev->dev, "Invalid SSP %d, max on this platform is %d\n",
+ ssp_num, chip->ssp_count);
+ return NULL;
+ }
+
tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL,
"%s%s%d",
sof_pdata->tplg_filename,
@@ -1416,7 +1555,7 @@ struct snd_soc_acpi_mach *hda_machine_select(struct snd_sof_dev *sdev)
add_extension = true;
}
- if (add_extension) {
+ if (tplg_fixup && add_extension) {
tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL,
"%s%s",
sof_pdata->tplg_filename,
diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h
index 06476ffe96d7..5ef3e8775e36 100644
--- a/sound/soc/sof/intel/hda.h
+++ b/sound/soc/sof/intel/hda.h
@@ -187,6 +187,69 @@
#define HDA_DSP_STACK_DUMP_SIZE 32
+/* ROM/FW status register */
+#define FSR_STATE_MASK GENMASK(23, 0)
+#define FSR_WAIT_STATE_MASK GENMASK(27, 24)
+#define FSR_MODULE_MASK GENMASK(30, 28)
+#define FSR_HALTED BIT(31)
+#define FSR_TO_STATE_CODE(x) ((x) & FSR_STATE_MASK)
+#define FSR_TO_WAIT_STATE_CODE(x) (((x) & FSR_WAIT_STATE_MASK) >> 24)
+#define FSR_TO_MODULE_CODE(x) (((x) & FSR_MODULE_MASK) >> 28)
+
+/* Wait states */
+#define FSR_WAIT_FOR_IPC_BUSY 0x1
+#define FSR_WAIT_FOR_IPC_DONE 0x2
+#define FSR_WAIT_FOR_CACHE_INVALIDATION 0x3
+#define FSR_WAIT_FOR_LP_SRAM_OFF 0x4
+#define FSR_WAIT_FOR_DMA_BUFFER_FULL 0x5
+#define FSR_WAIT_FOR_CSE_CSR 0x6
+
+/* Module codes */
+#define FSR_MOD_ROM 0x0
+#define FSR_MOD_ROM_BYP 0x1
+#define FSR_MOD_BASE_FW 0x2
+#define FSR_MOD_LP_BOOT 0x3
+#define FSR_MOD_BRNGUP 0x4
+#define FSR_MOD_ROM_EXT 0x5
+
+/* State codes (module dependent) */
+/* Module independent states */
+#define FSR_STATE_INIT 0x0
+#define FSR_STATE_INIT_DONE 0x1
+#define FSR_STATE_FW_ENTERED 0x5
+
+/* ROM states */
+#define FSR_STATE_ROM_INIT FSR_STATE_INIT
+#define FSR_STATE_ROM_INIT_DONE FSR_STATE_INIT_DONE
+#define FSR_STATE_ROM_CSE_MANIFEST_LOADED 0x2
+#define FSR_STATE_ROM_FW_MANIFEST_LOADED 0x3
+#define FSR_STATE_ROM_FW_FW_LOADED 0x4
+#define FSR_STATE_ROM_FW_ENTERED FSR_STATE_FW_ENTERED
+#define FSR_STATE_ROM_VERIFY_FEATURE_MASK 0x6
+#define FSR_STATE_ROM_GET_LOAD_OFFSET 0x7
+#define FSR_STATE_ROM_FETCH_ROM_EXT 0x8
+#define FSR_STATE_ROM_FETCH_ROM_EXT_DONE 0x9
+
+/* (ROM) CSE states */
+#define FSR_STATE_ROM_CSE_IMR_REQUEST 0x10
+#define FSR_STATE_ROM_CSE_IMR_GRANTED 0x11
+#define FSR_STATE_ROM_CSE_VALIDATE_IMAGE_REQUEST 0x12
+#define FSR_STATE_ROM_CSE_IMAGE_VALIDATED 0x13
+
+#define FSR_STATE_ROM_CSE_IPC_IFACE_INIT 0x20
+#define FSR_STATE_ROM_CSE_IPC_RESET_PHASE_1 0x21
+#define FSR_STATE_ROM_CSE_IPC_OPERATIONAL_ENTRY 0x22
+#define FSR_STATE_ROM_CSE_IPC_OPERATIONAL 0x23
+#define FSR_STATE_ROM_CSE_IPC_DOWN 0x24
+
+/* BRINGUP (or BRNGUP) states */
+#define FSR_STATE_BRINGUP_INIT FSR_STATE_INIT
+#define FSR_STATE_BRINGUP_INIT_DONE FSR_STATE_INIT_DONE
+#define FSR_STATE_BRINGUP_HPSRAM_LOAD 0x2
+#define FSR_STATE_BRINGUP_UNPACK_START 0X3
+#define FSR_STATE_BRINGUP_IMR_RESTORE 0x4
+#define FSR_STATE_BRINGUP_FW_ENTERED FSR_STATE_FW_ENTERED
+
/* ROM status/error values */
#define HDA_DSP_ROM_STS_MASK GENMASK(23, 0)
#define HDA_DSP_ROM_INIT 0x1
@@ -419,6 +482,7 @@ enum sof_hda_D0_substate {
/* represents DSP HDA controller frontend - i.e. host facing control */
struct sof_intel_hda_dev {
bool imrboot_supported;
+ bool skip_imr_boot;
int boot_iteration;
@@ -605,6 +669,7 @@ struct hdac_ext_stream *hda_cl_stream_prepare(struct snd_sof_dev *sdev, unsigned
int direction);
int hda_cl_cleanup(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab,
struct hdac_ext_stream *hext_stream);
+int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot);
#define HDA_CL_STREAM_FORMAT 0x40
/* pre and post fw run ops */
@@ -716,6 +781,8 @@ extern struct snd_sof_dsp_ops sof_tgl_ops;
int sof_tgl_ops_init(struct snd_sof_dev *sdev);
extern struct snd_sof_dsp_ops sof_icl_ops;
int sof_icl_ops_init(struct snd_sof_dev *sdev);
+extern struct snd_sof_dsp_ops sof_mtl_ops;
+int sof_mtl_ops_init(struct snd_sof_dev *sdev);
extern const struct sof_intel_dsp_desc apl_chip_info;
extern const struct sof_intel_dsp_desc cnl_chip_info;
@@ -725,6 +792,7 @@ extern const struct sof_intel_dsp_desc tglh_chip_info;
extern const struct sof_intel_dsp_desc ehl_chip_info;
extern const struct sof_intel_dsp_desc jsl_chip_info;
extern const struct sof_intel_dsp_desc adls_chip_info;
+extern const struct sof_intel_dsp_desc mtl_chip_info;
/* Probes support */
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES)
@@ -767,11 +835,13 @@ int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w, unsigned int quirk_f
extern int sof_hda_position_quirk;
void hda_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops);
+void hda_ops_free(struct snd_sof_dev *sdev);
/* IPC4 */
irqreturn_t cnl_ipc4_irq_thread(int irq, void *context);
int cnl_ipc4_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg);
irqreturn_t hda_dsp_ipc4_irq_thread(int irq, void *context);
int hda_dsp_ipc4_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg);
+extern struct sdw_intel_ops sdw_callback;
#endif
diff --git a/sound/soc/sof/intel/icl.c b/sound/soc/sof/intel/icl.c
index f19517dffd62..4e37b7fe0627 100644
--- a/sound/soc/sof/intel/icl.c
+++ b/sound/soc/sof/intel/icl.c
@@ -152,6 +152,7 @@ const struct sof_intel_dsp_desc icl_chip_info = {
.sdw_alh_base = SDW_ALH_BASE,
.check_sdw_irq = hda_common_check_sdw_irq,
.check_ipc_irq = hda_dsp_check_ipc_irq,
+ .cl_init = cl_dsp_init,
.hw_ip_version = SOF_INTEL_CAVS_2_0,
};
EXPORT_SYMBOL_NS(icl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
diff --git a/sound/soc/sof/intel/mtl.c b/sound/soc/sof/intel/mtl.c
new file mode 100644
index 000000000000..96239ebb1eed
--- /dev/null
+++ b/sound/soc/sof/intel/mtl.c
@@ -0,0 +1,794 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
+//
+// Authors: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
+//
+
+/*
+ * Hardware interface for audio DSP on Meteorlake.
+ */
+
+#include <linux/firmware.h>
+#include <sound/sof/ipc4/header.h>
+#include "../ipc4-priv.h"
+#include "../ops.h"
+#include "hda.h"
+#include "hda-ipc.h"
+#include "../sof-audio.h"
+#include "mtl.h"
+
+static const struct snd_sof_debugfs_map mtl_dsp_debugfs[] = {
+ {"hda", HDA_DSP_HDA_BAR, 0, 0x4000, SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"pp", HDA_DSP_PP_BAR, 0, 0x1000, SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"dsp", HDA_DSP_BAR, 0, 0x10000, SOF_DEBUGFS_ACCESS_ALWAYS},
+};
+
+static void mtl_ipc_host_done(struct snd_sof_dev *sdev)
+{
+ /*
+ * clear busy interrupt to tell dsp controller this interrupt has been accepted,
+ * not trigger it again
+ */
+ snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDR,
+ MTL_DSP_REG_HFIPCXTDR_BUSY, MTL_DSP_REG_HFIPCXTDR_BUSY);
+ /*
+ * clear busy bit to ack dsp the msg has been processed and send reply msg to dsp
+ */
+ snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDA,
+ MTL_DSP_REG_HFIPCXTDA_BUSY, 0);
+}
+
+static void mtl_ipc_dsp_done(struct snd_sof_dev *sdev)
+{
+ /*
+ * set DONE bit - tell DSP we have received the reply msg from DSP, and processed it,
+ * don't send more reply to host
+ */
+ snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDA,
+ MTL_DSP_REG_HFIPCXIDA_DONE, MTL_DSP_REG_HFIPCXIDA_DONE);
+
+ /* unmask Done interrupt */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXCTL,
+ MTL_DSP_REG_HFIPCXCTL_DONE, MTL_DSP_REG_HFIPCXCTL_DONE);
+}
+
+/* Check if an IPC IRQ occurred */
+static bool mtl_dsp_check_ipc_irq(struct snd_sof_dev *sdev)
+{
+ u32 irq_status;
+ u32 hfintipptr;
+
+ /* read Interrupt IP Pointer */
+ hfintipptr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFINTIPPTR) & MTL_HFINTIPPTR_PTR_MASK;
+ irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, hfintipptr + MTL_DSP_IRQSTS);
+
+ dev_vdbg(sdev->dev, "irq handler: irq_status:0x%x\n", irq_status);
+
+ if (irq_status != U32_MAX && (irq_status & MTL_DSP_IRQSTS_IPC))
+ return true;
+
+ return false;
+}
+
+/* Check if an SDW IRQ occurred */
+static bool mtl_dsp_check_sdw_irq(struct snd_sof_dev *sdev)
+{
+ u32 irq_status;
+ u32 hfintipptr;
+
+ /* read Interrupt IP Pointer */
+ hfintipptr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFINTIPPTR) & MTL_HFINTIPPTR_PTR_MASK;
+ irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, hfintipptr + MTL_DSP_IRQSTS);
+
+ if (irq_status != U32_MAX && (irq_status & MTL_DSP_IRQSTS_SDW))
+ return true;
+
+ return false;
+}
+
+static int mtl_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
+{
+ struct sof_ipc4_msg *msg_data = msg->msg_data;
+
+ /* send the message via mailbox */
+ if (msg_data->data_size)
+ sof_mailbox_write(sdev, sdev->host_box.offset, msg_data->data_ptr,
+ msg_data->data_size);
+
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDDY,
+ msg_data->extension);
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDR,
+ msg_data->primary | MTL_DSP_REG_HFIPCXIDR_BUSY);
+
+ return 0;
+}
+
+static void mtl_enable_ipc_interrupts(struct snd_sof_dev *sdev)
+{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ const struct sof_intel_dsp_desc *chip = hda->desc;
+
+ /* enable IPC DONE and BUSY interrupts */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl,
+ MTL_DSP_REG_HFIPCXCTL_BUSY | MTL_DSP_REG_HFIPCXCTL_DONE,
+ MTL_DSP_REG_HFIPCXCTL_BUSY | MTL_DSP_REG_HFIPCXCTL_DONE);
+}
+
+static void mtl_disable_ipc_interrupts(struct snd_sof_dev *sdev)
+{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ const struct sof_intel_dsp_desc *chip = hda->desc;
+
+ /* disable IPC DONE and BUSY interrupts */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl,
+ MTL_DSP_REG_HFIPCXCTL_BUSY | MTL_DSP_REG_HFIPCXCTL_DONE, 0);
+}
+
+static int mtl_enable_interrupts(struct snd_sof_dev *sdev)
+{
+ u32 hfintipptr;
+ u32 irqinten;
+ u32 host_ipc;
+ u32 hipcie;
+ int ret;
+
+ /* read Interrupt IP Pointer */
+ hfintipptr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFINTIPPTR) & MTL_HFINTIPPTR_PTR_MASK;
+
+ /* Enable Host IPC and SOUNDWIRE */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, hfintipptr,
+ MTL_IRQ_INTEN_L_HOST_IPC_MASK | MTL_IRQ_INTEN_L_SOUNDWIRE_MASK,
+ MTL_IRQ_INTEN_L_HOST_IPC_MASK | MTL_IRQ_INTEN_L_SOUNDWIRE_MASK);
+
+ /* check if operation was successful */
+ host_ipc = MTL_IRQ_INTEN_L_HOST_IPC_MASK | MTL_IRQ_INTEN_L_SOUNDWIRE_MASK;
+ irqinten = snd_sof_dsp_read(sdev, HDA_DSP_BAR, hfintipptr);
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, hfintipptr, irqinten,
+ (irqinten & host_ipc) == host_ipc,
+ HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_RESET_TIMEOUT_US);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to enable Host IPC and/or SOUNDWIRE\n");
+ return ret;
+ }
+
+ /* Set Host IPC interrupt enable */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfHIPCIE,
+ MTL_DSP_REG_HfHIPCIE_IE_MASK, MTL_DSP_REG_HfHIPCIE_IE_MASK);
+
+ /* check if operation was successful */
+ host_ipc = MTL_DSP_REG_HfHIPCIE_IE_MASK;
+ hipcie = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfHIPCIE);
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfHIPCIE, hipcie,
+ (hipcie & host_ipc) == host_ipc,
+ HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_RESET_TIMEOUT_US);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to set Host IPC interrupt enable\n");
+ return ret;
+ }
+
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfSNDWIE,
+ MTL_DSP_REG_HfSNDWIE_IE_MASK, MTL_DSP_REG_HfSNDWIE_IE_MASK);
+ host_ipc = MTL_DSP_REG_HfSNDWIE_IE_MASK;
+ hipcie = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfSNDWIE);
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfSNDWIE, hipcie,
+ (hipcie & host_ipc) == host_ipc,
+ HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_RESET_TIMEOUT_US);
+ if (ret < 0)
+ dev_err(sdev->dev, "failed to set SoundWire IPC interrupt enable\n");
+
+ return ret;
+}
+
+static int mtl_disable_interrupts(struct snd_sof_dev *sdev)
+{
+ u32 hfintipptr;
+ u32 irqinten;
+ u32 host_ipc;
+ u32 hipcie;
+ int ret1;
+ int ret;
+
+ /* read Interrupt IP Pointer */
+ hfintipptr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFINTIPPTR) & MTL_HFINTIPPTR_PTR_MASK;
+
+ /* Disable Host IPC and SOUNDWIRE */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, hfintipptr,
+ MTL_IRQ_INTEN_L_HOST_IPC_MASK | MTL_IRQ_INTEN_L_SOUNDWIRE_MASK, 0);
+
+ /* check if operation was successful */
+ host_ipc = MTL_IRQ_INTEN_L_HOST_IPC_MASK | MTL_IRQ_INTEN_L_SOUNDWIRE_MASK;
+ irqinten = snd_sof_dsp_read(sdev, HDA_DSP_BAR, hfintipptr);
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, hfintipptr, irqinten,
+ (irqinten & host_ipc) == 0,
+ HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_RESET_TIMEOUT_US);
+ /* Continue to disable other interrupts when error happens */
+ if (ret < 0)
+ dev_err(sdev->dev, "failed to disable Host IPC and SoundWire\n");
+
+ /* Set Host IPC interrupt disable */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfHIPCIE,
+ MTL_DSP_REG_HfHIPCIE_IE_MASK, 0);
+
+ /* check if operation was successful */
+ host_ipc = MTL_DSP_REG_HfHIPCIE_IE_MASK;
+ hipcie = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfHIPCIE);
+ ret1 = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfHIPCIE, hipcie,
+ (hipcie & host_ipc) == 0,
+ HDA_DSP_REG_POLL_INTERVAL_US,
+ HDA_DSP_RESET_TIMEOUT_US);
+ if (ret1 < 0) {
+ dev_err(sdev->dev, "failed to set Host IPC interrupt disable\n");
+ if (!ret)
+ ret = ret1;
+ }
+
+ /* Set SoundWire IPC interrupt disable */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfSNDWIE,
+ MTL_DSP_REG_HfSNDWIE_IE_MASK, 0);
+ host_ipc = MTL_DSP_REG_HfSNDWIE_IE_MASK;
+ hipcie = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfSNDWIE);
+ ret1 = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfSNDWIE, hipcie,
+ (hipcie & host_ipc) == 0,
+ HDA_DSP_REG_POLL_INTERVAL_US,
+ HDA_DSP_RESET_TIMEOUT_US);
+ if (ret1 < 0) {
+ dev_err(sdev->dev, "failed to set SoundWire IPC interrupt disable\n");
+ if (!ret)
+ ret = ret1;
+ }
+
+ return ret;
+}
+
+/* pre fw run operations */
+static int mtl_dsp_pre_fw_run(struct snd_sof_dev *sdev)
+{
+ u32 dsphfpwrsts;
+ u32 dsphfdsscs;
+ u32 cpa;
+ u32 pgs;
+ int ret;
+
+ /* Set the DSP subsystem power on */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_HFDSSCS,
+ MTL_HFDSSCS_SPA_MASK, MTL_HFDSSCS_SPA_MASK);
+
+ /* Wait for unstable CPA read (1 then 0 then 1) just after setting SPA bit */
+ usleep_range(1000, 1010);
+
+ /* poll with timeout to check if operation successful */
+ cpa = MTL_HFDSSCS_CPA_MASK;
+ dsphfdsscs = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFDSSCS);
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_HFDSSCS, dsphfdsscs,
+ (dsphfdsscs & cpa) == cpa, HDA_DSP_REG_POLL_INTERVAL_US,
+ HDA_DSP_RESET_TIMEOUT_US);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to enable DSP subsystem\n");
+ return ret;
+ }
+
+ /* Power up gated-DSP-0 domain in order to access the DSP shim register block. */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_HFPWRCTL,
+ MTL_HFPWRCTL_WPDSPHPXPG, MTL_HFPWRCTL_WPDSPHPXPG);
+
+ usleep_range(1000, 1010);
+
+ /* poll with timeout to check if operation successful */
+ pgs = MTL_HFPWRSTS_DSPHPXPGS_MASK;
+ dsphfpwrsts = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFPWRSTS);
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_HFPWRSTS, dsphfpwrsts,
+ (dsphfpwrsts & pgs) == pgs,
+ HDA_DSP_REG_POLL_INTERVAL_US,
+ HDA_DSP_RESET_TIMEOUT_US);
+ if (ret < 0)
+ dev_err(sdev->dev, "failed to power up gated DSP domain\n");
+
+ /* make sure SoundWire is not power-gated */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, MTL_HFPWRCTL,
+ MTL_HfPWRCTL_WPIOXPG(1), MTL_HfPWRCTL_WPIOXPG(1));
+ return ret;
+}
+
+static int mtl_dsp_post_fw_run(struct snd_sof_dev *sdev)
+{
+ int ret;
+
+ if (sdev->first_boot) {
+ struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
+
+ ret = hda_sdw_startup(sdev);
+ if (ret < 0) {
+ dev_err(sdev->dev, "could not startup SoundWire links\n");
+ return ret;
+ }
+
+ /* Check if IMR boot is usable */
+ if (!sof_debug_check_flag(SOF_DBG_IGNORE_D3_PERSISTENT))
+ hdev->imrboot_supported = true;
+ }
+
+ hda_sdw_int_enable(sdev, true);
+ return 0;
+}
+
+static void mtl_dsp_dump(struct snd_sof_dev *sdev, u32 flags)
+{
+ char *level = (flags & SOF_DBG_DUMP_OPTIONAL) ? KERN_DEBUG : KERN_ERR;
+ u32 romdbgsts;
+ u32 romdbgerr;
+ u32 fwsts;
+ u32 fwlec;
+
+ fwsts = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_ROM_STS);
+ fwlec = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_ROM_ERROR);
+ romdbgsts = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFFLGPXQWY);
+ romdbgerr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFFLGPXQWY_ERROR);
+
+ dev_err(sdev->dev, "ROM status: %#x, ROM error: %#x\n", fwsts, fwlec);
+ dev_err(sdev->dev, "ROM debug status: %#x, ROM debug error: %#x\n", romdbgsts,
+ romdbgerr);
+ romdbgsts = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFFLGPXQWY + 0x8 * 3);
+ dev_printk(level, sdev->dev, "ROM feature bit%s enabled\n",
+ romdbgsts & BIT(24) ? "" : " not");
+}
+
+static bool mtl_dsp_primary_core_is_enabled(struct snd_sof_dev *sdev)
+{
+ int val;
+
+ val = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE);
+ if (val != U32_MAX && val & MTL_DSP2CXCTL_PRIMARY_CORE_CPA_MASK)
+ return true;
+
+ return false;
+}
+
+static int mtl_dsp_core_power_up(struct snd_sof_dev *sdev, int core)
+{
+ unsigned int cpa;
+ u32 dspcxctl;
+ int ret;
+
+ /* Only the primary core can be powered up by the host */
+ if (core != SOF_DSP_PRIMARY_CORE || mtl_dsp_primary_core_is_enabled(sdev))
+ return 0;
+
+ /* Program the owner of the IP & shim registers (10: Host CPU) */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE,
+ MTL_DSP2CXCTL_PRIMARY_CORE_OSEL,
+ 0x2 << MTL_DSP2CXCTL_PRIMARY_CORE_OSEL_SHIFT);
+
+ /* enable SPA bit */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE,
+ MTL_DSP2CXCTL_PRIMARY_CORE_SPA_MASK,
+ MTL_DSP2CXCTL_PRIMARY_CORE_SPA_MASK);
+
+ /* Wait for unstable CPA read (1 then 0 then 1) just after setting SPA bit */
+ usleep_range(1000, 1010);
+
+ /* poll with timeout to check if operation successful */
+ cpa = MTL_DSP2CXCTL_PRIMARY_CORE_CPA_MASK;
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE, dspcxctl,
+ (dspcxctl & cpa) == cpa, HDA_DSP_REG_POLL_INTERVAL_US,
+ HDA_DSP_RESET_TIMEOUT_US);
+ if (ret < 0)
+ dev_err(sdev->dev, "%s: timeout on MTL_DSP2CXCTL_PRIMARY_CORE read\n",
+ __func__);
+
+ return ret;
+}
+
+static int mtl_dsp_core_power_down(struct snd_sof_dev *sdev, int core)
+{
+ u32 dspcxctl;
+ int ret;
+
+ /* Only the primary core can be powered down by the host */
+ if (core != SOF_DSP_PRIMARY_CORE || !mtl_dsp_primary_core_is_enabled(sdev))
+ return 0;
+
+ /* disable SPA bit */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE,
+ MTL_DSP2CXCTL_PRIMARY_CORE_SPA_MASK, 0);
+
+ /* Wait for unstable CPA read (1 then 0 then 1) just after setting SPA bit */
+ usleep_range(1000, 1010);
+
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE, dspcxctl,
+ !(dspcxctl & MTL_DSP2CXCTL_PRIMARY_CORE_CPA_MASK),
+ HDA_DSP_REG_POLL_INTERVAL_US,
+ HDA_DSP_PD_TIMEOUT * USEC_PER_MSEC);
+ if (ret < 0)
+ dev_err(sdev->dev, "failed to power down primary core\n");
+
+ return ret;
+}
+
+static int mtl_dsp_cl_init(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot)
+{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ const struct sof_intel_dsp_desc *chip = hda->desc;
+ unsigned int status;
+ u32 ipc_hdr;
+ int ret;
+
+ /* step 1: purge FW request */
+ ipc_hdr = chip->ipc_req_mask | HDA_DSP_ROM_IPC_CONTROL;
+ if (!imr_boot)
+ ipc_hdr |= HDA_DSP_ROM_IPC_PURGE_FW | ((stream_tag - 1) << 9);
+
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR, chip->ipc_req, ipc_hdr);
+
+ /* step 2: power up primary core */
+ ret = mtl_dsp_core_power_up(sdev, SOF_DSP_PRIMARY_CORE);
+ if (ret < 0) {
+ if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
+ dev_err(sdev->dev, "dsp core 0/1 power up failed\n");
+ goto err;
+ }
+
+ dev_dbg(sdev->dev, "Primary core power up successful\n");
+
+ /* step 3: wait for IPC DONE bit from ROM */
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, chip->ipc_ack, status,
+ ((status & chip->ipc_ack_mask) == chip->ipc_ack_mask),
+ HDA_DSP_REG_POLL_INTERVAL_US, MTL_DSP_PURGE_TIMEOUT_US);
+ if (ret < 0) {
+ if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
+ dev_err(sdev->dev, "timeout waiting for purge IPC done\n");
+ goto err;
+ }
+
+ /* set DONE bit to clear the reply IPC message */
+ snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, chip->ipc_ack, chip->ipc_ack_mask,
+ chip->ipc_ack_mask);
+
+ /* step 4: enable interrupts */
+ ret = mtl_enable_interrupts(sdev);
+ if (ret < 0) {
+ if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
+ dev_err(sdev->dev, "%s: failed to enable interrupts\n", __func__);
+ goto err;
+ }
+
+ mtl_enable_ipc_interrupts(sdev);
+
+ /*
+ * ACE workaround: don't wait for ROM INIT.
+ * The platform cannot catch ROM_INIT_DONE because of a very short
+ * timing window. Follow the recommendations and skip this part.
+ */
+
+ return 0;
+
+err:
+ snd_sof_dsp_dbg_dump(sdev, "MTL DSP init fail", 0);
+ mtl_dsp_core_power_down(sdev, SOF_DSP_PRIMARY_CORE);
+ return ret;
+}
+
+static irqreturn_t mtl_ipc_irq_thread(int irq, void *context)
+{
+ struct sof_ipc4_msg notification_data = {{ 0 }};
+ struct snd_sof_dev *sdev = context;
+ bool ipc_irq = false;
+ u32 hipcida;
+ u32 hipctdr;
+
+ hipcida = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDA);
+
+ /* reply message from DSP */
+ if (hipcida & MTL_DSP_REG_HFIPCXIDA_DONE) {
+ /* DSP received the message */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXCTL,
+ MTL_DSP_REG_HFIPCXCTL_DONE, 0);
+
+ mtl_ipc_dsp_done(sdev);
+
+ ipc_irq = true;
+ }
+
+ hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDR);
+ if (hipctdr & MTL_DSP_REG_HFIPCXTDR_BUSY) {
+ /* Message from DSP (reply or notification) */
+ u32 extension = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDDY);
+ u32 primary = hipctdr & MTL_DSP_REG_HFIPCXTDR_MSG_MASK;
+
+ /*
+ * ACE fw sends a new fw ipc message to host to
+ * notify the status of the last host ipc message
+ */
+ if (primary & SOF_IPC4_MSG_DIR_MASK) {
+ /* Reply received */
+ if (likely(sdev->fw_state == SOF_FW_BOOT_COMPLETE)) {
+ struct sof_ipc4_msg *data = sdev->ipc->msg.reply_data;
+
+ data->primary = primary;
+ data->extension = extension;
+
+ spin_lock_irq(&sdev->ipc_lock);
+
+ snd_sof_ipc_get_reply(sdev);
+ snd_sof_ipc_reply(sdev, data->primary);
+
+ spin_unlock_irq(&sdev->ipc_lock);
+ } else {
+ dev_dbg_ratelimited(sdev->dev,
+ "IPC reply before FW_READY: %#x|%#x\n",
+ primary, extension);
+ }
+ } else {
+ /* Notification received */
+ notification_data.primary = primary;
+ notification_data.extension = extension;
+
+ sdev->ipc->msg.rx_data = &notification_data;
+ snd_sof_ipc_msgs_rx(sdev);
+ sdev->ipc->msg.rx_data = NULL;
+ }
+
+ mtl_ipc_host_done(sdev);
+
+ ipc_irq = true;
+ }
+
+ if (!ipc_irq) {
+ /* This interrupt is not shared so no need to return IRQ_NONE. */
+ dev_dbg_ratelimited(sdev->dev, "nothing to do in IPC IRQ thread\n");
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int mtl_dsp_ipc_get_mailbox_offset(struct snd_sof_dev *sdev)
+{
+ return MTL_DSP_MBOX_UPLINK_OFFSET;
+}
+
+static int mtl_dsp_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id)
+{
+ return MTL_SRAM_WINDOW_OFFSET(id);
+}
+
+static int mtl_suspend(struct snd_sof_dev *sdev, bool runtime_suspend)
+{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ const struct sof_intel_dsp_desc *chip = hda->desc;
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
+ struct hdac_bus *bus = sof_to_bus(sdev);
+#endif
+ u32 dsphfdsscs;
+ u32 cpa;
+ int ret;
+ int i;
+
+ mtl_disable_ipc_interrupts(sdev);
+ ret = mtl_disable_interrupts(sdev);
+ if (ret)
+ return ret;
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
+ hda_codec_jack_wake_enable(sdev, runtime_suspend);
+ /* power down all hda link */
+ snd_hdac_ext_bus_link_power_down_all(bus);
+#endif
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_HFPWRCTL,
+ MTL_HFPWRCTL_WPDSPHPXPG, 0);
+
+ /* Set the DSP subsystem power down */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_HFDSSCS,
+ MTL_HFDSSCS_SPA_MASK, 0);
+
+ /* Wait for unstable CPA read (1 then 0 then 1) just after setting SPA bit */
+ usleep_range(1000, 1010);
+
+ /* poll with timeout to check if operation successful */
+ cpa = MTL_HFDSSCS_CPA_MASK;
+ dsphfdsscs = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFDSSCS);
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_HFDSSCS, dsphfdsscs,
+ (dsphfdsscs & cpa) == 0, HDA_DSP_REG_POLL_INTERVAL_US,
+ HDA_DSP_RESET_TIMEOUT_US);
+ if (ret < 0)
+ dev_err(sdev->dev, "failed to disable DSP subsystem\n");
+
+ /* reset ref counts for all cores */
+ for (i = 0; i < chip->cores_num; i++)
+ sdev->dsp_core_ref_count[i] = 0;
+
+ /* TODO: need to reset controller? */
+
+ /* display codec can be powered off after link reset */
+ hda_codec_i915_display_power(sdev, false);
+
+ return 0;
+}
+
+static int mtl_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state)
+{
+ const struct sof_dsp_power_state target_dsp_state = {
+ .state = target_state,
+ .substate = target_state == SOF_DSP_PM_D0 ?
+ SOF_HDA_DSP_PM_D0I3 : 0,
+ };
+ int ret;
+
+ ret = mtl_suspend(sdev, false);
+ if (ret < 0)
+ return ret;
+
+ return snd_sof_dsp_set_power_state(sdev, &target_dsp_state);
+}
+
+static int mtl_dsp_runtime_suspend(struct snd_sof_dev *sdev)
+{
+ const struct sof_dsp_power_state target_state = {
+ .state = SOF_DSP_PM_D3,
+ };
+ int ret;
+
+ ret = mtl_suspend(sdev, true);
+ if (ret < 0)
+ return ret;
+
+ return snd_sof_dsp_set_power_state(sdev, &target_state);
+}
+
+static int mtl_resume(struct snd_sof_dev *sdev, bool runtime_resume)
+{
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
+ struct hdac_bus *bus = sof_to_bus(sdev);
+ struct hdac_ext_link *hlink = NULL;
+#endif
+
+ /* display codec must be powered before link reset */
+ hda_codec_i915_display_power(sdev, true);
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
+ /* check jack status */
+ if (runtime_resume) {
+ hda_codec_jack_wake_enable(sdev, false);
+ if (sdev->system_suspend_target == SOF_SUSPEND_NONE)
+ hda_codec_jack_check(sdev);
+ }
+
+ /* turn off the links that were off before suspend */
+ list_for_each_entry(hlink, &bus->hlink_list, list) {
+ if (!hlink->ref_count)
+ snd_hdac_ext_bus_link_power_down(hlink);
+ }
+
+ /* check dma status and clean up CORB/RIRB buffers */
+ if (!bus->cmd_dma_state)
+ snd_hdac_bus_stop_cmd_io(bus);
+#endif
+
+ return 0;
+}
+
+static int mtl_dsp_resume(struct snd_sof_dev *sdev)
+{
+ const struct sof_dsp_power_state target_state = {
+ .state = SOF_DSP_PM_D0,
+ .substate = SOF_HDA_DSP_PM_D0I0,
+ };
+ int ret;
+
+ ret = mtl_resume(sdev, false);
+ if (ret < 0)
+ return ret;
+
+ return snd_sof_dsp_set_power_state(sdev, &target_state);
+}
+
+static int mtl_dsp_runtime_resume(struct snd_sof_dev *sdev)
+{
+ const struct sof_dsp_power_state target_state = {
+ .state = SOF_DSP_PM_D0,
+ };
+ int ret;
+
+ ret = mtl_resume(sdev, true);
+ if (ret < 0)
+ return ret;
+
+ return snd_sof_dsp_set_power_state(sdev, &target_state);
+}
+
+static void mtl_ipc_dump(struct snd_sof_dev *sdev)
+{
+ u32 hipcctl;
+ u32 hipcida;
+ u32 hipctdr;
+
+ /* read IPC status */
+ hipcida = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDA);
+ hipcctl = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXCTL);
+ hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDR);
+
+ /* dump the IPC regs */
+ /* TODO: parse the raw msg */
+ dev_err(sdev->dev,
+ "error: host status 0x%8.8x dsp status 0x%8.8x mask 0x%8.8x\n",
+ hipcida, hipctdr, hipcctl);
+}
+
+/* Meteorlake ops */
+struct snd_sof_dsp_ops sof_mtl_ops;
+EXPORT_SYMBOL_NS(sof_mtl_ops, SND_SOC_SOF_INTEL_HDA_COMMON);
+
+int sof_mtl_ops_init(struct snd_sof_dev *sdev)
+{
+ struct sof_ipc4_fw_data *ipc4_data;
+
+ /* common defaults */
+ memcpy(&sof_mtl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops));
+
+ /* shutdown */
+ sof_mtl_ops.shutdown = hda_dsp_shutdown;
+
+ /* doorbell */
+ sof_mtl_ops.irq_thread = mtl_ipc_irq_thread;
+
+ /* ipc */
+ sof_mtl_ops.send_msg = mtl_ipc_send_msg;
+ sof_mtl_ops.get_mailbox_offset = mtl_dsp_ipc_get_mailbox_offset;
+ sof_mtl_ops.get_window_offset = mtl_dsp_ipc_get_window_offset;
+
+ /* debug */
+ sof_mtl_ops.debug_map = mtl_dsp_debugfs;
+ sof_mtl_ops.debug_map_count = ARRAY_SIZE(mtl_dsp_debugfs);
+ sof_mtl_ops.dbg_dump = mtl_dsp_dump;
+ sof_mtl_ops.ipc_dump = mtl_ipc_dump;
+
+ /* pre/post fw run */
+ sof_mtl_ops.pre_fw_run = mtl_dsp_pre_fw_run;
+ sof_mtl_ops.post_fw_run = mtl_dsp_post_fw_run;
+
+ /* parse platform specific extended manifest */
+ sof_mtl_ops.parse_platform_ext_manifest = NULL;
+
+ /* dsp core get/put */
+ /* TODO: add core_get and core_put */
+
+ /* PM */
+ sof_mtl_ops.suspend = mtl_dsp_suspend;
+ sof_mtl_ops.resume = mtl_dsp_resume;
+ sof_mtl_ops.runtime_suspend = mtl_dsp_runtime_suspend;
+ sof_mtl_ops.runtime_resume = mtl_dsp_runtime_resume;
+
+ sdev->private = devm_kzalloc(sdev->dev, sizeof(struct sof_ipc4_fw_data), GFP_KERNEL);
+ if (!sdev->private)
+ return -ENOMEM;
+
+ ipc4_data = sdev->private;
+ ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET;
+
+ /* set DAI ops */
+ hda_set_dai_drv_ops(sdev, &sof_mtl_ops);
+
+ return 0;
+};
+EXPORT_SYMBOL_NS(sof_mtl_ops_init, SND_SOC_SOF_INTEL_HDA_COMMON);
+
+const struct sof_intel_dsp_desc mtl_chip_info = {
+ .cores_num = 3,
+ .init_core_mask = BIT(0),
+ .host_managed_cores_mask = BIT(0),
+ .ipc_req = MTL_DSP_REG_HFIPCXIDR,
+ .ipc_req_mask = MTL_DSP_REG_HFIPCXIDR_BUSY,
+ .ipc_ack = MTL_DSP_REG_HFIPCXIDA,
+ .ipc_ack_mask = MTL_DSP_REG_HFIPCXIDA_DONE,
+ .ipc_ctl = MTL_DSP_REG_HFIPCXCTL,
+ .rom_status_reg = MTL_DSP_ROM_STS,
+ .rom_init_timeout = 300,
+ .ssp_count = ICL_SSP_COUNT,
+ .ssp_base_offset = CNL_SSP_BASE_OFFSET,
+ .sdw_shim_base = SDW_SHIM_BASE_ACE,
+ .sdw_alh_base = SDW_ALH_BASE_ACE,
+ .check_sdw_irq = mtl_dsp_check_sdw_irq,
+ .check_ipc_irq = mtl_dsp_check_ipc_irq,
+ .cl_init = mtl_dsp_cl_init,
+ .hw_ip_version = SOF_INTEL_ACE_1_0,
+};
+EXPORT_SYMBOL_NS(mtl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
diff --git a/sound/soc/sof/intel/mtl.h b/sound/soc/sof/intel/mtl.h
new file mode 100644
index 000000000000..788bf0e3ea87
--- /dev/null
+++ b/sound/soc/sof/intel/mtl.h
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * Copyright(c) 2020-2022 Intel Corporation. All rights reserved.
+ */
+
+/* DSP Registers */
+#define MTL_HFDSSCS 0x1000
+#define MTL_HFDSSCS_SPA_MASK BIT(16)
+#define MTL_HFDSSCS_CPA_MASK BIT(24)
+#define MTL_HFSNDWIE 0x114C
+#define MTL_HFPWRCTL 0x1D18
+#define MTL_HfPWRCTL_WPIOXPG(x) BIT((x) + 8)
+#define MTL_HFPWRCTL_WPDSPHPXPG BIT(0)
+#define MTL_HFPWRSTS 0x1D1C
+#define MTL_HFPWRSTS_DSPHPXPGS_MASK BIT(0)
+#define MTL_HFINTIPPTR 0x1108
+#define MTL_IRQ_INTEN_L_HOST_IPC_MASK BIT(0)
+#define MTL_IRQ_INTEN_L_SOUNDWIRE_MASK BIT(6)
+#define MTL_HFINTIPPTR_PTR_MASK GENMASK(20, 0)
+
+#define MTL_DSP2CXCAP_PRIMARY_CORE 0x178D00
+#define MTL_DSP2CXCTL_PRIMARY_CORE 0x178D04
+#define MTL_DSP2CXCTL_PRIMARY_CORE_SPA_MASK BIT(0)
+#define MTL_DSP2CXCTL_PRIMARY_CORE_CPA_MASK BIT(8)
+#define MTL_DSP2CXCTL_PRIMARY_CORE_OSEL GENMASK(25, 24)
+#define MTL_DSP2CXCTL_PRIMARY_CORE_OSEL_SHIFT 24
+
+/* IPC Registers */
+#define MTL_DSP_REG_HFIPCXTDR 0x73200
+#define MTL_DSP_REG_HFIPCXTDR_BUSY BIT(31)
+#define MTL_DSP_REG_HFIPCXTDR_MSG_MASK GENMASK(30, 0)
+#define MTL_DSP_REG_HFIPCXTDA 0x73204
+#define MTL_DSP_REG_HFIPCXTDA_BUSY BIT(31)
+#define MTL_DSP_REG_HFIPCXIDR 0x73210
+#define MTL_DSP_REG_HFIPCXIDR_BUSY BIT(31)
+#define MTL_DSP_REG_HFIPCXIDR_MSG_MASK GENMASK(30, 0)
+#define MTL_DSP_REG_HFIPCXIDA 0x73214
+#define MTL_DSP_REG_HFIPCXIDA_DONE BIT(31)
+#define MTL_DSP_REG_HFIPCXIDA_MSG_MASK GENMASK(30, 0)
+#define MTL_DSP_REG_HFIPCXCTL 0x73228
+#define MTL_DSP_REG_HFIPCXCTL_BUSY BIT(0)
+#define MTL_DSP_REG_HFIPCXCTL_DONE BIT(1)
+#define MTL_DSP_REG_HFIPCXTDDY 0x73300
+#define MTL_DSP_REG_HFIPCXIDDY 0x73380
+#define MTL_DSP_REG_HfHIPCIE 0x1140
+#define MTL_DSP_REG_HfHIPCIE_IE_MASK BIT(0)
+#define MTL_DSP_REG_HfSNDWIE 0x114C
+#define MTL_DSP_REG_HfSNDWIE_IE_MASK GENMASK(3, 0)
+
+#define MTL_DSP_IRQSTS 0x20
+#define MTL_DSP_IRQSTS_IPC BIT(0)
+#define MTL_DSP_IRQSTS_SDW BIT(6)
+
+#define MTL_DSP_PURGE_TIMEOUT_US 20000000 /* 20s */
+#define MTL_DSP_REG_POLL_INTERVAL_US 10 /* 10 us */
+
+/* Memory windows */
+#define MTL_SRAM_WINDOW_OFFSET(x) (0x180000 + 0x8000 * (x))
+
+#define MTL_DSP_MBOX_UPLINK_OFFSET (MTL_SRAM_WINDOW_OFFSET(0) + 0x1000)
+#define MTL_DSP_MBOX_UPLINK_SIZE 0x1000
+#define MTL_DSP_MBOX_DOWNLINK_OFFSET MTL_SRAM_WINDOW_OFFSET(1)
+#define MTL_DSP_MBOX_DOWNLINK_SIZE 0x1000
+
+/* FW registers */
+#define MTL_DSP_ROM_STS MTL_SRAM_WINDOW_OFFSET(0) /* ROM status */
+#define MTL_DSP_ROM_ERROR (MTL_SRAM_WINDOW_OFFSET(0) + 0x4) /* ROM error code */
+
+#define MTL_DSP_REG_HFFLGPXQWY 0x163200 /* ROM debug status */
+#define MTL_DSP_REG_HFFLGPXQWY_ERROR 0x163204 /* ROM debug error code */
+#define MTL_DSP_REG_HfIMRIS1 0x162088
+#define MTL_DSP_REG_HfIMRIS1_IU_MASK BIT(0)
+
diff --git a/sound/soc/sof/intel/pci-apl.c b/sound/soc/sof/intel/pci-apl.c
index 2de3658eb817..998e219011f0 100644
--- a/sound/soc/sof/intel/pci-apl.c
+++ b/sound/soc/sof/intel/pci-apl.c
@@ -44,6 +44,7 @@ static const struct sof_dev_desc bxt_desc = {
.nocodec_tplg_filename = "sof-apl-nocodec.tplg",
.ops = &sof_apl_ops,
.ops_init = sof_apl_ops_init,
+ .ops_free = hda_ops_free,
};
static const struct sof_dev_desc glk_desc = {
diff --git a/sound/soc/sof/intel/pci-cnl.c b/sound/soc/sof/intel/pci-cnl.c
index 87e587aef9c9..c797356f7028 100644
--- a/sound/soc/sof/intel/pci-cnl.c
+++ b/sound/soc/sof/intel/pci-cnl.c
@@ -73,6 +73,7 @@ static const struct sof_dev_desc cfl_desc = {
.nocodec_tplg_filename = "sof-cnl-nocodec.tplg",
.ops = &sof_cnl_ops,
.ops_init = sof_cnl_ops_init,
+ .ops_free = hda_ops_free,
};
static const struct sof_dev_desc cml_desc = {
diff --git a/sound/soc/sof/intel/pci-icl.c b/sound/soc/sof/intel/pci-icl.c
index 1c7f16ce531e..48f24f8ace26 100644
--- a/sound/soc/sof/intel/pci-icl.c
+++ b/sound/soc/sof/intel/pci-icl.c
@@ -45,6 +45,7 @@ static const struct sof_dev_desc icl_desc = {
.nocodec_tplg_filename = "sof-icl-nocodec.tplg",
.ops = &sof_icl_ops,
.ops_init = sof_icl_ops_init,
+ .ops_free = hda_ops_free,
};
static const struct sof_dev_desc jsl_desc = {
diff --git a/sound/soc/sof/intel/pci-mtl.c b/sound/soc/sof/intel/pci-mtl.c
new file mode 100644
index 000000000000..899b00d53d64
--- /dev/null
+++ b/sound/soc/sof/intel/pci-mtl.c
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2018-2022 Intel Corporation. All rights reserved.
+//
+// Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
+//
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include <sound/sof.h>
+#include "../ops.h"
+#include "../sof-pci-dev.h"
+
+/* platform specific devices */
+#include "hda.h"
+#include "mtl.h"
+
+static const struct sof_dev_desc mtl_desc = {
+ .use_acpi_target_states = true,
+ .machines = snd_soc_acpi_intel_mtl_machines,
+ .alt_machines = snd_soc_acpi_intel_mtl_sdw_machines,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &mtl_chip_info,
+ .ipc_supported_mask = BIT(SOF_INTEL_IPC4),
+ .ipc_default = SOF_INTEL_IPC4,
+ .default_fw_path = {
+ [SOF_INTEL_IPC4] = "intel/sof-ipc4/mtl",
+ },
+ .default_tplg_path = {
+ [SOF_INTEL_IPC4] = "intel/sof-ace-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_INTEL_IPC4] = "dsp_basefw.bin",
+ },
+ .nocodec_tplg_filename = "sof-mtl-nocodec.tplg",
+ .ops = &sof_mtl_ops,
+ .ops_init = sof_mtl_ops_init,
+};
+
+/* PCI IDs */
+static const struct pci_device_id sof_pci_ids[] = {
+ { PCI_DEVICE(0x8086, 0x7E28), /* MTL */
+ .driver_data = (unsigned long)&mtl_desc},
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, sof_pci_ids);
+
+/* pci_driver definition */
+static struct pci_driver snd_sof_pci_intel_mtl_driver = {
+ .name = "sof-audio-pci-intel-mtl",
+ .id_table = sof_pci_ids,
+ .probe = hda_pci_intel_probe,
+ .remove = sof_pci_remove,
+ .shutdown = sof_pci_shutdown,
+ .driver = {
+ .pm = &sof_pci_pm,
+ },
+};
+module_pci_driver(snd_sof_pci_intel_mtl_driver);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HDA_COMMON);
+MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV);
diff --git a/sound/soc/sof/intel/pci-tgl.c b/sound/soc/sof/intel/pci-tgl.c
index 58a9bd92a237..ccc44ba3ad94 100644
--- a/sound/soc/sof/intel/pci-tgl.c
+++ b/sound/soc/sof/intel/pci-tgl.c
@@ -73,6 +73,7 @@ static const struct sof_dev_desc tglh_desc = {
.nocodec_tplg_filename = "sof-tgl-nocodec.tplg",
.ops = &sof_tgl_ops,
.ops_init = sof_tgl_ops_init,
+ .ops_free = hda_ops_free,
};
static const struct sof_dev_desc ehl_desc = {
diff --git a/sound/soc/sof/intel/shim.h b/sound/soc/sof/intel/shim.h
index 1fd7b485d821..638159bee864 100644
--- a/sound/soc/sof/intel/shim.h
+++ b/sound/soc/sof/intel/shim.h
@@ -20,6 +20,7 @@ enum sof_intel_hw_ip_version {
SOF_INTEL_CAVS_1_8, /* CannonLake, CometLake, CoffeeLake */
SOF_INTEL_CAVS_2_0, /* IceLake, JasperLake */
SOF_INTEL_CAVS_2_5, /* TigerLake, AlderLake */
+ SOF_INTEL_ACE_1_0, /* MeteorLake */
};
/*
@@ -185,6 +186,7 @@ struct sof_intel_dsp_desc {
enum sof_intel_hw_ip_version hw_ip_version;
bool (*check_sdw_irq)(struct snd_sof_dev *sdev);
bool (*check_ipc_irq)(struct snd_sof_dev *sdev);
+ int (*cl_init)(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot);
};
extern struct snd_sof_dsp_ops sof_tng_ops;
diff --git a/sound/soc/sof/intel/tgl.c b/sound/soc/sof/intel/tgl.c
index 1ddc492f1b13..6dfb4786c782 100644
--- a/sound/soc/sof/intel/tgl.c
+++ b/sound/soc/sof/intel/tgl.c
@@ -24,40 +24,30 @@ static const struct snd_sof_debugfs_map tgl_dsp_debugfs[] = {
static int tgl_dsp_core_get(struct snd_sof_dev *sdev, int core)
{
- struct sof_ipc_pm_core_config pm_core_config = {
- .hdr = {
- .cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CORE_ENABLE,
- .size = sizeof(pm_core_config),
- },
- .enable_mask = sdev->enabled_cores_mask | BIT(core),
- };
+ const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm;
/* power up primary core if not already powered up and return */
if (core == SOF_DSP_PRIMARY_CORE)
return hda_dsp_enable_core(sdev, BIT(core));
- /* notify DSP for secondary cores */
- return sof_ipc_tx_message(sdev->ipc, &pm_core_config, sizeof(pm_core_config),
- &pm_core_config, sizeof(pm_core_config));
+ if (pm_ops->set_core_state)
+ return pm_ops->set_core_state(sdev, core, true);
+
+ return 0;
}
static int tgl_dsp_core_put(struct snd_sof_dev *sdev, int core)
{
- struct sof_ipc_pm_core_config pm_core_config = {
- .hdr = {
- .cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CORE_ENABLE,
- .size = sizeof(pm_core_config),
- },
- .enable_mask = sdev->enabled_cores_mask & ~BIT(core),
- };
+ const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm;
/* power down primary core and return */
if (core == SOF_DSP_PRIMARY_CORE)
return hda_dsp_core_reset_power_down(sdev, BIT(core));
- /* notify DSP for secondary cores */
- return sof_ipc_tx_message(sdev->ipc, &pm_core_config, sizeof(pm_core_config),
- &pm_core_config, sizeof(pm_core_config));
+ if (pm_ops->set_core_state)
+ return pm_ops->set_core_state(sdev, core, false);
+
+ return 0;
}
/* Tigerlake ops */
@@ -137,6 +127,7 @@ const struct sof_intel_dsp_desc tgl_chip_info = {
.sdw_alh_base = SDW_ALH_BASE,
.check_sdw_irq = hda_common_check_sdw_irq,
.check_ipc_irq = hda_dsp_check_ipc_irq,
+ .cl_init = cl_dsp_init,
.hw_ip_version = SOF_INTEL_CAVS_2_5,
};
EXPORT_SYMBOL_NS(tgl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
@@ -159,6 +150,7 @@ const struct sof_intel_dsp_desc tglh_chip_info = {
.sdw_alh_base = SDW_ALH_BASE,
.check_sdw_irq = hda_common_check_sdw_irq,
.check_ipc_irq = hda_dsp_check_ipc_irq,
+ .cl_init = cl_dsp_init,
.hw_ip_version = SOF_INTEL_CAVS_2_5,
};
EXPORT_SYMBOL_NS(tglh_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
@@ -181,6 +173,7 @@ const struct sof_intel_dsp_desc ehl_chip_info = {
.sdw_alh_base = SDW_ALH_BASE,
.check_sdw_irq = hda_common_check_sdw_irq,
.check_ipc_irq = hda_dsp_check_ipc_irq,
+ .cl_init = cl_dsp_init,
.hw_ip_version = SOF_INTEL_CAVS_2_5,
};
EXPORT_SYMBOL_NS(ehl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
@@ -203,6 +196,7 @@ const struct sof_intel_dsp_desc adls_chip_info = {
.sdw_alh_base = SDW_ALH_BASE,
.check_sdw_irq = hda_common_check_sdw_irq,
.check_ipc_irq = hda_dsp_check_ipc_irq,
+ .cl_init = cl_dsp_init,
.hw_ip_version = SOF_INTEL_CAVS_2_5,
};
EXPORT_SYMBOL_NS(adls_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c
index c5aef5fc056b..6ed3f9b6a0c4 100644
--- a/sound/soc/sof/ipc.c
+++ b/sound/soc/sof/ipc.c
@@ -155,12 +155,22 @@ struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev)
init_waitqueue_head(&msg->waitq);
- /*
- * Use IPC3 ops as it is the only available version now. With the addition of new IPC
- * versions, this will need to be modified to use the selected version at runtime.
- */
- ipc->ops = &ipc3_ops;
- ops = ipc->ops;
+ switch (sdev->pdata->ipc_type) {
+#if defined(CONFIG_SND_SOC_SOF_IPC3)
+ case SOF_IPC:
+ ops = &ipc3_ops;
+ break;
+#endif
+#if defined(CONFIG_SND_SOC_SOF_INTEL_IPC4)
+ case SOF_INTEL_IPC4:
+ ops = &ipc4_ops;
+ break;
+#endif
+ default:
+ dev_err(sdev->dev, "Not supported IPC version: %d\n",
+ sdev->pdata->ipc_type);
+ return NULL;
+ }
/* check for mandatory ops */
if (!ops->tx_msg || !ops->rx_msg || !ops->set_get_data || !ops->get_reply) {
@@ -190,6 +200,8 @@ struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev)
return NULL;
}
+ ipc->ops = ops;
+
return ipc;
}
EXPORT_SYMBOL(snd_sof_ipc_init);
diff --git a/sound/soc/sof/ipc3-dtrace.c b/sound/soc/sof/ipc3-dtrace.c
index b4e1343f9138..b815b0244d9e 100644
--- a/sound/soc/sof/ipc3-dtrace.c
+++ b/sound/soc/sof/ipc3-dtrace.c
@@ -18,6 +18,7 @@
enum sof_dtrace_state {
SOF_DTRACE_DISABLED,
SOF_DTRACE_STOPPED,
+ SOF_DTRACE_INITIALIZING,
SOF_DTRACE_ENABLED,
};
@@ -32,6 +33,15 @@ struct sof_dtrace_priv {
enum sof_dtrace_state dtrace_state;
};
+static bool trace_pos_update_expected(struct sof_dtrace_priv *priv)
+{
+ if (priv->dtrace_state == SOF_DTRACE_ENABLED ||
+ priv->dtrace_state == SOF_DTRACE_INITIALIZING)
+ return true;
+
+ return false;
+}
+
static int trace_filter_append_elem(struct snd_sof_dev *sdev, u32 key, u32 value,
struct sof_ipc_trace_filter_elem *elem_list,
int capacity, int *counter)
@@ -157,9 +167,8 @@ static int ipc3_trace_update_filter(struct snd_sof_dev *sdev, int num_elems,
msg->elem_cnt = num_elems;
memcpy(&msg->elems[0], elems, num_elems * sizeof(*elems));
- ret = pm_runtime_get_sync(sdev->dev);
+ ret = pm_runtime_resume_and_get(sdev->dev);
if (ret < 0 && ret != -EACCES) {
- pm_runtime_put_noidle(sdev->dev);
dev_err(sdev->dev, "enabling device failed: %d\n", ret);
goto error;
}
@@ -242,6 +251,21 @@ static int debugfs_create_trace_filter(struct snd_sof_dev *sdev)
return 0;
}
+static bool sof_dtrace_set_host_offset(struct sof_dtrace_priv *priv, u32 new_offset)
+{
+ u32 host_offset = READ_ONCE(priv->host_offset);
+
+ if (host_offset != new_offset) {
+ /* This is a bit paranoid and unlikely that it is needed */
+ u32 ret = cmpxchg(&priv->host_offset, host_offset, new_offset);
+
+ if (ret == host_offset)
+ return true;
+ }
+
+ return false;
+}
+
static size_t sof_dtrace_avail(struct snd_sof_dev *sdev,
loff_t pos, size_t buffer_size)
{
@@ -274,7 +298,7 @@ static size_t sof_wait_dtrace_avail(struct snd_sof_dev *sdev, loff_t pos,
if (ret)
return ret;
- if (priv->dtrace_state != SOF_DTRACE_ENABLED && priv->dtrace_draining) {
+ if (priv->dtrace_draining && !trace_pos_update_expected(priv)) {
/*
* tracing has ended and all traces have been
* read by client, return EOF
@@ -328,6 +352,10 @@ static ssize_t dfsentry_dtrace_read(struct file *file, char __user *buffer,
return -EIO;
}
+ /* no new trace data */
+ if (!avail)
+ return 0;
+
/* make sure count is <= avail */
if (count > avail)
count = avail;
@@ -358,7 +386,7 @@ static int dfsentry_dtrace_release(struct inode *inode, struct file *file)
/* avoid duplicate traces at next open */
if (priv->dtrace_state != SOF_DTRACE_ENABLED)
- priv->host_offset = 0;
+ sof_dtrace_set_host_offset(priv, 0);
return 0;
}
@@ -434,7 +462,7 @@ static int ipc3_dtrace_enable(struct snd_sof_dev *sdev)
params.buffer.pages = priv->dma_trace_pages;
params.stream_tag = 0;
- priv->host_offset = 0;
+ sof_dtrace_set_host_offset(priv, 0);
priv->dtrace_draining = false;
ret = sof_dtrace_host_init(sdev, &priv->dmatb, &params);
@@ -442,9 +470,10 @@ static int ipc3_dtrace_enable(struct snd_sof_dev *sdev)
dev_err(sdev->dev, "Host dtrace init failed: %d\n", ret);
return ret;
}
- dev_dbg(sdev->dev, "%s: stream_tag: %d\n", __func__, params.stream_tag);
+ dev_dbg(sdev->dev, "stream_tag: %d\n", params.stream_tag);
/* send IPC to the DSP */
+ priv->dtrace_state = SOF_DTRACE_INITIALIZING;
ret = sof_ipc_tx_message(sdev->ipc, &params, sizeof(params), &ipc_reply, sizeof(ipc_reply));
if (ret < 0) {
dev_err(sdev->dev, "can't set params for DMA for trace %d\n", ret);
@@ -452,17 +481,18 @@ static int ipc3_dtrace_enable(struct snd_sof_dev *sdev)
}
start:
+ priv->dtrace_state = SOF_DTRACE_ENABLED;
+
ret = sof_dtrace_host_trigger(sdev, SNDRV_PCM_TRIGGER_START);
if (ret < 0) {
dev_err(sdev->dev, "Host dtrace trigger start failed: %d\n", ret);
goto trace_release;
}
- priv->dtrace_state = SOF_DTRACE_ENABLED;
-
return 0;
trace_release:
+ priv->dtrace_state = SOF_DTRACE_DISABLED;
sof_dtrace_host_release(sdev);
return ret;
}
@@ -514,8 +544,7 @@ static int ipc3_dtrace_init(struct snd_sof_dev *sdev)
goto table_err;
priv->dma_trace_pages = ret;
- dev_dbg(sdev->dev, "%s: dma_trace_pages: %d\n", __func__,
- priv->dma_trace_pages);
+ dev_dbg(sdev->dev, "dma_trace_pages: %d\n", priv->dma_trace_pages);
if (sdev->first_boot) {
ret = debugfs_create_dtrace(sdev);
@@ -546,11 +575,9 @@ int ipc3_dtrace_posn_update(struct snd_sof_dev *sdev,
if (!sdev->fw_trace_is_supported)
return 0;
- if (priv->dtrace_state == SOF_DTRACE_ENABLED &&
- priv->host_offset != posn->host_offset) {
- priv->host_offset = posn->host_offset;
+ if (trace_pos_update_expected(priv) &&
+ sof_dtrace_set_host_offset(priv, posn->host_offset))
wake_up(&priv->trace_sleep);
- }
if (posn->overflow != 0)
dev_err(sdev->dev,
diff --git a/sound/soc/sof/ipc3-loader.c b/sound/soc/sof/ipc3-loader.c
index f3c741b49519..bf423ca4e97b 100644
--- a/sound/soc/sof/ipc3-loader.c
+++ b/sound/soc/sof/ipc3-loader.c
@@ -75,13 +75,12 @@ static int ipc3_fw_ext_man_get_config_data(struct snd_sof_dev *sdev,
elems_size = config->hdr.size - sizeof(struct sof_ext_man_elem_header);
elems_counter = elems_size / sizeof(struct sof_config_elem);
- dev_dbg(sdev->dev, "%s can hold up to %d config elements\n",
- __func__, elems_counter);
+ dev_dbg(sdev->dev, "manifest can hold up to %d config elements\n", elems_counter);
for (i = 0; i < elems_counter; ++i) {
elem = &config->elems[i];
- dev_dbg(sdev->dev, "%s get index %d token %d val %d\n",
- __func__, i, elem->token, elem->value);
+ dev_dbg(sdev->dev, "get index %d token %d val %d\n",
+ i, elem->token, elem->value);
switch (elem->token) {
case SOF_EXT_MAN_CONFIG_EMPTY:
/* unused memory space is zero filled - mapped to EMPTY elements */
@@ -110,7 +109,7 @@ static int ipc3_fw_ext_man_get_config_data(struct snd_sof_dev *sdev,
return 0;
}
-static ssize_t ipc3_fw_ext_man_size(const struct firmware *fw)
+static ssize_t ipc3_fw_ext_man_size(struct snd_sof_dev *sdev, const struct firmware *fw)
{
const struct sof_ext_man_header *head;
@@ -132,6 +131,8 @@ static ssize_t ipc3_fw_ext_man_size(const struct firmware *fw)
return head->full_size;
/* otherwise given fw don't have an extended manifest */
+ dev_dbg(sdev->dev, "Unexpected extended manifest magic number: %#x\n",
+ head->magic);
return 0;
}
@@ -148,7 +149,7 @@ static size_t sof_ipc3_fw_parse_ext_man(struct snd_sof_dev *sdev)
head = (struct sof_ext_man_header *)fw->data;
remaining = head->full_size - head->header_size;
- ext_man_size = ipc3_fw_ext_man_size(fw);
+ ext_man_size = ipc3_fw_ext_man_size(sdev, fw);
/* Assert firmware starts with extended manifest */
if (ext_man_size <= 0)
@@ -323,10 +324,10 @@ static int sof_ipc3_load_fw_to_dsp(struct snd_sof_dev *sdev)
header = (struct snd_sof_fw_header *)(fw->data + plat_data->fw_offset);
load_module = sof_ops(sdev)->load_module;
if (!load_module) {
- dev_dbg(sdev->dev, "%s: Using generic module loading\n", __func__);
+ dev_dbg(sdev->dev, "Using generic module loading\n");
load_module = sof_ipc3_parse_module_memcpy;
} else {
- dev_dbg(sdev->dev, "%s: Using custom module loading\n", __func__);
+ dev_dbg(sdev->dev, "Using custom module loading\n");
}
/* parse each module */
diff --git a/sound/soc/sof/ipc3-pcm.c b/sound/soc/sof/ipc3-pcm.c
index c8774a891d6f..9c6a84bdeca7 100644
--- a/sound/soc/sof/ipc3-pcm.c
+++ b/sound/soc/sof/ipc3-pcm.c
@@ -115,6 +115,9 @@ static int sof_ipc3_pcm_hw_params(struct snd_soc_component *component,
pcm.params.no_stream_position = 1;
}
+ if (platform_params->cont_update_posn)
+ pcm.params.cont_update_posn = 1;
+
dev_dbg(component->dev, "stream_tag %d", pcm.params.stream_tag);
/* send hw_params IPC to the DSP */
@@ -344,10 +347,10 @@ static int sof_ipc3_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd,
channels->min, channels->max);
break;
case SOF_DAI_AMD_DMIC:
- rate->min = private->dai_config->acpdmic.fsync_rate;
- rate->max = private->dai_config->acpdmic.fsync_rate;
- channels->min = private->dai_config->acpdmic.tdm_slots;
- channels->max = private->dai_config->acpdmic.tdm_slots;
+ rate->min = private->dai_config->acpdmic.pdm_rate;
+ rate->max = private->dai_config->acpdmic.pdm_rate;
+ channels->min = private->dai_config->acpdmic.pdm_ch;
+ channels->max = private->dai_config->acpdmic.pdm_ch;
dev_dbg(component->dev,
"AMD_DMIC rate_min: %d rate_max: %d\n", rate->min, rate->max);
diff --git a/sound/soc/sof/ipc3-topology.c b/sound/soc/sof/ipc3-topology.c
index 10740c55294d..65923e7a5976 100644
--- a/sound/soc/sof/ipc3-topology.c
+++ b/sound/soc/sof/ipc3-topology.c
@@ -17,6 +17,9 @@
/* Full volume for default values */
#define VOL_ZERO_DB BIT(VOLUME_FWL)
+/* size of tplg ABI in bytes */
+#define SOF_IPC3_TPLG_ABI_SIZE 3
+
struct sof_widget_data {
int ctrl_type;
int ipc_cmd;
@@ -263,6 +266,16 @@ static const struct sof_topology_token afe_tokens[] = {
offsetof(struct sof_ipc_dai_mtk_afe_params, format)},
};
+/* ACPDMIC */
+static const struct sof_topology_token acpdmic_tokens[] = {
+ {SOF_TKN_AMD_ACPDMIC_RATE,
+ SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_acpdmic_params, pdm_rate)},
+ {SOF_TKN_AMD_ACPDMIC_CH,
+ SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_acpdmic_params, pdm_ch)},
+};
+
/* Core tokens */
static const struct sof_topology_token core_tokens[] = {
{SOF_TKN_COMP_CORE_ID, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
@@ -297,6 +310,7 @@ static const struct sof_token_info ipc3_token_list[SOF_TOKEN_COUNT] = {
[SOF_ESAI_TOKENS] = {"ESAI tokens", esai_tokens, ARRAY_SIZE(esai_tokens)},
[SOF_SAI_TOKENS] = {"SAI tokens", sai_tokens, ARRAY_SIZE(sai_tokens)},
[SOF_AFE_TOKENS] = {"AFE tokens", afe_tokens, ARRAY_SIZE(afe_tokens)},
+ [SOF_ACPDMIC_TOKENS] = {"ACPDMIC tokens", acpdmic_tokens, ARRAY_SIZE(acpdmic_tokens)},
};
/**
@@ -1117,20 +1131,22 @@ static int sof_link_acp_dmic_load(struct snd_soc_component *scomp, struct snd_so
struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs;
struct sof_dai_private_data *private = dai->private;
u32 size = sizeof(*config);
+ int ret;
/* handle master/slave and inverted clocks */
sof_dai_set_format(hw_config, config);
- /* init IPC */
- memset(&config->acpdmic, 0, sizeof(config->acpdmic));
config->hdr.size = size;
- config->acpdmic.fsync_rate = le32_to_cpu(hw_config->fsync_rate);
- config->acpdmic.tdm_slots = le32_to_cpu(hw_config->tdm_slots);
+ /* parse the required set of ACPDMIC tokens based on num_hw_cfgs */
+ ret = sof_update_ipc_object(scomp, &config->acpdmic, SOF_ACPDMIC_TOKENS, slink->tuples,
+ slink->num_tuples, size, slink->num_hw_configs);
+ if (ret < 0)
+ return ret;
dev_info(scomp->dev, "ACP_DMIC config ACP%d channel %d rate %d\n",
- config->dai_index, config->acpdmic.tdm_slots,
- config->acpdmic.fsync_rate);
+ config->dai_index, config->acpdmic.pdm_ch,
+ config->acpdmic.pdm_rate);
dai->number_configs = 1;
dai->current_config = 0;
@@ -1436,8 +1452,8 @@ static int sof_ipc3_widget_setup_comp_dai(struct snd_sof_widget *swidget)
if (ret < 0)
goto free;
- dev_dbg(scomp->dev, "%s dai %s: type %d index %d\n",
- __func__, swidget->widget->name, comp_dai->type, comp_dai->dai_index);
+ dev_dbg(scomp->dev, "dai %s: type %d index %d\n",
+ swidget->widget->name, comp_dai->type, comp_dai->dai_index);
sof_dbg_comp_config(scomp, &comp_dai->config);
/* now update DAI config */
@@ -1628,6 +1644,7 @@ static int sof_ipc3_control_load_bytes(struct snd_sof_dev *sdev, struct snd_sof_
return 0;
err:
kfree(scontrol->ipc_control_data);
+ scontrol->ipc_control_data = NULL;
return ret;
}
@@ -2302,6 +2319,45 @@ static int sof_ipc3_dai_get_clk(struct snd_sof_dev *sdev, struct snd_sof_dai *da
return -EINVAL;
}
+static int sof_ipc3_parse_manifest(struct snd_soc_component *scomp, int index,
+ struct snd_soc_tplg_manifest *man)
+{
+ u32 size = le32_to_cpu(man->priv.size);
+ u32 abi_version;
+
+ /* backward compatible with tplg without ABI info */
+ if (!size) {
+ dev_dbg(scomp->dev, "No topology ABI info\n");
+ return 0;
+ }
+
+ if (size != SOF_IPC3_TPLG_ABI_SIZE) {
+ dev_err(scomp->dev, "%s: Invalid topology ABI size: %u\n",
+ __func__, size);
+ return -EINVAL;
+ }
+
+ dev_info(scomp->dev,
+ "Topology: ABI %d:%d:%d Kernel ABI %d:%d:%d\n",
+ man->priv.data[0], man->priv.data[1], man->priv.data[2],
+ SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH);
+
+ abi_version = SOF_ABI_VER(man->priv.data[0], man->priv.data[1], man->priv.data[2]);
+
+ if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, abi_version)) {
+ dev_err(scomp->dev, "%s: Incompatible topology ABI version\n", __func__);
+ return -EINVAL;
+ }
+
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS) &&
+ SOF_ABI_VERSION_MINOR(abi_version) > SOF_ABI_MINOR) {
+ dev_err(scomp->dev, "%s: Topology ABI is more recent than kernel\n", __func__);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
/* token list for each topology object */
static enum sof_tokens host_token_list[] = {
SOF_CORE_TOKENS,
@@ -2412,4 +2468,5 @@ const struct sof_ipc_tplg_ops ipc3_tplg_ops = {
.dai_get_clk = sof_ipc3_dai_get_clk,
.set_up_all_pipelines = sof_ipc3_set_up_all_pipelines,
.tear_down_all_pipelines = sof_ipc3_tear_down_all_pipelines,
+ .parse_manifest = sof_ipc3_parse_manifest,
};
diff --git a/sound/soc/sof/ipc3.c b/sound/soc/sof/ipc3.c
index dff5feaad370..82fa320253be 100644
--- a/sound/soc/sof/ipc3.c
+++ b/sound/soc/sof/ipc3.c
@@ -147,6 +147,8 @@ static void ipc3_log_header(struct device *dev, u8 *text, u32 cmd)
case SOF_IPC_TRACE_DMA_PARAMS:
str2 = "DMA_PARAMS"; break;
case SOF_IPC_TRACE_DMA_POSITION:
+ if (!sof_debug_check_flag(SOF_DBG_PRINT_DMA_POSITION_UPDATE_LOGS))
+ return;
str2 = "DMA_POSITION"; break;
case SOF_IPC_TRACE_DMA_PARAMS_EXT:
str2 = "DMA_PARAMS_EXT"; break;
@@ -290,7 +292,7 @@ static int ipc3_wait_tx_done(struct snd_sof_ipc *ipc, void *reply_data)
dev_err(sdev->dev,
"ipc tx timed out for %#x (msg/reply size: %d/%zu)\n",
hdr->cmd, hdr->size, msg->reply_size);
- snd_sof_handle_fw_exception(ipc->sdev);
+ snd_sof_handle_fw_exception(ipc->sdev, "IPC timeout");
ret = -ETIMEDOUT;
} else {
ret = msg->reply_error;
@@ -299,7 +301,8 @@ static int ipc3_wait_tx_done(struct snd_sof_ipc *ipc, void *reply_data)
"ipc tx error for %#x (msg/reply size: %d/%zu): %d\n",
hdr->cmd, hdr->size, msg->reply_size, ret);
} else {
- ipc3_log_header(sdev->dev, "ipc tx succeeded", hdr->cmd);
+ if (sof_debug_check_flag(SOF_DBG_PRINT_IPC_SUCCESS_LOGS))
+ ipc3_log_header(sdev->dev, "ipc tx succeeded", hdr->cmd);
if (msg->reply_size)
/* copy the data returned from DSP */
memcpy(reply_data, msg->reply_data,
@@ -755,13 +758,10 @@ int sof_ipc3_validate_fw_version(struct snd_sof_dev *sdev)
return -EINVAL;
}
- if (SOF_ABI_VERSION_MINOR(v->abi_version) > SOF_ABI_MINOR) {
- if (!IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS)) {
- dev_warn(sdev->dev, "FW ABI is more recent than kernel\n");
- } else {
- dev_err(sdev->dev, "FW ABI is more recent than kernel\n");
- return -EINVAL;
- }
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS) &&
+ SOF_ABI_VERSION_MINOR(v->abi_version) > SOF_ABI_MINOR) {
+ dev_err(sdev->dev, "FW ABI is more recent than kernel\n");
+ return -EINVAL;
}
if (ready->flags & SOF_IPC_INFO_BUILD) {
@@ -1037,6 +1037,23 @@ static void sof_ipc3_rx_msg(struct snd_sof_dev *sdev)
ipc3_log_header(sdev->dev, "ipc rx done", hdr.cmd);
}
+static int sof_ipc3_set_core_state(struct snd_sof_dev *sdev, int core_idx, bool on)
+{
+ struct sof_ipc_pm_core_config core_cfg = {
+ .hdr.size = sizeof(core_cfg),
+ .hdr.cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CORE_ENABLE,
+ };
+ struct sof_ipc_reply reply;
+
+ if (on)
+ core_cfg.enable_mask = sdev->enabled_cores_mask | BIT(core_idx);
+ else
+ core_cfg.enable_mask = sdev->enabled_cores_mask & ~BIT(core_idx);
+
+ return sof_ipc3_tx_msg(sdev, &core_cfg, sizeof(core_cfg),
+ &reply, sizeof(reply), false);
+}
+
static int sof_ipc3_ctx_ipc(struct snd_sof_dev *sdev, int cmd)
{
struct sof_ipc_pm_ctx pm_ctx = {
@@ -1063,6 +1080,7 @@ static int sof_ipc3_ctx_restore(struct snd_sof_dev *sdev)
static const struct sof_ipc_pm_ops ipc3_pm_ops = {
.ctx_save = sof_ipc3_ctx_save,
.ctx_restore = sof_ipc3_ctx_restore,
+ .set_core_state = sof_ipc3_set_core_state,
};
const struct sof_ipc_ops ipc3_ops = {
diff --git a/sound/soc/sof/ipc4-control.c b/sound/soc/sof/ipc4-control.c
new file mode 100644
index 000000000000..0d5a578c3496
--- /dev/null
+++ b/sound/soc/sof/ipc4-control.c
@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
+//
+//
+
+#include "sof-priv.h"
+#include "sof-audio.h"
+#include "ipc4-priv.h"
+#include "ipc4-topology.h"
+
+static int sof_ipc4_set_get_kcontrol_data(struct snd_sof_control *scontrol, bool set)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_ops *iops = sdev->ipc->ops;
+ struct sof_ipc4_msg *msg = &cdata->msg;
+ struct snd_sof_widget *swidget;
+ bool widget_found = false;
+
+ /* find widget associated with the control */
+ list_for_each_entry(swidget, &sdev->widget_list, list) {
+ if (swidget->comp_id == scontrol->comp_id) {
+ widget_found = true;
+ break;
+ }
+ }
+
+ if (!widget_found) {
+ dev_err(scomp->dev, "Failed to find widget for kcontrol %s\n", scontrol->name);
+ return -ENOENT;
+ }
+
+ /*
+ * Volatile controls should always be part of static pipelines and the widget use_count
+ * would always be > 0 in this case. For the others, just return the cached value if the
+ * widget is not set up.
+ */
+ if (!swidget->use_count)
+ return 0;
+
+ msg->primary &= ~SOF_IPC4_MOD_INSTANCE_MASK;
+ msg->primary |= SOF_IPC4_MOD_INSTANCE(swidget->instance_id);
+
+ return iops->set_get_data(sdev, msg, msg->data_size, set);
+}
+
+static int
+sof_ipc4_set_volume_data(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget,
+ struct snd_sof_control *scontrol)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct sof_ipc4_gain *gain = swidget->private;
+ struct sof_ipc4_msg *msg = &cdata->msg;
+ struct sof_ipc4_gain_data data;
+ bool all_channels_equal = true;
+ u32 value;
+ int ret, i;
+
+ /* check if all channel values are equal */
+ value = cdata->chanv[0].value;
+ for (i = 1; i < scontrol->num_channels; i++) {
+ if (cdata->chanv[i].value != value) {
+ all_channels_equal = false;
+ break;
+ }
+ }
+
+ /*
+ * notify DSP with a single IPC message if all channel values are equal. Otherwise send
+ * a separate IPC for each channel.
+ */
+ for (i = 0; i < scontrol->num_channels; i++) {
+ if (all_channels_equal) {
+ data.channels = SOF_IPC4_GAIN_ALL_CHANNELS_MASK;
+ data.init_val = cdata->chanv[0].value;
+ } else {
+ data.channels = cdata->chanv[i].channel;
+ data.init_val = cdata->chanv[i].value;
+ }
+
+ /* set curve type and duration from topology */
+ data.curve_duration = gain->data.curve_duration;
+ data.curve_type = gain->data.curve_type;
+
+ msg->data_ptr = &data;
+ msg->data_size = sizeof(data);
+
+ ret = sof_ipc4_set_get_kcontrol_data(scontrol, true);
+ msg->data_ptr = NULL;
+ msg->data_size = 0;
+ if (ret < 0) {
+ dev_err(sdev->dev, "Failed to set volume update for %s\n",
+ scontrol->name);
+ return ret;
+ }
+
+ if (all_channels_equal)
+ break;
+ }
+
+ return 0;
+}
+
+static bool sof_ipc4_volume_put(struct snd_sof_control *scontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ unsigned int channels = scontrol->num_channels;
+ struct snd_sof_widget *swidget;
+ bool widget_found = false;
+ bool change = false;
+ unsigned int i;
+ int ret;
+
+ /* update each channel */
+ for (i = 0; i < channels; i++) {
+ u32 value = mixer_to_ipc(ucontrol->value.integer.value[i],
+ scontrol->volume_table, scontrol->max + 1);
+
+ change = change || (value != cdata->chanv[i].value);
+ cdata->chanv[i].channel = i;
+ cdata->chanv[i].value = value;
+ }
+
+ if (!pm_runtime_active(scomp->dev))
+ return change;
+
+ /* find widget associated with the control */
+ list_for_each_entry(swidget, &sdev->widget_list, list) {
+ if (swidget->comp_id == scontrol->comp_id) {
+ widget_found = true;
+ break;
+ }
+ }
+
+ if (!widget_found) {
+ dev_err(scomp->dev, "Failed to find widget for kcontrol %s\n", scontrol->name);
+ return false;
+ }
+
+ ret = sof_ipc4_set_volume_data(sdev, swidget, scontrol);
+ if (ret < 0)
+ return false;
+
+ return change;
+}
+
+static int sof_ipc4_volume_get(struct snd_sof_control *scontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ unsigned int channels = scontrol->num_channels;
+ unsigned int i;
+
+ for (i = 0; i < channels; i++)
+ ucontrol->value.integer.value[i] = ipc_to_mixer(cdata->chanv[i].value,
+ scontrol->volume_table,
+ scontrol->max + 1);
+
+ return 0;
+}
+
+/* set up all controls for the widget */
+static int sof_ipc4_widget_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
+{
+ struct snd_sof_control *scontrol;
+ int ret;
+
+ list_for_each_entry(scontrol, &sdev->kcontrol_list, list)
+ if (scontrol->comp_id == swidget->comp_id) {
+ ret = sof_ipc4_set_volume_data(sdev, swidget, scontrol);
+ if (ret < 0) {
+ dev_err(sdev->dev, "%s: kcontrol %d set up failed for widget %s\n",
+ __func__, scontrol->comp_id, swidget->widget->name);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int
+sof_ipc4_set_up_volume_table(struct snd_sof_control *scontrol, int tlv[SOF_TLV_ITEMS], int size)
+{
+ int i;
+
+ /* init the volume table */
+ scontrol->volume_table = kcalloc(size, sizeof(u32), GFP_KERNEL);
+ if (!scontrol->volume_table)
+ return -ENOMEM;
+
+ /* populate the volume table */
+ for (i = 0; i < size ; i++) {
+ u32 val = vol_compute_gain(i, tlv);
+ u64 q31val = ((u64)val) << 15; /* Can be over Q1.31, need to saturate */
+
+ scontrol->volume_table[i] = q31val > SOF_IPC4_VOL_ZERO_DB ?
+ SOF_IPC4_VOL_ZERO_DB : q31val;
+ }
+
+ return 0;
+}
+
+const struct sof_ipc_tplg_control_ops tplg_ipc4_control_ops = {
+ .volume_put = sof_ipc4_volume_put,
+ .volume_get = sof_ipc4_volume_get,
+ .widget_kcontrol_setup = sof_ipc4_widget_kcontrol_setup,
+ .set_up_volume_table = sof_ipc4_set_up_volume_table,
+};
diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c
new file mode 100644
index 000000000000..732872395980
--- /dev/null
+++ b/sound/soc/sof/ipc4-pcm.c
@@ -0,0 +1,234 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
+//
+
+#include <sound/pcm_params.h>
+#include <sound/sof/ipc4/header.h>
+#include "sof-audio.h"
+#include "sof-priv.h"
+#include "ipc4-priv.h"
+#include "ipc4-topology.h"
+
+int sof_ipc4_set_pipeline_state(struct snd_sof_dev *sdev, u32 id, u32 state)
+{
+ struct sof_ipc4_msg msg = {{ 0 }};
+ u32 primary;
+
+ dev_dbg(sdev->dev, "ipc4 set pipeline %d state %d", id, state);
+
+ primary = state;
+ primary |= SOF_IPC4_GLB_PIPE_STATE_ID(id);
+ primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_SET_PIPELINE_STATE);
+ primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG);
+
+ msg.primary = primary;
+
+ return sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0);
+}
+EXPORT_SYMBOL(sof_ipc4_set_pipeline_state);
+
+static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream, int state)
+{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_sof_widget *pipeline_widget;
+ struct snd_soc_dapm_widget_list *list;
+ struct snd_soc_dapm_widget *widget;
+ struct sof_ipc4_pipeline *pipeline;
+ struct snd_sof_widget *swidget;
+ struct snd_sof_pcm *spcm;
+ int ret = 0;
+ int num_widgets;
+
+ spcm = snd_sof_find_spcm_dai(component, rtd);
+ if (!spcm)
+ return -EINVAL;
+
+ list = spcm->stream[substream->stream].list;
+
+ for_each_dapm_widgets(list, num_widgets, widget) {
+ swidget = widget->dobj.private;
+
+ if (!swidget)
+ continue;
+
+ /*
+ * set pipeline state for both FE and BE pipelines for RUNNING state.
+ * For PAUSE/RESET, set the pipeline state only for the FE pipeline.
+ */
+ switch (state) {
+ case SOF_IPC4_PIPE_PAUSED:
+ case SOF_IPC4_PIPE_RESET:
+ if (!WIDGET_IS_AIF(swidget->id))
+ continue;
+ break;
+ default:
+ break;
+ }
+
+ /* find pipeline widget for the pipeline that this widget belongs to */
+ pipeline_widget = swidget->pipe_widget;
+ pipeline = (struct sof_ipc4_pipeline *)pipeline_widget->private;
+
+ if (pipeline->state == state)
+ continue;
+
+ /* first set the pipeline to PAUSED state */
+ if (pipeline->state != SOF_IPC4_PIPE_PAUSED) {
+ ret = sof_ipc4_set_pipeline_state(sdev, swidget->pipeline_id,
+ SOF_IPC4_PIPE_PAUSED);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to pause pipeline %d\n",
+ swidget->pipeline_id);
+ return ret;
+ }
+ }
+
+ pipeline->state = SOF_IPC4_PIPE_PAUSED;
+
+ if (pipeline->state == state)
+ continue;
+
+ /* then set the final state */
+ ret = sof_ipc4_set_pipeline_state(sdev, swidget->pipeline_id, state);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to set state %d for pipeline %d\n",
+ state, swidget->pipeline_id);
+ break;
+ }
+
+ pipeline->state = state;
+ }
+
+ return ret;
+}
+
+static int sof_ipc4_pcm_trigger(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ int state;
+
+ /* determine the pipeline state */
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ state = SOF_IPC4_PIPE_PAUSED;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_START:
+ state = SOF_IPC4_PIPE_RUNNING;
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ state = SOF_IPC4_PIPE_PAUSED;
+ break;
+ default:
+ dev_err(component->dev, "%s: unhandled trigger cmd %d\n", __func__, cmd);
+ return -EINVAL;
+ }
+
+ /* set the pipeline state */
+ return sof_ipc4_trigger_pipelines(component, substream, state);
+}
+
+static int sof_ipc4_pcm_hw_free(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ return sof_ipc4_trigger_pipelines(component, substream, SOF_IPC4_PIPE_RESET);
+}
+
+static void ipc4_ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, const char *link_name,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_sof_dai_link *slink;
+ struct snd_sof_dai *dai;
+ bool dai_link_found = false;
+ int i;
+
+ list_for_each_entry(slink, &sdev->dai_link_list, list) {
+ if (!strcmp(slink->link->name, link_name)) {
+ dai_link_found = true;
+ break;
+ }
+ }
+
+ if (!dai_link_found)
+ return;
+
+ for (i = 0; i < slink->num_hw_configs; i++) {
+ struct snd_soc_tplg_hw_config *hw_config = &slink->hw_configs[i];
+
+ if (params_rate(params) == le32_to_cpu(hw_config->fsync_rate)) {
+ /* set current config for all DAI's with matching name */
+ list_for_each_entry(dai, &sdev->dai_list, list)
+ if (!strcmp(slink->link->name, dai->name))
+ dai->current_config = le32_to_cpu(hw_config->id);
+ break;
+ }
+ }
+}
+
+static int sof_ipc4_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);
+ struct snd_sof_dai *dai = snd_sof_find_dai(component, rtd->dai_link->name);
+ struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ struct sof_ipc4_copier *ipc4_copier;
+ struct snd_soc_dpcm *dpcm;
+
+ if (!dai) {
+ dev_err(component->dev, "%s: No DAI found with name %s\n", __func__,
+ rtd->dai_link->name);
+ return -EINVAL;
+ }
+
+ ipc4_copier = dai->private;
+ if (!ipc4_copier) {
+ dev_err(component->dev, "%s: No private data found for DAI %s\n",
+ __func__, rtd->dai_link->name);
+ return -EINVAL;
+ }
+
+ /* always set BE format to 32-bits for both playback and capture */
+ snd_mask_none(fmt);
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE);
+
+ rate->min = ipc4_copier->available_fmt.base_config->audio_fmt.sampling_frequency;
+ rate->max = rate->min;
+
+ /*
+ * Set trigger order for capture to SND_SOC_DPCM_TRIGGER_PRE. This is required
+ * to ensure that the BE DAI pipeline gets stopped/suspended before the FE DAI
+ * pipeline gets triggered and the pipeline widgets are freed.
+ */
+ for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_CAPTURE, dpcm) {
+ struct snd_soc_pcm_runtime *fe = dpcm->fe;
+
+ fe->dai_link->trigger[SNDRV_PCM_STREAM_CAPTURE] = SND_SOC_DPCM_TRIGGER_PRE;
+ }
+
+ switch (ipc4_copier->dai_type) {
+ case SOF_DAI_INTEL_SSP:
+ ipc4_ssp_dai_config_pcm_params_match(sdev, (char *)rtd->dai_link->name, params);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+const struct sof_ipc_pcm_ops ipc4_pcm_ops = {
+ .trigger = sof_ipc4_pcm_trigger,
+ .hw_free = sof_ipc4_pcm_hw_free,
+ .dai_link_fixup = sof_ipc4_pcm_dai_link_fixup,
+};
diff --git a/sound/soc/sof/ipc4-priv.h b/sound/soc/sof/ipc4-priv.h
index 2b71d5675933..9492fe1796c2 100644
--- a/sound/soc/sof/ipc4-priv.h
+++ b/sound/soc/sof/ipc4-priv.h
@@ -18,11 +18,13 @@
* @manifest_fw_hdr_offset: FW header offset in the manifest
* @num_fw_modules : Number of modules in base FW
* @fw_modules: Array of base FW modules
+ * @nhlt: NHLT table either from the BIOS or the topology manifest
*/
struct sof_ipc4_fw_data {
u32 manifest_fw_hdr_offset;
int num_fw_modules;
void *fw_modules;
+ void *nhlt;
};
/**
@@ -40,5 +42,10 @@ struct sof_ipc4_fw_module {
};
extern const struct sof_ipc_fw_loader_ops ipc4_loader_ops;
+extern const struct sof_ipc_tplg_ops ipc4_tplg_ops;
+extern const struct sof_ipc_tplg_control_ops tplg_ipc4_control_ops;
+extern const struct sof_ipc_pcm_ops ipc4_pcm_ops;
+
+int sof_ipc4_set_pipeline_state(struct snd_sof_dev *sdev, u32 id, u32 state);
#endif
diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c
new file mode 100644
index 000000000000..af072b484a60
--- /dev/null
+++ b/sound/soc/sof/ipc4-topology.c
@@ -0,0 +1,1921 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
+//
+//
+#include <uapi/sound/sof/tokens.h>
+#include <sound/pcm_params.h>
+#include <sound/sof/ext_manifest4.h>
+#include <sound/intel-nhlt.h>
+#include "sof-priv.h"
+#include "sof-audio.h"
+#include "ipc4-priv.h"
+#include "ipc4-topology.h"
+#include "ops.h"
+
+#define SOF_IPC4_GAIN_PARAM_ID 0
+#define SOF_IPC4_TPLG_ABI_SIZE 6
+
+static DEFINE_IDA(alh_group_ida);
+
+static const struct sof_topology_token ipc4_sched_tokens[] = {
+ {SOF_TKN_SCHED_LP_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pipeline, lp_mode)}
+};
+
+static const struct sof_topology_token pipeline_tokens[] = {
+ {SOF_TKN_SCHED_DYNAMIC_PIPELINE, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16,
+ offsetof(struct snd_sof_widget, dynamic_pipeline_widget)},
+};
+
+static const struct sof_topology_token ipc4_comp_tokens[] = {
+ {SOF_TKN_COMP_CPC, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_base_module_cfg, cpc)},
+ {SOF_TKN_COMP_IS_PAGES, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_base_module_cfg, is_pages)},
+};
+
+static const struct sof_topology_token ipc4_audio_format_buffer_size_tokens[] = {
+ {SOF_TKN_CAVS_AUDIO_FORMAT_IBS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_base_module_cfg, ibs)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_OBS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_base_module_cfg, obs)},
+};
+
+static const struct sof_topology_token ipc4_in_audio_format_tokens[] = {
+ {SOF_TKN_CAVS_AUDIO_FORMAT_IN_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_audio_format, sampling_frequency)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_IN_BIT_DEPTH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_audio_format, bit_depth)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_IN_CH_MAP, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_audio_format, ch_map)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_IN_CH_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_audio_format, ch_cfg)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_IN_INTERLEAVING_STYLE, SND_SOC_TPLG_TUPLE_TYPE_WORD,
+ get_token_u32, offsetof(struct sof_ipc4_audio_format, interleaving_style)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_IN_FMT_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_audio_format, fmt_cfg)},
+};
+
+static const struct sof_topology_token ipc4_out_audio_format_tokens[] = {
+ {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_audio_format, sampling_frequency)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_BIT_DEPTH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_audio_format, bit_depth)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_CH_MAP, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_audio_format, ch_map)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_CH_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_audio_format, ch_cfg)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_INTERLEAVING_STYLE, SND_SOC_TPLG_TUPLE_TYPE_WORD,
+ get_token_u32, offsetof(struct sof_ipc4_audio_format, interleaving_style)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_FMT_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_audio_format, fmt_cfg)},
+};
+
+static const struct sof_topology_token ipc4_copier_gateway_cfg_tokens[] = {
+ {SOF_TKN_CAVS_AUDIO_FORMAT_DMA_BUFFER_SIZE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, 0},
+};
+
+static const struct sof_topology_token ipc4_copier_tokens[] = {
+ {SOF_TKN_INTEL_COPIER_NODE_TYPE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, 0},
+};
+
+static const struct sof_topology_token ipc4_audio_fmt_num_tokens[] = {
+ {SOF_TKN_COMP_NUM_AUDIO_FORMATS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ 0},
+};
+
+static const struct sof_topology_token dai_tokens[] = {
+ {SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type,
+ offsetof(struct sof_ipc4_copier, dai_type)},
+ {SOF_TKN_DAI_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_copier, dai_index)},
+};
+
+/* Component extended tokens */
+static const struct sof_topology_token comp_ext_tokens[] = {
+ {SOF_TKN_COMP_UUID, SND_SOC_TPLG_TUPLE_TYPE_UUID, get_token_uuid,
+ offsetof(struct snd_sof_widget, uuid)},
+};
+
+static const struct sof_topology_token gain_tokens[] = {
+ {SOF_TKN_GAIN_RAMP_TYPE, SND_SOC_TPLG_TUPLE_TYPE_WORD,
+ get_token_u32, offsetof(struct sof_ipc4_gain_data, curve_type)},
+ {SOF_TKN_GAIN_RAMP_DURATION,
+ SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_gain_data, curve_duration)},
+ {SOF_TKN_GAIN_VAL, SND_SOC_TPLG_TUPLE_TYPE_WORD,
+ get_token_u32, offsetof(struct sof_ipc4_gain_data, init_val)},
+};
+
+/* SRC */
+static const struct sof_topology_token src_tokens[] = {
+ {SOF_TKN_SRC_RATE_OUT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_src, sink_rate)},
+};
+
+static const struct sof_token_info ipc4_token_list[SOF_TOKEN_COUNT] = {
+ [SOF_DAI_TOKENS] = {"DAI tokens", dai_tokens, ARRAY_SIZE(dai_tokens)},
+ [SOF_PIPELINE_TOKENS] = {"Pipeline tokens", pipeline_tokens, ARRAY_SIZE(pipeline_tokens)},
+ [SOF_SCHED_TOKENS] = {"Scheduler tokens", ipc4_sched_tokens,
+ ARRAY_SIZE(ipc4_sched_tokens)},
+ [SOF_COMP_EXT_TOKENS] = {"Comp extended tokens", comp_ext_tokens,
+ ARRAY_SIZE(comp_ext_tokens)},
+ [SOF_COMP_TOKENS] = {"IPC4 Component tokens",
+ ipc4_comp_tokens, ARRAY_SIZE(ipc4_comp_tokens)},
+ [SOF_IN_AUDIO_FORMAT_TOKENS] = {"IPC4 Input Audio format tokens",
+ ipc4_in_audio_format_tokens, ARRAY_SIZE(ipc4_in_audio_format_tokens)},
+ [SOF_OUT_AUDIO_FORMAT_TOKENS] = {"IPC4 Output Audio format tokens",
+ ipc4_out_audio_format_tokens, ARRAY_SIZE(ipc4_out_audio_format_tokens)},
+ [SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS] = {"IPC4 Audio format buffer size tokens",
+ ipc4_audio_format_buffer_size_tokens,
+ ARRAY_SIZE(ipc4_audio_format_buffer_size_tokens)},
+ [SOF_COPIER_GATEWAY_CFG_TOKENS] = {"IPC4 Copier gateway config tokens",
+ ipc4_copier_gateway_cfg_tokens, ARRAY_SIZE(ipc4_copier_gateway_cfg_tokens)},
+ [SOF_COPIER_TOKENS] = {"IPC4 Copier tokens", ipc4_copier_tokens,
+ ARRAY_SIZE(ipc4_copier_tokens)},
+ [SOF_AUDIO_FMT_NUM_TOKENS] = {"IPC4 Audio format number tokens",
+ ipc4_audio_fmt_num_tokens, ARRAY_SIZE(ipc4_audio_fmt_num_tokens)},
+ [SOF_GAIN_TOKENS] = {"Gain tokens", gain_tokens, ARRAY_SIZE(gain_tokens)},
+ [SOF_SRC_TOKENS] = {"SRC tokens", src_tokens, ARRAY_SIZE(src_tokens)},
+};
+
+static void sof_ipc4_dbg_audio_format(struct device *dev,
+ struct sof_ipc4_audio_format *format,
+ size_t object_size, int num_format)
+{
+ struct sof_ipc4_audio_format *fmt;
+ void *ptr = format;
+ int i;
+
+ for (i = 0; i < num_format; i++, ptr = (u8 *)ptr + object_size) {
+ fmt = ptr;
+ dev_dbg(dev,
+ " #%d: %uKHz, %ubit (ch_map %#x ch_cfg %u interleaving_style %u fmt_cfg %#x)\n",
+ i, fmt->sampling_frequency, fmt->bit_depth, fmt->ch_map,
+ fmt->ch_cfg, fmt->interleaving_style, fmt->fmt_cfg);
+ }
+}
+
+/**
+ * sof_ipc4_get_audio_fmt - get available audio formats from swidget->tuples
+ * @scomp: pointer to pointer to SOC component
+ * @swidget: pointer to struct snd_sof_widget containing tuples
+ * @available_fmt: pointer to struct sof_ipc4_available_audio_format being filling in
+ * @has_out_format: true if available_fmt contains output format
+ *
+ * Return: 0 if successful
+ */
+static int sof_ipc4_get_audio_fmt(struct snd_soc_component *scomp,
+ struct snd_sof_widget *swidget,
+ struct sof_ipc4_available_audio_format *available_fmt,
+ bool has_out_format)
+{
+ struct sof_ipc4_base_module_cfg *base_config;
+ struct sof_ipc4_audio_format *out_format;
+ int audio_fmt_num = 0;
+ int ret, i;
+
+ ret = sof_update_ipc_object(scomp, &audio_fmt_num,
+ SOF_AUDIO_FMT_NUM_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(audio_fmt_num), 1);
+ if (ret || audio_fmt_num <= 0) {
+ dev_err(scomp->dev, "Invalid number of audio formats: %d\n", audio_fmt_num);
+ return -EINVAL;
+ }
+ available_fmt->audio_fmt_num = audio_fmt_num;
+
+ dev_dbg(scomp->dev, "Number of audio formats: %d\n", available_fmt->audio_fmt_num);
+
+ base_config = kcalloc(available_fmt->audio_fmt_num, sizeof(*base_config), GFP_KERNEL);
+ if (!base_config)
+ return -ENOMEM;
+
+ /* set cpc and is_pages for all base_cfg */
+ for (i = 0; i < available_fmt->audio_fmt_num; i++) {
+ ret = sof_update_ipc_object(scomp, &base_config[i],
+ SOF_COMP_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*base_config), 1);
+ if (ret) {
+ dev_err(scomp->dev, "parse comp tokens failed %d\n", ret);
+ goto err_in;
+ }
+ }
+
+ /* copy the ibs/obs for each base_cfg */
+ ret = sof_update_ipc_object(scomp, base_config,
+ SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*base_config),
+ available_fmt->audio_fmt_num);
+ if (ret) {
+ dev_err(scomp->dev, "parse buffer size tokens failed %d\n", ret);
+ goto err_in;
+ }
+
+ for (i = 0; i < available_fmt->audio_fmt_num; i++)
+ dev_dbg(scomp->dev, "%d: ibs: %d obs: %d cpc: %d is_pages: %d\n", i,
+ base_config[i].ibs, base_config[i].obs,
+ base_config[i].cpc, base_config[i].is_pages);
+
+ ret = sof_update_ipc_object(scomp, &base_config->audio_fmt,
+ SOF_IN_AUDIO_FORMAT_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*base_config),
+ available_fmt->audio_fmt_num);
+ if (ret) {
+ dev_err(scomp->dev, "parse base_config audio_fmt tokens failed %d\n", ret);
+ goto err_in;
+ }
+
+ dev_dbg(scomp->dev, "Get input audio formats for %s\n", swidget->widget->name);
+ sof_ipc4_dbg_audio_format(scomp->dev, &base_config->audio_fmt,
+ sizeof(*base_config),
+ available_fmt->audio_fmt_num);
+
+ available_fmt->base_config = base_config;
+
+ if (!has_out_format)
+ return 0;
+
+ out_format = kcalloc(available_fmt->audio_fmt_num, sizeof(*out_format), GFP_KERNEL);
+ if (!out_format) {
+ ret = -ENOMEM;
+ goto err_in;
+ }
+
+ ret = sof_update_ipc_object(scomp, out_format,
+ SOF_OUT_AUDIO_FORMAT_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*out_format),
+ available_fmt->audio_fmt_num);
+
+ if (ret) {
+ dev_err(scomp->dev, "parse output audio_fmt tokens failed\n");
+ goto err_out;
+ }
+
+ available_fmt->out_audio_fmt = out_format;
+ dev_dbg(scomp->dev, "Get output audio formats for %s\n", swidget->widget->name);
+ sof_ipc4_dbg_audio_format(scomp->dev, out_format, sizeof(*out_format),
+ available_fmt->audio_fmt_num);
+
+ return 0;
+
+err_out:
+ kfree(out_format);
+err_in:
+ kfree(base_config);
+
+ return ret;
+}
+
+/* release the memory allocated in sof_ipc4_get_audio_fmt */
+static void sof_ipc4_free_audio_fmt(struct sof_ipc4_available_audio_format *available_fmt)
+
+{
+ kfree(available_fmt->base_config);
+ available_fmt->base_config = NULL;
+ kfree(available_fmt->out_audio_fmt);
+ available_fmt->out_audio_fmt = NULL;
+}
+
+static void sof_ipc4_widget_free_comp(struct snd_sof_widget *swidget)
+{
+ kfree(swidget->private);
+}
+
+static int sof_ipc4_widget_set_module_info(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ struct sof_ipc4_fw_module *fw_modules = ipc4_data->fw_modules;
+ int i;
+
+ if (!fw_modules) {
+ dev_err(sdev->dev, "no fw_module information\n");
+ return -EINVAL;
+ }
+
+ /* set module info */
+ for (i = 0; i < ipc4_data->num_fw_modules; i++) {
+ if (guid_equal(&swidget->uuid, &fw_modules[i].man4_module_entry.uuid)) {
+ swidget->module_info = &fw_modules[i];
+ return 0;
+ }
+ }
+
+ dev_err(sdev->dev, "failed to find module info for widget %s with UUID %pUL\n",
+ swidget->widget->name, &swidget->uuid);
+ return -EINVAL;
+}
+
+static int sof_ipc4_widget_setup_msg(struct snd_sof_widget *swidget, struct sof_ipc4_msg *msg)
+{
+ struct sof_ipc4_fw_module *fw_module;
+ uint32_t type;
+ int ret;
+
+ ret = sof_ipc4_widget_set_module_info(swidget);
+ if (ret)
+ return ret;
+
+ fw_module = swidget->module_info;
+
+ msg->primary = fw_module->man4_module_entry.id;
+ msg->primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_INIT_INSTANCE);
+ msg->primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg->primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+
+ msg->extension = SOF_IPC4_MOD_EXT_PPL_ID(swidget->pipeline_id);
+ msg->extension |= SOF_IPC4_MOD_EXT_CORE_ID(swidget->core);
+
+ type = fw_module->man4_module_entry.type & SOF_IPC4_MODULE_DP ? 1 : 0;
+ msg->extension |= SOF_IPC4_MOD_EXT_DOMAIN(type);
+
+ return 0;
+}
+
+static int sof_ipc4_widget_setup_pcm(struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_available_audio_format *available_fmt;
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct sof_ipc4_copier *ipc4_copier;
+ int node_type = 0;
+ int ret, i;
+
+ ipc4_copier = kzalloc(sizeof(*ipc4_copier), GFP_KERNEL);
+ if (!ipc4_copier)
+ return -ENOMEM;
+
+ swidget->private = ipc4_copier;
+ available_fmt = &ipc4_copier->available_fmt;
+
+ dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name);
+
+ ret = sof_ipc4_get_audio_fmt(scomp, swidget, available_fmt, true);
+ if (ret)
+ goto free_copier;
+
+ available_fmt->dma_buffer_size = kcalloc(available_fmt->audio_fmt_num, sizeof(u32),
+ GFP_KERNEL);
+ if (!available_fmt->dma_buffer_size) {
+ ret = -ENOMEM;
+ goto free_available_fmt;
+ }
+
+ ret = sof_update_ipc_object(scomp, available_fmt->dma_buffer_size,
+ SOF_COPIER_GATEWAY_CFG_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(u32),
+ available_fmt->audio_fmt_num);
+ if (ret) {
+ dev_err(scomp->dev, "Failed to parse dma buffer size in audio format for %s\n",
+ swidget->widget->name);
+ goto err;
+ }
+
+ dev_dbg(scomp->dev, "dma buffer size:\n");
+ for (i = 0; i < available_fmt->audio_fmt_num; i++)
+ dev_dbg(scomp->dev, "%d: %u\n", i,
+ available_fmt->dma_buffer_size[i]);
+
+ ret = sof_update_ipc_object(scomp, &node_type,
+ SOF_COPIER_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(node_type), 1);
+
+ if (ret) {
+ dev_err(scomp->dev, "parse host copier node type token failed %d\n",
+ ret);
+ goto err;
+ }
+ dev_dbg(scomp->dev, "host copier '%s' node_type %u\n", swidget->widget->name, node_type);
+
+ ipc4_copier->data.gtw_cfg.node_id = SOF_IPC4_NODE_TYPE(node_type);
+ ipc4_copier->gtw_attr = kzalloc(sizeof(*ipc4_copier->gtw_attr), GFP_KERNEL);
+ if (!ipc4_copier->gtw_attr) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ ipc4_copier->copier_config = (uint32_t *)ipc4_copier->gtw_attr;
+ ipc4_copier->data.gtw_cfg.config_length =
+ sizeof(struct sof_ipc4_gtw_attributes) >> 2;
+
+ /* set up module info and message header */
+ ret = sof_ipc4_widget_setup_msg(swidget, &ipc4_copier->msg);
+ if (ret)
+ goto free_gtw_attr;
+
+ return 0;
+
+free_gtw_attr:
+ kfree(ipc4_copier->gtw_attr);
+err:
+ kfree(available_fmt->dma_buffer_size);
+free_available_fmt:
+ sof_ipc4_free_audio_fmt(available_fmt);
+free_copier:
+ kfree(ipc4_copier);
+ swidget->private = NULL;
+ return ret;
+}
+
+static void sof_ipc4_widget_free_comp_pcm(struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_copier *ipc4_copier = swidget->private;
+ struct sof_ipc4_available_audio_format *available_fmt;
+
+ if (!ipc4_copier)
+ return;
+
+ available_fmt = &ipc4_copier->available_fmt;
+ kfree(available_fmt->dma_buffer_size);
+ kfree(available_fmt->base_config);
+ kfree(available_fmt->out_audio_fmt);
+ kfree(ipc4_copier->gtw_attr);
+ kfree(ipc4_copier);
+ swidget->private = NULL;
+}
+
+static int sof_ipc4_widget_setup_comp_dai(struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_available_audio_format *available_fmt;
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_dai *dai = swidget->private;
+ struct sof_ipc4_copier *ipc4_copier;
+ int node_type = 0;
+ int ret, i;
+
+ ipc4_copier = kzalloc(sizeof(*ipc4_copier), GFP_KERNEL);
+ if (!ipc4_copier)
+ return -ENOMEM;
+
+ available_fmt = &ipc4_copier->available_fmt;
+
+ dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name);
+
+ ret = sof_ipc4_get_audio_fmt(scomp, swidget, available_fmt, true);
+ if (ret)
+ goto free_copier;
+
+ available_fmt->dma_buffer_size = kcalloc(available_fmt->audio_fmt_num, sizeof(u32),
+ GFP_KERNEL);
+ if (!available_fmt->dma_buffer_size) {
+ ret = -ENOMEM;
+ goto free_available_fmt;
+ }
+
+ ret = sof_update_ipc_object(scomp, available_fmt->dma_buffer_size,
+ SOF_COPIER_GATEWAY_CFG_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(u32),
+ available_fmt->audio_fmt_num);
+ if (ret) {
+ dev_err(scomp->dev, "Failed to parse dma buffer size in audio format for %s\n",
+ swidget->widget->name);
+ goto err;
+ }
+
+ for (i = 0; i < available_fmt->audio_fmt_num; i++)
+ dev_dbg(scomp->dev, "%d: dma buffer size: %u\n", i,
+ available_fmt->dma_buffer_size[i]);
+
+ ret = sof_update_ipc_object(scomp, &node_type,
+ SOF_COPIER_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(node_type), 1);
+ if (ret) {
+ dev_err(scomp->dev, "parse dai node type failed %d\n", ret);
+ goto err;
+ }
+
+ ret = sof_update_ipc_object(scomp, ipc4_copier,
+ SOF_DAI_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(u32), 1);
+ if (ret) {
+ dev_err(scomp->dev, "parse dai copier node token failed %d\n", ret);
+ goto err;
+ }
+
+ dev_dbg(scomp->dev, "dai %s node_type %u dai_type %u dai_index %d\n", swidget->widget->name,
+ node_type, ipc4_copier->dai_type, ipc4_copier->dai_index);
+
+ ipc4_copier->data.gtw_cfg.node_id = SOF_IPC4_NODE_TYPE(node_type);
+
+ switch (ipc4_copier->dai_type) {
+ case SOF_DAI_INTEL_ALH:
+ {
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_ipc4_alh_configuration_blob *blob;
+ struct snd_sof_widget *w;
+
+ blob = kzalloc(sizeof(*blob), GFP_KERNEL);
+ if (!blob) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ list_for_each_entry(w, &sdev->widget_list, list) {
+ if (w->widget->sname &&
+ strcmp(w->widget->sname, swidget->widget->sname))
+ continue;
+
+ blob->alh_cfg.count++;
+ }
+
+ ipc4_copier->copier_config = (uint32_t *)blob;
+ ipc4_copier->data.gtw_cfg.config_length = sizeof(*blob) >> 2;
+ break;
+ }
+ case SOF_DAI_INTEL_SSP:
+ /* set SSP DAI index as the node_id */
+ ipc4_copier->data.gtw_cfg.node_id |=
+ SOF_IPC4_NODE_INDEX_INTEL_SSP(ipc4_copier->dai_index);
+ break;
+ case SOF_DAI_INTEL_DMIC:
+ /* set DMIC DAI index as the node_id */
+ ipc4_copier->data.gtw_cfg.node_id |=
+ SOF_IPC4_NODE_INDEX_INTEL_DMIC(ipc4_copier->dai_index);
+ break;
+ default:
+ ipc4_copier->gtw_attr = kzalloc(sizeof(*ipc4_copier->gtw_attr), GFP_KERNEL);
+ if (!ipc4_copier->gtw_attr) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ ipc4_copier->copier_config = (uint32_t *)ipc4_copier->gtw_attr;
+ ipc4_copier->data.gtw_cfg.config_length =
+ sizeof(struct sof_ipc4_gtw_attributes) >> 2;
+ break;
+ }
+
+ dai->scomp = scomp;
+ dai->private = ipc4_copier;
+
+ /* set up module info and message header */
+ ret = sof_ipc4_widget_setup_msg(swidget, &ipc4_copier->msg);
+ if (ret)
+ goto free_copier_config;
+
+ return 0;
+
+free_copier_config:
+ kfree(ipc4_copier->copier_config);
+err:
+ kfree(available_fmt->dma_buffer_size);
+free_available_fmt:
+ sof_ipc4_free_audio_fmt(available_fmt);
+free_copier:
+ kfree(ipc4_copier);
+ dai->private = NULL;
+ dai->scomp = NULL;
+ return ret;
+}
+
+static void sof_ipc4_widget_free_comp_dai(struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_available_audio_format *available_fmt;
+ struct snd_sof_dai *dai = swidget->private;
+ struct sof_ipc4_copier *ipc4_copier;
+
+ if (!dai)
+ return;
+
+ if (!dai->private) {
+ kfree(dai);
+ swidget->private = NULL;
+ return;
+ }
+
+ ipc4_copier = dai->private;
+ available_fmt = &ipc4_copier->available_fmt;
+
+ kfree(available_fmt->dma_buffer_size);
+ kfree(available_fmt->base_config);
+ kfree(available_fmt->out_audio_fmt);
+ if (ipc4_copier->dai_type != SOF_DAI_INTEL_SSP &&
+ ipc4_copier->dai_type != SOF_DAI_INTEL_DMIC)
+ kfree(ipc4_copier->copier_config);
+ kfree(dai->private);
+ kfree(dai);
+ swidget->private = NULL;
+}
+
+static int sof_ipc4_widget_setup_comp_pipeline(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct sof_ipc4_pipeline *pipeline;
+ int ret;
+
+ pipeline = kzalloc(sizeof(*pipeline), GFP_KERNEL);
+ if (!pipeline)
+ return -ENOMEM;
+
+ ret = sof_update_ipc_object(scomp, pipeline, SOF_SCHED_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*pipeline), 1);
+ if (ret) {
+ dev_err(scomp->dev, "parsing scheduler tokens failed\n");
+ goto err;
+ }
+
+ /* parse one set of pipeline tokens */
+ ret = sof_update_ipc_object(scomp, swidget, SOF_PIPELINE_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*swidget), 1);
+ if (ret) {
+ dev_err(scomp->dev, "parsing pipeline tokens failed\n");
+ goto err;
+ }
+
+ /* TODO: Get priority from topology */
+ pipeline->priority = 0;
+
+ dev_dbg(scomp->dev, "pipeline '%s': id %d pri %d lp mode %d\n",
+ swidget->widget->name, swidget->pipeline_id,
+ pipeline->priority, pipeline->lp_mode);
+
+ swidget->private = pipeline;
+
+ pipeline->msg.primary = SOF_IPC4_GLB_PIPE_PRIORITY(pipeline->priority);
+ pipeline->msg.primary |= SOF_IPC4_GLB_PIPE_INSTANCE_ID(swidget->pipeline_id);
+ pipeline->msg.primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_CREATE_PIPELINE);
+ pipeline->msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ pipeline->msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG);
+
+ pipeline->msg.extension = pipeline->lp_mode;
+ pipeline->state = SOF_IPC4_PIPE_UNINITIALIZED;
+
+ return 0;
+err:
+ kfree(pipeline);
+ return ret;
+}
+
+static int sof_ipc4_widget_setup_comp_pga(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_ipc4_fw_module *fw_module;
+ struct snd_sof_control *scontrol;
+ struct sof_ipc4_gain *gain;
+ int ret;
+
+ gain = kzalloc(sizeof(*gain), GFP_KERNEL);
+ if (!gain)
+ return -ENOMEM;
+
+ swidget->private = gain;
+
+ gain->data.channels = SOF_IPC4_GAIN_ALL_CHANNELS_MASK;
+ gain->data.init_val = SOF_IPC4_VOL_ZERO_DB;
+
+ /* The out_audio_fmt in topology is ignored as it is not required to be sent to the FW */
+ ret = sof_ipc4_get_audio_fmt(scomp, swidget, &gain->available_fmt, false);
+ if (ret)
+ goto err;
+
+ ret = sof_update_ipc_object(scomp, &gain->data, SOF_GAIN_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(gain->data), 1);
+ if (ret) {
+ dev_err(scomp->dev, "Parsing gain tokens failed\n");
+ goto err;
+ }
+
+ dev_dbg(scomp->dev,
+ "pga widget %s: ramp type: %d, ramp duration %d, initial gain value: %#x, cpc %d\n",
+ swidget->widget->name, gain->data.curve_type, gain->data.curve_duration,
+ gain->data.init_val, gain->base_config.cpc);
+
+ ret = sof_ipc4_widget_setup_msg(swidget, &gain->msg);
+ if (ret)
+ goto err;
+
+ fw_module = swidget->module_info;
+
+ /* update module ID for all kcontrols for this widget */
+ list_for_each_entry(scontrol, &sdev->kcontrol_list, list)
+ if (scontrol->comp_id == swidget->comp_id) {
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct sof_ipc4_msg *msg = &cdata->msg;
+
+ msg->primary |= fw_module->man4_module_entry.id;
+ }
+
+ return 0;
+err:
+ sof_ipc4_free_audio_fmt(&gain->available_fmt);
+ kfree(gain);
+ swidget->private = NULL;
+ return ret;
+}
+
+static void sof_ipc4_widget_free_comp_pga(struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_gain *gain = swidget->private;
+
+ if (!gain)
+ return;
+
+ sof_ipc4_free_audio_fmt(&gain->available_fmt);
+ kfree(swidget->private);
+ swidget->private = NULL;
+}
+
+static int sof_ipc4_widget_setup_comp_mixer(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct sof_ipc4_mixer *mixer;
+ int ret;
+
+ dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name);
+
+ mixer = kzalloc(sizeof(*mixer), GFP_KERNEL);
+ if (!mixer)
+ return -ENOMEM;
+
+ swidget->private = mixer;
+
+ /* The out_audio_fmt in topology is ignored as it is not required to be sent to the FW */
+ ret = sof_ipc4_get_audio_fmt(scomp, swidget, &mixer->available_fmt, false);
+ if (ret)
+ goto err;
+
+ ret = sof_ipc4_widget_setup_msg(swidget, &mixer->msg);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ sof_ipc4_free_audio_fmt(&mixer->available_fmt);
+ kfree(mixer);
+ swidget->private = NULL;
+ return ret;
+}
+
+static int sof_ipc4_widget_setup_comp_src(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct sof_ipc4_src *src;
+ int ret;
+
+ dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name);
+
+ src = kzalloc(sizeof(*src), GFP_KERNEL);
+ if (!src)
+ return -ENOMEM;
+
+ swidget->private = src;
+
+ /* The out_audio_fmt in topology is ignored as it is not required by SRC */
+ ret = sof_ipc4_get_audio_fmt(scomp, swidget, &src->available_fmt, false);
+ if (ret)
+ goto err;
+
+ ret = sof_update_ipc_object(scomp, src, SOF_SRC_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(src), 1);
+ if (ret) {
+ dev_err(scomp->dev, "Parsing SRC tokens failed\n");
+ goto err;
+ }
+
+ dev_dbg(scomp->dev, "SRC sink rate %d\n", src->sink_rate);
+
+ ret = sof_ipc4_widget_setup_msg(swidget, &src->msg);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ sof_ipc4_free_audio_fmt(&src->available_fmt);
+ kfree(src);
+ swidget->private = NULL;
+ return ret;
+}
+
+static void sof_ipc4_widget_free_comp_src(struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_src *src = swidget->private;
+
+ if (!src)
+ return;
+
+ sof_ipc4_free_audio_fmt(&src->available_fmt);
+ kfree(swidget->private);
+ swidget->private = NULL;
+}
+
+static void sof_ipc4_widget_free_comp_mixer(struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_mixer *mixer = swidget->private;
+
+ if (!mixer)
+ return;
+
+ sof_ipc4_free_audio_fmt(&mixer->available_fmt);
+ kfree(swidget->private);
+ swidget->private = NULL;
+}
+
+static void
+sof_ipc4_update_pipeline_mem_usage(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget,
+ struct sof_ipc4_base_module_cfg *base_config)
+{
+ struct sof_ipc4_fw_module *fw_module = swidget->module_info;
+ struct snd_sof_widget *pipe_widget;
+ struct sof_ipc4_pipeline *pipeline;
+ int task_mem, queue_mem;
+ int ibs, bss, total;
+
+ ibs = base_config->ibs;
+ bss = base_config->is_pages;
+
+ task_mem = SOF_IPC4_PIPELINE_OBJECT_SIZE;
+ task_mem += SOF_IPC4_MODULE_INSTANCE_LIST_ITEM_SIZE + bss;
+
+ if (fw_module->man4_module_entry.type & SOF_IPC4_MODULE_LL) {
+ task_mem += SOF_IPC4_FW_ROUNDUP(SOF_IPC4_LL_TASK_OBJECT_SIZE);
+ task_mem += SOF_IPC4_FW_MAX_QUEUE_COUNT * SOF_IPC4_MODULE_INSTANCE_LIST_ITEM_SIZE;
+ task_mem += SOF_IPC4_LL_TASK_LIST_ITEM_SIZE;
+ } else {
+ task_mem += SOF_IPC4_FW_ROUNDUP(SOF_IPC4_DP_TASK_OBJECT_SIZE);
+ task_mem += SOF_IPC4_DP_TASK_LIST_SIZE;
+ }
+
+ ibs = SOF_IPC4_FW_ROUNDUP(ibs);
+ queue_mem = SOF_IPC4_FW_MAX_QUEUE_COUNT * (SOF_IPC4_DATA_QUEUE_OBJECT_SIZE + ibs);
+
+ total = SOF_IPC4_FW_PAGE(task_mem + queue_mem);
+
+ pipe_widget = swidget->pipe_widget;
+ pipeline = pipe_widget->private;
+ pipeline->mem_usage += total;
+}
+
+static int sof_ipc4_widget_assign_instance_id(struct snd_sof_dev *sdev,
+ struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_fw_module *fw_module = swidget->module_info;
+ int max_instances = fw_module->man4_module_entry.instance_max_count;
+
+ swidget->instance_id = ida_alloc_max(&fw_module->m_ida, max_instances, GFP_KERNEL);
+ if (swidget->instance_id < 0) {
+ dev_err(sdev->dev, "failed to assign instance id for widget %s",
+ swidget->widget->name);
+ return swidget->instance_id;
+ }
+
+ return 0;
+}
+
+static int sof_ipc4_init_audio_fmt(struct snd_sof_dev *sdev,
+ struct snd_sof_widget *swidget,
+ struct sof_ipc4_base_module_cfg *base_config,
+ struct sof_ipc4_audio_format *out_format,
+ struct snd_pcm_hw_params *params,
+ struct sof_ipc4_available_audio_format *available_fmt,
+ size_t object_offset)
+{
+ void *ptr = available_fmt->ref_audio_fmt;
+ u32 valid_bits;
+ u32 channels;
+ u32 rate;
+ int sample_valid_bits;
+ int i;
+
+ if (!ptr) {
+ dev_err(sdev->dev, "no reference formats for %s\n", swidget->widget->name);
+ return -EINVAL;
+ }
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ sample_valid_bits = 16;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ sample_valid_bits = 24;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ sample_valid_bits = 32;
+ break;
+ default:
+ dev_err(sdev->dev, "invalid pcm frame format %d\n", params_format(params));
+ return -EINVAL;
+ }
+
+ if (!available_fmt->audio_fmt_num) {
+ dev_err(sdev->dev, "no formats available for %s\n", swidget->widget->name);
+ return -EINVAL;
+ }
+
+ /*
+ * Search supported audio formats to match rate, channels ,and
+ * sample_valid_bytes from runtime params
+ */
+ for (i = 0; i < available_fmt->audio_fmt_num; i++, ptr = (u8 *)ptr + object_offset) {
+ struct sof_ipc4_audio_format *fmt = ptr;
+
+ rate = fmt->sampling_frequency;
+ channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg);
+ valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg);
+ if (params_rate(params) == rate && params_channels(params) == channels &&
+ sample_valid_bits == valid_bits) {
+ dev_dbg(sdev->dev, "matching audio format index for %uHz, %ubit, %u channels: %d\n",
+ rate, valid_bits, channels, i);
+
+ /* copy ibs/obs and input format */
+ memcpy(base_config, &available_fmt->base_config[i],
+ sizeof(struct sof_ipc4_base_module_cfg));
+
+ /* copy output format */
+ if (out_format)
+ memcpy(out_format, &available_fmt->out_audio_fmt[i],
+ sizeof(struct sof_ipc4_audio_format));
+ break;
+ }
+ }
+
+ if (i == available_fmt->audio_fmt_num) {
+ dev_err(sdev->dev, "%s: Unsupported audio format: %uHz, %ubit, %u channels\n",
+ __func__, params_rate(params), sample_valid_bits, params_channels(params));
+ return -EINVAL;
+ }
+
+ dev_dbg(sdev->dev, "Init input audio formats for %s\n", swidget->widget->name);
+ sof_ipc4_dbg_audio_format(sdev->dev, &base_config->audio_fmt,
+ sizeof(*base_config), 1);
+ if (out_format) {
+ dev_dbg(sdev->dev, "Init output audio formats for %s\n", swidget->widget->name);
+ sof_ipc4_dbg_audio_format(sdev->dev, out_format,
+ sizeof(*out_format), 1);
+ }
+
+ /* Return the index of the matched format */
+ return i;
+}
+
+static void sof_ipc4_unprepare_copier_module(struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_copier *ipc4_copier = NULL;
+ struct snd_sof_widget *pipe_widget;
+ struct sof_ipc4_pipeline *pipeline;
+
+ /* reset pipeline memory usage */
+ pipe_widget = swidget->pipe_widget;
+ pipeline = pipe_widget->private;
+ pipeline->mem_usage = 0;
+
+ if (WIDGET_IS_AIF(swidget->id)) {
+ ipc4_copier = swidget->private;
+ } else if (WIDGET_IS_DAI(swidget->id)) {
+ struct snd_sof_dai *dai = swidget->private;
+
+ ipc4_copier = dai->private;
+ if (ipc4_copier->dai_type == SOF_DAI_INTEL_ALH) {
+ struct sof_ipc4_alh_configuration_blob *blob;
+ unsigned int group_id;
+
+ blob = (struct sof_ipc4_alh_configuration_blob *)ipc4_copier->copier_config;
+ if (blob->alh_cfg.count > 1) {
+ group_id = SOF_IPC4_NODE_INDEX(ipc4_copier->data.gtw_cfg.node_id) -
+ ALH_MULTI_GTW_BASE;
+ ida_free(&alh_group_ida, group_id);
+ }
+ }
+ }
+
+ if (ipc4_copier) {
+ kfree(ipc4_copier->ipc_config_data);
+ ipc4_copier->ipc_config_data = NULL;
+ ipc4_copier->ipc_config_size = 0;
+ }
+}
+
+#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_INTEL_NHLT)
+static int snd_sof_get_hw_config_params(struct snd_sof_dev *sdev, struct snd_sof_dai *dai,
+ int *sample_rate, int *channel_count, int *bit_depth)
+{
+ struct snd_soc_tplg_hw_config *hw_config;
+ struct snd_sof_dai_link *slink;
+ bool dai_link_found = false;
+ bool hw_cfg_found = false;
+ int i;
+
+ /* get current hw_config from link */
+ list_for_each_entry(slink, &sdev->dai_link_list, list) {
+ if (!strcmp(slink->link->name, dai->name)) {
+ dai_link_found = true;
+ break;
+ }
+ }
+
+ if (!dai_link_found) {
+ dev_err(sdev->dev, "%s: no DAI link found for DAI %s\n", __func__, dai->name);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < slink->num_hw_configs; i++) {
+ hw_config = &slink->hw_configs[i];
+ if (dai->current_config == le32_to_cpu(hw_config->id)) {
+ hw_cfg_found = true;
+ break;
+ }
+ }
+
+ if (!hw_cfg_found) {
+ dev_err(sdev->dev, "%s: no matching hw_config found for DAI %s\n", __func__,
+ dai->name);
+ return -EINVAL;
+ }
+
+ *bit_depth = le32_to_cpu(hw_config->tdm_slot_width);
+ *channel_count = le32_to_cpu(hw_config->tdm_slots);
+ *sample_rate = le32_to_cpu(hw_config->fsync_rate);
+
+ dev_dbg(sdev->dev, "sample rate: %d sample width: %d channels: %d\n",
+ *sample_rate, *bit_depth, *channel_count);
+
+ return 0;
+}
+
+static int snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai,
+ struct snd_pcm_hw_params *params, u32 dai_index,
+ u32 linktype, u8 dir, u32 **dst, u32 *len)
+{
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ struct nhlt_specific_cfg *cfg;
+ int sample_rate, channel_count;
+ int bit_depth, ret;
+ u32 nhlt_type;
+
+ /* convert to NHLT type */
+ switch (linktype) {
+ case SOF_DAI_INTEL_DMIC:
+ nhlt_type = NHLT_LINK_DMIC;
+ bit_depth = params_width(params);
+ channel_count = params_channels(params);
+ sample_rate = params_rate(params);
+ break;
+ case SOF_DAI_INTEL_SSP:
+ nhlt_type = NHLT_LINK_SSP;
+ ret = snd_sof_get_hw_config_params(sdev, dai, &sample_rate, &channel_count,
+ &bit_depth);
+ if (ret < 0)
+ return ret;
+ break;
+ default:
+ return 0;
+ }
+
+ dev_dbg(sdev->dev, "dai index %d nhlt type %d direction %d\n",
+ dai_index, nhlt_type, dir);
+
+ /* find NHLT blob with matching params */
+ cfg = intel_nhlt_get_endpoint_blob(sdev->dev, ipc4_data->nhlt, dai_index, nhlt_type,
+ bit_depth, bit_depth, channel_count, sample_rate,
+ dir, 0);
+
+ if (!cfg) {
+ dev_err(sdev->dev,
+ "no matching blob for sample rate: %d sample width: %d channels: %d\n",
+ sample_rate, bit_depth, channel_count);
+ return -EINVAL;
+ }
+
+ /* config length should be in dwords */
+ *len = cfg->size >> 2;
+ *dst = (u32 *)cfg->caps;
+
+ return 0;
+}
+#else
+static int snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai,
+ struct snd_pcm_hw_params *params, u32 dai_index,
+ u32 linktype, u8 dir, u32 **dst, u32 *len)
+{
+ return 0;
+}
+#endif
+
+static int
+sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget,
+ struct snd_pcm_hw_params *fe_params,
+ struct snd_sof_platform_stream_params *platform_params,
+ struct snd_pcm_hw_params *pipeline_params, int dir)
+{
+ struct sof_ipc4_available_audio_format *available_fmt;
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_ipc4_copier_data *copier_data;
+ struct snd_pcm_hw_params *ref_params;
+ struct sof_ipc4_copier *ipc4_copier;
+ struct snd_sof_dai *dai;
+ struct snd_mask *fmt;
+ int out_sample_valid_bits;
+ size_t ref_audio_fmt_size;
+ void **ipc_config_data;
+ int *ipc_config_size;
+ u32 **data;
+ int ipc_size, ret;
+
+ dev_dbg(sdev->dev, "copier %s, type %d", swidget->widget->name, swidget->id);
+
+ switch (swidget->id) {
+ case snd_soc_dapm_aif_in:
+ case snd_soc_dapm_aif_out:
+ {
+ struct sof_ipc4_gtw_attributes *gtw_attr;
+ struct snd_sof_widget *pipe_widget;
+ struct sof_ipc4_pipeline *pipeline;
+
+ pipe_widget = swidget->pipe_widget;
+ pipeline = pipe_widget->private;
+ ipc4_copier = (struct sof_ipc4_copier *)swidget->private;
+ gtw_attr = ipc4_copier->gtw_attr;
+ copier_data = &ipc4_copier->data;
+ available_fmt = &ipc4_copier->available_fmt;
+
+ /*
+ * base_config->audio_fmt and out_audio_fmt represent the input and output audio
+ * formats. Use the input format as the reference to match pcm params for playback
+ * and the output format as reference for capture.
+ */
+ if (dir == SNDRV_PCM_STREAM_PLAYBACK) {
+ available_fmt->ref_audio_fmt = &available_fmt->base_config->audio_fmt;
+ ref_audio_fmt_size = sizeof(struct sof_ipc4_base_module_cfg);
+ } else {
+ available_fmt->ref_audio_fmt = available_fmt->out_audio_fmt;
+ ref_audio_fmt_size = sizeof(struct sof_ipc4_audio_format);
+ }
+ copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK;
+ copier_data->gtw_cfg.node_id |=
+ SOF_IPC4_NODE_INDEX(platform_params->stream_tag - 1);
+
+ /* set gateway attributes */
+ gtw_attr->lp_buffer_alloc = pipeline->lp_mode;
+ ref_params = fe_params;
+ break;
+ }
+ case snd_soc_dapm_dai_in:
+ case snd_soc_dapm_dai_out:
+ {
+ dai = swidget->private;
+
+ ipc4_copier = (struct sof_ipc4_copier *)dai->private;
+ copier_data = &ipc4_copier->data;
+ available_fmt = &ipc4_copier->available_fmt;
+ if (dir == SNDRV_PCM_STREAM_CAPTURE) {
+ available_fmt->ref_audio_fmt = available_fmt->out_audio_fmt;
+ ref_audio_fmt_size = sizeof(struct sof_ipc4_audio_format);
+
+ /*
+ * modify the input params for the dai copier as it only supports
+ * 32-bit always
+ */
+ fmt = hw_param_mask(pipeline_params, SNDRV_PCM_HW_PARAM_FORMAT);
+ snd_mask_none(fmt);
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE);
+ } else {
+ available_fmt->ref_audio_fmt = &available_fmt->base_config->audio_fmt;
+ ref_audio_fmt_size = sizeof(struct sof_ipc4_base_module_cfg);
+ }
+
+ ref_params = pipeline_params;
+
+ ret = snd_sof_get_nhlt_endpoint_data(sdev, dai, fe_params, ipc4_copier->dai_index,
+ ipc4_copier->dai_type, dir,
+ &ipc4_copier->copier_config,
+ &copier_data->gtw_cfg.config_length);
+ if (ret < 0)
+ return ret;
+
+ break;
+ }
+ default:
+ dev_err(sdev->dev, "unsupported type %d for copier %s",
+ swidget->id, swidget->widget->name);
+ return -EINVAL;
+ }
+
+ /* set input and output audio formats */
+ ret = sof_ipc4_init_audio_fmt(sdev, swidget, &copier_data->base_config,
+ &copier_data->out_format, ref_params,
+ available_fmt, ref_audio_fmt_size);
+ if (ret < 0)
+ return ret;
+
+ switch (swidget->id) {
+ case snd_soc_dapm_dai_in:
+ case snd_soc_dapm_dai_out:
+ {
+ /*
+ * Only SOF_DAI_INTEL_ALH needs copier_data to set blob.
+ * That's why only ALH dai's blob is set after sof_ipc4_init_audio_fmt
+ */
+ if (ipc4_copier->dai_type == SOF_DAI_INTEL_ALH) {
+ struct sof_ipc4_alh_configuration_blob *blob;
+ struct sof_ipc4_copier_data *alh_data;
+ struct sof_ipc4_copier *alh_copier;
+ struct snd_sof_widget *w;
+ u32 ch_mask = 0;
+ u32 ch_map;
+ int i;
+
+ blob = (struct sof_ipc4_alh_configuration_blob *)ipc4_copier->copier_config;
+
+ blob->gw_attr.lp_buffer_alloc = 0;
+
+ /* Get channel_mask from ch_map */
+ ch_map = copier_data->base_config.audio_fmt.ch_map;
+ for (i = 0; ch_map; i++) {
+ if ((ch_map & 0xf) != 0xf)
+ ch_mask |= BIT(i);
+ ch_map >>= 4;
+ }
+
+ /*
+ * Set each gtw_cfg.node_id to blob->alh_cfg.mapping[]
+ * for all widgets with the same stream name
+ */
+ i = 0;
+ list_for_each_entry(w, &sdev->widget_list, list) {
+ if (w->widget->sname &&
+ strcmp(w->widget->sname, swidget->widget->sname))
+ continue;
+
+ dai = w->private;
+ alh_copier = (struct sof_ipc4_copier *)dai->private;
+ alh_data = &alh_copier->data;
+ blob->alh_cfg.mapping[i].alh_id = alh_data->gtw_cfg.node_id;
+ blob->alh_cfg.mapping[i].channel_mask = ch_mask;
+ i++;
+ }
+ if (blob->alh_cfg.count > 1) {
+ int group_id;
+
+ group_id = ida_alloc_max(&alh_group_ida, ALH_MULTI_GTW_COUNT,
+ GFP_KERNEL);
+
+ if (group_id < 0)
+ return group_id;
+
+ /* add multi-gateway base */
+ group_id += ALH_MULTI_GTW_BASE;
+ copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK;
+ copier_data->gtw_cfg.node_id |= SOF_IPC4_NODE_INDEX(group_id);
+ }
+ }
+ }
+ }
+
+ /* modify the input params for the next widget */
+ fmt = hw_param_mask(pipeline_params, SNDRV_PCM_HW_PARAM_FORMAT);
+ out_sample_valid_bits =
+ SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(copier_data->out_format.fmt_cfg);
+ snd_mask_none(fmt);
+ switch (out_sample_valid_bits) {
+ case 16:
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
+ break;
+ case 24:
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
+ break;
+ case 32:
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE);
+ break;
+ default:
+ dev_err(sdev->dev, "invalid sample frame format %d\n",
+ params_format(pipeline_params));
+ return -EINVAL;
+ }
+
+ /* set the gateway dma_buffer_size using the matched ID returned above */
+ copier_data->gtw_cfg.dma_buffer_size = available_fmt->dma_buffer_size[ret];
+
+ data = &ipc4_copier->copier_config;
+ ipc_config_size = &ipc4_copier->ipc_config_size;
+ ipc_config_data = &ipc4_copier->ipc_config_data;
+
+ /* config_length is DWORD based */
+ ipc_size = sizeof(*copier_data) + copier_data->gtw_cfg.config_length * 4;
+
+ dev_dbg(sdev->dev, "copier %s, IPC size is %d", swidget->widget->name, ipc_size);
+
+ *ipc_config_data = kzalloc(ipc_size, GFP_KERNEL);
+ if (!*ipc_config_data)
+ return -ENOMEM;
+
+ *ipc_config_size = ipc_size;
+
+ /* copy IPC data */
+ memcpy(*ipc_config_data, (void *)copier_data, sizeof(*copier_data));
+ if (copier_data->gtw_cfg.config_length)
+ memcpy(*ipc_config_data + sizeof(*copier_data),
+ *data, copier_data->gtw_cfg.config_length * 4);
+
+ /* update pipeline memory usage */
+ sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &copier_data->base_config);
+
+ return 0;
+}
+
+static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget,
+ struct snd_pcm_hw_params *fe_params,
+ struct snd_sof_platform_stream_params *platform_params,
+ struct snd_pcm_hw_params *pipeline_params, int dir)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_ipc4_gain *gain = swidget->private;
+ int ret;
+
+ gain->available_fmt.ref_audio_fmt = &gain->available_fmt.base_config->audio_fmt;
+
+ /* output format is not required to be sent to the FW for gain */
+ ret = sof_ipc4_init_audio_fmt(sdev, swidget, &gain->base_config,
+ NULL, pipeline_params, &gain->available_fmt,
+ sizeof(gain->base_config));
+ if (ret < 0)
+ return ret;
+
+ /* update pipeline memory usage */
+ sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &gain->base_config);
+
+ return 0;
+}
+
+static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget,
+ struct snd_pcm_hw_params *fe_params,
+ struct snd_sof_platform_stream_params *platform_params,
+ struct snd_pcm_hw_params *pipeline_params, int dir)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_ipc4_mixer *mixer = swidget->private;
+ int ret;
+
+ /* only 32bit is supported by mixer */
+ mixer->available_fmt.ref_audio_fmt = &mixer->available_fmt.base_config->audio_fmt;
+
+ /* output format is not required to be sent to the FW for mixer */
+ ret = sof_ipc4_init_audio_fmt(sdev, swidget, &mixer->base_config,
+ NULL, pipeline_params, &mixer->available_fmt,
+ sizeof(mixer->base_config));
+ if (ret < 0)
+ return ret;
+
+ /* update pipeline memory usage */
+ sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &mixer->base_config);
+
+ return 0;
+}
+
+static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget,
+ struct snd_pcm_hw_params *fe_params,
+ struct snd_sof_platform_stream_params *platform_params,
+ struct snd_pcm_hw_params *pipeline_params, int dir)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_ipc4_src *src = swidget->private;
+ struct snd_interval *rate;
+ int ret;
+
+ src->available_fmt.ref_audio_fmt = &src->available_fmt.base_config->audio_fmt;
+
+ /* output format is not required to be sent to the FW for SRC */
+ ret = sof_ipc4_init_audio_fmt(sdev, swidget, &src->base_config,
+ NULL, pipeline_params, &src->available_fmt,
+ sizeof(src->base_config));
+ if (ret < 0)
+ return ret;
+
+ /* update pipeline memory usage */
+ sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &src->base_config);
+
+ /* update pipeline_params for sink widgets */
+ rate = hw_param_interval(pipeline_params, SNDRV_PCM_HW_PARAM_RATE);
+ rate->min = src->sink_rate;
+ rate->max = rate->min;
+
+ return 0;
+}
+
+static int sof_ipc4_control_load_volume(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol)
+{
+ struct sof_ipc4_control_data *control_data;
+ struct sof_ipc4_msg *msg;
+ int i;
+
+ scontrol->size = struct_size(control_data, chanv, scontrol->num_channels);
+
+ /* scontrol->ipc_control_data will be freed in sof_control_unload */
+ scontrol->ipc_control_data = kzalloc(scontrol->size, GFP_KERNEL);
+ if (!scontrol->ipc_control_data)
+ return -ENOMEM;
+
+ control_data = scontrol->ipc_control_data;
+ control_data->index = scontrol->index;
+
+ msg = &control_data->msg;
+ msg->primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_LARGE_CONFIG_SET);
+ msg->primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg->primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+
+ msg->extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_GAIN_PARAM_ID);
+
+ /* set default volume values to 0dB in control */
+ for (i = 0; i < scontrol->num_channels; i++) {
+ control_data->chanv[i].channel = i;
+ control_data->chanv[i].value = SOF_IPC4_VOL_ZERO_DB;
+ }
+
+ return 0;
+}
+
+static int sof_ipc4_control_setup(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol)
+{
+ switch (scontrol->info_type) {
+ case SND_SOC_TPLG_CTL_VOLSW:
+ case SND_SOC_TPLG_CTL_VOLSW_SX:
+ case SND_SOC_TPLG_CTL_VOLSW_XR_SX:
+ return sof_ipc4_control_load_volume(sdev, scontrol);
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
+{
+ struct snd_sof_widget *pipe_widget = swidget->pipe_widget;
+ struct sof_ipc4_pipeline *pipeline;
+ struct sof_ipc4_msg *msg;
+ void *ipc_data = NULL;
+ u32 ipc_size = 0;
+ int ret;
+
+ switch (swidget->id) {
+ case snd_soc_dapm_scheduler:
+ pipeline = swidget->private;
+
+ dev_dbg(sdev->dev, "pipeline: %d memory pages: %d\n", swidget->pipeline_id,
+ pipeline->mem_usage);
+
+ msg = &pipeline->msg;
+ msg->primary |= pipeline->mem_usage;
+ break;
+ case snd_soc_dapm_aif_in:
+ case snd_soc_dapm_aif_out:
+ {
+ struct sof_ipc4_copier *ipc4_copier = swidget->private;
+
+ ipc_size = ipc4_copier->ipc_config_size;
+ ipc_data = ipc4_copier->ipc_config_data;
+
+ msg = &ipc4_copier->msg;
+ break;
+ }
+ case snd_soc_dapm_dai_in:
+ case snd_soc_dapm_dai_out:
+ {
+ struct snd_sof_dai *dai = swidget->private;
+ struct sof_ipc4_copier *ipc4_copier = dai->private;
+
+ ipc_size = ipc4_copier->ipc_config_size;
+ ipc_data = ipc4_copier->ipc_config_data;
+
+ msg = &ipc4_copier->msg;
+ break;
+ }
+ case snd_soc_dapm_pga:
+ {
+ struct sof_ipc4_gain *gain = swidget->private;
+
+ ipc_size = sizeof(struct sof_ipc4_base_module_cfg) +
+ sizeof(struct sof_ipc4_gain_data);
+ ipc_data = gain;
+
+ msg = &gain->msg;
+ break;
+ }
+ case snd_soc_dapm_mixer:
+ {
+ struct sof_ipc4_mixer *mixer = swidget->private;
+
+ ipc_size = sizeof(mixer->base_config);
+ ipc_data = &mixer->base_config;
+
+ msg = &mixer->msg;
+ break;
+ }
+ case snd_soc_dapm_src:
+ {
+ struct sof_ipc4_src *src = swidget->private;
+
+ ipc_size = sizeof(struct sof_ipc4_base_module_cfg) + sizeof(src->sink_rate);
+ ipc_data = src;
+
+ msg = &src->msg;
+ break;
+ }
+ default:
+ dev_err(sdev->dev, "widget type %d not supported", swidget->id);
+ return -EINVAL;
+ }
+
+ if (swidget->id != snd_soc_dapm_scheduler) {
+ ret = sof_ipc4_widget_assign_instance_id(sdev, swidget);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to assign instance id for %s\n",
+ swidget->widget->name);
+ return ret;
+ }
+ pipeline = pipe_widget->private;
+ msg->primary &= ~SOF_IPC4_MOD_INSTANCE_MASK;
+ msg->primary |= SOF_IPC4_MOD_INSTANCE(swidget->instance_id);
+
+ msg->extension &= ~SOF_IPC4_MOD_EXT_PARAM_SIZE_MASK;
+ msg->extension |= ipc_size >> 2;
+ }
+ dev_dbg(sdev->dev, "Create widget %s instance %d - pipe %d - core %d\n",
+ swidget->widget->name, swidget->instance_id, swidget->pipeline_id, swidget->core);
+
+ msg->data_size = ipc_size;
+ msg->data_ptr = ipc_data;
+
+ ret = sof_ipc_tx_message(sdev->ipc, msg, ipc_size, NULL, 0);
+ if (ret < 0)
+ dev_err(sdev->dev, "failed to create module %s\n", swidget->widget->name);
+
+ return ret;
+}
+
+static int sof_ipc4_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_fw_module *fw_module = swidget->module_info;
+ int ret = 0;
+
+ /* freeing a pipeline frees all the widgets associated with it */
+ if (swidget->id == snd_soc_dapm_scheduler) {
+ struct sof_ipc4_pipeline *pipeline = swidget->private;
+ struct sof_ipc4_msg msg = {{ 0 }};
+ u32 header;
+
+ header = SOF_IPC4_GLB_PIPE_INSTANCE_ID(swidget->pipeline_id);
+ header |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_DELETE_PIPELINE);
+ header |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ header |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG);
+
+ msg.primary = header;
+
+ ret = sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0);
+ if (ret < 0)
+ dev_err(sdev->dev, "failed to free pipeline widget %s\n",
+ swidget->widget->name);
+
+ pipeline->mem_usage = 0;
+ pipeline->state = SOF_IPC4_PIPE_UNINITIALIZED;
+ } else {
+ ida_free(&fw_module->m_ida, swidget->instance_id);
+ }
+
+ return ret;
+}
+
+static int sof_ipc4_route_setup(struct snd_sof_dev *sdev, struct snd_sof_route *sroute)
+{
+ struct snd_sof_widget *src_widget = sroute->src_widget;
+ struct snd_sof_widget *sink_widget = sroute->sink_widget;
+ struct sof_ipc4_fw_module *src_fw_module = src_widget->module_info;
+ struct sof_ipc4_fw_module *sink_fw_module = sink_widget->module_info;
+ struct sof_ipc4_msg msg = {{ 0 }};
+ u32 header, extension;
+ int src_queue = 0;
+ int dst_queue = 0;
+ int ret;
+
+ dev_dbg(sdev->dev, "bind %s -> %s\n",
+ src_widget->widget->name, sink_widget->widget->name);
+
+ header = src_fw_module->man4_module_entry.id;
+ header |= SOF_IPC4_MOD_INSTANCE(src_widget->instance_id);
+ header |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_BIND);
+ header |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ header |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+
+ extension = sink_fw_module->man4_module_entry.id;
+ extension |= SOF_IPC4_MOD_EXT_DST_MOD_INSTANCE(sink_widget->instance_id);
+ extension |= SOF_IPC4_MOD_EXT_DST_MOD_QUEUE_ID(dst_queue);
+ extension |= SOF_IPC4_MOD_EXT_SRC_MOD_QUEUE_ID(src_queue);
+
+ msg.primary = header;
+ msg.extension = extension;
+
+ ret = sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0);
+ if (ret < 0)
+ dev_err(sdev->dev, "%s: failed to bind modules %s -> %s\n",
+ __func__, src_widget->widget->name, sink_widget->widget->name);
+
+ return ret;
+}
+
+static int sof_ipc4_route_free(struct snd_sof_dev *sdev, struct snd_sof_route *sroute)
+{
+ struct snd_sof_widget *src_widget = sroute->src_widget;
+ struct snd_sof_widget *sink_widget = sroute->sink_widget;
+ struct sof_ipc4_fw_module *src_fw_module = src_widget->module_info;
+ struct sof_ipc4_fw_module *sink_fw_module = sink_widget->module_info;
+ struct sof_ipc4_msg msg = {{ 0 }};
+ u32 header, extension;
+ int src_queue = 0;
+ int dst_queue = 0;
+ int ret;
+
+ dev_dbg(sdev->dev, "unbind modules %s -> %s\n",
+ src_widget->widget->name, sink_widget->widget->name);
+
+ header = src_fw_module->man4_module_entry.id;
+ header |= SOF_IPC4_MOD_INSTANCE(src_widget->instance_id);
+ header |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_UNBIND);
+ header |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ header |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+
+ extension = sink_fw_module->man4_module_entry.id;
+ extension |= SOF_IPC4_MOD_EXT_DST_MOD_INSTANCE(sink_widget->instance_id);
+ extension |= SOF_IPC4_MOD_EXT_DST_MOD_QUEUE_ID(dst_queue);
+ extension |= SOF_IPC4_MOD_EXT_SRC_MOD_QUEUE_ID(src_queue);
+
+ msg.primary = header;
+ msg.extension = extension;
+
+ ret = sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0);
+ if (ret < 0)
+ dev_err(sdev->dev, "failed to unbind modules %s -> %s\n",
+ src_widget->widget->name, sink_widget->widget->name);
+
+ return ret;
+}
+
+static int sof_ipc4_dai_config(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget,
+ unsigned int flags, struct snd_sof_dai_config_data *data)
+{
+ struct snd_sof_widget *pipe_widget = swidget->pipe_widget;
+ struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+ struct snd_sof_dai *dai = swidget->private;
+ struct sof_ipc4_gtw_attributes *gtw_attr;
+ struct sof_ipc4_copier_data *copier_data;
+ struct sof_ipc4_copier *ipc4_copier;
+
+ if (!dai || !dai->private) {
+ dev_err(sdev->dev, "Invalid DAI or DAI private data for %s\n",
+ swidget->widget->name);
+ return -EINVAL;
+ }
+
+ ipc4_copier = (struct sof_ipc4_copier *)dai->private;
+ copier_data = &ipc4_copier->data;
+
+ if (!data)
+ return 0;
+
+ switch (ipc4_copier->dai_type) {
+ case SOF_DAI_INTEL_HDA:
+ gtw_attr = ipc4_copier->gtw_attr;
+ gtw_attr->lp_buffer_alloc = pipeline->lp_mode;
+ fallthrough;
+ case SOF_DAI_INTEL_ALH:
+ copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK;
+ copier_data->gtw_cfg.node_id |= SOF_IPC4_NODE_INDEX(data->dai_data);
+ break;
+ case SOF_DAI_INTEL_DMIC:
+ case SOF_DAI_INTEL_SSP:
+ /* nothing to do for SSP/DMIC */
+ break;
+ default:
+ dev_err(sdev->dev, "%s: unsupported dai type %d\n", __func__,
+ ipc4_copier->dai_type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sof_ipc4_parse_manifest(struct snd_soc_component *scomp, int index,
+ struct snd_soc_tplg_manifest *man)
+{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ struct sof_manifest_tlv *manifest_tlv;
+ struct sof_manifest *manifest;
+ u32 size = le32_to_cpu(man->priv.size);
+ u8 *man_ptr = man->priv.data;
+ u32 len_check;
+ int i;
+
+ if (!size || size < SOF_IPC4_TPLG_ABI_SIZE) {
+ dev_err(scomp->dev, "%s: Invalid topology ABI size: %u\n",
+ __func__, size);
+ return -EINVAL;
+ }
+
+ manifest = (struct sof_manifest *)man_ptr;
+
+ dev_info(scomp->dev,
+ "Topology: ABI %d:%d:%d Kernel ABI %u:%u:%u\n",
+ le16_to_cpu(manifest->abi_major), le16_to_cpu(manifest->abi_minor),
+ le16_to_cpu(manifest->abi_patch),
+ SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH);
+
+ /* TODO: Add ABI compatibility check */
+
+ /* no more data after the ABI version */
+ if (size <= SOF_IPC4_TPLG_ABI_SIZE)
+ return 0;
+
+ manifest_tlv = manifest->items;
+ len_check = sizeof(struct sof_manifest);
+ for (i = 0; i < le16_to_cpu(manifest->count); i++) {
+ len_check += sizeof(struct sof_manifest_tlv) + le32_to_cpu(manifest_tlv->size);
+ if (len_check > size)
+ return -EINVAL;
+
+ switch (le32_to_cpu(manifest_tlv->type)) {
+ case SOF_MANIFEST_DATA_TYPE_NHLT:
+ /* no NHLT in BIOS, so use the one from topology manifest */
+ if (ipc4_data->nhlt)
+ break;
+ ipc4_data->nhlt = devm_kmemdup(sdev->dev, manifest_tlv->data,
+ le32_to_cpu(manifest_tlv->size), GFP_KERNEL);
+ if (!ipc4_data->nhlt)
+ return -ENOMEM;
+ break;
+ default:
+ dev_warn(scomp->dev, "Skipping unknown manifest data type %d\n",
+ manifest_tlv->type);
+ break;
+ }
+ man_ptr += sizeof(struct sof_manifest_tlv) + le32_to_cpu(manifest_tlv->size);
+ manifest_tlv = (struct sof_manifest_tlv *)man_ptr;
+ }
+
+ return 0;
+}
+
+static int sof_ipc4_dai_get_clk(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, int clk_type)
+{
+ struct sof_ipc4_copier *ipc4_copier = dai->private;
+ struct snd_soc_tplg_hw_config *hw_config;
+ struct snd_sof_dai_link *slink;
+ bool dai_link_found = false;
+ bool hw_cfg_found = false;
+ int i;
+
+ if (!ipc4_copier)
+ return 0;
+
+ list_for_each_entry(slink, &sdev->dai_link_list, list) {
+ if (!strcmp(slink->link->name, dai->name)) {
+ dai_link_found = true;
+ break;
+ }
+ }
+
+ if (!dai_link_found) {
+ dev_err(sdev->dev, "no DAI link found for DAI %s\n", dai->name);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < slink->num_hw_configs; i++) {
+ hw_config = &slink->hw_configs[i];
+ if (dai->current_config == le32_to_cpu(hw_config->id)) {
+ hw_cfg_found = true;
+ break;
+ }
+ }
+
+ if (!hw_cfg_found) {
+ dev_err(sdev->dev, "no matching hw_config found for DAI %s\n", dai->name);
+ return -EINVAL;
+ }
+
+ switch (ipc4_copier->dai_type) {
+ case SOF_DAI_INTEL_SSP:
+ switch (clk_type) {
+ case SOF_DAI_CLK_INTEL_SSP_MCLK:
+ return le32_to_cpu(hw_config->mclk_rate);
+ case SOF_DAI_CLK_INTEL_SSP_BCLK:
+ return le32_to_cpu(hw_config->bclk_rate);
+ default:
+ dev_err(sdev->dev, "Invalid clk type for SSP %d\n", clk_type);
+ break;
+ }
+ break;
+ default:
+ dev_err(sdev->dev, "DAI type %d not supported yet!\n", ipc4_copier->dai_type);
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static enum sof_tokens host_token_list[] = {
+ SOF_COMP_TOKENS,
+ SOF_AUDIO_FMT_NUM_TOKENS,
+ SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS,
+ SOF_IN_AUDIO_FORMAT_TOKENS,
+ SOF_OUT_AUDIO_FORMAT_TOKENS,
+ SOF_COPIER_GATEWAY_CFG_TOKENS,
+ SOF_COPIER_TOKENS,
+ SOF_COMP_EXT_TOKENS,
+};
+
+static enum sof_tokens pipeline_token_list[] = {
+ SOF_SCHED_TOKENS,
+ SOF_PIPELINE_TOKENS,
+};
+
+static enum sof_tokens dai_token_list[] = {
+ SOF_COMP_TOKENS,
+ SOF_AUDIO_FMT_NUM_TOKENS,
+ SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS,
+ SOF_IN_AUDIO_FORMAT_TOKENS,
+ SOF_OUT_AUDIO_FORMAT_TOKENS,
+ SOF_COPIER_GATEWAY_CFG_TOKENS,
+ SOF_COPIER_TOKENS,
+ SOF_DAI_TOKENS,
+ SOF_COMP_EXT_TOKENS,
+};
+
+static enum sof_tokens pga_token_list[] = {
+ SOF_COMP_TOKENS,
+ SOF_GAIN_TOKENS,
+ SOF_AUDIO_FMT_NUM_TOKENS,
+ SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS,
+ SOF_IN_AUDIO_FORMAT_TOKENS,
+ SOF_COMP_EXT_TOKENS,
+};
+
+static enum sof_tokens mixer_token_list[] = {
+ SOF_COMP_TOKENS,
+ SOF_AUDIO_FMT_NUM_TOKENS,
+ SOF_IN_AUDIO_FORMAT_TOKENS,
+ SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS,
+ SOF_COMP_EXT_TOKENS,
+};
+
+static enum sof_tokens src_token_list[] = {
+ SOF_COMP_TOKENS,
+ SOF_SRC_TOKENS,
+ SOF_AUDIO_FMT_NUM_TOKENS,
+ SOF_IN_AUDIO_FORMAT_TOKENS,
+ SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS,
+ SOF_COMP_EXT_TOKENS,
+};
+
+static const struct sof_ipc_tplg_widget_ops tplg_ipc4_widget_ops[SND_SOC_DAPM_TYPE_COUNT] = {
+ [snd_soc_dapm_aif_in] = {sof_ipc4_widget_setup_pcm, sof_ipc4_widget_free_comp_pcm,
+ host_token_list, ARRAY_SIZE(host_token_list), NULL,
+ sof_ipc4_prepare_copier_module,
+ sof_ipc4_unprepare_copier_module},
+ [snd_soc_dapm_aif_out] = {sof_ipc4_widget_setup_pcm, sof_ipc4_widget_free_comp_pcm,
+ host_token_list, ARRAY_SIZE(host_token_list), NULL,
+ sof_ipc4_prepare_copier_module,
+ sof_ipc4_unprepare_copier_module},
+ [snd_soc_dapm_dai_in] = {sof_ipc4_widget_setup_comp_dai, sof_ipc4_widget_free_comp_dai,
+ dai_token_list, ARRAY_SIZE(dai_token_list), NULL,
+ sof_ipc4_prepare_copier_module,
+ sof_ipc4_unprepare_copier_module},
+ [snd_soc_dapm_dai_out] = {sof_ipc4_widget_setup_comp_dai, sof_ipc4_widget_free_comp_dai,
+ dai_token_list, ARRAY_SIZE(dai_token_list), NULL,
+ sof_ipc4_prepare_copier_module,
+ sof_ipc4_unprepare_copier_module},
+ [snd_soc_dapm_scheduler] = {sof_ipc4_widget_setup_comp_pipeline, sof_ipc4_widget_free_comp,
+ pipeline_token_list, ARRAY_SIZE(pipeline_token_list), NULL,
+ NULL, NULL},
+ [snd_soc_dapm_pga] = {sof_ipc4_widget_setup_comp_pga, sof_ipc4_widget_free_comp_pga,
+ pga_token_list, ARRAY_SIZE(pga_token_list), NULL,
+ sof_ipc4_prepare_gain_module,
+ NULL},
+ [snd_soc_dapm_mixer] = {sof_ipc4_widget_setup_comp_mixer, sof_ipc4_widget_free_comp_mixer,
+ mixer_token_list, ARRAY_SIZE(mixer_token_list),
+ NULL, sof_ipc4_prepare_mixer_module,
+ NULL},
+ [snd_soc_dapm_src] = {sof_ipc4_widget_setup_comp_src, sof_ipc4_widget_free_comp_src,
+ src_token_list, ARRAY_SIZE(src_token_list),
+ NULL, sof_ipc4_prepare_src_module,
+ NULL},
+};
+
+const struct sof_ipc_tplg_ops ipc4_tplg_ops = {
+ .widget = tplg_ipc4_widget_ops,
+ .token_list = ipc4_token_list,
+ .control_setup = sof_ipc4_control_setup,
+ .control = &tplg_ipc4_control_ops,
+ .widget_setup = sof_ipc4_widget_setup,
+ .widget_free = sof_ipc4_widget_free,
+ .route_setup = sof_ipc4_route_setup,
+ .route_free = sof_ipc4_route_free,
+ .dai_config = sof_ipc4_dai_config,
+ .parse_manifest = sof_ipc4_parse_manifest,
+ .dai_get_clk = sof_ipc4_dai_get_clk,
+};
diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h
new file mode 100644
index 000000000000..0aa87a8add5d
--- /dev/null
+++ b/sound/soc/sof/ipc4-topology.h
@@ -0,0 +1,270 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * Copyright(c) 2022 Intel Corporation. All rights reserved.
+ */
+
+#ifndef __INCLUDE_SOUND_SOF_IPC4_TOPOLOGY_H__
+#define __INCLUDE_SOUND_SOF_IPC4_TOPOLOGY_H__
+
+#include <sound/sof/ipc4/header.h>
+
+#define SOF_IPC4_FW_PAGE_SIZE BIT(12)
+#define SOF_IPC4_FW_PAGE(x) ((((x) + BIT(12) - 1) & ~(BIT(12) - 1)) >> 12)
+#define SOF_IPC4_FW_ROUNDUP(x) (((x) + BIT(6) - 1) & (~(BIT(6) - 1)))
+
+#define SOF_IPC4_MODULE_LOAD_TYPE GENMASK(3, 0)
+#define SOF_IPC4_MODULE_AUTO_START BIT(4)
+/*
+ * Two module schedule domains in fw :
+ * LL domain - Low latency domain
+ * DP domain - Data processing domain
+ * The LL setting should be equal to !DP setting
+ */
+#define SOF_IPC4_MODULE_LL BIT(5)
+#define SOF_IPC4_MODULE_DP BIT(6)
+#define SOF_IPC4_MODULE_LIB_CODE BIT(7)
+
+#define SOF_IPC4_MODULE_INSTANCE_LIST_ITEM_SIZE 12
+#define SOF_IPC4_PIPELINE_OBJECT_SIZE 448
+#define SOF_IPC4_DATA_QUEUE_OBJECT_SIZE 128
+#define SOF_IPC4_LL_TASK_OBJECT_SIZE 72
+#define SOF_IPC4_DP_TASK_OBJECT_SIZE 104
+#define SOF_IPC4_DP_TASK_LIST_SIZE (12 + 8)
+#define SOF_IPC4_LL_TASK_LIST_ITEM_SIZE 12
+#define SOF_IPC4_FW_MAX_PAGE_COUNT 20
+#define SOF_IPC4_FW_MAX_QUEUE_COUNT 8
+
+/* Node index and mask applicable for host copier and ALH/HDA type DAI copiers */
+#define SOF_IPC4_NODE_INDEX_MASK 0xFF
+#define SOF_IPC4_NODE_INDEX(x) ((x) & SOF_IPC4_NODE_INDEX_MASK)
+#define SOF_IPC4_NODE_TYPE(x) ((x) << 8)
+
+/* Node ID for SSP type DAI copiers */
+#define SOF_IPC4_NODE_INDEX_INTEL_SSP(x) (((x) & 0xf) << 4)
+
+/* Node ID for DMIC type DAI copiers */
+#define SOF_IPC4_NODE_INDEX_INTEL_DMIC(x) (((x) & 0x7) << 5)
+
+#define SOF_IPC4_GAIN_ALL_CHANNELS_MASK 0xffffffff
+#define SOF_IPC4_VOL_ZERO_DB 0x7fffffff
+
+#define ALH_MAX_NUMBER_OF_GTW 16
+
+/*
+ * The base of multi-gateways. Multi-gateways addressing starts from
+ * ALH_MULTI_GTW_BASE and there are ALH_MULTI_GTW_COUNT multi-sources
+ * and ALH_MULTI_GTW_COUNT multi-sinks available.
+ * Addressing is continuous from ALH_MULTI_GTW_BASE to
+ * ALH_MULTI_GTW_BASE + ALH_MULTI_GTW_COUNT - 1.
+ */
+#define ALH_MULTI_GTW_BASE 0x50
+/* A magic number from FW */
+#define ALH_MULTI_GTW_COUNT 8
+
+/**
+ * struct sof_ipc4_pipeline - pipeline config data
+ * @priority: Priority of this pipeline
+ * @lp_mode: Low power mode
+ * @mem_usage: Memory usage
+ * @state: Pipeline state
+ * @msg: message structure for pipeline
+ */
+struct sof_ipc4_pipeline {
+ uint32_t priority;
+ uint32_t lp_mode;
+ uint32_t mem_usage;
+ int state;
+ struct sof_ipc4_msg msg;
+};
+
+/**
+ * struct sof_ipc4_available_audio_format - Available audio formats
+ * @base_config: Available base config
+ * @out_audio_fmt: Available output audio format
+ * @ref_audio_fmt: Reference audio format to match runtime audio format
+ * @dma_buffer_size: Available Gateway DMA buffer size (in bytes)
+ * @audio_fmt_num: Number of available audio formats
+ */
+struct sof_ipc4_available_audio_format {
+ struct sof_ipc4_base_module_cfg *base_config;
+ struct sof_ipc4_audio_format *out_audio_fmt;
+ struct sof_ipc4_audio_format *ref_audio_fmt;
+ u32 *dma_buffer_size;
+ int audio_fmt_num;
+};
+
+/**
+ * struct sof_copier_gateway_cfg - IPC gateway configuration
+ * @node_id: ID of Gateway Node
+ * @dma_buffer_size: Preferred Gateway DMA buffer size (in bytes)
+ * @config_length: Length of gateway node configuration blob specified in #config_data
+ * config_data: Gateway node configuration blob
+ */
+struct sof_copier_gateway_cfg {
+ uint32_t node_id;
+ uint32_t dma_buffer_size;
+ uint32_t config_length;
+ uint32_t config_data[];
+};
+
+/**
+ * struct sof_ipc4_copier_data - IPC data for copier
+ * @base_config: Base configuration including input audio format
+ * @out_format: Output audio format
+ * @copier_feature_mask: Copier feature mask
+ * @gtw_cfg: Gateway configuration
+ */
+struct sof_ipc4_copier_data {
+ struct sof_ipc4_base_module_cfg base_config;
+ struct sof_ipc4_audio_format out_format;
+ uint32_t copier_feature_mask;
+ struct sof_copier_gateway_cfg gtw_cfg;
+};
+
+/**
+ * struct sof_ipc4_gtw_attributes: Gateway attributes
+ * @lp_buffer_alloc: Gateway data requested in low power memory
+ * @alloc_from_reg_file: Gateway data requested in register file memory
+ * @rsvd: reserved for future use
+ */
+struct sof_ipc4_gtw_attributes {
+ uint32_t lp_buffer_alloc : 1;
+ uint32_t alloc_from_reg_file : 1;
+ uint32_t rsvd : 30;
+};
+
+/** struct sof_ipc4_alh_multi_gtw_cfg: ALH gateway cfg data
+ * @count: Number of streams (valid items in mapping array)
+ * @alh_id: ALH stream id of a single ALH stream aggregated
+ * @channel_mask: Channel mask
+ * @mapping: ALH streams
+ */
+struct sof_ipc4_alh_multi_gtw_cfg {
+ uint32_t count;
+ struct {
+ uint32_t alh_id;
+ uint32_t channel_mask;
+ } mapping[ALH_MAX_NUMBER_OF_GTW];
+} __packed;
+
+/** struct sof_ipc4_alh_configuration_blob: ALH blob
+ * @gw_attr: Gateway attributes
+ * @alh_cfg: ALH configuration data
+ */
+struct sof_ipc4_alh_configuration_blob {
+ struct sof_ipc4_gtw_attributes gw_attr;
+ struct sof_ipc4_alh_multi_gtw_cfg alh_cfg;
+};
+
+/**
+ * struct sof_ipc4_copier - copier config data
+ * @data: IPC copier data
+ * @copier_config: Copier + blob
+ * @ipc_config_size: Size of copier_config
+ * @available_fmt: Available audio format
+ * @frame_fmt: frame format
+ * @msg: message structure for copier
+ * @gtw_attr: Gateway attributes for copier blob
+ * @dai_type: DAI type
+ * @dai_index: DAI index
+ */
+struct sof_ipc4_copier {
+ struct sof_ipc4_copier_data data;
+ u32 *copier_config;
+ uint32_t ipc_config_size;
+ void *ipc_config_data;
+ struct sof_ipc4_available_audio_format available_fmt;
+ u32 frame_fmt;
+ struct sof_ipc4_msg msg;
+ struct sof_ipc4_gtw_attributes *gtw_attr;
+ u32 dai_type;
+ int dai_index;
+};
+
+/**
+ * struct sof_ipc4_ctrl_value_chan: generic channel mapped value data
+ * @channel: Channel ID
+ * @value: gain value
+ */
+struct sof_ipc4_ctrl_value_chan {
+ u32 channel;
+ u32 value;
+};
+
+/**
+ * struct sof_ipc4_control_data - IPC data for kcontrol IO
+ * @msg: message structure for kcontrol IO
+ * @index: pipeline ID
+ * @chanv: channel ID and value array used by volume type controls
+ * @data: data for binary kcontrols
+ */
+struct sof_ipc4_control_data {
+ struct sof_ipc4_msg msg;
+ int index;
+
+ union {
+ struct sof_ipc4_ctrl_value_chan chanv[0];
+ struct sof_abi_hdr data[0];
+ };
+};
+
+/**
+ * struct sof_ipc4_gain_data - IPC gain blob
+ * @channels: Channels
+ * @init_val: Initial value
+ * @curve_type: Curve type
+ * @reserved: reserved for future use
+ * @curve_duration: Curve duration
+ */
+struct sof_ipc4_gain_data {
+ uint32_t channels;
+ uint32_t init_val;
+ uint32_t curve_type;
+ uint32_t reserved;
+ uint32_t curve_duration;
+} __aligned(8);
+
+/**
+ * struct sof_ipc4_gain - gain config data
+ * @base_config: IPC base config data
+ * @data: IPC gain blob
+ * @available_fmt: Available audio format
+ * @msg: message structure for gain
+ */
+struct sof_ipc4_gain {
+ struct sof_ipc4_base_module_cfg base_config;
+ struct sof_ipc4_gain_data data;
+ struct sof_ipc4_available_audio_format available_fmt;
+ struct sof_ipc4_msg msg;
+};
+
+/**
+ * struct sof_ipc4_mixer - mixer config data
+ * @base_config: IPC base config data
+ * @available_fmt: Available audio format
+ * @msg: IPC4 message struct containing header and data info
+ */
+struct sof_ipc4_mixer {
+ struct sof_ipc4_base_module_cfg base_config;
+ struct sof_ipc4_available_audio_format available_fmt;
+ struct sof_ipc4_msg msg;
+};
+
+/**
+ * struct sof_ipc4_src SRC config data
+ * @base_config: IPC base config data
+ * @sink_rate: Output rate for sink module
+ * @available_fmt: Available audio format
+ * @msg: IPC4 message struct containing header and data info
+ */
+struct sof_ipc4_src {
+ struct sof_ipc4_base_module_cfg base_config;
+ uint32_t sink_rate;
+ struct sof_ipc4_available_audio_format available_fmt;
+ struct sof_ipc4_msg msg;
+};
+
+#endif
diff --git a/sound/soc/sof/ipc4.c b/sound/soc/sof/ipc4.c
index 658802c86685..432b812bdf9c 100644
--- a/sound/soc/sof/ipc4.c
+++ b/sound/soc/sof/ipc4.c
@@ -574,7 +574,7 @@ static void sof_ipc4_rx_msg(struct snd_sof_dev *sdev)
data_size = sizeof(struct sof_ipc4_notify_resource_data);
break;
default:
- dev_dbg(sdev->dev, "%s: Unhandled DSP message: %#x|%#x\n", __func__,
+ dev_dbg(sdev->dev, "Unhandled DSP message: %#x|%#x\n",
ipc4_msg->primary, ipc4_msg->extension);
break;
}
@@ -597,10 +597,53 @@ static void sof_ipc4_rx_msg(struct snd_sof_dev *sdev)
}
}
+static int sof_ipc4_set_core_state(struct snd_sof_dev *sdev, int core_idx, bool on)
+{
+ struct sof_ipc4_dx_state_info dx_state;
+ struct sof_ipc4_msg msg;
+
+ dx_state.core_mask = BIT(core_idx);
+ if (on)
+ dx_state.dx_mask = BIT(core_idx);
+ else
+ dx_state.dx_mask = 0;
+
+ msg.primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_SET_DX);
+ msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+ msg.extension = 0;
+ msg.data_ptr = &dx_state;
+ msg.data_size = sizeof(dx_state);
+
+ return sof_ipc4_tx_msg(sdev, &msg, msg.data_size, NULL, 0, false);
+}
+
+/*
+ * The context save callback is used to send a message to the firmware notifying
+ * it that the primary core is going to be turned off, which is used as an
+ * indication to prepare for a full power down, thus preparing for IMR boot
+ * (when supported)
+ *
+ * Note: in IPC4 there is no message used to restore context, thus no context
+ * restore callback is implemented
+ */
+static int sof_ipc4_ctx_save(struct snd_sof_dev *sdev)
+{
+ return sof_ipc4_set_core_state(sdev, SOF_DSP_PRIMARY_CORE, false);
+}
+
+static const struct sof_ipc_pm_ops ipc4_pm_ops = {
+ .ctx_save = sof_ipc4_ctx_save,
+ .set_core_state = sof_ipc4_set_core_state,
+};
+
const struct sof_ipc_ops ipc4_ops = {
.tx_msg = sof_ipc4_tx_msg,
.rx_msg = sof_ipc4_rx_msg,
.set_get_data = sof_ipc4_set_get_data,
.get_reply = sof_ipc4_get_reply,
+ .pm = &ipc4_pm_ops,
.fw_loader = &ipc4_loader_ops,
+ .tplg = &ipc4_tplg_ops,
+ .pcm = &ipc4_pcm_ops,
};
diff --git a/sound/soc/sof/mediatek/Kconfig b/sound/soc/sof/mediatek/Kconfig
index a149dd1b3f44..4a2eddf6009a 100644
--- a/sound/soc/sof/mediatek/Kconfig
+++ b/sound/soc/sof/mediatek/Kconfig
@@ -15,6 +15,7 @@ config SND_SOC_SOF_MTK_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
diff --git a/sound/soc/sof/mediatek/adsp_helper.h b/sound/soc/sof/mediatek/adsp_helper.h
index 4ab998756bbc..d41e904e6614 100644
--- a/sound/soc/sof/mediatek/adsp_helper.h
+++ b/sound/soc/sof/mediatek/adsp_helper.h
@@ -20,6 +20,7 @@ struct mtk_adsp_chip_info {
u32 sramsize;
u32 dramsize;
u32 cfgregsize;
+ u32 shared_size;
void __iomem *va_sram; /* corresponding to pa_sram */
void __iomem *va_dram; /* corresponding to pa_dram */
void __iomem *va_cfgreg;
diff --git a/sound/soc/sof/mediatek/mt8186/mt8186-clk.c b/sound/soc/sof/mediatek/mt8186/mt8186-clk.c
index 22220fd50b62..2df3b7ae1c6f 100644
--- a/sound/soc/sof/mediatek/mt8186/mt8186-clk.c
+++ b/sound/soc/sof/mediatek/mt8186/mt8186-clk.c
@@ -18,8 +18,8 @@
#include "mt8186-clk.h"
static const char *adsp_clks[ADSP_CLK_MAX] = {
- [CLK_TOP_AUDIODSP] = "audiodsp_sel",
- [CLK_TOP_ADSP_BUS] = "adsp_bus_sel",
+ [CLK_TOP_AUDIODSP] = "audiodsp",
+ [CLK_TOP_ADSP_BUS] = "adsp_bus",
};
int mt8186_adsp_init_clock(struct snd_sof_dev *sdev)
diff --git a/sound/soc/sof/mediatek/mt8195/mt8195-clk.c b/sound/soc/sof/mediatek/mt8195/mt8195-clk.c
index 6bcb4b9b00fb..9ef08e43aa38 100644
--- a/sound/soc/sof/mediatek/mt8195/mt8195-clk.c
+++ b/sound/soc/sof/mediatek/mt8195/mt8195-clk.c
@@ -132,6 +132,13 @@ static int adsp_default_clk_init(struct snd_sof_dev *sdev, bool enable)
return ret;
}
+ ret = clk_set_parent(priv->clk[CLK_TOP_AUDIO_H],
+ priv->clk[CLK_TOP_CLK26M]);
+ if (ret) {
+ dev_err(dev, "set audio_h_sel failed %d\n", ret);
+ return ret;
+ }
+
ret = adsp_enable_all_clock(sdev);
if (ret) {
dev_err(dev, "failed to adsp_enable_clock: %d\n", ret);
diff --git a/sound/soc/sof/mediatek/mt8195/mt8195-loader.c b/sound/soc/sof/mediatek/mt8195/mt8195-loader.c
index ed18d6379e92..4be99ff4ebd3 100644
--- a/sound/soc/sof/mediatek/mt8195/mt8195-loader.c
+++ b/sound/soc/sof/mediatek/mt8195/mt8195-loader.c
@@ -21,7 +21,7 @@ void sof_hifixdsp_boot_sequence(struct snd_sof_dev *sdev, u32 boot_addr)
/* pull high StatVectorSel to use AltResetVec (set bit4 to 1) */
snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, DSP_RESET_SW,
- DSP_RESET_SW, DSP_RESET_SW);
+ STATVECTOR_SEL, STATVECTOR_SEL);
/* toggle DReset & BReset */
/* pull high DReset & BReset */
@@ -29,6 +29,9 @@ void sof_hifixdsp_boot_sequence(struct snd_sof_dev *sdev, u32 boot_addr)
ADSP_BRESET_SW | ADSP_DRESET_SW,
ADSP_BRESET_SW | ADSP_DRESET_SW);
+ /* delay 10 DSP cycles at 26M about 1us by IP vendor's suggestion */
+ udelay(1);
+
/* pull low DReset & BReset */
snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, DSP_RESET_SW,
ADSP_BRESET_SW | ADSP_DRESET_SW,
@@ -46,11 +49,13 @@ void sof_hifixdsp_boot_sequence(struct snd_sof_dev *sdev, u32 boot_addr)
void sof_hifixdsp_shutdown(struct snd_sof_dev *sdev)
{
- /* Clear to 0 firstly */
- snd_sof_dsp_write(sdev, DSP_REG_BAR, DSP_RESET_SW, 0x0);
-
/* RUN_STALL pull high again to reset */
snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, DSP_RESET_SW,
ADSP_RUNSTALL, ADSP_RUNSTALL);
+
+ /* pull high DReset & BReset */
+ snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, DSP_RESET_SW,
+ ADSP_BRESET_SW | ADSP_DRESET_SW,
+ ADSP_BRESET_SW | ADSP_DRESET_SW);
}
diff --git a/sound/soc/sof/mediatek/mt8195/mt8195.c b/sound/soc/sof/mediatek/mt8195/mt8195.c
index 30111ab23bf5..9c146015cd1b 100644
--- a/sound/soc/sof/mediatek/mt8195/mt8195.c
+++ b/sound/soc/sof/mediatek/mt8195/mt8195.c
@@ -145,6 +145,14 @@ static int platform_parse_resource(struct platform_device *pdev, void *data)
dev_dbg(dev, "DMA %pR\n", &res);
+ adsp->pa_shared_dram = (phys_addr_t)res.start;
+ adsp->shared_size = resource_size(&res);
+ if (adsp->pa_shared_dram & DRAM_REMAP_MASK) {
+ dev_err(dev, "adsp shared dma memory(%#x) is not 4K-aligned\n",
+ (u32)adsp->pa_shared_dram);
+ return -EINVAL;
+ }
+
ret = of_reserved_mem_device_init(dev);
if (ret) {
dev_err(dev, "of_reserved_mem_device_init failed\n");
@@ -273,23 +281,18 @@ static int adsp_shared_base_ioremap(struct platform_device *pdev, void *data)
{
struct device *dev = &pdev->dev;
struct mtk_adsp_chip_info *adsp = data;
- u32 shared_size;
/* remap shared-dram base to be non-cachable */
- shared_size = TOTAL_SIZE_SHARED_DRAM_FROM_TAIL;
- adsp->pa_shared_dram = adsp->pa_dram + adsp->dramsize - shared_size;
- if (adsp->va_dram) {
- adsp->shared_dram = adsp->va_dram + DSP_DRAM_SIZE - shared_size;
- } else {
- adsp->shared_dram = devm_ioremap(dev, adsp->pa_shared_dram,
- shared_size);
- if (!adsp->shared_dram) {
- dev_err(dev, "ioremap failed for shared DRAM\n");
- return -ENOMEM;
- }
+ adsp->shared_dram = devm_ioremap(dev, adsp->pa_shared_dram,
+ adsp->shared_size);
+ if (!adsp->shared_dram) {
+ dev_err(dev, "failed to ioremap base %pa size %#x\n",
+ adsp->shared_dram, adsp->shared_size);
+ return -ENOMEM;
}
+
dev_dbg(dev, "shared-dram vbase=%p, phy addr :%pa, size=%#x\n",
- adsp->shared_dram, &adsp->pa_shared_dram, shared_size);
+ adsp->shared_dram, &adsp->pa_shared_dram, adsp->shared_size);
return 0;
}
@@ -361,9 +364,11 @@ static int mt8195_dsp_probe(struct snd_sof_dev *sdev)
goto err_adsp_sram_power_off;
}
- sdev->bar[SOF_FW_BLK_TYPE_SRAM] = devm_ioremap_wc(sdev->dev,
- priv->adsp->pa_dram,
- priv->adsp->dramsize);
+ priv->adsp->va_sram = sdev->bar[SOF_FW_BLK_TYPE_IRAM];
+
+ sdev->bar[SOF_FW_BLK_TYPE_SRAM] = devm_ioremap(sdev->dev,
+ priv->adsp->pa_dram,
+ priv->adsp->dramsize);
if (!sdev->bar[SOF_FW_BLK_TYPE_SRAM]) {
dev_err(sdev->dev, "failed to ioremap base %pa size %#x\n",
&priv->adsp->pa_dram, priv->adsp->dramsize);
@@ -438,6 +443,19 @@ static int mt8195_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state)
{
struct platform_device *pdev = container_of(sdev->dev, struct platform_device, dev);
int ret;
+ u32 reset_sw, dbg_pc;
+
+ /* wait dsp enter idle, timeout is 1 second */
+ ret = snd_sof_dsp_read_poll_timeout(sdev, DSP_REG_BAR,
+ DSP_RESET_SW, reset_sw,
+ ((reset_sw & ADSP_PWAIT) == ADSP_PWAIT),
+ SUSPEND_DSP_IDLE_POLL_INTERVAL_US,
+ SUSPEND_DSP_IDLE_TIMEOUT_US);
+ if (ret < 0) {
+ dbg_pc = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGPC);
+ dev_warn(sdev->dev, "dsp not idle, powering off anyway : swrest %#x, pc %#x, ret %d\n",
+ reset_sw, dbg_pc, ret);
+ }
/* stall and reset dsp */
sof_hifixdsp_shutdown(sdev);
diff --git a/sound/soc/sof/mediatek/mt8195/mt8195.h b/sound/soc/sof/mediatek/mt8195/mt8195.h
index 929424182357..7ffd523f936c 100644
--- a/sound/soc/sof/mediatek/mt8195/mt8195.h
+++ b/sound/soc/sof/mediatek/mt8195/mt8195.h
@@ -34,6 +34,7 @@ struct snd_sof_dev;
#define ADSP_DRESET_SW BIT(1)
#define ADSP_RUNSTALL BIT(3)
#define STATVECTOR_SEL BIT(4)
+#define ADSP_PWAIT BIT(16)
#define DSP_PFAULTBUS 0x0028
#define DSP_PFAULTINFO 0x002c
#define DSP_GPR00 0x0030
@@ -153,6 +154,10 @@ struct snd_sof_dev;
#define DRAM_REMAP_SHIFT 12
#define DRAM_REMAP_MASK (BIT(DRAM_REMAP_SHIFT) - 1)
+/* suspend dsp idle check interval and timeout */
+#define SUSPEND_DSP_IDLE_TIMEOUT_US 1000000 /* timeout to wait dsp idle, 1 sec */
+#define SUSPEND_DSP_IDLE_POLL_INTERVAL_US 500 /* 0.5 msec */
+
void sof_hifixdsp_boot_sequence(struct snd_sof_dev *sdev, u32 boot_addr);
void sof_hifixdsp_shutdown(struct snd_sof_dev *sdev);
#endif
diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h
index b79ae4f66eba..55d43adb6a29 100644
--- a/sound/soc/sof/ops.h
+++ b/sound/soc/sof/ops.h
@@ -29,6 +29,12 @@ static inline int sof_ops_init(struct snd_sof_dev *sdev)
return 0;
}
+static inline void sof_ops_free(struct snd_sof_dev *sdev)
+{
+ if (sdev->pdata->desc->ops_free)
+ sdev->pdata->desc->ops_free(sdev);
+}
+
/* Mandatory operations are verified during probing */
/* init */
diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c
index a76d0b5b2ad9..6cb6a432be5e 100644
--- a/sound/soc/sof/pcm.c
+++ b/sound/soc/sof/pcm.c
@@ -604,6 +604,14 @@ static int sof_pcm_probe(struct snd_soc_component *component)
const char *tplg_filename;
int ret;
+ /*
+ * make sure the device is pm_runtime_active before loading the
+ * topology and initiating IPC or bus transactions
+ */
+ ret = pm_runtime_resume_and_get(component->dev);
+ if (ret < 0 && ret != -EACCES)
+ return ret;
+
/* load the default topology */
sdev->component = component;
@@ -621,6 +629,9 @@ static int sof_pcm_probe(struct snd_soc_component *component)
return ret;
}
+ pm_runtime_mark_last_busy(component->dev);
+ pm_runtime_put_autosuspend(component->dev);
+
return ret;
}
@@ -671,4 +682,6 @@ void snd_sof_new_platform_drv(struct snd_sof_dev *sdev)
/* increment module refcount when a pcm is opened */
pd->module_get_upon_open = 1;
+
+ pd->legacy_dai_naming = 1;
}
diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h
index 27cc5fb642e5..4284ea2f3a1f 100644
--- a/sound/soc/sof/sof-audio.h
+++ b/sound/soc/sof/sof-audio.h
@@ -168,6 +168,7 @@ struct sof_ipc_tplg_widget_ops {
* @dai_get_clk: Function pointer for getting the DAI clock setting
* @set_up_all_pipelines: Function pointer for setting up all topology pipelines
* @tear_down_all_pipelines: Function pointer for tearing down all topology pipelines
+ * @parse_manifest: Optional function pointer for ipc4 specific parsing of topology manifest
*/
struct sof_ipc_tplg_ops {
const struct sof_ipc_tplg_widget_ops *widget;
@@ -185,6 +186,8 @@ struct sof_ipc_tplg_ops {
int (*dai_get_clk)(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, int clk_type);
int (*set_up_all_pipelines)(struct snd_sof_dev *sdev, bool verify);
int (*tear_down_all_pipelines)(struct snd_sof_dev *sdev, bool verify);
+ int (*parse_manifest)(struct snd_soc_component *scomp, int index,
+ struct snd_soc_tplg_manifest *man);
};
/** struct snd_sof_tuple - Tuple info
@@ -225,6 +228,15 @@ enum sof_tokens {
SOF_AFE_TOKENS,
SOF_CORE_TOKENS,
SOF_COMP_EXT_TOKENS,
+ SOF_IN_AUDIO_FORMAT_TOKENS,
+ SOF_OUT_AUDIO_FORMAT_TOKENS,
+ SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS,
+ SOF_COPIER_GATEWAY_CFG_TOKENS,
+ SOF_COPIER_TOKENS,
+ SOF_AUDIO_FMT_NUM_TOKENS,
+ SOF_COPIER_FORMAT_TOKENS,
+ SOF_GAIN_TOKENS,
+ SOF_ACPDMIC_TOKENS,
/* this should be the last */
SOF_TOKEN_COUNT,
diff --git a/sound/soc/sof/sof-client-ipc-msg-injector.c b/sound/soc/sof/sof-client-ipc-msg-injector.c
index 6bdfa527b7f7..752d5320680f 100644
--- a/sound/soc/sof/sof-client-ipc-msg-injector.c
+++ b/sound/soc/sof/sof-client-ipc-msg-injector.c
@@ -181,7 +181,7 @@ static ssize_t sof_msg_inject_ipc4_dfs_write(struct file *file,
struct sof_client_dev *cdev = file->private_data;
struct sof_msg_inject_priv *priv = cdev->data;
struct sof_ipc4_msg *ipc4_msg = priv->tx_buffer;
- ssize_t size;
+ size_t data_size;
int ret;
if (*ppos)
@@ -191,25 +191,20 @@ static ssize_t sof_msg_inject_ipc4_dfs_write(struct file *file,
return -EINVAL;
/* copy the header first */
- size = simple_write_to_buffer(&ipc4_msg->header_u64,
- sizeof(ipc4_msg->header_u64),
- ppos, buffer, count);
- if (size < 0)
- return size;
- if (size != sizeof(ipc4_msg->header_u64))
+ if (copy_from_user(&ipc4_msg->header_u64, buffer,
+ sizeof(ipc4_msg->header_u64)))
return -EFAULT;
- count -= size;
+ data_size = count - sizeof(ipc4_msg->header_u64);
+ if (data_size > priv->max_msg_size)
+ return -EINVAL;
+
/* Copy the payload */
- size = simple_write_to_buffer(ipc4_msg->data_ptr,
- priv->max_msg_size, ppos, buffer,
- count);
- if (size < 0)
- return size;
- if (size != count)
+ if (copy_from_user(ipc4_msg->data_ptr,
+ buffer + sizeof(ipc4_msg->header_u64), data_size))
return -EFAULT;
- ipc4_msg->data_size = count;
+ ipc4_msg->data_size = data_size;
/* Initialize the reply storage */
ipc4_msg = priv->rx_buffer;
@@ -221,9 +216,9 @@ static ssize_t sof_msg_inject_ipc4_dfs_write(struct file *file,
/* return the error code if test failed */
if (ret < 0)
- size = ret;
+ return ret;
- return size;
+ return count;
};
static int sof_msg_inject_dfs_release(struct inode *inode, struct file *file)
diff --git a/sound/soc/sof/sof-client-probes.c b/sound/soc/sof/sof-client-probes.c
index 34e6bd356e71..eb246b823461 100644
--- a/sound/soc/sof/sof-client-probes.c
+++ b/sound/soc/sof/sof-client-probes.c
@@ -270,9 +270,9 @@ static int sof_probes_compr_startup(struct snd_compr_stream *cstream,
if (ret)
return ret;
- ret = ops->assign(cdev, cstream, dai, &priv->extractor_stream_tag);
+ ret = ops->startup(cdev, cstream, dai, &priv->extractor_stream_tag);
if (ret) {
- dev_err(dai->dev, "Failed to assign probe stream: %d\n", ret);
+ dev_err(dai->dev, "Failed to startup probe stream: %d\n", ret);
priv->extractor_stream_tag = SOF_PROBES_INVALID_NODE_ID;
sof_client_core_module_put(cdev);
}
@@ -310,7 +310,7 @@ exit:
priv->extractor_stream_tag = SOF_PROBES_INVALID_NODE_ID;
snd_compr_free_pages(cstream);
- ret = ops->free(cdev, cstream, dai);
+ ret = ops->shutdown(cdev, cstream, dai);
sof_client_core_module_put(cdev);
@@ -667,6 +667,7 @@ static const struct snd_soc_component_driver sof_probes_component = {
.name = "sof-probes-component",
.compress_ops = &sof_probes_compressed_ops,
.module_get_upon_open = 1,
+ .legacy_dai_naming = 1,
};
SND_SOC_DAILINK_DEF(dummy, DAILINK_COMP_ARRAY(COMP_DUMMY()));
@@ -693,6 +694,10 @@ static int sof_probes_client_probe(struct auxiliary_device *auxdev,
if (!sof_probes_enabled)
return -ENXIO;
+ /* only ipc3 is supported */
+ if (sof_client_get_ipc_type(cdev) != SOF_IPC)
+ return -ENXIO;
+
if (!dev->platform_data) {
dev_err(dev, "missing platform data\n");
return -ENODEV;
@@ -704,7 +709,7 @@ static int sof_probes_client_probe(struct auxiliary_device *auxdev,
ops = dev->platform_data;
- if (!ops->assign || !ops->free || !ops->set_params || !ops->trigger ||
+ if (!ops->startup || !ops->shutdown || !ops->set_params || !ops->trigger ||
!ops->pointer) {
dev_err(dev, "missing platform callback(s)\n");
return -ENODEV;
diff --git a/sound/soc/sof/sof-client-probes.h b/sound/soc/sof/sof-client-probes.h
index 0f9ed4569fd3..9e43f3c444f8 100644
--- a/sound/soc/sof/sof-client-probes.h
+++ b/sound/soc/sof/sof-client-probes.h
@@ -14,10 +14,10 @@ struct snd_soc_dai;
* DSP and host, like HDA.
*/
struct sof_probes_host_ops {
- int (*assign)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream,
- struct snd_soc_dai *dai, u32 *stream_id);
- int (*free)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream,
- struct snd_soc_dai *dai);
+ int (*startup)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream,
+ struct snd_soc_dai *dai, u32 *stream_id);
+ int (*shutdown)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream,
+ struct snd_soc_dai *dai);
int (*set_params)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream,
struct snd_compr_params *params,
struct snd_soc_dai *dai);
diff --git a/sound/soc/sof/sof-client.c b/sound/soc/sof/sof-client.c
index 16cca666bb85..125aa2137195 100644
--- a/sound/soc/sof/sof-client.c
+++ b/sound/soc/sof/sof-client.c
@@ -383,8 +383,8 @@ void sof_client_ipc_rx_dispatcher(struct snd_sof_dev *sdev, void *msg_buf)
msg_type = SOF_IPC4_NOTIFICATION_TYPE_GET(msg->primary);
} else {
- dev_dbg_once(sdev->dev, "%s: Not supported IPC version: %d\n",
- __func__, sdev->pdata->ipc_type);
+ dev_dbg_once(sdev->dev, "Not supported IPC version: %d\n",
+ sdev->pdata->ipc_type);
return;
}
diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h
index f0f3d72c0da7..823583086279 100644
--- a/sound/soc/sof/sof-priv.h
+++ b/sound/soc/sof/sof-priv.h
@@ -37,6 +37,12 @@
#define SOF_DBG_IGNORE_D3_PERSISTENT BIT(7) /* ignore the DSP D3 persistent capability
* and always download firmware upon D3 exit
*/
+#define SOF_DBG_PRINT_DMA_POSITION_UPDATE_LOGS BIT(8) /* print DMA position updates
+ * in dmesg logs
+ */
+#define SOF_DBG_PRINT_IPC_SUCCESS_LOGS BIT(9) /* print IPC success
+ * in dmesg logs
+ */
/* Flag definitions used for controlling the DSP dump behavior */
#define SOF_DBG_DUMP_REGS BIT(0)
@@ -120,6 +126,7 @@ struct snd_sof_platform_stream_params {
bool use_phy_address;
u32 phy_addr;
bool no_ipc_position;
+ bool cont_update_posn;
};
/*
@@ -378,12 +385,14 @@ struct sof_ipc_fw_tracing_ops {
/**
* struct sof_ipc_pm_ops - IPC-specific PM ops
- * @ctx_save: Function pointer for context save
- * @ctx_restore: Function pointer for context restore
+ * @ctx_save: Optional function pointer for context save
+ * @ctx_restore: Optional function pointer for context restore
+ * @set_core_state: Optional function pointer for turning on/off a DSP core
*/
struct sof_ipc_pm_ops {
int (*ctx_save)(struct snd_sof_dev *sdev);
int (*ctx_restore)(struct snd_sof_dev *sdev);
+ int (*set_core_state)(struct snd_sof_dev *sdev, int core_idx, bool on);
};
/**
@@ -657,7 +666,7 @@ void sof_print_oops_and_stack(struct snd_sof_dev *sdev, const char *level,
u32 panic_code, u32 tracep_code, void *oops,
struct sof_ipc_panic_info *panic_info,
void *stack, size_t stack_words);
-void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev);
+void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev, const char *msg);
int snd_sof_dbg_memory_info_init(struct snd_sof_dev *sdev);
int snd_sof_debugfs_add_region_item_iomem(struct snd_sof_dev *sdev,
enum snd_sof_fw_blk_type blk_type, u32 offset, size_t size,
diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c
index b1fcab7ce48e..9273a70fec25 100644
--- a/sound/soc/sof/topology.c
+++ b/sound/soc/sof/topology.c
@@ -36,9 +36,6 @@
#define TLV_STEP 1
#define TLV_MUTE 2
-/* size of tplg abi in byte */
-#define SOF_TPLG_ABI_SIZE 3
-
/**
* sof_update_ipc_object - Parse multiple sets of tokens within the token array associated with the
* token ID.
@@ -1141,6 +1138,21 @@ static int spcm_bind(struct snd_soc_component *scomp, struct snd_sof_pcm *spcm,
return 0;
}
+static int sof_get_token_value(u32 token_id, struct snd_sof_tuple *tuples, int num_tuples)
+{
+ int i;
+
+ if (!tuples)
+ return -EINVAL;
+
+ for (i = 0; i < num_tuples; i++) {
+ if (tuples[i].token == token_id)
+ return tuples[i].value.v;
+ }
+
+ return -EINVAL;
+}
+
static int sof_widget_parse_tokens(struct snd_soc_component *scomp, struct snd_sof_widget *swidget,
struct snd_soc_tplg_dapm_widget *tw,
enum sof_tokens *object_token_list, int count)
@@ -1168,6 +1180,8 @@ static int sof_widget_parse_tokens(struct snd_soc_component *scomp, struct snd_s
/* parse token list for widget */
for (i = 0; i < count; i++) {
+ int num_sets = 1;
+
if (object_token_list[i] >= SOF_TOKEN_COUNT) {
dev_err(scomp->dev, "Invalid token id %d for widget %s\n",
object_token_list[i], swidget->widget->name);
@@ -1175,8 +1189,9 @@ static int sof_widget_parse_tokens(struct snd_soc_component *scomp, struct snd_s
goto err;
}
- /* parse and save UUID in swidget */
- if (object_token_list[i] == SOF_COMP_EXT_TOKENS) {
+ switch (object_token_list[i]) {
+ case SOF_COMP_EXT_TOKENS:
+ /* parse and save UUID in swidget */
ret = sof_parse_tokens(scomp, swidget,
token_list[object_token_list[i]].tokens,
token_list[object_token_list[i]].count,
@@ -1189,11 +1204,41 @@ static int sof_widget_parse_tokens(struct snd_soc_component *scomp, struct snd_s
}
continue;
+ case SOF_IN_AUDIO_FORMAT_TOKENS:
+ case SOF_OUT_AUDIO_FORMAT_TOKENS:
+ case SOF_COPIER_GATEWAY_CFG_TOKENS:
+ case SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS:
+ num_sets = sof_get_token_value(SOF_TKN_COMP_NUM_AUDIO_FORMATS,
+ swidget->tuples, swidget->num_tuples);
+
+ if (num_sets < 0) {
+ dev_err(sdev->dev, "Invalid audio format count for %s\n",
+ swidget->widget->name);
+ ret = num_sets;
+ goto err;
+ }
+
+ if (num_sets > 1) {
+ struct snd_sof_tuple *new_tuples;
+
+ num_tuples += token_list[object_token_list[i]].count * num_sets;
+ new_tuples = krealloc(swidget->tuples,
+ sizeof(*new_tuples) * num_tuples, GFP_KERNEL);
+ if (!new_tuples) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ swidget->tuples = new_tuples;
+ }
+ break;
+ default:
+ break;
}
/* copy one set of tuples per token ID into swidget->tuples */
ret = sof_copy_tuples(sdev, private->array, le32_to_cpu(private->size),
- object_token_list[i], 1, swidget->tuples,
+ object_token_list[i], num_sets, swidget->tuples,
num_tuples, &swidget->num_tuples);
if (ret < 0) {
dev_err(scomp->dev, "Failed parsing %s for widget %s err: %d\n",
@@ -1208,21 +1253,6 @@ err:
return ret;
}
-static int sof_get_token_value(u32 token_id, struct snd_sof_tuple *tuples, int num_tuples)
-{
- int i;
-
- if (!tuples)
- return -EINVAL;
-
- for (i = 0; i < num_tuples; i++) {
- if (tuples[i].token == token_id)
- return tuples[i].value.v;
- }
-
- return -EINVAL;
-}
-
/* external widget init - used for any driver specific init */
static int sof_widget_ready(struct snd_soc_component *scomp, int index,
struct snd_soc_dapm_widget *w,
@@ -1389,7 +1419,6 @@ static int sof_widget_unload(struct snd_soc_component *scomp,
struct soc_bytes_ext *sbe;
struct snd_sof_dai *dai;
struct soc_enum *se;
- int ret = 0;
int i;
swidget = dobj->private;
@@ -1450,7 +1479,7 @@ out:
list_del(&swidget->list);
kfree(swidget);
- return ret;
+ return 0;
}
/*
@@ -1709,6 +1738,10 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_
token_id = SOF_AFE_TOKENS;
num_tuples += token_list[SOF_AFE_TOKENS].count;
break;
+ case SOF_DAI_AMD_DMIC:
+ token_id = SOF_ACPDMIC_TOKENS;
+ num_tuples += token_list[SOF_ACPDMIC_TOKENS].count;
+ break;
default:
break;
}
@@ -1987,45 +2020,11 @@ static int sof_complete(struct snd_soc_component *scomp)
static int sof_manifest(struct snd_soc_component *scomp, int index,
struct snd_soc_tplg_manifest *man)
{
- u32 size;
- u32 abi_version;
-
- size = le32_to_cpu(man->priv.size);
-
- /* backward compatible with tplg without ABI info */
- if (!size) {
- dev_dbg(scomp->dev, "No topology ABI info\n");
- return 0;
- }
-
- if (size != SOF_TPLG_ABI_SIZE) {
- dev_err(scomp->dev, "error: invalid topology ABI size\n");
- return -EINVAL;
- }
-
- dev_info(scomp->dev,
- "Topology: ABI %d:%d:%d Kernel ABI %d:%d:%d\n",
- man->priv.data[0], man->priv.data[1],
- man->priv.data[2], SOF_ABI_MAJOR, SOF_ABI_MINOR,
- SOF_ABI_PATCH);
-
- abi_version = SOF_ABI_VER(man->priv.data[0],
- man->priv.data[1],
- man->priv.data[2]);
-
- if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, abi_version)) {
- dev_err(scomp->dev, "error: incompatible topology ABI version\n");
- return -EINVAL;
- }
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg;
- if (SOF_ABI_VERSION_MINOR(abi_version) > SOF_ABI_MINOR) {
- if (!IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS)) {
- dev_warn(scomp->dev, "warn: topology ABI is more recent than kernel\n");
- } else {
- dev_err(scomp->dev, "error: topology ABI is more recent than kernel\n");
- return -EINVAL;
- }
- }
+ if (ipc_tplg_ops->parse_manifest)
+ return ipc_tplg_ops->parse_manifest(scomp, index, man);
return 0;
}