pm.c (9545B)
1// SPDX-License-Identifier: (GPL-2.0-only 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/* 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 */ 21static 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_S5: 27 case SOF_SUSPEND_S4: 28 /* DSP should be in D3 if the system is suspending to S3+ */ 29 case SOF_SUSPEND_S3: 30 /* DSP should be in D3 if the system is suspending to S3 */ 31 target_dsp_state = SOF_DSP_PM_D3; 32 break; 33 case SOF_SUSPEND_S0IX: 34 /* 35 * Currently, the only criterion for retaining the DSP in D0 36 * is that there are streams that ignored the suspend trigger. 37 * Additional criteria such Soundwire clock-stop mode and 38 * device suspend latency considerations will be added later. 39 */ 40 if (snd_sof_stream_suspend_ignored(sdev)) 41 target_dsp_state = SOF_DSP_PM_D0; 42 else 43 target_dsp_state = SOF_DSP_PM_D3; 44 break; 45 default: 46 /* This case would be during runtime suspend */ 47 target_dsp_state = SOF_DSP_PM_D3; 48 break; 49 } 50 51 return target_dsp_state; 52} 53 54#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) 55static void sof_cache_debugfs(struct snd_sof_dev *sdev) 56{ 57 struct snd_sof_dfsentry *dfse; 58 59 list_for_each_entry(dfse, &sdev->dfsentry_list, list) { 60 61 /* nothing to do if debugfs buffer is not IO mem */ 62 if (dfse->type == SOF_DFSENTRY_TYPE_BUF) 63 continue; 64 65 /* cache memory that is only accessible in D0 */ 66 if (dfse->access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) 67 memcpy_fromio(dfse->cache_buf, dfse->io_mem, 68 dfse->size); 69 } 70} 71#endif 72 73static int sof_resume(struct device *dev, bool runtime_resume) 74{ 75 struct snd_sof_dev *sdev = dev_get_drvdata(dev); 76 const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm; 77 const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; 78 u32 old_state = sdev->dsp_power_state.state; 79 int ret; 80 81 /* do nothing if dsp resume callbacks are not set */ 82 if (!runtime_resume && !sof_ops(sdev)->resume) 83 return 0; 84 85 if (runtime_resume && !sof_ops(sdev)->runtime_resume) 86 return 0; 87 88 /* DSP was never successfully started, nothing to resume */ 89 if (sdev->first_boot) 90 return 0; 91 92 /* 93 * if the runtime_resume flag is set, call the runtime_resume routine 94 * or else call the system resume routine 95 */ 96 if (runtime_resume) 97 ret = snd_sof_dsp_runtime_resume(sdev); 98 else 99 ret = snd_sof_dsp_resume(sdev); 100 if (ret < 0) { 101 dev_err(sdev->dev, 102 "error: failed to power up DSP after resume\n"); 103 return ret; 104 } 105 106 /* 107 * Nothing further to be done for platforms that support the low power 108 * D0 substate. Resume trace and return when resuming from 109 * low-power D0 substate 110 */ 111 if (!runtime_resume && sof_ops(sdev)->set_power_state && 112 old_state == SOF_DSP_PM_D0) { 113 ret = sof_fw_trace_resume(sdev); 114 if (ret < 0) 115 /* non fatal */ 116 dev_warn(sdev->dev, 117 "failed to enable trace after resume %d\n", ret); 118 return 0; 119 } 120 121 sof_set_fw_state(sdev, SOF_FW_BOOT_PREPARE); 122 123 /* load the firmware */ 124 ret = snd_sof_load_firmware(sdev); 125 if (ret < 0) { 126 dev_err(sdev->dev, 127 "error: failed to load DSP firmware after resume %d\n", 128 ret); 129 sof_set_fw_state(sdev, SOF_FW_BOOT_FAILED); 130 return ret; 131 } 132 133 sof_set_fw_state(sdev, SOF_FW_BOOT_IN_PROGRESS); 134 135 /* 136 * Boot the firmware. The FW boot status will be modified 137 * in snd_sof_run_firmware() depending on the outcome. 138 */ 139 ret = snd_sof_run_firmware(sdev); 140 if (ret < 0) { 141 dev_err(sdev->dev, 142 "error: failed to boot DSP firmware after resume %d\n", 143 ret); 144 sof_set_fw_state(sdev, SOF_FW_BOOT_FAILED); 145 return ret; 146 } 147 148 /* resume DMA trace */ 149 ret = sof_fw_trace_resume(sdev); 150 if (ret < 0) { 151 /* non fatal */ 152 dev_warn(sdev->dev, 153 "warning: failed to init trace after resume %d\n", 154 ret); 155 } 156 157 /* restore pipelines */ 158 if (tplg_ops->set_up_all_pipelines) { 159 ret = tplg_ops->set_up_all_pipelines(sdev, false); 160 if (ret < 0) { 161 dev_err(sdev->dev, "Failed to restore pipeline after resume %d\n", ret); 162 return ret; 163 } 164 } 165 166 /* Notify clients not managed by pm framework about core resume */ 167 sof_resume_clients(sdev); 168 169 /* notify DSP of system resume */ 170 if (pm_ops && pm_ops->ctx_restore) { 171 ret = pm_ops->ctx_restore(sdev); 172 if (ret < 0) 173 dev_err(sdev->dev, "ctx_restore IPC error during resume: %d\n", ret); 174 } 175 176 return ret; 177} 178 179static int sof_suspend(struct device *dev, bool runtime_suspend) 180{ 181 struct snd_sof_dev *sdev = dev_get_drvdata(dev); 182 const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm; 183 const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; 184 pm_message_t pm_state; 185 u32 target_state = 0; 186 int ret; 187 188 /* do nothing if dsp suspend callback is not set */ 189 if (!runtime_suspend && !sof_ops(sdev)->suspend) 190 return 0; 191 192 if (runtime_suspend && !sof_ops(sdev)->runtime_suspend) 193 return 0; 194 195 if (sdev->fw_state != SOF_FW_BOOT_COMPLETE) 196 goto suspend; 197 198 /* prepare for streams to be resumed properly upon resume */ 199 if (!runtime_suspend) { 200 ret = snd_sof_dsp_hw_params_upon_resume(sdev); 201 if (ret < 0) { 202 dev_err(sdev->dev, 203 "error: setting hw_params flag during suspend %d\n", 204 ret); 205 return ret; 206 } 207 } 208 209 target_state = snd_sof_dsp_power_target(sdev); 210 pm_state.event = target_state; 211 212 /* Skip to platform-specific suspend if DSP is entering D0 */ 213 if (target_state == SOF_DSP_PM_D0) { 214 sof_fw_trace_suspend(sdev, pm_state); 215 /* Notify clients not managed by pm framework about core suspend */ 216 sof_suspend_clients(sdev, pm_state); 217 goto suspend; 218 } 219 220 if (tplg_ops->tear_down_all_pipelines) 221 tplg_ops->tear_down_all_pipelines(sdev, false); 222 223 /* suspend DMA trace */ 224 sof_fw_trace_suspend(sdev, pm_state); 225 226 /* Notify clients not managed by pm framework about core suspend */ 227 sof_suspend_clients(sdev, pm_state); 228 229#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) 230 /* cache debugfs contents during runtime suspend */ 231 if (runtime_suspend) 232 sof_cache_debugfs(sdev); 233#endif 234 /* notify DSP of upcoming power down */ 235 if (pm_ops && pm_ops->ctx_save) { 236 ret = pm_ops->ctx_save(sdev); 237 if (ret == -EBUSY || ret == -EAGAIN) { 238 /* 239 * runtime PM has logic to handle -EBUSY/-EAGAIN so 240 * pass these errors up 241 */ 242 dev_err(sdev->dev, "ctx_save IPC error during suspend: %d\n", ret); 243 return ret; 244 } else if (ret < 0) { 245 /* FW in unexpected state, continue to power down */ 246 dev_warn(sdev->dev, "ctx_save IPC error: %d, proceeding with suspend\n", 247 ret); 248 } 249 } 250 251suspend: 252 253 /* return if the DSP was not probed successfully */ 254 if (sdev->fw_state == SOF_FW_BOOT_NOT_STARTED) 255 return 0; 256 257 /* platform-specific suspend */ 258 if (runtime_suspend) 259 ret = snd_sof_dsp_runtime_suspend(sdev); 260 else 261 ret = snd_sof_dsp_suspend(sdev, target_state); 262 if (ret < 0) 263 dev_err(sdev->dev, 264 "error: failed to power down DSP during suspend %d\n", 265 ret); 266 267 /* Do not reset FW state if DSP is in D0 */ 268 if (target_state == SOF_DSP_PM_D0) 269 return ret; 270 271 /* reset FW state */ 272 sof_set_fw_state(sdev, SOF_FW_BOOT_NOT_STARTED); 273 sdev->enabled_cores_mask = 0; 274 275 return ret; 276} 277 278int snd_sof_dsp_power_down_notify(struct snd_sof_dev *sdev) 279{ 280 const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm; 281 282 /* Notify DSP of upcoming power down */ 283 if (sof_ops(sdev)->remove && pm_ops && pm_ops->ctx_save) 284 return pm_ops->ctx_save(sdev); 285 286 return 0; 287} 288 289int snd_sof_runtime_suspend(struct device *dev) 290{ 291 return sof_suspend(dev, true); 292} 293EXPORT_SYMBOL(snd_sof_runtime_suspend); 294 295int snd_sof_runtime_idle(struct device *dev) 296{ 297 struct snd_sof_dev *sdev = dev_get_drvdata(dev); 298 299 return snd_sof_dsp_runtime_idle(sdev); 300} 301EXPORT_SYMBOL(snd_sof_runtime_idle); 302 303int snd_sof_runtime_resume(struct device *dev) 304{ 305 return sof_resume(dev, true); 306} 307EXPORT_SYMBOL(snd_sof_runtime_resume); 308 309int snd_sof_resume(struct device *dev) 310{ 311 return sof_resume(dev, false); 312} 313EXPORT_SYMBOL(snd_sof_resume); 314 315int snd_sof_suspend(struct device *dev) 316{ 317 return sof_suspend(dev, false); 318} 319EXPORT_SYMBOL(snd_sof_suspend); 320 321int snd_sof_prepare(struct device *dev) 322{ 323 struct snd_sof_dev *sdev = dev_get_drvdata(dev); 324 const struct sof_dev_desc *desc = sdev->pdata->desc; 325 326 /* will suspend to S3 by default */ 327 sdev->system_suspend_target = SOF_SUSPEND_S3; 328 329 /* 330 * if the firmware is crashed or boot failed then we try to aim for S3 331 * to reboot the firmware 332 */ 333 if (sdev->fw_state == SOF_FW_CRASHED || 334 sdev->fw_state == SOF_FW_BOOT_FAILED) 335 return 0; 336 337 if (!desc->use_acpi_target_states) 338 return 0; 339 340#if defined(CONFIG_ACPI) 341 switch (acpi_target_system_state()) { 342 case ACPI_STATE_S0: 343 sdev->system_suspend_target = SOF_SUSPEND_S0IX; 344 break; 345 case ACPI_STATE_S1: 346 case ACPI_STATE_S2: 347 case ACPI_STATE_S3: 348 sdev->system_suspend_target = SOF_SUSPEND_S3; 349 break; 350 case ACPI_STATE_S4: 351 sdev->system_suspend_target = SOF_SUSPEND_S4; 352 break; 353 case ACPI_STATE_S5: 354 sdev->system_suspend_target = SOF_SUSPEND_S5; 355 break; 356 default: 357 break; 358 } 359#endif 360 361 return 0; 362} 363EXPORT_SYMBOL(snd_sof_prepare); 364 365void snd_sof_complete(struct device *dev) 366{ 367 struct snd_sof_dev *sdev = dev_get_drvdata(dev); 368 369 sdev->system_suspend_target = SOF_SUSPEND_NONE; 370} 371EXPORT_SYMBOL(snd_sof_complete);