Commit | Line | Data |
---|---|---|
1b49cb98 LG |
1 | /* |
2 | * tosa.c -- SoC audio for Tosa | |
3 | * | |
4 | * Copyright 2005 Wolfson Microelectronics PLC. | |
5 | * Copyright 2005 Openedhand Ltd. | |
6 | * | |
d331124d | 7 | * Authors: Liam Girdwood <lrg@slimlogic.co.uk> |
1b49cb98 LG |
8 | * Richard Purdie <richard@openedhand.com> |
9 | * | |
10 | * This program is free software; you can redistribute it and/or modify it | |
11 | * under the terms of the GNU General Public License as published by the | |
12 | * Free Software Foundation; either version 2 of the License, or (at your | |
13 | * option) any later version. | |
14 | * | |
1b49cb98 LG |
15 | * GPIO's |
16 | * 1 - Jack Insertion | |
17 | * 5 - Hookswitch (headset answer/hang up switch) | |
18 | * | |
19 | */ | |
20 | ||
21 | #include <linux/module.h> | |
22 | #include <linux/moduleparam.h> | |
23 | #include <linux/device.h> | |
4440cbd6 | 24 | #include <linux/gpio.h> |
1b49cb98 | 25 | |
1b49cb98 LG |
26 | #include <sound/core.h> |
27 | #include <sound/pcm.h> | |
28 | #include <sound/soc.h> | |
1b49cb98 LG |
29 | |
30 | #include <asm/mach-types.h> | |
a09e64fb | 31 | #include <mach/tosa.h> |
a09e64fb | 32 | #include <mach/audio.h> |
1b49cb98 LG |
33 | |
34 | #include "../codecs/wm9712.h" | |
cb4c048b | 35 | #include "pxa2xx-ac97.h" |
1b49cb98 | 36 | |
87506549 | 37 | static struct snd_soc_card tosa; |
1b49cb98 LG |
38 | |
39 | #define TOSA_HP 0 | |
40 | #define TOSA_MIC_INT 1 | |
41 | #define TOSA_HEADSET 2 | |
42 | #define TOSA_HP_OFF 3 | |
43 | #define TOSA_SPK_ON 0 | |
44 | #define TOSA_SPK_OFF 1 | |
45 | ||
46 | static int tosa_jack_func; | |
47 | static int tosa_spk_func; | |
48 | ||
49 | static void tosa_ext_control(struct snd_soc_codec *codec) | |
50 | { | |
ce6120cc LG |
51 | struct snd_soc_dapm_context *dapm = &codec->dapm; |
52 | ||
1b49cb98 LG |
53 | /* set up jack connection */ |
54 | switch (tosa_jack_func) { | |
55 | case TOSA_HP: | |
ce6120cc LG |
56 | snd_soc_dapm_disable_pin(dapm, "Mic (Internal)"); |
57 | snd_soc_dapm_enable_pin(dapm, "Headphone Jack"); | |
58 | snd_soc_dapm_disable_pin(dapm, "Headset Jack"); | |
1b49cb98 LG |
59 | break; |
60 | case TOSA_MIC_INT: | |
ce6120cc LG |
61 | snd_soc_dapm_enable_pin(dapm, "Mic (Internal)"); |
62 | snd_soc_dapm_disable_pin(dapm, "Headphone Jack"); | |
63 | snd_soc_dapm_disable_pin(dapm, "Headset Jack"); | |
1b49cb98 LG |
64 | break; |
65 | case TOSA_HEADSET: | |
ce6120cc LG |
66 | snd_soc_dapm_disable_pin(dapm, "Mic (Internal)"); |
67 | snd_soc_dapm_disable_pin(dapm, "Headphone Jack"); | |
68 | snd_soc_dapm_enable_pin(dapm, "Headset Jack"); | |
1b49cb98 LG |
69 | break; |
70 | } | |
71 | ||
72 | if (tosa_spk_func == TOSA_SPK_ON) | |
ce6120cc | 73 | snd_soc_dapm_enable_pin(dapm, "Speaker"); |
a5302181 | 74 | else |
ce6120cc | 75 | snd_soc_dapm_disable_pin(dapm, "Speaker"); |
1b49cb98 | 76 | |
ce6120cc | 77 | snd_soc_dapm_sync(dapm); |
1b49cb98 LG |
78 | } |
79 | ||
80 | static int tosa_startup(struct snd_pcm_substream *substream) | |
81 | { | |
82 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
cb990622 | 83 | struct snd_soc_codec *codec = rtd->codec; |
1b49cb98 | 84 | |
71a29560 MB |
85 | mutex_lock(&codec->mutex); |
86 | ||
1b49cb98 LG |
87 | /* check the jack status at stream startup */ |
88 | tosa_ext_control(codec); | |
71a29560 MB |
89 | |
90 | mutex_unlock(&codec->mutex); | |
91 | ||
1b49cb98 LG |
92 | return 0; |
93 | } | |
94 | ||
95 | static struct snd_soc_ops tosa_ops = { | |
96 | .startup = tosa_startup, | |
97 | }; | |
98 | ||
99 | static int tosa_get_jack(struct snd_kcontrol *kcontrol, | |
100 | struct snd_ctl_elem_value *ucontrol) | |
101 | { | |
102 | ucontrol->value.integer.value[0] = tosa_jack_func; | |
103 | return 0; | |
104 | } | |
105 | ||
106 | static int tosa_set_jack(struct snd_kcontrol *kcontrol, | |
107 | struct snd_ctl_elem_value *ucontrol) | |
108 | { | |
109 | struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); | |
110 | ||
111 | if (tosa_jack_func == ucontrol->value.integer.value[0]) | |
112 | return 0; | |
113 | ||
114 | tosa_jack_func = ucontrol->value.integer.value[0]; | |
115 | tosa_ext_control(codec); | |
116 | return 1; | |
117 | } | |
118 | ||
119 | static int tosa_get_spk(struct snd_kcontrol *kcontrol, | |
120 | struct snd_ctl_elem_value *ucontrol) | |
121 | { | |
122 | ucontrol->value.integer.value[0] = tosa_spk_func; | |
123 | return 0; | |
124 | } | |
125 | ||
126 | static int tosa_set_spk(struct snd_kcontrol *kcontrol, | |
127 | struct snd_ctl_elem_value *ucontrol) | |
128 | { | |
129 | struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); | |
130 | ||
131 | if (tosa_spk_func == ucontrol->value.integer.value[0]) | |
132 | return 0; | |
133 | ||
134 | tosa_spk_func = ucontrol->value.integer.value[0]; | |
135 | tosa_ext_control(codec); | |
136 | return 1; | |
137 | } | |
138 | ||
139 | /* tosa dapm event handlers */ | |
338c7ed0 JN |
140 | static int tosa_hp_event(struct snd_soc_dapm_widget *w, |
141 | struct snd_kcontrol *k, int event) | |
1b49cb98 | 142 | { |
4440cbd6 | 143 | gpio_set_value(TOSA_GPIO_L_MUTE, SND_SOC_DAPM_EVENT_ON(event) ? 1 :0); |
1b49cb98 LG |
144 | return 0; |
145 | } | |
146 | ||
147 | /* tosa machine dapm widgets */ | |
148 | static const struct snd_soc_dapm_widget tosa_dapm_widgets[] = { | |
149 | SND_SOC_DAPM_HP("Headphone Jack", tosa_hp_event), | |
150 | SND_SOC_DAPM_HP("Headset Jack", NULL), | |
151 | SND_SOC_DAPM_MIC("Mic (Internal)", NULL), | |
152 | SND_SOC_DAPM_SPK("Speaker", NULL), | |
153 | }; | |
154 | ||
155 | /* tosa audio map */ | |
76d39d0a | 156 | static const struct snd_soc_dapm_route audio_map[] = { |
1b49cb98 LG |
157 | |
158 | /* headphone connected to HPOUTL, HPOUTR */ | |
159 | {"Headphone Jack", NULL, "HPOUTL"}, | |
160 | {"Headphone Jack", NULL, "HPOUTR"}, | |
161 | ||
162 | /* ext speaker connected to LOUT2, ROUT2 */ | |
163 | {"Speaker", NULL, "LOUT2"}, | |
164 | {"Speaker", NULL, "ROUT2"}, | |
165 | ||
166 | /* internal mic is connected to mic1, mic2 differential - with bias */ | |
167 | {"MIC1", NULL, "Mic Bias"}, | |
168 | {"MIC2", NULL, "Mic Bias"}, | |
169 | {"Mic Bias", NULL, "Mic (Internal)"}, | |
170 | ||
171 | /* headset is connected to HPOUTR, and LINEINR with bias */ | |
172 | {"Headset Jack", NULL, "HPOUTR"}, | |
173 | {"LINEINR", NULL, "Mic Bias"}, | |
174 | {"Mic Bias", NULL, "Headset Jack"}, | |
1b49cb98 LG |
175 | }; |
176 | ||
177 | static const char *jack_function[] = {"Headphone", "Mic", "Line", "Headset", | |
178 | "Off"}; | |
179 | static const char *spk_function[] = {"On", "Off"}; | |
180 | static const struct soc_enum tosa_enum[] = { | |
181 | SOC_ENUM_SINGLE_EXT(5, jack_function), | |
182 | SOC_ENUM_SINGLE_EXT(2, spk_function), | |
183 | }; | |
184 | ||
185 | static const struct snd_kcontrol_new tosa_controls[] = { | |
186 | SOC_ENUM_EXT("Jack Function", tosa_enum[0], tosa_get_jack, | |
187 | tosa_set_jack), | |
188 | SOC_ENUM_EXT("Speaker Function", tosa_enum[1], tosa_get_spk, | |
189 | tosa_set_spk), | |
190 | }; | |
191 | ||
f0fba2ad | 192 | static int tosa_ac97_init(struct snd_soc_pcm_runtime *rtd) |
1b49cb98 | 193 | { |
f0fba2ad | 194 | struct snd_soc_codec *codec = rtd->codec; |
ce6120cc | 195 | struct snd_soc_dapm_context *dapm = &codec->dapm; |
eb5f6d75 | 196 | int err; |
1b49cb98 | 197 | |
ce6120cc LG |
198 | snd_soc_dapm_nc_pin(dapm, "OUT3"); |
199 | snd_soc_dapm_nc_pin(dapm, "MONOOUT"); | |
1b49cb98 LG |
200 | |
201 | /* add tosa specific controls */ | |
eb5f6d75 PZ |
202 | err = snd_soc_add_controls(codec, tosa_controls, |
203 | ARRAY_SIZE(tosa_controls)); | |
204 | if (err < 0) | |
205 | return err; | |
1b49cb98 LG |
206 | |
207 | /* add tosa specific widgets */ | |
ce6120cc | 208 | snd_soc_dapm_new_controls(dapm, tosa_dapm_widgets, |
25191c45 | 209 | ARRAY_SIZE(tosa_dapm_widgets)); |
1b49cb98 LG |
210 | |
211 | /* set up tosa specific audio path audio_map */ | |
ce6120cc | 212 | snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map)); |
1b49cb98 | 213 | |
ce6120cc | 214 | snd_soc_dapm_sync(dapm); |
1b49cb98 LG |
215 | return 0; |
216 | } | |
217 | ||
218 | static struct snd_soc_dai_link tosa_dai[] = { | |
219 | { | |
220 | .name = "AC97", | |
221 | .stream_name = "AC97 HiFi", | |
4bfc4e25 | 222 | .cpu_dai_name = "pxa2xx-ac97", |
f0fba2ad LG |
223 | .codec_dai_name = "wm9712-hifi", |
224 | .platform_name = "pxa-pcm-audio", | |
225 | .codec_name = "wm9712-codec", | |
1b49cb98 | 226 | .init = tosa_ac97_init, |
cb4c048b | 227 | .ops = &tosa_ops, |
1b49cb98 LG |
228 | }, |
229 | { | |
230 | .name = "AC97 Aux", | |
231 | .stream_name = "AC97 Aux", | |
4bfc4e25 | 232 | .cpu_dai_name = "pxa2xx-ac97-aux", |
f0fba2ad LG |
233 | .codec_dai_name = "wm9712-aux", |
234 | .platform_name = "pxa-pcm-audio", | |
235 | .codec_name = "wm9712-codec", | |
cb4c048b | 236 | .ops = &tosa_ops, |
1b49cb98 LG |
237 | }, |
238 | }; | |
239 | ||
e7361ec4 | 240 | static int tosa_probe(struct snd_soc_card *card) |
5c0d7bb7 DB |
241 | { |
242 | int ret; | |
243 | ||
244 | ret = gpio_request(TOSA_GPIO_L_MUTE, "Headphone Jack"); | |
245 | if (ret) | |
246 | return ret; | |
247 | ret = gpio_direction_output(TOSA_GPIO_L_MUTE, 0); | |
248 | if (ret) | |
249 | gpio_free(TOSA_GPIO_L_MUTE); | |
250 | ||
251 | return ret; | |
252 | } | |
253 | ||
e7361ec4 | 254 | static int tosa_remove(struct snd_soc_card *card) |
5c0d7bb7 DB |
255 | { |
256 | gpio_free(TOSA_GPIO_L_MUTE); | |
257 | return 0; | |
258 | } | |
259 | ||
87506549 | 260 | static struct snd_soc_card tosa = { |
1b49cb98 LG |
261 | .name = "Tosa", |
262 | .dai_link = tosa_dai, | |
263 | .num_links = ARRAY_SIZE(tosa_dai), | |
5c0d7bb7 DB |
264 | .probe = tosa_probe, |
265 | .remove = tosa_remove, | |
1b49cb98 LG |
266 | }; |
267 | ||
1b49cb98 LG |
268 | static struct platform_device *tosa_snd_device; |
269 | ||
270 | static int __init tosa_init(void) | |
271 | { | |
272 | int ret; | |
273 | ||
274 | if (!machine_is_tosa()) | |
275 | return -ENODEV; | |
276 | ||
277 | tosa_snd_device = platform_device_alloc("soc-audio", -1); | |
4440cbd6 DB |
278 | if (!tosa_snd_device) { |
279 | ret = -ENOMEM; | |
280 | goto err_alloc; | |
281 | } | |
1b49cb98 | 282 | |
f0fba2ad | 283 | platform_set_drvdata(tosa_snd_device, &tosa); |
1b49cb98 LG |
284 | ret = platform_device_add(tosa_snd_device); |
285 | ||
4440cbd6 DB |
286 | if (!ret) |
287 | return 0; | |
288 | ||
289 | platform_device_put(tosa_snd_device); | |
290 | ||
291 | err_alloc: | |
1b49cb98 LG |
292 | return ret; |
293 | } | |
294 | ||
295 | static void __exit tosa_exit(void) | |
296 | { | |
297 | platform_device_unregister(tosa_snd_device); | |
298 | } | |
299 | ||
300 | module_init(tosa_init); | |
301 | module_exit(tosa_exit); | |
302 | ||
303 | /* Module information */ | |
304 | MODULE_AUTHOR("Richard Purdie"); | |
305 | MODULE_DESCRIPTION("ALSA SoC Tosa"); | |
306 | MODULE_LICENSE("GPL"); |