Commit | Line | Data |
---|---|---|
e149ca29 | 1 | // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) |
ee1e79b7 RS |
2 | // |
3 | // This file is provided under a dual BSD/GPLv2 license. When using or | |
4 | // redistributing this file, you may do so under either license. | |
5 | // | |
6 | // Copyright(c) 2019 Intel Corporation. All rights reserved. | |
7 | // | |
8 | // Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> | |
9 | // | |
10 | ||
b30b60a2 | 11 | #include <linux/bitfield.h> |
fa6e73d6 | 12 | #include <trace/events/sof.h> |
ee1e79b7 | 13 | #include "sof-audio.h" |
6ace85b9 | 14 | #include "sof-of-dev.h" |
ee1e79b7 RS |
15 | #include "ops.h" |
16 | ||
5fcdbb2d RS |
17 | static void sof_reset_route_setup_status(struct snd_sof_dev *sdev, struct snd_sof_widget *widget) |
18 | { | |
d77d7795 | 19 | const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; |
5fcdbb2d RS |
20 | struct snd_sof_route *sroute; |
21 | ||
22 | list_for_each_entry(sroute, &sdev->route_list, list) | |
d77d7795 RS |
23 | if (sroute->src_widget == widget || sroute->sink_widget == widget) { |
24 | if (sroute->setup && tplg_ops->route_free) | |
25 | tplg_ops->route_free(sdev, sroute); | |
26 | ||
5fcdbb2d | 27 | sroute->setup = false; |
d77d7795 | 28 | } |
5fcdbb2d RS |
29 | } |
30 | ||
8b001416 RS |
31 | int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) |
32 | { | |
051744b1 RS |
33 | const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; |
34 | int err = 0; | |
35 | int ret; | |
8b001416 RS |
36 | |
37 | if (!swidget->private) | |
38 | return 0; | |
39 | ||
fa6e73d6 BL |
40 | trace_sof_widget_free(swidget); |
41 | ||
8b001416 RS |
42 | /* only free when use_count is 0 */ |
43 | if (--swidget->use_count) | |
44 | return 0; | |
45 | ||
33a3facd RS |
46 | /* reset route setup status for all routes that contain this widget */ |
47 | sof_reset_route_setup_status(sdev, swidget); | |
48 | ||
9ea80748 | 49 | /* continue to disable core even if IPC fails */ |
051744b1 RS |
50 | if (tplg_ops->widget_free) |
51 | err = tplg_ops->widget_free(sdev, swidget); | |
9ea80748 RS |
52 | |
53 | /* | |
54 | * disable widget core. continue to route setup status and complete flag | |
55 | * even if this fails and return the appropriate error | |
56 | */ | |
40c2c63a RS |
57 | ret = snd_sof_dsp_core_put(sdev, swidget->core); |
58 | if (ret < 0) { | |
9ea80748 | 59 | dev_err(sdev->dev, "error: failed to disable target core: %d for widget %s\n", |
3a790f3a | 60 | swidget->core, swidget->widget->name); |
40c2c63a RS |
61 | if (!err) |
62 | err = ret; | |
8b001416 RS |
63 | } |
64 | ||
40c2c63a RS |
65 | /* |
66 | * free the scheduler widget (same as pipe_widget) associated with the current swidget. | |
67 | * skip for static pipelines | |
68 | */ | |
69 | if (swidget->dynamic_pipeline_widget && swidget->id != snd_soc_dapm_scheduler) { | |
70 | ret = sof_widget_free(sdev, swidget->pipe_widget); | |
71 | if (ret < 0 && !err) | |
72 | err = ret; | |
463a809b | 73 | swidget->pipe_widget->complete = 0; |
40c2c63a RS |
74 | } |
75 | ||
76 | if (!err) | |
9ea80748 RS |
77 | dev_dbg(sdev->dev, "widget %s freed\n", swidget->widget->name); |
78 | ||
40c2c63a | 79 | return err; |
8b001416 RS |
80 | } |
81 | EXPORT_SYMBOL(sof_widget_free); | |
82 | ||
83 | int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) | |
93d71245 | 84 | { |
051744b1 | 85 | const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; |
93d71245 RS |
86 | int ret; |
87 | ||
88 | /* skip if there is no private data */ | |
89 | if (!swidget->private) | |
90 | return 0; | |
91 | ||
fa6e73d6 BL |
92 | trace_sof_widget_setup(swidget); |
93 | ||
8b001416 RS |
94 | /* widget already set up */ |
95 | if (++swidget->use_count > 1) | |
96 | return 0; | |
97 | ||
40c2c63a RS |
98 | /* |
99 | * The scheduler widget for a pipeline is not part of the connected DAPM | |
100 | * widget list and it needs to be set up before the widgets in the pipeline | |
101 | * are set up. The use_count for the scheduler widget is incremented for every | |
102 | * widget in a given pipeline to ensure that it is freed only after the last | |
103 | * widget in the pipeline is freed. Skip setting up scheduler widget for static pipelines. | |
104 | */ | |
105 | if (swidget->dynamic_pipeline_widget && swidget->id != snd_soc_dapm_scheduler) { | |
106 | if (!swidget->pipe_widget) { | |
107 | dev_err(sdev->dev, "No scheduler widget set for %s\n", | |
108 | swidget->widget->name); | |
109 | ret = -EINVAL; | |
110 | goto use_count_dec; | |
111 | } | |
112 | ||
113 | ret = sof_widget_setup(sdev, swidget->pipe_widget); | |
114 | if (ret < 0) | |
115 | goto use_count_dec; | |
116 | } | |
117 | ||
9ea80748 | 118 | /* enable widget core */ |
3a790f3a | 119 | ret = snd_sof_dsp_core_get(sdev, swidget->core); |
93d71245 | 120 | if (ret < 0) { |
9ea80748 RS |
121 | dev_err(sdev->dev, "error: failed to enable target core for widget %s\n", |
122 | swidget->widget->name); | |
40c2c63a | 123 | goto pipe_widget_free; |
93d71245 RS |
124 | } |
125 | ||
051744b1 RS |
126 | /* setup widget in the DSP */ |
127 | if (tplg_ops->widget_setup) { | |
128 | ret = tplg_ops->widget_setup(sdev, swidget); | |
129 | if (ret < 0) | |
9ea80748 | 130 | goto core_put; |
051744b1 | 131 | } |
5fcdbb2d | 132 | |
051744b1 RS |
133 | /* send config for DAI components */ |
134 | if (WIDGET_IS_DAI(swidget->id)) { | |
135 | unsigned int flags = SOF_DAI_CONFIG_FLAGS_NONE; | |
9ea80748 | 136 | |
051744b1 RS |
137 | if (tplg_ops->dai_config) { |
138 | ret = tplg_ops->dai_config(sdev, swidget, flags, NULL); | |
139 | if (ret < 0) | |
140 | goto widget_free; | |
5fcdbb2d | 141 | } |
5f3aad73 RS |
142 | } |
143 | ||
144 | /* restore kcontrols for widget */ | |
50d4d8cf PU |
145 | if (tplg_ops->control->widget_kcontrol_setup) { |
146 | ret = tplg_ops->control->widget_kcontrol_setup(sdev, swidget); | |
147 | if (ret < 0) | |
148 | goto widget_free; | |
5f3aad73 RS |
149 | } |
150 | ||
151 | dev_dbg(sdev->dev, "widget %s setup complete\n", swidget->widget->name); | |
93d71245 | 152 | |
8b001416 RS |
153 | return 0; |
154 | ||
051744b1 RS |
155 | widget_free: |
156 | /* widget use_count and core ref_count will both be decremented by sof_widget_free() */ | |
157 | sof_widget_free(sdev, swidget); | |
9ea80748 | 158 | core_put: |
3a790f3a | 159 | snd_sof_dsp_core_put(sdev, swidget->core); |
40c2c63a RS |
160 | pipe_widget_free: |
161 | if (swidget->id != snd_soc_dapm_scheduler) | |
162 | sof_widget_free(sdev, swidget->pipe_widget); | |
8b001416 RS |
163 | use_count_dec: |
164 | swidget->use_count--; | |
93d71245 RS |
165 | return ret; |
166 | } | |
8b001416 | 167 | EXPORT_SYMBOL(sof_widget_setup); |
93d71245 | 168 | |
3816bbea RS |
169 | int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsource, |
170 | struct snd_soc_dapm_widget *wsink) | |
5fcdbb2d | 171 | { |
85ec8560 | 172 | const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; |
5fcdbb2d RS |
173 | struct snd_sof_widget *src_widget = wsource->dobj.private; |
174 | struct snd_sof_widget *sink_widget = wsink->dobj.private; | |
175 | struct snd_sof_route *sroute; | |
176 | bool route_found = false; | |
85ec8560 | 177 | int ret; |
5fcdbb2d RS |
178 | |
179 | /* ignore routes involving virtual widgets in topology */ | |
180 | switch (src_widget->id) { | |
181 | case snd_soc_dapm_out_drv: | |
182 | case snd_soc_dapm_output: | |
183 | case snd_soc_dapm_input: | |
184 | return 0; | |
185 | default: | |
186 | break; | |
187 | } | |
188 | ||
189 | switch (sink_widget->id) { | |
190 | case snd_soc_dapm_out_drv: | |
191 | case snd_soc_dapm_output: | |
192 | case snd_soc_dapm_input: | |
193 | return 0; | |
194 | default: | |
195 | break; | |
196 | } | |
197 | ||
198 | /* find route matching source and sink widgets */ | |
199 | list_for_each_entry(sroute, &sdev->route_list, list) | |
200 | if (sroute->src_widget == src_widget && sroute->sink_widget == sink_widget) { | |
201 | route_found = true; | |
202 | break; | |
203 | } | |
204 | ||
205 | if (!route_found) { | |
206 | dev_err(sdev->dev, "error: cannot find SOF route for source %s -> %s sink\n", | |
207 | wsource->name, wsink->name); | |
208 | return -EINVAL; | |
209 | } | |
210 | ||
85ec8560 RS |
211 | /* nothing to do if route is already set up */ |
212 | if (sroute->setup) | |
213 | return 0; | |
214 | ||
215 | ret = ipc_tplg_ops->route_setup(sdev, sroute); | |
216 | if (ret < 0) | |
217 | return ret; | |
218 | ||
219 | sroute->setup = true; | |
220 | return 0; | |
5fcdbb2d RS |
221 | } |
222 | ||
223 | static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev, | |
224 | struct snd_soc_dapm_widget_list *list, int dir) | |
225 | { | |
226 | struct snd_soc_dapm_widget *widget; | |
227 | struct snd_soc_dapm_path *p; | |
228 | int ret; | |
229 | int i; | |
230 | ||
231 | /* | |
232 | * Set up connections between widgets in the sink/source paths based on direction. | |
233 | * Some non-SOF widgets exist in topology either for compatibility or for the | |
234 | * purpose of connecting a pipeline from a host to a DAI in order to receive the DAPM | |
235 | * events. But they are not handled by the firmware. So ignore them. | |
236 | */ | |
237 | if (dir == SNDRV_PCM_STREAM_PLAYBACK) { | |
238 | for_each_dapm_widgets(list, i, widget) { | |
239 | if (!widget->dobj.private) | |
240 | continue; | |
241 | ||
242 | snd_soc_dapm_widget_for_each_sink_path(widget, p) | |
243 | if (p->sink->dobj.private) { | |
244 | ret = sof_route_setup(sdev, widget, p->sink); | |
245 | if (ret < 0) | |
246 | return ret; | |
247 | } | |
248 | } | |
249 | } else { | |
250 | for_each_dapm_widgets(list, i, widget) { | |
251 | if (!widget->dobj.private) | |
252 | continue; | |
253 | ||
254 | snd_soc_dapm_widget_for_each_source_path(widget, p) | |
255 | if (p->source->dobj.private) { | |
256 | ret = sof_route_setup(sdev, p->source, widget); | |
257 | if (ret < 0) | |
258 | return ret; | |
259 | } | |
260 | } | |
261 | } | |
262 | ||
263 | return 0; | |
264 | } | |
265 | ||
66344c6d RS |
266 | static void |
267 | sof_unprepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget) | |
268 | { | |
269 | const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; | |
270 | const struct sof_ipc_tplg_widget_ops *widget_ops = ipc_tplg_ops->widget; | |
271 | struct snd_sof_widget *swidget = widget->dobj.private; | |
272 | struct snd_soc_dapm_path *p; | |
273 | ||
cc755b43 | 274 | /* skip if the widget is in use or if it is already unprepared */ |
0ad84b11 | 275 | if (!swidget || !swidget->prepared || swidget->use_count > 0) |
cc755b43 | 276 | goto sink_unprepare; |
ce59804d RW |
277 | |
278 | if (widget_ops[widget->id].ipc_unprepare) | |
279 | /* unprepare the source widget */ | |
280 | widget_ops[widget->id].ipc_unprepare(swidget); | |
66344c6d | 281 | |
66344c6d RS |
282 | swidget->prepared = false; |
283 | ||
cc755b43 | 284 | sink_unprepare: |
66344c6d RS |
285 | /* unprepare all widgets in the sink paths */ |
286 | snd_soc_dapm_widget_for_each_sink_path(widget, p) { | |
287 | if (!p->walking && p->sink->dobj.private) { | |
288 | p->walking = true; | |
289 | sof_unprepare_widgets_in_path(sdev, p->sink); | |
290 | p->walking = false; | |
291 | } | |
292 | } | |
293 | } | |
294 | ||
295 | static int | |
296 | sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget, | |
297 | struct snd_pcm_hw_params *fe_params, | |
298 | struct snd_sof_platform_stream_params *platform_params, | |
299 | struct snd_pcm_hw_params *pipeline_params, int dir) | |
300 | { | |
301 | const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; | |
302 | const struct sof_ipc_tplg_widget_ops *widget_ops = ipc_tplg_ops->widget; | |
303 | struct snd_sof_widget *swidget = widget->dobj.private; | |
304 | struct snd_soc_dapm_path *p; | |
305 | int ret; | |
306 | ||
0ad84b11 | 307 | if (!swidget || !widget_ops[widget->id].ipc_prepare || swidget->prepared) |
66344c6d RS |
308 | goto sink_prepare; |
309 | ||
310 | /* prepare the source widget */ | |
311 | ret = widget_ops[widget->id].ipc_prepare(swidget, fe_params, platform_params, | |
312 | pipeline_params, dir); | |
313 | if (ret < 0) { | |
314 | dev_err(sdev->dev, "failed to prepare widget %s\n", widget->name); | |
315 | return ret; | |
316 | } | |
317 | ||
318 | swidget->prepared = true; | |
319 | ||
320 | sink_prepare: | |
321 | /* prepare all widgets in the sink paths */ | |
322 | snd_soc_dapm_widget_for_each_sink_path(widget, p) { | |
323 | if (!p->walking && p->sink->dobj.private) { | |
324 | p->walking = true; | |
325 | ret = sof_prepare_widgets_in_path(sdev, p->sink, fe_params, | |
326 | platform_params, pipeline_params, dir); | |
327 | p->walking = false; | |
328 | if (ret < 0) { | |
329 | /* unprepare the source widget */ | |
fb429360 PU |
330 | if (widget_ops[widget->id].ipc_unprepare && |
331 | swidget && swidget->prepared) { | |
66344c6d RS |
332 | widget_ops[widget->id].ipc_unprepare(swidget); |
333 | swidget->prepared = false; | |
334 | } | |
335 | return ret; | |
336 | } | |
337 | } | |
338 | } | |
339 | ||
340 | return 0; | |
341 | } | |
342 | ||
5da0590a RS |
343 | /* |
344 | * free all widgets in the sink path starting from the source widget | |
345 | * (DAI type for capture, AIF type for playback) | |
346 | */ | |
347 | static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget, | |
348 | int dir) | |
349 | { | |
350 | struct snd_soc_dapm_path *p; | |
351 | int err; | |
352 | int ret = 0; | |
353 | ||
354 | /* free all widgets even in case of error to keep use counts balanced */ | |
355 | snd_soc_dapm_widget_for_each_sink_path(widget, p) { | |
356 | if (!p->walking && p->sink->dobj.private && widget->dobj.private) { | |
357 | p->walking = true; | |
358 | if (WIDGET_IS_AIF_OR_DAI(widget->id)) { | |
359 | err = sof_widget_free(sdev, widget->dobj.private); | |
360 | if (err < 0) | |
361 | ret = err; | |
362 | } | |
363 | ||
364 | err = sof_widget_free(sdev, p->sink->dobj.private); | |
365 | if (err < 0) | |
366 | ret = err; | |
367 | ||
368 | err = sof_free_widgets_in_path(sdev, p->sink, dir); | |
369 | if (err < 0) | |
370 | ret = err; | |
371 | p->walking = false; | |
372 | } | |
373 | } | |
374 | ||
375 | return ret; | |
376 | } | |
377 | ||
378 | /* | |
379 | * set up all widgets in the sink path starting from the source widget | |
380 | * (DAI type for capture, AIF type for playback). | |
381 | * The error path in this function ensures that all successfully set up widgets getting freed. | |
382 | */ | |
383 | static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget, | |
384 | int dir) | |
385 | { | |
386 | struct snd_soc_dapm_path *p; | |
387 | int ret; | |
388 | ||
389 | snd_soc_dapm_widget_for_each_sink_path(widget, p) { | |
390 | if (!p->walking && p->sink->dobj.private && widget->dobj.private) { | |
391 | p->walking = true; | |
392 | if (WIDGET_IS_AIF_OR_DAI(widget->id)) { | |
393 | ret = sof_widget_setup(sdev, widget->dobj.private); | |
394 | if (ret < 0) | |
395 | goto out; | |
396 | } | |
397 | ||
398 | ret = sof_widget_setup(sdev, p->sink->dobj.private); | |
399 | if (ret < 0) { | |
400 | if (WIDGET_IS_AIF_OR_DAI(widget->id)) | |
401 | sof_widget_free(sdev, widget->dobj.private); | |
402 | goto out; | |
403 | } | |
404 | ||
405 | ret = sof_set_up_widgets_in_path(sdev, p->sink, dir); | |
406 | if (ret < 0) { | |
407 | if (WIDGET_IS_AIF_OR_DAI(widget->id)) | |
408 | sof_widget_free(sdev, widget->dobj.private); | |
409 | sof_widget_free(sdev, p->sink->dobj.private); | |
410 | } | |
411 | out: | |
412 | p->walking = false; | |
413 | if (ret < 0) | |
414 | return ret; | |
415 | } | |
416 | } | |
417 | ||
418 | return 0; | |
419 | } | |
420 | ||
421 | static int | |
66344c6d RS |
422 | sof_walk_widgets_in_order(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget_list *list, |
423 | struct snd_pcm_hw_params *fe_params, | |
424 | struct snd_sof_platform_stream_params *platform_params, int dir, | |
425 | enum sof_widget_op op) | |
5da0590a RS |
426 | { |
427 | struct snd_soc_dapm_widget *widget; | |
66344c6d RS |
428 | char *str; |
429 | int ret = 0; | |
430 | int i; | |
5da0590a RS |
431 | |
432 | for_each_dapm_widgets(list, i, widget) { | |
433 | /* starting widget for playback is AIF type */ | |
fcc4348a | 434 | if (dir == SNDRV_PCM_STREAM_PLAYBACK && widget->id != snd_soc_dapm_aif_in) |
5da0590a RS |
435 | continue; |
436 | ||
437 | /* starting widget for capture is DAI type */ | |
fcc4348a | 438 | if (dir == SNDRV_PCM_STREAM_CAPTURE && widget->id != snd_soc_dapm_dai_out) |
5da0590a RS |
439 | continue; |
440 | ||
441 | switch (op) { | |
442 | case SOF_WIDGET_SETUP: | |
443 | ret = sof_set_up_widgets_in_path(sdev, widget, dir); | |
66344c6d | 444 | str = "set up"; |
5da0590a RS |
445 | break; |
446 | case SOF_WIDGET_FREE: | |
447 | ret = sof_free_widgets_in_path(sdev, widget, dir); | |
66344c6d RS |
448 | str = "free"; |
449 | break; | |
450 | case SOF_WIDGET_PREPARE: | |
451 | { | |
452 | struct snd_pcm_hw_params pipeline_params; | |
453 | ||
454 | str = "prepare"; | |
455 | /* | |
456 | * When walking the list of connected widgets, the pipeline_params for each | |
457 | * widget is modified by the source widget in the path. Use a local | |
458 | * copy of the runtime params as the pipeline_params so that the runtime | |
459 | * params does not get overwritten. | |
460 | */ | |
461 | memcpy(&pipeline_params, fe_params, sizeof(*fe_params)); | |
462 | ||
463 | ret = sof_prepare_widgets_in_path(sdev, widget, fe_params, | |
464 | platform_params, &pipeline_params, dir); | |
465 | break; | |
466 | } | |
467 | case SOF_WIDGET_UNPREPARE: | |
468 | sof_unprepare_widgets_in_path(sdev, widget); | |
5da0590a RS |
469 | break; |
470 | default: | |
471 | dev_err(sdev->dev, "Invalid widget op %d\n", op); | |
472 | return -EINVAL; | |
473 | } | |
474 | if (ret < 0) { | |
66344c6d | 475 | dev_err(sdev->dev, "Failed to %s connected widgets\n", str); |
5da0590a RS |
476 | return ret; |
477 | } | |
478 | } | |
479 | ||
480 | return 0; | |
481 | } | |
482 | ||
66344c6d RS |
483 | int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, |
484 | struct snd_pcm_hw_params *fe_params, | |
485 | struct snd_sof_platform_stream_params *platform_params, | |
486 | int dir) | |
5fcdbb2d | 487 | { |
61ad28ff | 488 | const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; |
5fcdbb2d RS |
489 | struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list; |
490 | struct snd_soc_dapm_widget *widget; | |
5da0590a | 491 | int i, ret; |
5fcdbb2d RS |
492 | |
493 | /* nothing to set up */ | |
494 | if (!list) | |
495 | return 0; | |
496 | ||
66344c6d RS |
497 | /* |
498 | * Prepare widgets for set up. The prepare step is used to allocate memory, assign | |
499 | * instance ID and pick the widget configuration based on the runtime PCM params. | |
500 | */ | |
501 | ret = sof_walk_widgets_in_order(sdev, list, fe_params, platform_params, | |
502 | dir, SOF_WIDGET_PREPARE); | |
5da0590a RS |
503 | if (ret < 0) |
504 | return ret; | |
5fcdbb2d | 505 | |
66344c6d RS |
506 | /* Set up is used to send the IPC to the DSP to create the widget */ |
507 | ret = sof_walk_widgets_in_order(sdev, list, fe_params, platform_params, | |
508 | dir, SOF_WIDGET_SETUP); | |
509 | if (ret < 0) { | |
510 | ret = sof_walk_widgets_in_order(sdev, list, fe_params, platform_params, | |
511 | dir, SOF_WIDGET_UNPREPARE); | |
512 | return ret; | |
513 | } | |
514 | ||
5fcdbb2d RS |
515 | /* |
516 | * error in setting pipeline connections will result in route status being reset for | |
517 | * routes that were successfully set up when the widgets are freed. | |
518 | */ | |
519 | ret = sof_setup_pipeline_connections(sdev, list, dir); | |
520 | if (ret < 0) | |
521 | goto widget_free; | |
522 | ||
523 | /* complete pipelines */ | |
524 | for_each_dapm_widgets(list, i, widget) { | |
525 | struct snd_sof_widget *swidget = widget->dobj.private; | |
526 | struct snd_sof_widget *pipe_widget; | |
527 | ||
528 | if (!swidget) | |
529 | continue; | |
530 | ||
531 | pipe_widget = swidget->pipe_widget; | |
532 | if (!pipe_widget) { | |
533 | dev_err(sdev->dev, "error: no pipeline widget found for %s\n", | |
534 | swidget->widget->name); | |
535 | ret = -EINVAL; | |
536 | goto widget_free; | |
537 | } | |
538 | ||
539 | if (pipe_widget->complete) | |
540 | continue; | |
541 | ||
61ad28ff RS |
542 | if (ipc_tplg_ops->pipeline_complete) { |
543 | pipe_widget->complete = ipc_tplg_ops->pipeline_complete(sdev, pipe_widget); | |
544 | if (pipe_widget->complete < 0) { | |
545 | ret = pipe_widget->complete; | |
546 | goto widget_free; | |
547 | } | |
5fcdbb2d RS |
548 | } |
549 | } | |
550 | ||
551 | return 0; | |
552 | ||
553 | widget_free: | |
66344c6d RS |
554 | sof_walk_widgets_in_order(sdev, list, fe_params, platform_params, dir, |
555 | SOF_WIDGET_FREE); | |
556 | sof_walk_widgets_in_order(sdev, list, NULL, NULL, dir, SOF_WIDGET_UNPREPARE); | |
5fcdbb2d RS |
557 | |
558 | return ret; | |
559 | } | |
560 | ||
561 | int sof_widget_list_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir) | |
562 | { | |
563 | struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list; | |
5da0590a | 564 | int ret; |
5fcdbb2d RS |
565 | |
566 | /* nothing to free */ | |
567 | if (!list) | |
568 | return 0; | |
569 | ||
66344c6d RS |
570 | /* send IPC to free widget in the DSP */ |
571 | ret = sof_walk_widgets_in_order(sdev, list, NULL, NULL, dir, SOF_WIDGET_FREE); | |
572 | ||
573 | /* unprepare the widget */ | |
574 | sof_walk_widgets_in_order(sdev, list, NULL, NULL, dir, SOF_WIDGET_UNPREPARE); | |
5fcdbb2d RS |
575 | |
576 | snd_soc_dapm_dai_free_widgets(&list); | |
577 | spcm->stream[dir].list = NULL; | |
578 | ||
5da0590a | 579 | return ret; |
5fcdbb2d RS |
580 | } |
581 | ||
de23a838 RS |
582 | /* |
583 | * helper to determine if there are only D0i3 compatible | |
584 | * streams active | |
585 | */ | |
586 | bool snd_sof_dsp_only_d0i3_compatible_stream_active(struct snd_sof_dev *sdev) | |
587 | { | |
588 | struct snd_pcm_substream *substream; | |
589 | struct snd_sof_pcm *spcm; | |
590 | bool d0i3_compatible_active = false; | |
591 | int dir; | |
592 | ||
593 | list_for_each_entry(spcm, &sdev->pcm_list, list) { | |
525c4107 | 594 | for_each_pcm_streams(dir) { |
de23a838 RS |
595 | substream = spcm->stream[dir].substream; |
596 | if (!substream || !substream->runtime) | |
597 | continue; | |
598 | ||
599 | /* | |
8932f0cb | 600 | * substream->runtime being not NULL indicates |
de23a838 RS |
601 | * that the stream is open. No need to check the |
602 | * stream state. | |
603 | */ | |
604 | if (!spcm->stream[dir].d0i3_compatible) | |
605 | return false; | |
606 | ||
607 | d0i3_compatible_active = true; | |
608 | } | |
609 | } | |
610 | ||
611 | return d0i3_compatible_active; | |
612 | } | |
613 | EXPORT_SYMBOL(snd_sof_dsp_only_d0i3_compatible_stream_active); | |
614 | ||
700d1677 | 615 | bool snd_sof_stream_suspend_ignored(struct snd_sof_dev *sdev) |
ee1e79b7 RS |
616 | { |
617 | struct snd_sof_pcm *spcm; | |
618 | ||
619 | list_for_each_entry(spcm, &sdev->pcm_list, list) { | |
620 | if (spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].suspend_ignored || | |
621 | spcm->stream[SNDRV_PCM_STREAM_CAPTURE].suspend_ignored) | |
622 | return true; | |
623 | } | |
624 | ||
625 | return false; | |
626 | } | |
627 | ||
d9a72465 RS |
628 | int sof_pcm_stream_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, |
629 | struct snd_sof_pcm *spcm, int dir, bool free_widget_list) | |
630 | { | |
4123c24b | 631 | const struct sof_ipc_pcm_ops *pcm_ops = sdev->ipc->ops->pcm; |
d9a72465 RS |
632 | int ret; |
633 | ||
634 | /* Send PCM_FREE IPC to reset pipeline */ | |
4123c24b RS |
635 | if (pcm_ops->hw_free && spcm->prepared[substream->stream]) { |
636 | ret = pcm_ops->hw_free(sdev->component, substream); | |
637 | if (ret < 0) | |
638 | return ret; | |
639 | } | |
640 | ||
641 | spcm->prepared[substream->stream] = false; | |
d9a72465 RS |
642 | |
643 | /* stop the DMA */ | |
644 | ret = snd_sof_pcm_platform_hw_free(sdev, substream); | |
645 | if (ret < 0) | |
646 | return ret; | |
647 | ||
648 | /* free widget list */ | |
649 | if (free_widget_list) { | |
650 | ret = sof_widget_list_free(sdev, spcm, dir); | |
651 | if (ret < 0) | |
652 | dev_err(sdev->dev, "failed to free widgets during suspend\n"); | |
653 | } | |
654 | ||
655 | return ret; | |
656 | } | |
657 | ||
ee1e79b7 RS |
658 | /* |
659 | * Generic object lookup APIs. | |
660 | */ | |
661 | ||
662 | struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_soc_component *scomp, | |
663 | const char *name) | |
664 | { | |
665 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); | |
666 | struct snd_sof_pcm *spcm; | |
667 | ||
668 | list_for_each_entry(spcm, &sdev->pcm_list, list) { | |
669 | /* match with PCM dai name */ | |
670 | if (strcmp(spcm->pcm.dai_name, name) == 0) | |
671 | return spcm; | |
672 | ||
673 | /* match with playback caps name if set */ | |
674 | if (*spcm->pcm.caps[0].name && | |
675 | !strcmp(spcm->pcm.caps[0].name, name)) | |
676 | return spcm; | |
677 | ||
678 | /* match with capture caps name if set */ | |
679 | if (*spcm->pcm.caps[1].name && | |
680 | !strcmp(spcm->pcm.caps[1].name, name)) | |
681 | return spcm; | |
682 | } | |
683 | ||
684 | return NULL; | |
685 | } | |
686 | ||
687 | struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_soc_component *scomp, | |
688 | unsigned int comp_id, | |
689 | int *direction) | |
690 | { | |
691 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); | |
692 | struct snd_sof_pcm *spcm; | |
693 | int dir; | |
694 | ||
695 | list_for_each_entry(spcm, &sdev->pcm_list, list) { | |
525c4107 KM |
696 | for_each_pcm_streams(dir) { |
697 | if (spcm->stream[dir].comp_id == comp_id) { | |
698 | *direction = dir; | |
699 | return spcm; | |
700 | } | |
ee1e79b7 RS |
701 | } |
702 | } | |
703 | ||
704 | return NULL; | |
705 | } | |
706 | ||
ee1e79b7 RS |
707 | struct snd_sof_widget *snd_sof_find_swidget(struct snd_soc_component *scomp, |
708 | const char *name) | |
709 | { | |
710 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); | |
711 | struct snd_sof_widget *swidget; | |
712 | ||
713 | list_for_each_entry(swidget, &sdev->widget_list, list) { | |
714 | if (strcmp(name, swidget->widget->name) == 0) | |
715 | return swidget; | |
716 | } | |
717 | ||
718 | return NULL; | |
719 | } | |
720 | ||
721 | /* find widget by stream name and direction */ | |
722 | struct snd_sof_widget * | |
723 | snd_sof_find_swidget_sname(struct snd_soc_component *scomp, | |
724 | const char *pcm_name, int dir) | |
725 | { | |
726 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); | |
727 | struct snd_sof_widget *swidget; | |
728 | enum snd_soc_dapm_type type; | |
729 | ||
730 | if (dir == SNDRV_PCM_STREAM_PLAYBACK) | |
731 | type = snd_soc_dapm_aif_in; | |
732 | else | |
733 | type = snd_soc_dapm_aif_out; | |
734 | ||
735 | list_for_each_entry(swidget, &sdev->widget_list, list) { | |
736 | if (!strcmp(pcm_name, swidget->widget->sname) && | |
737 | swidget->id == type) | |
738 | return swidget; | |
739 | } | |
740 | ||
741 | return NULL; | |
742 | } | |
743 | ||
744 | struct snd_sof_dai *snd_sof_find_dai(struct snd_soc_component *scomp, | |
745 | const char *name) | |
746 | { | |
747 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); | |
748 | struct snd_sof_dai *dai; | |
749 | ||
750 | list_for_each_entry(dai, &sdev->dai_list, list) { | |
751 | if (dai->name && (strcmp(name, dai->name) == 0)) | |
752 | return dai; | |
753 | } | |
754 | ||
755 | return NULL; | |
756 | } | |
757 | ||
bc619cfc | 758 | static int sof_dai_get_clk(struct snd_soc_pcm_runtime *rtd, int clk_type) |
b951b51e KJ |
759 | { |
760 | struct snd_soc_component *component = | |
761 | snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); | |
762 | struct snd_sof_dai *dai = | |
763 | snd_sof_find_dai(component, (char *)rtd->dai_link->name); | |
85f7a8b6 RS |
764 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); |
765 | const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; | |
b951b51e KJ |
766 | |
767 | /* use the tplg configured mclk if existed */ | |
85f7a8b6 | 768 | if (!dai) |
b951b51e KJ |
769 | return 0; |
770 | ||
85f7a8b6 RS |
771 | if (tplg_ops->dai_get_clk) |
772 | return tplg_ops->dai_get_clk(sdev, dai, clk_type); | |
773 | ||
774 | return 0; | |
b951b51e | 775 | } |
bc619cfc BL |
776 | |
777 | /* | |
778 | * Helper to get SSP MCLK from a pcm_runtime. | |
779 | * Return 0 if not exist. | |
780 | */ | |
781 | int sof_dai_get_mclk(struct snd_soc_pcm_runtime *rtd) | |
782 | { | |
783 | return sof_dai_get_clk(rtd, SOF_DAI_CLK_INTEL_SSP_MCLK); | |
784 | } | |
b951b51e KJ |
785 | EXPORT_SYMBOL(sof_dai_get_mclk); |
786 | ||
bc619cfc BL |
787 | /* |
788 | * Helper to get SSP BCLK from a pcm_runtime. | |
789 | * Return 0 if not exist. | |
790 | */ | |
791 | int sof_dai_get_bclk(struct snd_soc_pcm_runtime *rtd) | |
792 | { | |
793 | return sof_dai_get_clk(rtd, SOF_DAI_CLK_INTEL_SSP_BCLK); | |
794 | } | |
795 | EXPORT_SYMBOL(sof_dai_get_bclk); | |
796 | ||
354f6008 CL |
797 | static struct snd_sof_of_mach *sof_of_machine_select(struct snd_sof_dev *sdev) |
798 | { | |
799 | struct snd_sof_pdata *sof_pdata = sdev->pdata; | |
800 | const struct sof_dev_desc *desc = sof_pdata->desc; | |
801 | struct snd_sof_of_mach *mach = desc->of_machines; | |
802 | ||
803 | if (!mach) | |
804 | return NULL; | |
805 | ||
806 | for (; mach->compatible; mach++) { | |
807 | if (of_machine_is_compatible(mach->compatible)) { | |
808 | sof_pdata->tplg_filename = mach->sof_tplg_filename; | |
809 | if (mach->fw_filename) | |
810 | sof_pdata->fw_filename = mach->fw_filename; | |
811 | ||
812 | return mach; | |
813 | } | |
814 | } | |
815 | ||
816 | return NULL; | |
817 | } | |
818 | ||
285880a2 DB |
819 | /* |
820 | * SOF Driver enumeration. | |
821 | */ | |
822 | int sof_machine_check(struct snd_sof_dev *sdev) | |
823 | { | |
824 | struct snd_sof_pdata *sof_pdata = sdev->pdata; | |
825 | const struct sof_dev_desc *desc = sof_pdata->desc; | |
826 | struct snd_soc_acpi_mach *mach; | |
285880a2 | 827 | |
4c1cc83f | 828 | if (!IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE)) { |
354f6008 | 829 | const struct snd_sof_of_mach *of_mach; |
285880a2 | 830 | |
4bd1adb8 PLB |
831 | if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) && |
832 | sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC)) | |
833 | goto nocodec; | |
834 | ||
4c1cc83f | 835 | /* find machine */ |
cb515f10 GL |
836 | mach = snd_sof_machine_select(sdev); |
837 | if (mach) { | |
838 | sof_pdata->machine = mach; | |
839 | snd_sof_set_mach_params(mach, sdev); | |
4c1cc83f PLB |
840 | return 0; |
841 | } | |
842 | ||
354f6008 CL |
843 | of_mach = sof_of_machine_select(sdev); |
844 | if (of_mach) { | |
845 | sof_pdata->of_machine = of_mach; | |
846 | return 0; | |
847 | } | |
848 | ||
4c1cc83f PLB |
849 | if (!IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC)) { |
850 | dev_err(sdev->dev, "error: no matching ASoC machine driver found - aborting probe\n"); | |
851 | return -ENODEV; | |
852 | } | |
853 | } else { | |
854 | dev_warn(sdev->dev, "Force to use nocodec mode\n"); | |
285880a2 DB |
855 | } |
856 | ||
4bd1adb8 | 857 | nocodec: |
285880a2 DB |
858 | /* select nocodec mode */ |
859 | dev_warn(sdev->dev, "Using nocodec machine driver\n"); | |
860 | mach = devm_kzalloc(sdev->dev, sizeof(*mach), GFP_KERNEL); | |
861 | if (!mach) | |
862 | return -ENOMEM; | |
863 | ||
d612b455 | 864 | mach->drv_name = "sof-nocodec"; |
53fe24c2 PLB |
865 | if (!sof_pdata->tplg_filename) |
866 | sof_pdata->tplg_filename = desc->nocodec_tplg_filename; | |
d612b455 | 867 | |
285880a2 | 868 | sof_pdata->machine = mach; |
cb515f10 | 869 | snd_sof_set_mach_params(mach, sdev); |
285880a2 DB |
870 | |
871 | return 0; | |
872 | } | |
873 | EXPORT_SYMBOL(sof_machine_check); | |
874 | ||
875 | int sof_machine_register(struct snd_sof_dev *sdev, void *pdata) | |
876 | { | |
db69bcf9 | 877 | struct snd_sof_pdata *plat_data = pdata; |
285880a2 DB |
878 | const char *drv_name; |
879 | const void *mach; | |
880 | int size; | |
881 | ||
882 | drv_name = plat_data->machine->drv_name; | |
db69bcf9 | 883 | mach = plat_data->machine; |
285880a2 DB |
884 | size = sizeof(*plat_data->machine); |
885 | ||
886 | /* register machine driver, pass machine info as pdata */ | |
887 | plat_data->pdev_mach = | |
888 | platform_device_register_data(sdev->dev, drv_name, | |
889 | PLATFORM_DEVID_NONE, mach, size); | |
890 | if (IS_ERR(plat_data->pdev_mach)) | |
891 | return PTR_ERR(plat_data->pdev_mach); | |
892 | ||
893 | dev_dbg(sdev->dev, "created machine %s\n", | |
894 | dev_name(&plat_data->pdev_mach->dev)); | |
895 | ||
896 | return 0; | |
897 | } | |
898 | EXPORT_SYMBOL(sof_machine_register); | |
899 | ||
900 | void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata) | |
901 | { | |
db69bcf9 | 902 | struct snd_sof_pdata *plat_data = pdata; |
285880a2 DB |
903 | |
904 | if (!IS_ERR_OR_NULL(plat_data->pdev_mach)) | |
905 | platform_device_unregister(plat_data->pdev_mach); | |
906 | } | |
907 | EXPORT_SYMBOL(sof_machine_unregister); |