linux/sound/soc/sof/sof-audio.c
<<
>>
Prefs
   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) 2019 Intel Corporation. All rights reserved.
   7//
   8// Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
   9//
  10
  11#include "sof-audio.h"
  12#include "ops.h"
  13
  14/*
  15 * helper to determine if there are only D0i3 compatible
  16 * streams active
  17 */
  18bool snd_sof_dsp_only_d0i3_compatible_stream_active(struct snd_sof_dev *sdev)
  19{
  20        struct snd_pcm_substream *substream;
  21        struct snd_sof_pcm *spcm;
  22        bool d0i3_compatible_active = false;
  23        int dir;
  24
  25        list_for_each_entry(spcm, &sdev->pcm_list, list) {
  26                for_each_pcm_streams(dir) {
  27                        substream = spcm->stream[dir].substream;
  28                        if (!substream || !substream->runtime)
  29                                continue;
  30
  31                        /*
  32                         * substream->runtime being not NULL indicates
  33                         * that the stream is open. No need to check the
  34                         * stream state.
  35                         */
  36                        if (!spcm->stream[dir].d0i3_compatible)
  37                                return false;
  38
  39                        d0i3_compatible_active = true;
  40                }
  41        }
  42
  43        return d0i3_compatible_active;
  44}
  45EXPORT_SYMBOL(snd_sof_dsp_only_d0i3_compatible_stream_active);
  46
  47bool snd_sof_stream_suspend_ignored(struct snd_sof_dev *sdev)
  48{
  49        struct snd_sof_pcm *spcm;
  50
  51        list_for_each_entry(spcm, &sdev->pcm_list, list) {
  52                if (spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].suspend_ignored ||
  53                    spcm->stream[SNDRV_PCM_STREAM_CAPTURE].suspend_ignored)
  54                        return true;
  55        }
  56
  57        return false;
  58}
  59
  60int sof_set_hw_params_upon_resume(struct device *dev)
  61{
  62        struct snd_sof_dev *sdev = dev_get_drvdata(dev);
  63        struct snd_pcm_substream *substream;
  64        struct snd_sof_pcm *spcm;
  65        snd_pcm_state_t state;
  66        int dir;
  67
  68        /*
  69         * SOF requires hw_params to be set-up internally upon resume.
  70         * So, set the flag to indicate this for those streams that
  71         * have been suspended.
  72         */
  73        list_for_each_entry(spcm, &sdev->pcm_list, list) {
  74                for_each_pcm_streams(dir) {
  75                        /*
  76                         * do not reset hw_params upon resume for streams that
  77                         * were kept running during suspend
  78                         */
  79                        if (spcm->stream[dir].suspend_ignored)
  80                                continue;
  81
  82                        substream = spcm->stream[dir].substream;
  83                        if (!substream || !substream->runtime)
  84                                continue;
  85
  86                        state = substream->runtime->status->state;
  87                        if (state == SNDRV_PCM_STATE_SUSPENDED)
  88                                spcm->prepared[dir] = false;
  89                }
  90        }
  91
  92        /* set internal flag for BE */
  93        return snd_sof_dsp_hw_params_upon_resume(sdev);
  94}
  95
  96static int sof_restore_kcontrols(struct device *dev)
  97{
  98        struct snd_sof_dev *sdev = dev_get_drvdata(dev);
  99        struct snd_sof_control *scontrol;
 100        int ipc_cmd, ctrl_type;
 101        int ret = 0;
 102
 103        /* restore kcontrol values */
 104        list_for_each_entry(scontrol, &sdev->kcontrol_list, list) {
 105                /* reset readback offset for scontrol after resuming */
 106                scontrol->readback_offset = 0;
 107
 108                /* notify DSP of kcontrol values */
 109                switch (scontrol->cmd) {
 110                case SOF_CTRL_CMD_VOLUME:
 111                case SOF_CTRL_CMD_ENUM:
 112                case SOF_CTRL_CMD_SWITCH:
 113                        ipc_cmd = SOF_IPC_COMP_SET_VALUE;
 114                        ctrl_type = SOF_CTRL_TYPE_VALUE_CHAN_SET;
 115                        ret = snd_sof_ipc_set_get_comp_data(scontrol,
 116                                                            ipc_cmd, ctrl_type,
 117                                                            scontrol->cmd,
 118                                                            true);
 119                        break;
 120                case SOF_CTRL_CMD_BINARY:
 121                        ipc_cmd = SOF_IPC_COMP_SET_DATA;
 122                        ctrl_type = SOF_CTRL_TYPE_DATA_SET;
 123                        ret = snd_sof_ipc_set_get_comp_data(scontrol,
 124                                                            ipc_cmd, ctrl_type,
 125                                                            scontrol->cmd,
 126                                                            true);
 127                        break;
 128
 129                default:
 130                        break;
 131                }
 132
 133                if (ret < 0) {
 134                        dev_err(dev,
 135                                "error: failed kcontrol value set for widget: %d\n",
 136                                scontrol->comp_id);
 137
 138                        return ret;
 139                }
 140        }
 141
 142        return 0;
 143}
 144
 145const struct sof_ipc_pipe_new *snd_sof_pipeline_find(struct snd_sof_dev *sdev,
 146                                                     int pipeline_id)
 147{
 148        const struct snd_sof_widget *swidget;
 149
 150        list_for_each_entry(swidget, &sdev->widget_list, list)
 151                if (swidget->id == snd_soc_dapm_scheduler) {
 152                        const struct sof_ipc_pipe_new *pipeline =
 153                                swidget->private;
 154                        if (pipeline->pipeline_id == pipeline_id)
 155                                return pipeline;
 156                }
 157
 158        return NULL;
 159}
 160
 161int sof_restore_pipelines(struct device *dev)
 162{
 163        struct snd_sof_dev *sdev = dev_get_drvdata(dev);
 164        struct snd_sof_widget *swidget;
 165        struct snd_sof_route *sroute;
 166        struct sof_ipc_pipe_new *pipeline;
 167        struct snd_sof_dai *dai;
 168        struct sof_ipc_cmd_hdr *hdr;
 169        struct sof_ipc_comp *comp;
 170        size_t ipc_size;
 171        int ret;
 172
 173        /* restore pipeline components */
 174        list_for_each_entry_reverse(swidget, &sdev->widget_list, list) {
 175                struct sof_ipc_comp_reply r;
 176
 177                /* skip if there is no private data */
 178                if (!swidget->private)
 179                        continue;
 180
 181                ret = sof_pipeline_core_enable(sdev, swidget);
 182                if (ret < 0) {
 183                        dev_err(dev,
 184                                "error: failed to enable target core: %d\n",
 185                                ret);
 186
 187                        return ret;
 188                }
 189
 190                switch (swidget->id) {
 191                case snd_soc_dapm_dai_in:
 192                case snd_soc_dapm_dai_out:
 193                        ipc_size = sizeof(struct sof_ipc_comp_dai) +
 194                                   sizeof(struct sof_ipc_comp_ext);
 195                        comp = kzalloc(ipc_size, GFP_KERNEL);
 196                        if (!comp)
 197                                return -ENOMEM;
 198
 199                        dai = swidget->private;
 200                        memcpy(comp, &dai->comp_dai,
 201                               sizeof(struct sof_ipc_comp_dai));
 202
 203                        /* append extended data to the end of the component */
 204                        memcpy((u8 *)comp + sizeof(struct sof_ipc_comp_dai),
 205                               &swidget->comp_ext, sizeof(swidget->comp_ext));
 206
 207                        ret = sof_ipc_tx_message(sdev->ipc, comp->hdr.cmd,
 208                                                 comp, ipc_size,
 209                                                 &r, sizeof(r));
 210                        kfree(comp);
 211                        break;
 212                case snd_soc_dapm_scheduler:
 213
 214                        /*
 215                         * During suspend, all DSP cores are powered off.
 216                         * Therefore upon resume, create the pipeline comp
 217                         * and power up the core that the pipeline is
 218                         * scheduled on.
 219                         */
 220                        pipeline = swidget->private;
 221                        ret = sof_load_pipeline_ipc(dev, pipeline, &r);
 222                        break;
 223                default:
 224                        hdr = swidget->private;
 225                        ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd,
 226                                                 swidget->private, hdr->size,
 227                                                 &r, sizeof(r));
 228                        break;
 229                }
 230                if (ret < 0) {
 231                        dev_err(dev,
 232                                "error: failed to load widget type %d with ID: %d\n",
 233                                swidget->widget->id, swidget->comp_id);
 234
 235                        return ret;
 236                }
 237        }
 238
 239        /* restore pipeline connections */
 240        list_for_each_entry_reverse(sroute, &sdev->route_list, list) {
 241                struct sof_ipc_pipe_comp_connect *connect;
 242                struct sof_ipc_reply reply;
 243
 244                /* skip if there's no private data */
 245                if (!sroute->private)
 246                        continue;
 247
 248                connect = sroute->private;
 249
 250                /* send ipc */
 251                ret = sof_ipc_tx_message(sdev->ipc,
 252                                         connect->hdr.cmd,
 253                                         connect, sizeof(*connect),
 254                                         &reply, sizeof(reply));
 255                if (ret < 0) {
 256                        dev_err(dev,
 257                                "error: failed to load route sink %s control %s source %s\n",
 258                                sroute->route->sink,
 259                                sroute->route->control ? sroute->route->control
 260                                        : "none",
 261                                sroute->route->source);
 262
 263                        return ret;
 264                }
 265        }
 266
 267        /* restore dai links */
 268        list_for_each_entry_reverse(dai, &sdev->dai_list, list) {
 269                struct sof_ipc_reply reply;
 270                struct sof_ipc_dai_config *config = &dai->dai_config[dai->current_config];
 271
 272                if (!config) {
 273                        dev_err(dev, "error: no config for DAI %s\n",
 274                                dai->name);
 275                        continue;
 276                }
 277
 278                /*
 279                 * The link DMA channel would be invalidated for running
 280                 * streams but not for streams that were in the PAUSED
 281                 * state during suspend. So invalidate it here before setting
 282                 * the dai config in the DSP.
 283                 */
 284                if (config->type == SOF_DAI_INTEL_HDA)
 285                        config->hda.link_dma_ch = DMA_CHAN_INVALID;
 286
 287                ret = sof_ipc_tx_message(sdev->ipc,
 288                                         config->hdr.cmd, config,
 289                                         config->hdr.size,
 290                                         &reply, sizeof(reply));
 291
 292                if (ret < 0) {
 293                        dev_err(dev,
 294                                "error: failed to set dai config for %s\n",
 295                                dai->name);
 296
 297                        return ret;
 298                }
 299        }
 300
 301        /* complete pipeline */
 302        list_for_each_entry(swidget, &sdev->widget_list, list) {
 303                switch (swidget->id) {
 304                case snd_soc_dapm_scheduler:
 305                        swidget->complete =
 306                                snd_sof_complete_pipeline(dev, swidget);
 307                        break;
 308                default:
 309                        break;
 310                }
 311        }
 312
 313        /* restore pipeline kcontrols */
 314        ret = sof_restore_kcontrols(dev);
 315        if (ret < 0)
 316                dev_err(dev,
 317                        "error: restoring kcontrols after resume\n");
 318
 319        return ret;
 320}
 321
 322/*
 323 * Generic object lookup APIs.
 324 */
 325
 326struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_soc_component *scomp,
 327                                           const char *name)
 328{
 329        struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
 330        struct snd_sof_pcm *spcm;
 331
 332        list_for_each_entry(spcm, &sdev->pcm_list, list) {
 333                /* match with PCM dai name */
 334                if (strcmp(spcm->pcm.dai_name, name) == 0)
 335                        return spcm;
 336
 337                /* match with playback caps name if set */
 338                if (*spcm->pcm.caps[0].name &&
 339                    !strcmp(spcm->pcm.caps[0].name, name))
 340                        return spcm;
 341
 342                /* match with capture caps name if set */
 343                if (*spcm->pcm.caps[1].name &&
 344                    !strcmp(spcm->pcm.caps[1].name, name))
 345                        return spcm;
 346        }
 347
 348        return NULL;
 349}
 350
 351struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_soc_component *scomp,
 352                                           unsigned int comp_id,
 353                                           int *direction)
 354{
 355        struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
 356        struct snd_sof_pcm *spcm;
 357        int dir;
 358
 359        list_for_each_entry(spcm, &sdev->pcm_list, list) {
 360                for_each_pcm_streams(dir) {
 361                        if (spcm->stream[dir].comp_id == comp_id) {
 362                                *direction = dir;
 363                                return spcm;
 364                        }
 365                }
 366        }
 367
 368        return NULL;
 369}
 370
 371struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_soc_component *scomp,
 372                                             unsigned int pcm_id)
 373{
 374        struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
 375        struct snd_sof_pcm *spcm;
 376
 377        list_for_each_entry(spcm, &sdev->pcm_list, list) {
 378                if (le32_to_cpu(spcm->pcm.pcm_id) == pcm_id)
 379                        return spcm;
 380        }
 381
 382        return NULL;
 383}
 384
 385struct snd_sof_widget *snd_sof_find_swidget(struct snd_soc_component *scomp,
 386                                            const char *name)
 387{
 388        struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
 389        struct snd_sof_widget *swidget;
 390
 391        list_for_each_entry(swidget, &sdev->widget_list, list) {
 392                if (strcmp(name, swidget->widget->name) == 0)
 393                        return swidget;
 394        }
 395
 396        return NULL;
 397}
 398
 399/* find widget by stream name and direction */
 400struct snd_sof_widget *
 401snd_sof_find_swidget_sname(struct snd_soc_component *scomp,
 402                           const char *pcm_name, int dir)
 403{
 404        struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
 405        struct snd_sof_widget *swidget;
 406        enum snd_soc_dapm_type type;
 407
 408        if (dir == SNDRV_PCM_STREAM_PLAYBACK)
 409                type = snd_soc_dapm_aif_in;
 410        else
 411                type = snd_soc_dapm_aif_out;
 412
 413        list_for_each_entry(swidget, &sdev->widget_list, list) {
 414                if (!strcmp(pcm_name, swidget->widget->sname) &&
 415                    swidget->id == type)
 416                        return swidget;
 417        }
 418
 419        return NULL;
 420}
 421
 422struct snd_sof_dai *snd_sof_find_dai(struct snd_soc_component *scomp,
 423                                     const char *name)
 424{
 425        struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
 426        struct snd_sof_dai *dai;
 427
 428        list_for_each_entry(dai, &sdev->dai_list, list) {
 429                if (dai->name && (strcmp(name, dai->name) == 0))
 430                        return dai;
 431        }
 432
 433        return NULL;
 434}
 435
 436/*
 437 * Helper to get SSP MCLK from a pcm_runtime.
 438 * Return 0 if not exist.
 439 */
 440int sof_dai_get_mclk(struct snd_soc_pcm_runtime *rtd)
 441{
 442        struct snd_soc_component *component =
 443                snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);
 444        struct snd_sof_dai *dai =
 445                snd_sof_find_dai(component, (char *)rtd->dai_link->name);
 446
 447        /* use the tplg configured mclk if existed */
 448        if (!dai || !dai->dai_config)
 449                return 0;
 450
 451        switch (dai->dai_config->type) {
 452        case SOF_DAI_INTEL_SSP:
 453                return dai->dai_config->ssp.mclk_rate;
 454        default:
 455                /* not yet implemented for platforms other than the above */
 456                dev_err(rtd->dev, "mclk for dai_config->type %d not supported yet!\n",
 457                        dai->dai_config->type);
 458                return -EINVAL;
 459        }
 460}
 461EXPORT_SYMBOL(sof_dai_get_mclk);
 462
 463/*
 464 * SOF Driver enumeration.
 465 */
 466int sof_machine_check(struct snd_sof_dev *sdev)
 467{
 468        struct snd_sof_pdata *sof_pdata = sdev->pdata;
 469        const struct sof_dev_desc *desc = sof_pdata->desc;
 470        struct snd_soc_acpi_mach *mach;
 471
 472        if (!IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE)) {
 473
 474                /* find machine */
 475                snd_sof_machine_select(sdev);
 476                if (sof_pdata->machine) {
 477                        snd_sof_set_mach_params(sof_pdata->machine, sdev);
 478                        return 0;
 479                }
 480
 481                if (!IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC)) {
 482                        dev_err(sdev->dev, "error: no matching ASoC machine driver found - aborting probe\n");
 483                        return -ENODEV;
 484                }
 485        } else {
 486                dev_warn(sdev->dev, "Force to use nocodec mode\n");
 487        }
 488
 489        /* select nocodec mode */
 490        dev_warn(sdev->dev, "Using nocodec machine driver\n");
 491        mach = devm_kzalloc(sdev->dev, sizeof(*mach), GFP_KERNEL);
 492        if (!mach)
 493                return -ENOMEM;
 494
 495        mach->drv_name = "sof-nocodec";
 496        sof_pdata->tplg_filename = desc->nocodec_tplg_filename;
 497
 498        sof_pdata->machine = mach;
 499        snd_sof_set_mach_params(sof_pdata->machine, sdev);
 500
 501        return 0;
 502}
 503EXPORT_SYMBOL(sof_machine_check);
 504
 505int sof_machine_register(struct snd_sof_dev *sdev, void *pdata)
 506{
 507        struct snd_sof_pdata *plat_data = pdata;
 508        const char *drv_name;
 509        const void *mach;
 510        int size;
 511
 512        drv_name = plat_data->machine->drv_name;
 513        mach = plat_data->machine;
 514        size = sizeof(*plat_data->machine);
 515
 516        /* register machine driver, pass machine info as pdata */
 517        plat_data->pdev_mach =
 518                platform_device_register_data(sdev->dev, drv_name,
 519                                              PLATFORM_DEVID_NONE, mach, size);
 520        if (IS_ERR(plat_data->pdev_mach))
 521                return PTR_ERR(plat_data->pdev_mach);
 522
 523        dev_dbg(sdev->dev, "created machine %s\n",
 524                dev_name(&plat_data->pdev_mach->dev));
 525
 526        return 0;
 527}
 528EXPORT_SYMBOL(sof_machine_register);
 529
 530void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata)
 531{
 532        struct snd_sof_pdata *plat_data = pdata;
 533
 534        if (!IS_ERR_OR_NULL(plat_data->pdev_mach))
 535                platform_device_unregister(plat_data->pdev_mach);
 536}
 537EXPORT_SYMBOL(sof_machine_unregister);
 538