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