1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-only
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * Intel Broadwell Wildcatpoint SST Audio
4*4882a593Smuzhiyun *
5*4882a593Smuzhiyun * Copyright (C) 2013, Intel Corporation. All rights reserved.
6*4882a593Smuzhiyun */
7*4882a593Smuzhiyun
8*4882a593Smuzhiyun #include <linux/module.h>
9*4882a593Smuzhiyun #include <linux/platform_device.h>
10*4882a593Smuzhiyun #include <sound/core.h>
11*4882a593Smuzhiyun #include <sound/pcm.h>
12*4882a593Smuzhiyun #include <sound/soc.h>
13*4882a593Smuzhiyun #include <sound/jack.h>
14*4882a593Smuzhiyun #include <sound/pcm_params.h>
15*4882a593Smuzhiyun #include <sound/soc-acpi.h>
16*4882a593Smuzhiyun
17*4882a593Smuzhiyun #include "../../codecs/rt286.h"
18*4882a593Smuzhiyun
19*4882a593Smuzhiyun static struct snd_soc_jack broadwell_headset;
20*4882a593Smuzhiyun /* Headset jack detection DAPM pins */
21*4882a593Smuzhiyun static struct snd_soc_jack_pin broadwell_headset_pins[] = {
22*4882a593Smuzhiyun {
23*4882a593Smuzhiyun .pin = "Mic Jack",
24*4882a593Smuzhiyun .mask = SND_JACK_MICROPHONE,
25*4882a593Smuzhiyun },
26*4882a593Smuzhiyun {
27*4882a593Smuzhiyun .pin = "Headphone Jack",
28*4882a593Smuzhiyun .mask = SND_JACK_HEADPHONE,
29*4882a593Smuzhiyun },
30*4882a593Smuzhiyun };
31*4882a593Smuzhiyun
32*4882a593Smuzhiyun static const struct snd_kcontrol_new broadwell_controls[] = {
33*4882a593Smuzhiyun SOC_DAPM_PIN_SWITCH("Speaker"),
34*4882a593Smuzhiyun SOC_DAPM_PIN_SWITCH("Headphone Jack"),
35*4882a593Smuzhiyun };
36*4882a593Smuzhiyun
37*4882a593Smuzhiyun static const struct snd_soc_dapm_widget broadwell_widgets[] = {
38*4882a593Smuzhiyun SND_SOC_DAPM_HP("Headphone Jack", NULL),
39*4882a593Smuzhiyun SND_SOC_DAPM_SPK("Speaker", NULL),
40*4882a593Smuzhiyun SND_SOC_DAPM_MIC("Mic Jack", NULL),
41*4882a593Smuzhiyun SND_SOC_DAPM_MIC("DMIC1", NULL),
42*4882a593Smuzhiyun SND_SOC_DAPM_MIC("DMIC2", NULL),
43*4882a593Smuzhiyun SND_SOC_DAPM_LINE("Line Jack", NULL),
44*4882a593Smuzhiyun };
45*4882a593Smuzhiyun
46*4882a593Smuzhiyun static const struct snd_soc_dapm_route broadwell_rt286_map[] = {
47*4882a593Smuzhiyun
48*4882a593Smuzhiyun /* speaker */
49*4882a593Smuzhiyun {"Speaker", NULL, "SPOR"},
50*4882a593Smuzhiyun {"Speaker", NULL, "SPOL"},
51*4882a593Smuzhiyun
52*4882a593Smuzhiyun /* HP jack connectors - unknown if we have jack deteck */
53*4882a593Smuzhiyun {"Headphone Jack", NULL, "HPO Pin"},
54*4882a593Smuzhiyun
55*4882a593Smuzhiyun /* other jacks */
56*4882a593Smuzhiyun {"MIC1", NULL, "Mic Jack"},
57*4882a593Smuzhiyun {"LINE1", NULL, "Line Jack"},
58*4882a593Smuzhiyun
59*4882a593Smuzhiyun /* digital mics */
60*4882a593Smuzhiyun {"DMIC1 Pin", NULL, "DMIC1"},
61*4882a593Smuzhiyun {"DMIC2 Pin", NULL, "DMIC2"},
62*4882a593Smuzhiyun
63*4882a593Smuzhiyun /* CODEC BE connections */
64*4882a593Smuzhiyun {"SSP0 CODEC IN", NULL, "AIF1 Capture"},
65*4882a593Smuzhiyun {"AIF1 Playback", NULL, "SSP0 CODEC OUT"},
66*4882a593Smuzhiyun };
67*4882a593Smuzhiyun
broadwell_rt286_codec_init(struct snd_soc_pcm_runtime * rtd)68*4882a593Smuzhiyun static int broadwell_rt286_codec_init(struct snd_soc_pcm_runtime *rtd)
69*4882a593Smuzhiyun {
70*4882a593Smuzhiyun struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
71*4882a593Smuzhiyun int ret = 0;
72*4882a593Smuzhiyun ret = snd_soc_card_jack_new(rtd->card, "Headset",
73*4882a593Smuzhiyun SND_JACK_HEADSET | SND_JACK_BTN_0, &broadwell_headset,
74*4882a593Smuzhiyun broadwell_headset_pins, ARRAY_SIZE(broadwell_headset_pins));
75*4882a593Smuzhiyun if (ret)
76*4882a593Smuzhiyun return ret;
77*4882a593Smuzhiyun
78*4882a593Smuzhiyun rt286_mic_detect(component, &broadwell_headset);
79*4882a593Smuzhiyun return 0;
80*4882a593Smuzhiyun }
81*4882a593Smuzhiyun
82*4882a593Smuzhiyun
broadwell_ssp0_fixup(struct snd_soc_pcm_runtime * rtd,struct snd_pcm_hw_params * params)83*4882a593Smuzhiyun static int broadwell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd,
84*4882a593Smuzhiyun struct snd_pcm_hw_params *params)
85*4882a593Smuzhiyun {
86*4882a593Smuzhiyun struct snd_interval *rate = hw_param_interval(params,
87*4882a593Smuzhiyun SNDRV_PCM_HW_PARAM_RATE);
88*4882a593Smuzhiyun struct snd_interval *chan = hw_param_interval(params,
89*4882a593Smuzhiyun SNDRV_PCM_HW_PARAM_CHANNELS);
90*4882a593Smuzhiyun
91*4882a593Smuzhiyun /* The ADSP will covert the FE rate to 48k, stereo */
92*4882a593Smuzhiyun rate->min = rate->max = 48000;
93*4882a593Smuzhiyun chan->min = chan->max = 2;
94*4882a593Smuzhiyun
95*4882a593Smuzhiyun /* set SSP0 to 16 bit */
96*4882a593Smuzhiyun params_set_format(params, SNDRV_PCM_FORMAT_S16_LE);
97*4882a593Smuzhiyun return 0;
98*4882a593Smuzhiyun }
99*4882a593Smuzhiyun
broadwell_rt286_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params)100*4882a593Smuzhiyun static int broadwell_rt286_hw_params(struct snd_pcm_substream *substream,
101*4882a593Smuzhiyun struct snd_pcm_hw_params *params)
102*4882a593Smuzhiyun {
103*4882a593Smuzhiyun struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
104*4882a593Smuzhiyun struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
105*4882a593Smuzhiyun int ret;
106*4882a593Smuzhiyun
107*4882a593Smuzhiyun ret = snd_soc_dai_set_sysclk(codec_dai, RT286_SCLK_S_PLL, 24000000,
108*4882a593Smuzhiyun SND_SOC_CLOCK_IN);
109*4882a593Smuzhiyun
110*4882a593Smuzhiyun if (ret < 0) {
111*4882a593Smuzhiyun dev_err(rtd->dev, "can't set codec sysclk configuration\n");
112*4882a593Smuzhiyun return ret;
113*4882a593Smuzhiyun }
114*4882a593Smuzhiyun
115*4882a593Smuzhiyun return ret;
116*4882a593Smuzhiyun }
117*4882a593Smuzhiyun
118*4882a593Smuzhiyun static const struct snd_soc_ops broadwell_rt286_ops = {
119*4882a593Smuzhiyun .hw_params = broadwell_rt286_hw_params,
120*4882a593Smuzhiyun };
121*4882a593Smuzhiyun
122*4882a593Smuzhiyun static const unsigned int channels[] = {
123*4882a593Smuzhiyun 2,
124*4882a593Smuzhiyun };
125*4882a593Smuzhiyun
126*4882a593Smuzhiyun static const struct snd_pcm_hw_constraint_list constraints_channels = {
127*4882a593Smuzhiyun .count = ARRAY_SIZE(channels),
128*4882a593Smuzhiyun .list = channels,
129*4882a593Smuzhiyun .mask = 0,
130*4882a593Smuzhiyun };
131*4882a593Smuzhiyun
broadwell_fe_startup(struct snd_pcm_substream * substream)132*4882a593Smuzhiyun static int broadwell_fe_startup(struct snd_pcm_substream *substream)
133*4882a593Smuzhiyun {
134*4882a593Smuzhiyun struct snd_pcm_runtime *runtime = substream->runtime;
135*4882a593Smuzhiyun
136*4882a593Smuzhiyun /* Board supports stereo configuration only */
137*4882a593Smuzhiyun runtime->hw.channels_max = 2;
138*4882a593Smuzhiyun return snd_pcm_hw_constraint_list(runtime, 0,
139*4882a593Smuzhiyun SNDRV_PCM_HW_PARAM_CHANNELS,
140*4882a593Smuzhiyun &constraints_channels);
141*4882a593Smuzhiyun }
142*4882a593Smuzhiyun
143*4882a593Smuzhiyun static const struct snd_soc_ops broadwell_fe_ops = {
144*4882a593Smuzhiyun .startup = broadwell_fe_startup,
145*4882a593Smuzhiyun };
146*4882a593Smuzhiyun
147*4882a593Smuzhiyun SND_SOC_DAILINK_DEF(system,
148*4882a593Smuzhiyun DAILINK_COMP_ARRAY(COMP_CPU("System Pin")));
149*4882a593Smuzhiyun
150*4882a593Smuzhiyun SND_SOC_DAILINK_DEF(offload0,
151*4882a593Smuzhiyun DAILINK_COMP_ARRAY(COMP_CPU("Offload0 Pin")));
152*4882a593Smuzhiyun
153*4882a593Smuzhiyun SND_SOC_DAILINK_DEF(offload1,
154*4882a593Smuzhiyun DAILINK_COMP_ARRAY(COMP_CPU("Offload1 Pin")));
155*4882a593Smuzhiyun
156*4882a593Smuzhiyun SND_SOC_DAILINK_DEF(loopback,
157*4882a593Smuzhiyun DAILINK_COMP_ARRAY(COMP_CPU("Loopback Pin")));
158*4882a593Smuzhiyun
159*4882a593Smuzhiyun SND_SOC_DAILINK_DEF(dummy,
160*4882a593Smuzhiyun DAILINK_COMP_ARRAY(COMP_DUMMY()));
161*4882a593Smuzhiyun
162*4882a593Smuzhiyun SND_SOC_DAILINK_DEF(platform,
163*4882a593Smuzhiyun DAILINK_COMP_ARRAY(COMP_PLATFORM("haswell-pcm-audio")));
164*4882a593Smuzhiyun
165*4882a593Smuzhiyun SND_SOC_DAILINK_DEF(codec,
166*4882a593Smuzhiyun DAILINK_COMP_ARRAY(COMP_CODEC("i2c-INT343A:00", "rt286-aif1")));
167*4882a593Smuzhiyun
168*4882a593Smuzhiyun SND_SOC_DAILINK_DEF(ssp0_port,
169*4882a593Smuzhiyun DAILINK_COMP_ARRAY(COMP_CPU("ssp0-port")));
170*4882a593Smuzhiyun
171*4882a593Smuzhiyun /* broadwell digital audio interface glue - connects codec <--> CPU */
172*4882a593Smuzhiyun static struct snd_soc_dai_link broadwell_rt286_dais[] = {
173*4882a593Smuzhiyun /* Front End DAI links */
174*4882a593Smuzhiyun {
175*4882a593Smuzhiyun .name = "System PCM",
176*4882a593Smuzhiyun .stream_name = "System Playback/Capture",
177*4882a593Smuzhiyun .nonatomic = 1,
178*4882a593Smuzhiyun .dynamic = 1,
179*4882a593Smuzhiyun .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
180*4882a593Smuzhiyun .ops = &broadwell_fe_ops,
181*4882a593Smuzhiyun .dpcm_playback = 1,
182*4882a593Smuzhiyun .dpcm_capture = 1,
183*4882a593Smuzhiyun SND_SOC_DAILINK_REG(system, dummy, platform),
184*4882a593Smuzhiyun },
185*4882a593Smuzhiyun {
186*4882a593Smuzhiyun .name = "Offload0",
187*4882a593Smuzhiyun .stream_name = "Offload0 Playback",
188*4882a593Smuzhiyun .nonatomic = 1,
189*4882a593Smuzhiyun .dynamic = 1,
190*4882a593Smuzhiyun .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
191*4882a593Smuzhiyun .dpcm_playback = 1,
192*4882a593Smuzhiyun SND_SOC_DAILINK_REG(offload0, dummy, platform),
193*4882a593Smuzhiyun },
194*4882a593Smuzhiyun {
195*4882a593Smuzhiyun .name = "Offload1",
196*4882a593Smuzhiyun .stream_name = "Offload1 Playback",
197*4882a593Smuzhiyun .nonatomic = 1,
198*4882a593Smuzhiyun .dynamic = 1,
199*4882a593Smuzhiyun .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
200*4882a593Smuzhiyun .dpcm_playback = 1,
201*4882a593Smuzhiyun SND_SOC_DAILINK_REG(offload1, dummy, platform),
202*4882a593Smuzhiyun },
203*4882a593Smuzhiyun {
204*4882a593Smuzhiyun .name = "Loopback PCM",
205*4882a593Smuzhiyun .stream_name = "Loopback",
206*4882a593Smuzhiyun .nonatomic = 1,
207*4882a593Smuzhiyun .dynamic = 1,
208*4882a593Smuzhiyun .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
209*4882a593Smuzhiyun .dpcm_capture = 1,
210*4882a593Smuzhiyun SND_SOC_DAILINK_REG(loopback, dummy, platform),
211*4882a593Smuzhiyun },
212*4882a593Smuzhiyun /* Back End DAI links */
213*4882a593Smuzhiyun {
214*4882a593Smuzhiyun /* SSP0 - Codec */
215*4882a593Smuzhiyun .name = "Codec",
216*4882a593Smuzhiyun .id = 0,
217*4882a593Smuzhiyun .no_pcm = 1,
218*4882a593Smuzhiyun .init = broadwell_rt286_codec_init,
219*4882a593Smuzhiyun .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
220*4882a593Smuzhiyun SND_SOC_DAIFMT_CBS_CFS,
221*4882a593Smuzhiyun .ignore_pmdown_time = 1,
222*4882a593Smuzhiyun .be_hw_params_fixup = broadwell_ssp0_fixup,
223*4882a593Smuzhiyun .ops = &broadwell_rt286_ops,
224*4882a593Smuzhiyun .dpcm_playback = 1,
225*4882a593Smuzhiyun .dpcm_capture = 1,
226*4882a593Smuzhiyun SND_SOC_DAILINK_REG(ssp0_port, codec, platform),
227*4882a593Smuzhiyun },
228*4882a593Smuzhiyun };
229*4882a593Smuzhiyun
broadwell_disable_jack(struct snd_soc_card * card)230*4882a593Smuzhiyun static int broadwell_disable_jack(struct snd_soc_card *card)
231*4882a593Smuzhiyun {
232*4882a593Smuzhiyun struct snd_soc_component *component;
233*4882a593Smuzhiyun
234*4882a593Smuzhiyun for_each_card_components(card, component) {
235*4882a593Smuzhiyun if (!strcmp(component->name, "i2c-INT343A:00")) {
236*4882a593Smuzhiyun
237*4882a593Smuzhiyun dev_dbg(component->dev, "disabling jack detect before going to suspend.\n");
238*4882a593Smuzhiyun rt286_mic_detect(component, NULL);
239*4882a593Smuzhiyun break;
240*4882a593Smuzhiyun }
241*4882a593Smuzhiyun }
242*4882a593Smuzhiyun
243*4882a593Smuzhiyun return 0;
244*4882a593Smuzhiyun }
245*4882a593Smuzhiyun
broadwell_suspend(struct snd_soc_card * card)246*4882a593Smuzhiyun static int broadwell_suspend(struct snd_soc_card *card)
247*4882a593Smuzhiyun {
248*4882a593Smuzhiyun return broadwell_disable_jack(card);
249*4882a593Smuzhiyun }
250*4882a593Smuzhiyun
broadwell_resume(struct snd_soc_card * card)251*4882a593Smuzhiyun static int broadwell_resume(struct snd_soc_card *card){
252*4882a593Smuzhiyun struct snd_soc_component *component;
253*4882a593Smuzhiyun
254*4882a593Smuzhiyun for_each_card_components(card, component) {
255*4882a593Smuzhiyun if (!strcmp(component->name, "i2c-INT343A:00")) {
256*4882a593Smuzhiyun
257*4882a593Smuzhiyun dev_dbg(component->dev, "enabling jack detect for resume.\n");
258*4882a593Smuzhiyun rt286_mic_detect(component, &broadwell_headset);
259*4882a593Smuzhiyun break;
260*4882a593Smuzhiyun }
261*4882a593Smuzhiyun }
262*4882a593Smuzhiyun return 0;
263*4882a593Smuzhiyun }
264*4882a593Smuzhiyun
265*4882a593Smuzhiyun #if IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL)
266*4882a593Smuzhiyun /* use space before codec name to simplify card ID, and simplify driver name */
267*4882a593Smuzhiyun #define CARD_NAME "bdw rt286" /* card name will be 'sof-bdw rt286' */
268*4882a593Smuzhiyun #define DRIVER_NAME "SOF"
269*4882a593Smuzhiyun #else
270*4882a593Smuzhiyun #define CARD_NAME "broadwell-rt286"
271*4882a593Smuzhiyun #define DRIVER_NAME NULL /* card name will be used for driver name */
272*4882a593Smuzhiyun #endif
273*4882a593Smuzhiyun
274*4882a593Smuzhiyun /* broadwell audio machine driver for WPT + RT286S */
275*4882a593Smuzhiyun static struct snd_soc_card broadwell_rt286 = {
276*4882a593Smuzhiyun .name = CARD_NAME,
277*4882a593Smuzhiyun .driver_name = DRIVER_NAME,
278*4882a593Smuzhiyun .owner = THIS_MODULE,
279*4882a593Smuzhiyun .dai_link = broadwell_rt286_dais,
280*4882a593Smuzhiyun .num_links = ARRAY_SIZE(broadwell_rt286_dais),
281*4882a593Smuzhiyun .controls = broadwell_controls,
282*4882a593Smuzhiyun .num_controls = ARRAY_SIZE(broadwell_controls),
283*4882a593Smuzhiyun .dapm_widgets = broadwell_widgets,
284*4882a593Smuzhiyun .num_dapm_widgets = ARRAY_SIZE(broadwell_widgets),
285*4882a593Smuzhiyun .dapm_routes = broadwell_rt286_map,
286*4882a593Smuzhiyun .num_dapm_routes = ARRAY_SIZE(broadwell_rt286_map),
287*4882a593Smuzhiyun .fully_routed = true,
288*4882a593Smuzhiyun .suspend_pre = broadwell_suspend,
289*4882a593Smuzhiyun .resume_post = broadwell_resume,
290*4882a593Smuzhiyun };
291*4882a593Smuzhiyun
broadwell_audio_probe(struct platform_device * pdev)292*4882a593Smuzhiyun static int broadwell_audio_probe(struct platform_device *pdev)
293*4882a593Smuzhiyun {
294*4882a593Smuzhiyun struct snd_soc_acpi_mach *mach;
295*4882a593Smuzhiyun int ret;
296*4882a593Smuzhiyun
297*4882a593Smuzhiyun broadwell_rt286.dev = &pdev->dev;
298*4882a593Smuzhiyun
299*4882a593Smuzhiyun /* override plaform name, if required */
300*4882a593Smuzhiyun mach = pdev->dev.platform_data;
301*4882a593Smuzhiyun ret = snd_soc_fixup_dai_links_platform_name(&broadwell_rt286,
302*4882a593Smuzhiyun mach->mach_params.platform);
303*4882a593Smuzhiyun if (ret)
304*4882a593Smuzhiyun return ret;
305*4882a593Smuzhiyun
306*4882a593Smuzhiyun return devm_snd_soc_register_card(&pdev->dev, &broadwell_rt286);
307*4882a593Smuzhiyun }
308*4882a593Smuzhiyun
broadwell_audio_remove(struct platform_device * pdev)309*4882a593Smuzhiyun static int broadwell_audio_remove(struct platform_device *pdev)
310*4882a593Smuzhiyun {
311*4882a593Smuzhiyun struct snd_soc_card *card = platform_get_drvdata(pdev);
312*4882a593Smuzhiyun
313*4882a593Smuzhiyun return broadwell_disable_jack(card);
314*4882a593Smuzhiyun }
315*4882a593Smuzhiyun
316*4882a593Smuzhiyun static struct platform_driver broadwell_audio = {
317*4882a593Smuzhiyun .probe = broadwell_audio_probe,
318*4882a593Smuzhiyun .remove = broadwell_audio_remove,
319*4882a593Smuzhiyun .driver = {
320*4882a593Smuzhiyun .name = "broadwell-audio",
321*4882a593Smuzhiyun },
322*4882a593Smuzhiyun };
323*4882a593Smuzhiyun
324*4882a593Smuzhiyun module_platform_driver(broadwell_audio)
325*4882a593Smuzhiyun
326*4882a593Smuzhiyun /* Module information */
327*4882a593Smuzhiyun MODULE_AUTHOR("Liam Girdwood, Xingchao Wang");
328*4882a593Smuzhiyun MODULE_DESCRIPTION("Intel SST Audio for WPT/Broadwell");
329*4882a593Smuzhiyun MODULE_LICENSE("GPL v2");
330*4882a593Smuzhiyun MODULE_ALIAS("platform:broadwell-audio");
331