diff --git a/include/sound/soc-dpcm.h b/include/sound/soc-dpcm.h index 806059052bfcca..0373d79a8985c8 100644 --- a/include/sound/soc-dpcm.h +++ b/include/sound/soc-dpcm.h @@ -158,5 +158,8 @@ static inline void dpcm_path_put(struct snd_soc_dapm_widget_list **list) kfree(*list); } - +/* create/free virtual FE dai links */ +int soc_dpcm_vfe_new(struct snd_soc_card *, int index, const char *link_name, + const char *cpu_dai_name, const char *platform_name); +int soc_dpcm_vfe_free(struct snd_soc_card *card); #endif diff --git a/include/sound/soc.h b/include/sound/soc.h index 71a745fa8131d4..812a1de3792472 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1134,6 +1134,9 @@ struct snd_soc_dai_link { /* Do not create a PCM for this DAI link (Backend link) */ unsigned int ignore:1; + /* virtual link */ + unsigned int virtual:1; + struct list_head list; /* DAI link list of the soc card */ struct snd_soc_dobj dobj; /* For topology */ }; diff --git a/include/uapi/sound/sof-ipc.h b/include/uapi/sound/sof-ipc.h index e3c3de0a2d19f0..cb606d24c81320 100644 --- a/include/uapi/sound/sof-ipc.h +++ b/include/uapi/sound/sof-ipc.h @@ -649,6 +649,7 @@ struct sof_ipc_comp_mux { struct sof_ipc_comp_tone { struct sof_ipc_comp comp; struct sof_ipc_comp_config config; + int32_t sample_rate; int32_t frequency; int32_t amplitude; int32_t freq_mult; diff --git a/include/uapi/sound/sof-topology.h b/include/uapi/sound/sof-topology.h index 1a4957b7fd049d..5c40a897c44cf9 100644 --- a/include/uapi/sound/sof-topology.h +++ b/include/uapi/sound/sof-topology.h @@ -24,6 +24,7 @@ #define SOF_TPLG_KCTL_VOL_ID 256 #define SOF_TPLG_KCTL_ENUM_ID 257 #define SOF_TPLG_KCTL_BYTES_ID 258 +#define SOF_TPLG_KCTL_SWITCH_ID 259 /* * Tokens - must match values in topology configurations @@ -88,4 +89,7 @@ #define SOF_TKN_INTEL_DMIC_PDM_CLK_EDGE 705 #define SOF_TKN_INTEL_DMIC_PDM_SKEW 706 +/* Tone */ +#define SOF_TKN_TONE_SAMPLE_RATE 800 + #endif diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index a0160a409e4094..4a9bdcde3aa036 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1047,6 +1047,7 @@ static int soc_bind_dai_link(struct snd_soc_card *card, struct snd_soc_dai **codec_dais; struct snd_soc_platform *platform; struct device_node *platform_of_node; + struct snd_pcm_runtime *runtime; const char *platform_name; int i; @@ -1134,6 +1135,25 @@ static int soc_bind_dai_link(struct snd_soc_card *card, } soc_add_pcm_runtime(card, rtd); + + /* if the dai link is virtual, create runtime to set it as running */ + if (rtd->dai_link->virtual) { + runtime = kzalloc(sizeof(*runtime), + GFP_KERNEL); + if (!runtime) + return -ENOMEM; + + if (rtd->dai_link->dpcm_playback) { + rtd->dpcm[SNDRV_PCM_STREAM_PLAYBACK].runtime = runtime; + rtd->cpu_dai->playback_active = 1; + rtd->codec_dai->playback_active = 1; + } + + /* increment the active count for cpu dai */ + rtd->cpu_dai->active++; + + /* does virtual FE for capture make sense */ + } return 0; _err_defer: diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 6d54128e44b46b..a0c1527e1b2917 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -855,6 +855,7 @@ static int dapm_create_or_share_kcontrol(struct snd_soc_dapm_widget *w, kcname_in_long_name = true; } else { switch (w->id) { + case snd_soc_dapm_siggen: case snd_soc_dapm_switch: case snd_soc_dapm_mixer: case snd_soc_dapm_pga: @@ -1246,7 +1247,8 @@ int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream, custom_stop_condition); /* Drop starting point */ - list_del(widgets.next); + if (!list_is_singular(&widgets)) + list_del(widgets.next); ret = dapm_widget_list_create(list, &widgets); if (ret) @@ -3076,6 +3078,7 @@ int snd_soc_dapm_new_widgets(struct snd_soc_card *card) case snd_soc_dapm_demux: dapm_new_mux(w); break; + case snd_soc_dapm_siggen: case snd_soc_dapm_pga: case snd_soc_dapm_out_drv: dapm_new_pga(w); diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 4ce489165a6d9b..40a3f34ef2d66d 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1582,7 +1582,6 @@ static int dpcm_add_paths(struct snd_soc_pcm_runtime *fe, int stream, /* Create any new FE <--> BE connections */ for (i = 0; i < list->num_widgets; i++) { - switch (list->widgets[i]->id) { case snd_soc_dapm_dai_in: if (stream != SNDRV_PCM_STREAM_PLAYBACK) @@ -2759,6 +2758,8 @@ int soc_dpcm_runtime_update(struct snd_soc_card *card) mutex_unlock(&card->mutex); return 0; } +EXPORT_SYMBOL_GPL(soc_dpcm_runtime_update); + int soc_dpcm_be_digital_mute(struct snd_soc_pcm_runtime *fe, int mute) { struct snd_soc_dpcm *dpcm; @@ -2789,6 +2790,114 @@ int soc_dpcm_be_digital_mute(struct snd_soc_pcm_runtime *fe, int mute) return 0; } +/* + * create a virtual FE DAI link + * Virtual FE DAI links are used in hostless pipelines + * to enable the codecs when the pipeline is triggered + */ +int soc_dpcm_vfe_new(struct snd_soc_card *card, int index, + const char *link_name, const char *cpu_dai_name, + const char *platform_name) +{ + struct snd_soc_dai_link *link; + + link = kzalloc(sizeof(*link), GFP_KERNEL); + if (!link) + return -ENOMEM; + + dev_dbg(card->dev, "ASoC: adding new virtual FE DAI link %s\n", + link_name); + + /* define virtual FE DAI link */ + link->virtual = 1; + link->name = link_name; + link->id = 1; + link->cpu_dai_name = cpu_dai_name; + link->platform_name = platform_name; + link->codec_name = "snd-soc-dummy"; + link->codec_dai_name = "snd-soc-dummy-dai"; + link->num_codecs = 1; + + /* allocate memory for link codecs */ + link->codecs = devm_kzalloc(card->dev, + sizeof(struct snd_soc_dai_link_component), + GFP_KERNEL); + if (!link->codecs) + return -ENOMEM; + + link->codecs[0].name = link->codec_name; + link->codecs[0].dai_name = link->codec_dai_name; + + /* enable DPCM */ + link->dynamic = 1; + + /*TODO: check if we need to handle capture for virtual FE */ + link->dpcm_playback = 1; + + link->dobj.index = index; + link->dobj.type = SND_SOC_DOBJ_DAI_LINK; + + /* add virtual dai link to card dai link list */ + snd_soc_add_dai_link(card, link); + + return 0; +} +EXPORT_SYMBOL_GPL(soc_dpcm_vfe_new); + +/* free virtual FE DAI link */ +int soc_dpcm_vfe_free(struct snd_soc_card *card) +{ + struct snd_soc_rtdcom_list *rtdcom1, *rtdcom2; + struct snd_soc_pcm_runtime *rtd; + struct snd_pcm_str *pstr; + int stream_dir; + + list_for_each_entry(rtd, &card->rtd_list, list) { + + /* check if this is a virtual dai link */ + if (rtd->dai_link->virtual) { + + if (rtd->dai_link->dpcm_playback) { + stream_dir = SNDRV_PCM_STREAM_PLAYBACK; + + /* disconnect FE from BE */ + dpcm_be_disconnect(rtd, stream_dir); + + /* free pcm runtime */ + kfree(rtd->dpcm[stream_dir].runtime); + + pstr = &rtd->pcm->streams[stream_dir]; + + /* free pcm substream amd pcm */ + kfree(pstr->substream); + } + + /* free pcm */ + kfree(rtd->pcm); + + /* free codec dais and component list */ + kfree(rtd->codec_dais); + + for_each_rtdcom_safe(rtd, rtdcom1, rtdcom2) + kfree(rtdcom1); + + INIT_LIST_HEAD(&rtd->component_list); + + /* remove dai_link from card */ + snd_soc_remove_dai_link(card, rtd->dai_link); + + /* free link */ + kfree(rtd->dai_link); + + /* free runtime */ + kfree(rtd); + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(soc_dpcm_vfe_free); + static int dpcm_fe_dai_open(struct snd_pcm_substream *fe_substream) { struct snd_soc_pcm_runtime *fe = fe_substream->private_data; @@ -3004,7 +3113,9 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_soc_component *component; struct snd_soc_rtdcom_list *rtdcom; + struct snd_pcm_substream *substream; struct snd_pcm *pcm; + int stream_dir; char new_name[64]; int ret = 0, playback = 0, capture = 0; int i; @@ -3043,6 +3154,44 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num, playback, capture, &pcm); } else { + + /* + * for virtual FE dai links, there is no need + * to register PCM device. So only allocate memory for + * pcm device and substream for the requested direction + */ + if (rtd->dai_link->virtual) { + struct snd_pcm_str *pstr; + + if (rtd->dai_link->dpcm_playback) + stream_dir = SNDRV_PCM_STREAM_PLAYBACK; + + pcm = kzalloc(sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + pstr = &pcm->streams[stream_dir]; + + substream = kzalloc(sizeof(*substream), GFP_KERNEL); + if (!substream) + return -ENOMEM; + + substream->pcm = pcm; + substream->pstr = pstr; + substream->number = 0; + substream->stream = stream_dir; + sprintf(substream->name, "subdevice #%i", 0); + substream->buffer_bytes_max = UINT_MAX; + + pstr->substream = substream; + + pcm->nonatomic = rtd->dai_link->nonatomic; + rtd->pcm = pcm; + pcm->private_data = rtd; + + goto out; + } + if (rtd->dai_link->dynamic) snprintf(new_name, sizeof(new_name), "%s (*)", rtd->dai_link->stream_name); diff --git a/sound/soc/sof/control.c b/sound/soc/sof/control.c index db09ba63154b69..b4ca0b1aad6544 100644 --- a/sound/soc/sof/control.c +++ b/sound/soc/sof/control.c @@ -20,6 +20,104 @@ #include #include "sof-priv.h" +/* return the widget type of the comp the kcontrol is attached to */ +static int get_widget_type(struct snd_sof_dev *sdev, + struct snd_sof_control *scontrol) +{ + struct snd_sof_widget *swidget; + + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (swidget->comp_id == scontrol->comp_id) + return swidget->id; + } + + /* standalone kcontrol */ + return -EINVAL; +} + +/* helper function to send pcm params ipc */ +static int siggen_pcm_params(struct snd_sof_control *scontrol, + struct snd_sof_dev *sdev) +{ + struct sof_ipc_pcm_params_reply ipc_params_reply; + struct sof_ipc_pcm_params pcm; + int ret = 0; + + memset(&pcm, 0, sizeof(pcm)); + + /* set IPC PCM parameters */ + pcm.hdr.size = sizeof(pcm); + pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS; + pcm.comp_id = scontrol->comp_id; + pcm.params.channels = scontrol->num_channels; + pcm.params.direction = SOF_IPC_STREAM_PLAYBACK; + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, pcm.hdr.cmd, &pcm, sizeof(pcm), + &ipc_params_reply, sizeof(ipc_params_reply)); + if (ret < 0) + dev_err(sdev->dev, "error: setting pcm params for siggen\n"); + + return ret; +} + +/* helper function to send stream trigger ipc for siggen pipeline */ +static int siggen_trigger(struct snd_sof_control *scontrol, + struct snd_sof_dev *sdev, int cmd) +{ + struct sof_ipc_stream stream; + struct sof_ipc_reply reply; + int ret = 0; + + /* set IPC stream params */ + stream.hdr.size = sizeof(stream); + stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | cmd; + stream.comp_id = scontrol->comp_id; + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream, + sizeof(stream), &reply, sizeof(reply)); + if (ret < 0) + dev_err(sdev->dev, "error: failed to trigger stream\n"); + + return ret; +} + +/* + * Helper function to send ipc's to trigger siggen pipeline + * The siggen pipeline is enabled/disabled only if the + * control values change from the old state. + */ +static int siggen_pipeline_trigger(struct snd_sof_control *scontrol, + struct snd_sof_dev *sdev, + struct snd_soc_card *card, + int new_state) +{ + int ret = 0; + + if (!new_state) { + + /* free pcm and reset pipeline */ + ret = siggen_trigger(scontrol, sdev, + SOF_IPC_STREAM_PCM_FREE); + } else { + + /* enable BE DAI */ + soc_dpcm_runtime_update(card); + + /* set pcm params */ + ret = siggen_pcm_params(scontrol, sdev); + if (ret < 0) + return ret; + + /* enable siggen */ + ret = siggen_trigger(scontrol, sdev, + SOF_IPC_STREAM_TRIG_START); + } + + return ret; +} + static inline u32 mixer_to_ipc(unsigned int value, u32 *volume_map, int size) { if (value >= size) @@ -195,3 +293,142 @@ int snd_sof_bytes_put(struct snd_kcontrol *kcontrol, pm_runtime_put_autosuspend(sdev->dev); return 0; } + +/* IO handlers for switch kcontrol handlers */ +int snd_sof_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *sm = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_sof_control *scontrol = sm->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + unsigned int i, channels = scontrol->num_channels; + + pm_runtime_get_sync(sdev->dev); + + /* get all the mixer data from DSP */ + snd_sof_ipc_get_comp_data(sdev->ipc, scontrol, SOF_IPC_COMP_GET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_SWITCH); + + /* read back each channel */ + for (i = 0; i < channels; i++) + ucontrol->value.integer.value[i] = cdata->chanv[i].value; + + pm_runtime_mark_last_busy(sdev->dev); + pm_runtime_put_autosuspend(sdev->dev); + return 0; +} + +int snd_sof_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *sm = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_sof_control *scontrol = sm->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_card *card = dapm->card; + unsigned int i, channels = scontrol->num_channels; + int ret = 0, new_state, old_state; + int changed = 0; + + pm_runtime_get_sync(sdev->dev); + + switch (get_widget_type(sdev, scontrol)) { + case snd_soc_dapm_pga: + + /* + * if the kcontrol is used for processing as in the case of pga, + * values are channel-specific + */ + for (i = 0; i < channels; i++) { + new_state = ucontrol->value.integer.value[i]; + old_state = cdata->chanv[i].value; + if (new_state != old_state) + changed = 1; + cdata->chanv[i].value = new_state; + cdata->chanv[i].channel = i; + } + + /* + * notify DSP of switch state update + * if the control values are different + */ + if (changed) + snd_sof_ipc_set_comp_data(sdev->ipc, scontrol, + SOF_IPC_COMP_SET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_SWITCH); + break; + case snd_soc_dapm_siggen: + + /* + * A siggen kcontrol is used as an ON/OFF switch, + * so all channel values are assumed to be identical + */ + for (i = 0; i < channels; i++) { + new_state = ucontrol->value.integer.value[0]; + old_state = cdata->chanv[0].value; + if (new_state != old_state) + changed = 1; + cdata->chanv[i].value = new_state; + cdata->chanv[i].channel = i; + } + + if (changed) { + + /* + * notify DSP of switch state update + * if the control values are different + */ + snd_sof_ipc_set_comp_data(sdev->ipc, scontrol, + SOF_IPC_COMP_SET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_SWITCH); + + /* trigger siggen pipeline */ + ret = siggen_pipeline_trigger(scontrol, sdev, + card, new_state); + if (ret < 0) { + dev_err(sdev->dev, + "error: triggering siggen pipeline\n"); + changed = ret; + } + } + break; + default: + + /* + * if the kcontrol is for routing or a standalone control, + * all channel values are assumed to be identical + */ + for (i = 0; i < channels; i++) { + new_state = ucontrol->value.integer.value[0]; + old_state = cdata->chanv[0].value; + if (new_state != old_state) + changed = 1; + cdata->chanv[i].value = new_state; + cdata->chanv[i].channel = i; + } + + /* + * notify DSP of switch state update + * if the control values are different + */ + if (changed) + snd_sof_ipc_set_comp_data(sdev->ipc, scontrol, + SOF_IPC_COMP_SET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_SWITCH); + + break; + } + + pm_runtime_mark_last_busy(sdev->dev); + pm_runtime_put_autosuspend(sdev->dev); + return changed; +} diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index fabfcb25180940..4a97bc73ff6146 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -454,6 +454,10 @@ int snd_sof_bytes_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); int snd_sof_bytes_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); +int snd_sof_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); /* * DSP Architectures. diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index eb9e16f643b5a5..6f831d38ceb82d 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -376,6 +377,8 @@ static const struct sof_topology_token src_tokens[] = { /* Tone */ static const struct sof_topology_token tone_tokens[] = { + {SOF_TKN_TONE_SAMPLE_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_tone, sample_rate), 0}, }; /* PCM */ @@ -1172,10 +1175,39 @@ static int sof_widget_load_siggen(struct snd_soc_component *scomp, int index, struct sof_ipc_comp_reply *r) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_dapm_widget *widget = swidget->widget; struct snd_soc_tplg_private *private = &tw->priv; + const struct snd_kcontrol_new *kc = NULL; struct sof_ipc_comp_tone tone; + struct soc_mixer_control *sm; + struct snd_sof_control *scontrol; + const char *link_name = "TONE-VFE0"; + const char *cpu_dai_name = "sof-audio"; + const char *platform_name = "sof-audio"; int ret; + /* siggen needs 1 mixer type control to act as a trigger */ + if (tw->num_kcontrols != 1) { + dev_err(sdev->dev, "error: invalid kcontrol count %d for siggen\n", + tw->num_kcontrols); + return -EINVAL; + } + + /* get mixer control */ + kc = &widget->kcontrol_news[0]; + sm = (struct soc_mixer_control *)kc->private_value; + scontrol = sm->dobj.private; + + /* + * siggen kcontrol needs only 2 values + * 0 for disabling and 1 for enabling the comp + */ + if (sm->max != 1) { + dev_err(sdev->dev, "error: invalid max %d for siggen control\n", + sm->max); + return -EINVAL; + } + /* configure mixer IPC message */ memset(&tone, 0, sizeof(tone)); tone.comp.hdr.size = sizeof(tone); @@ -1202,13 +1234,31 @@ static int sof_widget_load_siggen(struct snd_soc_component *scomp, int index, return ret; } - dev_dbg(sdev->dev, "tone %s: frequency %d amplitude %d\n", - swidget->widget->name, tone.frequency, tone.amplitude); + dev_dbg(sdev->dev, "tone %s: frequency %d amplitude %d sample rate %d\n", + swidget->widget->name, tone.frequency, tone.amplitude, + tone.sample_rate); sof_dbg_comp_config(scomp, &tone.config); - return sof_ipc_tx_message(sdev->ipc, - tone.comp.hdr.cmd, &tone, sizeof(tone), r, - sizeof(*r)); + ret = sof_ipc_tx_message(sdev->ipc, + tone.comp.hdr.cmd, &tone, sizeof(tone), r, + sizeof(*r)); + if (ret < 0) + return ret; + + /* + * create virtual FE link + * This will be used to enable the codec when + * the siggen pipeline is triggered + */ + ret = soc_dpcm_vfe_new(scomp->card, index, link_name, cpu_dai_name, + platform_name); + if (ret < 0) { + dev_err(sdev->dev, "error: creating virtual FE %d\n", + private->size); + return ret; + } + + return 0; } /* @@ -1367,6 +1417,9 @@ static int sof_widget_unload(struct snd_soc_component *scomp, /* free volume table */ kfree(scontrol->volume_table); break; + case snd_soc_dapm_siggen: + soc_dpcm_vfe_free(scomp->card); + break; default: break; } @@ -1932,6 +1985,7 @@ static const struct snd_soc_tplg_kcontrol_ops sof_io_ops[] = { {SOF_TPLG_KCTL_VOL_ID, snd_sof_volume_get, snd_sof_volume_put}, {SOF_TPLG_KCTL_ENUM_ID, snd_sof_enum_get, snd_sof_enum_put}, {SOF_TPLG_KCTL_BYTES_ID, snd_sof_bytes_get, snd_sof_bytes_put}, + {SOF_TPLG_KCTL_SWITCH_ID, snd_sof_switch_get, snd_sof_switch_put}, }; /* vendor specific bytes ext handlers available for binding */