summaryrefslogtreecommitdiff
path: root/drivers/staging/greybus/audio_helper.c
blob: 1ed4772d2771502cfd673e45d1242340794ea675 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
// SPDX-License-Identifier: GPL-2.0
/*
 * Greybus Audio Sound SoC helper APIs
 */

#include <linux/debugfs.h>
#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, *next_w;
#ifdef CONFIG_DEBUG_FS
	struct dentry *parent = dapm->debugfs_dapm;
	struct dentry *debugfs_w = NULL;
#endif

	mutex_lock(&dapm->card->dapm_mutex);
	for (i = 0; i < num; i++) {
		/* below logic can be optimized to identify widget pointer */
		list_for_each_entry_safe(w, next_w, &dapm->card->widgets,
					 list) {
			if (w->dapm != dapm)
				continue;
			if (!strcmp(w->name, widget->name))
				break;
			w = NULL;
		}
		if (!w) {
			dev_err(dapm->dev, "%s: widget not found\n",
				widget->name);
			widget++;
			continue;
		}
		widget++;
#ifdef CONFIG_DEBUG_FS
		if (!parent)
			debugfs_w = debugfs_lookup(w->name, parent);
		debugfs_remove(debugfs_w);
		debugfs_w = NULL;
#endif
		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;
		struct snd_kcontrol *kctl;

		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;
		kctl = snd_ctl_find_id(card, &id);
		if (!kctl) {
			dev_err(dev, "Failed to find %s\n", control->name);
			continue;
		}
		err = snd_ctl_remove(card, kctl);
		if (err < 0) {
			dev_err(dev, "%d: Failed to remove %s\n", err,
				control->name);
			continue;
		}
	}
	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);
}