diff options
author | David S. Miller <davem@davemloft.net> | 2009-03-26 15:23:24 -0700 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2009-03-26 15:23:24 -0700 |
commit | 08abe18af1f78ee80c3c3a5ac47c3e0ae0beadf6 (patch) | |
tree | 2be39bf8942edca1bcec735145e144a682ca9cd3 /sound/soc/codecs | |
parent | f0de70f8bb56952f6e016a65a8a8d006918f5bf6 (diff) | |
parent | 0384e2959127a56d0640505d004d8dd92f9c29f5 (diff) | |
download | op-kernel-dev-08abe18af1f78ee80c3c3a5ac47c3e0ae0beadf6.zip op-kernel-dev-08abe18af1f78ee80c3c3a5ac47c3e0ae0beadf6.tar.gz |
Merge branch 'master' of /home/davem/src/GIT/linux-2.6/
Conflicts:
drivers/net/wimax/i2400m/usb-notif.c
Diffstat (limited to 'sound/soc/codecs')
40 files changed, 4567 insertions, 1937 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index d0e0d69..b6c7f7a 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -10,9 +10,11 @@ config SND_SOC_I2C_AND_SPI config SND_SOC_ALL_CODECS tristate "Build all ASoC CODEC drivers" + select SND_SOC_L3 select SND_SOC_AC97_CODEC if SND_SOC_AC97_BUS select SND_SOC_AD1980 if SND_SOC_AC97_BUS select SND_SOC_AD73311 if I2C + select SND_SOC_AK4104 if SPI_MASTER select SND_SOC_AK4535 if I2C select SND_SOC_CS4270 if I2C select SND_SOC_PCM3008 @@ -24,6 +26,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_UDA134X select SND_SOC_UDA1380 if I2C select SND_SOC_WM8350 if MFD_WM8350 + select SND_SOC_WM8400 if MFD_WM8400 select SND_SOC_WM8510 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8580 if I2C select SND_SOC_WM8728 if SND_SOC_I2C_AND_SPI @@ -34,6 +37,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8903 if I2C select SND_SOC_WM8971 if I2C select SND_SOC_WM8990 if I2C + select SND_SOC_WM9705 if SND_SOC_AC97_BUS select SND_SOC_WM9712 if SND_SOC_AC97_BUS select SND_SOC_WM9713 if SND_SOC_AC97_BUS help @@ -58,6 +62,9 @@ config SND_SOC_AD1980 config SND_SOC_AD73311 tristate +config SND_SOC_AK4104 + tristate + config SND_SOC_AK4535 tristate @@ -65,12 +72,6 @@ config SND_SOC_AK4535 config SND_SOC_CS4270 tristate -# Cirrus Logic CS4270 Codec Hardware Mute Support -# Select if you have external muting circuitry attached to your CS4270. -config SND_SOC_CS4270_HWMUTE - bool - depends on SND_SOC_CS4270 - # Cirrus Logic CS4270 Codec VD = 3.3V Errata # Select if you are affected by the errata where the part will not function # if MCLK divide-by-1.5 is selected and VD is set to 3.3V. The driver will @@ -90,7 +91,6 @@ config SND_SOC_SSM2602 config SND_SOC_TLV320AIC23 tristate - depends on I2C config SND_SOC_TLV320AIC26 tristate "TI TLV320AIC26 Codec support" if SND_SOC_OF_SIMPLE @@ -98,15 +98,12 @@ config SND_SOC_TLV320AIC26 config SND_SOC_TLV320AIC3X tristate - depends on I2C config SND_SOC_TWL4030 tristate - depends on TWL4030_CORE config SND_SOC_UDA134X tristate - select SND_SOC_L3 config SND_SOC_UDA1380 tristate @@ -114,6 +111,9 @@ config SND_SOC_UDA1380 config SND_SOC_WM8350 tristate +config SND_SOC_WM8400 + tristate + config SND_SOC_WM8510 tristate @@ -144,6 +144,9 @@ config SND_SOC_WM8971 config SND_SOC_WM8990 tristate +config SND_SOC_WM9705 + tristate + config SND_SOC_WM9712 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index c4ddc9a..030d245 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -1,6 +1,7 @@ snd-soc-ac97-objs := ac97.o snd-soc-ad1980-objs := ad1980.o snd-soc-ad73311-objs := ad73311.o +snd-soc-ak4104-objs := ak4104.o snd-soc-ak4535-objs := ak4535.o snd-soc-cs4270-objs := cs4270.o snd-soc-l3-objs := l3.o @@ -13,6 +14,7 @@ snd-soc-twl4030-objs := twl4030.o snd-soc-uda134x-objs := uda134x.o snd-soc-uda1380-objs := uda1380.o snd-soc-wm8350-objs := wm8350.o +snd-soc-wm8400-objs := wm8400.o snd-soc-wm8510-objs := wm8510.o snd-soc-wm8580-objs := wm8580.o snd-soc-wm8728-objs := wm8728.o @@ -23,12 +25,14 @@ snd-soc-wm8900-objs := wm8900.o snd-soc-wm8903-objs := wm8903.o snd-soc-wm8971-objs := wm8971.o snd-soc-wm8990-objs := wm8990.o +snd-soc-wm9705-objs := wm9705.o snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o +obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o @@ -41,6 +45,7 @@ obj-$(CONFIG_SND_SOC_TWL4030) += snd-soc-twl4030.o obj-$(CONFIG_SND_SOC_UDA134X) += snd-soc-uda134x.o obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o obj-$(CONFIG_SND_SOC_WM8350) += snd-soc-wm8350.o +obj-$(CONFIG_SND_SOC_WM8400) += snd-soc-wm8400.o obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o obj-$(CONFIG_SND_SOC_WM8580) += snd-soc-wm8580.o obj-$(CONFIG_SND_SOC_WM8728) += snd-soc-wm8728.o @@ -51,5 +56,7 @@ obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o +obj-$(CONFIG_SND_SOC_WM8991) += snd-soc-wm8991.o +obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o diff --git a/sound/soc/codecs/ac97.c b/sound/soc/codecs/ac97.c index fb53e65..b0d4af1 100644 --- a/sound/soc/codecs/ac97.c +++ b/sound/soc/codecs/ac97.c @@ -30,7 +30,7 @@ static int ac97_prepare(struct snd_pcm_substream *substream, struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int reg = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? AC97_PCM_FRONT_DAC_RATE : AC97_PCM_LR_ADC_RATE; @@ -41,6 +41,10 @@ static int ac97_prepare(struct snd_pcm_substream *substream, SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\ SNDRV_PCM_RATE_48000) +static struct snd_soc_dai_ops ac97_dai_ops = { + .prepare = ac97_prepare, +}; + struct snd_soc_dai ac97_dai = { .name = "AC97 HiFi", .ac97_control = 1, @@ -56,8 +60,7 @@ struct snd_soc_dai ac97_dai = { .channels_max = 2, .rates = STD_AC97_RATES, .formats = SNDRV_PCM_FMTBIT_S16_LE,}, - .ops = { - .prepare = ac97_prepare,}, + .ops = &ac97_dai_ops, }; EXPORT_SYMBOL_GPL(ac97_dai); @@ -84,10 +87,10 @@ static int ac97_soc_probe(struct platform_device *pdev) printk(KERN_INFO "AC97 SoC Audio Codec %s\n", AC97_VERSION); - socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); - if (!socdev->codec) + socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (!socdev->card->codec) return -ENOMEM; - codec = socdev->codec; + codec = socdev->card->codec; mutex_init(&codec->mutex); codec->name = "AC97"; @@ -123,23 +126,21 @@ bus_err: snd_soc_free_pcms(socdev); err: - kfree(socdev->codec->reg_cache); - kfree(socdev->codec); - socdev->codec = NULL; + kfree(socdev->card->codec); + socdev->card->codec = NULL; return ret; } static int ac97_soc_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; if (!codec) return 0; snd_soc_free_pcms(socdev); - kfree(socdev->codec->reg_cache); - kfree(socdev->codec); + kfree(socdev->card->codec); return 0; } @@ -149,7 +150,7 @@ static int ac97_soc_suspend(struct platform_device *pdev, pm_message_t msg) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - snd_ac97_suspend(socdev->codec->ac97); + snd_ac97_suspend(socdev->card->codec->ac97); return 0; } @@ -158,7 +159,7 @@ static int ac97_soc_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - snd_ac97_resume(socdev->codec->ac97); + snd_ac97_resume(socdev->card->codec->ac97); return 0; } diff --git a/sound/soc/codecs/ad1980.c b/sound/soc/codecs/ad1980.c index 73fdbb4..ddb3b08 100644 --- a/sound/soc/codecs/ad1980.c +++ b/sound/soc/codecs/ad1980.c @@ -93,20 +93,6 @@ SOC_ENUM("Capture Source", ad1980_cap_src), SOC_SINGLE("Mic Boost Switch", AC97_MIC, 6, 1, 0), }; -/* add non dapm controls */ -static int ad1980_add_controls(struct snd_soc_codec *codec) -{ - int err, i; - - for (i = 0; i < ARRAY_SIZE(ad1980_snd_ac97_controls); i++) { - err = snd_ctl_add(codec->card, snd_soc_cnew( - &ad1980_snd_ac97_controls[i], codec, NULL)); - if (err < 0) - return err; - } - return 0; -} - static unsigned int ac97_read(struct snd_soc_codec *codec, unsigned int reg) { @@ -123,7 +109,7 @@ static unsigned int ac97_read(struct snd_soc_codec *codec, default: reg = reg >> 1; - if (reg >= (ARRAY_SIZE(ad1980_reg))) + if (reg >= ARRAY_SIZE(ad1980_reg)) return -EINVAL; return cache[reg]; @@ -137,7 +123,7 @@ static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, soc_ac97_ops.write(codec->ac97, reg, val); reg = reg >> 1; - if (reg < (ARRAY_SIZE(ad1980_reg))) + if (reg < ARRAY_SIZE(ad1980_reg)) cache[reg] = val; return 0; @@ -200,10 +186,10 @@ static int ad1980_soc_probe(struct platform_device *pdev) printk(KERN_INFO "AD1980 SoC Audio Codec\n"); - socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); - if (socdev->codec == NULL) + socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (socdev->card->codec == NULL) return -ENOMEM; - codec = socdev->codec; + codec = socdev->card->codec; mutex_init(&codec->mutex); codec->reg_cache = @@ -269,7 +255,8 @@ static int ad1980_soc_probe(struct platform_device *pdev) ext_status = ac97_read(codec, AC97_EXTENDED_STATUS); ac97_write(codec, AC97_EXTENDED_STATUS, ext_status&~0x3800); - ad1980_add_controls(codec); + snd_soc_add_controls(codec, ad1980_snd_ac97_controls, + ARRAY_SIZE(ad1980_snd_ac97_controls)); ret = snd_soc_init_card(socdev); if (ret < 0) { printk(KERN_ERR "ad1980: failed to register card\n"); @@ -288,15 +275,15 @@ codec_err: kfree(codec->reg_cache); cache_err: - kfree(socdev->codec); - socdev->codec = NULL; + kfree(socdev->card->codec); + socdev->card->codec = NULL; return ret; } static int ad1980_soc_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; if (codec == NULL) return 0; diff --git a/sound/soc/codecs/ad73311.c b/sound/soc/codecs/ad73311.c index b09289a..e61dac5 100644 --- a/sound/soc/codecs/ad73311.c +++ b/sound/soc/codecs/ad73311.c @@ -53,7 +53,7 @@ static int ad73311_soc_probe(struct platform_device *pdev) codec->owner = THIS_MODULE; codec->dai = &ad73311_dai; codec->num_dai = 1; - socdev->codec = codec; + socdev->card->codec = codec; INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); @@ -75,15 +75,15 @@ static int ad73311_soc_probe(struct platform_device *pdev) register_err: snd_soc_free_pcms(socdev); pcm_err: - kfree(socdev->codec); - socdev->codec = NULL; + kfree(socdev->card->codec); + socdev->card->codec = NULL; return ret; } static int ad73311_soc_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; if (codec == NULL) return 0; diff --git a/sound/soc/codecs/ad73311.h b/sound/soc/codecs/ad73311.h index 507ce0c..569573d 100644 --- a/sound/soc/codecs/ad73311.h +++ b/sound/soc/codecs/ad73311.h @@ -70,7 +70,7 @@ #define REGD_IGS(x) (x & 0x7) #define REGD_RMOD (1 << 3) #define REGD_OGS(x) ((x & 0x7) << 4) -#define REGD_MUTE (x << 7) +#define REGD_MUTE (1 << 7) /* Control register E */ #define CTRL_REG_E (4 << 8) diff --git a/sound/soc/codecs/ak4104.c b/sound/soc/codecs/ak4104.c new file mode 100644 index 0000000..4d47bc4 --- /dev/null +++ b/sound/soc/codecs/ak4104.c @@ -0,0 +1,365 @@ +/* + * AK4104 ALSA SoC (ASoC) driver + * + * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/initval.h> +#include <linux/spi/spi.h> +#include <sound/asoundef.h> + +#include "ak4104.h" + +/* AK4104 registers addresses */ +#define AK4104_REG_CONTROL1 0x00 +#define AK4104_REG_RESERVED 0x01 +#define AK4104_REG_CONTROL2 0x02 +#define AK4104_REG_TX 0x03 +#define AK4104_REG_CHN_STATUS(x) ((x) + 0x04) +#define AK4104_NUM_REGS 10 + +#define AK4104_REG_MASK 0x1f +#define AK4104_READ 0xc0 +#define AK4104_WRITE 0xe0 +#define AK4104_RESERVED_VAL 0x5b + +/* Bit masks for AK4104 registers */ +#define AK4104_CONTROL1_RSTN (1 << 0) +#define AK4104_CONTROL1_PW (1 << 1) +#define AK4104_CONTROL1_DIF0 (1 << 2) +#define AK4104_CONTROL1_DIF1 (1 << 3) + +#define AK4104_CONTROL2_SEL0 (1 << 0) +#define AK4104_CONTROL2_SEL1 (1 << 1) +#define AK4104_CONTROL2_MODE (1 << 2) + +#define AK4104_TX_TXE (1 << 0) +#define AK4104_TX_V (1 << 1) + +#define DRV_NAME "ak4104" + +struct ak4104_private { + struct snd_soc_codec codec; + u8 reg_cache[AK4104_NUM_REGS]; +}; + +static int ak4104_fill_cache(struct snd_soc_codec *codec) +{ + int i; + u8 *reg_cache = codec->reg_cache; + struct spi_device *spi = codec->control_data; + + for (i = 0; i < codec->reg_cache_size; i++) { + int ret = spi_w8r8(spi, i | AK4104_READ); + if (ret < 0) { + dev_err(&spi->dev, "SPI write failure\n"); + return ret; + } + + reg_cache[i] = ret; + } + + return 0; +} + +static unsigned int ak4104_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 *reg_cache = codec->reg_cache; + + if (reg >= codec->reg_cache_size) + return -EINVAL; + + return reg_cache[reg]; +} + +static int ak4104_spi_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 *cache = codec->reg_cache; + struct spi_device *spi = codec->control_data; + + if (reg >= codec->reg_cache_size) + return -EINVAL; + + reg &= AK4104_REG_MASK; + reg |= AK4104_WRITE; + + /* only write to the hardware if value has changed */ + if (cache[reg] != value) { + u8 tmp[2] = { reg, value }; + if (spi_write(spi, tmp, sizeof(tmp))) { + dev_err(&spi->dev, "SPI write failed\n"); + return -EIO; + } + + cache[reg] = value; + } + + return 0; +} + +static int ak4104_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int format) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int val = 0; + + val = ak4104_read_reg_cache(codec, AK4104_REG_CONTROL1); + if (val < 0) + return val; + + val &= ~(AK4104_CONTROL1_DIF0 | AK4104_CONTROL1_DIF1); + + /* set DAI format */ + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + val |= AK4104_CONTROL1_DIF0; + break; + case SND_SOC_DAIFMT_I2S: + val |= AK4104_CONTROL1_DIF0 | AK4104_CONTROL1_DIF1; + break; + default: + dev_err(codec->dev, "invalid dai format\n"); + return -EINVAL; + } + + /* This device can only be slave */ + if ((format & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) + return -EINVAL; + + return ak4104_spi_write(codec, AK4104_REG_CONTROL1, val); +} + +static int ak4104_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + int val = 0; + + /* set the IEC958 bits: consumer mode, no copyright bit */ + val |= IEC958_AES0_CON_NOT_COPYRIGHT; + ak4104_spi_write(codec, AK4104_REG_CHN_STATUS(0), val); + + val = 0; + + switch (params_rate(params)) { + case 44100: + val |= IEC958_AES3_CON_FS_44100; + break; + case 48000: + val |= IEC958_AES3_CON_FS_48000; + break; + case 32000: + val |= IEC958_AES3_CON_FS_32000; + break; + default: + dev_err(codec->dev, "unsupported sampling rate\n"); + return -EINVAL; + } + + return ak4104_spi_write(codec, AK4104_REG_CHN_STATUS(3), val); +} + +static struct snd_soc_dai_ops ak4101_dai_ops = { + .hw_params = ak4104_hw_params, + .set_fmt = ak4104_set_dai_fmt, +}; + +struct snd_soc_dai ak4104_dai = { + .name = DRV_NAME, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_32000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S24_LE + }, + .ops = &ak4101_dai_ops, +}; + +static struct snd_soc_codec *ak4104_codec; + +static int ak4104_spi_probe(struct spi_device *spi) +{ + struct snd_soc_codec *codec; + struct ak4104_private *ak4104; + int ret, val; + + spi->bits_per_word = 8; + spi->mode = SPI_MODE_0; + ret = spi_setup(spi); + if (ret < 0) + return ret; + + ak4104 = kzalloc(sizeof(struct ak4104_private), GFP_KERNEL); + if (!ak4104) { + dev_err(&spi->dev, "could not allocate codec\n"); + return -ENOMEM; + } + + codec = &ak4104->codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->dev = &spi->dev; + codec->name = DRV_NAME; + codec->owner = THIS_MODULE; + codec->dai = &ak4104_dai; + codec->num_dai = 1; + codec->private_data = ak4104; + codec->control_data = spi; + codec->reg_cache = ak4104->reg_cache; + codec->reg_cache_size = AK4104_NUM_REGS; + + /* read all regs and fill the cache */ + ret = ak4104_fill_cache(codec); + if (ret < 0) { + dev_err(&spi->dev, "failed to fill register cache\n"); + return ret; + } + + /* read the 'reserved' register - according to the datasheet, it + * should contain 0x5b. Not a good way to verify the presence of + * the device, but there is no hardware ID register. */ + if (ak4104_read_reg_cache(codec, AK4104_REG_RESERVED) != + AK4104_RESERVED_VAL) { + ret = -ENODEV; + goto error_free_codec; + } + + /* set power-up and non-reset bits */ + val = ak4104_read_reg_cache(codec, AK4104_REG_CONTROL1); + val |= AK4104_CONTROL1_PW | AK4104_CONTROL1_RSTN; + ret = ak4104_spi_write(codec, AK4104_REG_CONTROL1, val); + if (ret < 0) + goto error_free_codec; + + /* enable transmitter */ + val = ak4104_read_reg_cache(codec, AK4104_REG_TX); + val |= AK4104_TX_TXE; + ret = ak4104_spi_write(codec, AK4104_REG_TX, val); + if (ret < 0) + goto error_free_codec; + + ak4104_codec = codec; + ret = snd_soc_register_dai(&ak4104_dai); + if (ret < 0) { + dev_err(&spi->dev, "failed to register DAI\n"); + goto error_free_codec; + } + + spi_set_drvdata(spi, ak4104); + dev_info(&spi->dev, "SPI device initialized\n"); + return 0; + +error_free_codec: + kfree(ak4104); + ak4104_dai.dev = NULL; + return ret; +} + +static int __devexit ak4104_spi_remove(struct spi_device *spi) +{ + int ret, val; + struct ak4104_private *ak4104 = spi_get_drvdata(spi); + + val = ak4104_read_reg_cache(&ak4104->codec, AK4104_REG_CONTROL1); + if (val < 0) + return val; + + /* clear power-up and non-reset bits */ + val &= ~(AK4104_CONTROL1_PW | AK4104_CONTROL1_RSTN); + ret = ak4104_spi_write(&ak4104->codec, AK4104_REG_CONTROL1, val); + if (ret < 0) + return ret; + + ak4104_codec = NULL; + kfree(ak4104); + return 0; +} + +static int ak4104_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = ak4104_codec; + int ret; + + /* Connect the codec to the socdev. snd_soc_new_pcms() needs this. */ + socdev->card->codec = codec; + + /* Register PCMs */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms\n"); + return ret; + } + + /* Register the socdev */ + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card\n"); + snd_soc_free_pcms(socdev); + return ret; + } + + return 0; +} + +static int ak4104_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + snd_soc_free_pcms(socdev); + return 0; +}; + +struct snd_soc_codec_device soc_codec_device_ak4104 = { + .probe = ak4104_probe, + .remove = ak4104_remove +}; +EXPORT_SYMBOL_GPL(soc_codec_device_ak4104); + +static struct spi_driver ak4104_spi_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = ak4104_spi_probe, + .remove = __devexit_p(ak4104_spi_remove), +}; + +static int __init ak4104_init(void) +{ + pr_info("Asahi Kasei AK4104 ALSA SoC Codec Driver\n"); + return spi_register_driver(&ak4104_spi_driver); +} +module_init(ak4104_init); + +static void __exit ak4104_exit(void) +{ + spi_unregister_driver(&ak4104_spi_driver); +} +module_exit(ak4104_exit); + +MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>"); +MODULE_DESCRIPTION("Asahi Kasei AK4104 ALSA SoC driver"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/codecs/ak4104.h b/sound/soc/codecs/ak4104.h new file mode 100644 index 0000000..eb88fe7 --- /dev/null +++ b/sound/soc/codecs/ak4104.h @@ -0,0 +1,7 @@ +#ifndef _AK4104_H +#define _AK4104_H + +extern struct snd_soc_dai ak4104_dai; +extern struct snd_soc_codec_device soc_codec_device_ak4104; + +#endif diff --git a/sound/soc/codecs/ak4535.c b/sound/soc/codecs/ak4535.c index 81300d8d..1f63d38 100644 --- a/sound/soc/codecs/ak4535.c +++ b/sound/soc/codecs/ak4535.c @@ -155,21 +155,6 @@ static const struct snd_kcontrol_new ak4535_snd_controls[] = { SOC_SINGLE("Mic Sidetone Volume", AK4535_VOL, 4, 7, 0), }; -/* add non dapm controls */ -static int ak4535_add_controls(struct snd_soc_codec *codec) -{ - int err, i; - - for (i = 0; i < ARRAY_SIZE(ak4535_snd_controls); i++) { - err = snd_ctl_add(codec->card, - snd_soc_cnew(&ak4535_snd_controls[i], codec, NULL)); - if (err < 0) - return err; - } - - return 0; -} - /* Mono 1 Mixer */ static const struct snd_kcontrol_new ak4535_mono1_mixer_controls[] = { SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG1, 4, 1, 0), @@ -344,7 +329,7 @@ static int ak4535_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct ak4535_priv *ak4535 = codec->private_data; u8 mode2 = ak4535_read_reg_cache(codec, AK4535_MODE2) & ~(0x3 << 5); int rate = params_rate(params), fs = 256; @@ -436,6 +421,13 @@ static int ak4535_set_bias_level(struct snd_soc_codec *codec, SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) +static struct snd_soc_dai_ops ak4535_dai_ops = { + .hw_params = ak4535_hw_params, + .set_fmt = ak4535_set_dai_fmt, + .digital_mute = ak4535_mute, + .set_sysclk = ak4535_set_dai_sysclk, +}; + struct snd_soc_dai ak4535_dai = { .name = "AK4535", .playback = { @@ -450,19 +442,14 @@ struct snd_soc_dai ak4535_dai = { .channels_max = 2, .rates = AK4535_RATES, .formats = SNDRV_PCM_FMTBIT_S16_LE,}, - .ops = { - .hw_params = ak4535_hw_params, - .set_fmt = ak4535_set_dai_fmt, - .digital_mute = ak4535_mute, - .set_sysclk = ak4535_set_dai_sysclk, - }, + .ops = &ak4535_dai_ops, }; EXPORT_SYMBOL_GPL(ak4535_dai); static int ak4535_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; ak4535_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; @@ -471,7 +458,7 @@ static int ak4535_suspend(struct platform_device *pdev, pm_message_t state) static int ak4535_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; ak4535_sync(codec); ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY); ak4535_set_bias_level(codec, codec->suspend_bias_level); @@ -484,7 +471,7 @@ static int ak4535_resume(struct platform_device *pdev) */ static int ak4535_init(struct snd_soc_device *socdev) { - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int ret = 0; codec->name = "AK4535"; @@ -510,7 +497,8 @@ static int ak4535_init(struct snd_soc_device *socdev) /* power on device */ ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - ak4535_add_controls(codec); + snd_soc_add_controls(codec, ak4535_snd_controls, + ARRAY_SIZE(ak4535_snd_controls)); ak4535_add_widgets(codec); ret = snd_soc_init_card(socdev); if (ret < 0) { @@ -537,7 +525,7 @@ static int ak4535_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct snd_soc_device *socdev = ak4535_socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int ret; i2c_set_clientdata(i2c, codec); @@ -636,7 +624,7 @@ static int ak4535_probe(struct platform_device *pdev) } codec->private_data = ak4535; - socdev->codec = codec; + socdev->card->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); @@ -663,7 +651,7 @@ static int ak4535_probe(struct platform_device *pdev) static int ak4535_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; if (codec->control_data) ak4535_set_bias_level(codec, SND_SOC_BIAS_OFF); diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index f1aa0c3..7fa09a3 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -3,27 +3,22 @@ * * Author: Timur Tabi <timur@freescale.com> * - * Copyright 2007 Freescale Semiconductor, Inc. This file is licensed under - * the terms of the GNU General Public License version 2. This program - * is licensed "as is" without any warranty of any kind, whether express - * or implied. + * Copyright 2007-2009 Freescale Semiconductor, Inc. This file is licensed + * under the terms of the GNU General Public License version 2. This + * program is licensed "as is" without any warranty of any kind, whether + * express or implied. * * This is an ASoC device driver for the Cirrus Logic CS4270 codec. * * Current features/limitations: * - * 1) Software mode is supported. Stand-alone mode is automatically - * selected if I2C is disabled or if a CS4270 is not found on the I2C - * bus. However, stand-alone mode is only partially implemented because - * there is no mechanism yet for this driver and the machine driver to - * communicate the values of the M0, M1, MCLK1, and MCLK2 pins. - * 2) Only I2C is supported, not SPI - * 3) Only Master mode is supported, not Slave. - * 4) The machine driver's 'startup' function must call - * cs4270_set_dai_sysclk() with the value of MCLK. - * 5) Only I2S and left-justified modes are supported - * 6) Power management is not supported - * 7) The only supported control is volume and hardware mute (if enabled) + * - Software mode is supported. Stand-alone mode is not supported. + * - Only I2C is supported, not SPI + * - Support for master and slave mode + * - The machine driver's 'startup' function must call + * cs4270_set_dai_sysclk() with the value of MCLK. + * - Only I2S and left-justified modes are supported + * - Power management is not supported */ #include <linux/module.h> @@ -35,18 +30,6 @@ #include "cs4270.h" -/* If I2C is defined, then we support software mode. However, if we're - not compiled as module but I2C is, then we can't use I2C calls. */ -#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) -#define USE_I2C -#endif - -/* Private data for the CS4270 */ -struct cs4270_private { - unsigned int mclk; /* Input frequency of the MCLK pin */ - unsigned int mode; /* The mode (I2S or left-justified) */ -}; - /* * The codec isn't really big-endian or little-endian, since the I2S * interface requires data to be sent serially with the MSbit first. @@ -60,8 +43,6 @@ struct cs4270_private { SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE) -#ifdef USE_I2C - /* CS4270 registers addresses */ #define CS4270_CHIPID 0x01 /* Chip ID */ #define CS4270_PWRCTL 0x02 /* Power Control */ @@ -121,8 +102,22 @@ struct cs4270_private { #define CS4270_MUTE_DAC_A 0x01 #define CS4270_MUTE_DAC_B 0x02 -/* - * Clock Ratio Selection for Master Mode with I2C enabled +/* Private data for the CS4270 */ +struct cs4270_private { + struct snd_soc_codec codec; + u8 reg_cache[CS4270_NUMREGS]; + unsigned int mclk; /* Input frequency of the MCLK pin */ + unsigned int mode; /* The mode (I2S or left-justified) */ + unsigned int slave_mode; +}; + +/** + * struct cs4270_mode_ratios - clock ratio tables + * @ratio: the ratio of MCLK to the sample rate + * @speed_mode: the Speed Mode bits to set in the Mode Control register for + * this ratio + * @mclk: the Ratio Select bits to set in the Mode Control register for this + * ratio * * The data for this chart is taken from Table 5 of the CS4270 reference * manual. @@ -131,31 +126,30 @@ struct cs4270_private { * It is also used by cs4270_set_dai_sysclk() to tell ALSA which sampling * rates the CS4270 currently supports. * - * Each element in this array corresponds to the ratios in mclk_ratios[]. - * These two arrays need to be in sync. - * - * 'speed_mode' is the corresponding bit pattern to be written to the + * @speed_mode is the corresponding bit pattern to be written to the * MODE bits of the Mode Control Register * - * 'mclk' is the corresponding bit pattern to be wirten to the MCLK bits of + * @mclk is the corresponding bit pattern to be wirten to the MCLK bits of * the Mode Control Register. * * In situations where a single ratio is represented by multiple speed * modes, we favor the slowest speed. E.g, for a ratio of 128, we pick * double-speed instead of quad-speed. However, the CS4270 errata states - * that Divide-By-1.5 can cause failures, so we avoid that mode where + * that divide-By-1.5 can cause failures, so we avoid that mode where * possible. * - * ERRATA: There is an errata for the CS4270 where divide-by-1.5 does not - * work if VD = 3.3V. If this effects you, select the + * Errata: There is an errata for the CS4270 where divide-by-1.5 does not + * work if Vd is 3.3V. If this effects you, select the * CONFIG_SND_SOC_CS4270_VD33_ERRATA Kconfig option, and the driver will * never select any sample rates that require divide-by-1.5. */ -static struct { +struct cs4270_mode_ratios { unsigned int ratio; u8 speed_mode; u8 mclk; -} cs4270_mode_ratios[] = { +}; + +static struct cs4270_mode_ratios cs4270_mode_ratios[] = { {64, CS4270_MODE_4X, CS4270_MODE_DIV1}, #ifndef CONFIG_SND_SOC_CS4270_VD33_ERRATA {96, CS4270_MODE_4X, CS4270_MODE_DIV15}, @@ -172,34 +166,27 @@ static struct { /* The number of MCLK/LRCK ratios supported by the CS4270 */ #define NUM_MCLK_RATIOS ARRAY_SIZE(cs4270_mode_ratios) -/* - * Determine the CS4270 samples rates. +/** + * cs4270_set_dai_sysclk - determine the CS4270 samples rates. + * @codec_dai: the codec DAI + * @clk_id: the clock ID (ignored) + * @freq: the MCLK input frequency + * @dir: the clock direction (ignored) * - * 'freq' is the input frequency to MCLK. The other parameters are ignored. + * This function is used to tell the codec driver what the input MCLK + * frequency is. * * The value of MCLK is used to determine which sample rates are supported * by the CS4270. The ratio of MCLK / Fs must be equal to one of nine - * support values: 64, 96, 128, 192, 256, 384, 512, 768, and 1024. + * supported values - 64, 96, 128, 192, 256, 384, 512, 768, and 1024. * * This function calculates the nine ratios and determines which ones match * a standard sample rate. If there's a match, then it is added to the list - * of support sample rates. + * of supported sample rates. * * This function must be called by the machine driver's 'startup' function, * otherwise the list of supported sample rates will not be available in * time for ALSA. - * - * Note that in stand-alone mode, the sample rate is determined by input - * pins M0, M1, MDIV1, and MDIV2. Also in stand-alone mode, divide-by-3 - * is not a programmable option. However, divide-by-3 is not an available - * option in stand-alone mode. This cases two problems: a ratio of 768 is - * not available (it requires divide-by-3) and B) ratios 192 and 384 can - * only be selected with divide-by-1.5, but there is an errate that make - * this selection difficult. - * - * In addition, there is no mechanism for communicating with the machine - * driver what the input settings can be. This would need to be implemented - * for stand-alone mode to work. */ static int cs4270_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) @@ -225,7 +212,7 @@ static int cs4270_set_dai_sysclk(struct snd_soc_dai *codec_dai, rates &= ~SNDRV_PCM_RATE_KNOT; if (!rates) { - printk(KERN_ERR "cs4270: could not find a valid sample rate\n"); + dev_err(codec->dev, "could not find a valid sample rate\n"); return -EINVAL; } @@ -240,8 +227,10 @@ static int cs4270_set_dai_sysclk(struct snd_soc_dai *codec_dai, return 0; } -/* - * Configure the codec for the selected audio format +/** + * cs4270_set_dai_fmt - configure the codec for the selected audio format + * @codec_dai: the codec DAI + * @format: a SND_SOC_DAIFMT_x value indicating the data format * * This function takes a bitmask of SND_SOC_DAIFMT_x bits and programs the * codec accordingly. @@ -258,32 +247,43 @@ static int cs4270_set_dai_fmt(struct snd_soc_dai *codec_dai, struct cs4270_private *cs4270 = codec->private_data; int ret = 0; + /* set DAI format */ switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: case SND_SOC_DAIFMT_LEFT_J: cs4270->mode = format & SND_SOC_DAIFMT_FORMAT_MASK; break; default: - printk(KERN_ERR "cs4270: invalid DAI format\n"); + dev_err(codec->dev, "invalid dai format\n"); + ret = -EINVAL; + } + + /* set master/slave audio interface */ + switch (format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + cs4270->slave_mode = 1; + break; + case SND_SOC_DAIFMT_CBM_CFM: + cs4270->slave_mode = 0; + break; + default: + /* all other modes are unsupported by the hardware */ ret = -EINVAL; } return ret; } -/* - * A list of addresses on which this CS4270 could use. I2C addresses are - * 7 bits. For the CS4270, the upper four bits are always 1001, and the - * lower three bits are determined via the AD2, AD1, and AD0 pins - * (respectively). - */ -static const unsigned short normal_i2c[] = { - 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, I2C_CLIENT_END -}; -I2C_CLIENT_INSMOD; - -/* - * Pre-fill the CS4270 register cache. +/** + * cs4270_fill_cache - pre-fill the CS4270 register cache. + * @codec: the codec for this CS4270 + * + * This function fills in the CS4270 register cache by reading the register + * values from the hardware. + * + * This CS4270 registers are cached to avoid excessive I2C I/O operations. + * After the initial read to pre-fill the cache, the CS4270 never updates + * the register values, so we won't have a cache coherency problem. * * We use the auto-increment feature of the CS4270 to read all registers in * one shot. @@ -298,7 +298,7 @@ static int cs4270_fill_cache(struct snd_soc_codec *codec) CS4270_FIRSTREG | 0x80, CS4270_NUMREGS, cache); if (length != CS4270_NUMREGS) { - printk(KERN_ERR "cs4270: I2C read failure, addr=0x%x\n", + dev_err(codec->dev, "i2c read failure, addr=0x%x\n", i2c_client->addr); return -EIO; } @@ -306,12 +306,17 @@ static int cs4270_fill_cache(struct snd_soc_codec *codec) return 0; } -/* - * Read from the CS4270 register cache. +/** + * cs4270_read_reg_cache - read from the CS4270 register cache. + * @codec: the codec for this CS4270 + * @reg: the register to read + * + * This function returns the value for a given register. It reads only from + * the register cache, not the hardware itself. * * This CS4270 registers are cached to avoid excessive I2C I/O operations. * After the initial read to pre-fill the cache, the CS4270 never updates - * the register values, so we won't have a cache coherncy problem. + * the register values, so we won't have a cache coherency problem. */ static unsigned int cs4270_read_reg_cache(struct snd_soc_codec *codec, unsigned int reg) @@ -324,8 +329,11 @@ static unsigned int cs4270_read_reg_cache(struct snd_soc_codec *codec, return cache[reg - CS4270_FIRSTREG]; } -/* - * Write to a CS4270 register via the I2C bus. +/** + * cs4270_i2c_write - write to a CS4270 register via the I2C bus. + * @codec: the codec for this CS4270 + * @reg: the register to write + * @value: the value to write to the register * * This function writes the given value to the given CS4270 register, and * also updates the register cache. @@ -346,7 +354,7 @@ static int cs4270_i2c_write(struct snd_soc_codec *codec, unsigned int reg, if (cache[reg - CS4270_FIRSTREG] != value) { struct i2c_client *client = codec->control_data; if (i2c_smbus_write_byte_data(client, reg, value)) { - printk(KERN_ERR "cs4270: I2C write failed\n"); + dev_err(codec->dev, "i2c write failed\n"); return -EIO; } @@ -357,11 +365,17 @@ static int cs4270_i2c_write(struct snd_soc_codec *codec, unsigned int reg, return 0; } -/* - * Program the CS4270 with the given hardware parameters. +/** + * cs4270_hw_params - program the CS4270 with the given hardware parameters. + * @substream: the audio stream + * @params: the hardware parameters to set + * @dai: the SOC DAI (ignored) + * + * This function programs the hardware with the values provided. + * Specifically, the sample rate and the data format. * - * The .ops functions are used to provide board-specific data, like - * input frequencies, to this driver. This function takes that information, + * The .ops functions are used to provide board-specific data, like input + * frequencies, to this driver. This function takes that information, * combines it with the hardware parameters provided, and programs the * hardware accordingly. */ @@ -371,7 +385,7 @@ static int cs4270_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct cs4270_private *cs4270 = codec->private_data; int ret; unsigned int i; @@ -391,33 +405,28 @@ static int cs4270_hw_params(struct snd_pcm_substream *substream, if (i == NUM_MCLK_RATIOS) { /* We did not find a matching ratio */ - printk(KERN_ERR "cs4270: could not find matching ratio\n"); + dev_err(codec->dev, "could not find matching ratio\n"); return -EINVAL; } - /* Freeze and power-down the codec */ - - ret = snd_soc_write(codec, CS4270_PWRCTL, CS4270_PWRCTL_FREEZE | - CS4270_PWRCTL_PDN_ADC | CS4270_PWRCTL_PDN_DAC | - CS4270_PWRCTL_PDN); - if (ret < 0) { - printk(KERN_ERR "cs4270: I2C write failed\n"); - return ret; - } - - /* Program the mode control register */ + /* Set the sample rate */ reg = snd_soc_read(codec, CS4270_MODE); reg &= ~(CS4270_MODE_SPEED_MASK | CS4270_MODE_DIV_MASK); - reg |= cs4270_mode_ratios[i].speed_mode | cs4270_mode_ratios[i].mclk; + reg |= cs4270_mode_ratios[i].mclk; + + if (cs4270->slave_mode) + reg |= CS4270_MODE_SLAVE; + else + reg |= cs4270_mode_ratios[i].speed_mode; ret = snd_soc_write(codec, CS4270_MODE, reg); if (ret < 0) { - printk(KERN_ERR "cs4270: I2C write failed\n"); + dev_err(codec->dev, "i2c write failed\n"); return ret; } - /* Program the format register */ + /* Set the DAI format */ reg = snd_soc_read(codec, CS4270_FORMAT); reg &= ~(CS4270_FORMAT_DAC_MASK | CS4270_FORMAT_ADC_MASK); @@ -430,55 +439,23 @@ static int cs4270_hw_params(struct snd_pcm_substream *substream, reg |= CS4270_FORMAT_DAC_LJ | CS4270_FORMAT_ADC_LJ; break; default: - printk(KERN_ERR "cs4270: unknown format\n"); + dev_err(codec->dev, "unknown dai format\n"); return -EINVAL; } ret = snd_soc_write(codec, CS4270_FORMAT, reg); if (ret < 0) { - printk(KERN_ERR "cs4270: I2C write failed\n"); - return ret; - } - - /* Disable auto-mute. This feature appears to be buggy, because in - some situations, auto-mute will not deactivate when it should. */ - - reg = snd_soc_read(codec, CS4270_MUTE); - reg &= ~CS4270_MUTE_AUTO; - ret = snd_soc_write(codec, CS4270_MUTE, reg); - if (ret < 0) { - printk(KERN_ERR "cs4270: I2C write failed\n"); - return ret; - } - - /* Disable automatic volume control. It's enabled by default, and - * it causes volume change commands to be delayed, sometimes until - * after playback has started. - */ - - reg = cs4270_read_reg_cache(codec, CS4270_TRANS); - reg &= ~(CS4270_TRANS_SOFT | CS4270_TRANS_ZERO); - ret = cs4270_i2c_write(codec, CS4270_TRANS, reg); - if (ret < 0) { - printk(KERN_ERR "I2C write failed\n"); - return ret; - } - - /* Thaw and power-up the codec */ - - ret = snd_soc_write(codec, CS4270_PWRCTL, 0); - if (ret < 0) { - printk(KERN_ERR "cs4270: I2C write failed\n"); + dev_err(codec->dev, "i2c write failed\n"); return ret; } return ret; } -#ifdef CONFIG_SND_SOC_CS4270_HWMUTE - -/* - * Set the CS4270 external mute +/** + * cs4270_mute - enable/disable the CS4270 external mute + * @dai: the SOC DAI + * @mute: 0 = disable mute, 1 = enable mute * * This function toggles the mute bits in the MUTE register. The CS4270's * mute capability is intended for external muting circuitry, so if the @@ -493,276 +470,306 @@ static int cs4270_mute(struct snd_soc_dai *dai, int mute) reg6 = snd_soc_read(codec, CS4270_MUTE); if (mute) - reg6 |= CS4270_MUTE_ADC_A | CS4270_MUTE_ADC_B | - CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B; + reg6 |= CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B; else - reg6 &= ~(CS4270_MUTE_ADC_A | CS4270_MUTE_ADC_B | - CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B); + reg6 &= ~(CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B); return snd_soc_write(codec, CS4270_MUTE, reg6); } -#endif - -static int cs4270_i2c_probe(struct i2c_client *, const struct i2c_device_id *); - /* A list of non-DAPM controls that the CS4270 supports */ static const struct snd_kcontrol_new cs4270_snd_controls[] = { SOC_DOUBLE_R("Master Playback Volume", - CS4270_VOLA, CS4270_VOLB, 0, 0xFF, 1) -}; - -static const struct i2c_device_id cs4270_id[] = { - {"cs4270", 0}, - {} -}; -MODULE_DEVICE_TABLE(i2c, cs4270_id); - -static struct i2c_driver cs4270_i2c_driver = { - .driver = { - .name = "CS4270 I2C", - .owner = THIS_MODULE, - }, - .id_table = cs4270_id, - .probe = cs4270_i2c_probe, + CS4270_VOLA, CS4270_VOLB, 0, 0xFF, 1), + SOC_SINGLE("Digital Sidetone Switch", CS4270_FORMAT, 5, 1, 0), + SOC_SINGLE("Soft Ramp Switch", CS4270_TRANS, 6, 1, 0), + SOC_SINGLE("Zero Cross Switch", CS4270_TRANS, 5, 1, 0), + SOC_SINGLE("Popguard Switch", CS4270_MODE, 0, 1, 1), + SOC_SINGLE("Auto-Mute Switch", CS4270_MUTE, 5, 1, 0), + SOC_DOUBLE("Master Capture Switch", CS4270_MUTE, 3, 4, 1, 0) }; /* - * Global variable to store socdev for i2c probe function. + * cs4270_codec - global variable to store codec for the ASoC probe function * * If struct i2c_driver had a private_data field, we wouldn't need to use - * cs4270_socdec. This is the only way to pass the socdev structure to - * cs4270_i2c_probe(). - * - * The real solution to cs4270_socdev is to create a mechanism - * that maps I2C addresses to snd_soc_device structures. Perhaps the - * creation of the snd_soc_device object should be moved out of - * cs4270_probe() and into cs4270_i2c_probe(), but that would make this - * driver dependent on I2C. The CS4270 supports "stand-alone" mode, whereby - * the chip is *not* connected to the I2C bus, but is instead configured via - * input pins. + * cs4270_codec. This is the only way to pass the codec structure from + * cs4270_i2c_probe() to cs4270_probe(). Unfortunately, there is no good + * way to synchronize these two functions. cs4270_i2c_probe() can be called + * multiple times before cs4270_probe() is called even once. So for now, we + * also only allow cs4270_i2c_probe() to be run once. That means that we do + * not support more than one cs4270 device in the system, at least for now. */ -static struct snd_soc_device *cs4270_socdev; +static struct snd_soc_codec *cs4270_codec; -/* - * Initialize the I2C interface of the CS4270 - * - * This function is called for whenever the I2C subsystem finds a device - * at a particular address. +static struct snd_soc_dai_ops cs4270_dai_ops = { + .hw_params = cs4270_hw_params, + .set_sysclk = cs4270_set_dai_sysclk, + .set_fmt = cs4270_set_dai_fmt, + .digital_mute = cs4270_mute, +}; + +struct snd_soc_dai cs4270_dai = { + .name = "cs4270", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = 0, + .formats = CS4270_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = 0, + .formats = CS4270_FORMATS, + }, + .ops = &cs4270_dai_ops, +}; +EXPORT_SYMBOL_GPL(cs4270_dai); + +/** + * cs4270_probe - ASoC probe function + * @pdev: platform device * - * Note: snd_soc_new_pcms() must be called before this function can be called, - * because of snd_ctl_add(). + * This function is called when ASoC has all the pieces it needs to + * instantiate a sound driver. */ -static int cs4270_i2c_probe(struct i2c_client *i2c_client, - const struct i2c_device_id *id) +static int cs4270_probe(struct platform_device *pdev) { - struct snd_soc_device *socdev = cs4270_socdev; - struct snd_soc_codec *codec = socdev->codec; - int i; - int ret = 0; - - /* Probing all possible addresses has one drawback: if there are - multiple CS4270s on the bus, then you cannot specify which - socdev is matched with which CS4270. For now, we just reject - this I2C device if the socdev already has one attached. */ - if (codec->control_data) - return -ENODEV; - - /* Note: codec_dai->codec is NULL here */ - - codec->reg_cache = kzalloc(CS4270_NUMREGS, GFP_KERNEL); - if (!codec->reg_cache) { - printk(KERN_ERR "cs4270: could not allocate register cache\n"); - ret = -ENOMEM; - goto error; - } + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = cs4270_codec; + int ret; - /* Verify that we have a CS4270 */ + /* Connect the codec to the socdev. snd_soc_new_pcms() needs this. */ + socdev->card->codec = codec; - ret = i2c_smbus_read_byte_data(i2c_client, CS4270_CHIPID); + /* Register PCMs */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { - printk(KERN_ERR "cs4270: failed to read I2C\n"); - goto error; - } - /* The top four bits of the chip ID should be 1100. */ - if ((ret & 0xF0) != 0xC0) { - /* The device at this address is not a CS4270 codec */ - ret = -ENODEV; - goto error; + dev_err(codec->dev, "failed to create pcms\n"); + return ret; } - printk(KERN_INFO "cs4270: found device at I2C address %X\n", - i2c_client->addr); - printk(KERN_INFO "cs4270: hardware revision %X\n", ret & 0xF); - - codec->control_data = i2c_client; - codec->read = cs4270_read_reg_cache; - codec->write = cs4270_i2c_write; - codec->reg_cache_size = CS4270_NUMREGS; - - /* The I2C interface is set up, so pre-fill our register cache */ - - ret = cs4270_fill_cache(codec); + /* Add the non-DAPM controls */ + ret = snd_soc_add_controls(codec, cs4270_snd_controls, + ARRAY_SIZE(cs4270_snd_controls)); if (ret < 0) { - printk(KERN_ERR "cs4270: failed to fill register cache\n"); - goto error; + dev_err(codec->dev, "failed to add controls\n"); + goto error_free_pcms; } - /* Add the non-DAPM controls */ - - for (i = 0; i < ARRAY_SIZE(cs4270_snd_controls); i++) { - struct snd_kcontrol *kctrl = - snd_soc_cnew(&cs4270_snd_controls[i], codec, NULL); - - ret = snd_ctl_add(codec->card, kctrl); - if (ret < 0) - goto error; + /* And finally, register the socdev */ + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card\n"); + goto error_free_pcms; } - i2c_set_clientdata(i2c_client, codec); - return 0; -error: - codec->control_data = NULL; - - kfree(codec->reg_cache); - codec->reg_cache = NULL; - codec->reg_cache_size = 0; +error_free_pcms: + snd_soc_free_pcms(socdev); return ret; } -#endif /* USE_I2C*/ +/** + * cs4270_remove - ASoC remove function + * @pdev: platform device + * + * This function is the counterpart to cs4270_probe(). + */ +static int cs4270_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); -struct snd_soc_dai cs4270_dai = { - .name = "CS4270", - .playback = { - .stream_name = "Playback", - .channels_min = 1, - .channels_max = 2, - .rates = 0, - .formats = CS4270_FORMATS, - }, - .capture = { - .stream_name = "Capture", - .channels_min = 1, - .channels_max = 2, - .rates = 0, - .formats = CS4270_FORMATS, - }, + snd_soc_free_pcms(socdev); + + return 0; }; -EXPORT_SYMBOL_GPL(cs4270_dai); -/* - * ASoC probe function +/** + * cs4270_i2c_probe - initialize the I2C interface of the CS4270 + * @i2c_client: the I2C client object + * @id: the I2C device ID (ignored) * - * This function is called when the machine driver calls - * platform_device_add(). + * This function is called whenever the I2C subsystem finds a device that + * matches the device ID given via a prior call to i2c_add_driver(). */ -static int cs4270_probe(struct platform_device *pdev) +static int cs4270_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) { - struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec; - int ret = 0; + struct cs4270_private *cs4270; + unsigned int reg; + int ret; - printk(KERN_INFO "CS4270 ALSA SoC Codec\n"); + /* For now, we only support one cs4270 device in the system. See the + * comment for cs4270_codec. + */ + if (cs4270_codec) { + dev_err(&i2c_client->dev, "ignoring CS4270 at addr %X\n", + i2c_client->addr); + dev_err(&i2c_client->dev, "only one per board allowed\n"); + /* Should we return something other than ENODEV here? */ + return -ENODEV; + } + + /* Verify that we have a CS4270 */ + + ret = i2c_smbus_read_byte_data(i2c_client, CS4270_CHIPID); + if (ret < 0) { + dev_err(&i2c_client->dev, "failed to read i2c at addr %X\n", + i2c_client->addr); + return ret; + } + /* The top four bits of the chip ID should be 1100. */ + if ((ret & 0xF0) != 0xC0) { + dev_err(&i2c_client->dev, "device at addr %X is not a CS4270\n", + i2c_client->addr); + return -ENODEV; + } + + dev_info(&i2c_client->dev, "found device at i2c address %X\n", + i2c_client->addr); + dev_info(&i2c_client->dev, "hardware revision %X\n", ret & 0xF); /* Allocate enough space for the snd_soc_codec structure and our private data together. */ - codec = kzalloc(ALIGN(sizeof(struct snd_soc_codec), 4) + - sizeof(struct cs4270_private), GFP_KERNEL); - if (!codec) { - printk(KERN_ERR "cs4270: Could not allocate codec structure\n"); + cs4270 = kzalloc(sizeof(struct cs4270_private), GFP_KERNEL); + if (!cs4270) { + dev_err(&i2c_client->dev, "could not allocate codec\n"); return -ENOMEM; } + codec = &cs4270->codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); + codec->dev = &i2c_client->dev; codec->name = "CS4270"; codec->owner = THIS_MODULE; codec->dai = &cs4270_dai; codec->num_dai = 1; - codec->private_data = (void *) codec + - ALIGN(sizeof(struct snd_soc_codec), 4); - - socdev->codec = codec; + codec->private_data = cs4270; + codec->control_data = i2c_client; + codec->read = cs4270_read_reg_cache; + codec->write = cs4270_i2c_write; + codec->reg_cache = cs4270->reg_cache; + codec->reg_cache_size = CS4270_NUMREGS; - /* Register PCMs */ + /* The I2C interface is set up, so pre-fill our register cache */ - ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + ret = cs4270_fill_cache(codec); if (ret < 0) { - printk(KERN_ERR "cs4270: failed to create PCMs\n"); + dev_err(&i2c_client->dev, "failed to fill register cache\n"); goto error_free_codec; } -#ifdef USE_I2C - cs4270_socdev = socdev; + /* Disable auto-mute. This feature appears to be buggy. In some + * situations, auto-mute will not deactivate when it should, so we want + * this feature disabled by default. An application (e.g. alsactl) can + * re-enabled it by using the controls. + */ - ret = i2c_add_driver(&cs4270_i2c_driver); - if (ret) { - printk(KERN_ERR "cs4270: failed to attach driver"); - goto error_free_pcms; + reg = cs4270_read_reg_cache(codec, CS4270_MUTE); + reg &= ~CS4270_MUTE_AUTO; + ret = cs4270_i2c_write(codec, CS4270_MUTE, reg); + if (ret < 0) { + dev_err(&i2c_client->dev, "i2c write failed\n"); + return ret; } - /* Did we find a CS4270 on the I2C bus? */ - if (codec->control_data) { - /* Initialize codec ops */ - cs4270_dai.ops.hw_params = cs4270_hw_params; - cs4270_dai.ops.set_sysclk = cs4270_set_dai_sysclk; - cs4270_dai.ops.set_fmt = cs4270_set_dai_fmt; -#ifdef CONFIG_SND_SOC_CS4270_HWMUTE - cs4270_dai.ops.digital_mute = cs4270_mute; -#endif - } else - printk(KERN_INFO "cs4270: no I2C device found, " - "using stand-alone mode\n"); -#else - printk(KERN_INFO "cs4270: I2C disabled, using stand-alone mode\n"); -#endif + /* Disable automatic volume control. The hardware enables, and it + * causes volume change commands to be delayed, sometimes until after + * playback has started. An application (e.g. alsactl) can + * re-enabled it by using the controls. + */ - ret = snd_soc_init_card(socdev); + reg = cs4270_read_reg_cache(codec, CS4270_TRANS); + reg &= ~(CS4270_TRANS_SOFT | CS4270_TRANS_ZERO); + ret = cs4270_i2c_write(codec, CS4270_TRANS, reg); if (ret < 0) { - printk(KERN_ERR "cs4270: failed to register card\n"); - goto error_del_driver; + dev_err(&i2c_client->dev, "i2c write failed\n"); + return ret; } - return 0; + /* Initialize the DAI. Normally, we'd prefer to have a kmalloc'd DAI + * structure for each CS4270 device, but the machine driver needs to + * have a pointer to the DAI structure, so for now it must be a global + * variable. + */ + cs4270_dai.dev = &i2c_client->dev; -error_del_driver: -#ifdef USE_I2C - i2c_del_driver(&cs4270_i2c_driver); + /* Register the DAI. If all the other ASoC driver have already + * registered, then this will call our probe function, so + * cs4270_codec needs to be ready. + */ + cs4270_codec = codec; + ret = snd_soc_register_dai(&cs4270_dai); + if (ret < 0) { + dev_err(&i2c_client->dev, "failed to register DAIe\n"); + goto error_free_codec; + } -error_free_pcms: -#endif - snd_soc_free_pcms(socdev); + i2c_set_clientdata(i2c_client, cs4270); + + return 0; error_free_codec: - kfree(socdev->codec); - socdev->codec = NULL; + kfree(cs4270); + cs4270_codec = NULL; + cs4270_dai.dev = NULL; return ret; } -static int cs4270_remove(struct platform_device *pdev) +/** + * cs4270_i2c_remove - remove an I2C device + * @i2c_client: the I2C client object + * + * This function is the counterpart to cs4270_i2c_probe(). + */ +static int cs4270_i2c_remove(struct i2c_client *i2c_client) { - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - - snd_soc_free_pcms(socdev); - -#ifdef USE_I2C - i2c_del_driver(&cs4270_i2c_driver); -#endif + struct cs4270_private *cs4270 = i2c_get_clientdata(i2c_client); - kfree(socdev->codec); - socdev->codec = NULL; + kfree(cs4270); + cs4270_codec = NULL; + cs4270_dai.dev = NULL; return 0; } /* + * cs4270_id - I2C device IDs supported by this driver + */ +static struct i2c_device_id cs4270_id[] = { + {"cs4270", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cs4270_id); + +/* + * cs4270_i2c_driver - I2C device identification + * + * This structure tells the I2C subsystem how to identify and support a + * given I2C device type. + */ +static struct i2c_driver cs4270_i2c_driver = { + .driver = { + .name = "cs4270", + .owner = THIS_MODULE, + }, + .id_table = cs4270_id, + .probe = cs4270_i2c_probe, + .remove = cs4270_i2c_remove, +}; + +/* * ASoC codec device structure * * Assign this variable to the codec_dev field of the machine driver's @@ -776,13 +783,15 @@ EXPORT_SYMBOL_GPL(soc_codec_device_cs4270); static int __init cs4270_init(void) { - return snd_soc_register_dai(&cs4270_dai); + pr_info("Cirrus Logic CS4270 ALSA SoC Codec Driver\n"); + + return i2c_add_driver(&cs4270_i2c_driver); } module_init(cs4270_init); static void __exit cs4270_exit(void) { - snd_soc_unregister_dai(&cs4270_dai); + i2c_del_driver(&cs4270_i2c_driver); } module_exit(cs4270_exit); diff --git a/sound/soc/codecs/pcm3008.c b/sound/soc/codecs/pcm3008.c index 9a3e67e..5cda9e6 100644 --- a/sound/soc/codecs/pcm3008.c +++ b/sound/soc/codecs/pcm3008.c @@ -67,11 +67,11 @@ static int pcm3008_soc_probe(struct platform_device *pdev) printk(KERN_INFO "PCM3008 SoC Audio Codec %s\n", PCM3008_VERSION); - socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); - if (!socdev->codec) + socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (!socdev->card->codec) return -ENOMEM; - codec = socdev->codec; + codec = socdev->card->codec; mutex_init(&codec->mutex); codec->name = "PCM3008"; @@ -139,7 +139,7 @@ gpio_err: card_err: snd_soc_free_pcms(socdev); pcm_err: - kfree(socdev->codec); + kfree(socdev->card->codec); return ret; } @@ -147,7 +147,7 @@ pcm_err: static int pcm3008_soc_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct pcm3008_setup_data *setup = socdev->codec_data; if (!codec) @@ -155,7 +155,7 @@ static int pcm3008_soc_remove(struct platform_device *pdev) pcm3008_gpio_free(setup); snd_soc_free_pcms(socdev); - kfree(socdev->codec); + kfree(socdev->card->codec); return 0; } diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index cac3736..87f606c7 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -151,21 +151,6 @@ SOC_ENUM("Capture Source", ssm2602_enum[0]), SOC_ENUM("Playback De-emphasis", ssm2602_enum[1]), }; -/* add non dapm controls */ -static int ssm2602_add_controls(struct snd_soc_codec *codec) -{ - int err, i; - - for (i = 0; i < ARRAY_SIZE(ssm2602_snd_controls); i++) { - err = snd_ctl_add(codec->card, - snd_soc_cnew(&ssm2602_snd_controls[i], codec, NULL)); - if (err < 0) - return err; - } - - return 0; -} - /* Output Mixer */ static const struct snd_kcontrol_new ssm2602_output_mixer_controls[] = { SOC_DAPM_SINGLE("Line Bypass Switch", SSM2602_APANA, 3, 1, 0), @@ -291,7 +276,7 @@ static int ssm2602_hw_params(struct snd_pcm_substream *substream, u16 srate; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct ssm2602_priv *ssm2602 = codec->private_data; struct i2c_client *i2c = codec->control_data; u16 iface = ssm2602_read_reg_cache(codec, SSM2602_IFACE) & 0xfff3; @@ -336,7 +321,7 @@ static int ssm2602_startup(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct ssm2602_priv *ssm2602 = codec->private_data; struct i2c_client *i2c = codec->control_data; struct snd_pcm_runtime *master_runtime; @@ -373,7 +358,7 @@ static int ssm2602_pcm_prepare(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; /* set active */ ssm2602_write(codec, SSM2602_ACTIVE, ACTIVE_ACTIVATE_CODEC); @@ -385,7 +370,7 @@ static void ssm2602_shutdown(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct ssm2602_priv *ssm2602 = codec->private_data; /* deactivate */ if (!codec->active) @@ -521,6 +506,16 @@ static int ssm2602_set_bias_level(struct snd_soc_codec *codec, #define SSM2602_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static struct snd_soc_dai_ops ssm2602_dai_ops = { + .startup = ssm2602_startup, + .prepare = ssm2602_pcm_prepare, + .hw_params = ssm2602_hw_params, + .shutdown = ssm2602_shutdown, + .digital_mute = ssm2602_mute, + .set_sysclk = ssm2602_set_dai_sysclk, + .set_fmt = ssm2602_set_dai_fmt, +}; + struct snd_soc_dai ssm2602_dai = { .name = "SSM2602", .playback = { @@ -535,22 +530,14 @@ struct snd_soc_dai ssm2602_dai = { .channels_max = 2, .rates = SSM2602_RATES, .formats = SSM2602_FORMATS,}, - .ops = { - .startup = ssm2602_startup, - .prepare = ssm2602_pcm_prepare, - .hw_params = ssm2602_hw_params, - .shutdown = ssm2602_shutdown, - .digital_mute = ssm2602_mute, - .set_sysclk = ssm2602_set_dai_sysclk, - .set_fmt = ssm2602_set_dai_fmt, - } + .ops = &ssm2602_dai_ops, }; EXPORT_SYMBOL_GPL(ssm2602_dai); static int ssm2602_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; ssm2602_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; @@ -559,7 +546,7 @@ static int ssm2602_suspend(struct platform_device *pdev, pm_message_t state) static int ssm2602_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int i; u8 data[2]; u16 *cache = codec->reg_cache; @@ -581,7 +568,7 @@ static int ssm2602_resume(struct platform_device *pdev) */ static int ssm2602_init(struct snd_soc_device *socdev) { - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int reg, ret = 0; codec->name = "SSM2602"; @@ -622,7 +609,8 @@ static int ssm2602_init(struct snd_soc_device *socdev) APANA_ENABLE_MIC_BOOST); ssm2602_write(codec, SSM2602_PWR, 0); - ssm2602_add_controls(codec); + snd_soc_add_controls(codec, ssm2602_snd_controls, + ARRAY_SIZE(ssm2602_snd_controls)); ssm2602_add_widgets(codec); ret = snd_soc_init_card(socdev); if (ret < 0) { @@ -653,7 +641,7 @@ static int ssm2602_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct snd_soc_device *socdev = ssm2602_socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int ret; i2c_set_clientdata(i2c, codec); @@ -747,7 +735,7 @@ static int ssm2602_probe(struct platform_device *pdev) } codec->private_data = ssm2602; - socdev->codec = codec; + socdev->card->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); @@ -768,7 +756,7 @@ static int ssm2602_probe(struct platform_device *pdev) static int ssm2602_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; if (codec->control_data) ssm2602_set_bias_level(codec, SND_SOC_BIAS_OFF); diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c index cfdea00..c3f4afb 100644 --- a/sound/soc/codecs/tlv320aic23.c +++ b/sound/soc/codecs/tlv320aic23.c @@ -183,24 +183,6 @@ static const struct snd_kcontrol_new tlv320aic23_snd_controls[] = { SOC_ENUM("Playback De-emphasis", tlv320aic23_deemph), }; -/* add non dapm controls */ -static int tlv320aic23_add_controls(struct snd_soc_codec *codec) -{ - - int err, i; - - for (i = 0; i < ARRAY_SIZE(tlv320aic23_snd_controls); i++) { - err = snd_ctl_add(codec->card, - snd_soc_cnew(&tlv320aic23_snd_controls[i], - codec, NULL)); - if (err < 0) - return err; - } - - return 0; - -} - /* PGA Mixer controls for Line and Mic switch */ static const struct snd_kcontrol_new tlv320aic23_output_mixer_controls[] = { SOC_DAPM_SINGLE("Line Bypass Switch", TLV320AIC23_ANLG, 3, 1, 0), @@ -423,7 +405,7 @@ static int tlv320aic23_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; u16 iface_reg; int ret; struct aic23 *aic23 = container_of(codec, struct aic23, codec); @@ -471,7 +453,7 @@ static int tlv320aic23_pcm_prepare(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; /* set active */ tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0001); @@ -484,7 +466,7 @@ static void tlv320aic23_shutdown(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct aic23 *aic23 = container_of(codec, struct aic23, codec); /* deactivate */ @@ -598,6 +580,15 @@ static int tlv320aic23_set_bias_level(struct snd_soc_codec *codec, #define AIC23_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) +static struct snd_soc_dai_ops tlv320aic23_dai_ops = { + .prepare = tlv320aic23_pcm_prepare, + .hw_params = tlv320aic23_hw_params, + .shutdown = tlv320aic23_shutdown, + .digital_mute = tlv320aic23_mute, + .set_fmt = tlv320aic23_set_dai_fmt, + .set_sysclk = tlv320aic23_set_dai_sysclk, +}; + struct snd_soc_dai tlv320aic23_dai = { .name = "tlv320aic23", .playback = { @@ -612,14 +603,7 @@ struct snd_soc_dai tlv320aic23_dai = { .channels_max = 2, .rates = AIC23_RATES, .formats = AIC23_FORMATS,}, - .ops = { - .prepare = tlv320aic23_pcm_prepare, - .hw_params = tlv320aic23_hw_params, - .shutdown = tlv320aic23_shutdown, - .digital_mute = tlv320aic23_mute, - .set_fmt = tlv320aic23_set_dai_fmt, - .set_sysclk = tlv320aic23_set_dai_sysclk, - } + .ops = &tlv320aic23_dai_ops, }; EXPORT_SYMBOL_GPL(tlv320aic23_dai); @@ -627,7 +611,7 @@ static int tlv320aic23_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0); tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF); @@ -638,7 +622,7 @@ static int tlv320aic23_suspend(struct platform_device *pdev, static int tlv320aic23_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int i; u16 reg; @@ -660,7 +644,7 @@ static int tlv320aic23_resume(struct platform_device *pdev) */ static int tlv320aic23_init(struct snd_soc_device *socdev) { - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int ret = 0; u16 reg; @@ -718,7 +702,8 @@ static int tlv320aic23_init(struct snd_soc_device *socdev) tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x1); - tlv320aic23_add_controls(codec); + snd_soc_add_controls(codec, tlv320aic23_snd_controls, + ARRAY_SIZE(tlv320aic23_snd_controls)); tlv320aic23_add_widgets(codec); ret = snd_soc_init_card(socdev); if (ret < 0) { @@ -746,7 +731,7 @@ static int tlv320aic23_codec_probe(struct i2c_client *i2c, const struct i2c_device_id *i2c_id) { struct snd_soc_device *socdev = tlv320aic23_socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int ret; if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) @@ -804,7 +789,7 @@ static int tlv320aic23_probe(struct platform_device *pdev) if (aic23 == NULL) return -ENOMEM; codec = &aic23->codec; - socdev->codec = codec; + socdev->card->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); @@ -823,7 +808,7 @@ static int tlv320aic23_probe(struct platform_device *pdev) static int tlv320aic23_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct aic23 *aic23 = container_of(codec, struct aic23, codec); if (codec->control_data) diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c index 29f2f1a..3387d9e 100644 --- a/sound/soc/codecs/tlv320aic26.c +++ b/sound/soc/codecs/tlv320aic26.c @@ -130,7 +130,7 @@ static int aic26_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct aic26 *aic26 = codec->private_data; int fsref, divisor, wlen, pval, jval, dval, qval; u16 reg; @@ -270,6 +270,13 @@ static int aic26_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) #define AIC26_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |\ SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE) +static struct snd_soc_dai_ops aic26_dai_ops = { + .hw_params = aic26_hw_params, + .digital_mute = aic26_mute, + .set_sysclk = aic26_set_sysclk, + .set_fmt = aic26_set_fmt, +}; + struct snd_soc_dai aic26_dai = { .name = "tlv320aic26", .playback = { @@ -286,12 +293,7 @@ struct snd_soc_dai aic26_dai = { .rates = AIC26_RATES, .formats = AIC26_FORMATS, }, - .ops = { - .hw_params = aic26_hw_params, - .digital_mute = aic26_mute, - .set_sysclk = aic26_set_sysclk, - .set_fmt = aic26_set_fmt, - }, + .ops = &aic26_dai_ops, }; EXPORT_SYMBOL_GPL(aic26_dai); @@ -322,9 +324,8 @@ static int aic26_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec; - struct snd_kcontrol *kcontrol; struct aic26 *aic26; - int i, ret, err; + int ret, err; dev_info(&pdev->dev, "Probing AIC26 SoC CODEC driver\n"); dev_dbg(&pdev->dev, "socdev=%p\n", socdev); @@ -338,7 +339,7 @@ static int aic26_probe(struct platform_device *pdev) return -ENODEV; } codec = &aic26->codec; - socdev->codec = codec; + socdev->card->codec = codec; dev_dbg(&pdev->dev, "Registering PCMs, dev=%p, socdev->dev=%p\n", &pdev->dev, socdev->dev); @@ -351,11 +352,9 @@ static int aic26_probe(struct platform_device *pdev) /* register controls */ dev_dbg(&pdev->dev, "Registering controls\n"); - for (i = 0; i < ARRAY_SIZE(aic26_snd_controls); i++) { - kcontrol = snd_soc_cnew(&aic26_snd_controls[i], codec, NULL); - err = snd_ctl_add(codec->card, kcontrol); - WARN_ON(err < 0); - } + err = snd_soc_add_controls(codec, aic26_snd_controls, + ARRAY_SIZE(aic26_snd_controls)); + WARN_ON(err < 0); /* CODEC is setup, we can register the card now */ dev_dbg(&pdev->dev, "Registering card\n"); diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index aea0cb7..ab099f4 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -45,6 +45,7 @@ #include <sound/soc.h> #include <sound/soc-dapm.h> #include <sound/initval.h> +#include <sound/tlv.h> #include "tlv320aic3x.h" @@ -250,56 +251,86 @@ static const struct soc_enum aic3x_enum[] = { SOC_ENUM_DOUBLE(AIC3X_CODEC_DFILT_CTRL, 6, 4, 4, aic3x_adc_hpf), }; +/* + * DAC digital volumes. From -63.5 to 0 dB in 0.5 dB steps + */ +static DECLARE_TLV_DB_SCALE(dac_tlv, -6350, 50, 0); +/* ADC PGA gain volumes. From 0 to 59.5 dB in 0.5 dB steps */ +static DECLARE_TLV_DB_SCALE(adc_tlv, 0, 50, 0); +/* + * Output stage volumes. From -78.3 to 0 dB. Muted below -78.3 dB. + * Step size is approximately 0.5 dB over most of the scale but increasing + * near the very low levels. + * Define dB scale so that it is mostly correct for range about -55 to 0 dB + * but having increasing dB difference below that (and where it doesn't count + * so much). This setting shows -50 dB (actual is -50.3 dB) for register + * value 100 and -58.5 dB (actual is -78.3 dB) for register value 117. + */ +static DECLARE_TLV_DB_SCALE(output_stage_tlv, -5900, 50, 1); + static const struct snd_kcontrol_new aic3x_snd_controls[] = { /* Output */ - SOC_DOUBLE_R("PCM Playback Volume", LDAC_VOL, RDAC_VOL, 0, 0x7f, 1), + SOC_DOUBLE_R_TLV("PCM Playback Volume", + LDAC_VOL, RDAC_VOL, 0, 0x7f, 1, dac_tlv), - SOC_DOUBLE_R("Line DAC Playback Volume", DACL1_2_LLOPM_VOL, - DACR1_2_RLOPM_VOL, 0, 0x7f, 1), + SOC_DOUBLE_R_TLV("Line DAC Playback Volume", + DACL1_2_LLOPM_VOL, DACR1_2_RLOPM_VOL, + 0, 118, 1, output_stage_tlv), SOC_SINGLE("LineL Playback Switch", LLOPM_CTRL, 3, 0x01, 0), SOC_SINGLE("LineR Playback Switch", RLOPM_CTRL, 3, 0x01, 0), - SOC_DOUBLE_R("LineL DAC Playback Volume", DACL1_2_LLOPM_VOL, - DACR1_2_LLOPM_VOL, 0, 0x7f, 1), - SOC_SINGLE("LineL Left PGA Bypass Playback Volume", PGAL_2_LLOPM_VOL, - 0, 0x7f, 1), - SOC_SINGLE("LineR Right PGA Bypass Playback Volume", PGAR_2_RLOPM_VOL, - 0, 0x7f, 1), - SOC_DOUBLE_R("LineL Line2 Bypass Playback Volume", LINE2L_2_LLOPM_VOL, - LINE2R_2_LLOPM_VOL, 0, 0x7f, 1), - SOC_DOUBLE_R("LineR Line2 Bypass Playback Volume", LINE2L_2_RLOPM_VOL, - LINE2R_2_RLOPM_VOL, 0, 0x7f, 1), - - SOC_DOUBLE_R("Mono DAC Playback Volume", DACL1_2_MONOLOPM_VOL, - DACR1_2_MONOLOPM_VOL, 0, 0x7f, 1), + SOC_DOUBLE_R_TLV("LineL DAC Playback Volume", + DACL1_2_LLOPM_VOL, DACR1_2_LLOPM_VOL, + 0, 118, 1, output_stage_tlv), + SOC_SINGLE_TLV("LineL Left PGA Bypass Playback Volume", + PGAL_2_LLOPM_VOL, 0, 118, 1, output_stage_tlv), + SOC_SINGLE_TLV("LineR Right PGA Bypass Playback Volume", + PGAR_2_RLOPM_VOL, 0, 118, 1, output_stage_tlv), + SOC_DOUBLE_R_TLV("LineL Line2 Bypass Playback Volume", + LINE2L_2_LLOPM_VOL, LINE2R_2_LLOPM_VOL, + 0, 118, 1, output_stage_tlv), + SOC_DOUBLE_R_TLV("LineR Line2 Bypass Playback Volume", + LINE2L_2_RLOPM_VOL, LINE2R_2_RLOPM_VOL, + 0, 118, 1, output_stage_tlv), + + SOC_DOUBLE_R_TLV("Mono DAC Playback Volume", + DACL1_2_MONOLOPM_VOL, DACR1_2_MONOLOPM_VOL, + 0, 118, 1, output_stage_tlv), SOC_SINGLE("Mono DAC Playback Switch", MONOLOPM_CTRL, 3, 0x01, 0), - SOC_DOUBLE_R("Mono PGA Bypass Playback Volume", PGAL_2_MONOLOPM_VOL, - PGAR_2_MONOLOPM_VOL, 0, 0x7f, 1), - SOC_DOUBLE_R("Mono Line2 Bypass Playback Volume", LINE2L_2_MONOLOPM_VOL, - LINE2R_2_MONOLOPM_VOL, 0, 0x7f, 1), - - SOC_DOUBLE_R("HP DAC Playback Volume", DACL1_2_HPLOUT_VOL, - DACR1_2_HPROUT_VOL, 0, 0x7f, 1), + SOC_DOUBLE_R_TLV("Mono PGA Bypass Playback Volume", + PGAL_2_MONOLOPM_VOL, PGAR_2_MONOLOPM_VOL, + 0, 118, 1, output_stage_tlv), + SOC_DOUBLE_R_TLV("Mono Line2 Bypass Playback Volume", + LINE2L_2_MONOLOPM_VOL, LINE2R_2_MONOLOPM_VOL, + 0, 118, 1, output_stage_tlv), + + SOC_DOUBLE_R_TLV("HP DAC Playback Volume", + DACL1_2_HPLOUT_VOL, DACR1_2_HPROUT_VOL, + 0, 118, 1, output_stage_tlv), SOC_DOUBLE_R("HP DAC Playback Switch", HPLOUT_CTRL, HPROUT_CTRL, 3, 0x01, 0), - SOC_DOUBLE_R("HP Right PGA Bypass Playback Volume", PGAR_2_HPLOUT_VOL, - PGAR_2_HPROUT_VOL, 0, 0x7f, 1), - SOC_SINGLE("HPL PGA Bypass Playback Volume", PGAL_2_HPLOUT_VOL, - 0, 0x7f, 1), - SOC_SINGLE("HPR PGA Bypass Playback Volume", PGAL_2_HPROUT_VOL, - 0, 0x7f, 1), - SOC_DOUBLE_R("HP Line2 Bypass Playback Volume", LINE2L_2_HPLOUT_VOL, - LINE2R_2_HPROUT_VOL, 0, 0x7f, 1), - - SOC_DOUBLE_R("HPCOM DAC Playback Volume", DACL1_2_HPLCOM_VOL, - DACR1_2_HPRCOM_VOL, 0, 0x7f, 1), + SOC_DOUBLE_R_TLV("HP Right PGA Bypass Playback Volume", + PGAR_2_HPLOUT_VOL, PGAR_2_HPROUT_VOL, + 0, 118, 1, output_stage_tlv), + SOC_SINGLE_TLV("HPL PGA Bypass Playback Volume", + PGAL_2_HPLOUT_VOL, 0, 118, 1, output_stage_tlv), + SOC_SINGLE_TLV("HPR PGA Bypass Playback Volume", + PGAL_2_HPROUT_VOL, 0, 118, 1, output_stage_tlv), + SOC_DOUBLE_R_TLV("HP Line2 Bypass Playback Volume", + LINE2L_2_HPLOUT_VOL, LINE2R_2_HPROUT_VOL, + 0, 118, 1, output_stage_tlv), + + SOC_DOUBLE_R_TLV("HPCOM DAC Playback Volume", + DACL1_2_HPLCOM_VOL, DACR1_2_HPRCOM_VOL, + 0, 118, 1, output_stage_tlv), SOC_DOUBLE_R("HPCOM DAC Playback Switch", HPLCOM_CTRL, HPRCOM_CTRL, 3, 0x01, 0), - SOC_SINGLE("HPLCOM PGA Bypass Playback Volume", PGAL_2_HPLCOM_VOL, - 0, 0x7f, 1), - SOC_SINGLE("HPRCOM PGA Bypass Playback Volume", PGAL_2_HPRCOM_VOL, - 0, 0x7f, 1), - SOC_DOUBLE_R("HPCOM Line2 Bypass Playback Volume", LINE2L_2_HPLCOM_VOL, - LINE2R_2_HPRCOM_VOL, 0, 0x7f, 1), + SOC_SINGLE_TLV("HPLCOM PGA Bypass Playback Volume", + PGAL_2_HPLCOM_VOL, 0, 118, 1, output_stage_tlv), + SOC_SINGLE_TLV("HPRCOM PGA Bypass Playback Volume", + PGAL_2_HPRCOM_VOL, 0, 118, 1, output_stage_tlv), + SOC_DOUBLE_R_TLV("HPCOM Line2 Bypass Playback Volume", + LINE2L_2_HPLCOM_VOL, LINE2R_2_HPRCOM_VOL, + 0, 118, 1, output_stage_tlv), /* * Note: enable Automatic input Gain Controller with care. It can @@ -308,28 +339,13 @@ static const struct snd_kcontrol_new aic3x_snd_controls[] = { SOC_DOUBLE_R("AGC Switch", LAGC_CTRL_A, RAGC_CTRL_A, 7, 0x01, 0), /* Input */ - SOC_DOUBLE_R("PGA Capture Volume", LADC_VOL, RADC_VOL, 0, 0x7f, 0), + SOC_DOUBLE_R_TLV("PGA Capture Volume", LADC_VOL, RADC_VOL, + 0, 119, 0, adc_tlv), SOC_DOUBLE_R("PGA Capture Switch", LADC_VOL, RADC_VOL, 7, 0x01, 1), SOC_ENUM("ADC HPF Cut-off", aic3x_enum[ADC_HPF_ENUM]), }; -/* add non dapm controls */ -static int aic3x_add_controls(struct snd_soc_codec *codec) -{ - int err, i; - - for (i = 0; i < ARRAY_SIZE(aic3x_snd_controls); i++) { - err = snd_ctl_add(codec->card, - snd_soc_cnew(&aic3x_snd_controls[i], - codec, NULL)); - if (err < 0) - return err; - } - - return 0; -} - /* Left DAC Mux */ static const struct snd_kcontrol_new aic3x_left_dac_mux_controls = SOC_DAPM_ENUM("Route", aic3x_enum[LDAC_ENUM]); @@ -746,7 +762,7 @@ static int aic3x_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct aic3x_priv *aic3x = codec->private_data; int codec_clk = 0, bypass_pll = 0, fsref, last_clk = 0; u8 data, r, p, pll_q, pll_p = 1, pll_r = 1, pll_j = 1; @@ -1072,6 +1088,13 @@ EXPORT_SYMBOL_GPL(aic3x_button_pressed); #define AIC3X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) +static struct snd_soc_dai_ops aic3x_dai_ops = { + .hw_params = aic3x_hw_params, + .digital_mute = aic3x_mute, + .set_sysclk = aic3x_set_dai_sysclk, + .set_fmt = aic3x_set_dai_fmt, +}; + struct snd_soc_dai aic3x_dai = { .name = "tlv320aic3x", .playback = { @@ -1086,19 +1109,14 @@ struct snd_soc_dai aic3x_dai = { .channels_max = 2, .rates = AIC3X_RATES, .formats = AIC3X_FORMATS,}, - .ops = { - .hw_params = aic3x_hw_params, - .digital_mute = aic3x_mute, - .set_sysclk = aic3x_set_dai_sysclk, - .set_fmt = aic3x_set_dai_fmt, - } + .ops = &aic3x_dai_ops, }; EXPORT_SYMBOL_GPL(aic3x_dai); static int aic3x_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; aic3x_set_bias_level(codec, SND_SOC_BIAS_OFF); @@ -1108,7 +1126,7 @@ static int aic3x_suspend(struct platform_device *pdev, pm_message_t state) static int aic3x_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int i; u8 data[2]; u8 *cache = codec->reg_cache; @@ -1131,7 +1149,7 @@ static int aic3x_resume(struct platform_device *pdev) */ static int aic3x_init(struct snd_soc_device *socdev) { - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct aic3x_setup_data *setup = socdev->codec_data; int reg, ret = 0; @@ -1227,7 +1245,8 @@ static int aic3x_init(struct snd_soc_device *socdev) aic3x_write(codec, AIC3X_GPIO1_REG, (setup->gpio_func[0] & 0xf) << 4); aic3x_write(codec, AIC3X_GPIO2_REG, (setup->gpio_func[1] & 0xf) << 4); - aic3x_add_controls(codec); + snd_soc_add_controls(codec, aic3x_snd_controls, + ARRAY_SIZE(aic3x_snd_controls)); aic3x_add_widgets(codec); ret = snd_soc_init_card(socdev); if (ret < 0) { @@ -1261,7 +1280,7 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct snd_soc_device *socdev = aic3x_socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int ret; i2c_set_clientdata(i2c, codec); @@ -1366,7 +1385,7 @@ static int aic3x_probe(struct platform_device *pdev) } codec->private_data = aic3x; - socdev->codec = codec; + socdev->card->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); @@ -1392,7 +1411,7 @@ static int aic3x_probe(struct platform_device *pdev) static int aic3x_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; /* power down chip */ if (codec->control_data) diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index ea370a4..97738e2 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -42,7 +42,7 @@ */ static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = { 0x00, /* this register not used */ - 0x93, /* REG_CODEC_MODE (0x1) */ + 0x91, /* REG_CODEC_MODE (0x1) */ 0xc3, /* REG_OPTION (0x2) */ 0x00, /* REG_UNKNOWN (0x3) */ 0x00, /* REG_MICBIAS_CTL (0x4) */ @@ -117,6 +117,13 @@ static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = { 0x00, /* REG_MISC_SET_2 (0x49) */ }; +/* codec private data */ +struct twl4030_priv { + unsigned int bypass_state; + unsigned int codec_powered; + unsigned int codec_muted; +}; + /* * read twl4030 register cache */ @@ -125,6 +132,9 @@ static inline unsigned int twl4030_read_reg_cache(struct snd_soc_codec *codec, { u8 *cache = codec->reg_cache; + if (reg >= TWL4030_CACHEREGNUM) + return -EIO; + return cache[reg]; } @@ -151,26 +161,22 @@ static int twl4030_write(struct snd_soc_codec *codec, return twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, value, reg); } -static void twl4030_clear_codecpdz(struct snd_soc_codec *codec) +static void twl4030_codec_enable(struct snd_soc_codec *codec, int enable) { + struct twl4030_priv *twl4030 = codec->private_data; u8 mode; - mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE); - twl4030_write(codec, TWL4030_REG_CODEC_MODE, - mode & ~TWL4030_CODECPDZ); - - /* REVISIT: this delay is present in TI sample drivers */ - /* but there seems to be no TRM requirement for it */ - udelay(10); -} - -static void twl4030_set_codecpdz(struct snd_soc_codec *codec) -{ - u8 mode; + if (enable == twl4030->codec_powered) + return; mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE); - twl4030_write(codec, TWL4030_REG_CODEC_MODE, - mode | TWL4030_CODECPDZ); + if (enable) + mode |= TWL4030_CODECPDZ; + else + mode &= ~TWL4030_CODECPDZ; + + twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode); + twl4030->codec_powered = enable; /* REVISIT: this delay is present in TI sample drivers */ /* but there seems to be no TRM requirement for it */ @@ -182,7 +188,7 @@ static void twl4030_init_chip(struct snd_soc_codec *codec) int i; /* clear CODECPDZ prior to setting register defaults */ - twl4030_clear_codecpdz(codec); + twl4030_codec_enable(codec, 0); /* set all audio section registers to reasonable defaults */ for (i = TWL4030_REG_OPTION; i <= TWL4030_REG_MISC_SET_2; i++) @@ -190,6 +196,122 @@ static void twl4030_init_chip(struct snd_soc_codec *codec) } +static void twl4030_codec_mute(struct snd_soc_codec *codec, int mute) +{ + struct twl4030_priv *twl4030 = codec->private_data; + u8 reg_val; + + if (mute == twl4030->codec_muted) + return; + + if (mute) { + /* Bypass the reg_cache and mute the volumes + * Headset mute is done in it's own event handler + * Things to mute: Earpiece, PreDrivL/R, CarkitL/R + */ + reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_EAR_CTL); + twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, + reg_val & (~TWL4030_EAR_GAIN), + TWL4030_REG_EAR_CTL); + + reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PREDL_CTL); + twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, + reg_val & (~TWL4030_PREDL_GAIN), + TWL4030_REG_PREDL_CTL); + reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PREDR_CTL); + twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, + reg_val & (~TWL4030_PREDR_GAIN), + TWL4030_REG_PREDL_CTL); + + reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PRECKL_CTL); + twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, + reg_val & (~TWL4030_PRECKL_GAIN), + TWL4030_REG_PRECKL_CTL); + reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PRECKR_CTL); + twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, + reg_val & (~TWL4030_PRECKL_GAIN), + TWL4030_REG_PRECKR_CTL); + + /* Disable PLL */ + reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_APLL_CTL); + reg_val &= ~TWL4030_APLL_EN; + twl4030_write(codec, TWL4030_REG_APLL_CTL, reg_val); + } else { + /* Restore the volumes + * Headset mute is done in it's own event handler + * Things to restore: Earpiece, PreDrivL/R, CarkitL/R + */ + twl4030_write(codec, TWL4030_REG_EAR_CTL, + twl4030_read_reg_cache(codec, TWL4030_REG_EAR_CTL)); + + twl4030_write(codec, TWL4030_REG_PREDL_CTL, + twl4030_read_reg_cache(codec, TWL4030_REG_PREDL_CTL)); + twl4030_write(codec, TWL4030_REG_PREDR_CTL, + twl4030_read_reg_cache(codec, TWL4030_REG_PREDR_CTL)); + + twl4030_write(codec, TWL4030_REG_PRECKL_CTL, + twl4030_read_reg_cache(codec, TWL4030_REG_PRECKL_CTL)); + twl4030_write(codec, TWL4030_REG_PRECKR_CTL, + twl4030_read_reg_cache(codec, TWL4030_REG_PRECKR_CTL)); + + /* Enable PLL */ + reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_APLL_CTL); + reg_val |= TWL4030_APLL_EN; + twl4030_write(codec, TWL4030_REG_APLL_CTL, reg_val); + } + + twl4030->codec_muted = mute; +} + +static void twl4030_power_up(struct snd_soc_codec *codec) +{ + struct twl4030_priv *twl4030 = codec->private_data; + u8 anamicl, regmisc1, byte; + int i = 0; + + if (twl4030->codec_powered) + return; + + /* set CODECPDZ to turn on codec */ + twl4030_codec_enable(codec, 1); + + /* initiate offset cancellation */ + anamicl = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICL); + twl4030_write(codec, TWL4030_REG_ANAMICL, + anamicl | TWL4030_CNCL_OFFSET_START); + + /* wait for offset cancellation to complete */ + do { + /* this takes a little while, so don't slam i2c */ + udelay(2000); + twl4030_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &byte, + TWL4030_REG_ANAMICL); + } while ((i++ < 100) && + ((byte & TWL4030_CNCL_OFFSET_START) == + TWL4030_CNCL_OFFSET_START)); + + /* Make sure that the reg_cache has the same value as the HW */ + twl4030_write_reg_cache(codec, TWL4030_REG_ANAMICL, byte); + + /* anti-pop when changing analog gain */ + regmisc1 = twl4030_read_reg_cache(codec, TWL4030_REG_MISC_SET_1); + twl4030_write(codec, TWL4030_REG_MISC_SET_1, + regmisc1 | TWL4030_SMOOTH_ANAVOL_EN); + + /* toggle CODECPDZ as per TRM */ + twl4030_codec_enable(codec, 0); + twl4030_codec_enable(codec, 1); +} + +/* + * Unconditional power down + */ +static void twl4030_power_down(struct snd_soc_codec *codec) +{ + /* power down */ + twl4030_codec_enable(codec, 0); +} + /* Earpiece */ static const char *twl4030_earpiece_texts[] = {"Off", "DACL1", "DACL2", "DACR1"}; @@ -366,6 +488,41 @@ static const struct soc_enum twl4030_micpathtx2_enum = static const struct snd_kcontrol_new twl4030_dapm_micpathtx2_control = SOC_DAPM_ENUM("Route", twl4030_micpathtx2_enum); +/* Analog bypass for AudioR1 */ +static const struct snd_kcontrol_new twl4030_dapm_abypassr1_control = + SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXR1_APGA_CTL, 2, 1, 0); + +/* Analog bypass for AudioL1 */ +static const struct snd_kcontrol_new twl4030_dapm_abypassl1_control = + SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXL1_APGA_CTL, 2, 1, 0); + +/* Analog bypass for AudioR2 */ +static const struct snd_kcontrol_new twl4030_dapm_abypassr2_control = + SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXR2_APGA_CTL, 2, 1, 0); + +/* Analog bypass for AudioL2 */ +static const struct snd_kcontrol_new twl4030_dapm_abypassl2_control = + SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXL2_APGA_CTL, 2, 1, 0); + +/* Digital bypass gain, 0 mutes the bypass */ +static const unsigned int twl4030_dapm_dbypass_tlv[] = { + TLV_DB_RANGE_HEAD(2), + 0, 3, TLV_DB_SCALE_ITEM(-2400, 0, 1), + 4, 7, TLV_DB_SCALE_ITEM(-1800, 600, 0), +}; + +/* Digital bypass left (TX1L -> RX2L) */ +static const struct snd_kcontrol_new twl4030_dapm_dbypassl_control = + SOC_DAPM_SINGLE_TLV("Volume", + TWL4030_REG_ATX2ARXPGA, 3, 7, 0, + twl4030_dapm_dbypass_tlv); + +/* Digital bypass right (TX1R -> RX2R) */ +static const struct snd_kcontrol_new twl4030_dapm_dbypassr_control = + SOC_DAPM_SINGLE_TLV("Volume", + TWL4030_REG_ATX2ARXPGA, 0, 7, 0, + twl4030_dapm_dbypass_tlv); + static int micpath_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { @@ -420,6 +577,79 @@ static int handsfree_event(struct snd_soc_dapm_widget *w, return 0; } +static int headsetl_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + unsigned char hs_gain, hs_pop; + + /* Save the current volume */ + hs_gain = twl4030_read_reg_cache(w->codec, TWL4030_REG_HS_GAIN_SET); + hs_pop = twl4030_read_reg_cache(w->codec, TWL4030_REG_HS_POPN_SET); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* Do the anti-pop/bias ramp enable according to the TRM */ + hs_pop |= TWL4030_VMID_EN; + twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop); + /* Is this needed? Can we just use whatever gain here? */ + twl4030_write(w->codec, TWL4030_REG_HS_GAIN_SET, + (hs_gain & (~0x0f)) | 0x0a); + hs_pop |= TWL4030_RAMP_EN; + twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop); + + /* Restore the original volume */ + twl4030_write(w->codec, TWL4030_REG_HS_GAIN_SET, hs_gain); + break; + case SND_SOC_DAPM_POST_PMD: + /* Do the anti-pop/bias ramp disable according to the TRM */ + hs_pop &= ~TWL4030_RAMP_EN; + twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop); + /* Bypass the reg_cache to mute the headset */ + twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, + hs_gain & (~0x0f), + TWL4030_REG_HS_GAIN_SET); + hs_pop &= ~TWL4030_VMID_EN; + twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop); + break; + } + return 0; +} + +static int bypass_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct soc_mixer_control *m = + (struct soc_mixer_control *)w->kcontrols->private_value; + struct twl4030_priv *twl4030 = w->codec->private_data; + unsigned char reg; + + reg = twl4030_read_reg_cache(w->codec, m->reg); + + if (m->reg <= TWL4030_REG_ARXR2_APGA_CTL) { + /* Analog bypass */ + if (reg & (1 << m->shift)) + twl4030->bypass_state |= + (1 << (m->reg - TWL4030_REG_ARXL1_APGA_CTL)); + else + twl4030->bypass_state &= + ~(1 << (m->reg - TWL4030_REG_ARXL1_APGA_CTL)); + } else { + /* Digital bypass */ + if (reg & (0x7 << m->shift)) + twl4030->bypass_state |= (1 << (m->shift ? 5 : 4)); + else + twl4030->bypass_state &= ~(1 << (m->shift ? 5 : 4)); + } + + if (w->codec->bias_level == SND_SOC_BIAS_STANDBY) { + if (twl4030->bypass_state) + twl4030_codec_mute(w->codec, 0); + else + twl4030_codec_mute(w->codec, 1); + } + return 0; +} + /* * Some of the gain controls in TWL (mostly those which are associated with * the outputs) are implemented in an interesting way: @@ -614,6 +844,17 @@ static DECLARE_TLV_DB_SCALE(digital_capture_tlv, 0, 100, 0); */ static DECLARE_TLV_DB_SCALE(input_gain_tlv, 0, 600, 0); +static const char *twl4030_rampdelay_texts[] = { + "27/20/14 ms", "55/40/27 ms", "109/81/55 ms", "218/161/109 ms", + "437/323/218 ms", "874/645/437 ms", "1748/1291/874 ms", + "3495/2581/1748 ms" +}; + +static const struct soc_enum twl4030_rampdelay_enum = + SOC_ENUM_SINGLE(TWL4030_REG_HS_POPN_SET, 2, + ARRAY_SIZE(twl4030_rampdelay_texts), + twl4030_rampdelay_texts); + static const struct snd_kcontrol_new twl4030_snd_controls[] = { /* Common playback gain controls */ SOC_DOUBLE_R_TLV("DAC1 Digital Fine Playback Volume", @@ -668,23 +909,9 @@ static const struct snd_kcontrol_new twl4030_snd_controls[] = { SOC_DOUBLE_TLV("Analog Capture Volume", TWL4030_REG_ANAMIC_GAIN, 0, 3, 5, 0, input_gain_tlv), -}; - -/* add non dapm controls */ -static int twl4030_add_controls(struct snd_soc_codec *codec) -{ - int err, i; - - for (i = 0; i < ARRAY_SIZE(twl4030_snd_controls); i++) { - err = snd_ctl_add(codec->card, - snd_soc_cnew(&twl4030_snd_controls[i], - codec, NULL)); - if (err < 0) - return err; - } - return 0; -} + SOC_ENUM("HS ramp delay", twl4030_rampdelay_enum), +}; static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { /* Left channel inputs */ @@ -714,13 +941,13 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { /* DACs */ SND_SOC_DAPM_DAC("DAC Right1", "Right Front Playback", - TWL4030_REG_AVDAC_CTL, 0, 0), + SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_DAC("DAC Left1", "Left Front Playback", - TWL4030_REG_AVDAC_CTL, 1, 0), + SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_DAC("DAC Right2", "Right Rear Playback", - TWL4030_REG_AVDAC_CTL, 2, 0), + SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_DAC("DAC Left2", "Left Rear Playback", - TWL4030_REG_AVDAC_CTL, 3, 0), + SND_SOC_NOPM, 0, 0), /* Analog PGAs */ SND_SOC_DAPM_PGA("ARXR1_APGA", TWL4030_REG_ARXR1_APGA_CTL, @@ -732,6 +959,37 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_PGA("ARXL2_APGA", TWL4030_REG_ARXL2_APGA_CTL, 0, 0, NULL, 0), + /* Analog bypasses */ + SND_SOC_DAPM_SWITCH_E("Right1 Analog Loopback", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_abypassr1_control, bypass_event, + SND_SOC_DAPM_POST_REG), + SND_SOC_DAPM_SWITCH_E("Left1 Analog Loopback", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_abypassl1_control, + bypass_event, SND_SOC_DAPM_POST_REG), + SND_SOC_DAPM_SWITCH_E("Right2 Analog Loopback", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_abypassr2_control, + bypass_event, SND_SOC_DAPM_POST_REG), + SND_SOC_DAPM_SWITCH_E("Left2 Analog Loopback", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_abypassl2_control, + bypass_event, SND_SOC_DAPM_POST_REG), + + /* Digital bypasses */ + SND_SOC_DAPM_SWITCH_E("Left Digital Loopback", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_dbypassl_control, bypass_event, + SND_SOC_DAPM_POST_REG), + SND_SOC_DAPM_SWITCH_E("Right Digital Loopback", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_dbypassr_control, bypass_event, + SND_SOC_DAPM_POST_REG), + + SND_SOC_DAPM_MIXER("Analog R1 Playback Mixer", TWL4030_REG_AVDAC_CTL, + 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Analog L1 Playback Mixer", TWL4030_REG_AVDAC_CTL, + 1, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Analog R2 Playback Mixer", TWL4030_REG_AVDAC_CTL, + 2, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Analog L2 Playback Mixer", TWL4030_REG_AVDAC_CTL, + 3, 0, NULL, 0), + /* Output MUX controls */ /* Earpiece */ SND_SOC_DAPM_VALUE_MUX("Earpiece Mux", SND_SOC_NOPM, 0, 0, @@ -742,8 +1000,9 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_VALUE_MUX("PredriveR Mux", SND_SOC_NOPM, 0, 0, &twl4030_dapm_predriver_control), /* HeadsetL/R */ - SND_SOC_DAPM_MUX("HeadsetL Mux", SND_SOC_NOPM, 0, 0, - &twl4030_dapm_hsol_control), + SND_SOC_DAPM_MUX_E("HeadsetL Mux", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_hsol_control, headsetl_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MUX("HeadsetR Mux", SND_SOC_NOPM, 0, 0, &twl4030_dapm_hsor_control), /* CarkitL/R */ @@ -782,16 +1041,16 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD| SND_SOC_DAPM_POST_REG), - /* Analog input muxes with power switch for the physical ADCL/R */ + /* Analog input muxes with switch for the capture amplifiers */ SND_SOC_DAPM_VALUE_MUX("Analog Left Capture Route", - TWL4030_REG_AVADC_CTL, 3, 0, &twl4030_dapm_analoglmic_control), + TWL4030_REG_ANAMICL, 4, 0, &twl4030_dapm_analoglmic_control), SND_SOC_DAPM_VALUE_MUX("Analog Right Capture Route", - TWL4030_REG_AVADC_CTL, 1, 0, &twl4030_dapm_analogrmic_control), + TWL4030_REG_ANAMICR, 4, 0, &twl4030_dapm_analogrmic_control), - SND_SOC_DAPM_PGA("Analog Left Amplifier", - TWL4030_REG_ANAMICL, 4, 0, NULL, 0), - SND_SOC_DAPM_PGA("Analog Right Amplifier", - TWL4030_REG_ANAMICR, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("ADC Physical Left", + TWL4030_REG_AVADC_CTL, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("ADC Physical Right", + TWL4030_REG_AVADC_CTL, 1, 0, NULL, 0), SND_SOC_DAPM_PGA("Digimic0 Enable", TWL4030_REG_ADCMICSEL, 1, 0, NULL, 0), @@ -801,13 +1060,19 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_MICBIAS("Mic Bias 1", TWL4030_REG_MICBIAS_CTL, 0, 0), SND_SOC_DAPM_MICBIAS("Mic Bias 2", TWL4030_REG_MICBIAS_CTL, 1, 0), SND_SOC_DAPM_MICBIAS("Headset Mic Bias", TWL4030_REG_MICBIAS_CTL, 2, 0), + }; static const struct snd_soc_dapm_route intercon[] = { - {"ARXL1_APGA", NULL, "DAC Left1"}, - {"ARXR1_APGA", NULL, "DAC Right1"}, - {"ARXL2_APGA", NULL, "DAC Left2"}, - {"ARXR2_APGA", NULL, "DAC Right2"}, + {"Analog L1 Playback Mixer", NULL, "DAC Left1"}, + {"Analog R1 Playback Mixer", NULL, "DAC Right1"}, + {"Analog L2 Playback Mixer", NULL, "DAC Left2"}, + {"Analog R2 Playback Mixer", NULL, "DAC Right2"}, + + {"ARXL1_APGA", NULL, "Analog L1 Playback Mixer"}, + {"ARXR1_APGA", NULL, "Analog R1 Playback Mixer"}, + {"ARXL2_APGA", NULL, "Analog L2 Playback Mixer"}, + {"ARXR2_APGA", NULL, "Analog R2 Playback Mixer"}, /* Internal playback routings */ /* Earpiece */ @@ -865,23 +1130,23 @@ static const struct snd_soc_dapm_route intercon[] = { {"Analog Right Capture Route", "Sub mic", "SUBMIC"}, {"Analog Right Capture Route", "AUXR", "AUXR"}, - {"Analog Left Amplifier", NULL, "Analog Left Capture Route"}, - {"Analog Right Amplifier", NULL, "Analog Right Capture Route"}, + {"ADC Physical Left", NULL, "Analog Left Capture Route"}, + {"ADC Physical Right", NULL, "Analog Right Capture Route"}, {"Digimic0 Enable", NULL, "DIGIMIC0"}, {"Digimic1 Enable", NULL, "DIGIMIC1"}, /* TX1 Left capture path */ - {"TX1 Capture Route", "Analog", "Analog Left Amplifier"}, + {"TX1 Capture Route", "Analog", "ADC Physical Left"}, {"TX1 Capture Route", "Digimic0", "Digimic0 Enable"}, /* TX1 Right capture path */ - {"TX1 Capture Route", "Analog", "Analog Right Amplifier"}, + {"TX1 Capture Route", "Analog", "ADC Physical Right"}, {"TX1 Capture Route", "Digimic0", "Digimic0 Enable"}, /* TX2 Left capture path */ - {"TX2 Capture Route", "Analog", "Analog Left Amplifier"}, + {"TX2 Capture Route", "Analog", "ADC Physical Left"}, {"TX2 Capture Route", "Digimic1", "Digimic1 Enable"}, /* TX2 Right capture path */ - {"TX2 Capture Route", "Analog", "Analog Right Amplifier"}, + {"TX2 Capture Route", "Analog", "ADC Physical Right"}, {"TX2 Capture Route", "Digimic1", "Digimic1 Enable"}, {"ADC Virtual Left1", NULL, "TX1 Capture Route"}, @@ -889,6 +1154,24 @@ static const struct snd_soc_dapm_route intercon[] = { {"ADC Virtual Left2", NULL, "TX2 Capture Route"}, {"ADC Virtual Right2", NULL, "TX2 Capture Route"}, + /* Analog bypass routes */ + {"Right1 Analog Loopback", "Switch", "Analog Right Capture Route"}, + {"Left1 Analog Loopback", "Switch", "Analog Left Capture Route"}, + {"Right2 Analog Loopback", "Switch", "Analog Right Capture Route"}, + {"Left2 Analog Loopback", "Switch", "Analog Left Capture Route"}, + + {"Analog R1 Playback Mixer", NULL, "Right1 Analog Loopback"}, + {"Analog L1 Playback Mixer", NULL, "Left1 Analog Loopback"}, + {"Analog R2 Playback Mixer", NULL, "Right2 Analog Loopback"}, + {"Analog L2 Playback Mixer", NULL, "Left2 Analog Loopback"}, + + /* Digital bypass routes */ + {"Right Digital Loopback", "Volume", "TX1 Capture Route"}, + {"Left Digital Loopback", "Volume", "TX1 Capture Route"}, + + {"Analog R2 Playback Mixer", NULL, "Right Digital Loopback"}, + {"Analog L2 Playback Mixer", NULL, "Left Digital Loopback"}, + }; static int twl4030_add_widgets(struct snd_soc_codec *codec) @@ -902,82 +1185,28 @@ static int twl4030_add_widgets(struct snd_soc_codec *codec) return 0; } -static void twl4030_power_up(struct snd_soc_codec *codec) -{ - u8 anamicl, regmisc1, byte, popn; - int i = 0; - - /* set CODECPDZ to turn on codec */ - twl4030_set_codecpdz(codec); - - /* initiate offset cancellation */ - anamicl = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICL); - twl4030_write(codec, TWL4030_REG_ANAMICL, - anamicl | TWL4030_CNCL_OFFSET_START); - - - /* wait for offset cancellation to complete */ - do { - /* this takes a little while, so don't slam i2c */ - udelay(2000); - twl4030_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &byte, - TWL4030_REG_ANAMICL); - } while ((i++ < 100) && - ((byte & TWL4030_CNCL_OFFSET_START) == - TWL4030_CNCL_OFFSET_START)); - - /* anti-pop when changing analog gain */ - regmisc1 = twl4030_read_reg_cache(codec, TWL4030_REG_MISC_SET_1); - twl4030_write(codec, TWL4030_REG_MISC_SET_1, - regmisc1 | TWL4030_SMOOTH_ANAVOL_EN); - - /* toggle CODECPDZ as per TRM */ - twl4030_clear_codecpdz(codec); - twl4030_set_codecpdz(codec); - - /* program anti-pop with bias ramp delay */ - popn = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET); - popn &= TWL4030_RAMP_DELAY; - popn |= TWL4030_RAMP_DELAY_645MS; - twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn); - popn |= TWL4030_VMID_EN; - twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn); - - /* enable anti-pop ramp */ - popn |= TWL4030_RAMP_EN; - twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn); -} - -static void twl4030_power_down(struct snd_soc_codec *codec) -{ - u8 popn; - - /* disable anti-pop ramp */ - popn = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET); - popn &= ~TWL4030_RAMP_EN; - twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn); - - /* disable bias out */ - popn &= ~TWL4030_VMID_EN; - twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn); - - /* power down */ - twl4030_clear_codecpdz(codec); -} - static int twl4030_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { + struct twl4030_priv *twl4030 = codec->private_data; + switch (level) { case SND_SOC_BIAS_ON: - twl4030_power_up(codec); + twl4030_codec_mute(codec, 0); break; case SND_SOC_BIAS_PREPARE: - /* TODO: develop a twl4030_prepare function */ + twl4030_power_up(codec); + if (twl4030->bypass_state) + twl4030_codec_mute(codec, 0); + else + twl4030_codec_mute(codec, 1); break; case SND_SOC_BIAS_STANDBY: - /* TODO: develop a twl4030_standby function */ - twl4030_power_down(codec); + twl4030_power_up(codec); + if (twl4030->bypass_state) + twl4030_codec_mute(codec, 0); + else + twl4030_codec_mute(codec, 1); break; case SND_SOC_BIAS_OFF: twl4030_power_down(codec); @@ -994,10 +1223,9 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; u8 mode, old_mode, format, old_format; - /* bit rate */ old_mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE) & ~TWL4030_CODECPDZ; @@ -1039,8 +1267,9 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream, if (mode != old_mode) { /* change rate and set CODECPDZ */ + twl4030_codec_enable(codec, 0); twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode); - twl4030_set_codecpdz(codec); + twl4030_codec_enable(codec, 1); } /* sample size */ @@ -1063,13 +1292,13 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream, if (format != old_format) { /* clear CODECPDZ before changing format (codec requirement) */ - twl4030_clear_codecpdz(codec); + twl4030_codec_enable(codec, 0); /* change format */ twl4030_write(codec, TWL4030_REG_AUDIO_IF, format); /* set CODECPDZ afterwards */ - twl4030_set_codecpdz(codec); + twl4030_codec_enable(codec, 1); } return 0; } @@ -1139,13 +1368,13 @@ static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai, if (format != old_format) { /* clear CODECPDZ before changing format (codec requirement) */ - twl4030_clear_codecpdz(codec); + twl4030_codec_enable(codec, 0); /* change format */ twl4030_write(codec, TWL4030_REG_AUDIO_IF, format); /* set CODECPDZ afterwards */ - twl4030_set_codecpdz(codec); + twl4030_codec_enable(codec, 1); } return 0; @@ -1154,6 +1383,12 @@ static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai, #define TWL4030_RATES (SNDRV_PCM_RATE_8000_48000) #define TWL4030_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE) +static struct snd_soc_dai_ops twl4030_dai_ops = { + .hw_params = twl4030_hw_params, + .set_sysclk = twl4030_set_dai_sysclk, + .set_fmt = twl4030_set_dai_fmt, +}; + struct snd_soc_dai twl4030_dai = { .name = "twl4030", .playback = { @@ -1168,18 +1403,14 @@ struct snd_soc_dai twl4030_dai = { .channels_max = 2, .rates = TWL4030_RATES, .formats = TWL4030_FORMATS,}, - .ops = { - .hw_params = twl4030_hw_params, - .set_sysclk = twl4030_set_dai_sysclk, - .set_fmt = twl4030_set_dai_fmt, - } + .ops = &twl4030_dai_ops, }; EXPORT_SYMBOL_GPL(twl4030_dai); static int twl4030_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; twl4030_set_bias_level(codec, SND_SOC_BIAS_OFF); @@ -1189,7 +1420,7 @@ static int twl4030_suspend(struct platform_device *pdev, pm_message_t state) static int twl4030_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; twl4030_set_bias_level(codec, SND_SOC_BIAS_STANDBY); twl4030_set_bias_level(codec, codec->suspend_bias_level); @@ -1203,7 +1434,7 @@ static int twl4030_resume(struct platform_device *pdev) static int twl4030_init(struct snd_soc_device *socdev) { - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int ret = 0; printk(KERN_INFO "TWL4030 Audio Codec init \n"); @@ -1233,7 +1464,8 @@ static int twl4030_init(struct snd_soc_device *socdev) /* power on device */ twl4030_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - twl4030_add_controls(codec); + snd_soc_add_controls(codec, twl4030_snd_controls, + ARRAY_SIZE(twl4030_snd_controls)); twl4030_add_widgets(codec); ret = snd_soc_init_card(socdev); @@ -1258,12 +1490,20 @@ static int twl4030_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec; + struct twl4030_priv *twl4030; codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); if (codec == NULL) return -ENOMEM; - socdev->codec = codec; + twl4030 = kzalloc(sizeof(struct twl4030_priv), GFP_KERNEL); + if (twl4030 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = twl4030; + socdev->card->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); @@ -1277,11 +1517,13 @@ static int twl4030_probe(struct platform_device *pdev) static int twl4030_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; printk(KERN_INFO "TWL4030 Audio Codec remove\n"); + twl4030_set_bias_level(codec, SND_SOC_BIAS_OFF); snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); + kfree(codec->private_data); kfree(codec); return 0; diff --git a/sound/soc/codecs/twl4030.h b/sound/soc/codecs/twl4030.h index 442e5a8..33dbb14 100644 --- a/sound/soc/codecs/twl4030.h +++ b/sound/soc/codecs/twl4030.h @@ -170,6 +170,9 @@ #define TWL4030_CLK256FS_EN 0x02 #define TWL4030_AIF_EN 0x01 +/* EAR_CTL (0x21) */ +#define TWL4030_EAR_GAIN 0x30 + /* HS_GAIN_SET (0x23) Fields */ #define TWL4030_HSR_GAIN 0x0C @@ -198,6 +201,18 @@ #define TWL4030_RAMP_DELAY_2581MS 0x1C #define TWL4030_RAMP_EN 0x02 +/* PREDL_CTL (0x25) */ +#define TWL4030_PREDL_GAIN 0x30 + +/* PREDR_CTL (0x26) */ +#define TWL4030_PREDR_GAIN 0x30 + +/* PRECKL_CTL (0x27) */ +#define TWL4030_PRECKL_GAIN 0x30 + +/* PRECKR_CTL (0x28) */ +#define TWL4030_PRECKR_GAIN 0x30 + /* HFL_CTL (0x29, 0x2A) Fields */ #define TWL4030_HF_CTL_HB_EN 0x04 #define TWL4030_HF_CTL_LOOP_EN 0x08 diff --git a/sound/soc/codecs/uda134x.c b/sound/soc/codecs/uda134x.c index a2c5064..ddefb8f 100644 --- a/sound/soc/codecs/uda134x.c +++ b/sound/soc/codecs/uda134x.c @@ -173,7 +173,7 @@ static int uda134x_startup(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct uda134x_priv *uda134x = codec->private_data; struct snd_pcm_runtime *master_runtime; @@ -206,7 +206,7 @@ static void uda134x_shutdown(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct uda134x_priv *uda134x = codec->private_data; if (uda134x->master_substream == substream) @@ -221,7 +221,7 @@ static int uda134x_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct uda134x_priv *uda134x = codec->private_data; u8 hw_params; @@ -431,38 +431,14 @@ SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]), SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0), }; -static int uda134x_add_controls(struct snd_soc_codec *codec) -{ - int err, i, n; - const struct snd_kcontrol_new *ctrls; - struct uda134x_platform_data *pd = codec->control_data; - - switch (pd->model) { - case UDA134X_UDA1340: - case UDA134X_UDA1344: - n = ARRAY_SIZE(uda1340_snd_controls); - ctrls = uda1340_snd_controls; - break; - case UDA134X_UDA1341: - n = ARRAY_SIZE(uda1341_snd_controls); - ctrls = uda1341_snd_controls; - break; - default: - printk(KERN_ERR "%s unkown codec type: %d", - __func__, pd->model); - return -EINVAL; - } - - for (i = 0; i < n; i++) { - err = snd_ctl_add(codec->card, - snd_soc_cnew(&ctrls[i], - codec, NULL)); - if (err < 0) - return err; - } - - return 0; -} +static struct snd_soc_dai_ops uda134x_dai_ops = { + .startup = uda134x_startup, + .shutdown = uda134x_shutdown, + .hw_params = uda134x_hw_params, + .digital_mute = uda134x_mute, + .set_sysclk = uda134x_set_dai_sysclk, + .set_fmt = uda134x_set_dai_fmt, +}; struct snd_soc_dai uda134x_dai = { .name = "UDA134X", @@ -483,14 +459,7 @@ struct snd_soc_dai uda134x_dai = { .formats = UDA134X_FORMATS, }, /* pcm operations */ - .ops = { - .startup = uda134x_startup, - .shutdown = uda134x_shutdown, - .hw_params = uda134x_hw_params, - .digital_mute = uda134x_mute, - .set_sysclk = uda134x_set_dai_sysclk, - .set_fmt = uda134x_set_dai_fmt, - } + .ops = &uda134x_dai_ops, }; EXPORT_SYMBOL(uda134x_dai); @@ -525,11 +494,11 @@ static int uda134x_soc_probe(struct platform_device *pdev) return -EINVAL; } - socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); - if (socdev->codec == NULL) + socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (socdev->card->codec == NULL) return ret; - codec = socdev->codec; + codec = socdev->card->codec; uda134x = kzalloc(sizeof(struct uda134x_priv), GFP_KERNEL); if (uda134x == NULL) @@ -572,7 +541,22 @@ static int uda134x_soc_probe(struct platform_device *pdev) goto pcm_err; } - ret = uda134x_add_controls(codec); + switch (pd->model) { + case UDA134X_UDA1340: + case UDA134X_UDA1344: + ret = snd_soc_add_controls(codec, uda1340_snd_controls, + ARRAY_SIZE(uda1340_snd_controls)); + break; + case UDA134X_UDA1341: + ret = snd_soc_add_controls(codec, uda1341_snd_controls, + ARRAY_SIZE(uda1341_snd_controls)); + break; + default: + printk(KERN_ERR "%s unkown codec type: %d", + __func__, pd->model); + return -EINVAL; + } + if (ret < 0) { printk(KERN_ERR "UDA134X: failed to register controls\n"); goto pcm_err; @@ -602,7 +586,7 @@ priv_err: static int uda134x_soc_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; uda134x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); uda134x_set_bias_level(codec, SND_SOC_BIAS_OFF); @@ -622,7 +606,7 @@ static int uda134x_soc_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; uda134x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); uda134x_set_bias_level(codec, SND_SOC_BIAS_OFF); @@ -632,7 +616,7 @@ static int uda134x_soc_suspend(struct platform_device *pdev, static int uda134x_soc_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; uda134x_set_bias_level(codec, SND_SOC_BIAS_PREPARE); uda134x_set_bias_level(codec, SND_SOC_BIAS_ON); diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c index e6bf084..5b21594 100644 --- a/sound/soc/codecs/uda1380.c +++ b/sound/soc/codecs/uda1380.c @@ -25,6 +25,7 @@ #include <linux/ioctl.h> #include <linux/delay.h> #include <linux/i2c.h> +#include <linux/workqueue.h> #include <sound/core.h> #include <sound/control.h> #include <sound/initval.h> @@ -35,7 +36,8 @@ #include "uda1380.h" -#define UDA1380_VERSION "0.6" +static struct work_struct uda1380_work; +static struct snd_soc_codec *uda1380_codec; /* * uda1380 register cache @@ -52,6 +54,8 @@ static const u16 uda1380_reg[UDA1380_CACHEREGNUM] = { 0x0000, 0x8000, 0x0002, 0x0000, }; +static unsigned long uda1380_cache_dirty; + /* * read uda1380 register cache */ @@ -73,8 +77,11 @@ static inline void uda1380_write_reg_cache(struct snd_soc_codec *codec, u16 reg, unsigned int value) { u16 *cache = codec->reg_cache; + if (reg >= UDA1380_CACHEREGNUM) return; + if ((reg >= 0x10) && (cache[reg] != value)) + set_bit(reg - 0x10, &uda1380_cache_dirty); cache[reg] = value; } @@ -113,6 +120,8 @@ static int uda1380_write(struct snd_soc_codec *codec, unsigned int reg, (data[0]<<8) | data[1]); return -EIO; } + if (reg >= 0x10) + clear_bit(reg - 0x10, &uda1380_cache_dirty); return 0; } else return -EIO; @@ -120,6 +129,20 @@ static int uda1380_write(struct snd_soc_codec *codec, unsigned int reg, #define uda1380_reset(c) uda1380_write(c, UDA1380_RESET, 0) +static void uda1380_flush_work(struct work_struct *work) +{ + int bit, reg; + + for_each_bit(bit, &uda1380_cache_dirty, UDA1380_CACHEREGNUM - 0x10) { + reg = 0x10 + bit; + pr_debug("uda1380: flush reg %x val %x:\n", reg, + uda1380_read_reg_cache(uda1380_codec, reg)); + uda1380_write(uda1380_codec, reg, + uda1380_read_reg_cache(uda1380_codec, reg)); + clear_bit(bit, &uda1380_cache_dirty); + } +} + /* declarations of ALSA reg_elem_REAL controls */ static const char *uda1380_deemp[] = { "None", @@ -254,7 +277,6 @@ static const struct snd_kcontrol_new uda1380_snd_controls[] = { SOC_SINGLE("DAC Polarity inverting Switch", UDA1380_MIXER, 15, 1, 0), /* DA_POL_INV */ SOC_ENUM("Noise Shaper", uda1380_sel_ns_enum), /* SEL_NS */ SOC_ENUM("Digital Mixer Signal Control", uda1380_mix_enum), /* MIX_POS, MIX */ - SOC_SINGLE("Silence Switch", UDA1380_MIXER, 7, 1, 0), /* SILENCE, force DAC output to silence */ SOC_SINGLE("Silence Detector Switch", UDA1380_MIXER, 6, 1, 0), /* SDET_ON */ SOC_ENUM("Silence Detector Setting", uda1380_sdet_enum), /* SD_VALUE */ SOC_ENUM("Oversampling Input", uda1380_os_enum), /* OS */ @@ -271,21 +293,6 @@ static const struct snd_kcontrol_new uda1380_snd_controls[] = { SOC_SINGLE("AGC Switch", UDA1380_AGC, 0, 1, 0), }; -/* add non dapm controls */ -static int uda1380_add_controls(struct snd_soc_codec *codec) -{ - int err, i; - - for (i = 0; i < ARRAY_SIZE(uda1380_snd_controls); i++) { - err = snd_ctl_add(codec->card, - snd_soc_cnew(&uda1380_snd_controls[i], codec, NULL)); - if (err < 0) - return err; - } - - return 0; -} - /* Input mux */ static const struct snd_kcontrol_new uda1380_input_mux_control = SOC_DAPM_ENUM("Route", uda1380_input_sel_enum); @@ -371,7 +378,7 @@ static int uda1380_add_widgets(struct snd_soc_codec *codec) return 0; } -static int uda1380_set_dai_fmt(struct snd_soc_dai *codec_dai, +static int uda1380_set_dai_fmt_both(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; @@ -381,61 +388,107 @@ static int uda1380_set_dai_fmt(struct snd_soc_dai *codec_dai, iface = uda1380_read_reg_cache(codec, UDA1380_IFACE); iface &= ~(R01_SFORI_MASK | R01_SIM | R01_SFORO_MASK); - /* FIXME: how to select I2S for DATAO and MSB for DATAI correctly? */ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: iface |= R01_SFORI_I2S | R01_SFORO_I2S; break; case SND_SOC_DAIFMT_LSB: - iface |= R01_SFORI_LSB16 | R01_SFORO_I2S; + iface |= R01_SFORI_LSB16 | R01_SFORO_LSB16; break; case SND_SOC_DAIFMT_MSB: - iface |= R01_SFORI_MSB | R01_SFORO_I2S; + iface |= R01_SFORI_MSB | R01_SFORO_MSB; } - if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFM) - iface |= R01_SIM; + /* DATAI is slave only, so in single-link mode, this has to be slave */ + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) + return -EINVAL; uda1380_write(codec, UDA1380_IFACE, iface); return 0; } -/* - * Flush reg cache - * We can only write the interpolator and decimator registers - * when the DAI is being clocked by the CPU DAI. It's up to the - * machine and cpu DAI driver to do this before we are called. - */ -static int uda1380_pcm_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static int uda1380_set_dai_fmt_playback(struct snd_soc_dai *codec_dai, + unsigned int fmt) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; - int reg, reg_start, reg_end, clk; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - reg_start = UDA1380_MVOL; - reg_end = UDA1380_MIXER; - } else { - reg_start = UDA1380_DEC; - reg_end = UDA1380_AGC; + struct snd_soc_codec *codec = codec_dai->codec; + int iface; + + /* set up DAI based upon fmt */ + iface = uda1380_read_reg_cache(codec, UDA1380_IFACE); + iface &= ~R01_SFORI_MASK; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= R01_SFORI_I2S; + break; + case SND_SOC_DAIFMT_LSB: + iface |= R01_SFORI_LSB16; + break; + case SND_SOC_DAIFMT_MSB: + iface |= R01_SFORI_MSB; } - /* FIXME disable DAC_CLK */ - clk = uda1380_read_reg_cache(codec, UDA1380_CLK); - uda1380_write(codec, UDA1380_CLK, clk & ~R00_DAC_CLK); + /* DATAI is slave only, so this has to be slave */ + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) + return -EINVAL; + + uda1380_write(codec, UDA1380_IFACE, iface); + + return 0; +} + +static int uda1380_set_dai_fmt_capture(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int iface; + + /* set up DAI based upon fmt */ + iface = uda1380_read_reg_cache(codec, UDA1380_IFACE); + iface &= ~(R01_SIM | R01_SFORO_MASK); - for (reg = reg_start; reg <= reg_end; reg++) { - pr_debug("uda1380: flush reg %x val %x:", reg, - uda1380_read_reg_cache(codec, reg)); - uda1380_write(codec, reg, uda1380_read_reg_cache(codec, reg)); + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= R01_SFORO_I2S; + break; + case SND_SOC_DAIFMT_LSB: + iface |= R01_SFORO_LSB16; + break; + case SND_SOC_DAIFMT_MSB: + iface |= R01_SFORO_MSB; } - /* FIXME enable DAC_CLK */ - uda1380_write(codec, UDA1380_CLK, clk | R00_DAC_CLK); + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFM) + iface |= R01_SIM; + uda1380_write(codec, UDA1380_IFACE, iface); + + return 0; +} + +static int uda1380_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + int mixer = uda1380_read_reg_cache(codec, UDA1380_MIXER); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + uda1380_write_reg_cache(codec, UDA1380_MIXER, + mixer & ~R14_SILENCE); + schedule_work(&uda1380_work); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + uda1380_write_reg_cache(codec, UDA1380_MIXER, + mixer | R14_SILENCE); + schedule_work(&uda1380_work); + break; + } return 0; } @@ -445,7 +498,7 @@ static int uda1380_pcm_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK); /* set WSPLL power and divider if running from this clock */ @@ -484,7 +537,7 @@ static void uda1380_pcm_shutdown(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK); /* shut down WSPLL power if running from this clock */ @@ -501,24 +554,6 @@ static void uda1380_pcm_shutdown(struct snd_pcm_substream *substream, uda1380_write(codec, UDA1380_CLK, clk); } -static int uda1380_mute(struct snd_soc_dai *codec_dai, int mute) -{ - struct snd_soc_codec *codec = codec_dai->codec; - u16 mute_reg = uda1380_read_reg_cache(codec, UDA1380_DEEMP) & ~R13_MTM; - - /* FIXME: mute(codec,0) is called when the magician clock is already - * set to WSPLL, but for some unknown reason writing to interpolator - * registers works only when clocked by SYSCLK */ - u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK); - uda1380_write(codec, UDA1380_CLK, ~R00_DAC_CLK & clk); - if (mute) - uda1380_write(codec, UDA1380_DEEMP, mute_reg | R13_MTM); - else - uda1380_write(codec, UDA1380_DEEMP, mute_reg); - uda1380_write(codec, UDA1380_CLK, clk); - return 0; -} - static int uda1380_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { @@ -544,6 +579,27 @@ static int uda1380_set_bias_level(struct snd_soc_codec *codec, SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) +static struct snd_soc_dai_ops uda1380_dai_ops = { + .hw_params = uda1380_pcm_hw_params, + .shutdown = uda1380_pcm_shutdown, + .trigger = uda1380_trigger, + .set_fmt = uda1380_set_dai_fmt_both, +}; + +static struct snd_soc_dai_ops uda1380_dai_ops_playback = { + .hw_params = uda1380_pcm_hw_params, + .shutdown = uda1380_pcm_shutdown, + .trigger = uda1380_trigger, + .set_fmt = uda1380_set_dai_fmt_playback, +}; + +static struct snd_soc_dai_ops uda1380_dai_ops_capture = { + .hw_params = uda1380_pcm_hw_params, + .shutdown = uda1380_pcm_shutdown, + .trigger = uda1380_trigger, + .set_fmt = uda1380_set_dai_fmt_capture, +}; + struct snd_soc_dai uda1380_dai[] = { { .name = "UDA1380", @@ -559,13 +615,7 @@ struct snd_soc_dai uda1380_dai[] = { .channels_max = 2, .rates = UDA1380_RATES, .formats = SNDRV_PCM_FMTBIT_S16_LE,}, - .ops = { - .hw_params = uda1380_pcm_hw_params, - .shutdown = uda1380_pcm_shutdown, - .prepare = uda1380_pcm_prepare, - .digital_mute = uda1380_mute, - .set_fmt = uda1380_set_dai_fmt, - }, + .ops = &uda1380_dai_ops, }, { /* playback only - dual interface */ .name = "UDA1380", @@ -576,13 +626,7 @@ struct snd_soc_dai uda1380_dai[] = { .rates = UDA1380_RATES, .formats = SNDRV_PCM_FMTBIT_S16_LE, }, - .ops = { - .hw_params = uda1380_pcm_hw_params, - .shutdown = uda1380_pcm_shutdown, - .prepare = uda1380_pcm_prepare, - .digital_mute = uda1380_mute, - .set_fmt = uda1380_set_dai_fmt, - }, + .ops = &uda1380_dai_ops_playback, }, { /* capture only - dual interface*/ .name = "UDA1380", @@ -593,12 +637,7 @@ struct snd_soc_dai uda1380_dai[] = { .rates = UDA1380_RATES, .formats = SNDRV_PCM_FMTBIT_S16_LE, }, - .ops = { - .hw_params = uda1380_pcm_hw_params, - .shutdown = uda1380_pcm_shutdown, - .prepare = uda1380_pcm_prepare, - .set_fmt = uda1380_set_dai_fmt, - }, + .ops = &uda1380_dai_ops_capture, }, }; EXPORT_SYMBOL_GPL(uda1380_dai); @@ -606,7 +645,7 @@ EXPORT_SYMBOL_GPL(uda1380_dai); static int uda1380_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; @@ -615,7 +654,7 @@ static int uda1380_suspend(struct platform_device *pdev, pm_message_t state) static int uda1380_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int i; u8 data[2]; u16 *cache = codec->reg_cache; @@ -637,7 +676,7 @@ static int uda1380_resume(struct platform_device *pdev) */ static int uda1380_init(struct snd_soc_device *socdev, int dac_clk) { - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int ret = 0; codec->name = "UDA1380"; @@ -655,6 +694,9 @@ static int uda1380_init(struct snd_soc_device *socdev, int dac_clk) codec->reg_cache_step = 1; uda1380_reset(codec); + uda1380_codec = codec; + INIT_WORK(&uda1380_work, uda1380_flush_work); + /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { @@ -675,7 +717,8 @@ static int uda1380_init(struct snd_soc_device *socdev, int dac_clk) } /* uda1380 init */ - uda1380_add_controls(codec); + snd_soc_add_controls(codec, uda1380_snd_controls, + ARRAY_SIZE(uda1380_snd_controls)); uda1380_add_widgets(codec); ret = snd_soc_init_card(socdev); if (ret < 0) { @@ -702,7 +745,7 @@ static int uda1380_i2c_probe(struct i2c_client *i2c, { struct snd_soc_device *socdev = uda1380_socdev; struct uda1380_setup_data *setup = socdev->codec_data; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int ret; i2c_set_clientdata(i2c, codec); @@ -786,14 +829,12 @@ static int uda1380_probe(struct platform_device *pdev) struct snd_soc_codec *codec; int ret; - pr_info("UDA1380 Audio Codec %s", UDA1380_VERSION); - setup = socdev->codec_data; codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); if (codec == NULL) return -ENOMEM; - socdev->codec = codec; + socdev->card->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); @@ -817,7 +858,7 @@ static int uda1380_probe(struct platform_device *pdev) static int uda1380_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; if (codec->control_data) uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF); diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index 35d9975..3b1d099 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -51,10 +51,17 @@ struct wm8350_output { u16 mute; }; +struct wm8350_jack_data { + struct snd_soc_jack *jack; + int report; +}; + struct wm8350_data { struct snd_soc_codec codec; struct wm8350_output out1; struct wm8350_output out2; + struct wm8350_jack_data hpl; + struct wm8350_jack_data hpr; struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)]; }; @@ -775,21 +782,6 @@ static const struct snd_soc_dapm_route audio_map[] = { {"Beep", NULL, "IN3R PGA"}, }; -static int wm8350_add_controls(struct snd_soc_codec *codec) -{ - int err, i; - - for (i = 0; i < ARRAY_SIZE(wm8350_snd_controls); i++) { - err = snd_ctl_add(codec->card, - snd_soc_cnew(&wm8350_snd_controls[i], - codec, NULL)); - if (err < 0) - return err; - } - - return 0; -} - static int wm8350_add_widgets(struct snd_soc_codec *codec) { int ret; @@ -1309,7 +1301,7 @@ static int wm8350_set_bias_level(struct snd_soc_codec *codec, static int wm8350_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; wm8350_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; @@ -1318,7 +1310,7 @@ static int wm8350_suspend(struct platform_device *pdev, pm_message_t state) static int wm8350_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; wm8350_set_bias_level(codec, SND_SOC_BIAS_STANDBY); @@ -1328,6 +1320,95 @@ static int wm8350_resume(struct platform_device *pdev) return 0; } +static void wm8350_hp_jack_handler(struct wm8350 *wm8350, int irq, void *data) +{ + struct wm8350_data *priv = data; + u16 reg; + int report; + int mask; + struct wm8350_jack_data *jack = NULL; + + switch (irq) { + case WM8350_IRQ_CODEC_JCK_DET_L: + jack = &priv->hpl; + mask = WM8350_JACK_L_LVL; + break; + + case WM8350_IRQ_CODEC_JCK_DET_R: + jack = &priv->hpr; + mask = WM8350_JACK_R_LVL; + break; + + default: + BUG(); + } + + if (!jack->jack) { + dev_warn(wm8350->dev, "Jack interrupt called with no jack\n"); + return; + } + + /* Debounce */ + msleep(200); + + reg = wm8350_reg_read(wm8350, WM8350_JACK_PIN_STATUS); + if (reg & mask) + report = jack->report; + else + report = 0; + + snd_soc_jack_report(jack->jack, report, jack->report); +} + +/** + * wm8350_hp_jack_detect - Enable headphone jack detection. + * + * @codec: WM8350 codec + * @which: left or right jack detect signal + * @jack: jack to report detection events on + * @report: value to report + * + * Enables the headphone jack detection of the WM8350. + */ +int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which, + struct snd_soc_jack *jack, int report) +{ + struct wm8350_data *priv = codec->private_data; + struct wm8350 *wm8350 = codec->control_data; + int irq; + int ena; + + switch (which) { + case WM8350_JDL: + priv->hpl.jack = jack; + priv->hpl.report = report; + irq = WM8350_IRQ_CODEC_JCK_DET_L; + ena = WM8350_JDL_ENA; + break; + + case WM8350_JDR: + priv->hpr.jack = jack; + priv->hpr.report = report; + irq = WM8350_IRQ_CODEC_JCK_DET_R; + ena = WM8350_JDR_ENA; + break; + + default: + return -EINVAL; + } + + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA); + wm8350_set_bits(wm8350, WM8350_JACK_DETECT, ena); + + /* Sync status */ + wm8350_hp_jack_handler(wm8350, irq, priv); + + wm8350_unmask_irq(wm8350, irq); + + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_hp_jack_detect); + static struct snd_soc_codec *wm8350_codec; static int wm8350_probe(struct platform_device *pdev) @@ -1342,8 +1423,8 @@ static int wm8350_probe(struct platform_device *pdev) BUG_ON(!wm8350_codec); - socdev->codec = wm8350_codec; - codec = socdev->codec; + socdev->card->codec = wm8350_codec; + codec = socdev->card->codec; wm8350 = codec->control_data; priv = codec->private_data; @@ -1381,13 +1462,21 @@ static int wm8350_probe(struct platform_device *pdev) wm8350_set_bits(wm8350, WM8350_ROUT2_VOLUME, WM8350_OUT2_VU | WM8350_OUT2R_MUTE); + wm8350_mask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L); + wm8350_mask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R); + wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L, + wm8350_hp_jack_handler, priv); + wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R, + wm8350_hp_jack_handler, priv); + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { dev_err(&pdev->dev, "failed to create pcms\n"); return ret; } - wm8350_add_controls(codec); + snd_soc_add_controls(codec, wm8350_snd_controls, + ARRAY_SIZE(wm8350_snd_controls)); wm8350_add_widgets(codec); wm8350_set_bias_level(codec, SND_SOC_BIAS_STANDBY); @@ -1409,10 +1498,23 @@ card_err: static int wm8350_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct wm8350 *wm8350 = codec->control_data; + struct wm8350_data *priv = codec->private_data; int ret; + wm8350_clear_bits(wm8350, WM8350_JACK_DETECT, + WM8350_JDL_ENA | WM8350_JDR_ENA); + wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA); + + wm8350_mask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L); + wm8350_mask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R); + wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L); + wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R); + + priv->hpl.jack = NULL; + priv->hpr.jack = NULL; + /* cancel any work waiting to be queued. */ ret = cancel_delayed_work(&codec->delayed_work); @@ -1436,6 +1538,16 @@ static int wm8350_remove(struct platform_device *pdev) SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static struct snd_soc_dai_ops wm8350_dai_ops = { + .hw_params = wm8350_pcm_hw_params, + .digital_mute = wm8350_mute, + .trigger = wm8350_pcm_trigger, + .set_fmt = wm8350_set_dai_fmt, + .set_sysclk = wm8350_set_dai_sysclk, + .set_pll = wm8350_set_fll, + .set_clkdiv = wm8350_set_clkdiv, +}; + struct snd_soc_dai wm8350_dai = { .name = "WM8350", .playback = { @@ -1452,15 +1564,7 @@ struct snd_soc_dai wm8350_dai = { .rates = WM8350_RATES, .formats = WM8350_FORMATS, }, - .ops = { - .hw_params = wm8350_pcm_hw_params, - .digital_mute = wm8350_mute, - .trigger = wm8350_pcm_trigger, - .set_fmt = wm8350_set_dai_fmt, - .set_sysclk = wm8350_set_dai_sysclk, - .set_pll = wm8350_set_fll, - .set_clkdiv = wm8350_set_clkdiv, - }, + .ops = &wm8350_dai_ops, }; EXPORT_SYMBOL_GPL(wm8350_dai); @@ -1472,7 +1576,7 @@ struct snd_soc_codec_device soc_codec_dev_wm8350 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_wm8350); -static int wm8350_codec_probe(struct platform_device *pdev) +static __devinit int wm8350_codec_probe(struct platform_device *pdev) { struct wm8350 *wm8350 = platform_get_drvdata(pdev); struct wm8350_data *priv; diff --git a/sound/soc/codecs/wm8350.h b/sound/soc/codecs/wm8350.h index cc2887a..d11bd92 100644 --- a/sound/soc/codecs/wm8350.h +++ b/sound/soc/codecs/wm8350.h @@ -17,4 +17,12 @@ extern struct snd_soc_dai wm8350_dai; extern struct snd_soc_codec_device soc_codec_dev_wm8350; +enum wm8350_jack { + WM8350_JDL = 1, + WM8350_JDR = 2, +}; + +int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which, + struct snd_soc_jack *jack, int report); + #endif diff --git a/sound/soc/codecs/wm8400.c b/sound/soc/codecs/wm8400.c new file mode 100644 index 0000000..510efa6 --- /dev/null +++ b/sound/soc/codecs/wm8400.c @@ -0,0 +1,1582 @@ +/* + * wm8400.c -- WM8400 ALSA Soc Audio driver + * + * Copyright 2008, 2009 Wolfson Microelectronics PLC. + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/mfd/wm8400-audio.h> +#include <linux/mfd/wm8400-private.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> + +#include "wm8400.h" + +/* Fake register for internal state */ +#define WM8400_INTDRIVBITS (WM8400_REGISTER_COUNT + 1) +#define WM8400_INMIXL_PWR 0 +#define WM8400_AINLMUX_PWR 1 +#define WM8400_INMIXR_PWR 2 +#define WM8400_AINRMUX_PWR 3 + +static struct regulator_bulk_data power[] = { + { + .supply = "I2S1VDD", + }, + { + .supply = "I2S2VDD", + }, + { + .supply = "DCVDD", + }, + { + .supply = "AVDD", + }, + { + .supply = "FLLVDD", + }, + { + .supply = "HPVDD", + }, + { + .supply = "SPKVDD", + }, +}; + +/* codec private data */ +struct wm8400_priv { + struct snd_soc_codec codec; + struct wm8400 *wm8400; + u16 fake_register; + unsigned int sysclk; + unsigned int pcmclk; + struct work_struct work; + int fll_in, fll_out; +}; + +static inline unsigned int wm8400_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct wm8400_priv *wm8400 = codec->private_data; + + if (reg == WM8400_INTDRIVBITS) + return wm8400->fake_register; + else + return wm8400_reg_read(wm8400->wm8400, reg); +} + +/* + * write to the wm8400 register space + */ +static int wm8400_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + struct wm8400_priv *wm8400 = codec->private_data; + + if (reg == WM8400_INTDRIVBITS) { + wm8400->fake_register = value; + return 0; + } else + return wm8400_set_bits(wm8400->wm8400, reg, 0xffff, value); +} + +static void wm8400_codec_reset(struct snd_soc_codec *codec) +{ + struct wm8400_priv *wm8400 = codec->private_data; + + wm8400_reset_codec_reg_cache(wm8400->wm8400); +} + +static const DECLARE_TLV_DB_LINEAR(rec_mix_tlv, -1500, 600); + +static const DECLARE_TLV_DB_LINEAR(in_pga_tlv, -1650, 3000); + +static const DECLARE_TLV_DB_LINEAR(out_mix_tlv, -2100, 0); + +static const DECLARE_TLV_DB_LINEAR(out_pga_tlv, -7300, 600); + +static const DECLARE_TLV_DB_LINEAR(out_omix_tlv, -600, 0); + +static const DECLARE_TLV_DB_LINEAR(out_dac_tlv, -7163, 0); + +static const DECLARE_TLV_DB_LINEAR(in_adc_tlv, -7163, 1763); + +static const DECLARE_TLV_DB_LINEAR(out_sidetone_tlv, -3600, 0); + +static int wm8400_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int reg = mc->reg; + int ret; + u16 val; + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + if (ret < 0) + return ret; + + /* now hit the volume update bits (always bit 8) */ + val = wm8400_read(codec, reg); + return wm8400_write(codec, reg, val | 0x0100); +} + +#define WM8400_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw, \ + .get = snd_soc_get_volsw, .put = wm8400_outpga_put_volsw_vu, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } + + +static const char *wm8400_digital_sidetone[] = + {"None", "Left ADC", "Right ADC", "Reserved"}; + +static const struct soc_enum wm8400_left_digital_sidetone_enum = +SOC_ENUM_SINGLE(WM8400_DIGITAL_SIDE_TONE, + WM8400_ADC_TO_DACL_SHIFT, 2, wm8400_digital_sidetone); + +static const struct soc_enum wm8400_right_digital_sidetone_enum = +SOC_ENUM_SINGLE(WM8400_DIGITAL_SIDE_TONE, + WM8400_ADC_TO_DACR_SHIFT, 2, wm8400_digital_sidetone); + +static const char *wm8400_adcmode[] = + {"Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"}; + +static const struct soc_enum wm8400_right_adcmode_enum = +SOC_ENUM_SINGLE(WM8400_ADC_CTRL, WM8400_ADC_HPF_CUT_SHIFT, 3, wm8400_adcmode); + +static const struct snd_kcontrol_new wm8400_snd_controls[] = { +/* INMIXL */ +SOC_SINGLE("LIN12 PGA Boost", WM8400_INPUT_MIXER3, WM8400_L12MNBST_SHIFT, + 1, 0), +SOC_SINGLE("LIN34 PGA Boost", WM8400_INPUT_MIXER3, WM8400_L34MNBST_SHIFT, + 1, 0), +/* INMIXR */ +SOC_SINGLE("RIN12 PGA Boost", WM8400_INPUT_MIXER3, WM8400_R12MNBST_SHIFT, + 1, 0), +SOC_SINGLE("RIN34 PGA Boost", WM8400_INPUT_MIXER3, WM8400_R34MNBST_SHIFT, + 1, 0), + +/* LOMIX */ +SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8400_OUTPUT_MIXER3, + WM8400_LLI3LOVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX RIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER3, + WM8400_LR12LOVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX LIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER3, + WM8400_LL12LOVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8400_OUTPUT_MIXER5, + WM8400_LRI3LOVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8400_OUTPUT_MIXER5, + WM8400_LRBLOVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8400_OUTPUT_MIXER5, + WM8400_LRBLOVOL_SHIFT, 7, 0, out_mix_tlv), + +/* ROMIX */ +SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8400_OUTPUT_MIXER4, + WM8400_RRI3ROVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX LIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER4, + WM8400_RL12ROVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX RIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER4, + WM8400_RR12ROVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX LIN3 Bypass Volume", WM8400_OUTPUT_MIXER6, + WM8400_RLI3ROVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX AINLMUX Bypass Volume", WM8400_OUTPUT_MIXER6, + WM8400_RLBROVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX AINRMUX Bypass Volume", WM8400_OUTPUT_MIXER6, + WM8400_RRBROVOL_SHIFT, 7, 0, out_mix_tlv), + +/* LOUT */ +WM8400_OUTPGA_SINGLE_R_TLV("LOUT Volume", WM8400_LEFT_OUTPUT_VOLUME, + WM8400_LOUTVOL_SHIFT, WM8400_LOUTVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("LOUT ZC", WM8400_LEFT_OUTPUT_VOLUME, WM8400_LOZC_SHIFT, 1, 0), + +/* ROUT */ +WM8400_OUTPGA_SINGLE_R_TLV("ROUT Volume", WM8400_RIGHT_OUTPUT_VOLUME, + WM8400_ROUTVOL_SHIFT, WM8400_ROUTVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("ROUT ZC", WM8400_RIGHT_OUTPUT_VOLUME, WM8400_ROZC_SHIFT, 1, 0), + +/* LOPGA */ +WM8400_OUTPGA_SINGLE_R_TLV("LOPGA Volume", WM8400_LEFT_OPGA_VOLUME, + WM8400_LOPGAVOL_SHIFT, WM8400_LOPGAVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("LOPGA ZC Switch", WM8400_LEFT_OPGA_VOLUME, + WM8400_LOPGAZC_SHIFT, 1, 0), + +/* ROPGA */ +WM8400_OUTPGA_SINGLE_R_TLV("ROPGA Volume", WM8400_RIGHT_OPGA_VOLUME, + WM8400_ROPGAVOL_SHIFT, WM8400_ROPGAVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("ROPGA ZC Switch", WM8400_RIGHT_OPGA_VOLUME, + WM8400_ROPGAZC_SHIFT, 1, 0), + +SOC_SINGLE("LON Mute Switch", WM8400_LINE_OUTPUTS_VOLUME, + WM8400_LONMUTE_SHIFT, 1, 0), +SOC_SINGLE("LOP Mute Switch", WM8400_LINE_OUTPUTS_VOLUME, + WM8400_LOPMUTE_SHIFT, 1, 0), +SOC_SINGLE("LOP Attenuation Switch", WM8400_LINE_OUTPUTS_VOLUME, + WM8400_LOATTN_SHIFT, 1, 0), +SOC_SINGLE("RON Mute Switch", WM8400_LINE_OUTPUTS_VOLUME, + WM8400_RONMUTE_SHIFT, 1, 0), +SOC_SINGLE("ROP Mute Switch", WM8400_LINE_OUTPUTS_VOLUME, + WM8400_ROPMUTE_SHIFT, 1, 0), +SOC_SINGLE("ROP Attenuation Switch", WM8400_LINE_OUTPUTS_VOLUME, + WM8400_ROATTN_SHIFT, 1, 0), + +SOC_SINGLE("OUT3 Mute Switch", WM8400_OUT3_4_VOLUME, + WM8400_OUT3MUTE_SHIFT, 1, 0), +SOC_SINGLE("OUT3 Attenuation Switch", WM8400_OUT3_4_VOLUME, + WM8400_OUT3ATTN_SHIFT, 1, 0), + +SOC_SINGLE("OUT4 Mute Switch", WM8400_OUT3_4_VOLUME, + WM8400_OUT4MUTE_SHIFT, 1, 0), +SOC_SINGLE("OUT4 Attenuation Switch", WM8400_OUT3_4_VOLUME, + WM8400_OUT4ATTN_SHIFT, 1, 0), + +SOC_SINGLE("Speaker Mode Switch", WM8400_CLASSD1, + WM8400_CDMODE_SHIFT, 1, 0), + +SOC_SINGLE("Speaker Output Attenuation Volume", WM8400_SPEAKER_VOLUME, + WM8400_SPKATTN_SHIFT, WM8400_SPKATTN_MASK, 0), +SOC_SINGLE("Speaker DC Boost Volume", WM8400_CLASSD3, + WM8400_DCGAIN_SHIFT, 6, 0), +SOC_SINGLE("Speaker AC Boost Volume", WM8400_CLASSD3, + WM8400_ACGAIN_SHIFT, 6, 0), + +WM8400_OUTPGA_SINGLE_R_TLV("Left DAC Digital Volume", + WM8400_LEFT_DAC_DIGITAL_VOLUME, WM8400_DACL_VOL_SHIFT, + 127, 0, out_dac_tlv), + +WM8400_OUTPGA_SINGLE_R_TLV("Right DAC Digital Volume", + WM8400_RIGHT_DAC_DIGITAL_VOLUME, WM8400_DACR_VOL_SHIFT, + 127, 0, out_dac_tlv), + +SOC_ENUM("Left Digital Sidetone", wm8400_left_digital_sidetone_enum), +SOC_ENUM("Right Digital Sidetone", wm8400_right_digital_sidetone_enum), + +SOC_SINGLE_TLV("Left Digital Sidetone Volume", WM8400_DIGITAL_SIDE_TONE, + WM8400_ADCL_DAC_SVOL_SHIFT, 15, 0, out_sidetone_tlv), +SOC_SINGLE_TLV("Right Digital Sidetone Volume", WM8400_DIGITAL_SIDE_TONE, + WM8400_ADCR_DAC_SVOL_SHIFT, 15, 0, out_sidetone_tlv), + +SOC_SINGLE("ADC Digital High Pass Filter Switch", WM8400_ADC_CTRL, + WM8400_ADC_HPF_ENA_SHIFT, 1, 0), + +SOC_ENUM("ADC HPF Mode", wm8400_right_adcmode_enum), + +WM8400_OUTPGA_SINGLE_R_TLV("Left ADC Digital Volume", + WM8400_LEFT_ADC_DIGITAL_VOLUME, + WM8400_ADCL_VOL_SHIFT, + WM8400_ADCL_VOL_MASK, + 0, + in_adc_tlv), + +WM8400_OUTPGA_SINGLE_R_TLV("Right ADC Digital Volume", + WM8400_RIGHT_ADC_DIGITAL_VOLUME, + WM8400_ADCR_VOL_SHIFT, + WM8400_ADCR_VOL_MASK, + 0, + in_adc_tlv), + +WM8400_OUTPGA_SINGLE_R_TLV("LIN12 Volume", + WM8400_LEFT_LINE_INPUT_1_2_VOLUME, + WM8400_LIN12VOL_SHIFT, + WM8400_LIN12VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("LIN12 ZC Switch", WM8400_LEFT_LINE_INPUT_1_2_VOLUME, + WM8400_LI12ZC_SHIFT, 1, 0), + +SOC_SINGLE("LIN12 Mute Switch", WM8400_LEFT_LINE_INPUT_1_2_VOLUME, + WM8400_LI12MUTE_SHIFT, 1, 0), + +WM8400_OUTPGA_SINGLE_R_TLV("LIN34 Volume", + WM8400_LEFT_LINE_INPUT_3_4_VOLUME, + WM8400_LIN34VOL_SHIFT, + WM8400_LIN34VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("LIN34 ZC Switch", WM8400_LEFT_LINE_INPUT_3_4_VOLUME, + WM8400_LI34ZC_SHIFT, 1, 0), + +SOC_SINGLE("LIN34 Mute Switch", WM8400_LEFT_LINE_INPUT_3_4_VOLUME, + WM8400_LI34MUTE_SHIFT, 1, 0), + +WM8400_OUTPGA_SINGLE_R_TLV("RIN12 Volume", + WM8400_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8400_RIN12VOL_SHIFT, + WM8400_RIN12VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("RIN12 ZC Switch", WM8400_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8400_RI12ZC_SHIFT, 1, 0), + +SOC_SINGLE("RIN12 Mute Switch", WM8400_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8400_RI12MUTE_SHIFT, 1, 0), + +WM8400_OUTPGA_SINGLE_R_TLV("RIN34 Volume", + WM8400_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8400_RIN34VOL_SHIFT, + WM8400_RIN34VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("RIN34 ZC Switch", WM8400_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8400_RI34ZC_SHIFT, 1, 0), + +SOC_SINGLE("RIN34 Mute Switch", WM8400_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8400_RI34MUTE_SHIFT, 1, 0), + +}; + +/* add non dapm controls */ +static int wm8400_add_controls(struct snd_soc_codec *codec) +{ + return snd_soc_add_controls(codec, wm8400_snd_controls, + ARRAY_SIZE(wm8400_snd_controls)); +} + +/* + * _DAPM_ Controls + */ + +static int inmixer_event (struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + u16 reg, fakepower; + + reg = wm8400_read(w->codec, WM8400_POWER_MANAGEMENT_2); + fakepower = wm8400_read(w->codec, WM8400_INTDRIVBITS); + + if (fakepower & ((1 << WM8400_INMIXL_PWR) | + (1 << WM8400_AINLMUX_PWR))) { + reg |= WM8400_AINL_ENA; + } else { + reg &= ~WM8400_AINL_ENA; + } + + if (fakepower & ((1 << WM8400_INMIXR_PWR) | + (1 << WM8400_AINRMUX_PWR))) { + reg |= WM8400_AINR_ENA; + } else { + reg &= ~WM8400_AINL_ENA; + } + wm8400_write(w->codec, WM8400_POWER_MANAGEMENT_2, reg); + + return 0; +} + +static int outmixer_event (struct snd_soc_dapm_widget *w, + struct snd_kcontrol * kcontrol, int event) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + u32 reg_shift = mc->shift; + int ret = 0; + u16 reg; + + switch (reg_shift) { + case WM8400_SPEAKER_MIXER | (WM8400_LDSPK << 8) : + reg = wm8400_read(w->codec, WM8400_OUTPUT_MIXER1); + if (reg & WM8400_LDLO) { + printk(KERN_WARNING + "Cannot set as Output Mixer 1 LDLO Set\n"); + ret = -1; + } + break; + case WM8400_SPEAKER_MIXER | (WM8400_RDSPK << 8): + reg = wm8400_read(w->codec, WM8400_OUTPUT_MIXER2); + if (reg & WM8400_RDRO) { + printk(KERN_WARNING + "Cannot set as Output Mixer 2 RDRO Set\n"); + ret = -1; + } + break; + case WM8400_OUTPUT_MIXER1 | (WM8400_LDLO << 8): + reg = wm8400_read(w->codec, WM8400_SPEAKER_MIXER); + if (reg & WM8400_LDSPK) { + printk(KERN_WARNING + "Cannot set as Speaker Mixer LDSPK Set\n"); + ret = -1; + } + break; + case WM8400_OUTPUT_MIXER2 | (WM8400_RDRO << 8): + reg = wm8400_read(w->codec, WM8400_SPEAKER_MIXER); + if (reg & WM8400_RDSPK) { + printk(KERN_WARNING + "Cannot set as Speaker Mixer RDSPK Set\n"); + ret = -1; + } + break; + } + + return ret; +} + +/* INMIX dB values */ +static const unsigned int in_mix_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0,7, TLV_DB_LINEAR_ITEM(-1200, 600), +}; + +/* Left In PGA Connections */ +static const struct snd_kcontrol_new wm8400_dapm_lin12_pga_controls[] = { +SOC_DAPM_SINGLE("LIN1 Switch", WM8400_INPUT_MIXER2, WM8400_LMN1_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LIN2 Switch", WM8400_INPUT_MIXER2, WM8400_LMP2_SHIFT, 1, 0), +}; + +static const struct snd_kcontrol_new wm8400_dapm_lin34_pga_controls[] = { +SOC_DAPM_SINGLE("LIN3 Switch", WM8400_INPUT_MIXER2, WM8400_LMN3_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LIN4 Switch", WM8400_INPUT_MIXER2, WM8400_LMP4_SHIFT, 1, 0), +}; + +/* Right In PGA Connections */ +static const struct snd_kcontrol_new wm8400_dapm_rin12_pga_controls[] = { +SOC_DAPM_SINGLE("RIN1 Switch", WM8400_INPUT_MIXER2, WM8400_RMN1_SHIFT, 1, 0), +SOC_DAPM_SINGLE("RIN2 Switch", WM8400_INPUT_MIXER2, WM8400_RMP2_SHIFT, 1, 0), +}; + +static const struct snd_kcontrol_new wm8400_dapm_rin34_pga_controls[] = { +SOC_DAPM_SINGLE("RIN3 Switch", WM8400_INPUT_MIXER2, WM8400_RMN3_SHIFT, 1, 0), +SOC_DAPM_SINGLE("RIN4 Switch", WM8400_INPUT_MIXER2, WM8400_RMP4_SHIFT, 1, 0), +}; + +/* INMIXL */ +static const struct snd_kcontrol_new wm8400_dapm_inmixl_controls[] = { +SOC_DAPM_SINGLE_TLV("Record Left Volume", WM8400_INPUT_MIXER3, + WM8400_LDBVOL_SHIFT, WM8400_LDBVOL_MASK, 0, in_mix_tlv), +SOC_DAPM_SINGLE_TLV("LIN2 Volume", WM8400_INPUT_MIXER5, WM8400_LI2BVOL_SHIFT, + 7, 0, in_mix_tlv), +SOC_DAPM_SINGLE("LINPGA12 Switch", WM8400_INPUT_MIXER3, WM8400_L12MNB_SHIFT, + 1, 0), +SOC_DAPM_SINGLE("LINPGA34 Switch", WM8400_INPUT_MIXER3, WM8400_L34MNB_SHIFT, + 1, 0), +}; + +/* INMIXR */ +static const struct snd_kcontrol_new wm8400_dapm_inmixr_controls[] = { +SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8400_INPUT_MIXER4, + WM8400_RDBVOL_SHIFT, WM8400_RDBVOL_MASK, 0, in_mix_tlv), +SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8400_INPUT_MIXER6, WM8400_RI2BVOL_SHIFT, + 7, 0, in_mix_tlv), +SOC_DAPM_SINGLE("RINPGA12 Switch", WM8400_INPUT_MIXER3, WM8400_L12MNB_SHIFT, + 1, 0), +SOC_DAPM_SINGLE("RINPGA34 Switch", WM8400_INPUT_MIXER3, WM8400_L34MNB_SHIFT, + 1, 0), +}; + +/* AINLMUX */ +static const char *wm8400_ainlmux[] = + {"INMIXL Mix", "RXVOICE Mix", "DIFFINL Mix"}; + +static const struct soc_enum wm8400_ainlmux_enum = +SOC_ENUM_SINGLE( WM8400_INPUT_MIXER1, WM8400_AINLMODE_SHIFT, + ARRAY_SIZE(wm8400_ainlmux), wm8400_ainlmux); + +static const struct snd_kcontrol_new wm8400_dapm_ainlmux_controls = +SOC_DAPM_ENUM("Route", wm8400_ainlmux_enum); + +/* DIFFINL */ + +/* AINRMUX */ +static const char *wm8400_ainrmux[] = + {"INMIXR Mix", "RXVOICE Mix", "DIFFINR Mix"}; + +static const struct soc_enum wm8400_ainrmux_enum = +SOC_ENUM_SINGLE( WM8400_INPUT_MIXER1, WM8400_AINRMODE_SHIFT, + ARRAY_SIZE(wm8400_ainrmux), wm8400_ainrmux); + +static const struct snd_kcontrol_new wm8400_dapm_ainrmux_controls = +SOC_DAPM_ENUM("Route", wm8400_ainrmux_enum); + +/* RXVOICE */ +static const struct snd_kcontrol_new wm8400_dapm_rxvoice_controls[] = { +SOC_DAPM_SINGLE_TLV("LIN4/RXN", WM8400_INPUT_MIXER5, WM8400_LR4BVOL_SHIFT, + WM8400_LR4BVOL_MASK, 0, in_mix_tlv), +SOC_DAPM_SINGLE_TLV("RIN4/RXP", WM8400_INPUT_MIXER6, WM8400_RL4BVOL_SHIFT, + WM8400_RL4BVOL_MASK, 0, in_mix_tlv), +}; + +/* LOMIX */ +static const struct snd_kcontrol_new wm8400_dapm_lomix_controls[] = { +SOC_DAPM_SINGLE("LOMIX Right ADC Bypass Switch", WM8400_OUTPUT_MIXER1, + WM8400_LRBLO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOMIX Left ADC Bypass Switch", WM8400_OUTPUT_MIXER1, + WM8400_LLBLO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOMIX RIN3 Bypass Switch", WM8400_OUTPUT_MIXER1, + WM8400_LRI3LO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOMIX LIN3 Bypass Switch", WM8400_OUTPUT_MIXER1, + WM8400_LLI3LO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOMIX RIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER1, + WM8400_LR12LO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOMIX LIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER1, + WM8400_LL12LO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOMIX Left DAC Switch", WM8400_OUTPUT_MIXER1, + WM8400_LDLO_SHIFT, 1, 0), +}; + +/* ROMIX */ +static const struct snd_kcontrol_new wm8400_dapm_romix_controls[] = { +SOC_DAPM_SINGLE("ROMIX Left ADC Bypass Switch", WM8400_OUTPUT_MIXER2, + WM8400_RLBRO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROMIX Right ADC Bypass Switch", WM8400_OUTPUT_MIXER2, + WM8400_RRBRO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROMIX LIN3 Bypass Switch", WM8400_OUTPUT_MIXER2, + WM8400_RLI3RO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROMIX RIN3 Bypass Switch", WM8400_OUTPUT_MIXER2, + WM8400_RRI3RO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROMIX LIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER2, + WM8400_RL12RO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROMIX RIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER2, + WM8400_RR12RO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROMIX Right DAC Switch", WM8400_OUTPUT_MIXER2, + WM8400_RDRO_SHIFT, 1, 0), +}; + +/* LONMIX */ +static const struct snd_kcontrol_new wm8400_dapm_lonmix_controls[] = { +SOC_DAPM_SINGLE("LONMIX Left Mixer PGA Switch", WM8400_LINE_MIXER1, + WM8400_LLOPGALON_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LONMIX Right Mixer PGA Switch", WM8400_LINE_MIXER1, + WM8400_LROPGALON_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LONMIX Inverted LOP Switch", WM8400_LINE_MIXER1, + WM8400_LOPLON_SHIFT, 1, 0), +}; + +/* LOPMIX */ +static const struct snd_kcontrol_new wm8400_dapm_lopmix_controls[] = { +SOC_DAPM_SINGLE("LOPMIX Right Mic Bypass Switch", WM8400_LINE_MIXER1, + WM8400_LR12LOP_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOPMIX Left Mic Bypass Switch", WM8400_LINE_MIXER1, + WM8400_LL12LOP_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOPMIX Left Mixer PGA Switch", WM8400_LINE_MIXER1, + WM8400_LLOPGALOP_SHIFT, 1, 0), +}; + +/* RONMIX */ +static const struct snd_kcontrol_new wm8400_dapm_ronmix_controls[] = { +SOC_DAPM_SINGLE("RONMIX Right Mixer PGA Switch", WM8400_LINE_MIXER2, + WM8400_RROPGARON_SHIFT, 1, 0), +SOC_DAPM_SINGLE("RONMIX Left Mixer PGA Switch", WM8400_LINE_MIXER2, + WM8400_RLOPGARON_SHIFT, 1, 0), +SOC_DAPM_SINGLE("RONMIX Inverted ROP Switch", WM8400_LINE_MIXER2, + WM8400_ROPRON_SHIFT, 1, 0), +}; + +/* ROPMIX */ +static const struct snd_kcontrol_new wm8400_dapm_ropmix_controls[] = { +SOC_DAPM_SINGLE("ROPMIX Left Mic Bypass Switch", WM8400_LINE_MIXER2, + WM8400_RL12ROP_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROPMIX Right Mic Bypass Switch", WM8400_LINE_MIXER2, + WM8400_RR12ROP_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROPMIX Right Mixer PGA Switch", WM8400_LINE_MIXER2, + WM8400_RROPGAROP_SHIFT, 1, 0), +}; + +/* OUT3MIX */ +static const struct snd_kcontrol_new wm8400_dapm_out3mix_controls[] = { +SOC_DAPM_SINGLE("OUT3MIX LIN4/RXP Bypass Switch", WM8400_OUT3_4_MIXER, + WM8400_LI4O3_SHIFT, 1, 0), +SOC_DAPM_SINGLE("OUT3MIX Left Out PGA Switch", WM8400_OUT3_4_MIXER, + WM8400_LPGAO3_SHIFT, 1, 0), +}; + +/* OUT4MIX */ +static const struct snd_kcontrol_new wm8400_dapm_out4mix_controls[] = { +SOC_DAPM_SINGLE("OUT4MIX Right Out PGA Switch", WM8400_OUT3_4_MIXER, + WM8400_RPGAO4_SHIFT, 1, 0), +SOC_DAPM_SINGLE("OUT4MIX RIN4/RXP Bypass Switch", WM8400_OUT3_4_MIXER, + WM8400_RI4O4_SHIFT, 1, 0), +}; + +/* SPKMIX */ +static const struct snd_kcontrol_new wm8400_dapm_spkmix_controls[] = { +SOC_DAPM_SINGLE("SPKMIX LIN2 Bypass Switch", WM8400_SPEAKER_MIXER, + WM8400_LI2SPK_SHIFT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX LADC Bypass Switch", WM8400_SPEAKER_MIXER, + WM8400_LB2SPK_SHIFT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Left Mixer PGA Switch", WM8400_SPEAKER_MIXER, + WM8400_LOPGASPK_SHIFT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Left DAC Switch", WM8400_SPEAKER_MIXER, + WM8400_LDSPK_SHIFT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Right DAC Switch", WM8400_SPEAKER_MIXER, + WM8400_RDSPK_SHIFT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Right Mixer PGA Switch", WM8400_SPEAKER_MIXER, + WM8400_ROPGASPK_SHIFT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX RADC Bypass Switch", WM8400_SPEAKER_MIXER, + WM8400_RL12ROP_SHIFT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX RIN2 Bypass Switch", WM8400_SPEAKER_MIXER, + WM8400_RI2SPK_SHIFT, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8400_dapm_widgets[] = { +/* Input Side */ +/* Input Lines */ +SND_SOC_DAPM_INPUT("LIN1"), +SND_SOC_DAPM_INPUT("LIN2"), +SND_SOC_DAPM_INPUT("LIN3"), +SND_SOC_DAPM_INPUT("LIN4/RXN"), +SND_SOC_DAPM_INPUT("RIN3"), +SND_SOC_DAPM_INPUT("RIN4/RXP"), +SND_SOC_DAPM_INPUT("RIN1"), +SND_SOC_DAPM_INPUT("RIN2"), +SND_SOC_DAPM_INPUT("Internal ADC Source"), + +/* DACs */ +SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8400_POWER_MANAGEMENT_2, + WM8400_ADCL_ENA_SHIFT, 0), +SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8400_POWER_MANAGEMENT_2, + WM8400_ADCR_ENA_SHIFT, 0), + +/* Input PGAs */ +SND_SOC_DAPM_MIXER("LIN12 PGA", WM8400_POWER_MANAGEMENT_2, + WM8400_LIN12_ENA_SHIFT, + 0, &wm8400_dapm_lin12_pga_controls[0], + ARRAY_SIZE(wm8400_dapm_lin12_pga_controls)), +SND_SOC_DAPM_MIXER("LIN34 PGA", WM8400_POWER_MANAGEMENT_2, + WM8400_LIN34_ENA_SHIFT, + 0, &wm8400_dapm_lin34_pga_controls[0], + ARRAY_SIZE(wm8400_dapm_lin34_pga_controls)), +SND_SOC_DAPM_MIXER("RIN12 PGA", WM8400_POWER_MANAGEMENT_2, + WM8400_RIN12_ENA_SHIFT, + 0, &wm8400_dapm_rin12_pga_controls[0], + ARRAY_SIZE(wm8400_dapm_rin12_pga_controls)), +SND_SOC_DAPM_MIXER("RIN34 PGA", WM8400_POWER_MANAGEMENT_2, + WM8400_RIN34_ENA_SHIFT, + 0, &wm8400_dapm_rin34_pga_controls[0], + ARRAY_SIZE(wm8400_dapm_rin34_pga_controls)), + +/* INMIXL */ +SND_SOC_DAPM_MIXER_E("INMIXL", WM8400_INTDRIVBITS, WM8400_INMIXL_PWR, 0, + &wm8400_dapm_inmixl_controls[0], + ARRAY_SIZE(wm8400_dapm_inmixl_controls), + inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +/* AINLMUX */ +SND_SOC_DAPM_MUX_E("AILNMUX", WM8400_INTDRIVBITS, WM8400_AINLMUX_PWR, 0, + &wm8400_dapm_ainlmux_controls, inmixer_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +/* INMIXR */ +SND_SOC_DAPM_MIXER_E("INMIXR", WM8400_INTDRIVBITS, WM8400_INMIXR_PWR, 0, + &wm8400_dapm_inmixr_controls[0], + ARRAY_SIZE(wm8400_dapm_inmixr_controls), + inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +/* AINRMUX */ +SND_SOC_DAPM_MUX_E("AIRNMUX", WM8400_INTDRIVBITS, WM8400_AINRMUX_PWR, 0, + &wm8400_dapm_ainrmux_controls, inmixer_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +/* Output Side */ +/* DACs */ +SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8400_POWER_MANAGEMENT_3, + WM8400_DACL_ENA_SHIFT, 0), +SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8400_POWER_MANAGEMENT_3, + WM8400_DACR_ENA_SHIFT, 0), + +/* LOMIX */ +SND_SOC_DAPM_MIXER_E("LOMIX", WM8400_POWER_MANAGEMENT_3, + WM8400_LOMIX_ENA_SHIFT, + 0, &wm8400_dapm_lomix_controls[0], + ARRAY_SIZE(wm8400_dapm_lomix_controls), + outmixer_event, SND_SOC_DAPM_PRE_REG), + +/* LONMIX */ +SND_SOC_DAPM_MIXER("LONMIX", WM8400_POWER_MANAGEMENT_3, WM8400_LON_ENA_SHIFT, + 0, &wm8400_dapm_lonmix_controls[0], + ARRAY_SIZE(wm8400_dapm_lonmix_controls)), + +/* LOPMIX */ +SND_SOC_DAPM_MIXER("LOPMIX", WM8400_POWER_MANAGEMENT_3, WM8400_LOP_ENA_SHIFT, + 0, &wm8400_dapm_lopmix_controls[0], + ARRAY_SIZE(wm8400_dapm_lopmix_controls)), + +/* OUT3MIX */ +SND_SOC_DAPM_MIXER("OUT3MIX", WM8400_POWER_MANAGEMENT_1, WM8400_OUT3_ENA_SHIFT, + 0, &wm8400_dapm_out3mix_controls[0], + ARRAY_SIZE(wm8400_dapm_out3mix_controls)), + +/* SPKMIX */ +SND_SOC_DAPM_MIXER_E("SPKMIX", WM8400_POWER_MANAGEMENT_1, WM8400_SPK_ENA_SHIFT, + 0, &wm8400_dapm_spkmix_controls[0], + ARRAY_SIZE(wm8400_dapm_spkmix_controls), outmixer_event, + SND_SOC_DAPM_PRE_REG), + +/* OUT4MIX */ +SND_SOC_DAPM_MIXER("OUT4MIX", WM8400_POWER_MANAGEMENT_1, WM8400_OUT4_ENA_SHIFT, + 0, &wm8400_dapm_out4mix_controls[0], + ARRAY_SIZE(wm8400_dapm_out4mix_controls)), + +/* ROPMIX */ +SND_SOC_DAPM_MIXER("ROPMIX", WM8400_POWER_MANAGEMENT_3, WM8400_ROP_ENA_SHIFT, + 0, &wm8400_dapm_ropmix_controls[0], + ARRAY_SIZE(wm8400_dapm_ropmix_controls)), + +/* RONMIX */ +SND_SOC_DAPM_MIXER("RONMIX", WM8400_POWER_MANAGEMENT_3, WM8400_RON_ENA_SHIFT, + 0, &wm8400_dapm_ronmix_controls[0], + ARRAY_SIZE(wm8400_dapm_ronmix_controls)), + +/* ROMIX */ +SND_SOC_DAPM_MIXER_E("ROMIX", WM8400_POWER_MANAGEMENT_3, + WM8400_ROMIX_ENA_SHIFT, + 0, &wm8400_dapm_romix_controls[0], + ARRAY_SIZE(wm8400_dapm_romix_controls), + outmixer_event, SND_SOC_DAPM_PRE_REG), + +/* LOUT PGA */ +SND_SOC_DAPM_PGA("LOUT PGA", WM8400_POWER_MANAGEMENT_1, WM8400_LOUT_ENA_SHIFT, + 0, NULL, 0), + +/* ROUT PGA */ +SND_SOC_DAPM_PGA("ROUT PGA", WM8400_POWER_MANAGEMENT_1, WM8400_ROUT_ENA_SHIFT, + 0, NULL, 0), + +/* LOPGA */ +SND_SOC_DAPM_PGA("LOPGA", WM8400_POWER_MANAGEMENT_3, WM8400_LOPGA_ENA_SHIFT, 0, + NULL, 0), + +/* ROPGA */ +SND_SOC_DAPM_PGA("ROPGA", WM8400_POWER_MANAGEMENT_3, WM8400_ROPGA_ENA_SHIFT, 0, + NULL, 0), + +/* MICBIAS */ +SND_SOC_DAPM_MICBIAS("MICBIAS", WM8400_POWER_MANAGEMENT_1, + WM8400_MIC1BIAS_ENA_SHIFT, 0), + +SND_SOC_DAPM_OUTPUT("LON"), +SND_SOC_DAPM_OUTPUT("LOP"), +SND_SOC_DAPM_OUTPUT("OUT3"), +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("SPKN"), +SND_SOC_DAPM_OUTPUT("SPKP"), +SND_SOC_DAPM_OUTPUT("ROUT"), +SND_SOC_DAPM_OUTPUT("OUT4"), +SND_SOC_DAPM_OUTPUT("ROP"), +SND_SOC_DAPM_OUTPUT("RON"), + +SND_SOC_DAPM_OUTPUT("Internal DAC Sink"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Make DACs turn on when playing even if not mixed into any outputs */ + {"Internal DAC Sink", NULL, "Left DAC"}, + {"Internal DAC Sink", NULL, "Right DAC"}, + + /* Make ADCs turn on when recording + * even if not mixed from any inputs */ + {"Left ADC", NULL, "Internal ADC Source"}, + {"Right ADC", NULL, "Internal ADC Source"}, + + /* Input Side */ + /* LIN12 PGA */ + {"LIN12 PGA", "LIN1 Switch", "LIN1"}, + {"LIN12 PGA", "LIN2 Switch", "LIN2"}, + /* LIN34 PGA */ + {"LIN34 PGA", "LIN3 Switch", "LIN3"}, + {"LIN34 PGA", "LIN4 Switch", "LIN4/RXN"}, + /* INMIXL */ + {"INMIXL", "Record Left Volume", "LOMIX"}, + {"INMIXL", "LIN2 Volume", "LIN2"}, + {"INMIXL", "LINPGA12 Switch", "LIN12 PGA"}, + {"INMIXL", "LINPGA34 Switch", "LIN34 PGA"}, + /* AILNMUX */ + {"AILNMUX", "INMIXL Mix", "INMIXL"}, + {"AILNMUX", "DIFFINL Mix", "LIN12 PGA"}, + {"AILNMUX", "DIFFINL Mix", "LIN34 PGA"}, + {"AILNMUX", "RXVOICE Mix", "LIN4/RXN"}, + {"AILNMUX", "RXVOICE Mix", "RIN4/RXP"}, + /* ADC */ + {"Left ADC", NULL, "AILNMUX"}, + + /* RIN12 PGA */ + {"RIN12 PGA", "RIN1 Switch", "RIN1"}, + {"RIN12 PGA", "RIN2 Switch", "RIN2"}, + /* RIN34 PGA */ + {"RIN34 PGA", "RIN3 Switch", "RIN3"}, + {"RIN34 PGA", "RIN4 Switch", "RIN4/RXP"}, + /* INMIXL */ + {"INMIXR", "Record Right Volume", "ROMIX"}, + {"INMIXR", "RIN2 Volume", "RIN2"}, + {"INMIXR", "RINPGA12 Switch", "RIN12 PGA"}, + {"INMIXR", "RINPGA34 Switch", "RIN34 PGA"}, + /* AIRNMUX */ + {"AIRNMUX", "INMIXR Mix", "INMIXR"}, + {"AIRNMUX", "DIFFINR Mix", "RIN12 PGA"}, + {"AIRNMUX", "DIFFINR Mix", "RIN34 PGA"}, + {"AIRNMUX", "RXVOICE Mix", "LIN4/RXN"}, + {"AIRNMUX", "RXVOICE Mix", "RIN4/RXP"}, + /* ADC */ + {"Right ADC", NULL, "AIRNMUX"}, + + /* LOMIX */ + {"LOMIX", "LOMIX RIN3 Bypass Switch", "RIN3"}, + {"LOMIX", "LOMIX LIN3 Bypass Switch", "LIN3"}, + {"LOMIX", "LOMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, + {"LOMIX", "LOMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, + {"LOMIX", "LOMIX Right ADC Bypass Switch", "AIRNMUX"}, + {"LOMIX", "LOMIX Left ADC Bypass Switch", "AILNMUX"}, + {"LOMIX", "LOMIX Left DAC Switch", "Left DAC"}, + + /* ROMIX */ + {"ROMIX", "ROMIX RIN3 Bypass Switch", "RIN3"}, + {"ROMIX", "ROMIX LIN3 Bypass Switch", "LIN3"}, + {"ROMIX", "ROMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, + {"ROMIX", "ROMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, + {"ROMIX", "ROMIX Right ADC Bypass Switch", "AIRNMUX"}, + {"ROMIX", "ROMIX Left ADC Bypass Switch", "AILNMUX"}, + {"ROMIX", "ROMIX Right DAC Switch", "Right DAC"}, + + /* SPKMIX */ + {"SPKMIX", "SPKMIX LIN2 Bypass Switch", "LIN2"}, + {"SPKMIX", "SPKMIX RIN2 Bypass Switch", "RIN2"}, + {"SPKMIX", "SPKMIX LADC Bypass Switch", "AILNMUX"}, + {"SPKMIX", "SPKMIX RADC Bypass Switch", "AIRNMUX"}, + {"SPKMIX", "SPKMIX Left Mixer PGA Switch", "LOPGA"}, + {"SPKMIX", "SPKMIX Right Mixer PGA Switch", "ROPGA"}, + {"SPKMIX", "SPKMIX Right DAC Switch", "Right DAC"}, + {"SPKMIX", "SPKMIX Left DAC Switch", "Right DAC"}, + + /* LONMIX */ + {"LONMIX", "LONMIX Left Mixer PGA Switch", "LOPGA"}, + {"LONMIX", "LONMIX Right Mixer PGA Switch", "ROPGA"}, + {"LONMIX", "LONMIX Inverted LOP Switch", "LOPMIX"}, + + /* LOPMIX */ + {"LOPMIX", "LOPMIX Right Mic Bypass Switch", "RIN12 PGA"}, + {"LOPMIX", "LOPMIX Left Mic Bypass Switch", "LIN12 PGA"}, + {"LOPMIX", "LOPMIX Left Mixer PGA Switch", "LOPGA"}, + + /* OUT3MIX */ + {"OUT3MIX", "OUT3MIX LIN4/RXP Bypass Switch", "LIN4/RXN"}, + {"OUT3MIX", "OUT3MIX Left Out PGA Switch", "LOPGA"}, + + /* OUT4MIX */ + {"OUT4MIX", "OUT4MIX Right Out PGA Switch", "ROPGA"}, + {"OUT4MIX", "OUT4MIX RIN4/RXP Bypass Switch", "RIN4/RXP"}, + + /* RONMIX */ + {"RONMIX", "RONMIX Right Mixer PGA Switch", "ROPGA"}, + {"RONMIX", "RONMIX Left Mixer PGA Switch", "LOPGA"}, + {"RONMIX", "RONMIX Inverted ROP Switch", "ROPMIX"}, + + /* ROPMIX */ + {"ROPMIX", "ROPMIX Left Mic Bypass Switch", "LIN12 PGA"}, + {"ROPMIX", "ROPMIX Right Mic Bypass Switch", "RIN12 PGA"}, + {"ROPMIX", "ROPMIX Right Mixer PGA Switch", "ROPGA"}, + + /* Out Mixer PGAs */ + {"LOPGA", NULL, "LOMIX"}, + {"ROPGA", NULL, "ROMIX"}, + + {"LOUT PGA", NULL, "LOMIX"}, + {"ROUT PGA", NULL, "ROMIX"}, + + /* Output Pins */ + {"LON", NULL, "LONMIX"}, + {"LOP", NULL, "LOPMIX"}, + {"OUT3", NULL, "OUT3MIX"}, + {"LOUT", NULL, "LOUT PGA"}, + {"SPKN", NULL, "SPKMIX"}, + {"ROUT", NULL, "ROUT PGA"}, + {"OUT4", NULL, "OUT4MIX"}, + {"ROP", NULL, "ROPMIX"}, + {"RON", NULL, "RONMIX"}, +}; + +static int wm8400_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8400_dapm_widgets, + ARRAY_SIZE(wm8400_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +/* + * Clock after FLL and dividers + */ +static int wm8400_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8400_priv *wm8400 = codec->private_data; + + wm8400->sysclk = freq; + return 0; +} + +struct fll_factors { + u16 n; + u16 k; + u16 outdiv; + u16 fratio; + u16 freq_ref; +}; + +#define FIXED_FLL_SIZE ((1 << 16) * 10) + +static int fll_factors(struct wm8400_priv *wm8400, struct fll_factors *factors, + unsigned int Fref, unsigned int Fout) +{ + u64 Kpart; + unsigned int K, Nmod, target; + + factors->outdiv = 2; + while (Fout * factors->outdiv < 90000000 || + Fout * factors->outdiv > 100000000) { + factors->outdiv *= 2; + if (factors->outdiv > 32) { + dev_err(wm8400->wm8400->dev, + "Unsupported FLL output frequency %dHz\n", + Fout); + return -EINVAL; + } + } + target = Fout * factors->outdiv; + factors->outdiv = factors->outdiv >> 2; + + if (Fref < 48000) + factors->freq_ref = 1; + else + factors->freq_ref = 0; + + if (Fref < 1000000) + factors->fratio = 9; + else + factors->fratio = 0; + + /* Ensure we have a fractional part */ + do { + if (Fref < 1000000) + factors->fratio--; + else + factors->fratio++; + + if (factors->fratio < 1 || factors->fratio > 8) { + dev_err(wm8400->wm8400->dev, + "Unable to calculate FRATIO\n"); + return -EINVAL; + } + + factors->n = target / (Fref * factors->fratio); + Nmod = target % (Fref * factors->fratio); + } while (Nmod == 0); + + /* Calculate fractional part - scale up so we can round. */ + Kpart = FIXED_FLL_SIZE * (long long)Nmod; + + do_div(Kpart, (Fref * factors->fratio)); + + K = Kpart & 0xFFFFFFFF; + + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + factors->k = K / 10; + + dev_dbg(wm8400->wm8400->dev, + "FLL: Fref=%d Fout=%d N=%x K=%x, FRATIO=%x OUTDIV=%x\n", + Fref, Fout, + factors->n, factors->k, factors->fratio, factors->outdiv); + + return 0; +} + +static int wm8400_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8400_priv *wm8400 = codec->private_data; + struct fll_factors factors; + int ret; + u16 reg; + + if (freq_in == wm8400->fll_in && freq_out == wm8400->fll_out) + return 0; + + if (freq_out != 0) { + ret = fll_factors(wm8400, &factors, freq_in, freq_out); + if (ret != 0) + return ret; + } + + wm8400->fll_out = freq_out; + wm8400->fll_in = freq_in; + + /* We *must* disable the FLL before any changes */ + reg = wm8400_read(codec, WM8400_POWER_MANAGEMENT_2); + reg &= ~WM8400_FLL_ENA; + wm8400_write(codec, WM8400_POWER_MANAGEMENT_2, reg); + + reg = wm8400_read(codec, WM8400_FLL_CONTROL_1); + reg &= ~WM8400_FLL_OSC_ENA; + wm8400_write(codec, WM8400_FLL_CONTROL_1, reg); + + if (freq_out == 0) + return 0; + + reg &= ~(WM8400_FLL_REF_FREQ | WM8400_FLL_FRATIO_MASK); + reg |= WM8400_FLL_FRAC | factors.fratio; + reg |= factors.freq_ref << WM8400_FLL_REF_FREQ_SHIFT; + wm8400_write(codec, WM8400_FLL_CONTROL_1, reg); + + wm8400_write(codec, WM8400_FLL_CONTROL_2, factors.k); + wm8400_write(codec, WM8400_FLL_CONTROL_3, factors.n); + + reg = wm8400_read(codec, WM8400_FLL_CONTROL_4); + reg &= WM8400_FLL_OUTDIV_MASK; + reg |= factors.outdiv; + wm8400_write(codec, WM8400_FLL_CONTROL_4, reg); + + return 0; +} + +/* + * Sets ADC and Voice DAC format. + */ +static int wm8400_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 audio1, audio3; + + audio1 = wm8400_read(codec, WM8400_AUDIO_INTERFACE_1); + audio3 = wm8400_read(codec, WM8400_AUDIO_INTERFACE_3); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + audio3 &= ~WM8400_AIF_MSTR1; + break; + case SND_SOC_DAIFMT_CBM_CFM: + audio3 |= WM8400_AIF_MSTR1; + break; + default: + return -EINVAL; + } + + audio1 &= ~WM8400_AIF_FMT_MASK; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + audio1 |= WM8400_AIF_FMT_I2S; + audio1 &= ~WM8400_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_RIGHT_J: + audio1 |= WM8400_AIF_FMT_RIGHTJ; + audio1 &= ~WM8400_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_LEFT_J: + audio1 |= WM8400_AIF_FMT_LEFTJ; + audio1 &= ~WM8400_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_A: + audio1 |= WM8400_AIF_FMT_DSP; + audio1 &= ~WM8400_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_B: + audio1 |= WM8400_AIF_FMT_DSP | WM8400_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + + wm8400_write(codec, WM8400_AUDIO_INTERFACE_1, audio1); + wm8400_write(codec, WM8400_AUDIO_INTERFACE_3, audio3); + return 0; +} + +static int wm8400_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM8400_MCLK_DIV: + reg = wm8400_read(codec, WM8400_CLOCKING_2) & + ~WM8400_MCLK_DIV_MASK; + wm8400_write(codec, WM8400_CLOCKING_2, reg | div); + break; + case WM8400_DACCLK_DIV: + reg = wm8400_read(codec, WM8400_CLOCKING_2) & + ~WM8400_DAC_CLKDIV_MASK; + wm8400_write(codec, WM8400_CLOCKING_2, reg | div); + break; + case WM8400_ADCCLK_DIV: + reg = wm8400_read(codec, WM8400_CLOCKING_2) & + ~WM8400_ADC_CLKDIV_MASK; + wm8400_write(codec, WM8400_CLOCKING_2, reg | div); + break; + case WM8400_BCLK_DIV: + reg = wm8400_read(codec, WM8400_CLOCKING_1) & + ~WM8400_BCLK_DIV_MASK; + wm8400_write(codec, WM8400_CLOCKING_1, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8400_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + u16 audio1 = wm8400_read(codec, WM8400_AUDIO_INTERFACE_1); + + audio1 &= ~WM8400_AIF_WL_MASK; + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + audio1 |= WM8400_AIF_WL_20BITS; + break; + case SNDRV_PCM_FORMAT_S24_LE: + audio1 |= WM8400_AIF_WL_24BITS; + break; + case SNDRV_PCM_FORMAT_S32_LE: + audio1 |= WM8400_AIF_WL_32BITS; + break; + } + + wm8400_write(codec, WM8400_AUDIO_INTERFACE_1, audio1); + return 0; +} + +static int wm8400_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 val = wm8400_read(codec, WM8400_DAC_CTRL) & ~WM8400_DAC_MUTE; + + if (mute) + wm8400_write(codec, WM8400_DAC_CTRL, val | WM8400_DAC_MUTE); + else + wm8400_write(codec, WM8400_DAC_CTRL, val); + + return 0; +} + +/* TODO: set bias for best performance at standby */ +static int wm8400_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct wm8400_priv *wm8400 = codec->private_data; + u16 val; + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* VMID=2*50k */ + val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1) & + ~WM8400_VMID_MODE_MASK; + wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val | 0x2); + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(power), + &power[0]); + if (ret != 0) { + dev_err(wm8400->wm8400->dev, + "Failed to enable regulators: %d\n", + ret); + return ret; + } + + wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, + WM8400_CODEC_ENA | WM8400_SYSCLK_ENA); + + /* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */ + wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST | + WM8400_BUFDCOPEN | WM8400_POBCTRL); + + msleep(50); + + /* Enable VREF & VMID at 2x50k */ + val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1); + val |= 0x2 | WM8400_VREF_ENA; + wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val); + + /* Enable BUFIOEN */ + wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST | + WM8400_BUFDCOPEN | WM8400_POBCTRL | + WM8400_BUFIOEN); + + /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ + wm8400_write(codec, WM8400_ANTIPOP2, WM8400_BUFIOEN); + } + + /* VMID=2*300k */ + val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1) & + ~WM8400_VMID_MODE_MASK; + wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val | 0x4); + break; + + case SND_SOC_BIAS_OFF: + /* Enable POBCTRL and SOFT_ST */ + wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST | + WM8400_POBCTRL | WM8400_BUFIOEN); + + /* Enable POBCTRL, SOFT_ST and BUFDCOPEN */ + wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST | + WM8400_BUFDCOPEN | WM8400_POBCTRL | + WM8400_BUFIOEN); + + /* mute DAC */ + val = wm8400_read(codec, WM8400_DAC_CTRL); + wm8400_write(codec, WM8400_DAC_CTRL, val | WM8400_DAC_MUTE); + + /* Enable any disabled outputs */ + val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1); + val |= WM8400_SPK_ENA | WM8400_OUT3_ENA | + WM8400_OUT4_ENA | WM8400_LOUT_ENA | + WM8400_ROUT_ENA; + wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val); + + /* Disable VMID */ + val &= ~WM8400_VMID_MODE_MASK; + wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val); + + msleep(300); + + /* Enable all output discharge bits */ + wm8400_write(codec, WM8400_ANTIPOP1, WM8400_DIS_LLINE | + WM8400_DIS_RLINE | WM8400_DIS_OUT3 | + WM8400_DIS_OUT4 | WM8400_DIS_LOUT | + WM8400_DIS_ROUT); + + /* Disable VREF */ + val &= ~WM8400_VREF_ENA; + wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val); + + /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ + wm8400_write(codec, WM8400_ANTIPOP2, 0x0); + + ret = regulator_bulk_disable(ARRAY_SIZE(power), + &power[0]); + if (ret != 0) + return ret; + + break; + } + + codec->bias_level = level; + return 0; +} + +#define WM8400_RATES SNDRV_PCM_RATE_8000_96000 + +#define WM8400_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops wm8400_dai_ops = { + .hw_params = wm8400_hw_params, + .digital_mute = wm8400_mute, + .set_fmt = wm8400_set_dai_fmt, + .set_clkdiv = wm8400_set_dai_clkdiv, + .set_sysclk = wm8400_set_dai_sysclk, + .set_pll = wm8400_set_dai_pll, +}; + +/* + * The WM8400 supports 2 different and mutually exclusive DAI + * configurations. + * + * 1. ADC/DAC on Primary Interface + * 2. ADC on Primary Interface/DAC on secondary + */ +struct snd_soc_dai wm8400_dai = { +/* ADC/DAC on primary */ + .name = "WM8400 ADC/DAC Primary", + .id = 1, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8400_RATES, + .formats = WM8400_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8400_RATES, + .formats = WM8400_FORMATS, + }, + .ops = &wm8400_dai_ops, +}; +EXPORT_SYMBOL_GPL(wm8400_dai); + +static int wm8400_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + wm8400_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm8400_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + wm8400_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} + +static struct snd_soc_codec *wm8400_codec; + +static int wm8400_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret; + + if (!wm8400_codec) { + dev_err(&pdev->dev, "wm8400 not yet discovered\n"); + return -ENODEV; + } + codec = wm8400_codec; + + socdev->card->codec = codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&pdev->dev, "failed to create pcms\n"); + goto pcm_err; + } + + wm8400_add_controls(codec); + wm8400_add_widgets(codec); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + return ret; +} + +/* power down chip */ +static int wm8400_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8400 = { + .probe = wm8400_probe, + .remove = wm8400_remove, + .suspend = wm8400_suspend, + .resume = wm8400_resume, +}; + +static void wm8400_probe_deferred(struct work_struct *work) +{ + struct wm8400_priv *priv = container_of(work, struct wm8400_priv, + work); + struct snd_soc_codec *codec = &priv->codec; + int ret; + + /* charge output caps */ + wm8400_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* We're done, tell the subsystem. */ + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(priv->wm8400->dev, + "Failed to register codec: %d\n", ret); + goto err; + } + + ret = snd_soc_register_dai(&wm8400_dai); + if (ret != 0) { + dev_err(priv->wm8400->dev, + "Failed to register DAI: %d\n", ret); + goto err_codec; + } + + return; + +err_codec: + snd_soc_unregister_codec(codec); +err: + wm8400_set_bias_level(codec, SND_SOC_BIAS_OFF); +} + +static int wm8400_codec_probe(struct platform_device *dev) +{ + struct wm8400_priv *priv; + int ret; + u16 reg; + struct snd_soc_codec *codec; + + priv = kzalloc(sizeof(struct wm8400_priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + codec = &priv->codec; + codec->private_data = priv; + codec->control_data = dev->dev.driver_data; + priv->wm8400 = dev->dev.driver_data; + + ret = regulator_bulk_get(priv->wm8400->dev, + ARRAY_SIZE(power), &power[0]); + if (ret != 0) { + dev_err(&dev->dev, "Failed to get regulators: %d\n", ret); + goto err; + } + + codec->dev = &dev->dev; + wm8400_dai.dev = &dev->dev; + + codec->name = "WM8400"; + codec->owner = THIS_MODULE; + codec->read = wm8400_read; + codec->write = wm8400_write; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8400_set_bias_level; + codec->dai = &wm8400_dai; + codec->num_dai = 1; + codec->reg_cache_size = WM8400_REGISTER_COUNT; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + INIT_WORK(&priv->work, wm8400_probe_deferred); + + wm8400_codec_reset(codec); + + reg = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1); + wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, reg | WM8400_CODEC_ENA); + + /* Latch volume update bits */ + reg = wm8400_read(codec, WM8400_LEFT_LINE_INPUT_1_2_VOLUME); + wm8400_write(codec, WM8400_LEFT_LINE_INPUT_1_2_VOLUME, + reg & WM8400_IPVU); + reg = wm8400_read(codec, WM8400_RIGHT_LINE_INPUT_1_2_VOLUME); + wm8400_write(codec, WM8400_RIGHT_LINE_INPUT_1_2_VOLUME, + reg & WM8400_IPVU); + + wm8400_write(codec, WM8400_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8)); + wm8400_write(codec, WM8400_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8)); + + wm8400_codec = codec; + + if (!schedule_work(&priv->work)) { + ret = -EINVAL; + goto err_regulator; + } + + return 0; + +err_regulator: + wm8400_codec = NULL; + regulator_bulk_free(ARRAY_SIZE(power), power); +err: + kfree(priv); + return ret; +} + +static int __exit wm8400_codec_remove(struct platform_device *dev) +{ + struct wm8400_priv *priv = wm8400_codec->private_data; + u16 reg; + + snd_soc_unregister_dai(&wm8400_dai); + snd_soc_unregister_codec(wm8400_codec); + + reg = wm8400_read(wm8400_codec, WM8400_POWER_MANAGEMENT_1); + wm8400_write(wm8400_codec, WM8400_POWER_MANAGEMENT_1, + reg & (~WM8400_CODEC_ENA)); + + regulator_bulk_free(ARRAY_SIZE(power), power); + kfree(priv); + + wm8400_codec = NULL; + + return 0; +} + +static struct platform_driver wm8400_codec_driver = { + .driver = { + .name = "wm8400-codec", + .owner = THIS_MODULE, + }, + .probe = wm8400_codec_probe, + .remove = __exit_p(wm8400_codec_remove), +}; + +static int __init wm8400_codec_init(void) +{ + return platform_driver_register(&wm8400_codec_driver); +} +module_init(wm8400_codec_init); + +static void __exit wm8400_codec_exit(void) +{ + platform_driver_unregister(&wm8400_codec_driver); +} +module_exit(wm8400_codec_exit); + +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8400); + +MODULE_DESCRIPTION("ASoC WM8400 driver"); +MODULE_AUTHOR("Mark Brown"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm8400-codec"); diff --git a/sound/soc/codecs/wm8400.h b/sound/soc/codecs/wm8400.h new file mode 100644 index 0000000..79c5934 --- /dev/null +++ b/sound/soc/codecs/wm8400.h @@ -0,0 +1,62 @@ +/* + * wm8400.h -- audio driver for WM8400 + * + * Copyright 2008 Wolfson Microelectronics PLC. + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _WM8400_CODEC_H +#define _WM8400_CODEC_H + +#define WM8400_MCLK_DIV 0 +#define WM8400_DACCLK_DIV 1 +#define WM8400_ADCCLK_DIV 2 +#define WM8400_BCLK_DIV 3 + +#define WM8400_MCLK_DIV_1 0x400 +#define WM8400_MCLK_DIV_2 0x800 + +#define WM8400_DAC_CLKDIV_1 0x00 +#define WM8400_DAC_CLKDIV_1_5 0x04 +#define WM8400_DAC_CLKDIV_2 0x08 +#define WM8400_DAC_CLKDIV_3 0x0c +#define WM8400_DAC_CLKDIV_4 0x10 +#define WM8400_DAC_CLKDIV_5_5 0x14 +#define WM8400_DAC_CLKDIV_6 0x18 + +#define WM8400_ADC_CLKDIV_1 0x00 +#define WM8400_ADC_CLKDIV_1_5 0x20 +#define WM8400_ADC_CLKDIV_2 0x40 +#define WM8400_ADC_CLKDIV_3 0x60 +#define WM8400_ADC_CLKDIV_4 0x80 +#define WM8400_ADC_CLKDIV_5_5 0xa0 +#define WM8400_ADC_CLKDIV_6 0xc0 + + +#define WM8400_BCLK_DIV_1 (0x0 << 1) +#define WM8400_BCLK_DIV_1_5 (0x1 << 1) +#define WM8400_BCLK_DIV_2 (0x2 << 1) +#define WM8400_BCLK_DIV_3 (0x3 << 1) +#define WM8400_BCLK_DIV_4 (0x4 << 1) +#define WM8400_BCLK_DIV_5_5 (0x5 << 1) +#define WM8400_BCLK_DIV_6 (0x6 << 1) +#define WM8400_BCLK_DIV_8 (0x7 << 1) +#define WM8400_BCLK_DIV_11 (0x8 << 1) +#define WM8400_BCLK_DIV_12 (0x9 << 1) +#define WM8400_BCLK_DIV_16 (0xA << 1) +#define WM8400_BCLK_DIV_22 (0xB << 1) +#define WM8400_BCLK_DIV_24 (0xC << 1) +#define WM8400_BCLK_DIV_32 (0xD << 1) +#define WM8400_BCLK_DIV_44 (0xE << 1) +#define WM8400_BCLK_DIV_48 (0xF << 1) + +extern struct snd_soc_dai wm8400_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8400; + +#endif diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c index 40f8238..6a4cea0 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -171,22 +171,6 @@ SOC_SINGLE("Capture Boost(+20dB)", WM8510_ADCBOOST, 8, 1, 0), SOC_SINGLE("Mono Playback Switch", WM8510_MONOMIX, 6, 1, 1), }; -/* add non dapm controls */ -static int wm8510_add_controls(struct snd_soc_codec *codec) -{ - int err, i; - - for (i = 0; i < ARRAY_SIZE(wm8510_snd_controls); i++) { - err = snd_ctl_add(codec->card, - snd_soc_cnew(&wm8510_snd_controls[i], codec, - NULL)); - if (err < 0) - return err; - } - - return 0; -} - /* Speaker Output Mixer */ static const struct snd_kcontrol_new wm8510_speaker_mixer_controls[] = { SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_SPKMIX, 1, 1, 0), @@ -352,7 +336,7 @@ static int wm8510_set_dai_pll(struct snd_soc_dai *codec_dai, return 0; } - pll_factors(freq_out*8, freq_in); + pll_factors(freq_out*4, freq_in); wm8510_write(codec, WM8510_PLLN, (pll_div.pre_div << 4) | pll_div.n); wm8510_write(codec, WM8510_PLLK1, pll_div.k >> 18); @@ -383,7 +367,7 @@ static int wm8510_set_dai_clkdiv(struct snd_soc_dai *codec_dai, wm8510_write(codec, WM8510_GPIO, reg | div); break; case WM8510_MCLKDIV: - reg = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1f; + reg = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x11f; wm8510_write(codec, WM8510_CLOCK, reg | div); break; case WM8510_ADCCLK: @@ -468,7 +452,7 @@ static int wm8510_pcm_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; u16 iface = wm8510_read_reg_cache(codec, WM8510_IFACE) & 0x19f; u16 adn = wm8510_read_reg_cache(codec, WM8510_ADD) & 0x1f1; @@ -570,6 +554,14 @@ static int wm8510_set_bias_level(struct snd_soc_codec *codec, #define WM8510_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static struct snd_soc_dai_ops wm8510_dai_ops = { + .hw_params = wm8510_pcm_hw_params, + .digital_mute = wm8510_mute, + .set_fmt = wm8510_set_dai_fmt, + .set_clkdiv = wm8510_set_dai_clkdiv, + .set_pll = wm8510_set_dai_pll, +}; + struct snd_soc_dai wm8510_dai = { .name = "WM8510 HiFi", .playback = { @@ -584,20 +576,14 @@ struct snd_soc_dai wm8510_dai = { .channels_max = 2, .rates = WM8510_RATES, .formats = WM8510_FORMATS,}, - .ops = { - .hw_params = wm8510_pcm_hw_params, - .digital_mute = wm8510_mute, - .set_fmt = wm8510_set_dai_fmt, - .set_clkdiv = wm8510_set_dai_clkdiv, - .set_pll = wm8510_set_dai_pll, - }, + .ops = &wm8510_dai_ops, }; EXPORT_SYMBOL_GPL(wm8510_dai); static int wm8510_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; wm8510_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; @@ -606,7 +592,7 @@ static int wm8510_suspend(struct platform_device *pdev, pm_message_t state) static int wm8510_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int i; u8 data[2]; u16 *cache = codec->reg_cache; @@ -628,7 +614,7 @@ static int wm8510_resume(struct platform_device *pdev) */ static int wm8510_init(struct snd_soc_device *socdev) { - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int ret = 0; codec->name = "WM8510"; @@ -656,7 +642,8 @@ static int wm8510_init(struct snd_soc_device *socdev) /* power on device */ codec->bias_level = SND_SOC_BIAS_OFF; wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - wm8510_add_controls(codec); + snd_soc_add_controls(codec, wm8510_snd_controls, + ARRAY_SIZE(wm8510_snd_controls)); wm8510_add_widgets(codec); ret = snd_soc_init_card(socdev); if (ret < 0) { @@ -685,7 +672,7 @@ static int wm8510_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct snd_soc_device *socdev = wm8510_socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int ret; i2c_set_clientdata(i2c, codec); @@ -766,7 +753,7 @@ err_driver: static int __devinit wm8510_spi_probe(struct spi_device *spi) { struct snd_soc_device *socdev = wm8510_socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int ret; codec->control_data = spi; @@ -832,7 +819,7 @@ static int wm8510_probe(struct platform_device *pdev) if (codec == NULL) return -ENOMEM; - socdev->codec = codec; + socdev->card->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); @@ -862,7 +849,7 @@ static int wm8510_probe(struct platform_device *pdev) static int wm8510_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; if (codec->control_data) wm8510_set_bias_level(codec, SND_SOC_BIAS_OFF); diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index d004e58..442ea6f 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -1,7 +1,7 @@ /* * wm8580.c -- WM8580 ALSA Soc Audio driver * - * Copyright 2008 Wolfson Microelectronics PLC. + * Copyright 2008, 2009 Wolfson Microelectronics PLC. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -35,19 +35,6 @@ #include "wm8580.h" -#define WM8580_VERSION "0.1" - -struct pll_state { - unsigned int in; - unsigned int out; -}; - -/* codec private data */ -struct wm8580_priv { - struct pll_state a; - struct pll_state b; -}; - /* WM8580 register space */ #define WM8580_PLLA1 0x00 #define WM8580_PLLA2 0x01 @@ -102,6 +89,8 @@ struct wm8580_priv { #define WM8580_READBACK 0x34 #define WM8580_RESET 0x35 +#define WM8580_MAX_REGISTER 0x35 + /* PLLB4 (register 7h) */ #define WM8580_PLLB4_MCLKOUTSRC_MASK 0x60 #define WM8580_PLLB4_MCLKOUTSRC_PLLA 0x20 @@ -193,6 +182,20 @@ static const u16 wm8580_reg[] = { 0x0000, 0x0000 /*R53*/ }; +struct pll_state { + unsigned int in; + unsigned int out; +}; + +/* codec private data */ +struct wm8580_priv { + struct snd_soc_codec codec; + u16 reg_cache[WM8580_MAX_REGISTER + 1]; + struct pll_state a; + struct pll_state b; +}; + + /* * read wm8580 register cache */ @@ -200,7 +203,7 @@ static inline unsigned int wm8580_read_reg_cache(struct snd_soc_codec *codec, unsigned int reg) { u16 *cache = codec->reg_cache; - BUG_ON(reg > ARRAY_SIZE(wm8580_reg)); + BUG_ON(reg >= ARRAY_SIZE(wm8580_reg)); return cache[reg]; } @@ -223,7 +226,7 @@ static int wm8580_write(struct snd_soc_codec *codec, unsigned int reg, { u8 data[2]; - BUG_ON(reg > ARRAY_SIZE(wm8580_reg)); + BUG_ON(reg >= ARRAY_SIZE(wm8580_reg)); /* Registers are 9 bits wide */ value &= 0x1ff; @@ -330,20 +333,6 @@ SOC_DOUBLE("ADC Mute Switch", WM8580_ADC_CONTROL1, 0, 1, 1, 0), SOC_SINGLE("ADC High-Pass Filter Switch", WM8580_ADC_CONTROL1, 4, 1, 0), }; -/* Add non-DAPM controls */ -static int wm8580_add_controls(struct snd_soc_codec *codec) -{ - int err, i; - - for (i = 0; i < ARRAY_SIZE(wm8580_snd_controls); i++) { - err = snd_ctl_add(codec->card, - snd_soc_cnew(&wm8580_snd_controls[i], - codec, NULL)); - if (err < 0) - return err; - } - return 0; -} static const struct snd_soc_dapm_widget wm8580_dapm_widgets[] = { SND_SOC_DAPM_DAC("DAC1", "Playback", WM8580_PWRDN1, 2, 1), SND_SOC_DAPM_DAC("DAC2", "Playback", WM8580_PWRDN1, 3, 1), @@ -553,7 +542,7 @@ static int wm8580_paif_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; u16 paifb = wm8580_read(codec, WM8580_PAIF3 + dai->id); paifb &= ~WM8580_AIF_LENGTH_MASK; @@ -771,8 +760,22 @@ static int wm8580_set_bias_level(struct snd_soc_codec *codec, switch (level) { case SND_SOC_BIAS_ON: case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Power up and get individual control of the DACs */ + reg = wm8580_read(codec, WM8580_PWRDN1); + reg &= ~(WM8580_PWRDN1_PWDN | WM8580_PWRDN1_ALLDACPD); + wm8580_write(codec, WM8580_PWRDN1, reg); + + /* Make VMID high impedence */ + reg = wm8580_read(codec, WM8580_ADC_CONTROL1); + reg &= ~0x100; + wm8580_write(codec, WM8580_ADC_CONTROL1, reg); + } break; + case SND_SOC_BIAS_OFF: reg = wm8580_read(codec, WM8580_PWRDN1); wm8580_write(codec, WM8580_PWRDN1, reg | WM8580_PWRDN1_PWDN); @@ -785,6 +788,21 @@ static int wm8580_set_bias_level(struct snd_soc_codec *codec, #define WM8580_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static struct snd_soc_dai_ops wm8580_dai_ops_playback = { + .hw_params = wm8580_paif_hw_params, + .set_fmt = wm8580_set_paif_dai_fmt, + .set_clkdiv = wm8580_set_dai_clkdiv, + .set_pll = wm8580_set_dai_pll, + .digital_mute = wm8580_digital_mute, +}; + +static struct snd_soc_dai_ops wm8580_dai_ops_capture = { + .hw_params = wm8580_paif_hw_params, + .set_fmt = wm8580_set_paif_dai_fmt, + .set_clkdiv = wm8580_set_dai_clkdiv, + .set_pll = wm8580_set_dai_pll, +}; + struct snd_soc_dai wm8580_dai[] = { { .name = "WM8580 PAIFRX", @@ -796,13 +814,7 @@ struct snd_soc_dai wm8580_dai[] = { .rates = SNDRV_PCM_RATE_8000_192000, .formats = WM8580_FORMATS, }, - .ops = { - .hw_params = wm8580_paif_hw_params, - .set_fmt = wm8580_set_paif_dai_fmt, - .set_clkdiv = wm8580_set_dai_clkdiv, - .set_pll = wm8580_set_dai_pll, - .digital_mute = wm8580_digital_mute, - }, + .ops = &wm8580_dai_ops_playback, }, { .name = "WM8580 PAIFTX", @@ -814,109 +826,168 @@ struct snd_soc_dai wm8580_dai[] = { .rates = SNDRV_PCM_RATE_8000_192000, .formats = WM8580_FORMATS, }, - .ops = { - .hw_params = wm8580_paif_hw_params, - .set_fmt = wm8580_set_paif_dai_fmt, - .set_clkdiv = wm8580_set_dai_clkdiv, - .set_pll = wm8580_set_dai_pll, - }, + .ops = &wm8580_dai_ops_capture, }, }; EXPORT_SYMBOL_GPL(wm8580_dai); -/* - * initialise the WM8580 driver - * register the mixer and dsp interfaces with the kernel - */ -static int wm8580_init(struct snd_soc_device *socdev) +static struct snd_soc_codec *wm8580_codec; + +static int wm8580_probe(struct platform_device *pdev) { - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; int ret = 0; - codec->name = "WM8580"; - codec->owner = THIS_MODULE; - codec->read = wm8580_read_reg_cache; - codec->write = wm8580_write; - codec->set_bias_level = wm8580_set_bias_level; - codec->dai = wm8580_dai; - codec->num_dai = ARRAY_SIZE(wm8580_dai); - codec->reg_cache_size = ARRAY_SIZE(wm8580_reg); - codec->reg_cache = kmemdup(wm8580_reg, sizeof(wm8580_reg), - GFP_KERNEL); - - if (codec->reg_cache == NULL) - return -ENOMEM; - - /* Get the codec into a known state */ - wm8580_write(codec, WM8580_RESET, 0); - - /* Power up and get individual control of the DACs */ - wm8580_write(codec, WM8580_PWRDN1, wm8580_read(codec, WM8580_PWRDN1) & - ~(WM8580_PWRDN1_PWDN | WM8580_PWRDN1_ALLDACPD)); + if (wm8580_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } - /* Make VMID high impedence */ - wm8580_write(codec, WM8580_ADC_CONTROL1, - wm8580_read(codec, WM8580_ADC_CONTROL1) & ~0x100); + socdev->card->codec = wm8580_codec; + codec = wm8580_codec; /* register pcms */ - ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, - SNDRV_DEFAULT_STR1); + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { - printk(KERN_ERR "wm8580: failed to create pcms\n"); + dev_err(codec->dev, "failed to create pcms: %d\n", ret); goto pcm_err; } - wm8580_add_controls(codec); + snd_soc_add_controls(codec, wm8580_snd_controls, + ARRAY_SIZE(wm8580_snd_controls)); wm8580_add_widgets(codec); - ret = snd_soc_init_card(socdev); if (ret < 0) { - printk(KERN_ERR "wm8580: failed to register card\n"); + dev_err(codec->dev, "failed to register card: %d\n", ret); goto card_err; } + return ret; card_err: snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); pcm_err: - kfree(codec->reg_cache); return ret; } -/* If the i2c layer weren't so broken, we could pass this kind of data - around */ -static struct snd_soc_device *wm8580_socdev; +/* power down chip */ +static int wm8580_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); -#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + return 0; +} -/* - * WM8580 2 wire address is determined by GPIO5 - * state during powerup. - * low = 0x1a - * high = 0x1b - */ +struct snd_soc_codec_device soc_codec_dev_wm8580 = { + .probe = wm8580_probe, + .remove = wm8580_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8580); + +static int wm8580_register(struct wm8580_priv *wm8580) +{ + int ret, i; + struct snd_soc_codec *codec = &wm8580->codec; + + if (wm8580_codec) { + dev_err(codec->dev, "Another WM8580 is registered\n"); + ret = -EINVAL; + goto err; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + codec->private_data = wm8580; + codec->name = "WM8580"; + codec->owner = THIS_MODULE; + codec->read = wm8580_read_reg_cache; + codec->write = wm8580_write; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8580_set_bias_level; + codec->dai = wm8580_dai; + codec->num_dai = ARRAY_SIZE(wm8580_dai); + codec->reg_cache_size = ARRAY_SIZE(wm8580->reg_cache); + codec->reg_cache = &wm8580->reg_cache; + + memcpy(codec->reg_cache, wm8580_reg, sizeof(wm8580_reg)); + + /* Get the codec into a known state */ + ret = wm8580_write(codec, WM8580_RESET, 0); + if (ret != 0) { + dev_err(codec->dev, "Failed to reset codec: %d\n", ret); + goto err; + } + + for (i = 0; i < ARRAY_SIZE(wm8580_dai); i++) + wm8580_dai[i].dev = codec->dev; + + wm8580_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + wm8580_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + goto err; + } + + ret = snd_soc_register_dais(wm8580_dai, ARRAY_SIZE(wm8580_dai)); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + goto err_codec; + } + + return 0; + +err_codec: + snd_soc_unregister_codec(codec); +err: + kfree(wm8580); + return ret; +} + +static void wm8580_unregister(struct wm8580_priv *wm8580) +{ + wm8580_set_bias_level(&wm8580->codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dais(wm8580_dai, ARRAY_SIZE(wm8580_dai)); + snd_soc_unregister_codec(&wm8580->codec); + kfree(wm8580); + wm8580_codec = NULL; +} + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) static int wm8580_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { - struct snd_soc_device *socdev = wm8580_socdev; - struct snd_soc_codec *codec = socdev->codec; - int ret; + struct wm8580_priv *wm8580; + struct snd_soc_codec *codec; - i2c_set_clientdata(i2c, codec); + wm8580 = kzalloc(sizeof(struct wm8580_priv), GFP_KERNEL); + if (wm8580 == NULL) + return -ENOMEM; + + codec = &wm8580->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + + i2c_set_clientdata(i2c, wm8580); codec->control_data = i2c; - ret = wm8580_init(socdev); - if (ret < 0) - dev_err(&i2c->dev, "failed to initialise WM8580\n"); - return ret; + codec->dev = &i2c->dev; + + return wm8580_register(wm8580); } static int wm8580_i2c_remove(struct i2c_client *client) { - struct snd_soc_codec *codec = i2c_get_clientdata(client); - kfree(codec->reg_cache); + struct wm8580_priv *wm8580 = i2c_get_clientdata(client); + wm8580_unregister(wm8580); return 0; } @@ -928,129 +999,35 @@ MODULE_DEVICE_TABLE(i2c, wm8580_i2c_id); static struct i2c_driver wm8580_i2c_driver = { .driver = { - .name = "WM8580 I2C Codec", + .name = "wm8580", .owner = THIS_MODULE, }, .probe = wm8580_i2c_probe, .remove = wm8580_i2c_remove, .id_table = wm8580_i2c_id, }; +#endif -static int wm8580_add_i2c_device(struct platform_device *pdev, - const struct wm8580_setup_data *setup) +static int __init wm8580_modinit(void) { - struct i2c_board_info info; - struct i2c_adapter *adapter; - struct i2c_client *client; int ret; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) ret = i2c_add_driver(&wm8580_i2c_driver); if (ret != 0) { - dev_err(&pdev->dev, "can't add i2c driver\n"); - return ret; - } - - memset(&info, 0, sizeof(struct i2c_board_info)); - info.addr = setup->i2c_address; - strlcpy(info.type, "wm8580", I2C_NAME_SIZE); - - adapter = i2c_get_adapter(setup->i2c_bus); - if (!adapter) { - dev_err(&pdev->dev, "can't get i2c adapter %d\n", - setup->i2c_bus); - goto err_driver; - } - - client = i2c_new_device(adapter, &info); - i2c_put_adapter(adapter); - if (!client) { - dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", - (unsigned int)info.addr); - goto err_driver; + pr_err("Failed to register WM8580 I2C driver: %d\n", ret); } - - return 0; - -err_driver: - i2c_del_driver(&wm8580_i2c_driver); - return -ENODEV; -} #endif -static int wm8580_probe(struct platform_device *pdev) -{ - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct wm8580_setup_data *setup; - struct snd_soc_codec *codec; - struct wm8580_priv *wm8580; - int ret = 0; - - pr_info("WM8580 Audio Codec %s\n", WM8580_VERSION); - - setup = socdev->codec_data; - codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); - if (codec == NULL) - return -ENOMEM; - - wm8580 = kzalloc(sizeof(struct wm8580_priv), GFP_KERNEL); - if (wm8580 == NULL) { - kfree(codec); - return -ENOMEM; - } - - codec->private_data = wm8580; - socdev->codec = codec; - mutex_init(&codec->mutex); - INIT_LIST_HEAD(&codec->dapm_widgets); - INIT_LIST_HEAD(&codec->dapm_paths); - wm8580_socdev = socdev; - -#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) - if (setup->i2c_address) { - codec->hw_write = (hw_write_t)i2c_master_send; - ret = wm8580_add_i2c_device(pdev, setup); - } -#else - /* Add other interfaces here */ -#endif - return ret; -} - -/* power down chip */ -static int wm8580_remove(struct platform_device *pdev) -{ - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; - - if (codec->control_data) - wm8580_set_bias_level(codec, SND_SOC_BIAS_OFF); - snd_soc_free_pcms(socdev); - snd_soc_dapm_free(socdev); -#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) - i2c_unregister_device(codec->control_data); - i2c_del_driver(&wm8580_i2c_driver); -#endif - kfree(codec->private_data); - kfree(codec); - return 0; } - -struct snd_soc_codec_device soc_codec_dev_wm8580 = { - .probe = wm8580_probe, - .remove = wm8580_remove, -}; -EXPORT_SYMBOL_GPL(soc_codec_dev_wm8580); - -static int __init wm8580_modinit(void) -{ - return snd_soc_register_dais(wm8580_dai, ARRAY_SIZE(wm8580_dai)); -} module_init(wm8580_modinit); static void __exit wm8580_exit(void) { - snd_soc_unregister_dais(wm8580_dai, ARRAY_SIZE(wm8580_dai)); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&wm8580_i2c_driver); +#endif } module_exit(wm8580_exit); diff --git a/sound/soc/codecs/wm8580.h b/sound/soc/codecs/wm8580.h index 09e4422..0dfb5dd 100644 --- a/sound/soc/codecs/wm8580.h +++ b/sound/soc/codecs/wm8580.h @@ -28,11 +28,6 @@ #define WM8580_CLKSRC_OSC 4 #define WM8580_CLKSRC_NONE 5 -struct wm8580_setup_data { - int i2c_bus; - unsigned short i2c_address; -}; - #define WM8580_DAI_PAIFRX 0 #define WM8580_DAI_PAIFTX 1 diff --git a/sound/soc/codecs/wm8728.c b/sound/soc/codecs/wm8728.c index 80b1198..e7ff212 100644 --- a/sound/soc/codecs/wm8728.c +++ b/sound/soc/codecs/wm8728.c @@ -47,7 +47,7 @@ static inline unsigned int wm8728_read_reg_cache(struct snd_soc_codec *codec, unsigned int reg) { u16 *cache = codec->reg_cache; - BUG_ON(reg > ARRAY_SIZE(wm8728_reg_defaults)); + BUG_ON(reg >= ARRAY_SIZE(wm8728_reg_defaults)); return cache[reg]; } @@ -55,7 +55,7 @@ static inline void wm8728_write_reg_cache(struct snd_soc_codec *codec, u16 reg, unsigned int value) { u16 *cache = codec->reg_cache; - BUG_ON(reg > ARRAY_SIZE(wm8728_reg_defaults)); + BUG_ON(reg >= ARRAY_SIZE(wm8728_reg_defaults)); cache[reg] = value; } @@ -92,21 +92,6 @@ SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8728_DACLVOL, WM8728_DACRVOL, SOC_SINGLE("Deemphasis", WM8728_DACCTL, 1, 1, 0), }; -static int wm8728_add_controls(struct snd_soc_codec *codec) -{ - int err, i; - - for (i = 0; i < ARRAY_SIZE(wm8728_snd_controls); i++) { - err = snd_ctl_add(codec->card, - snd_soc_cnew(&wm8728_snd_controls[i], - codec, NULL)); - if (err < 0) - return err; - } - - return 0; -} - /* * DAPM controls. */ @@ -152,7 +137,7 @@ static int wm8728_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; u16 dac = wm8728_read_reg_cache(codec, WM8728_DACCTL); dac &= ~0x18; @@ -259,6 +244,12 @@ static int wm8728_set_bias_level(struct snd_soc_codec *codec, #define WM8728_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static struct snd_soc_dai_ops wm8728_dai_ops = { + .hw_params = wm8728_hw_params, + .digital_mute = wm8728_mute, + .set_fmt = wm8728_set_dai_fmt, +}; + struct snd_soc_dai wm8728_dai = { .name = "WM8728", .playback = { @@ -268,18 +259,14 @@ struct snd_soc_dai wm8728_dai = { .rates = WM8728_RATES, .formats = WM8728_FORMATS, }, - .ops = { - .hw_params = wm8728_hw_params, - .digital_mute = wm8728_mute, - .set_fmt = wm8728_set_dai_fmt, - } + .ops = &wm8728_dai_ops, }; EXPORT_SYMBOL_GPL(wm8728_dai); static int wm8728_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; wm8728_set_bias_level(codec, SND_SOC_BIAS_OFF); @@ -289,7 +276,7 @@ static int wm8728_suspend(struct platform_device *pdev, pm_message_t state) static int wm8728_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; wm8728_set_bias_level(codec, codec->suspend_bias_level); @@ -302,7 +289,7 @@ static int wm8728_resume(struct platform_device *pdev) */ static int wm8728_init(struct snd_soc_device *socdev) { - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int ret = 0; codec->name = "WM8728"; @@ -330,7 +317,8 @@ static int wm8728_init(struct snd_soc_device *socdev) /* power on device */ wm8728_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - wm8728_add_controls(codec); + snd_soc_add_controls(codec, wm8728_snd_controls, + ARRAY_SIZE(wm8728_snd_controls)); wm8728_add_widgets(codec); ret = snd_soc_init_card(socdev); if (ret < 0) { @@ -363,7 +351,7 @@ static int wm8728_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct snd_soc_device *socdev = wm8728_socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int ret; i2c_set_clientdata(i2c, codec); @@ -444,7 +432,7 @@ err_driver: static int __devinit wm8728_spi_probe(struct spi_device *spi) { struct snd_soc_device *socdev = wm8728_socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int ret; codec->control_data = spi; @@ -508,7 +496,7 @@ static int wm8728_probe(struct platform_device *pdev) if (codec == NULL) return -ENOMEM; - socdev->codec = codec; + socdev->card->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); @@ -541,7 +529,7 @@ static int wm8728_probe(struct platform_device *pdev) static int wm8728_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; if (codec->control_data) wm8728_set_bias_level(codec, SND_SOC_BIAS_OFF); diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index c444b9f..e043e3f 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -29,15 +29,20 @@ #include "wm8731.h" -#define WM8731_VERSION "0.13" - +static struct snd_soc_codec *wm8731_codec; struct snd_soc_codec_device soc_codec_dev_wm8731; /* codec private data */ struct wm8731_priv { + struct snd_soc_codec codec; + u16 reg_cache[WM8731_CACHEREGNUM]; unsigned int sysclk; }; +#ifdef CONFIG_SPI_MASTER +static int wm8731_spi_write(struct spi_device *spi, const char *data, int len); +#endif + /* * wm8731 register cache * We can't read the WM8731 register space when we are @@ -129,22 +134,6 @@ SOC_SINGLE("Store DC Offset Switch", WM8731_APDIGI, 4, 1, 0), SOC_ENUM("Playback De-emphasis", wm8731_enum[1]), }; -/* add non dapm controls */ -static int wm8731_add_controls(struct snd_soc_codec *codec) -{ - int err, i; - - for (i = 0; i < ARRAY_SIZE(wm8731_snd_controls); i++) { - err = snd_ctl_add(codec->card, - snd_soc_cnew(&wm8731_snd_controls[i], - codec, NULL)); - if (err < 0) - return err; - } - - return 0; -} - /* Output Mixer */ static const struct snd_kcontrol_new wm8731_output_mixer_controls[] = { SOC_DAPM_SINGLE("Line Bypass Switch", WM8731_APANA, 3, 1, 0), @@ -269,7 +258,7 @@ static int wm8731_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct wm8731_priv *wm8731 = codec->private_data; u16 iface = wm8731_read_reg_cache(codec, WM8731_IFACE) & 0xfff3; int i = get_coeff(wm8731->sysclk, params_rate(params)); @@ -299,7 +288,7 @@ static int wm8731_pcm_prepare(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; /* set active */ wm8731_write(codec, WM8731_ACTIVE, 0x0001); @@ -312,7 +301,7 @@ static void wm8731_shutdown(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; /* deactivate */ if (!codec->active) { @@ -414,21 +403,19 @@ static int wm8731_set_dai_fmt(struct snd_soc_dai *codec_dai, static int wm8731_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - u16 reg = wm8731_read_reg_cache(codec, WM8731_PWR) & 0xff7f; + u16 reg; switch (level) { case SND_SOC_BIAS_ON: - /* vref/mid, osc on, dac unmute */ - wm8731_write(codec, WM8731_PWR, reg); break; case SND_SOC_BIAS_PREPARE: break; case SND_SOC_BIAS_STANDBY: - /* everything off except vref/vmid, */ + /* Clear PWROFF, gate CLKOUT, everything else as-is */ + reg = wm8731_read_reg_cache(codec, WM8731_PWR) & 0xff7f; wm8731_write(codec, WM8731_PWR, reg | 0x0040); break; case SND_SOC_BIAS_OFF: - /* everything off, dac mute, inactive */ wm8731_write(codec, WM8731_ACTIVE, 0x0); wm8731_write(codec, WM8731_PWR, 0xffff); break; @@ -446,6 +433,15 @@ static int wm8731_set_bias_level(struct snd_soc_codec *codec, #define WM8731_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static struct snd_soc_dai_ops wm8731_dai_ops = { + .prepare = wm8731_pcm_prepare, + .hw_params = wm8731_hw_params, + .shutdown = wm8731_shutdown, + .digital_mute = wm8731_mute, + .set_sysclk = wm8731_set_dai_sysclk, + .set_fmt = wm8731_set_dai_fmt, +}; + struct snd_soc_dai wm8731_dai = { .name = "WM8731", .playback = { @@ -460,21 +456,14 @@ struct snd_soc_dai wm8731_dai = { .channels_max = 2, .rates = WM8731_RATES, .formats = WM8731_FORMATS,}, - .ops = { - .prepare = wm8731_pcm_prepare, - .hw_params = wm8731_hw_params, - .shutdown = wm8731_shutdown, - .digital_mute = wm8731_mute, - .set_sysclk = wm8731_set_dai_sysclk, - .set_fmt = wm8731_set_dai_fmt, - } + .ops = &wm8731_dai_ops, }; EXPORT_SYMBOL_GPL(wm8731_dai); static int wm8731_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; wm8731_write(codec, WM8731_ACTIVE, 0x0); wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF); @@ -484,7 +473,7 @@ static int wm8731_suspend(struct platform_device *pdev, pm_message_t state) static int wm8731_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int i; u8 data[2]; u16 *cache = codec->reg_cache; @@ -500,54 +489,33 @@ static int wm8731_resume(struct platform_device *pdev) return 0; } -/* - * initialise the WM8731 driver - * register the mixer and dsp interfaces with the kernel - */ -static int wm8731_init(struct snd_soc_device *socdev) +static int wm8731_probe(struct platform_device *pdev) { - struct snd_soc_codec *codec = socdev->codec; - int reg, ret = 0; + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; - codec->name = "WM8731"; - codec->owner = THIS_MODULE; - codec->read = wm8731_read_reg_cache; - codec->write = wm8731_write; - codec->set_bias_level = wm8731_set_bias_level; - codec->dai = &wm8731_dai; - codec->num_dai = 1; - codec->reg_cache_size = ARRAY_SIZE(wm8731_reg); - codec->reg_cache = kmemdup(wm8731_reg, sizeof(wm8731_reg), GFP_KERNEL); - if (codec->reg_cache == NULL) - return -ENOMEM; + if (wm8731_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } - wm8731_reset(codec); + socdev->card->codec = wm8731_codec; + codec = wm8731_codec; /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { - printk(KERN_ERR "wm8731: failed to create pcms\n"); + dev_err(codec->dev, "failed to create pcms: %d\n", ret); goto pcm_err; } - /* power on device */ - wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - /* set the update bits */ - reg = wm8731_read_reg_cache(codec, WM8731_LOUT1V); - wm8731_write(codec, WM8731_LOUT1V, reg & ~0x0100); - reg = wm8731_read_reg_cache(codec, WM8731_ROUT1V); - wm8731_write(codec, WM8731_ROUT1V, reg & ~0x0100); - reg = wm8731_read_reg_cache(codec, WM8731_LINVOL); - wm8731_write(codec, WM8731_LINVOL, reg & ~0x0100); - reg = wm8731_read_reg_cache(codec, WM8731_RINVOL); - wm8731_write(codec, WM8731_RINVOL, reg & ~0x0100); - - wm8731_add_controls(codec); + snd_soc_add_controls(codec, wm8731_snd_controls, + ARRAY_SIZE(wm8731_snd_controls)); wm8731_add_widgets(codec); ret = snd_soc_init_card(socdev); if (ret < 0) { - printk(KERN_ERR "wm8731: failed to register card\n"); + dev_err(codec->dev, "failed to register card: %d\n", ret); goto card_err; } @@ -557,133 +525,109 @@ card_err: snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); pcm_err: - kfree(codec->reg_cache); return ret; } -static struct snd_soc_device *wm8731_socdev; - -#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) - -/* - * WM8731 2 wire address is determined by GPIO5 - * state during powerup. - * low = 0x1a - * high = 0x1b - */ - -static int wm8731_i2c_probe(struct i2c_client *i2c, - const struct i2c_device_id *id) +/* power down chip */ +static int wm8731_remove(struct platform_device *pdev) { - struct snd_soc_device *socdev = wm8731_socdev; - struct snd_soc_codec *codec = socdev->codec; - int ret; - - i2c_set_clientdata(i2c, codec); - codec->control_data = i2c; - - ret = wm8731_init(socdev); - if (ret < 0) - pr_err("failed to initialise WM8731\n"); + struct snd_soc_device *socdev = platform_get_drvdata(pdev); - return ret; -} + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); -static int wm8731_i2c_remove(struct i2c_client *client) -{ - struct snd_soc_codec *codec = i2c_get_clientdata(client); - kfree(codec->reg_cache); return 0; } -static const struct i2c_device_id wm8731_i2c_id[] = { - { "wm8731", 0 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, wm8731_i2c_id); - -static struct i2c_driver wm8731_i2c_driver = { - .driver = { - .name = "WM8731 I2C Codec", - .owner = THIS_MODULE, - }, - .probe = wm8731_i2c_probe, - .remove = wm8731_i2c_remove, - .id_table = wm8731_i2c_id, +struct snd_soc_codec_device soc_codec_dev_wm8731 = { + .probe = wm8731_probe, + .remove = wm8731_remove, + .suspend = wm8731_suspend, + .resume = wm8731_resume, }; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8731); -static int wm8731_add_i2c_device(struct platform_device *pdev, - const struct wm8731_setup_data *setup) +static int wm8731_register(struct wm8731_priv *wm8731) { - struct i2c_board_info info; - struct i2c_adapter *adapter; - struct i2c_client *client; int ret; + struct snd_soc_codec *codec = &wm8731->codec; + u16 reg; - ret = i2c_add_driver(&wm8731_i2c_driver); - if (ret != 0) { - dev_err(&pdev->dev, "can't add i2c driver\n"); - return ret; + if (wm8731_codec) { + dev_err(codec->dev, "Another WM8731 is registered\n"); + return -EINVAL; } - memset(&info, 0, sizeof(struct i2c_board_info)); - info.addr = setup->i2c_address; - strlcpy(info.type, "wm8731", I2C_NAME_SIZE); + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); - adapter = i2c_get_adapter(setup->i2c_bus); - if (!adapter) { - dev_err(&pdev->dev, "can't get i2c adapter %d\n", - setup->i2c_bus); - goto err_driver; - } + codec->private_data = wm8731; + codec->name = "WM8731"; + codec->owner = THIS_MODULE; + codec->read = wm8731_read_reg_cache; + codec->write = wm8731_write; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8731_set_bias_level; + codec->dai = &wm8731_dai; + codec->num_dai = 1; + codec->reg_cache_size = WM8731_CACHEREGNUM; + codec->reg_cache = &wm8731->reg_cache; - client = i2c_new_device(adapter, &info); - i2c_put_adapter(adapter); - if (!client) { - dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", - (unsigned int)info.addr); - goto err_driver; + memcpy(codec->reg_cache, wm8731_reg, sizeof(wm8731_reg)); + + ret = wm8731_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset\n"); + return ret; } - return 0; + wm8731_dai.dev = codec->dev; -err_driver: - i2c_del_driver(&wm8731_i2c_driver); - return -ENODEV; -} -#endif + wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY); -#if defined(CONFIG_SPI_MASTER) -static int __devinit wm8731_spi_probe(struct spi_device *spi) -{ - struct snd_soc_device *socdev = wm8731_socdev; - struct snd_soc_codec *codec = socdev->codec; - int ret; + /* Latch the update bits */ + reg = wm8731_read_reg_cache(codec, WM8731_LOUT1V); + wm8731_write(codec, WM8731_LOUT1V, reg & ~0x0100); + reg = wm8731_read_reg_cache(codec, WM8731_ROUT1V); + wm8731_write(codec, WM8731_ROUT1V, reg & ~0x0100); + reg = wm8731_read_reg_cache(codec, WM8731_LINVOL); + wm8731_write(codec, WM8731_LINVOL, reg & ~0x0100); + reg = wm8731_read_reg_cache(codec, WM8731_RINVOL); + wm8731_write(codec, WM8731_RINVOL, reg & ~0x0100); - codec->control_data = spi; + /* Disable bypass path by default */ + reg = wm8731_read_reg_cache(codec, WM8731_APANA); + wm8731_write(codec, WM8731_APANA, reg & ~0x4); - ret = wm8731_init(socdev); - if (ret < 0) - dev_err(&spi->dev, "failed to initialise WM8731\n"); + wm8731_codec = codec; - return ret; -} + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } + + ret = snd_soc_register_dai(&wm8731_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + return ret; + } -static int __devexit wm8731_spi_remove(struct spi_device *spi) -{ return 0; } -static struct spi_driver wm8731_spi_driver = { - .driver = { - .name = "wm8731", - .bus = &spi_bus_type, - .owner = THIS_MODULE, - }, - .probe = wm8731_spi_probe, - .remove = __devexit_p(wm8731_spi_remove), -}; +static void wm8731_unregister(struct wm8731_priv *wm8731) +{ + wm8731_set_bias_level(&wm8731->codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dai(&wm8731_dai); + snd_soc_unregister_codec(&wm8731->codec); + kfree(wm8731); + wm8731_codec = NULL; +} +#if defined(CONFIG_SPI_MASTER) static int wm8731_spi_write(struct spi_device *spi, const char *data, int len) { struct spi_transfer t; @@ -707,101 +651,121 @@ static int wm8731_spi_write(struct spi_device *spi, const char *data, int len) return len; } -#endif /* CONFIG_SPI_MASTER */ -static int wm8731_probe(struct platform_device *pdev) +static int __devinit wm8731_spi_probe(struct spi_device *spi) { - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct wm8731_setup_data *setup; struct snd_soc_codec *codec; struct wm8731_priv *wm8731; - int ret = 0; - - pr_info("WM8731 Audio Codec %s", WM8731_VERSION); - - setup = socdev->codec_data; - codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); - if (codec == NULL) - return -ENOMEM; wm8731 = kzalloc(sizeof(struct wm8731_priv), GFP_KERNEL); - if (wm8731 == NULL) { - kfree(codec); + if (wm8731 == NULL) return -ENOMEM; - } - codec->private_data = wm8731; - socdev->codec = codec; - mutex_init(&codec->mutex); - INIT_LIST_HEAD(&codec->dapm_widgets); - INIT_LIST_HEAD(&codec->dapm_paths); + codec = &wm8731->codec; + codec->control_data = spi; + codec->hw_write = (hw_write_t)wm8731_spi_write; + codec->dev = &spi->dev; - wm8731_socdev = socdev; - ret = -ENODEV; + spi->dev.driver_data = wm8731; -#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) - if (setup->i2c_address) { - codec->hw_write = (hw_write_t)i2c_master_send; - ret = wm8731_add_i2c_device(pdev, setup); - } -#endif -#if defined(CONFIG_SPI_MASTER) - if (setup->spi) { - codec->hw_write = (hw_write_t)wm8731_spi_write; - ret = spi_register_driver(&wm8731_spi_driver); - if (ret != 0) - printk(KERN_ERR "can't add spi driver"); - } -#endif - - if (ret != 0) { - kfree(codec->private_data); - kfree(codec); - } - return ret; + return wm8731_register(wm8731); } -/* power down chip */ -static int wm8731_remove(struct platform_device *pdev) +static int __devexit wm8731_spi_remove(struct spi_device *spi) { - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct wm8731_priv *wm8731 = spi->dev.driver_data; - if (codec->control_data) - wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF); + wm8731_unregister(wm8731); + + return 0; +} + +static struct spi_driver wm8731_spi_driver = { + .driver = { + .name = "wm8731", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = wm8731_spi_probe, + .remove = __devexit_p(wm8731_spi_remove), +}; +#endif /* CONFIG_SPI_MASTER */ - snd_soc_free_pcms(socdev); - snd_soc_dapm_free(socdev); #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) - i2c_unregister_device(codec->control_data); - i2c_del_driver(&wm8731_i2c_driver); -#endif -#if defined(CONFIG_SPI_MASTER) - spi_unregister_driver(&wm8731_spi_driver); -#endif - kfree(codec->private_data); - kfree(codec); +static __devinit int wm8731_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8731_priv *wm8731; + struct snd_soc_codec *codec; + + wm8731 = kzalloc(sizeof(struct wm8731_priv), GFP_KERNEL); + if (wm8731 == NULL) + return -ENOMEM; + + codec = &wm8731->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + i2c_set_clientdata(i2c, wm8731); + codec->control_data = i2c; + + codec->dev = &i2c->dev; + + return wm8731_register(wm8731); +} + +static __devexit int wm8731_i2c_remove(struct i2c_client *client) +{ + struct wm8731_priv *wm8731 = i2c_get_clientdata(client); + wm8731_unregister(wm8731); return 0; } -struct snd_soc_codec_device soc_codec_dev_wm8731 = { - .probe = wm8731_probe, - .remove = wm8731_remove, - .suspend = wm8731_suspend, - .resume = wm8731_resume, +static const struct i2c_device_id wm8731_i2c_id[] = { + { "wm8731", 0 }, + { } }; -EXPORT_SYMBOL_GPL(soc_codec_dev_wm8731); +MODULE_DEVICE_TABLE(i2c, wm8731_i2c_id); + +static struct i2c_driver wm8731_i2c_driver = { + .driver = { + .name = "WM8731 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = wm8731_i2c_probe, + .remove = __devexit_p(wm8731_i2c_remove), + .id_table = wm8731_i2c_id, +}; +#endif static int __init wm8731_modinit(void) { - return snd_soc_register_dai(&wm8731_dai); + int ret; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&wm8731_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8731 I2C driver: %d\n", + ret); + } +#endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&wm8731_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8731 SPI driver: %d\n", + ret); + } +#endif + return 0; } module_init(wm8731_modinit); static void __exit wm8731_exit(void) { - snd_soc_unregister_dai(&wm8731_dai); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&wm8731_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8731_spi_driver); +#endif } module_exit(wm8731_exit); diff --git a/sound/soc/codecs/wm8731.h b/sound/soc/codecs/wm8731.h index 95190e9..cd7b806 100644 --- a/sound/soc/codecs/wm8731.h +++ b/sound/soc/codecs/wm8731.h @@ -34,12 +34,6 @@ #define WM8731_SYSCLK 0 #define WM8731_DAI 0 -struct wm8731_setup_data { - int spi; - int i2c_bus; - unsigned short i2c_address; -}; - extern struct snd_soc_dai wm8731_dai; extern struct snd_soc_codec_device soc_codec_dev_wm8731; diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index 5997fa6..b64509b 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -231,21 +231,6 @@ SOC_SINGLE("Mono Playback Volume", WM8750_MOUTV, 0, 127, 0), }; -/* add non dapm controls */ -static int wm8750_add_controls(struct snd_soc_codec *codec) -{ - int err, i; - - for (i = 0; i < ARRAY_SIZE(wm8750_snd_controls); i++) { - err = snd_ctl_add(codec->card, - snd_soc_cnew(&wm8750_snd_controls[i], - codec, NULL)); - if (err < 0) - return err; - } - return 0; -} - /* * DAPM Controls */ @@ -619,7 +604,7 @@ static int wm8750_pcm_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct wm8750_priv *wm8750 = codec->private_data; u16 iface = wm8750_read_reg_cache(codec, WM8750_IFACE) & 0x1f3; u16 srate = wm8750_read_reg_cache(codec, WM8750_SRATE) & 0x1c0; @@ -694,6 +679,13 @@ static int wm8750_set_bias_level(struct snd_soc_codec *codec, #define WM8750_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static struct snd_soc_dai_ops wm8750_dai_ops = { + .hw_params = wm8750_pcm_hw_params, + .digital_mute = wm8750_mute, + .set_fmt = wm8750_set_dai_fmt, + .set_sysclk = wm8750_set_dai_sysclk, +}; + struct snd_soc_dai wm8750_dai = { .name = "WM8750", .playback = { @@ -708,12 +700,7 @@ struct snd_soc_dai wm8750_dai = { .channels_max = 2, .rates = WM8750_RATES, .formats = WM8750_FORMATS,}, - .ops = { - .hw_params = wm8750_pcm_hw_params, - .digital_mute = wm8750_mute, - .set_fmt = wm8750_set_dai_fmt, - .set_sysclk = wm8750_set_dai_sysclk, - }, + .ops = &wm8750_dai_ops, }; EXPORT_SYMBOL_GPL(wm8750_dai); @@ -727,7 +714,7 @@ static void wm8750_work(struct work_struct *work) static int wm8750_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; wm8750_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; @@ -736,7 +723,7 @@ static int wm8750_suspend(struct platform_device *pdev, pm_message_t state) static int wm8750_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int i; u8 data[2]; u16 *cache = codec->reg_cache; @@ -769,7 +756,7 @@ static int wm8750_resume(struct platform_device *pdev) */ static int wm8750_init(struct snd_soc_device *socdev) { - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int reg, ret = 0; codec->name = "WM8750"; @@ -816,7 +803,8 @@ static int wm8750_init(struct snd_soc_device *socdev) reg = wm8750_read_reg_cache(codec, WM8750_RINVOL); wm8750_write(codec, WM8750_RINVOL, reg | 0x0100); - wm8750_add_controls(codec); + snd_soc_add_controls(codec, wm8750_snd_controls, + ARRAY_SIZE(wm8750_snd_controls)); wm8750_add_widgets(codec); ret = snd_soc_init_card(socdev); if (ret < 0) { @@ -850,7 +838,7 @@ static int wm8750_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct snd_soc_device *socdev = wm8750_socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int ret; i2c_set_clientdata(i2c, codec); @@ -931,7 +919,7 @@ err_driver: static int __devinit wm8750_spi_probe(struct spi_device *spi) { struct snd_soc_device *socdev = wm8750_socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int ret; codec->control_data = spi; @@ -1003,7 +991,7 @@ static int wm8750_probe(struct platform_device *pdev) } codec->private_data = wm8750; - socdev->codec = codec; + socdev->card->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); @@ -1057,7 +1045,7 @@ static int run_delayed_work(struct delayed_work *dwork) static int wm8750_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; if (codec->control_data) wm8750_set_bias_level(codec, SND_SOC_BIAS_OFF); diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index 77620ab..a6e8f3f 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -51,8 +51,6 @@ #include "wm8753.h" -#define WM8753_VERSION "0.16" - static int caps_charge = 2000; module_param(caps_charge, int, 0); MODULE_PARM_DESC(caps_charge, "WM8753 cap charge time (msecs)"); @@ -60,12 +58,6 @@ MODULE_PARM_DESC(caps_charge, "WM8753 cap charge time (msecs)"); static void wm8753_set_dai_mode(struct snd_soc_codec *codec, unsigned int mode); -/* codec private data */ -struct wm8753_priv { - unsigned int sysclk; - unsigned int pcmclk; -}; - /* * wm8753 register cache * We can't read the WM8753 register space when we @@ -90,6 +82,14 @@ static const u16 wm8753_reg[] = { 0x0000, 0x0000 }; +/* codec private data */ +struct wm8753_priv { + unsigned int sysclk; + unsigned int pcmclk; + struct snd_soc_codec codec; + u16 reg_cache[ARRAY_SIZE(wm8753_reg)]; +}; + /* * read wm8753 register cache */ @@ -97,7 +97,7 @@ static inline unsigned int wm8753_read_reg_cache(struct snd_soc_codec *codec, unsigned int reg) { u16 *cache = codec->reg_cache; - if (reg < 1 || reg > (ARRAY_SIZE(wm8753_reg) + 1)) + if (reg < 1 || reg >= (ARRAY_SIZE(wm8753_reg) + 1)) return -1; return cache[reg - 1]; } @@ -109,7 +109,7 @@ static inline void wm8753_write_reg_cache(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { u16 *cache = codec->reg_cache; - if (reg < 1 || reg > 0x3f) + if (reg < 1 || reg >= (ARRAY_SIZE(wm8753_reg) + 1)) return; cache[reg - 1] = value; } @@ -339,21 +339,6 @@ SOC_ENUM("ADC Data Select", wm8753_enum[27]), SOC_ENUM("ROUT2 Phase", wm8753_enum[28]), }; -/* add non dapm controls */ -static int wm8753_add_controls(struct snd_soc_codec *codec) -{ - int err, i; - - for (i = 0; i < ARRAY_SIZE(wm8753_snd_controls); i++) { - err = snd_ctl_add(codec->card, - snd_soc_cnew(&wm8753_snd_controls[i], - codec, NULL)); - if (err < 0) - return err; - } - return 0; -} - /* * _DAPM_ Controls */ @@ -927,7 +912,7 @@ static int wm8753_pcm_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct wm8753_priv *wm8753 = codec->private_data; u16 voice = wm8753_read_reg_cache(codec, WM8753_PCM) & 0x01f3; u16 srate = wm8753_read_reg_cache(codec, WM8753_SRATE1) & 0x017f; @@ -1161,7 +1146,7 @@ static int wm8753_i2s_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct wm8753_priv *wm8753 = codec->private_data; u16 srate = wm8753_read_reg_cache(codec, WM8753_SRATE1) & 0x01c0; u16 hifi = wm8753_read_reg_cache(codec, WM8753_HIFI) & 0x01f3; @@ -1316,6 +1301,51 @@ static int wm8753_set_bias_level(struct snd_soc_codec *codec, * 3. Voice disabled - HIFI over HIFI * 4. Voice disabled - HIFI over HIFI, uses voice DAI LRC for capture */ +static struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode1 = { + .hw_params = wm8753_i2s_hw_params, + .digital_mute = wm8753_mute, + .set_fmt = wm8753_mode1h_set_dai_fmt, + .set_clkdiv = wm8753_set_dai_clkdiv, + .set_pll = wm8753_set_dai_pll, + .set_sysclk = wm8753_set_dai_sysclk, +}; + +static struct snd_soc_dai_ops wm8753_dai_ops_voice_mode1 = { + .hw_params = wm8753_pcm_hw_params, + .digital_mute = wm8753_mute, + .set_fmt = wm8753_mode1v_set_dai_fmt, + .set_clkdiv = wm8753_set_dai_clkdiv, + .set_pll = wm8753_set_dai_pll, + .set_sysclk = wm8753_set_dai_sysclk, +}; + +static struct snd_soc_dai_ops wm8753_dai_ops_voice_mode2 = { + .hw_params = wm8753_pcm_hw_params, + .digital_mute = wm8753_mute, + .set_fmt = wm8753_mode2_set_dai_fmt, + .set_clkdiv = wm8753_set_dai_clkdiv, + .set_pll = wm8753_set_dai_pll, + .set_sysclk = wm8753_set_dai_sysclk, +}; + +static struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode3 = { + .hw_params = wm8753_i2s_hw_params, + .digital_mute = wm8753_mute, + .set_fmt = wm8753_mode3_4_set_dai_fmt, + .set_clkdiv = wm8753_set_dai_clkdiv, + .set_pll = wm8753_set_dai_pll, + .set_sysclk = wm8753_set_dai_sysclk, +}; + +static struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode4 = { + .hw_params = wm8753_i2s_hw_params, + .digital_mute = wm8753_mute, + .set_fmt = wm8753_mode3_4_set_dai_fmt, + .set_clkdiv = wm8753_set_dai_clkdiv, + .set_pll = wm8753_set_dai_pll, + .set_sysclk = wm8753_set_dai_sysclk, +}; + static const struct snd_soc_dai wm8753_all_dai[] = { /* DAI HiFi mode 1 */ { .name = "WM8753 HiFi", @@ -1332,14 +1362,7 @@ static const struct snd_soc_dai wm8753_all_dai[] = { .channels_max = 2, .rates = WM8753_RATES, .formats = WM8753_FORMATS}, - .ops = { - .hw_params = wm8753_i2s_hw_params, - .digital_mute = wm8753_mute, - .set_fmt = wm8753_mode1h_set_dai_fmt, - .set_clkdiv = wm8753_set_dai_clkdiv, - .set_pll = wm8753_set_dai_pll, - .set_sysclk = wm8753_set_dai_sysclk, - }, + .ops = &wm8753_dai_ops_hifi_mode1, }, /* DAI Voice mode 1 */ { .name = "WM8753 Voice", @@ -1356,14 +1379,7 @@ static const struct snd_soc_dai wm8753_all_dai[] = { .channels_max = 2, .rates = WM8753_RATES, .formats = WM8753_FORMATS,}, - .ops = { - .hw_params = wm8753_pcm_hw_params, - .digital_mute = wm8753_mute, - .set_fmt = wm8753_mode1v_set_dai_fmt, - .set_clkdiv = wm8753_set_dai_clkdiv, - .set_pll = wm8753_set_dai_pll, - .set_sysclk = wm8753_set_dai_sysclk, - }, + .ops = &wm8753_dai_ops_voice_mode1, }, /* DAI HiFi mode 2 - dummy */ { .name = "WM8753 HiFi", @@ -1384,14 +1400,7 @@ static const struct snd_soc_dai wm8753_all_dai[] = { .channels_max = 2, .rates = WM8753_RATES, .formats = WM8753_FORMATS,}, - .ops = { - .hw_params = wm8753_pcm_hw_params, - .digital_mute = wm8753_mute, - .set_fmt = wm8753_mode2_set_dai_fmt, - .set_clkdiv = wm8753_set_dai_clkdiv, - .set_pll = wm8753_set_dai_pll, - .set_sysclk = wm8753_set_dai_sysclk, - }, + .ops = &wm8753_dai_ops_voice_mode2, }, /* DAI HiFi mode 3 */ { .name = "WM8753 HiFi", @@ -1408,14 +1417,7 @@ static const struct snd_soc_dai wm8753_all_dai[] = { .channels_max = 2, .rates = WM8753_RATES, .formats = WM8753_FORMATS,}, - .ops = { - .hw_params = wm8753_i2s_hw_params, - .digital_mute = wm8753_mute, - .set_fmt = wm8753_mode3_4_set_dai_fmt, - .set_clkdiv = wm8753_set_dai_clkdiv, - .set_pll = wm8753_set_dai_pll, - .set_sysclk = wm8753_set_dai_sysclk, - }, + .ops = &wm8753_dai_ops_hifi_mode3, }, /* DAI Voice mode 3 - dummy */ { .name = "WM8753 Voice", @@ -1436,14 +1438,7 @@ static const struct snd_soc_dai wm8753_all_dai[] = { .channels_max = 2, .rates = WM8753_RATES, .formats = WM8753_FORMATS,}, - .ops = { - .hw_params = wm8753_i2s_hw_params, - .digital_mute = wm8753_mute, - .set_fmt = wm8753_mode3_4_set_dai_fmt, - .set_clkdiv = wm8753_set_dai_clkdiv, - .set_pll = wm8753_set_dai_pll, - .set_sysclk = wm8753_set_dai_sysclk, - }, + .ops = &wm8753_dai_ops_hifi_mode4, }, /* DAI Voice mode 4 - dummy */ { .name = "WM8753 Voice", @@ -1466,30 +1461,35 @@ static void wm8753_set_dai_mode(struct snd_soc_codec *codec, unsigned int mode) if (mode < 4) { int playback_active, capture_active, codec_active, pop_wait; void *private_data; + struct list_head list; playback_active = wm8753_dai[0].playback.active; capture_active = wm8753_dai[0].capture.active; codec_active = wm8753_dai[0].active; private_data = wm8753_dai[0].private_data; pop_wait = wm8753_dai[0].pop_wait; + list = wm8753_dai[0].list; wm8753_dai[0] = wm8753_all_dai[mode << 1]; wm8753_dai[0].playback.active = playback_active; wm8753_dai[0].capture.active = capture_active; wm8753_dai[0].active = codec_active; wm8753_dai[0].private_data = private_data; wm8753_dai[0].pop_wait = pop_wait; + wm8753_dai[0].list = list; playback_active = wm8753_dai[1].playback.active; capture_active = wm8753_dai[1].capture.active; codec_active = wm8753_dai[1].active; private_data = wm8753_dai[1].private_data; pop_wait = wm8753_dai[1].pop_wait; + list = wm8753_dai[1].list; wm8753_dai[1] = wm8753_all_dai[(mode << 1) + 1]; wm8753_dai[1].playback.active = playback_active; wm8753_dai[1].capture.active = capture_active; wm8753_dai[1].active = codec_active; wm8753_dai[1].private_data = private_data; wm8753_dai[1].pop_wait = pop_wait; + wm8753_dai[1].list = list; } wm8753_dai[0].codec = codec; wm8753_dai[1].codec = codec; @@ -1505,7 +1505,7 @@ static void wm8753_work(struct work_struct *work) static int wm8753_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; /* we only need to suspend if we are a valid card */ if (!codec->card) @@ -1518,7 +1518,7 @@ static int wm8753_suspend(struct platform_device *pdev, pm_message_t state) static int wm8753_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int i; u8 data[2]; u16 *cache = codec->reg_cache; @@ -1531,6 +1531,11 @@ static int wm8753_resume(struct platform_device *pdev) for (i = 0; i < ARRAY_SIZE(wm8753_reg); i++) { if (i + 1 == WM8753_RESET) continue; + + /* No point in writing hardware default values back */ + if (cache[i] == wm8753_reg[i]) + continue; + data[0] = ((i + 1) << 1) | ((cache[i] >> 8) & 0x0001); data[1] = cache[i] & 0x00ff; codec->hw_write(codec->control_data, data, 2); @@ -1549,44 +1554,129 @@ static int wm8753_resume(struct platform_device *pdev) return 0; } +static struct snd_soc_codec *wm8753_codec; + +static int wm8753_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (!wm8753_codec) { + dev_err(&pdev->dev, "WM8753 codec not yet registered\n"); + return -EINVAL; + } + + socdev->card->codec = wm8753_codec; + codec = wm8753_codec; + + wm8753_set_dai_mode(codec, 0); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8753: failed to create pcms\n"); + goto pcm_err; + } + + snd_soc_add_controls(codec, wm8753_snd_controls, + ARRAY_SIZE(wm8753_snd_controls)); + wm8753_add_widgets(codec); + ret = snd_soc_init_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8753: failed to register card\n"); + goto card_err; + } + + return 0; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + +pcm_err: + return ret; +} + /* - * initialise the WM8753 driver - * register the mixer and dsp interfaces with the kernel + * This function forces any delayed work to be queued and run. */ -static int wm8753_init(struct snd_soc_device *socdev) +static int run_delayed_work(struct delayed_work *dwork) +{ + int ret; + + /* cancel any work waiting to be queued. */ + ret = cancel_delayed_work(dwork); + + /* if there was any work waiting then we run it now and + * wait for it's completion */ + if (ret) { + schedule_delayed_work(dwork, 0); + flush_scheduled_work(); + } + return ret; +} + +/* power down chip */ +static int wm8753_remove(struct platform_device *pdev) { - struct snd_soc_codec *codec = socdev->codec; - int reg, ret = 0; + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8753 = { + .probe = wm8753_probe, + .remove = wm8753_remove, + .suspend = wm8753_suspend, + .resume = wm8753_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8753); + +static int wm8753_register(struct wm8753_priv *wm8753) +{ + int ret, i; + struct snd_soc_codec *codec = &wm8753->codec; + u16 reg; + + if (wm8753_codec) { + dev_err(codec->dev, "Multiple WM8753 devices not supported\n"); + ret = -EINVAL; + goto err; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); codec->name = "WM8753"; codec->owner = THIS_MODULE; codec->read = wm8753_read_reg_cache; codec->write = wm8753_write; + codec->bias_level = SND_SOC_BIAS_STANDBY; codec->set_bias_level = wm8753_set_bias_level; codec->dai = wm8753_dai; codec->num_dai = 2; - codec->reg_cache_size = ARRAY_SIZE(wm8753_reg); - codec->reg_cache = kmemdup(wm8753_reg, sizeof(wm8753_reg), GFP_KERNEL); - - if (codec->reg_cache == NULL) - return -ENOMEM; - - wm8753_set_dai_mode(codec, 0); + codec->reg_cache_size = ARRAY_SIZE(wm8753->reg_cache); + codec->reg_cache = &wm8753->reg_cache; + codec->private_data = wm8753; - wm8753_reset(codec); + memcpy(codec->reg_cache, wm8753_reg, sizeof(codec->reg_cache)); + INIT_DELAYED_WORK(&codec->delayed_work, wm8753_work); - /* register pcms */ - ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + ret = wm8753_reset(codec); if (ret < 0) { - printk(KERN_ERR "wm8753: failed to create pcms\n"); - goto pcm_err; + dev_err(codec->dev, "Failed to issue reset\n"); + goto err; } /* charge output caps */ wm8753_set_bias_level(codec, SND_SOC_BIAS_PREPARE); - codec->bias_level = SND_SOC_BIAS_STANDBY; schedule_delayed_work(&codec->delayed_work, - msecs_to_jiffies(caps_charge)); + msecs_to_jiffies(caps_charge)); /* set the update bits */ reg = wm8753_read_reg_cache(codec, WM8753_LDAC); @@ -1610,59 +1700,70 @@ static int wm8753_init(struct snd_soc_device *socdev) reg = wm8753_read_reg_cache(codec, WM8753_RINVOL); wm8753_write(codec, WM8753_RINVOL, reg | 0x0100); - wm8753_add_controls(codec); - wm8753_add_widgets(codec); - ret = snd_soc_init_card(socdev); - if (ret < 0) { - printk(KERN_ERR "wm8753: failed to register card\n"); - goto card_err; + wm8753_codec = codec; + + for (i = 0; i < ARRAY_SIZE(wm8753_dai); i++) + wm8753_dai[i].dev = codec->dev; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + goto err; } - return ret; + ret = snd_soc_register_dais(&wm8753_dai[0], ARRAY_SIZE(wm8753_dai)); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAIs: %d\n", ret); + goto err_codec; + } -card_err: - snd_soc_free_pcms(socdev); - snd_soc_dapm_free(socdev); -pcm_err: - kfree(codec->reg_cache); + return 0; + +err_codec: + run_delayed_work(&codec->delayed_work); + snd_soc_unregister_codec(codec); +err: + kfree(wm8753); return ret; } -/* If the i2c layer weren't so broken, we could pass this kind of data - around */ -static struct snd_soc_device *wm8753_socdev; +static void wm8753_unregister(struct wm8753_priv *wm8753) +{ + wm8753_set_bias_level(&wm8753->codec, SND_SOC_BIAS_OFF); + run_delayed_work(&wm8753->codec.delayed_work); + snd_soc_unregister_dais(&wm8753_dai[0], ARRAY_SIZE(wm8753_dai)); + snd_soc_unregister_codec(&wm8753->codec); + kfree(wm8753); + wm8753_codec = NULL; +} #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) -/* - * WM8753 2 wire address is determined by GPIO5 - * state during powerup. - * low = 0x1a - * high = 0x1b - */ - static int wm8753_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { - struct snd_soc_device *socdev = wm8753_socdev; - struct snd_soc_codec *codec = socdev->codec; - int ret; + struct snd_soc_codec *codec; + struct wm8753_priv *wm8753; - i2c_set_clientdata(i2c, codec); - codec->control_data = i2c; + wm8753 = kzalloc(sizeof(struct wm8753_priv), GFP_KERNEL); + if (wm8753 == NULL) + return -ENOMEM; - ret = wm8753_init(socdev); - if (ret < 0) - pr_err("failed to initialise WM8753\n"); + codec = &wm8753->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + codec->control_data = i2c; + i2c_set_clientdata(i2c, wm8753); - return ret; + codec->dev = &i2c->dev; + + return wm8753_register(wm8753); } static int wm8753_i2c_remove(struct i2c_client *client) { - struct snd_soc_codec *codec = i2c_get_clientdata(client); - kfree(codec->reg_cache); - return 0; + struct wm8753_priv *wm8753 = i2c_get_clientdata(client); + wm8753_unregister(wm8753); + return 0; } static const struct i2c_device_id wm8753_i2c_id[] = { @@ -1673,86 +1774,16 @@ MODULE_DEVICE_TABLE(i2c, wm8753_i2c_id); static struct i2c_driver wm8753_i2c_driver = { .driver = { - .name = "WM8753 I2C Codec", + .name = "wm8753", .owner = THIS_MODULE, }, .probe = wm8753_i2c_probe, .remove = wm8753_i2c_remove, .id_table = wm8753_i2c_id, }; - -static int wm8753_add_i2c_device(struct platform_device *pdev, - const struct wm8753_setup_data *setup) -{ - struct i2c_board_info info; - struct i2c_adapter *adapter; - struct i2c_client *client; - int ret; - - ret = i2c_add_driver(&wm8753_i2c_driver); - if (ret != 0) { - dev_err(&pdev->dev, "can't add i2c driver\n"); - return ret; - } - - memset(&info, 0, sizeof(struct i2c_board_info)); - info.addr = setup->i2c_address; - strlcpy(info.type, "wm8753", I2C_NAME_SIZE); - - adapter = i2c_get_adapter(setup->i2c_bus); - if (!adapter) { - dev_err(&pdev->dev, "can't get i2c adapter %d\n", - setup->i2c_bus); - goto err_driver; - } - - client = i2c_new_device(adapter, &info); - i2c_put_adapter(adapter); - if (!client) { - dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", - (unsigned int)info.addr); - goto err_driver; - } - - return 0; - -err_driver: - i2c_del_driver(&wm8753_i2c_driver); - return -ENODEV; -} #endif #if defined(CONFIG_SPI_MASTER) -static int __devinit wm8753_spi_probe(struct spi_device *spi) -{ - struct snd_soc_device *socdev = wm8753_socdev; - struct snd_soc_codec *codec = socdev->codec; - int ret; - - codec->control_data = spi; - - ret = wm8753_init(socdev); - if (ret < 0) - dev_err(&spi->dev, "failed to initialise WM8753\n"); - - return ret; -} - -static int __devexit wm8753_spi_remove(struct spi_device *spi) -{ - return 0; -} - -static struct spi_driver wm8753_spi_driver = { - .driver = { - .name = "wm8753", - .bus = &spi_bus_type, - .owner = THIS_MODULE, - }, - .probe = wm8753_spi_probe, - .remove = __devexit_p(wm8753_spi_remove), -}; - static int wm8753_spi_write(struct spi_device *spi, const char *data, int len) { struct spi_transfer t; @@ -1776,120 +1807,69 @@ static int wm8753_spi_write(struct spi_device *spi, const char *data, int len) return len; } -#endif - -static int wm8753_probe(struct platform_device *pdev) +static int __devinit wm8753_spi_probe(struct spi_device *spi) { - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct wm8753_setup_data *setup; struct snd_soc_codec *codec; struct wm8753_priv *wm8753; - int ret = 0; - - pr_info("WM8753 Audio Codec %s", WM8753_VERSION); - - setup = socdev->codec_data; - codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); - if (codec == NULL) - return -ENOMEM; wm8753 = kzalloc(sizeof(struct wm8753_priv), GFP_KERNEL); - if (wm8753 == NULL) { - kfree(codec); + if (wm8753 == NULL) return -ENOMEM; - } - codec->private_data = wm8753; - socdev->codec = codec; - mutex_init(&codec->mutex); - INIT_LIST_HEAD(&codec->dapm_widgets); - INIT_LIST_HEAD(&codec->dapm_paths); - wm8753_socdev = socdev; - INIT_DELAYED_WORK(&codec->delayed_work, wm8753_work); + codec = &wm8753->codec; + codec->control_data = spi; + codec->hw_write = (hw_write_t)wm8753_spi_write; + codec->dev = &spi->dev; -#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) - if (setup->i2c_address) { - codec->hw_write = (hw_write_t)i2c_master_send; - ret = wm8753_add_i2c_device(pdev, setup); - } -#endif -#if defined(CONFIG_SPI_MASTER) - if (setup->spi) { - codec->hw_write = (hw_write_t)wm8753_spi_write; - ret = spi_register_driver(&wm8753_spi_driver); - if (ret != 0) - printk(KERN_ERR "can't add spi driver"); - } -#endif + spi->dev.driver_data = wm8753; - if (ret != 0) { - kfree(codec->private_data); - kfree(codec); - } - return ret; + return wm8753_register(wm8753); } -/* - * This function forces any delayed work to be queued and run. - */ -static int run_delayed_work(struct delayed_work *dwork) +static int __devexit wm8753_spi_remove(struct spi_device *spi) { - int ret; - - /* cancel any work waiting to be queued. */ - ret = cancel_delayed_work(dwork); - - /* if there was any work waiting then we run it now and - * wait for it's completion */ - if (ret) { - schedule_delayed_work(dwork, 0); - flush_scheduled_work(); - } - return ret; + struct wm8753_priv *wm8753 = spi->dev.driver_data; + wm8753_unregister(wm8753); + return 0; } -/* power down chip */ -static int wm8753_remove(struct platform_device *pdev) -{ - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; +static struct spi_driver wm8753_spi_driver = { + .driver = { + .name = "wm8753", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = wm8753_spi_probe, + .remove = __devexit_p(wm8753_spi_remove), +}; +#endif - if (codec->control_data) - wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF); - run_delayed_work(&codec->delayed_work); - snd_soc_free_pcms(socdev); - snd_soc_dapm_free(socdev); +static int __init wm8753_modinit(void) +{ + int ret; #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) - i2c_unregister_device(codec->control_data); - i2c_del_driver(&wm8753_i2c_driver); + ret = i2c_add_driver(&wm8753_i2c_driver); + if (ret != 0) + pr_err("Failed to register WM8753 I2C driver: %d\n", ret); #endif #if defined(CONFIG_SPI_MASTER) - spi_unregister_driver(&wm8753_spi_driver); + ret = spi_register_driver(&wm8753_spi_driver); + if (ret != 0) + pr_err("Failed to register WM8753 SPI driver: %d\n", ret); #endif - kfree(codec->private_data); - kfree(codec); - return 0; } - -struct snd_soc_codec_device soc_codec_dev_wm8753 = { - .probe = wm8753_probe, - .remove = wm8753_remove, - .suspend = wm8753_suspend, - .resume = wm8753_resume, -}; -EXPORT_SYMBOL_GPL(soc_codec_dev_wm8753); - -static int __init wm8753_modinit(void) -{ - return snd_soc_register_dais(wm8753_dai, ARRAY_SIZE(wm8753_dai)); -} module_init(wm8753_modinit); static void __exit wm8753_exit(void) { - snd_soc_unregister_dais(wm8753_dai, ARRAY_SIZE(wm8753_dai)); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&wm8753_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8753_spi_driver); +#endif } module_exit(wm8753_exit); diff --git a/sound/soc/codecs/wm8753.h b/sound/soc/codecs/wm8753.h index f55704c..57b2ba2 100644 --- a/sound/soc/codecs/wm8753.h +++ b/sound/soc/codecs/wm8753.h @@ -77,12 +77,6 @@ #define WM8753_BIASCTL 0x3d #define WM8753_ADCTL2 0x3f -struct wm8753_setup_data { - int spi; - int i2c_bus; - unsigned short i2c_address; -}; - #define WM8753_PLL1 0 #define WM8753_PLL2 1 diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c index 6767de1..46c5ea1 100644 --- a/sound/soc/codecs/wm8900.c +++ b/sound/soc/codecs/wm8900.c @@ -517,22 +517,6 @@ SOC_SINGLE("LINEOUT2 LP -12dB", WM8900_REG_LOUTMIXCTL1, }; -/* add non dapm controls */ -static int wm8900_add_controls(struct snd_soc_codec *codec) -{ - int err, i; - - for (i = 0; i < ARRAY_SIZE(wm8900_snd_controls); i++) { - err = snd_ctl_add(codec->card, - snd_soc_cnew(&wm8900_snd_controls[i], - codec, NULL)); - if (err < 0) - return err; - } - - return 0; -} - static const struct snd_kcontrol_new wm8900_dapm_loutput2_control = SOC_DAPM_SINGLE("LINEOUT2L Switch", WM8900_REG_POWER3, 6, 1, 0); @@ -736,7 +720,7 @@ static int wm8900_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; u16 reg; reg = wm8900_read(codec, WM8900_REG_AUDIO1) & ~0x60; @@ -1104,6 +1088,14 @@ static int wm8900_digital_mute(struct snd_soc_dai *codec_dai, int mute) (SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \ SNDRV_PCM_FORMAT_S24_LE) +static struct snd_soc_dai_ops wm8900_dai_ops = { + .hw_params = wm8900_hw_params, + .set_clkdiv = wm8900_set_dai_clkdiv, + .set_pll = wm8900_set_dai_pll, + .set_fmt = wm8900_set_dai_fmt, + .digital_mute = wm8900_digital_mute, +}; + struct snd_soc_dai wm8900_dai = { .name = "WM8900 HiFi", .playback = { @@ -1120,13 +1112,7 @@ struct snd_soc_dai wm8900_dai = { .rates = WM8900_RATES, .formats = WM8900_PCM_FORMATS, }, - .ops = { - .hw_params = wm8900_hw_params, - .set_clkdiv = wm8900_set_dai_clkdiv, - .set_pll = wm8900_set_dai_pll, - .set_fmt = wm8900_set_dai_fmt, - .digital_mute = wm8900_digital_mute, - }, + .ops = &wm8900_dai_ops, }; EXPORT_SYMBOL_GPL(wm8900_dai); @@ -1226,7 +1212,7 @@ static int wm8900_set_bias_level(struct snd_soc_codec *codec, static int wm8900_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct wm8900_priv *wm8900 = codec->private_data; int fll_out = wm8900->fll_out; int fll_in = wm8900->fll_in; @@ -1250,7 +1236,7 @@ static int wm8900_suspend(struct platform_device *pdev, pm_message_t state) static int wm8900_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct wm8900_priv *wm8900 = codec->private_data; u16 *cache; int i, ret; @@ -1288,8 +1274,8 @@ static int wm8900_resume(struct platform_device *pdev) static struct snd_soc_codec *wm8900_codec; -static int wm8900_i2c_probe(struct i2c_client *i2c, - const struct i2c_device_id *id) +static __devinit int wm8900_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) { struct wm8900_priv *wm8900; struct snd_soc_codec *codec; @@ -1388,7 +1374,7 @@ err: return ret; } -static int wm8900_i2c_remove(struct i2c_client *client) +static __devexit int wm8900_i2c_remove(struct i2c_client *client) { snd_soc_unregister_dai(&wm8900_dai); snd_soc_unregister_codec(wm8900_codec); @@ -1414,7 +1400,7 @@ static struct i2c_driver wm8900_i2c_driver = { .owner = THIS_MODULE, }, .probe = wm8900_i2c_probe, - .remove = wm8900_i2c_remove, + .remove = __devexit_p(wm8900_i2c_remove), .id_table = wm8900_i2c_id, }; @@ -1430,7 +1416,7 @@ static int wm8900_probe(struct platform_device *pdev) } codec = wm8900_codec; - socdev->codec = codec; + socdev->card->codec = codec; /* Register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); @@ -1439,7 +1425,8 @@ static int wm8900_probe(struct platform_device *pdev) goto pcm_err; } - wm8900_add_controls(codec); + snd_soc_add_controls(codec, wm8900_snd_controls, + ARRAY_SIZE(wm8900_snd_controls)); wm8900_add_widgets(codec); ret = snd_soc_init_card(socdev); diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index bde7454..8cf571f 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -744,21 +744,6 @@ SOC_DOUBLE_R_TLV("Speaker Volume", 0, 63, 0, out_tlv), }; -static int wm8903_add_controls(struct snd_soc_codec *codec) -{ - int err, i; - - for (i = 0; i < ARRAY_SIZE(wm8903_snd_controls); i++) { - err = snd_ctl_add(codec->card, - snd_soc_cnew(&wm8903_snd_controls[i], - codec, NULL)); - if (err < 0) - return err; - } - - return 0; -} - static const struct snd_kcontrol_new linput_mode_mux = SOC_DAPM_ENUM("Left Input Mode Mux", linput_mode_enum); @@ -1276,7 +1261,7 @@ static int wm8903_startup(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct wm8903_priv *wm8903 = codec->private_data; struct i2c_client *i2c = codec->control_data; struct snd_pcm_runtime *master_runtime; @@ -1318,7 +1303,7 @@ static void wm8903_shutdown(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct wm8903_priv *wm8903 = codec->private_data; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) @@ -1338,7 +1323,7 @@ static int wm8903_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct wm8903_priv *wm8903 = codec->private_data; struct i2c_client *i2c = codec->control_data; int fs = params_rate(params); @@ -1512,6 +1497,15 @@ static int wm8903_hw_params(struct snd_pcm_substream *substream, SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static struct snd_soc_dai_ops wm8903_dai_ops = { + .startup = wm8903_startup, + .shutdown = wm8903_shutdown, + .hw_params = wm8903_hw_params, + .digital_mute = wm8903_digital_mute, + .set_fmt = wm8903_set_dai_fmt, + .set_sysclk = wm8903_set_dai_sysclk, +}; + struct snd_soc_dai wm8903_dai = { .name = "WM8903", .playback = { @@ -1528,21 +1522,14 @@ struct snd_soc_dai wm8903_dai = { .rates = WM8903_CAPTURE_RATES, .formats = WM8903_FORMATS, }, - .ops = { - .startup = wm8903_startup, - .shutdown = wm8903_shutdown, - .hw_params = wm8903_hw_params, - .digital_mute = wm8903_digital_mute, - .set_fmt = wm8903_set_dai_fmt, - .set_sysclk = wm8903_set_dai_sysclk - } + .ops = &wm8903_dai_ops, }; EXPORT_SYMBOL_GPL(wm8903_dai); static int wm8903_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF); @@ -1552,7 +1539,7 @@ static int wm8903_suspend(struct platform_device *pdev, pm_message_t state) static int wm8903_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct i2c_client *i2c = codec->control_data; int i; u16 *reg_cache = codec->reg_cache; @@ -1577,8 +1564,8 @@ static int wm8903_resume(struct platform_device *pdev) static struct snd_soc_codec *wm8903_codec; -static int wm8903_i2c_probe(struct i2c_client *i2c, - const struct i2c_device_id *id) +static __devinit int wm8903_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) { struct wm8903_priv *wm8903; struct snd_soc_codec *codec; @@ -1684,7 +1671,7 @@ err: return ret; } -static int wm8903_i2c_remove(struct i2c_client *client) +static __devexit int wm8903_i2c_remove(struct i2c_client *client) { struct snd_soc_codec *codec = i2c_get_clientdata(client); @@ -1714,7 +1701,7 @@ static struct i2c_driver wm8903_i2c_driver = { .owner = THIS_MODULE, }, .probe = wm8903_i2c_probe, - .remove = wm8903_i2c_remove, + .remove = __devexit_p(wm8903_i2c_remove), .id_table = wm8903_i2c_id, }; @@ -1728,7 +1715,7 @@ static int wm8903_probe(struct platform_device *pdev) goto err; } - socdev->codec = wm8903_codec; + socdev->card->codec = wm8903_codec; /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); @@ -1737,8 +1724,9 @@ static int wm8903_probe(struct platform_device *pdev) goto err; } - wm8903_add_controls(socdev->codec); - wm8903_add_widgets(socdev->codec); + snd_soc_add_controls(socdev->card->codec, wm8903_snd_controls, + ARRAY_SIZE(wm8903_snd_controls)); + wm8903_add_widgets(socdev->card->codec); ret = snd_soc_init_card(socdev); if (ret < 0) { @@ -1759,7 +1747,7 @@ err: static int wm8903_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; if (codec->control_data) wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF); diff --git a/sound/soc/codecs/wm8971.c b/sound/soc/codecs/wm8971.c index 88ead7f..032dca2 100644 --- a/sound/soc/codecs/wm8971.c +++ b/sound/soc/codecs/wm8971.c @@ -195,21 +195,6 @@ static const struct snd_kcontrol_new wm8971_snd_controls[] = { SOC_DOUBLE_R("Mic Boost", WM8971_LADCIN, WM8971_RADCIN, 4, 3, 0), }; -/* add non-DAPM controls */ -static int wm8971_add_controls(struct snd_soc_codec *codec) -{ - int err, i; - - for (i = 0; i < ARRAY_SIZE(wm8971_snd_controls); i++) { - err = snd_ctl_add(codec->card, - snd_soc_cnew(&wm8971_snd_controls[i], - codec, NULL)); - if (err < 0) - return err; - } - return 0; -} - /* * DAPM Controls */ @@ -546,7 +531,7 @@ static int wm8971_pcm_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct wm8971_priv *wm8971 = codec->private_data; u16 iface = wm8971_read_reg_cache(codec, WM8971_IFACE) & 0x1f3; u16 srate = wm8971_read_reg_cache(codec, WM8971_SRATE) & 0x1c0; @@ -619,6 +604,13 @@ static int wm8971_set_bias_level(struct snd_soc_codec *codec, #define WM8971_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static struct snd_soc_dai_ops wm8971_dai_ops = { + .hw_params = wm8971_pcm_hw_params, + .digital_mute = wm8971_mute, + .set_fmt = wm8971_set_dai_fmt, + .set_sysclk = wm8971_set_dai_sysclk, +}; + struct snd_soc_dai wm8971_dai = { .name = "WM8971", .playback = { @@ -633,12 +625,7 @@ struct snd_soc_dai wm8971_dai = { .channels_max = 2, .rates = WM8971_RATES, .formats = WM8971_FORMATS,}, - .ops = { - .hw_params = wm8971_pcm_hw_params, - .digital_mute = wm8971_mute, - .set_fmt = wm8971_set_dai_fmt, - .set_sysclk = wm8971_set_dai_sysclk, - }, + .ops = &wm8971_dai_ops, }; EXPORT_SYMBOL_GPL(wm8971_dai); @@ -652,7 +639,7 @@ static void wm8971_work(struct work_struct *work) static int wm8971_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; @@ -661,7 +648,7 @@ static int wm8971_suspend(struct platform_device *pdev, pm_message_t state) static int wm8971_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int i; u8 data[2]; u16 *cache = codec->reg_cache; @@ -692,7 +679,7 @@ static int wm8971_resume(struct platform_device *pdev) static int wm8971_init(struct snd_soc_device *socdev) { - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int reg, ret = 0; codec->name = "WM8971"; @@ -745,7 +732,8 @@ static int wm8971_init(struct snd_soc_device *socdev) reg = wm8971_read_reg_cache(codec, WM8971_RINVOL); wm8971_write(codec, WM8971_RINVOL, reg | 0x0100); - wm8971_add_controls(codec); + snd_soc_add_controls(codec, wm8971_snd_controls, + ARRAY_SIZE(wm8971_snd_controls)); wm8971_add_widgets(codec); ret = snd_soc_init_card(socdev); if (ret < 0) { @@ -772,7 +760,7 @@ static int wm8971_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct snd_soc_device *socdev = wm8971_socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int ret; i2c_set_clientdata(i2c, codec); @@ -873,7 +861,7 @@ static int wm8971_probe(struct platform_device *pdev) } codec->private_data = wm8971; - socdev->codec = codec; + socdev->card->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); @@ -908,7 +896,7 @@ static int wm8971_probe(struct platform_device *pdev) static int wm8971_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; if (codec->control_data) wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF); diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c index a5731fa..c518c3e 100644 --- a/sound/soc/codecs/wm8990.c +++ b/sound/soc/codecs/wm8990.c @@ -115,7 +115,7 @@ static inline unsigned int wm8990_read_reg_cache(struct snd_soc_codec *codec, unsigned int reg) { u16 *cache = codec->reg_cache; - BUG_ON(reg > (ARRAY_SIZE(wm8990_reg)) - 1); + BUG_ON(reg >= ARRAY_SIZE(wm8990_reg)); return cache[reg]; } @@ -128,7 +128,7 @@ static inline void wm8990_write_reg_cache(struct snd_soc_codec *codec, u16 *cache = codec->reg_cache; /* Reset register and reserved registers are uncached */ - if (reg == 0 || reg > ARRAY_SIZE(wm8990_reg) - 1) + if (reg == 0 || reg >= ARRAY_SIZE(wm8990_reg)) return; cache[reg] = value; @@ -418,21 +418,6 @@ SOC_SINGLE("RIN34 Mute Switch", WM8990_RIGHT_LINE_INPUT_3_4_VOLUME, }; -/* add non dapm controls */ -static int wm8990_add_controls(struct snd_soc_codec *codec) -{ - int err, i; - - for (i = 0; i < ARRAY_SIZE(wm8990_snd_controls); i++) { - err = snd_ctl_add(codec->card, - snd_soc_cnew(&wm8990_snd_controls[i], codec, - NULL)); - if (err < 0) - return err; - } - return 0; -} - /* * _DAPM_ Controls */ @@ -1178,7 +1163,7 @@ static int wm8990_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; u16 audio1 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_1); audio1 &= ~WM8990_AIF_WL_MASK; @@ -1347,6 +1332,15 @@ static int wm8990_set_bias_level(struct snd_soc_codec *codec, * 1. ADC/DAC on Primary Interface * 2. ADC on Primary Interface/DAC on secondary */ +static struct snd_soc_dai_ops wm8990_dai_ops = { + .hw_params = wm8990_hw_params, + .digital_mute = wm8990_mute, + .set_fmt = wm8990_set_dai_fmt, + .set_clkdiv = wm8990_set_dai_clkdiv, + .set_pll = wm8990_set_dai_pll, + .set_sysclk = wm8990_set_dai_sysclk, +}; + struct snd_soc_dai wm8990_dai = { /* ADC/DAC on primary */ .name = "WM8990 ADC/DAC Primary", @@ -1363,21 +1357,14 @@ struct snd_soc_dai wm8990_dai = { .channels_max = 2, .rates = WM8990_RATES, .formats = WM8990_FORMATS,}, - .ops = { - .hw_params = wm8990_hw_params, - .digital_mute = wm8990_mute, - .set_fmt = wm8990_set_dai_fmt, - .set_clkdiv = wm8990_set_dai_clkdiv, - .set_pll = wm8990_set_dai_pll, - .set_sysclk = wm8990_set_dai_sysclk, - }, + .ops = &wm8990_dai_ops, }; EXPORT_SYMBOL_GPL(wm8990_dai); static int wm8990_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; /* we only need to suspend if we are a valid card */ if (!codec->card) @@ -1390,7 +1377,7 @@ static int wm8990_suspend(struct platform_device *pdev, pm_message_t state) static int wm8990_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int i; u8 data[2]; u16 *cache = codec->reg_cache; @@ -1418,7 +1405,7 @@ static int wm8990_resume(struct platform_device *pdev) */ static int wm8990_init(struct snd_soc_device *socdev) { - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; u16 reg; int ret = 0; @@ -1461,7 +1448,8 @@ static int wm8990_init(struct snd_soc_device *socdev) wm8990_write(codec, WM8990_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8)); wm8990_write(codec, WM8990_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8)); - wm8990_add_controls(codec); + snd_soc_add_controls(codec, wm8990_snd_controls, + ARRAY_SIZE(wm8990_snd_controls)); wm8990_add_widgets(codec); ret = snd_soc_init_card(socdev); if (ret < 0) { @@ -1495,7 +1483,7 @@ static int wm8990_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct snd_soc_device *socdev = wm8990_socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int ret; i2c_set_clientdata(i2c, codec); @@ -1594,7 +1582,7 @@ static int wm8990_probe(struct platform_device *pdev) } codec->private_data = wm8990; - socdev->codec = codec; + socdev->card->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); @@ -1620,7 +1608,7 @@ static int wm8990_probe(struct platform_device *pdev) static int wm8990_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; if (codec->control_data) wm8990_set_bias_level(codec, SND_SOC_BIAS_OFF); diff --git a/sound/soc/codecs/wm9705.c b/sound/soc/codecs/wm9705.c new file mode 100644 index 0000000..3265817 --- /dev/null +++ b/sound/soc/codecs/wm9705.c @@ -0,0 +1,415 @@ +/* + * wm9705.c -- ALSA Soc WM9705 codec support + * + * Copyright 2008 Ian Molton <spyro@f2s.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; Version 2 of the License only. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ac97_codec.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include "wm9705.h" + +/* + * WM9705 register cache + */ +static const u16 wm9705_reg[] = { + 0x6150, 0x8000, 0x8000, 0x8000, /* 0x0 */ + 0x0000, 0x8000, 0x8008, 0x8008, /* 0x8 */ + 0x8808, 0x8808, 0x8808, 0x8808, /* 0x10 */ + 0x8808, 0x0000, 0x8000, 0x0000, /* 0x18 */ + 0x0000, 0x0000, 0x0000, 0x000f, /* 0x20 */ + 0x0605, 0x0000, 0xbb80, 0x0000, /* 0x28 */ + 0x0000, 0xbb80, 0x0000, 0x0000, /* 0x30 */ + 0x0000, 0x2000, 0x0000, 0x0000, /* 0x38 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 0x40 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 0x48 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 0x50 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 0x58 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 0x60 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 0x68 */ + 0x0000, 0x0808, 0x0000, 0x0006, /* 0x70 */ + 0x0000, 0x0000, 0x574d, 0x4c05, /* 0x78 */ +}; + +static const struct snd_kcontrol_new wm9705_snd_ac97_controls[] = { + SOC_DOUBLE("Master Playback Volume", AC97_MASTER, 8, 0, 31, 1), + SOC_SINGLE("Master Playback Switch", AC97_MASTER, 15, 1, 1), + SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1), + SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1), + SOC_DOUBLE("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1), + SOC_SINGLE("PCM Playback Switch", AC97_PCM, 15, 1, 1), + SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1), + SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1), + SOC_SINGLE("PCBeep Playback Volume", AC97_PC_BEEP, 1, 15, 1), + SOC_SINGLE("Phone Playback Volume", AC97_PHONE, 0, 31, 1), + SOC_DOUBLE("Line Playback Volume", AC97_LINE, 8, 0, 31, 1), + SOC_DOUBLE("CD Playback Volume", AC97_CD, 8, 0, 31, 1), + SOC_SINGLE("Mic Playback Volume", AC97_MIC, 0, 31, 1), + SOC_SINGLE("Mic 20dB Boost Switch", AC97_MIC, 6, 1, 0), + SOC_DOUBLE("Capture Volume", AC97_REC_GAIN, 8, 0, 15, 0), + SOC_SINGLE("Capture Switch", AC97_REC_GAIN, 15, 1, 1), +}; + +static const char *wm9705_mic[] = {"Mic 1", "Mic 2"}; +static const char *wm9705_rec_sel[] = {"Mic", "CD", "NC", "NC", + "Line", "Stereo Mix", "Mono Mix", "Phone"}; + +static const struct soc_enum wm9705_enum_mic = + SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 8, 2, wm9705_mic); +static const struct soc_enum wm9705_enum_rec_l = + SOC_ENUM_SINGLE(AC97_REC_SEL, 8, 8, wm9705_rec_sel); +static const struct soc_enum wm9705_enum_rec_r = + SOC_ENUM_SINGLE(AC97_REC_SEL, 0, 8, wm9705_rec_sel); + +/* Headphone Mixer */ +static const struct snd_kcontrol_new wm9705_hp_mixer_controls[] = { + SOC_DAPM_SINGLE("PCBeep Playback Switch", AC97_PC_BEEP, 15, 1, 1), + SOC_DAPM_SINGLE("CD Playback Switch", AC97_CD, 15, 1, 1), + SOC_DAPM_SINGLE("Mic Playback Switch", AC97_MIC, 15, 1, 1), + SOC_DAPM_SINGLE("Phone Playback Switch", AC97_PHONE, 15, 1, 1), + SOC_DAPM_SINGLE("Line Playback Switch", AC97_LINE, 15, 1, 1), +}; + +/* Mic source */ +static const struct snd_kcontrol_new wm9705_mic_src_controls = + SOC_DAPM_ENUM("Route", wm9705_enum_mic); + +/* Capture source */ +static const struct snd_kcontrol_new wm9705_capture_selectl_controls = + SOC_DAPM_ENUM("Route", wm9705_enum_rec_l); +static const struct snd_kcontrol_new wm9705_capture_selectr_controls = + SOC_DAPM_ENUM("Route", wm9705_enum_rec_r); + +/* DAPM widgets */ +static const struct snd_soc_dapm_widget wm9705_dapm_widgets[] = { + SND_SOC_DAPM_MUX("Mic Source", SND_SOC_NOPM, 0, 0, + &wm9705_mic_src_controls), + SND_SOC_DAPM_MUX("Left Capture Source", SND_SOC_NOPM, 0, 0, + &wm9705_capture_selectl_controls), + SND_SOC_DAPM_MUX("Right Capture Source", SND_SOC_NOPM, 0, 0, + &wm9705_capture_selectr_controls), + SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_MIXER_NAMED_CTL("HP Mixer", SND_SOC_NOPM, 0, 0, + &wm9705_hp_mixer_controls[0], + ARRAY_SIZE(wm9705_hp_mixer_controls)), + SND_SOC_DAPM_MIXER("Mono Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_PGA("Headphone PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Speaker PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Line PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Line out PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mono PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Phone PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PCBEEP PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("CD PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("ADC PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("HPOUTL"), + SND_SOC_DAPM_OUTPUT("HPOUTR"), + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("ROUT"), + SND_SOC_DAPM_OUTPUT("MONOOUT"), + SND_SOC_DAPM_INPUT("PHONE"), + SND_SOC_DAPM_INPUT("LINEINL"), + SND_SOC_DAPM_INPUT("LINEINR"), + SND_SOC_DAPM_INPUT("CDINL"), + SND_SOC_DAPM_INPUT("CDINR"), + SND_SOC_DAPM_INPUT("PCBEEP"), + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), +}; + +/* Audio map + * WM9705 has no switches to disable the route from the inputs to the HP mixer + * so in order to prevent active inputs from forcing the audio outputs to be + * constantly enabled, we use the mutes on those inputs to simulate such + * controls. + */ +static const struct snd_soc_dapm_route audio_map[] = { + /* HP mixer */ + {"HP Mixer", "PCBeep Playback Switch", "PCBEEP PGA"}, + {"HP Mixer", "CD Playback Switch", "CD PGA"}, + {"HP Mixer", "Mic Playback Switch", "Mic PGA"}, + {"HP Mixer", "Phone Playback Switch", "Phone PGA"}, + {"HP Mixer", "Line Playback Switch", "Line PGA"}, + {"HP Mixer", NULL, "Left DAC"}, + {"HP Mixer", NULL, "Right DAC"}, + + /* mono mixer */ + {"Mono Mixer", NULL, "HP Mixer"}, + + /* outputs */ + {"Headphone PGA", NULL, "HP Mixer"}, + {"HPOUTL", NULL, "Headphone PGA"}, + {"HPOUTR", NULL, "Headphone PGA"}, + {"Line out PGA", NULL, "HP Mixer"}, + {"LOUT", NULL, "Line out PGA"}, + {"ROUT", NULL, "Line out PGA"}, + {"Mono PGA", NULL, "Mono Mixer"}, + {"MONOOUT", NULL, "Mono PGA"}, + + /* inputs */ + {"CD PGA", NULL, "CDINL"}, + {"CD PGA", NULL, "CDINR"}, + {"Line PGA", NULL, "LINEINL"}, + {"Line PGA", NULL, "LINEINR"}, + {"Phone PGA", NULL, "PHONE"}, + {"Mic Source", "Mic 1", "MIC1"}, + {"Mic Source", "Mic 2", "MIC2"}, + {"Mic PGA", NULL, "Mic Source"}, + {"PCBEEP PGA", NULL, "PCBEEP"}, + + /* Left capture selector */ + {"Left Capture Source", "Mic", "Mic Source"}, + {"Left Capture Source", "CD", "CDINL"}, + {"Left Capture Source", "Line", "LINEINL"}, + {"Left Capture Source", "Stereo Mix", "HP Mixer"}, + {"Left Capture Source", "Mono Mix", "HP Mixer"}, + {"Left Capture Source", "Phone", "PHONE"}, + + /* Right capture source */ + {"Right Capture Source", "Mic", "Mic Source"}, + {"Right Capture Source", "CD", "CDINR"}, + {"Right Capture Source", "Line", "LINEINR"}, + {"Right Capture Source", "Stereo Mix", "HP Mixer"}, + {"Right Capture Source", "Mono Mix", "HP Mixer"}, + {"Right Capture Source", "Phone", "PHONE"}, + + {"ADC PGA", NULL, "Left Capture Source"}, + {"ADC PGA", NULL, "Right Capture Source"}, + + /* ADC's */ + {"Left ADC", NULL, "ADC PGA"}, + {"Right ADC", NULL, "ADC PGA"}, +}; + +static int wm9705_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm9705_dapm_widgets, + ARRAY_SIZE(wm9705_dapm_widgets)); + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + snd_soc_dapm_new_widgets(codec); + + return 0; +} + +/* We use a register cache to enhance read performance. */ +static unsigned int ac97_read(struct snd_soc_codec *codec, unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + switch (reg) { + case AC97_RESET: + case AC97_VENDOR_ID1: + case AC97_VENDOR_ID2: + return soc_ac97_ops.read(codec->ac97, reg); + default: + reg = reg >> 1; + + if (reg >= (ARRAY_SIZE(wm9705_reg))) + return -EIO; + + return cache[reg]; + } +} + +static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + u16 *cache = codec->reg_cache; + + soc_ac97_ops.write(codec->ac97, reg, val); + reg = reg >> 1; + if (reg < (ARRAY_SIZE(wm9705_reg))) + cache[reg] = val; + + return 0; +} + +static int ac97_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + int reg; + u16 vra; + + vra = ac97_read(codec, AC97_EXTENDED_STATUS); + ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg = AC97_PCM_FRONT_DAC_RATE; + else + reg = AC97_PCM_LR_ADC_RATE; + + return ac97_write(codec, reg, runtime->rate); +} + +#define WM9705_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +static struct snd_soc_dai_ops wm9705_dai_ops = { + .prepare = ac97_prepare, +}; + +struct snd_soc_dai wm9705_dai[] = { + { + .name = "AC97 HiFi", + .ac97_control = 1, + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM9705_AC97_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM9705_AC97_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &wm9705_dai_ops, + }, + { + .name = "AC97 Aux", + .playback = { + .stream_name = "Aux Playback", + .channels_min = 1, + .channels_max = 1, + .rates = WM9705_AC97_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + } +}; +EXPORT_SYMBOL_GPL(wm9705_dai); + +static int wm9705_reset(struct snd_soc_codec *codec) +{ + if (soc_ac97_ops.reset) { + soc_ac97_ops.reset(codec->ac97); + if (ac97_read(codec, 0) == wm9705_reg[0]) + return 0; /* Success */ + } + + return -EIO; +} + +static int wm9705_soc_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + printk(KERN_INFO "WM9705 SoC Audio Codec\n"); + + socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), + GFP_KERNEL); + if (socdev->card->codec == NULL) + return -ENOMEM; + codec = socdev->card->codec; + mutex_init(&codec->mutex); + + codec->reg_cache = kmemdup(wm9705_reg, sizeof(wm9705_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) { + ret = -ENOMEM; + goto cache_err; + } + codec->reg_cache_size = sizeof(wm9705_reg); + codec->reg_cache_step = 2; + + codec->name = "WM9705"; + codec->owner = THIS_MODULE; + codec->dai = wm9705_dai; + codec->num_dai = ARRAY_SIZE(wm9705_dai); + codec->write = ac97_write; + codec->read = ac97_read; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0); + if (ret < 0) { + printk(KERN_ERR "wm9705: failed to register AC97 codec\n"); + goto codec_err; + } + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) + goto pcm_err; + + ret = wm9705_reset(codec); + if (ret) + goto reset_err; + + snd_soc_add_controls(codec, wm9705_snd_ac97_controls, + ARRAY_SIZE(wm9705_snd_ac97_controls)); + wm9705_add_widgets(codec); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm9705: failed to register card\n"); + goto pcm_err; + } + + return 0; + +reset_err: + snd_soc_free_pcms(socdev); +pcm_err: + snd_soc_free_ac97_codec(codec); +codec_err: + kfree(codec->reg_cache); +cache_err: + kfree(socdev->card->codec); + socdev->card->codec = NULL; + return ret; +} + +static int wm9705_soc_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + if (codec == NULL) + return 0; + + snd_soc_dapm_free(socdev); + snd_soc_free_pcms(socdev); + snd_soc_free_ac97_codec(codec); + kfree(codec->reg_cache); + kfree(codec); + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm9705 = { + .probe = wm9705_soc_probe, + .remove = wm9705_soc_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm9705); + +MODULE_DESCRIPTION("ASoC WM9705 driver"); +MODULE_AUTHOR("Ian Molton"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/wm9705.h b/sound/soc/codecs/wm9705.h new file mode 100644 index 0000000..d380f11 --- /dev/null +++ b/sound/soc/codecs/wm9705.h @@ -0,0 +1,14 @@ +/* + * wm9705.h -- WM9705 Soc Audio driver + */ + +#ifndef _WM9705_H +#define _WM9705_H + +#define WM9705_DAI_AC97_HIFI 0 +#define WM9705_DAI_AC97_AUX 1 + +extern struct snd_soc_dai wm9705_dai[2]; +extern struct snd_soc_codec_device soc_codec_dev_wm9705; + +#endif diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c index af83d62..765cf1e 100644 --- a/sound/soc/codecs/wm9712.c +++ b/sound/soc/codecs/wm9712.c @@ -154,21 +154,6 @@ SOC_SINGLE("Mic 2 Volume", AC97_MIC, 0, 31, 1), SOC_SINGLE("Mic 20dB Boost Switch", AC97_MIC, 7, 1, 0), }; -/* add non dapm controls */ -static int wm9712_add_controls(struct snd_soc_codec *codec) -{ - int err, i; - - for (i = 0; i < ARRAY_SIZE(wm9712_snd_ac97_controls); i++) { - err = snd_ctl_add(codec->card, - snd_soc_cnew(&wm9712_snd_ac97_controls[i], - codec, NULL)); - if (err < 0) - return err; - } - return 0; -} - /* We have to create a fake left and right HP mixers because * the codec only has a single control that is shared by both channels. * This makes it impossible to determine the audio path. @@ -467,7 +452,7 @@ static unsigned int ac97_read(struct snd_soc_codec *codec, else { reg = reg >> 1; - if (reg > (ARRAY_SIZE(wm9712_reg))) + if (reg >= (ARRAY_SIZE(wm9712_reg))) return -EIO; return cache[reg]; @@ -481,7 +466,7 @@ static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, soc_ac97_ops.write(codec->ac97, reg, val); reg = reg >> 1; - if (reg <= (ARRAY_SIZE(wm9712_reg))) + if (reg < (ARRAY_SIZE(wm9712_reg))) cache[reg] = val; return 0; @@ -493,7 +478,7 @@ static int ac97_prepare(struct snd_pcm_substream *substream, struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int reg; u16 vra; @@ -514,7 +499,7 @@ static int ac97_aux_prepare(struct snd_pcm_substream *substream, struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; u16 vra, xsle; vra = ac97_read(codec, AC97_EXTENDED_STATUS); @@ -532,6 +517,14 @@ static int ac97_aux_prepare(struct snd_pcm_substream *substream, SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\ SNDRV_PCM_RATE_48000) +static struct snd_soc_dai_ops wm9712_dai_ops_hifi = { + .prepare = ac97_prepare, +}; + +static struct snd_soc_dai_ops wm9712_dai_ops_aux = { + .prepare = ac97_aux_prepare, +}; + struct snd_soc_dai wm9712_dai[] = { { .name = "AC97 HiFi", @@ -548,8 +541,7 @@ struct snd_soc_dai wm9712_dai[] = { .channels_max = 2, .rates = WM9712_AC97_RATES, .formats = SNDRV_PCM_FMTBIT_S16_LE,}, - .ops = { - .prepare = ac97_prepare,}, + .ops = &wm9712_dai_ops_hifi, }, { .name = "AC97 Aux", @@ -559,8 +551,7 @@ struct snd_soc_dai wm9712_dai[] = { .channels_max = 1, .rates = WM9712_AC97_RATES, .formats = SNDRV_PCM_FMTBIT_S16_LE,}, - .ops = { - .prepare = ac97_aux_prepare,}, + .ops = &wm9712_dai_ops_aux, } }; EXPORT_SYMBOL_GPL(wm9712_dai); @@ -607,7 +598,7 @@ static int wm9712_soc_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; wm9712_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; @@ -616,7 +607,7 @@ static int wm9712_soc_suspend(struct platform_device *pdev, static int wm9712_soc_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; int i, ret; u16 *cache = codec->reg_cache; @@ -652,10 +643,11 @@ static int wm9712_soc_probe(struct platform_device *pdev) printk(KERN_INFO "WM9711/WM9712 SoC Audio Codec %s\n", WM9712_VERSION); - socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); - if (socdev->codec == NULL) + socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), + GFP_KERNEL); + if (socdev->card->codec == NULL) return -ENOMEM; - codec = socdev->codec; + codec = socdev->card->codec; mutex_init(&codec->mutex); codec->reg_cache = kmemdup(wm9712_reg, sizeof(wm9712_reg), GFP_KERNEL); @@ -698,7 +690,8 @@ static int wm9712_soc_probe(struct platform_device *pdev) ac97_write(codec, AC97_VIDEO, ac97_read(codec, AC97_VIDEO) | 0x3000); wm9712_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - wm9712_add_controls(codec); + snd_soc_add_controls(codec, wm9712_snd_ac97_controls, + ARRAY_SIZE(wm9712_snd_ac97_controls)); wm9712_add_widgets(codec); ret = snd_soc_init_card(socdev); if (ret < 0) { @@ -718,15 +711,15 @@ codec_err: kfree(codec->reg_cache); cache_err: - kfree(socdev->codec); - socdev->codec = NULL; + kfree(socdev->card->codec); + socdev->card->codec = NULL; return ret; } static int wm9712_soc_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; if (codec == NULL) return 0; diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c index f3ca8aa..523bad0 100644 --- a/sound/soc/codecs/wm9713.c +++ b/sound/soc/codecs/wm9713.c @@ -32,7 +32,6 @@ struct wm9713_priv { u32 pll_in; /* PLL input frequency */ - u32 pll_out; /* PLL output frequency */ }; static unsigned int ac97_read(struct snd_soc_codec *codec, @@ -190,21 +189,6 @@ SOC_SINGLE("3D Lower Cut-off Switch", AC97_REC_GAIN_MIC, 4, 1, 0), SOC_SINGLE("3D Depth", AC97_REC_GAIN_MIC, 0, 15, 1), }; -/* add non dapm controls */ -static int wm9713_add_controls(struct snd_soc_codec *codec) -{ - int err, i; - - for (i = 0; i < ARRAY_SIZE(wm9713_snd_ac97_controls); i++) { - err = snd_ctl_add(codec->card, - snd_soc_cnew(&wm9713_snd_ac97_controls[i], - codec, NULL)); - if (err < 0) - return err; - } - return 0; -} - /* We have to create a fake left and right HP mixers because * the codec only has a single control that is shared by both channels. * This makes it impossible to determine the audio path using the current @@ -636,7 +620,7 @@ static unsigned int ac97_read(struct snd_soc_codec *codec, else { reg = reg >> 1; - if (reg > (ARRAY_SIZE(wm9713_reg))) + if (reg >= (ARRAY_SIZE(wm9713_reg))) return -EIO; return cache[reg]; @@ -650,7 +634,7 @@ static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, if (reg < 0x7c) soc_ac97_ops.write(codec->ac97, reg, val); reg = reg >> 1; - if (reg <= (ARRAY_SIZE(wm9713_reg))) + if (reg < (ARRAY_SIZE(wm9713_reg))) cache[reg] = val; return 0; @@ -738,13 +722,13 @@ static int wm9713_set_pll(struct snd_soc_codec *codec, struct _pll_div pll_div; /* turn PLL off ? */ - if (freq_in == 0 || freq_out == 0) { + if (freq_in == 0) { /* disable PLL power and select ext source */ reg = ac97_read(codec, AC97_HANDSET_RATE); ac97_write(codec, AC97_HANDSET_RATE, reg | 0x0080); reg = ac97_read(codec, AC97_EXTENDED_MID); ac97_write(codec, AC97_EXTENDED_MID, reg | 0x0200); - wm9713->pll_out = 0; + wm9713->pll_in = 0; return 0; } @@ -788,7 +772,6 @@ static int wm9713_set_pll(struct snd_soc_codec *codec, ac97_write(codec, AC97_EXTENDED_MID, reg & 0xfdff); reg = ac97_read(codec, AC97_HANDSET_RATE); ac97_write(codec, AC97_HANDSET_RATE, reg & 0xff7f); - wm9713->pll_out = freq_out; wm9713->pll_in = freq_in; /* wait 10ms AC97 link frames for the link to stabilise */ @@ -957,13 +940,14 @@ static void wm9713_voiceshutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; - u16 status; + u16 status, rate; /* Gracefully shut down the voice interface. */ status = ac97_read(codec, AC97_EXTENDED_STATUS) | 0x1000; - ac97_write(codec, AC97_HANDSET_RATE, 0x0280); + rate = ac97_read(codec, AC97_HANDSET_RATE) & 0xF0FF; + ac97_write(codec, AC97_HANDSET_RATE, rate | 0x0200); schedule_timeout_interruptible(msecs_to_jiffies(1)); - ac97_write(codec, AC97_HANDSET_RATE, 0x0F80); + ac97_write(codec, AC97_HANDSET_RATE, rate | 0x0F00); ac97_write(codec, AC97_EXTENDED_MID, status); } @@ -1021,6 +1005,27 @@ static int ac97_aux_prepare(struct snd_pcm_substream *substream, (SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \ SNDRV_PCM_FORMAT_S24_LE) +static struct snd_soc_dai_ops wm9713_dai_ops_hifi = { + .prepare = ac97_hifi_prepare, + .set_clkdiv = wm9713_set_dai_clkdiv, + .set_pll = wm9713_set_dai_pll, +}; + +static struct snd_soc_dai_ops wm9713_dai_ops_aux = { + .prepare = ac97_aux_prepare, + .set_clkdiv = wm9713_set_dai_clkdiv, + .set_pll = wm9713_set_dai_pll, +}; + +static struct snd_soc_dai_ops wm9713_dai_ops_voice = { + .hw_params = wm9713_pcm_hw_params, + .shutdown = wm9713_voiceshutdown, + .set_clkdiv = wm9713_set_dai_clkdiv, + .set_pll = wm9713_set_dai_pll, + .set_fmt = wm9713_set_dai_fmt, + .set_tristate = wm9713_set_dai_tristate, +}; + struct snd_soc_dai wm9713_dai[] = { { .name = "AC97 HiFi", @@ -1037,10 +1042,7 @@ struct snd_soc_dai wm9713_dai[] = { .channels_max = 2, .rates = WM9713_RATES, .formats = SNDRV_PCM_FMTBIT_S16_LE,}, - .ops = { - .prepare = ac97_hifi_prepare, - .set_clkdiv = wm9713_set_dai_clkdiv, - .set_pll = wm9713_set_dai_pll,}, + .ops = &wm9713_dai_ops_hifi, }, { .name = "AC97 Aux", @@ -1050,10 +1052,7 @@ struct snd_soc_dai wm9713_dai[] = { .channels_max = 1, .rates = WM9713_RATES, .formats = SNDRV_PCM_FMTBIT_S16_LE,}, - .ops = { - .prepare = ac97_aux_prepare, - .set_clkdiv = wm9713_set_dai_clkdiv, - .set_pll = wm9713_set_dai_pll,}, + .ops = &wm9713_dai_ops_aux, }, { .name = "WM9713 Voice", @@ -1069,14 +1068,7 @@ struct snd_soc_dai wm9713_dai[] = { .channels_max = 2, .rates = WM9713_PCM_RATES, .formats = WM9713_PCM_FORMATS,}, - .ops = { - .hw_params = wm9713_pcm_hw_params, - .shutdown = wm9713_voiceshutdown, - .set_clkdiv = wm9713_set_dai_clkdiv, - .set_pll = wm9713_set_dai_pll, - .set_fmt = wm9713_set_dai_fmt, - .set_tristate = wm9713_set_dai_tristate, - }, + .ops = &wm9713_dai_ops_voice, }, }; EXPORT_SYMBOL_GPL(wm9713_dai); @@ -1132,7 +1124,7 @@ static int wm9713_soc_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; u16 reg; /* Disable everything except touchpanel - that will be handled @@ -1150,7 +1142,7 @@ static int wm9713_soc_suspend(struct platform_device *pdev, static int wm9713_soc_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; struct wm9713_priv *wm9713 = codec->private_data; int i, ret; u16 *cache = codec->reg_cache; @@ -1164,8 +1156,8 @@ static int wm9713_soc_resume(struct platform_device *pdev) wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* do we need to re-start the PLL ? */ - if (wm9713->pll_out) - wm9713_set_pll(codec, 0, wm9713->pll_in, wm9713->pll_out); + if (wm9713->pll_in) + wm9713_set_pll(codec, 0, wm9713->pll_in, 0); /* only synchronise the codec if warm reset failed */ if (ret == 0) { @@ -1191,10 +1183,11 @@ static int wm9713_soc_probe(struct platform_device *pdev) printk(KERN_INFO "WM9713/WM9714 SoC Audio Codec %s\n", WM9713_VERSION); - socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); - if (socdev->codec == NULL) + socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), + GFP_KERNEL); + if (socdev->card->codec == NULL) return -ENOMEM; - codec = socdev->codec; + codec = socdev->card->codec; mutex_init(&codec->mutex); codec->reg_cache = kmemdup(wm9713_reg, sizeof(wm9713_reg), GFP_KERNEL); @@ -1245,7 +1238,8 @@ static int wm9713_soc_probe(struct platform_device *pdev) reg = ac97_read(codec, AC97_CD) & 0x7fff; ac97_write(codec, AC97_CD, reg); - wm9713_add_controls(codec); + snd_soc_add_controls(codec, wm9713_snd_ac97_controls, + ARRAY_SIZE(wm9713_snd_ac97_controls)); wm9713_add_widgets(codec); ret = snd_soc_init_card(socdev); if (ret < 0) @@ -1265,15 +1259,15 @@ priv_err: kfree(codec->reg_cache); cache_err: - kfree(socdev->codec); - socdev->codec = NULL; + kfree(socdev->card->codec); + socdev->card->codec = NULL; return ret; } static int wm9713_soc_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = socdev->card->codec; if (codec == NULL) return 0; |