// SPDX-License-Identifier: GPL-2.0 /* * Greybus Audio Sound SoC helper APIs */ #include #include #include #include "audio_helper.h" #define gbaudio_dapm_for_each_direction(dir) \ for ((dir) = SND_SOC_DAPM_DIR_IN; (dir) <= SND_SOC_DAPM_DIR_OUT; \ (dir)++) static void gbaudio_dapm_link_dai_widget(struct snd_soc_dapm_widget *dai_w, struct snd_soc_card *card) { struct snd_soc_dapm_widget *w; struct snd_soc_dapm_widget *src, *sink; struct snd_soc_dai *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; /* * check if widget is already linked, * if (w->linked) * return; */ 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); /* Add the DAPM path and set widget's linked status * snd_soc_dapm_add_path(w->dapm, src, sink, NULL, NULL); * w->linked = 1; */ } } int gbaudio_dapm_link_component_dai_widgets(struct snd_soc_card *card, struct snd_soc_dapm_context *dapm) { struct snd_soc_dapm_widget *dai_w; /* 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; } gbaudio_dapm_link_dai_widget(dai_w, card); } return 0; } static void gbaudio_dapm_free_path(struct snd_soc_dapm_path *path) { list_del(&path->list_node[SND_SOC_DAPM_DIR_IN]); list_del(&path->list_node[SND_SOC_DAPM_DIR_OUT]); list_del(&path->list_kcontrol); list_del(&path->list); kfree(path); } static void gbaudio_dapm_free_widget(struct snd_soc_dapm_widget *w) { struct snd_soc_dapm_path *p, *next_p; enum snd_soc_dapm_direction dir; list_del(&w->list); /* * remove source and sink paths associated to this widget. * While removing the path, remove reference to it from both * source and sink widgets so that path is removed only once. */ gbaudio_dapm_for_each_direction(dir) { snd_soc_dapm_widget_for_each_path_safe(w, dir, p, next_p) gbaudio_dapm_free_path(p); } kfree(w->kcontrols); kfree_const(w->name); kfree_const(w->sname); kfree(w); } int gbaudio_dapm_free_controls(struct snd_soc_dapm_context *dapm, const struct snd_soc_dapm_widget *widget, int num) { int i; struct snd_soc_dapm_widget *w, *tmp_w; mutex_lock(&dapm->card->dapm_mutex); for (i = 0; i < num; i++) { /* below logic can be optimized to identify widget pointer */ w = NULL; list_for_each_entry(tmp_w, &dapm->card->widgets, list) { if (tmp_w->dapm == dapm && !strcmp(tmp_w->name, widget->name)) { w = tmp_w; break; } } if (!w) { dev_err(dapm->dev, "%s: widget not found\n", widget->name); widget++; continue; } widget++; gbaudio_dapm_free_widget(w); } mutex_unlock(&dapm->card->dapm_mutex); return 0; } static int gbaudio_remove_controls(struct snd_card *card, struct device *dev, const struct snd_kcontrol_new *controls, int num_controls, const char *prefix) { int i, err; for (i = 0; i < num_controls; i++) { const struct snd_kcontrol_new *control = &controls[i]; struct snd_ctl_elem_id id; if (prefix) snprintf(id.name, sizeof(id.name), "%s %s", prefix, control->name); else strscpy(id.name, control->name, sizeof(id.name)); id.numid = 0; id.iface = control->iface; id.device = control->device; id.subdevice = control->subdevice; id.index = control->index; err = snd_ctl_remove_id(card, &id); if (err < 0) dev_err(dev, "%d: Failed to remove %s\n", err, control->name); } return 0; } int gbaudio_remove_component_controls(struct snd_soc_component *component, const struct snd_kcontrol_new *controls, unsigned int num_controls) { struct snd_card *card = component->card->snd_card; return gbaudio_remove_controls(card, component->dev, controls, num_controls, component->name_prefix); }