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 */ 18 bool 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 } 45 EXPORT_SYMBOL(snd_sof_dsp_only_d0i3_compatible_stream_active); 46 47 bool 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 60 int 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 96 static 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 145 const 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 161 int 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 326 struct 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 351 struct 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 371 struct 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 385 struct 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 */ 400 struct snd_sof_widget * 401 snd_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 422 struct 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 #define SOF_DAI_CLK_INTEL_SSP_MCLK 0 437 #define SOF_DAI_CLK_INTEL_SSP_BCLK 1 438 439 static int sof_dai_get_clk(struct snd_soc_pcm_runtime *rtd, int clk_type) 440 { 441 struct snd_soc_component *component = 442 snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); 443 struct snd_sof_dai *dai = 444 snd_sof_find_dai(component, (char *)rtd->dai_link->name); 445 446 /* use the tplg configured mclk if existed */ 447 if (!dai || !dai->dai_config) 448 return 0; 449 450 switch (dai->dai_config->type) { 451 case SOF_DAI_INTEL_SSP: 452 switch (clk_type) { 453 case SOF_DAI_CLK_INTEL_SSP_MCLK: 454 return dai->dai_config->ssp.mclk_rate; 455 case SOF_DAI_CLK_INTEL_SSP_BCLK: 456 return dai->dai_config->ssp.bclk_rate; 457 default: 458 dev_err(rtd->dev, "fail to get SSP clk %d rate\n", 459 clk_type); 460 return -EINVAL; 461 } 462 break; 463 default: 464 /* not yet implemented for platforms other than the above */ 465 dev_err(rtd->dev, "DAI type %d not supported yet!\n", 466 dai->dai_config->type); 467 return -EINVAL; 468 } 469 } 470 471 /* 472 * Helper to get SSP MCLK from a pcm_runtime. 473 * Return 0 if not exist. 474 */ 475 int sof_dai_get_mclk(struct snd_soc_pcm_runtime *rtd) 476 { 477 return sof_dai_get_clk(rtd, SOF_DAI_CLK_INTEL_SSP_MCLK); 478 } 479 EXPORT_SYMBOL(sof_dai_get_mclk); 480 481 /* 482 * Helper to get SSP BCLK from a pcm_runtime. 483 * Return 0 if not exist. 484 */ 485 int sof_dai_get_bclk(struct snd_soc_pcm_runtime *rtd) 486 { 487 return sof_dai_get_clk(rtd, SOF_DAI_CLK_INTEL_SSP_BCLK); 488 } 489 EXPORT_SYMBOL(sof_dai_get_bclk); 490 491 /* 492 * SOF Driver enumeration. 493 */ 494 int sof_machine_check(struct snd_sof_dev *sdev) 495 { 496 struct snd_sof_pdata *sof_pdata = sdev->pdata; 497 const struct sof_dev_desc *desc = sof_pdata->desc; 498 struct snd_soc_acpi_mach *mach; 499 500 if (!IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE)) { 501 502 /* find machine */ 503 snd_sof_machine_select(sdev); 504 if (sof_pdata->machine) { 505 snd_sof_set_mach_params(sof_pdata->machine, sdev); 506 return 0; 507 } 508 509 if (!IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC)) { 510 dev_err(sdev->dev, "error: no matching ASoC machine driver found - aborting probe\n"); 511 return -ENODEV; 512 } 513 } else { 514 dev_warn(sdev->dev, "Force to use nocodec mode\n"); 515 } 516 517 /* select nocodec mode */ 518 dev_warn(sdev->dev, "Using nocodec machine driver\n"); 519 mach = devm_kzalloc(sdev->dev, sizeof(*mach), GFP_KERNEL); 520 if (!mach) 521 return -ENOMEM; 522 523 mach->drv_name = "sof-nocodec"; 524 sof_pdata->tplg_filename = desc->nocodec_tplg_filename; 525 526 sof_pdata->machine = mach; 527 snd_sof_set_mach_params(sof_pdata->machine, sdev); 528 529 return 0; 530 } 531 EXPORT_SYMBOL(sof_machine_check); 532 533 int sof_machine_register(struct snd_sof_dev *sdev, void *pdata) 534 { 535 struct snd_sof_pdata *plat_data = pdata; 536 const char *drv_name; 537 const void *mach; 538 int size; 539 540 drv_name = plat_data->machine->drv_name; 541 mach = plat_data->machine; 542 size = sizeof(*plat_data->machine); 543 544 /* register machine driver, pass machine info as pdata */ 545 plat_data->pdev_mach = 546 platform_device_register_data(sdev->dev, drv_name, 547 PLATFORM_DEVID_NONE, mach, size); 548 if (IS_ERR(plat_data->pdev_mach)) 549 return PTR_ERR(plat_data->pdev_mach); 550 551 dev_dbg(sdev->dev, "created machine %s\n", 552 dev_name(&plat_data->pdev_mach->dev)); 553 554 return 0; 555 } 556 EXPORT_SYMBOL(sof_machine_register); 557 558 void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata) 559 { 560 struct snd_sof_pdata *plat_data = pdata; 561 562 if (!IS_ERR_OR_NULL(plat_data->pdev_mach)) 563 platform_device_unregister(plat_data->pdev_mach); 564 } 565 EXPORT_SYMBOL(sof_machine_unregister); 566