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