Commit | Line | Data |
---|---|---|
a86d5057 HP |
1 | /* |
2 | * Intel Skylake I2S Machine Driver for NAU88L25+SSM4567 | |
3 | * | |
4 | * Copyright (C) 2015, Intel Corporation. All rights reserved. | |
5 | * | |
6 | * Modified from: | |
7 | * Intel Skylake I2S Machine Driver for NAU88L25 and SSM4567 | |
8 | * | |
9 | * Copyright (C) 2015, Intel Corporation. All rights reserved. | |
10 | * | |
11 | * This program is free software; you can redistribute it and/or | |
12 | * modify it under the terms of the GNU General Public License version | |
13 | * 2 as published by the Free Software Foundation. | |
14 | * | |
15 | * This program is distributed in the hope that it will be useful, | |
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
18 | * GNU General Public License for more details. | |
19 | */ | |
20 | ||
21 | #include <linux/module.h> | |
22 | #include <linux/platform_device.h> | |
23 | #include <sound/core.h> | |
24 | #include <sound/pcm.h> | |
25 | #include <sound/soc.h> | |
26 | #include <sound/jack.h> | |
27 | #include <sound/pcm_params.h> | |
28 | #include "../../codecs/nau8825.h" | |
29 | ||
30 | #define SKL_NUVOTON_CODEC_DAI "nau8825-hifi" | |
31 | #define SKL_SSM_CODEC_DAI "ssm4567-hifi" | |
32 | ||
33 | static struct snd_soc_jack skylake_headset; | |
34 | static struct snd_soc_card skylake_audio_card; | |
35 | ||
36 | static inline struct snd_soc_dai *skl_get_codec_dai(struct snd_soc_card *card) | |
37 | { | |
38 | int i; | |
39 | ||
40 | for (i = 0; i < card->num_rtd; i++) { | |
41 | struct snd_soc_pcm_runtime *rtd; | |
42 | ||
43 | rtd = card->rtd + i; | |
44 | if (!strncmp(rtd->codec_dai->name, SKL_NUVOTON_CODEC_DAI, | |
45 | strlen(SKL_NUVOTON_CODEC_DAI))) | |
46 | return rtd->codec_dai; | |
47 | } | |
48 | ||
49 | return NULL; | |
50 | } | |
51 | ||
52 | static const struct snd_kcontrol_new skylake_controls[] = { | |
53 | SOC_DAPM_PIN_SWITCH("Headphone Jack"), | |
54 | SOC_DAPM_PIN_SWITCH("Headset Mic"), | |
55 | SOC_DAPM_PIN_SWITCH("Left Speaker"), | |
56 | SOC_DAPM_PIN_SWITCH("Right Speaker"), | |
57 | }; | |
58 | ||
59 | static int platform_clock_control(struct snd_soc_dapm_widget *w, | |
60 | struct snd_kcontrol *k, int event) | |
61 | { | |
62 | struct snd_soc_dapm_context *dapm = w->dapm; | |
63 | struct snd_soc_card *card = dapm->card; | |
64 | struct snd_soc_dai *codec_dai; | |
65 | int ret; | |
66 | ||
67 | codec_dai = skl_get_codec_dai(card); | |
68 | if (!codec_dai) { | |
69 | dev_err(card->dev, "Codec dai not found\n"); | |
70 | return -EIO; | |
71 | } | |
72 | ||
73 | if (SND_SOC_DAPM_EVENT_ON(event)) { | |
74 | ret = snd_soc_dai_set_sysclk(codec_dai, | |
75 | NAU8825_CLK_MCLK, 24000000, 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(codec_dai, | |
82 | NAU8825_CLK_INTERNAL, 0, SND_SOC_CLOCK_IN); | |
83 | if (ret < 0) { | |
84 | dev_err(card->dev, "set sysclk err = %d\n", ret); | |
85 | return -EIO; | |
86 | } | |
87 | } | |
88 | return ret; | |
89 | } | |
90 | ||
91 | static const struct snd_soc_dapm_widget skylake_widgets[] = { | |
92 | SND_SOC_DAPM_HP("Headphone Jack", NULL), | |
93 | SND_SOC_DAPM_MIC("Headset Mic", NULL), | |
94 | SND_SOC_DAPM_SPK("Left Speaker", NULL), | |
95 | SND_SOC_DAPM_SPK("Right Speaker", NULL), | |
96 | SND_SOC_DAPM_MIC("SoC DMIC", NULL), | |
97 | SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, | |
98 | platform_clock_control, SND_SOC_DAPM_PRE_PMU | | |
99 | SND_SOC_DAPM_POST_PMD), | |
100 | }; | |
101 | ||
102 | static const struct snd_soc_dapm_route skylake_map[] = { | |
103 | /* HP jack connectors - unknown if we have jack detection */ | |
104 | {"Headphone Jack", NULL, "HPOL"}, | |
105 | {"Headphone Jack", NULL, "HPOR"}, | |
106 | ||
107 | /* speaker */ | |
108 | {"Left Speaker", NULL, "Left OUT"}, | |
109 | {"Right Speaker", NULL, "Right OUT"}, | |
110 | ||
111 | /* other jacks */ | |
112 | {"MIC", NULL, "Headset Mic"}, | |
113 | {"DMIC AIF", NULL, "SoC DMIC"}, | |
114 | ||
115 | /* CODEC BE connections */ | |
116 | { "Left Playback", NULL, "ssp0 Tx"}, | |
117 | { "Right Playback", NULL, "ssp0 Tx"}, | |
118 | { "ssp0 Tx", NULL, "codec0_out"}, | |
119 | ||
120 | { "AIF1 Playback", NULL, "ssp1 Tx"}, | |
121 | { "ssp1 Tx", NULL, "codec1_out"}, | |
122 | ||
123 | { "codec0_in", NULL, "ssp1 Rx" }, | |
124 | { "ssp1 Rx", NULL, "AIF1 Capture" }, | |
125 | ||
126 | /* DMIC */ | |
127 | { "dmic01_hifi", NULL, "DMIC01 Rx" }, | |
128 | { "DMIC01 Rx", NULL, "Capture" }, | |
129 | { "Headphone Jack", NULL, "Platform Clock" }, | |
130 | { "Headset Mic", NULL, "Platform Clock" }, | |
131 | }; | |
132 | ||
133 | static struct snd_soc_codec_conf ssm4567_codec_conf[] = { | |
134 | { | |
135 | .dev_name = "i2c-INT343B:00", | |
136 | .name_prefix = "Left", | |
137 | }, | |
138 | { | |
139 | .dev_name = "i2c-INT343B:01", | |
140 | .name_prefix = "Right", | |
141 | }, | |
142 | }; | |
143 | ||
144 | static struct snd_soc_dai_link_component ssm4567_codec_components[] = { | |
145 | { /* Left */ | |
146 | .name = "i2c-INT343B:00", | |
147 | .dai_name = SKL_SSM_CODEC_DAI, | |
148 | }, | |
149 | { /* Right */ | |
150 | .name = "i2c-INT343B:01", | |
151 | .dai_name = SKL_SSM_CODEC_DAI, | |
152 | }, | |
153 | }; | |
154 | ||
155 | static int skylake_ssm4567_codec_init(struct snd_soc_pcm_runtime *rtd) | |
156 | { | |
157 | int ret; | |
158 | ||
159 | /* Slot 1 for left */ | |
160 | ret = snd_soc_dai_set_tdm_slot(rtd->codec_dais[0], 0x01, 0x01, 2, 48); | |
161 | if (ret < 0) | |
162 | return ret; | |
163 | ||
164 | /* Slot 2 for right */ | |
165 | ret = snd_soc_dai_set_tdm_slot(rtd->codec_dais[1], 0x02, 0x02, 2, 48); | |
166 | if (ret < 0) | |
167 | return ret; | |
168 | ||
169 | return ret; | |
170 | } | |
171 | ||
172 | static int skylake_nau8825_codec_init(struct snd_soc_pcm_runtime *rtd) | |
173 | { | |
174 | int ret; | |
175 | struct snd_soc_codec *codec = rtd->codec; | |
176 | ||
177 | /* | |
178 | * 4 buttons here map to the google Reference headset | |
179 | * The use of these buttons can be decided by the user space. | |
180 | */ | |
181 | ret = snd_soc_card_jack_new(&skylake_audio_card, "Headset Jack", | |
182 | SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | | |
183 | SND_JACK_BTN_2 | SND_JACK_BTN_3, &skylake_headset, | |
184 | NULL, 0); | |
185 | if (ret) { | |
186 | dev_err(rtd->dev, "Headset Jack creation failed %d\n", ret); | |
187 | return ret; | |
188 | } | |
189 | ||
190 | nau8825_enable_jack_detect(codec, &skylake_headset); | |
191 | ||
192 | return ret; | |
193 | } | |
194 | ||
195 | static int skylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd, | |
196 | struct snd_pcm_hw_params *params) | |
197 | { | |
198 | struct snd_interval *rate = hw_param_interval(params, | |
199 | SNDRV_PCM_HW_PARAM_RATE); | |
200 | struct snd_interval *channels = hw_param_interval(params, | |
201 | SNDRV_PCM_HW_PARAM_CHANNELS); | |
202 | struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); | |
203 | ||
204 | /* The ADSP will covert the FE rate to 48k, stereo */ | |
205 | rate->min = rate->max = 48000; | |
206 | channels->min = channels->max = 2; | |
207 | ||
208 | /* set SSP0 to 24 bit */ | |
209 | snd_mask_none(fmt); | |
210 | snd_mask_set(fmt, SNDRV_PCM_FORMAT_S24_LE); | |
211 | return 0; | |
212 | } | |
213 | ||
214 | static int skylake_nau8825_hw_params(struct snd_pcm_substream *substream, | |
215 | struct snd_pcm_hw_params *params) | |
216 | { | |
217 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
218 | struct snd_soc_dai *codec_dai = rtd->codec_dai; | |
219 | int ret; | |
220 | ||
221 | ret = snd_soc_dai_set_sysclk(codec_dai, | |
222 | NAU8825_CLK_MCLK, 24000000, SND_SOC_CLOCK_IN); | |
223 | ||
224 | if (ret < 0) | |
225 | dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret); | |
226 | ||
227 | return ret; | |
228 | } | |
229 | ||
230 | static struct snd_soc_ops skylake_nau8825_ops = { | |
231 | .hw_params = skylake_nau8825_hw_params, | |
232 | }; | |
233 | ||
234 | /* skylake digital audio interface glue - connects codec <--> CPU */ | |
235 | static struct snd_soc_dai_link skylake_dais[] = { | |
236 | /* Front End DAI links */ | |
237 | { | |
238 | .name = "Skl Audio Port", | |
239 | .stream_name = "Audio", | |
240 | .cpu_dai_name = "System Pin", | |
241 | .platform_name = "0000:00:1f.3", | |
242 | .dynamic = 1, | |
243 | .codec_name = "snd-soc-dummy", | |
244 | .codec_dai_name = "snd-soc-dummy-dai", | |
245 | .nonatomic = 1, | |
246 | .trigger = { | |
247 | SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, | |
248 | .dpcm_playback = 1, | |
249 | }, | |
250 | { | |
251 | .name = "Skl Audio Capture Port", | |
252 | .stream_name = "Audio Record", | |
253 | .cpu_dai_name = "System Pin", | |
254 | .platform_name = "0000:00:1f.3", | |
255 | .dynamic = 1, | |
256 | .codec_name = "snd-soc-dummy", | |
257 | .codec_dai_name = "snd-soc-dummy-dai", | |
258 | .nonatomic = 1, | |
259 | .trigger = { | |
260 | SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, | |
261 | .dpcm_capture = 1, | |
262 | }, | |
263 | { | |
264 | .name = "Skl Audio Reference cap", | |
265 | .stream_name = "refcap", | |
266 | .cpu_dai_name = "Reference Pin", | |
267 | .codec_name = "snd-soc-dummy", | |
268 | .codec_dai_name = "snd-soc-dummy-dai", | |
269 | .platform_name = "0000:00:1f.3", | |
270 | .init = NULL, | |
271 | .dpcm_capture = 1, | |
272 | .ignore_suspend = 1, | |
273 | .nonatomic = 1, | |
274 | .dynamic = 1, | |
275 | }, | |
276 | /* Back End DAI links */ | |
277 | { | |
278 | /* SSP0 - Codec */ | |
279 | .name = "SSP0-Codec", | |
280 | .be_id = 0, | |
281 | .cpu_dai_name = "SSP0 Pin", | |
282 | .platform_name = "0000:00:1f.3", | |
283 | .no_pcm = 1, | |
284 | .codecs = ssm4567_codec_components, | |
285 | .num_codecs = ARRAY_SIZE(ssm4567_codec_components), | |
286 | .dai_fmt = SND_SOC_DAIFMT_DSP_A | | |
287 | SND_SOC_DAIFMT_IB_NF | | |
288 | SND_SOC_DAIFMT_CBS_CFS, | |
289 | .init = skylake_ssm4567_codec_init, | |
290 | .ignore_suspend = 1, | |
291 | .ignore_pmdown_time = 1, | |
292 | .be_hw_params_fixup = skylake_ssp_fixup, | |
293 | .dpcm_playback = 1, | |
294 | }, | |
295 | { | |
296 | /* SSP1 - Codec */ | |
297 | .name = "SSP1-Codec", | |
298 | .be_id = 0, | |
299 | .cpu_dai_name = "SSP1 Pin", | |
300 | .platform_name = "0000:00:1f.3", | |
301 | .no_pcm = 1, | |
302 | .codec_name = "i2c-10508825:00", | |
303 | .codec_dai_name = SKL_NUVOTON_CODEC_DAI, | |
304 | .init = skylake_nau8825_codec_init, | |
305 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | | |
306 | SND_SOC_DAIFMT_CBS_CFS, | |
307 | .ignore_suspend = 1, | |
308 | .ignore_pmdown_time = 1, | |
309 | .be_hw_params_fixup = skylake_ssp_fixup, | |
310 | .ops = &skylake_nau8825_ops, | |
311 | .dpcm_playback = 1, | |
312 | .dpcm_capture = 1, | |
313 | }, | |
314 | { | |
315 | .name = "dmic01", | |
316 | .be_id = 1, | |
317 | .cpu_dai_name = "DMIC01 Pin", | |
318 | .codec_name = "dmic-codec", | |
319 | .codec_dai_name = "dmic-hifi", | |
320 | .platform_name = "0000:00:1f.3", | |
321 | .ignore_suspend = 1, | |
322 | .dpcm_capture = 1, | |
323 | .no_pcm = 1, | |
324 | }, | |
325 | }; | |
326 | ||
327 | /* skylake audio machine driver for SPT + NAU88L25 */ | |
328 | static struct snd_soc_card skylake_audio_card = { | |
329 | .name = "sklnau8825adi", | |
330 | .owner = THIS_MODULE, | |
331 | .dai_link = skylake_dais, | |
332 | .num_links = ARRAY_SIZE(skylake_dais), | |
333 | .controls = skylake_controls, | |
334 | .num_controls = ARRAY_SIZE(skylake_controls), | |
335 | .dapm_widgets = skylake_widgets, | |
336 | .num_dapm_widgets = ARRAY_SIZE(skylake_widgets), | |
337 | .dapm_routes = skylake_map, | |
338 | .num_dapm_routes = ARRAY_SIZE(skylake_map), | |
339 | .codec_conf = ssm4567_codec_conf, | |
340 | .num_configs = ARRAY_SIZE(ssm4567_codec_conf), | |
341 | }; | |
342 | ||
343 | static int skylake_audio_probe(struct platform_device *pdev) | |
344 | { | |
345 | skylake_audio_card.dev = &pdev->dev; | |
346 | ||
347 | return devm_snd_soc_register_card(&pdev->dev, &skylake_audio_card); | |
348 | } | |
349 | ||
350 | static struct platform_driver skylake_audio = { | |
351 | .probe = skylake_audio_probe, | |
352 | .driver = { | |
353 | .name = "skl_nau88l25_ssm4567_i2s", | |
354 | .pm = &snd_soc_pm_ops, | |
355 | }, | |
356 | }; | |
357 | ||
358 | module_platform_driver(skylake_audio) | |
359 | ||
360 | /* Module information */ | |
361 | MODULE_AUTHOR("Conrad Cooke <conrad.cooke@intel.com>"); | |
362 | MODULE_AUTHOR("Harsha Priya <harshapriya.n@intel.com>"); | |
363 | MODULE_AUTHOR("Naveen M <naveen.m@intel.com>"); | |
364 | MODULE_AUTHOR("Sathya Prakash M R <sathya.prakash.m.r@intel.com>"); | |
365 | MODULE_AUTHOR("Yong Zhi <yong.zhi@intel.com>"); | |
366 | MODULE_DESCRIPTION("Intel Audio Machine driver for SKL with NAU88L25 and SSM4567 in I2S Mode"); | |
367 | MODULE_LICENSE("GPL v2"); | |
368 | MODULE_ALIAS("platform:skl_nau88l25_ssm4567_i2s"); |