]>
Commit | Line | Data |
---|---|---|
8920153c LG |
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" | |
ee1e79b7 | 13 | #include "sof-audio.h" |
8920153c | 14 | |
700d1677 RS |
15 | /* |
16 | * Helper function to determine the target DSP state during | |
17 | * system suspend. This function only cares about the device | |
18 | * D-states. Platform-specific substates, if any, should be | |
19 | * handled by the platform-specific parts. | |
20 | */ | |
21 | static u32 snd_sof_dsp_power_target(struct snd_sof_dev *sdev) | |
22 | { | |
23 | u32 target_dsp_state; | |
24 | ||
25 | switch (sdev->system_suspend_target) { | |
26 | case SOF_SUSPEND_S3: | |
27 | /* DSP should be in D3 if the system is suspending to S3 */ | |
28 | target_dsp_state = SOF_DSP_PM_D3; | |
29 | break; | |
30 | case SOF_SUSPEND_S0IX: | |
31 | /* | |
32 | * Currently, the only criterion for retaining the DSP in D0 | |
33 | * is that there are streams that ignored the suspend trigger. | |
34 | * Additional criteria such Soundwire clock-stop mode and | |
35 | * device suspend latency considerations will be added later. | |
36 | */ | |
37 | if (snd_sof_stream_suspend_ignored(sdev)) | |
38 | target_dsp_state = SOF_DSP_PM_D0; | |
39 | else | |
40 | target_dsp_state = SOF_DSP_PM_D3; | |
41 | break; | |
42 | default: | |
43 | /* This case would be during runtime suspend */ | |
44 | target_dsp_state = SOF_DSP_PM_D3; | |
45 | break; | |
46 | } | |
47 | ||
48 | return target_dsp_state; | |
49 | } | |
50 | ||
7c7eba24 | 51 | static int sof_send_pm_ctx_ipc(struct snd_sof_dev *sdev, int cmd) |
8920153c LG |
52 | { |
53 | struct sof_ipc_pm_ctx pm_ctx; | |
54 | struct sof_ipc_reply reply; | |
55 | ||
56 | memset(&pm_ctx, 0, sizeof(pm_ctx)); | |
57 | ||
58 | /* configure ctx save ipc message */ | |
59 | pm_ctx.hdr.size = sizeof(pm_ctx); | |
60 | pm_ctx.hdr.cmd = SOF_IPC_GLB_PM_MSG | cmd; | |
61 | ||
62 | /* send ctx save ipc to dsp */ | |
63 | return sof_ipc_tx_message(sdev->ipc, pm_ctx.hdr.cmd, &pm_ctx, | |
64 | sizeof(pm_ctx), &reply, sizeof(reply)); | |
65 | } | |
66 | ||
8920153c LG |
67 | #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) |
68 | static void sof_cache_debugfs(struct snd_sof_dev *sdev) | |
69 | { | |
70 | struct snd_sof_dfsentry *dfse; | |
71 | ||
72 | list_for_each_entry(dfse, &sdev->dfsentry_list, list) { | |
73 | ||
74 | /* nothing to do if debugfs buffer is not IO mem */ | |
75 | if (dfse->type == SOF_DFSENTRY_TYPE_BUF) | |
76 | continue; | |
77 | ||
78 | /* cache memory that is only accessible in D0 */ | |
79 | if (dfse->access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) | |
80 | memcpy_fromio(dfse->cache_buf, dfse->io_mem, | |
81 | dfse->size); | |
82 | } | |
83 | } | |
84 | #endif | |
85 | ||
86 | static int sof_resume(struct device *dev, bool runtime_resume) | |
87 | { | |
88 | struct snd_sof_dev *sdev = dev_get_drvdata(dev); | |
61e285ca | 89 | u32 old_state = sdev->dsp_power_state.state; |
8920153c LG |
90 | int ret; |
91 | ||
92 | /* do nothing if dsp resume callbacks are not set */ | |
93 | if (!sof_ops(sdev)->resume || !sof_ops(sdev)->runtime_resume) | |
94 | return 0; | |
95 | ||
410e5e55 PLB |
96 | /* DSP was never successfully started, nothing to resume */ |
97 | if (sdev->first_boot) | |
98 | return 0; | |
99 | ||
8920153c LG |
100 | /* |
101 | * if the runtime_resume flag is set, call the runtime_resume routine | |
102 | * or else call the system resume routine | |
103 | */ | |
104 | if (runtime_resume) | |
105 | ret = snd_sof_dsp_runtime_resume(sdev); | |
106 | else | |
107 | ret = snd_sof_dsp_resume(sdev); | |
108 | if (ret < 0) { | |
109 | dev_err(sdev->dev, | |
110 | "error: failed to power up DSP after resume\n"); | |
111 | return ret; | |
112 | } | |
113 | ||
61e285ca RS |
114 | /* Nothing further to do if resuming from a low-power D0 substate */ |
115 | if (!runtime_resume && old_state == SOF_DSP_PM_D0) | |
fb9a8119 RS |
116 | return 0; |
117 | ||
6ca5cecb RS |
118 | sdev->fw_state = SOF_FW_BOOT_PREPARE; |
119 | ||
8920153c LG |
120 | /* load the firmware */ |
121 | ret = snd_sof_load_firmware(sdev); | |
122 | if (ret < 0) { | |
123 | dev_err(sdev->dev, | |
124 | "error: failed to load DSP firmware after resume %d\n", | |
125 | ret); | |
126 | return ret; | |
127 | } | |
128 | ||
6ca5cecb RS |
129 | sdev->fw_state = SOF_FW_BOOT_IN_PROGRESS; |
130 | ||
131 | /* | |
132 | * Boot the firmware. The FW boot status will be modified | |
133 | * in snd_sof_run_firmware() depending on the outcome. | |
134 | */ | |
8920153c LG |
135 | ret = snd_sof_run_firmware(sdev); |
136 | if (ret < 0) { | |
137 | dev_err(sdev->dev, | |
138 | "error: failed to boot DSP firmware after resume %d\n", | |
139 | ret); | |
140 | return ret; | |
141 | } | |
142 | ||
143 | /* resume DMA trace, only need send ipc */ | |
144 | ret = snd_sof_init_trace_ipc(sdev); | |
145 | if (ret < 0) { | |
146 | /* non fatal */ | |
147 | dev_warn(sdev->dev, | |
148 | "warning: failed to init trace after resume %d\n", | |
149 | ret); | |
150 | } | |
151 | ||
152 | /* restore pipelines */ | |
ee1e79b7 | 153 | ret = sof_restore_pipelines(sdev->dev); |
8920153c LG |
154 | if (ret < 0) { |
155 | dev_err(sdev->dev, | |
156 | "error: failed to restore pipeline after resume %d\n", | |
157 | ret); | |
158 | return ret; | |
159 | } | |
160 | ||
161 | /* notify DSP of system resume */ | |
7c7eba24 | 162 | ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_RESTORE); |
8920153c LG |
163 | if (ret < 0) |
164 | dev_err(sdev->dev, | |
165 | "error: ctx_restore ipc error during resume %d\n", | |
166 | ret); | |
167 | ||
168 | return ret; | |
169 | } | |
170 | ||
171 | static int sof_suspend(struct device *dev, bool runtime_suspend) | |
172 | { | |
173 | struct snd_sof_dev *sdev = dev_get_drvdata(dev); | |
61e285ca | 174 | u32 target_state = 0; |
8920153c LG |
175 | int ret; |
176 | ||
177 | /* do nothing if dsp suspend callback is not set */ | |
178 | if (!sof_ops(sdev)->suspend) | |
179 | return 0; | |
180 | ||
6ca5cecb | 181 | if (sdev->fw_state != SOF_FW_BOOT_COMPLETE) |
fb9a8119 | 182 | goto suspend; |
8920153c LG |
183 | |
184 | /* set restore_stream for all streams during system suspend */ | |
7077a07a | 185 | if (!runtime_suspend) { |
ee1e79b7 | 186 | ret = sof_set_hw_params_upon_resume(sdev->dev); |
7077a07a RS |
187 | if (ret < 0) { |
188 | dev_err(sdev->dev, | |
189 | "error: setting hw_params flag during suspend %d\n", | |
190 | ret); | |
191 | return ret; | |
192 | } | |
193 | } | |
8920153c | 194 | |
61e285ca | 195 | target_state = snd_sof_dsp_power_target(sdev); |
fb9a8119 | 196 | |
61e285ca RS |
197 | /* Skip to platform-specific suspend if DSP is entering D0 */ |
198 | if (target_state == SOF_DSP_PM_D0) | |
fb9a8119 | 199 | goto suspend; |
fb9a8119 RS |
200 | |
201 | /* release trace */ | |
202 | snd_sof_release_trace(sdev); | |
203 | ||
8920153c LG |
204 | #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) |
205 | /* cache debugfs contents during runtime suspend */ | |
206 | if (runtime_suspend) | |
207 | sof_cache_debugfs(sdev); | |
208 | #endif | |
209 | /* notify DSP of upcoming power down */ | |
7c7eba24 | 210 | ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE); |
e2eba551 KV |
211 | if (ret == -EBUSY || ret == -EAGAIN) { |
212 | /* | |
213 | * runtime PM has logic to handle -EBUSY/-EAGAIN so | |
214 | * pass these errors up | |
215 | */ | |
8920153c LG |
216 | dev_err(sdev->dev, |
217 | "error: ctx_save ipc error during suspend %d\n", | |
218 | ret); | |
219 | return ret; | |
e2eba551 KV |
220 | } else if (ret < 0) { |
221 | /* FW in unexpected state, continue to power down */ | |
222 | dev_warn(sdev->dev, | |
223 | "ctx_save ipc error %d, proceeding with suspend\n", | |
224 | ret); | |
8920153c LG |
225 | } |
226 | ||
fb9a8119 | 227 | suspend: |
6ca5cecb RS |
228 | |
229 | /* return if the DSP was not probed successfully */ | |
230 | if (sdev->fw_state == SOF_FW_BOOT_NOT_STARTED) | |
231 | return 0; | |
232 | ||
fb9a8119 | 233 | /* platform-specific suspend */ |
8920153c | 234 | if (runtime_suspend) |
1c38c922 | 235 | ret = snd_sof_dsp_runtime_suspend(sdev); |
8920153c | 236 | else |
61e285ca | 237 | ret = snd_sof_dsp_suspend(sdev, target_state); |
8920153c LG |
238 | if (ret < 0) |
239 | dev_err(sdev->dev, | |
240 | "error: failed to power down DSP during suspend %d\n", | |
241 | ret); | |
242 | ||
61e285ca RS |
243 | /* Do not reset FW state if DSP is in D0 */ |
244 | if (target_state == SOF_DSP_PM_D0) | |
fb9a8119 RS |
245 | return ret; |
246 | ||
6ca5cecb RS |
247 | /* reset FW state */ |
248 | sdev->fw_state = SOF_FW_BOOT_NOT_STARTED; | |
249 | ||
8920153c LG |
250 | return ret; |
251 | } | |
252 | ||
253 | int snd_sof_runtime_suspend(struct device *dev) | |
254 | { | |
255 | return sof_suspend(dev, true); | |
256 | } | |
257 | EXPORT_SYMBOL(snd_sof_runtime_suspend); | |
258 | ||
62fde977 KV |
259 | int snd_sof_runtime_idle(struct device *dev) |
260 | { | |
261 | struct snd_sof_dev *sdev = dev_get_drvdata(dev); | |
262 | ||
263 | return snd_sof_dsp_runtime_idle(sdev); | |
264 | } | |
265 | EXPORT_SYMBOL(snd_sof_runtime_idle); | |
266 | ||
8920153c LG |
267 | int snd_sof_runtime_resume(struct device *dev) |
268 | { | |
269 | return sof_resume(dev, true); | |
270 | } | |
271 | EXPORT_SYMBOL(snd_sof_runtime_resume); | |
272 | ||
273 | int snd_sof_resume(struct device *dev) | |
274 | { | |
275 | return sof_resume(dev, false); | |
276 | } | |
277 | EXPORT_SYMBOL(snd_sof_resume); | |
278 | ||
279 | int snd_sof_suspend(struct device *dev) | |
280 | { | |
281 | return sof_suspend(dev, false); | |
282 | } | |
283 | EXPORT_SYMBOL(snd_sof_suspend); | |
0b50b3b1 KJ |
284 | |
285 | int snd_sof_prepare(struct device *dev) | |
286 | { | |
287 | struct snd_sof_dev *sdev = dev_get_drvdata(dev); | |
288 | ||
289 | #if defined(CONFIG_ACPI) | |
043ae13b RS |
290 | if (acpi_target_system_state() == ACPI_STATE_S0) |
291 | sdev->system_suspend_target = SOF_SUSPEND_S0IX; | |
292 | else | |
293 | sdev->system_suspend_target = SOF_SUSPEND_S3; | |
0b50b3b1 KJ |
294 | #else |
295 | /* will suspend to S3 by default */ | |
043ae13b | 296 | sdev->system_suspend_target = SOF_SUSPEND_S3; |
0b50b3b1 KJ |
297 | #endif |
298 | ||
299 | return 0; | |
300 | } | |
301 | EXPORT_SYMBOL(snd_sof_prepare); | |
302 | ||
303 | void snd_sof_complete(struct device *dev) | |
304 | { | |
305 | struct snd_sof_dev *sdev = dev_get_drvdata(dev); | |
306 | ||
043ae13b | 307 | sdev->system_suspend_target = SOF_SUSPEND_NONE; |
0b50b3b1 KJ |
308 | } |
309 | EXPORT_SYMBOL(snd_sof_complete); |