Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion include/sound/soc-dpcm.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,5 +158,9 @@ 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 stream_dir);
int soc_dpcm_vfe_free(struct snd_soc_card *card, const char *link_name);
#endif
26 changes: 26 additions & 0 deletions sound/soc/soc-core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -1134,6 +1135,31 @@ static int soc_bind_dai_link(struct snd_soc_card *card,
}

soc_add_pcm_runtime(card, rtd);

/* if this is a virtual FE link, create runtime and set it as active */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what 'active' runtime means. I would think it becomes 'active' when the switch is On, but maybe it's a different concept you are referring to.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, what I mean by active here is when the kcontrol is switched on and the dpcm_runtime_update() is called, it checks to see if playback or capture is active on the FE link. So I set the FE playback/capture active status by default.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, but I wonder if this has negative implications related to suspend/resume? If there is nothing going on it's a bit odd to force a runtime to be active.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thats a good point.

The problem is the runtime_update() method is oblivious to where it is being called from. It just looks for an active link to process the update.

Maybe I can avoid this by activating the FE in the kcontrol IO handler just before calling runtime_update.

if (rtd->dai_link->dynamic && rtd->dai_link->no_pcm) {
runtime = kzalloc(sizeof(*runtime), GFP_KERNEL);
if (!runtime)
return -ENOMEM;

/* set playback active status */
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;
}

/* set capture active statue */
if (rtd->dai_link->dpcm_capture) {
rtd->dpcm[SNDRV_PCM_STREAM_CAPTURE].runtime = runtime;
rtd->cpu_dai->capture_active = 1;
rtd->codec_dai->capture_active = 1;
}

/* increment the active count for cpu dai */
rtd->cpu_dai->active++;
}

return 0;

_err_defer:
Expand Down
5 changes: 4 additions & 1 deletion sound/soc/soc-dapm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as discussed last week, I wonder if we should also add a snd_soc_dapm_sig_sink (the dual of a generator). This would be very useful for testing e.g. by letting us set-up all sort of DMA channels with a self-test checking.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I will add it to my next update.

case snd_soc_dapm_switch:
case snd_soc_dapm_mixer:
case snd_soc_dapm_pga:
Expand Down Expand Up @@ -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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if you have more than one widget between widget and BE, it's valid and could be constructed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part is a bit unclear to me. The problem I was facing was that the snd_soc_dapm_dai_get_connected_widgets() returned a list with only the BE DAI in the case of the tone pipeline and dropping the starting point resulted in an empty list of paths to process.

the dai argument passed to this method was the cpu_dai->playback_widget() which is the BE dai in my case. And this was the only way I could get it to be enabled. I could use some help here to figure out the right way to do it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, what happens if you have tone followed by a volume control?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do have a tone followed by volume in my topology. But the snd_soc_dapm_dai_get_connected_widgets() is called starting with the dai->playback_widget as the starting point. And this is the BE DAI.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should also state in a comment what we are doing here and why.

list_del(widgets.next);

ret = dapm_widget_list_create(list, &widgets);
if (ret)
Expand Down Expand Up @@ -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);
Expand Down
162 changes: 161 additions & 1 deletion sound/soc/soc-pcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

best to keep this line as is.

switch (list->widgets[i]->id) {
case snd_soc_dapm_dai_in:
if (stream != SNDRV_PCM_STREAM_PLAYBACK)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -2789,6 +2790,109 @@ int soc_dpcm_be_digital_mute(struct snd_soc_pcm_runtime *fe, int mute)
return 0;
}

/*
* create a virtual FE DAI link which has no pcm device registered.
* 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, int stream_dir)
{
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 FE DAI link */
link->name = link_name;
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";

/*
* Enabling both dynamic and no_pcm flags indicates the link is virtual
* i.e. it is a FE dai link but without a registered pcm device
*/
link->dynamic = 1;
link->no_pcm = 1;

/* enable playback */
if (stream_dir == SNDRV_PCM_STREAM_PLAYBACK)
link->dpcm_playback = 1;

/* enable capture */
if (stream_dir == SNDRV_PCM_STREAM_CAPTURE)
link->dpcm_capture = 1;

link->dobj.index = index;
link->dobj.type = SND_SOC_DOBJ_DAI_LINK;

/* add 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, const char *link_name)
{
struct snd_soc_rtdcom_list *rtdcom1, *rtdcom2;
struct snd_soc_pcm_runtime *rtd, *temp;
struct snd_pcm_str *pstr;
int stream_dir;

list_for_each_entry_safe(rtd, temp, &card->rtd_list, list) {

/* delete requested FE link */
if (!strcmp(rtd->dai_link->name, link_name)) {

if (rtd->dai_link->dpcm_playback)
stream_dir = SNDRV_PCM_STREAM_PLAYBACK;
else
stream_dir = SNDRV_PCM_STREAM_CAPTURE;

/* disconnect FE from BE */
dpcm_be_disconnect(rtd, stream_dir);

/* free pcm runtime */
kfree(rtd->dpcm[stream_dir].runtime);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: all these frees don't reflect what is done for the _new method, could we rely on a helper to avoid tracking all these actions? It looks like either copy-paste or something extracted from somewhere else.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually not a copy-paste at all. I traced all the steps from the point of adding the link to the time when the sound card is registered and freed all the data that is allocated. Here're the steps I traced:

  1. Bind DAI link: The PCM runtime and the dpcm runtime is created during this step.
    There are some other runtime child structures allocated during this step too ie codec dais and component list.
  2. Probe DAI link :The pcm device and the substream is created here.
  3. dpcm runtime update: the FE link is connected to the BE

So, in the vfe_free() method, I undid all of this.

But I wonder if I can avoid all of this manually in the free method. Let me dig into these steps a bit more.


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);

/* 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;
Expand Down Expand Up @@ -2996,6 +3100,55 @@ static int soc_rtdcom_mmap(struct snd_pcm_substream *substream,
return -EINVAL;
}

/*
* for virtual FE dai links, there is no need
* to register PCM device. So only allocate memory for
* the dummy pcm device and substreams to be used for
* codec startup
*/
static int setup_virtual_fe(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm *pcm)
{
struct snd_pcm_substream *substream;
struct snd_pcm_str *pstr;
int stream_dir;

/* create pcm */
pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
if (!pcm)
return -ENOMEM;

/* set up playback/capture substream */
if (rtd->dai_link->dpcm_playback)
stream_dir = SNDRV_PCM_STREAM_PLAYBACK;
else
stream_dir = SNDRV_PCM_STREAM_CAPTURE;

pstr = &pcm->streams[stream_dir];

/* allocate memory for substream */
substream = kzalloc(sizeof(*substream), GFP_KERNEL);
if (!substream)
return -ENOMEM;

/* define substream */
substream->pcm = pcm;
substream->pstr = pstr;
substream->number = 0;
substream->stream = stream_dir;
sprintf(substream->name, "subdevice #%i", 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the name exposed by /sysff or /proc ? if so, do we have any naming collisions when we register 2 or more VFE ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm I did not check if it is exposed. Let me try to make it unique

substream->buffer_bytes_max = UINT_MAX;

/* set pcm substream */
pstr->substream = substream;

pcm->nonatomic = rtd->dai_link->nonatomic;
rtd->pcm = pcm;
pcm->private_data = rtd;

return 0;
}

/* create a new pcm */
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
Expand Down Expand Up @@ -3043,6 +3196,13 @@ 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 {

/* set up virtual FE dai link */
if (rtd->dai_link->dynamic && rtd->dai_link->no_pcm) {
setup_virtual_fe(rtd, pcm);
goto out;
}

if (rtd->dai_link->dynamic)
snprintf(new_name, sizeof(new_name), "%s (*)",
rtd->dai_link->stream_name);
Expand Down