1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Machine driver for AMD Vangogh platform using NAU8821 & CS35L41 4 * 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 VG_JUPITER 1 26 #define ACP5X_NAU8821_BCLK 3072000 27 #define ACP5X_NAU8821_FREQ_OUT 12288000 28 #define ACP5X_NAU8821_COMP_NAME "i2c-NVTN2020:00" 29 #define ACP5X_NAU8821_DAI_NAME "nau8821-hifi" 30 #define ACP5X_CS35L41_COMP_LNAME "spi-VLV1776:00" 31 #define ACP5X_CS35L41_COMP_RNAME "spi-VLV1776:01" 32 #define ACP5X_CS35L41_DAI_NAME "cs35l41-pcm" 33 34 static unsigned long acp5x_machine_id; 35 static struct snd_soc_jack vg_headset; 36 37 SND_SOC_DAILINK_DEF(platform, DAILINK_COMP_ARRAY(COMP_PLATFORM("acp5x_i2s_dma.0"))); 38 SND_SOC_DAILINK_DEF(acp5x_i2s, DAILINK_COMP_ARRAY(COMP_CPU("acp5x_i2s_playcap.0"))); 39 SND_SOC_DAILINK_DEF(acp5x_bt, DAILINK_COMP_ARRAY(COMP_CPU("acp5x_i2s_playcap.1"))); 40 SND_SOC_DAILINK_DEF(nau8821, DAILINK_COMP_ARRAY(COMP_CODEC(ACP5X_NAU8821_COMP_NAME, 41 ACP5X_NAU8821_DAI_NAME))); 42 43 static struct snd_soc_jack_pin acp5x_nau8821_jack_pins[] = { 44 { 45 .pin = "Headphone", 46 .mask = SND_JACK_HEADPHONE, 47 }, 48 { 49 .pin = "Headset Mic", 50 .mask = SND_JACK_MICROPHONE, 51 }, 52 }; 53 54 static const struct snd_kcontrol_new acp5x_8821_controls[] = { 55 SOC_DAPM_PIN_SWITCH("Headphone"), 56 SOC_DAPM_PIN_SWITCH("Headset Mic"), 57 SOC_DAPM_PIN_SWITCH("Int Mic"), 58 }; 59 60 static int platform_clock_control(struct snd_soc_dapm_widget *w, 61 struct snd_kcontrol *k, int event) 62 { 63 struct snd_soc_dapm_context *dapm = w->dapm; 64 struct snd_soc_card *card = dapm->card; 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 92 static int acp5x_8821_init(struct snd_soc_pcm_runtime *rtd) 93 { 94 struct snd_soc_component *component = asoc_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 116 static const unsigned int rates[] = { 117 48000, 118 }; 119 120 static const struct snd_pcm_hw_constraint_list constraints_rates = { 121 .count = ARRAY_SIZE(rates), 122 .list = rates, 123 .mask = 0, 124 }; 125 126 static const unsigned int channels[] = { 127 2, 128 }; 129 130 static const struct snd_pcm_hw_constraint_list constraints_channels = { 131 .count = ARRAY_SIZE(channels), 132 .list = channels, 133 .mask = 0, 134 }; 135 136 static const unsigned int acp5x_nau8821_format[] = {32}; 137 138 static struct snd_pcm_hw_constraint_list constraints_sample_bits = { 139 .list = acp5x_nau8821_format, 140 .count = ARRAY_SIZE(acp5x_nau8821_format), 141 }; 142 143 static 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 = asoc_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 164 static 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 = asoc_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 ret = snd_soc_dai_set_sysclk(dai, NAU8821_CLK_FLL_BLK, 0, SND_SOC_CLOCK_IN); 173 if (ret < 0) 174 dev_err(card->dev, "can't set FS clock %d\n", ret); 175 176 bclk = snd_soc_params_to_bclk(params); 177 if (bclk < 0) { 178 dev_err(dai->dev, "Fail to get BCLK rate: %d\n", bclk); 179 return bclk; 180 } 181 182 ret = snd_soc_dai_set_pll(dai, 0, 0, bclk, params_rate(params) * 256); 183 if (ret < 0) 184 dev_err(card->dev, "can't set FLL: %d\n", ret); 185 186 return ret; 187 } 188 189 static const struct snd_soc_ops acp5x_8821_ops = { 190 .startup = acp5x_8821_startup, 191 .hw_params = acp5x_nau8821_hw_params, 192 }; 193 194 static int acp5x_cs35l41_startup(struct snd_pcm_substream *substream) 195 { 196 struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 197 struct acp5x_platform_info *machine = snd_soc_card_get_drvdata(rtd->card); 198 struct snd_pcm_runtime *runtime = substream->runtime; 199 200 machine->play_i2s_instance = I2S_HS_INSTANCE; 201 202 runtime->hw.channels_max = DUAL_CHANNEL; 203 snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, 204 &constraints_channels); 205 snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, 206 &constraints_rates); 207 208 return 0; 209 } 210 211 static int acp5x_cs35l41_hw_params(struct snd_pcm_substream *substream, 212 struct snd_pcm_hw_params *params) 213 { 214 struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 215 unsigned int bclk, rate = params_rate(params); 216 struct snd_soc_component *comp; 217 int ret, i; 218 219 switch (rate) { 220 case 48000: 221 bclk = 1536000; 222 break; 223 default: 224 bclk = 0; 225 break; 226 } 227 228 for_each_rtd_components(rtd, i, comp) { 229 if (!(strcmp(comp->name, ACP5X_CS35L41_COMP_LNAME)) || 230 !(strcmp(comp->name, ACP5X_CS35L41_COMP_RNAME))) { 231 if (!bclk) { 232 dev_err(comp->dev, "Invalid sample rate: 0x%x\n", rate); 233 return -EINVAL; 234 } 235 236 ret = snd_soc_component_set_sysclk(comp, 0, 0, bclk, SND_SOC_CLOCK_IN); 237 if (ret) { 238 dev_err(comp->dev, "failed to set SYSCLK: %d\n", ret); 239 return ret; 240 } 241 } 242 } 243 244 return 0; 245 246 } 247 248 static const struct snd_soc_ops acp5x_cs35l41_play_ops = { 249 .startup = acp5x_cs35l41_startup, 250 .hw_params = acp5x_cs35l41_hw_params, 251 }; 252 253 static struct snd_soc_codec_conf acp5x_cs35l41_conf[] = { 254 { 255 .dlc = COMP_CODEC_CONF(ACP5X_CS35L41_COMP_LNAME), 256 .name_prefix = "Left", 257 }, 258 { 259 .dlc = COMP_CODEC_CONF(ACP5X_CS35L41_COMP_RNAME), 260 .name_prefix = "Right", 261 }, 262 }; 263 264 SND_SOC_DAILINK_DEF(cs35l41, DAILINK_COMP_ARRAY(COMP_CODEC(ACP5X_CS35L41_COMP_LNAME, 265 ACP5X_CS35L41_DAI_NAME), 266 COMP_CODEC(ACP5X_CS35L41_COMP_RNAME, 267 ACP5X_CS35L41_DAI_NAME))); 268 269 static struct snd_soc_dai_link acp5x_8821_35l41_dai[] = { 270 { 271 .name = "acp5x-8821-play", 272 .stream_name = "Playback/Capture", 273 .dai_fmt = SND_SOC_DAIFMT_I2S | 274 SND_SOC_DAIFMT_NB_NF | 275 SND_SOC_DAIFMT_CBC_CFC, 276 .dpcm_playback = 1, 277 .dpcm_capture = 1, 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 .dpcm_playback = 1, 289 .playback_only = 1, 290 .ops = &acp5x_cs35l41_play_ops, 291 SND_SOC_DAILINK_REG(acp5x_bt, cs35l41, platform), 292 }, 293 }; 294 295 296 297 static const struct snd_soc_dapm_widget acp5x_8821_35l41_widgets[] = { 298 SND_SOC_DAPM_HP("Headphone", NULL), 299 SND_SOC_DAPM_MIC("Headset Mic", NULL), 300 SND_SOC_DAPM_MIC("Int Mic", NULL), 301 SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, 302 platform_clock_control, 303 SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), 304 }; 305 306 static const struct snd_soc_dapm_route acp5x_8821_35l41_audio_route[] = { 307 /* HP jack connectors - unknown if we have jack detection */ 308 { "Headphone", NULL, "HPOL" }, 309 { "Headphone", NULL, "HPOR" }, 310 { "MICL", NULL, "Headset Mic" }, 311 { "MICR", NULL, "Headset Mic" }, 312 { "DMIC", NULL, "Int Mic" }, 313 314 { "Headphone", NULL, "Platform Clock" }, 315 { "Headset Mic", NULL, "Platform Clock" }, 316 { "Int Mic", NULL, "Platform Clock" }, 317 }; 318 319 static struct snd_soc_card acp5x_8821_35l41_card = { 320 .name = "acp5x", 321 .owner = THIS_MODULE, 322 .dai_link = acp5x_8821_35l41_dai, 323 .num_links = ARRAY_SIZE(acp5x_8821_35l41_dai), 324 .dapm_widgets = acp5x_8821_35l41_widgets, 325 .num_dapm_widgets = ARRAY_SIZE(acp5x_8821_35l41_widgets), 326 .dapm_routes = acp5x_8821_35l41_audio_route, 327 .num_dapm_routes = ARRAY_SIZE(acp5x_8821_35l41_audio_route), 328 .codec_conf = acp5x_cs35l41_conf, 329 .num_configs = ARRAY_SIZE(acp5x_cs35l41_conf), 330 .controls = acp5x_8821_controls, 331 .num_controls = ARRAY_SIZE(acp5x_8821_controls), 332 }; 333 334 static int acp5x_vg_quirk_cb(const struct dmi_system_id *id) 335 { 336 acp5x_machine_id = VG_JUPITER; 337 338 return 1; 339 } 340 341 static const struct dmi_system_id acp5x_vg_quirk_table[] = { 342 { 343 .callback = acp5x_vg_quirk_cb, 344 .matches = { 345 DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Valve"), 346 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Jupiter"), 347 } 348 }, 349 {} 350 }; 351 352 static int acp5x_probe(struct platform_device *pdev) 353 { 354 struct acp5x_platform_info *machine; 355 struct device *dev = &pdev->dev; 356 struct snd_soc_card *card; 357 int ret; 358 359 machine = devm_kzalloc(dev, sizeof(*machine), GFP_KERNEL); 360 if (!machine) 361 return -ENOMEM; 362 363 dmi_check_system(acp5x_vg_quirk_table); 364 switch (acp5x_machine_id) { 365 case VG_JUPITER: 366 card = &acp5x_8821_35l41_card; 367 break; 368 default: 369 return -ENODEV; 370 } 371 card->dev = dev; 372 platform_set_drvdata(pdev, card); 373 snd_soc_card_set_drvdata(card, machine); 374 375 ret = devm_snd_soc_register_card(dev, card); 376 if (ret) 377 return dev_err_probe(dev, ret, "Register card (%s) failed\n", card->name); 378 379 return 0; 380 } 381 382 static struct platform_driver acp5x_mach_driver = { 383 .driver = { 384 .name = "acp5x_mach", 385 .pm = &snd_soc_pm_ops, 386 }, 387 .probe = acp5x_probe, 388 }; 389 390 module_platform_driver(acp5x_mach_driver); 391 392 MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); 393 MODULE_DESCRIPTION("NAU8821 & CS35L41 audio support"); 394 MODULE_LICENSE("GPL v2"); 395 MODULE_ALIAS("platform:" DRV_NAME); 396