diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index 0034e7a34a1d..53593df95155 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -744,9 +744,31 @@ int snd_sof_ipc_set_get_comp_data(struct snd_sof_control *scontrol, struct sof_ipc_fw_ready *ready = &sdev->fw_ready; struct sof_ipc_fw_version *v = &ready->version; struct sof_ipc_ctrl_data_params sparams; + struct snd_sof_widget *swidget; + bool widget_found = false; size_t send_bytes; 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 */ diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index 4bed50847f1d..7a4aaabf091e 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -83,7 +83,53 @@ static int sof_widget_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_wi return 0; } -static int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) +int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) +{ + struct sof_ipc_free ipc_free = { + .hdr = { + .size = sizeof(ipc_free), + .cmd = SOF_IPC_GLB_TPLG_MSG, + }, + .id = swidget->comp_id, + }; + struct sof_ipc_reply reply; + int ret; + + if (!swidget->private) + return 0; + + /* only free when use_count is 0 */ + if (--swidget->use_count) + return 0; + + switch (swidget->id) { + case snd_soc_dapm_scheduler: + ipc_free.hdr.cmd |= SOF_IPC_TPLG_PIPE_FREE; + break; + case snd_soc_dapm_buffer: + ipc_free.hdr.cmd |= SOF_IPC_TPLG_BUFFER_FREE; + break; + default: + ipc_free.hdr.cmd |= SOF_IPC_TPLG_COMP_FREE; + break; + } + + ret = sof_ipc_tx_message(sdev->ipc, ipc_free.hdr.cmd, &ipc_free, sizeof(ipc_free), + &reply, sizeof(reply)); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to free widget %s\n", swidget->widget->name); + swidget->use_count++; + return ret; + } + + swidget->complete = 0; + dev_dbg(sdev->dev, "widget %s freed\n", swidget->widget->name); + + return 0; +} +EXPORT_SYMBOL(sof_widget_free); + +int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) { struct sof_ipc_pipe_new *pipeline; struct sof_ipc_comp_reply r; @@ -97,11 +143,15 @@ static int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swi if (!swidget->private) return 0; + /* widget already set up */ + if (++swidget->use_count > 1) + return 0; + ret = sof_pipeline_core_enable(sdev, swidget); if (ret < 0) { dev_err(sdev->dev, "error: failed to enable target core: %d for widget %s\n", ret, swidget->widget->name); - return ret; + goto use_count_dec; } switch (swidget->id) { @@ -134,7 +184,7 @@ static int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swi } if (ret < 0) { dev_err(sdev->dev, "error: failed to load widget %s\n", swidget->widget->name); - return ret; + goto use_count_dec; } /* restore kcontrols for widget */ @@ -147,8 +197,13 @@ static int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swi dev_dbg(sdev->dev, "widget %s setup complete\n", swidget->widget->name); + return 0; + +use_count_dec: + swidget->use_count--; return ret; } +EXPORT_SYMBOL(sof_widget_setup); /* * helper to determine if there are only D0i3 compatible @@ -258,6 +313,9 @@ int sof_set_up_pipelines(struct device *dev) /* restore pipeline components */ list_for_each_entry_reverse(swidget, &sdev->widget_list, list) { + /* reset widget use_count after resuming */ + swidget->use_count = 0; + ret = sof_widget_setup(sdev, swidget); if (ret < 0) return ret; @@ -325,16 +383,23 @@ int sof_set_up_pipelines(struct device *dev) return 0; } -/* This function doesn't free widgets. It only resets the set up status for all routes */ +/* + * This function doesn't free widgets. It only resets the set up status for all routes and + * use_count for all widgets. + */ void sof_tear_down_pipelines(struct device *dev) { struct snd_sof_dev *sdev = dev_get_drvdata(dev); + struct snd_sof_widget *swidget; struct snd_sof_route *sroute; /* - * No need to protect sroute->setup as this function is called only during the suspend - * callback and all streams should be suspended by then + * No need to protect swidget->use_count and sroute->setup as this function is called only + * during the suspend callback and all streams should be suspended by then */ + list_for_each_entry(swidget, &sdev->widget_list, list) + swidget->use_count = 0; + list_for_each_entry(sroute, &sdev->route_list, list) sroute->setup = false; } diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index f1f630028c21..6ac623137026 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -89,6 +89,7 @@ struct snd_sof_widget { int comp_id; int pipeline_id; int complete; + int use_count; /* use_count will be protected by the PCM mutex held by the core */ int core; int id; @@ -252,4 +253,7 @@ bool snd_sof_dsp_only_d0i3_compatible_stream_active(struct snd_sof_dev *sdev); int sof_machine_register(struct snd_sof_dev *sdev, void *pdata); void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata); +int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget); +int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget); + #endif