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