diff options
-rw-r--r-- | sound/soc/atmel/sam9g20_wm8731.c | 329 |
1 files changed, 329 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..4e191df4e2b7 --- /dev/null +++ b/sound/soc/atmel/sam9g20_wm8731.c | |||
@@ -0,0 +1,329 @@ | |||
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/version.h> | ||
35 | #include <linux/kernel.h> | ||
36 | #include <linux/clk.h> | ||
37 | #include <linux/timer.h> | ||
38 | #include <linux/interrupt.h> | ||
39 | #include <linux/platform_device.h> | ||
40 | |||
41 | #include <linux/atmel-ssc.h> | ||
42 | |||
43 | #include <sound/core.h> | ||
44 | #include <sound/pcm.h> | ||
45 | #include <sound/pcm_params.h> | ||
46 | #include <sound/soc.h> | ||
47 | #include <sound/soc-dapm.h> | ||
48 | |||
49 | #include <mach/hardware.h> | ||
50 | #include <mach/gpio.h> | ||
51 | |||
52 | #include "../codecs/wm8731.h" | ||
53 | #include "atmel-pcm.h" | ||
54 | #include "atmel_ssc_dai.h" | ||
55 | |||
56 | |||
57 | static int at91sam9g20ek_startup(struct snd_pcm_substream *substream) | ||
58 | { | ||
59 | struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); | ||
60 | struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; | ||
61 | int ret; | ||
62 | |||
63 | /* codec system clock is supplied by PCK0, set to 12MHz */ | ||
64 | ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK, | ||
65 | 12000000, SND_SOC_CLOCK_IN); | ||
66 | if (ret < 0) | ||
67 | return ret; | ||
68 | |||
69 | return 0; | ||
70 | } | ||
71 | |||
72 | static void at91sam9g20ek_shutdown(struct snd_pcm_substream *substream) | ||
73 | { | ||
74 | struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); | ||
75 | |||
76 | dev_dbg(rtd->socdev->dev, "shutdown"); | ||
77 | } | ||
78 | |||
79 | static int at91sam9g20ek_hw_params(struct snd_pcm_substream *substream, | ||
80 | struct snd_pcm_hw_params *params) | ||
81 | { | ||
82 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
83 | struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; | ||
84 | struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; | ||
85 | struct atmel_ssc_info *ssc_p = cpu_dai->private_data; | ||
86 | struct ssc_device *ssc = ssc_p->ssc; | ||
87 | int ret; | ||
88 | |||
89 | unsigned int rate; | ||
90 | int cmr_div, period; | ||
91 | |||
92 | if (ssc == NULL) { | ||
93 | printk(KERN_INFO "at91sam9g20ek_hw_params: ssc is NULL!\n"); | ||
94 | return -EINVAL; | ||
95 | } | ||
96 | |||
97 | /* set codec DAI configuration */ | ||
98 | ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | | ||
99 | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); | ||
100 | if (ret < 0) | ||
101 | return ret; | ||
102 | |||
103 | /* set cpu DAI configuration */ | ||
104 | ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | | ||
105 | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); | ||
106 | if (ret < 0) | ||
107 | return ret; | ||
108 | |||
109 | /* | ||
110 | * The SSC clock dividers depend on the sample rate. The CMR.DIV | ||
111 | * field divides the system master clock MCK to drive the SSC TK | ||
112 | * signal which provides the codec BCLK. The TCMR.PERIOD and | ||
113 | * RCMR.PERIOD fields further divide the BCLK signal to drive | ||
114 | * the SSC TF and RF signals which provide the codec DACLRC and | ||
115 | * ADCLRC clocks. | ||
116 | * | ||
117 | * The dividers were determined through trial and error, where a | ||
118 | * CMR.DIV value is chosen such that the resulting BCLK value is | ||
119 | * divisible, or almost divisible, by (2 * sample rate), and then | ||
120 | * the TCMR.PERIOD or RCMR.PERIOD is BCLK / (2 * sample rate) - 1. | ||
121 | */ | ||
122 | rate = params_rate(params); | ||
123 | |||
124 | switch (rate) { | ||
125 | case 8000: | ||
126 | cmr_div = 55; /* BCLK = 133MHz/(2*55) = 1.209MHz */ | ||
127 | period = 74; /* LRC = BCLK/(2*(74+1)) ~= 8060,6Hz */ | ||
128 | break; | ||
129 | case 11025: | ||
130 | cmr_div = 67; /* BCLK = 133MHz/(2*60) = 1.108MHz */ | ||
131 | period = 45; /* LRC = BCLK/(2*(49+1)) = 11083,3Hz */ | ||
132 | break; | ||
133 | case 16000: | ||
134 | cmr_div = 63; /* BCLK = 133MHz/(2*63) = 1.055MHz */ | ||
135 | period = 32; /* LRC = BCLK/(2*(32+1)) = 15993,2Hz */ | ||
136 | break; | ||
137 | case 22050: | ||
138 | cmr_div = 52; /* BCLK = 133MHz/(2*52) = 1.278MHz */ | ||
139 | period = 28; /* LRC = BCLK/(2*(28+1)) = 22049Hz */ | ||
140 | break; | ||
141 | case 32000: | ||
142 | cmr_div = 66; /* BCLK = 133MHz/(2*66) = 1.007MHz */ | ||
143 | period = 15; /* LRC = BCLK/(2*(15+1)) = 31486,742Hz */ | ||
144 | break; | ||
145 | case 44100: | ||
146 | cmr_div = 29; /* BCLK = 133MHz/(2*29) = 2.293MHz */ | ||
147 | period = 25; /* LRC = BCLK/(2*(25+1)) = 44098Hz */ | ||
148 | break; | ||
149 | case 48000: | ||
150 | cmr_div = 33; /* BCLK = 133MHz/(2*33) = 2.015MHz */ | ||
151 | period = 20; /* LRC = BCLK/(2*(20+1)) = 47979,79Hz */ | ||
152 | break; | ||
153 | case 88200: | ||
154 | cmr_div = 29; /* BCLK = 133MHz/(2*29) = 2.293MHz */ | ||
155 | period = 12; /* LRC = BCLK/(2*(12+1)) = 88196Hz */ | ||
156 | break; | ||
157 | case 96000: | ||
158 | cmr_div = 23; /* BCLK = 133MHz/(2*23) = 2.891MHz */ | ||
159 | period = 14; /* LRC = BCLK/(2*(14+1)) = 96376Hz */ | ||
160 | break; | ||
161 | default: | ||
162 | printk(KERN_WARNING "unsupported rate %d" | ||
163 | " on at91sam9g20ek board\n", rate); | ||
164 | return -EINVAL; | ||
165 | } | ||
166 | |||
167 | /* set the MCK divider for BCLK */ | ||
168 | ret = snd_soc_dai_set_clkdiv(cpu_dai, ATMEL_SSC_CMR_DIV, cmr_div); | ||
169 | if (ret < 0) | ||
170 | return ret; | ||
171 | |||
172 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
173 | /* set the BCLK divider for DACLRC */ | ||
174 | ret = snd_soc_dai_set_clkdiv(cpu_dai, | ||
175 | ATMEL_SSC_TCMR_PERIOD, period); | ||
176 | } else { | ||
177 | /* set the BCLK divider for ADCLRC */ | ||
178 | ret = snd_soc_dai_set_clkdiv(cpu_dai, | ||
179 | ATMEL_SSC_RCMR_PERIOD, period); | ||
180 | } | ||
181 | if (ret < 0) | ||
182 | return ret; | ||
183 | |||
184 | return 0; | ||
185 | } | ||
186 | |||
187 | static struct snd_soc_ops at91sam9g20ek_ops = { | ||
188 | .startup = at91sam9g20ek_startup, | ||
189 | .hw_params = at91sam9g20ek_hw_params, | ||
190 | .shutdown = at91sam9g20ek_shutdown, | ||
191 | }; | ||
192 | |||
193 | |||
194 | static const struct snd_soc_dapm_widget at91sam9g20ek_dapm_widgets[] = { | ||
195 | SND_SOC_DAPM_MIC("Int Mic", NULL), | ||
196 | SND_SOC_DAPM_SPK("Ext Spk", NULL), | ||
197 | }; | ||
198 | |||
199 | static const struct snd_soc_dapm_route intercon[] = { | ||
200 | |||
201 | /* speaker connected to LHPOUT */ | ||
202 | {"Ext Spk", NULL, "LHPOUT"}, | ||
203 | |||
204 | /* mic is connected to Mic Jack, with WM8731 Mic Bias */ | ||
205 | {"MICIN", NULL, "Mic Bias"}, | ||
206 | {"Mic Bias", NULL, "Int Mic"}, | ||
207 | }; | ||
208 | |||
209 | /* | ||
210 | * Logic for a wm8731 as connected on a at91sam9g20ek board. | ||
211 | */ | ||
212 | static int at91sam9g20ek_wm8731_init(struct snd_soc_codec *codec) | ||
213 | { | ||
214 | printk(KERN_DEBUG | ||
215 | "at91sam9g20ek_wm8731 " | ||
216 | ": at91sam9g20ek_wm8731_init() called\n"); | ||
217 | |||
218 | /* Add specific widgets */ | ||
219 | snd_soc_dapm_new_controls(codec, at91sam9g20ek_dapm_widgets, | ||
220 | ARRAY_SIZE(at91sam9g20ek_dapm_widgets)); | ||
221 | /* Set up specific audio path interconnects */ | ||
222 | snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); | ||
223 | |||
224 | /* not connected */ | ||
225 | snd_soc_dapm_disable_pin(codec, "RLINEIN"); | ||
226 | snd_soc_dapm_disable_pin(codec, "LLINEIN"); | ||
227 | |||
228 | /* always connected */ | ||
229 | snd_soc_dapm_enable_pin(codec, "Int Mic"); | ||
230 | snd_soc_dapm_enable_pin(codec, "Ext Spk"); | ||
231 | |||
232 | snd_soc_dapm_sync(codec); | ||
233 | |||
234 | return 0; | ||
235 | } | ||
236 | |||
237 | static struct snd_soc_dai_link at91sam9g20ek_dai = { | ||
238 | .name = "WM8731", | ||
239 | .stream_name = "WM8731 PCM", | ||
240 | .cpu_dai = &atmel_ssc_dai[0], | ||
241 | .codec_dai = &wm8731_dai, | ||
242 | .init = at91sam9g20ek_wm8731_init, | ||
243 | .ops = &at91sam9g20ek_ops, | ||
244 | }; | ||
245 | |||
246 | static struct snd_soc_machine snd_soc_machine_at91sam9g20ek = { | ||
247 | .name = "WM8731", | ||
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 | .machine = &snd_soc_machine_at91sam9g20ek, | ||
259 | .platform = &atmel_soc_platform, | ||
260 | .codec_dev = &soc_codec_dev_wm8731, | ||
261 | .codec_data = &at91sam9g20ek_wm8731_setup, | ||
262 | }; | ||
263 | |||
264 | static struct platform_device *at91sam9g20ek_snd_device; | ||
265 | |||
266 | static int __init at91sam9g20ek_init(void) | ||
267 | { | ||
268 | struct atmel_ssc_info *ssc_p = at91sam9g20ek_dai.cpu_dai->private_data; | ||
269 | struct ssc_device *ssc = NULL; | ||
270 | int ret; | ||
271 | |||
272 | /* | ||
273 | * Request SSC device | ||
274 | */ | ||
275 | ssc = ssc_request(0); | ||
276 | if (IS_ERR(ssc)) { | ||
277 | ret = PTR_ERR(ssc); | ||
278 | ssc = NULL; | ||
279 | goto err_ssc; | ||
280 | } | ||
281 | ssc_p->ssc = ssc; | ||
282 | |||
283 | at91sam9g20ek_snd_device = platform_device_alloc("soc-audio", -1); | ||
284 | if (!at91sam9g20ek_snd_device) { | ||
285 | printk(KERN_DEBUG | ||
286 | "platform device allocation failed\n"); | ||
287 | ret = -ENOMEM; | ||
288 | } | ||
289 | |||
290 | platform_set_drvdata(at91sam9g20ek_snd_device, | ||
291 | &at91sam9g20ek_snd_devdata); | ||
292 | at91sam9g20ek_snd_devdata.dev = &at91sam9g20ek_snd_device->dev; | ||
293 | |||
294 | ret = platform_device_add(at91sam9g20ek_snd_device); | ||
295 | if (ret) { | ||
296 | printk(KERN_DEBUG | ||
297 | "platform device allocation failed\n"); | ||
298 | platform_device_put(at91sam9g20ek_snd_device); | ||
299 | } | ||
300 | |||
301 | return ret; | ||
302 | |||
303 | err_ssc: | ||
304 | return ret; | ||
305 | } | ||
306 | |||
307 | static void __exit at91sam9g20ek_exit(void) | ||
308 | { | ||
309 | struct atmel_ssc_info *ssc_p = at91sam9g20ek_dai.cpu_dai->private_data; | ||
310 | struct ssc_device *ssc; | ||
311 | |||
312 | if (ssc_p != NULL) { | ||
313 | ssc = ssc_p->ssc; | ||
314 | if (ssc != NULL) | ||
315 | ssc_free(ssc); | ||
316 | ssc_p->ssc = NULL; | ||
317 | } | ||
318 | |||
319 | platform_device_unregister(at91sam9g20ek_snd_device); | ||
320 | at91sam9g20ek_snd_device = NULL; | ||
321 | } | ||
322 | |||
323 | module_init(at91sam9g20ek_init); | ||
324 | module_exit(at91sam9g20ek_exit); | ||
325 | |||
326 | /* Module information */ | ||
327 | MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>"); | ||
328 | MODULE_DESCRIPTION("ALSA SoC AT91SAM9G20EK_WM8731"); | ||
329 | MODULE_LICENSE("GPL"); | ||