FILE
acp5x-mach.c
File preview with syntax highlighting.
linux-6.19.14/sound/soc/amd/vangogh/acp5x-mach.c
1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * Machine driver for AMD Vangogh platform using either 4 * NAU8821 & CS35L41 or NAU8821 & MAX98388 codecs. 5 * 6 * Copyright 2021 Advanced Micro Devices, Inc. 7 */ 8 9#include <linux/acpi.h> 10#include <linux/dmi.h> 11#include <linux/gpio/consumer.h> 12#include <linux/i2c.h> 13#include <linux/input-event-codes.h> 14#include <linux/module.h> 15#include <sound/jack.h> 16#include <sound/pcm_params.h> 17#include <sound/soc.h> 18#include <sound/soc-dapm.h> 19 20#include "../../codecs/nau8821.h" 21#include "acp5x.h" 22 23#define DRV_NAME "acp5x_mach" 24#define DUAL_CHANNEL 2 25#define ACP5X_NAU8821_BCLK 3072000 26#define ACP5X_NAU8821_FREQ_OUT 12288000 27#define ACP5X_NAU8821_COMP_NAME "i2c-NVTN2020:00" 28#define ACP5X_NAU8821_DAI_NAME "nau8821-hifi" 29#define ACP5X_CS35L41_COMP_LNAME "spi-VLV1776:00" 30#define ACP5X_CS35L41_COMP_RNAME "spi-VLV1776:01" 31#define ACP5X_CS35L41_DAI_NAME "cs35l41-pcm" 32#define ACP5X_MAX98388_COMP_LNAME "i2c-ADS8388:00" 33#define ACP5X_MAX98388_COMP_RNAME "i2c-ADS8388:01" 34#define ACP5X_MAX98388_DAI_NAME "max98388-aif1" 35 36static struct snd_soc_jack vg_headset; 37 38SND_SOC_DAILINK_DEF(platform, DAILINK_COMP_ARRAY(COMP_PLATFORM("acp5x_i2s_dma.0"))); 39SND_SOC_DAILINK_DEF(acp5x_i2s, DAILINK_COMP_ARRAY(COMP_CPU("acp5x_i2s_playcap.0"))); 40SND_SOC_DAILINK_DEF(acp5x_bt, DAILINK_COMP_ARRAY(COMP_CPU("acp5x_i2s_playcap.1"))); 41SND_SOC_DAILINK_DEF(nau8821, DAILINK_COMP_ARRAY(COMP_CODEC(ACP5X_NAU8821_COMP_NAME, 42 ACP5X_NAU8821_DAI_NAME))); 43 44static struct snd_soc_jack_pin acp5x_nau8821_jack_pins[] = { 45 { 46 .pin = "Headphone", 47 .mask = SND_JACK_HEADPHONE, 48 }, 49 { 50 .pin = "Headset Mic", 51 .mask = SND_JACK_MICROPHONE, 52 }, 53}; 54 55static const struct snd_kcontrol_new acp5x_8821_controls[] = { 56 SOC_DAPM_PIN_SWITCH("Headphone"), 57 SOC_DAPM_PIN_SWITCH("Headset Mic"), 58 SOC_DAPM_PIN_SWITCH("Int Mic"), 59}; 60 61static int platform_clock_control(struct snd_soc_dapm_widget *w, 62 struct snd_kcontrol *k, int event) 63{ 64 struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); 65 struct snd_soc_dai *dai; 66 int ret = 0; 67 68 dai = snd_soc_card_get_codec_dai(card, ACP5X_NAU8821_DAI_NAME); 69 if (!dai) { 70 dev_err(card->dev, "Codec dai not found\n"); 71 return -EIO; 72 } 73 74 if (SND_SOC_DAPM_EVENT_OFF(event)) { 75 ret = snd_soc_dai_set_sysclk(dai, NAU8821_CLK_INTERNAL, 0, SND_SOC_CLOCK_IN); 76 if (ret < 0) { 77 dev_err(card->dev, "set sysclk err = %d\n", ret); 78 return -EIO; 79 } 80 } else { 81 ret = snd_soc_dai_set_sysclk(dai, NAU8821_CLK_FLL_BLK, 0, SND_SOC_CLOCK_IN); 82 if (ret < 0) 83 dev_err(dai->dev, "can't set BLK clock %d\n", ret); 84 ret = snd_soc_dai_set_pll(dai, 0, 0, ACP5X_NAU8821_BCLK, ACP5X_NAU8821_FREQ_OUT); 85 if (ret < 0) 86 dev_err(dai->dev, "can't set FLL: %d\n", ret); 87 } 88 89 return ret; 90} 91 92static int acp5x_8821_init(struct snd_soc_pcm_runtime *rtd) 93{ 94 struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; 95 int ret; 96 97 /* 98 * Headset buttons map to the google Reference headset. 99 * These can be configured by userspace. 100 */ 101 ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", 102 SND_JACK_HEADSET | SND_JACK_BTN_0, 103 &vg_headset, acp5x_nau8821_jack_pins, 104 ARRAY_SIZE(acp5x_nau8821_jack_pins)); 105 if (ret) { 106 dev_err(rtd->dev, "Headset Jack creation failed %d\n", ret); 107 return ret; 108 } 109 110 snd_jack_set_key(vg_headset.jack, SND_JACK_BTN_0, KEY_MEDIA); 111 nau8821_enable_jack_detect(component, &vg_headset); 112 113 return ret; 114} 115 116static const unsigned int rates[] = { 117 48000, 118}; 119 120static const struct snd_pcm_hw_constraint_list constraints_rates = { 121 .count = ARRAY_SIZE(rates), 122 .list = rates, 123 .mask = 0, 124}; 125 126static const unsigned int channels[] = { 127 2, 128}; 129 130static const struct snd_pcm_hw_constraint_list constraints_channels = { 131 .count = ARRAY_SIZE(channels), 132 .list = channels, 133 .mask = 0, 134}; 135 136static const unsigned int acp5x_nau8821_format[] = {32}; 137 138static struct snd_pcm_hw_constraint_list constraints_sample_bits = { 139 .list = acp5x_nau8821_format, 140 .count = ARRAY_SIZE(acp5x_nau8821_format), 141}; 142 143static int acp5x_8821_startup(struct snd_pcm_substream *substream) 144{ 145 struct snd_pcm_runtime *runtime = substream->runtime; 146 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); 147 struct acp5x_platform_info *machine = snd_soc_card_get_drvdata(rtd->card); 148 149 machine->play_i2s_instance = I2S_SP_INSTANCE; 150 machine->cap_i2s_instance = I2S_SP_INSTANCE; 151 152 runtime->hw.channels_max = DUAL_CHANNEL; 153 snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, 154 &constraints_channels); 155 snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, 156 &constraints_rates); 157 snd_pcm_hw_constraint_list(substream->runtime, 0, 158 SNDRV_PCM_HW_PARAM_SAMPLE_BITS, 159 &constraints_sample_bits); 160 161 return 0; 162} 163 164static int acp5x_nau8821_hw_params(struct snd_pcm_substream *substream, 165 struct snd_pcm_hw_params *params) 166{ 167 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); 168 struct snd_soc_card *card = rtd->card; 169 struct snd_soc_dai *dai = snd_soc_card_get_codec_dai(card, ACP5X_NAU8821_DAI_NAME); 170 int ret, bclk; 171 172 if (!dai) 173 return -EINVAL; 174 175 ret = snd_soc_dai_set_sysclk(dai, NAU8821_CLK_FLL_BLK, 0, SND_SOC_CLOCK_IN); 176 if (ret < 0) 177 dev_err(card->dev, "can't set FS clock %d\n", ret); 178 179 bclk = snd_soc_params_to_bclk(params); 180 if (bclk < 0) { 181 dev_err(dai->dev, "Fail to get BCLK rate: %d\n", bclk); 182 return bclk; 183 } 184 185 ret = snd_soc_dai_set_pll(dai, 0, 0, bclk, params_rate(params) * 256); 186 if (ret < 0) 187 dev_err(card->dev, "can't set FLL: %d\n", ret); 188 189 return ret; 190} 191 192static const struct snd_soc_ops acp5x_8821_ops = { 193 .startup = acp5x_8821_startup, 194 .hw_params = acp5x_nau8821_hw_params, 195}; 196 197static int acp5x_cs35l41_startup(struct snd_pcm_substream *substream) 198{ 199 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); 200 struct acp5x_platform_info *machine = snd_soc_card_get_drvdata(rtd->card); 201 struct snd_pcm_runtime *runtime = substream->runtime; 202 203 machine->play_i2s_instance = I2S_HS_INSTANCE; 204 205 runtime->hw.channels_max = DUAL_CHANNEL; 206 snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, 207 &constraints_channels); 208 snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, 209 &constraints_rates); 210 211 return 0; 212} 213 214static int acp5x_cs35l41_hw_params(struct snd_pcm_substream *substream, 215 struct snd_pcm_hw_params *params) 216{ 217 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); 218 unsigned int bclk, rate = params_rate(params); 219 struct snd_soc_component *comp; 220 int ret, i; 221 222 switch (rate) { 223 case 48000: 224 bclk = 1536000; 225 break; 226 default: 227 bclk = 0; 228 break; 229 } 230 231 for_each_rtd_components(rtd, i, comp) { 232 if (!(strcmp(comp->name, ACP5X_CS35L41_COMP_LNAME)) || 233 !(strcmp(comp->name, ACP5X_CS35L41_COMP_RNAME))) { 234 if (!bclk) { 235 dev_err(comp->dev, "Invalid sample rate: 0x%x\n", rate); 236 return -EINVAL; 237 } 238 239 ret = snd_soc_component_set_sysclk(comp, 0, 0, bclk, SND_SOC_CLOCK_IN); 240 if (ret) { 241 dev_err(comp->dev, "failed to set SYSCLK: %d\n", ret); 242 return ret; 243 } 244 } 245 } 246 247 return 0; 248} 249 250static const struct snd_soc_ops acp5x_cs35l41_play_ops = { 251 .startup = acp5x_cs35l41_startup, 252 .hw_params = acp5x_cs35l41_hw_params, 253}; 254 255static struct snd_soc_codec_conf acp5x_cs35l41_conf[] = { 256 { 257 .dlc = COMP_CODEC_CONF(ACP5X_CS35L41_COMP_LNAME), 258 .name_prefix = "Left", 259 }, 260 { 261 .dlc = COMP_CODEC_CONF(ACP5X_CS35L41_COMP_RNAME), 262 .name_prefix = "Right", 263 }, 264}; 265 266SND_SOC_DAILINK_DEF(cs35l41, DAILINK_COMP_ARRAY(COMP_CODEC(ACP5X_CS35L41_COMP_LNAME, 267 ACP5X_CS35L41_DAI_NAME), 268 COMP_CODEC(ACP5X_CS35L41_COMP_RNAME, 269 ACP5X_CS35L41_DAI_NAME))); 270 271static struct snd_soc_dai_link acp5x_8821_35l41_dai[] = { 272 { 273 .name = "acp5x-8821-play", 274 .stream_name = "Playback/Capture", 275 .dai_fmt = SND_SOC_DAIFMT_I2S | 276 SND_SOC_DAIFMT_NB_NF | 277 SND_SOC_DAIFMT_CBC_CFC, 278 .ops = &acp5x_8821_ops, 279 .init = acp5x_8821_init, 280 SND_SOC_DAILINK_REG(acp5x_i2s, nau8821, platform), 281 }, 282 { 283 .name = "acp5x-CS35L41-Stereo", 284 .stream_name = "CS35L41 Stereo Playback", 285 .dai_fmt = SND_SOC_DAIFMT_I2S | 286 SND_SOC_DAIFMT_NB_NF | 287 SND_SOC_DAIFMT_CBC_CFC, 288 .playback_only = 1, 289 .ops = &acp5x_cs35l41_play_ops, 290 SND_SOC_DAILINK_REG(acp5x_bt, cs35l41, platform), 291 }, 292}; 293 294static const struct snd_soc_dapm_widget acp5x_8821_35l41_widgets[] = { 295 SND_SOC_DAPM_HP("Headphone", NULL), 296 SND_SOC_DAPM_MIC("Headset Mic", NULL), 297 SND_SOC_DAPM_MIC("Int Mic", NULL), 298 SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, 299 platform_clock_control, 300 SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), 301}; 302 303static const struct snd_soc_dapm_route acp5x_8821_35l41_audio_route[] = { 304 /* HP jack connectors - unknown if we have jack detection */ 305 { "Headphone", NULL, "HPOL" }, 306 { "Headphone", NULL, "HPOR" }, 307 { "MICL", NULL, "Headset Mic" }, 308 { "MICR", NULL, "Headset Mic" }, 309 { "DMIC", NULL, "Int Mic" }, 310 311 { "Headphone", NULL, "Platform Clock" }, 312 { "Headset Mic", NULL, "Platform Clock" }, 313 { "Int Mic", NULL, "Platform Clock" }, 314}; 315 316static struct snd_soc_card acp5x_8821_35l41_card = { 317 .name = "acp5x", 318 .owner = THIS_MODULE, 319 .dai_link = acp5x_8821_35l41_dai, 320 .num_links = ARRAY_SIZE(acp5x_8821_35l41_dai), 321 .dapm_widgets = acp5x_8821_35l41_widgets, 322 .num_dapm_widgets = ARRAY_SIZE(acp5x_8821_35l41_widgets), 323 .dapm_routes = acp5x_8821_35l41_audio_route, 324 .num_dapm_routes = ARRAY_SIZE(acp5x_8821_35l41_audio_route), 325 .codec_conf = acp5x_cs35l41_conf, 326 .num_configs = ARRAY_SIZE(acp5x_cs35l41_conf), 327 .controls = acp5x_8821_controls, 328 .num_controls = ARRAY_SIZE(acp5x_8821_controls), 329}; 330 331static int acp5x_max98388_startup(struct snd_pcm_substream *substream) 332{ 333 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); 334 struct acp5x_platform_info *machine = snd_soc_card_get_drvdata(rtd->card); 335 struct snd_pcm_runtime *runtime = substream->runtime; 336 337 machine->play_i2s_instance = I2S_HS_INSTANCE; 338 339 runtime->hw.channels_max = DUAL_CHANNEL; 340 snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, 341 &constraints_channels); 342 snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, 343 &constraints_rates); 344 return 0; 345} 346 347static const struct snd_soc_ops acp5x_max98388_play_ops = { 348 .startup = acp5x_max98388_startup, 349}; 350 351static struct snd_soc_codec_conf acp5x_max98388_conf[] = { 352 { 353 .dlc = COMP_CODEC_CONF(ACP5X_MAX98388_COMP_LNAME), 354 .name_prefix = "Left", 355 }, 356 { 357 .dlc = COMP_CODEC_CONF(ACP5X_MAX98388_COMP_RNAME), 358 .name_prefix = "Right", 359 }, 360}; 361 362SND_SOC_DAILINK_DEF(max98388, DAILINK_COMP_ARRAY(COMP_CODEC(ACP5X_MAX98388_COMP_LNAME, 363 ACP5X_MAX98388_DAI_NAME), 364 COMP_CODEC(ACP5X_MAX98388_COMP_RNAME, 365 ACP5X_MAX98388_DAI_NAME))); 366 367static struct snd_soc_dai_link acp5x_8821_98388_dai[] = { 368 { 369 .name = "acp5x-8821-play", 370 .stream_name = "Playback/Capture", 371 .dai_fmt = SND_SOC_DAIFMT_I2S | 372 SND_SOC_DAIFMT_NB_NF | 373 SND_SOC_DAIFMT_CBC_CFC, 374 .ops = &acp5x_8821_ops, 375 .init = acp5x_8821_init, 376 SND_SOC_DAILINK_REG(acp5x_i2s, nau8821, platform), 377 }, 378 { 379 .name = "acp5x-max98388-play", 380 .stream_name = "MAX98388 Playback", 381 .dai_fmt = SND_SOC_DAIFMT_I2S | 382 SND_SOC_DAIFMT_NB_NF | 383 SND_SOC_DAIFMT_CBC_CFC, 384 .playback_only = 1, 385 .ops = &acp5x_max98388_play_ops, 386 SND_SOC_DAILINK_REG(acp5x_bt, max98388, platform), 387 }, 388}; 389 390static const struct snd_soc_dapm_widget acp5x_8821_98388_widgets[] = { 391 SND_SOC_DAPM_HP("Headphone", NULL), 392 SND_SOC_DAPM_MIC("Headset Mic", NULL), 393 SND_SOC_DAPM_MIC("Int Mic", NULL), 394 SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, 395 platform_clock_control, 396 SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), 397 SND_SOC_DAPM_SPK("SPK", NULL), 398}; 399 400static const struct snd_soc_dapm_route acp5x_8821_98388_route[] = { 401 { "Headphone", NULL, "HPOL" }, 402 { "Headphone", NULL, "HPOR" }, 403 { "MICL", NULL, "Headset Mic" }, 404 { "MICR", NULL, "Headset Mic" }, 405 { "DMIC", NULL, "Int Mic" }, 406 407 { "Headphone", NULL, "Platform Clock" }, 408 { "Headset Mic", NULL, "Platform Clock" }, 409 { "Int Mic", NULL, "Platform Clock" }, 410 411 { "SPK", NULL, "Left BE_OUT" }, 412 { "SPK", NULL, "Right BE_OUT" }, 413}; 414 415static struct snd_soc_card acp5x_8821_98388_card = { 416 .name = "acp5x-max98388", 417 .owner = THIS_MODULE, 418 .dai_link = acp5x_8821_98388_dai, 419 .num_links = ARRAY_SIZE(acp5x_8821_98388_dai), 420 .dapm_widgets = acp5x_8821_98388_widgets, 421 .num_dapm_widgets = ARRAY_SIZE(acp5x_8821_98388_widgets), 422 .dapm_routes = acp5x_8821_98388_route, 423 .num_dapm_routes = ARRAY_SIZE(acp5x_8821_98388_route), 424 .codec_conf = acp5x_max98388_conf, 425 .num_configs = ARRAY_SIZE(acp5x_max98388_conf), 426 .controls = acp5x_8821_controls, 427 .num_controls = ARRAY_SIZE(acp5x_8821_controls), 428}; 429 430static const struct dmi_system_id acp5x_vg_quirk_table[] = { 431 { 432 .matches = { 433 DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Valve"), 434 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Jupiter"), 435 }, 436 .driver_data = (void *)&acp5x_8821_35l41_card, 437 }, 438 { 439 .matches = { 440 DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Valve"), 441 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Galileo"), 442 }, 443 .driver_data = (void *)&acp5x_8821_98388_card, 444 }, 445 {} 446}; 447 448static int acp5x_probe(struct platform_device *pdev) 449{ 450 const struct dmi_system_id *dmi_id; 451 struct acp5x_platform_info *machine; 452 struct device *dev = &pdev->dev; 453 struct snd_soc_card *card; 454 int ret; 455 456 dmi_id = dmi_first_match(acp5x_vg_quirk_table); 457 if (!dmi_id || !dmi_id->driver_data) 458 return -ENODEV; 459 460 machine = devm_kzalloc(dev, sizeof(*machine), GFP_KERNEL); 461 if (!machine) 462 return -ENOMEM; 463 464 card = dmi_id->driver_data; 465 card->dev = dev; 466 platform_set_drvdata(pdev, card); 467 snd_soc_card_set_drvdata(card, machine); 468 469 ret = devm_snd_soc_register_card(dev, card); 470 if (ret) 471 return dev_err_probe(dev, ret, "Register card (%s) failed\n", card->name); 472 473 return 0; 474} 475 476static struct platform_driver acp5x_mach_driver = { 477 .driver = { 478 .name = DRV_NAME, 479 .pm = &snd_soc_pm_ops, 480 }, 481 .probe = acp5x_probe, 482}; 483 484module_platform_driver(acp5x_mach_driver); 485 486MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); 487MODULE_DESCRIPTION("NAU8821/CS35L41 & NAU8821/MAX98388 audio support"); 488MODULE_LICENSE("GPL v2"); 489MODULE_ALIAS("platform:" DRV_NAME);
~/
Read-only file preview.