diff --git a/include/sound/sof.h b/include/sound/sof.h index eddea82c7b5a..38d6c8cb5e83 100644 --- a/include/sound/sof.h +++ b/include/sound/sof.h @@ -159,6 +159,9 @@ struct sof_dev_desc { /* The platform supports DSPless mode */ bool dspless_mode_supported; + /* On demand DSP booting is possible on the platform */ + bool on_demand_dsp_boot; + /* defaults paths for firmware, library and topology files */ const char *default_fw_path[SOF_IPC_TYPE_COUNT]; const char *default_lib_path[SOF_IPC_TYPE_COUNT]; diff --git a/sound/soc/sof/compress.c b/sound/soc/sof/compress.c index 90b932ae3bab..86d563c864e5 100644 --- a/sound/soc/sof/compress.c +++ b/sound/soc/sof/compress.c @@ -195,6 +195,14 @@ static int sof_compr_set_params(struct snd_soc_component *component, if (sizeof(*pcm) + ext_data_size > sdev->ipc->max_payload_size) return -EINVAL; + /* + * Make sure that the DSP is booted up, which might not be the + * case if the on-demand DSP boot is used + */ + ret = snd_sof_boot_dsp_firmware(sdev); + if (ret) + return ret; + pcm = kzalloc(sizeof(*pcm) + ext_data_size, GFP_KERNEL); if (!pcm) return -ENOMEM; diff --git a/sound/soc/sof/control.c b/sound/soc/sof/control.c index 9582ab5f1113..74d997a4f620 100644 --- a/sound/soc/sof/control.c +++ b/sound/soc/sof/control.c @@ -198,7 +198,12 @@ int snd_sof_bytes_ext_volatile_get(struct snd_kcontrol *kcontrol, unsigned int _ return ret; } - ret = tplg_ops->control->bytes_ext_volatile_get(scontrol, binary_data, size); + /* Make sure the DSP/firmware is booted up */ + ret = snd_sof_boot_dsp_firmware(sdev); + if (!ret) + ret = tplg_ops->control->bytes_ext_volatile_get(scontrol, + binary_data, + size); err = pm_runtime_put_autosuspend(scomp->dev); if (err < 0) diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index b11f408f1366..2d394389c945 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -680,6 +680,7 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) mutex_init(&sdev->power_state_access); mutex_init(&sdev->ipc_client_mutex); mutex_init(&sdev->client_event_handler_mutex); + mutex_init(&sdev->dsp_fw_boot_mutex); /* set default timeouts if none provided */ if (plat_data->desc->ipc_timeout == 0) diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c index b24943a65c89..6b9e1f1ee657 100644 --- a/sound/soc/sof/debug.c +++ b/sound/soc/sof/debug.c @@ -216,7 +216,12 @@ static int memory_info_update(struct snd_sof_dev *sdev, char *buf, size_t buff_s goto error; } - ret = sof_ipc_tx_message(sdev->ipc, &msg, msg.size, reply, SOF_IPC_MSG_MAX_SIZE); + /* Make sure the DSP/firmware is booted up */ + ret = snd_sof_boot_dsp_firmware(sdev); + if (!ret) + ret = sof_ipc_tx_message(sdev->ipc, &msg, msg.size, reply, + SOF_IPC_MSG_MAX_SIZE); + pm_runtime_put_autosuspend(sdev->dev); if (ret < 0 || reply->rhdr.error < 0) { ret = min(ret, reply->rhdr.error); diff --git a/sound/soc/sof/ipc3-dtrace.c b/sound/soc/sof/ipc3-dtrace.c index 6ec391fd39a9..50700f5cb0ef 100644 --- a/sound/soc/sof/ipc3-dtrace.c +++ b/sound/soc/sof/ipc3-dtrace.c @@ -171,7 +171,12 @@ static int ipc3_trace_update_filter(struct snd_sof_dev *sdev, int num_elems, dev_err(sdev->dev, "enabling device failed: %d\n", ret); goto error; } - ret = sof_ipc_tx_message_no_reply(sdev->ipc, msg, msg->hdr.size); + + /* Make sure the DSP/firmware is booted up */ + ret = snd_sof_boot_dsp_firmware(sdev); + if (!ret) + ret = sof_ipc_tx_message_no_reply(sdev->ipc, msg, msg->hdr.size); + pm_runtime_put_autosuspend(sdev->dev); error: diff --git a/sound/soc/sof/ipc4.c b/sound/soc/sof/ipc4.c index a4a090e6724a..1df97129cee6 100644 --- a/sound/soc/sof/ipc4.c +++ b/sound/soc/sof/ipc4.c @@ -892,6 +892,19 @@ void sof_ipc4_mic_privacy_state_change(struct snd_sof_dev *sdev, bool state) struct sof_ipc4_msg msg; u32 data = state; + /* + * The mic privacy change notification's role is to notify the running + * firmware that there is a change in mic privacy state from whatever + * the state was before - since the firmware booted up or since the + * previous change during runtime. + * + * If the firmware has not been booted up, there is no need to send + * change notification (the firmware is not booted up). + * The firmware checks the current state during its boot. + */ + if (sdev->fw_state != SOF_FW_BOOT_COMPLETE) + return; + 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); diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index cee04574264e..31879a11c33e 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -122,6 +122,16 @@ static int sof_pcm_hw_params(struct snd_soc_component *component, spcm_dbg(spcm, substream->stream, "Entry: hw_params\n"); + if (!sdev->dspless_mode_selected) { + /* + * Make sure that the DSP is booted up, which might not be the + * case if the on-demand DSP boot is used + */ + ret = snd_sof_boot_dsp_firmware(sdev); + if (ret) + return ret; + } + /* * Handle repeated calls to hw_params() without free_pcm() in * between. At least ALSA OSS emulation depends on this. diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c index 8e3bcf602beb..dd7cd87f1fa5 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -8,10 +8,15 @@ // Author: Liam Girdwood // +#include #include "ops.h" #include "sof-priv.h" #include "sof-audio.h" +static int override_on_demand_boot = -1; +module_param_named(on_demand_boot, override_on_demand_boot, int, 0444); +MODULE_PARM_DESC(on_demand_boot, "Force on-demand DSP boot: 0 - disabled, 1 - enabled"); + /* * Helper function to determine the target DSP state during * system suspend. This function only cares about the device @@ -70,12 +75,96 @@ static void sof_cache_debugfs(struct snd_sof_dev *sdev) } #endif +int snd_sof_boot_dsp_firmware(struct snd_sof_dev *sdev) +{ + const struct sof_ipc_pm_ops *pm_ops = sof_ipc_get_ops(sdev, pm); + const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); + int ret; + + guard(mutex)(&sdev->dsp_fw_boot_mutex); + + if (sdev->fw_state == SOF_FW_BOOT_COMPLETE) { + /* Firmware already booted, just return */ + return 0; + } + + dev_dbg(sdev->dev, "Booting DSP firmware\n"); + + sof_set_fw_state(sdev, SOF_FW_BOOT_PREPARE); + + /* load the firmware */ + ret = snd_sof_load_firmware(sdev); + if (ret < 0) { + dev_err(sdev->dev, "%s: failed to load DSP firmware: %d\n", + __func__, ret); + sof_set_fw_state(sdev, SOF_FW_BOOT_FAILED); + return ret; + } + + sof_set_fw_state(sdev, SOF_FW_BOOT_IN_PROGRESS); + + /* + * Boot the firmware. The FW boot status will be modified + * in snd_sof_run_firmware() depending on the outcome. + */ + ret = snd_sof_run_firmware(sdev); + if (ret < 0) { + dev_err(sdev->dev, "%s: failed to boot DSP firmware: %d\n", + __func__, ret); + sof_set_fw_state(sdev, SOF_FW_BOOT_FAILED); + return ret; + } + + /* resume DMA trace */ + ret = sof_fw_trace_resume(sdev); + if (ret < 0) { + /* non fatal */ + dev_warn(sdev->dev, "%s: failed to resume trace: %d\n", + __func__, ret); + } + + /* restore pipelines */ + if (tplg_ops && tplg_ops->set_up_all_pipelines) { + ret = tplg_ops->set_up_all_pipelines(sdev, false); + if (ret < 0) { + dev_err(sdev->dev, "%s: failed to restore pipeline: %d\n", + __func__, ret); + goto setup_fail; + } + } + + /* Notify clients not managed by pm framework about core resume */ + sof_resume_clients(sdev); + + /* notify DSP of system resume */ + if (pm_ops && pm_ops->ctx_restore) { + ret = pm_ops->ctx_restore(sdev); + if (ret < 0) + dev_err(sdev->dev, "%s: ctx_restore IPC failed: %d\n", + __func__, ret); + } + +setup_fail: +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) + if (ret < 0) { + /* + * Debugfs cannot be read in runtime suspend, so cache + * the contents upon failure. This allows to capture + * possible DSP coredump information. + */ + sof_cache_debugfs(sdev); + } +#endif + + return ret; +} +EXPORT_SYMBOL(snd_sof_boot_dsp_firmware); + static int sof_resume(struct device *dev, bool runtime_resume) { struct snd_sof_dev *sdev = dev_get_drvdata(dev); - const struct sof_ipc_pm_ops *pm_ops = sof_ipc_get_ops(sdev, pm); - const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); u32 old_state = sdev->dsp_power_state.state; + bool on_demand_boot; int ret; /* do nothing if dsp resume callbacks are not set */ @@ -123,74 +212,18 @@ static int sof_resume(struct device *dev, bool runtime_resume) return 0; } - sof_set_fw_state(sdev, SOF_FW_BOOT_PREPARE); + if (override_on_demand_boot > -1) + on_demand_boot = override_on_demand_boot ? true : false; + else + on_demand_boot = sdev->pdata->desc->on_demand_dsp_boot; - /* load the firmware */ - ret = snd_sof_load_firmware(sdev); - if (ret < 0) { - dev_err(sdev->dev, - "error: failed to load DSP firmware after resume %d\n", - ret); - sof_set_fw_state(sdev, SOF_FW_BOOT_FAILED); - return ret; + if (on_demand_boot) { + /* Only change the fw_state to PREPARE but skip booting */ + sof_set_fw_state(sdev, SOF_FW_BOOT_PREPARE); + return 0; } - sof_set_fw_state(sdev, SOF_FW_BOOT_IN_PROGRESS); - - /* - * Boot the firmware. The FW boot status will be modified - * in snd_sof_run_firmware() depending on the outcome. - */ - ret = snd_sof_run_firmware(sdev); - if (ret < 0) { - dev_err(sdev->dev, - "error: failed to boot DSP firmware after resume %d\n", - ret); - sof_set_fw_state(sdev, SOF_FW_BOOT_FAILED); - return ret; - } - - /* resume DMA trace */ - ret = sof_fw_trace_resume(sdev); - if (ret < 0) { - /* non fatal */ - dev_warn(sdev->dev, - "warning: failed to init trace after resume %d\n", - ret); - } - - /* restore pipelines */ - if (tplg_ops && tplg_ops->set_up_all_pipelines) { - ret = tplg_ops->set_up_all_pipelines(sdev, false); - if (ret < 0) { - dev_err(sdev->dev, "Failed to restore pipeline after resume %d\n", ret); - goto setup_fail; - } - } - - /* Notify clients not managed by pm framework about core resume */ - sof_resume_clients(sdev); - - /* notify DSP of system resume */ - if (pm_ops && pm_ops->ctx_restore) { - ret = pm_ops->ctx_restore(sdev); - if (ret < 0) - dev_err(sdev->dev, "ctx_restore IPC error during resume: %d\n", ret); - } - -setup_fail: -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) - if (ret < 0) { - /* - * Debugfs cannot be read in runtime suspend, so cache - * the contents upon failure. This allows to capture - * possible DSP coredump information. - */ - sof_cache_debugfs(sdev); - } -#endif - - return ret; + return snd_sof_boot_dsp_firmware(sdev); } static int sof_suspend(struct device *dev, bool runtime_suspend) @@ -297,8 +330,12 @@ int snd_sof_dsp_power_down_notify(struct snd_sof_dev *sdev) { const struct sof_ipc_pm_ops *pm_ops = sof_ipc_get_ops(sdev, pm); - /* Notify DSP of upcoming power down */ - if (sof_ops(sdev)->remove && pm_ops && pm_ops->ctx_save) + /* + * Notify DSP of upcoming power down only if the firmware has been + * booted up + */ + if (sdev->fw_state == SOF_FW_BOOT_COMPLETE && sof_ops(sdev)->remove && + pm_ops && pm_ops->ctx_save) return pm_ops->ctx_save(sdev); return 0; diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 0f624d8cde20..693d063830fa 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -580,6 +580,8 @@ struct snd_sof_dev { wait_queue_head_t boot_wait; enum sof_fw_state fw_state; bool first_boot; + /* mutex to protect DSP firmware boot (except initial, probe time boot */ + struct mutex dsp_fw_boot_mutex; /* work queue in case the probe is implemented in two steps */ struct work_struct probe_work; @@ -703,6 +705,7 @@ int snd_sof_suspend(struct device *dev); int snd_sof_dsp_power_down_notify(struct snd_sof_dev *sdev); int snd_sof_prepare(struct device *dev); void snd_sof_complete(struct device *dev); +int snd_sof_boot_dsp_firmware(struct snd_sof_dev *sdev); void snd_sof_new_platform_drv(struct snd_sof_dev *sdev);