aboutsummaryrefslogtreecommitdiff
path: root/sound/soc/codecs
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/codecs')
-rw-r--r--sound/soc/codecs/Kconfig24
-rw-r--r--sound/soc/codecs/Makefile12
-rw-r--r--sound/soc/codecs/ac97.c4
-rw-r--r--sound/soc/codecs/ad1980.c4
-rw-r--r--sound/soc/codecs/cs4270.c105
-rw-r--r--sound/soc/codecs/spdif_transciever.c71
-rw-r--r--sound/soc/codecs/spdif_transciever.h17
-rw-r--r--sound/soc/codecs/ssm2602.c33
-rw-r--r--sound/soc/codecs/stac9766.c463
-rw-r--r--sound/soc/codecs/stac9766.h21
-rw-r--r--sound/soc/codecs/tlv320aic23.c16
-rw-r--r--sound/soc/codecs/twl4030.c1116
-rw-r--r--sound/soc/codecs/twl4030.h43
-rw-r--r--sound/soc/codecs/uda134x.c4
-rw-r--r--sound/soc/codecs/wm8350.c2
-rw-r--r--sound/soc/codecs/wm8350.h1
-rw-r--r--sound/soc/codecs/wm8400.c8
-rw-r--r--sound/soc/codecs/wm8510.c2
-rw-r--r--sound/soc/codecs/wm8580.c4
-rw-r--r--sound/soc/codecs/wm8731.c4
-rw-r--r--sound/soc/codecs/wm8753.c6
-rw-r--r--sound/soc/codecs/wm8900.c6
-rw-r--r--sound/soc/codecs/wm8903.c119
-rw-r--r--sound/soc/codecs/wm8940.c955
-rw-r--r--sound/soc/codecs/wm8940.h104
-rw-r--r--sound/soc/codecs/wm8960.c969
-rw-r--r--sound/soc/codecs/wm8960.h127
-rw-r--r--sound/soc/codecs/wm8988.c1097
-rw-r--r--sound/soc/codecs/wm8988.h60
-rw-r--r--sound/soc/codecs/wm8990.c2
-rw-r--r--sound/soc/codecs/wm9081.c1534
-rw-r--r--sound/soc/codecs/wm9081.h787
-rw-r--r--sound/soc/codecs/wm9705.c4
-rw-r--r--sound/soc/codecs/wm9712.c8
-rw-r--r--sound/soc/codecs/wm9713.c48
35 files changed, 7392 insertions, 388 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index b6c7f7a01cb..bbc97fd7664 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -18,7 +18,9 @@ config SND_SOC_ALL_CODECS
select SND_SOC_AK4535 if I2C
select SND_SOC_CS4270 if I2C
select SND_SOC_PCM3008
+ select SND_SOC_SPDIF
select SND_SOC_SSM2602 if I2C
+ select SND_SOC_STAC9766 if SND_SOC_AC97_BUS
select SND_SOC_TLV320AIC23 if I2C
select SND_SOC_TLV320AIC26 if SPI_MASTER
select SND_SOC_TLV320AIC3X if I2C
@@ -35,8 +37,12 @@ config SND_SOC_ALL_CODECS
select SND_SOC_WM8753 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8900 if I2C
select SND_SOC_WM8903 if I2C
+ select SND_SOC_WM8940 if I2C
+ select SND_SOC_WM8960 if I2C
select SND_SOC_WM8971 if I2C
+ select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8990 if I2C
+ select SND_SOC_WM9081 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
@@ -86,9 +92,15 @@ config SND_SOC_L3
config SND_SOC_PCM3008
tristate
+config SND_SOC_SPDIF
+ tristate
+
config SND_SOC_SSM2602
tristate
+config SND_SOC_STAC9766
+ tristate
+
config SND_SOC_TLV320AIC23
tristate
@@ -138,12 +150,24 @@ config SND_SOC_WM8900
config SND_SOC_WM8903
tristate
+config SND_SOC_WM8940
+ tristate
+
+config SND_SOC_WM8960
+ tristate
+
config SND_SOC_WM8971
tristate
+config SND_SOC_WM8988
+ tristate
+
config SND_SOC_WM8990
tristate
+config SND_SOC_WM9081
+ tristate
+
config SND_SOC_WM9705
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index f2653803ede..8b7530546f4 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -6,7 +6,9 @@ snd-soc-ak4535-objs := ak4535.o
snd-soc-cs4270-objs := cs4270.o
snd-soc-l3-objs := l3.o
snd-soc-pcm3008-objs := pcm3008.o
+snd-soc-spdif-objs := spdif_transciever.o
snd-soc-ssm2602-objs := ssm2602.o
+snd-soc-stac9766-objs := stac9766.o
snd-soc-tlv320aic23-objs := tlv320aic23.o
snd-soc-tlv320aic26-objs := tlv320aic26.o
snd-soc-tlv320aic3x-objs := tlv320aic3x.o
@@ -23,8 +25,12 @@ snd-soc-wm8750-objs := wm8750.o
snd-soc-wm8753-objs := wm8753.o
snd-soc-wm8900-objs := wm8900.o
snd-soc-wm8903-objs := wm8903.o
+snd-soc-wm8940-objs := wm8940.o
+snd-soc-wm8960-objs := wm8960.o
snd-soc-wm8971-objs := wm8971.o
+snd-soc-wm8988-objs := wm8988.o
snd-soc-wm8990-objs := wm8990.o
+snd-soc-wm9081-objs := wm9081.o
snd-soc-wm9705-objs := wm9705.o
snd-soc-wm9712-objs := wm9712.o
snd-soc-wm9713-objs := wm9713.o
@@ -37,7 +43,9 @@ 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
obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o
+obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o
obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o
+obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o
obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o
obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o
obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o
@@ -55,7 +63,11 @@ obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o
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_WM8940) += snd-soc-wm8940.o
+obj-$(CONFIG_SND_SOC_WM8960) += snd-soc-wm8960.o
+obj-$(CONFIG_SND_SOC_WM8988) += snd-soc-wm8988.o
obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o
+obj-$(CONFIG_SND_SOC_WM9081) += snd-soc-wm9081.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 b0d4af145b8..932299bb5d1 100644
--- a/sound/soc/codecs/ac97.c
+++ b/sound/soc/codecs/ac97.c
@@ -53,13 +53,13 @@ struct snd_soc_dai ac97_dai = {
.channels_min = 1,
.channels_max = 2,
.rates = STD_AC97_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .formats = SND_SOC_STD_AC97_FMTS,},
.capture = {
.stream_name = "AC97 Capture",
.channels_min = 1,
.channels_max = 2,
.rates = STD_AC97_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .formats = SND_SOC_STD_AC97_FMTS,},
.ops = &ac97_dai_ops,
};
EXPORT_SYMBOL_GPL(ac97_dai);
diff --git a/sound/soc/codecs/ad1980.c b/sound/soc/codecs/ad1980.c
index ddb3b08ac23..d7440a982d2 100644
--- a/sound/soc/codecs/ad1980.c
+++ b/sound/soc/codecs/ad1980.c
@@ -137,13 +137,13 @@ struct snd_soc_dai ad1980_dai = {
.channels_min = 2,
.channels_max = 6,
.rates = SNDRV_PCM_RATE_48000,
- .formats = SNDRV_PCM_FMTBIT_S16_LE, },
+ .formats = SND_SOC_STD_AC97_FMTS, },
.capture = {
.stream_name = "Capture",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_48000,
- .formats = SNDRV_PCM_FMTBIT_S16_LE, },
+ .formats = SND_SOC_STD_AC97_FMTS, },
};
EXPORT_SYMBOL_GPL(ad1980_dai);
diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c
index 7fa09a38762..a32b8226c8a 100644
--- a/sound/soc/codecs/cs4270.c
+++ b/sound/soc/codecs/cs4270.c
@@ -18,7 +18,7 @@
* - 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
+ * - Power management is supported
*/
#include <linux/module.h>
@@ -27,6 +27,7 @@
#include <sound/soc.h>
#include <sound/initval.h>
#include <linux/i2c.h>
+#include <linux/delay.h>
#include "cs4270.h"
@@ -56,6 +57,7 @@
#define CS4270_FIRSTREG 0x01
#define CS4270_LASTREG 0x08
#define CS4270_NUMREGS (CS4270_LASTREG - CS4270_FIRSTREG + 1)
+#define CS4270_I2C_INCR 0x80
/* Bit masks for the CS4270 registers */
#define CS4270_CHIPID_ID 0xF0
@@ -64,6 +66,8 @@
#define CS4270_PWRCTL_PDN_ADC 0x20
#define CS4270_PWRCTL_PDN_DAC 0x02
#define CS4270_PWRCTL_PDN 0x01
+#define CS4270_PWRCTL_PDN_ALL \
+ (CS4270_PWRCTL_PDN_ADC | CS4270_PWRCTL_PDN_DAC | CS4270_PWRCTL_PDN)
#define CS4270_MODE_SPEED_MASK 0x30
#define CS4270_MODE_1X 0x00
#define CS4270_MODE_2X 0x10
@@ -109,6 +113,7 @@ struct cs4270_private {
unsigned int mclk; /* Input frequency of the MCLK pin */
unsigned int mode; /* The mode (I2S or left-justified) */
unsigned int slave_mode;
+ unsigned int manual_mute;
};
/**
@@ -295,7 +300,7 @@ static int cs4270_fill_cache(struct snd_soc_codec *codec)
s32 length;
length = i2c_smbus_read_i2c_block_data(i2c_client,
- CS4270_FIRSTREG | 0x80, CS4270_NUMREGS, cache);
+ CS4270_FIRSTREG | CS4270_I2C_INCR, CS4270_NUMREGS, cache);
if (length != CS4270_NUMREGS) {
dev_err(codec->dev, "i2c read failure, addr=0x%x\n",
@@ -453,7 +458,7 @@ static int cs4270_hw_params(struct snd_pcm_substream *substream,
}
/**
- * cs4270_mute - enable/disable the CS4270 external mute
+ * cs4270_dai_mute - enable/disable the CS4270 external mute
* @dai: the SOC DAI
* @mute: 0 = disable mute, 1 = enable mute
*
@@ -462,21 +467,52 @@ static int cs4270_hw_params(struct snd_pcm_substream *substream,
* board does not have the MUTEA or MUTEB pins connected to such circuitry,
* then this function will do nothing.
*/
-static int cs4270_mute(struct snd_soc_dai *dai, int mute)
+static int cs4270_dai_mute(struct snd_soc_dai *dai, int mute)
{
struct snd_soc_codec *codec = dai->codec;
+ struct cs4270_private *cs4270 = codec->private_data;
int reg6;
reg6 = snd_soc_read(codec, CS4270_MUTE);
if (mute)
reg6 |= CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B;
- else
+ else {
reg6 &= ~(CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B);
+ reg6 |= cs4270->manual_mute;
+ }
return snd_soc_write(codec, CS4270_MUTE, reg6);
}
+/**
+ * cs4270_soc_put_mute - put callback for the 'Master Playback switch'
+ * alsa control.
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * This function basically passes the arguments on to the generic
+ * snd_soc_put_volsw() function and saves the mute information in
+ * our private data structure. This is because we want to prevent
+ * cs4270_dai_mute() neglecting the user's decision to manually
+ * mute the codec's output.
+ *
+ * Returns 0 for success.
+ */
+static int cs4270_soc_put_mute(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct cs4270_private *cs4270 = codec->private_data;
+ int left = !ucontrol->value.integer.value[0];
+ int right = !ucontrol->value.integer.value[1];
+
+ cs4270->manual_mute = (left ? CS4270_MUTE_DAC_A : 0) |
+ (right ? CS4270_MUTE_DAC_B : 0);
+
+ return snd_soc_put_volsw(kcontrol, ucontrol);
+}
+
/* 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",
@@ -486,7 +522,9 @@ static const struct snd_kcontrol_new cs4270_snd_controls[] = {
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)
+ SOC_DOUBLE("Master Capture Switch", CS4270_MUTE, 3, 4, 1, 1),
+ SOC_DOUBLE_EXT("Master Playback Switch", CS4270_MUTE, 0, 1, 1, 1,
+ snd_soc_get_volsw, cs4270_soc_put_mute),
};
/*
@@ -506,7 +544,7 @@ 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,
+ .digital_mute = cs4270_dai_mute,
};
struct snd_soc_dai cs4270_dai = {
@@ -753,6 +791,57 @@ static struct i2c_device_id cs4270_id[] = {
};
MODULE_DEVICE_TABLE(i2c, cs4270_id);
+#ifdef CONFIG_PM
+
+/* This suspend/resume implementation can handle both - a simple standby
+ * where the codec remains powered, and a full suspend, where the voltage
+ * domain the codec is connected to is teared down and/or any other hardware
+ * reset condition is asserted.
+ *
+ * The codec's own power saving features are enabled in the suspend callback,
+ * and all registers are written back to the hardware when resuming.
+ */
+
+static int cs4270_i2c_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+ struct cs4270_private *cs4270 = i2c_get_clientdata(client);
+ struct snd_soc_codec *codec = &cs4270->codec;
+ int reg = snd_soc_read(codec, CS4270_PWRCTL) | CS4270_PWRCTL_PDN_ALL;
+
+ return snd_soc_write(codec, CS4270_PWRCTL, reg);
+}
+
+static int cs4270_i2c_resume(struct i2c_client *client)
+{
+ struct cs4270_private *cs4270 = i2c_get_clientdata(client);
+ struct snd_soc_codec *codec = &cs4270->codec;
+ int reg;
+
+ /* In case the device was put to hard reset during sleep, we need to
+ * wait 500ns here before any I2C communication. */
+ ndelay(500);
+
+ /* first restore the entire register cache ... */
+ for (reg = CS4270_FIRSTREG; reg <= CS4270_LASTREG; reg++) {
+ u8 val = snd_soc_read(codec, reg);
+
+ if (i2c_smbus_write_byte_data(client, reg, val)) {
+ dev_err(codec->dev, "i2c write failed\n");
+ return -EIO;
+ }
+ }
+
+ /* ... then disable the power-down bits */
+ reg = snd_soc_read(codec, CS4270_PWRCTL);
+ reg &= ~CS4270_PWRCTL_PDN_ALL;
+
+ return snd_soc_write(codec, CS4270_PWRCTL, reg);
+}
+#else
+#define cs4270_i2c_suspend NULL
+#define cs4270_i2c_resume NULL
+#endif /* CONFIG_PM */
+
/*
* cs4270_i2c_driver - I2C device identification
*
@@ -767,6 +856,8 @@ static struct i2c_driver cs4270_i2c_driver = {
.id_table = cs4270_id,
.probe = cs4270_i2c_probe,
.remove = cs4270_i2c_remove,
+ .suspend = cs4270_i2c_suspend,
+ .resume = cs4270_i2c_resume,
};
/*
diff --git a/sound/soc/codecs/spdif_transciever.c b/sound/soc/codecs/spdif_transciever.c
new file mode 100644
index 00000000000..218b33adad9
--- /dev/null
+++ b/sound/soc/codecs/spdif_transciever.c
@@ -0,0 +1,71 @@
+/*
+ * ALSA SoC SPDIF DIT driver
+ *
+ * This driver is used by controllers which can operate in DIT (SPDI/F) where
+ * no codec is needed. This file provides stub codec that can be used
+ * in these configurations. TI DaVinci Audio controller uses this driver.
+ *
+ * Author: Steve Chen, <schen@mvista.com>
+ * Copyright: (C) 2009 MontaVista Software, Inc., <source@mvista.com>
+ * Copyright: (C) 2009 Texas Instruments, India
+ *
+ * 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
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+
+#include "spdif_transciever.h"
+
+#define STUB_RATES SNDRV_PCM_RATE_8000_96000
+#define STUB_FORMATS SNDRV_PCM_FMTBIT_S16_LE
+
+struct snd_soc_dai dit_stub_dai = {
+ .name = "DIT",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 384,
+ .rates = STUB_RATES,
+ .formats = STUB_FORMATS,
+ },
+};
+
+static int spdif_dit_probe(struct platform_device *pdev)
+{
+ dit_stub_dai.dev = &pdev->dev;
+ return snd_soc_register_dai(&dit_stub_dai);
+}
+
+static int spdif_dit_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_dai(&dit_stub_dai);
+ return 0;
+}
+
+static struct platform_driver spdif_dit_driver = {
+ .probe = spdif_dit_probe,
+ .remove = spdif_dit_remove,
+ .driver = {
+ .name = "spdif-dit",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init dit_modinit(void)
+{
+ return platform_driver_register(&spdif_dit_driver);
+}
+
+static void __exit dit_exit(void)
+{
+ platform_driver_unregister(&spdif_dit_driver);
+}
+
+module_init(dit_modinit);
+module_exit(dit_exit);
+
diff --git a/sound/soc/codecs/spdif_transciever.h b/sound/soc/codecs/spdif_transciever.h
new file mode 100644
index 00000000000..296f2eb6c4e
--- /dev/null
+++ b/sound/soc/codecs/spdif_transciever.h
@@ -0,0 +1,17 @@
+/*
+ * ALSA SoC DIT/DIR driver header
+ *
+ * Author: Steve Chen, <schen@mvista.com>
+ * Copyright: (C) 2008 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * 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
+ * published by the Free Software Foundation.
+ */
+
+#ifndef CODEC_STUBS_H
+#define CODEC_STUBS_H
+
+extern struct snd_soc_dai dit_stub_dai;
+
+#endif /* CODEC_STUBS_H */
diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c
index 87f606c7682..1fc4c8e0899 100644
--- a/sound/soc/codecs/ssm2602.c
+++ b/sound/soc/codecs/ssm2602.c
@@ -336,15 +336,17 @@ static int ssm2602_startup(struct snd_pcm_substream *substream,
master_runtime->sample_bits,
master_runtime->rate);
- 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);
+ if (master_runtime->rate != 0)
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ master_runtime->rate,
+ master_runtime->rate);
+
+ if (master_runtime->sample_bits != 0)
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ master_runtime->sample_bits,
+ master_runtime->sample_bits);
ssm2602->slave_substream = substream;
} else
@@ -372,6 +374,11 @@ static void ssm2602_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_device *socdev = rtd->socdev;
struct snd_soc_codec *codec = socdev->card->codec;
struct ssm2602_priv *ssm2602 = codec->private_data;
+
+ if (ssm2602->master_substream == substream)
+ ssm2602->master_substream = ssm2602->slave_substream;
+
+ ssm2602->slave_substream = NULL;
/* deactivate */
if (!codec->active)
ssm2602_write(codec, SSM2602_ACTIVE, 0);
@@ -497,11 +504,9 @@ static int ssm2602_set_bias_level(struct snd_soc_codec *codec,
return 0;
}
-#define SSM2602_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 | SNDRV_PCM_RATE_88200 |\
- SNDRV_PCM_RATE_96000)
+#define SSM2602_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_32000 |\
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
#define SSM2602_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
diff --git a/sound/soc/codecs/stac9766.c b/sound/soc/codecs/stac9766.c
new file mode 100644
index 00000000000..8ad4b7b3e3b
--- /dev/null
+++ b/sound/soc/codecs/stac9766.c
@@ -0,0 +1,463 @@
+/*
+ * stac9766.c -- ALSA SoC STAC9766 codec support
+ *
+ * Copyright 2009 Jon Smirl, Digispeaker
+ * Author: Jon Smirl <jonsmirl@gmail.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.
+ *
+ * Features:-
+ *
+ * o Support for AC97 Codec, S/PDIF
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/soc-of-simple.h>
+
+#include "stac9766.h"
+
+#define STAC9766_VERSION "0.10"
+
+/*
+ * STAC9766 register cache
+ */
+static const u16 stac9766_reg[] = {
+ 0x6A90, 0x8000, 0x8000, 0x8000, /* 6 */
+ 0x0000, 0x0000, 0x8008, 0x8008, /* e */
+ 0x8808, 0x8808, 0x8808, 0x8808, /* 16 */
+ 0x8808, 0x0000, 0x8000, 0x0000, /* 1e */
+ 0x0000, 0x0000, 0x0000, 0x000f, /* 26 */
+ 0x0a05, 0x0400, 0xbb80, 0x0000, /* 2e */
+ 0x0000, 0xbb80, 0x0000, 0x0000, /* 36 */
+ 0x0000, 0x2000, 0x0000, 0x0100, /* 3e */
+ 0x0000, 0x0000, 0x0080, 0x0000, /* 46 */
+ 0x0000, 0x0000, 0x0003, 0xffff, /* 4e */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 56 */
+ 0x4000, 0x0000, 0x0000, 0x0000, /* 5e */
+ 0x1201, 0xFFFF, 0xFFFF, 0x0000, /* 66 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 6e */
+ 0x0000, 0x0000, 0x0000, 0x0006, /* 76 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 7e */
+};
+
+static const char *stac9766_record_mux[] = {"Mic", "CD", "Video", "AUX",
+ "Line", "Stereo Mix", "Mono Mix", "Phone"};
+static const char *stac9766_mono_mux[] = {"Mix", "Mic"};
+static const char *stac9766_mic_mux[] = {"Mic1", "Mic2"};
+static const char *stac9766_SPDIF_mux[] = {"PCM", "ADC Record"};
+static const char *stac9766_popbypass_mux[] = {"Normal", "Bypass Mixer"};
+static const char *stac9766_record_all_mux[] = {"All analog",
+ "Analog plus DAC"};
+static const char *stac9766_boost1[] = {"0dB", "10dB"};
+static const char *stac9766_boost2[] = {"0dB", "20dB"};
+static const char *stac9766_stereo_mic[] = {"Off", "On"};
+
+static const struct soc_enum stac9766_record_enum =
+ SOC_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 8, stac9766_record_mux);
+static const struct soc_enum stac9766_mono_enum =
+ SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 9, 2, stac9766_mono_mux);
+static const struct soc_enum stac9766_mic_enum =
+ SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 8, 2, stac9766_mic_mux);
+static const struct soc_enum stac9766_SPDIF_enum =
+ SOC_ENUM_SINGLE(AC97_STAC_DA_CONTROL, 1, 2, stac9766_SPDIF_mux);
+static const struct soc_enum stac9766_popbypass_enum =
+ SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, stac9766_popbypass_mux);
+static const struct soc_enum stac9766_record_all_enum =
+ SOC_ENUM_SINGLE(AC97_STAC_ANALOG_SPECIAL, 12, 2,
+ stac9766_record_all_mux);
+static const struct soc_enum stac9766_boost1_enum =
+ SOC_ENUM_SINGLE(AC97_MIC, 6, 2, stac9766_boost1); /* 0/10dB */
+static const struct soc_enum stac9766_boost2_enum =
+ SOC_ENUM_SINGLE(AC97_STAC_ANALOG_SPECIAL, 2, 2, stac9766_boost2); /* 0/20dB */
+static const struct soc_enum stac9766_stereo_mic_enum =
+ SOC_ENUM_SINGLE(AC97_STAC_STEREO_MIC, 2, 1, stac9766_stereo_mic);
+
+static const DECLARE_TLV_DB_LINEAR(master_tlv, -4600, 0);
+static const DECLARE_TLV_DB_LINEAR(record_tlv, 0, 2250);
+static const DECLARE_TLV_DB_LINEAR(beep_tlv, -4500, 0);
+static const DECLARE_TLV_DB_LINEAR(mix_tlv, -3450, 1200);
+
+static const struct snd_kcontrol_new stac9766_snd_ac97_controls[] = {
+ SOC_DOUBLE_TLV("Speaker Volume", AC97_MASTER, 8, 0, 31, 1, master_tlv),
+ SOC_SINGLE("Speaker Switch", AC97_MASTER, 15, 1, 1),
+ SOC_DOUBLE_TLV("Headphone Volume", AC97_HEADPHONE, 8, 0, 31, 1,
+ master_tlv),
+ SOC_SINGLE("Headphone Switch", AC97_HEADPHONE, 15, 1, 1),
+ SOC_SINGLE_TLV("Mono Out Volume", AC97_MASTER_MONO, 0, 31, 1,
+ master_tlv),
+ SOC_SINGLE("Mono Out Switch", AC97_MASTER_MONO, 15, 1, 1),
+
+ SOC_DOUBLE_TLV("Record Volume", AC97_REC_GAIN, 8, 0, 15, 0, record_tlv),
+ SOC_SINGLE("Record Switch", AC97_REC_GAIN, 15, 1, 1),
+
+
+ SOC_SINGLE_TLV("Beep Volume", AC97_PC_BEEP, 1, 15, 1, beep_tlv),
+ SOC_SINGLE("Beep Switch", AC97_PC_BEEP, 15, 1, 1),
+ SOC_SINGLE("Beep Frequency", AC97_PC_BEEP, 5, 127, 1),
+ SOC_SINGLE_TLV("Phone Volume", AC97_PHONE, 0, 31, 1, mix_tlv),
+ SOC_SINGLE("Phone Switch", AC97_PHONE, 15, 1, 1),
+
+ SOC_ENUM("Mic Boost1", stac9766_boost1_enum),
+ SOC_ENUM("Mic Boost2", stac9766_boost2_enum),
+ SOC_SINGLE_TLV("Mic Volume", AC97_MIC, 0, 31, 1, mix_tlv),
+ SOC_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1),
+ SOC_ENUM("Stereo Mic", stac9766_stereo_mic_enum),
+
+ SOC_DOUBLE_TLV("Line Volume", AC97_LINE, 8, 0, 31, 1, mix_tlv),
+ SOC_SINGLE("Line Switch", AC97_LINE, 15, 1, 1),
+ SOC_DOUBLE_TLV("CD Volume", AC97_CD, 8, 0, 31, 1, mix_tlv),
+ SOC_SINGLE("CD Switch", AC97_CD, 15, 1, 1),
+ SOC_DOUBLE_TLV("AUX Volume", AC97_AUX, 8, 0, 31, 1, mix_tlv),
+ SOC_SINGLE("AUX Switch", AC97_AUX, 15, 1, 1),
+ SOC_DOUBLE_TLV("Video Volume", AC97_VIDEO, 8, 0, 31, 1, mix_tlv),
+ SOC_SINGLE("Video Switch", AC97_VIDEO, 15, 1, 1),
+
+ SOC_DOUBLE_TLV("DAC Volume", AC97_PCM, 8, 0, 31, 1, mix_tlv),
+ SOC_SINGLE("DAC Switch", AC97_PCM, 15, 1, 1),
+ SOC_SINGLE("Loopback Test Switch", AC97_GENERAL_PURPOSE, 7, 1, 0),
+ SOC_SINGLE("3D Volume", AC97_3D_CONTROL, 3, 2, 1),
+ SOC_SINGLE("3D Switch", AC97_GENERAL_PURPOSE, 13, 1, 0),
+
+ SOC_ENUM("SPDIF Mux", stac9766_SPDIF_enum),
+ SOC_ENUM("Mic1/2 Mux", stac9766_mic_enum),
+ SOC_ENUM("Record All Mux", stac9766_record_all_enum),
+ SOC_ENUM("Record Mux", stac9766_record_enum),
+ SOC_ENUM("Mono Mux", stac9766_mono_enum),
+ SOC_ENUM("Pop Bypass Mux", stac9766_popbypass_enum),
+};
+
+static int stac9766_ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ u16 *cache = codec->reg_cache;
+
+ if (reg > AC97_STAC_PAGE0) {
+ stac9766_ac97_write(codec, AC97_INT_PAGING, 0);
+ soc_ac97_ops.write(codec->ac97, reg, val);
+ stac9766_ac97_write(codec, AC97_INT_PAGING, 1);
+ return 0;
+ }
+ if (reg / 2 > ARRAY_SIZE(stac9766_reg))
+ return -EIO;
+
+ soc_ac97_ops.write(codec->ac97, reg, val);
+ cache[reg / 2] = val;
+ return 0;
+}
+
+static unsigned int stac9766_ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 val = 0, *cache = codec->reg_cache;
+
+ if (reg > AC97_STAC_PAGE0) {
+ stac9766_ac97_write(codec, AC97_INT_PAGING, 0);
+ val = soc_ac97_ops.read(codec->ac97, reg - AC97_STAC_PAGE0);
+ stac9766_ac97_write(codec, AC97_INT_PAGING, 1);
+ return val;
+ }
+ if (reg / 2 > ARRAY_SIZE(stac9766_reg))
+ return -EIO;
+
+ if (reg == AC97_RESET || reg == AC97_GPIO_STATUS ||
+ reg == AC97_INT_PAGING || reg == AC97_VENDOR_ID1 ||
+ reg == AC97_VENDOR_ID2) {
+
+ val = soc_ac97_ops.read(codec->ac97, reg);
+ return val;
+ }
+ return cache[reg / 2];
+}
+
+static int ac97_analog_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned short reg, vra;
+
+ vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS);
+
+ vra |= 0x1; /* enable variable rate audio */
+
+ stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ reg = AC97_PCM_FRONT_DAC_RATE;
+ else
+ reg = AC97_PCM_LR_ADC_RATE;
+
+ return stac9766_ac97_write(codec, reg, runtime->rate);
+}
+
+static int ac97_digital_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned short reg, vra;
+
+ stac9766_ac97_write(codec, AC97_SPDIF, 0x2002);
+
+ vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS);
+ vra |= 0x5; /* Enable VRA and SPDIF out */
+
+ stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra);
+
+ reg = AC97_PCM_FRONT_DAC_RATE;
+
+ return stac9766_ac97_write(codec, reg, runtime->rate);
+}
+
+static int ac97_digital_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ unsigned short vra;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_STOP:
+ vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS);
+ vra &= !0x04;
+ stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra);
+ break;
+ }
+ return 0;
+}
+
+static int stac9766_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ switch (level) {
+ case SND_SOC_BIAS_ON: /* full On */
+ case SND_SOC_BIAS_PREPARE: /* partial On */
+ case SND_SOC_BIAS_STANDBY: /* Off, with power */
+ stac9766_ac97_write(codec, AC97_POWERDOWN, 0x0000);
+ break;
+ case SND_SOC_BIAS_OFF: /* Off, without power */
+ /* disable everything including AC link */
+ stac9766_ac97_write(codec, AC97_POWERDOWN, 0xffff);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+static int stac9766_reset(struct snd_soc_codec *codec, int try_warm)
+{
+ if (try_warm && soc_ac97_ops.warm_reset) {
+ soc_ac97_ops.warm_reset(codec->ac97);
+ if (stac9766_ac97_read(codec, 0) == stac9766_reg[0])
+ return 1;
+ }
+
+ soc_ac97_ops.reset(codec->ac97);
+ if (soc_ac97_ops.warm_reset)
+ soc_ac97_ops.warm_reset(codec->ac97);
+ if (stac9766_ac97_read(codec, 0) != stac9766_reg[0])
+ return -EIO;
+ return 0;
+}
+
+static int stac9766_codec_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;
+