xref: /linux/sound/soc/sof/pm.c (revision f86fd32d)
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);
356