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