diff options
Diffstat (limited to 'sound/soc/atmel/sam9g20_wm8731.c')
-rw-r--r-- | sound/soc/atmel/sam9g20_wm8731.c | 124 |
1 files changed, 111 insertions, 13 deletions
diff --git a/sound/soc/atmel/sam9g20_wm8731.c b/sound/soc/atmel/sam9g20_wm8731.c index 6ea04be911d0..173a239a541c 100644 --- a/sound/soc/atmel/sam9g20_wm8731.c +++ b/sound/soc/atmel/sam9g20_wm8731.c | |||
@@ -36,6 +36,7 @@ | |||
36 | #include <linux/timer.h> | 36 | #include <linux/timer.h> |
37 | #include <linux/interrupt.h> | 37 | #include <linux/interrupt.h> |
38 | #include <linux/platform_device.h> | 38 | #include <linux/platform_device.h> |
39 | #include <linux/i2c.h> | ||
39 | 40 | ||
40 | #include <linux/atmel-ssc.h> | 41 | #include <linux/atmel-ssc.h> |
41 | 42 | ||
@@ -45,6 +46,7 @@ | |||
45 | #include <sound/soc.h> | 46 | #include <sound/soc.h> |
46 | #include <sound/soc-dapm.h> | 47 | #include <sound/soc-dapm.h> |
47 | 48 | ||
49 | #include <asm/mach-types.h> | ||
48 | #include <mach/hardware.h> | 50 | #include <mach/hardware.h> |
49 | #include <mach/gpio.h> | 51 | #include <mach/gpio.h> |
50 | 52 | ||
@@ -52,6 +54,9 @@ | |||
52 | #include "atmel-pcm.h" | 54 | #include "atmel-pcm.h" |
53 | #include "atmel_ssc_dai.h" | 55 | #include "atmel_ssc_dai.h" |
54 | 56 | ||
57 | #define MCLK_RATE 12000000 | ||
58 | |||
59 | static struct clk *mclk; | ||
55 | 60 | ||
56 | static int at91sam9g20ek_startup(struct snd_pcm_substream *substream) | 61 | static int at91sam9g20ek_startup(struct snd_pcm_substream *substream) |
57 | { | 62 | { |
@@ -59,11 +64,12 @@ static int at91sam9g20ek_startup(struct snd_pcm_substream *substream) | |||
59 | struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; | 64 | struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; |
60 | int ret; | 65 | int ret; |
61 | 66 | ||
62 | /* codec system clock is supplied by PCK0, set to 12MHz */ | ||
63 | ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK, | 67 | ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK, |
64 | 12000000, SND_SOC_CLOCK_IN); | 68 | MCLK_RATE, SND_SOC_CLOCK_IN); |
65 | if (ret < 0) | 69 | if (ret < 0) { |
70 | clk_disable(mclk); | ||
66 | return ret; | 71 | return ret; |
72 | } | ||
67 | 73 | ||
68 | return 0; | 74 | return 0; |
69 | } | 75 | } |
@@ -189,6 +195,31 @@ static struct snd_soc_ops at91sam9g20ek_ops = { | |||
189 | .shutdown = at91sam9g20ek_shutdown, | 195 | .shutdown = at91sam9g20ek_shutdown, |
190 | }; | 196 | }; |
191 | 197 | ||
198 | static int at91sam9g20ek_set_bias_level(struct snd_soc_card *card, | ||
199 | enum snd_soc_bias_level level) | ||
200 | { | ||
201 | static int mclk_on; | ||
202 | int ret = 0; | ||
203 | |||
204 | switch (level) { | ||
205 | case SND_SOC_BIAS_ON: | ||
206 | case SND_SOC_BIAS_PREPARE: | ||
207 | if (!mclk_on) | ||
208 | ret = clk_enable(mclk); | ||
209 | if (ret == 0) | ||
210 | mclk_on = 1; | ||
211 | break; | ||
212 | |||
213 | case SND_SOC_BIAS_OFF: | ||
214 | case SND_SOC_BIAS_STANDBY: | ||
215 | if (mclk_on) | ||
216 | clk_disable(mclk); | ||
217 | mclk_on = 0; | ||
218 | break; | ||
219 | } | ||
220 | |||
221 | return ret; | ||
222 | } | ||
192 | 223 | ||
193 | static const struct snd_soc_dapm_widget at91sam9g20ek_dapm_widgets[] = { | 224 | static const struct snd_soc_dapm_widget at91sam9g20ek_dapm_widgets[] = { |
194 | SND_SOC_DAPM_MIC("Int Mic", NULL), | 225 | SND_SOC_DAPM_MIC("Int Mic", NULL), |
@@ -243,21 +274,48 @@ static struct snd_soc_dai_link at91sam9g20ek_dai = { | |||
243 | }; | 274 | }; |
244 | 275 | ||
245 | static struct snd_soc_card snd_soc_at91sam9g20ek = { | 276 | static struct snd_soc_card snd_soc_at91sam9g20ek = { |
246 | .name = "WM8731", | 277 | .name = "AT91SAMG20-EK", |
247 | .platform = &atmel_soc_platform, | 278 | .platform = &atmel_soc_platform, |
248 | .dai_link = &at91sam9g20ek_dai, | 279 | .dai_link = &at91sam9g20ek_dai, |
249 | .num_links = 1, | 280 | .num_links = 1, |
281 | .set_bias_level = at91sam9g20ek_set_bias_level, | ||
250 | }; | 282 | }; |
251 | 283 | ||
252 | static struct wm8731_setup_data at91sam9g20ek_wm8731_setup = { | 284 | /* |
253 | .i2c_bus = 0, | 285 | * FIXME: This is a temporary bodge to avoid cross-tree merge issues. |
254 | .i2c_address = 0x1b, | 286 | * New drivers should register the wm8731 I2C device in the machine |
255 | }; | 287 | * setup code (under arch/arm for ARM systems). |
288 | */ | ||
289 | static int wm8731_i2c_register(void) | ||
290 | { | ||
291 | struct i2c_board_info info; | ||
292 | struct i2c_adapter *adapter; | ||
293 | struct i2c_client *client; | ||
294 | |||
295 | memset(&info, 0, sizeof(struct i2c_board_info)); | ||
296 | info.addr = 0x1b; | ||
297 | strlcpy(info.type, "wm8731", I2C_NAME_SIZE); | ||
298 | |||
299 | adapter = i2c_get_adapter(0); | ||
300 | if (!adapter) { | ||
301 | printk(KERN_ERR "can't get i2c adapter 0\n"); | ||
302 | return -ENODEV; | ||
303 | } | ||
304 | |||
305 | client = i2c_new_device(adapter, &info); | ||
306 | i2c_put_adapter(adapter); | ||
307 | if (!client) { | ||
308 | printk(KERN_ERR "can't add i2c device at 0x%x\n", | ||
309 | (unsigned int)info.addr); | ||
310 | return -ENODEV; | ||
311 | } | ||
312 | |||
313 | return 0; | ||
314 | } | ||
256 | 315 | ||
257 | static struct snd_soc_device at91sam9g20ek_snd_devdata = { | 316 | static struct snd_soc_device at91sam9g20ek_snd_devdata = { |
258 | .card = &snd_soc_at91sam9g20ek, | 317 | .card = &snd_soc_at91sam9g20ek, |
259 | .codec_dev = &soc_codec_dev_wm8731, | 318 | .codec_dev = &soc_codec_dev_wm8731, |
260 | .codec_data = &at91sam9g20ek_wm8731_setup, | ||
261 | }; | 319 | }; |
262 | 320 | ||
263 | static struct platform_device *at91sam9g20ek_snd_device; | 321 | static struct platform_device *at91sam9g20ek_snd_device; |
@@ -266,23 +324,56 @@ static int __init at91sam9g20ek_init(void) | |||
266 | { | 324 | { |
267 | struct atmel_ssc_info *ssc_p = at91sam9g20ek_dai.cpu_dai->private_data; | 325 | struct atmel_ssc_info *ssc_p = at91sam9g20ek_dai.cpu_dai->private_data; |
268 | struct ssc_device *ssc = NULL; | 326 | struct ssc_device *ssc = NULL; |
327 | struct clk *pllb; | ||
269 | int ret; | 328 | int ret; |
270 | 329 | ||
330 | if (!machine_is_at91sam9g20ek()) | ||
331 | return -ENODEV; | ||
332 | |||
333 | /* | ||
334 | * Codec MCLK is supplied by PCK0 - set it up. | ||
335 | */ | ||
336 | mclk = clk_get(NULL, "pck0"); | ||
337 | if (IS_ERR(mclk)) { | ||
338 | printk(KERN_ERR "ASoC: Failed to get MCLK\n"); | ||
339 | ret = PTR_ERR(mclk); | ||
340 | goto err; | ||
341 | } | ||
342 | |||
343 | pllb = clk_get(NULL, "pllb"); | ||
344 | if (IS_ERR(mclk)) { | ||
345 | printk(KERN_ERR "ASoC: Failed to get PLLB\n"); | ||
346 | ret = PTR_ERR(mclk); | ||
347 | goto err_mclk; | ||
348 | } | ||
349 | ret = clk_set_parent(mclk, pllb); | ||
350 | clk_put(pllb); | ||
351 | if (ret != 0) { | ||
352 | printk(KERN_ERR "ASoC: Failed to set MCLK parent\n"); | ||
353 | goto err_mclk; | ||
354 | } | ||
355 | |||
356 | clk_set_rate(mclk, MCLK_RATE); | ||
357 | |||
271 | /* | 358 | /* |
272 | * Request SSC device | 359 | * Request SSC device |
273 | */ | 360 | */ |
274 | ssc = ssc_request(0); | 361 | ssc = ssc_request(0); |
275 | if (IS_ERR(ssc)) { | 362 | if (IS_ERR(ssc)) { |
363 | printk(KERN_ERR "ASoC: Failed to request SSC 0\n"); | ||
276 | ret = PTR_ERR(ssc); | 364 | ret = PTR_ERR(ssc); |
277 | ssc = NULL; | 365 | ssc = NULL; |
278 | goto err_ssc; | 366 | goto err_ssc; |
279 | } | 367 | } |
280 | ssc_p->ssc = ssc; | 368 | ssc_p->ssc = ssc; |
281 | 369 | ||
370 | ret = wm8731_i2c_register(); | ||
371 | if (ret != 0) | ||
372 | goto err_ssc; | ||
373 | |||
282 | at91sam9g20ek_snd_device = platform_device_alloc("soc-audio", -1); | 374 | at91sam9g20ek_snd_device = platform_device_alloc("soc-audio", -1); |
283 | if (!at91sam9g20ek_snd_device) { | 375 | if (!at91sam9g20ek_snd_device) { |
284 | printk(KERN_DEBUG | 376 | printk(KERN_ERR "ASoC: Platform device allocation failed\n"); |
285 | "platform device allocation failed\n"); | ||
286 | ret = -ENOMEM; | 377 | ret = -ENOMEM; |
287 | } | 378 | } |
288 | 379 | ||
@@ -292,14 +383,19 @@ static int __init at91sam9g20ek_init(void) | |||
292 | 383 | ||
293 | ret = platform_device_add(at91sam9g20ek_snd_device); | 384 | ret = platform_device_add(at91sam9g20ek_snd_device); |
294 | if (ret) { | 385 | if (ret) { |
295 | printk(KERN_DEBUG | 386 | printk(KERN_ERR "ASoC: Platform device allocation failed\n"); |
296 | "platform device allocation failed\n"); | ||
297 | platform_device_put(at91sam9g20ek_snd_device); | 387 | platform_device_put(at91sam9g20ek_snd_device); |
298 | } | 388 | } |
299 | 389 | ||
300 | return ret; | 390 | return ret; |
301 | 391 | ||
302 | err_ssc: | 392 | err_ssc: |
393 | ssc_free(ssc); | ||
394 | ssc_p->ssc = NULL; | ||
395 | err_mclk: | ||
396 | clk_put(mclk); | ||
397 | mclk = NULL; | ||
398 | err: | ||
303 | return ret; | 399 | return ret; |
304 | } | 400 | } |
305 | 401 | ||
@@ -317,6 +413,8 @@ static void __exit at91sam9g20ek_exit(void) | |||
317 | 413 | ||
318 | platform_device_unregister(at91sam9g20ek_snd_device); | 414 | platform_device_unregister(at91sam9g20ek_snd_device); |
319 | at91sam9g20ek_snd_device = NULL; | 415 | at91sam9g20ek_snd_device = NULL; |
416 | clk_put(mclk); | ||
417 | mclk = NULL; | ||
320 | } | 418 | } |
321 | 419 | ||
322 | module_init(at91sam9g20ek_init); | 420 | module_init(at91sam9g20ek_init); |