Commit | Line | Data |
---|---|---|
f2b6a1b2 KM |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | // | |
3 | // soc-topology.c -- ALSA SoC Topology | |
4 | // | |
5 | // Copyright (C) 2012 Texas Instruments Inc. | |
6 | // Copyright (C) 2015 Intel Corporation. | |
7 | // | |
8 | // Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> | |
9 | // K, Mythri P <mythri.p.k@intel.com> | |
10 | // Prusty, Subhransu S <subhransu.s.prusty@intel.com> | |
11 | // B, Jayachandran <jayachandran.b@intel.com> | |
12 | // Abdullah, Omair M <omair.m.abdullah@intel.com> | |
13 | // Jin, Yao <yao.jin@intel.com> | |
14 | // Lin, Mengdong <mengdong.lin@intel.com> | |
15 | // | |
16 | // Add support to read audio firmware topology alongside firmware text. The | |
17 | // topology data can contain kcontrols, DAPM graphs, widgets, DAIs, DAI links, | |
18 | // equalizers, firmware, coefficients etc. | |
19 | // | |
20 | // This file only manages the core ALSA and ASoC components, all other bespoke | |
21 | // firmware topology data is passed to component drivers for bespoke handling. | |
8a978234 LG |
22 | |
23 | #include <linux/kernel.h> | |
24 | #include <linux/export.h> | |
25 | #include <linux/list.h> | |
26 | #include <linux/firmware.h> | |
27 | #include <linux/slab.h> | |
28 | #include <sound/soc.h> | |
29 | #include <sound/soc-dapm.h> | |
30 | #include <sound/soc-topology.h> | |
28a87eeb | 31 | #include <sound/tlv.h> |
8a978234 | 32 | |
2114171d PLB |
33 | #define SOC_TPLG_MAGIC_BIG_ENDIAN 0x436F5341 /* ASoC in reverse */ |
34 | ||
8a978234 LG |
35 | /* |
36 | * We make several passes over the data (since it wont necessarily be ordered) | |
37 | * and process objects in the following order. This guarantees the component | |
38 | * drivers will be ready with any vendor data before the mixers and DAPM objects | |
39 | * are loaded (that may make use of the vendor data). | |
40 | */ | |
41 | #define SOC_TPLG_PASS_MANIFEST 0 | |
42 | #define SOC_TPLG_PASS_VENDOR 1 | |
5e2cd47a | 43 | #define SOC_TPLG_PASS_CONTROL 2 |
8a978234 | 44 | #define SOC_TPLG_PASS_WIDGET 3 |
1a8e7fab ML |
45 | #define SOC_TPLG_PASS_PCM_DAI 4 |
46 | #define SOC_TPLG_PASS_GRAPH 5 | |
6257d224 AS |
47 | #define SOC_TPLG_PASS_BE_DAI 6 |
48 | #define SOC_TPLG_PASS_LINK 7 | |
8a978234 LG |
49 | |
50 | #define SOC_TPLG_PASS_START SOC_TPLG_PASS_MANIFEST | |
593d9e52 | 51 | #define SOC_TPLG_PASS_END SOC_TPLG_PASS_LINK |
8a978234 | 52 | |
583958fa | 53 | /* topology context */ |
8a978234 LG |
54 | struct soc_tplg { |
55 | const struct firmware *fw; | |
56 | ||
57 | /* runtime FW parsing */ | |
00ac8389 | 58 | const u8 *pos; /* read position */ |
8a978234 LG |
59 | const u8 *hdr_pos; /* header position */ |
60 | unsigned int pass; /* pass number */ | |
61 | ||
62 | /* component caller */ | |
63 | struct device *dev; | |
64 | struct snd_soc_component *comp; | |
65 | u32 index; /* current block index */ | |
8a978234 | 66 | |
88a17d8f | 67 | /* vendor specific kcontrol operations */ |
8a978234 LG |
68 | const struct snd_soc_tplg_kcontrol_ops *io_ops; |
69 | int io_ops_count; | |
70 | ||
1a3232d2 ML |
71 | /* vendor specific bytes ext handlers, for TLV bytes controls */ |
72 | const struct snd_soc_tplg_bytes_ext_ops *bytes_ext_ops; | |
73 | int bytes_ext_ops_count; | |
74 | ||
8a978234 LG |
75 | /* optional fw loading callbacks to component drivers */ |
76 | struct snd_soc_tplg_ops *ops; | |
77 | }; | |
78 | ||
8a978234 LG |
79 | /* check we dont overflow the data for this control chunk */ |
80 | static int soc_tplg_check_elem_count(struct soc_tplg *tplg, size_t elem_size, | |
81 | unsigned int count, size_t bytes, const char *elem_type) | |
82 | { | |
83 | const u8 *end = tplg->pos + elem_size * count; | |
84 | ||
85 | if (end > tplg->fw->data + tplg->fw->size) { | |
86 | dev_err(tplg->dev, "ASoC: %s overflow end of data\n", | |
87 | elem_type); | |
88 | return -EINVAL; | |
89 | } | |
90 | ||
91 | /* check there is enough room in chunk for control. | |
92 | extra bytes at the end of control are for vendor data here */ | |
93 | if (elem_size * count > bytes) { | |
94 | dev_err(tplg->dev, | |
95 | "ASoC: %s count %d of size %zu is bigger than chunk %zu\n", | |
96 | elem_type, count, elem_size, bytes); | |
97 | return -EINVAL; | |
98 | } | |
99 | ||
100 | return 0; | |
101 | } | |
102 | ||
4fad3cc6 | 103 | static inline bool soc_tplg_is_eof(struct soc_tplg *tplg) |
8a978234 LG |
104 | { |
105 | const u8 *end = tplg->hdr_pos; | |
106 | ||
107 | if (end >= tplg->fw->data + tplg->fw->size) | |
4fad3cc6 AS |
108 | return true; |
109 | return false; | |
8a978234 LG |
110 | } |
111 | ||
112 | static inline unsigned long soc_tplg_get_hdr_offset(struct soc_tplg *tplg) | |
113 | { | |
114 | return (unsigned long)(tplg->hdr_pos - tplg->fw->data); | |
115 | } | |
116 | ||
117 | static inline unsigned long soc_tplg_get_offset(struct soc_tplg *tplg) | |
118 | { | |
119 | return (unsigned long)(tplg->pos - tplg->fw->data); | |
120 | } | |
121 | ||
122 | /* mapping of Kcontrol types and associated operations. */ | |
123 | static const struct snd_soc_tplg_kcontrol_ops io_ops[] = { | |
124 | {SND_SOC_TPLG_CTL_VOLSW, snd_soc_get_volsw, | |
125 | snd_soc_put_volsw, snd_soc_info_volsw}, | |
126 | {SND_SOC_TPLG_CTL_VOLSW_SX, snd_soc_get_volsw_sx, | |
127 | snd_soc_put_volsw_sx, NULL}, | |
128 | {SND_SOC_TPLG_CTL_ENUM, snd_soc_get_enum_double, | |
129 | snd_soc_put_enum_double, snd_soc_info_enum_double}, | |
130 | {SND_SOC_TPLG_CTL_ENUM_VALUE, snd_soc_get_enum_double, | |
131 | snd_soc_put_enum_double, NULL}, | |
132 | {SND_SOC_TPLG_CTL_BYTES, snd_soc_bytes_get, | |
133 | snd_soc_bytes_put, snd_soc_bytes_info}, | |
134 | {SND_SOC_TPLG_CTL_RANGE, snd_soc_get_volsw_range, | |
135 | snd_soc_put_volsw_range, snd_soc_info_volsw_range}, | |
136 | {SND_SOC_TPLG_CTL_VOLSW_XR_SX, snd_soc_get_xr_sx, | |
137 | snd_soc_put_xr_sx, snd_soc_info_xr_sx}, | |
138 | {SND_SOC_TPLG_CTL_STROBE, snd_soc_get_strobe, | |
139 | snd_soc_put_strobe, NULL}, | |
140 | {SND_SOC_TPLG_DAPM_CTL_VOLSW, snd_soc_dapm_get_volsw, | |
2c57d478 | 141 | snd_soc_dapm_put_volsw, snd_soc_info_volsw}, |
8a978234 LG |
142 | {SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE, snd_soc_dapm_get_enum_double, |
143 | snd_soc_dapm_put_enum_double, snd_soc_info_enum_double}, | |
144 | {SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT, snd_soc_dapm_get_enum_double, | |
145 | snd_soc_dapm_put_enum_double, NULL}, | |
146 | {SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE, snd_soc_dapm_get_enum_double, | |
147 | snd_soc_dapm_put_enum_double, NULL}, | |
148 | {SND_SOC_TPLG_DAPM_CTL_PIN, snd_soc_dapm_get_pin_switch, | |
149 | snd_soc_dapm_put_pin_switch, snd_soc_dapm_info_pin_switch}, | |
150 | }; | |
151 | ||
152 | struct soc_tplg_map { | |
153 | int uid; | |
154 | int kid; | |
155 | }; | |
156 | ||
157 | /* mapping of widget types from UAPI IDs to kernel IDs */ | |
158 | static const struct soc_tplg_map dapm_map[] = { | |
159 | {SND_SOC_TPLG_DAPM_INPUT, snd_soc_dapm_input}, | |
160 | {SND_SOC_TPLG_DAPM_OUTPUT, snd_soc_dapm_output}, | |
161 | {SND_SOC_TPLG_DAPM_MUX, snd_soc_dapm_mux}, | |
162 | {SND_SOC_TPLG_DAPM_MIXER, snd_soc_dapm_mixer}, | |
163 | {SND_SOC_TPLG_DAPM_PGA, snd_soc_dapm_pga}, | |
164 | {SND_SOC_TPLG_DAPM_OUT_DRV, snd_soc_dapm_out_drv}, | |
165 | {SND_SOC_TPLG_DAPM_ADC, snd_soc_dapm_adc}, | |
166 | {SND_SOC_TPLG_DAPM_DAC, snd_soc_dapm_dac}, | |
167 | {SND_SOC_TPLG_DAPM_SWITCH, snd_soc_dapm_switch}, | |
168 | {SND_SOC_TPLG_DAPM_PRE, snd_soc_dapm_pre}, | |
169 | {SND_SOC_TPLG_DAPM_POST, snd_soc_dapm_post}, | |
170 | {SND_SOC_TPLG_DAPM_AIF_IN, snd_soc_dapm_aif_in}, | |
171 | {SND_SOC_TPLG_DAPM_AIF_OUT, snd_soc_dapm_aif_out}, | |
172 | {SND_SOC_TPLG_DAPM_DAI_IN, snd_soc_dapm_dai_in}, | |
173 | {SND_SOC_TPLG_DAPM_DAI_OUT, snd_soc_dapm_dai_out}, | |
174 | {SND_SOC_TPLG_DAPM_DAI_LINK, snd_soc_dapm_dai_link}, | |
8a70b454 LG |
175 | {SND_SOC_TPLG_DAPM_BUFFER, snd_soc_dapm_buffer}, |
176 | {SND_SOC_TPLG_DAPM_SCHEDULER, snd_soc_dapm_scheduler}, | |
177 | {SND_SOC_TPLG_DAPM_EFFECT, snd_soc_dapm_effect}, | |
178 | {SND_SOC_TPLG_DAPM_SIGGEN, snd_soc_dapm_siggen}, | |
179 | {SND_SOC_TPLG_DAPM_SRC, snd_soc_dapm_src}, | |
180 | {SND_SOC_TPLG_DAPM_ASRC, snd_soc_dapm_asrc}, | |
181 | {SND_SOC_TPLG_DAPM_ENCODER, snd_soc_dapm_encoder}, | |
182 | {SND_SOC_TPLG_DAPM_DECODER, snd_soc_dapm_decoder}, | |
8a978234 LG |
183 | }; |
184 | ||
8f9974d9 | 185 | static int tplg_chan_get_reg(struct soc_tplg *tplg, |
8a978234 LG |
186 | struct snd_soc_tplg_channel *chan, int map) |
187 | { | |
188 | int i; | |
189 | ||
190 | for (i = 0; i < SND_SOC_TPLG_MAX_CHAN; i++) { | |
5aebe7c7 PLB |
191 | if (le32_to_cpu(chan[i].id) == map) |
192 | return le32_to_cpu(chan[i].reg); | |
8a978234 LG |
193 | } |
194 | ||
195 | return -EINVAL; | |
196 | } | |
197 | ||
8f9974d9 | 198 | static int tplg_chan_get_shift(struct soc_tplg *tplg, |
8a978234 LG |
199 | struct snd_soc_tplg_channel *chan, int map) |
200 | { | |
201 | int i; | |
202 | ||
203 | for (i = 0; i < SND_SOC_TPLG_MAX_CHAN; i++) { | |
5aebe7c7 PLB |
204 | if (le32_to_cpu(chan[i].id) == map) |
205 | return le32_to_cpu(chan[i].shift); | |
8a978234 LG |
206 | } |
207 | ||
208 | return -EINVAL; | |
209 | } | |
210 | ||
211 | static int get_widget_id(int tplg_type) | |
212 | { | |
213 | int i; | |
214 | ||
215 | for (i = 0; i < ARRAY_SIZE(dapm_map); i++) { | |
216 | if (tplg_type == dapm_map[i].uid) | |
217 | return dapm_map[i].kid; | |
218 | } | |
219 | ||
220 | return -EINVAL; | |
221 | } | |
222 | ||
8a978234 LG |
223 | static inline void soc_bind_err(struct soc_tplg *tplg, |
224 | struct snd_soc_tplg_ctl_hdr *hdr, int index) | |
225 | { | |
226 | dev_err(tplg->dev, | |
227 | "ASoC: invalid control type (g,p,i) %d:%d:%d index %d at 0x%lx\n", | |
228 | hdr->ops.get, hdr->ops.put, hdr->ops.info, index, | |
229 | soc_tplg_get_offset(tplg)); | |
230 | } | |
231 | ||
232 | static inline void soc_control_err(struct soc_tplg *tplg, | |
233 | struct snd_soc_tplg_ctl_hdr *hdr, const char *name) | |
234 | { | |
235 | dev_err(tplg->dev, | |
34b31045 | 236 | "ASoC: no complete control IO handler for %s type (g,p,i) %d:%d:%d at 0x%lx\n", |
8a978234 LG |
237 | name, hdr->ops.get, hdr->ops.put, hdr->ops.info, |
238 | soc_tplg_get_offset(tplg)); | |
239 | } | |
240 | ||
241 | /* pass vendor data to component driver for processing */ | |
c2cbd0a7 KJ |
242 | static int soc_tplg_vendor_load(struct soc_tplg *tplg, |
243 | struct snd_soc_tplg_hdr *hdr) | |
8a978234 LG |
244 | { |
245 | int ret = 0; | |
246 | ||
c42464a4 | 247 | if (tplg->ops && tplg->ops->vendor_load) |
c60b613a | 248 | ret = tplg->ops->vendor_load(tplg->comp, tplg->index, hdr); |
8a978234 LG |
249 | else { |
250 | dev_err(tplg->dev, "ASoC: no vendor load callback for ID %d\n", | |
251 | hdr->vendor_type); | |
252 | return -EINVAL; | |
253 | } | |
254 | ||
255 | if (ret < 0) | |
256 | dev_err(tplg->dev, | |
257 | "ASoC: vendor load failed at hdr offset %ld/0x%lx for type %d:%d\n", | |
258 | soc_tplg_get_hdr_offset(tplg), | |
259 | soc_tplg_get_hdr_offset(tplg), | |
260 | hdr->type, hdr->vendor_type); | |
261 | return ret; | |
262 | } | |
263 | ||
8a978234 LG |
264 | /* optionally pass new dynamic widget to component driver. This is mainly for |
265 | * external widgets where we can assign private data/ops */ | |
266 | static int soc_tplg_widget_load(struct soc_tplg *tplg, | |
267 | struct snd_soc_dapm_widget *w, struct snd_soc_tplg_dapm_widget *tplg_w) | |
268 | { | |
c42464a4 | 269 | if (tplg->ops && tplg->ops->widget_load) |
c60b613a LG |
270 | return tplg->ops->widget_load(tplg->comp, tplg->index, w, |
271 | tplg_w); | |
8a978234 LG |
272 | |
273 | return 0; | |
274 | } | |
275 | ||
ebd259d3 LG |
276 | /* optionally pass new dynamic widget to component driver. This is mainly for |
277 | * external widgets where we can assign private data/ops */ | |
278 | static int soc_tplg_widget_ready(struct soc_tplg *tplg, | |
279 | struct snd_soc_dapm_widget *w, struct snd_soc_tplg_dapm_widget *tplg_w) | |
280 | { | |
c42464a4 | 281 | if (tplg->ops && tplg->ops->widget_ready) |
c60b613a LG |
282 | return tplg->ops->widget_ready(tplg->comp, tplg->index, w, |
283 | tplg_w); | |
ebd259d3 LG |
284 | |
285 | return 0; | |
286 | } | |
287 | ||
183b8021 | 288 | /* pass DAI configurations to component driver for extra initialization */ |
64527e8a | 289 | static int soc_tplg_dai_load(struct soc_tplg *tplg, |
c60b613a LG |
290 | struct snd_soc_dai_driver *dai_drv, |
291 | struct snd_soc_tplg_pcm *pcm, struct snd_soc_dai *dai) | |
8a978234 | 292 | { |
c42464a4 | 293 | if (tplg->ops && tplg->ops->dai_load) |
c60b613a LG |
294 | return tplg->ops->dai_load(tplg->comp, tplg->index, dai_drv, |
295 | pcm, dai); | |
8a978234 LG |
296 | |
297 | return 0; | |
298 | } | |
299 | ||
183b8021 | 300 | /* pass link configurations to component driver for extra initialization */ |
acfc7d46 | 301 | static int soc_tplg_dai_link_load(struct soc_tplg *tplg, |
c60b613a | 302 | struct snd_soc_dai_link *link, struct snd_soc_tplg_link_config *cfg) |
acfc7d46 | 303 | { |
c42464a4 | 304 | if (tplg->ops && tplg->ops->link_load) |
c60b613a | 305 | return tplg->ops->link_load(tplg->comp, tplg->index, link, cfg); |
acfc7d46 ML |
306 | |
307 | return 0; | |
308 | } | |
309 | ||
8a978234 | 310 | /* tell the component driver that all firmware has been loaded in this request */ |
415717e1 | 311 | static int soc_tplg_complete(struct soc_tplg *tplg) |
8a978234 | 312 | { |
c42464a4 | 313 | if (tplg->ops && tplg->ops->complete) |
415717e1 RS |
314 | return tplg->ops->complete(tplg->comp); |
315 | ||
316 | return 0; | |
8a978234 LG |
317 | } |
318 | ||
319 | /* add a dynamic kcontrol */ | |
320 | static int soc_tplg_add_dcontrol(struct snd_card *card, struct device *dev, | |
321 | const struct snd_kcontrol_new *control_new, const char *prefix, | |
322 | void *data, struct snd_kcontrol **kcontrol) | |
323 | { | |
324 | int err; | |
325 | ||
326 | *kcontrol = snd_soc_cnew(control_new, data, control_new->name, prefix); | |
327 | if (*kcontrol == NULL) { | |
328 | dev_err(dev, "ASoC: Failed to create new kcontrol %s\n", | |
329 | control_new->name); | |
330 | return -ENOMEM; | |
331 | } | |
332 | ||
333 | err = snd_ctl_add(card, *kcontrol); | |
334 | if (err < 0) { | |
335 | dev_err(dev, "ASoC: Failed to add %s: %d\n", | |
336 | control_new->name, err); | |
337 | return err; | |
338 | } | |
339 | ||
340 | return 0; | |
341 | } | |
342 | ||
343 | /* add a dynamic kcontrol for component driver */ | |
344 | static int soc_tplg_add_kcontrol(struct soc_tplg *tplg, | |
345 | struct snd_kcontrol_new *k, struct snd_kcontrol **kcontrol) | |
346 | { | |
347 | struct snd_soc_component *comp = tplg->comp; | |
348 | ||
349 | return soc_tplg_add_dcontrol(comp->card->snd_card, | |
2a710bb3 | 350 | tplg->dev, k, comp->name_prefix, comp, kcontrol); |
8a978234 LG |
351 | } |
352 | ||
353 | /* remove a mixer kcontrol */ | |
2abfd4bd | 354 | static void soc_tplg_remove_mixer(struct snd_soc_component *comp, |
8a978234 LG |
355 | struct snd_soc_dobj *dobj, int pass) |
356 | { | |
357 | struct snd_card *card = comp->card->snd_card; | |
8a978234 | 358 | |
5e2cd47a | 359 | if (pass != SOC_TPLG_PASS_CONTROL) |
8a978234 LG |
360 | return; |
361 | ||
362 | if (dobj->ops && dobj->ops->control_unload) | |
363 | dobj->ops->control_unload(comp, dobj); | |
364 | ||
33ae6ae2 AS |
365 | snd_ctl_remove(card, dobj->control.kcontrol); |
366 | list_del(&dobj->list); | |
8a978234 LG |
367 | } |
368 | ||
369 | /* remove an enum kcontrol */ | |
2abfd4bd | 370 | static void soc_tplg_remove_enum(struct snd_soc_component *comp, |
8a978234 LG |
371 | struct snd_soc_dobj *dobj, int pass) |
372 | { | |
373 | struct snd_card *card = comp->card->snd_card; | |
8a978234 | 374 | |
5e2cd47a | 375 | if (pass != SOC_TPLG_PASS_CONTROL) |
8a978234 LG |
376 | return; |
377 | ||
378 | if (dobj->ops && dobj->ops->control_unload) | |
379 | dobj->ops->control_unload(comp, dobj); | |
380 | ||
33ae6ae2 AS |
381 | snd_ctl_remove(card, dobj->control.kcontrol); |
382 | list_del(&dobj->list); | |
8a978234 LG |
383 | } |
384 | ||
385 | /* remove a byte kcontrol */ | |
2abfd4bd | 386 | static void soc_tplg_remove_bytes(struct snd_soc_component *comp, |
8a978234 LG |
387 | struct snd_soc_dobj *dobj, int pass) |
388 | { | |
389 | struct snd_card *card = comp->card->snd_card; | |
8a978234 | 390 | |
5e2cd47a | 391 | if (pass != SOC_TPLG_PASS_CONTROL) |
8a978234 LG |
392 | return; |
393 | ||
394 | if (dobj->ops && dobj->ops->control_unload) | |
395 | dobj->ops->control_unload(comp, dobj); | |
396 | ||
33ae6ae2 AS |
397 | snd_ctl_remove(card, dobj->control.kcontrol); |
398 | list_del(&dobj->list); | |
8a978234 LG |
399 | } |
400 | ||
7df04ea7 | 401 | /* remove a route */ |
2abfd4bd | 402 | static void soc_tplg_remove_route(struct snd_soc_component *comp, |
7df04ea7 RS |
403 | struct snd_soc_dobj *dobj, int pass) |
404 | { | |
7df04ea7 RS |
405 | if (pass != SOC_TPLG_PASS_GRAPH) |
406 | return; | |
407 | ||
408 | if (dobj->ops && dobj->ops->dapm_route_unload) | |
409 | dobj->ops->dapm_route_unload(comp, dobj); | |
410 | ||
411 | list_del(&dobj->list); | |
7df04ea7 RS |
412 | } |
413 | ||
8a978234 | 414 | /* remove a widget and it's kcontrols - routes must be removed first */ |
2abfd4bd | 415 | static void soc_tplg_remove_widget(struct snd_soc_component *comp, |
8a978234 LG |
416 | struct snd_soc_dobj *dobj, int pass) |
417 | { | |
418 | struct snd_card *card = comp->card->snd_card; | |
419 | struct snd_soc_dapm_widget *w = | |
420 | container_of(dobj, struct snd_soc_dapm_widget, dobj); | |
421 | int i; | |
422 | ||
423 | if (pass != SOC_TPLG_PASS_WIDGET) | |
424 | return; | |
425 | ||
426 | if (dobj->ops && dobj->ops->widget_unload) | |
427 | dobj->ops->widget_unload(comp, dobj); | |
428 | ||
05bdcf12 LG |
429 | if (!w->kcontrols) |
430 | goto free_news; | |
431 | ||
8d456654 AS |
432 | for (i = 0; w->kcontrols && i < w->num_kcontrols; i++) |
433 | snd_ctl_remove(card, w->kcontrols[i]); | |
05bdcf12 LG |
434 | |
435 | free_news: | |
05bdcf12 | 436 | |
a46e8393 AS |
437 | list_del(&dobj->list); |
438 | ||
8a978234 LG |
439 | /* widget w is freed by soc-dapm.c */ |
440 | } | |
441 | ||
64527e8a | 442 | /* remove DAI configurations */ |
2abfd4bd | 443 | static void soc_tplg_remove_dai(struct snd_soc_component *comp, |
8a978234 LG |
444 | struct snd_soc_dobj *dobj, int pass) |
445 | { | |
64527e8a ML |
446 | struct snd_soc_dai_driver *dai_drv = |
447 | container_of(dobj, struct snd_soc_dai_driver, dobj); | |
fc4cb1e1 | 448 | struct snd_soc_dai *dai, *_dai; |
64527e8a | 449 | |
8a978234 LG |
450 | if (pass != SOC_TPLG_PASS_PCM_DAI) |
451 | return; | |
452 | ||
64527e8a ML |
453 | if (dobj->ops && dobj->ops->dai_unload) |
454 | dobj->ops->dai_unload(comp, dobj); | |
8a978234 | 455 | |
fc4cb1e1 | 456 | for_each_component_dais_safe(comp, dai, _dai) |
52abe6cc | 457 | if (dai->driver == dai_drv) |
fc4cb1e1 | 458 | snd_soc_unregister_dai(dai); |
52abe6cc | 459 | |
8a978234 | 460 | list_del(&dobj->list); |
8a978234 LG |
461 | } |
462 | ||
acfc7d46 | 463 | /* remove link configurations */ |
2abfd4bd | 464 | static void soc_tplg_remove_link(struct snd_soc_component *comp, |
acfc7d46 ML |
465 | struct snd_soc_dobj *dobj, int pass) |
466 | { | |
467 | struct snd_soc_dai_link *link = | |
468 | container_of(dobj, struct snd_soc_dai_link, dobj); | |
469 | ||
470 | if (pass != SOC_TPLG_PASS_PCM_DAI) | |
471 | return; | |
472 | ||
473 | if (dobj->ops && dobj->ops->link_unload) | |
474 | dobj->ops->link_unload(comp, dobj); | |
475 | ||
dd836ddf | 476 | list_del(&dobj->list); |
50cd9b53 KM |
477 | snd_soc_remove_pcm_runtime(comp->card, |
478 | snd_soc_get_pcm_runtime(comp->card, link)); | |
acfc7d46 ML |
479 | } |
480 | ||
adfebb51 B |
481 | /* unload dai link */ |
482 | static void remove_backend_link(struct snd_soc_component *comp, | |
483 | struct snd_soc_dobj *dobj, int pass) | |
484 | { | |
485 | if (pass != SOC_TPLG_PASS_LINK) | |
486 | return; | |
487 | ||
488 | if (dobj->ops && dobj->ops->link_unload) | |
489 | dobj->ops->link_unload(comp, dobj); | |
490 | ||
491 | /* | |
2abfd4bd | 492 | * We don't free the link here as what soc_tplg_remove_link() do since BE |
adfebb51 B |
493 | * links are not allocated by topology. |
494 | * We however need to reset the dobj type to its initial values | |
495 | */ | |
496 | dobj->type = SND_SOC_DOBJ_NONE; | |
497 | list_del(&dobj->list); | |
498 | } | |
499 | ||
8a978234 LG |
500 | /* bind a kcontrol to it's IO handlers */ |
501 | static int soc_tplg_kcontrol_bind_io(struct snd_soc_tplg_ctl_hdr *hdr, | |
502 | struct snd_kcontrol_new *k, | |
2b5cdb91 | 503 | const struct soc_tplg *tplg) |
8a978234 | 504 | { |
2b5cdb91 | 505 | const struct snd_soc_tplg_kcontrol_ops *ops; |
1a3232d2 | 506 | const struct snd_soc_tplg_bytes_ext_ops *ext_ops; |
2b5cdb91 | 507 | int num_ops, i; |
8a978234 | 508 | |
5aebe7c7 | 509 | if (le32_to_cpu(hdr->ops.info) == SND_SOC_TPLG_CTL_BYTES |
1a3232d2 | 510 | && k->iface & SNDRV_CTL_ELEM_IFACE_MIXER |
feb00b73 AS |
511 | && (k->access & SNDRV_CTL_ELEM_ACCESS_TLV_READ |
512 | || k->access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE) | |
1a3232d2 ML |
513 | && k->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) { |
514 | struct soc_bytes_ext *sbe; | |
515 | struct snd_soc_tplg_bytes_control *be; | |
516 | ||
517 | sbe = (struct soc_bytes_ext *)k->private_value; | |
518 | be = container_of(hdr, struct snd_soc_tplg_bytes_control, hdr); | |
519 | ||
520 | /* TLV bytes controls need standard kcontrol info handler, | |
521 | * TLV callback and extended put/get handlers. | |
522 | */ | |
f4be978b | 523 | k->info = snd_soc_bytes_info_ext; |
1a3232d2 ML |
524 | k->tlv.c = snd_soc_bytes_tlv_callback; |
525 | ||
6788fc1a PLB |
526 | /* |
527 | * When a topology-based implementation abuses the | |
528 | * control interface and uses bytes_ext controls of | |
529 | * more than 512 bytes, we need to disable the size | |
530 | * checks, otherwise accesses to such controls will | |
531 | * return an -EINVAL error and prevent the card from | |
532 | * being configured. | |
533 | */ | |
2c7463d0 | 534 | if (sbe->max > 512) |
6788fc1a PLB |
535 | k->access |= SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK; |
536 | ||
1a3232d2 ML |
537 | ext_ops = tplg->bytes_ext_ops; |
538 | num_ops = tplg->bytes_ext_ops_count; | |
539 | for (i = 0; i < num_ops; i++) { | |
72bbeda0 PLB |
540 | if (!sbe->put && |
541 | ext_ops[i].id == le32_to_cpu(be->ext_ops.put)) | |
1a3232d2 | 542 | sbe->put = ext_ops[i].put; |
72bbeda0 PLB |
543 | if (!sbe->get && |
544 | ext_ops[i].id == le32_to_cpu(be->ext_ops.get)) | |
1a3232d2 ML |
545 | sbe->get = ext_ops[i].get; |
546 | } | |
547 | ||
819b9f60 | 548 | if ((k->access & SNDRV_CTL_ELEM_ACCESS_TLV_READ) && !sbe->get) |
1a3232d2 | 549 | return -EINVAL; |
819b9f60 D |
550 | if ((k->access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE) && !sbe->put) |
551 | return -EINVAL; | |
552 | return 0; | |
1a3232d2 | 553 | } |
8a978234 | 554 | |
88a17d8f | 555 | /* try and map vendor specific kcontrol handlers first */ |
2b5cdb91 ML |
556 | ops = tplg->io_ops; |
557 | num_ops = tplg->io_ops_count; | |
8a978234 LG |
558 | for (i = 0; i < num_ops; i++) { |
559 | ||
72bbeda0 | 560 | if (k->put == NULL && ops[i].id == le32_to_cpu(hdr->ops.put)) |
8a978234 | 561 | k->put = ops[i].put; |
72bbeda0 | 562 | if (k->get == NULL && ops[i].id == le32_to_cpu(hdr->ops.get)) |
8a978234 | 563 | k->get = ops[i].get; |
72bbeda0 | 564 | if (k->info == NULL && ops[i].id == le32_to_cpu(hdr->ops.info)) |
8a978234 LG |
565 | k->info = ops[i].info; |
566 | } | |
567 | ||
88a17d8f | 568 | /* vendor specific handlers found ? */ |
8a978234 LG |
569 | if (k->put && k->get && k->info) |
570 | return 0; | |
571 | ||
88a17d8f | 572 | /* none found so try standard kcontrol handlers */ |
2b5cdb91 ML |
573 | ops = io_ops; |
574 | num_ops = ARRAY_SIZE(io_ops); | |
88a17d8f | 575 | for (i = 0; i < num_ops; i++) { |
8a978234 | 576 | |
72bbeda0 | 577 | if (k->put == NULL && ops[i].id == le32_to_cpu(hdr->ops.put)) |
88a17d8f | 578 | k->put = ops[i].put; |
72bbeda0 | 579 | if (k->get == NULL && ops[i].id == le32_to_cpu(hdr->ops.get)) |
88a17d8f | 580 | k->get = ops[i].get; |
72bbeda0 | 581 | if (k->info == NULL && ops[i].id == le32_to_cpu(hdr->ops.info)) |
88a17d8f | 582 | k->info = ops[i].info; |
8a978234 LG |
583 | } |
584 | ||
88a17d8f | 585 | /* standard handlers found ? */ |
8a978234 LG |
586 | if (k->put && k->get && k->info) |
587 | return 0; | |
588 | ||
589 | /* nothing to bind */ | |
590 | return -EINVAL; | |
591 | } | |
592 | ||
593 | /* bind a widgets to it's evnt handlers */ | |
594 | int snd_soc_tplg_widget_bind_event(struct snd_soc_dapm_widget *w, | |
595 | const struct snd_soc_tplg_widget_events *events, | |
596 | int num_events, u16 event_type) | |
597 | { | |
598 | int i; | |
599 | ||
600 | w->event = NULL; | |
601 | ||
602 | for (i = 0; i < num_events; i++) { | |
603 | if (event_type == events[i].type) { | |
604 | ||
605 | /* found - so assign event */ | |
606 | w->event = events[i].event_handler; | |
607 | return 0; | |
608 | } | |
609 | } | |
610 | ||
611 | /* not found */ | |
612 | return -EINVAL; | |
613 | } | |
614 | EXPORT_SYMBOL_GPL(snd_soc_tplg_widget_bind_event); | |
615 | ||
616 | /* optionally pass new dynamic kcontrol to component driver. */ | |
430791dd | 617 | static int soc_tplg_control_load(struct soc_tplg *tplg, |
8a978234 LG |
618 | struct snd_kcontrol_new *k, struct snd_soc_tplg_ctl_hdr *hdr) |
619 | { | |
c42464a4 | 620 | if (tplg->ops && tplg->ops->control_load) |
c60b613a LG |
621 | return tplg->ops->control_load(tplg->comp, tplg->index, k, |
622 | hdr); | |
8a978234 LG |
623 | |
624 | return 0; | |
625 | } | |
626 | ||
8a978234 | 627 | |
28a87eeb ML |
628 | static int soc_tplg_create_tlv_db_scale(struct soc_tplg *tplg, |
629 | struct snd_kcontrol_new *kc, struct snd_soc_tplg_tlv_dbscale *scale) | |
630 | { | |
631 | unsigned int item_len = 2 * sizeof(unsigned int); | |
632 | unsigned int *p; | |
8a978234 | 633 | |
ff922622 | 634 | p = devm_kzalloc(tplg->dev, item_len + 2 * sizeof(unsigned int), GFP_KERNEL); |
28a87eeb | 635 | if (!p) |
8a978234 LG |
636 | return -ENOMEM; |
637 | ||
28a87eeb ML |
638 | p[0] = SNDRV_CTL_TLVT_DB_SCALE; |
639 | p[1] = item_len; | |
5aebe7c7 PLB |
640 | p[2] = le32_to_cpu(scale->min); |
641 | p[3] = (le32_to_cpu(scale->step) & TLV_DB_SCALE_MASK) | |
642 | | (le32_to_cpu(scale->mute) ? TLV_DB_SCALE_MUTE : 0); | |
28a87eeb ML |
643 | |
644 | kc->tlv.p = (void *)p; | |
645 | return 0; | |
646 | } | |
647 | ||
648 | static int soc_tplg_create_tlv(struct soc_tplg *tplg, | |
649 | struct snd_kcontrol_new *kc, struct snd_soc_tplg_ctl_hdr *tc) | |
650 | { | |
651 | struct snd_soc_tplg_ctl_tlv *tplg_tlv; | |
5aebe7c7 | 652 | u32 access = le32_to_cpu(tc->access); |
28a87eeb | 653 | |
5aebe7c7 | 654 | if (!(access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE)) |
28a87eeb | 655 | return 0; |
8a978234 | 656 | |
5aebe7c7 | 657 | if (!(access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK)) { |
28a87eeb | 658 | tplg_tlv = &tc->tlv; |
5aebe7c7 | 659 | switch (le32_to_cpu(tplg_tlv->type)) { |
28a87eeb ML |
660 | case SNDRV_CTL_TLVT_DB_SCALE: |
661 | return soc_tplg_create_tlv_db_scale(tplg, kc, | |
662 | &tplg_tlv->scale); | |
663 | ||
664 | /* TODO: add support for other TLV types */ | |
665 | default: | |
666 | dev_dbg(tplg->dev, "Unsupported TLV type %d\n", | |
667 | tplg_tlv->type); | |
668 | return -EINVAL; | |
669 | } | |
670 | } | |
8a978234 LG |
671 | |
672 | return 0; | |
673 | } | |
674 | ||
0db627c4 | 675 | static int soc_tplg_dbytes_create(struct soc_tplg *tplg, size_t size) |
8a978234 LG |
676 | { |
677 | struct snd_soc_tplg_bytes_control *be; | |
678 | struct soc_bytes_ext *sbe; | |
679 | struct snd_kcontrol_new kc; | |
0db627c4 | 680 | int ret = 0; |
8a978234 LG |
681 | |
682 | if (soc_tplg_check_elem_count(tplg, | |
3ce57f22 | 683 | sizeof(struct snd_soc_tplg_bytes_control), |
0db627c4 | 684 | 1, size, "mixer bytes")) |
8a978234 | 685 | return -EINVAL; |
8a978234 | 686 | |
0db627c4 | 687 | be = (struct snd_soc_tplg_bytes_control *)tplg->pos; |
8a978234 | 688 | |
0db627c4 AS |
689 | /* validate kcontrol */ |
690 | if (strnlen(be->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == | |
691 | SNDRV_CTL_ELEM_ID_NAME_MAXLEN) | |
692 | return -EINVAL; | |
8a978234 | 693 | |
0db627c4 AS |
694 | sbe = devm_kzalloc(tplg->dev, sizeof(*sbe), GFP_KERNEL); |
695 | if (sbe == NULL) | |
696 | return -ENOMEM; | |
8a978234 | 697 | |
0db627c4 AS |
698 | tplg->pos += (sizeof(struct snd_soc_tplg_bytes_control) + |
699 | le32_to_cpu(be->priv.size)); | |
700 | ||
701 | dev_dbg(tplg->dev, | |
702 | "ASoC: adding bytes kcontrol %s with access 0x%x\n", | |
703 | be->hdr.name, be->hdr.access); | |
704 | ||
705 | memset(&kc, 0, sizeof(kc)); | |
706 | kc.name = be->hdr.name; | |
707 | kc.private_value = (long)sbe; | |
708 | kc.iface = SNDRV_CTL_ELEM_IFACE_MIXER; | |
709 | kc.access = le32_to_cpu(be->hdr.access); | |
710 | ||
711 | sbe->max = le32_to_cpu(be->max); | |
712 | sbe->dobj.type = SND_SOC_DOBJ_BYTES; | |
713 | sbe->dobj.ops = tplg->ops; | |
714 | INIT_LIST_HEAD(&sbe->dobj.list); | |
715 | ||
716 | /* map io handlers */ | |
717 | ret = soc_tplg_kcontrol_bind_io(&be->hdr, &kc, tplg); | |
718 | if (ret) { | |
719 | soc_control_err(tplg, &be->hdr, be->hdr.name); | |
720 | goto err; | |
721 | } | |
8a978234 | 722 | |
0db627c4 | 723 | /* pass control to driver for optional further init */ |
430791dd | 724 | ret = soc_tplg_control_load(tplg, &kc, (struct snd_soc_tplg_ctl_hdr *)be); |
0db627c4 AS |
725 | if (ret < 0) { |
726 | dev_err(tplg->dev, "ASoC: failed to init %s\n", be->hdr.name); | |
727 | goto err; | |
728 | } | |
8a978234 | 729 | |
0db627c4 AS |
730 | /* register control here */ |
731 | ret = soc_tplg_add_kcontrol(tplg, &kc, &sbe->dobj.control.kcontrol); | |
732 | if (ret < 0) { | |
733 | dev_err(tplg->dev, "ASoC: failed to add %s\n", be->hdr.name); | |
734 | goto err; | |
8a978234 | 735 | } |
8a978234 | 736 | |
0db627c4 AS |
737 | list_add(&sbe->dobj.list, &tplg->comp->dobj_list); |
738 | ||
739 | err: | |
740 | return ret; | |
8a978234 LG |
741 | } |
742 | ||
0db627c4 | 743 | static int soc_tplg_dmixer_create(struct soc_tplg *tplg, size_t size) |
8a978234 LG |
744 | { |
745 | struct snd_soc_tplg_mixer_control *mc; | |
746 | struct soc_mixer_control *sm; | |
747 | struct snd_kcontrol_new kc; | |
0db627c4 | 748 | int ret = 0; |
8a978234 LG |
749 | |
750 | if (soc_tplg_check_elem_count(tplg, | |
3ce57f22 | 751 | sizeof(struct snd_soc_tplg_mixer_control), |
0db627c4 | 752 | 1, size, "mixers")) |
8a978234 | 753 | return -EINVAL; |
8a978234 | 754 | |
0db627c4 | 755 | mc = (struct snd_soc_tplg_mixer_control *)tplg->pos; |
8a978234 | 756 | |
0db627c4 AS |
757 | /* validate kcontrol */ |
758 | if (strnlen(mc->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == | |
759 | SNDRV_CTL_ELEM_ID_NAME_MAXLEN) | |
760 | return -EINVAL; | |
8a978234 | 761 | |
0db627c4 AS |
762 | sm = devm_kzalloc(tplg->dev, sizeof(*sm), GFP_KERNEL); |
763 | if (sm == NULL) | |
764 | return -ENOMEM; | |
765 | tplg->pos += (sizeof(struct snd_soc_tplg_mixer_control) + | |
766 | le32_to_cpu(mc->priv.size)); | |
767 | ||
768 | dev_dbg(tplg->dev, | |
769 | "ASoC: adding mixer kcontrol %s with access 0x%x\n", | |
770 | mc->hdr.name, mc->hdr.access); | |
771 | ||
772 | memset(&kc, 0, sizeof(kc)); | |
773 | kc.name = mc->hdr.name; | |
774 | kc.private_value = (long)sm; | |
775 | kc.iface = SNDRV_CTL_ELEM_IFACE_MIXER; | |
776 | kc.access = le32_to_cpu(mc->hdr.access); | |
777 | ||
778 | /* we only support FL/FR channel mapping atm */ | |
8f9974d9 AS |
779 | sm->reg = tplg_chan_get_reg(tplg, mc->channel, SNDRV_CHMAP_FL); |
780 | sm->rreg = tplg_chan_get_reg(tplg, mc->channel, SNDRV_CHMAP_FR); | |
781 | sm->shift = tplg_chan_get_shift(tplg, mc->channel, SNDRV_CHMAP_FL); | |
782 | sm->rshift = tplg_chan_get_shift(tplg, mc->channel, SNDRV_CHMAP_FR); | |
0db627c4 AS |
783 | |
784 | sm->max = le32_to_cpu(mc->max); | |
785 | sm->min = le32_to_cpu(mc->min); | |
786 | sm->invert = le32_to_cpu(mc->invert); | |
787 | sm->platform_max = le32_to_cpu(mc->platform_max); | |
788 | sm->dobj.index = tplg->index; | |
789 | sm->dobj.ops = tplg->ops; | |
790 | sm->dobj.type = SND_SOC_DOBJ_MIXER; | |
791 | INIT_LIST_HEAD(&sm->dobj.list); | |
792 | ||
793 | /* map io handlers */ | |
794 | ret = soc_tplg_kcontrol_bind_io(&mc->hdr, &kc, tplg); | |
795 | if (ret) { | |
796 | soc_control_err(tplg, &mc->hdr, mc->hdr.name); | |
797 | goto err; | |
798 | } | |
3789debf | 799 | |
0db627c4 AS |
800 | /* create any TLV data */ |
801 | ret = soc_tplg_create_tlv(tplg, &kc, &mc->hdr); | |
802 | if (ret < 0) { | |
803 | dev_err(tplg->dev, "ASoC: failed to create TLV %s\n", mc->hdr.name); | |
804 | goto err; | |
805 | } | |
8a978234 | 806 | |
0db627c4 | 807 | /* pass control to driver for optional further init */ |
430791dd | 808 | ret = soc_tplg_control_load(tplg, &kc, (struct snd_soc_tplg_ctl_hdr *)mc); |
0db627c4 AS |
809 | if (ret < 0) { |
810 | dev_err(tplg->dev, "ASoC: failed to init %s\n", mc->hdr.name); | |
811 | goto err; | |
812 | } | |
8a978234 | 813 | |
0db627c4 AS |
814 | /* register control here */ |
815 | ret = soc_tplg_add_kcontrol(tplg, &kc, &sm->dobj.control.kcontrol); | |
816 | if (ret < 0) { | |
817 | dev_err(tplg->dev, "ASoC: failed to add %s\n", mc->hdr.name); | |
818 | goto err; | |
8a978234 LG |
819 | } |
820 | ||
0db627c4 AS |
821 | list_add(&sm->dobj.list, &tplg->comp->dobj_list); |
822 | ||
823 | err: | |
824 | return ret; | |
8a978234 LG |
825 | } |
826 | ||
ff922622 AS |
827 | static int soc_tplg_denum_create_texts(struct soc_tplg *tplg, struct soc_enum *se, |
828 | struct snd_soc_tplg_enum_control *ec) | |
8a978234 LG |
829 | { |
830 | int i, ret; | |
831 | ||
f5824e5c AS |
832 | if (le32_to_cpu(ec->items) > ARRAY_SIZE(ec->texts)) |
833 | return -EINVAL; | |
834 | ||
8a978234 | 835 | se->dobj.control.dtexts = |
ff922622 | 836 | devm_kcalloc(tplg->dev, le32_to_cpu(ec->items), sizeof(char *), GFP_KERNEL); |
8a978234 LG |
837 | if (se->dobj.control.dtexts == NULL) |
838 | return -ENOMEM; | |
839 | ||
72bbeda0 | 840 | for (i = 0; i < le32_to_cpu(ec->items); i++) { |
8a978234 LG |
841 | |
842 | if (strnlen(ec->texts[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == | |
843 | SNDRV_CTL_ELEM_ID_NAME_MAXLEN) { | |
844 | ret = -EINVAL; | |
845 | goto err; | |
846 | } | |
847 | ||
ff922622 | 848 | se->dobj.control.dtexts[i] = devm_kstrdup(tplg->dev, ec->texts[i], GFP_KERNEL); |
8a978234 LG |
849 | if (!se->dobj.control.dtexts[i]) { |
850 | ret = -ENOMEM; | |
851 | goto err; | |
852 | } | |
853 | } | |
854 | ||
3cde818c | 855 | se->items = le32_to_cpu(ec->items); |
b6e38b29 | 856 | se->texts = (const char * const *)se->dobj.control.dtexts; |
8a978234 LG |
857 | return 0; |
858 | ||
859 | err: | |
3cde818c AS |
860 | return ret; |
861 | } | |
862 | ||
ff922622 AS |
863 | static int soc_tplg_denum_create_values(struct soc_tplg *tplg, struct soc_enum *se, |
864 | struct snd_soc_tplg_enum_control *ec) | |
8a978234 | 865 | { |
5aebe7c7 PLB |
866 | int i; |
867 | ||
631c78ed AS |
868 | /* |
869 | * Following "if" checks if we have at most SND_SOC_TPLG_NUM_TEXTS | |
870 | * values instead of using ARRAY_SIZE(ec->values) due to the fact that | |
871 | * it is oversized for its purpose. Additionally it is done so because | |
872 | * it is defined in UAPI header where it can't be easily changed. | |
873 | */ | |
874 | if (le32_to_cpu(ec->items) > SND_SOC_TPLG_NUM_TEXTS) | |
8a978234 LG |
875 | return -EINVAL; |
876 | ||
631c78ed | 877 | se->dobj.control.dvalues = devm_kcalloc(tplg->dev, le32_to_cpu(ec->items), |
543466ef | 878 | sizeof(*se->dobj.control.dvalues), |
376c0afe | 879 | GFP_KERNEL); |
8a978234 LG |
880 | if (!se->dobj.control.dvalues) |
881 | return -ENOMEM; | |
882 | ||
5aebe7c7 PLB |
883 | /* convert from little-endian */ |
884 | for (i = 0; i < le32_to_cpu(ec->items); i++) { | |
885 | se->dobj.control.dvalues[i] = le32_to_cpu(ec->values[i]); | |
886 | } | |
887 | ||
8a978234 LG |
888 | return 0; |
889 | } | |
890 | ||
0db627c4 | 891 | static int soc_tplg_denum_create(struct soc_tplg *tplg, size_t size) |
8a978234 LG |
892 | { |
893 | struct snd_soc_tplg_enum_control *ec; | |
894 | struct soc_enum *se; | |
895 | struct snd_kcontrol_new kc; | |
0db627c4 | 896 | int ret = 0; |
8a978234 LG |
897 | |
898 | if (soc_tplg_check_elem_count(tplg, | |
3ce57f22 | 899 | sizeof(struct snd_soc_tplg_enum_control), |
0db627c4 | 900 | 1, size, "enums")) |
8a978234 | 901 | return -EINVAL; |
8a978234 | 902 | |
0db627c4 | 903 | ec = (struct snd_soc_tplg_enum_control *)tplg->pos; |
8a978234 | 904 | |
0db627c4 AS |
905 | /* validate kcontrol */ |
906 | if (strnlen(ec->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == | |
907 | SNDRV_CTL_ELEM_ID_NAME_MAXLEN) | |
908 | return -EINVAL; | |
8a978234 | 909 | |
0db627c4 AS |
910 | se = devm_kzalloc(tplg->dev, (sizeof(*se)), GFP_KERNEL); |
911 | if (se == NULL) | |
912 | return -ENOMEM; | |
8a978234 | 913 | |
0db627c4 AS |
914 | tplg->pos += (sizeof(struct snd_soc_tplg_enum_control) + |
915 | le32_to_cpu(ec->priv.size)); | |
916 | ||
917 | dev_dbg(tplg->dev, "ASoC: adding enum kcontrol %s size %d\n", | |
918 | ec->hdr.name, ec->items); | |
919 | ||
920 | memset(&kc, 0, sizeof(kc)); | |
921 | kc.name = ec->hdr.name; | |
922 | kc.private_value = (long)se; | |
923 | kc.iface = SNDRV_CTL_ELEM_IFACE_MIXER; | |
924 | kc.access = le32_to_cpu(ec->hdr.access); | |
925 | ||
8f9974d9 AS |
926 | se->reg = tplg_chan_get_reg(tplg, ec->channel, SNDRV_CHMAP_FL); |
927 | se->shift_l = tplg_chan_get_shift(tplg, ec->channel, | |
0db627c4 | 928 | SNDRV_CHMAP_FL); |
8f9974d9 | 929 | se->shift_r = tplg_chan_get_shift(tplg, ec->channel, |
0db627c4 AS |
930 | SNDRV_CHMAP_FL); |
931 | ||
932 | se->mask = le32_to_cpu(ec->mask); | |
933 | se->dobj.index = tplg->index; | |
934 | se->dobj.type = SND_SOC_DOBJ_ENUM; | |
935 | se->dobj.ops = tplg->ops; | |
936 | INIT_LIST_HEAD(&se->dobj.list); | |
937 | ||
938 | switch (le32_to_cpu(ec->hdr.ops.info)) { | |
939 | case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: | |
940 | case SND_SOC_TPLG_CTL_ENUM_VALUE: | |
941 | ret = soc_tplg_denum_create_values(tplg, se, ec); | |
942 | if (ret < 0) { | |
8a978234 | 943 | dev_err(tplg->dev, |
0db627c4 | 944 | "ASoC: could not create values for %s\n", |
8a978234 | 945 | ec->hdr.name); |
0db627c4 | 946 | goto err; |
8a978234 | 947 | } |
0db627c4 AS |
948 | fallthrough; |
949 | case SND_SOC_TPLG_CTL_ENUM: | |
950 | case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE: | |
951 | case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT: | |
952 | ret = soc_tplg_denum_create_texts(tplg, se, ec); | |
953 | if (ret < 0) { | |
954 | dev_err(tplg->dev, | |
955 | "ASoC: could not create texts for %s\n", | |
8a978234 | 956 | ec->hdr.name); |
0db627c4 | 957 | goto err; |
8a978234 | 958 | } |
0db627c4 AS |
959 | break; |
960 | default: | |
961 | ret = -EINVAL; | |
962 | dev_err(tplg->dev, | |
963 | "ASoC: invalid enum control type %d for %s\n", | |
964 | ec->hdr.ops.info, ec->hdr.name); | |
965 | goto err; | |
966 | } | |
8a978234 | 967 | |
0db627c4 AS |
968 | /* map io handlers */ |
969 | ret = soc_tplg_kcontrol_bind_io(&ec->hdr, &kc, tplg); | |
970 | if (ret) { | |
971 | soc_control_err(tplg, &ec->hdr, ec->hdr.name); | |
972 | goto err; | |
8a978234 | 973 | } |
952bd937 | 974 | |
0db627c4 | 975 | /* pass control to driver for optional further init */ |
430791dd | 976 | ret = soc_tplg_control_load(tplg, &kc, (struct snd_soc_tplg_ctl_hdr *)ec); |
0db627c4 AS |
977 | if (ret < 0) { |
978 | dev_err(tplg->dev, "ASoC: failed to init %s\n", ec->hdr.name); | |
979 | goto err; | |
980 | } | |
981 | ||
982 | /* register control here */ | |
983 | ret = soc_tplg_add_kcontrol(tplg, &kc, &se->dobj.control.kcontrol); | |
984 | if (ret < 0) { | |
985 | dev_err(tplg->dev, "ASoC: could not add kcontrol %s\n", ec->hdr.name); | |
986 | goto err; | |
987 | } | |
988 | ||
989 | list_add(&se->dobj.list, &tplg->comp->dobj_list); | |
990 | ||
991 | err: | |
992 | return ret; | |
8a978234 LG |
993 | } |
994 | ||
995 | static int soc_tplg_kcontrol_elems_load(struct soc_tplg *tplg, | |
996 | struct snd_soc_tplg_hdr *hdr) | |
997 | { | |
2ae548f3 | 998 | int ret; |
8a978234 LG |
999 | int i; |
1000 | ||
8a978234 LG |
1001 | dev_dbg(tplg->dev, "ASoC: adding %d kcontrols at 0x%lx\n", hdr->count, |
1002 | soc_tplg_get_offset(tplg)); | |
1003 | ||
5aebe7c7 | 1004 | for (i = 0; i < le32_to_cpu(hdr->count); i++) { |
ea8f6b29 | 1005 | struct snd_soc_tplg_ctl_hdr *control_hdr = (struct snd_soc_tplg_ctl_hdr *)tplg->pos; |
8a978234 | 1006 | |
5aebe7c7 | 1007 | if (le32_to_cpu(control_hdr->size) != sizeof(*control_hdr)) { |
06eb49f7 ML |
1008 | dev_err(tplg->dev, "ASoC: invalid control size\n"); |
1009 | return -EINVAL; | |
1010 | } | |
1011 | ||
5aebe7c7 | 1012 | switch (le32_to_cpu(control_hdr->ops.info)) { |
8a978234 LG |
1013 | case SND_SOC_TPLG_CTL_VOLSW: |
1014 | case SND_SOC_TPLG_CTL_STROBE: | |
1015 | case SND_SOC_TPLG_CTL_VOLSW_SX: | |
1016 | case SND_SOC_TPLG_CTL_VOLSW_XR_SX: | |
1017 | case SND_SOC_TPLG_CTL_RANGE: | |
1018 | case SND_SOC_TPLG_DAPM_CTL_VOLSW: | |
1019 | case SND_SOC_TPLG_DAPM_CTL_PIN: | |
0db627c4 | 1020 | ret = soc_tplg_dmixer_create(tplg, le32_to_cpu(hdr->payload_size)); |
8a978234 LG |
1021 | break; |
1022 | case SND_SOC_TPLG_CTL_ENUM: | |
1023 | case SND_SOC_TPLG_CTL_ENUM_VALUE: | |
1024 | case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE: | |
1025 | case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT: | |
1026 | case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: | |
0db627c4 | 1027 | ret = soc_tplg_denum_create(tplg, le32_to_cpu(hdr->payload_size)); |
8a978234 LG |
1028 | break; |
1029 | case SND_SOC_TPLG_CTL_BYTES: | |
0db627c4 | 1030 | ret = soc_tplg_dbytes_create(tplg, le32_to_cpu(hdr->payload_size)); |
8a978234 LG |
1031 | break; |
1032 | default: | |
1033 | soc_bind_err(tplg, control_hdr, i); | |
1034 | return -EINVAL; | |
1035 | } | |
2ae548f3 AS |
1036 | if (ret < 0) { |
1037 | dev_err(tplg->dev, "ASoC: invalid control\n"); | |
1038 | return ret; | |
1039 | } | |
1040 | ||
8a978234 LG |
1041 | } |
1042 | ||
1043 | return 0; | |
1044 | } | |
1045 | ||
503e79b7 LG |
1046 | /* optionally pass new dynamic kcontrol to component driver. */ |
1047 | static int soc_tplg_add_route(struct soc_tplg *tplg, | |
1048 | struct snd_soc_dapm_route *route) | |
1049 | { | |
c42464a4 | 1050 | if (tplg->ops && tplg->ops->dapm_route_load) |
503e79b7 LG |
1051 | return tplg->ops->dapm_route_load(tplg->comp, tplg->index, |
1052 | route); | |
1053 | ||
1054 | return 0; | |
1055 | } | |
1056 | ||
8a978234 LG |
1057 | static int soc_tplg_dapm_graph_elems_load(struct soc_tplg *tplg, |
1058 | struct snd_soc_tplg_hdr *hdr) | |
1059 | { | |
1060 | struct snd_soc_dapm_context *dapm = &tplg->comp->dapm; | |
8a978234 | 1061 | struct snd_soc_tplg_dapm_graph_elem *elem; |
cc44c749 | 1062 | struct snd_soc_dapm_route *route; |
ff922622 | 1063 | int count, i; |
7df04ea7 | 1064 | int ret = 0; |
8a978234 | 1065 | |
5aebe7c7 PLB |
1066 | count = le32_to_cpu(hdr->count); |
1067 | ||
8a978234 | 1068 | if (soc_tplg_check_elem_count(tplg, |
3ce57f22 AS |
1069 | sizeof(struct snd_soc_tplg_dapm_graph_elem), |
1070 | count, le32_to_cpu(hdr->payload_size), "graph")) | |
8a978234 | 1071 | return -EINVAL; |
8a978234 | 1072 | |
b75a6511 LG |
1073 | dev_dbg(tplg->dev, "ASoC: adding %d DAPM routes for index %d\n", count, |
1074 | hdr->index); | |
8a978234 | 1075 | |
7df04ea7 | 1076 | for (i = 0; i < count; i++) { |
cc44c749 AS |
1077 | route = devm_kzalloc(tplg->dev, sizeof(*route), GFP_KERNEL); |
1078 | if (!route) | |
7df04ea7 | 1079 | return -ENOMEM; |
8a978234 LG |
1080 | elem = (struct snd_soc_tplg_dapm_graph_elem *)tplg->pos; |
1081 | tplg->pos += sizeof(struct snd_soc_tplg_dapm_graph_elem); | |
1082 | ||
1083 | /* validate routes */ | |
1084 | if (strnlen(elem->source, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == | |
7df04ea7 RS |
1085 | SNDRV_CTL_ELEM_ID_NAME_MAXLEN) { |
1086 | ret = -EINVAL; | |
1087 | break; | |
1088 | } | |
8a978234 | 1089 | if (strnlen(elem->sink, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == |
7df04ea7 RS |
1090 | SNDRV_CTL_ELEM_ID_NAME_MAXLEN) { |
1091 | ret = -EINVAL; | |
1092 | break; | |
1093 | } | |
8a978234 | 1094 | if (strnlen(elem->control, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == |
7df04ea7 RS |
1095 | SNDRV_CTL_ELEM_ID_NAME_MAXLEN) { |
1096 | ret = -EINVAL; | |
1097 | break; | |
1098 | } | |
1099 | ||
cc44c749 AS |
1100 | route->source = elem->source; |
1101 | route->sink = elem->sink; | |
8a978234 | 1102 | |
7df04ea7 | 1103 | /* set to NULL atm for tplg users */ |
cc44c749 | 1104 | route->connected = NULL; |
8a978234 | 1105 | if (strnlen(elem->control, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == 0) |
cc44c749 | 1106 | route->control = NULL; |
8a978234 | 1107 | else |
cc44c749 | 1108 | route->control = elem->control; |
7df04ea7 RS |
1109 | |
1110 | /* add route dobj to dobj_list */ | |
cc44c749 AS |
1111 | route->dobj.type = SND_SOC_DOBJ_GRAPH; |
1112 | route->dobj.ops = tplg->ops; | |
1113 | route->dobj.index = tplg->index; | |
1114 | list_add(&route->dobj.list, &tplg->comp->dobj_list); | |
8a978234 | 1115 | |
cc44c749 | 1116 | ret = soc_tplg_add_route(tplg, route); |
6f0307df | 1117 | if (ret < 0) { |
8bf9475f | 1118 | dev_err(tplg->dev, "ASoC: topology: add_route failed: %d\n", ret); |
6856e887 | 1119 | break; |
6f0307df | 1120 | } |
503e79b7 | 1121 | |
8a978234 | 1122 | /* add route, but keep going if some fail */ |
cc44c749 | 1123 | snd_soc_dapm_add_routes(dapm, route, 1); |
8a978234 LG |
1124 | } |
1125 | ||
7df04ea7 | 1126 | return ret; |
8a978234 LG |
1127 | } |
1128 | ||
d29d41e2 | 1129 | static int soc_tplg_dapm_widget_dmixer_create(struct soc_tplg *tplg, struct snd_kcontrol_new *kc) |
8a978234 | 1130 | { |
8a978234 LG |
1131 | struct soc_mixer_control *sm; |
1132 | struct snd_soc_tplg_mixer_control *mc; | |
d29d41e2 | 1133 | int err; |
9f90af3a | 1134 | |
d29d41e2 | 1135 | mc = (struct snd_soc_tplg_mixer_control *)tplg->pos; |
8a978234 | 1136 | |
d29d41e2 JU |
1137 | /* validate kcontrol */ |
1138 | if (strnlen(mc->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == | |
1139 | SNDRV_CTL_ELEM_ID_NAME_MAXLEN) | |
1140 | return -EINVAL; | |
8a978234 | 1141 | |
d29d41e2 JU |
1142 | sm = devm_kzalloc(tplg->dev, sizeof(*sm), GFP_KERNEL); |
1143 | if (!sm) | |
1144 | return -ENOMEM; | |
8a978234 | 1145 | |
d29d41e2 JU |
1146 | tplg->pos += sizeof(struct snd_soc_tplg_mixer_control) + |
1147 | le32_to_cpu(mc->priv.size); | |
8a978234 | 1148 | |
d29d41e2 JU |
1149 | dev_dbg(tplg->dev, " adding DAPM widget mixer control %s\n", |
1150 | mc->hdr.name); | |
8a978234 | 1151 | |
d29d41e2 JU |
1152 | kc->private_value = (long)sm; |
1153 | kc->name = devm_kstrdup(tplg->dev, mc->hdr.name, GFP_KERNEL); | |
1154 | if (!kc->name) | |
1155 | return -ENOMEM; | |
1156 | kc->iface = SNDRV_CTL_ELEM_IFACE_MIXER; | |
1157 | kc->access = le32_to_cpu(mc->hdr.access); | |
1158 | ||
1159 | /* we only support FL/FR channel mapping atm */ | |
8f9974d9 | 1160 | sm->reg = tplg_chan_get_reg(tplg, mc->channel, |
d29d41e2 | 1161 | SNDRV_CHMAP_FL); |
8f9974d9 | 1162 | sm->rreg = tplg_chan_get_reg(tplg, mc->channel, |
d29d41e2 | 1163 | SNDRV_CHMAP_FR); |
8f9974d9 | 1164 | sm->shift = tplg_chan_get_shift(tplg, mc->channel, |
d29d41e2 | 1165 | SNDRV_CHMAP_FL); |
8f9974d9 | 1166 | sm->rshift = tplg_chan_get_shift(tplg, mc->channel, |
d29d41e2 JU |
1167 | SNDRV_CHMAP_FR); |
1168 | ||
1169 | sm->max = le32_to_cpu(mc->max); | |
1170 | sm->min = le32_to_cpu(mc->min); | |
1171 | sm->invert = le32_to_cpu(mc->invert); | |
1172 | sm->platform_max = le32_to_cpu(mc->platform_max); | |
1173 | sm->dobj.index = tplg->index; | |
1174 | INIT_LIST_HEAD(&sm->dobj.list); | |
1175 | ||
1176 | /* map io handlers */ | |
1177 | err = soc_tplg_kcontrol_bind_io(&mc->hdr, kc, tplg); | |
1178 | if (err) { | |
1179 | soc_control_err(tplg, &mc->hdr, mc->hdr.name); | |
1180 | return err; | |
1181 | } | |
8a978234 | 1182 | |
d29d41e2 JU |
1183 | /* create any TLV data */ |
1184 | err = soc_tplg_create_tlv(tplg, kc, &mc->hdr); | |
1185 | if (err < 0) { | |
1186 | dev_err(tplg->dev, "ASoC: failed to create TLV %s\n", | |
1187 | mc->hdr.name); | |
1188 | return err; | |
1189 | } | |
3789debf | 1190 | |
d29d41e2 | 1191 | /* pass control to driver for optional further init */ |
430791dd | 1192 | err = soc_tplg_control_load(tplg, kc, (struct snd_soc_tplg_ctl_hdr *)mc); |
d29d41e2 JU |
1193 | if (err < 0) { |
1194 | dev_err(tplg->dev, "ASoC: failed to init %s\n", | |
1195 | mc->hdr.name); | |
1196 | return err; | |
8a978234 | 1197 | } |
8a978234 | 1198 | |
d29d41e2 | 1199 | return 0; |
8a978234 LG |
1200 | } |
1201 | ||
d29d41e2 | 1202 | static int soc_tplg_dapm_widget_denum_create(struct soc_tplg *tplg, struct snd_kcontrol_new *kc) |
8a978234 | 1203 | { |
8a978234 LG |
1204 | struct snd_soc_tplg_enum_control *ec; |
1205 | struct soc_enum *se; | |
d29d41e2 | 1206 | int err; |
1a7dd6e2 | 1207 | |
d29d41e2 JU |
1208 | ec = (struct snd_soc_tplg_enum_control *)tplg->pos; |
1209 | /* validate kcontrol */ | |
1210 | if (strnlen(ec->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == | |
1211 | SNDRV_CTL_ELEM_ID_NAME_MAXLEN) | |
1212 | return -EINVAL; | |
8a978234 | 1213 | |
d29d41e2 JU |
1214 | se = devm_kzalloc(tplg->dev, sizeof(*se), GFP_KERNEL); |
1215 | if (!se) | |
1216 | return -ENOMEM; | |
02b64245 | 1217 | |
d29d41e2 JU |
1218 | tplg->pos += (sizeof(struct snd_soc_tplg_enum_control) + |
1219 | le32_to_cpu(ec->priv.size)); | |
8a978234 | 1220 | |
d29d41e2 JU |
1221 | dev_dbg(tplg->dev, " adding DAPM widget enum control %s\n", |
1222 | ec->hdr.name); | |
8a978234 | 1223 | |
d29d41e2 JU |
1224 | kc->private_value = (long)se; |
1225 | kc->name = devm_kstrdup(tplg->dev, ec->hdr.name, GFP_KERNEL); | |
1226 | if (!kc->name) | |
1227 | return -ENOMEM; | |
1228 | kc->iface = SNDRV_CTL_ELEM_IFACE_MIXER; | |
1229 | kc->access = le32_to_cpu(ec->hdr.access); | |
8a978234 | 1230 | |
d29d41e2 | 1231 | /* we only support FL/FR channel mapping atm */ |
8f9974d9 AS |
1232 | se->reg = tplg_chan_get_reg(tplg, ec->channel, SNDRV_CHMAP_FL); |
1233 | se->shift_l = tplg_chan_get_shift(tplg, ec->channel, | |
d29d41e2 | 1234 | SNDRV_CHMAP_FL); |
8f9974d9 | 1235 | se->shift_r = tplg_chan_get_shift(tplg, ec->channel, |
d29d41e2 | 1236 | SNDRV_CHMAP_FR); |
8a978234 | 1237 | |
d29d41e2 JU |
1238 | se->items = le32_to_cpu(ec->items); |
1239 | se->mask = le32_to_cpu(ec->mask); | |
1240 | se->dobj.index = tplg->index; | |
1a7dd6e2 | 1241 | |
d29d41e2 JU |
1242 | switch (le32_to_cpu(ec->hdr.ops.info)) { |
1243 | case SND_SOC_TPLG_CTL_ENUM_VALUE: | |
1244 | case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: | |
1245 | err = soc_tplg_denum_create_values(tplg, se, ec); | |
1246 | if (err < 0) { | |
1247 | dev_err(tplg->dev, "ASoC: could not create values for %s\n", | |
1248 | ec->hdr.name); | |
1249 | return err; | |
1a7dd6e2 | 1250 | } |
d29d41e2 JU |
1251 | fallthrough; |
1252 | case SND_SOC_TPLG_CTL_ENUM: | |
1253 | case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE: | |
1254 | case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT: | |
1255 | err = soc_tplg_denum_create_texts(tplg, se, ec); | |
8a978234 | 1256 | if (err < 0) { |
d29d41e2 | 1257 | dev_err(tplg->dev, "ASoC: could not create texts for %s\n", |
8a978234 | 1258 | ec->hdr.name); |
d29d41e2 | 1259 | return err; |
8a978234 | 1260 | } |
d29d41e2 JU |
1261 | break; |
1262 | default: | |
1263 | dev_err(tplg->dev, "ASoC: invalid enum control type %d for %s\n", | |
1264 | ec->hdr.ops.info, ec->hdr.name); | |
1265 | return -EINVAL; | |
8a978234 LG |
1266 | } |
1267 | ||
d29d41e2 JU |
1268 | /* map io handlers */ |
1269 | err = soc_tplg_kcontrol_bind_io(&ec->hdr, kc, tplg); | |
1270 | if (err) { | |
1271 | soc_control_err(tplg, &ec->hdr, ec->hdr.name); | |
1272 | return err; | |
1273 | } | |
8a978234 | 1274 | |
d29d41e2 | 1275 | /* pass control to driver for optional further init */ |
430791dd | 1276 | err = soc_tplg_control_load(tplg, kc, (struct snd_soc_tplg_ctl_hdr *)ec); |
d29d41e2 JU |
1277 | if (err < 0) { |
1278 | dev_err(tplg->dev, "ASoC: failed to init %s\n", | |
1279 | ec->hdr.name); | |
1280 | return err; | |
1281 | } | |
1282 | ||
1283 | return 0; | |
8a978234 LG |
1284 | } |
1285 | ||
d29d41e2 | 1286 | static int soc_tplg_dapm_widget_dbytes_create(struct soc_tplg *tplg, struct snd_kcontrol_new *kc) |
8a978234 LG |
1287 | { |
1288 | struct snd_soc_tplg_bytes_control *be; | |
9f90af3a | 1289 | struct soc_bytes_ext *sbe; |
d29d41e2 | 1290 | int err; |
8a978234 | 1291 | |
d29d41e2 | 1292 | be = (struct snd_soc_tplg_bytes_control *)tplg->pos; |
8a978234 | 1293 | |
d29d41e2 JU |
1294 | /* validate kcontrol */ |
1295 | if (strnlen(be->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == | |
1296 | SNDRV_CTL_ELEM_ID_NAME_MAXLEN) | |
1297 | return -EINVAL; | |
8a978234 | 1298 | |
d29d41e2 JU |
1299 | sbe = devm_kzalloc(tplg->dev, sizeof(*sbe), GFP_KERNEL); |
1300 | if (!sbe) | |
1301 | return -ENOMEM; | |
8a978234 | 1302 | |
d29d41e2 JU |
1303 | tplg->pos += (sizeof(struct snd_soc_tplg_bytes_control) + |
1304 | le32_to_cpu(be->priv.size)); | |
8a978234 | 1305 | |
d29d41e2 JU |
1306 | dev_dbg(tplg->dev, |
1307 | "ASoC: adding bytes kcontrol %s with access 0x%x\n", | |
1308 | be->hdr.name, be->hdr.access); | |
8a978234 | 1309 | |
d29d41e2 JU |
1310 | kc->private_value = (long)sbe; |
1311 | kc->name = devm_kstrdup(tplg->dev, be->hdr.name, GFP_KERNEL); | |
1312 | if (!kc->name) | |
1313 | return -ENOMEM; | |
1314 | kc->iface = SNDRV_CTL_ELEM_IFACE_MIXER; | |
1315 | kc->access = le32_to_cpu(be->hdr.access); | |
8a978234 | 1316 | |
d29d41e2 JU |
1317 | sbe->max = le32_to_cpu(be->max); |
1318 | INIT_LIST_HEAD(&sbe->dobj.list); | |
8a978234 | 1319 | |
d29d41e2 JU |
1320 | /* map standard io handlers and check for external handlers */ |
1321 | err = soc_tplg_kcontrol_bind_io(&be->hdr, kc, tplg); | |
1322 | if (err) { | |
1323 | soc_control_err(tplg, &be->hdr, be->hdr.name); | |
1324 | return err; | |
8a978234 LG |
1325 | } |
1326 | ||
d29d41e2 | 1327 | /* pass control to driver for optional further init */ |
430791dd | 1328 | err = soc_tplg_control_load(tplg, kc, (struct snd_soc_tplg_ctl_hdr *)be); |
d29d41e2 JU |
1329 | if (err < 0) { |
1330 | dev_err(tplg->dev, "ASoC: failed to init %s\n", | |
1331 | be->hdr.name); | |
1332 | return err; | |
1333 | } | |
9f90af3a | 1334 | |
d29d41e2 | 1335 | return 0; |
8a978234 LG |
1336 | } |
1337 | ||
1338 | static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg, | |
1339 | struct snd_soc_tplg_dapm_widget *w) | |
1340 | { | |
1341 | struct snd_soc_dapm_context *dapm = &tplg->comp->dapm; | |
1342 | struct snd_soc_dapm_widget template, *widget; | |
1343 | struct snd_soc_tplg_ctl_hdr *control_hdr; | |
1344 | struct snd_soc_card *card = tplg->comp->card; | |
b9c035aa | 1345 | unsigned int *kcontrol_type = NULL; |
d29d41e2 JU |
1346 | struct snd_kcontrol_new *kc; |
1347 | int mixer_count = 0; | |
1348 | int bytes_count = 0; | |
1349 | int enum_count = 0; | |
8a978234 | 1350 | int ret = 0; |
d29d41e2 | 1351 | int i; |
8a978234 LG |
1352 | |
1353 | if (strnlen(w->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == | |
1354 | SNDRV_CTL_ELEM_ID_NAME_MAXLEN) | |
1355 | return -EINVAL; | |
1356 | if (strnlen(w->sname, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == | |
1357 | SNDRV_CTL_ELEM_ID_NAME_MAXLEN) | |
1358 | return -EINVAL; | |
1359 | ||
1360 | dev_dbg(tplg->dev, "ASoC: creating DAPM widget %s id %d\n", | |
1361 | w->name, w->id); | |
1362 | ||
1363 | memset(&template, 0, sizeof(template)); | |
1364 | ||
1365 | /* map user to kernel widget ID */ | |
5aebe7c7 | 1366 | template.id = get_widget_id(le32_to_cpu(w->id)); |
752c938a | 1367 | if ((int)template.id < 0) |
8a978234 LG |
1368 | return template.id; |
1369 | ||
c3421a6a | 1370 | /* strings are allocated here, but used and freed by the widget */ |
8a978234 LG |
1371 | template.name = kstrdup(w->name, GFP_KERNEL); |
1372 | if (!template.name) | |
1373 | return -ENOMEM; | |
1374 | template.sname = kstrdup(w->sname, GFP_KERNEL); | |
1375 | if (!template.sname) { | |
1376 | ret = -ENOMEM; | |
1377 | goto err; | |
1378 | } | |
5aebe7c7 PLB |
1379 | template.reg = le32_to_cpu(w->reg); |
1380 | template.shift = le32_to_cpu(w->shift); | |
1381 | template.mask = le32_to_cpu(w->mask); | |
1382 | template.subseq = le32_to_cpu(w->subseq); | |
8a978234 LG |
1383 | template.on_val = w->invert ? 0 : 1; |
1384 | template.off_val = w->invert ? 1 : 0; | |
5aebe7c7 PLB |
1385 | template.ignore_suspend = le32_to_cpu(w->ignore_suspend); |
1386 | template.event_flags = le16_to_cpu(w->event_flags); | |
8a978234 LG |
1387 | template.dobj.index = tplg->index; |
1388 | ||
1389 | tplg->pos += | |
5aebe7c7 PLB |
1390 | (sizeof(struct snd_soc_tplg_dapm_widget) + |
1391 | le32_to_cpu(w->priv.size)); | |
1392 | ||
8a978234 LG |
1393 | if (w->num_kcontrols == 0) { |
1394 | template.num_kcontrols = 0; | |
1395 | goto widget; | |
1396 | } | |
1397 | ||
d29d41e2 JU |
1398 | template.num_kcontrols = le32_to_cpu(w->num_kcontrols); |
1399 | kc = devm_kcalloc(tplg->dev, le32_to_cpu(w->num_kcontrols), sizeof(*kc), GFP_KERNEL); | |
1400 | if (!kc) | |
9c363532 | 1401 | goto hdr_err; |
d29d41e2 JU |
1402 | |
1403 | kcontrol_type = devm_kcalloc(tplg->dev, le32_to_cpu(w->num_kcontrols), sizeof(unsigned int), | |
1404 | GFP_KERNEL); | |
1405 | if (!kcontrol_type) | |
9c363532 | 1406 | goto hdr_err; |
d29d41e2 | 1407 | |
1baad7da | 1408 | for (i = 0; i < le32_to_cpu(w->num_kcontrols); i++) { |
d29d41e2 JU |
1409 | control_hdr = (struct snd_soc_tplg_ctl_hdr *)tplg->pos; |
1410 | switch (le32_to_cpu(control_hdr->ops.info)) { | |
1411 | case SND_SOC_TPLG_CTL_VOLSW: | |
1412 | case SND_SOC_TPLG_CTL_STROBE: | |
1413 | case SND_SOC_TPLG_CTL_VOLSW_SX: | |
1414 | case SND_SOC_TPLG_CTL_VOLSW_XR_SX: | |
1415 | case SND_SOC_TPLG_CTL_RANGE: | |
1416 | case SND_SOC_TPLG_DAPM_CTL_VOLSW: | |
1417 | /* volume mixer */ | |
1418 | kc[i].index = mixer_count; | |
1419 | kcontrol_type[i] = SND_SOC_TPLG_TYPE_MIXER; | |
1420 | mixer_count++; | |
1421 | ret = soc_tplg_dapm_widget_dmixer_create(tplg, &kc[i]); | |
1422 | if (ret < 0) | |
1423 | goto hdr_err; | |
1424 | break; | |
1425 | case SND_SOC_TPLG_CTL_ENUM: | |
1426 | case SND_SOC_TPLG_CTL_ENUM_VALUE: | |
1427 | case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE: | |
1428 | case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT: | |
1429 | case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: | |
1430 | /* enumerated mixer */ | |
1431 | kc[i].index = enum_count; | |
1432 | kcontrol_type[i] = SND_SOC_TPLG_TYPE_ENUM; | |
1433 | enum_count++; | |
1434 | ret = soc_tplg_dapm_widget_denum_create(tplg, &kc[i]); | |
1435 | if (ret < 0) | |
1436 | goto hdr_err; | |
1437 | break; | |
1438 | case SND_SOC_TPLG_CTL_BYTES: | |
1439 | /* bytes control */ | |
1440 | kc[i].index = bytes_count; | |
1441 | kcontrol_type[i] = SND_SOC_TPLG_TYPE_BYTES; | |
1442 | bytes_count++; | |
1443 | ret = soc_tplg_dapm_widget_dbytes_create(tplg, &kc[i]); | |
1444 | if (ret < 0) | |
1445 | goto hdr_err; | |
1446 | break; | |
1447 | default: | |
1448 | dev_err(tplg->dev, "ASoC: invalid widget control type %d:%d:%d\n", | |
1449 | control_hdr->ops.get, control_hdr->ops.put, | |
1450 | le32_to_cpu(control_hdr->ops.info)); | |
1451 | ret = -EINVAL; | |
8a978234 LG |
1452 | goto hdr_err; |
1453 | } | |
8a978234 LG |
1454 | } |
1455 | ||
d29d41e2 | 1456 | template.kcontrol_news = kc; |
8facf84b PU |
1457 | dev_dbg(tplg->dev, "ASoC: template %s with %d/%d/%d (mixer/enum/bytes) control\n", |
1458 | w->name, mixer_count, enum_count, bytes_count); | |
d29d41e2 | 1459 | |
8a978234 LG |
1460 | widget: |
1461 | ret = soc_tplg_widget_load(tplg, &template, w); | |
1462 | if (ret < 0) | |
1463 | goto hdr_err; | |
1464 | ||
1465 | /* card dapm mutex is held by the core if we are loading topology | |
1466 | * data during sound card init. */ | |
1467 | if (card->instantiated) | |
1468 | widget = snd_soc_dapm_new_control(dapm, &template); | |
1469 | else | |
1470 | widget = snd_soc_dapm_new_control_unlocked(dapm, &template); | |
37e1df8c LW |
1471 | if (IS_ERR(widget)) { |
1472 | ret = PTR_ERR(widget); | |
8a978234 LG |
1473 | goto hdr_err; |
1474 | } | |
1475 | ||
1476 | widget->dobj.type = SND_SOC_DOBJ_WIDGET; | |
eea3dd4f | 1477 | widget->dobj.widget.kcontrol_type = kcontrol_type; |
8a978234 LG |
1478 | widget->dobj.ops = tplg->ops; |
1479 | widget->dobj.index = tplg->index; | |
1480 | list_add(&widget->dobj.list, &tplg->comp->dobj_list); | |
ebd259d3 LG |
1481 | |
1482 | ret = soc_tplg_widget_ready(tplg, widget, w); | |
1483 | if (ret < 0) | |
1484 | goto ready_err; | |
1485 | ||
7620fe91 B |
1486 | kfree(template.sname); |
1487 | kfree(template.name); | |
1488 | ||
8a978234 LG |
1489 | return 0; |
1490 | ||
ebd259d3 | 1491 | ready_err: |
2abfd4bd | 1492 | soc_tplg_remove_widget(widget->dapm->component, &widget->dobj, SOC_TPLG_PASS_WIDGET); |
ebd259d3 | 1493 | snd_soc_dapm_free_widget(widget); |
8a978234 LG |
1494 | hdr_err: |
1495 | kfree(template.sname); | |
1496 | err: | |
1497 | kfree(template.name); | |
1498 | return ret; | |
1499 | } | |
1500 | ||
1501 | static int soc_tplg_dapm_widget_elems_load(struct soc_tplg *tplg, | |
1502 | struct snd_soc_tplg_hdr *hdr) | |
1503 | { | |
e9aa139f | 1504 | int count, i; |
5aebe7c7 PLB |
1505 | |
1506 | count = le32_to_cpu(hdr->count); | |
8a978234 | 1507 | |
8a978234 LG |
1508 | dev_dbg(tplg->dev, "ASoC: adding %d DAPM widgets\n", count); |
1509 | ||
1510 | for (i = 0; i < count; i++) { | |
e9aa139f KM |
1511 | struct snd_soc_tplg_dapm_widget *widget = (struct snd_soc_tplg_dapm_widget *) tplg->pos; |
1512 | int ret; | |
1513 | ||
2e288333 AS |
1514 | /* |
1515 | * check if widget itself fits within topology file | |
1516 | * use sizeof instead of widget->size, as we can't be sure | |
1517 | * it is set properly yet (file may end before it is present) | |
1518 | */ | |
1519 | if (soc_tplg_get_offset(tplg) + sizeof(*widget) >= tplg->fw->size) { | |
1520 | dev_err(tplg->dev, "ASoC: invalid widget data size\n"); | |
1521 | return -EINVAL; | |
1522 | } | |
1523 | ||
1524 | /* check if widget has proper size */ | |
5aebe7c7 | 1525 | if (le32_to_cpu(widget->size) != sizeof(*widget)) { |
06eb49f7 ML |
1526 | dev_err(tplg->dev, "ASoC: invalid widget size\n"); |
1527 | return -EINVAL; | |
1528 | } | |
1529 | ||
2e288333 AS |
1530 | /* check if widget private data fits within topology file */ |
1531 | if (soc_tplg_get_offset(tplg) + le32_to_cpu(widget->priv.size) >= tplg->fw->size) { | |
1532 | dev_err(tplg->dev, "ASoC: invalid widget private data size\n"); | |
1533 | return -EINVAL; | |
1534 | } | |
1535 | ||
8a978234 | 1536 | ret = soc_tplg_dapm_widget_create(tplg, widget); |
7de76b62 | 1537 | if (ret < 0) { |
8a978234 LG |
1538 | dev_err(tplg->dev, "ASoC: failed to load widget %s\n", |
1539 | widget->name); | |
7de76b62 ML |
1540 | return ret; |
1541 | } | |
8a978234 LG |
1542 | } |
1543 | ||
1544 | return 0; | |
1545 | } | |
1546 | ||
1547 | static int soc_tplg_dapm_complete(struct soc_tplg *tplg) | |
1548 | { | |
1549 | struct snd_soc_card *card = tplg->comp->card; | |
1550 | int ret; | |
1551 | ||
1552 | /* Card might not have been registered at this point. | |
1553 | * If so, just return success. | |
1554 | */ | |
1555 | if (!card || !card->instantiated) { | |
1556 | dev_warn(tplg->dev, "ASoC: Parent card not yet available," | |
cc9d4714 | 1557 | " widget card binding deferred\n"); |
8a978234 LG |
1558 | return 0; |
1559 | } | |
1560 | ||
1561 | ret = snd_soc_dapm_new_widgets(card); | |
1562 | if (ret < 0) | |
1563 | dev_err(tplg->dev, "ASoC: failed to create new widgets %d\n", | |
1564 | ret); | |
1565 | ||
1566 | return 0; | |
1567 | } | |
1568 | ||
ff922622 AS |
1569 | static int set_stream_info(struct soc_tplg *tplg, struct snd_soc_pcm_stream *stream, |
1570 | struct snd_soc_tplg_stream_caps *caps) | |
b6b6e4d6 | 1571 | { |
ff922622 | 1572 | stream->stream_name = devm_kstrdup(tplg->dev, caps->name, GFP_KERNEL); |
abc3caac AS |
1573 | if (!stream->stream_name) |
1574 | return -ENOMEM; | |
1575 | ||
5aebe7c7 PLB |
1576 | stream->channels_min = le32_to_cpu(caps->channels_min); |
1577 | stream->channels_max = le32_to_cpu(caps->channels_max); | |
1578 | stream->rates = le32_to_cpu(caps->rates); | |
1579 | stream->rate_min = le32_to_cpu(caps->rate_min); | |
1580 | stream->rate_max = le32_to_cpu(caps->rate_max); | |
1581 | stream->formats = le64_to_cpu(caps->formats); | |
1582 | stream->sig_bits = le32_to_cpu(caps->sig_bits); | |
abc3caac AS |
1583 | |
1584 | return 0; | |
b6b6e4d6 ML |
1585 | } |
1586 | ||
0038be9a ML |
1587 | static void set_dai_flags(struct snd_soc_dai_driver *dai_drv, |
1588 | unsigned int flag_mask, unsigned int flags) | |
1589 | { | |
1590 | if (flag_mask & SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_RATES) | |
f14654dd | 1591 | dai_drv->symmetric_rate = |
47108a61 | 1592 | (flags & SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_RATES) ? 1 : 0; |
0038be9a ML |
1593 | |
1594 | if (flag_mask & SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_CHANNELS) | |
1595 | dai_drv->symmetric_channels = | |
47108a61 | 1596 | (flags & SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_CHANNELS) ? |
0038be9a ML |
1597 | 1 : 0; |
1598 | ||
1599 | if (flag_mask & SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_SAMPLEBITS) | |
f14654dd | 1600 | dai_drv->symmetric_sample_bits = |
47108a61 | 1601 | (flags & SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_SAMPLEBITS) ? |
0038be9a ML |
1602 | 1 : 0; |
1603 | } | |
1604 | ||
64527e8a ML |
1605 | static int soc_tplg_dai_create(struct soc_tplg *tplg, |
1606 | struct snd_soc_tplg_pcm *pcm) | |
1607 | { | |
1608 | struct snd_soc_dai_driver *dai_drv; | |
1609 | struct snd_soc_pcm_stream *stream; | |
1610 | struct snd_soc_tplg_stream_caps *caps; | |
e443c205 KM |
1611 | struct snd_soc_dai *dai; |
1612 | struct snd_soc_dapm_context *dapm = | |
1613 | snd_soc_component_get_dapm(tplg->comp); | |
64527e8a ML |
1614 | int ret; |
1615 | ||
ff922622 | 1616 | dai_drv = devm_kzalloc(tplg->dev, sizeof(struct snd_soc_dai_driver), GFP_KERNEL); |
64527e8a ML |
1617 | if (dai_drv == NULL) |
1618 | return -ENOMEM; | |
1619 | ||
abc3caac | 1620 | if (strlen(pcm->dai_name)) { |
ff922622 | 1621 | dai_drv->name = devm_kstrdup(tplg->dev, pcm->dai_name, GFP_KERNEL); |
abc3caac AS |
1622 | if (!dai_drv->name) { |
1623 | ret = -ENOMEM; | |
1624 | goto err; | |
1625 | } | |
1626 | } | |
5aebe7c7 | 1627 | dai_drv->id = le32_to_cpu(pcm->dai_id); |
64527e8a ML |
1628 | |
1629 | if (pcm->playback) { | |
1630 | stream = &dai_drv->playback; | |
1631 | caps = &pcm->caps[SND_SOC_TPLG_STREAM_PLAYBACK]; | |
ff922622 | 1632 | ret = set_stream_info(tplg, stream, caps); |
abc3caac AS |
1633 | if (ret < 0) |
1634 | goto err; | |
64527e8a ML |
1635 | } |
1636 | ||
1637 | if (pcm->capture) { | |
1638 | stream = &dai_drv->capture; | |
1639 | caps = &pcm->caps[SND_SOC_TPLG_STREAM_CAPTURE]; | |
ff922622 | 1640 | ret = set_stream_info(tplg, stream, caps); |
abc3caac AS |
1641 | if (ret < 0) |
1642 | goto err; | |
64527e8a ML |
1643 | } |
1644 | ||
5db6aab6 LG |
1645 | if (pcm->compress) |
1646 | dai_drv->compress_new = snd_soc_new_compress; | |
1647 | ||
64527e8a | 1648 | /* pass control to component driver for optional further init */ |
c60b613a | 1649 | ret = soc_tplg_dai_load(tplg, dai_drv, pcm, NULL); |
64527e8a | 1650 | if (ret < 0) { |
e59db12b | 1651 | dev_err(tplg->dev, "ASoC: DAI loading failed\n"); |
abc3caac | 1652 | goto err; |
64527e8a ML |
1653 | } |
1654 | ||
1655 | dai_drv->dobj.index = tplg->index; | |
1656 | dai_drv->dobj.ops = tplg->ops; | |
1657 | dai_drv->dobj.type = SND_SOC_DOBJ_PCM; | |
1658 | list_add(&dai_drv->dobj.list, &tplg->comp->dobj_list); | |
1659 | ||
1660 | /* register the DAI to the component */ | |
fc4cb1e1 | 1661 | dai = snd_soc_register_dai(tplg->comp, dai_drv, false); |
e443c205 KM |
1662 | if (!dai) |
1663 | return -ENOMEM; | |
1664 | ||
1665 | /* Create the DAI widgets here */ | |
1666 | ret = snd_soc_dapm_new_dai_widgets(dapm, dai); | |
1667 | if (ret != 0) { | |
1668 | dev_err(dai->dev, "Failed to create DAI widgets %d\n", ret); | |
fc4cb1e1 | 1669 | snd_soc_unregister_dai(dai); |
e443c205 KM |
1670 | return ret; |
1671 | } | |
1672 | ||
abc3caac AS |
1673 | return 0; |
1674 | ||
1675 | err: | |
e443c205 | 1676 | return ret; |
64527e8a ML |
1677 | } |
1678 | ||
717a8e72 ML |
1679 | static void set_link_flags(struct snd_soc_dai_link *link, |
1680 | unsigned int flag_mask, unsigned int flags) | |
1681 | { | |
1682 | if (flag_mask & SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_RATES) | |
f14654dd | 1683 | link->symmetric_rate = |
47108a61 | 1684 | (flags & SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_RATES) ? 1 : 0; |
717a8e72 ML |
1685 | |
1686 | if (flag_mask & SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_CHANNELS) | |
1687 | link->symmetric_channels = | |
47108a61 | 1688 | (flags & SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_CHANNELS) ? |
717a8e72 ML |
1689 | 1 : 0; |
1690 | ||
1691 | if (flag_mask & SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_SAMPLEBITS) | |
f14654dd | 1692 | link->symmetric_sample_bits = |
47108a61 | 1693 | (flags & SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_SAMPLEBITS) ? |
717a8e72 | 1694 | 1 : 0; |
6ff67cca ML |
1695 | |
1696 | if (flag_mask & SND_SOC_TPLG_LNK_FLGBIT_VOICE_WAKEUP) | |
1697 | link->ignore_suspend = | |
47108a61 PLB |
1698 | (flags & SND_SOC_TPLG_LNK_FLGBIT_VOICE_WAKEUP) ? |
1699 | 1 : 0; | |
717a8e72 ML |
1700 | } |
1701 | ||
67d1c21e | 1702 | /* create the FE DAI link */ |
ab4bc5ee | 1703 | static int soc_tplg_fe_link_create(struct soc_tplg *tplg, |
acfc7d46 ML |
1704 | struct snd_soc_tplg_pcm *pcm) |
1705 | { | |
1706 | struct snd_soc_dai_link *link; | |
23b946ce | 1707 | struct snd_soc_dai_link_component *dlc; |
acfc7d46 ML |
1708 | int ret; |
1709 | ||
3e6de894 | 1710 | /* link + cpu + codec + platform */ |
ff922622 | 1711 | link = devm_kzalloc(tplg->dev, sizeof(*link) + (3 * sizeof(*dlc)), GFP_KERNEL); |
acfc7d46 ML |
1712 | if (link == NULL) |
1713 | return -ENOMEM; | |
1714 | ||
23b946ce KM |
1715 | dlc = (struct snd_soc_dai_link_component *)(link + 1); |
1716 | ||
1717 | link->cpus = &dlc[0]; | |
1718 | link->codecs = &dlc[1]; | |
3e6de894 | 1719 | link->platforms = &dlc[2]; |
23b946ce KM |
1720 | |
1721 | link->num_cpus = 1; | |
1722 | link->num_codecs = 1; | |
3e6de894 | 1723 | link->num_platforms = 1; |
23b946ce | 1724 | |
8ce1cbd6 JK |
1725 | link->dobj.index = tplg->index; |
1726 | link->dobj.ops = tplg->ops; | |
1727 | link->dobj.type = SND_SOC_DOBJ_DAI_LINK; | |
1728 | ||
8f27c4ab | 1729 | if (strlen(pcm->pcm_name)) { |
ff922622 AS |
1730 | link->name = devm_kstrdup(tplg->dev, pcm->pcm_name, GFP_KERNEL); |
1731 | link->stream_name = devm_kstrdup(tplg->dev, pcm->pcm_name, GFP_KERNEL); | |
abc3caac AS |
1732 | if (!link->name || !link->stream_name) { |
1733 | ret = -ENOMEM; | |
1734 | goto err; | |
1735 | } | |
8f27c4ab | 1736 | } |
5aebe7c7 | 1737 | link->id = le32_to_cpu(pcm->pcm_id); |
acfc7d46 | 1738 | |
abc3caac | 1739 | if (strlen(pcm->dai_name)) { |
ff922622 | 1740 | link->cpus->dai_name = devm_kstrdup(tplg->dev, pcm->dai_name, GFP_KERNEL); |
abc3caac AS |
1741 | if (!link->cpus->dai_name) { |
1742 | ret = -ENOMEM; | |
1743 | goto err; | |
1744 | } | |
1745 | } | |
8f27c4ab | 1746 | |
23b946ce KM |
1747 | link->codecs->name = "snd-soc-dummy"; |
1748 | link->codecs->dai_name = "snd-soc-dummy-dai"; | |
67d1c21e | 1749 | |
3e6de894 PLB |
1750 | link->platforms->name = "snd-soc-dummy"; |
1751 | ||
67d1c21e GS |
1752 | /* enable DPCM */ |
1753 | link->dynamic = 1; | |
c403dcd8 | 1754 | link->ignore_pmdown_time = 1; |
5aebe7c7 PLB |
1755 | link->dpcm_playback = le32_to_cpu(pcm->playback); |
1756 | link->dpcm_capture = le32_to_cpu(pcm->capture); | |
717a8e72 | 1757 | if (pcm->flag_mask) |
5aebe7c7 PLB |
1758 | set_link_flags(link, |
1759 | le32_to_cpu(pcm->flag_mask), | |
1760 | le32_to_cpu(pcm->flags)); | |
67d1c21e | 1761 | |
acfc7d46 | 1762 | /* pass control to component driver for optional further init */ |
c60b613a | 1763 | ret = soc_tplg_dai_link_load(tplg, link, NULL); |
acfc7d46 | 1764 | if (ret < 0) { |
e59db12b | 1765 | dev_err(tplg->dev, "ASoC: FE link loading failed\n"); |
76d27036 DT |
1766 | goto err; |
1767 | } | |
1768 | ||
2acf6ce2 | 1769 | ret = snd_soc_add_pcm_runtime(tplg->comp->card, link); |
76d27036 | 1770 | if (ret < 0) { |
e59db12b | 1771 | dev_err(tplg->dev, "ASoC: adding FE link failed\n"); |
76d27036 | 1772 | goto err; |
acfc7d46 ML |
1773 | } |
1774 | ||
acfc7d46 ML |
1775 | list_add(&link->dobj.list, &tplg->comp->dobj_list); |
1776 | ||
acfc7d46 | 1777 | return 0; |
76d27036 | 1778 | err: |
76d27036 | 1779 | return ret; |
acfc7d46 ML |
1780 | } |
1781 | ||
1782 | /* create a FE DAI and DAI link from the PCM object */ | |
64527e8a ML |
1783 | static int soc_tplg_pcm_create(struct soc_tplg *tplg, |
1784 | struct snd_soc_tplg_pcm *pcm) | |
1785 | { | |
acfc7d46 ML |
1786 | int ret; |
1787 | ||
1788 | ret = soc_tplg_dai_create(tplg, pcm); | |
1789 | if (ret < 0) | |
1790 | return ret; | |
1791 | ||
ab4bc5ee | 1792 | return soc_tplg_fe_link_create(tplg, pcm); |
64527e8a ML |
1793 | } |
1794 | ||
55726dc9 ML |
1795 | /* copy stream caps from the old version 4 of source */ |
1796 | static void stream_caps_new_ver(struct snd_soc_tplg_stream_caps *dest, | |
1797 | struct snd_soc_tplg_stream_caps_v4 *src) | |
1798 | { | |
5aebe7c7 | 1799 | dest->size = cpu_to_le32(sizeof(*dest)); |
55726dc9 ML |
1800 | memcpy(dest->name, src->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); |
1801 | dest->formats = src->formats; | |
1802 | dest->rates = src->rates; | |
1803 | dest->rate_min = src->rate_min; | |
1804 | dest->rate_max = src->rate_max; | |
1805 | dest->channels_min = src->channels_min; | |
1806 | dest->channels_max = src->channels_max; | |
1807 | dest->periods_min = src->periods_min; | |
1808 | dest->periods_max = src->periods_max; | |
1809 | dest->period_size_min = src->period_size_min; | |
1810 | dest->period_size_max = src->period_size_max; | |
1811 | dest->buffer_size_min = src->buffer_size_min; | |
1812 | dest->buffer_size_max = src->buffer_size_max; | |
1813 | } | |
1814 | ||
1815 | /** | |
1816 | * pcm_new_ver - Create the new version of PCM from the old version. | |
1817 | * @tplg: topology context | |
1818 | * @src: older version of pcm as a source | |
1819 | * @pcm: latest version of pcm created from the source | |
1820 | * | |
ce1f2571 | 1821 | * Support from version 4. User should free the returned pcm manually. |
55726dc9 ML |
1822 | */ |
1823 | static int pcm_new_ver(struct soc_tplg *tplg, | |
1824 | struct snd_soc_tplg_pcm *src, | |
1825 | struct snd_soc_tplg_pcm **pcm) | |
1826 | { | |
1827 | struct snd_soc_tplg_pcm *dest; | |
1828 | struct snd_soc_tplg_pcm_v4 *src_v4; | |
1829 | int i; | |
1830 | ||
1831 | *pcm = NULL; | |
1832 | ||
5aebe7c7 | 1833 | if (le32_to_cpu(src->size) != sizeof(*src_v4)) { |
55726dc9 ML |
1834 | dev_err(tplg->dev, "ASoC: invalid PCM size\n"); |
1835 | return -EINVAL; | |
1836 | } | |
1837 | ||
1838 | dev_warn(tplg->dev, "ASoC: old version of PCM\n"); | |
1839 | src_v4 = (struct snd_soc_tplg_pcm_v4 *)src; | |
1840 | dest = kzalloc(sizeof(*dest), GFP_KERNEL); | |
1841 | if (!dest) | |
1842 | return -ENOMEM; | |
1843 | ||
5aebe7c7 | 1844 | dest->size = cpu_to_le32(sizeof(*dest)); /* size of latest abi version */ |
55726dc9 ML |
1845 | memcpy(dest->pcm_name, src_v4->pcm_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); |
1846 | memcpy(dest->dai_name, src_v4->dai_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); | |
1847 | dest->pcm_id = src_v4->pcm_id; | |
1848 | dest->dai_id = src_v4->dai_id; | |
1849 | dest->playback = src_v4->playback; | |
1850 | dest->capture = src_v4->capture; | |
1851 | dest->compress = src_v4->compress; | |
1852 | dest->num_streams = src_v4->num_streams; | |
5aebe7c7 | 1853 | for (i = 0; i < le32_to_cpu(dest->num_streams); i++) |
55726dc9 ML |
1854 | memcpy(&dest->stream[i], &src_v4->stream[i], |
1855 | sizeof(struct snd_soc_tplg_stream)); | |
1856 | ||
1857 | for (i = 0; i < 2; i++) | |
1858 | stream_caps_new_ver(&dest->caps[i], &src_v4->caps[i]); | |
1859 | ||
1860 | *pcm = dest; | |
1861 | return 0; | |
1862 | } | |
1863 | ||
64527e8a | 1864 | static int soc_tplg_pcm_elems_load(struct soc_tplg *tplg, |
8a978234 LG |
1865 | struct snd_soc_tplg_hdr *hdr) |
1866 | { | |
55726dc9 | 1867 | struct snd_soc_tplg_pcm *pcm, *_pcm; |
5aebe7c7 PLB |
1868 | int count; |
1869 | int size; | |
fd340455 | 1870 | int i; |
55726dc9 | 1871 | bool abi_match; |
a3039aef | 1872 | int ret; |
8a978234 | 1873 | |
5aebe7c7 PLB |
1874 | count = le32_to_cpu(hdr->count); |
1875 | ||
55726dc9 ML |
1876 | /* check the element size and count */ |
1877 | pcm = (struct snd_soc_tplg_pcm *)tplg->pos; | |
5aebe7c7 PLB |
1878 | size = le32_to_cpu(pcm->size); |
1879 | if (size > sizeof(struct snd_soc_tplg_pcm) | |
1880 | || size < sizeof(struct snd_soc_tplg_pcm_v4)) { | |
55726dc9 | 1881 | dev_err(tplg->dev, "ASoC: invalid size %d for PCM elems\n", |
5aebe7c7 | 1882 | size); |
55726dc9 ML |
1883 | return -EINVAL; |
1884 | } | |
1885 | ||
8a978234 | 1886 | if (soc_tplg_check_elem_count(tplg, |
5aebe7c7 PLB |
1887 | size, count, |
1888 | le32_to_cpu(hdr->payload_size), | |
3ce57f22 | 1889 | "PCM DAI")) |
8a978234 | 1890 | return -EINVAL; |
8a978234 | 1891 | |
64527e8a | 1892 | for (i = 0; i < count; i++) { |
55726dc9 | 1893 | pcm = (struct snd_soc_tplg_pcm *)tplg->pos; |
5aebe7c7 | 1894 | size = le32_to_cpu(pcm->size); |
55726dc9 ML |
1895 | |
1896 | /* check ABI version by size, create a new version of pcm | |
1897 | * if abi not match. | |
1898 | */ | |
5aebe7c7 | 1899 | if (size == sizeof(*pcm)) { |
55726dc9 ML |
1900 | abi_match = true; |
1901 | _pcm = pcm; | |
1902 | } else { | |
1903 | abi_match = false; | |
b3677fc3 AS |
1904 | ret = pcm_new_ver(tplg, pcm, &_pcm); |
1905 | if (ret < 0) | |
1906 | return ret; | |
06eb49f7 ML |
1907 | } |
1908 | ||
55726dc9 | 1909 | /* create the FE DAIs and DAI links */ |
a3039aef DT |
1910 | ret = soc_tplg_pcm_create(tplg, _pcm); |
1911 | if (ret < 0) { | |
1912 | if (!abi_match) | |
1913 | kfree(_pcm); | |
1914 | return ret; | |
1915 | } | |
55726dc9 | 1916 | |
717a8e72 ML |
1917 | /* offset by version-specific struct size and |
1918 | * real priv data size | |
1919 | */ | |
5aebe7c7 | 1920 | tplg->pos += size + le32_to_cpu(_pcm->priv.size); |
717a8e72 | 1921 | |
55726dc9 ML |
1922 | if (!abi_match) |
1923 | kfree(_pcm); /* free the duplicated one */ | |
64527e8a ML |
1924 | } |
1925 | ||
8a978234 | 1926 | dev_dbg(tplg->dev, "ASoC: adding %d PCM DAIs\n", count); |
8a978234 | 1927 | |
8a978234 | 1928 | return 0; |
8a978234 LG |
1929 | } |
1930 | ||
593d9e52 ML |
1931 | /** |
1932 | * set_link_hw_format - Set the HW audio format of the physical DAI link. | |
8abab35f | 1933 | * @link: &snd_soc_dai_link which should be updated |
593d9e52 ML |
1934 | * @cfg: physical link configs. |
1935 | * | |
1936 | * Topology context contains a list of supported HW formats (configs) and | |
1937 | * a default format ID for the physical link. This function will use this | |
1938 | * default ID to choose the HW format to set the link's DAI format for init. | |
1939 | */ | |
1940 | static void set_link_hw_format(struct snd_soc_dai_link *link, | |
1941 | struct snd_soc_tplg_link_config *cfg) | |
1942 | { | |
1943 | struct snd_soc_tplg_hw_config *hw_config; | |
f026c123 | 1944 | unsigned char bclk_provider, fsync_provider; |
593d9e52 ML |
1945 | unsigned char invert_bclk, invert_fsync; |
1946 | int i; | |
1947 | ||
5aebe7c7 | 1948 | for (i = 0; i < le32_to_cpu(cfg->num_hw_configs); i++) { |
593d9e52 ML |
1949 | hw_config = &cfg->hw_config[i]; |
1950 | if (hw_config->id != cfg->default_hw_config_id) | |
1951 | continue; | |
1952 | ||
5aebe7c7 PLB |
1953 | link->dai_fmt = le32_to_cpu(hw_config->fmt) & |
1954 | SND_SOC_DAIFMT_FORMAT_MASK; | |
593d9e52 | 1955 | |
933e1c4a | 1956 | /* clock gating */ |
fbeabd09 KM |
1957 | switch (hw_config->clock_gated) { |
1958 | case SND_SOC_TPLG_DAI_CLK_GATE_GATED: | |
933e1c4a | 1959 | link->dai_fmt |= SND_SOC_DAIFMT_GATED; |
fbeabd09 KM |
1960 | break; |
1961 | ||
1962 | case SND_SOC_TPLG_DAI_CLK_GATE_CONT: | |
933e1c4a | 1963 | link->dai_fmt |= SND_SOC_DAIFMT_CONT; |
fbeabd09 KM |
1964 | break; |
1965 | ||
1966 | default: | |
1967 | /* ignore the value */ | |
1968 | break; | |
1969 | } | |
933e1c4a | 1970 | |
593d9e52 ML |
1971 | /* clock signal polarity */ |
1972 | invert_bclk = hw_config->invert_bclk; | |
1973 | invert_fsync = hw_config->invert_fsync; | |
1974 | if (!invert_bclk && !invert_fsync) | |
1975 | link->dai_fmt |= SND_SOC_DAIFMT_NB_NF; | |
1976 | else if (!invert_bclk && invert_fsync) | |
1977 | link->dai_fmt |= SND_SOC_DAIFMT_NB_IF; | |
1978 | else if (invert_bclk && !invert_fsync) | |
1979 | link->dai_fmt |= SND_SOC_DAIFMT_IB_NF; | |
1980 | else | |
1981 | link->dai_fmt |= SND_SOC_DAIFMT_IB_IF; | |
1982 | ||
1983 | /* clock masters */ | |
f026c123 PLB |
1984 | bclk_provider = (hw_config->bclk_provider == |
1985 | SND_SOC_TPLG_BCLK_CP); | |
1986 | fsync_provider = (hw_config->fsync_provider == | |
1987 | SND_SOC_TPLG_FSYNC_CP); | |
1988 | if (bclk_provider && fsync_provider) | |
1989 | link->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; | |
1990 | else if (!bclk_provider && fsync_provider) | |
1991 | link->dai_fmt |= SND_SOC_DAIFMT_CBC_CFP; | |
1992 | else if (bclk_provider && !fsync_provider) | |
1993 | link->dai_fmt |= SND_SOC_DAIFMT_CBP_CFC; | |
593d9e52 | 1994 | else |
f026c123 | 1995 | link->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; |
593d9e52 ML |
1996 | } |
1997 | } | |
1998 | ||
1999 | /** | |
2000 | * link_new_ver - Create a new physical link config from the old | |
2001 | * version of source. | |
8abab35f | 2002 | * @tplg: topology context |
593d9e52 ML |
2003 | * @src: old version of phyical link config as a source |
2004 | * @link: latest version of physical link config created from the source | |
2005 | * | |
ce1f2571 | 2006 | * Support from version 4. User need free the returned link config manually. |
593d9e52 ML |
2007 | */ |
2008 | static int link_new_ver(struct soc_tplg *tplg, | |
2009 | struct snd_soc_tplg_link_config *src, | |
2010 | struct snd_soc_tplg_link_config **link) | |
2011 | { | |
2012 | struct snd_soc_tplg_link_config *dest; | |
2013 | struct snd_soc_tplg_link_config_v4 *src_v4; | |
2014 | int i; | |
2015 | ||
2016 | *link = NULL; | |
2017 | ||
5aebe7c7 PLB |
2018 | if (le32_to_cpu(src->size) != |
2019 | sizeof(struct snd_soc_tplg_link_config_v4)) { | |
593d9e52 ML |
2020 | dev_err(tplg->dev, "ASoC: invalid physical link config size\n"); |
2021 | return -EINVAL; | |
2022 | } | |
2023 | ||
2024 | dev_warn(tplg->dev, "ASoC: old version of physical link config\n"); | |
2025 | ||
2026 | src_v4 = (struct snd_soc_tplg_link_config_v4 *)src; | |
2027 | dest = kzalloc(sizeof(*dest), GFP_KERNEL); | |
2028 | if (!dest) | |
2029 | return -ENOMEM; | |
2030 | ||
5aebe7c7 | 2031 | dest->size = cpu_to_le32(sizeof(*dest)); |
593d9e52 ML |
2032 | dest->id = src_v4->id; |
2033 | dest->num_streams = src_v4->num_streams; | |
5aebe7c7 | 2034 | for (i = 0; i < le32_to_cpu(dest->num_streams); i++) |
593d9e52 ML |
2035 | memcpy(&dest->stream[i], &src_v4->stream[i], |
2036 | sizeof(struct snd_soc_tplg_stream)); | |
2037 | ||
2038 | *link = dest; | |
2039 | return 0; | |
2040 | } | |
2041 | ||
d6f31e0e KM |
2042 | /** |
2043 | * snd_soc_find_dai_link - Find a DAI link | |
2044 | * | |
2045 | * @card: soc card | |
2046 | * @id: DAI link ID to match | |
2047 | * @name: DAI link name to match, optional | |
2048 | * @stream_name: DAI link stream name to match, optional | |
2049 | * | |
2050 | * This function will search all existing DAI links of the soc card to | |
2051 | * find the link of the same ID. Since DAI links may not have their | |
2052 | * unique ID, so name and stream name should also match if being | |
2053 | * specified. | |
2054 | * | |
2055 | * Return: pointer of DAI link, or NULL if not found. | |
2056 | */ | |
2057 | static struct snd_soc_dai_link *snd_soc_find_dai_link(struct snd_soc_card *card, | |
2058 | int id, const char *name, | |
2059 | const char *stream_name) | |
2060 | { | |
2061 | struct snd_soc_pcm_runtime *rtd; | |
d6f31e0e KM |
2062 | |
2063 | for_each_card_rtds(card, rtd) { | |
b81e8efa | 2064 | struct snd_soc_dai_link *link = rtd->dai_link; |
d6f31e0e KM |
2065 | |
2066 | if (link->id != id) | |
2067 | continue; | |
2068 | ||
2069 | if (name && (!link->name || strcmp(name, link->name))) | |
2070 | continue; | |
2071 | ||
2072 | if (stream_name && (!link->stream_name | |
2073 | || strcmp(stream_name, link->stream_name))) | |
2074 | continue; | |
2075 | ||
2076 | return link; | |
2077 | } | |
2078 | ||
2079 | return NULL; | |
2080 | } | |
2081 | ||
593d9e52 ML |
2082 | /* Find and configure an existing physical DAI link */ |
2083 | static int soc_tplg_link_config(struct soc_tplg *tplg, | |
2084 | struct snd_soc_tplg_link_config *cfg) | |
2085 | { | |
2086 | struct snd_soc_dai_link *link; | |
2087 | const char *name, *stream_name; | |
dbab1cb8 | 2088 | size_t len; |
593d9e52 ML |
2089 | int ret; |
2090 | ||
dbab1cb8 ML |
2091 | len = strnlen(cfg->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); |
2092 | if (len == SNDRV_CTL_ELEM_ID_NAME_MAXLEN) | |
2093 | return -EINVAL; | |
2094 | else if (len) | |
2095 | name = cfg->name; | |
2096 | else | |
2097 | name = NULL; | |
2098 | ||
2099 | len = strnlen(cfg->stream_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); | |
2100 | if (len == SNDRV_CTL_ELEM_ID_NAME_MAXLEN) | |
2101 | return -EINVAL; | |
2102 | else if (len) | |
2103 | stream_name = cfg->stream_name; | |
2104 | else | |
2105 | stream_name = NULL; | |
593d9e52 | 2106 | |
5aebe7c7 | 2107 | link = snd_soc_find_dai_link(tplg->comp->card, le32_to_cpu(cfg->id), |
593d9e52 ML |
2108 | name, stream_name); |
2109 | if (!link) { | |
2110 | dev_err(tplg->dev, "ASoC: physical link %s (id %d) not exist\n", | |
2111 | name, cfg->id); | |
2112 | return -EINVAL; | |
2113 | } | |
2114 | ||
2115 | /* hw format */ | |
2116 | if (cfg->num_hw_configs) | |
2117 | set_link_hw_format(link, cfg); | |
2118 | ||
2119 | /* flags */ | |
2120 | if (cfg->flag_mask) | |
5aebe7c7 PLB |
2121 | set_link_flags(link, |
2122 | le32_to_cpu(cfg->flag_mask), | |
2123 | le32_to_cpu(cfg->flags)); | |
593d9e52 ML |
2124 | |
2125 | /* pass control to component driver for optional further init */ | |
c60b613a | 2126 | ret = soc_tplg_dai_link_load(tplg, link, cfg); |
593d9e52 ML |
2127 | if (ret < 0) { |
2128 | dev_err(tplg->dev, "ASoC: physical link loading failed\n"); | |
2129 | return ret; | |
2130 | } | |
2131 | ||
adfebb51 B |
2132 | /* for unloading it in snd_soc_tplg_component_remove */ |
2133 | link->dobj.index = tplg->index; | |
2134 | link->dobj.ops = tplg->ops; | |
2135 | link->dobj.type = SND_SOC_DOBJ_BACKEND_LINK; | |
2136 | list_add(&link->dobj.list, &tplg->comp->dobj_list); | |
2137 | ||
593d9e52 ML |
2138 | return 0; |
2139 | } | |
2140 | ||
2141 | ||
2142 | /* Load physical link config elements from the topology context */ | |
2143 | static int soc_tplg_link_elems_load(struct soc_tplg *tplg, | |
2144 | struct snd_soc_tplg_hdr *hdr) | |
2145 | { | |
2146 | struct snd_soc_tplg_link_config *link, *_link; | |
5aebe7c7 PLB |
2147 | int count; |
2148 | int size; | |
593d9e52 ML |
2149 | int i, ret; |
2150 | bool abi_match; | |
2151 | ||
5aebe7c7 PLB |
2152 | count = le32_to_cpu(hdr->count); |
2153 | ||
593d9e52 ML |
2154 | /* check the element size and count */ |
2155 | link = (struct snd_soc_tplg_link_config *)tplg->pos; | |
5aebe7c7 PLB |
2156 | size = le32_to_cpu(link->size); |
2157 | if (size > sizeof(struct snd_soc_tplg_link_config) | |
2158 | || size < sizeof(struct snd_soc_tplg_link_config_v4)) { | |
593d9e52 | 2159 | dev_err(tplg->dev, "ASoC: invalid size %d for physical link elems\n", |
5aebe7c7 | 2160 | size); |
593d9e52 ML |
2161 | return -EINVAL; |
2162 | } | |
2163 | ||
3ce57f22 | 2164 | if (soc_tplg_check_elem_count(tplg, size, count, |
5aebe7c7 | 2165 | le32_to_cpu(hdr->payload_size), |
3ce57f22 | 2166 | "physical link config")) |
593d9e52 | 2167 | return -EINVAL; |
593d9e52 ML |
2168 | |
2169 | /* config physical DAI links */ | |
2170 | for (i = 0; i < count; i++) { | |
2171 | link = (struct snd_soc_tplg_link_config *)tplg->pos; | |
5aebe7c7 PLB |
2172 | size = le32_to_cpu(link->size); |
2173 | if (size == sizeof(*link)) { | |
593d9e52 ML |
2174 | abi_match = true; |
2175 | _link = link; | |
2176 | } else { | |
2177 | abi_match = false; | |
2178 | ret = link_new_ver(tplg, link, &_link); | |
2179 | if (ret < 0) | |
2180 | return ret; | |
2181 | } | |
2182 | ||
2183 | ret = soc_tplg_link_config(tplg, _link); | |
2b2d5c4d DT |
2184 | if (ret < 0) { |
2185 | if (!abi_match) | |
2186 | kfree(_link); | |
593d9e52 | 2187 | return ret; |
2b2d5c4d | 2188 | } |
593d9e52 ML |
2189 | |
2190 | /* offset by version-specific struct size and | |
2191 | * real priv data size | |
2192 | */ | |
5aebe7c7 | 2193 | tplg->pos += size + le32_to_cpu(_link->priv.size); |
593d9e52 ML |
2194 | |
2195 | if (!abi_match) | |
2196 | kfree(_link); /* free the duplicated one */ | |
2197 | } | |
2198 | ||
2199 | return 0; | |
2200 | } | |
2201 | ||
9aa3f034 ML |
2202 | /** |
2203 | * soc_tplg_dai_config - Find and configure an existing physical DAI. | |
0038be9a | 2204 | * @tplg: topology context |
9aa3f034 | 2205 | * @d: physical DAI configs. |
0038be9a | 2206 | * |
9aa3f034 ML |
2207 | * The physical dai should already be registered by the platform driver. |
2208 | * The platform driver should specify the DAI name and ID for matching. | |
0038be9a | 2209 | */ |
9aa3f034 ML |
2210 | static int soc_tplg_dai_config(struct soc_tplg *tplg, |
2211 | struct snd_soc_tplg_dai *d) | |
0038be9a | 2212 | { |
5aebe7c7 | 2213 | struct snd_soc_dai_link_component dai_component; |
0038be9a ML |
2214 | struct snd_soc_dai *dai; |
2215 | struct snd_soc_dai_driver *dai_drv; | |
2216 | struct snd_soc_pcm_stream *stream; | |
2217 | struct snd_soc_tplg_stream_caps *caps; | |
2218 | int ret; | |
2219 | ||
5aebe7c7 PLB |
2220 | memset(&dai_component, 0, sizeof(dai_component)); |
2221 | ||
9aa3f034 | 2222 | dai_component.dai_name = d->dai_name; |
0038be9a ML |
2223 | dai = snd_soc_find_dai(&dai_component); |
2224 | if (!dai) { | |
9aa3f034 ML |
2225 | dev_err(tplg->dev, "ASoC: physical DAI %s not registered\n", |
2226 | d->dai_name); | |
0038be9a ML |
2227 | return -EINVAL; |
2228 | } | |
2229 | ||
5aebe7c7 | 2230 | if (le32_to_cpu(d->dai_id) != dai->id) { |
9aa3f034 ML |
2231 | dev_err(tplg->dev, "ASoC: physical DAI %s id mismatch\n", |
2232 | d->dai_name); | |
0038be9a ML |
2233 | return -EINVAL; |
2234 | } | |
2235 | ||
2236 | dai_drv = dai->driver; | |
2237 | if (!dai_drv) | |
2238 | return -EINVAL; | |
2239 | ||
9aa3f034 | 2240 | if (d->playback) { |
0038be9a | 2241 | stream = &dai_drv->playback; |
9aa3f034 | 2242 | caps = &d->caps[SND_SOC_TPLG_STREAM_PLAYBACK]; |
ff922622 | 2243 | ret = set_stream_info(tplg, stream, caps); |
abc3caac AS |
2244 | if (ret < 0) |
2245 | goto err; | |
0038be9a ML |
2246 | } |
2247 | ||
9aa3f034 | 2248 | if (d->capture) { |
0038be9a | 2249 | stream = &dai_drv->capture; |
9aa3f034 | 2250 | caps = &d->caps[SND_SOC_TPLG_STREAM_CAPTURE]; |
ff922622 | 2251 | ret = set_stream_info(tplg, stream, caps); |
abc3caac AS |
2252 | if (ret < 0) |
2253 | goto err; | |
0038be9a ML |
2254 | } |
2255 | ||
9aa3f034 | 2256 | if (d->flag_mask) |
5aebe7c7 PLB |
2257 | set_dai_flags(dai_drv, |
2258 | le32_to_cpu(d->flag_mask), | |
2259 | le32_to_cpu(d->flags)); | |
0038be9a ML |
2260 | |
2261 | /* pass control to component driver for optional further init */ | |
c60b613a | 2262 | ret = soc_tplg_dai_load(tplg, dai_drv, NULL, dai); |
0038be9a | 2263 | if (ret < 0) { |
e59db12b | 2264 | dev_err(tplg->dev, "ASoC: DAI loading failed\n"); |
abc3caac | 2265 | goto err; |
0038be9a ML |
2266 | } |
2267 | ||
2268 | return 0; | |
abc3caac AS |
2269 | |
2270 | err: | |
abc3caac | 2271 | return ret; |
0038be9a ML |
2272 | } |
2273 | ||
9aa3f034 ML |
2274 | /* load physical DAI elements */ |
2275 | static int soc_tplg_dai_elems_load(struct soc_tplg *tplg, | |
2276 | struct snd_soc_tplg_hdr *hdr) | |
0038be9a | 2277 | { |
5aebe7c7 | 2278 | int count; |
65a4cfdd | 2279 | int i; |
0038be9a | 2280 | |
5aebe7c7 PLB |
2281 | count = le32_to_cpu(hdr->count); |
2282 | ||
0038be9a ML |
2283 | /* config the existing BE DAIs */ |
2284 | for (i = 0; i < count; i++) { | |
65a4cfdd KM |
2285 | struct snd_soc_tplg_dai *dai = (struct snd_soc_tplg_dai *)tplg->pos; |
2286 | int ret; | |
2287 | ||
5aebe7c7 | 2288 | if (le32_to_cpu(dai->size) != sizeof(*dai)) { |
9aa3f034 | 2289 | dev_err(tplg->dev, "ASoC: invalid physical DAI size\n"); |
0038be9a ML |
2290 | return -EINVAL; |
2291 | } | |
2292 | ||
dd8e871d AS |
2293 | ret = soc_tplg_dai_config(tplg, dai); |
2294 | if (ret < 0) { | |
2295 | dev_err(tplg->dev, "ASoC: failed to configure DAI\n"); | |
2296 | return ret; | |
2297 | } | |
2298 | ||
5aebe7c7 | 2299 | tplg->pos += (sizeof(*dai) + le32_to_cpu(dai->priv.size)); |
0038be9a ML |
2300 | } |
2301 | ||
2302 | dev_dbg(tplg->dev, "ASoC: Configure %d BE DAIs\n", count); | |
2303 | return 0; | |
2304 | } | |
2305 | ||
583958fa ML |
2306 | /** |
2307 | * manifest_new_ver - Create a new version of manifest from the old version | |
2308 | * of source. | |
8abab35f | 2309 | * @tplg: topology context |
583958fa ML |
2310 | * @src: old version of manifest as a source |
2311 | * @manifest: latest version of manifest created from the source | |
2312 | * | |
ce1f2571 | 2313 | * Support from version 4. Users need free the returned manifest manually. |
583958fa ML |
2314 | */ |
2315 | static int manifest_new_ver(struct soc_tplg *tplg, | |
2316 | struct snd_soc_tplg_manifest *src, | |
2317 | struct snd_soc_tplg_manifest **manifest) | |
2318 | { | |
2319 | struct snd_soc_tplg_manifest *dest; | |
2320 | struct snd_soc_tplg_manifest_v4 *src_v4; | |
5aebe7c7 | 2321 | int size; |
583958fa ML |
2322 | |
2323 | *manifest = NULL; | |
2324 | ||
5aebe7c7 PLB |
2325 | size = le32_to_cpu(src->size); |
2326 | if (size != sizeof(*src_v4)) { | |
ac9391da | 2327 | dev_warn(tplg->dev, "ASoC: invalid manifest size %d\n", |
5aebe7c7 PLB |
2328 | size); |
2329 | if (size) | |
ac9391da | 2330 | return -EINVAL; |
5aebe7c7 | 2331 | src->size = cpu_to_le32(sizeof(*src_v4)); |
583958fa ML |
2332 | } |
2333 | ||
2334 | dev_warn(tplg->dev, "ASoC: old version of manifest\n"); | |
2335 | ||
2336 | src_v4 = (struct snd_soc_tplg_manifest_v4 *)src; | |
5aebe7c7 PLB |
2337 | dest = kzalloc(sizeof(*dest) + le32_to_cpu(src_v4->priv.size), |
2338 | GFP_KERNEL); | |
583958fa ML |
2339 | if (!dest) |
2340 | return -ENOMEM; | |
2341 | ||
5aebe7c7 | 2342 | dest->size = cpu_to_le32(sizeof(*dest)); /* size of latest abi version */ |
583958fa ML |
2343 | dest->control_elems = src_v4->control_elems; |
2344 | dest->widget_elems = src_v4->widget_elems; | |
2345 | dest->graph_elems = src_v4->graph_elems; | |
2346 | dest->pcm_elems = src_v4->pcm_elems; | |
2347 | dest->dai_link_elems = src_v4->dai_link_elems; | |
2348 | dest->priv.size = src_v4->priv.size; | |
2349 | if (dest->priv.size) | |
2350 | memcpy(dest->priv.data, src_v4->priv.data, | |
5aebe7c7 | 2351 | le32_to_cpu(src_v4->priv.size)); |
583958fa ML |
2352 | |
2353 | *manifest = dest; | |
2354 | return 0; | |
2355 | } | |
0038be9a | 2356 | |
8a978234 | 2357 | static int soc_tplg_manifest_load(struct soc_tplg *tplg, |
0038be9a | 2358 | struct snd_soc_tplg_hdr *hdr) |
8a978234 | 2359 | { |
583958fa ML |
2360 | struct snd_soc_tplg_manifest *manifest, *_manifest; |
2361 | bool abi_match; | |
242c46c0 | 2362 | int ret = 0; |
8a978234 | 2363 | |
8a978234 | 2364 | manifest = (struct snd_soc_tplg_manifest *)tplg->pos; |
06eb49f7 | 2365 | |
583958fa | 2366 | /* check ABI version by size, create a new manifest if abi not match */ |
5aebe7c7 | 2367 | if (le32_to_cpu(manifest->size) == sizeof(*manifest)) { |
583958fa ML |
2368 | abi_match = true; |
2369 | _manifest = manifest; | |
2370 | } else { | |
2371 | abi_match = false; | |
86e2d14b | 2372 | |
242c46c0 DT |
2373 | ret = manifest_new_ver(tplg, manifest, &_manifest); |
2374 | if (ret < 0) | |
2375 | return ret; | |
583958fa | 2376 | } |
8a978234 | 2377 | |
583958fa | 2378 | /* pass control to component driver for optional further init */ |
c42464a4 | 2379 | if (tplg->ops && tplg->ops->manifest) |
242c46c0 | 2380 | ret = tplg->ops->manifest(tplg->comp, tplg->index, _manifest); |
583958fa ML |
2381 | |
2382 | if (!abi_match) /* free the duplicated one */ | |
2383 | kfree(_manifest); | |
8a978234 | 2384 | |
242c46c0 | 2385 | return ret; |
8a978234 LG |
2386 | } |
2387 | ||
2388 | /* validate header magic, size and type */ | |
23e591dc | 2389 | static int soc_tplg_valid_header(struct soc_tplg *tplg, |
8a978234 LG |
2390 | struct snd_soc_tplg_hdr *hdr) |
2391 | { | |
2392 | if (soc_tplg_get_hdr_offset(tplg) >= tplg->fw->size) | |
2393 | return 0; | |
2394 | ||
5aebe7c7 | 2395 | if (le32_to_cpu(hdr->size) != sizeof(*hdr)) { |
06eb49f7 ML |
2396 | dev_err(tplg->dev, |
2397 | "ASoC: invalid header size for type %d at offset 0x%lx size 0x%zx.\n", | |
5aebe7c7 | 2398 | le32_to_cpu(hdr->type), soc_tplg_get_hdr_offset(tplg), |
06eb49f7 ML |
2399 | tplg->fw->size); |
2400 | return -EINVAL; | |
2401 | } | |
2402 | ||
c5d184c9 | 2403 | if (soc_tplg_get_hdr_offset(tplg) + le32_to_cpu(hdr->payload_size) >= tplg->fw->size) { |
86e2d14b CR |
2404 | dev_err(tplg->dev, |
2405 | "ASoC: invalid header of type %d at offset %ld payload_size %d\n", | |
2406 | le32_to_cpu(hdr->type), soc_tplg_get_hdr_offset(tplg), | |
2407 | hdr->payload_size); | |
2408 | return -EINVAL; | |
2409 | } | |
2410 | ||
8a978234 | 2411 | /* big endian firmware objects not supported atm */ |
26d87881 | 2412 | if (le32_to_cpu(hdr->magic) == SOC_TPLG_MAGIC_BIG_ENDIAN) { |
8a978234 LG |
2413 | dev_err(tplg->dev, |
2414 | "ASoC: pass %d big endian not supported header got %x at offset 0x%lx size 0x%zx.\n", | |
2415 | tplg->pass, hdr->magic, | |
2416 | soc_tplg_get_hdr_offset(tplg), tplg->fw->size); | |
2417 | return -EINVAL; | |
2418 | } | |
2419 | ||
5aebe7c7 | 2420 | if (le32_to_cpu(hdr->magic) != SND_SOC_TPLG_MAGIC) { |
8a978234 LG |
2421 | dev_err(tplg->dev, |
2422 | "ASoC: pass %d does not have a valid header got %x at offset 0x%lx size 0x%zx.\n", | |
2423 | tplg->pass, hdr->magic, | |
2424 | soc_tplg_get_hdr_offset(tplg), tplg->fw->size); | |
2425 | return -EINVAL; | |
2426 | } | |
2427 | ||
288b8da7 | 2428 | /* Support ABI from version 4 */ |
5aebe7c7 PLB |
2429 | if (le32_to_cpu(hdr->abi) > SND_SOC_TPLG_ABI_VERSION || |
2430 | le32_to_cpu(hdr->abi) < SND_SOC_TPLG_ABI_VERSION_MIN) { | |
8a978234 LG |
2431 | dev_err(tplg->dev, |
2432 | "ASoC: pass %d invalid ABI version got 0x%x need 0x%x at offset 0x%lx size 0x%zx.\n", | |
2433 | tplg->pass, hdr->abi, | |
2434 | SND_SOC_TPLG_ABI_VERSION, soc_tplg_get_hdr_offset(tplg), | |
2435 | tplg->fw->size); | |
2436 | return -EINVAL; | |
2437 | } | |
2438 | ||
2439 | if (hdr->payload_size == 0) { | |
2440 | dev_err(tplg->dev, "ASoC: header has 0 size at offset 0x%lx.\n", | |
2441 | soc_tplg_get_hdr_offset(tplg)); | |
2442 | return -EINVAL; | |
2443 | } | |
2444 | ||
8a978234 LG |
2445 | return 1; |
2446 | } | |
2447 | ||
2448 | /* check header type and call appropriate handler */ | |
2449 | static int soc_tplg_load_header(struct soc_tplg *tplg, | |
2450 | struct snd_soc_tplg_hdr *hdr) | |
2451 | { | |
82ed7418 KJ |
2452 | int (*elem_load)(struct soc_tplg *tplg, |
2453 | struct snd_soc_tplg_hdr *hdr); | |
2454 | unsigned int hdr_pass; | |
2455 | ||
8a978234 LG |
2456 | tplg->pos = tplg->hdr_pos + sizeof(struct snd_soc_tplg_hdr); |
2457 | ||
5aebe7c7 | 2458 | tplg->index = le32_to_cpu(hdr->index); |
8a978234 | 2459 | |
5aebe7c7 | 2460 | switch (le32_to_cpu(hdr->type)) { |
8a978234 LG |
2461 | case SND_SOC_TPLG_TYPE_MIXER: |
2462 | case SND_SOC_TPLG_TYPE_ENUM: | |
2463 | case SND_SOC_TPLG_TYPE_BYTES: | |
5e2cd47a | 2464 | hdr_pass = SOC_TPLG_PASS_CONTROL; |
82ed7418 KJ |
2465 | elem_load = soc_tplg_kcontrol_elems_load; |
2466 | break; | |
8a978234 | 2467 | case SND_SOC_TPLG_TYPE_DAPM_GRAPH: |
82ed7418 KJ |
2468 | hdr_pass = SOC_TPLG_PASS_GRAPH; |
2469 | elem_load = soc_tplg_dapm_graph_elems_load; | |
2470 | break; | |
8a978234 | 2471 | case SND_SOC_TPLG_TYPE_DAPM_WIDGET: |
82ed7418 KJ |
2472 | hdr_pass = SOC_TPLG_PASS_WIDGET; |
2473 | elem_load = soc_tplg_dapm_widget_elems_load; | |
2474 | break; | |
8a978234 | 2475 | case SND_SOC_TPLG_TYPE_PCM: |
82ed7418 KJ |
2476 | hdr_pass = SOC_TPLG_PASS_PCM_DAI; |
2477 | elem_load = soc_tplg_pcm_elems_load; | |
2478 | break; | |
3fbf7935 | 2479 | case SND_SOC_TPLG_TYPE_DAI: |
82ed7418 KJ |
2480 | hdr_pass = SOC_TPLG_PASS_BE_DAI; |
2481 | elem_load = soc_tplg_dai_elems_load; | |
2482 | break; | |
593d9e52 ML |
2483 | case SND_SOC_TPLG_TYPE_DAI_LINK: |
2484 | case SND_SOC_TPLG_TYPE_BACKEND_LINK: | |
2485 | /* physical link configurations */ | |
82ed7418 KJ |
2486 | hdr_pass = SOC_TPLG_PASS_LINK; |
2487 | elem_load = soc_tplg_link_elems_load; | |
2488 | break; | |
8a978234 | 2489 | case SND_SOC_TPLG_TYPE_MANIFEST: |
82ed7418 KJ |
2490 | hdr_pass = SOC_TPLG_PASS_MANIFEST; |
2491 | elem_load = soc_tplg_manifest_load; | |
2492 | break; | |
8a978234 LG |
2493 | default: |
2494 | /* bespoke vendor data object */ | |
82ed7418 KJ |
2495 | hdr_pass = SOC_TPLG_PASS_VENDOR; |
2496 | elem_load = soc_tplg_vendor_load; | |
2497 | break; | |
2498 | } | |
2499 | ||
2500 | if (tplg->pass == hdr_pass) { | |
2501 | dev_dbg(tplg->dev, | |
2502 | "ASoC: Got 0x%x bytes of type %d version %d vendor %d at pass %d\n", | |
2503 | hdr->payload_size, hdr->type, hdr->version, | |
2504 | hdr->vendor_type, tplg->pass); | |
2505 | return elem_load(tplg, hdr); | |
8a978234 LG |
2506 | } |
2507 | ||
2508 | return 0; | |
2509 | } | |
2510 | ||
2511 | /* process the topology file headers */ | |
2512 | static int soc_tplg_process_headers(struct soc_tplg *tplg) | |
2513 | { | |
8a978234 LG |
2514 | int ret; |
2515 | ||
8a978234 | 2516 | /* process the header types from start to end */ |
395f8fd6 | 2517 | for (tplg->pass = SOC_TPLG_PASS_START; tplg->pass <= SOC_TPLG_PASS_END; tplg->pass++) { |
f79e4b2a | 2518 | struct snd_soc_tplg_hdr *hdr; |
8a978234 LG |
2519 | |
2520 | tplg->hdr_pos = tplg->fw->data; | |
2521 | hdr = (struct snd_soc_tplg_hdr *)tplg->hdr_pos; | |
2522 | ||
2523 | while (!soc_tplg_is_eof(tplg)) { | |
2524 | ||
2525 | /* make sure header is valid before loading */ | |
23e591dc | 2526 | ret = soc_tplg_valid_header(tplg, hdr); |
8bf9475f PLB |
2527 | if (ret < 0) { |
2528 | dev_err(tplg->dev, | |
2529 | "ASoC: topology: invalid header: %d\n", ret); | |
8a978234 | 2530 | return ret; |
8bf9475f | 2531 | } else if (ret == 0) { |
8a978234 | 2532 | break; |
8bf9475f | 2533 | } |
8a978234 LG |
2534 | |
2535 | /* load the header object */ | |
2536 | ret = soc_tplg_load_header(tplg, hdr); | |
8bf9475f PLB |
2537 | if (ret < 0) { |
2538 | dev_err(tplg->dev, | |
2539 | "ASoC: topology: could not load header: %d\n", ret); | |
8a978234 | 2540 | return ret; |
8bf9475f | 2541 | } |
8a978234 LG |
2542 | |
2543 | /* goto next header */ | |
5aebe7c7 | 2544 | tplg->hdr_pos += le32_to_cpu(hdr->payload_size) + |
8a978234 LG |
2545 | sizeof(struct snd_soc_tplg_hdr); |
2546 | hdr = (struct snd_soc_tplg_hdr *)tplg->hdr_pos; | |
2547 | } | |
2548 | ||
8a978234 LG |
2549 | } |
2550 | ||
2551 | /* signal DAPM we are complete */ | |
2552 | ret = soc_tplg_dapm_complete(tplg); | |
2553 | if (ret < 0) | |
2554 | dev_err(tplg->dev, | |
2555 | "ASoC: failed to initialise DAPM from Firmware\n"); | |
2556 | ||
2557 | return ret; | |
2558 | } | |
2559 | ||
2560 | static int soc_tplg_load(struct soc_tplg *tplg) | |
2561 | { | |
2562 | int ret; | |
2563 | ||
2564 | ret = soc_tplg_process_headers(tplg); | |
2565 | if (ret == 0) | |
415717e1 | 2566 | return soc_tplg_complete(tplg); |
8a978234 LG |
2567 | |
2568 | return ret; | |
2569 | } | |
2570 | ||
2571 | /* load audio component topology from "firmware" file */ | |
2572 | int snd_soc_tplg_component_load(struct snd_soc_component *comp, | |
a5b8f71c | 2573 | struct snd_soc_tplg_ops *ops, const struct firmware *fw) |
8a978234 LG |
2574 | { |
2575 | struct soc_tplg tplg; | |
304017d3 | 2576 | int ret; |
8a978234 | 2577 | |
d40ab86f AS |
2578 | /* |
2579 | * check if we have sane parameters: | |
2580 | * comp - needs to exist to keep and reference data while parsing | |
d40ab86f | 2581 | * comp->card - used for setting card related parameters |
f714fbc1 | 2582 | * comp->card->dev - used for resource management and prints |
d40ab86f AS |
2583 | * fw - we need it, as it is the very thing we parse |
2584 | */ | |
f714fbc1 | 2585 | if (!comp || !comp->card || !comp->card->dev || !fw) |
c42464a4 | 2586 | return -EINVAL; |
8a978234 LG |
2587 | |
2588 | /* setup parsing context */ | |
2589 | memset(&tplg, 0, sizeof(tplg)); | |
2590 | tplg.fw = fw; | |
f714fbc1 | 2591 | tplg.dev = comp->card->dev; |
8a978234 | 2592 | tplg.comp = comp; |
9c88a983 AS |
2593 | if (ops) { |
2594 | tplg.ops = ops; | |
2595 | tplg.io_ops = ops->io_ops; | |
2596 | tplg.io_ops_count = ops->io_ops_count; | |
2597 | tplg.bytes_ext_ops = ops->bytes_ext_ops; | |
2598 | tplg.bytes_ext_ops_count = ops->bytes_ext_ops_count; | |
2599 | } | |
8a978234 | 2600 | |
304017d3 B |
2601 | ret = soc_tplg_load(&tplg); |
2602 | /* free the created components if fail to load topology */ | |
2603 | if (ret) | |
a5b8f71c | 2604 | snd_soc_tplg_component_remove(comp); |
304017d3 B |
2605 | |
2606 | return ret; | |
8a978234 LG |
2607 | } |
2608 | EXPORT_SYMBOL_GPL(snd_soc_tplg_component_load); | |
2609 | ||
8a978234 | 2610 | /* remove dynamic controls from the component driver */ |
a5b8f71c | 2611 | int snd_soc_tplg_component_remove(struct snd_soc_component *comp) |
8a978234 | 2612 | { |
7e567b5a | 2613 | struct snd_card *card = comp->card->snd_card; |
8a978234 | 2614 | struct snd_soc_dobj *dobj, *next_dobj; |
395f8fd6 | 2615 | int pass; |
8a978234 LG |
2616 | |
2617 | /* process the header types from end to start */ | |
395f8fd6 | 2618 | for (pass = SOC_TPLG_PASS_END; pass >= SOC_TPLG_PASS_START; pass--) { |
8a978234 LG |
2619 | |
2620 | /* remove mixer controls */ | |
7e567b5a | 2621 | down_write(&card->controls_rwsem); |
8a978234 LG |
2622 | list_for_each_entry_safe(dobj, next_dobj, &comp->dobj_list, |
2623 | list) { | |
2624 | ||
8a978234 LG |
2625 | switch (dobj->type) { |
2626 | case SND_SOC_DOBJ_MIXER: | |
2abfd4bd | 2627 | soc_tplg_remove_mixer(comp, dobj, pass); |
8a978234 LG |
2628 | break; |
2629 | case SND_SOC_DOBJ_ENUM: | |
2abfd4bd | 2630 | soc_tplg_remove_enum(comp, dobj, pass); |
8a978234 LG |
2631 | break; |
2632 | case SND_SOC_DOBJ_BYTES: | |
2abfd4bd | 2633 | soc_tplg_remove_bytes(comp, dobj, pass); |
8a978234 | 2634 | break; |
7df04ea7 | 2635 | case SND_SOC_DOBJ_GRAPH: |
2abfd4bd | 2636 | soc_tplg_remove_route(comp, dobj, pass); |
7df04ea7 | 2637 | break; |
8a978234 | 2638 | case SND_SOC_DOBJ_WIDGET: |
2abfd4bd | 2639 | soc_tplg_remove_widget(comp, dobj, pass); |
8a978234 LG |
2640 | break; |
2641 | case SND_SOC_DOBJ_PCM: | |
2abfd4bd | 2642 | soc_tplg_remove_dai(comp, dobj, pass); |
8a978234 | 2643 | break; |
acfc7d46 | 2644 | case SND_SOC_DOBJ_DAI_LINK: |
2abfd4bd | 2645 | soc_tplg_remove_link(comp, dobj, pass); |
acfc7d46 | 2646 | break; |
adfebb51 B |
2647 | case SND_SOC_DOBJ_BACKEND_LINK: |
2648 | /* | |
2649 | * call link_unload ops if extra | |
2650 | * deinitialization is needed. | |
2651 | */ | |
2652 | remove_backend_link(comp, dobj, pass); | |
2653 | break; | |
8a978234 LG |
2654 | default: |
2655 | dev_err(comp->dev, "ASoC: invalid component type %d for removal\n", | |
2656 | dobj->type); | |
2657 | break; | |
2658 | } | |
2659 | } | |
7e567b5a | 2660 | up_write(&card->controls_rwsem); |
8a978234 LG |
2661 | } |
2662 | ||
2663 | /* let caller know if FW can be freed when no objects are left */ | |
2664 | return !list_empty(&comp->dobj_list); | |
2665 | } | |
2666 | EXPORT_SYMBOL_GPL(snd_soc_tplg_component_remove); |