diff options
author | Liam Girdwood <liam.girdwood@wolfsonmicro.com> | 2006-10-12 08:31:16 -0400 |
---|---|---|
committer | Jaroslav Kysela <perex@suse.cz> | 2007-02-09 03:00:46 -0500 |
commit | a1eb4b3caf3abd0d1a8474f07d29959e1879bb29 (patch) | |
tree | a2061adb453d926eebfe9599b67e863f0abcbcc2 /sound/soc/pxa | |
parent | 75b41027662e29822746342865fa8abd941d2604 (diff) |
[ALSA] ASoC pxa2xx Corgi machine support
This patch adds Alsa audio support to the Sharp Zaurus SL-C7x0/C860
(Corgi) machines.
From: Liam Girdwood <liam.girdwood@wolfsonmicro.com>
Signed-off-by: Graeme Gregory <dp@xora.org.uk>
Signed-off-by: Richard Purdie <rpurdie@rpsys.net>
Signed-off-by: Liam Girdwood <liam.girdwood@wolfsonmicro.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@suse.cz>
Diffstat (limited to 'sound/soc/pxa')
-rw-r--r-- | sound/soc/pxa/corgi.c | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/sound/soc/pxa/corgi.c b/sound/soc/pxa/corgi.c new file mode 100644 index 000000000000..2b1c6e94d1e4 --- /dev/null +++ b/sound/soc/pxa/corgi.c | |||
@@ -0,0 +1,361 @@ | |||
1 | /* | ||
2 | * corgi.c -- SoC audio for Corgi | ||
3 | * | ||
4 | * Copyright 2005 Wolfson Microelectronics PLC. | ||
5 | * Copyright 2005 Openedhand Ltd. | ||
6 | * | ||
7 | * Authors: Liam Girdwood <liam.girdwood@wolfsonmicro.com> | ||
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 | * | ||
15 | * Revision history | ||
16 | * 30th Nov 2005 Initial version. | ||
17 | * | ||
18 | */ | ||
19 | |||
20 | #include <linux/module.h> | ||
21 | #include <linux/moduleparam.h> | ||
22 | #include <linux/timer.h> | ||
23 | #include <linux/interrupt.h> | ||
24 | #include <linux/platform_device.h> | ||
25 | #include <sound/driver.h> | ||
26 | #include <sound/core.h> | ||
27 | #include <sound/pcm.h> | ||
28 | #include <sound/soc.h> | ||
29 | #include <sound/soc-dapm.h> | ||
30 | |||
31 | #include <asm/mach-types.h> | ||
32 | #include <asm/hardware/scoop.h> | ||
33 | #include <asm/arch/pxa-regs.h> | ||
34 | #include <asm/arch/hardware.h> | ||
35 | #include <asm/arch/corgi.h> | ||
36 | #include <asm/arch/audio.h> | ||
37 | |||
38 | #include "../codecs/wm8731.h" | ||
39 | #include "pxa2xx-pcm.h" | ||
40 | |||
41 | #define CORGI_HP 0 | ||
42 | #define CORGI_MIC 1 | ||
43 | #define CORGI_LINE 2 | ||
44 | #define CORGI_HEADSET 3 | ||
45 | #define CORGI_HP_OFF 4 | ||
46 | #define CORGI_SPK_ON 0 | ||
47 | #define CORGI_SPK_OFF 1 | ||
48 | |||
49 | /* audio clock in Hz - rounded from 12.235MHz */ | ||
50 | #define CORGI_AUDIO_CLOCK 12288000 | ||
51 | |||
52 | static int corgi_jack_func; | ||
53 | static int corgi_spk_func; | ||
54 | |||
55 | static void corgi_ext_control(struct snd_soc_codec *codec) | ||
56 | { | ||
57 | int spk = 0, mic = 0, line = 0, hp = 0, hs = 0; | ||
58 | |||
59 | /* set up jack connection */ | ||
60 | switch (corgi_jack_func) { | ||
61 | case CORGI_HP: | ||
62 | hp = 1; | ||
63 | /* set = unmute headphone */ | ||
64 | set_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MUTE_L); | ||
65 | set_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MUTE_R); | ||
66 | break; | ||
67 | case CORGI_MIC: | ||
68 | mic = 1; | ||
69 | /* reset = mute headphone */ | ||
70 | reset_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MUTE_L); | ||
71 | reset_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MUTE_R); | ||
72 | break; | ||
73 | case CORGI_LINE: | ||
74 | line = 1; | ||
75 | reset_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MUTE_L); | ||
76 | reset_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MUTE_R); | ||
77 | break; | ||
78 | case CORGI_HEADSET: | ||
79 | hs = 1; | ||
80 | mic = 1; | ||
81 | reset_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MUTE_L); | ||
82 | set_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MUTE_R); | ||
83 | break; | ||
84 | } | ||
85 | |||
86 | if (corgi_spk_func == CORGI_SPK_ON) | ||
87 | spk = 1; | ||
88 | |||
89 | /* set the enpoints to their new connetion states */ | ||
90 | snd_soc_dapm_set_endpoint(codec, "Ext Spk", spk); | ||
91 | snd_soc_dapm_set_endpoint(codec, "Mic Jack", mic); | ||
92 | snd_soc_dapm_set_endpoint(codec, "Line Jack", line); | ||
93 | snd_soc_dapm_set_endpoint(codec, "Headphone Jack", hp); | ||
94 | snd_soc_dapm_set_endpoint(codec, "Headset Jack", hs); | ||
95 | |||
96 | /* signal a DAPM event */ | ||
97 | snd_soc_dapm_sync_endpoints(codec); | ||
98 | } | ||
99 | |||
100 | static int corgi_startup(struct snd_pcm_substream *substream) | ||
101 | { | ||
102 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
103 | struct snd_soc_codec *codec = rtd->socdev->codec; | ||
104 | |||
105 | /* check the jack status at stream startup */ | ||
106 | corgi_ext_control(codec); | ||
107 | return 0; | ||
108 | } | ||
109 | |||
110 | /* we need to unmute the HP at shutdown as the mute burns power on corgi */ | ||
111 | static int corgi_shutdown(struct snd_pcm_substream *substream) | ||
112 | { | ||
113 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
114 | struct snd_soc_codec *codec = rtd->socdev->codec; | ||
115 | |||
116 | /* set = unmute headphone */ | ||
117 | set_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MUTE_L); | ||
118 | set_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MUTE_R); | ||
119 | return 0; | ||
120 | } | ||
121 | |||
122 | static struct snd_soc_ops corgi_ops = { | ||
123 | .startup = corgi_startup, | ||
124 | .shutdown = corgi_shutdown, | ||
125 | }; | ||
126 | |||
127 | static int corgi_get_jack(struct snd_kcontrol *kcontrol, | ||
128 | struct snd_ctl_elem_value *ucontrol) | ||
129 | { | ||
130 | ucontrol->value.integer.value[0] = corgi_jack_func; | ||
131 | return 0; | ||
132 | } | ||
133 | |||
134 | static int corgi_set_jack(struct snd_kcontrol *kcontrol, | ||
135 | struct snd_ctl_elem_value *ucontrol) | ||
136 | { | ||
137 | struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); | ||
138 | |||
139 | if (corgi_jack_func == ucontrol->value.integer.value[0]) | ||
140 | return 0; | ||
141 | |||
142 | corgi_jack_func = ucontrol->value.integer.value[0]; | ||
143 | corgi_ext_control(codec); | ||
144 | return 1; | ||
145 | } | ||
146 | |||
147 | static int corgi_get_spk(struct snd_kcontrol *kcontrol, | ||
148 | struct snd_ctl_elem_value *ucontrol) | ||
149 | { | ||
150 | ucontrol->value.integer.value[0] = corgi_spk_func; | ||
151 | return 0; | ||
152 | } | ||
153 | |||
154 | static int corgi_set_spk(struct snd_kcontrol *kcontrol, | ||
155 | struct snd_ctl_elem_value *ucontrol) | ||
156 | { | ||
157 | struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); | ||
158 | |||
159 | if (corgi_spk_func == ucontrol->value.integer.value[0]) | ||
160 | return 0; | ||
161 | |||
162 | corgi_spk_func = ucontrol->value.integer.value[0]; | ||
163 | corgi_ext_control(codec); | ||
164 | return 1; | ||
165 | } | ||
166 | |||
167 | static int corgi_amp_event(struct snd_soc_dapm_widget *w, int event) | ||
168 | { | ||
169 | if (SND_SOC_DAPM_EVENT_ON(event)) | ||
170 | set_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_APM_ON); | ||
171 | else | ||
172 | reset_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_APM_ON); | ||
173 | |||
174 | return 0; | ||
175 | } | ||
176 | |||
177 | static int corgi_mic_event(struct snd_soc_dapm_widget *w, int event) | ||
178 | { | ||
179 | if (SND_SOC_DAPM_EVENT_ON(event)) | ||
180 | set_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MIC_BIAS); | ||
181 | else | ||
182 | reset_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MIC_BIAS); | ||
183 | |||
184 | return 0; | ||
185 | } | ||
186 | |||
187 | /* corgi machine dapm widgets */ | ||
188 | static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = { | ||
189 | SND_SOC_DAPM_HP("Headphone Jack", NULL), | ||
190 | SND_SOC_DAPM_MIC("Mic Jack", corgi_mic_event), | ||
191 | SND_SOC_DAPM_SPK("Ext Spk", corgi_amp_event), | ||
192 | SND_SOC_DAPM_LINE("Line Jack", NULL), | ||
193 | SND_SOC_DAPM_HP("Headset Jack", NULL), | ||
194 | }; | ||
195 | |||
196 | /* Corgi machine audio map (connections to the codec pins) */ | ||
197 | static const char *audio_map[][3] = { | ||
198 | |||
199 | /* headset Jack - in = micin, out = LHPOUT*/ | ||
200 | {"Headset Jack", NULL, "LHPOUT"}, | ||
201 | |||
202 | /* headphone connected to LHPOUT1, RHPOUT1 */ | ||
203 | {"Headphone Jack", NULL, "LHPOUT"}, | ||
204 | {"Headphone Jack", NULL, "RHPOUT"}, | ||
205 | |||
206 | /* speaker connected to LOUT, ROUT */ | ||
207 | {"Ext Spk", NULL, "ROUT"}, | ||
208 | {"Ext Spk", NULL, "LOUT"}, | ||
209 | |||
210 | /* mic is connected to MICIN (via right channel of headphone jack) */ | ||
211 | {"MICIN", NULL, "Mic Jack"}, | ||
212 | |||
213 | /* Same as the above but no mic bias for line signals */ | ||
214 | {"MICIN", NULL, "Line Jack"}, | ||
215 | |||
216 | {NULL, NULL, NULL}, | ||
217 | }; | ||
218 | |||
219 | static const char *jack_function[] = {"Headphone", "Mic", "Line", "Headset", | ||
220 | "Off"}; | ||
221 | static const char *spk_function[] = {"On", "Off"}; | ||
222 | static const struct soc_enum corgi_enum[] = { | ||
223 | SOC_ENUM_SINGLE_EXT(5, jack_function), | ||
224 | SOC_ENUM_SINGLE_EXT(2, spk_function), | ||
225 | }; | ||
226 | |||
227 | static const struct snd_kcontrol_new wm8731_corgi_controls[] = { | ||
228 | SOC_ENUM_EXT("Jack Function", corgi_enum[0], corgi_get_jack, | ||
229 | corgi_set_jack), | ||
230 | SOC_ENUM_EXT("Speaker Function", corgi_enum[1], corgi_get_spk, | ||
231 | corgi_set_spk), | ||
232 | }; | ||
233 | |||
234 | /* | ||
235 | * Logic for a wm8731 as connected on a Sharp SL-C7x0 Device | ||
236 | */ | ||
237 | static int corgi_wm8731_init(struct snd_soc_codec *codec) | ||
238 | { | ||
239 | int i, err; | ||
240 | |||
241 | snd_soc_dapm_set_endpoint(codec, "LLINEIN", 0); | ||
242 | snd_soc_dapm_set_endpoint(codec, "RLINEIN", 0); | ||
243 | |||
244 | /* Add corgi specific controls */ | ||
245 | for (i = 0; i < ARRAY_SIZE(wm8731_corgi_controls); i++) { | ||
246 | err = snd_ctl_add(codec->card, | ||
247 | snd_soc_cnew(&wm8731_corgi_controls[i],codec, NULL)); | ||
248 | if (err < 0) | ||
249 | return err; | ||
250 | } | ||
251 | |||
252 | /* Add corgi specific widgets */ | ||
253 | for(i = 0; i < ARRAY_SIZE(wm8731_dapm_widgets); i++) { | ||
254 | snd_soc_dapm_new_control(codec, &wm8731_dapm_widgets[i]); | ||
255 | } | ||
256 | |||
257 | /* Set up corgi specific audio path audio_map */ | ||
258 | for(i = 0; audio_map[i][0] != NULL; i++) { | ||
259 | snd_soc_dapm_connect_input(codec, audio_map[i][0], | ||
260 | audio_map[i][1], audio_map[i][2]); | ||
261 | } | ||
262 | |||
263 | snd_soc_dapm_sync_endpoints(codec); | ||
264 | return 0; | ||
265 | } | ||
266 | |||
267 | static unsigned int corgi_config_sysclk(struct snd_soc_pcm_runtime *rtd, | ||
268 | struct snd_soc_clock_info *info) | ||
269 | { | ||
270 | if (info->bclk_master & SND_SOC_DAIFMT_CBS_CFS) { | ||
271 | /* pxa2xx is i2s master */ | ||
272 | switch (info->rate) { | ||
273 | case 44100: | ||
274 | case 88200: | ||
275 | /* configure codec digital filters for 44.1, 88.2 */ | ||
276 | rtd->codec_dai->config_sysclk(rtd->codec_dai, info, | ||
277 | 11289600); | ||
278 | break; | ||
279 | default: | ||
280 | /* configure codec digital filters for all other rates */ | ||
281 | rtd->codec_dai->config_sysclk(rtd->codec_dai, info, | ||
282 | CORGI_AUDIO_CLOCK); | ||
283 | break; | ||
284 | } | ||
285 | /* config pxa i2s as master */ | ||
286 | return rtd->cpu_dai->config_sysclk(rtd->cpu_dai, info, | ||
287 | CORGI_AUDIO_CLOCK); | ||
288 | } else { | ||
289 | /* codec is i2s master - | ||
290 | * only configure codec DAI clock and filters */ | ||
291 | return rtd->codec_dai->config_sysclk(rtd->codec_dai, info, | ||
292 | CORGI_AUDIO_CLOCK); | ||
293 | } | ||
294 | } | ||
295 | |||
296 | /* corgi digital audio interface glue - connects codec <--> CPU */ | ||
297 | static struct snd_soc_dai_link corgi_dai = { | ||
298 | .name = "WM8731", | ||
299 | .stream_name = "WM8731", | ||
300 | .cpu_dai = &pxa_i2s_dai, | ||
301 | .codec_dai = &wm8731_dai, | ||
302 | .init = corgi_wm8731_init, | ||
303 | .config_sysclk = corgi_config_sysclk, | ||
304 | }; | ||
305 | |||
306 | /* corgi audio machine driver */ | ||
307 | static struct snd_soc_machine snd_soc_machine_corgi = { | ||
308 | .name = "Corgi", | ||
309 | .dai_link = &corgi_dai, | ||
310 | .num_links = 1, | ||
311 | .ops = &corgi_ops, | ||
312 | }; | ||
313 | |||
314 | /* corgi audio private data */ | ||
315 | static struct wm8731_setup_data corgi_wm8731_setup = { | ||
316 | .i2c_address = 0x1b, | ||
317 | }; | ||
318 | |||
319 | /* corgi audio subsystem */ | ||
320 | static struct snd_soc_device corgi_snd_devdata = { | ||
321 | .machine = &snd_soc_machine_corgi, | ||
322 | .platform = &pxa2xx_soc_platform, | ||
323 | .codec_dev = &soc_codec_dev_wm8731, | ||
324 | .codec_data = &corgi_wm8731_setup, | ||
325 | }; | ||
326 | |||
327 | static struct platform_device *corgi_snd_device; | ||
328 | |||
329 | static int __init corgi_init(void) | ||
330 | { | ||
331 | int ret; | ||
332 | |||
333 | if (!(machine_is_corgi() || machine_is_shepherd() || machine_is_husky())) | ||
334 | return -ENODEV; | ||
335 | |||
336 | corgi_snd_device = platform_device_alloc("soc-audio", -1); | ||
337 | if (!corgi_snd_device) | ||
338 | return -ENOMEM; | ||
339 | |||
340 | platform_set_drvdata(corgi_snd_device, &corgi_snd_devdata); | ||
341 | corgi_snd_devdata.dev = &corgi_snd_device->dev; | ||
342 | ret = platform_device_add(corgi_snd_device); | ||
343 | |||
344 | if (ret) | ||
345 | platform_device_put(corgi_snd_device); | ||
346 | |||
347 | return ret; | ||
348 | } | ||
349 | |||
350 | static void __exit corgi_exit(void) | ||
351 | { | ||
352 | platform_device_unregister(corgi_snd_device); | ||
353 | } | ||
354 | |||
355 | module_init(corgi_init); | ||
356 | module_exit(corgi_exit); | ||
357 | |||
358 | /* Module information */ | ||
359 | MODULE_AUTHOR("Richard Purdie"); | ||
360 | MODULE_DESCRIPTION("ALSA SoC Corgi"); | ||
361 | MODULE_LICENSE("GPL"); | ||