]>
Commit | Line | Data |
---|---|---|
6b8e4e7d AA |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | // | |
3 | // Machine driver for AMD ACP Audio engine using DA7219 & MAX98357 codec. | |
4 | // | |
5 | //Copyright 2016 Advanced Micro Devices, Inc. | |
6 | ||
7 | #include <sound/core.h> | |
8 | #include <sound/soc.h> | |
9 | #include <sound/pcm.h> | |
10 | #include <sound/pcm_params.h> | |
11 | #include <sound/soc-dapm.h> | |
12 | #include <sound/jack.h> | |
13 | #include <linux/clk.h> | |
14 | #include <linux/gpio.h> | |
72c3b2b0 | 15 | #include <linux/gpio/consumer.h> |
6b8e4e7d AA |
16 | #include <linux/module.h> |
17 | #include <linux/i2c.h> | |
18 | #include <linux/input.h> | |
14beaccc | 19 | #include <linux/io.h> |
6b8e4e7d AA |
20 | #include <linux/acpi.h> |
21 | ||
22 | #include "raven/acp3x.h" | |
23 | #include "../codecs/rt5682.h" | |
24 | ||
25 | #define PCO_PLAT_CLK 48000000 | |
26 | #define RT5682_PLL_FREQ (48000 * 512) | |
27 | #define DUAL_CHANNEL 2 | |
28 | ||
29 | static struct snd_soc_jack pco_jack; | |
30 | static struct clk *rt5682_dai_wclk; | |
31 | static struct clk *rt5682_dai_bclk; | |
72c3b2b0 | 32 | static struct gpio_desc *dmic_sel; |
6b8e4e7d AA |
33 | |
34 | static int acp3x_5682_init(struct snd_soc_pcm_runtime *rtd) | |
35 | { | |
36 | int ret; | |
37 | struct snd_soc_card *card = rtd->card; | |
b09b22fc | 38 | struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); |
6b8e4e7d AA |
39 | struct snd_soc_component *component = codec_dai->component; |
40 | ||
41 | dev_info(rtd->dev, "codec dai name = %s\n", codec_dai->name); | |
42 | ||
43 | /* set rt5682 dai fmt */ | |
44 | ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | |
45 | | SND_SOC_DAIFMT_NB_NF | |
46 | | SND_SOC_DAIFMT_CBM_CFM); | |
47 | if (ret < 0) { | |
48 | dev_err(rtd->card->dev, | |
49 | "Failed to set rt5682 dai fmt: %d\n", ret); | |
50 | return ret; | |
51 | } | |
52 | ||
53 | /* set codec PLL */ | |
54 | ret = snd_soc_dai_set_pll(codec_dai, RT5682_PLL2, RT5682_PLL2_S_MCLK, | |
55 | PCO_PLAT_CLK, RT5682_PLL_FREQ); | |
56 | if (ret < 0) { | |
57 | dev_err(rtd->dev, "can't set rt5682 PLL: %d\n", ret); | |
58 | return ret; | |
59 | } | |
60 | ||
61 | /* Set codec sysclk */ | |
62 | ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL2, | |
63 | RT5682_PLL_FREQ, SND_SOC_CLOCK_IN); | |
64 | if (ret < 0) { | |
65 | dev_err(rtd->dev, | |
66 | "Failed to set rt5682 SYSCLK: %d\n", ret); | |
67 | return ret; | |
68 | } | |
69 | ||
70 | /* Set tdm/i2s1 master bclk ratio */ | |
71 | ret = snd_soc_dai_set_bclk_ratio(codec_dai, 64); | |
72 | if (ret < 0) { | |
73 | dev_err(rtd->dev, | |
74 | "Failed to set rt5682 tdm bclk ratio: %d\n", ret); | |
75 | return ret; | |
76 | } | |
77 | ||
78 | rt5682_dai_wclk = clk_get(component->dev, "rt5682-dai-wclk"); | |
79 | rt5682_dai_bclk = clk_get(component->dev, "rt5682-dai-bclk"); | |
80 | ||
81 | ret = snd_soc_card_jack_new(card, "Headset Jack", | |
82 | SND_JACK_HEADSET | SND_JACK_LINEOUT | | |
83 | SND_JACK_BTN_0 | SND_JACK_BTN_1 | | |
84 | SND_JACK_BTN_2 | SND_JACK_BTN_3, | |
85 | &pco_jack, NULL, 0); | |
86 | if (ret) { | |
87 | dev_err(card->dev, "HP jack creation failed %d\n", ret); | |
88 | return ret; | |
89 | } | |
90 | ||
91 | snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); | |
8dbcfcfc AA |
92 | snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); |
93 | snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_2, KEY_VOLUMEUP); | |
94 | snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); | |
6b8e4e7d AA |
95 | |
96 | ret = snd_soc_component_set_jack(component, &pco_jack, NULL); | |
97 | if (ret) { | |
98 | dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret); | |
99 | return ret; | |
100 | } | |
101 | ||
102 | return ret; | |
103 | } | |
104 | ||
105 | static int rt5682_clk_enable(struct snd_pcm_substream *substream) | |
106 | { | |
107 | int ret = 0; | |
108 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
109 | ||
110 | /* RT5682 will support only 48K output with 48M mclk */ | |
111 | clk_set_rate(rt5682_dai_wclk, 48000); | |
112 | clk_set_rate(rt5682_dai_bclk, 48000 * 64); | |
113 | ret = clk_prepare_enable(rt5682_dai_wclk); | |
114 | if (ret < 0) { | |
115 | dev_err(rtd->dev, "can't enable wclk %d\n", ret); | |
116 | return ret; | |
117 | } | |
118 | ||
119 | return ret; | |
120 | } | |
121 | ||
122 | static void rt5682_clk_disable(void) | |
123 | { | |
124 | clk_disable_unprepare(rt5682_dai_wclk); | |
125 | } | |
126 | ||
127 | static const unsigned int channels[] = { | |
128 | DUAL_CHANNEL, | |
129 | }; | |
130 | ||
131 | static const unsigned int rates[] = { | |
132 | 48000, | |
133 | }; | |
134 | ||
135 | static const struct snd_pcm_hw_constraint_list constraints_rates = { | |
136 | .count = ARRAY_SIZE(rates), | |
137 | .list = rates, | |
138 | .mask = 0, | |
139 | }; | |
140 | ||
141 | static const struct snd_pcm_hw_constraint_list constraints_channels = { | |
142 | .count = ARRAY_SIZE(channels), | |
143 | .list = channels, | |
144 | .mask = 0, | |
145 | }; | |
146 | ||
147 | static int acp3x_5682_startup(struct snd_pcm_substream *substream) | |
148 | { | |
149 | struct snd_pcm_runtime *runtime = substream->runtime; | |
150 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
151 | struct snd_soc_card *card = rtd->card; | |
152 | struct acp3x_platform_info *machine = snd_soc_card_get_drvdata(card); | |
153 | ||
154 | machine->play_i2s_instance = I2S_SP_INSTANCE; | |
155 | machine->cap_i2s_instance = I2S_SP_INSTANCE; | |
156 | ||
157 | runtime->hw.channels_max = DUAL_CHANNEL; | |
158 | snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, | |
159 | &constraints_channels); | |
160 | snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, | |
161 | &constraints_rates); | |
162 | return rt5682_clk_enable(substream); | |
163 | } | |
164 | ||
165 | static int acp3x_max_startup(struct snd_pcm_substream *substream) | |
166 | { | |
167 | struct snd_pcm_runtime *runtime = substream->runtime; | |
168 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
169 | struct snd_soc_card *card = rtd->card; | |
170 | struct acp3x_platform_info *machine = snd_soc_card_get_drvdata(card); | |
171 | ||
172 | machine->play_i2s_instance = I2S_BT_INSTANCE; | |
173 | ||
174 | runtime->hw.channels_max = DUAL_CHANNEL; | |
175 | snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, | |
176 | &constraints_channels); | |
177 | snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, | |
178 | &constraints_rates); | |
179 | return rt5682_clk_enable(substream); | |
180 | } | |
181 | ||
72c3b2b0 | 182 | static int acp3x_ec_dmic0_startup(struct snd_pcm_substream *substream) |
6b8e4e7d AA |
183 | { |
184 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
185 | struct snd_soc_card *card = rtd->card; | |
b09b22fc | 186 | struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); |
6b8e4e7d AA |
187 | struct acp3x_platform_info *machine = snd_soc_card_get_drvdata(card); |
188 | ||
189 | machine->cap_i2s_instance = I2S_BT_INSTANCE; | |
190 | snd_soc_dai_set_bclk_ratio(codec_dai, 64); | |
72c3b2b0 AA |
191 | if (dmic_sel) |
192 | gpiod_set_value(dmic_sel, 0); | |
193 | ||
194 | return rt5682_clk_enable(substream); | |
195 | } | |
196 | ||
197 | static int acp3x_ec_dmic1_startup(struct snd_pcm_substream *substream) | |
198 | { | |
199 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
200 | struct snd_soc_card *card = rtd->card; | |
b09b22fc | 201 | struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); |
72c3b2b0 AA |
202 | struct acp3x_platform_info *machine = snd_soc_card_get_drvdata(card); |
203 | ||
204 | machine->cap_i2s_instance = I2S_BT_INSTANCE; | |
205 | snd_soc_dai_set_bclk_ratio(codec_dai, 64); | |
206 | if (dmic_sel) | |
207 | gpiod_set_value(dmic_sel, 1); | |
6b8e4e7d AA |
208 | |
209 | return rt5682_clk_enable(substream); | |
210 | } | |
211 | ||
212 | static void rt5682_shutdown(struct snd_pcm_substream *substream) | |
213 | { | |
214 | rt5682_clk_disable(); | |
215 | } | |
216 | ||
217 | static const struct snd_soc_ops acp3x_5682_ops = { | |
218 | .startup = acp3x_5682_startup, | |
219 | .shutdown = rt5682_shutdown, | |
220 | }; | |
221 | ||
222 | static const struct snd_soc_ops acp3x_max_play_ops = { | |
223 | .startup = acp3x_max_startup, | |
224 | .shutdown = rt5682_shutdown, | |
225 | }; | |
226 | ||
72c3b2b0 AA |
227 | static const struct snd_soc_ops acp3x_ec_cap0_ops = { |
228 | .startup = acp3x_ec_dmic0_startup, | |
229 | .shutdown = rt5682_shutdown, | |
230 | }; | |
231 | ||
232 | static const struct snd_soc_ops acp3x_ec_cap1_ops = { | |
233 | .startup = acp3x_ec_dmic1_startup, | |
6b8e4e7d AA |
234 | .shutdown = rt5682_shutdown, |
235 | }; | |
236 | ||
237 | SND_SOC_DAILINK_DEF(acp3x_i2s, | |
238 | DAILINK_COMP_ARRAY(COMP_CPU("acp3x_i2s_playcap.0"))); | |
239 | SND_SOC_DAILINK_DEF(acp3x_bt, | |
240 | DAILINK_COMP_ARRAY(COMP_CPU("acp3x_i2s_playcap.2"))); | |
241 | ||
242 | SND_SOC_DAILINK_DEF(rt5682, | |
243 | DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5682:00", "rt5682-aif1"))); | |
244 | SND_SOC_DAILINK_DEF(max, | |
245 | DAILINK_COMP_ARRAY(COMP_CODEC("MX98357A:00", "HiFi"))); | |
246 | SND_SOC_DAILINK_DEF(cros_ec, | |
247 | DAILINK_COMP_ARRAY(COMP_CODEC("GOOG0013:00", "EC Codec I2S RX"))); | |
248 | ||
249 | SND_SOC_DAILINK_DEF(platform, | |
250 | DAILINK_COMP_ARRAY(COMP_PLATFORM("acp3x_rv_i2s_dma.0"))); | |
251 | ||
252 | static struct snd_soc_dai_link acp3x_dai_5682_98357[] = { | |
253 | { | |
254 | .name = "acp3x-5682-play", | |
255 | .stream_name = "Playback", | |
256 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
257 | | SND_SOC_DAIFMT_CBM_CFM, | |
258 | .init = acp3x_5682_init, | |
259 | .dpcm_playback = 1, | |
260 | .dpcm_capture = 1, | |
261 | .ops = &acp3x_5682_ops, | |
262 | SND_SOC_DAILINK_REG(acp3x_i2s, rt5682, platform), | |
263 | }, | |
264 | { | |
265 | .name = "acp3x-max98357-play", | |
266 | .stream_name = "HiFi Playback", | |
267 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
268 | | SND_SOC_DAIFMT_CBM_CFM, | |
269 | .dpcm_playback = 1, | |
270 | .ops = &acp3x_max_play_ops, | |
271 | SND_SOC_DAILINK_REG(acp3x_bt, max, platform), | |
272 | }, | |
273 | { | |
72c3b2b0 AA |
274 | .name = "acp3x-ec-dmic0-capture", |
275 | .stream_name = "Capture DMIC0", | |
276 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
277 | | SND_SOC_DAIFMT_CBS_CFS, | |
278 | .dpcm_capture = 1, | |
279 | .ops = &acp3x_ec_cap0_ops, | |
280 | SND_SOC_DAILINK_REG(acp3x_bt, cros_ec, platform), | |
281 | }, | |
282 | { | |
283 | .name = "acp3x-ec-dmic1-capture", | |
284 | .stream_name = "Capture DMIC1", | |
6b8e4e7d AA |
285 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
286 | | SND_SOC_DAIFMT_CBS_CFS, | |
287 | .dpcm_capture = 1, | |
72c3b2b0 | 288 | .ops = &acp3x_ec_cap1_ops, |
6b8e4e7d AA |
289 | SND_SOC_DAILINK_REG(acp3x_bt, cros_ec, platform), |
290 | }, | |
291 | }; | |
292 | ||
293 | static const struct snd_soc_dapm_widget acp3x_widgets[] = { | |
294 | SND_SOC_DAPM_HP("Headphone Jack", NULL), | |
295 | SND_SOC_DAPM_SPK("Spk", NULL), | |
296 | SND_SOC_DAPM_MIC("Headset Mic", NULL), | |
297 | }; | |
298 | ||
299 | static const struct snd_soc_dapm_route acp3x_audio_route[] = { | |
300 | {"Headphone Jack", NULL, "HPOL"}, | |
301 | {"Headphone Jack", NULL, "HPOR"}, | |
302 | {"IN1P", NULL, "Headset Mic"}, | |
303 | {"Spk", NULL, "Speaker"}, | |
304 | }; | |
305 | ||
306 | static const struct snd_kcontrol_new acp3x_mc_controls[] = { | |
307 | SOC_DAPM_PIN_SWITCH("Headphone Jack"), | |
308 | SOC_DAPM_PIN_SWITCH("Spk"), | |
309 | SOC_DAPM_PIN_SWITCH("Headset Mic"), | |
310 | }; | |
311 | ||
312 | static struct snd_soc_card acp3x_card = { | |
313 | .name = "acp3xalc5682m98357", | |
314 | .owner = THIS_MODULE, | |
315 | .dai_link = acp3x_dai_5682_98357, | |
316 | .num_links = ARRAY_SIZE(acp3x_dai_5682_98357), | |
317 | .dapm_widgets = acp3x_widgets, | |
318 | .num_dapm_widgets = ARRAY_SIZE(acp3x_widgets), | |
319 | .dapm_routes = acp3x_audio_route, | |
320 | .num_dapm_routes = ARRAY_SIZE(acp3x_audio_route), | |
321 | .controls = acp3x_mc_controls, | |
322 | .num_controls = ARRAY_SIZE(acp3x_mc_controls), | |
323 | }; | |
324 | ||
325 | static int acp3x_probe(struct platform_device *pdev) | |
326 | { | |
327 | int ret; | |
328 | struct snd_soc_card *card; | |
329 | struct acp3x_platform_info *machine; | |
330 | ||
331 | machine = devm_kzalloc(&pdev->dev, sizeof(*machine), GFP_KERNEL); | |
332 | if (!machine) | |
333 | return -ENOMEM; | |
334 | ||
335 | card = &acp3x_card; | |
336 | acp3x_card.dev = &pdev->dev; | |
337 | platform_set_drvdata(pdev, card); | |
338 | snd_soc_card_set_drvdata(card, machine); | |
72c3b2b0 AA |
339 | |
340 | dmic_sel = devm_gpiod_get(&pdev->dev, "dmic", GPIOD_OUT_LOW); | |
341 | if (IS_ERR(dmic_sel)) { | |
d7729c40 | 342 | dev_err(&pdev->dev, "DMIC gpio failed err=%ld\n", |
72c3b2b0 AA |
343 | PTR_ERR(dmic_sel)); |
344 | return PTR_ERR(dmic_sel); | |
345 | } | |
346 | ||
6b8e4e7d AA |
347 | ret = devm_snd_soc_register_card(&pdev->dev, &acp3x_card); |
348 | if (ret) { | |
349 | dev_err(&pdev->dev, | |
350 | "devm_snd_soc_register_card(%s) failed: %d\n", | |
351 | acp3x_card.name, ret); | |
352 | return ret; | |
353 | } | |
354 | return 0; | |
355 | } | |
356 | ||
357 | static const struct acpi_device_id acp3x_audio_acpi_match[] = { | |
358 | { "AMDI5682", 0 }, | |
359 | {}, | |
360 | }; | |
361 | MODULE_DEVICE_TABLE(acpi, acp3x_audio_acpi_match); | |
362 | ||
363 | static struct platform_driver acp3x_audio = { | |
364 | .driver = { | |
365 | .name = "acp3x-alc5682-max98357", | |
366 | .acpi_match_table = ACPI_PTR(acp3x_audio_acpi_match), | |
367 | .pm = &snd_soc_pm_ops, | |
368 | }, | |
369 | .probe = acp3x_probe, | |
370 | }; | |
371 | ||
372 | module_platform_driver(acp3x_audio); | |
373 | ||
374 | MODULE_AUTHOR("akshu.agrawal@amd.com"); | |
375 | MODULE_DESCRIPTION("ALC5682 & MAX98357 audio support"); | |
376 | MODULE_LICENSE("GPL v2"); |