diff options
author | Takashi Iwai <tiwai@suse.de> | 2022-05-23 16:03:04 +0200 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2022-05-23 16:03:04 +0200 |
commit | 0163717ed5dec4fc3aaf937baa9f66f21ca11c1d (patch) | |
tree | b0618a944385d1d35bd829c9aa09eddb5c9633fa /sound/soc/sof | |
parent | 1693e265e0a5dbe11fba21b48272dd15dbb71ec0 (diff) | |
parent | e5cd20e0d6713138444cc3f3f982712cf9a36143 (diff) |
Merge tag 'asoc-v5.19' of https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound into for-linus
ASoC: Updates for v5.19
This is quite a big update, partly due to the addition of some larger
drivers (more of which is to follow since at least the AVS driver is
still a work in progress) and partly due to Charles' work sorting out
our handling of endianness. As has been the case recently it's much
more about drivers than the core.
- Overhaul of endianness specification for data formats, avoiding
needless restrictions due to CODECs.
- Initial stages of Intel AVS driver merge.
- Introduction of v4 IPC mechanism for SOF.
- TDM mode support for AK4613.
- Support for Analog Devices ADAU1361, Cirrus Logic CS35L45, Maxim
MAX98396, MediaTek MT8186, NXP i.MX8 micfil and SAI interfaces,
nVidia Tegra186 ASRC, and Texas Instruments TAS2764 and TAS2780
Diffstat (limited to 'sound/soc/sof')
77 files changed, 6578 insertions, 3405 deletions
diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index 18acbc001b9a..92b5e83601be 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -2,7 +2,9 @@ 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.o ipc3-control.o ipc3-pcm.o + ipc3-topology.o ipc3-control.o ipc3.o ipc3-pcm.o ipc3-loader.o\ + ipc3-dtrace.o\ + ipc4.o ipc4-loader.o ifneq ($(CONFIG_SND_SOC_SOF_CLIENT),) snd-sof-objs += sof-client.o endif diff --git a/sound/soc/sof/amd/acp-trace.c b/sound/soc/sof/amd/acp-trace.c index 903b6cc3dda3..c9482b27cbe3 100644 --- a/sound/soc/sof/amd/acp-trace.c +++ b/sound/soc/sof/amd/acp-trace.c @@ -34,7 +34,7 @@ int acp_sof_trace_release(struct snd_sof_dev *sdev) } EXPORT_SYMBOL_NS(acp_sof_trace_release, SND_SOC_SOF_AMD_COMMON); -int acp_sof_trace_init(struct snd_sof_dev *sdev, +int acp_sof_trace_init(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, struct sof_ipc_dma_trace_params_ext *dtrace_params) { struct acp_dsp_stream *stream; @@ -46,7 +46,7 @@ int acp_sof_trace_init(struct snd_sof_dev *sdev, if (!stream) return -ENODEV; - stream->dmab = &sdev->dmatb; + stream->dmab = dmab; stream->num_pages = NUM_PAGES; ret = acp_dsp_stream_config(sdev, stream); diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index 71d71c152342..0c272573df97 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -138,23 +138,75 @@ int configure_and_run_dma(struct acp_dev_data *adata, unsigned int src_addr, return ret; } -static int psp_fw_validate(struct acp_dev_data *adata) +/* + * psp_mbox_ready- function to poll ready bit of psp mbox + * @adata: acp device data + * @ack: bool variable to check ready bit status or psp ack + */ + +static int psp_mbox_ready(struct acp_dev_data *adata, bool ack) { struct snd_sof_dev *sdev = adata->dev; int timeout; u32 data; - smn_write(adata->smn_dev, MP0_C2PMSG_26_REG, MBOX_ACP_SHA_DMA_COMMAND); - for (timeout = ACP_PSP_TIMEOUT_COUNTER; timeout > 0; timeout--) { msleep(20); - smn_read(adata->smn_dev, MP0_C2PMSG_26_REG, &data); + smn_read(adata->smn_dev, MP0_C2PMSG_114_REG, &data); if (data & MBOX_READY_MASK) return 0; } - dev_err(sdev->dev, "FW validation timedout: status %x\n", data & MBOX_STATUS_MASK); - return -ETIMEDOUT; + dev_err(sdev->dev, "PSP error status %x\n", data & MBOX_STATUS_MASK); + + if (ack) + return -ETIMEDOUT; + + return -EBUSY; +} + +/* + * psp_send_cmd - function to send psp command over mbox + * @adata: acp device data + * @cmd: non zero integer value for command type + */ + +static int psp_send_cmd(struct acp_dev_data *adata, int cmd) +{ + struct snd_sof_dev *sdev = adata->dev; + int ret, timeout; + u32 data; + + if (!cmd) + return -EINVAL; + + /* Get a non-zero Doorbell value from PSP */ + for (timeout = ACP_PSP_TIMEOUT_COUNTER; timeout > 0; timeout--) { + msleep(MBOX_DELAY); + smn_read(adata->smn_dev, MP0_C2PMSG_73_REG, &data); + if (data) + break; + } + + if (!timeout) { + dev_err(sdev->dev, "Failed to get Doorbell from MBOX %x\n", MP0_C2PMSG_73_REG); + return -EINVAL; + } + + /* Check if PSP is ready for new command */ + ret = psp_mbox_ready(adata, 0); + if (ret) + return ret; + + smn_write(adata->smn_dev, MP0_C2PMSG_114_REG, cmd); + + /* Ring the Doorbell for PSP */ + smn_write(adata->smn_dev, MP0_C2PMSG_73_REG, data); + + /* Check MBOX ready as PSP ack */ + ret = psp_mbox_ready(adata, 1); + + return ret; } int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, @@ -196,7 +248,7 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, return ret; } - ret = psp_fw_validate(adata); + ret = psp_send_cmd(adata, MBOX_ACP_SHA_DMA_COMMAND); if (ret) return ret; diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index 35e46fe6676a..291b44c54bcc 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -57,8 +57,10 @@ #define ACP_SHA_STAT 0x8000 #define ACP_PSP_TIMEOUT_COUNTER 5 #define ACP_EXT_INTR_ERROR_STAT 0x20000000 -#define MP0_C2PMSG_26_REG 0x03810570 -#define MBOX_ACP_SHA_DMA_COMMAND 0x330000 +#define MP0_C2PMSG_114_REG 0x3810AC8 +#define MP0_C2PMSG_73_REG 0x3810A24 +#define MBOX_ACP_SHA_DMA_COMMAND 0x70000 +#define MBOX_DELAY 1000 #define MBOX_READY_MASK 0x80000000 #define MBOX_STATUS_MASK 0xFFFF @@ -204,13 +206,13 @@ int acp_pcm_hw_params(struct snd_sof_dev *sdev, struct snd_pcm_substream *substr struct snd_pcm_hw_params *params, struct snd_sof_platform_stream_params *platform_params); -extern const struct snd_sof_dsp_ops sof_renoir_ops; +extern struct snd_sof_dsp_ops sof_renoir_ops; /* Machine configuration */ int snd_amd_acp_find_config(struct pci_dev *pci); /* Trace */ -int acp_sof_trace_init(struct snd_sof_dev *sdev, +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); diff --git a/sound/soc/sof/amd/pci-rn.c b/sound/soc/sof/amd/pci-rn.c index 392ffbdf6417..d5d9bcc2c997 100644 --- a/sound/soc/sof/amd/pci-rn.c +++ b/sound/soc/sof/amd/pci-rn.c @@ -54,9 +54,17 @@ static const struct sof_dev_desc renoir_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &renoir_chip_info, - .default_fw_path = "amd/sof", - .default_tplg_path = "amd/sof-tplg", - .default_fw_filename = "sof-rn.ri", + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "amd/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "amd/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-rn.ri", + }, .nocodec_tplg_filename = "sof-acp.tplg", .ops = &sof_renoir_ops, }; @@ -93,6 +101,7 @@ static int acp_pci_rn_probe(struct pci_dev *pci, const struct pci_device_id *pci res = devm_kzalloc(&pci->dev, sizeof(struct resource) * ARRAY_SIZE(renoir_res), GFP_KERNEL); if (!res) { sof_pci_remove(pci); + platform_device_unregister(dmic_dev); return -ENOMEM; } diff --git a/sound/soc/sof/amd/renoir.c b/sound/soc/sof/amd/renoir.c index 409fd57448b8..70190365328c 100644 --- a/sound/soc/sof/amd/renoir.c +++ b/sound/soc/sof/amd/renoir.c @@ -123,7 +123,7 @@ static struct snd_soc_acpi_mach *amd_sof_machine_select(struct snd_sof_dev *sdev } /* AMD Renoir DSP ops */ -const struct snd_sof_dsp_ops sof_renoir_ops = { +struct snd_sof_dsp_ops sof_renoir_ops = { /* probe and remove */ .probe = amd_sof_acp_probe, .remove = amd_sof_acp_remove, @@ -136,9 +136,6 @@ const struct snd_sof_dsp_ops sof_renoir_ops = { .block_read = acp_dsp_block_read, .block_write = acp_dsp_block_write, - /* Module loading */ - .load_module = snd_sof_parse_module_memcpy, - /*Firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy, .pre_fw_run = acp_dsp_pre_fw_run, @@ -152,7 +149,6 @@ const struct snd_sof_dsp_ops sof_renoir_ops = { .ipc_msg_data = acp_sof_ipc_msg_data, .get_mailbox_offset = acp_sof_ipc_get_mailbox_offset, .irq_thread = acp_sof_ipc_irq_thread, - .fw_ready = sof_fw_ready, /* DAI drivers */ .drv = renoir_sof_dai, diff --git a/sound/soc/sof/compress.c b/sound/soc/sof/compress.c index a8e908e50101..47639b6344c8 100644 --- a/sound/soc/sof/compress.c +++ b/sound/soc/sof/compress.c @@ -147,8 +147,7 @@ static int sof_compr_free(struct snd_soc_component *component, stream.comp_id = spcm->stream[cstream->direction].comp_id; if (spcm->prepared[cstream->direction]) { - ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, - &stream, sizeof(stream), + ret = sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream), &reply, sizeof(reply)); if (!ret) spcm->prepared[cstream->direction] = false; @@ -209,7 +208,7 @@ static int sof_compr_set_params(struct snd_soc_component *component, snd_pcm_format_physical_width(SNDRV_PCM_FORMAT_S32) >> 3; pcm.params.host_period_bytes = params->buffer.fragment_size; - ret = sof_ipc_tx_message(sdev->ipc, pcm.hdr.cmd, &pcm, sizeof(pcm), + ret = sof_ipc_tx_message(sdev->ipc, &pcm, sizeof(pcm), &ipc_params_reply, sizeof(ipc_params_reply)); if (ret < 0) { dev_err(component->dev, "error ipc failed\n"); @@ -268,8 +267,7 @@ static int sof_compr_trigger(struct snd_soc_component *component, break; } - return sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, - &stream, sizeof(stream), + return sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream), &reply, sizeof(reply)); } diff --git a/sound/soc/sof/control.c b/sound/soc/sof/control.c index de1778c4002b..e0e9efd25d34 100644 --- a/sound/soc/sof/control.c +++ b/sound/soc/sof/control.c @@ -15,36 +15,6 @@ #include "sof-priv.h" #include "sof-audio.h" -static void update_mute_led(struct snd_sof_control *scontrol, - struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - int temp = 0; - int mask; - int i; - - mask = 1U << snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); - - for (i = 0; i < scontrol->num_channels; i++) { - if (ucontrol->value.integer.value[i]) { - temp |= mask; - break; - } - } - - if (temp == scontrol->led_ctl.led_value) - return; - - scontrol->led_ctl.led_value = temp; - -#if IS_REACHABLE(CONFIG_LEDS_TRIGGER_AUDIO) - if (!scontrol->led_ctl.direction) - ledtrig_audio_set(LED_AUDIO_MUTE, temp ? LED_OFF : LED_ON); - else - ledtrig_audio_set(LED_AUDIO_MICMUTE, temp ? LED_OFF : LED_ON); -#endif -} - int snd_sof_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -121,9 +91,6 @@ int snd_sof_switch_put(struct snd_kcontrol *kcontrol, struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; - if (scontrol->led_ctl.use_led) - update_mute_led(scontrol, kcontrol, ucontrol); - if (tplg_ops->control->switch_put) return tplg_ops->control->switch_put(scontrol, ucontrol); @@ -220,10 +187,9 @@ int snd_sof_bytes_ext_volatile_get(struct snd_kcontrol *kcontrol, unsigned int _ const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; int ret, err; - ret = pm_runtime_get_sync(scomp->dev); + ret = pm_runtime_resume_and_get(scomp->dev); if (ret < 0 && ret != -EACCES) { dev_err_ratelimited(scomp->dev, "%s: failed to resume %d\n", __func__, ret); - pm_runtime_put_noidle(scomp->dev); return ret; } diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index e91631618bff..53719c04658f 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -250,14 +250,13 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) } if (sof_debug_check_flag(SOF_DBG_ENABLE_TRACE)) { - sdev->dtrace_is_supported = true; + sdev->fw_trace_is_supported = true; - /* init DMA trace */ - ret = snd_sof_init_trace(sdev); + /* init firmware tracing */ + ret = sof_fw_trace_init(sdev); if (ret < 0) { /* non fatal */ - dev_warn(sdev->dev, - "warning: failed to initialize trace %d\n", + dev_warn(sdev->dev, "failed to initialize firmware tracing %d\n", ret); } } else { @@ -308,7 +307,7 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) sof_machine_err: snd_sof_machine_unregister(sdev, plat_data); fw_trace_err: - snd_sof_free_trace(sdev); + sof_fw_trace_free(sdev); fw_run_err: snd_sof_fw_unload(sdev); fw_load_err: @@ -342,6 +341,7 @@ static void sof_probe_work(struct work_struct *work) int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) { struct snd_sof_dev *sdev; + int ret; sdev = devm_kzalloc(dev, sizeof(*sdev), GFP_KERNEL); if (!sdev) @@ -357,11 +357,23 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) sdev->first_boot = true; dev_set_drvdata(dev, sdev); + /* check IPC support */ + if (!(BIT(plat_data->ipc_type) & plat_data->desc->ipc_supported_mask)) { + dev_err(dev, "ipc_type %d is not supported on this platform, mask is %#x\n", + plat_data->ipc_type, plat_data->desc->ipc_supported_mask); + return -EINVAL; + } + + /* init ops, if necessary */ + ret = sof_ops_init(sdev); + if (ret < 0) + return ret; + /* check all mandatory ops */ if (!sof_ops(sdev) || !sof_ops(sdev)->probe || !sof_ops(sdev)->run || !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(sdev)->fw_ready) { + !sof_ops(sdev)->ipc_msg_data) { dev_err(dev, "error: missing mandatory ops\n"); return -EINVAL; } @@ -434,7 +446,7 @@ int snd_sof_device_remove(struct device *dev) snd_sof_machine_unregister(sdev, pdata); if (sdev->fw_state > SOF_FW_BOOT_NOT_STARTED) { - snd_sof_free_trace(sdev); + sof_fw_trace_free(sdev); ret = snd_sof_dsp_power_down_notify(sdev); if (ret < 0) dev_warn(dev, "error: %d failed to prepare DSP for device removal", diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c index 7b1139961a99..cf1271eb29b2 100644 --- a/sound/soc/sof/debug.c +++ b/sound/soc/sof/debug.c @@ -229,14 +229,13 @@ static int memory_info_update(struct snd_sof_dev *sdev, char *buf, size_t buff_s if (!reply) return -ENOMEM; - 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, "error: enabling device failed: %d\n", ret); goto error; } - ret = sof_ipc_tx_message(sdev->ipc, msg.cmd, &msg, msg.size, reply, SOF_IPC_MSG_MAX_SIZE); + ret = sof_ipc_tx_message(sdev->ipc, &msg, msg.size, reply, SOF_IPC_MSG_MAX_SIZE); pm_runtime_mark_last_busy(sdev->dev); pm_runtime_put_autosuspend(sdev->dev); if (ret < 0 || reply->rhdr.error < 0) { @@ -331,7 +330,7 @@ EXPORT_SYMBOL_GPL(snd_sof_dbg_memory_info_init); int snd_sof_dbg_init(struct snd_sof_dev *sdev) { - const struct snd_sof_dsp_ops *ops = sof_ops(sdev); + struct snd_sof_dsp_ops *ops = sof_ops(sdev); const struct snd_sof_debugfs_map *map; int i; int err; @@ -444,6 +443,6 @@ void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev) snd_sof_ipc_dump(sdev); snd_sof_dsp_dbg_dump(sdev, "Firmware exception", SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX); - snd_sof_trace_notify_for_error(sdev); + sof_fw_trace_fw_crashed(sdev); } EXPORT_SYMBOL(snd_sof_handle_fw_exception); diff --git a/sound/soc/sof/imx/imx8.c b/sound/soc/sof/imx/imx8.c index 825bd2b9b7a1..2844d9a8040a 100644 --- a/sound/soc/sof/imx/imx8.c +++ b/sound/soc/sof/imx/imx8.c @@ -487,7 +487,7 @@ static int imx8_dsp_set_power_state(struct snd_sof_dev *sdev, } /* i.MX8 ops */ -static const struct snd_sof_dsp_ops sof_imx8_ops = { +static struct snd_sof_dsp_ops sof_imx8_ops = { /* probe and remove */ .probe = imx8_probe, .remove = imx8_remove, @@ -504,16 +504,14 @@ static const struct snd_sof_dsp_ops sof_imx8_ops = { /* ipc */ .send_msg = imx8_send_msg, - .fw_ready = sof_fw_ready, .get_mailbox_offset = imx8_get_mailbox_offset, .get_window_offset = imx8_get_window_offset, .ipc_msg_data = sof_ipc_msg_data, .set_stream_data_offset = sof_set_stream_data_offset, - /* module loading */ - .load_module = snd_sof_parse_module_memcpy, .get_bar_index = imx8_get_bar_index, + /* firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy, @@ -550,7 +548,7 @@ static const struct snd_sof_dsp_ops sof_imx8_ops = { }; /* i.MX8X ops */ -static const struct snd_sof_dsp_ops sof_imx8x_ops = { +static struct snd_sof_dsp_ops sof_imx8x_ops = { /* probe and remove */ .probe = imx8_probe, .remove = imx8_remove, @@ -567,16 +565,14 @@ static const struct snd_sof_dsp_ops sof_imx8x_ops = { /* ipc */ .send_msg = imx8_send_msg, - .fw_ready = sof_fw_ready, .get_mailbox_offset = imx8_get_mailbox_offset, .get_window_offset = imx8_get_window_offset, .ipc_msg_data = sof_ipc_msg_data, .set_stream_data_offset = sof_set_stream_data_offset, - /* module loading */ - .load_module = snd_sof_parse_module_memcpy, .get_bar_index = imx8_get_bar_index, + /* firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy, @@ -613,17 +609,33 @@ static const struct snd_sof_dsp_ops sof_imx8x_ops = { }; static struct sof_dev_desc sof_of_imx8qxp_desc = { - .default_fw_path = "imx/sof", - .default_tplg_path = "imx/sof-tplg", - .default_fw_filename = "sof-imx8x.ri", + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "imx/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "imx/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-imx8x.ri", + }, .nocodec_tplg_filename = "sof-imx8-nocodec.tplg", .ops = &sof_imx8x_ops, }; static struct sof_dev_desc sof_of_imx8qm_desc = { - .default_fw_path = "imx/sof", - .default_tplg_path = "imx/sof-tplg", - .default_fw_filename = "sof-imx8.ri", + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "imx/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "imx/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-imx8.ri", + }, .nocodec_tplg_filename = "sof-imx8-nocodec.tplg", .ops = &sof_imx8_ops, }; diff --git a/sound/soc/sof/imx/imx8m.c b/sound/soc/sof/imx/imx8m.c index 803d6be6b4fb..1243f8a6141e 100644 --- a/sound/soc/sof/imx/imx8m.c +++ b/sound/soc/sof/imx/imx8m.c @@ -412,7 +412,7 @@ static int imx8m_dsp_suspend(struct snd_sof_dev *sdev, unsigned int target_state } /* i.MX8 ops */ -static const struct snd_sof_dsp_ops sof_imx8m_ops = { +static struct snd_sof_dsp_ops sof_imx8m_ops = { /* probe and remove */ .probe = imx8m_probe, .remove = imx8m_remove, @@ -430,16 +430,14 @@ static const struct snd_sof_dsp_ops sof_imx8m_ops = { /* ipc */ .send_msg = imx8m_send_msg, - .fw_ready = sof_fw_ready, .get_mailbox_offset = imx8m_get_mailbox_offset, .get_window_offset = imx8m_get_window_offset, .ipc_msg_data = sof_ipc_msg_data, .set_stream_data_offset = sof_set_stream_data_offset, - /* module loading */ - .load_module = snd_sof_parse_module_memcpy, .get_bar_index = imx8m_get_bar_index, + /* firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy, @@ -473,9 +471,17 @@ static const struct snd_sof_dsp_ops sof_imx8m_ops = { }; static struct sof_dev_desc sof_of_imx8mp_desc = { - .default_fw_path = "imx/sof", - .default_tplg_path = "imx/sof-tplg", - .default_fw_filename = "sof-imx8m.ri", + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "imx/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "imx/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-imx8m.ri", + }, .nocodec_tplg_filename = "sof-imx8-nocodec.tplg", .ops = &sof_imx8m_ops, }; diff --git a/sound/soc/sof/intel/Kconfig b/sound/soc/sof/intel/Kconfig index 172419392b33..0def2aa5581d 100644 --- a/sound/soc/sof/intel/Kconfig +++ b/sound/soc/sof/intel/Kconfig @@ -260,7 +260,7 @@ config SND_SOC_SOF_HDA 'select' statements at a higher level. config SND_SOC_SOF_HDA_PROBES - bool + tristate select SND_SOC_SOF_DEBUG_PROBES help The option enables the data probing for Intel(R) Skylake and newer diff --git a/sound/soc/sof/intel/Makefile b/sound/soc/sof/intel/Makefile index 1f473d4d8416..b9d51dc39ffa 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 + apl.o cnl.o tgl.o icl.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 diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c index 6721c8f95161..0cea280a6d2d 100644 --- a/sound/soc/sof/intel/apl.c +++ b/sound/soc/sof/intel/apl.c @@ -15,6 +15,8 @@ * Hardware interface for audio DSP on Apollolake and GeminiLake */ +#include <sound/sof/ext_manifest4.h> +#include "../ipc4-priv.h" #include "../sof-priv.h" #include "hda.h" #include "../sof-audio.h" @@ -26,108 +28,62 @@ static const struct snd_sof_debugfs_map apl_dsp_debugfs[] = { }; /* apollolake ops */ -const struct snd_sof_dsp_ops sof_apl_ops = { +struct snd_sof_dsp_ops sof_apl_ops; +EXPORT_SYMBOL_NS(sof_apl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); + +int sof_apl_ops_init(struct snd_sof_dev *sdev) +{ + /* common defaults */ + memcpy(&sof_apl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops)); + /* probe/remove/shutdown */ - .probe = hda_dsp_probe, - .remove = hda_dsp_remove, - .shutdown = hda_dsp_shutdown, - - /* Register IO */ - .write = sof_io_write, - .read = sof_io_read, - .write64 = sof_io_write64, - .read64 = sof_io_read64, - - /* Block IO */ - .block_read = sof_block_read, - .block_write = sof_block_write, - - /* Mailbox IO */ - .mailbox_read = sof_mailbox_read, - .mailbox_write = sof_mailbox_write, - - /* doorbell */ - .irq_thread = hda_dsp_ipc_irq_thread, - - /* ipc */ - .send_msg = hda_dsp_ipc_send_msg, - .fw_ready = sof_fw_ready, - .get_mailbox_offset = hda_dsp_ipc_get_mailbox_offset, - .get_window_offset = hda_dsp_ipc_get_window_offset, - - .ipc_msg_data = hda_ipc_msg_data, - .set_stream_data_offset = hda_set_stream_data_offset, - - /* machine driver */ - .machine_select = hda_machine_select, - .machine_register = sof_machine_register, - .machine_unregister = sof_machine_unregister, - .set_mach_params = hda_set_mach_params, + sof_apl_ops.shutdown = hda_dsp_shutdown; + + if (sdev->pdata->ipc_type == SOF_IPC) { + /* doorbell */ + sof_apl_ops.irq_thread = hda_dsp_ipc_irq_thread; + + /* ipc */ + sof_apl_ops.send_msg = hda_dsp_ipc_send_msg; + } + + if (sdev->pdata->ipc_type == SOF_INTEL_IPC4) { + struct sof_ipc4_fw_data *ipc4_data; + + sdev->private = devm_kzalloc(sdev->dev, sizeof(*ipc4_data), GFP_KERNEL); + if (!sdev->private) + return -ENOMEM; + + ipc4_data = sdev->private; + ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; + + /* doorbell */ + sof_apl_ops.irq_thread = hda_dsp_ipc4_irq_thread; + + /* ipc */ + sof_apl_ops.send_msg = hda_dsp_ipc4_send_msg; + } + + /* set DAI driver ops */ + hda_set_dai_drv_ops(sdev, &sof_apl_ops); /* debug */ - .debug_map = apl_dsp_debugfs, - .debug_map_count = ARRAY_SIZE(apl_dsp_debugfs), - .dbg_dump = hda_dsp_dump, - .ipc_dump = hda_ipc_dump, - .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, - - /* stream callbacks */ - .pcm_open = hda_dsp_pcm_open, - .pcm_close = hda_dsp_pcm_close, - .pcm_hw_params = hda_dsp_pcm_hw_params, - .pcm_hw_free = hda_dsp_stream_hw_free, - .pcm_trigger = hda_dsp_pcm_trigger, - .pcm_pointer = hda_dsp_pcm_pointer, - .pcm_ack = hda_dsp_pcm_ack, - - /* firmware loading */ - .load_firmware = snd_sof_load_firmware_raw, + sof_apl_ops.debug_map = apl_dsp_debugfs; + sof_apl_ops.debug_map_count = ARRAY_SIZE(apl_dsp_debugfs); + sof_apl_ops.ipc_dump = hda_ipc_dump; /* firmware run */ - .run = hda_dsp_cl_boot_firmware, + sof_apl_ops.run = hda_dsp_cl_boot_firmware; /* pre/post fw run */ - .pre_fw_run = hda_dsp_pre_fw_run, - .post_fw_run = hda_dsp_post_fw_run, - - /* parse platform specific extended manifest */ - .parse_platform_ext_manifest = hda_dsp_ext_man_get_cavs_config_data, + sof_apl_ops.post_fw_run = hda_dsp_post_fw_run; /* dsp core get/put */ - .core_get = hda_dsp_core_get, - - /* trace callback */ - .trace_init = hda_dsp_trace_init, - .trace_release = hda_dsp_trace_release, - .trace_trigger = hda_dsp_trace_trigger, - - /* client ops */ - .register_ipc_clients = hda_register_clients, - .unregister_ipc_clients = hda_unregister_clients, - - /* DAI drivers */ - .drv = skl_dai, - .num_drv = SOF_SKL_NUM_DAIS, - - /* PM */ - .suspend = hda_dsp_suspend, - .resume = hda_dsp_resume, - .runtime_suspend = hda_dsp_runtime_suspend, - .runtime_resume = hda_dsp_runtime_resume, - .runtime_idle = hda_dsp_runtime_idle, - .set_hw_params_upon_resume = hda_dsp_set_hw_params_upon_resume, - .set_power_state = hda_dsp_set_power_state, - - /* ALSA HW info flags */ - .hw_info = SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_PAUSE | - SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, - - .dsp_arch_ops = &sof_xtensa_arch_ops, + sof_apl_ops.core_get = hda_dsp_core_get; + + return 0; }; -EXPORT_SYMBOL_NS(sof_apl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); +EXPORT_SYMBOL_NS(sof_apl_ops_init, SND_SOC_SOF_INTEL_HDA_COMMON); const struct sof_intel_dsp_desc apl_chip_info = { /* Apollolake */ @@ -139,9 +95,12 @@ const struct sof_intel_dsp_desc apl_chip_info = { .ipc_ack = HDA_DSP_REG_HIPCIE, .ipc_ack_mask = HDA_DSP_REG_HIPCIE_DONE, .ipc_ctl = HDA_DSP_REG_HIPCCTL, + .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS, .rom_init_timeout = 150, .ssp_count = APL_SSP_COUNT, .ssp_base_offset = APL_SSP_BASE_OFFSET, .quirks = SOF_INTEL_PROCEN_FMT_QUIRK, + .check_ipc_irq = hda_dsp_check_ipc_irq, + .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/bdw.c b/sound/soc/sof/intel/bdw.c index fb9682b2fe32..26df780c702e 100644 --- a/sound/soc/sof/intel/bdw.c +++ b/sound/soc/sof/intel/bdw.c @@ -567,7 +567,7 @@ static struct snd_soc_dai_driver bdw_dai[] = { }; /* broadwell ops */ -static const struct snd_sof_dsp_ops sof_bdw_ops = { +static struct snd_sof_dsp_ops sof_bdw_ops = { /*Device init */ .probe = bdw_probe, @@ -591,7 +591,6 @@ static const struct snd_sof_dsp_ops sof_bdw_ops = { /* ipc */ .send_msg = bdw_send_msg, - .fw_ready = sof_fw_ready, .get_mailbox_offset = bdw_get_mailbox_offset, .get_window_offset = bdw_get_window_offset, @@ -614,9 +613,6 @@ static const struct snd_sof_dsp_ops sof_bdw_ops = { .pcm_open = sof_stream_pcm_open, .pcm_close = sof_stream_pcm_close, - /* Module loading */ - .load_module = snd_sof_parse_module_memcpy, - /*Firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy, @@ -637,6 +633,7 @@ static const struct snd_sof_dsp_ops sof_bdw_ops = { static const struct sof_intel_dsp_desc bdw_chip_info = { .cores_num = 1, .host_managed_cores_mask = 1, + .hw_ip_version = SOF_INTEL_BROADWELL, }; static const struct sof_dev_desc sof_acpi_broadwell_desc = { @@ -646,9 +643,17 @@ static const struct sof_dev_desc sof_acpi_broadwell_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = 0, .chip_info = &bdw_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-bdw.ri", + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-bdw.ri", + }, .nocodec_tplg_filename = "sof-bdw-nocodec.tplg", .ops = &sof_bdw_ops, }; diff --git a/sound/soc/sof/intel/byt.c b/sound/soc/sof/intel/byt.c index bb84a4aa587a..4ed8381eceda 100644 --- a/sound/soc/sof/intel/byt.c +++ b/sound/soc/sof/intel/byt.c @@ -216,7 +216,7 @@ irq: } /* baytrail ops */ -static const struct snd_sof_dsp_ops sof_byt_ops = { +static struct snd_sof_dsp_ops sof_byt_ops = { /* device init */ .probe = byt_acpi_probe, .remove = byt_remove, @@ -245,7 +245,6 @@ static const struct snd_sof_dsp_ops sof_byt_ops = { /* ipc */ .send_msg = atom_send_msg, - .fw_ready = sof_fw_ready, .get_mailbox_offset = atom_get_mailbox_offset, .get_window_offset = atom_get_window_offset, @@ -268,9 +267,6 @@ static const struct snd_sof_dsp_ops sof_byt_ops = { .pcm_open = sof_stream_pcm_open, .pcm_close = sof_stream_pcm_close, - /* module loading */ - .load_module = snd_sof_parse_module_memcpy, - /*Firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy, @@ -295,10 +291,11 @@ static const struct snd_sof_dsp_ops sof_byt_ops = { static const struct sof_intel_dsp_desc byt_chip_info = { .cores_num = 1, .host_managed_cores_mask = 1, + .hw_ip_version = SOF_INTEL_BAYTRAIL, }; /* cherrytrail and braswell ops */ -static const struct snd_sof_dsp_ops sof_cht_ops = { +static struct snd_sof_dsp_ops sof_cht_ops = { /* device init */ .probe = byt_acpi_probe, .remove = byt_remove, @@ -327,7 +324,6 @@ static const struct snd_sof_dsp_ops sof_cht_ops = { /* ipc */ .send_msg = atom_send_msg, - .fw_ready = sof_fw_ready, .get_mailbox_offset = atom_get_mailbox_offset, .get_window_offset = atom_get_window_offset, @@ -350,9 +346,6 @@ static const struct snd_sof_dsp_ops sof_cht_ops = { .pcm_open = sof_stream_pcm_open, .pcm_close = sof_stream_pcm_close, - /* module loading */ - .load_module = snd_sof_parse_module_memcpy, - /*Firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy, @@ -378,6 +371,7 @@ static const struct snd_sof_dsp_ops sof_cht_ops = { static const struct sof_intel_dsp_desc cht_chip_info = { .cores_num = 1, .host_managed_cores_mask = 1, + .hw_ip_version = SOF_INTEL_BAYTRAIL, }; /* BYTCR uses different IRQ index */ @@ -388,9 +382,17 @@ static const struct sof_dev_desc sof_acpi_baytrailcr_desc = { .resindex_imr_base = 2, .irqindex_host_ipc = 0, .chip_info = &byt_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-byt.ri", + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-byt.ri", + }, .nocodec_tplg_filename = "sof-byt-nocodec.tplg", .ops = &sof_byt_ops, }; @@ -402,9 +404,17 @@ static const struct sof_dev_desc sof_acpi_baytrail_desc = { .resindex_imr_base = 2, .irqindex_host_ipc = 5, .chip_info = &byt_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-byt.ri", + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-byt.ri", + }, .nocodec_tplg_filename = "sof-byt-nocodec.tplg", .ops = &sof_byt_ops, }; @@ -416,9 +426,17 @@ static const struct sof_dev_desc sof_acpi_cherrytrail_desc = { .resindex_imr_base = 2, .irqindex_host_ipc = 5, .chip_info = &cht_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-cht.ri", + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-cht.ri", + }, .nocodec_tplg_filename = "sof-cht-nocodec.tplg", .ops = &sof_cht_ops, }; diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c index 6a96470b967f..cd6e5f8a5eb4 100644 --- a/sound/soc/sof/intel/cnl.c +++ b/sound/soc/sof/intel/cnl.c @@ -15,6 +15,9 @@ * Hardware interface for audio DSP on Cannonlake. */ +#include <sound/sof/ext_manifest4.h> +#include <sound/sof/ipc4/header.h> +#include "../ipc4-priv.h" #include "../ops.h" #include "hda.h" #include "hda-ipc.h" @@ -29,6 +32,68 @@ static const struct snd_sof_debugfs_map cnl_dsp_debugfs[] = { static void cnl_ipc_host_done(struct snd_sof_dev *sdev); static void cnl_ipc_dsp_done(struct snd_sof_dev *sdev); +irqreturn_t cnl_ipc4_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, hipctdr; + + hipcida = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDA); + if (hipcida & CNL_DSP_REG_HIPCIDA_DONE) { + /* DSP received the message */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, + CNL_DSP_REG_HIPCCTL, + CNL_DSP_REG_HIPCCTL_DONE, 0); + cnl_ipc_dsp_done(sdev); + + ipc_irq = true; + } + + hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCTDR); + if (hipctdr & CNL_DSP_REG_HIPCTDR_BUSY) { + /* Message from DSP (reply or notification) */ + u32 hipctdd = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + CNL_DSP_REG_HIPCTDD); + u32 primary = hipctdr & CNL_DSP_REG_HIPCTDR_MSG_MASK; + u32 extension = hipctdd & CNL_DSP_REG_HIPCTDD_MSG_MASK; + + if (primary & SOF_IPC4_MSG_DIR_MASK) { + /* Reply received */ + 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 { + /* Notification received */ + notification_data.primary = primary; + notification_data.extension = extension; + + sdev->ipc->msg.rx_data = ¬ification_data; + snd_sof_ipc_msgs_rx(sdev); + sdev->ipc->msg.rx_data = NULL; + } + + /* Let DSP know that we have finished processing the message */ + cnl_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; +} + irqreturn_t cnl_ipc_irq_thread(int irq, void *context) { struct snd_sof_dev *sdev = context; @@ -176,6 +241,22 @@ static bool cnl_compact_ipc_compress(struct snd_sof_ipc_msg *msg, return false; } +int cnl_ipc4_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, CNL_DSP_REG_HIPCIDD, msg_data->extension); + snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDR, + msg_data->primary | CNL_DSP_REG_HIPCIDR_BUSY); + + return 0; +} + int cnl_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) { struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; @@ -244,108 +325,63 @@ void cnl_ipc_dump(struct snd_sof_dev *sdev) } /* cannonlake ops */ -const struct snd_sof_dsp_ops sof_cnl_ops = { +struct snd_sof_dsp_ops sof_cnl_ops; +EXPORT_SYMBOL_NS(sof_cnl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); + +int sof_cnl_ops_init(struct snd_sof_dev *sdev) +{ + /* common defaults */ + memcpy(&sof_cnl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops)); + /* probe/remove/shutdown */ - .probe = hda_dsp_probe, - .remove = hda_dsp_remove, - .shutdown = hda_dsp_shutdown, + sof_cnl_ops.shutdown = hda_dsp_shutdown; + + /* ipc */ + if (sdev->pdata->ipc_type == SOF_IPC) { + /* doorbell */ + sof_cnl_ops.irq_thread = cnl_ipc_irq_thread; - /* Register IO */ - .write = sof_io_write, - .read = sof_io_read, - .write64 = sof_io_write64, - .read64 = sof_io_read64, + /* ipc */ + sof_cnl_ops.send_msg = cnl_ipc_send_msg; + } - /* Block IO */ - .block_read = sof_block_read, - .block_write = sof_block_write, + if (sdev->pdata->ipc_type == SOF_INTEL_IPC4) { + struct sof_ipc4_fw_data *ipc4_data; - /* Mailbox IO */ - .mailbox_read = sof_mailbox_read, - .mailbox_write = sof_mailbox_write, + sdev->private = devm_kzalloc(sdev->dev, sizeof(*ipc4_data), GFP_KERNEL); + if (!sdev->private) + return -ENOMEM; - /* doorbell */ - .irq_thread = cnl_ipc_irq_thread, + ipc4_data = sdev->private; + ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; - /* ipc */ - .send_msg = cnl_ipc_send_msg, - .fw_ready = sof_fw_ready, - .get_mailbox_offset = hda_dsp_ipc_get_mailbox_offset, - .get_window_offset = hda_dsp_ipc_get_window_offset, + /* doorbell */ + sof_cnl_ops.irq_thread = cnl_ipc4_irq_thread; - .ipc_msg_data = hda_ipc_msg_data, - .set_stream_data_offset = hda_set_stream_data_offset, + /* ipc */ + sof_cnl_ops.send_msg = cnl_ipc4_send_msg; + } - /* machine driver */ - .machine_select = hda_machine_select, - .machine_register = sof_machine_register, - .machine_unregister = sof_machine_unregister, - .set_mach_params = hda_set_mach_params, + /* set DAI driver ops */ + hda_set_dai_drv_ops(sdev, &sof_cnl_ops); /* debug */ - .debug_map = cnl_dsp_debugfs, - .debug_map_count = ARRAY_SIZE(cnl_dsp_debugfs), - .dbg_dump = hda_dsp_dump, - .ipc_dump = cnl_ipc_dump, - .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, - - /* stream callbacks */ - .pcm_open = hda_dsp_pcm_open, - .pcm_close = hda_dsp_pcm_close, - .pcm_hw_params = hda_dsp_pcm_hw_params, - .pcm_hw_free = hda_dsp_stream_hw_free, - .pcm_trigger = hda_dsp_pcm_trigger, - .pcm_pointer = hda_dsp_pcm_pointer, - .pcm_ack = hda_dsp_pcm_ack, - - /* firmware loading */ - .load_firmware = snd_sof_load_firmware_raw, + sof_cnl_ops.debug_map = cnl_dsp_debugfs; + sof_cnl_ops.debug_map_count = ARRAY_SIZE(cnl_dsp_debugfs); + sof_cnl_ops.ipc_dump = cnl_ipc_dump; /* pre/post fw run */ - .pre_fw_run = hda_dsp_pre_fw_run, - .post_fw_run = hda_dsp_post_fw_run, + sof_cnl_ops.post_fw_run = hda_dsp_post_fw_run; - /* parse platform specific extended manifest */ - .parse_platform_ext_manifest = hda_dsp_ext_man_get_cavs_config_data, + /* firmware run */ + sof_cnl_ops.run = hda_dsp_cl_boot_firmware; /* dsp core get/put */ - .core_get = hda_dsp_core_get, + sof_cnl_ops.core_get = hda_dsp_core_get; - /* firmware run */ - .run = hda_dsp_cl_boot_firmware, - - /* trace callback */ - .trace_init = hda_dsp_trace_init, - .trace_release = hda_dsp_trace_release, - .trace_trigger = hda_dsp_trace_trigger, - - /* client ops */ - .register_ipc_clients = hda_register_clients, - .unregister_ipc_clients = hda_unregister_clients, - - /* DAI drivers */ - .drv = skl_dai, - .num_drv = SOF_SKL_NUM_DAIS, - - /* PM */ - .suspend = hda_dsp_suspend, - .resume = hda_dsp_resume, - .runtime_suspend = hda_dsp_runtime_suspend, - .runtime_resume = hda_dsp_runtime_resume, - .runtime_idle = hda_dsp_runtime_idle, - .set_hw_params_upon_resume = hda_dsp_set_hw_params_upon_resume, - .set_power_state = hda_dsp_set_power_state, - - /* ALSA HW info flags */ - .hw_info = SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_PAUSE | - SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, - - .dsp_arch_ops = &sof_xtensa_arch_ops, + return 0; }; -EXPORT_SYMBOL_NS(sof_cnl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); +EXPORT_SYMBOL_NS(sof_cnl_ops_init, SND_SOC_SOF_INTEL_HDA_COMMON); const struct sof_intel_dsp_desc cnl_chip_info = { /* Cannonlake */ @@ -357,12 +393,15 @@ const struct sof_intel_dsp_desc cnl_chip_info = { .ipc_ack = CNL_DSP_REG_HIPCIDA, .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE, .ipc_ctl = CNL_DSP_REG_HIPCCTL, + .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS, .rom_init_timeout = 300, .ssp_count = CNL_SSP_COUNT, .ssp_base_offset = CNL_SSP_BASE_OFFSET, .sdw_shim_base = SDW_SHIM_BASE, .sdw_alh_base = SDW_ALH_BASE, .check_sdw_irq = hda_common_check_sdw_irq, + .check_ipc_irq = hda_dsp_check_ipc_irq, + .hw_ip_version = SOF_INTEL_CAVS_1_8, }; EXPORT_SYMBOL_NS(cnl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); @@ -383,11 +422,14 @@ const struct sof_intel_dsp_desc jsl_chip_info = { .ipc_ack = CNL_DSP_REG_HIPCIDA, .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE, .ipc_ctl = CNL_DSP_REG_HIPCCTL, + .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS, .rom_init_timeout = 300, .ssp_count = ICL_SSP_COUNT, .ssp_base_offset = CNL_SSP_BASE_OFFSET, .sdw_shim_base = SDW_SHIM_BASE, .sdw_alh_base = SDW_ALH_BASE, .check_sdw_irq = hda_common_check_sdw_irq, + .check_ipc_irq = hda_dsp_check_ipc_irq, + .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-common-ops.c b/sound/soc/sof/intel/hda-common-ops.c new file mode 100644 index 000000000000..b2326396c870 --- /dev/null +++ b/sound/soc/sof/intel/hda-common-ops.c @@ -0,0 +1,106 @@ +// 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. +// + +/* + * common ops for SKL+ HDAudio platforms + */ + +#include "../sof-priv.h" +#include "hda.h" +#include "../sof-audio.h" + +struct snd_sof_dsp_ops sof_hda_common_ops = { + /* probe/remove/shutdown */ + .probe = hda_dsp_probe, + .remove = hda_dsp_remove, + + /* Register IO */ + .write = sof_io_write, + .read = sof_io_read, + .write64 = sof_io_write64, + .read64 = sof_io_read64, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* Mailbox IO */ + .mailbox_read = sof_mailbox_read, + .mailbox_write = sof_mailbox_write, + + /* ipc */ + .get_mailbox_offset = hda_dsp_ipc_get_mailbox_offset, + .get_window_offset = hda_dsp_ipc_get_window_offset, + + .ipc_msg_data = hda_ipc_msg_data, + .set_stream_data_offset = hda_set_stream_data_offset, + + /* machine driver */ + .machine_select = hda_machine_select, + .machine_register = sof_machine_register, + .machine_unregister = sof_machine_unregister, + .set_mach_params = hda_set_mach_params, + + /* debug */ + .dbg_dump = hda_dsp_dump, + .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, + + /* stream callbacks */ + .pcm_open = hda_dsp_pcm_open, + .pcm_close = hda_dsp_pcm_close, + .pcm_hw_params = hda_dsp_pcm_hw_params, + .pcm_hw_free = hda_dsp_stream_hw_free, + .pcm_trigger = hda_dsp_pcm_trigger, + .pcm_pointer = hda_dsp_pcm_pointer, + .pcm_ack = hda_dsp_pcm_ack, + + /* firmware loading */ + .load_firmware = snd_sof_load_firmware_raw, + + /* pre/post fw run */ + .pre_fw_run = hda_dsp_pre_fw_run, + + /* firmware run */ + .run = hda_dsp_cl_boot_firmware, + + /* parse platform specific extended manifest */ + .parse_platform_ext_manifest = hda_dsp_ext_man_get_cavs_config_data, + + /* dsp core get/put */ + + /* trace callback */ + .trace_init = hda_dsp_trace_init, + .trace_release = hda_dsp_trace_release, + .trace_trigger = hda_dsp_trace_trigger, + + /* client ops */ + .register_ipc_clients = hda_register_clients, + .unregister_ipc_clients = hda_unregister_clients, + + /* DAI drivers */ + .drv = skl_dai, + .num_drv = SOF_SKL_NUM_DAIS, + + /* PM */ + .suspend = hda_dsp_suspend, + .resume = hda_dsp_resume, + .runtime_suspend = hda_dsp_runtime_suspend, + .runtime_resume = hda_dsp_runtime_resume, + .runtime_idle = hda_dsp_runtime_idle, + .set_hw_params_upon_resume = hda_dsp_set_hw_params_upon_resume, + .set_power_state = hda_dsp_set_power_state, + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, + + .dsp_arch_ops = &sof_xtensa_arch_ops, +}; diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index f9cb9f1f0237..9823230d2ef4 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -50,8 +50,8 @@ static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd, } static struct hdac_ext_stream * - hda_link_stream_assign(struct hdac_bus *bus, - struct snd_pcm_substream *substream) +hda_link_stream_assign(struct hdac_bus *bus, + struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct sof_intel_hda_stream *hda_stream; @@ -128,6 +128,40 @@ static struct hdac_ext_stream * return res; } +static int hda_link_dma_cleanup(struct snd_pcm_substream *substream, + struct hdac_stream *hstream, + struct snd_soc_dai *cpu_dai, + struct snd_soc_dai *codec_dai, + bool trigger_suspend_stop) +{ + struct hdac_ext_stream *hext_stream = snd_soc_dai_get_dma_data(cpu_dai, substream); + struct hdac_bus *bus = hstream->bus; + struct sof_intel_hda_stream *hda_stream; + struct hdac_ext_link *link; + int stream_tag; + + link = snd_hdac_ext_bus_get_link(bus, codec_dai->component->name); + if (!link) + return -EINVAL; + + if (trigger_suspend_stop) + snd_hdac_ext_link_stream_clear(hext_stream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + stream_tag = hdac_stream(hext_stream)->stream_tag; + snd_hdac_ext_link_clear_stream_id(link, stream_tag); + } + snd_soc_dai_set_dma_data(cpu_dai, substream, NULL); + snd_hdac_ext_stream_release(hext_stream, HDAC_EXT_STREAM_TYPE_LINK); + hext_stream->link_prepared = 0; + + /* free the host DMA channel reserved by hostless streams */ + hda_stream = hstream_to_sof_hda_stream(hext_stream); + hda_stream->host_reserved = 0; + + return 0; +} + static int hda_link_dma_params(struct hdac_ext_stream *hext_stream, struct hda_pipe_params *params) { @@ -162,61 +196,28 @@ static int hda_link_dma_params(struct hdac_ext_stream *hext_stream, return 0; } -static int hda_link_dai_widget_update(struct sof_intel_hda_stream *hda_stream, - struct snd_soc_dapm_widget *w, - int channel, bool widget_setup) -{ - struct snd_sof_dai_config_data data; - - data.dai_data = channel; - - /* set up/free DAI widget and send DAI_CONFIG IPC */ - if (widget_setup) - return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_2_STEP_STOP, &data); - - return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE, &data); -} - -static int hda_link_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) +static int hda_link_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) { struct hdac_stream *hstream = substream->runtime->private_data; - struct hdac_bus *bus = hstream->bus; struct hdac_ext_stream *hext_stream; struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); - struct sof_intel_hda_stream *hda_stream; struct hda_pipe_params p_params = {0}; - struct snd_soc_dapm_widget *w; + struct hdac_bus *bus = hstream->bus; struct hdac_ext_link *link; - int stream_tag; - int ret; /* get stored dma data if resuming from system suspend */ - hext_stream = snd_soc_dai_get_dma_data(dai, substream); + hext_stream = snd_soc_dai_get_dma_data(cpu_dai, substream); if (!hext_stream) { hext_stream = hda_link_stream_assign(bus, substream); if (!hext_stream) return -EBUSY; - snd_soc_dai_set_dma_data(dai, substream, (void *)hext_stream); + snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)hext_stream); } - stream_tag = hdac_stream(hext_stream)->stream_tag; - - hda_stream = hstream_to_sof_hda_stream(hext_stream); - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - w = dai->playback_widget; - else - w = dai->capture_widget; - - /* set up the DAI widget and send the DAI_CONFIG with the new tag */ - ret = hda_link_dai_widget_update(hda_stream, w, stream_tag - 1, true); - if (ret < 0) - return ret; - link = snd_hdac_ext_bus_get_link(bus, codec_dai->component->name); if (!link) return -EINVAL; @@ -239,26 +240,118 @@ static int hda_link_hw_params(struct snd_pcm_substream *substream, return hda_link_dma_params(hext_stream, &p_params); } -static int hda_link_pcm_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static int hda_link_dma_prepare(struct snd_pcm_substream *substream) { - 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 = asoc_substream_to_rtd(substream); int stream = substream->stream; - if (hext_stream->link_prepared) + return hda_link_dma_hw_params(substream, &rtd->dpcm[stream].hw_params); +} + +static int hda_link_dma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct hdac_stream *hstream = substream->runtime->private_data; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + 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; + + 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: + ret = hda_link_dma_cleanup(substream, hstream, cpu_dai, codec_dai, true); + if (ret < 0) + return ret; + + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + snd_hdac_ext_link_stream_clear(hext_stream); + + break; + default: + return -EINVAL; + } + return 0; +} + +static int hda_link_dma_hw_free(struct snd_pcm_substream *substream) +{ + struct hdac_stream *hstream = substream->runtime->private_data; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct hdac_ext_stream *hext_stream; + + hext_stream = snd_soc_dai_get_dma_data(cpu_dai, substream); + if (!hext_stream) + return 0; + + return hda_link_dma_cleanup(substream, hstream, cpu_dai, codec_dai, false); +} + +static int hda_dai_widget_update(struct snd_soc_dapm_widget *w, + int channel, bool widget_setup) +{ + struct snd_sof_dai_config_data data; + + data.dai_data = channel; + + /* set up/free DAI widget and send DAI_CONFIG IPC */ + if (widget_setup) + return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_2_STEP_STOP, &data); + + return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE, &data); +} + +static int hda_dai_hw_params_update(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct hdac_ext_stream *hext_stream; + struct snd_soc_dapm_widget *w; + int stream_tag; + + hext_stream = snd_soc_dai_get_dma_data(dai, substream); + if (!hext_stream) + return -EINVAL; + + stream_tag = hdac_stream(hext_stream)->stream_tag; + + w = snd_soc_dai_get_widget(dai, substream->stream); + + /* set up the DAI widget and send the DAI_CONFIG with the new tag */ + return hda_dai_widget_update(w, stream_tag - 1, true); +} + +static int hda_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct hdac_ext_stream *hext_stream = + snd_soc_dai_get_dma_data(dai, substream); + int ret; + + if (hext_stream && hext_stream->link_prepared) return 0; - dev_dbg(sdev->dev, "hda: prepare stream dir %d\n", substream->stream); + ret = hda_link_dma_hw_params(substream, params); + if (ret < 0) + return ret; - return hda_link_hw_params(substream, &rtd->dpcm[stream].hw_params, - dai); + return hda_dai_hw_params_update(substream, params, dai); } -static int hda_link_dai_config_pause_push_ipc(struct snd_soc_dapm_widget *w) + +static int hda_dai_config_pause_push_ipc(struct snd_soc_dapm_widget *w) { struct snd_sof_widget *swidget = w->dobj.private; struct snd_soc_component *component = swidget->scomp; @@ -276,132 +369,134 @@ static int hda_link_dai_config_pause_push_ipc(struct snd_soc_dapm_widget *w) return ret; } -static int hda_link_pcm_trigger(struct snd_pcm_substream *substream, - int cmd, struct snd_soc_dai *dai) +static int ipc3_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); - struct sof_intel_hda_stream *hda_stream; - struct snd_soc_pcm_runtime *rtd; - struct snd_soc_dapm_widget *w; - struct hdac_ext_link *link; - struct hdac_stream *hstream; - struct hdac_bus *bus; - int stream_tag; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + int stream = substream->stream; int ret; - hstream = substream->runtime->private_data; - bus = hstream->bus; - rtd = asoc_substream_to_rtd(substream); + if (hext_stream && hext_stream->link_prepared) + return 0; - link = snd_hdac_ext_bus_get_link(bus, asoc_rtd_to_codec(rtd, 0)->component->name); - if (!link) - return -EINVAL; + dev_dbg(sdev->dev, "%s: prepare stream dir %d\n", __func__, substream->stream); - hda_stream = hstream_to_sof_hda_stream(hext_stream); + ret = hda_link_dma_prepare(substream); + if (ret < 0) + return ret; + + return hda_dai_hw_params_update(substream, &rtd->dpcm[stream].hw_params, dai); +} - dev_dbg(dai->dev, "In %s cmd=%d\n", __func__, cmd); +static int hda_dai_hw_free_ipc(int stream, /* direction */ + struct snd_soc_dai *dai) +{ + struct snd_soc_dapm_widget *w; + + w = snd_soc_dai_get_widget(dai, stream); + + /* free the link DMA channel in the FW and the DAI widget */ + return hda_dai_widget_update(w, DMA_CHAN_INVALID, false); +} + +static int ipc3_hda_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct snd_soc_dapm_widget *w; + int ret; + + 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_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: - snd_hdac_ext_link_stream_clear(hext_stream); - /* * free DAI widget during stop/suspend to keep widget use_count's balanced. */ - ret = hda_link_dai_widget_update(hda_stream, w, DMA_CHAN_INVALID, false); + ret = hda_dai_hw_free_ipc(substream->stream, dai); if (ret < 0) return ret; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - stream_tag = hdac_stream(hext_stream)->stream_tag; - snd_hdac_ext_link_clear_stream_id(link, stream_tag); - } - - hext_stream->link_prepared = 0; break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - snd_hdac_ext_link_stream_clear(hext_stream); - - ret = hda_link_dai_config_pause_push_ipc(w); + ret = hda_dai_config_pause_push_ipc(w); if (ret < 0) return ret; break; + default: - return -EINVAL; + break; } return 0; } -static int hda_link_hw_free(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static int hda_dai_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { - unsigned int stream_tag; - struct sof_intel_hda_stream *hda_stream; - struct hdac_bus *bus; - struct hdac_ext_link *link; - struct hdac_stream *hstream; - struct snd_soc_pcm_runtime *rtd; - struct hdac_ext_stream *hext_stream; - struct snd_soc_dapm_widget *w; int ret; - hstream = substream->runtime->private_data; - bus = hstream->bus; - rtd = asoc_substream_to_rtd(substream); - hext_stream = snd_soc_dai_get_dma_data(dai, substream); + ret = hda_link_dma_hw_free(substream); + if (ret < 0) + return ret; - if (!hext_stream) { - dev_dbg(dai->dev, - "%s: hext_stream is not assigned\n", __func__); - return -EINVAL; - } + return hda_dai_hw_free_ipc(substream->stream, dai); +} - hda_stream = hstream_to_sof_hda_stream(hext_stream); +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, +}; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - w = dai->playback_widget; - else - w = dai->capture_widget; +static int hda_dai_suspend(struct hdac_bus *bus) +{ + struct snd_soc_pcm_runtime *rtd; + struct hdac_ext_stream *hext_stream; + struct hdac_stream *s; + int ret; - /* free the link DMA channel in the FW and the DAI widget */ - ret = hda_link_dai_widget_update(hda_stream, w, DMA_CHAN_INVALID, false); - if (ret < 0) - return ret; + /* set internal flag for BE */ + list_for_each_entry(s, &bus->stream_list, list) { - link = snd_hdac_ext_bus_get_link(bus, asoc_rtd_to_codec(rtd, 0)->component->name); - if (!link) - return -EINVAL; + hext_stream = stream_to_hdac_ext_stream(s); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - stream_tag = hdac_stream(hext_stream)->stream_tag; - snd_hdac_ext_link_clear_stream_id(link, stream_tag); + /* + * clear stream. This should already be taken care for running + * streams when the SUSPEND trigger is called. But paused + * streams do not get suspended, so this needs to be done + * explicitly during suspend. + */ + if (hext_stream->link_substream) { + struct snd_soc_dai *cpu_dai; + struct snd_soc_dai *codec_dai; + + rtd = asoc_substream_to_rtd(hext_stream->link_substream); + cpu_dai = asoc_rtd_to_cpu(rtd, 0); + codec_dai = asoc_rtd_to_codec(rtd, 0); + + ret = hda_link_dma_cleanup(hext_stream->link_substream, s, + cpu_dai, codec_dai, false); + if (ret < 0) + return ret; + + /* for consistency with TRIGGER_SUSPEND we free DAI resources */ + ret = hda_dai_hw_free_ipc(hdac_stream(hext_stream)->direction, cpu_dai); + if (ret < 0) + return ret; + } } - snd_soc_dai_set_dma_data(dai, substream, NULL); - snd_hdac_ext_stream_release(hext_stream, HDAC_EXT_STREAM_TYPE_LINK); - hext_stream->link_prepared = 0; - - /* free the host DMA channel reserved by hostless streams */ - hda_stream->host_reserved = 0; - return 0; } - -static const struct snd_soc_dai_ops hda_link_dai_ops = { - .hw_params = hda_link_hw_params, - .hw_free = hda_link_hw_free, - .trigger = hda_link_pcm_trigger, - .prepare = hda_link_pcm_prepare, -}; - #endif /* only one flag used so far to harden hw_params/hw_free/trigger/prepare */ @@ -414,10 +509,7 @@ static int ssp_dai_setup_or_free(struct snd_pcm_substream *substream, struct snd { struct snd_soc_dapm_widget *w; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - w = dai->playback_widget; - else - w = dai->capture_widget; + w = snd_soc_dai_get_widget(dai, substream->stream); if (setup) return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_NONE, NULL); @@ -478,8 +570,8 @@ static int ssp_dai_prepare(struct snd_pcm_substream *substream, return ssp_dai_setup(substream, dai, true); } -static int ssp_dai_trigger(struct snd_pcm_substream *substream, - int cmd, struct snd_soc_dai *dai) +static int ipc3_ssp_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) { if (cmd != SNDRV_PCM_TRIGGER_SUSPEND) return 0; @@ -507,15 +599,39 @@ static void ssp_dai_shutdown(struct snd_pcm_substream *substream, kfree(dma_data); } -static const struct snd_soc_dai_ops ssp_dai_ops = { +static const struct snd_soc_dai_ops ipc3_ssp_dai_ops = { .startup = ssp_dai_startup, .hw_params = ssp_dai_hw_params, .prepare = ssp_dai_prepare, - .trigger = ssp_dai_trigger, + .trigger = ipc3_ssp_dai_trigger, .hw_free = ssp_dai_hw_free, .shutdown = ssp_dai_shutdown, }; +void hda_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops) +{ + int i; + + switch (sdev->pdata->ipc_type) { + case SOF_IPC: + for (i = 0; i < ops->num_drv; i++) { + if (strstr(ops->drv[i].name, "SSP")) { + ops->drv[i].ops = &ipc3_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 = &ipc3_hda_dai_ops; +#endif + } + break; + default: + break; + } +} + /* * common dai driver for skl+ platforms. * some products who use this DAI array only physically have a subset of @@ -524,7 +640,6 @@ static const struct snd_soc_dai_ops ssp_dai_ops = { struct snd_soc_dai_driver skl_dai[] = { { .name = "SSP0 Pin", - .ops = &ssp_dai_ops, .playback = { .channels_min = 1, .channels_max = 8, @@ -536,7 +651,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "SSP1 Pin", - .ops = &ssp_dai_ops, .playback = { .channels_min = 1, .channels_max = 8, @@ -548,7 +662,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "SSP2 Pin", - .ops = &ssp_dai_ops, .playback = { .channels_min = 1, .channels_max = 8, @@ -560,7 +673,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "SSP3 Pin", - .ops = &ssp_dai_ops, .playback = { .channels_min = 1, .channels_max = 8, @@ -572,7 +684,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "SSP4 Pin", - .ops = &ssp_dai_ops, .playback = { .channels_min = 1, .channels_max = 8, @@ -584,7 +695,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "SSP5 Pin", - .ops = &ssp_dai_ops, .playback = { .channels_min = 1, .channels_max = 8, @@ -611,7 +721,6 @@ struct snd_soc_dai_driver skl_dai[] = { #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) { .name = "iDisp1 Pin", - .ops = &hda_link_dai_ops, .playback = { .channels_min = 1, .channels_max = 8, @@ -619,7 +728,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "iDisp2 Pin", - .ops = &hda_link_dai_ops, .playback = { .channels_min = 1, .channels_max = 8, @@ -627,7 +735,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "iDisp3 Pin", - .ops = &hda_link_dai_ops, .playback = { .channels_min = 1, .channels_max = 8, @@ -635,7 +742,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "iDisp4 Pin", - .ops = &hda_link_dai_ops, .playback = { .channels_min = 1, .channels_max = 8, @@ -643,7 +749,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "Analog CPU DAI", - .ops = &hda_link_dai_ops, .playback = { .channels_min = 1, .channels_max = 16, @@ -655,7 +760,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "Digital CPU DAI", - .ops = &hda_link_dai_ops, .playback = { .channels_min = 1, .channels_max = 16, @@ -667,7 +771,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "Alt Analog CPU DAI", - .ops = &hda_link_dai_ops, .playback = { .channels_min = 1, .channels_max = 16, @@ -679,3 +782,22 @@ struct snd_soc_dai_driver skl_dai[] = { }, #endif }; + +int hda_dsp_dais_suspend(struct snd_sof_dev *sdev) +{ + /* + * In the corner case where a SUSPEND happens during a PAUSE, the ALSA core + * does not throw the TRIGGER_SUSPEND. This leaves the DAIs in an unbalanced state. + * Since the component suspend is called last, we can trap this corner case + * and force the DAIs to release their resources. + */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + int ret; + + ret = hda_dai_suspend(sof_to_bus(sdev)); + if (ret < 0) + return ret; +#endif + + return 0; +} diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c index 8ddde60c56b3..000ea906670c 100644 --- a/sound/soc/sof/intel/hda-dsp.c +++ b/sound/soc/sof/intel/hda-dsp.c @@ -363,9 +363,8 @@ static int hda_dsp_send_pm_gate_ipc(struct snd_sof_dev *sdev, u32 flags) pm_gate.flags = flags; /* send pm_gate ipc to dsp */ - return sof_ipc_tx_message_no_pm(sdev->ipc, pm_gate.hdr.cmd, - &pm_gate, sizeof(pm_gate), &reply, - sizeof(reply)); + return sof_ipc_tx_message_no_pm(sdev->ipc, &pm_gate, sizeof(pm_gate), + &reply, sizeof(reply)); } static int hda_dsp_update_d0i3c_register(struct snd_sof_dev *sdev, u8 value) @@ -433,7 +432,7 @@ static int hda_dsp_set_D0_state(struct snd_sof_dev *sdev, * when the DSP enters D0I3 while the system is in S0 * for debug purpose. */ - if (!sdev->dtrace_is_supported || + if (!sdev->fw_trace_is_supported || !hda_enable_trace_D0I3_S0 || sdev->system_suspend_target != SOF_SUSPEND_NONE) flags = HDA_PM_NO_DMA_TRACE; @@ -895,44 +894,14 @@ int hda_dsp_shutdown(struct snd_sof_dev *sdev) int hda_dsp_set_hw_params_upon_resume(struct snd_sof_dev *sdev) { -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) - struct hdac_bus *bus = sof_to_bus(sdev); - struct snd_soc_pcm_runtime *rtd; - struct hdac_ext_stream *hext_stream; - struct hdac_ext_link *link; - struct hdac_stream *s; - const char *name; - int stream_tag; - - /* set internal flag for BE */ - list_for_each_entry(s, &bus->stream_list, list) { - hext_stream = stream_to_hdac_ext_stream(s); - - /* - * clear stream. This should already be taken care for running - * streams when the SUSPEND trigger is called. But paused - * streams do not get suspended, so this needs to be done - * explicitly during suspend. - */ - if (hext_stream->link_substream) { - rtd = asoc_substream_to_rtd(hext_stream->link_substream); - name = asoc_rtd_to_codec(rtd, 0)->component->name; - link = snd_hdac_ext_bus_get_link(bus, name); - if (!link) - return -EINVAL; - - hext_stream->link_prepared = 0; + int ret; - if (hdac_stream(hext_stream)->direction == - SNDRV_PCM_STREAM_CAPTURE) - continue; + /* make sure all DAI resources are freed */ + ret = hda_dsp_dais_suspend(sdev); + if (ret < 0) + dev_warn(sdev->dev, "%s: failure in hda_dsp_dais_suspend\n", __func__); - stream_tag = hdac_stream(hext_stream)->stream_tag; - snd_hdac_ext_link_clear_stream_id(link, stream_tag); - } - } -#endif - return 0; + return ret; } void hda_dsp_d0i3_work(struct work_struct *work) @@ -985,8 +954,7 @@ int hda_dsp_core_get(struct snd_sof_dev *sdev, int core) return 0; /* Now notify DSP for secondary cores */ - ret = sof_ipc_tx_message(sdev->ipc, pm_core_config.hdr.cmd, - &pm_core_config, sizeof(pm_core_config), + ret = sof_ipc_tx_message(sdev->ipc, &pm_core_config, sizeof(pm_core_config), &pm_core_config, sizeof(pm_core_config)); if (ret < 0) { dev_err(sdev->dev, "failed to enable secondary core '%d' failed with %d\n", diff --git a/sound/soc/sof/intel/hda-ipc.c b/sound/soc/sof/intel/hda-ipc.c index 0395638c43ae..f08011249955 100644 --- a/sound/soc/sof/intel/hda-ipc.c +++ b/sound/soc/sof/intel/hda-ipc.c @@ -15,6 +15,7 @@ * Hardware interface for generic Intel audio DSP HDA IP */ +#include <sound/sof/ipc4/header.h> #include "../ops.h" #include "hda.h" @@ -65,6 +66,22 @@ int hda_dsp_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) return 0; } +int hda_dsp_ipc4_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, HDA_DSP_REG_HIPCIE, msg_data->extension); + snd_sof_dsp_write(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCI, + msg_data->primary | HDA_DSP_REG_HIPCI_BUSY); + + return 0; +} + void hda_dsp_ipc_get_reply(struct snd_sof_dev *sdev) { struct snd_sof_ipc_msg *msg = sdev->msg; @@ -100,6 +117,71 @@ void hda_dsp_ipc_get_reply(struct snd_sof_dev *sdev) } } +irqreturn_t hda_dsp_ipc4_irq_thread(int irq, void *context) +{ + struct sof_ipc4_msg notification_data = {{ 0 }}; + struct snd_sof_dev *sdev = context; + bool ipc_irq = false; + u32 hipcie, hipct; + + hipcie = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCIE); + if (hipcie & HDA_DSP_REG_HIPCIE_DONE) { + /* DSP received the message */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCCTL, + HDA_DSP_REG_HIPCCTL_DONE, 0); + hda_dsp_ipc_dsp_done(sdev); + + ipc_irq = true; + } + + hipct = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCT); + if (hipct & HDA_DSP_REG_HIPCT_BUSY) { + /* Message from DSP (reply or notification) */ + u32 hipcte = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_DSP_REG_HIPCTE); + u32 primary = hipct & HDA_DSP_REG_HIPCT_MSG_MASK; + u32 extension = hipcte & HDA_DSP_REG_HIPCTE_MSG_MASK; + + /* mask BUSY interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCCTL, + HDA_DSP_REG_HIPCCTL_BUSY, 0); + + if (primary & SOF_IPC4_MSG_DIR_MASK) { + /* Reply received */ + 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 { + /* Notification received */ + + notification_data.primary = primary; + notification_data.extension = extension; + sdev->ipc->msg.rx_data = ¬ification_data; + snd_sof_ipc_msgs_rx(sdev); + sdev->ipc->msg.rx_data = NULL; + } + + /* Let DSP know that we have finished processing the message */ + hda_dsp_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; +} + /* IPC handler thread */ irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context) { diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c index 2ac5d9d0719b..64290125d7cd 100644 --- a/sound/soc/sof/intel/hda-loader.c +++ b/sound/soc/sof/intel/hda-loader.c @@ -24,8 +24,6 @@ #include "../sof-priv.h" #include "hda.h" -#define HDA_CL_STREAM_FORMAT 0x40 - static void hda_ssp_set_cbp_cfp(struct snd_sof_dev *sdev) { struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; @@ -43,9 +41,9 @@ static void hda_ssp_set_cbp_cfp(struct snd_sof_dev *sdev) } } -static struct hdac_ext_stream *cl_stream_prepare(struct snd_sof_dev *sdev, unsigned int format, - unsigned int size, struct snd_dma_buffer *dmab, - int direction) +struct hdac_ext_stream *hda_cl_stream_prepare(struct snd_sof_dev *sdev, unsigned int format, + unsigned int size, struct snd_dma_buffer *dmab, + int direction) { struct hdac_ext_stream *hext_stream; struct hdac_stream *hstream; @@ -101,14 +99,14 @@ out_put: * status on core 1, so power up core 1 also momentarily, keep it in * reset/stall and then turn it off */ -static int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag) +static 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; - unsigned int status; + unsigned int status, target_status; + u32 flags, ipc_hdr, j; unsigned long mask; char *dump_msg; - u32 flags, j; int ret; /* step 1: power up corex */ @@ -121,10 +119,12 @@ static int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag) hda_ssp_set_cbp_cfp(sdev); - /* step 2: purge FW request */ - snd_sof_dsp_write(sdev, HDA_DSP_BAR, chip->ipc_req, - chip->ipc_req_mask | (HDA_DSP_IPC_PURGE_FW | - ((stream_tag - 1) << 9))); + /* step 2: Send ROM_CONTROL command (stream_tag is ignored for IMR boot) */ + 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 3: unset core 0 reset state & unstall/run core 0 */ ret = hda_dsp_core_run(sdev, BIT(0)); @@ -171,11 +171,20 @@ static int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag) /* step 6: enable IPC interrupts */ hda_dsp_ipc_int_enable(sdev); - /* step 7: wait for ROM init */ + /* + * step 7: + * - Cold/Full boot: wait for ROM init to proceed to download the firmware + * - IMR boot: wait for ROM firmware entered (firmware booted up from IMR) + */ + if (imr_boot) + target_status = HDA_DSP_ROM_FW_ENTERED; + else + target_status = HDA_DSP_ROM_INIT; + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, - HDA_DSP_SRAM_REG_ROM_STATUS, status, + chip->rom_status_reg, status, ((status & HDA_DSP_ROM_STS_MASK) - == HDA_DSP_ROM_INIT), + == target_status), HDA_DSP_REG_POLL_INTERVAL_US, chip->rom_init_timeout * USEC_PER_MSEC); @@ -190,8 +199,8 @@ static int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag) if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS) dev_err(sdev->dev, - "error: %s: timeout HDA_DSP_SRAM_REG_ROM_STATUS read\n", - __func__); + "%s: timeout with rom_status_reg (%#x) read\n", + __func__, chip->rom_status_reg); err: flags = SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_MBOX | SOF_DBG_DUMP_OPTIONAL; @@ -236,8 +245,8 @@ static int cl_trigger(struct snd_sof_dev *sdev, } } -static int cl_cleanup(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, - struct hdac_ext_stream *hext_stream) +int hda_cl_cleanup(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, + struct hdac_ext_stream *hext_stream) { struct hdac_stream *hstream = &hext_stream->hstream; int sd_offset = SOF_STREAM_SD_OFFSET(hstream); @@ -268,8 +277,10 @@ static int cl_cleanup(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, return ret; } -static int cl_copy_fw(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream) +int hda_cl_copy_fw(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream) { + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; unsigned int reg; int ret, status; @@ -280,7 +291,7 @@ static int cl_copy_fw(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_str } status = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, - HDA_DSP_SRAM_REG_ROM_STATUS, reg, + chip->rom_status_reg, reg, ((reg & HDA_DSP_ROM_STS_MASK) == HDA_DSP_ROM_FW_ENTERED), HDA_DSP_REG_POLL_INTERVAL_US, @@ -293,8 +304,8 @@ static int cl_copy_fw(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_str if (status < 0) { dev_err(sdev->dev, - "error: %s: timeout HDA_DSP_SRAM_REG_ROM_STATUS read\n", - __func__); + "%s: timeout with rom_status_reg (%#x) read\n", + __func__, chip->rom_status_reg); } ret = cl_trigger(sdev, hext_stream, SNDRV_PCM_TRIGGER_STOP); @@ -313,6 +324,7 @@ int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev) struct hdac_ext_stream *iccmax_stream; struct hdac_bus *bus = sof_to_bus(sdev); struct firmware stripped_firmware; + struct snd_dma_buffer dmab_bdl; int ret, ret1; u8 original_gb; @@ -327,8 +339,8 @@ int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev) stripped_firmware.size = plat_data->fw->size - plat_data->fw_offset; /* prepare capture stream for ICCMAX */ - iccmax_stream = cl_stream_prepare(sdev, HDA_CL_STREAM_FORMAT, stripped_firmware.size, - &sdev->dmab_bdl, SNDRV_PCM_STREAM_CAPTURE); + iccmax_stream = hda_cl_stream_prepare(sdev, HDA_CL_STREAM_FORMAT, stripped_firmware.size, + &dmab_bdl, SNDRV_PCM_STREAM_CAPTURE); if (IS_ERR(iccmax_stream)) { dev_err(sdev->dev, "error: dma prepare for ICCMAX stream failed\n"); return PTR_ERR(iccmax_stream); @@ -340,7 +352,7 @@ int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev) * Perform iccmax stream cleanup. This should be done even if firmware loading fails. * If the cleanup also fails, we return the initial error */ - ret1 = cl_cleanup(sdev, &sdev->dmab_bdl, iccmax_stream); + ret1 = hda_cl_cleanup(sdev, &dmab_bdl, iccmax_stream); if (ret1 < 0) { dev_err(sdev->dev, "error: ICCMAX stream cleanup failed\n"); @@ -357,32 +369,11 @@ int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev) static int hda_dsp_boot_imr(struct snd_sof_dev *sdev) { - struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; - const struct sof_intel_dsp_desc *chip = hda->desc; - unsigned long mask; - u32 j; int ret; - /* power up & unstall/run the cores to run the firmware */ - ret = hda_dsp_enable_core(sdev, chip->init_core_mask); - if (ret < 0) { - dev_err(sdev->dev, "dsp core start failed %d\n", ret); - return -EIO; - } - - /* set enabled cores mask and increment ref count for cores in init_core_mask */ - sdev->enabled_cores_mask |= chip->init_core_mask; - mask = sdev->enabled_cores_mask; - for_each_set_bit(j, &mask, SOF_MAX_DSP_NUM_CORES) - sdev->dsp_core_ref_count[j]++; - - hda_ssp_set_cbp_cfp(sdev); - - /* enable IPC interrupts */ - hda_dsp_ipc_int_enable(sdev); - - /* process wakes */ - hda_sdw_process_wakeen(sdev); + ret = cl_dsp_init(sdev, 0, true); + if (!ret) + hda_sdw_process_wakeen(sdev); return ret; } @@ -395,13 +386,17 @@ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev) const struct sof_intel_dsp_desc *chip_info; struct hdac_ext_stream *hext_stream; struct firmware stripped_firmware; + struct snd_dma_buffer dmab; int ret, ret1, i; - if ((sdev->fw_ready.flags & SOF_IPC_INFO_D3_PERSISTENT) && - !(sof_debug_check_flag(SOF_DBG_IGNORE_D3_PERSISTENT)) && - !sdev->first_boot) { + if (hda->imrboot_supported && !sdev->first_boot) { dev_dbg(sdev->dev, "IMR restore supported, booting from IMR directly\n"); - return hda_dsp_boot_imr(sdev); + hda->boot_iteration = 0; + ret = hda_dsp_boot_imr(sdev); + if (!ret) + return 0; + + dev_warn(sdev->dev, "IMR restore failed, trying to cold boot\n"); } chip_info = desc->chip_info; @@ -418,14 +413,15 @@ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev) init_waitqueue_head(&sdev->boot_wait); /* prepare DMA for code loader stream */ - hext_stream = cl_stream_prepare(sdev, HDA_CL_STREAM_FORMAT, stripped_firmware.size, - &sdev->dmab, SNDRV_PCM_STREAM_PLAYBACK); + hext_stream = hda_cl_stream_prepare(sdev, HDA_CL_STREAM_FORMAT, + stripped_firmware.size, + &dmab, SNDRV_PCM_STREAM_PLAYBACK); if (IS_ERR(hext_stream)) { dev_err(sdev->dev, "error: dma prepare for fw loading failed\n"); return PTR_ERR(hext_stream); } - memcpy(sdev->dmab.area, stripped_firmware.data, + memcpy(dmab.area, stripped_firmware.data, stripped_firmware.size); /* try ROM init a few times before giving up */ @@ -434,7 +430,7 @@ 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); + ret = cl_dsp_init(sdev, hext_stream->hstream.stream_tag, false); /* don't retry anymore if successful */ if (!ret) @@ -473,7 +469,7 @@ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev) * Continue with code loading and firmware boot */ hda->boot_iteration = HDA_FW_BOOT_ATTEMPTS; - ret = cl_copy_fw(sdev, hext_stream); + ret = hda_cl_copy_fw(sdev, hext_stream); if (!ret) dev_dbg(sdev->dev, "Firmware download successful, booting...\n"); else @@ -486,7 +482,7 @@ cleanup: * This should be done even if firmware loading fails. * If the cleanup also fails, we return the initial error */ - ret1 = cl_cleanup(sdev, &sdev->dmab, hext_stream); + ret1 = hda_cl_cleanup(sdev, &dmab, hext_stream); if (ret1 < 0) { dev_err(sdev->dev, "error: Code loader DSP cleanup failed\n"); @@ -522,12 +518,19 @@ int hda_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, "error: could not startup SoundWire links\n"); return ret; } + + /* 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) + hdev->imrboot_supported = true; } hda_sdw_int_enable(sdev, true); diff --git a/sound/soc/sof/intel/hda-trace.c b/sound/soc/sof/intel/hda-trace.c index 755ef1d835e0..cbb9bd7770e6 100644 --- a/sound/soc/sof/intel/hda-trace.c +++ b/sound/soc/sof/intel/hda-trace.c @@ -36,7 +36,7 @@ static int hda_dsp_trace_prepare(struct snd_sof_dev *sdev, struct snd_dma_buffer return ret; } -int hda_dsp_trace_init(struct snd_sof_dev *sdev, +int hda_dsp_trace_init(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, struct sof_ipc_dma_trace_params_ext *dtrace_params) { struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; @@ -57,7 +57,7 @@ int hda_dsp_trace_init(struct snd_sof_dev *sdev, * initialize capture stream, set BDL address and return corresponding * stream tag which will be sent to the firmware by IPC message. */ - ret = hda_dsp_trace_prepare(sdev, &sdev->dmatb); + ret = hda_dsp_trace_prepare(sdev, dmab); if (ret < 0) { dev_err(sdev->dev, "error: hdac trace init failed: %d\n", ret); hda_dsp_stream_put(sdev, SNDRV_PCM_STREAM_CAPTURE, diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index 9c97c80a7f48..bc07df1fc39f 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -406,11 +406,13 @@ static const struct hda_dsp_msg_code hda_dsp_rom_msg[] = { static void hda_dsp_get_status(struct snd_sof_dev *sdev, const char *level) { + 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, - HDA_DSP_SRAM_REG_ROM_STATUS); + chip->rom_status_reg); for (i = 0; i < ARRAY_SIZE(hda_dsp_rom_msg); i++) { if (status == hda_dsp_rom_msg[i].code) { @@ -456,13 +458,15 @@ static void hda_dsp_get_registers(struct snd_sof_dev *sdev, static void hda_dsp_dump_ext_rom_status(struct snd_sof_dev *sdev, const char *level, u32 flags) { + const struct sof_intel_dsp_desc *chip; char msg[128]; int len = 0; u32 value; int i; + 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, HDA_DSP_SRAM_REG_ROM_STATUS + i * 0x4); + 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); } @@ -493,6 +497,17 @@ void hda_dsp_dump(struct snd_sof_dev *sdev, u32 flags) } } +static bool hda_check_ipc_irq(struct snd_sof_dev *sdev) +{ + const struct sof_intel_dsp_desc *chip; + + chip = get_chip_info(sdev->pdata); + if (chip && chip->check_ipc_irq) + return chip->check_ipc_irq(sdev); + + return false; +} + void hda_ipc_irq_dump(struct snd_sof_dev *sdev) { struct hdac_bus *bus = sof_to_bus(sdev); @@ -584,14 +599,13 @@ static int hda_init(struct snd_sof_dev *sdev) static int check_dmic_num(struct snd_sof_dev *sdev) { + struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; struct nhlt_acpi_table *nhlt; int dmic_num = 0; - nhlt = intel_nhlt_init(sdev->dev); - if (nhlt) { + nhlt = hdev->nhlt; + if (nhlt) dmic_num = intel_nhlt_get_dmic_geo(sdev->dev, nhlt); - intel_nhlt_free(nhlt); - } /* allow for module parameter override */ if (dmic_num_override != -1) { @@ -611,10 +625,11 @@ static int check_dmic_num(struct snd_sof_dev *sdev) static int check_nhlt_ssp_mask(struct snd_sof_dev *sdev) { + struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; struct nhlt_acpi_table *nhlt; int ssp_mask = 0; - nhlt = intel_nhlt_init(sdev->dev); + nhlt = hdev->nhlt; if (!nhlt) return ssp_mask; @@ -623,7 +638,6 @@ static int check_nhlt_ssp_mask(struct snd_sof_dev *sdev) if (ssp_mask) dev_info(sdev->dev, "NHLT_DEVICE_I2S detected, ssp_mask %#x\n", ssp_mask); } - intel_nhlt_free(nhlt); return ssp_mask; } @@ -816,7 +830,7 @@ static irqreturn_t hda_dsp_interrupt_thread(int irq, void *context) if (hda_dsp_check_stream_irq(sdev)) hda_dsp_stream_threaded_handler(irq, sdev); - if (hda_dsp_check_ipc_irq(sdev)) + if (hda_check_ipc_irq(sdev)) sof_ops(sdev)->irq_thread(irq, sdev); if (hda_dsp_check_sdw_irq(sdev)) @@ -984,6 +998,8 @@ int hda_dsp_probe(struct snd_sof_dev *sdev) INIT_DELAYED_WORK(&hdev->d0i3_work, hda_dsp_d0i3_work); + hdev->nhlt = intel_nhlt_init(sdev->dev); + return 0; free_ipc_irq: @@ -1009,6 +1025,10 @@ int hda_dsp_remove(struct snd_sof_dev *sdev) const struct sof_intel_dsp_desc *chip = hda->desc; struct hdac_bus *bus = sof_to_bus(sdev); struct pci_dev *pci = to_pci_dev(sdev->dev); + struct nhlt_acpi_table *nhlt = hda->nhlt; + + if (nhlt) + intel_nhlt_free(nhlt); /* cancel any attempt for DSP D0I3 */ cancel_delayed_work_sync(&hda->d0i3_work); @@ -1274,7 +1294,7 @@ static struct snd_soc_acpi_mach *hda_sdw_machine_select(struct snd_sof_dev *sdev 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->fw_filename = pdata->desc->default_fw_filename[pdata->ipc_type]; pdata->tplg_filename = mach->sof_tplg_filename; /* diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index 05e5e158614a..3e0f7b0c586a 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -210,7 +210,9 @@ #define HDA_DSP_ROM_USER_EXCEPTION 0xBEEF0000 #define HDA_DSP_ROM_UNEXPECTED_RESET 0xDECAF000 #define HDA_DSP_ROM_NULL_FW_ENTRY 0x4c4c4e55 -#define HDA_DSP_IPC_PURGE_FW 0x01004000 + +#define HDA_DSP_ROM_IPC_CONTROL 0x01000000 +#define HDA_DSP_ROM_IPC_PURGE_FW 0x00004000 /* various timeout values */ #define HDA_DSP_PU_TIMEOUT 50 @@ -223,8 +225,8 @@ #define HDA_DSP_REG_POLL_INTERVAL_US 500 /* 0.5 msec */ #define HDA_DSP_REG_POLL_RETRY_COUNT 50 -#define HDA_DSP_ADSPIC_IPC 1 -#define HDA_DSP_ADSPIS_IPC 1 +#define HDA_DSP_ADSPIC_IPC BIT(0) +#define HDA_DSP_ADSPIS_IPC BIT(0) /* Intel HD Audio General DSP Registers */ #define HDA_DSP_GEN_BASE 0x0 @@ -268,8 +270,8 @@ /* HIPCTE */ #define HDA_DSP_REG_HIPCTE_MSG_MASK 0x3FFFFFFF -#define HDA_DSP_ADSPIC_CL_DMA 0x2 -#define HDA_DSP_ADSPIS_CL_DMA 0x2 +#define HDA_DSP_ADSPIC_CL_DMA BIT(1) +#define HDA_DSP_ADSPIS_CL_DMA BIT(1) /* Delay before scheduling D0i3 entry */ #define BXT_D0I3_DELAY 5000 @@ -416,6 +418,8 @@ enum sof_hda_D0_substate { /* represents DSP HDA controller frontend - i.e. host facing control */ struct sof_intel_hda_dev { + bool imrboot_supported; + int boot_iteration; struct hda_bus hbus; @@ -449,6 +453,9 @@ struct sof_intel_hda_dev { /* FW clock config, 0:HPRO, 1:LPRO */ bool clk_config_lpro; + + /* Intel NHLT information */ + struct nhlt_acpi_table *nhlt; }; static inline struct hdac_bus *sof_to_bus(struct snd_sof_dev *s) @@ -588,6 +595,13 @@ int hda_dsp_ipc_cmd_done(struct snd_sof_dev *sdev, int dir); */ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev); int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev); +int hda_cl_copy_fw(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream); +struct hdac_ext_stream *hda_cl_stream_prepare(struct snd_sof_dev *sdev, unsigned int format, + unsigned int size, struct snd_dma_buffer *dmab, + int direction); +int hda_cl_cleanup(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, + struct hdac_ext_stream *hext_stream); +#define HDA_CL_STREAM_FORMAT 0x40 /* pre and post fw run ops */ int hda_dsp_pre_fw_run(struct snd_sof_dev *sdev); @@ -644,7 +658,7 @@ static inline int hda_codec_i915_exit(struct snd_sof_dev *sdev) { return 0; } /* * Trace Control. */ -int hda_dsp_trace_init(struct snd_sof_dev *sdev, +int hda_dsp_trace_init(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, struct sof_ipc_dma_trace_params_ext *dtrace_params); int hda_dsp_trace_release(struct snd_sof_dev *sdev); int hda_dsp_trace_trigger(struct snd_sof_dev *sdev, int cmd); @@ -683,18 +697,24 @@ static inline bool hda_common_check_sdw_irq(struct snd_sof_dev *sdev) /* common dai driver */ extern struct snd_soc_dai_driver skl_dai[]; +int hda_dsp_dais_suspend(struct snd_sof_dev *sdev); /* * Platform Specific HW abstraction Ops. */ -extern const struct snd_sof_dsp_ops sof_apl_ops; -extern const struct snd_sof_dsp_ops sof_cnl_ops; -extern const struct snd_sof_dsp_ops sof_tgl_ops; -extern const struct snd_sof_dsp_ops sof_icl_ops; +extern struct snd_sof_dsp_ops sof_hda_common_ops; + +extern struct snd_sof_dsp_ops sof_apl_ops; +int sof_apl_ops_init(struct snd_sof_dev *sdev); +extern struct snd_sof_dsp_ops sof_cnl_ops; +int sof_cnl_ops_init(struct snd_sof_dev *sdev); +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 const struct sof_intel_dsp_desc apl_chip_info; extern const struct sof_intel_dsp_desc cnl_chip_info; -extern const struct sof_intel_dsp_desc skl_chip_info; extern const struct sof_intel_dsp_desc icl_chip_info; extern const struct sof_intel_dsp_desc tgl_chip_info; extern const struct sof_intel_dsp_desc tglh_chip_info; @@ -742,4 +762,12 @@ 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); + +/* 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); + #endif diff --git a/sound/soc/sof/intel/icl.c b/sound/soc/sof/intel/icl.c index b44a649bfc0b..f19517dffd62 100644 --- a/sound/soc/sof/intel/icl.c +++ b/sound/soc/sof/intel/icl.c @@ -56,11 +56,18 @@ static int icl_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, "error: could not startup SoundWire links\n"); return ret; } + + /* 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) + hdev->imrboot_supported = true; } hda_sdw_int_enable(sdev, true); @@ -88,109 +95,44 @@ static int icl_dsp_post_fw_run(struct snd_sof_dev *sdev) } /* Icelake ops */ -const struct snd_sof_dsp_ops sof_icl_ops = { - /* probe/remove/shutdown */ - .probe = hda_dsp_probe, - .remove = hda_dsp_remove, - .shutdown = hda_dsp_shutdown, - - /* Register IO */ - .write = sof_io_write, - .read = sof_io_read, - .write64 = sof_io_write64, - .read64 = sof_io_read64, +struct snd_sof_dsp_ops sof_icl_ops; +EXPORT_SYMBOL_NS(sof_icl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); - /* Block IO */ - .block_read = sof_block_read, - .block_write = sof_block_write, +int sof_icl_ops_init(struct snd_sof_dev *sdev) +{ + /* common defaults */ + memcpy(&sof_icl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops)); - /* Mailbox IO */ - .mailbox_read = sof_mailbox_read, - .mailbox_write = sof_mailbox_write, + /* probe/remove/shutdown */ + sof_icl_ops.shutdown = hda_dsp_shutdown; /* doorbell */ - .irq_thread = cnl_ipc_irq_thread, + sof_icl_ops.irq_thread = cnl_ipc_irq_thread; /* ipc */ - .send_msg = cnl_ipc_send_msg, - .fw_ready = sof_fw_ready, - .get_mailbox_offset = hda_dsp_ipc_get_mailbox_offset, - .get_window_offset = hda_dsp_ipc_get_window_offset, - - .ipc_msg_data = hda_ipc_msg_data, - .set_stream_data_offset = hda_set_stream_data_offset, - - /* machine driver */ - .machine_select = hda_machine_select, - .machine_register = sof_machine_register, - .machine_unregister = sof_machine_unregister, - .set_mach_params = hda_set_mach_params, + sof_icl_ops.send_msg = cnl_ipc_send_msg; /* debug */ - .debug_map = icl_dsp_debugfs, - .debug_map_count = ARRAY_SIZE(icl_dsp_debugfs), - .dbg_dump = hda_dsp_dump, - .ipc_dump = cnl_ipc_dump, - .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, - - /* stream callbacks */ - .pcm_open = hda_dsp_pcm_open, - .pcm_close = hda_dsp_pcm_close, - .pcm_hw_params = hda_dsp_pcm_hw_params, - .pcm_hw_free = hda_dsp_stream_hw_free, - .pcm_trigger = hda_dsp_pcm_trigger, - .pcm_pointer = hda_dsp_pcm_pointer, - .pcm_ack = hda_dsp_pcm_ack, - - /* firmware loading */ - .load_firmware = snd_sof_load_firmware_raw, + sof_icl_ops.debug_map = icl_dsp_debugfs; + sof_icl_ops.debug_map_count = ARRAY_SIZE(icl_dsp_debugfs); + sof_icl_ops.ipc_dump = cnl_ipc_dump; /* pre/post fw run */ - .pre_fw_run = hda_dsp_pre_fw_run, - .post_fw_run = icl_dsp_post_fw_run, + sof_icl_ops.post_fw_run = icl_dsp_post_fw_run; - /* parse platform specific extended manifest */ - .parse_platform_ext_manifest = hda_dsp_ext_man_get_cavs_config_data, + /* firmware run */ + sof_icl_ops.run = hda_dsp_cl_boot_firmware_iccmax; + sof_icl_ops.stall = icl_dsp_core_stall; /* dsp core get/put */ - .core_get = hda_dsp_core_get, + sof_icl_ops.core_get = hda_dsp_core_get; - /* firmware run */ - .run = hda_dsp_cl_boot_firmware_iccmax, - .stall = icl_dsp_core_stall, - - /* trace callback */ - .trace_init = hda_dsp_trace_init, - .trace_release = hda_dsp_trace_release, - .trace_trigger = hda_dsp_trace_trigger, - - /* client ops */ - .register_ipc_clients = hda_register_clients, - .unregister_ipc_clients = hda_unregister_clients, - - /* DAI drivers */ - .drv = skl_dai, - .num_drv = SOF_SKL_NUM_DAIS, - - /* PM */ - .suspend = hda_dsp_suspend, - .resume = hda_dsp_resume, - .runtime_suspend = hda_dsp_runtime_suspend, - .runtime_resume = hda_dsp_runtime_resume, - .runtime_idle = hda_dsp_runtime_idle, - .set_hw_params_upon_resume = hda_dsp_set_hw_params_upon_resume, - .set_power_state = hda_dsp_set_power_state, - - /* ALSA HW info flags */ - .hw_info = SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_PAUSE | - SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, - - .dsp_arch_ops = &sof_xtensa_arch_ops, + /* set DAI driver ops */ + hda_set_dai_drv_ops(sdev, &sof_icl_ops); + + return 0; }; -EXPORT_SYMBOL_NS(sof_icl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); +EXPORT_SYMBOL_NS(sof_icl_ops_init, SND_SOC_SOF_INTEL_HDA_COMMON); const struct sof_intel_dsp_desc icl_chip_info = { /* Icelake */ @@ -202,11 +144,14 @@ const struct sof_intel_dsp_desc icl_chip_info = { .ipc_ack = CNL_DSP_REG_HIPCIDA, .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE, .ipc_ctl = CNL_DSP_REG_HIPCCTL, + .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS, .rom_init_timeout = 300, .ssp_count = ICL_SSP_COUNT, .ssp_base_offset = CNL_SSP_BASE_OFFSET, .sdw_shim_base = SDW_SHIM_BASE, .sdw_alh_base = SDW_ALH_BASE, .check_sdw_irq = hda_common_check_sdw_irq, + .check_ipc_irq = hda_dsp_check_ipc_irq, + .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/pci-apl.c b/sound/soc/sof/intel/pci-apl.c index a023b3cc0af4..2de3658eb817 100644 --- a/sound/soc/sof/intel/pci-apl.c +++ b/sound/soc/sof/intel/pci-apl.c @@ -27,11 +27,23 @@ static const struct sof_dev_desc bxt_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &apl_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-apl.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/apl", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-apl.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-apl-nocodec.tplg", .ops = &sof_apl_ops, + .ops_init = sof_apl_ops_init, }; static const struct sof_dev_desc glk_desc = { @@ -42,11 +54,23 @@ static const struct sof_dev_desc glk_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &apl_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-glk.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/glk", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-glk.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-glk-nocodec.tplg", .ops = &sof_apl_ops, + .ops_init = sof_apl_ops_init, }; /* PCI IDs */ diff --git a/sound/soc/sof/intel/pci-cnl.c b/sound/soc/sof/intel/pci-cnl.c index 40cf1cd00042..87e587aef9c9 100644 --- a/sound/soc/sof/intel/pci-cnl.c +++ b/sound/soc/sof/intel/pci-cnl.c @@ -28,11 +28,23 @@ static const struct sof_dev_desc cnl_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &cnl_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-cnl.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/cnl", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-cnl.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-cnl-nocodec.tplg", .ops = &sof_cnl_ops, + .ops_init = sof_cnl_ops_init, }; static const struct sof_dev_desc cfl_desc = { @@ -44,11 +56,23 @@ static const struct sof_dev_desc cfl_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &cnl_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-cfl.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/cnl", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-cfl.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-cnl-nocodec.tplg", .ops = &sof_cnl_ops, + .ops_init = sof_cnl_ops_init, }; static const struct sof_dev_desc cml_desc = { @@ -60,11 +84,23 @@ static const struct sof_dev_desc cml_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &cnl_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-cml.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/cnl", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-cml.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-cnl-nocodec.tplg", .ops = &sof_cnl_ops, + .ops_init = sof_cnl_ops_init, }; /* PCI IDs */ diff --git a/sound/soc/sof/intel/pci-icl.c b/sound/soc/sof/intel/pci-icl.c index 39c84121b313..1c7f16ce531e 100644 --- a/sound/soc/sof/intel/pci-icl.c +++ b/sound/soc/sof/intel/pci-icl.c @@ -28,11 +28,23 @@ static const struct sof_dev_desc icl_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &icl_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-icl.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/icl", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-icl.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-icl-nocodec.tplg", .ops = &sof_icl_ops, + .ops_init = sof_icl_ops_init, }; static const struct sof_dev_desc jsl_desc = { @@ -43,11 +55,23 @@ static const struct sof_dev_desc jsl_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &jsl_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-jsl.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/jsl", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-jsl.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-jsl-nocodec.tplg", .ops = &sof_cnl_ops, + .ops_init = sof_cnl_ops_init, }; /* PCI IDs */ diff --git a/sound/soc/sof/intel/pci-tgl.c b/sound/soc/sof/intel/pci-tgl.c index feaec251adc8..58a9bd92a237 100644 --- a/sound/soc/sof/intel/pci-tgl.c +++ b/sound/soc/sof/intel/pci-tgl.c @@ -28,11 +28,23 @@ static const struct sof_dev_desc tgl_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &tgl_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-tgl.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/tgl", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-tgl.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-tgl-nocodec.tplg", .ops = &sof_tgl_ops, + .ops_init = sof_tgl_ops_init, }; static const struct sof_dev_desc tglh_desc = { @@ -44,11 +56,23 @@ static const struct sof_dev_desc tglh_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &tglh_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-tgl-h.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/tgl-h", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-tgl-h.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-tgl-nocodec.tplg", .ops = &sof_tgl_ops, + .ops_init = sof_tgl_ops_init, }; static const struct sof_dev_desc ehl_desc = { @@ -59,11 +83,23 @@ static const struct sof_dev_desc ehl_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &ehl_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-ehl.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/ehl", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-ehl.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-ehl-nocodec.tplg", .ops = &sof_tgl_ops, + .ops_init = sof_tgl_ops_init, }; static const struct sof_dev_desc adls_desc = { @@ -75,11 +111,23 @@ static const struct sof_dev_desc adls_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &adls_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-adl-s.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/adl-s", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-adl-s.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-adl-nocodec.tplg", .ops = &sof_tgl_ops, + .ops_init = sof_tgl_ops_init, }; static const struct sof_dev_desc adl_desc = { @@ -91,11 +139,23 @@ static const struct sof_dev_desc adl_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &tgl_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-adl.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/adl", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-adl.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-adl-nocodec.tplg", .ops = &sof_tgl_ops, + .ops_init = sof_tgl_ops_init, }; /* PCI IDs */ @@ -116,6 +176,12 @@ static const struct pci_device_id sof_pci_ids[] = { .driver_data = (unsigned long)&adl_desc}, { PCI_DEVICE(0x8086, 0x51cd), /* ADL-P */ .driver_data = (unsigned long)&adl_desc}, + { PCI_DEVICE(0x8086, 0x51c9), /* ADL-PS */ + .driver_data = (unsigned long)&adl_desc}, + { PCI_DEVICE(0x8086, 0x51ca), /* RPL-P */ + .driver_data = (unsigned long)&adl_desc}, + { PCI_DEVICE(0x8086, 0x51cb), /* RPL-P */ + .driver_data = (unsigned long)&adl_desc}, { PCI_DEVICE(0x8086, 0x51cc), /* ADL-M */ .driver_data = (unsigned long)&adl_desc}, { PCI_DEVICE(0x8086, 0x54c8), /* ADL-N */ @@ -140,4 +206,3 @@ module_pci_driver(snd_sof_pci_intel_tgl_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-tng.c b/sound/soc/sof/intel/pci-tng.c index 6efef225973f..f0f6d9ba8803 100644 --- a/sound/soc/sof/intel/pci-tng.c +++ b/sound/soc/sof/intel/pci-tng.c @@ -75,7 +75,11 @@ static int tangier_pci_probe(struct snd_sof_dev *sdev) /* LPE base */ base = pci_resource_start(pci, desc->resindex_lpe_base) - IRAM_OFFSET; - size = PCI_BAR_SIZE; + size = pci_resource_len(pci, desc->resindex_lpe_base); + if (size < PCI_BAR_SIZE) { + dev_err(sdev->dev, "error: I/O region is too small.\n"); + return -ENODEV; + } dev_dbg(sdev->dev, "LPE PHY base at 0x%x size 0x%x", base, size); sdev->bar[DSP_BAR] = devm_ioremap(sdev->dev, base, size); @@ -132,7 +136,7 @@ irq: return ret; } -const struct snd_sof_dsp_ops sof_tng_ops = { +struct snd_sof_dsp_ops sof_tng_ops = { /* device init */ .probe = tangier_pci_probe, @@ -160,7 +164,6 @@ const struct snd_sof_dsp_ops sof_tng_ops = { /* ipc */ .send_msg = atom_send_msg, - .fw_ready = sof_fw_ready, .get_mailbox_offset = atom_get_mailbox_offset, .get_window_offset = atom_get_window_offset, @@ -183,9 +186,6 @@ const struct snd_sof_dsp_ops sof_tng_ops = { .pcm_open = sof_stream_pcm_open, .pcm_close = sof_stream_pcm_close, - /* module loading */ - .load_module = snd_sof_parse_module_memcpy, - /*Firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy, @@ -206,6 +206,7 @@ const struct snd_sof_dsp_ops sof_tng_ops = { const struct sof_intel_dsp_desc tng_chip_info = { .cores_num = 1, .host_managed_cores_mask = 1, + .hw_ip_version = SOF_INTEL_TANGIER, }; static const struct sof_dev_desc tng_desc = { @@ -215,9 +216,17 @@ static const struct sof_dev_desc tng_desc = { .resindex_imr_base = 0, .irqindex_host_ipc = -1, .chip_info = &tng_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-byt.ri", + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-byt.ri", + }, .nocodec_tplg_filename = "sof-byt.tplg", .ops = &sof_tng_ops, }; diff --git a/sound/soc/sof/intel/shim.h b/sound/soc/sof/intel/shim.h index f36cd9d5eb94..1fd7b485d821 100644 --- a/sound/soc/sof/intel/shim.h +++ b/sound/soc/sof/intel/shim.h @@ -11,6 +11,17 @@ #ifndef __SOF_INTEL_SHIM_H #define __SOF_INTEL_SHIM_H +enum sof_intel_hw_ip_version { + SOF_INTEL_TANGIER, + SOF_INTEL_BAYTRAIL, + SOF_INTEL_BROADWELL, + SOF_INTEL_CAVS_1_5, /* SkyLake, KabyLake, AmberLake */ + SOF_INTEL_CAVS_1_5_PLUS,/* ApolloLake, GeminiLake */ + SOF_INTEL_CAVS_1_8, /* CannonLake, CometLake, CoffeeLake */ + SOF_INTEL_CAVS_2_0, /* IceLake, JasperLake */ + SOF_INTEL_CAVS_2_5, /* TigerLake, AlderLake */ +}; + /* * SHIM registers for BYT, BSW, CHT, BDW */ @@ -164,16 +175,19 @@ struct sof_intel_dsp_desc { int ipc_ack; int ipc_ack_mask; int ipc_ctl; + int rom_status_reg; int rom_init_timeout; int ssp_count; /* ssp count of the platform */ int ssp_base_offset; /* base address of the SSPs */ u32 sdw_shim_base; u32 sdw_alh_base; u32 quirks; + 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); }; -extern const struct snd_sof_dsp_ops sof_tng_ops; +extern struct snd_sof_dsp_ops sof_tng_ops; extern const struct sof_intel_dsp_desc tng_chip_info; diff --git a/sound/soc/sof/intel/tgl.c b/sound/soc/sof/intel/tgl.c index cb1c319d5bee..1ddc492f1b13 100644 --- a/sound/soc/sof/intel/tgl.c +++ b/sound/soc/sof/intel/tgl.c @@ -9,6 +9,8 @@ * Hardware interface for audio DSP on Tigerlake. */ +#include <sound/sof/ext_manifest4.h> +#include "../ipc4-priv.h" #include "../ops.h" #include "hda.h" #include "hda-ipc.h" @@ -35,8 +37,7 @@ static int tgl_dsp_core_get(struct snd_sof_dev *sdev, int core) return hda_dsp_enable_core(sdev, BIT(core)); /* notify DSP for secondary cores */ - return sof_ipc_tx_message(sdev->ipc, pm_core_config.hdr.cmd, - &pm_core_config, sizeof(pm_core_config), + return sof_ipc_tx_message(sdev->ipc, &pm_core_config, sizeof(pm_core_config), &pm_core_config, sizeof(pm_core_config)); } @@ -55,115 +56,68 @@ static int tgl_dsp_core_put(struct snd_sof_dev *sdev, int 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.hdr.cmd, - &pm_core_config, sizeof(pm_core_config), + return sof_ipc_tx_message(sdev->ipc, &pm_core_config, sizeof(pm_core_config), &pm_core_config, sizeof(pm_core_config)); } /* Tigerlake ops */ -const struct snd_sof_dsp_ops sof_tgl_ops = { +struct snd_sof_dsp_ops sof_tgl_ops; +EXPORT_SYMBOL_NS(sof_tgl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); + +int sof_tgl_ops_init(struct snd_sof_dev *sdev) +{ + /* common defaults */ + memcpy(&sof_tgl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops)); + /* probe/remove/shutdown */ - .probe = hda_dsp_probe, - .remove = hda_dsp_remove, - .shutdown = hda_dsp_shutdown, - - /* Register IO */ - .write = sof_io_write, - .read = sof_io_read, - .write64 = sof_io_write64, - .read64 = sof_io_read64, - - /* Block IO */ - .block_read = sof_block_read, - .block_write = sof_block_write, - - /* Mailbox IO */ - .mailbox_read = sof_mailbox_read, - .mailbox_write = sof_mailbox_write, - - /* doorbell */ - .irq_thread = cnl_ipc_irq_thread, - - /* ipc */ - .send_msg = cnl_ipc_send_msg, - .fw_ready = sof_fw_ready, - .get_mailbox_offset = hda_dsp_ipc_get_mailbox_offset, - .get_window_offset = hda_dsp_ipc_get_window_offset, - - .ipc_msg_data = hda_ipc_msg_data, - .set_stream_data_offset = hda_set_stream_data_offset, - - /* machine driver */ - .machine_select = hda_machine_select, - .machine_register = sof_machine_register, - .machine_unregister = sof_machine_unregister, - .set_mach_params = hda_set_mach_params, + sof_tgl_ops.shutdown = hda_dsp_shutdown; + + if (sdev->pdata->ipc_type == SOF_IPC) { + /* doorbell */ + sof_tgl_ops.irq_thread = cnl_ipc_irq_thread; + + /* ipc */ + sof_tgl_ops.send_msg = cnl_ipc_send_msg; + } + + if (sdev->pdata->ipc_type == SOF_INTEL_IPC4) { + struct sof_ipc4_fw_data *ipc4_data; + + sdev->private = devm_kzalloc(sdev->dev, sizeof(*ipc4_data), GFP_KERNEL); + if (!sdev->private) + return -ENOMEM; + + ipc4_data = sdev->private; + ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; + + /* doorbell */ + sof_tgl_ops.irq_thread = cnl_ipc4_irq_thread; + + /* ipc */ + sof_tgl_ops.send_msg = cnl_ipc4_send_msg; + } + + /* set DAI driver ops */ + hda_set_dai_drv_ops(sdev, &sof_tgl_ops); /* debug */ - .debug_map = tgl_dsp_debugfs, - .debug_map_count = ARRAY_SIZE(tgl_dsp_debugfs), - .dbg_dump = hda_dsp_dump, - .ipc_dump = cnl_ipc_dump, - .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, - - /* stream callbacks */ - .pcm_open = hda_dsp_pcm_open, - .pcm_close = hda_dsp_pcm_close, - .pcm_hw_params = hda_dsp_pcm_hw_params, - .pcm_hw_free = hda_dsp_stream_hw_free, - .pcm_trigger = hda_dsp_pcm_trigger, - .pcm_pointer = hda_dsp_pcm_pointer, - .pcm_ack = hda_dsp_pcm_ack, - - /* firmware loading */ - .load_firmware = snd_sof_load_firmware_raw, + sof_tgl_ops.debug_map = tgl_dsp_debugfs; + sof_tgl_ops.debug_map_count = ARRAY_SIZE(tgl_dsp_debugfs); + sof_tgl_ops.ipc_dump = cnl_ipc_dump; /* pre/post fw run */ - .pre_fw_run = hda_dsp_pre_fw_run, - .post_fw_run = hda_dsp_post_fw_run, + sof_tgl_ops.post_fw_run = hda_dsp_post_fw_run; - /* parse platform specific extended manifest */ - .parse_platform_ext_manifest = hda_dsp_ext_man_get_cavs_config_data, + /* firmware run */ + sof_tgl_ops.run = hda_dsp_cl_boot_firmware_iccmax; /* dsp core get/put */ - .core_get = tgl_dsp_core_get, - .core_put = tgl_dsp_core_put, + sof_tgl_ops.core_get = tgl_dsp_core_get; + sof_tgl_ops.core_put = tgl_dsp_core_put; - /* firmware run */ - .run = hda_dsp_cl_boot_firmware_iccmax, - - /* trace callback */ - .trace_init = hda_dsp_trace_init, - .trace_release = hda_dsp_trace_release, - .trace_trigger = hda_dsp_trace_trigger, - - /* client ops */ - .register_ipc_clients = hda_register_clients, - .unregister_ipc_clients = hda_unregister_clients, - - /* DAI drivers */ - .drv = skl_dai, - .num_drv = SOF_SKL_NUM_DAIS, - - /* PM */ - .suspend = hda_dsp_suspend, - .resume = hda_dsp_resume, - .runtime_suspend = hda_dsp_runtime_suspend, - .runtime_resume = hda_dsp_runtime_resume, - .runtime_idle = hda_dsp_runtime_idle, - .set_hw_params_upon_resume = hda_dsp_set_hw_params_upon_resume, - .set_power_state = hda_dsp_set_power_state, - - /* ALSA HW info flags */ - .hw_info = SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_PAUSE | - SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, - - .dsp_arch_ops = &sof_xtensa_arch_ops, + return 0; }; -EXPORT_SYMBOL_NS(sof_tgl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); +EXPORT_SYMBOL_NS(sof_tgl_ops_init, SND_SOC_SOF_INTEL_HDA_COMMON); const struct sof_intel_dsp_desc tgl_chip_info = { /* Tigerlake , Alderlake */ @@ -175,12 +129,15 @@ const struct sof_intel_dsp_desc tgl_chip_info = { .ipc_ack = CNL_DSP_REG_HIPCIDA, .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE, .ipc_ctl = CNL_DSP_REG_HIPCCTL, + .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS, .rom_init_timeout = 300, .ssp_count = ICL_SSP_COUNT, .ssp_base_offset = CNL_SSP_BASE_OFFSET, .sdw_shim_base = SDW_SHIM_BASE, .sdw_alh_base = SDW_ALH_BASE, .check_sdw_irq = hda_common_check_sdw_irq, + .check_ipc_irq = hda_dsp_check_ipc_irq, + .hw_ip_version = SOF_INTEL_CAVS_2_5, }; EXPORT_SYMBOL_NS(tgl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); @@ -194,12 +151,15 @@ const struct sof_intel_dsp_desc tglh_chip_info = { .ipc_ack = CNL_DSP_REG_HIPCIDA, .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE, .ipc_ctl = CNL_DSP_REG_HIPCCTL, + .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS, .rom_init_timeout = 300, .ssp_count = ICL_SSP_COUNT, .ssp_base_offset = CNL_SSP_BASE_OFFSET, .sdw_shim_base = SDW_SHIM_BASE, .sdw_alh_base = SDW_ALH_BASE, .check_sdw_irq = hda_common_check_sdw_irq, + .check_ipc_irq = hda_dsp_check_ipc_irq, + .hw_ip_version = SOF_INTEL_CAVS_2_5, }; EXPORT_SYMBOL_NS(tglh_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); @@ -213,12 +173,15 @@ const struct sof_intel_dsp_desc ehl_chip_info = { .ipc_ack = CNL_DSP_REG_HIPCIDA, .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE, .ipc_ctl = CNL_DSP_REG_HIPCCTL, + .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS, .rom_init_timeout = 300, .ssp_count = ICL_SSP_COUNT, .ssp_base_offset = CNL_SSP_BASE_OFFSET, .sdw_shim_base = SDW_SHIM_BASE, .sdw_alh_base = SDW_ALH_BASE, .check_sdw_irq = hda_common_check_sdw_irq, + .check_ipc_irq = hda_dsp_check_ipc_irq, + .hw_ip_version = SOF_INTEL_CAVS_2_5, }; EXPORT_SYMBOL_NS(ehl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); @@ -232,11 +195,14 @@ const struct sof_intel_dsp_desc adls_chip_info = { .ipc_ack = CNL_DSP_REG_HIPCIDA, .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE, .ipc_ctl = CNL_DSP_REG_HIPCCTL, + .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS, .rom_init_timeout = 300, .ssp_count = ICL_SSP_COUNT, .ssp_base_offset = CNL_SSP_BASE_OFFSET, .sdw_shim_base = SDW_SHIM_BASE, .sdw_alh_base = SDW_ALH_BASE, .check_sdw_irq = hda_common_check_sdw_irq, + .check_ipc_irq = hda_dsp_check_ipc_irq, + .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 5f5753608c79..c5aef5fc056b 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -17,294 +17,35 @@ #include "sof-priv.h" #include "sof-audio.h" #include "ops.h" -#include "ipc3-ops.h" -typedef void (*ipc_rx_callback)(struct snd_sof_dev *sdev, void *msg_buf); - -static void ipc_trace_message(struct snd_sof_dev *sdev, void *msg_buf); -static void ipc_stream_message(struct snd_sof_dev *sdev, void *msg_buf); - -/* - * IPC message Tx/Rx message handling. +/** + * sof_ipc_send_msg - generic function to prepare and send one IPC message + * @sdev: pointer to SOF core device struct + * @msg_data: pointer to a message to send + * @msg_bytes: number of bytes in the message + * @reply_bytes: number of bytes available for the reply. + * The buffer for the reply data is not passed to this + * function, the available size is an information for the + * reply handling functions. + * + * On success the function returns 0, otherwise negative error number. + * + * Note: higher level sdev->ipc->tx_mutex must be held to make sure that + * transfers are synchronized. */ - -struct sof_ipc_ctrl_data_params { - size_t msg_bytes; - size_t hdr_bytes; - size_t pl_size; - size_t elems; - u32 num_msg; - u8 *src; - u8 *dst; -}; - -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_VERBOSE_IPC) -static void ipc_log_header(struct device *dev, u8 *text, u32 cmd) -{ - u8 *str; - u8 *str2 = NULL; - u32 glb; - u32 type; - bool vdbg = false; - - glb = cmd & SOF_GLB_TYPE_MASK; - type = cmd & SOF_CMD_TYPE_MASK; - - switch (glb) { - case SOF_IPC_GLB_REPLY: - str = "GLB_REPLY"; break; - case SOF_IPC_GLB_COMPOUND: - str = "GLB_COMPOUND"; break; - case SOF_IPC_GLB_TPLG_MSG: - str = "GLB_TPLG_MSG"; - switch (type) { - case SOF_IPC_TPLG_COMP_NEW: - str2 = "COMP_NEW"; break; - case SOF_IPC_TPLG_COMP_FREE: - str2 = "COMP_FREE"; break; - case SOF_IPC_TPLG_COMP_CONNECT: - str2 = "COMP_CONNECT"; break; - case SOF_IPC_TPLG_PIPE_NEW: - str2 = "PIPE_NEW"; break; - case SOF_IPC_TPLG_PIPE_FREE: - str2 = "PIPE_FREE"; break; - case SOF_IPC_TPLG_PIPE_CONNECT: - str2 = "PIPE_CONNECT"; break; - case SOF_IPC_TPLG_PIPE_COMPLETE: - str2 = "PIPE_COMPLETE"; break; - case SOF_IPC_TPLG_BUFFER_NEW: - str2 = "BUFFER_NEW"; break; - case SOF_IPC_TPLG_BUFFER_FREE: - str2 = "BUFFER_FREE"; break; - default: - str2 = "unknown type"; break; - } - break; - case SOF_IPC_GLB_PM_MSG: - str = "GLB_PM_MSG"; - switch (type) { - case SOF_IPC_PM_CTX_SAVE: - str2 = "CTX_SAVE"; break; - case SOF_IPC_PM_CTX_RESTORE: - str2 = "CTX_RESTORE"; break; - case SOF_IPC_PM_CTX_SIZE: - str2 = "CTX_SIZE"; break; - case SOF_IPC_PM_CLK_SET: - str2 = "CLK_SET"; break; - case SOF_IPC_PM_CLK_GET: - str2 = "CLK_GET"; break; - case SOF_IPC_PM_CLK_REQ: - str2 = "CLK_REQ"; break; - case SOF_IPC_PM_CORE_ENABLE: - str2 = "CORE_ENABLE"; break; - case SOF_IPC_PM_GATE: - str2 = "GATE"; break; - default: - str2 = "unknown type"; break; - } - break; - case SOF_IPC_GLB_COMP_MSG: - str = "GLB_COMP_MSG"; - switch (type) { - case SOF_IPC_COMP_SET_VALUE: - str2 = "SET_VALUE"; break; - case SOF_IPC_COMP_GET_VALUE: - str2 = "GET_VALUE"; break; - case SOF_IPC_COMP_SET_DATA: - str2 = "SET_DATA"; break; - case SOF_IPC_COMP_GET_DATA: - str2 = "GET_DATA"; break; - default: - str2 = "unknown type"; break; - } - break; - case SOF_IPC_GLB_STREAM_MSG: - str = "GLB_STREAM_MSG"; - switch (type) { - case SOF_IPC_STREAM_PCM_PARAMS: - str2 = "PCM_PARAMS"; break; - case SOF_IPC_STREAM_PCM_PARAMS_REPLY: - str2 = "PCM_REPLY"; break; - case SOF_IPC_STREAM_PCM_FREE: - str2 = "PCM_FREE"; break; - case SOF_IPC_STREAM_TRIG_START: - str2 = "TRIG_START"; break; - case SOF_IPC_STREAM_TRIG_STOP: - str2 = "TRIG_STOP"; break; - case SOF_IPC_STREAM_TRIG_PAUSE: - str2 = "TRIG_PAUSE"; break; - case SOF_IPC_STREAM_TRIG_RELEASE: - str2 = "TRIG_RELEASE"; break; - case SOF_IPC_STREAM_TRIG_DRAIN: - str2 = "TRIG_DRAIN"; break; - case SOF_IPC_STREAM_TRIG_XRUN: - str2 = "TRIG_XRUN"; break; - case SOF_IPC_STREAM_POSITION: - vdbg = true; - str2 = "POSITION"; break; - case SOF_IPC_STREAM_VORBIS_PARAMS: - str2 = "VORBIS_PARAMS"; break; - case SOF_IPC_STREAM_VORBIS_FREE: - str2 = "VORBIS_FREE"; break; - default: - str2 = "unknown type"; break; - } - break; - case SOF_IPC_FW_READY: - str = "FW_READY"; break; - case SOF_IPC_GLB_DAI_MSG: - str = "GLB_DAI_MSG"; - switch (type) { - case SOF_IPC_DAI_CONFIG: - str2 = "CONFIG"; break; - case SOF_IPC_DAI_LOOPBACK: - str2 = "LOOPBACK"; break; - default: - str2 = "unknown type"; break; - } - break; - case SOF_IPC_GLB_TRACE_MSG: - str = "GLB_TRACE_MSG"; - switch (type) { - case SOF_IPC_TRACE_DMA_PARAMS: - str2 = "DMA_PARAMS"; break; - case SOF_IPC_TRACE_DMA_POSITION: - str2 = "DMA_POSITION"; break; - case SOF_IPC_TRACE_DMA_PARAMS_EXT: - str2 = "DMA_PARAMS_EXT"; break; - case SOF_IPC_TRACE_FILTER_UPDATE: - str2 = "FILTER_UPDATE"; break; - case SOF_IPC_TRACE_DMA_FREE: - str2 = "DMA_FREE"; break; - default: - str2 = "unknown type"; break; - } - break; - case SOF_IPC_GLB_TEST_MSG: - str = "GLB_TEST_MSG"; - switch (type) { - case SOF_IPC_TEST_IPC_FLOOD: - str2 = "IPC_FLOOD"; break; - default: - str2 = "unknown type"; break; - } - break; - case SOF_IPC_GLB_DEBUG: - str = "GLB_DEBUG"; - switch (type) { - case SOF_IPC_DEBUG_MEM_USAGE: - str2 = "MEM_USAGE"; break; - default: - str2 = "unknown type"; break; - } - break; - case SOF_IPC_GLB_PROBE: - str = "GLB_PROBE"; - switch (type) { - case SOF_IPC_PROBE_INIT: - str2 = "INIT"; break; - case SOF_IPC_PROBE_DEINIT: - str2 = "DEINIT"; break; - case SOF_IPC_PROBE_DMA_ADD: - str2 = "DMA_ADD"; break; - case SOF_IPC_PROBE_DMA_INFO: - str2 = "DMA_INFO"; break; - case SOF_IPC_PROBE_DMA_REMOVE: - str2 = "DMA_REMOVE"; break; - case SOF_IPC_PROBE_POINT_ADD: - str2 = "POINT_ADD"; break; - case SOF_IPC_PROBE_POINT_INFO: - str2 = "POINT_INFO"; break; - case SOF_IPC_PROBE_POINT_REMOVE: - str2 = "POINT_REMOVE"; break; - default: - str2 = "unknown type"; break; - } - break; - default: - str = "unknown GLB command"; break; - } - - if (str2) { - if (vdbg) - dev_vdbg(dev, "%s: 0x%x: %s: %s\n", text, cmd, str, str2); - else - dev_dbg(dev, "%s: 0x%x: %s: %s\n", text, cmd, str, str2); - } else { - dev_dbg(dev, "%s: 0x%x: %s\n", text, cmd, str); - } -} -#else -static inline void ipc_log_header(struct device *dev, u8 *text, u32 cmd) -{ - if ((cmd & SOF_GLB_TYPE_MASK) != SOF_IPC_GLB_TRACE_MSG) - dev_dbg(dev, "%s: 0x%x\n", text, cmd); -} -#endif - -/* wait for IPC message reply */ -static int tx_wait_done(struct snd_sof_ipc *ipc, struct snd_sof_ipc_msg *msg, - void *reply_data) -{ - struct snd_sof_dev *sdev = ipc->sdev; - struct sof_ipc_cmd_hdr *hdr = msg->msg_data; - int ret; - - /* wait for DSP IPC completion */ - ret = wait_event_timeout(msg->waitq, msg->ipc_complete, - msecs_to_jiffies(sdev->ipc_timeout)); - - if (ret == 0) { - 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); - ret = -ETIMEDOUT; - } else { - ret = msg->reply_error; - if (ret < 0) { - dev_err(sdev->dev, - "ipc tx error for %#x (msg/reply size: %d/%zu): %d\n", - hdr->cmd, hdr->size, msg->reply_size, ret); - } else { - ipc_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, - msg->reply_size); - } - - /* re-enable dumps after successful IPC tx */ - if (sdev->ipc_dump_printed) { - sdev->dbg_dump_printed = false; - sdev->ipc_dump_printed = false; - } - } - - return ret; -} - -/* send IPC message from host to DSP */ -static int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, - void *msg_data, size_t msg_bytes, - void *reply_data, size_t reply_bytes) +int sof_ipc_send_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes, + size_t reply_bytes) { - struct sof_ipc_cmd_hdr *hdr = msg_data; - struct snd_sof_dev *sdev = ipc->sdev; + struct snd_sof_ipc *ipc = sdev->ipc; struct snd_sof_ipc_msg *msg; int ret; - if (!msg_data || msg_bytes < sizeof(*hdr)) { - dev_err_ratelimited(sdev->dev, "No IPC message to send\n"); - return -EINVAL; - } - if (ipc->disable_ipc_tx || sdev->fw_state != SOF_FW_BOOT_COMPLETE) return -ENODEV; /* - * The spin-lock is also still needed to protect message objects against - * other atomic contexts. + * The spin-lock is needed to protect message objects against other + * atomic contexts. */ spin_lock_irq(&sdev->ipc_lock); @@ -327,38 +68,19 @@ static int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, spin_unlock_irq(&sdev->ipc_lock); - if (ret) { - dev_err_ratelimited(sdev->dev, - "error: ipc tx failed with error %d\n", - ret); - return ret; - } - - ipc_log_header(sdev->dev, "ipc tx", hdr->cmd); - - /* now wait for completion */ - return tx_wait_done(ipc, msg, reply_data); + return ret; } /* send IPC message from host to DSP */ -int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header, - void *msg_data, size_t msg_bytes, void *reply_data, - size_t reply_bytes) +int sof_ipc_tx_message(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes, + void *reply_data, size_t reply_bytes) { - const struct sof_dsp_power_state target_state = { - .state = SOF_DSP_PM_D0, - }; - int ret; - - /* ensure the DSP is in D0 before sending a new IPC */ - ret = snd_sof_dsp_set_power_state(ipc->sdev, &target_state); - if (ret < 0) { - dev_err(ipc->sdev->dev, "error: resuming DSP %d\n", ret); - return ret; - } + if (msg_bytes > ipc->max_payload_size || + reply_bytes > ipc->max_payload_size) + return -ENOBUFS; - return sof_ipc_tx_message_no_pm(ipc, header, msg_data, msg_bytes, - reply_data, reply_bytes); + return ipc->ops->tx_msg(ipc->sdev, msg_data, msg_bytes, reply_data, + reply_bytes, false); } EXPORT_SYMBOL(sof_ipc_tx_message); @@ -367,86 +89,32 @@ EXPORT_SYMBOL(sof_ipc_tx_message); * This will be used for IPC's that can be handled by the DSP * even in a low-power D0 substate. */ -int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, u32 header, - void *msg_data, size_t msg_bytes, +int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes, void *reply_data, size_t reply_bytes) { - int ret; - - if (msg_bytes > SOF_IPC_MSG_MAX_SIZE || - reply_bytes > SOF_IPC_MSG_MAX_SIZE) + if (msg_bytes > ipc->max_payload_size || + reply_bytes > ipc->max_payload_size) return -ENOBUFS; - /* Serialise IPC TX */ - mutex_lock(&ipc->tx_mutex); - - ret = sof_ipc_tx_message_unlocked(ipc, msg_data, msg_bytes, - reply_data, reply_bytes); - - mutex_unlock(&ipc->tx_mutex); - - return ret; + return ipc->ops->tx_msg(ipc->sdev, msg_data, msg_bytes, reply_data, + reply_bytes, true); } EXPORT_SYMBOL(sof_ipc_tx_message_no_pm); /* Generic helper function to retrieve the reply */ void snd_sof_ipc_get_reply(struct snd_sof_dev *sdev) { - struct snd_sof_ipc_msg *msg = sdev->msg; - struct sof_ipc_reply reply; - int ret = 0; - /* * Sometimes, there is unexpected reply ipc arriving. The reply * ipc belongs to none of the ipcs sent from driver. * In this case, the driver must ignore the ipc. */ - if (!msg) { + if (!sdev->msg) { dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n"); return; } - /* get the generic reply */ - snd_sof_dsp_mailbox_read(sdev, sdev->host_box.offset, &reply, - sizeof(reply)); - - if (reply.error < 0) { - memcpy(msg->reply_data, &reply, sizeof(reply)); - ret = reply.error; - } else if (!reply.hdr.size) { - /* Reply should always be >= sizeof(struct sof_ipc_reply) */ - if (msg->reply_size) - dev_err(sdev->dev, - "empty reply received, expected %zu bytes\n", - msg->reply_size); - else - dev_err(sdev->dev, "empty reply received\n"); - - ret = -EINVAL; - } else if (msg->reply_size > 0) { - if (reply.hdr.size == msg->reply_size) { - ret = 0; - } else if (reply.hdr.size < msg->reply_size) { - dev_dbg(sdev->dev, - "reply size (%u) is less than expected (%zu)\n", - reply.hdr.size, msg->reply_size); - - msg->reply_size = reply.hdr.size; - ret = 0; - } else { - dev_err(sdev->dev, - "reply size (%u) exceeds the buffer size (%zu)\n", - reply.hdr.size, msg->reply_size); - ret = -EINVAL; - } - - /* get the full message if reply.hdr.size <= msg->reply_size */ - if (!ret) - snd_sof_dsp_mailbox_read(sdev, sdev->host_box.offset, - msg->reply_data, msg->reply_size); - } - - msg->reply_error = ret; + sdev->msg->reply_error = sdev->ipc->ops->get_reply(sdev); } EXPORT_SYMBOL(snd_sof_ipc_get_reply); @@ -468,550 +136,11 @@ void snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id) } EXPORT_SYMBOL(snd_sof_ipc_reply); -static void ipc_comp_notification(struct snd_sof_dev *sdev, void *msg_buf) -{ - const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; - struct sof_ipc_cmd_hdr *hdr = msg_buf; - u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK; - - switch (msg_type) { - case SOF_IPC_COMP_GET_VALUE: - case SOF_IPC_COMP_GET_DATA: - break; - default: - dev_err(sdev->dev, "error: unhandled component message %#x\n", msg_type); - return; - } - - if (tplg_ops->control->update) - tplg_ops->control->update(sdev, msg_buf); -} - -/* DSP firmware has sent host a message */ -void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) -{ - ipc_rx_callback rx_callback = NULL; - struct sof_ipc_cmd_hdr hdr; - void *msg_buf; - u32 cmd; - int err; - - /* read back header */ - err = snd_sof_ipc_msg_data(sdev, NULL, &hdr, sizeof(hdr)); - if (err < 0) { - dev_warn(sdev->dev, "failed to read IPC header: %d\n", err); - return; - } - - if (hdr.size < sizeof(hdr)) { - dev_err(sdev->dev, "The received message size is invalid\n"); - return; - } - - ipc_log_header(sdev->dev, "ipc rx", hdr.cmd); - - cmd = hdr.cmd & SOF_GLB_TYPE_MASK; - - /* check message type */ - switch (cmd) { - case SOF_IPC_GLB_REPLY: - dev_err(sdev->dev, "error: ipc reply unknown\n"); - break; - case SOF_IPC_FW_READY: - /* check for FW boot completion */ - if (sdev->fw_state == SOF_FW_BOOT_IN_PROGRESS) { - err = sof_ops(sdev)->fw_ready(sdev, cmd); - if (err < 0) - sof_set_fw_state(sdev, SOF_FW_BOOT_READY_FAILED); - else - sof_set_fw_state(sdev, SOF_FW_BOOT_READY_OK); - - /* wake up firmware loader */ - wake_up(&sdev->boot_wait); - } - break; - case SOF_IPC_GLB_COMPOUND: - case SOF_IPC_GLB_TPLG_MSG: - case SOF_IPC_GLB_PM_MSG: - break; - case SOF_IPC_GLB_COMP_MSG: - rx_callback = ipc_comp_notification; - break; - case SOF_IPC_GLB_STREAM_MSG: - rx_callback = ipc_stream_message; - break; - case SOF_IPC_GLB_TRACE_MSG: - rx_callback = ipc_trace_message; - break; - default: - dev_err(sdev->dev, "%s: Unknown DSP message: 0x%x\n", __func__, cmd); - break; - } - - /* read the full message */ - msg_buf = kmalloc(hdr.size, GFP_KERNEL); - if (!msg_buf) - return; - - err = snd_sof_ipc_msg_data(sdev, NULL, msg_buf, hdr.size); - if (err < 0) { - dev_err(sdev->dev, "%s: Failed to read message: %d\n", __func__, err); - } else { - /* Call local handler for the message */ - if (rx_callback) - rx_callback(sdev, msg_buf); - - /* Notify registered clients */ - sof_client_ipc_rx_dispatcher(sdev, msg_buf); - } - - kfree(msg_buf); - - ipc_log_header(sdev->dev, "ipc rx done", hdr.cmd); -} -EXPORT_SYMBOL(snd_sof_ipc_msgs_rx); - -/* - * IPC trace mechanism. - */ - -static void ipc_trace_message(struct snd_sof_dev *sdev, void *msg_buf) -{ - struct sof_ipc_cmd_hdr *hdr = msg_buf; - u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK; - - switch (msg_type) { - case SOF_IPC_TRACE_DMA_POSITION: - snd_sof_trace_update_pos(sdev, msg_buf); - break; - default: - dev_err(sdev->dev, "error: unhandled trace message %#x\n", msg_type); - break; - } -} - -/* - * IPC stream position. - */ - -static void ipc_period_elapsed(struct snd_sof_dev *sdev, u32 msg_id) -{ - struct snd_soc_component *scomp = sdev->component; - struct snd_sof_pcm_stream *stream; - struct sof_ipc_stream_posn posn; - struct snd_sof_pcm *spcm; - int direction, ret; - - spcm = snd_sof_find_spcm_comp(scomp, msg_id, &direction); - if (!spcm) { - dev_err(sdev->dev, - "error: period elapsed for unknown stream, msg_id %d\n", - msg_id); - return; - } - - stream = &spcm->stream[direction]; - ret = snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); - if (ret < 0) { - dev_warn(sdev->dev, "failed to read stream position: %d\n", ret); - return; - } - - dev_vdbg(sdev->dev, "posn : host 0x%llx dai 0x%llx wall 0x%llx\n", - posn.host_posn, posn.dai_posn, posn.wallclock); - - memcpy(&stream->posn, &posn, sizeof(posn)); - - if (spcm->pcm.compress) - snd_sof_compr_fragment_elapsed(stream->cstream); - else if (stream->substream->runtime && - !stream->substream->runtime->no_period_wakeup) - /* only inform ALSA for period_wakeup mode */ - snd_sof_pcm_period_elapsed(stream->substream); -} - -/* DSP notifies host of an XRUN within FW */ -static void ipc_xrun(struct snd_sof_dev *sdev, u32 msg_id) -{ - struct snd_soc_component *scomp = sdev->component; - struct snd_sof_pcm_stream *stream; - struct sof_ipc_stream_posn posn; - struct snd_sof_pcm *spcm; - int direction, ret; - - spcm = snd_sof_find_spcm_comp(scomp, msg_id, &direction); - if (!spcm) { - dev_err(sdev->dev, "error: XRUN for unknown stream, msg_id %d\n", - msg_id); - return; - } - - stream = &spcm->stream[direction]; - ret = snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); - if (ret < 0) { - dev_warn(sdev->dev, "failed to read overrun position: %d\n", ret); - return; - } - - dev_dbg(sdev->dev, "posn XRUN: host %llx comp %d size %d\n", - posn.host_posn, posn.xrun_comp_id, posn.xrun_size); - -#if defined(CONFIG_SND_SOC_SOF_DEBUG_XRUN_STOP) - /* stop PCM on XRUN - used for pipeline debug */ - memcpy(&stream->posn, &posn, sizeof(posn)); - snd_pcm_stop_xrun(stream->substream); -#endif -} - -/* stream notifications from DSP FW */ -static void ipc_stream_message(struct snd_sof_dev *sdev, void *msg_buf) -{ - struct sof_ipc_cmd_hdr *hdr = msg_buf; - u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK; - u32 msg_id = SOF_IPC_MESSAGE_ID(hdr->cmd); - - switch (msg_type) { - case SOF_IPC_STREAM_POSITION: - ipc_period_elapsed(sdev, msg_id); - break; - case SOF_IPC_STREAM_TRIG_XRUN: - ipc_xrun(sdev, msg_id); - break; - default: - dev_err(sdev->dev, "error: unhandled stream message %#x\n", - msg_id); - break; - } -} - -/* get stream position IPC - use faster MMIO method if available on platform */ -int snd_sof_ipc_stream_posn(struct snd_soc_component *scomp, - struct snd_sof_pcm *spcm, int direction, - struct sof_ipc_stream_posn *posn) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct sof_ipc_stream stream; - int err; - - /* read position via slower IPC */ - stream.hdr.size = sizeof(stream); - stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_POSITION; - stream.comp_id = spcm->stream[direction].comp_id; - - /* send IPC to the DSP */ - err = sof_ipc_tx_message(sdev->ipc, - stream.hdr.cmd, &stream, sizeof(stream), posn, - sizeof(*posn)); - if (err < 0) { - dev_err(sdev->dev, "error: failed to get stream %d position\n", - stream.comp_id); - return err; - } - - return 0; -} -EXPORT_SYMBOL(snd_sof_ipc_stream_posn); - -static int sof_get_ctrl_copy_params(enum sof_ipc_ctrl_type ctrl_type, - struct sof_ipc_ctrl_data *src, - struct sof_ipc_ctrl_data *dst, - struct sof_ipc_ctrl_data_params *sparams) -{ - switch (ctrl_type) { - case SOF_CTRL_TYPE_VALUE_CHAN_GET: - case SOF_CTRL_TYPE_VALUE_CHAN_SET: - sparams->src = (u8 *)src->chanv; - sparams->dst = (u8 *)dst->chanv; - break; - case SOF_CTRL_TYPE_DATA_GET: - case SOF_CTRL_TYPE_DATA_SET: - sparams->src = (u8 *)src->data->data; - sparams->dst = (u8 *)dst->data->data; - break; - default: - return -EINVAL; - } - - /* calculate payload size and number of messages */ - sparams->pl_size = SOF_IPC_MSG_MAX_SIZE - sparams->hdr_bytes; - sparams->num_msg = DIV_ROUND_UP(sparams->msg_bytes, sparams->pl_size); - - return 0; -} - -static int sof_set_get_large_ctrl_data(struct snd_sof_dev *sdev, - struct sof_ipc_ctrl_data *cdata, - struct sof_ipc_ctrl_data_params *sparams, - bool set) -{ - struct sof_ipc_ctrl_data *partdata; - size_t send_bytes; - size_t offset = 0; - size_t msg_bytes; - size_t pl_size; - int err; - int i; - - /* allocate max ipc size because we have at least one */ - partdata = kzalloc(SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); - if (!partdata) - return -ENOMEM; - - if (set) - err = sof_get_ctrl_copy_params(cdata->type, cdata, partdata, - sparams); - else - err = sof_get_ctrl_copy_params(cdata->type, partdata, cdata, - sparams); - if (err < 0) { - kfree(partdata); - return err; - } - - msg_bytes = sparams->msg_bytes; - pl_size = sparams->pl_size; - - /* copy the header data */ - memcpy(partdata, cdata, sparams->hdr_bytes); - - /* Serialise IPC TX */ - mutex_lock(&sdev->ipc->tx_mutex); - - /* copy the payload data in a loop */ - for (i = 0; i < sparams->num_msg; i++) { - send_bytes = min(msg_bytes, pl_size); - partdata->num_elems = send_bytes; - partdata->rhdr.hdr.size = sparams->hdr_bytes + send_bytes; - partdata->msg_index = i; - msg_bytes -= send_bytes; - partdata->elems_remaining = msg_bytes; - - if (set) - memcpy(sparams->dst, sparams->src + offset, send_bytes); - - err = sof_ipc_tx_message_unlocked(sdev->ipc, - partdata, - partdata->rhdr.hdr.size, - partdata, - partdata->rhdr.hdr.size); - if (err < 0) - break; - - if (!set) - memcpy(sparams->dst + offset, sparams->src, send_bytes); - - offset += pl_size; - } - - mutex_unlock(&sdev->ipc->tx_mutex); - - kfree(partdata); - return err; -} - -/* - * IPC get()/set() for kcontrols. - */ -int snd_sof_ipc_set_get_comp_data(struct snd_sof_control *scontrol, bool set) -{ - struct snd_soc_component *scomp = scontrol->scomp; - struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct sof_ipc_fw_ready *ready = &sdev->fw_ready; - struct sof_ipc_fw_version *v = &ready->version; - struct sof_ipc_ctrl_data_params sparams; - enum sof_ipc_ctrl_type ctrl_type; - struct snd_sof_widget *swidget; - bool widget_found = false; - size_t send_bytes; - u32 ipc_cmd; - int err; - - 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(sdev->dev, "error: can't find widget with id %d\n", scontrol->comp_id); - return -EINVAL; - } - - /* - * 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; - - /* read or write firmware volume */ - if (scontrol->readback_offset != 0) { - /* write/read value header via mmaped region */ - send_bytes = sizeof(struct sof_ipc_ctrl_value_chan) * - cdata->num_elems; - if (set) - err = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_IRAM, - scontrol->readback_offset, - cdata->chanv, send_bytes); - - else - err = snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_IRAM, - scontrol->readback_offset, - cdata->chanv, send_bytes); - - if (err) - dev_err_once(sdev->dev, "error: %s TYPE_IRAM failed\n", - set ? "write to" : "read from"); - return err; - } - - /* - * Select the IPC cmd and the ctrl_type based on the ctrl_cmd and the - * direction - * Note: SOF_CTRL_TYPE_VALUE_COMP_* is not used and supported currently - * for ctrl_type - */ - if (cdata->cmd == SOF_CTRL_CMD_BINARY) { - ipc_cmd = set ? SOF_IPC_COMP_SET_DATA : SOF_IPC_COMP_GET_DATA; - ctrl_type = set ? SOF_CTRL_TYPE_DATA_SET : SOF_CTRL_TYPE_DATA_GET; - } else { - ipc_cmd = set ? SOF_IPC_COMP_SET_VALUE : SOF_IPC_COMP_GET_VALUE; - ctrl_type = set ? SOF_CTRL_TYPE_VALUE_CHAN_SET : SOF_CTRL_TYPE_VALUE_CHAN_GET; - } - - cdata->rhdr.hdr.cmd = SOF_IPC_GLB_COMP_MSG | ipc_cmd; - cdata->type = ctrl_type; - cdata->comp_id = scontrol->comp_id; - cdata->msg_index = 0; - - /* calculate header and data size */ - switch (cdata->type) { - case SOF_CTRL_TYPE_VALUE_CHAN_GET: - case SOF_CTRL_TYPE_VALUE_CHAN_SET: - sparams.msg_bytes = scontrol->num_channels * - sizeof(struct sof_ipc_ctrl_value_chan); - sparams.hdr_bytes = sizeof(struct sof_ipc_ctrl_data); - sparams.elems = scontrol->num_channels; - break; - case SOF_CTRL_TYPE_DATA_GET: - case SOF_CTRL_TYPE_DATA_SET: - sparams.msg_bytes = cdata->data->size; - sparams.hdr_bytes = sizeof(struct sof_ipc_ctrl_data) + - sizeof(struct sof_abi_hdr); - sparams.elems = cdata->data->size; - break; - default: - return -EINVAL; - } - - cdata->rhdr.hdr.size = sparams.msg_bytes + sparams.hdr_bytes; - cdata->num_elems = sparams.elems; - cdata->elems_remaining = 0; - - /* send normal size ipc in one part */ - if (cdata->rhdr.hdr.size <= SOF_IPC_MSG_MAX_SIZE) { - err = sof_ipc_tx_message(sdev->ipc, cdata->rhdr.hdr.cmd, cdata, - cdata->rhdr.hdr.size, cdata, - cdata->rhdr.hdr.size); - - if (err < 0) - dev_err(sdev->dev, "error: set/get ctrl ipc comp %d\n", - cdata->comp_id); - - return err; - } - - /* data is bigger than max ipc size, chop into smaller pieces */ - dev_dbg(sdev->dev, "large ipc size %u, control size %u\n", - cdata->rhdr.hdr.size, scontrol->size); - - /* large messages is only supported from ABI 3.3.0 onwards */ - if (v->abi_version < SOF_ABI_VER(3, 3, 0)) { - dev_err(sdev->dev, "error: incompatible FW ABI version\n"); - return -EINVAL; - } - - err = sof_set_get_large_ctrl_data(sdev, cdata, &sparams, set); - - if (err < 0) - dev_err(sdev->dev, "error: set/get large ctrl ipc comp %d\n", - cdata->comp_id); - - return err; -} -EXPORT_SYMBOL(snd_sof_ipc_set_get_comp_data); - -int snd_sof_ipc_valid(struct snd_sof_dev *sdev) -{ - struct sof_ipc_fw_ready *ready = &sdev->fw_ready; - struct sof_ipc_fw_version *v = &ready->version; - - dev_info(sdev->dev, - "Firmware info: version %d:%d:%d-%s\n", v->major, v->minor, - v->micro, v->tag); - dev_info(sdev->dev, - "Firmware: ABI %d:%d:%d Kernel ABI %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), - SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH); - - if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, v->abi_version)) { - dev_err(sdev->dev, "error: incompatible FW ABI version\n"); - 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, "warn: FW ABI is more recent than kernel\n"); - } else { - dev_err(sdev->dev, "error: FW ABI is more recent than kernel\n"); - return -EINVAL; - } - } - - if (ready->flags & SOF_IPC_INFO_BUILD) { - dev_info(sdev->dev, - "Firmware debug build %d on %s-%s - options:\n" - " GDB: %s\n" - " lock debug: %s\n" - " lock vdebug: %s\n", - v->build, v->date, v->time, - (ready->flags & SOF_IPC_INFO_GDB) ? - "enabled" : "disabled", - (ready->flags & SOF_IPC_INFO_LOCKS) ? - "enabled" : "disabled", - (ready->flags & SOF_IPC_INFO_LOCKSV) ? - "enabled" : "disabled"); - } - - /* copy the fw_version into debugfs at first boot */ - memcpy(&sdev->fw_version, v, sizeof(*v)); - - return 0; -} -EXPORT_SYMBOL(snd_sof_ipc_valid); - -int sof_ipc_init_msg_memory(struct snd_sof_dev *sdev) -{ - struct snd_sof_ipc_msg *msg; - - msg = &sdev->ipc->msg; - - msg->reply_data = devm_kzalloc(sdev->dev, SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); - if (!msg->reply_data) - return -ENOMEM; - - return 0; -} - struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev) { struct snd_sof_ipc *ipc; struct snd_sof_ipc_msg *msg; + const struct sof_ipc_ops *ops; ipc = devm_kzalloc(sdev->dev, sizeof(*ipc), GFP_KERNEL); if (!ipc) @@ -1031,11 +160,33 @@ struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev) * versions, this will need to be modified to use the selected version at runtime. */ ipc->ops = &ipc3_ops; + ops = ipc->ops; /* check for mandatory ops */ - if (!ipc->ops->pcm || !ipc->ops->tplg || !ipc->ops->tplg->widget || - !ipc->ops->tplg->control) { - dev_err(sdev->dev, "Invalid IPC ops\n"); + if (!ops->tx_msg || !ops->rx_msg || !ops->set_get_data || !ops->get_reply) { + dev_err(sdev->dev, "Missing IPC message handling ops\n"); + return NULL; + } + + if (!ops->fw_loader || !ops->fw_loader->validate || + !ops->fw_loader->parse_ext_manifest) { + dev_err(sdev->dev, "Missing IPC firmware loading ops\n"); + return NULL; + } + + if (!ops->pcm) { + dev_err(sdev->dev, "Missing IPC PCM ops\n"); + return NULL; + } + + if (!ops->tplg || !ops->tplg->widget || !ops->tplg->control) { + dev_err(sdev->dev, "Missing IPC topology ops\n"); + return NULL; + } + + if (ops->fw_tracing && (!ops->fw_tracing->init || !ops->fw_tracing->suspend || + !ops->fw_tracing->resume)) { + dev_err(sdev->dev, "Missing firmware tracing ops\n"); return NULL; } diff --git a/sound/soc/sof/ipc3-control.c b/sound/soc/sof/ipc3-control.c index cdd5ad860a94..3fdc0d854e65 100644 --- a/sound/soc/sof/ipc3-control.c +++ b/sound/soc/sof/ipc3-control.c @@ -9,26 +9,85 @@ #include "sof-priv.h" #include "sof-audio.h" -#include "ipc3-ops.h" +#include "ipc3-priv.h" -static inline u32 mixer_to_ipc(unsigned int value, u32 *volume_map, int size) +/* IPC set()/get() for kcontrols. */ +static int sof_ipc3_set_get_kcontrol_data(struct snd_sof_control *scontrol, bool set) { - if (value >= size) - return volume_map[size - 1]; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scontrol->scomp); + struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; + const struct sof_ipc_ops *iops = sdev->ipc->ops; + enum sof_ipc_ctrl_type ctrl_type; + struct snd_sof_widget *swidget; + bool widget_found = false; + u32 ipc_cmd, msg_bytes; - return volume_map[value]; -} + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (swidget->comp_id == scontrol->comp_id) { + widget_found = true; + break; + } + } -static inline u32 ipc_to_mixer(u32 value, u32 *volume_map, int size) -{ - int i; + if (!widget_found) { + dev_err(sdev->dev, "%s: can't find widget with id %d\n", __func__, + scontrol->comp_id); + return -EINVAL; + } + + /* + * 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; + + /* + * Select the IPC cmd and the ctrl_type based on the ctrl_cmd and the + * direction + * Note: SOF_CTRL_TYPE_VALUE_COMP_* is not used and supported currently + * for ctrl_type + */ + if (cdata->cmd == SOF_CTRL_CMD_BINARY) { + ipc_cmd = set ? SOF_IPC_COMP_SET_DATA : SOF_IPC_COMP_GET_DATA; + ctrl_type = set ? SOF_CTRL_TYPE_DATA_SET : SOF_CTRL_TYPE_DATA_GET; + } else { + ipc_cmd = set ? SOF_IPC_COMP_SET_VALUE : SOF_IPC_COMP_GET_VALUE; + ctrl_type = set ? SOF_CTRL_TYPE_VALUE_CHAN_SET : SOF_CTRL_TYPE_VALUE_CHAN_GET; + } + + cdata->rhdr.hdr.cmd = SOF_IPC_GLB_COMP_MSG | ipc_cmd; + cdata->type = ctrl_type; + cdata->comp_id = scontrol->comp_id; + cdata->msg_index = 0; + + /* calculate header and data size */ + switch (cdata->type) { + case SOF_CTRL_TYPE_VALUE_CHAN_GET: + case SOF_CTRL_TYPE_VALUE_CHAN_SET: + cdata->num_elems = scontrol->num_channels; - for (i = 0; i < size; i++) { - if (volume_map[i] >= value) - return i; + msg_bytes = scontrol->num_channels * + sizeof(struct sof_ipc_ctrl_value_chan); + msg_bytes += sizeof(struct sof_ipc_ctrl_data); + break; + case SOF_CTRL_TYPE_DATA_GET: + case SOF_CTRL_TYPE_DATA_SET: + cdata->num_elems = cdata->data->size; + + msg_bytes = cdata->data->size; + msg_bytes += sizeof(struct sof_ipc_ctrl_data) + + sizeof(struct sof_abi_hdr); + break; + default: + return -EINVAL; } - return i - 1; + cdata->rhdr.hdr.size = msg_bytes; + cdata->elems_remaining = 0; + + return iops->set_get_data(sdev, cdata, cdata->rhdr.hdr.size, set); } static void snd_sof_refresh_control(struct snd_sof_control *scontrol) @@ -49,7 +108,7 @@ static void snd_sof_refresh_control(struct snd_sof_control *scontrol) /* refresh the component data from DSP */ scontrol->comp_data_dirty = false; - ret = snd_sof_ipc_set_get_comp_data(scontrol, false); + ret = sof_ipc3_set_get_kcontrol_data(scontrol, false); if (ret < 0) { dev_err(scomp->dev, "Failed to get control data: %d\n", ret); @@ -97,7 +156,7 @@ static bool sof_ipc3_volume_put(struct snd_sof_control *scontrol, /* notify DSP of mixer updates */ if (pm_runtime_active(scomp->dev)) { - int ret = snd_sof_ipc_set_get_comp_data(scontrol, true); + int ret = sof_ipc3_set_get_kcontrol_data(scontrol, true); if (ret < 0) { dev_err(scomp->dev, "Failed to set mixer updates for %s\n", @@ -145,7 +204,7 @@ static bool sof_ipc3_switch_put(struct snd_sof_control *scontrol, /* notify DSP of mixer updates */ if (pm_runtime_active(scomp->dev)) { - int ret = snd_sof_ipc_set_get_comp_data(scontrol, true); + int ret = sof_ipc3_set_get_kcontrol_data(scontrol, true); if (ret < 0) { dev_err(scomp->dev, "Failed to set mixer updates for %s\n", @@ -193,7 +252,7 @@ static bool sof_ipc3_enum_put(struct snd_sof_control *scontrol, /* notify DSP of enum updates */ if (pm_runtime_active(scomp->dev)) { - int ret = snd_sof_ipc_set_get_comp_data(scontrol, true); + int ret = sof_ipc3_set_get_kcontrol_data(scontrol, true); if (ret < 0) { dev_err(scomp->dev, "Failed to set enum updates for %s\n", @@ -265,7 +324,7 @@ static int sof_ipc3_bytes_put(struct snd_sof_control *scontrol, /* notify DSP of byte control updates */ if (pm_runtime_active(scomp->dev)) - return snd_sof_ipc_set_get_comp_data(scontrol, true); + return sof_ipc3_set_get_kcontrol_data(scontrol, true); return 0; } @@ -379,7 +438,7 @@ static int sof_ipc3_bytes_ext_put(struct snd_sof_control *scontrol, /* notify DSP of byte control updates */ if (pm_runtime_active(scomp->dev)) - return snd_sof_ipc_set_get_comp_data(scontrol, true); + return sof_ipc3_set_get_kcontrol_data(scontrol, true); return 0; } @@ -409,7 +468,7 @@ static int sof_ipc3_bytes_ext_volatile_get(struct snd_sof_control *scontrol, cdata->data->abi = SOF_ABI_VERSION; /* get all the component data from DSP */ - ret = snd_sof_ipc_set_get_comp_data(scontrol, false); + ret = sof_ipc3_set_get_kcontrol_data(scontrol, false); if (ret < 0) return ret; @@ -578,6 +637,60 @@ static void sof_ipc3_control_update(struct snd_sof_dev *sdev, void *ipc_control_ snd_ctl_notify_one(swidget->scomp->card->snd_card, SNDRV_CTL_EVENT_MASK_VALUE, kc, 0); } +static int sof_ipc3_widget_kcontrol_setup(struct snd_sof_dev *sdev, + struct snd_sof_widget *swidget) +{ + struct snd_sof_control *scontrol; + int ret; + + /* set up all controls for the widget */ + list_for_each_entry(scontrol, &sdev->kcontrol_list, list) + if (scontrol->comp_id == swidget->comp_id) { + /* set kcontrol data in DSP */ + ret = sof_ipc3_set_get_kcontrol_data(scontrol, true); + if (ret < 0) { + dev_err(sdev->dev, + "kcontrol %d set up failed for widget %s\n", + scontrol->comp_id, swidget->widget->name); + return ret; + } + + /* + * Read back the data from the DSP for static widgets. + * This is particularly useful for binary kcontrols + * associated with static pipeline widgets to initialize + * the data size to match that in the DSP. + */ + if (swidget->dynamic_pipeline_widget) + continue; + + ret = sof_ipc3_set_get_kcontrol_data(scontrol, false); + if (ret < 0) + dev_warn(sdev->dev, + "kcontrol %d read failed for widget %s\n", + scontrol->comp_id, swidget->widget->name); + } + + return 0; +} + +static int +sof_ipc3_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++) + scontrol->volume_table[i] = vol_compute_gain(i, tlv); + + return 0; +} + const struct sof_ipc_tplg_control_ops tplg_ipc3_control_ops = { .volume_put = sof_ipc3_volume_put, .volume_get = sof_ipc3_volume_get, @@ -591,4 +704,6 @@ const struct sof_ipc_tplg_control_ops tplg_ipc3_control_ops = { .bytes_ext_get = sof_ipc3_bytes_ext_get, .bytes_ext_volatile_get = sof_ipc3_bytes_ext_volatile_get, .update = sof_ipc3_control_update, + .widget_kcontrol_setup = sof_ipc3_widget_kcontrol_setup, + .set_up_volume_table = sof_ipc3_set_up_volume_table, }; diff --git a/sound/soc/sof/ipc3-dtrace.c b/sound/soc/sof/ipc3-dtrace.c new file mode 100644 index 000000000000..b4e1343f9138 --- /dev/null +++ b/sound/soc/sof/ipc3-dtrace.c @@ -0,0 +1,649 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> + +#include <linux/debugfs.h> +#include <linux/sched/signal.h> +#include "sof-priv.h" +#include "sof-audio.h" +#include "ops.h" +#include "sof-utils.h" +#include "ipc3-priv.h" + +#define TRACE_FILTER_ELEMENTS_PER_ENTRY 4 +#define TRACE_FILTER_MAX_CONFIG_STRING_LENGTH 1024 + +enum sof_dtrace_state { + SOF_DTRACE_DISABLED, + SOF_DTRACE_STOPPED, + SOF_DTRACE_ENABLED, +}; + +struct sof_dtrace_priv { + struct snd_dma_buffer dmatb; + struct snd_dma_buffer dmatp; + int dma_trace_pages; + wait_queue_head_t trace_sleep; + u32 host_offset; + bool dtrace_error; + bool dtrace_draining; + enum sof_dtrace_state dtrace_state; +}; + +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) +{ + if (*counter >= capacity) + return -ENOMEM; + + elem_list[*counter].key = key; + elem_list[*counter].value = value; + ++*counter; + + return 0; +} + +static int trace_filter_parse_entry(struct snd_sof_dev *sdev, const char *line, + struct sof_ipc_trace_filter_elem *elem, + int capacity, int *counter) +{ + int log_level, pipe_id, comp_id, read, ret; + int len = strlen(line); + int cnt = *counter; + u32 uuid_id; + + /* ignore empty content */ + ret = sscanf(line, " %n", &read); + if (!ret && read == len) + return len; + + ret = sscanf(line, " %d %x %d %d %n", &log_level, &uuid_id, &pipe_id, &comp_id, &read); + if (ret != TRACE_FILTER_ELEMENTS_PER_ENTRY || read != len) { + dev_err(sdev->dev, "Invalid trace filter entry '%s'\n", line); + return -EINVAL; + } + + if (uuid_id > 0) { + ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_BY_UUID, + uuid_id, elem, capacity, &cnt); + if (ret) + return ret; + } + if (pipe_id >= 0) { + ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_BY_PIPE, + pipe_id, elem, capacity, &cnt); + if (ret) + return ret; + } + if (comp_id >= 0) { + ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_BY_COMP, + comp_id, elem, capacity, &cnt); + if (ret) + return ret; + } + + ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_SET_LEVEL | + SOF_IPC_TRACE_FILTER_ELEM_FIN, + log_level, elem, capacity, &cnt); + if (ret) + return ret; + + /* update counter only when parsing whole entry passed */ + *counter = cnt; + + return len; +} + +static int trace_filter_parse(struct snd_sof_dev *sdev, char *string, + int *out_elem_cnt, + struct sof_ipc_trace_filter_elem **out) +{ + static const char entry_delimiter[] = ";"; + char *entry = string; + int capacity = 0; + int entry_len; + int cnt = 0; + + /* + * Each entry contains at least 1, up to TRACE_FILTER_ELEMENTS_PER_ENTRY + * IPC elements, depending on content. Calculate IPC elements capacity + * for the input string where each element is set. + */ + while (entry) { + capacity += TRACE_FILTER_ELEMENTS_PER_ENTRY; + entry = strchr(entry + 1, entry_delimiter[0]); + } + *out = kmalloc(capacity * sizeof(**out), GFP_KERNEL); + if (!*out) + return -ENOMEM; + + /* split input string by ';', and parse each entry separately in trace_filter_parse_entry */ + while ((entry = strsep(&string, entry_delimiter))) { + entry_len = trace_filter_parse_entry(sdev, entry, *out, capacity, &cnt); + if (entry_len < 0) { + dev_err(sdev->dev, + "Parsing filter entry '%s' failed with %d\n", + entry, entry_len); + return -EINVAL; + } + } + + *out_elem_cnt = cnt; + + return 0; +} + +static int ipc3_trace_update_filter(struct snd_sof_dev *sdev, int num_elems, + struct sof_ipc_trace_filter_elem *elems) +{ + struct sof_ipc_trace_filter *msg; + struct sof_ipc_reply reply; + size_t size; + int ret; + + size = struct_size(msg, elems, num_elems); + if (size > SOF_IPC_MSG_MAX_SIZE) + return -EINVAL; + + msg = kmalloc(size, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + msg->hdr.size = size; + msg->hdr.cmd = SOF_IPC_GLB_TRACE_MSG | SOF_IPC_TRACE_FILTER_UPDATE; + msg->elem_cnt = num_elems; + memcpy(&msg->elems[0], elems, num_elems * sizeof(*elems)); + + ret = pm_runtime_get_sync(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; + } + ret = sof_ipc_tx_message(sdev->ipc, msg, msg->hdr.size, &reply, sizeof(reply)); + pm_runtime_mark_last_busy(sdev->dev); + pm_runtime_put_autosuspend(sdev->dev); + +error: + kfree(msg); + return ret ? ret : reply.error; +} + +static ssize_t dfsentry_trace_filter_write(struct file *file, const char __user *from, + size_t count, loff_t *ppos) +{ + struct snd_sof_dfsentry *dfse = file->private_data; + struct sof_ipc_trace_filter_elem *elems = NULL; + struct snd_sof_dev *sdev = dfse->sdev; + loff_t pos = 0; + int num_elems; + char *string; + int ret; + + if (count > TRACE_FILTER_MAX_CONFIG_STRING_LENGTH) { + dev_err(sdev->dev, "%s too long input, %zu > %d\n", __func__, count, + TRACE_FILTER_MAX_CONFIG_STRING_LENGTH); + return -EINVAL; + } + + string = kmalloc(count + 1, GFP_KERNEL); + if (!string) + return -ENOMEM; + + /* assert null termination */ + string[count] = 0; + ret = simple_write_to_buffer(string, count, &pos, from, count); + if (ret < 0) + goto error; + + ret = trace_filter_parse(sdev, string, &num_elems, &elems); + if (ret < 0) + goto error; + + if (num_elems) { + ret = ipc3_trace_update_filter(sdev, num_elems, elems); + if (ret < 0) { + dev_err(sdev->dev, "Filter update failed: %d\n", ret); + goto error; + } + } + ret = count; +error: + kfree(string); + kfree(elems); + return ret; +} + +static const struct file_operations sof_dfs_trace_filter_fops = { + .open = simple_open, + .write = dfsentry_trace_filter_write, + .llseek = default_llseek, +}; + +static int debugfs_create_trace_filter(struct snd_sof_dev *sdev) +{ + struct snd_sof_dfsentry *dfse; + + dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); + if (!dfse) + return -ENOMEM; + + dfse->sdev = sdev; + dfse->type = SOF_DFSENTRY_TYPE_BUF; + + debugfs_create_file("filter", 0200, sdev->debugfs_root, dfse, + &sof_dfs_trace_filter_fops); + /* add to dfsentry list */ + list_add(&dfse->list, &sdev->dfsentry_list); + + return 0; +} + +static size_t sof_dtrace_avail(struct snd_sof_dev *sdev, + loff_t pos, size_t buffer_size) +{ + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + loff_t host_offset = READ_ONCE(priv->host_offset); + + /* + * If host offset is less than local pos, it means write pointer of + * host DMA buffer has been wrapped. We should output the trace data + * at the end of host DMA buffer at first. + */ + if (host_offset < pos) + return buffer_size - pos; + + /* If there is available trace data now, it is unnecessary to wait. */ + if (host_offset > pos) + return host_offset - pos; + + return 0; +} + +static size_t sof_wait_dtrace_avail(struct snd_sof_dev *sdev, loff_t pos, + size_t buffer_size) +{ + size_t ret = sof_dtrace_avail(sdev, pos, buffer_size); + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + wait_queue_entry_t wait; + + /* data immediately available */ + if (ret) + return ret; + + if (priv->dtrace_state != SOF_DTRACE_ENABLED && priv->dtrace_draining) { + /* + * tracing has ended and all traces have been + * read by client, return EOF + */ + priv->dtrace_draining = false; + return 0; + } + + /* wait for available trace data from FW */ + init_waitqueue_entry(&wait, current); + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&priv->trace_sleep, &wait); + + if (!signal_pending(current)) { + /* set timeout to max value, no error code */ + schedule_timeout(MAX_SCHEDULE_TIMEOUT); + } + remove_wait_queue(&priv->trace_sleep, &wait); + + return sof_dtrace_avail(sdev, pos, buffer_size); +} + +static ssize_t dfsentry_dtrace_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct snd_sof_dfsentry *dfse = file->private_data; + struct snd_sof_dev *sdev = dfse->sdev; + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + unsigned long rem; + loff_t lpos = *ppos; + size_t avail, buffer_size = dfse->size; + u64 lpos_64; + + /* make sure we know about any failures on the DSP side */ + priv->dtrace_error = false; + + /* check pos and count */ + if (lpos < 0) + return -EINVAL; + if (!count) + return 0; + + /* check for buffer wrap and count overflow */ + lpos_64 = lpos; + lpos = do_div(lpos_64, buffer_size); + + /* get available count based on current host offset */ + avail = sof_wait_dtrace_avail(sdev, lpos, buffer_size); + if (priv->dtrace_error) { + dev_err(sdev->dev, "trace IO error\n"); + return -EIO; + } + + /* make sure count is <= avail */ + if (count > avail) + count = avail; + + /* + * make sure that all trace data is available for the CPU as the trace + * data buffer might be allocated from non consistent memory. + * Note: snd_dma_buffer_sync() is called for normal audio playback and + * capture streams also. + */ + snd_dma_buffer_sync(&priv->dmatb, SNDRV_DMA_SYNC_CPU); + /* copy available trace data to debugfs */ + rem = copy_to_user(buffer, ((u8 *)(dfse->buf) + lpos), count); + if (rem) + return -EFAULT; + + *ppos += count; + + /* move debugfs reading position */ + return count; +} + +static int dfsentry_dtrace_release(struct inode *inode, struct file *file) +{ + struct snd_sof_dfsentry *dfse = inode->i_private; + struct snd_sof_dev *sdev = dfse->sdev; + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + + /* avoid duplicate traces at next open */ + if (priv->dtrace_state != SOF_DTRACE_ENABLED) + priv->host_offset = 0; + + return 0; +} + +static const struct file_operations sof_dfs_dtrace_fops = { + .open = simple_open, + .read = dfsentry_dtrace_read, + .llseek = default_llseek, + .release = dfsentry_dtrace_release, +}; + +static int debugfs_create_dtrace(struct snd_sof_dev *sdev) +{ + struct sof_dtrace_priv *priv; + struct snd_sof_dfsentry *dfse; + int ret; + + if (!sdev) + return -EINVAL; + + priv = sdev->fw_trace_data; + + ret = debugfs_create_trace_filter(sdev); + if (ret < 0) + dev_warn(sdev->dev, "failed to create filter debugfs file: %d", ret); + + dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); + if (!dfse) + return -ENOMEM; + + dfse->type = SOF_DFSENTRY_TYPE_BUF; + dfse->buf = priv->dmatb.area; + dfse->size = priv->dmatb.bytes; + dfse->sdev = sdev; + + debugfs_create_file("trace", 0444, sdev->debugfs_root, dfse, + &sof_dfs_dtrace_fops); + + return 0; +} + +static int ipc3_dtrace_enable(struct snd_sof_dev *sdev) +{ + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + struct sof_ipc_fw_ready *ready = &sdev->fw_ready; + struct sof_ipc_fw_version *v = &ready->version; + struct sof_ipc_dma_trace_params_ext params; + struct sof_ipc_reply ipc_reply; + int ret; + + if (!sdev->fw_trace_is_supported) + return 0; + + if (priv->dtrace_state == SOF_DTRACE_ENABLED || !priv->dma_trace_pages) + return -EINVAL; + + if (priv->dtrace_state == SOF_DTRACE_STOPPED) + goto start; + + /* set IPC parameters */ + params.hdr.cmd = SOF_IPC_GLB_TRACE_MSG; + /* PARAMS_EXT is only supported from ABI 3.7.0 onwards */ + if (v->abi_version >= SOF_ABI_VER(3, 7, 0)) { + params.hdr.size = sizeof(struct sof_ipc_dma_trace_params_ext); + params.hdr.cmd |= SOF_IPC_TRACE_DMA_PARAMS_EXT; + params.timestamp_ns = ktime_get(); /* in nanosecond */ + } else { + params.hdr.size = sizeof(struct sof_ipc_dma_trace_params); + params.hdr.cmd |= SOF_IPC_TRACE_DMA_PARAMS; + } + params.buffer.phy_addr = priv->dmatp.addr; + params.buffer.size = priv->dmatb.bytes; + params.buffer.pages = priv->dma_trace_pages; + params.stream_tag = 0; + + priv->host_offset = 0; + priv->dtrace_draining = false; + + ret = sof_dtrace_host_init(sdev, &priv->dmatb, ¶ms); + if (ret < 0) { + 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); + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, ¶ms, 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); + goto trace_release; + } + +start: + 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: + sof_dtrace_host_release(sdev); + return ret; +} + +static int ipc3_dtrace_init(struct snd_sof_dev *sdev) +{ + struct sof_dtrace_priv *priv; + int ret; + + /* dtrace is only supported with SOF_IPC */ + if (sdev->pdata->ipc_type != SOF_IPC) + return -EOPNOTSUPP; + + if (sdev->fw_trace_data) { + dev_err(sdev->dev, "fw_trace_data has been already allocated\n"); + return -EBUSY; + } + + priv = devm_kzalloc(sdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + sdev->fw_trace_data = priv; + + /* set false before start initialization */ + priv->dtrace_state = SOF_DTRACE_DISABLED; + + /* allocate trace page table buffer */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev, + PAGE_SIZE, &priv->dmatp); + if (ret < 0) { + dev_err(sdev->dev, "can't alloc page table for trace %d\n", ret); + return ret; + } + + /* allocate trace data buffer */ + ret = snd_dma_alloc_dir_pages(SNDRV_DMA_TYPE_DEV_SG, sdev->dev, + DMA_FROM_DEVICE, DMA_BUF_SIZE_FOR_TRACE, + &priv->dmatb); + if (ret < 0) { + dev_err(sdev->dev, "can't alloc buffer for trace %d\n", ret); + goto page_err; + } + + /* create compressed page table for audio firmware */ + ret = snd_sof_create_page_table(sdev->dev, &priv->dmatb, + priv->dmatp.area, priv->dmatb.bytes); + if (ret < 0) + goto table_err; + + priv->dma_trace_pages = ret; + dev_dbg(sdev->dev, "%s: dma_trace_pages: %d\n", __func__, + priv->dma_trace_pages); + + if (sdev->first_boot) { + ret = debugfs_create_dtrace(sdev); + if (ret < 0) + goto table_err; + } + + init_waitqueue_head(&priv->trace_sleep); + + ret = ipc3_dtrace_enable(sdev); + if (ret < 0) + goto table_err; + + return 0; +table_err: + priv->dma_trace_pages = 0; + snd_dma_free_pages(&priv->dmatb); +page_err: + snd_dma_free_pages(&priv->dmatp); + return ret; +} + +int ipc3_dtrace_posn_update(struct snd_sof_dev *sdev, + struct sof_ipc_dma_trace_posn *posn) +{ + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + + 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; + wake_up(&priv->trace_sleep); + } + + if (posn->overflow != 0) + dev_err(sdev->dev, + "DSP trace buffer overflow %u bytes. Total messages %d\n", + posn->overflow, posn->messages); + + return 0; +} + +/* an error has occurred within the DSP that prevents further trace */ +static void ipc3_dtrace_fw_crashed(struct snd_sof_dev *sdev) +{ + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + + if (priv->dtrace_state == SOF_DTRACE_ENABLED) { + priv->dtrace_error = true; + wake_up(&priv->trace_sleep); + } +} + +static void ipc3_dtrace_release(struct snd_sof_dev *sdev, bool only_stop) +{ + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + struct sof_ipc_fw_ready *ready = &sdev->fw_ready; + struct sof_ipc_fw_version *v = &ready->version; + struct sof_ipc_cmd_hdr hdr; + struct sof_ipc_reply ipc_reply; + int ret; + + if (!sdev->fw_trace_is_supported || priv->dtrace_state == SOF_DTRACE_DISABLED) + return; + + ret = sof_dtrace_host_trigger(sdev, SNDRV_PCM_TRIGGER_STOP); + if (ret < 0) + dev_err(sdev->dev, "Host dtrace trigger stop failed: %d\n", ret); + priv->dtrace_state = SOF_DTRACE_STOPPED; + + /* + * stop and free trace DMA in the DSP. TRACE_DMA_FREE is only supported from + * ABI 3.20.0 onwards + */ + if (v->abi_version >= SOF_ABI_VER(3, 20, 0)) { + hdr.size = sizeof(hdr); + hdr.cmd = SOF_IPC_GLB_TRACE_MSG | SOF_IPC_TRACE_DMA_FREE; + + ret = sof_ipc_tx_message(sdev->ipc, &hdr, hdr.size, + &ipc_reply, sizeof(ipc_reply)); + if (ret < 0) + dev_err(sdev->dev, "DMA_TRACE_FREE failed with error: %d\n", ret); + } + + if (only_stop) + goto out; + + ret = sof_dtrace_host_release(sdev); + if (ret < 0) + dev_err(sdev->dev, "Host dtrace release failed %d\n", ret); + + priv->dtrace_state = SOF_DTRACE_DISABLED; + +out: + priv->dtrace_draining = true; + wake_up(&priv->trace_sleep); +} + +static void ipc3_dtrace_suspend(struct snd_sof_dev *sdev, pm_message_t pm_state) +{ + ipc3_dtrace_release(sdev, pm_state.event == SOF_DSP_PM_D0); +} + +static int ipc3_dtrace_resume(struct snd_sof_dev *sdev) +{ + return ipc3_dtrace_enable(sdev); +} + +static void ipc3_dtrace_free(struct snd_sof_dev *sdev) +{ + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + + /* release trace */ + ipc3_dtrace_release(sdev, false); + + if (priv->dma_trace_pages) { + snd_dma_free_pages(&priv->dmatb); + snd_dma_free_pages(&priv->dmatp); + priv->dma_trace_pages = 0; + } +} + +const struct sof_ipc_fw_tracing_ops ipc3_dtrace_ops = { + .init = ipc3_dtrace_init, + .free = ipc3_dtrace_free, + .fw_crashed = ipc3_dtrace_fw_crashed, + .suspend = ipc3_dtrace_suspend, + .resume = ipc3_dtrace_resume, +}; diff --git a/sound/soc/sof/ipc3-loader.c b/sound/soc/sof/ipc3-loader.c new file mode 100644 index 000000000000..f3c741b49519 --- /dev/null +++ b/sound/soc/sof/ipc3-loader.c @@ -0,0 +1,415 @@ +// 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 <linux/firmware.h> +#include "sof-priv.h" +#include "sof-audio.h" +#include "ipc3-priv.h" +#include "ops.h" + +static int ipc3_fw_ext_man_get_version(struct snd_sof_dev *sdev, + const struct sof_ext_man_elem_header *hdr) +{ + const struct sof_ext_man_fw_version *v = + container_of(hdr, struct sof_ext_man_fw_version, hdr); + + memcpy(&sdev->fw_ready.version, &v->version, sizeof(v->version)); + sdev->fw_ready.flags = v->flags; + + /* log ABI versions and check FW compatibility */ + return sof_ipc3_validate_fw_version(sdev); +} + +static int ipc3_fw_ext_man_get_windows(struct snd_sof_dev *sdev, + const struct sof_ext_man_elem_header *hdr) +{ + const struct sof_ext_man_window *w; + + w = container_of(hdr, struct sof_ext_man_window, hdr); + + return sof_ipc3_get_ext_windows(sdev, &w->ipc_window.ext_hdr); +} + +static int ipc3_fw_ext_man_get_cc_info(struct snd_sof_dev *sdev, + const struct sof_ext_man_elem_header *hdr) +{ + const struct sof_ext_man_cc_version *cc; + + cc = container_of(hdr, struct sof_ext_man_cc_version, hdr); + + return sof_ipc3_get_cc_info(sdev, &cc->cc_version.ext_hdr); +} + +static int ipc3_fw_ext_man_get_dbg_abi_info(struct snd_sof_dev *sdev, + const struct sof_ext_man_elem_header *hdr) +{ + const struct ext_man_dbg_abi *dbg_abi = + container_of(hdr, struct ext_man_dbg_abi, hdr); + + if (sdev->first_boot) + dev_dbg(sdev->dev, + "Firmware: DBG_ABI %d:%d:%d\n", + SOF_ABI_VERSION_MAJOR(dbg_abi->dbg_abi.abi_dbg_version), + SOF_ABI_VERSION_MINOR(dbg_abi->dbg_abi.abi_dbg_version), + SOF_ABI_VERSION_PATCH(dbg_abi->dbg_abi.abi_dbg_version)); + + return 0; +} + +static int ipc3_fw_ext_man_get_config_data(struct snd_sof_dev *sdev, + const struct sof_ext_man_elem_header *hdr) +{ + const struct sof_ext_man_config_data *config = + container_of(hdr, struct sof_ext_man_config_data, hdr); + const struct sof_config_elem *elem; + int elems_counter; + int elems_size; + int ret = 0; + int i; + + /* calculate elements counter */ + 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); + + 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); + switch (elem->token) { + case SOF_EXT_MAN_CONFIG_EMPTY: + /* unused memory space is zero filled - mapped to EMPTY elements */ + break; + case SOF_EXT_MAN_CONFIG_IPC_MSG_SIZE: + /* TODO: use ipc msg size from config data */ + break; + case SOF_EXT_MAN_CONFIG_MEMORY_USAGE_SCAN: + if (sdev->first_boot && elem->value) + ret = snd_sof_dbg_memory_info_init(sdev); + break; + default: + dev_info(sdev->dev, + "Unknown firmware configuration token %d value %d", + elem->token, elem->value); + break; + } + if (ret < 0) { + dev_err(sdev->dev, + "%s: processing failed for token %d value %#x, %d\n", + __func__, elem->token, elem->value, ret); + return ret; + } + } + + return 0; +} + +static ssize_t ipc3_fw_ext_man_size(const struct firmware *fw) +{ + const struct sof_ext_man_header *head; + + head = (struct sof_ext_man_header *)fw->data; + + /* + * assert fw size is big enough to contain extended manifest header, + * it prevents from reading unallocated memory from `head` in following + * step. + */ + if (fw->size < sizeof(*head)) + return -EINVAL; + + /* + * When fw points to extended manifest, + * then first u32 must be equal SOF_EXT_MAN_MAGIC_NUMBER. + */ + if (head->magic == SOF_EXT_MAN_MAGIC_NUMBER) + return head->full_size; + + /* otherwise given fw don't have an extended manifest */ + return 0; +} + +static size_t sof_ipc3_fw_parse_ext_man(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + const struct firmware *fw = plat_data->fw; + const struct sof_ext_man_elem_header *elem_hdr; + const struct sof_ext_man_header *head; + ssize_t ext_man_size; + ssize_t remaining; + uintptr_t iptr; + int ret = 0; + + head = (struct sof_ext_man_header *)fw->data; + remaining = head->full_size - head->header_size; + ext_man_size = ipc3_fw_ext_man_size(fw); + + /* Assert firmware starts with extended manifest */ + if (ext_man_size <= 0) + return ext_man_size; + + /* incompatible version */ + if (SOF_EXT_MAN_VERSION_INCOMPATIBLE(SOF_EXT_MAN_VERSION, + head->header_version)) { + dev_err(sdev->dev, + "extended manifest version %#x differ from used %#x\n", + head->header_version, SOF_EXT_MAN_VERSION); + return -EINVAL; + } + + /* get first extended manifest element header */ + iptr = (uintptr_t)fw->data + head->header_size; + + while (remaining > sizeof(*elem_hdr)) { + elem_hdr = (struct sof_ext_man_elem_header *)iptr; + + dev_dbg(sdev->dev, "found sof_ext_man header type %d size %#x\n", + elem_hdr->type, elem_hdr->size); + + if (elem_hdr->size < sizeof(*elem_hdr) || + elem_hdr->size > remaining) { + dev_err(sdev->dev, + "invalid sof_ext_man header size, type %d size %#x\n", + elem_hdr->type, elem_hdr->size); + return -EINVAL; + } + + /* process structure data */ + switch (elem_hdr->type) { + case SOF_EXT_MAN_ELEM_FW_VERSION: + ret = ipc3_fw_ext_man_get_version(sdev, elem_hdr); + break; + case SOF_EXT_MAN_ELEM_WINDOW: + ret = ipc3_fw_ext_man_get_windows(sdev, elem_hdr); + break; + case SOF_EXT_MAN_ELEM_CC_VERSION: + ret = ipc3_fw_ext_man_get_cc_info(sdev, elem_hdr); + break; + case SOF_EXT_MAN_ELEM_DBG_ABI: + ret = ipc3_fw_ext_man_get_dbg_abi_info(sdev, elem_hdr); + break; + case SOF_EXT_MAN_ELEM_CONFIG_DATA: + ret = ipc3_fw_ext_man_get_config_data(sdev, elem_hdr); + break; + case SOF_EXT_MAN_ELEM_PLATFORM_CONFIG_DATA: + ret = snd_sof_dsp_parse_platform_ext_manifest(sdev, elem_hdr); + break; + default: + dev_info(sdev->dev, + "unknown sof_ext_man header type %d size %#x\n", + elem_hdr->type, elem_hdr->size); + break; + } + + if (ret < 0) { + dev_err(sdev->dev, + "failed to parse sof_ext_man header type %d size %#x\n", + elem_hdr->type, elem_hdr->size); + return ret; + } + + remaining -= elem_hdr->size; + iptr += elem_hdr->size; + } + + if (remaining) { + dev_err(sdev->dev, "error: sof_ext_man header is inconsistent\n"); + return -EINVAL; + } + + return ext_man_size; +} + +/* generic module parser for mmaped DSPs */ +static int sof_ipc3_parse_module_memcpy(struct snd_sof_dev *sdev, + struct snd_sof_mod_hdr *module) +{ + struct snd_sof_blk_hdr *block; + int count, ret; + u32 offset; + size_t remaining; + + dev_dbg(sdev->dev, "new module size %#x blocks %#x type %#x\n", + module->size, module->num_blocks, module->type); + + block = (struct snd_sof_blk_hdr *)((u8 *)module + sizeof(*module)); + + /* module->size doesn't include header size */ + remaining = module->size; + for (count = 0; count < module->num_blocks; count++) { + /* check for wrap */ + if (remaining < sizeof(*block)) { + dev_err(sdev->dev, "not enough data remaining\n"); + return -EINVAL; + } + + /* minus header size of block */ + remaining -= sizeof(*block); + + if (block->size == 0) { + dev_warn(sdev->dev, + "warning: block %d size zero\n", count); + dev_warn(sdev->dev, " type %#x offset %#x\n", + block->type, block->offset); + continue; + } + + switch (block->type) { + case SOF_FW_BLK_TYPE_RSRVD0: + case SOF_FW_BLK_TYPE_ROM...SOF_FW_BLK_TYPE_RSRVD14: + continue; /* not handled atm */ + case SOF_FW_BLK_TYPE_IRAM: + case SOF_FW_BLK_TYPE_DRAM: + case SOF_FW_BLK_TYPE_SRAM: + offset = block->offset; + break; + default: + dev_err(sdev->dev, "%s: bad type %#x for block %#x\n", + __func__, block->type, count); + return -EINVAL; + } + + dev_dbg(sdev->dev, "block %d type %#x size %#x ==> offset %#x\n", + count, block->type, block->size, offset); + + /* checking block->size to avoid unaligned access */ + if (block->size % sizeof(u32)) { + dev_err(sdev->dev, "%s: invalid block size %#x\n", + __func__, block->size); + return -EINVAL; + } + ret = snd_sof_dsp_block_write(sdev, block->type, offset, + block + 1, block->size); + if (ret < 0) { + dev_err(sdev->dev, "%s: write to block type %#x failed\n", + __func__, block->type); + return ret; + } + + if (remaining < block->size) { + dev_err(sdev->dev, "%s: not enough data remaining\n", __func__); + return -EINVAL; + } + + /* minus body size of block */ + remaining -= block->size; + /* next block */ + block = (struct snd_sof_blk_hdr *)((u8 *)block + sizeof(*block) + + block->size); + } + + return 0; +} + +static int sof_ipc3_load_fw_to_dsp(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + const struct firmware *fw = plat_data->fw; + struct snd_sof_fw_header *header; + struct snd_sof_mod_hdr *module; + int (*load_module)(struct snd_sof_dev *sof_dev, struct snd_sof_mod_hdr *hdr); + size_t remaining; + int ret, count; + + if (!plat_data->fw) + return -EINVAL; + + 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__); + load_module = sof_ipc3_parse_module_memcpy; + } else { + dev_dbg(sdev->dev, "%s: Using custom module loading\n", __func__); + } + + /* parse each module */ + module = (struct snd_sof_mod_hdr *)(fw->data + plat_data->fw_offset + + sizeof(*header)); + remaining = fw->size - sizeof(*header) - plat_data->fw_offset; + /* check for wrap */ + if (remaining > fw->size) { + dev_err(sdev->dev, "%s: fw size smaller than header size\n", __func__); + return -EINVAL; + } + + for (count = 0; count < header->num_modules; count++) { + /* check for wrap */ + if (remaining < sizeof(*module)) { + dev_err(sdev->dev, "%s: not enough data for a module\n", + __func__); + return -EINVAL; + } + + /* minus header size of module */ + remaining -= sizeof(*module); + + /* module */ + ret = load_module(sdev, module); + if (ret < 0) { + dev_err(sdev->dev, "%s: invalid module %d\n", __func__, count); + return ret; + } + + if (remaining < module->size) { + dev_err(sdev->dev, "%s: not enough data remaining\n", __func__); + return -EINVAL; + } + + /* minus body size of module */ + remaining -= module->size; + module = (struct snd_sof_mod_hdr *)((u8 *)module + + sizeof(*module) + module->size); + } + + return 0; +} + +static int sof_ipc3_validate_firmware(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + const struct firmware *fw = plat_data->fw; + struct snd_sof_fw_header *header; + size_t fw_size = fw->size - plat_data->fw_offset; + + if (fw->size <= plat_data->fw_offset) { + dev_err(sdev->dev, + "firmware size must be greater than firmware offset\n"); + return -EINVAL; + } + + /* Read the header information from the data pointer */ + header = (struct snd_sof_fw_header *)(fw->data + plat_data->fw_offset); + + /* verify FW sig */ + if (strncmp(header->sig, SND_SOF_FW_SIG, SND_SOF_FW_SIG_SIZE) != 0) { + dev_err(sdev->dev, "invalid firmware signature\n"); + return -EINVAL; + } + + /* check size is valid */ + if (fw_size != header->file_size + sizeof(*header)) { + dev_err(sdev->dev, + "invalid filesize mismatch got 0x%zx expected 0x%zx\n", + fw_size, header->file_size + sizeof(*header)); + return -EINVAL; + } + + dev_dbg(sdev->dev, "header size=0x%x modules=0x%x abi=0x%x size=%zu\n", + header->file_size, header->num_modules, + header->abi, sizeof(*header)); + + return 0; +} + +const struct sof_ipc_fw_loader_ops ipc3_loader_ops = { + .validate = sof_ipc3_validate_firmware, + .parse_ext_manifest = sof_ipc3_fw_parse_ext_man, + .load_fw_to_dsp = sof_ipc3_load_fw_to_dsp, +}; diff --git a/sound/soc/sof/ipc3-ops.h b/sound/soc/sof/ipc3-ops.h deleted file mode 100644 index a4784626a3d7..000000000000 --- a/sound/soc/sof/ipc3-ops.h +++ /dev/null @@ -1,21 +0,0 @@ -/* 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) 2021 Intel Corporation. All rights reserved. - * - * Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> - */ - -#ifndef __SOUND_SOC_SOF_IPC3_OPS_H -#define __SOUND_SOC_SOF_IPC3_OPS_H - -#include "sof-priv.h" - -extern const struct sof_ipc_tplg_ops ipc3_tplg_ops; -extern const struct sof_ipc_ops ipc3_ops; -extern const struct sof_ipc_tplg_control_ops tplg_ipc3_control_ops; -extern const struct sof_ipc_pcm_ops ipc3_pcm_ops; - -#endif diff --git a/sound/soc/sof/ipc3-pcm.c b/sound/soc/sof/ipc3-pcm.c index 58b75943cf6d..c8774a891d6f 100644 --- a/sound/soc/sof/ipc3-pcm.c +++ b/sound/soc/sof/ipc3-pcm.c @@ -8,7 +8,7 @@ // #include <sound/pcm_params.h> -#include "ipc3-ops.h" +#include "ipc3-priv.h" #include "ops.h" #include "sof-priv.h" #include "sof-audio.h" @@ -34,8 +34,7 @@ static int sof_ipc3_pcm_hw_free(struct snd_soc_component *component, stream.comp_id = spcm->stream[substream->stream].comp_id; /* send IPC to the DSP */ - return sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream, - sizeof(stream), &reply, sizeof(reply)); + return sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream), &reply, sizeof(reply)); } static int sof_ipc3_pcm_hw_params(struct snd_soc_component *component, @@ -119,7 +118,7 @@ static int sof_ipc3_pcm_hw_params(struct snd_soc_component *component, dev_dbg(component->dev, "stream_tag %d", pcm.params.stream_tag); /* send hw_params IPC to the DSP */ - ret = sof_ipc_tx_message(sdev->ipc, pcm.hdr.cmd, &pcm, sizeof(pcm), + ret = sof_ipc_tx_message(sdev->ipc, &pcm, sizeof(pcm), &ipc_params_reply, sizeof(ipc_params_reply)); if (ret < 0) { dev_err(component->dev, "HW params ipc failed for stream %d\n", @@ -175,8 +174,7 @@ static int sof_ipc3_pcm_trigger(struct snd_soc_component *component, } /* send IPC to the DSP */ - return sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream, - sizeof(stream), &reply, sizeof(reply)); + return sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream), &reply, sizeof(reply)); } static void ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, const char *link_name, diff --git a/sound/soc/sof/ipc3-priv.h b/sound/soc/sof/ipc3-priv.h new file mode 100644 index 000000000000..f5044202f3c5 --- /dev/null +++ b/sound/soc/sof/ipc3-priv.h @@ -0,0 +1,65 @@ +/* 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) 2021 Intel Corporation. All rights reserved. + */ + +#ifndef __SOUND_SOC_SOF_IPC3_PRIV_H +#define __SOUND_SOC_SOF_IPC3_PRIV_H + +#include "sof-priv.h" + +/* IPC3 specific ops */ +extern const struct sof_ipc_pcm_ops ipc3_pcm_ops; +extern const struct sof_ipc_tplg_ops ipc3_tplg_ops; +extern const struct sof_ipc_tplg_control_ops tplg_ipc3_control_ops; +extern const struct sof_ipc_fw_loader_ops ipc3_loader_ops; +extern const struct sof_ipc_fw_tracing_ops ipc3_dtrace_ops; + +/* helpers for fw_ready and ext_manifest parsing */ +int sof_ipc3_get_ext_windows(struct snd_sof_dev *sdev, + const struct sof_ipc_ext_data_hdr *ext_hdr); +int sof_ipc3_get_cc_info(struct snd_sof_dev *sdev, + const struct sof_ipc_ext_data_hdr *ext_hdr); +int sof_ipc3_validate_fw_version(struct snd_sof_dev *sdev); + +/* dtrace position update */ +int ipc3_dtrace_posn_update(struct snd_sof_dev *sdev, + struct sof_ipc_dma_trace_posn *posn); + +/* dtrace platform callback wrappers */ +static inline int sof_dtrace_host_init(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmatb, + struct sof_ipc_dma_trace_params_ext *dtrace_params) +{ + struct snd_sof_dsp_ops *dsp_ops = sdev->pdata->desc->ops; + + if (dsp_ops->trace_init) + return dsp_ops->trace_init(sdev, dmatb, dtrace_params); + + return 0; +} + +static inline int sof_dtrace_host_release(struct snd_sof_dev *sdev) +{ + struct snd_sof_dsp_ops *dsp_ops = sdev->pdata->desc->ops; + + if (dsp_ops->trace_release) + return dsp_ops->trace_release(sdev); + + return 0; +} + +static inline int sof_dtrace_host_trigger(struct snd_sof_dev *sdev, int cmd) +{ + struct snd_sof_dsp_ops *dsp_ops = sdev->pdata->desc->ops; + + if (dsp_ops->trace_trigger) + return dsp_ops->trace_trigger(sdev, cmd); + + return 0; +} + +#endif diff --git a/sound/soc/sof/ipc3-topology.c b/sound/soc/sof/ipc3-topology.c index 2f8450a8c0a1..043554d7cb4a 100644 --- a/sound/soc/sof/ipc3-topology.c +++ b/sound/soc/sof/ipc3-topology.c @@ -11,7 +11,7 @@ #include <sound/pcm_params.h> #include "sof-priv.h" #include "sof-audio.h" -#include "ipc3-ops.h" +#include "ipc3-priv.h" #include "ops.h" /* Full volume for default values */ @@ -20,7 +20,8 @@ struct sof_widget_data { int ctrl_type; int ipc_cmd; - struct sof_abi_hdr *pdata; + void *pdata; + size_t pdata_size; struct snd_sof_control *control; }; @@ -784,16 +785,26 @@ static int sof_get_control_data(struct snd_soc_component *scomp, } cdata = wdata[i].control->ipc_control_data; - wdata[i].pdata = cdata->data; - if (!wdata[i].pdata) - return -EINVAL; - /* make sure data is valid - data can be updated at runtime */ - if (widget->dobj.widget.kcontrol_type[i] == SND_SOC_TPLG_TYPE_BYTES && - wdata[i].pdata->magic != SOF_ABI_MAGIC) - return -EINVAL; + if (widget->dobj.widget.kcontrol_type[i] == SND_SOC_TPLG_TYPE_BYTES) { + /* make sure data is valid - data can be updated at runtime */ + if (cdata->data->magic != SOF_ABI_MAGIC) + return -EINVAL; + + wdata[i].pdata = cdata->data->data; + wdata[i].pdata_size = cdata->data->size; + } else { + /* points to the control data union */ + wdata[i].pdata = cdata->chanv; + /* + * wdata[i].control->size is calculated with struct_size + * and includes the size of struct sof_ipc_ctrl_data + */ + wdata[i].pdata_size = wdata[i].control->size - + sizeof(struct sof_ipc_ctrl_data); + } - *size += wdata[i].pdata->size; + *size += wdata[i].pdata_size; /* get data type */ switch (cdata->cmd) { @@ -876,10 +887,12 @@ static int sof_process_load(struct snd_soc_component *scomp, */ if (ipc_data_size) { for (i = 0; i < widget->num_kcontrols; i++) { - memcpy(&process->data[offset], - wdata[i].pdata->data, - wdata[i].pdata->size); - offset += wdata[i].pdata->size; + if (!wdata[i].pdata_size) + continue; + + memcpy(&process->data[offset], wdata[i].pdata, + wdata[i].pdata_size); + offset += wdata[i].pdata_size; } } @@ -1551,8 +1564,7 @@ static int sof_ipc3_route_setup(struct snd_sof_dev *sdev, struct snd_sof_route * sroute->sink_widget->widget->name); /* send ipc */ - ret = sof_ipc_tx_message(sdev->ipc, connect.hdr.cmd, &connect, sizeof(connect), - &reply, sizeof(reply)); + ret = sof_ipc_tx_message(sdev->ipc, &connect, sizeof(connect), &reply, sizeof(reply)); if (ret < 0) dev_err(sdev->dev, "%s: route %s -> %s failed\n", __func__, sroute->src_widget->widget->name, sroute->sink_widget->widget->name); @@ -1592,6 +1604,7 @@ static int sof_ipc3_control_load_bytes(struct snd_sof_dev *sdev, struct snd_sof_ if (scontrol->priv_size > 0) { memcpy(cdata->data, scontrol->priv, scontrol->priv_size); kfree(scontrol->priv); + scontrol->priv = NULL; if (cdata->data->magic != SOF_ABI_MAGIC) { dev_err(sdev->dev, "Wrong ABI magic 0x%08x.\n", cdata->data->magic); @@ -1697,7 +1710,7 @@ static int sof_ipc3_control_free(struct snd_sof_dev *sdev, struct snd_sof_contro fcomp.id = scontrol->comp_id; /* send IPC to the DSP */ - return sof_ipc_tx_message(sdev->ipc, fcomp.hdr.cmd, &fcomp, sizeof(fcomp), NULL, 0); + return sof_ipc_tx_message(sdev->ipc, &fcomp, sizeof(fcomp), NULL, 0); } /* send pcm params ipc */ @@ -1749,7 +1762,7 @@ static int sof_ipc3_keyword_detect_pcm_params(struct snd_sof_widget *swidget, in } /* send IPC to the DSP */ - ret = sof_ipc_tx_message(sdev->ipc, pcm.hdr.cmd, &pcm, sizeof(pcm), + ret = sof_ipc_tx_message(sdev->ipc, &pcm, sizeof(pcm), &ipc_params_reply, sizeof(ipc_params_reply)); if (ret < 0) dev_err(scomp->dev, "%s: PCM params failed for %s\n", __func__, @@ -1773,8 +1786,7 @@ static int sof_ipc3_keyword_detect_trigger(struct snd_sof_widget *swidget, int c stream.comp_id = swidget->comp_id; /* send IPC to the DSP */ - ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream, - sizeof(stream), &reply, sizeof(reply)); + ret = sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream), &reply, sizeof(reply)); if (ret < 0) dev_err(scomp->dev, "%s: Failed to trigger %s\n", __func__, swidget->widget->name); @@ -1902,8 +1914,7 @@ static int sof_ipc3_complete_pipeline(struct snd_sof_dev *sdev, struct snd_sof_w ready.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_PIPE_COMPLETE; ready.comp_id = swidget->comp_id; - ret = sof_ipc_tx_message(sdev->ipc, ready.hdr.cmd, &ready, sizeof(ready), &reply, - sizeof(reply)); + ret = sof_ipc_tx_message(sdev->ipc, &ready, sizeof(ready), &reply, sizeof(reply)); if (ret < 0) return ret; @@ -1939,7 +1950,7 @@ static int sof_ipc3_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget break; } - ret = sof_ipc_tx_message(sdev->ipc, ipc_free.hdr.cmd, &ipc_free, sizeof(ipc_free), + ret = sof_ipc_tx_message(sdev->ipc, &ipc_free, sizeof(ipc_free), &reply, sizeof(reply)); if (ret < 0) dev_err(sdev->dev, "failed to free widget %s\n", swidget->widget->name); @@ -2003,7 +2014,7 @@ static int sof_ipc3_dai_config(struct snd_sof_dev *sdev, struct snd_sof_widget * /* only send the IPC if the widget is set up in the DSP */ if (swidget->use_count > 0) { - ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size, + ret = sof_ipc_tx_message(sdev->ipc, config, config->hdr.size, &reply, sizeof(reply)); if (ret < 0) dev_err(sdev->dev, "Failed to set dai config for %s\n", dai->name); @@ -2028,7 +2039,7 @@ static int sof_ipc3_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget struct sof_dai_private_data *dai_data = dai->private; struct sof_ipc_comp *comp = &dai_data->comp_dai->comp; - ret = sof_ipc_tx_message(sdev->ipc, comp->hdr.cmd, dai_data->comp_dai, + ret = sof_ipc_tx_message(sdev->ipc, dai_data->comp_dai, comp->hdr.size, &reply, sizeof(reply)); break; } @@ -2037,8 +2048,8 @@ static int sof_ipc3_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget struct sof_ipc_pipe_new *pipeline; pipeline = swidget->private; - ret = sof_ipc_tx_message(sdev->ipc, pipeline->hdr.cmd, pipeline, - sizeof(*pipeline), &reply, sizeof(reply)); + ret = sof_ipc_tx_message(sdev->ipc, pipeline, sizeof(*pipeline), + &reply, sizeof(reply)); break; } default: @@ -2046,7 +2057,7 @@ static int sof_ipc3_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget struct sof_ipc_cmd_hdr *hdr; hdr = swidget->private; - ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd, swidget->private, hdr->size, + ret = sof_ipc_tx_message(sdev->ipc, swidget->private, hdr->size, &reply, sizeof(reply)); break; } @@ -2249,6 +2260,18 @@ static int sof_ipc3_tear_down_all_pipelines(struct snd_sof_dev *sdev, bool verif list_for_each_entry(sroute, &sdev->route_list, list) sroute->setup = false; + /* + * before suspending, make sure the refcounts are all zeroed out. There's no way + * to recover at this point but this will help root cause bad sequences leading to + * more issues on resume + */ + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (swidget->use_count != 0) { + dev_err(sdev->dev, "%s: widget %s is still in use: count %d\n", + __func__, swidget->widget->name, swidget->use_count); + } + } + return 0; } diff --git a/sound/soc/sof/ipc3.c b/sound/soc/sof/ipc3.c index 03e914b62728..dff5feaad370 100644 --- a/sound/soc/sof/ipc3.c +++ b/sound/soc/sof/ipc3.c @@ -7,8 +7,1035 @@ // // +#include <sound/sof/stream.h> +#include <sound/sof/control.h> #include "sof-priv.h" -#include "ipc3-ops.h" +#include "sof-audio.h" +#include "ipc3-priv.h" +#include "ops.h" + +typedef void (*ipc3_rx_callback)(struct snd_sof_dev *sdev, void *msg_buf); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_VERBOSE_IPC) +static void ipc3_log_header(struct device *dev, u8 *text, u32 cmd) +{ + u8 *str; + u8 *str2 = NULL; + u32 glb; + u32 type; + bool vdbg = false; + + glb = cmd & SOF_GLB_TYPE_MASK; + type = cmd & SOF_CMD_TYPE_MASK; + + switch (glb) { + case SOF_IPC_GLB_REPLY: + str = "GLB_REPLY"; break; + case SOF_IPC_GLB_COMPOUND: + str = "GLB_COMPOUND"; break; + case SOF_IPC_GLB_TPLG_MSG: + str = "GLB_TPLG_MSG"; + switch (type) { + case SOF_IPC_TPLG_COMP_NEW: + str2 = "COMP_NEW"; break; + case SOF_IPC_TPLG_COMP_FREE: + str2 = "COMP_FREE"; break; + case SOF_IPC_TPLG_COMP_CONNECT: + str2 = "COMP_CONNECT"; break; + case SOF_IPC_TPLG_PIPE_NEW: + str2 = "PIPE_NEW"; break; + case SOF_IPC_TPLG_PIPE_FREE: + str2 = "PIPE_FREE"; break; + case SOF_IPC_TPLG_PIPE_CONNECT: + str2 = "PIPE_CONNECT"; break; + case SOF_IPC_TPLG_PIPE_COMPLETE: + str2 = "PIPE_COMPLETE"; break; + case SOF_IPC_TPLG_BUFFER_NEW: + str2 = "BUFFER_NEW"; break; + case SOF_IPC_TPLG_BUFFER_FREE: + str2 = "BUFFER_FREE"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_PM_MSG: + str = "GLB_PM_MSG"; + switch (type) { + case SOF_IPC_PM_CTX_SAVE: + str2 = "CTX_SAVE"; break; + case SOF_IPC_PM_CTX_RESTORE: + str2 = "CTX_RESTORE"; break; + case SOF_IPC_PM_CTX_SIZE: + str2 = "CTX_SIZE"; break; + case SOF_IPC_PM_CLK_SET: + str2 = "CLK_SET"; break; + case SOF_IPC_PM_CLK_GET: + str2 = "CLK_GET"; break; + case SOF_IPC_PM_CLK_REQ: + str2 = "CLK_REQ"; break; + case SOF_IPC_PM_CORE_ENABLE: + str2 = "CORE_ENABLE"; break; + case SOF_IPC_PM_GATE: + str2 = "GATE"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_COMP_MSG: + str = "GLB_COMP_MSG"; + switch (type) { + case SOF_IPC_COMP_SET_VALUE: + str2 = "SET_VALUE"; break; + case SOF_IPC_COMP_GET_VALUE: + str2 = "GET_VALUE"; break; + case SOF_IPC_COMP_SET_DATA: + str2 = "SET_DATA"; break; + case SOF_IPC_COMP_GET_DATA: + str2 = "GET_DATA"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_STREAM_MSG: + str = "GLB_STREAM_MSG"; + switch (type) { + case SOF_IPC_STREAM_PCM_PARAMS: + str2 = "PCM_PARAMS"; break; + case SOF_IPC_STREAM_PCM_PARAMS_REPLY: + str2 = "PCM_REPLY"; break; + case SOF_IPC_STREAM_PCM_FREE: + str2 = "PCM_FREE"; break; + case SOF_IPC_STREAM_TRIG_START: + str2 = "TRIG_START"; break; + case SOF_IPC_STREAM_TRIG_STOP: + str2 = "TRIG_STOP"; break; + case SOF_IPC_STREAM_TRIG_PAUSE: + str2 = "TRIG_PAUSE"; break; + case SOF_IPC_STREAM_TRIG_RELEASE: + str2 = "TRIG_RELEASE"; break; + case SOF_IPC_STREAM_TRIG_DRAIN: + str2 = "TRIG_DRAIN"; break; + case SOF_IPC_STREAM_TRIG_XRUN: + str2 = "TRIG_XRUN"; break; + case SOF_IPC_STREAM_POSITION: + vdbg = true; + str2 = "POSITION"; break; + case SOF_IPC_STREAM_VORBIS_PARAMS: + str2 = "VORBIS_PARAMS"; break; + case SOF_IPC_STREAM_VORBIS_FREE: + str2 = "VORBIS_FREE"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_FW_READY: + str = "FW_READY"; break; + case SOF_IPC_GLB_DAI_MSG: + str = "GLB_DAI_MSG"; + switch (type) { + case SOF_IPC_DAI_CONFIG: + str2 = "CONFIG"; break; + case SOF_IPC_DAI_LOOPBACK: + str2 = "LOOPBACK"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_TRACE_MSG: + str = "GLB_TRACE_MSG"; + switch (type) { + case SOF_IPC_TRACE_DMA_PARAMS: + str2 = "DMA_PARAMS"; break; + case SOF_IPC_TRACE_DMA_POSITION: + str2 = "DMA_POSITION"; break; + case SOF_IPC_TRACE_DMA_PARAMS_EXT: + str2 = "DMA_PARAMS_EXT"; break; + case SOF_IPC_TRACE_FILTER_UPDATE: + str2 = "FILTER_UPDATE"; break; + case SOF_IPC_TRACE_DMA_FREE: + str2 = "DMA_FREE"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_TEST_MSG: + str = "GLB_TEST_MSG"; + switch (type) { + case SOF_IPC_TEST_IPC_FLOOD: + str2 = "IPC_FLOOD"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_DEBUG: + str = "GLB_DEBUG"; + switch (type) { + case SOF_IPC_DEBUG_MEM_USAGE: + str2 = "MEM_USAGE"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_PROBE: + str = "GLB_PROBE"; + switch (type) { + case SOF_IPC_PROBE_INIT: + str2 = "INIT"; break; + case SOF_IPC_PROBE_DEINIT: + str2 = "DEINIT"; break; + case SOF_IPC_PROBE_DMA_ADD: + str2 = "DMA_ADD"; break; + case SOF_IPC_PROBE_DMA_INFO: + str2 = "DMA_INFO"; break; + case SOF_IPC_PROBE_DMA_REMOVE: + str2 = "DMA_REMOVE"; break; + case SOF_IPC_PROBE_POINT_ADD: + str2 = "POINT_ADD"; break; + case SOF_IPC_PROBE_POINT_INFO: + str2 = "POINT_INFO"; break; + case SOF_IPC_PROBE_POINT_REMOVE: + str2 = "POINT_REMOVE"; break; + default: + str2 = "unknown type"; break; + } + break; + default: + str = "unknown GLB command"; break; + } + + if (str2) { + if (vdbg) + dev_vdbg(dev, "%s: 0x%x: %s: %s\n", text, cmd, str, str2); + else + dev_dbg(dev, "%s: 0x%x: %s: %s\n", text, cmd, str, str2); + } else { + dev_dbg(dev, "%s: 0x%x: %s\n", text, cmd, str); + } +} +#else +static inline void ipc3_log_header(struct device *dev, u8 *text, u32 cmd) +{ + if ((cmd & SOF_GLB_TYPE_MASK) != SOF_IPC_GLB_TRACE_MSG) + dev_dbg(dev, "%s: 0x%x\n", text, cmd); +} +#endif + +static int sof_ipc3_get_reply(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc_msg *msg = sdev->msg; + struct sof_ipc_reply *reply; + int ret = 0; + + /* get the generic reply */ + reply = msg->reply_data; + snd_sof_dsp_mailbox_read(sdev, sdev->host_box.offset, reply, sizeof(*reply)); + + if (reply->error < 0) + return reply->error; + + if (!reply->hdr.size) { + /* Reply should always be >= sizeof(struct sof_ipc_reply) */ + if (msg->reply_size) + dev_err(sdev->dev, + "empty reply received, expected %zu bytes\n", + msg->reply_size); + else + dev_err(sdev->dev, "empty reply received\n"); + + return -EINVAL; + } + + if (msg->reply_size > 0) { + if (reply->hdr.size == msg->reply_size) { + ret = 0; + } else if (reply->hdr.size < msg->reply_size) { + dev_dbg(sdev->dev, + "reply size (%u) is less than expected (%zu)\n", + reply->hdr.size, msg->reply_size); + + msg->reply_size = reply->hdr.size; + ret = 0; + } else { + dev_err(sdev->dev, + "reply size (%u) exceeds the buffer size (%zu)\n", + reply->hdr.size, msg->reply_size); + ret = -EINVAL; + } + + /* + * get the full message if reply->hdr.size <= msg->reply_size + * and the reply->hdr.size > sizeof(struct sof_ipc_reply) + */ + if (!ret && msg->reply_size > sizeof(*reply)) + snd_sof_dsp_mailbox_read(sdev, sdev->host_box.offset, + msg->reply_data, msg->reply_size); + } + + return ret; +} + +/* wait for IPC message reply */ +static int ipc3_wait_tx_done(struct snd_sof_ipc *ipc, void *reply_data) +{ + struct snd_sof_ipc_msg *msg = &ipc->msg; + struct sof_ipc_cmd_hdr *hdr = msg->msg_data; + struct snd_sof_dev *sdev = ipc->sdev; + int ret; + + /* wait for DSP IPC completion */ + ret = wait_event_timeout(msg->waitq, msg->ipc_complete, + msecs_to_jiffies(sdev->ipc_timeout)); + + if (ret == 0) { + 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); + ret = -ETIMEDOUT; + } else { + ret = msg->reply_error; + if (ret < 0) { + dev_err(sdev->dev, + "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 (msg->reply_size) + /* copy the data returned from DSP */ + memcpy(reply_data, msg->reply_data, + msg->reply_size); + } + + /* re-enable dumps after successful IPC tx */ + if (sdev->ipc_dump_printed) { + sdev->dbg_dump_printed = false; + sdev->ipc_dump_printed = false; + } + } + + return ret; +} + +/* send IPC message from host to DSP */ +static int ipc3_tx_msg_unlocked(struct snd_sof_ipc *ipc, + void *msg_data, size_t msg_bytes, + void *reply_data, size_t reply_bytes) +{ + struct sof_ipc_cmd_hdr *hdr = msg_data; + struct snd_sof_dev *sdev = ipc->sdev; + int ret; + + ret = sof_ipc_send_msg(sdev, msg_data, msg_bytes, reply_bytes); + + if (ret) { + dev_err_ratelimited(sdev->dev, + "%s: ipc message send for %#x failed: %d\n", + __func__, hdr->cmd, ret); + return ret; + } + + ipc3_log_header(sdev->dev, "ipc tx", hdr->cmd); + + /* now wait for completion */ + return ipc3_wait_tx_done(ipc, reply_data); +} + +static int sof_ipc3_tx_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes, + void *reply_data, size_t reply_bytes, bool no_pm) +{ + struct snd_sof_ipc *ipc = sdev->ipc; + int ret; + + if (!msg_data || msg_bytes < sizeof(struct sof_ipc_cmd_hdr)) { + dev_err_ratelimited(sdev->dev, "No IPC message to send\n"); + return -EINVAL; + } + + if (!no_pm) { + const struct sof_dsp_power_state target_state = { + .state = SOF_DSP_PM_D0, + }; + + /* ensure the DSP is in D0 before sending a new IPC */ + ret = snd_sof_dsp_set_power_state(sdev, &target_state); + if (ret < 0) { + dev_err(sdev->dev, "%s: resuming DSP failed: %d\n", + __func__, ret); + return ret; + } + } + + /* Serialise IPC TX */ + mutex_lock(&ipc->tx_mutex); + + ret = ipc3_tx_msg_unlocked(ipc, msg_data, msg_bytes, reply_data, reply_bytes); + + mutex_unlock(&ipc->tx_mutex); + + return ret; +} + +static int sof_ipc3_set_get_data(struct snd_sof_dev *sdev, void *data, size_t data_bytes, + bool set) +{ + size_t msg_bytes, hdr_bytes, payload_size, send_bytes; + struct sof_ipc_ctrl_data *cdata = data; + struct sof_ipc_ctrl_data *cdata_chunk; + struct snd_sof_ipc *ipc = sdev->ipc; + size_t offset = 0; + u8 *src, *dst; + u32 num_msg; + int ret = 0; + int i; + + if (!cdata || data_bytes < sizeof(*cdata)) + return -EINVAL; + + if ((cdata->rhdr.hdr.cmd & SOF_GLB_TYPE_MASK) != SOF_IPC_GLB_COMP_MSG) { + dev_err(sdev->dev, "%s: Not supported message type of %#x\n", + __func__, cdata->rhdr.hdr.cmd); + return -EINVAL; + } + + /* send normal size ipc in one part */ + if (cdata->rhdr.hdr.size <= ipc->max_payload_size) + return sof_ipc3_tx_msg(sdev, cdata, cdata->rhdr.hdr.size, + cdata, cdata->rhdr.hdr.size, false); + + cdata_chunk = kzalloc(ipc->max_payload_size, GFP_KERNEL); + if (!cdata_chunk) + return -ENOMEM; + + switch (cdata->type) { + case SOF_CTRL_TYPE_VALUE_CHAN_GET: + case SOF_CTRL_TYPE_VALUE_CHAN_SET: + hdr_bytes = sizeof(struct sof_ipc_ctrl_data); + if (set) { + src = (u8 *)cdata->chanv; + dst = (u8 *)cdata_chunk->chanv; + } else { + src = (u8 *)cdata_chunk->chanv; + dst = (u8 *)cdata->chanv; + } + break; + case SOF_CTRL_TYPE_DATA_GET: + case SOF_CTRL_TYPE_DATA_SET: + hdr_bytes = sizeof(struct sof_ipc_ctrl_data) + sizeof(struct sof_abi_hdr); + if (set) { + src = (u8 *)cdata->data->data; + dst = (u8 *)cdata_chunk->data->data; + } else { + src = (u8 *)cdata_chunk->data->data; + dst = (u8 *)cdata->data->data; + } + break; + default: + kfree(cdata_chunk); + return -EINVAL; + } + + msg_bytes = cdata->rhdr.hdr.size - hdr_bytes; + payload_size = ipc->max_payload_size - hdr_bytes; + num_msg = DIV_ROUND_UP(msg_bytes, payload_size); + + /* copy the header data */ + memcpy(cdata_chunk, cdata, hdr_bytes); + + /* Serialise IPC TX */ + mutex_lock(&sdev->ipc->tx_mutex); + + /* copy the payload data in a loop */ + for (i = 0; i < num_msg; i++) { + send_bytes = min(msg_bytes, payload_size); + cdata_chunk->num_elems = send_bytes; + cdata_chunk->rhdr.hdr.size = hdr_bytes + send_bytes; + cdata_chunk->msg_index = i; + msg_bytes -= send_bytes; + cdata_chunk->elems_remaining = msg_bytes; + + if (set) + memcpy(dst, src + offset, send_bytes); + + ret = ipc3_tx_msg_unlocked(sdev->ipc, + cdata_chunk, cdata_chunk->rhdr.hdr.size, + cdata_chunk, cdata_chunk->rhdr.hdr.size); + if (ret < 0) + break; + + if (!set) + memcpy(dst + offset, src, send_bytes); + + offset += payload_size; + } + + mutex_unlock(&sdev->ipc->tx_mutex); + + kfree(cdata_chunk); + + return ret; +} + +int sof_ipc3_get_ext_windows(struct snd_sof_dev *sdev, + const struct sof_ipc_ext_data_hdr *ext_hdr) +{ + const struct sof_ipc_window *w = + container_of(ext_hdr, struct sof_ipc_window, ext_hdr); + + if (w->num_windows == 0 || w->num_windows > SOF_IPC_MAX_ELEMS) + return -EINVAL; + + if (sdev->info_window) { + if (memcmp(sdev->info_window, w, ext_hdr->hdr.size)) { + dev_err(sdev->dev, "mismatch between window descriptor from extended manifest and mailbox"); + return -EINVAL; + } + return 0; + } + + /* keep a local copy of the data */ + sdev->info_window = devm_kmemdup(sdev->dev, w, ext_hdr->hdr.size, GFP_KERNEL); + if (!sdev->info_window) + return -ENOMEM; + + return 0; +} + +int sof_ipc3_get_cc_info(struct snd_sof_dev *sdev, + const struct sof_ipc_ext_data_hdr *ext_hdr) +{ + int ret; + + const struct sof_ipc_cc_version *cc = + container_of(ext_hdr, struct sof_ipc_cc_version, ext_hdr); + + if (sdev->cc_version) { + if (memcmp(sdev->cc_version, cc, cc->ext_hdr.hdr.size)) { + dev_err(sdev->dev, + "Receive diverged cc_version descriptions"); + return -EINVAL; + } + return 0; + } + + dev_dbg(sdev->dev, + "Firmware info: used compiler %s %d:%d:%d%s used optimization flags %s\n", + cc->name, cc->major, cc->minor, cc->micro, cc->desc, cc->optim); + + /* create read-only cc_version debugfs to store compiler version info */ + /* use local copy of the cc_version to prevent data corruption */ + if (sdev->first_boot) { + sdev->cc_version = devm_kmalloc(sdev->dev, cc->ext_hdr.hdr.size, + GFP_KERNEL); + + if (!sdev->cc_version) + return -ENOMEM; + + memcpy(sdev->cc_version, cc, cc->ext_hdr.hdr.size); + ret = snd_sof_debugfs_buf_item(sdev, sdev->cc_version, + cc->ext_hdr.hdr.size, + "cc_version", 0444); + + /* errors are only due to memory allocation, not debugfs */ + if (ret < 0) { + dev_err(sdev->dev, "snd_sof_debugfs_buf_item failed\n"); + return ret; + } + } + + return 0; +} + +/* parse the extended FW boot data structures from FW boot message */ +static int ipc3_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 offset) +{ + struct sof_ipc_ext_data_hdr *ext_hdr; + void *ext_data; + int ret = 0; + + ext_data = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!ext_data) + return -ENOMEM; + + /* get first header */ + snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, offset, ext_data, + sizeof(*ext_hdr)); + ext_hdr = ext_data; + + while (ext_hdr->hdr.cmd == SOF_IPC_FW_READY) { + /* read in ext structure */ + snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, + offset + sizeof(*ext_hdr), + (void *)((u8 *)ext_data + sizeof(*ext_hdr)), + ext_hdr->hdr.size - sizeof(*ext_hdr)); + + dev_dbg(sdev->dev, "found ext header type %d size 0x%x\n", + ext_hdr->type, ext_hdr->hdr.size); + + /* process structure data */ + switch (ext_hdr->type) { + case SOF_IPC_EXT_WINDOW: + ret = sof_ipc3_get_ext_windows(sdev, ext_hdr); + break; + case SOF_IPC_EXT_CC_INFO: + ret = sof_ipc3_get_cc_info(sdev, ext_hdr); + break; + case SOF_IPC_EXT_UNUSED: + case SOF_IPC_EXT_PROBE_INFO: + case SOF_IPC_EXT_USER_ABI_INFO: + /* They are supported but we don't do anything here */ + break; + default: + dev_info(sdev->dev, "unknown ext header type %d size 0x%x\n", + ext_hdr->type, ext_hdr->hdr.size); + ret = 0; + break; + } + + if (ret < 0) { + dev_err(sdev->dev, "Failed to parse ext data type %d\n", + ext_hdr->type); + break; + } + + /* move to next header */ + offset += ext_hdr->hdr.size; + snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, offset, ext_data, + sizeof(*ext_hdr)); + ext_hdr = ext_data; + } + + kfree(ext_data); + return ret; +} + +static void ipc3_get_windows(struct snd_sof_dev *sdev) +{ + struct sof_ipc_window_elem *elem; + u32 outbox_offset = 0; + u32 stream_offset = 0; + u32 inbox_offset = 0; + u32 outbox_size = 0; + u32 stream_size = 0; + u32 inbox_size = 0; + u32 debug_size = 0; + u32 debug_offset = 0; + int window_offset; + int i; + + if (!sdev->info_window) { + dev_err(sdev->dev, "%s: No window info present\n", __func__); + return; + } + + for (i = 0; i < sdev->info_window->num_windows; i++) { + elem = &sdev->info_window->window[i]; + + window_offset = snd_sof_dsp_get_window_offset(sdev, elem->id); + if (window_offset < 0) { + dev_warn(sdev->dev, "No offset for window %d\n", elem->id); + continue; + } + + switch (elem->type) { + case SOF_IPC_REGION_UPBOX: + inbox_offset = window_offset + elem->offset; + inbox_size = elem->size; + snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, + inbox_offset, + elem->size, "inbox", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_DOWNBOX: + outbox_offset = window_offset + elem->offset; + outbox_size = elem->size; + snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, + outbox_offset, + elem->size, "outbox", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_TRACE: + snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, + window_offset + elem->offset, + elem->size, "etrace", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_DEBUG: + debug_offset = window_offset + elem->offset; + debug_size = elem->size; + snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, + window_offset + elem->offset, + elem->size, "debug", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_STREAM: + stream_offset = window_offset + elem->offset; + stream_size = elem->size; + snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, + stream_offset, + elem->size, "stream", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_REGS: + snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, + window_offset + elem->offset, + elem->size, "regs", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_EXCEPTION: + sdev->dsp_oops_offset = window_offset + elem->offset; + snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, + window_offset + elem->offset, + elem->size, "exception", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + default: + dev_err(sdev->dev, "%s: Illegal window info: %u\n", + __func__, elem->type); + return; + } + } + + if (outbox_size == 0 || inbox_size == 0) { + dev_err(sdev->dev, "%s: Illegal mailbox window\n", __func__); + return; + } + + sdev->dsp_box.offset = inbox_offset; + sdev->dsp_box.size = inbox_size; + + sdev->host_box.offset = outbox_offset; + sdev->host_box.size = outbox_size; + + sdev->stream_box.offset = stream_offset; + sdev->stream_box.size = stream_size; + + sdev->debug_box.offset = debug_offset; + sdev->debug_box.size = debug_size; + + dev_dbg(sdev->dev, " mailbox upstream 0x%x - size 0x%x\n", + inbox_offset, inbox_size); + dev_dbg(sdev->dev, " mailbox downstream 0x%x - size 0x%x\n", + outbox_offset, outbox_size); + dev_dbg(sdev->dev, " stream region 0x%x - size 0x%x\n", + stream_offset, stream_size); + dev_dbg(sdev->dev, " debug region 0x%x - size 0x%x\n", + debug_offset, debug_size); +} + +static int ipc3_init_reply_data_buffer(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc_msg *msg = &sdev->ipc->msg; + + msg->reply_data = devm_kzalloc(sdev->dev, SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); + if (!msg->reply_data) + return -ENOMEM; + + sdev->ipc->max_payload_size = SOF_IPC_MSG_MAX_SIZE; + + return 0; +} + +int sof_ipc3_validate_fw_version(struct snd_sof_dev *sdev) +{ + struct sof_ipc_fw_ready *ready = &sdev->fw_ready; + struct sof_ipc_fw_version *v = &ready->version; + + dev_info(sdev->dev, + "Firmware info: version %d:%d:%d-%s\n", v->major, v->minor, + v->micro, v->tag); + dev_info(sdev->dev, + "Firmware: ABI %d:%d:%d Kernel ABI %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), + SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH); + + if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, v->abi_version)) { + dev_err(sdev->dev, "incompatible FW ABI version\n"); + 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 (ready->flags & SOF_IPC_INFO_BUILD) { + dev_info(sdev->dev, + "Firmware debug build %d on %s-%s - options:\n" + " GDB: %s\n" + " lock debug: %s\n" + " lock vdebug: %s\n", + v->build, v->date, v->time, + (ready->flags & SOF_IPC_INFO_GDB) ? + "enabled" : "disabled", + (ready->flags & SOF_IPC_INFO_LOCKS) ? + "enabled" : "disabled", + (ready->flags & SOF_IPC_INFO_LOCKSV) ? + "enabled" : "disabled"); + } + + /* copy the fw_version into debugfs at first boot */ + memcpy(&sdev->fw_version, v, sizeof(*v)); + + return 0; +} + +static int ipc3_fw_ready(struct snd_sof_dev *sdev, u32 cmd) +{ + struct sof_ipc_fw_ready *fw_ready = &sdev->fw_ready; + int offset; + int ret; + + /* mailbox must be on 4k boundary */ + offset = snd_sof_dsp_get_mailbox_offset(sdev); + if (offset < 0) { + dev_err(sdev->dev, "%s: no mailbox offset\n", __func__); + return offset; + } + + dev_dbg(sdev->dev, "DSP is ready 0x%8.8x offset 0x%x\n", cmd, offset); + + /* no need to re-check version/ABI for subsequent boots */ + if (!sdev->first_boot) + return 0; + + /* + * copy data from the DSP FW ready offset + * Subsequent error handling is not needed for BLK_TYPE_SRAM + */ + ret = snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, offset, fw_ready, + sizeof(*fw_ready)); + if (ret) { + dev_err(sdev->dev, + "Unable to read fw_ready, read from TYPE_SRAM failed\n"); + return ret; + } + + /* make sure ABI version is compatible */ + ret = sof_ipc3_validate_fw_version(sdev); + if (ret < 0) + return ret; + + /* now check for extended data */ + ipc3_fw_parse_ext_data(sdev, offset + sizeof(struct sof_ipc_fw_ready)); + + ipc3_get_windows(sdev); + + return ipc3_init_reply_data_buffer(sdev); +} + +/* IPC stream position. */ +static void ipc3_period_elapsed(struct snd_sof_dev *sdev, u32 msg_id) +{ + struct snd_soc_component *scomp = sdev->component; + struct snd_sof_pcm_stream *stream; + struct sof_ipc_stream_posn posn; + struct snd_sof_pcm *spcm; + int direction, ret; + + spcm = snd_sof_find_spcm_comp(scomp, msg_id, &direction); + if (!spcm) { + dev_err(sdev->dev, "period elapsed for unknown stream, msg_id %d\n", + msg_id); + return; + } + + stream = &spcm->stream[direction]; + ret = snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); + if (ret < 0) { + dev_warn(sdev->dev, "failed to read stream position: %d\n", ret); + return; + } + + dev_vdbg(sdev->dev, "posn : host 0x%llx dai 0x%llx wall 0x%llx\n", + posn.host_posn, posn.dai_posn, posn.wallclock); + + memcpy(&stream->posn, &posn, sizeof(posn)); + + if (spcm->pcm.compress) + snd_sof_compr_fragment_elapsed(stream->cstream); + else if (stream->substream->runtime && + !stream->substream->runtime->no_period_wakeup) + /* only inform ALSA for period_wakeup mode */ + snd_sof_pcm_period_elapsed(stream->substream); +} + +/* DSP notifies host of an XRUN within FW */ +static void ipc3_xrun(struct snd_sof_dev *sdev, u32 msg_id) +{ + struct snd_soc_component *scomp = sdev->component; + struct snd_sof_pcm_stream *stream; + struct sof_ipc_stream_posn posn; + struct snd_sof_pcm *spcm; + int direction, ret; + + spcm = snd_sof_find_spcm_comp(scomp, msg_id, &direction); + if (!spcm) { + dev_err(sdev->dev, "XRUN for unknown stream, msg_id %d\n", + msg_id); + return; + } + + stream = &spcm->stream[direction]; + ret = snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); + if (ret < 0) { + dev_warn(sdev->dev, "failed to read overrun position: %d\n", ret); + return; + } + + dev_dbg(sdev->dev, "posn XRUN: host %llx comp %d size %d\n", + posn.host_posn, posn.xrun_comp_id, posn.xrun_size); + +#if defined(CONFIG_SND_SOC_SOF_DEBUG_XRUN_STOP) + /* stop PCM on XRUN - used for pipeline debug */ + memcpy(&stream->posn, &posn, sizeof(posn)); + snd_pcm_stop_xrun(stream->substream); +#endif +} + +/* stream notifications from firmware */ +static void ipc3_stream_message(struct snd_sof_dev *sdev, void *msg_buf) +{ + struct sof_ipc_cmd_hdr *hdr = msg_buf; + u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK; + u32 msg_id = SOF_IPC_MESSAGE_ID(hdr->cmd); + + switch (msg_type) { + case SOF_IPC_STREAM_POSITION: + ipc3_period_elapsed(sdev, msg_id); + break; + case SOF_IPC_STREAM_TRIG_XRUN: + ipc3_xrun(sdev, msg_id); + break; + default: + dev_err(sdev->dev, "unhandled stream message %#x\n", + msg_id); + break; + } +} + +/* component notifications from firmware */ +static void ipc3_comp_notification(struct snd_sof_dev *sdev, void *msg_buf) +{ + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; + struct sof_ipc_cmd_hdr *hdr = msg_buf; + u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK; + + switch (msg_type) { + case SOF_IPC_COMP_GET_VALUE: + case SOF_IPC_COMP_GET_DATA: + break; + default: + dev_err(sdev->dev, "unhandled component message %#x\n", msg_type); + return; + } + + if (tplg_ops->control->update) + tplg_ops->control->update(sdev, msg_buf); +} + +static void ipc3_trace_message(struct snd_sof_dev *sdev, void *msg_buf) +{ + struct sof_ipc_cmd_hdr *hdr = msg_buf; + u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK; + + switch (msg_type) { + case SOF_IPC_TRACE_DMA_POSITION: + ipc3_dtrace_posn_update(sdev, msg_buf); + break; + default: + dev_err(sdev->dev, "unhandled trace message %#x\n", msg_type); + break; + } +} + +/* DSP firmware has sent host a message */ +static void sof_ipc3_rx_msg(struct snd_sof_dev *sdev) +{ + ipc3_rx_callback rx_callback = NULL; + struct sof_ipc_cmd_hdr hdr; + void *msg_buf; + u32 cmd; + int err; + + /* read back header */ + err = snd_sof_ipc_msg_data(sdev, NULL, &hdr, sizeof(hdr)); + if (err < 0) { + dev_warn(sdev->dev, "failed to read IPC header: %d\n", err); + return; + } + + if (hdr.size < sizeof(hdr)) { + dev_err(sdev->dev, "The received message size is invalid\n"); + return; + } + + ipc3_log_header(sdev->dev, "ipc rx", hdr.cmd); + + cmd = hdr.cmd & SOF_GLB_TYPE_MASK; + + /* check message type */ + switch (cmd) { + case SOF_IPC_GLB_REPLY: + dev_err(sdev->dev, "ipc reply unknown\n"); + break; + case SOF_IPC_FW_READY: + /* check for FW boot completion */ + if (sdev->fw_state == SOF_FW_BOOT_IN_PROGRESS) { + err = ipc3_fw_ready(sdev, cmd); + if (err < 0) + sof_set_fw_state(sdev, SOF_FW_BOOT_READY_FAILED); + else + sof_set_fw_state(sdev, SOF_FW_BOOT_READY_OK); + + /* wake up firmware loader */ + wake_up(&sdev->boot_wait); + } + break; + case SOF_IPC_GLB_COMPOUND: + case SOF_IPC_GLB_TPLG_MSG: + case SOF_IPC_GLB_PM_MSG: + break; + case SOF_IPC_GLB_COMP_MSG: + rx_callback = ipc3_comp_notification; + break; + case SOF_IPC_GLB_STREAM_MSG: + rx_callback = ipc3_stream_message; + break; + case SOF_IPC_GLB_TRACE_MSG: + rx_callback = ipc3_trace_message; + break; + default: + dev_err(sdev->dev, "%s: Unknown DSP message: 0x%x\n", __func__, cmd); + break; + } + + /* read the full message */ + msg_buf = kmalloc(hdr.size, GFP_KERNEL); + if (!msg_buf) + return; + + err = snd_sof_ipc_msg_data(sdev, NULL, msg_buf, hdr.size); + if (err < 0) { + dev_err(sdev->dev, "%s: Failed to read message: %d\n", __func__, err); + } else { + /* Call local handler for the message */ + if (rx_callback) + rx_callback(sdev, msg_buf); + + /* Notify registered clients */ + sof_client_ipc_rx_dispatcher(sdev, msg_buf); + } + + kfree(msg_buf); + + ipc3_log_header(sdev->dev, "ipc rx done", hdr.cmd); +} static int sof_ipc3_ctx_ipc(struct snd_sof_dev *sdev, int cmd) { @@ -19,8 +1046,8 @@ static int sof_ipc3_ctx_ipc(struct snd_sof_dev *sdev, int cmd) struct sof_ipc_reply reply; /* send ctx save ipc to dsp */ - return sof_ipc_tx_message(sdev->ipc, pm_ctx.hdr.cmd, &pm_ctx, - sizeof(pm_ctx), &reply, sizeof(reply)); + return sof_ipc3_tx_msg(sdev, &pm_ctx, sizeof(pm_ctx), + &reply, sizeof(reply), false); } static int sof_ipc3_ctx_save(struct snd_sof_dev *sdev) @@ -42,4 +1069,11 @@ const struct sof_ipc_ops ipc3_ops = { .tplg = &ipc3_tplg_ops, .pm = &ipc3_pm_ops, .pcm = &ipc3_pcm_ops, + .fw_loader = &ipc3_loader_ops, + .fw_tracing = &ipc3_dtrace_ops, + + .tx_msg = sof_ipc3_tx_msg, + .rx_msg = sof_ipc3_rx_msg, + .set_get_data = sof_ipc3_set_get_data, + .get_reply = sof_ipc3_get_reply, }; diff --git a/sound/soc/sof/ipc4-loader.c b/sound/soc/sof/ipc4-loader.c new file mode 100644 index 000000000000..9fadae8fd011 --- /dev/null +++ b/sound/soc/sof/ipc4-loader.c @@ -0,0 +1,210 @@ +// 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 <linux/firmware.h> +#include <sound/sof/ext_manifest4.h> +#include <sound/sof/ipc4/header.h> +#include "ipc4-priv.h" +#include "sof-audio.h" +#include "sof-priv.h" +#include "ops.h" + +static size_t sof_ipc4_fw_parse_ext_man(struct snd_sof_dev *sdev) +{ + struct sof_ipc4_fw_data *ipc4_data = sdev->private; + struct snd_sof_pdata *plat_data = sdev->pdata; + struct sof_man4_fw_binary_header *fw_header; + const struct firmware *fw = plat_data->fw; + struct sof_ext_manifest4_hdr *ext_man_hdr; + struct sof_man4_module_config *fm_config; + struct sof_ipc4_fw_module *fw_module; + struct sof_man4_module *fm_entry; + ssize_t remaining; + u32 fw_hdr_offset; + int i; + + if (!ipc4_data) { + dev_err(sdev->dev, "%s: ipc4_data is not available\n", __func__); + return -EINVAL; + } + + remaining = fw->size; + if (remaining <= sizeof(*ext_man_hdr)) { + dev_err(sdev->dev, "Firmware size is too small: %zu\n", remaining); + return -EINVAL; + } + + ext_man_hdr = (struct sof_ext_manifest4_hdr *)fw->data; + + fw_hdr_offset = ipc4_data->manifest_fw_hdr_offset; + if (!fw_hdr_offset) + return -EINVAL; + + if (remaining <= ext_man_hdr->len + fw_hdr_offset + sizeof(*fw_header)) { + dev_err(sdev->dev, "Invalid firmware size %zu, should be at least %zu\n", + remaining, ext_man_hdr->len + fw_hdr_offset + sizeof(*fw_header)); + return -EINVAL; + } + + fw_header = (struct sof_man4_fw_binary_header *) + (fw->data + ext_man_hdr->len + fw_hdr_offset); + remaining -= (ext_man_hdr->len + fw_hdr_offset); + + if (remaining <= fw_header->len) { + dev_err(sdev->dev, "Invalid fw_header->len %u\n", fw_header->len); + return -EINVAL; + } + + dev_info(sdev->dev, "Loaded firmware version: %u.%u.%u.%u\n", + fw_header->major_version, fw_header->minor_version, + fw_header->hotfix_version, fw_header->build_version); + dev_dbg(sdev->dev, "Firmware name: %s, header length: %u, module count: %u\n", + fw_header->name, fw_header->len, fw_header->num_module_entries); + + ipc4_data->fw_modules = devm_kmalloc_array(sdev->dev, + fw_header->num_module_entries, + sizeof(*fw_module), GFP_KERNEL); + if (!ipc4_data->fw_modules) + return -ENOMEM; + + ipc4_data->num_fw_modules = fw_header->num_module_entries; + fw_module = ipc4_data->fw_modules; + + fm_entry = (struct sof_man4_module *)((u8 *)fw_header + fw_header->len); + remaining -= fw_header->len; + + if (remaining < fw_header->num_module_entries * sizeof(*fm_entry)) { + dev_err(sdev->dev, "Invalid num_module_entries %u\n", + fw_header->num_module_entries); + return -EINVAL; + } + + fm_config = (struct sof_man4_module_config *) + (fm_entry + fw_header->num_module_entries); + remaining -= (fw_header->num_module_entries * sizeof(*fm_entry)); + for (i = 0; i < fw_header->num_module_entries; i++) { + memcpy(&fw_module->man4_module_entry, fm_entry, sizeof(*fm_entry)); + + if (fm_entry->cfg_count) { + if (remaining < (fm_entry->cfg_offset + fm_entry->cfg_count) * + sizeof(*fm_config)) { + dev_err(sdev->dev, "Invalid module cfg_offset %u\n", + fm_entry->cfg_offset); + return -EINVAL; + } + + /* a module's config is always the same size */ + fw_module->bss_size = fm_config[fm_entry->cfg_offset].is_bytes; + + dev_dbg(sdev->dev, + "module %s: UUID %pUL cfg_count: %u, bss_size: %#x\n", + fm_entry->name, &fm_entry->uuid, fm_entry->cfg_count, + fw_module->bss_size); + } else { + fw_module->bss_size = 0; + + dev_dbg(sdev->dev, "module %s: UUID %pUL\n", fm_entry->name, + &fm_entry->uuid); + } + + fw_module->man4_module_entry.id = i; + ida_init(&fw_module->m_ida); + fw_module->private = NULL; + + fw_module++; + fm_entry++; + } + + return ext_man_hdr->len; +} + +static int sof_ipc4_validate_firmware(struct snd_sof_dev *sdev) +{ + struct sof_ipc4_fw_data *ipc4_data = sdev->private; + u32 fw_hdr_offset = ipc4_data->manifest_fw_hdr_offset; + struct snd_sof_pdata *plat_data = sdev->pdata; + struct sof_man4_fw_binary_header *fw_header; + const struct firmware *fw = plat_data->fw; + struct sof_ext_manifest4_hdr *ext_man_hdr; + + ext_man_hdr = (struct sof_ext_manifest4_hdr *)fw->data; + fw_header = (struct sof_man4_fw_binary_header *) + (fw->data + ext_man_hdr->len + fw_hdr_offset); + + /* TODO: Add firmware verification code here */ + + dev_dbg(sdev->dev, "Validated firmware version: %u.%u.%u.%u\n", + fw_header->major_version, fw_header->minor_version, + fw_header->hotfix_version, fw_header->build_version); + + return 0; +} + +static int sof_ipc4_query_fw_configuration(struct snd_sof_dev *sdev) +{ + const struct sof_ipc_ops *iops = sdev->ipc->ops; + struct sof_ipc4_fw_version *fw_ver; + struct sof_ipc4_tuple *tuple; + struct sof_ipc4_msg msg; + size_t offset = 0; + int ret; + + /* Get the firmware configuration */ + msg.primary = SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG); + msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); + msg.primary |= SOF_IPC4_MOD_ID(SOF_IPC4_MOD_INIT_BASEFW_MOD_ID); + msg.primary |= SOF_IPC4_MOD_INSTANCE(SOF_IPC4_MOD_INIT_BASEFW_INSTANCE_ID); + msg.extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_FW_PARAM_FW_CONFIG); + + msg.data_size = sdev->ipc->max_payload_size; + msg.data_ptr = kzalloc(msg.data_size, GFP_KERNEL); + if (!msg.data_ptr) + return -ENOMEM; + + ret = iops->set_get_data(sdev, &msg, msg.data_size, false); + if (ret) + goto out; + + while (offset < msg.data_size) { + tuple = (struct sof_ipc4_tuple *)((u8 *)msg.data_ptr + offset); + + switch (tuple->type) { + case SOF_IPC4_FW_CFG_FW_VERSION: + fw_ver = (struct sof_ipc4_fw_version *)tuple->value; + + dev_info(sdev->dev, + "Booted firmware version: %u.%u.%u.%u\n", + fw_ver->major, fw_ver->minor, fw_ver->hotfix, + fw_ver->build); + break; + case SOF_IPC4_FW_CFG_DL_MAILBOX_BYTES: + dev_vdbg(sdev->dev, "DL mailbox size: %u\n", *tuple->value); + break; + case SOF_IPC4_FW_CFG_UL_MAILBOX_BYTES: + dev_vdbg(sdev->dev, "UL mailbox size: %u\n", *tuple->value); + break; + case SOF_IPC4_FW_CFG_TRACE_LOG_BYTES: + dev_vdbg(sdev->dev, "Trace log size: %u\n", *tuple->value); + break; + default: + break; + } + + offset += sizeof(*tuple) + tuple->size; + } + +out: + kfree(msg.data_ptr); + + return ret; +} + +const struct sof_ipc_fw_loader_ops ipc4_loader_ops = { + .validate = sof_ipc4_validate_firmware, + .parse_ext_manifest = sof_ipc4_fw_parse_ext_man, + .query_fw_configuration = sof_ipc4_query_fw_configuration, +}; diff --git a/sound/soc/sof/ipc4-priv.h b/sound/soc/sof/ipc4-priv.h new file mode 100644 index 000000000000..2b71d5675933 --- /dev/null +++ b/sound/soc/sof/ipc4-priv.h @@ -0,0 +1,44 @@ +/* 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 __SOUND_SOC_SOF_IPC4_PRIV_H +#define __SOUND_SOC_SOF_IPC4_PRIV_H + +#include <linux/idr.h> +#include <sound/sof/ext_manifest4.h> +#include "sof-priv.h" + +/** + * struct sof_ipc4_fw_data - IPC4-specific data + * @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 + */ +struct sof_ipc4_fw_data { + u32 manifest_fw_hdr_offset; + int num_fw_modules; + void *fw_modules; +}; + +/** + * struct sof_ipc4_fw_module - IPC4 module info + * @sof_man4_module : Module info + * @m_ida: Module instance identifier + * @bss_size: Module object size + * @private: Module private data + */ +struct sof_ipc4_fw_module { + struct sof_man4_module man4_module_entry; + struct ida m_ida; + u32 bss_size; + void *private; +}; + +extern const struct sof_ipc_fw_loader_ops ipc4_loader_ops; + +#endif diff --git a/sound/soc/sof/ipc4.c b/sound/soc/sof/ipc4.c new file mode 100644 index 000000000000..658802c86685 --- /dev/null +++ b/sound/soc/sof/ipc4.c @@ -0,0 +1,606 @@ +// 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. +// +// Authors: Rander Wang <rander.wang@linux.intel.com> +// Peter Ujfalusi <peter.ujfalusi@linux.intel.com> +// +#include <sound/sof/header.h> +#include <sound/sof/ipc4/header.h> +#include "sof-priv.h" +#include "sof-audio.h" +#include "ipc4-priv.h" +#include "ops.h" + +#ifdef DEBUG_VERBOSE +#define sof_ipc4_dump_payload(sdev, ipc_data, size) \ + print_hex_dump_debug("Message payload: ", \ + DUMP_PREFIX_OFFSET, \ + 16, 4, ipc_data, size, false) +#else +#define sof_ipc4_dump_payload(sdev, ipc_data, size) do { } while (0) +#endif + +static const struct sof_ipc4_fw_status { + int status; + char *msg; +} ipc4_status[] = { + {0, "The operation was successful"}, + {1, "Invalid parameter specified"}, + {2, "Unknown message type specified"}, + {3, "Not enough space in the IPC reply buffer to complete the request"}, + {4, "The system or resource is busy"}, + {5, "Replaced ADSP IPC PENDING (unused)"}, + {6, "Unknown error while processing the request"}, + {7, "Unsupported operation requested"}, + {8, "Reserved (ADSP_STAGE_UNINITIALIZED removed)"}, + {9, "Specified resource not found"}, + {10, "A resource's ID requested to be created is already assigned"}, + {11, "Reserved (ADSP_IPC_OUT_OF_MIPS removed)"}, + {12, "Required resource is in invalid state"}, + {13, "Requested power transition failed to complete"}, + {14, "Manifest of the library being loaded is invalid"}, + {15, "Requested service or data is unavailable on the target platform"}, + {42, "Library target address is out of storage memory range"}, + {43, "Reserved"}, + {44, "Image verification by CSE failed"}, + {100, "General module management error"}, + {101, "Module loading failed"}, + {102, "Integrity check of the loaded module content failed"}, + {103, "Attempt to unload code of the module in use"}, + {104, "Other failure of module instance initialization request"}, + {105, "Reserved (ADSP_IPC_OUT_OF_MIPS removed)"}, + {106, "Reserved (ADSP_IPC_CONFIG_GET_ERROR removed)"}, + {107, "Reserved (ADSP_IPC_CONFIG_SET_ERROR removed)"}, + {108, "Reserved (ADSP_IPC_LARGE_CONFIG_GET_ERROR removed)"}, + {109, "Reserved (ADSP_IPC_LARGE_CONFIG_SET_ERROR removed)"}, + {110, "Invalid (out of range) module ID provided"}, + {111, "Invalid module instance ID provided"}, + {112, "Invalid queue (pin) ID provided"}, + {113, "Invalid destination queue (pin) ID provided"}, + {114, "Reserved (ADSP_IPC_BIND_UNBIND_DST_SINK_UNSUPPORTED removed)"}, + {115, "Reserved (ADSP_IPC_UNLOAD_INST_EXISTS removed)"}, + {116, "Invalid target code ID provided"}, + {117, "Injection DMA buffer is too small for probing the input pin"}, + {118, "Extraction DMA buffer is too small for probing the output pin"}, + {120, "Invalid ID of configuration item provided in TLV list"}, + {121, "Invalid length of configuration item provided in TLV list"}, + {122, "Invalid structure of configuration item provided"}, + {140, "Initialization of DMA Gateway failed"}, + {141, "Invalid ID of gateway provided"}, + {142, "Setting state of DMA Gateway failed"}, + {143, "DMA_CONTROL message targeting gateway not allocated yet"}, + {150, "Attempt to configure SCLK while I2S port is running"}, + {151, "Attempt to configure MCLK while I2S port is running"}, + {152, "Attempt to stop SCLK that is not running"}, + {153, "Attempt to stop MCLK that is not running"}, + {160, "Reserved (ADSP_IPC_PIPELINE_NOT_INITIALIZED removed)"}, + {161, "Reserved (ADSP_IPC_PIPELINE_NOT_EXIST removed)"}, + {162, "Reserved (ADSP_IPC_PIPELINE_SAVE_FAILED removed)"}, + {163, "Reserved (ADSP_IPC_PIPELINE_RESTORE_FAILED removed)"}, + {165, "Reserved (ADSP_IPC_PIPELINE_ALREADY_EXISTS removed)"}, +}; + +static int sof_ipc4_check_reply_status(struct snd_sof_dev *sdev, u32 status) +{ + int i, ret; + + status &= SOF_IPC4_REPLY_STATUS; + + if (!status) + return 0; + + for (i = 0; i < ARRAY_SIZE(ipc4_status); i++) { + if (ipc4_status[i].status == status) { + dev_err(sdev->dev, "FW reported error: %u - %s\n", + status, ipc4_status[i].msg); + goto to_errno; + } + } + + if (i == ARRAY_SIZE(ipc4_status)) + dev_err(sdev->dev, "FW reported error: %u - Unknown\n", status); + +to_errno: + switch (status) { + case 8: + case 11: + case 105 ... 109: + case 114 ... 115: + case 160 ... 163: + case 165: + ret = -ENOENT; + break; + case 4: + case 150: + case 151: + ret = -EBUSY; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_VERBOSE_IPC) +#define DBG_IPC4_MSG_TYPE_ENTRY(type) [SOF_IPC4_##type] = #type +static const char * const ipc4_dbg_mod_msg_type[] = { + DBG_IPC4_MSG_TYPE_ENTRY(MOD_INIT_INSTANCE), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_CONFIG_GET), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_CONFIG_SET), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_LARGE_CONFIG_GET), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_LARGE_CONFIG_SET), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_BIND), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_UNBIND), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_SET_DX), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_SET_D0IX), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_ENTER_MODULE_RESTORE), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_EXIT_MODULE_RESTORE), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_DELETE_INSTANCE), +}; + +static const char * const ipc4_dbg_glb_msg_type[] = { + DBG_IPC4_MSG_TYPE_ENTRY(GLB_BOOT_CONFIG), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_ROM_CONTROL), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_IPCGATEWAY_CMD), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_PERF_MEASUREMENTS_CMD), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_CHAIN_DMA), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_LOAD_MULTIPLE_MODULES), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_UNLOAD_MULTIPLE_MODULES), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_CREATE_PIPELINE), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_DELETE_PIPELINE), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_SET_PIPELINE_STATE), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_GET_PIPELINE_STATE), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_GET_PIPELINE_CONTEXT_SIZE), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_SAVE_PIPELINE), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_RESTORE_PIPELINE), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_LOAD_LIBRARY), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_INTERNAL_MESSAGE), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_NOTIFICATION), +}; + +#define DBG_IPC4_NOTIFICATION_TYPE_ENTRY(type) [SOF_IPC4_NOTIFY_##type] = #type +static const char * const ipc4_dbg_notification_type[] = { + DBG_IPC4_NOTIFICATION_TYPE_ENTRY(PHRASE_DETECTED), + DBG_IPC4_NOTIFICATION_TYPE_ENTRY(RESOURCE_EVENT), + DBG_IPC4_NOTIFICATION_TYPE_ENTRY(LOG_BUFFER_STATUS), + DBG_IPC4_NOTIFICATION_TYPE_ENTRY(TIMESTAMP_CAPTURED), + DBG_IPC4_NOTIFICATION_TYPE_ENTRY(FW_READY), + DBG_IPC4_NOTIFICATION_TYPE_ENTRY(FW_AUD_CLASS_RESULT), + DBG_IPC4_NOTIFICATION_TYPE_ENTRY(EXCEPTION_CAUGHT), + DBG_IPC4_NOTIFICATION_TYPE_ENTRY(MODULE_NOTIFICATION), + DBG_IPC4_NOTIFICATION_TYPE_ENTRY(PROBE_DATA_AVAILABLE), + DBG_IPC4_NOTIFICATION_TYPE_ENTRY(ASYNC_MSG_SRVC_MESSAGE), +}; + +static void sof_ipc4_log_header(struct device *dev, u8 *text, struct sof_ipc4_msg *msg, + bool data_size_valid) +{ + u32 val, type; + const u8 *str2 = NULL; + const u8 *str = NULL; + + val = msg->primary & SOF_IPC4_MSG_TARGET_MASK; + type = SOF_IPC4_MSG_TYPE_GET(msg->primary); + + if (val == SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG)) { + /* Module message */ + if (type < SOF_IPC4_MOD_TYPE_LAST) + str = ipc4_dbg_mod_msg_type[type]; + if (!str) + str = "Unknown Module message type"; + } else { + /* Global FW message */ + if (type < SOF_IPC4_GLB_TYPE_LAST) + str = ipc4_dbg_glb_msg_type[type]; + if (!str) + str = "Unknown Global message type"; + + if (type == SOF_IPC4_GLB_NOTIFICATION) { + /* Notification message */ + u32 notif = SOF_IPC4_NOTIFICATION_TYPE_GET(msg->primary); + + if (notif < SOF_IPC4_NOTIFY_TYPE_LAST) + str2 = ipc4_dbg_notification_type[notif]; + if (!str2) + str2 = "Unknown Global notification"; + } + } + + if (str2) { + if (data_size_valid && msg->data_size) + dev_dbg(dev, "%s: %#x|%#x: %s|%s [data size: %zu]\n", + text, msg->primary, msg->extension, str, str2, + msg->data_size); + else + dev_dbg(dev, "%s: %#x|%#x: %s|%s\n", text, msg->primary, + msg->extension, str, str2); + } else { + if (data_size_valid && msg->data_size) + dev_dbg(dev, "%s: %#x|%#x: %s [data size: %zu]\n", + text, msg->primary, msg->extension, str, + msg->data_size); + else + dev_dbg(dev, "%s: %#x|%#x: %s\n", text, msg->primary, + msg->extension, str); + } +} +#else /* CONFIG_SND_SOC_SOF_DEBUG_VERBOSE_IPC */ +static void sof_ipc4_log_header(struct device *dev, u8 *text, struct sof_ipc4_msg *msg, + bool data_size_valid) +{ + if (data_size_valid && msg->data_size) + dev_dbg(dev, "%s: %#x|%#x [data size: %zu]\n", text, + msg->primary, msg->extension, msg->data_size); + else + dev_dbg(dev, "%s: %#x|%#x\n", text, msg->primary, msg->extension); +} +#endif + +static int sof_ipc4_get_reply(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc_msg *msg = sdev->msg; + struct sof_ipc4_msg *ipc4_reply; + int ret; + + /* get the generic reply */ + ipc4_reply = msg->reply_data; + + sof_ipc4_log_header(sdev->dev, "ipc tx reply", ipc4_reply, false); + + ret = sof_ipc4_check_reply_status(sdev, ipc4_reply->primary); + if (ret) + return ret; + + /* No other information is expected for non large config get replies */ + if (!msg->reply_size || !SOF_IPC4_MSG_IS_MODULE_MSG(ipc4_reply->primary) || + (SOF_IPC4_MSG_TYPE_GET(ipc4_reply->primary) != SOF_IPC4_MOD_LARGE_CONFIG_GET)) + return 0; + + /* Read the requested payload */ + snd_sof_dsp_mailbox_read(sdev, sdev->dsp_box.offset, ipc4_reply->data_ptr, + msg->reply_size); + + return 0; +} + +/* wait for IPC message reply */ +static int ipc4_wait_tx_done(struct snd_sof_ipc *ipc, void *reply_data) +{ + struct snd_sof_ipc_msg *msg = &ipc->msg; + struct sof_ipc4_msg *ipc4_msg = msg->msg_data; + struct snd_sof_dev *sdev = ipc->sdev; + int ret; + + /* wait for DSP IPC completion */ + ret = wait_event_timeout(msg->waitq, msg->ipc_complete, + msecs_to_jiffies(sdev->ipc_timeout)); + if (ret == 0) { + dev_err(sdev->dev, "ipc timed out for %#x|%#x\n", + ipc4_msg->primary, ipc4_msg->extension); + return -ETIMEDOUT; + } + + if (msg->reply_error) { + dev_err(sdev->dev, "ipc error for msg %#x|%#x\n", + ipc4_msg->primary, ipc4_msg->extension); + ret = msg->reply_error; + } else { + if (reply_data) { + struct sof_ipc4_msg *ipc4_reply = msg->reply_data; + struct sof_ipc4_msg *ipc4_reply_data = reply_data; + + /* Copy the header */ + ipc4_reply_data->header_u64 = ipc4_reply->header_u64; + if (msg->reply_size && ipc4_reply_data->data_ptr) { + /* copy the payload returned from DSP */ + memcpy(ipc4_reply_data->data_ptr, ipc4_reply->data_ptr, + msg->reply_size); + ipc4_reply_data->data_size = msg->reply_size; + } + } + + ret = 0; + sof_ipc4_log_header(sdev->dev, "ipc tx done ", ipc4_msg, true); + } + + /* re-enable dumps after successful IPC tx */ + if (sdev->ipc_dump_printed) { + sdev->dbg_dump_printed = false; + sdev->ipc_dump_printed = false; + } + + return ret; +} + +static int ipc4_tx_msg_unlocked(struct snd_sof_ipc *ipc, + void *msg_data, size_t msg_bytes, + void *reply_data, size_t reply_bytes) +{ + struct sof_ipc4_msg *ipc4_msg = msg_data; + struct snd_sof_dev *sdev = ipc->sdev; + int ret; + + if (msg_bytes > ipc->max_payload_size || reply_bytes > ipc->max_payload_size) + return -EINVAL; + + ret = sof_ipc_send_msg(sdev, msg_data, msg_bytes, reply_bytes); + if (ret) { + dev_err_ratelimited(sdev->dev, + "%s: ipc message send for %#x|%#x failed: %d\n", + __func__, ipc4_msg->primary, ipc4_msg->extension, ret); + return ret; + } + + sof_ipc4_log_header(sdev->dev, "ipc tx ", msg_data, true); + + /* now wait for completion */ + return ipc4_wait_tx_done(ipc, reply_data); +} + +static int sof_ipc4_tx_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes, + void *reply_data, size_t reply_bytes, bool no_pm) +{ + struct snd_sof_ipc *ipc = sdev->ipc; +#ifdef DEBUG_VERBOSE + struct sof_ipc4_msg *msg = NULL; +#endif + int ret; + + if (!msg_data) + return -EINVAL; + + /* Serialise IPC TX */ + mutex_lock(&ipc->tx_mutex); + + ret = ipc4_tx_msg_unlocked(ipc, msg_data, msg_bytes, reply_data, reply_bytes); + + mutex_unlock(&ipc->tx_mutex); + +#ifdef DEBUG_VERBOSE + /* payload is indicated by non zero msg/reply_bytes */ + if (msg_bytes) + msg = msg_data; + else if (reply_bytes) + msg = reply_data; + + if (msg) + sof_ipc4_dump_payload(sdev, msg->data_ptr, msg->data_size); +#endif + + return ret; +} + +static int sof_ipc4_set_get_data(struct snd_sof_dev *sdev, void *data, + size_t payload_bytes, bool set) +{ + size_t payload_limit = sdev->ipc->max_payload_size; + struct sof_ipc4_msg *ipc4_msg = data; + struct sof_ipc4_msg tx = {{ 0 }}; + struct sof_ipc4_msg rx = {{ 0 }}; + size_t remaining = payload_bytes; + size_t offset = 0; + size_t chunk_size; + int ret; + + if (!data) + return -EINVAL; + + if ((ipc4_msg->primary & SOF_IPC4_MSG_TARGET_MASK) != + SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG)) + return -EINVAL; + + ipc4_msg->primary &= ~SOF_IPC4_MSG_TYPE_MASK; + tx.primary = ipc4_msg->primary; + tx.extension = ipc4_msg->extension; + + if (set) + tx.primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_LARGE_CONFIG_SET); + else + tx.primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_LARGE_CONFIG_GET); + + tx.extension &= ~SOF_IPC4_MOD_EXT_MSG_SIZE_MASK; + tx.extension |= SOF_IPC4_MOD_EXT_MSG_SIZE(payload_bytes); + + tx.extension |= SOF_IPC4_MOD_EXT_MSG_FIRST_BLOCK(1); + + /* Serialise IPC TX */ + mutex_lock(&sdev->ipc->tx_mutex); + + do { + size_t tx_size, rx_size; + + if (remaining > payload_limit) { + chunk_size = payload_limit; + } else { + chunk_size = remaining; + if (set) + tx.extension |= SOF_IPC4_MOD_EXT_MSG_LAST_BLOCK(1); + } + + if (offset) { + tx.extension &= ~SOF_IPC4_MOD_EXT_MSG_FIRST_BLOCK_MASK; + tx.extension &= ~SOF_IPC4_MOD_EXT_MSG_SIZE_MASK; + tx.extension |= SOF_IPC4_MOD_EXT_MSG_SIZE(offset); + } + + if (set) { + tx.data_size = chunk_size; + tx.data_ptr = ipc4_msg->data_ptr + offset; + + tx_size = chunk_size; + rx_size = 0; + } else { + rx.primary = 0; + rx.extension = 0; + rx.data_size = chunk_size; + rx.data_ptr = ipc4_msg->data_ptr + offset; + + tx_size = 0; + rx_size = chunk_size; + } + + /* Send the message for the current chunk */ + ret = ipc4_tx_msg_unlocked(sdev->ipc, &tx, tx_size, &rx, rx_size); + if (ret < 0) { + dev_err(sdev->dev, + "%s: large config %s failed at offset %zu: %d\n", + __func__, set ? "set" : "get", offset, ret); + goto out; + } + + if (!set && rx.extension & SOF_IPC4_MOD_EXT_MSG_FIRST_BLOCK_MASK) { + /* Verify the firmware reported total payload size */ + rx_size = rx.extension & SOF_IPC4_MOD_EXT_MSG_SIZE_MASK; + + if (rx_size > payload_bytes) { + dev_err(sdev->dev, + "%s: Receive buffer (%zu) is too small for %zu\n", + __func__, payload_bytes, rx_size); + ret = -ENOMEM; + goto out; + } + + if (rx_size < chunk_size) { + chunk_size = rx_size; + remaining = rx_size; + } else if (rx_size < payload_bytes) { + remaining = rx_size; + } + } + + offset += chunk_size; + remaining -= chunk_size; + } while (remaining); + + /* Adjust the received data size if needed */ + if (!set && payload_bytes != offset) + ipc4_msg->data_size = offset; + + sof_ipc4_dump_payload(sdev, ipc4_msg->data_ptr, ipc4_msg->data_size); + +out: + mutex_unlock(&sdev->ipc->tx_mutex); + + return ret; +} + +static int sof_ipc4_init_msg_memory(struct snd_sof_dev *sdev) +{ + struct sof_ipc4_msg *ipc4_msg; + struct snd_sof_ipc_msg *msg = &sdev->ipc->msg; + + /* TODO: get max_payload_size from firmware */ + sdev->ipc->max_payload_size = SOF_IPC4_MSG_MAX_SIZE; + + /* Allocate memory for the ipc4 container and the maximum payload */ + msg->reply_data = devm_kzalloc(sdev->dev, sdev->ipc->max_payload_size + + sizeof(struct sof_ipc4_msg), GFP_KERNEL); + if (!msg->reply_data) + return -ENOMEM; + + ipc4_msg = msg->reply_data; + ipc4_msg->data_ptr = msg->reply_data + sizeof(struct sof_ipc4_msg); + + return 0; +} + +static int ipc4_fw_ready(struct snd_sof_dev *sdev, struct sof_ipc4_msg *ipc4_msg) +{ + int inbox_offset, inbox_size, outbox_offset, outbox_size; + + /* no need to re-check version/ABI for subsequent boots */ + if (!sdev->first_boot) + return 0; + + /* Set up the windows for IPC communication */ + inbox_offset = snd_sof_dsp_get_mailbox_offset(sdev); + if (inbox_offset < 0) { + dev_err(sdev->dev, "%s: No mailbox offset\n", __func__); + return inbox_offset; + } + inbox_size = SOF_IPC4_MSG_MAX_SIZE; + outbox_offset = snd_sof_dsp_get_window_offset(sdev, 1); + outbox_size = SOF_IPC4_MSG_MAX_SIZE; + + sdev->dsp_box.offset = inbox_offset; + sdev->dsp_box.size = inbox_size; + sdev->host_box.offset = outbox_offset; + sdev->host_box.size = outbox_size; + + dev_dbg(sdev->dev, "mailbox upstream 0x%x - size 0x%x\n", + inbox_offset, inbox_size); + dev_dbg(sdev->dev, "mailbox downstream 0x%x - size 0x%x\n", + outbox_offset, outbox_size); + + return sof_ipc4_init_msg_memory(sdev); +} + +static void sof_ipc4_rx_msg(struct snd_sof_dev *sdev) +{ + struct sof_ipc4_msg *ipc4_msg = sdev->ipc->msg.rx_data; + size_t data_size = 0; + int err; + + if (!ipc4_msg || !SOF_IPC4_MSG_IS_NOTIFICATION(ipc4_msg->primary)) + return; + + ipc4_msg->data_ptr = NULL; + ipc4_msg->data_size = 0; + + sof_ipc4_log_header(sdev->dev, "ipc rx ", ipc4_msg, false); + + switch (SOF_IPC4_NOTIFICATION_TYPE_GET(ipc4_msg->primary)) { + case SOF_IPC4_NOTIFY_FW_READY: + /* check for FW boot completion */ + if (sdev->fw_state == SOF_FW_BOOT_IN_PROGRESS) { + err = ipc4_fw_ready(sdev, ipc4_msg); + if (err < 0) + sof_set_fw_state(sdev, SOF_FW_BOOT_READY_FAILED); + else + sof_set_fw_state(sdev, SOF_FW_BOOT_READY_OK); + + /* wake up firmware loader */ + wake_up(&sdev->boot_wait); + } + + break; + case SOF_IPC4_NOTIFY_RESOURCE_EVENT: + data_size = sizeof(struct sof_ipc4_notify_resource_data); + break; + default: + dev_dbg(sdev->dev, "%s: Unhandled DSP message: %#x|%#x\n", __func__, + ipc4_msg->primary, ipc4_msg->extension); + break; + } + + if (data_size) { + ipc4_msg->data_ptr = kmalloc(data_size, GFP_KERNEL); + if (!ipc4_msg->data_ptr) + return; + + ipc4_msg->data_size = data_size; + snd_sof_ipc_msg_data(sdev, NULL, ipc4_msg->data_ptr, ipc4_msg->data_size); + } + + sof_ipc4_log_header(sdev->dev, "ipc rx done ", ipc4_msg, true); + + if (data_size) { + kfree(ipc4_msg->data_ptr); + ipc4_msg->data_ptr = NULL; + ipc4_msg->data_size = 0; + } +} + +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, + .fw_loader = &ipc4_loader_ops, +}; diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c index 697f03565a70..5f51d936b306 100644 --- a/sound/soc/sof/loader.c +++ b/sound/soc/sof/loader.c @@ -11,690 +11,9 @@ // #include <linux/firmware.h> -#include <sound/sof.h> -#include <sound/sof/ext_manifest.h> #include "sof-priv.h" #include "ops.h" -static int get_ext_windows(struct snd_sof_dev *sdev, - const struct sof_ipc_ext_data_hdr *ext_hdr) -{ - const struct sof_ipc_window *w = - container_of(ext_hdr, struct sof_ipc_window, ext_hdr); - - if (w->num_windows == 0 || w->num_windows > SOF_IPC_MAX_ELEMS) - return -EINVAL; - - if (sdev->info_window) { - if (memcmp(sdev->info_window, w, ext_hdr->hdr.size)) { - dev_err(sdev->dev, "error: mismatch between window descriptor from extended manifest and mailbox"); - return -EINVAL; - } - return 0; - } - - /* keep a local copy of the data */ - sdev->info_window = devm_kmemdup(sdev->dev, w, ext_hdr->hdr.size, - GFP_KERNEL); - if (!sdev->info_window) - return -ENOMEM; - - return 0; -} - -static int get_cc_info(struct snd_sof_dev *sdev, - const struct sof_ipc_ext_data_hdr *ext_hdr) -{ - int ret; - - const struct sof_ipc_cc_version *cc = - container_of(ext_hdr, struct sof_ipc_cc_version, ext_hdr); - - if (sdev->cc_version) { - if (memcmp(sdev->cc_version, cc, cc->ext_hdr.hdr.size)) { - dev_err(sdev->dev, "error: receive diverged cc_version descriptions"); - return -EINVAL; - } - return 0; - } - - dev_dbg(sdev->dev, "Firmware info: used compiler %s %d:%d:%d%s used optimization flags %s\n", - cc->name, cc->major, cc->minor, cc->micro, cc->desc, - cc->optim); - - /* create read-only cc_version debugfs to store compiler version info */ - /* use local copy of the cc_version to prevent data corruption */ - if (sdev->first_boot) { - sdev->cc_version = devm_kmalloc(sdev->dev, cc->ext_hdr.hdr.size, - GFP_KERNEL); - - if (!sdev->cc_version) - return -ENOMEM; - - memcpy(sdev->cc_version, cc, cc->ext_hdr.hdr.size); - ret = snd_sof_debugfs_buf_item(sdev, sdev->cc_version, - cc->ext_hdr.hdr.size, - "cc_version", 0444); - - /* errors are only due to memory allocation, not debugfs */ - if (ret < 0) { - dev_err(sdev->dev, "error: snd_sof_debugfs_buf_item failed\n"); - return ret; - } - } - - return 0; -} - -/* parse the extended FW boot data structures from FW boot message */ -static int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 offset) -{ - struct sof_ipc_ext_data_hdr *ext_hdr; - void *ext_data; - int ret = 0; - - ext_data = kzalloc(PAGE_SIZE, GFP_KERNEL); - if (!ext_data) - return -ENOMEM; - - /* get first header */ - snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, offset, ext_data, - sizeof(*ext_hdr)); - ext_hdr = ext_data; - - while (ext_hdr->hdr.cmd == SOF_IPC_FW_READY) { - /* read in ext structure */ - snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, - offset + sizeof(*ext_hdr), - (void *)((u8 *)ext_data + sizeof(*ext_hdr)), - ext_hdr->hdr.size - sizeof(*ext_hdr)); - - dev_dbg(sdev->dev, "found ext header type %d size 0x%x\n", - ext_hdr->type, ext_hdr->hdr.size); - - /* process structure data */ - switch (ext_hdr->type) { - case SOF_IPC_EXT_WINDOW: - ret = get_ext_windows(sdev, ext_hdr); - break; - case SOF_IPC_EXT_CC_INFO: - ret = get_cc_info(sdev, ext_hdr); - break; - case SOF_IPC_EXT_UNUSED: - case SOF_IPC_EXT_PROBE_INFO: - case SOF_IPC_EXT_USER_ABI_INFO: - /* They are supported but we don't do anything here */ - break; - default: - dev_info(sdev->dev, "unknown ext header type %d size 0x%x\n", - ext_hdr->type, ext_hdr->hdr.size); - ret = 0; - break; - } - - if (ret < 0) { - dev_err(sdev->dev, "error: failed to parse ext data type %d\n", - ext_hdr->type); - break; - } - - /* move to next header */ - offset += ext_hdr->hdr.size; - snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, offset, ext_data, - sizeof(*ext_hdr)); - ext_hdr = ext_data; - } - - kfree(ext_data); - return ret; -} - -static int ext_man_get_fw_version(struct snd_sof_dev *sdev, - const struct sof_ext_man_elem_header *hdr) -{ - const struct sof_ext_man_fw_version *v = - container_of(hdr, struct sof_ext_man_fw_version, hdr); - - memcpy(&sdev->fw_ready.version, &v->version, sizeof(v->version)); - sdev->fw_ready.flags = v->flags; - - /* log ABI versions and check FW compatibility */ - return snd_sof_ipc_valid(sdev); -} - -static int ext_man_get_windows(struct snd_sof_dev *sdev, - const struct sof_ext_man_elem_header *hdr) -{ - const struct sof_ext_man_window *w; - - w = container_of(hdr, struct sof_ext_man_window, hdr); - - return get_ext_windows(sdev, &w->ipc_window.ext_hdr); -} - -static int ext_man_get_cc_info(struct snd_sof_dev *sdev, - const struct sof_ext_man_elem_header *hdr) -{ - const struct sof_ext_man_cc_version *cc; - - cc = container_of(hdr, struct sof_ext_man_cc_version, hdr); - - return get_cc_info(sdev, &cc->cc_version.ext_hdr); -} - -static int ext_man_get_dbg_abi_info(struct snd_sof_dev *sdev, - const struct sof_ext_man_elem_header *hdr) -{ - const struct ext_man_dbg_abi *dbg_abi = - container_of(hdr, struct ext_man_dbg_abi, hdr); - - if (sdev->first_boot) - dev_dbg(sdev->dev, - "Firmware: DBG_ABI %d:%d:%d\n", - SOF_ABI_VERSION_MAJOR(dbg_abi->dbg_abi.abi_dbg_version), - SOF_ABI_VERSION_MINOR(dbg_abi->dbg_abi.abi_dbg_version), - SOF_ABI_VERSION_PATCH(dbg_abi->dbg_abi.abi_dbg_version)); - - return 0; -} - -static int ext_man_get_config_data(struct snd_sof_dev *sdev, - const struct sof_ext_man_elem_header *hdr) -{ - const struct sof_ext_man_config_data *config = - container_of(hdr, struct sof_ext_man_config_data, hdr); - const struct sof_config_elem *elem; - int elems_counter; - int elems_size; - int ret = 0; - int i; - - /* calculate elements counter */ - 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); - - 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); - switch (elem->token) { - case SOF_EXT_MAN_CONFIG_EMPTY: - /* unused memory space is zero filled - mapped to EMPTY elements */ - break; - case SOF_EXT_MAN_CONFIG_IPC_MSG_SIZE: - /* TODO: use ipc msg size from config data */ - break; - case SOF_EXT_MAN_CONFIG_MEMORY_USAGE_SCAN: - if (sdev->first_boot && elem->value) - ret = snd_sof_dbg_memory_info_init(sdev); - break; - default: - dev_info(sdev->dev, "Unknown firmware configuration token %d value %d", - elem->token, elem->value); - break; - } - if (ret < 0) { - dev_err(sdev->dev, "error: processing sof_ext_man_config_data failed for token %d value 0x%x, %d\n", - elem->token, elem->value, ret); - return ret; - } - } - - return 0; -} - -static ssize_t snd_sof_ext_man_size(const struct firmware *fw) -{ - const struct sof_ext_man_header *head; - - head = (struct sof_ext_man_header *)fw->data; - - /* - * assert fw size is big enough to contain extended manifest header, - * it prevents from reading unallocated memory from `head` in following - * step. - */ - if (fw->size < sizeof(*head)) - return -EINVAL; - - /* - * When fw points to extended manifest, - * then first u32 must be equal SOF_EXT_MAN_MAGIC_NUMBER. - */ - if (head->magic == SOF_EXT_MAN_MAGIC_NUMBER) - return head->full_size; - - /* otherwise given fw don't have an extended manifest */ - return 0; -} - -/* parse extended FW manifest data structures */ -static int snd_sof_fw_ext_man_parse(struct snd_sof_dev *sdev, - const struct firmware *fw) -{ - const struct sof_ext_man_elem_header *elem_hdr; - const struct sof_ext_man_header *head; - ssize_t ext_man_size; - ssize_t remaining; - uintptr_t iptr; - int ret = 0; - - head = (struct sof_ext_man_header *)fw->data; - remaining = head->full_size - head->header_size; - ext_man_size = snd_sof_ext_man_size(fw); - - /* Assert firmware starts with extended manifest */ - if (ext_man_size <= 0) - return ext_man_size; - - /* incompatible version */ - if (SOF_EXT_MAN_VERSION_INCOMPATIBLE(SOF_EXT_MAN_VERSION, - head->header_version)) { - dev_err(sdev->dev, "error: extended manifest version 0x%X differ from used 0x%X\n", - head->header_version, SOF_EXT_MAN_VERSION); - return -EINVAL; - } - - /* get first extended manifest element header */ - iptr = (uintptr_t)fw->data + head->header_size; - - while (remaining > sizeof(*elem_hdr)) { - elem_hdr = (struct sof_ext_man_elem_header *)iptr; - - dev_dbg(sdev->dev, "found sof_ext_man header type %d size 0x%X\n", - elem_hdr->type, elem_hdr->size); - - if (elem_hdr->size < sizeof(*elem_hdr) || - elem_hdr->size > remaining) { - dev_err(sdev->dev, "error: invalid sof_ext_man header size, type %d size 0x%X\n", - elem_hdr->type, elem_hdr->size); - return -EINVAL; - } - - /* process structure data */ - switch (elem_hdr->type) { - case SOF_EXT_MAN_ELEM_FW_VERSION: - ret = ext_man_get_fw_version(sdev, elem_hdr); - break; - case SOF_EXT_MAN_ELEM_WINDOW: - ret = ext_man_get_windows(sdev, elem_hdr); - break; - case SOF_EXT_MAN_ELEM_CC_VERSION: - ret = ext_man_get_cc_info(sdev, elem_hdr); - break; - case SOF_EXT_MAN_ELEM_DBG_ABI: - ret = ext_man_get_dbg_abi_info(sdev, elem_hdr); - break; - case SOF_EXT_MAN_ELEM_CONFIG_DATA: - ret = ext_man_get_config_data(sdev, elem_hdr); - break; - case SOF_EXT_MAN_ELEM_PLATFORM_CONFIG_DATA: - ret = snd_sof_dsp_parse_platform_ext_manifest(sdev, elem_hdr); - break; - default: - dev_info(sdev->dev, "unknown sof_ext_man header type %d size 0x%X\n", - elem_hdr->type, elem_hdr->size); - break; - } - - if (ret < 0) { - dev_err(sdev->dev, "error: failed to parse sof_ext_man header type %d size 0x%X\n", - elem_hdr->type, elem_hdr->size); - return ret; - } - - remaining -= elem_hdr->size; - iptr += elem_hdr->size; - } - - if (remaining) { - dev_err(sdev->dev, "error: sof_ext_man header is inconsistent\n"); - return -EINVAL; - } - - return ext_man_size; -} - -/* - * IPC Firmware ready. - */ -static void sof_get_windows(struct snd_sof_dev *sdev) -{ - struct sof_ipc_window_elem *elem; - u32 outbox_offset = 0; - u32 stream_offset = 0; - u32 inbox_offset = 0; - u32 outbox_size = 0; - u32 stream_size = 0; - u32 inbox_size = 0; - u32 debug_size = 0; - u32 debug_offset = 0; - int window_offset; - int i; - - if (!sdev->info_window) { - dev_err(sdev->dev, "error: have no window info\n"); - return; - } - - for (i = 0; i < sdev->info_window->num_windows; i++) { - elem = &sdev->info_window->window[i]; - - window_offset = snd_sof_dsp_get_window_offset(sdev, elem->id); - if (window_offset < 0) { - dev_warn(sdev->dev, "warn: no offset for window %d\n", - elem->id); - continue; - } - - switch (elem->type) { - case SOF_IPC_REGION_UPBOX: - inbox_offset = window_offset + elem->offset; - inbox_size = elem->size; - snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, - inbox_offset, - elem->size, "inbox", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_DOWNBOX: - outbox_offset = window_offset + elem->offset; - outbox_size = elem->size; - snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, - outbox_offset, - elem->size, "outbox", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_TRACE: - snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, - window_offset + elem->offset, - elem->size, "etrace", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_DEBUG: - debug_offset = window_offset + elem->offset; - debug_size = elem->size; - snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, - window_offset + elem->offset, - elem->size, "debug", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_STREAM: - stream_offset = window_offset + elem->offset; - stream_size = elem->size; - snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, - stream_offset, - elem->size, "stream", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_REGS: - snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, - window_offset + elem->offset, - elem->size, "regs", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_EXCEPTION: - sdev->dsp_oops_offset = window_offset + elem->offset; - snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, - window_offset + elem->offset, - elem->size, "exception", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - default: - dev_err(sdev->dev, "error: get illegal window info\n"); - return; - } - } - - if (outbox_size == 0 || inbox_size == 0) { - dev_err(sdev->dev, "error: get illegal mailbox window\n"); - return; - } - - sdev->dsp_box.offset = inbox_offset; - sdev->dsp_box.size = inbox_size; - - sdev->host_box.offset = outbox_offset; - sdev->host_box.size = outbox_size; - - sdev->stream_box.offset = stream_offset; - sdev->stream_box.size = stream_size; - - sdev->debug_box.offset = debug_offset; - sdev->debug_box.size = debug_size; - - dev_dbg(sdev->dev, " mailbox upstream 0x%x - size 0x%x\n", - inbox_offset, inbox_size); - dev_dbg(sdev->dev, " mailbox downstream 0x%x - size 0x%x\n", - outbox_offset, outbox_size); - dev_dbg(sdev->dev, " stream region 0x%x - size 0x%x\n", - stream_offset, stream_size); - dev_dbg(sdev->dev, " debug region 0x%x - size 0x%x\n", - debug_offset, debug_size); -} - -/* check for ABI compatibility and create memory windows on first boot */ -int sof_fw_ready(struct snd_sof_dev *sdev, u32 msg_id) -{ - struct sof_ipc_fw_ready *fw_ready = &sdev->fw_ready; - int offset; - int ret; - - /* mailbox must be on 4k boundary */ - offset = snd_sof_dsp_get_mailbox_offset(sdev); - if (offset < 0) { - dev_err(sdev->dev, "error: have no mailbox offset\n"); - return offset; - } - - dev_dbg(sdev->dev, "ipc: DSP is ready 0x%8.8x offset 0x%x\n", - msg_id, offset); - - /* no need to re-check version/ABI for subsequent boots */ - if (!sdev->first_boot) - return 0; - - /* - * copy data from the DSP FW ready offset - * Subsequent error handling is not needed for BLK_TYPE_SRAM - */ - ret = snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, offset, fw_ready, - sizeof(*fw_ready)); - if (ret) { - dev_err(sdev->dev, - "error: unable to read fw_ready, read from TYPE_SRAM failed\n"); - return ret; - } - - /* make sure ABI version is compatible */ - ret = snd_sof_ipc_valid(sdev); - if (ret < 0) - return ret; - - /* now check for extended data */ - snd_sof_fw_parse_ext_data(sdev, offset + sizeof(struct sof_ipc_fw_ready)); - - sof_get_windows(sdev); - - return sof_ipc_init_msg_memory(sdev); -} -EXPORT_SYMBOL(sof_fw_ready); - -/* generic module parser for mmaped DSPs */ -int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev, - struct snd_sof_mod_hdr *module) -{ - struct snd_sof_blk_hdr *block; - int count, ret; - u32 offset; - size_t remaining; - - dev_dbg(sdev->dev, "new module size 0x%x blocks 0x%x type 0x%x\n", - module->size, module->num_blocks, module->type); - - block = (struct snd_sof_blk_hdr *)((u8 *)module + sizeof(*module)); - - /* module->size doesn't include header size */ - remaining = module->size; - for (count = 0; count < module->num_blocks; count++) { - /* check for wrap */ - if (remaining < sizeof(*block)) { - dev_err(sdev->dev, "error: not enough data remaining\n"); - return -EINVAL; - } - - /* minus header size of block */ - remaining -= sizeof(*block); - - if (block->size == 0) { - dev_warn(sdev->dev, - "warning: block %d size zero\n", count); - dev_warn(sdev->dev, " type 0x%x offset 0x%x\n", - block->type, block->offset); - continue; - } - - switch (block->type) { - case SOF_FW_BLK_TYPE_RSRVD0: - case SOF_FW_BLK_TYPE_ROM...SOF_FW_BLK_TYPE_RSRVD14: - continue; /* not handled atm */ - case SOF_FW_BLK_TYPE_IRAM: - case SOF_FW_BLK_TYPE_DRAM: - case SOF_FW_BLK_TYPE_SRAM: - offset = block->offset; - break; - default: - dev_err(sdev->dev, "error: bad type 0x%x for block 0x%x\n", - block->type, count); - return -EINVAL; - } - - dev_dbg(sdev->dev, - "block %d type 0x%x size 0x%x ==> offset 0x%x\n", - count, block->type, block->size, offset); - - /* checking block->size to avoid unaligned access */ - if (block->size % sizeof(u32)) { - dev_err(sdev->dev, "error: invalid block size 0x%x\n", - block->size); - return -EINVAL; - } - ret = snd_sof_dsp_block_write(sdev, block->type, offset, - block + 1, block->size); - if (ret < 0) { - dev_err(sdev->dev, "error: write to block type 0x%x failed\n", - block->type); - return ret; - } - - if (remaining < block->size) { - dev_err(sdev->dev, "error: not enough data remaining\n"); - return -EINVAL; - } - - /* minus body size of block */ - remaining -= block->size; - /* next block */ - block = (struct snd_sof_blk_hdr *)((u8 *)block + sizeof(*block) - + block->size); - } - - return 0; -} -EXPORT_SYMBOL(snd_sof_parse_module_memcpy); - -static int check_header(struct snd_sof_dev *sdev, const struct firmware *fw, - size_t fw_offset) -{ - struct snd_sof_fw_header *header; - size_t fw_size = fw->size - fw_offset; - - if (fw->size <= fw_offset) { - dev_err(sdev->dev, "error: firmware size must be greater than firmware offset\n"); - return -EINVAL; - } - - /* Read the header information from the data pointer */ - header = (struct snd_sof_fw_header *)(fw->data + fw_offset); - - /* verify FW sig */ - if (strncmp(header->sig, SND_SOF_FW_SIG, SND_SOF_FW_SIG_SIZE) != 0) { - dev_err(sdev->dev, "error: invalid firmware signature\n"); - return -EINVAL; - } - - /* check size is valid */ - if (fw_size != header->file_size + sizeof(*header)) { - dev_err(sdev->dev, "error: invalid filesize mismatch got 0x%zx expected 0x%zx\n", - fw_size, header->file_size + sizeof(*header)); - return -EINVAL; - } - - dev_dbg(sdev->dev, "header size=0x%x modules=0x%x abi=0x%x size=%zu\n", - header->file_size, header->num_modules, - header->abi, sizeof(*header)); - - return 0; -} - -static int load_modules(struct snd_sof_dev *sdev, const struct firmware *fw, - size_t fw_offset) -{ - struct snd_sof_fw_header *header; - struct snd_sof_mod_hdr *module; - int (*load_module)(struct snd_sof_dev *sof_dev, - struct snd_sof_mod_hdr *hdr); - int ret, count; - size_t remaining; - - header = (struct snd_sof_fw_header *)(fw->data + fw_offset); - load_module = sof_ops(sdev)->load_module; - if (!load_module) - return -EINVAL; - - /* parse each module */ - module = (struct snd_sof_mod_hdr *)(fw->data + fw_offset + - sizeof(*header)); - remaining = fw->size - sizeof(*header) - fw_offset; - /* check for wrap */ - if (remaining > fw->size) { - dev_err(sdev->dev, "error: fw size smaller than header size\n"); - return -EINVAL; - } - - for (count = 0; count < header->num_modules; count++) { - /* check for wrap */ - if (remaining < sizeof(*module)) { - dev_err(sdev->dev, "error: not enough data remaining\n"); - return -EINVAL; - } - - /* minus header size of module */ - remaining -= sizeof(*module); - - /* module */ - ret = load_module(sdev, module); - if (ret < 0) { - dev_err(sdev->dev, "error: invalid module %d\n", count); - return ret; - } - - if (remaining < module->size) { - dev_err(sdev->dev, "error: not enough data remaining\n"); - return -EINVAL; - } - - /* minus body size of module */ - remaining -= module->size; - module = (struct snd_sof_mod_hdr *)((u8 *)module - + sizeof(*module) + module->size); - } - - return 0; -} - int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev) { struct snd_sof_pdata *plat_data = sdev->pdata; @@ -726,7 +45,7 @@ int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev) } /* check for extended manifest */ - ext_man_size = snd_sof_fw_ext_man_parse(sdev, plat_data->fw); + ext_man_size = sdev->ipc->ops->fw_loader->parse_ext_manifest(sdev); if (ext_man_size > 0) { /* when no error occurred, drop extended manifest */ plat_data->fw_offset = ext_man_size; @@ -756,7 +75,7 @@ int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev) return ret; /* make sure the FW header and file is valid */ - ret = check_header(sdev, plat_data->fw, plat_data->fw_offset); + ret = sdev->ipc->ops->fw_loader->validate(sdev); if (ret < 0) { dev_err(sdev->dev, "error: invalid FW header\n"); goto error; @@ -770,10 +89,12 @@ int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev) } /* parse and load firmware modules to DSP */ - ret = load_modules(sdev, plat_data->fw, plat_data->fw_offset); - if (ret < 0) { - dev_err(sdev->dev, "error: invalid FW modules\n"); - goto error; + if (sdev->ipc->ops->fw_loader->load_fw_to_dsp) { + ret = sdev->ipc->ops->fw_loader->load_fw_to_dsp(sdev); + if (ret < 0) { + dev_err(sdev->dev, "Firmware loading failed\n"); + goto error; + } } return 0; @@ -854,6 +175,9 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev) dev_dbg(sdev->dev, "firmware boot complete\n"); sof_set_fw_state(sdev, SOF_FW_BOOT_COMPLETE); + if (sdev->first_boot && sdev->ipc->ops->fw_loader->query_fw_configuration) + return sdev->ipc->ops->fw_loader->query_fw_configuration(sdev); + return 0; } EXPORT_SYMBOL(snd_sof_run_firmware); diff --git a/sound/soc/sof/mediatek/Kconfig b/sound/soc/sof/mediatek/Kconfig index aeacf0e5bfbb..a149dd1b3f44 100644 --- a/sound/soc/sof/mediatek/Kconfig +++ b/sound/soc/sof/mediatek/Kconfig @@ -21,9 +21,20 @@ config SND_SOC_SOF_MTK_COMMON This option is not user-selectable but automagically handled by 'select' statements at a higher level +config SND_SOC_SOF_MT8186 + tristate "SOF support for MT8186 audio DSP" + select SND_SOC_SOF_MTK_COMMON + depends on MTK_ADSP_IPC + help + This adds support for Sound Open Firmware for Mediatek platforms + using the mt8186 processors. + Say Y if you have such a device. + If unsure select "N". + config SND_SOC_SOF_MT8195 tristate "SOF support for MT8195 audio DSP" select SND_SOC_SOF_MTK_COMMON + depends on MTK_ADSP_IPC help This adds support for Sound Open Firmware for Mediatek platforms using the mt8195 processors. diff --git a/sound/soc/sof/mediatek/Makefile b/sound/soc/sof/mediatek/Makefile index e8ec6da981de..29c5afb2f3d6 100644 --- a/sound/soc/sof/mediatek/Makefile +++ b/sound/soc/sof/mediatek/Makefile @@ -1,2 +1,4 @@ # SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +obj-$(CONFIG_SND_SOC_SOF_MTK_COMMON) += mtk-adsp-common.o obj-$(CONFIG_SND_SOC_SOF_MT8195) += mt8195/ +obj-$(CONFIG_SND_SOC_SOF_MT8186) += mt8186/ diff --git a/sound/soc/sof/mediatek/adsp_helper.h b/sound/soc/sof/mediatek/adsp_helper.h index 6734e2c0c6b1..4ab998756bbc 100644 --- a/sound/soc/sof/mediatek/adsp_helper.h +++ b/sound/soc/sof/mediatek/adsp_helper.h @@ -7,37 +7,41 @@ #ifndef __MTK_ADSP_HELPER_H__ #define __MTK_ADSP_HELPER_H__ +#include <linux/firmware/mediatek/mtk-adsp-ipc.h> + /* * Global important adsp data structure. */ -#define DSP_MBOX_NUM 3 - struct mtk_adsp_chip_info { phys_addr_t pa_sram; phys_addr_t pa_dram; /* adsp dram physical base */ phys_addr_t pa_shared_dram; /* adsp dram physical base */ phys_addr_t pa_cfgreg; - phys_addr_t pa_mboxreg[DSP_MBOX_NUM]; u32 sramsize; u32 dramsize; u32 cfgregsize; void __iomem *va_sram; /* corresponding to pa_sram */ void __iomem *va_dram; /* corresponding to pa_dram */ void __iomem *va_cfgreg; - void __iomem *va_mboxreg[DSP_MBOX_NUM]; void __iomem *shared_sram; /* part of va_sram */ void __iomem *shared_dram; /* part of va_dram */ phys_addr_t adsp_bootup_addr; int dram_offset; /*dram offset between system and dsp view*/ + + phys_addr_t pa_secreg; + u32 secregsize; + void __iomem *va_secreg; + + phys_addr_t pa_busreg; + u32 busregsize; + void __iomem *va_busreg; }; struct adsp_priv { struct device *dev; struct snd_sof_dev *sdev; - - /* DSP IPC handler */ - struct mbox_controller *adsp_mbox; - + struct mtk_adsp_ipc *dsp_ipc; + struct platform_device *ipc_dev; struct mtk_adsp_chip_info *adsp; struct clk **clk; u32 (*ap2adsp_addr)(u32 addr, void *data); diff --git a/sound/soc/sof/mediatek/mt8186/Makefile b/sound/soc/sof/mediatek/mt8186/Makefile new file mode 100644 index 000000000000..c1f5fc4e2495 --- /dev/null +++ b/sound/soc/sof/mediatek/mt8186/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +snd-sof-mt8186-objs := mt8186.o mt8186-clk.o mt8186-loader.o +obj-$(CONFIG_SND_SOC_SOF_MT8186) += snd-sof-mt8186.o + diff --git a/sound/soc/sof/mediatek/mt8186/mt8186-clk.c b/sound/soc/sof/mediatek/mt8186/mt8186-clk.c new file mode 100644 index 000000000000..22220fd50b62 --- /dev/null +++ b/sound/soc/sof/mediatek/mt8186/mt8186-clk.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// Copyright(c) 2022 Mediatek Corporation. All rights reserved. +// +// Author: Allen-KH Cheng <allen-kh.cheng@mediatek.com> +// Tinghan Shen <tinghan.shen@mediatek.com> +// +// Hardware interface for mt8186 DSP clock + +#include <linux/clk.h> +#include <linux/pm_runtime.h> +#include <linux/io.h> + +#include "../../sof-audio.h" +#include "../../ops.h" +#include "../adsp_helper.h" +#include "mt8186.h" +#include "mt8186-clk.h" + +static const char *adsp_clks[ADSP_CLK_MAX] = { + [CLK_TOP_AUDIODSP] = "audiodsp_sel", + [CLK_TOP_ADSP_BUS] = "adsp_bus_sel", +}; + +int mt8186_adsp_init_clock(struct snd_sof_dev *sdev) +{ + struct adsp_priv *priv = sdev->pdata->hw_pdata; + struct device *dev = sdev->dev; + int i; + + priv->clk = devm_kcalloc(dev, ADSP_CLK_MAX, sizeof(*priv->clk), GFP_KERNEL); + if (!priv->clk) + return -ENOMEM; + + for (i = 0; i < ADSP_CLK_MAX; i++) { + priv->clk[i] = devm_clk_get(dev, adsp_clks[i]); + + if (IS_ERR(priv->clk[i])) + return PTR_ERR(priv->clk[i]); + } + + return 0; +} + +static int adsp_enable_all_clock(struct snd_sof_dev *sdev) +{ + struct adsp_priv *priv = sdev->pdata->hw_pdata; + struct device *dev = sdev->dev; + int ret; + + ret = clk_prepare_enable(priv->clk[CLK_TOP_AUDIODSP]); + if (ret) { + dev_err(dev, "%s clk_prepare_enable(audiodsp) fail %d\n", + __func__, ret); + return ret; + } + + ret = clk_prepare_enable(priv->clk[CLK_TOP_ADSP_BUS]); + if (ret) { + dev_err(dev, "%s clk_prepare_enable(adsp_bus) fail %d\n", + __func__, ret); + clk_disable_unprepare(priv->clk[CLK_TOP_AUDIODSP]); + return ret; + } + + return 0; +} + +static void adsp_disable_all_clock(struct snd_sof_dev *sdev) +{ + struct adsp_priv *priv = sdev->pdata->hw_pdata; + + clk_disable_unprepare(priv->clk[CLK_TOP_ADSP_BUS]); + clk_disable_unprepare(priv->clk[CLK_TOP_AUDIODSP]); +} + +int mt8186_adsp_clock_on(struct snd_sof_dev *sdev) +{ + struct device *dev = sdev->dev; + int ret; + + ret = adsp_enable_all_clock(sdev); + if (ret) { + dev_err(dev, "failed to adsp_enable_clock: %d\n", ret); + return ret; + } + snd_sof_dsp_write(sdev, DSP_REG_BAR, ADSP_CK_EN, + UART_EN | DMA_EN | TIMER_EN | COREDBG_EN | CORE_CLK_EN); + snd_sof_dsp_write(sdev, DSP_REG_BAR, ADSP_UART_CTRL, + UART_BCLK_CG | UART_RSTN); + + return 0; +} + +void mt8186_adsp_clock_off(struct snd_sof_dev *sdev) +{ + snd_sof_dsp_write(sdev, DSP_REG_BAR, ADSP_CK_EN, 0); + snd_sof_dsp_write(sdev, DSP_REG_BAR, ADSP_UART_CTRL, 0); + adsp_disable_all_clock(sdev); +} + diff --git a/sound/soc/sof/mediatek/mt8186/mt8186-clk.h b/sound/soc/sof/mediatek/mt8186/mt8186-clk.h new file mode 100644 index 000000000000..89c23caf0fee --- /dev/null +++ b/sound/soc/sof/mediatek/mt8186/mt8186-clk.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ + +/* + * Copyright (c) 2022 MediaTek Corporation. All rights reserved. + * + * Header file for the mt8186 DSP clock definition + */ + +#ifndef __MT8186_CLK_H +#define __MT8186_CLK_H + +struct snd_sof_dev; + +/* DSP clock */ +enum adsp_clk_id { + CLK_TOP_AUDIODSP, + CLK_TOP_ADSP_BUS, + ADSP_CLK_MAX +}; + +int mt8186_adsp_init_clock(struct snd_sof_dev *sdev); +int mt8186_adsp_clock_on(struct snd_sof_dev *sdev); +void mt8186_adsp_clock_off(struct snd_sof_dev *sdev); +#endif diff --git a/sound/soc/sof/mediatek/mt8186/mt8186-loader.c b/sound/soc/sof/mediatek/mt8186/mt8186-loader.c new file mode 100644 index 000000000000..946e6c43204f --- /dev/null +++ b/sound/soc/sof/mediatek/mt8186/mt8186-loader.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// Copyright (c) 2022 Mediatek Corporation. All rights reserved. +// +// Author: Allen-KH Cheng <allen-kh.cheng@mediatek.com> +// Tinghan Shen <tinghan.shen@mediatek.com> +// +// Hardware interface for mt8186 DSP code loader + +#include <sound/sof.h> +#include "mt8186.h" +#include "../../ops.h" + +void mt8186_sof_hifixdsp_boot_sequence(struct snd_sof_dev *sdev, u32 boot_addr) +{ + /* set RUNSTALL to stop core */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, ADSP_HIFI_IO_CONFIG, + RUNSTALL, RUNSTALL); + + /* enable mbox 0 & 1 IRQ */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, ADSP_MBOX_IRQ_EN, + DSP_MBOX0_IRQ_EN | DSP_MBOX1_IRQ_EN, + DSP_MBOX0_IRQ_EN | DSP_MBOX1_IRQ_EN); + + /* set core boot address */ + snd_sof_dsp_write(sdev, DSP_SECREG_BAR, ADSP_ALTVEC_C0, boot_addr); + snd_sof_dsp_write(sdev, DSP_SECREG_BAR, ADSP_ALTVECSEL, ADSP_ALTVECSEL_C0); + + /* assert core reset */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, ADSP_CFGREG_SW_RSTN, + SW_RSTN_C0 | SW_DBG_RSTN_C0, + SW_RSTN_C0 | SW_DBG_RSTN_C0); + + /* hardware requirement */ + udelay(1); + + /* release core reset */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, ADSP_CFGREG_SW_RSTN, + SW_RSTN_C0 | SW_DBG_RSTN_C0, + 0); + + /* clear RUNSTALL (bit31) to start core */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, ADSP_HIFI_IO_CONFIG, + RUNSTALL, 0); +} + +void mt8186_sof_hifixdsp_shutdown(struct snd_sof_dev *sdev) +{ + /* set RUNSTALL to stop core */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, ADSP_HIFI_IO_CONFIG, + RUNSTALL, RUNSTALL); + + /* assert core reset */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, ADSP_CFGREG_SW_RSTN, + SW_RSTN_C0 | SW_DBG_RSTN_C0, + SW_RSTN_C0 | SW_DBG_RSTN_C0); +} + diff --git a/sound/soc/sof/mediatek/mt8186/mt8186.c b/sound/soc/sof/mediatek/mt8186/mt8186.c new file mode 100644 index 000000000000..3333a0634e29 --- /dev/null +++ b/sound/soc/sof/mediatek/mt8186/mt8186.c @@ -0,0 +1,554 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// Copyright(c) 2022 Mediatek Inc. All rights reserved. +// +// Author: Allen-KH Cheng <allen-kh.cheng@mediatek.com> +// Tinghan Shen <tinghan.shen@mediatek.com> + +/* + * Hardware interface for audio DSP on mt8186 + */ + +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/io.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/of_reserved_mem.h> +#include <linux/module.h> + +#include <sound/sof.h> +#include <sound/sof/xtensa.h> +#include "../../ops.h" +#include "../../sof-of-dev.h" +#include "../../sof-audio.h" +#include "../adsp_helper.h" +#include "mt8186.h" +#include "mt8186-clk.h" + +static int mt8186_get_mailbox_offset(struct snd_sof_dev *sdev) +{ + return MBOX_OFFSET; +} + +static int mt8186_get_window_offset(struct snd_sof_dev *sdev, u32 id) +{ + return MBOX_OFFSET; +} + +static int mt8186_send_msg(struct snd_sof_dev *sdev, + struct snd_sof_ipc_msg *msg) +{ + struct adsp_priv *priv = sdev->pdata->hw_pdata; + + sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, + msg->msg_size); + + return mtk_adsp_ipc_send(priv->dsp_ipc, MTK_ADSP_IPC_REQ, MTK_ADSP_IPC_OP_REQ); +} + +static void mt8186_get_reply(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc_msg *msg = sdev->msg; + struct sof_ipc_reply reply; + int ret = 0; + + if (!msg) { + dev_warn(sdev->dev, "unexpected ipc interrupt\n"); + return; + } + + /* get reply */ + sof_mailbox_read(sdev, sdev->host_box.offset, &reply, sizeof(reply)); + if (reply.error < 0) { + memcpy(msg->reply_data, &reply, sizeof(reply)); + ret = reply.error; + } else { + /* reply has correct size? */ + if (reply.hdr.size != msg->reply_size) { + dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n", + msg->reply_size, reply.hdr.size); + ret = -EINVAL; + } + + /* read the message */ + if (msg->reply_size > 0) + sof_mailbox_read(sdev, sdev->host_box.offset, + msg->reply_data, msg->reply_size); + } + + msg->reply_error = ret; +} + +static void mt8186_dsp_handle_reply(struct mtk_adsp_ipc *ipc) +{ + struct adsp_priv *priv = mtk_adsp_ipc_get_data(ipc); + unsigned long flags; + + spin_lock_irqsave(&priv->sdev->ipc_lock, flags); + mt8186_get_reply(priv->sdev); + snd_sof_ipc_reply(priv->sdev, 0); + spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags); +} + +static void mt8186_dsp_handle_request(struct mtk_adsp_ipc *ipc) +{ + struct adsp_priv *priv = mtk_adsp_ipc_get_data(ipc); + u32 p; /* panic code */ + int ret; + + /* Read the message from the debug box. */ + sof_mailbox_read(priv->sdev, priv->sdev->debug_box.offset + 4, + &p, sizeof(p)); + + /* Check to see if the message is a panic code 0x0dead*** */ + if ((p & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) { + snd_sof_dsp_panic(priv->sdev, p, true); + } else { + snd_sof_ipc_msgs_rx(priv->sdev); + + /* tell DSP cmd is done */ + ret = mtk_adsp_ipc_send(priv->dsp_ipc, MTK_ADSP_IPC_RSP, MTK_ADSP_IPC_OP_RSP); + if (ret) + dev_err(priv->dev, "request send ipc failed"); + } +} + +static struct mtk_adsp_ipc_ops dsp_ops = { + .handle_reply = mt8186_dsp_handle_reply, + .handle_request = mt8186_dsp_handle_request, +}; + +static int platform_parse_resource(struct platform_device *pdev, void *data) +{ + struct resource *mmio; + struct resource res; + struct device_node *mem_region; + struct device *dev = &pdev->dev; + struct mtk_adsp_chip_info *adsp = data; + int ret; + + mem_region = of_parse_phandle(dev->of_node, "memory-region", 0); + if (!mem_region) { + dev_err(dev, "no dma memory-region phandle\n"); + return -ENODEV; + } + + ret = of_address_to_resource(mem_region, 0, &res); + of_node_put(mem_region); + if (ret) { + dev_err(dev, "of_address_to_resource dma failed\n"); + return ret; + } + + dev_dbg(dev, "DMA %pR\n", &res); + + ret = of_reserved_mem_device_init(dev); + if (ret) { + dev_err(dev, "of_reserved_mem_device_init failed\n"); + return ret; + } + + mem_region = of_parse_phandle(dev->of_node, "memory-region", 1); + if (!mem_region) { + dev_err(dev, "no memory-region sysmem phandle\n"); + return -ENODEV; + } + + ret = of_address_to_resource(mem_region, 0, &res); + of_node_put(mem_region); + if (ret) { + dev_err(dev, "of_address_to_resource sysmem failed\n"); + return ret; + } + + adsp->pa_dram = (phys_addr_t)res.start; + if (adsp->pa_dram & DRAM_REMAP_MASK) { + dev_err(dev, "adsp memory(%#x) is not 4K-aligned\n", + (u32)adsp->pa_dram); + return -EINVAL; + } + + adsp->dramsize = resource_size(&res); + if (adsp->dramsize < TOTAL_SIZE_SHARED_DRAM_FROM_TAIL) { + dev_err(dev, "adsp memory(%#x) is not enough for share\n", + adsp->dramsize); + return -EINVAL; + } + + dev_dbg(dev, "dram pbase=%pa size=%#x\n", &adsp->pa_dram, adsp->dramsize); + + mmio = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cfg"); + if (!mmio) { + dev_err(dev, "no ADSP-CFG register resource\n"); + return -ENXIO; + } + + adsp->va_cfgreg = devm_ioremap_resource(dev, mmio); + if (IS_ERR(adsp->va_cfgreg)) + return PTR_ERR(adsp->va_cfgreg); + + adsp->pa_cfgreg = (phys_addr_t)mmio->start; + adsp->cfgregsize = resource_size(mmio); + + dev_dbg(dev, "cfgreg pbase=%pa size=%#x\n", &adsp->pa_cfgreg, adsp->cfgregsize); + + mmio = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram"); + if (!mmio) { + dev_err(dev, "no SRAM resource\n"); + return -ENXIO; + } + + adsp->pa_sram = (phys_addr_t)mmio->start; + adsp->sramsize = resource_size(mmio); + + dev_dbg(dev, "sram pbase=%pa size=%#x\n", &adsp->pa_sram, adsp->sramsize); + + mmio = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sec"); + if (!mmio) { + dev_err(dev, "no SEC register resource\n"); + return -ENXIO; + } + + adsp->va_secreg = devm_ioremap_resource(dev, mmio); + if (IS_ERR(adsp->va_secreg)) + return PTR_ERR(adsp->va_secreg); + + adsp->pa_secreg = (phys_addr_t)mmio->start; + adsp->secregsize = resource_size(mmio); + + dev_dbg(dev, "secreg pbase=%pa size=%#x\n", &adsp->pa_secreg, adsp->secregsize); + + mmio = platform_get_resource_byname(pdev, IORESOURCE_MEM, "bus"); + if (!mmio) { + dev_err(dev, "no BUS register resource\n"); + return -ENXIO; + } + + adsp->va_busreg = devm_ioremap_resource(dev, mmio); + if (IS_ERR(adsp->va_busreg)) + return PTR_ERR(adsp->va_busreg); + + adsp->pa_busreg = (phys_addr_t)mmio->start; + adsp->busregsize = resource_size(mmio); + + dev_dbg(dev, "busreg pbase=%pa size=%#x\n", &adsp->pa_busreg, adsp->busregsize); + + return 0; +} + +static void adsp_sram_power_on(struct snd_sof_dev *sdev) +{ + snd_sof_dsp_update_bits(sdev, DSP_BUSREG_BAR, ADSP_SRAM_POOL_CON, + DSP_SRAM_POOL_PD_MASK, 0); +} + +static void adsp_sram_power_off(struct snd_sof_dev *sdev) +{ + snd_sof_dsp_update_bits(sdev, DSP_BUSREG_BAR, ADSP_SRAM_POOL_CON, + DSP_SRAM_POOL_PD_MASK, DSP_SRAM_POOL_PD_MASK); +} + +/* Init the basic DSP DRAM address */ +static int adsp_memory_remap_init(struct snd_sof_dev *sdev, struct mtk_adsp_chip_info *adsp) +{ + u32 offset; + + offset = adsp->pa_dram - DRAM_PHYS_BASE_FROM_DSP_VIEW; + adsp->dram_offset = offset; + offset >>= DRAM_REMAP_SHIFT; + + dev_dbg(sdev->dev, "adsp->pa_dram %pa, offset %#x\n", &adsp->pa_dram, offset); + + snd_sof_dsp_write(sdev, DSP_BUSREG_BAR, DSP_C0_EMI_MAP_ADDR, offset); + snd_sof_dsp_write(sdev, DSP_BUSREG_BAR, DSP_C0_DMAEMI_MAP_ADDR, offset); + + if (offset != snd_sof_dsp_read(sdev, DSP_BUSREG_BAR, DSP_C0_EMI_MAP_ADDR) || + offset != snd_sof_dsp_read(sdev, DSP_BUSREG_BAR, DSP_C0_DMAEMI_MAP_ADDR)) { + dev_err(sdev->dev, "emi remap fail\n"); + return -EIO; + } + + return 0; +} + +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; + } + } + dev_dbg(dev, "shared-dram vbase=%p, phy addr :%pa, size=%#x\n", + adsp->shared_dram, &adsp->pa_shared_dram, shared_size); + + return 0; +} + +static int mt8186_run(struct snd_sof_dev *sdev) +{ + u32 adsp_bootup_addr; + + adsp_bootup_addr = SRAM_PHYS_BASE_FROM_DSP_VIEW; + dev_dbg(sdev->dev, "HIFIxDSP boot from base : 0x%08X\n", adsp_bootup_addr); + mt8186_sof_hifixdsp_boot_sequence(sdev, adsp_bootup_addr); + + return 0; +} + +static int mt8186_dsp_probe(struct snd_sof_dev *sdev) +{ + struct platform_device *pdev = container_of(sdev->dev, struct platform_device, dev); + struct adsp_priv *priv; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + sdev->pdata->hw_pdata = priv; + priv->dev = sdev->dev; + priv->sdev = sdev; + + priv->adsp = devm_kzalloc(&pdev->dev, sizeof(struct mtk_adsp_chip_info), GFP_KERNEL); + if (!priv->adsp) + return -ENOMEM; + + ret = platform_parse_resource(pdev, priv->adsp); + if (ret) + return ret; + + sdev->bar[SOF_FW_BLK_TYPE_IRAM] = devm_ioremap(sdev->dev, + priv->adsp->pa_sram, + priv->adsp->sramsize); + if (!sdev->bar[SOF_FW_BLK_TYPE_IRAM]) { + dev_err(sdev->dev, "failed to ioremap base %pa size %#x\n", + &priv->adsp->pa_sram, priv->adsp->sramsize); + return -ENOMEM; + } + + sdev->bar[SOF_FW_BLK_TYPE_SRAM] = devm_ioremap_wc(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); + return -ENOMEM; + } + + priv->adsp->va_dram = sdev->bar[SOF_FW_BLK_TYPE_SRAM]; + + ret = adsp_shared_base_ioremap(pdev, priv->adsp); + if (ret) { + dev_err(sdev->dev, "adsp_shared_base_ioremap fail!\n"); + return ret; + } + + sdev->bar[DSP_REG_BAR] = priv->adsp->va_cfgreg; + sdev->bar[DSP_SECREG_BAR] = priv->adsp->va_secreg; + sdev->bar[DSP_BUSREG_BAR] = priv->adsp->va_busreg; + + sdev->mmio_bar = SOF_FW_BLK_TYPE_SRAM; + sdev->mailbox_bar = SOF_FW_BLK_TYPE_SRAM; + + /* set default mailbox offset for FW ready message */ + sdev->dsp_box.offset = mt8186_get_mailbox_offset(sdev); + + ret = adsp_memory_remap_init(sdev, priv->adsp); + if (ret) { + dev_err(sdev->dev, "adsp_memory_remap_init fail!\n"); + return ret; + } + + /* enable adsp clock before touching registers */ + ret = mt8186_adsp_init_clock(sdev); + if (ret) { + dev_err(sdev->dev, "mt8186_adsp_init_clock failed\n"); + return ret; + } + + ret = mt8186_adsp_clock_on(sdev); + if (ret) { + dev_err(sdev->dev, "mt8186_adsp_clock_on fail!\n"); + return ret; + } + + adsp_sram_power_on(sdev); + + priv->ipc_dev = platform_device_register_data(&pdev->dev, "mtk-adsp-ipc", + PLATFORM_DEVID_NONE, + pdev, sizeof(*pdev)); + if (IS_ERR(priv->ipc_dev)) { + ret = IS_ERR(priv->ipc_dev); + dev_err(sdev->dev, "failed to create mtk-adsp-ipc device\n"); + goto err_adsp_off; + } + + priv->dsp_ipc = dev_get_drvdata(&priv->ipc_dev->dev); + if (!priv->dsp_ipc) { + ret = -EPROBE_DEFER; + dev_err(sdev->dev, "failed to get drvdata\n"); + goto exit_pdev_unregister; + } + + mtk_adsp_ipc_set_data(priv->dsp_ipc, priv); + priv->dsp_ipc->ops = &dsp_ops; + + return 0; + +exit_pdev_unregister: + platform_device_unregister(priv->ipc_dev); +err_adsp_off: + adsp_sram_power_off(sdev); + mt8186_adsp_clock_off(sdev); + + return ret; +} + +static int mt8186_dsp_remove(struct snd_sof_dev *sdev) +{ + struct adsp_priv *priv = sdev->pdata->hw_pdata; + + platform_device_unregister(priv->ipc_dev); + mt8186_sof_hifixdsp_shutdown(sdev); + adsp_sram_power_off(sdev); + mt8186_adsp_clock_off(sdev); + + return 0; +} + +static int mt8186_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state) +{ + mt8186_sof_hifixdsp_shutdown(sdev); + adsp_sram_power_off(sdev); + mt8186_adsp_clock_off(sdev); + + return 0; +} + +static int mt8186_dsp_resume(struct snd_sof_dev *sdev) +{ + int ret; + + ret = mt8186_adsp_clock_on(sdev); + if (ret) { + dev_err(sdev->dev, "mt8186_adsp_clock_on fail!\n"); + return ret; + } + + adsp_sram_power_on(sdev); + + return ret; +} + +/* on mt8186 there is 1 to 1 match between type and BAR idx */ +static int mt8186_get_bar_index(struct snd_sof_dev *sdev, u32 type) +{ + return type; +} + +static int mt8186_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz) +{ + sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); + return 0; +} + +/* mt8186 ops */ +static struct snd_sof_dsp_ops sof_mt8186_ops = { + /* probe and remove */ + .probe = mt8186_dsp_probe, + .remove = mt8186_dsp_remove, + + /* DSP core boot */ + .run = mt8186_run, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* Register IO */ + .write = sof_io_write, + .read = sof_io_read, + .write64 = sof_io_write64, + .read64 = sof_io_read64, + + /* ipc */ + .send_msg = mt8186_send_msg, + .get_mailbox_offset = mt8186_get_mailbox_offset, + .get_window_offset = mt8186_get_window_offset, + .ipc_msg_data = mt8186_ipc_msg_data, + .set_stream_data_offset = sof_set_stream_data_offset, + + /* misc */ + .get_bar_index = mt8186_get_bar_index, + + /* firmware loading */ + .load_firmware = snd_sof_load_firmware_memcpy, + + /* Firmware ops */ + .dsp_arch_ops = &sof_xtensa_arch_ops, + + /* PM */ + .suspend = mt8186_dsp_suspend, + .resume = mt8186_dsp_resume, + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, +}; + +static const struct sof_dev_desc sof_of_mt8186_desc = { + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "mediatek/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "mediatek/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-mt8186.ri", + }, + .nocodec_tplg_filename = "sof-mt8186-nocodec.tplg", + .ops = &sof_mt8186_ops, +}; + +static const struct of_device_id sof_of_mt8186_ids[] = { + { .compatible = "mediatek,mt8186-dsp", .data = &sof_of_mt8186_desc}, + { } +}; +MODULE_DEVICE_TABLE(of, sof_of_mt8186_ids); + +/* DT driver definition */ +static struct platform_driver snd_sof_of_mt8186_driver = { + .probe = sof_of_probe, + .remove = sof_of_remove, + .driver = { + .name = "sof-audio-of-mt8186", + .pm = &sof_of_pm, + .of_match_table = sof_of_mt8186_ids, + }, +}; +module_platform_driver(snd_sof_of_mt8186_driver); + +MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA); +MODULE_IMPORT_NS(SND_SOC_SOF_MTK_COMMON); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/mediatek/mt8186/mt8186.h b/sound/soc/sof/mediatek/mt8186/mt8186.h new file mode 100644 index 000000000000..98b2965e5ba6 --- /dev/null +++ b/sound/soc/sof/mediatek/mt8186/mt8186.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ + +/* + * Copyright (c) 2022 MediaTek Corporation. All rights reserved. + * + * Header file for the mt8186 DSP register definition + */ + +#ifndef __MT8186_H +#define __MT8186_H + +struct mtk_adsp_chip_info; +struct snd_sof_dev; + +#define DSP_REG_BAR 4 +#define DSP_SECREG_BAR 5 +#define DSP_BUSREG_BAR 6 + +/***************************************************************************** + * R E G I S T E R TABLE + *****************************************************************************/ +/* dsp cfg */ +#define ADSP_CFGREG_SW_RSTN 0x0000 +#define SW_DBG_RSTN_C0 BIT(0) +#define SW_RSTN_C0 BIT(4) +#define ADSP_HIFI_IO_CONFIG 0x000C +#define TRACEMEMREADY BIT(15) +#define RUNSTALL BIT(31) +#define ADSP_IRQ_MASK 0x0030 +#define ADSP_DVFSRC_REQ 0x0040 +#define ADSP_DDREN_REQ_0 0x0044 +#define ADSP_SEMAPHORE 0x0064 +#define ADSP_WDT_CON_C0 0x007C +#define ADSP_MBOX_IRQ_EN 0x009C +#define DSP_MBOX0_IRQ_EN BIT(0) +#define DSP_MBOX1_IRQ_EN BIT(1) +#define DSP_MBOX2_IRQ_EN BIT(2) +#define DSP_MBOX3_IRQ_EN BIT(3) +#define DSP_MBOX4_IRQ_EN BIT(4) +#define DSP_PDEBUGPC 0x013C +#define ADSP_CK_EN 0x1000 +#define CORE_CLK_EN BIT(0) +#define COREDBG_EN BIT(1) +#define TIMER_EN BIT(3) +#define DMA_EN BIT(4) +#define UART_EN BIT(5) +#define ADSP_UART_CTRL 0x1010 +#define UART_BCLK_CG BIT(0) +#define UART_RSTN BIT(3) + +/* dsp sec */ +#define ADSP_PRID 0x0 +#define ADSP_ALTVEC_C0 0x04 +#define ADSP_ALTVECSEL 0x0C +#define ADSP_ALTVECSEL_C0 BIT(1) + +/* dsp bus */ +#define ADSP_SRAM_POOL_CON 0x190 +#define DSP_SRAM_POOL_PD_MASK 0xF00F /* [0:3] and [12:15] */ +#define DSP_C0_EMI_MAP_ADDR 0xA00 /* ADSP Core0 To EMI Address Remap */ +#define DSP_C0_DMAEMI_MAP_ADDR 0xA08 /* DMA0 To EMI Address Remap */ + +/* DSP memories */ +#define MBOX_OFFSET 0x500000 /* DRAM */ +#define MBOX_SIZE 0x1000 /* consistent with which in memory.h of sof fw */ +#define DSP_DRAM_SIZE 0xA00000 /* 16M */ + +/*remap dram between AP and DSP view, 4KB aligned*/ +#define SRAM_PHYS_BASE_FROM_DSP_VIEW 0x4E100000 /* MT8186 DSP view */ +#define DRAM_PHYS_BASE_FROM_DSP_VIEW 0x60000000 /* MT8186 DSP view */ +#define DRAM_REMAP_SHIFT 12 +#define DRAM_REMAP_MASK 0xFFF + +#define SIZE_SHARED_DRAM_DL 0x40000 /*Shared buffer for Downlink*/ +#define SIZE_SHARED_DRAM_UL 0x40000 /*Shared buffer for Uplink*/ +#define TOTAL_SIZE_SHARED_DRAM_FROM_TAIL (SIZE_SHARED_DRAM_DL + SIZE_SHARED_DRAM_UL) + +void mt8186_sof_hifixdsp_boot_sequence(struct snd_sof_dev *sdev, u32 boot_addr); +void mt8186_sof_hifixdsp_shutdown(struct snd_sof_dev *sdev); +#endif diff --git a/sound/soc/sof/mediatek/mt8195/mt8195.c b/sound/soc/sof/mediatek/mt8195/mt8195.c index 3ab12f352935..30111ab23bf5 100644 --- a/sound/soc/sof/mediatek/mt8195/mt8195.c +++ b/sound/soc/sof/mediatek/mt8195/mt8195.c @@ -24,9 +24,103 @@ #include "../../sof-of-dev.h" #include "../../sof-audio.h" #include "../adsp_helper.h" +#include "../mtk-adsp-common.h" #include "mt8195.h" #include "mt8195-clk.h" +static int mt8195_get_mailbox_offset(struct snd_sof_dev *sdev) +{ + return MBOX_OFFSET; +} + +static int mt8195_get_window_offset(struct snd_sof_dev *sdev, u32 id) +{ + return MBOX_OFFSET; +} + +static int mt8195_send_msg(struct snd_sof_dev *sdev, + struct snd_sof_ipc_msg *msg) +{ + struct adsp_priv *priv = sdev->pdata->hw_pdata; + + sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, + msg->msg_size); + + return mtk_adsp_ipc_send(priv->dsp_ipc, MTK_ADSP_IPC_REQ, MTK_ADSP_IPC_OP_REQ); +} + +static void mt8195_get_reply(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc_msg *msg = sdev->msg; + struct sof_ipc_reply reply; + int ret = 0; + + if (!msg) { + dev_warn(sdev->dev, "unexpected ipc interrupt\n"); + return; + } + + /* get reply */ + sof_mailbox_read(sdev, sdev->host_box.offset, &reply, sizeof(reply)); + if (reply.error < 0) { + memcpy(msg->reply_data, &reply, sizeof(reply)); + ret = reply.error; + } else { + /* reply has correct size? */ + if (reply.hdr.size != msg->reply_size) { + dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n", + msg->reply_size, reply.hdr.size); + ret = -EINVAL; + } + + /* read the message */ + if (msg->reply_size > 0) + sof_mailbox_read(sdev, sdev->host_box.offset, + msg->reply_data, msg->reply_size); + } + + msg->reply_error = ret; +} + +static void mt8195_dsp_handle_reply(struct mtk_adsp_ipc *ipc) +{ + struct adsp_priv *priv = mtk_adsp_ipc_get_data(ipc); + unsigned long flags; + + spin_lock_irqsave(&priv->sdev->ipc_lock, flags); + mt8195_get_reply(priv->sdev); + snd_sof_ipc_reply(priv->sdev, 0); + spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags); +} + +static void mt8195_dsp_handle_request(struct mtk_adsp_ipc *ipc) +{ + struct adsp_priv *priv = mtk_adsp_ipc_get_data(ipc); + u32 p; /* panic code */ + int ret; + + /* Read the message from the debug box. */ + sof_mailbox_read(priv->sdev, priv->sdev->debug_box.offset + 4, + &p, sizeof(p)); + + /* Check to see if the message is a panic code 0x0dead*** */ + if ((p & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) { + snd_sof_dsp_panic(priv->sdev, p, true); + } else { + snd_sof_ipc_msgs_rx(priv->sdev); + + /* tell DSP cmd is done */ + ret = mtk_adsp_ipc_send(priv->dsp_ipc, MTK_ADSP_IPC_RSP, MTK_ADSP_IPC_OP_RSP); + if (ret) + dev_err(priv->dev, "request send ipc failed"); + } +} + +static struct mtk_adsp_ipc_ops dsp_ops = { + .handle_reply = mt8195_dsp_handle_reply, + .handle_request = mt8195_dsp_handle_request, +}; + static int platform_parse_resource(struct platform_device *pdev, void *data) { struct resource *mmio; @@ -285,15 +379,36 @@ static int mt8195_dsp_probe(struct snd_sof_dev *sdev) } sdev->bar[DSP_REG_BAR] = priv->adsp->va_cfgreg; - sdev->bar[DSP_MBOX0_BAR] = priv->adsp->va_mboxreg[0]; - sdev->bar[DSP_MBOX1_BAR] = priv->adsp->va_mboxreg[1]; - sdev->bar[DSP_MBOX2_BAR] = priv->adsp->va_mboxreg[2]; sdev->mmio_bar = SOF_FW_BLK_TYPE_SRAM; sdev->mailbox_bar = SOF_FW_BLK_TYPE_SRAM; + /* set default mailbox offset for FW ready message */ + sdev->dsp_box.offset = mt8195_get_mailbox_offset(sdev); + + priv->ipc_dev = platform_device_register_data(&pdev->dev, "mtk-adsp-ipc", + PLATFORM_DEVID_NONE, + pdev, sizeof(*pdev)); + if (IS_ERR(priv->ipc_dev)) { + ret = PTR_ERR(priv->ipc_dev); + dev_err(sdev->dev, "failed to register mtk-adsp-ipc device\n"); + goto err_adsp_sram_power_off; + } + + priv->dsp_ipc = dev_get_drvdata(&priv->ipc_dev->dev); + if (!priv->dsp_ipc) { + ret = -EPROBE_DEFER; + dev_err(sdev->dev, "failed to get drvdata\n"); + goto exit_pdev_unregister; + } + + mtk_adsp_ipc_set_data(priv->dsp_ipc, priv); + priv->dsp_ipc->ops = &dsp_ops; + return 0; +exit_pdev_unregister: + platform_device_unregister(priv->ipc_dev); err_adsp_sram_power_off: adsp_sram_power_on(&pdev->dev, false); exit_clk_disable: @@ -302,10 +417,17 @@ exit_clk_disable: return ret; } +static int mt8195_dsp_shutdown(struct snd_sof_dev *sdev) +{ + return snd_sof_suspend(sdev->dev); +} + static int mt8195_dsp_remove(struct snd_sof_dev *sdev) { struct platform_device *pdev = container_of(sdev->dev, struct platform_device, dev); + struct adsp_priv *priv = sdev->pdata->hw_pdata; + platform_device_unregister(priv->ipc_dev); adsp_sram_power_on(&pdev->dev, false); adsp_clock_off(sdev); @@ -356,6 +478,39 @@ static int mt8195_get_bar_index(struct snd_sof_dev *sdev, u32 type) return type; } +static int mt8195_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz) +{ + sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); + return 0; +} + +static void mt8195_adsp_dump(struct snd_sof_dev *sdev, u32 flags) +{ + u32 dbg_pc, dbg_data, dbg_bus0, dbg_bus1, dbg_inst; + u32 dbg_ls0stat, dbg_ls1stat, faultbus, faultinfo, swrest; + + /* dump debug registers */ + dbg_pc = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGPC); + dbg_data = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGDATA); + dbg_bus0 = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGBUS0); + dbg_bus1 = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGBUS1); + dbg_inst = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGINST); + dbg_ls0stat = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGLS0STAT); + dbg_ls1stat = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGLS1STAT); + faultbus = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PFAULTBUS); + faultinfo = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PFAULTINFO); + swrest = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_RESET_SW); + + dev_info(sdev->dev, "adsp dump : pc %#x, data %#x, bus0 %#x, bus1 %#x, swrest %#x", + dbg_pc, dbg_data, dbg_bus0, dbg_bus1, swrest); + dev_info(sdev->dev, "dbg_inst %#x, ls0stat %#x, ls1stat %#x, faultbus %#x, faultinfo %#x", + dbg_inst, dbg_ls0stat, dbg_ls1stat, faultbus, faultinfo); + + mtk_adsp_dump(sdev, flags); +} + static struct snd_soc_dai_driver mt8195_dai[] = { { .name = "SOF_DL2", @@ -388,10 +543,11 @@ static struct snd_soc_dai_driver mt8195_dai[] = { }; /* mt8195 ops */ -static const struct snd_sof_dsp_ops sof_mt8195_ops = { +static struct snd_sof_dsp_ops sof_mt8195_ops = { /* probe and remove */ .probe = mt8195_dsp_probe, .remove = mt8195_dsp_remove, + .shutdown = mt8195_dsp_shutdown, /* DSP core boot */ .run = mt8195_run, @@ -406,17 +562,25 @@ static const struct snd_sof_dsp_ops sof_mt8195_ops = { .write64 = sof_io_write64, .read64 = sof_io_read64, + /* ipc */ + .send_msg = mt8195_send_msg, + .get_mailbox_offset = mt8195_get_mailbox_offset, + .get_window_offset = mt8195_get_window_offset, + .ipc_msg_data = mt8195_ipc_msg_data, + .set_stream_data_offset = sof_set_stream_data_offset, + /* misc */ .get_bar_index = mt8195_get_bar_index, - /* module loading */ - .load_module = snd_sof_parse_module_memcpy, /* firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy, /* Firmware ops */ .dsp_arch_ops = &sof_xtensa_arch_ops, + /* Debug information */ + .dbg_dump = mt8195_adsp_dump, + /* DAI drivers */ .drv = mt8195_dai, .num_drv = ARRAY_SIZE(mt8195_dai), @@ -434,11 +598,20 @@ static const struct snd_sof_dsp_ops sof_mt8195_ops = { }; static const struct sof_dev_desc sof_of_mt8195_desc = { - .default_fw_path = "mediatek/sof", - .default_tplg_path = "mediatek/sof-tplg", - .default_fw_filename = "sof-mt8195.ri", + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "mediatek/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "mediatek/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-mt8195.ri", + }, .nocodec_tplg_filename = "sof-mt8195-nocodec.tplg", .ops = &sof_mt8195_ops, + .ipc_timeout = 1000, }; static const struct of_device_id sof_of_mt8195_ids[] = { @@ -451,6 +624,7 @@ MODULE_DEVICE_TABLE(of, sof_of_mt8195_ids); static struct platform_driver snd_sof_of_mt8195_driver = { .probe = sof_of_probe, .remove = sof_of_remove, + .shutdown = sof_of_shutdown, .driver = { .name = "sof-audio-of-mt8195", .pm = &sof_of_pm, diff --git a/sound/soc/sof/mediatek/mtk-adsp-common.c b/sound/soc/sof/mediatek/mtk-adsp-common.c new file mode 100644 index 000000000000..1e0769c668a7 --- /dev/null +++ b/sound/soc/sof/mediatek/mtk-adsp-common.c @@ -0,0 +1,84 @@ +// 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 MediaTek Inc. All rights reserved. +// +// Author: YC Hung <yc.hung@mediatek.com> + +/* + * Common helpers for the audio DSP on MediaTek platforms + */ + +#include <linux/module.h> +#include <sound/sof/xtensa.h> +#include "../ops.h" +#include "mtk-adsp-common.h" + +/** + * mtk_adsp_get_registers() - This function is called in case of DSP oops + * in order to gather information about the registers, filename and + * linenumber and stack. + * @sdev: SOF device + * @xoops: Stores information about registers. + * @panic_info: Stores information about filename and line number. + * @stack: Stores the stack dump. + * @stack_words: Size of the stack dump. + */ +static void mtk_adsp_get_registers(struct snd_sof_dev *sdev, + struct sof_ipc_dsp_oops_xtensa *xoops, + struct sof_ipc_panic_info *panic_info, + u32 *stack, size_t stack_words) +{ + u32 offset = sdev->dsp_oops_offset; + + /* first read registers */ + sof_mailbox_read(sdev, offset, xoops, sizeof(*xoops)); + + /* then get panic info */ + if (xoops->arch_hdr.totalsize > EXCEPT_MAX_HDR_SIZE) { + dev_err(sdev->dev, "invalid header size 0x%x\n", + xoops->arch_hdr.totalsize); + return; + } + offset += xoops->arch_hdr.totalsize; + sof_mailbox_read(sdev, offset, panic_info, sizeof(*panic_info)); + + /* then get the stack */ + offset += sizeof(*panic_info); + sof_mailbox_read(sdev, offset, stack, stack_words * sizeof(u32)); +} + +/** + * mtk_adsp_dump() - This function is called when a panic message is + * received from the firmware. + * @sdev: SOF device + * @flags: parameter not used but required by ops prototype + */ +void mtk_adsp_dump(struct snd_sof_dev *sdev, u32 flags) +{ + char *level = (flags & SOF_DBG_DUMP_OPTIONAL) ? KERN_DEBUG : KERN_ERR; + struct sof_ipc_dsp_oops_xtensa xoops; + struct sof_ipc_panic_info panic_info; + u32 stack[MTK_ADSP_STACK_DUMP_SIZE]; + u32 status; + + /* Get information about the panic status from the debug box area. + * Compute the trace point based on the status. + */ + sof_mailbox_read(sdev, sdev->debug_box.offset + 0x4, &status, 4); + + /* Get information about the registers, the filename and line + * number and the stack. + */ + mtk_adsp_get_registers(sdev, &xoops, &panic_info, stack, + MTK_ADSP_STACK_DUMP_SIZE); + + /* Print the information to the console */ + sof_print_oops_and_stack(sdev, level, status, status, &xoops, &panic_info, + stack, MTK_ADSP_STACK_DUMP_SIZE); +} +EXPORT_SYMBOL(mtk_adsp_dump); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/mediatek/mtk-adsp-common.h b/sound/soc/sof/mediatek/mtk-adsp-common.h new file mode 100644 index 000000000000..612cff1f38f7 --- /dev/null +++ b/sound/soc/sof/mediatek/mtk-adsp-common.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ + +#ifndef __MEDIATEK_ADSP_COMMON_H__ +#define __MEDIATEK_ADSP_COMMON_H__ + +#define EXCEPT_MAX_HDR_SIZE 0x400 +#define MTK_ADSP_STACK_DUMP_SIZE 32 + +void mtk_adsp_dump(struct snd_sof_dev *sdev, u32 flags); +#endif diff --git a/sound/soc/sof/ops.c b/sound/soc/sof/ops.c index 235e2ef72178..ff066de4ceb9 100644 --- a/sound/soc/sof/ops.c +++ b/sound/soc/sof/ops.c @@ -177,7 +177,7 @@ void snd_sof_dsp_panic(struct snd_sof_dev *sdev, u32 offset, bool non_recoverabl snd_sof_dsp_dbg_dump(sdev, "DSP panic!", SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX); sof_set_fw_state(sdev, SOF_FW_CRASHED); - snd_sof_trace_notify_for_error(sdev); + sof_fw_trace_fw_crashed(sdev); } else { snd_sof_dsp_dbg_dump(sdev, "DSP panic (recovery will be attempted)", diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h index a19474663767..b79ae4f66eba 100644 --- a/sound/soc/sof/ops.h +++ b/sound/soc/sof/ops.h @@ -21,6 +21,14 @@ #define sof_ops(sdev) \ ((sdev)->pdata->desc->ops) +static inline int sof_ops_init(struct snd_sof_dev *sdev) +{ + if (sdev->pdata->desc->ops_init) + return sdev->pdata->desc->ops_init(sdev); + + return 0; +} + /* Mandatory operations are verified during probing */ /* init */ @@ -367,32 +375,6 @@ static inline int snd_sof_dsp_send_msg(struct snd_sof_dev *sdev, return sof_ops(sdev)->send_msg(sdev, msg); } -/* host DMA trace */ -static inline int snd_sof_dma_trace_init(struct snd_sof_dev *sdev, - struct sof_ipc_dma_trace_params_ext *dtrace_params) -{ - if (sof_ops(sdev)->trace_init) - return sof_ops(sdev)->trace_init(sdev, dtrace_params); - - return 0; -} - -static inline int snd_sof_dma_trace_release(struct snd_sof_dev *sdev) -{ - if (sof_ops(sdev)->trace_release) - return sof_ops(sdev)->trace_release(sdev); - - return 0; -} - -static inline int snd_sof_dma_trace_trigger(struct snd_sof_dev *sdev, int cmd) -{ - if (sof_ops(sdev)->trace_trigger) - return sof_ops(sdev)->trace_trigger(sdev, cmd); - - return 0; -} - /* host PCM ops */ static inline int snd_sof_pcm_platform_open(struct snd_sof_dev *sdev, diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 658cd8966c9a..a76d0b5b2ad9 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -82,8 +82,10 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream) } EXPORT_SYMBOL(snd_sof_pcm_period_elapsed); -int sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_runtime *rtd, - struct snd_sof_pcm *spcm, int dir) +static int +sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_runtime *rtd, + struct snd_sof_pcm *spcm, struct snd_pcm_hw_params *params, + struct snd_sof_platform_stream_params *platform_params, int dir) { struct snd_soc_dai *dai; int ret, j; @@ -102,7 +104,7 @@ int sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm spcm->stream[dir].list = list; - ret = sof_widget_list_setup(sdev, spcm, dir); + ret = sof_widget_list_setup(sdev, spcm, params, platform_params, dir); if (ret < 0) { dev_err(sdev->dev, "error: failed widget list set up for pcm %d dir %d\n", spcm->pcm.pcm_id, dir); @@ -150,9 +152,16 @@ static int sof_pcm_hw_params(struct snd_soc_component *component, dev_dbg(component->dev, "pcm: hw params stream %d dir %d\n", spcm->pcm.pcm_id, substream->stream); + ret = snd_sof_pcm_platform_hw_params(sdev, substream, params, &platform_params); + if (ret < 0) { + dev_err(component->dev, "platform hw params failed\n"); + return ret; + } + /* if this is a repeated hw_params without hw_free, skip setting up widgets */ if (!spcm->stream[substream->stream].list) { - ret = sof_pcm_setup_connected_widgets(sdev, rtd, spcm, substream->stream); + ret = sof_pcm_setup_connected_widgets(sdev, rtd, spcm, params, &platform_params, + substream->stream); if (ret < 0) return ret; } @@ -166,12 +175,6 @@ static int sof_pcm_hw_params(struct snd_soc_component *component, return ret; } - ret = snd_sof_pcm_platform_hw_params(sdev, substream, params, &platform_params); - if (ret < 0) { - dev_err(component->dev, "platform hw params failed\n"); - return ret; - } - if (pcm_ops->hw_params) { ret = pcm_ops->hw_params(component, substream, params, &platform_params); if (ret < 0) @@ -347,12 +350,9 @@ static int sof_pcm_trigger(struct snd_soc_component *component, snd_sof_pcm_platform_trigger(sdev, substream, cmd); /* free PCM if reset_hw_params is set and the STOP IPC is successful */ - if (!ret && reset_hw_params) { + if (!ret && reset_hw_params) ret = sof_pcm_stream_free(sdev, substream, spcm, substream->stream, free_widget_list); - if (ret < 0) - return ret; - } return ret; } @@ -396,7 +396,7 @@ static int sof_pcm_open(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_pcm_runtime *runtime = substream->runtime; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); - const struct snd_sof_dsp_ops *ops = sof_ops(sdev); + struct snd_sof_dsp_ops *ops = sof_ops(sdev); struct snd_sof_pcm *spcm; struct snd_soc_tplg_stream_caps *caps; int ret; diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c index 1c319582ca6f..18eb327a57f0 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -102,11 +102,18 @@ static int sof_resume(struct device *dev, bool runtime_resume) /* * Nothing further to be done for platforms that support the low power - * D0 substate. + * D0 substate. Resume trace and return when resuming from + * low-power D0 substate */ if (!runtime_resume && sof_ops(sdev)->set_power_state && - old_state == SOF_DSP_PM_D0) + old_state == SOF_DSP_PM_D0) { + ret = sof_fw_trace_resume(sdev); + if (ret < 0) + /* non fatal */ + dev_warn(sdev->dev, + "failed to enable trace after resume %d\n", ret); return 0; + } sof_set_fw_state(sdev, SOF_FW_BOOT_PREPARE); @@ -135,8 +142,8 @@ static int sof_resume(struct device *dev, bool runtime_resume) return ret; } - /* resume DMA trace, only need send ipc */ - ret = snd_sof_init_trace_ipc(sdev); + /* resume DMA trace */ + ret = sof_fw_trace_resume(sdev); if (ret < 0) { /* non fatal */ dev_warn(sdev->dev, @@ -187,7 +194,7 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) /* prepare for streams to be resumed properly upon resume */ if (!runtime_suspend) { - ret = sof_set_hw_params_upon_resume(sdev->dev); + ret = snd_sof_dsp_hw_params_upon_resume(sdev); if (ret < 0) { dev_err(sdev->dev, "error: setting hw_params flag during suspend %d\n", @@ -201,6 +208,7 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) /* Skip to platform-specific suspend if DSP is entering D0 */ if (target_state == SOF_DSP_PM_D0) { + sof_fw_trace_suspend(sdev, pm_state); /* Notify clients not managed by pm framework about core suspend */ sof_suspend_clients(sdev, pm_state); goto suspend; @@ -209,8 +217,8 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) if (tplg_ops->tear_down_all_pipelines) tplg_ops->tear_down_all_pipelines(sdev, false); - /* release trace */ - snd_sof_release_trace(sdev); + /* suspend DMA trace */ + sof_fw_trace_suspend(sdev, pm_state); /* Notify clients not managed by pm framework about core suspend */ sof_suspend_clients(sdev, pm_state); diff --git a/sound/soc/sof/sof-acpi-dev.c b/sound/soc/sof/sof-acpi-dev.c index 74982c04497b..1b04dcb33293 100644 --- a/sound/soc/sof/sof-acpi-dev.c +++ b/sound/soc/sof/sof-acpi-dev.c @@ -74,20 +74,20 @@ int sof_acpi_probe(struct platform_device *pdev, const struct sof_dev_desc *desc sof_pdata->desc = desc; sof_pdata->dev = &pdev->dev; - sof_pdata->fw_filename = desc->default_fw_filename; + sof_pdata->fw_filename = desc->default_fw_filename[SOF_IPC]; /* alternate fw and tplg filenames ? */ if (fw_path) sof_pdata->fw_filename_prefix = fw_path; else sof_pdata->fw_filename_prefix = - sof_pdata->desc->default_fw_path; + sof_pdata->desc->default_fw_path[SOF_IPC]; if (tplg_path) sof_pdata->tplg_filename_prefix = tplg_path; else sof_pdata->tplg_filename_prefix = - sof_pdata->desc->default_tplg_path; + sof_pdata->desc->default_tplg_path[SOF_IPC]; /* set callback to be called on successful device probe to enable runtime_pm */ sof_pdata->sof_probe_complete = sof_acpi_probe_complete; diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index b2f009a0c5b7..8d740635a4bb 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -12,61 +12,18 @@ #include "sof-audio.h" #include "ops.h" -static int sof_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol) -{ - int ret; - - /* reset readback offset for scontrol */ - scontrol->readback_offset = 0; - - ret = snd_sof_ipc_set_get_comp_data(scontrol, true); - if (ret < 0) - dev_err(sdev->dev, "error: failed kcontrol value set for widget: %d\n", - scontrol->comp_id); - - return ret; -} - -static int sof_widget_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) -{ - struct snd_sof_control *scontrol; - int ret; - - /* set up all controls for the widget */ - list_for_each_entry(scontrol, &sdev->kcontrol_list, list) - if (scontrol->comp_id == swidget->comp_id) { - /* set kcontrol data in DSP */ - ret = sof_kcontrol_setup(sdev, scontrol); - if (ret < 0) { - dev_err(sdev->dev, "error: fail to set up kcontrols for widget %s\n", - swidget->widget->name); - return ret; - } - - /* - * Read back the data from the DSP for static widgets. This is particularly - * useful for binary kcontrols associated with static pipeline widgets to - * initialize the data size to match that in the DSP. - */ - if (swidget->dynamic_pipeline_widget) - continue; - - ret = snd_sof_ipc_set_get_comp_data(scontrol, false); - if (ret < 0) - dev_warn(sdev->dev, "Failed kcontrol get for control in widget %s\n", - swidget->widget->name); - } - - return 0; -} - static void sof_reset_route_setup_status(struct snd_sof_dev *sdev, struct snd_sof_widget *widget) { + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; struct snd_sof_route *sroute; list_for_each_entry(sroute, &sdev->route_list, list) - if (sroute->src_widget == widget || sroute->sink_widget == widget) + if (sroute->src_widget == widget || sroute->sink_widget == widget) { + if (sroute->setup && tplg_ops->route_free) + tplg_ops->route_free(sdev, sroute); + sroute->setup = false; + } } int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) @@ -82,6 +39,9 @@ int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) if (--swidget->use_count) return 0; + /* reset route setup status for all routes that contain this widget */ + sof_reset_route_setup_status(sdev, swidget); + /* continue to disable core even if IPC fails */ if (tplg_ops->widget_free) err = tplg_ops->widget_free(sdev, swidget); @@ -98,10 +58,6 @@ int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) err = ret; } - /* reset route setup status for all routes that contain this widget */ - sof_reset_route_setup_status(sdev, swidget); - swidget->complete = 0; - /* * free the scheduler widget (same as pipe_widget) associated with the current swidget. * skip for static pipelines @@ -110,6 +66,7 @@ int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) ret = sof_widget_free(sdev, swidget->pipe_widget); if (ret < 0 && !err) err = ret; + swidget->pipe_widget->complete = 0; } if (!err) @@ -179,11 +136,10 @@ int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) } /* restore kcontrols for widget */ - ret = sof_widget_kcontrol_setup(sdev, swidget); - if (ret < 0) { - dev_err(sdev->dev, "error: failed to restore kcontrols for widget %s\n", - swidget->widget->name); - goto widget_free; + if (tplg_ops->control->widget_kcontrol_setup) { + ret = tplg_ops->control->widget_kcontrol_setup(sdev, swidget); + if (ret < 0) + goto widget_free; } dev_dbg(sdev->dev, "widget %s setup complete\n", swidget->widget->name); @@ -301,28 +257,249 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev, return 0; } -int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir) +static void +sof_unprepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget) +{ + const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; + const struct sof_ipc_tplg_widget_ops *widget_ops = ipc_tplg_ops->widget; + struct snd_sof_widget *swidget = widget->dobj.private; + struct snd_soc_dapm_path *p; + + if (!widget_ops[widget->id].ipc_unprepare || !swidget->prepared) + goto sink_unprepare; + + /* unprepare the source widget */ + widget_ops[widget->id].ipc_unprepare(swidget); + swidget->prepared = false; + +sink_unprepare: + /* unprepare all widgets in the sink paths */ + snd_soc_dapm_widget_for_each_sink_path(widget, p) { + if (!p->walking && p->sink->dobj.private) { + p->walking = true; + sof_unprepare_widgets_in_path(sdev, p->sink); + p->walking = false; + } + } +} + +static int +sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget, + struct snd_pcm_hw_params *fe_params, + struct snd_sof_platform_stream_params *platform_params, + struct snd_pcm_hw_params *pipeline_params, int dir) +{ + const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; + const struct sof_ipc_tplg_widget_ops *widget_ops = ipc_tplg_ops->widget; + struct snd_sof_widget *swidget = widget->dobj.private; + struct snd_soc_dapm_path *p; + int ret; + + if (!widget_ops[widget->id].ipc_prepare || swidget->prepared) + goto sink_prepare; + + /* prepare the source widget */ + ret = widget_ops[widget->id].ipc_prepare(swidget, fe_params, platform_params, + pipeline_params, dir); + if (ret < 0) { + dev_err(sdev->dev, "failed to prepare widget %s\n", widget->name); + return ret; + } + + swidget->prepared = true; + +sink_prepare: + /* prepare all widgets in the sink paths */ + snd_soc_dapm_widget_for_each_sink_path(widget, p) { + if (!p->walking && p->sink->dobj.private) { + p->walking = true; + ret = sof_prepare_widgets_in_path(sdev, p->sink, fe_params, + platform_params, pipeline_params, dir); + p->walking = false; + if (ret < 0) { + /* unprepare the source widget */ + if (!widget_ops[widget->id].ipc_unprepare && swidget->prepared) { + widget_ops[widget->id].ipc_unprepare(swidget); + swidget->prepared = false; + } + return ret; + } + } + } + + return 0; +} + +/* + * free all widgets in the sink path starting from the source widget + * (DAI type for capture, AIF type for playback) + */ +static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget, + int dir) +{ + struct snd_soc_dapm_path *p; + int err; + int ret = 0; + + /* free all widgets even in case of error to keep use counts balanced */ + snd_soc_dapm_widget_for_each_sink_path(widget, p) { + if (!p->walking && p->sink->dobj.private && widget->dobj.private) { + p->walking = true; + if (WIDGET_IS_AIF_OR_DAI(widget->id)) { + err = sof_widget_free(sdev, widget->dobj.private); + if (err < 0) + ret = err; + } + + err = sof_widget_free(sdev, p->sink->dobj.private); + if (err < 0) + ret = err; + + err = sof_free_widgets_in_path(sdev, p->sink, dir); + if (err < 0) + ret = err; + p->walking = false; + } + } + + return ret; +} + +/* + * set up all widgets in the sink path starting from the source widget + * (DAI type for capture, AIF type for playback). + * The error path in this function ensures that all successfully set up widgets getting freed. + */ +static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget, + int dir) +{ + struct snd_soc_dapm_path *p; + int ret; + + snd_soc_dapm_widget_for_each_sink_path(widget, p) { + if (!p->walking && p->sink->dobj.private && widget->dobj.private) { + p->walking = true; + if (WIDGET_IS_AIF_OR_DAI(widget->id)) { + ret = sof_widget_setup(sdev, widget->dobj.private); + if (ret < 0) + goto out; + } + + ret = sof_widget_setup(sdev, p->sink->dobj.private); + if (ret < 0) { + if (WIDGET_IS_AIF_OR_DAI(widget->id)) + sof_widget_free(sdev, widget->dobj.private); + goto out; + } + + ret = sof_set_up_widgets_in_path(sdev, p->sink, dir); + if (ret < 0) { + if (WIDGET_IS_AIF_OR_DAI(widget->id)) + sof_widget_free(sdev, widget->dobj.private); + sof_widget_free(sdev, p->sink->dobj.private); + } +out: + p->walking = false; + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int +sof_walk_widgets_in_order(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget_list *list, + struct snd_pcm_hw_params *fe_params, + struct snd_sof_platform_stream_params *platform_params, int dir, + enum sof_widget_op op) +{ + struct snd_soc_dapm_widget *widget; + char *str; + int ret = 0; + int i; + + for_each_dapm_widgets(list, i, widget) { + /* starting widget for playback is AIF type */ + if (dir == SNDRV_PCM_STREAM_PLAYBACK && !WIDGET_IS_AIF(widget->id)) + continue; + + /* starting widget for capture is DAI type */ + if (dir == SNDRV_PCM_STREAM_CAPTURE && !WIDGET_IS_DAI(widget->id)) + continue; + + switch (op) { + case SOF_WIDGET_SETUP: + ret = sof_set_up_widgets_in_path(sdev, widget, dir); + str = "set up"; + break; + case SOF_WIDGET_FREE: + ret = sof_free_widgets_in_path(sdev, widget, dir); + str = "free"; + break; + case SOF_WIDGET_PREPARE: + { + struct snd_pcm_hw_params pipeline_params; + + str = "prepare"; + /* + * When walking the list of connected widgets, the pipeline_params for each + * widget is modified by the source widget in the path. Use a local + * copy of the runtime params as the pipeline_params so that the runtime + * params does not get overwritten. + */ + memcpy(&pipeline_params, fe_params, sizeof(*fe_params)); + + ret = sof_prepare_widgets_in_path(sdev, widget, fe_params, + platform_params, &pipeline_params, dir); + break; + } + case SOF_WIDGET_UNPREPARE: + sof_unprepare_widgets_in_path(sdev, widget); + break; + default: + dev_err(sdev->dev, "Invalid widget op %d\n", op); + return -EINVAL; + } + if (ret < 0) { + dev_err(sdev->dev, "Failed to %s connected widgets\n", str); + return ret; + } + } + + return 0; +} + +int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, + struct snd_pcm_hw_params *fe_params, + struct snd_sof_platform_stream_params *platform_params, + int dir) { const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list; struct snd_soc_dapm_widget *widget; - int i, ret, num_widgets; + int i, ret; /* nothing to set up */ if (!list) return 0; - /* set up widgets in the list */ - for_each_dapm_widgets(list, num_widgets, widget) { - struct snd_sof_widget *swidget = widget->dobj.private; - - if (!swidget) - continue; + /* + * Prepare widgets for set up. The prepare step is used to allocate memory, assign + * instance ID and pick the widget configuration based on the runtime PCM params. + */ + ret = sof_walk_widgets_in_order(sdev, list, fe_params, platform_params, + dir, SOF_WIDGET_PREPARE); + if (ret < 0) + return ret; - /* set up the widget */ - ret = sof_widget_setup(sdev, swidget); - if (ret < 0) - goto widget_free; + /* Set up is used to send the IPC to the DSP to create the widget */ + ret = sof_walk_widgets_in_order(sdev, list, fe_params, platform_params, + dir, SOF_WIDGET_SETUP); + if (ret < 0) { + ret = sof_walk_widgets_in_order(sdev, list, fe_params, platform_params, + dir, SOF_WIDGET_UNPREPARE); + return ret; } /* @@ -364,18 +541,9 @@ int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, in return 0; widget_free: - /* free all widgets that have been set up successfully */ - for_each_dapm_widgets(list, i, widget) { - struct snd_sof_widget *swidget = widget->dobj.private; - - if (!swidget) - continue; - - if (!num_widgets--) - break; - - sof_widget_free(sdev, swidget); - } + sof_walk_widgets_in_order(sdev, list, fe_params, platform_params, dir, + SOF_WIDGET_FREE); + sof_walk_widgets_in_order(sdev, list, NULL, NULL, dir, SOF_WIDGET_UNPREPARE); return ret; } @@ -383,37 +551,22 @@ widget_free: int sof_widget_list_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir) { struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list; - struct snd_soc_dapm_widget *widget; - int i, ret; - int ret1 = 0; + int ret; /* nothing to free */ if (!list) return 0; - /* - * Free widgets in the list. This can fail but continue freeing other widgets to keep - * use_counts balanced. - */ - for_each_dapm_widgets(list, i, widget) { - struct snd_sof_widget *swidget = widget->dobj.private; - - if (!swidget) - continue; + /* send IPC to free widget in the DSP */ + ret = sof_walk_widgets_in_order(sdev, list, NULL, NULL, dir, SOF_WIDGET_FREE); - /* - * free widget and its pipe_widget. Either of these can fail, but free as many as - * possible before freeing the list and returning the error. - */ - ret = sof_widget_free(sdev, swidget); - if (ret < 0) - ret1 = ret; - } + /* unprepare the widget */ + sof_walk_widgets_in_order(sdev, list, NULL, NULL, dir, SOF_WIDGET_UNPREPARE); snd_soc_dapm_dai_free_widgets(&list); spcm->stream[dir].list = NULL; - return ret1; + return ret; } /* @@ -462,42 +615,6 @@ bool snd_sof_stream_suspend_ignored(struct snd_sof_dev *sdev) return false; } -int sof_set_hw_params_upon_resume(struct device *dev) -{ - struct snd_sof_dev *sdev = dev_get_drvdata(dev); - struct snd_pcm_substream *substream; - struct snd_sof_pcm *spcm; - snd_pcm_state_t state; - int dir; - - /* - * SOF requires hw_params to be set-up internally upon resume. - * So, set the flag to indicate this for those streams that - * have been suspended. - */ - list_for_each_entry(spcm, &sdev->pcm_list, list) { - for_each_pcm_streams(dir) { - /* - * do not reset hw_params upon resume for streams that - * were kept running during suspend - */ - if (spcm->stream[dir].suspend_ignored) - continue; - - substream = spcm->stream[dir].substream; - if (!substream || !substream->runtime) - continue; - - state = substream->runtime->status->state; - if (state == SNDRV_PCM_STATE_SUSPENDED) - spcm->prepared[dir] = false; - } - } - - /* set internal flag for BE */ - return snd_sof_dsp_hw_params_upon_resume(sdev); -} - int sof_pcm_stream_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, struct snd_sof_pcm *spcm, int dir, bool free_widget_list) { @@ -701,7 +818,8 @@ int sof_machine_check(struct snd_sof_dev *sdev) return -ENOMEM; mach->drv_name = "sof-nocodec"; - sof_pdata->tplg_filename = desc->nocodec_tplg_filename; + if (!sof_pdata->tplg_filename) + sof_pdata->tplg_filename = desc->nocodec_tplg_filename; sof_pdata->machine = mach; snd_sof_set_mach_params(mach, sdev); diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index 7f15b3bc8196..27cc5fb642e5 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -29,16 +29,47 @@ #define DMA_CHAN_INVALID 0xFFFFFFFF #define WIDGET_IS_DAI(id) ((id) == snd_soc_dapm_dai_in || (id) == snd_soc_dapm_dai_out) +#define WIDGET_IS_AIF(id) ((id) == snd_soc_dapm_aif_in || (id) == snd_soc_dapm_aif_out) +#define WIDGET_IS_AIF_OR_DAI(id) (WIDGET_IS_DAI(id) || WIDGET_IS_AIF(id)) #define SOF_DAI_CLK_INTEL_SSP_MCLK 0 #define SOF_DAI_CLK_INTEL_SSP_BCLK 1 +enum sof_widget_op { + SOF_WIDGET_PREPARE, + SOF_WIDGET_SETUP, + SOF_WIDGET_FREE, + SOF_WIDGET_UNPREPARE, +}; + /* * Volume fractional word length define to 16 sets * the volume linear gain value to use Qx.16 format */ #define VOLUME_FWL 16 +#define SOF_TLV_ITEMS 3 + +static inline u32 mixer_to_ipc(unsigned int value, u32 *volume_map, int size) +{ + if (value >= size) + return volume_map[size - 1]; + + return volume_map[value]; +} + +static inline u32 ipc_to_mixer(u32 value, u32 *volume_map, int size) +{ + int i; + + for (i = 0; i < size; i++) { + if (volume_map[i] >= value) + return i; + } + + return i - 1; +} + struct snd_sof_widget; struct snd_sof_route; struct snd_sof_control; @@ -86,6 +117,11 @@ struct sof_ipc_tplg_control_ops { const unsigned int __user *binary_data, unsigned int size); /* update control data based on notification from the DSP */ void (*update)(struct snd_sof_dev *sdev, void *ipc_control_message); + /* Optional callback to setup kcontrols associated with an swidget */ + int (*widget_kcontrol_setup)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget); + /* mandatory callback to set up volume table for volume kcontrols */ + int (*set_up_volume_table)(struct snd_sof_control *scontrol, int tlv[SOF_TLV_ITEMS], + int size); }; /** @@ -95,6 +131,8 @@ struct sof_ipc_tplg_control_ops { * @token_list: List of token ID's that should be parsed for the widget * @token_list_size: number of elements in token_list * @bind_event: Function pointer for binding events to the widget + * @ipc_prepare: Optional op for preparing a widget for set up + * @ipc_unprepare: Optional op for unpreparing a widget */ struct sof_ipc_tplg_widget_ops { int (*ipc_setup)(struct snd_sof_widget *swidget); @@ -103,6 +141,11 @@ struct sof_ipc_tplg_widget_ops { int token_list_size; int (*bind_event)(struct snd_soc_component *scomp, struct snd_sof_widget *swidget, u16 event_type); + int (*ipc_prepare)(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 *source_params, int dir); + void (*ipc_unprepare)(struct snd_sof_widget *swidget); }; /** @@ -112,6 +155,7 @@ struct sof_ipc_tplg_widget_ops { * initialized to 0. * @control: Pointer to the IPC-specific ops for topology kcontrol IO * @route_setup: Function pointer for setting up pipeline connections + * @route_free: Optional op for freeing pipeline connections. * @token_list: List of all tokens supported by the IPC version. The size of the token_list * array should be SOF_TOKEN_COUNT. The unused elements in the array will be * initialized to 0. @@ -129,6 +173,7 @@ struct sof_ipc_tplg_ops { const struct sof_ipc_tplg_widget_ops *widget; const struct sof_ipc_tplg_control_ops *control; int (*route_setup)(struct snd_sof_dev *sdev, struct snd_sof_route *sroute); + int (*route_free)(struct snd_sof_dev *sdev, struct snd_sof_route *sroute); const struct sof_token_info *token_list; int (*control_setup)(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol); int (*control_free)(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol); @@ -247,7 +292,6 @@ struct snd_sof_control { int max_volume_step; /* max volume step for volume_table */ int num_channels; unsigned int access; - u32 readback_offset; /* offset to mmapped data if used */ int info_type; int index; /* pipeline ID */ void *priv; /* private data copied from topology */ @@ -292,10 +336,24 @@ struct snd_sof_widget { struct snd_soc_component *scomp; int comp_id; int pipeline_id; + /* + * complete flag is used to indicate that pipeline set up is complete for scheduler type + * widgets. It is unused for all other widget types. + */ int complete; + /* + * the prepared flag is used to indicate that a widget has been prepared for getting set + * up in the DSP. + */ + bool prepared; int use_count; /* use_count will be protected by the PCM mutex held by the core */ int core; - int id; + int id; /* id is the DAPM widget type */ + /* + * Instance ID is set dynamically when the widget gets set up in the FW. It should be + * unique for each module type across all pipelines. This will not be used in SOF_IPC. + */ + int instance_id; /* * Flag indicating if the widget should be set up dynamically when a PCM is opened. @@ -310,6 +368,7 @@ struct snd_sof_widget { struct snd_soc_dapm_widget *widget; struct list_head list; /* list in sdev widget list */ struct snd_sof_widget *pipe_widget; + void *module_info; const guid_t uuid; @@ -403,8 +462,7 @@ struct snd_sof_pcm *snd_sof_find_spcm_dai(struct snd_soc_component *scomp, struct snd_soc_pcm_runtime *rtd) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - - struct snd_sof_pcm *spcm = NULL; + struct snd_sof_pcm *spcm; list_for_each_entry(spcm, &sdev->pcm_list, list) { if (le32_to_cpu(spcm->pcm.dai_id) == rtd->dai_link->id) @@ -430,16 +488,10 @@ static inline void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstre static inline void snd_sof_compr_init_elapsed_work(struct work_struct *work) { } #endif -/* - * Mixer IPC - */ -int snd_sof_ipc_set_get_comp_data(struct snd_sof_control *scontrol, bool set); - /* DAI link fixup */ int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params); /* PM */ -int sof_set_hw_params_upon_resume(struct device *dev); bool snd_sof_stream_suspend_ignored(struct snd_sof_dev *sdev); bool snd_sof_dsp_only_d0i3_compatible_stream_active(struct snd_sof_dev *sdev); @@ -453,7 +505,10 @@ int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsourc struct snd_soc_dapm_widget *wsink); /* PCM */ -int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir); +int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, + struct snd_pcm_hw_params *fe_params, + struct snd_sof_platform_stream_params *platform_params, + int dir); int sof_widget_list_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir); int sof_pcm_dsp_pcm_free(struct snd_pcm_substream *substream, struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm); @@ -467,6 +522,5 @@ int get_token_uuid(void *elem, void *object, u32 offset); int sof_update_ipc_object(struct snd_soc_component *scomp, void *object, enum sof_tokens token_id, struct snd_sof_tuple *tuples, int num_tuples, size_t object_size, int token_instance_num); -int sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_runtime *rtd, - struct snd_sof_pcm *spcm, int dir); +u32 vol_compute_gain(u32 value, int *tlv); #endif diff --git a/sound/soc/sof/sof-client-ipc-flood-test.c b/sound/soc/sof/sof-client-ipc-flood-test.c index db3a052c5dd2..4bdecd80248a 100644 --- a/sound/soc/sof/sof-client-ipc-flood-test.c +++ b/sound/soc/sof/sof-client-ipc-flood-test.c @@ -217,10 +217,9 @@ static ssize_t sof_ipc_flood_dfs_write(struct file *file, const char __user *buf ipc_count = MAX_IPC_FLOOD_COUNT; } - ret = pm_runtime_get_sync(dev); + ret = pm_runtime_resume_and_get(dev); if (ret < 0 && ret != -EACCES) { dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret); - pm_runtime_put_noidle(dev); goto out; } diff --git a/sound/soc/sof/sof-client-ipc-msg-injector.c b/sound/soc/sof/sof-client-ipc-msg-injector.c index dba6cfd7db09..03490a4d4ae7 100644 --- a/sound/soc/sof/sof-client-ipc-msg-injector.c +++ b/sound/soc/sof/sof-client-ipc-msg-injector.c @@ -15,6 +15,7 @@ #include <linux/slab.h> #include <linux/uaccess.h> #include <sound/sof/header.h> +#include <sound/sof/ipc4/header.h> #include "sof-client.h" @@ -22,6 +23,8 @@ struct sof_msg_inject_priv { struct dentry *dfs_file; + size_t max_msg_size; + enum sof_ipc_type ipc_type; void *tx_buffer; void *rx_buffer; @@ -66,44 +69,157 @@ static ssize_t sof_msg_inject_dfs_read(struct file *file, char __user *buffer, return count; } -static ssize_t sof_msg_inject_dfs_write(struct file *file, const char __user *buffer, - size_t count, loff_t *ppos) +static ssize_t sof_msg_inject_ipc4_dfs_read(struct file *file, + char __user *buffer, + size_t count, loff_t *ppos) { struct sof_client_dev *cdev = file->private_data; struct sof_msg_inject_priv *priv = cdev->data; - struct device *dev = &cdev->auxdev.dev; - int ret, err; - size_t size; + struct sof_ipc4_msg *ipc4_msg = priv->rx_buffer; + size_t header_size = sizeof(ipc4_msg->header_u64); + size_t remaining; - if (*ppos) + if (!ipc4_msg->header_u64 || !count || *ppos) return 0; - size = simple_write_to_buffer(priv->tx_buffer, SOF_IPC_MSG_MAX_SIZE, - ppos, buffer, count); - if (size != count) - return size > 0 ? -EFAULT : size; + /* we need space for the header at minimum (u64) */ + if (count < header_size) + return -ENOSPC; + + remaining = header_size; + + /* Only get large config have payload */ + if (SOF_IPC4_MSG_IS_MODULE_MSG(ipc4_msg->primary) && + (SOF_IPC4_MSG_TYPE_GET(ipc4_msg->primary) == SOF_IPC4_MOD_LARGE_CONFIG_GET)) + remaining += ipc4_msg->data_size; + + if (count > remaining) + count = remaining; + else if (count < remaining) + remaining = count; + + /* copy the header first */ + if (copy_to_user(buffer, &ipc4_msg->header_u64, header_size)) + return -EFAULT; + + *ppos += header_size; + remaining -= header_size; + + if (!remaining) + return count; + + if (remaining > ipc4_msg->data_size) + remaining = ipc4_msg->data_size; + + /* Copy the payload */ + if (copy_to_user(buffer + *ppos, ipc4_msg->data_ptr, remaining)) + return -EFAULT; + + *ppos += remaining; + return count; +} - ret = pm_runtime_get_sync(dev); +static int sof_msg_inject_send_message(struct sof_client_dev *cdev) +{ + struct sof_msg_inject_priv *priv = cdev->data; + struct device *dev = &cdev->auxdev.dev; + int ret, err; + + ret = pm_runtime_resume_and_get(dev); if (ret < 0 && ret != -EACCES) { dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret); - pm_runtime_put_noidle(dev); return ret; } /* send the message */ - memset(priv->rx_buffer, 0, SOF_IPC_MSG_MAX_SIZE); ret = sof_client_ipc_tx_message(cdev, priv->tx_buffer, priv->rx_buffer, - SOF_IPC_MSG_MAX_SIZE); + priv->max_msg_size); + if (ret) + dev_err(dev, "IPC message send failed: %d\n", ret); + pm_runtime_mark_last_busy(dev); err = pm_runtime_put_autosuspend(dev); if (err < 0) dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", err); - /* return size if test is successful */ - if (ret >= 0) - ret = size; - return ret; +} + +static ssize_t sof_msg_inject_dfs_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_msg_inject_priv *priv = cdev->data; + size_t size; + int ret; + + if (*ppos) + return 0; + + size = simple_write_to_buffer(priv->tx_buffer, priv->max_msg_size, + ppos, buffer, count); + if (size != count) + return size > 0 ? -EFAULT : size; + + memset(priv->rx_buffer, 0, priv->max_msg_size); + + ret = sof_msg_inject_send_message(cdev); + + /* return the error code if test failed */ + if (ret < 0) + size = ret; + + return size; +}; + +static ssize_t sof_msg_inject_ipc4_dfs_write(struct file *file, + const char __user *buffer, + size_t count, loff_t *ppos) +{ + 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; + size_t size; + int ret; + + if (*ppos) + return 0; + + if (count < sizeof(ipc4_msg->header_u64)) + 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 != sizeof(ipc4_msg->header_u64)) + return size > 0 ? -EFAULT : size; + + count -= size; + if (!count) { + /* Copy the payload */ + size = simple_write_to_buffer(ipc4_msg->data_ptr, + priv->max_msg_size, ppos, buffer, + count); + if (size != count) + return size > 0 ? -EFAULT : size; + } + + ipc4_msg->data_size = count; + + /* Initialize the reply storage */ + ipc4_msg = priv->rx_buffer; + ipc4_msg->header_u64 = 0; + ipc4_msg->data_size = priv->max_msg_size; + memset(ipc4_msg->data_ptr, 0, priv->max_msg_size); + + ret = sof_msg_inject_send_message(cdev); + + /* return the error code if test failed */ + if (ret < 0) + size = ret; + + return size; }; static int sof_msg_inject_dfs_release(struct inode *inode, struct file *file) @@ -123,28 +239,61 @@ static const struct file_operations sof_msg_inject_fops = { .owner = THIS_MODULE, }; +static const struct file_operations sof_msg_inject_ipc4_fops = { + .open = sof_msg_inject_dfs_open, + .read = sof_msg_inject_ipc4_dfs_read, + .write = sof_msg_inject_ipc4_dfs_write, + .llseek = default_llseek, + .release = sof_msg_inject_dfs_release, + + .owner = THIS_MODULE, +}; + static int sof_msg_inject_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id) { struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); struct dentry *debugfs_root = sof_client_get_debugfs_root(cdev); + static const struct file_operations *fops; struct device *dev = &auxdev->dev; struct sof_msg_inject_priv *priv; + size_t alloc_size; /* allocate memory for client data */ priv = devm_kzalloc(&auxdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; - priv->tx_buffer = devm_kmalloc(dev, SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); - priv->rx_buffer = devm_kzalloc(dev, SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); + priv->ipc_type = sof_client_get_ipc_type(cdev); + priv->max_msg_size = sof_client_get_ipc_max_payload_size(cdev); + alloc_size = priv->max_msg_size; + + if (priv->ipc_type == SOF_INTEL_IPC4) + alloc_size += sizeof(struct sof_ipc4_msg); + + priv->tx_buffer = devm_kmalloc(dev, alloc_size, GFP_KERNEL); + priv->rx_buffer = devm_kzalloc(dev, alloc_size, GFP_KERNEL); if (!priv->tx_buffer || !priv->rx_buffer) return -ENOMEM; + if (priv->ipc_type == SOF_INTEL_IPC4) { + struct sof_ipc4_msg *ipc4_msg; + + ipc4_msg = priv->tx_buffer; + ipc4_msg->data_ptr = priv->tx_buffer + sizeof(struct sof_ipc4_msg); + + ipc4_msg = priv->rx_buffer; + ipc4_msg->data_ptr = priv->rx_buffer + sizeof(struct sof_ipc4_msg); + + fops = &sof_msg_inject_ipc4_fops; + } else { + fops = &sof_msg_inject_fops; + } + cdev->data = priv; priv->dfs_file = debugfs_create_file("ipc_msg_inject", 0644, debugfs_root, - cdev, &sof_msg_inject_fops); + cdev, fops); /* enable runtime PM */ pm_runtime_set_autosuspend_delay(dev, SOF_IPC_CLIENT_SUSPEND_DELAY_MS); diff --git a/sound/soc/sof/sof-client-probes.c b/sound/soc/sof/sof-client-probes.c index 797dedb26163..34e6bd356e71 100644 --- a/sound/soc/sof/sof-client-probes.c +++ b/sound/soc/sof/sof-client-probes.c @@ -132,6 +132,7 @@ static int sof_probes_deinit(struct sof_client_dev *cdev) static int sof_probes_info(struct sof_client_dev *cdev, unsigned int cmd, void **params, size_t *num_params) { + size_t max_msg_size = sof_client_get_ipc_max_payload_size(cdev); struct sof_ipc_probe_info_params msg = {{{0}}}; struct sof_ipc_probe_info_params *reply; size_t bytes; @@ -140,13 +141,13 @@ static int sof_probes_info(struct sof_client_dev *cdev, unsigned int cmd, *params = NULL; *num_params = 0; - reply = kzalloc(SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); + reply = kzalloc(max_msg_size, GFP_KERNEL); if (!reply) return -ENOMEM; msg.rhdr.hdr.size = sizeof(msg); msg.rhdr.hdr.cmd = SOF_IPC_GLB_PROBE | cmd; - ret = sof_client_ipc_tx_message(cdev, &msg, reply, SOF_IPC_MSG_MAX_SIZE); + ret = sof_client_ipc_tx_message(cdev, &msg, reply, max_msg_size); if (ret < 0 || reply->rhdr.error < 0) goto exit; @@ -503,10 +504,9 @@ static ssize_t sof_probes_dfs_points_read(struct file *file, char __user *to, if (!buf) return -ENOMEM; - ret = pm_runtime_get_sync(dev); + ret = pm_runtime_resume_and_get(dev); if (ret < 0 && ret != -EACCES) { dev_err_ratelimited(dev, "debugfs read failed to resume %d\n", ret); - pm_runtime_put_noidle(dev); goto exit; } @@ -568,10 +568,9 @@ sof_probes_dfs_points_write(struct file *file, const char __user *from, desc = (struct sof_probe_point_desc *)tkns; - ret = pm_runtime_get_sync(dev); + ret = pm_runtime_resume_and_get(dev); if (ret < 0 && ret != -EACCES) { dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret); - pm_runtime_put_noidle(dev); goto exit; } @@ -621,10 +620,9 @@ sof_probes_dfs_points_remove_write(struct file *file, const char __user *from, goto exit; } - ret = pm_runtime_get_sync(dev); + ret = pm_runtime_resume_and_get(dev); if (ret < 0) { dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret); - pm_runtime_put_noidle(dev); goto exit; } diff --git a/sound/soc/sof/sof-client.c b/sound/soc/sof/sof-client.c index 686ad0c3bb61..16cca666bb85 100644 --- a/sound/soc/sof/sof-client.c +++ b/sound/soc/sof/sof-client.c @@ -12,6 +12,7 @@ #include <linux/module.h> #include <linux/mutex.h> #include <linux/slab.h> +#include <sound/sof/ipc4/header.h> #include "ops.h" #include "sof-client.h" #include "sof-priv.h" @@ -72,6 +73,9 @@ static int sof_register_ipc_flood_test(struct snd_sof_dev *sdev) int ret = 0; int i; + if (sdev->pdata->ipc_type != SOF_IPC) + return 0; + for (i = 0; i < CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_NUM; i++) { ret = sof_client_dev_register(sdev, "ipc_flood", i, NULL, 0); if (ret < 0) @@ -245,10 +249,19 @@ EXPORT_SYMBOL_NS_GPL(sof_client_dev_unregister, SND_SOC_SOF_CLIENT); int sof_client_ipc_tx_message(struct sof_client_dev *cdev, void *ipc_msg, void *reply_data, size_t reply_bytes) { - struct sof_ipc_cmd_hdr *hdr = ipc_msg; + if (cdev->sdev->pdata->ipc_type == SOF_IPC) { + struct sof_ipc_cmd_hdr *hdr = ipc_msg; + + return sof_ipc_tx_message(cdev->sdev->ipc, ipc_msg, hdr->size, + reply_data, reply_bytes); + } else if (cdev->sdev->pdata->ipc_type == SOF_INTEL_IPC4) { + struct sof_ipc4_msg *msg = ipc_msg; - return sof_ipc_tx_message(cdev->sdev->ipc, hdr->cmd, ipc_msg, hdr->size, - reply_data, reply_bytes); + return sof_ipc_tx_message(cdev->sdev->ipc, ipc_msg, msg->data_size, + reply_data, reply_bytes); + } + + return -EINVAL; } EXPORT_SYMBOL_NS_GPL(sof_client_ipc_tx_message, SND_SOC_SOF_CLIENT); @@ -319,6 +332,22 @@ const struct sof_ipc_fw_version *sof_client_get_fw_version(struct sof_client_dev } EXPORT_SYMBOL_NS_GPL(sof_client_get_fw_version, SND_SOC_SOF_CLIENT); +size_t sof_client_get_ipc_max_payload_size(struct sof_client_dev *cdev) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + + return sdev->ipc->max_payload_size; +} +EXPORT_SYMBOL_NS_GPL(sof_client_get_ipc_max_payload_size, SND_SOC_SOF_CLIENT); + +enum sof_ipc_type sof_client_get_ipc_type(struct sof_client_dev *cdev) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + + return sdev->pdata->ipc_type; +} +EXPORT_SYMBOL_NS_GPL(sof_client_get_ipc_type, SND_SOC_SOF_CLIENT); + /* module refcount management of SOF core */ int sof_client_core_module_get(struct sof_client_dev *cdev) { @@ -342,9 +371,22 @@ EXPORT_SYMBOL_NS_GPL(sof_client_core_module_put, SND_SOC_SOF_CLIENT); /* IPC event handling */ void sof_client_ipc_rx_dispatcher(struct snd_sof_dev *sdev, void *msg_buf) { - struct sof_ipc_cmd_hdr *hdr = msg_buf; - u32 msg_type = hdr->cmd & SOF_GLB_TYPE_MASK; struct sof_ipc_event_entry *event; + u32 msg_type; + + if (sdev->pdata->ipc_type == SOF_IPC) { + struct sof_ipc_cmd_hdr *hdr = msg_buf; + + msg_type = hdr->cmd & SOF_GLB_TYPE_MASK; + } else if (sdev->pdata->ipc_type == SOF_INTEL_IPC4) { + struct sof_ipc4_msg *msg = 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); + return; + } mutex_lock(&sdev->client_event_handler_mutex); @@ -363,9 +405,21 @@ int sof_client_register_ipc_rx_handler(struct sof_client_dev *cdev, struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); struct sof_ipc_event_entry *event; - if (!callback || !(ipc_msg_type & SOF_GLB_TYPE_MASK)) + if (!callback) return -EINVAL; + if (cdev->sdev->pdata->ipc_type == SOF_IPC) { + if (!(ipc_msg_type & SOF_GLB_TYPE_MASK)) + return -EINVAL; + } else if (cdev->sdev->pdata->ipc_type == SOF_INTEL_IPC4) { + if (!(ipc_msg_type & SOF_IPC4_NOTIFICATION_TYPE_MASK)) + return -EINVAL; + } else { + dev_warn(sdev->dev, "%s: Not supported IPC version: %d\n", + __func__, sdev->pdata->ipc_type); + return -EINVAL; + } + event = kmalloc(sizeof(*event), GFP_KERNEL); if (!event) return -ENOMEM; diff --git a/sound/soc/sof/sof-client.h b/sound/soc/sof/sof-client.h index 4b6394b4c694..46b215d9200f 100644 --- a/sound/soc/sof/sof-client.h +++ b/sound/soc/sof/sof-client.h @@ -41,6 +41,8 @@ int sof_client_ipc_tx_message(struct sof_client_dev *cdev, void *ipc_msg, struct dentry *sof_client_get_debugfs_root(struct sof_client_dev *cdev); struct device *sof_client_get_dma_dev(struct sof_client_dev *cdev); const struct sof_ipc_fw_version *sof_client_get_fw_version(struct sof_client_dev *cdev); +size_t sof_client_get_ipc_max_payload_size(struct sof_client_dev *cdev); +enum sof_ipc_type sof_client_get_ipc_type(struct sof_client_dev *cdev); /* module refcount management of SOF core */ int sof_client_core_module_get(struct sof_client_dev *cdev); diff --git a/sound/soc/sof/sof-of-dev.c b/sound/soc/sof/sof-of-dev.c index e3718638f9ce..53faeccedd4f 100644 --- a/sound/soc/sof/sof-of-dev.c +++ b/sound/soc/sof/sof-of-dev.c @@ -64,17 +64,17 @@ int sof_of_probe(struct platform_device *pdev) sof_pdata->desc = desc; sof_pdata->dev = &pdev->dev; - sof_pdata->fw_filename = desc->default_fw_filename; + sof_pdata->fw_filename = desc->default_fw_filename[SOF_IPC]; if (fw_path) sof_pdata->fw_filename_prefix = fw_path; else - sof_pdata->fw_filename_prefix = sof_pdata->desc->default_fw_path; + sof_pdata->fw_filename_prefix = sof_pdata->desc->default_fw_path[SOF_IPC]; if (tplg_path) sof_pdata->tplg_filename_prefix = tplg_path; else - sof_pdata->tplg_filename_prefix = sof_pdata->desc->default_tplg_path; + sof_pdata->tplg_filename_prefix = sof_pdata->desc->default_tplg_path[SOF_IPC]; /* set callback to be called on successful device probe to enable runtime_pm */ sof_pdata->sof_probe_complete = sof_of_probe_complete; @@ -95,4 +95,10 @@ int sof_of_remove(struct platform_device *pdev) } EXPORT_SYMBOL(sof_of_remove); +void sof_of_shutdown(struct platform_device *pdev) +{ + snd_sof_device_shutdown(&pdev->dev); +} +EXPORT_SYMBOL(sof_of_shutdown); + MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/sof-of-dev.h b/sound/soc/sof/sof-of-dev.h index 4e0f6588dad9..fd950a222ba4 100644 --- a/sound/soc/sof/sof-of-dev.h +++ b/sound/soc/sof/sof-of-dev.h @@ -13,5 +13,6 @@ extern const struct dev_pm_ops sof_of_pm; int sof_of_probe(struct platform_device *pdev); int sof_of_remove(struct platform_device *pdev); +void sof_of_shutdown(struct platform_device *pdev); #endif diff --git a/sound/soc/sof/sof-pci-dev.c b/sound/soc/sof/sof-pci-dev.c index 7fa2649e56e5..d627092b399d 100644 --- a/sound/soc/sof/sof-pci-dev.c +++ b/sound/soc/sof/sof-pci-dev.c @@ -12,6 +12,7 @@ #include <linux/dmi.h> #include <linux/module.h> #include <linux/pci.h> +#include <linux/platform_data/x86/soc.h> #include <linux/pm_runtime.h> #include <sound/soc-acpi.h> #include <sound/soc-acpi-intel-match.h> @@ -23,21 +24,34 @@ static char *fw_path; module_param(fw_path, charp, 0444); MODULE_PARM_DESC(fw_path, "alternate path for SOF firmware."); +static char *fw_filename; +module_param(fw_filename, charp, 0444); +MODULE_PARM_DESC(fw_filename, "alternate filename for SOF firmware."); + static char *tplg_path; module_param(tplg_path, charp, 0444); MODULE_PARM_DESC(tplg_path, "alternate path for SOF topology."); +static char *tplg_filename; +module_param(tplg_filename, charp, 0444); +MODULE_PARM_DESC(tplg_filename, "alternate filename for SOF topology."); + static int sof_pci_debug; module_param_named(sof_pci_debug, sof_pci_debug, int, 0444); MODULE_PARM_DESC(sof_pci_debug, "SOF PCI debug options (0x0 all off)"); -static const char *sof_override_tplg_name; +static int sof_pci_ipc_type = -1; +module_param_named(ipc_type, sof_pci_ipc_type, int, 0444); +MODULE_PARM_DESC(ipc_type, "SOF IPC type (0): SOF, (1) Intel CAVS"); + +static const char *sof_dmi_override_tplg_name; +static bool sof_dmi_use_community_key; #define SOF_PCI_DISABLE_PM_RUNTIME BIT(0) static int sof_tplg_cb(const struct dmi_system_id *id) { - sof_override_tplg_name = id->driver_data; + sof_dmi_override_tplg_name = id->driver_data; return 1; } @@ -94,15 +108,35 @@ static const struct dmi_system_id sof_tplg_table[] = { {} }; +/* all Up boards use the community key */ +static int up_use_community_key(const struct dmi_system_id *id) +{ + sof_dmi_use_community_key = true; + return 1; +} + +/* + * For ApolloLake Chromebooks we want to force the use of the Intel production key. + * All newer platforms use the community key + */ +static int chromebook_use_community_key(const struct dmi_system_id *id) +{ + if (!soc_intel_is_apl()) + sof_dmi_use_community_key = true; + return 1; +} + static const struct dmi_system_id community_key_platforms[] = { { .ident = "Up boards", + .callback = up_use_community_key, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "AAEON"), } }, { .ident = "Google Chromebooks", + .callback = chromebook_use_community_key, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Google"), } @@ -178,7 +212,36 @@ int sof_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) sof_pdata->name = pci_name(pci); sof_pdata->desc = desc; sof_pdata->dev = dev; - sof_pdata->fw_filename = desc->default_fw_filename; + + sof_pdata->ipc_type = desc->ipc_default; + + if (sof_pci_ipc_type < 0) { + sof_pdata->ipc_type = desc->ipc_default; + } else { + dev_info(dev, "overriding default IPC %d to requested %d\n", + desc->ipc_default, sof_pci_ipc_type); + if (sof_pci_ipc_type >= SOF_IPC_TYPE_COUNT) { + dev_err(dev, "invalid request value %d\n", sof_pci_ipc_type); + ret = -EINVAL; + goto out; + } + if (!(BIT(sof_pci_ipc_type) & desc->ipc_supported_mask)) { + dev_err(dev, "invalid request value %d, supported mask is %#x\n", + sof_pci_ipc_type, desc->ipc_supported_mask); + ret = -EINVAL; + goto out; + } + sof_pdata->ipc_type = sof_pci_ipc_type; + } + + if (fw_filename) { + sof_pdata->fw_filename = fw_filename; + + dev_dbg(dev, "Module parameter used, changed fw filename to %s\n", + sof_pdata->fw_filename); + } else { + sof_pdata->fw_filename = desc->default_fw_filename[sof_pdata->ipc_type]; + } /* * for platforms using the SOF community key, change the @@ -195,10 +258,10 @@ int sof_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) "Module parameter used, changed fw path to %s\n", sof_pdata->fw_filename_prefix); - } else if (dmi_check_system(community_key_platforms)) { + } else if (dmi_check_system(community_key_platforms) && sof_dmi_use_community_key) { sof_pdata->fw_filename_prefix = devm_kasprintf(dev, GFP_KERNEL, "%s/%s", - sof_pdata->desc->default_fw_path, + sof_pdata->desc->default_fw_path[sof_pdata->ipc_type], "community"); dev_dbg(dev, @@ -206,24 +269,37 @@ int sof_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) sof_pdata->fw_filename_prefix); } else { sof_pdata->fw_filename_prefix = - sof_pdata->desc->default_fw_path; + sof_pdata->desc->default_fw_path[sof_pdata->ipc_type]; } if (tplg_path) sof_pdata->tplg_filename_prefix = tplg_path; else sof_pdata->tplg_filename_prefix = - sof_pdata->desc->default_tplg_path; + sof_pdata->desc->default_tplg_path[sof_pdata->ipc_type]; - dmi_check_system(sof_tplg_table); - if (sof_override_tplg_name) - sof_pdata->tplg_filename = sof_override_tplg_name; + /* + * the topology filename will be provided in the machine descriptor, unless + * it is overridden by a module parameter or DMI quirk. + */ + if (tplg_filename) { + sof_pdata->tplg_filename = tplg_filename; + + dev_dbg(dev, "Module parameter used, changed tplg filename to %s\n", + sof_pdata->tplg_filename); + } else { + dmi_check_system(sof_tplg_table); + if (sof_dmi_override_tplg_name) + sof_pdata->tplg_filename = sof_dmi_override_tplg_name; + } /* set callback to be called on successful device probe to enable runtime_pm */ sof_pdata->sof_probe_complete = sof_pci_probe_complete; /* call sof helper for DSP hardware probe */ ret = snd_sof_device_probe(dev, sof_pdata); + +out: if (ret) pci_release_regions(pci); diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 0d9b640ae24c..9d7f53ff9c70 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -181,11 +181,6 @@ struct snd_sof_dsp_ops { int (*load_firmware)(struct snd_sof_dev *sof_dev); /* mandatory */ int (*load_module)(struct snd_sof_dev *sof_dev, struct snd_sof_mod_hdr *hdr); /* optional */ - /* - * FW ready checks for ABI compatibility and creates - * memory windows at first boot - */ - int (*fw_ready)(struct snd_sof_dev *sdev, u32 msg_id); /* mandatory */ /* connect pcm substream to a host stream */ int (*pcm_open)(struct snd_sof_dev *sdev, @@ -259,8 +254,9 @@ struct snd_sof_dsp_ops { size_t size, const char *name, enum sof_debugfs_access_type access_type); /* optional */ - /* host DMA trace initialization */ + /* host DMA trace (IPC3) */ int (*trace_init)(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmatb, struct sof_ipc_dma_trace_params_ext *dtrace_params); /* optional */ int (*trace_release)(struct snd_sof_dev *sdev); /* optional */ int (*trace_trigger)(struct snd_sof_dev *sdev, @@ -349,18 +345,36 @@ struct snd_sof_mailbox { /* IPC message descriptor for host <-> DSP IO */ struct snd_sof_ipc_msg { /* message data */ - u32 header; void *msg_data; void *reply_data; size_t msg_size; size_t reply_size; int reply_error; + /* notification, firmware initiated messages */ + void *rx_data; + wait_queue_head_t waitq; bool ipc_complete; }; /** + * struct sof_ipc_fw_tracing_ops - IPC-specific firmware tracing ops + * @init: Function pointer for initialization of the tracing + * @free: Optional function pointer for freeing of the tracing + * @fw_crashed: Optional function pointer to notify the tracing of a firmware crash + * @suspend: Function pointer for system/runtime suspend + * @resume: Function pointer for system/runtime resume + */ +struct sof_ipc_fw_tracing_ops { + int (*init)(struct snd_sof_dev *sdev); + void (*free)(struct snd_sof_dev *sdev); + void (*fw_crashed)(struct snd_sof_dev *sdev); + void (*suspend)(struct snd_sof_dev *sdev, pm_message_t pm_state); + int (*resume)(struct snd_sof_dev *sdev); +}; + +/** * struct sof_ipc_pm_ops - IPC-specific PM ops * @ctx_save: Function pointer for context save * @ctx_restore: Function pointer for context restore @@ -370,6 +384,25 @@ struct sof_ipc_pm_ops { int (*ctx_restore)(struct snd_sof_dev *sdev); }; +/** + * struct sof_ipc_fw_loader_ops - IPC/FW-specific loader ops + * @validate: Function pointer for validating the firmware image + * @parse_ext_manifest: Function pointer for parsing the manifest of the firmware + * @load_fw_to_dsp: Optional function pointer for loading the firmware to the + * DSP. + * The function implements generic, hardware independent way + * of loading the initial firmware and its modules (if any). + * @query_fw_configuration: Optional function pointer to query information and + * configuration from the booted firmware. + * Executed after the first successful firmware boot. + */ +struct sof_ipc_fw_loader_ops { + int (*validate)(struct snd_sof_dev *sdev); + size_t (*parse_ext_manifest)(struct snd_sof_dev *sdev); + int (*load_fw_to_dsp)(struct snd_sof_dev *sdev); + int (*query_fw_configuration)(struct snd_sof_dev *sdev); +}; + struct sof_ipc_tplg_ops; struct sof_ipc_pcm_ops; @@ -378,11 +411,36 @@ struct sof_ipc_pcm_ops; * @tplg: Pointer to IPC-specific topology ops * @pm: Pointer to PM ops * @pcm: Pointer to PCM ops + * @fw_loader: Pointer to Firmware Loader ops + * @fw_tracing: Pointer to Firmware tracing ops + * + * @tx_msg: Function pointer for sending a 'short' IPC message + * @set_get_data: Function pointer for set/get data ('large' IPC message). This + * function may split up the 'large' message and use the @tx_msg + * path to transfer individual chunks, or use other means to transfer + * the message. + * @get_reply: Function pointer for fetching the reply to + * sdev->ipc->msg.reply_data + * @rx_msg: Function pointer for handling a received message + * + * Note: both @tx_msg and @set_get_data considered as TX functions and they are + * serialized for the duration of the instructed transfer. A large message sent + * via @set_get_data is a single transfer even if at the hardware level it is + * handled with multiple chunks. */ struct sof_ipc_ops { const struct sof_ipc_tplg_ops *tplg; const struct sof_ipc_pm_ops *pm; const struct sof_ipc_pcm_ops *pcm; + const struct sof_ipc_fw_loader_ops *fw_loader; + const struct sof_ipc_fw_tracing_ops *fw_tracing; + + int (*tx_msg)(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes, + void *reply_data, size_t reply_bytes, bool no_pm); + int (*set_get_data)(struct snd_sof_dev *sdev, void *data, size_t data_bytes, + bool set); + int (*get_reply)(struct snd_sof_dev *sdev); + void (*rx_msg)(struct snd_sof_dev *sdev); }; /* SOF generic IPC data */ @@ -394,6 +452,9 @@ struct snd_sof_ipc { /* disables further sending of ipc's */ bool disable_ipc_tx; + /* Maximum allowed size of a single IPC message/reply */ + size_t max_payload_size; + struct snd_sof_ipc_msg msg; /* IPC ops based on version */ @@ -457,8 +518,6 @@ struct snd_sof_dev { bool ipc_dump_printed; /* firmware loader */ - struct snd_dma_buffer dmab; - struct snd_dma_buffer dmab_bdl; struct sof_ipc_fw_ready fw_ready; struct sof_ipc_fw_version fw_version; struct sof_ipc_cc_version *cc_version; @@ -473,6 +532,7 @@ struct snd_sof_dev { struct list_head route_list; struct snd_soc_component *component; u32 enabled_cores_mask; /* keep track of enabled cores */ + bool led_present; /* FW configuration */ struct sof_ipc_window *info_window; @@ -481,16 +541,9 @@ struct snd_sof_dev { int ipc_timeout; int boot_timeout; - /* DMA for Trace */ - struct snd_dma_buffer dmatb; - struct snd_dma_buffer dmatp; - int dma_trace_pages; - wait_queue_head_t trace_sleep; - u32 host_offset; - bool dtrace_is_supported; /* set with Kconfig or module parameter */ - bool dtrace_is_enabled; - bool dtrace_error; - bool dtrace_draining; + /* firmwre tracing */ + bool fw_trace_is_supported; /* set with Kconfig or module parameter */ + void *fw_trace_data; /* private data used by firmware tracing implementation */ bool msi_enabled; @@ -564,8 +617,6 @@ extern struct snd_compress_ops sof_compressed_ops; int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev); int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev); int snd_sof_run_firmware(struct snd_sof_dev *sdev); -int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev, - struct snd_sof_mod_hdr *module); void snd_sof_fw_unload(struct snd_sof_dev *sdev); /* @@ -575,15 +626,17 @@ struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev); void snd_sof_ipc_free(struct snd_sof_dev *sdev); void snd_sof_ipc_get_reply(struct snd_sof_dev *sdev); void snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id); -void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev); -int snd_sof_ipc_valid(struct snd_sof_dev *sdev); -int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header, - void *msg_data, size_t msg_bytes, void *reply_data, - size_t reply_bytes); -int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, u32 header, - void *msg_data, size_t msg_bytes, +static inline void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) +{ + sdev->ipc->ops->rx_msg(sdev); +} +int sof_ipc_tx_message(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes, + void *reply_data, size_t reply_bytes); +int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes, void *reply_data, size_t reply_bytes); -int sof_ipc_init_msg_memory(struct snd_sof_dev *sdev); +int sof_ipc_send_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes, + size_t reply_bytes); + static inline void snd_sof_ipc_process_reply(struct snd_sof_dev *sdev, u32 msg_id) { snd_sof_ipc_get_reply(sdev); @@ -593,27 +646,26 @@ static inline void snd_sof_ipc_process_reply(struct snd_sof_dev *sdev, u32 msg_i /* * Trace/debug */ -int snd_sof_init_trace(struct snd_sof_dev *sdev); -void snd_sof_release_trace(struct snd_sof_dev *sdev); -void snd_sof_free_trace(struct snd_sof_dev *sdev); int snd_sof_dbg_init(struct snd_sof_dev *sdev); void snd_sof_free_debug(struct snd_sof_dev *sdev); int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev, void *base, size_t size, const char *name, mode_t mode); -int snd_sof_trace_update_pos(struct snd_sof_dev *sdev, - struct sof_ipc_dma_trace_posn *posn); -void snd_sof_trace_notify_for_error(struct snd_sof_dev *sdev); 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); -int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev); void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev); 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, const char *name, enum sof_debugfs_access_type access_type); +/* Firmware tracing */ +int sof_fw_trace_init(struct snd_sof_dev *sdev); +void sof_fw_trace_free(struct snd_sof_dev *sdev); +void sof_fw_trace_fw_crashed(struct snd_sof_dev *sdev); +void sof_fw_trace_suspend(struct snd_sof_dev *sdev, pm_message_t pm_state); +int sof_fw_trace_resume(struct snd_sof_dev *sdev); /* * DSP Architectures. @@ -654,8 +706,6 @@ int sof_block_write(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, int sof_block_read(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, u32 offset, void *dest, size_t size); -int sof_fw_ready(struct snd_sof_dev *sdev, u32 msg_id); - int sof_ipc_msg_data(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, void *p, size_t sz); @@ -721,4 +771,8 @@ static inline int sof_resume_clients(struct snd_sof_dev *sdev) } #endif /* CONFIG_SND_SOC_SOF_CLIENT */ +/* Main ops for IPC implementations */ +extern const struct sof_ipc_ops ipc3_ops; +extern const struct sof_ipc_ops ipc4_ops; + #endif diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 3e5b319b44c7..b1fcab7ce48e 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -32,7 +32,6 @@ #define VOL_HALF_DB_STEP 50 /* TLV data items */ -#define TLV_ITEMS 3 #define TLV_MIN 0 #define TLV_STEP 1 #define TLV_MUTE 2 @@ -134,7 +133,7 @@ int sof_update_ipc_object(struct snd_soc_component *scomp, void *object, enum so return 0; } -static inline int get_tlv_data(const int *p, int tlv[TLV_ITEMS]) +static inline int get_tlv_data(const int *p, int tlv[SOF_TLV_ITEMS]) { /* we only support dB scale TLV type at the moment */ if ((int)p[SNDRV_CTL_TLVO_TYPE] != SNDRV_CTL_TLVT_DB_SCALE) @@ -224,7 +223,7 @@ static u32 vol_pow32(u32 a, int exp, u32 fwl) * Function to calculate volume gain from TLV data. * This function can only handle gain steps that are multiples of 0.5 dB */ -static u32 vol_compute_gain(u32 value, int *tlv) +u32 vol_compute_gain(u32 value, int *tlv) { int dB_gain; u32 linear_gain; @@ -263,20 +262,17 @@ static u32 vol_compute_gain(u32 value, int *tlv) * "size" specifies the number of entries in the table */ static int set_up_volume_table(struct snd_sof_control *scontrol, - int tlv[TLV_ITEMS], int size) + int tlv[SOF_TLV_ITEMS], int size) { - int j; - - /* init the volume table */ - scontrol->volume_table = kcalloc(size, sizeof(u32), GFP_KERNEL); - if (!scontrol->volume_table) - return -ENOMEM; + struct snd_soc_component *scomp = scontrol->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; - /* populate the volume table */ - for (j = 0; j < size ; j++) - scontrol->volume_table[j] = vol_compute_gain(j, tlv); + if (tplg_ops->control->set_up_volume_table) + return tplg_ops->control->set_up_volume_table(scontrol, tlv, size); - return 0; + dev_err(scomp->dev, "Mandatory op %s not set\n", __func__); + return -EINVAL; } struct sof_dai_types { @@ -772,7 +768,8 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_soc_tplg_mixer_control *mc = container_of(hdr, struct snd_soc_tplg_mixer_control, hdr); - int tlv[TLV_ITEMS]; + int tlv[SOF_TLV_ITEMS]; + unsigned int mask; int ret; /* validate topology data */ @@ -821,6 +818,16 @@ skip: goto err; } + if (scontrol->led_ctl.use_led) { + mask = scontrol->led_ctl.direction ? SNDRV_CTL_ELEM_ACCESS_MIC_LED : + SNDRV_CTL_ELEM_ACCESS_SPK_LED; + scontrol->access &= ~SNDRV_CTL_ELEM_ACCESS_LED_MASK; + scontrol->access |= mask; + kc->access &= ~SNDRV_CTL_ELEM_ACCESS_LED_MASK; + kc->access |= mask; + sdev->led_present = true; + } + dev_dbg(scomp->dev, "tplg: load kcontrol index %d chans %d\n", scontrol->comp_id, scontrol->num_channels); @@ -1001,15 +1008,18 @@ static int sof_connect_dai_widget(struct snd_soc_component *scomp, struct snd_soc_dai *cpu_dai; int i; + if (!w->sname) { + dev_err(scomp->dev, "Widget %s does not have stream\n", w->name); + return -EINVAL; + } + list_for_each_entry(rtd, &card->rtd_list, list) { dev_vdbg(scomp->dev, "tplg: check widget: %s stream: %s dai stream: %s\n", w->name, w->sname, rtd->dai_link->stream_name); - if (!w->sname || !rtd->dai_link->stream_name) - continue; - /* does stream match DAI link ? */ - if (strcmp(w->sname, rtd->dai_link->stream_name)) + if (!rtd->dai_link->stream_name || + strcmp(w->sname, rtd->dai_link->stream_name)) continue; switch (w->id) { @@ -1140,7 +1150,6 @@ static int sof_widget_parse_tokens(struct snd_soc_component *scomp, struct snd_s const struct sof_token_info *token_list = ipc_tplg_ops->token_list; struct snd_soc_tplg_private *private = &tw->priv; int num_tuples = 0; - size_t size; int ret, i; if (count > 0 && !object_token_list) { @@ -1153,8 +1162,7 @@ static int sof_widget_parse_tokens(struct snd_soc_component *scomp, struct snd_s num_tuples += token_list[object_token_list[i]].count; /* allocate memory for tuples array */ - size = sizeof(struct snd_sof_tuple) * num_tuples; - swidget->tuples = kzalloc(size, GFP_KERNEL); + swidget->tuples = kcalloc(num_tuples, sizeof(*swidget->tuples), GFP_KERNEL); if (!swidget->tuples) return -ENOMEM; @@ -1595,7 +1603,6 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ const struct sof_token_info *token_list = ipc_tplg_ops->token_list; struct snd_soc_tplg_private *private = &cfg->priv; struct snd_sof_dai_link *slink; - size_t size; u32 token_id = 0; int num_tuples = 0; int ret, num_sets; @@ -1707,22 +1714,23 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ } /* allocate memory for tuples array */ - size = sizeof(struct snd_sof_tuple) * num_tuples; - slink->tuples = kzalloc(size, GFP_KERNEL); + slink->tuples = kcalloc(num_tuples, sizeof(*slink->tuples), GFP_KERNEL); if (!slink->tuples) { kfree(slink->hw_configs); kfree(slink); return -ENOMEM; } - /* parse one set of DAI link tokens */ - ret = sof_copy_tuples(sdev, private->array, le32_to_cpu(private->size), - SOF_DAI_LINK_TOKENS, 1, slink->tuples, - num_tuples, &slink->num_tuples); - if (ret < 0) { - dev_err(scomp->dev, "failed to parse %s for dai link %s\n", - token_list[SOF_DAI_LINK_TOKENS].name, link->name); - goto err; + if (token_list[SOF_DAI_LINK_TOKENS].tokens) { + /* parse one set of DAI link tokens */ + ret = sof_copy_tuples(sdev, private->array, le32_to_cpu(private->size), + SOF_DAI_LINK_TOKENS, 1, slink->tuples, + num_tuples, &slink->num_tuples); + if (ret < 0) { + dev_err(scomp->dev, "failed to parse %s for dai link %s\n", + token_list[SOF_DAI_LINK_TOKENS].name, link->name); + goto err; + } } /* nothing more to do if there are no DAI type-specific tokens defined */ @@ -2075,6 +2083,7 @@ static struct snd_soc_tplg_ops sof_tplg_ops = { int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) { + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); const struct firmware *fw; int ret; @@ -2097,6 +2106,10 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) } release_firmware(fw); + + if (ret >= 0 && sdev->led_present) + ret = snd_ctl_led_request(); + return ret; } EXPORT_SYMBOL(snd_sof_load_topology); diff --git a/sound/soc/sof/trace.c b/sound/soc/sof/trace.c index ea8e4506d02e..6f662642d611 100644 --- a/sound/soc/sof/trace.c +++ b/sound/soc/sof/trace.c @@ -1,601 +1,51 @@ -// 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 Intel Corporation. All rights reserved. -// -// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// SPDX-License-Identifier: GPL-2.0-only // +// Copyright(c) 2022 Intel Corporation. All rights reserved. -#include <linux/debugfs.h> -#include <linux/sched/signal.h> #include "sof-priv.h" -#include "ops.h" -#include "sof-utils.h" - -#define TRACE_FILTER_ELEMENTS_PER_ENTRY 4 -#define TRACE_FILTER_MAX_CONFIG_STRING_LENGTH 1024 - -static int trace_filter_append_elem(struct snd_sof_dev *sdev, uint32_t key, uint32_t value, - struct sof_ipc_trace_filter_elem *elem_list, - int capacity, int *counter) -{ - if (*counter >= capacity) - return -ENOMEM; - - elem_list[*counter].key = key; - elem_list[*counter].value = value; - ++*counter; - - return 0; -} - -static int trace_filter_parse_entry(struct snd_sof_dev *sdev, const char *line, - struct sof_ipc_trace_filter_elem *elem, - int capacity, int *counter) -{ - int len = strlen(line); - int cnt = *counter; - uint32_t uuid_id; - int log_level; - int pipe_id; - int comp_id; - int read; - int ret; - - /* ignore empty content */ - ret = sscanf(line, " %n", &read); - if (!ret && read == len) - return len; - - ret = sscanf(line, " %d %x %d %d %n", &log_level, &uuid_id, &pipe_id, &comp_id, &read); - if (ret != TRACE_FILTER_ELEMENTS_PER_ENTRY || read != len) { - dev_err(sdev->dev, "error: invalid trace filter entry '%s'\n", line); - return -EINVAL; - } - - if (uuid_id > 0) { - ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_BY_UUID, - uuid_id, elem, capacity, &cnt); - if (ret) - return ret; - } - if (pipe_id >= 0) { - ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_BY_PIPE, - pipe_id, elem, capacity, &cnt); - if (ret) - return ret; - } - if (comp_id >= 0) { - ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_BY_COMP, - comp_id, elem, capacity, &cnt); - if (ret) - return ret; - } - - ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_SET_LEVEL | - SOF_IPC_TRACE_FILTER_ELEM_FIN, - log_level, elem, capacity, &cnt); - if (ret) - return ret; - - /* update counter only when parsing whole entry passed */ - *counter = cnt; - - return len; -} - -static int trace_filter_parse(struct snd_sof_dev *sdev, char *string, - int *out_elem_cnt, - struct sof_ipc_trace_filter_elem **out) -{ - static const char entry_delimiter[] = ";"; - char *entry = string; - int capacity = 0; - int entry_len; - int cnt = 0; - - /* - * Each entry contains at least 1, up to TRACE_FILTER_ELEMENTS_PER_ENTRY - * IPC elements, depending on content. Calculate IPC elements capacity - * for the input string where each element is set. - */ - while (entry) { - capacity += TRACE_FILTER_ELEMENTS_PER_ENTRY; - entry = strchr(entry + 1, entry_delimiter[0]); - } - *out = kmalloc(capacity * sizeof(**out), GFP_KERNEL); - if (!*out) - return -ENOMEM; - - /* split input string by ';', and parse each entry separately in trace_filter_parse_entry */ - while ((entry = strsep(&string, entry_delimiter))) { - entry_len = trace_filter_parse_entry(sdev, entry, *out, capacity, &cnt); - if (entry_len < 0) { - dev_err(sdev->dev, "error: %s failed for '%s', %d\n", __func__, entry, - entry_len); - return -EINVAL; - } - } - - *out_elem_cnt = cnt; - - return 0; -} - -static int sof_ipc_trace_update_filter(struct snd_sof_dev *sdev, int num_elems, - struct sof_ipc_trace_filter_elem *elems) -{ - struct sof_ipc_trace_filter *msg; - struct sof_ipc_reply reply; - size_t size; - int ret; - - size = struct_size(msg, elems, num_elems); - if (size > SOF_IPC_MSG_MAX_SIZE) - return -EINVAL; - - msg = kmalloc(size, GFP_KERNEL); - if (!msg) - return -ENOMEM; - - msg->hdr.size = size; - msg->hdr.cmd = SOF_IPC_GLB_TRACE_MSG | SOF_IPC_TRACE_FILTER_UPDATE; - msg->elem_cnt = num_elems; - memcpy(&msg->elems[0], elems, num_elems * sizeof(*elems)); - - ret = pm_runtime_get_sync(sdev->dev); - if (ret < 0 && ret != -EACCES) { - pm_runtime_put_noidle(sdev->dev); - dev_err(sdev->dev, "error: enabling device failed: %d\n", ret); - goto error; - } - ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size, - &reply, sizeof(reply)); - pm_runtime_mark_last_busy(sdev->dev); - pm_runtime_put_autosuspend(sdev->dev); - -error: - kfree(msg); - return ret ? ret : reply.error; -} - -static ssize_t sof_dfsentry_trace_filter_write(struct file *file, const char __user *from, - size_t count, loff_t *ppos) -{ - struct snd_sof_dfsentry *dfse = file->private_data; - struct sof_ipc_trace_filter_elem *elems = NULL; - struct snd_sof_dev *sdev = dfse->sdev; - loff_t pos = 0; - int num_elems; - char *string; - int ret; - - if (count > TRACE_FILTER_MAX_CONFIG_STRING_LENGTH) { - dev_err(sdev->dev, "%s too long input, %zu > %d\n", __func__, count, - TRACE_FILTER_MAX_CONFIG_STRING_LENGTH); - return -EINVAL; - } - - string = kmalloc(count + 1, GFP_KERNEL); - if (!string) - return -ENOMEM; - - /* assert null termination */ - string[count] = 0; - ret = simple_write_to_buffer(string, count, &pos, from, count); - if (ret < 0) - goto error; - - ret = trace_filter_parse(sdev, string, &num_elems, &elems); - if (ret < 0) { - dev_err(sdev->dev, "error: fail in trace_filter_parse, %d\n", ret); - goto error; - } - - if (num_elems) { - ret = sof_ipc_trace_update_filter(sdev, num_elems, elems); - if (ret < 0) { - dev_err(sdev->dev, "error: fail in sof_ipc_trace_update_filter %d\n", ret); - goto error; - } - } - ret = count; -error: - kfree(string); - kfree(elems); - return ret; -} - -static const struct file_operations sof_dfs_trace_filter_fops = { - .open = simple_open, - .write = sof_dfsentry_trace_filter_write, - .llseek = default_llseek, -}; - -static int trace_debugfs_filter_create(struct snd_sof_dev *sdev) -{ - struct snd_sof_dfsentry *dfse; - - dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); - if (!dfse) - return -ENOMEM; - - dfse->sdev = sdev; - dfse->type = SOF_DFSENTRY_TYPE_BUF; - - debugfs_create_file("filter", 0200, sdev->debugfs_root, dfse, - &sof_dfs_trace_filter_fops); - /* add to dfsentry list */ - list_add(&dfse->list, &sdev->dfsentry_list); - - return 0; -} - -static size_t sof_trace_avail(struct snd_sof_dev *sdev, - loff_t pos, size_t buffer_size) -{ - loff_t host_offset = READ_ONCE(sdev->host_offset); - - /* - * If host offset is less than local pos, it means write pointer of - * host DMA buffer has been wrapped. We should output the trace data - * at the end of host DMA buffer at first. - */ - if (host_offset < pos) - return buffer_size - pos; - - /* If there is available trace data now, it is unnecessary to wait. */ - if (host_offset > pos) - return host_offset - pos; - - return 0; -} - -static size_t sof_wait_trace_avail(struct snd_sof_dev *sdev, - loff_t pos, size_t buffer_size) -{ - wait_queue_entry_t wait; - size_t ret = sof_trace_avail(sdev, pos, buffer_size); - - /* data immediately available */ - if (ret) - return ret; - - if (!sdev->dtrace_is_enabled && sdev->dtrace_draining) { - /* - * tracing has ended and all traces have been - * read by client, return EOF - */ - sdev->dtrace_draining = false; - return 0; - } - - /* wait for available trace data from FW */ - init_waitqueue_entry(&wait, current); - set_current_state(TASK_INTERRUPTIBLE); - add_wait_queue(&sdev->trace_sleep, &wait); - - if (!signal_pending(current)) { - /* set timeout to max value, no error code */ - schedule_timeout(MAX_SCHEDULE_TIMEOUT); - } - remove_wait_queue(&sdev->trace_sleep, &wait); - return sof_trace_avail(sdev, pos, buffer_size); -} - -static ssize_t sof_dfsentry_trace_read(struct file *file, char __user *buffer, - size_t count, loff_t *ppos) -{ - struct snd_sof_dfsentry *dfse = file->private_data; - struct snd_sof_dev *sdev = dfse->sdev; - unsigned long rem; - loff_t lpos = *ppos; - size_t avail, buffer_size = dfse->size; - u64 lpos_64; - - /* make sure we know about any failures on the DSP side */ - sdev->dtrace_error = false; - - /* check pos and count */ - if (lpos < 0) - return -EINVAL; - if (!count) - return 0; - - /* check for buffer wrap and count overflow */ - lpos_64 = lpos; - lpos = do_div(lpos_64, buffer_size); - - /* get available count based on current host offset */ - avail = sof_wait_trace_avail(sdev, lpos, buffer_size); - if (sdev->dtrace_error) { - dev_err(sdev->dev, "error: trace IO error\n"); - return -EIO; - } - - /* make sure count is <= avail */ - if (count > avail) - count = avail; - - /* - * make sure that all trace data is available for the CPU as the trace - * data buffer might be allocated from non consistent memory. - * Note: snd_dma_buffer_sync() is called for normal audio playback and - * capture streams also. - */ - snd_dma_buffer_sync(&sdev->dmatb, SNDRV_DMA_SYNC_CPU); - /* copy available trace data to debugfs */ - rem = copy_to_user(buffer, ((u8 *)(dfse->buf) + lpos), count); - if (rem) - return -EFAULT; - - *ppos += count; - - /* move debugfs reading position */ - return count; -} - -static int sof_dfsentry_trace_release(struct inode *inode, struct file *file) -{ - struct snd_sof_dfsentry *dfse = inode->i_private; - struct snd_sof_dev *sdev = dfse->sdev; - - /* avoid duplicate traces at next open */ - if (!sdev->dtrace_is_enabled) - sdev->host_offset = 0; - - return 0; -} - -static const struct file_operations sof_dfs_trace_fops = { - .open = simple_open, - .read = sof_dfsentry_trace_read, - .llseek = default_llseek, - .release = sof_dfsentry_trace_release, -}; - -static int trace_debugfs_create(struct snd_sof_dev *sdev) -{ - struct snd_sof_dfsentry *dfse; - int ret; - - if (!sdev) - return -EINVAL; - - ret = trace_debugfs_filter_create(sdev); - if (ret < 0) - dev_err(sdev->dev, "error: fail in %s, %d", __func__, ret); - - dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); - if (!dfse) - return -ENOMEM; - - dfse->type = SOF_DFSENTRY_TYPE_BUF; - dfse->buf = sdev->dmatb.area; - dfse->size = sdev->dmatb.bytes; - dfse->sdev = sdev; - - debugfs_create_file("trace", 0444, sdev->debugfs_root, dfse, - &sof_dfs_trace_fops); - - return 0; -} - -int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev) -{ - struct sof_ipc_fw_ready *ready = &sdev->fw_ready; - struct sof_ipc_fw_version *v = &ready->version; - struct sof_ipc_dma_trace_params_ext params; - struct sof_ipc_reply ipc_reply; - int ret; - - if (!sdev->dtrace_is_supported) - return 0; - - if (sdev->dtrace_is_enabled || !sdev->dma_trace_pages) - return -EINVAL; - - /* set IPC parameters */ - params.hdr.cmd = SOF_IPC_GLB_TRACE_MSG; - /* PARAMS_EXT is only supported from ABI 3.7.0 onwards */ - if (v->abi_version >= SOF_ABI_VER(3, 7, 0)) { - params.hdr.size = sizeof(struct sof_ipc_dma_trace_params_ext); - params.hdr.cmd |= SOF_IPC_TRACE_DMA_PARAMS_EXT; - params.timestamp_ns = ktime_get(); /* in nanosecond */ - } else { - params.hdr.size = sizeof(struct sof_ipc_dma_trace_params); - params.hdr.cmd |= SOF_IPC_TRACE_DMA_PARAMS; - } - params.buffer.phy_addr = sdev->dmatp.addr; - params.buffer.size = sdev->dmatb.bytes; - params.buffer.pages = sdev->dma_trace_pages; - params.stream_tag = 0; - - sdev->host_offset = 0; - sdev->dtrace_draining = false; - - ret = snd_sof_dma_trace_init(sdev, ¶ms); - if (ret < 0) { - dev_err(sdev->dev, - "error: fail in snd_sof_dma_trace_init %d\n", ret); - return ret; - } - dev_dbg(sdev->dev, "%s: stream_tag: %d\n", __func__, params.stream_tag); - - /* send IPC to the DSP */ - ret = sof_ipc_tx_message(sdev->ipc, - params.hdr.cmd, ¶ms, sizeof(params), - &ipc_reply, sizeof(ipc_reply)); - if (ret < 0) { - dev_err(sdev->dev, - "error: can't set params for DMA for trace %d\n", ret); - goto trace_release; - } - - ret = snd_sof_dma_trace_trigger(sdev, SNDRV_PCM_TRIGGER_START); - if (ret < 0) { - dev_err(sdev->dev, - "error: snd_sof_dma_trace_trigger: start: %d\n", ret); - goto trace_release; - } - - sdev->dtrace_is_enabled = true; - - return 0; - -trace_release: - snd_sof_dma_trace_release(sdev); - return ret; -} - -int snd_sof_init_trace(struct snd_sof_dev *sdev) +int sof_fw_trace_init(struct snd_sof_dev *sdev) { - int ret; + if (!sdev->ipc->ops->fw_tracing) { + dev_info(sdev->dev, "Firmware tracing is not available\n"); + sdev->fw_trace_is_supported = false; - if (!sdev->dtrace_is_supported) return 0; - - /* set false before start initialization */ - sdev->dtrace_is_enabled = false; - - /* allocate trace page table buffer */ - ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev, - PAGE_SIZE, &sdev->dmatp); - if (ret < 0) { - dev_err(sdev->dev, - "error: can't alloc page table for trace %d\n", ret); - return ret; - } - - /* allocate trace data buffer */ - ret = snd_dma_alloc_dir_pages(SNDRV_DMA_TYPE_DEV_SG, sdev->dev, - DMA_FROM_DEVICE, DMA_BUF_SIZE_FOR_TRACE, - &sdev->dmatb); - if (ret < 0) { - dev_err(sdev->dev, - "error: can't alloc buffer for trace %d\n", ret); - goto page_err; - } - - /* create compressed page table for audio firmware */ - ret = snd_sof_create_page_table(sdev->dev, &sdev->dmatb, - sdev->dmatp.area, sdev->dmatb.bytes); - if (ret < 0) - goto table_err; - - sdev->dma_trace_pages = ret; - dev_dbg(sdev->dev, "%s: dma_trace_pages: %d\n", - __func__, sdev->dma_trace_pages); - - if (sdev->first_boot) { - ret = trace_debugfs_create(sdev); - if (ret < 0) - goto table_err; } - init_waitqueue_head(&sdev->trace_sleep); - - ret = snd_sof_init_trace_ipc(sdev); - if (ret < 0) - goto table_err; - - return 0; -table_err: - sdev->dma_trace_pages = 0; - snd_dma_free_pages(&sdev->dmatb); -page_err: - snd_dma_free_pages(&sdev->dmatp); - return ret; + return sdev->ipc->ops->fw_tracing->init(sdev); } -EXPORT_SYMBOL(snd_sof_init_trace); -int snd_sof_trace_update_pos(struct snd_sof_dev *sdev, - struct sof_ipc_dma_trace_posn *posn) +void sof_fw_trace_free(struct snd_sof_dev *sdev) { - if (!sdev->dtrace_is_supported) - return 0; - - if (sdev->dtrace_is_enabled && sdev->host_offset != posn->host_offset) { - sdev->host_offset = posn->host_offset; - wake_up(&sdev->trace_sleep); - } - - if (posn->overflow != 0) - dev_err(sdev->dev, - "error: DSP trace buffer overflow %u bytes. Total messages %d\n", - posn->overflow, posn->messages); + if (!sdev->fw_trace_is_supported || !sdev->ipc->ops->fw_tracing) + return; - return 0; + if (sdev->ipc->ops->fw_tracing->free) + sdev->ipc->ops->fw_tracing->free(sdev); } -/* an error has occurred within the DSP that prevents further trace */ -void snd_sof_trace_notify_for_error(struct snd_sof_dev *sdev) +void sof_fw_trace_fw_crashed(struct snd_sof_dev *sdev) { - if (!sdev->dtrace_is_supported) + if (!sdev->fw_trace_is_supported) return; - if (sdev->dtrace_is_enabled) { - sdev->dtrace_error = true; - wake_up(&sdev->trace_sleep); - } + if (sdev->ipc->ops->fw_tracing->fw_crashed) + sdev->ipc->ops->fw_tracing->fw_crashed(sdev); } -EXPORT_SYMBOL(snd_sof_trace_notify_for_error); -void snd_sof_release_trace(struct snd_sof_dev *sdev) +void sof_fw_trace_suspend(struct snd_sof_dev *sdev, pm_message_t pm_state) { - struct sof_ipc_fw_ready *ready = &sdev->fw_ready; - struct sof_ipc_fw_version *v = &ready->version; - struct sof_ipc_cmd_hdr hdr; - struct sof_ipc_reply ipc_reply; - int ret; - - if (!sdev->dtrace_is_supported || !sdev->dtrace_is_enabled) + if (!sdev->fw_trace_is_supported) return; - ret = snd_sof_dma_trace_trigger(sdev, SNDRV_PCM_TRIGGER_STOP); - if (ret < 0) - dev_err(sdev->dev, - "error: snd_sof_dma_trace_trigger: stop: %d\n", ret); - - /* - * stop and free trace DMA in the DSP. TRACE_DMA_FREE is only supported from - * ABI 3.20.0 onwards - */ - if (v->abi_version >= SOF_ABI_VER(3, 20, 0)) { - hdr.size = sizeof(hdr); - hdr.cmd = SOF_IPC_GLB_TRACE_MSG | SOF_IPC_TRACE_DMA_FREE; - - ret = sof_ipc_tx_message(sdev->ipc, hdr.cmd, &hdr, hdr.size, - &ipc_reply, sizeof(ipc_reply)); - if (ret < 0) - dev_err(sdev->dev, "DMA_TRACE_FREE failed with error: %d\n", ret); - } - - ret = snd_sof_dma_trace_release(sdev); - if (ret < 0) - dev_err(sdev->dev, - "error: fail in snd_sof_dma_trace_release %d\n", ret); - - sdev->dtrace_is_enabled = false; - sdev->dtrace_draining = true; - wake_up(&sdev->trace_sleep); + sdev->ipc->ops->fw_tracing->suspend(sdev, pm_state); } -EXPORT_SYMBOL(snd_sof_release_trace); -void snd_sof_free_trace(struct snd_sof_dev *sdev) +int sof_fw_trace_resume(struct snd_sof_dev *sdev) { - if (!sdev->dtrace_is_supported) - return; - - snd_sof_release_trace(sdev); + if (!sdev->fw_trace_is_supported) + return 0; - if (sdev->dma_trace_pages) { - snd_dma_free_pages(&sdev->dmatb); - snd_dma_free_pages(&sdev->dmatp); - sdev->dma_trace_pages = 0; - } + return sdev->ipc->ops->fw_tracing->resume(sdev); } -EXPORT_SYMBOL(snd_sof_free_trace); |