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 <linux/slab.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#include <sound/tlv.h> 29 30#include "wm8728.h" 31 32struct snd_soc_codec_device soc_codec_dev_wm8728; 33 34/* 35 * We can't read the WM8728 register space so we cache them instead. 36 * Note that the defaults here aren't the physical defaults, we latch 37 * the volume update bits, mute the output and enable infinite zero 38 * detect. 39 */ 40static const u16 wm8728_reg_defaults[] = { 41 0x1ff, 42 0x1ff, 43 0x001, 44 0x100, 45}; 46 47static const DECLARE_TLV_DB_SCALE(wm8728_tlv, -12750, 50, 1); 48 49static const struct snd_kcontrol_new wm8728_snd_controls[] = { 50 51SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8728_DACLVOL, WM8728_DACRVOL, 52 0, 255, 0, wm8728_tlv), 53 54SOC_SINGLE("Deemphasis", WM8728_DACCTL, 1, 1, 0), 55}; 56 57/* 58 * DAPM controls. 59 */ 60static const struct snd_soc_dapm_widget wm8728_dapm_widgets[] = { 61SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SND_SOC_NOPM, 0, 0), 62SND_SOC_DAPM_OUTPUT("VOUTL"), 63SND_SOC_DAPM_OUTPUT("VOUTR"), 64}; 65 66static const struct snd_soc_dapm_route intercon[] = { 67 {"VOUTL", NULL, "DAC"}, 68 {"VOUTR", NULL, "DAC"}, 69}; 70 71static int wm8728_add_widgets(struct snd_soc_codec *codec) 72{ 73 snd_soc_dapm_new_controls(codec, wm8728_dapm_widgets, 74 ARRAY_SIZE(wm8728_dapm_widgets)); 75 76 snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); 77 78 return 0; 79} 80 81static int wm8728_mute(struct snd_soc_dai *dai, int mute) 82{ 83 struct snd_soc_codec *codec = dai->codec; 84 u16 mute_reg = snd_soc_read(codec, WM8728_DACCTL); 85 86 if (mute) 87 snd_soc_write(codec, WM8728_DACCTL, mute_reg | 1); 88 else 89 snd_soc_write(codec, WM8728_DACCTL, mute_reg & ~1); 90 91 return 0; 92} 93 94static int wm8728_hw_params(struct snd_pcm_substream *substream, 95 struct snd_pcm_hw_params *params, 96 struct snd_soc_dai *dai) 97{ 98 struct snd_soc_pcm_runtime *rtd = substream->private_data; 99 struct snd_soc_device *socdev = rtd->socdev; 100 struct snd_soc_codec *codec = socdev->card->codec; 101 u16 dac = snd_soc_read(codec, WM8728_DACCTL); 102 103 dac &= ~0x18; 104 105 switch (params_format(params)) { 106 case SNDRV_PCM_FORMAT_S16_LE: 107 break; 108 case SNDRV_PCM_FORMAT_S20_3LE: 109 dac |= 0x10; 110 break; 111 case SNDRV_PCM_FORMAT_S24_LE: 112 dac |= 0x08; 113 break; 114 default: 115 return -EINVAL; 116 } 117 118 snd_soc_write(codec, WM8728_DACCTL, dac); 119 120 return 0; 121} 122 123static int wm8728_set_dai_fmt(struct snd_soc_dai *codec_dai, 124 unsigned int fmt) 125{ 126 struct snd_soc_codec *codec = codec_dai->codec; 127 u16 iface = snd_soc_read(codec, WM8728_IFCTL); 128 129 /* Currently only I2S is supported by the driver, though the 130 * hardware is more flexible. 131 */ 132 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 133 case SND_SOC_DAIFMT_I2S: 134 iface |= 1; 135 break; 136 default: 137 return -EINVAL; 138 } 139 140 /* The hardware only support full slave mode */ 141 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { 142 case SND_SOC_DAIFMT_CBS_CFS: 143 break; 144 default: 145 return -EINVAL; 146 } 147 148 switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 149 case SND_SOC_DAIFMT_NB_NF: 150 iface &= ~0x22; 151 break; 152 case SND_SOC_DAIFMT_IB_NF: 153 iface |= 0x20; 154 iface &= ~0x02; 155 break; 156 case SND_SOC_DAIFMT_NB_IF: 157 iface |= 0x02; 158 iface &= ~0x20; 159 break; 160 case SND_SOC_DAIFMT_IB_IF: 161 iface |= 0x22; 162 break; 163 default: 164 return -EINVAL; 165 } 166 167 snd_soc_write(codec, WM8728_IFCTL, iface); 168 return 0; 169} 170 171static int wm8728_set_bias_level(struct snd_soc_codec *codec, 172 enum snd_soc_bias_level level) 173{ 174 u16 reg; 175 int i; 176 177 switch (level) { 178 case SND_SOC_BIAS_ON: 179 case SND_SOC_BIAS_PREPARE: 180 case SND_SOC_BIAS_STANDBY: 181 if (codec->bias_level == SND_SOC_BIAS_OFF) { 182 /* Power everything up... */ 183 reg = snd_soc_read(codec, WM8728_DACCTL); 184 snd_soc_write(codec, WM8728_DACCTL, reg & ~0x4); 185 186 /* ..then sync in the register cache. */ 187 for (i = 0; i < ARRAY_SIZE(wm8728_reg_defaults); i++) 188 snd_soc_write(codec, i, 189 snd_soc_read(codec, i)); 190 } 191 break; 192 193 case SND_SOC_BIAS_OFF: 194 reg = snd_soc_read(codec, WM8728_DACCTL); 195 snd_soc_write(codec, WM8728_DACCTL, reg | 0x4); 196 break; 197 } 198 codec->bias_level = level; 199 return 0; 200} 201 202#define WM8728_RATES (SNDRV_PCM_RATE_8000_192000) 203 204#define WM8728_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ 205 SNDRV_PCM_FMTBIT_S24_LE) 206 207static struct snd_soc_dai_ops wm8728_dai_ops = { 208 .hw_params = wm8728_hw_params, 209 .digital_mute = wm8728_mute, 210 .set_fmt = wm8728_set_dai_fmt, 211}; 212 213struct snd_soc_dai wm8728_dai = { 214 .name = "WM8728", 215 .playback = { 216 .stream_name = "Playback", 217 .channels_min = 2, 218 .channels_max = 2, 219 .rates = WM8728_RATES, 220 .formats = WM8728_FORMATS, 221 }, 222 .ops = &wm8728_dai_ops, 223}; 224EXPORT_SYMBOL_GPL(wm8728_dai); 225 226static int wm8728_suspend(struct platform_device *pdev, pm_message_t state) 227{ 228 struct snd_soc_device *socdev = platform_get_drvdata(pdev); 229 struct snd_soc_codec *codec = socdev->card->codec; 230 231 wm8728_set_bias_level(codec, SND_SOC_BIAS_OFF); 232 233 return 0; 234} 235 236static int wm8728_resume(struct platform_device *pdev) 237{ 238 struct snd_soc_device *socdev = platform_get_drvdata(pdev); 239 struct snd_soc_codec *codec = socdev->card->codec; 240 241 wm8728_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 242 243 return 0; 244} 245 246/* 247 * initialise the WM8728 driver 248 * register the mixer and dsp interfaces with the kernel 249 */ 250static int wm8728_init(struct snd_soc_device *socdev, 251 enum snd_soc_control_type control) 252{ 253 struct snd_soc_codec *codec = socdev->card->codec; 254 int ret = 0; 255 256 codec->name = "WM8728"; 257 codec->owner = THIS_MODULE; 258 codec->set_bias_level = wm8728_set_bias_level; 259 codec->dai = &wm8728_dai; 260 codec->num_dai = 1; 261 codec->bias_level = SND_SOC_BIAS_OFF; 262 codec->reg_cache_size = ARRAY_SIZE(wm8728_reg_defaults); 263 codec->reg_cache = kmemdup(wm8728_reg_defaults, 264 sizeof(wm8728_reg_defaults), 265 GFP_KERNEL); 266 if (codec->reg_cache == NULL) 267 return -ENOMEM; 268 269 ret = snd_soc_codec_set_cache_io(codec, 7, 9, control); 270 if (ret < 0) { 271 printk(KERN_ERR "wm8728: failed to configure cache I/O: %d\n", 272 ret); 273 goto err; 274 } 275 276 /* register pcms */ 277 ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); 278 if (ret < 0) { 279 printk(KERN_ERR "wm8728: failed to create pcms\n"); 280 goto err; 281 } 282 283 /* power on device */ 284 wm8728_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 285 286 snd_soc_add_controls(codec, wm8728_snd_controls, 287 ARRAY_SIZE(wm8728_snd_controls)); 288 wm8728_add_widgets(codec); 289 290 return ret; 291 292err: 293 kfree(codec->reg_cache); 294 return ret; 295} 296 297static struct snd_soc_device *wm8728_socdev; 298 299#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) 300 301/* 302 * WM8728 2 wire address is determined by GPIO5 303 * state during powerup. 304 * low = 0x1a 305 * high = 0x1b 306 */ 307 308static int wm8728_i2c_probe(struct i2c_client *i2c, 309 const struct i2c_device_id *id) 310{ 311 struct snd_soc_device *socdev = wm8728_socdev; 312 struct snd_soc_codec *codec = socdev->card->codec; 313 int ret; 314 315 i2c_set_clientdata(i2c, codec); 316 codec->control_data = i2c; 317 318 ret = wm8728_init(socdev, SND_SOC_I2C); 319 if (ret < 0) 320 pr_err("failed to initialise WM8728\n"); 321 322 return ret; 323} 324 325static int wm8728_i2c_remove(struct i2c_client *client) 326{ 327 struct snd_soc_codec *codec = i2c_get_clientdata(client); 328 kfree(codec->reg_cache); 329 return 0; 330} 331 332static const struct i2c_device_id wm8728_i2c_id[] = { 333 { "wm8728", 0 }, 334 { } 335}; 336MODULE_DEVICE_TABLE(i2c, wm8728_i2c_id); 337 338static struct i2c_driver wm8728_i2c_driver = { 339 .driver = { 340 .name = "WM8728 I2C Codec", 341 .owner = THIS_MODULE, 342 }, 343 .probe = wm8728_i2c_probe, 344 .remove = wm8728_i2c_remove, 345 .id_table = wm8728_i2c_id, 346}; 347 348static int wm8728_add_i2c_device(struct platform_device *pdev, 349 const struct wm8728_setup_data *setup) 350{ 351 struct i2c_board_info info; 352 struct i2c_adapter *adapter; 353 struct i2c_client *client; 354 int ret; 355 356 ret = i2c_add_driver(&wm8728_i2c_driver); 357 if (ret != 0) { 358 dev_err(&pdev->dev, "can't add i2c driver\n"); 359 return ret; 360 } 361 362 memset(&info, 0, sizeof(struct i2c_board_info)); 363 info.addr = setup->i2c_address; 364 strlcpy(info.type, "wm8728", I2C_NAME_SIZE); 365 366 adapter = i2c_get_adapter(setup->i2c_bus); 367 if (!adapter) { 368 dev_err(&pdev->dev, "can't get i2c adapter %d\n", 369 setup->i2c_bus); 370 goto err_driver; 371 } 372 373 client = i2c_new_device(adapter, &info); 374 i2c_put_adapter(adapter); 375 if (!client) { 376 dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", 377 (unsigned int)info.addr); 378 goto err_driver; 379 } 380 381 return 0; 382 383err_driver: 384 i2c_del_driver(&wm8728_i2c_driver); 385 return -ENODEV; 386} 387#endif 388 389#if defined(CONFIG_SPI_MASTER) 390static int __devinit wm8728_spi_probe(struct spi_device *spi) 391{ 392 struct snd_soc_device *socdev = wm8728_socdev; 393 struct snd_soc_codec *codec = socdev->card->codec; 394 int ret; 395 396 codec->control_data = spi; 397 398 ret = wm8728_init(socdev, SND_SOC_SPI); 399 if (ret < 0) 400 dev_err(&spi->dev, "failed to initialise WM8728\n"); 401 402 return ret; 403} 404 405static int __devexit wm8728_spi_remove(struct spi_device *spi) 406{ 407 return 0; 408} 409 410static struct spi_driver wm8728_spi_driver = { 411 .driver = { 412 .name = "wm8728", 413 .bus = &spi_bus_type, 414 .owner = THIS_MODULE, 415 }, 416 .probe = wm8728_spi_probe, 417 .remove = __devexit_p(wm8728_spi_remove), 418}; 419#endif /* CONFIG_SPI_MASTER */ 420 421static int wm8728_probe(struct platform_device *pdev) 422{ 423 struct snd_soc_device *socdev = platform_get_drvdata(pdev); 424 struct wm8728_setup_data *setup; 425 struct snd_soc_codec *codec; 426 int ret = 0; 427 428 setup = socdev->codec_data; 429 codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); 430 if (codec == NULL) 431 return -ENOMEM; 432 433 socdev->card->codec = codec; 434 mutex_init(&codec->mutex); 435 INIT_LIST_HEAD(&codec->dapm_widgets); 436 INIT_LIST_HEAD(&codec->dapm_paths); 437 438 wm8728_socdev = socdev; 439 ret = -ENODEV; 440 441#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) 442 if (setup->i2c_address) { 443 ret = wm8728_add_i2c_device(pdev, setup); 444 } 445#endif 446#if defined(CONFIG_SPI_MASTER) 447 if (setup->spi) { 448 ret = spi_register_driver(&wm8728_spi_driver); 449 if (ret != 0) 450 printk(KERN_ERR "can't add spi driver"); 451 } 452#endif 453 454 if (ret != 0) 455 kfree(codec); 456 457 return ret; 458} 459 460/* power down chip */ 461static int wm8728_remove(struct platform_device *pdev) 462{ 463 struct snd_soc_device *socdev = platform_get_drvdata(pdev); 464 struct snd_soc_codec *codec = socdev->card->codec; 465 466 if (codec->control_data) 467 wm8728_set_bias_level(codec, SND_SOC_BIAS_OFF); 468 469 snd_soc_free_pcms(socdev); 470 snd_soc_dapm_free(socdev); 471#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) 472 i2c_unregister_device(codec->control_data); 473 i2c_del_driver(&wm8728_i2c_driver); 474#endif 475#if defined(CONFIG_SPI_MASTER) 476 spi_unregister_driver(&wm8728_spi_driver); 477#endif 478 kfree(codec); 479 480 return 0; 481} 482 483struct snd_soc_codec_device soc_codec_dev_wm8728 = { 484 .probe = wm8728_probe, 485 .remove = wm8728_remove, 486 .suspend = wm8728_suspend, 487 .resume = wm8728_resume, 488}; 489EXPORT_SYMBOL_GPL(soc_codec_dev_wm8728); 490 491static int __init wm8728_modinit(void) 492{ 493 return snd_soc_register_dai(&wm8728_dai); 494} 495module_init(wm8728_modinit); 496 497static void __exit wm8728_exit(void) 498{ 499 snd_soc_unregister_dai(&wm8728_dai); 500} 501module_exit(wm8728_exit); 502 503MODULE_DESCRIPTION("ASoC WM8728 driver"); 504MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); 505MODULE_LICENSE("GPL"); 506