diff options
Diffstat (limited to 'sound/soc/omap/n810.c')
-rw-r--r-- | sound/soc/omap/n810.c | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/sound/soc/omap/n810.c b/sound/soc/omap/n810.c new file mode 100644 index 000000000000..83b1eb4e40f3 --- /dev/null +++ b/sound/soc/omap/n810.c | |||
@@ -0,0 +1,336 @@ | |||
1 | /* | ||
2 | * n810.c -- SoC audio for Nokia N810 | ||
3 | * | ||
4 | * Copyright (C) 2008 Nokia Corporation | ||
5 | * | ||
6 | * Contact: Jarkko Nikula <jarkko.nikula@nokia.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or | ||
9 | * modify it under the terms of the GNU General Public License | ||
10 | * version 2 as published by the Free Software Foundation. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, but | ||
13 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
15 | * General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with this program; if not, write to the Free Software | ||
19 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | ||
20 | * 02110-1301 USA | ||
21 | * | ||
22 | */ | ||
23 | |||
24 | #include <linux/clk.h> | ||
25 | #include <linux/platform_device.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/arch/hardware.h> | ||
33 | #include <asm/arch/gpio.h> | ||
34 | #include <asm/arch/mcbsp.h> | ||
35 | |||
36 | #include "omap-mcbsp.h" | ||
37 | #include "omap-pcm.h" | ||
38 | #include "../codecs/tlv320aic3x.h" | ||
39 | |||
40 | #define RX44_HEADSET_AMP_GPIO 10 | ||
41 | #define RX44_SPEAKER_AMP_GPIO 101 | ||
42 | |||
43 | static struct clk *sys_clkout2; | ||
44 | static struct clk *sys_clkout2_src; | ||
45 | static struct clk *func96m_clk; | ||
46 | |||
47 | static int n810_spk_func; | ||
48 | static int n810_jack_func; | ||
49 | |||
50 | static void n810_ext_control(struct snd_soc_codec *codec) | ||
51 | { | ||
52 | snd_soc_dapm_set_endpoint(codec, "Ext Spk", n810_spk_func); | ||
53 | snd_soc_dapm_set_endpoint(codec, "Headphone Jack", n810_jack_func); | ||
54 | |||
55 | snd_soc_dapm_sync_endpoints(codec); | ||
56 | } | ||
57 | |||
58 | static int n810_startup(struct snd_pcm_substream *substream) | ||
59 | { | ||
60 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
61 | struct snd_soc_codec *codec = rtd->socdev->codec; | ||
62 | |||
63 | n810_ext_control(codec); | ||
64 | return clk_enable(sys_clkout2); | ||
65 | } | ||
66 | |||
67 | static void n810_shutdown(struct snd_pcm_substream *substream) | ||
68 | { | ||
69 | clk_disable(sys_clkout2); | ||
70 | } | ||
71 | |||
72 | static int n810_hw_params(struct snd_pcm_substream *substream, | ||
73 | struct snd_pcm_hw_params *params) | ||
74 | { | ||
75 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
76 | struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai; | ||
77 | struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; | ||
78 | int err; | ||
79 | |||
80 | /* Set codec DAI configuration */ | ||
81 | err = codec_dai->dai_ops.set_fmt(codec_dai, | ||
82 | SND_SOC_DAIFMT_I2S | | ||
83 | SND_SOC_DAIFMT_NB_NF | | ||
84 | SND_SOC_DAIFMT_CBM_CFM); | ||
85 | if (err < 0) | ||
86 | return err; | ||
87 | |||
88 | /* Set cpu DAI configuration */ | ||
89 | err = cpu_dai->dai_ops.set_fmt(cpu_dai, | ||
90 | SND_SOC_DAIFMT_I2S | | ||
91 | SND_SOC_DAIFMT_NB_NF | | ||
92 | SND_SOC_DAIFMT_CBM_CFM); | ||
93 | if (err < 0) | ||
94 | return err; | ||
95 | |||
96 | /* Set the codec system clock for DAC and ADC */ | ||
97 | err = codec_dai->dai_ops.set_sysclk(codec_dai, 0, 12000000, | ||
98 | SND_SOC_CLOCK_IN); | ||
99 | |||
100 | return err; | ||
101 | } | ||
102 | |||
103 | static struct snd_soc_ops n810_ops = { | ||
104 | .startup = n810_startup, | ||
105 | .hw_params = n810_hw_params, | ||
106 | .shutdown = n810_shutdown, | ||
107 | }; | ||
108 | |||
109 | static int n810_get_spk(struct snd_kcontrol *kcontrol, | ||
110 | struct snd_ctl_elem_value *ucontrol) | ||
111 | { | ||
112 | ucontrol->value.integer.value[0] = n810_spk_func; | ||
113 | |||
114 | return 0; | ||
115 | } | ||
116 | |||
117 | static int n810_set_spk(struct snd_kcontrol *kcontrol, | ||
118 | struct snd_ctl_elem_value *ucontrol) | ||
119 | { | ||
120 | struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); | ||
121 | |||
122 | if (n810_spk_func == ucontrol->value.integer.value[0]) | ||
123 | return 0; | ||
124 | |||
125 | n810_spk_func = ucontrol->value.integer.value[0]; | ||
126 | n810_ext_control(codec); | ||
127 | |||
128 | return 1; | ||
129 | } | ||
130 | |||
131 | static int n810_get_jack(struct snd_kcontrol *kcontrol, | ||
132 | struct snd_ctl_elem_value *ucontrol) | ||
133 | { | ||
134 | ucontrol->value.integer.value[0] = n810_jack_func; | ||
135 | |||
136 | return 0; | ||
137 | } | ||
138 | |||
139 | static int n810_set_jack(struct snd_kcontrol *kcontrol, | ||
140 | struct snd_ctl_elem_value *ucontrol) | ||
141 | { | ||
142 | struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); | ||
143 | |||
144 | if (n810_jack_func == ucontrol->value.integer.value[0]) | ||
145 | return 0; | ||
146 | |||
147 | n810_jack_func = ucontrol->value.integer.value[0]; | ||
148 | n810_ext_control(codec); | ||
149 | |||
150 | return 1; | ||
151 | } | ||
152 | |||
153 | static int n810_spk_event(struct snd_soc_dapm_widget *w, | ||
154 | struct snd_kcontrol *k, int event) | ||
155 | { | ||
156 | if (SND_SOC_DAPM_EVENT_ON(event)) | ||
157 | omap_set_gpio_dataout(RX44_SPEAKER_AMP_GPIO, 1); | ||
158 | else | ||
159 | omap_set_gpio_dataout(RX44_SPEAKER_AMP_GPIO, 0); | ||
160 | |||
161 | return 0; | ||
162 | } | ||
163 | |||
164 | static int n810_jack_event(struct snd_soc_dapm_widget *w, | ||
165 | struct snd_kcontrol *k, int event) | ||
166 | { | ||
167 | if (SND_SOC_DAPM_EVENT_ON(event)) | ||
168 | omap_set_gpio_dataout(RX44_HEADSET_AMP_GPIO, 1); | ||
169 | else | ||
170 | omap_set_gpio_dataout(RX44_HEADSET_AMP_GPIO, 0); | ||
171 | |||
172 | return 0; | ||
173 | } | ||
174 | |||
175 | static const struct snd_soc_dapm_widget aic33_dapm_widgets[] = { | ||
176 | SND_SOC_DAPM_SPK("Ext Spk", n810_spk_event), | ||
177 | SND_SOC_DAPM_HP("Headphone Jack", n810_jack_event), | ||
178 | }; | ||
179 | |||
180 | static const char *audio_map[][3] = { | ||
181 | {"Headphone Jack", NULL, "HPLOUT"}, | ||
182 | {"Headphone Jack", NULL, "HPROUT"}, | ||
183 | |||
184 | {"Ext Spk", NULL, "LLOUT"}, | ||
185 | {"Ext Spk", NULL, "RLOUT"}, | ||
186 | }; | ||
187 | |||
188 | static const char *spk_function[] = {"Off", "On"}; | ||
189 | static const char *jack_function[] = {"Off", "Headphone"}; | ||
190 | static const struct soc_enum n810_enum[] = { | ||
191 | SOC_ENUM_SINGLE_EXT(2, spk_function), | ||
192 | SOC_ENUM_SINGLE_EXT(3, jack_function), | ||
193 | }; | ||
194 | |||
195 | static const struct snd_kcontrol_new aic33_n810_controls[] = { | ||
196 | SOC_ENUM_EXT("Speaker Function", n810_enum[0], | ||
197 | n810_get_spk, n810_set_spk), | ||
198 | SOC_ENUM_EXT("Jack Function", n810_enum[1], | ||
199 | n810_get_jack, n810_set_jack), | ||
200 | }; | ||
201 | |||
202 | static int n810_aic33_init(struct snd_soc_codec *codec) | ||
203 | { | ||
204 | int i, err; | ||
205 | |||
206 | /* Not connected */ | ||
207 | snd_soc_dapm_set_endpoint(codec, "MONO_LOUT", 0); | ||
208 | snd_soc_dapm_set_endpoint(codec, "HPLCOM", 0); | ||
209 | snd_soc_dapm_set_endpoint(codec, "HPRCOM", 0); | ||
210 | |||
211 | /* Add N810 specific controls */ | ||
212 | for (i = 0; i < ARRAY_SIZE(aic33_n810_controls); i++) { | ||
213 | err = snd_ctl_add(codec->card, | ||
214 | snd_soc_cnew(&aic33_n810_controls[i], codec, NULL)); | ||
215 | if (err < 0) | ||
216 | return err; | ||
217 | } | ||
218 | |||
219 | /* Add N810 specific widgets */ | ||
220 | for (i = 0; i < ARRAY_SIZE(aic33_dapm_widgets); i++) | ||
221 | snd_soc_dapm_new_control(codec, &aic33_dapm_widgets[i]); | ||
222 | |||
223 | /* Set up N810 specific audio path audio_map */ | ||
224 | for (i = 0; i < ARRAY_SIZE(audio_map); i++) | ||
225 | snd_soc_dapm_connect_input(codec, audio_map[i][0], | ||
226 | audio_map[i][1], audio_map[i][2]); | ||
227 | |||
228 | snd_soc_dapm_sync_endpoints(codec); | ||
229 | |||
230 | return 0; | ||
231 | } | ||
232 | |||
233 | /* Digital audio interface glue - connects codec <--> CPU */ | ||
234 | static struct snd_soc_dai_link n810_dai = { | ||
235 | .name = "TLV320AIC33", | ||
236 | .stream_name = "AIC33", | ||
237 | .cpu_dai = &omap_mcbsp_dai[0], | ||
238 | .codec_dai = &aic3x_dai, | ||
239 | .init = n810_aic33_init, | ||
240 | .ops = &n810_ops, | ||
241 | }; | ||
242 | |||
243 | /* Audio machine driver */ | ||
244 | static struct snd_soc_machine snd_soc_machine_n810 = { | ||
245 | .name = "N810", | ||
246 | .dai_link = &n810_dai, | ||
247 | .num_links = 1, | ||
248 | }; | ||
249 | |||
250 | /* Audio private data */ | ||
251 | static struct aic3x_setup_data n810_aic33_setup = { | ||
252 | .i2c_address = 0x18, | ||
253 | }; | ||
254 | |||
255 | /* Audio subsystem */ | ||
256 | static struct snd_soc_device n810_snd_devdata = { | ||
257 | .machine = &snd_soc_machine_n810, | ||
258 | .platform = &omap_soc_platform, | ||
259 | .codec_dev = &soc_codec_dev_aic3x, | ||
260 | .codec_data = &n810_aic33_setup, | ||
261 | }; | ||
262 | |||
263 | static struct platform_device *n810_snd_device; | ||
264 | |||
265 | static int __init n810_soc_init(void) | ||
266 | { | ||
267 | int err; | ||
268 | struct device *dev; | ||
269 | |||
270 | if (!machine_is_nokia_n810()) | ||
271 | return -ENODEV; | ||
272 | |||
273 | n810_snd_device = platform_device_alloc("soc-audio", -1); | ||
274 | if (!n810_snd_device) | ||
275 | return -ENOMEM; | ||
276 | |||
277 | platform_set_drvdata(n810_snd_device, &n810_snd_devdata); | ||
278 | n810_snd_devdata.dev = &n810_snd_device->dev; | ||
279 | *(unsigned int *)n810_dai.cpu_dai->private_data = 1; /* McBSP2 */ | ||
280 | err = platform_device_add(n810_snd_device); | ||
281 | if (err) | ||
282 | goto err1; | ||
283 | |||
284 | dev = &n810_snd_device->dev; | ||
285 | |||
286 | sys_clkout2_src = clk_get(dev, "sys_clkout2_src"); | ||
287 | if (IS_ERR(sys_clkout2_src)) { | ||
288 | dev_err(dev, "Could not get sys_clkout2_src clock\n"); | ||
289 | return -ENODEV; | ||
290 | } | ||
291 | sys_clkout2 = clk_get(dev, "sys_clkout2"); | ||
292 | if (IS_ERR(sys_clkout2)) { | ||
293 | dev_err(dev, "Could not get sys_clkout2\n"); | ||
294 | goto err1; | ||
295 | } | ||
296 | /* | ||
297 | * Configure 12 MHz output on SYS_CLKOUT2. Therefore we must use | ||
298 | * 96 MHz as its parent in order to get 12 MHz | ||
299 | */ | ||
300 | func96m_clk = clk_get(dev, "func_96m_ck"); | ||
301 | if (IS_ERR(func96m_clk)) { | ||
302 | dev_err(dev, "Could not get func 96M clock\n"); | ||
303 | goto err2; | ||
304 | } | ||
305 | clk_set_parent(sys_clkout2_src, func96m_clk); | ||
306 | clk_set_rate(sys_clkout2, 12000000); | ||
307 | |||
308 | if (omap_request_gpio(RX44_HEADSET_AMP_GPIO) < 0) | ||
309 | BUG(); | ||
310 | if (omap_request_gpio(RX44_SPEAKER_AMP_GPIO) < 0) | ||
311 | BUG(); | ||
312 | omap_set_gpio_direction(RX44_HEADSET_AMP_GPIO, 0); | ||
313 | omap_set_gpio_direction(RX44_SPEAKER_AMP_GPIO, 0); | ||
314 | |||
315 | return 0; | ||
316 | err2: | ||
317 | clk_put(sys_clkout2); | ||
318 | platform_device_del(n810_snd_device); | ||
319 | err1: | ||
320 | platform_device_put(n810_snd_device); | ||
321 | |||
322 | return err; | ||
323 | |||
324 | } | ||
325 | |||
326 | static void __exit n810_soc_exit(void) | ||
327 | { | ||
328 | platform_device_unregister(n810_snd_device); | ||
329 | } | ||
330 | |||
331 | module_init(n810_soc_init); | ||
332 | module_exit(n810_soc_exit); | ||
333 | |||
334 | MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@nokia.com>"); | ||
335 | MODULE_DESCRIPTION("ALSA SoC Nokia N810"); | ||
336 | MODULE_LICENSE("GPL"); | ||