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 | |
15 | static 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 | |
59 | int 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 | |
81 | static 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 | |
90 | static 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 | |
112 | int 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 | |
143 | static 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 | |
171 | int 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 | |