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