1 // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) 2 // Copyright(c) 2023 Intel Corporation. All rights reserved. 3 4 /* 5 * Soundwire Intel ops for LunarLake 6 */ 7 8 #include <linux/acpi.h> 9 #include <linux/device.h> 10 #include <linux/soundwire/sdw_registers.h> 11 #include <linux/soundwire/sdw.h> 12 #include <linux/soundwire/sdw_intel.h> 13 #include <sound/pcm_params.h> 14 #include <sound/hda-mlink.h> 15 #include "cadence_master.h" 16 #include "bus.h" 17 #include "intel.h" 18 19 /* 20 * shim vendor-specific (vs) ops 21 */ 22 23 static void intel_shim_vs_init(struct sdw_intel *sdw) 24 { 25 void __iomem *shim_vs = sdw->link_res->shim_vs; 26 u16 act; 27 28 act = intel_readw(shim_vs, SDW_SHIM2_INTEL_VS_ACTMCTL); 29 u16p_replace_bits(&act, 0x1, SDW_SHIM2_INTEL_VS_ACTMCTL_DOAIS); 30 act |= SDW_SHIM2_INTEL_VS_ACTMCTL_DACTQE; 31 act |= SDW_SHIM2_INTEL_VS_ACTMCTL_DODS; 32 intel_writew(shim_vs, SDW_SHIM2_INTEL_VS_ACTMCTL, act); 33 usleep_range(10, 15); 34 } 35 36 static int intel_shim_check_wake(struct sdw_intel *sdw) 37 { 38 void __iomem *shim_vs; 39 u16 wake_sts; 40 41 shim_vs = sdw->link_res->shim_vs; 42 wake_sts = intel_readw(shim_vs, SDW_SHIM2_INTEL_VS_WAKESTS); 43 44 return wake_sts & SDW_SHIM2_INTEL_VS_WAKEEN_PWS; 45 } 46 47 static void intel_shim_wake(struct sdw_intel *sdw, bool wake_enable) 48 { 49 void __iomem *shim_vs = sdw->link_res->shim_vs; 50 u16 wake_en; 51 u16 wake_sts; 52 53 wake_en = intel_readw(shim_vs, SDW_SHIM2_INTEL_VS_WAKEEN); 54 55 if (wake_enable) { 56 /* Enable the wakeup */ 57 wake_en |= SDW_SHIM2_INTEL_VS_WAKEEN_PWE; 58 intel_writew(shim_vs, SDW_SHIM2_INTEL_VS_WAKEEN, wake_en); 59 } else { 60 /* Disable the wake up interrupt */ 61 wake_en &= ~SDW_SHIM2_INTEL_VS_WAKEEN_PWE; 62 intel_writew(shim_vs, SDW_SHIM2_INTEL_VS_WAKEEN, wake_en); 63 64 /* Clear wake status (W1C) */ 65 wake_sts = intel_readw(shim_vs, SDW_SHIM2_INTEL_VS_WAKESTS); 66 wake_sts |= SDW_SHIM2_INTEL_VS_WAKEEN_PWS; 67 intel_writew(shim_vs, SDW_SHIM2_INTEL_VS_WAKESTS, wake_sts); 68 } 69 } 70 71 static int intel_link_power_up(struct sdw_intel *sdw) 72 { 73 struct sdw_bus *bus = &sdw->cdns.bus; 74 struct sdw_master_prop *prop = &bus->prop; 75 u32 *shim_mask = sdw->link_res->shim_mask; 76 unsigned int link_id = sdw->instance; 77 u32 syncprd; 78 int ret; 79 80 mutex_lock(sdw->link_res->shim_lock); 81 82 if (!*shim_mask) { 83 /* we first need to program the SyncPRD/CPU registers */ 84 dev_dbg(sdw->cdns.dev, "first link up, programming SYNCPRD\n"); 85 86 if (prop->mclk_freq % 6000000) 87 syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_38_4; 88 else 89 syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_24; 90 91 ret = hdac_bus_eml_sdw_set_syncprd_unlocked(sdw->link_res->hbus, syncprd); 92 if (ret < 0) { 93 dev_err(sdw->cdns.dev, "%s: hdac_bus_eml_sdw_set_syncprd failed: %d\n", 94 __func__, ret); 95 goto out; 96 } 97 } 98 99 ret = hdac_bus_eml_sdw_power_up_unlocked(sdw->link_res->hbus, link_id); 100 if (ret < 0) { 101 dev_err(sdw->cdns.dev, "%s: hdac_bus_eml_sdw_power_up failed: %d\n", 102 __func__, ret); 103 goto out; 104 } 105 106 if (!*shim_mask) { 107 /* SYNCPU will change once link is active */ 108 ret = hdac_bus_eml_sdw_wait_syncpu_unlocked(sdw->link_res->hbus); 109 if (ret < 0) { 110 dev_err(sdw->cdns.dev, "%s: hdac_bus_eml_sdw_wait_syncpu failed: %d\n", 111 __func__, ret); 112 goto out; 113 } 114 } 115 116 *shim_mask |= BIT(link_id); 117 118 sdw->cdns.link_up = true; 119 120 intel_shim_vs_init(sdw); 121 122 out: 123 mutex_unlock(sdw->link_res->shim_lock); 124 125 return ret; 126 } 127 128 static int intel_link_power_down(struct sdw_intel *sdw) 129 { 130 u32 *shim_mask = sdw->link_res->shim_mask; 131 unsigned int link_id = sdw->instance; 132 int ret; 133 134 mutex_lock(sdw->link_res->shim_lock); 135 136 sdw->cdns.link_up = false; 137 138 *shim_mask &= ~BIT(link_id); 139 140 ret = hdac_bus_eml_sdw_power_down_unlocked(sdw->link_res->hbus, link_id); 141 if (ret < 0) { 142 dev_err(sdw->cdns.dev, "%s: hdac_bus_eml_sdw_power_down failed: %d\n", 143 __func__, ret); 144 145 /* 146 * we leave the sdw->cdns.link_up flag as false since we've disabled 147 * the link at this point and cannot handle interrupts any longer. 148 */ 149 } 150 151 mutex_unlock(sdw->link_res->shim_lock); 152 153 return ret; 154 } 155 156 static void intel_sync_arm(struct sdw_intel *sdw) 157 { 158 unsigned int link_id = sdw->instance; 159 160 mutex_lock(sdw->link_res->shim_lock); 161 162 hdac_bus_eml_sdw_sync_arm_unlocked(sdw->link_res->hbus, link_id); 163 164 mutex_unlock(sdw->link_res->shim_lock); 165 } 166 167 static int intel_sync_go_unlocked(struct sdw_intel *sdw) 168 { 169 int ret; 170 171 ret = hdac_bus_eml_sdw_sync_go_unlocked(sdw->link_res->hbus); 172 if (ret < 0) 173 dev_err(sdw->cdns.dev, "%s: SyncGO clear failed: %d\n", __func__, ret); 174 175 return ret; 176 } 177 178 static int intel_sync_go(struct sdw_intel *sdw) 179 { 180 int ret; 181 182 mutex_lock(sdw->link_res->shim_lock); 183 184 ret = intel_sync_go_unlocked(sdw); 185 186 mutex_unlock(sdw->link_res->shim_lock); 187 188 return ret; 189 } 190 191 static bool intel_check_cmdsync_unlocked(struct sdw_intel *sdw) 192 { 193 return hdac_bus_eml_sdw_check_cmdsync_unlocked(sdw->link_res->hbus); 194 } 195 196 /* DAI callbacks */ 197 static int intel_params_stream(struct sdw_intel *sdw, 198 struct snd_pcm_substream *substream, 199 struct snd_soc_dai *dai, 200 struct snd_pcm_hw_params *hw_params, 201 int link_id, int alh_stream_id) 202 { 203 struct sdw_intel_link_res *res = sdw->link_res; 204 struct sdw_intel_stream_params_data params_data; 205 206 params_data.substream = substream; 207 params_data.dai = dai; 208 params_data.hw_params = hw_params; 209 params_data.link_id = link_id; 210 params_data.alh_stream_id = alh_stream_id; 211 212 if (res->ops && res->ops->params_stream && res->dev) 213 return res->ops->params_stream(res->dev, 214 ¶ms_data); 215 return -EIO; 216 } 217 218 static int intel_free_stream(struct sdw_intel *sdw, 219 struct snd_pcm_substream *substream, 220 struct snd_soc_dai *dai, 221 int link_id) 222 223 { 224 struct sdw_intel_link_res *res = sdw->link_res; 225 struct sdw_intel_stream_free_data free_data; 226 227 free_data.substream = substream; 228 free_data.dai = dai; 229 free_data.link_id = link_id; 230 231 if (res->ops && res->ops->free_stream && res->dev) 232 return res->ops->free_stream(res->dev, 233 &free_data); 234 235 return 0; 236 } 237 238 /* 239 * DAI operations 240 */ 241 static int intel_hw_params(struct snd_pcm_substream *substream, 242 struct snd_pcm_hw_params *params, 243 struct snd_soc_dai *dai) 244 { 245 struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); 246 struct sdw_intel *sdw = cdns_to_intel(cdns); 247 struct sdw_cdns_dai_runtime *dai_runtime; 248 struct sdw_cdns_pdi *pdi; 249 struct sdw_stream_config sconfig; 250 struct sdw_port_config *pconfig; 251 int ch, dir; 252 int ret; 253 254 dai_runtime = cdns->dai_runtime_array[dai->id]; 255 if (!dai_runtime) 256 return -EIO; 257 258 ch = params_channels(params); 259 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 260 dir = SDW_DATA_DIR_RX; 261 else 262 dir = SDW_DATA_DIR_TX; 263 264 pdi = sdw_cdns_alloc_pdi(cdns, &cdns->pcm, ch, dir, dai->id); 265 266 if (!pdi) { 267 ret = -EINVAL; 268 goto error; 269 } 270 271 /* the SHIM will be configured in the callback functions */ 272 273 sdw_cdns_config_stream(cdns, ch, dir, pdi); 274 275 /* store pdi and state, may be needed in prepare step */ 276 dai_runtime->paused = false; 277 dai_runtime->suspended = false; 278 dai_runtime->pdi = pdi; 279 280 /* Inform DSP about PDI stream number */ 281 ret = intel_params_stream(sdw, substream, dai, params, 282 sdw->instance, 283 pdi->intel_alh_id); 284 if (ret) 285 goto error; 286 287 sconfig.direction = dir; 288 sconfig.ch_count = ch; 289 sconfig.frame_rate = params_rate(params); 290 sconfig.type = dai_runtime->stream_type; 291 292 sconfig.bps = snd_pcm_format_width(params_format(params)); 293 294 /* Port configuration */ 295 pconfig = kzalloc(sizeof(*pconfig), GFP_KERNEL); 296 if (!pconfig) { 297 ret = -ENOMEM; 298 goto error; 299 } 300 301 pconfig->num = pdi->num; 302 pconfig->ch_mask = (1 << ch) - 1; 303 304 ret = sdw_stream_add_master(&cdns->bus, &sconfig, 305 pconfig, 1, dai_runtime->stream); 306 if (ret) 307 dev_err(cdns->dev, "add master to stream failed:%d\n", ret); 308 309 kfree(pconfig); 310 error: 311 return ret; 312 } 313 314 static int intel_prepare(struct snd_pcm_substream *substream, 315 struct snd_soc_dai *dai) 316 { 317 struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); 318 struct sdw_intel *sdw = cdns_to_intel(cdns); 319 struct sdw_cdns_dai_runtime *dai_runtime; 320 int ch, dir; 321 int ret = 0; 322 323 dai_runtime = cdns->dai_runtime_array[dai->id]; 324 if (!dai_runtime) { 325 dev_err(dai->dev, "failed to get dai runtime in %s\n", 326 __func__); 327 return -EIO; 328 } 329 330 if (dai_runtime->suspended) { 331 struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 332 struct snd_pcm_hw_params *hw_params; 333 334 hw_params = &rtd->dpcm[substream->stream].hw_params; 335 336 dai_runtime->suspended = false; 337 338 /* 339 * .prepare() is called after system resume, where we 340 * need to reinitialize the SHIM/ALH/Cadence IP. 341 * .prepare() is also called to deal with underflows, 342 * but in those cases we cannot touch ALH/SHIM 343 * registers 344 */ 345 346 /* configure stream */ 347 ch = params_channels(hw_params); 348 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 349 dir = SDW_DATA_DIR_RX; 350 else 351 dir = SDW_DATA_DIR_TX; 352 353 /* the SHIM will be configured in the callback functions */ 354 355 sdw_cdns_config_stream(cdns, ch, dir, dai_runtime->pdi); 356 357 /* Inform DSP about PDI stream number */ 358 ret = intel_params_stream(sdw, substream, dai, 359 hw_params, 360 sdw->instance, 361 dai_runtime->pdi->intel_alh_id); 362 } 363 364 return ret; 365 } 366 367 static int 368 intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) 369 { 370 struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); 371 struct sdw_intel *sdw = cdns_to_intel(cdns); 372 struct sdw_cdns_dai_runtime *dai_runtime; 373 int ret; 374 375 dai_runtime = cdns->dai_runtime_array[dai->id]; 376 if (!dai_runtime) 377 return -EIO; 378 379 /* 380 * The sdw stream state will transition to RELEASED when stream-> 381 * master_list is empty. So the stream state will transition to 382 * DEPREPARED for the first cpu-dai and to RELEASED for the last 383 * cpu-dai. 384 */ 385 ret = sdw_stream_remove_master(&cdns->bus, dai_runtime->stream); 386 if (ret < 0) { 387 dev_err(dai->dev, "remove master from stream %s failed: %d\n", 388 dai_runtime->stream->name, ret); 389 return ret; 390 } 391 392 ret = intel_free_stream(sdw, substream, dai, sdw->instance); 393 if (ret < 0) { 394 dev_err(dai->dev, "intel_free_stream: failed %d\n", ret); 395 return ret; 396 } 397 398 dai_runtime->pdi = NULL; 399 400 return 0; 401 } 402 403 static int intel_pcm_set_sdw_stream(struct snd_soc_dai *dai, 404 void *stream, int direction) 405 { 406 return cdns_set_sdw_stream(dai, stream, direction); 407 } 408 409 static void *intel_get_sdw_stream(struct snd_soc_dai *dai, 410 int direction) 411 { 412 struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); 413 struct sdw_cdns_dai_runtime *dai_runtime; 414 415 dai_runtime = cdns->dai_runtime_array[dai->id]; 416 if (!dai_runtime) 417 return ERR_PTR(-EINVAL); 418 419 return dai_runtime->stream; 420 } 421 422 static int intel_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) 423 { 424 struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); 425 struct sdw_intel *sdw = cdns_to_intel(cdns); 426 struct sdw_intel_link_res *res = sdw->link_res; 427 struct sdw_cdns_dai_runtime *dai_runtime; 428 int ret = 0; 429 430 /* 431 * The .trigger callback is used to program HDaudio DMA and send required IPC to audio 432 * firmware. 433 */ 434 if (res->ops && res->ops->trigger) { 435 ret = res->ops->trigger(substream, cmd, dai); 436 if (ret < 0) 437 return ret; 438 } 439 440 dai_runtime = cdns->dai_runtime_array[dai->id]; 441 if (!dai_runtime) { 442 dev_err(dai->dev, "failed to get dai runtime in %s\n", 443 __func__); 444 return -EIO; 445 } 446 447 switch (cmd) { 448 case SNDRV_PCM_TRIGGER_SUSPEND: 449 450 /* 451 * The .prepare callback is used to deal with xruns and resume operations. 452 * In the case of xruns, the DMAs and SHIM registers cannot be touched, 453 * but for resume operations the DMAs and SHIM registers need to be initialized. 454 * the .trigger callback is used to track the suspend case only. 455 */ 456 457 dai_runtime->suspended = true; 458 459 break; 460 461 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 462 dai_runtime->paused = true; 463 break; 464 case SNDRV_PCM_TRIGGER_STOP: 465 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 466 dai_runtime->paused = false; 467 break; 468 default: 469 break; 470 } 471 472 return ret; 473 } 474 475 static const struct snd_soc_dai_ops intel_pcm_dai_ops = { 476 .hw_params = intel_hw_params, 477 .prepare = intel_prepare, 478 .hw_free = intel_hw_free, 479 .trigger = intel_trigger, 480 .set_stream = intel_pcm_set_sdw_stream, 481 .get_stream = intel_get_sdw_stream, 482 }; 483 484 static const struct snd_soc_component_driver dai_component = { 485 .name = "soundwire", 486 }; 487 488 /* 489 * PDI routines 490 */ 491 static void intel_pdi_init(struct sdw_intel *sdw, 492 struct sdw_cdns_stream_config *config) 493 { 494 void __iomem *shim = sdw->link_res->shim; 495 int pcm_cap; 496 497 /* PCM Stream Capability */ 498 pcm_cap = intel_readw(shim, SDW_SHIM2_PCMSCAP); 499 500 config->pcm_bd = FIELD_GET(SDW_SHIM2_PCMSCAP_BSS, pcm_cap); 501 config->pcm_in = FIELD_GET(SDW_SHIM2_PCMSCAP_ISS, pcm_cap); 502 config->pcm_out = FIELD_GET(SDW_SHIM2_PCMSCAP_ISS, pcm_cap); 503 504 dev_dbg(sdw->cdns.dev, "PCM cap bd:%d in:%d out:%d\n", 505 config->pcm_bd, config->pcm_in, config->pcm_out); 506 } 507 508 static int 509 intel_pdi_get_ch_cap(struct sdw_intel *sdw, unsigned int pdi_num) 510 { 511 void __iomem *shim = sdw->link_res->shim; 512 513 /* zero based values for channel count in register */ 514 return intel_readw(shim, SDW_SHIM2_PCMSYCHC(pdi_num)) + 1; 515 } 516 517 static void intel_pdi_get_ch_update(struct sdw_intel *sdw, 518 struct sdw_cdns_pdi *pdi, 519 unsigned int num_pdi, 520 unsigned int *num_ch) 521 { 522 int ch_count = 0; 523 int i; 524 525 for (i = 0; i < num_pdi; i++) { 526 pdi->ch_count = intel_pdi_get_ch_cap(sdw, pdi->num); 527 ch_count += pdi->ch_count; 528 pdi++; 529 } 530 531 *num_ch = ch_count; 532 } 533 534 static void intel_pdi_stream_ch_update(struct sdw_intel *sdw, 535 struct sdw_cdns_streams *stream) 536 { 537 intel_pdi_get_ch_update(sdw, stream->bd, stream->num_bd, 538 &stream->num_ch_bd); 539 540 intel_pdi_get_ch_update(sdw, stream->in, stream->num_in, 541 &stream->num_ch_in); 542 543 intel_pdi_get_ch_update(sdw, stream->out, stream->num_out, 544 &stream->num_ch_out); 545 } 546 547 static int intel_create_dai(struct sdw_cdns *cdns, 548 struct snd_soc_dai_driver *dais, 549 enum intel_pdi_type type, 550 u32 num, u32 off, u32 max_ch) 551 { 552 int i; 553 554 if (!num) 555 return 0; 556 557 for (i = off; i < (off + num); i++) { 558 dais[i].name = devm_kasprintf(cdns->dev, GFP_KERNEL, 559 "SDW%d Pin%d", 560 cdns->instance, i); 561 if (!dais[i].name) 562 return -ENOMEM; 563 564 if (type == INTEL_PDI_BD || type == INTEL_PDI_OUT) { 565 dais[i].playback.channels_min = 1; 566 dais[i].playback.channels_max = max_ch; 567 } 568 569 if (type == INTEL_PDI_BD || type == INTEL_PDI_IN) { 570 dais[i].capture.channels_min = 1; 571 dais[i].capture.channels_max = max_ch; 572 } 573 574 dais[i].ops = &intel_pcm_dai_ops; 575 } 576 577 return 0; 578 } 579 580 static int intel_register_dai(struct sdw_intel *sdw) 581 { 582 struct sdw_cdns_dai_runtime **dai_runtime_array; 583 struct sdw_cdns_stream_config config; 584 struct sdw_cdns *cdns = &sdw->cdns; 585 struct sdw_cdns_streams *stream; 586 struct snd_soc_dai_driver *dais; 587 int num_dai; 588 int ret; 589 int off = 0; 590 591 /* Read the PDI config and initialize cadence PDI */ 592 intel_pdi_init(sdw, &config); 593 ret = sdw_cdns_pdi_init(cdns, config); 594 if (ret) 595 return ret; 596 597 intel_pdi_stream_ch_update(sdw, &sdw->cdns.pcm); 598 599 /* DAIs are created based on total number of PDIs supported */ 600 num_dai = cdns->pcm.num_pdi; 601 602 dai_runtime_array = devm_kcalloc(cdns->dev, num_dai, 603 sizeof(struct sdw_cdns_dai_runtime *), 604 GFP_KERNEL); 605 if (!dai_runtime_array) 606 return -ENOMEM; 607 cdns->dai_runtime_array = dai_runtime_array; 608 609 dais = devm_kcalloc(cdns->dev, num_dai, sizeof(*dais), GFP_KERNEL); 610 if (!dais) 611 return -ENOMEM; 612 613 /* Create PCM DAIs */ 614 stream = &cdns->pcm; 615 616 ret = intel_create_dai(cdns, dais, INTEL_PDI_IN, cdns->pcm.num_in, 617 off, stream->num_ch_in); 618 if (ret) 619 return ret; 620 621 off += cdns->pcm.num_in; 622 ret = intel_create_dai(cdns, dais, INTEL_PDI_OUT, cdns->pcm.num_out, 623 off, stream->num_ch_out); 624 if (ret) 625 return ret; 626 627 off += cdns->pcm.num_out; 628 ret = intel_create_dai(cdns, dais, INTEL_PDI_BD, cdns->pcm.num_bd, 629 off, stream->num_ch_bd); 630 if (ret) 631 return ret; 632 633 return devm_snd_soc_register_component(cdns->dev, &dai_component, 634 dais, num_dai); 635 } 636 637 static void intel_program_sdi(struct sdw_intel *sdw, int dev_num) 638 { 639 int ret; 640 641 ret = hdac_bus_eml_sdw_set_lsdiid(sdw->link_res->hbus, sdw->instance, dev_num); 642 if (ret < 0) 643 dev_err(sdw->cdns.dev, "%s: could not set lsdiid for link %d %d\n", 644 __func__, sdw->instance, dev_num); 645 } 646 647 const struct sdw_intel_hw_ops sdw_intel_lnl_hw_ops = { 648 .debugfs_init = intel_ace2x_debugfs_init, 649 .debugfs_exit = intel_ace2x_debugfs_exit, 650 651 .register_dai = intel_register_dai, 652 653 .check_clock_stop = intel_check_clock_stop, 654 .start_bus = intel_start_bus, 655 .start_bus_after_reset = intel_start_bus_after_reset, 656 .start_bus_after_clock_stop = intel_start_bus_after_clock_stop, 657 .stop_bus = intel_stop_bus, 658 659 .link_power_up = intel_link_power_up, 660 .link_power_down = intel_link_power_down, 661 662 .shim_check_wake = intel_shim_check_wake, 663 .shim_wake = intel_shim_wake, 664 665 .pre_bank_switch = intel_pre_bank_switch, 666 .post_bank_switch = intel_post_bank_switch, 667 668 .sync_arm = intel_sync_arm, 669 .sync_go_unlocked = intel_sync_go_unlocked, 670 .sync_go = intel_sync_go, 671 .sync_check_cmdsync_unlocked = intel_check_cmdsync_unlocked, 672 673 .program_sdi = intel_program_sdi, 674 }; 675 EXPORT_SYMBOL_NS(sdw_intel_lnl_hw_ops, SOUNDWIRE_INTEL); 676 677 MODULE_IMPORT_NS(SND_SOC_SOF_HDA_MLINK); 678