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..c310a3fdc90651 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1134,6 +1134,17 @@ struct snd_soc_dai_link { /* Do not create a PCM for this DAI link (Backend link) */ unsigned int ignore:1; + /* + * virtual FE link + * This flag indicates that there is no PCM device registered with ALSA + * This is intended to be used for establishing a connection to the + * BE DAI in the case of hostless pipelines such as, + * DSP component -> codec, ex: tone generator -> codec + * This connection will be established at runtime by triggering the + * hostless pipeline with a kcontrol attached to the component. + */ + 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/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..08e38883d43299 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -1246,7 +1246,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) 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);