[alsa-devel] [RFC 3/4] ASoC: Enable dynamic DAIlink insertion & removal
Vaibhav Agarwal
vaibhav.agarwal at linaro.org
Mon Feb 15 13:19:31 CET 2016
This patch enables dynamic DAI link insertion & removal from
machine driver.
With the evolvement of modularized platforms, codecs can be
dynamically added to/removed from a platform.
Thus, there is a need to add FE/BE DAIs to an existing sound card
in response to codec inserted/removed.
Another kconfig option SND_SOC_DYNAMIC_DAILINK (default set to y)
is added to avoid compilation issues for client (machine, codec)
drivers with other kernel versions.
Limitations:
This patch enables support for new DAI links in response to new
codec driver & DAIs only.
The same can be extended for new platform drivers added/removed
as well.
Signed-off-by: Vaibhav Agarwal <vaibhav.agarwal at linaro.org>
---
include/sound/soc-dapm.h | 7 +-
include/sound/soc-dpcm.h | 1 +
include/sound/soc.h | 6 ++
sound/soc/Kconfig | 4 +
sound/soc/soc-core.c | 264 ++++++++++++++++++++++++++++++++++++++++++++++-
sound/soc/soc-dapm.c | 105 +++++++++++++++----
sound/soc/soc-pcm.c | 25 +++++
7 files changed, 391 insertions(+), 21 deletions(-)
diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h
index 9706946..f680e3a 100644
--- a/include/sound/soc-dapm.h
+++ b/include/sound/soc-dapm.h
@@ -384,7 +384,10 @@ int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm,
int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm,
struct snd_soc_dai *dai);
int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card);
-void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card);
+int snd_soc_dapm_link_dai_widgets_component(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm);
+void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card,
+ struct snd_soc_pcm_runtime *rtd);
int snd_soc_dapm_new_pcm(struct snd_soc_card *card,
const struct snd_soc_pcm_stream *params,
unsigned int num_params,
@@ -620,6 +623,8 @@ struct snd_soc_dapm_context {
unsigned int idle_bias_off:1; /* Use BIAS_OFF instead of STANDBY */
/* Go to BIAS_OFF in suspend if the DAPM context is idle */
unsigned int suspend_bias_off:1;
+ /* registered dynamically in response to dynamic DAI links */
+ unsigned int dynamic_registered:1;
void (*seq_notifier)(struct snd_soc_dapm_context *,
enum snd_soc_dapm_type, int);
diff --git a/include/sound/soc-dpcm.h b/include/sound/soc-dpcm.h
index 8060590..ffac57d 100644
--- a/include/sound/soc-dpcm.h
+++ b/include/sound/soc-dpcm.h
@@ -144,6 +144,7 @@ int dpcm_process_paths(struct snd_soc_pcm_runtime *fe,
int stream, struct snd_soc_dapm_widget_list **list, int new);
int dpcm_be_dai_startup(struct snd_soc_pcm_runtime *fe, int stream);
int dpcm_be_dai_shutdown(struct snd_soc_pcm_runtime *fe, int stream);
+void dpcm_fe_disconnect(struct snd_soc_pcm_runtime *be, int stream);
void dpcm_be_disconnect(struct snd_soc_pcm_runtime *fe, int stream);
void dpcm_clear_pending_state(struct snd_soc_pcm_runtime *fe, int stream);
int dpcm_be_dai_hw_free(struct snd_soc_pcm_runtime *fe, int stream);
diff --git a/include/sound/soc.h b/include/sound/soc.h
index 3dda0c4..44d8568 100644
--- a/include/sound/soc.h
+++ b/include/sound/soc.h
@@ -796,6 +796,8 @@ struct snd_soc_component {
unsigned int ignore_pmdown_time:1; /* pmdown_time is ignored at stop */
unsigned int registered_as_component:1;
+ /* registered dynamically in response to dynamic DAI links */
+ unsigned int dynamic_registered:1;
struct list_head list;
struct list_head list_aux; /* for auxiliary component of the card */
@@ -1682,6 +1684,10 @@ int snd_soc_add_dai_link(struct snd_soc_card *card,
void snd_soc_remove_dai_link(struct snd_soc_card *card,
struct snd_soc_dai_link *dai_link);
+int snd_soc_add_dailink(struct snd_soc_card *card,
+ struct snd_soc_dai_link *dai_link);
+void snd_soc_remove_dailink(struct snd_soc_card *card, const char *link_name);
+
int snd_soc_register_dai(struct snd_soc_component *component,
struct snd_soc_dai_driver *dai_drv);
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 7ea66ee..a8bb03c 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -22,6 +22,10 @@ menuconfig SND_SOC
if SND_SOC
+config SND_SOC_DYNAMIC_DAILINK
+ bool
+ default y
+
config SND_SOC_AC97_BUS
bool
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index 2b83814..7049f9b 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -67,6 +67,13 @@ static int pmdown_time = 5000;
module_param(pmdown_time, int, 0);
MODULE_PARM_DESC(pmdown_time, "DAPM stream powerdown time (msecs)");
+static int snd_soc_init_codec_cache(struct snd_soc_codec *codec);
+static int soc_probe_link_components(struct snd_soc_card *card,
+ struct snd_soc_pcm_runtime *rtd,
+ int order);
+static int soc_probe_link_dais(struct snd_soc_card *card,
+ struct snd_soc_pcm_runtime *rtd, int order);
+
/* returns the minimum number of bytes needed to represent
* a particular given value */
static int min_bytes_needed(unsigned long val)
@@ -1055,8 +1062,41 @@ _err_defer:
return -EPROBE_DEFER;
}
+static void soc_remove_component_controls(struct snd_soc_component *component)
+{
+ int i, ret, name_len;
+ const struct snd_kcontrol_new *controls = component->controls;
+ struct snd_card *card = component->card->snd_card;
+ const char *prefix, *long_name;
+
+ for (i = 0; i < component->num_controls; i++) {
+ const struct snd_kcontrol_new *control = &controls[i];
+ struct snd_ctl_elem_id id;
+
+ long_name = control->name;
+ prefix = component->name_prefix;
+ name_len = sizeof(id.name);
+ if (prefix)
+ snprintf(id.name, name_len, "%s %s", prefix,
+ long_name);
+ else
+ strlcpy(id.name, long_name, sizeof(id.name));
+ id.numid = 0;
+ id.iface = control->iface;
+ id.device = control->device;
+ id.subdevice = control->subdevice;
+ id.index = control->index;
+ ret = snd_ctl_remove_id_locked(card, &id);
+ if (ret < 0) {
+ dev_err(component->dev, "%d: Failed to remove %s\n",
+ ret, control->name);
+ }
+ }
+}
+
static void soc_remove_component(struct snd_soc_component *component)
{
+ struct snd_soc_dapm_context *dapm;
if (!component->card)
return;
@@ -1065,6 +1105,17 @@ static void soc_remove_component(struct snd_soc_component *component)
if (component->ref_count)
return;
+ /*
+ * should be done, only in case
+ * component probed after card instantiation
+ * assumptions:
+ * relevant DAI links are already removed
+ * mutex acquired for soc-card
+ * semaphore acquired for sound card
+ */
+ if (component->controls && component->dynamic_registered)
+ soc_remove_component_controls(component);
+
/* This is a HACK and will be removed soon */
if (component->codec)
list_del(&component->codec->card_list);
@@ -1072,9 +1123,12 @@ static void soc_remove_component(struct snd_soc_component *component)
if (component->remove)
component->remove(component);
- snd_soc_dapm_free(snd_soc_component_get_dapm(component));
+ dapm = snd_soc_component_get_dapm(component);
+ snd_soc_dapm_free(dapm);
soc_cleanup_component_debugfs(component);
+ component->dynamic_registered = 0;
+ dapm->dynamic_registered = 0;
component->card = NULL;
module_put(component->dev->driver->owner);
}
@@ -1143,6 +1197,28 @@ static void soc_remove_link_components(struct snd_soc_card *card,
}
}
+static void soc_remove_dai_link(struct snd_soc_card *card,
+ struct snd_soc_pcm_runtime *rtd)
+{
+ int order;
+ struct snd_soc_dai_link *link;
+
+ for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
+ order++)
+ soc_remove_link_dais(card, rtd, order);
+
+ for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
+ order++)
+ soc_remove_link_components(card, rtd, order);
+
+ link = rtd->dai_link;
+ if (link->dobj.type == SND_SOC_DOBJ_DAI_LINK)
+ dev_warn(card->dev, "Topology forgot to remove link %s?\n",
+ link->name);
+ list_del(&link->list);
+ card->num_dai_links--;
+}
+
static void soc_remove_dai_links(struct snd_soc_card *card)
{
int order;
@@ -1339,6 +1415,175 @@ void snd_soc_remove_dai_link(struct snd_soc_card *card,
}
EXPORT_SYMBOL_GPL(snd_soc_remove_dai_link);
+/**
+ * snd_soc_add_dailink - add DAI link to an instantiated sound card.
+ *
+ * @card: Sound card identifier to add DAI link to
+ * @dai_link: dai_link configuration to add
+ *
+ * Return 0 for success, else error.
+ */
+int snd_soc_add_dailink(struct snd_soc_card *card,
+ struct snd_soc_dai_link *dai_link)
+{
+ int ret, order;
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_codec *codec;
+ struct snd_soc_dapm_context *dapm;
+
+ if (!card)
+ return -EINVAL;
+
+ mutex_lock(&card->mutex);
+ /* init check DAI link */
+ ret = soc_init_dai_link(card, dai_link);
+ if (ret)
+ goto init_error;
+
+ /* bind DAIs */
+ ret = soc_bind_dai_link(card, dai_link);
+ if (ret)
+ goto init_error;
+ rtd = snd_soc_get_pcm_runtime(card, dai_link->name);
+
+ ret = snd_soc_add_dai_link(card, dai_link);
+ if (ret)
+ goto base_error;
+
+ if (!card->instantiated) {
+ dev_info(card->dev,
+ "ASoC: card not yet instantiated, can exit here\n");
+ mutex_unlock(&card->mutex);
+ return 0;
+ }
+
+ /* initialize the register cache for each available codec */
+ list_for_each_entry(codec, &codec_list, list) {
+ if (codec->cache_init)
+ continue;
+ ret = snd_soc_init_codec_cache(codec);
+ if (ret < 0)
+ goto probe_dai_err;
+ }
+
+ /* probe all components used by DAI link on this card */
+ for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
+ order++) {
+ ret = soc_probe_link_components(card, rtd, order);
+ if (ret < 0) {
+ dev_err(card->dev, "ASoC: failed to probe %s link components, %d\n",
+ dai_link->name, ret);
+ goto probe_dai_err;
+ }
+ }
+
+ /* Find new DAI links added during probing components and bind them.
+ * Components with topology may bring new DAIs and DAI links.
+ */
+ list_for_each_entry(dai_link, &card->dai_link_list, list) {
+ if (soc_is_dai_link_bound(card, dai_link))
+ continue;
+
+ ret = soc_init_dai_link(card, dai_link);
+ if (ret)
+ goto probe_dai_err;
+ ret = soc_bind_dai_link(card, dai_link);
+ if (ret)
+ goto probe_dai_err;
+ }
+
+
+ /* probe DAI links on this card */
+ for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
+ order++) {
+ ret = soc_probe_link_dais(card, rtd, order);
+ if (ret < 0) {
+ dev_err(card->dev, "ASoC: failed to probe %s dai link,%d\n",
+ dai_link->name, ret);
+ goto probe_dai_err;
+ }
+ }
+
+ dapm = &rtd->codec->component.dapm;
+ snd_soc_dapm_new_widgets(card);
+ snd_soc_dapm_link_dai_widgets_component(card, dapm);
+ snd_soc_dapm_connect_dai_link_widgets(card, rtd);
+ snd_device_register(rtd->card->snd_card, rtd->pcm);
+ snd_soc_dapm_sync(&card->dapm);
+ mutex_unlock(&card->mutex);
+
+ return 0;
+
+probe_dai_err:
+ soc_remove_dai_link(card, rtd);
+base_error:
+ list_del(&rtd->list);
+ card->num_rtd--;
+ soc_free_pcm_runtime(rtd);
+init_error:
+ mutex_unlock(&card->mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_add_dailink);
+
+/**
+ * snd_soc_remove_dailink - Remove DAI link from the active sound card
+ *
+ * @card_name: Sound card identifier to remove DAI link from
+ * @link_name: DAI link identfier to remove
+ */
+void snd_soc_remove_dailink(struct snd_soc_card *card, const char *link_name)
+{
+ int ret;
+ struct snd_soc_dai_link *dai_link;
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_card *sndcard = card->snd_card;
+
+ if (!card)
+ return;
+
+ rtd = snd_soc_get_pcm_runtime(card, link_name);
+ if (!rtd) {
+ dev_err(card->dev, "DAI link not found\n");
+ return;
+ }
+
+ dai_link = rtd->dai_link;
+
+ /* check if link is active */
+ if (rtd->codec_dai->active) {
+ mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
+ if (rtd->codec_dai->playback_active)
+ dpcm_dapm_stream_event(rtd, SNDRV_PCM_STREAM_PLAYBACK,
+ SND_SOC_DAPM_STREAM_STOP);
+ if (rtd->codec_dai->capture_active)
+ dpcm_dapm_stream_event(rtd, SNDRV_PCM_STREAM_CAPTURE,
+ SND_SOC_DAPM_STREAM_STOP);
+ mutex_unlock(&rtd->pcm_mutex);
+ ret = soc_dpcm_runtime_update(rtd->card);
+ }
+ cancel_delayed_work_sync(&rtd->delayed_work);
+
+ down_write(&sndcard->controls_rwsem);
+ mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_RUNTIME);
+ /* in case of BE DAI, update fe_clients list */
+ if (dai_link->no_pcm) {
+ dpcm_fe_disconnect(rtd, SNDRV_PCM_STREAM_PLAYBACK);
+ dpcm_fe_disconnect(rtd, SNDRV_PCM_STREAM_CAPTURE);
+ }
+
+ /* free associated PCM device */
+ snd_device_free(rtd->card->snd_card, rtd->pcm);
+ soc_remove_dai_link(card, rtd);
+ list_del(&rtd->list);
+ card->num_rtd--;
+ soc_free_pcm_runtime(rtd);
+
+ mutex_unlock(&card->mutex);
+ up_write(&sndcard->controls_rwsem);
+}
+EXPORT_SYMBOL_GPL(snd_soc_remove_dailink);
+
static void soc_set_name_prefix(struct snd_soc_card *card,
struct snd_soc_component *component)
{
@@ -1439,6 +1684,11 @@ static int soc_probe_component(struct snd_soc_card *card,
snd_soc_dapm_add_routes(dapm, component->dapm_routes,
component->num_dapm_routes);
+ if (card->instantiated) {
+ component->dynamic_registered = 1;
+ dapm->dynamic_registered = 1;
+ }
+
list_add(&dapm->list, &card->dapm_list);
/* This is a HACK and will be removed soon */
@@ -1968,7 +2218,17 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card)
}
snd_soc_dapm_link_dai_widgets(card);
- snd_soc_dapm_connect_dai_link_widgets(card);
+ /* for each BE DAI link... */
+ list_for_each_entry(rtd, &card->rtd_list, list) {
+ /*
+ * dynamic FE links have no fixed DAI mapping.
+ * CODEC<->CODEC links have no direct connection.
+ */
+ if (rtd->dai_link->dynamic || rtd->dai_link->params)
+ continue;
+
+ snd_soc_dapm_connect_dai_link_widgets(card, rtd);
+ }
if (card->controls)
snd_soc_add_card_controls(card, card->controls, card->num_controls);
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index 0d37079..dedc0ba 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -2272,6 +2272,34 @@ static void dapm_free_path(struct snd_soc_dapm_path *path)
kfree(path);
}
+static void snd_soc_dapm_remove_kcontrols(struct snd_soc_dapm_widget *w)
+{
+ int i, ret;
+ struct snd_kcontrol *kctl;
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_card *card = dapm->card->snd_card;
+
+ if (!w->num_kcontrols)
+ return;
+
+ for (i = 0; i < w->num_kcontrols; i++) {
+ kctl = w->kcontrols[i];
+ if (!kctl) {
+ dev_err(dapm->dev, "%s: Failed to find %d kcontrol\n",
+ w->name, i);
+ continue;
+ }
+ dev_dbg(dapm->dev, "Remove %d: %s\n", kctl->id.numid,
+ kctl->id.name);
+ ret = snd_ctl_remove_id_locked(card, &kctl->id);
+ if (ret < 0) {
+ dev_err(dapm->dev, "Err %d: while remove %s:%s\n", ret,
+ w->name, kctl->id.name);
+ }
+ }
+ w->num_kcontrols = 0;
+}
+
void snd_soc_dapm_free_widget(struct snd_soc_dapm_widget *w)
{
struct snd_soc_dapm_path *p, *next_p;
@@ -2288,6 +2316,12 @@ void snd_soc_dapm_free_widget(struct snd_soc_dapm_widget *w)
dapm_free_path(p);
}
+ /*
+ * remove associated kcontrols,
+ * in case added dynamically
+ */
+ if (w->dapm->dynamic_registered && w->num_kcontrols)
+ snd_soc_dapm_remove_kcontrols(w);
kfree(w->kcontrols);
kfree_const(w->name);
kfree(w);
@@ -3778,6 +3812,58 @@ int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm,
return 0;
}
+int snd_soc_dapm_link_dai_widgets_component(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm)
+{
+ struct snd_soc_dapm_widget *dai_w, *w;
+ struct snd_soc_dapm_widget *src, *sink;
+ struct snd_soc_dai *dai;
+
+ /* For each DAI widget... */
+ list_for_each_entry(dai_w, &card->widgets, list) {
+ if (dai_w->dapm != dapm)
+ continue;
+ switch (dai_w->id) {
+ case snd_soc_dapm_dai_in:
+ case snd_soc_dapm_dai_out:
+ break;
+ default:
+ continue;
+ }
+
+ dai = dai_w->priv;
+
+ /* ...find all widgets with the same stream and link them */
+ list_for_each_entry(w, &card->widgets, list) {
+ if (w->dapm != dai_w->dapm)
+ continue;
+
+ switch (w->id) {
+ case snd_soc_dapm_dai_in:
+ case snd_soc_dapm_dai_out:
+ continue;
+ default:
+ break;
+ }
+
+ if (!w->sname || !strstr(w->sname, dai_w->sname))
+ continue;
+
+ if (dai_w->id == snd_soc_dapm_dai_in) {
+ src = dai_w;
+ sink = w;
+ } else {
+ src = w;
+ sink = dai_w;
+ }
+ dev_dbg(dai->dev, "%s -> %s\n", src->name, sink->name);
+ snd_soc_dapm_add_path(w->dapm, src, sink, NULL, NULL);
+ }
+ }
+
+ return 0;
+}
+
int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card)
{
struct snd_soc_dapm_widget *dai_w, *w;
@@ -3827,7 +3913,7 @@ int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card)
return 0;
}
-static void dapm_connect_dai_link_widgets(struct snd_soc_card *card,
+void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card,
struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
@@ -3903,23 +3989,6 @@ static void soc_dapm_dai_stream_event(struct snd_soc_dai *dai, int stream,
}
}
-void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card)
-{
- struct snd_soc_pcm_runtime *rtd;
-
- /* for each BE DAI link... */
- list_for_each_entry(rtd, &card->rtd_list, list) {
- /*
- * dynamic FE links have no fixed DAI mapping.
- * CODEC<->CODEC links have no direct connection.
- */
- if (rtd->dai_link->dynamic || rtd->dai_link->params)
- continue;
-
- dapm_connect_dai_link_widgets(card, rtd);
- }
-}
-
static void soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, int stream,
int event)
{
diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c
index aa99dac..903cfd5 100644
--- a/sound/soc/soc-pcm.c
+++ b/sound/soc/soc-pcm.c
@@ -1192,6 +1192,31 @@ static void dpcm_be_reparent(struct snd_soc_pcm_runtime *fe,
}
/* disconnect a BE and FE */
+void dpcm_fe_disconnect(struct snd_soc_pcm_runtime *be, int stream)
+{
+ struct snd_soc_dpcm *dpcm, *d;
+
+ list_for_each_entry_safe(dpcm, d, &be->dpcm[stream].fe_clients,
+ list_fe) {
+ dev_dbg(be->dev, "ASoC: BE %s disconnect check for %s\n",
+ stream ? "capture" : "playback",
+ dpcm->fe->dai_link->name);
+
+ dev_dbg(be->dev, " freed DSP %s path %s %s %s\n",
+ stream ? "capture" : "playback", be->dai_link->name,
+ stream ? "<-" : "->", dpcm->fe->dai_link->name);
+
+#ifdef CONFIG_DEBUG_FS
+ debugfs_remove(dpcm->debugfs_state);
+#endif
+ /* FE still alive, update it's BE client list */
+ list_del(&dpcm->list_be);
+ list_del(&dpcm->list_fe);
+ kfree(dpcm);
+ }
+}
+
+/* disconnect a BE and FE */
void dpcm_be_disconnect(struct snd_soc_pcm_runtime *fe, int stream)
{
struct snd_soc_dpcm *dpcm, *d;
--
2.1.4
More information about the Alsa-devel
mailing list