]>
Commit | Line | Data |
---|---|---|
8920153c LG |
1 | // SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. | |
7 | // | |
8 | // Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> | |
9 | // | |
10 | ||
11 | #include "ops.h" | |
12 | #include "sof-priv.h" | |
13 | ||
14 | static int sof_restore_kcontrols(struct snd_sof_dev *sdev) | |
15 | { | |
16 | struct snd_sof_control *scontrol; | |
17 | int ipc_cmd, ctrl_type; | |
18 | int ret = 0; | |
19 | ||
20 | /* restore kcontrol values */ | |
21 | list_for_each_entry(scontrol, &sdev->kcontrol_list, list) { | |
22 | /* reset readback offset for scontrol after resuming */ | |
23 | scontrol->readback_offset = 0; | |
24 | ||
25 | /* notify DSP of kcontrol values */ | |
26 | switch (scontrol->cmd) { | |
27 | case SOF_CTRL_CMD_VOLUME: | |
28 | case SOF_CTRL_CMD_ENUM: | |
29 | case SOF_CTRL_CMD_SWITCH: | |
30 | ipc_cmd = SOF_IPC_COMP_SET_VALUE; | |
31 | ctrl_type = SOF_CTRL_TYPE_VALUE_CHAN_SET; | |
32 | ret = snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, | |
33 | ipc_cmd, ctrl_type, | |
34 | scontrol->cmd, | |
35 | true); | |
36 | break; | |
37 | case SOF_CTRL_CMD_BINARY: | |
38 | ipc_cmd = SOF_IPC_COMP_SET_DATA; | |
39 | ctrl_type = SOF_CTRL_TYPE_DATA_SET; | |
40 | ret = snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, | |
41 | ipc_cmd, ctrl_type, | |
42 | scontrol->cmd, | |
43 | true); | |
44 | break; | |
45 | ||
46 | default: | |
47 | break; | |
48 | } | |
49 | ||
50 | if (ret < 0) { | |
51 | dev_err(sdev->dev, | |
52 | "error: failed kcontrol value set for widget: %d\n", | |
53 | scontrol->comp_id); | |
54 | ||
55 | return ret; | |
56 | } | |
57 | } | |
58 | ||
59 | return 0; | |
60 | } | |
61 | ||
62 | static int sof_restore_pipelines(struct snd_sof_dev *sdev) | |
63 | { | |
64 | struct snd_sof_widget *swidget; | |
65 | struct snd_sof_route *sroute; | |
66 | struct sof_ipc_pipe_new *pipeline; | |
67 | struct snd_sof_dai *dai; | |
68 | struct sof_ipc_comp_dai *comp_dai; | |
69 | struct sof_ipc_cmd_hdr *hdr; | |
70 | int ret; | |
71 | ||
72 | /* restore pipeline components */ | |
73 | list_for_each_entry_reverse(swidget, &sdev->widget_list, list) { | |
74 | struct sof_ipc_comp_reply r; | |
75 | ||
76 | /* skip if there is no private data */ | |
77 | if (!swidget->private) | |
78 | continue; | |
79 | ||
80 | switch (swidget->id) { | |
81 | case snd_soc_dapm_dai_in: | |
82 | case snd_soc_dapm_dai_out: | |
83 | dai = swidget->private; | |
84 | comp_dai = &dai->comp_dai; | |
85 | ret = sof_ipc_tx_message(sdev->ipc, | |
86 | comp_dai->comp.hdr.cmd, | |
87 | comp_dai, sizeof(*comp_dai), | |
88 | &r, sizeof(r)); | |
89 | break; | |
90 | case snd_soc_dapm_scheduler: | |
91 | ||
92 | /* | |
93 | * During suspend, all DSP cores are powered off. | |
94 | * Therefore upon resume, create the pipeline comp | |
95 | * and power up the core that the pipeline is | |
96 | * scheduled on. | |
97 | */ | |
98 | pipeline = swidget->private; | |
99 | ret = sof_load_pipeline_ipc(sdev, pipeline, &r); | |
100 | break; | |
101 | default: | |
102 | hdr = swidget->private; | |
103 | ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd, | |
104 | swidget->private, hdr->size, | |
105 | &r, sizeof(r)); | |
106 | break; | |
107 | } | |
108 | if (ret < 0) { | |
109 | dev_err(sdev->dev, | |
110 | "error: failed to load widget type %d with ID: %d\n", | |
111 | swidget->widget->id, swidget->comp_id); | |
112 | ||
113 | return ret; | |
114 | } | |
115 | } | |
116 | ||
117 | /* restore pipeline connections */ | |
118 | list_for_each_entry_reverse(sroute, &sdev->route_list, list) { | |
119 | struct sof_ipc_pipe_comp_connect *connect; | |
120 | struct sof_ipc_reply reply; | |
121 | ||
122 | /* skip if there's no private data */ | |
123 | if (!sroute->private) | |
124 | continue; | |
125 | ||
126 | connect = sroute->private; | |
127 | ||
128 | /* send ipc */ | |
129 | ret = sof_ipc_tx_message(sdev->ipc, | |
130 | connect->hdr.cmd, | |
131 | connect, sizeof(*connect), | |
132 | &reply, sizeof(reply)); | |
133 | if (ret < 0) { | |
134 | dev_err(sdev->dev, | |
135 | "error: failed to load route sink %s control %s source %s\n", | |
136 | sroute->route->sink, | |
137 | sroute->route->control ? sroute->route->control | |
138 | : "none", | |
139 | sroute->route->source); | |
140 | ||
141 | return ret; | |
142 | } | |
143 | } | |
144 | ||
145 | /* restore dai links */ | |
146 | list_for_each_entry_reverse(dai, &sdev->dai_list, list) { | |
147 | struct sof_ipc_reply reply; | |
148 | struct sof_ipc_dai_config *config = dai->dai_config; | |
149 | ||
150 | if (!config) { | |
151 | dev_err(sdev->dev, "error: no config for DAI %s\n", | |
152 | dai->name); | |
153 | continue; | |
154 | } | |
155 | ||
156 | ret = sof_ipc_tx_message(sdev->ipc, | |
157 | config->hdr.cmd, config, | |
158 | config->hdr.size, | |
159 | &reply, sizeof(reply)); | |
160 | ||
161 | if (ret < 0) { | |
162 | dev_err(sdev->dev, | |
163 | "error: failed to set dai config for %s\n", | |
164 | dai->name); | |
165 | ||
166 | return ret; | |
167 | } | |
168 | } | |
169 | ||
170 | /* complete pipeline */ | |
171 | list_for_each_entry(swidget, &sdev->widget_list, list) { | |
172 | switch (swidget->id) { | |
173 | case snd_soc_dapm_scheduler: | |
174 | swidget->complete = | |
175 | snd_sof_complete_pipeline(sdev, swidget); | |
176 | break; | |
177 | default: | |
178 | break; | |
179 | } | |
180 | } | |
181 | ||
182 | /* restore pipeline kcontrols */ | |
183 | ret = sof_restore_kcontrols(sdev); | |
184 | if (ret < 0) | |
185 | dev_err(sdev->dev, | |
186 | "error: restoring kcontrols after resume\n"); | |
187 | ||
188 | return ret; | |
189 | } | |
190 | ||
191 | static int sof_send_pm_ipc(struct snd_sof_dev *sdev, int cmd) | |
192 | { | |
193 | struct sof_ipc_pm_ctx pm_ctx; | |
194 | struct sof_ipc_reply reply; | |
195 | ||
196 | memset(&pm_ctx, 0, sizeof(pm_ctx)); | |
197 | ||
198 | /* configure ctx save ipc message */ | |
199 | pm_ctx.hdr.size = sizeof(pm_ctx); | |
200 | pm_ctx.hdr.cmd = SOF_IPC_GLB_PM_MSG | cmd; | |
201 | ||
202 | /* send ctx save ipc to dsp */ | |
203 | return sof_ipc_tx_message(sdev->ipc, pm_ctx.hdr.cmd, &pm_ctx, | |
204 | sizeof(pm_ctx), &reply, sizeof(reply)); | |
205 | } | |
206 | ||
207 | static void sof_set_hw_params_upon_resume(struct snd_sof_dev *sdev) | |
208 | { | |
209 | struct snd_pcm_substream *substream; | |
210 | struct snd_sof_pcm *spcm; | |
211 | snd_pcm_state_t state; | |
212 | int dir; | |
213 | ||
214 | /* | |
215 | * SOF requires hw_params to be set-up internally upon resume. | |
216 | * So, set the flag to indicate this for those streams that | |
217 | * have been suspended. | |
218 | */ | |
219 | list_for_each_entry(spcm, &sdev->pcm_list, list) { | |
220 | for (dir = 0; dir <= SNDRV_PCM_STREAM_CAPTURE; dir++) { | |
221 | substream = spcm->stream[dir].substream; | |
222 | if (!substream || !substream->runtime) | |
223 | continue; | |
224 | ||
225 | state = substream->runtime->status->state; | |
226 | if (state == SNDRV_PCM_STATE_SUSPENDED) | |
227 | spcm->hw_params_upon_resume[dir] = 1; | |
228 | } | |
229 | } | |
ed3baacd RS |
230 | |
231 | /* set internal flag for BE */ | |
232 | snd_sof_dsp_hw_params_upon_resume(sdev); | |
8920153c LG |
233 | } |
234 | ||
235 | #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) | |
236 | static void sof_cache_debugfs(struct snd_sof_dev *sdev) | |
237 | { | |
238 | struct snd_sof_dfsentry *dfse; | |
239 | ||
240 | list_for_each_entry(dfse, &sdev->dfsentry_list, list) { | |
241 | ||
242 | /* nothing to do if debugfs buffer is not IO mem */ | |
243 | if (dfse->type == SOF_DFSENTRY_TYPE_BUF) | |
244 | continue; | |
245 | ||
246 | /* cache memory that is only accessible in D0 */ | |
247 | if (dfse->access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) | |
248 | memcpy_fromio(dfse->cache_buf, dfse->io_mem, | |
249 | dfse->size); | |
250 | } | |
251 | } | |
252 | #endif | |
253 | ||
254 | static int sof_resume(struct device *dev, bool runtime_resume) | |
255 | { | |
256 | struct snd_sof_dev *sdev = dev_get_drvdata(dev); | |
257 | int ret; | |
258 | ||
259 | /* do nothing if dsp resume callbacks are not set */ | |
260 | if (!sof_ops(sdev)->resume || !sof_ops(sdev)->runtime_resume) | |
261 | return 0; | |
262 | ||
263 | /* | |
264 | * if the runtime_resume flag is set, call the runtime_resume routine | |
265 | * or else call the system resume routine | |
266 | */ | |
267 | if (runtime_resume) | |
268 | ret = snd_sof_dsp_runtime_resume(sdev); | |
269 | else | |
270 | ret = snd_sof_dsp_resume(sdev); | |
271 | if (ret < 0) { | |
272 | dev_err(sdev->dev, | |
273 | "error: failed to power up DSP after resume\n"); | |
274 | return ret; | |
275 | } | |
276 | ||
277 | /* load the firmware */ | |
278 | ret = snd_sof_load_firmware(sdev); | |
279 | if (ret < 0) { | |
280 | dev_err(sdev->dev, | |
281 | "error: failed to load DSP firmware after resume %d\n", | |
282 | ret); | |
283 | return ret; | |
284 | } | |
285 | ||
286 | /* boot the firmware */ | |
287 | ret = snd_sof_run_firmware(sdev); | |
288 | if (ret < 0) { | |
289 | dev_err(sdev->dev, | |
290 | "error: failed to boot DSP firmware after resume %d\n", | |
291 | ret); | |
292 | return ret; | |
293 | } | |
294 | ||
295 | /* resume DMA trace, only need send ipc */ | |
296 | ret = snd_sof_init_trace_ipc(sdev); | |
297 | if (ret < 0) { | |
298 | /* non fatal */ | |
299 | dev_warn(sdev->dev, | |
300 | "warning: failed to init trace after resume %d\n", | |
301 | ret); | |
302 | } | |
303 | ||
304 | /* restore pipelines */ | |
305 | ret = sof_restore_pipelines(sdev); | |
306 | if (ret < 0) { | |
307 | dev_err(sdev->dev, | |
308 | "error: failed to restore pipeline after resume %d\n", | |
309 | ret); | |
310 | return ret; | |
311 | } | |
312 | ||
313 | /* notify DSP of system resume */ | |
314 | ret = sof_send_pm_ipc(sdev, SOF_IPC_PM_CTX_RESTORE); | |
315 | if (ret < 0) | |
316 | dev_err(sdev->dev, | |
317 | "error: ctx_restore ipc error during resume %d\n", | |
318 | ret); | |
319 | ||
320 | return ret; | |
321 | } | |
322 | ||
323 | static int sof_suspend(struct device *dev, bool runtime_suspend) | |
324 | { | |
325 | struct snd_sof_dev *sdev = dev_get_drvdata(dev); | |
326 | int ret; | |
327 | ||
328 | /* do nothing if dsp suspend callback is not set */ | |
329 | if (!sof_ops(sdev)->suspend) | |
330 | return 0; | |
331 | ||
332 | /* release trace */ | |
333 | snd_sof_release_trace(sdev); | |
334 | ||
335 | /* set restore_stream for all streams during system suspend */ | |
336 | if (!runtime_suspend) | |
337 | sof_set_hw_params_upon_resume(sdev); | |
338 | ||
339 | #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) | |
340 | /* cache debugfs contents during runtime suspend */ | |
341 | if (runtime_suspend) | |
342 | sof_cache_debugfs(sdev); | |
343 | #endif | |
344 | /* notify DSP of upcoming power down */ | |
345 | ret = sof_send_pm_ipc(sdev, SOF_IPC_PM_CTX_SAVE); | |
346 | if (ret < 0) { | |
347 | dev_err(sdev->dev, | |
348 | "error: ctx_save ipc error during suspend %d\n", | |
349 | ret); | |
350 | return ret; | |
351 | } | |
352 | ||
353 | /* power down all DSP cores */ | |
354 | if (runtime_suspend) | |
355 | ret = snd_sof_dsp_runtime_suspend(sdev, 0); | |
356 | else | |
357 | ret = snd_sof_dsp_suspend(sdev, 0); | |
358 | if (ret < 0) | |
359 | dev_err(sdev->dev, | |
360 | "error: failed to power down DSP during suspend %d\n", | |
361 | ret); | |
362 | ||
363 | return ret; | |
364 | } | |
365 | ||
366 | int snd_sof_runtime_suspend(struct device *dev) | |
367 | { | |
368 | return sof_suspend(dev, true); | |
369 | } | |
370 | EXPORT_SYMBOL(snd_sof_runtime_suspend); | |
371 | ||
372 | int snd_sof_runtime_resume(struct device *dev) | |
373 | { | |
374 | return sof_resume(dev, true); | |
375 | } | |
376 | EXPORT_SYMBOL(snd_sof_runtime_resume); | |
377 | ||
378 | int snd_sof_resume(struct device *dev) | |
379 | { | |
380 | return sof_resume(dev, false); | |
381 | } | |
382 | EXPORT_SYMBOL(snd_sof_resume); | |
383 | ||
384 | int snd_sof_suspend(struct device *dev) | |
385 | { | |
386 | return sof_suspend(dev, false); | |
387 | } | |
388 | EXPORT_SYMBOL(snd_sof_suspend); |