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(entry: &path->list_node[SND_SOC_DAPM_DIR_IN]);
84 list_del(entry: &path->list_node[SND_SOC_DAPM_DIR_OUT]);
85 list_del(entry: &path->list_kcontrol);
86 list_del(entry: &path->list);
87 kfree(objp: 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(entry: &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(path: p);
104 }
105
106 kfree(objp: w->kcontrols);
107 kfree_const(x: w->name);
108 kfree_const(x: w->sname);
109 kfree(objp: 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(lock: &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(buf: id.name, size: sizeof(id.name), fmt: "%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: &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, dev: component->dev, controls,
178 num_controls, prefix: component->name_prefix);
179}
180

source code of linux/drivers/staging/greybus/audio_helper.c