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 */
18bool 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}
45EXPORT_SYMBOL(snd_sof_dsp_only_d0i3_compatible_stream_active);
46
47bool 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
60int 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
96static 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
145const 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
161int 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
326struct 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
351struct 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
371struct 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
385struct 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 */
400struct snd_sof_widget *
401snd_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
422struct 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 */
439int 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)
464nocodec:
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}
484EXPORT_SYMBOL(sof_machine_check);
485
486int 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}
509EXPORT_SYMBOL(sof_machine_register);
510
511void 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}
518EXPORT_SYMBOL(sof_machine_unregister);
519