1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * ASoC machine driver for Intel Broadwell platforms with RT5677 codec
4 *
5 * Copyright (c) 2014, The Chromium OS Authors. All rights reserved.
6 */
7
8#include <linux/acpi.h>
9#include <linux/module.h>
10#include <linux/platform_device.h>
11#include <linux/gpio/consumer.h>
12#include <linux/delay.h>
13#include <sound/core.h>
14#include <sound/pcm.h>
15#include <sound/soc.h>
16#include <sound/pcm_params.h>
17#include <sound/jack.h>
18#include <sound/soc-acpi.h>
19
20#include "../../codecs/rt5677.h"
21
22struct bdw_rt5677_priv {
23 struct gpio_desc *gpio_hp_en;
24 struct snd_soc_component *component;
25};
26
27static int bdw_rt5677_event_hp(struct snd_soc_dapm_widget *w,
28 struct snd_kcontrol *k, int event)
29{
30 struct snd_soc_card *card = snd_soc_dapm_to_card(dapm: w->dapm);
31 struct bdw_rt5677_priv *bdw_rt5677 = snd_soc_card_get_drvdata(card);
32
33 if (SND_SOC_DAPM_EVENT_ON(event))
34 msleep(msecs: 70);
35
36 gpiod_set_value_cansleep(desc: bdw_rt5677->gpio_hp_en,
37 SND_SOC_DAPM_EVENT_ON(event));
38
39 return 0;
40}
41
42static const struct snd_soc_dapm_widget bdw_rt5677_widgets[] = {
43 SND_SOC_DAPM_HP("Headphone", bdw_rt5677_event_hp),
44 SND_SOC_DAPM_SPK("Speaker", NULL),
45 SND_SOC_DAPM_MIC("Headset Mic", NULL),
46 SND_SOC_DAPM_MIC("Local DMICs", NULL),
47 SND_SOC_DAPM_MIC("Remote DMICs", NULL),
48};
49
50static const struct snd_soc_dapm_route bdw_rt5677_map[] = {
51 /* Speakers */
52 {"Speaker", NULL, "PDM1L"},
53 {"Speaker", NULL, "PDM1R"},
54
55 /* Headset jack connectors */
56 {"Headphone", NULL, "LOUT1"},
57 {"Headphone", NULL, "LOUT2"},
58 {"IN1P", NULL, "Headset Mic"},
59 {"IN1N", NULL, "Headset Mic"},
60
61 /* Digital MICs
62 * Local DMICs: the two DMICs on the mainboard
63 * Remote DMICs: the two DMICs on the camera module
64 */
65 {"DMIC L1", NULL, "Remote DMICs"},
66 {"DMIC R1", NULL, "Remote DMICs"},
67 {"DMIC L2", NULL, "Local DMICs"},
68 {"DMIC R2", NULL, "Local DMICs"},
69
70 /* CODEC BE connections */
71 {"SSP0 CODEC IN", NULL, "AIF1 Capture"},
72 {"AIF1 Playback", NULL, "SSP0 CODEC OUT"},
73 {"DSP Capture", NULL, "DSP Buffer"},
74
75 /* DSP Clock Connections */
76 { "DSP Buffer", NULL, "SSP0 CODEC IN" },
77 { "SSP0 CODEC IN", NULL, "DSPTX" },
78};
79
80static const struct snd_kcontrol_new bdw_rt5677_controls[] = {
81 SOC_DAPM_PIN_SWITCH("Speaker"),
82 SOC_DAPM_PIN_SWITCH("Headphone"),
83 SOC_DAPM_PIN_SWITCH("Headset Mic"),
84 SOC_DAPM_PIN_SWITCH("Local DMICs"),
85 SOC_DAPM_PIN_SWITCH("Remote DMICs"),
86};
87
88
89static struct snd_soc_jack headphone_jack;
90static struct snd_soc_jack mic_jack;
91
92static struct snd_soc_jack_pin headphone_jack_pin = {
93 .pin = "Headphone",
94 .mask = SND_JACK_HEADPHONE,
95};
96
97static struct snd_soc_jack_pin mic_jack_pin = {
98 .pin = "Headset Mic",
99 .mask = SND_JACK_MICROPHONE,
100};
101
102static struct snd_soc_jack_gpio headphone_jack_gpio = {
103 .name = "plug-det",
104 .report = SND_JACK_HEADPHONE,
105 .debounce_time = 200,
106};
107
108static struct snd_soc_jack_gpio mic_jack_gpio = {
109 .name = "mic-present",
110 .report = SND_JACK_MICROPHONE,
111 .debounce_time = 200,
112 .invert = 1,
113};
114
115/* GPIO indexes defined by ACPI */
116enum {
117 RT5677_GPIO_PLUG_DET = 0,
118 RT5677_GPIO_MIC_PRESENT_L = 1,
119 RT5677_GPIO_HOTWORD_DET_L = 2,
120 RT5677_GPIO_DSP_INT = 3,
121 RT5677_GPIO_HP_AMP_SHDN_L = 4,
122};
123
124static const struct acpi_gpio_params plug_det_gpio = { RT5677_GPIO_PLUG_DET, 0, false };
125static const struct acpi_gpio_params mic_present_gpio = { RT5677_GPIO_MIC_PRESENT_L, 0, false };
126static const struct acpi_gpio_params headphone_enable_gpio = { RT5677_GPIO_HP_AMP_SHDN_L, 0, false };
127
128static const struct acpi_gpio_mapping bdw_rt5677_gpios[] = {
129 { "plug-det-gpios", &plug_det_gpio, 1 },
130 { "mic-present-gpios", &mic_present_gpio, 1 },
131 { "headphone-enable-gpios", &headphone_enable_gpio, 1 },
132 { NULL },
133};
134
135static int broadwell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd,
136 struct snd_pcm_hw_params *params)
137{
138 struct snd_interval *rate = hw_param_interval(params,
139 SNDRV_PCM_HW_PARAM_RATE);
140 struct snd_interval *chan = hw_param_interval(params,
141 SNDRV_PCM_HW_PARAM_CHANNELS);
142
143 /* The ADSP will convert the FE rate to 48k, stereo */
144 rate->min = rate->max = 48000;
145 chan->min = chan->max = 2;
146
147 /* set SSP0 to 16 bit */
148 params_set_format(p: params, SNDRV_PCM_FORMAT_S16_LE);
149 return 0;
150}
151
152static int bdw_rt5677_hw_params(struct snd_pcm_substream *substream,
153 struct snd_pcm_hw_params *params)
154{
155 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
156 struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
157 int ret;
158
159 ret = snd_soc_dai_set_sysclk(dai: codec_dai, clk_id: RT5677_SCLK_S_MCLK, freq: 24576000,
160 SND_SOC_CLOCK_IN);
161 if (ret < 0) {
162 dev_err(rtd->dev, "can't set codec sysclk configuration\n");
163 return ret;
164 }
165
166 return ret;
167}
168
169static int bdw_rt5677_dsp_hw_params(struct snd_pcm_substream *substream,
170 struct snd_pcm_hw_params *params)
171{
172 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
173 struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
174 int ret;
175
176 ret = snd_soc_dai_set_sysclk(dai: codec_dai, clk_id: RT5677_SCLK_S_PLL1, freq: 24576000,
177 SND_SOC_CLOCK_IN);
178 if (ret < 0) {
179 dev_err(rtd->dev, "can't set codec sysclk configuration\n");
180 return ret;
181 }
182 ret = snd_soc_dai_set_pll(dai: codec_dai, pll_id: 0, source: RT5677_PLL1_S_MCLK,
183 freq_in: 24000000, freq_out: 24576000);
184 if (ret < 0) {
185 dev_err(rtd->dev, "can't set codec pll configuration\n");
186 return ret;
187 }
188
189 return 0;
190}
191
192static const struct snd_soc_ops bdw_rt5677_ops = {
193 .hw_params = bdw_rt5677_hw_params,
194};
195
196static const struct snd_soc_ops bdw_rt5677_dsp_ops = {
197 .hw_params = bdw_rt5677_dsp_hw_params,
198};
199
200static const unsigned int channels[] = {
201 2,
202};
203
204static const struct snd_pcm_hw_constraint_list constraints_channels = {
205 .count = ARRAY_SIZE(channels),
206 .list = channels,
207 .mask = 0,
208};
209
210static int bdw_rt5677_fe_startup(struct snd_pcm_substream *substream)
211{
212 struct snd_pcm_runtime *runtime = substream->runtime;
213
214 /* Board supports stereo configuration only */
215 runtime->hw.channels_max = 2;
216 return snd_pcm_hw_constraint_list(runtime, cond: 0,
217 SNDRV_PCM_HW_PARAM_CHANNELS,
218 l: &constraints_channels);
219}
220
221static const struct snd_soc_ops bdw_rt5677_fe_ops = {
222 .startup = bdw_rt5677_fe_startup,
223};
224
225static int bdw_rt5677_init(struct snd_soc_pcm_runtime *rtd)
226{
227 struct bdw_rt5677_priv *bdw_rt5677 =
228 snd_soc_card_get_drvdata(card: rtd->card);
229 struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
230 struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
231 int ret;
232
233 ret = devm_acpi_dev_add_driver_gpios(dev: component->dev, gpios: bdw_rt5677_gpios);
234 if (ret)
235 dev_warn(component->dev, "Failed to add driver gpios\n");
236
237 /* Enable codec ASRC function for Stereo DAC/Stereo1 ADC/DMIC/I2S1.
238 * The ASRC clock source is clk_i2s1_asrc.
239 */
240 rt5677_sel_asrc_clk_src(component, filter_mask: RT5677_DA_STEREO_FILTER |
241 RT5677_AD_STEREO1_FILTER | RT5677_I2S1_SOURCE,
242 clk_src: RT5677_CLK_SEL_I2S1_ASRC);
243 /* Enable codec ASRC function for Mono ADC L.
244 * The ASRC clock source is clk_sys2_asrc.
245 */
246 rt5677_sel_asrc_clk_src(component, filter_mask: RT5677_AD_MONO_L_FILTER,
247 clk_src: RT5677_CLK_SEL_SYS2);
248
249 /* Request rt5677 GPIO for headphone amp control */
250 bdw_rt5677->gpio_hp_en = gpiod_get(dev: component->dev, con_id: "headphone-enable",
251 flags: GPIOD_OUT_LOW);
252 if (IS_ERR(ptr: bdw_rt5677->gpio_hp_en)) {
253 dev_err(component->dev, "Can't find HP_AMP_SHDN_L gpio\n");
254 return PTR_ERR(ptr: bdw_rt5677->gpio_hp_en);
255 }
256
257 /* Create and initialize headphone jack */
258 if (!snd_soc_card_jack_new_pins(card: rtd->card, id: "Headphone Jack",
259 type: SND_JACK_HEADPHONE, jack: &headphone_jack,
260 pins: &headphone_jack_pin, num_pins: 1)) {
261 headphone_jack_gpio.gpiod_dev = component->dev;
262 if (snd_soc_jack_add_gpios(jack: &headphone_jack, count: 1,
263 gpios: &headphone_jack_gpio))
264 dev_err(component->dev, "Can't add headphone jack gpio\n");
265 } else {
266 dev_err(component->dev, "Can't create headphone jack\n");
267 }
268
269 /* Create and initialize mic jack */
270 if (!snd_soc_card_jack_new_pins(card: rtd->card, id: "Mic Jack",
271 type: SND_JACK_MICROPHONE, jack: &mic_jack,
272 pins: &mic_jack_pin, num_pins: 1)) {
273 mic_jack_gpio.gpiod_dev = component->dev;
274 if (snd_soc_jack_add_gpios(jack: &mic_jack, count: 1, gpios: &mic_jack_gpio))
275 dev_err(component->dev, "Can't add mic jack gpio\n");
276 } else {
277 dev_err(component->dev, "Can't create mic jack\n");
278 }
279 bdw_rt5677->component = component;
280
281 snd_soc_dapm_force_enable_pin(dapm, pin: "MICBIAS1");
282 return 0;
283}
284
285static void bdw_rt5677_exit(struct snd_soc_pcm_runtime *rtd)
286{
287 struct bdw_rt5677_priv *bdw_rt5677 =
288 snd_soc_card_get_drvdata(card: rtd->card);
289
290 /*
291 * The .exit() can be reached without going through the .init()
292 * so explicitly test if the gpiod is valid
293 */
294 if (!IS_ERR_OR_NULL(ptr: bdw_rt5677->gpio_hp_en))
295 gpiod_put(desc: bdw_rt5677->gpio_hp_en);
296}
297
298/* broadwell digital audio interface glue - connects codec <--> CPU */
299SND_SOC_DAILINK_DEF(dummy,
300 DAILINK_COMP_ARRAY(COMP_DUMMY()));
301
302SND_SOC_DAILINK_DEF(fe,
303 DAILINK_COMP_ARRAY(COMP_CPU("System Pin")));
304
305SND_SOC_DAILINK_DEF(platform,
306 DAILINK_COMP_ARRAY(COMP_PLATFORM("haswell-pcm-audio")));
307
308SND_SOC_DAILINK_DEF(be,
309 DAILINK_COMP_ARRAY(COMP_CODEC("i2c-RT5677CE:00", "rt5677-aif1")));
310
311SND_SOC_DAILINK_DEF(ssp0_port,
312 DAILINK_COMP_ARRAY(COMP_CPU("ssp0-port")));
313
314/* Wake on voice interface */
315SND_SOC_DAILINK_DEFS(dsp,
316 DAILINK_COMP_ARRAY(COMP_CPU("spi-RT5677AA:00")),
317 DAILINK_COMP_ARRAY(COMP_CODEC("i2c-RT5677CE:00", "rt5677-dspbuffer")),
318 DAILINK_COMP_ARRAY(COMP_PLATFORM("spi-RT5677AA:00")));
319
320static struct snd_soc_dai_link bdw_rt5677_dais[] = {
321 /* Front End DAI links */
322 {
323 .name = "System PCM",
324 .stream_name = "System Playback/Capture",
325 .nonatomic = 1,
326 .dynamic = 1,
327 .trigger = {
328 SND_SOC_DPCM_TRIGGER_POST,
329 SND_SOC_DPCM_TRIGGER_POST
330 },
331 .ops = &bdw_rt5677_fe_ops,
332 SND_SOC_DAILINK_REG(fe, dummy, platform),
333 },
334
335 /* Non-DPCM links */
336 {
337 .name = "Codec DSP",
338 .stream_name = "Wake on Voice",
339 .capture_only = 1,
340 .ops = &bdw_rt5677_dsp_ops,
341 SND_SOC_DAILINK_REG(dsp),
342 },
343
344 /* Back End DAI links */
345 {
346 /* SSP0 - Codec */
347 .name = "Codec",
348 .id = 0,
349 .nonatomic = 1,
350 .no_pcm = 1,
351 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
352 SND_SOC_DAIFMT_CBC_CFC,
353 .ignore_pmdown_time = 1,
354 .be_hw_params_fixup = broadwell_ssp0_fixup,
355 .ops = &bdw_rt5677_ops,
356 .init = bdw_rt5677_init,
357 .exit = bdw_rt5677_exit,
358 SND_SOC_DAILINK_REG(ssp0_port, be, platform),
359 },
360};
361
362static int bdw_rt5677_suspend_pre(struct snd_soc_card *card)
363{
364 struct bdw_rt5677_priv *bdw_rt5677 = snd_soc_card_get_drvdata(card);
365 struct snd_soc_dapm_context *dapm;
366
367 if (bdw_rt5677->component) {
368 dapm = snd_soc_component_to_dapm(component: bdw_rt5677->component);
369 snd_soc_dapm_disable_pin(dapm, pin: "MICBIAS1");
370 }
371 return 0;
372}
373
374static int bdw_rt5677_resume_post(struct snd_soc_card *card)
375{
376 struct bdw_rt5677_priv *bdw_rt5677 = snd_soc_card_get_drvdata(card);
377 struct snd_soc_dapm_context *dapm;
378
379 if (bdw_rt5677->component) {
380 dapm = snd_soc_component_to_dapm(component: bdw_rt5677->component);
381 snd_soc_dapm_force_enable_pin(dapm, pin: "MICBIAS1");
382 }
383 return 0;
384}
385
386/* use space before codec name to simplify card ID, and simplify driver name */
387#define SOF_CARD_NAME "bdw rt5677" /* card name will be 'sof-bdw rt5677' */
388#define SOF_DRIVER_NAME "SOF"
389
390#define CARD_NAME "bdw-rt5677"
391#define DRIVER_NAME NULL /* card name will be used for driver name */
392
393/* ASoC machine driver for Broadwell DSP + RT5677 */
394static struct snd_soc_card bdw_rt5677_card = {
395 .name = CARD_NAME,
396 .driver_name = DRIVER_NAME,
397 .owner = THIS_MODULE,
398 .dai_link = bdw_rt5677_dais,
399 .num_links = ARRAY_SIZE(bdw_rt5677_dais),
400 .dapm_widgets = bdw_rt5677_widgets,
401 .num_dapm_widgets = ARRAY_SIZE(bdw_rt5677_widgets),
402 .dapm_routes = bdw_rt5677_map,
403 .num_dapm_routes = ARRAY_SIZE(bdw_rt5677_map),
404 .controls = bdw_rt5677_controls,
405 .num_controls = ARRAY_SIZE(bdw_rt5677_controls),
406 .fully_routed = true,
407 .suspend_pre = bdw_rt5677_suspend_pre,
408 .resume_post = bdw_rt5677_resume_post,
409};
410
411static int bdw_rt5677_probe(struct platform_device *pdev)
412{
413 struct bdw_rt5677_priv *bdw_rt5677;
414 struct snd_soc_acpi_mach *mach;
415 int ret;
416
417 bdw_rt5677_card.dev = &pdev->dev;
418
419 /* Allocate driver private struct */
420 bdw_rt5677 = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct bdw_rt5677_priv),
421 GFP_KERNEL);
422 if (!bdw_rt5677)
423 return -ENOMEM;
424
425 /* override platform name, if required */
426 mach = pdev->dev.platform_data;
427 ret = snd_soc_fixup_dai_links_platform_name(card: &bdw_rt5677_card,
428 platform_name: mach->mach_params.platform);
429 if (ret)
430 return ret;
431
432 /* set card and driver name */
433 if (snd_soc_acpi_sof_parent(dev: &pdev->dev)) {
434 bdw_rt5677_card.name = SOF_CARD_NAME;
435 bdw_rt5677_card.driver_name = SOF_DRIVER_NAME;
436 } else {
437 bdw_rt5677_card.name = CARD_NAME;
438 bdw_rt5677_card.driver_name = DRIVER_NAME;
439 }
440
441 snd_soc_card_set_drvdata(card: &bdw_rt5677_card, data: bdw_rt5677);
442
443 return devm_snd_soc_register_card(dev: &pdev->dev, card: &bdw_rt5677_card);
444}
445
446static struct platform_driver bdw_rt5677_audio = {
447 .probe = bdw_rt5677_probe,
448 .driver = {
449 .name = "bdw-rt5677",
450 .pm = &snd_soc_pm_ops
451 },
452};
453
454module_platform_driver(bdw_rt5677_audio)
455
456/* Module information */
457MODULE_AUTHOR("Ben Zhang");
458MODULE_DESCRIPTION("Intel Broadwell RT5677 machine driver");
459MODULE_LICENSE("GPL v2");
460MODULE_ALIAS("platform:bdw-rt5677");
461

source code of linux/sound/soc/intel/boards/bdw-rt5677.c