aboutsummaryrefslogtreecommitdiff
path: root/sound/soc/codecs
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/codecs')
-rw-r--r--sound/soc/codecs/Kconfig23
-rw-r--r--sound/soc/codecs/Makefile7
-rw-r--r--sound/soc/codecs/ac97.c29
-rw-r--r--sound/soc/codecs/ad1980.c33
-rw-r--r--sound/soc/codecs/ad73311.c8
-rw-r--r--sound/soc/codecs/ad73311.h2
-rw-r--r--sound/soc/codecs/ak4104.c365
-rw-r--r--sound/soc/codecs/ak4104.h7
-rw-r--r--sound/soc/codecs/ak4535.c46
-rw-r--r--sound/soc/codecs/cs4270.c667
-rw-r--r--sound/soc/codecs/pcm3008.c12
-rw-r--r--sound/soc/codecs/ssm2602.c58
-rw-r--r--sound/soc/codecs/tlv320aic23.c57
-rw-r--r--sound/soc/codecs/tlv320aic26.c29
-rw-r--r--sound/soc/codecs/tlv320aic3x.c172
-rw-r--r--sound/soc/codecs/twl4030.c581
-rw-r--r--sound/soc/codecs/twl4030.h16
-rw-r--r--sound/soc/codecs/uda134x.c84
-rw-r--r--sound/soc/codecs/uda1380.c241
-rw-r--r--sound/soc/codecs/wm8350.c168
-rw-r--r--sound/soc/codecs/wm8350.h8
-rw-r--r--sound/soc/codecs/wm8400.c1582
-rw-r--r--sound/soc/codecs/wm8400.h62
-rw-r--r--sound/soc/codecs/wm8510.c55
-rw-r--r--sound/soc/codecs/wm8580.c381
-rw-r--r--sound/soc/codecs/wm8580.h5
-rw-r--r--sound/soc/codecs/wm8728.c50
-rw-r--r--sound/soc/codecs/wm8731.c432
-rw-r--r--sound/soc/codecs/wm8731.h6
-rw-r--r--sound/soc/codecs/wm8750.c48
-rw-r--r--sound/soc/codecs/wm8753.c551
-rw-r--r--sound/soc/codecs/wm8753.h6
-rw-r--r--sound/soc/codecs/wm8900.c51
-rw-r--r--sound/soc/codecs/wm8903.c60
-rw-r--r--sound/soc/codecs/wm8971.c46
-rw-r--r--sound/soc/codecs/wm8990.c61
-rw-r--r--sound/soc/codecs/wm9705.c452
-rw-r--r--sound/soc/codecs/wm9705.h14
-rw-r--r--sound/soc/codecs/wm9712.c57
-rw-r--r--sound/soc/codecs/wm9713.c96
40 files changed, 4682 insertions, 1946 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index d0e0d691ae5..b6c7f7a01cb 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 c4ddc9aa2bb..030d2454725 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 fb53e6511af..b0d4af145b8 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 73fdbb4d4a3..ddb3b08ac23 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 b09289a1e55..e61dac5e7b8 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 507ce0c30ed..569573d2d4d 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 00000000000..4d47bc4f742
--- /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 00000000000..eb88fe7e4de
--- /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 81300d8d42c..1f63d387a2f 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 f1aa0c34421..7fa09a38762 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 9a3e67e5319..5cda9e6b5a7 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 cac37361676..87f606c7682 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 cfdea007c4c..c3f4afb5d01 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 29f2f1a017f..3387d9e736e 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 b47a749c5ea..ab099f48248 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"
@@ -165,10 +166,13 @@ static int snd_soc_dapm_put_volsw_aic3x(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
- int reg = kcontrol->private_value & 0xff;
- int shift = (kcontrol->private_value >> 8) & 0x0f;
- int mask = (kcontrol->private_value >> 16) & 0xff;
- int invert = (kcontrol->private_value >> 24) & 0x01;
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ unsigned int reg = mc->reg;
+ unsigned int shift = mc->shift;
+ int max = mc->max;
+ unsigned int mask = (1 << fls(max)) - 1;
+ unsigned int invert = mc->invert;
unsigned short val, val_mask;
int ret;
struct snd_soc_dapm_path *path;
@@ -247,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
@@ -305,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]);
@@ -743,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;
@@ -1069,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 = {
@@ -1083,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);
@@ -1105,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;
@@ -1128,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;
@@ -1224,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) {
@@ -1258,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);
@@ -1363,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);
@@ -1389,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 ea370a4f86d..bfda7a88e82 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,16 @@ 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;
+
+ struct snd_pcm_substream *master_substream;
+ struct snd_pcm_substream *slave_substream;
+};
+
/*
* read twl4030 register cache
*/
@@ -125,6 +135,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 +164,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 +191,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 +199,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 +491,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 +580,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 +847,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 +912,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 +944,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 +962,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 +1003,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 +1044,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 +1063,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 +1133,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 +1157,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 +1188,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);
@@ -988,15 +1220,63 @@ static int twl4030_set_bias_level(struct snd_soc_codec *codec,
return 0;
}
+static int twl4030_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 twl4030_priv *twl4030 = codec->private_data;
+
+ /* If we already have a playback or capture going then constrain
+ * this substream to match it.
+ */
+ if (twl4030->master_substream) {
+ struct snd_pcm_runtime *master_runtime;
+ master_runtime = twl4030->master_substream->runtime;
+
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ master_runtime->rate,
+ master_runtime->rate);
+
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ master_runtime->sample_bits,
+ master_runtime->sample_bits);
+
+ twl4030->slave_substream = substream;
+ } else
+ twl4030->master_substream = substream;
+
+ return 0;
+}
+
+static void twl4030_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 twl4030_priv *twl4030 = codec->private_data;
+
+ if (twl4030->master_substream == substream)
+ twl4030->master_substream = twl4030->slave_substream;
+
+ twl4030->slave_substream = NULL;
+}
+
static int twl4030_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->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct twl4030_priv *twl4030 = codec->private_data;
u8 mode, old_mode, format, old_format;
+ if (substream == twl4030->slave_substream)
+ /* Ignoring hw_params for slave substream */
+ return 0;
/* bit rate */
old_mode = twl4030_read_reg_cache(codec,
@@ -1031,6 +1311,9 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream,
case 48000:
mode |= TWL4030_APLL_RATE_48000;
break;
+ case 96000:
+ mode |= TWL4030_APLL_RATE_96000;
+ break;
default:
printk(KERN_ERR "TWL4030 hw params: unknown rate %d\n",
params_rate(params));
@@ -1039,8 +1322,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 +1347,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 +1423,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,13 +1438,21 @@ 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 = {
+ .startup = twl4030_startup,
+ .shutdown = twl4030_shutdown,
+ .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 = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 2,
- .rates = TWL4030_RATES,
+ .rates = TWL4030_RATES | SNDRV_PCM_RATE_96000,
.formats = TWL4030_FORMATS,},
.capture = {
.stream_name = "Capture",
@@ -1168,18 +1460,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 +1477,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 +1491,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 +1521,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 +1547,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 +1574,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 442e5a82861..cb63765db1d 100644
--- a/sound/soc/codecs/twl4030.h
+++ b/sound/soc/codecs/twl4030.h
@@ -109,6 +109,7 @@
#define TWL4030_APLL_RATE_32000 0x80
#define TWL4030_APLL_RATE_44100 0x90
#define TWL4030_APLL_RATE_48000 0xA0
+#define TWL4030_APLL_RATE_96000 0xE0
#define TWL4030_SEL_16K 0x04
#define TWL4030_CODECPDZ 0x02
#define TWL4030_OPT_MODE 0x01
@@ -170,6 +171,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 +202,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 a2c5064a774..ddefb8f8014 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 e6bf0844fbf..5b21594e0e5 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 e3989d406f5..3b1d0993bed 100644
--- a/sound/soc/codecs/wm8350.c
+++ b/sound/soc/codecs/wm8350.c
@@ -3,7 +3,7 @@
*
* Copyright (C) 2007, 2008 Wolfson Microelectronics PLC.
*
- * Author: Liam Girdwood <lg@opensource.wolfsonmicro.com>
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
@@ -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 cc2887aa6c3..d11bd9288cf 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 00000000000..510efa60400
--- /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 00000000000..79c5934d477
--- /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 40f8238df71..6a4cea09c45 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 d004e584529..442ea6f160f 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 09e4422f6f2..0dfb5ddde6a 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 80b11983e13..e7ff2121ede 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 c444b9f2701..e043e3f6000 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 95190e9c0c1..cd7b806e8ad 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 5997fa68e0d..b64509b01a4 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 6c21b50c937..a6e8f3f7f05 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",
@@ -1451,7 +1446,14 @@ static const struct snd_soc_dai wm8753_all_dai[] = {
},
};
-struct snd_soc_dai wm8753_dai[2];
+struct snd_soc_dai wm8753_dai[] = {
+ {
+ .name = "WM8753 DAI 0",
+ },
+ {
+ .name = "WM8753 DAI 1",
+ },
+};
EXPORT_SYMBOL_GPL(wm8753_dai);
static void wm8753_set_dai_mode(struct snd_soc_codec *codec, unsigned int mode)
@@ -1459,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;
@@ -1498,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)
@@ -1511,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;
@@ -1524,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);
@@ -1542,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_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)
{
- struct snd_soc_codec *codec = socdev->codec;
- int reg, ret = 0;
+ 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);
@@ -1603,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[] = {
@@ -1666,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;
@@ -1769,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 f55704ce931..57b2ba24404 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 6767de10ded..46c5ea1ff92 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 bde74546db4..8cf571f1a80 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 88ead7f8dd9..032dca22dbd 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 5b5afc14447..c518c3e5aa3 100644
--- a/sound/soc/codecs/wm8990.c
+++ b/sound/soc/codecs/wm8990.c
@@ -2,8 +2,7 @@
* wm8990.c -- WM8990 ALSA Soc Audio driver
*
* Copyright 2008 Wolfson Microelectronics PLC.
- * Author: Liam Girdwood
- * lg@opensource.wolfsonmicro.com or linux@wolfsonmicro.com
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
*
* 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
@@ -116,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];
}
@@ -129,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;
@@ -177,7 +176,9 @@ static int wm899x_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
- int reg = kcontrol->private_value & 0xff;
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int reg = mc->reg;
int ret;
u16 val;
@@ -417,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
*/
@@ -1177,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;
@@ -1346,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",
@@ -1362,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)
@@ -1389,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;
@@ -1417,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;
@@ -1460,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) {
@@ -1494,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);
@@ -1593,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);
@@ -1619,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 00000000000..6e23a81dba7
--- /dev/null
+++ b/sound/soc/codecs/wm9705.c
@@ -0,0 +1,452 @@
+/*
+ * 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;
+}
+
+#ifdef CONFIG_PM
+static int wm9705_soc_suspend(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ soc_ac97_ops.write(codec->ac97, AC97_POWERDOWN, 0xffff);
+
+ return 0;
+}
+
+static int wm9705_soc_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+ int i, ret;
+ u16 *cache = codec->reg_cache;
+
+ ret = wm9705_reset(codec);
+ if (ret < 0) {
+ printk(KERN_ERR "could not reset AC97 codec\n");
+ return ret;
+ }
+
+ for (i = 2; i < ARRAY_SIZE(wm9705_reg) << 1; i += 2) {
+ soc_ac97_ops.write(codec->ac97, i, cache[i>>1]);
+ }
+
+ return 0;
+}
+#else
+#define wm9705_soc_suspend NULL
+#define wm9705_soc_resume NULL
+#endif
+
+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,
+ .suspend = wm9705_soc_suspend,
+ .resume = wm9705_soc_resume,
+};
+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 00000000000..d380f110f9e
--- /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 af83d629078..765cf1e7369 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 f3ca8aaf013..523bad077fa 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;