diff options
Diffstat (limited to 'sound/soc/samsung/speyside.c')
-rw-r--r-- | sound/soc/samsung/speyside.c | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/sound/soc/samsung/speyside.c b/sound/soc/samsung/speyside.c new file mode 100644 index 000000000000..360a333cb7c0 --- /dev/null +++ b/sound/soc/samsung/speyside.c | |||
@@ -0,0 +1,332 @@ | |||
1 | /* | ||
2 | * Speyside 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 | |||
17 | #include "../codecs/wm8915.h" | ||
18 | #include "../codecs/wm9081.h" | ||
19 | |||
20 | #define WM8915_HPSEL_GPIO 214 | ||
21 | |||
22 | static int speyside_set_bias_level(struct snd_soc_card *card, | ||
23 | enum snd_soc_bias_level level) | ||
24 | { | ||
25 | struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai; | ||
26 | int ret; | ||
27 | |||
28 | switch (level) { | ||
29 | case SND_SOC_BIAS_STANDBY: | ||
30 | ret = snd_soc_dai_set_sysclk(codec_dai, WM8915_SYSCLK_MCLK1, | ||
31 | 32768, SND_SOC_CLOCK_IN); | ||
32 | if (ret < 0) | ||
33 | return ret; | ||
34 | |||
35 | ret = snd_soc_dai_set_pll(codec_dai, WM8915_FLL_MCLK1, | ||
36 | 0, 0, 0); | ||
37 | if (ret < 0) { | ||
38 | pr_err("Failed to stop FLL\n"); | ||
39 | return ret; | ||
40 | } | ||
41 | |||
42 | default: | ||
43 | break; | ||
44 | } | ||
45 | |||
46 | return 0; | ||
47 | } | ||
48 | |||
49 | static int speyside_hw_params(struct snd_pcm_substream *substream, | ||
50 | struct snd_pcm_hw_params *params) | ||
51 | { | ||
52 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
53 | struct snd_soc_dai *cpu_dai = rtd->cpu_dai; | ||
54 | struct snd_soc_dai *codec_dai = rtd->codec_dai; | ||
55 | int ret; | ||
56 | |||
57 | ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | ||
58 | | SND_SOC_DAIFMT_NB_NF | ||
59 | | SND_SOC_DAIFMT_CBM_CFM); | ||
60 | if (ret < 0) | ||
61 | return ret; | ||
62 | |||
63 | ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | ||
64 | | SND_SOC_DAIFMT_NB_NF | ||
65 | | SND_SOC_DAIFMT_CBM_CFM); | ||
66 | if (ret < 0) | ||
67 | return ret; | ||
68 | |||
69 | ret = snd_soc_dai_set_pll(codec_dai, 0, WM8915_FLL_MCLK1, | ||
70 | 32768, 256 * 48000); | ||
71 | if (ret < 0) | ||
72 | return ret; | ||
73 | |||
74 | ret = snd_soc_dai_set_sysclk(codec_dai, WM8915_SYSCLK_FLL, | ||
75 | 256 * 48000, SND_SOC_CLOCK_IN); | ||
76 | if (ret < 0) | ||
77 | return ret; | ||
78 | |||
79 | return 0; | ||
80 | } | ||
81 | |||
82 | static struct snd_soc_ops speyside_ops = { | ||
83 | .hw_params = speyside_hw_params, | ||
84 | }; | ||
85 | |||
86 | static struct snd_soc_jack speyside_headset; | ||
87 | |||
88 | /* Headset jack detection DAPM pins */ | ||
89 | static struct snd_soc_jack_pin speyside_headset_pins[] = { | ||
90 | { | ||
91 | .pin = "Headset Mic", | ||
92 | .mask = SND_JACK_MICROPHONE, | ||
93 | }, | ||
94 | { | ||
95 | .pin = "Headphone", | ||
96 | .mask = SND_JACK_HEADPHONE, | ||
97 | }, | ||
98 | }; | ||
99 | |||
100 | /* Default the headphone selection to active high */ | ||
101 | static int speyside_jack_polarity; | ||
102 | |||
103 | static int speyside_get_micbias(struct snd_soc_dapm_widget *source, | ||
104 | struct snd_soc_dapm_widget *sink) | ||
105 | { | ||
106 | if (speyside_jack_polarity && (strcmp(source->name, "MICB1") == 0)) | ||
107 | return 1; | ||
108 | if (!speyside_jack_polarity && (strcmp(source->name, "MICB2") == 0)) | ||
109 | return 1; | ||
110 | |||
111 | return 0; | ||
112 | } | ||
113 | |||
114 | static void speyside_set_polarity(struct snd_soc_codec *codec, | ||
115 | int polarity) | ||
116 | { | ||
117 | speyside_jack_polarity = !polarity; | ||
118 | gpio_direction_output(WM8915_HPSEL_GPIO, speyside_jack_polarity); | ||
119 | |||
120 | /* Re-run DAPM to make sure we're using the correct mic bias */ | ||
121 | snd_soc_dapm_sync(&codec->dapm); | ||
122 | } | ||
123 | |||
124 | static int speyside_wm8915_init(struct snd_soc_pcm_runtime *rtd) | ||
125 | { | ||
126 | struct snd_soc_dai *dai = rtd->codec_dai; | ||
127 | struct snd_soc_codec *codec = rtd->codec; | ||
128 | int ret; | ||
129 | |||
130 | ret = snd_soc_dai_set_sysclk(dai, WM8915_SYSCLK_MCLK1, 32768, 0); | ||
131 | if (ret < 0) | ||
132 | return ret; | ||
133 | |||
134 | ret = gpio_request(WM8915_HPSEL_GPIO, "HP_SEL"); | ||
135 | if (ret != 0) | ||
136 | pr_err("Failed to request HP_SEL GPIO: %d\n", ret); | ||
137 | gpio_direction_output(WM8915_HPSEL_GPIO, speyside_jack_polarity); | ||
138 | |||
139 | ret = snd_soc_jack_new(codec, "Headset", | ||
140 | SND_JACK_HEADSET | SND_JACK_BTN_0, | ||
141 | &speyside_headset); | ||
142 | if (ret) | ||
143 | return ret; | ||
144 | |||
145 | ret = snd_soc_jack_add_pins(&speyside_headset, | ||
146 | ARRAY_SIZE(speyside_headset_pins), | ||
147 | speyside_headset_pins); | ||
148 | if (ret) | ||
149 | return ret; | ||
150 | |||
151 | wm8915_detect(codec, &speyside_headset, speyside_set_polarity); | ||
152 | |||
153 | return 0; | ||
154 | } | ||
155 | |||
156 | static int speyside_late_probe(struct snd_soc_card *card) | ||
157 | { | ||
158 | snd_soc_dapm_ignore_suspend(&card->dapm, "Headphone"); | ||
159 | snd_soc_dapm_ignore_suspend(&card->dapm, "Headset Mic"); | ||
160 | snd_soc_dapm_ignore_suspend(&card->dapm, "Main AMIC"); | ||
161 | snd_soc_dapm_ignore_suspend(&card->dapm, "Main DMIC"); | ||
162 | snd_soc_dapm_ignore_suspend(&card->dapm, "Speaker"); | ||
163 | snd_soc_dapm_ignore_suspend(&card->dapm, "WM1250 Output"); | ||
164 | snd_soc_dapm_ignore_suspend(&card->dapm, "WM1250 Input"); | ||
165 | |||
166 | return 0; | ||
167 | } | ||
168 | |||
169 | static struct snd_soc_dai_link speyside_dai[] = { | ||
170 | { | ||
171 | .name = "CPU", | ||
172 | .stream_name = "CPU", | ||
173 | .cpu_dai_name = "samsung-i2s.0", | ||
174 | .codec_dai_name = "wm8915-aif1", | ||
175 | .platform_name = "samsung-audio", | ||
176 | .codec_name = "wm8915.1-001a", | ||
177 | .init = speyside_wm8915_init, | ||
178 | .ops = &speyside_ops, | ||
179 | }, | ||
180 | { | ||
181 | .name = "Baseband", | ||
182 | .stream_name = "Baseband", | ||
183 | .cpu_dai_name = "wm8915-aif2", | ||
184 | .codec_dai_name = "wm1250-ev1", | ||
185 | .codec_name = "wm1250-ev1.1-0027", | ||
186 | .ops = &speyside_ops, | ||
187 | .ignore_suspend = 1, | ||
188 | }, | ||
189 | }; | ||
190 | |||
191 | static int speyside_wm9081_init(struct snd_soc_dapm_context *dapm) | ||
192 | { | ||
193 | snd_soc_dapm_nc_pin(dapm, "LINEOUT"); | ||
194 | |||
195 | /* At any time the WM9081 is active it will have this clock */ | ||
196 | return snd_soc_codec_set_sysclk(dapm->codec, WM9081_SYSCLK_MCLK, | ||
197 | 48000 * 256, 0); | ||
198 | } | ||
199 | |||
200 | static struct snd_soc_aux_dev speyside_aux_dev[] = { | ||
201 | { | ||
202 | .name = "wm9081", | ||
203 | .codec_name = "wm9081.1-006c", | ||
204 | .init = speyside_wm9081_init, | ||
205 | }, | ||
206 | }; | ||
207 | |||
208 | static struct snd_soc_codec_conf speyside_codec_conf[] = { | ||
209 | { | ||
210 | .dev_name = "wm9081.1-006c", | ||
211 | .name_prefix = "Sub", | ||
212 | }, | ||
213 | }; | ||
214 | |||
215 | static const struct snd_kcontrol_new controls[] = { | ||
216 | SOC_DAPM_PIN_SWITCH("Main Speaker"), | ||
217 | SOC_DAPM_PIN_SWITCH("Main DMIC"), | ||
218 | SOC_DAPM_PIN_SWITCH("Main AMIC"), | ||
219 | SOC_DAPM_PIN_SWITCH("WM1250 Input"), | ||
220 | SOC_DAPM_PIN_SWITCH("WM1250 Output"), | ||
221 | }; | ||
222 | |||
223 | static struct snd_soc_dapm_widget widgets[] = { | ||
224 | SND_SOC_DAPM_HP("Headphone", NULL), | ||
225 | SND_SOC_DAPM_MIC("Headset Mic", NULL), | ||
226 | |||
227 | SND_SOC_DAPM_SPK("Main Speaker", NULL), | ||
228 | |||
229 | SND_SOC_DAPM_MIC("Main AMIC", NULL), | ||
230 | SND_SOC_DAPM_MIC("Main DMIC", NULL), | ||
231 | }; | ||
232 | |||
233 | static struct snd_soc_dapm_route audio_paths[] = { | ||
234 | { "IN1RN", NULL, "MICB1" }, | ||
235 | { "IN1RP", NULL, "MICB1" }, | ||
236 | { "IN1RN", NULL, "MICB2" }, | ||
237 | { "IN1RP", NULL, "MICB2" }, | ||
238 | { "MICB1", NULL, "Headset Mic", speyside_get_micbias }, | ||
239 | { "MICB2", NULL, "Headset Mic", speyside_get_micbias }, | ||
240 | |||
241 | { "IN1LP", NULL, "MICB2" }, | ||
242 | { "IN1RN", NULL, "MICB1" }, | ||
243 | { "MICB2", NULL, "Main AMIC" }, | ||
244 | |||
245 | { "DMIC1DAT", NULL, "MICB1" }, | ||
246 | { "DMIC2DAT", NULL, "MICB1" }, | ||
247 | { "MICB1", NULL, "Main DMIC" }, | ||
248 | |||
249 | { "Headphone", NULL, "HPOUT1L" }, | ||
250 | { "Headphone", NULL, "HPOUT1R" }, | ||
251 | |||
252 | { "Sub IN1", NULL, "HPOUT2L" }, | ||
253 | { "Sub IN2", NULL, "HPOUT2R" }, | ||
254 | |||
255 | { "Main Speaker", NULL, "Sub SPKN" }, | ||
256 | { "Main Speaker", NULL, "Sub SPKP" }, | ||
257 | { "Main Speaker", NULL, "SPKDAT" }, | ||
258 | }; | ||
259 | |||
260 | static struct snd_soc_card speyside = { | ||
261 | .name = "Speyside", | ||
262 | .dai_link = speyside_dai, | ||
263 | .num_links = ARRAY_SIZE(speyside_dai), | ||
264 | .aux_dev = speyside_aux_dev, | ||
265 | .num_aux_devs = ARRAY_SIZE(speyside_aux_dev), | ||
266 | .codec_conf = speyside_codec_conf, | ||
267 | .num_configs = ARRAY_SIZE(speyside_codec_conf), | ||
268 | |||
269 | .set_bias_level = speyside_set_bias_level, | ||
270 | |||
271 | .controls = controls, | ||
272 | .num_controls = ARRAY_SIZE(controls), | ||
273 | .dapm_widgets = widgets, | ||
274 | .num_dapm_widgets = ARRAY_SIZE(widgets), | ||
275 | .dapm_routes = audio_paths, | ||
276 | .num_dapm_routes = ARRAY_SIZE(audio_paths), | ||
277 | |||
278 | .late_probe = speyside_late_probe, | ||
279 | }; | ||
280 | |||
281 | static __devinit int speyside_probe(struct platform_device *pdev) | ||
282 | { | ||
283 | struct snd_soc_card *card = &speyside; | ||
284 | int ret; | ||
285 | |||
286 | card->dev = &pdev->dev; | ||
287 | |||
288 | ret = snd_soc_register_card(card); | ||
289 | if (ret) { | ||
290 | dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", | ||
291 | ret); | ||
292 | return ret; | ||
293 | } | ||
294 | |||
295 | return 0; | ||
296 | } | ||
297 | |||
298 | static int __devexit speyside_remove(struct platform_device *pdev) | ||
299 | { | ||
300 | struct snd_soc_card *card = platform_get_drvdata(pdev); | ||
301 | |||
302 | snd_soc_unregister_card(card); | ||
303 | |||
304 | return 0; | ||
305 | } | ||
306 | |||
307 | static struct platform_driver speyside_driver = { | ||
308 | .driver = { | ||
309 | .name = "speyside", | ||
310 | .owner = THIS_MODULE, | ||
311 | .pm = &snd_soc_pm_ops, | ||
312 | }, | ||
313 | .probe = speyside_probe, | ||
314 | .remove = __devexit_p(speyside_remove), | ||
315 | }; | ||
316 | |||
317 | static int __init speyside_audio_init(void) | ||
318 | { | ||
319 | return platform_driver_register(&speyside_driver); | ||
320 | } | ||
321 | module_init(speyside_audio_init); | ||
322 | |||
323 | static void __exit speyside_audio_exit(void) | ||
324 | { | ||
325 | platform_driver_unregister(&speyside_driver); | ||
326 | } | ||
327 | module_exit(speyside_audio_exit); | ||
328 | |||
329 | MODULE_DESCRIPTION("Speyside audio support"); | ||
330 | MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); | ||
331 | MODULE_LICENSE("GPL"); | ||
332 | MODULE_ALIAS("platform:speyside"); | ||