linux/sound/soc/sof/pm.c
<<
>>
Prefs
   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
  14static int sof_restore_kcontrols(struct snd_sof_dev *sdev)
  15{
  16        struct snd_sof_control *scontrol;
  17        int ipc_cmd, ctrl_type;
  18        int ret = 0;
  19
  20        /* restore kcontrol values */
  21        list_for_each_entry(scontrol, &sdev->kcontrol_list, list) {
  22                /* reset readback offset for scontrol after resuming */
  23                scontrol->readback_offset = 0;
  24
  25                /* notify DSP of kcontrol values */
  26                switch (scontrol->cmd) {
  27                case SOF_CTRL_CMD_VOLUME:
  28                case SOF_CTRL_CMD_ENUM:
  29                case SOF_CTRL_CMD_SWITCH:
  30                        ipc_cmd = SOF_IPC_COMP_SET_VALUE;
  31                        ctrl_type = SOF_CTRL_TYPE_VALUE_CHAN_SET;
  32                        ret = snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol,
  33                                                            ipc_cmd, ctrl_type,
  34                                                            scontrol->cmd,
  35                                                            true);
  36                        break;
  37                case SOF_CTRL_CMD_BINARY:
  38                        ipc_cmd = SOF_IPC_COMP_SET_DATA;
  39                        ctrl_type = SOF_CTRL_TYPE_DATA_SET;
  40                        ret = snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol,
  41                                                            ipc_cmd, ctrl_type,
  42                                                            scontrol->cmd,
  43                                                            true);
  44                        break;
  45
  46                default:
  47                        break;
  48                }
  49
  50                if (ret < 0) {
  51                        dev_err(sdev->dev,
  52                                "error: failed kcontrol value set for widget: %d\n",
  53                                scontrol->comp_id);
  54
  55                        return ret;
  56                }
  57        }
  58
  59        return 0;
  60}
  61
  62static int sof_restore_pipelines(struct snd_sof_dev *sdev)
  63{
  64        struct snd_sof_widget *swidget;
  65        struct snd_sof_route *sroute;
  66        struct sof_ipc_pipe_new *pipeline;
  67        struct snd_sof_dai *dai;
  68        struct sof_ipc_comp_dai *comp_dai;
  69        struct sof_ipc_cmd_hdr *hdr;
  70        int ret;
  71
  72        /* restore pipeline components */
  73        list_for_each_entry_reverse(swidget, &sdev->widget_list, list) {
  74                struct sof_ipc_comp_reply r;
  75
  76                /* skip if there is no private data */
  77                if (!swidget->private)
  78                        continue;
  79
  80                switch (swidget->id) {
  81                case snd_soc_dapm_dai_in:
  82                case snd_soc_dapm_dai_out:
  83                        dai = swidget->private;
  84                        comp_dai = &dai->comp_dai;
  85                        ret = sof_ipc_tx_message(sdev->ipc,
  86                                                 comp_dai->comp.hdr.cmd,
  87                                                 comp_dai, sizeof(*comp_dai),
  88                                                 &r, sizeof(r));
  89                        break;
  90                case snd_soc_dapm_scheduler:
  91
  92                        /*
  93                         * During suspend, all DSP cores are powered off.
  94                         * Therefore upon resume, create the pipeline comp
  95                         * and power up the core that the pipeline is
  96                         * scheduled on.
  97                         */
  98                        pipeline = swidget->private;
  99                        ret = sof_load_pipeline_ipc(sdev, pipeline, &r);
 100                        break;
 101                default:
 102                        hdr = swidget->private;
 103                        ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd,
 104                                                 swidget->private, hdr->size,
 105                                                 &r, sizeof(r));
 106                        break;
 107                }
 108                if (ret < 0) {
 109                        dev_err(sdev->dev,
 110                                "error: failed to load widget type %d with ID: %d\n",
 111                                swidget->widget->id, swidget->comp_id);
 112
 113                        return ret;
 114                }
 115        }
 116
 117        /* restore pipeline connections */
 118        list_for_each_entry_reverse(sroute, &sdev->route_list, list) {
 119                struct sof_ipc_pipe_comp_connect *connect;
 120                struct sof_ipc_reply reply;
 121
 122                /* skip if there's no private data */
 123                if (!sroute->private)
 124                        continue;
 125
 126                connect = sroute->private;
 127
 128                /* send ipc */
 129                ret = sof_ipc_tx_message(sdev->ipc,
 130                                         connect->hdr.cmd,
 131                                         connect, sizeof(*connect),
 132                                         &reply, sizeof(reply));
 133                if (ret < 0) {
 134                        dev_err(sdev->dev,
 135                                "error: failed to load route sink %s control %s source %s\n",
 136                                sroute->route->sink,
 137                                sroute->route->control ? sroute->route->control
 138                                        : "none",
 139                                sroute->route->source);
 140
 141                        return ret;
 142                }
 143        }
 144
 145        /* restore dai links */
 146        list_for_each_entry_reverse(dai, &sdev->dai_list, list) {
 147                struct sof_ipc_reply reply;
 148                struct sof_ipc_dai_config *config = dai->dai_config;
 149
 150                if (!config) {
 151                        dev_err(sdev->dev, "error: no config for DAI %s\n",
 152                                dai->name);
 153                        continue;
 154                }
 155
 156                /*
 157                 * The link DMA channel would be invalidated for running
 158                 * streams but not for streams that were in the PAUSED
 159                 * state during suspend. So invalidate it here before setting
 160                 * the dai config in the DSP.
 161                 */
 162                if (config->type == SOF_DAI_INTEL_HDA)
 163                        config->hda.link_dma_ch = DMA_CHAN_INVALID;
 164
 165                ret = sof_ipc_tx_message(sdev->ipc,
 166                                         config->hdr.cmd, config,
 167                                         config->hdr.size,
 168                                         &reply, sizeof(reply));
 169
 170                if (ret < 0) {
 171                        dev_err(sdev->dev,
 172                                "error: failed to set dai config for %s\n",
 173                                dai->name);
 174
 175                        return ret;
 176                }
 177        }
 178
 179        /* complete pipeline */
 180        list_for_each_entry(swidget, &sdev->widget_list, list) {
 181                switch (swidget->id) {
 182                case snd_soc_dapm_scheduler:
 183                        swidget->complete =
 184                                snd_sof_complete_pipeline(sdev, swidget);
 185                        break;
 186                default:
 187                        break;
 188                }
 189        }
 190
 191        /* restore pipeline kcontrols */
 192        ret = sof_restore_kcontrols(sdev);
 193        if (ret < 0)
 194                dev_err(sdev->dev,
 195                        "error: restoring kcontrols after resume\n");
 196
 197        return ret;
 198}
 199
 200static int sof_send_pm_ctx_ipc(struct snd_sof_dev *sdev, int cmd)
 201{
 202        struct sof_ipc_pm_ctx pm_ctx;
 203        struct sof_ipc_reply reply;
 204
 205        memset(&pm_ctx, 0, sizeof(pm_ctx));
 206
 207        /* configure ctx save ipc message */
 208        pm_ctx.hdr.size = sizeof(pm_ctx);
 209        pm_ctx.hdr.cmd = SOF_IPC_GLB_PM_MSG | cmd;
 210
 211        /* send ctx save ipc to dsp */
 212        return sof_ipc_tx_message(sdev->ipc, pm_ctx.hdr.cmd, &pm_ctx,
 213                                 sizeof(pm_ctx), &reply, sizeof(reply));
 214}
 215
 216static int sof_set_hw_params_upon_resume(struct snd_sof_dev *sdev)
 217{
 218        struct snd_pcm_substream *substream;
 219        struct snd_sof_pcm *spcm;
 220        snd_pcm_state_t state;
 221        int dir;
 222
 223        /*
 224         * SOF requires hw_params to be set-up internally upon resume.
 225         * So, set the flag to indicate this for those streams that
 226         * have been suspended.
 227         */
 228        list_for_each_entry(spcm, &sdev->pcm_list, list) {
 229                for (dir = 0; dir <= SNDRV_PCM_STREAM_CAPTURE; dir++) {
 230                        substream = spcm->stream[dir].substream;
 231                        if (!substream || !substream->runtime)
 232                                continue;
 233
 234                        state = substream->runtime->status->state;
 235                        if (state == SNDRV_PCM_STATE_SUSPENDED)
 236                                spcm->prepared[dir] = false;
 237                }
 238        }
 239
 240        /* set internal flag for BE */
 241        return snd_sof_dsp_hw_params_upon_resume(sdev);
 242}
 243
 244#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
 245static void sof_cache_debugfs(struct snd_sof_dev *sdev)
 246{
 247        struct snd_sof_dfsentry *dfse;
 248
 249        list_for_each_entry(dfse, &sdev->dfsentry_list, list) {
 250
 251                /* nothing to do if debugfs buffer is not IO mem */
 252                if (dfse->type == SOF_DFSENTRY_TYPE_BUF)
 253                        continue;
 254
 255                /* cache memory that is only accessible in D0 */
 256                if (dfse->access_type == SOF_DEBUGFS_ACCESS_D0_ONLY)
 257                        memcpy_fromio(dfse->cache_buf, dfse->io_mem,
 258                                      dfse->size);
 259        }
 260}
 261#endif
 262
 263static int sof_resume(struct device *dev, bool runtime_resume)
 264{
 265        struct snd_sof_dev *sdev = dev_get_drvdata(dev);
 266        int ret;
 267
 268        /* do nothing if dsp resume callbacks are not set */
 269        if (!sof_ops(sdev)->resume || !sof_ops(sdev)->runtime_resume)
 270                return 0;
 271
 272        /*
 273         * if the runtime_resume flag is set, call the runtime_resume routine
 274         * or else call the system resume routine
 275         */
 276        if (runtime_resume)
 277                ret = snd_sof_dsp_runtime_resume(sdev);
 278        else
 279                ret = snd_sof_dsp_resume(sdev);
 280        if (ret < 0) {
 281                dev_err(sdev->dev,
 282                        "error: failed to power up DSP after resume\n");
 283                return ret;
 284        }
 285
 286        /* load the firmware */
 287        ret = snd_sof_load_firmware(sdev);
 288        if (ret < 0) {
 289                dev_err(sdev->dev,
 290                        "error: failed to load DSP firmware after resume %d\n",
 291                        ret);
 292                return ret;
 293        }
 294
 295        /* boot the firmware */
 296        ret = snd_sof_run_firmware(sdev);
 297        if (ret < 0) {
 298                dev_err(sdev->dev,
 299                        "error: failed to boot DSP firmware after resume %d\n",
 300                        ret);
 301                return ret;
 302        }
 303
 304        /* resume DMA trace, only need send ipc */
 305        ret = snd_sof_init_trace_ipc(sdev);
 306        if (ret < 0) {
 307                /* non fatal */
 308                dev_warn(sdev->dev,
 309                         "warning: failed to init trace after resume %d\n",
 310                         ret);
 311        }
 312
 313        /* restore pipelines */
 314        ret = sof_restore_pipelines(sdev);
 315        if (ret < 0) {
 316                dev_err(sdev->dev,
 317                        "error: failed to restore pipeline after resume %d\n",
 318                        ret);
 319                return ret;
 320        }
 321
 322        /* notify DSP of system resume */
 323        ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_RESTORE);
 324        if (ret < 0)
 325                dev_err(sdev->dev,
 326                        "error: ctx_restore ipc error during resume %d\n",
 327                        ret);
 328
 329        /* initialize default D0 sub-state */
 330        sdev->d0_substate = SOF_DSP_D0I0;
 331
 332        return ret;
 333}
 334
 335static int sof_suspend(struct device *dev, bool runtime_suspend)
 336{
 337        struct snd_sof_dev *sdev = dev_get_drvdata(dev);
 338        int ret;
 339
 340        /* do nothing if dsp suspend callback is not set */
 341        if (!sof_ops(sdev)->suspend)
 342                return 0;
 343
 344        /* release trace */
 345        snd_sof_release_trace(sdev);
 346
 347        /* set restore_stream for all streams during system suspend */
 348        if (!runtime_suspend) {
 349                ret = sof_set_hw_params_upon_resume(sdev);
 350                if (ret < 0) {
 351                        dev_err(sdev->dev,
 352                                "error: setting hw_params flag during suspend %d\n",
 353                                ret);
 354                        return ret;
 355                }
 356        }
 357
 358#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
 359        /* cache debugfs contents during runtime suspend */
 360        if (runtime_suspend)
 361                sof_cache_debugfs(sdev);
 362#endif
 363        /* notify DSP of upcoming power down */
 364        ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE);
 365        if (ret == -EBUSY || ret == -EAGAIN) {
 366                /*
 367                 * runtime PM has logic to handle -EBUSY/-EAGAIN so
 368                 * pass these errors up
 369                 */
 370                dev_err(sdev->dev,
 371                        "error: ctx_save ipc error during suspend %d\n",
 372                        ret);
 373                return ret;
 374        } else if (ret < 0) {
 375                /* FW in unexpected state, continue to power down */
 376                dev_warn(sdev->dev,
 377                         "ctx_save ipc error %d, proceeding with suspend\n",
 378                         ret);
 379        }
 380
 381        /* power down all DSP cores */
 382        if (runtime_suspend)
 383                ret = snd_sof_dsp_runtime_suspend(sdev);
 384        else
 385                ret = snd_sof_dsp_suspend(sdev);
 386        if (ret < 0)
 387                dev_err(sdev->dev,
 388                        "error: failed to power down DSP during suspend %d\n",
 389                        ret);
 390
 391        return ret;
 392}
 393
 394int snd_sof_runtime_suspend(struct device *dev)
 395{
 396        return sof_suspend(dev, true);
 397}
 398EXPORT_SYMBOL(snd_sof_runtime_suspend);
 399
 400int snd_sof_runtime_idle(struct device *dev)
 401{
 402        struct snd_sof_dev *sdev = dev_get_drvdata(dev);
 403
 404        return snd_sof_dsp_runtime_idle(sdev);
 405}
 406EXPORT_SYMBOL(snd_sof_runtime_idle);
 407
 408int snd_sof_runtime_resume(struct device *dev)
 409{
 410        return sof_resume(dev, true);
 411}
 412EXPORT_SYMBOL(snd_sof_runtime_resume);
 413
 414int snd_sof_set_d0_substate(struct snd_sof_dev *sdev,
 415                            enum sof_d0_substate d0_substate)
 416{
 417        int ret;
 418
 419        if (sdev->d0_substate == d0_substate)
 420                return 0;
 421
 422        /* do platform specific set_state */
 423        ret = snd_sof_dsp_set_power_state(sdev, d0_substate);
 424        if (ret < 0)
 425                return ret;
 426
 427        /* update dsp D0 sub-state */
 428        sdev->d0_substate = d0_substate;
 429
 430        return 0;
 431}
 432EXPORT_SYMBOL(snd_sof_set_d0_substate);
 433
 434/*
 435 * Audio DSP states may transform as below:-
 436 *
 437 *                                         D0I3 compatible stream
 438 *     Runtime    +---------------------+   opened only, timeout
 439 *     suspend    |                     +--------------------+
 440 *   +------------+       D0(active)    |                    |
 441 *   |            |                     <---------------+    |
 442 *   |   +-------->                     |               |    |
 443 *   |   |Runtime +--^--+---------^--+--+ The last      |    |
 444 *   |   |resume     |  |         |  |    opened D0I3   |    |
 445 *   |   |           |  |         |  |    compatible    |    |
 446 *   |   |     resume|  |         |  |    stream closed |    |
 447 *   |   |      from |  | D3      |  |                  |    |
 448 *   |   |       D3  |  |suspend  |  | d0i3             |    |
 449 *   |   |           |  |         |  |suspend           |    |
 450 *   |   |           |  |         |  |                  |    |
 451 *   |   |           |  |         |  |                  |    |
 452 * +-v---+-----------+--v-------+ |  |           +------+----v----+
 453 * |                            | |  +----------->                |
 454 * |       D3 (suspended)       | |              |      D0I3      +-----+
 455 * |                            | +--------------+                |     |
 456 * |                            |  resume from   |                |     |
 457 * +-------------------^--------+  d0i3 suspend  +----------------+     |
 458 *                     |                                                |
 459 *                     |                       D3 suspend               |
 460 *                     +------------------------------------------------+
 461 *
 462 * d0i3_suspend = s0_suspend && D0I3 stream opened,
 463 * D3 suspend = !d0i3_suspend,
 464 */
 465
 466int snd_sof_resume(struct device *dev)
 467{
 468        struct snd_sof_dev *sdev = dev_get_drvdata(dev);
 469        int ret;
 470
 471        if (snd_sof_dsp_d0i3_on_suspend(sdev)) {
 472                /* resume from D0I3 */
 473                dev_dbg(sdev->dev, "DSP will exit from D0i3...\n");
 474                ret = snd_sof_set_d0_substate(sdev, SOF_DSP_D0I0);
 475                if (ret == -ENOTSUPP) {
 476                        /* fallback to resume from D3 */
 477                        dev_dbg(sdev->dev, "D0i3 not supported, fall back to resume from D3...\n");
 478                        goto d3_resume;
 479                } else if (ret < 0) {
 480                        dev_err(sdev->dev, "error: failed to exit from D0I3 %d\n",
 481                                ret);
 482                        return ret;
 483                }
 484
 485                /* platform-specific resume from D0i3 */
 486                return snd_sof_dsp_resume(sdev);
 487        }
 488
 489d3_resume:
 490        /* resume from D3 */
 491        return sof_resume(dev, false);
 492}
 493EXPORT_SYMBOL(snd_sof_resume);
 494
 495int snd_sof_suspend(struct device *dev)
 496{
 497        struct snd_sof_dev *sdev = dev_get_drvdata(dev);
 498        int ret;
 499
 500        if (snd_sof_dsp_d0i3_on_suspend(sdev)) {
 501                /* suspend to D0i3 */
 502                dev_dbg(sdev->dev, "DSP is trying to enter D0i3...\n");
 503                ret = snd_sof_set_d0_substate(sdev, SOF_DSP_D0I3);
 504                if (ret == -ENOTSUPP) {
 505                        /* fallback to D3 suspend */
 506                        dev_dbg(sdev->dev, "D0i3 not supported, fall back to D3...\n");
 507                        goto d3_suspend;
 508                } else if (ret < 0) {
 509                        dev_err(sdev->dev, "error: failed to enter D0I3, %d\n",
 510                                ret);
 511                        return ret;
 512                }
 513
 514                /* platform-specific suspend to D0i3 */
 515                return snd_sof_dsp_suspend(sdev);
 516        }
 517
 518d3_suspend:
 519        /* suspend to D3 */
 520        return sof_suspend(dev, false);
 521}
 522EXPORT_SYMBOL(snd_sof_suspend);
 523
 524int snd_sof_prepare(struct device *dev)
 525{
 526        struct snd_sof_dev *sdev = dev_get_drvdata(dev);
 527
 528#if defined(CONFIG_ACPI)
 529        sdev->s0_suspend = acpi_target_system_state() == ACPI_STATE_S0;
 530#else
 531        /* will suspend to S3 by default */
 532        sdev->s0_suspend = false;
 533#endif
 534
 535        return 0;
 536}
 537EXPORT_SYMBOL(snd_sof_prepare);
 538
 539void snd_sof_complete(struct device *dev)
 540{
 541        struct snd_sof_dev *sdev = dev_get_drvdata(dev);
 542
 543        sdev->s0_suspend = false;
 544}
 545EXPORT_SYMBOL(snd_sof_complete);
 546