1 // SPDX-License-Identifier: GPL-2.0-only
2 // Copyright(c) 2018-2020 Intel Corporation.
5 * Intel SOF Machine Driver for Intel platforms with TI PCM512x codec,
6 * e.g. Up or Up2 with Hifiberry DAC+ HAT
10 #include <linux/i2c.h>
11 #include <linux/input.h>
12 #include <linux/module.h>
13 #include <linux/platform_device.h>
14 #include <linux/types.h>
15 #include <sound/core.h>
16 #include <sound/jack.h>
17 #include <sound/pcm.h>
18 #include <sound/pcm_params.h>
19 #include <sound/soc.h>
20 #include <sound/soc-acpi.h>
21 #include "../../codecs/pcm512x.h"
22 #include "../common/soc-intel-quirks.h"
23 #include "hda_dsp_common.h"
27 #define SOF_PCM512X_SSP_CODEC(quirk) ((quirk) & GENMASK(3, 0))
28 #define SOF_PCM512X_SSP_CODEC_MASK (GENMASK(3, 0))
30 #define IDISP_CODEC_MASK 0x4
33 static unsigned long sof_pcm512x_quirk
= SOF_PCM512X_SSP_CODEC(5);
35 static bool is_legacy_cpu
;
38 struct list_head head
;
39 struct snd_soc_dai
*codec_dai
;
43 struct sof_card_private
{
44 struct list_head hdmi_pcm_list
;
48 static int sof_pcm512x_quirk_cb(const struct dmi_system_id
*id
)
50 sof_pcm512x_quirk
= (unsigned long)id
->driver_data
;
54 static const struct dmi_system_id sof_pcm512x_quirk_table
[] = {
56 .callback
= sof_pcm512x_quirk_cb
,
58 DMI_MATCH(DMI_SYS_VENDOR
, "AAEON"),
59 DMI_MATCH(DMI_PRODUCT_NAME
, "UP-CHT01"),
61 .driver_data
= (void *)(SOF_PCM512X_SSP_CODEC(2)),
66 static int sof_hdmi_init(struct snd_soc_pcm_runtime
*rtd
)
68 struct sof_card_private
*ctx
= snd_soc_card_get_drvdata(rtd
->card
);
69 struct snd_soc_dai
*dai
= asoc_rtd_to_codec(rtd
, 0);
70 struct sof_hdmi_pcm
*pcm
;
72 pcm
= devm_kzalloc(rtd
->card
->dev
, sizeof(*pcm
), GFP_KERNEL
);
76 /* dai_link id is 1:1 mapped to the PCM device */
77 pcm
->device
= rtd
->dai_link
->id
;
80 list_add_tail(&pcm
->head
, &ctx
->hdmi_pcm_list
);
85 static int sof_pcm512x_codec_init(struct snd_soc_pcm_runtime
*rtd
)
87 struct snd_soc_component
*codec
= asoc_rtd_to_codec(rtd
, 0)->component
;
89 snd_soc_component_update_bits(codec
, PCM512x_GPIO_EN
, 0x08, 0x08);
90 snd_soc_component_update_bits(codec
, PCM512x_GPIO_OUTPUT_4
, 0x0f, 0x02);
91 snd_soc_component_update_bits(codec
, PCM512x_GPIO_CONTROL_1
,
97 static int aif1_startup(struct snd_pcm_substream
*substream
)
99 struct snd_soc_pcm_runtime
*rtd
= asoc_substream_to_rtd(substream
);
100 struct snd_soc_component
*codec
= asoc_rtd_to_codec(rtd
, 0)->component
;
102 snd_soc_component_update_bits(codec
, PCM512x_GPIO_CONTROL_1
,
108 static void aif1_shutdown(struct snd_pcm_substream
*substream
)
110 struct snd_soc_pcm_runtime
*rtd
= asoc_substream_to_rtd(substream
);
111 struct snd_soc_component
*codec
= asoc_rtd_to_codec(rtd
, 0)->component
;
113 snd_soc_component_update_bits(codec
, PCM512x_GPIO_CONTROL_1
,
117 static const struct snd_soc_ops sof_pcm512x_ops
= {
118 .startup
= aif1_startup
,
119 .shutdown
= aif1_shutdown
,
122 static struct snd_soc_dai_link_component platform_component
[] = {
124 /* name might be overridden during probe */
125 .name
= "0000:00:1f.3"
129 static int sof_card_late_probe(struct snd_soc_card
*card
)
131 struct sof_card_private
*ctx
= snd_soc_card_get_drvdata(card
);
132 struct sof_hdmi_pcm
*pcm
;
134 /* HDMI is not supported by SOF on Baytrail/CherryTrail */
138 if (list_empty(&ctx
->hdmi_pcm_list
))
141 if (!ctx
->idisp_codec
)
144 pcm
= list_first_entry(&ctx
->hdmi_pcm_list
, struct sof_hdmi_pcm
, head
);
146 return hda_dsp_hdmi_build_controls(card
, pcm
->codec_dai
->component
);
149 static const struct snd_kcontrol_new sof_controls
[] = {
150 SOC_DAPM_PIN_SWITCH("Ext Spk"),
153 static const struct snd_soc_dapm_widget sof_widgets
[] = {
154 SND_SOC_DAPM_SPK("Ext Spk", NULL
),
157 static const struct snd_soc_dapm_widget dmic_widgets
[] = {
158 SND_SOC_DAPM_MIC("SoC DMIC", NULL
),
161 static const struct snd_soc_dapm_route sof_map
[] = {
163 {"Ext Spk", NULL
, "OUTR"},
164 {"Ext Spk", NULL
, "OUTL"},
167 static const struct snd_soc_dapm_route dmic_map
[] = {
169 {"DMic", NULL
, "SoC DMIC"},
172 static int dmic_init(struct snd_soc_pcm_runtime
*rtd
)
174 struct snd_soc_card
*card
= rtd
->card
;
177 ret
= snd_soc_dapm_new_controls(&card
->dapm
, dmic_widgets
,
178 ARRAY_SIZE(dmic_widgets
));
180 dev_err(card
->dev
, "DMic widget addition failed: %d\n", ret
);
181 /* Don't need to add routes if widget addition failed */
185 ret
= snd_soc_dapm_add_routes(&card
->dapm
, dmic_map
,
186 ARRAY_SIZE(dmic_map
));
189 dev_err(card
->dev
, "DMic map addition failed: %d\n", ret
);
194 /* sof audio machine driver for pcm512x codec */
195 static struct snd_soc_card sof_audio_card_pcm512x
= {
197 .owner
= THIS_MODULE
,
198 .controls
= sof_controls
,
199 .num_controls
= ARRAY_SIZE(sof_controls
),
200 .dapm_widgets
= sof_widgets
,
201 .num_dapm_widgets
= ARRAY_SIZE(sof_widgets
),
202 .dapm_routes
= sof_map
,
203 .num_dapm_routes
= ARRAY_SIZE(sof_map
),
204 .fully_routed
= true,
205 .late_probe
= sof_card_late_probe
,
208 SND_SOC_DAILINK_DEF(pcm512x_component
,
209 DAILINK_COMP_ARRAY(COMP_CODEC("i2c-104C5122:00", "pcm512x-hifi")));
210 SND_SOC_DAILINK_DEF(dmic_component
,
211 DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi")));
213 static struct snd_soc_dai_link
*sof_card_dai_links_create(struct device
*dev
,
219 struct snd_soc_dai_link_component
*idisp_components
;
220 struct snd_soc_dai_link_component
*cpus
;
221 struct snd_soc_dai_link
*links
;
224 links
= devm_kcalloc(dev
, sof_audio_card_pcm512x
.num_links
,
225 sizeof(struct snd_soc_dai_link
), GFP_KERNEL
);
226 cpus
= devm_kcalloc(dev
, sof_audio_card_pcm512x
.num_links
,
227 sizeof(struct snd_soc_dai_link_component
), GFP_KERNEL
);
232 links
[id
].name
= devm_kasprintf(dev
, GFP_KERNEL
,
233 "SSP%d-Codec", ssp_codec
);
238 links
[id
].codecs
= pcm512x_component
;
239 links
[id
].num_codecs
= ARRAY_SIZE(pcm512x_component
);
240 links
[id
].platforms
= platform_component
;
241 links
[id
].num_platforms
= ARRAY_SIZE(platform_component
);
242 links
[id
].init
= sof_pcm512x_codec_init
;
243 links
[id
].ops
= &sof_pcm512x_ops
;
244 links
[id
].nonatomic
= true;
245 links
[id
].dpcm_playback
= 1;
247 * capture only supported with specific versions of the Hifiberry DAC+
248 * links[id].dpcm_capture = 1;
250 links
[id
].no_pcm
= 1;
251 links
[id
].cpus
= &cpus
[id
];
252 links
[id
].num_cpus
= 1;
254 links
[id
].cpus
->dai_name
= devm_kasprintf(dev
, GFP_KERNEL
,
257 if (!links
[id
].cpus
->dai_name
)
260 links
[id
].cpus
->dai_name
= devm_kasprintf(dev
, GFP_KERNEL
,
263 if (!links
[id
].cpus
->dai_name
)
269 if (dmic_be_num
> 0) {
270 /* at least we have dmic01 */
271 links
[id
].name
= "dmic01";
272 links
[id
].cpus
= &cpus
[id
];
273 links
[id
].cpus
->dai_name
= "DMIC01 Pin";
274 links
[id
].init
= dmic_init
;
275 if (dmic_be_num
> 1) {
276 /* set up 2 BE links at most */
277 links
[id
+ 1].name
= "dmic16k";
278 links
[id
+ 1].cpus
= &cpus
[id
+ 1];
279 links
[id
+ 1].cpus
->dai_name
= "DMIC16k Pin";
284 for (i
= 0; i
< dmic_be_num
; i
++) {
286 links
[id
].num_cpus
= 1;
287 links
[id
].codecs
= dmic_component
;
288 links
[id
].num_codecs
= ARRAY_SIZE(dmic_component
);
289 links
[id
].platforms
= platform_component
;
290 links
[id
].num_platforms
= ARRAY_SIZE(platform_component
);
291 links
[id
].ignore_suspend
= 1;
292 links
[id
].dpcm_capture
= 1;
293 links
[id
].no_pcm
= 1;
299 idisp_components
= devm_kcalloc(dev
, hdmi_num
,
300 sizeof(struct snd_soc_dai_link_component
),
302 if (!idisp_components
)
305 for (i
= 1; i
<= hdmi_num
; i
++) {
306 links
[id
].name
= devm_kasprintf(dev
, GFP_KERNEL
,
312 links
[id
].cpus
= &cpus
[id
];
313 links
[id
].num_cpus
= 1;
314 links
[id
].cpus
->dai_name
= devm_kasprintf(dev
, GFP_KERNEL
,
316 if (!links
[id
].cpus
->dai_name
)
320 * topology cannot be loaded if codec is missing, so
321 * use the dummy codec if needed
324 idisp_components
[i
- 1].name
= "ehdaudio0D2";
325 idisp_components
[i
- 1].dai_name
=
326 devm_kasprintf(dev
, GFP_KERNEL
,
327 "intel-hdmi-hifi%d", i
);
329 idisp_components
[i
- 1].name
= "snd-soc-dummy";
330 idisp_components
[i
- 1].dai_name
= "snd-soc-dummy-dai";
332 if (!idisp_components
[i
- 1].dai_name
)
335 links
[id
].codecs
= &idisp_components
[i
- 1];
336 links
[id
].num_codecs
= 1;
337 links
[id
].platforms
= platform_component
;
338 links
[id
].num_platforms
= ARRAY_SIZE(platform_component
);
339 links
[id
].init
= sof_hdmi_init
;
340 links
[id
].dpcm_playback
= 1;
341 links
[id
].no_pcm
= 1;
350 static int sof_audio_probe(struct platform_device
*pdev
)
352 struct snd_soc_acpi_mach
*mach
= pdev
->dev
.platform_data
;
353 struct snd_soc_dai_link
*dai_links
;
354 struct sof_card_private
*ctx
;
355 int dmic_be_num
, hdmi_num
;
358 ctx
= devm_kzalloc(&pdev
->dev
, sizeof(*ctx
), GFP_KERNEL
);
363 if (soc_intel_is_byt() || soc_intel_is_cht()) {
364 is_legacy_cpu
= true;
366 /* default quirk for legacy cpu */
367 sof_pcm512x_quirk
= SOF_PCM512X_SSP_CODEC(2);
370 if (mach
->mach_params
.common_hdmi_codec_drv
&&
371 (mach
->mach_params
.codec_mask
& IDISP_CODEC_MASK
))
372 ctx
->idisp_codec
= true;
374 /* links are always present in topology */
378 dmi_check_system(sof_pcm512x_quirk_table
);
380 dev_dbg(&pdev
->dev
, "sof_pcm512x_quirk = %lx\n", sof_pcm512x_quirk
);
382 ssp_codec
= sof_pcm512x_quirk
& SOF_PCM512X_SSP_CODEC_MASK
;
384 /* compute number of dai links */
385 sof_audio_card_pcm512x
.num_links
= 1 + dmic_be_num
+ hdmi_num
;
387 dai_links
= sof_card_dai_links_create(&pdev
->dev
, ssp_codec
,
388 dmic_be_num
, hdmi_num
,
393 sof_audio_card_pcm512x
.dai_link
= dai_links
;
395 INIT_LIST_HEAD(&ctx
->hdmi_pcm_list
);
397 sof_audio_card_pcm512x
.dev
= &pdev
->dev
;
399 /* set platform name for each dailink */
400 ret
= snd_soc_fixup_dai_links_platform_name(&sof_audio_card_pcm512x
,
401 mach
->mach_params
.platform
);
405 snd_soc_card_set_drvdata(&sof_audio_card_pcm512x
, ctx
);
407 return devm_snd_soc_register_card(&pdev
->dev
,
408 &sof_audio_card_pcm512x
);
411 static int sof_pcm512x_remove(struct platform_device
*pdev
)
413 struct snd_soc_card
*card
= platform_get_drvdata(pdev
);
414 struct snd_soc_component
*component
= NULL
;
416 for_each_card_components(card
, component
) {
417 if (!strcmp(component
->name
, pcm512x_component
[0].name
)) {
418 snd_soc_component_set_jack(component
, NULL
, NULL
);
426 static struct platform_driver sof_audio
= {
427 .probe
= sof_audio_probe
,
428 .remove
= sof_pcm512x_remove
,
430 .name
= "sof_pcm512x",
431 .pm
= &snd_soc_pm_ops
,
434 module_platform_driver(sof_audio
)
436 MODULE_DESCRIPTION("ASoC Intel(R) SOF + PCM512x Machine driver");
437 MODULE_AUTHOR("Pierre-Louis Bossart");
438 MODULE_LICENSE("GPL v2");
439 MODULE_ALIAS("platform:sof_pcm512x");