1/* 2 * wm8776.c -- WM8776 ALSA SoC Audio driver 3 * 4 * Copyright 2009 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 * TODO: Input ALC/limiter support 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 <linux/spi/spi.h> 23#include <linux/slab.h> 24#include <sound/core.h> 25#include <sound/pcm.h> 26#include <sound/pcm_params.h> 27#include <sound/soc.h> 28#include <sound/soc-dapm.h> 29#include <sound/initval.h> 30#include <sound/tlv.h> 31 32#include "wm8776.h" 33 34static struct snd_soc_codec *wm8776_codec; 35struct snd_soc_codec_device soc_codec_dev_wm8776; 36 37/* codec private data */ 38struct wm8776_priv { 39 struct snd_soc_codec codec; 40 u16 reg_cache[WM8776_CACHEREGNUM]; 41 int sysclk[2]; 42}; 43 44#ifdef CONFIG_SPI_MASTER 45static int wm8776_spi_write(struct spi_device *spi, const char *data, int len); 46#endif 47 48static const u16 wm8776_reg[WM8776_CACHEREGNUM] = { 49 0x79, 0x79, 0x79, 0xff, 0xff, /* 4 */ 50 0xff, 0x00, 0x90, 0x00, 0x00, /* 9 */ 51 0x22, 0x22, 0x22, 0x08, 0xcf, /* 14 */ 52 0xcf, 0x7b, 0x00, 0x32, 0x00, /* 19 */ 53 0xa6, 0x01, 0x01 54}; 55 56static int wm8776_reset(struct snd_soc_codec *codec) 57{ 58 return snd_soc_write(codec, WM8776_RESET, 0); 59} 60 61static const DECLARE_TLV_DB_SCALE(hp_tlv, -12100, 100, 1); 62static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); 63static const DECLARE_TLV_DB_SCALE(adc_tlv, -10350, 50, 1); 64 65static const struct snd_kcontrol_new wm8776_snd_controls[] = { 66SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8776_HPLVOL, WM8776_HPRVOL, 67 0, 127, 0, hp_tlv), 68SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8776_DACLVOL, WM8776_DACRVOL, 69 0, 255, 0, dac_tlv), 70SOC_SINGLE("Digital Playback ZC Switch", WM8776_DACCTRL1, 0, 1, 0), 71 72SOC_SINGLE("Deemphasis Switch", WM8776_DACCTRL2, 0, 1, 0), 73 74SOC_DOUBLE_R_TLV("Capture Volume", WM8776_ADCLVOL, WM8776_ADCRVOL, 75 0, 255, 0, adc_tlv), 76SOC_DOUBLE("Capture Switch", WM8776_ADCMUX, 7, 6, 1, 1), 77SOC_DOUBLE_R("Capture ZC Switch", WM8776_ADCLVOL, WM8776_ADCRVOL, 8, 1, 0), 78SOC_SINGLE("Capture HPF Switch", WM8776_ADCIFCTRL, 8, 1, 1), 79}; 80 81static const struct snd_kcontrol_new inmix_controls[] = { 82SOC_DAPM_SINGLE("AIN1 Switch", WM8776_ADCMUX, 0, 1, 0), 83SOC_DAPM_SINGLE("AIN2 Switch", WM8776_ADCMUX, 1, 1, 0), 84SOC_DAPM_SINGLE("AIN3 Switch", WM8776_ADCMUX, 2, 1, 0), 85SOC_DAPM_SINGLE("AIN4 Switch", WM8776_ADCMUX, 3, 1, 0), 86SOC_DAPM_SINGLE("AIN5 Switch", WM8776_ADCMUX, 4, 1, 0), 87}; 88 89static const struct snd_kcontrol_new outmix_controls[] = { 90SOC_DAPM_SINGLE("DAC Switch", WM8776_OUTMUX, 0, 1, 0), 91SOC_DAPM_SINGLE("AUX Switch", WM8776_OUTMUX, 1, 1, 0), 92SOC_DAPM_SINGLE("Bypass Switch", WM8776_OUTMUX, 2, 1, 0), 93}; 94 95static const struct snd_soc_dapm_widget wm8776_dapm_widgets[] = { 96SND_SOC_DAPM_INPUT("AUX"), 97 98SND_SOC_DAPM_INPUT("AIN1"), 99SND_SOC_DAPM_INPUT("AIN2"), 100SND_SOC_DAPM_INPUT("AIN3"), 101SND_SOC_DAPM_INPUT("AIN4"), 102SND_SOC_DAPM_INPUT("AIN5"), 103 104SND_SOC_DAPM_MIXER("Input Mixer", WM8776_PWRDOWN, 6, 1, 105 inmix_controls, ARRAY_SIZE(inmix_controls)), 106 107SND_SOC_DAPM_ADC("ADC", "Capture", WM8776_PWRDOWN, 1, 1), 108SND_SOC_DAPM_DAC("DAC", "Playback", WM8776_PWRDOWN, 2, 1), 109 110SND_SOC_DAPM_MIXER("Output Mixer", SND_SOC_NOPM, 0, 0, 111 outmix_controls, ARRAY_SIZE(outmix_controls)), 112 113SND_SOC_DAPM_PGA("Headphone PGA", WM8776_PWRDOWN, 3, 1, NULL, 0), 114 115SND_SOC_DAPM_OUTPUT("VOUT"), 116 117SND_SOC_DAPM_OUTPUT("HPOUTL"), 118SND_SOC_DAPM_OUTPUT("HPOUTR"), 119}; 120 121static const struct snd_soc_dapm_route routes[] = { 122 { "Input Mixer", "AIN1 Switch", "AIN1" }, 123 { "Input Mixer", "AIN2 Switch", "AIN2" }, 124 { "Input Mixer", "AIN3 Switch", "AIN3" }, 125 { "Input Mixer", "AIN4 Switch", "AIN4" }, 126 { "Input Mixer", "AIN5 Switch", "AIN5" }, 127 128 { "ADC", NULL, "Input Mixer" }, 129 130 { "Output Mixer", "DAC Switch", "DAC" }, 131 { "Output Mixer", "AUX Switch", "AUX" }, 132 { "Output Mixer", "Bypass Switch", "Input Mixer" }, 133 134 { "VOUT", NULL, "Output Mixer" }, 135 136 { "Headphone PGA", NULL, "Output Mixer" }, 137 138 { "HPOUTL", NULL, "Headphone PGA" }, 139 { "HPOUTR", NULL, "Headphone PGA" }, 140}; 141 142static int wm8776_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) 143{ 144 struct snd_soc_codec *codec = dai->codec; 145 int reg, iface, master; 146 147 switch (dai->id) { 148 case WM8776_DAI_DAC: 149 reg = WM8776_DACIFCTRL; 150 master = 0x80; 151 break; 152 case WM8776_DAI_ADC: 153 reg = WM8776_ADCIFCTRL; 154 master = 0x100; 155 break; 156 default: 157 return -EINVAL; 158 } 159 160 iface = 0; 161 162 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { 163 case SND_SOC_DAIFMT_CBM_CFM: 164 break; 165 case SND_SOC_DAIFMT_CBS_CFS: 166 master = 0; 167 break; 168 default: 169 return -EINVAL; 170 } 171 172 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 173 case SND_SOC_DAIFMT_I2S: 174 iface |= 0x0002; 175 break; 176 case SND_SOC_DAIFMT_RIGHT_J: 177 break; 178 case SND_SOC_DAIFMT_LEFT_J: 179 iface |= 0x0001; 180 break; 181 default: 182 return -EINVAL; 183 } 184 185 switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 186 case SND_SOC_DAIFMT_NB_NF: 187 break; 188 case SND_SOC_DAIFMT_IB_IF: 189 iface |= 0x00c; 190 break; 191 case SND_SOC_DAIFMT_IB_NF: 192 iface |= 0x008; 193 break; 194 case SND_SOC_DAIFMT_NB_IF: 195 iface |= 0x004; 196 break; 197 default: 198 return -EINVAL; 199 } 200 201 /* Finally, write out the values */ 202 snd_soc_update_bits(codec, reg, 0xf, iface); 203 snd_soc_update_bits(codec, WM8776_MSTRCTRL, 0x180, master); 204 205 return 0; 206} 207 208static int mclk_ratios[] = { 209 128, 210 192, 211 256, 212 384, 213 512, 214 768, 215}; 216 217static int wm8776_hw_params(struct snd_pcm_substream *substream, 218 struct snd_pcm_hw_params *params, 219 struct snd_soc_dai *dai) 220{ 221 struct snd_soc_codec *codec = dai->codec; 222 struct wm8776_priv *wm8776 = snd_soc_codec_get_drvdata(codec); 223 int iface_reg, iface; 224 int ratio_shift, master; 225 int i; 226 227 iface = 0; 228 229 switch (dai->id) { 230 case WM8776_DAI_DAC: 231 iface_reg = WM8776_DACIFCTRL; 232 master = 0x80; 233 ratio_shift = 4; 234 break; 235 case WM8776_DAI_ADC: 236 iface_reg = WM8776_ADCIFCTRL; 237 master = 0x100; 238 ratio_shift = 0; 239 break; 240 default: 241 return -EINVAL; 242 } 243 244 245 /* Set word length */ 246 switch (params_format(params)) { 247 case SNDRV_PCM_FORMAT_S16_LE: 248 break; 249 case SNDRV_PCM_FORMAT_S20_3LE: 250 iface |= 0x10; 251 break; 252 case SNDRV_PCM_FORMAT_S24_LE: 253 iface |= 0x20; 254 break; 255 case SNDRV_PCM_FORMAT_S32_LE: 256 iface |= 0x30; 257 break; 258 } 259 260 /* Only need to set MCLK/LRCLK ratio if we're master */ 261 if (snd_soc_read(codec, WM8776_MSTRCTRL) & master) { 262 for (i = 0; i < ARRAY_SIZE(mclk_ratios); i++) { 263 if (wm8776->sysclk[dai->id] / params_rate(params) 264 == mclk_ratios[i]) 265 break; 266 } 267 268 if (i == ARRAY_SIZE(mclk_ratios)) { 269 dev_err(codec->dev, 270 "Unable to configure MCLK ratio %d/%d\n", 271 wm8776->sysclk[dai->id], params_rate(params)); 272 return -EINVAL; 273 } 274 275 dev_dbg(codec->dev, "MCLK is %dfs\n", mclk_ratios[i]); 276 277 snd_soc_update_bits(codec, WM8776_MSTRCTRL, 278 0x7 << ratio_shift, i << ratio_shift); 279 } else { 280 dev_dbg(codec->dev, "DAI in slave mode\n"); 281 } 282 283 snd_soc_update_bits(codec, iface_reg, 0x30, iface); 284 285 return 0; 286} 287 288static int wm8776_mute(struct snd_soc_dai *dai, int mute) 289{ 290 struct snd_soc_codec *codec = dai->codec; 291 292 return snd_soc_write(codec, WM8776_DACMUTE, !!mute); 293} 294 295static int wm8776_set_sysclk(struct snd_soc_dai *dai, 296 int clk_id, unsigned int freq, int dir) 297{ 298 struct snd_soc_codec *codec = dai->codec; 299 struct wm8776_priv *wm8776 = snd_soc_codec_get_drvdata(codec); 300 301 BUG_ON(dai->id >= ARRAY_SIZE(wm8776->sysclk)); 302 303 wm8776->sysclk[dai->id] = freq; 304 305 return 0; 306} 307 308static int wm8776_set_bias_level(struct snd_soc_codec *codec, 309 enum snd_soc_bias_level level) 310{ 311 switch (level) { 312 case SND_SOC_BIAS_ON: 313 break; 314 case SND_SOC_BIAS_PREPARE: 315 break; 316 case SND_SOC_BIAS_STANDBY: 317 if (codec->bias_level == SND_SOC_BIAS_OFF) { 318 /* Disable the global powerdown; DAPM does the rest */ 319 snd_soc_update_bits(codec, WM8776_PWRDOWN, 1, 0); 320 } 321 322 break; 323 case SND_SOC_BIAS_OFF: 324 snd_soc_update_bits(codec, WM8776_PWRDOWN, 1, 1); 325 break; 326 } 327 328 codec->bias_level = level; 329 return 0; 330} 331 332#define WM8776_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ 333 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\ 334 SNDRV_PCM_RATE_96000) 335 336 337#define WM8776_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ 338 SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) 339 340static struct snd_soc_dai_ops wm8776_dac_ops = { 341 .digital_mute = wm8776_mute, 342 .hw_params = wm8776_hw_params, 343 .set_fmt = wm8776_set_fmt, 344 .set_sysclk = wm8776_set_sysclk, 345}; 346 347static struct snd_soc_dai_ops wm8776_adc_ops = { 348 .hw_params = wm8776_hw_params, 349 .set_fmt = wm8776_set_fmt, 350 .set_sysclk = wm8776_set_sysclk, 351}; 352 353struct snd_soc_dai wm8776_dai[] = { 354 { 355 .name = "WM8776 Playback", 356 .id = WM8776_DAI_DAC, 357 .playback = { 358 .stream_name = "Playback", 359 .channels_min = 2, 360 .channels_max = 2, 361 .rates = WM8776_RATES, 362 .formats = WM8776_FORMATS, 363 }, 364 .ops = &wm8776_dac_ops, 365 }, 366 { 367 .name = "WM8776 Capture", 368 .id = WM8776_DAI_ADC, 369 .capture = { 370 .stream_name = "Capture", 371 .channels_min = 2, 372 .channels_max = 2, 373 .rates = WM8776_RATES, 374 .formats = WM8776_FORMATS, 375 }, 376 .ops = &wm8776_adc_ops, 377 }, 378}; 379EXPORT_SYMBOL_GPL(wm8776_dai); 380 381#ifdef CONFIG_PM 382static int wm8776_suspend(struct platform_device *pdev, pm_message_t state) 383{ 384 struct snd_soc_device *socdev = platform_get_drvdata(pdev); 385 struct snd_soc_codec *codec = socdev->card->codec; 386 387 wm8776_set_bias_level(codec, SND_SOC_BIAS_OFF); 388 389 return 0; 390} 391 392static int wm8776_resume(struct platform_device *pdev) 393{ 394 struct snd_soc_device *socdev = platform_get_drvdata(pdev); 395 struct snd_soc_codec *codec = socdev->card->codec; 396 int i; 397 u8 data[2]; 398 u16 *cache = codec->reg_cache; 399 400 /* Sync reg_cache with the hardware */ 401 for (i = 0; i < ARRAY_SIZE(wm8776_reg); i++) { 402 if (cache[i] == wm8776_reg[i]) 403 continue; 404 data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); 405 data[1] = cache[i] & 0x00ff; 406 codec->hw_write(codec->control_data, data, 2); 407 } 408 409 wm8776_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 410 411 return 0; 412} 413#else 414#define wm8776_suspend NULL 415#define wm8776_resume NULL 416#endif 417 418static int wm8776_probe(struct platform_device *pdev) 419{ 420 struct snd_soc_device *socdev = platform_get_drvdata(pdev); 421 struct snd_soc_codec *codec; 422 int ret = 0; 423 424 if (wm8776_codec == NULL) { 425 dev_err(&pdev->dev, "Codec device not registered\n"); 426 return -ENODEV; 427 } 428 429 socdev->card->codec = wm8776_codec; 430 codec = wm8776_codec; 431 432 /* register pcms */ 433 ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); 434 if (ret < 0) { 435 dev_err(codec->dev, "failed to create pcms: %d\n", ret); 436 goto pcm_err; 437 } 438 439 snd_soc_add_controls(codec, wm8776_snd_controls, 440 ARRAY_SIZE(wm8776_snd_controls)); 441 snd_soc_dapm_new_controls(codec, wm8776_dapm_widgets, 442 ARRAY_SIZE(wm8776_dapm_widgets)); 443 snd_soc_dapm_add_routes(codec, routes, ARRAY_SIZE(routes)); 444 445 return ret; 446 447pcm_err: 448 return ret; 449} 450 451/* power down chip */ 452static int wm8776_remove(struct platform_device *pdev) 453{ 454 struct snd_soc_device *socdev = platform_get_drvdata(pdev); 455 456 snd_soc_free_pcms(socdev); 457 snd_soc_dapm_free(socdev); 458 459 return 0; 460} 461 462struct snd_soc_codec_device soc_codec_dev_wm8776 = { 463 .probe = wm8776_probe, 464 .remove = wm8776_remove, 465 .suspend = wm8776_suspend, 466 .resume = wm8776_resume, 467}; 468EXPORT_SYMBOL_GPL(soc_codec_dev_wm8776); 469 470static int wm8776_register(struct wm8776_priv *wm8776, 471 enum snd_soc_control_type control) 472{ 473 int ret, i; 474 struct snd_soc_codec *codec = &wm8776->codec; 475 476 if (wm8776_codec) { 477 dev_err(codec->dev, "Another WM8776 is registered\n"); 478 ret = -EINVAL; 479 goto err; 480 } 481 482 mutex_init(&codec->mutex); 483 INIT_LIST_HEAD(&codec->dapm_widgets); 484 INIT_LIST_HEAD(&codec->dapm_paths); 485 486 snd_soc_codec_set_drvdata(codec, wm8776); 487 codec->name = "WM8776"; 488 codec->owner = THIS_MODULE; 489 codec->bias_level = SND_SOC_BIAS_OFF; 490 codec->set_bias_level = wm8776_set_bias_level; 491 codec->dai = wm8776_dai; 492 codec->num_dai = ARRAY_SIZE(wm8776_dai); 493 codec->reg_cache_size = WM8776_CACHEREGNUM; 494 codec->reg_cache = &wm8776->reg_cache; 495 496 memcpy(codec->reg_cache, wm8776_reg, sizeof(wm8776_reg)); 497 498 ret = snd_soc_codec_set_cache_io(codec, 7, 9, control); 499 if (ret < 0) { 500 dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); 501 goto err; 502 } 503 504 for (i = 0; i < ARRAY_SIZE(wm8776_dai); i++) 505 wm8776_dai[i].dev = codec->dev; 506 507 ret = wm8776_reset(codec); 508 if (ret < 0) { 509 dev_err(codec->dev, "Failed to issue reset: %d\n", ret); 510 goto err; 511 } 512 513 wm8776_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 514 515 /* Latch the update bits; right channel only since we always 516 * update both. */ 517 snd_soc_update_bits(codec, WM8776_HPRVOL, 0x100, 0x100); 518 snd_soc_update_bits(codec, WM8776_DACRVOL, 0x100, 0x100); 519 520 wm8776_codec = codec; 521 522 ret = snd_soc_register_codec(codec); 523 if (ret != 0) { 524 dev_err(codec->dev, "Failed to register codec: %d\n", ret); 525 goto err; 526 } 527 528 ret = snd_soc_register_dais(wm8776_dai, ARRAY_SIZE(wm8776_dai)); 529 if (ret != 0) { 530 dev_err(codec->dev, "Failed to register DAIs: %d\n", ret); 531 goto err_codec; 532 } 533 534 return 0; 535 536err_codec: 537 snd_soc_unregister_codec(codec); 538err: 539 kfree(wm8776); 540 return ret; 541} 542 543static void wm8776_unregister(struct wm8776_priv *wm8776) 544{ 545 wm8776_set_bias_level(&wm8776->codec, SND_SOC_BIAS_OFF); 546 snd_soc_unregister_dais(wm8776_dai, ARRAY_SIZE(wm8776_dai)); 547 snd_soc_unregister_codec(&wm8776->codec); 548 kfree(wm8776); 549 wm8776_codec = NULL; 550} 551 552#if defined(CONFIG_SPI_MASTER) 553static int wm8776_spi_write(struct spi_device *spi, const char *data, int len) 554{ 555 struct spi_transfer t; 556 struct spi_message m; 557 u8 msg[2]; 558 559 if (len <= 0) 560 return 0; 561 562 msg[0] = data[0]; 563 msg[1] = data[1]; 564 565 spi_message_init(&m); 566 memset(&t, 0, (sizeof t)); 567 568 t.tx_buf = &msg[0]; 569 t.len = len; 570 571 spi_message_add_tail(&t, &m); 572 spi_sync(spi, &m); 573 574 return len; 575} 576 577static int __devinit wm8776_spi_probe(struct spi_device *spi) 578{ 579 struct snd_soc_codec *codec; 580 struct wm8776_priv *wm8776; 581 582 wm8776 = kzalloc(sizeof(struct wm8776_priv), GFP_KERNEL); 583 if (wm8776 == NULL) 584 return -ENOMEM; 585 586 codec = &wm8776->codec; 587 codec->control_data = spi; 588 codec->hw_write = (hw_write_t)wm8776_spi_write; 589 codec->dev = &spi->dev; 590 591 dev_set_drvdata(&spi->dev, wm8776); 592 593 return wm8776_register(wm8776, SND_SOC_SPI); 594} 595 596static int __devexit wm8776_spi_remove(struct spi_device *spi) 597{ 598 struct wm8776_priv *wm8776 = dev_get_drvdata(&spi->dev); 599 600 wm8776_unregister(wm8776); 601 602 return 0; 603} 604 605static struct spi_driver wm8776_spi_driver = { 606 .driver = { 607 .name = "wm8776", 608 .bus = &spi_bus_type, 609 .owner = THIS_MODULE, 610 }, 611 .probe = wm8776_spi_probe, 612 .remove = __devexit_p(wm8776_spi_remove), 613}; 614#endif /* CONFIG_SPI_MASTER */ 615 616#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) 617static __devinit int wm8776_i2c_probe(struct i2c_client *i2c, 618 const struct i2c_device_id *id) 619{ 620 struct wm8776_priv *wm8776; 621 struct snd_soc_codec *codec; 622 623 wm8776 = kzalloc(sizeof(struct wm8776_priv), GFP_KERNEL); 624 if (wm8776 == NULL) 625 return -ENOMEM; 626 627 codec = &wm8776->codec; 628 codec->hw_write = (hw_write_t)i2c_master_send; 629 630 i2c_set_clientdata(i2c, wm8776); 631 codec->control_data = i2c; 632 633 codec->dev = &i2c->dev; 634 635 return wm8776_register(wm8776, SND_SOC_I2C); 636} 637 638static __devexit int wm8776_i2c_remove(struct i2c_client *client) 639{ 640 struct wm8776_priv *wm8776 = i2c_get_clientdata(client); 641 wm8776_unregister(wm8776); 642 return 0; 643} 644 645static const struct i2c_device_id wm8776_i2c_id[] = { 646 { "wm8776", 0 }, 647 { } 648}; 649MODULE_DEVICE_TABLE(i2c, wm8776_i2c_id); 650 651static struct i2c_driver wm8776_i2c_driver = { 652 .driver = { 653 .name = "wm8776", 654 .owner = THIS_MODULE, 655 }, 656 .probe = wm8776_i2c_probe, 657 .remove = __devexit_p(wm8776_i2c_remove), 658 .id_table = wm8776_i2c_id, 659}; 660#endif 661 662static int __init wm8776_modinit(void) 663{ 664 int ret; 665#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) 666 ret = i2c_add_driver(&wm8776_i2c_driver); 667 if (ret != 0) { 668 printk(KERN_ERR "Failed to register WM8776 I2C driver: %d\n", 669 ret); 670 } 671#endif 672#if defined(CONFIG_SPI_MASTER) 673 ret = spi_register_driver(&wm8776_spi_driver); 674 if (ret != 0) { 675 printk(KERN_ERR "Failed to register WM8776 SPI driver: %d\n", 676 ret); 677 } 678#endif 679 return 0; 680} 681module_init(wm8776_modinit); 682 683static void __exit wm8776_exit(void) 684{ 685#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) 686 i2c_del_driver(&wm8776_i2c_driver); 687#endif 688#if defined(CONFIG_SPI_MASTER) 689 spi_unregister_driver(&wm8776_spi_driver); 690#endif 691} 692module_exit(wm8776_exit); 693 694MODULE_DESCRIPTION("ASoC WM8776 driver"); 695MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); 696MODULE_LICENSE("GPL"); 697