1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Greybus Audio Sound SoC helper APIs
4 */
5
6#include <sound/core.h>
7#include <sound/soc.h>
8#include <sound/soc-dapm.h>
9#include "audio_helper.h"
10
11#define gbaudio_dapm_for_each_direction(dir) \
12	for ((dir) = SND_SOC_DAPM_DIR_IN; (dir) <= SND_SOC_DAPM_DIR_OUT; \
13		(dir)++)
14
15static void gbaudio_dapm_link_dai_widget(struct snd_soc_dapm_widget *dai_w,
16					 struct snd_soc_card *card)
17{
18	struct snd_soc_dapm_widget *w;
19	struct snd_soc_dapm_widget *src, *sink;
20	struct snd_soc_dai *dai = dai_w->priv;
21
22	/* ...find all widgets with the same stream and link them */
23	list_for_each_entry(w, &card->widgets, list) {
24		if (w->dapm != dai_w->dapm)
25			continue;
26
27		switch (w->id) {
28		case snd_soc_dapm_dai_in:
29		case snd_soc_dapm_dai_out:
30			continue;
31		default:
32			break;
33		}
34
35		if (!w->sname || !strstr(w->sname, dai_w->sname))
36			continue;
37
38		/*
39		 * check if widget is already linked,
40		 * if (w->linked)
41		 *	return;
42		 */
43
44		if (dai_w->id == snd_soc_dapm_dai_in) {
45			src = dai_w;
46			sink = w;
47		} else {
48			src = w;
49			sink = dai_w;
50		}
51		dev_dbg(dai->dev, "%s -> %s\n", src->name, sink->name);
52		/* Add the DAPM path and set widget's linked status
53		 * snd_soc_dapm_add_path(w->dapm, src, sink, NULL, NULL);
54		 * w->linked = 1;
55		 */
56	}
57}
58
59int gbaudio_dapm_link_component_dai_widgets(struct snd_soc_card *card,
60					    struct snd_soc_dapm_context *dapm)
61{
62	struct snd_soc_dapm_widget *dai_w;
63
64	/* For each DAI widget... */
65	list_for_each_entry(dai_w, &card->widgets, list) {
66		if (dai_w->dapm != dapm)
67			continue;
68		switch (dai_w->id) {
69		case snd_soc_dapm_dai_in:
70		case snd_soc_dapm_dai_out:
71			break;
72		default:
73			continue;
74		}
75		gbaudio_dapm_link_dai_widget(dai_w, card);
76	}
77
78	return 0;
79}
80
81static void gbaudio_dapm_free_path(struct snd_soc_dapm_path *path)
82{
83	list_del(&path->list_node[SND_SOC_DAPM_DIR_IN]);
84	list_del(&path->list_node[SND_SOC_DAPM_DIR_OUT]);
85	list_del(&path->list_kcontrol);
86	list_del(&path->list);
87	kfree(path);
88}
89
90static void gbaudio_dapm_free_widget(struct snd_soc_dapm_widget *w)
91{
92	struct snd_soc_dapm_path *p, *next_p;
93	enum snd_soc_dapm_direction dir;
94
95	list_del(&w->list);
96	/*
97	 * remove source and sink paths associated to this widget.
98	 * While removing the path, remove reference to it from both
99	 * source and sink widgets so that path is removed only once.
100	 */
101	gbaudio_dapm_for_each_direction(dir) {
102		snd_soc_dapm_widget_for_each_path_safe(w, dir, p, next_p)
103			gbaudio_dapm_free_path(p);
104	}
105
106	kfree(w->kcontrols);
107	kfree_const(w->name);
108	kfree_const(w->sname);
109	kfree(w);
110}
111
112int gbaudio_dapm_free_controls(struct snd_soc_dapm_context *dapm,
113			       const struct snd_soc_dapm_widget *widget,
114			       int num)
115{
116	int i;
117	struct snd_soc_dapm_widget *w, *tmp_w;
118
119	mutex_lock(&dapm->card->dapm_mutex);
120	for (i = 0; i < num; i++) {
121		/* below logic can be optimized to identify widget pointer */
122		w = NULL;
123		list_for_each_entry(tmp_w, &dapm->card->widgets, list) {
124			if (tmp_w->dapm == dapm &&
125			    !strcmp(tmp_w->name, widget->name)) {
126				w = tmp_w;
127				break;
128			}
129		}
130		if (!w) {
131			dev_err(dapm->dev, "%s: widget not found\n",
132				widget->name);
133			widget++;
134			continue;
135		}
136		widget++;
137		gbaudio_dapm_free_widget(w);
138	}
139	mutex_unlock(&dapm->card->dapm_mutex);
140	return 0;
141}
142
143static int gbaudio_remove_controls(struct snd_card *card, struct device *dev,
144				   const struct snd_kcontrol_new *controls,
145				   int num_controls, const char *prefix)
146{
147	int i, err;
148
149	for (i = 0; i < num_controls; i++) {
150		const struct snd_kcontrol_new *control = &controls[i];
151		struct snd_ctl_elem_id id;
152
153		if (prefix)
154			snprintf(id.name, sizeof(id.name), "%s %s", prefix,
155				 control->name);
156		else
157			strscpy(id.name, control->name, sizeof(id.name));
158		id.numid = 0;
159		id.iface = control->iface;
160		id.device = control->device;
161		id.subdevice = control->subdevice;
162		id.index = control->index;
163		err = snd_ctl_remove_id(card, &id);
164		if (err < 0)
165			dev_err(dev, "%d: Failed to remove %s\n", err,
166				control->name);
167	}
168	return 0;
169}
170
171int gbaudio_remove_component_controls(struct snd_soc_component *component,
172				      const struct snd_kcontrol_new *controls,
173				      unsigned int num_controls)
174{
175	struct snd_card *card = component->card->snd_card;
176
177	return gbaudio_remove_controls(card, component->dev, controls,
178				       num_controls, component->name_prefix);
179}
180