diff options
Diffstat (limited to 'sound/soc/codecs/wm8728.c')
-rw-r--r-- | sound/soc/codecs/wm8728.c | 585 |
1 files changed, 585 insertions, 0 deletions
diff --git a/sound/soc/codecs/wm8728.c b/sound/soc/codecs/wm8728.c new file mode 100644 index 000000000000..80b11983e137 --- /dev/null +++ b/sound/soc/codecs/wm8728.c | |||
@@ -0,0 +1,585 @@ | |||
1 | /* | ||
2 | * wm8728.c -- WM8728 ALSA SoC Audio driver | ||
3 | * | ||
4 | * Copyright 2008 Wolfson Microelectronics plc | ||
5 | * | ||
6 | * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License version 2 as | ||
10 | * published by the Free Software Foundation. | ||
11 | */ | ||
12 | |||
13 | #include <linux/module.h> | ||
14 | #include <linux/moduleparam.h> | ||
15 | #include <linux/init.h> | ||
16 | #include <linux/delay.h> | ||
17 | #include <linux/pm.h> | ||
18 | #include <linux/i2c.h> | ||
19 | #include <linux/platform_device.h> | ||
20 | #include <linux/spi/spi.h> | ||
21 | #include <sound/core.h> | ||
22 | #include <sound/pcm.h> | ||
23 | #include <sound/pcm_params.h> | ||
24 | #include <sound/soc.h> | ||
25 | #include <sound/soc-dapm.h> | ||
26 | #include <sound/initval.h> | ||
27 | #include <sound/tlv.h> | ||
28 | |||
29 | #include "wm8728.h" | ||
30 | |||
31 | struct snd_soc_codec_device soc_codec_dev_wm8728; | ||
32 | |||
33 | /* | ||
34 | * We can't read the WM8728 register space so we cache them instead. | ||
35 | * Note that the defaults here aren't the physical defaults, we latch | ||
36 | * the volume update bits, mute the output and enable infinite zero | ||
37 | * detect. | ||
38 | */ | ||
39 | static const u16 wm8728_reg_defaults[] = { | ||
40 | 0x1ff, | ||
41 | 0x1ff, | ||
42 | 0x001, | ||
43 | 0x100, | ||
44 | }; | ||
45 | |||
46 | static inline unsigned int wm8728_read_reg_cache(struct snd_soc_codec *codec, | ||
47 | unsigned int reg) | ||
48 | { | ||
49 | u16 *cache = codec->reg_cache; | ||
50 | BUG_ON(reg > ARRAY_SIZE(wm8728_reg_defaults)); | ||
51 | return cache[reg]; | ||
52 | } | ||
53 | |||
54 | static inline void wm8728_write_reg_cache(struct snd_soc_codec *codec, | ||
55 | u16 reg, unsigned int value) | ||
56 | { | ||
57 | u16 *cache = codec->reg_cache; | ||
58 | BUG_ON(reg > ARRAY_SIZE(wm8728_reg_defaults)); | ||
59 | cache[reg] = value; | ||
60 | } | ||
61 | |||
62 | /* | ||
63 | * write to the WM8728 register space | ||
64 | */ | ||
65 | static int wm8728_write(struct snd_soc_codec *codec, unsigned int reg, | ||
66 | unsigned int value) | ||
67 | { | ||
68 | u8 data[2]; | ||
69 | |||
70 | /* data is | ||
71 | * D15..D9 WM8728 register offset | ||
72 | * D8...D0 register data | ||
73 | */ | ||
74 | data[0] = (reg << 1) | ((value >> 8) & 0x0001); | ||
75 | data[1] = value & 0x00ff; | ||
76 | |||
77 | wm8728_write_reg_cache(codec, reg, value); | ||
78 | |||
79 | if (codec->hw_write(codec->control_data, data, 2) == 2) | ||
80 | return 0; | ||
81 | else | ||
82 | return -EIO; | ||
83 | } | ||
84 | |||
85 | static const DECLARE_TLV_DB_SCALE(wm8728_tlv, -12750, 50, 1); | ||
86 | |||
87 | static const struct snd_kcontrol_new wm8728_snd_controls[] = { | ||
88 | |||
89 | SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8728_DACLVOL, WM8728_DACRVOL, | ||
90 | 0, 255, 0, wm8728_tlv), | ||
91 | |||
92 | SOC_SINGLE("Deemphasis", WM8728_DACCTL, 1, 1, 0), | ||
93 | }; | ||
94 | |||
95 | static int wm8728_add_controls(struct snd_soc_codec *codec) | ||
96 | { | ||
97 | int err, i; | ||
98 | |||
99 | for (i = 0; i < ARRAY_SIZE(wm8728_snd_controls); i++) { | ||
100 | err = snd_ctl_add(codec->card, | ||
101 | snd_soc_cnew(&wm8728_snd_controls[i], | ||
102 | codec, NULL)); | ||
103 | if (err < 0) | ||
104 | return err; | ||
105 | } | ||
106 | |||
107 | return 0; | ||
108 | } | ||
109 | |||
110 | /* | ||
111 | * DAPM controls. | ||
112 | */ | ||
113 | static const struct snd_soc_dapm_widget wm8728_dapm_widgets[] = { | ||
114 | SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SND_SOC_NOPM, 0, 0), | ||
115 | SND_SOC_DAPM_OUTPUT("VOUTL"), | ||
116 | SND_SOC_DAPM_OUTPUT("VOUTR"), | ||
117 | }; | ||
118 | |||
119 | static const struct snd_soc_dapm_route intercon[] = { | ||
120 | {"VOUTL", NULL, "DAC"}, | ||
121 | {"VOUTR", NULL, "DAC"}, | ||
122 | }; | ||
123 | |||
124 | static int wm8728_add_widgets(struct snd_soc_codec *codec) | ||
125 | { | ||
126 | snd_soc_dapm_new_controls(codec, wm8728_dapm_widgets, | ||
127 | ARRAY_SIZE(wm8728_dapm_widgets)); | ||
128 | |||
129 | snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); | ||
130 | |||
131 | snd_soc_dapm_new_widgets(codec); | ||
132 | |||
133 | return 0; | ||
134 | } | ||
135 | |||
136 | static int wm8728_mute(struct snd_soc_dai *dai, int mute) | ||
137 | { | ||
138 | struct snd_soc_codec *codec = dai->codec; | ||
139 | u16 mute_reg = wm8728_read_reg_cache(codec, WM8728_DACCTL); | ||
140 | |||
141 | if (mute) | ||
142 | wm8728_write(codec, WM8728_DACCTL, mute_reg | 1); | ||
143 | else | ||
144 | wm8728_write(codec, WM8728_DACCTL, mute_reg & ~1); | ||
145 | |||
146 | return 0; | ||
147 | } | ||
148 | |||
149 | static int wm8728_hw_params(struct snd_pcm_substream *substream, | ||
150 | struct snd_pcm_hw_params *params, | ||
151 | struct snd_soc_dai *dai) | ||
152 | { | ||
153 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
154 | struct snd_soc_device *socdev = rtd->socdev; | ||
155 | struct snd_soc_codec *codec = socdev->codec; | ||
156 | u16 dac = wm8728_read_reg_cache(codec, WM8728_DACCTL); | ||
157 | |||
158 | dac &= ~0x18; | ||
159 | |||
160 | switch (params_format(params)) { | ||
161 | case SNDRV_PCM_FORMAT_S16_LE: | ||
162 | break; | ||
163 | case SNDRV_PCM_FORMAT_S20_3LE: | ||
164 | dac |= 0x10; | ||
165 | break; | ||
166 | case SNDRV_PCM_FORMAT_S24_LE: | ||
167 | dac |= 0x08; | ||
168 | break; | ||
169 | default: | ||
170 | return -EINVAL; | ||
171 | } | ||
172 | |||
173 | wm8728_write(codec, WM8728_DACCTL, dac); | ||
174 | |||
175 | return 0; | ||
176 | } | ||
177 | |||
178 | static int wm8728_set_dai_fmt(struct snd_soc_dai *codec_dai, | ||
179 | unsigned int fmt) | ||
180 | { | ||
181 | struct snd_soc_codec *codec = codec_dai->codec; | ||
182 | u16 iface = wm8728_read_reg_cache(codec, WM8728_IFCTL); | ||
183 | |||
184 | /* Currently only I2S is supported by the driver, though the | ||
185 | * hardware is more flexible. | ||
186 | */ | ||
187 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | ||
188 | case SND_SOC_DAIFMT_I2S: | ||
189 | iface |= 1; | ||
190 | break; | ||
191 | default: | ||
192 | return -EINVAL; | ||
193 | } | ||
194 | |||
195 | /* The hardware only support full slave mode */ | ||
196 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { | ||
197 | case SND_SOC_DAIFMT_CBS_CFS: | ||
198 | break; | ||
199 | default: | ||
200 | return -EINVAL; | ||
201 | } | ||
202 | |||
203 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { | ||
204 | case SND_SOC_DAIFMT_NB_NF: | ||
205 | iface &= ~0x22; | ||
206 | break; | ||
207 | case SND_SOC_DAIFMT_IB_NF: | ||
208 | iface |= 0x20; | ||
209 | iface &= ~0x02; | ||
210 | break; | ||
211 | case SND_SOC_DAIFMT_NB_IF: | ||
212 | iface |= 0x02; | ||
213 | iface &= ~0x20; | ||
214 | break; | ||
215 | case SND_SOC_DAIFMT_IB_IF: | ||
216 | iface |= 0x22; | ||
217 | break; | ||
218 | default: | ||
219 | return -EINVAL; | ||
220 | } | ||
221 | |||
222 | wm8728_write(codec, WM8728_IFCTL, iface); | ||
223 | return 0; | ||
224 | } | ||
225 | |||
226 | static int wm8728_set_bias_level(struct snd_soc_codec *codec, | ||
227 | enum snd_soc_bias_level level) | ||
228 | { | ||
229 | u16 reg; | ||
230 | int i; | ||
231 | |||
232 | switch (level) { | ||
233 | case SND_SOC_BIAS_ON: | ||
234 | case SND_SOC_BIAS_PREPARE: | ||
235 | case SND_SOC_BIAS_STANDBY: | ||
236 | if (codec->bias_level == SND_SOC_BIAS_OFF) { | ||
237 | /* Power everything up... */ | ||
238 | reg = wm8728_read_reg_cache(codec, WM8728_DACCTL); | ||
239 | wm8728_write(codec, WM8728_DACCTL, reg & ~0x4); | ||
240 | |||
241 | /* ..then sync in the register cache. */ | ||
242 | for (i = 0; i < ARRAY_SIZE(wm8728_reg_defaults); i++) | ||
243 | wm8728_write(codec, i, | ||
244 | wm8728_read_reg_cache(codec, i)); | ||
245 | } | ||
246 | break; | ||
247 | |||
248 | case SND_SOC_BIAS_OFF: | ||
249 | reg = wm8728_read_reg_cache(codec, WM8728_DACCTL); | ||
250 | wm8728_write(codec, WM8728_DACCTL, reg | 0x4); | ||
251 | break; | ||
252 | } | ||
253 | codec->bias_level = level; | ||
254 | return 0; | ||
255 | } | ||
256 | |||
257 | #define WM8728_RATES (SNDRV_PCM_RATE_8000_192000) | ||
258 | |||
259 | #define WM8728_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ | ||
260 | SNDRV_PCM_FMTBIT_S24_LE) | ||
261 | |||
262 | struct snd_soc_dai wm8728_dai = { | ||
263 | .name = "WM8728", | ||
264 | .playback = { | ||
265 | .stream_name = "Playback", | ||
266 | .channels_min = 2, | ||
267 | .channels_max = 2, | ||
268 | .rates = WM8728_RATES, | ||
269 | .formats = WM8728_FORMATS, | ||
270 | }, | ||
271 | .ops = { | ||
272 | .hw_params = wm8728_hw_params, | ||
273 | .digital_mute = wm8728_mute, | ||
274 | .set_fmt = wm8728_set_dai_fmt, | ||
275 | } | ||
276 | }; | ||
277 | EXPORT_SYMBOL_GPL(wm8728_dai); | ||
278 | |||
279 | static int wm8728_suspend(struct platform_device *pdev, pm_message_t state) | ||
280 | { | ||
281 | struct snd_soc_device *socdev = platform_get_drvdata(pdev); | ||
282 | struct snd_soc_codec *codec = socdev->codec; | ||
283 | |||
284 | wm8728_set_bias_level(codec, SND_SOC_BIAS_OFF); | ||
285 | |||
286 | return 0; | ||
287 | } | ||
288 | |||
289 | static int wm8728_resume(struct platform_device *pdev) | ||
290 | { | ||
291 | struct snd_soc_device *socdev = platform_get_drvdata(pdev); | ||
292 | struct snd_soc_codec *codec = socdev->codec; | ||
293 | |||
294 | wm8728_set_bias_level(codec, codec->suspend_bias_level); | ||
295 | |||
296 | return 0; | ||
297 | } | ||
298 | |||
299 | /* | ||
300 | * initialise the WM8728 driver | ||
301 | * register the mixer and dsp interfaces with the kernel | ||
302 | */ | ||
303 | static int wm8728_init(struct snd_soc_device *socdev) | ||
304 | { | ||
305 | struct snd_soc_codec *codec = socdev->codec; | ||
306 | int ret = 0; | ||
307 | |||
308 | codec->name = "WM8728"; | ||
309 | codec->owner = THIS_MODULE; | ||
310 | codec->read = wm8728_read_reg_cache; | ||
311 | codec->write = wm8728_write; | ||
312 | codec->set_bias_level = wm8728_set_bias_level; | ||
313 | codec->dai = &wm8728_dai; | ||
314 | codec->num_dai = 1; | ||
315 | codec->bias_level = SND_SOC_BIAS_OFF; | ||
316 | codec->reg_cache_size = ARRAY_SIZE(wm8728_reg_defaults); | ||
317 | codec->reg_cache = kmemdup(wm8728_reg_defaults, | ||
318 | sizeof(wm8728_reg_defaults), | ||
319 | GFP_KERNEL); | ||
320 | if (codec->reg_cache == NULL) | ||
321 | return -ENOMEM; | ||
322 | |||
323 | /* register pcms */ | ||
324 | ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); | ||
325 | if (ret < 0) { | ||
326 | printk(KERN_ERR "wm8728: failed to create pcms\n"); | ||
327 | goto pcm_err; | ||
328 | } | ||
329 | |||
330 | /* power on device */ | ||
331 | wm8728_set_bias_level(codec, SND_SOC_BIAS_STANDBY); | ||
332 | |||
333 | wm8728_add_controls(codec); | ||
334 | wm8728_add_widgets(codec); | ||
335 | ret = snd_soc_init_card(socdev); | ||
336 | if (ret < 0) { | ||
337 | printk(KERN_ERR "wm8728: failed to register card\n"); | ||
338 | goto card_err; | ||
339 | } | ||
340 | |||
341 | return ret; | ||
342 | |||
343 | card_err: | ||
344 | snd_soc_free_pcms(socdev); | ||
345 | snd_soc_dapm_free(socdev); | ||
346 | pcm_err: | ||
347 | kfree(codec->reg_cache); | ||
348 | return ret; | ||
349 | } | ||
350 | |||
351 | static struct snd_soc_device *wm8728_socdev; | ||
352 | |||
353 | #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) | ||
354 | |||
355 | /* | ||
356 | * WM8728 2 wire address is determined by GPIO5 | ||
357 | * state during powerup. | ||
358 | * low = 0x1a | ||
359 | * high = 0x1b | ||
360 | */ | ||
361 | |||
362 | static int wm8728_i2c_probe(struct i2c_client *i2c, | ||
363 | const struct i2c_device_id *id) | ||
364 | { | ||
365 | struct snd_soc_device *socdev = wm8728_socdev; | ||
366 | struct snd_soc_codec *codec = socdev->codec; | ||
367 | int ret; | ||
368 | |||
369 | i2c_set_clientdata(i2c, codec); | ||
370 | codec->control_data = i2c; | ||
371 | |||
372 | ret = wm8728_init(socdev); | ||
373 | if (ret < 0) | ||
374 | pr_err("failed to initialise WM8728\n"); | ||
375 | |||
376 | return ret; | ||
377 | } | ||
378 | |||
379 | static int wm8728_i2c_remove(struct i2c_client *client) | ||
380 | { | ||
381 | struct snd_soc_codec *codec = i2c_get_clientdata(client); | ||
382 | kfree(codec->reg_cache); | ||
383 | return 0; | ||
384 | } | ||
385 | |||
386 | static const struct i2c_device_id wm8728_i2c_id[] = { | ||
387 | { "wm8728", 0 }, | ||
388 | { } | ||
389 | }; | ||
390 | MODULE_DEVICE_TABLE(i2c, wm8728_i2c_id); | ||
391 | |||
392 | static struct i2c_driver wm8728_i2c_driver = { | ||
393 | .driver = { | ||
394 | .name = "WM8728 I2C Codec", | ||
395 | .owner = THIS_MODULE, | ||
396 | }, | ||
397 | .probe = wm8728_i2c_probe, | ||
398 | .remove = wm8728_i2c_remove, | ||
399 | .id_table = wm8728_i2c_id, | ||
400 | }; | ||
401 | |||
402 | static int wm8728_add_i2c_device(struct platform_device *pdev, | ||
403 | const struct wm8728_setup_data *setup) | ||
404 | { | ||
405 | struct i2c_board_info info; | ||
406 | struct i2c_adapter *adapter; | ||
407 | struct i2c_client *client; | ||
408 | int ret; | ||
409 | |||
410 | ret = i2c_add_driver(&wm8728_i2c_driver); | ||
411 | if (ret != 0) { | ||
412 | dev_err(&pdev->dev, "can't add i2c driver\n"); | ||
413 | return ret; | ||
414 | } | ||
415 | |||
416 | memset(&info, 0, sizeof(struct i2c_board_info)); | ||
417 | info.addr = setup->i2c_address; | ||
418 | strlcpy(info.type, "wm8728", I2C_NAME_SIZE); | ||
419 | |||
420 | adapter = i2c_get_adapter(setup->i2c_bus); | ||
421 | if (!adapter) { | ||
422 | dev_err(&pdev->dev, "can't get i2c adapter %d\n", | ||
423 | setup->i2c_bus); | ||
424 | goto err_driver; | ||
425 | } | ||
426 | |||
427 | client = i2c_new_device(adapter, &info); | ||
428 | i2c_put_adapter(adapter); | ||
429 | if (!client) { | ||
430 | dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", | ||
431 | (unsigned int)info.addr); | ||
432 | goto err_driver; | ||
433 | } | ||
434 | |||
435 | return 0; | ||
436 | |||
437 | err_driver: | ||
438 | i2c_del_driver(&wm8728_i2c_driver); | ||
439 | return -ENODEV; | ||
440 | } | ||
441 | #endif | ||
442 | |||
443 | #if defined(CONFIG_SPI_MASTER) | ||
444 | static int __devinit wm8728_spi_probe(struct spi_device *spi) | ||
445 | { | ||
446 | struct snd_soc_device *socdev = wm8728_socdev; | ||
447 | struct snd_soc_codec *codec = socdev->codec; | ||
448 | int ret; | ||
449 | |||
450 | codec->control_data = spi; | ||
451 | |||
452 | ret = wm8728_init(socdev); | ||
453 | if (ret < 0) | ||
454 | dev_err(&spi->dev, "failed to initialise WM8728\n"); | ||
455 | |||
456 | return ret; | ||
457 | } | ||
458 | |||
459 | static int __devexit wm8728_spi_remove(struct spi_device *spi) | ||
460 | { | ||
461 | return 0; | ||
462 | } | ||
463 | |||
464 | static struct spi_driver wm8728_spi_driver = { | ||
465 | .driver = { | ||
466 | .name = "wm8728", | ||
467 | .bus = &spi_bus_type, | ||
468 | .owner = THIS_MODULE, | ||
469 | }, | ||
470 | .probe = wm8728_spi_probe, | ||
471 | .remove = __devexit_p(wm8728_spi_remove), | ||
472 | }; | ||
473 | |||
474 | static int wm8728_spi_write(struct spi_device *spi, const char *data, int len) | ||
475 | { | ||
476 | struct spi_transfer t; | ||
477 | struct spi_message m; | ||
478 | u8 msg[2]; | ||
479 | |||
480 | if (len <= 0) | ||
481 | return 0; | ||
482 | |||
483 | msg[0] = data[0]; | ||
484 | msg[1] = data[1]; | ||
485 | |||
486 | spi_message_init(&m); | ||
487 | memset(&t, 0, (sizeof t)); | ||
488 | |||
489 | t.tx_buf = &msg[0]; | ||
490 | t.len = len; | ||
491 | |||
492 | spi_message_add_tail(&t, &m); | ||
493 | spi_sync(spi, &m); | ||
494 | |||
495 | return len; | ||
496 | } | ||
497 | #endif /* CONFIG_SPI_MASTER */ | ||
498 | |||
499 | static int wm8728_probe(struct platform_device *pdev) | ||
500 | { | ||
501 | struct snd_soc_device *socdev = platform_get_drvdata(pdev); | ||
502 | struct wm8728_setup_data *setup; | ||
503 | struct snd_soc_codec *codec; | ||
504 | int ret = 0; | ||
505 | |||
506 | setup = socdev->codec_data; | ||
507 | codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); | ||
508 | if (codec == NULL) | ||
509 | return -ENOMEM; | ||
510 | |||
511 | socdev->codec = codec; | ||
512 | mutex_init(&codec->mutex); | ||
513 | INIT_LIST_HEAD(&codec->dapm_widgets); | ||
514 | INIT_LIST_HEAD(&codec->dapm_paths); | ||
515 | |||
516 | wm8728_socdev = socdev; | ||
517 | ret = -ENODEV; | ||
518 | |||
519 | #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) | ||
520 | if (setup->i2c_address) { | ||
521 | codec->hw_write = (hw_write_t)i2c_master_send; | ||
522 | ret = wm8728_add_i2c_device(pdev, setup); | ||
523 | } | ||
524 | #endif | ||
525 | #if defined(CONFIG_SPI_MASTER) | ||
526 | if (setup->spi) { | ||
527 | codec->hw_write = (hw_write_t)wm8728_spi_write; | ||
528 | ret = spi_register_driver(&wm8728_spi_driver); | ||
529 | if (ret != 0) | ||
530 | printk(KERN_ERR "can't add spi driver"); | ||
531 | } | ||
532 | #endif | ||
533 | |||
534 | if (ret != 0) | ||
535 | kfree(codec); | ||
536 | |||
537 | return ret; | ||
538 | } | ||
539 | |||
540 | /* power down chip */ | ||
541 | static int wm8728_remove(struct platform_device *pdev) | ||
542 | { | ||
543 | struct snd_soc_device *socdev = platform_get_drvdata(pdev); | ||
544 | struct snd_soc_codec *codec = socdev->codec; | ||
545 | |||
546 | if (codec->control_data) | ||
547 | wm8728_set_bias_level(codec, SND_SOC_BIAS_OFF); | ||
548 | |||
549 | snd_soc_free_pcms(socdev); | ||
550 | snd_soc_dapm_free(socdev); | ||
551 | #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) | ||
552 | i2c_unregister_device(codec->control_data); | ||
553 | i2c_del_driver(&wm8728_i2c_driver); | ||
554 | #endif | ||
555 | #if defined(CONFIG_SPI_MASTER) | ||
556 | spi_unregister_driver(&wm8728_spi_driver); | ||
557 | #endif | ||
558 | kfree(codec); | ||
559 | |||
560 | return 0; | ||
561 | } | ||
562 | |||
563 | struct snd_soc_codec_device soc_codec_dev_wm8728 = { | ||
564 | .probe = wm8728_probe, | ||
565 | .remove = wm8728_remove, | ||
566 | .suspend = wm8728_suspend, | ||
567 | .resume = wm8728_resume, | ||
568 | }; | ||
569 | EXPORT_SYMBOL_GPL(soc_codec_dev_wm8728); | ||
570 | |||
571 | static int __init wm8728_modinit(void) | ||
572 | { | ||
573 | return snd_soc_register_dai(&wm8728_dai); | ||
574 | } | ||
575 | module_init(wm8728_modinit); | ||
576 | |||
577 | static void __exit wm8728_exit(void) | ||
578 | { | ||
579 | snd_soc_unregister_dai(&wm8728_dai); | ||
580 | } | ||
581 | module_exit(wm8728_exit); | ||
582 | |||
583 | MODULE_DESCRIPTION("ASoC WM8728 driver"); | ||
584 | MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); | ||
585 | MODULE_LICENSE("GPL"); | ||