| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | |
| 8 | |
| 9 | |
| 10 |
|
| 11 | #include <linux/bitfield.h>
|
| 12 | #include <trace/events/sof.h>
|
| 13 | #include "sof-audio.h"
|
| 14 | #include "ops.h"
|
| 15 |
|
| 16 | |
| 17 | * Check if a DAI widget is an aggregated DAI. Aggregated DAI's have names ending in numbers
|
| 18 | * starting with 0. For example: in the case of a SDW speaker with 2 amps, the topology contains
|
| 19 | * 2 DAI's names alh-copier.SDW1.Playback.0 and alh-copier-SDW1.Playback.1. In this case, only the
|
| 20 | * DAI alh-copier.SDW1.Playback.0 is set up in the firmware. The other DAI,
|
| 21 | * alh-copier.SDW1.Playback.1 in topology is for the sake of completeness to show aggregation for
|
| 22 | * the speaker amp and does not need any firmware configuration.
|
| 23 |
|
| 24 | static bool is_aggregated_dai(struct snd_sof_widget *swidget)
|
| 25 | {
|
| 26 | return (WIDGET_IS_DAI(swidget->id) &&
|
| 27 | isdigit(swidget->widget->name[strlen(swidget->widget->name) - 1]) &&
|
| 28 | swidget->widget->name[strlen(swidget->widget->name) - 1] != '0');
|
| 29 | }
|
| 30 |
|
| 31 | static bool is_virtual_widget(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget,
|
| 32 | const char *func)
|
| 33 | {
|
| 34 | switch (widget->id) {
|
| 35 | case snd_soc_dapm_out_drv:
|
| 36 | case snd_soc_dapm_output:
|
| 37 | case snd_soc_dapm_input:
|
| 38 | dev_dbg(sdev->dev, "%s: %s is a virtual widget\n", func, widget->name);
|
| 39 | return true;
|
| 40 | default:
|
| 41 | return false;
|
| 42 | }
|
| 43 | }
|
| 44 |
|
| 45 | static void sof_reset_route_setup_status(struct snd_sof_dev *sdev, struct snd_sof_widget *widget)
|
| 46 | {
|
| 47 | const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
|
| 48 | struct snd_sof_route *sroute;
|
| 49 |
|
| 50 | list_for_each_entry(sroute, &sdev->route_list, list)
|
| 51 | if (sroute->src_widget == widget || sroute->sink_widget == widget) {
|
| 52 | if (sroute->setup && tplg_ops && tplg_ops->route_free)
|
| 53 | tplg_ops->route_free(sdev, sroute);
|
| 54 |
|
| 55 | sroute->setup = false;
|
| 56 | }
|
| 57 | }
|
| 58 |
|
| 59 | static int sof_widget_free_unlocked(struct snd_sof_dev *sdev,
|
| 60 | struct snd_sof_widget *swidget)
|
| 61 | {
|
| 62 | const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
|
| 63 | struct snd_sof_pipeline *spipe = swidget->spipe;
|
| 64 | int err = 0;
|
| 65 | int ret;
|
| 66 |
|
| 67 | if (!swidget->private)
|
| 68 | return 0;
|
| 69 |
|
| 70 | trace_sof_widget_free(swidget);
|
| 71 |
|
| 72 |
|
| 73 | if (--swidget->use_count)
|
| 74 | return 0;
|
| 75 |
|
| 76 |
|
| 77 | sof_reset_route_setup_status(sdev, swidget);
|
| 78 |
|
| 79 |
|
| 80 | if (WIDGET_IS_DAI(swidget->id)) {
|
| 81 | struct snd_sof_dai_config_data data;
|
| 82 | unsigned int flags = SOF_DAI_CONFIG_FLAGS_HW_FREE;
|
| 83 |
|
| 84 | data.dai_data = DMA_CHAN_INVALID;
|
| 85 |
|
| 86 | if (tplg_ops && tplg_ops->dai_config) {
|
| 87 | err = tplg_ops->dai_config(sdev, swidget, flags, &data);
|
| 88 | if (err < 0)
|
| 89 | dev_err(sdev->dev, "failed to free config for widget %s\n",
|
| 90 | swidget->widget->name);
|
| 91 | }
|
| 92 | }
|
| 93 |
|
| 94 |
|
| 95 | if (tplg_ops && tplg_ops->widget_free) {
|
| 96 | ret = tplg_ops->widget_free(sdev, swidget);
|
| 97 | if (ret < 0 && !err)
|
| 98 | err = ret;
|
| 99 | }
|
| 100 |
|
| 101 | |
| 102 | * decrement ref count for cores associated with all modules in the pipeline and clear
|
| 103 | * the complete flag
|
| 104 |
|
| 105 | if (swidget->id == snd_soc_dapm_scheduler) {
|
| 106 | int i;
|
| 107 |
|
| 108 | for_each_set_bit(i, &spipe->core_mask, sdev->num_cores) {
|
| 109 | ret = snd_sof_dsp_core_put(sdev, i);
|
| 110 | if (ret < 0) {
|
| 111 | dev_err(sdev->dev, "failed to disable target core: %d for pipeline %s\n",
|
| 112 | i, swidget->widget->name);
|
| 113 | if (!err)
|
| 114 | err = ret;
|
| 115 | }
|
| 116 | }
|
| 117 | swidget->spipe->complete = 0;
|
| 118 | }
|
| 119 |
|
| 120 | |
| 121 | * free the scheduler widget (same as pipe_widget) associated with the current swidget.
|
| 122 | * skip for static pipelines
|
| 123 |
|
| 124 | if (swidget->spipe && swidget->dynamic_pipeline_widget &&
|
| 125 | swidget->id != snd_soc_dapm_scheduler) {
|
| 126 | ret = sof_widget_free_unlocked(sdev, swidget->spipe->pipe_widget);
|
| 127 | if (ret < 0 && !err)
|
| 128 | err = ret;
|
| 129 | }
|
| 130 |
|
| 131 | if (!err)
|
| 132 | dev_dbg(sdev->dev, "widget %s freed\n", swidget->widget->name);
|
| 133 |
|
| 134 | return err;
|
| 135 | }
|
| 136 |
|
| 137 | int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
|
| 138 | {
|
| 139 | guard(mutex)(&swidget->setup_mutex);
|
| 140 | return sof_widget_free_unlocked(sdev, swidget);
|
| 141 | }
|
| 142 | EXPORT_SYMBOL(sof_widget_free);
|
| 143 |
|
| 144 | static int sof_widget_setup_unlocked(struct snd_sof_dev *sdev,
|
| 145 | struct snd_sof_widget *swidget)
|
| 146 | {
|
| 147 | const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
|
| 148 | struct snd_sof_pipeline *spipe = swidget->spipe;
|
| 149 | int ret;
|
| 150 | int i;
|
| 151 |
|
| 152 |
|
| 153 | if (!swidget->private)
|
| 154 | return 0;
|
| 155 |
|
| 156 | trace_sof_widget_setup(swidget);
|
| 157 |
|
| 158 |
|
| 159 | if (++swidget->use_count > 1)
|
| 160 | return 0;
|
| 161 |
|
| 162 | |
| 163 | * The scheduler widget for a pipeline is not part of the connected DAPM
|
| 164 | * widget list and it needs to be set up before the widgets in the pipeline
|
| 165 | * are set up. The use_count for the scheduler widget is incremented for every
|
| 166 | * widget in a given pipeline to ensure that it is freed only after the last
|
| 167 | * widget in the pipeline is freed. Skip setting up scheduler widget for static pipelines.
|
| 168 |
|
| 169 | if (swidget->dynamic_pipeline_widget && swidget->id != snd_soc_dapm_scheduler) {
|
| 170 | if (!swidget->spipe || !swidget->spipe->pipe_widget) {
|
| 171 | dev_err(sdev->dev, "No pipeline set for %s\n", swidget->widget->name);
|
| 172 | ret = -EINVAL;
|
| 173 | goto use_count_dec;
|
| 174 | }
|
| 175 |
|
| 176 | ret = sof_widget_setup_unlocked(sdev, swidget->spipe->pipe_widget);
|
| 177 | if (ret < 0)
|
| 178 | goto use_count_dec;
|
| 179 | }
|
| 180 |
|
| 181 |
|
| 182 | if (swidget->id == snd_soc_dapm_scheduler) {
|
| 183 | for_each_set_bit(i, &spipe->core_mask, sdev->num_cores) {
|
| 184 | ret = snd_sof_dsp_core_get(sdev, i);
|
| 185 | if (ret < 0) {
|
| 186 | dev_err(sdev->dev, "failed to enable target core %d for pipeline %s\n",
|
| 187 | i, swidget->widget->name);
|
| 188 | goto pipe_widget_free;
|
| 189 | }
|
| 190 | }
|
| 191 | }
|
| 192 |
|
| 193 |
|
| 194 | if (tplg_ops && tplg_ops->widget_setup) {
|
| 195 | ret = tplg_ops->widget_setup(sdev, swidget);
|
| 196 | if (ret < 0)
|
| 197 | goto pipe_widget_free;
|
| 198 | }
|
| 199 |
|
| 200 |
|
| 201 | if (WIDGET_IS_DAI(swidget->id)) {
|
| 202 | unsigned int flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS;
|
| 203 |
|
| 204 | |
| 205 | * The config flags saved during BE DAI hw_params will be used for IPC3. IPC4 does
|
| 206 | * not use the flags argument.
|
| 207 |
|
| 208 | if (tplg_ops && tplg_ops->dai_config) {
|
| 209 | ret = tplg_ops->dai_config(sdev, swidget, flags, NULL);
|
| 210 | if (ret < 0)
|
| 211 | goto widget_free;
|
| 212 | }
|
| 213 | }
|
| 214 |
|
| 215 |
|
| 216 | if (tplg_ops && tplg_ops->control && tplg_ops->control->widget_kcontrol_setup) {
|
| 217 | ret = tplg_ops->control->widget_kcontrol_setup(sdev, swidget);
|
| 218 | if (ret < 0)
|
| 219 | goto widget_free;
|
| 220 | }
|
| 221 |
|
| 222 | dev_dbg(sdev->dev, "widget %s setup complete\n", swidget->widget->name);
|
| 223 |
|
| 224 | return 0;
|
| 225 |
|
| 226 | widget_free:
|
| 227 |
|
| 228 | sof_widget_free_unlocked(sdev, swidget);
|
| 229 | return ret;
|
| 230 |
|
| 231 | pipe_widget_free:
|
| 232 | if (swidget->id != snd_soc_dapm_scheduler) {
|
| 233 | sof_widget_free_unlocked(sdev, swidget->spipe->pipe_widget);
|
| 234 | } else {
|
| 235 | int j;
|
| 236 |
|
| 237 |
|
| 238 | for_each_set_bit(j, &spipe->core_mask, sdev->num_cores) {
|
| 239 | if (j >= i)
|
| 240 | break;
|
| 241 | snd_sof_dsp_core_put(sdev, j);
|
| 242 | }
|
| 243 | }
|
| 244 | use_count_dec:
|
| 245 | swidget->use_count--;
|
| 246 |
|
| 247 | return ret;
|
| 248 | }
|
| 249 |
|
| 250 | int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
|
| 251 | {
|
| 252 | guard(mutex)(&swidget->setup_mutex);
|
| 253 | return sof_widget_setup_unlocked(sdev, swidget);
|
| 254 | }
|
| 255 | EXPORT_SYMBOL(sof_widget_setup);
|
| 256 |
|
| 257 | int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsource,
|
| 258 | struct snd_soc_dapm_widget *wsink)
|
| 259 | {
|
| 260 | const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
|
| 261 | struct snd_sof_widget *src_widget = wsource->dobj.private;
|
| 262 | struct snd_sof_widget *sink_widget = wsink->dobj.private;
|
| 263 | struct snd_sof_route *sroute;
|
| 264 | bool route_found = false;
|
| 265 |
|
| 266 |
|
| 267 | if (is_virtual_widget(sdev, src_widget->widget, __func__) ||
|
| 268 | is_virtual_widget(sdev, sink_widget->widget, __func__))
|
| 269 | return 0;
|
| 270 |
|
| 271 |
|
| 272 | if (!src_widget->use_count || !sink_widget->use_count)
|
| 273 | return 0;
|
| 274 |
|
| 275 |
|
| 276 | list_for_each_entry(sroute, &sdev->route_list, list)
|
| 277 | if (sroute->src_widget == src_widget && sroute->sink_widget == sink_widget) {
|
| 278 | route_found = true;
|
| 279 | break;
|
| 280 | }
|
| 281 |
|
| 282 | if (!route_found) {
|
| 283 | dev_err(sdev->dev, "error: cannot find SOF route for source %s -> %s sink\n",
|
| 284 | wsource->name, wsink->name);
|
| 285 | return -EINVAL;
|
| 286 | }
|
| 287 |
|
| 288 |
|
| 289 | if (sroute->setup)
|
| 290 | return 0;
|
| 291 |
|
| 292 | if (tplg_ops && tplg_ops->route_setup) {
|
| 293 | int ret = tplg_ops->route_setup(sdev, sroute);
|
| 294 |
|
| 295 | if (ret < 0)
|
| 296 | return ret;
|
| 297 | }
|
| 298 |
|
| 299 | sroute->setup = true;
|
| 300 | return 0;
|
| 301 | }
|
| 302 |
|
| 303 | static bool sof_widget_in_same_direction(struct snd_sof_widget *swidget, int dir)
|
| 304 | {
|
| 305 | return swidget->spipe->direction == dir;
|
| 306 | }
|
| 307 |
|
| 308 | static int sof_set_up_same_dir_widget_routes(struct snd_sof_dev *sdev,
|
| 309 | struct snd_soc_dapm_widget *wsource,
|
| 310 | struct snd_soc_dapm_widget *wsink)
|
| 311 | {
|
| 312 | struct snd_sof_widget *src_widget = wsource->dobj.private;
|
| 313 | struct snd_sof_widget *sink_widget = wsink->dobj.private;
|
| 314 |
|
| 315 | |
| 316 | * skip setting up route if source and sink are in different directions (ex. playback and
|
| 317 | * echo ref) if the direction is set in topology. These will be set up later. It is enough
|
| 318 | * to check if the direction_valid is set for one of the widgets as all widgets will have
|
| 319 | * the direction set in topology if one is set.
|
| 320 |
|
| 321 | if (sink_widget->spipe && sink_widget->spipe->direction_valid &&
|
| 322 | !sof_widget_in_same_direction(sink_widget, src_widget->spipe->direction))
|
| 323 | return 0;
|
| 324 |
|
| 325 | return sof_route_setup(sdev, wsource, wsink);
|
| 326 | }
|
| 327 |
|
| 328 | static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev,
|
| 329 | struct snd_soc_dapm_widget_list *list, int dir)
|
| 330 | {
|
| 331 | struct snd_soc_dapm_widget *widget;
|
| 332 | struct snd_sof_route *sroute;
|
| 333 | struct snd_soc_dapm_path *p;
|
| 334 | int ret = 0;
|
| 335 | int i;
|
| 336 |
|
| 337 | |
| 338 | * Set up connections between widgets in the sink/source paths based on direction.
|
| 339 | * Some non-SOF widgets exist in topology either for compatibility or for the
|
| 340 | * purpose of connecting a pipeline from a host to a DAI in order to receive the DAPM
|
| 341 | * events. But they are not handled by the firmware. So ignore them.
|
| 342 |
|
| 343 | if (dir == SNDRV_PCM_STREAM_PLAYBACK) {
|
| 344 | for_each_dapm_widgets(list, i, widget) {
|
| 345 | if (!widget->dobj.private)
|
| 346 | continue;
|
| 347 |
|
| 348 | snd_soc_dapm_widget_for_each_sink_path(widget, p) {
|
| 349 | if (!widget_in_list(list, p->sink))
|
| 350 | continue;
|
| 351 |
|
| 352 | if (p->sink->dobj.private) {
|
| 353 | ret = sof_set_up_same_dir_widget_routes(sdev, widget,
|
| 354 | p->sink);
|
| 355 | if (ret < 0)
|
| 356 | return ret;
|
| 357 | }
|
| 358 | }
|
| 359 | }
|
| 360 | } else {
|
| 361 | for_each_dapm_widgets(list, i, widget) {
|
| 362 | if (!widget->dobj.private)
|
| 363 | continue;
|
| 364 |
|
| 365 | snd_soc_dapm_widget_for_each_source_path(widget, p) {
|
| 366 | if (!widget_in_list(list, p->source))
|
| 367 | continue;
|
| 368 |
|
| 369 | if (p->source->dobj.private) {
|
| 370 | ret = sof_set_up_same_dir_widget_routes(sdev, p->source,
|
| 371 | widget);
|
| 372 | if (ret < 0)
|
| 373 | return ret;
|
| 374 | }
|
| 375 | }
|
| 376 | }
|
| 377 | }
|
| 378 |
|
| 379 | |
| 380 | * The above loop handles connections between widgets that belong to the DAPM widget list.
|
| 381 | * This is not sufficient to handle loopback cases between pipelines configured with
|
| 382 | * different directions, e.g. a sidetone or an amplifier feedback connected to a speaker
|
| 383 | * protection module.
|
| 384 |
|
| 385 | list_for_each_entry(sroute, &sdev->route_list, list) {
|
| 386 | bool src_widget_in_dapm_list, sink_widget_in_dapm_list;
|
| 387 |
|
| 388 | if (sroute->setup)
|
| 389 | continue;
|
| 390 |
|
| 391 | src_widget_in_dapm_list = widget_in_list(list, sroute->src_widget->widget);
|
| 392 | sink_widget_in_dapm_list = widget_in_list(list, sroute->sink_widget->widget);
|
| 393 |
|
| 394 | |
| 395 | * no need to set up the route if both the source and sink widgets are not in the
|
| 396 | * DAPM list
|
| 397 |
|
| 398 | if (!src_widget_in_dapm_list && !sink_widget_in_dapm_list)
|
| 399 | continue;
|
| 400 |
|
| 401 | |
| 402 | * set up the route only if both the source and sink widgets are in the DAPM list
|
| 403 | * but are in different directions. The ones in the same direction would already
|
| 404 | * have been set up in the previous loop.
|
| 405 |
|
| 406 | if (src_widget_in_dapm_list && sink_widget_in_dapm_list) {
|
| 407 | struct snd_sof_widget *src_widget, *sink_widget;
|
| 408 |
|
| 409 | src_widget = sroute->src_widget->widget->dobj.private;
|
| 410 | sink_widget = sroute->sink_widget->widget->dobj.private;
|
| 411 |
|
| 412 | |
| 413 | * it is enough to check if the direction_valid is set for one of the
|
| 414 | * widgets as all widgets will have the direction set in topology if one
|
| 415 | * is set.
|
| 416 |
|
| 417 | if (src_widget && sink_widget &&
|
| 418 | src_widget->spipe && src_widget->spipe->direction_valid &&
|
| 419 | sof_widget_in_same_direction(sink_widget, src_widget->spipe->direction))
|
| 420 | continue;
|
| 421 | }
|
| 422 |
|
| 423 | ret = sof_route_setup(sdev, sroute->src_widget->widget,
|
| 424 | sroute->sink_widget->widget);
|
| 425 |
|
| 426 | if (ret < 0)
|
| 427 | return ret;
|
| 428 | }
|
| 429 |
|
| 430 | return 0;
|
| 431 | }
|
| 432 |
|
| 433 | static void
|
| 434 | sof_unprepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget,
|
| 435 | struct snd_soc_dapm_widget_list *list, int dir)
|
| 436 | {
|
| 437 | const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
|
| 438 | struct snd_sof_widget *swidget = widget->dobj.private;
|
| 439 | const struct sof_ipc_tplg_widget_ops *widget_ops;
|
| 440 | struct snd_soc_dapm_path *p;
|
| 441 |
|
| 442 | if (is_virtual_widget(sdev, widget, __func__))
|
| 443 | return;
|
| 444 |
|
| 445 | if (!swidget)
|
| 446 | goto sink_unprepare;
|
| 447 |
|
| 448 | if (swidget->spipe && swidget->spipe->direction_valid &&
|
| 449 | !sof_widget_in_same_direction(swidget, dir))
|
| 450 | return;
|
| 451 |
|
| 452 |
|
| 453 | if (!swidget->prepared || swidget->use_count > 0 || is_aggregated_dai(swidget))
|
| 454 | goto sink_unprepare;
|
| 455 |
|
| 456 | widget_ops = tplg_ops ? tplg_ops->widget : NULL;
|
| 457 | if (widget_ops && widget_ops[widget->id].ipc_unprepare)
|
| 458 |
|
| 459 | widget_ops[widget->id].ipc_unprepare(swidget);
|
| 460 |
|
| 461 | swidget->prepared = false;
|
| 462 |
|
| 463 | sink_unprepare:
|
| 464 |
|
| 465 | snd_soc_dapm_widget_for_each_sink_path(widget, p) {
|
| 466 | if (!widget_in_list(list, p->sink))
|
| 467 | continue;
|
| 468 |
|
| 469 | if (!p->walking && p->sink->dobj.private) {
|
| 470 | p->walking = true;
|
| 471 | sof_unprepare_widgets_in_path(sdev, p->sink, list, dir);
|
| 472 | p->walking = false;
|
| 473 | }
|
| 474 | }
|
| 475 | }
|
| 476 |
|
| 477 | static int
|
| 478 | sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget,
|
| 479 | struct snd_pcm_hw_params *fe_params,
|
| 480 | struct snd_sof_platform_stream_params *platform_params,
|
| 481 | struct snd_pcm_hw_params *pipeline_params, int dir,
|
| 482 | struct snd_soc_dapm_widget_list *list)
|
| 483 | {
|
| 484 | const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
|
| 485 | struct snd_sof_widget *swidget = widget->dobj.private;
|
| 486 | const struct sof_ipc_tplg_widget_ops *widget_ops;
|
| 487 | struct snd_soc_dapm_path *p;
|
| 488 | int ret;
|
| 489 |
|
| 490 | if (is_virtual_widget(sdev, widget, __func__))
|
| 491 | return 0;
|
| 492 |
|
| 493 | if (!swidget)
|
| 494 | goto sink_prepare;
|
| 495 |
|
| 496 | widget_ops = tplg_ops ? tplg_ops->widget : NULL;
|
| 497 | if (!widget_ops)
|
| 498 | return 0;
|
| 499 |
|
| 500 | if (swidget->spipe && swidget->spipe->direction_valid &&
|
| 501 | !sof_widget_in_same_direction(swidget, dir))
|
| 502 | return 0;
|
| 503 |
|
| 504 |
|
| 505 | if (!widget_ops[widget->id].ipc_prepare || is_aggregated_dai(swidget))
|
| 506 | goto sink_prepare;
|
| 507 |
|
| 508 |
|
| 509 | ret = widget_ops[widget->id].ipc_prepare(swidget, fe_params, platform_params,
|
| 510 | pipeline_params, dir);
|
| 511 | if (ret < 0) {
|
| 512 | dev_err(sdev->dev, "failed to prepare widget %s\n", widget->name);
|
| 513 | return ret;
|
| 514 | }
|
| 515 |
|
| 516 | swidget->prepared = true;
|
| 517 |
|
| 518 | sink_prepare:
|
| 519 |
|
| 520 | snd_soc_dapm_widget_for_each_sink_path(widget, p) {
|
| 521 | if (!widget_in_list(list, p->sink))
|
| 522 | continue;
|
| 523 |
|
| 524 | if (!p->walking && p->sink->dobj.private) {
|
| 525 | p->walking = true;
|
| 526 | ret = sof_prepare_widgets_in_path(sdev, p->sink, fe_params,
|
| 527 | platform_params, pipeline_params, dir,
|
| 528 | list);
|
| 529 | p->walking = false;
|
| 530 | if (ret < 0) {
|
| 531 |
|
| 532 | if (widget_ops[widget->id].ipc_unprepare &&
|
| 533 | swidget && swidget->prepared && swidget->use_count == 0) {
|
| 534 | widget_ops[widget->id].ipc_unprepare(swidget);
|
| 535 | swidget->prepared = false;
|
| 536 | }
|
| 537 | return ret;
|
| 538 | }
|
| 539 | }
|
| 540 | }
|
| 541 |
|
| 542 | return 0;
|
| 543 | }
|
| 544 |
|
| 545 | |
| 546 | * free all widgets in the sink path starting from the source widget
|
| 547 | * (DAI type for capture, AIF type for playback)
|
| 548 |
|
| 549 | static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget,
|
| 550 | int dir, struct snd_sof_pcm *spcm)
|
| 551 | {
|
| 552 | struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list;
|
| 553 | struct snd_sof_widget *swidget = widget->dobj.private;
|
| 554 | struct snd_soc_dapm_path *p;
|
| 555 | int err;
|
| 556 | int ret = 0;
|
| 557 |
|
| 558 | if (is_virtual_widget(sdev, widget, __func__))
|
| 559 | return 0;
|
| 560 |
|
| 561 | if (!swidget)
|
| 562 | goto sink_free;
|
| 563 |
|
| 564 | if (swidget->spipe && swidget->spipe->direction_valid &&
|
| 565 | !sof_widget_in_same_direction(swidget, dir))
|
| 566 | return 0;
|
| 567 |
|
| 568 |
|
| 569 | if (is_aggregated_dai(swidget))
|
| 570 | goto sink_free;
|
| 571 |
|
| 572 | err = sof_widget_free(sdev, widget->dobj.private);
|
| 573 | if (err < 0)
|
| 574 | ret = err;
|
| 575 | sink_free:
|
| 576 |
|
| 577 | snd_soc_dapm_widget_for_each_sink_path(widget, p) {
|
| 578 | if (!p->walking) {
|
| 579 | if (!widget_in_list(list, p->sink))
|
| 580 | continue;
|
| 581 |
|
| 582 | p->walking = true;
|
| 583 |
|
| 584 | err = sof_free_widgets_in_path(sdev, p->sink, dir, spcm);
|
| 585 | if (err < 0)
|
| 586 | ret = err;
|
| 587 | p->walking = false;
|
| 588 | }
|
| 589 | }
|
| 590 |
|
| 591 | return ret;
|
| 592 | }
|
| 593 |
|
| 594 | |
| 595 | * set up all widgets in the sink path starting from the source widget
|
| 596 | * (DAI type for capture, AIF type for playback).
|
| 597 | * The error path in this function ensures that all successfully set up widgets getting freed.
|
| 598 |
|
| 599 | static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget,
|
| 600 | int dir, struct snd_sof_pcm *spcm)
|
| 601 | {
|
| 602 | struct snd_sof_pcm_stream_pipeline_list *pipeline_list = &spcm->stream[dir].pipeline_list;
|
| 603 | struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list;
|
| 604 | struct snd_sof_widget *swidget = widget->dobj.private;
|
| 605 | struct snd_sof_pipeline *spipe;
|
| 606 | struct snd_soc_dapm_path *p;
|
| 607 | int ret;
|
| 608 |
|
| 609 | if (is_virtual_widget(sdev, widget, __func__))
|
| 610 | return 0;
|
| 611 |
|
| 612 | if (swidget) {
|
| 613 | int i;
|
| 614 |
|
| 615 | if (swidget->spipe && swidget->spipe->direction_valid &&
|
| 616 | !sof_widget_in_same_direction(swidget, dir))
|
| 617 | return 0;
|
| 618 |
|
| 619 |
|
| 620 | if (is_aggregated_dai(swidget))
|
| 621 | goto sink_setup;
|
| 622 |
|
| 623 | ret = sof_widget_setup(sdev, swidget);
|
| 624 | if (ret < 0)
|
| 625 | return ret;
|
| 626 |
|
| 627 |
|
| 628 | if (!pipeline_list->pipelines)
|
| 629 | goto sink_setup;
|
| 630 |
|
| 631 | |
| 632 | * Add the widget's pipe_widget to the list of pipelines to be triggered if not
|
| 633 | * already in the list. This will result in the pipelines getting added in the
|
| 634 | * order source to sink.
|
| 635 |
|
| 636 | for (i = 0; i < pipeline_list->count; i++) {
|
| 637 | spipe = pipeline_list->pipelines[i];
|
| 638 | if (spipe == swidget->spipe)
|
| 639 | break;
|
| 640 | }
|
| 641 |
|
| 642 | if (i == pipeline_list->count) {
|
| 643 | pipeline_list->count++;
|
| 644 | pipeline_list->pipelines[i] = swidget->spipe;
|
| 645 | }
|
| 646 | }
|
| 647 |
|
| 648 | sink_setup:
|
| 649 | snd_soc_dapm_widget_for_each_sink_path(widget, p) {
|
| 650 | if (!p->walking) {
|
| 651 | if (!widget_in_list(list, p->sink))
|
| 652 | continue;
|
| 653 |
|
| 654 | p->walking = true;
|
| 655 |
|
| 656 | ret = sof_set_up_widgets_in_path(sdev, p->sink, dir, spcm);
|
| 657 | p->walking = false;
|
| 658 | if (ret < 0) {
|
| 659 | if (swidget)
|
| 660 | sof_widget_free(sdev, swidget);
|
| 661 | return ret;
|
| 662 | }
|
| 663 | }
|
| 664 | }
|
| 665 |
|
| 666 | return 0;
|
| 667 | }
|
| 668 |
|
| 669 | static int
|
| 670 | sof_walk_widgets_in_order(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm,
|
| 671 | struct snd_pcm_hw_params *fe_params,
|
| 672 | struct snd_sof_platform_stream_params *platform_params, int dir,
|
| 673 | enum sof_widget_op op)
|
| 674 | {
|
| 675 | struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list;
|
| 676 | struct snd_soc_dapm_widget *widget;
|
| 677 | char *str;
|
| 678 | int ret = 0;
|
| 679 | int i;
|
| 680 |
|
| 681 | if (!list)
|
| 682 | return 0;
|
| 683 |
|
| 684 | for_each_dapm_widgets(list, i, widget) {
|
| 685 |
|
| 686 | if (dir == SNDRV_PCM_STREAM_PLAYBACK && widget->id != snd_soc_dapm_aif_in)
|
| 687 | continue;
|
| 688 |
|
| 689 |
|
| 690 | if (dir == SNDRV_PCM_STREAM_CAPTURE && widget->id != snd_soc_dapm_dai_out &&
|
| 691 | widget->id != snd_soc_dapm_output)
|
| 692 | continue;
|
| 693 |
|
| 694 | switch (op) {
|
| 695 | case SOF_WIDGET_SETUP:
|
| 696 | ret = sof_set_up_widgets_in_path(sdev, widget, dir, spcm);
|
| 697 | str = "set up";
|
| 698 | break;
|
| 699 | case SOF_WIDGET_FREE:
|
| 700 | ret = sof_free_widgets_in_path(sdev, widget, dir, spcm);
|
| 701 | str = "free";
|
| 702 | break;
|
| 703 | case SOF_WIDGET_PREPARE:
|
| 704 | {
|
| 705 | struct snd_pcm_hw_params pipeline_params;
|
| 706 |
|
| 707 | str = "prepare";
|
| 708 | |
| 709 | * When walking the list of connected widgets, the pipeline_params for each
|
| 710 | * widget is modified by the source widget in the path. Use a local
|
| 711 | * copy of the runtime params as the pipeline_params so that the runtime
|
| 712 | * params does not get overwritten.
|
| 713 |
|
| 714 | memcpy(&pipeline_params, fe_params, sizeof(*fe_params));
|
| 715 |
|
| 716 | ret = sof_prepare_widgets_in_path(sdev, widget, fe_params, platform_params,
|
| 717 | &pipeline_params, dir, list);
|
| 718 | break;
|
| 719 | }
|
| 720 | case SOF_WIDGET_UNPREPARE:
|
| 721 | sof_unprepare_widgets_in_path(sdev, widget, list, dir);
|
| 722 | break;
|
| 723 | default:
|
| 724 | dev_err(sdev->dev, "Invalid widget op %d\n", op);
|
| 725 | return -EINVAL;
|
| 726 | }
|
| 727 | if (ret < 0) {
|
| 728 | dev_err(sdev->dev, "Failed to %s connected widgets\n", str);
|
| 729 | return ret;
|
| 730 | }
|
| 731 | }
|
| 732 |
|
| 733 | return 0;
|
| 734 | }
|
| 735 |
|
| 736 | int sof_widget_list_prepare(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm,
|
| 737 | struct snd_pcm_hw_params *fe_params,
|
| 738 | struct snd_sof_platform_stream_params *platform_params,
|
| 739 | int dir)
|
| 740 | {
|
| 741 | |
| 742 | * Prepare widgets for set up. The prepare step is used to allocate memory, assign
|
| 743 | * instance ID and pick the widget configuration based on the runtime PCM params.
|
| 744 |
|
| 745 | return sof_walk_widgets_in_order(sdev, spcm, fe_params, platform_params,
|
| 746 | dir, SOF_WIDGET_PREPARE);
|
| 747 | }
|
| 748 |
|
| 749 | void sof_widget_list_unprepare(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir)
|
| 750 | {
|
| 751 | struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list;
|
| 752 |
|
| 753 |
|
| 754 | sof_walk_widgets_in_order(sdev, spcm, NULL, NULL, dir, SOF_WIDGET_UNPREPARE);
|
| 755 |
|
| 756 | snd_soc_dapm_dai_free_widgets(&list);
|
| 757 | spcm->stream[dir].list = NULL;
|
| 758 | }
|
| 759 |
|
| 760 | int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm,
|
| 761 | struct snd_pcm_hw_params *fe_params,
|
| 762 | struct snd_sof_platform_stream_params *platform_params,
|
| 763 | int dir)
|
| 764 | {
|
| 765 | const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
|
| 766 | struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list;
|
| 767 | struct snd_soc_dapm_widget *widget;
|
| 768 | int i, ret;
|
| 769 |
|
| 770 |
|
| 771 | if (!list || spcm->setup_done[dir])
|
| 772 | return 0;
|
| 773 |
|
| 774 |
|
| 775 | ret = sof_walk_widgets_in_order(sdev, spcm, fe_params, platform_params,
|
| 776 | dir, SOF_WIDGET_SETUP);
|
| 777 | if (ret < 0) {
|
| 778 | sof_walk_widgets_in_order(sdev, spcm, fe_params, platform_params,
|
| 779 | dir, SOF_WIDGET_UNPREPARE);
|
| 780 | return ret;
|
| 781 | }
|
| 782 |
|
| 783 | |
| 784 | * error in setting pipeline connections will result in route status being reset for
|
| 785 | * routes that were successfully set up when the widgets are freed.
|
| 786 |
|
| 787 | ret = sof_setup_pipeline_connections(sdev, list, dir);
|
| 788 | if (ret < 0)
|
| 789 | goto widget_free;
|
| 790 |
|
| 791 |
|
| 792 | for_each_dapm_widgets(list, i, widget) {
|
| 793 | struct snd_sof_widget *swidget = widget->dobj.private;
|
| 794 | struct snd_sof_widget *pipe_widget;
|
| 795 | struct snd_sof_pipeline *spipe;
|
| 796 |
|
| 797 | if (!swidget || sdev->dspless_mode_selected)
|
| 798 | continue;
|
| 799 |
|
| 800 | spipe = swidget->spipe;
|
| 801 | if (!spipe) {
|
| 802 | dev_err(sdev->dev, "no pipeline found for %s\n",
|
| 803 | swidget->widget->name);
|
| 804 | ret = -EINVAL;
|
| 805 | goto widget_free;
|
| 806 | }
|
| 807 |
|
| 808 | pipe_widget = spipe->pipe_widget;
|
| 809 | if (!pipe_widget) {
|
| 810 | dev_err(sdev->dev, "error: no pipeline widget found for %s\n",
|
| 811 | swidget->widget->name);
|
| 812 | ret = -EINVAL;
|
| 813 | goto widget_free;
|
| 814 | }
|
| 815 |
|
| 816 | if (spipe->complete)
|
| 817 | continue;
|
| 818 |
|
| 819 | if (tplg_ops && tplg_ops->pipeline_complete) {
|
| 820 | spipe->complete = tplg_ops->pipeline_complete(sdev, pipe_widget);
|
| 821 | if (spipe->complete < 0) {
|
| 822 | ret = spipe->complete;
|
| 823 | goto widget_free;
|
| 824 | }
|
| 825 | }
|
| 826 | }
|
| 827 |
|
| 828 | spcm->setup_done[dir] = true;
|
| 829 |
|
| 830 | return 0;
|
| 831 |
|
| 832 | widget_free:
|
| 833 | sof_walk_widgets_in_order(sdev, spcm, fe_params, platform_params, dir,
|
| 834 | SOF_WIDGET_FREE);
|
| 835 | sof_walk_widgets_in_order(sdev, spcm, NULL, NULL, dir, SOF_WIDGET_UNPREPARE);
|
| 836 |
|
| 837 | return ret;
|
| 838 | }
|
| 839 |
|
| 840 | int sof_widget_list_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir)
|
| 841 | {
|
| 842 | struct snd_sof_pcm_stream_pipeline_list *pipeline_list = &spcm->stream[dir].pipeline_list;
|
| 843 | struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list;
|
| 844 | int ret;
|
| 845 |
|
| 846 |
|
| 847 | if (!list || !spcm->setup_done[dir])
|
| 848 | return 0;
|
| 849 |
|
| 850 |
|
| 851 | ret = sof_walk_widgets_in_order(sdev, spcm, NULL, NULL, dir, SOF_WIDGET_FREE);
|
| 852 |
|
| 853 | spcm->setup_done[dir] = false;
|
| 854 | pipeline_list->count = 0;
|
| 855 |
|
| 856 | return ret;
|
| 857 | }
|
| 858 |
|
| 859 | |
| 860 | * helper to determine if there are only D0i3 compatible
|
| 861 | * streams active
|
| 862 |
|
| 863 | bool snd_sof_dsp_only_d0i3_compatible_stream_active(struct snd_sof_dev *sdev)
|
| 864 | {
|
| 865 | struct snd_pcm_substream *substream;
|
| 866 | struct snd_sof_pcm *spcm;
|
| 867 | bool d0i3_compatible_active = false;
|
| 868 | int dir;
|
| 869 |
|
| 870 | list_for_each_entry(spcm, &sdev->pcm_list, list) {
|
| 871 | for_each_pcm_streams(dir) {
|
| 872 | substream = spcm->stream[dir].substream;
|
| 873 | if (!substream || !substream->runtime)
|
| 874 | continue;
|
| 875 |
|
| 876 | |
| 877 | * substream->runtime being not NULL indicates
|
| 878 | * that the stream is open. No need to check the
|
| 879 | * stream state.
|
| 880 |
|
| 881 | if (!spcm->stream[dir].d0i3_compatible)
|
| 882 | return false;
|
| 883 |
|
| 884 | d0i3_compatible_active = true;
|
| 885 | }
|
| 886 | }
|
| 887 |
|
| 888 | return d0i3_compatible_active;
|
| 889 | }
|
| 890 | EXPORT_SYMBOL(snd_sof_dsp_only_d0i3_compatible_stream_active);
|
| 891 |
|
| 892 | bool snd_sof_stream_suspend_ignored(struct snd_sof_dev *sdev)
|
| 893 | {
|
| 894 | struct snd_sof_pcm *spcm;
|
| 895 |
|
| 896 | list_for_each_entry(spcm, &sdev->pcm_list, list) {
|
| 897 | if (spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].suspend_ignored ||
|
| 898 | spcm->stream[SNDRV_PCM_STREAM_CAPTURE].suspend_ignored)
|
| 899 | return true;
|
| 900 | }
|
| 901 |
|
| 902 | return false;
|
| 903 | }
|
| 904 |
|
| 905 | |
| 906 | * Generic object lookup APIs.
|
| 907 |
|
| 908 |
|
| 909 | struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_soc_component *scomp,
|
| 910 | const char *name)
|
| 911 | {
|
| 912 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
|
| 913 | struct snd_sof_pcm *spcm;
|
| 914 |
|
| 915 | list_for_each_entry(spcm, &sdev->pcm_list, list) {
|
| 916 |
|
| 917 | if (strcmp(spcm->pcm.dai_name, name) == 0)
|
| 918 | return spcm;
|
| 919 |
|
| 920 |
|
| 921 | if (*spcm->pcm.caps[0].name &&
|
| 922 | !strcmp(spcm->pcm.caps[0].name, name))
|
| 923 | return spcm;
|
| 924 |
|
| 925 |
|
| 926 | if (*spcm->pcm.caps[1].name &&
|
| 927 | !strcmp(spcm->pcm.caps[1].name, name))
|
| 928 | return spcm;
|
| 929 | }
|
| 930 |
|
| 931 | return NULL;
|
| 932 | }
|
| 933 |
|
| 934 | struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_soc_component *scomp,
|
| 935 | unsigned int comp_id,
|
| 936 | int *direction)
|
| 937 | {
|
| 938 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
|
| 939 | struct snd_sof_pcm *spcm;
|
| 940 | int dir;
|
| 941 |
|
| 942 | list_for_each_entry(spcm, &sdev->pcm_list, list) {
|
| 943 | for_each_pcm_streams(dir) {
|
| 944 | if (spcm->stream[dir].comp_id == comp_id) {
|
| 945 | *direction = dir;
|
| 946 | return spcm;
|
| 947 | }
|
| 948 | }
|
| 949 | }
|
| 950 |
|
| 951 | return NULL;
|
| 952 | }
|
| 953 |
|
| 954 | struct snd_sof_widget *snd_sof_find_swidget(struct snd_soc_component *scomp,
|
| 955 | const char *name)
|
| 956 | {
|
| 957 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
|
| 958 | struct snd_sof_widget *swidget;
|
| 959 |
|
| 960 | list_for_each_entry(swidget, &sdev->widget_list, list) {
|
| 961 | if (strcmp(name, swidget->widget->name) == 0)
|
| 962 | return swidget;
|
| 963 | }
|
| 964 |
|
| 965 | return NULL;
|
| 966 | }
|
| 967 |
|
| 968 |
|
| 969 | struct snd_sof_widget *
|
| 970 | snd_sof_find_swidget_sname(struct snd_soc_component *scomp,
|
| 971 | const char *pcm_name, int dir)
|
| 972 | {
|
| 973 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
|
| 974 | struct snd_sof_widget *swidget;
|
| 975 | enum snd_soc_dapm_type type;
|
| 976 |
|
| 977 | if (dir == SNDRV_PCM_STREAM_PLAYBACK)
|
| 978 | type = snd_soc_dapm_aif_in;
|
| 979 | else
|
| 980 | type = snd_soc_dapm_aif_out;
|
| 981 |
|
| 982 | list_for_each_entry(swidget, &sdev->widget_list, list) {
|
| 983 | if (!strcmp(pcm_name, swidget->widget->sname) &&
|
| 984 | swidget->id == type)
|
| 985 | return swidget;
|
| 986 | }
|
| 987 |
|
| 988 | return NULL;
|
| 989 | }
|
| 990 |
|
| 991 | struct snd_sof_dai *snd_sof_find_dai(struct snd_soc_component *scomp,
|
| 992 | const char *name)
|
| 993 | {
|
| 994 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
|
| 995 | struct snd_sof_dai *dai;
|
| 996 |
|
| 997 | list_for_each_entry(dai, &sdev->dai_list, list) {
|
| 998 | if (dai->name && (strcmp(name, dai->name) == 0))
|
| 999 | return dai;
|
| 1000 | }
|
| 1001 |
|
| 1002 | return NULL;
|
| 1003 | }
|
| 1004 |
|
| 1005 | static int sof_dai_get_param(struct snd_soc_pcm_runtime *rtd, int param_type)
|
| 1006 | {
|
| 1007 | struct snd_soc_component *component =
|
| 1008 | snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);
|
| 1009 | struct snd_sof_dai *dai =
|
| 1010 | snd_sof_find_dai(component, (char *)rtd->dai_link->name);
|
| 1011 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
|
| 1012 | const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
|
| 1013 |
|
| 1014 |
|
| 1015 | if (!dai)
|
| 1016 | return 0;
|
| 1017 |
|
| 1018 | if (tplg_ops && tplg_ops->dai_get_param)
|
| 1019 | return tplg_ops->dai_get_param(sdev, dai, param_type);
|
| 1020 |
|
| 1021 | return 0;
|
| 1022 | }
|
| 1023 |
|
| 1024 | |
| 1025 | * Helper to get SSP MCLK from a pcm_runtime.
|
| 1026 | * Return 0 if not exist.
|
| 1027 |
|
| 1028 | int sof_dai_get_mclk(struct snd_soc_pcm_runtime *rtd)
|
| 1029 | {
|
| 1030 | return sof_dai_get_param(rtd, SOF_DAI_PARAM_INTEL_SSP_MCLK);
|
| 1031 | }
|
| 1032 | EXPORT_SYMBOL(sof_dai_get_mclk);
|
| 1033 |
|
| 1034 | |
| 1035 | * Helper to get SSP BCLK from a pcm_runtime.
|
| 1036 | * Return 0 if not exist.
|
| 1037 |
|
| 1038 | int sof_dai_get_bclk(struct snd_soc_pcm_runtime *rtd)
|
| 1039 | {
|
| 1040 | return sof_dai_get_param(rtd, SOF_DAI_PARAM_INTEL_SSP_BCLK);
|
| 1041 | }
|
| 1042 | EXPORT_SYMBOL(sof_dai_get_bclk);
|
| 1043 |
|
| 1044 | |
| 1045 | * Helper to get SSP TDM slot number from a pcm_runtime.
|
| 1046 | * Return 0 if not exist.
|
| 1047 |
|
| 1048 | int sof_dai_get_tdm_slots(struct snd_soc_pcm_runtime *rtd)
|
| 1049 | {
|
| 1050 | return sof_dai_get_param(rtd, SOF_DAI_PARAM_INTEL_SSP_TDM_SLOTS);
|
| 1051 | }
|
| 1052 | EXPORT_SYMBOL(sof_dai_get_tdm_slots);
|