Commit | Line | Data |
---|---|---|
0a590b1d MB |
1 | /* |
2 | * Littlemill audio support | |
3 | * | |
4 | * Copyright 2011 Wolfson Microelectronics | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms of the GNU General Public License as published by the | |
8 | * Free Software Foundation; either version 2 of the License, or (at your | |
9 | * option) any later version. | |
10 | */ | |
11 | ||
12 | #include <sound/soc.h> | |
13 | #include <sound/soc-dapm.h> | |
14 | #include <sound/jack.h> | |
15 | #include <linux/gpio.h> | |
16 | #include <linux/module.h> | |
17 | ||
18 | #include "../codecs/wm8994.h" | |
19 | ||
20 | static int sample_rate = 44100; | |
21 | ||
22 | static int littlemill_set_bias_level(struct snd_soc_card *card, | |
23 | struct snd_soc_dapm_context *dapm, | |
24 | enum snd_soc_bias_level level) | |
25 | { | |
5015920a ML |
26 | struct snd_soc_pcm_runtime *rtd; |
27 | struct snd_soc_dai *aif1_dai; | |
0a590b1d MB |
28 | int ret; |
29 | ||
5015920a ML |
30 | rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); |
31 | aif1_dai = rtd->codec_dai; | |
32 | ||
0cbe4b36 | 33 | if (dapm->dev != aif1_dai->dev) |
0a590b1d MB |
34 | return 0; |
35 | ||
36 | switch (level) { | |
37 | case SND_SOC_BIAS_PREPARE: | |
38 | /* | |
39 | * If we've not already clocked things via hw_params() | |
40 | * then do so now, otherwise these are noops. | |
41 | */ | |
42 | if (dapm->bias_level == SND_SOC_BIAS_STANDBY) { | |
0cbe4b36 | 43 | ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, |
0a590b1d MB |
44 | WM8994_FLL_SRC_MCLK2, 32768, |
45 | sample_rate * 512); | |
46 | if (ret < 0) { | |
47 | pr_err("Failed to start FLL: %d\n", ret); | |
48 | return ret; | |
49 | } | |
50 | ||
0cbe4b36 | 51 | ret = snd_soc_dai_set_sysclk(aif1_dai, |
0a590b1d MB |
52 | WM8994_SYSCLK_FLL1, |
53 | sample_rate * 512, | |
54 | SND_SOC_CLOCK_IN); | |
55 | if (ret < 0) { | |
56 | pr_err("Failed to set SYSCLK: %d\n", ret); | |
57 | return ret; | |
58 | } | |
59 | } | |
60 | break; | |
61 | ||
62 | default: | |
63 | break; | |
64 | } | |
65 | ||
66 | return 0; | |
67 | } | |
68 | ||
69 | static int littlemill_set_bias_level_post(struct snd_soc_card *card, | |
70 | struct snd_soc_dapm_context *dapm, | |
71 | enum snd_soc_bias_level level) | |
72 | { | |
5015920a ML |
73 | struct snd_soc_pcm_runtime *rtd; |
74 | struct snd_soc_dai *aif1_dai; | |
0a590b1d MB |
75 | int ret; |
76 | ||
5015920a ML |
77 | rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); |
78 | aif1_dai = rtd->codec_dai; | |
79 | ||
0cbe4b36 | 80 | if (dapm->dev != aif1_dai->dev) |
0a590b1d MB |
81 | return 0; |
82 | ||
83 | switch (level) { | |
84 | case SND_SOC_BIAS_STANDBY: | |
0cbe4b36 MB |
85 | ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2, |
86 | 32768, SND_SOC_CLOCK_IN); | |
87 | if (ret < 0) { | |
88 | pr_err("Failed to switch away from FLL1: %d\n", ret); | |
89 | return ret; | |
90 | } | |
91 | ||
92 | ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, | |
93 | 0, 0, 0); | |
94 | if (ret < 0) { | |
95 | pr_err("Failed to stop FLL1: %d\n", ret); | |
96 | return ret; | |
97 | } | |
98 | break; | |
99 | ||
0a590b1d MB |
100 | default: |
101 | break; | |
102 | } | |
103 | ||
104 | dapm->bias_level = level; | |
105 | ||
106 | return 0; | |
107 | } | |
108 | ||
109 | static int littlemill_hw_params(struct snd_pcm_substream *substream, | |
110 | struct snd_pcm_hw_params *params) | |
111 | { | |
112 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
113 | struct snd_soc_dai *codec_dai = rtd->codec_dai; | |
114 | int ret; | |
115 | ||
116 | sample_rate = params_rate(params); | |
117 | ||
118 | ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, | |
119 | WM8994_FLL_SRC_MCLK2, 32768, | |
120 | sample_rate * 512); | |
121 | if (ret < 0) { | |
122 | pr_err("Failed to start FLL: %d\n", ret); | |
123 | return ret; | |
124 | } | |
125 | ||
126 | ret = snd_soc_dai_set_sysclk(codec_dai, | |
127 | WM8994_SYSCLK_FLL1, | |
128 | sample_rate * 512, | |
129 | SND_SOC_CLOCK_IN); | |
130 | if (ret < 0) { | |
131 | pr_err("Failed to set SYSCLK: %d\n", ret); | |
132 | return ret; | |
133 | } | |
134 | ||
135 | return 0; | |
136 | } | |
137 | ||
138 | static struct snd_soc_ops littlemill_ops = { | |
139 | .hw_params = littlemill_hw_params, | |
140 | }; | |
141 | ||
0cbe4b36 MB |
142 | static const struct snd_soc_pcm_stream baseband_params = { |
143 | .formats = SNDRV_PCM_FMTBIT_S32_LE, | |
144 | .rate_min = 8000, | |
145 | .rate_max = 8000, | |
146 | .channels_min = 2, | |
147 | .channels_max = 2, | |
148 | }; | |
149 | ||
0a590b1d MB |
150 | static struct snd_soc_dai_link littlemill_dai[] = { |
151 | { | |
152 | .name = "CPU", | |
153 | .stream_name = "CPU", | |
154 | .cpu_dai_name = "samsung-i2s.0", | |
155 | .codec_dai_name = "wm8994-aif1", | |
a08485d8 | 156 | .platform_name = "samsung-i2s.0", |
0a590b1d MB |
157 | .codec_name = "wm8994-codec", |
158 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
159 | | SND_SOC_DAIFMT_CBM_CFM, | |
160 | .ops = &littlemill_ops, | |
161 | }, | |
0cbe4b36 MB |
162 | { |
163 | .name = "Baseband", | |
164 | .stream_name = "Baseband", | |
165 | .cpu_dai_name = "wm8994-aif2", | |
166 | .codec_dai_name = "wm1250-ev1", | |
167 | .codec_name = "wm1250-ev1.1-0027", | |
168 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
169 | | SND_SOC_DAIFMT_CBM_CFM, | |
170 | .ignore_suspend = 1, | |
171 | .params = &baseband_params, | |
172 | }, | |
0a590b1d MB |
173 | }; |
174 | ||
68a02db4 MB |
175 | static int bbclk_ev(struct snd_soc_dapm_widget *w, |
176 | struct snd_kcontrol *kcontrol, int event) | |
177 | { | |
178 | struct snd_soc_card *card = w->dapm->card; | |
5015920a ML |
179 | struct snd_soc_pcm_runtime *rtd; |
180 | struct snd_soc_dai *aif2_dai; | |
68a02db4 MB |
181 | int ret; |
182 | ||
5015920a ML |
183 | rtd = snd_soc_get_pcm_runtime(card, card->dai_link[1].name); |
184 | aif2_dai = rtd->cpu_dai; | |
185 | ||
68a02db4 MB |
186 | switch (event) { |
187 | case SND_SOC_DAPM_PRE_PMU: | |
188 | ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2, | |
189 | WM8994_FLL_SRC_BCLK, 64 * 8000, | |
190 | 8000 * 256); | |
191 | if (ret < 0) { | |
192 | pr_err("Failed to start FLL: %d\n", ret); | |
193 | return ret; | |
194 | } | |
195 | ||
196 | ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_FLL2, | |
197 | 8000 * 256, | |
198 | SND_SOC_CLOCK_IN); | |
199 | if (ret < 0) { | |
200 | pr_err("Failed to set SYSCLK: %d\n", ret); | |
201 | return ret; | |
202 | } | |
203 | break; | |
204 | case SND_SOC_DAPM_POST_PMD: | |
205 | ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_MCLK2, | |
206 | 32768, SND_SOC_CLOCK_IN); | |
207 | if (ret < 0) { | |
208 | pr_err("Failed to switch away from FLL2: %d\n", ret); | |
209 | return ret; | |
210 | } | |
211 | ||
212 | ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2, | |
213 | 0, 0, 0); | |
214 | if (ret < 0) { | |
215 | pr_err("Failed to stop FLL2: %d\n", ret); | |
216 | return ret; | |
217 | } | |
218 | break; | |
219 | default: | |
220 | return -EINVAL; | |
221 | } | |
222 | ||
223 | return 0; | |
224 | } | |
225 | ||
31a2239a MB |
226 | static const struct snd_kcontrol_new controls[] = { |
227 | SOC_DAPM_PIN_SWITCH("WM1250 Input"), | |
228 | SOC_DAPM_PIN_SWITCH("WM1250 Output"), | |
229 | }; | |
230 | ||
0a590b1d MB |
231 | static struct snd_soc_dapm_widget widgets[] = { |
232 | SND_SOC_DAPM_HP("Headphone", NULL), | |
8f103167 MB |
233 | |
234 | SND_SOC_DAPM_MIC("AMIC", NULL), | |
235 | SND_SOC_DAPM_MIC("DMIC", NULL), | |
68a02db4 MB |
236 | |
237 | SND_SOC_DAPM_SUPPLY_S("Baseband Clock", -1, SND_SOC_NOPM, 0, 0, | |
238 | bbclk_ev, | |
239 | SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), | |
0a590b1d MB |
240 | }; |
241 | ||
242 | static struct snd_soc_dapm_route audio_paths[] = { | |
243 | { "Headphone", NULL, "HPOUT1L" }, | |
244 | { "Headphone", NULL, "HPOUT1R" }, | |
8f103167 MB |
245 | |
246 | { "AMIC", NULL, "MICBIAS1" }, /* Default for AMICBIAS jumper */ | |
247 | { "IN1LN", NULL, "AMIC" }, | |
248 | ||
249 | { "DMIC", NULL, "MICBIAS2" }, /* Default for DMICBIAS jumper */ | |
250 | { "DMIC1DAT", NULL, "DMIC" }, | |
251 | { "DMIC2DAT", NULL, "DMIC" }, | |
68a02db4 MB |
252 | |
253 | { "AIF2CLK", NULL, "Baseband Clock" }, | |
0a590b1d MB |
254 | }; |
255 | ||
f2e2026c MB |
256 | static struct snd_soc_jack littlemill_headset; |
257 | ||
0a590b1d MB |
258 | static int littlemill_late_probe(struct snd_soc_card *card) |
259 | { | |
5015920a ML |
260 | struct snd_soc_pcm_runtime *rtd; |
261 | struct snd_soc_codec *codec; | |
262 | struct snd_soc_dai *aif1_dai; | |
263 | struct snd_soc_dai *aif2_dai; | |
0a590b1d MB |
264 | int ret; |
265 | ||
5015920a ML |
266 | rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); |
267 | codec = rtd->codec; | |
268 | aif1_dai = rtd->codec_dai; | |
269 | ||
270 | rtd = snd_soc_get_pcm_runtime(card, card->dai_link[1].name); | |
271 | aif2_dai = rtd->cpu_dai; | |
272 | ||
0cbe4b36 MB |
273 | ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2, |
274 | 32768, SND_SOC_CLOCK_IN); | |
275 | if (ret < 0) | |
276 | return ret; | |
277 | ||
278 | ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_MCLK2, | |
0a590b1d MB |
279 | 32768, SND_SOC_CLOCK_IN); |
280 | if (ret < 0) | |
281 | return ret; | |
282 | ||
39ec5109 LPC |
283 | ret = snd_soc_card_jack_new(card, "Headset", |
284 | SND_JACK_HEADSET | SND_JACK_MECHANICAL | | |
285 | SND_JACK_BTN_0 | SND_JACK_BTN_1 | | |
286 | SND_JACK_BTN_2 | SND_JACK_BTN_3 | | |
287 | SND_JACK_BTN_4 | SND_JACK_BTN_5, | |
288 | &littlemill_headset, NULL, 0); | |
f2e2026c MB |
289 | if (ret) |
290 | return ret; | |
291 | ||
292 | /* This will check device compatibility itself */ | |
98869f68 | 293 | wm8958_mic_detect(codec, &littlemill_headset, NULL, NULL, NULL, NULL); |
f2e2026c | 294 | |
654a28c9 MB |
295 | /* As will this */ |
296 | wm8994_mic_detect(codec, &littlemill_headset, 1); | |
297 | ||
0a590b1d MB |
298 | return 0; |
299 | } | |
300 | ||
301 | static struct snd_soc_card littlemill = { | |
302 | .name = "Littlemill", | |
095d79dc | 303 | .owner = THIS_MODULE, |
0a590b1d MB |
304 | .dai_link = littlemill_dai, |
305 | .num_links = ARRAY_SIZE(littlemill_dai), | |
306 | ||
307 | .set_bias_level = littlemill_set_bias_level, | |
308 | .set_bias_level_post = littlemill_set_bias_level_post, | |
309 | ||
31a2239a MB |
310 | .controls = controls, |
311 | .num_controls = ARRAY_SIZE(controls), | |
0a590b1d MB |
312 | .dapm_widgets = widgets, |
313 | .num_dapm_widgets = ARRAY_SIZE(widgets), | |
314 | .dapm_routes = audio_paths, | |
315 | .num_dapm_routes = ARRAY_SIZE(audio_paths), | |
316 | ||
317 | .late_probe = littlemill_late_probe, | |
318 | }; | |
319 | ||
fdca21ad | 320 | static int littlemill_probe(struct platform_device *pdev) |
0a590b1d MB |
321 | { |
322 | struct snd_soc_card *card = &littlemill; | |
323 | int ret; | |
324 | ||
325 | card->dev = &pdev->dev; | |
326 | ||
c583883e TB |
327 | ret = devm_snd_soc_register_card(&pdev->dev, card); |
328 | if (ret) | |
0a590b1d MB |
329 | dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", |
330 | ret); | |
0a590b1d | 331 | |
c583883e | 332 | return ret; |
0a590b1d MB |
333 | } |
334 | ||
335 | static struct platform_driver littlemill_driver = { | |
336 | .driver = { | |
337 | .name = "littlemill", | |
0a590b1d MB |
338 | .pm = &snd_soc_pm_ops, |
339 | }, | |
340 | .probe = littlemill_probe, | |
0a590b1d MB |
341 | }; |
342 | ||
343 | module_platform_driver(littlemill_driver); | |
344 | ||
345 | MODULE_DESCRIPTION("Littlemill audio support"); | |
346 | MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); | |
347 | MODULE_LICENSE("GPL"); | |
348 | MODULE_ALIAS("platform:littlemill"); |