summaryrefslogtreecommitdiff
path: root/sound/soc/codecs
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/codecs')
-rw-r--r--sound/soc/codecs/Kconfig18
-rw-r--r--sound/soc/codecs/Makefile8
-rw-r--r--sound/soc/codecs/ad1836.c313
-rw-r--r--sound/soc/codecs/ad1836.h44
-rw-r--r--sound/soc/codecs/adau1701.c549
-rw-r--r--sound/soc/codecs/adau1701.h17
-rw-r--r--sound/soc/codecs/adav80x.c951
-rw-r--r--sound/soc/codecs/adav80x.h35
-rw-r--r--sound/soc/codecs/ak4641.c2
-rw-r--r--sound/soc/codecs/cs4270.c5
-rw-r--r--sound/soc/codecs/max98088.c2
-rw-r--r--sound/soc/codecs/max98095.c10
-rw-r--r--sound/soc/codecs/sta32x.c917
-rw-r--r--sound/soc/codecs/sta32x.h210
-rw-r--r--sound/soc/codecs/tlv320aic3x.c34
-rw-r--r--sound/soc/codecs/twl6040.c4
-rw-r--r--sound/soc/codecs/wm8782.c80
-rw-r--r--sound/soc/codecs/wm8900.c1
-rw-r--r--sound/soc/codecs/wm8904.c1
-rw-r--r--sound/soc/codecs/wm8915.c156
-rw-r--r--sound/soc/codecs/wm8940.c7
-rw-r--r--sound/soc/codecs/wm8962.c132
-rw-r--r--sound/soc/codecs/wm8993.c3
-rw-r--r--sound/soc/codecs/wm8994.c116
-rw-r--r--sound/soc/codecs/wm8994.h3
-rw-r--r--sound/soc/codecs/wm9081.c2
-rw-r--r--sound/soc/codecs/wm_hubs.c58
-rw-r--r--sound/soc/codecs/wm_hubs.h10
28 files changed, 3369 insertions, 319 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 98175a096df2..ff43405752a1 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -17,6 +17,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_AD193X if SND_SOC_I2C_AND_SPI
select SND_SOC_AD1980 if SND_SOC_AC97_BUS
select SND_SOC_AD73311
+ select SND_SOC_ADAV80X
select SND_SOC_ADS117X
select SND_SOC_AK4104 if SPI_MASTER
select SND_SOC_AK4535 if I2C
@@ -42,6 +43,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_SN95031 if INTEL_SCU_IPC
select SND_SOC_SPDIF
select SND_SOC_SSM2602 if SND_SOC_I2C_AND_SPI
+ select SND_SOC_STA32X if I2C
select SND_SOC_STAC9766 if SND_SOC_AC97_BUS
select SND_SOC_TLV320AIC23 if I2C
select SND_SOC_TLV320AIC26 if SPI_MASTER
@@ -71,6 +73,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_WM8753 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8770 if SPI_MASTER
select SND_SOC_WM8776 if SND_SOC_I2C_AND_SPI
+ select SND_SOC_WM8782
select SND_SOC_WM8804 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8900 if I2C
select SND_SOC_WM8903 if I2C
@@ -130,7 +133,14 @@ config SND_SOC_AD1980
config SND_SOC_AD73311
tristate
-
+
+config SND_SOC_ADAU1701
+ select SIGMA
+ tristate
+
+config SND_SOC_ADAV80X
+ tristate
+
config SND_SOC_ADS117X
tristate
@@ -216,6 +226,9 @@ config SND_SOC_SPDIF
config SND_SOC_SSM2602
tristate
+config SND_SOC_STA32X
+ tristate
+
config SND_SOC_STAC9766
tristate
@@ -299,6 +312,9 @@ config SND_SOC_WM8770
config SND_SOC_WM8776
tristate
+config SND_SOC_WM8782
+ tristate
+
config SND_SOC_WM8804
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index fd8558406ef0..4957431e23fc 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -4,6 +4,8 @@ snd-soc-ad1836-objs := ad1836.o
snd-soc-ad193x-objs := ad193x.o
snd-soc-ad1980-objs := ad1980.o
snd-soc-ad73311-objs := ad73311.o
+snd-soc-adau1701-objs := adau1701.o
+snd-soc-adav80x-objs := adav80x.o
snd-soc-ads117x-objs := ads117x.o
snd-soc-ak4104-objs := ak4104.o
snd-soc-ak4535-objs := ak4535.o
@@ -28,6 +30,7 @@ snd-soc-alc5623-objs := alc5623.o
snd-soc-sn95031-objs := sn95031.o
snd-soc-spdif-objs := spdif_transciever.o
snd-soc-ssm2602-objs := ssm2602.o
+snd-soc-sta32x-objs := sta32x.o
snd-soc-stac9766-objs := stac9766.o
snd-soc-tlv320aic23-objs := tlv320aic23.o
snd-soc-tlv320aic26-objs := tlv320aic26.o
@@ -55,6 +58,7 @@ snd-soc-wm8750-objs := wm8750.o
snd-soc-wm8753-objs := wm8753.o
snd-soc-wm8770-objs := wm8770.o
snd-soc-wm8776-objs := wm8776.o
+snd-soc-wm8782-objs := wm8782.o
snd-soc-wm8804-objs := wm8804.o
snd-soc-wm8900-objs := wm8900.o
snd-soc-wm8903-objs := wm8903.o
@@ -95,6 +99,8 @@ obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o
obj-$(CONFIG_SND_SOC_AD193X) += snd-soc-ad193x.o
obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o
obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o
+obj-$(CONFIG_SND_SOC_ADAU1701) += snd-soc-adau1701.o
+obj-$(CONFIG_SND_SOC_ADAV80X) += snd-soc-adav80x.o
obj-$(CONFIG_SND_SOC_ADS117X) += snd-soc-ads117x.o
obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o
obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o
@@ -120,6 +126,7 @@ obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o
obj-$(CONFIG_SND_SOC_SN95031) +=snd-soc-sn95031.o
obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o
obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o
+obj-$(CONFIG_SND_SOC_STA32X) += snd-soc-sta32x.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
@@ -147,6 +154,7 @@ obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o
obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o
obj-$(CONFIG_SND_SOC_WM8770) += snd-soc-wm8770.o
obj-$(CONFIG_SND_SOC_WM8776) += snd-soc-wm8776.o
+obj-$(CONFIG_SND_SOC_WM8782) += snd-soc-wm8782.o
obj-$(CONFIG_SND_SOC_WM8804) += snd-soc-wm8804.o
obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o
obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o
diff --git a/sound/soc/codecs/ad1836.c b/sound/soc/codecs/ad1836.c
index 754c496412bd..4e5c5726366b 100644
--- a/sound/soc/codecs/ad1836.c
+++ b/sound/soc/codecs/ad1836.c
@@ -1,19 +1,10 @@
-/*
- * File: sound/soc/codecs/ad1836.c
- * Author: Barry Song <Barry.Song@analog.com>
- *
- * Created: Aug 04 2009
- * Description: Driver for AD1836 sound chip
- *
- * Modified:
- * Copyright 2009 Analog Devices Inc.
+ /*
+ * Audio Codec driver supporting:
+ * AD1835A, AD1836, AD1837A, AD1838A, AD1839A
*
- * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ * Copyright 2009-2011 Analog Devices Inc.
*
- * 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.
+ * Licensed under the GPL-2 or later.
*/
#include <linux/init.h>
@@ -30,10 +21,15 @@
#include <linux/spi/spi.h>
#include "ad1836.h"
+enum ad1836_type {
+ AD1835,
+ AD1836,
+ AD1838,
+};
+
/* codec private data */
struct ad1836_priv {
- enum snd_soc_control_type control_type;
- void *control_data;
+ enum ad1836_type type;
};
/*
@@ -44,29 +40,60 @@ static const char *ad1836_deemp[] = {"None", "44.1kHz", "32kHz", "48kHz"};
static const struct soc_enum ad1836_deemp_enum =
SOC_ENUM_SINGLE(AD1836_DAC_CTRL1, 8, 4, ad1836_deemp);
-static const struct snd_kcontrol_new ad1836_snd_controls[] = {
- /* DAC volume control */
- SOC_DOUBLE_R("DAC1 Volume", AD1836_DAC_L1_VOL,
- AD1836_DAC_R1_VOL, 0, 0x3FF, 0),
- SOC_DOUBLE_R("DAC2 Volume", AD1836_DAC_L2_VOL,
- AD1836_DAC_R2_VOL, 0, 0x3FF, 0),
- SOC_DOUBLE_R("DAC3 Volume", AD1836_DAC_L3_VOL,
- AD1836_DAC_R3_VOL, 0, 0x3FF, 0),
-
- /* ADC switch control */
- SOC_DOUBLE("ADC1 Switch", AD1836_ADC_CTRL2, AD1836_ADCL1_MUTE,
- AD1836_ADCR1_MUTE, 1, 1),
- SOC_DOUBLE("ADC2 Switch", AD1836_ADC_CTRL2, AD1836_ADCL2_MUTE,
- AD1836_ADCR2_MUTE, 1, 1),
-
- /* DAC switch control */
- SOC_DOUBLE("DAC1 Switch", AD1836_DAC_CTRL2, AD1836_DACL1_MUTE,
- AD1836_DACR1_MUTE, 1, 1),
- SOC_DOUBLE("DAC2 Switch", AD1836_DAC_CTRL2, AD1836_DACL2_MUTE,
- AD1836_DACR2_MUTE, 1, 1),
- SOC_DOUBLE("DAC3 Switch", AD1836_DAC_CTRL2, AD1836_DACL3_MUTE,
- AD1836_DACR3_MUTE, 1, 1),
+#define AD1836_DAC_VOLUME(x) \
+ SOC_DOUBLE_R("DAC" #x " Playback Volume", AD1836_DAC_L_VOL(x), \
+ AD1836_DAC_R_VOL(x), 0, 0x3FF, 0)
+
+#define AD1836_DAC_SWITCH(x) \
+ SOC_DOUBLE("DAC" #x " Playback Switch", AD1836_DAC_CTRL2, \
+ AD1836_MUTE_LEFT(x), AD1836_MUTE_RIGHT(x), 1, 1)
+
+#define AD1836_ADC_SWITCH(x) \
+ SOC_DOUBLE("ADC" #x " Capture Switch", AD1836_ADC_CTRL2, \
+ AD1836_MUTE_LEFT(x), AD1836_MUTE_RIGHT(x), 1, 1)
+
+static const struct snd_kcontrol_new ad183x_dac_controls[] = {
+ AD1836_DAC_VOLUME(1),
+ AD1836_DAC_SWITCH(1),
+ AD1836_DAC_VOLUME(2),
+ AD1836_DAC_SWITCH(2),
+ AD1836_DAC_VOLUME(3),
+ AD1836_DAC_SWITCH(3),
+ AD1836_DAC_VOLUME(4),
+ AD1836_DAC_SWITCH(4),
+};
+
+static const struct snd_soc_dapm_widget ad183x_dac_dapm_widgets[] = {
+ SND_SOC_DAPM_OUTPUT("DAC1OUT"),
+ SND_SOC_DAPM_OUTPUT("DAC2OUT"),
+ SND_SOC_DAPM_OUTPUT("DAC3OUT"),
+ SND_SOC_DAPM_OUTPUT("DAC4OUT"),
+};
+
+static const struct snd_soc_dapm_route ad183x_dac_routes[] = {
+ { "DAC1OUT", NULL, "DAC" },
+ { "DAC2OUT", NULL, "DAC" },
+ { "DAC3OUT", NULL, "DAC" },
+ { "DAC4OUT", NULL, "DAC" },
+};
+
+static const struct snd_kcontrol_new ad183x_adc_controls[] = {
+ AD1836_ADC_SWITCH(1),
+ AD1836_ADC_SWITCH(2),
+ AD1836_ADC_SWITCH(3),
+};
+
+static const struct snd_soc_dapm_widget ad183x_adc_dapm_widgets[] = {
+ SND_SOC_DAPM_INPUT("ADC1IN"),
+ SND_SOC_DAPM_INPUT("ADC2IN"),
+};
+
+static const struct snd_soc_dapm_route ad183x_adc_routes[] = {
+ { "ADC", NULL, "ADC1IN" },
+ { "ADC", NULL, "ADC2IN" },
+};
+static const struct snd_kcontrol_new ad183x_controls[] = {
/* ADC high-pass filter */
SOC_SINGLE("ADC High Pass Filter Switch", AD1836_ADC_CTRL1,
AD1836_ADC_HIGHPASS_FILTER, 1, 0),
@@ -75,27 +102,24 @@ static const struct snd_kcontrol_new ad1836_snd_controls[] = {
SOC_ENUM("Playback Deemphasis", ad1836_deemp_enum),
};
-static const struct snd_soc_dapm_widget ad1836_dapm_widgets[] = {
+static const struct snd_soc_dapm_widget ad183x_dapm_widgets[] = {
SND_SOC_DAPM_DAC("DAC", "Playback", AD1836_DAC_CTRL1,
AD1836_DAC_POWERDOWN, 1),
SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_SUPPLY("ADC_PWR", AD1836_ADC_CTRL1,
AD1836_ADC_POWERDOWN, 1, NULL, 0),
- SND_SOC_DAPM_OUTPUT("DAC1OUT"),
- SND_SOC_DAPM_OUTPUT("DAC2OUT"),
- SND_SOC_DAPM_OUTPUT("DAC3OUT"),
- SND_SOC_DAPM_INPUT("ADC1IN"),
- SND_SOC_DAPM_INPUT("ADC2IN"),
};
-static const struct snd_soc_dapm_route audio_paths[] = {
+static const struct snd_soc_dapm_route ad183x_dapm_routes[] = {
{ "DAC", NULL, "ADC_PWR" },
{ "ADC", NULL, "ADC_PWR" },
- { "DAC1OUT", "DAC1 Switch", "DAC" },
- { "DAC2OUT", "DAC2 Switch", "DAC" },
- { "DAC3OUT", "DAC3 Switch", "DAC" },
- { "ADC", "ADC1 Switch", "ADC1IN" },
- { "ADC", "ADC2 Switch", "ADC2IN" },
+};
+
+static const DECLARE_TLV_DB_SCALE(ad1836_in_tlv, 0, 300, 0);
+
+static const struct snd_kcontrol_new ad1836_controls[] = {
+ SOC_DOUBLE_TLV("ADC2 Capture Volume", AD1836_ADC_CTRL1, 3, 0, 4, 0,
+ ad1836_in_tlv),
};
/*
@@ -165,64 +189,69 @@ static int ad1836_hw_params(struct snd_pcm_substream *substream,
return 0;
}
+static struct snd_soc_dai_ops ad1836_dai_ops = {
+ .hw_params = ad1836_hw_params,
+ .set_fmt = ad1836_set_dai_fmt,
+};
+
+#define AD183X_DAI(_name, num_dacs, num_adcs) \
+{ \
+ .name = _name "-hifi", \
+ .playback = { \
+ .stream_name = "Playback", \
+ .channels_min = 2, \
+ .channels_max = (num_dacs) * 2, \
+ .rates = SNDRV_PCM_RATE_48000, \
+ .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, \
+ }, \
+ .capture = { \
+ .stream_name = "Capture", \
+ .channels_min = 2, \
+ .channels_max = (num_adcs) * 2, \
+ .rates = SNDRV_PCM_RATE_48000, \
+ .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, \
+ }, \
+ .ops = &ad1836_dai_ops, \
+}
+
+static struct snd_soc_dai_driver ad183x_dais[] = {
+ [AD1835] = AD183X_DAI("ad1835", 4, 1),
+ [AD1836] = AD183X_DAI("ad1836", 3, 2),
+ [AD1838] = AD183X_DAI("ad1838", 3, 1),
+};
+
#ifdef CONFIG_PM
-static int ad1836_soc_suspend(struct snd_soc_codec *codec,
- pm_message_t state)
+static int ad1836_suspend(struct snd_soc_codec *codec, pm_message_t state)
{
/* reset clock control mode */
- u16 adc_ctrl2 = snd_soc_read(codec, AD1836_ADC_CTRL2);
- adc_ctrl2 &= ~AD1836_ADC_SERFMT_MASK;
-
- return snd_soc_write(codec, AD1836_ADC_CTRL2, adc_ctrl2);
+ return snd_soc_update_bits(codec, AD1836_ADC_CTRL2,
+ AD1836_ADC_SERFMT_MASK, 0);
}
-static int ad1836_soc_resume(struct snd_soc_codec *codec)
+static int ad1836_resume(struct snd_soc_codec *codec)
{
/* restore clock control mode */
- u16 adc_ctrl2 = snd_soc_read(codec, AD1836_ADC_CTRL2);
- adc_ctrl2 |= AD1836_ADC_AUX;
-
- return snd_soc_write(codec, AD1836_ADC_CTRL2, adc_ctrl2);
+ return snd_soc_update_bits(codec, AD1836_ADC_CTRL2,
+ AD1836_ADC_SERFMT_MASK, AD1836_ADC_AUX);
}
#else
-#define ad1836_soc_suspend NULL
-#define ad1836_soc_resume NULL
+#define ad1836_suspend NULL
+#define ad1836_resume NULL
#endif
-static struct snd_soc_dai_ops ad1836_dai_ops = {
- .hw_params = ad1836_hw_params,
- .set_fmt = ad1836_set_dai_fmt,
-};
-
-/* codec DAI instance */
-static struct snd_soc_dai_driver ad1836_dai = {
- .name = "ad1836-hifi",
- .playback = {
- .stream_name = "Playback",
- .channels_min = 2,
- .channels_max = 6,
- .rates = SNDRV_PCM_RATE_48000,
- .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE |
- SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
- },
- .capture = {
- .stream_name = "Capture",
- .channels_min = 2,
- .channels_max = 4,
- .rates = SNDRV_PCM_RATE_48000,
- .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE |
- SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
- },
- .ops = &ad1836_dai_ops,
-};
-
static int ad1836_probe(struct snd_soc_codec *codec)
{
struct ad1836_priv *ad1836 = snd_soc_codec_get_drvdata(codec);
struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int num_dacs, num_adcs;
int ret = 0;
+ int i;
+
+ num_dacs = ad183x_dais[ad1836->type].playback.channels_max / 2;
+ num_adcs = ad183x_dais[ad1836->type].capture.channels_max / 2;
- codec->control_data = ad1836->control_data;
ret = snd_soc_codec_set_cache_io(codec, 4, 12, SND_SOC_SPI);
if (ret < 0) {
dev_err(codec->dev, "failed to set cache I/O: %d\n",
@@ -239,21 +268,46 @@ static int ad1836_probe(struct snd_soc_codec *codec)
snd_soc_write(codec, AD1836_ADC_CTRL1, 0x100);
/* unmute adc channles, adc aux mode */
snd_soc_write(codec, AD1836_ADC_CTRL2, 0x180);
- /* left/right diff:PGA/MUX */
- snd_soc_write(codec, AD1836_ADC_CTRL3, 0x3A);
/* volume */
- snd_soc_write(codec, AD1836_DAC_L1_VOL, 0x3FF);
- snd_soc_write(codec, AD1836_DAC_R1_VOL, 0x3FF);
- snd_soc_write(codec, AD1836_DAC_L2_VOL, 0x3FF);
- snd_soc_write(codec, AD1836_DAC_R2_VOL, 0x3FF);
- snd_soc_write(codec, AD1836_DAC_L3_VOL, 0x3FF);
- snd_soc_write(codec, AD1836_DAC_R3_VOL, 0x3FF);
-
- snd_soc_add_controls(codec, ad1836_snd_controls,
- ARRAY_SIZE(ad1836_snd_controls));
- snd_soc_dapm_new_controls(dapm, ad1836_dapm_widgets,
- ARRAY_SIZE(ad1836_dapm_widgets));
- snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths));
+ for (i = 1; i <= num_dacs; ++i) {
+ snd_soc_write(codec, AD1836_DAC_L_VOL(i), 0x3FF);
+ snd_soc_write(codec, AD1836_DAC_R_VOL(i), 0x3FF);
+ }
+
+ if (ad1836->type == AD1836) {
+ /* left/right diff:PGA/MUX */
+ snd_soc_write(codec, AD1836_ADC_CTRL3, 0x3A);
+ ret = snd_soc_add_controls(codec, ad1836_controls,
+ ARRAY_SIZE(ad1836_controls));
+ if (ret)
+ return ret;
+ } else {
+ snd_soc_write(codec, AD1836_ADC_CTRL3, 0x00);
+ }
+
+ ret = snd_soc_add_controls(codec, ad183x_dac_controls, num_dacs * 2);
+ if (ret)
+ return ret;
+
+ ret = snd_soc_add_controls(codec, ad183x_adc_controls, num_adcs);
+ if (ret)
+ return ret;
+
+ ret = snd_soc_dapm_new_controls(dapm, ad183x_dac_dapm_widgets, num_dacs);
+ if (ret)
+ return ret;
+
+ ret = snd_soc_dapm_new_controls(dapm, ad183x_adc_dapm_widgets, num_adcs);
+ if (ret)
+ return ret;
+
+ ret = snd_soc_dapm_add_routes(dapm, ad183x_dac_routes, num_dacs);
+ if (ret)
+ return ret;
+
+ ret = snd_soc_dapm_add_routes(dapm, ad183x_adc_routes, num_adcs);
+ if (ret)
+ return ret;
return ret;
}
@@ -262,19 +316,24 @@ static int ad1836_probe(struct snd_soc_codec *codec)
static int ad1836_remove(struct snd_soc_codec *codec)
{
/* reset clock control mode */
- u16 adc_ctrl2 = snd_soc_read(codec, AD1836_ADC_CTRL2);
- adc_ctrl2 &= ~AD1836_ADC_SERFMT_MASK;
-
- return snd_soc_write(codec, AD1836_ADC_CTRL2, adc_ctrl2);
+ return snd_soc_update_bits(codec, AD1836_ADC_CTRL2,
+ AD1836_ADC_SERFMT_MASK, 0);
}
static struct snd_soc_codec_driver soc_codec_dev_ad1836 = {
- .probe = ad1836_probe,
- .remove = ad1836_remove,
- .suspend = ad1836_soc_suspend,
- .resume = ad1836_soc_resume,
+ .probe = ad1836_probe,
+ .remove = ad1836_remove,
+ .suspend = ad1836_suspend,
+ .resume = ad1836_resume,
.reg_cache_size = AD1836_NUM_REGS,
.reg_word_size = sizeof(u16),
+
+ .controls = ad183x_controls,
+ .num_controls = ARRAY_SIZE(ad183x_controls),
+ .dapm_widgets = ad183x_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(ad183x_dapm_widgets),
+ .dapm_routes = ad183x_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(ad183x_dapm_routes),
};
static int __devinit ad1836_spi_probe(struct spi_device *spi)
@@ -286,12 +345,12 @@ static int __devinit ad1836_spi_probe(struct spi_device *spi)
if (ad1836 == NULL)
return -ENOMEM;
+ ad1836->type = spi_get_device_id(spi)->driver_data;
+
spi_set_drvdata(spi, ad1836);
- ad1836->control_data = spi;
- ad1836->control_type = SND_SOC_SPI;
ret = snd_soc_register_codec(&spi->dev,
- &soc_codec_dev_ad1836, &ad1836_dai, 1);
+ &soc_codec_dev_ad1836, &ad183x_dais[ad1836->type], 1);
if (ret < 0)
kfree(ad1836);
return ret;
@@ -303,27 +362,29 @@ static int __devexit ad1836_spi_remove(struct spi_device *spi)
kfree(spi_get_drvdata(spi));
return 0;
}
+static const struct spi_device_id ad1836_ids[] = {
+ { "ad1835", AD1835 },
+ { "ad1836", AD1836 },
+ { "ad1837", AD1835 },
+ { "ad1838", AD1838 },
+ { "ad1839", AD1838 },
+ { },
+};
+MODULE_DEVICE_TABLE(spi, ad1836_ids);
static struct spi_driver ad1836_spi_driver = {
.driver = {
- .name = "ad1836-codec",
+ .name = "ad1836",
.owner = THIS_MODULE,
},
.probe = ad1836_spi_probe,
.remove = __devexit_p(ad1836_spi_remove),
+ .id_table = ad1836_ids,
};
static int __init ad1836_init(void)
{
- int ret;
-
- ret = spi_register_driver(&ad1836_spi_driver);
- if (ret != 0) {
- printk(KERN_ERR "Failed to register ad1836 SPI driver: %d\n",
- ret);
- }
-
- return ret;
+ return spi_register_driver(&ad1836_spi_driver);
}
module_init(ad1836_init);
diff --git a/sound/soc/codecs/ad1836.h b/sound/soc/codecs/ad1836.h
index 9d6a3f8f8aaf..444747f0db26 100644
--- a/sound/soc/codecs/ad1836.h
+++ b/sound/soc/codecs/ad1836.h
@@ -1,19 +1,10 @@
/*
- * File: sound/soc/codecs/ad1836.h
- * Based on:
- * Author: Barry Song <Barry.Song@analog.com>
+ * Audio Codec driver supporting:
+ * AD1835A, AD1836, AD1837A, AD1838A, AD1839A
*
- * Created: Aug 04, 2009
- * Description: definitions for AD1836 registers
+ * Copyright 2009-2011 Analog Devices Inc.
*
- * Modified:
- *
- * Bugs: Enter bugs at http://blackfin.uclinux.org/
- *
- * 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.
+ * Licensed under the GPL-2 or later.
*/
#ifndef __AD1836_H__
@@ -21,39 +12,30 @@
#define AD1836_DAC_CTRL1 0
#define AD1836_DAC_POWERDOWN 2
-#define AD1836_DAC_SERFMT_MASK 0xE0
+#define AD1836_DAC_SERFMT_MASK 0xE0
#define AD1836_DAC_SERFMT_PCK256 (0x4 << 5)
#define AD1836_DAC_SERFMT_PCK128 (0x5 << 5)
#define AD1836_DAC_WORD_LEN_MASK 0x18
#define AD1836_DAC_WORD_LEN_OFFSET 3
#define AD1836_DAC_CTRL2 1
-#define AD1836_DACL1_MUTE 0
-#define AD1836_DACR1_MUTE 1
-#define AD1836_DACL2_MUTE 2
-#define AD1836_DACR2_MUTE 3
-#define AD1836_DACL3_MUTE 4
-#define AD1836_DACR3_MUTE 5
-#define AD1836_DAC_L1_VOL 2
-#define AD1836_DAC_R1_VOL 3
-#define AD1836_DAC_L2_VOL 4
-#define AD1836_DAC_R2_VOL 5
-#define AD1836_DAC_L3_VOL 6
-#define AD1836_DAC_R3_VOL 7
+/* These macros are one-based. So AD183X_MUTE_LEFT(1) will return the mute bit
+ * for the first ADC/DAC */
+#define AD1836_MUTE_LEFT(x) (((x) * 2) - 2)
+#define AD1836_MUTE_RIGHT(x) (((x) * 2) - 1)
+
+#define AD1836_DAC_L_VOL(x) ((x) * 2)
+#define AD1836_DAC_R_VOL(x) (1 + ((x) * 2))
#define AD1836_ADC_CTRL1 12
#define AD1836_ADC_POWERDOWN 7
#define AD1836_ADC_HIGHPASS_FILTER 8
#define AD1836_ADC_CTRL2 13
-#define AD1836_ADCL1_MUTE 0
-#define AD1836_ADCR1_MUTE 1
-#define AD1836_ADCL2_MUTE 2
-#define AD1836_ADCR2_MUTE 3
#define AD1836_ADC_WORD_LEN_MASK 0x30
#define AD1836_ADC_WORD_OFFSET 5
-#define AD1836_ADC_SERFMT_MASK (7 << 6)
+#define AD1836_ADC_SERFMT_MASK (7 << 6)
#define AD1836_ADC_SERFMT_PCK256 (0x4 << 6)
#define AD1836_ADC_SERFMT_PCK128 (0x5 << 6)
#define AD1836_ADC_AUX (0x6 << 6)
diff --git a/sound/soc/codecs/adau1701.c b/sound/soc/codecs/adau1701.c
new file mode 100644
index 000000000000..2758d5fc60d6
--- /dev/null
+++ b/sound/soc/codecs/adau1701.c
@@ -0,0 +1,549 @@
+/*
+ * Driver for ADAU1701 SigmaDSP processor
+ *
+ * Copyright 2011 Analog Devices Inc.
+ * Author: Lars-Peter Clausen <lars@metafoo.de>
+ * based on an inital version by Cliff Cai <cliff.cai@analog.com>
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/sigma.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "adau1701.h"
+
+#define ADAU1701_DSPCTRL 0x1c
+#define ADAU1701_SEROCTL 0x1e
+#define ADAU1701_SERICTL 0x1f
+
+#define ADAU1701_AUXNPOW 0x22
+
+#define ADAU1701_OSCIPOW 0x26
+#define ADAU1701_DACSET 0x27
+
+#define ADAU1701_NUM_REGS 0x28
+
+#define ADAU1701_DSPCTRL_CR (1 << 2)
+#define ADAU1701_DSPCTRL_DAM (1 << 3)
+#define ADAU1701_DSPCTRL_ADM (1 << 4)
+#define ADAU1701_DSPCTRL_SR_48 0x00
+#define ADAU1701_DSPCTRL_SR_96 0x01
+#define ADAU1701_DSPCTRL_SR_192 0x02
+#define ADAU1701_DSPCTRL_SR_MASK 0x03
+
+#define ADAU1701_SEROCTL_INV_LRCLK 0x2000
+#define ADAU1701_SEROCTL_INV_BCLK 0x1000
+#define ADAU1701_SEROCTL_MASTER 0x0800
+
+#define ADAU1701_SEROCTL_OBF16 0x0000
+#define ADAU1701_SEROCTL_OBF8 0x0200
+#define ADAU1701_SEROCTL_OBF4 0x0400
+#define ADAU1701_SEROCTL_OBF2 0x0600
+#define ADAU1701_SEROCTL_OBF_MASK 0x0600
+
+#define ADAU1701_SEROCTL_OLF1024 0x0000
+#define ADAU1701_SEROCTL_OLF512 0x0080
+#define ADAU1701_SEROCTL_OLF256 0x0100
+#define ADAU1701_SEROCTL_OLF_MASK 0x0180
+
+#define ADAU1701_SEROCTL_MSB_DEALY1 0x0000
+#define ADAU1701_SEROCTL_MSB_DEALY0 0x0004
+#define ADAU1701_SEROCTL_MSB_DEALY8 0x0008
+#define ADAU1701_SEROCTL_MSB_DEALY12 0x000c
+#define ADAU1701_SEROCTL_MSB_DEALY16 0x0010
+#define ADAU1701_SEROCTL_MSB_DEALY_MASK 0x001c
+
+#define ADAU1701_SEROCTL_WORD_LEN_24 0x0000
+#define ADAU1701_SEROCTL_WORD_LEN_20 0x0001
+#define ADAU1701_SEROCTL_WORD_LEN_16 0x0010
+#define ADAU1701_SEROCTL_WORD_LEN_MASK 0x0003
+
+#define ADAU1701_AUXNPOW_VBPD 0x40
+#define ADAU1701_AUXNPOW_VRPD 0x20
+
+#define ADAU1701_SERICTL_I2S 0
+#define ADAU1701_SERICTL_LEFTJ 1
+#define ADAU1701_SERICTL_TDM 2
+#define ADAU1701_SERICTL_RIGHTJ_24 3
+#define ADAU1701_SERICTL_RIGHTJ_20 4
+#define ADAU1701_SERICTL_RIGHTJ_18 5
+#define ADAU1701_SERICTL_RIGHTJ_16 6
+#define ADAU1701_SERICTL_MODE_MASK 7
+#define ADAU1701_SERICTL_INV_BCLK BIT(3)
+#define ADAU1701_SERICTL_INV_LRCLK BIT(4)
+
+#define ADAU1701_OSCIPOW_OPD 0x04
+#define ADAU1701_DACSET_DACINIT 1
+
+#define ADAU1701_FIRMWARE "adau1701.bin"
+
+struct adau1701 {
+ unsigned int dai_fmt;
+};
+
+static const struct snd_kcontrol_new adau1701_controls[] = {
+ SOC_SINGLE("Master Capture Switch", ADAU1701_DSPCTRL, 4, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget adau1701_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC("DAC0", "Playback", ADAU1701_AUXNPOW, 3, 1),
+ SND_SOC_DAPM_DAC("DAC1", "Playback", ADAU1701_AUXNPOW, 2, 1),
+ SND_SOC_DAPM_DAC("DAC2", "Playback", ADAU1701_AUXNPOW, 1, 1),
+ SND_SOC_DAPM_DAC("DAC3", "Playback", ADAU1701_AUXNPOW, 0, 1),
+ SND_SOC_DAPM_ADC("ADC", "Capture", ADAU1701_AUXNPOW, 7, 1),
+
+ SND_SOC_DAPM_OUTPUT("OUT0"),
+ SND_SOC_DAPM_OUTPUT("OUT1"),
+ SND_SOC_DAPM_OUTPUT("OUT2"),
+ SND_SOC_DAPM_OUTPUT("OUT3"),
+ SND_SOC_DAPM_INPUT("IN0"),
+ SND_SOC_DAPM_INPUT("IN1"),
+};
+
+static const struct snd_soc_dapm_route adau1701_dapm_routes[] = {
+ { "OUT0", NULL, "DAC0" },
+ { "OUT1", NULL, "DAC1" },
+ { "OUT2", NULL, "DAC2" },
+ { "OUT3", NULL, "DAC3" },
+
+ { "ADC", NULL, "IN0" },
+ { "ADC", NULL, "IN1" },
+};
+
+static unsigned int adau1701_register_size(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ switch (reg) {
+ case ADAU1701_DSPCTRL:
+ case ADAU1701_SEROCTL:
+ case ADAU1701_AUXNPOW:
+ case ADAU1701_OSCIPOW:
+ case ADAU1701_DACSET:
+ return 2;
+ case ADAU1701_SERICTL:
+ return 1;
+ }
+
+ dev_err(codec->dev, "Unsupported register address: %d\n", reg);
+ return 0;
+}
+
+static int adau1701_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ unsigned int i;
+ unsigned int size;
+ uint8_t buf[4];
+ int ret;
+
+ size = adau1701_register_size(codec, reg);
+ if (size == 0)
+ return -EINVAL;
+
+ snd_soc_cache_write(codec, reg, value);
+
+ buf[0] = 0x08;
+ buf[1] = reg;
+
+ for (i = size + 1; i >= 2; --i) {
+ buf[i] = value;
+ value >>= 8;
+ }
+
+ ret = i2c_master_send(to_i2c_client(codec->dev), buf, size + 2);
+ if (ret == size + 2)
+ return 0;
+ else if (ret < 0)
+ return ret;
+ else
+ return -EIO;
+}
+
+static unsigned int adau1701_read(struct snd_soc_codec *codec, unsigned int reg)
+{
+ unsigned int value;
+ unsigned int ret;
+
+ ret = snd_soc_cache_read(codec, reg, &value);
+ if (ret)
+ return ret;
+
+ return value;
+}
+
+static int adau1701_load_firmware(struct snd_soc_codec *codec)
+{
+ return process_sigma_firmware(codec->control_data, ADAU1701_FIRMWARE);
+}
+
+static int adau1701_set_capture_pcm_format(struct snd_soc_codec *codec,
+ snd_pcm_format_t format)
+{
+ struct adau1701 *adau1701 = snd_soc_codec_get_drvdata(codec);
+ unsigned int mask = ADAU1701_SEROCTL_WORD_LEN_MASK;
+ unsigned int val;
+
+ switch (format) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ val = ADAU1701_SEROCTL_WORD_LEN_16;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ val = ADAU1701_SEROCTL_WORD_LEN_20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ val = ADAU1701_SEROCTL_WORD_LEN_24;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (adau1701->dai_fmt == SND_SOC_DAIFMT_RIGHT_J) {
+ switch (format) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ val |= ADAU1701_SEROCTL_MSB_DEALY16;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ val |= ADAU1701_SEROCTL_MSB_DEALY12;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ val |= ADAU1701_SEROCTL_MSB_DEALY8;
+ break;
+ }
+ mask |= ADAU1701_SEROCTL_MSB_DEALY_MASK;
+ }
+
+ snd_soc_update_bits(codec, ADAU1701_SEROCTL, mask, val);
+
+ return 0;
+}
+
+static int adau1701_set_playback_pcm_format(struct snd_soc_codec *codec,
+ snd_pcm_format_t format)
+{
+ struct adau1701 *adau1701 = snd_soc_codec_get_drvdata(codec);
+ unsigned int val;
+
+ if (adau1701->dai_fmt != SND_SOC_DAIFMT_RIGHT_J)
+ return 0;
+
+ switch (format) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ val = ADAU1701_SERICTL_RIGHTJ_16;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ val = ADAU1701_SERICTL_RIGHTJ_20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ val = ADAU1701_SERICTL_RIGHTJ_24;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, ADAU1701_SERICTL,
+ ADAU1701_SERICTL_MODE_MASK, val);
+
+ return 0;
+}
+
+static int adau1701_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_codec *codec = rtd->codec;
+ snd_pcm_format_t format;
+ unsigned int val;
+
+ switch (params_rate(params)) {
+ case 192000:
+ val = ADAU1701_DSPCTRL_SR_192;
+ break;
+ case 96000:
+ val = ADAU1701_DSPCTRL_SR_96;
+ break;
+ case 48000:
+ val = ADAU1701_DSPCTRL_SR_48;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, ADAU1701_DSPCTRL,
+ ADAU1701_DSPCTRL_SR_MASK, val);
+
+ format = params_format(params);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ return adau1701_set_playback_pcm_format(codec, format);
+ else
+ return adau1701_set_capture_pcm_format(codec, format);
+}
+
+static int adau1701_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct adau1701 *adau1701 = snd_soc_codec_get_drvdata(codec);
+ unsigned int serictl = 0x00, seroctl = 0x00;
+ bool invert_lrclk;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ /* master, 64-bits per sample, 1 frame per sample */
+ seroctl |= ADAU1701_SEROCTL_MASTER | ADAU1701_SEROCTL_OBF16
+ | ADAU1701_SEROCTL_OLF1024;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ invert_lrclk = false;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ invert_lrclk = true;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ invert_lrclk = false;
+ serictl |= ADAU1701_SERICTL_INV_BCLK;
+ seroctl |= ADAU1701_SEROCTL_INV_BCLK;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ invert_lrclk = true;
+ serictl |= ADAU1701_SERICTL_INV_BCLK;
+ seroctl |= ADAU1701_SEROCTL_INV_BCLK;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ serictl |= ADAU1701_SERICTL_LEFTJ;
+ seroctl |= ADAU1701_SEROCTL_MSB_DEALY0;
+ invert_lrclk = !invert_lrclk;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ serictl |= ADAU1701_SERICTL_RIGHTJ_24;
+ seroctl |= ADAU1701_SEROCTL_MSB_DEALY8;
+ invert_lrclk = !invert_lrclk;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (invert_lrclk) {
+ seroctl |= ADAU1701_SEROCTL_INV_LRCLK;
+ serictl |= ADAU1701_SERICTL_INV_LRCLK;
+ }
+
+ adau1701->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
+
+ snd_soc_write(codec, ADAU1701_SERICTL, serictl);
+ snd_soc_update_bits(codec, ADAU1701_SEROCTL,
+ ~ADAU1701_SEROCTL_WORD_LEN_MASK, seroctl);
+
+ return 0;
+}
+
+static int adau1701_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ unsigned int mask = ADAU1701_AUXNPOW_VBPD | ADAU1701_AUXNPOW_VRPD;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* Enable VREF and VREF buffer */
+ snd_soc_update_bits(codec, ADAU1701_AUXNPOW, mask, 0x00);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* Disable VREF and VREF buffer */
+ snd_soc_update_bits(codec, ADAU1701_AUXNPOW, mask, mask);
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+static int adau1701_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ unsigned int mask = ADAU1701_DSPCTRL_DAM;
+ unsigned int val;
+
+ if (mute)
+ val = 0;
+ else
+ val = mask;
+
+ snd_soc_update_bits(codec, ADAU1701_DSPCTRL, mask, val);
+
+ return 0;
+}
+
+static int adau1701_set_sysclk(struct snd_soc_codec *codec, int clk_id,
+ unsigned int freq, int dir)
+{
+ unsigned int val;
+
+ switch (clk_id) {
+ case ADAU1701_CLK_SRC_OSC:
+ val = 0x0;
+ break;
+ case ADAU1701_CLK_SRC_MCLK:
+ val = ADAU1701_OSCIPOW_OPD;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, ADAU1701_OSCIPOW, ADAU1701_OSCIPOW_OPD, val);
+
+ return 0;
+}
+
+#define ADAU1701_RATES (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | \
+ SNDRV_PCM_RATE_192000)
+
+#define ADAU1701_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static const struct snd_soc_dai_ops adau1701_dai_ops = {
+ .set_fmt = adau1701_set_dai_fmt,
+ .hw_params = adau1701_hw_params,
+ .digital_mute = adau1701_digital_mute,
+};
+
+static struct snd_soc_dai_driver adau1701_dai = {
+ .name = "adau1701",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 8,
+ .rates = ADAU1701_RATES,
+ .formats = ADAU1701_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 8,
+ .rates = ADAU1701_RATES,
+ .formats = ADAU1701_FORMATS,
+ },
+ .ops = &adau1701_dai_ops,
+ .symmetric_rates = 1,
+};
+
+static int adau1701_probe(struct snd_soc_codec *codec)
+{
+ int ret;
+
+ codec->dapm.idle_bias_off = 1;
+
+ ret = adau1701_load_firmware(codec);
+ if (ret)
+ dev_warn(codec->dev, "Failed to load firmware\n");
+
+ snd_soc_write(codec, ADAU1701_DACSET, ADAU1701_DACSET_DACINIT);
+ snd_soc_write(codec, ADAU1701_DSPCTRL, ADAU1701_DSPCTRL_CR);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver adau1701_codec_drv = {
+ .probe = adau1701_probe,
+ .set_bias_level = adau1701_set_bias_level,
+
+ .reg_cache_size = ADAU1701_NUM_REGS,
+ .reg_word_size = sizeof(u16),
+
+ .controls = adau1701_controls,
+ .num_controls = ARRAY_SIZE(adau1701_controls),
+ .dapm_widgets = adau1701_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(adau1701_dapm_widgets),
+ .dapm_routes = adau1701_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(adau1701_dapm_routes),
+
+ .write = adau1701_write,
+ .read = adau1701_read,
+
+ .set_sysclk = adau1701_set_sysclk,
+};
+
+static __devinit int adau1701_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct adau1701 *adau1701;
+ int ret;
+
+ adau1701 = kzalloc(sizeof(*adau1701), GFP_KERNEL);
+ if (!adau1701)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, adau1701);
+ ret = snd_soc_register_codec(&client->dev, &adau1701_codec_drv,
+ &adau1701_dai, 1);
+ if (ret < 0)
+ kfree(adau1701);
+
+ return ret;
+}
+
+static __devexit int adau1701_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id adau1701_i2c_id[] = {
+ { "adau1701", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, adau1701_i2c_id);
+
+static struct i2c_driver adau1701_i2c_driver = {
+ .driver = {
+ .name = "adau1701",
+ .owner = THIS_MODULE,
+ },
+ .probe = adau1701_i2c_probe,
+ .remove = __devexit_p(adau1701_i2c_remove),
+ .id_table = adau1701_i2c_id,
+};
+
+static int __init adau1701_init(void)
+{
+ return i2c_add_driver(&adau1701_i2c_driver);
+}
+module_init(adau1701_init);
+
+static void __exit adau1701_exit(void)
+{
+ i2c_del_driver(&adau1701_i2c_driver);
+}
+module_exit(adau1701_exit);
+
+MODULE_DESCRIPTION("ASoC ADAU1701 SigmaDSP driver");
+MODULE_AUTHOR("Cliff Cai <cliff.cai@analog.com>");
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/adau1701.h b/sound/soc/codecs/adau1701.h
new file mode 100644
index 000000000000..8d0949a2aec9
--- /dev/null
+++ b/sound/soc/codecs/adau1701.h
@@ -0,0 +1,17 @@
+/*
+ * header file for ADAU1701 SigmaDSP processor
+ *
+ * Copyright 2011 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#ifndef _ADAU1701_H
+#define _ADAU1701_H
+
+enum adau1701_clk_src {
+ ADAU1701_CLK_SRC_OSC,
+ ADAU1701_CLK_SRC_MCLK,
+};
+
+#endif
diff --git a/sound/soc/codecs/adav80x.c b/sound/soc/codecs/adav80x.c
new file mode 100644
index 000000000000..e30fba62392d
--- /dev/null
+++ b/sound/soc/codecs/adav80x.c
@@ -0,0 +1,951 @@
+/*
+ * ADAV80X Audio Codec driver supporting ADAV801, ADAV803
+ *
+ * Copyright 2011 Analog Devices Inc.
+ * Author: Yi Li <yi.li@analog.com>
+ * Author: Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include <sound/soc.h>
+
+#include "adav80x.h"
+
+#define ADAV80X_PLAYBACK_CTRL 0x04
+#define ADAV80X_AUX_IN_CTRL 0x05
+#define ADAV80X_REC_CTRL 0x06
+#define ADAV80X_AUX_OUT_CTRL 0x07
+#define ADAV80X_DPATH_CTRL1 0x62
+#define ADAV80X_DPATH_CTRL2 0x63
+#define ADAV80X_DAC_CTRL1 0x64
+#define ADAV80X_DAC_CTRL2 0x65
+#define ADAV80X_DAC_CTRL3 0x66
+#define ADAV80X_DAC_L_VOL 0x68
+#define ADAV80X_DAC_R_VOL 0x69
+#define ADAV80X_PGA_L_VOL 0x6c
+#define ADAV80X_PGA_R_VOL 0x6d
+#define ADAV80X_ADC_CTRL1 0x6e
+#define ADAV80X_ADC_CTRL2 0x6f
+#define ADAV80X_ADC_L_VOL 0x70
+#define ADAV80X_ADC_R_VOL 0x71
+#define ADAV80X_PLL_CTRL1 0x74
+#define ADAV80X_PLL_CTRL2 0x75
+#define ADAV80X_ICLK_CTRL1 0x76
+#define ADAV80X_ICLK_CTRL2 0x77
+#define ADAV80X_PLL_CLK_SRC 0x78
+#define ADAV80X_PLL_OUTE 0x7a
+
+#define ADAV80X_PLL_CLK_SRC_PLL_XIN(pll) 0x00
+#define ADAV80X_PLL_CLK_SRC_PLL_MCLKI(pll) (0x40 << (pll))
+#define ADAV80X_PLL_CLK_SRC_PLL_MASK(pll) (0x40 << (pll))
+
+#define ADAV80X_ICLK_CTRL1_DAC_SRC(src) ((src) << 5)
+#define ADAV80X_ICLK_CTRL1_ADC_SRC(src) ((src) << 2)
+#define ADAV80X_ICLK_CTRL1_ICLK2_SRC(src) (src)
+#define ADAV80X_ICLK_CTRL2_ICLK1_SRC(src) ((src) << 3)
+
+#define ADAV80X_PLL_CTRL1_PLLDIV 0x10
+#define ADAV80X_PLL_CTRL1_PLLPD(pll) (0x04 << (pll))
+#define ADAV80X_PLL_CTRL1_XTLPD 0x02
+
+#define ADAV80X_PLL_CTRL2_FIELD(pll, x) ((x) << ((pll) * 4))
+
+#define ADAV80X_PLL_CTRL2_FS_48(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x00)
+#define ADAV80X_PLL_CTRL2_FS_32(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x08)
+#define ADAV80X_PLL_CTRL2_FS_44(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x0c)
+
+#define ADAV80X_PLL_CTRL2_SEL(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x02)
+#define ADAV80X_PLL_CTRL2_DOUB(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x01)
+#define ADAV80X_PLL_CTRL2_PLL_MASK(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x0f)
+
+#define ADAV80X_ADC_CTRL1_MODULATOR_MASK 0x80
+#define ADAV80X_ADC_CTRL1_MODULATOR_128FS 0x00
+#define ADAV80X_ADC_CTRL1_MODULATOR_64FS 0x80
+
+#define ADAV80X_DAC_CTRL1_PD 0x80
+
+#define ADAV80X_DAC_CTRL2_DIV1 0x00
+#define ADAV80X_DAC_CTRL2_DIV1_5 0x10
+#define ADAV80X_DAC_CTRL2_DIV2 0x20
+#define ADAV80X_DAC_CTRL2_DIV3 0x30
+#define ADAV80X_DAC_CTRL2_DIV_MASK 0x30
+
+#define ADAV80X_DAC_CTRL2_INTERPOL_256FS 0x00
+#define ADAV80X_DAC_CTRL2_INTERPOL_128FS 0x40
+#define ADAV80X_DAC_CTRL2_INTERPOL_64FS 0x80
+#define ADAV80X_DAC_CTRL2_INTERPOL_MASK 0xc0
+
+#define ADAV80X_DAC_CTRL2_DEEMPH_NONE 0x00
+#define ADAV80X_DAC_CTRL2_DEEMPH_44 0x01
+#define ADAV80X_DAC_CTRL2_DEEMPH_32 0x02
+#define ADAV80X_DAC_CTRL2_DEEMPH_48 0x03
+#define ADAV80X_DAC_CTRL2_DEEMPH_MASK 0x01
+
+#define ADAV80X_CAPTURE_MODE_MASTER 0x20
+#define ADAV80X_CAPTURE_WORD_LEN24 0x00
+#define ADAV80X_CAPTURE_WORD_LEN20 0x04
+#define ADAV80X_CAPTRUE_WORD_LEN18 0x08
+#define ADAV80X_CAPTURE_WORD_LEN16 0x0c
+#define ADAV80X_CAPTURE_WORD_LEN_MASK 0x0c
+
+#define ADAV80X_CAPTURE_MODE_LEFT_J 0x00
+#define ADAV80X_CAPTURE_MODE_I2S 0x01
+#define ADAV80X_CAPTURE_MODE_RIGHT_J 0x03
+#define ADAV80X_CAPTURE_MODE_MASK 0x03
+
+#define ADAV80X_PLAYBACK_MODE_MASTER 0x10
+#define ADAV80X_PLAYBACK_MODE_LEFT_J 0x00
+#define ADAV80X_PLAYBACK_MODE_I2S 0x01
+#define ADAV80X_PLAYBACK_MODE_RIGHT_J_24 0x04
+#define ADAV80X_PLAYBACK_MODE_RIGHT_J_20 0x05
+#define ADAV80X_PLAYBACK_MODE_RIGHT_J_18 0x06
+#define ADAV80X_PLAYBACK_MODE_RIGHT_J_16 0x07
+#define ADAV80X_PLAYBACK_MODE_MASK 0x07
+
+#define ADAV80X_PLL_OUTE_SYSCLKPD(x) BIT(2 - (x))
+
+static u8 adav80x_default_regs[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x01, 0x80, 0x26, 0x00, 0x00,
+ 0x02, 0x40, 0x20, 0x00, 0x09, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd1, 0x92, 0xb1, 0x37,
+ 0x48, 0xd2, 0xfb, 0xca, 0xd2, 0x15, 0xe8, 0x29, 0xb9, 0x6a, 0xda, 0x2b,
+ 0xb7, 0xc0, 0x11, 0x65, 0x5c, 0xf6, 0xff, 0x8d, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x00, 0x00,
+ 0x00, 0xe8, 0x46, 0xe1, 0x5b, 0xd3, 0x43, 0x77, 0x93, 0xa7, 0x44, 0xee,
+ 0x32, 0x12, 0xc0, 0x11, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x3f, 0x3f,
+ 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x52, 0x00,
+};
+
+struct adav80x {
+ enum snd_soc_control_type control_type;
+
+ enum adav80x_clk_src clk_src;
+ unsigned int sysclk;
+ enum adav80x_pll_src pll_src;
+
+ unsigned int dai_fmt[2];
+ unsigned int rate;
+ bool deemph;
+ bool sysclk_pd[3];
+};
+
+static const char *adav80x_mux_text[] = {
+ "ADC",
+ "Playback",
+ "Aux Playback",
+};
+
+static const unsigned int adav80x_mux_values[] = {
+ 0, 2, 3,
+};
+
+#define ADAV80X_MUX_ENUM_DECL(name, reg, shift) \
+ SOC_VALUE_ENUM_DOUBLE_DECL(name, reg, shift, 7, \
+ ARRAY_SIZE(adav80x_mux_text), adav80x_mux_text, \
+ adav80x_mux_values)
+
+static ADAV80X_MUX_ENUM_DECL(adav80x_aux_capture_enum, ADAV80X_DPATH_CTRL1, 0);
+static ADAV80X_MUX_ENUM_DECL(adav80x_capture_enum, ADAV80X_DPATH_CTRL1, 3);
+static ADAV80X_MUX_ENUM_DECL(adav80x_dac_enum, ADAV80X_DPATH_CTRL2, 3);
+
+static const struct snd_kcontrol_new adav80x_aux_capture_mux_ctrl =
+ SOC_DAPM_VALUE_ENUM("Route", adav80x_aux_capture_enum);
+static const struct snd_kcontrol_new adav80x_capture_mux_ctrl =
+ SOC_DAPM_VALUE_ENUM("Route", adav80x_capture_enum);
+static const struct snd_kcontrol_new adav80x_dac_mux_ctrl =
+ SOC_DAPM_VALUE_ENUM("Route", adav80x_dac_enum);
+
+#define ADAV80X_MUX(name, ctrl) \
+ SND_SOC_DAPM_VALUE_MUX(name, SND_SOC_NOPM, 0, 0, ctrl)
+
+static const struct snd_soc_dapm_widget adav80x_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC("DAC", NULL, ADAV80X_DAC_CTRL1, 7, 1),
+ SND_SOC_DAPM_ADC("ADC", NULL, ADAV80X_ADC_CTRL1, 5, 1),
+
+ SND_SOC_DAPM_PGA("Right PGA", ADAV80X_ADC_CTRL1, 0, 1, NULL, 0),
+ SND_SOC_DAPM_PGA("Left PGA", ADAV80X_ADC_CTRL1, 1, 1, NULL, 0),
+
+ SND_SOC_DAPM_AIF_OUT("AIFOUT", "HiFi Capture", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("AIFIN", "HiFi Playback", 0, SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_AIF_OUT("AIFAUXOUT", "Aux Capture", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("AIFAUXIN", "Aux Playback", 0, SND_SOC_NOPM, 0, 0),
+
+ ADAV80X_MUX("Aux Capture Select", &adav80x_aux_capture_mux_ctrl),
+ ADAV80X_MUX("Capture Select", &adav80x_capture_mux_ctrl),
+ ADAV80X_MUX("DAC Select", &adav80x_dac_mux_ctrl),
+
+ SND_SOC_DAPM_INPUT("VINR"),
+ SND_SOC_DAPM_INPUT("VINL"),
+ SND_SOC_DAPM_OUTPUT("VOUTR"),
+ SND_SOC_DAPM_OUTPUT("VOUTL"),
+
+ SND_SOC_DAPM_SUPPLY("SYSCLK", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("PLL1", ADAV80X_PLL_CTRL1, 2, 1, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("PLL2", ADAV80X_PLL_CTRL1, 3, 1, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("OSC", ADAV80X_PLL_CTRL1, 1, 1, NULL, 0),
+};
+
+static int adav80x_dapm_sysclk_check(struct snd_soc_dapm_widget *source,
+ struct snd_soc_dapm_widget *sink)
+{
+ struct snd_soc_codec *codec = source->codec;
+ struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
+ const char *clk;
+
+ switch (adav80x->clk_src) {
+ case ADAV80X_CLK_PLL1:
+ clk = "PLL1";
+ break;
+ case ADAV80X_CLK_PLL2:
+ clk = "PLL2";
+ break;
+ case ADAV80X_CLK_XTAL:
+ clk = "OSC";
+ break;
+ default:
+ return 0;
+ }
+
+ return strcmp(source->name, clk) == 0;
+}
+
+static int adav80x_dapm_pll_check(struct snd_soc_dapm_widget *source,
+ struct snd_soc_dapm_widget *sink)
+{
+ struct snd_soc_codec *codec = source->codec;
+ struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
+
+ return adav80x->pll_src == ADAV80X_PLL_SRC_XTAL;
+}
+
+
+static const struct snd_soc_dapm_route adav80x_dapm_routes[] = {
+ { "DAC Select", "ADC", "ADC" },
+ { "DAC Select", "Playback", "AIFIN" },
+ { "DAC Select", "Aux Playback", "AIFAUXIN" },
+ { "DAC", NULL, "DAC Select" },
+
+ { "Capture Select", "ADC", "ADC" },
+ { "Capture Select", "Playback", "AIFIN" },
+ { "Capture Select", "Aux Playback", "AIFAUXIN" },
+ { "AIFOUT", NULL, "Capture Select" },
+
+ { "Aux Capture Select", "ADC", "ADC" },
+ { "Aux Capture Select", "Playback", "AIFIN" },
+ { "Aux Capture Select", "Aux Playback", "AIFAUXIN" },
+ { "AIFAUXOUT", NULL, "Aux Capture Select" },
+
+ { "VOUTR", NULL, "DAC" },
+ { "VOUTL", NULL, "DAC" },
+
+ { "Left PGA", NULL, "VINL" },
+ { "Right PGA", NULL, "VINR" },
+ { "ADC", NULL, "Left PGA" },
+ { "ADC", NULL, "Right PGA" },
+
+ { "SYSCLK", NULL, "PLL1", adav80x_dapm_sysclk_check },
+ { "SYSCLK", NULL, "PLL2", adav80x_dapm_sysclk_check },
+ { "SYSCLK", NULL, "OSC", adav80x_dapm_sysclk_check },
+ { "PLL1", NULL, "OSC", adav80x_dapm_pll_check },
+ { "PLL2", NULL, "OSC", adav80x_dapm_pll_check },
+
+ { "ADC", NULL, "SYSCLK" },
+ { "DAC", NULL, "SYSCLK" },
+ { "AIFOUT", NULL, "SYSCLK" },
+ { "AIFAUXOUT", NULL, "SYSCLK" },
+ { "AIFIN", NULL, "SYSCLK" },
+ { "AIFAUXIN", NULL, "SYSCLK" },
+};
+
+static int adav80x_set_deemph(struct snd_soc_codec *codec)
+{
+ struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
+ unsigned int val;
+
+ if (adav80x->deemph) {
+ switch (adav80x->rate) {
+ case 32000:
+ val = ADAV80X_DAC_CTRL2_DEEMPH_32;
+ break;
+ case 44100:
+ val = ADAV80X_DAC_CTRL2_DEEMPH_44;
+ break;
+ case 48000:
+ case 64000:
+ case 88200:
+ case 96000:
+ val = ADAV80X_DAC_CTRL2_DEEMPH_48;
+ break;
+ default:
+ val = ADAV80X_DAC_CTRL2_DEEMPH_NONE;
+ break;
+ }
+ } else {
+ val = ADAV80X_DAC_CTRL2_DEEMPH_NONE;
+ }
+
+ return snd_soc_update_bits(codec, ADAV80X_DAC_CTRL2,
+ ADAV80X_DAC_CTRL2_DEEMPH_MASK, val);
+}
+
+static int adav80x_put_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
+ unsigned int deemph = ucontrol->value.enumerated.item[0];
+
+ if (deemph > 1)
+ return -EINVAL;
+
+ adav80x->deemph = deemph;
+
+ return adav80x_set_deemph(codec);
+}
+
+static int adav80x_get_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = adav80x->deemph;
+ return 0;
+};
+
+static const DECLARE_TLV_DB_SCALE(adav80x_inpga_tlv, 0, 50, 0);
+static const DECLARE_TLV_DB_MINMAX(adav80x_digital_tlv, -9563, 0);
+
+static const struct snd_kcontrol_new adav80x_controls[] = {
+ SOC_DOUBLE_R_TLV("Master Playback Volume", ADAV80X_DAC_L_VOL,
+ ADAV80X_DAC_R_VOL, 0, 0xff, 0, adav80x_digital_tlv),
+ SOC_DOUBLE_R_TLV("Master Capture Volume", ADAV80X_ADC_L_VOL,
+ ADAV80X_ADC_R_VOL, 0, 0xff, 0, adav80x_digital_tlv),
+
+ SOC_DOUBLE_R_TLV("PGA Capture Volume", ADAV80X_PGA_L_VOL,
+ ADAV80X_PGA_R_VOL, 0, 0x30, 0, adav80x_inpga_tlv),
+
+ SOC_DOUBLE("Master Playback Switch", ADAV80X_DAC_CTRL1, 0, 1, 1, 0),
+ SOC_DOUBLE("Master Capture Switch", ADAV80X_ADC_CTRL1, 2, 3, 1, 1),
+
+ SOC_SINGLE("ADC High Pass Filter Switch", ADAV80X_ADC_CTRL1, 6, 1, 0),
+
+ SOC_SINGLE_BOOL_EXT("Playback De-emphasis Switch", 0,
+ adav80x_get_deemph, adav80x_put_deemph),
+};
+
+static unsigned int adav80x_port_ctrl_regs[2][2] = {
+ { ADAV80X_REC_CTRL, ADAV80X_PLAYBACK_CTRL, },
+ { ADAV80X_AUX_OUT_CTRL, ADAV80X_AUX_IN_CTRL },
+};
+
+static int adav80x_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
+ unsigned int capture = 0x00;
+ unsigned int playback = 0x00;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ capture |= ADAV80X_CAPTURE_MODE_MASTER;
+ playback |= ADAV80X_PLAYBACK_MODE_MASTER;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ capture |= ADAV80X_CAPTURE_MODE_I2S;
+ playback |= ADAV80X_PLAYBACK_MODE_I2S;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ capture |= ADAV80X_CAPTURE_MODE_LEFT_J;
+ playback |= ADAV80X_PLAYBACK_MODE_LEFT_J;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ capture |= ADAV80X_CAPTURE_MODE_RIGHT_J;
+ playback |= ADAV80X_PLAYBACK_MODE_RIGHT_J_24;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, adav80x_port_ctrl_regs[dai->id][0],
+ ADAV80X_CAPTURE_MODE_MASK | ADAV80X_CAPTURE_MODE_MASTER,
+ capture);
+ snd_soc_write(codec, adav80x_port_ctrl_regs[dai->id][1], playback);
+
+ adav80x->dai_fmt[dai->id] = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
+
+ return 0;
+}
+
+static int adav80x_set_adc_clock(struct snd_soc_codec *codec,
+ unsigned int sample_rate)
+{
+ unsigned int val;
+
+ if (sample_rate <= 48000)
+ val = ADAV80X_ADC_CTRL1_MODULATOR_128FS;
+ else
+ val = ADAV80X_ADC_CTRL1_MODULATOR_64FS;
+
+ snd_soc_update_bits(codec, ADAV80X_ADC_CTRL1,
+ ADAV80X_ADC_CTRL1_MODULATOR_MASK, val);
+
+ return 0;
+}
+
+static int adav80x_set_dac_clock(struct snd_soc_codec *codec,
+ unsigned int sample_rate)
+{
+ unsigned int val;
+
+ if (sample_rate <= 48000)
+ val = ADAV80X_DAC_CTRL2_DIV1 | ADAV80X_DAC_CTRL2_INTERPOL_256FS;
+ else
+ val = ADAV80X_DAC_CTRL2_DIV2 | ADAV80X_DAC_CTRL2_INTERPOL_128FS;
+
+ snd_soc_update_bits(codec, ADAV80X_DAC_CTRL2,
+ ADAV80X_DAC_CTRL2_DIV_MASK | ADAV80X_DAC_CTRL2_INTERPOL_MASK,
+ val);
+
+ return 0;
+}
+
+static int adav80x_set_capture_pcm_format(struct snd_soc_codec *codec,
+ struct snd_soc_dai *dai, snd_pcm_format_t format)
+{
+ unsigned int val;
+
+ switch (format) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ val = ADAV80X_CAPTURE_WORD_LEN16;
+ break;
+ case SNDRV_PCM_FORMAT_S18_3LE:
+ val = ADAV80X_CAPTRUE_WORD_LEN18;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ val = ADAV80X_CAPTURE_WORD_LEN20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ val = ADAV80X_CAPTURE_WORD_LEN24;
+ break;
+ default:
+ break;
+ }
+
+ snd_soc_update_bits(codec, adav80x_port_ctrl_regs[dai->id][0],
+ ADAV80X_CAPTURE_WORD_LEN_MASK, val);
+
+ return 0;
+}
+
+static int adav80x_set_playback_pcm_format(struct snd_soc_codec *codec,
+ struct snd_soc_dai *dai, snd_pcm_format_t format)
+{
+ struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
+ unsigned int val;
+
+ if (adav80x->dai_fmt[dai->id] != SND_SOC_DAIFMT_RIGHT_J)
+ return 0;
+
+ switch (format) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ val = ADAV80X_PLAYBACK_MODE_RIGHT_J_16;
+ break;
+ case SNDRV_PCM_FORMAT_S18_3LE:
+ val = ADAV80X_PLAYBACK_MODE_RIGHT_J_18;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ val = ADAV80X_PLAYBACK_MODE_RIGHT_J_20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ val = ADAV80X_PLAYBACK_MODE_RIGHT_J_24;
+ break;
+ default:
+ break;
+ }
+
+ snd_soc_update_bits(codec, adav80x_port_ctrl_regs[dai->id][1],
+ ADAV80X_PLAYBACK_MODE_MASK, val);
+
+ return 0;
+}
+
+static int adav80x_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
+ unsigned int rate = params_rate(params);
+
+ if (rate * 256 != adav80x->sysclk)
+ return -EINVAL;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ adav80x_set_playback_pcm_format(codec, dai,
+ params_format(params));
+ adav80x_set_dac_clock(codec, rate);
+ } else {
+ adav80x_set_capture_pcm_format(codec, dai,
+ params_format(params));
+ adav80x_set_adc_clock(codec, rate);
+ }
+ adav80x->rate = rate;
+ adav80x_set_deemph(codec);
+
+ return 0;
+}
+
+static int adav80x_set_sysclk(struct snd_soc_codec *codec,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
+
+ if (dir == SND_SOC_CLOCK_IN) {
+ switch (clk_id) {
+ case ADAV80X_CLK_XIN:
+ case ADAV80X_CLK_XTAL:
+ case ADAV80X_CLK_MCLKI:
+ case ADAV80X_CLK_PLL1:
+ case ADAV80X_CLK_PLL2:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ adav80x->sysclk = freq;
+
+ if (adav80x->clk_src != clk_id) {
+ unsigned int iclk_ctrl1, iclk_ctrl2;
+
+ adav80x->clk_src = clk_id;
+ if (clk_id == ADAV80X_CLK_XTAL)
+ clk_id = ADAV80X_CLK_XIN;
+
+ iclk_ctrl1 = ADAV80X_ICLK_CTRL1_DAC_SRC(clk_id) |
+ ADAV80X_ICLK_CTRL1_ADC_SRC(clk_id) |
+ ADAV80X_ICLK_CTRL1_ICLK2_SRC(clk_id);
+ iclk_ctrl2 = ADAV80X_ICLK_CTRL2_ICLK1_SRC(clk_id);
+
+ snd_soc_write(codec, ADAV80X_ICLK_CTRL1, iclk_ctrl1);
+ snd_soc_write(codec, ADAV80X_ICLK_CTRL2, iclk_ctrl2);
+
+ snd_soc_dapm_sync(&codec->dapm);
+ }
+ } else {
+ unsigned int mask;
+
+ switch (clk_id) {
+ case ADAV80X_CLK_SYSCLK1:
+ case ADAV80X_CLK_SYSCLK2:
+ case ADAV80X_CLK_SYSCLK3:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ clk_id -= ADAV80X_CLK_SYSCLK1;
+ mask = ADAV80X_PLL_OUTE_SYSCLKPD(clk_id);
+
+ if (freq == 0) {
+ snd_soc_update_bits(codec, ADAV80X_PLL_OUTE, mask, mask);
+ adav80x->sysclk_pd[clk_id] = true;
+ } else {
+ snd_soc_update_bits(codec, ADAV80X_PLL_OUTE, mask, 0);
+ adav80x->sysclk_pd[clk_id] = false;
+ }
+
+ if (adav80x->sysclk_pd[0])
+ snd_soc_dapm_disable_pin(&codec->dapm, "PLL1");
+ else
+ snd_soc_dapm_force_enable_pin(&codec->dapm, "PLL1");
+
+ if (adav80x->sysclk_pd[1] || adav80x->sysclk_pd[2])
+ snd_soc_dapm_disable_pin(&codec->dapm, "PLL2");
+ else
+ snd_soc_dapm_force_enable_pin(&codec->dapm, "PLL2");
+
+ snd_soc_dapm_sync(&codec->dapm);
+ }
+
+ return 0;
+}
+
+static int adav80x_set_pll(struct snd_soc_codec *codec, int pll_id,
+ int source, unsigned int freq_in, unsigned int freq_out)
+{
+ struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
+ unsigned int pll_ctrl1 = 0;
+ unsigned int pll_ctrl2 = 0;
+ unsigned int pll_src;
+
+ switch (source) {
+ case ADAV80X_PLL_SRC_XTAL:
+ case ADAV80X_PLL_SRC_XIN:
+ case ADAV80X_PLL_SRC_MCLKI:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!freq_out)
+ return 0;
+
+ switch (freq_in) {
+ case 27000000:
+ break;
+ case 54000000:
+ if (source == ADAV80X_PLL_SRC_XIN) {
+ pll_ctrl1 |= ADAV80X_PLL_CTRL1_PLLDIV;
+ break;
+ }
+ default:
+ return -EINVAL;
+ }
+
+ if (freq_out > 12288000) {
+ pll_ctrl2 |= ADAV80X_PLL_CTRL2_DOUB(pll_id);
+ freq_out /= 2;
+ }
+
+ /* freq_out = sample_rate * 256 */
+ switch (freq_out) {
+ case 8192000:
+ pll_ctrl2 |= ADAV80X_PLL_CTRL2_FS_32(pll_id);
+ break;
+ case 11289600:
+ pll_ctrl2 |= ADAV80X_PLL_CTRL2_FS_44(pll_id);
+ break;
+ case 12288000:
+ pll_ctrl2 |= ADAV80X_PLL_CTRL2_FS_48(pll_id);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, ADAV80X_PLL_CTRL1, ADAV80X_PLL_CTRL1_PLLDIV,
+ pll_ctrl1);
+ snd_soc_update_bits(codec, ADAV80X_PLL_CTRL2,
+ ADAV80X_PLL_CTRL2_PLL_MASK(pll_id), pll_ctrl2);
+
+ if (source != adav80x->pll_src) {
+ if (source == ADAV80X_PLL_SRC_MCLKI)
+ pll_src = ADAV80X_PLL_CLK_SRC_PLL_MCLKI(pll_id);
+ else
+ pll_src = ADAV80X_PLL_CLK_SRC_PLL_XIN(pll_id);
+
+ snd_soc_update_bits(codec, ADAV80X_PLL_CLK_SRC,
+ ADAV80X_PLL_CLK_SRC_PLL_MASK(pll_id), pll_src);
+
+ adav80x->pll_src = source;
+
+ snd_soc_dapm_sync(&codec->dapm);
+ }
+
+ return 0;
+}
+
+static int adav80x_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ unsigned int mask = ADAV80X_DAC_CTRL1_PD;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ snd_soc_update_bits(codec, ADAV80X_DAC_CTRL1, mask, 0x00);
+ break;
+ case SND_SOC_BIAS_OFF:
+ snd_soc_update_bits(codec, ADAV80X_DAC_CTRL1, mask, mask);
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+/* Enforce the same sample rate on all audio interfaces */
+static int adav80x_dai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
+
+ if (!codec->active || !adav80x->rate)
+ return 0;
+
+ return snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE, adav80x->rate, adav80x->rate);
+}
+
+static void adav80x_dai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
+
+ if (!codec->active)
+ adav80x->rate = 0;
+}
+
+static const struct snd_soc_dai_ops adav80x_dai_ops = {
+ .set_fmt = adav80x_set_dai_fmt,
+ .hw_params = adav80x_hw_params,
+ .startup = adav80x_dai_startup,
+ .shutdown = adav80x_dai_shutdown,
+};
+
+#define ADAV80X_PLAYBACK_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | SNDRV_PCM_RATE_88200 | \
+ SNDRV_PCM_RATE_96000)
+
+#define ADAV80X_CAPTURE_RATES (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)
+
+#define ADAV80X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_driver adav80x_dais[] = {
+ {
+ .name = "adav80x-hifi",
+ .id = 0,
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = ADAV80X_PLAYBACK_RATES,
+ .formats = ADAV80X_FORMATS,
+ },
+ .capture = {
+ .stream_name = "HiFi Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = ADAV80X_CAPTURE_RATES,
+ .formats = ADAV80X_FORMATS,
+ },
+ .ops = &adav80x_dai_ops,
+ },
+ {
+ .name = "adav80x-aux",
+ .id = 1,
+ .playback = {
+ .stream_name = "Aux Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = ADAV80X_PLAYBACK_RATES,
+ .formats = ADAV80X_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Aux Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = ADAV80X_CAPTURE_RATES,
+ .formats = ADAV80X_FORMATS,
+ },
+ .ops = &adav80x_dai_ops,
+ },
+};
+
+static int adav80x_probe(struct snd_soc_codec *codec)
+{
+ int ret;
+ struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec);
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, adav80x->control_type);
+ if (ret) {
+ dev_err(codec->dev, "failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ /* Force PLLs on for SYSCLK output */
+ snd_soc_dapm_force_enable_pin(&codec->dapm, "PLL1");
+ snd_soc_dapm_force_enable_pin(&codec->dapm, "PLL2");
+
+ /* Power down S/PDIF receiver, since it is currently not supported */
+ snd_soc_write(codec, ADAV80X_PLL_OUTE, 0x20);
+ /* Disable DAC zero flag */
+ snd_soc_write(codec, ADAV80X_DAC_CTRL3, 0x6);
+
+ return adav80x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+}
+
+static int adav80x_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ return adav80x_set_bias_level(codec, SND_SOC_BIAS_OFF);
+}
+
+static int adav80x_resume(struct snd_soc_codec *codec)
+{
+ adav80x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ codec->cache_sync = 1;
+ snd_soc_cache_sync(codec);
+
+ return 0;
+}
+
+static int adav80x_remove(struct snd_soc_codec *codec)
+{
+ return adav80x_set_bias_level(codec, SND_SOC_BIAS_OFF);
+}
+
+static struct snd_soc_codec_driver adav80x_codec_driver = {
+ .probe = adav80x_probe,
+ .remove = adav80x_remove,
+ .suspend = adav80x_suspend,
+ .resume = adav80x_resume,
+ .set_bias_level = adav80x_set_bias_level,
+
+ .set_pll = adav80x_set_pll,
+ .set_sysclk = adav80x_set_sysclk,
+
+ .reg_word_size = sizeof(u8),
+ .reg_cache_size = ARRAY_SIZE(adav80x_default_regs),
+ .reg_cache_default = adav80x_default_regs,
+
+ .controls = adav80x_controls,
+ .num_controls = ARRAY_SIZE(adav80x_controls),
+ .dapm_widgets = adav80x_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(adav80x_dapm_widgets),
+ .dapm_routes = adav80x_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(adav80x_dapm_routes),
+};
+
+static int __devinit adav80x_bus_probe(struct device *dev,
+ enum snd_soc_control_type control_type)
+{
+ struct adav80x *adav80x;
+ int ret;
+
+ adav80x = kzalloc(sizeof(*adav80x), GFP_KERNEL);
+ if (!adav80x)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, adav80x);
+ adav80x->control_type = control_type;
+
+ ret = snd_soc_register_codec(dev, &adav80x_codec_driver,
+ adav80x_dais, ARRAY_SIZE(adav80x_dais));
+ if (ret)
+ kfree(adav80x);
+
+ return ret;
+}
+
+static int __devexit adav80x_bus_remove(struct device *dev)
+{
+ snd_soc_unregister_codec(dev);
+ kfree(dev_get_drvdata(dev));
+ return 0;
+}
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit adav80x_spi_probe(struct spi_device *spi)
+{
+ return adav80x_bus_probe(&spi->dev, SND_SOC_SPI);
+}
+
+static int __devexit adav80x_spi_remove(struct spi_device *spi)
+{
+ return adav80x_bus_remove(&spi->dev);
+}
+
+static struct spi_driver adav80x_spi_driver = {
+ .driver = {
+ .name = "adav801",
+ .owner = THIS_MODULE,
+ },
+ .probe = adav80x_spi_probe,
+ .remove = __devexit_p(adav80x_spi_remove),
+};
+#endif
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static const struct i2c_device_id adav80x_id[] = {
+ { "adav803", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, adav80x_id);
+
+static int __devinit adav80x_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ return adav80x_bus_probe(&client->dev, SND_SOC_I2C);
+}
+
+static int __devexit adav80x_i2c_remove(struct i2c_client *client)
+{
+ return adav80x_bus_remove(&client->dev);
+}
+
+static struct i2c_driver adav80x_i2c_driver = {
+ .driver = {
+ .name = "adav803",
+ .owner = THIS_MODULE,
+ },
+ .probe = adav80x_i2c_probe,
+ .remove = __devexit_p(adav80x_i2c_remove),
+ .id_table = adav80x_id,
+};
+#endif
+
+static int __init adav80x_init(void)
+{
+ int ret = 0;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&adav80x_i2c_driver);
+ if (ret)
+ return ret;
+#endif
+
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&adav80x_spi_driver);
+#endif
+
+ return ret;
+}
+module_init(adav80x_init);
+
+static void __exit adav80x_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&adav80x_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&adav80x_spi_driver);
+#endif
+}
+module_exit(adav80x_exit);
+
+MODULE_DESCRIPTION("ASoC ADAV80x driver");
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_AUTHOR("Yi Li <yi.li@analog.com>>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/adav80x.h b/sound/soc/codecs/adav80x.h
new file mode 100644
index 000000000000..adb0fc76d4e3
--- /dev/null
+++ b/sound/soc/codecs/adav80x.h
@@ -0,0 +1,35 @@
+/*
+ * header file for ADAV80X parts
+ *
+ * Copyright 2011 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#ifndef _ADAV80X_H
+#define _ADAV80X_H
+
+enum adav80x_pll_src {
+ ADAV80X_PLL_SRC_XIN,
+ ADAV80X_PLL_SRC_XTAL,
+ ADAV80X_PLL_SRC_MCLKI,
+};
+
+enum adav80x_pll {
+ ADAV80X_PLL1 = 0,
+ ADAV80X_PLL2 = 1,
+};
+
+enum adav80x_clk_src {
+ ADAV80X_CLK_XIN = 0,
+ ADAV80X_CLK_MCLKI = 1,
+ ADAV80X_CLK_PLL1 = 2,
+ ADAV80X_CLK_PLL2 = 3,
+ ADAV80X_CLK_XTAL = 6,
+
+ ADAV80X_CLK_SYSCLK1 = 6,
+ ADAV80X_CLK_SYSCLK2 = 7,
+ ADAV80X_CLK_SYSCLK3 = 8,
+};
+
+#endif
diff --git a/sound/soc/codecs/ak4641.c b/sound/soc/codecs/ak4641.c
index ed96f247c2da..7a64e58cddc4 100644
--- a/sound/soc/codecs/ak4641.c
+++ b/sound/soc/codecs/ak4641.c
@@ -457,7 +457,7 @@ static struct snd_soc_dai_ops ak4641_pcm_dai_ops = {
.set_sysclk = ak4641_set_dai_sysclk,
};
-struct snd_soc_dai_driver ak4641_dai[] = {
+static struct snd_soc_dai_driver ak4641_dai[] = {
{
.name = "ak4641-hifi",
.id = 1,
diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c
index 0206a17d7283..6cc8678f49f3 100644
--- a/sound/soc/codecs/cs4270.c
+++ b/sound/soc/codecs/cs4270.c
@@ -636,10 +636,7 @@ static int cs4270_soc_resume(struct snd_soc_codec *codec)
#endif /* CONFIG_PM */
/*
- * ASoC codec device structure
- *
- * Assign this variable to the codec_dev field of the machine driver's
- * snd_soc_device structure.
+ * ASoC codec driver structure
*/
static const struct snd_soc_codec_driver soc_codec_device_cs4270 = {
.probe = cs4270_probe,
diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c
index 4173b67c94d1..ac65a2d36408 100644
--- a/sound/soc/codecs/max98088.c
+++ b/sound/soc/codecs/max98088.c
@@ -1397,8 +1397,6 @@ static int max98088_dai_set_sysclk(struct snd_soc_dai *dai,
if (freq == max98088->sysclk)
return 0;
- max98088->sysclk = freq; /* remember current sysclk */
-
/* Setup clocks for slave mode, and using the PLL
* PSCLK = 0x01 (when master clk is 10MHz to 20MHz)
* 0x02 (when master clk is 20MHz to 30MHz)..
diff --git a/sound/soc/codecs/max98095.c b/sound/soc/codecs/max98095.c
index e1d282d477da..668434d44303 100644
--- a/sound/soc/codecs/max98095.c
+++ b/sound/soc/codecs/max98095.c
@@ -1517,8 +1517,6 @@ static int max98095_dai_set_sysclk(struct snd_soc_dai *dai,
if (freq == max98095->sysclk)
return 0;
- max98095->sysclk = freq; /* remember current sysclk */
-
/* Setup clocks for slave mode, and using the PLL
* PSCLK = 0x01 (when master clk is 10MHz to 20MHz)
* 0x02 (when master clk is 20MHz to 40MHz)..
@@ -2261,11 +2259,11 @@ static int max98095_probe(struct snd_soc_codec *codec)
ret = snd_soc_read(codec, M98095_0FF_REV_ID);
if (ret < 0) {
- dev_err(codec->dev, "Failed to read device revision: %d\n",
+ dev_err(codec->dev, "Failure reading hardware revision: %d\n",
ret);
goto err_access;
}
- dev_info(codec->dev, "revision %c\n", ret + 'A');
+ dev_info(codec->dev, "Hardware revision: %c\n", ret - 0x40 + 'A');
snd_soc_write(codec, M98095_097_PWR_SYS, M98095_PWRSV);
@@ -2342,8 +2340,8 @@ static int max98095_i2c_probe(struct i2c_client *i2c,
max98095->control_data = i2c;
max98095->pdata = i2c->dev.platform_data;
- ret = snd_soc_register_codec(&i2c->dev,
- &soc_codec_dev_max98095, &max98095_dai[0], 3);
+ ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_max98095,
+ max98095_dai, ARRAY_SIZE(max98095_dai));
if (ret < 0)
kfree(max98095);
return ret;
diff --git a/sound/soc/codecs/sta32x.c b/sound/soc/codecs/sta32x.c
new file mode 100644
index 000000000000..409d89d1f34c
--- /dev/null
+++ b/sound/soc/codecs/sta32x.c
@@ -0,0 +1,917 @@
+/*
+ * Codec driver for ST STA32x 2.1-channel high-efficiency digital audio system
+ *
+ * Copyright: 2011 Raumfeld GmbH
+ * Author: Johannes Stezenbach <js@sig21.net>
+ *
+ * based on code from:
+ * Wolfson Microelectronics PLC.
+ * Mark Brown <broonie@opensource.wolfsonmicro.com>
+ * Freescale Semiconductor, Inc.
+ * Timur Tabi <timur@freescale.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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ":%s:%d: " fmt, __func__, __LINE__
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.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 "sta32x.h"
+
+#define STA32X_RATES (SNDRV_PCM_RATE_32000 | \
+ SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | \
+ SNDRV_PCM_RATE_88200 | \
+ SNDRV_PCM_RATE_96000 | \
+ SNDRV_PCM_RATE_176400 | \
+ SNDRV_PCM_RATE_192000)
+
+#define STA32X_FORMATS \
+ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \
+ SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \
+ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE | \
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE | \
+ SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE)
+
+/* Power-up register defaults */
+static const u8 sta32x_regs[STA32X_REGISTER_COUNT] = {
+ 0x63, 0x80, 0xc2, 0x40, 0xc2, 0x5c, 0x10, 0xff, 0x60, 0x60,
+ 0x60, 0x80, 0x00, 0x00, 0x00, 0x40, 0x80, 0x77, 0x6a, 0x69,
+ 0x6a, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d,
+ 0xc0, 0xf3, 0x33, 0x00, 0x0c,
+};
+
+/* regulator power supply names */
+static const char *sta32x_supply_names[] = {
+ "Vdda", /* analog supply, 3.3VV */
+ "Vdd3", /* digital supply, 3.3V */
+ "Vcc" /* power amp spply, 10V - 36V */
+};
+
+/* codec private data */
+struct sta32x_priv {
+ struct regulator_bulk_data supplies[ARRAY_SIZE(sta32x_supply_names)];
+ struct snd_soc_codec *codec;
+
+ unsigned int mclk;
+ unsigned int format;
+};
+
+static const DECLARE_TLV_DB_SCALE(mvol_tlv, -12700, 50, 1);
+static const DECLARE_TLV_DB_SCALE(chvol_tlv, -7950, 50, 1);
+static const DECLARE_TLV_DB_SCALE(tone_tlv, -120, 200, 0);
+
+static const char *sta32x_drc_ac[] = {
+ "Anti-Clipping", "Dynamic Range Compression" };
+static const char *sta32x_auto_eq_mode[] = {
+ "User", "Preset", "Loudness" };
+static const char *sta32x_auto_gc_mode[] = {
+ "User", "AC no clipping", "AC limited clipping (10%)",
+ "DRC nighttime listening mode" };
+static const char *sta32x_auto_xo_mode[] = {
+ "User", "80Hz", "100Hz", "120Hz", "140Hz", "160Hz", "180Hz", "200Hz",
+ "220Hz", "240Hz", "260Hz", "280Hz", "300Hz", "320Hz", "340Hz", "360Hz" };
+static const char *sta32x_preset_eq_mode[] = {
+ "Flat", "Rock", "Soft Rock", "Jazz", "Classical", "Dance", "Pop", "Soft",
+ "Hard", "Party", "Vocal", "Hip-Hop", "Dialog", "Bass-boost #1",
+ "Bass-boost #2", "Bass-boost #3", "Loudness 1", "Loudness 2",
+ "Loudness 3", "Loudness 4", "Loudness 5", "Loudness 6", "Loudness 7",
+ "Loudness 8", "Loudness 9", "Loudness 10", "Loudness 11", "Loudness 12",
+ "Loudness 13", "Loudness 14", "Loudness 15", "Loudness 16" };
+static const char *sta32x_limiter_select[] = {
+ "Limiter Disabled", "Limiter #1", "Limiter #2" };
+static const char *sta32x_limiter_attack_rate[] = {
+ "3.1584", "2.7072", "2.2560", "1.8048", "1.3536", "0.9024",
+ "0.4512", "0.2256", "0.1504", "0.1123", "0.0902", "0.0752",
+ "0.0645", "0.0564", "0.0501", "0.0451" };
+static const char *sta32x_limiter_release_rate[] = {
+ "0.5116", "0.1370", "0.0744", "0.0499", "0.0360", "0.0299",
+ "0.0264", "0.0208", "0.0198", "0.0172", "0.0147", "0.0137",
+ "0.0134", "0.0117", "0.0110", "0.0104" };
+
+static const unsigned int sta32x_limiter_ac_attack_tlv[] = {
+ TLV_DB_RANGE_HEAD(2),
+ 0, 7, TLV_DB_SCALE_ITEM(-1200, 200, 0),
+ 8, 16, TLV_DB_SCALE_ITEM(300, 100, 0),
+};
+
+static const unsigned int sta32x_limiter_ac_release_tlv[] = {
+ TLV_DB_RANGE_HEAD(5),
+ 0, 0, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 0),
+ 1, 1, TLV_DB_SCALE_ITEM(-2900, 0, 0),
+ 2, 2, TLV_DB_SCALE_ITEM(-2000, 0, 0),
+ 3, 8, TLV_DB_SCALE_ITEM(-1400, 200, 0),
+ 8, 16, TLV_DB_SCALE_ITEM(-700, 100, 0),
+};
+
+static const unsigned int sta32x_limiter_drc_attack_tlv[] = {
+ TLV_DB_RANGE_HEAD(3),
+ 0, 7, TLV_DB_SCALE_ITEM(-3100, 200, 0),
+ 8, 13, TLV_DB_SCALE_ITEM(-1600, 100, 0),
+ 14, 16, TLV_DB_SCALE_ITEM(-1000, 300, 0),
+};
+
+static const unsigned int sta32x_limiter_drc_release_tlv[] = {
+ TLV_DB_RANGE_HEAD(5),
+ 0, 0, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 0),
+ 1, 2, TLV_DB_SCALE_ITEM(-3800, 200, 0),
+ 3, 4, TLV_DB_SCALE_ITEM(-3300, 200, 0),
+ 5, 12, TLV_DB_SCALE_ITEM(-3000, 200, 0),
+ 13, 16, TLV_DB_SCALE_ITEM(-1500, 300, 0),
+};
+
+static const struct soc_enum sta32x_drc_ac_enum =
+ SOC_ENUM_SINGLE(STA32X_CONFD, STA32X_CONFD_DRC_SHIFT,
+ 2, sta32x_drc_ac);
+static const struct soc_enum sta32x_auto_eq_enum =
+ SOC_ENUM_SINGLE(STA32X_AUTO1, STA32X_AUTO1_AMEQ_SHIFT,
+ 3, sta32x_auto_eq_mode);
+static const struct soc_enum sta32x_auto_gc_enum =
+ SOC_ENUM_SINGLE(STA32X_AUTO1, STA32X_AUTO1_AMGC_SHIFT,
+ 4, sta32x_auto_gc_mode);
+static const struct soc_enum sta32x_auto_xo_enum =
+ SOC_ENUM_SINGLE(STA32X_AUTO2, STA32X_AUTO2_XO_SHIFT,
+ 16, sta32x_auto_xo_mode);
+static const struct soc_enum sta32x_preset_eq_enum =
+ SOC_ENUM_SINGLE(STA32X_AUTO3, STA32X_AUTO3_PEQ_SHIFT,
+ 32, sta32x_preset_eq_mode);
+static const struct soc_enum sta32x_limiter_ch1_enum =
+ SOC_ENUM_SINGLE(STA32X_C1CFG, STA32X_CxCFG_LS_SHIFT,
+ 3, sta32x_limiter_select);
+static const struct soc_enum sta32x_limiter_ch2_enum =
+ SOC_ENUM_SINGLE(STA32X_C2CFG, STA32X_CxCFG_LS_SHIFT,
+ 3, sta32x_limiter_select);
+static const struct soc_enum sta32x_limiter_ch3_enum =
+ SOC_ENUM_SINGLE(STA32X_C3CFG, STA32X_CxCFG_LS_SHIFT,
+ 3, sta32x_limiter_select);
+static const struct soc_enum sta32x_limiter1_attack_rate_enum =
+ SOC_ENUM_SINGLE(STA32X_L1AR, STA32X_LxA_SHIFT,
+ 16, sta32x_limiter_attack_rate);
+static const struct soc_enum sta32x_limiter2_attack_rate_enum =
+ SOC_ENUM_SINGLE(STA32X_L2AR, STA32X_LxA_SHIFT,
+ 16, sta32x_limiter_attack_rate);
+static const struct soc_enum sta32x_limiter1_release_rate_enum =
+ SOC_ENUM_SINGLE(STA32X_L1AR, STA32X_LxR_SHIFT,
+ 16, sta32x_limiter_release_rate);
+static const struct soc_enum sta32x_limiter2_release_rate_enum =
+ SOC_ENUM_SINGLE(STA32X_L2AR, STA32X_LxR_SHIFT,
+ 16, sta32x_limiter_release_rate);
+
+/* byte array controls for setting biquad, mixer, scaling coefficients;
+ * for biquads all five coefficients need to be set in one go,
+ * mixer and pre/postscale coefs can be set individually;
+ * each coef is 24bit, the bytes are ordered in the same way
+ * as given in the STA32x data sheet (big endian; b1, b2, a1, a2, b0)
+ */
+
+static int sta32x_coefficient_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ int numcoef = kcontrol->private_value >> 16;
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+ uinfo->count = 3 * numcoef;
+ return 0;
+}
+
+static int sta32x_coefficient_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int numcoef = kcontrol->private_value >> 16;
+ int index = kcontrol->private_value & 0xffff;
+ unsigned int cfud;
+ int i;
+
+ /* preserve reserved bits in STA32X_CFUD */
+ cfud = snd_soc_read(codec, STA32X_CFUD) & 0xf0;
+ /* chip documentation does not say if the bits are self clearing,
+ * so do it explicitly */
+ snd_soc_write(codec, STA32X_CFUD, cfud);
+
+ snd_soc_write(codec, STA32X_CFADDR2, index);
+ if (numcoef == 1)
+ snd_soc_write(codec, STA32X_CFUD, cfud | 0x04);
+ else if (numcoef == 5)
+ snd_soc_write(codec, STA32X_CFUD, cfud | 0x08);
+ else
+ return -EINVAL;
+ for (i = 0; i < 3 * numcoef; i++)
+ ucontrol->value.bytes.data[i] =
+ snd_soc_read(codec, STA32X_B1CF1 + i);
+
+ return 0;
+}
+
+static int sta32x_coefficient_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int numcoef = kcontrol->private_value >> 16;
+ int index = kcontrol->private_value & 0xffff;
+ unsigned int cfud;
+ int i;
+
+ /* preserve reserved bits in STA32X_CFUD */
+ cfud = snd_soc_read(codec, STA32X_CFUD) & 0xf0;
+ /* chip documentation does not say if the bits are self clearing,
+ * so do it explicitly */
+ snd_soc_write(codec, STA32X_CFUD, cfud);
+
+ snd_soc_write(codec, STA32X_CFADDR2, index);
+ for (i = 0; i < 3 * numcoef; i++)
+ snd_soc_write(codec, STA32X_B1CF1 + i,
+ ucontrol->value.bytes.data[i]);
+ if (numcoef == 1)
+ snd_soc_write(codec, STA32X_CFUD, cfud | 0x01);
+ else if (numcoef == 5)
+ snd_soc_write(codec, STA32X_CFUD, cfud | 0x02);
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+#define SINGLE_COEF(xname, index) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = sta32x_coefficient_info, \
+ .get = sta32x_coefficient_get,\
+ .put = sta32x_coefficient_put, \
+ .private_value = index | (1 << 16) }
+
+#define BIQUAD_COEFS(xname, index) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = sta32x_coefficient_info, \
+ .get = sta32x_coefficient_get,\
+ .put = sta32x_coefficient_put, \
+ .private_value = index | (5 << 16) }
+
+static const struct snd_kcontrol_new sta32x_snd_controls[] = {
+SOC_SINGLE_TLV("Master Volume", STA32X_MVOL, 0, 0xff, 1, mvol_tlv),
+SOC_SINGLE("Master Switch", STA32X_MMUTE, 0, 1, 1),
+SOC_SINGLE("Ch1 Switch", STA32X_MMUTE, 1, 1, 1),
+SOC_SINGLE("Ch2 Switch", STA32X_MMUTE, 2, 1, 1),
+SOC_SINGLE("Ch3 Switch", STA32X_MMUTE, 3, 1, 1),
+SOC_SINGLE_TLV("Ch1 Volume", STA32X_C1VOL, 0, 0xff, 1, chvol_tlv),
+SOC_SINGLE_TLV("Ch2 Volume", STA32X_C2VOL, 0, 0xff, 1, chvol_tlv),
+SOC_SINGLE_TLV("Ch3 Volume", STA32X_C3VOL, 0, 0xff, 1, chvol_tlv),
+SOC_SINGLE("De-emphasis Filter Switch", STA32X_CONFD, STA32X_CONFD_DEMP_SHIFT, 1, 0),
+SOC_ENUM("Compressor/Limiter Switch", sta32x_drc_ac_enum),
+SOC_SINGLE("Miami Mode Switch", STA32X_CONFD, STA32X_CONFD_MME_SHIFT, 1, 0),
+SOC_SINGLE("Zero Cross Switch", STA32X_CONFE, STA32X_CONFE_ZCE_SHIFT, 1, 0),
+SOC_SINGLE("Soft Ramp Switch", STA32X_CONFE, STA32X_CONFE_SVE_SHIFT, 1, 0),
+SOC_SINGLE("Auto-Mute Switch", STA32X_CONFF, STA32X_CONFF_IDE_SHIFT, 1, 0),
+SOC_ENUM("Automode EQ", sta32x_auto_eq_enum),
+SOC_ENUM("Automode GC", sta32x_auto_gc_enum),
+SOC_ENUM("Automode XO", sta32x_auto_xo_enum),
+SOC_ENUM("Preset EQ", sta32x_preset_eq_enum),
+SOC_SINGLE("Ch1 Tone Control Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_TCB_SHIFT, 1, 0),
+SOC_SINGLE("Ch2 Tone Control Bypass Switch", STA32X_C2CFG, STA32X_CxCFG_TCB_SHIFT, 1, 0),
+SOC_SINGLE("Ch1 EQ Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_EQBP_SHIFT, 1, 0),
+SOC_SINGLE("Ch2 EQ Bypass Switch", STA32X_C2CFG, STA32X_CxCFG_EQBP_SHIFT, 1, 0),
+SOC_SINGLE("Ch1 Master Volume Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0),
+SOC_SINGLE("Ch2 Master Volume Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0),
+SOC_SINGLE("Ch3 Master Volume Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0),
+SOC_ENUM("Ch1 Limiter Select", sta32x_limiter_ch1_enum),
+SOC_ENUM("Ch2 Limiter Select", sta32x_limiter_ch2_enum),
+SOC_ENUM("Ch3 Limiter Select", sta32x_limiter_ch3_enum),
+SOC_SINGLE_TLV("Bass Tone Control", STA32X_TONE, STA32X_TONE_BTC_SHIFT, 15, 0, tone_tlv),
+SOC_SINGLE_TLV("Treble Tone Control", STA32X_TONE, STA32X_TONE_TTC_SHIFT, 15, 0, tone_tlv),
+SOC_ENUM("Limiter1 Attack Rate (dB/ms)", sta32x_limiter1_attack_rate_enum),
+SOC_ENUM("Limiter2 Attack Rate (dB/ms)", sta32x_limiter2_attack_rate_enum),
+SOC_ENUM("Limiter1 Release Rate (dB/ms)", sta32x_limiter1_release_rate_enum),
+SOC_ENUM("Limiter2 Release Rate (dB/ms)", sta32x_limiter1_release_rate_enum),
+
+/* depending on mode, the attack/release thresholds have
+ * two different enum definitions; provide both
+ */
+SOC_SINGLE_TLV("Limiter1 Attack Threshold (AC Mode)", STA32X_L1ATRT, STA32X_LxA_SHIFT,
+ 16, 0, sta32x_limiter_ac_attack_tlv),
+SOC_SINGLE_TLV("Limiter2 Attack Threshold (AC Mode)", STA32X_L2ATRT, STA32X_LxA_SHIFT,
+ 16, 0, sta32x_limiter_ac_attack_tlv),
+SOC_SINGLE_TLV("Limiter1 Release Threshold (AC Mode)", STA32X_L1ATRT, STA32X_LxR_SHIFT,
+ 16, 0, sta32x_limiter_ac_release_tlv),
+SOC_SINGLE_TLV("Limiter2 Release Threshold (AC Mode)", STA32X_L2ATRT, STA32X_LxR_SHIFT,
+ 16, 0, sta32x_limiter_ac_release_tlv),
+SOC_SINGLE_TLV("Limiter1 Attack Threshold (DRC Mode)", STA32X_L1ATRT, STA32X_LxA_SHIFT,
+ 16, 0, sta32x_limiter_drc_attack_tlv),
+SOC_SINGLE_TLV("Limiter2 Attack Threshold (DRC Mode)", STA32X_L2ATRT, STA32X_LxA_SHIFT,
+ 16, 0, sta32x_limiter_drc_attack_tlv),
+SOC_SINGLE_TLV("Limiter1 Release Threshold (DRC Mode)", STA32X_L1ATRT, STA32X_LxR_SHIFT,
+ 16, 0, sta32x_limiter_drc_release_tlv),
+SOC_SINGLE_TLV("Limiter2 Release Threshold (DRC Mode)", STA32X_L2ATRT, STA32X_LxR_SHIFT,
+ 16, 0, sta32x_limiter_drc_release_tlv),
+
+BIQUAD_COEFS("Ch1 - Biquad 1", 0),
+BIQUAD_COEFS("Ch1 - Biquad 2", 5),
+BIQUAD_COEFS("Ch1 - Biquad 3", 10),
+BIQUAD_COEFS("Ch1 - Biquad 4", 15),
+BIQUAD_COEFS("Ch2 - Biquad 1", 20),
+BIQUAD_COEFS("Ch2 - Biquad 2", 25),
+BIQUAD_COEFS("Ch2 - Biquad 3", 30),
+BIQUAD_COEFS("Ch2 - Biquad 4", 35),
+BIQUAD_COEFS("High-pass", 40),
+BIQUAD_COEFS("Low-pass", 45),
+SINGLE_COEF("Ch1 - Prescale", 50),
+SINGLE_COEF("Ch2 - Prescale", 51),
+SINGLE_COEF("Ch1 - Postscale", 52),
+SINGLE_COEF("Ch2 - Postscale", 53),
+SINGLE_COEF("Ch3 - Postscale", 54),
+SINGLE_COEF("Thermal warning - Postscale", 55),
+SINGLE_COEF("Ch1 - Mix 1", 56),
+SINGLE_COEF("Ch1 - Mix 2", 57),
+SINGLE_COEF("Ch2 - Mix 1", 58),
+SINGLE_COEF("Ch2 - Mix 2", 59),
+SINGLE_COEF("Ch3 - Mix 1", 60),
+SINGLE_COEF("Ch3 - Mix 2", 61),
+};
+
+static const struct snd_soc_dapm_widget sta32x_dapm_widgets[] = {
+SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_OUTPUT("LEFT"),
+SND_SOC_DAPM_OUTPUT("RIGHT"),
+SND_SOC_DAPM_OUTPUT("SUB"),
+};
+
+static const struct snd_soc_dapm_route sta32x_dapm_routes[] = {
+ { "LEFT", NULL, "DAC" },
+ { "RIGHT", NULL, "DAC" },
+ { "SUB", NULL, "DAC" },
+};
+
+/* MCLK interpolation ratio per fs */
+static struct {
+ int fs;
+ int ir;
+} interpolation_ratios[] = {
+ { 32000, 0 },
+ { 44100, 0 },
+ { 48000, 0 },
+ { 88200, 1 },
+ { 96000, 1 },
+ { 176400, 2 },
+ { 192000, 2 },
+};
+
+/* MCLK to fs clock ratios */
+static struct {
+ int ratio;
+ int mcs;
+} mclk_ratios[3][7] = {
+ { { 768, 0 }, { 512, 1 }, { 384, 2 }, { 256, 3 },
+ { 128, 4 }, { 576, 5 }, { 0, 0 } },
+ { { 384, 2 }, { 256, 3 }, { 192, 4 }, { 128, 5 }, {64, 0 }, { 0, 0 } },
+ { { 384, 2 }, { 256, 3 }, { 192, 4 }, { 128, 5 }, {64, 0 }, { 0, 0 } },
+};
+
+
+/**
+ * sta32x_set_dai_sysclk - configure MCLK
+ * @codec_dai: the codec DAI
+ * @clk_id: the clock ID (ignored)
+ * @freq: the MCLK input frequency
+ * @dir: the clock direction (ignored)
+ *
+ * The value of MCLK is used to determine which sample rates are supported
+ * by the STA32X, based on the mclk_ratios table.
+ *
+ * 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.
+ *
+ * For setups with variable MCLKs, pass 0 as 'freq' argument. This will cause
+ * theoretically possible sample rates to be enabled. Call it again with a
+ * proper value set one the external clock is set (most probably you would do
+ * that from a machine's driver 'hw_param' hook.
+ */
+static int sta32x_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 sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec);
+ int i, j, ir, fs;
+ unsigned int rates = 0;
+ unsigned int rate_min = -1;
+ unsigned int rate_max = 0;
+
+ pr_debug("mclk=%u\n", freq);
+ sta32x->mclk = freq;
+
+ if (sta32x->mclk) {
+ for (i = 0; i < ARRAY_SIZE(interpolation_ratios); i++) {
+ ir = interpolation_ratios[i].ir;
+ fs = interpolation_ratios[i].fs;
+ for (j = 0; mclk_ratios[ir][j].ratio; j++) {
+ if (mclk_ratios[ir][j].ratio * fs == freq) {
+ rates |= snd_pcm_rate_to_rate_bit(fs);
+ if (fs < rate_min)
+ rate_min = fs;
+ if (fs > rate_max)
+ rate_max = fs;
+ }
+ }
+ }
+ /* FIXME: soc should support a rate list */
+ rates &= ~SNDRV_PCM_RATE_KNOT;
+
+ if (!rates) {
+ dev_err(codec->dev, "could not find a valid sample rate\n");
+ return -EINVAL;
+ }
+ } else {
+ /* enable all possible rates */
+ rates = STA32X_RATES;
+ rate_min = 32000;
+ rate_max = 192000;
+ }
+
+ codec_dai->driver->playback.rates = rates;
+ codec_dai->driver->playback.rate_min = rate_min;
+ codec_dai->driver->playback.rate_max = rate_max;
+ return 0;
+}
+
+/**
+ * sta32x_set_dai_fmt - configure the codec for the selected audio format
+ * @codec_dai: the codec DAI
+ * @fmt: 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.
+ */
+static int sta32x_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec);
+ u8 confb = snd_soc_read(codec, STA32X_CONFB);
+
+ pr_debug("\n");
+ confb &= ~(STA32X_CONFB_C1IM | STA32X_CONFB_C2IM);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ sta32x->format = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ confb |= STA32X_CONFB_C2IM;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ confb |= STA32X_CONFB_C1IM;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, STA32X_CONFB, confb);
+ return 0;
+}
+
+/**
+ * sta32x_hw_params - program the STA32X 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.
+ */
+static int sta32x_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_codec *codec = rtd->codec;
+ struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec);
+ unsigned int rate;
+ int i, mcs = -1, ir = -1;
+ u8 confa, confb;
+
+ rate = params_rate(params);
+ pr_debug("rate: %u\n", rate);
+ for (i = 0; i < ARRAY_SIZE(interpolation_ratios); i++)
+ if (interpolation_ratios[i].fs == rate)
+ ir = interpolation_ratios[i].ir;
+ if (ir < 0)
+ return -EINVAL;
+ for (i = 0; mclk_ratios[ir][i].ratio; i++)
+ if (mclk_ratios[ir][i].ratio * rate == sta32x->mclk)
+ mcs = mclk_ratios[ir][i].mcs;
+ if (mcs < 0)
+ return -EINVAL;
+
+ confa = snd_soc_read(codec, STA32X_CONFA);
+ confa &= ~(STA32X_CONFA_MCS_MASK | STA32X_CONFA_IR_MASK);
+ confa |= (ir << STA32X_CONFA_IR_SHIFT) | (mcs << STA32X_CONFA_MCS_SHIFT);
+
+ confb = snd_soc_read(codec, STA32X_CONFB);
+ confb &= ~(STA32X_CONFB_SAI_MASK | STA32X_CONFB_SAIFB);
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S24_LE:
+ case SNDRV_PCM_FORMAT_S24_BE:
+ case SNDRV_PCM_FORMAT_S24_3LE:
+ case SNDRV_PCM_FORMAT_S24_3BE:
+ pr_debug("24bit\n");
+ /* fall through */
+ case SNDRV_PCM_FORMAT_S32_LE:
+ case SNDRV_PCM_FORMAT_S32_BE:
+ pr_debug("24bit or 32bit\n");
+ switch (sta32x->format) {
+ case SND_SOC_DAIFMT_I2S:
+ confb |= 0x0;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ confb |= 0x1;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ confb |= 0x2;
+ break;
+ }
+
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ case SNDRV_PCM_FORMAT_S20_3BE:
+ pr_debug("20bit\n");
+ switch (sta32x->format) {
+ case SND_SOC_DAIFMT_I2S:
+ confb |= 0x4;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ confb |= 0x5;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ confb |= 0x6;
+ break;
+ }
+
+ break;
+ case SNDRV_PCM_FORMAT_S18_3LE:
+ case SNDRV_PCM_FORMAT_S18_3BE:
+ pr_debug("18bit\n");
+ switch (sta32x->format) {
+ case SND_SOC_DAIFMT_I2S:
+ confb |= 0x8;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ confb |= 0x9;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ confb |= 0xa;
+ break;
+ }
+
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ case SNDRV_PCM_FORMAT_S16_BE:
+ pr_debug("16bit\n");
+ switch (sta32x->format) {
+ case SND_SOC_DAIFMT_I2S:
+ confb |= 0x0;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ confb |= 0xd;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ confb |= 0xe;
+ break;
+ }
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, STA32X_CONFA, confa);
+ snd_soc_write(codec, STA32X_CONFB, confb);
+ return 0;
+}
+
+/**
+ * sta32x_set_bias_level - DAPM callback
+ * @codec: the codec device
+ * @level: DAPM power level
+ *
+ * This is called by ALSA to put the codec into low power mode
+ * or to wake it up. If the codec is powered off completely
+ * all registers must be restored after power on.
+ */
+static int sta32x_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ int ret;
+ struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec);
+
+ pr_debug("level = %d\n", level);
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ /* Full power on */
+ snd_soc_update_bits(codec, STA32X_CONFF,
+ STA32X_CONFF_PWDN | STA32X_CONFF_EAPD,
+ STA32X_CONFF_PWDN | STA32X_CONFF_EAPD);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(sta32x->supplies),
+ sta32x->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev,
+ "Failed to enable supplies: %d\n", ret);
+ return ret;
+ }
+
+ snd_soc_cache_sync(codec);
+ }
+
+ /* Power up to mute */
+ /* FIXME */
+ snd_soc_update_bits(codec, STA32X_CONFF,
+ STA32X_CONFF_PWDN | STA32X_CONFF_EAPD,
+ STA32X_CONFF_PWDN | STA32X_CONFF_EAPD);
+
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ /* The chip runs through the power down sequence for us. */
+ snd_soc_update_bits(codec, STA32X_CONFF,
+ STA32X_CONFF_PWDN | STA32X_CONFF_EAPD,
+ STA32X_CONFF_PWDN);
+ msleep(300);
+
+ regulator_bulk_disable(ARRAY_SIZE(sta32x->supplies),
+ sta32x->supplies);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+static struct snd_soc_dai_ops sta32x_dai_ops = {
+ .hw_params = sta32x_hw_params,
+ .set_sysclk = sta32x_set_dai_sysclk,
+ .set_fmt = sta32x_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver sta32x_dai = {
+ .name = "STA32X",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = STA32X_RATES,
+ .formats = STA32X_FORMATS,
+ },
+ .ops = &sta32x_dai_ops,
+};
+
+#ifdef CONFIG_PM
+static int sta32x_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ sta32x_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int sta32x_resume(struct snd_soc_codec *codec)
+{
+ sta32x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+#else
+#define sta32x_suspend NULL
+#define sta32x_resume NULL
+#endif
+
+static int sta32x_probe(struct snd_soc_codec *codec)
+{
+ struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec);
+ int i, ret = 0;
+
+ sta32x->codec = codec;
+
+ /* regulators */
+ for (i = 0; i < ARRAY_SIZE(sta32x->supplies); i++)
+ sta32x->supplies[i].supply = sta32x_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(sta32x->supplies),
+ sta32x->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ goto err;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(sta32x->supplies),
+ sta32x->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+ goto err_get;
+ }
+
+ /* Tell ASoC what kind of I/O to use to read the registers. ASoC will
+ * then do the I2C transactions itself.
+ */
+ ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to set cache I/O (ret=%i)\n", ret);
+ return ret;
+ }
+
+ /* read reg reset values into cache */
+ for (i = 0; i < STA32X_REGISTER_COUNT; i++)
+ snd_soc_cache_write(codec, i, sta32x_regs[i]);
+
+ /* preserve reset values of reserved register bits */
+ snd_soc_cache_write(codec, STA32X_CONFC,
+ codec->hw_read(codec, STA32X_CONFC));
+ snd_soc_cache_write(codec, STA32X_CONFE,
+ codec->hw_read(codec, STA32X_CONFE));
+ snd_soc_cache_write(codec, STA32X_CONFF,
+ codec->hw_read(codec, STA32X_CONFF));
+ snd_soc_cache_write(codec, STA32X_MMUTE,
+ codec->hw_read(codec, STA32X_MMUTE));
+ snd_soc_cache_write(codec, STA32X_AUTO1,
+ codec->hw_read(codec, STA32X_AUTO1));
+ snd_soc_cache_write(codec, STA32X_AUTO3,
+ codec->hw_read(codec, STA32X_AUTO3));
+ snd_soc_cache_write(codec, STA32X_C3CFG,
+ codec->hw_read(codec, STA32X_C3CFG));
+
+ /* FIXME enable thermal warning adjustment and recovery */
+ snd_soc_update_bits(codec, STA32X_CONFA,
+ STA32X_CONFA_TWAB | STA32X_CONFA_TWRB, 0);
+
+ /* FIXME select 2.1 mode */
+ snd_soc_update_bits(codec, STA32X_CONFF,
+ STA32X_CONFF_OCFG_MASK,
+ 1 << STA32X_CONFF_OCFG_SHIFT);
+
+ /* FIXME channel to output mapping */
+ snd_soc_update_bits(codec, STA32X_C1CFG,
+ STA32X_CxCFG_OM_MASK,
+ 0 << STA32X_CxCFG_OM_SHIFT);
+ snd_soc_update_bits(codec, STA32X_C2CFG,
+ STA32X_CxCFG_OM_MASK,
+ 1 << STA32X_CxCFG_OM_SHIFT);
+ snd_soc_update_bits(codec, STA32X_C3CFG,
+ STA32X_CxCFG_OM_MASK,
+ 2 << STA32X_CxCFG_OM_SHIFT);
+
+ sta32x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ /* Bias level configuration will have done an extra enable */
+ regulator_bulk_disable(ARRAY_SIZE(sta32x->supplies), sta32x->supplies);
+
+ return 0;
+
+err_get:
+ regulator_bulk_free(ARRAY_SIZE(sta32x->supplies), sta32x->supplies);
+err:
+ return ret;
+}
+
+static int sta32x_remove(struct snd_soc_codec *codec)
+{
+ struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec);
+
+ regulator_bulk_disable(ARRAY_SIZE(sta32x->supplies), sta32x->supplies);
+ regulator_bulk_free(ARRAY_SIZE(sta32x->supplies), sta32x->supplies);
+
+ return 0;
+}
+
+static int sta32x_reg_is_volatile(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ switch (reg) {
+ case STA32X_CONFA ... STA32X_L2ATRT:
+ case STA32X_MPCC1 ... STA32X_FDRC2:
+ return 0;
+ }
+ return 1;
+}
+
+static const struct snd_soc_codec_driver sta32x_codec = {
+ .probe = sta32x_probe,
+ .remove = sta32x_remove,
+ .suspend = sta32x_suspend,
+ .resume = sta32x_resume,
+ .reg_cache_size = STA32X_REGISTER_COUNT,
+ .reg_word_size = sizeof(u8),
+ .volatile_register = sta32x_reg_is_volatile,
+ .set_bias_level = sta32x_set_bias_level,
+ .controls = sta32x_snd_controls,
+ .num_controls = ARRAY_SIZE(sta32x_snd_controls),
+ .dapm_widgets = sta32x_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sta32x_dapm_widgets),
+ .dapm_routes = sta32x_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(sta32x_dapm_routes),
+};
+
+static __devinit int sta32x_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct sta32x_priv *sta32x;
+ int ret;
+
+ sta32x = kzalloc(sizeof(struct sta32x_priv), GFP_KERNEL);
+ if (!sta32x)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, sta32x);
+
+ ret = snd_soc_register_codec(&i2c->dev, &sta32x_codec, &sta32x_dai, 1);
+ if (ret != 0) {
+ dev_err(&i2c->dev, "Failed to register codec (%d)\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static __devexit int sta32x_i2c_remove(struct i2c_client *client)
+{
+ struct sta32x_priv *sta32x = i2c_get_clientdata(client);
+ struct snd_soc_codec *codec = sta32x->codec;
+
+ if (codec)
+ sta32x_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ regulator_bulk_free(ARRAY_SIZE(sta32x->supplies), sta32x->supplies);
+
+ if (codec) {
+ snd_soc_unregister_codec(&client->dev);
+ snd_soc_codec_set_drvdata(codec, NULL);
+ }
+
+ kfree(sta32x);
+ return 0;
+}
+
+static const struct i2c_device_id sta32x_i2c_id[] = {
+ { "sta326", 0 },
+ { "sta328", 0 },
+ { "sta329", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, sta32x_i2c_id);
+
+static struct i2c_driver sta32x_i2c_driver = {
+ .driver = {
+ .name = "sta32x",
+ .owner = THIS_MODULE,
+ },
+ .probe = sta32x_i2c_probe,
+ .remove = __devexit_p(sta32x_i2c_remove),
+ .id_table = sta32x_i2c_id,
+};
+
+static int __init sta32x_init(void)
+{
+ return i2c_add_driver(&sta32x_i2c_driver);
+}
+module_init(sta32x_init);
+
+static void __exit sta32x_exit(void)
+{
+ i2c_del_driver(&sta32x_i2c_driver);
+}
+module_exit(sta32x_exit);
+
+MODULE_DESCRIPTION("ASoC STA32X driver");
+MODULE_AUTHOR("Johannes Stezenbach <js@sig21.net>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/sta32x.h b/sound/soc/codecs/sta32x.h
new file mode 100644
index 000000000000..b97ee5a75667
--- /dev/null
+++ b/sound/soc/codecs/sta32x.h
@@ -0,0 +1,210 @@
+/*
+ * Codec driver for ST STA32x 2.1-channel high-efficiency digital audio system
+ *
+ * Copyright: 2011 Raumfeld GmbH
+ * Author: Johannes Stezenbach <js@sig21.net>
+ *
+ * based on code from:
+ * Wolfson Microelectronics PLC.
+ * 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 _ASOC_STA_32X_H
+#define _ASOC_STA_32X_H
+
+/* STA326 register addresses */
+
+#define STA32X_REGISTER_COUNT 0x2d
+
+#define STA32X_CONFA 0x00
+#define STA32X_CONFB 0x01
+#define STA32X_CONFC 0x02
+#define STA32X_CONFD 0x03
+#define STA32X_CONFE 0x04
+#define STA32X_CONFF 0x05
+#define STA32X_MMUTE 0x06
+#define STA32X_MVOL 0x07
+#define STA32X_C1VOL 0x08
+#define STA32X_C2VOL 0x09
+#define STA32X_C3VOL 0x0a
+#define STA32X_AUTO1 0x0b
+#define STA32X_AUTO2 0x0c
+#define STA32X_AUTO3 0x0d
+#define STA32X_C1CFG 0x0e
+#define STA32X_C2CFG 0x0f
+#define STA32X_C3CFG 0x10
+#define STA32X_TONE 0x11
+#define STA32X_L1AR 0x12
+#define STA32X_L1ATRT 0x13
+#define STA32X_L2AR 0x14
+#define STA32X_L2ATRT 0x15
+#define STA32X_CFADDR2 0x16
+#define STA32X_B1CF1 0x17
+#define STA32X_B1CF2 0x18
+#define STA32X_B1CF3 0x19
+#define STA32X_B2CF1 0x1a
+#define STA32X_B2CF2 0x1b
+#define STA32X_B2CF3 0x1c
+#define STA32X_A1CF1 0x1d
+#define STA32X_A1CF2 0x1e
+#define STA32X_A1CF3 0x1f
+#define STA32X_A2CF1 0x20
+#define STA32X_A2CF2 0x21
+#define STA32X_A2CF3 0x22
+#define STA32X_B0CF1 0x23
+#define STA32X_B0CF2 0x24
+#define STA32X_B0CF3 0x25
+#define STA32X_CFUD 0x26
+#define STA32X_MPCC1 0x27
+#define STA32X_MPCC2 0x28
+/* Reserved 0x29 */
+/* Reserved 0x2a */
+#define STA32X_Reserved 0x2a
+#define STA32X_FDRC1 0x2b
+#define STA32X_FDRC2 0x2c
+/* Reserved 0x2d */
+
+
+/* STA326 register field definitions */
+
+/* 0x00 CONFA */
+#define STA32X_CONFA_MCS_MASK 0x03
+#define STA32X_CONFA_MCS_SHIFT 0
+#define STA32X_CONFA_IR_MASK 0x18
+#define STA32X_CONFA_IR_SHIFT 3
+#define STA32X_CONFA_TWRB 0x20
+#define STA32X_CONFA_TWAB 0x40
+#define STA32X_CONFA_FDRB 0x80
+
+/* 0x01 CONFB */
+#define STA32X_CONFB_SAI_MASK 0x0f
+#define STA32X_CONFB_SAI_SHIFT 0
+#define STA32X_CONFB_SAIFB 0x10
+#define STA32X_CONFB_DSCKE 0x20
+#define STA32X_CONFB_C1IM 0x40
+#define STA32X_CONFB_C2IM 0x80
+
+/* 0x02 CONFC */
+#define STA32X_CONFC_OM_MASK 0x03
+#define STA32X_CONFC_OM_SHIFT 0
+#define STA32X_CONFC_CSZ_MASK 0x7c
+#define STA32X_CONFC_CSZ_SHIFT 2
+
+/* 0x03 CONFD */
+#define STA32X_CONFD_HPB 0x01
+#define STA32X_CONFD_HPB_SHIFT 0
+#define STA32X_CONFD_DEMP 0x02
+#define STA32X_CONFD_DEMP_SHIFT 1
+#define STA32X_CONFD_DSPB 0x04
+#define STA32X_CONFD_DSPB_SHIFT 2
+#define STA32X_CONFD_PSL 0x08
+#define STA32X_CONFD_PSL_SHIFT 3
+#define STA32X_CONFD_BQL 0x10
+#define STA32X_CONFD_BQL_SHIFT 4
+#define STA32X_CONFD_DRC 0x20
+#define STA32X_CONFD_DRC_SHIFT 5
+#define STA32X_CONFD_ZDE 0x40
+#define STA32X_CONFD_ZDE_SHIFT 6
+#define STA32X_CONFD_MME 0x80
+#define STA32X_CONFD_MME_SHIFT 7
+
+/* 0x04 CONFE */
+#define STA32X_CONFE_MPCV 0x01
+#define STA32X_CONFE_MPCV_SHIFT 0
+#define STA32X_CONFE_MPC 0x02
+#define STA32X_CONFE_MPC_SHIFT 1
+#define STA32X_CONFE_AME 0x08
+#define STA32X_CONFE_AME_SHIFT 3
+#define STA32X_CONFE_PWMS 0x10
+#define STA32X_CONFE_PWMS_SHIFT 4
+#define STA32X_CONFE_ZCE 0x40
+#define STA32X_CONFE_ZCE_SHIFT 6
+#define STA32X_CONFE_SVE 0x80
+#define STA32X_CONFE_SVE_SHIFT 7
+
+/* 0x05 CONFF */
+#define STA32X_CONFF_OCFG_MASK 0x03
+#define STA32X_CONFF_OCFG_SHIFT 0
+#define STA32X_CONFF_IDE 0x04
+#define STA32X_CONFF_IDE_SHIFT 3
+#define STA32X_CONFF_BCLE 0x08
+#define STA32X_CONFF_ECLE 0x20
+#define STA32X_CONFF_PWDN 0x40
+#define STA32X_CONFF_EAPD 0x80
+
+/* 0x06 MMUTE */
+#define STA32X_MMUTE_MMUTE 0x01
+
+/* 0x0b AUTO1 */
+#define STA32X_AUTO1_AMEQ_MASK 0x03
+#define STA32X_AUTO1_AMEQ_SHIFT 0
+#define STA32X_AUTO1_AMV_MASK 0xc0
+#define STA32X_AUTO1_AMV_SHIFT 2
+#define STA32X_AUTO1_AMGC_MASK 0x30
+#define STA32X_AUTO1_AMGC_SHIFT 4
+#define STA32X_AUTO1_AMPS 0x80
+
+/* 0x0c AUTO2 */
+#define STA32X_AUTO2_AMAME 0x01
+#define STA32X_AUTO2_AMAM_MASK 0x0e
+#define STA32X_AUTO2_AMAM_SHIFT 1
+#define STA32X_AUTO2_XO_MASK 0xf0
+#define STA32X_AUTO2_XO_SHIFT 4
+
+/* 0x0d AUTO3 */
+#define STA32X_AUTO3_PEQ_MASK 0x1f
+#define STA32X_AUTO3_PEQ_SHIFT 0
+
+/* 0x0e 0x0f 0x10 CxCFG */
+#define STA32X_CxCFG_TCB 0x01 /* only C1 and C2 */
+#define STA32X_CxCFG_TCB_SHIFT 0
+#define STA32X_CxCFG_EQBP 0x02 /* only C1 and C2 */
+#define STA32X_CxCFG_EQBP_SHIFT 1
+#define STA32X_CxCFG_VBP 0x03
+#define STA32X_CxCFG_VBP_SHIFT 2
+#define STA32X_CxCFG_BO 0x04
+#define STA32X_CxCFG_LS_MASK 0x30
+#define STA32X_CxCFG_LS_SHIFT 4
+#define STA32X_CxCFG_OM_MASK 0xc0
+#define STA32X_CxCFG_OM_SHIFT 6
+
+/* 0x11 TONE */
+#define STA32X_TONE_BTC_SHIFT 0
+#define STA32X_TONE_TTC_SHIFT 4
+
+/* 0x12 0x13 0x14 0x15 limiter attack/release */
+#define STA32X_LxA_SHIFT 0
+#define STA32X_LxR_SHIFT 4
+
+/* 0x26 CFUD */
+#define STA32X_CFUD_W1 0x01
+#define STA32X_CFUD_WA 0x02
+#define STA32X_CFUD_R1 0x04
+#define STA32X_CFUD_RA 0x08
+
+
+/* biquad filter coefficient table offsets */
+#define STA32X_C1_BQ_BASE 0
+#define STA32X_C2_BQ_BASE 20
+#define STA32X_CH_BQ_NUM 4
+#define STA32X_BQ_NUM_COEF 5
+#define STA32X_XO_HP_BQ_BASE 40
+#define STA32X_XO_LP_BQ_BASE 45
+#define STA32X_C1_PRESCALE 50
+#define STA32X_C2_PRESCALE 51
+#define STA32X_C1_POSTSCALE 52
+#define STA32X_C2_POSTSCALE 53
+#define STA32X_C3_POSTSCALE 54
+#define STA32X_TW_POSTSCALE 55
+#define STA32X_C1_MIX1 56
+#define STA32X_C1_MIX2 57
+#define STA32X_C2_MIX1 58
+#define STA32X_C2_MIX2 59
+#define STA32X_C3_MIX1 60
+#define STA32X_C3_MIX2 61
+
+#endif /* _ASOC_STA_32X_H */
diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c
index 789453d44ec5..0963c4c7a83f 100644
--- a/sound/soc/codecs/tlv320aic3x.c
+++ b/sound/soc/codecs/tlv320aic3x.c
@@ -226,11 +226,13 @@ static const char *aic3x_adc_hpf[] =
#define RDAC_ENUM 1
#define LHPCOM_ENUM 2
#define RHPCOM_ENUM 3
-#define LINE1L_ENUM 4
-#define LINE1R_ENUM 5
-#define LINE2L_ENUM 6
-#define LINE2R_ENUM 7
-#define ADC_HPF_ENUM 8
+#define LINE1L_2_L_ENUM 4
+#define LINE1L_2_R_ENUM 5
+#define LINE1R_2_L_ENUM 6
+#define LINE1R_2_R_ENUM 7
+#define LINE2L_ENUM 8
+#define LINE2R_ENUM 9
+#define ADC_HPF_ENUM 10
static const struct soc_enum aic3x_enum[] = {
SOC_ENUM_SINGLE(DAC_LINE_MUX, 6, 3, aic3x_left_dac_mux),
@@ -238,6 +240,8 @@ static const struct soc_enum aic3x_enum[] = {
SOC_ENUM_SINGLE(HPLCOM_CFG, 4, 3, aic3x_left_hpcom_mux),
SOC_ENUM_SINGLE(HPRCOM_CFG, 3, 5, aic3x_right_hpcom_mux),
SOC_ENUM_SINGLE(LINE1L_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux),
+ SOC_ENUM_SINGLE(LINE1L_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux),
+ SOC_ENUM_SINGLE(LINE1R_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux),
SOC_ENUM_SINGLE(LINE1R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux),
SOC_ENUM_SINGLE(LINE2L_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux),
SOC_ENUM_SINGLE(LINE2R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux),
@@ -490,12 +494,16 @@ static const struct snd_kcontrol_new aic3x_right_pga_mixer_controls[] = {
};
/* Left Line1 Mux */
-static const struct snd_kcontrol_new aic3x_left_line1_mux_controls =
-SOC_DAPM_ENUM("Route", aic3x_enum[LINE1L_ENUM]);
+static const struct snd_kcontrol_new aic3x_left_line1l_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LINE1L_2_L_ENUM]);
+static const struct snd_kcontrol_new aic3x_right_line1l_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LINE1L_2_R_ENUM]);
/* Right Line1 Mux */
-static const struct snd_kcontrol_new aic3x_right_line1_mux_controls =
-SOC_DAPM_ENUM("Route", aic3x_enum[LINE1R_ENUM]);
+static const struct snd_kcontrol_new aic3x_right_line1r_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LINE1R_2_R_ENUM]);
+static const struct snd_kcontrol_new aic3x_left_line1r_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LINE1R_2_L_ENUM]);
/* Left Line2 Mux */
static const struct snd_kcontrol_new aic3x_left_line2_mux_controls =
@@ -535,9 +543,9 @@ static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = {
&aic3x_left_pga_mixer_controls[0],
ARRAY_SIZE(aic3x_left_pga_mixer_controls)),
SND_SOC_DAPM_MUX("Left Line1L Mux", SND_SOC_NOPM, 0, 0,
- &aic3x_left_line1_mux_controls),
+ &aic3x_left_line1l_mux_controls),
SND_SOC_DAPM_MUX("Left Line1R Mux", SND_SOC_NOPM, 0, 0,
- &aic3x_left_line1_mux_controls),
+ &aic3x_left_line1r_mux_controls),
SND_SOC_DAPM_MUX("Left Line2L Mux", SND_SOC_NOPM, 0, 0,
&aic3x_left_line2_mux_controls),
@@ -548,9 +556,9 @@ static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = {
&aic3x_right_pga_mixer_controls[0],
ARRAY_SIZE(aic3x_right_pga_mixer_controls)),
SND_SOC_DAPM_MUX("Right Line1L Mux", SND_SOC_NOPM, 0, 0,
- &aic3x_right_line1_mux_controls),
+ &aic3x_right_line1l_mux_controls),
SND_SOC_DAPM_MUX("Right Line1R Mux", SND_SOC_NOPM, 0, 0,
- &aic3x_right_line1_mux_controls),
+ &aic3x_right_line1r_mux_controls),
SND_SOC_DAPM_MUX("Right Line2R Mux", SND_SOC_NOPM, 0, 0,
&aic3x_right_line2_mux_controls),
diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c
index 4c336636d4f5..cd63bba623df 100644
--- a/sound/soc/codecs/twl6040.c
+++ b/sound/soc/codecs/twl6040.c
@@ -954,9 +954,9 @@ static DECLARE_TLV_DB_SCALE(mic_preamp_tlv, -600, 600, 0);
/*
* MICGAIN volume control:
- * from -6 to 30 dB in 6 dB steps
+ * from 6 to 30 dB in 6 dB steps
*/
-static DECLARE_TLV_DB_SCALE(mic_amp_tlv, -600, 600, 0);
+static DECLARE_TLV_DB_SCALE(mic_amp_tlv, 600, 600, 0);
/*
* AFMGAIN volume control:
diff --git a/sound/soc/codecs/wm8782.c b/sound/soc/codecs/wm8782.c
new file mode 100644
index 000000000000..a2a09f85ea99
--- /dev/null
+++ b/sound/soc/codecs/wm8782.c
@@ -0,0 +1,80 @@
+/*
+ * sound/soc/codecs/wm8782.c
+ * simple, strap-pin configured 24bit 2ch ADC
+ *
+ * Copyright: 2011 Raumfeld GmbH
+ * Author: Johannes Stezenbach <js@sig21.net>
+ *
+ * based on ad73311.c
+ * Copyright: Analog Device Inc.
+ * Author: Cliff Cai <cliff.cai@analog.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/init.h>
+#include <linux/slab.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>
+
+static struct snd_soc_dai_driver wm8782_dai = {
+ .name = "wm8782",
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ /* For configurations with FSAMPEN=0 */
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S20_3LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ },
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8782;
+
+static __devinit int wm8782_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev,
+ &soc_codec_dev_wm8782, &wm8782_dai, 1);
+}
+
+static int __devexit wm8782_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver wm8782_codec_driver = {
+ .driver = {
+ .name = "wm8782",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8782_probe,
+ .remove = wm8782_remove,
+};
+
+static int __init wm8782_init(void)
+{
+ return platform_driver_register(&wm8782_codec_driver);
+}
+module_init(wm8782_init);
+
+static void __exit wm8782_exit(void)
+{
+ platform_driver_unregister(&wm8782_codec_driver);
+}
+module_exit(wm8782_exit);
+
+MODULE_DESCRIPTION("ASoC WM8782 driver");
+MODULE_AUTHOR("Johannes Stezenbach <js@sig21.net>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c
index 449ea09a193d..082040eda8a2 100644
--- a/sound/soc/codecs/wm8900.c
+++ b/sound/soc/codecs/wm8900.c
@@ -1167,6 +1167,7 @@ static int wm8900_resume(struct snd_soc_codec *codec)
ret = wm8900_set_fll(codec, 0, fll_in, fll_out);
if (ret != 0) {
dev_err(codec->dev, "Failed to restart FLL\n");
+ kfree(cache);
return ret;
}
}
diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c
index 9b3bba4df5b3..b085575d4aa5 100644
--- a/sound/soc/codecs/wm8904.c
+++ b/sound/soc/codecs/wm8904.c
@@ -2560,6 +2560,7 @@ static __devexit int wm8904_i2c_remove(struct i2c_client *client)
static const struct i2c_device_id wm8904_i2c_id[] = {
{ "wm8904", WM8904 },
{ "wm8912", WM8912 },
+ { "wm8918", WM8904 }, /* Actually a subset, updates to follow */
{ }
};
MODULE_DEVICE_TABLE(i2c, wm8904_i2c_id);
diff --git a/sound/soc/codecs/wm8915.c b/sound/soc/codecs/wm8915.c
index e2ab4fac2819..423baa9be241 100644
--- a/sound/soc/codecs/wm8915.c
+++ b/sound/soc/codecs/wm8915.c
@@ -41,14 +41,12 @@
#define HPOUT2L 4
#define HPOUT2R 8
-#define WM8915_NUM_SUPPLIES 6
+#define WM8915_NUM_SUPPLIES 4
static const char *wm8915_supply_names[WM8915_NUM_SUPPLIES] = {
- "DCVDD",
"DBVDD",
"AVDD1",
"AVDD2",
"CPVDD",
- "MICVDD",
};
struct wm8915_priv {
@@ -57,6 +55,7 @@ struct wm8915_priv {
int ldo1ena;
int sysclk;
+ int sysclk_src;
int fll_src;
int fll_fref;
@@ -76,6 +75,7 @@ struct wm8915_priv {
struct wm8915_pdata pdata;
int rx_rate[WM8915_AIFS];
+ int bclk_rate[WM8915_AIFS];
/* Platform dependant ReTune mobile configuration */
int num_retune_mobile_texts;
@@ -113,8 +113,6 @@ WM8915_REGULATOR_EVENT(0)
WM8915_REGULATOR_EVENT(1)
WM8915_REGULATOR_EVENT(2)
WM8915_REGULATOR_EVENT(3)
-WM8915_REGULATOR_EVENT(4)
-WM8915_REGULATOR_EVENT(5)
static const u16 wm8915_reg[WM8915_MAX_REGISTER] = {
[WM8915_SOFTWARE_RESET] = 0x8915,
@@ -1565,6 +1563,50 @@ static int wm8915_reset(struct snd_soc_codec *codec)
return snd_soc_write(codec, WM8915_SOFTWARE_RESET, 0x8915);
}
+static const int bclk_divs[] = {
+ 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96
+};
+
+static void wm8915_update_bclk(struct snd_soc_codec *codec)
+{
+ struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
+ int aif, best, cur_val, bclk_rate, bclk_reg, i;
+
+ /* Don't bother if we're in a low frequency idle mode that
+ * can't support audio.
+ */
+ if (wm8915->sysclk < 64000)
+ return;
+
+ for (aif = 0; aif < WM8915_AIFS; aif++) {
+ switch (aif) {
+ case 0:
+ bclk_reg = WM8915_AIF1_BCLK;
+ break;
+ case 1:
+ bclk_reg = WM8915_AIF2_BCLK;
+ break;
+ }
+
+ bclk_rate = wm8915->bclk_rate[aif];
+
+ /* Pick a divisor for BCLK as close as we can get to ideal */
+ best = 0;
+ for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) {
+ cur_val = (wm8915->sysclk / bclk_divs[i]) - bclk_rate;
+ if (cur_val < 0) /* BCLK table is sorted */
+ break;
+ best = i;
+ }
+ bclk_rate = wm8915->sysclk / bclk_divs[best];
+ dev_dbg(codec->dev, "Using BCLK_DIV %d for actual BCLK %dHz\n",
+ bclk_divs[best], bclk_rate);
+
+ snd_soc_update_bits(codec, bclk_reg,
+ WM8915_AIF1_BCLK_DIV_MASK, best);
+ }
+}
+
static int wm8915_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
@@ -1717,10 +1759,6 @@ static int wm8915_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
return 0;
}
-static const int bclk_divs[] = {
- 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96
-};
-
static const int dsp_divs[] = {
48000, 32000, 16000, 8000
};
@@ -1731,17 +1769,11 @@ static int wm8915_hw_params(struct snd_pcm_substream *substream,
{
struct snd_soc_codec *codec = dai->codec;
struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
- int bits, i, bclk_rate, best, cur_val;
+ int bits, i, bclk_rate;
int aifdata = 0;
- int bclk = 0;
int lrclk = 0;
int dsp = 0;
- int aifdata_reg, bclk_reg, lrclk_reg, dsp_shift;
-
- if (!wm8915->sysclk) {
- dev_err(codec->dev, "SYSCLK not configured\n");
- return -EINVAL;
- }
+ int aifdata_reg, lrclk_reg, dsp_shift;
switch (dai->id) {
case 0:
@@ -1753,7 +1785,6 @@ static int wm8915_hw_params(struct snd_pcm_substream *substream,
aifdata_reg = WM8915_AIF1TX_DATA_CONFIGURATION_1;
lrclk_reg = WM8915_AIF1_TX_LRCLK_1;
}
- bclk_reg = WM8915_AIF1_BCLK;
dsp_shift = 0;
break;
case 1:
@@ -1765,7 +1796,6 @@ static int wm8915_hw_params(struct snd_pcm_substream *substream,
aifdata_reg = WM8915_AIF2TX_DATA_CONFIGURATION_1;
lrclk_reg = WM8915_AIF2_TX_LRCLK_1;
}
- bclk_reg = WM8915_AIF2_BCLK;
dsp_shift = WM8915_DSP2_DIV_SHIFT;
break;
default:
@@ -1779,6 +1809,9 @@ static int wm8915_hw_params(struct snd_pcm_substream *substream,
return bclk_rate;
}
+ wm8915->bclk_rate[dai->id] = bclk_rate;
+ wm8915->rx_rate[dai->id] = params_rate(params);
+
/* Needs looking at for TDM */
bits = snd_pcm_format_width(params_format(params));
if (bits < 0)
@@ -1796,18 +1829,7 @@ static int wm8915_hw_params(struct snd_pcm_substream *substream,
}
dsp |= i << dsp_shift;
- /* Pick a divisor for BCLK as close as we can get to ideal */
- best = 0;
- for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) {
- cur_val = (wm8915->sysclk / bclk_divs[i]) - bclk_rate;
- if (cur_val < 0) /* BCLK table is sorted */
- break;
- best = i;
- }
- bclk_rate = wm8915->sysclk / bclk_divs[best];
- dev_dbg(dai->dev, "Using BCLK_DIV %d for actual BCLK %dHz\n",
- bclk_divs[best], bclk_rate);
- bclk |= best;
+ wm8915_update_bclk(codec);
lrclk = bclk_rate / params_rate(params);
dev_dbg(dai->dev, "Using LRCLK rate %d for actual LRCLK %dHz\n",
@@ -1817,14 +1839,11 @@ static int wm8915_hw_params(struct snd_pcm_substream *substream,
WM8915_AIF1TX_WL_MASK |
WM8915_AIF1TX_SLOT_LEN_MASK,
aifdata);
- snd_soc_update_bits(codec, bclk_reg, WM8915_AIF1_BCLK_DIV_MASK, bclk);
snd_soc_update_bits(codec, lrclk_reg, WM8915_AIF1RX_RATE_MASK,
lrclk);
snd_soc_update_bits(codec, WM8915_AIF_CLOCKING_2,
WM8915_DSP1_DIV_SHIFT << dsp_shift, dsp);
- wm8915->rx_rate[dai->id] = params_rate(params);
-
return 0;
}
@@ -1838,6 +1857,9 @@ static int wm8915_set_sysclk(struct snd_soc_dai *dai,
int src;
int old;
+ if (freq == wm8915->sysclk && clk_id == wm8915->sysclk_src)
+ return 0;
+
/* Disable SYSCLK while we reconfigure */
old = snd_soc_read(codec, WM8915_AIF_CLOCKING_1) & WM8915_SYSCLK_ENA;
snd_soc_update_bits(codec, WM8915_AIF_CLOCKING_1,
@@ -1882,6 +1904,8 @@ static int wm8915_set_sysclk(struct snd_soc_dai *dai,
return -EINVAL;
}
+ wm8915_update_bclk(codec);
+
snd_soc_update_bits(codec, WM8915_AIF_CLOCKING_1,
WM8915_SYSCLK_SRC_MASK | WM8915_SYSCLK_DIV_MASK,
src << WM8915_SYSCLK_SRC_SHIFT | ratediv);
@@ -1889,6 +1913,8 @@ static int wm8915_set_sysclk(struct snd_soc_dai *dai,
snd_soc_update_bits(codec, WM8915_AIF_CLOCKING_1,
WM8915_SYSCLK_ENA, old);
+ wm8915->sysclk_src = clk_id;
+
return 0;
}
@@ -2007,6 +2033,7 @@ static int wm8915_set_fll(struct snd_soc_codec *codec, int fll_id, int source,
unsigned int Fref, unsigned int Fout)
{
struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
+ struct i2c_client *i2c = to_i2c_client(codec->dev);
struct _fll_div fll_div;
unsigned long timeout;
int ret, reg;
@@ -2093,7 +2120,18 @@ static int wm8915_set_fll(struct snd_soc_codec *codec, int fll_id, int source,
else
timeout = msecs_to_jiffies(2);
- wait_for_completion_timeout(&wm8915->fll_lock, timeout);
+ /* Allow substantially longer if we've actually got the IRQ */
+ if (i2c->irq)
+ timeout *= 1000;
+
+ ret = wait_for_completion_timeout(&wm8915->fll_lock, timeout);
+
+ if (ret == 0 && i2c->irq) {
+ dev_err(codec->dev, "Timed out waiting for FLL\n");
+ ret = -ETIMEDOUT;
+ } else {
+ ret = 0;
+ }
dev_dbg(codec->dev, "FLL configured for %dHz->%dHz\n", Fref, Fout);
@@ -2101,7 +2139,7 @@ static int wm8915_set_fll(struct snd_soc_codec *codec, int fll_id, int source,
wm8915->fll_fout = Fout;
wm8915->fll_src = source;
- return 0;
+ return ret;
}
#ifdef CONFIG_GPIOLIB
@@ -2293,6 +2331,12 @@ static void wm8915_micd(struct snd_soc_codec *codec)
SND_JACK_HEADSET | SND_JACK_BTN_0);
wm8915->jack_mic = true;
wm8915->detecting = false;
+
+ /* Increase poll rate to give better responsiveness
+ * for buttons */
+ snd_soc_update_bits(codec, WM8915_MIC_DETECT_1,
+ WM8915_MICD_RATE_MASK,
+ 5 << WM8915_MICD_RATE_SHIFT);
}
/* If we detected a lower impedence during initial startup
@@ -2333,15 +2377,17 @@ static void wm8915_micd(struct snd_soc_codec *codec)
SND_JACK_HEADPHONE,
SND_JACK_HEADSET |
SND_JACK_BTN_0);
+
+ /* Increase the detection rate a bit for
+ * responsiveness.
+ */
+ snd_soc_update_bits(codec, WM8915_MIC_DETECT_1,
+ WM8915_MICD_RATE_MASK,
+ 7 << WM8915_MICD_RATE_SHIFT);
+
wm8915->detecting = false;
}
}
-
- /* Increase poll rate to give better responsiveness for buttons */
- if (!wm8915->detecting)
- snd_soc_update_bits(codec, WM8915_MIC_DETECT_1,
- WM8915_MICD_RATE_MASK,
- 5 << WM8915_MICD_RATE_SHIFT);
}
static irqreturn_t wm8915_irq(int irq, void *data)
@@ -2383,6 +2429,20 @@ static irqreturn_t wm8915_irq(int irq, void *data)
}
}
+static irqreturn_t wm8915_edge_irq(int irq, void *data)
+{
+ irqreturn_t ret = IRQ_NONE;
+ irqreturn_t val;
+
+ do {
+ val = wm8915_irq(irq, data);
+ if (val != IRQ_NONE)
+ ret = val;
+ } while (val != IRQ_NONE);
+
+ return ret;
+}
+
static void wm8915_retune_mobile_pdata(struct snd_soc_codec *codec)
{
struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec);
@@ -2482,8 +2542,6 @@ static int wm8915_probe(struct snd_soc_codec *codec)
wm8915->disable_nb[1].notifier_call = wm8915_regulator_event_1;
wm8915->disable_nb[2].notifier_call = wm8915_regulator_event_2;
wm8915->disable_nb[3].notifier_call = wm8915_regulator_event_3;
- wm8915->disable_nb[4].notifier_call = wm8915_regulator_event_4;
- wm8915->disable_nb[5].notifier_call = wm8915_regulator_event_5;
/* This should really be moved into the regulator core */
for (i = 0; i < ARRAY_SIZE(wm8915->supplies); i++) {
@@ -2709,8 +2767,14 @@ static int wm8915_probe(struct snd_soc_codec *codec)
irq_flags |= IRQF_ONESHOT;
- ret = request_threaded_irq(i2c->irq, NULL, wm8915_irq,
- irq_flags, "wm8915", codec);
+ if (irq_flags & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING))
+ ret = request_threaded_irq(i2c->irq, NULL,
+ wm8915_edge_irq,
+ irq_flags, "wm8915", codec);
+ else
+ ret = request_threaded_irq(i2c->irq, NULL, wm8915_irq,
+ irq_flags, "wm8915", codec);
+
if (ret == 0) {
/* Unmask the interrupt */
snd_soc_update_bits(codec, WM8915_INTERRUPT_CONTROL,
diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c
index 25580e3ee7c4..056daa0010f9 100644
--- a/sound/soc/codecs/wm8940.c
+++ b/sound/soc/codecs/wm8940.c
@@ -297,8 +297,6 @@ static int wm8940_add_widgets(struct snd_soc_codec *codec)
if (ret)
goto error_ret;
ret = snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
- if (ret)
- goto error_ret;
error_ret:
return ret;
@@ -683,8 +681,6 @@ static int wm8940_resume(struct snd_soc_codec *codec)
}
}
ret = wm8940_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
- if (ret)
- goto error_ret;
error_ret:
return ret;
@@ -730,9 +726,6 @@ static int wm8940_probe(struct snd_soc_codec *codec)
if (ret)
return ret;
ret = wm8940_add_widgets(codec);
- if (ret)
- return ret;
-
return ret;
}
diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c
index 5e05eed96c38..8499c563a9b5 100644
--- a/sound/soc/codecs/wm8962.c
+++ b/sound/soc/codecs/wm8962.c
@@ -78,6 +78,8 @@ struct wm8962_priv {
#ifdef CONFIG_GPIOLIB
struct gpio_chip gpio_chip;
#endif
+
+ int irq;
};
/* We can't use the same notifier block for more than one supply and
@@ -1982,6 +1984,7 @@ static const unsigned int classd_tlv[] = {
0, 6, TLV_DB_SCALE_ITEM(0, 150, 0),
7, 7, TLV_DB_SCALE_ITEM(1200, 0, 0),
};
+static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
/* The VU bits for the headphones are in a different register to the mute
* bits and only take effect on the PGA if it is actually powered.
@@ -2119,6 +2122,18 @@ SOC_SINGLE_TLV("HPMIXR MIXINR Volume", WM8962_HEADPHONE_MIXER_4,
SOC_SINGLE_TLV("Speaker Boost Volume", WM8962_CLASS_D_CONTROL_2, 0, 7, 0,
classd_tlv),
+
+SOC_SINGLE("EQ Switch", WM8962_EQ1, WM8962_EQ_ENA_SHIFT, 1, 0),
+SOC_DOUBLE_R_TLV("EQ1 Volume", WM8962_EQ2, WM8962_EQ22,
+ WM8962_EQL_B1_GAIN_SHIFT, 31, 0, eq_tlv),
+SOC_DOUBLE_R_TLV("EQ2 Volume", WM8962_EQ2, WM8962_EQ22,
+ WM8962_EQL_B2_GAIN_SHIFT, 31, 0, eq_tlv),
+SOC_DOUBLE_R_TLV("EQ3 Volume", WM8962_EQ2, WM8962_EQ22,
+ WM8962_EQL_B3_GAIN_SHIFT, 31, 0, eq_tlv),
+SOC_DOUBLE_R_TLV("EQ4 Volume", WM8962_EQ3, WM8962_EQ23,
+ WM8962_EQL_B4_GAIN_SHIFT, 31, 0, eq_tlv),
+SOC_DOUBLE_R_TLV("EQ5 Volume", WM8962_EQ3, WM8962_EQ23,
+ WM8962_EQL_B5_GAIN_SHIFT, 31, 0, eq_tlv),
};
static const struct snd_kcontrol_new wm8962_spk_mono_controls[] = {
@@ -2184,6 +2199,8 @@ static int sysclk_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_codec *codec = w->codec;
+ struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+ unsigned long timeout;
int src;
int fll;
@@ -2203,9 +2220,19 @@ static int sysclk_event(struct snd_soc_dapm_widget *w,
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
- if (fll)
+ if (fll) {
snd_soc_update_bits(codec, WM8962_FLL_CONTROL_1,
WM8962_FLL_ENA, WM8962_FLL_ENA);
+ if (wm8962->irq) {
+ timeout = msecs_to_jiffies(5);
+ timeout = wait_for_completion_timeout(&wm8962->fll_lock,
+ timeout);
+
+ if (timeout == 0)
+ dev_err(codec->dev,
+ "Timed out starting FLL\n");
+ }
+ }
break;
case SND_SOC_DAPM_POST_PMD:
@@ -2763,18 +2790,44 @@ static const int bclk_divs[] = {
1, -1, 2, 3, 4, -1, 6, 8, -1, 12, 16, 24, -1, 32, 32, 32
};
+static const int sysclk_rates[] = {
+ 64, 128, 192, 256, 384, 512, 768, 1024, 1408, 1536,
+};
+
static void wm8962_configure_bclk(struct snd_soc_codec *codec)
{
struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
int dspclk, i;
int clocking2 = 0;
+ int clocking4 = 0;
int aif2 = 0;
- if (!wm8962->bclk) {
- dev_dbg(codec->dev, "No BCLK rate configured\n");
+ if (!wm8962->sysclk_rate) {
+ dev_dbg(codec->dev, "No SYSCLK configured\n");
+ return;
+ }
+
+ if (!wm8962->bclk || !wm8962->lrclk) {
+ dev_dbg(codec->dev, "No audio clocks configured\n");
return;
}
+ for (i = 0; i < ARRAY_SIZE(sysclk_rates); i++) {
+ if (sysclk_rates[i] == wm8962->sysclk_rate / wm8962->lrclk) {
+ clocking4 |= i << WM8962_SYSCLK_RATE_SHIFT;
+ break;
+ }
+ }
+
+ if (i == ARRAY_SIZE(sysclk_rates)) {
+ dev_err(codec->dev, "Unsupported sysclk ratio %d\n",
+ wm8962->sysclk_rate / wm8962->lrclk);
+ return;
+ }
+
+ snd_soc_update_bits(codec, WM8962_CLOCKING_4,
+ WM8962_SYSCLK_RATE_MASK, clocking4);
+
dspclk = snd_soc_read(codec, WM8962_CLOCKING1);
if (dspclk < 0) {
dev_err(codec->dev, "Failed to read DSPCLK: %d\n", dspclk);
@@ -2844,6 +2897,8 @@ static int wm8962_set_bias_level(struct snd_soc_codec *codec,
/* VMID 2*50k */
snd_soc_update_bits(codec, WM8962_PWR_MGMT_1,
WM8962_VMID_SEL_MASK, 0x80);
+
+ wm8962_configure_bclk(codec);
break;
case SND_SOC_BIAS_STANDBY:
@@ -2876,8 +2931,6 @@ static int wm8962_set_bias_level(struct snd_soc_codec *codec,
snd_soc_update_bits(codec, WM8962_CLOCKING2,
WM8962_CLKREG_OVD,
WM8962_CLKREG_OVD);
-
- wm8962_configure_bclk(codec);
}
/* VMID 2*250k */
@@ -2918,10 +2971,6 @@ static const struct {
{ 96000, 6 },
};
-static const int sysclk_rates[] = {
- 64, 128, 192, 256, 384, 512, 768, 1024, 1408, 1536,
-};
-
static int wm8962_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
@@ -2929,41 +2978,27 @@ static int wm8962_hw_params(struct snd_pcm_substream *substream,
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_codec *codec = rtd->codec;
struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
- int rate = params_rate(params);
int i;
int aif0 = 0;
int adctl3 = 0;
- int clocking4 = 0;
wm8962->bclk = snd_soc_params_to_bclk(params);
wm8962->lrclk = params_rate(params);
for (i = 0; i < ARRAY_SIZE(sr_vals); i++) {
- if (sr_vals[i].rate == rate) {
+ if (sr_vals[i].rate == wm8962->lrclk) {
adctl3 |= sr_vals[i].reg;
break;
}
}
if (i == ARRAY_SIZE(sr_vals)) {
- dev_err(codec->dev, "Unsupported rate %dHz\n", rate);
+ dev_err(codec->dev, "Unsupported rate %dHz\n", wm8962->lrclk);
return -EINVAL;
}
- if (rate % 8000 == 0)
+ if (wm8962->lrclk % 8000 == 0)
adctl3 |= WM8962_SAMPLE_RATE_INT_MODE;
- for (i = 0; i < ARRAY_SIZE(sysclk_rates); i++) {
- if (sysclk_rates[i] == wm8962->sysclk_rate / rate) {
- clocking4 |= i << WM8962_SYSCLK_RATE_SHIFT;
- break;
- }
- }
- if (i == ARRAY_SIZE(sysclk_rates)) {
- dev_err(codec->dev, "Unsupported sysclk ratio %d\n",
- wm8962->sysclk_rate / rate);
- return -EINVAL;
- }
-
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
break;
@@ -2985,8 +3020,6 @@ static int wm8962_hw_params(struct snd_pcm_substream *substream,
snd_soc_update_bits(codec, WM8962_ADDITIONAL_CONTROL_3,
WM8962_SAMPLE_RATE_INT_MODE |
WM8962_SAMPLE_RATE_MASK, adctl3);
- snd_soc_update_bits(codec, WM8962_CLOCKING_4,
- WM8962_SYSCLK_RATE_MASK, clocking4);
wm8962_configure_bclk(codec);
@@ -3261,16 +3294,31 @@ static int wm8962_set_fll(struct snd_soc_codec *codec, int fll_id, int source,
dev_dbg(codec->dev, "FLL configured for %dHz->%dHz\n", Fref, Fout);
- /* This should be a massive overestimate */
- timeout = msecs_to_jiffies(1);
+ ret = 0;
+
+ if (fll1 & WM8962_FLL_ENA) {
+ /* This should be a massive overestimate but go even
+ * higher if we'll error out
+ */
+ if (wm8962->irq)
+ timeout = msecs_to_jiffies(5);
+ else
+ timeout = msecs_to_jiffies(1);
+
+ timeout = wait_for_completion_timeout(&wm8962->fll_lock,
+ timeout);
- wait_for_completion_timeout(&wm8962->fll_lock, timeout);
+ if (timeout == 0 && wm8962->irq) {
+ dev_err(codec->dev, "FLL lock timed out");
+ ret = -ETIMEDOUT;
+ }
+ }
wm8962->fll_fref = Fref;
wm8962->fll_fout = Fout;
wm8962->fll_src = source;
- return 0;
+ return ret;
}
static int wm8962_mute(struct snd_soc_dai *dai, int mute)
@@ -3731,8 +3779,6 @@ static int wm8962_probe(struct snd_soc_codec *codec)
int ret;
struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
struct wm8962_pdata *pdata = dev_get_platdata(codec->dev);
- struct i2c_client *i2c = container_of(codec->dev, struct i2c_client,
- dev);
u16 *reg_cache = codec->reg_cache;
int i, trigger, irq_pol;
bool dmicclk, dmicdat;
@@ -3871,6 +3917,9 @@ static int wm8962_probe(struct snd_soc_codec *codec)
snd_soc_update_bits(codec, WM8962_HPOUTR_VOLUME,
WM8962_HPOUT_VU, WM8962_HPOUT_VU);
+ /* Stereo control for EQ */
+ snd_soc_update_bits(codec, WM8962_EQ1, WM8962_EQ_SHARED_COEFF, 0);
+
wm8962_add_widgets(codec);
/* Save boards having to disable DMIC when not in use */
@@ -3899,7 +3948,7 @@ static int wm8962_probe(struct snd_soc_codec *codec)
wm8962_init_beep(codec);
wm8962_init_gpio(codec);
- if (i2c->irq) {
+ if (wm8962->irq) {
if (pdata && pdata->irq_active_low) {
trigger = IRQF_TRIGGER_LOW;
irq_pol = WM8962_IRQ_POL;
@@ -3911,12 +3960,13 @@ static int wm8962_probe(struct snd_soc_codec *codec)
snd_soc_update_bits(codec, WM8962_INTERRUPT_CONTROL,
WM8962_IRQ_POL, irq_pol);
- ret = request_threaded_irq(i2c->irq, NULL, wm8962_irq,
+ ret = request_threaded_irq(wm8962->irq, NULL, wm8962_irq,
trigger | IRQF_ONESHOT,
"wm8962", codec);
if (ret != 0) {
dev_err(codec->dev, "Failed to request IRQ %d: %d\n",
- i2c->irq, ret);
+ wm8962->irq, ret);
+ wm8962->irq = 0;
/* Non-fatal */
} else {
/* Enable some IRQs by default */
@@ -3941,12 +3991,10 @@ err:
static int wm8962_remove(struct snd_soc_codec *codec)
{
struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
- struct i2c_client *i2c = container_of(codec->dev, struct i2c_client,
- dev);
int i;
- if (i2c->irq)
- free_irq(i2c->irq, codec);
+ if (wm8962->irq)
+ free_irq(wm8962->irq, codec);
cancel_delayed_work_sync(&wm8962->mic_work);
@@ -3986,6 +4034,8 @@ static __devinit int wm8962_i2c_probe(struct i2c_client *i2c,
i2c_set_clientdata(i2c, wm8962);
+ wm8962->irq = i2c->irq;
+
ret = snd_soc_register_codec(&i2c->dev,
&soc_codec_dev_wm8962, &wm8962_dai, 1);
if (ret < 0)
diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c
index 9e5ff789b805..6e85b8869af7 100644
--- a/sound/soc/codecs/wm8993.c
+++ b/sound/soc/codecs/wm8993.c
@@ -876,7 +876,7 @@ SND_SOC_DAPM_MIXER("SPKL", WM8993_POWER_MANAGEMENT_3, 8, 0,
left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)),
SND_SOC_DAPM_MIXER("SPKR", WM8993_POWER_MANAGEMENT_3, 9, 0,
right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)),
-
+SND_SOC_DAPM_PGA("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0),
};
static const struct snd_soc_dapm_route routes[] = {
@@ -1434,6 +1434,7 @@ static int wm8993_probe(struct snd_soc_codec *codec)
wm8993->hubs_data.hp_startup_mode = 1;
wm8993->hubs_data.dcs_codes = -2;
+ wm8993->hubs_data.series_startup = 1;
ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C);
if (ret != 0) {
diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c
index 5f0c238e1783..ee64be2d9942 100644
--- a/sound/soc/codecs/wm8994.c
+++ b/sound/soc/codecs/wm8994.c
@@ -195,10 +195,6 @@ static int configure_aif_clock(struct snd_soc_codec *codec, int aif)
aif + 1, rate);
}
- if (rate && rate < 3000000)
- dev_warn(codec->dev, "AIF%dCLK is %dHz, should be >=3MHz for optimal performance\n",
- aif + 1, rate);
-
wm8994->aifclk[aif] = rate;
snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1 + offset,
@@ -1146,13 +1142,33 @@ SND_SOC_DAPM_PGA_E("Late DAC2L Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0,
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
SND_SOC_DAPM_PGA_E("Late DAC2R Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0,
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
+SND_SOC_DAPM_PGA_E("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0,
+ late_enable_ev, SND_SOC_DAPM_PRE_PMU),
+
+SND_SOC_DAPM_MIXER_E("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0,
+ left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer),
+ late_enable_ev, SND_SOC_DAPM_PRE_PMU),
+SND_SOC_DAPM_MIXER_E("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0,
+ right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer),
+ late_enable_ev, SND_SOC_DAPM_PRE_PMU),
+SND_SOC_DAPM_MUX_E("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux,
+ late_enable_ev, SND_SOC_DAPM_PRE_PMU),
+SND_SOC_DAPM_MUX_E("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux,
+ late_enable_ev, SND_SOC_DAPM_PRE_PMU),
SND_SOC_DAPM_POST("Late Disable PGA", late_disable_ev)
};
static const struct snd_soc_dapm_widget wm8994_lateclk_widgets[] = {
SND_SOC_DAPM_SUPPLY("AIF1CLK", WM8994_AIF1_CLOCKING_1, 0, 0, NULL, 0),
-SND_SOC_DAPM_SUPPLY("AIF2CLK", WM8994_AIF2_CLOCKING_1, 0, 0, NULL, 0)
+SND_SOC_DAPM_SUPPLY("AIF2CLK", WM8994_AIF2_CLOCKING_1, 0, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0,
+ left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)),
+SND_SOC_DAPM_MIXER("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0,
+ right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)),
+SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux),
+SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux),
};
static const struct snd_soc_dapm_widget wm8994_dac_revd_widgets[] = {
@@ -1283,14 +1299,6 @@ SND_SOC_DAPM_ADC("DMIC1R", NULL, WM8994_POWER_MANAGEMENT_4, 2, 0),
SND_SOC_DAPM_ADC("ADCL", NULL, SND_SOC_NOPM, 1, 0),
SND_SOC_DAPM_ADC("ADCR", NULL, SND_SOC_NOPM, 0, 0),
-SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux),
-SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux),
-
-SND_SOC_DAPM_MIXER("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0,
- left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)),
-SND_SOC_DAPM_MIXER("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0,
- right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)),
-
SND_SOC_DAPM_POST("Debug log", post_ev),
};
@@ -1623,6 +1631,7 @@ static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src,
int reg_offset, ret;
struct fll_div fll;
u16 reg, aif1, aif2;
+ unsigned long timeout;
aif1 = snd_soc_read(codec, WM8994_AIF1_CLOCKING_1)
& WM8994_AIF1CLK_ENA;
@@ -1714,7 +1723,15 @@ static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src,
WM8994_FLL1_ENA | WM8994_FLL1_FRAC,
reg);
- msleep(5);
+ if (wm8994->fll_locked_irq) {
+ timeout = wait_for_completion_timeout(&wm8994->fll_locked[id],
+ msecs_to_jiffies(10));
+ if (timeout == 0)
+ dev_warn(codec->dev,
+ "Timed out waiting for FLL lock\n");
+ } else {
+ msleep(5);
+ }
}
wm8994->fll[id].in = freq_in;
@@ -1732,6 +1749,14 @@ static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src,
return 0;
}
+static irqreturn_t wm8994_fll_locked_irq(int irq, void *data)
+{
+ struct completion *completion = data;
+
+ complete(completion);
+
+ return IRQ_HANDLED;
+}
static int opclk_divs[] = { 10, 20, 30, 40, 55, 60, 80, 120, 160 };
@@ -2849,6 +2874,15 @@ out:
return IRQ_HANDLED;
}
+static irqreturn_t wm8994_fifo_error(int irq, void *data)
+{
+ struct snd_soc_codec *codec = data;
+
+ dev_err(codec->dev, "FIFO error\n");
+
+ return IRQ_HANDLED;
+}
+
static int wm8994_codec_probe(struct snd_soc_codec *codec)
{
struct wm8994 *control;
@@ -2867,6 +2901,9 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec)
wm8994->pdata = dev_get_platdata(codec->dev->parent);
wm8994->codec = codec;
+ for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++)
+ init_completion(&wm8994->fll_locked[i]);
+
if (wm8994->pdata && wm8994->pdata->micdet_irq)
wm8994->micdet_irq = wm8994->pdata->micdet_irq;
else if (wm8994->pdata && wm8994->pdata->irq_base)
@@ -2905,6 +2942,7 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec)
wm8994->hubs.dcs_codes = -5;
wm8994->hubs.hp_startup_mode = 1;
wm8994->hubs.dcs_readback_mode = 1;
+ wm8994->hubs.series_startup = 1;
break;
default:
wm8994->hubs.dcs_readback_mode = 1;
@@ -2919,6 +2957,15 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec)
break;
}
+ wm8994_request_irq(codec->control_data, WM8994_IRQ_FIFOS_ERR,
+ wm8994_fifo_error, "FIFO error", codec);
+
+ ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_DCS_DONE,
+ wm_hubs_dcs_done, "DC servo done",
+ &wm8994->hubs);
+ if (ret == 0)
+ wm8994->hubs.dcs_done_irq = true;
+
switch (control->type) {
case WM8994:
if (wm8994->micdet_irq) {
@@ -2975,6 +3022,16 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec)
}
}
+ wm8994->fll_locked_irq = true;
+ for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++) {
+ ret = wm8994_request_irq(codec->control_data,
+ WM8994_IRQ_FLL1_LOCK + i,
+ wm8994_fll_locked_irq, "FLL lock",
+ &wm8994->fll_locked[i]);
+ if (ret != 0)
+ wm8994->fll_locked_irq = false;
+ }
+
/* Remember if AIFnLRCLK is configured as a GPIO. This should be
* configured on init - if a system wants to do this dynamically
* at runtime we can deal with that then.
@@ -3050,10 +3107,18 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec)
1 << WM8994_AIF2DAC_3D_GAIN_SHIFT,
1 << WM8994_AIF2DAC_3D_GAIN_SHIFT);
- /* Unconditionally enable AIF1 ADC TDM mode; it only affects
- * behaviour on idle TDM clock cycles. */
- snd_soc_update_bits(codec, WM8994_AIF1_CONTROL_1,
- WM8994_AIF1ADC_TDM, WM8994_AIF1ADC_TDM);
+ /* Unconditionally enable AIF1 ADC TDM mode on chips which can
+ * use this; it only affects behaviour on idle TDM clock
+ * cycles. */
+ switch (control->type) {
+ case WM8994:
+ case WM8958:
+ snd_soc_update_bits(codec, WM8994_AIF1_CONTROL_1,
+ WM8994_AIF1ADC_TDM, WM8994_AIF1ADC_TDM);
+ break;
+ default:
+ break;
+ }
wm8994_update_class_w(codec);
@@ -3152,6 +3217,12 @@ err_irq:
wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, wm8994);
if (wm8994->micdet_irq)
free_irq(wm8994->micdet_irq, wm8994);
+ for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++)
+ wm8994_free_irq(codec->control_data, WM8994_IRQ_FLL1_LOCK + i,
+ &wm8994->fll_locked[i]);
+ wm8994_free_irq(codec->control_data, WM8994_IRQ_DCS_DONE,
+ &wm8994->hubs);
+ wm8994_free_irq(codec->control_data, WM8994_IRQ_FIFOS_ERR, codec);
err:
kfree(wm8994);
return ret;
@@ -3161,11 +3232,20 @@ static int wm8994_codec_remove(struct snd_soc_codec *codec)
{
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
struct wm8994 *control = codec->control_data;
+ int i;
wm8994_set_bias_level(codec, SND_SOC_BIAS_OFF);
pm_runtime_disable(codec->dev);
+ for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++)
+ wm8994_free_irq(codec->control_data, WM8994_IRQ_FLL1_LOCK + i,
+ &wm8994->fll_locked[i]);
+
+ wm8994_free_irq(codec->control_data, WM8994_IRQ_DCS_DONE,
+ &wm8994->hubs);
+ wm8994_free_irq(codec->control_data, WM8994_IRQ_FIFOS_ERR, codec);
+
switch (control->type) {
case WM8994:
if (wm8994->micdet_irq)
diff --git a/sound/soc/codecs/wm8994.h b/sound/soc/codecs/wm8994.h
index 0a1db04b73bd..1ab2266039f7 100644
--- a/sound/soc/codecs/wm8994.h
+++ b/sound/soc/codecs/wm8994.h
@@ -11,6 +11,7 @@
#include <sound/soc.h>
#include <linux/firmware.h>
+#include <linux/completion.h>
#include "wm_hubs.h"
@@ -79,6 +80,8 @@ struct wm8994_priv {
int mclk[2];
int aifclk[2];
struct wm8994_fll_config fll[2], fll_suspend[2];
+ struct completion fll_locked[2];
+ bool fll_locked_irq;
int dac_rates[2];
int lrclk_shared[2];
diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c
index 91c6b39de50c..a4691321f9b3 100644
--- a/sound/soc/codecs/wm9081.c
+++ b/sound/soc/codecs/wm9081.c
@@ -727,7 +727,7 @@ SND_SOC_DAPM_MIXER_NAMED_CTL("Mixer", SND_SOC_NOPM, 0, 0,
SND_SOC_DAPM_PGA("LINEOUT PGA", WM9081_POWER_MANAGEMENT, 4, 0, NULL, 0),
SND_SOC_DAPM_PGA("Speaker PGA", WM9081_POWER_MANAGEMENT, 2, 0, NULL, 0),
-SND_SOC_DAPM_PGA("Speaker", WM9081_POWER_MANAGEMENT, 1, 0, NULL, 0),
+SND_SOC_DAPM_OUT_DRV("Speaker", WM9081_POWER_MANAGEMENT, 1, 0, NULL, 0),
SND_SOC_DAPM_OUTPUT("LINEOUT"),
SND_SOC_DAPM_OUTPUT("SPKN"),
diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c
index 9e370d14ad88..5c2d5657b472 100644
--- a/sound/soc/codecs/wm_hubs.c
+++ b/sound/soc/codecs/wm_hubs.c
@@ -63,9 +63,11 @@ static const struct soc_enum speaker_mode =
static void wait_for_dc_servo(struct snd_soc_codec *codec, unsigned int op)
{
+ struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
unsigned int reg;
int count = 0;
unsigned int val;
+ unsigned long timeout;
val = op | WM8993_DCS_ENA_CHAN_0 | WM8993_DCS_ENA_CHAN_1;
@@ -74,18 +76,37 @@ static void wait_for_dc_servo(struct snd_soc_codec *codec, unsigned int op)
dev_dbg(codec->dev, "Waiting for DC servo...\n");
- do {
- count++;
- msleep(1);
+ if (hubs->dcs_done_irq) {
+ timeout = wait_for_completion_timeout(&hubs->dcs_done,
+ msecs_to_jiffies(500));
+ if (timeout == 0)
+ dev_warn(codec->dev, "No DC servo interrupt\n");
+
reg = snd_soc_read(codec, WM8993_DC_SERVO_0);
- dev_dbg(codec->dev, "DC servo: %x\n", reg);
- } while (reg & op && count < 400);
+ } else {
+ do {
+ count++;
+ msleep(1);
+ reg = snd_soc_read(codec, WM8993_DC_SERVO_0);
+ dev_dbg(codec->dev, "DC servo: %x\n", reg);
+ } while (reg & op && count < 400);
+ }
if (reg & op)
dev_err(codec->dev, "Timed out waiting for DC Servo %x\n",
op);
}
+irqreturn_t wm_hubs_dcs_done(int irq, void *data)
+{
+ struct wm_hubs_data *hubs = data;
+
+ complete(&hubs->dcs_done);
+
+ return IRQ_HANDLED;
+}
+EXPORT_SYMBOL_GPL(wm_hubs_dcs_done);
+
/*
* Startup calibration of the DC servo
*/
@@ -107,8 +128,7 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec)
return;
}
- /* Devices not using a DCS code correction have startup mode */
- if (hubs->dcs_codes) {
+ if (hubs->series_startup) {
/* Set for 32 series updates */
snd_soc_update_bits(codec, WM8993_DC_SERVO_1,
WM8993_DCS_SERIES_NO_01_MASK,
@@ -134,9 +154,9 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec)
break;
case 1:
reg = snd_soc_read(codec, WM8993_DC_SERVO_3);
- reg_l = (reg & WM8993_DCS_DAC_WR_VAL_1_MASK)
+ reg_r = (reg & WM8993_DCS_DAC_WR_VAL_1_MASK)
>> WM8993_DCS_DAC_WR_VAL_1_SHIFT;
- reg_r = reg & WM8993_DCS_DAC_WR_VAL_0_MASK;
+ reg_l = reg & WM8993_DCS_DAC_WR_VAL_0_MASK;
break;
default:
WARN(1, "Unknown DCS readback method\n");
@@ -150,13 +170,13 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec)
dev_dbg(codec->dev, "Applying %d code DC servo correction\n",
hubs->dcs_codes);
- /* HPOUT1L */
- offset = reg_l;
+ /* HPOUT1R */
+ offset = reg_r;
offset += hubs->dcs_codes;
dcs_cfg = (u8)offset << WM8993_DCS_DAC_WR_VAL_1_SHIFT;
- /* HPOUT1R */
- offset = reg_r;
+ /* HPOUT1L */
+ offset = reg_l;
offset += hubs->dcs_codes;
dcs_cfg |= (u8)offset;
@@ -168,8 +188,8 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec)
WM8993_DCS_TRIG_DAC_WR_0 |
WM8993_DCS_TRIG_DAC_WR_1);
} else {
- dcs_cfg = reg_l << WM8993_DCS_DAC_WR_VAL_1_SHIFT;
- dcs_cfg |= reg_r;
+ dcs_cfg = reg_r << WM8993_DCS_DAC_WR_VAL_1_SHIFT;
+ dcs_cfg |= reg_l;
}
/* Save the callibrated offset if we're in class W mode and
@@ -195,7 +215,7 @@ static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol,
/* If we're applying an offset correction then updating the
* callibration would be likely to introduce further offsets. */
- if (hubs->dcs_codes)
+ if (hubs->dcs_codes || hubs->no_series_update)
return ret;
/* Only need to do this if the outputs are active */
@@ -599,9 +619,6 @@ SND_SOC_DAPM_MIXER("IN2L PGA", WM8993_POWER_MANAGEMENT_2, 7, 0,
SND_SOC_DAPM_MIXER("IN2R PGA", WM8993_POWER_MANAGEMENT_2, 5, 0,
in2r_pga, ARRAY_SIZE(in2r_pga)),
-/* Dummy widgets to represent differential paths */
-SND_SOC_DAPM_PGA("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0),
-
SND_SOC_DAPM_MIXER("MIXINL", WM8993_POWER_MANAGEMENT_2, 9, 0,
mixinl, ARRAY_SIZE(mixinl)),
SND_SOC_DAPM_MIXER("MIXINR", WM8993_POWER_MANAGEMENT_2, 8, 0,
@@ -867,8 +884,11 @@ EXPORT_SYMBOL_GPL(wm_hubs_add_analogue_controls);
int wm_hubs_add_analogue_routes(struct snd_soc_codec *codec,
int lineout1_diff, int lineout2_diff)
{
+ struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
struct snd_soc_dapm_context *dapm = &codec->dapm;
+ init_completion(&hubs->dcs_done);
+
snd_soc_dapm_add_routes(dapm, analogue_routes,
ARRAY_SIZE(analogue_routes));
diff --git a/sound/soc/codecs/wm_hubs.h b/sound/soc/codecs/wm_hubs.h
index f8a5e976b5e6..676b1252ab91 100644
--- a/sound/soc/codecs/wm_hubs.h
+++ b/sound/soc/codecs/wm_hubs.h
@@ -14,6 +14,9 @@
#ifndef _WM_HUBS_H
#define _WM_HUBS_H
+#include <linux/completion.h>
+#include <linux/interrupt.h>
+
struct snd_soc_codec;
extern const unsigned int wm_hubs_spkmix_tlv[];
@@ -23,9 +26,14 @@ struct wm_hubs_data {
int dcs_codes;
int dcs_readback_mode;
int hp_startup_mode;
+ int series_startup;
+ int no_series_update;
bool class_w;
u16 class_w_dcs;
+
+ bool dcs_done_irq;
+ struct completion dcs_done;
};
extern int wm_hubs_add_analogue_controls(struct snd_soc_codec *);
@@ -36,4 +44,6 @@ extern int wm_hubs_handle_analogue_pdata(struct snd_soc_codec *,
int jd_scthr, int jd_thr,
int micbias1_lvl, int micbias2_lvl);
+extern irqreturn_t wm_hubs_dcs_done(int irq, void *data);
+
#endif