File Index
Files and directories
Sign in
FILE

acp5x-mach.c

File preview with syntax highlighting.

linux-6.19.14/sound/soc/amd/vangogh/acp5x-mach.c 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.