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 <linux/bitfield.h> 12 #include "sof-audio.h" 13 #include "ops.h" 14 15 static int sof_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol) 16 { 17 int ret; 18 19 /* reset readback offset for scontrol */ 20 scontrol->readback_offset = 0; 21 22 ret = snd_sof_ipc_set_get_comp_data(scontrol, true); 23 if (ret < 0) 24 dev_err(sdev->dev, "error: failed kcontrol value set for widget: %d\n", 25 scontrol->comp_id); 26 27 return ret; 28 } 29 30 static int sof_widget_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) 31 { 32 struct snd_sof_control *scontrol; 33 int ret; 34 35 /* set up all controls for the widget */ 36 list_for_each_entry(scontrol, &sdev->kcontrol_list, list) 37 if (scontrol->comp_id == swidget->comp_id) { 38 /* set kcontrol data in DSP */ 39 ret = sof_kcontrol_setup(sdev, scontrol); 40 if (ret < 0) { 41 dev_err(sdev->dev, "error: fail to set up kcontrols for widget %s\n", 42 swidget->widget->name); 43 return ret; 44 } 45 46 /* 47 * Read back the data from the DSP for static widgets. This is particularly 48 * useful for binary kcontrols associated with static pipeline widgets to 49 * initialize the data size to match that in the DSP. 50 */ 51 if (swidget->dynamic_pipeline_widget) 52 continue; 53 54 ret = snd_sof_ipc_set_get_comp_data(scontrol, false); 55 if (ret < 0) 56 dev_warn(sdev->dev, "Failed kcontrol get for control in widget %s\n", 57 swidget->widget->name); 58 } 59 60 return 0; 61 } 62 63 static void sof_reset_route_setup_status(struct snd_sof_dev *sdev, struct snd_sof_widget *widget) 64 { 65 struct snd_sof_route *sroute; 66 67 list_for_each_entry(sroute, &sdev->route_list, list) 68 if (sroute->src_widget == widget || sroute->sink_widget == widget) 69 sroute->setup = false; 70 } 71 72 int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) 73 { 74 const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; 75 int err = 0; 76 int ret; 77 78 if (!swidget->private) 79 return 0; 80 81 /* only free when use_count is 0 */ 82 if (--swidget->use_count) 83 return 0; 84 85 /* continue to disable core even if IPC fails */ 86 if (tplg_ops->widget_free) 87 err = tplg_ops->widget_free(sdev, swidget); 88 89 /* 90 * disable widget core. continue to route setup status and complete flag 91 * even if this fails and return the appropriate error 92 */ 93 ret = snd_sof_dsp_core_put(sdev, swidget->core); 94 if (ret < 0) { 95 dev_err(sdev->dev, "error: failed to disable target core: %d for widget %s\n", 96 swidget->core, swidget->widget->name); 97 if (!err) 98 err = ret; 99 } 100 101 /* reset route setup status for all routes that contain this widget */ 102 sof_reset_route_setup_status(sdev, swidget); 103 swidget->complete = 0; 104 105 /* 106 * free the scheduler widget (same as pipe_widget) associated with the current swidget. 107 * skip for static pipelines 108 */ 109 if (swidget->dynamic_pipeline_widget && swidget->id != snd_soc_dapm_scheduler) { 110 ret = sof_widget_free(sdev, swidget->pipe_widget); 111 if (ret < 0 && !err) 112 err = ret; 113 } 114 115 if (!err) 116 dev_dbg(sdev->dev, "widget %s freed\n", swidget->widget->name); 117 118 return err; 119 } 120 EXPORT_SYMBOL(sof_widget_free); 121 122 int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) 123 { 124 const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; 125 int ret; 126 127 /* skip if there is no private data */ 128 if (!swidget->private) 129 return 0; 130 131 /* widget already set up */ 132 if (++swidget->use_count > 1) 133 return 0; 134 135 /* 136 * The scheduler widget for a pipeline is not part of the connected DAPM 137 * widget list and it needs to be set up before the widgets in the pipeline 138 * are set up. The use_count for the scheduler widget is incremented for every 139 * widget in a given pipeline to ensure that it is freed only after the last 140 * widget in the pipeline is freed. Skip setting up scheduler widget for static pipelines. 141 */ 142 if (swidget->dynamic_pipeline_widget && swidget->id != snd_soc_dapm_scheduler) { 143 if (!swidget->pipe_widget) { 144 dev_err(sdev->dev, "No scheduler widget set for %s\n", 145 swidget->widget->name); 146 ret = -EINVAL; 147 goto use_count_dec; 148 } 149 150 ret = sof_widget_setup(sdev, swidget->pipe_widget); 151 if (ret < 0) 152 goto use_count_dec; 153 } 154 155 /* enable widget core */ 156 ret = snd_sof_dsp_core_get(sdev, swidget->core); 157 if (ret < 0) { 158 dev_err(sdev->dev, "error: failed to enable target core for widget %s\n", 159 swidget->widget->name); 160 goto pipe_widget_free; 161 } 162 163 /* setup widget in the DSP */ 164 if (tplg_ops->widget_setup) { 165 ret = tplg_ops->widget_setup(sdev, swidget); 166 if (ret < 0) 167 goto core_put; 168 } 169 170 /* send config for DAI components */ 171 if (WIDGET_IS_DAI(swidget->id)) { 172 unsigned int flags = SOF_DAI_CONFIG_FLAGS_NONE; 173 174 if (tplg_ops->dai_config) { 175 ret = tplg_ops->dai_config(sdev, swidget, flags, NULL); 176 if (ret < 0) 177 goto widget_free; 178 } 179 } 180 181 /* restore kcontrols for widget */ 182 ret = sof_widget_kcontrol_setup(sdev, swidget); 183 if (ret < 0) { 184 dev_err(sdev->dev, "error: failed to restore kcontrols for widget %s\n", 185 swidget->widget->name); 186 goto widget_free; 187 } 188 189 dev_dbg(sdev->dev, "widget %s setup complete\n", swidget->widget->name); 190 191 return 0; 192 193 widget_free: 194 /* widget use_count and core ref_count will both be decremented by sof_widget_free() */ 195 sof_widget_free(sdev, swidget); 196 core_put: 197 snd_sof_dsp_core_put(sdev, swidget->core); 198 pipe_widget_free: 199 if (swidget->id != snd_soc_dapm_scheduler) 200 sof_widget_free(sdev, swidget->pipe_widget); 201 use_count_dec: 202 swidget->use_count--; 203 return ret; 204 } 205 EXPORT_SYMBOL(sof_widget_setup); 206 207 int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsource, 208 struct snd_soc_dapm_widget *wsink) 209 { 210 const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; 211 struct snd_sof_widget *src_widget = wsource->dobj.private; 212 struct snd_sof_widget *sink_widget = wsink->dobj.private; 213 struct snd_sof_route *sroute; 214 bool route_found = false; 215 int ret; 216 217 /* ignore routes involving virtual widgets in topology */ 218 switch (src_widget->id) { 219 case snd_soc_dapm_out_drv: 220 case snd_soc_dapm_output: 221 case snd_soc_dapm_input: 222 return 0; 223 default: 224 break; 225 } 226 227 switch (sink_widget->id) { 228 case snd_soc_dapm_out_drv: 229 case snd_soc_dapm_output: 230 case snd_soc_dapm_input: 231 return 0; 232 default: 233 break; 234 } 235 236 /* find route matching source and sink widgets */ 237 list_for_each_entry(sroute, &sdev->route_list, list) 238 if (sroute->src_widget == src_widget && sroute->sink_widget == sink_widget) { 239 route_found = true; 240 break; 241 } 242 243 if (!route_found) { 244 dev_err(sdev->dev, "error: cannot find SOF route for source %s -> %s sink\n", 245 wsource->name, wsink->name); 246 return -EINVAL; 247 } 248 249 /* nothing to do if route is already set up */ 250 if (sroute->setup) 251 return 0; 252 253 ret = ipc_tplg_ops->route_setup(sdev, sroute); 254 if (ret < 0) 255 return ret; 256 257 sroute->setup = true; 258 return 0; 259 } 260 261 static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev, 262 struct snd_soc_dapm_widget_list *list, int dir) 263 { 264 struct snd_soc_dapm_widget *widget; 265 struct snd_soc_dapm_path *p; 266 int ret; 267 int i; 268 269 /* 270 * Set up connections between widgets in the sink/source paths based on direction. 271 * Some non-SOF widgets exist in topology either for compatibility or for the 272 * purpose of connecting a pipeline from a host to a DAI in order to receive the DAPM 273 * events. But they are not handled by the firmware. So ignore them. 274 */ 275 if (dir == SNDRV_PCM_STREAM_PLAYBACK) { 276 for_each_dapm_widgets(list, i, widget) { 277 if (!widget->dobj.private) 278 continue; 279 280 snd_soc_dapm_widget_for_each_sink_path(widget, p) 281 if (p->sink->dobj.private) { 282 ret = sof_route_setup(sdev, widget, p->sink); 283 if (ret < 0) 284 return ret; 285 } 286 } 287 } else { 288 for_each_dapm_widgets(list, i, widget) { 289 if (!widget->dobj.private) 290 continue; 291 292 snd_soc_dapm_widget_for_each_source_path(widget, p) 293 if (p->source->dobj.private) { 294 ret = sof_route_setup(sdev, p->source, widget); 295 if (ret < 0) 296 return ret; 297 } 298 } 299 } 300 301 return 0; 302 } 303 304 int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir) 305 { 306 const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; 307 struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list; 308 struct snd_soc_dapm_widget *widget; 309 int i, ret, num_widgets; 310 311 /* nothing to set up */ 312 if (!list) 313 return 0; 314 315 /* set up widgets in the list */ 316 for_each_dapm_widgets(list, num_widgets, widget) { 317 struct snd_sof_widget *swidget = widget->dobj.private; 318 319 if (!swidget) 320 continue; 321 322 /* set up the widget */ 323 ret = sof_widget_setup(sdev, swidget); 324 if (ret < 0) 325 goto widget_free; 326 } 327 328 /* 329 * error in setting pipeline connections will result in route status being reset for 330 * routes that were successfully set up when the widgets are freed. 331 */ 332 ret = sof_setup_pipeline_connections(sdev, list, dir); 333 if (ret < 0) 334 goto widget_free; 335 336 /* complete pipelines */ 337 for_each_dapm_widgets(list, i, widget) { 338 struct snd_sof_widget *swidget = widget->dobj.private; 339 struct snd_sof_widget *pipe_widget; 340 341 if (!swidget) 342 continue; 343 344 pipe_widget = swidget->pipe_widget; 345 if (!pipe_widget) { 346 dev_err(sdev->dev, "error: no pipeline widget found for %s\n", 347 swidget->widget->name); 348 ret = -EINVAL; 349 goto widget_free; 350 } 351 352 if (pipe_widget->complete) 353 continue; 354 355 if (ipc_tplg_ops->pipeline_complete) { 356 pipe_widget->complete = ipc_tplg_ops->pipeline_complete(sdev, pipe_widget); 357 if (pipe_widget->complete < 0) { 358 ret = pipe_widget->complete; 359 goto widget_free; 360 } 361 } 362 } 363 364 return 0; 365 366 widget_free: 367 /* free all widgets that have been set up successfully */ 368 for_each_dapm_widgets(list, i, widget) { 369 struct snd_sof_widget *swidget = widget->dobj.private; 370 371 if (!swidget) 372 continue; 373 374 if (!num_widgets--) 375 break; 376 377 sof_widget_free(sdev, swidget); 378 } 379 380 return ret; 381 } 382 383 int sof_widget_list_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir) 384 { 385 struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list; 386 struct snd_soc_dapm_widget *widget; 387 int i, ret; 388 int ret1 = 0; 389 390 /* nothing to free */ 391 if (!list) 392 return 0; 393 394 /* 395 * Free widgets in the list. This can fail but continue freeing other widgets to keep 396 * use_counts balanced. 397 */ 398 for_each_dapm_widgets(list, i, widget) { 399 struct snd_sof_widget *swidget = widget->dobj.private; 400 401 if (!swidget) 402 continue; 403 404 /* 405 * free widget and its pipe_widget. Either of these can fail, but free as many as 406 * possible before freeing the list and returning the error. 407 */ 408 ret = sof_widget_free(sdev, swidget); 409 if (ret < 0) 410 ret1 = ret; 411 } 412 413 snd_soc_dapm_dai_free_widgets(&list); 414 spcm->stream[dir].list = NULL; 415 416 return ret1; 417 } 418 419 /* 420 * helper to determine if there are only D0i3 compatible 421 * streams active 422 */ 423 bool snd_sof_dsp_only_d0i3_compatible_stream_active(struct snd_sof_dev *sdev) 424 { 425 struct snd_pcm_substream *substream; 426 struct snd_sof_pcm *spcm; 427 bool d0i3_compatible_active = false; 428 int dir; 429 430 list_for_each_entry(spcm, &sdev->pcm_list, list) { 431 for_each_pcm_streams(dir) { 432 substream = spcm->stream[dir].substream; 433 if (!substream || !substream->runtime) 434 continue; 435 436 /* 437 * substream->runtime being not NULL indicates 438 * that the stream is open. No need to check the 439 * stream state. 440 */ 441 if (!spcm->stream[dir].d0i3_compatible) 442 return false; 443 444 d0i3_compatible_active = true; 445 } 446 } 447 448 return d0i3_compatible_active; 449 } 450 EXPORT_SYMBOL(snd_sof_dsp_only_d0i3_compatible_stream_active); 451 452 bool snd_sof_stream_suspend_ignored(struct snd_sof_dev *sdev) 453 { 454 struct snd_sof_pcm *spcm; 455 456 list_for_each_entry(spcm, &sdev->pcm_list, list) { 457 if (spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].suspend_ignored || 458 spcm->stream[SNDRV_PCM_STREAM_CAPTURE].suspend_ignored) 459 return true; 460 } 461 462 return false; 463 } 464 465 int sof_set_hw_params_upon_resume(struct device *dev) 466 { 467 struct snd_sof_dev *sdev = dev_get_drvdata(dev); 468 struct snd_pcm_substream *substream; 469 struct snd_sof_pcm *spcm; 470 snd_pcm_state_t state; 471 int dir; 472 473 /* 474 * SOF requires hw_params to be set-up internally upon resume. 475 * So, set the flag to indicate this for those streams that 476 * have been suspended. 477 */ 478 list_for_each_entry(spcm, &sdev->pcm_list, list) { 479 for_each_pcm_streams(dir) { 480 /* 481 * do not reset hw_params upon resume for streams that 482 * were kept running during suspend 483 */ 484 if (spcm->stream[dir].suspend_ignored) 485 continue; 486 487 substream = spcm->stream[dir].substream; 488 if (!substream || !substream->runtime) 489 continue; 490 491 state = substream->runtime->status->state; 492 if (state == SNDRV_PCM_STATE_SUSPENDED) 493 spcm->prepared[dir] = false; 494 } 495 } 496 497 /* set internal flag for BE */ 498 return snd_sof_dsp_hw_params_upon_resume(sdev); 499 } 500 501 int sof_pcm_stream_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, 502 struct snd_sof_pcm *spcm, int dir, bool free_widget_list) 503 { 504 const struct sof_ipc_pcm_ops *pcm_ops = sdev->ipc->ops->pcm; 505 int ret; 506 507 /* Send PCM_FREE IPC to reset pipeline */ 508 if (pcm_ops->hw_free && spcm->prepared[substream->stream]) { 509 ret = pcm_ops->hw_free(sdev->component, substream); 510 if (ret < 0) 511 return ret; 512 } 513 514 spcm->prepared[substream->stream] = false; 515 516 /* stop the DMA */ 517 ret = snd_sof_pcm_platform_hw_free(sdev, substream); 518 if (ret < 0) 519 return ret; 520 521 /* free widget list */ 522 if (free_widget_list) { 523 ret = sof_widget_list_free(sdev, spcm, dir); 524 if (ret < 0) 525 dev_err(sdev->dev, "failed to free widgets during suspend\n"); 526 } 527 528 return ret; 529 } 530 531 /* 532 * Generic object lookup APIs. 533 */ 534 535 struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_soc_component *scomp, 536 const char *name) 537 { 538 struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); 539 struct snd_sof_pcm *spcm; 540 541 list_for_each_entry(spcm, &sdev->pcm_list, list) { 542 /* match with PCM dai name */ 543 if (strcmp(spcm->pcm.dai_name, name) == 0) 544 return spcm; 545 546 /* match with playback caps name if set */ 547 if (*spcm->pcm.caps[0].name && 548 !strcmp(spcm->pcm.caps[0].name, name)) 549 return spcm; 550 551 /* match with capture caps name if set */ 552 if (*spcm->pcm.caps[1].name && 553 !strcmp(spcm->pcm.caps[1].name, name)) 554 return spcm; 555 } 556 557 return NULL; 558 } 559 560 struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_soc_component *scomp, 561 unsigned int comp_id, 562 int *direction) 563 { 564 struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); 565 struct snd_sof_pcm *spcm; 566 int dir; 567 568 list_for_each_entry(spcm, &sdev->pcm_list, list) { 569 for_each_pcm_streams(dir) { 570 if (spcm->stream[dir].comp_id == comp_id) { 571 *direction = dir; 572 return spcm; 573 } 574 } 575 } 576 577 return NULL; 578 } 579 580 struct snd_sof_widget *snd_sof_find_swidget(struct snd_soc_component *scomp, 581 const char *name) 582 { 583 struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); 584 struct snd_sof_widget *swidget; 585 586 list_for_each_entry(swidget, &sdev->widget_list, list) { 587 if (strcmp(name, swidget->widget->name) == 0) 588 return swidget; 589 } 590 591 return NULL; 592 } 593 594 /* find widget by stream name and direction */ 595 struct snd_sof_widget * 596 snd_sof_find_swidget_sname(struct snd_soc_component *scomp, 597 const char *pcm_name, int dir) 598 { 599 struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); 600 struct snd_sof_widget *swidget; 601 enum snd_soc_dapm_type type; 602 603 if (dir == SNDRV_PCM_STREAM_PLAYBACK) 604 type = snd_soc_dapm_aif_in; 605 else 606 type = snd_soc_dapm_aif_out; 607 608 list_for_each_entry(swidget, &sdev->widget_list, list) { 609 if (!strcmp(pcm_name, swidget->widget->sname) && 610 swidget->id == type) 611 return swidget; 612 } 613 614 return NULL; 615 } 616 617 struct snd_sof_dai *snd_sof_find_dai(struct snd_soc_component *scomp, 618 const char *name) 619 { 620 struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); 621 struct snd_sof_dai *dai; 622 623 list_for_each_entry(dai, &sdev->dai_list, list) { 624 if (dai->name && (strcmp(name, dai->name) == 0)) 625 return dai; 626 } 627 628 return NULL; 629 } 630 631 static int sof_dai_get_clk(struct snd_soc_pcm_runtime *rtd, int clk_type) 632 { 633 struct snd_soc_component *component = 634 snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); 635 struct snd_sof_dai *dai = 636 snd_sof_find_dai(component, (char *)rtd->dai_link->name); 637 struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); 638 const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; 639 640 /* use the tplg configured mclk if existed */ 641 if (!dai) 642 return 0; 643 644 if (tplg_ops->dai_get_clk) 645 return tplg_ops->dai_get_clk(sdev, dai, clk_type); 646 647 return 0; 648 } 649 650 /* 651 * Helper to get SSP MCLK from a pcm_runtime. 652 * Return 0 if not exist. 653 */ 654 int sof_dai_get_mclk(struct snd_soc_pcm_runtime *rtd) 655 { 656 return sof_dai_get_clk(rtd, SOF_DAI_CLK_INTEL_SSP_MCLK); 657 } 658 EXPORT_SYMBOL(sof_dai_get_mclk); 659 660 /* 661 * Helper to get SSP BCLK from a pcm_runtime. 662 * Return 0 if not exist. 663 */ 664 int sof_dai_get_bclk(struct snd_soc_pcm_runtime *rtd) 665 { 666 return sof_dai_get_clk(rtd, SOF_DAI_CLK_INTEL_SSP_BCLK); 667 } 668 EXPORT_SYMBOL(sof_dai_get_bclk); 669 670 /* 671 * SOF Driver enumeration. 672 */ 673 int sof_machine_check(struct snd_sof_dev *sdev) 674 { 675 struct snd_sof_pdata *sof_pdata = sdev->pdata; 676 const struct sof_dev_desc *desc = sof_pdata->desc; 677 struct snd_soc_acpi_mach *mach; 678 679 if (!IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE)) { 680 681 /* find machine */ 682 mach = snd_sof_machine_select(sdev); 683 if (mach) { 684 sof_pdata->machine = mach; 685 snd_sof_set_mach_params(mach, sdev); 686 return 0; 687 } 688 689 if (!IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC)) { 690 dev_err(sdev->dev, "error: no matching ASoC machine driver found - aborting probe\n"); 691 return -ENODEV; 692 } 693 } else { 694 dev_warn(sdev->dev, "Force to use nocodec mode\n"); 695 } 696 697 /* select nocodec mode */ 698 dev_warn(sdev->dev, "Using nocodec machine driver\n"); 699 mach = devm_kzalloc(sdev->dev, sizeof(*mach), GFP_KERNEL); 700 if (!mach) 701 return -ENOMEM; 702 703 mach->drv_name = "sof-nocodec"; 704 sof_pdata->tplg_filename = desc->nocodec_tplg_filename; 705 706 sof_pdata->machine = mach; 707 snd_sof_set_mach_params(mach, sdev); 708 709 return 0; 710 } 711 EXPORT_SYMBOL(sof_machine_check); 712 713 int sof_machine_register(struct snd_sof_dev *sdev, void *pdata) 714 { 715 struct snd_sof_pdata *plat_data = pdata; 716 const char *drv_name; 717 const void *mach; 718 int size; 719 720 drv_name = plat_data->machine->drv_name; 721 mach = plat_data->machine; 722 size = sizeof(*plat_data->machine); 723 724 /* register machine driver, pass machine info as pdata */ 725 plat_data->pdev_mach = 726 platform_device_register_data(sdev->dev, drv_name, 727 PLATFORM_DEVID_NONE, mach, size); 728 if (IS_ERR(plat_data->pdev_mach)) 729 return PTR_ERR(plat_data->pdev_mach); 730 731 dev_dbg(sdev->dev, "created machine %s\n", 732 dev_name(&plat_data->pdev_mach->dev)); 733 734 return 0; 735 } 736 EXPORT_SYMBOL(sof_machine_register); 737 738 void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata) 739 { 740 struct snd_sof_pdata *plat_data = pdata; 741 742 if (!IS_ERR_OR_NULL(plat_data->pdev_mach)) 743 platform_device_unregister(plat_data->pdev_mach); 744 } 745 EXPORT_SYMBOL(sof_machine_unregister); 746