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) 2018 Intel Corporation. All rights reserved. 7 // 8 // Authors: Keyon Jie <yang.jie@linux.intel.com> 9 // 10 11 #include <sound/pcm_params.h> 12 #include <sound/hdaudio_ext.h> 13 #include "../sof-priv.h" 14 #include "../sof-audio.h" 15 #include "hda.h" 16 17 #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) 18 19 #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) 20 #include "../sof-probes.h" 21 #endif 22 23 struct hda_pipe_params { 24 u8 host_dma_id; 25 u8 link_dma_id; 26 u32 ch; 27 u32 s_freq; 28 u32 s_fmt; 29 u8 linktype; 30 snd_pcm_format_t format; 31 int link_index; 32 int stream; 33 unsigned int host_bps; 34 unsigned int link_bps; 35 }; 36 37 /* 38 * This function checks if the host dma channel corresponding 39 * to the link DMA stream_tag argument is assigned to one 40 * of the FEs connected to the BE DAI. 41 */ 42 static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd, 43 int dir, int stream_tag) 44 { 45 struct snd_pcm_substream *fe_substream; 46 struct hdac_stream *fe_hstream; 47 struct snd_soc_dpcm *dpcm; 48 49 for_each_dpcm_fe(rtd, dir, dpcm) { 50 fe_substream = snd_soc_dpcm_get_substream(dpcm->fe, dir); 51 fe_hstream = fe_substream->runtime->private_data; 52 if (fe_hstream->stream_tag == stream_tag) 53 return true; 54 } 55 56 return false; 57 } 58 59 static struct hdac_ext_stream * 60 hda_link_stream_assign(struct hdac_bus *bus, 61 struct snd_pcm_substream *substream) 62 { 63 struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 64 struct sof_intel_hda_stream *hda_stream; 65 struct hdac_ext_stream *res = NULL; 66 struct hdac_stream *stream = NULL; 67 68 int stream_dir = substream->stream; 69 70 if (!bus->ppcap) { 71 dev_err(bus->dev, "stream type not supported\n"); 72 return NULL; 73 } 74 75 spin_lock_irq(&bus->reg_lock); 76 list_for_each_entry(stream, &bus->stream_list, list) { 77 struct hdac_ext_stream *hstream = 78 stream_to_hdac_ext_stream(stream); 79 if (stream->direction != substream->stream) 80 continue; 81 82 hda_stream = hstream_to_sof_hda_stream(hstream); 83 84 /* check if link is available */ 85 if (!hstream->link_locked) { 86 if (stream->opened) { 87 /* 88 * check if the stream tag matches the stream 89 * tag of one of the connected FEs 90 */ 91 if (hda_check_fes(rtd, stream_dir, 92 stream->stream_tag)) { 93 res = hstream; 94 break; 95 } 96 } else { 97 res = hstream; 98 99 /* 100 * This must be a hostless stream. 101 * So reserve the host DMA channel. 102 */ 103 hda_stream->host_reserved = 1; 104 break; 105 } 106 } 107 } 108 109 if (res) { 110 /* 111 * Decouple host and link DMA. The decoupled flag 112 * is updated in snd_hdac_ext_stream_decouple(). 113 */ 114 if (!res->decoupled) 115 snd_hdac_ext_stream_decouple_locked(bus, res, true); 116 117 res->link_locked = 1; 118 res->link_substream = substream; 119 } 120 spin_unlock_irq(&bus->reg_lock); 121 122 return res; 123 } 124 125 static int hda_link_dma_params(struct hdac_ext_stream *stream, 126 struct hda_pipe_params *params) 127 { 128 struct hdac_stream *hstream = &stream->hstream; 129 unsigned char stream_tag = hstream->stream_tag; 130 struct hdac_bus *bus = hstream->bus; 131 struct hdac_ext_link *link; 132 unsigned int format_val; 133 134 snd_hdac_ext_stream_decouple(bus, stream, true); 135 snd_hdac_ext_link_stream_reset(stream); 136 137 format_val = snd_hdac_calc_stream_format(params->s_freq, params->ch, 138 params->format, 139 params->link_bps, 0); 140 141 dev_dbg(bus->dev, "format_val=%d, rate=%d, ch=%d, format=%d\n", 142 format_val, params->s_freq, params->ch, params->format); 143 144 snd_hdac_ext_link_stream_setup(stream, format_val); 145 146 if (stream->hstream.direction == SNDRV_PCM_STREAM_PLAYBACK) { 147 list_for_each_entry(link, &bus->hlink_list, list) { 148 if (link->index == params->link_index) 149 snd_hdac_ext_link_set_stream_id(link, 150 stream_tag); 151 } 152 } 153 154 stream->link_prepared = 1; 155 156 return 0; 157 } 158 159 /* Update config for the DAI widget */ 160 static struct sof_ipc_dai_config *hda_dai_update_config(struct snd_soc_dapm_widget *w, 161 int channel) 162 { 163 struct snd_sof_widget *swidget = w->dobj.private; 164 struct sof_ipc_dai_config *config; 165 struct snd_sof_dai *sof_dai; 166 167 if (!swidget) 168 return NULL; 169 170 sof_dai = swidget->private; 171 172 if (!sof_dai || !sof_dai->dai_config) { 173 dev_err(swidget->scomp->dev, "error: No config for DAI %s\n", w->name); 174 return NULL; 175 } 176 177 config = &sof_dai->dai_config[sof_dai->current_config]; 178 179 /* update config with stream tag */ 180 config->hda.link_dma_ch = channel; 181 182 return config; 183 } 184 185 static int hda_link_config_ipc(struct sof_intel_hda_stream *hda_stream, 186 struct snd_soc_dapm_widget *w, int channel) 187 { 188 struct snd_sof_dev *sdev = hda_stream->sdev; 189 struct sof_ipc_dai_config *config; 190 struct sof_ipc_reply reply; 191 192 config = hda_dai_update_config(w, channel); 193 if (!config) { 194 dev_err(sdev->dev, "error: no config for DAI %s\n", w->name); 195 return -ENOENT; 196 } 197 198 /* send DAI_CONFIG IPC */ 199 return sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size, 200 &reply, sizeof(reply)); 201 } 202 203 static int hda_link_dai_widget_update(struct sof_intel_hda_stream *hda_stream, 204 struct snd_soc_dapm_widget *w, 205 int channel, bool widget_setup) 206 { 207 struct snd_sof_dev *sdev = hda_stream->sdev; 208 struct sof_ipc_dai_config *config; 209 210 config = hda_dai_update_config(w, channel); 211 if (!config) { 212 dev_err(sdev->dev, "error: no config for DAI %s\n", w->name); 213 return -ENOENT; 214 } 215 216 /* set up/free DAI widget and send DAI_CONFIG IPC */ 217 if (widget_setup) 218 return hda_ctrl_dai_widget_setup(w); 219 220 return hda_ctrl_dai_widget_free(w); 221 } 222 223 static int hda_link_hw_params(struct snd_pcm_substream *substream, 224 struct snd_pcm_hw_params *params, 225 struct snd_soc_dai *dai) 226 { 227 struct hdac_stream *hstream = substream->runtime->private_data; 228 struct hdac_bus *bus = hstream->bus; 229 struct hdac_ext_stream *link_dev; 230 struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 231 struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); 232 struct sof_intel_hda_stream *hda_stream; 233 struct hda_pipe_params p_params = {0}; 234 struct snd_soc_dapm_widget *w; 235 struct hdac_ext_link *link; 236 int stream_tag; 237 int ret; 238 239 /* get stored dma data if resuming from system suspend */ 240 link_dev = snd_soc_dai_get_dma_data(dai, substream); 241 if (!link_dev) { 242 link_dev = hda_link_stream_assign(bus, substream); 243 if (!link_dev) 244 return -EBUSY; 245 246 snd_soc_dai_set_dma_data(dai, substream, (void *)link_dev); 247 } 248 249 stream_tag = hdac_stream(link_dev)->stream_tag; 250 251 hda_stream = hstream_to_sof_hda_stream(link_dev); 252 253 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 254 w = dai->playback_widget; 255 else 256 w = dai->capture_widget; 257 258 /* set up the DAI widget and send the DAI_CONFIG with the new tag */ 259 ret = hda_link_dai_widget_update(hda_stream, w, stream_tag - 1, true); 260 if (ret < 0) 261 return ret; 262 263 link = snd_hdac_ext_bus_get_link(bus, codec_dai->component->name); 264 if (!link) 265 return -EINVAL; 266 267 /* set the stream tag in the codec dai dma params */ 268 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 269 snd_soc_dai_set_tdm_slot(codec_dai, stream_tag, 0, 0, 0); 270 else 271 snd_soc_dai_set_tdm_slot(codec_dai, 0, stream_tag, 0, 0); 272 273 p_params.s_fmt = snd_pcm_format_width(params_format(params)); 274 p_params.ch = params_channels(params); 275 p_params.s_freq = params_rate(params); 276 p_params.stream = substream->stream; 277 p_params.link_dma_id = stream_tag - 1; 278 p_params.link_index = link->index; 279 p_params.format = params_format(params); 280 281 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 282 p_params.link_bps = codec_dai->driver->playback.sig_bits; 283 else 284 p_params.link_bps = codec_dai->driver->capture.sig_bits; 285 286 return hda_link_dma_params(link_dev, &p_params); 287 } 288 289 static int hda_link_pcm_prepare(struct snd_pcm_substream *substream, 290 struct snd_soc_dai *dai) 291 { 292 struct hdac_ext_stream *link_dev = 293 snd_soc_dai_get_dma_data(dai, substream); 294 struct snd_sof_dev *sdev = 295 snd_soc_component_get_drvdata(dai->component); 296 struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 297 int stream = substream->stream; 298 299 if (link_dev->link_prepared) 300 return 0; 301 302 dev_dbg(sdev->dev, "hda: prepare stream dir %d\n", substream->stream); 303 304 return hda_link_hw_params(substream, &rtd->dpcm[stream].hw_params, 305 dai); 306 } 307 308 static int hda_link_pcm_trigger(struct snd_pcm_substream *substream, 309 int cmd, struct snd_soc_dai *dai) 310 { 311 struct hdac_ext_stream *link_dev = 312 snd_soc_dai_get_dma_data(dai, substream); 313 struct sof_intel_hda_stream *hda_stream; 314 struct snd_soc_pcm_runtime *rtd; 315 struct snd_soc_dapm_widget *w; 316 struct hdac_ext_link *link; 317 struct hdac_stream *hstream; 318 struct hdac_bus *bus; 319 int stream_tag; 320 int ret; 321 322 hstream = substream->runtime->private_data; 323 bus = hstream->bus; 324 rtd = asoc_substream_to_rtd(substream); 325 326 link = snd_hdac_ext_bus_get_link(bus, asoc_rtd_to_codec(rtd, 0)->component->name); 327 if (!link) 328 return -EINVAL; 329 330 hda_stream = hstream_to_sof_hda_stream(link_dev); 331 332 dev_dbg(dai->dev, "In %s cmd=%d\n", __func__, cmd); 333 switch (cmd) { 334 case SNDRV_PCM_TRIGGER_RESUME: 335 /* set up hw_params */ 336 ret = hda_link_pcm_prepare(substream, dai); 337 if (ret < 0) { 338 dev_err(dai->dev, 339 "error: setting up hw_params during resume\n"); 340 return ret; 341 } 342 343 fallthrough; 344 case SNDRV_PCM_TRIGGER_START: 345 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 346 snd_hdac_ext_link_stream_start(link_dev); 347 break; 348 case SNDRV_PCM_TRIGGER_SUSPEND: 349 case SNDRV_PCM_TRIGGER_STOP: 350 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 351 w = dai->playback_widget; 352 else 353 w = dai->capture_widget; 354 355 /* 356 * clear link DMA channel. It will be assigned when 357 * hw_params is set up again after resume. 358 */ 359 ret = hda_link_config_ipc(hda_stream, w, DMA_CHAN_INVALID); 360 if (ret < 0) 361 return ret; 362 363 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 364 stream_tag = hdac_stream(link_dev)->stream_tag; 365 snd_hdac_ext_link_clear_stream_id(link, stream_tag); 366 } 367 368 link_dev->link_prepared = 0; 369 370 fallthrough; 371 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 372 snd_hdac_ext_link_stream_clear(link_dev); 373 break; 374 default: 375 return -EINVAL; 376 } 377 return 0; 378 } 379 380 static int hda_link_hw_free(struct snd_pcm_substream *substream, 381 struct snd_soc_dai *dai) 382 { 383 unsigned int stream_tag; 384 struct sof_intel_hda_stream *hda_stream; 385 struct hdac_bus *bus; 386 struct hdac_ext_link *link; 387 struct hdac_stream *hstream; 388 struct snd_soc_pcm_runtime *rtd; 389 struct hdac_ext_stream *link_dev; 390 struct snd_soc_dapm_widget *w; 391 int ret; 392 393 hstream = substream->runtime->private_data; 394 bus = hstream->bus; 395 rtd = asoc_substream_to_rtd(substream); 396 link_dev = snd_soc_dai_get_dma_data(dai, substream); 397 398 if (!link_dev) { 399 dev_dbg(dai->dev, 400 "%s: link_dev is not assigned\n", __func__); 401 return -EINVAL; 402 } 403 404 hda_stream = hstream_to_sof_hda_stream(link_dev); 405 406 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 407 w = dai->playback_widget; 408 else 409 w = dai->capture_widget; 410 411 /* free the link DMA channel in the FW and the DAI widget */ 412 ret = hda_link_dai_widget_update(hda_stream, w, DMA_CHAN_INVALID, false); 413 if (ret < 0) 414 return ret; 415 416 link = snd_hdac_ext_bus_get_link(bus, asoc_rtd_to_codec(rtd, 0)->component->name); 417 if (!link) 418 return -EINVAL; 419 420 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 421 stream_tag = hdac_stream(link_dev)->stream_tag; 422 snd_hdac_ext_link_clear_stream_id(link, stream_tag); 423 } 424 425 snd_soc_dai_set_dma_data(dai, substream, NULL); 426 snd_hdac_ext_stream_release(link_dev, HDAC_EXT_STREAM_TYPE_LINK); 427 link_dev->link_prepared = 0; 428 429 /* free the host DMA channel reserved by hostless streams */ 430 hda_stream->host_reserved = 0; 431 432 return 0; 433 } 434 435 static const struct snd_soc_dai_ops hda_link_dai_ops = { 436 .hw_params = hda_link_hw_params, 437 .hw_free = hda_link_hw_free, 438 .trigger = hda_link_pcm_trigger, 439 .prepare = hda_link_pcm_prepare, 440 }; 441 442 #endif 443 444 /* only one flag used so far to harden hw_params/hw_free/trigger/prepare */ 445 struct ssp_dai_dma_data { 446 bool setup; 447 }; 448 449 static int ssp_dai_setup_or_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai, 450 bool setup) 451 { 452 struct snd_soc_component *component; 453 struct snd_sof_widget *swidget; 454 struct snd_soc_dapm_widget *w; 455 struct sof_ipc_fw_version *v; 456 struct snd_sof_dev *sdev; 457 458 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 459 w = dai->playback_widget; 460 else 461 w = dai->capture_widget; 462 463 swidget = w->dobj.private; 464 component = swidget->scomp; 465 sdev = snd_soc_component_get_drvdata(component); 466 v = &sdev->fw_ready.version; 467 468 /* DAI_CONFIG IPC during hw_params is not supported in older firmware */ 469 if (v->abi_version < SOF_ABI_VER(3, 18, 0)) 470 return 0; 471 472 if (setup) 473 return hda_ctrl_dai_widget_setup(w); 474 475 return hda_ctrl_dai_widget_free(w); 476 } 477 478 static int ssp_dai_startup(struct snd_pcm_substream *substream, 479 struct snd_soc_dai *dai) 480 { 481 struct ssp_dai_dma_data *dma_data; 482 483 dma_data = kzalloc(sizeof(*dma_data), GFP_KERNEL); 484 if (!dma_data) 485 return -ENOMEM; 486 487 snd_soc_dai_set_dma_data(dai, substream, dma_data); 488 489 return 0; 490 } 491 492 static int ssp_dai_setup(struct snd_pcm_substream *substream, 493 struct snd_soc_dai *dai, 494 bool setup) 495 { 496 struct ssp_dai_dma_data *dma_data; 497 int ret = 0; 498 499 dma_data = snd_soc_dai_get_dma_data(dai, substream); 500 if (!dma_data) { 501 dev_err(dai->dev, "%s: failed to get dma_data\n", __func__); 502 return -EIO; 503 } 504 505 if (dma_data->setup != setup) { 506 ret = ssp_dai_setup_or_free(substream, dai, setup); 507 if (!ret) 508 dma_data->setup = setup; 509 } 510 return ret; 511 } 512 513 static int ssp_dai_hw_params(struct snd_pcm_substream *substream, 514 struct snd_pcm_hw_params *params, 515 struct snd_soc_dai *dai) 516 { 517 /* params are ignored for now */ 518 return ssp_dai_setup(substream, dai, true); 519 } 520 521 static int ssp_dai_prepare(struct snd_pcm_substream *substream, 522 struct snd_soc_dai *dai) 523 { 524 /* 525 * the SSP will only be reconfigured during resume operations and 526 * not in case of xruns 527 */ 528 return ssp_dai_setup(substream, dai, true); 529 } 530 531 static int ssp_dai_trigger(struct snd_pcm_substream *substream, 532 int cmd, struct snd_soc_dai *dai) 533 { 534 if (cmd != SNDRV_PCM_TRIGGER_SUSPEND) 535 return 0; 536 537 return ssp_dai_setup(substream, dai, false); 538 } 539 540 static int ssp_dai_hw_free(struct snd_pcm_substream *substream, 541 struct snd_soc_dai *dai) 542 { 543 return ssp_dai_setup(substream, dai, false); 544 } 545 546 static void ssp_dai_shutdown(struct snd_pcm_substream *substream, 547 struct snd_soc_dai *dai) 548 { 549 struct ssp_dai_dma_data *dma_data; 550 551 dma_data = snd_soc_dai_get_dma_data(dai, substream); 552 if (!dma_data) { 553 dev_err(dai->dev, "%s: failed to get dma_data\n", __func__); 554 return; 555 } 556 snd_soc_dai_set_dma_data(dai, substream, NULL); 557 kfree(dma_data); 558 } 559 560 static const struct snd_soc_dai_ops ssp_dai_ops = { 561 .startup = ssp_dai_startup, 562 .hw_params = ssp_dai_hw_params, 563 .prepare = ssp_dai_prepare, 564 .trigger = ssp_dai_trigger, 565 .hw_free = ssp_dai_hw_free, 566 .shutdown = ssp_dai_shutdown, 567 }; 568 569 /* 570 * common dai driver for skl+ platforms. 571 * some products who use this DAI array only physically have a subset of 572 * the DAIs, but no harm is done here by adding the whole set. 573 */ 574 struct snd_soc_dai_driver skl_dai[] = { 575 { 576 .name = "SSP0 Pin", 577 .ops = &ssp_dai_ops, 578 .playback = { 579 .channels_min = 1, 580 .channels_max = 8, 581 }, 582 .capture = { 583 .channels_min = 1, 584 .channels_max = 8, 585 }, 586 }, 587 { 588 .name = "SSP1 Pin", 589 .ops = &ssp_dai_ops, 590 .playback = { 591 .channels_min = 1, 592 .channels_max = 8, 593 }, 594 .capture = { 595 .channels_min = 1, 596 .channels_max = 8, 597 }, 598 }, 599 { 600 .name = "SSP2 Pin", 601 .ops = &ssp_dai_ops, 602 .playback = { 603 .channels_min = 1, 604 .channels_max = 8, 605 }, 606 .capture = { 607 .channels_min = 1, 608 .channels_max = 8, 609 }, 610 }, 611 { 612 .name = "SSP3 Pin", 613 .ops = &ssp_dai_ops, 614 .playback = { 615 .channels_min = 1, 616 .channels_max = 8, 617 }, 618 .capture = { 619 .channels_min = 1, 620 .channels_max = 8, 621 }, 622 }, 623 { 624 .name = "SSP4 Pin", 625 .ops = &ssp_dai_ops, 626 .playback = { 627 .channels_min = 1, 628 .channels_max = 8, 629 }, 630 .capture = { 631 .channels_min = 1, 632 .channels_max = 8, 633 }, 634 }, 635 { 636 .name = "SSP5 Pin", 637 .ops = &ssp_dai_ops, 638 .playback = { 639 .channels_min = 1, 640 .channels_max = 8, 641 }, 642 .capture = { 643 .channels_min = 1, 644 .channels_max = 8, 645 }, 646 }, 647 { 648 .name = "DMIC01 Pin", 649 .capture = { 650 .channels_min = 1, 651 .channels_max = 4, 652 }, 653 }, 654 { 655 .name = "DMIC16k Pin", 656 .capture = { 657 .channels_min = 1, 658 .channels_max = 4, 659 }, 660 }, 661 #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) 662 { 663 .name = "iDisp1 Pin", 664 .ops = &hda_link_dai_ops, 665 .playback = { 666 .channels_min = 1, 667 .channels_max = 8, 668 }, 669 }, 670 { 671 .name = "iDisp2 Pin", 672 .ops = &hda_link_dai_ops, 673 .playback = { 674 .channels_min = 1, 675 .channels_max = 8, 676 }, 677 }, 678 { 679 .name = "iDisp3 Pin", 680 .ops = &hda_link_dai_ops, 681 .playback = { 682 .channels_min = 1, 683 .channels_max = 8, 684 }, 685 }, 686 { 687 .name = "iDisp4 Pin", 688 .ops = &hda_link_dai_ops, 689 .playback = { 690 .channels_min = 1, 691 .channels_max = 8, 692 }, 693 }, 694 { 695 .name = "Analog CPU DAI", 696 .ops = &hda_link_dai_ops, 697 .playback = { 698 .channels_min = 1, 699 .channels_max = 16, 700 }, 701 .capture = { 702 .channels_min = 1, 703 .channels_max = 16, 704 }, 705 }, 706 { 707 .name = "Digital CPU DAI", 708 .ops = &hda_link_dai_ops, 709 .playback = { 710 .channels_min = 1, 711 .channels_max = 16, 712 }, 713 .capture = { 714 .channels_min = 1, 715 .channels_max = 16, 716 }, 717 }, 718 { 719 .name = "Alt Analog CPU DAI", 720 .ops = &hda_link_dai_ops, 721 .playback = { 722 .channels_min = 1, 723 .channels_max = 16, 724 }, 725 .capture = { 726 .channels_min = 1, 727 .channels_max = 16, 728 }, 729 }, 730 #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) 731 { 732 .name = "Probe Extraction CPU DAI", 733 .compress_new = snd_soc_new_compress, 734 .cops = &sof_probe_compr_ops, 735 .capture = { 736 .stream_name = "Probe Extraction", 737 .channels_min = 1, 738 .channels_max = 8, 739 .rates = SNDRV_PCM_RATE_48000, 740 .rate_min = 48000, 741 .rate_max = 48000, 742 }, 743 }, 744 #endif 745 #endif 746 }; 747