Skip to content

Commit ab6ff22

Browse files
committed
ASoC: SOF: sof-audio: Add support for loopback capture
An example of a DAI-less loopback pipeline would be the echo reference capture in the speaker playback path. This pipeline is set up as follows: Host(Playback) -> mixin -> mixout -> gain -> module-copier -> DAI | V Host(Capture) <- Process module <- virtual DAI In the above example, the virtual DAI exploits the concept of an aggregated DAI (one with a non-zero DAI ID) in topology to enable this pipeline to work with DPCM. A virtual DAI is a DAI widget with a non-zero DAI ID and hence is skipped when traversing the list of DAPM widgets during widget prepare/set/up/free/unprepare. The process module in the above pipeline generates 0's that are captured by the echo reference PCM. When the playback path is active, the process module acts as a passthrough module to allow the playback samples to be passthrough to the capture host. In order for these pipelines to work properly, the logic for setting/preparing/freeing/unpreparing the widgets needs to be amended to make sure that only the widgets that are in the pipeline in the same direction as the PCM being started are set up. For example, when the playback PCM is started, the capture pipeline widgets also show up in the list of connected DAPM widgets but they shouldn't be set up yet because the echo reference capture PCM hasn't been started yet. Alternatively, when the echo reference capture PCM is started, the playback pipeline widgets should not be setup. Finally, the last step needed to put this all together is the set the routes for widgets connecting the playback and the capture pipelines when both are active. Signed-off-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
1 parent 7700d9f commit ab6ff22

File tree

1 file changed

+96
-52
lines changed

1 file changed

+96
-52
lines changed

sound/soc/sof/sof-audio.c

Lines changed: 96 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,10 @@ int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsourc
269269
is_virtual_widget(sdev, sink_widget->widget, __func__))
270270
return 0;
271271

272+
/* skip route if source/sink widget is not set up */
273+
if (!src_widget->use_count || !sink_widget->use_count)
274+
return 0;
275+
272276
/* find route matching source and sink widgets */
273277
list_for_each_entry(sroute, &sdev->route_list, list)
274278
if (sroute->src_widget == src_widget && sroute->sink_widget == sink_widget) {
@@ -297,10 +301,34 @@ int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsourc
297301
return 0;
298302
}
299303

304+
static bool sof_widget_in_same_direction(struct snd_sof_widget *swidget, int dir)
305+
{
306+
return swidget->spipe->direction == dir;
307+
}
308+
309+
static int sof_set_up_same_dir_widget_routes(struct snd_sof_dev *sdev,
310+
struct snd_soc_dapm_widget *wsource,
311+
struct snd_soc_dapm_widget *wsink)
312+
{
313+
struct snd_sof_widget *src_widget = wsource->dobj.private;
314+
struct snd_sof_widget *sink_widget = wsink->dobj.private;
315+
316+
/*
317+
* skip setting up route if source and sink are in different directions (ex. playback and
318+
* echo ref) if the direction is set in topology. These will be set up later. It is enough
319+
* to check if the direction_valid is set for one of the widgets as all widgets will have
320+
* the direction set in topology if one is set.
321+
*/
322+
if (sink_widget->spipe->direction_valid &&
323+
!sof_widget_in_same_direction(sink_widget, src_widget->spipe->direction))
324+
return 0;
325+
326+
return sof_route_setup(sdev, wsource, wsink);
327+
}
328+
300329
static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev,
301330
struct snd_soc_dapm_widget_list *list, int dir)
302331
{
303-
const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
304332
struct snd_soc_dapm_widget *widget;
305333
struct snd_sof_route *sroute;
306334
struct snd_soc_dapm_path *p;
@@ -323,7 +351,8 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev,
323351
continue;
324352

325353
if (p->sink->dobj.private) {
326-
ret = sof_route_setup(sdev, widget, p->sink);
354+
ret = sof_set_up_same_dir_widget_routes(sdev, widget,
355+
p->sink);
327356
if (ret < 0)
328357
return ret;
329358
}
@@ -339,7 +368,8 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev,
339368
continue;
340369

341370
if (p->source->dobj.private) {
342-
ret = sof_route_setup(sdev, p->source, widget);
371+
ret = sof_set_up_same_dir_widget_routes(sdev, p->source,
372+
widget);
343373
if (ret < 0)
344374
return ret;
345375
}
@@ -355,7 +385,6 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev,
355385
*/
356386
list_for_each_entry(sroute, &sdev->route_list, list) {
357387
bool src_widget_in_dapm_list, sink_widget_in_dapm_list;
358-
struct snd_sof_widget *swidget;
359388

360389
if (sroute->setup)
361390
continue;
@@ -364,40 +393,37 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev,
364393
sink_widget_in_dapm_list = widget_in_list(list, sroute->sink_widget->widget);
365394

366395
/*
367-
* if both source and sink are in the DAPM list, the route must already have been
368-
* set up above. And if neither are in the DAPM list, the route shouldn't be
369-
* handled now.
396+
* no need to set up the route if both the source and sink widgets are not in the
397+
* DAPM list
370398
*/
371-
if (src_widget_in_dapm_list == sink_widget_in_dapm_list)
399+
if (!src_widget_in_dapm_list && !sink_widget_in_dapm_list)
372400
continue;
373401

374402
/*
375-
* At this point either the source widget or the sink widget is in the DAPM list
376-
* with a route that might need to be set up. Check the use_count of the widget
377-
* that is not in the DAPM list to confirm if it is in use currently before setting
378-
* up the route.
403+
* set up the route only if both the source and sink widgets are in the DAPM list
404+
* but are in different directions. The ones in the same direction would already
405+
* have been set up in the previous loop.
379406
*/
380-
if (src_widget_in_dapm_list)
381-
swidget = sroute->sink_widget;
382-
else
383-
swidget = sroute->src_widget;
407+
if (src_widget_in_dapm_list && sink_widget_in_dapm_list) {
408+
struct snd_sof_widget *src_widget, *sink_widget;
384409

385-
scoped_guard(mutex, &swidget->setup_mutex) {
386-
if (!swidget->use_count)
387-
continue;
410+
src_widget = sroute->src_widget->widget->dobj.private;
411+
sink_widget = sroute->sink_widget->widget->dobj.private;
388412

389-
if (tplg_ops && tplg_ops->route_setup) {
390-
/*
391-
* this route will get freed when either the
392-
* source widget or the sink widget is freed
393-
* during hw_free
394-
*/
395-
ret = tplg_ops->route_setup(sdev, sroute);
396-
if (!ret)
397-
sroute->setup = true;
398-
}
413+
/*
414+
* it is enough to check if the direction_valid is set for one of the
415+
* widgets as all widgets will have the direction set in topology if one
416+
* is set.
417+
*/
418+
if (src_widget && sink_widget &&
419+
src_widget->spipe->direction_valid &&
420+
sof_widget_in_same_direction(sink_widget, src_widget->spipe->direction))
421+
continue;
399422
}
400423

424+
ret = sof_route_setup(sdev, sroute->src_widget->widget,
425+
sroute->sink_widget->widget);
426+
401427
if (ret < 0)
402428
return ret;
403429
}
@@ -407,7 +433,7 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev,
407433

408434
static void
409435
sof_unprepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget,
410-
struct snd_soc_dapm_widget_list *list)
436+
struct snd_soc_dapm_widget_list *list, int dir)
411437
{
412438
const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
413439
struct snd_sof_widget *swidget = widget->dobj.private;
@@ -417,9 +443,14 @@ sof_unprepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widg
417443
if (is_virtual_widget(sdev, widget, __func__))
418444
return;
419445

420-
/* skip if the widget in use or already unprepared or is an aggregated DAI */
421-
if (!swidget || !swidget->prepared || swidget->use_count > 0 ||
422-
is_aggregated_dai(swidget))
446+
if (!swidget)
447+
goto sink_unprepare;
448+
449+
if (swidget->spipe->direction_valid && !sof_widget_in_same_direction(swidget, dir))
450+
return;
451+
452+
/* skip widgets in use, those already unprepared or aggregated DAIs */
453+
if (!swidget->prepared || swidget->use_count > 0 || is_aggregated_dai(swidget))
423454
goto sink_unprepare;
424455

425456
widget_ops = tplg_ops ? tplg_ops->widget : NULL;
@@ -434,9 +465,10 @@ sof_unprepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widg
434465
snd_soc_dapm_widget_for_each_sink_path(widget, p) {
435466
if (!widget_in_list(list, p->sink))
436467
continue;
468+
437469
if (!p->walking && p->sink->dobj.private) {
438470
p->walking = true;
439-
sof_unprepare_widgets_in_path(sdev, p->sink, list);
471+
sof_unprepare_widgets_in_path(sdev, p->sink, list, dir);
440472
p->walking = false;
441473
}
442474
}
@@ -458,15 +490,19 @@ sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget
458490
if (is_virtual_widget(sdev, widget, __func__))
459491
return 0;
460492

493+
if (!swidget)
494+
goto sink_prepare;
495+
461496
widget_ops = tplg_ops ? tplg_ops->widget : NULL;
462497
if (!widget_ops)
463498
return 0;
464499

465-
if (!swidget || !widget_ops[widget->id].ipc_prepare || swidget->prepared)
466-
goto sink_prepare;
500+
if (swidget->spipe->direction_valid && !sof_widget_in_same_direction(swidget, dir))
501+
return 0;
467502

468-
/* skip aggregated DAIs */
469-
if (is_aggregated_dai(swidget))
503+
/* skip widgets already prepared or aggregated DAI widgets*/
504+
if (!widget_ops[widget->id].ipc_prepare || swidget->prepared ||
505+
is_aggregated_dai(swidget))
470506
goto sink_prepare;
471507

472508
/* prepare the source widget */
@@ -484,6 +520,7 @@ sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget
484520
snd_soc_dapm_widget_for_each_sink_path(widget, p) {
485521
if (!widget_in_list(list, p->sink))
486522
continue;
523+
487524
if (!p->walking && p->sink->dobj.private) {
488525
p->walking = true;
489526
ret = sof_prepare_widgets_in_path(sdev, p->sink, fe_params,
@@ -513,23 +550,28 @@ static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dap
513550
int dir, struct snd_sof_pcm *spcm)
514551
{
515552
struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list;
516-
struct snd_sof_widget *swidget;
553+
struct snd_sof_widget *swidget = widget->dobj.private;
517554
struct snd_soc_dapm_path *p;
518555
int err;
519556
int ret = 0;
520557

521558
if (is_virtual_widget(sdev, widget, __func__))
522559
return 0;
523560

524-
swidget = widget->dobj.private;
561+
if (!swidget)
562+
goto sink_free;
525563

526-
/* no need to free aggregated DAI widgets */
527-
if (swidget && !is_aggregated_dai(swidget)) {
528-
err = sof_widget_free(sdev, swidget);
529-
if (err < 0)
530-
ret = err;
531-
}
564+
if (swidget->spipe->direction_valid && !sof_widget_in_same_direction(swidget, dir))
565+
return 0;
532566

567+
/* skip aggregated DAIs */
568+
if (is_aggregated_dai(swidget))
569+
goto sink_free;
570+
571+
err = sof_widget_free(sdev, widget->dobj.private);
572+
if (err < 0)
573+
ret = err;
574+
sink_free:
533575
/* free all widgets in the sink paths even in case of error to keep use counts balanced */
534576
snd_soc_dapm_widget_for_each_sink_path(widget, p) {
535577
if (!p->walking) {
@@ -569,6 +611,10 @@ static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_d
569611
if (swidget) {
570612
int i;
571613

614+
if (swidget->spipe->direction_valid && !sof_widget_in_same_direction(swidget, dir))
615+
return 0;
616+
617+
/* skip aggregated DAIs */
572618
if (is_aggregated_dai(swidget))
573619
goto sink_setup;
574620

@@ -634,15 +680,13 @@ sof_walk_widgets_in_order(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm,
634680
return 0;
635681

636682
for_each_dapm_widgets(list, i, widget) {
637-
if (is_virtual_widget(sdev, widget, __func__))
638-
continue;
639-
640-
/* starting widget for playback is AIF type */
683+
/* starting widget for playback is of AIF type */
641684
if (dir == SNDRV_PCM_STREAM_PLAYBACK && widget->id != snd_soc_dapm_aif_in)
642685
continue;
643686

644687
/* starting widget for capture is DAI type */
645-
if (dir == SNDRV_PCM_STREAM_CAPTURE && widget->id != snd_soc_dapm_dai_out)
688+
if (dir == SNDRV_PCM_STREAM_CAPTURE && widget->id != snd_soc_dapm_dai_out &&
689+
widget->id != snd_soc_dapm_output)
646690
continue;
647691

648692
switch (op) {
@@ -672,7 +716,7 @@ sof_walk_widgets_in_order(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm,
672716
break;
673717
}
674718
case SOF_WIDGET_UNPREPARE:
675-
sof_unprepare_widgets_in_path(sdev, widget, list);
719+
sof_unprepare_widgets_in_path(sdev, widget, list, dir);
676720
break;
677721
default:
678722
dev_err(sdev->dev, "Invalid widget op %d\n", op);

0 commit comments

Comments
 (0)