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