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