1 // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
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
14 /*
15 * helper to determine if there are only D0i3 compatible
16 * streams active
17 */
snd_sof_dsp_only_d0i3_compatible_stream_active(struct snd_sof_dev * sdev)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) {
26 for_each_pcm_streams(dir) {
27 substream = spcm->stream[dir].substream;
28 if (!substream || !substream->runtime)
29 continue;
30
31 /*
32 * substream->runtime being not NULL indicates
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
snd_sof_stream_suspend_ignored(struct snd_sof_dev * sdev)47 bool snd_sof_stream_suspend_ignored(struct snd_sof_dev *sdev)
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
sof_set_hw_params_upon_resume(struct device * dev)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) {
74 for_each_pcm_streams(dir) {
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
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
sof_restore_kcontrols(struct device * dev)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
snd_sof_pipeline_find(struct snd_sof_dev * sdev,int pipeline_id)145 const struct sof_ipc_pipe_new *snd_sof_pipeline_find(struct snd_sof_dev *sdev,
146 int pipeline_id)
147 {
148 const struct snd_sof_widget *swidget;
149
150 list_for_each_entry(swidget, &sdev->widget_list, list)
151 if (swidget->id == snd_soc_dapm_scheduler) {
152 const struct sof_ipc_pipe_new *pipeline =
153 swidget->private;
154 if (pipeline->pipeline_id == pipeline_id)
155 return pipeline;
156 }
157
158 return NULL;
159 }
160
sof_restore_pipelines(struct device * dev)161 int sof_restore_pipelines(struct device *dev)
162 {
163 struct snd_sof_dev *sdev = dev_get_drvdata(dev);
164 struct snd_sof_widget *swidget;
165 struct snd_sof_route *sroute;
166 struct sof_ipc_pipe_new *pipeline;
167 struct snd_sof_dai *dai;
168 struct sof_ipc_cmd_hdr *hdr;
169 struct sof_ipc_comp *comp;
170 size_t ipc_size;
171 int ret;
172
173 /* restore pipeline components */
174 list_for_each_entry_reverse(swidget, &sdev->widget_list, list) {
175 struct sof_ipc_comp_reply r;
176
177 /* skip if there is no private data */
178 if (!swidget->private)
179 continue;
180
181 ret = sof_pipeline_core_enable(sdev, swidget);
182 if (ret < 0) {
183 dev_err(dev,
184 "error: failed to enable target core: %d\n",
185 ret);
186
187 return ret;
188 }
189
190 switch (swidget->id) {
191 case snd_soc_dapm_dai_in:
192 case snd_soc_dapm_dai_out:
193 ipc_size = sizeof(struct sof_ipc_comp_dai) +
194 sizeof(struct sof_ipc_comp_ext);
195 comp = kzalloc(ipc_size, GFP_KERNEL);
196 if (!comp)
197 return -ENOMEM;
198
199 dai = swidget->private;
200 memcpy(comp, &dai->comp_dai,
201 sizeof(struct sof_ipc_comp_dai));
202
203 /* append extended data to the end of the component */
204 memcpy((u8 *)comp + sizeof(struct sof_ipc_comp_dai),
205 &swidget->comp_ext, sizeof(swidget->comp_ext));
206
207 ret = sof_ipc_tx_message(sdev->ipc, comp->hdr.cmd,
208 comp, ipc_size,
209 &r, sizeof(r));
210 kfree(comp);
211 break;
212 case snd_soc_dapm_scheduler:
213
214 /*
215 * During suspend, all DSP cores are powered off.
216 * Therefore upon resume, create the pipeline comp
217 * and power up the core that the pipeline is
218 * scheduled on.
219 */
220 pipeline = swidget->private;
221 ret = sof_load_pipeline_ipc(dev, pipeline, &r);
222 break;
223 default:
224 hdr = swidget->private;
225 ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd,
226 swidget->private, hdr->size,
227 &r, sizeof(r));
228 break;
229 }
230 if (ret < 0) {
231 dev_err(dev,
232 "error: failed to load widget type %d with ID: %d\n",
233 swidget->widget->id, swidget->comp_id);
234
235 return ret;
236 }
237 }
238
239 /* restore pipeline connections */
240 list_for_each_entry_reverse(sroute, &sdev->route_list, list) {
241 struct sof_ipc_pipe_comp_connect *connect;
242 struct sof_ipc_reply reply;
243
244 /* skip if there's no private data */
245 if (!sroute->private)
246 continue;
247
248 connect = sroute->private;
249
250 /* send ipc */
251 ret = sof_ipc_tx_message(sdev->ipc,
252 connect->hdr.cmd,
253 connect, sizeof(*connect),
254 &reply, sizeof(reply));
255 if (ret < 0) {
256 dev_err(dev,
257 "error: failed to load route sink %s control %s source %s\n",
258 sroute->route->sink,
259 sroute->route->control ? sroute->route->control
260 : "none",
261 sroute->route->source);
262
263 return ret;
264 }
265 }
266
267 /* restore dai links */
268 list_for_each_entry_reverse(dai, &sdev->dai_list, list) {
269 struct sof_ipc_reply reply;
270 struct sof_ipc_dai_config *config = dai->dai_config;
271
272 if (!config) {
273 dev_err(dev, "error: no config for DAI %s\n",
274 dai->name);
275 continue;
276 }
277
278 /*
279 * The link DMA channel would be invalidated for running
280 * streams but not for streams that were in the PAUSED
281 * state during suspend. So invalidate it here before setting
282 * the dai config in the DSP.
283 */
284 if (config->type == SOF_DAI_INTEL_HDA)
285 config->hda.link_dma_ch = DMA_CHAN_INVALID;
286
287 ret = sof_ipc_tx_message(sdev->ipc,
288 config->hdr.cmd, config,
289 config->hdr.size,
290 &reply, sizeof(reply));
291
292 if (ret < 0) {
293 dev_err(dev,
294 "error: failed to set dai config for %s\n",
295 dai->name);
296
297 return ret;
298 }
299 }
300
301 /* complete pipeline */
302 list_for_each_entry(swidget, &sdev->widget_list, list) {
303 switch (swidget->id) {
304 case snd_soc_dapm_scheduler:
305 swidget->complete =
306 snd_sof_complete_pipeline(dev, swidget);
307 break;
308 default:
309 break;
310 }
311 }
312
313 /* restore pipeline kcontrols */
314 ret = sof_restore_kcontrols(dev);
315 if (ret < 0)
316 dev_err(dev,
317 "error: restoring kcontrols after resume\n");
318
319 return ret;
320 }
321
322 /*
323 * Generic object lookup APIs.
324 */
325
snd_sof_find_spcm_name(struct snd_soc_component * scomp,const char * name)326 struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_soc_component *scomp,
327 const char *name)
328 {
329 struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
330 struct snd_sof_pcm *spcm;
331
332 list_for_each_entry(spcm, &sdev->pcm_list, list) {
333 /* match with PCM dai name */
334 if (strcmp(spcm->pcm.dai_name, name) == 0)
335 return spcm;
336
337 /* match with playback caps name if set */
338 if (*spcm->pcm.caps[0].name &&
339 !strcmp(spcm->pcm.caps[0].name, name))
340 return spcm;
341
342 /* match with capture caps name if set */
343 if (*spcm->pcm.caps[1].name &&
344 !strcmp(spcm->pcm.caps[1].name, name))
345 return spcm;
346 }
347
348 return NULL;
349 }
350
snd_sof_find_spcm_comp(struct snd_soc_component * scomp,unsigned int comp_id,int * direction)351 struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_soc_component *scomp,
352 unsigned int comp_id,
353 int *direction)
354 {
355 struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
356 struct snd_sof_pcm *spcm;
357 int dir;
358
359 list_for_each_entry(spcm, &sdev->pcm_list, list) {
360 for_each_pcm_streams(dir) {
361 if (spcm->stream[dir].comp_id == comp_id) {
362 *direction = dir;
363 return spcm;
364 }
365 }
366 }
367
368 return NULL;
369 }
370
snd_sof_find_spcm_pcm_id(struct snd_soc_component * scomp,unsigned int pcm_id)371 struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_soc_component *scomp,
372 unsigned int pcm_id)
373 {
374 struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
375 struct snd_sof_pcm *spcm;
376
377 list_for_each_entry(spcm, &sdev->pcm_list, list) {
378 if (le32_to_cpu(spcm->pcm.pcm_id) == pcm_id)
379 return spcm;
380 }
381
382 return NULL;
383 }
384
snd_sof_find_swidget(struct snd_soc_component * scomp,const char * name)385 struct snd_sof_widget *snd_sof_find_swidget(struct snd_soc_component *scomp,
386 const char *name)
387 {
388 struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
389 struct snd_sof_widget *swidget;
390
391 list_for_each_entry(swidget, &sdev->widget_list, list) {
392 if (strcmp(name, swidget->widget->name) == 0)
393 return swidget;
394 }
395
396 return NULL;
397 }
398
399 /* find widget by stream name and direction */
400 struct snd_sof_widget *
snd_sof_find_swidget_sname(struct snd_soc_component * scomp,const char * pcm_name,int dir)401 snd_sof_find_swidget_sname(struct snd_soc_component *scomp,
402 const char *pcm_name, int dir)
403 {
404 struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
405 struct snd_sof_widget *swidget;
406 enum snd_soc_dapm_type type;
407
408 if (dir == SNDRV_PCM_STREAM_PLAYBACK)
409 type = snd_soc_dapm_aif_in;
410 else
411 type = snd_soc_dapm_aif_out;
412
413 list_for_each_entry(swidget, &sdev->widget_list, list) {
414 if (!strcmp(pcm_name, swidget->widget->sname) &&
415 swidget->id == type)
416 return swidget;
417 }
418
419 return NULL;
420 }
421
snd_sof_find_dai(struct snd_soc_component * scomp,const char * name)422 struct snd_sof_dai *snd_sof_find_dai(struct snd_soc_component *scomp,
423 const char *name)
424 {
425 struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
426 struct snd_sof_dai *dai;
427
428 list_for_each_entry(dai, &sdev->dai_list, list) {
429 if (dai->name && (strcmp(name, dai->name) == 0))
430 return dai;
431 }
432
433 return NULL;
434 }
435
436 /*
437 * SOF Driver enumeration.
438 */
sof_machine_check(struct snd_sof_dev * sdev)439 int sof_machine_check(struct snd_sof_dev *sdev)
440 {
441 struct snd_sof_pdata *sof_pdata = sdev->pdata;
442 const struct sof_dev_desc *desc = sof_pdata->desc;
443 struct snd_soc_acpi_mach *mach;
444 int ret;
445
446 /* force nocodec mode */
447 #if IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE)
448 dev_warn(sdev->dev, "Force to use nocodec mode\n");
449 goto nocodec;
450 #endif
451
452 /* find machine */
453 snd_sof_machine_select(sdev);
454 if (sof_pdata->machine) {
455 snd_sof_set_mach_params(sof_pdata->machine, sdev->dev);
456 return 0;
457 }
458
459 #if !IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC)
460 dev_err(sdev->dev, "error: no matching ASoC machine driver found - aborting probe\n");
461 return -ENODEV;
462 #endif
463 #if IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE)
464 nocodec:
465 #endif
466 /* select nocodec mode */
467 dev_warn(sdev->dev, "Using nocodec machine driver\n");
468 mach = devm_kzalloc(sdev->dev, sizeof(*mach), GFP_KERNEL);
469 if (!mach)
470 return -ENOMEM;
471
472 mach->drv_name = "sof-nocodec";
473 sof_pdata->tplg_filename = desc->nocodec_tplg_filename;
474
475 ret = sof_nocodec_setup(sdev->dev, desc->ops);
476 if (ret < 0)
477 return ret;
478
479 sof_pdata->machine = mach;
480 snd_sof_set_mach_params(sof_pdata->machine, sdev->dev);
481
482 return 0;
483 }
484 EXPORT_SYMBOL(sof_machine_check);
485
sof_machine_register(struct snd_sof_dev * sdev,void * pdata)486 int sof_machine_register(struct snd_sof_dev *sdev, void *pdata)
487 {
488 struct snd_sof_pdata *plat_data = pdata;
489 const char *drv_name;
490 const void *mach;
491 int size;
492
493 drv_name = plat_data->machine->drv_name;
494 mach = plat_data->machine;
495 size = sizeof(*plat_data->machine);
496
497 /* register machine driver, pass machine info as pdata */
498 plat_data->pdev_mach =
499 platform_device_register_data(sdev->dev, drv_name,
500 PLATFORM_DEVID_NONE, mach, size);
501 if (IS_ERR(plat_data->pdev_mach))
502 return PTR_ERR(plat_data->pdev_mach);
503
504 dev_dbg(sdev->dev, "created machine %s\n",
505 dev_name(&plat_data->pdev_mach->dev));
506
507 return 0;
508 }
509 EXPORT_SYMBOL(sof_machine_register);
510
sof_machine_unregister(struct snd_sof_dev * sdev,void * pdata)511 void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata)
512 {
513 struct snd_sof_pdata *plat_data = pdata;
514
515 if (!IS_ERR_OR_NULL(plat_data->pdev_mach))
516 platform_device_unregister(plat_data->pdev_mach);
517 }
518 EXPORT_SYMBOL(sof_machine_unregister);
519