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 <sound/soc.h> 10 #include <sound/soc-dapm.h> 11 #include <linux/module.h> 12 #include <linux/io.h> 13 #include <sound/pcm.h> 14 #include <sound/pcm_params.h> 15 16 #include <sound/jack.h> 17 #include <linux/clk.h> 18 #include <linux/gpio.h> 19 #include <linux/gpio/consumer.h> 20 #include <linux/module.h> 21 #include <linux/i2c.h> 22 #include <linux/input.h> 23 #include <linux/io.h> 24 #include <linux/acpi.h> 25 #include <linux/dmi.h> 26 27 #include "../../codecs/nau8821.h" 28 #include "../../codecs/cs35l41.h" 29 30 #include "acp5x.h" 31 32 #define DRV_NAME "acp5x_mach" 33 #define DUAL_CHANNEL 2 34 #define ACP5X_NUVOTON_CODEC_DAI "nau8821-hifi" 35 #define VG_JUPITER 1 36 #define ACP5X_NUVOTON_BCLK 3072000 37 #define ACP5X_NAU8821_FREQ_OUT 12288000 38 39 static unsigned long acp5x_machine_id; 40 static struct snd_soc_jack vg_headset; 41 42 static struct snd_soc_jack_pin acp5x_nau8821_jack_pins[] = { 43 { 44 .pin = "Headphone", 45 .mask = SND_JACK_HEADPHONE, 46 }, 47 { 48 .pin = "Headset Mic", 49 .mask = SND_JACK_MICROPHONE, 50 }, 51 }; 52 53 static int acp5x_8821_init(struct snd_soc_pcm_runtime *rtd) 54 { 55 int ret; 56 struct snd_soc_card *card = rtd->card; 57 struct snd_soc_component *component = 58 asoc_rtd_to_codec(rtd, 0)->component; 59 60 /* 61 * Headset buttons map to the google Reference headset. 62 * These can be configured by userspace. 63 */ 64 ret = snd_soc_card_jack_new(card, "Headset Jack", 65 SND_JACK_HEADSET | SND_JACK_BTN_0, 66 &vg_headset, acp5x_nau8821_jack_pins, 67 ARRAY_SIZE(acp5x_nau8821_jack_pins)); 68 if (ret) { 69 dev_err(rtd->dev, "Headset Jack creation failed %d\n", ret); 70 return ret; 71 } 72 73 snd_jack_set_key(vg_headset.jack, SND_JACK_BTN_0, KEY_MEDIA); 74 nau8821_enable_jack_detect(component, &vg_headset); 75 return ret; 76 } 77 78 static int acp5x_cs35l41_init(struct snd_soc_pcm_runtime *rtd) 79 { 80 return 0; 81 } 82 83 static const unsigned int rates[] = { 84 48000, 85 }; 86 87 static const struct snd_pcm_hw_constraint_list constraints_rates = { 88 .count = ARRAY_SIZE(rates), 89 .list = rates, 90 .mask = 0, 91 }; 92 93 static const unsigned int channels[] = { 94 2, 95 }; 96 97 static const struct snd_pcm_hw_constraint_list constraints_channels = { 98 .count = ARRAY_SIZE(channels), 99 .list = channels, 100 .mask = 0, 101 }; 102 103 static int acp5x_8821_startup(struct snd_pcm_substream *substream) 104 { 105 struct snd_pcm_runtime *runtime = substream->runtime; 106 struct snd_soc_pcm_runtime *rtd = substream->private_data; 107 struct snd_soc_card *card = rtd->card; 108 struct acp5x_platform_info *machine = snd_soc_card_get_drvdata(card); 109 110 machine->play_i2s_instance = I2S_SP_INSTANCE; 111 machine->cap_i2s_instance = I2S_SP_INSTANCE; 112 113 runtime->hw.channels_max = DUAL_CHANNEL; 114 snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, 115 &constraints_channels); 116 snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, 117 &constraints_rates); 118 return 0; 119 } 120 121 static int acp5x_nau8821_hw_params(struct snd_pcm_substream *substream, 122 struct snd_pcm_hw_params *params) 123 { 124 struct snd_soc_pcm_runtime *rtd = substream->private_data; 125 struct snd_soc_card *card = rtd->card; 126 struct snd_soc_dai *codec_dai = 127 snd_soc_card_get_codec_dai(card, 128 ACP5X_NUVOTON_CODEC_DAI); 129 int ret; 130 131 ret = snd_soc_dai_set_sysclk(codec_dai, NAU8821_CLK_FLL_BLK, 0, 132 SND_SOC_CLOCK_IN); 133 if (ret < 0) 134 dev_err(card->dev, "can't set FS clock %d\n", ret); 135 ret = snd_soc_dai_set_pll(codec_dai, 0, 0, snd_soc_params_to_bclk(params), 136 params_rate(params) * 256); 137 if (ret < 0) 138 dev_err(card->dev, "can't set FLL: %d\n", ret); 139 140 return ret; 141 } 142 143 static int acp5x_cs35l41_startup(struct snd_pcm_substream *substream) 144 { 145 struct snd_pcm_runtime *runtime = substream->runtime; 146 struct snd_soc_pcm_runtime *rtd = substream->private_data; 147 struct snd_soc_card *card = rtd->card; 148 struct acp5x_platform_info *machine = snd_soc_card_get_drvdata(card); 149 150 machine->play_i2s_instance = I2S_HS_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 return 0; 158 } 159 160 static int acp5x_cs35l41_hw_params(struct snd_pcm_substream *substream, 161 struct snd_pcm_hw_params *params) 162 { 163 struct snd_soc_pcm_runtime *rtd = substream->private_data; 164 struct snd_soc_card *card = rtd->card; 165 struct snd_soc_dai *codec_dai; 166 int ret, i; 167 unsigned int num_codecs = rtd->num_codecs; 168 unsigned int bclk_val; 169 170 for (i = 0; i < num_codecs; i++) { 171 codec_dai = asoc_rtd_to_codec(rtd, i); 172 if ((strcmp(codec_dai->name, "spi-VLV1776:00") == 0) || 173 (strcmp(codec_dai->name, "spi-VLV1776:01") == 0)) { 174 switch (params_rate(params)) { 175 case 48000: 176 bclk_val = 1536000; 177 break; 178 default: 179 dev_err(card->dev, "Invalid Samplerate:0x%x\n", 180 params_rate(params)); 181 return -EINVAL; 182 } 183 ret = snd_soc_component_set_sysclk(codec_dai->component, 184 0, 0, bclk_val, SND_SOC_CLOCK_IN); 185 if (ret < 0) { 186 dev_err(card->dev, "failed to set sysclk for CS35l41 dai\n"); 187 return ret; 188 } 189 } 190 } 191 192 return ret; 193 } 194 195 static const struct snd_soc_ops acp5x_8821_ops = { 196 .startup = acp5x_8821_startup, 197 .hw_params = acp5x_nau8821_hw_params, 198 }; 199 200 static const struct snd_soc_ops acp5x_cs35l41_play_ops = { 201 .startup = acp5x_cs35l41_startup, 202 .hw_params = acp5x_cs35l41_hw_params, 203 }; 204 205 static struct snd_soc_codec_conf cs35l41_conf[] = { 206 { 207 .dlc = COMP_CODEC_CONF("spi-VLV1776:00"), 208 .name_prefix = "Left", 209 }, 210 { 211 .dlc = COMP_CODEC_CONF("spi-VLV1776:01"), 212 .name_prefix = "Right", 213 }, 214 }; 215 216 SND_SOC_DAILINK_DEF(acp5x_i2s, 217 DAILINK_COMP_ARRAY(COMP_CPU("acp5x_i2s_playcap.0"))); 218 219 SND_SOC_DAILINK_DEF(acp5x_bt, 220 DAILINK_COMP_ARRAY(COMP_CPU("acp5x_i2s_playcap.1"))); 221 222 SND_SOC_DAILINK_DEF(nau8821, 223 DAILINK_COMP_ARRAY(COMP_CODEC("i2c-NVTN2020:00", 224 "nau8821-hifi"))); 225 226 SND_SOC_DAILINK_DEF(cs35l41, 227 DAILINK_COMP_ARRAY(COMP_CODEC("spi-VLV1776:00", "cs35l41-pcm"), 228 COMP_CODEC("spi-VLV1776:01", "cs35l41-pcm"))); 229 230 SND_SOC_DAILINK_DEF(platform, 231 DAILINK_COMP_ARRAY(COMP_PLATFORM("acp5x_i2s_dma.0"))); 232 233 static struct snd_soc_dai_link acp5x_dai[] = { 234 { 235 .name = "acp5x-8825-play", 236 .stream_name = "Playback/Capture", 237 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 238 SND_SOC_DAIFMT_CBC_CFC, 239 .dpcm_playback = 1, 240 .dpcm_capture = 1, 241 .ops = &acp5x_8821_ops, 242 .init = acp5x_8821_init, 243 SND_SOC_DAILINK_REG(acp5x_i2s, nau8821, platform), 244 }, 245 { 246 .name = "acp5x-CS35L41-Stereo", 247 .stream_name = "CS35L41 Stereo Playback", 248 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 249 SND_SOC_DAIFMT_CBC_CFC, 250 .dpcm_playback = 1, 251 .playback_only = 1, 252 .ops = &acp5x_cs35l41_play_ops, 253 .init = acp5x_cs35l41_init, 254 SND_SOC_DAILINK_REG(acp5x_bt, cs35l41, platform), 255 }, 256 }; 257 258 static int platform_clock_control(struct snd_soc_dapm_widget *w, 259 struct snd_kcontrol *k, int event) 260 { 261 struct snd_soc_dapm_context *dapm = w->dapm; 262 struct snd_soc_card *card = dapm->card; 263 struct snd_soc_dai *codec_dai; 264 int ret = 0; 265 266 codec_dai = snd_soc_card_get_codec_dai(card, ACP5X_NUVOTON_CODEC_DAI); 267 if (!codec_dai) { 268 dev_err(card->dev, "Codec dai not found\n"); 269 return -EIO; 270 } 271 272 if (SND_SOC_DAPM_EVENT_OFF(event)) { 273 ret = snd_soc_dai_set_sysclk(codec_dai, NAU8821_CLK_INTERNAL, 274 0, SND_SOC_CLOCK_IN); 275 if (ret < 0) { 276 dev_err(card->dev, "set sysclk err = %d\n", ret); 277 return -EIO; 278 } 279 } else { 280 ret = snd_soc_dai_set_sysclk(codec_dai, NAU8821_CLK_FLL_BLK, 0, 281 SND_SOC_CLOCK_IN); 282 if (ret < 0) 283 dev_err(codec_dai->dev, "can't set BLK clock %d\n", ret); 284 ret = snd_soc_dai_set_pll(codec_dai, 0, 0, ACP5X_NUVOTON_BCLK, 285 ACP5X_NAU8821_FREQ_OUT); 286 if (ret < 0) 287 dev_err(codec_dai->dev, "can't set FLL: %d\n", ret); 288 } 289 return ret; 290 } 291 292 static const struct snd_kcontrol_new acp5x_8821_controls[] = { 293 SOC_DAPM_PIN_SWITCH("Headphone"), 294 SOC_DAPM_PIN_SWITCH("Headset Mic"), 295 SOC_DAPM_PIN_SWITCH("Int Mic"), 296 }; 297 298 static const struct snd_soc_dapm_widget acp5x_8821_widgets[] = { 299 SND_SOC_DAPM_HP("Headphone", NULL), 300 SND_SOC_DAPM_MIC("Headset Mic", NULL), 301 SND_SOC_DAPM_MIC("Int Mic", NULL), 302 SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, 303 platform_clock_control, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), 304 }; 305 306 static const struct snd_soc_dapm_route acp5x_8821_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_card = { 320 .name = "acp5x", 321 .owner = THIS_MODULE, 322 .dai_link = acp5x_dai, 323 .num_links = ARRAY_SIZE(acp5x_dai), 324 .dapm_widgets = acp5x_8821_widgets, 325 .num_dapm_widgets = ARRAY_SIZE(acp5x_8821_widgets), 326 .dapm_routes = acp5x_8821_audio_route, 327 .num_dapm_routes = ARRAY_SIZE(acp5x_8821_audio_route), 328 .codec_conf = cs35l41_conf, 329 .num_configs = ARRAY_SIZE(cs35l41_conf), 330 .controls = acp5x_8821_controls, 331 .num_controls = ARRAY_SIZE(acp5x_8821_controls), 332 }; 333 334 335 static int acp5x_vg_quirk_cb(const struct dmi_system_id *id) 336 { 337 acp5x_machine_id = VG_JUPITER; 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 int ret; 355 struct acp5x_platform_info *machine; 356 struct snd_soc_card *card; 357 358 machine = devm_kzalloc(&pdev->dev, sizeof(struct acp5x_platform_info), 359 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_card; 367 acp5x_card.dev = &pdev->dev; 368 break; 369 default: 370 return -ENODEV; 371 } 372 platform_set_drvdata(pdev, card); 373 snd_soc_card_set_drvdata(card, machine); 374 375 ret = devm_snd_soc_register_card(&pdev->dev, card); 376 if (ret) { 377 return dev_err_probe(&pdev->dev, ret, 378 "snd_soc_register_card(%s) failed\n", 379 acp5x_card.name); 380 } 381 return 0; 382 } 383 384 static struct platform_driver acp5x_mach_driver = { 385 .driver = { 386 .name = "acp5x_mach", 387 .pm = &snd_soc_pm_ops, 388 }, 389 .probe = acp5x_probe, 390 }; 391 392 module_platform_driver(acp5x_mach_driver); 393 394 MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); 395 MODULE_DESCRIPTION("NAU8821 & CS35L41 audio support"); 396 MODULE_LICENSE("GPL v2"); 397 MODULE_ALIAS("platform:" DRV_NAME); 398