]>
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 | ||
11 | #include "sof-audio.h" | |
12 | #include "ops.h" | |
13 | ||
de23a838 RS |
14 | /* |
15 | * helper to determine if there are only D0i3 compatible | |
16 | * streams active | |
17 | */ | |
18 | bool snd_sof_dsp_only_d0i3_compatible_stream_active(struct snd_sof_dev *sdev) | |
19 | { | |
20 | struct snd_pcm_substream *substream; | |
21 | struct snd_sof_pcm *spcm; | |
22 | bool d0i3_compatible_active = false; | |
23 | int dir; | |
24 | ||
25 | list_for_each_entry(spcm, &sdev->pcm_list, list) { | |
525c4107 | 26 | for_each_pcm_streams(dir) { |
de23a838 RS |
27 | substream = spcm->stream[dir].substream; |
28 | if (!substream || !substream->runtime) | |
29 | continue; | |
30 | ||
31 | /* | |
32 | * substream->runtime being not NULL indicates that | |
33 | * that the stream is open. No need to check the | |
34 | * stream state. | |
35 | */ | |
36 | if (!spcm->stream[dir].d0i3_compatible) | |
37 | return false; | |
38 | ||
39 | d0i3_compatible_active = true; | |
40 | } | |
41 | } | |
42 | ||
43 | return d0i3_compatible_active; | |
44 | } | |
45 | EXPORT_SYMBOL(snd_sof_dsp_only_d0i3_compatible_stream_active); | |
46 | ||
700d1677 | 47 | bool snd_sof_stream_suspend_ignored(struct snd_sof_dev *sdev) |
ee1e79b7 RS |
48 | { |
49 | struct snd_sof_pcm *spcm; | |
50 | ||
51 | list_for_each_entry(spcm, &sdev->pcm_list, list) { | |
52 | if (spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].suspend_ignored || | |
53 | spcm->stream[SNDRV_PCM_STREAM_CAPTURE].suspend_ignored) | |
54 | return true; | |
55 | } | |
56 | ||
57 | return false; | |
58 | } | |
59 | ||
60 | int sof_set_hw_params_upon_resume(struct device *dev) | |
61 | { | |
62 | struct snd_sof_dev *sdev = dev_get_drvdata(dev); | |
63 | struct snd_pcm_substream *substream; | |
64 | struct snd_sof_pcm *spcm; | |
65 | snd_pcm_state_t state; | |
66 | int dir; | |
67 | ||
68 | /* | |
69 | * SOF requires hw_params to be set-up internally upon resume. | |
70 | * So, set the flag to indicate this for those streams that | |
71 | * have been suspended. | |
72 | */ | |
73 | list_for_each_entry(spcm, &sdev->pcm_list, list) { | |
525c4107 | 74 | for_each_pcm_streams(dir) { |
3f06501e RS |
75 | /* |
76 | * do not reset hw_params upon resume for streams that | |
77 | * were kept running during suspend | |
78 | */ | |
79 | if (spcm->stream[dir].suspend_ignored) | |
80 | continue; | |
81 | ||
ee1e79b7 RS |
82 | substream = spcm->stream[dir].substream; |
83 | if (!substream || !substream->runtime) | |
84 | continue; | |
85 | ||
86 | state = substream->runtime->status->state; | |
87 | if (state == SNDRV_PCM_STATE_SUSPENDED) | |
88 | spcm->prepared[dir] = false; | |
89 | } | |
90 | } | |
91 | ||
92 | /* set internal flag for BE */ | |
93 | return snd_sof_dsp_hw_params_upon_resume(sdev); | |
94 | } | |
95 | ||
96 | static int sof_restore_kcontrols(struct device *dev) | |
97 | { | |
98 | struct snd_sof_dev *sdev = dev_get_drvdata(dev); | |
99 | struct snd_sof_control *scontrol; | |
100 | int ipc_cmd, ctrl_type; | |
101 | int ret = 0; | |
102 | ||
103 | /* restore kcontrol values */ | |
104 | list_for_each_entry(scontrol, &sdev->kcontrol_list, list) { | |
105 | /* reset readback offset for scontrol after resuming */ | |
106 | scontrol->readback_offset = 0; | |
107 | ||
108 | /* notify DSP of kcontrol values */ | |
109 | switch (scontrol->cmd) { | |
110 | case SOF_CTRL_CMD_VOLUME: | |
111 | case SOF_CTRL_CMD_ENUM: | |
112 | case SOF_CTRL_CMD_SWITCH: | |
113 | ipc_cmd = SOF_IPC_COMP_SET_VALUE; | |
114 | ctrl_type = SOF_CTRL_TYPE_VALUE_CHAN_SET; | |
115 | ret = snd_sof_ipc_set_get_comp_data(scontrol, | |
116 | ipc_cmd, ctrl_type, | |
117 | scontrol->cmd, | |
118 | true); | |
119 | break; | |
120 | case SOF_CTRL_CMD_BINARY: | |
121 | ipc_cmd = SOF_IPC_COMP_SET_DATA; | |
122 | ctrl_type = SOF_CTRL_TYPE_DATA_SET; | |
123 | ret = snd_sof_ipc_set_get_comp_data(scontrol, | |
124 | ipc_cmd, ctrl_type, | |
125 | scontrol->cmd, | |
126 | true); | |
127 | break; | |
128 | ||
129 | default: | |
130 | break; | |
131 | } | |
132 | ||
133 | if (ret < 0) { | |
134 | dev_err(dev, | |
135 | "error: failed kcontrol value set for widget: %d\n", | |
136 | scontrol->comp_id); | |
137 | ||
138 | return ret; | |
139 | } | |
140 | } | |
141 | ||
142 | return 0; | |
143 | } | |
144 | ||
145 | int sof_restore_pipelines(struct device *dev) | |
146 | { | |
147 | struct snd_sof_dev *sdev = dev_get_drvdata(dev); | |
148 | struct snd_sof_widget *swidget; | |
149 | struct snd_sof_route *sroute; | |
150 | struct sof_ipc_pipe_new *pipeline; | |
151 | struct snd_sof_dai *dai; | |
152 | struct sof_ipc_comp_dai *comp_dai; | |
153 | struct sof_ipc_cmd_hdr *hdr; | |
154 | int ret; | |
155 | ||
156 | /* restore pipeline components */ | |
157 | list_for_each_entry_reverse(swidget, &sdev->widget_list, list) { | |
158 | struct sof_ipc_comp_reply r; | |
159 | ||
160 | /* skip if there is no private data */ | |
161 | if (!swidget->private) | |
162 | continue; | |
163 | ||
164 | switch (swidget->id) { | |
165 | case snd_soc_dapm_dai_in: | |
166 | case snd_soc_dapm_dai_out: | |
167 | dai = swidget->private; | |
168 | comp_dai = &dai->comp_dai; | |
169 | ret = sof_ipc_tx_message(sdev->ipc, | |
170 | comp_dai->comp.hdr.cmd, | |
171 | comp_dai, sizeof(*comp_dai), | |
172 | &r, sizeof(r)); | |
173 | break; | |
174 | case snd_soc_dapm_scheduler: | |
175 | ||
176 | /* | |
177 | * During suspend, all DSP cores are powered off. | |
178 | * Therefore upon resume, create the pipeline comp | |
179 | * and power up the core that the pipeline is | |
180 | * scheduled on. | |
181 | */ | |
182 | pipeline = swidget->private; | |
183 | ret = sof_load_pipeline_ipc(dev, pipeline, &r); | |
184 | break; | |
185 | default: | |
186 | hdr = swidget->private; | |
187 | ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd, | |
188 | swidget->private, hdr->size, | |
189 | &r, sizeof(r)); | |
190 | break; | |
191 | } | |
192 | if (ret < 0) { | |
193 | dev_err(dev, | |
194 | "error: failed to load widget type %d with ID: %d\n", | |
195 | swidget->widget->id, swidget->comp_id); | |
196 | ||
197 | return ret; | |
198 | } | |
199 | } | |
200 | ||
201 | /* restore pipeline connections */ | |
202 | list_for_each_entry_reverse(sroute, &sdev->route_list, list) { | |
203 | struct sof_ipc_pipe_comp_connect *connect; | |
204 | struct sof_ipc_reply reply; | |
205 | ||
206 | /* skip if there's no private data */ | |
207 | if (!sroute->private) | |
208 | continue; | |
209 | ||
210 | connect = sroute->private; | |
211 | ||
212 | /* send ipc */ | |
213 | ret = sof_ipc_tx_message(sdev->ipc, | |
214 | connect->hdr.cmd, | |
215 | connect, sizeof(*connect), | |
216 | &reply, sizeof(reply)); | |
217 | if (ret < 0) { | |
218 | dev_err(dev, | |
219 | "error: failed to load route sink %s control %s source %s\n", | |
220 | sroute->route->sink, | |
221 | sroute->route->control ? sroute->route->control | |
222 | : "none", | |
223 | sroute->route->source); | |
224 | ||
225 | return ret; | |
226 | } | |
227 | } | |
228 | ||
229 | /* restore dai links */ | |
230 | list_for_each_entry_reverse(dai, &sdev->dai_list, list) { | |
231 | struct sof_ipc_reply reply; | |
232 | struct sof_ipc_dai_config *config = dai->dai_config; | |
233 | ||
234 | if (!config) { | |
235 | dev_err(dev, "error: no config for DAI %s\n", | |
236 | dai->name); | |
237 | continue; | |
238 | } | |
239 | ||
240 | /* | |
241 | * The link DMA channel would be invalidated for running | |
242 | * streams but not for streams that were in the PAUSED | |
243 | * state during suspend. So invalidate it here before setting | |
244 | * the dai config in the DSP. | |
245 | */ | |
246 | if (config->type == SOF_DAI_INTEL_HDA) | |
247 | config->hda.link_dma_ch = DMA_CHAN_INVALID; | |
248 | ||
249 | ret = sof_ipc_tx_message(sdev->ipc, | |
250 | config->hdr.cmd, config, | |
251 | config->hdr.size, | |
252 | &reply, sizeof(reply)); | |
253 | ||
254 | if (ret < 0) { | |
255 | dev_err(dev, | |
256 | "error: failed to set dai config for %s\n", | |
257 | dai->name); | |
258 | ||
259 | return ret; | |
260 | } | |
261 | } | |
262 | ||
263 | /* complete pipeline */ | |
264 | list_for_each_entry(swidget, &sdev->widget_list, list) { | |
265 | switch (swidget->id) { | |
266 | case snd_soc_dapm_scheduler: | |
267 | swidget->complete = | |
268 | snd_sof_complete_pipeline(dev, swidget); | |
269 | break; | |
270 | default: | |
271 | break; | |
272 | } | |
273 | } | |
274 | ||
275 | /* restore pipeline kcontrols */ | |
276 | ret = sof_restore_kcontrols(dev); | |
277 | if (ret < 0) | |
278 | dev_err(dev, | |
279 | "error: restoring kcontrols after resume\n"); | |
280 | ||
281 | return ret; | |
282 | } | |
283 | ||
284 | /* | |
285 | * Generic object lookup APIs. | |
286 | */ | |
287 | ||
288 | struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_soc_component *scomp, | |
289 | const char *name) | |
290 | { | |
291 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); | |
292 | struct snd_sof_pcm *spcm; | |
293 | ||
294 | list_for_each_entry(spcm, &sdev->pcm_list, list) { | |
295 | /* match with PCM dai name */ | |
296 | if (strcmp(spcm->pcm.dai_name, name) == 0) | |
297 | return spcm; | |
298 | ||
299 | /* match with playback caps name if set */ | |
300 | if (*spcm->pcm.caps[0].name && | |
301 | !strcmp(spcm->pcm.caps[0].name, name)) | |
302 | return spcm; | |
303 | ||
304 | /* match with capture caps name if set */ | |
305 | if (*spcm->pcm.caps[1].name && | |
306 | !strcmp(spcm->pcm.caps[1].name, name)) | |
307 | return spcm; | |
308 | } | |
309 | ||
310 | return NULL; | |
311 | } | |
312 | ||
313 | struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_soc_component *scomp, | |
314 | unsigned int comp_id, | |
315 | int *direction) | |
316 | { | |
317 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); | |
318 | struct snd_sof_pcm *spcm; | |
319 | int dir; | |
320 | ||
321 | list_for_each_entry(spcm, &sdev->pcm_list, list) { | |
525c4107 KM |
322 | for_each_pcm_streams(dir) { |
323 | if (spcm->stream[dir].comp_id == comp_id) { | |
324 | *direction = dir; | |
325 | return spcm; | |
326 | } | |
ee1e79b7 RS |
327 | } |
328 | } | |
329 | ||
330 | return NULL; | |
331 | } | |
332 | ||
333 | struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_soc_component *scomp, | |
334 | unsigned int pcm_id) | |
335 | { | |
336 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); | |
337 | struct snd_sof_pcm *spcm; | |
338 | ||
339 | list_for_each_entry(spcm, &sdev->pcm_list, list) { | |
340 | if (le32_to_cpu(spcm->pcm.pcm_id) == pcm_id) | |
341 | return spcm; | |
342 | } | |
343 | ||
344 | return NULL; | |
345 | } | |
346 | ||
347 | struct snd_sof_widget *snd_sof_find_swidget(struct snd_soc_component *scomp, | |
348 | const char *name) | |
349 | { | |
350 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); | |
351 | struct snd_sof_widget *swidget; | |
352 | ||
353 | list_for_each_entry(swidget, &sdev->widget_list, list) { | |
354 | if (strcmp(name, swidget->widget->name) == 0) | |
355 | return swidget; | |
356 | } | |
357 | ||
358 | return NULL; | |
359 | } | |
360 | ||
361 | /* find widget by stream name and direction */ | |
362 | struct snd_sof_widget * | |
363 | snd_sof_find_swidget_sname(struct snd_soc_component *scomp, | |
364 | const char *pcm_name, int dir) | |
365 | { | |
366 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); | |
367 | struct snd_sof_widget *swidget; | |
368 | enum snd_soc_dapm_type type; | |
369 | ||
370 | if (dir == SNDRV_PCM_STREAM_PLAYBACK) | |
371 | type = snd_soc_dapm_aif_in; | |
372 | else | |
373 | type = snd_soc_dapm_aif_out; | |
374 | ||
375 | list_for_each_entry(swidget, &sdev->widget_list, list) { | |
376 | if (!strcmp(pcm_name, swidget->widget->sname) && | |
377 | swidget->id == type) | |
378 | return swidget; | |
379 | } | |
380 | ||
381 | return NULL; | |
382 | } | |
383 | ||
384 | struct snd_sof_dai *snd_sof_find_dai(struct snd_soc_component *scomp, | |
385 | const char *name) | |
386 | { | |
387 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); | |
388 | struct snd_sof_dai *dai; | |
389 | ||
390 | list_for_each_entry(dai, &sdev->dai_list, list) { | |
391 | if (dai->name && (strcmp(name, dai->name) == 0)) | |
392 | return dai; | |
393 | } | |
394 | ||
395 | return NULL; | |
396 | } | |
397 | ||
285880a2 DB |
398 | /* |
399 | * SOF Driver enumeration. | |
400 | */ | |
401 | int sof_machine_check(struct snd_sof_dev *sdev) | |
402 | { | |
403 | struct snd_sof_pdata *sof_pdata = sdev->pdata; | |
404 | const struct sof_dev_desc *desc = sof_pdata->desc; | |
405 | struct snd_soc_acpi_mach *mach; | |
406 | int ret; | |
407 | ||
408 | /* force nocodec mode */ | |
409 | #if IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE) | |
410 | dev_warn(sdev->dev, "Force to use nocodec mode\n"); | |
411 | goto nocodec; | |
412 | #endif | |
413 | ||
414 | /* find machine */ | |
415 | snd_sof_machine_select(sdev); | |
416 | if (sof_pdata->machine) { | |
417 | snd_sof_set_mach_params(sof_pdata->machine, sdev->dev); | |
418 | return 0; | |
419 | } | |
420 | ||
421 | #if !IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC) | |
422 | dev_err(sdev->dev, "error: no matching ASoC machine driver found - aborting probe\n"); | |
423 | return -ENODEV; | |
424 | #endif | |
425 | #if IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE) | |
426 | nocodec: | |
427 | #endif | |
428 | /* select nocodec mode */ | |
429 | dev_warn(sdev->dev, "Using nocodec machine driver\n"); | |
430 | mach = devm_kzalloc(sdev->dev, sizeof(*mach), GFP_KERNEL); | |
431 | if (!mach) | |
432 | return -ENOMEM; | |
433 | ||
d612b455 RS |
434 | mach->drv_name = "sof-nocodec"; |
435 | sof_pdata->tplg_filename = desc->nocodec_tplg_filename; | |
436 | ||
437 | ret = sof_nocodec_setup(sdev->dev, desc->ops); | |
285880a2 DB |
438 | if (ret < 0) |
439 | return ret; | |
440 | ||
441 | sof_pdata->machine = mach; | |
442 | snd_sof_set_mach_params(sof_pdata->machine, sdev->dev); | |
443 | ||
444 | return 0; | |
445 | } | |
446 | EXPORT_SYMBOL(sof_machine_check); | |
447 | ||
448 | int sof_machine_register(struct snd_sof_dev *sdev, void *pdata) | |
449 | { | |
450 | struct snd_sof_pdata *plat_data = (struct snd_sof_pdata *)pdata; | |
451 | const char *drv_name; | |
452 | const void *mach; | |
453 | int size; | |
454 | ||
455 | drv_name = plat_data->machine->drv_name; | |
456 | mach = (const void *)plat_data->machine; | |
457 | size = sizeof(*plat_data->machine); | |
458 | ||
459 | /* register machine driver, pass machine info as pdata */ | |
460 | plat_data->pdev_mach = | |
461 | platform_device_register_data(sdev->dev, drv_name, | |
462 | PLATFORM_DEVID_NONE, mach, size); | |
463 | if (IS_ERR(plat_data->pdev_mach)) | |
464 | return PTR_ERR(plat_data->pdev_mach); | |
465 | ||
466 | dev_dbg(sdev->dev, "created machine %s\n", | |
467 | dev_name(&plat_data->pdev_mach->dev)); | |
468 | ||
469 | return 0; | |
470 | } | |
471 | EXPORT_SYMBOL(sof_machine_register); | |
472 | ||
473 | void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata) | |
474 | { | |
475 | struct snd_sof_pdata *plat_data = (struct snd_sof_pdata *)pdata; | |
476 | ||
477 | if (!IS_ERR_OR_NULL(plat_data->pdev_mach)) | |
478 | platform_device_unregister(plat_data->pdev_mach); | |
479 | } | |
480 | EXPORT_SYMBOL(sof_machine_unregister); |