diff options
Diffstat (limited to 'sound/soc/atmel/sam9g20_wm8731.c')
-rw-r--r-- | sound/soc/atmel/sam9g20_wm8731.c | 328 |
1 files changed, 328 insertions, 0 deletions
diff --git a/sound/soc/atmel/sam9g20_wm8731.c b/sound/soc/atmel/sam9g20_wm8731.c new file mode 100644 index 000000000000..1fb59a9d3719 --- /dev/null +++ b/sound/soc/atmel/sam9g20_wm8731.c | |||
@@ -0,0 +1,328 @@ | |||
1 | /* | ||
2 | * sam9g20_wm8731 -- SoC audio for AT91SAM9G20-based | ||
3 | * ATMEL AT91SAM9G20ek board. | ||
4 | * | ||
5 | * Copyright (C) 2005 SAN People | ||
6 | * Copyright (C) 2008 Atmel | ||
7 | * | ||
8 | * Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com> | ||
9 | * | ||
10 | * Based on ati_b1_wm8731.c by: | ||
11 | * Frank Mandarino <fmandarino@endrelia.com> | ||
12 | * Copyright 2006 Endrelia Technologies Inc. | ||
13 | * Based on corgi.c by: | ||
14 | * Copyright 2005 Wolfson Microelectronics PLC. | ||
15 | * Copyright 2005 Openedhand Ltd. | ||
16 | * | ||
17 | * This program is free software; you can redistribute it and/or modify | ||
18 | * it under the terms of the GNU General Public License as published by | ||
19 | * the Free Software Foundation; either version 2 of the License, or | ||
20 | * (at your option) any later version. | ||
21 | * | ||
22 | * This program is distributed in the hope that it will be useful, | ||
23 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
24 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
25 | * GNU General Public License for more details. | ||
26 | * | ||
27 | * You should have received a copy of the GNU General Public License | ||
28 | * along with this program; if not, write to the Free Software | ||
29 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
30 | */ | ||
31 | |||
32 | #include <linux/module.h> | ||
33 | #include <linux/moduleparam.h> | ||
34 | #include <linux/kernel.h> | ||
35 | #include <linux/clk.h> | ||
36 | #include <linux/timer.h> | ||
37 | #include <linux/interrupt.h> | ||
38 | #include <linux/platform_device.h> | ||
39 | |||
40 | #include <linux/atmel-ssc.h> | ||
41 | |||
42 | #include <sound/core.h> | ||
43 | #include <sound/pcm.h> | ||
44 | #include <sound/pcm_params.h> | ||
45 | #include <sound/soc.h> | ||
46 | #include <sound/soc-dapm.h> | ||
47 | |||
48 | #include <mach/hardware.h> | ||
49 | #include <mach/gpio.h> | ||
50 | |||
51 | #include "../codecs/wm8731.h" | ||
52 | #include "atmel-pcm.h" | ||
53 | #include "atmel_ssc_dai.h" | ||
54 | |||
55 | |||
56 | static int at91sam9g20ek_startup(struct snd_pcm_substream *substream) | ||
57 | { | ||
58 | struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); | ||
59 | struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; | ||
60 | int ret; | ||
61 | |||
62 | /* codec system clock is supplied by PCK0, set to 12MHz */ | ||
63 | ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK, | ||
64 | 12000000, SND_SOC_CLOCK_IN); | ||
65 | if (ret < 0) | ||
66 | return ret; | ||
67 | |||
68 | return 0; | ||
69 | } | ||
70 | |||
71 | static void at91sam9g20ek_shutdown(struct snd_pcm_substream *substream) | ||
72 | { | ||
73 | struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); | ||
74 | |||
75 | dev_dbg(rtd->socdev->dev, "shutdown"); | ||
76 | } | ||
77 | |||
78 | static int at91sam9g20ek_hw_params(struct snd_pcm_substream *substream, | ||
79 | struct snd_pcm_hw_params *params) | ||
80 | { | ||
81 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
82 | struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; | ||
83 | struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; | ||
84 | struct atmel_ssc_info *ssc_p = cpu_dai->private_data; | ||
85 | struct ssc_device *ssc = ssc_p->ssc; | ||
86 | int ret; | ||
87 | |||
88 | unsigned int rate; | ||
89 | int cmr_div, period; | ||
90 | |||
91 | if (ssc == NULL) { | ||
92 | printk(KERN_INFO "at91sam9g20ek_hw_params: ssc is NULL!\n"); | ||
93 | return -EINVAL; | ||
94 | } | ||
95 | |||
96 | /* set codec DAI configuration */ | ||
97 | ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | | ||
98 | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); | ||
99 | if (ret < 0) | ||
100 | return ret; | ||
101 | |||
102 | /* set cpu DAI configuration */ | ||
103 | ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | | ||
104 | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); | ||
105 | if (ret < 0) | ||
106 | return ret; | ||
107 | |||
108 | /* | ||
109 | * The SSC clock dividers depend on the sample rate. The CMR.DIV | ||
110 | * field divides the system master clock MCK to drive the SSC TK | ||
111 | * signal which provides the codec BCLK. The TCMR.PERIOD and | ||
112 | * RCMR.PERIOD fields further divide the BCLK signal to drive | ||
113 | * the SSC TF and RF signals which provide the codec DACLRC and | ||
114 | * ADCLRC clocks. | ||
115 | * | ||
116 | * The dividers were determined through trial and error, where a | ||
117 | * CMR.DIV value is chosen such that the resulting BCLK value is | ||
118 | * divisible, or almost divisible, by (2 * sample rate), and then | ||
119 | * the TCMR.PERIOD or RCMR.PERIOD is BCLK / (2 * sample rate) - 1. | ||
120 | */ | ||
121 | rate = params_rate(params); | ||
122 | |||
123 | switch (rate) { | ||
124 | case 8000: | ||
125 | cmr_div = 55; /* BCLK = 133MHz/(2*55) = 1.209MHz */ | ||
126 | period = 74; /* LRC = BCLK/(2*(74+1)) ~= 8060,6Hz */ | ||
127 | break; | ||
128 | case 11025: | ||
129 | cmr_div = 67; /* BCLK = 133MHz/(2*60) = 1.108MHz */ | ||
130 | period = 45; /* LRC = BCLK/(2*(49+1)) = 11083,3Hz */ | ||
131 | break; | ||
132 | case 16000: | ||
133 | cmr_div = 63; /* BCLK = 133MHz/(2*63) = 1.055MHz */ | ||
134 | period = 32; /* LRC = BCLK/(2*(32+1)) = 15993,2Hz */ | ||
135 | break; | ||
136 | case 22050: | ||
137 | cmr_div = 52; /* BCLK = 133MHz/(2*52) = 1.278MHz */ | ||
138 | period = 28; /* LRC = BCLK/(2*(28+1)) = 22049Hz */ | ||
139 | break; | ||
140 | case 32000: | ||
141 | cmr_div = 66; /* BCLK = 133MHz/(2*66) = 1.007MHz */ | ||
142 | period = 15; /* LRC = BCLK/(2*(15+1)) = 31486,742Hz */ | ||
143 | break; | ||
144 | case 44100: | ||
145 | cmr_div = 29; /* BCLK = 133MHz/(2*29) = 2.293MHz */ | ||
146 | period = 25; /* LRC = BCLK/(2*(25+1)) = 44098Hz */ | ||
147 | break; | ||
148 | case 48000: | ||
149 | cmr_div = 33; /* BCLK = 133MHz/(2*33) = 2.015MHz */ | ||
150 | period = 20; /* LRC = BCLK/(2*(20+1)) = 47979,79Hz */ | ||
151 | break; | ||
152 | case 88200: | ||
153 | cmr_div = 29; /* BCLK = 133MHz/(2*29) = 2.293MHz */ | ||
154 | period = 12; /* LRC = BCLK/(2*(12+1)) = 88196Hz */ | ||
155 | break; | ||
156 | case 96000: | ||
157 | cmr_div = 23; /* BCLK = 133MHz/(2*23) = 2.891MHz */ | ||
158 | period = 14; /* LRC = BCLK/(2*(14+1)) = 96376Hz */ | ||
159 | break; | ||
160 | default: | ||
161 | printk(KERN_WARNING "unsupported rate %d" | ||
162 | " on at91sam9g20ek board\n", rate); | ||
163 | return -EINVAL; | ||
164 | } | ||
165 | |||
166 | /* set the MCK divider for BCLK */ | ||
167 | ret = snd_soc_dai_set_clkdiv(cpu_dai, ATMEL_SSC_CMR_DIV, cmr_div); | ||
168 | if (ret < 0) | ||
169 | return ret; | ||
170 | |||
171 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
172 | /* set the BCLK divider for DACLRC */ | ||
173 | ret = snd_soc_dai_set_clkdiv(cpu_dai, | ||
174 | ATMEL_SSC_TCMR_PERIOD, period); | ||
175 | } else { | ||
176 | /* set the BCLK divider for ADCLRC */ | ||
177 | ret = snd_soc_dai_set_clkdiv(cpu_dai, | ||
178 | ATMEL_SSC_RCMR_PERIOD, period); | ||
179 | } | ||
180 | if (ret < 0) | ||
181 | return ret; | ||
182 | |||
183 | return 0; | ||
184 | } | ||
185 | |||
186 | static struct snd_soc_ops at91sam9g20ek_ops = { | ||
187 | .startup = at91sam9g20ek_startup, | ||
188 | .hw_params = at91sam9g20ek_hw_params, | ||
189 | .shutdown = at91sam9g20ek_shutdown, | ||
190 | }; | ||
191 | |||
192 | |||
193 | static const struct snd_soc_dapm_widget at91sam9g20ek_dapm_widgets[] = { | ||
194 | SND_SOC_DAPM_MIC("Int Mic", NULL), | ||
195 | SND_SOC_DAPM_SPK("Ext Spk", NULL), | ||
196 | }; | ||
197 | |||
198 | static const struct snd_soc_dapm_route intercon[] = { | ||
199 | |||
200 | /* speaker connected to LHPOUT */ | ||
201 | {"Ext Spk", NULL, "LHPOUT"}, | ||
202 | |||
203 | /* mic is connected to Mic Jack, with WM8731 Mic Bias */ | ||
204 | {"MICIN", NULL, "Mic Bias"}, | ||
205 | {"Mic Bias", NULL, "Int Mic"}, | ||
206 | }; | ||
207 | |||
208 | /* | ||
209 | * Logic for a wm8731 as connected on a at91sam9g20ek board. | ||
210 | */ | ||
211 | static int at91sam9g20ek_wm8731_init(struct snd_soc_codec *codec) | ||
212 | { | ||
213 | printk(KERN_DEBUG | ||
214 | "at91sam9g20ek_wm8731 " | ||
215 | ": at91sam9g20ek_wm8731_init() called\n"); | ||
216 | |||
217 | /* Add specific widgets */ | ||
218 | snd_soc_dapm_new_controls(codec, at91sam9g20ek_dapm_widgets, | ||
219 | ARRAY_SIZE(at91sam9g20ek_dapm_widgets)); | ||
220 | /* Set up specific audio path interconnects */ | ||
221 | snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); | ||
222 | |||
223 | /* not connected */ | ||
224 | snd_soc_dapm_disable_pin(codec, "RLINEIN"); | ||
225 | snd_soc_dapm_disable_pin(codec, "LLINEIN"); | ||
226 | |||
227 | /* always connected */ | ||
228 | snd_soc_dapm_enable_pin(codec, "Int Mic"); | ||
229 | snd_soc_dapm_enable_pin(codec, "Ext Spk"); | ||
230 | |||
231 | snd_soc_dapm_sync(codec); | ||
232 | |||
233 | return 0; | ||
234 | } | ||
235 | |||
236 | static struct snd_soc_dai_link at91sam9g20ek_dai = { | ||
237 | .name = "WM8731", | ||
238 | .stream_name = "WM8731 PCM", | ||
239 | .cpu_dai = &atmel_ssc_dai[0], | ||
240 | .codec_dai = &wm8731_dai, | ||
241 | .init = at91sam9g20ek_wm8731_init, | ||
242 | .ops = &at91sam9g20ek_ops, | ||
243 | }; | ||
244 | |||
245 | static struct snd_soc_card snd_soc_at91sam9g20ek = { | ||
246 | .name = "WM8731", | ||
247 | .platform = &atmel_soc_platform, | ||
248 | .dai_link = &at91sam9g20ek_dai, | ||
249 | .num_links = 1, | ||
250 | }; | ||
251 | |||
252 | static struct wm8731_setup_data at91sam9g20ek_wm8731_setup = { | ||
253 | .i2c_bus = 0, | ||
254 | .i2c_address = 0x1b, | ||
255 | }; | ||
256 | |||
257 | static struct snd_soc_device at91sam9g20ek_snd_devdata = { | ||
258 | .card = &snd_soc_at91sam9g20ek, | ||
259 | .codec_dev = &soc_codec_dev_wm8731, | ||
260 | .codec_data = &at91sam9g20ek_wm8731_setup, | ||
261 | }; | ||
262 | |||
263 | static struct platform_device *at91sam9g20ek_snd_device; | ||
264 | |||
265 | static int __init at91sam9g20ek_init(void) | ||
266 | { | ||
267 | struct atmel_ssc_info *ssc_p = at91sam9g20ek_dai.cpu_dai->private_data; | ||
268 | struct ssc_device *ssc = NULL; | ||
269 | int ret; | ||
270 | |||
271 | /* | ||
272 | * Request SSC device | ||
273 | */ | ||
274 | ssc = ssc_request(0); | ||
275 | if (IS_ERR(ssc)) { | ||
276 | ret = PTR_ERR(ssc); | ||
277 | ssc = NULL; | ||
278 | goto err_ssc; | ||
279 | } | ||
280 | ssc_p->ssc = ssc; | ||
281 | |||
282 | at91sam9g20ek_snd_device = platform_device_alloc("soc-audio", -1); | ||
283 | if (!at91sam9g20ek_snd_device) { | ||
284 | printk(KERN_DEBUG | ||
285 | "platform device allocation failed\n"); | ||
286 | ret = -ENOMEM; | ||
287 | } | ||
288 | |||
289 | platform_set_drvdata(at91sam9g20ek_snd_device, | ||
290 | &at91sam9g20ek_snd_devdata); | ||
291 | at91sam9g20ek_snd_devdata.dev = &at91sam9g20ek_snd_device->dev; | ||
292 | |||
293 | ret = platform_device_add(at91sam9g20ek_snd_device); | ||
294 | if (ret) { | ||
295 | printk(KERN_DEBUG | ||
296 | "platform device allocation failed\n"); | ||
297 | platform_device_put(at91sam9g20ek_snd_device); | ||
298 | } | ||
299 | |||
300 | return ret; | ||
301 | |||
302 | err_ssc: | ||
303 | return ret; | ||
304 | } | ||
305 | |||
306 | static void __exit at91sam9g20ek_exit(void) | ||
307 | { | ||
308 | struct atmel_ssc_info *ssc_p = at91sam9g20ek_dai.cpu_dai->private_data; | ||
309 | struct ssc_device *ssc; | ||
310 | |||
311 | if (ssc_p != NULL) { | ||
312 | ssc = ssc_p->ssc; | ||
313 | if (ssc != NULL) | ||
314 | ssc_free(ssc); | ||
315 | ssc_p->ssc = NULL; | ||
316 | } | ||
317 | |||
318 | platform_device_unregister(at91sam9g20ek_snd_device); | ||
319 | at91sam9g20ek_snd_device = NULL; | ||
320 | } | ||
321 | |||
322 | module_init(at91sam9g20ek_init); | ||
323 | module_exit(at91sam9g20ek_exit); | ||
324 | |||
325 | /* Module information */ | ||
326 | MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>"); | ||
327 | MODULE_DESCRIPTION("ALSA SoC AT91SAM9G20EK_WM8731"); | ||
328 | MODULE_LICENSE("GPL"); | ||