root/drivers/staging/greybus/audio_helper.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Greybus Audio Sound SoC helper APIs
 */

#include <sound/core.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#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;
        struct snd_soc_card *card = snd_soc_dapm_to_card(dapm);

        mutex_lock(&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, &card->widgets, list) {
                        if (tmp_w->dapm == dapm &&
                            !strcmp(tmp_w->name, widget->name)) {
                                w = tmp_w;
                                break;
                        }
                }
                if (!w) {
                        dev_err(card->dev, "%s: widget not found\n",
                                widget->name);
                        widget++;
                        continue;
                }
                widget++;
                gbaudio_dapm_free_widget(w);
        }
        mutex_unlock(&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);
}