1171 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2// This file incorporates work covered by the following copyright notice:3// Copyright (c) 2020 Intel Corporation4// Copyright(c) 2024 Advanced Micro Devices, Inc.5/*6 * soc-sdw-utils.c - common SoundWire machine driver helper functions7 */8 9#include <linux/device.h>10#include <linux/module.h>11#include <linux/soundwire/sdw.h>12#include <linux/soundwire/sdw_type.h>13#include <sound/soc_sdw_utils.h>14 15static const struct snd_soc_dapm_widget generic_dmic_widgets[] = {16 SND_SOC_DAPM_MIC("DMIC", NULL),17};18 19static const struct snd_soc_dapm_widget generic_jack_widgets[] = {20 SND_SOC_DAPM_HP("Headphone", NULL),21 SND_SOC_DAPM_MIC("Headset Mic", NULL),22};23 24static const struct snd_kcontrol_new generic_jack_controls[] = {25 SOC_DAPM_PIN_SWITCH("Headphone"),26 SOC_DAPM_PIN_SWITCH("Headset Mic"),27};28 29static const struct snd_soc_dapm_widget generic_spk_widgets[] = {30 SND_SOC_DAPM_SPK("Speaker", NULL),31};32 33static const struct snd_kcontrol_new generic_spk_controls[] = {34 SOC_DAPM_PIN_SWITCH("Speaker"),35};36 37static const struct snd_soc_dapm_widget maxim_widgets[] = {38 SND_SOC_DAPM_SPK("Left Spk", NULL),39 SND_SOC_DAPM_SPK("Right Spk", NULL),40};41 42static const struct snd_kcontrol_new maxim_controls[] = {43 SOC_DAPM_PIN_SWITCH("Left Spk"),44 SOC_DAPM_PIN_SWITCH("Right Spk"),45};46 47static const struct snd_soc_dapm_widget rt700_widgets[] = {48 SND_SOC_DAPM_HP("Headphones", NULL),49 SND_SOC_DAPM_MIC("AMIC", NULL),50 SND_SOC_DAPM_SPK("Speaker", NULL),51};52 53static const struct snd_kcontrol_new rt700_controls[] = {54 SOC_DAPM_PIN_SWITCH("Headphones"),55 SOC_DAPM_PIN_SWITCH("AMIC"),56 SOC_DAPM_PIN_SWITCH("Speaker"),57};58 59struct asoc_sdw_codec_info codec_info_list[] = {60 {61 .part_id = 0x700,62 .dais = {63 {64 .direction = {true, true},65 .dai_name = "rt700-aif1",66 .dai_type = SOC_SDW_DAI_TYPE_JACK,67 .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID},68 .rtd_init = asoc_sdw_rt700_rtd_init,69 .controls = rt700_controls,70 .num_controls = ARRAY_SIZE(rt700_controls),71 .widgets = rt700_widgets,72 .num_widgets = ARRAY_SIZE(rt700_widgets),73 },74 },75 .dai_num = 1,76 },77 {78 .part_id = 0x711,79 .version_id = 3,80 .dais = {81 {82 .direction = {true, true},83 .dai_name = "rt711-sdca-aif1",84 .dai_type = SOC_SDW_DAI_TYPE_JACK,85 .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID},86 .init = asoc_sdw_rt_sdca_jack_init,87 .exit = asoc_sdw_rt_sdca_jack_exit,88 .rtd_init = asoc_sdw_rt_sdca_jack_rtd_init,89 .controls = generic_jack_controls,90 .num_controls = ARRAY_SIZE(generic_jack_controls),91 .widgets = generic_jack_widgets,92 .num_widgets = ARRAY_SIZE(generic_jack_widgets),93 },94 },95 .dai_num = 1,96 },97 {98 .part_id = 0x711,99 .version_id = 2,100 .dais = {101 {102 .direction = {true, true},103 .dai_name = "rt711-aif1",104 .dai_type = SOC_SDW_DAI_TYPE_JACK,105 .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID},106 .init = asoc_sdw_rt711_init,107 .exit = asoc_sdw_rt711_exit,108 .rtd_init = asoc_sdw_rt711_rtd_init,109 .controls = generic_jack_controls,110 .num_controls = ARRAY_SIZE(generic_jack_controls),111 .widgets = generic_jack_widgets,112 .num_widgets = ARRAY_SIZE(generic_jack_widgets),113 },114 },115 .dai_num = 1,116 },117 {118 .part_id = 0x712,119 .version_id = 3,120 .dais = {121 {122 .direction = {true, true},123 .dai_name = "rt712-sdca-aif1",124 .dai_type = SOC_SDW_DAI_TYPE_JACK,125 .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID},126 .init = asoc_sdw_rt_sdca_jack_init,127 .exit = asoc_sdw_rt_sdca_jack_exit,128 .rtd_init = asoc_sdw_rt_sdca_jack_rtd_init,129 .controls = generic_jack_controls,130 .num_controls = ARRAY_SIZE(generic_jack_controls),131 .widgets = generic_jack_widgets,132 .num_widgets = ARRAY_SIZE(generic_jack_widgets),133 },134 {135 .direction = {true, false},136 .dai_name = "rt712-sdca-aif2",137 .dai_type = SOC_SDW_DAI_TYPE_AMP,138 .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID},139 .init = asoc_sdw_rt_amp_init,140 .exit = asoc_sdw_rt_amp_exit,141 .rtd_init = asoc_sdw_rt712_spk_rtd_init,142 .controls = generic_spk_controls,143 .num_controls = ARRAY_SIZE(generic_spk_controls),144 .widgets = generic_spk_widgets,145 .num_widgets = ARRAY_SIZE(generic_spk_widgets),146 },147 },148 .dai_num = 2,149 },150 {151 .part_id = 0x1712,152 .version_id = 3,153 .dais = {154 {155 .direction = {false, true},156 .dai_name = "rt712-sdca-dmic-aif1",157 .dai_type = SOC_SDW_DAI_TYPE_MIC,158 .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID},159 .rtd_init = asoc_sdw_rt_dmic_rtd_init,160 },161 },162 .dai_num = 1,163 },164 {165 .part_id = 0x713,166 .version_id = 3,167 .dais = {168 {169 .direction = {true, true},170 .dai_name = "rt712-sdca-aif1",171 .dai_type = SOC_SDW_DAI_TYPE_JACK,172 .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID},173 .init = asoc_sdw_rt_sdca_jack_init,174 .exit = asoc_sdw_rt_sdca_jack_exit,175 .rtd_init = asoc_sdw_rt_sdca_jack_rtd_init,176 .controls = generic_jack_controls,177 .num_controls = ARRAY_SIZE(generic_jack_controls),178 .widgets = generic_jack_widgets,179 .num_widgets = ARRAY_SIZE(generic_jack_widgets),180 },181 },182 .dai_num = 1,183 },184 {185 .part_id = 0x1713,186 .version_id = 3,187 .dais = {188 {189 .direction = {false, true},190 .dai_name = "rt712-sdca-dmic-aif1",191 .dai_type = SOC_SDW_DAI_TYPE_MIC,192 .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID},193 .rtd_init = asoc_sdw_rt_dmic_rtd_init,194 },195 },196 .dai_num = 1,197 },198 {199 .part_id = 0x1308,200 .acpi_id = "10EC1308",201 .dais = {202 {203 .direction = {true, false},204 .dai_name = "rt1308-aif",205 .dai_type = SOC_SDW_DAI_TYPE_AMP,206 .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID},207 .init = asoc_sdw_rt_amp_init,208 .exit = asoc_sdw_rt_amp_exit,209 .rtd_init = asoc_sdw_rt_amp_spk_rtd_init,210 .controls = generic_spk_controls,211 .num_controls = ARRAY_SIZE(generic_spk_controls),212 .widgets = generic_spk_widgets,213 .num_widgets = ARRAY_SIZE(generic_spk_widgets),214 },215 },216 .dai_num = 1,217 .ops = &soc_sdw_rt1308_i2s_ops,218 },219 {220 .part_id = 0x1316,221 .dais = {222 {223 .direction = {true, true},224 .dai_name = "rt1316-aif",225 .dai_type = SOC_SDW_DAI_TYPE_AMP,226 .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID},227 .init = asoc_sdw_rt_amp_init,228 .exit = asoc_sdw_rt_amp_exit,229 .rtd_init = asoc_sdw_rt_amp_spk_rtd_init,230 .controls = generic_spk_controls,231 .num_controls = ARRAY_SIZE(generic_spk_controls),232 .widgets = generic_spk_widgets,233 .num_widgets = ARRAY_SIZE(generic_spk_widgets),234 },235 },236 .dai_num = 1,237 },238 {239 .part_id = 0x1318,240 .dais = {241 {242 .direction = {true, true},243 .dai_name = "rt1318-aif",244 .dai_type = SOC_SDW_DAI_TYPE_AMP,245 .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID},246 .init = asoc_sdw_rt_amp_init,247 .exit = asoc_sdw_rt_amp_exit,248 .rtd_init = asoc_sdw_rt_amp_spk_rtd_init,249 .controls = generic_spk_controls,250 .num_controls = ARRAY_SIZE(generic_spk_controls),251 .widgets = generic_spk_widgets,252 .num_widgets = ARRAY_SIZE(generic_spk_widgets),253 },254 },255 .dai_num = 1,256 },257 {258 .part_id = 0x1320,259 .dais = {260 {261 .direction = {true, false},262 .dai_name = "rt1320-aif1",263 .dai_type = SOC_SDW_DAI_TYPE_AMP,264 .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID},265 .init = asoc_sdw_rt_amp_init,266 .exit = asoc_sdw_rt_amp_exit,267 .rtd_init = asoc_sdw_rt_amp_spk_rtd_init,268 .controls = generic_spk_controls,269 .num_controls = ARRAY_SIZE(generic_spk_controls),270 .widgets = generic_spk_widgets,271 .num_widgets = ARRAY_SIZE(generic_spk_widgets),272 },273 },274 .dai_num = 1,275 },276 {277 .part_id = 0x714,278 .version_id = 3,279 .ignore_internal_dmic = true,280 .dais = {281 {282 .direction = {false, true},283 .dai_name = "rt715-sdca-aif2",284 .dai_type = SOC_SDW_DAI_TYPE_MIC,285 .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID},286 .rtd_init = asoc_sdw_rt_dmic_rtd_init,287 },288 },289 .dai_num = 1,290 },291 {292 .part_id = 0x715,293 .version_id = 3,294 .ignore_internal_dmic = true,295 .dais = {296 {297 .direction = {false, true},298 .dai_name = "rt715-sdca-aif2",299 .dai_type = SOC_SDW_DAI_TYPE_MIC,300 .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID},301 .rtd_init = asoc_sdw_rt_dmic_rtd_init,302 },303 },304 .dai_num = 1,305 },306 {307 .part_id = 0x714,308 .version_id = 2,309 .ignore_internal_dmic = true,310 .dais = {311 {312 .direction = {false, true},313 .dai_name = "rt715-aif2",314 .dai_type = SOC_SDW_DAI_TYPE_MIC,315 .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID},316 .rtd_init = asoc_sdw_rt_dmic_rtd_init,317 },318 },319 .dai_num = 1,320 },321 {322 .part_id = 0x715,323 .version_id = 2,324 .ignore_internal_dmic = true,325 .dais = {326 {327 .direction = {false, true},328 .dai_name = "rt715-aif2",329 .dai_type = SOC_SDW_DAI_TYPE_MIC,330 .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID},331 .rtd_init = asoc_sdw_rt_dmic_rtd_init,332 },333 },334 .dai_num = 1,335 },336 {337 .part_id = 0x722,338 .version_id = 3,339 .dais = {340 {341 .direction = {true, true},342 .dai_name = "rt722-sdca-aif1",343 .dai_type = SOC_SDW_DAI_TYPE_JACK,344 .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID},345 .init = asoc_sdw_rt_sdca_jack_init,346 .exit = asoc_sdw_rt_sdca_jack_exit,347 .rtd_init = asoc_sdw_rt_sdca_jack_rtd_init,348 .controls = generic_jack_controls,349 .num_controls = ARRAY_SIZE(generic_jack_controls),350 .widgets = generic_jack_widgets,351 .num_widgets = ARRAY_SIZE(generic_jack_widgets),352 },353 {354 .direction = {true, false},355 .dai_name = "rt722-sdca-aif2",356 .dai_type = SOC_SDW_DAI_TYPE_AMP,357 /* No feedback capability is provided by rt722-sdca codec driver*/358 .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID},359 .init = asoc_sdw_rt_amp_init,360 .exit = asoc_sdw_rt_amp_exit,361 .rtd_init = asoc_sdw_rt722_spk_rtd_init,362 .controls = generic_spk_controls,363 .num_controls = ARRAY_SIZE(generic_spk_controls),364 .widgets = generic_spk_widgets,365 .num_widgets = ARRAY_SIZE(generic_spk_widgets),366 },367 {368 .direction = {false, true},369 .dai_name = "rt722-sdca-aif3",370 .dai_type = SOC_SDW_DAI_TYPE_MIC,371 .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID},372 .rtd_init = asoc_sdw_rt_dmic_rtd_init,373 },374 },375 .dai_num = 3,376 },377 {378 .part_id = 0x8373,379 .dais = {380 {381 .direction = {true, true},382 .dai_name = "max98373-aif1",383 .dai_type = SOC_SDW_DAI_TYPE_AMP,384 .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID},385 .init = asoc_sdw_maxim_init,386 .rtd_init = asoc_sdw_maxim_spk_rtd_init,387 .controls = maxim_controls,388 .num_controls = ARRAY_SIZE(maxim_controls),389 .widgets = maxim_widgets,390 .num_widgets = ARRAY_SIZE(maxim_widgets),391 },392 },393 .dai_num = 1,394 },395 {396 .part_id = 0x8363,397 .dais = {398 {399 .direction = {true, false},400 .dai_name = "max98363-aif1",401 .dai_type = SOC_SDW_DAI_TYPE_AMP,402 .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID},403 .init = asoc_sdw_maxim_init,404 .rtd_init = asoc_sdw_maxim_spk_rtd_init,405 .controls = maxim_controls,406 .num_controls = ARRAY_SIZE(maxim_controls),407 .widgets = maxim_widgets,408 .num_widgets = ARRAY_SIZE(maxim_widgets),409 },410 },411 .dai_num = 1,412 },413 {414 .part_id = 0x5682,415 .dais = {416 {417 .direction = {true, true},418 .dai_name = "rt5682-sdw",419 .dai_type = SOC_SDW_DAI_TYPE_JACK,420 .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID},421 .rtd_init = asoc_sdw_rt5682_rtd_init,422 .controls = generic_jack_controls,423 .num_controls = ARRAY_SIZE(generic_jack_controls),424 .widgets = generic_jack_widgets,425 .num_widgets = ARRAY_SIZE(generic_jack_widgets),426 },427 },428 .dai_num = 1,429 },430 {431 .part_id = 0x3556,432 .dais = {433 {434 .direction = {true, true},435 .dai_name = "cs35l56-sdw1",436 .dai_type = SOC_SDW_DAI_TYPE_AMP,437 .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID},438 .init = asoc_sdw_cs_amp_init,439 .rtd_init = asoc_sdw_cs_spk_rtd_init,440 .controls = generic_spk_controls,441 .num_controls = ARRAY_SIZE(generic_spk_controls),442 .widgets = generic_spk_widgets,443 .num_widgets = ARRAY_SIZE(generic_spk_widgets),444 },445 },446 .dai_num = 1,447 },448 {449 .part_id = 0x4242,450 .dais = {451 {452 .direction = {true, true},453 .dai_name = "cs42l42-sdw",454 .dai_type = SOC_SDW_DAI_TYPE_JACK,455 .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID},456 .rtd_init = asoc_sdw_cs42l42_rtd_init,457 .controls = generic_jack_controls,458 .num_controls = ARRAY_SIZE(generic_jack_controls),459 .widgets = generic_jack_widgets,460 .num_widgets = ARRAY_SIZE(generic_jack_widgets),461 },462 },463 .dai_num = 1,464 },465 {466 .part_id = 0x4243,467 .codec_name = "cs42l43-codec",468 .count_sidecar = asoc_sdw_bridge_cs35l56_count_sidecar,469 .add_sidecar = asoc_sdw_bridge_cs35l56_add_sidecar,470 .dais = {471 {472 .direction = {true, false},473 .dai_name = "cs42l43-dp5",474 .dai_type = SOC_SDW_DAI_TYPE_JACK,475 .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID},476 .rtd_init = asoc_sdw_cs42l43_hs_rtd_init,477 .controls = generic_jack_controls,478 .num_controls = ARRAY_SIZE(generic_jack_controls),479 .widgets = generic_jack_widgets,480 .num_widgets = ARRAY_SIZE(generic_jack_widgets),481 },482 {483 .direction = {false, true},484 .dai_name = "cs42l43-dp1",485 .dai_type = SOC_SDW_DAI_TYPE_MIC,486 .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID},487 .rtd_init = asoc_sdw_cs42l43_dmic_rtd_init,488 .widgets = generic_dmic_widgets,489 .num_widgets = ARRAY_SIZE(generic_dmic_widgets),490 },491 {492 .direction = {false, true},493 .dai_name = "cs42l43-dp2",494 .dai_type = SOC_SDW_DAI_TYPE_JACK,495 .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_JACK_IN_DAI_ID},496 },497 {498 .direction = {true, false},499 .dai_name = "cs42l43-dp6",500 .dai_type = SOC_SDW_DAI_TYPE_AMP,501 .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID},502 .init = asoc_sdw_cs42l43_spk_init,503 .rtd_init = asoc_sdw_cs42l43_spk_rtd_init,504 .controls = generic_spk_controls,505 .num_controls = ARRAY_SIZE(generic_spk_controls),506 .widgets = generic_spk_widgets,507 .num_widgets = ARRAY_SIZE(generic_spk_widgets),508 .quirk = SOC_SDW_CODEC_SPKR | SOC_SDW_SIDECAR_AMPS,509 },510 },511 .dai_num = 4,512 },513 {514 .part_id = 0xaaaa, /* generic codec mockup */515 .version_id = 0,516 .dais = {517 {518 .direction = {true, true},519 .dai_name = "sdw-mockup-aif1",520 .dai_type = SOC_SDW_DAI_TYPE_JACK,521 .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID},522 },523 },524 .dai_num = 1,525 },526 {527 .part_id = 0xaa55, /* headset codec mockup */528 .version_id = 0,529 .dais = {530 {531 .direction = {true, true},532 .dai_name = "sdw-mockup-aif1",533 .dai_type = SOC_SDW_DAI_TYPE_JACK,534 .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID},535 },536 },537 .dai_num = 1,538 },539 {540 .part_id = 0x55aa, /* amplifier mockup */541 .version_id = 0,542 .dais = {543 {544 .direction = {true, true},545 .dai_name = "sdw-mockup-aif1",546 .dai_type = SOC_SDW_DAI_TYPE_AMP,547 .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID},548 },549 },550 .dai_num = 1,551 },552 {553 .part_id = 0x5555,554 .version_id = 0,555 .dais = {556 {557 .dai_name = "sdw-mockup-aif1",558 .direction = {false, true},559 .dai_type = SOC_SDW_DAI_TYPE_MIC,560 .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID},561 },562 },563 .dai_num = 1,564 },565};566EXPORT_SYMBOL_NS(codec_info_list, SND_SOC_SDW_UTILS);567 568int asoc_sdw_get_codec_info_list_count(void)569{570 return ARRAY_SIZE(codec_info_list);571};572EXPORT_SYMBOL_NS(asoc_sdw_get_codec_info_list_count, SND_SOC_SDW_UTILS);573 574struct asoc_sdw_codec_info *asoc_sdw_find_codec_info_part(const u64 adr)575{576 unsigned int part_id, sdw_version;577 int i;578 579 part_id = SDW_PART_ID(adr);580 sdw_version = SDW_VERSION(adr);581 for (i = 0; i < ARRAY_SIZE(codec_info_list); i++)582 /*583 * A codec info is for all sdw version with the part id if584 * version_id is not specified in the codec info.585 */586 if (part_id == codec_info_list[i].part_id &&587 (!codec_info_list[i].version_id ||588 sdw_version == codec_info_list[i].version_id))589 return &codec_info_list[i];590 591 return NULL;592}593EXPORT_SYMBOL_NS(asoc_sdw_find_codec_info_part, SND_SOC_SDW_UTILS);594 595struct asoc_sdw_codec_info *asoc_sdw_find_codec_info_acpi(const u8 *acpi_id)596{597 int i;598 599 if (!acpi_id[0])600 return NULL;601 602 for (i = 0; i < ARRAY_SIZE(codec_info_list); i++)603 if (!memcmp(codec_info_list[i].acpi_id, acpi_id, ACPI_ID_LEN))604 return &codec_info_list[i];605 606 return NULL;607}608EXPORT_SYMBOL_NS(asoc_sdw_find_codec_info_acpi, SND_SOC_SDW_UTILS);609 610struct asoc_sdw_codec_info *asoc_sdw_find_codec_info_dai(const char *dai_name, int *dai_index)611{612 int i, j;613 614 for (i = 0; i < ARRAY_SIZE(codec_info_list); i++) {615 for (j = 0; j < codec_info_list[i].dai_num; j++) {616 if (!strcmp(codec_info_list[i].dais[j].dai_name, dai_name)) {617 *dai_index = j;618 return &codec_info_list[i];619 }620 }621 }622 623 return NULL;624}625EXPORT_SYMBOL_NS(asoc_sdw_find_codec_info_dai, SND_SOC_SDW_UTILS);626 627int asoc_sdw_rtd_init(struct snd_soc_pcm_runtime *rtd)628{629 struct snd_soc_card *card = rtd->card;630 struct asoc_sdw_codec_info *codec_info;631 struct snd_soc_dai *dai;632 int dai_index;633 int ret;634 int i;635 636 for_each_rtd_codec_dais(rtd, i, dai) {637 codec_info = asoc_sdw_find_codec_info_dai(dai->name, &dai_index);638 if (!codec_info)639 return -EINVAL;640 641 /*642 * A codec dai can be connected to different dai links for capture and playback,643 * but we only need to call the rtd_init function once.644 * The rtd_init for each codec dai is independent. So, the order of rtd_init645 * doesn't matter.646 */647 if (codec_info->dais[dai_index].rtd_init_done)648 continue;649 650 /*651 * Add card controls and dapm widgets for the first codec dai.652 * The controls and widgets will be used for all codec dais.653 */654 655 if (i > 0)656 goto skip_add_controls_widgets;657 658 if (codec_info->dais[dai_index].controls) {659 ret = snd_soc_add_card_controls(card, codec_info->dais[dai_index].controls,660 codec_info->dais[dai_index].num_controls);661 if (ret) {662 dev_err(card->dev, "%#x controls addition failed: %d\n",663 codec_info->part_id, ret);664 return ret;665 }666 }667 if (codec_info->dais[dai_index].widgets) {668 ret = snd_soc_dapm_new_controls(&card->dapm,669 codec_info->dais[dai_index].widgets,670 codec_info->dais[dai_index].num_widgets);671 if (ret) {672 dev_err(card->dev, "%#x widgets addition failed: %d\n",673 codec_info->part_id, ret);674 return ret;675 }676 }677 678skip_add_controls_widgets:679 if (codec_info->dais[dai_index].rtd_init) {680 ret = codec_info->dais[dai_index].rtd_init(rtd, dai);681 if (ret)682 return ret;683 }684 codec_info->dais[dai_index].rtd_init_done = true;685 }686 687 return 0;688}689EXPORT_SYMBOL_NS(asoc_sdw_rtd_init, SND_SOC_SDW_UTILS);690 691/* these wrappers are only needed to avoid typecast compilation errors */692int asoc_sdw_startup(struct snd_pcm_substream *substream)693{694 return sdw_startup_stream(substream);695}696EXPORT_SYMBOL_NS(asoc_sdw_startup, SND_SOC_SDW_UTILS);697 698int asoc_sdw_prepare(struct snd_pcm_substream *substream)699{700 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);701 struct sdw_stream_runtime *sdw_stream;702 struct snd_soc_dai *dai;703 704 /* Find stream from first CPU DAI */705 dai = snd_soc_rtd_to_cpu(rtd, 0);706 707 sdw_stream = snd_soc_dai_get_stream(dai, substream->stream);708 if (IS_ERR(sdw_stream)) {709 dev_err(rtd->dev, "no stream found for DAI %s\n", dai->name);710 return PTR_ERR(sdw_stream);711 }712 713 return sdw_prepare_stream(sdw_stream);714}715EXPORT_SYMBOL_NS(asoc_sdw_prepare, SND_SOC_SDW_UTILS);716 717int asoc_sdw_trigger(struct snd_pcm_substream *substream, int cmd)718{719 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);720 struct sdw_stream_runtime *sdw_stream;721 struct snd_soc_dai *dai;722 int ret;723 724 /* Find stream from first CPU DAI */725 dai = snd_soc_rtd_to_cpu(rtd, 0);726 727 sdw_stream = snd_soc_dai_get_stream(dai, substream->stream);728 if (IS_ERR(sdw_stream)) {729 dev_err(rtd->dev, "no stream found for DAI %s\n", dai->name);730 return PTR_ERR(sdw_stream);731 }732 733 switch (cmd) {734 case SNDRV_PCM_TRIGGER_START:735 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:736 case SNDRV_PCM_TRIGGER_RESUME:737 ret = sdw_enable_stream(sdw_stream);738 break;739 740 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:741 case SNDRV_PCM_TRIGGER_SUSPEND:742 case SNDRV_PCM_TRIGGER_STOP:743 ret = sdw_disable_stream(sdw_stream);744 break;745 default:746 ret = -EINVAL;747 break;748 }749 750 if (ret)751 dev_err(rtd->dev, "%s trigger %d failed: %d\n", __func__, cmd, ret);752 753 return ret;754}755EXPORT_SYMBOL_NS(asoc_sdw_trigger, SND_SOC_SDW_UTILS);756 757int asoc_sdw_hw_params(struct snd_pcm_substream *substream,758 struct snd_pcm_hw_params *params)759{760 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);761 struct snd_soc_dai_link_ch_map *ch_maps;762 int ch = params_channels(params);763 unsigned int ch_mask;764 int num_codecs;765 int step;766 int i;767 768 if (!rtd->dai_link->ch_maps)769 return 0;770 771 /* Identical data will be sent to all codecs in playback */772 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {773 ch_mask = GENMASK(ch - 1, 0);774 step = 0;775 } else {776 num_codecs = rtd->dai_link->num_codecs;777 778 if (ch < num_codecs || ch % num_codecs != 0) {779 dev_err(rtd->dev, "Channels number %d is invalid when codec number = %d\n",780 ch, num_codecs);781 return -EINVAL;782 }783 784 ch_mask = GENMASK(ch / num_codecs - 1, 0);785 step = hweight_long(ch_mask);786 }787 788 /*789 * The captured data will be combined from each cpu DAI if the dai790 * link has more than one codec DAIs. Set codec channel mask and791 * ASoC will set the corresponding channel numbers for each cpu dai.792 */793 for_each_link_ch_maps(rtd->dai_link, i, ch_maps)794 ch_maps->ch_mask = ch_mask << (i * step);795 796 return 0;797}798EXPORT_SYMBOL_NS(asoc_sdw_hw_params, SND_SOC_SDW_UTILS);799 800int asoc_sdw_hw_free(struct snd_pcm_substream *substream)801{802 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);803 struct sdw_stream_runtime *sdw_stream;804 struct snd_soc_dai *dai;805 806 /* Find stream from first CPU DAI */807 dai = snd_soc_rtd_to_cpu(rtd, 0);808 809 sdw_stream = snd_soc_dai_get_stream(dai, substream->stream);810 if (IS_ERR(sdw_stream)) {811 dev_err(rtd->dev, "no stream found for DAI %s\n", dai->name);812 return PTR_ERR(sdw_stream);813 }814 815 return sdw_deprepare_stream(sdw_stream);816}817EXPORT_SYMBOL_NS(asoc_sdw_hw_free, SND_SOC_SDW_UTILS);818 819void asoc_sdw_shutdown(struct snd_pcm_substream *substream)820{821 sdw_shutdown_stream(substream);822}823EXPORT_SYMBOL_NS(asoc_sdw_shutdown, SND_SOC_SDW_UTILS);824 825static bool asoc_sdw_is_unique_device(const struct snd_soc_acpi_link_adr *adr_link,826 unsigned int sdw_version,827 unsigned int mfg_id,828 unsigned int part_id,829 unsigned int class_id,830 int index_in_link)831{832 int i;833 834 for (i = 0; i < adr_link->num_adr; i++) {835 unsigned int sdw1_version, mfg1_id, part1_id, class1_id;836 u64 adr;837 838 /* skip itself */839 if (i == index_in_link)840 continue;841 842 adr = adr_link->adr_d[i].adr;843 844 sdw1_version = SDW_VERSION(adr);845 mfg1_id = SDW_MFG_ID(adr);846 part1_id = SDW_PART_ID(adr);847 class1_id = SDW_CLASS_ID(adr);848 849 if (sdw_version == sdw1_version &&850 mfg_id == mfg1_id &&851 part_id == part1_id &&852 class_id == class1_id)853 return false;854 }855 856 return true;857}858 859const char *asoc_sdw_get_codec_name(struct device *dev,860 const struct asoc_sdw_codec_info *codec_info,861 const struct snd_soc_acpi_link_adr *adr_link,862 int adr_index)863{864 u64 adr = adr_link->adr_d[adr_index].adr;865 unsigned int sdw_version = SDW_VERSION(adr);866 unsigned int link_id = SDW_DISCO_LINK_ID(adr);867 unsigned int unique_id = SDW_UNIQUE_ID(adr);868 unsigned int mfg_id = SDW_MFG_ID(adr);869 unsigned int part_id = SDW_PART_ID(adr);870 unsigned int class_id = SDW_CLASS_ID(adr);871 872 if (codec_info->codec_name)873 return devm_kstrdup(dev, codec_info->codec_name, GFP_KERNEL);874 else if (asoc_sdw_is_unique_device(adr_link, sdw_version, mfg_id, part_id,875 class_id, adr_index))876 return devm_kasprintf(dev, GFP_KERNEL, "sdw:0:%01x:%04x:%04x:%02x",877 link_id, mfg_id, part_id, class_id);878 else879 return devm_kasprintf(dev, GFP_KERNEL, "sdw:0:%01x:%04x:%04x:%02x:%01x",880 link_id, mfg_id, part_id, class_id, unique_id);881 882 return NULL;883}884EXPORT_SYMBOL_NS(asoc_sdw_get_codec_name, SND_SOC_SDW_UTILS);885 886/* helper to get the link that the codec DAI is used */887struct snd_soc_dai_link *asoc_sdw_mc_find_codec_dai_used(struct snd_soc_card *card,888 const char *dai_name)889{890 struct snd_soc_dai_link *dai_link;891 int i;892 int j;893 894 for_each_card_prelinks(card, i, dai_link) {895 for (j = 0; j < dai_link->num_codecs; j++) {896 /* Check each codec in a link */897 if (!strcmp(dai_link->codecs[j].dai_name, dai_name))898 return dai_link;899 }900 }901 return NULL;902}903EXPORT_SYMBOL_NS(asoc_sdw_mc_find_codec_dai_used, SND_SOC_SDW_UTILS);904 905void asoc_sdw_mc_dailink_exit_loop(struct snd_soc_card *card)906{907 struct snd_soc_dai_link *dai_link;908 struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card);909 int ret;910 int i, j;911 912 for (i = 0; i < ctx->codec_info_list_count; i++) {913 for (j = 0; j < codec_info_list[i].dai_num; j++) {914 codec_info_list[i].dais[j].rtd_init_done = false;915 /* Check each dai in codec_info_lis to see if it is used in the link */916 if (!codec_info_list[i].dais[j].exit)917 continue;918 /*919 * We don't need to call .exit function if there is no matched920 * dai link found.921 */922 dai_link = asoc_sdw_mc_find_codec_dai_used(card,923 codec_info_list[i].dais[j].dai_name);924 if (dai_link) {925 /* Do the .exit function if the codec dai is used in the link */926 ret = codec_info_list[i].dais[j].exit(card, dai_link);927 if (ret)928 dev_warn(card->dev,929 "codec exit failed %d\n",930 ret);931 break;932 }933 }934 }935}936EXPORT_SYMBOL_NS(asoc_sdw_mc_dailink_exit_loop, SND_SOC_SDW_UTILS);937 938int asoc_sdw_card_late_probe(struct snd_soc_card *card)939{940 int ret = 0;941 int i;942 943 for (i = 0; i < ARRAY_SIZE(codec_info_list); i++) {944 if (codec_info_list[i].codec_card_late_probe) {945 ret = codec_info_list[i].codec_card_late_probe(card);946 if (ret < 0)947 return ret;948 }949 }950 return ret;951}952EXPORT_SYMBOL_NS(asoc_sdw_card_late_probe, SND_SOC_SDW_UTILS);953 954void asoc_sdw_init_dai_link(struct device *dev, struct snd_soc_dai_link *dai_links,955 int *be_id, char *name, int playback, int capture,956 struct snd_soc_dai_link_component *cpus, int cpus_num,957 struct snd_soc_dai_link_component *platform_component,958 int num_platforms, struct snd_soc_dai_link_component *codecs,959 int codecs_num, int (*init)(struct snd_soc_pcm_runtime *rtd),960 const struct snd_soc_ops *ops)961{962 dev_dbg(dev, "create dai link %s, id %d\n", name, *be_id);963 dai_links->id = (*be_id)++;964 dai_links->name = name;965 dai_links->platforms = platform_component;966 dai_links->num_platforms = num_platforms;967 dai_links->no_pcm = 1;968 dai_links->cpus = cpus;969 dai_links->num_cpus = cpus_num;970 dai_links->codecs = codecs;971 dai_links->num_codecs = codecs_num;972 dai_links->playback_only = playback && !capture;973 dai_links->capture_only = !playback && capture;974 dai_links->init = init;975 dai_links->ops = ops;976}977EXPORT_SYMBOL_NS(asoc_sdw_init_dai_link, SND_SOC_SDW_UTILS);978 979int asoc_sdw_init_simple_dai_link(struct device *dev, struct snd_soc_dai_link *dai_links,980 int *be_id, char *name, int playback, int capture,981 const char *cpu_dai_name, const char *platform_comp_name,982 int num_platforms, const char *codec_name,983 const char *codec_dai_name,984 int (*init)(struct snd_soc_pcm_runtime *rtd),985 const struct snd_soc_ops *ops)986{987 struct snd_soc_dai_link_component *dlc;988 989 /* Allocate three DLCs one for the CPU, one for platform and one for the CODEC */990 dlc = devm_kcalloc(dev, 3, sizeof(*dlc), GFP_KERNEL);991 if (!dlc || !name || !cpu_dai_name || !platform_comp_name || !codec_name || !codec_dai_name)992 return -ENOMEM;993 994 dlc[0].dai_name = cpu_dai_name;995 dlc[1].name = platform_comp_name;996 997 dlc[2].name = codec_name;998 dlc[2].dai_name = codec_dai_name;999 1000 asoc_sdw_init_dai_link(dev, dai_links, be_id, name, playback, capture,1001 &dlc[0], 1, &dlc[1], num_platforms,1002 &dlc[2], 1, init, ops);1003 1004 return 0;1005}1006EXPORT_SYMBOL_NS(asoc_sdw_init_simple_dai_link, SND_SOC_SDW_UTILS);1007 1008int asoc_sdw_count_sdw_endpoints(struct snd_soc_card *card, int *num_devs, int *num_ends)1009{1010 struct device *dev = card->dev;1011 struct snd_soc_acpi_mach *mach = dev_get_platdata(dev);1012 struct snd_soc_acpi_mach_params *mach_params = &mach->mach_params;1013 const struct snd_soc_acpi_link_adr *adr_link;1014 int i;1015 1016 for (adr_link = mach_params->links; adr_link->num_adr; adr_link++) {1017 *num_devs += adr_link->num_adr;1018 1019 for (i = 0; i < adr_link->num_adr; i++)1020 *num_ends += adr_link->adr_d[i].num_endpoints;1021 }1022 1023 dev_dbg(dev, "Found %d devices with %d endpoints\n", *num_devs, *num_ends);1024 1025 return 0;1026}1027EXPORT_SYMBOL_NS(asoc_sdw_count_sdw_endpoints, SND_SOC_SDW_UTILS);1028 1029struct asoc_sdw_dailink *asoc_sdw_find_dailink(struct asoc_sdw_dailink *dailinks,1030 const struct snd_soc_acpi_endpoint *new)1031{1032 while (dailinks->initialised) {1033 if (new->aggregated && dailinks->group_id == new->group_id)1034 return dailinks;1035 1036 dailinks++;1037 }1038 1039 INIT_LIST_HEAD(&dailinks->endpoints);1040 dailinks->group_id = new->group_id;1041 dailinks->initialised = true;1042 1043 return dailinks;1044}1045EXPORT_SYMBOL_NS(asoc_sdw_find_dailink, SND_SOC_SDW_UTILS);1046 1047int asoc_sdw_parse_sdw_endpoints(struct snd_soc_card *card,1048 struct asoc_sdw_dailink *soc_dais,1049 struct asoc_sdw_endpoint *soc_ends,1050 int *num_devs)1051{1052 struct device *dev = card->dev;1053 struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card);1054 struct snd_soc_acpi_mach *mach = dev_get_platdata(dev);1055 struct snd_soc_acpi_mach_params *mach_params = &mach->mach_params;1056 const struct snd_soc_acpi_link_adr *adr_link;1057 struct asoc_sdw_endpoint *soc_end = soc_ends;1058 int num_dais = 0;1059 int i, j;1060 int ret;1061 1062 for (adr_link = mach_params->links; adr_link->num_adr; adr_link++) {1063 int num_link_dailinks = 0;1064 1065 if (!is_power_of_2(adr_link->mask)) {1066 dev_err(dev, "link with multiple mask bits: 0x%x\n",1067 adr_link->mask);1068 return -EINVAL;1069 }1070 1071 for (i = 0; i < adr_link->num_adr; i++) {1072 const struct snd_soc_acpi_adr_device *adr_dev = &adr_link->adr_d[i];1073 struct asoc_sdw_codec_info *codec_info;1074 const char *codec_name;1075 1076 if (!adr_dev->name_prefix) {1077 dev_err(dev, "codec 0x%llx does not have a name prefix\n",1078 adr_dev->adr);1079 return -EINVAL;1080 }1081 1082 codec_info = asoc_sdw_find_codec_info_part(adr_dev->adr);1083 if (!codec_info)1084 return -EINVAL;1085 1086 ctx->ignore_internal_dmic |= codec_info->ignore_internal_dmic;1087 1088 codec_name = asoc_sdw_get_codec_name(dev, codec_info, adr_link, i);1089 if (!codec_name)1090 return -ENOMEM;1091 1092 dev_dbg(dev, "Adding prefix %s for %s\n",1093 adr_dev->name_prefix, codec_name);1094 1095 soc_end->name_prefix = adr_dev->name_prefix;1096 1097 if (codec_info->count_sidecar && codec_info->add_sidecar) {1098 ret = codec_info->count_sidecar(card, &num_dais, num_devs);1099 if (ret)1100 return ret;1101 1102 soc_end->include_sidecar = true;1103 }1104 1105 for (j = 0; j < adr_dev->num_endpoints; j++) {1106 const struct snd_soc_acpi_endpoint *adr_end;1107 const struct asoc_sdw_dai_info *dai_info;1108 struct asoc_sdw_dailink *soc_dai;1109 int stream;1110 1111 adr_end = &adr_dev->endpoints[j];1112 dai_info = &codec_info->dais[adr_end->num];1113 soc_dai = asoc_sdw_find_dailink(soc_dais, adr_end);1114 1115 if (dai_info->quirk && !(dai_info->quirk & ctx->mc_quirk))1116 continue;1117 1118 dev_dbg(dev,1119 "Add dev: %d, 0x%llx end: %d, dai: %d, %c/%c to %s: %d\n",1120 ffs(adr_link->mask) - 1, adr_dev->adr,1121 adr_end->num, dai_info->dai_type,1122 dai_info->direction[SNDRV_PCM_STREAM_PLAYBACK] ? 'P' : '-',1123 dai_info->direction[SNDRV_PCM_STREAM_CAPTURE] ? 'C' : '-',1124 adr_end->aggregated ? "group" : "solo",1125 adr_end->group_id);1126 1127 if (adr_end->num >= codec_info->dai_num) {1128 dev_err(dev,1129 "%d is too many endpoints for codec: 0x%x\n",1130 adr_end->num, codec_info->part_id);1131 return -EINVAL;1132 }1133 1134 for_each_pcm_streams(stream) {1135 if (dai_info->direction[stream] &&1136 dai_info->dailink[stream] < 0) {1137 dev_err(dev,1138 "Invalid dailink id %d for codec: 0x%x\n",1139 dai_info->dailink[stream],1140 codec_info->part_id);1141 return -EINVAL;1142 }1143 1144 if (dai_info->direction[stream]) {1145 num_dais += !soc_dai->num_devs[stream];1146 soc_dai->num_devs[stream]++;1147 soc_dai->link_mask[stream] |= adr_link->mask;1148 }1149 }1150 1151 num_link_dailinks += !!list_empty(&soc_dai->endpoints);1152 list_add_tail(&soc_end->list, &soc_dai->endpoints);1153 1154 soc_end->link_mask = adr_link->mask;1155 soc_end->codec_name = codec_name;1156 soc_end->codec_info = codec_info;1157 soc_end->dai_info = dai_info;1158 soc_end++;1159 }1160 }1161 1162 ctx->append_dai_type |= (num_link_dailinks > 1);1163 }1164 1165 return num_dais;1166}1167EXPORT_SYMBOL_NS(asoc_sdw_parse_sdw_endpoints, SND_SOC_SDW_UTILS);1168 1169MODULE_LICENSE("GPL");1170MODULE_DESCRIPTION("SoundWire ASoC helpers");1171