ASoC: SOF: partition audio-related parts from SOF core
[linux-2.6-microblaze.git] / sound / soc / sof / pm.c
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 #include "sof-audio.h"
14
15 static int sof_send_pm_ctx_ipc(struct snd_sof_dev *sdev, int cmd)
16 {
17         struct sof_ipc_pm_ctx pm_ctx;
18         struct sof_ipc_reply reply;
19
20         memset(&pm_ctx, 0, sizeof(pm_ctx));
21
22         /* configure ctx save ipc message */
23         pm_ctx.hdr.size = sizeof(pm_ctx);
24         pm_ctx.hdr.cmd = SOF_IPC_GLB_PM_MSG | cmd;
25
26         /* send ctx save ipc to dsp */
27         return sof_ipc_tx_message(sdev->ipc, pm_ctx.hdr.cmd, &pm_ctx,
28                                  sizeof(pm_ctx), &reply, sizeof(reply));
29 }
30
31 #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
32 static void sof_cache_debugfs(struct snd_sof_dev *sdev)
33 {
34         struct snd_sof_dfsentry *dfse;
35
36         list_for_each_entry(dfse, &sdev->dfsentry_list, list) {
37
38                 /* nothing to do if debugfs buffer is not IO mem */
39                 if (dfse->type == SOF_DFSENTRY_TYPE_BUF)
40                         continue;
41
42                 /* cache memory that is only accessible in D0 */
43                 if (dfse->access_type == SOF_DEBUGFS_ACCESS_D0_ONLY)
44                         memcpy_fromio(dfse->cache_buf, dfse->io_mem,
45                                       dfse->size);
46         }
47 }
48 #endif
49
50 static int sof_resume(struct device *dev, bool runtime_resume)
51 {
52         struct snd_sof_dev *sdev = dev_get_drvdata(dev);
53         int ret;
54
55         /* do nothing if dsp resume callbacks are not set */
56         if (!sof_ops(sdev)->resume || !sof_ops(sdev)->runtime_resume)
57                 return 0;
58
59         /*
60          * if the runtime_resume flag is set, call the runtime_resume routine
61          * or else call the system resume routine
62          */
63         if (runtime_resume)
64                 ret = snd_sof_dsp_runtime_resume(sdev);
65         else
66                 ret = snd_sof_dsp_resume(sdev);
67         if (ret < 0) {
68                 dev_err(sdev->dev,
69                         "error: failed to power up DSP after resume\n");
70                 return ret;
71         }
72
73         /* load the firmware */
74         ret = snd_sof_load_firmware(sdev);
75         if (ret < 0) {
76                 dev_err(sdev->dev,
77                         "error: failed to load DSP firmware after resume %d\n",
78                         ret);
79                 return ret;
80         }
81
82         /* boot the firmware */
83         ret = snd_sof_run_firmware(sdev);
84         if (ret < 0) {
85                 dev_err(sdev->dev,
86                         "error: failed to boot DSP firmware after resume %d\n",
87                         ret);
88                 return ret;
89         }
90
91         /* resume DMA trace, only need send ipc */
92         ret = snd_sof_init_trace_ipc(sdev);
93         if (ret < 0) {
94                 /* non fatal */
95                 dev_warn(sdev->dev,
96                          "warning: failed to init trace after resume %d\n",
97                          ret);
98         }
99
100         /* restore pipelines */
101         ret = sof_restore_pipelines(sdev->dev);
102         if (ret < 0) {
103                 dev_err(sdev->dev,
104                         "error: failed to restore pipeline after resume %d\n",
105                         ret);
106                 return ret;
107         }
108
109         /* notify DSP of system resume */
110         ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_RESTORE);
111         if (ret < 0)
112                 dev_err(sdev->dev,
113                         "error: ctx_restore ipc error during resume %d\n",
114                         ret);
115
116         /* initialize default D0 sub-state */
117         sdev->d0_substate = SOF_DSP_D0I0;
118
119         return ret;
120 }
121
122 static int sof_suspend(struct device *dev, bool runtime_suspend)
123 {
124         struct snd_sof_dev *sdev = dev_get_drvdata(dev);
125         int ret;
126
127         /* do nothing if dsp suspend callback is not set */
128         if (!sof_ops(sdev)->suspend)
129                 return 0;
130
131         /* release trace */
132         snd_sof_release_trace(sdev);
133
134         /* set restore_stream for all streams during system suspend */
135         if (!runtime_suspend) {
136                 ret = sof_set_hw_params_upon_resume(sdev->dev);
137                 if (ret < 0) {
138                         dev_err(sdev->dev,
139                                 "error: setting hw_params flag during suspend %d\n",
140                                 ret);
141                         return ret;
142                 }
143         }
144
145 #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
146         /* cache debugfs contents during runtime suspend */
147         if (runtime_suspend)
148                 sof_cache_debugfs(sdev);
149 #endif
150         /* notify DSP of upcoming power down */
151         ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE);
152         if (ret == -EBUSY || ret == -EAGAIN) {
153                 /*
154                  * runtime PM has logic to handle -EBUSY/-EAGAIN so
155                  * pass these errors up
156                  */
157                 dev_err(sdev->dev,
158                         "error: ctx_save ipc error during suspend %d\n",
159                         ret);
160                 return ret;
161         } else if (ret < 0) {
162                 /* FW in unexpected state, continue to power down */
163                 dev_warn(sdev->dev,
164                          "ctx_save ipc error %d, proceeding with suspend\n",
165                          ret);
166         }
167
168         /* power down all DSP cores */
169         if (runtime_suspend)
170                 ret = snd_sof_dsp_runtime_suspend(sdev);
171         else
172                 ret = snd_sof_dsp_suspend(sdev);
173         if (ret < 0)
174                 dev_err(sdev->dev,
175                         "error: failed to power down DSP during suspend %d\n",
176                         ret);
177
178         return ret;
179 }
180
181 int snd_sof_runtime_suspend(struct device *dev)
182 {
183         return sof_suspend(dev, true);
184 }
185 EXPORT_SYMBOL(snd_sof_runtime_suspend);
186
187 int snd_sof_runtime_idle(struct device *dev)
188 {
189         struct snd_sof_dev *sdev = dev_get_drvdata(dev);
190
191         return snd_sof_dsp_runtime_idle(sdev);
192 }
193 EXPORT_SYMBOL(snd_sof_runtime_idle);
194
195 int snd_sof_runtime_resume(struct device *dev)
196 {
197         return sof_resume(dev, true);
198 }
199 EXPORT_SYMBOL(snd_sof_runtime_resume);
200
201 int snd_sof_set_d0_substate(struct snd_sof_dev *sdev,
202                             enum sof_d0_substate d0_substate)
203 {
204         int ret;
205
206         if (sdev->d0_substate == d0_substate)
207                 return 0;
208
209         /* do platform specific set_state */
210         ret = snd_sof_dsp_set_power_state(sdev, d0_substate);
211         if (ret < 0)
212                 return ret;
213
214         /* update dsp D0 sub-state */
215         sdev->d0_substate = d0_substate;
216
217         return 0;
218 }
219 EXPORT_SYMBOL(snd_sof_set_d0_substate);
220
221 /*
222  * Audio DSP states may transform as below:-
223  *
224  *                                         D0I3 compatible stream
225  *     Runtime    +---------------------+   opened only, timeout
226  *     suspend    |                     +--------------------+
227  *   +------------+       D0(active)    |                    |
228  *   |            |                     <---------------+    |
229  *   |   +-------->                     |               |    |
230  *   |   |Runtime +--^--+---------^--+--+ The last      |    |
231  *   |   |resume     |  |         |  |    opened D0I3   |    |
232  *   |   |           |  |         |  |    compatible    |    |
233  *   |   |     resume|  |         |  |    stream closed |    |
234  *   |   |      from |  | D3      |  |                  |    |
235  *   |   |       D3  |  |suspend  |  | d0i3             |    |
236  *   |   |           |  |         |  |suspend           |    |
237  *   |   |           |  |         |  |                  |    |
238  *   |   |           |  |         |  |                  |    |
239  * +-v---+-----------+--v-------+ |  |           +------+----v----+
240  * |                            | |  +----------->                |
241  * |       D3 (suspended)       | |              |      D0I3      +-----+
242  * |                            | +--------------+                |     |
243  * |                            |  resume from   |                |     |
244  * +-------------------^--------+  d0i3 suspend  +----------------+     |
245  *                     |                                                |
246  *                     |                       D3 suspend               |
247  *                     +------------------------------------------------+
248  *
249  * d0i3_suspend = s0_suspend && D0I3 stream opened,
250  * D3 suspend = !d0i3_suspend,
251  */
252
253 int snd_sof_resume(struct device *dev)
254 {
255         struct snd_sof_dev *sdev = dev_get_drvdata(dev);
256         int ret;
257
258         if (snd_sof_dsp_d0i3_on_suspend(sdev)) {
259                 /* resume from D0I3 */
260                 dev_dbg(sdev->dev, "DSP will exit from D0i3...\n");
261                 ret = snd_sof_set_d0_substate(sdev, SOF_DSP_D0I0);
262                 if (ret == -ENOTSUPP) {
263                         /* fallback to resume from D3 */
264                         dev_dbg(sdev->dev, "D0i3 not supported, fall back to resume from D3...\n");
265                         goto d3_resume;
266                 } else if (ret < 0) {
267                         dev_err(sdev->dev, "error: failed to exit from D0I3 %d\n",
268                                 ret);
269                         return ret;
270                 }
271
272                 /* platform-specific resume from D0i3 */
273                 return snd_sof_dsp_resume(sdev);
274         }
275
276 d3_resume:
277         /* resume from D3 */
278         return sof_resume(dev, false);
279 }
280 EXPORT_SYMBOL(snd_sof_resume);
281
282 int snd_sof_suspend(struct device *dev)
283 {
284         struct snd_sof_dev *sdev = dev_get_drvdata(dev);
285         int ret;
286
287         if (snd_sof_dsp_d0i3_on_suspend(sdev)) {
288                 /* suspend to D0i3 */
289                 dev_dbg(sdev->dev, "DSP is trying to enter D0i3...\n");
290                 ret = snd_sof_set_d0_substate(sdev, SOF_DSP_D0I3);
291                 if (ret == -ENOTSUPP) {
292                         /* fallback to D3 suspend */
293                         dev_dbg(sdev->dev, "D0i3 not supported, fall back to D3...\n");
294                         goto d3_suspend;
295                 } else if (ret < 0) {
296                         dev_err(sdev->dev, "error: failed to enter D0I3, %d\n",
297                                 ret);
298                         return ret;
299                 }
300
301                 /* platform-specific suspend to D0i3 */
302                 return snd_sof_dsp_suspend(sdev);
303         }
304
305 d3_suspend:
306         /* suspend to D3 */
307         return sof_suspend(dev, false);
308 }
309 EXPORT_SYMBOL(snd_sof_suspend);
310
311 int snd_sof_prepare(struct device *dev)
312 {
313         struct snd_sof_dev *sdev = dev_get_drvdata(dev);
314
315 #if defined(CONFIG_ACPI)
316         sdev->s0_suspend = acpi_target_system_state() == ACPI_STATE_S0;
317 #else
318         /* will suspend to S3 by default */
319         sdev->s0_suspend = false;
320 #endif
321
322         return 0;
323 }
324 EXPORT_SYMBOL(snd_sof_prepare);
325
326 void snd_sof_complete(struct device *dev)
327 {
328         struct snd_sof_dev *sdev = dev_get_drvdata(dev);
329
330         sdev->s0_suspend = false;
331 }
332 EXPORT_SYMBOL(snd_sof_complete);