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_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_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        return ret;
 330}
 331
 332static int sof_suspend(struct device *dev, bool runtime_suspend)
 333{
 334        struct snd_sof_dev *sdev = dev_get_drvdata(dev);
 335        int ret;
 336
 337        /* do nothing if dsp suspend callback is not set */
 338        if (!sof_ops(sdev)->suspend)
 339                return 0;
 340
 341        /* release trace */
 342        snd_sof_release_trace(sdev);
 343
 344        /* set restore_stream for all streams during system suspend */
 345        if (!runtime_suspend) {
 346                ret = sof_set_hw_params_upon_resume(sdev);
 347                if (ret < 0) {
 348                        dev_err(sdev->dev,
 349                                "error: setting hw_params flag during suspend %d\n",
 350                                ret);
 351                        return ret;
 352                }
 353        }
 354
 355#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
 356        /* cache debugfs contents during runtime suspend */
 357        if (runtime_suspend)
 358                sof_cache_debugfs(sdev);
 359#endif
 360        /* notify DSP of upcoming power down */
 361        ret = sof_send_pm_ipc(sdev, SOF_IPC_PM_CTX_SAVE);
 362        if (ret == -EBUSY || ret == -EAGAIN) {
 363                /*
 364                 * runtime PM has logic to handle -EBUSY/-EAGAIN so
 365                 * pass these errors up
 366                 */
 367                dev_err(sdev->dev,
 368                        "error: ctx_save ipc error during suspend %d\n",
 369                        ret);
 370                return ret;
 371        } else if (ret < 0) {
 372                /* FW in unexpected state, continue to power down */
 373                dev_warn(sdev->dev,
 374                         "ctx_save ipc error %d, proceeding with suspend\n",
 375                         ret);
 376        }
 377
 378        /* power down all DSP cores */
 379        if (runtime_suspend)
 380                ret = snd_sof_dsp_runtime_suspend(sdev);
 381        else
 382                ret = snd_sof_dsp_suspend(sdev);
 383        if (ret < 0)
 384                dev_err(sdev->dev,
 385                        "error: failed to power down DSP during suspend %d\n",
 386                        ret);
 387
 388        return ret;
 389}
 390
 391int snd_sof_runtime_suspend(struct device *dev)
 392{
 393        return sof_suspend(dev, true);
 394}
 395EXPORT_SYMBOL(snd_sof_runtime_suspend);
 396
 397int snd_sof_runtime_idle(struct device *dev)
 398{
 399        struct snd_sof_dev *sdev = dev_get_drvdata(dev);
 400
 401        return snd_sof_dsp_runtime_idle(sdev);
 402}
 403EXPORT_SYMBOL(snd_sof_runtime_idle);
 404
 405int snd_sof_runtime_resume(struct device *dev)
 406{
 407        return sof_resume(dev, true);
 408}
 409EXPORT_SYMBOL(snd_sof_runtime_resume);
 410
 411int snd_sof_resume(struct device *dev)
 412{
 413        return sof_resume(dev, false);
 414}
 415EXPORT_SYMBOL(snd_sof_resume);
 416
 417int snd_sof_suspend(struct device *dev)
 418{
 419        return sof_suspend(dev, false);
 420}
 421EXPORT_SYMBOL(snd_sof_suspend);
 422