Merge tag 'compat-ioctl-fix' of git://git.kernel.org:/pub/scm/linux/kernel/git/arnd...
[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         /* DSP was never successfully started, nothing to resume */
60         if (sdev->first_boot)
61                 return 0;
62
63         /*
64          * if the runtime_resume flag is set, call the runtime_resume routine
65          * or else call the system resume routine
66          */
67         if (runtime_resume)
68                 ret = snd_sof_dsp_runtime_resume(sdev);
69         else
70                 ret = snd_sof_dsp_resume(sdev);
71         if (ret < 0) {
72                 dev_err(sdev->dev,
73                         "error: failed to power up DSP after resume\n");
74                 return ret;
75         }
76
77         sdev->fw_state = SOF_FW_BOOT_PREPARE;
78
79         /* load the firmware */
80         ret = snd_sof_load_firmware(sdev);
81         if (ret < 0) {
82                 dev_err(sdev->dev,
83                         "error: failed to load DSP firmware after resume %d\n",
84                         ret);
85                 return ret;
86         }
87
88         sdev->fw_state = SOF_FW_BOOT_IN_PROGRESS;
89
90         /*
91          * Boot the firmware. The FW boot status will be modified
92          * in snd_sof_run_firmware() depending on the outcome.
93          */
94         ret = snd_sof_run_firmware(sdev);
95         if (ret < 0) {
96                 dev_err(sdev->dev,
97                         "error: failed to boot DSP firmware after resume %d\n",
98                         ret);
99                 return ret;
100         }
101
102         /* resume DMA trace, only need send ipc */
103         ret = snd_sof_init_trace_ipc(sdev);
104         if (ret < 0) {
105                 /* non fatal */
106                 dev_warn(sdev->dev,
107                          "warning: failed to init trace after resume %d\n",
108                          ret);
109         }
110
111         /* restore pipelines */
112         ret = sof_restore_pipelines(sdev->dev);
113         if (ret < 0) {
114                 dev_err(sdev->dev,
115                         "error: failed to restore pipeline after resume %d\n",
116                         ret);
117                 return ret;
118         }
119
120         /* notify DSP of system resume */
121         ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_RESTORE);
122         if (ret < 0)
123                 dev_err(sdev->dev,
124                         "error: ctx_restore ipc error during resume %d\n",
125                         ret);
126
127         /* initialize default D0 sub-state */
128         sdev->d0_substate = SOF_DSP_D0I0;
129
130         return ret;
131 }
132
133 static int sof_suspend(struct device *dev, bool runtime_suspend)
134 {
135         struct snd_sof_dev *sdev = dev_get_drvdata(dev);
136         int ret;
137
138         /* do nothing if dsp suspend callback is not set */
139         if (!sof_ops(sdev)->suspend)
140                 return 0;
141
142         if (sdev->fw_state != SOF_FW_BOOT_COMPLETE)
143                 goto power_down;
144
145         /* release trace */
146         snd_sof_release_trace(sdev);
147
148         /* set restore_stream for all streams during system suspend */
149         if (!runtime_suspend) {
150                 ret = sof_set_hw_params_upon_resume(sdev->dev);
151                 if (ret < 0) {
152                         dev_err(sdev->dev,
153                                 "error: setting hw_params flag during suspend %d\n",
154                                 ret);
155                         return ret;
156                 }
157         }
158
159 #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
160         /* cache debugfs contents during runtime suspend */
161         if (runtime_suspend)
162                 sof_cache_debugfs(sdev);
163 #endif
164         /* notify DSP of upcoming power down */
165         ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE);
166         if (ret == -EBUSY || ret == -EAGAIN) {
167                 /*
168                  * runtime PM has logic to handle -EBUSY/-EAGAIN so
169                  * pass these errors up
170                  */
171                 dev_err(sdev->dev,
172                         "error: ctx_save ipc error during suspend %d\n",
173                         ret);
174                 return ret;
175         } else if (ret < 0) {
176                 /* FW in unexpected state, continue to power down */
177                 dev_warn(sdev->dev,
178                          "ctx_save ipc error %d, proceeding with suspend\n",
179                          ret);
180         }
181
182 power_down:
183
184         /* return if the DSP was not probed successfully */
185         if (sdev->fw_state == SOF_FW_BOOT_NOT_STARTED)
186                 return 0;
187
188         /* power down all DSP cores */
189         if (runtime_suspend)
190                 ret = snd_sof_dsp_runtime_suspend(sdev);
191         else
192                 ret = snd_sof_dsp_suspend(sdev);
193         if (ret < 0)
194                 dev_err(sdev->dev,
195                         "error: failed to power down DSP during suspend %d\n",
196                         ret);
197
198         /* reset FW state */
199         sdev->fw_state = SOF_FW_BOOT_NOT_STARTED;
200
201         return ret;
202 }
203
204 int snd_sof_runtime_suspend(struct device *dev)
205 {
206         return sof_suspend(dev, true);
207 }
208 EXPORT_SYMBOL(snd_sof_runtime_suspend);
209
210 int snd_sof_runtime_idle(struct device *dev)
211 {
212         struct snd_sof_dev *sdev = dev_get_drvdata(dev);
213
214         return snd_sof_dsp_runtime_idle(sdev);
215 }
216 EXPORT_SYMBOL(snd_sof_runtime_idle);
217
218 int snd_sof_runtime_resume(struct device *dev)
219 {
220         return sof_resume(dev, true);
221 }
222 EXPORT_SYMBOL(snd_sof_runtime_resume);
223
224 int snd_sof_set_d0_substate(struct snd_sof_dev *sdev,
225                             enum sof_d0_substate d0_substate)
226 {
227         int ret;
228
229         if (sdev->d0_substate == d0_substate)
230                 return 0;
231
232         /* do platform specific set_state */
233         ret = snd_sof_dsp_set_power_state(sdev, d0_substate);
234         if (ret < 0)
235                 return ret;
236
237         /* update dsp D0 sub-state */
238         sdev->d0_substate = d0_substate;
239
240         return 0;
241 }
242 EXPORT_SYMBOL(snd_sof_set_d0_substate);
243
244 /*
245  * Audio DSP states may transform as below:-
246  *
247  *                                         D0I3 compatible stream
248  *     Runtime    +---------------------+   opened only, timeout
249  *     suspend    |                     +--------------------+
250  *   +------------+       D0(active)    |                    |
251  *   |            |                     <---------------+    |
252  *   |   +-------->                     |               |    |
253  *   |   |Runtime +--^--+---------^--+--+ The last      |    |
254  *   |   |resume     |  |         |  |    opened D0I3   |    |
255  *   |   |           |  |         |  |    compatible    |    |
256  *   |   |     resume|  |         |  |    stream closed |    |
257  *   |   |      from |  | D3      |  |                  |    |
258  *   |   |       D3  |  |suspend  |  | d0i3             |    |
259  *   |   |           |  |         |  |suspend           |    |
260  *   |   |           |  |         |  |                  |    |
261  *   |   |           |  |         |  |                  |    |
262  * +-v---+-----------+--v-------+ |  |           +------+----v----+
263  * |                            | |  +----------->                |
264  * |       D3 (suspended)       | |              |      D0I3      +-----+
265  * |                            | +--------------+                |     |
266  * |                            |  resume from   |                |     |
267  * +-------------------^--------+  d0i3 suspend  +----------------+     |
268  *                     |                                                |
269  *                     |                       D3 suspend               |
270  *                     +------------------------------------------------+
271  *
272  * d0i3_suspend = s0_suspend && D0I3 stream opened,
273  * D3 suspend = !d0i3_suspend,
274  */
275
276 int snd_sof_resume(struct device *dev)
277 {
278         struct snd_sof_dev *sdev = dev_get_drvdata(dev);
279         int ret;
280
281         if (snd_sof_dsp_d0i3_on_suspend(sdev)) {
282                 /* resume from D0I3 */
283                 dev_dbg(sdev->dev, "DSP will exit from D0i3...\n");
284                 ret = snd_sof_set_d0_substate(sdev, SOF_DSP_D0I0);
285                 if (ret == -ENOTSUPP) {
286                         /* fallback to resume from D3 */
287                         dev_dbg(sdev->dev, "D0i3 not supported, fall back to resume from D3...\n");
288                         goto d3_resume;
289                 } else if (ret < 0) {
290                         dev_err(sdev->dev, "error: failed to exit from D0I3 %d\n",
291                                 ret);
292                         return ret;
293                 }
294
295                 /* platform-specific resume from D0i3 */
296                 return snd_sof_dsp_resume(sdev);
297         }
298
299 d3_resume:
300         /* resume from D3 */
301         return sof_resume(dev, false);
302 }
303 EXPORT_SYMBOL(snd_sof_resume);
304
305 int snd_sof_suspend(struct device *dev)
306 {
307         struct snd_sof_dev *sdev = dev_get_drvdata(dev);
308         int ret;
309
310         if (snd_sof_dsp_d0i3_on_suspend(sdev)) {
311                 /* suspend to D0i3 */
312                 dev_dbg(sdev->dev, "DSP is trying to enter D0i3...\n");
313                 ret = snd_sof_set_d0_substate(sdev, SOF_DSP_D0I3);
314                 if (ret == -ENOTSUPP) {
315                         /* fallback to D3 suspend */
316                         dev_dbg(sdev->dev, "D0i3 not supported, fall back to D3...\n");
317                         goto d3_suspend;
318                 } else if (ret < 0) {
319                         dev_err(sdev->dev, "error: failed to enter D0I3, %d\n",
320                                 ret);
321                         return ret;
322                 }
323
324                 /* platform-specific suspend to D0i3 */
325                 return snd_sof_dsp_suspend(sdev);
326         }
327
328 d3_suspend:
329         /* suspend to D3 */
330         return sof_suspend(dev, false);
331 }
332 EXPORT_SYMBOL(snd_sof_suspend);
333
334 int snd_sof_prepare(struct device *dev)
335 {
336         struct snd_sof_dev *sdev = dev_get_drvdata(dev);
337
338 #if defined(CONFIG_ACPI)
339         sdev->s0_suspend = acpi_target_system_state() == ACPI_STATE_S0;
340 #else
341         /* will suspend to S3 by default */
342         sdev->s0_suspend = false;
343 #endif
344
345         return 0;
346 }
347 EXPORT_SYMBOL(snd_sof_prepare);
348
349 void snd_sof_complete(struct device *dev)
350 {
351         struct snd_sof_dev *sdev = dev_get_drvdata(dev);
352
353         sdev->s0_suspend = false;
354 }
355 EXPORT_SYMBOL(snd_sof_complete);