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_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, clk_id: NAU8821_CLK_INTERNAL, freq: 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, clk_id: NAU8821_CLK_FLL_BLK, freq: 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, pll_id: 0, source: 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
93static 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(card: rtd->card, id: "Headset Jack",
103 type: SND_JACK_HEADSET | SND_JACK_BTN_0,
104 jack: &vg_headset, pins: 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(jack: vg_headset.jack, type: SND_JACK_BTN_0, KEY_MEDIA);
112 nau8821_enable_jack_detect(component, jack: &vg_headset);
113
114 return ret;
115}
116
117static const unsigned int rates[] = {
118 48000,
119};
120
121static const struct snd_pcm_hw_constraint_list constraints_rates = {
122 .count = ARRAY_SIZE(rates),
123 .list = rates,
124 .mask = 0,
125};
126
127static const unsigned int channels[] = {
128 2,
129};
130
131static const struct snd_pcm_hw_constraint_list constraints_channels = {
132 .count = ARRAY_SIZE(channels),
133 .list = channels,
134 .mask = 0,
135};
136
137static const unsigned int acp5x_nau8821_format[] = {32};
138
139static struct snd_pcm_hw_constraint_list constraints_sample_bits = {
140 .list = acp5x_nau8821_format,
141 .count = ARRAY_SIZE(acp5x_nau8821_format),
142};
143
144static 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(card: 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, cond: 0, SNDRV_PCM_HW_PARAM_CHANNELS,
155 l: &constraints_channels);
156 snd_pcm_hw_constraint_list(runtime, cond: 0, SNDRV_PCM_HW_PARAM_RATE,
157 l: &constraints_rates);
158 snd_pcm_hw_constraint_list(runtime: substream->runtime, cond: 0,
159 SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
160 l: &constraints_sample_bits);
161
162 return 0;
163}
164
165static 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, clk_id: NAU8821_CLK_FLL_BLK, freq: 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(parms: 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, pll_id: 0, source: 0, freq_in: bclk, freq_out: params_rate(p: params) * 256);
187 if (ret < 0)
188 dev_err(card->dev, "can't set FLL: %d\n", ret);
189
190 return ret;
191}
192
193static const struct snd_soc_ops acp5x_8821_ops = {
194 .startup = acp5x_8821_startup,
195 .hw_params = acp5x_nau8821_hw_params,
196};
197
198static 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(card: 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, cond: 0, SNDRV_PCM_HW_PARAM_CHANNELS,
208 l: &constraints_channels);
209 snd_pcm_hw_constraint_list(runtime, cond: 0, SNDRV_PCM_HW_PARAM_RATE,
210 l: &constraints_rates);
211
212 return 0;
213}
214
215static 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(p: 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(component: comp, clk_id: 0, source: 0, freq: 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
251static const struct snd_soc_ops acp5x_cs35l41_play_ops = {
252 .startup = acp5x_cs35l41_startup,
253 .hw_params = acp5x_cs35l41_hw_params,
254};
255
256static 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
267SND_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
272static 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 .dpcm_playback = 1,
280 .dpcm_capture = 1,
281 .ops = &acp5x_8821_ops,
282 .init = acp5x_8821_init,
283 SND_SOC_DAILINK_REG(acp5x_i2s, nau8821, platform),
284 },
285 {
286 .name = "acp5x-CS35L41-Stereo",
287 .stream_name = "CS35L41 Stereo Playback",
288 .dai_fmt = SND_SOC_DAIFMT_I2S |
289 SND_SOC_DAIFMT_NB_NF |
290 SND_SOC_DAIFMT_CBC_CFC,
291 .dpcm_playback = 1,
292 .playback_only = 1,
293 .ops = &acp5x_cs35l41_play_ops,
294 SND_SOC_DAILINK_REG(acp5x_bt, cs35l41, platform),
295 },
296};
297
298static const struct snd_soc_dapm_widget acp5x_8821_35l41_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,
304 SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
305};
306
307static const struct snd_soc_dapm_route acp5x_8821_35l41_audio_route[] = {
308 /* HP jack connectors - unknown if we have jack detection */
309 { "Headphone", NULL, "HPOL" },
310 { "Headphone", NULL, "HPOR" },
311 { "MICL", NULL, "Headset Mic" },
312 { "MICR", NULL, "Headset Mic" },
313 { "DMIC", NULL, "Int Mic" },
314
315 { "Headphone", NULL, "Platform Clock" },
316 { "Headset Mic", NULL, "Platform Clock" },
317 { "Int Mic", NULL, "Platform Clock" },
318};
319
320static struct snd_soc_card acp5x_8821_35l41_card = {
321 .name = "acp5x",
322 .owner = THIS_MODULE,
323 .dai_link = acp5x_8821_35l41_dai,
324 .num_links = ARRAY_SIZE(acp5x_8821_35l41_dai),
325 .dapm_widgets = acp5x_8821_35l41_widgets,
326 .num_dapm_widgets = ARRAY_SIZE(acp5x_8821_35l41_widgets),
327 .dapm_routes = acp5x_8821_35l41_audio_route,
328 .num_dapm_routes = ARRAY_SIZE(acp5x_8821_35l41_audio_route),
329 .codec_conf = acp5x_cs35l41_conf,
330 .num_configs = ARRAY_SIZE(acp5x_cs35l41_conf),
331 .controls = acp5x_8821_controls,
332 .num_controls = ARRAY_SIZE(acp5x_8821_controls),
333};
334
335static int acp5x_max98388_startup(struct snd_pcm_substream *substream)
336{
337 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
338 struct acp5x_platform_info *machine = snd_soc_card_get_drvdata(card: rtd->card);
339 struct snd_pcm_runtime *runtime = substream->runtime;
340
341 machine->play_i2s_instance = I2S_HS_INSTANCE;
342
343 runtime->hw.channels_max = DUAL_CHANNEL;
344 snd_pcm_hw_constraint_list(runtime, cond: 0, SNDRV_PCM_HW_PARAM_CHANNELS,
345 l: &constraints_channels);
346 snd_pcm_hw_constraint_list(runtime, cond: 0, SNDRV_PCM_HW_PARAM_RATE,
347 l: &constraints_rates);
348 return 0;
349}
350
351static const struct snd_soc_ops acp5x_max98388_play_ops = {
352 .startup = acp5x_max98388_startup,
353};
354
355static struct snd_soc_codec_conf acp5x_max98388_conf[] = {
356 {
357 .dlc = COMP_CODEC_CONF(ACP5X_MAX98388_COMP_LNAME),
358 .name_prefix = "Left",
359 },
360 {
361 .dlc = COMP_CODEC_CONF(ACP5X_MAX98388_COMP_RNAME),
362 .name_prefix = "Right",
363 },
364};
365
366SND_SOC_DAILINK_DEF(max98388, DAILINK_COMP_ARRAY(COMP_CODEC(ACP5X_MAX98388_COMP_LNAME,
367 ACP5X_MAX98388_DAI_NAME),
368 COMP_CODEC(ACP5X_MAX98388_COMP_RNAME,
369 ACP5X_MAX98388_DAI_NAME)));
370
371static struct snd_soc_dai_link acp5x_8821_98388_dai[] = {
372 {
373 .name = "acp5x-8821-play",
374 .stream_name = "Playback/Capture",
375 .dai_fmt = SND_SOC_DAIFMT_I2S |
376 SND_SOC_DAIFMT_NB_NF |
377 SND_SOC_DAIFMT_CBC_CFC,
378 .dpcm_playback = 1,
379 .dpcm_capture = 1,
380 .ops = &acp5x_8821_ops,
381 .init = acp5x_8821_init,
382 SND_SOC_DAILINK_REG(acp5x_i2s, nau8821, platform),
383 },
384 {
385 .name = "acp5x-max98388-play",
386 .stream_name = "MAX98388 Playback",
387 .dai_fmt = SND_SOC_DAIFMT_I2S |
388 SND_SOC_DAIFMT_NB_NF |
389 SND_SOC_DAIFMT_CBC_CFC,
390 .dpcm_playback = 1,
391 .playback_only = 1,
392 .ops = &acp5x_max98388_play_ops,
393 SND_SOC_DAILINK_REG(acp5x_bt, max98388, platform),
394 },
395};
396
397static const struct snd_soc_dapm_widget acp5x_8821_98388_widgets[] = {
398 SND_SOC_DAPM_HP("Headphone", NULL),
399 SND_SOC_DAPM_MIC("Headset Mic", NULL),
400 SND_SOC_DAPM_MIC("Int Mic", NULL),
401 SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,
402 platform_clock_control,
403 SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
404 SND_SOC_DAPM_SPK("SPK", NULL),
405};
406
407static const struct snd_soc_dapm_route acp5x_8821_98388_route[] = {
408 { "Headphone", NULL, "HPOL" },
409 { "Headphone", NULL, "HPOR" },
410 { "MICL", NULL, "Headset Mic" },
411 { "MICR", NULL, "Headset Mic" },
412 { "DMIC", NULL, "Int Mic" },
413
414 { "Headphone", NULL, "Platform Clock" },
415 { "Headset Mic", NULL, "Platform Clock" },
416 { "Int Mic", NULL, "Platform Clock" },
417
418 { "SPK", NULL, "Left BE_OUT" },
419 { "SPK", NULL, "Right BE_OUT" },
420};
421
422static struct snd_soc_card acp5x_8821_98388_card = {
423 .name = "acp5x-max98388",
424 .owner = THIS_MODULE,
425 .dai_link = acp5x_8821_98388_dai,
426 .num_links = ARRAY_SIZE(acp5x_8821_98388_dai),
427 .dapm_widgets = acp5x_8821_98388_widgets,
428 .num_dapm_widgets = ARRAY_SIZE(acp5x_8821_98388_widgets),
429 .dapm_routes = acp5x_8821_98388_route,
430 .num_dapm_routes = ARRAY_SIZE(acp5x_8821_98388_route),
431 .codec_conf = acp5x_max98388_conf,
432 .num_configs = ARRAY_SIZE(acp5x_max98388_conf),
433 .controls = acp5x_8821_controls,
434 .num_controls = ARRAY_SIZE(acp5x_8821_controls),
435};
436
437static const struct dmi_system_id acp5x_vg_quirk_table[] = {
438 {
439 .matches = {
440 DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Valve"),
441 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Jupiter"),
442 },
443 .driver_data = (void *)&acp5x_8821_35l41_card,
444 },
445 {
446 .matches = {
447 DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Valve"),
448 DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Galileo"),
449 },
450 .driver_data = (void *)&acp5x_8821_98388_card,
451 },
452 {}
453};
454
455static int acp5x_probe(struct platform_device *pdev)
456{
457 const struct dmi_system_id *dmi_id;
458 struct acp5x_platform_info *machine;
459 struct device *dev = &pdev->dev;
460 struct snd_soc_card *card;
461 int ret;
462
463 dmi_id = dmi_first_match(list: acp5x_vg_quirk_table);
464 if (!dmi_id || !dmi_id->driver_data)
465 return -ENODEV;
466
467 machine = devm_kzalloc(dev, size: sizeof(*machine), GFP_KERNEL);
468 if (!machine)
469 return -ENOMEM;
470
471 card = dmi_id->driver_data;
472 card->dev = dev;
473 platform_set_drvdata(pdev, data: card);
474 snd_soc_card_set_drvdata(card, data: machine);
475
476 ret = devm_snd_soc_register_card(dev, card);
477 if (ret)
478 return dev_err_probe(dev, err: ret, fmt: "Register card (%s) failed\n", card->name);
479
480 return 0;
481}
482
483static struct platform_driver acp5x_mach_driver = {
484 .driver = {
485 .name = DRV_NAME,
486 .pm = &snd_soc_pm_ops,
487 },
488 .probe = acp5x_probe,
489};
490
491module_platform_driver(acp5x_mach_driver);
492
493MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
494MODULE_DESCRIPTION("NAU8821/CS35L41 & NAU8821/MAX98388 audio support");
495MODULE_LICENSE("GPL v2");
496MODULE_ALIAS("platform:" DRV_NAME);
497

source code of linux/sound/soc/amd/vangogh/acp5x-mach.c