Date: | 2010-08-01 21:34:54 (13 years 7 months ago) |
---|---|
Author: | Lars C. |
Commit: | 3765c7b1061af1e9a4e6f6a93a6b81e5627e0ae7 |
Message: | Add N516 sound SoC board driver |
Files: |
sound/soc/jz4740/Kconfig (1 diff) sound/soc/jz4740/Makefile (1 diff) sound/soc/jz4740/n516.c (1 diff) |
Change Details
sound/soc/jz4740/Kconfig | ||
---|---|---|
21 | 21 | help |
22 | 22 | Say Y if you want to add support for ASoC audio on the Qi LB60 board |
23 | 23 | a.k.a Qi Ben NanoNote. |
24 | ||
25 | config SND_JZ4740_SOC_N516 | |
26 | tristate "SoC Audio support for Hanvon N516 eBook reader" | |
27 | depends on SND_JZ4740_SOC && JZ4740_N516 | |
28 | select SND_JZ4740_SOC_I2S | |
29 | select SND_SOC_JZCODEC | |
30 | help | |
31 | Say Y if you want to enable support for SoC audio on the Hanvon N516. |
sound/soc/jz4740/Makefile | ||
---|---|---|
9 | 9 | |
10 | 10 | # Jz4740 Machine Support |
11 | 11 | snd-soc-qi-lb60-objs := qi_lb60.o |
12 | snd-soc-n516-objs := n516.o | |
12 | 13 | |
13 | 14 | obj-$(CONFIG_SND_JZ4740_SOC_QI_LB60) += snd-soc-qi-lb60.o |
15 | obj-$(CONFIG_SND_JZ4740_SOC_N516) += snd-soc-n516.o |
sound/soc/jz4740/n516.c | ||
---|---|---|
1 | /* | |
2 | * Copyright (C) 2009, Yauhen Kharuzhy <jekhor@gmail.com> | |
3 | * OpenInkpot project | |
4 | * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License version 2 as | |
8 | * published by the Free Software Foundation. | |
9 | * | |
10 | * You should have received a copy of the GNU General Public License along | |
11 | * with this program; if not, write to the Free Software Foundation, Inc., | |
12 | * 675 Mass Ave, Cambridge, MA 02139, USA. | |
13 | * | |
14 | */ | |
15 | ||
16 | #include <linux/module.h> | |
17 | #include <linux/interrupt.h> | |
18 | #include <linux/platform_device.h> | |
19 | #include <sound/core.h> | |
20 | #include <sound/pcm.h> | |
21 | #include <sound/soc.h> | |
22 | #include <sound/soc-dapm.h> | |
23 | #include <sound/jack.h> | |
24 | #include <linux/gpio.h> | |
25 | #include <linux/workqueue.h> | |
26 | ||
27 | #include "../codecs/jzcodec.h" | |
28 | #include "jz4740-pcm.h" | |
29 | #include "jz4740-i2s.h" | |
30 | ||
31 | #include <asm/mach-jz4740/board-n516.h> | |
32 | ||
33 | enum { | |
34 | N516_SPEAKER_AUTO = 0, | |
35 | N516_SPEAKER_OFF = 1, | |
36 | N516_SPEAKER_ON = 2, | |
37 | }; | |
38 | ||
39 | static int n516_speaker_mode; | |
40 | static struct snd_soc_codec *n516_codec; | |
41 | static struct work_struct n516_headphone_work; | |
42 | ||
43 | static void n516_ext_control(void) | |
44 | { | |
45 | if (!n516_codec) | |
46 | return; | |
47 | ||
48 | switch (n516_speaker_mode) { | |
49 | case N516_SPEAKER_ON: | |
50 | snd_soc_dapm_enable_pin(n516_codec, "Speaker"); | |
51 | break; | |
52 | case N516_SPEAKER_OFF: | |
53 | snd_soc_dapm_disable_pin(n516_codec, "Speaker"); | |
54 | break; | |
55 | case N516_SPEAKER_AUTO: | |
56 | if (snd_soc_dapm_get_pin_status(n516_codec, "Headphone")) | |
57 | snd_soc_dapm_disable_pin(n516_codec, "Speaker"); | |
58 | else | |
59 | snd_soc_dapm_enable_pin(n516_codec, "Speaker"); | |
60 | break; | |
61 | default: | |
62 | break; | |
63 | } | |
64 | ||
65 | /* signal a DAPM event */ | |
66 | snd_soc_dapm_sync(n516_codec); | |
67 | } | |
68 | ||
69 | static int n516_speaker_event(struct snd_soc_dapm_widget *widget, | |
70 | struct snd_kcontrol *ctrl, int event) | |
71 | { | |
72 | int on = !SND_SOC_DAPM_EVENT_OFF(event); | |
73 | ||
74 | gpio_set_value(GPIO_SPEAKER_ENABLE, on); | |
75 | ||
76 | return 0; | |
77 | } | |
78 | ||
79 | static void n516_headphone_event_work(struct work_struct *work) | |
80 | { | |
81 | n516_ext_control(); | |
82 | } | |
83 | ||
84 | static int n516_headphone_event(struct snd_soc_dapm_widget *widget, | |
85 | struct snd_kcontrol *ctrl, int event) | |
86 | { | |
87 | /* We can't call soc_dapm_sync from a event handler */ | |
88 | if (event & (SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD)) | |
89 | schedule_work(&n516_headphone_work); | |
90 | return 0; | |
91 | } | |
92 | ||
93 | static const struct snd_soc_dapm_widget n516_widgets[] = { | |
94 | SND_SOC_DAPM_SPK("Speaker", n516_speaker_event), | |
95 | SND_SOC_DAPM_HP("Headphone", n516_headphone_event), | |
96 | SND_SOC_DAPM_MIC("Mic", NULL), | |
97 | }; | |
98 | ||
99 | static const struct snd_soc_dapm_route n516_routes[] = { | |
100 | {"Mic", NULL, "MIC"}, | |
101 | {"Speaker", NULL, "LOUT"}, | |
102 | {"Speaker", NULL, "ROUT"}, | |
103 | {"Headphone", NULL, "LOUT"}, | |
104 | {"Headphone", NULL, "ROUT"}, | |
105 | }; | |
106 | ||
107 | static const char *n516_speaker_modes[] = {"Auto", "Off", "On"}; | |
108 | static const struct soc_enum n516_speaker_mode_enum = | |
109 | SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(n516_speaker_modes), n516_speaker_modes); | |
110 | ||
111 | static int n516_get_speaker_mode(struct snd_kcontrol *kcontrol, | |
112 | struct snd_ctl_elem_value *ucontrol) | |
113 | { | |
114 | ucontrol->value.integer.value[0] = n516_speaker_mode; | |
115 | return 0; | |
116 | } | |
117 | ||
118 | static int n516_set_speaker_mode(struct snd_kcontrol *kcontrol, | |
119 | struct snd_ctl_elem_value *ucontrol) | |
120 | { | |
121 | if (n516_speaker_mode == ucontrol->value.integer.value[0]) | |
122 | return 0; | |
123 | ||
124 | n516_speaker_mode = ucontrol->value.integer.value[0]; | |
125 | n516_ext_control(); | |
126 | return 1; | |
127 | } | |
128 | ||
129 | static const struct snd_kcontrol_new n516_controls[] = { | |
130 | SOC_ENUM_EXT("Speaker Function", n516_speaker_mode_enum, | |
131 | n516_get_speaker_mode, n516_set_speaker_mode), | |
132 | }; | |
133 | ||
134 | #define N516_DAIFMT (SND_SOC_DAIFMT_I2S | \ | |
135 | SND_SOC_DAIFMT_NB_NF | \ | |
136 | SND_SOC_DAIFMT_CBM_CFM) | |
137 | ||
138 | static int n516_codec_init(struct snd_soc_codec *codec) | |
139 | { | |
140 | int ret; | |
141 | struct snd_soc_dai *cpu_dai = codec->socdev->card->dai_link->cpu_dai; | |
142 | struct snd_soc_dai *codec_dai = codec->socdev->card->dai_link->codec_dai; | |
143 | ||
144 | n516_codec = codec; | |
145 | ||
146 | snd_soc_dapm_nc_pin(codec, "LIN"); | |
147 | snd_soc_dapm_nc_pin(codec, "RIN"); | |
148 | ||
149 | ret = snd_soc_dai_set_fmt(codec_dai, N516_DAIFMT); | |
150 | if (ret < 0) { | |
151 | dev_err(codec->dev, "Failed to set codec dai format: %d\n", ret); | |
152 | return ret; | |
153 | } | |
154 | ||
155 | ret = snd_soc_dai_set_fmt(cpu_dai, N516_DAIFMT); | |
156 | if (ret < 0) { | |
157 | dev_err(codec->dev, "Failed to set cpu dai format: %d\n", ret); | |
158 | return ret; | |
159 | } | |
160 | ||
161 | ret = snd_soc_dai_set_sysclk(codec_dai, JZCODEC_SYSCLK, 111, | |
162 | SND_SOC_CLOCK_IN); | |
163 | if (ret < 0) { | |
164 | dev_err(codec->dev, "Failed to set codec dai sysclk: %d\n", ret); | |
165 | return ret; | |
166 | } | |
167 | ||
168 | ret = snd_soc_add_controls(codec, n516_controls, | |
169 | ARRAY_SIZE(n516_controls)); | |
170 | if (ret) { | |
171 | dev_err(codec->dev, "Failed to add controls: %d\n", ret); | |
172 | return ret; | |
173 | } | |
174 | ||
175 | ||
176 | ret = snd_soc_dapm_new_controls(codec, n516_widgets, | |
177 | ARRAY_SIZE(n516_widgets)); | |
178 | if (ret) { | |
179 | dev_err(codec->dev, "Failed to add dapm controls: %d\n", ret); | |
180 | return ret; | |
181 | } | |
182 | ||
183 | ret = snd_soc_dapm_add_routes(codec, n516_routes, ARRAY_SIZE(n516_routes)); | |
184 | if (ret) { | |
185 | dev_err(codec->dev, "Failed to add dapm routes: %d\n", ret); | |
186 | return ret; | |
187 | } | |
188 | ||
189 | snd_soc_dapm_sync(codec); | |
190 | ||
191 | return 0; | |
192 | } | |
193 | ||
194 | static struct snd_soc_dai_link n516_dai = { | |
195 | .name = "jz-codec", | |
196 | .stream_name = "JZCODEC", | |
197 | .cpu_dai = &jz4740_i2s_dai, | |
198 | .codec_dai = &jz_codec_dai, | |
199 | .init = n516_codec_init, | |
200 | }; | |
201 | ||
202 | static struct snd_soc_card n516_card = { | |
203 | .name = "N516", | |
204 | .dai_link = &n516_dai, | |
205 | .num_links = 1, | |
206 | .platform = &jz4740_soc_platform, | |
207 | }; | |
208 | ||
209 | static struct snd_soc_device n516_snd_devdata = { | |
210 | .card = &n516_card, | |
211 | .codec_dev = &soc_codec_dev_jzcodec, | |
212 | }; | |
213 | ||
214 | static struct platform_device *n516_snd_device; | |
215 | ||
216 | static struct snd_soc_jack n516_hp_jack; | |
217 | ||
218 | static struct snd_soc_jack_pin n516_hp_pin = { | |
219 | .pin = "Headphone", | |
220 | .mask = SND_JACK_HEADPHONE, | |
221 | }; | |
222 | ||
223 | static struct snd_soc_jack_gpio n516_hp_gpio = { | |
224 | .gpio = GPIO_HPHONE_DETECT, | |
225 | .name = "Headphone detect", | |
226 | .report = SND_JACK_HEADPHONE, | |
227 | .debounce_time = 100, | |
228 | }; | |
229 | ||
230 | static int __init n516_add_headphone_jack(void) | |
231 | { | |
232 | int ret; | |
233 | ||
234 | ret = snd_soc_jack_new(&n516_card, "Headphone jack", | |
235 | SND_JACK_HEADPHONE, &n516_hp_jack); | |
236 | if (ret) | |
237 | return ret; | |
238 | ||
239 | ret = snd_soc_jack_add_pins(&n516_hp_jack, 1, &n516_hp_pin); | |
240 | if (ret) | |
241 | return ret; | |
242 | ||
243 | ret = snd_soc_jack_add_gpios(&n516_hp_jack, 1, &n516_hp_gpio); | |
244 | ||
245 | return ret; | |
246 | } | |
247 | ||
248 | static int __init n516_init(void) | |
249 | { | |
250 | int ret; | |
251 | ||
252 | n516_snd_device = platform_device_alloc("soc-audio", -1); | |
253 | ||
254 | if (!n516_snd_device) | |
255 | return -ENOMEM; | |
256 | ||
257 | ret = gpio_request(GPIO_SPEAKER_ENABLE, "Speaker enable"); | |
258 | if (ret) { | |
259 | pr_err("n516 snd: Failed to request SPEAKER_ENABLE GPIO(%d): %d\n", | |
260 | GPIO_SPEAKER_ENABLE, ret); | |
261 | goto err_device_put; | |
262 | } | |
263 | ||
264 | gpio_direction_output(GPIO_SPEAKER_ENABLE, 0); | |
265 | INIT_WORK(&n516_headphone_work, n516_headphone_event_work); | |
266 | ||
267 | platform_set_drvdata(n516_snd_device, &n516_snd_devdata); | |
268 | n516_snd_devdata.dev = &n516_snd_device->dev; | |
269 | ret = platform_device_add(n516_snd_device); | |
270 | if (ret) { | |
271 | pr_err("n516 snd: Failed to add snd soc device: %d\n", ret); | |
272 | goto err_unset_pdata; | |
273 | } | |
274 | ||
275 | ret = n516_add_headphone_jack(); | |
276 | /* We can live without it, so just print a warning */ | |
277 | if (ret) | |
278 | pr_warning("n516 snd: Failed to initalise headphone jack: %d\n", ret); | |
279 | ||
280 | return 0; | |
281 | ||
282 | err_unset_pdata: | |
283 | platform_set_drvdata(n516_snd_device, NULL); | |
284 | /*err_gpio_free_speaker:*/ | |
285 | gpio_free(GPIO_SPEAKER_ENABLE); | |
286 | err_device_put: | |
287 | platform_device_put(n516_snd_device); | |
288 | ||
289 | return ret; | |
290 | } | |
291 | module_init(n516_init); | |
292 | ||
293 | static void __exit n516_exit(void) | |
294 | { | |
295 | snd_soc_jack_free_gpios(&n516_hp_jack, 1, &n516_hp_gpio); | |
296 | gpio_free(GPIO_SPEAKER_ENABLE); | |
297 | platform_device_unregister(n516_snd_device); | |
298 | } | |
299 | module_exit(n516_exit); | |
300 | ||
301 | MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); | |
302 | MODULE_DESCRIPTION("ALSA SoC N516 Audio support"); | |
303 | MODULE_LICENSE("GPL v2"); |
Branches:
ben-wpan
ben-wpan-stefan
5396a9238205f20f811ea57898980d3ca82df0b6
jz-2.6.34
jz-2.6.34-rc5
jz-2.6.34-rc6
jz-2.6.34-rc7
jz-2.6.35
jz-2.6.36
jz-2.6.37
jz-2.6.38
jz-2.6.39
jz-3.0
jz-3.1
jz-3.11
jz-3.12
jz-3.13
jz-3.15
jz-3.16
jz-3.18-dt
jz-3.2
jz-3.3
jz-3.4
jz-3.5
jz-3.6
jz-3.6-rc2-pwm
jz-3.9
jz-3.9-clk
jz-3.9-rc8
jz47xx
jz47xx-2.6.38
master
Tags:
od-2011-09-04
od-2011-09-18
v2.6.34-rc5
v2.6.34-rc6
v2.6.34-rc7
v3.9