aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/codecs/wm8711.c
diff options
context:
space:
mode:
authorMike Arthur <Mike.Arthur@wolfsonmicro.com>2009-08-18 15:37:49 -0400
committerMark Brown <broonie@opensource.wolfsonmicro.com>2009-08-18 15:37:49 -0400
commitbd6d417743d941c3e5eabb21abbcac9737f11061 (patch)
tree46e3c73ff2f845e461df9b39aa529f25b56253de /sound/soc/codecs/wm8711.c
parent07a2039b8eb0af4ff464efd3dfd95de5c02648c6 (diff)
ASoC: Add WM8711 CODEC driver
The WM8711 or WM8711L (WM8711/L) is a low power stereo DAC with an integrated headphone driver. The WM8711/L is designed specifically for portable MP3 audio and speech players. The WM8711/L is also ideal for MD, CD machines and DAT players. Signed-off-by: Mike Arthur <Mike.Arthur@wolfsonmicro.com> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound/soc/codecs/wm8711.c')
-rw-r--r--sound/soc/codecs/wm8711.c685
1 files changed, 685 insertions, 0 deletions
diff --git a/sound/soc/codecs/wm8711.c b/sound/soc/codecs/wm8711.c
new file mode 100644
index 000000000000..84ead3f9293f
--- /dev/null
+++ b/sound/soc/codecs/wm8711.c
@@ -0,0 +1,685 @@
1/*
2 * wm8711.c -- WM8711 ALSA SoC Audio driver
3 *
4 * Copyright 2006 Wolfson Microelectronics
5 *
6 * Author: Mike Arthur <linux@wolfsonmicro.com>
7 *
8 * Based on wm8731.c by Richard Purdie
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2 as
12 * published by the Free Software Foundation.
13 */
14
15#include <linux/module.h>
16#include <linux/moduleparam.h>
17#include <linux/init.h>
18#include <linux/delay.h>
19#include <linux/pm.h>
20#include <linux/i2c.h>
21#include <linux/platform_device.h>
22#include <sound/core.h>
23#include <sound/pcm.h>
24#include <sound/pcm_params.h>
25#include <sound/soc.h>
26#include <sound/soc-dapm.h>
27#include <sound/initval.h>
28
29#include "wm8711.h"
30
31#define AUDIO_NAME "wm8711"
32#define WM8711_VERSION "0.3"
33
34/* codec private data */
35struct wm8711_priv {
36 unsigned int sysclk;
37};
38
39/*
40 * wm8711 register cache
41 * We can't read the WM8711 register space when we are
42 * using 2 wire for device control, so we cache them instead.
43 * There is no point in caching the reset register
44 */
45static const u16 wm8711_reg[WM8711_CACHEREGNUM] = {
46 0x0079, 0x0079, 0x000a, 0x0008,
47 0x009f, 0x000a, 0x0000, 0x0000
48};
49
50/*
51 * read wm8711 register cache
52 */
53static inline unsigned int wm8711_read_reg_cache(struct snd_soc_codec *codec,
54 unsigned int reg)
55{
56 u16 *cache = codec->reg_cache;
57 if (reg == WM8711_RESET)
58 return 0;
59 if (reg >= WM8711_CACHEREGNUM)
60 return -1;
61 return cache[reg];
62}
63
64/*
65 * write wm8711 register cache
66 */
67static inline void wm8711_write_reg_cache(struct snd_soc_codec *codec,
68 u16 reg, unsigned int value)
69{
70 u16 *cache = codec->reg_cache;
71 if (reg >= WM8711_CACHEREGNUM)
72 return;
73 cache[reg] = value;
74}
75
76/*
77 * write to the WM8711 register space
78 */
79static int wm8711_write(struct snd_soc_codec *codec, unsigned int reg,
80 unsigned int value)
81{
82 u8 data[2];
83
84 /* data is
85 * D15..D9 WM8753 register offset
86 * D8...D0 register data
87 */
88 data[0] = (reg << 1) | ((value >> 8) & 0x0001);
89 data[1] = value & 0x00ff;
90
91 wm8711_write_reg_cache(codec, reg, value);
92 if (codec->hw_write(codec->control_data, data, 2) == 2)
93 return 0;
94 else
95 return -EIO;
96}
97
98#define wm8711_reset(c) wm8711_write(c, WM8711_RESET, 0)
99
100static const struct snd_kcontrol_new wm8711_snd_controls[] = {
101
102SOC_DOUBLE_R("Master Playback Volume", WM8711_LOUT1V, WM8711_ROUT1V,
103 0, 127, 0),
104SOC_DOUBLE_R("Master Playback ZC Switch", WM8711_LOUT1V, WM8711_ROUT1V,
105 7, 1, 0),
106
107};
108
109/* add non dapm controls */
110static int wm8711_add_controls(struct snd_soc_codec *codec)
111{
112 int err, i;
113
114 for (i = 0; i < ARRAY_SIZE(wm8711_snd_controls); i++) {
115 err = snd_ctl_add(codec->card,
116 snd_soc_cnew(&wm8711_snd_controls[i], codec,
117 NULL));
118 if (err < 0)
119 return err;
120 }
121
122 return 0;
123}
124
125/* Output Mixer */
126static const struct snd_kcontrol_new wm8711_output_mixer_controls[] = {
127SOC_DAPM_SINGLE("Line Bypass Switch", WM8711_APANA, 3, 1, 0),
128SOC_DAPM_SINGLE("HiFi Playback Switch", WM8711_APANA, 4, 1, 0),
129};
130
131static const struct snd_soc_dapm_widget wm8711_dapm_widgets[] = {
132SND_SOC_DAPM_MIXER("Output Mixer", WM8711_PWR, 4, 1,
133 &wm8711_output_mixer_controls[0],
134 ARRAY_SIZE(wm8711_output_mixer_controls)),
135SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8711_PWR, 3, 1),
136SND_SOC_DAPM_OUTPUT("LOUT"),
137SND_SOC_DAPM_OUTPUT("LHPOUT"),
138SND_SOC_DAPM_OUTPUT("ROUT"),
139SND_SOC_DAPM_OUTPUT("RHPOUT"),
140};
141
142static const struct snd_soc_dapm_route intercon[] = {
143 /* output mixer */
144 {"Output Mixer", "Line Bypass Switch", "Line Input"},
145 {"Output Mixer", "HiFi Playback Switch", "DAC"},
146
147 /* outputs */
148 {"RHPOUT", NULL, "Output Mixer"},
149 {"ROUT", NULL, "Output Mixer"},
150 {"LHPOUT", NULL, "Output Mixer"},
151 {"LOUT", NULL, "Output Mixer"},
152};
153
154static int wm8711_add_widgets(struct snd_soc_codec *codec)
155{
156 snd_soc_dapm_new_controls(codec, wm8711_dapm_widgets,
157 ARRAY_SIZE(wm8711_dapm_widgets));
158
159 snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
160
161 snd_soc_dapm_new_widgets(codec);
162 return 0;
163}
164
165struct _coeff_div {
166 u32 mclk;
167 u32 rate;
168 u16 fs;
169 u8 sr:4;
170 u8 bosr:1;
171 u8 usb:1;
172};
173
174/* codec mclk clock divider coefficients */
175static const struct _coeff_div coeff_div[] = {
176 /* 48k */
177 {12288000, 48000, 256, 0x0, 0x0, 0x0},
178 {18432000, 48000, 384, 0x0, 0x1, 0x0},
179 {12000000, 48000, 250, 0x0, 0x0, 0x1},
180
181 /* 32k */
182 {12288000, 32000, 384, 0x6, 0x0, 0x0},
183 {18432000, 32000, 576, 0x6, 0x1, 0x0},
184 {12000000, 32000, 375, 0x6, 0x0, 0x1},
185
186 /* 8k */
187 {12288000, 8000, 1536, 0x3, 0x0, 0x0},
188 {18432000, 8000, 2304, 0x3, 0x1, 0x0},
189 {11289600, 8000, 1408, 0xb, 0x0, 0x0},
190 {16934400, 8000, 2112, 0xb, 0x1, 0x0},
191 {12000000, 8000, 1500, 0x3, 0x0, 0x1},
192
193 /* 96k */
194 {12288000, 96000, 128, 0x7, 0x0, 0x0},
195 {18432000, 96000, 192, 0x7, 0x1, 0x0},
196 {12000000, 96000, 125, 0x7, 0x0, 0x1},
197
198 /* 44.1k */
199 {11289600, 44100, 256, 0x8, 0x0, 0x0},
200 {16934400, 44100, 384, 0x8, 0x1, 0x0},
201 {12000000, 44100, 272, 0x8, 0x1, 0x1},
202
203 /* 88.2k */
204 {11289600, 88200, 128, 0xf, 0x0, 0x0},
205 {16934400, 88200, 192, 0xf, 0x1, 0x0},
206 {12000000, 88200, 136, 0xf, 0x1, 0x1},
207};
208
209static inline int get_coeff(int mclk, int rate)
210{
211 int i;
212
213 for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
214 if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
215 return i;
216 }
217 return 0;
218}
219
220static int wm8711_hw_params(struct snd_pcm_substream *substream,
221 struct snd_pcm_hw_params *params,
222 struct snd_soc_dai *dai)
223{
224 struct snd_soc_codec *codec = dai->codec;
225 struct wm8711_priv *wm8711 = codec->private_data;
226 u16 iface = wm8711_read_reg_cache(codec, WM8711_IFACE) & 0xfffc;
227 int i = get_coeff(wm8711->sysclk, params_rate(params));
228 u16 srate = (coeff_div[i].sr << 2) |
229 (coeff_div[i].bosr << 1) | coeff_div[i].usb;
230
231 wm8711_write(codec, WM8711_SRATE, srate);
232
233 /* bit size */
234 switch (params_format(params)) {
235 case SNDRV_PCM_FORMAT_S16_LE:
236 break;
237 case SNDRV_PCM_FORMAT_S20_3LE:
238 iface |= 0x0004;
239 break;
240 case SNDRV_PCM_FORMAT_S24_LE:
241 iface |= 0x0008;
242 break;
243 }
244
245 wm8711_write(codec, WM8711_IFACE, iface);
246 return 0;
247}
248
249static int wm8711_pcm_prepare(struct snd_pcm_substream *substream,
250 struct snd_soc_dai *dai)
251{
252 struct snd_soc_codec *codec = dai->codec;
253
254 /* set active */
255 wm8711_write(codec, WM8711_ACTIVE, 0x0001);
256
257 return 0;
258}
259
260static void wm8711_shutdown(struct snd_pcm_substream *substream,
261 struct snd_soc_dai *dai)
262{
263 struct snd_soc_codec *codec = dai->codec;
264
265 /* deactivate */
266 if (!codec->active) {
267 udelay(50);
268 wm8711_write(codec, WM8711_ACTIVE, 0x0);
269 }
270}
271
272static int wm8711_mute(struct snd_soc_dai *dai, int mute)
273{
274 struct snd_soc_codec *codec = dai->codec;
275 u16 mute_reg = wm8711_read_reg_cache(codec, WM8711_APDIGI) & 0xfff7;
276
277 if (mute)
278 wm8711_write(codec, WM8711_APDIGI, mute_reg | 0x8);
279 else
280 wm8711_write(codec, WM8711_APDIGI, mute_reg);
281
282 return 0;
283}
284
285static int wm8711_set_dai_sysclk(struct snd_soc_dai *codec_dai,
286 int clk_id, unsigned int freq, int dir)
287{
288 struct snd_soc_codec *codec = codec_dai->codec;
289 struct wm8711_priv *wm8711 = codec->private_data;
290
291 switch (freq) {
292 case 11289600:
293 case 12000000:
294 case 12288000:
295 case 16934400:
296 case 18432000:
297 wm8711->sysclk = freq;
298 return 0;
299 }
300 return -EINVAL;
301}
302
303static int wm8711_set_dai_fmt(struct snd_soc_dai *codec_dai,
304 unsigned int fmt)
305{
306 struct snd_soc_codec *codec = codec_dai->codec;
307 u16 iface = 0;
308
309 /* set master/slave audio interface */
310 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
311 case SND_SOC_DAIFMT_CBM_CFM:
312 iface |= 0x0040;
313 break;
314 case SND_SOC_DAIFMT_CBS_CFS:
315 break;
316 default:
317 return -EINVAL;
318 }
319
320 /* interface format */
321 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
322 case SND_SOC_DAIFMT_I2S:
323 iface |= 0x0002;
324 break;
325 case SND_SOC_DAIFMT_RIGHT_J:
326 break;
327 case SND_SOC_DAIFMT_LEFT_J:
328 iface |= 0x0001;
329 break;
330 case SND_SOC_DAIFMT_DSP_A:
331 iface |= 0x0003;
332 break;
333 case SND_SOC_DAIFMT_DSP_B:
334 iface |= 0x0013;
335 break;
336 default:
337 return -EINVAL;
338 }
339
340 /* clock inversion */
341 switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
342 case SND_SOC_DAIFMT_NB_NF:
343 break;
344 case SND_SOC_DAIFMT_IB_IF:
345 iface |= 0x0090;
346 break;
347 case SND_SOC_DAIFMT_IB_NF:
348 iface |= 0x0080;
349 break;
350 case SND_SOC_DAIFMT_NB_IF:
351 iface |= 0x0010;
352 break;
353 default:
354 return -EINVAL;
355 }
356
357 /* set iface */
358 wm8711_write(codec, WM8711_IFACE, iface);
359 return 0;
360}
361
362
363static int wm8711_set_bias_level(struct snd_soc_codec *codec,
364 enum snd_soc_bias_level level)
365{
366 u16 reg = wm8711_read_reg_cache(codec, WM8711_PWR) & 0xff7f;
367
368 switch (level) {
369 case SND_SOC_BIAS_ON:
370 wm8711_write(codec, WM8711_PWR, reg);
371 break;
372 case SND_SOC_BIAS_PREPARE:
373 break;
374 case SND_SOC_BIAS_STANDBY:
375 wm8711_write(codec, WM8711_PWR, reg | 0x0040);
376 break;
377 case SND_SOC_BIAS_OFF:
378 wm8711_write(codec, WM8711_ACTIVE, 0x0);
379 wm8711_write(codec, WM8711_PWR, 0xffff);
380 break;
381 }
382 codec->bias_level = level;
383 return 0;
384}
385
386#define WM8711_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
387 SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
388 SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
389 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\
390 SNDRV_PCM_RATE_96000)
391
392#define WM8711_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
393 SNDRV_PCM_FMTBIT_S24_LE)
394
395static struct snd_soc_dai_ops wm8711_ops = {
396 .prepare = wm8711_pcm_prepare,
397 .hw_params = wm8711_hw_params,
398 .shutdown = wm8711_shutdown,
399 .digital_mute = wm8711_mute,
400 .set_sysclk = wm8711_set_dai_sysclk,
401 .set_fmt = wm8711_set_dai_fmt,
402};
403
404struct snd_soc_dai wm8711_dai = {
405 .name = "WM8711",
406 .playback = {
407 .stream_name = "Playback",
408 .channels_min = 1,
409 .channels_max = 2,
410 .rates = WM8711_RATES,
411 .formats = WM8711_FORMATS,},
412 .ops = &wm8711_ops,
413};
414EXPORT_SYMBOL_GPL(wm8711_dai);
415
416static int wm8711_suspend(struct platform_device *pdev, pm_message_t state)
417{
418 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
419 struct snd_soc_codec *codec = socdev->card->codec;
420
421 wm8711_write(codec, WM8711_ACTIVE, 0x0);
422 wm8711_set_bias_level(codec, SND_SOC_BIAS_OFF);
423 return 0;
424}
425
426static int wm8711_resume(struct platform_device *pdev)
427{
428 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
429 struct snd_soc_codec *codec = socdev->card->codec;
430 int i;
431 u8 data[2];
432 u16 *cache = codec->reg_cache;
433
434 /* Sync reg_cache with the hardware */
435 for (i = 0; i < ARRAY_SIZE(wm8711_reg); i++) {
436 data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
437 data[1] = cache[i] & 0x00ff;
438 codec->hw_write(codec->control_data, data, 2);
439 }
440 wm8711_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
441 wm8711_set_bias_level(codec, codec->suspend_bias_level);
442 return 0;
443}
444
445/*
446 * initialise the WM8711 driver
447 * register the mixer and dsp interfaces with the kernel
448 */
449static int wm8711_init(struct snd_soc_device *socdev)
450{
451 struct snd_soc_codec *codec = socdev->card->codec;
452 int reg, ret = 0;
453
454 codec->name = "WM8711";
455 codec->owner = THIS_MODULE;
456 codec->read = wm8711_read_reg_cache;
457 codec->write = wm8711_write;
458 codec->set_bias_level = wm8711_set_bias_level;
459 codec->dai = &wm8711_dai;
460 codec->num_dai = 1;
461 codec->reg_cache_size = ARRAY_SIZE(wm8711_reg);
462 codec->reg_cache = kmemdup(wm8711_reg, sizeof(wm8711_reg), GFP_KERNEL);
463
464 if (codec->reg_cache == NULL)
465 return -ENOMEM;
466
467 wm8711_reset(codec);
468
469 /* register pcms */
470 ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
471 if (ret < 0) {
472 printk(KERN_ERR "wm8711: failed to create pcms\n");
473 goto pcm_err;
474 }
475
476 /* power on device */
477 wm8711_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
478
479 /* set the update bits */
480 reg = wm8711_read_reg_cache(codec, WM8711_LOUT1V);
481 wm8711_write(codec, WM8711_LOUT1V, reg | 0x0100);
482 reg = wm8711_read_reg_cache(codec, WM8711_ROUT1V);
483 wm8711_write(codec, WM8711_ROUT1V, reg | 0x0100);
484
485 wm8711_add_controls(codec);
486 wm8711_add_widgets(codec);
487 ret = snd_soc_init_card(socdev);
488 if (ret < 0) {
489 printk(KERN_ERR "wm8711: failed to register card\n");
490 goto card_err;
491 }
492 return ret;
493
494card_err:
495 snd_soc_free_pcms(socdev);
496 snd_soc_dapm_free(socdev);
497pcm_err:
498 kfree(codec->reg_cache);
499 return ret;
500}
501
502static struct snd_soc_device *wm8711_socdev;
503
504#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
505
506/*
507 * WM8711 2 wire address is determined by GPIO5
508 * state during powerup.
509 * low = 0x1a
510 * high = 0x1b
511 */
512#define I2C_DRIVERID_WM8711 0xfefe /* liam - need a proper id */
513
514static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END };
515
516/* Magic definition of all other variables and things */
517I2C_CLIENT_INSMOD;
518
519static struct i2c_driver wm8711_i2c_driver;
520static struct i2c_client client_template;
521
522/* If the i2c layer weren't so broken, we could pass this kind of data
523 around */
524
525static int wm8711_codec_probe(struct i2c_adapter *adap, int addr, int kind)
526{
527 struct snd_soc_device *socdev = wm8711_socdev;
528 struct wm8711_setup_data *setup = socdev->codec_data;
529 struct snd_soc_codec *codec = socdev->card->codec;
530 struct i2c_client *i2c;
531 int ret;
532
533 if (addr != setup->i2c_address)
534 return -ENODEV;
535
536 client_template.adapter = adap;
537 client_template.addr = addr;
538
539 i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL);
540 if (i2c == NULL) {
541 kfree(codec);
542 return -ENOMEM;
543 }
544
545 i2c_set_clientdata(i2c, codec);
546
547 codec->control_data = i2c;
548
549 ret = i2c_attach_client(i2c);
550 if (ret < 0) {
551 pr_err("failed to attach codec at addr %x\n", addr);
552 goto err;
553 }
554
555 ret = wm8711_init(socdev);
556 if (ret < 0) {
557 pr_err("failed to initialise WM8711\n");
558 goto err;
559 }
560 return ret;
561
562err:
563 kfree(codec);
564 kfree(i2c);
565 return ret;
566}
567
568static int wm8711_i2c_detach(struct i2c_client *client)
569{
570 struct snd_soc_codec *codec = i2c_get_clientdata(client);
571
572 i2c_detach_client(client);
573 kfree(codec->reg_cache);
574 kfree(client);
575 return 0;
576}
577
578static int wm8711_i2c_attach(struct i2c_adapter *adap)
579{
580 return i2c_probe(adap, &addr_data, wm8711_codec_probe);
581}
582
583/* corgi i2c codec control layer */
584static struct i2c_driver wm8711_i2c_driver = {
585 .driver = {
586 .name = "WM8711 I2C Codec",
587 .owner = THIS_MODULE,
588 },
589 .id = I2C_DRIVERID_WM8711,
590 .attach_adapter = wm8711_i2c_attach,
591 .detach_client = wm8711_i2c_detach,
592 .command = NULL,
593};
594
595static struct i2c_client client_template = {
596 .name = "WM8711",
597 .driver = &wm8711_i2c_driver,
598};
599#endif
600
601static int wm8711_probe(struct platform_device *pdev)
602{
603 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
604 struct wm8711_setup_data *setup;
605 struct snd_soc_codec *codec;
606 struct wm8711_priv *wm8711;
607 int ret = 0;
608
609 pr_info("WM8711 Audio Codec %s", WM8711_VERSION);
610
611 setup = socdev->codec_data;
612 codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
613 if (codec == NULL)
614 return -ENOMEM;
615
616 wm8711 = kzalloc(sizeof(struct wm8711_priv), GFP_KERNEL);
617 if (wm8711 == NULL) {
618 kfree(codec);
619 return -ENOMEM;
620 }
621
622 codec->private_data = wm8711;
623 socdev->card->codec = codec;
624 mutex_init(&codec->mutex);
625 INIT_LIST_HEAD(&codec->dapm_widgets);
626 INIT_LIST_HEAD(&codec->dapm_paths);
627
628 wm8711_socdev = socdev;
629#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
630 if (setup->i2c_address) {
631 normal_i2c[0] = setup->i2c_address;
632 codec->hw_write = (hw_write_t)i2c_master_send;
633 ret = i2c_add_driver(&wm8711_i2c_driver);
634 if (ret != 0)
635 printk(KERN_ERR "can't add i2c driver");
636 }
637#else
638 /* Add other interfaces here */
639#endif
640 return ret;
641}
642
643/* power down chip */
644static int wm8711_remove(struct platform_device *pdev)
645{
646 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
647 struct snd_soc_codec *codec = socdev->card->codec;
648
649 if (codec->control_data)
650 wm8711_set_bias_level(codec, SND_SOC_BIAS_OFF);
651
652 snd_soc_free_pcms(socdev);
653 snd_soc_dapm_free(socdev);
654#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
655 i2c_del_driver(&wm8711_i2c_driver);
656#endif
657 kfree(codec->private_data);
658 kfree(codec);
659
660 return 0;
661}
662
663struct snd_soc_codec_device soc_codec_dev_wm8711 = {
664 .probe = wm8711_probe,
665 .remove = wm8711_remove,
666 .suspend = wm8711_suspend,
667 .resume = wm8711_resume,
668};
669EXPORT_SYMBOL_GPL(soc_codec_dev_wm8711);
670
671static int __init wm8711_modinit(void)
672{
673 return snd_soc_register_dai(&wm8711_dai);
674}
675module_init(wm8711_modinit);
676
677static void __exit wm8711_exit(void)
678{
679 snd_soc_unregister_dai(&wm8711_dai);
680}
681module_exit(wm8711_exit);
682
683MODULE_DESCRIPTION("ASoC WM8711 driver");
684MODULE_AUTHOR("Mike Arthur");
685MODULE_LICENSE("GPL");