summaryrefslogtreecommitdiffstats
path: root/sound/soc/codecs
diff options
context:
space:
mode:
authorTimothy Pearson <tpearson@raptorengineering.com>2017-08-23 14:45:25 -0500
committerTimothy Pearson <tpearson@raptorengineering.com>2017-08-23 14:45:25 -0500
commitfcbb27b0ec6dcbc5a5108cb8fb19eae64593d204 (patch)
tree22962a4387943edc841c72a4e636a068c66d58fd /sound/soc/codecs
downloadast2050-linux-kernel-fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204.zip
ast2050-linux-kernel-fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204.tar.gz
Initial import of modified Linux 2.6.28 tree
Original upstream URL: git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git | branch linux-2.6.28.y
Diffstat (limited to 'sound/soc/codecs')
-rw-r--r--sound/soc/codecs/Kconfig112
-rw-r--r--sound/soc/codecs/Makefile43
-rw-r--r--sound/soc/codecs/ac97.c179
-rw-r--r--sound/soc/codecs/ac97.h19
-rw-r--r--sound/soc/codecs/ad1980.c308
-rw-r--r--sound/soc/codecs/ad1980.h23
-rw-r--r--sound/soc/codecs/ad73311.c107
-rw-r--r--sound/soc/codecs/ad73311.h90
-rw-r--r--sound/soc/codecs/ak4535.c694
-rw-r--r--sound/soc/codecs/ak4535.h47
-rw-r--r--sound/soc/codecs/cs4270.c765
-rw-r--r--sound/soc/codecs/cs4270.h28
-rw-r--r--sound/soc/codecs/ssm2602.c775
-rw-r--r--sound/soc/codecs/ssm2602.h130
-rw-r--r--sound/soc/codecs/tlv320aic23.c714
-rw-r--r--sound/soc/codecs/tlv320aic23.h122
-rw-r--r--sound/soc/codecs/tlv320aic26.c520
-rw-r--r--sound/soc/codecs/tlv320aic26.h96
-rw-r--r--sound/soc/codecs/tlv320aic3x.c1346
-rw-r--r--sound/soc/codecs/tlv320aic3x.h235
-rw-r--r--sound/soc/codecs/uda1380.c849
-rw-r--r--sound/soc/codecs/uda1380.h90
-rw-r--r--sound/soc/codecs/wm8510.c895
-rw-r--r--sound/soc/codecs/wm8510.h105
-rw-r--r--sound/soc/codecs/wm8580.c1053
-rw-r--r--sound/soc/codecs/wm8580.h42
-rw-r--r--sound/soc/codecs/wm8731.c797
-rw-r--r--sound/soc/codecs/wm8731.h46
-rw-r--r--sound/soc/codecs/wm8750.c1091
-rw-r--r--sound/soc/codecs/wm8750.h69
-rw-r--r--sound/soc/codecs/wm8753.c1882
-rw-r--r--sound/soc/codecs/wm8753.h127
-rw-r--r--sound/soc/codecs/wm8900.c1541
-rw-r--r--sound/soc/codecs/wm8900.h64
-rw-r--r--sound/soc/codecs/wm8903.c1813
-rw-r--r--sound/soc/codecs/wm8903.h1463
-rw-r--r--sound/soc/codecs/wm8971.c941
-rw-r--r--sound/soc/codecs/wm8971.h64
-rw-r--r--sound/soc/codecs/wm8990.c1635
-rw-r--r--sound/soc/codecs/wm8990.h843
-rw-r--r--sound/soc/codecs/wm9712.c750
-rw-r--r--sound/soc/codecs/wm9712.h14
-rw-r--r--sound/soc/codecs/wm9713.c1306
-rw-r--r--sound/soc/codecs/wm9713.h53
44 files changed, 23886 insertions, 0 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
new file mode 100644
index 0000000..38a0e3b
--- /dev/null
+++ b/sound/soc/codecs/Kconfig
@@ -0,0 +1,112 @@
+config SND_SOC_ALL_CODECS
+ tristate "Build all ASoC CODEC drivers"
+ depends on I2C
+ select SPI
+ select SPI_MASTER
+ select SND_SOC_AD73311
+ select SND_SOC_AK4535
+ select SND_SOC_CS4270
+ select SND_SOC_SSM2602
+ select SND_SOC_TLV320AIC23
+ select SND_SOC_TLV320AIC26
+ select SND_SOC_TLV320AIC3X
+ select SND_SOC_UDA1380
+ select SND_SOC_WM8510
+ select SND_SOC_WM8580
+ select SND_SOC_WM8731
+ select SND_SOC_WM8750
+ select SND_SOC_WM8753
+ select SND_SOC_WM8900
+ select SND_SOC_WM8903
+ select SND_SOC_WM8971
+ select SND_SOC_WM8990
+ help
+ Normally ASoC codec drivers are only built if a machine driver which
+ uses them is also built since they are only usable with a machine
+ driver. Selecting this option will allow these drivers to be built
+ without an explicit machine driver for test and development purposes.
+
+ If unsure select "N".
+
+
+config SND_SOC_AC97_CODEC
+ tristate
+ select SND_AC97_CODEC
+
+config SND_SOC_AD1980
+ tristate
+
+config SND_SOC_AD73311
+ tristate
+
+config SND_SOC_AK4535
+ tristate
+
+# Cirrus Logic CS4270 Codec
+config SND_SOC_CS4270
+ tristate
+
+# Cirrus Logic CS4270 Codec Hardware Mute Support
+# Select if you have external muting circuitry attached to your CS4270.
+config SND_SOC_CS4270_HWMUTE
+ bool
+ depends on SND_SOC_CS4270
+
+# Cirrus Logic CS4270 Codec VD = 3.3V Errata
+# Select if you are affected by the errata where the part will not function
+# if MCLK divide-by-1.5 is selected and VD is set to 3.3V. The driver will
+# not select any sample rates that require MCLK to be divided by 1.5.
+config SND_SOC_CS4270_VD33_ERRATA
+ bool
+ depends on SND_SOC_CS4270
+
+config SND_SOC_SSM2602
+ tristate
+
+config SND_SOC_TLV320AIC23
+ tristate
+ depends on I2C
+
+config SND_SOC_TLV320AIC26
+ tristate "TI TLV320AIC26 Codec support" if SND_SOC_OF_SIMPLE
+ depends on SPI
+
+config SND_SOC_TLV320AIC3X
+ tristate
+ depends on I2C
+
+config SND_SOC_UDA1380
+ tristate
+
+config SND_SOC_WM8510
+ tristate
+
+config SND_SOC_WM8580
+ tristate
+
+config SND_SOC_WM8731
+ tristate
+
+config SND_SOC_WM8750
+ tristate
+
+config SND_SOC_WM8753
+ tristate
+
+config SND_SOC_WM8900
+ tristate
+
+config SND_SOC_WM8903
+ tristate
+
+config SND_SOC_WM8971
+ tristate
+
+config SND_SOC_WM8990
+ tristate
+
+config SND_SOC_WM9712
+ tristate
+
+config SND_SOC_WM9713
+ tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
new file mode 100644
index 0000000..90f0a58
--- /dev/null
+++ b/sound/soc/codecs/Makefile
@@ -0,0 +1,43 @@
+snd-soc-ac97-objs := ac97.o
+snd-soc-ad1980-objs := ad1980.o
+snd-soc-ad73311-objs := ad73311.o
+snd-soc-ak4535-objs := ak4535.o
+snd-soc-cs4270-objs := cs4270.o
+snd-soc-ssm2602-objs := ssm2602.o
+snd-soc-tlv320aic23-objs := tlv320aic23.o
+snd-soc-tlv320aic26-objs := tlv320aic26.o
+snd-soc-tlv320aic3x-objs := tlv320aic3x.o
+snd-soc-uda1380-objs := uda1380.o
+snd-soc-wm8510-objs := wm8510.o
+snd-soc-wm8580-objs := wm8580.o
+snd-soc-wm8731-objs := wm8731.o
+snd-soc-wm8750-objs := wm8750.o
+snd-soc-wm8753-objs := wm8753.o
+snd-soc-wm8900-objs := wm8900.o
+snd-soc-wm8903-objs := wm8903.o
+snd-soc-wm8971-objs := wm8971.o
+snd-soc-wm8990-objs := wm8990.o
+snd-soc-wm9712-objs := wm9712.o
+snd-soc-wm9713-objs := wm9713.o
+
+obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o
+obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o
+obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o
+obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o
+obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
+obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o
+obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o
+obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o
+obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o
+obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o
+obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o
+obj-$(CONFIG_SND_SOC_WM8580) += snd-soc-wm8580.o
+obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o
+obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o
+obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o
+obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o
+obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o
+obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o
+obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o
+obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o
+obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o
diff --git a/sound/soc/codecs/ac97.c b/sound/soc/codecs/ac97.c
new file mode 100644
index 0000000..bd1ebdc
--- /dev/null
+++ b/sound/soc/codecs/ac97.c
@@ -0,0 +1,179 @@
+/*
+ * ac97.c -- ALSA Soc AC97 codec support
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * Generic AC97 support.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include "ac97.h"
+
+#define AC97_VERSION "0.6"
+
+static int ac97_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+
+ int reg = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+ AC97_PCM_FRONT_DAC_RATE : AC97_PCM_LR_ADC_RATE;
+ return snd_ac97_set_rate(codec->ac97, reg, runtime->rate);
+}
+
+#define STD_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\
+ SNDRV_PCM_RATE_48000)
+
+struct snd_soc_dai ac97_dai = {
+ .name = "AC97 HiFi",
+ .type = SND_SOC_DAI_AC97,
+ .playback = {
+ .stream_name = "AC97 Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = STD_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "AC97 Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = STD_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .prepare = ac97_prepare,},
+};
+EXPORT_SYMBOL_GPL(ac97_dai);
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ return soc_ac97_ops.read(codec->ac97, reg);
+}
+
+static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ soc_ac97_ops.write(codec->ac97, reg, val);
+ return 0;
+}
+
+static int ac97_soc_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ struct snd_ac97_bus *ac97_bus;
+ struct snd_ac97_template ac97_template;
+ int ret = 0;
+
+ printk(KERN_INFO "AC97 SoC Audio Codec %s\n", AC97_VERSION);
+
+ socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (!socdev->codec)
+ return -ENOMEM;
+ codec = socdev->codec;
+ mutex_init(&codec->mutex);
+
+ codec->name = "AC97";
+ codec->owner = THIS_MODULE;
+ codec->dai = &ac97_dai;
+ codec->num_dai = 1;
+ codec->write = ac97_write;
+ codec->read = ac97_read;
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0)
+ goto err;
+
+ /* add codec as bus device for standard ac97 */
+ ret = snd_ac97_bus(codec->card, 0, &soc_ac97_ops, NULL, &ac97_bus);
+ if (ret < 0)
+ goto bus_err;
+
+ memset(&ac97_template, 0, sizeof(struct snd_ac97_template));
+ ret = snd_ac97_mixer(ac97_bus, &ac97_template, &codec->ac97);
+ if (ret < 0)
+ goto bus_err;
+
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0)
+ goto bus_err;
+ return 0;
+
+bus_err:
+ snd_soc_free_pcms(socdev);
+
+err:
+ kfree(socdev->codec->reg_cache);
+ kfree(socdev->codec);
+ socdev->codec = NULL;
+ return ret;
+}
+
+static int ac97_soc_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (!codec)
+ return 0;
+
+ snd_soc_free_pcms(socdev);
+ kfree(socdev->codec->reg_cache);
+ kfree(socdev->codec);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int ac97_soc_suspend(struct platform_device *pdev, pm_message_t msg)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+ snd_ac97_suspend(socdev->codec->ac97);
+
+ return 0;
+}
+
+static int ac97_soc_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+ snd_ac97_resume(socdev->codec->ac97);
+
+ return 0;
+}
+#else
+#define ac97_soc_suspend NULL
+#define ac97_soc_resume NULL
+#endif
+
+struct snd_soc_codec_device soc_codec_dev_ac97 = {
+ .probe = ac97_soc_probe,
+ .remove = ac97_soc_remove,
+ .suspend = ac97_soc_suspend,
+ .resume = ac97_soc_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_ac97);
+
+MODULE_DESCRIPTION("Soc Generic AC97 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ac97.h b/sound/soc/codecs/ac97.h
new file mode 100644
index 0000000..281aa42
--- /dev/null
+++ b/sound/soc/codecs/ac97.h
@@ -0,0 +1,19 @@
+/*
+ * linux/sound/codecs/ac97.h -- ALSA SoC Layer
+ *
+ * Author: Liam Girdwood
+ * Created: Dec 1st 2005
+ * Copyright: Wolfson Microelectronics. PLC.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __LINUX_SND_SOC_AC97_H
+#define __LINUX_SND_SOC_AC97_H
+
+extern struct snd_soc_codec_device soc_codec_dev_ac97;
+extern struct snd_soc_dai ac97_dai;
+
+#endif
diff --git a/sound/soc/codecs/ad1980.c b/sound/soc/codecs/ad1980.c
new file mode 100644
index 0000000..1397b8e
--- /dev/null
+++ b/sound/soc/codecs/ad1980.c
@@ -0,0 +1,308 @@
+/*
+ * ad1980.c -- ALSA Soc AD1980 codec support
+ *
+ * Copyright: Analog Device Inc.
+ * Author: Roy Huang <roy.huang@analog.com>
+ * 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/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "ad1980.h"
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg);
+static int ac97_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int val);
+
+/*
+ * AD1980 register cache
+ */
+static const u16 ad1980_reg[] = {
+ 0x0090, 0x8000, 0x8000, 0x8000, /* 0 - 6 */
+ 0x0000, 0x0000, 0x8008, 0x8008, /* 8 - e */
+ 0x8808, 0x8808, 0x0000, 0x8808, /* 10 - 16 */
+ 0x8808, 0x0000, 0x8000, 0x0000, /* 18 - 1e */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 20 - 26 */
+ 0x03c7, 0x0000, 0xbb80, 0xbb80, /* 28 - 2e */
+ 0xbb80, 0xbb80, 0x0000, 0x8080, /* 30 - 36 */
+ 0x8080, 0x2000, 0x0000, 0x0000, /* 38 - 3e */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
+ 0x8080, 0x0000, 0x0000, 0x0000, /* 60 - 66 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
+ 0x0000, 0x0000, 0x1001, 0x0000, /* 70 - 76 */
+ 0x0000, 0x0000, 0x4144, 0x5370 /* 78 - 7e */
+};
+
+static const char *ad1980_rec_sel[] = {"Mic", "CD", "NC", "AUX", "Line",
+ "Stereo Mix", "Mono Mix", "Phone"};
+
+static const struct soc_enum ad1980_cap_src =
+ SOC_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 7, ad1980_rec_sel);
+
+static const struct snd_kcontrol_new ad1980_snd_ac97_controls[] = {
+SOC_DOUBLE("Master Playback Volume", AC97_MASTER, 8, 0, 31, 1),
+SOC_SINGLE("Master Playback Switch", AC97_MASTER, 15, 1, 1),
+
+SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1),
+SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1),
+
+SOC_DOUBLE("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1),
+SOC_SINGLE("PCM Playback Switch", AC97_PCM, 15, 1, 1),
+
+SOC_DOUBLE("PCM Capture Volume", AC97_REC_GAIN, 8, 0, 31, 0),
+SOC_SINGLE("PCM Capture Switch", AC97_REC_GAIN, 15, 1, 1),
+
+SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1),
+SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1),
+
+SOC_SINGLE("Phone Capture Volume", AC97_PHONE, 0, 31, 1),
+SOC_SINGLE("Phone Capture Switch", AC97_PHONE, 15, 1, 1),
+
+SOC_SINGLE("Mic Volume", AC97_MIC, 0, 31, 1),
+SOC_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1),
+
+SOC_SINGLE("Stereo Mic Switch", AC97_AD_MISC, 6, 1, 0),
+SOC_DOUBLE("Line HP Swap Switch", AC97_AD_MISC, 10, 5, 1, 0),
+
+SOC_DOUBLE("Surround Playback Volume", AC97_SURROUND_MASTER, 8, 0, 31, 1),
+SOC_DOUBLE("Surround Playback Switch", AC97_SURROUND_MASTER, 15, 7, 1, 1),
+
+SOC_ENUM("Capture Source", ad1980_cap_src),
+
+SOC_SINGLE("Mic Boost Switch", AC97_MIC, 6, 1, 0),
+};
+
+/* add non dapm controls */
+static int ad1980_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(ad1980_snd_ac97_controls); i++) {
+ err = snd_ctl_add(codec->card, snd_soc_cnew(
+ &ad1980_snd_ac97_controls[i], codec, NULL));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+
+ switch (reg) {
+ case AC97_RESET:
+ case AC97_INT_PAGING:
+ case AC97_POWERDOWN:
+ case AC97_EXTENDED_STATUS:
+ case AC97_VENDOR_ID1:
+ case AC97_VENDOR_ID2:
+ return soc_ac97_ops.read(codec->ac97, reg);
+ default:
+ reg = reg >> 1;
+
+ if (reg >= (ARRAY_SIZE(ad1980_reg)))
+ return -EINVAL;
+
+ return cache[reg];
+ }
+}
+
+static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ u16 *cache = codec->reg_cache;
+
+ soc_ac97_ops.write(codec->ac97, reg, val);
+ reg = reg >> 1;
+ if (reg < (ARRAY_SIZE(ad1980_reg)))
+ cache[reg] = val;
+
+ return 0;
+}
+
+struct snd_soc_dai ad1980_dai = {
+ .name = "AC97",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, },
+};
+EXPORT_SYMBOL_GPL(ad1980_dai);
+
+static int ad1980_reset(struct snd_soc_codec *codec, int try_warm)
+{
+ u16 retry_cnt = 0;
+
+retry:
+ if (try_warm && soc_ac97_ops.warm_reset) {
+ soc_ac97_ops.warm_reset(codec->ac97);
+ if (ac97_read(codec, AC97_RESET) == 0x0090)
+ return 1;
+ }
+
+ soc_ac97_ops.reset(codec->ac97);
+ /* Set bit 16slot in register 74h, then every slot will has only 16
+ * bits. This command is sent out in 20bit mode, in which case the
+ * first nibble of data is eaten by the addr. (Tag is always 16 bit)*/
+ ac97_write(codec, AC97_AD_SERIAL_CFG, 0x9900);
+
+ if (ac97_read(codec, AC97_RESET) != 0x0090)
+ goto err;
+ return 0;
+
+err:
+ while (retry_cnt++ < 10)
+ goto retry;
+
+ printk(KERN_ERR "AD1980 AC97 reset failed\n");
+ return -EIO;
+}
+
+static int ad1980_soc_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret = 0;
+ u16 vendor_id2;
+
+ printk(KERN_INFO "AD1980 SoC Audio Codec\n");
+
+ socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (socdev->codec == NULL)
+ return -ENOMEM;
+ codec = socdev->codec;
+ mutex_init(&codec->mutex);
+
+ codec->reg_cache =
+ kzalloc(sizeof(u16) * ARRAY_SIZE(ad1980_reg), GFP_KERNEL);
+ if (codec->reg_cache == NULL) {
+ ret = -ENOMEM;
+ goto cache_err;
+ }
+ memcpy(codec->reg_cache, ad1980_reg, sizeof(u16) * \
+ ARRAY_SIZE(ad1980_reg));
+ codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(ad1980_reg);
+ codec->reg_cache_step = 2;
+ codec->name = "AD1980";
+ codec->owner = THIS_MODULE;
+ codec->dai = &ad1980_dai;
+ codec->num_dai = 1;
+ codec->write = ac97_write;
+ codec->read = ac97_read;
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0);
+ if (ret < 0) {
+ printk(KERN_ERR "ad1980: failed to register AC97 codec\n");
+ goto codec_err;
+ }
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0)
+ goto pcm_err;
+
+
+ ret = ad1980_reset(codec, 0);
+ if (ret < 0) {
+ printk(KERN_ERR "AC97 link error\n");
+ goto reset_err;
+ }
+
+ /* Read out vendor ID to make sure it is ad1980 */
+ if (ac97_read(codec, AC97_VENDOR_ID1) != 0x4144)
+ goto reset_err;
+
+ vendor_id2 = ac97_read(codec, AC97_VENDOR_ID2);
+
+ if (vendor_id2 != 0x5370) {
+ if (vendor_id2 != 0x5374)
+ goto reset_err;
+ else
+ printk(KERN_WARNING "ad1980: "
+ "Found AD1981 - only 2/2 IN/OUT Channels "
+ "supported\n");
+ }
+
+ ac97_write(codec, AC97_MASTER, 0x0000); /* unmute line out volume */
+ ac97_write(codec, AC97_PCM, 0x0000); /* unmute PCM out volume */
+ ac97_write(codec, AC97_REC_GAIN, 0x0000);/* unmute record volume */
+
+ ad1980_add_controls(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "ad1980: failed to register card\n");
+ goto reset_err;
+ }
+
+ return 0;
+
+reset_err:
+ snd_soc_free_pcms(socdev);
+
+pcm_err:
+ snd_soc_free_ac97_codec(codec);
+
+codec_err:
+ kfree(codec->reg_cache);
+
+cache_err:
+ kfree(socdev->codec);
+ socdev->codec = NULL;
+ return ret;
+}
+
+static int ad1980_soc_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec == NULL)
+ return 0;
+
+ snd_soc_dapm_free(socdev);
+ snd_soc_free_pcms(socdev);
+ snd_soc_free_ac97_codec(codec);
+ kfree(codec->reg_cache);
+ kfree(codec);
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_ad1980 = {
+ .probe = ad1980_soc_probe,
+ .remove = ad1980_soc_remove,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_ad1980);
+
+MODULE_DESCRIPTION("ASoC ad1980 driver");
+MODULE_AUTHOR("Roy Huang, Cliff Cai");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ad1980.h b/sound/soc/codecs/ad1980.h
new file mode 100644
index 0000000..db6c850
--- /dev/null
+++ b/sound/soc/codecs/ad1980.h
@@ -0,0 +1,23 @@
+/*
+ * ad1980.h -- ad1980 Soc Audio driver
+ */
+
+#ifndef _AD1980_H
+#define _AD1980_H
+/* Bit definition of Power-Down Control/Status Register */
+#define ADC 0x0001
+#define DAC 0x0002
+#define ANL 0x0004
+#define REF 0x0008
+#define PR0 0x0100
+#define PR1 0x0200
+#define PR2 0x0400
+#define PR3 0x0800
+#define PR4 0x1000
+#define PR5 0x2000
+#define PR6 0x4000
+
+extern struct snd_soc_dai ad1980_dai;
+extern struct snd_soc_codec_device soc_codec_dev_ad1980;
+
+#endif
diff --git a/sound/soc/codecs/ad73311.c b/sound/soc/codecs/ad73311.c
new file mode 100644
index 0000000..37af860
--- /dev/null
+++ b/sound/soc/codecs/ad73311.c
@@ -0,0 +1,107 @@
+/*
+ * ad73311.c -- ALSA Soc AD73311 codec support
+ *
+ * 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.
+ *
+ * Revision history
+ * 25th Sep 2008 Initial version.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "ad73311.h"
+
+struct snd_soc_dai ad73311_dai = {
+ .name = "AD73311",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, },
+};
+EXPORT_SYMBOL_GPL(ad73311_dai);
+
+static int ad73311_soc_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+ mutex_init(&codec->mutex);
+ codec->name = "AD73311";
+ codec->owner = THIS_MODULE;
+ codec->dai = &ad73311_dai;
+ codec->num_dai = 1;
+ socdev->codec = codec;
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "ad73311: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "ad73311: failed to register card\n");
+ goto register_err;
+ }
+
+ return ret;
+
+register_err:
+ snd_soc_free_pcms(socdev);
+pcm_err:
+ kfree(socdev->codec);
+ socdev->codec = NULL;
+ return ret;
+}
+
+static int ad73311_soc_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec == NULL)
+ return 0;
+ snd_soc_free_pcms(socdev);
+ kfree(codec);
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_ad73311 = {
+ .probe = ad73311_soc_probe,
+ .remove = ad73311_soc_remove,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_ad73311);
+
+MODULE_DESCRIPTION("ASoC ad73311 driver");
+MODULE_AUTHOR("Cliff Cai ");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ad73311.h b/sound/soc/codecs/ad73311.h
new file mode 100644
index 0000000..507ce0c
--- /dev/null
+++ b/sound/soc/codecs/ad73311.h
@@ -0,0 +1,90 @@
+/*
+ * File: sound/soc/codec/ad73311.h
+ * Based on:
+ * Author: Cliff Cai <cliff.cai@analog.com>
+ *
+ * Created: Thur Sep 25, 2008
+ * Description: definitions for AD73311 registers
+ *
+ *
+ * Modified:
+ * Copyright 2006 Analog Devices Inc.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __AD73311_H__
+#define __AD73311_H__
+
+#define AD_CONTROL 0x8000
+#define AD_DATA 0x0000
+#define AD_READ 0x4000
+#define AD_WRITE 0x0000
+
+/* Control register A */
+#define CTRL_REG_A (0 << 8)
+
+#define REGA_MODE_PRO 0x00
+#define REGA_MODE_DATA 0x01
+#define REGA_MODE_MIXED 0x03
+#define REGA_DLB 0x04
+#define REGA_SLB 0x08
+#define REGA_DEVC(x) ((x & 0x7) << 4)
+#define REGA_RESET 0x80
+
+/* Control register B */
+#define CTRL_REG_B (1 << 8)
+
+#define REGB_DIRATE(x) (x & 0x3)
+#define REGB_SCDIV(x) ((x & 0x3) << 2)
+#define REGB_MCDIV(x) ((x & 0x7) << 4)
+#define REGB_CEE (1 << 7)
+
+/* Control register C */
+#define CTRL_REG_C (2 << 8)
+
+#define REGC_PUDEV (1 << 0)
+#define REGC_PUADC (1 << 3)
+#define REGC_PUDAC (1 << 4)
+#define REGC_PUREF (1 << 5)
+#define REGC_REFUSE (1 << 6)
+
+/* Control register D */
+#define CTRL_REG_D (3 << 8)
+
+#define REGD_IGS(x) (x & 0x7)
+#define REGD_RMOD (1 << 3)
+#define REGD_OGS(x) ((x & 0x7) << 4)
+#define REGD_MUTE (x << 7)
+
+/* Control register E */
+#define CTRL_REG_E (4 << 8)
+
+#define REGE_DA(x) (x & 0x1f)
+#define REGE_IBYP (1 << 5)
+
+/* Control register F */
+#define CTRL_REG_F (5 << 8)
+
+#define REGF_SEEN (1 << 5)
+#define REGF_INV (1 << 6)
+#define REGF_ALB (1 << 7)
+
+extern struct snd_soc_dai ad73311_dai;
+extern struct snd_soc_codec_device soc_codec_dev_ad73311;
+#endif
diff --git a/sound/soc/codecs/ak4535.c b/sound/soc/codecs/ak4535.c
new file mode 100644
index 0000000..2a89b58
--- /dev/null
+++ b/sound/soc/codecs/ak4535.c
@@ -0,0 +1,694 @@
+/*
+ * ak4535.c -- AK4535 ALSA Soc Audio driver
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on wm8753.c by Liam Girdwood
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.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 "ak4535.h"
+
+#define AK4535_VERSION "0.3"
+
+struct snd_soc_codec_device soc_codec_dev_ak4535;
+
+/* codec private data */
+struct ak4535_priv {
+ unsigned int sysclk;
+};
+
+/*
+ * ak4535 register cache
+ */
+static const u16 ak4535_reg[AK4535_CACHEREGNUM] = {
+ 0x0000, 0x0080, 0x0000, 0x0003,
+ 0x0002, 0x0000, 0x0011, 0x0001,
+ 0x0000, 0x0040, 0x0036, 0x0010,
+ 0x0000, 0x0000, 0x0057, 0x0000,
+};
+
+/*
+ * read ak4535 register cache
+ */
+static inline unsigned int ak4535_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= AK4535_CACHEREGNUM)
+ return -1;
+ return cache[reg];
+}
+
+static inline unsigned int ak4535_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u8 data;
+ data = reg;
+
+ if (codec->hw_write(codec->control_data, &data, 1) != 1)
+ return -EIO;
+
+ if (codec->hw_read(codec->control_data, &data, 1) != 1)
+ return -EIO;
+
+ return data;
+};
+
+/*
+ * write ak4535 register cache
+ */
+static inline void ak4535_write_reg_cache(struct snd_soc_codec *codec,
+ u16 reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= AK4535_CACHEREGNUM)
+ return;
+ cache[reg] = value;
+}
+
+/*
+ * write to the AK4535 register space
+ */
+static int ak4535_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ /* data is
+ * D15..D8 AK4535 register offset
+ * D7...D0 register data
+ */
+ data[0] = reg & 0xff;
+ data[1] = value & 0xff;
+
+ ak4535_write_reg_cache(codec, reg, value);
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+static int ak4535_sync(struct snd_soc_codec *codec)
+{
+ u16 *cache = codec->reg_cache;
+ int i, r = 0;
+
+ for (i = 0; i < AK4535_CACHEREGNUM; i++)
+ r |= ak4535_write(codec, i, cache[i]);
+
+ return r;
+};
+
+static const char *ak4535_mono_gain[] = {"+6dB", "-17dB"};
+static const char *ak4535_mono_out[] = {"(L + R)/2", "Hi-Z"};
+static const char *ak4535_hp_out[] = {"Stereo", "Mono"};
+static const char *ak4535_deemp[] = {"44.1kHz", "Off", "48kHz", "32kHz"};
+static const char *ak4535_mic_select[] = {"Internal", "External"};
+
+static const struct soc_enum ak4535_enum[] = {
+ SOC_ENUM_SINGLE(AK4535_SIG1, 7, 2, ak4535_mono_gain),
+ SOC_ENUM_SINGLE(AK4535_SIG1, 6, 2, ak4535_mono_out),
+ SOC_ENUM_SINGLE(AK4535_MODE2, 2, 2, ak4535_hp_out),
+ SOC_ENUM_SINGLE(AK4535_DAC, 0, 4, ak4535_deemp),
+ SOC_ENUM_SINGLE(AK4535_MIC, 1, 2, ak4535_mic_select),
+};
+
+static const struct snd_kcontrol_new ak4535_snd_controls[] = {
+ SOC_SINGLE("ALC2 Switch", AK4535_SIG1, 1, 1, 0),
+ SOC_ENUM("Mono 1 Output", ak4535_enum[1]),
+ SOC_ENUM("Mono 1 Gain", ak4535_enum[0]),
+ SOC_ENUM("Headphone Output", ak4535_enum[2]),
+ SOC_ENUM("Playback Deemphasis", ak4535_enum[3]),
+ SOC_SINGLE("Bass Volume", AK4535_DAC, 2, 3, 0),
+ SOC_SINGLE("Mic Boost (+20dB) Switch", AK4535_MIC, 0, 1, 0),
+ SOC_ENUM("Mic Select", ak4535_enum[4]),
+ SOC_SINGLE("ALC Operation Time", AK4535_TIMER, 0, 3, 0),
+ SOC_SINGLE("ALC Recovery Time", AK4535_TIMER, 2, 3, 0),
+ SOC_SINGLE("ALC ZC Time", AK4535_TIMER, 4, 3, 0),
+ SOC_SINGLE("ALC 1 Switch", AK4535_ALC1, 5, 1, 0),
+ SOC_SINGLE("ALC 2 Switch", AK4535_ALC1, 6, 1, 0),
+ SOC_SINGLE("ALC Volume", AK4535_ALC2, 0, 127, 0),
+ SOC_SINGLE("Capture Volume", AK4535_PGA, 0, 127, 0),
+ SOC_SINGLE("Left Playback Volume", AK4535_LATT, 0, 127, 1),
+ SOC_SINGLE("Right Playback Volume", AK4535_RATT, 0, 127, 1),
+ SOC_SINGLE("AUX Bypass Volume", AK4535_VOL, 0, 15, 0),
+ SOC_SINGLE("Mic Sidetone Volume", AK4535_VOL, 4, 7, 0),
+};
+
+/* add non dapm controls */
+static int ak4535_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(ak4535_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&ak4535_snd_controls[i], codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/* Mono 1 Mixer */
+static const struct snd_kcontrol_new ak4535_mono1_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG1, 4, 1, 0),
+ SOC_DAPM_SINGLE("Mono Playback Switch", AK4535_SIG1, 5, 1, 0),
+};
+
+/* Stereo Mixer */
+static const struct snd_kcontrol_new ak4535_stereo_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG2, 4, 1, 0),
+ SOC_DAPM_SINGLE("Playback Switch", AK4535_SIG2, 7, 1, 0),
+ SOC_DAPM_SINGLE("Aux Bypass Switch", AK4535_SIG2, 5, 1, 0),
+};
+
+/* Input Mixer */
+static const struct snd_kcontrol_new ak4535_input_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Mic Capture Switch", AK4535_MIC, 2, 1, 0),
+ SOC_DAPM_SINGLE("Aux Capture Switch", AK4535_MIC, 5, 1, 0),
+};
+
+/* Input mux */
+static const struct snd_kcontrol_new ak4535_input_mux_control =
+ SOC_DAPM_ENUM("Input Select", ak4535_enum[4]);
+
+/* HP L switch */
+static const struct snd_kcontrol_new ak4535_hpl_control =
+ SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 1, 1, 1);
+
+/* HP R switch */
+static const struct snd_kcontrol_new ak4535_hpr_control =
+ SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 0, 1, 1);
+
+/* mono 2 switch */
+static const struct snd_kcontrol_new ak4535_mono2_control =
+ SOC_DAPM_SINGLE("Switch", AK4535_SIG1, 0, 1, 0);
+
+/* Line out switch */
+static const struct snd_kcontrol_new ak4535_line_control =
+ SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 6, 1, 0);
+
+/* ak4535 dapm widgets */
+static const struct snd_soc_dapm_widget ak4535_dapm_widgets[] = {
+ SND_SOC_DAPM_MIXER("Stereo Mixer", SND_SOC_NOPM, 0, 0,
+ &ak4535_stereo_mixer_controls[0],
+ ARRAY_SIZE(ak4535_stereo_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Mono1 Mixer", SND_SOC_NOPM, 0, 0,
+ &ak4535_mono1_mixer_controls[0],
+ ARRAY_SIZE(ak4535_mono1_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0,
+ &ak4535_input_mixer_controls[0],
+ ARRAY_SIZE(ak4535_input_mixer_controls)),
+ SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0,
+ &ak4535_input_mux_control),
+ SND_SOC_DAPM_DAC("DAC", "Playback", AK4535_PM2, 0, 0),
+ SND_SOC_DAPM_SWITCH("Mono 2 Enable", SND_SOC_NOPM, 0, 0,
+ &ak4535_mono2_control),
+ /* speaker powersave bit */
+ SND_SOC_DAPM_PGA("Speaker Enable", AK4535_MODE2, 0, 0, NULL, 0),
+ SND_SOC_DAPM_SWITCH("Line Out Enable", SND_SOC_NOPM, 0, 0,
+ &ak4535_line_control),
+ SND_SOC_DAPM_SWITCH("Left HP Enable", SND_SOC_NOPM, 0, 0,
+ &ak4535_hpl_control),
+ SND_SOC_DAPM_SWITCH("Right HP Enable", SND_SOC_NOPM, 0, 0,
+ &ak4535_hpr_control),
+ SND_SOC_DAPM_OUTPUT("LOUT"),
+ SND_SOC_DAPM_OUTPUT("HPL"),
+ SND_SOC_DAPM_OUTPUT("ROUT"),
+ SND_SOC_DAPM_OUTPUT("HPR"),
+ SND_SOC_DAPM_OUTPUT("SPP"),
+ SND_SOC_DAPM_OUTPUT("SPN"),
+ SND_SOC_DAPM_OUTPUT("MOUT1"),
+ SND_SOC_DAPM_OUTPUT("MOUT2"),
+ SND_SOC_DAPM_OUTPUT("MICOUT"),
+ SND_SOC_DAPM_ADC("ADC", "Capture", AK4535_PM1, 0, 0),
+ SND_SOC_DAPM_PGA("Spk Amp", AK4535_PM2, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("HP R Amp", AK4535_PM2, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("HP L Amp", AK4535_PM2, 2, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Mic", AK4535_PM1, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Line Out", AK4535_PM1, 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Mono Out", AK4535_PM1, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("AUX In", AK4535_PM1, 2, 0, NULL, 0),
+
+ SND_SOC_DAPM_MICBIAS("Mic Int Bias", AK4535_MIC, 3, 0),
+ SND_SOC_DAPM_MICBIAS("Mic Ext Bias", AK4535_MIC, 4, 0),
+ SND_SOC_DAPM_INPUT("MICIN"),
+ SND_SOC_DAPM_INPUT("MICEXT"),
+ SND_SOC_DAPM_INPUT("AUX"),
+ SND_SOC_DAPM_INPUT("MIN"),
+ SND_SOC_DAPM_INPUT("AIN"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /*stereo mixer */
+ {"Stereo Mixer", "Playback Switch", "DAC"},
+ {"Stereo Mixer", "Mic Sidetone Switch", "Mic"},
+ {"Stereo Mixer", "Aux Bypass Switch", "AUX In"},
+
+ /* mono1 mixer */
+ {"Mono1 Mixer", "Mic Sidetone Switch", "Mic"},
+ {"Mono1 Mixer", "Mono Playback Switch", "DAC"},
+
+ /* Mic */
+ {"Mic", NULL, "AIN"},
+ {"Input Mux", "Internal", "Mic Int Bias"},
+ {"Input Mux", "External", "Mic Ext Bias"},
+ {"Mic Int Bias", NULL, "MICIN"},
+ {"Mic Ext Bias", NULL, "MICEXT"},
+ {"MICOUT", NULL, "Input Mux"},
+
+ /* line out */
+ {"LOUT", NULL, "Line Out Enable"},
+ {"ROUT", NULL, "Line Out Enable"},
+ {"Line Out Enable", "Switch", "Line Out"},
+ {"Line Out", NULL, "Stereo Mixer"},
+
+ /* mono1 out */
+ {"MOUT1", NULL, "Mono Out"},
+ {"Mono Out", NULL, "Mono1 Mixer"},
+
+ /* left HP */
+ {"HPL", NULL, "Left HP Enable"},
+ {"Left HP Enable", "Switch", "HP L Amp"},
+ {"HP L Amp", NULL, "Stereo Mixer"},
+
+ /* right HP */
+ {"HPR", NULL, "Right HP Enable"},
+ {"Right HP Enable", "Switch", "HP R Amp"},
+ {"HP R Amp", NULL, "Stereo Mixer"},
+
+ /* speaker */
+ {"SPP", NULL, "Speaker Enable"},
+ {"SPN", NULL, "Speaker Enable"},
+ {"Speaker Enable", "Switch", "Spk Amp"},
+ {"Spk Amp", NULL, "MIN"},
+
+ /* mono 2 */
+ {"MOUT2", NULL, "Mono 2 Enable"},
+ {"Mono 2 Enable", "Switch", "Stereo Mixer"},
+
+ /* Aux In */
+ {"Aux In", NULL, "AUX"},
+
+ /* ADC */
+ {"ADC", NULL, "Input Mixer"},
+ {"Input Mixer", "Mic Capture Switch", "Mic"},
+ {"Input Mixer", "Aux Capture Switch", "Aux In"},
+};
+
+static int ak4535_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, ak4535_dapm_widgets,
+ ARRAY_SIZE(ak4535_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+static int ak4535_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 ak4535_priv *ak4535 = codec->private_data;
+
+ ak4535->sysclk = freq;
+ return 0;
+}
+
+static int ak4535_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct ak4535_priv *ak4535 = codec->private_data;
+ u8 mode2 = ak4535_read_reg_cache(codec, AK4535_MODE2) & ~(0x3 << 5);
+ int rate = params_rate(params), fs = 256;
+
+ if (rate)
+ fs = ak4535->sysclk / rate;
+
+ /* set fs */
+ switch (fs) {
+ case 1024:
+ mode2 |= (0x2 << 5);
+ break;
+ case 512:
+ mode2 |= (0x1 << 5);
+ break;
+ case 256:
+ break;
+ }
+
+ /* set rate */
+ ak4535_write(codec, AK4535_MODE2, mode2);
+ return 0;
+}
+
+static int ak4535_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u8 mode1 = 0;
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ mode1 = 0x0002;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ mode1 = 0x0001;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* use 32 fs for BCLK to save power */
+ mode1 |= 0x4;
+
+ ak4535_write(codec, AK4535_MODE1, mode1);
+ return 0;
+}
+
+static int ak4535_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = ak4535_read_reg_cache(codec, AK4535_DAC) & 0xffdf;
+ if (!mute)
+ ak4535_write(codec, AK4535_DAC, mute_reg);
+ else
+ ak4535_write(codec, AK4535_DAC, mute_reg | 0x20);
+ return 0;
+}
+
+static int ak4535_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 i;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ ak4535_mute(codec->dai, 0);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ ak4535_mute(codec->dai, 1);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ i = ak4535_read_reg_cache(codec, AK4535_PM1);
+ ak4535_write(codec, AK4535_PM1, i | 0x80);
+ i = ak4535_read_reg_cache(codec, AK4535_PM2);
+ ak4535_write(codec, AK4535_PM2, i & (~0x80));
+ break;
+ case SND_SOC_BIAS_OFF:
+ i = ak4535_read_reg_cache(codec, AK4535_PM1);
+ ak4535_write(codec, AK4535_PM1, i & (~0x80));
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define AK4535_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+struct snd_soc_dai ak4535_dai = {
+ .name = "AK4535",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AK4535_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AK4535_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .hw_params = ak4535_hw_params,
+ },
+ .dai_ops = {
+ .set_fmt = ak4535_set_dai_fmt,
+ .digital_mute = ak4535_mute,
+ .set_sysclk = ak4535_set_dai_sysclk,
+ },
+};
+EXPORT_SYMBOL_GPL(ak4535_dai);
+
+static int ak4535_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ ak4535_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int ak4535_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ ak4535_sync(codec);
+ ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ ak4535_set_bias_level(codec, codec->suspend_bias_level);
+ return 0;
+}
+
+/*
+ * initialise the AK4535 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int ak4535_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret = 0;
+
+ codec->name = "AK4535";
+ codec->owner = THIS_MODULE;
+ codec->read = ak4535_read_reg_cache;
+ codec->write = ak4535_write;
+ codec->set_bias_level = ak4535_set_bias_level;
+ codec->dai = &ak4535_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = ARRAY_SIZE(ak4535_reg);
+ codec->reg_cache = kmemdup(ak4535_reg, sizeof(ak4535_reg), GFP_KERNEL);
+
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "ak4535: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* power on device */
+ ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ ak4535_add_controls(codec);
+ ak4535_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "ak4535: failed to register card\n");
+ goto card_err;
+ }
+
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+
+ return ret;
+}
+
+static struct snd_soc_device *ak4535_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+
+static int ak4535_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = ak4535_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = ak4535_init(socdev);
+ if (ret < 0)
+ printk(KERN_ERR "failed to initialise AK4535\n");
+
+ return ret;
+}
+
+static int ak4535_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+static const struct i2c_device_id ak4535_i2c_id[] = {
+ { "ak4535", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ak4535_i2c_id);
+
+static struct i2c_driver ak4535_i2c_driver = {
+ .driver = {
+ .name = "AK4535 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = ak4535_i2c_probe,
+ .remove = ak4535_i2c_remove,
+ .id_table = ak4535_i2c_id,
+};
+
+static int ak4535_add_i2c_device(struct platform_device *pdev,
+ const struct ak4535_setup_data *setup)
+{
+ struct i2c_board_info info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+ int ret;
+
+ ret = i2c_add_driver(&ak4535_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ return ret;
+ }
+
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ info.addr = setup->i2c_address;
+ strlcpy(info.type, "ak4535", I2C_NAME_SIZE);
+
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "can't get i2c adapter %d\n",
+ setup->i2c_bus);
+ goto err_driver;
+ }
+
+ client = i2c_new_device(adapter, &info);
+ i2c_put_adapter(adapter);
+ if (!client) {
+ dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
+ (unsigned int)info.addr);
+ goto err_driver;
+ }
+
+ return 0;
+
+err_driver:
+ i2c_del_driver(&ak4535_i2c_driver);
+ return -ENODEV;
+}
+#endif
+
+static int ak4535_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct ak4535_setup_data *setup;
+ struct snd_soc_codec *codec;
+ struct ak4535_priv *ak4535;
+ int ret;
+
+ printk(KERN_INFO "AK4535 Audio Codec %s", AK4535_VERSION);
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ ak4535 = kzalloc(sizeof(struct ak4535_priv), GFP_KERNEL);
+ if (ak4535 == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->private_data = ak4535;
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ ak4535_socdev = socdev;
+ ret = -ENODEV;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ codec->hw_read = (hw_read_t)i2c_master_recv;
+ ret = ak4535_add_i2c_device(pdev, setup);
+ }
+#endif
+
+ if (ret != 0) {
+ kfree(codec->private_data);
+ kfree(codec);
+ }
+ return ret;
+}
+
+/* power down chip */
+static int ak4535_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec->control_data)
+ ak4535_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_unregister_device(codec->control_data);
+ i2c_del_driver(&ak4535_i2c_driver);
+#endif
+ kfree(codec->private_data);
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_ak4535 = {
+ .probe = ak4535_probe,
+ .remove = ak4535_remove,
+ .suspend = ak4535_suspend,
+ .resume = ak4535_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_ak4535);
+
+MODULE_DESCRIPTION("Soc AK4535 driver");
+MODULE_AUTHOR("Richard Purdie");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ak4535.h b/sound/soc/codecs/ak4535.h
new file mode 100644
index 0000000..c7a5870
--- /dev/null
+++ b/sound/soc/codecs/ak4535.h
@@ -0,0 +1,47 @@
+/*
+ * ak4535.h -- AK4535 Soc Audio driver
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on wm8753.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _AK4535_H
+#define _AK4535_H
+
+/* AK4535 register space */
+
+#define AK4535_PM1 0x0
+#define AK4535_PM2 0x1
+#define AK4535_SIG1 0x2
+#define AK4535_SIG2 0x3
+#define AK4535_MODE1 0x4
+#define AK4535_MODE2 0x5
+#define AK4535_DAC 0x6
+#define AK4535_MIC 0x7
+#define AK4535_TIMER 0x8
+#define AK4535_ALC1 0x9
+#define AK4535_ALC2 0xa
+#define AK4535_PGA 0xb
+#define AK4535_LATT 0xc
+#define AK4535_RATT 0xd
+#define AK4535_VOL 0xe
+#define AK4535_STATUS 0xf
+
+#define AK4535_CACHEREGNUM 0x10
+
+struct ak4535_setup_data {
+ int i2c_bus;
+ unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai ak4535_dai;
+extern struct snd_soc_codec_device soc_codec_dev_ak4535;
+
+#endif
diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c
new file mode 100644
index 0000000..0bbd945
--- /dev/null
+++ b/sound/soc/codecs/cs4270.c
@@ -0,0 +1,765 @@
+/*
+ * CS4270 ALSA SoC (ASoC) codec driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007 Freescale Semiconductor, Inc. This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ *
+ * This is an ASoC device driver for the Cirrus Logic CS4270 codec.
+ *
+ * Current features/limitations:
+ *
+ * 1) Software mode is supported. Stand-alone mode is automatically
+ * selected if I2C is disabled or if a CS4270 is not found on the I2C
+ * bus. However, stand-alone mode is only partially implemented because
+ * there is no mechanism yet for this driver and the machine driver to
+ * communicate the values of the M0, M1, MCLK1, and MCLK2 pins.
+ * 2) Only I2C is supported, not SPI
+ * 3) Only Master mode is supported, not Slave.
+ * 4) The machine driver's 'startup' function must call
+ * cs4270_set_dai_sysclk() with the value of MCLK.
+ * 5) Only I2S and left-justified modes are supported
+ * 6) Power management is not supported
+ * 7) The only supported control is volume and hardware mute (if enabled)
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <linux/i2c.h>
+
+#include "cs4270.h"
+
+/* If I2C is defined, then we support software mode. However, if we're
+ not compiled as module but I2C is, then we can't use I2C calls. */
+#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
+#define USE_I2C
+#endif
+
+/* Private data for the CS4270 */
+struct cs4270_private {
+ unsigned int mclk; /* Input frequency of the MCLK pin */
+ unsigned int mode; /* The mode (I2S or left-justified) */
+};
+
+/*
+ * The codec isn't really big-endian or little-endian, since the I2S
+ * interface requires data to be sent serially with the MSbit first.
+ * However, to support BE and LE I2S devices, we specify both here. That
+ * way, ALSA will always match the bit patterns.
+ */
+#define CS4270_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
+ 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)
+
+#ifdef USE_I2C
+
+/* CS4270 registers addresses */
+#define CS4270_CHIPID 0x01 /* Chip ID */
+#define CS4270_PWRCTL 0x02 /* Power Control */
+#define CS4270_MODE 0x03 /* Mode Control */
+#define CS4270_FORMAT 0x04 /* Serial Format, ADC/DAC Control */
+#define CS4270_TRANS 0x05 /* Transition Control */
+#define CS4270_MUTE 0x06 /* Mute Control */
+#define CS4270_VOLA 0x07 /* DAC Channel A Volume Control */
+#define CS4270_VOLB 0x08 /* DAC Channel B Volume Control */
+
+#define CS4270_FIRSTREG 0x01
+#define CS4270_LASTREG 0x08
+#define CS4270_NUMREGS (CS4270_LASTREG - CS4270_FIRSTREG + 1)
+
+/* Bit masks for the CS4270 registers */
+#define CS4270_CHIPID_ID 0xF0
+#define CS4270_CHIPID_REV 0x0F
+#define CS4270_PWRCTL_FREEZE 0x80
+#define CS4270_PWRCTL_PDN_ADC 0x20
+#define CS4270_PWRCTL_PDN_DAC 0x02
+#define CS4270_PWRCTL_PDN 0x01
+#define CS4270_MODE_SPEED_MASK 0x30
+#define CS4270_MODE_1X 0x00
+#define CS4270_MODE_2X 0x10
+#define CS4270_MODE_4X 0x20
+#define CS4270_MODE_SLAVE 0x30
+#define CS4270_MODE_DIV_MASK 0x0E
+#define CS4270_MODE_DIV1 0x00
+#define CS4270_MODE_DIV15 0x02
+#define CS4270_MODE_DIV2 0x04
+#define CS4270_MODE_DIV3 0x06
+#define CS4270_MODE_DIV4 0x08
+#define CS4270_MODE_POPGUARD 0x01
+#define CS4270_FORMAT_FREEZE_A 0x80
+#define CS4270_FORMAT_FREEZE_B 0x40
+#define CS4270_FORMAT_LOOPBACK 0x20
+#define CS4270_FORMAT_DAC_MASK 0x18
+#define CS4270_FORMAT_DAC_LJ 0x00
+#define CS4270_FORMAT_DAC_I2S 0x08
+#define CS4270_FORMAT_DAC_RJ16 0x18
+#define CS4270_FORMAT_DAC_RJ24 0x10
+#define CS4270_FORMAT_ADC_MASK 0x01
+#define CS4270_FORMAT_ADC_LJ 0x00
+#define CS4270_FORMAT_ADC_I2S 0x01
+#define CS4270_TRANS_ONE_VOL 0x80
+#define CS4270_TRANS_SOFT 0x40
+#define CS4270_TRANS_ZERO 0x20
+#define CS4270_TRANS_INV_ADC_A 0x08
+#define CS4270_TRANS_INV_ADC_B 0x10
+#define CS4270_TRANS_INV_DAC_A 0x02
+#define CS4270_TRANS_INV_DAC_B 0x04
+#define CS4270_TRANS_DEEMPH 0x01
+#define CS4270_MUTE_AUTO 0x20
+#define CS4270_MUTE_ADC_A 0x08
+#define CS4270_MUTE_ADC_B 0x10
+#define CS4270_MUTE_POLARITY 0x04
+#define CS4270_MUTE_DAC_A 0x01
+#define CS4270_MUTE_DAC_B 0x02
+
+/*
+ * Clock Ratio Selection for Master Mode with I2C enabled
+ *
+ * The data for this chart is taken from Table 5 of the CS4270 reference
+ * manual.
+ *
+ * This table is used to determine how to program the Mode Control register.
+ * It is also used by cs4270_set_dai_sysclk() to tell ALSA which sampling
+ * rates the CS4270 currently supports.
+ *
+ * Each element in this array corresponds to the ratios in mclk_ratios[].
+ * These two arrays need to be in sync.
+ *
+ * 'speed_mode' is the corresponding bit pattern to be written to the
+ * MODE bits of the Mode Control Register
+ *
+ * 'mclk' is the corresponding bit pattern to be wirten to the MCLK bits of
+ * the Mode Control Register.
+ *
+ * In situations where a single ratio is represented by multiple speed
+ * modes, we favor the slowest speed. E.g, for a ratio of 128, we pick
+ * double-speed instead of quad-speed. However, the CS4270 errata states
+ * that Divide-By-1.5 can cause failures, so we avoid that mode where
+ * possible.
+ *
+ * ERRATA: There is an errata for the CS4270 where divide-by-1.5 does not
+ * work if VD = 3.3V. If this effects you, select the
+ * CONFIG_SND_SOC_CS4270_VD33_ERRATA Kconfig option, and the driver will
+ * never select any sample rates that require divide-by-1.5.
+ */
+static struct {
+ unsigned int ratio;
+ u8 speed_mode;
+ u8 mclk;
+} cs4270_mode_ratios[] = {
+ {64, CS4270_MODE_4X, CS4270_MODE_DIV1},
+#ifndef CONFIG_SND_SOC_CS4270_VD33_ERRATA
+ {96, CS4270_MODE_4X, CS4270_MODE_DIV15},
+#endif
+ {128, CS4270_MODE_2X, CS4270_MODE_DIV1},
+ {192, CS4270_MODE_4X, CS4270_MODE_DIV3},
+ {256, CS4270_MODE_1X, CS4270_MODE_DIV1},
+ {384, CS4270_MODE_2X, CS4270_MODE_DIV3},
+ {512, CS4270_MODE_1X, CS4270_MODE_DIV2},
+ {768, CS4270_MODE_1X, CS4270_MODE_DIV3},
+ {1024, CS4270_MODE_1X, CS4270_MODE_DIV4}
+};
+
+/* The number of MCLK/LRCK ratios supported by the CS4270 */
+#define NUM_MCLK_RATIOS ARRAY_SIZE(cs4270_mode_ratios)
+
+/*
+ * Determine the CS4270 samples rates.
+ *
+ * 'freq' is the input frequency to MCLK. The other parameters are ignored.
+ *
+ * The value of MCLK is used to determine which sample rates are supported
+ * by the CS4270. The ratio of MCLK / Fs must be equal to one of nine
+ * support values: 64, 96, 128, 192, 256, 384, 512, 768, and 1024.
+ *
+ * This function calculates the nine ratios and determines which ones match
+ * a standard sample rate. If there's a match, then it is added to the list
+ * of support sample rates.
+ *
+ * This function must be called by the machine driver's 'startup' function,
+ * otherwise the list of supported sample rates will not be available in
+ * time for ALSA.
+ *
+ * Note that in stand-alone mode, the sample rate is determined by input
+ * pins M0, M1, MDIV1, and MDIV2. Also in stand-alone mode, divide-by-3
+ * is not a programmable option. However, divide-by-3 is not an available
+ * option in stand-alone mode. This cases two problems: a ratio of 768 is
+ * not available (it requires divide-by-3) and B) ratios 192 and 384 can
+ * only be selected with divide-by-1.5, but there is an errate that make
+ * this selection difficult.
+ *
+ * In addition, there is no mechanism for communicating with the machine
+ * driver what the input settings can be. This would need to be implemented
+ * for stand-alone mode to work.
+ */
+static int cs4270_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs4270_private *cs4270 = codec->private_data;
+ unsigned int rates = 0;
+ unsigned int rate_min = -1;
+ unsigned int rate_max = 0;
+ unsigned int i;
+
+ cs4270->mclk = freq;
+
+ for (i = 0; i < NUM_MCLK_RATIOS; i++) {
+ unsigned int rate = freq / cs4270_mode_ratios[i].ratio;
+ rates |= snd_pcm_rate_to_rate_bit(rate);
+ if (rate < rate_min)
+ rate_min = rate;
+ if (rate > rate_max)
+ rate_max = rate;
+ }
+ /* FIXME: soc should support a rate list */
+ rates &= ~SNDRV_PCM_RATE_KNOT;
+
+ if (!rates) {
+ printk(KERN_ERR "cs4270: could not find a valid sample rate\n");
+ return -EINVAL;
+ }
+
+ codec_dai->playback.rates = rates;
+ codec_dai->playback.rate_min = rate_min;
+ codec_dai->playback.rate_max = rate_max;
+
+ codec_dai->capture.rates = rates;
+ codec_dai->capture.rate_min = rate_min;
+ codec_dai->capture.rate_max = rate_max;
+
+ return 0;
+}
+
+/*
+ * Configure the codec for the selected audio format
+ *
+ * This function takes a bitmask of SND_SOC_DAIFMT_x bits and programs the
+ * codec accordingly.
+ *
+ * Currently, this function only supports SND_SOC_DAIFMT_I2S and
+ * SND_SOC_DAIFMT_LEFT_J. The CS4270 codec also supports right-justified
+ * data for playback only, but ASoC currently does not support different
+ * formats for playback vs. record.
+ */
+static int cs4270_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int format)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs4270_private *cs4270 = codec->private_data;
+ int ret = 0;
+
+ switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_LEFT_J:
+ cs4270->mode = format & SND_SOC_DAIFMT_FORMAT_MASK;
+ break;
+ default:
+ printk(KERN_ERR "cs4270: invalid DAI format\n");
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+/*
+ * A list of addresses on which this CS4270 could use. I2C addresses are
+ * 7 bits. For the CS4270, the upper four bits are always 1001, and the
+ * lower three bits are determined via the AD2, AD1, and AD0 pins
+ * (respectively).
+ */
+static const unsigned short normal_i2c[] = {
+ 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, I2C_CLIENT_END
+};
+I2C_CLIENT_INSMOD;
+
+/*
+ * Pre-fill the CS4270 register cache.
+ *
+ * We use the auto-increment feature of the CS4270 to read all registers in
+ * one shot.
+ */
+static int cs4270_fill_cache(struct snd_soc_codec *codec)
+{
+ u8 *cache = codec->reg_cache;
+ struct i2c_client *i2c_client = codec->control_data;
+ s32 length;
+
+ length = i2c_smbus_read_i2c_block_data(i2c_client,
+ CS4270_FIRSTREG | 0x80, CS4270_NUMREGS, cache);
+
+ if (length != CS4270_NUMREGS) {
+ printk(KERN_ERR "cs4270: I2C read failure, addr=0x%x\n",
+ i2c_client->addr);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/*
+ * Read from the CS4270 register cache.
+ *
+ * This CS4270 registers are cached to avoid excessive I2C I/O operations.
+ * After the initial read to pre-fill the cache, the CS4270 never updates
+ * the register values, so we won't have a cache coherncy problem.
+ */
+static unsigned int cs4270_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u8 *cache = codec->reg_cache;
+
+ if ((reg < CS4270_FIRSTREG) || (reg > CS4270_LASTREG))
+ return -EIO;
+
+ return cache[reg - CS4270_FIRSTREG];
+}
+
+/*
+ * Write to a CS4270 register via the I2C bus.
+ *
+ * This function writes the given value to the given CS4270 register, and
+ * also updates the register cache.
+ *
+ * Note that we don't use the hw_write function pointer of snd_soc_codec.
+ * That's because it's too clunky: the hw_write_t prototype does not match
+ * i2c_smbus_write_byte_data(), and it's just another layer of overhead.
+ */
+static int cs4270_i2c_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 *cache = codec->reg_cache;
+
+ if ((reg < CS4270_FIRSTREG) || (reg > CS4270_LASTREG))
+ return -EIO;
+
+ /* Only perform an I2C operation if the new value is different */
+ if (cache[reg - CS4270_FIRSTREG] != value) {
+ struct i2c_client *client = codec->control_data;
+ if (i2c_smbus_write_byte_data(client, reg, value)) {
+ printk(KERN_ERR "cs4270: I2C write failed\n");
+ return -EIO;
+ }
+
+ /* We've written to the hardware, so update the cache */
+ cache[reg - CS4270_FIRSTREG] = value;
+ }
+
+ return 0;
+}
+
+/*
+ * Program the CS4270 with the given hardware parameters.
+ *
+ * The .dai_ops functions are used to provide board-specific data, like
+ * input frequencies, to this driver. This function takes that information,
+ * combines it with the hardware parameters provided, and programs the
+ * hardware accordingly.
+ */
+static int cs4270_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct cs4270_private *cs4270 = codec->private_data;
+ int ret;
+ unsigned int i;
+ unsigned int rate;
+ unsigned int ratio;
+ int reg;
+
+ /* Figure out which MCLK/LRCK ratio to use */
+
+ rate = params_rate(params); /* Sampling rate, in Hz */
+ ratio = cs4270->mclk / rate; /* MCLK/LRCK ratio */
+
+ for (i = 0; i < NUM_MCLK_RATIOS; i++) {
+ if (cs4270_mode_ratios[i].ratio == ratio)
+ break;
+ }
+
+ if (i == NUM_MCLK_RATIOS) {
+ /* We did not find a matching ratio */
+ printk(KERN_ERR "cs4270: could not find matching ratio\n");
+ return -EINVAL;
+ }
+
+ /* Freeze and power-down the codec */
+
+ ret = snd_soc_write(codec, CS4270_PWRCTL, CS4270_PWRCTL_FREEZE |
+ CS4270_PWRCTL_PDN_ADC | CS4270_PWRCTL_PDN_DAC |
+ CS4270_PWRCTL_PDN);
+ if (ret < 0) {
+ printk(KERN_ERR "cs4270: I2C write failed\n");
+ return ret;
+ }
+
+ /* Program the mode control register */
+
+ reg = snd_soc_read(codec, CS4270_MODE);
+ reg &= ~(CS4270_MODE_SPEED_MASK | CS4270_MODE_DIV_MASK);
+ reg |= cs4270_mode_ratios[i].speed_mode | cs4270_mode_ratios[i].mclk;
+
+ ret = snd_soc_write(codec, CS4270_MODE, reg);
+ if (ret < 0) {
+ printk(KERN_ERR "cs4270: I2C write failed\n");
+ return ret;
+ }
+
+ /* Program the format register */
+
+ reg = snd_soc_read(codec, CS4270_FORMAT);
+ reg &= ~(CS4270_FORMAT_DAC_MASK | CS4270_FORMAT_ADC_MASK);
+
+ switch (cs4270->mode) {
+ case SND_SOC_DAIFMT_I2S:
+ reg |= CS4270_FORMAT_DAC_I2S | CS4270_FORMAT_ADC_I2S;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ reg |= CS4270_FORMAT_DAC_LJ | CS4270_FORMAT_ADC_LJ;
+ break;
+ default:
+ printk(KERN_ERR "cs4270: unknown format\n");
+ return -EINVAL;
+ }
+
+ ret = snd_soc_write(codec, CS4270_FORMAT, reg);
+ if (ret < 0) {
+ printk(KERN_ERR "cs4270: I2C write failed\n");
+ return ret;
+ }
+
+ /* Disable auto-mute. This feature appears to be buggy, because in
+ some situations, auto-mute will not deactivate when it should. */
+
+ reg = snd_soc_read(codec, CS4270_MUTE);
+ reg &= ~CS4270_MUTE_AUTO;
+ ret = snd_soc_write(codec, CS4270_MUTE, reg);
+ if (ret < 0) {
+ printk(KERN_ERR "cs4270: I2C write failed\n");
+ return ret;
+ }
+
+ /* Thaw and power-up the codec */
+
+ ret = snd_soc_write(codec, CS4270_PWRCTL, 0);
+ if (ret < 0) {
+ printk(KERN_ERR "cs4270: I2C write failed\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+#ifdef CONFIG_SND_SOC_CS4270_HWMUTE
+
+/*
+ * Set the CS4270 external mute
+ *
+ * This function toggles the mute bits in the MUTE register. The CS4270's
+ * mute capability is intended for external muting circuitry, so if the
+ * board does not have the MUTEA or MUTEB pins connected to such circuitry,
+ * then this function will do nothing.
+ */
+static int cs4270_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ int reg6;
+
+ reg6 = snd_soc_read(codec, CS4270_MUTE);
+
+ if (mute)
+ reg6 |= CS4270_MUTE_ADC_A | CS4270_MUTE_ADC_B |
+ CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B;
+ else
+ reg6 &= ~(CS4270_MUTE_ADC_A | CS4270_MUTE_ADC_B |
+ CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B);
+
+ return snd_soc_write(codec, CS4270_MUTE, reg6);
+}
+
+#endif
+
+static int cs4270_i2c_probe(struct i2c_client *, const struct i2c_device_id *);
+
+/* A list of non-DAPM controls that the CS4270 supports */
+static const struct snd_kcontrol_new cs4270_snd_controls[] = {
+ SOC_DOUBLE_R("Master Playback Volume",
+ CS4270_VOLA, CS4270_VOLB, 0, 0xFF, 1)
+};
+
+static const struct i2c_device_id cs4270_id[] = {
+ {"cs4270", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, cs4270_id);
+
+static struct i2c_driver cs4270_i2c_driver = {
+ .driver = {
+ .name = "CS4270 I2C",
+ .owner = THIS_MODULE,
+ },
+ .id_table = cs4270_id,
+ .probe = cs4270_i2c_probe,
+};
+
+/*
+ * Global variable to store socdev for i2c probe function.
+ *
+ * If struct i2c_driver had a private_data field, we wouldn't need to use
+ * cs4270_socdec. This is the only way to pass the socdev structure to
+ * cs4270_i2c_probe().
+ *
+ * The real solution to cs4270_socdev is to create a mechanism
+ * that maps I2C addresses to snd_soc_device structures. Perhaps the
+ * creation of the snd_soc_device object should be moved out of
+ * cs4270_probe() and into cs4270_i2c_probe(), but that would make this
+ * driver dependent on I2C. The CS4270 supports "stand-alone" mode, whereby
+ * the chip is *not* connected to the I2C bus, but is instead configured via
+ * input pins.
+ */
+static struct snd_soc_device *cs4270_socdev;
+
+/*
+ * Initialize the I2C interface of the CS4270
+ *
+ * This function is called for whenever the I2C subsystem finds a device
+ * at a particular address.
+ *
+ * Note: snd_soc_new_pcms() must be called before this function can be called,
+ * because of snd_ctl_add().
+ */
+static int cs4270_i2c_probe(struct i2c_client *i2c_client,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = cs4270_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int i;
+ int ret = 0;
+
+ /* Probing all possible addresses has one drawback: if there are
+ multiple CS4270s on the bus, then you cannot specify which
+ socdev is matched with which CS4270. For now, we just reject
+ this I2C device if the socdev already has one attached. */
+ if (codec->control_data)
+ return -ENODEV;
+
+ /* Note: codec_dai->codec is NULL here */
+
+ codec->reg_cache = kzalloc(CS4270_NUMREGS, GFP_KERNEL);
+ if (!codec->reg_cache) {
+ printk(KERN_ERR "cs4270: could not allocate register cache\n");
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ /* Verify that we have a CS4270 */
+
+ ret = i2c_smbus_read_byte_data(i2c_client, CS4270_CHIPID);
+ if (ret < 0) {
+ printk(KERN_ERR "cs4270: failed to read I2C\n");
+ goto error;
+ }
+ /* The top four bits of the chip ID should be 1100. */
+ if ((ret & 0xF0) != 0xC0) {
+ /* The device at this address is not a CS4270 codec */
+ ret = -ENODEV;
+ goto error;
+ }
+
+ printk(KERN_INFO "cs4270: found device at I2C address %X\n",
+ i2c_client->addr);
+ printk(KERN_INFO "cs4270: hardware revision %X\n", ret & 0xF);
+
+ codec->control_data = i2c_client;
+ codec->read = cs4270_read_reg_cache;
+ codec->write = cs4270_i2c_write;
+ codec->reg_cache_size = CS4270_NUMREGS;
+
+ /* The I2C interface is set up, so pre-fill our register cache */
+
+ ret = cs4270_fill_cache(codec);
+ if (ret < 0) {
+ printk(KERN_ERR "cs4270: failed to fill register cache\n");
+ goto error;
+ }
+
+ /* Add the non-DAPM controls */
+
+ for (i = 0; i < ARRAY_SIZE(cs4270_snd_controls); i++) {
+ struct snd_kcontrol *kctrl =
+ snd_soc_cnew(&cs4270_snd_controls[i], codec, NULL);
+
+ ret = snd_ctl_add(codec->card, kctrl);
+ if (ret < 0)
+ goto error;
+ }
+
+ i2c_set_clientdata(i2c_client, codec);
+
+ return 0;
+
+error:
+ codec->control_data = NULL;
+
+ kfree(codec->reg_cache);
+ codec->reg_cache = NULL;
+ codec->reg_cache_size = 0;
+
+ return ret;
+}
+
+#endif /* USE_I2C*/
+
+struct snd_soc_dai cs4270_dai = {
+ .name = "CS4270",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = 0,
+ .formats = CS4270_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = 0,
+ .formats = CS4270_FORMATS,
+ },
+};
+EXPORT_SYMBOL_GPL(cs4270_dai);
+
+/*
+ * ASoC probe function
+ *
+ * This function is called when the machine driver calls
+ * platform_device_add().
+ */
+static int cs4270_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ printk(KERN_INFO "CS4270 ALSA SoC Codec\n");
+
+ /* Allocate enough space for the snd_soc_codec structure
+ and our private data together. */
+ codec = kzalloc(ALIGN(sizeof(struct snd_soc_codec), 4) +
+ sizeof(struct cs4270_private), GFP_KERNEL);
+ if (!codec) {
+ printk(KERN_ERR "cs4270: Could not allocate codec structure\n");
+ return -ENOMEM;
+ }
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ codec->name = "CS4270";
+ codec->owner = THIS_MODULE;
+ codec->dai = &cs4270_dai;
+ codec->num_dai = 1;
+ codec->private_data = (void *) codec +
+ ALIGN(sizeof(struct snd_soc_codec), 4);
+
+ socdev->codec = codec;
+
+ /* Register PCMs */
+
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "cs4270: failed to create PCMs\n");
+ goto error_free_codec;
+ }
+
+#ifdef USE_I2C
+ cs4270_socdev = socdev;
+
+ ret = i2c_add_driver(&cs4270_i2c_driver);
+ if (ret) {
+ printk(KERN_ERR "cs4270: failed to attach driver");
+ goto error_free_pcms;
+ }
+
+ /* Did we find a CS4270 on the I2C bus? */
+ if (codec->control_data) {
+ /* Initialize codec ops */
+ cs4270_dai.ops.hw_params = cs4270_hw_params;
+ cs4270_dai.dai_ops.set_sysclk = cs4270_set_dai_sysclk;
+ cs4270_dai.dai_ops.set_fmt = cs4270_set_dai_fmt;
+#ifdef CONFIG_SND_SOC_CS4270_HWMUTE
+ cs4270_dai.dai_ops.digital_mute = cs4270_mute;
+#endif
+ } else
+ printk(KERN_INFO "cs4270: no I2C device found, "
+ "using stand-alone mode\n");
+#else
+ printk(KERN_INFO "cs4270: I2C disabled, using stand-alone mode\n");
+#endif
+
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "cs4270: failed to register card\n");
+ goto error_del_driver;
+ }
+
+ return 0;
+
+error_del_driver:
+#ifdef USE_I2C
+ i2c_del_driver(&cs4270_i2c_driver);
+
+error_free_pcms:
+#endif
+ snd_soc_free_pcms(socdev);
+
+error_free_codec:
+ kfree(socdev->codec);
+ socdev->codec = NULL;
+
+ return ret;
+}
+
+static int cs4270_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+ snd_soc_free_pcms(socdev);
+
+#ifdef USE_I2C
+ i2c_del_driver(&cs4270_i2c_driver);
+#endif
+
+ kfree(socdev->codec);
+ socdev->codec = NULL;
+
+ return 0;
+}
+
+/*
+ * ASoC codec device structure
+ *
+ * Assign this variable to the codec_dev field of the machine driver's
+ * snd_soc_device structure.
+ */
+struct snd_soc_codec_device soc_codec_device_cs4270 = {
+ .probe = cs4270_probe,
+ .remove = cs4270_remove
+};
+EXPORT_SYMBOL_GPL(soc_codec_device_cs4270);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Cirrus Logic CS4270 ALSA SoC Codec Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/cs4270.h b/sound/soc/codecs/cs4270.h
new file mode 100644
index 0000000..adc6cd9
--- /dev/null
+++ b/sound/soc/codecs/cs4270.h
@@ -0,0 +1,28 @@
+/*
+ * Cirrus Logic CS4270 ALSA SoC Codec Driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007 Freescale Semiconductor, Inc. This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ */
+
+#ifndef _CS4270_H
+#define _CS4270_H
+
+/*
+ * The ASoC codec DAI structure for the CS4270. Assign this structure to
+ * the .codec_dai field of your machine driver's snd_soc_dai_link structure.
+ */
+extern struct snd_soc_dai cs4270_dai;
+
+/*
+ * The ASoC codec device structure for the CS4270. Assign this structure
+ * to the .codec_dev field of your machine driver's snd_soc_device
+ * structure.
+ */
+extern struct snd_soc_codec_device soc_codec_device_cs4270;
+
+#endif
diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c
new file mode 100644
index 0000000..44ef0da
--- /dev/null
+++ b/sound/soc/codecs/ssm2602.c
@@ -0,0 +1,775 @@
+/*
+ * File: sound/soc/codecs/ssm2602.c
+ * Author: Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created: Tue June 06 2008
+ * Description: Driver for ssm2602 sound chip
+ *
+ * Modified:
+ * Copyright 2008 Analog Devices Inc.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#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 <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 "ssm2602.h"
+
+#define SSM2602_VERSION "0.1"
+
+struct snd_soc_codec_device soc_codec_dev_ssm2602;
+
+/* codec private data */
+struct ssm2602_priv {
+ unsigned int sysclk;
+ struct snd_pcm_substream *master_substream;
+ struct snd_pcm_substream *slave_substream;
+};
+
+/*
+ * ssm2602 register cache
+ * We can't read the ssm2602 register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ * There is no point in caching the reset register
+ */
+static const u16 ssm2602_reg[SSM2602_CACHEREGNUM] = {
+ 0x0017, 0x0017, 0x0079, 0x0079,
+ 0x0000, 0x0000, 0x0000, 0x000a,
+ 0x0000, 0x0000
+};
+
+/*
+ * read ssm2602 register cache
+ */
+static inline unsigned int ssm2602_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg == SSM2602_RESET)
+ return 0;
+ if (reg >= SSM2602_CACHEREGNUM)
+ return -1;
+ return cache[reg];
+}
+
+/*
+ * write ssm2602 register cache
+ */
+static inline void ssm2602_write_reg_cache(struct snd_soc_codec *codec,
+ u16 reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= SSM2602_CACHEREGNUM)
+ return;
+ cache[reg] = value;
+}
+
+/*
+ * write to the ssm2602 register space
+ */
+static int ssm2602_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ /* data is
+ * D15..D9 ssm2602 register offset
+ * D8...D0 register data
+ */
+ data[0] = (reg << 1) | ((value >> 8) & 0x0001);
+ data[1] = value & 0x00ff;
+
+ ssm2602_write_reg_cache(codec, reg, value);
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+#define ssm2602_reset(c) ssm2602_write(c, SSM2602_RESET, 0)
+
+/*Appending several "None"s just for OSS mixer use*/
+static const char *ssm2602_input_select[] = {
+ "Line", "Mic", "None", "None", "None",
+ "None", "None", "None",
+};
+
+static const char *ssm2602_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+
+static const struct soc_enum ssm2602_enum[] = {
+ SOC_ENUM_SINGLE(SSM2602_APANA, 2, 2, ssm2602_input_select),
+ SOC_ENUM_SINGLE(SSM2602_APDIGI, 1, 4, ssm2602_deemph),
+};
+
+static const struct snd_kcontrol_new ssm2602_snd_controls[] = {
+
+SOC_DOUBLE_R("Master Playback Volume", SSM2602_LOUT1V, SSM2602_ROUT1V,
+ 0, 127, 0),
+SOC_DOUBLE_R("Master Playback ZC Switch", SSM2602_LOUT1V, SSM2602_ROUT1V,
+ 7, 1, 0),
+
+SOC_DOUBLE_R("Capture Volume", SSM2602_LINVOL, SSM2602_RINVOL, 0, 31, 0),
+SOC_DOUBLE_R("Capture Switch", SSM2602_LINVOL, SSM2602_RINVOL, 7, 1, 1),
+
+SOC_SINGLE("Mic Boost (+20dB)", SSM2602_APANA, 0, 1, 0),
+SOC_SINGLE("Mic Switch", SSM2602_APANA, 1, 1, 1),
+
+SOC_SINGLE("Sidetone Playback Volume", SSM2602_APANA, 6, 3, 1),
+
+SOC_SINGLE("ADC High Pass Filter Switch", SSM2602_APDIGI, 0, 1, 1),
+SOC_SINGLE("Store DC Offset Switch", SSM2602_APDIGI, 4, 1, 0),
+
+SOC_ENUM("Capture Source", ssm2602_enum[0]),
+
+SOC_ENUM("Playback De-emphasis", ssm2602_enum[1]),
+};
+
+/* add non dapm controls */
+static int ssm2602_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(ssm2602_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&ssm2602_snd_controls[i], codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/* Output Mixer */
+static const struct snd_kcontrol_new ssm2602_output_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", SSM2602_APANA, 3, 1, 0),
+SOC_DAPM_SINGLE("Mic Sidetone Switch", SSM2602_APANA, 5, 1, 0),
+SOC_DAPM_SINGLE("HiFi Playback Switch", SSM2602_APANA, 4, 1, 0),
+};
+
+/* Input mux */
+static const struct snd_kcontrol_new ssm2602_input_mux_controls =
+SOC_DAPM_ENUM("Input Select", ssm2602_enum[0]);
+
+static const struct snd_soc_dapm_widget ssm2602_dapm_widgets[] = {
+SND_SOC_DAPM_MIXER("Output Mixer", SSM2602_PWR, 4, 1,
+ &ssm2602_output_mixer_controls[0],
+ ARRAY_SIZE(ssm2602_output_mixer_controls)),
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SSM2602_PWR, 3, 1),
+SND_SOC_DAPM_OUTPUT("LOUT"),
+SND_SOC_DAPM_OUTPUT("LHPOUT"),
+SND_SOC_DAPM_OUTPUT("ROUT"),
+SND_SOC_DAPM_OUTPUT("RHPOUT"),
+SND_SOC_DAPM_ADC("ADC", "HiFi Capture", SSM2602_PWR, 2, 1),
+SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, &ssm2602_input_mux_controls),
+SND_SOC_DAPM_PGA("Line Input", SSM2602_PWR, 0, 1, NULL, 0),
+SND_SOC_DAPM_MICBIAS("Mic Bias", SSM2602_PWR, 1, 1),
+SND_SOC_DAPM_INPUT("MICIN"),
+SND_SOC_DAPM_INPUT("RLINEIN"),
+SND_SOC_DAPM_INPUT("LLINEIN"),
+};
+
+static const struct snd_soc_dapm_route audio_conn[] = {
+ /* output mixer */
+ {"Output Mixer", "Line Bypass Switch", "Line Input"},
+ {"Output Mixer", "HiFi Playback Switch", "DAC"},
+ {"Output Mixer", "Mic Sidetone Switch", "Mic Bias"},
+
+ /* outputs */
+ {"RHPOUT", NULL, "Output Mixer"},
+ {"ROUT", NULL, "Output Mixer"},
+ {"LHPOUT", NULL, "Output Mixer"},
+ {"LOUT", NULL, "Output Mixer"},
+
+ /* input mux */
+ {"Input Mux", "Line", "Line Input"},
+ {"Input Mux", "Mic", "Mic Bias"},
+ {"ADC", NULL, "Input Mux"},
+
+ /* inputs */
+ {"Line Input", NULL, "LLINEIN"},
+ {"Line Input", NULL, "RLINEIN"},
+ {"Mic Bias", NULL, "MICIN"},
+};
+
+static int ssm2602_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, ssm2602_dapm_widgets,
+ ARRAY_SIZE(ssm2602_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_conn, ARRAY_SIZE(audio_conn));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+struct _coeff_div {
+ u32 mclk;
+ u32 rate;
+ u16 fs;
+ u8 sr:4;
+ u8 bosr:1;
+ u8 usb:1;
+};
+
+/* codec mclk clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+ /* 48k */
+ {12288000, 48000, 256, 0x0, 0x0, 0x0},
+ {18432000, 48000, 384, 0x0, 0x1, 0x0},
+ {12000000, 48000, 250, 0x0, 0x0, 0x1},
+
+ /* 32k */
+ {12288000, 32000, 384, 0x6, 0x0, 0x0},
+ {18432000, 32000, 576, 0x6, 0x1, 0x0},
+ {12000000, 32000, 375, 0x6, 0x0, 0x1},
+
+ /* 8k */
+ {12288000, 8000, 1536, 0x3, 0x0, 0x0},
+ {18432000, 8000, 2304, 0x3, 0x1, 0x0},
+ {11289600, 8000, 1408, 0xb, 0x0, 0x0},
+ {16934400, 8000, 2112, 0xb, 0x1, 0x0},
+ {12000000, 8000, 1500, 0x3, 0x0, 0x1},
+
+ /* 96k */
+ {12288000, 96000, 128, 0x7, 0x0, 0x0},
+ {18432000, 96000, 192, 0x7, 0x1, 0x0},
+ {12000000, 96000, 125, 0x7, 0x0, 0x1},
+
+ /* 44.1k */
+ {11289600, 44100, 256, 0x8, 0x0, 0x0},
+ {16934400, 44100, 384, 0x8, 0x1, 0x0},
+ {12000000, 44100, 272, 0x8, 0x1, 0x1},
+
+ /* 88.2k */
+ {11289600, 88200, 128, 0xf, 0x0, 0x0},
+ {16934400, 88200, 192, 0xf, 0x1, 0x0},
+ {12000000, 88200, 136, 0xf, 0x1, 0x1},
+};
+
+static inline int get_coeff(int mclk, int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+ if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+ return i;
+ }
+ return i;
+}
+
+static int ssm2602_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ u16 srate;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct ssm2602_priv *ssm2602 = codec->private_data;
+ u16 iface = ssm2602_read_reg_cache(codec, SSM2602_IFACE) & 0xfff3;
+ int i = get_coeff(ssm2602->sysclk, params_rate(params));
+
+ /*no match is found*/
+ if (i == ARRAY_SIZE(coeff_div))
+ return -EINVAL;
+
+ srate = (coeff_div[i].sr << 2) |
+ (coeff_div[i].bosr << 1) | coeff_div[i].usb;
+
+ ssm2602_write(codec, SSM2602_ACTIVE, 0);
+ ssm2602_write(codec, SSM2602_SRATE, srate);
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x0008;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= 0x000c;
+ break;
+ }
+ ssm2602_write(codec, SSM2602_IFACE, iface);
+ ssm2602_write(codec, SSM2602_ACTIVE, ACTIVE_ACTIVATE_CODEC);
+ return 0;
+}
+
+static int ssm2602_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct ssm2602_priv *ssm2602 = codec->private_data;
+ struct snd_pcm_runtime *master_runtime;
+
+ /* The DAI has shared clocks so if we already have a playback or
+ * capture going then constrain this substream to match it.
+ */
+ if (ssm2602->master_substream) {
+ master_runtime = ssm2602->master_substream->runtime;
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ master_runtime->rate,
+ master_runtime->rate);
+
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ master_runtime->sample_bits,
+ master_runtime->sample_bits);
+
+ ssm2602->slave_substream = substream;
+ } else
+ ssm2602->master_substream = substream;
+
+ return 0;
+}
+
+static int ssm2602_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ /* set active */
+ ssm2602_write(codec, SSM2602_ACTIVE, ACTIVE_ACTIVATE_CODEC);
+
+ return 0;
+}
+
+static void ssm2602_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ /* deactivate */
+ if (!codec->active)
+ ssm2602_write(codec, SSM2602_ACTIVE, 0);
+}
+
+static int ssm2602_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = ssm2602_read_reg_cache(codec, SSM2602_APDIGI) & ~APDIGI_ENABLE_DAC_MUTE;
+ if (mute)
+ ssm2602_write(codec, SSM2602_APDIGI,
+ mute_reg | APDIGI_ENABLE_DAC_MUTE);
+ else
+ ssm2602_write(codec, SSM2602_APDIGI, mute_reg);
+ return 0;
+}
+
+static int ssm2602_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 ssm2602_priv *ssm2602 = codec->private_data;
+ switch (freq) {
+ case 11289600:
+ case 12000000:
+ case 12288000:
+ case 16934400:
+ case 18432000:
+ ssm2602->sysclk = freq;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int ssm2602_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface |= 0x0040;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set iface */
+ ssm2602_write(codec, SSM2602_IFACE, iface);
+ return 0;
+}
+
+static int ssm2602_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg = ssm2602_read_reg_cache(codec, SSM2602_PWR) & 0xff7f;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* vref/mid, osc on, dac unmute */
+ ssm2602_write(codec, SSM2602_PWR, reg);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* everything off except vref/vmid, */
+ ssm2602_write(codec, SSM2602_PWR, reg | PWR_CLK_OUT_PDN);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* everything off, dac mute, inactive */
+ ssm2602_write(codec, SSM2602_ACTIVE, 0);
+ ssm2602_write(codec, SSM2602_PWR, 0xffff);
+ break;
+
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define SSM2602_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\
+ SNDRV_PCM_RATE_96000)
+
+struct snd_soc_dai ssm2602_dai = {
+ .name = "SSM2602",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SSM2602_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SSM2602_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,},
+ .ops = {
+ .startup = ssm2602_startup,
+ .prepare = ssm2602_pcm_prepare,
+ .hw_params = ssm2602_hw_params,
+ .shutdown = ssm2602_shutdown,
+ },
+ .dai_ops = {
+ .digital_mute = ssm2602_mute,
+ .set_sysclk = ssm2602_set_dai_sysclk,
+ .set_fmt = ssm2602_set_dai_fmt,
+ }
+};
+EXPORT_SYMBOL_GPL(ssm2602_dai);
+
+static int ssm2602_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ ssm2602_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int ssm2602_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(ssm2602_reg); i++) {
+ data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+ ssm2602_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ ssm2602_set_bias_level(codec, codec->suspend_bias_level);
+ return 0;
+}
+
+/*
+ * initialise the ssm2602 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int ssm2602_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int reg, ret = 0;
+
+ codec->name = "SSM2602";
+ codec->owner = THIS_MODULE;
+ codec->read = ssm2602_read_reg_cache;
+ codec->write = ssm2602_write;
+ codec->set_bias_level = ssm2602_set_bias_level;
+ codec->dai = &ssm2602_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = sizeof(ssm2602_reg);
+ codec->reg_cache = kmemdup(ssm2602_reg, sizeof(ssm2602_reg),
+ GFP_KERNEL);
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ ssm2602_reset(codec);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ pr_err("ssm2602: failed to create pcms\n");
+ goto pcm_err;
+ }
+ /*power on device*/
+ ssm2602_write(codec, SSM2602_ACTIVE, 0);
+ /* set the update bits */
+ reg = ssm2602_read_reg_cache(codec, SSM2602_LINVOL);
+ ssm2602_write(codec, SSM2602_LINVOL, reg | LINVOL_LRIN_BOTH);
+ reg = ssm2602_read_reg_cache(codec, SSM2602_RINVOL);
+ ssm2602_write(codec, SSM2602_RINVOL, reg | RINVOL_RLIN_BOTH);
+ reg = ssm2602_read_reg_cache(codec, SSM2602_LOUT1V);
+ ssm2602_write(codec, SSM2602_LOUT1V, reg | LOUT1V_LRHP_BOTH);
+ reg = ssm2602_read_reg_cache(codec, SSM2602_ROUT1V);
+ ssm2602_write(codec, SSM2602_ROUT1V, reg | ROUT1V_RLHP_BOTH);
+ /*select Line in as default input*/
+ ssm2602_write(codec, SSM2602_APANA,
+ APANA_ENABLE_MIC_BOOST2 | APANA_SELECT_DAC |
+ APANA_ENABLE_MIC_BOOST);
+ ssm2602_write(codec, SSM2602_PWR, 0);
+
+ ssm2602_add_controls(codec);
+ ssm2602_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ pr_err("ssm2602: failed to register card\n");
+ goto card_err;
+ }
+
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+ return ret;
+}
+
+static struct snd_soc_device *ssm2602_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+/*
+ * ssm2602 2 wire address is determined by GPIO5
+ * state during powerup.
+ * low = 0x1a
+ * high = 0x1b
+ */
+static int ssm2602_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = ssm2602_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = ssm2602_init(socdev);
+ if (ret < 0)
+ pr_err("failed to initialise SSM2602\n");
+
+ return ret;
+}
+
+static int ssm2602_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+static const struct i2c_device_id ssm2602_i2c_id[] = {
+ { "ssm2602", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ssm2602_i2c_id);
+/* corgi i2c codec control layer */
+static struct i2c_driver ssm2602_i2c_driver = {
+ .driver = {
+ .name = "SSM2602 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = ssm2602_i2c_probe,
+ .remove = ssm2602_i2c_remove,
+ .id_table = ssm2602_i2c_id,
+};
+
+static int ssm2602_add_i2c_device(struct platform_device *pdev,
+ const struct ssm2602_setup_data *setup)
+{
+ struct i2c_board_info info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+ int ret;
+
+ ret = i2c_add_driver(&ssm2602_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ return ret;
+ }
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ info.addr = setup->i2c_address;
+ strlcpy(info.type, "ssm2602", I2C_NAME_SIZE);
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "can't get i2c adapter %d\n",
+ setup->i2c_bus);
+ goto err_driver;
+ }
+ client = i2c_new_device(adapter, &info);
+ i2c_put_adapter(adapter);
+ if (!client) {
+ dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
+ (unsigned int)info.addr);
+ goto err_driver;
+ }
+ return 0;
+err_driver:
+ i2c_del_driver(&ssm2602_i2c_driver);
+ return -ENODEV;
+}
+#endif
+
+static int ssm2602_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct ssm2602_setup_data *setup;
+ struct snd_soc_codec *codec;
+ struct ssm2602_priv *ssm2602;
+ int ret = 0;
+
+ pr_info("ssm2602 Audio Codec %s", SSM2602_VERSION);
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ ssm2602 = kzalloc(sizeof(struct ssm2602_priv), GFP_KERNEL);
+ if (ssm2602 == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->private_data = ssm2602;
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ ssm2602_socdev = socdev;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = ssm2602_add_i2c_device(pdev, setup);
+ }
+#else
+ /* other interfaces */
+#endif
+ return ret;
+}
+
+/* remove everything here */
+static int ssm2602_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec->control_data)
+ ssm2602_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_unregister_device(codec->control_data);
+ i2c_del_driver(&ssm2602_i2c_driver);
+#endif
+ kfree(codec->private_data);
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_ssm2602 = {
+ .probe = ssm2602_probe,
+ .remove = ssm2602_remove,
+ .suspend = ssm2602_suspend,
+ .resume = ssm2602_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_ssm2602);
+
+MODULE_DESCRIPTION("ASoC ssm2602 driver");
+MODULE_AUTHOR("Cliff Cai");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ssm2602.h b/sound/soc/codecs/ssm2602.h
new file mode 100644
index 0000000..f344e6d
--- /dev/null
+++ b/sound/soc/codecs/ssm2602.h
@@ -0,0 +1,130 @@
+/*
+ * File: sound/soc/codecs/ssm2602.h
+ * Author: Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created: Tue June 06 2008
+ *
+ * Modified:
+ * Copyright 2008 Analog Devices Inc.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _SSM2602_H
+#define _SSM2602_H
+
+/* SSM2602 Codec Register definitions */
+
+#define SSM2602_LINVOL 0x00
+#define SSM2602_RINVOL 0x01
+#define SSM2602_LOUT1V 0x02
+#define SSM2602_ROUT1V 0x03
+#define SSM2602_APANA 0x04
+#define SSM2602_APDIGI 0x05
+#define SSM2602_PWR 0x06
+#define SSM2602_IFACE 0x07
+#define SSM2602_SRATE 0x08
+#define SSM2602_ACTIVE 0x09
+#define SSM2602_RESET 0x0f
+
+/*SSM2602 Codec Register Field definitions
+ *(Mask value to extract the corresponding Register field)
+ */
+
+/*Left ADC Volume Control (SSM2602_REG_LEFT_ADC_VOL)*/
+#define LINVOL_LIN_VOL 0x01F /* Left Channel PGA Volume control */
+#define LINVOL_LIN_ENABLE_MUTE 0x080 /* Left Channel Input Mute */
+#define LINVOL_LRIN_BOTH 0x100 /* Left Channel Line Input Volume update */
+
+/*Right ADC Volume Control (SSM2602_REG_RIGHT_ADC_VOL)*/
+#define RINVOL_RIN_VOL 0x01F /* Right Channel PGA Volume control */
+#define RINVOL_RIN_ENABLE_MUTE 0x080 /* Right Channel Input Mute */
+#define RINVOL_RLIN_BOTH 0x100 /* Right Channel Line Input Volume update */
+
+/*Left DAC Volume Control (SSM2602_REG_LEFT_DAC_VOL)*/
+#define LOUT1V_LHP_VOL 0x07F /* Left Channel Headphone volume control */
+#define LOUT1V_ENABLE_LZC 0x080 /* Left Channel Zero cross detect enable */
+#define LOUT1V_LRHP_BOTH 0x100 /* Left Channel Headphone volume update */
+
+/*Right DAC Volume Control (SSM2602_REG_RIGHT_DAC_VOL)*/
+#define ROUT1V_RHP_VOL 0x07F /* Right Channel Headphone volume control */
+#define ROUT1V_ENABLE_RZC 0x080 /* Right Channel Zero cross detect enable */
+#define ROUT1V_RLHP_BOTH 0x100 /* Right Channel Headphone volume update */
+
+/*Analogue Audio Path Control (SSM2602_REG_ANALOGUE_PATH)*/
+#define APANA_ENABLE_MIC_BOOST 0x001 /* Primary Microphone Amplifier gain booster control */
+#define APANA_ENABLE_MIC_MUTE 0x002 /* Microphone Mute Control */
+#define APANA_ADC_IN_SELECT 0x004 /* Microphone/Line IN select to ADC (1=MIC, 0=Line In) */
+#define APANA_ENABLE_BYPASS 0x008 /* Line input bypass to line output */
+#define APANA_SELECT_DAC 0x010 /* Select DAC (1=Select DAC, 0=Don't Select DAC) */
+#define APANA_ENABLE_SIDETONE 0x020 /* Enable/Disable Side Tone */
+#define APANA_SIDETONE_ATTN 0x0C0 /* Side Tone Attenuation */
+#define APANA_ENABLE_MIC_BOOST2 0x100 /* Secondary Microphone Amplifier gain booster control */
+
+/*Digital Audio Path Control (SSM2602_REG_DIGITAL_PATH)*/
+#define APDIGI_ENABLE_ADC_HPF 0x001 /* Enable/Disable ADC Highpass Filter */
+#define APDIGI_DE_EMPHASIS 0x006 /* De-Emphasis Control */
+#define APDIGI_ENABLE_DAC_MUTE 0x008 /* DAC Mute Control */
+#define APDIGI_STORE_OFFSET 0x010 /* Store/Clear DC offset when HPF is disabled */
+
+/*Power Down Control (SSM2602_REG_POWER)
+ *(1=Enable PowerDown, 0=Disable PowerDown)
+ */
+#define PWR_LINE_IN_PDN 0x001 /* Line Input Power Down */
+#define PWR_MIC_PDN 0x002 /* Microphone Input & Bias Power Down */
+#define PWR_ADC_PDN 0x004 /* ADC Power Down */
+#define PWR_DAC_PDN 0x008 /* DAC Power Down */
+#define PWR_OUT_PDN 0x010 /* Outputs Power Down */
+#define PWR_OSC_PDN 0x020 /* Oscillator Power Down */
+#define PWR_CLK_OUT_PDN 0x040 /* CLKOUT Power Down */
+#define PWR_POWER_OFF 0x080 /* POWEROFF Mode */
+
+/*Digital Audio Interface Format (SSM2602_REG_DIGITAL_IFACE)*/
+#define IFACE_IFACE_FORMAT 0x003 /* Digital Audio input format control */
+#define IFACE_AUDIO_DATA_LEN 0x00C /* Audio Data word length control */
+#define IFACE_DAC_LR_POLARITY 0x010 /* Polarity Control for clocks in RJ,LJ and I2S modes */
+#define IFACE_DAC_LR_SWAP 0x020 /* Swap DAC data control */
+#define IFACE_ENABLE_MASTER 0x040 /* Enable/Disable Master Mode */
+#define IFACE_BCLK_INVERT 0x080 /* Bit Clock Inversion control */
+
+/*Sampling Control (SSM2602_REG_SAMPLING_CTRL)*/
+#define SRATE_ENABLE_USB_MODE 0x001 /* Enable/Disable USB Mode */
+#define SRATE_BOS_RATE 0x002 /* Base Over-Sampling rate */
+#define SRATE_SAMPLE_RATE 0x03C /* Clock setting condition (Sampling rate control) */
+#define SRATE_CORECLK_DIV2 0x040 /* Core Clock divider select */
+#define SRATE_CLKOUT_DIV2 0x080 /* Clock Out divider select */
+
+/*Active Control (SSM2602_REG_ACTIVE_CTRL)*/
+#define ACTIVE_ACTIVATE_CODEC 0x001 /* Activate Codec Digital Audio Interface */
+
+/*********************************************************************/
+
+#define SSM2602_CACHEREGNUM 10
+
+#define SSM2602_SYSCLK 0
+#define SSM2602_DAI 0
+
+struct ssm2602_setup_data {
+ int i2c_bus;
+ unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai ssm2602_dai;
+extern struct snd_soc_codec_device soc_codec_dev_ssm2602;
+
+#endif
diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c
new file mode 100644
index 0000000..44308da
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic23.c
@@ -0,0 +1,714 @@
+/*
+ * ALSA SoC TLV320AIC23 codec driver
+ *
+ * Author: Arun KS, <arunks@mistralsolutions.com>
+ * Copyright: (C) 2008 Mistral Solutions Pvt Ltd.,
+ *
+ * Based on sound/soc/codecs/wm8731.c by Richard Purdie
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Notes:
+ * The AIC23 is a driver for a low power stereo audio
+ * codec tlv320aic23
+ *
+ * The machine layer should disable unsupported inputs/outputs by
+ * snd_soc_dapm_disable_pin(codec, "LHPOUT"), etc.
+ */
+
+#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 <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <sound/initval.h>
+
+#include "tlv320aic23.h"
+
+#define AIC23_VERSION "0.1"
+
+struct tlv320aic23_srate_reg_info {
+ u32 sample_rate;
+ u8 control; /* SR3, SR2, SR1, SR0 and BOSR */
+ u8 divider; /* if 0 CLKIN = MCLK, if 1 CLKIN = MCLK/2 */
+};
+
+/*
+ * AIC23 register cache
+ */
+static const u16 tlv320aic23_reg[] = {
+ 0x0097, 0x0097, 0x00F9, 0x00F9, /* 0 */
+ 0x001A, 0x0004, 0x0007, 0x0001, /* 4 */
+ 0x0020, 0x0000, 0x0000, 0x0000, /* 8 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 12 */
+};
+
+/*
+ * read tlv320aic23 register cache
+ */
+static inline unsigned int tlv320aic23_read_reg_cache(struct snd_soc_codec
+ *codec, unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= ARRAY_SIZE(tlv320aic23_reg))
+ return -1;
+ return cache[reg];
+}
+
+/*
+ * write tlv320aic23 register cache
+ */
+static inline void tlv320aic23_write_reg_cache(struct snd_soc_codec *codec,
+ u8 reg, u16 value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= ARRAY_SIZE(tlv320aic23_reg))
+ return;
+ cache[reg] = value;
+}
+
+/*
+ * write to the tlv320aic23 register space
+ */
+static int tlv320aic23_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+
+ u8 data[2];
+
+ /* TLV320AIC23 has 7 bit address and 9 bits of data
+ * so we need to switch one data bit into reg and rest
+ * of data into val
+ */
+
+ if ((reg < 0 || reg > 9) && (reg != 15)) {
+ printk(KERN_WARNING "%s Invalid register R%d\n", __func__, reg);
+ return -1;
+ }
+
+ data[0] = (reg << 1) | (value >> 8 & 0x01);
+ data[1] = value & 0xff;
+
+ tlv320aic23_write_reg_cache(codec, reg, value);
+
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+
+ printk(KERN_ERR "%s cannot write %03x to register R%d\n", __func__,
+ value, reg);
+
+ return -EIO;
+}
+
+static const char *rec_src_text[] = { "Line", "Mic" };
+static const char *deemph_text[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+
+static const struct soc_enum rec_src_enum =
+ SOC_ENUM_SINGLE(TLV320AIC23_ANLG, 2, 2, rec_src_text);
+
+static const struct snd_kcontrol_new tlv320aic23_rec_src_mux_controls =
+SOC_DAPM_ENUM("Input Select", rec_src_enum);
+
+static const struct soc_enum tlv320aic23_rec_src =
+ SOC_ENUM_SINGLE(TLV320AIC23_ANLG, 2, 2, rec_src_text);
+static const struct soc_enum tlv320aic23_deemph =
+ SOC_ENUM_SINGLE(TLV320AIC23_DIGT, 1, 4, deemph_text);
+
+static const DECLARE_TLV_DB_SCALE(out_gain_tlv, -12100, 100, 0);
+static const DECLARE_TLV_DB_SCALE(input_gain_tlv, -1725, 75, 0);
+static const DECLARE_TLV_DB_SCALE(sidetone_vol_tlv, -1800, 300, 0);
+
+static int snd_soc_tlv320aic23_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ u16 val, reg;
+
+ val = (ucontrol->value.integer.value[0] & 0x07);
+
+ /* linear conversion to userspace
+ * 000 = -6db
+ * 001 = -9db
+ * 010 = -12db
+ * 011 = -18db (Min)
+ * 100 = 0db (Max)
+ */
+ val = (val >= 4) ? 4 : (3 - val);
+
+ reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_ANLG) & (~0x1C0);
+ tlv320aic23_write(codec, TLV320AIC23_ANLG, reg | (val << 6));
+
+ return 0;
+}
+
+static int snd_soc_tlv320aic23_get_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ u16 val;
+
+ val = tlv320aic23_read_reg_cache(codec, TLV320AIC23_ANLG) & (0x1C0);
+ val = val >> 6;
+ val = (val >= 4) ? 4 : (3 - val);
+ ucontrol->value.integer.value[0] = val;
+ return 0;
+
+}
+
+#define SOC_TLV320AIC23_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+ SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw, .get = snd_soc_tlv320aic23_get_volsw,\
+ .put = snd_soc_tlv320aic23_put_volsw, \
+ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+static const struct snd_kcontrol_new tlv320aic23_snd_controls[] = {
+ SOC_DOUBLE_R_TLV("Digital Playback Volume", TLV320AIC23_LCHNVOL,
+ TLV320AIC23_RCHNVOL, 0, 127, 0, out_gain_tlv),
+ SOC_SINGLE("Digital Playback Switch", TLV320AIC23_DIGT, 3, 1, 1),
+ SOC_DOUBLE_R("Line Input Switch", TLV320AIC23_LINVOL,
+ TLV320AIC23_RINVOL, 7, 1, 0),
+ SOC_DOUBLE_R_TLV("Line Input Volume", TLV320AIC23_LINVOL,
+ TLV320AIC23_RINVOL, 0, 31, 0, input_gain_tlv),
+ SOC_SINGLE("Mic Input Switch", TLV320AIC23_ANLG, 1, 1, 1),
+ SOC_SINGLE("Mic Booster Switch", TLV320AIC23_ANLG, 0, 1, 0),
+ SOC_TLV320AIC23_SINGLE_TLV("Sidetone Volume", TLV320AIC23_ANLG,
+ 6, 4, 0, sidetone_vol_tlv),
+ SOC_ENUM("Playback De-emphasis", tlv320aic23_deemph),
+};
+
+/* add non dapm controls */
+static int tlv320aic23_add_controls(struct snd_soc_codec *codec)
+{
+
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(tlv320aic23_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&tlv320aic23_snd_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+
+}
+
+/* PGA Mixer controls for Line and Mic switch */
+static const struct snd_kcontrol_new tlv320aic23_output_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line Bypass Switch", TLV320AIC23_ANLG, 3, 1, 0),
+ SOC_DAPM_SINGLE("Mic Sidetone Switch", TLV320AIC23_ANLG, 5, 1, 0),
+ SOC_DAPM_SINGLE("Playback Switch", TLV320AIC23_ANLG, 4, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC("DAC", "Playback", TLV320AIC23_PWR, 3, 1),
+ SND_SOC_DAPM_ADC("ADC", "Capture", TLV320AIC23_PWR, 2, 1),
+ SND_SOC_DAPM_MUX("Capture Source", SND_SOC_NOPM, 0, 0,
+ &tlv320aic23_rec_src_mux_controls),
+ SND_SOC_DAPM_MIXER("Output Mixer", TLV320AIC23_PWR, 4, 1,
+ &tlv320aic23_output_mixer_controls[0],
+ ARRAY_SIZE(tlv320aic23_output_mixer_controls)),
+ SND_SOC_DAPM_PGA("Line Input", TLV320AIC23_PWR, 0, 1, NULL, 0),
+ SND_SOC_DAPM_PGA("Mic Input", TLV320AIC23_PWR, 1, 1, NULL, 0),
+
+ SND_SOC_DAPM_OUTPUT("LHPOUT"),
+ SND_SOC_DAPM_OUTPUT("RHPOUT"),
+ SND_SOC_DAPM_OUTPUT("LOUT"),
+ SND_SOC_DAPM_OUTPUT("ROUT"),
+
+ SND_SOC_DAPM_INPUT("LLINEIN"),
+ SND_SOC_DAPM_INPUT("RLINEIN"),
+
+ SND_SOC_DAPM_INPUT("MICIN"),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ /* Output Mixer */
+ {"Output Mixer", "Line Bypass Switch", "Line Input"},
+ {"Output Mixer", "Playback Switch", "DAC"},
+ {"Output Mixer", "Mic Sidetone Switch", "Mic Input"},
+
+ /* Outputs */
+ {"RHPOUT", NULL, "Output Mixer"},
+ {"LHPOUT", NULL, "Output Mixer"},
+ {"LOUT", NULL, "Output Mixer"},
+ {"ROUT", NULL, "Output Mixer"},
+
+ /* Inputs */
+ {"Line Input", "NULL", "LLINEIN"},
+ {"Line Input", "NULL", "RLINEIN"},
+
+ {"Mic Input", "NULL", "MICIN"},
+
+ /* input mux */
+ {"Capture Source", "Line", "Line Input"},
+ {"Capture Source", "Mic", "Mic Input"},
+ {"ADC", NULL, "Capture Source"},
+
+};
+
+/* tlv320aic23 related */
+static const struct tlv320aic23_srate_reg_info srate_reg_info[] = {
+ {4000, 0x06, 1}, /* 4000 */
+ {8000, 0x06, 0}, /* 8000 */
+ {16000, 0x0C, 1}, /* 16000 */
+ {22050, 0x11, 1}, /* 22050 */
+ {24000, 0x00, 1}, /* 24000 */
+ {32000, 0x0C, 0}, /* 32000 */
+ {44100, 0x11, 0}, /* 44100 */
+ {48000, 0x00, 0}, /* 48000 */
+ {88200, 0x1F, 0}, /* 88200 */
+ {96000, 0x0E, 0}, /* 96000 */
+};
+
+static int tlv320aic23_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets,
+ ARRAY_SIZE(tlv320aic23_dapm_widgets));
+
+ /* set up audio path interconnects */
+ snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+static int tlv320aic23_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 iface_reg, data;
+ u8 count = 0;
+
+ iface_reg =
+ tlv320aic23_read_reg_cache(codec,
+ TLV320AIC23_DIGT_FMT) & ~(0x03 << 2);
+
+ /* Search for the right sample rate */
+ /* Verify what happens if the rate is not supported
+ * now it goes to 96Khz */
+ while ((srate_reg_info[count].sample_rate != params_rate(params)) &&
+ (count < ARRAY_SIZE(srate_reg_info))) {
+ count++;
+ }
+
+ data = (srate_reg_info[count].divider << TLV320AIC23_CLKIN_SHIFT) |
+ (srate_reg_info[count]. control << TLV320AIC23_BOSR_SHIFT) |
+ TLV320AIC23_USB_CLK_ON;
+
+ tlv320aic23_write(codec, TLV320AIC23_SRATE, data);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface_reg |= (0x01 << 2);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface_reg |= (0x02 << 2);
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface_reg |= (0x03 << 2);
+ break;
+ }
+ tlv320aic23_write(codec, TLV320AIC23_DIGT_FMT, iface_reg);
+
+ return 0;
+}
+
+static int tlv320aic23_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+
+ /* set active */
+ tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0001);
+
+ return 0;
+}
+
+static void tlv320aic23_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+
+ /* deactivate */
+ if (!codec->active) {
+ udelay(50);
+ tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0);
+ }
+}
+
+static int tlv320aic23_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 reg;
+
+ reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_DIGT);
+ if (mute)
+ reg |= TLV320AIC23_DACM_MUTE;
+
+ else
+ reg &= ~TLV320AIC23_DACM_MUTE;
+
+ tlv320aic23_write(codec, TLV320AIC23_DIGT, reg);
+
+ return 0;
+}
+
+static int tlv320aic23_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface_reg;
+
+ iface_reg =
+ tlv320aic23_read_reg_cache(codec, TLV320AIC23_DIGT_FMT) & (~0x03);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface_reg |= TLV320AIC23_MS_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface_reg |= TLV320AIC23_FOR_I2S;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface_reg |= TLV320AIC23_FOR_DSP;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface_reg |= TLV320AIC23_FOR_LJUST;
+ break;
+ default:
+ return -EINVAL;
+
+ }
+
+ tlv320aic23_write(codec, TLV320AIC23_DIGT_FMT, iface_reg);
+
+ return 0;
+}
+
+static int tlv320aic23_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;
+
+ switch (freq) {
+ case 12000000:
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int tlv320aic23_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_PWR) & 0xff7f;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* vref/mid, osc on, dac unmute */
+ tlv320aic23_write(codec, TLV320AIC23_PWR, reg);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* everything off except vref/vmid, */
+ tlv320aic23_write(codec, TLV320AIC23_PWR, reg | 0x0040);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* everything off, dac mute, inactive */
+ tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0);
+ tlv320aic23_write(codec, TLV320AIC23_PWR, 0xffff);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define AIC23_RATES SNDRV_PCM_RATE_8000_96000
+#define AIC23_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+struct snd_soc_dai tlv320aic23_dai = {
+ .name = "tlv320aic23",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AIC23_RATES,
+ .formats = AIC23_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AIC23_RATES,
+ .formats = AIC23_FORMATS,},
+ .ops = {
+ .prepare = tlv320aic23_pcm_prepare,
+ .hw_params = tlv320aic23_hw_params,
+ .shutdown = tlv320aic23_shutdown,
+ },
+ .dai_ops = {
+ .digital_mute = tlv320aic23_mute,
+ .set_fmt = tlv320aic23_set_dai_fmt,
+ .set_sysclk = tlv320aic23_set_dai_sysclk,
+ }
+};
+EXPORT_SYMBOL_GPL(tlv320aic23_dai);
+
+static int tlv320aic23_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0);
+ tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int tlv320aic23_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ int i;
+ u16 reg;
+
+ /* Sync reg_cache with the hardware */
+ for (reg = 0; reg < ARRAY_SIZE(tlv320aic23_reg); i++) {
+ u16 val = tlv320aic23_read_reg_cache(codec, reg);
+ tlv320aic23_write(codec, reg, val);
+ }
+
+ tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ tlv320aic23_set_bias_level(codec, codec->suspend_bias_level);
+
+ return 0;
+}
+
+/*
+ * initialise the AIC23 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int tlv320aic23_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret = 0;
+ u16 reg;
+
+ codec->name = "tlv320aic23";
+ codec->owner = THIS_MODULE;
+ codec->read = tlv320aic23_read_reg_cache;
+ codec->write = tlv320aic23_write;
+ codec->set_bias_level = tlv320aic23_set_bias_level;
+ codec->dai = &tlv320aic23_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = ARRAY_SIZE(tlv320aic23_reg);
+ codec->reg_cache =
+ kmemdup(tlv320aic23_reg, sizeof(tlv320aic23_reg), GFP_KERNEL);
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ /* Reset codec */
+ tlv320aic23_write(codec, TLV320AIC23_RESET, 0);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "tlv320aic23: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* power on device */
+ tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ tlv320aic23_write(codec, TLV320AIC23_DIGT, TLV320AIC23_DEEMP_44K);
+
+ /* Unmute input */
+ reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_LINVOL);
+ tlv320aic23_write(codec, TLV320AIC23_LINVOL,
+ (reg & (~TLV320AIC23_LIM_MUTED)) |
+ (TLV320AIC23_LRS_ENABLED));
+
+ reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_RINVOL);
+ tlv320aic23_write(codec, TLV320AIC23_RINVOL,
+ (reg & (~TLV320AIC23_LIM_MUTED)) |
+ TLV320AIC23_LRS_ENABLED);
+
+ reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_ANLG);
+ tlv320aic23_write(codec, TLV320AIC23_ANLG,
+ (reg) & (~TLV320AIC23_BYPASS_ON) &
+ (~TLV320AIC23_MICM_MUTED));
+
+ /* Default output volume */
+ tlv320aic23_write(codec, TLV320AIC23_LCHNVOL,
+ TLV320AIC23_DEFAULT_OUT_VOL &
+ TLV320AIC23_OUT_VOL_MASK);
+ tlv320aic23_write(codec, TLV320AIC23_RCHNVOL,
+ TLV320AIC23_DEFAULT_OUT_VOL &
+ TLV320AIC23_OUT_VOL_MASK);
+
+ tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x1);
+
+ tlv320aic23_add_controls(codec);
+ tlv320aic23_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "tlv320aic23: failed to register card\n");
+ goto card_err;
+ }
+
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+ return ret;
+}
+static struct snd_soc_device *tlv320aic23_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+/*
+ * If the i2c layer weren't so broken, we could pass this kind of data
+ * around
+ */
+static int tlv320aic23_codec_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *i2c_id)
+{
+ struct snd_soc_device *socdev = tlv320aic23_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+ return -EINVAL;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = tlv320aic23_init(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "tlv320aic23: failed to initialise AIC23\n");
+ goto err;
+ }
+ return ret;
+
+err:
+ kfree(codec);
+ kfree(i2c);
+ return ret;
+}
+static int __exit tlv320aic23_i2c_remove(struct i2c_client *i2c)
+{
+ put_device(&i2c->dev);
+ return 0;
+}
+
+static const struct i2c_device_id tlv320aic23_id[] = {
+ {"tlv320aic23", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, tlv320aic23_id);
+
+static struct i2c_driver tlv320aic23_i2c_driver = {
+ .driver = {
+ .name = "tlv320aic23",
+ },
+ .probe = tlv320aic23_codec_probe,
+ .remove = __exit_p(tlv320aic23_i2c_remove),
+ .id_table = tlv320aic23_id,
+};
+
+#endif
+
+static int tlv320aic23_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ printk(KERN_INFO "AIC23 Audio Codec %s\n", AIC23_VERSION);
+
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ tlv320aic23_socdev = socdev;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ codec->hw_write = (hw_write_t) i2c_master_send;
+ codec->hw_read = NULL;
+ ret = i2c_add_driver(&tlv320aic23_i2c_driver);
+ if (ret != 0)
+ printk(KERN_ERR "can't add i2c driver");
+#endif
+ return ret;
+}
+
+static int tlv320aic23_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec->control_data)
+ tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&tlv320aic23_i2c_driver);
+#endif
+ kfree(codec->reg_cache);
+ kfree(codec);
+
+ return 0;
+}
+struct snd_soc_codec_device soc_codec_dev_tlv320aic23 = {
+ .probe = tlv320aic23_probe,
+ .remove = tlv320aic23_remove,
+ .suspend = tlv320aic23_suspend,
+ .resume = tlv320aic23_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_tlv320aic23);
+
+MODULE_DESCRIPTION("ASoC TLV320AIC23 codec driver");
+MODULE_AUTHOR("Arun KS <arunks@mistralsolutions.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/tlv320aic23.h b/sound/soc/codecs/tlv320aic23.h
new file mode 100644
index 0000000..79d1faf
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic23.h
@@ -0,0 +1,122 @@
+/*
+ * ALSA SoC TLV320AIC23 codec driver
+ *
+ * Author: Arun KS, <arunks@mistralsolutions.com>
+ * Copyright: (C) 2008 Mistral Solutions Pvt Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _TLV320AIC23_H
+#define _TLV320AIC23_H
+
+/* Codec TLV320AIC23 */
+#define TLV320AIC23_LINVOL 0x00
+#define TLV320AIC23_RINVOL 0x01
+#define TLV320AIC23_LCHNVOL 0x02
+#define TLV320AIC23_RCHNVOL 0x03
+#define TLV320AIC23_ANLG 0x04
+#define TLV320AIC23_DIGT 0x05
+#define TLV320AIC23_PWR 0x06
+#define TLV320AIC23_DIGT_FMT 0x07
+#define TLV320AIC23_SRATE 0x08
+#define TLV320AIC23_ACTIVE 0x09
+#define TLV320AIC23_RESET 0x0F
+
+/* Left (right) line input volume control register */
+#define TLV320AIC23_LRS_ENABLED 0x0100
+#define TLV320AIC23_LIM_MUTED 0x0080
+#define TLV320AIC23_LIV_DEFAULT 0x0017
+#define TLV320AIC23_LIV_MAX 0x001f
+#define TLV320AIC23_LIV_MIN 0x0000
+
+/* Left (right) channel headphone volume control register */
+#define TLV320AIC23_LZC_ON 0x0080
+#define TLV320AIC23_LHV_DEFAULT 0x0079
+#define TLV320AIC23_LHV_MAX 0x007f
+#define TLV320AIC23_LHV_MIN 0x0000
+
+/* Analog audio path control register */
+#define TLV320AIC23_STA_REG(x) ((x)<<6)
+#define TLV320AIC23_STE_ENABLED 0x0020
+#define TLV320AIC23_DAC_SELECTED 0x0010
+#define TLV320AIC23_BYPASS_ON 0x0008
+#define TLV320AIC23_INSEL_MIC 0x0004
+#define TLV320AIC23_MICM_MUTED 0x0002
+#define TLV320AIC23_MICB_20DB 0x0001
+
+/* Digital audio path control register */
+#define TLV320AIC23_DACM_MUTE 0x0008
+#define TLV320AIC23_DEEMP_32K 0x0002
+#define TLV320AIC23_DEEMP_44K 0x0004
+#define TLV320AIC23_DEEMP_48K 0x0006
+#define TLV320AIC23_ADCHP_ON 0x0001
+
+/* Power control down register */
+#define TLV320AIC23_DEVICE_PWR_OFF 0x0080
+#define TLV320AIC23_CLK_OFF 0x0040
+#define TLV320AIC23_OSC_OFF 0x0020
+#define TLV320AIC23_OUT_OFF 0x0010
+#define TLV320AIC23_DAC_OFF 0x0008
+#define TLV320AIC23_ADC_OFF 0x0004
+#define TLV320AIC23_MIC_OFF 0x0002
+#define TLV320AIC23_LINE_OFF 0x0001
+
+/* Digital audio interface register */
+#define TLV320AIC23_MS_MASTER 0x0040
+#define TLV320AIC23_LRSWAP_ON 0x0020
+#define TLV320AIC23_LRP_ON 0x0010
+#define TLV320AIC23_IWL_16 0x0000
+#define TLV320AIC23_IWL_20 0x0004
+#define TLV320AIC23_IWL_24 0x0008
+#define TLV320AIC23_IWL_32 0x000C
+#define TLV320AIC23_FOR_I2S 0x0002
+#define TLV320AIC23_FOR_DSP 0x0003
+#define TLV320AIC23_FOR_LJUST 0x0001
+
+/* Sample rate control register */
+#define TLV320AIC23_CLKOUT_HALF 0x0080
+#define TLV320AIC23_CLKIN_HALF 0x0040
+#define TLV320AIC23_BOSR_384fs 0x0002 /* BOSR_272fs in USB mode */
+#define TLV320AIC23_USB_CLK_ON 0x0001
+#define TLV320AIC23_SR_MASK 0xf
+#define TLV320AIC23_CLKOUT_SHIFT 7
+#define TLV320AIC23_CLKIN_SHIFT 6
+#define TLV320AIC23_SR_SHIFT 2
+#define TLV320AIC23_BOSR_SHIFT 1
+
+/* Digital interface register */
+#define TLV320AIC23_ACT_ON 0x0001
+
+/*
+ * AUDIO related MACROS
+ */
+
+#define TLV320AIC23_DEFAULT_OUT_VOL 0x70
+#define TLV320AIC23_DEFAULT_IN_VOLUME 0x10
+
+#define TLV320AIC23_OUT_VOL_MIN TLV320AIC23_LHV_MIN
+#define TLV320AIC23_OUT_VOL_MAX TLV320AIC23_LHV_MAX
+#define TLV320AIC23_OUT_VO_RANGE (TLV320AIC23_OUT_VOL_MAX - \
+ TLV320AIC23_OUT_VOL_MIN)
+#define TLV320AIC23_OUT_VOL_MASK TLV320AIC23_OUT_VOL_MAX
+
+#define TLV320AIC23_IN_VOL_MIN TLV320AIC23_LIV_MIN
+#define TLV320AIC23_IN_VOL_MAX TLV320AIC23_LIV_MAX
+#define TLV320AIC23_IN_VOL_RANGE (TLV320AIC23_IN_VOL_MAX - \
+ TLV320AIC23_IN_VOL_MIN)
+#define TLV320AIC23_IN_VOL_MASK TLV320AIC23_IN_VOL_MAX
+
+#define TLV320AIC23_SIDETONE_MASK 0x1c0
+#define TLV320AIC23_SIDETONE_0 0x100
+#define TLV320AIC23_SIDETONE_6 0x000
+#define TLV320AIC23_SIDETONE_9 0x040
+#define TLV320AIC23_SIDETONE_12 0x080
+#define TLV320AIC23_SIDETONE_18 0x0c0
+
+extern struct snd_soc_dai tlv320aic23_dai;
+extern struct snd_soc_codec_device soc_codec_dev_tlv320aic23;
+
+#endif /* _TLV320AIC23_H */
diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c
new file mode 100644
index 0000000..bed8a9e
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic26.c
@@ -0,0 +1,520 @@
+/*
+ * Texas Instruments TLV320AIC26 low power audio CODEC
+ * ALSA SoC CODEC driver
+ *
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/device.h>
+#include <linux/sysfs.h>
+#include <linux/spi/spi.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/soc-of-simple.h>
+#include <sound/initval.h>
+
+#include "tlv320aic26.h"
+
+MODULE_DESCRIPTION("ASoC TLV320AIC26 codec driver");
+MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
+MODULE_LICENSE("GPL");
+
+/* AIC26 driver private data */
+struct aic26 {
+ struct spi_device *spi;
+ struct snd_soc_codec codec;
+ u16 reg_cache[AIC26_NUM_REGS]; /* shadow registers */
+ int master;
+ int datfm;
+ int mclk;
+
+ /* Keyclick parameters */
+ int keyclick_amplitude;
+ int keyclick_freq;
+ int keyclick_len;
+};
+
+/* ---------------------------------------------------------------------
+ * Register access routines
+ */
+static unsigned int aic26_reg_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ struct aic26 *aic26 = codec->private_data;
+ u16 *cache = codec->reg_cache;
+ u16 cmd, value;
+ u8 buffer[2];
+ int rc;
+
+ if (reg >= AIC26_NUM_REGS) {
+ WARN_ON_ONCE(1);
+ return 0;
+ }
+
+ /* Do SPI transfer; first 16bits are command; remaining is
+ * register contents */
+ cmd = AIC26_READ_COMMAND_WORD(reg);
+ buffer[0] = (cmd >> 8) & 0xff;
+ buffer[1] = cmd & 0xff;
+ rc = spi_write_then_read(aic26->spi, buffer, 2, buffer, 2);
+ if (rc) {
+ dev_err(&aic26->spi->dev, "AIC26 reg read error\n");
+ return -EIO;
+ }
+ value = (buffer[0] << 8) | buffer[1];
+
+ /* Update the cache before returning with the value */
+ cache[reg] = value;
+ return value;
+}
+
+static unsigned int aic26_reg_read_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+
+ if (reg >= AIC26_NUM_REGS) {
+ WARN_ON_ONCE(1);
+ return 0;
+ }
+
+ return cache[reg];
+}
+
+static int aic26_reg_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ struct aic26 *aic26 = codec->private_data;
+ u16 *cache = codec->reg_cache;
+ u16 cmd;
+ u8 buffer[4];
+ int rc;
+
+ if (reg >= AIC26_NUM_REGS) {
+ WARN_ON_ONCE(1);
+ return -EINVAL;
+ }
+
+ /* Do SPI transfer; first 16bits are command; remaining is data
+ * to write into register */
+ cmd = AIC26_WRITE_COMMAND_WORD(reg);
+ buffer[0] = (cmd >> 8) & 0xff;
+ buffer[1] = cmd & 0xff;
+ buffer[2] = value >> 8;
+ buffer[3] = value;
+ rc = spi_write(aic26->spi, buffer, 4);
+ if (rc) {
+ dev_err(&aic26->spi->dev, "AIC26 reg read error\n");
+ return -EIO;
+ }
+
+ /* update cache before returning */
+ cache[reg] = value;
+ return 0;
+}
+
+/* ---------------------------------------------------------------------
+ * Digital Audio Interface Operations
+ */
+static int aic26_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct aic26 *aic26 = codec->private_data;
+ int fsref, divisor, wlen, pval, jval, dval, qval;
+ u16 reg;
+
+ dev_dbg(&aic26->spi->dev, "aic26_hw_params(substream=%p, params=%p)\n",
+ substream, params);
+ dev_dbg(&aic26->spi->dev, "rate=%i format=%i\n", params_rate(params),
+ params_format(params));
+
+ switch (params_rate(params)) {
+ case 8000: fsref = 48000; divisor = AIC26_DIV_6; break;
+ case 11025: fsref = 44100; divisor = AIC26_DIV_4; break;
+ case 12000: fsref = 48000; divisor = AIC26_DIV_4; break;
+ case 16000: fsref = 48000; divisor = AIC26_DIV_3; break;
+ case 22050: fsref = 44100; divisor = AIC26_DIV_2; break;
+ case 24000: fsref = 48000; divisor = AIC26_DIV_2; break;
+ case 32000: fsref = 48000; divisor = AIC26_DIV_1_5; break;
+ case 44100: fsref = 44100; divisor = AIC26_DIV_1; break;
+ case 48000: fsref = 48000; divisor = AIC26_DIV_1; break;
+ default:
+ dev_dbg(&aic26->spi->dev, "bad rate\n"); return -EINVAL;
+ }
+
+ /* select data word length */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8: wlen = AIC26_WLEN_16; break;
+ case SNDRV_PCM_FORMAT_S16_BE: wlen = AIC26_WLEN_16; break;
+ case SNDRV_PCM_FORMAT_S24_BE: wlen = AIC26_WLEN_24; break;
+ case SNDRV_PCM_FORMAT_S32_BE: wlen = AIC26_WLEN_32; break;
+ default:
+ dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL;
+ }
+
+ /* Configure PLL */
+ pval = 1;
+ jval = (fsref == 44100) ? 7 : 8;
+ dval = (fsref == 44100) ? 5264 : 1920;
+ qval = 0;
+ reg = 0x8000 | qval << 11 | pval << 8 | jval << 2;
+ aic26_reg_write(codec, AIC26_REG_PLL_PROG1, reg);
+ reg = dval << 2;
+ aic26_reg_write(codec, AIC26_REG_PLL_PROG2, reg);
+
+ /* Audio Control 3 (master mode, fsref rate) */
+ reg = aic26_reg_read_cache(codec, AIC26_REG_AUDIO_CTRL3);
+ reg &= ~0xf800;
+ if (aic26->master)
+ reg |= 0x0800;
+ if (fsref == 48000)
+ reg |= 0x2000;
+ aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL3, reg);
+
+ /* Audio Control 1 (FSref divisor) */
+ reg = aic26_reg_read_cache(codec, AIC26_REG_AUDIO_CTRL1);
+ reg &= ~0x0fff;
+ reg |= wlen | aic26->datfm | (divisor << 3) | divisor;
+ aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL1, reg);
+
+ return 0;
+}
+
+/**
+ * aic26_mute - Mute control to reduce noise when changing audio format
+ */
+static int aic26_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct aic26 *aic26 = codec->private_data;
+ u16 reg = aic26_reg_read_cache(codec, AIC26_REG_DAC_GAIN);
+
+ dev_dbg(&aic26->spi->dev, "aic26_mute(dai=%p, mute=%i)\n",
+ dai, mute);
+
+ if (mute)
+ reg |= 0x8080;
+ else
+ reg &= ~0x8080;
+ aic26_reg_write(codec, AIC26_REG_DAC_GAIN, reg);
+
+ return 0;
+}
+
+static int aic26_set_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct aic26 *aic26 = codec->private_data;
+
+ dev_dbg(&aic26->spi->dev, "aic26_set_sysclk(dai=%p, clk_id==%i,"
+ " freq=%i, dir=%i)\n",
+ codec_dai, clk_id, freq, dir);
+
+ /* MCLK needs to fall between 2MHz and 50 MHz */
+ if ((freq < 2000000) || (freq > 50000000))
+ return -EINVAL;
+
+ aic26->mclk = freq;
+ return 0;
+}
+
+static int aic26_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct aic26 *aic26 = codec->private_data;
+
+ dev_dbg(&aic26->spi->dev, "aic26_set_fmt(dai=%p, fmt==%i)\n",
+ codec_dai, fmt);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM: aic26->master = 1; break;
+ case SND_SOC_DAIFMT_CBS_CFS: aic26->master = 0; break;
+ default:
+ dev_dbg(&aic26->spi->dev, "bad master\n"); return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S: aic26->datfm = AIC26_DATFM_I2S; break;
+ case SND_SOC_DAIFMT_DSP_A: aic26->datfm = AIC26_DATFM_DSP; break;
+ case SND_SOC_DAIFMT_RIGHT_J: aic26->datfm = AIC26_DATFM_RIGHTJ; break;
+ case SND_SOC_DAIFMT_LEFT_J: aic26->datfm = AIC26_DATFM_LEFTJ; break;
+ default:
+ dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* ---------------------------------------------------------------------
+ * Digital Audio Interface Definition
+ */
+#define AIC26_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
+ SNDRV_PCM_RATE_48000)
+#define AIC26_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |\
+ SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE)
+
+struct snd_soc_dai aic26_dai = {
+ .name = "tlv320aic26",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AIC26_RATES,
+ .formats = AIC26_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AIC26_RATES,
+ .formats = AIC26_FORMATS,
+ },
+ .ops = {
+ .hw_params = aic26_hw_params,
+ },
+ .dai_ops = {
+ .digital_mute = aic26_mute,
+ .set_sysclk = aic26_set_sysclk,
+ .set_fmt = aic26_set_fmt,
+ },
+};
+EXPORT_SYMBOL_GPL(aic26_dai);
+
+/* ---------------------------------------------------------------------
+ * ALSA controls
+ */
+static const char *aic26_capture_src_text[] = {"Mic", "Aux"};
+static const struct soc_enum aic26_capture_src_enum =
+ SOC_ENUM_SINGLE(AIC26_REG_AUDIO_CTRL1, 12, 2, aic26_capture_src_text);
+
+static const struct snd_kcontrol_new aic26_snd_controls[] = {
+ /* Output */
+ SOC_DOUBLE("PCM Playback Volume", AIC26_REG_DAC_GAIN, 8, 0, 0x7f, 1),
+ SOC_DOUBLE("PCM Playback Switch", AIC26_REG_DAC_GAIN, 15, 7, 1, 1),
+ SOC_SINGLE("PCM Capture Volume", AIC26_REG_ADC_GAIN, 8, 0x7f, 0),
+ SOC_SINGLE("PCM Capture Mute", AIC26_REG_ADC_GAIN, 15, 1, 1),
+ SOC_SINGLE("Keyclick activate", AIC26_REG_AUDIO_CTRL2, 15, 0x1, 0),
+ SOC_SINGLE("Keyclick amplitude", AIC26_REG_AUDIO_CTRL2, 12, 0x7, 0),
+ SOC_SINGLE("Keyclick frequency", AIC26_REG_AUDIO_CTRL2, 8, 0x7, 0),
+ SOC_SINGLE("Keyclick period", AIC26_REG_AUDIO_CTRL2, 4, 0xf, 0),
+ SOC_ENUM("Capture Source", aic26_capture_src_enum),
+};
+
+/* ---------------------------------------------------------------------
+ * SoC CODEC portion of driver: probe and release routines
+ */
+static int aic26_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ struct snd_kcontrol *kcontrol;
+ struct aic26 *aic26;
+ int i, ret, err;
+
+ dev_info(&pdev->dev, "Probing AIC26 SoC CODEC driver\n");
+ dev_dbg(&pdev->dev, "socdev=%p\n", socdev);
+ dev_dbg(&pdev->dev, "codec_data=%p\n", socdev->codec_data);
+
+ /* Fetch the relevant aic26 private data here (it's already been
+ * stored in the .codec pointer) */
+ aic26 = socdev->codec_data;
+ if (aic26 == NULL) {
+ dev_err(&pdev->dev, "aic26: missing codec pointer\n");
+ return -ENODEV;
+ }
+ codec = &aic26->codec;
+ socdev->codec = codec;
+
+ dev_dbg(&pdev->dev, "Registering PCMs, dev=%p, socdev->dev=%p\n",
+ &pdev->dev, socdev->dev);
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "aic26: failed to create pcms\n");
+ return -ENODEV;
+ }
+
+ /* register controls */
+ dev_dbg(&pdev->dev, "Registering controls\n");
+ for (i = 0; i < ARRAY_SIZE(aic26_snd_controls); i++) {
+ kcontrol = snd_soc_cnew(&aic26_snd_controls[i], codec, NULL);
+ err = snd_ctl_add(codec->card, kcontrol);
+ WARN_ON(err < 0);
+ }
+
+ /* CODEC is setup, we can register the card now */
+ dev_dbg(&pdev->dev, "Registering card\n");
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "aic26: failed to register card\n");
+ goto card_err;
+ }
+ return 0;
+
+ card_err:
+ snd_soc_free_pcms(socdev);
+ return ret;
+}
+
+static int aic26_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ snd_soc_free_pcms(socdev);
+ return 0;
+}
+
+struct snd_soc_codec_device aic26_soc_codec_dev = {
+ .probe = aic26_probe,
+ .remove = aic26_remove,
+};
+EXPORT_SYMBOL_GPL(aic26_soc_codec_dev);
+
+/* ---------------------------------------------------------------------
+ * SPI device portion of driver: sysfs files for debugging
+ */
+
+static ssize_t aic26_keyclick_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct aic26 *aic26 = dev_get_drvdata(dev);
+ int val, amp, freq, len;
+
+ val = aic26_reg_read_cache(&aic26->codec, AIC26_REG_AUDIO_CTRL2);
+ amp = (val >> 12) & 0x7;
+ freq = (125 << ((val >> 8) & 0x7)) >> 1;
+ len = 2 * (1 + ((val >> 4) & 0xf));
+
+ return sprintf(buf, "amp=%x freq=%iHz len=%iclks\n", amp, freq, len);
+}
+
+/* Any write to the keyclick attribute will trigger the keyclick event */
+static ssize_t aic26_keyclick_set(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct aic26 *aic26 = dev_get_drvdata(dev);
+ int val;
+
+ val = aic26_reg_read_cache(&aic26->codec, AIC26_REG_AUDIO_CTRL2);
+ val |= 0x8000;
+ aic26_reg_write(&aic26->codec, AIC26_REG_AUDIO_CTRL2, val);
+
+ return count;
+}
+
+static DEVICE_ATTR(keyclick, 0644, aic26_keyclick_show, aic26_keyclick_set);
+
+/* ---------------------------------------------------------------------
+ * SPI device portion of driver: probe and release routines and SPI
+ * driver registration.
+ */
+static int aic26_spi_probe(struct spi_device *spi)
+{
+ struct aic26 *aic26;
+ int rc, i, reg;
+
+ dev_dbg(&spi->dev, "probing tlv320aic26 spi device\n");
+
+ /* Allocate driver data */
+ aic26 = kzalloc(sizeof *aic26, GFP_KERNEL);
+ if (!aic26)
+ return -ENOMEM;
+
+ /* Initialize the driver data */
+ aic26->spi = spi;
+ dev_set_drvdata(&spi->dev, aic26);
+
+ /* Setup what we can in the codec structure so that the register
+ * access functions will work as expected. More will be filled
+ * out when it is probed by the SoC CODEC part of this driver */
+ aic26->codec.private_data = aic26;
+ aic26->codec.name = "aic26";
+ aic26->codec.owner = THIS_MODULE;
+ aic26->codec.dai = &aic26_dai;
+ aic26->codec.num_dai = 1;
+ aic26->codec.read = aic26_reg_read;
+ aic26->codec.write = aic26_reg_write;
+ aic26->master = 1;
+ mutex_init(&aic26->codec.mutex);
+ INIT_LIST_HEAD(&aic26->codec.dapm_widgets);
+ INIT_LIST_HEAD(&aic26->codec.dapm_paths);
+ aic26->codec.reg_cache_size = AIC26_NUM_REGS;
+ aic26->codec.reg_cache = aic26->reg_cache;
+
+ /* Reset the codec to power on defaults */
+ aic26_reg_write(&aic26->codec, AIC26_REG_RESET, 0xBB00);
+
+ /* Power up CODEC */
+ aic26_reg_write(&aic26->codec, AIC26_REG_POWER_CTRL, 0);
+
+ /* Audio Control 3 (master mode, fsref rate) */
+ reg = aic26_reg_read(&aic26->codec, AIC26_REG_AUDIO_CTRL3);
+ reg &= ~0xf800;
+ reg |= 0x0800; /* set master mode */
+ aic26_reg_write(&aic26->codec, AIC26_REG_AUDIO_CTRL3, reg);
+
+ /* Fill register cache */
+ for (i = 0; i < ARRAY_SIZE(aic26->reg_cache); i++)
+ aic26_reg_read(&aic26->codec, i);
+
+ /* Register the sysfs files for debugging */
+ /* Create SysFS files */
+ rc = device_create_file(&spi->dev, &dev_attr_keyclick);
+ if (rc)
+ dev_info(&spi->dev, "error creating sysfs files\n");
+
+#if defined(CONFIG_SND_SOC_OF_SIMPLE)
+ /* Tell the of_soc helper about this codec */
+ of_snd_soc_register_codec(&aic26_soc_codec_dev, aic26, &aic26_dai,
+ spi->dev.archdata.of_node);
+#endif
+
+ dev_dbg(&spi->dev, "SPI device initialized\n");
+ return 0;
+}
+
+static int aic26_spi_remove(struct spi_device *spi)
+{
+ struct aic26 *aic26 = dev_get_drvdata(&spi->dev);
+
+ kfree(aic26);
+
+ return 0;
+}
+
+static struct spi_driver aic26_spi = {
+ .driver = {
+ .name = "tlv320aic26",
+ .owner = THIS_MODULE,
+ },
+ .probe = aic26_spi_probe,
+ .remove = aic26_spi_remove,
+};
+
+static int __init aic26_init(void)
+{
+ return spi_register_driver(&aic26_spi);
+}
+module_init(aic26_init);
+
+static void __exit aic26_exit(void)
+{
+ spi_unregister_driver(&aic26_spi);
+}
+module_exit(aic26_exit);
diff --git a/sound/soc/codecs/tlv320aic26.h b/sound/soc/codecs/tlv320aic26.h
new file mode 100644
index 0000000..786ba16
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic26.h
@@ -0,0 +1,96 @@
+/*
+ * Texas Instruments TLV320AIC26 low power audio CODEC
+ * register definitions
+ *
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ */
+
+#ifndef _TLV320AIC16_H_
+#define _TLV320AIC16_H_
+
+/* AIC26 Registers */
+#define AIC26_READ_COMMAND_WORD(addr) ((1 << 15) | (addr << 5))
+#define AIC26_WRITE_COMMAND_WORD(addr) ((0 << 15) | (addr << 5))
+#define AIC26_PAGE_ADDR(page, offset) ((page << 6) | offset)
+#define AIC26_NUM_REGS AIC26_PAGE_ADDR(3, 0)
+
+/* Page 0: Auxillary data registers */
+#define AIC26_REG_BAT1 AIC26_PAGE_ADDR(0, 0x05)
+#define AIC26_REG_BAT2 AIC26_PAGE_ADDR(0, 0x06)
+#define AIC26_REG_AUX AIC26_PAGE_ADDR(0, 0x07)
+#define AIC26_REG_TEMP1 AIC26_PAGE_ADDR(0, 0x09)
+#define AIC26_REG_TEMP2 AIC26_PAGE_ADDR(0, 0x0A)
+
+/* Page 1: Auxillary control registers */
+#define AIC26_REG_AUX_ADC AIC26_PAGE_ADDR(1, 0x00)
+#define AIC26_REG_STATUS AIC26_PAGE_ADDR(1, 0x01)
+#define AIC26_REG_REFERENCE AIC26_PAGE_ADDR(1, 0x03)
+#define AIC26_REG_RESET AIC26_PAGE_ADDR(1, 0x04)
+
+/* Page 2: Audio control registers */
+#define AIC26_REG_AUDIO_CTRL1 AIC26_PAGE_ADDR(2, 0x00)
+#define AIC26_REG_ADC_GAIN AIC26_PAGE_ADDR(2, 0x01)
+#define AIC26_REG_DAC_GAIN AIC26_PAGE_ADDR(2, 0x02)
+#define AIC26_REG_SIDETONE AIC26_PAGE_ADDR(2, 0x03)
+#define AIC26_REG_AUDIO_CTRL2 AIC26_PAGE_ADDR(2, 0x04)
+#define AIC26_REG_POWER_CTRL AIC26_PAGE_ADDR(2, 0x05)
+#define AIC26_REG_AUDIO_CTRL3 AIC26_PAGE_ADDR(2, 0x06)
+
+#define AIC26_REG_FILTER_COEFF_L_N0 AIC26_PAGE_ADDR(2, 0x07)
+#define AIC26_REG_FILTER_COEFF_L_N1 AIC26_PAGE_ADDR(2, 0x08)
+#define AIC26_REG_FILTER_COEFF_L_N2 AIC26_PAGE_ADDR(2, 0x09)
+#define AIC26_REG_FILTER_COEFF_L_N3 AIC26_PAGE_ADDR(2, 0x0A)
+#define AIC26_REG_FILTER_COEFF_L_N4 AIC26_PAGE_ADDR(2, 0x0B)
+#define AIC26_REG_FILTER_COEFF_L_N5 AIC26_PAGE_ADDR(2, 0x0C)
+#define AIC26_REG_FILTER_COEFF_L_D1 AIC26_PAGE_ADDR(2, 0x0D)
+#define AIC26_REG_FILTER_COEFF_L_D2 AIC26_PAGE_ADDR(2, 0x0E)
+#define AIC26_REG_FILTER_COEFF_L_D4 AIC26_PAGE_ADDR(2, 0x0F)
+#define AIC26_REG_FILTER_COEFF_L_D5 AIC26_PAGE_ADDR(2, 0x10)
+#define AIC26_REG_FILTER_COEFF_R_N0 AIC26_PAGE_ADDR(2, 0x11)
+#define AIC26_REG_FILTER_COEFF_R_N1 AIC26_PAGE_ADDR(2, 0x12)
+#define AIC26_REG_FILTER_COEFF_R_N2 AIC26_PAGE_ADDR(2, 0x13)
+#define AIC26_REG_FILTER_COEFF_R_N3 AIC26_PAGE_ADDR(2, 0x14)
+#define AIC26_REG_FILTER_COEFF_R_N4 AIC26_PAGE_ADDR(2, 0x15)
+#define AIC26_REG_FILTER_COEFF_R_N5 AIC26_PAGE_ADDR(2, 0x16)
+#define AIC26_REG_FILTER_COEFF_R_D1 AIC26_PAGE_ADDR(2, 0x17)
+#define AIC26_REG_FILTER_COEFF_R_D2 AIC26_PAGE_ADDR(2, 0x18)
+#define AIC26_REG_FILTER_COEFF_R_D4 AIC26_PAGE_ADDR(2, 0x19)
+#define AIC26_REG_FILTER_COEFF_R_D5 AIC26_PAGE_ADDR(2, 0x1A)
+
+#define AIC26_REG_PLL_PROG1 AIC26_PAGE_ADDR(2, 0x1B)
+#define AIC26_REG_PLL_PROG2 AIC26_PAGE_ADDR(2, 0x1C)
+#define AIC26_REG_AUDIO_CTRL4 AIC26_PAGE_ADDR(2, 0x1D)
+#define AIC26_REG_AUDIO_CTRL5 AIC26_PAGE_ADDR(2, 0x1E)
+
+/* fsref dividers; used in register 'Audio Control 1' */
+enum aic26_divisors {
+ AIC26_DIV_1 = 0,
+ AIC26_DIV_1_5 = 1,
+ AIC26_DIV_2 = 2,
+ AIC26_DIV_3 = 3,
+ AIC26_DIV_4 = 4,
+ AIC26_DIV_5 = 5,
+ AIC26_DIV_5_5 = 6,
+ AIC26_DIV_6 = 7,
+};
+
+/* Digital data format */
+enum aic26_datfm {
+ AIC26_DATFM_I2S = 0 << 8,
+ AIC26_DATFM_DSP = 1 << 8,
+ AIC26_DATFM_RIGHTJ = 2 << 8, /* right justified */
+ AIC26_DATFM_LEFTJ = 3 << 8, /* left justified */
+};
+
+/* Sample word length in bits; used in register 'Audio Control 1' */
+enum aic26_wlen {
+ AIC26_WLEN_16 = 0 << 10,
+ AIC26_WLEN_20 = 1 << 10,
+ AIC26_WLEN_24 = 2 << 10,
+ AIC26_WLEN_32 = 3 << 10,
+};
+
+extern struct snd_soc_dai aic26_dai;
+extern struct snd_soc_codec_device aic26_soc_codec_dev;
+
+#endif /* _TLV320AIC16_H_ */
diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c
new file mode 100644
index 0000000..cff276e
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic3x.c
@@ -0,0 +1,1346 @@
+/*
+ * ALSA SoC TLV320AIC3X codec driver
+ *
+ * Author: Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * Based on sound/soc/codecs/wm8753.c by Liam Girdwood
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Notes:
+ * The AIC3X is a driver for a low power stereo audio
+ * codecs aic31, aic32, aic33.
+ *
+ * It supports full aic33 codec functionality.
+ * The compatibility with aic32, aic31 is as follows:
+ * aic32 | aic31
+ * ---------------------------------------
+ * MONO_LOUT -> N/A | MONO_LOUT -> N/A
+ * | IN1L -> LINE1L
+ * | IN1R -> LINE1R
+ * | IN2L -> LINE2L
+ * | IN2R -> LINE2R
+ * | MIC3L/R -> N/A
+ * truncated internal functionality in
+ * accordance with documentation
+ * ---------------------------------------
+ *
+ * Hence the machine layer should disable unsupported inputs/outputs by
+ * snd_soc_dapm_disable_pin(codec, "MONO_LOUT"), etc.
+ */
+
+#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 <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 "tlv320aic3x.h"
+
+#define AIC3X_VERSION "0.2"
+
+/* codec private data */
+struct aic3x_priv {
+ unsigned int sysclk;
+ int master;
+};
+
+/*
+ * AIC3X register cache
+ * We can't read the AIC3X register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ * There is no point in caching the reset register
+ */
+static const u8 aic3x_reg[AIC3X_CACHEREGNUM] = {
+ 0x00, 0x00, 0x00, 0x10, /* 0 */
+ 0x04, 0x00, 0x00, 0x00, /* 4 */
+ 0x00, 0x00, 0x00, 0x01, /* 8 */
+ 0x00, 0x00, 0x00, 0x80, /* 12 */
+ 0x80, 0xff, 0xff, 0x78, /* 16 */
+ 0x78, 0x78, 0x78, 0x78, /* 20 */
+ 0x78, 0x00, 0x00, 0xfe, /* 24 */
+ 0x00, 0x00, 0xfe, 0x00, /* 28 */
+ 0x18, 0x18, 0x00, 0x00, /* 32 */
+ 0x00, 0x00, 0x00, 0x00, /* 36 */
+ 0x00, 0x00, 0x00, 0x80, /* 40 */
+ 0x80, 0x00, 0x00, 0x00, /* 44 */
+ 0x00, 0x00, 0x00, 0x04, /* 48 */
+ 0x00, 0x00, 0x00, 0x00, /* 52 */
+ 0x00, 0x00, 0x04, 0x00, /* 56 */
+ 0x00, 0x00, 0x00, 0x00, /* 60 */
+ 0x00, 0x04, 0x00, 0x00, /* 64 */
+ 0x00, 0x00, 0x00, 0x00, /* 68 */
+ 0x04, 0x00, 0x00, 0x00, /* 72 */
+ 0x00, 0x00, 0x00, 0x00, /* 76 */
+ 0x00, 0x00, 0x00, 0x00, /* 80 */
+ 0x00, 0x00, 0x00, 0x00, /* 84 */
+ 0x00, 0x00, 0x00, 0x00, /* 88 */
+ 0x00, 0x00, 0x00, 0x00, /* 92 */
+ 0x00, 0x00, 0x00, 0x00, /* 96 */
+ 0x00, 0x00, 0x02, /* 100 */
+};
+
+/*
+ * read aic3x register cache
+ */
+static inline unsigned int aic3x_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u8 *cache = codec->reg_cache;
+ if (reg >= AIC3X_CACHEREGNUM)
+ return -1;
+ return cache[reg];
+}
+
+/*
+ * write aic3x register cache
+ */
+static inline void aic3x_write_reg_cache(struct snd_soc_codec *codec,
+ u8 reg, u8 value)
+{
+ u8 *cache = codec->reg_cache;
+ if (reg >= AIC3X_CACHEREGNUM)
+ return;
+ cache[reg] = value;
+}
+
+/*
+ * write to the aic3x register space
+ */
+static int aic3x_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ /* data is
+ * D15..D8 aic3x register offset
+ * D7...D0 register data
+ */
+ data[0] = reg & 0xff;
+ data[1] = value & 0xff;
+
+ aic3x_write_reg_cache(codec, data[0], data[1]);
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+/*
+ * read from the aic3x register space
+ */
+static int aic3x_read(struct snd_soc_codec *codec, unsigned int reg,
+ u8 *value)
+{
+ *value = reg & 0xff;
+ if (codec->hw_read(codec->control_data, value, 1) != 1)
+ return -EIO;
+
+ aic3x_write_reg_cache(codec, reg, *value);
+ return 0;
+}
+
+#define SOC_DAPM_SINGLE_AIC3X(xname, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = snd_soc_info_volsw, \
+ .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw_aic3x, \
+ .private_value = SOC_SINGLE_VALUE(reg, shift, mask, invert) }
+
+/*
+ * All input lines are connected when !0xf and disconnected with 0xf bit field,
+ * so we have to use specific dapm_put call for input mixer
+ */
+static int snd_soc_dapm_put_volsw_aic3x(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
+ int reg = kcontrol->private_value & 0xff;
+ int shift = (kcontrol->private_value >> 8) & 0x0f;
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+ int invert = (kcontrol->private_value >> 24) & 0x01;
+ unsigned short val, val_mask;
+ int ret;
+ struct snd_soc_dapm_path *path;
+ int found = 0;
+
+ val = (ucontrol->value.integer.value[0] & mask);
+
+ mask = 0xf;
+ if (val)
+ val = mask;
+
+ if (invert)
+ val = mask - val;
+ val_mask = mask << shift;
+ val = val << shift;
+
+ mutex_lock(&widget->codec->mutex);
+
+ if (snd_soc_test_bits(widget->codec, reg, val_mask, val)) {
+ /* find dapm widget path assoc with kcontrol */
+ list_for_each_entry(path, &widget->codec->dapm_paths, list) {
+ if (path->kcontrol != kcontrol)
+ continue;
+
+ /* found, now check type */
+ found = 1;
+ if (val)
+ /* new connection */
+ path->connect = invert ? 0 : 1;
+ else
+ /* old connection must be powered down */
+ path->connect = invert ? 1 : 0;
+ break;
+ }
+
+ if (found)
+ snd_soc_dapm_sync(widget->codec);
+ }
+
+ ret = snd_soc_update_bits(widget->codec, reg, val_mask, val);
+
+ mutex_unlock(&widget->codec->mutex);
+ return ret;
+}
+
+static const char *aic3x_left_dac_mux[] = { "DAC_L1", "DAC_L3", "DAC_L2" };
+static const char *aic3x_right_dac_mux[] = { "DAC_R1", "DAC_R3", "DAC_R2" };
+static const char *aic3x_left_hpcom_mux[] =
+ { "differential of HPLOUT", "constant VCM", "single-ended" };
+static const char *aic3x_right_hpcom_mux[] =
+ { "differential of HPROUT", "constant VCM", "single-ended",
+ "differential of HPLCOM", "external feedback" };
+static const char *aic3x_linein_mode_mux[] = { "single-ended", "differential" };
+static const char *aic3x_adc_hpf[] =
+ { "Disabled", "0.0045xFs", "0.0125xFs", "0.025xFs" };
+
+#define LDAC_ENUM 0
+#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
+
+static const struct soc_enum aic3x_enum[] = {
+ SOC_ENUM_SINGLE(DAC_LINE_MUX, 6, 3, aic3x_left_dac_mux),
+ SOC_ENUM_SINGLE(DAC_LINE_MUX, 4, 3, aic3x_right_dac_mux),
+ 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(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),
+ SOC_ENUM_DOUBLE(AIC3X_CODEC_DFILT_CTRL, 6, 4, 4, aic3x_adc_hpf),
+};
+
+static const struct snd_kcontrol_new aic3x_snd_controls[] = {
+ /* Output */
+ SOC_DOUBLE_R("PCM Playback Volume", LDAC_VOL, RDAC_VOL, 0, 0x7f, 1),
+
+ SOC_DOUBLE_R("Line DAC Playback Volume", DACL1_2_LLOPM_VOL,
+ DACR1_2_RLOPM_VOL, 0, 0x7f, 1),
+ SOC_DOUBLE_R("Line DAC Playback Switch", LLOPM_CTRL, RLOPM_CTRL, 3,
+ 0x01, 0),
+ SOC_DOUBLE_R("Line PGA Bypass Playback Volume", PGAL_2_LLOPM_VOL,
+ PGAR_2_RLOPM_VOL, 0, 0x7f, 1),
+ SOC_DOUBLE_R("Line Line2 Bypass Playback Volume", LINE2L_2_LLOPM_VOL,
+ LINE2R_2_RLOPM_VOL, 0, 0x7f, 1),
+
+ SOC_DOUBLE_R("Mono DAC Playback Volume", DACL1_2_MONOLOPM_VOL,
+ DACR1_2_MONOLOPM_VOL, 0, 0x7f, 1),
+ SOC_SINGLE("Mono DAC Playback Switch", MONOLOPM_CTRL, 3, 0x01, 0),
+ SOC_DOUBLE_R("Mono PGA Bypass Playback Volume", PGAL_2_MONOLOPM_VOL,
+ PGAR_2_MONOLOPM_VOL, 0, 0x7f, 1),
+ SOC_DOUBLE_R("Mono Line2 Bypass Playback Volume", LINE2L_2_MONOLOPM_VOL,
+ LINE2R_2_MONOLOPM_VOL, 0, 0x7f, 1),
+
+ SOC_DOUBLE_R("HP DAC Playback Volume", DACL1_2_HPLOUT_VOL,
+ DACR1_2_HPROUT_VOL, 0, 0x7f, 1),
+ SOC_DOUBLE_R("HP DAC Playback Switch", HPLOUT_CTRL, HPROUT_CTRL, 3,
+ 0x01, 0),
+ SOC_DOUBLE_R("HP PGA Bypass Playback Volume", PGAL_2_HPLOUT_VOL,
+ PGAR_2_HPROUT_VOL, 0, 0x7f, 1),
+ SOC_DOUBLE_R("HP Line2 Bypass Playback Volume", LINE2L_2_HPLOUT_VOL,
+ LINE2R_2_HPROUT_VOL, 0, 0x7f, 1),
+
+ SOC_DOUBLE_R("HPCOM DAC Playback Volume", DACL1_2_HPLCOM_VOL,
+ DACR1_2_HPRCOM_VOL, 0, 0x7f, 1),
+ SOC_DOUBLE_R("HPCOM DAC Playback Switch", HPLCOM_CTRL, HPRCOM_CTRL, 3,
+ 0x01, 0),
+ SOC_DOUBLE_R("HPCOM PGA Bypass Playback Volume", PGAL_2_HPLCOM_VOL,
+ PGAR_2_HPRCOM_VOL, 0, 0x7f, 1),
+ SOC_DOUBLE_R("HPCOM Line2 Bypass Playback Volume", LINE2L_2_HPLCOM_VOL,
+ LINE2R_2_HPRCOM_VOL, 0, 0x7f, 1),
+
+ /*
+ * Note: enable Automatic input Gain Controller with care. It can
+ * adjust PGA to max value when ADC is on and will never go back.
+ */
+ SOC_DOUBLE_R("AGC Switch", LAGC_CTRL_A, RAGC_CTRL_A, 7, 0x01, 0),
+
+ /* Input */
+ SOC_DOUBLE_R("PGA Capture Volume", LADC_VOL, RADC_VOL, 0, 0x7f, 0),
+ SOC_DOUBLE_R("PGA Capture Switch", LADC_VOL, RADC_VOL, 7, 0x01, 1),
+
+ SOC_ENUM("ADC HPF Cut-off", aic3x_enum[ADC_HPF_ENUM]),
+};
+
+/* add non dapm controls */
+static int aic3x_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(aic3x_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&aic3x_snd_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/* Left DAC Mux */
+static const struct snd_kcontrol_new aic3x_left_dac_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LDAC_ENUM]);
+
+/* Right DAC Mux */
+static const struct snd_kcontrol_new aic3x_right_dac_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[RDAC_ENUM]);
+
+/* Left HPCOM Mux */
+static const struct snd_kcontrol_new aic3x_left_hpcom_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LHPCOM_ENUM]);
+
+/* Right HPCOM Mux */
+static const struct snd_kcontrol_new aic3x_right_hpcom_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[RHPCOM_ENUM]);
+
+/* Left DAC_L1 Mixer */
+static const struct snd_kcontrol_new aic3x_left_dac_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line Switch", DACL1_2_LLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("Mono Switch", DACL1_2_MONOLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HP Switch", DACL1_2_HPLOUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HPCOM Switch", DACL1_2_HPLCOM_VOL, 7, 1, 0),
+};
+
+/* Right DAC_R1 Mixer */
+static const struct snd_kcontrol_new aic3x_right_dac_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line Switch", DACR1_2_RLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("Mono Switch", DACR1_2_MONOLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HP Switch", DACR1_2_HPROUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HPCOM Switch", DACR1_2_HPRCOM_VOL, 7, 1, 0),
+};
+
+/* Left PGA Mixer */
+static const struct snd_kcontrol_new aic3x_left_pga_mixer_controls[] = {
+ SOC_DAPM_SINGLE_AIC3X("Line1L Switch", LINE1L_2_LADC_CTRL, 3, 1, 1),
+ SOC_DAPM_SINGLE_AIC3X("Line2L Switch", LINE2L_2_LADC_CTRL, 3, 1, 1),
+ SOC_DAPM_SINGLE_AIC3X("Mic3L Switch", MIC3LR_2_LADC_CTRL, 4, 1, 1),
+};
+
+/* Right PGA Mixer */
+static const struct snd_kcontrol_new aic3x_right_pga_mixer_controls[] = {
+ SOC_DAPM_SINGLE_AIC3X("Line1R Switch", LINE1R_2_RADC_CTRL, 3, 1, 1),
+ SOC_DAPM_SINGLE_AIC3X("Line2R Switch", LINE2R_2_RADC_CTRL, 3, 1, 1),
+ SOC_DAPM_SINGLE_AIC3X("Mic3R Switch", MIC3LR_2_RADC_CTRL, 0, 1, 1),
+};
+
+/* Left Line1 Mux */
+static const struct snd_kcontrol_new aic3x_left_line1_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LINE1L_ENUM]);
+
+/* Right Line1 Mux */
+static const struct snd_kcontrol_new aic3x_right_line1_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LINE1R_ENUM]);
+
+/* Left Line2 Mux */
+static const struct snd_kcontrol_new aic3x_left_line2_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LINE2L_ENUM]);
+
+/* Right Line2 Mux */
+static const struct snd_kcontrol_new aic3x_right_line2_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LINE2R_ENUM]);
+
+/* Left PGA Bypass Mixer */
+static const struct snd_kcontrol_new aic3x_left_pga_bp_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line Switch", PGAL_2_LLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("Mono Switch", PGAL_2_MONOLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HP Switch", PGAL_2_HPLOUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HPCOM Switch", PGAL_2_HPLCOM_VOL, 7, 1, 0),
+};
+
+/* Right PGA Bypass Mixer */
+static const struct snd_kcontrol_new aic3x_right_pga_bp_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line Switch", PGAR_2_RLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("Mono Switch", PGAR_2_MONOLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HP Switch", PGAR_2_HPROUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HPCOM Switch", PGAR_2_HPRCOM_VOL, 7, 1, 0),
+};
+
+/* Left Line2 Bypass Mixer */
+static const struct snd_kcontrol_new aic3x_left_line2_bp_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line Switch", LINE2L_2_LLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("Mono Switch", LINE2L_2_MONOLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HP Switch", LINE2L_2_HPLOUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HPCOM Switch", LINE2L_2_HPLCOM_VOL, 7, 1, 0),
+};
+
+/* Right Line2 Bypass Mixer */
+static const struct snd_kcontrol_new aic3x_right_line2_bp_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line Switch", LINE2R_2_RLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("Mono Switch", LINE2R_2_MONOLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HP Switch", LINE2R_2_HPROUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HPCOM Switch", LINE2R_2_HPRCOM_VOL, 7, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = {
+ /* Left DAC to Left Outputs */
+ SND_SOC_DAPM_DAC("Left DAC", "Left Playback", DAC_PWR, 7, 0),
+ SND_SOC_DAPM_MUX("Left DAC Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_dac_mux_controls),
+ SND_SOC_DAPM_MIXER("Left DAC_L1 Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_dac_mixer_controls[0],
+ ARRAY_SIZE(aic3x_left_dac_mixer_controls)),
+ SND_SOC_DAPM_MUX("Left HPCOM Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_hpcom_mux_controls),
+ SND_SOC_DAPM_PGA("Left Line Out", LLOPM_CTRL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left HP Out", HPLOUT_CTRL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left HP Com", HPLCOM_CTRL, 0, 0, NULL, 0),
+
+ /* Right DAC to Right Outputs */
+ SND_SOC_DAPM_DAC("Right DAC", "Right Playback", DAC_PWR, 6, 0),
+ SND_SOC_DAPM_MUX("Right DAC Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_dac_mux_controls),
+ SND_SOC_DAPM_MIXER("Right DAC_R1 Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_dac_mixer_controls[0],
+ ARRAY_SIZE(aic3x_right_dac_mixer_controls)),
+ SND_SOC_DAPM_MUX("Right HPCOM Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_hpcom_mux_controls),
+ SND_SOC_DAPM_PGA("Right Line Out", RLOPM_CTRL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right HP Out", HPROUT_CTRL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right HP Com", HPRCOM_CTRL, 0, 0, NULL, 0),
+
+ /* Mono Output */
+ SND_SOC_DAPM_PGA("Mono Out", MONOLOPM_CTRL, 0, 0, NULL, 0),
+
+ /* Left Inputs to Left ADC */
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", LINE1L_2_LADC_CTRL, 2, 0),
+ SND_SOC_DAPM_MIXER("Left PGA Mixer", SND_SOC_NOPM, 0, 0,
+ &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),
+ SND_SOC_DAPM_MUX("Left Line2L Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_line2_mux_controls),
+
+ /* Right Inputs to Right ADC */
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture",
+ LINE1R_2_RADC_CTRL, 2, 0),
+ SND_SOC_DAPM_MIXER("Right PGA Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_pga_mixer_controls[0],
+ ARRAY_SIZE(aic3x_right_pga_mixer_controls)),
+ SND_SOC_DAPM_MUX("Right Line1R Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_line1_mux_controls),
+ SND_SOC_DAPM_MUX("Right Line2R Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_line2_mux_controls),
+
+ /*
+ * Not a real mic bias widget but similar function. This is for dynamic
+ * control of GPIO1 digital mic modulator clock output function when
+ * using digital mic.
+ */
+ SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "GPIO1 dmic modclk",
+ AIC3X_GPIO1_REG, 4, 0xf,
+ AIC3X_GPIO1_FUNC_DIGITAL_MIC_MODCLK,
+ AIC3X_GPIO1_FUNC_DISABLED),
+
+ /*
+ * Also similar function like mic bias. Selects digital mic with
+ * configurable oversampling rate instead of ADC converter.
+ */
+ SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 128",
+ AIC3X_ASD_INTF_CTRLA, 0, 3, 1, 0),
+ SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 64",
+ AIC3X_ASD_INTF_CTRLA, 0, 3, 2, 0),
+ SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 32",
+ AIC3X_ASD_INTF_CTRLA, 0, 3, 3, 0),
+
+ /* Mic Bias */
+ SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias 2V",
+ MICBIAS_CTRL, 6, 3, 1, 0),
+ SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias 2.5V",
+ MICBIAS_CTRL, 6, 3, 2, 0),
+ SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias AVDD",
+ MICBIAS_CTRL, 6, 3, 3, 0),
+
+ /* Left PGA to Left Output bypass */
+ SND_SOC_DAPM_MIXER("Left PGA Bypass Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_pga_bp_mixer_controls[0],
+ ARRAY_SIZE(aic3x_left_pga_bp_mixer_controls)),
+
+ /* Right PGA to Right Output bypass */
+ SND_SOC_DAPM_MIXER("Right PGA Bypass Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_pga_bp_mixer_controls[0],
+ ARRAY_SIZE(aic3x_right_pga_bp_mixer_controls)),
+
+ /* Left Line2 to Left Output bypass */
+ SND_SOC_DAPM_MIXER("Left Line2 Bypass Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_line2_bp_mixer_controls[0],
+ ARRAY_SIZE(aic3x_left_line2_bp_mixer_controls)),
+
+ /* Right Line2 to Right Output bypass */
+ SND_SOC_DAPM_MIXER("Right Line2 Bypass Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_line2_bp_mixer_controls[0],
+ ARRAY_SIZE(aic3x_right_line2_bp_mixer_controls)),
+
+ SND_SOC_DAPM_OUTPUT("LLOUT"),
+ SND_SOC_DAPM_OUTPUT("RLOUT"),
+ SND_SOC_DAPM_OUTPUT("MONO_LOUT"),
+ SND_SOC_DAPM_OUTPUT("HPLOUT"),
+ SND_SOC_DAPM_OUTPUT("HPROUT"),
+ SND_SOC_DAPM_OUTPUT("HPLCOM"),
+ SND_SOC_DAPM_OUTPUT("HPRCOM"),
+
+ SND_SOC_DAPM_INPUT("MIC3L"),
+ SND_SOC_DAPM_INPUT("MIC3R"),
+ SND_SOC_DAPM_INPUT("LINE1L"),
+ SND_SOC_DAPM_INPUT("LINE1R"),
+ SND_SOC_DAPM_INPUT("LINE2L"),
+ SND_SOC_DAPM_INPUT("LINE2R"),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ /* Left Output */
+ {"Left DAC Mux", "DAC_L1", "Left DAC"},
+ {"Left DAC Mux", "DAC_L2", "Left DAC"},
+ {"Left DAC Mux", "DAC_L3", "Left DAC"},
+
+ {"Left DAC_L1 Mixer", "Line Switch", "Left DAC Mux"},
+ {"Left DAC_L1 Mixer", "Mono Switch", "Left DAC Mux"},
+ {"Left DAC_L1 Mixer", "HP Switch", "Left DAC Mux"},
+ {"Left DAC_L1 Mixer", "HPCOM Switch", "Left DAC Mux"},
+ {"Left Line Out", NULL, "Left DAC Mux"},
+ {"Left HP Out", NULL, "Left DAC Mux"},
+
+ {"Left HPCOM Mux", "differential of HPLOUT", "Left DAC_L1 Mixer"},
+ {"Left HPCOM Mux", "constant VCM", "Left DAC_L1 Mixer"},
+ {"Left HPCOM Mux", "single-ended", "Left DAC_L1 Mixer"},
+
+ {"Left Line Out", NULL, "Left DAC_L1 Mixer"},
+ {"Mono Out", NULL, "Left DAC_L1 Mixer"},
+ {"Left HP Out", NULL, "Left DAC_L1 Mixer"},
+ {"Left HP Com", NULL, "Left HPCOM Mux"},
+
+ {"LLOUT", NULL, "Left Line Out"},
+ {"LLOUT", NULL, "Left Line Out"},
+ {"HPLOUT", NULL, "Left HP Out"},
+ {"HPLCOM", NULL, "Left HP Com"},
+
+ /* Right Output */
+ {"Right DAC Mux", "DAC_R1", "Right DAC"},
+ {"Right DAC Mux", "DAC_R2", "Right DAC"},
+ {"Right DAC Mux", "DAC_R3", "Right DAC"},
+
+ {"Right DAC_R1 Mixer", "Line Switch", "Right DAC Mux"},
+ {"Right DAC_R1 Mixer", "Mono Switch", "Right DAC Mux"},
+ {"Right DAC_R1 Mixer", "HP Switch", "Right DAC Mux"},
+ {"Right DAC_R1 Mixer", "HPCOM Switch", "Right DAC Mux"},
+ {"Right Line Out", NULL, "Right DAC Mux"},
+ {"Right HP Out", NULL, "Right DAC Mux"},
+
+ {"Right HPCOM Mux", "differential of HPROUT", "Right DAC_R1 Mixer"},
+ {"Right HPCOM Mux", "constant VCM", "Right DAC_R1 Mixer"},
+ {"Right HPCOM Mux", "single-ended", "Right DAC_R1 Mixer"},
+ {"Right HPCOM Mux", "differential of HPLCOM", "Right DAC_R1 Mixer"},
+ {"Right HPCOM Mux", "external feedback", "Right DAC_R1 Mixer"},
+
+ {"Right Line Out", NULL, "Right DAC_R1 Mixer"},
+ {"Mono Out", NULL, "Right DAC_R1 Mixer"},
+ {"Right HP Out", NULL, "Right DAC_R1 Mixer"},
+ {"Right HP Com", NULL, "Right HPCOM Mux"},
+
+ {"RLOUT", NULL, "Right Line Out"},
+ {"RLOUT", NULL, "Right Line Out"},
+ {"HPROUT", NULL, "Right HP Out"},
+ {"HPRCOM", NULL, "Right HP Com"},
+
+ /* Mono Output */
+ {"MONO_LOUT", NULL, "Mono Out"},
+ {"MONO_LOUT", NULL, "Mono Out"},
+
+ /* Left Input */
+ {"Left Line1L Mux", "single-ended", "LINE1L"},
+ {"Left Line1L Mux", "differential", "LINE1L"},
+
+ {"Left Line2L Mux", "single-ended", "LINE2L"},
+ {"Left Line2L Mux", "differential", "LINE2L"},
+
+ {"Left PGA Mixer", "Line1L Switch", "Left Line1L Mux"},
+ {"Left PGA Mixer", "Line2L Switch", "Left Line2L Mux"},
+ {"Left PGA Mixer", "Mic3L Switch", "MIC3L"},
+
+ {"Left ADC", NULL, "Left PGA Mixer"},
+ {"Left ADC", NULL, "GPIO1 dmic modclk"},
+
+ /* Right Input */
+ {"Right Line1R Mux", "single-ended", "LINE1R"},
+ {"Right Line1R Mux", "differential", "LINE1R"},
+
+ {"Right Line2R Mux", "single-ended", "LINE2R"},
+ {"Right Line2R Mux", "differential", "LINE2R"},
+
+ {"Right PGA Mixer", "Line1R Switch", "Right Line1R Mux"},
+ {"Right PGA Mixer", "Line2R Switch", "Right Line2R Mux"},
+ {"Right PGA Mixer", "Mic3R Switch", "MIC3R"},
+
+ {"Right ADC", NULL, "Right PGA Mixer"},
+ {"Right ADC", NULL, "GPIO1 dmic modclk"},
+
+ /* Left PGA Bypass */
+ {"Left PGA Bypass Mixer", "Line Switch", "Left PGA Mixer"},
+ {"Left PGA Bypass Mixer", "Mono Switch", "Left PGA Mixer"},
+ {"Left PGA Bypass Mixer", "HP Switch", "Left PGA Mixer"},
+ {"Left PGA Bypass Mixer", "HPCOM Switch", "Left PGA Mixer"},
+
+ {"Left HPCOM Mux", "differential of HPLOUT", "Left PGA Bypass Mixer"},
+ {"Left HPCOM Mux", "constant VCM", "Left PGA Bypass Mixer"},
+ {"Left HPCOM Mux", "single-ended", "Left PGA Bypass Mixer"},
+
+ {"Left Line Out", NULL, "Left PGA Bypass Mixer"},
+ {"Mono Out", NULL, "Left PGA Bypass Mixer"},
+ {"Left HP Out", NULL, "Left PGA Bypass Mixer"},
+
+ /* Right PGA Bypass */
+ {"Right PGA Bypass Mixer", "Line Switch", "Right PGA Mixer"},
+ {"Right PGA Bypass Mixer", "Mono Switch", "Right PGA Mixer"},
+ {"Right PGA Bypass Mixer", "HP Switch", "Right PGA Mixer"},
+ {"Right PGA Bypass Mixer", "HPCOM Switch", "Right PGA Mixer"},
+
+ {"Right HPCOM Mux", "differential of HPROUT", "Right PGA Bypass Mixer"},
+ {"Right HPCOM Mux", "constant VCM", "Right PGA Bypass Mixer"},
+ {"Right HPCOM Mux", "single-ended", "Right PGA Bypass Mixer"},
+ {"Right HPCOM Mux", "differential of HPLCOM", "Right PGA Bypass Mixer"},
+ {"Right HPCOM Mux", "external feedback", "Right PGA Bypass Mixer"},
+
+ {"Right Line Out", NULL, "Right PGA Bypass Mixer"},
+ {"Mono Out", NULL, "Right PGA Bypass Mixer"},
+ {"Right HP Out", NULL, "Right PGA Bypass Mixer"},
+
+ /* Left Line2 Bypass */
+ {"Left Line2 Bypass Mixer", "Line Switch", "Left Line2L Mux"},
+ {"Left Line2 Bypass Mixer", "Mono Switch", "Left Line2L Mux"},
+ {"Left Line2 Bypass Mixer", "HP Switch", "Left Line2L Mux"},
+ {"Left Line2 Bypass Mixer", "HPCOM Switch", "Left Line2L Mux"},
+
+ {"Left HPCOM Mux", "differential of HPLOUT", "Left Line2 Bypass Mixer"},
+ {"Left HPCOM Mux", "constant VCM", "Left Line2 Bypass Mixer"},
+ {"Left HPCOM Mux", "single-ended", "Left Line2 Bypass Mixer"},
+
+ {"Left Line Out", NULL, "Left Line2 Bypass Mixer"},
+ {"Mono Out", NULL, "Left Line2 Bypass Mixer"},
+ {"Left HP Out", NULL, "Left Line2 Bypass Mixer"},
+
+ /* Right Line2 Bypass */
+ {"Right Line2 Bypass Mixer", "Line Switch", "Right Line2R Mux"},
+ {"Right Line2 Bypass Mixer", "Mono Switch", "Right Line2R Mux"},
+ {"Right Line2 Bypass Mixer", "HP Switch", "Right Line2R Mux"},
+ {"Right Line2 Bypass Mixer", "HPCOM Switch", "Right Line2R Mux"},
+
+ {"Right HPCOM Mux", "differential of HPROUT", "Right Line2 Bypass Mixer"},
+ {"Right HPCOM Mux", "constant VCM", "Right Line2 Bypass Mixer"},
+ {"Right HPCOM Mux", "single-ended", "Right Line2 Bypass Mixer"},
+ {"Right HPCOM Mux", "differential of HPLCOM", "Right Line2 Bypass Mixer"},
+ {"Right HPCOM Mux", "external feedback", "Right Line2 Bypass Mixer"},
+
+ {"Right Line Out", NULL, "Right Line2 Bypass Mixer"},
+ {"Mono Out", NULL, "Right Line2 Bypass Mixer"},
+ {"Right HP Out", NULL, "Right Line2 Bypass Mixer"},
+
+ /*
+ * Logical path between digital mic enable and GPIO1 modulator clock
+ * output function
+ */
+ {"GPIO1 dmic modclk", NULL, "DMic Rate 128"},
+ {"GPIO1 dmic modclk", NULL, "DMic Rate 64"},
+ {"GPIO1 dmic modclk", NULL, "DMic Rate 32"},
+};
+
+static int aic3x_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, aic3x_dapm_widgets,
+ ARRAY_SIZE(aic3x_dapm_widgets));
+
+ /* set up audio path interconnects */
+ snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+static int aic3x_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct aic3x_priv *aic3x = codec->private_data;
+ int codec_clk = 0, bypass_pll = 0, fsref, last_clk = 0;
+ u8 data, r, p, pll_q, pll_p = 1, pll_r = 1, pll_j = 1;
+ u16 pll_d = 1;
+
+ /* select data word length */
+ data =
+ aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLB) & (~(0x3 << 4));
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ data |= (0x01 << 4);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ data |= (0x02 << 4);
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ data |= (0x03 << 4);
+ break;
+ }
+ aic3x_write(codec, AIC3X_ASD_INTF_CTRLB, data);
+
+ /* Fsref can be 44100 or 48000 */
+ fsref = (params_rate(params) % 11025 == 0) ? 44100 : 48000;
+
+ /* Try to find a value for Q which allows us to bypass the PLL and
+ * generate CODEC_CLK directly. */
+ for (pll_q = 2; pll_q < 18; pll_q++)
+ if (aic3x->sysclk / (128 * pll_q) == fsref) {
+ bypass_pll = 1;
+ break;
+ }
+
+ if (bypass_pll) {
+ pll_q &= 0xf;
+ aic3x_write(codec, AIC3X_PLL_PROGA_REG, pll_q << PLLQ_SHIFT);
+ aic3x_write(codec, AIC3X_GPIOB_REG, CODEC_CLKIN_CLKDIV);
+ } else
+ aic3x_write(codec, AIC3X_GPIOB_REG, CODEC_CLKIN_PLLDIV);
+
+ /* Route Left DAC to left channel input and
+ * right DAC to right channel input */
+ data = (LDAC2LCH | RDAC2RCH);
+ data |= (fsref == 44100) ? FSREF_44100 : FSREF_48000;
+ if (params_rate(params) >= 64000)
+ data |= DUAL_RATE_MODE;
+ aic3x_write(codec, AIC3X_CODEC_DATAPATH_REG, data);
+
+ /* codec sample rate select */
+ data = (fsref * 20) / params_rate(params);
+ if (params_rate(params) < 64000)
+ data /= 2;
+ data /= 5;
+ data -= 2;
+ data |= (data << 4);
+ aic3x_write(codec, AIC3X_SAMPLE_RATE_SEL_REG, data);
+
+ if (bypass_pll)
+ return 0;
+
+ /* Use PLL
+ * find an apropriate setup for j, d, r and p by iterating over
+ * p and r - j and d are calculated for each fraction.
+ * Up to 128 values are probed, the closest one wins the game.
+ * The sysclk is divided by 1000 to prevent integer overflows.
+ */
+ codec_clk = (2048 * fsref) / (aic3x->sysclk / 1000);
+
+ for (r = 1; r <= 16; r++)
+ for (p = 1; p <= 8; p++) {
+ int clk, tmp = (codec_clk * pll_r * 10) / pll_p;
+ u8 j = tmp / 10000;
+ u16 d = tmp % 10000;
+
+ if (j > 63)
+ continue;
+
+ if (d != 0 && aic3x->sysclk < 10000000)
+ continue;
+
+ /* This is actually 1000 * ((j + (d/10000)) * r) / p
+ * The term had to be converted to get rid of the
+ * division by 10000 */
+ clk = ((10000 * j * r) + (d * r)) / (10 * p);
+
+ /* check whether this values get closer than the best
+ * ones we had before */
+ if (abs(codec_clk - clk) < abs(codec_clk - last_clk)) {
+ pll_j = j; pll_d = d; pll_r = r; pll_p = p;
+ last_clk = clk;
+ }
+
+ /* Early exit for exact matches */
+ if (clk == codec_clk)
+ break;
+ }
+
+ if (last_clk == 0) {
+ printk(KERN_ERR "%s(): unable to setup PLL\n", __func__);
+ return -EINVAL;
+ }
+
+ data = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG);
+ aic3x_write(codec, AIC3X_PLL_PROGA_REG, data | (pll_p << PLLP_SHIFT));
+ aic3x_write(codec, AIC3X_OVRF_STATUS_AND_PLLR_REG, pll_r << PLLR_SHIFT);
+ aic3x_write(codec, AIC3X_PLL_PROGB_REG, pll_j << PLLJ_SHIFT);
+ aic3x_write(codec, AIC3X_PLL_PROGC_REG, (pll_d >> 6) << PLLD_MSB_SHIFT);
+ aic3x_write(codec, AIC3X_PLL_PROGD_REG,
+ (pll_d & 0x3F) << PLLD_LSB_SHIFT);
+
+ return 0;
+}
+
+static int aic3x_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u8 ldac_reg = aic3x_read_reg_cache(codec, LDAC_VOL) & ~MUTE_ON;
+ u8 rdac_reg = aic3x_read_reg_cache(codec, RDAC_VOL) & ~MUTE_ON;
+
+ if (mute) {
+ aic3x_write(codec, LDAC_VOL, ldac_reg | MUTE_ON);
+ aic3x_write(codec, RDAC_VOL, rdac_reg | MUTE_ON);
+ } else {
+ aic3x_write(codec, LDAC_VOL, ldac_reg);
+ aic3x_write(codec, RDAC_VOL, rdac_reg);
+ }
+
+ return 0;
+}
+
+static int aic3x_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 aic3x_priv *aic3x = codec->private_data;
+
+ aic3x->sysclk = freq;
+ return 0;
+}
+
+static int aic3x_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct aic3x_priv *aic3x = codec->private_data;
+ u8 iface_areg, iface_breg;
+
+ iface_areg = aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLA) & 0x3f;
+ iface_breg = aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLB) & 0x3f;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ aic3x->master = 1;
+ iface_areg |= BIT_CLK_MASTER | WORD_CLK_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ aic3x->master = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * match both interface format and signal polarities since they
+ * are fixed
+ */
+ switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK |
+ SND_SOC_DAIFMT_INV_MASK)) {
+ case (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF):
+ break;
+ case (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF):
+ iface_breg |= (0x01 << 6);
+ break;
+ case (SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_NB_NF):
+ iface_breg |= (0x02 << 6);
+ break;
+ case (SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF):
+ iface_breg |= (0x03 << 6);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set iface */
+ aic3x_write(codec, AIC3X_ASD_INTF_CTRLA, iface_areg);
+ aic3x_write(codec, AIC3X_ASD_INTF_CTRLB, iface_breg);
+
+ return 0;
+}
+
+static int aic3x_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct aic3x_priv *aic3x = codec->private_data;
+ u8 reg;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* all power is driven by DAPM system */
+ if (aic3x->master) {
+ /* enable pll */
+ reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG);
+ aic3x_write(codec, AIC3X_PLL_PROGA_REG,
+ reg | PLL_ENABLE);
+ }
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /*
+ * all power is driven by DAPM system,
+ * so output power is safe if bypass was set
+ */
+ if (aic3x->master) {
+ /* disable pll */
+ reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG);
+ aic3x_write(codec, AIC3X_PLL_PROGA_REG,
+ reg & ~PLL_ENABLE);
+ }
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* force all power off */
+ reg = aic3x_read_reg_cache(codec, LINE1L_2_LADC_CTRL);
+ aic3x_write(codec, LINE1L_2_LADC_CTRL, reg & ~LADC_PWR_ON);
+ reg = aic3x_read_reg_cache(codec, LINE1R_2_RADC_CTRL);
+ aic3x_write(codec, LINE1R_2_RADC_CTRL, reg & ~RADC_PWR_ON);
+
+ reg = aic3x_read_reg_cache(codec, DAC_PWR);
+ aic3x_write(codec, DAC_PWR, reg & ~(LDAC_PWR_ON | RDAC_PWR_ON));
+
+ reg = aic3x_read_reg_cache(codec, HPLOUT_CTRL);
+ aic3x_write(codec, HPLOUT_CTRL, reg & ~HPLOUT_PWR_ON);
+ reg = aic3x_read_reg_cache(codec, HPROUT_CTRL);
+ aic3x_write(codec, HPROUT_CTRL, reg & ~HPROUT_PWR_ON);
+
+ reg = aic3x_read_reg_cache(codec, HPLCOM_CTRL);
+ aic3x_write(codec, HPLCOM_CTRL, reg & ~HPLCOM_PWR_ON);
+ reg = aic3x_read_reg_cache(codec, HPRCOM_CTRL);
+ aic3x_write(codec, HPRCOM_CTRL, reg & ~HPRCOM_PWR_ON);
+
+ reg = aic3x_read_reg_cache(codec, MONOLOPM_CTRL);
+ aic3x_write(codec, MONOLOPM_CTRL, reg & ~MONOLOPM_PWR_ON);
+
+ reg = aic3x_read_reg_cache(codec, LLOPM_CTRL);
+ aic3x_write(codec, LLOPM_CTRL, reg & ~LLOPM_PWR_ON);
+ reg = aic3x_read_reg_cache(codec, RLOPM_CTRL);
+ aic3x_write(codec, RLOPM_CTRL, reg & ~RLOPM_PWR_ON);
+
+ if (aic3x->master) {
+ /* disable pll */
+ reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG);
+ aic3x_write(codec, AIC3X_PLL_PROGA_REG,
+ reg & ~PLL_ENABLE);
+ }
+ break;
+ }
+ codec->bias_level = level;
+
+ return 0;
+}
+
+void aic3x_set_gpio(struct snd_soc_codec *codec, int gpio, int state)
+{
+ u8 reg = gpio ? AIC3X_GPIO2_REG : AIC3X_GPIO1_REG;
+ u8 bit = gpio ? 3: 0;
+ u8 val = aic3x_read_reg_cache(codec, reg) & ~(1 << bit);
+ aic3x_write(codec, reg, val | (!!state << bit));
+}
+EXPORT_SYMBOL_GPL(aic3x_set_gpio);
+
+int aic3x_get_gpio(struct snd_soc_codec *codec, int gpio)
+{
+ u8 reg = gpio ? AIC3X_GPIO2_REG : AIC3X_GPIO1_REG;
+ u8 val, bit = gpio ? 2: 1;
+
+ aic3x_read(codec, reg, &val);
+ return (val >> bit) & 1;
+}
+EXPORT_SYMBOL_GPL(aic3x_get_gpio);
+
+int aic3x_headset_detected(struct snd_soc_codec *codec)
+{
+ u8 val;
+ aic3x_read(codec, AIC3X_RT_IRQ_FLAGS_REG, &val);
+ return (val >> 2) & 1;
+}
+EXPORT_SYMBOL_GPL(aic3x_headset_detected);
+
+#define AIC3X_RATES SNDRV_PCM_RATE_8000_96000
+#define AIC3X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+struct snd_soc_dai aic3x_dai = {
+ .name = "tlv320aic3x",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AIC3X_RATES,
+ .formats = AIC3X_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AIC3X_RATES,
+ .formats = AIC3X_FORMATS,},
+ .ops = {
+ .hw_params = aic3x_hw_params,
+ },
+ .dai_ops = {
+ .digital_mute = aic3x_mute,
+ .set_sysclk = aic3x_set_dai_sysclk,
+ .set_fmt = aic3x_set_dai_fmt,
+ }
+};
+EXPORT_SYMBOL_GPL(aic3x_dai);
+
+static int aic3x_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ aic3x_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int aic3x_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ int i;
+ u8 data[2];
+ u8 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(aic3x_reg); i++) {
+ data[0] = i;
+ data[1] = cache[i];
+ codec->hw_write(codec->control_data, data, 2);
+ }
+
+ aic3x_set_bias_level(codec, codec->suspend_bias_level);
+
+ return 0;
+}
+
+/*
+ * initialise the AIC3X driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int aic3x_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ struct aic3x_setup_data *setup = socdev->codec_data;
+ int reg, ret = 0;
+
+ codec->name = "tlv320aic3x";
+ codec->owner = THIS_MODULE;
+ codec->read = aic3x_read_reg_cache;
+ codec->write = aic3x_write;
+ codec->set_bias_level = aic3x_set_bias_level;
+ codec->dai = &aic3x_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = ARRAY_SIZE(aic3x_reg);
+ codec->reg_cache = kmemdup(aic3x_reg, sizeof(aic3x_reg), GFP_KERNEL);
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ aic3x_write(codec, AIC3X_PAGE_SELECT, PAGE0_SELECT);
+ aic3x_write(codec, AIC3X_RESET, SOFT_RESET);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "aic3x: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* DAC default volume and mute */
+ aic3x_write(codec, LDAC_VOL, DEFAULT_VOL | MUTE_ON);
+ aic3x_write(codec, RDAC_VOL, DEFAULT_VOL | MUTE_ON);
+
+ /* DAC to HP default volume and route to Output mixer */
+ aic3x_write(codec, DACL1_2_HPLOUT_VOL, DEFAULT_VOL | ROUTE_ON);
+ aic3x_write(codec, DACR1_2_HPROUT_VOL, DEFAULT_VOL | ROUTE_ON);
+ aic3x_write(codec, DACL1_2_HPLCOM_VOL, DEFAULT_VOL | ROUTE_ON);
+ aic3x_write(codec, DACR1_2_HPRCOM_VOL, DEFAULT_VOL | ROUTE_ON);
+ /* DAC to Line Out default volume and route to Output mixer */
+ aic3x_write(codec, DACL1_2_LLOPM_VOL, DEFAULT_VOL | ROUTE_ON);
+ aic3x_write(codec, DACR1_2_RLOPM_VOL, DEFAULT_VOL | ROUTE_ON);
+ /* DAC to Mono Line Out default volume and route to Output mixer */
+ aic3x_write(codec, DACL1_2_MONOLOPM_VOL, DEFAULT_VOL | ROUTE_ON);
+ aic3x_write(codec, DACR1_2_MONOLOPM_VOL, DEFAULT_VOL | ROUTE_ON);
+
+ /* unmute all outputs */
+ reg = aic3x_read_reg_cache(codec, LLOPM_CTRL);
+ aic3x_write(codec, LLOPM_CTRL, reg | UNMUTE);
+ reg = aic3x_read_reg_cache(codec, RLOPM_CTRL);
+ aic3x_write(codec, RLOPM_CTRL, reg | UNMUTE);
+ reg = aic3x_read_reg_cache(codec, MONOLOPM_CTRL);
+ aic3x_write(codec, MONOLOPM_CTRL, reg | UNMUTE);
+ reg = aic3x_read_reg_cache(codec, HPLOUT_CTRL);
+ aic3x_write(codec, HPLOUT_CTRL, reg | UNMUTE);
+ reg = aic3x_read_reg_cache(codec, HPROUT_CTRL);
+ aic3x_write(codec, HPROUT_CTRL, reg | UNMUTE);
+ reg = aic3x_read_reg_cache(codec, HPLCOM_CTRL);
+ aic3x_write(codec, HPLCOM_CTRL, reg | UNMUTE);
+ reg = aic3x_read_reg_cache(codec, HPRCOM_CTRL);
+ aic3x_write(codec, HPRCOM_CTRL, reg | UNMUTE);
+
+ /* ADC default volume and unmute */
+ aic3x_write(codec, LADC_VOL, DEFAULT_GAIN);
+ aic3x_write(codec, RADC_VOL, DEFAULT_GAIN);
+ /* By default route Line1 to ADC PGA mixer */
+ aic3x_write(codec, LINE1L_2_LADC_CTRL, 0x0);
+ aic3x_write(codec, LINE1R_2_RADC_CTRL, 0x0);
+
+ /* PGA to HP Bypass default volume, disconnect from Output Mixer */
+ aic3x_write(codec, PGAL_2_HPLOUT_VOL, DEFAULT_VOL);
+ aic3x_write(codec, PGAR_2_HPROUT_VOL, DEFAULT_VOL);
+ aic3x_write(codec, PGAL_2_HPLCOM_VOL, DEFAULT_VOL);
+ aic3x_write(codec, PGAR_2_HPRCOM_VOL, DEFAULT_VOL);
+ /* PGA to Line Out default volume, disconnect from Output Mixer */
+ aic3x_write(codec, PGAL_2_LLOPM_VOL, DEFAULT_VOL);
+ aic3x_write(codec, PGAR_2_RLOPM_VOL, DEFAULT_VOL);
+ /* PGA to Mono Line Out default volume, disconnect from Output Mixer */
+ aic3x_write(codec, PGAL_2_MONOLOPM_VOL, DEFAULT_VOL);
+ aic3x_write(codec, PGAR_2_MONOLOPM_VOL, DEFAULT_VOL);
+
+ /* Line2 to HP Bypass default volume, disconnect from Output Mixer */
+ aic3x_write(codec, LINE2L_2_HPLOUT_VOL, DEFAULT_VOL);
+ aic3x_write(codec, LINE2R_2_HPROUT_VOL, DEFAULT_VOL);
+ aic3x_write(codec, LINE2L_2_HPLCOM_VOL, DEFAULT_VOL);
+ aic3x_write(codec, LINE2R_2_HPRCOM_VOL, DEFAULT_VOL);
+ /* Line2 Line Out default volume, disconnect from Output Mixer */
+ aic3x_write(codec, LINE2L_2_LLOPM_VOL, DEFAULT_VOL);
+ aic3x_write(codec, LINE2R_2_RLOPM_VOL, DEFAULT_VOL);
+ /* Line2 to Mono Out default volume, disconnect from Output Mixer */
+ aic3x_write(codec, LINE2L_2_MONOLOPM_VOL, DEFAULT_VOL);
+ aic3x_write(codec, LINE2R_2_MONOLOPM_VOL, DEFAULT_VOL);
+
+ /* off, with power on */
+ aic3x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* setup GPIO functions */
+ aic3x_write(codec, AIC3X_GPIO1_REG, (setup->gpio_func[0] & 0xf) << 4);
+ aic3x_write(codec, AIC3X_GPIO2_REG, (setup->gpio_func[1] & 0xf) << 4);
+
+ aic3x_add_controls(codec);
+ aic3x_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "aic3x: failed to register card\n");
+ goto card_err;
+ }
+
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+ return ret;
+}
+
+static struct snd_soc_device *aic3x_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+/*
+ * AIC3X 2 wire address can be up to 4 devices with device addresses
+ * 0x18, 0x19, 0x1A, 0x1B
+ */
+
+/*
+ * If the i2c layer weren't so broken, we could pass this kind of data
+ * around
+ */
+static int aic3x_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = aic3x_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = aic3x_init(socdev);
+ if (ret < 0)
+ printk(KERN_ERR "aic3x: failed to initialise AIC3X\n");
+ return ret;
+}
+
+static int aic3x_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+static const struct i2c_device_id aic3x_i2c_id[] = {
+ { "tlv320aic3x", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, aic3x_i2c_id);
+
+/* machine i2c codec control layer */
+static struct i2c_driver aic3x_i2c_driver = {
+ .driver = {
+ .name = "aic3x I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = aic3x_i2c_probe,
+ .remove = aic3x_i2c_remove,
+ .id_table = aic3x_i2c_id,
+};
+
+static int aic3x_i2c_read(struct i2c_client *client, u8 *value, int len)
+{
+ value[0] = i2c_smbus_read_byte_data(client, value[0]);
+ return (len == 1);
+}
+
+static int aic3x_add_i2c_device(struct platform_device *pdev,
+ const struct aic3x_setup_data *setup)
+{
+ struct i2c_board_info info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+ int ret;
+
+ ret = i2c_add_driver(&aic3x_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ return ret;
+ }
+
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ info.addr = setup->i2c_address;
+ strlcpy(info.type, "tlv320aic3x", I2C_NAME_SIZE);
+
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "can't get i2c adapter %d\n",
+ setup->i2c_bus);
+ goto err_driver;
+ }
+
+ client = i2c_new_device(adapter, &info);
+ i2c_put_adapter(adapter);
+ if (!client) {
+ dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
+ (unsigned int)info.addr);
+ goto err_driver;
+ }
+
+ return 0;
+
+err_driver:
+ i2c_del_driver(&aic3x_i2c_driver);
+ return -ENODEV;
+}
+#endif
+
+static int aic3x_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct aic3x_setup_data *setup;
+ struct snd_soc_codec *codec;
+ struct aic3x_priv *aic3x;
+ int ret = 0;
+
+ printk(KERN_INFO "AIC3X Audio Codec %s\n", AIC3X_VERSION);
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ aic3x = kzalloc(sizeof(struct aic3x_priv), GFP_KERNEL);
+ if (aic3x == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->private_data = aic3x;
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ aic3x_socdev = socdev;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ codec->hw_write = (hw_write_t) i2c_master_send;
+ codec->hw_read = (hw_read_t) aic3x_i2c_read;
+ ret = aic3x_add_i2c_device(pdev, setup);
+ }
+#else
+ /* Add other interfaces here */
+#endif
+
+ if (ret != 0) {
+ kfree(codec->private_data);
+ kfree(codec);
+ }
+ return ret;
+}
+
+static int aic3x_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ /* power down chip */
+ if (codec->control_data)
+ aic3x_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_unregister_device(codec->control_data);
+ i2c_del_driver(&aic3x_i2c_driver);
+#endif
+ kfree(codec->private_data);
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_aic3x = {
+ .probe = aic3x_probe,
+ .remove = aic3x_remove,
+ .suspend = aic3x_suspend,
+ .resume = aic3x_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_aic3x);
+
+MODULE_DESCRIPTION("ASoC TLV320AIC3X codec driver");
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/tlv320aic3x.h b/sound/soc/codecs/tlv320aic3x.h
new file mode 100644
index 0000000..00a195a
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic3x.h
@@ -0,0 +1,235 @@
+/*
+ * ALSA SoC TLV320AIC3X codec driver
+ *
+ * Author: Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _AIC3X_H
+#define _AIC3X_H
+
+/* AIC3X register space */
+#define AIC3X_CACHEREGNUM 103
+
+/* Page select register */
+#define AIC3X_PAGE_SELECT 0
+/* Software reset register */
+#define AIC3X_RESET 1
+/* Codec Sample rate select register */
+#define AIC3X_SAMPLE_RATE_SEL_REG 2
+/* PLL progrramming register A */
+#define AIC3X_PLL_PROGA_REG 3
+/* PLL progrramming register B */
+#define AIC3X_PLL_PROGB_REG 4
+/* PLL progrramming register C */
+#define AIC3X_PLL_PROGC_REG 5
+/* PLL progrramming register D */
+#define AIC3X_PLL_PROGD_REG 6
+/* Codec datapath setup register */
+#define AIC3X_CODEC_DATAPATH_REG 7
+/* Audio serial data interface control register A */
+#define AIC3X_ASD_INTF_CTRLA 8
+/* Audio serial data interface control register B */
+#define AIC3X_ASD_INTF_CTRLB 9
+/* Audio overflow status and PLL R value programming register */
+#define AIC3X_OVRF_STATUS_AND_PLLR_REG 11
+/* Audio codec digital filter control register */
+#define AIC3X_CODEC_DFILT_CTRL 12
+
+/* ADC PGA Gain control registers */
+#define LADC_VOL 15
+#define RADC_VOL 16
+/* MIC3 control registers */
+#define MIC3LR_2_LADC_CTRL 17
+#define MIC3LR_2_RADC_CTRL 18
+/* Line1 Input control registers */
+#define LINE1L_2_LADC_CTRL 19
+#define LINE1R_2_RADC_CTRL 22
+/* Line2 Input control registers */
+#define LINE2L_2_LADC_CTRL 20
+#define LINE2R_2_RADC_CTRL 23
+/* MICBIAS Control Register */
+#define MICBIAS_CTRL 25
+
+/* AGC Control Registers A, B, C */
+#define LAGC_CTRL_A 26
+#define LAGC_CTRL_B 27
+#define LAGC_CTRL_C 28
+#define RAGC_CTRL_A 29
+#define RAGC_CTRL_B 30
+#define RAGC_CTRL_C 31
+
+/* DAC Power and Left High Power Output control registers */
+#define DAC_PWR 37
+#define HPLCOM_CFG 37
+/* Right High Power Output control registers */
+#define HPRCOM_CFG 38
+/* DAC Output Switching control registers */
+#define DAC_LINE_MUX 41
+/* High Power Output Driver Pop Reduction registers */
+#define HPOUT_POP_REDUCTION 42
+/* DAC Digital control registers */
+#define LDAC_VOL 43
+#define RDAC_VOL 44
+/* High Power Output control registers */
+#define LINE2L_2_HPLOUT_VOL 45
+#define LINE2R_2_HPROUT_VOL 62
+#define PGAL_2_HPLOUT_VOL 46
+#define PGAR_2_HPROUT_VOL 63
+#define DACL1_2_HPLOUT_VOL 47
+#define DACR1_2_HPROUT_VOL 64
+#define HPLOUT_CTRL 51
+#define HPROUT_CTRL 65
+/* High Power COM control registers */
+#define LINE2L_2_HPLCOM_VOL 52
+#define LINE2R_2_HPRCOM_VOL 69
+#define PGAL_2_HPLCOM_VOL 53
+#define PGAR_2_HPRCOM_VOL 70
+#define DACL1_2_HPLCOM_VOL 54
+#define DACR1_2_HPRCOM_VOL 71
+#define HPLCOM_CTRL 58
+#define HPRCOM_CTRL 72
+/* Mono Line Output Plus/Minus control registers */
+#define LINE2L_2_MONOLOPM_VOL 73
+#define LINE2R_2_MONOLOPM_VOL 76
+#define PGAL_2_MONOLOPM_VOL 74
+#define PGAR_2_MONOLOPM_VOL 77
+#define DACL1_2_MONOLOPM_VOL 75
+#define DACR1_2_MONOLOPM_VOL 78
+#define MONOLOPM_CTRL 79
+/* Line Output Plus/Minus control registers */
+#define LINE2L_2_LLOPM_VOL 80
+#define LINE2R_2_RLOPM_VOL 90
+#define PGAL_2_LLOPM_VOL 81
+#define PGAR_2_RLOPM_VOL 91
+#define DACL1_2_LLOPM_VOL 82
+#define DACR1_2_RLOPM_VOL 92
+#define LLOPM_CTRL 86
+#define RLOPM_CTRL 93
+/* GPIO/IRQ registers */
+#define AIC3X_STICKY_IRQ_FLAGS_REG 96
+#define AIC3X_RT_IRQ_FLAGS_REG 97
+#define AIC3X_GPIO1_REG 98
+#define AIC3X_GPIO2_REG 99
+#define AIC3X_GPIOA_REG 100
+#define AIC3X_GPIOB_REG 101
+/* Clock generation control register */
+#define AIC3X_CLKGEN_CTRL_REG 102
+
+/* Page select register bits */
+#define PAGE0_SELECT 0
+#define PAGE1_SELECT 1
+
+/* Audio serial data interface control register A bits */
+#define BIT_CLK_MASTER 0x80
+#define WORD_CLK_MASTER 0x40
+
+/* Codec Datapath setup register 7 */
+#define FSREF_44100 (1 << 7)
+#define FSREF_48000 (0 << 7)
+#define DUAL_RATE_MODE ((1 << 5) | (1 << 6))
+#define LDAC2LCH (0x1 << 3)
+#define RDAC2RCH (0x1 << 1)
+
+/* PLL registers bitfields */
+#define PLLP_SHIFT 0
+#define PLLQ_SHIFT 3
+#define PLLR_SHIFT 0
+#define PLLJ_SHIFT 2
+#define PLLD_MSB_SHIFT 0
+#define PLLD_LSB_SHIFT 2
+
+/* Clock generation register bits */
+#define CODEC_CLKIN_PLLDIV 0
+#define CODEC_CLKIN_CLKDIV 1
+#define PLL_CLKIN_SHIFT 4
+#define MCLK_SOURCE 0x0
+#define PLL_CLKDIV_SHIFT 0
+
+/* Software reset register bits */
+#define SOFT_RESET 0x80
+
+/* PLL progrramming register A bits */
+#define PLL_ENABLE 0x80
+
+/* Route bits */
+#define ROUTE_ON 0x80
+
+/* Mute bits */
+#define UNMUTE 0x08
+#define MUTE_ON 0x80
+
+/* Power bits */
+#define LADC_PWR_ON 0x04
+#define RADC_PWR_ON 0x04
+#define LDAC_PWR_ON 0x80
+#define RDAC_PWR_ON 0x40
+#define HPLOUT_PWR_ON 0x01
+#define HPROUT_PWR_ON 0x01
+#define HPLCOM_PWR_ON 0x01
+#define HPRCOM_PWR_ON 0x01
+#define MONOLOPM_PWR_ON 0x01
+#define LLOPM_PWR_ON 0x01
+#define RLOPM_PWR_ON 0x01
+
+#define INVERT_VOL(val) (0x7f - val)
+
+/* Default output volume (inverted) */
+#define DEFAULT_VOL INVERT_VOL(0x50)
+/* Default input volume */
+#define DEFAULT_GAIN 0x20
+
+/* GPIO API */
+enum {
+ AIC3X_GPIO1_FUNC_DISABLED = 0,
+ AIC3X_GPIO1_FUNC_AUDIO_WORDCLK_ADC = 1,
+ AIC3X_GPIO1_FUNC_CLOCK_MUX = 2,
+ AIC3X_GPIO1_FUNC_CLOCK_MUX_DIV2 = 3,
+ AIC3X_GPIO1_FUNC_CLOCK_MUX_DIV4 = 4,
+ AIC3X_GPIO1_FUNC_CLOCK_MUX_DIV8 = 5,
+ AIC3X_GPIO1_FUNC_SHORT_CIRCUIT_IRQ = 6,
+ AIC3X_GPIO1_FUNC_AGC_NOISE_IRQ = 7,
+ AIC3X_GPIO1_FUNC_INPUT = 8,
+ AIC3X_GPIO1_FUNC_OUTPUT = 9,
+ AIC3X_GPIO1_FUNC_DIGITAL_MIC_MODCLK = 10,
+ AIC3X_GPIO1_FUNC_AUDIO_WORDCLK = 11,
+ AIC3X_GPIO1_FUNC_BUTTON_IRQ = 12,
+ AIC3X_GPIO1_FUNC_HEADSET_DETECT_IRQ = 13,
+ AIC3X_GPIO1_FUNC_HEADSET_DETECT_OR_BUTTON_IRQ = 14,
+ AIC3X_GPIO1_FUNC_ALL_IRQ = 16
+};
+
+enum {
+ AIC3X_GPIO2_FUNC_DISABLED = 0,
+ AIC3X_GPIO2_FUNC_HEADSET_DETECT_IRQ = 2,
+ AIC3X_GPIO2_FUNC_INPUT = 3,
+ AIC3X_GPIO2_FUNC_OUTPUT = 4,
+ AIC3X_GPIO2_FUNC_DIGITAL_MIC_INPUT = 5,
+ AIC3X_GPIO2_FUNC_AUDIO_BITCLK = 8,
+ AIC3X_GPIO2_FUNC_HEADSET_DETECT_OR_BUTTON_IRQ = 9,
+ AIC3X_GPIO2_FUNC_ALL_IRQ = 10,
+ AIC3X_GPIO2_FUNC_SHORT_CIRCUIT_OR_AGC_IRQ = 11,
+ AIC3X_GPIO2_FUNC_HEADSET_OR_BUTTON_PRESS_OR_SHORT_CIRCUIT_IRQ = 12,
+ AIC3X_GPIO2_FUNC_SHORT_CIRCUIT_IRQ = 13,
+ AIC3X_GPIO2_FUNC_AGC_NOISE_IRQ = 14,
+ AIC3X_GPIO2_FUNC_BUTTON_PRESS_IRQ = 15
+};
+
+void aic3x_set_gpio(struct snd_soc_codec *codec, int gpio, int state);
+int aic3x_get_gpio(struct snd_soc_codec *codec, int gpio);
+int aic3x_headset_detected(struct snd_soc_codec *codec);
+
+struct aic3x_setup_data {
+ int i2c_bus;
+ unsigned short i2c_address;
+ unsigned int gpio_func[2];
+};
+
+extern struct snd_soc_dai aic3x_dai;
+extern struct snd_soc_codec_device soc_codec_dev_aic3x;
+
+#endif /* _AIC3X_H */
diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c
new file mode 100644
index 0000000..a69ee72
--- /dev/null
+++ b/sound/soc/codecs/uda1380.c
@@ -0,0 +1,849 @@
+/*
+ * uda1380.c - Philips UDA1380 ALSA SoC audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Copyright (c) 2007 Philipp Zabel <philipp.zabel@gmail.com>
+ * Improved support for DAPM and audio routing/mixing capabilities,
+ * added TLV support.
+ *
+ * Modified by Richard Purdie <richard@openedhand.com> to fit into SoC
+ * codec model.
+ *
+ * Copyright (c) 2005 Giorgio Padrin <giorgio@mandarinlogiq.org>
+ * Copyright 2005 Openedhand Ltd.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/ioctl.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+#include <sound/info.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+
+#include "uda1380.h"
+
+#define UDA1380_VERSION "0.6"
+
+/*
+ * uda1380 register cache
+ */
+static const u16 uda1380_reg[UDA1380_CACHEREGNUM] = {
+ 0x0502, 0x0000, 0x0000, 0x3f3f,
+ 0x0202, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0xff00, 0x0000, 0x4800,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x8000, 0x0002, 0x0000,
+};
+
+/*
+ * read uda1380 register cache
+ */
+static inline unsigned int uda1380_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg == UDA1380_RESET)
+ return 0;
+ if (reg >= UDA1380_CACHEREGNUM)
+ return -1;
+ return cache[reg];
+}
+
+/*
+ * write uda1380 register cache
+ */
+static inline void uda1380_write_reg_cache(struct snd_soc_codec *codec,
+ u16 reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= UDA1380_CACHEREGNUM)
+ return;
+ cache[reg] = value;
+}
+
+/*
+ * write to the UDA1380 register space
+ */
+static int uda1380_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[3];
+
+ /* data is
+ * data[0] is register offset
+ * data[1] is MS byte
+ * data[2] is LS byte
+ */
+ data[0] = reg;
+ data[1] = (value & 0xff00) >> 8;
+ data[2] = value & 0x00ff;
+
+ uda1380_write_reg_cache(codec, reg, value);
+
+ /* the interpolator & decimator regs must only be written when the
+ * codec DAI is active.
+ */
+ if (!codec->active && (reg >= UDA1380_MVOL))
+ return 0;
+ pr_debug("uda1380: hw write %x val %x\n", reg, value);
+ if (codec->hw_write(codec->control_data, data, 3) == 3) {
+ unsigned int val;
+ i2c_master_send(codec->control_data, data, 1);
+ i2c_master_recv(codec->control_data, data, 2);
+ val = (data[0]<<8) | data[1];
+ if (val != value) {
+ pr_debug("uda1380: READ BACK VAL %x\n",
+ (data[0]<<8) | data[1]);
+ return -EIO;
+ }
+ return 0;
+ } else
+ return -EIO;
+}
+
+#define uda1380_reset(c) uda1380_write(c, UDA1380_RESET, 0)
+
+/* declarations of ALSA reg_elem_REAL controls */
+static const char *uda1380_deemp[] = {
+ "None",
+ "32kHz",
+ "44.1kHz",
+ "48kHz",
+ "96kHz",
+};
+static const char *uda1380_input_sel[] = {
+ "Line",
+ "Mic + Line R",
+ "Line L",
+ "Mic",
+};
+static const char *uda1380_output_sel[] = {
+ "DAC",
+ "Analog Mixer",
+};
+static const char *uda1380_spf_mode[] = {
+ "Flat",
+ "Minimum1",
+ "Minimum2",
+ "Maximum"
+};
+static const char *uda1380_capture_sel[] = {
+ "ADC",
+ "Digital Mixer"
+};
+static const char *uda1380_sel_ns[] = {
+ "3rd-order",
+ "5th-order"
+};
+static const char *uda1380_mix_control[] = {
+ "off",
+ "PCM only",
+ "before sound processing",
+ "after sound processing"
+};
+static const char *uda1380_sdet_setting[] = {
+ "3200",
+ "4800",
+ "9600",
+ "19200"
+};
+static const char *uda1380_os_setting[] = {
+ "single-speed",
+ "double-speed (no mixing)",
+ "quad-speed (no mixing)"
+};
+
+static const struct soc_enum uda1380_deemp_enum[] = {
+ SOC_ENUM_SINGLE(UDA1380_DEEMP, 8, 5, uda1380_deemp),
+ SOC_ENUM_SINGLE(UDA1380_DEEMP, 0, 5, uda1380_deemp),
+};
+static const struct soc_enum uda1380_input_sel_enum =
+ SOC_ENUM_SINGLE(UDA1380_ADC, 2, 4, uda1380_input_sel); /* SEL_MIC, SEL_LNA */
+static const struct soc_enum uda1380_output_sel_enum =
+ SOC_ENUM_SINGLE(UDA1380_PM, 7, 2, uda1380_output_sel); /* R02_EN_AVC */
+static const struct soc_enum uda1380_spf_enum =
+ SOC_ENUM_SINGLE(UDA1380_MODE, 14, 4, uda1380_spf_mode); /* M */
+static const struct soc_enum uda1380_capture_sel_enum =
+ SOC_ENUM_SINGLE(UDA1380_IFACE, 6, 2, uda1380_capture_sel); /* SEL_SOURCE */
+static const struct soc_enum uda1380_sel_ns_enum =
+ SOC_ENUM_SINGLE(UDA1380_MIXER, 14, 2, uda1380_sel_ns); /* SEL_NS */
+static const struct soc_enum uda1380_mix_enum =
+ SOC_ENUM_SINGLE(UDA1380_MIXER, 12, 4, uda1380_mix_control); /* MIX, MIX_POS */
+static const struct soc_enum uda1380_sdet_enum =
+ SOC_ENUM_SINGLE(UDA1380_MIXER, 4, 4, uda1380_sdet_setting); /* SD_VALUE */
+static const struct soc_enum uda1380_os_enum =
+ SOC_ENUM_SINGLE(UDA1380_MIXER, 0, 3, uda1380_os_setting); /* OS */
+
+/*
+ * from -48 dB in 1.5 dB steps (mute instead of -49.5 dB)
+ */
+static DECLARE_TLV_DB_SCALE(amix_tlv, -4950, 150, 1);
+
+/*
+ * from -78 dB in 1 dB steps (3 dB steps, really. LSB are ignored),
+ * from -66 dB in 0.5 dB steps (2 dB steps, really) and
+ * from -52 dB in 0.25 dB steps
+ */
+static const unsigned int mvol_tlv[] = {
+ TLV_DB_RANGE_HEAD(3),
+ 0, 15, TLV_DB_SCALE_ITEM(-8200, 100, 1),
+ 16, 43, TLV_DB_SCALE_ITEM(-6600, 50, 0),
+ 44, 252, TLV_DB_SCALE_ITEM(-5200, 25, 0),
+};
+
+/*
+ * from -72 dB in 1.5 dB steps (6 dB steps really),
+ * from -66 dB in 0.75 dB steps (3 dB steps really),
+ * from -60 dB in 0.5 dB steps (2 dB steps really) and
+ * from -46 dB in 0.25 dB steps
+ */
+static const unsigned int vc_tlv[] = {
+ TLV_DB_RANGE_HEAD(4),
+ 0, 7, TLV_DB_SCALE_ITEM(-7800, 150, 1),
+ 8, 15, TLV_DB_SCALE_ITEM(-6600, 75, 0),
+ 16, 43, TLV_DB_SCALE_ITEM(-6000, 50, 0),
+ 44, 228, TLV_DB_SCALE_ITEM(-4600, 25, 0),
+};
+
+/* from 0 to 6 dB in 2 dB steps if SPF mode != flat */
+static DECLARE_TLV_DB_SCALE(tr_tlv, 0, 200, 0);
+
+/* from 0 to 24 dB in 2 dB steps, if SPF mode == maximum, otherwise cuts
+ * off at 18 dB max) */
+static DECLARE_TLV_DB_SCALE(bb_tlv, 0, 200, 0);
+
+/* from -63 to 24 dB in 0.5 dB steps (-128...48) */
+static DECLARE_TLV_DB_SCALE(dec_tlv, -6400, 50, 1);
+
+/* from 0 to 24 dB in 3 dB steps */
+static DECLARE_TLV_DB_SCALE(pga_tlv, 0, 300, 0);
+
+/* from 0 to 30 dB in 2 dB steps */
+static DECLARE_TLV_DB_SCALE(vga_tlv, 0, 200, 0);
+
+static const struct snd_kcontrol_new uda1380_snd_controls[] = {
+ SOC_DOUBLE_TLV("Analog Mixer Volume", UDA1380_AMIX, 0, 8, 44, 1, amix_tlv), /* AVCR, AVCL */
+ SOC_DOUBLE_TLV("Master Playback Volume", UDA1380_MVOL, 0, 8, 252, 1, mvol_tlv), /* MVCL, MVCR */
+ SOC_SINGLE_TLV("ADC Playback Volume", UDA1380_MIXVOL, 8, 228, 1, vc_tlv), /* VC2 */
+ SOC_SINGLE_TLV("PCM Playback Volume", UDA1380_MIXVOL, 0, 228, 1, vc_tlv), /* VC1 */
+ SOC_ENUM("Sound Processing Filter", uda1380_spf_enum), /* M */
+ SOC_DOUBLE_TLV("Tone Control - Treble", UDA1380_MODE, 4, 12, 3, 0, tr_tlv), /* TRL, TRR */
+ SOC_DOUBLE_TLV("Tone Control - Bass", UDA1380_MODE, 0, 8, 15, 0, bb_tlv), /* BBL, BBR */
+/**/ SOC_SINGLE("Master Playback Switch", UDA1380_DEEMP, 14, 1, 1), /* MTM */
+ SOC_SINGLE("ADC Playback Switch", UDA1380_DEEMP, 11, 1, 1), /* MT2 from decimation filter */
+ SOC_ENUM("ADC Playback De-emphasis", uda1380_deemp_enum[0]), /* DE2 */
+ SOC_SINGLE("PCM Playback Switch", UDA1380_DEEMP, 3, 1, 1), /* MT1, from digital data input */
+ SOC_ENUM("PCM Playback De-emphasis", uda1380_deemp_enum[1]), /* DE1 */
+ SOC_SINGLE("DAC Polarity inverting Switch", UDA1380_MIXER, 15, 1, 0), /* DA_POL_INV */
+ SOC_ENUM("Noise Shaper", uda1380_sel_ns_enum), /* SEL_NS */
+ SOC_ENUM("Digital Mixer Signal Control", uda1380_mix_enum), /* MIX_POS, MIX */
+ SOC_SINGLE("Silence Switch", UDA1380_MIXER, 7, 1, 0), /* SILENCE, force DAC output to silence */
+ SOC_SINGLE("Silence Detector Switch", UDA1380_MIXER, 6, 1, 0), /* SDET_ON */
+ SOC_ENUM("Silence Detector Setting", uda1380_sdet_enum), /* SD_VALUE */
+ SOC_ENUM("Oversampling Input", uda1380_os_enum), /* OS */
+ SOC_DOUBLE_S8_TLV("ADC Capture Volume", UDA1380_DEC, -128, 48, dec_tlv), /* ML_DEC, MR_DEC */
+/**/ SOC_SINGLE("ADC Capture Switch", UDA1380_PGA, 15, 1, 1), /* MT_ADC */
+ SOC_DOUBLE_TLV("Line Capture Volume", UDA1380_PGA, 0, 8, 8, 0, pga_tlv), /* PGA_GAINCTRLL, PGA_GAINCTRLR */
+ SOC_SINGLE("ADC Polarity inverting Switch", UDA1380_ADC, 12, 1, 0), /* ADCPOL_INV */
+ SOC_SINGLE_TLV("Mic Capture Volume", UDA1380_ADC, 8, 15, 0, vga_tlv), /* VGA_CTRL */
+ SOC_SINGLE("DC Filter Bypass Switch", UDA1380_ADC, 1, 1, 0), /* SKIP_DCFIL (before decimator) */
+ SOC_SINGLE("DC Filter Enable Switch", UDA1380_ADC, 0, 1, 0), /* EN_DCFIL (at output of decimator) */
+ SOC_SINGLE("AGC Timing", UDA1380_AGC, 8, 7, 0), /* TODO: enum, see table 62 */
+ SOC_SINGLE("AGC Target level", UDA1380_AGC, 2, 3, 1), /* AGC_LEVEL */
+ /* -5.5, -8, -11.5, -14 dBFS */
+ SOC_SINGLE("AGC Switch", UDA1380_AGC, 0, 1, 0),
+};
+
+/* add non dapm controls */
+static int uda1380_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(uda1380_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&uda1380_snd_controls[i], codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/* Input mux */
+static const struct snd_kcontrol_new uda1380_input_mux_control =
+ SOC_DAPM_ENUM("Route", uda1380_input_sel_enum);
+
+/* Output mux */
+static const struct snd_kcontrol_new uda1380_output_mux_control =
+ SOC_DAPM_ENUM("Route", uda1380_output_sel_enum);
+
+/* Capture mux */
+static const struct snd_kcontrol_new uda1380_capture_mux_control =
+ SOC_DAPM_ENUM("Route", uda1380_capture_sel_enum);
+
+
+static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = {
+ SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0,
+ &uda1380_input_mux_control),
+ SND_SOC_DAPM_MUX("Output Mux", SND_SOC_NOPM, 0, 0,
+ &uda1380_output_mux_control),
+ SND_SOC_DAPM_MUX("Capture Mux", SND_SOC_NOPM, 0, 0,
+ &uda1380_capture_mux_control),
+ SND_SOC_DAPM_PGA("Left PGA", UDA1380_PM, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right PGA", UDA1380_PM, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Mic LNA", UDA1380_PM, 4, 0, NULL, 0),
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", UDA1380_PM, 2, 0),
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture", UDA1380_PM, 0, 0),
+ SND_SOC_DAPM_INPUT("VINM"),
+ SND_SOC_DAPM_INPUT("VINL"),
+ SND_SOC_DAPM_INPUT("VINR"),
+ SND_SOC_DAPM_MIXER("Analog Mixer", UDA1380_PM, 6, 0, NULL, 0),
+ SND_SOC_DAPM_OUTPUT("VOUTLHP"),
+ SND_SOC_DAPM_OUTPUT("VOUTRHP"),
+ SND_SOC_DAPM_OUTPUT("VOUTL"),
+ SND_SOC_DAPM_OUTPUT("VOUTR"),
+ SND_SOC_DAPM_DAC("DAC", "Playback", UDA1380_PM, 10, 0),
+ SND_SOC_DAPM_PGA("HeadPhone Driver", UDA1380_PM, 13, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* output mux */
+ {"HeadPhone Driver", NULL, "Output Mux"},
+ {"VOUTR", NULL, "Output Mux"},
+ {"VOUTL", NULL, "Output Mux"},
+
+ {"Analog Mixer", NULL, "VINR"},
+ {"Analog Mixer", NULL, "VINL"},
+ {"Analog Mixer", NULL, "DAC"},
+
+ {"Output Mux", "DAC", "DAC"},
+ {"Output Mux", "Analog Mixer", "Analog Mixer"},
+
+ /* {"DAC", "Digital Mixer", "I2S" } */
+
+ /* headphone driver */
+ {"VOUTLHP", NULL, "HeadPhone Driver"},
+ {"VOUTRHP", NULL, "HeadPhone Driver"},
+
+ /* input mux */
+ {"Left ADC", NULL, "Input Mux"},
+ {"Input Mux", "Mic", "Mic LNA"},
+ {"Input Mux", "Mic + Line R", "Mic LNA"},
+ {"Input Mux", "Line L", "Left PGA"},
+ {"Input Mux", "Line", "Left PGA"},
+
+ /* right input */
+ {"Right ADC", "Mic + Line R", "Right PGA"},
+ {"Right ADC", "Line", "Right PGA"},
+
+ /* inputs */
+ {"Mic LNA", NULL, "VINM"},
+ {"Left PGA", NULL, "VINL"},
+ {"Right PGA", NULL, "VINR"},
+};
+
+static int uda1380_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, uda1380_dapm_widgets,
+ ARRAY_SIZE(uda1380_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+static int uda1380_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int iface;
+
+ /* set up DAI based upon fmt */
+ iface = uda1380_read_reg_cache(codec, UDA1380_IFACE);
+ iface &= ~(R01_SFORI_MASK | R01_SIM | R01_SFORO_MASK);
+
+ /* FIXME: how to select I2S for DATAO and MSB for DATAI correctly? */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= R01_SFORI_I2S | R01_SFORO_I2S;
+ break;
+ case SND_SOC_DAIFMT_LSB:
+ iface |= R01_SFORI_LSB16 | R01_SFORO_I2S;
+ break;
+ case SND_SOC_DAIFMT_MSB:
+ iface |= R01_SFORI_MSB | R01_SFORO_I2S;
+ }
+
+ if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFM)
+ iface |= R01_SIM;
+
+ uda1380_write(codec, UDA1380_IFACE, iface);
+
+ return 0;
+}
+
+/*
+ * Flush reg cache
+ * We can only write the interpolator and decimator registers
+ * when the DAI is being clocked by the CPU DAI. It's up to the
+ * machine and cpu DAI driver to do this before we are called.
+ */
+static int uda1380_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int reg, reg_start, reg_end, clk;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ reg_start = UDA1380_MVOL;
+ reg_end = UDA1380_MIXER;
+ } else {
+ reg_start = UDA1380_DEC;
+ reg_end = UDA1380_AGC;
+ }
+
+ /* FIXME disable DAC_CLK */
+ clk = uda1380_read_reg_cache(codec, UDA1380_CLK);
+ uda1380_write(codec, UDA1380_CLK, clk & ~R00_DAC_CLK);
+
+ for (reg = reg_start; reg <= reg_end; reg++) {
+ pr_debug("uda1380: flush reg %x val %x:", reg,
+ uda1380_read_reg_cache(codec, reg));
+ uda1380_write(codec, reg, uda1380_read_reg_cache(codec, reg));
+ }
+
+ /* FIXME enable DAC_CLK */
+ uda1380_write(codec, UDA1380_CLK, clk | R00_DAC_CLK);
+
+ return 0;
+}
+
+static int uda1380_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK);
+
+ /* set WSPLL power and divider if running from this clock */
+ if (clk & R00_DAC_CLK) {
+ int rate = params_rate(params);
+ u16 pm = uda1380_read_reg_cache(codec, UDA1380_PM);
+ clk &= ~0x3; /* clear SEL_LOOP_DIV */
+ switch (rate) {
+ case 6250 ... 12500:
+ clk |= 0x0;
+ break;
+ case 12501 ... 25000:
+ clk |= 0x1;
+ break;
+ case 25001 ... 50000:
+ clk |= 0x2;
+ break;
+ case 50001 ... 100000:
+ clk |= 0x3;
+ break;
+ }
+ uda1380_write(codec, UDA1380_PM, R02_PON_PLL | pm);
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ clk |= R00_EN_DAC | R00_EN_INT;
+ else
+ clk |= R00_EN_ADC | R00_EN_DEC;
+
+ uda1380_write(codec, UDA1380_CLK, clk);
+ return 0;
+}
+
+static void uda1380_pcm_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK);
+
+ /* shut down WSPLL power if running from this clock */
+ if (clk & R00_DAC_CLK) {
+ u16 pm = uda1380_read_reg_cache(codec, UDA1380_PM);
+ uda1380_write(codec, UDA1380_PM, ~R02_PON_PLL & pm);
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ clk &= ~(R00_EN_DAC | R00_EN_INT);
+ else
+ clk &= ~(R00_EN_ADC | R00_EN_DEC);
+
+ uda1380_write(codec, UDA1380_CLK, clk);
+}
+
+static int uda1380_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 mute_reg = uda1380_read_reg_cache(codec, UDA1380_DEEMP) & ~R13_MTM;
+
+ /* FIXME: mute(codec,0) is called when the magician clock is already
+ * set to WSPLL, but for some unknown reason writing to interpolator
+ * registers works only when clocked by SYSCLK */
+ u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK);
+ uda1380_write(codec, UDA1380_CLK, ~R00_DAC_CLK & clk);
+ if (mute)
+ uda1380_write(codec, UDA1380_DEEMP, mute_reg | R13_MTM);
+ else
+ uda1380_write(codec, UDA1380_DEEMP, mute_reg);
+ uda1380_write(codec, UDA1380_CLK, clk);
+ return 0;
+}
+
+static int uda1380_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ int pm = uda1380_read_reg_cache(codec, UDA1380_PM);
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ uda1380_write(codec, UDA1380_PM, R02_PON_BIAS | pm);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ uda1380_write(codec, UDA1380_PM, R02_PON_BIAS);
+ break;
+ case SND_SOC_BIAS_OFF:
+ uda1380_write(codec, UDA1380_PM, 0x0);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define UDA1380_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+struct snd_soc_dai uda1380_dai[] = {
+{
+ .name = "UDA1380",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = UDA1380_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = UDA1380_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .hw_params = uda1380_pcm_hw_params,
+ .shutdown = uda1380_pcm_shutdown,
+ .prepare = uda1380_pcm_prepare,
+ },
+ .dai_ops = {
+ .digital_mute = uda1380_mute,
+ .set_fmt = uda1380_set_dai_fmt,
+ },
+},
+{ /* playback only - dual interface */
+ .name = "UDA1380",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = UDA1380_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = {
+ .hw_params = uda1380_pcm_hw_params,
+ .shutdown = uda1380_pcm_shutdown,
+ .prepare = uda1380_pcm_prepare,
+ },
+ .dai_ops = {
+ .digital_mute = uda1380_mute,
+ .set_fmt = uda1380_set_dai_fmt,
+ },
+},
+{ /* capture only - dual interface*/
+ .name = "UDA1380",
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = UDA1380_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = {
+ .hw_params = uda1380_pcm_hw_params,
+ .shutdown = uda1380_pcm_shutdown,
+ .prepare = uda1380_pcm_prepare,
+ },
+ .dai_ops = {
+ .set_fmt = uda1380_set_dai_fmt,
+ },
+},
+};
+EXPORT_SYMBOL_GPL(uda1380_dai);
+
+static int uda1380_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int uda1380_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(uda1380_reg); i++) {
+ data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+ uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ uda1380_set_bias_level(codec, codec->suspend_bias_level);
+ return 0;
+}
+
+/*
+ * initialise the UDA1380 driver
+ * register mixer and dsp interfaces with the kernel
+ */
+static int uda1380_init(struct snd_soc_device *socdev, int dac_clk)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret = 0;
+
+ codec->name = "UDA1380";
+ codec->owner = THIS_MODULE;
+ codec->read = uda1380_read_reg_cache;
+ codec->write = uda1380_write;
+ codec->set_bias_level = uda1380_set_bias_level;
+ codec->dai = uda1380_dai;
+ codec->num_dai = ARRAY_SIZE(uda1380_dai);
+ codec->reg_cache = kmemdup(uda1380_reg, sizeof(uda1380_reg),
+ GFP_KERNEL);
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+ codec->reg_cache_size = ARRAY_SIZE(uda1380_reg);
+ codec->reg_cache_step = 1;
+ uda1380_reset(codec);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ pr_err("uda1380: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* power on device */
+ uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ /* set clock input */
+ switch (dac_clk) {
+ case UDA1380_DAC_CLK_SYSCLK:
+ uda1380_write(codec, UDA1380_CLK, 0);
+ break;
+ case UDA1380_DAC_CLK_WSPLL:
+ uda1380_write(codec, UDA1380_CLK, R00_DAC_CLK);
+ break;
+ }
+
+ /* uda1380 init */
+ uda1380_add_controls(codec);
+ uda1380_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ pr_err("uda1380: failed to register card\n");
+ goto card_err;
+ }
+
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+ return ret;
+}
+
+static struct snd_soc_device *uda1380_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+
+static int uda1380_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = uda1380_socdev;
+ struct uda1380_setup_data *setup = socdev->codec_data;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = uda1380_init(socdev, setup->dac_clk);
+ if (ret < 0)
+ pr_err("uda1380: failed to initialise UDA1380\n");
+
+ return ret;
+}
+
+static int uda1380_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+static const struct i2c_device_id uda1380_i2c_id[] = {
+ { "uda1380", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, uda1380_i2c_id);
+
+static struct i2c_driver uda1380_i2c_driver = {
+ .driver = {
+ .name = "UDA1380 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = uda1380_i2c_probe,
+ .remove = uda1380_i2c_remove,
+ .id_table = uda1380_i2c_id,
+};
+
+static int uda1380_add_i2c_device(struct platform_device *pdev,
+ const struct uda1380_setup_data *setup)
+{
+ struct i2c_board_info info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+ int ret;
+
+ ret = i2c_add_driver(&uda1380_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ return ret;
+ }
+
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ info.addr = setup->i2c_address;
+ strlcpy(info.type, "uda1380", I2C_NAME_SIZE);
+
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "can't get i2c adapter %d\n",
+ setup->i2c_bus);
+ goto err_driver;
+ }
+
+ client = i2c_new_device(adapter, &info);
+ i2c_put_adapter(adapter);
+ if (!client) {
+ dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
+ (unsigned int)info.addr);
+ goto err_driver;
+ }
+
+ return 0;
+
+err_driver:
+ i2c_del_driver(&uda1380_i2c_driver);
+ return -ENODEV;
+}
+#endif
+
+static int uda1380_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct uda1380_setup_data *setup;
+ struct snd_soc_codec *codec;
+ int ret;
+
+ pr_info("UDA1380 Audio Codec %s", UDA1380_VERSION);
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ uda1380_socdev = socdev;
+ ret = -ENODEV;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = uda1380_add_i2c_device(pdev, setup);
+ }
+#endif
+
+ if (ret != 0)
+ kfree(codec);
+ return ret;
+}
+
+/* power down chip */
+static int uda1380_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec->control_data)
+ uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_unregister_device(codec->control_data);
+ i2c_del_driver(&uda1380_i2c_driver);
+#endif
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_uda1380 = {
+ .probe = uda1380_probe,
+ .remove = uda1380_remove,
+ .suspend = uda1380_suspend,
+ .resume = uda1380_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_uda1380);
+
+MODULE_AUTHOR("Giorgio Padrin");
+MODULE_DESCRIPTION("Audio support for codec Philips UDA1380");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/uda1380.h b/sound/soc/codecs/uda1380.h
new file mode 100644
index 0000000..c55c17a
--- /dev/null
+++ b/sound/soc/codecs/uda1380.h
@@ -0,0 +1,90 @@
+/*
+ * Audio support for Philips UDA1380
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Copyright (c) 2005 Giorgio Padrin <giorgio@mandarinlogiq.org>
+ */
+
+#ifndef _UDA1380_H
+#define _UDA1380_H
+
+#define UDA1380_CLK 0x00
+#define UDA1380_IFACE 0x01
+#define UDA1380_PM 0x02
+#define UDA1380_AMIX 0x03
+#define UDA1380_HP 0x04
+#define UDA1380_MVOL 0x10
+#define UDA1380_MIXVOL 0x11
+#define UDA1380_MODE 0x12
+#define UDA1380_DEEMP 0x13
+#define UDA1380_MIXER 0x14
+#define UDA1380_INTSTAT 0x18
+#define UDA1380_DEC 0x20
+#define UDA1380_PGA 0x21
+#define UDA1380_ADC 0x22
+#define UDA1380_AGC 0x23
+#define UDA1380_DECSTAT 0x28
+#define UDA1380_RESET 0x7f
+
+#define UDA1380_CACHEREGNUM 0x24
+
+/* Register flags */
+#define R00_EN_ADC 0x0800
+#define R00_EN_DEC 0x0400
+#define R00_EN_DAC 0x0200
+#define R00_EN_INT 0x0100
+#define R00_DAC_CLK 0x0010
+#define R01_SFORI_I2S 0x0000
+#define R01_SFORI_LSB16 0x0100
+#define R01_SFORI_LSB18 0x0200
+#define R01_SFORI_LSB20 0x0300
+#define R01_SFORI_MSB 0x0500
+#define R01_SFORI_MASK 0x0700
+#define R01_SFORO_I2S 0x0000
+#define R01_SFORO_LSB16 0x0001
+#define R01_SFORO_LSB18 0x0002
+#define R01_SFORO_LSB20 0x0003
+#define R01_SFORO_LSB24 0x0004
+#define R01_SFORO_MSB 0x0005
+#define R01_SFORO_MASK 0x0007
+#define R01_SEL_SOURCE 0x0040
+#define R01_SIM 0x0010
+#define R02_PON_PLL 0x8000
+#define R02_PON_HP 0x2000
+#define R02_PON_DAC 0x0400
+#define R02_PON_BIAS 0x0100
+#define R02_EN_AVC 0x0080
+#define R02_PON_AVC 0x0040
+#define R02_PON_LNA 0x0010
+#define R02_PON_PGAL 0x0008
+#define R02_PON_ADCL 0x0004
+#define R02_PON_PGAR 0x0002
+#define R02_PON_ADCR 0x0001
+#define R13_MTM 0x4000
+#define R14_SILENCE 0x0080
+#define R14_SDET_ON 0x0040
+#define R21_MT_ADC 0x8000
+#define R22_SEL_LNA 0x0008
+#define R22_SEL_MIC 0x0004
+#define R22_SKIP_DCFIL 0x0002
+#define R23_AGC_EN 0x0001
+
+struct uda1380_setup_data {
+ int i2c_bus;
+ unsigned short i2c_address;
+ int dac_clk;
+#define UDA1380_DAC_CLK_SYSCLK 0
+#define UDA1380_DAC_CLK_WSPLL 1
+};
+
+#define UDA1380_DAI_DUPLEX 0 /* playback and capture on single DAI */
+#define UDA1380_DAI_PLAYBACK 1 /* playback DAI */
+#define UDA1380_DAI_CAPTURE 2 /* capture DAI */
+
+extern struct snd_soc_dai uda1380_dai[3];
+extern struct snd_soc_codec_device soc_codec_dev_uda1380;
+
+#endif /* _UDA1380_H */
diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c
new file mode 100644
index 0000000..d8ca2da
--- /dev/null
+++ b/sound/soc/codecs/wm8510.c
@@ -0,0 +1,895 @@
+/*
+ * wm8510.c -- WM8510 ALSA Soc Audio driver
+ *
+ * Copyright 2006 Wolfson Microelectronics PLC.
+ *
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.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 "wm8510.h"
+
+#define WM8510_VERSION "0.6"
+
+struct snd_soc_codec_device soc_codec_dev_wm8510;
+
+/*
+ * wm8510 register cache
+ * We can't read the WM8510 register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8510_reg[WM8510_CACHEREGNUM] = {
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0050, 0x0000, 0x0140, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x00ff,
+ 0x0000, 0x0000, 0x0100, 0x00ff,
+ 0x0000, 0x0000, 0x012c, 0x002c,
+ 0x002c, 0x002c, 0x002c, 0x0000,
+ 0x0032, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0038, 0x000b, 0x0032, 0x0000,
+ 0x0008, 0x000c, 0x0093, 0x00e9,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0003, 0x0010, 0x0000, 0x0000,
+ 0x0000, 0x0002, 0x0001, 0x0000,
+ 0x0000, 0x0000, 0x0039, 0x0000,
+ 0x0001,
+};
+
+#define WM8510_POWER1_BIASEN 0x08
+#define WM8510_POWER1_BUFIOEN 0x10
+
+/*
+ * read wm8510 register cache
+ */
+static inline unsigned int wm8510_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg == WM8510_RESET)
+ return 0;
+ if (reg >= WM8510_CACHEREGNUM)
+ return -1;
+ return cache[reg];
+}
+
+/*
+ * write wm8510 register cache
+ */
+static inline void wm8510_write_reg_cache(struct snd_soc_codec *codec,
+ u16 reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= WM8510_CACHEREGNUM)
+ return;
+ cache[reg] = value;
+}
+
+/*
+ * write to the WM8510 register space
+ */
+static int wm8510_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ /* data is
+ * D15..D9 WM8510 register offset
+ * D8...D0 register data
+ */
+ data[0] = (reg << 1) | ((value >> 8) & 0x0001);
+ data[1] = value & 0x00ff;
+
+ wm8510_write_reg_cache(codec, reg, value);
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+#define wm8510_reset(c) wm8510_write(c, WM8510_RESET, 0)
+
+static const char *wm8510_companding[] = { "Off", "NC", "u-law", "A-law" };
+static const char *wm8510_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" };
+static const char *wm8510_alc[] = { "ALC", "Limiter" };
+
+static const struct soc_enum wm8510_enum[] = {
+ SOC_ENUM_SINGLE(WM8510_COMP, 1, 4, wm8510_companding), /* adc */
+ SOC_ENUM_SINGLE(WM8510_COMP, 3, 4, wm8510_companding), /* dac */
+ SOC_ENUM_SINGLE(WM8510_DAC, 4, 4, wm8510_deemp),
+ SOC_ENUM_SINGLE(WM8510_ALC3, 8, 2, wm8510_alc),
+};
+
+static const struct snd_kcontrol_new wm8510_snd_controls[] = {
+
+SOC_SINGLE("Digital Loopback Switch", WM8510_COMP, 0, 1, 0),
+
+SOC_ENUM("DAC Companding", wm8510_enum[1]),
+SOC_ENUM("ADC Companding", wm8510_enum[0]),
+
+SOC_ENUM("Playback De-emphasis", wm8510_enum[2]),
+SOC_SINGLE("DAC Inversion Switch", WM8510_DAC, 0, 1, 0),
+
+SOC_SINGLE("Master Playback Volume", WM8510_DACVOL, 0, 127, 0),
+
+SOC_SINGLE("High Pass Filter Switch", WM8510_ADC, 8, 1, 0),
+SOC_SINGLE("High Pass Cut Off", WM8510_ADC, 4, 7, 0),
+SOC_SINGLE("ADC Inversion Switch", WM8510_COMP, 0, 1, 0),
+
+SOC_SINGLE("Capture Volume", WM8510_ADCVOL, 0, 127, 0),
+
+SOC_SINGLE("DAC Playback Limiter Switch", WM8510_DACLIM1, 8, 1, 0),
+SOC_SINGLE("DAC Playback Limiter Decay", WM8510_DACLIM1, 4, 15, 0),
+SOC_SINGLE("DAC Playback Limiter Attack", WM8510_DACLIM1, 0, 15, 0),
+
+SOC_SINGLE("DAC Playback Limiter Threshold", WM8510_DACLIM2, 4, 7, 0),
+SOC_SINGLE("DAC Playback Limiter Boost", WM8510_DACLIM2, 0, 15, 0),
+
+SOC_SINGLE("ALC Enable Switch", WM8510_ALC1, 8, 1, 0),
+SOC_SINGLE("ALC Capture Max Gain", WM8510_ALC1, 3, 7, 0),
+SOC_SINGLE("ALC Capture Min Gain", WM8510_ALC1, 0, 7, 0),
+
+SOC_SINGLE("ALC Capture ZC Switch", WM8510_ALC2, 8, 1, 0),
+SOC_SINGLE("ALC Capture Hold", WM8510_ALC2, 4, 7, 0),
+SOC_SINGLE("ALC Capture Target", WM8510_ALC2, 0, 15, 0),
+
+SOC_ENUM("ALC Capture Mode", wm8510_enum[3]),
+SOC_SINGLE("ALC Capture Decay", WM8510_ALC3, 4, 15, 0),
+SOC_SINGLE("ALC Capture Attack", WM8510_ALC3, 0, 15, 0),
+
+SOC_SINGLE("ALC Capture Noise Gate Switch", WM8510_NGATE, 3, 1, 0),
+SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8510_NGATE, 0, 7, 0),
+
+SOC_SINGLE("Capture PGA ZC Switch", WM8510_INPPGA, 7, 1, 0),
+SOC_SINGLE("Capture PGA Volume", WM8510_INPPGA, 0, 63, 0),
+
+SOC_SINGLE("Speaker Playback ZC Switch", WM8510_SPKVOL, 7, 1, 0),
+SOC_SINGLE("Speaker Playback Switch", WM8510_SPKVOL, 6, 1, 1),
+SOC_SINGLE("Speaker Playback Volume", WM8510_SPKVOL, 0, 63, 0),
+SOC_SINGLE("Speaker Boost", WM8510_OUTPUT, 2, 1, 0),
+
+SOC_SINGLE("Capture Boost(+20dB)", WM8510_ADCBOOST, 8, 1, 0),
+SOC_SINGLE("Mono Playback Switch", WM8510_MONOMIX, 6, 1, 1),
+};
+
+/* add non dapm controls */
+static int wm8510_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8510_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8510_snd_controls[i], codec,
+ NULL));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/* Speaker Output Mixer */
+static const struct snd_kcontrol_new wm8510_speaker_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_SPKMIX, 1, 1, 0),
+SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_SPKMIX, 5, 1, 0),
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_SPKMIX, 0, 1, 0),
+};
+
+/* Mono Output Mixer */
+static const struct snd_kcontrol_new wm8510_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_MONOMIX, 1, 1, 0),
+SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_MONOMIX, 2, 1, 0),
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_MONOMIX, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8510_boost_controls[] = {
+SOC_DAPM_SINGLE("Mic PGA Switch", WM8510_INPPGA, 6, 1, 1),
+SOC_DAPM_SINGLE("Aux Volume", WM8510_ADCBOOST, 0, 7, 0),
+SOC_DAPM_SINGLE("Mic Volume", WM8510_ADCBOOST, 4, 7, 0),
+};
+
+static const struct snd_kcontrol_new wm8510_micpga_controls[] = {
+SOC_DAPM_SINGLE("MICP Switch", WM8510_INPUT, 0, 1, 0),
+SOC_DAPM_SINGLE("MICN Switch", WM8510_INPUT, 1, 1, 0),
+SOC_DAPM_SINGLE("AUX Switch", WM8510_INPUT, 2, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8510_dapm_widgets[] = {
+SND_SOC_DAPM_MIXER("Speaker Mixer", WM8510_POWER3, 2, 0,
+ &wm8510_speaker_mixer_controls[0],
+ ARRAY_SIZE(wm8510_speaker_mixer_controls)),
+SND_SOC_DAPM_MIXER("Mono Mixer", WM8510_POWER3, 3, 0,
+ &wm8510_mono_mixer_controls[0],
+ ARRAY_SIZE(wm8510_mono_mixer_controls)),
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8510_POWER3, 0, 0),
+SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8510_POWER2, 0, 0),
+SND_SOC_DAPM_PGA("Aux Input", WM8510_POWER1, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("SpkN Out", WM8510_POWER3, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("SpkP Out", WM8510_POWER3, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Mono Out", WM8510_POWER3, 7, 0, NULL, 0),
+
+SND_SOC_DAPM_MIXER("Mic PGA", WM8510_POWER2, 2, 0,
+ &wm8510_micpga_controls[0],
+ ARRAY_SIZE(wm8510_micpga_controls)),
+SND_SOC_DAPM_MIXER("Boost Mixer", WM8510_POWER2, 4, 0,
+ &wm8510_boost_controls[0],
+ ARRAY_SIZE(wm8510_boost_controls)),
+
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8510_POWER1, 4, 0),
+
+SND_SOC_DAPM_INPUT("MICN"),
+SND_SOC_DAPM_INPUT("MICP"),
+SND_SOC_DAPM_INPUT("AUX"),
+SND_SOC_DAPM_OUTPUT("MONOOUT"),
+SND_SOC_DAPM_OUTPUT("SPKOUTP"),
+SND_SOC_DAPM_OUTPUT("SPKOUTN"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Mono output mixer */
+ {"Mono Mixer", "PCM Playback Switch", "DAC"},
+ {"Mono Mixer", "Aux Playback Switch", "Aux Input"},
+ {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"},
+
+ /* Speaker output mixer */
+ {"Speaker Mixer", "PCM Playback Switch", "DAC"},
+ {"Speaker Mixer", "Aux Playback Switch", "Aux Input"},
+ {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"},
+
+ /* Outputs */
+ {"Mono Out", NULL, "Mono Mixer"},
+ {"MONOOUT", NULL, "Mono Out"},
+ {"SpkN Out", NULL, "Speaker Mixer"},
+ {"SpkP Out", NULL, "Speaker Mixer"},
+ {"SPKOUTN", NULL, "SpkN Out"},
+ {"SPKOUTP", NULL, "SpkP Out"},
+
+ /* Microphone PGA */
+ {"Mic PGA", "MICN Switch", "MICN"},
+ {"Mic PGA", "MICP Switch", "MICP"},
+ { "Mic PGA", "AUX Switch", "Aux Input" },
+
+ /* Boost Mixer */
+ {"Boost Mixer", "Mic PGA Switch", "Mic PGA"},
+ {"Boost Mixer", "Mic Volume", "MICP"},
+ {"Boost Mixer", "Aux Volume", "Aux Input"},
+
+ {"ADC", NULL, "Boost Mixer"},
+};
+
+static int wm8510_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm8510_dapm_widgets,
+ ARRAY_SIZE(wm8510_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+struct pll_ {
+ unsigned int pre_div:4; /* prescale - 1 */
+ unsigned int n:4;
+ unsigned int k;
+};
+
+static struct pll_ pll_div;
+
+/* The size in bits of the pll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 24) * 10)
+
+static void pll_factors(unsigned int target, unsigned int source)
+{
+ unsigned long long Kpart;
+ unsigned int K, Ndiv, Nmod;
+
+ Ndiv = target / source;
+ if (Ndiv < 6) {
+ source >>= 1;
+ pll_div.pre_div = 1;
+ Ndiv = target / source;
+ } else
+ pll_div.pre_div = 0;
+
+ if ((Ndiv < 6) || (Ndiv > 12))
+ printk(KERN_WARNING
+ "WM8510 N value %d outwith recommended range!d\n",
+ Ndiv);
+
+ pll_div.n = Ndiv;
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ /* Check if we need to round */
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ K /= 10;
+
+ pll_div.k = K;
+}
+
+static int wm8510_set_dai_pll(struct snd_soc_dai *codec_dai,
+ int pll_id, unsigned int freq_in, unsigned int freq_out)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ if (freq_in == 0 || freq_out == 0) {
+ /* Clock CODEC directly from MCLK */
+ reg = wm8510_read_reg_cache(codec, WM8510_CLOCK);
+ wm8510_write(codec, WM8510_CLOCK, reg & 0x0ff);
+
+ /* Turn off PLL */
+ reg = wm8510_read_reg_cache(codec, WM8510_POWER1);
+ wm8510_write(codec, WM8510_POWER1, reg & 0x1df);
+ return 0;
+ }
+
+ pll_factors(freq_out*8, freq_in);
+
+ wm8510_write(codec, WM8510_PLLN, (pll_div.pre_div << 4) | pll_div.n);
+ wm8510_write(codec, WM8510_PLLK1, pll_div.k >> 18);
+ wm8510_write(codec, WM8510_PLLK2, (pll_div.k >> 9) & 0x1ff);
+ wm8510_write(codec, WM8510_PLLK3, pll_div.k & 0x1ff);
+ reg = wm8510_read_reg_cache(codec, WM8510_POWER1);
+ wm8510_write(codec, WM8510_POWER1, reg | 0x020);
+
+ /* Run CODEC from PLL instead of MCLK */
+ reg = wm8510_read_reg_cache(codec, WM8510_CLOCK);
+ wm8510_write(codec, WM8510_CLOCK, reg | 0x100);
+
+ return 0;
+}
+
+/*
+ * Configure WM8510 clock dividers.
+ */
+static int wm8510_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ switch (div_id) {
+ case WM8510_OPCLKDIV:
+ reg = wm8510_read_reg_cache(codec, WM8510_GPIO) & 0x1cf;
+ wm8510_write(codec, WM8510_GPIO, reg | div);
+ break;
+ case WM8510_MCLKDIV:
+ reg = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1f;
+ wm8510_write(codec, WM8510_CLOCK, reg | div);
+ break;
+ case WM8510_ADCCLK:
+ reg = wm8510_read_reg_cache(codec, WM8510_ADC) & 0x1f7;
+ wm8510_write(codec, WM8510_ADC, reg | div);
+ break;
+ case WM8510_DACCLK:
+ reg = wm8510_read_reg_cache(codec, WM8510_DAC) & 0x1f7;
+ wm8510_write(codec, WM8510_DAC, reg | div);
+ break;
+ case WM8510_BCLKDIV:
+ reg = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1e3;
+ wm8510_write(codec, WM8510_CLOCK, reg | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm8510_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+ u16 clk = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1fe;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ clk |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0010;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0008;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x00018;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0180;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0100;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0080;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8510_write(codec, WM8510_IFACE, iface);
+ wm8510_write(codec, WM8510_CLOCK, clk);
+ return 0;
+}
+
+static int wm8510_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 iface = wm8510_read_reg_cache(codec, WM8510_IFACE) & 0x19f;
+ u16 adn = wm8510_read_reg_cache(codec, WM8510_ADD) & 0x1f1;
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x0020;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x0040;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= 0x0060;
+ break;
+ }
+
+ /* filter coefficient */
+ switch (params_rate(params)) {
+ case SNDRV_PCM_RATE_8000:
+ adn |= 0x5 << 1;
+ break;
+ case SNDRV_PCM_RATE_11025:
+ adn |= 0x4 << 1;
+ break;
+ case SNDRV_PCM_RATE_16000:
+ adn |= 0x3 << 1;
+ break;
+ case SNDRV_PCM_RATE_22050:
+ adn |= 0x2 << 1;
+ break;
+ case SNDRV_PCM_RATE_32000:
+ adn |= 0x1 << 1;
+ break;
+ case SNDRV_PCM_RATE_44100:
+ case SNDRV_PCM_RATE_48000:
+ break;
+ }
+
+ wm8510_write(codec, WM8510_IFACE, iface);
+ wm8510_write(codec, WM8510_ADD, adn);
+ return 0;
+}
+
+static int wm8510_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = wm8510_read_reg_cache(codec, WM8510_DAC) & 0xffbf;
+
+ if (mute)
+ wm8510_write(codec, WM8510_DAC, mute_reg | 0x40);
+ else
+ wm8510_write(codec, WM8510_DAC, mute_reg);
+ return 0;
+}
+
+/* liam need to make this lower power with dapm */
+static int wm8510_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 power1 = wm8510_read_reg_cache(codec, WM8510_POWER1) & ~0x3;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ power1 |= 0x1; /* VMID 50k */
+ wm8510_write(codec, WM8510_POWER1, power1);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ power1 |= WM8510_POWER1_BIASEN | WM8510_POWER1_BUFIOEN;
+
+ if (codec->bias_level == SND_SOC_BIAS_OFF) {
+ /* Initial cap charge at VMID 5k */
+ wm8510_write(codec, WM8510_POWER1, power1 | 0x3);
+ mdelay(100);
+ }
+
+ power1 |= 0x2; /* VMID 500k */
+ wm8510_write(codec, WM8510_POWER1, power1);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ wm8510_write(codec, WM8510_POWER1, 0);
+ wm8510_write(codec, WM8510_POWER2, 0);
+ wm8510_write(codec, WM8510_POWER3, 0);
+ break;
+ }
+
+ codec->bias_level = level;
+ return 0;
+}
+
+#define WM8510_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+#define WM8510_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+struct snd_soc_dai wm8510_dai = {
+ .name = "WM8510 HiFi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8510_RATES,
+ .formats = WM8510_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8510_RATES,
+ .formats = WM8510_FORMATS,},
+ .ops = {
+ .hw_params = wm8510_pcm_hw_params,
+ },
+ .dai_ops = {
+ .digital_mute = wm8510_mute,
+ .set_fmt = wm8510_set_dai_fmt,
+ .set_clkdiv = wm8510_set_dai_clkdiv,
+ .set_pll = wm8510_set_dai_pll,
+ },
+};
+EXPORT_SYMBOL_GPL(wm8510_dai);
+
+static int wm8510_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ wm8510_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8510_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8510_reg); i++) {
+ data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+ wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ wm8510_set_bias_level(codec, codec->suspend_bias_level);
+ return 0;
+}
+
+/*
+ * initialise the WM8510 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int wm8510_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret = 0;
+
+ codec->name = "WM8510";
+ codec->owner = THIS_MODULE;
+ codec->read = wm8510_read_reg_cache;
+ codec->write = wm8510_write;
+ codec->set_bias_level = wm8510_set_bias_level;
+ codec->dai = &wm8510_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = ARRAY_SIZE(wm8510_reg);
+ codec->reg_cache = kmemdup(wm8510_reg, sizeof(wm8510_reg), GFP_KERNEL);
+
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ wm8510_reset(codec);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8510: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* power on device */
+ codec->bias_level = SND_SOC_BIAS_OFF;
+ wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ wm8510_add_controls(codec);
+ wm8510_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8510: failed to register card\n");
+ goto card_err;
+ }
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+ return ret;
+}
+
+static struct snd_soc_device *wm8510_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+
+/*
+ * WM8510 2 wire address is 0x1a
+ */
+
+static int wm8510_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = wm8510_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = wm8510_init(socdev);
+ if (ret < 0)
+ pr_err("failed to initialise WM8510\n");
+
+ return ret;
+}
+
+static int wm8510_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+static const struct i2c_device_id wm8510_i2c_id[] = {
+ { "wm8510", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8510_i2c_id);
+
+static struct i2c_driver wm8510_i2c_driver = {
+ .driver = {
+ .name = "WM8510 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8510_i2c_probe,
+ .remove = wm8510_i2c_remove,
+ .id_table = wm8510_i2c_id,
+};
+
+static int wm8510_add_i2c_device(struct platform_device *pdev,
+ const struct wm8510_setup_data *setup)
+{
+ struct i2c_board_info info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+ int ret;
+
+ ret = i2c_add_driver(&wm8510_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ return ret;
+ }
+
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ info.addr = setup->i2c_address;
+ strlcpy(info.type, "wm8510", I2C_NAME_SIZE);
+
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "can't get i2c adapter %d\n",
+ setup->i2c_bus);
+ goto err_driver;
+ }
+
+ client = i2c_new_device(adapter, &info);
+ i2c_put_adapter(adapter);
+ if (!client) {
+ dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
+ (unsigned int)info.addr);
+ goto err_driver;
+ }
+
+ return 0;
+
+err_driver:
+ i2c_del_driver(&wm8510_i2c_driver);
+ return -ENODEV;
+}
+#endif
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8510_spi_probe(struct spi_device *spi)
+{
+ struct snd_soc_device *socdev = wm8510_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ codec->control_data = spi;
+
+ ret = wm8510_init(socdev);
+ if (ret < 0)
+ dev_err(&spi->dev, "failed to initialise WM8510\n");
+
+ return ret;
+}
+
+static int __devexit wm8510_spi_remove(struct spi_device *spi)
+{
+ return 0;
+}
+
+static struct spi_driver wm8510_spi_driver = {
+ .driver = {
+ .name = "wm8510",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8510_spi_probe,
+ .remove = __devexit_p(wm8510_spi_remove),
+};
+
+static int wm8510_spi_write(struct spi_device *spi, const char *data, int len)
+{
+ struct spi_transfer t;
+ struct spi_message m;
+ u8 msg[2];
+
+ if (len <= 0)
+ return 0;
+
+ msg[0] = data[0];
+ msg[1] = data[1];
+
+ spi_message_init(&m);
+ memset(&t, 0, (sizeof t));
+
+ t.tx_buf = &msg[0];
+ t.len = len;
+
+ spi_message_add_tail(&t, &m);
+ spi_sync(spi, &m);
+
+ return len;
+}
+#endif /* CONFIG_SPI_MASTER */
+
+static int wm8510_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct wm8510_setup_data *setup;
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ pr_info("WM8510 Audio Codec %s", WM8510_VERSION);
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ wm8510_socdev = socdev;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = wm8510_add_i2c_device(pdev, setup);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ if (setup->spi) {
+ codec->hw_write = (hw_write_t)wm8510_spi_write;
+ ret = spi_register_driver(&wm8510_spi_driver);
+ if (ret != 0)
+ printk(KERN_ERR "can't add spi driver");
+ }
+#endif
+
+ if (ret != 0)
+ kfree(codec);
+ return ret;
+}
+
+/* power down chip */
+static int wm8510_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec->control_data)
+ wm8510_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_unregister_device(codec->control_data);
+ i2c_del_driver(&wm8510_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8510_spi_driver);
+#endif
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8510 = {
+ .probe = wm8510_probe,
+ .remove = wm8510_remove,
+ .suspend = wm8510_suspend,
+ .resume = wm8510_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8510);
+
+MODULE_DESCRIPTION("ASoC WM8510 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8510.h b/sound/soc/codecs/wm8510.h
new file mode 100644
index 0000000..bdefcf5
--- /dev/null
+++ b/sound/soc/codecs/wm8510.h
@@ -0,0 +1,105 @@
+/*
+ * wm8510.h -- WM8510 Soc Audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8510_H
+#define _WM8510_H
+
+/* WM8510 register space */
+
+#define WM8510_RESET 0x0
+#define WM8510_POWER1 0x1
+#define WM8510_POWER2 0x2
+#define WM8510_POWER3 0x3
+#define WM8510_IFACE 0x4
+#define WM8510_COMP 0x5
+#define WM8510_CLOCK 0x6
+#define WM8510_ADD 0x7
+#define WM8510_GPIO 0x8
+#define WM8510_DAC 0xa
+#define WM8510_DACVOL 0xb
+#define WM8510_ADC 0xe
+#define WM8510_ADCVOL 0xf
+#define WM8510_EQ1 0x12
+#define WM8510_EQ2 0x13
+#define WM8510_EQ3 0x14
+#define WM8510_EQ4 0x15
+#define WM8510_EQ5 0x16
+#define WM8510_DACLIM1 0x18
+#define WM8510_DACLIM2 0x19
+#define WM8510_NOTCH1 0x1b
+#define WM8510_NOTCH2 0x1c
+#define WM8510_NOTCH3 0x1d
+#define WM8510_NOTCH4 0x1e
+#define WM8510_ALC1 0x20
+#define WM8510_ALC2 0x21
+#define WM8510_ALC3 0x22
+#define WM8510_NGATE 0x23
+#define WM8510_PLLN 0x24
+#define WM8510_PLLK1 0x25
+#define WM8510_PLLK2 0x26
+#define WM8510_PLLK3 0x27
+#define WM8510_ATTEN 0x28
+#define WM8510_INPUT 0x2c
+#define WM8510_INPPGA 0x2d
+#define WM8510_ADCBOOST 0x2f
+#define WM8510_OUTPUT 0x31
+#define WM8510_SPKMIX 0x32
+#define WM8510_SPKVOL 0x36
+#define WM8510_MONOMIX 0x38
+
+#define WM8510_CACHEREGNUM 57
+
+/* Clock divider Id's */
+#define WM8510_OPCLKDIV 0
+#define WM8510_MCLKDIV 1
+#define WM8510_ADCCLK 2
+#define WM8510_DACCLK 3
+#define WM8510_BCLKDIV 4
+
+/* DAC clock dividers */
+#define WM8510_DACCLK_F2 (1 << 3)
+#define WM8510_DACCLK_F4 (0 << 3)
+
+/* ADC clock dividers */
+#define WM8510_ADCCLK_F2 (1 << 3)
+#define WM8510_ADCCLK_F4 (0 << 3)
+
+/* PLL Out dividers */
+#define WM8510_OPCLKDIV_1 (0 << 4)
+#define WM8510_OPCLKDIV_2 (1 << 4)
+#define WM8510_OPCLKDIV_3 (2 << 4)
+#define WM8510_OPCLKDIV_4 (3 << 4)
+
+/* BCLK clock dividers */
+#define WM8510_BCLKDIV_1 (0 << 2)
+#define WM8510_BCLKDIV_2 (1 << 2)
+#define WM8510_BCLKDIV_4 (2 << 2)
+#define WM8510_BCLKDIV_8 (3 << 2)
+#define WM8510_BCLKDIV_16 (4 << 2)
+#define WM8510_BCLKDIV_32 (5 << 2)
+
+/* MCLK clock dividers */
+#define WM8510_MCLKDIV_1 (0 << 5)
+#define WM8510_MCLKDIV_1_5 (1 << 5)
+#define WM8510_MCLKDIV_2 (2 << 5)
+#define WM8510_MCLKDIV_3 (3 << 5)
+#define WM8510_MCLKDIV_4 (4 << 5)
+#define WM8510_MCLKDIV_6 (5 << 5)
+#define WM8510_MCLKDIV_8 (6 << 5)
+#define WM8510_MCLKDIV_12 (7 << 5)
+
+struct wm8510_setup_data {
+ int spi;
+ int i2c_bus;
+ unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai wm8510_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8510;
+
+#endif
diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c
new file mode 100644
index 0000000..627ebfb
--- /dev/null
+++ b/sound/soc/codecs/wm8580.c
@@ -0,0 +1,1053 @@
+/*
+ * wm8580.c -- WM8580 ALSA Soc Audio driver
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * Notes:
+ * The WM8580 is a multichannel codec with S/PDIF support, featuring six
+ * DAC channels and two ADC channels.
+ *
+ * Currently only the primary audio interface is supported - S/PDIF and
+ * the secondary audio interfaces are not.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.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/tlv.h>
+#include <sound/initval.h>
+#include <asm/div64.h>
+
+#include "wm8580.h"
+
+#define WM8580_VERSION "0.1"
+
+struct pll_state {
+ unsigned int in;
+ unsigned int out;
+};
+
+/* codec private data */
+struct wm8580_priv {
+ struct pll_state a;
+ struct pll_state b;
+};
+
+/* WM8580 register space */
+#define WM8580_PLLA1 0x00
+#define WM8580_PLLA2 0x01
+#define WM8580_PLLA3 0x02
+#define WM8580_PLLA4 0x03
+#define WM8580_PLLB1 0x04
+#define WM8580_PLLB2 0x05
+#define WM8580_PLLB3 0x06
+#define WM8580_PLLB4 0x07
+#define WM8580_CLKSEL 0x08
+#define WM8580_PAIF1 0x09
+#define WM8580_PAIF2 0x0A
+#define WM8580_SAIF1 0x0B
+#define WM8580_PAIF3 0x0C
+#define WM8580_PAIF4 0x0D
+#define WM8580_SAIF2 0x0E
+#define WM8580_DAC_CONTROL1 0x0F
+#define WM8580_DAC_CONTROL2 0x10
+#define WM8580_DAC_CONTROL3 0x11
+#define WM8580_DAC_CONTROL4 0x12
+#define WM8580_DAC_CONTROL5 0x13
+#define WM8580_DIGITAL_ATTENUATION_DACL1 0x14
+#define WM8580_DIGITAL_ATTENUATION_DACR1 0x15
+#define WM8580_DIGITAL_ATTENUATION_DACL2 0x16
+#define WM8580_DIGITAL_ATTENUATION_DACR2 0x17
+#define WM8580_DIGITAL_ATTENUATION_DACL3 0x18
+#define WM8580_DIGITAL_ATTENUATION_DACR3 0x19
+#define WM8580_MASTER_DIGITAL_ATTENUATION 0x1C
+#define WM8580_ADC_CONTROL1 0x1D
+#define WM8580_SPDTXCHAN0 0x1E
+#define WM8580_SPDTXCHAN1 0x1F
+#define WM8580_SPDTXCHAN2 0x20
+#define WM8580_SPDTXCHAN3 0x21
+#define WM8580_SPDTXCHAN4 0x22
+#define WM8580_SPDTXCHAN5 0x23
+#define WM8580_SPDMODE 0x24
+#define WM8580_INTMASK 0x25
+#define WM8580_GPO1 0x26
+#define WM8580_GPO2 0x27
+#define WM8580_GPO3 0x28
+#define WM8580_GPO4 0x29
+#define WM8580_GPO5 0x2A
+#define WM8580_INTSTAT 0x2B
+#define WM8580_SPDRXCHAN1 0x2C
+#define WM8580_SPDRXCHAN2 0x2D
+#define WM8580_SPDRXCHAN3 0x2E
+#define WM8580_SPDRXCHAN4 0x2F
+#define WM8580_SPDRXCHAN5 0x30
+#define WM8580_SPDSTAT 0x31
+#define WM8580_PWRDN1 0x32
+#define WM8580_PWRDN2 0x33
+#define WM8580_READBACK 0x34
+#define WM8580_RESET 0x35
+
+/* PLLB4 (register 7h) */
+#define WM8580_PLLB4_MCLKOUTSRC_MASK 0x60
+#define WM8580_PLLB4_MCLKOUTSRC_PLLA 0x20
+#define WM8580_PLLB4_MCLKOUTSRC_PLLB 0x40
+#define WM8580_PLLB4_MCLKOUTSRC_OSC 0x60
+
+#define WM8580_PLLB4_CLKOUTSRC_MASK 0x180
+#define WM8580_PLLB4_CLKOUTSRC_PLLACLK 0x080
+#define WM8580_PLLB4_CLKOUTSRC_PLLBCLK 0x100
+#define WM8580_PLLB4_CLKOUTSRC_OSCCLK 0x180
+
+/* CLKSEL (register 8h) */
+#define WM8580_CLKSEL_DAC_CLKSEL_MASK 0x03
+#define WM8580_CLKSEL_DAC_CLKSEL_PLLA 0x01
+#define WM8580_CLKSEL_DAC_CLKSEL_PLLB 0x02
+
+/* AIF control 1 (registers 9h-bh) */
+#define WM8580_AIF_RATE_MASK 0x7
+#define WM8580_AIF_RATE_128 0x0
+#define WM8580_AIF_RATE_192 0x1
+#define WM8580_AIF_RATE_256 0x2
+#define WM8580_AIF_RATE_384 0x3
+#define WM8580_AIF_RATE_512 0x4
+#define WM8580_AIF_RATE_768 0x5
+#define WM8580_AIF_RATE_1152 0x6
+
+#define WM8580_AIF_BCLKSEL_MASK 0x18
+#define WM8580_AIF_BCLKSEL_64 0x00
+#define WM8580_AIF_BCLKSEL_128 0x08
+#define WM8580_AIF_BCLKSEL_256 0x10
+#define WM8580_AIF_BCLKSEL_SYSCLK 0x18
+
+#define WM8580_AIF_MS 0x20
+
+#define WM8580_AIF_CLKSRC_MASK 0xc0
+#define WM8580_AIF_CLKSRC_PLLA 0x40
+#define WM8580_AIF_CLKSRC_PLLB 0x40
+#define WM8580_AIF_CLKSRC_MCLK 0xc0
+
+/* AIF control 2 (registers ch-eh) */
+#define WM8580_AIF_FMT_MASK 0x03
+#define WM8580_AIF_FMT_RIGHTJ 0x00
+#define WM8580_AIF_FMT_LEFTJ 0x01
+#define WM8580_AIF_FMT_I2S 0x02
+#define WM8580_AIF_FMT_DSP 0x03
+
+#define WM8580_AIF_LENGTH_MASK 0x0c
+#define WM8580_AIF_LENGTH_16 0x00
+#define WM8580_AIF_LENGTH_20 0x04
+#define WM8580_AIF_LENGTH_24 0x08
+#define WM8580_AIF_LENGTH_32 0x0c
+
+#define WM8580_AIF_LRP 0x10
+#define WM8580_AIF_BCP 0x20
+
+/* Powerdown Register 1 (register 32h) */
+#define WM8580_PWRDN1_PWDN 0x001
+#define WM8580_PWRDN1_ALLDACPD 0x040
+
+/* Powerdown Register 2 (register 33h) */
+#define WM8580_PWRDN2_OSSCPD 0x001
+#define WM8580_PWRDN2_PLLAPD 0x002
+#define WM8580_PWRDN2_PLLBPD 0x004
+#define WM8580_PWRDN2_SPDIFPD 0x008
+#define WM8580_PWRDN2_SPDIFTXD 0x010
+#define WM8580_PWRDN2_SPDIFRXD 0x020
+
+#define WM8580_DAC_CONTROL5_MUTEALL 0x10
+
+/*
+ * wm8580 register cache
+ * We can't read the WM8580 register space when we
+ * are using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8580_reg[] = {
+ 0x0121, 0x017e, 0x007d, 0x0014, /*R3*/
+ 0x0121, 0x017e, 0x007d, 0x0194, /*R7*/
+ 0x001c, 0x0002, 0x0002, 0x00c2, /*R11*/
+ 0x0182, 0x0082, 0x000a, 0x0024, /*R15*/
+ 0x0009, 0x0000, 0x00ff, 0x0000, /*R19*/
+ 0x00ff, 0x00ff, 0x00ff, 0x00ff, /*R23*/
+ 0x00ff, 0x00ff, 0x00ff, 0x00ff, /*R27*/
+ 0x01f0, 0x0040, 0x0000, 0x0000, /*R31(0x1F)*/
+ 0x0000, 0x0000, 0x0031, 0x000b, /*R35*/
+ 0x0039, 0x0000, 0x0010, 0x0032, /*R39*/
+ 0x0054, 0x0076, 0x0098, 0x0000, /*R43(0x2B)*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R47*/
+ 0x0000, 0x0000, 0x005e, 0x003e, /*R51(0x33)*/
+ 0x0000, 0x0000 /*R53*/
+};
+
+/*
+ * read wm8580 register cache
+ */
+static inline unsigned int wm8580_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ BUG_ON(reg > ARRAY_SIZE(wm8580_reg));
+ return cache[reg];
+}
+
+/*
+ * write wm8580 register cache
+ */
+static inline void wm8580_write_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+
+ cache[reg] = value;
+}
+
+/*
+ * write to the WM8580 register space
+ */
+static int wm8580_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ BUG_ON(reg > ARRAY_SIZE(wm8580_reg));
+
+ /* Registers are 9 bits wide */
+ value &= 0x1ff;
+
+ switch (reg) {
+ case WM8580_RESET:
+ /* Uncached */
+ break;
+ default:
+ if (value == wm8580_read_reg_cache(codec, reg))
+ return 0;
+ }
+
+ /* data is
+ * D15..D9 WM8580 register offset
+ * D8...D0 register data
+ */
+ data[0] = (reg << 1) | ((value >> 8) & 0x0001);
+ data[1] = value & 0x00ff;
+
+ wm8580_write_reg_cache(codec, reg, value);
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+static inline unsigned int wm8580_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ switch (reg) {
+ default:
+ return wm8580_read_reg_cache(codec, reg);
+ }
+}
+
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
+
+static int wm8580_out_vu(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int reg = kcontrol->private_value & 0xff;
+ int reg2 = (kcontrol->private_value >> 24) & 0xff;
+ int ret;
+ u16 val;
+
+ /* Clear the register cache so we write without VU set */
+ wm8580_write_reg_cache(codec, reg, 0);
+ wm8580_write_reg_cache(codec, reg2, 0);
+
+ ret = snd_soc_put_volsw_2r(kcontrol, ucontrol);
+ if (ret < 0)
+ return ret;
+
+ /* Now write again with the volume update bit set */
+ val = wm8580_read_reg_cache(codec, reg);
+ wm8580_write(codec, reg, val | 0x0100);
+
+ val = wm8580_read_reg_cache(codec, reg2);
+ wm8580_write(codec, reg2, val | 0x0100);
+
+ return 0;
+}
+
+#define SOC_WM8580_OUT_DOUBLE_R_TLV(xname, reg_left, reg_right, shift, max, invert, tlv_array) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+ SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw_2r, \
+ .get = snd_soc_get_volsw_2r, .put = wm8580_out_vu, \
+ .private_value = (reg_left) | ((shift) << 8) | \
+ ((max) << 12) | ((invert) << 20) | ((reg_right) << 24) }
+
+static const struct snd_kcontrol_new wm8580_snd_controls[] = {
+SOC_WM8580_OUT_DOUBLE_R_TLV("DAC1 Playback Volume",
+ WM8580_DIGITAL_ATTENUATION_DACL1,
+ WM8580_DIGITAL_ATTENUATION_DACR1,
+ 0, 0xff, 0, dac_tlv),
+SOC_WM8580_OUT_DOUBLE_R_TLV("DAC2 Playback Volume",
+ WM8580_DIGITAL_ATTENUATION_DACL2,
+ WM8580_DIGITAL_ATTENUATION_DACR2,
+ 0, 0xff, 0, dac_tlv),
+SOC_WM8580_OUT_DOUBLE_R_TLV("DAC3 Playback Volume",
+ WM8580_DIGITAL_ATTENUATION_DACL3,
+ WM8580_DIGITAL_ATTENUATION_DACR3,
+ 0, 0xff, 0, dac_tlv),
+
+SOC_SINGLE("DAC1 Deemphasis Switch", WM8580_DAC_CONTROL3, 0, 1, 0),
+SOC_SINGLE("DAC2 Deemphasis Switch", WM8580_DAC_CONTROL3, 1, 1, 0),
+SOC_SINGLE("DAC3 Deemphasis Switch", WM8580_DAC_CONTROL3, 2, 1, 0),
+
+SOC_DOUBLE("DAC1 Invert Switch", WM8580_DAC_CONTROL4, 0, 1, 1, 0),
+SOC_DOUBLE("DAC2 Invert Switch", WM8580_DAC_CONTROL4, 2, 3, 1, 0),
+SOC_DOUBLE("DAC3 Invert Switch", WM8580_DAC_CONTROL4, 4, 5, 1, 0),
+
+SOC_SINGLE("DAC ZC Switch", WM8580_DAC_CONTROL5, 5, 1, 0),
+SOC_SINGLE("DAC1 Switch", WM8580_DAC_CONTROL5, 0, 1, 0),
+SOC_SINGLE("DAC2 Switch", WM8580_DAC_CONTROL5, 1, 1, 0),
+SOC_SINGLE("DAC3 Switch", WM8580_DAC_CONTROL5, 2, 1, 0),
+
+SOC_DOUBLE("ADC Mute Switch", WM8580_ADC_CONTROL1, 0, 1, 1, 0),
+SOC_SINGLE("ADC High-Pass Filter Switch", WM8580_ADC_CONTROL1, 4, 1, 0),
+};
+
+/* Add non-DAPM controls */
+static int wm8580_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8580_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8580_snd_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+static const struct snd_soc_dapm_widget wm8580_dapm_widgets[] = {
+SND_SOC_DAPM_DAC("DAC1", "Playback", WM8580_PWRDN1, 2, 1),
+SND_SOC_DAPM_DAC("DAC2", "Playback", WM8580_PWRDN1, 3, 1),
+SND_SOC_DAPM_DAC("DAC3", "Playback", WM8580_PWRDN1, 4, 1),
+
+SND_SOC_DAPM_OUTPUT("VOUT1L"),
+SND_SOC_DAPM_OUTPUT("VOUT1R"),
+SND_SOC_DAPM_OUTPUT("VOUT2L"),
+SND_SOC_DAPM_OUTPUT("VOUT2R"),
+SND_SOC_DAPM_OUTPUT("VOUT3L"),
+SND_SOC_DAPM_OUTPUT("VOUT3R"),
+
+SND_SOC_DAPM_ADC("ADC", "Capture", WM8580_PWRDN1, 1, 1),
+
+SND_SOC_DAPM_INPUT("AINL"),
+SND_SOC_DAPM_INPUT("AINR"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ { "VOUT1L", NULL, "DAC1" },
+ { "VOUT1R", NULL, "DAC1" },
+
+ { "VOUT2L", NULL, "DAC2" },
+ { "VOUT2R", NULL, "DAC2" },
+
+ { "VOUT3L", NULL, "DAC3" },
+ { "VOUT3R", NULL, "DAC3" },
+
+ { "ADC", NULL, "AINL" },
+ { "ADC", NULL, "AINR" },
+};
+
+static int wm8580_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm8580_dapm_widgets,
+ ARRAY_SIZE(wm8580_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+/* PLL divisors */
+struct _pll_div {
+ u32 prescale:1;
+ u32 postscale:1;
+ u32 freqmode:2;
+ u32 n:4;
+ u32 k:24;
+};
+
+/* The size in bits of the pll divide */
+#define FIXED_PLL_SIZE (1 << 22)
+
+/* PLL rate to output rate divisions */
+static struct {
+ unsigned int div;
+ unsigned int freqmode;
+ unsigned int postscale;
+} post_table[] = {
+ { 2, 0, 0 },
+ { 4, 0, 1 },
+ { 4, 1, 0 },
+ { 8, 1, 1 },
+ { 8, 2, 0 },
+ { 16, 2, 1 },
+ { 12, 3, 0 },
+ { 24, 3, 1 }
+};
+
+static int pll_factors(struct _pll_div *pll_div, unsigned int target,
+ unsigned int source)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod;
+ int i;
+
+ pr_debug("wm8580: PLL %dHz->%dHz\n", source, target);
+
+ /* Scale the output frequency up; the PLL should run in the
+ * region of 90-100MHz.
+ */
+ for (i = 0; i < ARRAY_SIZE(post_table); i++) {
+ if (target * post_table[i].div >= 90000000 &&
+ target * post_table[i].div <= 100000000) {
+ pll_div->freqmode = post_table[i].freqmode;
+ pll_div->postscale = post_table[i].postscale;
+ target *= post_table[i].div;
+ break;
+ }
+ }
+
+ if (i == ARRAY_SIZE(post_table)) {
+ printk(KERN_ERR "wm8580: Unable to scale output frequency "
+ "%u\n", target);
+ return -EINVAL;
+ }
+
+ Ndiv = target / source;
+
+ if (Ndiv < 5) {
+ source /= 2;
+ pll_div->prescale = 1;
+ Ndiv = target / source;
+ } else
+ pll_div->prescale = 0;
+
+ if ((Ndiv < 5) || (Ndiv > 13)) {
+ printk(KERN_ERR
+ "WM8580 N=%d outside supported range\n", Ndiv);
+ return -EINVAL;
+ }
+
+ pll_div->n = Ndiv;
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ pll_div->k = K;
+
+ pr_debug("PLL %x.%x prescale %d freqmode %d postscale %d\n",
+ pll_div->n, pll_div->k, pll_div->prescale, pll_div->freqmode,
+ pll_div->postscale);
+
+ return 0;
+}
+
+static int wm8580_set_dai_pll(struct snd_soc_dai *codec_dai,
+ int pll_id, unsigned int freq_in, unsigned int freq_out)
+{
+ int offset;
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8580_priv *wm8580 = codec->private_data;
+ struct pll_state *state;
+ struct _pll_div pll_div;
+ unsigned int reg;
+ unsigned int pwr_mask;
+ int ret;
+
+ /* GCC isn't able to work out the ifs below for initialising/using
+ * pll_div so suppress warnings.
+ */
+ memset(&pll_div, 0, sizeof(pll_div));
+
+ switch (pll_id) {
+ case WM8580_PLLA:
+ state = &wm8580->a;
+ offset = 0;
+ pwr_mask = WM8580_PWRDN2_PLLAPD;
+ break;
+ case WM8580_PLLB:
+ state = &wm8580->b;
+ offset = 4;
+ pwr_mask = WM8580_PWRDN2_PLLBPD;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ if (freq_in && freq_out) {
+ ret = pll_factors(&pll_div, freq_out, freq_in);
+ if (ret != 0)
+ return ret;
+ }
+
+ state->in = freq_in;
+ state->out = freq_out;
+
+ /* Always disable the PLL - it is not safe to leave it running
+ * while reprogramming it.
+ */
+ reg = wm8580_read(codec, WM8580_PWRDN2);
+ wm8580_write(codec, WM8580_PWRDN2, reg | pwr_mask);
+
+ if (!freq_in || !freq_out)
+ return 0;
+
+ wm8580_write(codec, WM8580_PLLA1 + offset, pll_div.k & 0x1ff);
+ wm8580_write(codec, WM8580_PLLA2 + offset, (pll_div.k >> 9) & 0xff);
+ wm8580_write(codec, WM8580_PLLA3 + offset,
+ (pll_div.k >> 18 & 0xf) | (pll_div.n << 4));
+
+ reg = wm8580_read(codec, WM8580_PLLA4 + offset);
+ reg &= ~0x3f;
+ reg |= pll_div.prescale | pll_div.postscale << 1 |
+ pll_div.freqmode << 4;
+
+ wm8580_write(codec, WM8580_PLLA4 + offset, reg);
+
+ /* All done, turn it on */
+ reg = wm8580_read(codec, WM8580_PWRDN2);
+ wm8580_write(codec, WM8580_PWRDN2, reg & ~pwr_mask);
+
+ return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8580_paif_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai_link *dai = rtd->dai;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 paifb = wm8580_read(codec, WM8580_PAIF3 + dai->codec_dai->id);
+
+ paifb &= ~WM8580_AIF_LENGTH_MASK;
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ paifb |= WM8580_AIF_LENGTH_20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ paifb |= WM8580_AIF_LENGTH_24;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ paifb |= WM8580_AIF_LENGTH_24;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8580_write(codec, WM8580_PAIF3 + dai->codec_dai->id, paifb);
+ return 0;
+}
+
+static int wm8580_set_paif_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ unsigned int aifa;
+ unsigned int aifb;
+ int can_invert_lrclk;
+
+ aifa = wm8580_read(codec, WM8580_PAIF1 + codec_dai->id);
+ aifb = wm8580_read(codec, WM8580_PAIF3 + codec_dai->id);
+
+ aifb &= ~(WM8580_AIF_FMT_MASK | WM8580_AIF_LRP | WM8580_AIF_BCP);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ aifa &= ~WM8580_AIF_MS;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ aifa |= WM8580_AIF_MS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ can_invert_lrclk = 1;
+ aifb |= WM8580_AIF_FMT_I2S;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ can_invert_lrclk = 1;
+ aifb |= WM8580_AIF_FMT_RIGHTJ;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ can_invert_lrclk = 1;
+ aifb |= WM8580_AIF_FMT_LEFTJ;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ can_invert_lrclk = 0;
+ aifb |= WM8580_AIF_FMT_DSP;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ can_invert_lrclk = 0;
+ aifb |= WM8580_AIF_FMT_DSP;
+ aifb |= WM8580_AIF_LRP;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+
+ case SND_SOC_DAIFMT_IB_IF:
+ if (!can_invert_lrclk)
+ return -EINVAL;
+ aifb |= WM8580_AIF_BCP;
+ aifb |= WM8580_AIF_LRP;
+ break;
+
+ case SND_SOC_DAIFMT_IB_NF:
+ aifb |= WM8580_AIF_BCP;
+ break;
+
+ case SND_SOC_DAIFMT_NB_IF:
+ if (!can_invert_lrclk)
+ return -EINVAL;
+ aifb |= WM8580_AIF_LRP;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ wm8580_write(codec, WM8580_PAIF1 + codec_dai->id, aifa);
+ wm8580_write(codec, WM8580_PAIF3 + codec_dai->id, aifb);
+
+ return 0;
+}
+
+static int wm8580_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ unsigned int reg;
+
+ switch (div_id) {
+ case WM8580_MCLK:
+ reg = wm8580_read(codec, WM8580_PLLB4);
+ reg &= ~WM8580_PLLB4_MCLKOUTSRC_MASK;
+
+ switch (div) {
+ case WM8580_CLKSRC_MCLK:
+ /* Input */
+ break;
+
+ case WM8580_CLKSRC_PLLA:
+ reg |= WM8580_PLLB4_MCLKOUTSRC_PLLA;
+ break;
+ case WM8580_CLKSRC_PLLB:
+ reg |= WM8580_PLLB4_MCLKOUTSRC_PLLB;
+ break;
+
+ case WM8580_CLKSRC_OSC:
+ reg |= WM8580_PLLB4_MCLKOUTSRC_OSC;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ wm8580_write(codec, WM8580_PLLB4, reg);
+ break;
+
+ case WM8580_DAC_CLKSEL:
+ reg = wm8580_read(codec, WM8580_CLKSEL);
+ reg &= ~WM8580_CLKSEL_DAC_CLKSEL_MASK;
+
+ switch (div) {
+ case WM8580_CLKSRC_MCLK:
+ break;
+
+ case WM8580_CLKSRC_PLLA:
+ reg |= WM8580_CLKSEL_DAC_CLKSEL_PLLA;
+ break;
+
+ case WM8580_CLKSRC_PLLB:
+ reg |= WM8580_CLKSEL_DAC_CLKSEL_PLLB;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ wm8580_write(codec, WM8580_CLKSEL, reg);
+ break;
+
+ case WM8580_CLKOUTSRC:
+ reg = wm8580_read(codec, WM8580_PLLB4);
+ reg &= ~WM8580_PLLB4_CLKOUTSRC_MASK;
+
+ switch (div) {
+ case WM8580_CLKSRC_NONE:
+ break;
+
+ case WM8580_CLKSRC_PLLA:
+ reg |= WM8580_PLLB4_CLKOUTSRC_PLLACLK;
+ break;
+
+ case WM8580_CLKSRC_PLLB:
+ reg |= WM8580_PLLB4_CLKOUTSRC_PLLBCLK;
+ break;
+
+ case WM8580_CLKSRC_OSC:
+ reg |= WM8580_PLLB4_CLKOUTSRC_OSCCLK;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ wm8580_write(codec, WM8580_PLLB4, reg);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm8580_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ unsigned int reg;
+
+ reg = wm8580_read(codec, WM8580_DAC_CONTROL5);
+
+ if (mute)
+ reg |= WM8580_DAC_CONTROL5_MUTEALL;
+ else
+ reg &= ~WM8580_DAC_CONTROL5_MUTEALL;
+
+ wm8580_write(codec, WM8580_DAC_CONTROL5, reg);
+
+ return 0;
+}
+
+static int wm8580_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg;
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ case SND_SOC_BIAS_STANDBY:
+ break;
+ case SND_SOC_BIAS_OFF:
+ reg = wm8580_read(codec, WM8580_PWRDN1);
+ wm8580_write(codec, WM8580_PWRDN1, reg | WM8580_PWRDN1_PWDN);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define WM8580_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+struct snd_soc_dai wm8580_dai[] = {
+ {
+ .name = "WM8580 PAIFRX",
+ .id = 0,
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 6,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = WM8580_FORMATS,
+ },
+ .ops = {
+ .hw_params = wm8580_paif_hw_params,
+ },
+ .dai_ops = {
+ .set_fmt = wm8580_set_paif_dai_fmt,
+ .set_clkdiv = wm8580_set_dai_clkdiv,
+ .set_pll = wm8580_set_dai_pll,
+ .digital_mute = wm8580_digital_mute,
+ },
+ },
+ {
+ .name = "WM8580 PAIFTX",
+ .id = 1,
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = WM8580_FORMATS,
+ },
+ .ops = {
+ .hw_params = wm8580_paif_hw_params,
+ },
+ .dai_ops = {
+ .set_fmt = wm8580_set_paif_dai_fmt,
+ .set_clkdiv = wm8580_set_dai_clkdiv,
+ .set_pll = wm8580_set_dai_pll,
+ },
+ },
+};
+EXPORT_SYMBOL_GPL(wm8580_dai);
+
+/*
+ * initialise the WM8580 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int wm8580_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret = 0;
+
+ codec->name = "WM8580";
+ codec->owner = THIS_MODULE;
+ codec->read = wm8580_read_reg_cache;
+ codec->write = wm8580_write;
+ codec->set_bias_level = wm8580_set_bias_level;
+ codec->dai = wm8580_dai;
+ codec->num_dai = ARRAY_SIZE(wm8580_dai);
+ codec->reg_cache_size = ARRAY_SIZE(wm8580_reg);
+ codec->reg_cache = kmemdup(wm8580_reg, sizeof(wm8580_reg),
+ GFP_KERNEL);
+
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ /* Get the codec into a known state */
+ wm8580_write(codec, WM8580_RESET, 0);
+
+ /* Power up and get individual control of the DACs */
+ wm8580_write(codec, WM8580_PWRDN1, wm8580_read(codec, WM8580_PWRDN1) &
+ ~(WM8580_PWRDN1_PWDN | WM8580_PWRDN1_ALLDACPD));
+
+ /* Make VMID high impedence */
+ wm8580_write(codec, WM8580_ADC_CONTROL1,
+ wm8580_read(codec, WM8580_ADC_CONTROL1) & ~0x100);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1,
+ SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8580: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ wm8580_add_controls(codec);
+ wm8580_add_widgets(codec);
+
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8580: failed to register card\n");
+ goto card_err;
+ }
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+ return ret;
+}
+
+/* If the i2c layer weren't so broken, we could pass this kind of data
+ around */
+static struct snd_soc_device *wm8580_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+
+/*
+ * WM8580 2 wire address is determined by GPIO5
+ * state during powerup.
+ * low = 0x1a
+ * high = 0x1b
+ */
+static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END };
+
+/* Magic definition of all other variables and things */
+I2C_CLIENT_INSMOD;
+
+static struct i2c_driver wm8580_i2c_driver;
+static struct i2c_client client_template;
+
+static int wm8580_codec_probe(struct i2c_adapter *adap, int addr, int kind)
+{
+ struct snd_soc_device *socdev = wm8580_socdev;
+ struct wm8580_setup_data *setup = socdev->codec_data;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct i2c_client *i2c;
+ int ret;
+
+ if (addr != setup->i2c_address)
+ return -ENODEV;
+
+ client_template.adapter = adap;
+ client_template.addr = addr;
+
+ i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL);
+ if (i2c == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = i2c_attach_client(i2c);
+ if (ret < 0) {
+ dev_err(&i2c->dev, "failed to attach codec at addr %x\n", addr);
+ goto err;
+ }
+
+ ret = wm8580_init(socdev);
+ if (ret < 0) {
+ dev_err(&i2c->dev, "failed to initialise WM8580\n");
+ goto err;
+ }
+
+ return ret;
+
+err:
+ kfree(codec);
+ kfree(i2c);
+ return ret;
+}
+
+static int wm8580_i2c_detach(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ i2c_detach_client(client);
+ kfree(codec->reg_cache);
+ kfree(client);
+ return 0;
+}
+
+static int wm8580_i2c_attach(struct i2c_adapter *adap)
+{
+ return i2c_probe(adap, &addr_data, wm8580_codec_probe);
+}
+
+/* corgi i2c codec control layer */
+static struct i2c_driver wm8580_i2c_driver = {
+ .driver = {
+ .name = "WM8580 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .attach_adapter = wm8580_i2c_attach,
+ .detach_client = wm8580_i2c_detach,
+ .command = NULL,
+};
+
+static struct i2c_client client_template = {
+ .name = "WM8580",
+ .driver = &wm8580_i2c_driver,
+};
+#endif
+
+static int wm8580_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct wm8580_setup_data *setup;
+ struct snd_soc_codec *codec;
+ struct wm8580_priv *wm8580;
+ int ret = 0;
+
+ pr_info("WM8580 Audio Codec %s\n", WM8580_VERSION);
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ wm8580 = kzalloc(sizeof(struct wm8580_priv), GFP_KERNEL);
+ if (wm8580 == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->private_data = wm8580;
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+ wm8580_socdev = socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ normal_i2c[0] = setup->i2c_address;
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = i2c_add_driver(&wm8580_i2c_driver);
+ if (ret != 0)
+ printk(KERN_ERR "can't add i2c driver");
+ }
+#else
+ /* Add other interfaces here */
+#endif
+ return ret;
+}
+
+/* power down chip */
+static int wm8580_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec->control_data)
+ wm8580_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8580_i2c_driver);
+#endif
+ kfree(codec->private_data);
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8580 = {
+ .probe = wm8580_probe,
+ .remove = wm8580_remove,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8580);
+
+MODULE_DESCRIPTION("ASoC WM8580 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8580.h b/sound/soc/codecs/wm8580.h
new file mode 100644
index 0000000..589ddab
--- /dev/null
+++ b/sound/soc/codecs/wm8580.h
@@ -0,0 +1,42 @@
+/*
+ * wm8580.h -- audio driver for WM8580
+ *
+ * Copyright 2008 Samsung Electronics.
+ * Author: Ryu Euiyoul
+ * ryu.real@gmail.com
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#ifndef _WM8580_H
+#define _WM8580_H
+
+#define WM8580_PLLA 1
+#define WM8580_PLLB 2
+
+#define WM8580_MCLK 1
+#define WM8580_DAC_CLKSEL 2
+#define WM8580_CLKOUTSRC 3
+
+#define WM8580_CLKSRC_MCLK 1
+#define WM8580_CLKSRC_PLLA 2
+#define WM8580_CLKSRC_PLLB 3
+#define WM8580_CLKSRC_OSC 4
+#define WM8580_CLKSRC_NONE 5
+
+struct wm8580_setup_data {
+ unsigned short i2c_address;
+};
+
+#define WM8580_DAI_PAIFRX 0
+#define WM8580_DAI_PAIFTX 1
+
+extern struct snd_soc_dai wm8580_dai[];
+extern struct snd_soc_codec_device soc_codec_dev_wm8580;
+
+#endif
+
diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c
new file mode 100644
index 0000000..7f8a7e3
--- /dev/null
+++ b/sound/soc/codecs/wm8731.c
@@ -0,0 +1,797 @@
+/*
+ * wm8731.c -- WM8731 ALSA SoC Audio driver
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on wm8753.c by Liam Girdwood
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.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 "wm8731.h"
+
+#define WM8731_VERSION "0.13"
+
+struct snd_soc_codec_device soc_codec_dev_wm8731;
+
+/* codec private data */
+struct wm8731_priv {
+ unsigned int sysclk;
+};
+
+/*
+ * wm8731 register cache
+ * We can't read the WM8731 register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ * There is no point in caching the reset register
+ */
+static const u16 wm8731_reg[WM8731_CACHEREGNUM] = {
+ 0x0097, 0x0097, 0x0079, 0x0079,
+ 0x000a, 0x0008, 0x009f, 0x000a,
+ 0x0000, 0x0000
+};
+
+/*
+ * read wm8731 register cache
+ */
+static inline unsigned int wm8731_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg == WM8731_RESET)
+ return 0;
+ if (reg >= WM8731_CACHEREGNUM)
+ return -1;
+ return cache[reg];
+}
+
+/*
+ * write wm8731 register cache
+ */
+static inline void wm8731_write_reg_cache(struct snd_soc_codec *codec,
+ u16 reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= WM8731_CACHEREGNUM)
+ return;
+ cache[reg] = value;
+}
+
+/*
+ * write to the WM8731 register space
+ */
+static int wm8731_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ /* data is
+ * D15..D9 WM8731 register offset
+ * D8...D0 register data
+ */
+ data[0] = (reg << 1) | ((value >> 8) & 0x0001);
+ data[1] = value & 0x00ff;
+
+ wm8731_write_reg_cache(codec, reg, value);
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+#define wm8731_reset(c) wm8731_write(c, WM8731_RESET, 0)
+
+static const char *wm8731_input_select[] = {"Line In", "Mic"};
+static const char *wm8731_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+
+static const struct soc_enum wm8731_enum[] = {
+ SOC_ENUM_SINGLE(WM8731_APANA, 2, 2, wm8731_input_select),
+ SOC_ENUM_SINGLE(WM8731_APDIGI, 1, 4, wm8731_deemph),
+};
+
+static const struct snd_kcontrol_new wm8731_snd_controls[] = {
+
+SOC_DOUBLE_R("Master Playback Volume", WM8731_LOUT1V, WM8731_ROUT1V,
+ 0, 127, 0),
+SOC_DOUBLE_R("Master Playback ZC Switch", WM8731_LOUT1V, WM8731_ROUT1V,
+ 7, 1, 0),
+
+SOC_DOUBLE_R("Capture Volume", WM8731_LINVOL, WM8731_RINVOL, 0, 31, 0),
+SOC_DOUBLE_R("Line Capture Switch", WM8731_LINVOL, WM8731_RINVOL, 7, 1, 1),
+
+SOC_SINGLE("Mic Boost (+20dB)", WM8731_APANA, 0, 1, 0),
+SOC_SINGLE("Capture Mic Switch", WM8731_APANA, 1, 1, 1),
+
+SOC_SINGLE("Sidetone Playback Volume", WM8731_APANA, 6, 3, 1),
+
+SOC_SINGLE("ADC High Pass Filter Switch", WM8731_APDIGI, 0, 1, 1),
+SOC_SINGLE("Store DC Offset Switch", WM8731_APDIGI, 4, 1, 0),
+
+SOC_ENUM("Playback De-emphasis", wm8731_enum[1]),
+};
+
+/* add non dapm controls */
+static int wm8731_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8731_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8731_snd_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/* Output Mixer */
+static const struct snd_kcontrol_new wm8731_output_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", WM8731_APANA, 3, 1, 0),
+SOC_DAPM_SINGLE("Mic Sidetone Switch", WM8731_APANA, 5, 1, 0),
+SOC_DAPM_SINGLE("HiFi Playback Switch", WM8731_APANA, 4, 1, 0),
+};
+
+/* Input mux */
+static const struct snd_kcontrol_new wm8731_input_mux_controls =
+SOC_DAPM_ENUM("Input Select", wm8731_enum[0]);
+
+static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = {
+SND_SOC_DAPM_MIXER("Output Mixer", WM8731_PWR, 4, 1,
+ &wm8731_output_mixer_controls[0],
+ ARRAY_SIZE(wm8731_output_mixer_controls)),
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8731_PWR, 3, 1),
+SND_SOC_DAPM_OUTPUT("LOUT"),
+SND_SOC_DAPM_OUTPUT("LHPOUT"),
+SND_SOC_DAPM_OUTPUT("ROUT"),
+SND_SOC_DAPM_OUTPUT("RHPOUT"),
+SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8731_PWR, 2, 1),
+SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, &wm8731_input_mux_controls),
+SND_SOC_DAPM_PGA("Line Input", WM8731_PWR, 0, 1, NULL, 0),
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8731_PWR, 1, 1),
+SND_SOC_DAPM_INPUT("MICIN"),
+SND_SOC_DAPM_INPUT("RLINEIN"),
+SND_SOC_DAPM_INPUT("LLINEIN"),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ /* output mixer */
+ {"Output Mixer", "Line Bypass Switch", "Line Input"},
+ {"Output Mixer", "HiFi Playback Switch", "DAC"},
+ {"Output Mixer", "Mic Sidetone Switch", "Mic Bias"},
+
+ /* outputs */
+ {"RHPOUT", NULL, "Output Mixer"},
+ {"ROUT", NULL, "Output Mixer"},
+ {"LHPOUT", NULL, "Output Mixer"},
+ {"LOUT", NULL, "Output Mixer"},
+
+ /* input mux */
+ {"Input Mux", "Line In", "Line Input"},
+ {"Input Mux", "Mic", "Mic Bias"},
+ {"ADC", NULL, "Input Mux"},
+
+ /* inputs */
+ {"Line Input", NULL, "LLINEIN"},
+ {"Line Input", NULL, "RLINEIN"},
+ {"Mic Bias", NULL, "MICIN"},
+};
+
+static int wm8731_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm8731_dapm_widgets,
+ ARRAY_SIZE(wm8731_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+struct _coeff_div {
+ u32 mclk;
+ u32 rate;
+ u16 fs;
+ u8 sr:4;
+ u8 bosr:1;
+ u8 usb:1;
+};
+
+/* codec mclk clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+ /* 48k */
+ {12288000, 48000, 256, 0x0, 0x0, 0x0},
+ {18432000, 48000, 384, 0x0, 0x1, 0x0},
+ {12000000, 48000, 250, 0x0, 0x0, 0x1},
+
+ /* 32k */
+ {12288000, 32000, 384, 0x6, 0x0, 0x0},
+ {18432000, 32000, 576, 0x6, 0x1, 0x0},
+ {12000000, 32000, 375, 0x6, 0x0, 0x1},
+
+ /* 8k */
+ {12288000, 8000, 1536, 0x3, 0x0, 0x0},
+ {18432000, 8000, 2304, 0x3, 0x1, 0x0},
+ {11289600, 8000, 1408, 0xb, 0x0, 0x0},
+ {16934400, 8000, 2112, 0xb, 0x1, 0x0},
+ {12000000, 8000, 1500, 0x3, 0x0, 0x1},
+
+ /* 96k */
+ {12288000, 96000, 128, 0x7, 0x0, 0x0},
+ {18432000, 96000, 192, 0x7, 0x1, 0x0},
+ {12000000, 96000, 125, 0x7, 0x0, 0x1},
+
+ /* 44.1k */
+ {11289600, 44100, 256, 0x8, 0x0, 0x0},
+ {16934400, 44100, 384, 0x8, 0x1, 0x0},
+ {12000000, 44100, 272, 0x8, 0x1, 0x1},
+
+ /* 88.2k */
+ {11289600, 88200, 128, 0xf, 0x0, 0x0},
+ {16934400, 88200, 192, 0xf, 0x1, 0x0},
+ {12000000, 88200, 136, 0xf, 0x1, 0x1},
+};
+
+static inline int get_coeff(int mclk, int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+ if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+ return i;
+ }
+ return 0;
+}
+
+static int wm8731_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct wm8731_priv *wm8731 = codec->private_data;
+ u16 iface = wm8731_read_reg_cache(codec, WM8731_IFACE) & 0xfff3;
+ int i = get_coeff(wm8731->sysclk, params_rate(params));
+ u16 srate = (coeff_div[i].sr << 2) |
+ (coeff_div[i].bosr << 1) | coeff_div[i].usb;
+
+ wm8731_write(codec, WM8731_SRATE, srate);
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x0008;
+ break;
+ }
+
+ wm8731_write(codec, WM8731_IFACE, iface);
+ return 0;
+}
+
+static int wm8731_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+
+ /* set active */
+ wm8731_write(codec, WM8731_ACTIVE, 0x0001);
+
+ return 0;
+}
+
+static void wm8731_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+
+ /* deactivate */
+ if (!codec->active) {
+ udelay(50);
+ wm8731_write(codec, WM8731_ACTIVE, 0x0);
+ }
+}
+
+static int wm8731_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = wm8731_read_reg_cache(codec, WM8731_APDIGI) & 0xfff7;
+
+ if (mute)
+ wm8731_write(codec, WM8731_APDIGI, mute_reg | 0x8);
+ else
+ wm8731_write(codec, WM8731_APDIGI, mute_reg);
+ return 0;
+}
+
+static int wm8731_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 wm8731_priv *wm8731 = codec->private_data;
+
+ switch (freq) {
+ case 11289600:
+ case 12000000:
+ case 12288000:
+ case 16934400:
+ case 18432000:
+ wm8731->sysclk = freq;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+
+static int wm8731_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface |= 0x0040;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set iface */
+ wm8731_write(codec, WM8731_IFACE, iface);
+ return 0;
+}
+
+static int wm8731_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg = wm8731_read_reg_cache(codec, WM8731_PWR) & 0xff7f;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* vref/mid, osc on, dac unmute */
+ wm8731_write(codec, WM8731_PWR, reg);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* everything off except vref/vmid, */
+ wm8731_write(codec, WM8731_PWR, reg | 0x0040);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* everything off, dac mute, inactive */
+ wm8731_write(codec, WM8731_ACTIVE, 0x0);
+ wm8731_write(codec, WM8731_PWR, 0xffff);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define WM8731_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\
+ SNDRV_PCM_RATE_96000)
+
+#define WM8731_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+struct snd_soc_dai wm8731_dai = {
+ .name = "WM8731",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8731_RATES,
+ .formats = WM8731_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8731_RATES,
+ .formats = WM8731_FORMATS,},
+ .ops = {
+ .prepare = wm8731_pcm_prepare,
+ .hw_params = wm8731_hw_params,
+ .shutdown = wm8731_shutdown,
+ },
+ .dai_ops = {
+ .digital_mute = wm8731_mute,
+ .set_sysclk = wm8731_set_dai_sysclk,
+ .set_fmt = wm8731_set_dai_fmt,
+ }
+};
+EXPORT_SYMBOL_GPL(wm8731_dai);
+
+static int wm8731_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ wm8731_write(codec, WM8731_ACTIVE, 0x0);
+ wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8731_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8731_reg); i++) {
+ data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+ wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ wm8731_set_bias_level(codec, codec->suspend_bias_level);
+ return 0;
+}
+
+/*
+ * initialise the WM8731 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int wm8731_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int reg, ret = 0;
+
+ codec->name = "WM8731";
+ codec->owner = THIS_MODULE;
+ codec->read = wm8731_read_reg_cache;
+ codec->write = wm8731_write;
+ codec->set_bias_level = wm8731_set_bias_level;
+ codec->dai = &wm8731_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = ARRAY_SIZE(wm8731_reg);
+ codec->reg_cache = kmemdup(wm8731_reg, sizeof(wm8731_reg), GFP_KERNEL);
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ wm8731_reset(codec);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8731: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* power on device */
+ wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* set the update bits */
+ reg = wm8731_read_reg_cache(codec, WM8731_LOUT1V);
+ wm8731_write(codec, WM8731_LOUT1V, reg & ~0x0100);
+ reg = wm8731_read_reg_cache(codec, WM8731_ROUT1V);
+ wm8731_write(codec, WM8731_ROUT1V, reg & ~0x0100);
+ reg = wm8731_read_reg_cache(codec, WM8731_LINVOL);
+ wm8731_write(codec, WM8731_LINVOL, reg & ~0x0100);
+ reg = wm8731_read_reg_cache(codec, WM8731_RINVOL);
+ wm8731_write(codec, WM8731_RINVOL, reg & ~0x0100);
+
+ wm8731_add_controls(codec);
+ wm8731_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8731: failed to register card\n");
+ goto card_err;
+ }
+
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+ return ret;
+}
+
+static struct snd_soc_device *wm8731_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+
+/*
+ * WM8731 2 wire address is determined by GPIO5
+ * state during powerup.
+ * low = 0x1a
+ * high = 0x1b
+ */
+
+static int wm8731_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = wm8731_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = wm8731_init(socdev);
+ if (ret < 0)
+ pr_err("failed to initialise WM8731\n");
+
+ return ret;
+}
+
+static int wm8731_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+static const struct i2c_device_id wm8731_i2c_id[] = {
+ { "wm8731", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8731_i2c_id);
+
+static struct i2c_driver wm8731_i2c_driver = {
+ .driver = {
+ .name = "WM8731 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8731_i2c_probe,
+ .remove = wm8731_i2c_remove,
+ .id_table = wm8731_i2c_id,
+};
+
+static int wm8731_add_i2c_device(struct platform_device *pdev,
+ const struct wm8731_setup_data *setup)
+{
+ struct i2c_board_info info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+ int ret;
+
+ ret = i2c_add_driver(&wm8731_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ return ret;
+ }
+
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ info.addr = setup->i2c_address;
+ strlcpy(info.type, "wm8731", I2C_NAME_SIZE);
+
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "can't get i2c adapter %d\n",
+ setup->i2c_bus);
+ goto err_driver;
+ }
+
+ client = i2c_new_device(adapter, &info);
+ i2c_put_adapter(adapter);
+ if (!client) {
+ dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
+ (unsigned int)info.addr);
+ goto err_driver;
+ }
+
+ return 0;
+
+err_driver:
+ i2c_del_driver(&wm8731_i2c_driver);
+ return -ENODEV;
+}
+#endif
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8731_spi_probe(struct spi_device *spi)
+{
+ struct snd_soc_device *socdev = wm8731_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ codec->control_data = spi;
+
+ ret = wm8731_init(socdev);
+ if (ret < 0)
+ dev_err(&spi->dev, "failed to initialise WM8731\n");
+
+ return ret;
+}
+
+static int __devexit wm8731_spi_remove(struct spi_device *spi)
+{
+ return 0;
+}
+
+static struct spi_driver wm8731_spi_driver = {
+ .driver = {
+ .name = "wm8731",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8731_spi_probe,
+ .remove = __devexit_p(wm8731_spi_remove),
+};
+
+static int wm8731_spi_write(struct spi_device *spi, const char *data, int len)
+{
+ struct spi_transfer t;
+ struct spi_message m;
+ u8 msg[2];
+
+ if (len <= 0)
+ return 0;
+
+ msg[0] = data[0];
+ msg[1] = data[1];
+
+ spi_message_init(&m);
+ memset(&t, 0, (sizeof t));
+
+ t.tx_buf = &msg[0];
+ t.len = len;
+
+ spi_message_add_tail(&t, &m);
+ spi_sync(spi, &m);
+
+ return len;
+}
+#endif /* CONFIG_SPI_MASTER */
+
+static int wm8731_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct wm8731_setup_data *setup;
+ struct snd_soc_codec *codec;
+ struct wm8731_priv *wm8731;
+ int ret = 0;
+
+ pr_info("WM8731 Audio Codec %s", WM8731_VERSION);
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ wm8731 = kzalloc(sizeof(struct wm8731_priv), GFP_KERNEL);
+ if (wm8731 == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->private_data = wm8731;
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ wm8731_socdev = socdev;
+ ret = -ENODEV;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = wm8731_add_i2c_device(pdev, setup);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ if (setup->spi) {
+ codec->hw_write = (hw_write_t)wm8731_spi_write;
+ ret = spi_register_driver(&wm8731_spi_driver);
+ if (ret != 0)
+ printk(KERN_ERR "can't add spi driver");
+ }
+#endif
+
+ if (ret != 0) {
+ kfree(codec->private_data);
+ kfree(codec);
+ }
+ return ret;
+}
+
+/* power down chip */
+static int wm8731_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec->control_data)
+ wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_unregister_device(codec->control_data);
+ i2c_del_driver(&wm8731_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8731_spi_driver);
+#endif
+ kfree(codec->private_data);
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8731 = {
+ .probe = wm8731_probe,
+ .remove = wm8731_remove,
+ .suspend = wm8731_suspend,
+ .resume = wm8731_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8731);
+
+MODULE_DESCRIPTION("ASoC WM8731 driver");
+MODULE_AUTHOR("Richard Purdie");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8731.h b/sound/soc/codecs/wm8731.h
new file mode 100644
index 0000000..95190e9
--- /dev/null
+++ b/sound/soc/codecs/wm8731.h
@@ -0,0 +1,46 @@
+/*
+ * wm8731.h -- WM8731 Soc Audio driver
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on wm8753.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8731_H
+#define _WM8731_H
+
+/* WM8731 register space */
+
+#define WM8731_LINVOL 0x00
+#define WM8731_RINVOL 0x01
+#define WM8731_LOUT1V 0x02
+#define WM8731_ROUT1V 0x03
+#define WM8731_APANA 0x04
+#define WM8731_APDIGI 0x05
+#define WM8731_PWR 0x06
+#define WM8731_IFACE 0x07
+#define WM8731_SRATE 0x08
+#define WM8731_ACTIVE 0x09
+#define WM8731_RESET 0x0f
+
+#define WM8731_CACHEREGNUM 10
+
+#define WM8731_SYSCLK 0
+#define WM8731_DAI 0
+
+struct wm8731_setup_data {
+ int spi;
+ int i2c_bus;
+ unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai wm8731_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8731;
+
+#endif
diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c
new file mode 100644
index 0000000..9b7296e
--- /dev/null
+++ b/sound/soc/codecs/wm8750.c
@@ -0,0 +1,1091 @@
+/*
+ * wm8750.c -- WM8750 ALSA SoC audio driver
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on WM8753.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.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 "wm8750.h"
+
+#define WM8750_VERSION "0.12"
+
+/* codec private data */
+struct wm8750_priv {
+ unsigned int sysclk;
+};
+
+/*
+ * wm8750 register cache
+ * We can't read the WM8750 register space when we
+ * are using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8750_reg[] = {
+ 0x0097, 0x0097, 0x0079, 0x0079, /* 0 */
+ 0x0000, 0x0008, 0x0000, 0x000a, /* 4 */
+ 0x0000, 0x0000, 0x00ff, 0x00ff, /* 8 */
+ 0x000f, 0x000f, 0x0000, 0x0000, /* 12 */
+ 0x0000, 0x007b, 0x0000, 0x0032, /* 16 */
+ 0x0000, 0x00c3, 0x00c3, 0x00c0, /* 20 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 24 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 28 */
+ 0x0000, 0x0000, 0x0050, 0x0050, /* 32 */
+ 0x0050, 0x0050, 0x0050, 0x0050, /* 36 */
+ 0x0079, 0x0079, 0x0079, /* 40 */
+};
+
+/*
+ * read wm8750 register cache
+ */
+static inline unsigned int wm8750_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg > WM8750_CACHE_REGNUM)
+ return -1;
+ return cache[reg];
+}
+
+/*
+ * write wm8750 register cache
+ */
+static inline void wm8750_write_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg > WM8750_CACHE_REGNUM)
+ return;
+ cache[reg] = value;
+}
+
+static int wm8750_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ /* data is
+ * D15..D9 WM8753 register offset
+ * D8...D0 register data
+ */
+ data[0] = (reg << 1) | ((value >> 8) & 0x0001);
+ data[1] = value & 0x00ff;
+
+ wm8750_write_reg_cache(codec, reg, value);
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+#define wm8750_reset(c) wm8750_write(c, WM8750_RESET, 0)
+
+/*
+ * WM8750 Controls
+ */
+static const char *wm8750_bass[] = {"Linear Control", "Adaptive Boost"};
+static const char *wm8750_bass_filter[] = { "130Hz @ 48kHz", "200Hz @ 48kHz" };
+static const char *wm8750_treble[] = {"8kHz", "4kHz"};
+static const char *wm8750_3d_lc[] = {"200Hz", "500Hz"};
+static const char *wm8750_3d_uc[] = {"2.2kHz", "1.5kHz"};
+static const char *wm8750_3d_func[] = {"Capture", "Playback"};
+static const char *wm8750_alc_func[] = {"Off", "Right", "Left", "Stereo"};
+static const char *wm8750_ng_type[] = {"Constant PGA Gain",
+ "Mute ADC Output"};
+static const char *wm8750_line_mux[] = {"Line 1", "Line 2", "Line 3", "PGA",
+ "Differential"};
+static const char *wm8750_pga_sel[] = {"Line 1", "Line 2", "Line 3",
+ "Differential"};
+static const char *wm8750_out3[] = {"VREF", "ROUT1 + Vol", "MonoOut",
+ "ROUT1"};
+static const char *wm8750_diff_sel[] = {"Line 1", "Line 2"};
+static const char *wm8750_adcpol[] = {"Normal", "L Invert", "R Invert",
+ "L + R Invert"};
+static const char *wm8750_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+static const char *wm8750_mono_mux[] = {"Stereo", "Mono (Left)",
+ "Mono (Right)", "Digital Mono"};
+
+static const struct soc_enum wm8750_enum[] = {
+SOC_ENUM_SINGLE(WM8750_BASS, 7, 2, wm8750_bass),
+SOC_ENUM_SINGLE(WM8750_BASS, 6, 2, wm8750_bass_filter),
+SOC_ENUM_SINGLE(WM8750_TREBLE, 6, 2, wm8750_treble),
+SOC_ENUM_SINGLE(WM8750_3D, 5, 2, wm8750_3d_lc),
+SOC_ENUM_SINGLE(WM8750_3D, 6, 2, wm8750_3d_uc),
+SOC_ENUM_SINGLE(WM8750_3D, 7, 2, wm8750_3d_func),
+SOC_ENUM_SINGLE(WM8750_ALC1, 7, 4, wm8750_alc_func),
+SOC_ENUM_SINGLE(WM8750_NGATE, 1, 2, wm8750_ng_type),
+SOC_ENUM_SINGLE(WM8750_LOUTM1, 0, 5, wm8750_line_mux),
+SOC_ENUM_SINGLE(WM8750_ROUTM1, 0, 5, wm8750_line_mux),
+SOC_ENUM_SINGLE(WM8750_LADCIN, 6, 4, wm8750_pga_sel), /* 10 */
+SOC_ENUM_SINGLE(WM8750_RADCIN, 6, 4, wm8750_pga_sel),
+SOC_ENUM_SINGLE(WM8750_ADCTL2, 7, 4, wm8750_out3),
+SOC_ENUM_SINGLE(WM8750_ADCIN, 8, 2, wm8750_diff_sel),
+SOC_ENUM_SINGLE(WM8750_ADCDAC, 5, 4, wm8750_adcpol),
+SOC_ENUM_SINGLE(WM8750_ADCDAC, 1, 4, wm8750_deemph),
+SOC_ENUM_SINGLE(WM8750_ADCIN, 6, 4, wm8750_mono_mux), /* 16 */
+
+};
+
+static const struct snd_kcontrol_new wm8750_snd_controls[] = {
+
+SOC_DOUBLE_R("Capture Volume", WM8750_LINVOL, WM8750_RINVOL, 0, 63, 0),
+SOC_DOUBLE_R("Capture ZC Switch", WM8750_LINVOL, WM8750_RINVOL, 6, 1, 0),
+SOC_DOUBLE_R("Capture Switch", WM8750_LINVOL, WM8750_RINVOL, 7, 1, 1),
+
+SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8750_LOUT1V,
+ WM8750_ROUT1V, 7, 1, 0),
+SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8750_LOUT2V,
+ WM8750_ROUT2V, 7, 1, 0),
+
+SOC_ENUM("Playback De-emphasis", wm8750_enum[15]),
+
+SOC_ENUM("Capture Polarity", wm8750_enum[14]),
+SOC_SINGLE("Playback 6dB Attenuate", WM8750_ADCDAC, 7, 1, 0),
+SOC_SINGLE("Capture 6dB Attenuate", WM8750_ADCDAC, 8, 1, 0),
+
+SOC_DOUBLE_R("PCM Volume", WM8750_LDAC, WM8750_RDAC, 0, 255, 0),
+
+SOC_ENUM("Bass Boost", wm8750_enum[0]),
+SOC_ENUM("Bass Filter", wm8750_enum[1]),
+SOC_SINGLE("Bass Volume", WM8750_BASS, 0, 15, 1),
+
+SOC_SINGLE("Treble Volume", WM8750_TREBLE, 0, 15, 1),
+SOC_ENUM("Treble Cut-off", wm8750_enum[2]),
+
+SOC_SINGLE("3D Switch", WM8750_3D, 0, 1, 0),
+SOC_SINGLE("3D Volume", WM8750_3D, 1, 15, 0),
+SOC_ENUM("3D Lower Cut-off", wm8750_enum[3]),
+SOC_ENUM("3D Upper Cut-off", wm8750_enum[4]),
+SOC_ENUM("3D Mode", wm8750_enum[5]),
+
+SOC_SINGLE("ALC Capture Target Volume", WM8750_ALC1, 0, 7, 0),
+SOC_SINGLE("ALC Capture Max Volume", WM8750_ALC1, 4, 7, 0),
+SOC_ENUM("ALC Capture Function", wm8750_enum[6]),
+SOC_SINGLE("ALC Capture ZC Switch", WM8750_ALC2, 7, 1, 0),
+SOC_SINGLE("ALC Capture Hold Time", WM8750_ALC2, 0, 15, 0),
+SOC_SINGLE("ALC Capture Decay Time", WM8750_ALC3, 4, 15, 0),
+SOC_SINGLE("ALC Capture Attack Time", WM8750_ALC3, 0, 15, 0),
+SOC_SINGLE("ALC Capture NG Threshold", WM8750_NGATE, 3, 31, 0),
+SOC_ENUM("ALC Capture NG Type", wm8750_enum[4]),
+SOC_SINGLE("ALC Capture NG Switch", WM8750_NGATE, 0, 1, 0),
+
+SOC_SINGLE("Left ADC Capture Volume", WM8750_LADC, 0, 255, 0),
+SOC_SINGLE("Right ADC Capture Volume", WM8750_RADC, 0, 255, 0),
+
+SOC_SINGLE("ZC Timeout Switch", WM8750_ADCTL1, 0, 1, 0),
+SOC_SINGLE("Playback Invert Switch", WM8750_ADCTL1, 1, 1, 0),
+
+SOC_SINGLE("Right Speaker Playback Invert Switch", WM8750_ADCTL2, 4, 1, 0),
+
+/* Unimplemented */
+/* ADCDAC Bit 0 - ADCHPD */
+/* ADCDAC Bit 4 - HPOR */
+/* ADCTL1 Bit 2,3 - DATSEL */
+/* ADCTL1 Bit 4,5 - DMONOMIX */
+/* ADCTL1 Bit 6,7 - VSEL */
+/* ADCTL2 Bit 2 - LRCM */
+/* ADCTL2 Bit 3 - TRI */
+/* ADCTL3 Bit 5 - HPFLREN */
+/* ADCTL3 Bit 6 - VROI */
+/* ADCTL3 Bit 7,8 - ADCLRM */
+/* ADCIN Bit 4 - LDCM */
+/* ADCIN Bit 5 - RDCM */
+
+SOC_DOUBLE_R("Mic Boost", WM8750_LADCIN, WM8750_RADCIN, 4, 3, 0),
+
+SOC_DOUBLE_R("Bypass Left Playback Volume", WM8750_LOUTM1,
+ WM8750_LOUTM2, 4, 7, 1),
+SOC_DOUBLE_R("Bypass Right Playback Volume", WM8750_ROUTM1,
+ WM8750_ROUTM2, 4, 7, 1),
+SOC_DOUBLE_R("Bypass Mono Playback Volume", WM8750_MOUTM1,
+ WM8750_MOUTM2, 4, 7, 1),
+
+SOC_SINGLE("Mono Playback ZC Switch", WM8750_MOUTV, 7, 1, 0),
+
+SOC_DOUBLE_R("Headphone Playback Volume", WM8750_LOUT1V, WM8750_ROUT1V,
+ 0, 127, 0),
+SOC_DOUBLE_R("Speaker Playback Volume", WM8750_LOUT2V, WM8750_ROUT2V,
+ 0, 127, 0),
+
+SOC_SINGLE("Mono Playback Volume", WM8750_MOUTV, 0, 127, 0),
+
+};
+
+/* add non dapm controls */
+static int wm8750_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8750_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8750_snd_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+/*
+ * DAPM Controls
+ */
+
+/* Left Mixer */
+static const struct snd_kcontrol_new wm8750_left_mixer_controls[] = {
+SOC_DAPM_SINGLE("Playback Switch", WM8750_LOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8750_LOUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8750_LOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8750_LOUTM2, 7, 1, 0),
+};
+
+/* Right Mixer */
+static const struct snd_kcontrol_new wm8750_right_mixer_controls[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8750_ROUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8750_ROUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Playback Switch", WM8750_ROUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8750_ROUTM2, 7, 1, 0),
+};
+
+/* Mono Mixer */
+static const struct snd_kcontrol_new wm8750_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8750_MOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8750_MOUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8750_MOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8750_MOUTM2, 7, 1, 0),
+};
+
+/* Left Line Mux */
+static const struct snd_kcontrol_new wm8750_left_line_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[8]);
+
+/* Right Line Mux */
+static const struct snd_kcontrol_new wm8750_right_line_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[9]);
+
+/* Left PGA Mux */
+static const struct snd_kcontrol_new wm8750_left_pga_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[10]);
+
+/* Right PGA Mux */
+static const struct snd_kcontrol_new wm8750_right_pga_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[11]);
+
+/* Out 3 Mux */
+static const struct snd_kcontrol_new wm8750_out3_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[12]);
+
+/* Differential Mux */
+static const struct snd_kcontrol_new wm8750_diffmux_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[13]);
+
+/* Mono ADC Mux */
+static const struct snd_kcontrol_new wm8750_monomux_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[16]);
+
+static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = {
+ SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0,
+ &wm8750_left_mixer_controls[0],
+ ARRAY_SIZE(wm8750_left_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0,
+ &wm8750_right_mixer_controls[0],
+ ARRAY_SIZE(wm8750_right_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Mono Mixer", WM8750_PWR2, 2, 0,
+ &wm8750_mono_mixer_controls[0],
+ ARRAY_SIZE(wm8750_mono_mixer_controls)),
+
+ SND_SOC_DAPM_PGA("Right Out 2", WM8750_PWR2, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left Out 2", WM8750_PWR2, 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right Out 1", WM8750_PWR2, 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left Out 1", WM8750_PWR2, 6, 0, NULL, 0),
+ SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8750_PWR2, 7, 0),
+ SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8750_PWR2, 8, 0),
+
+ SND_SOC_DAPM_MICBIAS("Mic Bias", WM8750_PWR1, 1, 0),
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8750_PWR1, 2, 0),
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8750_PWR1, 3, 0),
+
+ SND_SOC_DAPM_MUX("Left PGA Mux", WM8750_PWR1, 5, 0,
+ &wm8750_left_pga_controls),
+ SND_SOC_DAPM_MUX("Right PGA Mux", WM8750_PWR1, 4, 0,
+ &wm8750_right_pga_controls),
+ SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0,
+ &wm8750_left_line_controls),
+ SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0,
+ &wm8750_right_line_controls),
+
+ SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0, &wm8750_out3_controls),
+ SND_SOC_DAPM_PGA("Out 3", WM8750_PWR2, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Mono Out 1", WM8750_PWR2, 2, 0, NULL, 0),
+
+ SND_SOC_DAPM_MUX("Differential Mux", SND_SOC_NOPM, 0, 0,
+ &wm8750_diffmux_controls),
+ SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0,
+ &wm8750_monomux_controls),
+ SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0,
+ &wm8750_monomux_controls),
+
+ SND_SOC_DAPM_OUTPUT("LOUT1"),
+ SND_SOC_DAPM_OUTPUT("ROUT1"),
+ SND_SOC_DAPM_OUTPUT("LOUT2"),
+ SND_SOC_DAPM_OUTPUT("ROUT2"),
+ SND_SOC_DAPM_OUTPUT("MONO1"),
+ SND_SOC_DAPM_OUTPUT("OUT3"),
+ SND_SOC_DAPM_OUTPUT("VREF"),
+
+ SND_SOC_DAPM_INPUT("LINPUT1"),
+ SND_SOC_DAPM_INPUT("LINPUT2"),
+ SND_SOC_DAPM_INPUT("LINPUT3"),
+ SND_SOC_DAPM_INPUT("RINPUT1"),
+ SND_SOC_DAPM_INPUT("RINPUT2"),
+ SND_SOC_DAPM_INPUT("RINPUT3"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* left mixer */
+ {"Left Mixer", "Playback Switch", "Left DAC"},
+ {"Left Mixer", "Left Bypass Switch", "Left Line Mux"},
+ {"Left Mixer", "Right Playback Switch", "Right DAC"},
+ {"Left Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+ /* right mixer */
+ {"Right Mixer", "Left Playback Switch", "Left DAC"},
+ {"Right Mixer", "Left Bypass Switch", "Left Line Mux"},
+ {"Right Mixer", "Playback Switch", "Right DAC"},
+ {"Right Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+ /* left out 1 */
+ {"Left Out 1", NULL, "Left Mixer"},
+ {"LOUT1", NULL, "Left Out 1"},
+
+ /* left out 2 */
+ {"Left Out 2", NULL, "Left Mixer"},
+ {"LOUT2", NULL, "Left Out 2"},
+
+ /* right out 1 */
+ {"Right Out 1", NULL, "Right Mixer"},
+ {"ROUT1", NULL, "Right Out 1"},
+
+ /* right out 2 */
+ {"Right Out 2", NULL, "Right Mixer"},
+ {"ROUT2", NULL, "Right Out 2"},
+
+ /* mono mixer */
+ {"Mono Mixer", "Left Playback Switch", "Left DAC"},
+ {"Mono Mixer", "Left Bypass Switch", "Left Line Mux"},
+ {"Mono Mixer", "Right Playback Switch", "Right DAC"},
+ {"Mono Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+ /* mono out */
+ {"Mono Out 1", NULL, "Mono Mixer"},
+ {"MONO1", NULL, "Mono Out 1"},
+
+ /* out 3 */
+ {"Out3 Mux", "VREF", "VREF"},
+ {"Out3 Mux", "ROUT1 + Vol", "ROUT1"},
+ {"Out3 Mux", "ROUT1", "Right Mixer"},
+ {"Out3 Mux", "MonoOut", "MONO1"},
+ {"Out 3", NULL, "Out3 Mux"},
+ {"OUT3", NULL, "Out 3"},
+
+ /* Left Line Mux */
+ {"Left Line Mux", "Line 1", "LINPUT1"},
+ {"Left Line Mux", "Line 2", "LINPUT2"},
+ {"Left Line Mux", "Line 3", "LINPUT3"},
+ {"Left Line Mux", "PGA", "Left PGA Mux"},
+ {"Left Line Mux", "Differential", "Differential Mux"},
+
+ /* Right Line Mux */
+ {"Right Line Mux", "Line 1", "RINPUT1"},
+ {"Right Line Mux", "Line 2", "RINPUT2"},
+ {"Right Line Mux", "Line 3", "RINPUT3"},
+ {"Right Line Mux", "PGA", "Right PGA Mux"},
+ {"Right Line Mux", "Differential", "Differential Mux"},
+
+ /* Left PGA Mux */
+ {"Left PGA Mux", "Line 1", "LINPUT1"},
+ {"Left PGA Mux", "Line 2", "LINPUT2"},
+ {"Left PGA Mux", "Line 3", "LINPUT3"},
+ {"Left PGA Mux", "Differential", "Differential Mux"},
+
+ /* Right PGA Mux */
+ {"Right PGA Mux", "Line 1", "RINPUT1"},
+ {"Right PGA Mux", "Line 2", "RINPUT2"},
+ {"Right PGA Mux", "Line 3", "RINPUT3"},
+ {"Right PGA Mux", "Differential", "Differential Mux"},
+
+ /* Differential Mux */
+ {"Differential Mux", "Line 1", "LINPUT1"},
+ {"Differential Mux", "Line 1", "RINPUT1"},
+ {"Differential Mux", "Line 2", "LINPUT2"},
+ {"Differential Mux", "Line 2", "RINPUT2"},
+
+ /* Left ADC Mux */
+ {"Left ADC Mux", "Stereo", "Left PGA Mux"},
+ {"Left ADC Mux", "Mono (Left)", "Left PGA Mux"},
+ {"Left ADC Mux", "Digital Mono", "Left PGA Mux"},
+
+ /* Right ADC Mux */
+ {"Right ADC Mux", "Stereo", "Right PGA Mux"},
+ {"Right ADC Mux", "Mono (Right)", "Right PGA Mux"},
+ {"Right ADC Mux", "Digital Mono", "Right PGA Mux"},
+
+ /* ADC */
+ {"Left ADC", NULL, "Left ADC Mux"},
+ {"Right ADC", NULL, "Right ADC Mux"},
+};
+
+static int wm8750_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm8750_dapm_widgets,
+ ARRAY_SIZE(wm8750_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+struct _coeff_div {
+ u32 mclk;
+ u32 rate;
+ u16 fs;
+ u8 sr:5;
+ u8 usb:1;
+};
+
+/* codec hifi mclk clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+ /* 8k */
+ {12288000, 8000, 1536, 0x6, 0x0},
+ {11289600, 8000, 1408, 0x16, 0x0},
+ {18432000, 8000, 2304, 0x7, 0x0},
+ {16934400, 8000, 2112, 0x17, 0x0},
+ {12000000, 8000, 1500, 0x6, 0x1},
+
+ /* 11.025k */
+ {11289600, 11025, 1024, 0x18, 0x0},
+ {16934400, 11025, 1536, 0x19, 0x0},
+ {12000000, 11025, 1088, 0x19, 0x1},
+
+ /* 16k */
+ {12288000, 16000, 768, 0xa, 0x0},
+ {18432000, 16000, 1152, 0xb, 0x0},
+ {12000000, 16000, 750, 0xa, 0x1},
+
+ /* 22.05k */
+ {11289600, 22050, 512, 0x1a, 0x0},
+ {16934400, 22050, 768, 0x1b, 0x0},
+ {12000000, 22050, 544, 0x1b, 0x1},
+
+ /* 32k */
+ {12288000, 32000, 384, 0xc, 0x0},
+ {18432000, 32000, 576, 0xd, 0x0},
+ {12000000, 32000, 375, 0xa, 0x1},
+
+ /* 44.1k */
+ {11289600, 44100, 256, 0x10, 0x0},
+ {16934400, 44100, 384, 0x11, 0x0},
+ {12000000, 44100, 272, 0x11, 0x1},
+
+ /* 48k */
+ {12288000, 48000, 256, 0x0, 0x0},
+ {18432000, 48000, 384, 0x1, 0x0},
+ {12000000, 48000, 250, 0x0, 0x1},
+
+ /* 88.2k */
+ {11289600, 88200, 128, 0x1e, 0x0},
+ {16934400, 88200, 192, 0x1f, 0x0},
+ {12000000, 88200, 136, 0x1f, 0x1},
+
+ /* 96k */
+ {12288000, 96000, 128, 0xe, 0x0},
+ {18432000, 96000, 192, 0xf, 0x0},
+ {12000000, 96000, 125, 0xe, 0x1},
+};
+
+static inline int get_coeff(int mclk, int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+ if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+ return i;
+ }
+
+ printk(KERN_ERR "wm8750: could not get coeff for mclk %d @ rate %d\n",
+ mclk, rate);
+ return -EINVAL;
+}
+
+static int wm8750_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 wm8750_priv *wm8750 = codec->private_data;
+
+ switch (freq) {
+ case 11289600:
+ case 12000000:
+ case 12288000:
+ case 16934400:
+ case 18432000:
+ wm8750->sysclk = freq;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int wm8750_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface = 0x0040;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8750_write(codec, WM8750_IFACE, iface);
+ return 0;
+}
+
+static int wm8750_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct wm8750_priv *wm8750 = codec->private_data;
+ u16 iface = wm8750_read_reg_cache(codec, WM8750_IFACE) & 0x1f3;
+ u16 srate = wm8750_read_reg_cache(codec, WM8750_SRATE) & 0x1c0;
+ int coeff = get_coeff(wm8750->sysclk, params_rate(params));
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x0008;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= 0x000c;
+ break;
+ }
+
+ /* set iface & srate */
+ wm8750_write(codec, WM8750_IFACE, iface);
+ if (coeff >= 0)
+ wm8750_write(codec, WM8750_SRATE, srate |
+ (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb);
+
+ return 0;
+}
+
+static int wm8750_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = wm8750_read_reg_cache(codec, WM8750_ADCDAC) & 0xfff7;
+
+ if (mute)
+ wm8750_write(codec, WM8750_ADCDAC, mute_reg | 0x8);
+ else
+ wm8750_write(codec, WM8750_ADCDAC, mute_reg);
+ return 0;
+}
+
+static int wm8750_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 pwr_reg = wm8750_read_reg_cache(codec, WM8750_PWR1) & 0xfe3e;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* set vmid to 50k and unmute dac */
+ wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x00c0);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ /* set vmid to 5k for quick power up */
+ wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x01c1);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* mute dac and set vmid to 500k, enable VREF */
+ wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x0141);
+ break;
+ case SND_SOC_BIAS_OFF:
+ wm8750_write(codec, WM8750_PWR1, 0x0001);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define WM8750_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+#define WM8750_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+struct snd_soc_dai wm8750_dai = {
+ .name = "WM8750",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8750_RATES,
+ .formats = WM8750_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8750_RATES,
+ .formats = WM8750_FORMATS,},
+ .ops = {
+ .hw_params = wm8750_pcm_hw_params,
+ },
+ .dai_ops = {
+ .digital_mute = wm8750_mute,
+ .set_fmt = wm8750_set_dai_fmt,
+ .set_sysclk = wm8750_set_dai_sysclk,
+ },
+};
+EXPORT_SYMBOL_GPL(wm8750_dai);
+
+static void wm8750_work(struct work_struct *work)
+{
+ struct snd_soc_codec *codec =
+ container_of(work, struct snd_soc_codec, delayed_work.work);
+ wm8750_set_bias_level(codec, codec->bias_level);
+}
+
+static int wm8750_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ wm8750_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8750_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8750_reg); i++) {
+ if (i == WM8750_RESET)
+ continue;
+ data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+
+ wm8750_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* charge wm8750 caps */
+ if (codec->suspend_bias_level == SND_SOC_BIAS_ON) {
+ wm8750_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
+ codec->bias_level = SND_SOC_BIAS_ON;
+ schedule_delayed_work(&codec->delayed_work,
+ msecs_to_jiffies(1000));
+ }
+
+ return 0;
+}
+
+/*
+ * initialise the WM8750 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int wm8750_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int reg, ret = 0;
+
+ codec->name = "WM8750";
+ codec->owner = THIS_MODULE;
+ codec->read = wm8750_read_reg_cache;
+ codec->write = wm8750_write;
+ codec->set_bias_level = wm8750_set_bias_level;
+ codec->dai = &wm8750_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = ARRAY_SIZE(wm8750_reg);
+ codec->reg_cache = kmemdup(wm8750_reg, sizeof(wm8750_reg), GFP_KERNEL);
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ wm8750_reset(codec);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8750: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* charge output caps */
+ wm8750_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
+ codec->bias_level = SND_SOC_BIAS_STANDBY;
+ schedule_delayed_work(&codec->delayed_work, msecs_to_jiffies(1000));
+
+ /* set the update bits */
+ reg = wm8750_read_reg_cache(codec, WM8750_LDAC);
+ wm8750_write(codec, WM8750_LDAC, reg | 0x0100);
+ reg = wm8750_read_reg_cache(codec, WM8750_RDAC);
+ wm8750_write(codec, WM8750_RDAC, reg | 0x0100);
+ reg = wm8750_read_reg_cache(codec, WM8750_LOUT1V);
+ wm8750_write(codec, WM8750_LOUT1V, reg | 0x0100);
+ reg = wm8750_read_reg_cache(codec, WM8750_ROUT1V);
+ wm8750_write(codec, WM8750_ROUT1V, reg | 0x0100);
+ reg = wm8750_read_reg_cache(codec, WM8750_LOUT2V);
+ wm8750_write(codec, WM8750_LOUT2V, reg | 0x0100);
+ reg = wm8750_read_reg_cache(codec, WM8750_ROUT2V);
+ wm8750_write(codec, WM8750_ROUT2V, reg | 0x0100);
+ reg = wm8750_read_reg_cache(codec, WM8750_LINVOL);
+ wm8750_write(codec, WM8750_LINVOL, reg | 0x0100);
+ reg = wm8750_read_reg_cache(codec, WM8750_RINVOL);
+ wm8750_write(codec, WM8750_RINVOL, reg | 0x0100);
+
+ wm8750_add_controls(codec);
+ wm8750_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8750: failed to register card\n");
+ goto card_err;
+ }
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+ return ret;
+}
+
+/* If the i2c layer weren't so broken, we could pass this kind of data
+ around */
+static struct snd_soc_device *wm8750_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+
+/*
+ * WM8750 2 wire address is determined by GPIO5
+ * state during powerup.
+ * low = 0x1a
+ * high = 0x1b
+ */
+
+static int wm8750_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = wm8750_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = wm8750_init(socdev);
+ if (ret < 0)
+ pr_err("failed to initialise WM8750\n");
+
+ return ret;
+}
+
+static int wm8750_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+static const struct i2c_device_id wm8750_i2c_id[] = {
+ { "wm8750", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8750_i2c_id);
+
+static struct i2c_driver wm8750_i2c_driver = {
+ .driver = {
+ .name = "WM8750 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8750_i2c_probe,
+ .remove = wm8750_i2c_remove,
+ .id_table = wm8750_i2c_id,
+};
+
+static int wm8750_add_i2c_device(struct platform_device *pdev,
+ const struct wm8750_setup_data *setup)
+{
+ struct i2c_board_info info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+ int ret;
+
+ ret = i2c_add_driver(&wm8750_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ return ret;
+ }
+
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ info.addr = setup->i2c_address;
+ strlcpy(info.type, "wm8750", I2C_NAME_SIZE);
+
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "can't get i2c adapter %d\n",
+ setup->i2c_bus);
+ goto err_driver;
+ }
+
+ client = i2c_new_device(adapter, &info);
+ i2c_put_adapter(adapter);
+ if (!client) {
+ dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
+ (unsigned int)info.addr);
+ goto err_driver;
+ }
+
+ return 0;
+
+err_driver:
+ i2c_del_driver(&wm8750_i2c_driver);
+ return -ENODEV;
+}
+#endif
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8750_spi_probe(struct spi_device *spi)
+{
+ struct snd_soc_device *socdev = wm8750_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ codec->control_data = spi;
+
+ ret = wm8750_init(socdev);
+ if (ret < 0)
+ dev_err(&spi->dev, "failed to initialise WM8750\n");
+
+ return ret;
+}
+
+static int __devexit wm8750_spi_remove(struct spi_device *spi)
+{
+ return 0;
+}
+
+static struct spi_driver wm8750_spi_driver = {
+ .driver = {
+ .name = "wm8750",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8750_spi_probe,
+ .remove = __devexit_p(wm8750_spi_remove),
+};
+
+static int wm8750_spi_write(struct spi_device *spi, const char *data, int len)
+{
+ struct spi_transfer t;
+ struct spi_message m;
+ u8 msg[2];
+
+ if (len <= 0)
+ return 0;
+
+ msg[0] = data[0];
+ msg[1] = data[1];
+
+ spi_message_init(&m);
+ memset(&t, 0, (sizeof t));
+
+ t.tx_buf = &msg[0];
+ t.len = len;
+
+ spi_message_add_tail(&t, &m);
+ spi_sync(spi, &m);
+
+ return len;
+}
+#endif
+
+static int wm8750_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct wm8750_setup_data *setup = socdev->codec_data;
+ struct snd_soc_codec *codec;
+ struct wm8750_priv *wm8750;
+ int ret;
+
+ pr_info("WM8750 Audio Codec %s", WM8750_VERSION);
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ wm8750 = kzalloc(sizeof(struct wm8750_priv), GFP_KERNEL);
+ if (wm8750 == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->private_data = wm8750;
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+ wm8750_socdev = socdev;
+ INIT_DELAYED_WORK(&codec->delayed_work, wm8750_work);
+
+ ret = -ENODEV;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = wm8750_add_i2c_device(pdev, setup);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ if (setup->spi) {
+ codec->hw_write = (hw_write_t)wm8750_spi_write;
+ ret = spi_register_driver(&wm8750_spi_driver);
+ if (ret != 0)
+ printk(KERN_ERR "can't add spi driver");
+ }
+#endif
+
+ if (ret != 0) {
+ kfree(codec->private_data);
+ kfree(codec);
+ }
+ return ret;
+}
+
+/*
+ * This function forces any delayed work to be queued and run.
+ */
+static int run_delayed_work(struct delayed_work *dwork)
+{
+ int ret;
+
+ /* cancel any work waiting to be queued. */
+ ret = cancel_delayed_work(dwork);
+
+ /* if there was any work waiting then we run it now and
+ * wait for it's completion */
+ if (ret) {
+ schedule_delayed_work(dwork, 0);
+ flush_scheduled_work();
+ }
+ return ret;
+}
+
+/* power down chip */
+static int wm8750_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec->control_data)
+ wm8750_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ run_delayed_work(&codec->delayed_work);
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_unregister_device(codec->control_data);
+ i2c_del_driver(&wm8750_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8750_spi_driver);
+#endif
+ kfree(codec->private_data);
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8750 = {
+ .probe = wm8750_probe,
+ .remove = wm8750_remove,
+ .suspend = wm8750_suspend,
+ .resume = wm8750_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8750);
+
+MODULE_DESCRIPTION("ASoC WM8750 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8750.h b/sound/soc/codecs/wm8750.h
new file mode 100644
index 0000000..1dc100e
--- /dev/null
+++ b/sound/soc/codecs/wm8750.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on WM8753.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef _WM8750_H
+#define _WM8750_H
+
+/* WM8750 register space */
+
+#define WM8750_LINVOL 0x00
+#define WM8750_RINVOL 0x01
+#define WM8750_LOUT1V 0x02
+#define WM8750_ROUT1V 0x03
+#define WM8750_ADCDAC 0x05
+#define WM8750_IFACE 0x07
+#define WM8750_SRATE 0x08
+#define WM8750_LDAC 0x0a
+#define WM8750_RDAC 0x0b
+#define WM8750_BASS 0x0c
+#define WM8750_TREBLE 0x0d
+#define WM8750_RESET 0x0f
+#define WM8750_3D 0x10
+#define WM8750_ALC1 0x11
+#define WM8750_ALC2 0x12
+#define WM8750_ALC3 0x13
+#define WM8750_NGATE 0x14
+#define WM8750_LADC 0x15
+#define WM8750_RADC 0x16
+#define WM8750_ADCTL1 0x17
+#define WM8750_ADCTL2 0x18
+#define WM8750_PWR1 0x19
+#define WM8750_PWR2 0x1a
+#define WM8750_ADCTL3 0x1b
+#define WM8750_ADCIN 0x1f
+#define WM8750_LADCIN 0x20
+#define WM8750_RADCIN 0x21
+#define WM8750_LOUTM1 0x22
+#define WM8750_LOUTM2 0x23
+#define WM8750_ROUTM1 0x24
+#define WM8750_ROUTM2 0x25
+#define WM8750_MOUTM1 0x26
+#define WM8750_MOUTM2 0x27
+#define WM8750_LOUT2V 0x28
+#define WM8750_ROUT2V 0x29
+#define WM8750_MOUTV 0x2a
+
+#define WM8750_CACHE_REGNUM 0x2a
+
+#define WM8750_SYSCLK 0
+
+struct wm8750_setup_data {
+ int spi;
+ int i2c_bus;
+ unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai wm8750_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8750;
+
+#endif
diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c
new file mode 100644
index 0000000..d426eaa
--- /dev/null
+++ b/sound/soc/codecs/wm8753.c
@@ -0,0 +1,1882 @@
+/*
+ * wm8753.c -- WM8753 ALSA Soc Audio driver
+ *
+ * Copyright 2003 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * Notes:
+ * The WM8753 is a low power, high quality stereo codec with integrated PCM
+ * codec designed for portable digital telephony applications.
+ *
+ * Dual DAI:-
+ *
+ * This driver support 2 DAI PCM's. This makes the default PCM available for
+ * HiFi audio (e.g. MP3, ogg) playback/capture and the other PCM available for
+ * voice.
+ *
+ * Please note that the voice PCM can be connected directly to a Bluetooth
+ * codec or GSM modem and thus cannot be read or written to, although it is
+ * available to be configured with snd_hw_params(), etc and kcontrols in the
+ * normal alsa manner.
+ *
+ * Fast DAI switching:-
+ *
+ * The driver can now fast switch between the DAI configurations via a
+ * an alsa kcontrol. This allows the PCM to remain open.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.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 <asm/div64.h>
+
+#include "wm8753.h"
+
+#define WM8753_VERSION "0.16"
+
+static int caps_charge = 2000;
+module_param(caps_charge, int, 0);
+MODULE_PARM_DESC(caps_charge, "WM8753 cap charge time (msecs)");
+
+static void wm8753_set_dai_mode(struct snd_soc_codec *codec,
+ unsigned int mode);
+
+/* codec private data */
+struct wm8753_priv {
+ unsigned int sysclk;
+ unsigned int pcmclk;
+};
+
+/*
+ * wm8753 register cache
+ * We can't read the WM8753 register space when we
+ * are using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8753_reg[] = {
+ 0x0008, 0x0000, 0x000a, 0x000a,
+ 0x0033, 0x0000, 0x0007, 0x00ff,
+ 0x00ff, 0x000f, 0x000f, 0x007b,
+ 0x0000, 0x0032, 0x0000, 0x00c3,
+ 0x00c3, 0x00c0, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0055,
+ 0x0005, 0x0050, 0x0055, 0x0050,
+ 0x0055, 0x0050, 0x0055, 0x0079,
+ 0x0079, 0x0079, 0x0079, 0x0079,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0097, 0x0097, 0x0000, 0x0004,
+ 0x0000, 0x0083, 0x0024, 0x01ba,
+ 0x0000, 0x0083, 0x0024, 0x01ba,
+ 0x0000, 0x0000
+};
+
+/*
+ * read wm8753 register cache
+ */
+static inline unsigned int wm8753_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg < 1 || reg > (ARRAY_SIZE(wm8753_reg) + 1))
+ return -1;
+ return cache[reg - 1];
+}
+
+/*
+ * write wm8753 register cache
+ */
+static inline void wm8753_write_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg < 1 || reg > 0x3f)
+ return;
+ cache[reg - 1] = value;
+}
+
+/*
+ * write to the WM8753 register space
+ */
+static int wm8753_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ /* data is
+ * D15..D9 WM8753 register offset
+ * D8...D0 register data
+ */
+ data[0] = (reg << 1) | ((value >> 8) & 0x0001);
+ data[1] = value & 0x00ff;
+
+ wm8753_write_reg_cache(codec, reg, value);
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+#define wm8753_reset(c) wm8753_write(c, WM8753_RESET, 0)
+
+/*
+ * WM8753 Controls
+ */
+static const char *wm8753_base[] = {"Linear Control", "Adaptive Boost"};
+static const char *wm8753_base_filter[] =
+ {"130Hz @ 48kHz", "200Hz @ 48kHz", "100Hz @ 16kHz", "400Hz @ 48kHz",
+ "100Hz @ 8kHz", "200Hz @ 8kHz"};
+static const char *wm8753_treble[] = {"8kHz", "4kHz"};
+static const char *wm8753_alc_func[] = {"Off", "Right", "Left", "Stereo"};
+static const char *wm8753_ng_type[] = {"Constant PGA Gain", "Mute ADC Output"};
+static const char *wm8753_3d_func[] = {"Capture", "Playback"};
+static const char *wm8753_3d_uc[] = {"2.2kHz", "1.5kHz"};
+static const char *wm8753_3d_lc[] = {"200Hz", "500Hz"};
+static const char *wm8753_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz"};
+static const char *wm8753_mono_mix[] = {"Stereo", "Left", "Right", "Mono"};
+static const char *wm8753_dac_phase[] = {"Non Inverted", "Inverted"};
+static const char *wm8753_line_mix[] = {"Line 1 + 2", "Line 1 - 2",
+ "Line 1", "Line 2"};
+static const char *wm8753_mono_mux[] = {"Line Mix", "Rx Mix"};
+static const char *wm8753_right_mux[] = {"Line 2", "Rx Mix"};
+static const char *wm8753_left_mux[] = {"Line 1", "Rx Mix"};
+static const char *wm8753_rxmsel[] = {"RXP - RXN", "RXP + RXN", "RXP", "RXN"};
+static const char *wm8753_sidetone_mux[] = {"Left PGA", "Mic 1", "Mic 2",
+ "Right PGA"};
+static const char *wm8753_mono2_src[] = {"Inverted Mono 1", "Left", "Right",
+ "Left + Right"};
+static const char *wm8753_out3[] = {"VREF", "ROUT2", "Left + Right"};
+static const char *wm8753_out4[] = {"VREF", "Capture ST", "LOUT2"};
+static const char *wm8753_radcsel[] = {"PGA", "Line or RXP-RXN", "Sidetone"};
+static const char *wm8753_ladcsel[] = {"PGA", "Line or RXP-RXN", "Line"};
+static const char *wm8753_mono_adc[] = {"Stereo", "Analogue Mix Left",
+ "Analogue Mix Right", "Digital Mono Mix"};
+static const char *wm8753_adc_hp[] = {"3.4Hz @ 48kHz", "82Hz @ 16k",
+ "82Hz @ 8kHz", "170Hz @ 8kHz"};
+static const char *wm8753_adc_filter[] = {"HiFi", "Voice"};
+static const char *wm8753_mic_sel[] = {"Mic 1", "Mic 2", "Mic 3"};
+static const char *wm8753_dai_mode[] = {"DAI 0", "DAI 1", "DAI 2", "DAI 3"};
+static const char *wm8753_dat_sel[] = {"Stereo", "Left ADC", "Right ADC",
+ "Channel Swap"};
+static const char *wm8753_rout2_phase[] = {"Non Inverted", "Inverted"};
+
+static const struct soc_enum wm8753_enum[] = {
+SOC_ENUM_SINGLE(WM8753_BASS, 7, 2, wm8753_base),
+SOC_ENUM_SINGLE(WM8753_BASS, 4, 6, wm8753_base_filter),
+SOC_ENUM_SINGLE(WM8753_TREBLE, 6, 2, wm8753_treble),
+SOC_ENUM_SINGLE(WM8753_ALC1, 7, 4, wm8753_alc_func),
+SOC_ENUM_SINGLE(WM8753_NGATE, 1, 2, wm8753_ng_type),
+SOC_ENUM_SINGLE(WM8753_3D, 7, 2, wm8753_3d_func),
+SOC_ENUM_SINGLE(WM8753_3D, 6, 2, wm8753_3d_uc),
+SOC_ENUM_SINGLE(WM8753_3D, 5, 2, wm8753_3d_lc),
+SOC_ENUM_SINGLE(WM8753_DAC, 1, 4, wm8753_deemp),
+SOC_ENUM_SINGLE(WM8753_DAC, 4, 4, wm8753_mono_mix),
+SOC_ENUM_SINGLE(WM8753_DAC, 6, 2, wm8753_dac_phase),
+SOC_ENUM_SINGLE(WM8753_INCTL1, 3, 4, wm8753_line_mix),
+SOC_ENUM_SINGLE(WM8753_INCTL1, 2, 2, wm8753_mono_mux),
+SOC_ENUM_SINGLE(WM8753_INCTL1, 1, 2, wm8753_right_mux),
+SOC_ENUM_SINGLE(WM8753_INCTL1, 0, 2, wm8753_left_mux),
+SOC_ENUM_SINGLE(WM8753_INCTL2, 6, 4, wm8753_rxmsel),
+SOC_ENUM_SINGLE(WM8753_INCTL2, 4, 4, wm8753_sidetone_mux),
+SOC_ENUM_SINGLE(WM8753_OUTCTL, 7, 4, wm8753_mono2_src),
+SOC_ENUM_SINGLE(WM8753_OUTCTL, 0, 3, wm8753_out3),
+SOC_ENUM_SINGLE(WM8753_ADCTL2, 7, 3, wm8753_out4),
+SOC_ENUM_SINGLE(WM8753_ADCIN, 2, 3, wm8753_radcsel),
+SOC_ENUM_SINGLE(WM8753_ADCIN, 0, 3, wm8753_ladcsel),
+SOC_ENUM_SINGLE(WM8753_ADCIN, 4, 4, wm8753_mono_adc),
+SOC_ENUM_SINGLE(WM8753_ADC, 2, 4, wm8753_adc_hp),
+SOC_ENUM_SINGLE(WM8753_ADC, 4, 2, wm8753_adc_filter),
+SOC_ENUM_SINGLE(WM8753_MICBIAS, 6, 3, wm8753_mic_sel),
+SOC_ENUM_SINGLE(WM8753_IOCTL, 2, 4, wm8753_dai_mode),
+SOC_ENUM_SINGLE(WM8753_ADC, 7, 4, wm8753_dat_sel),
+SOC_ENUM_SINGLE(WM8753_OUTCTL, 2, 2, wm8753_rout2_phase),
+};
+
+
+static int wm8753_get_dai(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int mode = wm8753_read_reg_cache(codec, WM8753_IOCTL);
+
+ ucontrol->value.integer.value[0] = (mode & 0xc) >> 2;
+ return 0;
+}
+
+static int wm8753_set_dai(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int mode = wm8753_read_reg_cache(codec, WM8753_IOCTL);
+
+ if (((mode & 0xc) >> 2) == ucontrol->value.integer.value[0])
+ return 0;
+
+ mode &= 0xfff3;
+ mode |= (ucontrol->value.integer.value[0] << 2);
+
+ wm8753_write(codec, WM8753_IOCTL, mode);
+ wm8753_set_dai_mode(codec, ucontrol->value.integer.value[0]);
+ return 1;
+}
+
+static const DECLARE_TLV_DB_SCALE(rec_mix_tlv, -1500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(mic_preamp_tlv, 1200, 600, 0);
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -9750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
+static const unsigned int out_tlv[] = {
+ TLV_DB_RANGE_HEAD(2),
+ /* 0000000 - 0101111 = "Analogue mute" */
+ 0, 48, TLV_DB_SCALE_ITEM(-25500, 0, 0),
+ 48, 127, TLV_DB_SCALE_ITEM(-7300, 100, 0),
+};
+static const DECLARE_TLV_DB_SCALE(mix_tlv, -1500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(voice_mix_tlv, -1200, 300, 0);
+static const DECLARE_TLV_DB_SCALE(pga_tlv, -1725, 75, 0);
+
+static const struct snd_kcontrol_new wm8753_snd_controls[] = {
+SOC_DOUBLE_R_TLV("PCM Volume", WM8753_LDAC, WM8753_RDAC, 0, 255, 0, dac_tlv),
+
+SOC_DOUBLE_R_TLV("ADC Capture Volume", WM8753_LADC, WM8753_RADC, 0, 255, 0,
+ adc_tlv),
+
+SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8753_LOUT1V, WM8753_ROUT1V,
+ 0, 127, 0, out_tlv),
+SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8753_LOUT2V, WM8753_ROUT2V, 0,
+ 127, 0, out_tlv),
+
+SOC_SINGLE_TLV("Mono Playback Volume", WM8753_MOUTV, 0, 127, 0, out_tlv),
+
+SOC_DOUBLE_R_TLV("Bypass Playback Volume", WM8753_LOUTM1, WM8753_ROUTM1, 4, 7,
+ 1, mix_tlv),
+SOC_DOUBLE_R_TLV("Sidetone Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 4,
+ 7, 1, mix_tlv),
+SOC_DOUBLE_R_TLV("Voice Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 0, 7,
+ 1, voice_mix_tlv),
+
+SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8753_LOUT1V, WM8753_ROUT1V, 7,
+ 1, 0),
+SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8753_LOUT2V, WM8753_ROUT2V, 7,
+ 1, 0),
+
+SOC_SINGLE_TLV("Mono Bypass Playback Volume", WM8753_MOUTM1, 4, 7, 1, mix_tlv),
+SOC_SINGLE_TLV("Mono Sidetone Playback Volume", WM8753_MOUTM2, 4, 7, 1,
+ mix_tlv),
+SOC_SINGLE_TLV("Mono Voice Playback Volume", WM8753_MOUTM2, 0, 7, 1,
+ voice_mix_tlv),
+SOC_SINGLE("Mono Playback ZC Switch", WM8753_MOUTV, 7, 1, 0),
+
+SOC_ENUM("Bass Boost", wm8753_enum[0]),
+SOC_ENUM("Bass Filter", wm8753_enum[1]),
+SOC_SINGLE("Bass Volume", WM8753_BASS, 0, 15, 1),
+
+SOC_SINGLE("Treble Volume", WM8753_TREBLE, 0, 15, 1),
+SOC_ENUM("Treble Cut-off", wm8753_enum[2]),
+
+SOC_DOUBLE_TLV("Sidetone Capture Volume", WM8753_RECMIX1, 0, 4, 7, 1,
+ rec_mix_tlv),
+SOC_SINGLE_TLV("Voice Sidetone Capture Volume", WM8753_RECMIX2, 0, 7, 1,
+ rec_mix_tlv),
+
+SOC_DOUBLE_R_TLV("Capture Volume", WM8753_LINVOL, WM8753_RINVOL, 0, 63, 0,
+ pga_tlv),
+SOC_DOUBLE_R("Capture ZC Switch", WM8753_LINVOL, WM8753_RINVOL, 6, 1, 0),
+SOC_DOUBLE_R("Capture Switch", WM8753_LINVOL, WM8753_RINVOL, 7, 1, 1),
+
+SOC_ENUM("Capture Filter Select", wm8753_enum[23]),
+SOC_ENUM("Capture Filter Cut-off", wm8753_enum[24]),
+SOC_SINGLE("Capture Filter Switch", WM8753_ADC, 0, 1, 1),
+
+SOC_SINGLE("ALC Capture Target Volume", WM8753_ALC1, 0, 7, 0),
+SOC_SINGLE("ALC Capture Max Volume", WM8753_ALC1, 4, 7, 0),
+SOC_ENUM("ALC Capture Function", wm8753_enum[3]),
+SOC_SINGLE("ALC Capture ZC Switch", WM8753_ALC2, 8, 1, 0),
+SOC_SINGLE("ALC Capture Hold Time", WM8753_ALC2, 0, 15, 1),
+SOC_SINGLE("ALC Capture Decay Time", WM8753_ALC3, 4, 15, 1),
+SOC_SINGLE("ALC Capture Attack Time", WM8753_ALC3, 0, 15, 0),
+SOC_SINGLE("ALC Capture NG Threshold", WM8753_NGATE, 3, 31, 0),
+SOC_ENUM("ALC Capture NG Type", wm8753_enum[4]),
+SOC_SINGLE("ALC Capture NG Switch", WM8753_NGATE, 0, 1, 0),
+
+SOC_ENUM("3D Function", wm8753_enum[5]),
+SOC_ENUM("3D Upper Cut-off", wm8753_enum[6]),
+SOC_ENUM("3D Lower Cut-off", wm8753_enum[7]),
+SOC_SINGLE("3D Volume", WM8753_3D, 1, 15, 0),
+SOC_SINGLE("3D Switch", WM8753_3D, 0, 1, 0),
+
+SOC_SINGLE("Capture 6dB Attenuate", WM8753_ADCTL1, 2, 1, 0),
+SOC_SINGLE("Playback 6dB Attenuate", WM8753_ADCTL1, 1, 1, 0),
+
+SOC_ENUM("De-emphasis", wm8753_enum[8]),
+SOC_ENUM("Playback Mono Mix", wm8753_enum[9]),
+SOC_ENUM("Playback Phase", wm8753_enum[10]),
+
+SOC_SINGLE_TLV("Mic2 Capture Volume", WM8753_INCTL1, 7, 3, 0, mic_preamp_tlv),
+SOC_SINGLE_TLV("Mic1 Capture Volume", WM8753_INCTL1, 5, 3, 0, mic_preamp_tlv),
+
+SOC_ENUM_EXT("DAI Mode", wm8753_enum[26], wm8753_get_dai, wm8753_set_dai),
+
+SOC_ENUM("ADC Data Select", wm8753_enum[27]),
+SOC_ENUM("ROUT2 Phase", wm8753_enum[28]),
+};
+
+/* add non dapm controls */
+static int wm8753_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8753_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8753_snd_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+/*
+ * _DAPM_ Controls
+ */
+
+/* Left Mixer */
+static const struct snd_kcontrol_new wm8753_left_mixer_controls[] = {
+SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_LOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_LOUTM2, 7, 1, 0),
+SOC_DAPM_SINGLE("Left Playback Switch", WM8753_LOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_LOUTM1, 7, 1, 0),
+};
+
+/* Right mixer */
+static const struct snd_kcontrol_new wm8753_right_mixer_controls[] = {
+SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_ROUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_ROUTM2, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8753_ROUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_ROUTM1, 7, 1, 0),
+};
+
+/* Mono mixer */
+static const struct snd_kcontrol_new wm8753_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8753_MOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8753_MOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_MOUTM2, 3, 1, 0),
+SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_MOUTM2, 7, 1, 0),
+SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_MOUTM1, 7, 1, 0),
+};
+
+/* Mono 2 Mux */
+static const struct snd_kcontrol_new wm8753_mono2_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[17]);
+
+/* Out 3 Mux */
+static const struct snd_kcontrol_new wm8753_out3_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[18]);
+
+/* Out 4 Mux */
+static const struct snd_kcontrol_new wm8753_out4_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[19]);
+
+/* ADC Mono Mix */
+static const struct snd_kcontrol_new wm8753_adc_mono_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[22]);
+
+/* Record mixer */
+static const struct snd_kcontrol_new wm8753_record_mixer_controls[] = {
+SOC_DAPM_SINGLE("Voice Capture Switch", WM8753_RECMIX2, 3, 1, 0),
+SOC_DAPM_SINGLE("Left Capture Switch", WM8753_RECMIX1, 3, 1, 0),
+SOC_DAPM_SINGLE("Right Capture Switch", WM8753_RECMIX1, 7, 1, 0),
+};
+
+/* Left ADC mux */
+static const struct snd_kcontrol_new wm8753_adc_left_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[21]);
+
+/* Right ADC mux */
+static const struct snd_kcontrol_new wm8753_adc_right_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[20]);
+
+/* MIC mux */
+static const struct snd_kcontrol_new wm8753_mic_mux_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[16]);
+
+/* ALC mixer */
+static const struct snd_kcontrol_new wm8753_alc_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Capture Switch", WM8753_INCTL2, 3, 1, 0),
+SOC_DAPM_SINGLE("Mic2 Capture Switch", WM8753_INCTL2, 2, 1, 0),
+SOC_DAPM_SINGLE("Mic1 Capture Switch", WM8753_INCTL2, 1, 1, 0),
+SOC_DAPM_SINGLE("Rx Capture Switch", WM8753_INCTL2, 0, 1, 0),
+};
+
+/* Left Line mux */
+static const struct snd_kcontrol_new wm8753_line_left_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[14]);
+
+/* Right Line mux */
+static const struct snd_kcontrol_new wm8753_line_right_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[13]);
+
+/* Mono Line mux */
+static const struct snd_kcontrol_new wm8753_line_mono_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[12]);
+
+/* Line mux and mixer */
+static const struct snd_kcontrol_new wm8753_line_mux_mix_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[11]);
+
+/* Rx mux and mixer */
+static const struct snd_kcontrol_new wm8753_rx_mux_mix_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[15]);
+
+/* Mic Selector Mux */
+static const struct snd_kcontrol_new wm8753_mic_sel_mux_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[25]);
+
+static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = {
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8753_PWR1, 5, 0),
+SND_SOC_DAPM_MIXER("Left Mixer", WM8753_PWR4, 0, 0,
+ &wm8753_left_mixer_controls[0], ARRAY_SIZE(wm8753_left_mixer_controls)),
+SND_SOC_DAPM_PGA("Left Out 1", WM8753_PWR3, 8, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Left Out 2", WM8753_PWR3, 6, 0, NULL, 0),
+SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", WM8753_PWR1, 3, 0),
+SND_SOC_DAPM_OUTPUT("LOUT1"),
+SND_SOC_DAPM_OUTPUT("LOUT2"),
+SND_SOC_DAPM_MIXER("Right Mixer", WM8753_PWR4, 1, 0,
+ &wm8753_right_mixer_controls[0], ARRAY_SIZE(wm8753_right_mixer_controls)),
+SND_SOC_DAPM_PGA("Right Out 1", WM8753_PWR3, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Out 2", WM8753_PWR3, 5, 0, NULL, 0),
+SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", WM8753_PWR1, 2, 0),
+SND_SOC_DAPM_OUTPUT("ROUT1"),
+SND_SOC_DAPM_OUTPUT("ROUT2"),
+SND_SOC_DAPM_MIXER("Mono Mixer", WM8753_PWR4, 2, 0,
+ &wm8753_mono_mixer_controls[0], ARRAY_SIZE(wm8753_mono_mixer_controls)),
+SND_SOC_DAPM_PGA("Mono Out 1", WM8753_PWR3, 2, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Mono Out 2", WM8753_PWR3, 1, 0, NULL, 0),
+SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", WM8753_PWR1, 4, 0),
+SND_SOC_DAPM_OUTPUT("MONO1"),
+SND_SOC_DAPM_MUX("Mono 2 Mux", SND_SOC_NOPM, 0, 0, &wm8753_mono2_controls),
+SND_SOC_DAPM_OUTPUT("MONO2"),
+SND_SOC_DAPM_MIXER("Out3 Left + Right", -1, 0, 0, NULL, 0),
+SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0, &wm8753_out3_controls),
+SND_SOC_DAPM_PGA("Out 3", WM8753_PWR3, 4, 0, NULL, 0),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+SND_SOC_DAPM_MUX("Out4 Mux", SND_SOC_NOPM, 0, 0, &wm8753_out4_controls),
+SND_SOC_DAPM_PGA("Out 4", WM8753_PWR3, 3, 0, NULL, 0),
+SND_SOC_DAPM_OUTPUT("OUT4"),
+SND_SOC_DAPM_MIXER("Playback Mixer", WM8753_PWR4, 3, 0,
+ &wm8753_record_mixer_controls[0],
+ ARRAY_SIZE(wm8753_record_mixer_controls)),
+SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8753_PWR2, 3, 0),
+SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8753_PWR2, 2, 0),
+SND_SOC_DAPM_MUX("Capture Left Mixer", SND_SOC_NOPM, 0, 0,
+ &wm8753_adc_mono_controls),
+SND_SOC_DAPM_MUX("Capture Right Mixer", SND_SOC_NOPM, 0, 0,
+ &wm8753_adc_mono_controls),
+SND_SOC_DAPM_MUX("Capture Left Mux", SND_SOC_NOPM, 0, 0,
+ &wm8753_adc_left_controls),
+SND_SOC_DAPM_MUX("Capture Right Mux", SND_SOC_NOPM, 0, 0,
+ &wm8753_adc_right_controls),
+SND_SOC_DAPM_MUX("Mic Sidetone Mux", SND_SOC_NOPM, 0, 0,
+ &wm8753_mic_mux_controls),
+SND_SOC_DAPM_PGA("Left Capture Volume", WM8753_PWR2, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Capture Volume", WM8753_PWR2, 4, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("ALC Mixer", WM8753_PWR2, 6, 0,
+ &wm8753_alc_mixer_controls[0], ARRAY_SIZE(wm8753_alc_mixer_controls)),
+SND_SOC_DAPM_MUX("Line Left Mux", SND_SOC_NOPM, 0, 0,
+ &wm8753_line_left_controls),
+SND_SOC_DAPM_MUX("Line Right Mux", SND_SOC_NOPM, 0, 0,
+ &wm8753_line_right_controls),
+SND_SOC_DAPM_MUX("Line Mono Mux", SND_SOC_NOPM, 0, 0,
+ &wm8753_line_mono_controls),
+SND_SOC_DAPM_MUX("Line Mixer", WM8753_PWR2, 0, 0,
+ &wm8753_line_mux_mix_controls),
+SND_SOC_DAPM_MUX("Rx Mixer", WM8753_PWR2, 1, 0,
+ &wm8753_rx_mux_mix_controls),
+SND_SOC_DAPM_PGA("Mic 1 Volume", WM8753_PWR2, 8, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Mic 2 Volume", WM8753_PWR2, 7, 0, NULL, 0),
+SND_SOC_DAPM_MUX("Mic Selection Mux", SND_SOC_NOPM, 0, 0,
+ &wm8753_mic_sel_mux_controls),
+SND_SOC_DAPM_INPUT("LINE1"),
+SND_SOC_DAPM_INPUT("LINE2"),
+SND_SOC_DAPM_INPUT("RXP"),
+SND_SOC_DAPM_INPUT("RXN"),
+SND_SOC_DAPM_INPUT("ACIN"),
+SND_SOC_DAPM_OUTPUT("ACOP"),
+SND_SOC_DAPM_INPUT("MIC1N"),
+SND_SOC_DAPM_INPUT("MIC1"),
+SND_SOC_DAPM_INPUT("MIC2N"),
+SND_SOC_DAPM_INPUT("MIC2"),
+SND_SOC_DAPM_VMID("VREF"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* left mixer */
+ {"Left Mixer", "Left Playback Switch", "Left DAC"},
+ {"Left Mixer", "Voice Playback Switch", "Voice DAC"},
+ {"Left Mixer", "Sidetone Playback Switch", "Mic Sidetone Mux"},
+ {"Left Mixer", "Bypass Playback Switch", "Line Left Mux"},
+
+ /* right mixer */
+ {"Right Mixer", "Right Playback Switch", "Right DAC"},
+ {"Right Mixer", "Voice Playback Switch", "Voice DAC"},
+ {"Right Mixer", "Sidetone Playback Switch", "Mic Sidetone Mux"},
+ {"Right Mixer", "Bypass Playback Switch", "Line Right Mux"},
+
+ /* mono mixer */
+ {"Mono Mixer", "Voice Playback Switch", "Voice DAC"},
+ {"Mono Mixer", "Left Playback Switch", "Left DAC"},
+ {"Mono Mixer", "Right Playback Switch", "Right DAC"},
+ {"Mono Mixer", "Sidetone Playback Switch", "Mic Sidetone Mux"},
+ {"Mono Mixer", "Bypass Playback Switch", "Line Mono Mux"},
+
+ /* left out */
+ {"Left Out 1", NULL, "Left Mixer"},
+ {"Left Out 2", NULL, "Left Mixer"},
+ {"LOUT1", NULL, "Left Out 1"},
+ {"LOUT2", NULL, "Left Out 2"},
+
+ /* right out */
+ {"Right Out 1", NULL, "Right Mixer"},
+ {"Right Out 2", NULL, "Right Mixer"},
+ {"ROUT1", NULL, "Right Out 1"},
+ {"ROUT2", NULL, "Right Out 2"},
+
+ /* mono 1 out */
+ {"Mono Out 1", NULL, "Mono Mixer"},
+ {"MONO1", NULL, "Mono Out 1"},
+
+ /* mono 2 out */
+ {"Mono 2 Mux", "Left + Right", "Out3 Left + Right"},
+ {"Mono 2 Mux", "Inverted Mono 1", "MONO1"},
+ {"Mono 2 Mux", "Left", "Left Mixer"},
+ {"Mono 2 Mux", "Right", "Right Mixer"},
+ {"Mono Out 2", NULL, "Mono 2 Mux"},
+ {"MONO2", NULL, "Mono Out 2"},
+
+ /* out 3 */
+ {"Out3 Left + Right", NULL, "Left Mixer"},
+ {"Out3 Left + Right", NULL, "Right Mixer"},
+ {"Out3 Mux", "VREF", "VREF"},
+ {"Out3 Mux", "Left + Right", "Out3 Left + Right"},
+ {"Out3 Mux", "ROUT2", "ROUT2"},
+ {"Out 3", NULL, "Out3 Mux"},
+ {"OUT3", NULL, "Out 3"},
+
+ /* out 4 */
+ {"Out4 Mux", "VREF", "VREF"},
+ {"Out4 Mux", "Capture ST", "Playback Mixer"},
+ {"Out4 Mux", "LOUT2", "LOUT2"},
+ {"Out 4", NULL, "Out4 Mux"},
+ {"OUT4", NULL, "Out 4"},
+
+ /* record mixer */
+ {"Playback Mixer", "Left Capture Switch", "Left Mixer"},
+ {"Playback Mixer", "Voice Capture Switch", "Mono Mixer"},
+ {"Playback Mixer", "Right Capture Switch", "Right Mixer"},
+
+ /* Mic/SideTone Mux */
+ {"Mic Sidetone Mux", "Left PGA", "Left Capture Volume"},
+ {"Mic Sidetone Mux", "Right PGA", "Right Capture Volume"},
+ {"Mic Sidetone Mux", "Mic 1", "Mic 1 Volume"},
+ {"Mic Sidetone Mux", "Mic 2", "Mic 2 Volume"},
+
+ /* Capture Left Mux */
+ {"Capture Left Mux", "PGA", "Left Capture Volume"},
+ {"Capture Left Mux", "Line or RXP-RXN", "Line Left Mux"},
+ {"Capture Left Mux", "Line", "LINE1"},
+
+ /* Capture Right Mux */
+ {"Capture Right Mux", "PGA", "Right Capture Volume"},
+ {"Capture Right Mux", "Line or RXP-RXN", "Line Right Mux"},
+ {"Capture Right Mux", "Sidetone", "Playback Mixer"},
+
+ /* Mono Capture mixer-mux */
+ {"Capture Right Mixer", "Stereo", "Capture Right Mux"},
+ {"Capture Left Mixer", "Analogue Mix Left", "Capture Left Mux"},
+ {"Capture Left Mixer", "Analogue Mix Left", "Capture Right Mux"},
+ {"Capture Right Mixer", "Analogue Mix Right", "Capture Left Mux"},
+ {"Capture Right Mixer", "Analogue Mix Right", "Capture Right Mux"},
+ {"Capture Left Mixer", "Digital Mono Mix", "Capture Left Mux"},
+ {"Capture Left Mixer", "Digital Mono Mix", "Capture Right Mux"},
+ {"Capture Right Mixer", "Digital Mono Mix", "Capture Left Mux"},
+ {"Capture Right Mixer", "Digital Mono Mix", "Capture Right Mux"},
+
+ /* ADC */
+ {"Left ADC", NULL, "Capture Left Mixer"},
+ {"Right ADC", NULL, "Capture Right Mixer"},
+
+ /* Left Capture Volume */
+ {"Left Capture Volume", NULL, "ACIN"},
+
+ /* Right Capture Volume */
+ {"Right Capture Volume", NULL, "Mic 2 Volume"},
+
+ /* ALC Mixer */
+ {"ALC Mixer", "Line Capture Switch", "Line Mixer"},
+ {"ALC Mixer", "Mic2 Capture Switch", "Mic 2 Volume"},
+ {"ALC Mixer", "Mic1 Capture Switch", "Mic 1 Volume"},
+ {"ALC Mixer", "Rx Capture Switch", "Rx Mixer"},
+
+ /* Line Left Mux */
+ {"Line Left Mux", "Line 1", "LINE1"},
+ {"Line Left Mux", "Rx Mix", "Rx Mixer"},
+
+ /* Line Right Mux */
+ {"Line Right Mux", "Line 2", "LINE2"},
+ {"Line Right Mux", "Rx Mix", "Rx Mixer"},
+
+ /* Line Mono Mux */
+ {"Line Mono Mux", "Line Mix", "Line Mixer"},
+ {"Line Mono Mux", "Rx Mix", "Rx Mixer"},
+
+ /* Line Mixer/Mux */
+ {"Line Mixer", "Line 1 + 2", "LINE1"},
+ {"Line Mixer", "Line 1 - 2", "LINE1"},
+ {"Line Mixer", "Line 1 + 2", "LINE2"},
+ {"Line Mixer", "Line 1 - 2", "LINE2"},
+ {"Line Mixer", "Line 1", "LINE1"},
+ {"Line Mixer", "Line 2", "LINE2"},
+
+ /* Rx Mixer/Mux */
+ {"Rx Mixer", "RXP - RXN", "RXP"},
+ {"Rx Mixer", "RXP + RXN", "RXP"},
+ {"Rx Mixer", "RXP - RXN", "RXN"},
+ {"Rx Mixer", "RXP + RXN", "RXN"},
+ {"Rx Mixer", "RXP", "RXP"},
+ {"Rx Mixer", "RXN", "RXN"},
+
+ /* Mic 1 Volume */
+ {"Mic 1 Volume", NULL, "MIC1N"},
+ {"Mic 1 Volume", NULL, "Mic Selection Mux"},
+
+ /* Mic 2 Volume */
+ {"Mic 2 Volume", NULL, "MIC2N"},
+ {"Mic 2 Volume", NULL, "MIC2"},
+
+ /* Mic Selector Mux */
+ {"Mic Selection Mux", "Mic 1", "MIC1"},
+ {"Mic Selection Mux", "Mic 2", "MIC2N"},
+ {"Mic Selection Mux", "Mic 3", "MIC2"},
+
+ /* ACOP */
+ {"ACOP", NULL, "ALC Mixer"},
+};
+
+static int wm8753_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm8753_dapm_widgets,
+ ARRAY_SIZE(wm8753_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+/* PLL divisors */
+struct _pll_div {
+ u32 div2:1;
+ u32 n:4;
+ u32 k:24;
+};
+
+/* The size in bits of the pll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 22) * 10)
+
+static void pll_factors(struct _pll_div *pll_div, unsigned int target,
+ unsigned int source)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod;
+
+ Ndiv = target / source;
+ if (Ndiv < 6) {
+ source >>= 1;
+ pll_div->div2 = 1;
+ Ndiv = target / source;
+ } else
+ pll_div->div2 = 0;
+
+ if ((Ndiv < 6) || (Ndiv > 12))
+ printk(KERN_WARNING
+ "wm8753: unsupported N = %d\n", Ndiv);
+
+ pll_div->n = Ndiv;
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ /* Check if we need to round */
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ K /= 10;
+
+ pll_div->k = K;
+}
+
+static int wm8753_set_dai_pll(struct snd_soc_dai *codec_dai,
+ int pll_id, unsigned int freq_in, unsigned int freq_out)
+{
+ u16 reg, enable;
+ int offset;
+ struct snd_soc_codec *codec = codec_dai->codec;
+
+ if (pll_id < WM8753_PLL1 || pll_id > WM8753_PLL2)
+ return -ENODEV;
+
+ if (pll_id == WM8753_PLL1) {
+ offset = 0;
+ enable = 0x10;
+ reg = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0xffef;
+ } else {
+ offset = 4;
+ enable = 0x8;
+ reg = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0xfff7;
+ }
+
+ if (!freq_in || !freq_out) {
+ /* disable PLL */
+ wm8753_write(codec, WM8753_PLL1CTL1 + offset, 0x0026);
+ wm8753_write(codec, WM8753_CLOCK, reg);
+ return 0;
+ } else {
+ u16 value = 0;
+ struct _pll_div pll_div;
+
+ pll_factors(&pll_div, freq_out * 8, freq_in);
+
+ /* set up N and K PLL divisor ratios */
+ /* bits 8:5 = PLL_N, bits 3:0 = PLL_K[21:18] */
+ value = (pll_div.n << 5) + ((pll_div.k & 0x3c0000) >> 18);
+ wm8753_write(codec, WM8753_PLL1CTL2 + offset, value);
+
+ /* bits 8:0 = PLL_K[17:9] */
+ value = (pll_div.k & 0x03fe00) >> 9;
+ wm8753_write(codec, WM8753_PLL1CTL3 + offset, value);
+
+ /* bits 8:0 = PLL_K[8:0] */
+ value = pll_div.k & 0x0001ff;
+ wm8753_write(codec, WM8753_PLL1CTL4 + offset, value);
+
+ /* set PLL as input and enable */
+ wm8753_write(codec, WM8753_PLL1CTL1 + offset, 0x0027 |
+ (pll_div.div2 << 3));
+ wm8753_write(codec, WM8753_CLOCK, reg | enable);
+ }
+ return 0;
+}
+
+struct _coeff_div {
+ u32 mclk;
+ u32 rate;
+ u8 sr:5;
+ u8 usb:1;
+};
+
+/* codec hifi mclk (after PLL) clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+ /* 8k */
+ {12288000, 8000, 0x6, 0x0},
+ {11289600, 8000, 0x16, 0x0},
+ {18432000, 8000, 0x7, 0x0},
+ {16934400, 8000, 0x17, 0x0},
+ {12000000, 8000, 0x6, 0x1},
+
+ /* 11.025k */
+ {11289600, 11025, 0x18, 0x0},
+ {16934400, 11025, 0x19, 0x0},
+ {12000000, 11025, 0x19, 0x1},
+
+ /* 16k */
+ {12288000, 16000, 0xa, 0x0},
+ {18432000, 16000, 0xb, 0x0},
+ {12000000, 16000, 0xa, 0x1},
+
+ /* 22.05k */
+ {11289600, 22050, 0x1a, 0x0},
+ {16934400, 22050, 0x1b, 0x0},
+ {12000000, 22050, 0x1b, 0x1},
+
+ /* 32k */
+ {12288000, 32000, 0xc, 0x0},
+ {18432000, 32000, 0xd, 0x0},
+ {12000000, 32000, 0xa, 0x1},
+
+ /* 44.1k */
+ {11289600, 44100, 0x10, 0x0},
+ {16934400, 44100, 0x11, 0x0},
+ {12000000, 44100, 0x11, 0x1},
+
+ /* 48k */
+ {12288000, 48000, 0x0, 0x0},
+ {18432000, 48000, 0x1, 0x0},
+ {12000000, 48000, 0x0, 0x1},
+
+ /* 88.2k */
+ {11289600, 88200, 0x1e, 0x0},
+ {16934400, 88200, 0x1f, 0x0},
+ {12000000, 88200, 0x1f, 0x1},
+
+ /* 96k */
+ {12288000, 96000, 0xe, 0x0},
+ {18432000, 96000, 0xf, 0x0},
+ {12000000, 96000, 0xe, 0x1},
+};
+
+static int get_coeff(int mclk, int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+ if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+ return i;
+ }
+ return -EINVAL;
+}
+
+/*
+ * Clock after PLL and dividers
+ */
+static int wm8753_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 wm8753_priv *wm8753 = codec->private_data;
+
+ switch (freq) {
+ case 11289600:
+ case 12000000:
+ case 12288000:
+ case 16934400:
+ case 18432000:
+ if (clk_id == WM8753_MCLK) {
+ wm8753->sysclk = freq;
+ return 0;
+ } else if (clk_id == WM8753_PCMCLK) {
+ wm8753->pcmclk = freq;
+ return 0;
+ }
+ break;
+ }
+ return -EINVAL;
+}
+
+/*
+ * Set's ADC and Voice DAC format.
+ */
+static int wm8753_vdac_adc_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 voice = wm8753_read_reg_cache(codec, WM8753_PCM) & 0x01ec;
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ voice |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ voice |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ voice |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ voice |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8753_write(codec, WM8753_PCM, voice);
+ return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8753_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct wm8753_priv *wm8753 = codec->private_data;
+ u16 voice = wm8753_read_reg_cache(codec, WM8753_PCM) & 0x01f3;
+ u16 srate = wm8753_read_reg_cache(codec, WM8753_SRATE1) & 0x017f;
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ voice |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ voice |= 0x0008;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ voice |= 0x000c;
+ break;
+ }
+
+ /* sample rate */
+ if (params_rate(params) * 384 == wm8753->pcmclk)
+ srate |= 0x80;
+ wm8753_write(codec, WM8753_SRATE1, srate);
+
+ wm8753_write(codec, WM8753_PCM, voice);
+ return 0;
+}
+
+/*
+ * Set's PCM dai fmt and BCLK.
+ */
+static int wm8753_pcm_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 voice, ioctl;
+
+ voice = wm8753_read_reg_cache(codec, WM8753_PCM) & 0x011f;
+ ioctl = wm8753_read_reg_cache(codec, WM8753_IOCTL) & 0x015d;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ ioctl |= 0x2;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ voice |= 0x0040;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ /* frame inversion not valid for DSP modes */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ voice |= 0x0080;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ voice &= ~0x0010;
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ voice |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ voice |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ voice |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8753_write(codec, WM8753_PCM, voice);
+ wm8753_write(codec, WM8753_IOCTL, ioctl);
+ return 0;
+}
+
+static int wm8753_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ switch (div_id) {
+ case WM8753_PCMDIV:
+ reg = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0x003f;
+ wm8753_write(codec, WM8753_CLOCK, reg | div);
+ break;
+ case WM8753_BCLKDIV:
+ reg = wm8753_read_reg_cache(codec, WM8753_SRATE2) & 0x01c7;
+ wm8753_write(codec, WM8753_SRATE2, reg | div);
+ break;
+ case WM8753_VXCLKDIV:
+ reg = wm8753_read_reg_cache(codec, WM8753_SRATE2) & 0x003f;
+ wm8753_write(codec, WM8753_SRATE2, reg | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*
+ * Set's HiFi DAC format.
+ */
+static int wm8753_hdac_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 hifi = wm8753_read_reg_cache(codec, WM8753_HIFI) & 0x01e0;
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ hifi |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ hifi |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ hifi |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ hifi |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8753_write(codec, WM8753_HIFI, hifi);
+ return 0;
+}
+
+/*
+ * Set's I2S DAI format.
+ */
+static int wm8753_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 ioctl, hifi;
+
+ hifi = wm8753_read_reg_cache(codec, WM8753_HIFI) & 0x011f;
+ ioctl = wm8753_read_reg_cache(codec, WM8753_IOCTL) & 0x00ae;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ ioctl |= 0x1;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ hifi |= 0x0040;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ /* frame inversion not valid for DSP modes */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ hifi |= 0x0080;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ hifi &= ~0x0010;
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ hifi |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ hifi |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ hifi |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8753_write(codec, WM8753_HIFI, hifi);
+ wm8753_write(codec, WM8753_IOCTL, ioctl);
+ return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8753_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct wm8753_priv *wm8753 = codec->private_data;
+ u16 srate = wm8753_read_reg_cache(codec, WM8753_SRATE1) & 0x01c0;
+ u16 hifi = wm8753_read_reg_cache(codec, WM8753_HIFI) & 0x01f3;
+ int coeff;
+
+ /* is digital filter coefficient valid ? */
+ coeff = get_coeff(wm8753->sysclk, params_rate(params));
+ if (coeff < 0) {
+ printk(KERN_ERR "wm8753 invalid MCLK or rate\n");
+ return coeff;
+ }
+ wm8753_write(codec, WM8753_SRATE1, srate | (coeff_div[coeff].sr << 1) |
+ coeff_div[coeff].usb);
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ hifi |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ hifi |= 0x0008;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ hifi |= 0x000c;
+ break;
+ }
+
+ wm8753_write(codec, WM8753_HIFI, hifi);
+ return 0;
+}
+
+static int wm8753_mode1v_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 clock;
+
+ /* set clk source as pcmclk */
+ clock = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0xfffb;
+ wm8753_write(codec, WM8753_CLOCK, clock);
+
+ if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0)
+ return -EINVAL;
+ return wm8753_pcm_set_dai_fmt(codec_dai, fmt);
+}
+
+static int wm8753_mode1h_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ if (wm8753_hdac_set_dai_fmt(codec_dai, fmt) < 0)
+ return -EINVAL;
+ return wm8753_i2s_set_dai_fmt(codec_dai, fmt);
+}
+
+static int wm8753_mode2_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 clock;
+
+ /* set clk source as pcmclk */
+ clock = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0xfffb;
+ wm8753_write(codec, WM8753_CLOCK, clock);
+
+ if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0)
+ return -EINVAL;
+ return wm8753_i2s_set_dai_fmt(codec_dai, fmt);
+}
+
+static int wm8753_mode3_4_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 clock;
+
+ /* set clk source as mclk */
+ clock = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0xfffb;
+ wm8753_write(codec, WM8753_CLOCK, clock | 0x4);
+
+ if (wm8753_hdac_set_dai_fmt(codec_dai, fmt) < 0)
+ return -EINVAL;
+ if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0)
+ return -EINVAL;
+ return wm8753_i2s_set_dai_fmt(codec_dai, fmt);
+}
+
+static int wm8753_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = wm8753_read_reg_cache(codec, WM8753_DAC) & 0xfff7;
+
+ /* the digital mute covers the HiFi and Voice DAC's on the WM8753.
+ * make sure we check if they are not both active when we mute */
+ if (mute && dai->id == 1) {
+ if (!wm8753_dai[WM8753_DAI_VOICE].playback.active ||
+ !wm8753_dai[WM8753_DAI_HIFI].playback.active)
+ wm8753_write(codec, WM8753_DAC, mute_reg | 0x8);
+ } else {
+ if (mute)
+ wm8753_write(codec, WM8753_DAC, mute_reg | 0x8);
+ else
+ wm8753_write(codec, WM8753_DAC, mute_reg);
+ }
+
+ return 0;
+}
+
+static int wm8753_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 pwr_reg = wm8753_read_reg_cache(codec, WM8753_PWR1) & 0xfe3e;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* set vmid to 50k and unmute dac */
+ wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x00c0);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ /* set vmid to 5k for quick power up */
+ wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x01c1);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* mute dac and set vmid to 500k, enable VREF */
+ wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x0141);
+ break;
+ case SND_SOC_BIAS_OFF:
+ wm8753_write(codec, WM8753_PWR1, 0x0001);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define WM8753_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+#define WM8753_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+/*
+ * The WM8753 supports upto 4 different and mutually exclusive DAI
+ * configurations. This gives 2 PCM's available for use, hifi and voice.
+ * NOTE: The Voice PCM cannot play or capture audio to the CPU as it's DAI
+ * is connected between the wm8753 and a BT codec or GSM modem.
+ *
+ * 1. Voice over PCM DAI - HIFI DAC over HIFI DAI
+ * 2. Voice over HIFI DAI - HIFI disabled
+ * 3. Voice disabled - HIFI over HIFI
+ * 4. Voice disabled - HIFI over HIFI, uses voice DAI LRC for capture
+ */
+static const struct snd_soc_dai wm8753_all_dai[] = {
+/* DAI HiFi mode 1 */
+{ .name = "WM8753 HiFi",
+ .id = 1,
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,},
+ .capture = { /* dummy for fast DAI switching */
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,},
+ .ops = {
+ .hw_params = wm8753_i2s_hw_params,},
+ .dai_ops = {
+ .digital_mute = wm8753_mute,
+ .set_fmt = wm8753_mode1h_set_dai_fmt,
+ .set_clkdiv = wm8753_set_dai_clkdiv,
+ .set_pll = wm8753_set_dai_pll,
+ .set_sysclk = wm8753_set_dai_sysclk,
+ },
+},
+/* DAI Voice mode 1 */
+{ .name = "WM8753 Voice",
+ .id = 1,
+ .playback = {
+ .stream_name = "Voice Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,},
+ .ops = {
+ .hw_params = wm8753_pcm_hw_params,},
+ .dai_ops = {
+ .digital_mute = wm8753_mute,
+ .set_fmt = wm8753_mode1v_set_dai_fmt,
+ .set_clkdiv = wm8753_set_dai_clkdiv,
+ .set_pll = wm8753_set_dai_pll,
+ .set_sysclk = wm8753_set_dai_sysclk,
+ },
+},
+/* DAI HiFi mode 2 - dummy */
+{ .name = "WM8753 HiFi",
+ .id = 2,
+},
+/* DAI Voice mode 2 */
+{ .name = "WM8753 Voice",
+ .id = 2,
+ .playback = {
+ .stream_name = "Voice Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,},
+ .ops = {
+ .hw_params = wm8753_pcm_hw_params,},
+ .dai_ops = {
+ .digital_mute = wm8753_mute,
+ .set_fmt = wm8753_mode2_set_dai_fmt,
+ .set_clkdiv = wm8753_set_dai_clkdiv,
+ .set_pll = wm8753_set_dai_pll,
+ .set_sysclk = wm8753_set_dai_sysclk,
+ },
+},
+/* DAI HiFi mode 3 */
+{ .name = "WM8753 HiFi",
+ .id = 3,
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,},
+ .ops = {
+ .hw_params = wm8753_i2s_hw_params,},
+ .dai_ops = {
+ .digital_mute = wm8753_mute,
+ .set_fmt = wm8753_mode3_4_set_dai_fmt,
+ .set_clkdiv = wm8753_set_dai_clkdiv,
+ .set_pll = wm8753_set_dai_pll,
+ .set_sysclk = wm8753_set_dai_sysclk,
+ },
+},
+/* DAI Voice mode 3 - dummy */
+{ .name = "WM8753 Voice",
+ .id = 3,
+},
+/* DAI HiFi mode 4 */
+{ .name = "WM8753 HiFi",
+ .id = 4,
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,},
+ .ops = {
+ .hw_params = wm8753_i2s_hw_params,},
+ .dai_ops = {
+ .digital_mute = wm8753_mute,
+ .set_fmt = wm8753_mode3_4_set_dai_fmt,
+ .set_clkdiv = wm8753_set_dai_clkdiv,
+ .set_pll = wm8753_set_dai_pll,
+ .set_sysclk = wm8753_set_dai_sysclk,
+ },
+},
+/* DAI Voice mode 4 - dummy */
+{ .name = "WM8753 Voice",
+ .id = 4,
+},
+};
+
+struct snd_soc_dai wm8753_dai[2];
+EXPORT_SYMBOL_GPL(wm8753_dai);
+
+static void wm8753_set_dai_mode(struct snd_soc_codec *codec, unsigned int mode)
+{
+ if (mode < 4) {
+ int playback_active, capture_active, codec_active, pop_wait;
+ void *private_data;
+
+ playback_active = wm8753_dai[0].playback.active;
+ capture_active = wm8753_dai[0].capture.active;
+ codec_active = wm8753_dai[0].active;
+ private_data = wm8753_dai[0].private_data;
+ pop_wait = wm8753_dai[0].pop_wait;
+ wm8753_dai[0] = wm8753_all_dai[mode << 1];
+ wm8753_dai[0].playback.active = playback_active;
+ wm8753_dai[0].capture.active = capture_active;
+ wm8753_dai[0].active = codec_active;
+ wm8753_dai[0].private_data = private_data;
+ wm8753_dai[0].pop_wait = pop_wait;
+
+ playback_active = wm8753_dai[1].playback.active;
+ capture_active = wm8753_dai[1].capture.active;
+ codec_active = wm8753_dai[1].active;
+ private_data = wm8753_dai[1].private_data;
+ pop_wait = wm8753_dai[1].pop_wait;
+ wm8753_dai[1] = wm8753_all_dai[(mode << 1) + 1];
+ wm8753_dai[1].playback.active = playback_active;
+ wm8753_dai[1].capture.active = capture_active;
+ wm8753_dai[1].active = codec_active;
+ wm8753_dai[1].private_data = private_data;
+ wm8753_dai[1].pop_wait = pop_wait;
+ }
+ wm8753_dai[0].codec = codec;
+ wm8753_dai[1].codec = codec;
+}
+
+static void wm8753_work(struct work_struct *work)
+{
+ struct snd_soc_codec *codec =
+ container_of(work, struct snd_soc_codec, delayed_work.work);
+ wm8753_set_bias_level(codec, codec->bias_level);
+}
+
+static int wm8753_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ /* we only need to suspend if we are a valid card */
+ if (!codec->card)
+ return 0;
+
+ wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8753_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+
+ /* we only need to resume if we are a valid card */
+ if (!codec->card)
+ return 0;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8753_reg); i++) {
+ if (i + 1 == WM8753_RESET)
+ continue;
+ data[0] = ((i + 1) << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+
+ wm8753_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* charge wm8753 caps */
+ if (codec->suspend_bias_level == SND_SOC_BIAS_ON) {
+ wm8753_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
+ codec->bias_level = SND_SOC_BIAS_ON;
+ schedule_delayed_work(&codec->delayed_work,
+ msecs_to_jiffies(caps_charge));
+ }
+
+ return 0;
+}
+
+/*
+ * initialise the WM8753 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int wm8753_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int reg, ret = 0;
+
+ codec->name = "WM8753";
+ codec->owner = THIS_MODULE;
+ codec->read = wm8753_read_reg_cache;
+ codec->write = wm8753_write;
+ codec->set_bias_level = wm8753_set_bias_level;
+ codec->dai = wm8753_dai;
+ codec->num_dai = 2;
+ codec->reg_cache_size = ARRAY_SIZE(wm8753_reg);
+ codec->reg_cache = kmemdup(wm8753_reg, sizeof(wm8753_reg), GFP_KERNEL);
+
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ wm8753_set_dai_mode(codec, 0);
+
+ wm8753_reset(codec);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8753: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* charge output caps */
+ wm8753_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
+ codec->bias_level = SND_SOC_BIAS_STANDBY;
+ schedule_delayed_work(&codec->delayed_work,
+ msecs_to_jiffies(caps_charge));
+
+ /* set the update bits */
+ reg = wm8753_read_reg_cache(codec, WM8753_LDAC);
+ wm8753_write(codec, WM8753_LDAC, reg | 0x0100);
+ reg = wm8753_read_reg_cache(codec, WM8753_RDAC);
+ wm8753_write(codec, WM8753_RDAC, reg | 0x0100);
+ reg = wm8753_read_reg_cache(codec, WM8753_LADC);
+ wm8753_write(codec, WM8753_LADC, reg | 0x0100);
+ reg = wm8753_read_reg_cache(codec, WM8753_RADC);
+ wm8753_write(codec, WM8753_RADC, reg | 0x0100);
+ reg = wm8753_read_reg_cache(codec, WM8753_LOUT1V);
+ wm8753_write(codec, WM8753_LOUT1V, reg | 0x0100);
+ reg = wm8753_read_reg_cache(codec, WM8753_ROUT1V);
+ wm8753_write(codec, WM8753_ROUT1V, reg | 0x0100);
+ reg = wm8753_read_reg_cache(codec, WM8753_LOUT2V);
+ wm8753_write(codec, WM8753_LOUT2V, reg | 0x0100);
+ reg = wm8753_read_reg_cache(codec, WM8753_ROUT2V);
+ wm8753_write(codec, WM8753_ROUT2V, reg | 0x0100);
+ reg = wm8753_read_reg_cache(codec, WM8753_LINVOL);
+ wm8753_write(codec, WM8753_LINVOL, reg | 0x0100);
+ reg = wm8753_read_reg_cache(codec, WM8753_RINVOL);
+ wm8753_write(codec, WM8753_RINVOL, reg | 0x0100);
+
+ wm8753_add_controls(codec);
+ wm8753_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8753: failed to register card\n");
+ goto card_err;
+ }
+
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+ return ret;
+}
+
+/* If the i2c layer weren't so broken, we could pass this kind of data
+ around */
+static struct snd_soc_device *wm8753_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+
+/*
+ * WM8753 2 wire address is determined by GPIO5
+ * state during powerup.
+ * low = 0x1a
+ * high = 0x1b
+ */
+
+static int wm8753_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = wm8753_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = wm8753_init(socdev);
+ if (ret < 0)
+ pr_err("failed to initialise WM8753\n");
+
+ return ret;
+}
+
+static int wm8753_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+static const struct i2c_device_id wm8753_i2c_id[] = {
+ { "wm8753", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8753_i2c_id);
+
+static struct i2c_driver wm8753_i2c_driver = {
+ .driver = {
+ .name = "WM8753 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8753_i2c_probe,
+ .remove = wm8753_i2c_remove,
+ .id_table = wm8753_i2c_id,
+};
+
+static int wm8753_add_i2c_device(struct platform_device *pdev,
+ const struct wm8753_setup_data *setup)
+{
+ struct i2c_board_info info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+ int ret;
+
+ ret = i2c_add_driver(&wm8753_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ return ret;
+ }
+
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ info.addr = setup->i2c_address;
+ strlcpy(info.type, "wm8753", I2C_NAME_SIZE);
+
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "can't get i2c adapter %d\n",
+ setup->i2c_bus);
+ goto err_driver;
+ }
+
+ client = i2c_new_device(adapter, &info);
+ i2c_put_adapter(adapter);
+ if (!client) {
+ dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
+ (unsigned int)info.addr);
+ goto err_driver;
+ }
+
+ return 0;
+
+err_driver:
+ i2c_del_driver(&wm8753_i2c_driver);
+ return -ENODEV;
+}
+#endif
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8753_spi_probe(struct spi_device *spi)
+{
+ struct snd_soc_device *socdev = wm8753_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ codec->control_data = spi;
+
+ ret = wm8753_init(socdev);
+ if (ret < 0)
+ dev_err(&spi->dev, "failed to initialise WM8753\n");
+
+ return ret;
+}
+
+static int __devexit wm8753_spi_remove(struct spi_device *spi)
+{
+ return 0;
+}
+
+static struct spi_driver wm8753_spi_driver = {
+ .driver = {
+ .name = "wm8753",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8753_spi_probe,
+ .remove = __devexit_p(wm8753_spi_remove),
+};
+
+static int wm8753_spi_write(struct spi_device *spi, const char *data, int len)
+{
+ struct spi_transfer t;
+ struct spi_message m;
+ u8 msg[2];
+
+ if (len <= 0)
+ return 0;
+
+ msg[0] = data[0];
+ msg[1] = data[1];
+
+ spi_message_init(&m);
+ memset(&t, 0, (sizeof t));
+
+ t.tx_buf = &msg[0];
+ t.len = len;
+
+ spi_message_add_tail(&t, &m);
+ spi_sync(spi, &m);
+
+ return len;
+}
+#endif
+
+
+static int wm8753_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct wm8753_setup_data *setup;
+ struct snd_soc_codec *codec;
+ struct wm8753_priv *wm8753;
+ int ret = 0;
+
+ pr_info("WM8753 Audio Codec %s", WM8753_VERSION);
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ wm8753 = kzalloc(sizeof(struct wm8753_priv), GFP_KERNEL);
+ if (wm8753 == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->private_data = wm8753;
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+ wm8753_socdev = socdev;
+ INIT_DELAYED_WORK(&codec->delayed_work, wm8753_work);
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = wm8753_add_i2c_device(pdev, setup);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ if (setup->spi) {
+ codec->hw_write = (hw_write_t)wm8753_spi_write;
+ ret = spi_register_driver(&wm8753_spi_driver);
+ if (ret != 0)
+ printk(KERN_ERR "can't add spi driver");
+ }
+#endif
+
+ if (ret != 0) {
+ kfree(codec->private_data);
+ kfree(codec);
+ }
+ return ret;
+}
+
+/*
+ * This function forces any delayed work to be queued and run.
+ */
+static int run_delayed_work(struct delayed_work *dwork)
+{
+ int ret;
+
+ /* cancel any work waiting to be queued. */
+ ret = cancel_delayed_work(dwork);
+
+ /* if there was any work waiting then we run it now and
+ * wait for it's completion */
+ if (ret) {
+ schedule_delayed_work(dwork, 0);
+ flush_scheduled_work();
+ }
+ return ret;
+}
+
+/* power down chip */
+static int wm8753_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec->control_data)
+ wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ run_delayed_work(&codec->delayed_work);
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_unregister_device(codec->control_data);
+ i2c_del_driver(&wm8753_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8753_spi_driver);
+#endif
+ kfree(codec->private_data);
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8753 = {
+ .probe = wm8753_probe,
+ .remove = wm8753_remove,
+ .suspend = wm8753_suspend,
+ .resume = wm8753_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8753);
+
+MODULE_DESCRIPTION("ASoC WM8753 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8753.h b/sound/soc/codecs/wm8753.h
new file mode 100644
index 0000000..f55704c
--- /dev/null
+++ b/sound/soc/codecs/wm8753.h
@@ -0,0 +1,127 @@
+/*
+ * wm8753.h -- audio driver for WM8753
+ *
+ * Copyright 2003 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#ifndef _WM8753_H
+#define _WM8753_H
+
+/* WM8753 register space */
+
+#define WM8753_DAC 0x01
+#define WM8753_ADC 0x02
+#define WM8753_PCM 0x03
+#define WM8753_HIFI 0x04
+#define WM8753_IOCTL 0x05
+#define WM8753_SRATE1 0x06
+#define WM8753_SRATE2 0x07
+#define WM8753_LDAC 0x08
+#define WM8753_RDAC 0x09
+#define WM8753_BASS 0x0a
+#define WM8753_TREBLE 0x0b
+#define WM8753_ALC1 0x0c
+#define WM8753_ALC2 0x0d
+#define WM8753_ALC3 0x0e
+#define WM8753_NGATE 0x0f
+#define WM8753_LADC 0x10
+#define WM8753_RADC 0x11
+#define WM8753_ADCTL1 0x12
+#define WM8753_3D 0x13
+#define WM8753_PWR1 0x14
+#define WM8753_PWR2 0x15
+#define WM8753_PWR3 0x16
+#define WM8753_PWR4 0x17
+#define WM8753_ID 0x18
+#define WM8753_INTPOL 0x19
+#define WM8753_INTEN 0x1a
+#define WM8753_GPIO1 0x1b
+#define WM8753_GPIO2 0x1c
+#define WM8753_RESET 0x1f
+#define WM8753_RECMIX1 0x20
+#define WM8753_RECMIX2 0x21
+#define WM8753_LOUTM1 0x22
+#define WM8753_LOUTM2 0x23
+#define WM8753_ROUTM1 0x24
+#define WM8753_ROUTM2 0x25
+#define WM8753_MOUTM1 0x26
+#define WM8753_MOUTM2 0x27
+#define WM8753_LOUT1V 0x28
+#define WM8753_ROUT1V 0x29
+#define WM8753_LOUT2V 0x2a
+#define WM8753_ROUT2V 0x2b
+#define WM8753_MOUTV 0x2c
+#define WM8753_OUTCTL 0x2d
+#define WM8753_ADCIN 0x2e
+#define WM8753_INCTL1 0x2f
+#define WM8753_INCTL2 0x30
+#define WM8753_LINVOL 0x31
+#define WM8753_RINVOL 0x32
+#define WM8753_MICBIAS 0x33
+#define WM8753_CLOCK 0x34
+#define WM8753_PLL1CTL1 0x35
+#define WM8753_PLL1CTL2 0x36
+#define WM8753_PLL1CTL3 0x37
+#define WM8753_PLL1CTL4 0x38
+#define WM8753_PLL2CTL1 0x39
+#define WM8753_PLL2CTL2 0x3a
+#define WM8753_PLL2CTL3 0x3b
+#define WM8753_PLL2CTL4 0x3c
+#define WM8753_BIASCTL 0x3d
+#define WM8753_ADCTL2 0x3f
+
+struct wm8753_setup_data {
+ int spi;
+ int i2c_bus;
+ unsigned short i2c_address;
+};
+
+#define WM8753_PLL1 0
+#define WM8753_PLL2 1
+
+/* clock inputs */
+#define WM8753_MCLK 0
+#define WM8753_PCMCLK 1
+
+/* clock divider id's */
+#define WM8753_PCMDIV 0
+#define WM8753_BCLKDIV 1
+#define WM8753_VXCLKDIV 2
+
+/* PCM clock dividers */
+#define WM8753_PCM_DIV_1 (0 << 6)
+#define WM8753_PCM_DIV_3 (2 << 6)
+#define WM8753_PCM_DIV_5_5 (3 << 6)
+#define WM8753_PCM_DIV_2 (4 << 6)
+#define WM8753_PCM_DIV_4 (5 << 6)
+#define WM8753_PCM_DIV_6 (6 << 6)
+#define WM8753_PCM_DIV_8 (7 << 6)
+
+/* BCLK clock dividers */
+#define WM8753_BCLK_DIV_1 (0 << 3)
+#define WM8753_BCLK_DIV_2 (1 << 3)
+#define WM8753_BCLK_DIV_4 (2 << 3)
+#define WM8753_BCLK_DIV_8 (3 << 3)
+#define WM8753_BCLK_DIV_16 (4 << 3)
+
+/* VXCLK clock dividers */
+#define WM8753_VXCLK_DIV_1 (0 << 6)
+#define WM8753_VXCLK_DIV_2 (1 << 6)
+#define WM8753_VXCLK_DIV_4 (2 << 6)
+#define WM8753_VXCLK_DIV_8 (3 << 6)
+#define WM8753_VXCLK_DIV_16 (4 << 6)
+
+#define WM8753_DAI_HIFI 0
+#define WM8753_DAI_VOICE 1
+
+extern struct snd_soc_dai wm8753_dai[2];
+extern struct snd_soc_codec_device soc_codec_dev_wm8753;
+
+#endif
diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c
new file mode 100644
index 0000000..3b326c9
--- /dev/null
+++ b/sound/soc/codecs/wm8900.c
@@ -0,0 +1,1541 @@
+/*
+ * wm8900.c -- WM8900 ALSA Soc Audio driver
+ *
+ * Copyright 2007, 2008 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * TODO:
+ * - Tristating.
+ * - TDM.
+ * - Jack detect.
+ * - FLL source configuration, currently only MCLK is supported.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.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 "wm8900.h"
+
+/* WM8900 register space */
+#define WM8900_REG_RESET 0x0
+#define WM8900_REG_ID 0x0
+#define WM8900_REG_POWER1 0x1
+#define WM8900_REG_POWER2 0x2
+#define WM8900_REG_POWER3 0x3
+#define WM8900_REG_AUDIO1 0x4
+#define WM8900_REG_AUDIO2 0x5
+#define WM8900_REG_CLOCKING1 0x6
+#define WM8900_REG_CLOCKING2 0x7
+#define WM8900_REG_AUDIO3 0x8
+#define WM8900_REG_AUDIO4 0x9
+#define WM8900_REG_DACCTRL 0xa
+#define WM8900_REG_LDAC_DV 0xb
+#define WM8900_REG_RDAC_DV 0xc
+#define WM8900_REG_SIDETONE 0xd
+#define WM8900_REG_ADCCTRL 0xe
+#define WM8900_REG_LADC_DV 0xf
+#define WM8900_REG_RADC_DV 0x10
+#define WM8900_REG_GPIO 0x12
+#define WM8900_REG_INCTL 0x15
+#define WM8900_REG_LINVOL 0x16
+#define WM8900_REG_RINVOL 0x17
+#define WM8900_REG_INBOOSTMIX1 0x18
+#define WM8900_REG_INBOOSTMIX2 0x19
+#define WM8900_REG_ADCPATH 0x1a
+#define WM8900_REG_AUXBOOST 0x1b
+#define WM8900_REG_ADDCTL 0x1e
+#define WM8900_REG_FLLCTL1 0x24
+#define WM8900_REG_FLLCTL2 0x25
+#define WM8900_REG_FLLCTL3 0x26
+#define WM8900_REG_FLLCTL4 0x27
+#define WM8900_REG_FLLCTL5 0x28
+#define WM8900_REG_FLLCTL6 0x29
+#define WM8900_REG_LOUTMIXCTL1 0x2c
+#define WM8900_REG_ROUTMIXCTL1 0x2d
+#define WM8900_REG_BYPASS1 0x2e
+#define WM8900_REG_BYPASS2 0x2f
+#define WM8900_REG_AUXOUT_CTL 0x30
+#define WM8900_REG_LOUT1CTL 0x33
+#define WM8900_REG_ROUT1CTL 0x34
+#define WM8900_REG_LOUT2CTL 0x35
+#define WM8900_REG_ROUT2CTL 0x36
+#define WM8900_REG_HPCTL1 0x3a
+#define WM8900_REG_OUTBIASCTL 0x73
+
+#define WM8900_MAXREG 0x80
+
+#define WM8900_REG_ADDCTL_OUT1_DIS 0x80
+#define WM8900_REG_ADDCTL_OUT2_DIS 0x40
+#define WM8900_REG_ADDCTL_VMID_DIS 0x20
+#define WM8900_REG_ADDCTL_BIAS_SRC 0x10
+#define WM8900_REG_ADDCTL_VMID_SOFTST 0x04
+#define WM8900_REG_ADDCTL_TEMP_SD 0x02
+
+#define WM8900_REG_GPIO_TEMP_ENA 0x2
+
+#define WM8900_REG_POWER1_STARTUP_BIAS_ENA 0x0100
+#define WM8900_REG_POWER1_BIAS_ENA 0x0008
+#define WM8900_REG_POWER1_VMID_BUF_ENA 0x0004
+#define WM8900_REG_POWER1_FLL_ENA 0x0040
+
+#define WM8900_REG_POWER2_SYSCLK_ENA 0x8000
+#define WM8900_REG_POWER2_ADCL_ENA 0x0002
+#define WM8900_REG_POWER2_ADCR_ENA 0x0001
+
+#define WM8900_REG_POWER3_DACL_ENA 0x0002
+#define WM8900_REG_POWER3_DACR_ENA 0x0001
+
+#define WM8900_REG_AUDIO1_AIF_FMT_MASK 0x0018
+#define WM8900_REG_AUDIO1_LRCLK_INV 0x0080
+#define WM8900_REG_AUDIO1_BCLK_INV 0x0100
+
+#define WM8900_REG_CLOCKING1_BCLK_DIR 0x1
+#define WM8900_REG_CLOCKING1_MCLK_SRC 0x100
+#define WM8900_REG_CLOCKING1_BCLK_MASK (~0x01e)
+#define WM8900_REG_CLOCKING1_OPCLK_MASK (~0x7000)
+
+#define WM8900_REG_CLOCKING2_ADC_CLKDIV 0xe0
+#define WM8900_REG_CLOCKING2_DAC_CLKDIV 0x1c
+
+#define WM8900_REG_DACCTRL_MUTE 0x004
+#define WM8900_REG_DACCTRL_AIF_LRCLKRATE 0x400
+
+#define WM8900_REG_AUDIO3_ADCLRC_DIR 0x0800
+
+#define WM8900_REG_AUDIO4_DACLRC_DIR 0x0800
+
+#define WM8900_REG_FLLCTL1_OSC_ENA 0x100
+
+#define WM8900_REG_FLLCTL6_FLL_SLOW_LOCK_REF 0x100
+
+#define WM8900_REG_HPCTL1_HP_IPSTAGE_ENA 0x80
+#define WM8900_REG_HPCTL1_HP_OPSTAGE_ENA 0x40
+#define WM8900_REG_HPCTL1_HP_CLAMP_IP 0x20
+#define WM8900_REG_HPCTL1_HP_CLAMP_OP 0x10
+#define WM8900_REG_HPCTL1_HP_SHORT 0x08
+#define WM8900_REG_HPCTL1_HP_SHORT2 0x04
+
+#define WM8900_LRC_MASK 0xfc00
+
+struct snd_soc_codec_device soc_codec_dev_wm8900;
+
+struct wm8900_priv {
+ u32 fll_in; /* FLL input frequency */
+ u32 fll_out; /* FLL output frequency */
+};
+
+/*
+ * wm8900 register cache. We can't read the entire register space and we
+ * have slow control buses so we cache the registers.
+ */
+static const u16 wm8900_reg_defaults[WM8900_MAXREG] = {
+ 0x8900, 0x0000,
+ 0xc000, 0x0000,
+ 0x4050, 0x4000,
+ 0x0008, 0x0000,
+ 0x0040, 0x0040,
+ 0x1004, 0x00c0,
+ 0x00c0, 0x0000,
+ 0x0100, 0x00c0,
+ 0x00c0, 0x0000,
+ 0xb001, 0x0000,
+ 0x0000, 0x0044,
+ 0x004c, 0x004c,
+ 0x0044, 0x0044,
+ 0x0000, 0x0044,
+ 0x0000, 0x0000,
+ 0x0002, 0x0000,
+ 0x0000, 0x0000,
+ 0x0000, 0x0000,
+ 0x0008, 0x0000,
+ 0x0000, 0x0008,
+ 0x0097, 0x0100,
+ 0x0000, 0x0000,
+ 0x0050, 0x0050,
+ 0x0055, 0x0055,
+ 0x0055, 0x0000,
+ 0x0000, 0x0079,
+ 0x0079, 0x0079,
+ 0x0079, 0x0000,
+ /* Remaining registers all zero */
+};
+
+/*
+ * read wm8900 register cache
+ */
+static inline unsigned int wm8900_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+
+ BUG_ON(reg >= WM8900_MAXREG);
+
+ if (reg == WM8900_REG_ID)
+ return 0;
+
+ return cache[reg];
+}
+
+/*
+ * write wm8900 register cache
+ */
+static inline void wm8900_write_reg_cache(struct snd_soc_codec *codec,
+ u16 reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+
+ BUG_ON(reg >= WM8900_MAXREG);
+
+ cache[reg] = value;
+}
+
+/*
+ * write to the WM8900 register space
+ */
+static int wm8900_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[3];
+
+ if (value == wm8900_read_reg_cache(codec, reg))
+ return 0;
+
+ /* data is
+ * D15..D9 WM8900 register offset
+ * D8...D0 register data
+ */
+ data[0] = reg;
+ data[1] = value >> 8;
+ data[2] = value & 0x00ff;
+
+ wm8900_write_reg_cache(codec, reg, value);
+ if (codec->hw_write(codec->control_data, data, 3) == 3)
+ return 0;
+ else
+ return -EIO;
+}
+
+/*
+ * Read from the wm8900.
+ */
+static unsigned int wm8900_chip_read(struct snd_soc_codec *codec, u8 reg)
+{
+ struct i2c_msg xfer[2];
+ u16 data;
+ int ret;
+ struct i2c_client *client = codec->control_data;
+
+ BUG_ON(reg != WM8900_REG_ID && reg != WM8900_REG_POWER1);
+
+ /* Write register */
+ xfer[0].addr = client->addr;
+ xfer[0].flags = 0;
+ xfer[0].len = 1;
+ xfer[0].buf = &reg;
+
+ /* Read data */
+ xfer[1].addr = client->addr;
+ xfer[1].flags = I2C_M_RD;
+ xfer[1].len = 2;
+ xfer[1].buf = (u8 *)&data;
+
+ ret = i2c_transfer(client->adapter, xfer, 2);
+ if (ret != 2) {
+ printk(KERN_CRIT "i2c_transfer returned %d\n", ret);
+ return 0;
+ }
+
+ return (data >> 8) | ((data & 0xff) << 8);
+}
+
+/*
+ * Read from the WM8900 register space. Most registers can't be read
+ * and are therefore supplied from cache.
+ */
+static unsigned int wm8900_read(struct snd_soc_codec *codec, unsigned int reg)
+{
+ switch (reg) {
+ case WM8900_REG_ID:
+ return wm8900_chip_read(codec, reg);
+ default:
+ return wm8900_read_reg_cache(codec, reg);
+ }
+}
+
+static void wm8900_reset(struct snd_soc_codec *codec)
+{
+ wm8900_write(codec, WM8900_REG_RESET, 0);
+
+ memcpy(codec->reg_cache, wm8900_reg_defaults,
+ sizeof(codec->reg_cache));
+}
+
+static int wm8900_hp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ u16 hpctl1 = wm8900_read(codec, WM8900_REG_HPCTL1);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ /* Clamp headphone outputs */
+ hpctl1 = WM8900_REG_HPCTL1_HP_CLAMP_IP |
+ WM8900_REG_HPCTL1_HP_CLAMP_OP;
+ wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
+ break;
+
+ case SND_SOC_DAPM_POST_PMU:
+ /* Enable the input stage */
+ hpctl1 &= ~WM8900_REG_HPCTL1_HP_CLAMP_IP;
+ hpctl1 |= WM8900_REG_HPCTL1_HP_SHORT |
+ WM8900_REG_HPCTL1_HP_SHORT2 |
+ WM8900_REG_HPCTL1_HP_IPSTAGE_ENA;
+ wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
+
+ msleep(400);
+
+ /* Enable the output stage */
+ hpctl1 &= ~WM8900_REG_HPCTL1_HP_CLAMP_OP;
+ hpctl1 |= WM8900_REG_HPCTL1_HP_OPSTAGE_ENA;
+ wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
+
+ /* Remove the shorts */
+ hpctl1 &= ~WM8900_REG_HPCTL1_HP_SHORT2;
+ wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
+ hpctl1 &= ~WM8900_REG_HPCTL1_HP_SHORT;
+ wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
+ break;
+
+ case SND_SOC_DAPM_PRE_PMD:
+ /* Short the output */
+ hpctl1 |= WM8900_REG_HPCTL1_HP_SHORT;
+ wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
+
+ /* Disable the output stage */
+ hpctl1 &= ~WM8900_REG_HPCTL1_HP_OPSTAGE_ENA;
+ wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
+
+ /* Clamp the outputs and power down input */
+ hpctl1 |= WM8900_REG_HPCTL1_HP_CLAMP_IP |
+ WM8900_REG_HPCTL1_HP_CLAMP_OP;
+ hpctl1 &= ~WM8900_REG_HPCTL1_HP_IPSTAGE_ENA;
+ wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
+ break;
+
+ case SND_SOC_DAPM_POST_PMD:
+ /* Disable everything */
+ wm8900_write(codec, WM8900_REG_HPCTL1, 0);
+ break;
+
+ default:
+ BUG();
+ }
+
+ return 0;
+}
+
+static const DECLARE_TLV_DB_SCALE(out_pga_tlv, -5700, 100, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_mix_tlv, -1500, 300, 0);
+
+static const DECLARE_TLV_DB_SCALE(in_boost_tlv, -1200, 600, 0);
+
+static const DECLARE_TLV_DB_SCALE(in_pga_tlv, -1200, 100, 0);
+
+static const DECLARE_TLV_DB_SCALE(dac_boost_tlv, 0, 600, 0);
+
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -7200, 75, 1);
+
+static const DECLARE_TLV_DB_SCALE(adc_svol_tlv, -3600, 300, 0);
+
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -7200, 75, 1);
+
+static const char *mic_bias_level_txt[] = { "0.9*AVDD", "0.65*AVDD" };
+
+static const struct soc_enum mic_bias_level =
+SOC_ENUM_SINGLE(WM8900_REG_INCTL, 8, 2, mic_bias_level_txt);
+
+static const char *dac_mute_rate_txt[] = { "Fast", "Slow" };
+
+static const struct soc_enum dac_mute_rate =
+SOC_ENUM_SINGLE(WM8900_REG_DACCTRL, 7, 2, dac_mute_rate_txt);
+
+static const char *dac_deemphasis_txt[] = {
+ "Disabled", "32kHz", "44.1kHz", "48kHz"
+};
+
+static const struct soc_enum dac_deemphasis =
+SOC_ENUM_SINGLE(WM8900_REG_DACCTRL, 4, 4, dac_deemphasis_txt);
+
+static const char *adc_hpf_cut_txt[] = {
+ "Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"
+};
+
+static const struct soc_enum adc_hpf_cut =
+SOC_ENUM_SINGLE(WM8900_REG_ADCCTRL, 5, 4, adc_hpf_cut_txt);
+
+static const char *lr_txt[] = {
+ "Left", "Right"
+};
+
+static const struct soc_enum aifl_src =
+SOC_ENUM_SINGLE(WM8900_REG_AUDIO1, 15, 2, lr_txt);
+
+static const struct soc_enum aifr_src =
+SOC_ENUM_SINGLE(WM8900_REG_AUDIO1, 14, 2, lr_txt);
+
+static const struct soc_enum dacl_src =
+SOC_ENUM_SINGLE(WM8900_REG_AUDIO2, 15, 2, lr_txt);
+
+static const struct soc_enum dacr_src =
+SOC_ENUM_SINGLE(WM8900_REG_AUDIO2, 14, 2, lr_txt);
+
+static const char *sidetone_txt[] = {
+ "Disabled", "Left ADC", "Right ADC"
+};
+
+static const struct soc_enum dacl_sidetone =
+SOC_ENUM_SINGLE(WM8900_REG_SIDETONE, 2, 3, sidetone_txt);
+
+static const struct soc_enum dacr_sidetone =
+SOC_ENUM_SINGLE(WM8900_REG_SIDETONE, 0, 3, sidetone_txt);
+
+static const struct snd_kcontrol_new wm8900_snd_controls[] = {
+SOC_ENUM("Mic Bias Level", mic_bias_level),
+
+SOC_SINGLE_TLV("Left Input PGA Volume", WM8900_REG_LINVOL, 0, 31, 0,
+ in_pga_tlv),
+SOC_SINGLE("Left Input PGA Switch", WM8900_REG_LINVOL, 6, 1, 1),
+SOC_SINGLE("Left Input PGA ZC Switch", WM8900_REG_LINVOL, 7, 1, 0),
+
+SOC_SINGLE_TLV("Right Input PGA Volume", WM8900_REG_RINVOL, 0, 31, 0,
+ in_pga_tlv),
+SOC_SINGLE("Right Input PGA Switch", WM8900_REG_RINVOL, 6, 1, 1),
+SOC_SINGLE("Right Input PGA ZC Switch", WM8900_REG_RINVOL, 7, 1, 0),
+
+SOC_SINGLE("DAC Soft Mute Switch", WM8900_REG_DACCTRL, 6, 1, 1),
+SOC_ENUM("DAC Mute Rate", dac_mute_rate),
+SOC_SINGLE("DAC Mono Switch", WM8900_REG_DACCTRL, 9, 1, 0),
+SOC_ENUM("DAC Deemphasis", dac_deemphasis),
+SOC_SINGLE("DAC Sloping Stopband Filter Switch", WM8900_REG_DACCTRL, 8, 1, 0),
+SOC_SINGLE("DAC Sigma-Delta Modulator Clock Switch", WM8900_REG_DACCTRL,
+ 12, 1, 0),
+
+SOC_SINGLE("ADC HPF Switch", WM8900_REG_ADCCTRL, 8, 1, 0),
+SOC_ENUM("ADC HPF Cut-Off", adc_hpf_cut),
+SOC_DOUBLE("ADC Invert Switch", WM8900_REG_ADCCTRL, 1, 0, 1, 0),
+SOC_SINGLE_TLV("Left ADC Sidetone Volume", WM8900_REG_SIDETONE, 9, 12, 0,
+ adc_svol_tlv),
+SOC_SINGLE_TLV("Right ADC Sidetone Volume", WM8900_REG_SIDETONE, 5, 12, 0,
+ adc_svol_tlv),
+SOC_ENUM("Left Digital Audio Source", aifl_src),
+SOC_ENUM("Right Digital Audio Source", aifr_src),
+
+SOC_SINGLE_TLV("DAC Input Boost Volume", WM8900_REG_AUDIO2, 10, 4, 0,
+ dac_boost_tlv),
+SOC_ENUM("Left DAC Source", dacl_src),
+SOC_ENUM("Right DAC Source", dacr_src),
+SOC_ENUM("Left DAC Sidetone", dacl_sidetone),
+SOC_ENUM("Right DAC Sidetone", dacr_sidetone),
+SOC_DOUBLE("DAC Invert Switch", WM8900_REG_DACCTRL, 1, 0, 1, 0),
+
+SOC_DOUBLE_R_TLV("Digital Playback Volume",
+ WM8900_REG_LDAC_DV, WM8900_REG_RDAC_DV,
+ 1, 96, 0, dac_tlv),
+SOC_DOUBLE_R_TLV("Digital Capture Volume",
+ WM8900_REG_LADC_DV, WM8900_REG_RADC_DV, 1, 119, 0, adc_tlv),
+
+SOC_SINGLE_TLV("LINPUT3 Bypass Volume", WM8900_REG_LOUTMIXCTL1, 4, 7, 0,
+ out_mix_tlv),
+SOC_SINGLE_TLV("RINPUT3 Bypass Volume", WM8900_REG_ROUTMIXCTL1, 4, 7, 0,
+ out_mix_tlv),
+SOC_SINGLE_TLV("Left AUX Bypass Volume", WM8900_REG_AUXOUT_CTL, 4, 7, 0,
+ out_mix_tlv),
+SOC_SINGLE_TLV("Right AUX Bypass Volume", WM8900_REG_AUXOUT_CTL, 0, 7, 0,
+ out_mix_tlv),
+
+SOC_SINGLE_TLV("LeftIn to RightOut Mixer Volume", WM8900_REG_BYPASS1, 0, 7, 0,
+ out_mix_tlv),
+SOC_SINGLE_TLV("LeftIn to LeftOut Mixer Volume", WM8900_REG_BYPASS1, 4, 7, 0,
+ out_mix_tlv),
+SOC_SINGLE_TLV("RightIn to LeftOut Mixer Volume", WM8900_REG_BYPASS2, 0, 7, 0,
+ out_mix_tlv),
+SOC_SINGLE_TLV("RightIn to RightOut Mixer Volume", WM8900_REG_BYPASS2, 4, 7, 0,
+ out_mix_tlv),
+
+SOC_SINGLE_TLV("IN2L Boost Volume", WM8900_REG_INBOOSTMIX1, 0, 3, 0,
+ in_boost_tlv),
+SOC_SINGLE_TLV("IN3L Boost Volume", WM8900_REG_INBOOSTMIX1, 4, 3, 0,
+ in_boost_tlv),
+SOC_SINGLE_TLV("IN2R Boost Volume", WM8900_REG_INBOOSTMIX2, 0, 3, 0,
+ in_boost_tlv),
+SOC_SINGLE_TLV("IN3R Boost Volume", WM8900_REG_INBOOSTMIX2, 4, 3, 0,
+ in_boost_tlv),
+SOC_SINGLE_TLV("Left AUX Boost Volume", WM8900_REG_AUXBOOST, 4, 3, 0,
+ in_boost_tlv),
+SOC_SINGLE_TLV("Right AUX Boost Volume", WM8900_REG_AUXBOOST, 0, 3, 0,
+ in_boost_tlv),
+
+SOC_DOUBLE_R_TLV("LINEOUT1 Volume", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL,
+ 0, 63, 0, out_pga_tlv),
+SOC_DOUBLE_R("LINEOUT1 Switch", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL,
+ 6, 1, 1),
+SOC_DOUBLE_R("LINEOUT1 ZC Switch", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL,
+ 7, 1, 0),
+
+SOC_DOUBLE_R_TLV("LINEOUT2 Volume",
+ WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL,
+ 0, 63, 0, out_pga_tlv),
+SOC_DOUBLE_R("LINEOUT2 Switch",
+ WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL, 6, 1, 1),
+SOC_DOUBLE_R("LINEOUT2 ZC Switch",
+ WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL, 7, 1, 0),
+SOC_SINGLE("LINEOUT2 LP -12dB", WM8900_REG_LOUTMIXCTL1,
+ 0, 1, 1),
+
+};
+
+/* add non dapm controls */
+static int wm8900_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8900_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8900_snd_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new wm8900_dapm_loutput2_control =
+SOC_DAPM_SINGLE("LINEOUT2L Switch", WM8900_REG_POWER3, 6, 1, 0);
+
+static const struct snd_kcontrol_new wm8900_dapm_routput2_control =
+SOC_DAPM_SINGLE("LINEOUT2R Switch", WM8900_REG_POWER3, 5, 1, 0);
+
+static const struct snd_kcontrol_new wm8900_loutmix_controls[] = {
+SOC_DAPM_SINGLE("LINPUT3 Bypass Switch", WM8900_REG_LOUTMIXCTL1, 7, 1, 0),
+SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 7, 1, 0),
+SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 3, 1, 0),
+SOC_DAPM_SINGLE("DACL Switch", WM8900_REG_LOUTMIXCTL1, 8, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8900_routmix_controls[] = {
+SOC_DAPM_SINGLE("RINPUT3 Bypass Switch", WM8900_REG_ROUTMIXCTL1, 7, 1, 0),
+SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 3, 1, 0),
+SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 3, 1, 0),
+SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 7, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8900_REG_ROUTMIXCTL1, 8, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8900_linmix_controls[] = {
+SOC_DAPM_SINGLE("LINPUT2 Switch", WM8900_REG_INBOOSTMIX1, 2, 1, 1),
+SOC_DAPM_SINGLE("LINPUT3 Switch", WM8900_REG_INBOOSTMIX1, 6, 1, 1),
+SOC_DAPM_SINGLE("AUX Switch", WM8900_REG_AUXBOOST, 6, 1, 1),
+SOC_DAPM_SINGLE("Input PGA Switch", WM8900_REG_ADCPATH, 6, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8900_rinmix_controls[] = {
+SOC_DAPM_SINGLE("RINPUT2 Switch", WM8900_REG_INBOOSTMIX2, 2, 1, 1),
+SOC_DAPM_SINGLE("RINPUT3 Switch", WM8900_REG_INBOOSTMIX2, 6, 1, 1),
+SOC_DAPM_SINGLE("AUX Switch", WM8900_REG_AUXBOOST, 2, 1, 1),
+SOC_DAPM_SINGLE("Input PGA Switch", WM8900_REG_ADCPATH, 2, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8900_linpga_controls[] = {
+SOC_DAPM_SINGLE("LINPUT1 Switch", WM8900_REG_INCTL, 6, 1, 0),
+SOC_DAPM_SINGLE("LINPUT2 Switch", WM8900_REG_INCTL, 5, 1, 0),
+SOC_DAPM_SINGLE("LINPUT3 Switch", WM8900_REG_INCTL, 4, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8900_rinpga_controls[] = {
+SOC_DAPM_SINGLE("RINPUT1 Switch", WM8900_REG_INCTL, 2, 1, 0),
+SOC_DAPM_SINGLE("RINPUT2 Switch", WM8900_REG_INCTL, 1, 1, 0),
+SOC_DAPM_SINGLE("RINPUT3 Switch", WM8900_REG_INCTL, 0, 1, 0),
+};
+
+static const char *wm9700_lp_mux[] = { "Disabled", "Enabled" };
+
+static const struct soc_enum wm8900_lineout2_lp_mux =
+SOC_ENUM_SINGLE(WM8900_REG_LOUTMIXCTL1, 1, 2, wm9700_lp_mux);
+
+static const struct snd_kcontrol_new wm8900_lineout2_lp =
+SOC_DAPM_ENUM("Route", wm8900_lineout2_lp_mux);
+
+static const struct snd_soc_dapm_widget wm8900_dapm_widgets[] = {
+
+/* Externally visible pins */
+SND_SOC_DAPM_OUTPUT("LINEOUT1L"),
+SND_SOC_DAPM_OUTPUT("LINEOUT1R"),
+SND_SOC_DAPM_OUTPUT("LINEOUT2L"),
+SND_SOC_DAPM_OUTPUT("LINEOUT2R"),
+SND_SOC_DAPM_OUTPUT("HP_L"),
+SND_SOC_DAPM_OUTPUT("HP_R"),
+
+SND_SOC_DAPM_INPUT("RINPUT1"),
+SND_SOC_DAPM_INPUT("LINPUT1"),
+SND_SOC_DAPM_INPUT("RINPUT2"),
+SND_SOC_DAPM_INPUT("LINPUT2"),
+SND_SOC_DAPM_INPUT("RINPUT3"),
+SND_SOC_DAPM_INPUT("LINPUT3"),
+SND_SOC_DAPM_INPUT("AUX"),
+
+SND_SOC_DAPM_VMID("VMID"),
+
+/* Input */
+SND_SOC_DAPM_MIXER("Left Input PGA", WM8900_REG_POWER2, 3, 0,
+ wm8900_linpga_controls,
+ ARRAY_SIZE(wm8900_linpga_controls)),
+SND_SOC_DAPM_MIXER("Right Input PGA", WM8900_REG_POWER2, 2, 0,
+ wm8900_rinpga_controls,
+ ARRAY_SIZE(wm8900_rinpga_controls)),
+
+SND_SOC_DAPM_MIXER("Left Input Mixer", WM8900_REG_POWER2, 5, 0,
+ wm8900_linmix_controls,
+ ARRAY_SIZE(wm8900_linmix_controls)),
+SND_SOC_DAPM_MIXER("Right Input Mixer", WM8900_REG_POWER2, 4, 0,
+ wm8900_rinmix_controls,
+ ARRAY_SIZE(wm8900_rinmix_controls)),
+
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8900_REG_POWER1, 4, 0),
+
+SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8900_REG_POWER2, 1, 0),
+SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8900_REG_POWER2, 0, 0),
+
+/* Output */
+SND_SOC_DAPM_DAC("DACL", "Left HiFi Playback", WM8900_REG_POWER3, 1, 0),
+SND_SOC_DAPM_DAC("DACR", "Right HiFi Playback", WM8900_REG_POWER3, 0, 0),
+
+SND_SOC_DAPM_PGA_E("Headphone Amplifier", WM8900_REG_POWER3, 7, 0, NULL, 0,
+ wm8900_hp_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
+
+SND_SOC_DAPM_PGA("LINEOUT1L PGA", WM8900_REG_POWER2, 8, 0, NULL, 0),
+SND_SOC_DAPM_PGA("LINEOUT1R PGA", WM8900_REG_POWER2, 7, 0, NULL, 0),
+
+SND_SOC_DAPM_MUX("LINEOUT2 LP", SND_SOC_NOPM, 0, 0, &wm8900_lineout2_lp),
+SND_SOC_DAPM_PGA("LINEOUT2L PGA", WM8900_REG_POWER3, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("LINEOUT2R PGA", WM8900_REG_POWER3, 5, 0, NULL, 0),
+
+SND_SOC_DAPM_MIXER("Left Output Mixer", WM8900_REG_POWER3, 3, 0,
+ wm8900_loutmix_controls,
+ ARRAY_SIZE(wm8900_loutmix_controls)),
+SND_SOC_DAPM_MIXER("Right Output Mixer", WM8900_REG_POWER3, 2, 0,
+ wm8900_routmix_controls,
+ ARRAY_SIZE(wm8900_routmix_controls)),
+};
+
+/* Target, Path, Source */
+static const struct snd_soc_dapm_route audio_map[] = {
+/* Inputs */
+{"Left Input PGA", "LINPUT1 Switch", "LINPUT1"},
+{"Left Input PGA", "LINPUT2 Switch", "LINPUT2"},
+{"Left Input PGA", "LINPUT3 Switch", "LINPUT3"},
+
+{"Right Input PGA", "RINPUT1 Switch", "RINPUT1"},
+{"Right Input PGA", "RINPUT2 Switch", "RINPUT2"},
+{"Right Input PGA", "RINPUT3 Switch", "RINPUT3"},
+
+{"Left Input Mixer", "LINPUT2 Switch", "LINPUT2"},
+{"Left Input Mixer", "LINPUT3 Switch", "LINPUT3"},
+{"Left Input Mixer", "AUX Switch", "AUX"},
+{"Left Input Mixer", "Input PGA Switch", "Left Input PGA"},
+
+{"Right Input Mixer", "RINPUT2 Switch", "RINPUT2"},
+{"Right Input Mixer", "RINPUT3 Switch", "RINPUT3"},
+{"Right Input Mixer", "AUX Switch", "AUX"},
+{"Right Input Mixer", "Input PGA Switch", "Right Input PGA"},
+
+{"ADCL", NULL, "Left Input Mixer"},
+{"ADCR", NULL, "Right Input Mixer"},
+
+/* Outputs */
+{"LINEOUT1L", NULL, "LINEOUT1L PGA"},
+{"LINEOUT1L PGA", NULL, "Left Output Mixer"},
+{"LINEOUT1R", NULL, "LINEOUT1R PGA"},
+{"LINEOUT1R PGA", NULL, "Right Output Mixer"},
+
+{"LINEOUT2L PGA", NULL, "Left Output Mixer"},
+{"LINEOUT2 LP", "Disabled", "LINEOUT2L PGA"},
+{"LINEOUT2 LP", "Enabled", "Left Output Mixer"},
+{"LINEOUT2L", NULL, "LINEOUT2 LP"},
+
+{"LINEOUT2R PGA", NULL, "Right Output Mixer"},
+{"LINEOUT2 LP", "Disabled", "LINEOUT2R PGA"},
+{"LINEOUT2 LP", "Enabled", "Right Output Mixer"},
+{"LINEOUT2R", NULL, "LINEOUT2 LP"},
+
+{"Left Output Mixer", "LINPUT3 Bypass Switch", "LINPUT3"},
+{"Left Output Mixer", "AUX Bypass Switch", "AUX"},
+{"Left Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"},
+{"Left Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"},
+{"Left Output Mixer", "DACL Switch", "DACL"},
+
+{"Right Output Mixer", "RINPUT3 Bypass Switch", "RINPUT3"},
+{"Right Output Mixer", "AUX Bypass Switch", "AUX"},
+{"Right Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"},
+{"Right Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"},
+{"Right Output Mixer", "DACR Switch", "DACR"},
+
+/* Note that the headphone output stage needs to be connected
+ * externally to LINEOUT2 via DC blocking capacitors. Other
+ * configurations are not supported.
+ *
+ * Note also that left and right headphone paths are treated as a
+ * mono path.
+ */
+{"Headphone Amplifier", NULL, "LINEOUT2 LP"},
+{"Headphone Amplifier", NULL, "LINEOUT2 LP"},
+{"HP_L", NULL, "Headphone Amplifier"},
+{"HP_R", NULL, "Headphone Amplifier"},
+};
+
+static int wm8900_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm8900_dapm_widgets,
+ ARRAY_SIZE(wm8900_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+
+ return 0;
+}
+
+static int wm8900_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 reg;
+
+ reg = wm8900_read(codec, WM8900_REG_AUDIO1) & ~0x60;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ reg |= 0x20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ reg |= 0x40;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ reg |= 0x60;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8900_write(codec, WM8900_REG_AUDIO1, reg);
+
+ return 0;
+}
+
+/* FLL divisors */
+struct _fll_div {
+ u16 fll_ratio;
+ u16 fllclk_div;
+ u16 fll_slow_lock_ref;
+ u16 n;
+ u16 k;
+};
+
+/* The size in bits of the FLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_FLL_SIZE ((1 << 16) * 10)
+
+static int fll_factors(struct _fll_div *fll_div, unsigned int Fref,
+ unsigned int Fout)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod, target;
+ unsigned int div;
+
+ BUG_ON(!Fout);
+
+ /* The FLL must run at 90-100MHz which is then scaled down to
+ * the output value by FLLCLK_DIV. */
+ target = Fout;
+ div = 1;
+ while (target < 90000000) {
+ div *= 2;
+ target *= 2;
+ }
+
+ if (target > 100000000)
+ printk(KERN_WARNING "wm8900: FLL rate %d out of range, Fref=%d"
+ " Fout=%d\n", target, Fref, Fout);
+ if (div > 32) {
+ printk(KERN_ERR "wm8900: Invalid FLL division rate %u, "
+ "Fref=%d, Fout=%d, target=%d\n",
+ div, Fref, Fout, target);
+ return -EINVAL;
+ }
+
+ fll_div->fllclk_div = div >> 2;
+
+ if (Fref < 48000)
+ fll_div->fll_slow_lock_ref = 1;
+ else
+ fll_div->fll_slow_lock_ref = 0;
+
+ Ndiv = target / Fref;
+
+ if (Fref < 1000000)
+ fll_div->fll_ratio = 8;
+ else
+ fll_div->fll_ratio = 1;
+
+ fll_div->n = Ndiv / fll_div->fll_ratio;
+ Nmod = (target / fll_div->fll_ratio) % Fref;
+
+ /* Calculate fractional part - scale up so we can round. */
+ Kpart = FIXED_FLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, Fref);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ fll_div->k = K / 10;
+
+ BUG_ON(target != Fout * (fll_div->fllclk_div << 2));
+ BUG_ON(!K && target != Fref * fll_div->fll_ratio * fll_div->n);
+
+ return 0;
+}
+
+static int wm8900_set_fll(struct snd_soc_codec *codec,
+ int fll_id, unsigned int freq_in, unsigned int freq_out)
+{
+ struct wm8900_priv *wm8900 = codec->private_data;
+ struct _fll_div fll_div;
+ unsigned int reg;
+
+ if (wm8900->fll_in == freq_in && wm8900->fll_out == freq_out)
+ return 0;
+
+ /* The digital side should be disabled during any change. */
+ reg = wm8900_read(codec, WM8900_REG_POWER1);
+ wm8900_write(codec, WM8900_REG_POWER1,
+ reg & (~WM8900_REG_POWER1_FLL_ENA));
+
+ /* Disable the FLL? */
+ if (!freq_in || !freq_out) {
+ reg = wm8900_read(codec, WM8900_REG_CLOCKING1);
+ wm8900_write(codec, WM8900_REG_CLOCKING1,
+ reg & (~WM8900_REG_CLOCKING1_MCLK_SRC));
+
+ reg = wm8900_read(codec, WM8900_REG_FLLCTL1);
+ wm8900_write(codec, WM8900_REG_FLLCTL1,
+ reg & (~WM8900_REG_FLLCTL1_OSC_ENA));
+
+ wm8900->fll_in = freq_in;
+ wm8900->fll_out = freq_out;
+
+ return 0;
+ }
+
+ if (fll_factors(&fll_div, freq_in, freq_out) != 0)
+ goto reenable;
+
+ wm8900->fll_in = freq_in;
+ wm8900->fll_out = freq_out;
+
+ /* The osclilator *MUST* be enabled before we enable the
+ * digital circuit. */
+ wm8900_write(codec, WM8900_REG_FLLCTL1,
+ fll_div.fll_ratio | WM8900_REG_FLLCTL1_OSC_ENA);
+
+ wm8900_write(codec, WM8900_REG_FLLCTL4, fll_div.n >> 5);
+ wm8900_write(codec, WM8900_REG_FLLCTL5,
+ (fll_div.fllclk_div << 6) | (fll_div.n & 0x1f));
+
+ if (fll_div.k) {
+ wm8900_write(codec, WM8900_REG_FLLCTL2,
+ (fll_div.k >> 8) | 0x100);
+ wm8900_write(codec, WM8900_REG_FLLCTL3, fll_div.k & 0xff);
+ } else
+ wm8900_write(codec, WM8900_REG_FLLCTL2, 0);
+
+ if (fll_div.fll_slow_lock_ref)
+ wm8900_write(codec, WM8900_REG_FLLCTL6,
+ WM8900_REG_FLLCTL6_FLL_SLOW_LOCK_REF);
+ else
+ wm8900_write(codec, WM8900_REG_FLLCTL6, 0);
+
+ reg = wm8900_read(codec, WM8900_REG_POWER1);
+ wm8900_write(codec, WM8900_REG_POWER1,
+ reg | WM8900_REG_POWER1_FLL_ENA);
+
+reenable:
+ reg = wm8900_read(codec, WM8900_REG_CLOCKING1);
+ wm8900_write(codec, WM8900_REG_CLOCKING1,
+ reg | WM8900_REG_CLOCKING1_MCLK_SRC);
+
+ return 0;
+}
+
+static int wm8900_set_dai_pll(struct snd_soc_dai *codec_dai,
+ int pll_id, unsigned int freq_in, unsigned int freq_out)
+{
+ return wm8900_set_fll(codec_dai->codec, pll_id, freq_in, freq_out);
+}
+
+static int wm8900_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ unsigned int reg;
+
+ switch (div_id) {
+ case WM8900_BCLK_DIV:
+ reg = wm8900_read(codec, WM8900_REG_CLOCKING1);
+ wm8900_write(codec, WM8900_REG_CLOCKING1,
+ div | (reg & WM8900_REG_CLOCKING1_BCLK_MASK));
+ break;
+ case WM8900_OPCLK_DIV:
+ reg = wm8900_read(codec, WM8900_REG_CLOCKING1);
+ wm8900_write(codec, WM8900_REG_CLOCKING1,
+ div | (reg & WM8900_REG_CLOCKING1_OPCLK_MASK));
+ break;
+ case WM8900_DAC_LRCLK:
+ reg = wm8900_read(codec, WM8900_REG_AUDIO4);
+ wm8900_write(codec, WM8900_REG_AUDIO4,
+ div | (reg & WM8900_LRC_MASK));
+ break;
+ case WM8900_ADC_LRCLK:
+ reg = wm8900_read(codec, WM8900_REG_AUDIO3);
+ wm8900_write(codec, WM8900_REG_AUDIO3,
+ div | (reg & WM8900_LRC_MASK));
+ break;
+ case WM8900_DAC_CLKDIV:
+ reg = wm8900_read(codec, WM8900_REG_CLOCKING2);
+ wm8900_write(codec, WM8900_REG_CLOCKING2,
+ div | (reg & WM8900_REG_CLOCKING2_DAC_CLKDIV));
+ break;
+ case WM8900_ADC_CLKDIV:
+ reg = wm8900_read(codec, WM8900_REG_CLOCKING2);
+ wm8900_write(codec, WM8900_REG_CLOCKING2,
+ div | (reg & WM8900_REG_CLOCKING2_ADC_CLKDIV));
+ break;
+ case WM8900_LRCLK_MODE:
+ reg = wm8900_read(codec, WM8900_REG_DACCTRL);
+ wm8900_write(codec, WM8900_REG_DACCTRL,
+ div | (reg & WM8900_REG_DACCTRL_AIF_LRCLKRATE));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+static int wm8900_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ unsigned int clocking1, aif1, aif3, aif4;
+
+ clocking1 = wm8900_read(codec, WM8900_REG_CLOCKING1);
+ aif1 = wm8900_read(codec, WM8900_REG_AUDIO1);
+ aif3 = wm8900_read(codec, WM8900_REG_AUDIO3);
+ aif4 = wm8900_read(codec, WM8900_REG_AUDIO4);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ clocking1 &= ~WM8900_REG_CLOCKING1_BCLK_DIR;
+ aif3 &= ~WM8900_REG_AUDIO3_ADCLRC_DIR;
+ aif4 &= ~WM8900_REG_AUDIO4_DACLRC_DIR;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ clocking1 &= ~WM8900_REG_CLOCKING1_BCLK_DIR;
+ aif3 |= WM8900_REG_AUDIO3_ADCLRC_DIR;
+ aif4 |= WM8900_REG_AUDIO4_DACLRC_DIR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ clocking1 |= WM8900_REG_CLOCKING1_BCLK_DIR;
+ aif3 |= WM8900_REG_AUDIO3_ADCLRC_DIR;
+ aif4 |= WM8900_REG_AUDIO4_DACLRC_DIR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ clocking1 |= WM8900_REG_CLOCKING1_BCLK_DIR;
+ aif3 &= ~WM8900_REG_AUDIO3_ADCLRC_DIR;
+ aif4 &= ~WM8900_REG_AUDIO4_DACLRC_DIR;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ aif1 |= WM8900_REG_AUDIO1_AIF_FMT_MASK;
+ aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ aif1 |= WM8900_REG_AUDIO1_AIF_FMT_MASK;
+ aif1 |= WM8900_REG_AUDIO1_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK;
+ aif1 |= 0x10;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK;
+ aif1 |= 0x8;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ /* frame inversion not valid for DSP modes */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif1 |= WM8900_REG_AUDIO1_BCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV;
+ aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ aif1 |= WM8900_REG_AUDIO1_BCLK_INV;
+ aif1 |= WM8900_REG_AUDIO1_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif1 |= WM8900_REG_AUDIO1_BCLK_INV;
+ aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV;
+ aif1 |= WM8900_REG_AUDIO1_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8900_write(codec, WM8900_REG_CLOCKING1, clocking1);
+ wm8900_write(codec, WM8900_REG_AUDIO1, aif1);
+ wm8900_write(codec, WM8900_REG_AUDIO3, aif3);
+ wm8900_write(codec, WM8900_REG_AUDIO4, aif4);
+
+ return 0;
+}
+
+static int wm8900_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ reg = wm8900_read(codec, WM8900_REG_DACCTRL);
+
+ if (mute)
+ reg |= WM8900_REG_DACCTRL_MUTE;
+ else
+ reg &= ~WM8900_REG_DACCTRL_MUTE;
+
+ wm8900_write(codec, WM8900_REG_DACCTRL, reg);
+
+ return 0;
+}
+
+#define WM8900_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+#define WM8900_PCM_FORMATS \
+ (SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \
+ SNDRV_PCM_FORMAT_S24_LE)
+
+struct snd_soc_dai wm8900_dai = {
+ .name = "WM8900 HiFi",
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8900_RATES,
+ .formats = WM8900_PCM_FORMATS,
+ },
+ .capture = {
+ .stream_name = "HiFi Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8900_RATES,
+ .formats = WM8900_PCM_FORMATS,
+ },
+ .ops = {
+ .hw_params = wm8900_hw_params,
+ },
+ .dai_ops = {
+ .set_clkdiv = wm8900_set_dai_clkdiv,
+ .set_pll = wm8900_set_dai_pll,
+ .set_fmt = wm8900_set_dai_fmt,
+ .digital_mute = wm8900_digital_mute,
+ },
+};
+EXPORT_SYMBOL_GPL(wm8900_dai);
+
+static int wm8900_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* Enable thermal shutdown */
+ reg = wm8900_read(codec, WM8900_REG_GPIO);
+ wm8900_write(codec, WM8900_REG_GPIO,
+ reg | WM8900_REG_GPIO_TEMP_ENA);
+ reg = wm8900_read(codec, WM8900_REG_ADDCTL);
+ wm8900_write(codec, WM8900_REG_ADDCTL,
+ reg | WM8900_REG_ADDCTL_TEMP_SD);
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ /* Charge capacitors if initial power up */
+ if (codec->bias_level == SND_SOC_BIAS_OFF) {
+ /* STARTUP_BIAS_ENA on */
+ wm8900_write(codec, WM8900_REG_POWER1,
+ WM8900_REG_POWER1_STARTUP_BIAS_ENA);
+
+ /* Startup bias mode */
+ wm8900_write(codec, WM8900_REG_ADDCTL,
+ WM8900_REG_ADDCTL_BIAS_SRC |
+ WM8900_REG_ADDCTL_VMID_SOFTST);
+
+ /* VMID 2x50k */
+ wm8900_write(codec, WM8900_REG_POWER1,
+ WM8900_REG_POWER1_STARTUP_BIAS_ENA | 0x1);
+
+ /* Allow capacitors to charge */
+ schedule_timeout_interruptible(msecs_to_jiffies(400));
+
+ /* Enable bias */
+ wm8900_write(codec, WM8900_REG_POWER1,
+ WM8900_REG_POWER1_STARTUP_BIAS_ENA |
+ WM8900_REG_POWER1_BIAS_ENA | 0x1);
+
+ wm8900_write(codec, WM8900_REG_ADDCTL, 0);
+
+ wm8900_write(codec, WM8900_REG_POWER1,
+ WM8900_REG_POWER1_BIAS_ENA | 0x1);
+ }
+
+ reg = wm8900_read(codec, WM8900_REG_POWER1);
+ wm8900_write(codec, WM8900_REG_POWER1,
+ (reg & WM8900_REG_POWER1_FLL_ENA) |
+ WM8900_REG_POWER1_BIAS_ENA | 0x1);
+ wm8900_write(codec, WM8900_REG_POWER2,
+ WM8900_REG_POWER2_SYSCLK_ENA);
+ wm8900_write(codec, WM8900_REG_POWER3, 0);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ /* Startup bias enable */
+ reg = wm8900_read(codec, WM8900_REG_POWER1);
+ wm8900_write(codec, WM8900_REG_POWER1,
+ reg & WM8900_REG_POWER1_STARTUP_BIAS_ENA);
+ wm8900_write(codec, WM8900_REG_ADDCTL,
+ WM8900_REG_ADDCTL_BIAS_SRC |
+ WM8900_REG_ADDCTL_VMID_SOFTST);
+
+ /* Discharge caps */
+ wm8900_write(codec, WM8900_REG_POWER1,
+ WM8900_REG_POWER1_STARTUP_BIAS_ENA);
+ schedule_timeout_interruptible(msecs_to_jiffies(500));
+
+ /* Remove clamp */
+ wm8900_write(codec, WM8900_REG_HPCTL1, 0);
+
+ /* Power down */
+ wm8900_write(codec, WM8900_REG_ADDCTL, 0);
+ wm8900_write(codec, WM8900_REG_POWER1, 0);
+ wm8900_write(codec, WM8900_REG_POWER2, 0);
+ wm8900_write(codec, WM8900_REG_POWER3, 0);
+
+ /* Need to let things settle before stopping the clock
+ * to ensure that restart works, see "Stopping the
+ * master clock" in the datasheet. */
+ schedule_timeout_interruptible(msecs_to_jiffies(1));
+ wm8900_write(codec, WM8900_REG_POWER2,
+ WM8900_REG_POWER2_SYSCLK_ENA);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+static int wm8900_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ struct wm8900_priv *wm8900 = codec->private_data;
+ int fll_out = wm8900->fll_out;
+ int fll_in = wm8900->fll_in;
+ int ret;
+
+ /* Stop the FLL in an orderly fashion */
+ ret = wm8900_set_fll(codec, 0, 0, 0);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to stop FLL\n");
+ return ret;
+ }
+
+ wm8900->fll_out = fll_out;
+ wm8900->fll_in = fll_in;
+
+ wm8900_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int wm8900_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ struct wm8900_priv *wm8900 = codec->private_data;
+ u16 *cache;
+ int i, ret;
+
+ cache = kmemdup(codec->reg_cache, sizeof(wm8900_reg_defaults),
+ GFP_KERNEL);
+
+ wm8900_reset(codec);
+ wm8900_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Restart the FLL? */
+ if (wm8900->fll_out) {
+ int fll_out = wm8900->fll_out;
+ int fll_in = wm8900->fll_in;
+
+ wm8900->fll_in = 0;
+ wm8900->fll_out = 0;
+
+ ret = wm8900_set_fll(codec, 0, fll_in, fll_out);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to restart FLL\n");
+ return ret;
+ }
+ }
+
+ if (cache) {
+ for (i = 0; i < WM8900_MAXREG; i++)
+ wm8900_write(codec, i, cache[i]);
+ kfree(cache);
+ } else
+ dev_err(&pdev->dev, "Unable to allocate register cache\n");
+
+ return 0;
+}
+
+/*
+ * initialise the WM8900 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int wm8900_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret = 0;
+ unsigned int reg;
+ struct i2c_client *i2c_client = socdev->codec->control_data;
+
+ codec->name = "WM8900";
+ codec->owner = THIS_MODULE;
+ codec->read = wm8900_read;
+ codec->write = wm8900_write;
+ codec->dai = &wm8900_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = WM8900_MAXREG;
+ codec->reg_cache = kmemdup(wm8900_reg_defaults,
+ sizeof(wm8900_reg_defaults), GFP_KERNEL);
+
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ reg = wm8900_read(codec, WM8900_REG_ID);
+ if (reg != 0x8900) {
+ dev_err(&i2c_client->dev, "Device is not a WM8900 - ID %x\n",
+ reg);
+ return -ENODEV;
+ }
+
+ codec->private_data = kzalloc(sizeof(struct wm8900_priv), GFP_KERNEL);
+ if (codec->private_data == NULL) {
+ ret = -ENOMEM;
+ goto priv_err;
+ }
+
+ /* Read back from the chip */
+ reg = wm8900_chip_read(codec, WM8900_REG_POWER1);
+ reg = (reg >> 12) & 0xf;
+ dev_info(&i2c_client->dev, "WM8900 revision %d\n", reg);
+
+ wm8900_reset(codec);
+
+ /* Latch the volume update bits */
+ wm8900_write(codec, WM8900_REG_LINVOL,
+ wm8900_read(codec, WM8900_REG_LINVOL) | 0x100);
+ wm8900_write(codec, WM8900_REG_RINVOL,
+ wm8900_read(codec, WM8900_REG_RINVOL) | 0x100);
+ wm8900_write(codec, WM8900_REG_LOUT1CTL,
+ wm8900_read(codec, WM8900_REG_LOUT1CTL) | 0x100);
+ wm8900_write(codec, WM8900_REG_ROUT1CTL,
+ wm8900_read(codec, WM8900_REG_ROUT1CTL) | 0x100);
+ wm8900_write(codec, WM8900_REG_LOUT2CTL,
+ wm8900_read(codec, WM8900_REG_LOUT2CTL) | 0x100);
+ wm8900_write(codec, WM8900_REG_ROUT2CTL,
+ wm8900_read(codec, WM8900_REG_ROUT2CTL) | 0x100);
+ wm8900_write(codec, WM8900_REG_LDAC_DV,
+ wm8900_read(codec, WM8900_REG_LDAC_DV) | 0x100);
+ wm8900_write(codec, WM8900_REG_RDAC_DV,
+ wm8900_read(codec, WM8900_REG_RDAC_DV) | 0x100);
+ wm8900_write(codec, WM8900_REG_LADC_DV,
+ wm8900_read(codec, WM8900_REG_LADC_DV) | 0x100);
+ wm8900_write(codec, WM8900_REG_RADC_DV,
+ wm8900_read(codec, WM8900_REG_RADC_DV) | 0x100);
+
+ /* Set the DAC and mixer output bias */
+ wm8900_write(codec, WM8900_REG_OUTBIASCTL, 0x81);
+
+ /* Register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ dev_err(&i2c_client->dev, "Failed to register new PCMs\n");
+ goto pcm_err;
+ }
+
+ /* Turn the chip on */
+ codec->bias_level = SND_SOC_BIAS_OFF;
+ wm8900_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ wm8900_add_controls(codec);
+ wm8900_add_widgets(codec);
+
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ dev_err(&i2c_client->dev, "Failed to register card\n");
+ goto card_err;
+ }
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+priv_err:
+ kfree(codec->private_data);
+ return ret;
+}
+
+static struct snd_soc_device *wm8900_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+
+static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END };
+
+/* Magic definition of all other variables and things */
+I2C_CLIENT_INSMOD;
+
+static struct i2c_driver wm8900_i2c_driver;
+static struct i2c_client client_template;
+
+/* If the i2c layer weren't so broken, we could pass this kind of data
+ around */
+static int wm8900_codec_probe(struct i2c_adapter *adap, int addr, int kind)
+{
+ struct snd_soc_device *socdev = wm8900_socdev;
+ struct wm8900_setup_data *setup = socdev->codec_data;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct i2c_client *i2c;
+ int ret;
+
+ if (addr != setup->i2c_address)
+ return -ENODEV;
+
+ dev_err(&adap->dev, "Probe on %x\n", addr);
+
+ client_template.adapter = adap;
+ client_template.addr = addr;
+
+ i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL);
+ if (i2c == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = i2c_attach_client(i2c);
+ if (ret < 0) {
+ dev_err(&adap->dev,
+ "failed to attach codec at addr %x\n", addr);
+ goto err;
+ }
+
+ ret = wm8900_init(socdev);
+ if (ret < 0) {
+ dev_err(&adap->dev, "failed to initialise WM8900\n");
+ goto err;
+ }
+ return ret;
+
+err:
+ kfree(codec);
+ kfree(i2c);
+ return ret;
+}
+
+static int wm8900_i2c_detach(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ i2c_detach_client(client);
+ kfree(codec->reg_cache);
+ kfree(client);
+ return 0;
+}
+
+static int wm8900_i2c_attach(struct i2c_adapter *adap)
+{
+ return i2c_probe(adap, &addr_data, wm8900_codec_probe);
+}
+
+/* corgi i2c codec control layer */
+static struct i2c_driver wm8900_i2c_driver = {
+ .driver = {
+ .name = "WM8900 I2C codec",
+ .owner = THIS_MODULE,
+ },
+ .attach_adapter = wm8900_i2c_attach,
+ .detach_client = wm8900_i2c_detach,
+ .command = NULL,
+};
+
+static struct i2c_client client_template = {
+ .name = "WM8900",
+ .driver = &wm8900_i2c_driver,
+};
+#endif
+
+static int wm8900_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct wm8900_setup_data *setup;
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ dev_info(&pdev->dev, "WM8900 Audio Codec\n");
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ socdev->codec = codec;
+
+ codec->set_bias_level = wm8900_set_bias_level;
+
+ wm8900_socdev = socdev;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ normal_i2c[0] = setup->i2c_address;
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = i2c_add_driver(&wm8900_i2c_driver);
+ if (ret != 0)
+ printk(KERN_ERR "can't add i2c driver");
+ }
+#else
+#error Non-I2C interfaces not yet supported
+#endif
+ return ret;
+}
+
+/* power down chip */
+static int wm8900_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec->control_data)
+ wm8900_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8900_i2c_driver);
+#endif
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8900 = {
+ .probe = wm8900_probe,
+ .remove = wm8900_remove,
+ .suspend = wm8900_suspend,
+ .resume = wm8900_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8900);
+
+MODULE_DESCRIPTION("ASoC WM8900 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8900.h b/sound/soc/codecs/wm8900.h
new file mode 100644
index 0000000..ba450d9
--- /dev/null
+++ b/sound/soc/codecs/wm8900.h
@@ -0,0 +1,64 @@
+/*
+ * wm8900.h -- WM890 Soc Audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8900_H
+#define _WM8900_H
+
+#define WM8900_FLL 1
+
+#define WM8900_BCLK_DIV 1
+#define WM8900_ADC_CLKDIV 2
+#define WM8900_DAC_CLKDIV 3
+#define WM8900_ADC_LRCLK 4
+#define WM8900_DAC_LRCLK 5
+#define WM8900_OPCLK_DIV 6
+#define WM8900_LRCLK_MODE 7
+
+#define WM8900_BCLK_DIV_1 0x00
+#define WM8900_BCLK_DIV_1_5 0x02
+#define WM8900_BCLK_DIV_2 0x04
+#define WM8900_BCLK_DIV_3 0x06
+#define WM8900_BCLK_DIV_4 0x08
+#define WM8900_BCLK_DIV_5_5 0x0a
+#define WM8900_BCLK_DIV_6 0x0c
+#define WM8900_BCLK_DIV_8 0x0e
+#define WM8900_BCLK_DIV_11 0x10
+#define WM8900_BCLK_DIV_12 0x12
+#define WM8900_BCLK_DIV_16 0x14
+#define WM8900_BCLK_DIV_22 0x16
+#define WM8900_BCLK_DIV_24 0x18
+#define WM8900_BCLK_DIV_32 0x1a
+#define WM8900_BCLK_DIV_44 0x1c
+#define WM8900_BCLK_DIV_48 0x1e
+
+#define WM8900_ADC_CLKDIV_1 0x00
+#define WM8900_ADC_CLKDIV_1_5 0x20
+#define WM8900_ADC_CLKDIV_2 0x40
+#define WM8900_ADC_CLKDIV_3 0x60
+#define WM8900_ADC_CLKDIV_4 0x80
+#define WM8900_ADC_CLKDIV_5_5 0xa0
+#define WM8900_ADC_CLKDIV_6 0xc0
+
+#define WM8900_DAC_CLKDIV_1 0x00
+#define WM8900_DAC_CLKDIV_1_5 0x04
+#define WM8900_DAC_CLKDIV_2 0x08
+#define WM8900_DAC_CLKDIV_3 0x0c
+#define WM8900_DAC_CLKDIV_4 0x10
+#define WM8900_DAC_CLKDIV_5_5 0x14
+#define WM8900_DAC_CLKDIV_6 0x18
+
+#define WM8900_
+
+struct wm8900_setup_data {
+ unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai wm8900_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8900;
+
+#endif
diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c
new file mode 100644
index 0000000..ce40d78
--- /dev/null
+++ b/sound/soc/codecs/wm8903.c
@@ -0,0 +1,1813 @@
+/*
+ * wm8903.c -- WM8903 ALSA SoC Audio driver
+ *
+ * Copyright 2008 Wolfson Microelectronics
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * TODO:
+ * - TDM mode configuration.
+ * - Mic detect.
+ * - Digital microphone support.
+ * - Interrupt support (mic detect and sequencer).
+ */
+
+#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 <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "wm8903.h"
+
+struct wm8903_priv {
+ int sysclk;
+
+ /* Reference counts */
+ int charge_pump_users;
+ int class_w_users;
+ int playback_active;
+ int capture_active;
+
+ struct snd_pcm_substream *master_substream;
+ struct snd_pcm_substream *slave_substream;
+};
+
+/* Register defaults at reset */
+static u16 wm8903_reg_defaults[] = {
+ 0x8903, /* R0 - SW Reset and ID */
+ 0x0000, /* R1 - Revision Number */
+ 0x0000, /* R2 */
+ 0x0000, /* R3 */
+ 0x0018, /* R4 - Bias Control 0 */
+ 0x0000, /* R5 - VMID Control 0 */
+ 0x0000, /* R6 - Mic Bias Control 0 */
+ 0x0000, /* R7 */
+ 0x0001, /* R8 - Analogue DAC 0 */
+ 0x0000, /* R9 */
+ 0x0001, /* R10 - Analogue ADC 0 */
+ 0x0000, /* R11 */
+ 0x0000, /* R12 - Power Management 0 */
+ 0x0000, /* R13 - Power Management 1 */
+ 0x0000, /* R14 - Power Management 2 */
+ 0x0000, /* R15 - Power Management 3 */
+ 0x0000, /* R16 - Power Management 4 */
+ 0x0000, /* R17 - Power Management 5 */
+ 0x0000, /* R18 - Power Management 6 */
+ 0x0000, /* R19 */
+ 0x0400, /* R20 - Clock Rates 0 */
+ 0x0D07, /* R21 - Clock Rates 1 */
+ 0x0000, /* R22 - Clock Rates 2 */
+ 0x0000, /* R23 */
+ 0x0050, /* R24 - Audio Interface 0 */
+ 0x0242, /* R25 - Audio Interface 1 */
+ 0x0008, /* R26 - Audio Interface 2 */
+ 0x0022, /* R27 - Audio Interface 3 */
+ 0x0000, /* R28 */
+ 0x0000, /* R29 */
+ 0x00C0, /* R30 - DAC Digital Volume Left */
+ 0x00C0, /* R31 - DAC Digital Volume Right */
+ 0x0000, /* R32 - DAC Digital 0 */
+ 0x0000, /* R33 - DAC Digital 1 */
+ 0x0000, /* R34 */
+ 0x0000, /* R35 */
+ 0x00C0, /* R36 - ADC Digital Volume Left */
+ 0x00C0, /* R37 - ADC Digital Volume Right */
+ 0x0000, /* R38 - ADC Digital 0 */
+ 0x0073, /* R39 - Digital Microphone 0 */
+ 0x09BF, /* R40 - DRC 0 */
+ 0x3241, /* R41 - DRC 1 */
+ 0x0020, /* R42 - DRC 2 */
+ 0x0000, /* R43 - DRC 3 */
+ 0x0085, /* R44 - Analogue Left Input 0 */
+ 0x0085, /* R45 - Analogue Right Input 0 */
+ 0x0044, /* R46 - Analogue Left Input 1 */
+ 0x0044, /* R47 - Analogue Right Input 1 */
+ 0x0000, /* R48 */
+ 0x0000, /* R49 */
+ 0x0008, /* R50 - Analogue Left Mix 0 */
+ 0x0004, /* R51 - Analogue Right Mix 0 */
+ 0x0000, /* R52 - Analogue Spk Mix Left 0 */
+ 0x0000, /* R53 - Analogue Spk Mix Left 1 */
+ 0x0000, /* R54 - Analogue Spk Mix Right 0 */
+ 0x0000, /* R55 - Analogue Spk Mix Right 1 */
+ 0x0000, /* R56 */
+ 0x002D, /* R57 - Analogue OUT1 Left */
+ 0x002D, /* R58 - Analogue OUT1 Right */
+ 0x0039, /* R59 - Analogue OUT2 Left */
+ 0x0039, /* R60 - Analogue OUT2 Right */
+ 0x0100, /* R61 */
+ 0x0139, /* R62 - Analogue OUT3 Left */
+ 0x0139, /* R63 - Analogue OUT3 Right */
+ 0x0000, /* R64 */
+ 0x0000, /* R65 - Analogue SPK Output Control 0 */
+ 0x0000, /* R66 */
+ 0x0010, /* R67 - DC Servo 0 */
+ 0x0100, /* R68 */
+ 0x00A4, /* R69 - DC Servo 2 */
+ 0x0807, /* R70 */
+ 0x0000, /* R71 */
+ 0x0000, /* R72 */
+ 0x0000, /* R73 */
+ 0x0000, /* R74 */
+ 0x0000, /* R75 */
+ 0x0000, /* R76 */
+ 0x0000, /* R77 */
+ 0x0000, /* R78 */
+ 0x000E, /* R79 */
+ 0x0000, /* R80 */
+ 0x0000, /* R81 */
+ 0x0000, /* R82 */
+ 0x0000, /* R83 */
+ 0x0000, /* R84 */
+ 0x0000, /* R85 */
+ 0x0000, /* R86 */
+ 0x0006, /* R87 */
+ 0x0000, /* R88 */
+ 0x0000, /* R89 */
+ 0x0000, /* R90 - Analogue HP 0 */
+ 0x0060, /* R91 */
+ 0x0000, /* R92 */
+ 0x0000, /* R93 */
+ 0x0000, /* R94 - Analogue Lineout 0 */
+ 0x0060, /* R95 */
+ 0x0000, /* R96 */
+ 0x0000, /* R97 */
+ 0x0000, /* R98 - Charge Pump 0 */
+ 0x1F25, /* R99 */
+ 0x2B19, /* R100 */
+ 0x01C0, /* R101 */
+ 0x01EF, /* R102 */
+ 0x2B00, /* R103 */
+ 0x0000, /* R104 - Class W 0 */
+ 0x01C0, /* R105 */
+ 0x1C10, /* R106 */
+ 0x0000, /* R107 */
+ 0x0000, /* R108 - Write Sequencer 0 */
+ 0x0000, /* R109 - Write Sequencer 1 */
+ 0x0000, /* R110 - Write Sequencer 2 */
+ 0x0000, /* R111 - Write Sequencer 3 */
+ 0x0000, /* R112 - Write Sequencer 4 */
+ 0x0000, /* R113 */
+ 0x0000, /* R114 - Control Interface */
+ 0x0000, /* R115 */
+ 0x00A8, /* R116 - GPIO Control 1 */
+ 0x00A8, /* R117 - GPIO Control 2 */
+ 0x00A8, /* R118 - GPIO Control 3 */
+ 0x0220, /* R119 - GPIO Control 4 */
+ 0x01A0, /* R120 - GPIO Control 5 */
+ 0x0000, /* R121 - Interrupt Status 1 */
+ 0xFFFF, /* R122 - Interrupt Status 1 Mask */
+ 0x0000, /* R123 - Interrupt Polarity 1 */
+ 0x0000, /* R124 */
+ 0x0003, /* R125 */
+ 0x0000, /* R126 - Interrupt Control */
+ 0x0000, /* R127 */
+ 0x0005, /* R128 */
+ 0x0000, /* R129 - Control Interface Test 1 */
+ 0x0000, /* R130 */
+ 0x0000, /* R131 */
+ 0x0000, /* R132 */
+ 0x0000, /* R133 */
+ 0x0000, /* R134 */
+ 0x03FF, /* R135 */
+ 0x0007, /* R136 */
+ 0x0040, /* R137 */
+ 0x0000, /* R138 */
+ 0x0000, /* R139 */
+ 0x0000, /* R140 */
+ 0x0000, /* R141 */
+ 0x0000, /* R142 */
+ 0x0000, /* R143 */
+ 0x0000, /* R144 */
+ 0x0000, /* R145 */
+ 0x0000, /* R146 */
+ 0x0000, /* R147 */
+ 0x4000, /* R148 */
+ 0x6810, /* R149 - Charge Pump Test 1 */
+ 0x0004, /* R150 */
+ 0x0000, /* R151 */
+ 0x0000, /* R152 */
+ 0x0000, /* R153 */
+ 0x0000, /* R154 */
+ 0x0000, /* R155 */
+ 0x0000, /* R156 */
+ 0x0000, /* R157 */
+ 0x0000, /* R158 */
+ 0x0000, /* R159 */
+ 0x0000, /* R160 */
+ 0x0000, /* R161 */
+ 0x0000, /* R162 */
+ 0x0000, /* R163 */
+ 0x0028, /* R164 - Clock Rate Test 4 */
+ 0x0004, /* R165 */
+ 0x0000, /* R166 */
+ 0x0060, /* R167 */
+ 0x0000, /* R168 */
+ 0x0000, /* R169 */
+ 0x0000, /* R170 */
+ 0x0000, /* R171 */
+ 0x0000, /* R172 - Analogue Output Bias 0 */
+};
+
+static unsigned int wm8903_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+
+ BUG_ON(reg >= ARRAY_SIZE(wm8903_reg_defaults));
+
+ return cache[reg];
+}
+
+static unsigned int wm8903_hw_read(struct snd_soc_codec *codec, u8 reg)
+{
+ struct i2c_msg xfer[2];
+ u16 data;
+ int ret;
+ struct i2c_client *client = codec->control_data;
+
+ /* Write register */
+ xfer[0].addr = client->addr;
+ xfer[0].flags = 0;
+ xfer[0].len = 1;
+ xfer[0].buf = &reg;
+
+ /* Read data */
+ xfer[1].addr = client->addr;
+ xfer[1].flags = I2C_M_RD;
+ xfer[1].len = 2;
+ xfer[1].buf = (u8 *)&data;
+
+ ret = i2c_transfer(client->adapter, xfer, 2);
+ if (ret != 2) {
+ pr_err("i2c_transfer returned %d\n", ret);
+ return 0;
+ }
+
+ return (data >> 8) | ((data & 0xff) << 8);
+}
+
+static unsigned int wm8903_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ switch (reg) {
+ case WM8903_SW_RESET_AND_ID:
+ case WM8903_REVISION_NUMBER:
+ case WM8903_INTERRUPT_STATUS_1:
+ case WM8903_WRITE_SEQUENCER_4:
+ return wm8903_hw_read(codec, reg);
+
+ default:
+ return wm8903_read_reg_cache(codec, reg);
+ }
+}
+
+static void wm8903_write_reg_cache(struct snd_soc_codec *codec,
+ u16 reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+
+ BUG_ON(reg >= ARRAY_SIZE(wm8903_reg_defaults));
+
+ switch (reg) {
+ case WM8903_SW_RESET_AND_ID:
+ case WM8903_REVISION_NUMBER:
+ break;
+
+ default:
+ cache[reg] = value;
+ break;
+ }
+}
+
+static int wm8903_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[3];
+
+ wm8903_write_reg_cache(codec, reg, value);
+
+ /* Data format is 1 byte of address followed by 2 bytes of data */
+ data[0] = reg;
+ data[1] = (value >> 8) & 0xff;
+ data[2] = value & 0xff;
+
+ if (codec->hw_write(codec->control_data, data, 3) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start)
+{
+ u16 reg[5];
+ struct i2c_client *i2c = codec->control_data;
+
+ BUG_ON(start > 48);
+
+ /* Enable the sequencer */
+ reg[0] = wm8903_read(codec, WM8903_WRITE_SEQUENCER_0);
+ reg[0] |= WM8903_WSEQ_ENA;
+ wm8903_write(codec, WM8903_WRITE_SEQUENCER_0, reg[0]);
+
+ dev_dbg(&i2c->dev, "Starting sequence at %d\n", start);
+
+ wm8903_write(codec, WM8903_WRITE_SEQUENCER_3,
+ start | WM8903_WSEQ_START);
+
+ /* Wait for it to complete. If we have the interrupt wired up then
+ * we could block waiting for an interrupt, though polling may still
+ * be desirable for diagnostic purposes.
+ */
+ do {
+ msleep(10);
+
+ reg[4] = wm8903_read(codec, WM8903_WRITE_SEQUENCER_4);
+ } while (reg[4] & WM8903_WSEQ_BUSY);
+
+ dev_dbg(&i2c->dev, "Sequence complete\n");
+
+ /* Disable the sequencer again */
+ wm8903_write(codec, WM8903_WRITE_SEQUENCER_0,
+ reg[0] & ~WM8903_WSEQ_ENA);
+
+ return 0;
+}
+
+static void wm8903_sync_reg_cache(struct snd_soc_codec *codec, u16 *cache)
+{
+ int i;
+
+ /* There really ought to be something better we can do here :/ */
+ for (i = 0; i < ARRAY_SIZE(wm8903_reg_defaults); i++)
+ cache[i] = wm8903_hw_read(codec, i);
+}
+
+static void wm8903_reset(struct snd_soc_codec *codec)
+{
+ wm8903_write(codec, WM8903_SW_RESET_AND_ID, 0);
+}
+
+#define WM8903_OUTPUT_SHORT 0x8
+#define WM8903_OUTPUT_OUT 0x4
+#define WM8903_OUTPUT_INT 0x2
+#define WM8903_OUTPUT_IN 0x1
+
+/*
+ * Event for headphone and line out amplifier power changes. Special
+ * power up/down sequences are required in order to maximise pop/click
+ * performance.
+ */
+static int wm8903_output_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct wm8903_priv *wm8903 = codec->private_data;
+ struct i2c_client *i2c = codec->control_data;
+ u16 val;
+ u16 reg;
+ int shift;
+ u16 cp_reg = wm8903_read(codec, WM8903_CHARGE_PUMP_0);
+
+ switch (w->reg) {
+ case WM8903_POWER_MANAGEMENT_2:
+ reg = WM8903_ANALOGUE_HP_0;
+ break;
+ case WM8903_POWER_MANAGEMENT_3:
+ reg = WM8903_ANALOGUE_LINEOUT_0;
+ break;
+ default:
+ BUG();
+ }
+
+ switch (w->shift) {
+ case 0:
+ shift = 0;
+ break;
+ case 1:
+ shift = 4;
+ break;
+ default:
+ BUG();
+ }
+
+ if (event & SND_SOC_DAPM_PRE_PMU) {
+ val = wm8903_read(codec, reg);
+
+ /* Short the output */
+ val &= ~(WM8903_OUTPUT_SHORT << shift);
+ wm8903_write(codec, reg, val);
+
+ wm8903->charge_pump_users++;
+
+ dev_dbg(&i2c->dev, "Charge pump use count now %d\n",
+ wm8903->charge_pump_users);
+
+ if (wm8903->charge_pump_users == 1) {
+ dev_dbg(&i2c->dev, "Enabling charge pump\n");
+ wm8903_write(codec, WM8903_CHARGE_PUMP_0,
+ cp_reg | WM8903_CP_ENA);
+ mdelay(4);
+ }
+ }
+
+ if (event & SND_SOC_DAPM_POST_PMU) {
+ val = wm8903_read(codec, reg);
+
+ val |= (WM8903_OUTPUT_IN << shift);
+ wm8903_write(codec, reg, val);
+
+ val |= (WM8903_OUTPUT_INT << shift);
+ wm8903_write(codec, reg, val);
+
+ /* Turn on the output ENA_OUTP */
+ val |= (WM8903_OUTPUT_OUT << shift);
+ wm8903_write(codec, reg, val);
+
+ /* Remove the short */
+ val |= (WM8903_OUTPUT_SHORT << shift);
+ wm8903_write(codec, reg, val);
+ }
+
+ if (event & SND_SOC_DAPM_PRE_PMD) {
+ val = wm8903_read(codec, reg);
+
+ /* Short the output */
+ val &= ~(WM8903_OUTPUT_SHORT << shift);
+ wm8903_write(codec, reg, val);
+
+ /* Then disable the intermediate and output stages */
+ val &= ~((WM8903_OUTPUT_OUT | WM8903_OUTPUT_INT |
+ WM8903_OUTPUT_IN) << shift);
+ wm8903_write(codec, reg, val);
+ }
+
+ if (event & SND_SOC_DAPM_POST_PMD) {
+ wm8903->charge_pump_users--;
+
+ dev_dbg(&i2c->dev, "Charge pump use count now %d\n",
+ wm8903->charge_pump_users);
+
+ if (wm8903->charge_pump_users == 0) {
+ dev_dbg(&i2c->dev, "Disabling charge pump\n");
+ wm8903_write(codec, WM8903_CHARGE_PUMP_0,
+ cp_reg & ~WM8903_CP_ENA);
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * When used with DAC outputs only the WM8903 charge pump supports
+ * operation in class W mode, providing very low power consumption
+ * when used with digital sources. Enable and disable this mode
+ * automatically depending on the mixer configuration.
+ *
+ * All the relevant controls are simple switches.
+ */
+static int wm8903_class_w_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_codec *codec = widget->codec;
+ struct wm8903_priv *wm8903 = codec->private_data;
+ struct i2c_client *i2c = codec->control_data;
+ u16 reg;
+ int ret;
+
+ reg = wm8903_read(codec, WM8903_CLASS_W_0);
+
+ /* Turn it off if we're about to enable bypass */
+ if (ucontrol->value.integer.value[0]) {
+ if (wm8903->class_w_users == 0) {
+ dev_dbg(&i2c->dev, "Disabling Class W\n");
+ wm8903_write(codec, WM8903_CLASS_W_0, reg &
+ ~(WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V));
+ }
+ wm8903->class_w_users++;
+ }
+
+ /* Implement the change */
+ ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol);
+
+ /* If we've just disabled the last bypass path turn Class W on */
+ if (!ucontrol->value.integer.value[0]) {
+ if (wm8903->class_w_users == 1) {
+ dev_dbg(&i2c->dev, "Enabling Class W\n");
+ wm8903_write(codec, WM8903_CLASS_W_0, reg |
+ WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V);
+ }
+ wm8903->class_w_users--;
+ }
+
+ dev_dbg(&i2c->dev, "Bypass use count now %d\n",
+ wm8903->class_w_users);
+
+ return ret;
+}
+
+#define SOC_DAPM_SINGLE_W(xname, reg, shift, max, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = snd_soc_info_volsw, \
+ .get = snd_soc_dapm_get_volsw, .put = wm8903_class_w_put, \
+ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+
+/* ALSA can only do steps of .01dB */
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1);
+
+static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0);
+
+static const DECLARE_TLV_DB_SCALE(drc_tlv_thresh, 0, 75, 0);
+static const DECLARE_TLV_DB_SCALE(drc_tlv_amp, -2250, 75, 0);
+static const DECLARE_TLV_DB_SCALE(drc_tlv_min, 0, 600, 0);
+static const DECLARE_TLV_DB_SCALE(drc_tlv_max, 1200, 600, 0);
+static const DECLARE_TLV_DB_SCALE(drc_tlv_startup, -300, 50, 0);
+
+static const char *drc_slope_text[] = {
+ "1", "1/2", "1/4", "1/8", "1/16", "0"
+};
+
+static const struct soc_enum drc_slope_r0 =
+ SOC_ENUM_SINGLE(WM8903_DRC_2, 3, 6, drc_slope_text);
+
+static const struct soc_enum drc_slope_r1 =
+ SOC_ENUM_SINGLE(WM8903_DRC_2, 0, 6, drc_slope_text);
+
+static const char *drc_attack_text[] = {
+ "instantaneous",
+ "363us", "762us", "1.45ms", "2.9ms", "5.8ms", "11.6ms", "23.2ms",
+ "46.4ms", "92.8ms", "185.6ms"
+};
+
+static const struct soc_enum drc_attack =
+ SOC_ENUM_SINGLE(WM8903_DRC_1, 12, 11, drc_attack_text);
+
+static const char *drc_decay_text[] = {
+ "186ms", "372ms", "743ms", "1.49s", "2.97s", "5.94s", "11.89s",
+ "23.87s", "47.56s"
+};
+
+static const struct soc_enum drc_decay =
+ SOC_ENUM_SINGLE(WM8903_DRC_1, 8, 9, drc_decay_text);
+
+static const char *drc_ff_delay_text[] = {
+ "5 samples", "9 samples"
+};
+
+static const struct soc_enum drc_ff_delay =
+ SOC_ENUM_SINGLE(WM8903_DRC_0, 5, 2, drc_ff_delay_text);
+
+static const char *drc_qr_decay_text[] = {
+ "0.725ms", "1.45ms", "5.8ms"
+};
+
+static const struct soc_enum drc_qr_decay =
+ SOC_ENUM_SINGLE(WM8903_DRC_1, 4, 3, drc_qr_decay_text);
+
+static const char *drc_smoothing_text[] = {
+ "Low", "Medium", "High"
+};
+
+static const struct soc_enum drc_smoothing =
+ SOC_ENUM_SINGLE(WM8903_DRC_0, 11, 3, drc_smoothing_text);
+
+static const char *soft_mute_text[] = {
+ "Fast (fs/2)", "Slow (fs/32)"
+};
+
+static const struct soc_enum soft_mute =
+ SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_1, 10, 2, soft_mute_text);
+
+static const char *mute_mode_text[] = {
+ "Hard", "Soft"
+};
+
+static const struct soc_enum mute_mode =
+ SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_1, 9, 2, mute_mode_text);
+
+static const char *dac_deemphasis_text[] = {
+ "Disabled", "32kHz", "44.1kHz", "48kHz"
+};
+
+static const struct soc_enum dac_deemphasis =
+ SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_1, 1, 4, dac_deemphasis_text);
+
+static const char *companding_text[] = {
+ "ulaw", "alaw"
+};
+
+static const struct soc_enum dac_companding =
+ SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 0, 2, companding_text);
+
+static const struct soc_enum adc_companding =
+ SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 2, 2, companding_text);
+
+static const char *input_mode_text[] = {
+ "Single-Ended", "Differential Line", "Differential Mic"
+};
+
+static const struct soc_enum linput_mode_enum =
+ SOC_ENUM_SINGLE(WM8903_ANALOGUE_LEFT_INPUT_1, 0, 3, input_mode_text);
+
+static const struct soc_enum rinput_mode_enum =
+ SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 0, 3, input_mode_text);
+
+static const char *linput_mux_text[] = {
+ "IN1L", "IN2L", "IN3L"
+};
+
+static const struct soc_enum linput_enum =
+ SOC_ENUM_SINGLE(WM8903_ANALOGUE_LEFT_INPUT_1, 2, 3, linput_mux_text);
+
+static const struct soc_enum linput_inv_enum =
+ SOC_ENUM_SINGLE(WM8903_ANALOGUE_LEFT_INPUT_1, 4, 3, linput_mux_text);
+
+static const char *rinput_mux_text[] = {
+ "IN1R", "IN2R", "IN3R"
+};
+
+static const struct soc_enum rinput_enum =
+ SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 2, 3, rinput_mux_text);
+
+static const struct soc_enum rinput_inv_enum =
+ SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 4, 3, rinput_mux_text);
+
+
+static const struct snd_kcontrol_new wm8903_snd_controls[] = {
+
+/* Input PGAs - No TLV since the scale depends on PGA mode */
+SOC_SINGLE("Left Input PGA Switch", WM8903_ANALOGUE_LEFT_INPUT_0,
+ 7, 1, 1),
+SOC_SINGLE("Left Input PGA Volume", WM8903_ANALOGUE_LEFT_INPUT_0,
+ 0, 31, 0),
+SOC_SINGLE("Left Input PGA Common Mode Switch", WM8903_ANALOGUE_LEFT_INPUT_1,
+ 6, 1, 0),
+
+SOC_SINGLE("Right Input PGA Switch", WM8903_ANALOGUE_RIGHT_INPUT_0,
+ 7, 1, 1),
+SOC_SINGLE("Right Input PGA Volume", WM8903_ANALOGUE_RIGHT_INPUT_0,
+ 0, 31, 0),
+SOC_SINGLE("Right Input PGA Common Mode Switch", WM8903_ANALOGUE_RIGHT_INPUT_1,
+ 6, 1, 0),
+
+/* ADCs */
+SOC_SINGLE("DRC Switch", WM8903_DRC_0, 15, 1, 0),
+SOC_ENUM("DRC Compressor Slope R0", drc_slope_r0),
+SOC_ENUM("DRC Compressor Slope R1", drc_slope_r1),
+SOC_SINGLE_TLV("DRC Compressor Threashold Volume", WM8903_DRC_3, 5, 124, 1,
+ drc_tlv_thresh),
+SOC_SINGLE_TLV("DRC Volume", WM8903_DRC_3, 0, 30, 1, drc_tlv_amp),
+SOC_SINGLE_TLV("DRC Minimum Gain Volume", WM8903_DRC_1, 2, 3, 1, drc_tlv_min),
+SOC_SINGLE_TLV("DRC Maximum Gain Volume", WM8903_DRC_1, 0, 3, 0, drc_tlv_max),
+SOC_ENUM("DRC Attack Rate", drc_attack),
+SOC_ENUM("DRC Decay Rate", drc_decay),
+SOC_ENUM("DRC FF Delay", drc_ff_delay),
+SOC_SINGLE("DRC Anticlip Switch", WM8903_DRC_0, 1, 1, 0),
+SOC_SINGLE("DRC QR Switch", WM8903_DRC_0, 2, 1, 0),
+SOC_SINGLE_TLV("DRC QR Threashold Volume", WM8903_DRC_0, 6, 3, 0, drc_tlv_max),
+SOC_ENUM("DRC QR Decay Rate", drc_qr_decay),
+SOC_SINGLE("DRC Smoothing Switch", WM8903_DRC_0, 3, 1, 0),
+SOC_SINGLE("DRC Smoothing Hysteresis Switch", WM8903_DRC_0, 0, 1, 0),
+SOC_ENUM("DRC Smoothing Threashold", drc_smoothing),
+SOC_SINGLE_TLV("DRC Startup Volume", WM8903_DRC_0, 6, 18, 0, drc_tlv_startup),
+
+SOC_DOUBLE_R_TLV("Digital Capture Volume", WM8903_ADC_DIGITAL_VOLUME_LEFT,
+ WM8903_ADC_DIGITAL_VOLUME_RIGHT, 1, 96, 0, digital_tlv),
+SOC_ENUM("ADC Companding Mode", adc_companding),
+SOC_SINGLE("ADC Companding Switch", WM8903_AUDIO_INTERFACE_0, 3, 1, 0),
+
+/* DAC */
+SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8903_DAC_DIGITAL_VOLUME_LEFT,
+ WM8903_DAC_DIGITAL_VOLUME_RIGHT, 1, 120, 0, digital_tlv),
+SOC_ENUM("DAC Soft Mute Rate", soft_mute),
+SOC_ENUM("DAC Mute Mode", mute_mode),
+SOC_SINGLE("DAC Mono Switch", WM8903_DAC_DIGITAL_1, 12, 1, 0),
+SOC_ENUM("DAC De-emphasis", dac_deemphasis),
+SOC_SINGLE("DAC Sloping Stopband Filter Switch",
+ WM8903_DAC_DIGITAL_1, 11, 1, 0),
+SOC_ENUM("DAC Companding Mode", dac_companding),
+SOC_SINGLE("DAC Companding Switch", WM8903_AUDIO_INTERFACE_0, 1, 1, 0),
+
+/* Headphones */
+SOC_DOUBLE_R("Headphone Switch",
+ WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT,
+ 8, 1, 1),
+SOC_DOUBLE_R("Headphone ZC Switch",
+ WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT,
+ 6, 1, 0),
+SOC_DOUBLE_R_TLV("Headphone Volume",
+ WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT,
+ 0, 63, 0, out_tlv),
+
+/* Line out */
+SOC_DOUBLE_R("Line Out Switch",
+ WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT,
+ 8, 1, 1),
+SOC_DOUBLE_R("Line Out ZC Switch",
+ WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT,
+ 6, 1, 0),
+SOC_DOUBLE_R_TLV("Line Out Volume",
+ WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT,
+ 0, 63, 0, out_tlv),
+
+/* Speaker */
+SOC_DOUBLE_R("Speaker Switch",
+ WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT, 8, 1, 1),
+SOC_DOUBLE_R("Speaker ZC Switch",
+ WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT, 6, 1, 0),
+SOC_DOUBLE_R_TLV("Speaker Volume",
+ WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT,
+ 0, 63, 0, out_tlv),
+};
+
+static int wm8903_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8903_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8903_snd_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new linput_mode_mux =
+ SOC_DAPM_ENUM("Left Input Mode Mux", linput_mode_enum);
+
+static const struct snd_kcontrol_new rinput_mode_mux =
+ SOC_DAPM_ENUM("Right Input Mode Mux", rinput_mode_enum);
+
+static const struct snd_kcontrol_new linput_mux =
+ SOC_DAPM_ENUM("Left Input Mux", linput_enum);
+
+static const struct snd_kcontrol_new linput_inv_mux =
+ SOC_DAPM_ENUM("Left Inverting Input Mux", linput_inv_enum);
+
+static const struct snd_kcontrol_new rinput_mux =
+ SOC_DAPM_ENUM("Right Input Mux", rinput_enum);
+
+static const struct snd_kcontrol_new rinput_inv_mux =
+ SOC_DAPM_ENUM("Right Inverting Input Mux", rinput_inv_enum);
+
+static const struct snd_kcontrol_new left_output_mixer[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_LEFT_MIX_0, 3, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_LEFT_MIX_0, 2, 1, 0),
+SOC_DAPM_SINGLE_W("Left Bypass Switch", WM8903_ANALOGUE_LEFT_MIX_0, 1, 1, 0),
+SOC_DAPM_SINGLE_W("Right Bypass Switch", WM8903_ANALOGUE_LEFT_MIX_0, 1, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_output_mixer[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 3, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 2, 1, 0),
+SOC_DAPM_SINGLE_W("Left Bypass Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 1, 1, 0),
+SOC_DAPM_SINGLE_W("Right Bypass Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 1, 1, 0),
+};
+
+static const struct snd_kcontrol_new left_speaker_mixer[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 3, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 2, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 1, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0,
+ 1, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_speaker_mixer[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, 3, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, 2, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0,
+ 1, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0,
+ 1, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8903_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("IN1L"),
+SND_SOC_DAPM_INPUT("IN1R"),
+SND_SOC_DAPM_INPUT("IN2L"),
+SND_SOC_DAPM_INPUT("IN2R"),
+SND_SOC_DAPM_INPUT("IN3L"),
+SND_SOC_DAPM_INPUT("IN3R"),
+
+SND_SOC_DAPM_OUTPUT("HPOUTL"),
+SND_SOC_DAPM_OUTPUT("HPOUTR"),
+SND_SOC_DAPM_OUTPUT("LINEOUTL"),
+SND_SOC_DAPM_OUTPUT("LINEOUTR"),
+SND_SOC_DAPM_OUTPUT("LOP"),
+SND_SOC_DAPM_OUTPUT("LON"),
+SND_SOC_DAPM_OUTPUT("ROP"),
+SND_SOC_DAPM_OUTPUT("RON"),
+
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8903_MIC_BIAS_CONTROL_0, 0, 0),
+
+SND_SOC_DAPM_MUX("Left Input Mux", SND_SOC_NOPM, 0, 0, &linput_mux),
+SND_SOC_DAPM_MUX("Left Input Inverting Mux", SND_SOC_NOPM, 0, 0,
+ &linput_inv_mux),
+SND_SOC_DAPM_MUX("Left Input Mode Mux", SND_SOC_NOPM, 0, 0, &linput_mode_mux),
+
+SND_SOC_DAPM_MUX("Right Input Mux", SND_SOC_NOPM, 0, 0, &rinput_mux),
+SND_SOC_DAPM_MUX("Right Input Inverting Mux", SND_SOC_NOPM, 0, 0,
+ &rinput_inv_mux),
+SND_SOC_DAPM_MUX("Right Input Mode Mux", SND_SOC_NOPM, 0, 0, &rinput_mode_mux),
+
+SND_SOC_DAPM_PGA("Left Input PGA", WM8903_POWER_MANAGEMENT_0, 1, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Input PGA", WM8903_POWER_MANAGEMENT_0, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8903_POWER_MANAGEMENT_6, 1, 0),
+SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8903_POWER_MANAGEMENT_6, 0, 0),
+
+SND_SOC_DAPM_DAC("DACL", "Left Playback", WM8903_POWER_MANAGEMENT_6, 3, 0),
+SND_SOC_DAPM_DAC("DACR", "Right Playback", WM8903_POWER_MANAGEMENT_6, 2, 0),
+
+SND_SOC_DAPM_MIXER("Left Output Mixer", WM8903_POWER_MANAGEMENT_1, 1, 0,
+ left_output_mixer, ARRAY_SIZE(left_output_mixer)),
+SND_SOC_DAPM_MIXER("Right Output Mixer", WM8903_POWER_MANAGEMENT_1, 0, 0,
+ right_output_mixer, ARRAY_SIZE(right_output_mixer)),
+
+SND_SOC_DAPM_MIXER("Left Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 1, 0,
+ left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)),
+SND_SOC_DAPM_MIXER("Right Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 0, 0,
+ right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)),
+
+SND_SOC_DAPM_PGA_E("Left Headphone Output PGA", WM8903_POWER_MANAGEMENT_2,
+ 1, 0, NULL, 0, wm8903_output_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_PGA_E("Right Headphone Output PGA", WM8903_POWER_MANAGEMENT_2,
+ 0, 0, NULL, 0, wm8903_output_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
+
+SND_SOC_DAPM_PGA_E("Left Line Output PGA", WM8903_POWER_MANAGEMENT_3, 1, 0,
+ NULL, 0, wm8903_output_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_PGA_E("Right Line Output PGA", WM8903_POWER_MANAGEMENT_3, 0, 0,
+ NULL, 0, wm8903_output_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
+
+SND_SOC_DAPM_PGA("Left Speaker PGA", WM8903_POWER_MANAGEMENT_5, 1, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA("Right Speaker PGA", WM8903_POWER_MANAGEMENT_5, 0, 0,
+ NULL, 0),
+
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+
+ { "Left Input Mux", "IN1L", "IN1L" },
+ { "Left Input Mux", "IN2L", "IN2L" },
+ { "Left Input Mux", "IN3L", "IN3L" },
+
+ { "Left Input Inverting Mux", "IN1L", "IN1L" },
+ { "Left Input Inverting Mux", "IN2L", "IN2L" },
+ { "Left Input Inverting Mux", "IN3L", "IN3L" },
+
+ { "Right Input Mux", "IN1R", "IN1R" },
+ { "Right Input Mux", "IN2R", "IN2R" },
+ { "Right Input Mux", "IN3R", "IN3R" },
+
+ { "Right Input Inverting Mux", "IN1R", "IN1R" },
+ { "Right Input Inverting Mux", "IN2R", "IN2R" },
+ { "Right Input Inverting Mux", "IN3R", "IN3R" },
+
+ { "Left Input Mode Mux", "Single-Ended", "Left Input Inverting Mux" },
+ { "Left Input Mode Mux", "Differential Line",
+ "Left Input Mux" },
+ { "Left Input Mode Mux", "Differential Line",
+ "Left Input Inverting Mux" },
+ { "Left Input Mode Mux", "Differential Mic",
+ "Left Input Mux" },
+ { "Left Input Mode Mux", "Differential Mic",
+ "Left Input Inverting Mux" },
+
+ { "Right Input Mode Mux", "Single-Ended",
+ "Right Input Inverting Mux" },
+ { "Right Input Mode Mux", "Differential Line",
+ "Right Input Mux" },
+ { "Right Input Mode Mux", "Differential Line",
+ "Right Input Inverting Mux" },
+ { "Right Input Mode Mux", "Differential Mic",
+ "Right Input Mux" },
+ { "Right Input Mode Mux", "Differential Mic",
+ "Right Input Inverting Mux" },
+
+ { "Left Input PGA", NULL, "Left Input Mode Mux" },
+ { "Right Input PGA", NULL, "Right Input Mode Mux" },
+
+ { "ADCL", NULL, "Left Input PGA" },
+ { "ADCR", NULL, "Right Input PGA" },
+
+ { "Left Output Mixer", "Left Bypass Switch", "Left Input PGA" },
+ { "Left Output Mixer", "Right Bypass Switch", "Right Input PGA" },
+ { "Left Output Mixer", "DACL Switch", "DACL" },
+ { "Left Output Mixer", "DACR Switch", "DACR" },
+
+ { "Right Output Mixer", "Left Bypass Switch", "Left Input PGA" },
+ { "Right Output Mixer", "Right Bypass Switch", "Right Input PGA" },
+ { "Right Output Mixer", "DACL Switch", "DACL" },
+ { "Right Output Mixer", "DACR Switch", "DACR" },
+
+ { "Left Speaker Mixer", "Left Bypass Switch", "Left Input PGA" },
+ { "Left Speaker Mixer", "Right Bypass Switch", "Right Input PGA" },
+ { "Left Speaker Mixer", "DACL Switch", "DACL" },
+ { "Left Speaker Mixer", "DACR Switch", "DACR" },
+
+ { "Right Speaker Mixer", "Left Bypass Switch", "Left Input PGA" },
+ { "Right Speaker Mixer", "Right Bypass Switch", "Right Input PGA" },
+ { "Right Speaker Mixer", "DACL Switch", "DACL" },
+ { "Right Speaker Mixer", "DACR Switch", "DACR" },
+
+ { "Left Line Output PGA", NULL, "Left Output Mixer" },
+ { "Right Line Output PGA", NULL, "Right Output Mixer" },
+
+ { "Left Headphone Output PGA", NULL, "Left Output Mixer" },
+ { "Right Headphone Output PGA", NULL, "Right Output Mixer" },
+
+ { "Left Speaker PGA", NULL, "Left Speaker Mixer" },
+ { "Right Speaker PGA", NULL, "Right Speaker Mixer" },
+
+ { "HPOUTL", NULL, "Left Headphone Output PGA" },
+ { "HPOUTR", NULL, "Right Headphone Output PGA" },
+
+ { "LINEOUTL", NULL, "Left Line Output PGA" },
+ { "LINEOUTR", NULL, "Right Line Output PGA" },
+
+ { "LOP", NULL, "Left Speaker PGA" },
+ { "LON", NULL, "Left Speaker PGA" },
+
+ { "ROP", NULL, "Right Speaker PGA" },
+ { "RON", NULL, "Right Speaker PGA" },
+};
+
+static int wm8903_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm8903_dapm_widgets,
+ ARRAY_SIZE(wm8903_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+ snd_soc_dapm_new_widgets(codec);
+
+ return 0;
+}
+
+static int wm8903_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct i2c_client *i2c = codec->control_data;
+ u16 reg, reg2;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ reg = wm8903_read(codec, WM8903_VMID_CONTROL_0);
+ reg &= ~(WM8903_VMID_RES_MASK);
+ reg |= WM8903_VMID_RES_50K;
+ wm8903_write(codec, WM8903_VMID_CONTROL_0, reg);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->bias_level == SND_SOC_BIAS_OFF) {
+ wm8903_run_sequence(codec, 0);
+ wm8903_sync_reg_cache(codec, codec->reg_cache);
+
+ /* Enable low impedence charge pump output */
+ reg = wm8903_read(codec,
+ WM8903_CONTROL_INTERFACE_TEST_1);
+ wm8903_write(codec, WM8903_CONTROL_INTERFACE_TEST_1,
+ reg | WM8903_TEST_KEY);
+ reg2 = wm8903_read(codec, WM8903_CHARGE_PUMP_TEST_1);
+ wm8903_write(codec, WM8903_CHARGE_PUMP_TEST_1,
+ reg2 | WM8903_CP_SW_KELVIN_MODE_MASK);
+ wm8903_write(codec, WM8903_CONTROL_INTERFACE_TEST_1,
+ reg);
+
+ /* By default no bypass paths are enabled so
+ * enable Class W support.
+ */
+ dev_dbg(&i2c->dev, "Enabling Class W\n");
+ wm8903_write(codec, WM8903_CLASS_W_0, reg |
+ WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V);
+ }
+
+ reg = wm8903_read(codec, WM8903_VMID_CONTROL_0);
+ reg &= ~(WM8903_VMID_RES_MASK);
+ reg |= WM8903_VMID_RES_250K;
+ wm8903_write(codec, WM8903_VMID_CONTROL_0, reg);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ wm8903_run_sequence(codec, 32);
+ break;
+ }
+
+ codec->bias_level = level;
+
+ return 0;
+}
+
+static int wm8903_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 wm8903_priv *wm8903 = codec->private_data;
+
+ wm8903->sysclk = freq;
+
+ return 0;
+}
+
+static int wm8903_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 aif1 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_1);
+
+ aif1 &= ~(WM8903_LRCLK_DIR | WM8903_BCLK_DIR | WM8903_AIF_FMT_MASK |
+ WM8903_AIF_LRCLK_INV | WM8903_AIF_BCLK_INV);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ aif1 |= WM8903_LRCLK_DIR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ aif1 |= WM8903_LRCLK_DIR | WM8903_BCLK_DIR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ aif1 |= WM8903_BCLK_DIR;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ aif1 |= 0x3;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ aif1 |= 0x3 | WM8903_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ aif1 |= 0x2;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ aif1 |= 0x1;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ /* frame inversion not valid for DSP modes */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif1 |= WM8903_AIF_BCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ aif1 |= WM8903_AIF_BCLK_INV | WM8903_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif1 |= WM8903_AIF_BCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ aif1 |= WM8903_AIF_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8903_write(codec, WM8903_AUDIO_INTERFACE_1, aif1);
+
+ return 0;
+}
+
+static int wm8903_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ reg = wm8903_read(codec, WM8903_DAC_DIGITAL_1);
+
+ if (mute)
+ reg |= WM8903_DAC_MUTE;
+ else
+ reg &= ~WM8903_DAC_MUTE;
+
+ wm8903_write(codec, WM8903_DAC_DIGITAL_1, reg);
+
+ return 0;
+}
+
+/* Lookup table for CLK_SYS/fs ratio. 256fs or more is recommended
+ * for optimal performance so we list the lower rates first and match
+ * on the last match we find. */
+static struct {
+ int div;
+ int rate;
+ int mode;
+ int mclk_div;
+} clk_sys_ratios[] = {
+ { 64, 0x0, 0x0, 1 },
+ { 68, 0x0, 0x1, 1 },
+ { 125, 0x0, 0x2, 1 },
+ { 128, 0x1, 0x0, 1 },
+ { 136, 0x1, 0x1, 1 },
+ { 192, 0x2, 0x0, 1 },
+ { 204, 0x2, 0x1, 1 },
+
+ { 64, 0x0, 0x0, 2 },
+ { 68, 0x0, 0x1, 2 },
+ { 125, 0x0, 0x2, 2 },
+ { 128, 0x1, 0x0, 2 },
+ { 136, 0x1, 0x1, 2 },
+ { 192, 0x2, 0x0, 2 },
+ { 204, 0x2, 0x1, 2 },
+
+ { 250, 0x2, 0x2, 1 },
+ { 256, 0x3, 0x0, 1 },
+ { 272, 0x3, 0x1, 1 },
+ { 384, 0x4, 0x0, 1 },
+ { 408, 0x4, 0x1, 1 },
+ { 375, 0x4, 0x2, 1 },
+ { 512, 0x5, 0x0, 1 },
+ { 544, 0x5, 0x1, 1 },
+ { 500, 0x5, 0x2, 1 },
+ { 768, 0x6, 0x0, 1 },
+ { 816, 0x6, 0x1, 1 },
+ { 750, 0x6, 0x2, 1 },
+ { 1024, 0x7, 0x0, 1 },
+ { 1088, 0x7, 0x1, 1 },
+ { 1000, 0x7, 0x2, 1 },
+ { 1408, 0x8, 0x0, 1 },
+ { 1496, 0x8, 0x1, 1 },
+ { 1536, 0x9, 0x0, 1 },
+ { 1632, 0x9, 0x1, 1 },
+ { 1500, 0x9, 0x2, 1 },
+
+ { 250, 0x2, 0x2, 2 },
+ { 256, 0x3, 0x0, 2 },
+ { 272, 0x3, 0x1, 2 },
+ { 384, 0x4, 0x0, 2 },
+ { 408, 0x4, 0x1, 2 },
+ { 375, 0x4, 0x2, 2 },
+ { 512, 0x5, 0x0, 2 },
+ { 544, 0x5, 0x1, 2 },
+ { 500, 0x5, 0x2, 2 },
+ { 768, 0x6, 0x0, 2 },
+ { 816, 0x6, 0x1, 2 },
+ { 750, 0x6, 0x2, 2 },
+ { 1024, 0x7, 0x0, 2 },
+ { 1088, 0x7, 0x1, 2 },
+ { 1000, 0x7, 0x2, 2 },
+ { 1408, 0x8, 0x0, 2 },
+ { 1496, 0x8, 0x1, 2 },
+ { 1536, 0x9, 0x0, 2 },
+ { 1632, 0x9, 0x1, 2 },
+ { 1500, 0x9, 0x2, 2 },
+};
+
+/* CLK_SYS/BCLK ratios - multiplied by 10 due to .5s */
+static struct {
+ int ratio;
+ int div;
+} bclk_divs[] = {
+ { 10, 0 },
+ { 15, 1 },
+ { 20, 2 },
+ { 30, 3 },
+ { 40, 4 },
+ { 50, 5 },
+ { 55, 6 },
+ { 60, 7 },
+ { 80, 8 },
+ { 100, 9 },
+ { 110, 10 },
+ { 120, 11 },
+ { 160, 12 },
+ { 200, 13 },
+ { 220, 14 },
+ { 240, 15 },
+ { 250, 16 },
+ { 300, 17 },
+ { 320, 18 },
+ { 440, 19 },
+ { 480, 20 },
+};
+
+/* Sample rates for DSP */
+static struct {
+ int rate;
+ int value;
+} sample_rates[] = {
+ { 8000, 0 },
+ { 11025, 1 },
+ { 12000, 2 },
+ { 16000, 3 },
+ { 22050, 4 },
+ { 24000, 5 },
+ { 32000, 6 },
+ { 44100, 7 },
+ { 48000, 8 },
+ { 88200, 9 },
+ { 96000, 10 },
+ { 0, 0 },
+};
+
+static int wm8903_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct wm8903_priv *wm8903 = codec->private_data;
+ struct i2c_client *i2c = codec->control_data;
+ struct snd_pcm_runtime *master_runtime;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ wm8903->playback_active++;
+ else
+ wm8903->capture_active++;
+
+ /* The DAI has shared clocks so if we already have a playback or
+ * capture going then constrain this substream to match it.
+ */
+ if (wm8903->master_substream) {
+ master_runtime = wm8903->master_substream->runtime;
+
+ dev_dbg(&i2c->dev, "Constraining to %d bits at %dHz\n",
+ master_runtime->sample_bits,
+ master_runtime->rate);
+
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ master_runtime->rate,
+ master_runtime->rate);
+
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ master_runtime->sample_bits,
+ master_runtime->sample_bits);
+
+ wm8903->slave_substream = substream;
+ } else
+ wm8903->master_substream = substream;
+
+ return 0;
+}
+
+static void wm8903_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct wm8903_priv *wm8903 = codec->private_data;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ wm8903->playback_active--;
+ else
+ wm8903->capture_active--;
+
+ if (wm8903->master_substream == substream)
+ wm8903->master_substream = wm8903->slave_substream;
+
+ wm8903->slave_substream = NULL;
+}
+
+static int wm8903_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct wm8903_priv *wm8903 = codec->private_data;
+ struct i2c_client *i2c = codec->control_data;
+ int fs = params_rate(params);
+ int bclk;
+ int bclk_div;
+ int i;
+ int dsp_config;
+ int clk_config;
+ int best_val;
+ int cur_val;
+ int clk_sys;
+
+ u16 aif1 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_1);
+ u16 aif2 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_2);
+ u16 aif3 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_3);
+ u16 clock0 = wm8903_read(codec, WM8903_CLOCK_RATES_0);
+ u16 clock1 = wm8903_read(codec, WM8903_CLOCK_RATES_1);
+
+ if (substream == wm8903->slave_substream) {
+ dev_dbg(&i2c->dev, "Ignoring hw_params for slave substream\n");
+ return 0;
+ }
+
+ /* Configure sample rate logic for DSP - choose nearest rate */
+ dsp_config = 0;
+ best_val = abs(sample_rates[dsp_config].rate - fs);
+ for (i = 1; i < ARRAY_SIZE(sample_rates); i++) {
+ cur_val = abs(sample_rates[i].rate - fs);
+ if (cur_val <= best_val) {
+ dsp_config = i;
+ best_val = cur_val;
+ }
+ }
+
+ /* Constraints should stop us hitting this but let's make sure */
+ if (wm8903->capture_active)
+ switch (sample_rates[dsp_config].rate) {
+ case 88200:
+ case 96000:
+ dev_err(&i2c->dev, "%dHz unsupported by ADC\n",
+ fs);
+ return -EINVAL;
+
+ default:
+ break;
+ }
+
+ dev_dbg(&i2c->dev, "DSP fs = %dHz\n", sample_rates[dsp_config].rate);
+ clock1 &= ~WM8903_SAMPLE_RATE_MASK;
+ clock1 |= sample_rates[dsp_config].value;
+
+ aif1 &= ~WM8903_AIF_WL_MASK;
+ bclk = 2 * fs;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ bclk *= 16;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ bclk *= 20;
+ aif1 |= 0x4;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ bclk *= 24;
+ aif1 |= 0x8;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ bclk *= 32;
+ aif1 |= 0xc;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(&i2c->dev, "MCLK = %dHz, target sample rate = %dHz\n",
+ wm8903->sysclk, fs);
+
+ /* We may not have an MCLK which allows us to generate exactly
+ * the clock we want, particularly with USB derived inputs, so
+ * approximate.
+ */
+ clk_config = 0;
+ best_val = abs((wm8903->sysclk /
+ (clk_sys_ratios[0].mclk_div *
+ clk_sys_ratios[0].div)) - fs);
+ for (i = 1; i < ARRAY_SIZE(clk_sys_ratios); i++) {
+ cur_val = abs((wm8903->sysclk /
+ (clk_sys_ratios[i].mclk_div *
+ clk_sys_ratios[i].div)) - fs);
+
+ if (cur_val <= best_val) {
+ clk_config = i;
+ best_val = cur_val;
+ }
+ }
+
+ if (clk_sys_ratios[clk_config].mclk_div == 2) {
+ clock0 |= WM8903_MCLKDIV2;
+ clk_sys = wm8903->sysclk / 2;
+ } else {
+ clock0 &= ~WM8903_MCLKDIV2;
+ clk_sys = wm8903->sysclk;
+ }
+
+ clock1 &= ~(WM8903_CLK_SYS_RATE_MASK |
+ WM8903_CLK_SYS_MODE_MASK);
+ clock1 |= clk_sys_ratios[clk_config].rate << WM8903_CLK_SYS_RATE_SHIFT;
+ clock1 |= clk_sys_ratios[clk_config].mode << WM8903_CLK_SYS_MODE_SHIFT;
+
+ dev_dbg(&i2c->dev, "CLK_SYS_RATE=%x, CLK_SYS_MODE=%x div=%d\n",
+ clk_sys_ratios[clk_config].rate,
+ clk_sys_ratios[clk_config].mode,
+ clk_sys_ratios[clk_config].div);
+
+ dev_dbg(&i2c->dev, "Actual CLK_SYS = %dHz\n", clk_sys);
+
+ /* We may not get quite the right frequency if using
+ * approximate clocks so look for the closest match that is
+ * higher than the target (we need to ensure that there enough
+ * BCLKs to clock out the samples).
+ */
+ bclk_div = 0;
+ best_val = ((clk_sys * 10) / bclk_divs[0].ratio) - bclk;
+ i = 1;
+ while (i < ARRAY_SIZE(bclk_divs)) {
+ cur_val = ((clk_sys * 10) / bclk_divs[i].ratio) - bclk;
+ if (cur_val < 0) /* BCLK table is sorted */
+ break;
+ bclk_div = i;
+ best_val = cur_val;
+ i++;
+ }
+
+ aif2 &= ~WM8903_BCLK_DIV_MASK;
+ aif3 &= ~WM8903_LRCLK_RATE_MASK;
+
+ dev_dbg(&i2c->dev, "BCLK ratio %d for %dHz - actual BCLK = %dHz\n",
+ bclk_divs[bclk_div].ratio / 10, bclk,
+ (clk_sys * 10) / bclk_divs[bclk_div].ratio);
+
+ aif2 |= bclk_divs[bclk_div].div;
+ aif3 |= bclk / fs;
+
+ wm8903_write(codec, WM8903_CLOCK_RATES_0, clock0);
+ wm8903_write(codec, WM8903_CLOCK_RATES_1, clock1);
+ wm8903_write(codec, WM8903_AUDIO_INTERFACE_1, aif1);
+ wm8903_write(codec, WM8903_AUDIO_INTERFACE_2, aif2);
+ wm8903_write(codec, WM8903_AUDIO_INTERFACE_3, aif3);
+
+ return 0;
+}
+
+#define WM8903_PLAYBACK_RATES (SNDRV_PCM_RATE_8000 |\
+ SNDRV_PCM_RATE_11025 | \
+ SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_32000 | \
+ SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | \
+ SNDRV_PCM_RATE_88200 | \
+ SNDRV_PCM_RATE_96000)
+
+#define WM8903_CAPTURE_RATES (SNDRV_PCM_RATE_8000 |\
+ SNDRV_PCM_RATE_11025 | \
+ SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_32000 | \
+ SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000)
+
+#define WM8903_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+struct snd_soc_dai wm8903_dai = {
+ .name = "WM8903",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8903_PLAYBACK_RATES,
+ .formats = WM8903_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8903_CAPTURE_RATES,
+ .formats = WM8903_FORMATS,
+ },
+ .ops = {
+ .startup = wm8903_startup,
+ .shutdown = wm8903_shutdown,
+ .hw_params = wm8903_hw_params,
+ },
+ .dai_ops = {
+ .digital_mute = wm8903_digital_mute,
+ .set_fmt = wm8903_set_dai_fmt,
+ .set_sysclk = wm8903_set_dai_sysclk
+ }
+};
+EXPORT_SYMBOL_GPL(wm8903_dai);
+
+static int wm8903_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int wm8903_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ struct i2c_client *i2c = codec->control_data;
+ int i;
+ u16 *reg_cache = codec->reg_cache;
+ u16 *tmp_cache = kmemdup(codec->reg_cache, sizeof(wm8903_reg_defaults),
+ GFP_KERNEL);
+
+ /* Bring the codec back up to standby first to minimise pop/clicks */
+ wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ wm8903_set_bias_level(codec, codec->suspend_bias_level);
+
+ /* Sync back everything else */
+ if (tmp_cache) {
+ for (i = 2; i < ARRAY_SIZE(wm8903_reg_defaults); i++)
+ if (tmp_cache[i] != reg_cache[i])
+ wm8903_write(codec, i, tmp_cache[i]);
+ } else {
+ dev_err(&i2c->dev, "Failed to allocate temporary cache\n");
+ }
+
+ return 0;
+}
+
+/*
+ * initialise the WM8903 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int wm8903_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ struct i2c_client *i2c = codec->control_data;
+ int ret = 0;
+ u16 val;
+
+ val = wm8903_hw_read(codec, WM8903_SW_RESET_AND_ID);
+ if (val != wm8903_reg_defaults[WM8903_SW_RESET_AND_ID]) {
+ dev_err(&i2c->dev,
+ "Device with ID register %x is not a WM8903\n", val);
+ return -ENODEV;
+ }
+
+ codec->name = "WM8903";
+ codec->owner = THIS_MODULE;
+ codec->read = wm8903_read;
+ codec->write = wm8903_write;
+ codec->bias_level = SND_SOC_BIAS_OFF;
+ codec->set_bias_level = wm8903_set_bias_level;
+ codec->dai = &wm8903_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = ARRAY_SIZE(wm8903_reg_defaults);
+ codec->reg_cache = kmemdup(wm8903_reg_defaults,
+ sizeof(wm8903_reg_defaults),
+ GFP_KERNEL);
+ if (codec->reg_cache == NULL) {
+ dev_err(&i2c->dev, "Failed to allocate register cache\n");
+ return -ENOMEM;
+ }
+
+ val = wm8903_read(codec, WM8903_REVISION_NUMBER);
+ dev_info(&i2c->dev, "WM8903 revision %d\n",
+ val & WM8903_CHIP_REV_MASK);
+
+ wm8903_reset(codec);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ dev_err(&i2c->dev, "failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* SYSCLK is required for pretty much anything */
+ wm8903_write(codec, WM8903_CLOCK_RATES_2, WM8903_CLK_SYS_ENA);
+
+ /* power on device */
+ wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Latch volume update bits */
+ val = wm8903_read(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT);
+ val |= WM8903_ADCVU;
+ wm8903_write(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT, val);
+ wm8903_write(codec, WM8903_ADC_DIGITAL_VOLUME_RIGHT, val);
+
+ val = wm8903_read(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT);
+ val |= WM8903_DACVU;
+ wm8903_write(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT, val);
+ wm8903_write(codec, WM8903_DAC_DIGITAL_VOLUME_RIGHT, val);
+
+ val = wm8903_read(codec, WM8903_ANALOGUE_OUT1_LEFT);
+ val |= WM8903_HPOUTVU;
+ wm8903_write(codec, WM8903_ANALOGUE_OUT1_LEFT, val);
+ wm8903_write(codec, WM8903_ANALOGUE_OUT1_RIGHT, val);
+
+ val = wm8903_read(codec, WM8903_ANALOGUE_OUT2_LEFT);
+ val |= WM8903_LINEOUTVU;
+ wm8903_write(codec, WM8903_ANALOGUE_OUT2_LEFT, val);
+ wm8903_write(codec, WM8903_ANALOGUE_OUT2_RIGHT, val);
+
+ val = wm8903_read(codec, WM8903_ANALOGUE_OUT3_LEFT);
+ val |= WM8903_SPKVU;
+ wm8903_write(codec, WM8903_ANALOGUE_OUT3_LEFT, val);
+ wm8903_write(codec, WM8903_ANALOGUE_OUT3_RIGHT, val);
+
+ /* Enable DAC soft mute by default */
+ val = wm8903_read(codec, WM8903_DAC_DIGITAL_1);
+ val |= WM8903_DAC_MUTEMODE;
+ wm8903_write(codec, WM8903_DAC_DIGITAL_1, val);
+
+ wm8903_add_controls(codec);
+ wm8903_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ dev_err(&i2c->dev, "wm8903: failed to register card\n");
+ goto card_err;
+ }
+
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+ return ret;
+}
+
+static struct snd_soc_device *wm8903_socdev;
+
+static int wm8903_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = wm8903_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = wm8903_init(socdev);
+ if (ret < 0)
+ dev_err(&i2c->dev, "Device initialisation failed\n");
+
+ return ret;
+}
+
+static int wm8903_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+/* i2c codec control layer */
+static const struct i2c_device_id wm8903_i2c_id[] = {
+ { "wm8903", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8903_i2c_id);
+
+static struct i2c_driver wm8903_i2c_driver = {
+ .driver = {
+ .name = "WM8903",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8903_i2c_probe,
+ .remove = wm8903_i2c_remove,
+ .id_table = wm8903_i2c_id,
+};
+
+static int wm8903_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct wm8903_setup_data *setup;
+ struct snd_soc_codec *codec;
+ struct wm8903_priv *wm8903;
+ struct i2c_board_info board_info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *i2c_client;
+ int ret = 0;
+
+ setup = socdev->codec_data;
+
+ if (!setup->i2c_address) {
+ dev_err(&pdev->dev, "No codec address provided\n");
+ return -ENODEV;
+ }
+
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ wm8903 = kzalloc(sizeof(struct wm8903_priv), GFP_KERNEL);
+ if (wm8903 == NULL) {
+ ret = -ENOMEM;
+ goto err_codec;
+ }
+
+ codec->private_data = wm8903;
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ wm8903_socdev = socdev;
+
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = i2c_add_driver(&wm8903_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ goto err_priv;
+ } else {
+ memset(&board_info, 0, sizeof(board_info));
+ strlcpy(board_info.type, "wm8903", I2C_NAME_SIZE);
+ board_info.addr = setup->i2c_address;
+
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "Can't get I2C bus %d\n",
+ setup->i2c_bus);
+ ret = -ENODEV;
+ goto err_adapter;
+ }
+
+ i2c_client = i2c_new_device(adapter, &board_info);
+ i2c_put_adapter(adapter);
+ if (i2c_client == NULL) {
+ dev_err(&pdev->dev,
+ "I2C driver registration failed\n");
+ ret = -ENODEV;
+ goto err_adapter;
+ }
+ }
+
+ return ret;
+
+err_adapter:
+ i2c_del_driver(&wm8903_i2c_driver);
+err_priv:
+ kfree(codec->private_data);
+err_codec:
+ kfree(codec);
+ return ret;
+}
+
+/* power down chip */
+static int wm8903_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec->control_data)
+ wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+ i2c_unregister_device(socdev->codec->control_data);
+ i2c_del_driver(&wm8903_i2c_driver);
+ kfree(codec->private_data);
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8903 = {
+ .probe = wm8903_probe,
+ .remove = wm8903_remove,
+ .suspend = wm8903_suspend,
+ .resume = wm8903_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8903);
+
+MODULE_DESCRIPTION("ASoC WM8903 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.cm>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8903.h b/sound/soc/codecs/wm8903.h
new file mode 100644
index 0000000..cec622f
--- /dev/null
+++ b/sound/soc/codecs/wm8903.h
@@ -0,0 +1,1463 @@
+/*
+ * wm8903.h - WM8903 audio codec interface
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#ifndef _WM8903_H
+#define _WM8903_H
+
+#include <linux/i2c.h>
+
+extern struct snd_soc_dai wm8903_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8903;
+
+struct wm8903_setup_data {
+ int i2c_bus;
+ int i2c_address;
+};
+
+#define WM8903_MCLK_DIV_2 1
+#define WM8903_CLK_SYS 2
+#define WM8903_BCLK 3
+#define WM8903_LRCLK 4
+
+/*
+ * Register values.
+ */
+#define WM8903_SW_RESET_AND_ID 0x00
+#define WM8903_REVISION_NUMBER 0x01
+#define WM8903_BIAS_CONTROL_0 0x04
+#define WM8903_VMID_CONTROL_0 0x05
+#define WM8903_MIC_BIAS_CONTROL_0 0x06
+#define WM8903_ANALOGUE_DAC_0 0x08
+#define WM8903_ANALOGUE_ADC_0 0x0A
+#define WM8903_POWER_MANAGEMENT_0 0x0C
+#define WM8903_POWER_MANAGEMENT_1 0x0D
+#define WM8903_POWER_MANAGEMENT_2 0x0E
+#define WM8903_POWER_MANAGEMENT_3 0x0F
+#define WM8903_POWER_MANAGEMENT_4 0x10
+#define WM8903_POWER_MANAGEMENT_5 0x11
+#define WM8903_POWER_MANAGEMENT_6 0x12
+#define WM8903_CLOCK_RATES_0 0x14
+#define WM8903_CLOCK_RATES_1 0x15
+#define WM8903_CLOCK_RATES_2 0x16
+#define WM8903_AUDIO_INTERFACE_0 0x18
+#define WM8903_AUDIO_INTERFACE_1 0x19
+#define WM8903_AUDIO_INTERFACE_2 0x1A
+#define WM8903_AUDIO_INTERFACE_3 0x1B
+#define WM8903_DAC_DIGITAL_VOLUME_LEFT 0x1E
+#define WM8903_DAC_DIGITAL_VOLUME_RIGHT 0x1F
+#define WM8903_DAC_DIGITAL_0 0x20
+#define WM8903_DAC_DIGITAL_1 0x21
+#define WM8903_ADC_DIGITAL_VOLUME_LEFT 0x24
+#define WM8903_ADC_DIGITAL_VOLUME_RIGHT 0x25
+#define WM8903_ADC_DIGITAL_0 0x26
+#define WM8903_DIGITAL_MICROPHONE_0 0x27
+#define WM8903_DRC_0 0x28
+#define WM8903_DRC_1 0x29
+#define WM8903_DRC_2 0x2A
+#define WM8903_DRC_3 0x2B
+#define WM8903_ANALOGUE_LEFT_INPUT_0 0x2C
+#define WM8903_ANALOGUE_RIGHT_INPUT_0 0x2D
+#define WM8903_ANALOGUE_LEFT_INPUT_1 0x2E
+#define WM8903_ANALOGUE_RIGHT_INPUT_1 0x2F
+#define WM8903_ANALOGUE_LEFT_MIX_0 0x32
+#define WM8903_ANALOGUE_RIGHT_MIX_0 0x33
+#define WM8903_ANALOGUE_SPK_MIX_LEFT_0 0x34
+#define WM8903_ANALOGUE_SPK_MIX_LEFT_1 0x35
+#define WM8903_ANALOGUE_SPK_MIX_RIGHT_0 0x36
+#define WM8903_ANALOGUE_SPK_MIX_RIGHT_1 0x37
+#define WM8903_ANALOGUE_OUT1_LEFT 0x39
+#define WM8903_ANALOGUE_OUT1_RIGHT 0x3A
+#define WM8903_ANALOGUE_OUT2_LEFT 0x3B
+#define WM8903_ANALOGUE_OUT2_RIGHT 0x3C
+#define WM8903_ANALOGUE_OUT3_LEFT 0x3E
+#define WM8903_ANALOGUE_OUT3_RIGHT 0x3F
+#define WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0 0x41
+#define WM8903_DC_SERVO_0 0x43
+#define WM8903_DC_SERVO_2 0x45
+#define WM8903_ANALOGUE_HP_0 0x5A
+#define WM8903_ANALOGUE_LINEOUT_0 0x5E
+#define WM8903_CHARGE_PUMP_0 0x62
+#define WM8903_CLASS_W_0 0x68
+#define WM8903_WRITE_SEQUENCER_0 0x6C
+#define WM8903_WRITE_SEQUENCER_1 0x6D
+#define WM8903_WRITE_SEQUENCER_2 0x6E
+#define WM8903_WRITE_SEQUENCER_3 0x6F
+#define WM8903_WRITE_SEQUENCER_4 0x70
+#define WM8903_CONTROL_INTERFACE 0x72
+#define WM8903_GPIO_CONTROL_1 0x74
+#define WM8903_GPIO_CONTROL_2 0x75
+#define WM8903_GPIO_CONTROL_3 0x76
+#define WM8903_GPIO_CONTROL_4 0x77
+#define WM8903_GPIO_CONTROL_5 0x78
+#define WM8903_INTERRUPT_STATUS_1 0x79
+#define WM8903_INTERRUPT_STATUS_1_MASK 0x7A
+#define WM8903_INTERRUPT_POLARITY_1 0x7B
+#define WM8903_INTERRUPT_CONTROL 0x7E
+#define WM8903_CONTROL_INTERFACE_TEST_1 0x81
+#define WM8903_CHARGE_PUMP_TEST_1 0x95
+#define WM8903_CLOCK_RATE_TEST_4 0xA4
+#define WM8903_ANALOGUE_OUTPUT_BIAS_0 0xAC
+
+#define WM8903_REGISTER_COUNT 75
+#define WM8903_MAX_REGISTER 0xAC
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - SW Reset and ID
+ */
+#define WM8903_SW_RESET_DEV_ID1_MASK 0xFFFF /* SW_RESET_DEV_ID1 - [15:0] */
+#define WM8903_SW_RESET_DEV_ID1_SHIFT 0 /* SW_RESET_DEV_ID1 - [15:0] */
+#define WM8903_SW_RESET_DEV_ID1_WIDTH 16 /* SW_RESET_DEV_ID1 - [15:0] */
+
+/*
+ * R1 (0x01) - Revision Number
+ */
+#define WM8903_CHIP_REV_MASK 0x000F /* CHIP_REV - [3:0] */
+#define WM8903_CHIP_REV_SHIFT 0 /* CHIP_REV - [3:0] */
+#define WM8903_CHIP_REV_WIDTH 4 /* CHIP_REV - [3:0] */
+
+/*
+ * R4 (0x04) - Bias Control 0
+ */
+#define WM8903_POBCTRL 0x0010 /* POBCTRL */
+#define WM8903_POBCTRL_MASK 0x0010 /* POBCTRL */
+#define WM8903_POBCTRL_SHIFT 4 /* POBCTRL */
+#define WM8903_POBCTRL_WIDTH 1 /* POBCTRL */
+#define WM8903_ISEL_MASK 0x000C /* ISEL - [3:2] */
+#define WM8903_ISEL_SHIFT 2 /* ISEL - [3:2] */
+#define WM8903_ISEL_WIDTH 2 /* ISEL - [3:2] */
+#define WM8903_STARTUP_BIAS_ENA 0x0002 /* STARTUP_BIAS_ENA */
+#define WM8903_STARTUP_BIAS_ENA_MASK 0x0002 /* STARTUP_BIAS_ENA */
+#define WM8903_STARTUP_BIAS_ENA_SHIFT 1 /* STARTUP_BIAS_ENA */
+#define WM8903_STARTUP_BIAS_ENA_WIDTH 1 /* STARTUP_BIAS_ENA */
+#define WM8903_BIAS_ENA 0x0001 /* BIAS_ENA */
+#define WM8903_BIAS_ENA_MASK 0x0001 /* BIAS_ENA */
+#define WM8903_BIAS_ENA_SHIFT 0 /* BIAS_ENA */
+#define WM8903_BIAS_ENA_WIDTH 1 /* BIAS_ENA */
+
+/*
+ * R5 (0x05) - VMID Control 0
+ */
+#define WM8903_VMID_TIE_ENA 0x0080 /* VMID_TIE_ENA */
+#define WM8903_VMID_TIE_ENA_MASK 0x0080 /* VMID_TIE_ENA */
+#define WM8903_VMID_TIE_ENA_SHIFT 7 /* VMID_TIE_ENA */
+#define WM8903_VMID_TIE_ENA_WIDTH 1 /* VMID_TIE_ENA */
+#define WM8903_BUFIO_ENA 0x0040 /* BUFIO_ENA */
+#define WM8903_BUFIO_ENA_MASK 0x0040 /* BUFIO_ENA */
+#define WM8903_BUFIO_ENA_SHIFT 6 /* BUFIO_ENA */
+#define WM8903_BUFIO_ENA_WIDTH 1 /* BUFIO_ENA */
+#define WM8903_VMID_IO_ENA 0x0020 /* VMID_IO_ENA */
+#define WM8903_VMID_IO_ENA_MASK 0x0020 /* VMID_IO_ENA */
+#define WM8903_VMID_IO_ENA_SHIFT 5 /* VMID_IO_ENA */
+#define WM8903_VMID_IO_ENA_WIDTH 1 /* VMID_IO_ENA */
+#define WM8903_VMID_SOFT_MASK 0x0018 /* VMID_SOFT - [4:3] */
+#define WM8903_VMID_SOFT_SHIFT 3 /* VMID_SOFT - [4:3] */
+#define WM8903_VMID_SOFT_WIDTH 2 /* VMID_SOFT - [4:3] */
+#define WM8903_VMID_RES_MASK 0x0006 /* VMID_RES - [2:1] */
+#define WM8903_VMID_RES_SHIFT 1 /* VMID_RES - [2:1] */
+#define WM8903_VMID_RES_WIDTH 2 /* VMID_RES - [2:1] */
+#define WM8903_VMID_BUF_ENA 0x0001 /* VMID_BUF_ENA */
+#define WM8903_VMID_BUF_ENA_MASK 0x0001 /* VMID_BUF_ENA */
+#define WM8903_VMID_BUF_ENA_SHIFT 0 /* VMID_BUF_ENA */
+#define WM8903_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */
+
+#define WM8903_VMID_RES_50K 2
+#define WM8903_VMID_RES_250K 3
+#define WM8903_VMID_RES_5K 4
+
+/*
+ * R6 (0x06) - Mic Bias Control 0
+ */
+#define WM8903_MICDET_HYST_ENA 0x0080 /* MICDET_HYST_ENA */
+#define WM8903_MICDET_HYST_ENA_MASK 0x0080 /* MICDET_HYST_ENA */
+#define WM8903_MICDET_HYST_ENA_SHIFT 7 /* MICDET_HYST_ENA */
+#define WM8903_MICDET_HYST_ENA_WIDTH 1 /* MICDET_HYST_ENA */
+#define WM8903_MICDET_THR_MASK 0x0070 /* MICDET_THR - [6:4] */
+#define WM8903_MICDET_THR_SHIFT 4 /* MICDET_THR - [6:4] */
+#define WM8903_MICDET_THR_WIDTH 3 /* MICDET_THR - [6:4] */
+#define WM8903_MICSHORT_THR_MASK 0x000C /* MICSHORT_THR - [3:2] */
+#define WM8903_MICSHORT_THR_SHIFT 2 /* MICSHORT_THR - [3:2] */
+#define WM8903_MICSHORT_THR_WIDTH 2 /* MICSHORT_THR - [3:2] */
+#define WM8903_MICDET_ENA 0x0002 /* MICDET_ENA */
+#define WM8903_MICDET_ENA_MASK 0x0002 /* MICDET_ENA */
+#define WM8903_MICDET_ENA_SHIFT 1 /* MICDET_ENA */
+#define WM8903_MICDET_ENA_WIDTH 1 /* MICDET_ENA */
+#define WM8903_MICBIAS_ENA 0x0001 /* MICBIAS_ENA */
+#define WM8903_MICBIAS_ENA_MASK 0x0001 /* MICBIAS_ENA */
+#define WM8903_MICBIAS_ENA_SHIFT 0 /* MICBIAS_ENA */
+#define WM8903_MICBIAS_ENA_WIDTH 1 /* MICBIAS_ENA */
+
+/*
+ * R8 (0x08) - Analogue DAC 0
+ */
+#define WM8903_DACBIAS_SEL_MASK 0x0018 /* DACBIAS_SEL - [4:3] */
+#define WM8903_DACBIAS_SEL_SHIFT 3 /* DACBIAS_SEL - [4:3] */
+#define WM8903_DACBIAS_SEL_WIDTH 2 /* DACBIAS_SEL - [4:3] */
+#define WM8903_DACVMID_BIAS_SEL_MASK 0x0006 /* DACVMID_BIAS_SEL - [2:1] */
+#define WM8903_DACVMID_BIAS_SEL_SHIFT 1 /* DACVMID_BIAS_SEL - [2:1] */
+#define WM8903_DACVMID_BIAS_SEL_WIDTH 2 /* DACVMID_BIAS_SEL - [2:1] */
+
+/*
+ * R10 (0x0A) - Analogue ADC 0
+ */
+#define WM8903_ADC_OSR128 0x0001 /* ADC_OSR128 */
+#define WM8903_ADC_OSR128_MASK 0x0001 /* ADC_OSR128 */
+#define WM8903_ADC_OSR128_SHIFT 0 /* ADC_OSR128 */
+#define WM8903_ADC_OSR128_WIDTH 1 /* ADC_OSR128 */
+
+/*
+ * R12 (0x0C) - Power Management 0
+ */
+#define WM8903_INL_ENA 0x0002 /* INL_ENA */
+#define WM8903_INL_ENA_MASK 0x0002 /* INL_ENA */
+#define WM8903_INL_ENA_SHIFT 1 /* INL_ENA */
+#define WM8903_INL_ENA_WIDTH 1 /* INL_ENA */
+#define WM8903_INR_ENA 0x0001 /* INR_ENA */
+#define WM8903_INR_ENA_MASK 0x0001 /* INR_ENA */
+#define WM8903_INR_ENA_SHIFT 0 /* INR_ENA */
+#define WM8903_INR_ENA_WIDTH 1 /* INR_ENA */
+
+/*
+ * R13 (0x0D) - Power Management 1
+ */
+#define WM8903_MIXOUTL_ENA 0x0002 /* MIXOUTL_ENA */
+#define WM8903_MIXOUTL_ENA_MASK 0x0002 /* MIXOUTL_ENA */
+#define WM8903_MIXOUTL_ENA_SHIFT 1 /* MIXOUTL_ENA */
+#define WM8903_MIXOUTL_ENA_WIDTH 1 /* MIXOUTL_ENA */
+#define WM8903_MIXOUTR_ENA 0x0001 /* MIXOUTR_ENA */
+#define WM8903_MIXOUTR_ENA_MASK 0x0001 /* MIXOUTR_ENA */
+#define WM8903_MIXOUTR_ENA_SHIFT 0 /* MIXOUTR_ENA */
+#define WM8903_MIXOUTR_ENA_WIDTH 1 /* MIXOUTR_ENA */
+
+/*
+ * R14 (0x0E) - Power Management 2
+ */
+#define WM8903_HPL_PGA_ENA 0x0002 /* HPL_PGA_ENA */
+#define WM8903_HPL_PGA_ENA_MASK 0x0002 /* HPL_PGA_ENA */
+#define WM8903_HPL_PGA_ENA_SHIFT 1 /* HPL_PGA_ENA */
+#define WM8903_HPL_PGA_ENA_WIDTH 1 /* HPL_PGA_ENA */
+#define WM8903_HPR_PGA_ENA 0x0001 /* HPR_PGA_ENA */
+#define WM8903_HPR_PGA_ENA_MASK 0x0001 /* HPR_PGA_ENA */
+#define WM8903_HPR_PGA_ENA_SHIFT 0 /* HPR_PGA_ENA */
+#define WM8903_HPR_PGA_ENA_WIDTH 1 /* HPR_PGA_ENA */
+
+/*
+ * R15 (0x0F) - Power Management 3
+ */
+#define WM8903_LINEOUTL_PGA_ENA 0x0002 /* LINEOUTL_PGA_ENA */
+#define WM8903_LINEOUTL_PGA_ENA_MASK 0x0002 /* LINEOUTL_PGA_ENA */
+#define WM8903_LINEOUTL_PGA_ENA_SHIFT 1 /* LINEOUTL_PGA_ENA */
+#define WM8903_LINEOUTL_PGA_ENA_WIDTH 1 /* LINEOUTL_PGA_ENA */
+#define WM8903_LINEOUTR_PGA_ENA 0x0001 /* LINEOUTR_PGA_ENA */
+#define WM8903_LINEOUTR_PGA_ENA_MASK 0x0001 /* LINEOUTR_PGA_ENA */
+#define WM8903_LINEOUTR_PGA_ENA_SHIFT 0 /* LINEOUTR_PGA_ENA */
+#define WM8903_LINEOUTR_PGA_ENA_WIDTH 1 /* LINEOUTR_PGA_ENA */
+
+/*
+ * R16 (0x10) - Power Management 4
+ */
+#define WM8903_MIXSPKL_ENA 0x0002 /* MIXSPKL_ENA */
+#define WM8903_MIXSPKL_ENA_MASK 0x0002 /* MIXSPKL_ENA */
+#define WM8903_MIXSPKL_ENA_SHIFT 1 /* MIXSPKL_ENA */
+#define WM8903_MIXSPKL_ENA_WIDTH 1 /* MIXSPKL_ENA */
+#define WM8903_MIXSPKR_ENA 0x0001 /* MIXSPKR_ENA */
+#define WM8903_MIXSPKR_ENA_MASK 0x0001 /* MIXSPKR_ENA */
+#define WM8903_MIXSPKR_ENA_SHIFT 0 /* MIXSPKR_ENA */
+#define WM8903_MIXSPKR_ENA_WIDTH 1 /* MIXSPKR_ENA */
+
+/*
+ * R17 (0x11) - Power Management 5
+ */
+#define WM8903_SPKL_ENA 0x0002 /* SPKL_ENA */
+#define WM8903_SPKL_ENA_MASK 0x0002 /* SPKL_ENA */
+#define WM8903_SPKL_ENA_SHIFT 1 /* SPKL_ENA */
+#define WM8903_SPKL_ENA_WIDTH 1 /* SPKL_ENA */
+#define WM8903_SPKR_ENA 0x0001 /* SPKR_ENA */
+#define WM8903_SPKR_ENA_MASK 0x0001 /* SPKR_ENA */
+#define WM8903_SPKR_ENA_SHIFT 0 /* SPKR_ENA */
+#define WM8903_SPKR_ENA_WIDTH 1 /* SPKR_ENA */
+
+/*
+ * R18 (0x12) - Power Management 6
+ */
+#define WM8903_DACL_ENA 0x0008 /* DACL_ENA */
+#define WM8903_DACL_ENA_MASK 0x0008 /* DACL_ENA */
+#define WM8903_DACL_ENA_SHIFT 3 /* DACL_ENA */
+#define WM8903_DACL_ENA_WIDTH 1 /* DACL_ENA */
+#define WM8903_DACR_ENA 0x0004 /* DACR_ENA */
+#define WM8903_DACR_ENA_MASK 0x0004 /* DACR_ENA */
+#define WM8903_DACR_ENA_SHIFT 2 /* DACR_ENA */
+#define WM8903_DACR_ENA_WIDTH 1 /* DACR_ENA */
+#define WM8903_ADCL_ENA 0x0002 /* ADCL_ENA */
+#define WM8903_ADCL_ENA_MASK 0x0002 /* ADCL_ENA */
+#define WM8903_ADCL_ENA_SHIFT 1 /* ADCL_ENA */
+#define WM8903_ADCL_ENA_WIDTH 1 /* ADCL_ENA */
+#define WM8903_ADCR_ENA 0x0001 /* ADCR_ENA */
+#define WM8903_ADCR_ENA_MASK 0x0001 /* ADCR_ENA */
+#define WM8903_ADCR_ENA_SHIFT 0 /* ADCR_ENA */
+#define WM8903_ADCR_ENA_WIDTH 1 /* ADCR_ENA */
+
+/*
+ * R20 (0x14) - Clock Rates 0
+ */
+#define WM8903_MCLKDIV2 0x0001 /* MCLKDIV2 */
+#define WM8903_MCLKDIV2_MASK 0x0001 /* MCLKDIV2 */
+#define WM8903_MCLKDIV2_SHIFT 0 /* MCLKDIV2 */
+#define WM8903_MCLKDIV2_WIDTH 1 /* MCLKDIV2 */
+
+/*
+ * R21 (0x15) - Clock Rates 1
+ */
+#define WM8903_CLK_SYS_RATE_MASK 0x3C00 /* CLK_SYS_RATE - [13:10] */
+#define WM8903_CLK_SYS_RATE_SHIFT 10 /* CLK_SYS_RATE - [13:10] */
+#define WM8903_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [13:10] */
+#define WM8903_CLK_SYS_MODE_MASK 0x0300 /* CLK_SYS_MODE - [9:8] */
+#define WM8903_CLK_SYS_MODE_SHIFT 8 /* CLK_SYS_MODE - [9:8] */
+#define WM8903_CLK_SYS_MODE_WIDTH 2 /* CLK_SYS_MODE - [9:8] */
+#define WM8903_SAMPLE_RATE_MASK 0x000F /* SAMPLE_RATE - [3:0] */
+#define WM8903_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [3:0] */
+#define WM8903_SAMPLE_RATE_WIDTH 4 /* SAMPLE_RATE - [3:0] */
+
+/*
+ * R22 (0x16) - Clock Rates 2
+ */
+#define WM8903_CLK_SYS_ENA 0x0004 /* CLK_SYS_ENA */
+#define WM8903_CLK_SYS_ENA_MASK 0x0004 /* CLK_SYS_ENA */
+#define WM8903_CLK_SYS_ENA_SHIFT 2 /* CLK_SYS_ENA */
+#define WM8903_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */
+#define WM8903_CLK_DSP_ENA 0x0002 /* CLK_DSP_ENA */
+#define WM8903_CLK_DSP_ENA_MASK 0x0002 /* CLK_DSP_ENA */
+#define WM8903_CLK_DSP_ENA_SHIFT 1 /* CLK_DSP_ENA */
+#define WM8903_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */
+#define WM8903_TO_ENA 0x0001 /* TO_ENA */
+#define WM8903_TO_ENA_MASK 0x0001 /* TO_ENA */
+#define WM8903_TO_ENA_SHIFT 0 /* TO_ENA */
+#define WM8903_TO_ENA_WIDTH 1 /* TO_ENA */
+
+/*
+ * R24 (0x18) - Audio Interface 0
+ */
+#define WM8903_DACL_DATINV 0x1000 /* DACL_DATINV */
+#define WM8903_DACL_DATINV_MASK 0x1000 /* DACL_DATINV */
+#define WM8903_DACL_DATINV_SHIFT 12 /* DACL_DATINV */
+#define WM8903_DACL_DATINV_WIDTH 1 /* DACL_DATINV */
+#define WM8903_DACR_DATINV 0x0800 /* DACR_DATINV */
+#define WM8903_DACR_DATINV_MASK 0x0800 /* DACR_DATINV */
+#define WM8903_DACR_DATINV_SHIFT 11 /* DACR_DATINV */
+#define WM8903_DACR_DATINV_WIDTH 1 /* DACR_DATINV */
+#define WM8903_DAC_BOOST_MASK 0x0600 /* DAC_BOOST - [10:9] */
+#define WM8903_DAC_BOOST_SHIFT 9 /* DAC_BOOST - [10:9] */
+#define WM8903_DAC_BOOST_WIDTH 2 /* DAC_BOOST - [10:9] */
+#define WM8903_LOOPBACK 0x0100 /* LOOPBACK */
+#define WM8903_LOOPBACK_MASK 0x0100 /* LOOPBACK */
+#define WM8903_LOOPBACK_SHIFT 8 /* LOOPBACK */
+#define WM8903_LOOPBACK_WIDTH 1 /* LOOPBACK */
+#define WM8903_AIFADCL_SRC 0x0080 /* AIFADCL_SRC */
+#define WM8903_AIFADCL_SRC_MASK 0x0080 /* AIFADCL_SRC */
+#define WM8903_AIFADCL_SRC_SHIFT 7 /* AIFADCL_SRC */
+#define WM8903_AIFADCL_SRC_WIDTH 1 /* AIFADCL_SRC */
+#define WM8903_AIFADCR_SRC 0x0040 /* AIFADCR_SRC */
+#define WM8903_AIFADCR_SRC_MASK 0x0040 /* AIFADCR_SRC */
+#define WM8903_AIFADCR_SRC_SHIFT 6 /* AIFADCR_SRC */
+#define WM8903_AIFADCR_SRC_WIDTH 1 /* AIFADCR_SRC */
+#define WM8903_AIFDACL_SRC 0x0020 /* AIFDACL_SRC */
+#define WM8903_AIFDACL_SRC_MASK 0x0020 /* AIFDACL_SRC */
+#define WM8903_AIFDACL_SRC_SHIFT 5 /* AIFDACL_SRC */
+#define WM8903_AIFDACL_SRC_WIDTH 1 /* AIFDACL_SRC */
+#define WM8903_AIFDACR_SRC 0x0010 /* AIFDACR_SRC */
+#define WM8903_AIFDACR_SRC_MASK 0x0010 /* AIFDACR_SRC */
+#define WM8903_AIFDACR_SRC_SHIFT 4 /* AIFDACR_SRC */
+#define WM8903_AIFDACR_SRC_WIDTH 1 /* AIFDACR_SRC */
+#define WM8903_ADC_COMP 0x0008 /* ADC_COMP */
+#define WM8903_ADC_COMP_MASK 0x0008 /* ADC_COMP */
+#define WM8903_ADC_COMP_SHIFT 3 /* ADC_COMP */
+#define WM8903_ADC_COMP_WIDTH 1 /* ADC_COMP */
+#define WM8903_ADC_COMPMODE 0x0004 /* ADC_COMPMODE */
+#define WM8903_ADC_COMPMODE_MASK 0x0004 /* ADC_COMPMODE */
+#define WM8903_ADC_COMPMODE_SHIFT 2 /* ADC_COMPMODE */
+#define WM8903_ADC_COMPMODE_WIDTH 1 /* ADC_COMPMODE */
+#define WM8903_DAC_COMP 0x0002 /* DAC_COMP */
+#define WM8903_DAC_COMP_MASK 0x0002 /* DAC_COMP */
+#define WM8903_DAC_COMP_SHIFT 1 /* DAC_COMP */
+#define WM8903_DAC_COMP_WIDTH 1 /* DAC_COMP */
+#define WM8903_DAC_COMPMODE 0x0001 /* DAC_COMPMODE */
+#define WM8903_DAC_COMPMODE_MASK 0x0001 /* DAC_COMPMODE */
+#define WM8903_DAC_COMPMODE_SHIFT 0 /* DAC_COMPMODE */
+#define WM8903_DAC_COMPMODE_WIDTH 1 /* DAC_COMPMODE */
+
+/*
+ * R25 (0x19) - Audio Interface 1
+ */
+#define WM8903_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */
+#define WM8903_AIFDAC_TDM_MASK 0x2000 /* AIFDAC_TDM */
+#define WM8903_AIFDAC_TDM_SHIFT 13 /* AIFDAC_TDM */
+#define WM8903_AIFDAC_TDM_WIDTH 1 /* AIFDAC_TDM */
+#define WM8903_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */
+#define WM8903_AIFDAC_TDM_CHAN_MASK 0x1000 /* AIFDAC_TDM_CHAN */
+#define WM8903_AIFDAC_TDM_CHAN_SHIFT 12 /* AIFDAC_TDM_CHAN */
+#define WM8903_AIFDAC_TDM_CHAN_WIDTH 1 /* AIFDAC_TDM_CHAN */
+#define WM8903_AIFADC_TDM 0x0800 /* AIFADC_TDM */
+#define WM8903_AIFADC_TDM_MASK 0x0800 /* AIFADC_TDM */
+#define WM8903_AIFADC_TDM_SHIFT 11 /* AIFADC_TDM */
+#define WM8903_AIFADC_TDM_WIDTH 1 /* AIFADC_TDM */
+#define WM8903_AIFADC_TDM_CHAN 0x0400 /* AIFADC_TDM_CHAN */
+#define WM8903_AIFADC_TDM_CHAN_MASK 0x0400 /* AIFADC_TDM_CHAN */
+#define WM8903_AIFADC_TDM_CHAN_SHIFT 10 /* AIFADC_TDM_CHAN */
+#define WM8903_AIFADC_TDM_CHAN_WIDTH 1 /* AIFADC_TDM_CHAN */
+#define WM8903_LRCLK_DIR 0x0200 /* LRCLK_DIR */
+#define WM8903_LRCLK_DIR_MASK 0x0200 /* LRCLK_DIR */
+#define WM8903_LRCLK_DIR_SHIFT 9 /* LRCLK_DIR */
+#define WM8903_LRCLK_DIR_WIDTH 1 /* LRCLK_DIR */
+#define WM8903_AIF_BCLK_INV 0x0080 /* AIF_BCLK_INV */
+#define WM8903_AIF_BCLK_INV_MASK 0x0080 /* AIF_BCLK_INV */
+#define WM8903_AIF_BCLK_INV_SHIFT 7 /* AIF_BCLK_INV */
+#define WM8903_AIF_BCLK_INV_WIDTH 1 /* AIF_BCLK_INV */
+#define WM8903_BCLK_DIR 0x0040 /* BCLK_DIR */
+#define WM8903_BCLK_DIR_MASK 0x0040 /* BCLK_DIR */
+#define WM8903_BCLK_DIR_SHIFT 6 /* BCLK_DIR */
+#define WM8903_BCLK_DIR_WIDTH 1 /* BCLK_DIR */
+#define WM8903_AIF_LRCLK_INV 0x0010 /* AIF_LRCLK_INV */
+#define WM8903_AIF_LRCLK_INV_MASK 0x0010 /* AIF_LRCLK_INV */
+#define WM8903_AIF_LRCLK_INV_SHIFT 4 /* AIF_LRCLK_INV */
+#define WM8903_AIF_LRCLK_INV_WIDTH 1 /* AIF_LRCLK_INV */
+#define WM8903_AIF_WL_MASK 0x000C /* AIF_WL - [3:2] */
+#define WM8903_AIF_WL_SHIFT 2 /* AIF_WL - [3:2] */
+#define WM8903_AIF_WL_WIDTH 2 /* AIF_WL - [3:2] */
+#define WM8903_AIF_FMT_MASK 0x0003 /* AIF_FMT - [1:0] */
+#define WM8903_AIF_FMT_SHIFT 0 /* AIF_FMT - [1:0] */
+#define WM8903_AIF_FMT_WIDTH 2 /* AIF_FMT - [1:0] */
+
+/*
+ * R26 (0x1A) - Audio Interface 2
+ */
+#define WM8903_BCLK_DIV_MASK 0x001F /* BCLK_DIV - [4:0] */
+#define WM8903_BCLK_DIV_SHIFT 0 /* BCLK_DIV - [4:0] */
+#define WM8903_BCLK_DIV_WIDTH 5 /* BCLK_DIV - [4:0] */
+
+/*
+ * R27 (0x1B) - Audio Interface 3
+ */
+#define WM8903_LRCLK_RATE_MASK 0x07FF /* LRCLK_RATE - [10:0] */
+#define WM8903_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [10:0] */
+#define WM8903_LRCLK_RATE_WIDTH 11 /* LRCLK_RATE - [10:0] */
+
+/*
+ * R30 (0x1E) - DAC Digital Volume Left
+ */
+#define WM8903_DACVU 0x0100 /* DACVU */
+#define WM8903_DACVU_MASK 0x0100 /* DACVU */
+#define WM8903_DACVU_SHIFT 8 /* DACVU */
+#define WM8903_DACVU_WIDTH 1 /* DACVU */
+#define WM8903_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */
+#define WM8903_DACL_VOL_SHIFT 0 /* DACL_VOL - [7:0] */
+#define WM8903_DACL_VOL_WIDTH 8 /* DACL_VOL - [7:0] */
+
+/*
+ * R31 (0x1F) - DAC Digital Volume Right
+ */
+#define WM8903_DACVU 0x0100 /* DACVU */
+#define WM8903_DACVU_MASK 0x0100 /* DACVU */
+#define WM8903_DACVU_SHIFT 8 /* DACVU */
+#define WM8903_DACVU_WIDTH 1 /* DACVU */
+#define WM8903_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */
+#define WM8903_DACR_VOL_SHIFT 0 /* DACR_VOL - [7:0] */
+#define WM8903_DACR_VOL_WIDTH 8 /* DACR_VOL - [7:0] */
+
+/*
+ * R32 (0x20) - DAC Digital 0
+ */
+#define WM8903_ADCL_DAC_SVOL_MASK 0x0F00 /* ADCL_DAC_SVOL - [11:8] */
+#define WM8903_ADCL_DAC_SVOL_SHIFT 8 /* ADCL_DAC_SVOL - [11:8] */
+#define WM8903_ADCL_DAC_SVOL_WIDTH 4 /* ADCL_DAC_SVOL - [11:8] */
+#define WM8903_ADCR_DAC_SVOL_MASK 0x00F0 /* ADCR_DAC_SVOL - [7:4] */
+#define WM8903_ADCR_DAC_SVOL_SHIFT 4 /* ADCR_DAC_SVOL - [7:4] */
+#define WM8903_ADCR_DAC_SVOL_WIDTH 4 /* ADCR_DAC_SVOL - [7:4] */
+#define WM8903_ADC_TO_DACL_MASK 0x000C /* ADC_TO_DACL - [3:2] */
+#define WM8903_ADC_TO_DACL_SHIFT 2 /* ADC_TO_DACL - [3:2] */
+#define WM8903_ADC_TO_DACL_WIDTH 2 /* ADC_TO_DACL - [3:2] */
+#define WM8903_ADC_TO_DACR_MASK 0x0003 /* ADC_TO_DACR - [1:0] */
+#define WM8903_ADC_TO_DACR_SHIFT 0 /* ADC_TO_DACR - [1:0] */
+#define WM8903_ADC_TO_DACR_WIDTH 2 /* ADC_TO_DACR - [1:0] */
+
+/*
+ * R33 (0x21) - DAC Digital 1
+ */
+#define WM8903_DAC_MONO 0x1000 /* DAC_MONO */
+#define WM8903_DAC_MONO_MASK 0x1000 /* DAC_MONO */
+#define WM8903_DAC_MONO_SHIFT 12 /* DAC_MONO */
+#define WM8903_DAC_MONO_WIDTH 1 /* DAC_MONO */
+#define WM8903_DAC_SB_FILT 0x0800 /* DAC_SB_FILT */
+#define WM8903_DAC_SB_FILT_MASK 0x0800 /* DAC_SB_FILT */
+#define WM8903_DAC_SB_FILT_SHIFT 11 /* DAC_SB_FILT */
+#define WM8903_DAC_SB_FILT_WIDTH 1 /* DAC_SB_FILT */
+#define WM8903_DAC_MUTERATE 0x0400 /* DAC_MUTERATE */
+#define WM8903_DAC_MUTERATE_MASK 0x0400 /* DAC_MUTERATE */
+#define WM8903_DAC_MUTERATE_SHIFT 10 /* DAC_MUTERATE */
+#define WM8903_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */
+#define WM8903_DAC_MUTEMODE 0x0200 /* DAC_MUTEMODE */
+#define WM8903_DAC_MUTEMODE_MASK 0x0200 /* DAC_MUTEMODE */
+#define WM8903_DAC_MUTEMODE_SHIFT 9 /* DAC_MUTEMODE */
+#define WM8903_DAC_MUTEMODE_WIDTH 1 /* DAC_MUTEMODE */
+#define WM8903_DAC_MUTE 0x0008 /* DAC_MUTE */
+#define WM8903_DAC_MUTE_MASK 0x0008 /* DAC_MUTE */
+#define WM8903_DAC_MUTE_SHIFT 3 /* DAC_MUTE */
+#define WM8903_DAC_MUTE_WIDTH 1 /* DAC_MUTE */
+#define WM8903_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */
+#define WM8903_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */
+#define WM8903_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */
+
+/*
+ * R36 (0x24) - ADC Digital Volume Left
+ */
+#define WM8903_ADCVU 0x0100 /* ADCVU */
+#define WM8903_ADCVU_MASK 0x0100 /* ADCVU */
+#define WM8903_ADCVU_SHIFT 8 /* ADCVU */
+#define WM8903_ADCVU_WIDTH 1 /* ADCVU */
+#define WM8903_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */
+#define WM8903_ADCL_VOL_SHIFT 0 /* ADCL_VOL - [7:0] */
+#define WM8903_ADCL_VOL_WIDTH 8 /* ADCL_VOL - [7:0] */
+
+/*
+ * R37 (0x25) - ADC Digital Volume Right
+ */
+#define WM8903_ADCVU 0x0100 /* ADCVU */
+#define WM8903_ADCVU_MASK 0x0100 /* ADCVU */
+#define WM8903_ADCVU_SHIFT 8 /* ADCVU */
+#define WM8903_ADCVU_WIDTH 1 /* ADCVU */
+#define WM8903_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */
+#define WM8903_ADCR_VOL_SHIFT 0 /* ADCR_VOL - [7:0] */
+#define WM8903_ADCR_VOL_WIDTH 8 /* ADCR_VOL - [7:0] */
+
+/*
+ * R38 (0x26) - ADC Digital 0
+ */
+#define WM8903_ADC_HPF_CUT_MASK 0x0060 /* ADC_HPF_CUT - [6:5] */
+#define WM8903_ADC_HPF_CUT_SHIFT 5 /* ADC_HPF_CUT - [6:5] */
+#define WM8903_ADC_HPF_CUT_WIDTH 2 /* ADC_HPF_CUT - [6:5] */
+#define WM8903_ADC_HPF_ENA 0x0010 /* ADC_HPF_ENA */
+#define WM8903_ADC_HPF_ENA_MASK 0x0010 /* ADC_HPF_ENA */
+#define WM8903_ADC_HPF_ENA_SHIFT 4 /* ADC_HPF_ENA */
+#define WM8903_ADC_HPF_ENA_WIDTH 1 /* ADC_HPF_ENA */
+#define WM8903_ADCL_DATINV 0x0002 /* ADCL_DATINV */
+#define WM8903_ADCL_DATINV_MASK 0x0002 /* ADCL_DATINV */
+#define WM8903_ADCL_DATINV_SHIFT 1 /* ADCL_DATINV */
+#define WM8903_ADCL_DATINV_WIDTH 1 /* ADCL_DATINV */
+#define WM8903_ADCR_DATINV 0x0001 /* ADCR_DATINV */
+#define WM8903_ADCR_DATINV_MASK 0x0001 /* ADCR_DATINV */
+#define WM8903_ADCR_DATINV_SHIFT 0 /* ADCR_DATINV */
+#define WM8903_ADCR_DATINV_WIDTH 1 /* ADCR_DATINV */
+
+/*
+ * R39 (0x27) - Digital Microphone 0
+ */
+#define WM8903_DIGMIC_MODE_SEL 0x0100 /* DIGMIC_MODE_SEL */
+#define WM8903_DIGMIC_MODE_SEL_MASK 0x0100 /* DIGMIC_MODE_SEL */
+#define WM8903_DIGMIC_MODE_SEL_SHIFT 8 /* DIGMIC_MODE_SEL */
+#define WM8903_DIGMIC_MODE_SEL_WIDTH 1 /* DIGMIC_MODE_SEL */
+#define WM8903_DIGMIC_CLK_SEL_L_MASK 0x00C0 /* DIGMIC_CLK_SEL_L - [7:6] */
+#define WM8903_DIGMIC_CLK_SEL_L_SHIFT 6 /* DIGMIC_CLK_SEL_L - [7:6] */
+#define WM8903_DIGMIC_CLK_SEL_L_WIDTH 2 /* DIGMIC_CLK_SEL_L - [7:6] */
+#define WM8903_DIGMIC_CLK_SEL_R_MASK 0x0030 /* DIGMIC_CLK_SEL_R - [5:4] */
+#define WM8903_DIGMIC_CLK_SEL_R_SHIFT 4 /* DIGMIC_CLK_SEL_R - [5:4] */
+#define WM8903_DIGMIC_CLK_SEL_R_WIDTH 2 /* DIGMIC_CLK_SEL_R - [5:4] */
+#define WM8903_DIGMIC_CLK_SEL_RT_MASK 0x000C /* DIGMIC_CLK_SEL_RT - [3:2] */
+#define WM8903_DIGMIC_CLK_SEL_RT_SHIFT 2 /* DIGMIC_CLK_SEL_RT - [3:2] */
+#define WM8903_DIGMIC_CLK_SEL_RT_WIDTH 2 /* DIGMIC_CLK_SEL_RT - [3:2] */
+#define WM8903_DIGMIC_CLK_SEL_MASK 0x0003 /* DIGMIC_CLK_SEL - [1:0] */
+#define WM8903_DIGMIC_CLK_SEL_SHIFT 0 /* DIGMIC_CLK_SEL - [1:0] */
+#define WM8903_DIGMIC_CLK_SEL_WIDTH 2 /* DIGMIC_CLK_SEL - [1:0] */
+
+/*
+ * R40 (0x28) - DRC 0
+ */
+#define WM8903_DRC_ENA 0x8000 /* DRC_ENA */
+#define WM8903_DRC_ENA_MASK 0x8000 /* DRC_ENA */
+#define WM8903_DRC_ENA_SHIFT 15 /* DRC_ENA */
+#define WM8903_DRC_ENA_WIDTH 1 /* DRC_ENA */
+#define WM8903_DRC_THRESH_HYST_MASK 0x1800 /* DRC_THRESH_HYST - [12:11] */
+#define WM8903_DRC_THRESH_HYST_SHIFT 11 /* DRC_THRESH_HYST - [12:11] */
+#define WM8903_DRC_THRESH_HYST_WIDTH 2 /* DRC_THRESH_HYST - [12:11] */
+#define WM8903_DRC_STARTUP_GAIN_MASK 0x07C0 /* DRC_STARTUP_GAIN - [10:6] */
+#define WM8903_DRC_STARTUP_GAIN_SHIFT 6 /* DRC_STARTUP_GAIN - [10:6] */
+#define WM8903_DRC_STARTUP_GAIN_WIDTH 5 /* DRC_STARTUP_GAIN - [10:6] */
+#define WM8903_DRC_FF_DELAY 0x0020 /* DRC_FF_DELAY */
+#define WM8903_DRC_FF_DELAY_MASK 0x0020 /* DRC_FF_DELAY */
+#define WM8903_DRC_FF_DELAY_SHIFT 5 /* DRC_FF_DELAY */
+#define WM8903_DRC_FF_DELAY_WIDTH 1 /* DRC_FF_DELAY */
+#define WM8903_DRC_SMOOTH_ENA 0x0008 /* DRC_SMOOTH_ENA */
+#define WM8903_DRC_SMOOTH_ENA_MASK 0x0008 /* DRC_SMOOTH_ENA */
+#define WM8903_DRC_SMOOTH_ENA_SHIFT 3 /* DRC_SMOOTH_ENA */
+#define WM8903_DRC_SMOOTH_ENA_WIDTH 1 /* DRC_SMOOTH_ENA */
+#define WM8903_DRC_QR_ENA 0x0004 /* DRC_QR_ENA */
+#define WM8903_DRC_QR_ENA_MASK 0x0004 /* DRC_QR_ENA */
+#define WM8903_DRC_QR_ENA_SHIFT 2 /* DRC_QR_ENA */
+#define WM8903_DRC_QR_ENA_WIDTH 1 /* DRC_QR_ENA */
+#define WM8903_DRC_ANTICLIP_ENA 0x0002 /* DRC_ANTICLIP_ENA */
+#define WM8903_DRC_ANTICLIP_ENA_MASK 0x0002 /* DRC_ANTICLIP_ENA */
+#define WM8903_DRC_ANTICLIP_ENA_SHIFT 1 /* DRC_ANTICLIP_ENA */
+#define WM8903_DRC_ANTICLIP_ENA_WIDTH 1 /* DRC_ANTICLIP_ENA */
+#define WM8903_DRC_HYST_ENA 0x0001 /* DRC_HYST_ENA */
+#define WM8903_DRC_HYST_ENA_MASK 0x0001 /* DRC_HYST_ENA */
+#define WM8903_DRC_HYST_ENA_SHIFT 0 /* DRC_HYST_ENA */
+#define WM8903_DRC_HYST_ENA_WIDTH 1 /* DRC_HYST_ENA */
+
+/*
+ * R41 (0x29) - DRC 1
+ */
+#define WM8903_DRC_ATTACK_RATE_MASK 0xF000 /* DRC_ATTACK_RATE - [15:12] */
+#define WM8903_DRC_ATTACK_RATE_SHIFT 12 /* DRC_ATTACK_RATE - [15:12] */
+#define WM8903_DRC_ATTACK_RATE_WIDTH 4 /* DRC_ATTACK_RATE - [15:12] */
+#define WM8903_DRC_DECAY_RATE_MASK 0x0F00 /* DRC_DECAY_RATE - [11:8] */
+#define WM8903_DRC_DECAY_RATE_SHIFT 8 /* DRC_DECAY_RATE - [11:8] */
+#define WM8903_DRC_DECAY_RATE_WIDTH 4 /* DRC_DECAY_RATE - [11:8] */
+#define WM8903_DRC_THRESH_QR_MASK 0x00C0 /* DRC_THRESH_QR - [7:6] */
+#define WM8903_DRC_THRESH_QR_SHIFT 6 /* DRC_THRESH_QR - [7:6] */
+#define WM8903_DRC_THRESH_QR_WIDTH 2 /* DRC_THRESH_QR - [7:6] */
+#define WM8903_DRC_RATE_QR_MASK 0x0030 /* DRC_RATE_QR - [5:4] */
+#define WM8903_DRC_RATE_QR_SHIFT 4 /* DRC_RATE_QR - [5:4] */
+#define WM8903_DRC_RATE_QR_WIDTH 2 /* DRC_RATE_QR - [5:4] */
+#define WM8903_DRC_MINGAIN_MASK 0x000C /* DRC_MINGAIN - [3:2] */
+#define WM8903_DRC_MINGAIN_SHIFT 2 /* DRC_MINGAIN - [3:2] */
+#define WM8903_DRC_MINGAIN_WIDTH 2 /* DRC_MINGAIN - [3:2] */
+#define WM8903_DRC_MAXGAIN_MASK 0x0003 /* DRC_MAXGAIN - [1:0] */
+#define WM8903_DRC_MAXGAIN_SHIFT 0 /* DRC_MAXGAIN - [1:0] */
+#define WM8903_DRC_MAXGAIN_WIDTH 2 /* DRC_MAXGAIN - [1:0] */
+
+/*
+ * R42 (0x2A) - DRC 2
+ */
+#define WM8903_DRC_R0_SLOPE_COMP_MASK 0x0038 /* DRC_R0_SLOPE_COMP - [5:3] */
+#define WM8903_DRC_R0_SLOPE_COMP_SHIFT 3 /* DRC_R0_SLOPE_COMP - [5:3] */
+#define WM8903_DRC_R0_SLOPE_COMP_WIDTH 3 /* DRC_R0_SLOPE_COMP - [5:3] */
+#define WM8903_DRC_R1_SLOPE_COMP_MASK 0x0007 /* DRC_R1_SLOPE_COMP - [2:0] */
+#define WM8903_DRC_R1_SLOPE_COMP_SHIFT 0 /* DRC_R1_SLOPE_COMP - [2:0] */
+#define WM8903_DRC_R1_SLOPE_COMP_WIDTH 3 /* DRC_R1_SLOPE_COMP - [2:0] */
+
+/*
+ * R43 (0x2B) - DRC 3
+ */
+#define WM8903_DRC_THRESH_COMP_MASK 0x07E0 /* DRC_THRESH_COMP - [10:5] */
+#define WM8903_DRC_THRESH_COMP_SHIFT 5 /* DRC_THRESH_COMP - [10:5] */
+#define WM8903_DRC_THRESH_COMP_WIDTH 6 /* DRC_THRESH_COMP - [10:5] */
+#define WM8903_DRC_AMP_COMP_MASK 0x001F /* DRC_AMP_COMP - [4:0] */
+#define WM8903_DRC_AMP_COMP_SHIFT 0 /* DRC_AMP_COMP - [4:0] */
+#define WM8903_DRC_AMP_COMP_WIDTH 5 /* DRC_AMP_COMP - [4:0] */
+
+/*
+ * R44 (0x2C) - Analogue Left Input 0
+ */
+#define WM8903_LINMUTE 0x0080 /* LINMUTE */
+#define WM8903_LINMUTE_MASK 0x0080 /* LINMUTE */
+#define WM8903_LINMUTE_SHIFT 7 /* LINMUTE */
+#define WM8903_LINMUTE_WIDTH 1 /* LINMUTE */
+#define WM8903_LIN_VOL_MASK 0x001F /* LIN_VOL - [4:0] */
+#define WM8903_LIN_VOL_SHIFT 0 /* LIN_VOL - [4:0] */
+#define WM8903_LIN_VOL_WIDTH 5 /* LIN_VOL - [4:0] */
+
+/*
+ * R45 (0x2D) - Analogue Right Input 0
+ */
+#define WM8903_RINMUTE 0x0080 /* RINMUTE */
+#define WM8903_RINMUTE_MASK 0x0080 /* RINMUTE */
+#define WM8903_RINMUTE_SHIFT 7 /* RINMUTE */
+#define WM8903_RINMUTE_WIDTH 1 /* RINMUTE */
+#define WM8903_RIN_VOL_MASK 0x001F /* RIN_VOL - [4:0] */
+#define WM8903_RIN_VOL_SHIFT 0 /* RIN_VOL - [4:0] */
+#define WM8903_RIN_VOL_WIDTH 5 /* RIN_VOL - [4:0] */
+
+/*
+ * R46 (0x2E) - Analogue Left Input 1
+ */
+#define WM8903_INL_CM_ENA 0x0040 /* INL_CM_ENA */
+#define WM8903_INL_CM_ENA_MASK 0x0040 /* INL_CM_ENA */
+#define WM8903_INL_CM_ENA_SHIFT 6 /* INL_CM_ENA */
+#define WM8903_INL_CM_ENA_WIDTH 1 /* INL_CM_ENA */
+#define WM8903_L_IP_SEL_N_MASK 0x0030 /* L_IP_SEL_N - [5:4] */
+#define WM8903_L_IP_SEL_N_SHIFT 4 /* L_IP_SEL_N - [5:4] */
+#define WM8903_L_IP_SEL_N_WIDTH 2 /* L_IP_SEL_N - [5:4] */
+#define WM8903_L_IP_SEL_P_MASK 0x000C /* L_IP_SEL_P - [3:2] */
+#define WM8903_L_IP_SEL_P_SHIFT 2 /* L_IP_SEL_P - [3:2] */
+#define WM8903_L_IP_SEL_P_WIDTH 2 /* L_IP_SEL_P - [3:2] */
+#define WM8903_L_MODE_MASK 0x0003 /* L_MODE - [1:0] */
+#define WM8903_L_MODE_SHIFT 0 /* L_MODE - [1:0] */
+#define WM8903_L_MODE_WIDTH 2 /* L_MODE - [1:0] */
+
+/*
+ * R47 (0x2F) - Analogue Right Input 1
+ */
+#define WM8903_INR_CM_ENA 0x0040 /* INR_CM_ENA */
+#define WM8903_INR_CM_ENA_MASK 0x0040 /* INR_CM_ENA */
+#define WM8903_INR_CM_ENA_SHIFT 6 /* INR_CM_ENA */
+#define WM8903_INR_CM_ENA_WIDTH 1 /* INR_CM_ENA */
+#define WM8903_R_IP_SEL_N_MASK 0x0030 /* R_IP_SEL_N - [5:4] */
+#define WM8903_R_IP_SEL_N_SHIFT 4 /* R_IP_SEL_N - [5:4] */
+#define WM8903_R_IP_SEL_N_WIDTH 2 /* R_IP_SEL_N - [5:4] */
+#define WM8903_R_IP_SEL_P_MASK 0x000C /* R_IP_SEL_P - [3:2] */
+#define WM8903_R_IP_SEL_P_SHIFT 2 /* R_IP_SEL_P - [3:2] */
+#define WM8903_R_IP_SEL_P_WIDTH 2 /* R_IP_SEL_P - [3:2] */
+#define WM8903_R_MODE_MASK 0x0003 /* R_MODE - [1:0] */
+#define WM8903_R_MODE_SHIFT 0 /* R_MODE - [1:0] */
+#define WM8903_R_MODE_WIDTH 2 /* R_MODE - [1:0] */
+
+/*
+ * R50 (0x32) - Analogue Left Mix 0
+ */
+#define WM8903_DACL_TO_MIXOUTL 0x0008 /* DACL_TO_MIXOUTL */
+#define WM8903_DACL_TO_MIXOUTL_MASK 0x0008 /* DACL_TO_MIXOUTL */
+#define WM8903_DACL_TO_MIXOUTL_SHIFT 3 /* DACL_TO_MIXOUTL */
+#define WM8903_DACL_TO_MIXOUTL_WIDTH 1 /* DACL_TO_MIXOUTL */
+#define WM8903_DACR_TO_MIXOUTL 0x0004 /* DACR_TO_MIXOUTL */
+#define WM8903_DACR_TO_MIXOUTL_MASK 0x0004 /* DACR_TO_MIXOUTL */
+#define WM8903_DACR_TO_MIXOUTL_SHIFT 2 /* DACR_TO_MIXOUTL */
+#define WM8903_DACR_TO_MIXOUTL_WIDTH 1 /* DACR_TO_MIXOUTL */
+#define WM8903_BYPASSL_TO_MIXOUTL 0x0002 /* BYPASSL_TO_MIXOUTL */
+#define WM8903_BYPASSL_TO_MIXOUTL_MASK 0x0002 /* BYPASSL_TO_MIXOUTL */
+#define WM8903_BYPASSL_TO_MIXOUTL_SHIFT 1 /* BYPASSL_TO_MIXOUTL */
+#define WM8903_BYPASSL_TO_MIXOUTL_WIDTH 1 /* BYPASSL_TO_MIXOUTL */
+#define WM8903_BYPASSR_TO_MIXOUTL 0x0001 /* BYPASSR_TO_MIXOUTL */
+#define WM8903_BYPASSR_TO_MIXOUTL_MASK 0x0001 /* BYPASSR_TO_MIXOUTL */
+#define WM8903_BYPASSR_TO_MIXOUTL_SHIFT 0 /* BYPASSR_TO_MIXOUTL */
+#define WM8903_BYPASSR_TO_MIXOUTL_WIDTH 1 /* BYPASSR_TO_MIXOUTL */
+
+/*
+ * R51 (0x33) - Analogue Right Mix 0
+ */
+#define WM8903_DACL_TO_MIXOUTR 0x0008 /* DACL_TO_MIXOUTR */
+#define WM8903_DACL_TO_MIXOUTR_MASK 0x0008 /* DACL_TO_MIXOUTR */
+#define WM8903_DACL_TO_MIXOUTR_SHIFT 3 /* DACL_TO_MIXOUTR */
+#define WM8903_DACL_TO_MIXOUTR_WIDTH 1 /* DACL_TO_MIXOUTR */
+#define WM8903_DACR_TO_MIXOUTR 0x0004 /* DACR_TO_MIXOUTR */
+#define WM8903_DACR_TO_MIXOUTR_MASK 0x0004 /* DACR_TO_MIXOUTR */
+#define WM8903_DACR_TO_MIXOUTR_SHIFT 2 /* DACR_TO_MIXOUTR */
+#define WM8903_DACR_TO_MIXOUTR_WIDTH 1 /* DACR_TO_MIXOUTR */
+#define WM8903_BYPASSL_TO_MIXOUTR 0x0002 /* BYPASSL_TO_MIXOUTR */
+#define WM8903_BYPASSL_TO_MIXOUTR_MASK 0x0002 /* BYPASSL_TO_MIXOUTR */
+#define WM8903_BYPASSL_TO_MIXOUTR_SHIFT 1 /* BYPASSL_TO_MIXOUTR */
+#define WM8903_BYPASSL_TO_MIXOUTR_WIDTH 1 /* BYPASSL_TO_MIXOUTR */
+#define WM8903_BYPASSR_TO_MIXOUTR 0x0001 /* BYPASSR_TO_MIXOUTR */
+#define WM8903_BYPASSR_TO_MIXOUTR_MASK 0x0001 /* BYPASSR_TO_MIXOUTR */
+#define WM8903_BYPASSR_TO_MIXOUTR_SHIFT 0 /* BYPASSR_TO_MIXOUTR */
+#define WM8903_BYPASSR_TO_MIXOUTR_WIDTH 1 /* BYPASSR_TO_MIXOUTR */
+
+/*
+ * R52 (0x34) - Analogue Spk Mix Left 0
+ */
+#define WM8903_DACL_TO_MIXSPKL 0x0008 /* DACL_TO_MIXSPKL */
+#define WM8903_DACL_TO_MIXSPKL_MASK 0x0008 /* DACL_TO_MIXSPKL */
+#define WM8903_DACL_TO_MIXSPKL_SHIFT 3 /* DACL_TO_MIXSPKL */
+#define WM8903_DACL_TO_MIXSPKL_WIDTH 1 /* DACL_TO_MIXSPKL */
+#define WM8903_DACR_TO_MIXSPKL 0x0004 /* DACR_TO_MIXSPKL */
+#define WM8903_DACR_TO_MIXSPKL_MASK 0x0004 /* DACR_TO_MIXSPKL */
+#define WM8903_DACR_TO_MIXSPKL_SHIFT 2 /* DACR_TO_MIXSPKL */
+#define WM8903_DACR_TO_MIXSPKL_WIDTH 1 /* DACR_TO_MIXSPKL */
+#define WM8903_BYPASSL_TO_MIXSPKL 0x0002 /* BYPASSL_TO_MIXSPKL */
+#define WM8903_BYPASSL_TO_MIXSPKL_MASK 0x0002 /* BYPASSL_TO_MIXSPKL */
+#define WM8903_BYPASSL_TO_MIXSPKL_SHIFT 1 /* BYPASSL_TO_MIXSPKL */
+#define WM8903_BYPASSL_TO_MIXSPKL_WIDTH 1 /* BYPASSL_TO_MIXSPKL */
+#define WM8903_BYPASSR_TO_MIXSPKL 0x0001 /* BYPASSR_TO_MIXSPKL */
+#define WM8903_BYPASSR_TO_MIXSPKL_MASK 0x0001 /* BYPASSR_TO_MIXSPKL */
+#define WM8903_BYPASSR_TO_MIXSPKL_SHIFT 0 /* BYPASSR_TO_MIXSPKL */
+#define WM8903_BYPASSR_TO_MIXSPKL_WIDTH 1 /* BYPASSR_TO_MIXSPKL */
+
+/*
+ * R53 (0x35) - Analogue Spk Mix Left 1
+ */
+#define WM8903_DACL_MIXSPKL_VOL 0x0008 /* DACL_MIXSPKL_VOL */
+#define WM8903_DACL_MIXSPKL_VOL_MASK 0x0008 /* DACL_MIXSPKL_VOL */
+#define WM8903_DACL_MIXSPKL_VOL_SHIFT 3 /* DACL_MIXSPKL_VOL */
+#define WM8903_DACL_MIXSPKL_VOL_WIDTH 1 /* DACL_MIXSPKL_VOL */
+#define WM8903_DACR_MIXSPKL_VOL 0x0004 /* DACR_MIXSPKL_VOL */
+#define WM8903_DACR_MIXSPKL_VOL_MASK 0x0004 /* DACR_MIXSPKL_VOL */
+#define WM8903_DACR_MIXSPKL_VOL_SHIFT 2 /* DACR_MIXSPKL_VOL */
+#define WM8903_DACR_MIXSPKL_VOL_WIDTH 1 /* DACR_MIXSPKL_VOL */
+#define WM8903_BYPASSL_MIXSPKL_VOL 0x0002 /* BYPASSL_MIXSPKL_VOL */
+#define WM8903_BYPASSL_MIXSPKL_VOL_MASK 0x0002 /* BYPASSL_MIXSPKL_VOL */
+#define WM8903_BYPASSL_MIXSPKL_VOL_SHIFT 1 /* BYPASSL_MIXSPKL_VOL */
+#define WM8903_BYPASSL_MIXSPKL_VOL_WIDTH 1 /* BYPASSL_MIXSPKL_VOL */
+#define WM8903_BYPASSR_MIXSPKL_VOL 0x0001 /* BYPASSR_MIXSPKL_VOL */
+#define WM8903_BYPASSR_MIXSPKL_VOL_MASK 0x0001 /* BYPASSR_MIXSPKL_VOL */
+#define WM8903_BYPASSR_MIXSPKL_VOL_SHIFT 0 /* BYPASSR_MIXSPKL_VOL */
+#define WM8903_BYPASSR_MIXSPKL_VOL_WIDTH 1 /* BYPASSR_MIXSPKL_VOL */
+
+/*
+ * R54 (0x36) - Analogue Spk Mix Right 0
+ */
+#define WM8903_DACL_TO_MIXSPKR 0x0008 /* DACL_TO_MIXSPKR */
+#define WM8903_DACL_TO_MIXSPKR_MASK 0x0008 /* DACL_TO_MIXSPKR */
+#define WM8903_DACL_TO_MIXSPKR_SHIFT 3 /* DACL_TO_MIXSPKR */
+#define WM8903_DACL_TO_MIXSPKR_WIDTH 1 /* DACL_TO_MIXSPKR */
+#define WM8903_DACR_TO_MIXSPKR 0x0004 /* DACR_TO_MIXSPKR */
+#define WM8903_DACR_TO_MIXSPKR_MASK 0x0004 /* DACR_TO_MIXSPKR */
+#define WM8903_DACR_TO_MIXSPKR_SHIFT 2 /* DACR_TO_MIXSPKR */
+#define WM8903_DACR_TO_MIXSPKR_WIDTH 1 /* DACR_TO_MIXSPKR */
+#define WM8903_BYPASSL_TO_MIXSPKR 0x0002 /* BYPASSL_TO_MIXSPKR */
+#define WM8903_BYPASSL_TO_MIXSPKR_MASK 0x0002 /* BYPASSL_TO_MIXSPKR */
+#define WM8903_BYPASSL_TO_MIXSPKR_SHIFT 1 /* BYPASSL_TO_MIXSPKR */
+#define WM8903_BYPASSL_TO_MIXSPKR_WIDTH 1 /* BYPASSL_TO_MIXSPKR */
+#define WM8903_BYPASSR_TO_MIXSPKR 0x0001 /* BYPASSR_TO_MIXSPKR */
+#define WM8903_BYPASSR_TO_MIXSPKR_MASK 0x0001 /* BYPASSR_TO_MIXSPKR */
+#define WM8903_BYPASSR_TO_MIXSPKR_SHIFT 0 /* BYPASSR_TO_MIXSPKR */
+#define WM8903_BYPASSR_TO_MIXSPKR_WIDTH 1 /* BYPASSR_TO_MIXSPKR */
+
+/*
+ * R55 (0x37) - Analogue Spk Mix Right 1
+ */
+#define WM8903_DACL_MIXSPKR_VOL 0x0008 /* DACL_MIXSPKR_VOL */
+#define WM8903_DACL_MIXSPKR_VOL_MASK 0x0008 /* DACL_MIXSPKR_VOL */
+#define WM8903_DACL_MIXSPKR_VOL_SHIFT 3 /* DACL_MIXSPKR_VOL */
+#define WM8903_DACL_MIXSPKR_VOL_WIDTH 1 /* DACL_MIXSPKR_VOL */
+#define WM8903_DACR_MIXSPKR_VOL 0x0004 /* DACR_MIXSPKR_VOL */
+#define WM8903_DACR_MIXSPKR_VOL_MASK 0x0004 /* DACR_MIXSPKR_VOL */
+#define WM8903_DACR_MIXSPKR_VOL_SHIFT 2 /* DACR_MIXSPKR_VOL */
+#define WM8903_DACR_MIXSPKR_VOL_WIDTH 1 /* DACR_MIXSPKR_VOL */
+#define WM8903_BYPASSL_MIXSPKR_VOL 0x0002 /* BYPASSL_MIXSPKR_VOL */
+#define WM8903_BYPASSL_MIXSPKR_VOL_MASK 0x0002 /* BYPASSL_MIXSPKR_VOL */
+#define WM8903_BYPASSL_MIXSPKR_VOL_SHIFT 1 /* BYPASSL_MIXSPKR_VOL */
+#define WM8903_BYPASSL_MIXSPKR_VOL_WIDTH 1 /* BYPASSL_MIXSPKR_VOL */
+#define WM8903_BYPASSR_MIXSPKR_VOL 0x0001 /* BYPASSR_MIXSPKR_VOL */
+#define WM8903_BYPASSR_MIXSPKR_VOL_MASK 0x0001 /* BYPASSR_MIXSPKR_VOL */
+#define WM8903_BYPASSR_MIXSPKR_VOL_SHIFT 0 /* BYPASSR_MIXSPKR_VOL */
+#define WM8903_BYPASSR_MIXSPKR_VOL_WIDTH 1 /* BYPASSR_MIXSPKR_VOL */
+
+/*
+ * R57 (0x39) - Analogue OUT1 Left
+ */
+#define WM8903_HPL_MUTE 0x0100 /* HPL_MUTE */
+#define WM8903_HPL_MUTE_MASK 0x0100 /* HPL_MUTE */
+#define WM8903_HPL_MUTE_SHIFT 8 /* HPL_MUTE */
+#define WM8903_HPL_MUTE_WIDTH 1 /* HPL_MUTE */
+#define WM8903_HPOUTVU 0x0080 /* HPOUTVU */
+#define WM8903_HPOUTVU_MASK 0x0080 /* HPOUTVU */
+#define WM8903_HPOUTVU_SHIFT 7 /* HPOUTVU */
+#define WM8903_HPOUTVU_WIDTH 1 /* HPOUTVU */
+#define WM8903_HPOUTLZC 0x0040 /* HPOUTLZC */
+#define WM8903_HPOUTLZC_MASK 0x0040 /* HPOUTLZC */
+#define WM8903_HPOUTLZC_SHIFT 6 /* HPOUTLZC */
+#define WM8903_HPOUTLZC_WIDTH 1 /* HPOUTLZC */
+#define WM8903_HPOUTL_VOL_MASK 0x003F /* HPOUTL_VOL - [5:0] */
+#define WM8903_HPOUTL_VOL_SHIFT 0 /* HPOUTL_VOL - [5:0] */
+#define WM8903_HPOUTL_VOL_WIDTH 6 /* HPOUTL_VOL - [5:0] */
+
+/*
+ * R58 (0x3A) - Analogue OUT1 Right
+ */
+#define WM8903_HPR_MUTE 0x0100 /* HPR_MUTE */
+#define WM8903_HPR_MUTE_MASK 0x0100 /* HPR_MUTE */
+#define WM8903_HPR_MUTE_SHIFT 8 /* HPR_MUTE */
+#define WM8903_HPR_MUTE_WIDTH 1 /* HPR_MUTE */
+#define WM8903_HPOUTVU 0x0080 /* HPOUTVU */
+#define WM8903_HPOUTVU_MASK 0x0080 /* HPOUTVU */
+#define WM8903_HPOUTVU_SHIFT 7 /* HPOUTVU */
+#define WM8903_HPOUTVU_WIDTH 1 /* HPOUTVU */
+#define WM8903_HPOUTRZC 0x0040 /* HPOUTRZC */
+#define WM8903_HPOUTRZC_MASK 0x0040 /* HPOUTRZC */
+#define WM8903_HPOUTRZC_SHIFT 6 /* HPOUTRZC */
+#define WM8903_HPOUTRZC_WIDTH 1 /* HPOUTRZC */
+#define WM8903_HPOUTR_VOL_MASK 0x003F /* HPOUTR_VOL - [5:0] */
+#define WM8903_HPOUTR_VOL_SHIFT 0 /* HPOUTR_VOL - [5:0] */
+#define WM8903_HPOUTR_VOL_WIDTH 6 /* HPOUTR_VOL - [5:0] */
+
+/*
+ * R59 (0x3B) - Analogue OUT2 Left
+ */
+#define WM8903_LINEOUTL_MUTE 0x0100 /* LINEOUTL_MUTE */
+#define WM8903_LINEOUTL_MUTE_MASK 0x0100 /* LINEOUTL_MUTE */
+#define WM8903_LINEOUTL_MUTE_SHIFT 8 /* LINEOUTL_MUTE */
+#define WM8903_LINEOUTL_MUTE_WIDTH 1 /* LINEOUTL_MUTE */
+#define WM8903_LINEOUTVU 0x0080 /* LINEOUTVU */
+#define WM8903_LINEOUTVU_MASK 0x0080 /* LINEOUTVU */
+#define WM8903_LINEOUTVU_SHIFT 7 /* LINEOUTVU */
+#define WM8903_LINEOUTVU_WIDTH 1 /* LINEOUTVU */
+#define WM8903_LINEOUTLZC 0x0040 /* LINEOUTLZC */
+#define WM8903_LINEOUTLZC_MASK 0x0040 /* LINEOUTLZC */
+#define WM8903_LINEOUTLZC_SHIFT 6 /* LINEOUTLZC */
+#define WM8903_LINEOUTLZC_WIDTH 1 /* LINEOUTLZC */
+#define WM8903_LINEOUTL_VOL_MASK 0x003F /* LINEOUTL_VOL - [5:0] */
+#define WM8903_LINEOUTL_VOL_SHIFT 0 /* LINEOUTL_VOL - [5:0] */
+#define WM8903_LINEOUTL_VOL_WIDTH 6 /* LINEOUTL_VOL - [5:0] */
+
+/*
+ * R60 (0x3C) - Analogue OUT2 Right
+ */
+#define WM8903_LINEOUTR_MUTE 0x0100 /* LINEOUTR_MUTE */
+#define WM8903_LINEOUTR_MUTE_MASK 0x0100 /* LINEOUTR_MUTE */
+#define WM8903_LINEOUTR_MUTE_SHIFT 8 /* LINEOUTR_MUTE */
+#define WM8903_LINEOUTR_MUTE_WIDTH 1 /* LINEOUTR_MUTE */
+#define WM8903_LINEOUTVU 0x0080 /* LINEOUTVU */
+#define WM8903_LINEOUTVU_MASK 0x0080 /* LINEOUTVU */
+#define WM8903_LINEOUTVU_SHIFT 7 /* LINEOUTVU */
+#define WM8903_LINEOUTVU_WIDTH 1 /* LINEOUTVU */
+#define WM8903_LINEOUTRZC 0x0040 /* LINEOUTRZC */
+#define WM8903_LINEOUTRZC_MASK 0x0040 /* LINEOUTRZC */
+#define WM8903_LINEOUTRZC_SHIFT 6 /* LINEOUTRZC */
+#define WM8903_LINEOUTRZC_WIDTH 1 /* LINEOUTRZC */
+#define WM8903_LINEOUTR_VOL_MASK 0x003F /* LINEOUTR_VOL - [5:0] */
+#define WM8903_LINEOUTR_VOL_SHIFT 0 /* LINEOUTR_VOL - [5:0] */
+#define WM8903_LINEOUTR_VOL_WIDTH 6 /* LINEOUTR_VOL - [5:0] */
+
+/*
+ * R62 (0x3E) - Analogue OUT3 Left
+ */
+#define WM8903_SPKL_MUTE 0x0100 /* SPKL_MUTE */
+#define WM8903_SPKL_MUTE_MASK 0x0100 /* SPKL_MUTE */
+#define WM8903_SPKL_MUTE_SHIFT 8 /* SPKL_MUTE */
+#define WM8903_SPKL_MUTE_WIDTH 1 /* SPKL_MUTE */
+#define WM8903_SPKVU 0x0080 /* SPKVU */
+#define WM8903_SPKVU_MASK 0x0080 /* SPKVU */
+#define WM8903_SPKVU_SHIFT 7 /* SPKVU */
+#define WM8903_SPKVU_WIDTH 1 /* SPKVU */
+#define WM8903_SPKLZC 0x0040 /* SPKLZC */
+#define WM8903_SPKLZC_MASK 0x0040 /* SPKLZC */
+#define WM8903_SPKLZC_SHIFT 6 /* SPKLZC */
+#define WM8903_SPKLZC_WIDTH 1 /* SPKLZC */
+#define WM8903_SPKL_VOL_MASK 0x003F /* SPKL_VOL - [5:0] */
+#define WM8903_SPKL_VOL_SHIFT 0 /* SPKL_VOL - [5:0] */
+#define WM8903_SPKL_VOL_WIDTH 6 /* SPKL_VOL - [5:0] */
+
+/*
+ * R63 (0x3F) - Analogue OUT3 Right
+ */
+#define WM8903_SPKR_MUTE 0x0100 /* SPKR_MUTE */
+#define WM8903_SPKR_MUTE_MASK 0x0100 /* SPKR_MUTE */
+#define WM8903_SPKR_MUTE_SHIFT 8 /* SPKR_MUTE */
+#define WM8903_SPKR_MUTE_WIDTH 1 /* SPKR_MUTE */
+#define WM8903_SPKVU 0x0080 /* SPKVU */
+#define WM8903_SPKVU_MASK 0x0080 /* SPKVU */
+#define WM8903_SPKVU_SHIFT 7 /* SPKVU */
+#define WM8903_SPKVU_WIDTH 1 /* SPKVU */
+#define WM8903_SPKRZC 0x0040 /* SPKRZC */
+#define WM8903_SPKRZC_MASK 0x0040 /* SPKRZC */
+#define WM8903_SPKRZC_SHIFT 6 /* SPKRZC */
+#define WM8903_SPKRZC_WIDTH 1 /* SPKRZC */
+#define WM8903_SPKR_VOL_MASK 0x003F /* SPKR_VOL - [5:0] */
+#define WM8903_SPKR_VOL_SHIFT 0 /* SPKR_VOL - [5:0] */
+#define WM8903_SPKR_VOL_WIDTH 6 /* SPKR_VOL - [5:0] */
+
+/*
+ * R65 (0x41) - Analogue SPK Output Control 0
+ */
+#define WM8903_SPK_DISCHARGE 0x0002 /* SPK_DISCHARGE */
+#define WM8903_SPK_DISCHARGE_MASK 0x0002 /* SPK_DISCHARGE */
+#define WM8903_SPK_DISCHARGE_SHIFT 1 /* SPK_DISCHARGE */
+#define WM8903_SPK_DISCHARGE_WIDTH 1 /* SPK_DISCHARGE */
+#define WM8903_VROI 0x0001 /* VROI */
+#define WM8903_VROI_MASK 0x0001 /* VROI */
+#define WM8903_VROI_SHIFT 0 /* VROI */
+#define WM8903_VROI_WIDTH 1 /* VROI */
+
+/*
+ * R67 (0x43) - DC Servo 0
+ */
+#define WM8903_DCS_MASTER_ENA 0x0010 /* DCS_MASTER_ENA */
+#define WM8903_DCS_MASTER_ENA_MASK 0x0010 /* DCS_MASTER_ENA */
+#define WM8903_DCS_MASTER_ENA_SHIFT 4 /* DCS_MASTER_ENA */
+#define WM8903_DCS_MASTER_ENA_WIDTH 1 /* DCS_MASTER_ENA */
+#define WM8903_DCS_ENA_MASK 0x000F /* DCS_ENA - [3:0] */
+#define WM8903_DCS_ENA_SHIFT 0 /* DCS_ENA - [3:0] */
+#define WM8903_DCS_ENA_WIDTH 4 /* DCS_ENA - [3:0] */
+
+/*
+ * R69 (0x45) - DC Servo 2
+ */
+#define WM8903_DCS_MODE_MASK 0x0003 /* DCS_MODE - [1:0] */
+#define WM8903_DCS_MODE_SHIFT 0 /* DCS_MODE - [1:0] */
+#define WM8903_DCS_MODE_WIDTH 2 /* DCS_MODE - [1:0] */
+
+/*
+ * R90 (0x5A) - Analogue HP 0
+ */
+#define WM8903_HPL_RMV_SHORT 0x0080 /* HPL_RMV_SHORT */
+#define WM8903_HPL_RMV_SHORT_MASK 0x0080 /* HPL_RMV_SHORT */
+#define WM8903_HPL_RMV_SHORT_SHIFT 7 /* HPL_RMV_SHORT */
+#define WM8903_HPL_RMV_SHORT_WIDTH 1 /* HPL_RMV_SHORT */
+#define WM8903_HPL_ENA_OUTP 0x0040 /* HPL_ENA_OUTP */
+#define WM8903_HPL_ENA_OUTP_MASK 0x0040 /* HPL_ENA_OUTP */
+#define WM8903_HPL_ENA_OUTP_SHIFT 6 /* HPL_ENA_OUTP */
+#define WM8903_HPL_ENA_OUTP_WIDTH 1 /* HPL_ENA_OUTP */
+#define WM8903_HPL_ENA_DLY 0x0020 /* HPL_ENA_DLY */
+#define WM8903_HPL_ENA_DLY_MASK 0x0020 /* HPL_ENA_DLY */
+#define WM8903_HPL_ENA_DLY_SHIFT 5 /* HPL_ENA_DLY */
+#define WM8903_HPL_ENA_DLY_WIDTH 1 /* HPL_ENA_DLY */
+#define WM8903_HPL_ENA 0x0010 /* HPL_ENA */
+#define WM8903_HPL_ENA_MASK 0x0010 /* HPL_ENA */
+#define WM8903_HPL_ENA_SHIFT 4 /* HPL_ENA */
+#define WM8903_HPL_ENA_WIDTH 1 /* HPL_ENA */
+#define WM8903_HPR_RMV_SHORT 0x0008 /* HPR_RMV_SHORT */
+#define WM8903_HPR_RMV_SHORT_MASK 0x0008 /* HPR_RMV_SHORT */
+#define WM8903_HPR_RMV_SHORT_SHIFT 3 /* HPR_RMV_SHORT */
+#define WM8903_HPR_RMV_SHORT_WIDTH 1 /* HPR_RMV_SHORT */
+#define WM8903_HPR_ENA_OUTP 0x0004 /* HPR_ENA_OUTP */
+#define WM8903_HPR_ENA_OUTP_MASK 0x0004 /* HPR_ENA_OUTP */
+#define WM8903_HPR_ENA_OUTP_SHIFT 2 /* HPR_ENA_OUTP */
+#define WM8903_HPR_ENA_OUTP_WIDTH 1 /* HPR_ENA_OUTP */
+#define WM8903_HPR_ENA_DLY 0x0002 /* HPR_ENA_DLY */
+#define WM8903_HPR_ENA_DLY_MASK 0x0002 /* HPR_ENA_DLY */
+#define WM8903_HPR_ENA_DLY_SHIFT 1 /* HPR_ENA_DLY */
+#define WM8903_HPR_ENA_DLY_WIDTH 1 /* HPR_ENA_DLY */
+#define WM8903_HPR_ENA 0x0001 /* HPR_ENA */
+#define WM8903_HPR_ENA_MASK 0x0001 /* HPR_ENA */
+#define WM8903_HPR_ENA_SHIFT 0 /* HPR_ENA */
+#define WM8903_HPR_ENA_WIDTH 1 /* HPR_ENA */
+
+/*
+ * R94 (0x5E) - Analogue Lineout 0
+ */
+#define WM8903_LINEOUTL_RMV_SHORT 0x0080 /* LINEOUTL_RMV_SHORT */
+#define WM8903_LINEOUTL_RMV_SHORT_MASK 0x0080 /* LINEOUTL_RMV_SHORT */
+#define WM8903_LINEOUTL_RMV_SHORT_SHIFT 7 /* LINEOUTL_RMV_SHORT */
+#define WM8903_LINEOUTL_RMV_SHORT_WIDTH 1 /* LINEOUTL_RMV_SHORT */
+#define WM8903_LINEOUTL_ENA_OUTP 0x0040 /* LINEOUTL_ENA_OUTP */
+#define WM8903_LINEOUTL_ENA_OUTP_MASK 0x0040 /* LINEOUTL_ENA_OUTP */
+#define WM8903_LINEOUTL_ENA_OUTP_SHIFT 6 /* LINEOUTL_ENA_OUTP */
+#define WM8903_LINEOUTL_ENA_OUTP_WIDTH 1 /* LINEOUTL_ENA_OUTP */
+#define WM8903_LINEOUTL_ENA_DLY 0x0020 /* LINEOUTL_ENA_DLY */
+#define WM8903_LINEOUTL_ENA_DLY_MASK 0x0020 /* LINEOUTL_ENA_DLY */
+#define WM8903_LINEOUTL_ENA_DLY_SHIFT 5 /* LINEOUTL_ENA_DLY */
+#define WM8903_LINEOUTL_ENA_DLY_WIDTH 1 /* LINEOUTL_ENA_DLY */
+#define WM8903_LINEOUTL_ENA 0x0010 /* LINEOUTL_ENA */
+#define WM8903_LINEOUTL_ENA_MASK 0x0010 /* LINEOUTL_ENA */
+#define WM8903_LINEOUTL_ENA_SHIFT 4 /* LINEOUTL_ENA */
+#define WM8903_LINEOUTL_ENA_WIDTH 1 /* LINEOUTL_ENA */
+#define WM8903_LINEOUTR_RMV_SHORT 0x0008 /* LINEOUTR_RMV_SHORT */
+#define WM8903_LINEOUTR_RMV_SHORT_MASK 0x0008 /* LINEOUTR_RMV_SHORT */
+#define WM8903_LINEOUTR_RMV_SHORT_SHIFT 3 /* LINEOUTR_RMV_SHORT */
+#define WM8903_LINEOUTR_RMV_SHORT_WIDTH 1 /* LINEOUTR_RMV_SHORT */
+#define WM8903_LINEOUTR_ENA_OUTP 0x0004 /* LINEOUTR_ENA_OUTP */
+#define WM8903_LINEOUTR_ENA_OUTP_MASK 0x0004 /* LINEOUTR_ENA_OUTP */
+#define WM8903_LINEOUTR_ENA_OUTP_SHIFT 2 /* LINEOUTR_ENA_OUTP */
+#define WM8903_LINEOUTR_ENA_OUTP_WIDTH 1 /* LINEOUTR_ENA_OUTP */
+#define WM8903_LINEOUTR_ENA_DLY 0x0002 /* LINEOUTR_ENA_DLY */
+#define WM8903_LINEOUTR_ENA_DLY_MASK 0x0002 /* LINEOUTR_ENA_DLY */
+#define WM8903_LINEOUTR_ENA_DLY_SHIFT 1 /* LINEOUTR_ENA_DLY */
+#define WM8903_LINEOUTR_ENA_DLY_WIDTH 1 /* LINEOUTR_ENA_DLY */
+#define WM8903_LINEOUTR_ENA 0x0001 /* LINEOUTR_ENA */
+#define WM8903_LINEOUTR_ENA_MASK 0x0001 /* LINEOUTR_ENA */
+#define WM8903_LINEOUTR_ENA_SHIFT 0 /* LINEOUTR_ENA */
+#define WM8903_LINEOUTR_ENA_WIDTH 1 /* LINEOUTR_ENA */
+
+/*
+ * R98 (0x62) - Charge Pump 0
+ */
+#define WM8903_CP_ENA 0x0001 /* CP_ENA */
+#define WM8903_CP_ENA_MASK 0x0001 /* CP_ENA */
+#define WM8903_CP_ENA_SHIFT 0 /* CP_ENA */
+#define WM8903_CP_ENA_WIDTH 1 /* CP_ENA */
+
+/*
+ * R104 (0x68) - Class W 0
+ */
+#define WM8903_CP_DYN_FREQ 0x0002 /* CP_DYN_FREQ */
+#define WM8903_CP_DYN_FREQ_MASK 0x0002 /* CP_DYN_FREQ */
+#define WM8903_CP_DYN_FREQ_SHIFT 1 /* CP_DYN_FREQ */
+#define WM8903_CP_DYN_FREQ_WIDTH 1 /* CP_DYN_FREQ */
+#define WM8903_CP_DYN_V 0x0001 /* CP_DYN_V */
+#define WM8903_CP_DYN_V_MASK 0x0001 /* CP_DYN_V */
+#define WM8903_CP_DYN_V_SHIFT 0 /* CP_DYN_V */
+#define WM8903_CP_DYN_V_WIDTH 1 /* CP_DYN_V */
+
+/*
+ * R108 (0x6C) - Write Sequencer 0
+ */
+#define WM8903_WSEQ_ENA 0x0100 /* WSEQ_ENA */
+#define WM8903_WSEQ_ENA_MASK 0x0100 /* WSEQ_ENA */
+#define WM8903_WSEQ_ENA_SHIFT 8 /* WSEQ_ENA */
+#define WM8903_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */
+#define WM8903_WSEQ_WRITE_INDEX_MASK 0x001F /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8903_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8903_WSEQ_WRITE_INDEX_WIDTH 5 /* WSEQ_WRITE_INDEX - [4:0] */
+
+/*
+ * R109 (0x6D) - Write Sequencer 1
+ */
+#define WM8903_WSEQ_DATA_WIDTH_MASK 0x7000 /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8903_WSEQ_DATA_WIDTH_SHIFT 12 /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8903_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8903_WSEQ_DATA_START_MASK 0x0F00 /* WSEQ_DATA_START - [11:8] */
+#define WM8903_WSEQ_DATA_START_SHIFT 8 /* WSEQ_DATA_START - [11:8] */
+#define WM8903_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [11:8] */
+#define WM8903_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */
+#define WM8903_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */
+#define WM8903_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */
+
+/*
+ * R110 (0x6E) - Write Sequencer 2
+ */
+#define WM8903_WSEQ_EOS 0x4000 /* WSEQ_EOS */
+#define WM8903_WSEQ_EOS_MASK 0x4000 /* WSEQ_EOS */
+#define WM8903_WSEQ_EOS_SHIFT 14 /* WSEQ_EOS */
+#define WM8903_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */
+#define WM8903_WSEQ_DELAY_MASK 0x0F00 /* WSEQ_DELAY - [11:8] */
+#define WM8903_WSEQ_DELAY_SHIFT 8 /* WSEQ_DELAY - [11:8] */
+#define WM8903_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [11:8] */
+#define WM8903_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */
+#define WM8903_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */
+#define WM8903_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */
+
+/*
+ * R111 (0x6F) - Write Sequencer 3
+ */
+#define WM8903_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */
+#define WM8903_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */
+#define WM8903_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */
+#define WM8903_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */
+#define WM8903_WSEQ_START 0x0100 /* WSEQ_START */
+#define WM8903_WSEQ_START_MASK 0x0100 /* WSEQ_START */
+#define WM8903_WSEQ_START_SHIFT 8 /* WSEQ_START */
+#define WM8903_WSEQ_START_WIDTH 1 /* WSEQ_START */
+#define WM8903_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */
+#define WM8903_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */
+#define WM8903_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */
+
+/*
+ * R112 (0x70) - Write Sequencer 4
+ */
+#define WM8903_WSEQ_CURRENT_INDEX_MASK 0x03F0 /* WSEQ_CURRENT_INDEX - [9:4] */
+#define WM8903_WSEQ_CURRENT_INDEX_SHIFT 4 /* WSEQ_CURRENT_INDEX - [9:4] */
+#define WM8903_WSEQ_CURRENT_INDEX_WIDTH 6 /* WSEQ_CURRENT_INDEX - [9:4] */
+#define WM8903_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */
+#define WM8903_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */
+#define WM8903_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */
+#define WM8903_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */
+
+/*
+ * R114 (0x72) - Control Interface
+ */
+#define WM8903_MASK_WRITE_ENA 0x0001 /* MASK_WRITE_ENA */
+#define WM8903_MASK_WRITE_ENA_MASK 0x0001 /* MASK_WRITE_ENA */
+#define WM8903_MASK_WRITE_ENA_SHIFT 0 /* MASK_WRITE_ENA */
+#define WM8903_MASK_WRITE_ENA_WIDTH 1 /* MASK_WRITE_ENA */
+
+/*
+ * R116 (0x74) - GPIO Control 1
+ */
+#define WM8903_GP1_FN_MASK 0x1F00 /* GP1_FN - [12:8] */
+#define WM8903_GP1_FN_SHIFT 8 /* GP1_FN - [12:8] */
+#define WM8903_GP1_FN_WIDTH 5 /* GP1_FN - [12:8] */
+#define WM8903_GP1_DIR 0x0080 /* GP1_DIR */
+#define WM8903_GP1_DIR_MASK 0x0080 /* GP1_DIR */
+#define WM8903_GP1_DIR_SHIFT 7 /* GP1_DIR */
+#define WM8903_GP1_DIR_WIDTH 1 /* GP1_DIR */
+#define WM8903_GP1_OP_CFG 0x0040 /* GP1_OP_CFG */
+#define WM8903_GP1_OP_CFG_MASK 0x0040 /* GP1_OP_CFG */
+#define WM8903_GP1_OP_CFG_SHIFT 6 /* GP1_OP_CFG */
+#define WM8903_GP1_OP_CFG_WIDTH 1 /* GP1_OP_CFG */
+#define WM8903_GP1_IP_CFG 0x0020 /* GP1_IP_CFG */
+#define WM8903_GP1_IP_CFG_MASK 0x0020 /* GP1_IP_CFG */
+#define WM8903_GP1_IP_CFG_SHIFT 5 /* GP1_IP_CFG */
+#define WM8903_GP1_IP_CFG_WIDTH 1 /* GP1_IP_CFG */
+#define WM8903_GP1_LVL 0x0010 /* GP1_LVL */
+#define WM8903_GP1_LVL_MASK 0x0010 /* GP1_LVL */
+#define WM8903_GP1_LVL_SHIFT 4 /* GP1_LVL */
+#define WM8903_GP1_LVL_WIDTH 1 /* GP1_LVL */
+#define WM8903_GP1_PD 0x0008 /* GP1_PD */
+#define WM8903_GP1_PD_MASK 0x0008 /* GP1_PD */
+#define WM8903_GP1_PD_SHIFT 3 /* GP1_PD */
+#define WM8903_GP1_PD_WIDTH 1 /* GP1_PD */
+#define WM8903_GP1_PU 0x0004 /* GP1_PU */
+#define WM8903_GP1_PU_MASK 0x0004 /* GP1_PU */
+#define WM8903_GP1_PU_SHIFT 2 /* GP1_PU */
+#define WM8903_GP1_PU_WIDTH 1 /* GP1_PU */
+#define WM8903_GP1_INTMODE 0x0002 /* GP1_INTMODE */
+#define WM8903_GP1_INTMODE_MASK 0x0002 /* GP1_INTMODE */
+#define WM8903_GP1_INTMODE_SHIFT 1 /* GP1_INTMODE */
+#define WM8903_GP1_INTMODE_WIDTH 1 /* GP1_INTMODE */
+#define WM8903_GP1_DB 0x0001 /* GP1_DB */
+#define WM8903_GP1_DB_MASK 0x0001 /* GP1_DB */
+#define WM8903_GP1_DB_SHIFT 0 /* GP1_DB */
+#define WM8903_GP1_DB_WIDTH 1 /* GP1_DB */
+
+/*
+ * R117 (0x75) - GPIO Control 2
+ */
+#define WM8903_GP2_FN_MASK 0x1F00 /* GP2_FN - [12:8] */
+#define WM8903_GP2_FN_SHIFT 8 /* GP2_FN - [12:8] */
+#define WM8903_GP2_FN_WIDTH 5 /* GP2_FN - [12:8] */
+#define WM8903_GP2_DIR 0x0080 /* GP2_DIR */
+#define WM8903_GP2_DIR_MASK 0x0080 /* GP2_DIR */
+#define WM8903_GP2_DIR_SHIFT 7 /* GP2_DIR */
+#define WM8903_GP2_DIR_WIDTH 1 /* GP2_DIR */
+#define WM8903_GP2_OP_CFG 0x0040 /* GP2_OP_CFG */
+#define WM8903_GP2_OP_CFG_MASK 0x0040 /* GP2_OP_CFG */
+#define WM8903_GP2_OP_CFG_SHIFT 6 /* GP2_OP_CFG */
+#define WM8903_GP2_OP_CFG_WIDTH 1 /* GP2_OP_CFG */
+#define WM8903_GP2_IP_CFG 0x0020 /* GP2_IP_CFG */
+#define WM8903_GP2_IP_CFG_MASK 0x0020 /* GP2_IP_CFG */
+#define WM8903_GP2_IP_CFG_SHIFT 5 /* GP2_IP_CFG */
+#define WM8903_GP2_IP_CFG_WIDTH 1 /* GP2_IP_CFG */
+#define WM8903_GP2_LVL 0x0010 /* GP2_LVL */
+#define WM8903_GP2_LVL_MASK 0x0010 /* GP2_LVL */
+#define WM8903_GP2_LVL_SHIFT 4 /* GP2_LVL */
+#define WM8903_GP2_LVL_WIDTH 1 /* GP2_LVL */
+#define WM8903_GP2_PD 0x0008 /* GP2_PD */
+#define WM8903_GP2_PD_MASK 0x0008 /* GP2_PD */
+#define WM8903_GP2_PD_SHIFT 3 /* GP2_PD */
+#define WM8903_GP2_PD_WIDTH 1 /* GP2_PD */
+#define WM8903_GP2_PU 0x0004 /* GP2_PU */
+#define WM8903_GP2_PU_MASK 0x0004 /* GP2_PU */
+#define WM8903_GP2_PU_SHIFT 2 /* GP2_PU */
+#define WM8903_GP2_PU_WIDTH 1 /* GP2_PU */
+#define WM8903_GP2_INTMODE 0x0002 /* GP2_INTMODE */
+#define WM8903_GP2_INTMODE_MASK 0x0002 /* GP2_INTMODE */
+#define WM8903_GP2_INTMODE_SHIFT 1 /* GP2_INTMODE */
+#define WM8903_GP2_INTMODE_WIDTH 1 /* GP2_INTMODE */
+#define WM8903_GP2_DB 0x0001 /* GP2_DB */
+#define WM8903_GP2_DB_MASK 0x0001 /* GP2_DB */
+#define WM8903_GP2_DB_SHIFT 0 /* GP2_DB */
+#define WM8903_GP2_DB_WIDTH 1 /* GP2_DB */
+
+/*
+ * R118 (0x76) - GPIO Control 3
+ */
+#define WM8903_GP3_FN_MASK 0x1F00 /* GP3_FN - [12:8] */
+#define WM8903_GP3_FN_SHIFT 8 /* GP3_FN - [12:8] */
+#define WM8903_GP3_FN_WIDTH 5 /* GP3_FN - [12:8] */
+#define WM8903_GP3_DIR 0x0080 /* GP3_DIR */
+#define WM8903_GP3_DIR_MASK 0x0080 /* GP3_DIR */
+#define WM8903_GP3_DIR_SHIFT 7 /* GP3_DIR */
+#define WM8903_GP3_DIR_WIDTH 1 /* GP3_DIR */
+#define WM8903_GP3_OP_CFG 0x0040 /* GP3_OP_CFG */
+#define WM8903_GP3_OP_CFG_MASK 0x0040 /* GP3_OP_CFG */
+#define WM8903_GP3_OP_CFG_SHIFT 6 /* GP3_OP_CFG */
+#define WM8903_GP3_OP_CFG_WIDTH 1 /* GP3_OP_CFG */
+#define WM8903_GP3_IP_CFG 0x0020 /* GP3_IP_CFG */
+#define WM8903_GP3_IP_CFG_MASK 0x0020 /* GP3_IP_CFG */
+#define WM8903_GP3_IP_CFG_SHIFT 5 /* GP3_IP_CFG */
+#define WM8903_GP3_IP_CFG_WIDTH 1 /* GP3_IP_CFG */
+#define WM8903_GP3_LVL 0x0010 /* GP3_LVL */
+#define WM8903_GP3_LVL_MASK 0x0010 /* GP3_LVL */
+#define WM8903_GP3_LVL_SHIFT 4 /* GP3_LVL */
+#define WM8903_GP3_LVL_WIDTH 1 /* GP3_LVL */
+#define WM8903_GP3_PD 0x0008 /* GP3_PD */
+#define WM8903_GP3_PD_MASK 0x0008 /* GP3_PD */
+#define WM8903_GP3_PD_SHIFT 3 /* GP3_PD */
+#define WM8903_GP3_PD_WIDTH 1 /* GP3_PD */
+#define WM8903_GP3_PU 0x0004 /* GP3_PU */
+#define WM8903_GP3_PU_MASK 0x0004 /* GP3_PU */
+#define WM8903_GP3_PU_SHIFT 2 /* GP3_PU */
+#define WM8903_GP3_PU_WIDTH 1 /* GP3_PU */
+#define WM8903_GP3_INTMODE 0x0002 /* GP3_INTMODE */
+#define WM8903_GP3_INTMODE_MASK 0x0002 /* GP3_INTMODE */
+#define WM8903_GP3_INTMODE_SHIFT 1 /* GP3_INTMODE */
+#define WM8903_GP3_INTMODE_WIDTH 1 /* GP3_INTMODE */
+#define WM8903_GP3_DB 0x0001 /* GP3_DB */
+#define WM8903_GP3_DB_MASK 0x0001 /* GP3_DB */
+#define WM8903_GP3_DB_SHIFT 0 /* GP3_DB */
+#define WM8903_GP3_DB_WIDTH 1 /* GP3_DB */
+
+/*
+ * R119 (0x77) - GPIO Control 4
+ */
+#define WM8903_GP4_FN_MASK 0x1F00 /* GP4_FN - [12:8] */
+#define WM8903_GP4_FN_SHIFT 8 /* GP4_FN - [12:8] */
+#define WM8903_GP4_FN_WIDTH 5 /* GP4_FN - [12:8] */
+#define WM8903_GP4_DIR 0x0080 /* GP4_DIR */
+#define WM8903_GP4_DIR_MASK 0x0080 /* GP4_DIR */
+#define WM8903_GP4_DIR_SHIFT 7 /* GP4_DIR */
+#define WM8903_GP4_DIR_WIDTH 1 /* GP4_DIR */
+#define WM8903_GP4_OP_CFG 0x0040 /* GP4_OP_CFG */
+#define WM8903_GP4_OP_CFG_MASK 0x0040 /* GP4_OP_CFG */
+#define WM8903_GP4_OP_CFG_SHIFT 6 /* GP4_OP_CFG */
+#define WM8903_GP4_OP_CFG_WIDTH 1 /* GP4_OP_CFG */
+#define WM8903_GP4_IP_CFG 0x0020 /* GP4_IP_CFG */
+#define WM8903_GP4_IP_CFG_MASK 0x0020 /* GP4_IP_CFG */
+#define WM8903_GP4_IP_CFG_SHIFT 5 /* GP4_IP_CFG */
+#define WM8903_GP4_IP_CFG_WIDTH 1 /* GP4_IP_CFG */
+#define WM8903_GP4_LVL 0x0010 /* GP4_LVL */
+#define WM8903_GP4_LVL_MASK 0x0010 /* GP4_LVL */
+#define WM8903_GP4_LVL_SHIFT 4 /* GP4_LVL */
+#define WM8903_GP4_LVL_WIDTH 1 /* GP4_LVL */
+#define WM8903_GP4_PD 0x0008 /* GP4_PD */
+#define WM8903_GP4_PD_MASK 0x0008 /* GP4_PD */
+#define WM8903_GP4_PD_SHIFT 3 /* GP4_PD */
+#define WM8903_GP4_PD_WIDTH 1 /* GP4_PD */
+#define WM8903_GP4_PU 0x0004 /* GP4_PU */
+#define WM8903_GP4_PU_MASK 0x0004 /* GP4_PU */
+#define WM8903_GP4_PU_SHIFT 2 /* GP4_PU */
+#define WM8903_GP4_PU_WIDTH 1 /* GP4_PU */
+#define WM8903_GP4_INTMODE 0x0002 /* GP4_INTMODE */
+#define WM8903_GP4_INTMODE_MASK 0x0002 /* GP4_INTMODE */
+#define WM8903_GP4_INTMODE_SHIFT 1 /* GP4_INTMODE */
+#define WM8903_GP4_INTMODE_WIDTH 1 /* GP4_INTMODE */
+#define WM8903_GP4_DB 0x0001 /* GP4_DB */
+#define WM8903_GP4_DB_MASK 0x0001 /* GP4_DB */
+#define WM8903_GP4_DB_SHIFT 0 /* GP4_DB */
+#define WM8903_GP4_DB_WIDTH 1 /* GP4_DB */
+
+/*
+ * R120 (0x78) - GPIO Control 5
+ */
+#define WM8903_GP5_FN_MASK 0x1F00 /* GP5_FN - [12:8] */
+#define WM8903_GP5_FN_SHIFT 8 /* GP5_FN - [12:8] */
+#define WM8903_GP5_FN_WIDTH 5 /* GP5_FN - [12:8] */
+#define WM8903_GP5_DIR 0x0080 /* GP5_DIR */
+#define WM8903_GP5_DIR_MASK 0x0080 /* GP5_DIR */
+#define WM8903_GP5_DIR_SHIFT 7 /* GP5_DIR */
+#define WM8903_GP5_DIR_WIDTH 1 /* GP5_DIR */
+#define WM8903_GP5_OP_CFG 0x0040 /* GP5_OP_CFG */
+#define WM8903_GP5_OP_CFG_MASK 0x0040 /* GP5_OP_CFG */
+#define WM8903_GP5_OP_CFG_SHIFT 6 /* GP5_OP_CFG */
+#define WM8903_GP5_OP_CFG_WIDTH 1 /* GP5_OP_CFG */
+#define WM8903_GP5_IP_CFG 0x0020 /* GP5_IP_CFG */
+#define WM8903_GP5_IP_CFG_MASK 0x0020 /* GP5_IP_CFG */
+#define WM8903_GP5_IP_CFG_SHIFT 5 /* GP5_IP_CFG */
+#define WM8903_GP5_IP_CFG_WIDTH 1 /* GP5_IP_CFG */
+#define WM8903_GP5_LVL 0x0010 /* GP5_LVL */
+#define WM8903_GP5_LVL_MASK 0x0010 /* GP5_LVL */
+#define WM8903_GP5_LVL_SHIFT 4 /* GP5_LVL */
+#define WM8903_GP5_LVL_WIDTH 1 /* GP5_LVL */
+#define WM8903_GP5_PD 0x0008 /* GP5_PD */
+#define WM8903_GP5_PD_MASK 0x0008 /* GP5_PD */
+#define WM8903_GP5_PD_SHIFT 3 /* GP5_PD */
+#define WM8903_GP5_PD_WIDTH 1 /* GP5_PD */
+#define WM8903_GP5_PU 0x0004 /* GP5_PU */
+#define WM8903_GP5_PU_MASK 0x0004 /* GP5_PU */
+#define WM8903_GP5_PU_SHIFT 2 /* GP5_PU */
+#define WM8903_GP5_PU_WIDTH 1 /* GP5_PU */
+#define WM8903_GP5_INTMODE 0x0002 /* GP5_INTMODE */
+#define WM8903_GP5_INTMODE_MASK 0x0002 /* GP5_INTMODE */
+#define WM8903_GP5_INTMODE_SHIFT 1 /* GP5_INTMODE */
+#define WM8903_GP5_INTMODE_WIDTH 1 /* GP5_INTMODE */
+#define WM8903_GP5_DB 0x0001 /* GP5_DB */
+#define WM8903_GP5_DB_MASK 0x0001 /* GP5_DB */
+#define WM8903_GP5_DB_SHIFT 0 /* GP5_DB */
+#define WM8903_GP5_DB_WIDTH 1 /* GP5_DB */
+
+/*
+ * R121 (0x79) - Interrupt Status 1
+ */
+#define WM8903_MICSHRT_EINT 0x8000 /* MICSHRT_EINT */
+#define WM8903_MICSHRT_EINT_MASK 0x8000 /* MICSHRT_EINT */
+#define WM8903_MICSHRT_EINT_SHIFT 15 /* MICSHRT_EINT */
+#define WM8903_MICSHRT_EINT_WIDTH 1 /* MICSHRT_EINT */
+#define WM8903_MICDET_EINT 0x4000 /* MICDET_EINT */
+#define WM8903_MICDET_EINT_MASK 0x4000 /* MICDET_EINT */
+#define WM8903_MICDET_EINT_SHIFT 14 /* MICDET_EINT */
+#define WM8903_MICDET_EINT_WIDTH 1 /* MICDET_EINT */
+#define WM8903_WSEQ_BUSY_EINT 0x2000 /* WSEQ_BUSY_EINT */
+#define WM8903_WSEQ_BUSY_EINT_MASK 0x2000 /* WSEQ_BUSY_EINT */
+#define WM8903_WSEQ_BUSY_EINT_SHIFT 13 /* WSEQ_BUSY_EINT */
+#define WM8903_WSEQ_BUSY_EINT_WIDTH 1 /* WSEQ_BUSY_EINT */
+#define WM8903_GP5_EINT 0x0010 /* GP5_EINT */
+#define WM8903_GP5_EINT_MASK 0x0010 /* GP5_EINT */
+#define WM8903_GP5_EINT_SHIFT 4 /* GP5_EINT */
+#define WM8903_GP5_EINT_WIDTH 1 /* GP5_EINT */
+#define WM8903_GP4_EINT 0x0008 /* GP4_EINT */
+#define WM8903_GP4_EINT_MASK 0x0008 /* GP4_EINT */
+#define WM8903_GP4_EINT_SHIFT 3 /* GP4_EINT */
+#define WM8903_GP4_EINT_WIDTH 1 /* GP4_EINT */
+#define WM8903_GP3_EINT 0x0004 /* GP3_EINT */
+#define WM8903_GP3_EINT_MASK 0x0004 /* GP3_EINT */
+#define WM8903_GP3_EINT_SHIFT 2 /* GP3_EINT */
+#define WM8903_GP3_EINT_WIDTH 1 /* GP3_EINT */
+#define WM8903_GP2_EINT 0x0002 /* GP2_EINT */
+#define WM8903_GP2_EINT_MASK 0x0002 /* GP2_EINT */
+#define WM8903_GP2_EINT_SHIFT 1 /* GP2_EINT */
+#define WM8903_GP2_EINT_WIDTH 1 /* GP2_EINT */
+#define WM8903_GP1_EINT 0x0001 /* GP1_EINT */
+#define WM8903_GP1_EINT_MASK 0x0001 /* GP1_EINT */
+#define WM8903_GP1_EINT_SHIFT 0 /* GP1_EINT */
+#define WM8903_GP1_EINT_WIDTH 1 /* GP1_EINT */
+
+/*
+ * R122 (0x7A) - Interrupt Status 1 Mask
+ */
+#define WM8903_IM_MICSHRT_EINT 0x8000 /* IM_MICSHRT_EINT */
+#define WM8903_IM_MICSHRT_EINT_MASK 0x8000 /* IM_MICSHRT_EINT */
+#define WM8903_IM_MICSHRT_EINT_SHIFT 15 /* IM_MICSHRT_EINT */
+#define WM8903_IM_MICSHRT_EINT_WIDTH 1 /* IM_MICSHRT_EINT */
+#define WM8903_IM_MICDET_EINT 0x4000 /* IM_MICDET_EINT */
+#define WM8903_IM_MICDET_EINT_MASK 0x4000 /* IM_MICDET_EINT */
+#define WM8903_IM_MICDET_EINT_SHIFT 14 /* IM_MICDET_EINT */
+#define WM8903_IM_MICDET_EINT_WIDTH 1 /* IM_MICDET_EINT */
+#define WM8903_IM_WSEQ_BUSY_EINT 0x2000 /* IM_WSEQ_BUSY_EINT */
+#define WM8903_IM_WSEQ_BUSY_EINT_MASK 0x2000 /* IM_WSEQ_BUSY_EINT */
+#define WM8903_IM_WSEQ_BUSY_EINT_SHIFT 13 /* IM_WSEQ_BUSY_EINT */
+#define WM8903_IM_WSEQ_BUSY_EINT_WIDTH 1 /* IM_WSEQ_BUSY_EINT */
+#define WM8903_IM_GP5_EINT 0x0010 /* IM_GP5_EINT */
+#define WM8903_IM_GP5_EINT_MASK 0x0010 /* IM_GP5_EINT */
+#define WM8903_IM_GP5_EINT_SHIFT 4 /* IM_GP5_EINT */
+#define WM8903_IM_GP5_EINT_WIDTH 1 /* IM_GP5_EINT */
+#define WM8903_IM_GP4_EINT 0x0008 /* IM_GP4_EINT */
+#define WM8903_IM_GP4_EINT_MASK 0x0008 /* IM_GP4_EINT */
+#define WM8903_IM_GP4_EINT_SHIFT 3 /* IM_GP4_EINT */
+#define WM8903_IM_GP4_EINT_WIDTH 1 /* IM_GP4_EINT */
+#define WM8903_IM_GP3_EINT 0x0004 /* IM_GP3_EINT */
+#define WM8903_IM_GP3_EINT_MASK 0x0004 /* IM_GP3_EINT */
+#define WM8903_IM_GP3_EINT_SHIFT 2 /* IM_GP3_EINT */
+#define WM8903_IM_GP3_EINT_WIDTH 1 /* IM_GP3_EINT */
+#define WM8903_IM_GP2_EINT 0x0002 /* IM_GP2_EINT */
+#define WM8903_IM_GP2_EINT_MASK 0x0002 /* IM_GP2_EINT */
+#define WM8903_IM_GP2_EINT_SHIFT 1 /* IM_GP2_EINT */
+#define WM8903_IM_GP2_EINT_WIDTH 1 /* IM_GP2_EINT */
+#define WM8903_IM_GP1_EINT 0x0001 /* IM_GP1_EINT */
+#define WM8903_IM_GP1_EINT_MASK 0x0001 /* IM_GP1_EINT */
+#define WM8903_IM_GP1_EINT_SHIFT 0 /* IM_GP1_EINT */
+#define WM8903_IM_GP1_EINT_WIDTH 1 /* IM_GP1_EINT */
+
+/*
+ * R123 (0x7B) - Interrupt Polarity 1
+ */
+#define WM8903_MICSHRT_INV 0x8000 /* MICSHRT_INV */
+#define WM8903_MICSHRT_INV_MASK 0x8000 /* MICSHRT_INV */
+#define WM8903_MICSHRT_INV_SHIFT 15 /* MICSHRT_INV */
+#define WM8903_MICSHRT_INV_WIDTH 1 /* MICSHRT_INV */
+#define WM8903_MICDET_INV 0x4000 /* MICDET_INV */
+#define WM8903_MICDET_INV_MASK 0x4000 /* MICDET_INV */
+#define WM8903_MICDET_INV_SHIFT 14 /* MICDET_INV */
+#define WM8903_MICDET_INV_WIDTH 1 /* MICDET_INV */
+
+/*
+ * R126 (0x7E) - Interrupt Control
+ */
+#define WM8903_IRQ_POL 0x0001 /* IRQ_POL */
+#define WM8903_IRQ_POL_MASK 0x0001 /* IRQ_POL */
+#define WM8903_IRQ_POL_SHIFT 0 /* IRQ_POL */
+#define WM8903_IRQ_POL_WIDTH 1 /* IRQ_POL */
+
+/*
+ * R129 (0x81) - Control Interface Test 1
+ */
+#define WM8903_USER_KEY 0x0002 /* USER_KEY */
+#define WM8903_USER_KEY_MASK 0x0002 /* USER_KEY */
+#define WM8903_USER_KEY_SHIFT 1 /* USER_KEY */
+#define WM8903_USER_KEY_WIDTH 1 /* USER_KEY */
+#define WM8903_TEST_KEY 0x0001 /* TEST_KEY */
+#define WM8903_TEST_KEY_MASK 0x0001 /* TEST_KEY */
+#define WM8903_TEST_KEY_SHIFT 0 /* TEST_KEY */
+#define WM8903_TEST_KEY_WIDTH 1 /* TEST_KEY */
+
+/*
+ * R149 (0x95) - Charge Pump Test 1
+ */
+#define WM8903_CP_SW_KELVIN_MODE_MASK 0x0006 /* CP_SW_KELVIN_MODE - [2:1] */
+#define WM8903_CP_SW_KELVIN_MODE_SHIFT 1 /* CP_SW_KELVIN_MODE - [2:1] */
+#define WM8903_CP_SW_KELVIN_MODE_WIDTH 2 /* CP_SW_KELVIN_MODE - [2:1] */
+
+/*
+ * R164 (0xA4) - Clock Rate Test 4
+ */
+#define WM8903_ADC_DIG_MIC 0x0200 /* ADC_DIG_MIC */
+#define WM8903_ADC_DIG_MIC_MASK 0x0200 /* ADC_DIG_MIC */
+#define WM8903_ADC_DIG_MIC_SHIFT 9 /* ADC_DIG_MIC */
+#define WM8903_ADC_DIG_MIC_WIDTH 1 /* ADC_DIG_MIC */
+
+/*
+ * R172 (0xAC) - Analogue Output Bias 0
+ */
+#define WM8903_PGA_BIAS_MASK 0x0070 /* PGA_BIAS - [6:4] */
+#define WM8903_PGA_BIAS_SHIFT 4 /* PGA_BIAS - [6:4] */
+#define WM8903_PGA_BIAS_WIDTH 3 /* PGA_BIAS - [6:4] */
+
+#endif
diff --git a/sound/soc/codecs/wm8971.c b/sound/soc/codecs/wm8971.c
new file mode 100644
index 0000000..f41a578
--- /dev/null
+++ b/sound/soc/codecs/wm8971.c
@@ -0,0 +1,941 @@
+/*
+ * wm8971.c -- WM8971 ALSA SoC Audio driver
+ *
+ * Copyright 2005 Lab126, Inc.
+ *
+ * Author: Kenneth Kiraly <kiraly@lab126.com>
+ *
+ * Based on wm8753.c by Liam Girdwood
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.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 "wm8971.h"
+
+#define WM8971_VERSION "0.9"
+
+#define WM8971_REG_COUNT 43
+
+static struct workqueue_struct *wm8971_workq = NULL;
+
+/* codec private data */
+struct wm8971_priv {
+ unsigned int sysclk;
+};
+
+/*
+ * wm8971 register cache
+ * We can't read the WM8971 register space when we
+ * are using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8971_reg[] = {
+ 0x0097, 0x0097, 0x0079, 0x0079, /* 0 */
+ 0x0000, 0x0008, 0x0000, 0x000a, /* 4 */
+ 0x0000, 0x0000, 0x00ff, 0x00ff, /* 8 */
+ 0x000f, 0x000f, 0x0000, 0x0000, /* 12 */
+ 0x0000, 0x007b, 0x0000, 0x0032, /* 16 */
+ 0x0000, 0x00c3, 0x00c3, 0x00c0, /* 20 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 24 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 28 */
+ 0x0000, 0x0000, 0x0050, 0x0050, /* 32 */
+ 0x0050, 0x0050, 0x0050, 0x0050, /* 36 */
+ 0x0079, 0x0079, 0x0079, /* 40 */
+};
+
+static inline unsigned int wm8971_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg < WM8971_REG_COUNT)
+ return cache[reg];
+
+ return -1;
+}
+
+static inline void wm8971_write_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg < WM8971_REG_COUNT)
+ cache[reg] = value;
+}
+
+static int wm8971_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ /* data is
+ * D15..D9 WM8753 register offset
+ * D8...D0 register data
+ */
+ data[0] = (reg << 1) | ((value >> 8) & 0x0001);
+ data[1] = value & 0x00ff;
+
+ wm8971_write_reg_cache (codec, reg, value);
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+#define wm8971_reset(c) wm8971_write(c, WM8971_RESET, 0)
+
+/* WM8971 Controls */
+static const char *wm8971_bass[] = { "Linear Control", "Adaptive Boost" };
+static const char *wm8971_bass_filter[] = { "130Hz @ 48kHz",
+ "200Hz @ 48kHz" };
+static const char *wm8971_treble[] = { "8kHz", "4kHz" };
+static const char *wm8971_alc_func[] = { "Off", "Right", "Left", "Stereo" };
+static const char *wm8971_ng_type[] = { "Constant PGA Gain",
+ "Mute ADC Output" };
+static const char *wm8971_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" };
+static const char *wm8971_mono_mux[] = {"Stereo", "Mono (Left)",
+ "Mono (Right)", "Digital Mono"};
+static const char *wm8971_dac_phase[] = { "Non Inverted", "Inverted" };
+static const char *wm8971_lline_mux[] = {"Line", "NC", "NC", "PGA",
+ "Differential"};
+static const char *wm8971_rline_mux[] = {"Line", "Mic", "NC", "PGA",
+ "Differential"};
+static const char *wm8971_lpga_sel[] = {"Line", "NC", "NC", "Differential"};
+static const char *wm8971_rpga_sel[] = {"Line", "Mic", "NC", "Differential"};
+static const char *wm8971_adcpol[] = {"Normal", "L Invert", "R Invert",
+ "L + R Invert"};
+
+static const struct soc_enum wm8971_enum[] = {
+ SOC_ENUM_SINGLE(WM8971_BASS, 7, 2, wm8971_bass), /* 0 */
+ SOC_ENUM_SINGLE(WM8971_BASS, 6, 2, wm8971_bass_filter),
+ SOC_ENUM_SINGLE(WM8971_TREBLE, 6, 2, wm8971_treble),
+ SOC_ENUM_SINGLE(WM8971_ALC1, 7, 4, wm8971_alc_func),
+ SOC_ENUM_SINGLE(WM8971_NGATE, 1, 2, wm8971_ng_type), /* 4 */
+ SOC_ENUM_SINGLE(WM8971_ADCDAC, 1, 4, wm8971_deemp),
+ SOC_ENUM_SINGLE(WM8971_ADCTL1, 4, 4, wm8971_mono_mux),
+ SOC_ENUM_SINGLE(WM8971_ADCTL1, 1, 2, wm8971_dac_phase),
+ SOC_ENUM_SINGLE(WM8971_LOUTM1, 0, 5, wm8971_lline_mux), /* 8 */
+ SOC_ENUM_SINGLE(WM8971_ROUTM1, 0, 5, wm8971_rline_mux),
+ SOC_ENUM_SINGLE(WM8971_LADCIN, 6, 4, wm8971_lpga_sel),
+ SOC_ENUM_SINGLE(WM8971_RADCIN, 6, 4, wm8971_rpga_sel),
+ SOC_ENUM_SINGLE(WM8971_ADCDAC, 5, 4, wm8971_adcpol), /* 12 */
+ SOC_ENUM_SINGLE(WM8971_ADCIN, 6, 4, wm8971_mono_mux),
+};
+
+static const struct snd_kcontrol_new wm8971_snd_controls[] = {
+ SOC_DOUBLE_R("Capture Volume", WM8971_LINVOL, WM8971_RINVOL, 0, 63, 0),
+ SOC_DOUBLE_R("Capture ZC Switch", WM8971_LINVOL, WM8971_RINVOL,
+ 6, 1, 0),
+ SOC_DOUBLE_R("Capture Switch", WM8971_LINVOL, WM8971_RINVOL, 7, 1, 1),
+
+ SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8971_LOUT1V,
+ WM8971_ROUT1V, 7, 1, 0),
+ SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8971_LOUT2V,
+ WM8971_ROUT2V, 7, 1, 0),
+ SOC_SINGLE("Mono Playback ZC Switch", WM8971_MOUTV, 7, 1, 0),
+
+ SOC_DOUBLE_R("PCM Volume", WM8971_LDAC, WM8971_RDAC, 0, 255, 0),
+
+ SOC_DOUBLE_R("Bypass Left Playback Volume", WM8971_LOUTM1,
+ WM8971_LOUTM2, 4, 7, 1),
+ SOC_DOUBLE_R("Bypass Right Playback Volume", WM8971_ROUTM1,
+ WM8971_ROUTM2, 4, 7, 1),
+ SOC_DOUBLE_R("Bypass Mono Playback Volume", WM8971_MOUTM1,
+ WM8971_MOUTM2, 4, 7, 1),
+
+ SOC_DOUBLE_R("Headphone Playback Volume", WM8971_LOUT1V,
+ WM8971_ROUT1V, 0, 127, 0),
+ SOC_DOUBLE_R("Speaker Playback Volume", WM8971_LOUT2V,
+ WM8971_ROUT2V, 0, 127, 0),
+
+ SOC_ENUM("Bass Boost", wm8971_enum[0]),
+ SOC_ENUM("Bass Filter", wm8971_enum[1]),
+ SOC_SINGLE("Bass Volume", WM8971_BASS, 0, 7, 1),
+
+ SOC_SINGLE("Treble Volume", WM8971_TREBLE, 0, 7, 0),
+ SOC_ENUM("Treble Cut-off", wm8971_enum[2]),
+
+ SOC_SINGLE("Capture Filter Switch", WM8971_ADCDAC, 0, 1, 1),
+
+ SOC_SINGLE("ALC Target Volume", WM8971_ALC1, 0, 7, 0),
+ SOC_SINGLE("ALC Max Volume", WM8971_ALC1, 4, 7, 0),
+
+ SOC_SINGLE("ALC Capture Target Volume", WM8971_ALC1, 0, 7, 0),
+ SOC_SINGLE("ALC Capture Max Volume", WM8971_ALC1, 4, 7, 0),
+ SOC_ENUM("ALC Capture Function", wm8971_enum[3]),
+ SOC_SINGLE("ALC Capture ZC Switch", WM8971_ALC2, 7, 1, 0),
+ SOC_SINGLE("ALC Capture Hold Time", WM8971_ALC2, 0, 15, 0),
+ SOC_SINGLE("ALC Capture Decay Time", WM8971_ALC3, 4, 15, 0),
+ SOC_SINGLE("ALC Capture Attack Time", WM8971_ALC3, 0, 15, 0),
+ SOC_SINGLE("ALC Capture NG Threshold", WM8971_NGATE, 3, 31, 0),
+ SOC_ENUM("ALC Capture NG Type", wm8971_enum[4]),
+ SOC_SINGLE("ALC Capture NG Switch", WM8971_NGATE, 0, 1, 0),
+
+ SOC_SINGLE("Capture 6dB Attenuate", WM8971_ADCDAC, 8, 1, 0),
+ SOC_SINGLE("Playback 6dB Attenuate", WM8971_ADCDAC, 7, 1, 0),
+
+ SOC_ENUM("Playback De-emphasis", wm8971_enum[5]),
+ SOC_ENUM("Playback Function", wm8971_enum[6]),
+ SOC_ENUM("Playback Phase", wm8971_enum[7]),
+
+ SOC_DOUBLE_R("Mic Boost", WM8971_LADCIN, WM8971_RADCIN, 4, 3, 0),
+};
+
+/* add non-DAPM controls */
+static int wm8971_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8971_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8971_snd_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+/*
+ * DAPM Controls
+ */
+
+/* Left Mixer */
+static const struct snd_kcontrol_new wm8971_left_mixer_controls[] = {
+SOC_DAPM_SINGLE("Playback Switch", WM8971_LOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_LOUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8971_LOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_LOUTM2, 7, 1, 0),
+};
+
+/* Right Mixer */
+static const struct snd_kcontrol_new wm8971_right_mixer_controls[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8971_ROUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_ROUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Playback Switch", WM8971_ROUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_ROUTM2, 7, 1, 0),
+};
+
+/* Mono Mixer */
+static const struct snd_kcontrol_new wm8971_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8971_MOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_MOUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8971_MOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_MOUTM2, 7, 1, 0),
+};
+
+/* Left Line Mux */
+static const struct snd_kcontrol_new wm8971_left_line_controls =
+SOC_DAPM_ENUM("Route", wm8971_enum[8]);
+
+/* Right Line Mux */
+static const struct snd_kcontrol_new wm8971_right_line_controls =
+SOC_DAPM_ENUM("Route", wm8971_enum[9]);
+
+/* Left PGA Mux */
+static const struct snd_kcontrol_new wm8971_left_pga_controls =
+SOC_DAPM_ENUM("Route", wm8971_enum[10]);
+
+/* Right PGA Mux */
+static const struct snd_kcontrol_new wm8971_right_pga_controls =
+SOC_DAPM_ENUM("Route", wm8971_enum[11]);
+
+/* Mono ADC Mux */
+static const struct snd_kcontrol_new wm8971_monomux_controls =
+SOC_DAPM_ENUM("Route", wm8971_enum[13]);
+
+static const struct snd_soc_dapm_widget wm8971_dapm_widgets[] = {
+ SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0,
+ &wm8971_left_mixer_controls[0],
+ ARRAY_SIZE(wm8971_left_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0,
+ &wm8971_right_mixer_controls[0],
+ ARRAY_SIZE(wm8971_right_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Mono Mixer", WM8971_PWR2, 2, 0,
+ &wm8971_mono_mixer_controls[0],
+ ARRAY_SIZE(wm8971_mono_mixer_controls)),
+
+ SND_SOC_DAPM_PGA("Right Out 2", WM8971_PWR2, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left Out 2", WM8971_PWR2, 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right Out 1", WM8971_PWR2, 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left Out 1", WM8971_PWR2, 6, 0, NULL, 0),
+ SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8971_PWR2, 7, 0),
+ SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8971_PWR2, 8, 0),
+ SND_SOC_DAPM_PGA("Mono Out 1", WM8971_PWR2, 2, 0, NULL, 0),
+
+ SND_SOC_DAPM_MICBIAS("Mic Bias", WM8971_PWR1, 1, 0),
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8971_PWR1, 2, 0),
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8971_PWR1, 3, 0),
+
+ SND_SOC_DAPM_MUX("Left PGA Mux", WM8971_PWR1, 5, 0,
+ &wm8971_left_pga_controls),
+ SND_SOC_DAPM_MUX("Right PGA Mux", WM8971_PWR1, 4, 0,
+ &wm8971_right_pga_controls),
+ SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0,
+ &wm8971_left_line_controls),
+ SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0,
+ &wm8971_right_line_controls),
+
+ SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0,
+ &wm8971_monomux_controls),
+ SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0,
+ &wm8971_monomux_controls),
+
+ SND_SOC_DAPM_OUTPUT("LOUT1"),
+ SND_SOC_DAPM_OUTPUT("ROUT1"),
+ SND_SOC_DAPM_OUTPUT("LOUT2"),
+ SND_SOC_DAPM_OUTPUT("ROUT2"),
+ SND_SOC_DAPM_OUTPUT("MONO"),
+
+ SND_SOC_DAPM_INPUT("LINPUT1"),
+ SND_SOC_DAPM_INPUT("RINPUT1"),
+ SND_SOC_DAPM_INPUT("MIC"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* left mixer */
+ {"Left Mixer", "Playback Switch", "Left DAC"},
+ {"Left Mixer", "Left Bypass Switch", "Left Line Mux"},
+ {"Left Mixer", "Right Playback Switch", "Right DAC"},
+ {"Left Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+ /* right mixer */
+ {"Right Mixer", "Left Playback Switch", "Left DAC"},
+ {"Right Mixer", "Left Bypass Switch", "Left Line Mux"},
+ {"Right Mixer", "Playback Switch", "Right DAC"},
+ {"Right Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+ /* left out 1 */
+ {"Left Out 1", NULL, "Left Mixer"},
+ {"LOUT1", NULL, "Left Out 1"},
+
+ /* left out 2 */
+ {"Left Out 2", NULL, "Left Mixer"},
+ {"LOUT2", NULL, "Left Out 2"},
+
+ /* right out 1 */
+ {"Right Out 1", NULL, "Right Mixer"},
+ {"ROUT1", NULL, "Right Out 1"},
+
+ /* right out 2 */
+ {"Right Out 2", NULL, "Right Mixer"},
+ {"ROUT2", NULL, "Right Out 2"},
+
+ /* mono mixer */
+ {"Mono Mixer", "Left Playback Switch", "Left DAC"},
+ {"Mono Mixer", "Left Bypass Switch", "Left Line Mux"},
+ {"Mono Mixer", "Right Playback Switch", "Right DAC"},
+ {"Mono Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+ /* mono out */
+ {"Mono Out", NULL, "Mono Mixer"},
+ {"MONO1", NULL, "Mono Out"},
+
+ /* Left Line Mux */
+ {"Left Line Mux", "Line", "LINPUT1"},
+ {"Left Line Mux", "PGA", "Left PGA Mux"},
+ {"Left Line Mux", "Differential", "Differential Mux"},
+
+ /* Right Line Mux */
+ {"Right Line Mux", "Line", "RINPUT1"},
+ {"Right Line Mux", "Mic", "MIC"},
+ {"Right Line Mux", "PGA", "Right PGA Mux"},
+ {"Right Line Mux", "Differential", "Differential Mux"},
+
+ /* Left PGA Mux */
+ {"Left PGA Mux", "Line", "LINPUT1"},
+ {"Left PGA Mux", "Differential", "Differential Mux"},
+
+ /* Right PGA Mux */
+ {"Right PGA Mux", "Line", "RINPUT1"},
+ {"Right PGA Mux", "Differential", "Differential Mux"},
+
+ /* Differential Mux */
+ {"Differential Mux", "Line", "LINPUT1"},
+ {"Differential Mux", "Line", "RINPUT1"},
+
+ /* Left ADC Mux */
+ {"Left ADC Mux", "Stereo", "Left PGA Mux"},
+ {"Left ADC Mux", "Mono (Left)", "Left PGA Mux"},
+ {"Left ADC Mux", "Digital Mono", "Left PGA Mux"},
+
+ /* Right ADC Mux */
+ {"Right ADC Mux", "Stereo", "Right PGA Mux"},
+ {"Right ADC Mux", "Mono (Right)", "Right PGA Mux"},
+ {"Right ADC Mux", "Digital Mono", "Right PGA Mux"},
+
+ /* ADC */
+ {"Left ADC", NULL, "Left ADC Mux"},
+ {"Right ADC", NULL, "Right ADC Mux"},
+};
+
+static int wm8971_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm8971_dapm_widgets,
+ ARRAY_SIZE(wm8971_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+
+ return 0;
+}
+
+struct _coeff_div {
+ u32 mclk;
+ u32 rate;
+ u16 fs;
+ u8 sr:5;
+ u8 usb:1;
+};
+
+/* codec hifi mclk clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+ /* 8k */
+ {12288000, 8000, 1536, 0x6, 0x0},
+ {11289600, 8000, 1408, 0x16, 0x0},
+ {18432000, 8000, 2304, 0x7, 0x0},
+ {16934400, 8000, 2112, 0x17, 0x0},
+ {12000000, 8000, 1500, 0x6, 0x1},
+
+ /* 11.025k */
+ {11289600, 11025, 1024, 0x18, 0x0},
+ {16934400, 11025, 1536, 0x19, 0x0},
+ {12000000, 11025, 1088, 0x19, 0x1},
+
+ /* 16k */
+ {12288000, 16000, 768, 0xa, 0x0},
+ {18432000, 16000, 1152, 0xb, 0x0},
+ {12000000, 16000, 750, 0xa, 0x1},
+
+ /* 22.05k */
+ {11289600, 22050, 512, 0x1a, 0x0},
+ {16934400, 22050, 768, 0x1b, 0x0},
+ {12000000, 22050, 544, 0x1b, 0x1},
+
+ /* 32k */
+ {12288000, 32000, 384, 0xc, 0x0},
+ {18432000, 32000, 576, 0xd, 0x0},
+ {12000000, 32000, 375, 0xa, 0x1},
+
+ /* 44.1k */
+ {11289600, 44100, 256, 0x10, 0x0},
+ {16934400, 44100, 384, 0x11, 0x0},
+ {12000000, 44100, 272, 0x11, 0x1},
+
+ /* 48k */
+ {12288000, 48000, 256, 0x0, 0x0},
+ {18432000, 48000, 384, 0x1, 0x0},
+ {12000000, 48000, 250, 0x0, 0x1},
+
+ /* 88.2k */
+ {11289600, 88200, 128, 0x1e, 0x0},
+ {16934400, 88200, 192, 0x1f, 0x0},
+ {12000000, 88200, 136, 0x1f, 0x1},
+
+ /* 96k */
+ {12288000, 96000, 128, 0xe, 0x0},
+ {18432000, 96000, 192, 0xf, 0x0},
+ {12000000, 96000, 125, 0xe, 0x1},
+};
+
+static int get_coeff(int mclk, int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+ if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+ return i;
+ }
+ return -EINVAL;
+}
+
+static int wm8971_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 wm8971_priv *wm8971 = codec->private_data;
+
+ switch (freq) {
+ case 11289600:
+ case 12000000:
+ case 12288000:
+ case 16934400:
+ case 18432000:
+ wm8971->sysclk = freq;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int wm8971_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface = 0x0040;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8971_write(codec, WM8971_IFACE, iface);
+ return 0;
+}
+
+static int wm8971_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct wm8971_priv *wm8971 = codec->private_data;
+ u16 iface = wm8971_read_reg_cache(codec, WM8971_IFACE) & 0x1f3;
+ u16 srate = wm8971_read_reg_cache(codec, WM8971_SRATE) & 0x1c0;
+ int coeff = get_coeff(wm8971->sysclk, params_rate(params));
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x0008;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= 0x000c;
+ break;
+ }
+
+ /* set iface & srate */
+ wm8971_write(codec, WM8971_IFACE, iface);
+ if (coeff >= 0)
+ wm8971_write(codec, WM8971_SRATE, srate |
+ (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb);
+
+ return 0;
+}
+
+static int wm8971_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = wm8971_read_reg_cache(codec, WM8971_ADCDAC) & 0xfff7;
+
+ if (mute)
+ wm8971_write(codec, WM8971_ADCDAC, mute_reg | 0x8);
+ else
+ wm8971_write(codec, WM8971_ADCDAC, mute_reg);
+ return 0;
+}
+
+static int wm8971_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 pwr_reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* set vmid to 50k and unmute dac */
+ wm8971_write(codec, WM8971_PWR1, pwr_reg | 0x00c1);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* mute dac and set vmid to 500k, enable VREF */
+ wm8971_write(codec, WM8971_PWR1, pwr_reg | 0x0140);
+ break;
+ case SND_SOC_BIAS_OFF:
+ wm8971_write(codec, WM8971_PWR1, 0x0001);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define WM8971_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+#define WM8971_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+struct snd_soc_dai wm8971_dai = {
+ .name = "WM8971",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8971_RATES,
+ .formats = WM8971_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8971_RATES,
+ .formats = WM8971_FORMATS,},
+ .ops = {
+ .hw_params = wm8971_pcm_hw_params,
+ },
+ .dai_ops = {
+ .digital_mute = wm8971_mute,
+ .set_fmt = wm8971_set_dai_fmt,
+ .set_sysclk = wm8971_set_dai_sysclk,
+ },
+};
+EXPORT_SYMBOL_GPL(wm8971_dai);
+
+static void wm8971_work(struct work_struct *work)
+{
+ struct snd_soc_codec *codec =
+ container_of(work, struct snd_soc_codec, delayed_work.work);
+ wm8971_set_bias_level(codec, codec->bias_level);
+}
+
+static int wm8971_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8971_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+ u16 reg;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8971_reg); i++) {
+ if (i + 1 == WM8971_RESET)
+ continue;
+ data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+
+ wm8971_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* charge wm8971 caps */
+ if (codec->suspend_bias_level == SND_SOC_BIAS_ON) {
+ reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e;
+ wm8971_write(codec, WM8971_PWR1, reg | 0x01c0);
+ codec->bias_level = SND_SOC_BIAS_ON;
+ queue_delayed_work(wm8971_workq, &codec->delayed_work,
+ msecs_to_jiffies(1000));
+ }
+
+ return 0;
+}
+
+static int wm8971_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int reg, ret = 0;
+
+ codec->name = "WM8971";
+ codec->owner = THIS_MODULE;
+ codec->read = wm8971_read_reg_cache;
+ codec->write = wm8971_write;
+ codec->set_bias_level = wm8971_set_bias_level;
+ codec->dai = &wm8971_dai;
+ codec->reg_cache_size = ARRAY_SIZE(wm8971_reg);
+ codec->num_dai = 1;
+ codec->reg_cache = kmemdup(wm8971_reg, sizeof(wm8971_reg), GFP_KERNEL);
+
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ wm8971_reset(codec);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8971: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* charge output caps - set vmid to 5k for quick power up */
+ reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e;
+ wm8971_write(codec, WM8971_PWR1, reg | 0x01c0);
+ codec->bias_level = SND_SOC_BIAS_STANDBY;
+ queue_delayed_work(wm8971_workq, &codec->delayed_work,
+ msecs_to_jiffies(1000));
+
+ /* set the update bits */
+ reg = wm8971_read_reg_cache(codec, WM8971_LDAC);
+ wm8971_write(codec, WM8971_LDAC, reg | 0x0100);
+ reg = wm8971_read_reg_cache(codec, WM8971_RDAC);
+ wm8971_write(codec, WM8971_RDAC, reg | 0x0100);
+
+ reg = wm8971_read_reg_cache(codec, WM8971_LOUT1V);
+ wm8971_write(codec, WM8971_LOUT1V, reg | 0x0100);
+ reg = wm8971_read_reg_cache(codec, WM8971_ROUT1V);
+ wm8971_write(codec, WM8971_ROUT1V, reg | 0x0100);
+
+ reg = wm8971_read_reg_cache(codec, WM8971_LOUT2V);
+ wm8971_write(codec, WM8971_LOUT2V, reg | 0x0100);
+ reg = wm8971_read_reg_cache(codec, WM8971_ROUT2V);
+ wm8971_write(codec, WM8971_ROUT2V, reg | 0x0100);
+
+ reg = wm8971_read_reg_cache(codec, WM8971_LINVOL);
+ wm8971_write(codec, WM8971_LINVOL, reg | 0x0100);
+ reg = wm8971_read_reg_cache(codec, WM8971_RINVOL);
+ wm8971_write(codec, WM8971_RINVOL, reg | 0x0100);
+
+ wm8971_add_controls(codec);
+ wm8971_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8971: failed to register card\n");
+ goto card_err;
+ }
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+ return ret;
+}
+
+/* If the i2c layer weren't so broken, we could pass this kind of data
+ around */
+static struct snd_soc_device *wm8971_socdev;
+
+#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
+
+static int wm8971_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = wm8971_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+
+ codec->control_data = i2c;
+
+ ret = wm8971_init(socdev);
+ if (ret < 0)
+ pr_err("failed to initialise WM8971\n");
+
+ return ret;
+}
+
+static int wm8971_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+static const struct i2c_device_id wm8971_i2c_id[] = {
+ { "wm8971", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8971_i2c_id);
+
+static struct i2c_driver wm8971_i2c_driver = {
+ .driver = {
+ .name = "WM8971 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8971_i2c_probe,
+ .remove = wm8971_i2c_remove,
+ .id_table = wm8971_i2c_id,
+};
+
+static int wm8971_add_i2c_device(struct platform_device *pdev,
+ const struct wm8971_setup_data *setup)
+{
+ struct i2c_board_info info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+ int ret;
+
+ ret = i2c_add_driver(&wm8971_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ return ret;
+ }
+
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ info.addr = setup->i2c_address;
+ strlcpy(info.type, "wm8971", I2C_NAME_SIZE);
+
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "can't get i2c adapter %d\n",
+ setup->i2c_bus);
+ goto err_driver;
+ }
+
+ client = i2c_new_device(adapter, &info);
+ i2c_put_adapter(adapter);
+ if (!client) {
+ dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
+ (unsigned int)info.addr);
+ goto err_driver;
+ }
+
+ return 0;
+
+err_driver:
+ i2c_del_driver(&wm8971_i2c_driver);
+ return -ENODEV;
+}
+
+#endif
+
+static int wm8971_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct wm8971_setup_data *setup;
+ struct snd_soc_codec *codec;
+ struct wm8971_priv *wm8971;
+ int ret = 0;
+
+ pr_info("WM8971 Audio Codec %s", WM8971_VERSION);
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ wm8971 = kzalloc(sizeof(struct wm8971_priv), GFP_KERNEL);
+ if (wm8971 == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->private_data = wm8971;
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+ wm8971_socdev = socdev;
+
+ INIT_DELAYED_WORK(&codec->delayed_work, wm8971_work);
+ wm8971_workq = create_workqueue("wm8971");
+ if (wm8971_workq == NULL) {
+ kfree(codec->private_data);
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = wm8971_add_i2c_device(pdev, setup);
+ }
+#endif
+ /* Add other interfaces here */
+
+ if (ret != 0) {
+ destroy_workqueue(wm8971_workq);
+ kfree(codec->private_data);
+ kfree(codec);
+ }
+
+ return ret;
+}
+
+/* power down chip */
+static int wm8971_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec->control_data)
+ wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ if (wm8971_workq)
+ destroy_workqueue(wm8971_workq);
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
+ i2c_unregister_device(codec->control_data);
+ i2c_del_driver(&wm8971_i2c_driver);
+#endif
+ kfree(codec->private_data);
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8971 = {
+ .probe = wm8971_probe,
+ .remove = wm8971_remove,
+ .suspend = wm8971_suspend,
+ .resume = wm8971_resume,
+};
+
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8971);
+
+MODULE_DESCRIPTION("ASoC WM8971 driver");
+MODULE_AUTHOR("Lab126");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8971.h b/sound/soc/codecs/wm8971.h
new file mode 100644
index 0000000..ef4f08f
--- /dev/null
+++ b/sound/soc/codecs/wm8971.h
@@ -0,0 +1,64 @@
+/*
+ * wm8971.h -- audio driver for WM8971
+ *
+ * Copyright 2005 Lab126, Inc.
+ *
+ * Author: Kenneth Kiraly <kiraly@lab126.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 _WM8971_H
+#define _WM8971_H
+
+#define WM8971_LINVOL 0x00
+#define WM8971_RINVOL 0x01
+#define WM8971_LOUT1V 0x02
+#define WM8971_ROUT1V 0x03
+#define WM8971_ADCDAC 0x05
+#define WM8971_IFACE 0x07
+#define WM8971_SRATE 0x08
+#define WM8971_LDAC 0x0a
+#define WM8971_RDAC 0x0b
+#define WM8971_BASS 0x0c
+#define WM8971_TREBLE 0x0d
+#define WM8971_RESET 0x0f
+#define WM8971_ALC1 0x11
+#define WM8971_ALC2 0x12
+#define WM8971_ALC3 0x13
+#define WM8971_NGATE 0x14
+#define WM8971_LADC 0x15
+#define WM8971_RADC 0x16
+#define WM8971_ADCTL1 0x17
+#define WM8971_ADCTL2 0x18
+#define WM8971_PWR1 0x19
+#define WM8971_PWR2 0x1a
+#define WM8971_ADCTL3 0x1b
+#define WM8971_ADCIN 0x1f
+#define WM8971_LADCIN 0x20
+#define WM8971_RADCIN 0x21
+#define WM8971_LOUTM1 0x22
+#define WM8971_LOUTM2 0x23
+#define WM8971_ROUTM1 0x24
+#define WM8971_ROUTM2 0x25
+#define WM8971_MOUTM1 0x26
+#define WM8971_MOUTM2 0x27
+#define WM8971_LOUT2V 0x28
+#define WM8971_ROUT2V 0x29
+#define WM8971_MOUTV 0x2A
+
+#define WM8971_SYSCLK 0
+
+struct wm8971_setup_data {
+ int i2c_bus;
+ unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai wm8971_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8971;
+
+#endif
diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c
new file mode 100644
index 0000000..572d22b
--- /dev/null
+++ b/sound/soc/codecs/wm8990.c
@@ -0,0 +1,1635 @@
+/*
+ * wm8990.c -- WM8990 ALSA Soc Audio driver
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood
+ * lg@opensource.wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.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 <asm/div64.h>
+
+#include "wm8990.h"
+
+#define WM8990_VERSION "0.2"
+
+/* codec private data */
+struct wm8990_priv {
+ unsigned int sysclk;
+ unsigned int pcmclk;
+};
+
+/*
+ * wm8990 register cache. Note that register 0 is not included in the
+ * cache.
+ */
+static const u16 wm8990_reg[] = {
+ 0x8990, /* R0 - Reset */
+ 0x0000, /* R1 - Power Management (1) */
+ 0x6000, /* R2 - Power Management (2) */
+ 0x0000, /* R3 - Power Management (3) */
+ 0x4050, /* R4 - Audio Interface (1) */
+ 0x4000, /* R5 - Audio Interface (2) */
+ 0x01C8, /* R6 - Clocking (1) */
+ 0x0000, /* R7 - Clocking (2) */
+ 0x0040, /* R8 - Audio Interface (3) */
+ 0x0040, /* R9 - Audio Interface (4) */
+ 0x0004, /* R10 - DAC CTRL */
+ 0x00C0, /* R11 - Left DAC Digital Volume */
+ 0x00C0, /* R12 - Right DAC Digital Volume */
+ 0x0000, /* R13 - Digital Side Tone */
+ 0x0100, /* R14 - ADC CTRL */
+ 0x00C0, /* R15 - Left ADC Digital Volume */
+ 0x00C0, /* R16 - Right ADC Digital Volume */
+ 0x0000, /* R17 */
+ 0x0000, /* R18 - GPIO CTRL 1 */
+ 0x1000, /* R19 - GPIO1 & GPIO2 */
+ 0x1010, /* R20 - GPIO3 & GPIO4 */
+ 0x1010, /* R21 - GPIO5 & GPIO6 */
+ 0x8000, /* R22 - GPIOCTRL 2 */
+ 0x0800, /* R23 - GPIO_POL */
+ 0x008B, /* R24 - Left Line Input 1&2 Volume */
+ 0x008B, /* R25 - Left Line Input 3&4 Volume */
+ 0x008B, /* R26 - Right Line Input 1&2 Volume */
+ 0x008B, /* R27 - Right Line Input 3&4 Volume */
+ 0x0000, /* R28 - Left Output Volume */
+ 0x0000, /* R29 - Right Output Volume */
+ 0x0066, /* R30 - Line Outputs Volume */
+ 0x0022, /* R31 - Out3/4 Volume */
+ 0x0079, /* R32 - Left OPGA Volume */
+ 0x0079, /* R33 - Right OPGA Volume */
+ 0x0003, /* R34 - Speaker Volume */
+ 0x0003, /* R35 - ClassD1 */
+ 0x0000, /* R36 */
+ 0x0100, /* R37 - ClassD3 */
+ 0x0079, /* R38 - ClassD4 */
+ 0x0000, /* R39 - Input Mixer1 */
+ 0x0000, /* R40 - Input Mixer2 */
+ 0x0000, /* R41 - Input Mixer3 */
+ 0x0000, /* R42 - Input Mixer4 */
+ 0x0000, /* R43 - Input Mixer5 */
+ 0x0000, /* R44 - Input Mixer6 */
+ 0x0000, /* R45 - Output Mixer1 */
+ 0x0000, /* R46 - Output Mixer2 */
+ 0x0000, /* R47 - Output Mixer3 */
+ 0x0000, /* R48 - Output Mixer4 */
+ 0x0000, /* R49 - Output Mixer5 */
+ 0x0000, /* R50 - Output Mixer6 */
+ 0x0180, /* R51 - Out3/4 Mixer */
+ 0x0000, /* R52 - Line Mixer1 */
+ 0x0000, /* R53 - Line Mixer2 */
+ 0x0000, /* R54 - Speaker Mixer */
+ 0x0000, /* R55 - Additional Control */
+ 0x0000, /* R56 - AntiPOP1 */
+ 0x0000, /* R57 - AntiPOP2 */
+ 0x0000, /* R58 - MICBIAS */
+ 0x0000, /* R59 */
+ 0x0008, /* R60 - PLL1 */
+ 0x0031, /* R61 - PLL2 */
+ 0x0026, /* R62 - PLL3 */
+};
+
+/*
+ * read wm8990 register cache
+ */
+static inline unsigned int wm8990_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ BUG_ON(reg > (ARRAY_SIZE(wm8990_reg)) - 1);
+ return cache[reg];
+}
+
+/*
+ * write wm8990 register cache
+ */
+static inline void wm8990_write_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ BUG_ON(reg > (ARRAY_SIZE(wm8990_reg)) - 1);
+
+ /* Reset register is uncached */
+ if (reg == 0)
+ return;
+
+ cache[reg] = value;
+}
+
+/*
+ * write to the wm8990 register space
+ */
+static int wm8990_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[3];
+
+ data[0] = reg & 0xFF;
+ data[1] = (value >> 8) & 0xFF;
+ data[2] = value & 0xFF;
+
+ wm8990_write_reg_cache(codec, reg, value);
+
+ if (codec->hw_write(codec->control_data, data, 3) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+#define wm8990_reset(c) wm8990_write(c, WM8990_RESET, 0)
+
+static const DECLARE_TLV_DB_LINEAR(rec_mix_tlv, -1500, 600);
+
+static const DECLARE_TLV_DB_LINEAR(in_pga_tlv, -1650, 3000);
+
+static const DECLARE_TLV_DB_LINEAR(out_mix_tlv, 0, -2100);
+
+static const DECLARE_TLV_DB_LINEAR(out_pga_tlv, -7300, 600);
+
+static const DECLARE_TLV_DB_LINEAR(out_omix_tlv, -600, 0);
+
+static const DECLARE_TLV_DB_LINEAR(out_dac_tlv, -7163, 0);
+
+static const DECLARE_TLV_DB_LINEAR(in_adc_tlv, -7163, 1763);
+
+static const DECLARE_TLV_DB_LINEAR(out_sidetone_tlv, -3600, 0);
+
+static int wm899x_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int reg = kcontrol->private_value & 0xff;
+ int ret;
+ u16 val;
+
+ ret = snd_soc_put_volsw(kcontrol, ucontrol);
+ if (ret < 0)
+ return ret;
+
+ /* now hit the volume update bits (always bit 8) */
+ val = wm8990_read_reg_cache(codec, reg);
+ return wm8990_write(codec, reg, val | 0x0100);
+}
+
+#define SOC_WM899X_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert,\
+ tlv_array) {\
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+ SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw, \
+ .get = snd_soc_get_volsw, .put = wm899x_outpga_put_volsw_vu, \
+ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+
+static const char *wm8990_digital_sidetone[] =
+ {"None", "Left ADC", "Right ADC", "Reserved"};
+
+static const struct soc_enum wm8990_left_digital_sidetone_enum =
+SOC_ENUM_SINGLE(WM8990_DIGITAL_SIDE_TONE,
+ WM8990_ADC_TO_DACL_SHIFT,
+ WM8990_ADC_TO_DACL_MASK,
+ wm8990_digital_sidetone);
+
+static const struct soc_enum wm8990_right_digital_sidetone_enum =
+SOC_ENUM_SINGLE(WM8990_DIGITAL_SIDE_TONE,
+ WM8990_ADC_TO_DACR_SHIFT,
+ WM8990_ADC_TO_DACR_MASK,
+ wm8990_digital_sidetone);
+
+static const char *wm8990_adcmode[] =
+ {"Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"};
+
+static const struct soc_enum wm8990_right_adcmode_enum =
+SOC_ENUM_SINGLE(WM8990_ADC_CTRL,
+ WM8990_ADC_HPF_CUT_SHIFT,
+ WM8990_ADC_HPF_CUT_MASK,
+ wm8990_adcmode);
+
+static const struct snd_kcontrol_new wm8990_snd_controls[] = {
+/* INMIXL */
+SOC_SINGLE("LIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L12MNBST_BIT, 1, 0),
+SOC_SINGLE("LIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L34MNBST_BIT, 1, 0),
+/* INMIXR */
+SOC_SINGLE("RIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R12MNBST_BIT, 1, 0),
+SOC_SINGLE("RIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R34MNBST_BIT, 1, 0),
+
+/* LOMIX */
+SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8990_OUTPUT_MIXER3,
+ WM8990_LLI3LOVOL_SHIFT, WM8990_LLI3LOVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX RIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER3,
+ WM8990_LR12LOVOL_SHIFT, WM8990_LR12LOVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX LIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER3,
+ WM8990_LL12LOVOL_SHIFT, WM8990_LL12LOVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER5,
+ WM8990_LRI3LOVOL_SHIFT, WM8990_LRI3LOVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8990_OUTPUT_MIXER5,
+ WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8990_OUTPUT_MIXER5,
+ WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv),
+
+/* ROMIX */
+SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER4,
+ WM8990_RRI3ROVOL_SHIFT, WM8990_RRI3ROVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX LIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER4,
+ WM8990_RL12ROVOL_SHIFT, WM8990_RL12ROVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX RIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER4,
+ WM8990_RR12ROVOL_SHIFT, WM8990_RR12ROVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX LIN3 Bypass Volume", WM8990_OUTPUT_MIXER6,
+ WM8990_RLI3ROVOL_SHIFT, WM8990_RLI3ROVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX AINLMUX Bypass Volume", WM8990_OUTPUT_MIXER6,
+ WM8990_RLBROVOL_SHIFT, WM8990_RLBROVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX AINRMUX Bypass Volume", WM8990_OUTPUT_MIXER6,
+ WM8990_RRBROVOL_SHIFT, WM8990_RRBROVOL_MASK, 1, out_mix_tlv),
+
+/* LOUT */
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOUT Volume", WM8990_LEFT_OUTPUT_VOLUME,
+ WM8990_LOUTVOL_SHIFT, WM8990_LOUTVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("LOUT ZC", WM8990_LEFT_OUTPUT_VOLUME, WM8990_LOZC_BIT, 1, 0),
+
+/* ROUT */
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROUT Volume", WM8990_RIGHT_OUTPUT_VOLUME,
+ WM8990_ROUTVOL_SHIFT, WM8990_ROUTVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("ROUT ZC", WM8990_RIGHT_OUTPUT_VOLUME, WM8990_ROZC_BIT, 1, 0),
+
+/* LOPGA */
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOPGA Volume", WM8990_LEFT_OPGA_VOLUME,
+ WM8990_LOPGAVOL_SHIFT, WM8990_LOPGAVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("LOPGA ZC Switch", WM8990_LEFT_OPGA_VOLUME,
+ WM8990_LOPGAZC_BIT, 1, 0),
+
+/* ROPGA */
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROPGA Volume", WM8990_RIGHT_OPGA_VOLUME,
+ WM8990_ROPGAVOL_SHIFT, WM8990_ROPGAVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("ROPGA ZC Switch", WM8990_RIGHT_OPGA_VOLUME,
+ WM8990_ROPGAZC_BIT, 1, 0),
+
+SOC_SINGLE("LON Mute Switch", WM8990_LINE_OUTPUTS_VOLUME,
+ WM8990_LONMUTE_BIT, 1, 0),
+SOC_SINGLE("LOP Mute Switch", WM8990_LINE_OUTPUTS_VOLUME,
+ WM8990_LOPMUTE_BIT, 1, 0),
+SOC_SINGLE("LOP Attenuation Switch", WM8990_LINE_OUTPUTS_VOLUME,
+ WM8990_LOATTN_BIT, 1, 0),
+SOC_SINGLE("RON Mute Switch", WM8990_LINE_OUTPUTS_VOLUME,
+ WM8990_RONMUTE_BIT, 1, 0),
+SOC_SINGLE("ROP Mute Switch", WM8990_LINE_OUTPUTS_VOLUME,
+ WM8990_ROPMUTE_BIT, 1, 0),
+SOC_SINGLE("ROP Attenuation Switch", WM8990_LINE_OUTPUTS_VOLUME,
+ WM8990_ROATTN_BIT, 1, 0),
+
+SOC_SINGLE("OUT3 Mute Switch", WM8990_OUT3_4_VOLUME,
+ WM8990_OUT3MUTE_BIT, 1, 0),
+SOC_SINGLE("OUT3 Attenuation Switch", WM8990_OUT3_4_VOLUME,
+ WM8990_OUT3ATTN_BIT, 1, 0),
+
+SOC_SINGLE("OUT4 Mute Switch", WM8990_OUT3_4_VOLUME,
+ WM8990_OUT4MUTE_BIT, 1, 0),
+SOC_SINGLE("OUT4 Attenuation Switch", WM8990_OUT3_4_VOLUME,
+ WM8990_OUT4ATTN_BIT, 1, 0),
+
+SOC_SINGLE("Speaker Mode Switch", WM8990_CLASSD1,
+ WM8990_CDMODE_BIT, 1, 0),
+
+SOC_SINGLE("Speaker Output Attenuation Volume", WM8990_SPEAKER_VOLUME,
+ WM8990_SPKATTN_SHIFT, WM8990_SPKATTN_MASK, 0),
+SOC_SINGLE("Speaker DC Boost Volume", WM8990_CLASSD3,
+ WM8990_DCGAIN_SHIFT, WM8990_DCGAIN_MASK, 0),
+SOC_SINGLE("Speaker AC Boost Volume", WM8990_CLASSD3,
+ WM8990_ACGAIN_SHIFT, WM8990_ACGAIN_MASK, 0),
+SOC_SINGLE_TLV("Speaker Volume", WM8990_CLASSD4,
+ WM8990_SPKVOL_SHIFT, WM8990_SPKVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("Speaker ZC Switch", WM8990_CLASSD4,
+ WM8990_SPKZC_SHIFT, WM8990_SPKZC_MASK, 0),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left DAC Digital Volume",
+ WM8990_LEFT_DAC_DIGITAL_VOLUME,
+ WM8990_DACL_VOL_SHIFT,
+ WM8990_DACL_VOL_MASK,
+ 0,
+ out_dac_tlv),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right DAC Digital Volume",
+ WM8990_RIGHT_DAC_DIGITAL_VOLUME,
+ WM8990_DACR_VOL_SHIFT,
+ WM8990_DACR_VOL_MASK,
+ 0,
+ out_dac_tlv),
+
+SOC_ENUM("Left Digital Sidetone", wm8990_left_digital_sidetone_enum),
+SOC_ENUM("Right Digital Sidetone", wm8990_right_digital_sidetone_enum),
+
+SOC_SINGLE_TLV("Left Digital Sidetone Volume", WM8990_DIGITAL_SIDE_TONE,
+ WM8990_ADCL_DAC_SVOL_SHIFT, WM8990_ADCL_DAC_SVOL_MASK, 0,
+ out_sidetone_tlv),
+SOC_SINGLE_TLV("Right Digital Sidetone Volume", WM8990_DIGITAL_SIDE_TONE,
+ WM8990_ADCR_DAC_SVOL_SHIFT, WM8990_ADCR_DAC_SVOL_MASK, 0,
+ out_sidetone_tlv),
+
+SOC_SINGLE("ADC Digital High Pass Filter Switch", WM8990_ADC_CTRL,
+ WM8990_ADC_HPF_ENA_BIT, 1, 0),
+
+SOC_ENUM("ADC HPF Mode", wm8990_right_adcmode_enum),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left ADC Digital Volume",
+ WM8990_LEFT_ADC_DIGITAL_VOLUME,
+ WM8990_ADCL_VOL_SHIFT,
+ WM8990_ADCL_VOL_MASK,
+ 0,
+ in_adc_tlv),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right ADC Digital Volume",
+ WM8990_RIGHT_ADC_DIGITAL_VOLUME,
+ WM8990_ADCR_VOL_SHIFT,
+ WM8990_ADCR_VOL_MASK,
+ 0,
+ in_adc_tlv),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN12 Volume",
+ WM8990_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8990_LIN12VOL_SHIFT,
+ WM8990_LIN12VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+SOC_SINGLE("LIN12 ZC Switch", WM8990_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8990_LI12ZC_BIT, 1, 0),
+
+SOC_SINGLE("LIN12 Mute Switch", WM8990_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8990_LI12MUTE_BIT, 1, 0),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN34 Volume",
+ WM8990_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8990_LIN34VOL_SHIFT,
+ WM8990_LIN34VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+SOC_SINGLE("LIN34 ZC Switch", WM8990_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8990_LI34ZC_BIT, 1, 0),
+
+SOC_SINGLE("LIN34 Mute Switch", WM8990_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8990_LI34MUTE_BIT, 1, 0),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN12 Volume",
+ WM8990_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8990_RIN12VOL_SHIFT,
+ WM8990_RIN12VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+SOC_SINGLE("RIN12 ZC Switch", WM8990_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8990_RI12ZC_BIT, 1, 0),
+
+SOC_SINGLE("RIN12 Mute Switch", WM8990_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8990_RI12MUTE_BIT, 1, 0),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN34 Volume",
+ WM8990_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8990_RIN34VOL_SHIFT,
+ WM8990_RIN34VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+SOC_SINGLE("RIN34 ZC Switch", WM8990_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8990_RI34ZC_BIT, 1, 0),
+
+SOC_SINGLE("RIN34 Mute Switch", WM8990_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8990_RI34MUTE_BIT, 1, 0),
+
+};
+
+/* add non dapm controls */
+static int wm8990_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8990_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8990_snd_controls[i], codec,
+ NULL));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+/*
+ * _DAPM_ Controls
+ */
+
+static int inmixer_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ u16 reg, fakepower;
+
+ reg = wm8990_read_reg_cache(w->codec, WM8990_POWER_MANAGEMENT_2);
+ fakepower = wm8990_read_reg_cache(w->codec, WM8990_INTDRIVBITS);
+
+ if (fakepower & ((1 << WM8990_INMIXL_PWR_BIT) |
+ (1 << WM8990_AINLMUX_PWR_BIT))) {
+ reg |= WM8990_AINL_ENA;
+ } else {
+ reg &= ~WM8990_AINL_ENA;
+ }
+
+ if (fakepower & ((1 << WM8990_INMIXR_PWR_BIT) |
+ (1 << WM8990_AINRMUX_PWR_BIT))) {
+ reg |= WM8990_AINR_ENA;
+ } else {
+ reg &= ~WM8990_AINL_ENA;
+ }
+ wm8990_write(w->codec, WM8990_POWER_MANAGEMENT_2, reg);
+
+ return 0;
+}
+
+static int outmixer_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ u32 reg_shift = kcontrol->private_value & 0xfff;
+ int ret = 0;
+ u16 reg;
+
+ switch (reg_shift) {
+ case WM8990_SPEAKER_MIXER | (WM8990_LDSPK_BIT << 8) :
+ reg = wm8990_read_reg_cache(w->codec, WM8990_OUTPUT_MIXER1);
+ if (reg & WM8990_LDLO) {
+ printk(KERN_WARNING
+ "Cannot set as Output Mixer 1 LDLO Set\n");
+ ret = -1;
+ }
+ break;
+ case WM8990_SPEAKER_MIXER | (WM8990_RDSPK_BIT << 8):
+ reg = wm8990_read_reg_cache(w->codec, WM8990_OUTPUT_MIXER2);
+ if (reg & WM8990_RDRO) {
+ printk(KERN_WARNING
+ "Cannot set as Output Mixer 2 RDRO Set\n");
+ ret = -1;
+ }
+ break;
+ case WM8990_OUTPUT_MIXER1 | (WM8990_LDLO_BIT << 8):
+ reg = wm8990_read_reg_cache(w->codec, WM8990_SPEAKER_MIXER);
+ if (reg & WM8990_LDSPK) {
+ printk(KERN_WARNING
+ "Cannot set as Speaker Mixer LDSPK Set\n");
+ ret = -1;
+ }
+ break;
+ case WM8990_OUTPUT_MIXER2 | (WM8990_RDRO_BIT << 8):
+ reg = wm8990_read_reg_cache(w->codec, WM8990_SPEAKER_MIXER);
+ if (reg & WM8990_RDSPK) {
+ printk(KERN_WARNING
+ "Cannot set as Speaker Mixer RDSPK Set\n");
+ ret = -1;
+ }
+ break;
+ }
+
+ return ret;
+}
+
+/* INMIX dB values */
+static const unsigned int in_mix_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0, 7, TLV_DB_LINEAR_ITEM(-1200, 600),
+};
+
+/* Left In PGA Connections */
+static const struct snd_kcontrol_new wm8990_dapm_lin12_pga_controls[] = {
+SOC_DAPM_SINGLE("LIN1 Switch", WM8990_INPUT_MIXER2, WM8990_LMN1_BIT, 1, 0),
+SOC_DAPM_SINGLE("LIN2 Switch", WM8990_INPUT_MIXER2, WM8990_LMP2_BIT, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8990_dapm_lin34_pga_controls[] = {
+SOC_DAPM_SINGLE("LIN3 Switch", WM8990_INPUT_MIXER2, WM8990_LMN3_BIT, 1, 0),
+SOC_DAPM_SINGLE("LIN4 Switch", WM8990_INPUT_MIXER2, WM8990_LMP4_BIT, 1, 0),
+};
+
+/* Right In PGA Connections */
+static const struct snd_kcontrol_new wm8990_dapm_rin12_pga_controls[] = {
+SOC_DAPM_SINGLE("RIN1 Switch", WM8990_INPUT_MIXER2, WM8990_RMN1_BIT, 1, 0),
+SOC_DAPM_SINGLE("RIN2 Switch", WM8990_INPUT_MIXER2, WM8990_RMP2_BIT, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8990_dapm_rin34_pga_controls[] = {
+SOC_DAPM_SINGLE("RIN3 Switch", WM8990_INPUT_MIXER2, WM8990_RMN3_BIT, 1, 0),
+SOC_DAPM_SINGLE("RIN4 Switch", WM8990_INPUT_MIXER2, WM8990_RMP4_BIT, 1, 0),
+};
+
+/* INMIXL */
+static const struct snd_kcontrol_new wm8990_dapm_inmixl_controls[] = {
+SOC_DAPM_SINGLE_TLV("Record Left Volume", WM8990_INPUT_MIXER3,
+ WM8990_LDBVOL_SHIFT, WM8990_LDBVOL_MASK, 0, in_mix_tlv),
+SOC_DAPM_SINGLE_TLV("LIN2 Volume", WM8990_INPUT_MIXER5, WM8990_LI2BVOL_SHIFT,
+ 7, 0, in_mix_tlv),
+SOC_DAPM_SINGLE("LINPGA12 Switch", WM8990_INPUT_MIXER3, WM8990_L12MNB_BIT,
+ 1, 0),
+SOC_DAPM_SINGLE("LINPGA34 Switch", WM8990_INPUT_MIXER3, WM8990_L34MNB_BIT,
+ 1, 0),
+};
+
+/* INMIXR */
+static const struct snd_kcontrol_new wm8990_dapm_inmixr_controls[] = {
+SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8990_INPUT_MIXER4,
+ WM8990_RDBVOL_SHIFT, WM8990_RDBVOL_MASK, 0, in_mix_tlv),
+SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8990_INPUT_MIXER6, WM8990_RI2BVOL_SHIFT,
+ 7, 0, in_mix_tlv),
+SOC_DAPM_SINGLE("RINPGA12 Switch", WM8990_INPUT_MIXER3, WM8990_L12MNB_BIT,
+ 1, 0),
+SOC_DAPM_SINGLE("RINPGA34 Switch", WM8990_INPUT_MIXER3, WM8990_L34MNB_BIT,
+ 1, 0),
+};
+
+/* AINLMUX */
+static const char *wm8990_ainlmux[] =
+ {"INMIXL Mix", "RXVOICE Mix", "DIFFINL Mix"};
+
+static const struct soc_enum wm8990_ainlmux_enum =
+SOC_ENUM_SINGLE(WM8990_INPUT_MIXER1, WM8990_AINLMODE_SHIFT,
+ ARRAY_SIZE(wm8990_ainlmux), wm8990_ainlmux);
+
+static const struct snd_kcontrol_new wm8990_dapm_ainlmux_controls =
+SOC_DAPM_ENUM("Route", wm8990_ainlmux_enum);
+
+/* DIFFINL */
+
+/* AINRMUX */
+static const char *wm8990_ainrmux[] =
+ {"INMIXR Mix", "RXVOICE Mix", "DIFFINR Mix"};
+
+static const struct soc_enum wm8990_ainrmux_enum =
+SOC_ENUM_SINGLE(WM8990_INPUT_MIXER1, WM8990_AINRMODE_SHIFT,
+ ARRAY_SIZE(wm8990_ainrmux), wm8990_ainrmux);
+
+static const struct snd_kcontrol_new wm8990_dapm_ainrmux_controls =
+SOC_DAPM_ENUM("Route", wm8990_ainrmux_enum);
+
+/* RXVOICE */
+static const struct snd_kcontrol_new wm8990_dapm_rxvoice_controls[] = {
+SOC_DAPM_SINGLE_TLV("LIN4/RXN", WM8990_INPUT_MIXER5, WM8990_LR4BVOL_SHIFT,
+ WM8990_LR4BVOL_MASK, 0, in_mix_tlv),
+SOC_DAPM_SINGLE_TLV("RIN4/RXP", WM8990_INPUT_MIXER6, WM8990_RL4BVOL_SHIFT,
+ WM8990_RL4BVOL_MASK, 0, in_mix_tlv),
+};
+
+/* LOMIX */
+static const struct snd_kcontrol_new wm8990_dapm_lomix_controls[] = {
+SOC_DAPM_SINGLE("LOMIX Right ADC Bypass Switch", WM8990_OUTPUT_MIXER1,
+ WM8990_LRBLO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX Left ADC Bypass Switch", WM8990_OUTPUT_MIXER1,
+ WM8990_LLBLO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX RIN3 Bypass Switch", WM8990_OUTPUT_MIXER1,
+ WM8990_LRI3LO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX LIN3 Bypass Switch", WM8990_OUTPUT_MIXER1,
+ WM8990_LLI3LO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX RIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER1,
+ WM8990_LR12LO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX LIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER1,
+ WM8990_LL12LO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX Left DAC Switch", WM8990_OUTPUT_MIXER1,
+ WM8990_LDLO_BIT, 1, 0),
+};
+
+/* ROMIX */
+static const struct snd_kcontrol_new wm8990_dapm_romix_controls[] = {
+SOC_DAPM_SINGLE("ROMIX Left ADC Bypass Switch", WM8990_OUTPUT_MIXER2,
+ WM8990_RLBRO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX Right ADC Bypass Switch", WM8990_OUTPUT_MIXER2,
+ WM8990_RRBRO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX LIN3 Bypass Switch", WM8990_OUTPUT_MIXER2,
+ WM8990_RLI3RO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX RIN3 Bypass Switch", WM8990_OUTPUT_MIXER2,
+ WM8990_RRI3RO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX LIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER2,
+ WM8990_RL12RO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX RIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER2,
+ WM8990_RR12RO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX Right DAC Switch", WM8990_OUTPUT_MIXER2,
+ WM8990_RDRO_BIT, 1, 0),
+};
+
+/* LONMIX */
+static const struct snd_kcontrol_new wm8990_dapm_lonmix_controls[] = {
+SOC_DAPM_SINGLE("LONMIX Left Mixer PGA Switch", WM8990_LINE_MIXER1,
+ WM8990_LLOPGALON_BIT, 1, 0),
+SOC_DAPM_SINGLE("LONMIX Right Mixer PGA Switch", WM8990_LINE_MIXER1,
+ WM8990_LROPGALON_BIT, 1, 0),
+SOC_DAPM_SINGLE("LONMIX Inverted LOP Switch", WM8990_LINE_MIXER1,
+ WM8990_LOPLON_BIT, 1, 0),
+};
+
+/* LOPMIX */
+static const struct snd_kcontrol_new wm8990_dapm_lopmix_controls[] = {
+SOC_DAPM_SINGLE("LOPMIX Right Mic Bypass Switch", WM8990_LINE_MIXER1,
+ WM8990_LR12LOP_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOPMIX Left Mic Bypass Switch", WM8990_LINE_MIXER1,
+ WM8990_LL12LOP_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOPMIX Left Mixer PGA Switch", WM8990_LINE_MIXER1,
+ WM8990_LLOPGALOP_BIT, 1, 0),
+};
+
+/* RONMIX */
+static const struct snd_kcontrol_new wm8990_dapm_ronmix_controls[] = {
+SOC_DAPM_SINGLE("RONMIX Right Mixer PGA Switch", WM8990_LINE_MIXER2,
+ WM8990_RROPGARON_BIT, 1, 0),
+SOC_DAPM_SINGLE("RONMIX Left Mixer PGA Switch", WM8990_LINE_MIXER2,
+ WM8990_RLOPGARON_BIT, 1, 0),
+SOC_DAPM_SINGLE("RONMIX Inverted ROP Switch", WM8990_LINE_MIXER2,
+ WM8990_ROPRON_BIT, 1, 0),
+};
+
+/* ROPMIX */
+static const struct snd_kcontrol_new wm8990_dapm_ropmix_controls[] = {
+SOC_DAPM_SINGLE("ROPMIX Left Mic Bypass Switch", WM8990_LINE_MIXER2,
+ WM8990_RL12ROP_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROPMIX Right Mic Bypass Switch", WM8990_LINE_MIXER2,
+ WM8990_RR12ROP_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROPMIX Right Mixer PGA Switch", WM8990_LINE_MIXER2,
+ WM8990_RROPGAROP_BIT, 1, 0),
+};
+
+/* OUT3MIX */
+static const struct snd_kcontrol_new wm8990_dapm_out3mix_controls[] = {
+SOC_DAPM_SINGLE("OUT3MIX LIN4/RXP Bypass Switch", WM8990_OUT3_4_MIXER,
+ WM8990_LI4O3_BIT, 1, 0),
+SOC_DAPM_SINGLE("OUT3MIX Left Out PGA Switch", WM8990_OUT3_4_MIXER,
+ WM8990_LPGAO3_BIT, 1, 0),
+};
+
+/* OUT4MIX */
+static const struct snd_kcontrol_new wm8990_dapm_out4mix_controls[] = {
+SOC_DAPM_SINGLE("OUT4MIX Right Out PGA Switch", WM8990_OUT3_4_MIXER,
+ WM8990_RPGAO4_BIT, 1, 0),
+SOC_DAPM_SINGLE("OUT4MIX RIN4/RXP Bypass Switch", WM8990_OUT3_4_MIXER,
+ WM8990_RI4O4_BIT, 1, 0),
+};
+
+/* SPKMIX */
+static const struct snd_kcontrol_new wm8990_dapm_spkmix_controls[] = {
+SOC_DAPM_SINGLE("SPKMIX LIN2 Bypass Switch", WM8990_SPEAKER_MIXER,
+ WM8990_LI2SPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX LADC Bypass Switch", WM8990_SPEAKER_MIXER,
+ WM8990_LB2SPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Left Mixer PGA Switch", WM8990_SPEAKER_MIXER,
+ WM8990_LOPGASPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Left DAC Switch", WM8990_SPEAKER_MIXER,
+ WM8990_LDSPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Right DAC Switch", WM8990_SPEAKER_MIXER,
+ WM8990_RDSPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Right Mixer PGA Switch", WM8990_SPEAKER_MIXER,
+ WM8990_ROPGASPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX RADC Bypass Switch", WM8990_SPEAKER_MIXER,
+ WM8990_RL12ROP_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX RIN2 Bypass Switch", WM8990_SPEAKER_MIXER,
+ WM8990_RI2SPK_BIT, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8990_dapm_widgets[] = {
+/* Input Side */
+/* Input Lines */
+SND_SOC_DAPM_INPUT("LIN1"),
+SND_SOC_DAPM_INPUT("LIN2"),
+SND_SOC_DAPM_INPUT("LIN3"),
+SND_SOC_DAPM_INPUT("LIN4/RXN"),
+SND_SOC_DAPM_INPUT("RIN3"),
+SND_SOC_DAPM_INPUT("RIN4/RXP"),
+SND_SOC_DAPM_INPUT("RIN1"),
+SND_SOC_DAPM_INPUT("RIN2"),
+SND_SOC_DAPM_INPUT("Internal ADC Source"),
+
+/* DACs */
+SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8990_POWER_MANAGEMENT_2,
+ WM8990_ADCL_ENA_BIT, 0),
+SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8990_POWER_MANAGEMENT_2,
+ WM8990_ADCR_ENA_BIT, 0),
+
+/* Input PGAs */
+SND_SOC_DAPM_MIXER("LIN12 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_LIN12_ENA_BIT,
+ 0, &wm8990_dapm_lin12_pga_controls[0],
+ ARRAY_SIZE(wm8990_dapm_lin12_pga_controls)),
+SND_SOC_DAPM_MIXER("LIN34 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_LIN34_ENA_BIT,
+ 0, &wm8990_dapm_lin34_pga_controls[0],
+ ARRAY_SIZE(wm8990_dapm_lin34_pga_controls)),
+SND_SOC_DAPM_MIXER("RIN12 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_RIN12_ENA_BIT,
+ 0, &wm8990_dapm_rin12_pga_controls[0],
+ ARRAY_SIZE(wm8990_dapm_rin12_pga_controls)),
+SND_SOC_DAPM_MIXER("RIN34 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_RIN34_ENA_BIT,
+ 0, &wm8990_dapm_rin34_pga_controls[0],
+ ARRAY_SIZE(wm8990_dapm_rin34_pga_controls)),
+
+/* INMIXL */
+SND_SOC_DAPM_MIXER_E("INMIXL", WM8990_INTDRIVBITS, WM8990_INMIXL_PWR_BIT, 0,
+ &wm8990_dapm_inmixl_controls[0],
+ ARRAY_SIZE(wm8990_dapm_inmixl_controls),
+ inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* AINLMUX */
+SND_SOC_DAPM_MUX_E("AILNMUX", WM8990_INTDRIVBITS, WM8990_AINLMUX_PWR_BIT, 0,
+ &wm8990_dapm_ainlmux_controls, inmixer_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* INMIXR */
+SND_SOC_DAPM_MIXER_E("INMIXR", WM8990_INTDRIVBITS, WM8990_INMIXR_PWR_BIT, 0,
+ &wm8990_dapm_inmixr_controls[0],
+ ARRAY_SIZE(wm8990_dapm_inmixr_controls),
+ inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* AINRMUX */
+SND_SOC_DAPM_MUX_E("AIRNMUX", WM8990_INTDRIVBITS, WM8990_AINRMUX_PWR_BIT, 0,
+ &wm8990_dapm_ainrmux_controls, inmixer_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* Output Side */
+/* DACs */
+SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8990_POWER_MANAGEMENT_3,
+ WM8990_DACL_ENA_BIT, 0),
+SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8990_POWER_MANAGEMENT_3,
+ WM8990_DACR_ENA_BIT, 0),
+
+/* LOMIX */
+SND_SOC_DAPM_MIXER_E("LOMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LOMIX_ENA_BIT,
+ 0, &wm8990_dapm_lomix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_lomix_controls),
+ outmixer_event, SND_SOC_DAPM_PRE_REG),
+
+/* LONMIX */
+SND_SOC_DAPM_MIXER("LONMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LON_ENA_BIT, 0,
+ &wm8990_dapm_lonmix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_lonmix_controls)),
+
+/* LOPMIX */
+SND_SOC_DAPM_MIXER("LOPMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LOP_ENA_BIT, 0,
+ &wm8990_dapm_lopmix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_lopmix_controls)),
+
+/* OUT3MIX */
+SND_SOC_DAPM_MIXER("OUT3MIX", WM8990_POWER_MANAGEMENT_1, WM8990_OUT3_ENA_BIT, 0,
+ &wm8990_dapm_out3mix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_out3mix_controls)),
+
+/* SPKMIX */
+SND_SOC_DAPM_MIXER_E("SPKMIX", WM8990_POWER_MANAGEMENT_1, WM8990_SPK_ENA_BIT, 0,
+ &wm8990_dapm_spkmix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_spkmix_controls), outmixer_event,
+ SND_SOC_DAPM_PRE_REG),
+
+/* OUT4MIX */
+SND_SOC_DAPM_MIXER("OUT4MIX", WM8990_POWER_MANAGEMENT_1, WM8990_OUT4_ENA_BIT, 0,
+ &wm8990_dapm_out4mix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_out4mix_controls)),
+
+/* ROPMIX */
+SND_SOC_DAPM_MIXER("ROPMIX", WM8990_POWER_MANAGEMENT_3, WM8990_ROP_ENA_BIT, 0,
+ &wm8990_dapm_ropmix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_ropmix_controls)),
+
+/* RONMIX */
+SND_SOC_DAPM_MIXER("RONMIX", WM8990_POWER_MANAGEMENT_3, WM8990_RON_ENA_BIT, 0,
+ &wm8990_dapm_ronmix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_ronmix_controls)),
+
+/* ROMIX */
+SND_SOC_DAPM_MIXER_E("ROMIX", WM8990_POWER_MANAGEMENT_3, WM8990_ROMIX_ENA_BIT,
+ 0, &wm8990_dapm_romix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_romix_controls),
+ outmixer_event, SND_SOC_DAPM_PRE_REG),
+
+/* LOUT PGA */
+SND_SOC_DAPM_PGA("LOUT PGA", WM8990_POWER_MANAGEMENT_1, WM8990_LOUT_ENA_BIT, 0,
+ NULL, 0),
+
+/* ROUT PGA */
+SND_SOC_DAPM_PGA("ROUT PGA", WM8990_POWER_MANAGEMENT_1, WM8990_ROUT_ENA_BIT, 0,
+ NULL, 0),
+
+/* LOPGA */
+SND_SOC_DAPM_PGA("LOPGA", WM8990_POWER_MANAGEMENT_3, WM8990_LOPGA_ENA_BIT, 0,
+ NULL, 0),
+
+/* ROPGA */
+SND_SOC_DAPM_PGA("ROPGA", WM8990_POWER_MANAGEMENT_3, WM8990_ROPGA_ENA_BIT, 0,
+ NULL, 0),
+
+/* MICBIAS */
+SND_SOC_DAPM_MICBIAS("MICBIAS", WM8990_POWER_MANAGEMENT_1,
+ WM8990_MICBIAS_ENA_BIT, 0),
+
+SND_SOC_DAPM_OUTPUT("LON"),
+SND_SOC_DAPM_OUTPUT("LOP"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+SND_SOC_DAPM_OUTPUT("LOUT"),
+SND_SOC_DAPM_OUTPUT("SPKN"),
+SND_SOC_DAPM_OUTPUT("SPKP"),
+SND_SOC_DAPM_OUTPUT("ROUT"),
+SND_SOC_DAPM_OUTPUT("OUT4"),
+SND_SOC_DAPM_OUTPUT("ROP"),
+SND_SOC_DAPM_OUTPUT("RON"),
+
+SND_SOC_DAPM_OUTPUT("Internal DAC Sink"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Make DACs turn on when playing even if not mixed into any outputs */
+ {"Internal DAC Sink", NULL, "Left DAC"},
+ {"Internal DAC Sink", NULL, "Right DAC"},
+
+ /* Make ADCs turn on when recording even if not mixed from any inputs */
+ {"Left ADC", NULL, "Internal ADC Source"},
+ {"Right ADC", NULL, "Internal ADC Source"},
+
+ /* Input Side */
+ /* LIN12 PGA */
+ {"LIN12 PGA", "LIN1 Switch", "LIN1"},
+ {"LIN12 PGA", "LIN2 Switch", "LIN2"},
+ /* LIN34 PGA */
+ {"LIN34 PGA", "LIN3 Switch", "LIN3"},
+ {"LIN34 PGA", "LIN4 Switch", "LIN4"},
+ /* INMIXL */
+ {"INMIXL", "Record Left Volume", "LOMIX"},
+ {"INMIXL", "LIN2 Volume", "LIN2"},
+ {"INMIXL", "LINPGA12 Switch", "LIN12 PGA"},
+ {"INMIXL", "LINPGA34 Switch", "LIN34 PGA"},
+ /* AILNMUX */
+ {"AILNMUX", "INMIXL Mix", "INMIXL"},
+ {"AILNMUX", "DIFFINL Mix", "LIN12PGA"},
+ {"AILNMUX", "DIFFINL Mix", "LIN34PGA"},
+ {"AILNMUX", "RXVOICE Mix", "LIN4/RXN"},
+ {"AILNMUX", "RXVOICE Mix", "RIN4/RXP"},
+ /* ADC */
+ {"Left ADC", NULL, "AILNMUX"},
+
+ /* RIN12 PGA */
+ {"RIN12 PGA", "RIN1 Switch", "RIN1"},
+ {"RIN12 PGA", "RIN2 Switch", "RIN2"},
+ /* RIN34 PGA */
+ {"RIN34 PGA", "RIN3 Switch", "RIN3"},
+ {"RIN34 PGA", "RIN4 Switch", "RIN4"},
+ /* INMIXL */
+ {"INMIXR", "Record Right Volume", "ROMIX"},
+ {"INMIXR", "RIN2 Volume", "RIN2"},
+ {"INMIXR", "RINPGA12 Switch", "RIN12 PGA"},
+ {"INMIXR", "RINPGA34 Switch", "RIN34 PGA"},
+ /* AIRNMUX */
+ {"AIRNMUX", "INMIXR Mix", "INMIXR"},
+ {"AIRNMUX", "DIFFINR Mix", "RIN12PGA"},
+ {"AIRNMUX", "DIFFINR Mix", "RIN34PGA"},
+ {"AIRNMUX", "RXVOICE Mix", "RIN4/RXN"},
+ {"AIRNMUX", "RXVOICE Mix", "RIN4/RXP"},
+ /* ADC */
+ {"Right ADC", NULL, "AIRNMUX"},
+
+ /* LOMIX */
+ {"LOMIX", "LOMIX RIN3 Bypass Switch", "RIN3"},
+ {"LOMIX", "LOMIX LIN3 Bypass Switch", "LIN3"},
+ {"LOMIX", "LOMIX LIN12 PGA Bypass Switch", "LIN12 PGA"},
+ {"LOMIX", "LOMIX RIN12 PGA Bypass Switch", "RIN12 PGA"},
+ {"LOMIX", "LOMIX Right ADC Bypass Switch", "AINRMUX"},
+ {"LOMIX", "LOMIX Left ADC Bypass Switch", "AINLMUX"},
+ {"LOMIX", "LOMIX Left DAC Switch", "Left DAC"},
+
+ /* ROMIX */
+ {"ROMIX", "ROMIX RIN3 Bypass Switch", "RIN3"},
+ {"ROMIX", "ROMIX LIN3 Bypass Switch", "LIN3"},
+ {"ROMIX", "ROMIX LIN12 PGA Bypass Switch", "LIN12 PGA"},
+ {"ROMIX", "ROMIX RIN12 PGA Bypass Switch", "RIN12 PGA"},
+ {"ROMIX", "ROMIX Right ADC Bypass Switch", "AINRMUX"},
+ {"ROMIX", "ROMIX Left ADC Bypass Switch", "AINLMUX"},
+ {"ROMIX", "ROMIX Right DAC Switch", "Right DAC"},
+
+ /* SPKMIX */
+ {"SPKMIX", "SPKMIX LIN2 Bypass Switch", "LIN2"},
+ {"SPKMIX", "SPKMIX RIN2 Bypass Switch", "RIN2"},
+ {"SPKMIX", "SPKMIX LADC Bypass Switch", "AINLMUX"},
+ {"SPKMIX", "SPKMIX RADC Bypass Switch", "AINRMUX"},
+ {"SPKMIX", "SPKMIX Left Mixer PGA Switch", "LOPGA"},
+ {"SPKMIX", "SPKMIX Right Mixer PGA Switch", "ROPGA"},
+ {"SPKMIX", "SPKMIX Right DAC Switch", "Right DAC"},
+ {"SPKMIX", "SPKMIX Left DAC Switch", "Left DAC"},
+
+ /* LONMIX */
+ {"LONMIX", "LONMIX Left Mixer PGA Switch", "LOPGA"},
+ {"LONMIX", "LONMIX Right Mixer PGA Switch", "ROPGA"},
+ {"LONMIX", "LONMIX Inverted LOP Switch", "LOPMIX"},
+
+ /* LOPMIX */
+ {"LOPMIX", "LOPMIX Right Mic Bypass Switch", "RIN12 PGA"},
+ {"LOPMIX", "LOPMIX Left Mic Bypass Switch", "LIN12 PGA"},
+ {"LOPMIX", "LOPMIX Left Mixer PGA Switch", "LOPGA"},
+
+ /* OUT3MIX */
+ {"OUT3MIX", "OUT3MIX LIN4/RXP Bypass Switch", "LIN4/RXP"},
+ {"OUT3MIX", "OUT3MIX Left Out PGA Switch", "LOPGA"},
+
+ /* OUT4MIX */
+ {"OUT4MIX", "OUT4MIX Right Out PGA Switch", "ROPGA"},
+ {"OUT4MIX", "OUT4MIX RIN4/RXP Bypass Switch", "RIN4/RXP"},
+
+ /* RONMIX */
+ {"RONMIX", "RONMIX Right Mixer PGA Switch", "ROPGA"},
+ {"RONMIX", "RONMIX Left Mixer PGA Switch", "LOPGA"},
+ {"RONMIX", "RONMIX Inverted ROP Switch", "ROPMIX"},
+
+ /* ROPMIX */
+ {"ROPMIX", "ROPMIX Left Mic Bypass Switch", "LIN12 PGA"},
+ {"ROPMIX", "ROPMIX Right Mic Bypass Switch", "RIN12 PGA"},
+ {"ROPMIX", "ROPMIX Right Mixer PGA Switch", "ROPGA"},
+
+ /* Out Mixer PGAs */
+ {"LOPGA", NULL, "LOMIX"},
+ {"ROPGA", NULL, "ROMIX"},
+
+ {"LOUT PGA", NULL, "LOMIX"},
+ {"ROUT PGA", NULL, "ROMIX"},
+
+ /* Output Pins */
+ {"LON", NULL, "LONMIX"},
+ {"LOP", NULL, "LOPMIX"},
+ {"OUT", NULL, "OUT3MIX"},
+ {"LOUT", NULL, "LOUT PGA"},
+ {"SPKN", NULL, "SPKMIX"},
+ {"ROUT", NULL, "ROUT PGA"},
+ {"OUT4", NULL, "OUT4MIX"},
+ {"ROP", NULL, "ROPMIX"},
+ {"RON", NULL, "RONMIX"},
+};
+
+static int wm8990_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm8990_dapm_widgets,
+ ARRAY_SIZE(wm8990_dapm_widgets));
+
+ /* set up the WM8990 audio map */
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+/* PLL divisors */
+struct _pll_div {
+ u32 div2;
+ u32 n;
+ u32 k;
+};
+
+/* The size in bits of the pll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 16) * 10)
+
+static void pll_factors(struct _pll_div *pll_div, unsigned int target,
+ unsigned int source)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod;
+
+
+ Ndiv = target / source;
+ if (Ndiv < 6) {
+ source >>= 1;
+ pll_div->div2 = 1;
+ Ndiv = target / source;
+ } else
+ pll_div->div2 = 0;
+
+ if ((Ndiv < 6) || (Ndiv > 12))
+ printk(KERN_WARNING
+ "WM8990 N value outwith recommended range! N = %d\n", Ndiv);
+
+ pll_div->n = Ndiv;
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ /* Check if we need to round */
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ K /= 10;
+
+ pll_div->k = K;
+}
+
+static int wm8990_set_dai_pll(struct snd_soc_dai *codec_dai,
+ int pll_id, unsigned int freq_in, unsigned int freq_out)
+{
+ u16 reg;
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct _pll_div pll_div;
+
+ if (freq_in && freq_out) {
+ pll_factors(&pll_div, freq_out * 4, freq_in);
+
+ /* Turn on PLL */
+ reg = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_2);
+ reg |= WM8990_PLL_ENA;
+ wm8990_write(codec, WM8990_POWER_MANAGEMENT_2, reg);
+
+ /* sysclk comes from PLL */
+ reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2);
+ wm8990_write(codec, WM8990_CLOCKING_2, reg | WM8990_SYSCLK_SRC);
+
+ /* set up N , fractional mode and pre-divisor if neccessary */
+ wm8990_write(codec, WM8990_PLL1, pll_div.n | WM8990_SDM |
+ (pll_div.div2?WM8990_PRESCALE:0));
+ wm8990_write(codec, WM8990_PLL2, (u8)(pll_div.k>>8));
+ wm8990_write(codec, WM8990_PLL3, (u8)(pll_div.k & 0xFF));
+ } else {
+ /* Turn on PLL */
+ reg = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_2);
+ reg &= ~WM8990_PLL_ENA;
+ wm8990_write(codec, WM8990_POWER_MANAGEMENT_2, reg);
+ }
+ return 0;
+}
+
+/*
+ * Clock after PLL and dividers
+ */
+static int wm8990_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 wm8990_priv *wm8990 = codec->private_data;
+
+ wm8990->sysclk = freq;
+ return 0;
+}
+
+/*
+ * Set's ADC and Voice DAC format.
+ */
+static int wm8990_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 audio1, audio3;
+
+ audio1 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_1);
+ audio3 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_3);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ audio3 &= ~WM8990_AIF_MSTR1;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ audio3 |= WM8990_AIF_MSTR1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ audio1 &= ~WM8990_AIF_FMT_MASK;
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ audio1 |= WM8990_AIF_TMF_I2S;
+ audio1 &= ~WM8990_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ audio1 |= WM8990_AIF_TMF_RIGHTJ;
+ audio1 &= ~WM8990_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ audio1 |= WM8990_AIF_TMF_LEFTJ;
+ audio1 &= ~WM8990_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ audio1 |= WM8990_AIF_TMF_DSP;
+ audio1 &= ~WM8990_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ audio1 |= WM8990_AIF_TMF_DSP | WM8990_AIF_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8990_write(codec, WM8990_AUDIO_INTERFACE_1, audio1);
+ wm8990_write(codec, WM8990_AUDIO_INTERFACE_3, audio3);
+ return 0;
+}
+
+static int wm8990_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ switch (div_id) {
+ case WM8990_MCLK_DIV:
+ reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2) &
+ ~WM8990_MCLK_DIV_MASK;
+ wm8990_write(codec, WM8990_CLOCKING_2, reg | div);
+ break;
+ case WM8990_DACCLK_DIV:
+ reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2) &
+ ~WM8990_DAC_CLKDIV_MASK;
+ wm8990_write(codec, WM8990_CLOCKING_2, reg | div);
+ break;
+ case WM8990_ADCCLK_DIV:
+ reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2) &
+ ~WM8990_ADC_CLKDIV_MASK;
+ wm8990_write(codec, WM8990_CLOCKING_2, reg | div);
+ break;
+ case WM8990_BCLK_DIV:
+ reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_1) &
+ ~WM8990_BCLK_DIV_MASK;
+ wm8990_write(codec, WM8990_CLOCKING_1, reg | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8990_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 audio1 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_1);
+
+ audio1 &= ~WM8990_AIF_WL_MASK;
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ audio1 |= WM8990_AIF_WL_20BITS;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ audio1 |= WM8990_AIF_WL_24BITS;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ audio1 |= WM8990_AIF_WL_32BITS;
+ break;
+ }
+
+ wm8990_write(codec, WM8990_AUDIO_INTERFACE_1, audio1);
+ return 0;
+}
+
+static int wm8990_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 val;
+
+ val = wm8990_read_reg_cache(codec, WM8990_DAC_CTRL) & ~WM8990_DAC_MUTE;
+
+ if (mute)
+ wm8990_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE);
+ else
+ wm8990_write(codec, WM8990_DAC_CTRL, val);
+
+ return 0;
+}
+
+static int wm8990_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 val;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->bias_level == SND_SOC_BIAS_OFF) {
+ /* Enable all output discharge bits */
+ wm8990_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE |
+ WM8990_DIS_RLINE | WM8990_DIS_OUT3 |
+ WM8990_DIS_OUT4 | WM8990_DIS_LOUT |
+ WM8990_DIS_ROUT);
+
+ /* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */
+ wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
+ WM8990_BUFDCOPEN | WM8990_POBCTRL |
+ WM8990_VMIDTOG);
+
+ /* Delay to allow output caps to discharge */
+ msleep(msecs_to_jiffies(300));
+
+ /* Disable VMIDTOG */
+ wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
+ WM8990_BUFDCOPEN | WM8990_POBCTRL);
+
+ /* disable all output discharge bits */
+ wm8990_write(codec, WM8990_ANTIPOP1, 0);
+
+ /* Enable outputs */
+ wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1b00);
+
+ msleep(msecs_to_jiffies(50));
+
+ /* Enable VMID at 2x50k */
+ wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f02);
+
+ msleep(msecs_to_jiffies(100));
+
+ /* Enable VREF */
+ wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03);
+
+ msleep(msecs_to_jiffies(600));
+
+ /* Enable BUFIOEN */
+ wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
+ WM8990_BUFDCOPEN | WM8990_POBCTRL |
+ WM8990_BUFIOEN);
+
+ /* Disable outputs */
+ wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x3);
+
+ /* disable POBCTRL, SOFT_ST and BUFDCOPEN */
+ wm8990_write(codec, WM8990_ANTIPOP2, WM8990_BUFIOEN);
+ } else {
+ /* ON -> standby */
+
+ }
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ /* Enable POBCTRL and SOFT_ST */
+ wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
+ WM8990_POBCTRL | WM8990_BUFIOEN);
+
+ /* Enable POBCTRL, SOFT_ST and BUFDCOPEN */
+ wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
+ WM8990_BUFDCOPEN | WM8990_POBCTRL |
+ WM8990_BUFIOEN);
+
+ /* mute DAC */
+ val = wm8990_read_reg_cache(codec, WM8990_DAC_CTRL);
+ wm8990_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE);
+
+ /* Enable any disabled outputs */
+ wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03);
+
+ /* Disable VMID */
+ wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f01);
+
+ msleep(msecs_to_jiffies(300));
+
+ /* Enable all output discharge bits */
+ wm8990_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE |
+ WM8990_DIS_RLINE | WM8990_DIS_OUT3 |
+ WM8990_DIS_OUT4 | WM8990_DIS_LOUT |
+ WM8990_DIS_ROUT);
+
+ /* Disable VREF */
+ wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x0);
+
+ /* disable POBCTRL, SOFT_ST and BUFDCOPEN */
+ wm8990_write(codec, WM8990_ANTIPOP2, 0x0);
+ break;
+ }
+
+ codec->bias_level = level;
+ return 0;
+}
+
+#define WM8990_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000)
+
+#define WM8990_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+/*
+ * The WM8990 supports 2 different and mutually exclusive DAI
+ * configurations.
+ *
+ * 1. ADC/DAC on Primary Interface
+ * 2. ADC on Primary Interface/DAC on secondary
+ */
+struct snd_soc_dai wm8990_dai = {
+/* ADC/DAC on primary */
+ .name = "WM8990 ADC/DAC Primary",
+ .id = 1,
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8990_RATES,
+ .formats = WM8990_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8990_RATES,
+ .formats = WM8990_FORMATS,},
+ .ops = {
+ .hw_params = wm8990_hw_params,},
+ .dai_ops = {
+ .digital_mute = wm8990_mute,
+ .set_fmt = wm8990_set_dai_fmt,
+ .set_clkdiv = wm8990_set_dai_clkdiv,
+ .set_pll = wm8990_set_dai_pll,
+ .set_sysclk = wm8990_set_dai_sysclk,
+ },
+};
+EXPORT_SYMBOL_GPL(wm8990_dai);
+
+static int wm8990_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ /* we only need to suspend if we are a valid card */
+ if (!codec->card)
+ return 0;
+
+ wm8990_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8990_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+
+ /* we only need to resume if we are a valid card */
+ if (!codec->card)
+ return 0;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8990_reg); i++) {
+ if (i + 1 == WM8990_RESET)
+ continue;
+ data[0] = ((i + 1) << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+
+ wm8990_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+
+/*
+ * initialise the WM8990 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int wm8990_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 reg;
+ int ret = 0;
+
+ codec->name = "WM8990";
+ codec->owner = THIS_MODULE;
+ codec->read = wm8990_read_reg_cache;
+ codec->write = wm8990_write;
+ codec->set_bias_level = wm8990_set_bias_level;
+ codec->dai = &wm8990_dai;
+ codec->num_dai = 2;
+ codec->reg_cache_size = ARRAY_SIZE(wm8990_reg);
+ codec->reg_cache = kmemdup(wm8990_reg, sizeof(wm8990_reg), GFP_KERNEL);
+
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ wm8990_reset(codec);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8990: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* charge output caps */
+ codec->bias_level = SND_SOC_BIAS_OFF;
+ wm8990_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ reg = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_4);
+ wm8990_write(codec, WM8990_AUDIO_INTERFACE_4, reg | WM8990_ALRCGPIO1);
+
+ reg = wm8990_read_reg_cache(codec, WM8990_GPIO1_GPIO2) &
+ ~WM8990_GPIO1_SEL_MASK;
+ wm8990_write(codec, WM8990_GPIO1_GPIO2, reg | 1);
+
+ reg = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_2);
+ wm8990_write(codec, WM8990_POWER_MANAGEMENT_2, reg | WM8990_OPCLK_ENA);
+
+ wm8990_write(codec, WM8990_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8));
+ wm8990_write(codec, WM8990_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8));
+
+ wm8990_add_controls(codec);
+ wm8990_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8990: failed to register card\n");
+ goto card_err;
+ }
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+ return ret;
+}
+
+/* If the i2c layer weren't so broken, we could pass this kind of data
+ around */
+static struct snd_soc_device *wm8990_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+
+/*
+ * WM891 2 wire address is determined by GPIO5
+ * state during powerup.
+ * low = 0x34
+ * high = 0x36
+ */
+
+static int wm8990_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = wm8990_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = wm8990_init(socdev);
+ if (ret < 0)
+ pr_err("failed to initialise WM8990\n");
+
+ return ret;
+}
+
+static int wm8990_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+static const struct i2c_device_id wm8990_i2c_id[] = {
+ { "wm8990", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8990_i2c_id);
+
+static struct i2c_driver wm8990_i2c_driver = {
+ .driver = {
+ .name = "WM8990 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8990_i2c_probe,
+ .remove = wm8990_i2c_remove,
+ .id_table = wm8990_i2c_id,
+};
+
+static int wm8990_add_i2c_device(struct platform_device *pdev,
+ const struct wm8990_setup_data *setup)
+{
+ struct i2c_board_info info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+ int ret;
+
+ ret = i2c_add_driver(&wm8990_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ return ret;
+ }
+
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ info.addr = setup->i2c_address;
+ strlcpy(info.type, "wm8990", I2C_NAME_SIZE);
+
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "can't get i2c adapter %d\n",
+ setup->i2c_bus);
+ goto err_driver;
+ }
+
+ client = i2c_new_device(adapter, &info);
+ i2c_put_adapter(adapter);
+ if (!client) {
+ dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
+ (unsigned int)info.addr);
+ goto err_driver;
+ }
+
+ return 0;
+
+err_driver:
+ i2c_del_driver(&wm8990_i2c_driver);
+ return -ENODEV;
+}
+#endif
+
+static int wm8990_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct wm8990_setup_data *setup;
+ struct snd_soc_codec *codec;
+ struct wm8990_priv *wm8990;
+ int ret;
+
+ pr_info("WM8990 Audio Codec %s\n", WM8990_VERSION);
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ wm8990 = kzalloc(sizeof(struct wm8990_priv), GFP_KERNEL);
+ if (wm8990 == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->private_data = wm8990;
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+ wm8990_socdev = socdev;
+
+ ret = -ENODEV;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = wm8990_add_i2c_device(pdev, setup);
+ }
+#endif
+
+ if (ret != 0) {
+ kfree(codec->private_data);
+ kfree(codec);
+ }
+ return ret;
+}
+
+/* power down chip */
+static int wm8990_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec->control_data)
+ wm8990_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_unregister_device(codec->control_data);
+ i2c_del_driver(&wm8990_i2c_driver);
+#endif
+ kfree(codec->private_data);
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8990 = {
+ .probe = wm8990_probe,
+ .remove = wm8990_remove,
+ .suspend = wm8990_suspend,
+ .resume = wm8990_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8990);
+
+MODULE_DESCRIPTION("ASoC WM8990 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8990.h b/sound/soc/codecs/wm8990.h
new file mode 100644
index 0000000..0e192f3
--- /dev/null
+++ b/sound/soc/codecs/wm8990.h
@@ -0,0 +1,843 @@
+/*
+ * wm8990.h -- audio driver for WM8990
+ *
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ * graeme.gregory@wolfsonmicro.com or linux@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 __WM8990REGISTERDEFS_H__
+#define __WM8990REGISTERDEFS_H__
+
+/*
+ * Register values.
+ */
+#define WM8990_RESET 0x00
+#define WM8990_POWER_MANAGEMENT_1 0x01
+#define WM8990_POWER_MANAGEMENT_2 0x02
+#define WM8990_POWER_MANAGEMENT_3 0x03
+#define WM8990_AUDIO_INTERFACE_1 0x04
+#define WM8990_AUDIO_INTERFACE_2 0x05
+#define WM8990_CLOCKING_1 0x06
+#define WM8990_CLOCKING_2 0x07
+#define WM8990_AUDIO_INTERFACE_3 0x08
+#define WM8990_AUDIO_INTERFACE_4 0x09
+#define WM8990_DAC_CTRL 0x0A
+#define WM8990_LEFT_DAC_DIGITAL_VOLUME 0x0B
+#define WM8990_RIGHT_DAC_DIGITAL_VOLUME 0x0C
+#define WM8990_DIGITAL_SIDE_TONE 0x0D
+#define WM8990_ADC_CTRL 0x0E
+#define WM8990_LEFT_ADC_DIGITAL_VOLUME 0x0F
+#define WM8990_RIGHT_ADC_DIGITAL_VOLUME 0x10
+#define WM8990_GPIO_CTRL_1 0x12
+#define WM8990_GPIO1_GPIO2 0x13
+#define WM8990_GPIO3_GPIO4 0x14
+#define WM8990_GPIO5_GPIO6 0x15
+#define WM8990_GPIOCTRL_2 0x16
+#define WM8990_GPIO_POL 0x17
+#define WM8990_LEFT_LINE_INPUT_1_2_VOLUME 0x18
+#define WM8990_LEFT_LINE_INPUT_3_4_VOLUME 0x19
+#define WM8990_RIGHT_LINE_INPUT_1_2_VOLUME 0x1A
+#define WM8990_RIGHT_LINE_INPUT_3_4_VOLUME 0x1B
+#define WM8990_LEFT_OUTPUT_VOLUME 0x1C
+#define WM8990_RIGHT_OUTPUT_VOLUME 0x1D
+#define WM8990_LINE_OUTPUTS_VOLUME 0x1E
+#define WM8990_OUT3_4_VOLUME 0x1F
+#define WM8990_LEFT_OPGA_VOLUME 0x20
+#define WM8990_RIGHT_OPGA_VOLUME 0x21
+#define WM8990_SPEAKER_VOLUME 0x22
+#define WM8990_CLASSD1 0x23
+#define WM8990_CLASSD3 0x25
+#define WM8990_CLASSD4 0x26
+#define WM8990_INPUT_MIXER1 0x27
+#define WM8990_INPUT_MIXER2 0x28
+#define WM8990_INPUT_MIXER3 0x29
+#define WM8990_INPUT_MIXER4 0x2A
+#define WM8990_INPUT_MIXER5 0x2B
+#define WM8990_INPUT_MIXER6 0x2C
+#define WM8990_OUTPUT_MIXER1 0x2D
+#define WM8990_OUTPUT_MIXER2 0x2E
+#define WM8990_OUTPUT_MIXER3 0x2F
+#define WM8990_OUTPUT_MIXER4 0x30
+#define WM8990_OUTPUT_MIXER5 0x31
+#define WM8990_OUTPUT_MIXER6 0x32
+#define WM8990_OUT3_4_MIXER 0x33
+#define WM8990_LINE_MIXER1 0x34
+#define WM8990_LINE_MIXER2 0x35
+#define WM8990_SPEAKER_MIXER 0x36
+#define WM8990_ADDITIONAL_CONTROL 0x37
+#define WM8990_ANTIPOP1 0x38
+#define WM8990_ANTIPOP2 0x39
+#define WM8990_MICBIAS 0x3A
+#define WM8990_PLL1 0x3C
+#define WM8990_PLL2 0x3D
+#define WM8990_PLL3 0x3E
+#define WM8990_INTDRIVBITS 0x3F
+
+#define WM8990_REGISTER_COUNT 60
+#define WM8990_MAX_REGISTER 0x3F
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - Reset
+ */
+#define WM8990_SW_RESET_CHIP_ID_MASK 0xFFFF /* SW_RESET_CHIP_ID */
+
+/*
+ * R1 (0x01) - Power Management (1)
+ */
+#define WM8990_SPK_ENA 0x1000 /* SPK_ENA */
+#define WM8990_SPK_ENA_BIT 12
+#define WM8990_OUT3_ENA 0x0800 /* OUT3_ENA */
+#define WM8990_OUT3_ENA_BIT 11
+#define WM8990_OUT4_ENA 0x0400 /* OUT4_ENA */
+#define WM8990_OUT4_ENA_BIT 10
+#define WM8990_LOUT_ENA 0x0200 /* LOUT_ENA */
+#define WM8990_LOUT_ENA_BIT 9
+#define WM8990_ROUT_ENA 0x0100 /* ROUT_ENA */
+#define WM8990_ROUT_ENA_BIT 8
+#define WM8990_MICBIAS_ENA 0x0010 /* MICBIAS_ENA */
+#define WM8990_MICBIAS_ENA_BIT 4
+#define WM8990_VMID_MODE_MASK 0x0006 /* VMID_MODE - [2:1] */
+#define WM8990_VREF_ENA 0x0001 /* VREF_ENA */
+#define WM8990_VREF_ENA_BIT 0
+
+/*
+ * R2 (0x02) - Power Management (2)
+ */
+#define WM8990_PLL_ENA 0x8000 /* PLL_ENA */
+#define WM8990_PLL_ENA_BIT 15
+#define WM8990_TSHUT_ENA 0x4000 /* TSHUT_ENA */
+#define WM8990_TSHUT_ENA_BIT 14
+#define WM8990_TSHUT_OPDIS 0x2000 /* TSHUT_OPDIS */
+#define WM8990_TSHUT_OPDIS_BIT 13
+#define WM8990_OPCLK_ENA 0x0800 /* OPCLK_ENA */
+#define WM8990_OPCLK_ENA_BIT 11
+#define WM8990_AINL_ENA 0x0200 /* AINL_ENA */
+#define WM8990_AINL_ENA_BIT 9
+#define WM8990_AINR_ENA 0x0100 /* AINR_ENA */
+#define WM8990_AINR_ENA_BIT 8
+#define WM8990_LIN34_ENA 0x0080 /* LIN34_ENA */
+#define WM8990_LIN34_ENA_BIT 7
+#define WM8990_LIN12_ENA 0x0040 /* LIN12_ENA */
+#define WM8990_LIN12_ENA_BIT 6
+#define WM8990_RIN34_ENA 0x0020 /* RIN34_ENA */
+#define WM8990_RIN34_ENA_BIT 5
+#define WM8990_RIN12_ENA 0x0010 /* RIN12_ENA */
+#define WM8990_RIN12_ENA_BIT 4
+#define WM8990_ADCL_ENA 0x0002 /* ADCL_ENA */
+#define WM8990_ADCL_ENA_BIT 1
+#define WM8990_ADCR_ENA 0x0001 /* ADCR_ENA */
+#define WM8990_ADCR_ENA_BIT 0
+
+/*
+ * R3 (0x03) - Power Management (3)
+ */
+#define WM8990_LON_ENA 0x2000 /* LON_ENA */
+#define WM8990_LON_ENA_BIT 13
+#define WM8990_LOP_ENA 0x1000 /* LOP_ENA */
+#define WM8990_LOP_ENA_BIT 12
+#define WM8990_RON_ENA 0x0800 /* RON_ENA */
+#define WM8990_RON_ENA_BIT 11
+#define WM8990_ROP_ENA 0x0400 /* ROP_ENA */
+#define WM8990_ROP_ENA_BIT 10
+#define WM8990_LOPGA_ENA 0x0080 /* LOPGA_ENA */
+#define WM8990_LOPGA_ENA_BIT 7
+#define WM8990_ROPGA_ENA 0x0040 /* ROPGA_ENA */
+#define WM8990_ROPGA_ENA_BIT 6
+#define WM8990_LOMIX_ENA 0x0020 /* LOMIX_ENA */
+#define WM8990_LOMIX_ENA_BIT 5
+#define WM8990_ROMIX_ENA 0x0010 /* ROMIX_ENA */
+#define WM8990_ROMIX_ENA_BIT 4
+#define WM8990_DACL_ENA 0x0002 /* DACL_ENA */
+#define WM8990_DACL_ENA_BIT 1
+#define WM8990_DACR_ENA 0x0001 /* DACR_ENA */
+#define WM8990_DACR_ENA_BIT 0
+
+/*
+ * R4 (0x04) - Audio Interface (1)
+ */
+#define WM8990_AIFADCL_SRC 0x8000 /* AIFADCL_SRC */
+#define WM8990_AIFADCR_SRC 0x4000 /* AIFADCR_SRC */
+#define WM8990_AIFADC_TDM 0x2000 /* AIFADC_TDM */
+#define WM8990_AIFADC_TDM_CHAN 0x1000 /* AIFADC_TDM_CHAN */
+#define WM8990_AIF_BCLK_INV 0x0100 /* AIF_BCLK_INV */
+#define WM8990_AIF_LRCLK_INV 0x0080 /* AIF_LRCLK_INV */
+#define WM8990_AIF_WL_MASK 0x0060 /* AIF_WL - [6:5] */
+#define WM8990_AIF_WL_16BITS (0 << 5)
+#define WM8990_AIF_WL_20BITS (1 << 5)
+#define WM8990_AIF_WL_24BITS (2 << 5)
+#define WM8990_AIF_WL_32BITS (3 << 5)
+#define WM8990_AIF_FMT_MASK 0x0018 /* AIF_FMT - [4:3] */
+#define WM8990_AIF_TMF_RIGHTJ (0 << 3)
+#define WM8990_AIF_TMF_LEFTJ (1 << 3)
+#define WM8990_AIF_TMF_I2S (2 << 3)
+#define WM8990_AIF_TMF_DSP (3 << 3)
+
+/*
+ * R5 (0x05) - Audio Interface (2)
+ */
+#define WM8990_DACL_SRC 0x8000 /* DACL_SRC */
+#define WM8990_DACR_SRC 0x4000 /* DACR_SRC */
+#define WM8990_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */
+#define WM8990_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */
+#define WM8990_DAC_BOOST_MASK 0x0C00 /* DAC_BOOST */
+#define WM8990_DAC_COMP 0x0010 /* DAC_COMP */
+#define WM8990_DAC_COMPMODE 0x0008 /* DAC_COMPMODE */
+#define WM8990_ADC_COMP 0x0004 /* ADC_COMP */
+#define WM8990_ADC_COMPMODE 0x0002 /* ADC_COMPMODE */
+#define WM8990_LOOPBACK 0x0001 /* LOOPBACK */
+
+/*
+ * R6 (0x06) - Clocking (1)
+ */
+#define WM8990_TOCLK_RATE 0x8000 /* TOCLK_RATE */
+#define WM8990_TOCLK_ENA 0x4000 /* TOCLK_ENA */
+#define WM8990_OPCLKDIV_MASK 0x1E00 /* OPCLKDIV - [12:9] */
+#define WM8990_DCLKDIV_MASK 0x01C0 /* DCLKDIV - [8:6] */
+#define WM8990_BCLK_DIV_MASK 0x001E /* BCLK_DIV - [4:1] */
+#define WM8990_BCLK_DIV_1 (0x0 << 1)
+#define WM8990_BCLK_DIV_1_5 (0x1 << 1)
+#define WM8990_BCLK_DIV_2 (0x2 << 1)
+#define WM8990_BCLK_DIV_3 (0x3 << 1)
+#define WM8990_BCLK_DIV_4 (0x4 << 1)
+#define WM8990_BCLK_DIV_5_5 (0x5 << 1)
+#define WM8990_BCLK_DIV_6 (0x6 << 1)
+#define WM8990_BCLK_DIV_8 (0x7 << 1)
+#define WM8990_BCLK_DIV_11 (0x8 << 1)
+#define WM8990_BCLK_DIV_12 (0x9 << 1)
+#define WM8990_BCLK_DIV_16 (0xA << 1)
+#define WM8990_BCLK_DIV_22 (0xB << 1)
+#define WM8990_BCLK_DIV_24 (0xC << 1)
+#define WM8990_BCLK_DIV_32 (0xD << 1)
+#define WM8990_BCLK_DIV_44 (0xE << 1)
+#define WM8990_BCLK_DIV_48 (0xF << 1)
+
+/*
+ * R7 (0x07) - Clocking (2)
+ */
+#define WM8990_MCLK_SRC 0x8000 /* MCLK_SRC */
+#define WM8990_SYSCLK_SRC 0x4000 /* SYSCLK_SRC */
+#define WM8990_CLK_FORCE 0x2000 /* CLK_FORCE */
+#define WM8990_MCLK_DIV_MASK 0x1800 /* MCLK_DIV - [12:11] */
+#define WM8990_MCLK_DIV_1 (0 << 11)
+#define WM8990_MCLK_DIV_2 (2 << 11)
+#define WM8990_MCLK_INV 0x0400 /* MCLK_INV */
+#define WM8990_ADC_CLKDIV_MASK 0x00E0 /* ADC_CLKDIV */
+#define WM8990_ADC_CLKDIV_1 (0 << 5)
+#define WM8990_ADC_CLKDIV_1_5 (1 << 5)
+#define WM8990_ADC_CLKDIV_2 (2 << 5)
+#define WM8990_ADC_CLKDIV_3 (3 << 5)
+#define WM8990_ADC_CLKDIV_4 (4 << 5)
+#define WM8990_ADC_CLKDIV_5_5 (5 << 5)
+#define WM8990_ADC_CLKDIV_6 (6 << 5)
+#define WM8990_DAC_CLKDIV_MASK 0x001C /* DAC_CLKDIV - [4:2] */
+#define WM8990_DAC_CLKDIV_1 (0 << 2)
+#define WM8990_DAC_CLKDIV_1_5 (1 << 2)
+#define WM8990_DAC_CLKDIV_2 (2 << 2)
+#define WM8990_DAC_CLKDIV_3 (3 << 2)
+#define WM8990_DAC_CLKDIV_4 (4 << 2)
+#define WM8990_DAC_CLKDIV_5_5 (5 << 2)
+#define WM8990_DAC_CLKDIV_6 (6 << 2)
+
+/*
+ * R8 (0x08) - Audio Interface (3)
+ */
+#define WM8990_AIF_MSTR1 0x8000 /* AIF_MSTR1 */
+#define WM8990_AIF_MSTR2 0x4000 /* AIF_MSTR2 */
+#define WM8990_AIF_SEL 0x2000 /* AIF_SEL */
+#define WM8990_ADCLRC_DIR 0x0800 /* ADCLRC_DIR */
+#define WM8990_ADCLRC_RATE_MASK 0x07FF /* ADCLRC_RATE */
+
+/*
+ * R9 (0x09) - Audio Interface (4)
+ */
+#define WM8990_ALRCGPIO1 0x8000 /* ALRCGPIO1 */
+#define WM8990_ALRCBGPIO6 0x4000 /* ALRCBGPIO6 */
+#define WM8990_AIF_TRIS 0x2000 /* AIF_TRIS */
+#define WM8990_DACLRC_DIR 0x0800 /* DACLRC_DIR */
+#define WM8990_DACLRC_RATE_MASK 0x07FF /* DACLRC_RATE */
+
+/*
+ * R10 (0x0A) - DAC CTRL
+ */
+#define WM8990_AIF_LRCLKRATE 0x0400 /* AIF_LRCLKRATE */
+#define WM8990_DAC_MONO 0x0200 /* DAC_MONO */
+#define WM8990_DAC_SB_FILT 0x0100 /* DAC_SB_FILT */
+#define WM8990_DAC_MUTERATE 0x0080 /* DAC_MUTERATE */
+#define WM8990_DAC_MUTEMODE 0x0040 /* DAC_MUTEMODE */
+#define WM8990_DEEMP_MASK 0x0030 /* DEEMP - [5:4] */
+#define WM8990_DAC_MUTE 0x0004 /* DAC_MUTE */
+#define WM8990_DACL_DATINV 0x0002 /* DACL_DATINV */
+#define WM8990_DACR_DATINV 0x0001 /* DACR_DATINV */
+
+/*
+ * R11 (0x0B) - Left DAC Digital Volume
+ */
+#define WM8990_DAC_VU 0x0100 /* DAC_VU */
+#define WM8990_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */
+#define WM8990_DACL_VOL_SHIFT 0
+/*
+ * R12 (0x0C) - Right DAC Digital Volume
+ */
+#define WM8990_DAC_VU 0x0100 /* DAC_VU */
+#define WM8990_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */
+#define WM8990_DACR_VOL_SHIFT 0
+/*
+ * R13 (0x0D) - Digital Side Tone
+ */
+#define WM8990_ADCL_DAC_SVOL_MASK 0x0F /* ADCL_DAC_SVOL */
+#define WM8990_ADCL_DAC_SVOL_SHIFT 9
+#define WM8990_ADCR_DAC_SVOL_MASK 0x0F /* ADCR_DAC_SVOL */
+#define WM8990_ADCR_DAC_SVOL_SHIFT 5
+#define WM8990_ADC_TO_DACL_MASK 0x03 /* ADC_TO_DACL - [3:2] */
+#define WM8990_ADC_TO_DACL_SHIFT 2
+#define WM8990_ADC_TO_DACR_MASK 0x03 /* ADC_TO_DACR - [1:0] */
+#define WM8990_ADC_TO_DACR_SHIFT 0
+
+/*
+ * R14 (0x0E) - ADC CTRL
+ */
+#define WM8990_ADC_HPF_ENA 0x0100 /* ADC_HPF_ENA */
+#define WM8990_ADC_HPF_ENA_BIT 8
+#define WM8990_ADC_HPF_CUT_MASK 0x03 /* ADC_HPF_CUT - [6:5] */
+#define WM8990_ADC_HPF_CUT_SHIFT 5
+#define WM8990_ADCL_DATINV 0x0002 /* ADCL_DATINV */
+#define WM8990_ADCL_DATINV_BIT 1
+#define WM8990_ADCR_DATINV 0x0001 /* ADCR_DATINV */
+#define WM8990_ADCR_DATINV_BIT 0
+
+/*
+ * R15 (0x0F) - Left ADC Digital Volume
+ */
+#define WM8990_ADC_VU 0x0100 /* ADC_VU */
+#define WM8990_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */
+#define WM8990_ADCL_VOL_SHIFT 0
+
+/*
+ * R16 (0x10) - Right ADC Digital Volume
+ */
+#define WM8990_ADC_VU 0x0100 /* ADC_VU */
+#define WM8990_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */
+#define WM8990_ADCR_VOL_SHIFT 0
+
+/*
+ * R18 (0x12) - GPIO CTRL 1
+ */
+#define WM8990_IRQ 0x1000 /* IRQ */
+#define WM8990_TEMPOK 0x0800 /* TEMPOK */
+#define WM8990_MICSHRT 0x0400 /* MICSHRT */
+#define WM8990_MICDET 0x0200 /* MICDET */
+#define WM8990_PLL_LCK 0x0100 /* PLL_LCK */
+#define WM8990_GPI8_STATUS 0x0080 /* GPI8_STATUS */
+#define WM8990_GPI7_STATUS 0x0040 /* GPI7_STATUS */
+#define WM8990_GPIO6_STATUS 0x0020 /* GPIO6_STATUS */
+#define WM8990_GPIO5_STATUS 0x0010 /* GPIO5_STATUS */
+#define WM8990_GPIO4_STATUS 0x0008 /* GPIO4_STATUS */
+#define WM8990_GPIO3_STATUS 0x0004 /* GPIO3_STATUS */
+#define WM8990_GPIO2_STATUS 0x0002 /* GPIO2_STATUS */
+#define WM8990_GPIO1_STATUS 0x0001 /* GPIO1_STATUS */
+
+/*
+ * R19 (0x13) - GPIO1 & GPIO2
+ */
+#define WM8990_GPIO2_DEB_ENA 0x8000 /* GPIO2_DEB_ENA */
+#define WM8990_GPIO2_IRQ_ENA 0x4000 /* GPIO2_IRQ_ENA */
+#define WM8990_GPIO2_PU 0x2000 /* GPIO2_PU */
+#define WM8990_GPIO2_PD 0x1000 /* GPIO2_PD */
+#define WM8990_GPIO2_SEL_MASK 0x0F00 /* GPIO2_SEL - [11:8] */
+#define WM8990_GPIO1_DEB_ENA 0x0080 /* GPIO1_DEB_ENA */
+#define WM8990_GPIO1_IRQ_ENA 0x0040 /* GPIO1_IRQ_ENA */
+#define WM8990_GPIO1_PU 0x0020 /* GPIO1_PU */
+#define WM8990_GPIO1_PD 0x0010 /* GPIO1_PD */
+#define WM8990_GPIO1_SEL_MASK 0x000F /* GPIO1_SEL - [3:0] */
+
+/*
+ * R20 (0x14) - GPIO3 & GPIO4
+ */
+#define WM8990_GPIO4_DEB_ENA 0x8000 /* GPIO4_DEB_ENA */
+#define WM8990_GPIO4_IRQ_ENA 0x4000 /* GPIO4_IRQ_ENA */
+#define WM8990_GPIO4_PU 0x2000 /* GPIO4_PU */
+#define WM8990_GPIO4_PD 0x1000 /* GPIO4_PD */
+#define WM8990_GPIO4_SEL_MASK 0x0F00 /* GPIO4_SEL - [11:8] */
+#define WM8990_GPIO3_DEB_ENA 0x0080 /* GPIO3_DEB_ENA */
+#define WM8990_GPIO3_IRQ_ENA 0x0040 /* GPIO3_IRQ_ENA */
+#define WM8990_GPIO3_PU 0x0020 /* GPIO3_PU */
+#define WM8990_GPIO3_PD 0x0010 /* GPIO3_PD */
+#define WM8990_GPIO3_SEL_MASK 0x000F /* GPIO3_SEL - [3:0] */
+
+/*
+ * R21 (0x15) - GPIO5 & GPIO6
+ */
+#define WM8990_GPIO6_DEB_ENA 0x8000 /* GPIO6_DEB_ENA */
+#define WM8990_GPIO6_IRQ_ENA 0x4000 /* GPIO6_IRQ_ENA */
+#define WM8990_GPIO6_PU 0x2000 /* GPIO6_PU */
+#define WM8990_GPIO6_PD 0x1000 /* GPIO6_PD */
+#define WM8990_GPIO6_SEL_MASK 0x0F00 /* GPIO6_SEL - [11:8] */
+#define WM8990_GPIO5_DEB_ENA 0x0080 /* GPIO5_DEB_ENA */
+#define WM8990_GPIO5_IRQ_ENA 0x0040 /* GPIO5_IRQ_ENA */
+#define WM8990_GPIO5_PU 0x0020 /* GPIO5_PU */
+#define WM8990_GPIO5_PD 0x0010 /* GPIO5_PD */
+#define WM8990_GPIO5_SEL_MASK 0x000F /* GPIO5_SEL - [3:0] */
+
+/*
+ * R22 (0x16) - GPIOCTRL 2
+ */
+#define WM8990_RD_3W_ENA 0x8000 /* RD_3W_ENA */
+#define WM8990_MODE_3W4W 0x4000 /* MODE_3W4W */
+#define WM8990_TEMPOK_IRQ_ENA 0x0800 /* TEMPOK_IRQ_ENA */
+#define WM8990_MICSHRT_IRQ_ENA 0x0400 /* MICSHRT_IRQ_ENA */
+#define WM8990_MICDET_IRQ_ENA 0x0200 /* MICDET_IRQ_ENA */
+#define WM8990_PLL_LCK_IRQ_ENA 0x0100 /* PLL_LCK_IRQ_ENA */
+#define WM8990_GPI8_DEB_ENA 0x0080 /* GPI8_DEB_ENA */
+#define WM8990_GPI8_IRQ_ENA 0x0040 /* GPI8_IRQ_ENA */
+#define WM8990_GPI8_ENA 0x0010 /* GPI8_ENA */
+#define WM8990_GPI7_DEB_ENA 0x0008 /* GPI7_DEB_ENA */
+#define WM8990_GPI7_IRQ_ENA 0x0004 /* GPI7_IRQ_ENA */
+#define WM8990_GPI7_ENA 0x0001 /* GPI7_ENA */
+
+/*
+ * R23 (0x17) - GPIO_POL
+ */
+#define WM8990_IRQ_INV 0x1000 /* IRQ_INV */
+#define WM8990_TEMPOK_POL 0x0800 /* TEMPOK_POL */
+#define WM8990_MICSHRT_POL 0x0400 /* MICSHRT_POL */
+#define WM8990_MICDET_POL 0x0200 /* MICDET_POL */
+#define WM8990_PLL_LCK_POL 0x0100 /* PLL_LCK_POL */
+#define WM8990_GPI8_POL 0x0080 /* GPI8_POL */
+#define WM8990_GPI7_POL 0x0040 /* GPI7_POL */
+#define WM8990_GPIO6_POL 0x0020 /* GPIO6_POL */
+#define WM8990_GPIO5_POL 0x0010 /* GPIO5_POL */
+#define WM8990_GPIO4_POL 0x0008 /* GPIO4_POL */
+#define WM8990_GPIO3_POL 0x0004 /* GPIO3_POL */
+#define WM8990_GPIO2_POL 0x0002 /* GPIO2_POL */
+#define WM8990_GPIO1_POL 0x0001 /* GPIO1_POL */
+
+/*
+ * R24 (0x18) - Left Line Input 1&2 Volume
+ */
+#define WM8990_IPVU 0x0100 /* IPVU */
+#define WM8990_LI12MUTE 0x0080 /* LI12MUTE */
+#define WM8990_LI12MUTE_BIT 7
+#define WM8990_LI12ZC 0x0040 /* LI12ZC */
+#define WM8990_LI12ZC_BIT 6
+#define WM8990_LIN12VOL_MASK 0x001F /* LIN12VOL - [4:0] */
+#define WM8990_LIN12VOL_SHIFT 0
+/*
+ * R25 (0x19) - Left Line Input 3&4 Volume
+ */
+#define WM8990_IPVU 0x0100 /* IPVU */
+#define WM8990_LI34MUTE 0x0080 /* LI34MUTE */
+#define WM8990_LI34MUTE_BIT 7
+#define WM8990_LI34ZC 0x0040 /* LI34ZC */
+#define WM8990_LI34ZC_BIT 6
+#define WM8990_LIN34VOL_MASK 0x001F /* LIN34VOL - [4:0] */
+#define WM8990_LIN34VOL_SHIFT 0
+
+/*
+ * R26 (0x1A) - Right Line Input 1&2 Volume
+ */
+#define WM8990_IPVU 0x0100 /* IPVU */
+#define WM8990_RI12MUTE 0x0080 /* RI12MUTE */
+#define WM8990_RI12MUTE_BIT 7
+#define WM8990_RI12ZC 0x0040 /* RI12ZC */
+#define WM8990_RI12ZC_BIT 6
+#define WM8990_RIN12VOL_MASK 0x001F /* RIN12VOL - [4:0] */
+#define WM8990_RIN12VOL_SHIFT 0
+
+/*
+ * R27 (0x1B) - Right Line Input 3&4 Volume
+ */
+#define WM8990_IPVU 0x0100 /* IPVU */
+#define WM8990_RI34MUTE 0x0080 /* RI34MUTE */
+#define WM8990_RI34MUTE_BIT 7
+#define WM8990_RI34ZC 0x0040 /* RI34ZC */
+#define WM8990_RI34ZC_BIT 6
+#define WM8990_RIN34VOL_MASK 0x001F /* RIN34VOL - [4:0] */
+#define WM8990_RIN34VOL_SHIFT 0
+
+/*
+ * R28 (0x1C) - Left Output Volume
+ */
+#define WM8990_OPVU 0x0100 /* OPVU */
+#define WM8990_LOZC 0x0080 /* LOZC */
+#define WM8990_LOZC_BIT 7
+#define WM8990_LOUTVOL_MASK 0x007F /* LOUTVOL - [6:0] */
+#define WM8990_LOUTVOL_SHIFT 0
+/*
+ * R29 (0x1D) - Right Output Volume
+ */
+#define WM8990_OPVU 0x0100 /* OPVU */
+#define WM8990_ROZC 0x0080 /* ROZC */
+#define WM8990_ROZC_BIT 7
+#define WM8990_ROUTVOL_MASK 0x007F /* ROUTVOL - [6:0] */
+#define WM8990_ROUTVOL_SHIFT 0
+/*
+ * R30 (0x1E) - Line Outputs Volume
+ */
+#define WM8990_LONMUTE 0x0040 /* LONMUTE */
+#define WM8990_LONMUTE_BIT 6
+#define WM8990_LOPMUTE 0x0020 /* LOPMUTE */
+#define WM8990_LOPMUTE_BIT 5
+#define WM8990_LOATTN 0x0010 /* LOATTN */
+#define WM8990_LOATTN_BIT 4
+#define WM8990_RONMUTE 0x0004 /* RONMUTE */
+#define WM8990_RONMUTE_BIT 2
+#define WM8990_ROPMUTE 0x0002 /* ROPMUTE */
+#define WM8990_ROPMUTE_BIT 1
+#define WM8990_ROATTN 0x0001 /* ROATTN */
+#define WM8990_ROATTN_BIT 0
+
+/*
+ * R31 (0x1F) - Out3/4 Volume
+ */
+#define WM8990_OUT3MUTE 0x0020 /* OUT3MUTE */
+#define WM8990_OUT3MUTE_BIT 5
+#define WM8990_OUT3ATTN 0x0010 /* OUT3ATTN */
+#define WM8990_OUT3ATTN_BIT 4
+#define WM8990_OUT4MUTE 0x0002 /* OUT4MUTE */
+#define WM8990_OUT4MUTE_BIT 1
+#define WM8990_OUT4ATTN 0x0001 /* OUT4ATTN */
+#define WM8990_OUT4ATTN_BIT 0
+
+/*
+ * R32 (0x20) - Left OPGA Volume
+ */
+#define WM8990_OPVU 0x0100 /* OPVU */
+#define WM8990_LOPGAZC 0x0080 /* LOPGAZC */
+#define WM8990_LOPGAZC_BIT 7
+#define WM8990_LOPGAVOL_MASK 0x007F /* LOPGAVOL - [6:0] */
+#define WM8990_LOPGAVOL_SHIFT 0
+
+/*
+ * R33 (0x21) - Right OPGA Volume
+ */
+#define WM8990_OPVU 0x0100 /* OPVU */
+#define WM8990_ROPGAZC 0x0080 /* ROPGAZC */
+#define WM8990_ROPGAZC_BIT 7
+#define WM8990_ROPGAVOL_MASK 0x007F /* ROPGAVOL - [6:0] */
+#define WM8990_ROPGAVOL_SHIFT 0
+/*
+ * R34 (0x22) - Speaker Volume
+ */
+#define WM8990_SPKATTN_MASK 0x0003 /* SPKATTN - [1:0] */
+#define WM8990_SPKATTN_SHIFT 0
+
+/*
+ * R35 (0x23) - ClassD1
+ */
+#define WM8990_CDMODE 0x0100 /* CDMODE */
+#define WM8990_CDMODE_BIT 8
+
+/*
+ * R37 (0x25) - ClassD3
+ */
+#define WM8990_DCGAIN_MASK 0x0007 /* DCGAIN - [5:3] */
+#define WM8990_DCGAIN_SHIFT 3
+#define WM8990_ACGAIN_MASK 0x0007 /* ACGAIN - [2:0] */
+#define WM8990_ACGAIN_SHIFT 0
+
+/*
+ * R38 (0x26) - ClassD4
+ */
+#define WM8990_SPKZC_MASK 0x0001 /* SPKZC */
+#define WM8990_SPKZC_SHIFT 7 /* SPKZC */
+#define WM8990_SPKVOL_MASK 0x007F /* SPKVOL - [6:0] */
+#define WM8990_SPKVOL_SHIFT 0 /* SPKVOL - [6:0] */
+
+/*
+ * R39 (0x27) - Input Mixer1
+ */
+#define WM8990_AINLMODE_MASK 0x000C /* AINLMODE - [3:2] */
+#define WM8990_AINLMODE_SHIFT 2
+#define WM8990_AINRMODE_MASK 0x0003 /* AINRMODE - [1:0] */
+#define WM8990_AINRMODE_SHIFT 0
+
+/*
+ * R40 (0x28) - Input Mixer2
+ */
+#define WM8990_LMP4 0x0080 /* LMP4 */
+#define WM8990_LMP4_BIT 7 /* LMP4 */
+#define WM8990_LMN3 0x0040 /* LMN3 */
+#define WM8990_LMN3_BIT 6 /* LMN3 */
+#define WM8990_LMP2 0x0020 /* LMP2 */
+#define WM8990_LMP2_BIT 5 /* LMP2 */
+#define WM8990_LMN1 0x0010 /* LMN1 */
+#define WM8990_LMN1_BIT 4 /* LMN1 */
+#define WM8990_RMP4 0x0008 /* RMP4 */
+#define WM8990_RMP4_BIT 3 /* RMP4 */
+#define WM8990_RMN3 0x0004 /* RMN3 */
+#define WM8990_RMN3_BIT 2 /* RMN3 */
+#define WM8990_RMP2 0x0002 /* RMP2 */
+#define WM8990_RMP2_BIT 1 /* RMP2 */
+#define WM8990_RMN1 0x0001 /* RMN1 */
+#define WM8990_RMN1_BIT 0 /* RMN1 */
+
+/*
+ * R41 (0x29) - Input Mixer3
+ */
+#define WM8990_L34MNB 0x0100 /* L34MNB */
+#define WM8990_L34MNB_BIT 8
+#define WM8990_L34MNBST 0x0080 /* L34MNBST */
+#define WM8990_L34MNBST_BIT 7
+#define WM8990_L12MNB 0x0020 /* L12MNB */
+#define WM8990_L12MNB_BIT 5
+#define WM8990_L12MNBST 0x0010 /* L12MNBST */
+#define WM8990_L12MNBST_BIT 4
+#define WM8990_LDBVOL_MASK 0x0007 /* LDBVOL - [2:0] */
+#define WM8990_LDBVOL_SHIFT 0
+
+/*
+ * R42 (0x2A) - Input Mixer4
+ */
+#define WM8990_R34MNB 0x0100 /* R34MNB */
+#define WM8990_R34MNB_BIT 8
+#define WM8990_R34MNBST 0x0080 /* R34MNBST */
+#define WM8990_R34MNBST_BIT 7
+#define WM8990_R12MNB 0x0020 /* R12MNB */
+#define WM8990_R12MNB_BIT 5
+#define WM8990_R12MNBST 0x0010 /* R12MNBST */
+#define WM8990_R12MNBST_BIT 4
+#define WM8990_RDBVOL_MASK 0x0007 /* RDBVOL - [2:0] */
+#define WM8990_RDBVOL_SHIFT 0
+
+/*
+ * R43 (0x2B) - Input Mixer5
+ */
+#define WM8990_LI2BVOL_MASK 0x07 /* LI2BVOL - [8:6] */
+#define WM8990_LI2BVOL_SHIFT 6
+#define WM8990_LR4BVOL_MASK 0x07 /* LR4BVOL - [5:3] */
+#define WM8990_LR4BVOL_SHIFT 3
+#define WM8990_LL4BVOL_MASK 0x07 /* LL4BVOL - [2:0] */
+#define WM8990_LL4BVOL_SHIFT 0
+
+/*
+ * R44 (0x2C) - Input Mixer6
+ */
+#define WM8990_RI2BVOL_MASK 0x07 /* RI2BVOL - [8:6] */
+#define WM8990_RI2BVOL_SHIFT 6
+#define WM8990_RL4BVOL_MASK 0x07 /* RL4BVOL - [5:3] */
+#define WM8990_RL4BVOL_SHIFT 3
+#define WM8990_RR4BVOL_MASK 0x07 /* RR4BVOL - [2:0] */
+#define WM8990_RR4BVOL_SHIFT 0
+
+/*
+ * R45 (0x2D) - Output Mixer1
+ */
+#define WM8990_LRBLO 0x0080 /* LRBLO */
+#define WM8990_LRBLO_BIT 7
+#define WM8990_LLBLO 0x0040 /* LLBLO */
+#define WM8990_LLBLO_BIT 6
+#define WM8990_LRI3LO 0x0020 /* LRI3LO */
+#define WM8990_LRI3LO_BIT 5
+#define WM8990_LLI3LO 0x0010 /* LLI3LO */
+#define WM8990_LLI3LO_BIT 4
+#define WM8990_LR12LO 0x0008 /* LR12LO */
+#define WM8990_LR12LO_BIT 3
+#define WM8990_LL12LO 0x0004 /* LL12LO */
+#define WM8990_LL12LO_BIT 2
+#define WM8990_LDLO 0x0001 /* LDLO */
+#define WM8990_LDLO_BIT 0
+
+/*
+ * R46 (0x2E) - Output Mixer2
+ */
+#define WM8990_RLBRO 0x0080 /* RLBRO */
+#define WM8990_RLBRO_BIT 7
+#define WM8990_RRBRO 0x0040 /* RRBRO */
+#define WM8990_RRBRO_BIT 6
+#define WM8990_RLI3RO 0x0020 /* RLI3RO */
+#define WM8990_RLI3RO_BIT 5
+#define WM8990_RRI3RO 0x0010 /* RRI3RO */
+#define WM8990_RRI3RO_BIT 4
+#define WM8990_RL12RO 0x0008 /* RL12RO */
+#define WM8990_RL12RO_BIT 3
+#define WM8990_RR12RO 0x0004 /* RR12RO */
+#define WM8990_RR12RO_BIT 2
+#define WM8990_RDRO 0x0001 /* RDRO */
+#define WM8990_RDRO_BIT 0
+
+/*
+ * R47 (0x2F) - Output Mixer3
+ */
+#define WM8990_LLI3LOVOL_MASK 0x07 /* LLI3LOVOL - [8:6] */
+#define WM8990_LLI3LOVOL_SHIFT 6
+#define WM8990_LR12LOVOL_MASK 0x07 /* LR12LOVOL - [5:3] */
+#define WM8990_LR12LOVOL_SHIFT 3
+#define WM8990_LL12LOVOL_MASK 0x07 /* LL12LOVOL - [2:0] */
+#define WM8990_LL12LOVOL_SHIFT 0
+
+/*
+ * R48 (0x30) - Output Mixer4
+ */
+#define WM8990_RRI3ROVOL_MASK 0x07 /* RRI3ROVOL - [8:6] */
+#define WM8990_RRI3ROVOL_SHIFT 6
+#define WM8990_RL12ROVOL_MASK 0x07 /* RL12ROVOL - [5:3] */
+#define WM8990_RL12ROVOL_SHIFT 3
+#define WM8990_RR12ROVOL_MASK 0x07 /* RR12ROVOL - [2:0] */
+#define WM8990_RR12ROVOL_SHIFT 0
+
+/*
+ * R49 (0x31) - Output Mixer5
+ */
+#define WM8990_LRI3LOVOL_MASK 0x07 /* LRI3LOVOL - [8:6] */
+#define WM8990_LRI3LOVOL_SHIFT 6
+#define WM8990_LRBLOVOL_MASK 0x07 /* LRBLOVOL - [5:3] */
+#define WM8990_LRBLOVOL_SHIFT 3
+#define WM8990_LLBLOVOL_MASK 0x07 /* LLBLOVOL - [2:0] */
+#define WM8990_LLBLOVOL_SHIFT 0
+
+/*
+ * R50 (0x32) - Output Mixer6
+ */
+#define WM8990_RLI3ROVOL_MASK 0x07 /* RLI3ROVOL - [8:6] */
+#define WM8990_RLI3ROVOL_SHIFT 6
+#define WM8990_RLBROVOL_MASK 0x07 /* RLBROVOL - [5:3] */
+#define WM8990_RLBROVOL_SHIFT 3
+#define WM8990_RRBROVOL_MASK 0x07 /* RRBROVOL - [2:0] */
+#define WM8990_RRBROVOL_SHIFT 0
+
+/*
+ * R51 (0x33) - Out3/4 Mixer
+ */
+#define WM8990_VSEL_MASK 0x0180 /* VSEL - [8:7] */
+#define WM8990_LI4O3 0x0020 /* LI4O3 */
+#define WM8990_LI4O3_BIT 5
+#define WM8990_LPGAO3 0x0010 /* LPGAO3 */
+#define WM8990_LPGAO3_BIT 4
+#define WM8990_RI4O4 0x0002 /* RI4O4 */
+#define WM8990_RI4O4_BIT 1
+#define WM8990_RPGAO4 0x0001 /* RPGAO4 */
+#define WM8990_RPGAO4_BIT 0
+/*
+ * R52 (0x34) - Line Mixer1
+ */
+#define WM8990_LLOPGALON 0x0040 /* LLOPGALON */
+#define WM8990_LLOPGALON_BIT 6
+#define WM8990_LROPGALON 0x0020 /* LROPGALON */
+#define WM8990_LROPGALON_BIT 5
+#define WM8990_LOPLON 0x0010 /* LOPLON */
+#define WM8990_LOPLON_BIT 4
+#define WM8990_LR12LOP 0x0004 /* LR12LOP */
+#define WM8990_LR12LOP_BIT 2
+#define WM8990_LL12LOP 0x0002 /* LL12LOP */
+#define WM8990_LL12LOP_BIT 1
+#define WM8990_LLOPGALOP 0x0001 /* LLOPGALOP */
+#define WM8990_LLOPGALOP_BIT 0
+/*
+ * R53 (0x35) - Line Mixer2
+ */
+#define WM8990_RROPGARON 0x0040 /* RROPGARON */
+#define WM8990_RROPGARON_BIT 6
+#define WM8990_RLOPGARON 0x0020 /* RLOPGARON */
+#define WM8990_RLOPGARON_BIT 5
+#define WM8990_ROPRON 0x0010 /* ROPRON */
+#define WM8990_ROPRON_BIT 4
+#define WM8990_RL12ROP 0x0004 /* RL12ROP */
+#define WM8990_RL12ROP_BIT 2
+#define WM8990_RR12ROP 0x0002 /* RR12ROP */
+#define WM8990_RR12ROP_BIT 1
+#define WM8990_RROPGAROP 0x0001 /* RROPGAROP */
+#define WM8990_RROPGAROP_BIT 0
+
+/*
+ * R54 (0x36) - Speaker Mixer
+ */
+#define WM8990_LB2SPK 0x0080 /* LB2SPK */
+#define WM8990_LB2SPK_BIT 7
+#define WM8990_RB2SPK 0x0040 /* RB2SPK */
+#define WM8990_RB2SPK_BIT 6
+#define WM8990_LI2SPK 0x0020 /* LI2SPK */
+#define WM8990_LI2SPK_BIT 5
+#define WM8990_RI2SPK 0x0010 /* RI2SPK */
+#define WM8990_RI2SPK_BIT 4
+#define WM8990_LOPGASPK 0x0008 /* LOPGASPK */
+#define WM8990_LOPGASPK_BIT 3
+#define WM8990_ROPGASPK 0x0004 /* ROPGASPK */
+#define WM8990_ROPGASPK_BIT 2
+#define WM8990_LDSPK 0x0002 /* LDSPK */
+#define WM8990_LDSPK_BIT 1
+#define WM8990_RDSPK 0x0001 /* RDSPK */
+#define WM8990_RDSPK_BIT 0
+
+/*
+ * R55 (0x37) - Additional Control
+ */
+#define WM8990_VROI 0x0001 /* VROI */
+
+/*
+ * R56 (0x38) - AntiPOP1
+ */
+#define WM8990_DIS_LLINE 0x0020 /* DIS_LLINE */
+#define WM8990_DIS_RLINE 0x0010 /* DIS_RLINE */
+#define WM8990_DIS_OUT3 0x0008 /* DIS_OUT3 */
+#define WM8990_DIS_OUT4 0x0004 /* DIS_OUT4 */
+#define WM8990_DIS_LOUT 0x0002 /* DIS_LOUT */
+#define WM8990_DIS_ROUT 0x0001 /* DIS_ROUT */
+
+/*
+ * R57 (0x39) - AntiPOP2
+ */
+#define WM8990_SOFTST 0x0040 /* SOFTST */
+#define WM8990_BUFIOEN 0x0008 /* BUFIOEN */
+#define WM8990_BUFDCOPEN 0x0004 /* BUFDCOPEN */
+#define WM8990_POBCTRL 0x0002 /* POBCTRL */
+#define WM8990_VMIDTOG 0x0001 /* VMIDTOG */
+
+/*
+ * R58 (0x3A) - MICBIAS
+ */
+#define WM8990_MCDSCTH_MASK 0x00C0 /* MCDSCTH - [7:6] */
+#define WM8990_MCDTHR_MASK 0x0038 /* MCDTHR - [5:3] */
+#define WM8990_MCD 0x0004 /* MCD */
+#define WM8990_MBSEL 0x0001 /* MBSEL */
+
+/*
+ * R60 (0x3C) - PLL1
+ */
+#define WM8990_SDM 0x0080 /* SDM */
+#define WM8990_PRESCALE 0x0040 /* PRESCALE */
+#define WM8990_PLLN_MASK 0x000F /* PLLN - [3:0] */
+
+/*
+ * R61 (0x3D) - PLL2
+ */
+#define WM8990_PLLK1_MASK 0x00FF /* PLLK1 - [7:0] */
+
+/*
+ * R62 (0x3E) - PLL3
+ */
+#define WM8990_PLLK2_MASK 0x00FF /* PLLK2 - [7:0] */
+
+/*
+ * R63 (0x3F) - Internal Driver Bits
+ */
+#define WM8990_INMIXL_PWR_BIT 0
+#define WM8990_AINLMUX_PWR_BIT 1
+#define WM8990_INMIXR_PWR_BIT 2
+#define WM8990_AINRMUX_PWR_BIT 3
+
+struct wm8990_setup_data {
+ unsigned i2c_bus;
+ unsigned short i2c_address;
+};
+
+#define WM8990_MCLK_DIV 0
+#define WM8990_DACCLK_DIV 1
+#define WM8990_ADCCLK_DIV 2
+#define WM8990_BCLK_DIV 3
+
+extern struct snd_soc_dai wm8990_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8990;
+
+#endif /* __WM8990REGISTERDEFS_H__ */
+/*------------------------------ END OF FILE ---------------------------------*/
diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c
new file mode 100644
index 0000000..ffb471e
--- /dev/null
+++ b/sound/soc/codecs/wm9712.c
@@ -0,0 +1,750 @@
+/*
+ * wm9712.c -- ALSA Soc WM9712 codec support
+ *
+ * Copyright 2006 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include "wm9712.h"
+
+#define WM9712_VERSION "0.4"
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg);
+static int ac97_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int val);
+
+/*
+ * WM9712 register cache
+ */
+static const u16 wm9712_reg[] = {
+ 0x6174, 0x8000, 0x8000, 0x8000, /* 6 */
+ 0x0f0f, 0xaaa0, 0xc008, 0x6808, /* e */
+ 0xe808, 0xaaa0, 0xad00, 0x8000, /* 16 */
+ 0xe808, 0x3000, 0x8000, 0x0000, /* 1e */
+ 0x0000, 0x0000, 0x0000, 0x000f, /* 26 */
+ 0x0405, 0x0410, 0xbb80, 0xbb80, /* 2e */
+ 0x0000, 0xbb80, 0x0000, 0x0000, /* 36 */
+ 0x0000, 0x2000, 0x0000, 0x0000, /* 3e */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 46 */
+ 0x0000, 0x0000, 0xf83e, 0xffff, /* 4e */
+ 0x0000, 0x0000, 0x0000, 0xf83e, /* 56 */
+ 0x0008, 0x0000, 0x0000, 0x0000, /* 5e */
+ 0xb032, 0x3e00, 0x0000, 0x0000, /* 66 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 6e */
+ 0x0000, 0x0000, 0x0000, 0x0006, /* 76 */
+ 0x0001, 0x0000, 0x574d, 0x4c12, /* 7e */
+ 0x0000, 0x0000 /* virtual hp mixers */
+};
+
+/* virtual HP mixers regs */
+#define HPL_MIXER 0x80
+#define HPR_MIXER 0x82
+
+static const char *wm9712_alc_select[] = {"None", "Left", "Right", "Stereo"};
+static const char *wm9712_alc_mux[] = {"Stereo", "Left", "Right", "None"};
+static const char *wm9712_out3_src[] = {"Left", "VREF", "Left + Right",
+ "Mono"};
+static const char *wm9712_spk_src[] = {"Speaker Mix", "Headphone Mix"};
+static const char *wm9712_rec_adc[] = {"Stereo", "Left", "Right", "Mute"};
+static const char *wm9712_base[] = {"Linear Control", "Adaptive Boost"};
+static const char *wm9712_rec_gain[] = {"+1.5dB Steps", "+0.75dB Steps"};
+static const char *wm9712_mic[] = {"Mic 1", "Differential", "Mic 2",
+ "Stereo"};
+static const char *wm9712_rec_sel[] = {"Mic", "NC", "NC", "Speaker Mixer",
+ "Line", "Headphone Mixer", "Phone Mixer", "Phone"};
+static const char *wm9712_ng_type[] = {"Constant Gain", "Mute"};
+static const char *wm9712_diff_sel[] = {"Mic", "Line"};
+
+static const struct soc_enum wm9712_enum[] = {
+SOC_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9712_alc_select),
+SOC_ENUM_SINGLE(AC97_VIDEO, 12, 4, wm9712_alc_mux),
+SOC_ENUM_SINGLE(AC97_AUX, 9, 4, wm9712_out3_src),
+SOC_ENUM_SINGLE(AC97_AUX, 8, 2, wm9712_spk_src),
+SOC_ENUM_SINGLE(AC97_REC_SEL, 12, 4, wm9712_rec_adc),
+SOC_ENUM_SINGLE(AC97_MASTER_TONE, 15, 2, wm9712_base),
+SOC_ENUM_DOUBLE(AC97_REC_GAIN, 14, 6, 2, wm9712_rec_gain),
+SOC_ENUM_SINGLE(AC97_MIC, 5, 4, wm9712_mic),
+SOC_ENUM_SINGLE(AC97_REC_SEL, 8, 8, wm9712_rec_sel),
+SOC_ENUM_SINGLE(AC97_REC_SEL, 0, 8, wm9712_rec_sel),
+SOC_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9712_ng_type),
+SOC_ENUM_SINGLE(0x5c, 8, 2, wm9712_diff_sel),
+};
+
+static const struct snd_kcontrol_new wm9712_snd_ac97_controls[] = {
+SOC_DOUBLE("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1),
+SOC_SINGLE("Speaker Playback Switch", AC97_MASTER, 15, 1, 1),
+SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1),
+SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1),
+SOC_DOUBLE("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1),
+
+SOC_SINGLE("Speaker Playback ZC Switch", AC97_MASTER, 7, 1, 0),
+SOC_SINGLE("Speaker Playback Invert Switch", AC97_MASTER, 6, 1, 0),
+SOC_SINGLE("Headphone Playback ZC Switch", AC97_HEADPHONE, 7, 1, 0),
+SOC_SINGLE("Mono Playback ZC Switch", AC97_MASTER_MONO, 7, 1, 0),
+SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1),
+SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1),
+
+SOC_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0),
+SOC_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0),
+SOC_SINGLE("ALC Decay Time", AC97_CODEC_CLASS_REV, 4, 15, 0),
+SOC_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0),
+SOC_ENUM("ALC Function", wm9712_enum[0]),
+SOC_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 0),
+SOC_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 1),
+SOC_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0),
+SOC_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0),
+SOC_ENUM("ALC NG Type", wm9712_enum[10]),
+SOC_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 1),
+
+SOC_SINGLE("Mic Headphone Volume", AC97_VIDEO, 12, 7, 1),
+SOC_SINGLE("ALC Headphone Volume", AC97_VIDEO, 7, 7, 1),
+
+SOC_SINGLE("Out3 Switch", AC97_AUX, 15, 1, 1),
+SOC_SINGLE("Out3 ZC Switch", AC97_AUX, 7, 1, 1),
+SOC_SINGLE("Out3 Volume", AC97_AUX, 0, 31, 1),
+
+SOC_SINGLE("PCBeep Bypass Headphone Volume", AC97_PC_BEEP, 12, 7, 1),
+SOC_SINGLE("PCBeep Bypass Speaker Volume", AC97_PC_BEEP, 8, 7, 1),
+SOC_SINGLE("PCBeep Bypass Phone Volume", AC97_PC_BEEP, 4, 7, 1),
+
+SOC_SINGLE("Aux Playback Headphone Volume", AC97_CD, 12, 7, 1),
+SOC_SINGLE("Aux Playback Speaker Volume", AC97_CD, 8, 7, 1),
+SOC_SINGLE("Aux Playback Phone Volume", AC97_CD, 4, 7, 1),
+
+SOC_SINGLE("Phone Volume", AC97_PHONE, 0, 15, 1),
+SOC_DOUBLE("Line Capture Volume", AC97_LINE, 8, 0, 31, 1),
+
+SOC_SINGLE("Capture 20dB Boost Switch", AC97_REC_SEL, 14, 1, 0),
+SOC_SINGLE("Capture to Phone 20dB Boost Switch", AC97_REC_SEL, 11, 1, 1),
+
+SOC_SINGLE("3D Upper Cut-off Switch", AC97_3D_CONTROL, 5, 1, 1),
+SOC_SINGLE("3D Lower Cut-off Switch", AC97_3D_CONTROL, 4, 1, 1),
+SOC_SINGLE("3D Playback Volume", AC97_3D_CONTROL, 0, 15, 0),
+
+SOC_ENUM("Bass Control", wm9712_enum[5]),
+SOC_SINGLE("Bass Cut-off Switch", AC97_MASTER_TONE, 12, 1, 1),
+SOC_SINGLE("Tone Cut-off Switch", AC97_MASTER_TONE, 4, 1, 1),
+SOC_SINGLE("Playback Attenuate (-6dB) Switch", AC97_MASTER_TONE, 6, 1, 0),
+SOC_SINGLE("Bass Volume", AC97_MASTER_TONE, 8, 15, 1),
+SOC_SINGLE("Treble Volume", AC97_MASTER_TONE, 0, 15, 1),
+
+SOC_SINGLE("Capture ADC Switch", AC97_REC_GAIN, 15, 1, 1),
+SOC_ENUM("Capture Volume Steps", wm9712_enum[6]),
+SOC_DOUBLE("Capture Volume", AC97_REC_GAIN, 8, 0, 63, 1),
+SOC_SINGLE("Capture ZC Switch", AC97_REC_GAIN, 7, 1, 0),
+
+SOC_SINGLE("Mic 1 Volume", AC97_MIC, 8, 31, 1),
+SOC_SINGLE("Mic 2 Volume", AC97_MIC, 0, 31, 1),
+SOC_SINGLE("Mic 20dB Boost Switch", AC97_MIC, 7, 1, 0),
+};
+
+/* add non dapm controls */
+static int wm9712_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm9712_snd_ac97_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm9712_snd_ac97_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+/* We have to create a fake left and right HP mixers because
+ * the codec only has a single control that is shared by both channels.
+ * This makes it impossible to determine the audio path.
+ */
+static int mixer_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ u16 l, r, beep, line, phone, mic, pcm, aux;
+
+ l = ac97_read(w->codec, HPL_MIXER);
+ r = ac97_read(w->codec, HPR_MIXER);
+ beep = ac97_read(w->codec, AC97_PC_BEEP);
+ mic = ac97_read(w->codec, AC97_VIDEO);
+ phone = ac97_read(w->codec, AC97_PHONE);
+ line = ac97_read(w->codec, AC97_LINE);
+ pcm = ac97_read(w->codec, AC97_PCM);
+ aux = ac97_read(w->codec, AC97_CD);
+
+ if (l & 0x1 || r & 0x1)
+ ac97_write(w->codec, AC97_VIDEO, mic & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_VIDEO, mic | 0x8000);
+
+ if (l & 0x2 || r & 0x2)
+ ac97_write(w->codec, AC97_PCM, pcm & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_PCM, pcm | 0x8000);
+
+ if (l & 0x4 || r & 0x4)
+ ac97_write(w->codec, AC97_LINE, line & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_LINE, line | 0x8000);
+
+ if (l & 0x8 || r & 0x8)
+ ac97_write(w->codec, AC97_PHONE, phone & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_PHONE, phone | 0x8000);
+
+ if (l & 0x10 || r & 0x10)
+ ac97_write(w->codec, AC97_CD, aux & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_CD, aux | 0x8000);
+
+ if (l & 0x20 || r & 0x20)
+ ac97_write(w->codec, AC97_PC_BEEP, beep & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_PC_BEEP, beep | 0x8000);
+
+ return 0;
+}
+
+/* Left Headphone Mixers */
+static const struct snd_kcontrol_new wm9712_hpl_mixer_controls[] = {
+ SOC_DAPM_SINGLE("PCBeep Bypass Switch", HPL_MIXER, 5, 1, 0),
+ SOC_DAPM_SINGLE("Aux Playback Switch", HPL_MIXER, 4, 1, 0),
+ SOC_DAPM_SINGLE("Phone Bypass Switch", HPL_MIXER, 3, 1, 0),
+ SOC_DAPM_SINGLE("Line Bypass Switch", HPL_MIXER, 2, 1, 0),
+ SOC_DAPM_SINGLE("PCM Playback Switch", HPL_MIXER, 1, 1, 0),
+ SOC_DAPM_SINGLE("Mic Sidetone Switch", HPL_MIXER, 0, 1, 0),
+};
+
+/* Right Headphone Mixers */
+static const struct snd_kcontrol_new wm9712_hpr_mixer_controls[] = {
+ SOC_DAPM_SINGLE("PCBeep Bypass Switch", HPR_MIXER, 5, 1, 0),
+ SOC_DAPM_SINGLE("Aux Playback Switch", HPR_MIXER, 4, 1, 0),
+ SOC_DAPM_SINGLE("Phone Bypass Switch", HPR_MIXER, 3, 1, 0),
+ SOC_DAPM_SINGLE("Line Bypass Switch", HPR_MIXER, 2, 1, 0),
+ SOC_DAPM_SINGLE("PCM Playback Switch", HPR_MIXER, 1, 1, 0),
+ SOC_DAPM_SINGLE("Mic Sidetone Switch", HPR_MIXER, 0, 1, 0),
+};
+
+/* Speaker Mixer */
+static const struct snd_kcontrol_new wm9712_speaker_mixer_controls[] = {
+ SOC_DAPM_SINGLE("PCBeep Bypass Switch", AC97_PC_BEEP, 11, 1, 1),
+ SOC_DAPM_SINGLE("Aux Playback Switch", AC97_CD, 11, 1, 1),
+ SOC_DAPM_SINGLE("Phone Bypass Switch", AC97_PHONE, 14, 1, 1),
+ SOC_DAPM_SINGLE("Line Bypass Switch", AC97_LINE, 14, 1, 1),
+ SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PCM, 14, 1, 1),
+};
+
+/* Phone Mixer */
+static const struct snd_kcontrol_new wm9712_phone_mixer_controls[] = {
+ SOC_DAPM_SINGLE("PCBeep Bypass Switch", AC97_PC_BEEP, 7, 1, 1),
+ SOC_DAPM_SINGLE("Aux Playback Switch", AC97_CD, 7, 1, 1),
+ SOC_DAPM_SINGLE("Line Bypass Switch", AC97_LINE, 13, 1, 1),
+ SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PCM, 13, 1, 1),
+ SOC_DAPM_SINGLE("Mic 1 Sidetone Switch", AC97_MIC, 14, 1, 1),
+ SOC_DAPM_SINGLE("Mic 2 Sidetone Switch", AC97_MIC, 13, 1, 1),
+};
+
+/* ALC headphone mux */
+static const struct snd_kcontrol_new wm9712_alc_mux_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[1]);
+
+/* out 3 mux */
+static const struct snd_kcontrol_new wm9712_out3_mux_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[2]);
+
+/* spk mux */
+static const struct snd_kcontrol_new wm9712_spk_mux_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[3]);
+
+/* Capture to Phone mux */
+static const struct snd_kcontrol_new wm9712_capture_phone_mux_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[4]);
+
+/* Capture left select */
+static const struct snd_kcontrol_new wm9712_capture_selectl_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[8]);
+
+/* Capture right select */
+static const struct snd_kcontrol_new wm9712_capture_selectr_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[9]);
+
+/* Mic select */
+static const struct snd_kcontrol_new wm9712_mic_src_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[7]);
+
+/* diff select */
+static const struct snd_kcontrol_new wm9712_diff_sel_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[11]);
+
+static const struct snd_soc_dapm_widget wm9712_dapm_widgets[] = {
+SND_SOC_DAPM_MUX("ALC Sidetone Mux", SND_SOC_NOPM, 0, 0,
+ &wm9712_alc_mux_controls),
+SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0,
+ &wm9712_out3_mux_controls),
+SND_SOC_DAPM_MUX("Speaker Mux", SND_SOC_NOPM, 0, 0,
+ &wm9712_spk_mux_controls),
+SND_SOC_DAPM_MUX("Capture Phone Mux", SND_SOC_NOPM, 0, 0,
+ &wm9712_capture_phone_mux_controls),
+SND_SOC_DAPM_MUX("Left Capture Select", SND_SOC_NOPM, 0, 0,
+ &wm9712_capture_selectl_controls),
+SND_SOC_DAPM_MUX("Right Capture Select", SND_SOC_NOPM, 0, 0,
+ &wm9712_capture_selectr_controls),
+SND_SOC_DAPM_MUX("Mic Select Source", SND_SOC_NOPM, 0, 0,
+ &wm9712_mic_src_controls),
+SND_SOC_DAPM_MUX("Differential Source", SND_SOC_NOPM, 0, 0,
+ &wm9712_diff_sel_controls),
+SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER_E("Left HP Mixer", AC97_INT_PAGING, 9, 1,
+ &wm9712_hpl_mixer_controls[0], ARRAY_SIZE(wm9712_hpl_mixer_controls),
+ mixer_event, SND_SOC_DAPM_POST_REG),
+SND_SOC_DAPM_MIXER_E("Right HP Mixer", AC97_INT_PAGING, 8, 1,
+ &wm9712_hpr_mixer_controls[0], ARRAY_SIZE(wm9712_hpr_mixer_controls),
+ mixer_event, SND_SOC_DAPM_POST_REG),
+SND_SOC_DAPM_MIXER("Phone Mixer", AC97_INT_PAGING, 6, 1,
+ &wm9712_phone_mixer_controls[0], ARRAY_SIZE(wm9712_phone_mixer_controls)),
+SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_INT_PAGING, 7, 1,
+ &wm9712_speaker_mixer_controls[0],
+ ARRAY_SIZE(wm9712_speaker_mixer_controls)),
+SND_SOC_DAPM_MIXER("Mono Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", AC97_INT_PAGING, 14, 1),
+SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", AC97_INT_PAGING, 13, 1),
+SND_SOC_DAPM_DAC("Aux DAC", "Aux Playback", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture", AC97_INT_PAGING, 12, 1),
+SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture", AC97_INT_PAGING, 11, 1),
+SND_SOC_DAPM_PGA("Headphone PGA", AC97_INT_PAGING, 4, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Speaker PGA", AC97_INT_PAGING, 3, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Out 3 PGA", AC97_INT_PAGING, 5, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Line PGA", AC97_INT_PAGING, 2, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Phone PGA", AC97_INT_PAGING, 1, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mic PGA", AC97_INT_PAGING, 0, 1, NULL, 0),
+SND_SOC_DAPM_MICBIAS("Mic Bias", AC97_INT_PAGING, 10, 1),
+SND_SOC_DAPM_OUTPUT("MONOOUT"),
+SND_SOC_DAPM_OUTPUT("HPOUTL"),
+SND_SOC_DAPM_OUTPUT("HPOUTR"),
+SND_SOC_DAPM_OUTPUT("LOUT2"),
+SND_SOC_DAPM_OUTPUT("ROUT2"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+SND_SOC_DAPM_INPUT("LINEINL"),
+SND_SOC_DAPM_INPUT("LINEINR"),
+SND_SOC_DAPM_INPUT("PHONE"),
+SND_SOC_DAPM_INPUT("PCBEEP"),
+SND_SOC_DAPM_INPUT("MIC1"),
+SND_SOC_DAPM_INPUT("MIC2"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* virtual mixer - mixes left & right channels for spk and mono */
+ {"AC97 Mixer", NULL, "Left DAC"},
+ {"AC97 Mixer", NULL, "Right DAC"},
+
+ /* Left HP mixer */
+ {"Left HP Mixer", "PCBeep Bypass Switch", "PCBEEP"},
+ {"Left HP Mixer", "Aux Playback Switch", "Aux DAC"},
+ {"Left HP Mixer", "Phone Bypass Switch", "Phone PGA"},
+ {"Left HP Mixer", "Line Bypass Switch", "Line PGA"},
+ {"Left HP Mixer", "PCM Playback Switch", "Left DAC"},
+ {"Left HP Mixer", "Mic Sidetone Switch", "Mic PGA"},
+ {"Left HP Mixer", NULL, "ALC Sidetone Mux"},
+
+ /* Right HP mixer */
+ {"Right HP Mixer", "PCBeep Bypass Switch", "PCBEEP"},
+ {"Right HP Mixer", "Aux Playback Switch", "Aux DAC"},
+ {"Right HP Mixer", "Phone Bypass Switch", "Phone PGA"},
+ {"Right HP Mixer", "Line Bypass Switch", "Line PGA"},
+ {"Right HP Mixer", "PCM Playback Switch", "Right DAC"},
+ {"Right HP Mixer", "Mic Sidetone Switch", "Mic PGA"},
+ {"Right HP Mixer", NULL, "ALC Sidetone Mux"},
+
+ /* speaker mixer */
+ {"Speaker Mixer", "PCBeep Bypass Switch", "PCBEEP"},
+ {"Speaker Mixer", "Line Bypass Switch", "Line PGA"},
+ {"Speaker Mixer", "PCM Playback Switch", "AC97 Mixer"},
+ {"Speaker Mixer", "Phone Bypass Switch", "Phone PGA"},
+ {"Speaker Mixer", "Aux Playback Switch", "Aux DAC"},
+
+ /* Phone mixer */
+ {"Phone Mixer", "PCBeep Bypass Switch", "PCBEEP"},
+ {"Phone Mixer", "Line Bypass Switch", "Line PGA"},
+ {"Phone Mixer", "Aux Playback Switch", "Aux DAC"},
+ {"Phone Mixer", "PCM Playback Switch", "AC97 Mixer"},
+ {"Phone Mixer", "Mic 1 Sidetone Switch", "Mic PGA"},
+ {"Phone Mixer", "Mic 2 Sidetone Switch", "Mic PGA"},
+
+ /* inputs */
+ {"Line PGA", NULL, "LINEINL"},
+ {"Line PGA", NULL, "LINEINR"},
+ {"Phone PGA", NULL, "PHONE"},
+ {"Mic PGA", NULL, "MIC1"},
+ {"Mic PGA", NULL, "MIC2"},
+
+ /* left capture selector */
+ {"Left Capture Select", "Mic", "MIC1"},
+ {"Left Capture Select", "Speaker Mixer", "Speaker Mixer"},
+ {"Left Capture Select", "Line", "LINEINL"},
+ {"Left Capture Select", "Headphone Mixer", "Left HP Mixer"},
+ {"Left Capture Select", "Phone Mixer", "Phone Mixer"},
+ {"Left Capture Select", "Phone", "PHONE"},
+
+ /* right capture selector */
+ {"Right Capture Select", "Mic", "MIC2"},
+ {"Right Capture Select", "Speaker Mixer", "Speaker Mixer"},
+ {"Right Capture Select", "Line", "LINEINR"},
+ {"Right Capture Select", "Headphone Mixer", "Right HP Mixer"},
+ {"Right Capture Select", "Phone Mixer", "Phone Mixer"},
+ {"Right Capture Select", "Phone", "PHONE"},
+
+ /* ALC Sidetone */
+ {"ALC Sidetone Mux", "Stereo", "Left Capture Select"},
+ {"ALC Sidetone Mux", "Stereo", "Right Capture Select"},
+ {"ALC Sidetone Mux", "Left", "Left Capture Select"},
+ {"ALC Sidetone Mux", "Right", "Right Capture Select"},
+
+ /* ADC's */
+ {"Left ADC", NULL, "Left Capture Select"},
+ {"Right ADC", NULL, "Right Capture Select"},
+
+ /* outputs */
+ {"MONOOUT", NULL, "Phone Mixer"},
+ {"HPOUTL", NULL, "Headphone PGA"},
+ {"Headphone PGA", NULL, "Left HP Mixer"},
+ {"HPOUTR", NULL, "Headphone PGA"},
+ {"Headphone PGA", NULL, "Right HP Mixer"},
+
+ /* mono mixer */
+ {"Mono Mixer", NULL, "Left HP Mixer"},
+ {"Mono Mixer", NULL, "Right HP Mixer"},
+
+ /* Out3 Mux */
+ {"Out3 Mux", "Left", "Left HP Mixer"},
+ {"Out3 Mux", "Mono", "Phone Mixer"},
+ {"Out3 Mux", "Left + Right", "Mono Mixer"},
+ {"Out 3 PGA", NULL, "Out3 Mux"},
+ {"OUT3", NULL, "Out 3 PGA"},
+
+ /* speaker Mux */
+ {"Speaker Mux", "Speaker Mix", "Speaker Mixer"},
+ {"Speaker Mux", "Headphone Mix", "Mono Mixer"},
+ {"Speaker PGA", NULL, "Speaker Mux"},
+ {"LOUT2", NULL, "Speaker PGA"},
+ {"ROUT2", NULL, "Speaker PGA"},
+};
+
+static int wm9712_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm9712_dapm_widgets,
+ ARRAY_SIZE(wm9712_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+
+ if (reg == AC97_RESET || reg == AC97_GPIO_STATUS ||
+ reg == AC97_VENDOR_ID1 || reg == AC97_VENDOR_ID2 ||
+ reg == AC97_REC_GAIN)
+ return soc_ac97_ops.read(codec->ac97, reg);
+ else {
+ reg = reg >> 1;
+
+ if (reg > (ARRAY_SIZE(wm9712_reg)))
+ return -EIO;
+
+ return cache[reg];
+ }
+}
+
+static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ u16 *cache = codec->reg_cache;
+
+ soc_ac97_ops.write(codec->ac97, reg, val);
+ reg = reg >> 1;
+ if (reg <= (ARRAY_SIZE(wm9712_reg)))
+ cache[reg] = val;
+
+ return 0;
+}
+
+static int ac97_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int reg;
+ u16 vra;
+
+ vra = ac97_read(codec, AC97_EXTENDED_STATUS);
+ ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ reg = AC97_PCM_FRONT_DAC_RATE;
+ else
+ reg = AC97_PCM_LR_ADC_RATE;
+
+ return ac97_write(codec, reg, runtime->rate);
+}
+
+static int ac97_aux_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 vra, xsle;
+
+ vra = ac97_read(codec, AC97_EXTENDED_STATUS);
+ ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
+ xsle = ac97_read(codec, AC97_PCI_SID);
+ ac97_write(codec, AC97_PCI_SID, xsle | 0x8000);
+
+ if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
+ return -ENODEV;
+
+ return ac97_write(codec, AC97_PCM_SURR_DAC_RATE, runtime->rate);
+}
+
+#define WM9712_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\
+ SNDRV_PCM_RATE_48000)
+
+struct snd_soc_dai wm9712_dai[] = {
+{
+ .name = "AC97 HiFi",
+ .type = SND_SOC_DAI_AC97_BUS,
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM9712_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "HiFi Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM9712_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .prepare = ac97_prepare,},
+},
+{
+ .name = "AC97 Aux",
+ .playback = {
+ .stream_name = "Aux Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = WM9712_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .prepare = ac97_aux_prepare,},
+}
+};
+EXPORT_SYMBOL_GPL(wm9712_dai);
+
+static int wm9712_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ ac97_write(codec, AC97_POWERDOWN, 0x0000);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* disable everything including AC link */
+ ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff);
+ ac97_write(codec, AC97_POWERDOWN, 0xffff);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+static int wm9712_reset(struct snd_soc_codec *codec, int try_warm)
+{
+ if (try_warm && soc_ac97_ops.warm_reset) {
+ soc_ac97_ops.warm_reset(codec->ac97);
+ if (ac97_read(codec, 0) == wm9712_reg[0])
+ return 1;
+ }
+
+ soc_ac97_ops.reset(codec->ac97);
+ if (ac97_read(codec, 0) != wm9712_reg[0])
+ goto err;
+ return 0;
+
+err:
+ printk(KERN_ERR "WM9712 AC97 reset failed\n");
+ return -EIO;
+}
+
+static int wm9712_soc_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ wm9712_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm9712_soc_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ int i, ret;
+ u16 *cache = codec->reg_cache;
+
+ ret = wm9712_reset(codec, 1);
+ if (ret < 0) {
+ printk(KERN_ERR "could not reset AC97 codec\n");
+ return ret;
+ }
+
+ wm9712_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ if (ret == 0) {
+ /* Sync reg_cache with the hardware after cold reset */
+ for (i = 2; i < ARRAY_SIZE(wm9712_reg) << 1; i += 2) {
+ if (i == AC97_INT_PAGING || i == AC97_POWERDOWN ||
+ (i > 0x58 && i != 0x5c))
+ continue;
+ soc_ac97_ops.write(codec->ac97, i, cache[i>>1]);
+ }
+ }
+
+ if (codec->suspend_bias_level == SND_SOC_BIAS_ON)
+ wm9712_set_bias_level(codec, SND_SOC_BIAS_ON);
+
+ return ret;
+}
+
+static int wm9712_soc_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ printk(KERN_INFO "WM9711/WM9712 SoC Audio Codec %s\n", WM9712_VERSION);
+
+ socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (socdev->codec == NULL)
+ return -ENOMEM;
+ codec = socdev->codec;
+ mutex_init(&codec->mutex);
+
+ codec->reg_cache = kmemdup(wm9712_reg, sizeof(wm9712_reg), GFP_KERNEL);
+
+ if (codec->reg_cache == NULL) {
+ ret = -ENOMEM;
+ goto cache_err;
+ }
+ codec->reg_cache_size = sizeof(wm9712_reg);
+ codec->reg_cache_step = 2;
+
+ codec->name = "WM9712";
+ codec->owner = THIS_MODULE;
+ codec->dai = wm9712_dai;
+ codec->num_dai = ARRAY_SIZE(wm9712_dai);
+ codec->write = ac97_write;
+ codec->read = ac97_read;
+ codec->set_bias_level = wm9712_set_bias_level;
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0);
+ if (ret < 0) {
+ printk(KERN_ERR "wm9712: failed to register AC97 codec\n");
+ goto codec_err;
+ }
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0)
+ goto pcm_err;
+
+ ret = wm9712_reset(codec, 0);
+ if (ret < 0) {
+ printk(KERN_ERR "AC97 link error\n");
+ goto reset_err;
+ }
+
+ /* set alc mux to none */
+ ac97_write(codec, AC97_VIDEO, ac97_read(codec, AC97_VIDEO) | 0x3000);
+
+ wm9712_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ wm9712_add_controls(codec);
+ wm9712_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "wm9712: failed to register card\n");
+ goto reset_err;
+ }
+
+ return 0;
+
+reset_err:
+ snd_soc_free_pcms(socdev);
+
+pcm_err:
+ snd_soc_free_ac97_codec(codec);
+
+codec_err:
+ kfree(codec->reg_cache);
+
+cache_err:
+ kfree(socdev->codec);
+ socdev->codec = NULL;
+ return ret;
+}
+
+static int wm9712_soc_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec == NULL)
+ return 0;
+
+ snd_soc_dapm_free(socdev);
+ snd_soc_free_pcms(socdev);
+ snd_soc_free_ac97_codec(codec);
+ kfree(codec->reg_cache);
+ kfree(codec);
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm9712 = {
+ .probe = wm9712_soc_probe,
+ .remove = wm9712_soc_remove,
+ .suspend = wm9712_soc_suspend,
+ .resume = wm9712_soc_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm9712);
+
+MODULE_DESCRIPTION("ASoC WM9711/WM9712 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm9712.h b/sound/soc/codecs/wm9712.h
new file mode 100644
index 0000000..d29e8a1
--- /dev/null
+++ b/sound/soc/codecs/wm9712.h
@@ -0,0 +1,14 @@
+/*
+ * wm9712.h -- WM9712 Soc Audio driver
+ */
+
+#ifndef _WM9712_H
+#define _WM9712_H
+
+#define WM9712_DAI_AC97_HIFI 0
+#define WM9712_DAI_AC97_AUX 1
+
+extern struct snd_soc_dai wm9712_dai[2];
+extern struct snd_soc_codec_device soc_codec_dev_wm9712;
+
+#endif
diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c
new file mode 100644
index 0000000..945b32e
--- /dev/null
+++ b/sound/soc/codecs/wm9713.c
@@ -0,0 +1,1306 @@
+/*
+ * wm9713.c -- ALSA Soc WM9713 codec support
+ *
+ * Copyright 2006 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * Features:-
+ *
+ * o Support for AC97 Codec, Voice DAC and Aux DAC
+ * o Support for DAPM
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "wm9713.h"
+
+#define WM9713_VERSION "0.15"
+
+struct wm9713_priv {
+ u32 pll_in; /* PLL input frequency */
+ u32 pll_out; /* PLL output frequency */
+};
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg);
+static int ac97_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int val);
+
+/*
+ * WM9713 register cache
+ * Reg 0x3c bit 15 is used by touch driver.
+ */
+static const u16 wm9713_reg[] = {
+ 0x6174, 0x8080, 0x8080, 0x8080,
+ 0xc880, 0xe808, 0xe808, 0x0808,
+ 0x00da, 0x8000, 0xd600, 0xaaa0,
+ 0xaaa0, 0xaaa0, 0x0000, 0x0000,
+ 0x0f0f, 0x0040, 0x0000, 0x7f00,
+ 0x0405, 0x0410, 0xbb80, 0xbb80,
+ 0x0000, 0xbb80, 0x0000, 0x4523,
+ 0x0000, 0x2000, 0x7eff, 0xffff,
+ 0x0000, 0x0000, 0x0080, 0x0000,
+ 0x0000, 0x0000, 0xfffe, 0xffff,
+ 0x0000, 0x0000, 0x0000, 0xfffe,
+ 0x4000, 0x0000, 0x0000, 0x0000,
+ 0xb032, 0x3e00, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0006,
+ 0x0001, 0x0000, 0x574d, 0x4c13,
+ 0x0000, 0x0000, 0x0000
+};
+
+/* virtual HP mixers regs */
+#define HPL_MIXER 0x80
+#define HPR_MIXER 0x82
+#define MICB_MUX 0x82
+
+static const char *wm9713_mic_mixer[] = {"Stereo", "Mic 1", "Mic 2", "Mute"};
+static const char *wm9713_rec_mux[] = {"Stereo", "Left", "Right", "Mute"};
+static const char *wm9713_rec_src[] =
+ {"Mic 1", "Mic 2", "Line", "Mono In", "Headphone", "Speaker",
+ "Mono Out", "Zh"};
+static const char *wm9713_rec_gain[] = {"+1.5dB Steps", "+0.75dB Steps"};
+static const char *wm9713_alc_select[] = {"None", "Left", "Right", "Stereo"};
+static const char *wm9713_mono_pga[] = {"Vmid", "Zh", "Mono", "Inv",
+ "Mono Vmid", "Inv Vmid"};
+static const char *wm9713_spk_pga[] =
+ {"Vmid", "Zh", "Headphone", "Speaker", "Inv", "Headphone Vmid",
+ "Speaker Vmid", "Inv Vmid"};
+static const char *wm9713_hp_pga[] = {"Vmid", "Zh", "Headphone",
+ "Headphone Vmid"};
+static const char *wm9713_out3_pga[] = {"Vmid", "Zh", "Inv 1", "Inv 1 Vmid"};
+static const char *wm9713_out4_pga[] = {"Vmid", "Zh", "Inv 2", "Inv 2 Vmid"};
+static const char *wm9713_dac_inv[] =
+ {"Off", "Mono", "Speaker", "Left Headphone", "Right Headphone",
+ "Headphone Mono", "NC", "Vmid"};
+static const char *wm9713_bass[] = {"Linear Control", "Adaptive Boost"};
+static const char *wm9713_ng_type[] = {"Constant Gain", "Mute"};
+static const char *wm9713_mic_select[] = {"Mic 1", "Mic 2 A", "Mic 2 B"};
+static const char *wm9713_micb_select[] = {"MPB", "MPA"};
+
+static const struct soc_enum wm9713_enum[] = {
+SOC_ENUM_SINGLE(AC97_LINE, 3, 4, wm9713_mic_mixer), /* record mic mixer 0 */
+SOC_ENUM_SINGLE(AC97_VIDEO, 14, 4, wm9713_rec_mux), /* record mux hp 1 */
+SOC_ENUM_SINGLE(AC97_VIDEO, 9, 4, wm9713_rec_mux), /* record mux mono 2 */
+SOC_ENUM_SINGLE(AC97_VIDEO, 3, 8, wm9713_rec_src), /* record mux left 3 */
+SOC_ENUM_SINGLE(AC97_VIDEO, 0, 8, wm9713_rec_src), /* record mux right 4*/
+SOC_ENUM_DOUBLE(AC97_CD, 14, 6, 2, wm9713_rec_gain), /* record step size 5 */
+SOC_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9713_alc_select), /* alc source select 6*/
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 14, 4, wm9713_mono_pga), /* mono input select 7 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 11, 8, wm9713_spk_pga), /* speaker left input select 8 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 8, 8, wm9713_spk_pga), /* speaker right input select 9 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 6, 3, wm9713_hp_pga), /* headphone left input 10 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 4, 3, wm9713_hp_pga), /* headphone right input 11 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 2, 4, wm9713_out3_pga), /* out 3 source 12 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 0, 4, wm9713_out4_pga), /* out 4 source 13 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 13, 8, wm9713_dac_inv), /* dac invert 1 14 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 10, 8, wm9713_dac_inv), /* dac invert 2 15 */
+SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, wm9713_bass), /* bass control 16 */
+SOC_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9713_ng_type), /* noise gate type 17 */
+SOC_ENUM_SINGLE(AC97_3D_CONTROL, 12, 3, wm9713_mic_select), /* mic selection 18 */
+SOC_ENUM_SINGLE(MICB_MUX, 0, 2, wm9713_micb_select), /* mic selection 19 */
+};
+
+static const struct snd_kcontrol_new wm9713_snd_ac97_controls[] = {
+SOC_DOUBLE("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1),
+SOC_DOUBLE("Speaker Playback Switch", AC97_MASTER, 15, 7, 1, 1),
+SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1),
+SOC_DOUBLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 7, 1, 1),
+SOC_DOUBLE("Line In Volume", AC97_PC_BEEP, 8, 0, 31, 1),
+SOC_DOUBLE("PCM Playback Volume", AC97_PHONE, 8, 0, 31, 1),
+SOC_SINGLE("Mic 1 Volume", AC97_MIC, 8, 31, 1),
+SOC_SINGLE("Mic 2 Volume", AC97_MIC, 0, 31, 1),
+
+SOC_SINGLE("Mic Boost (+20dB) Switch", AC97_LINE, 5, 1, 0),
+SOC_SINGLE("Mic Headphone Mixer Volume", AC97_LINE, 0, 7, 1),
+
+SOC_SINGLE("Capture Switch", AC97_CD, 15, 1, 1),
+SOC_ENUM("Capture Volume Steps", wm9713_enum[5]),
+SOC_DOUBLE("Capture Volume", AC97_CD, 8, 0, 31, 0),
+SOC_SINGLE("Capture ZC Switch", AC97_CD, 7, 1, 0),
+
+SOC_SINGLE("Capture to Headphone Volume", AC97_VIDEO, 11, 7, 1),
+SOC_SINGLE("Capture to Mono Boost (+20dB) Switch", AC97_VIDEO, 8, 1, 0),
+SOC_SINGLE("Capture ADC Boost (+20dB) Switch", AC97_VIDEO, 6, 1, 0),
+
+SOC_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0),
+SOC_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0),
+SOC_SINGLE("ALC Decay Time", AC97_CODEC_CLASS_REV, 4, 15, 0),
+SOC_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0),
+SOC_ENUM("ALC Function", wm9713_enum[6]),
+SOC_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 0),
+SOC_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 0),
+SOC_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0),
+SOC_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0),
+SOC_ENUM("ALC NG Type", wm9713_enum[17]),
+SOC_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 0),
+
+SOC_DOUBLE("Speaker Playback ZC Switch", AC97_MASTER, 14, 6, 1, 0),
+SOC_DOUBLE("Headphone Playback ZC Switch", AC97_HEADPHONE, 14, 6, 1, 0),
+
+SOC_SINGLE("Out4 Playback Switch", AC97_MASTER_MONO, 15, 1, 1),
+SOC_SINGLE("Out4 Playback ZC Switch", AC97_MASTER_MONO, 14, 1, 0),
+SOC_SINGLE("Out4 Playback Volume", AC97_MASTER_MONO, 8, 63, 1),
+
+SOC_SINGLE("Out3 Playback Switch", AC97_MASTER_MONO, 7, 1, 1),
+SOC_SINGLE("Out3 Playback ZC Switch", AC97_MASTER_MONO, 6, 1, 0),
+SOC_SINGLE("Out3 Playback Volume", AC97_MASTER_MONO, 0, 63, 1),
+
+SOC_SINGLE("Mono Capture Volume", AC97_MASTER_TONE, 8, 31, 1),
+SOC_SINGLE("Mono Playback Switch", AC97_MASTER_TONE, 7, 1, 1),
+SOC_SINGLE("Mono Playback ZC Switch", AC97_MASTER_TONE, 6, 1, 0),
+SOC_SINGLE("Mono Playback Volume", AC97_MASTER_TONE, 0, 31, 1),
+
+SOC_SINGLE("PC Beep Playback Headphone Volume", AC97_AUX, 12, 7, 1),
+SOC_SINGLE("PC Beep Playback Speaker Volume", AC97_AUX, 8, 7, 1),
+SOC_SINGLE("PC Beep Playback Mono Volume", AC97_AUX, 4, 7, 1),
+
+SOC_SINGLE("Voice Playback Headphone Volume", AC97_PCM, 12, 7, 1),
+SOC_SINGLE("Voice Playback Master Volume", AC97_PCM, 8, 7, 1),
+SOC_SINGLE("Voice Playback Mono Volume", AC97_PCM, 4, 7, 1),
+
+SOC_SINGLE("Aux Playback Headphone Volume", AC97_REC_SEL, 12, 7, 1),
+SOC_SINGLE("Aux Playback Master Volume", AC97_REC_SEL, 8, 7, 1),
+SOC_SINGLE("Aux Playback Mono Volume", AC97_REC_SEL, 4, 7, 1),
+
+SOC_ENUM("Bass Control", wm9713_enum[16]),
+SOC_SINGLE("Bass Cut-off Switch", AC97_GENERAL_PURPOSE, 12, 1, 1),
+SOC_SINGLE("Tone Cut-off Switch", AC97_GENERAL_PURPOSE, 4, 1, 1),
+SOC_SINGLE("Playback Attenuate (-6dB) Switch", AC97_GENERAL_PURPOSE, 6, 1, 0),
+SOC_SINGLE("Bass Volume", AC97_GENERAL_PURPOSE, 8, 15, 1),
+SOC_SINGLE("Tone Volume", AC97_GENERAL_PURPOSE, 0, 15, 1),
+
+SOC_SINGLE("3D Upper Cut-off Switch", AC97_REC_GAIN_MIC, 5, 1, 0),
+SOC_SINGLE("3D Lower Cut-off Switch", AC97_REC_GAIN_MIC, 4, 1, 0),
+SOC_SINGLE("3D Depth", AC97_REC_GAIN_MIC, 0, 15, 1),
+};
+
+/* add non dapm controls */
+static int wm9713_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm9713_snd_ac97_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm9713_snd_ac97_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+/* We have to create a fake left and right HP mixers because
+ * the codec only has a single control that is shared by both channels.
+ * This makes it impossible to determine the audio path using the current
+ * register map, thus we add a new (virtual) register to help determine the
+ * audio route within the device.
+ */
+static int mixer_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ u16 l, r, beep, tone, phone, rec, pcm, aux;
+
+ l = ac97_read(w->codec, HPL_MIXER);
+ r = ac97_read(w->codec, HPR_MIXER);
+ beep = ac97_read(w->codec, AC97_PC_BEEP);
+ tone = ac97_read(w->codec, AC97_MASTER_TONE);
+ phone = ac97_read(w->codec, AC97_PHONE);
+ rec = ac97_read(w->codec, AC97_REC_SEL);
+ pcm = ac97_read(w->codec, AC97_PCM);
+ aux = ac97_read(w->codec, AC97_AUX);
+
+ if (event & SND_SOC_DAPM_PRE_REG)
+ return 0;
+ if ((l & 0x1) || (r & 0x1))
+ ac97_write(w->codec, AC97_PC_BEEP, beep & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_PC_BEEP, beep | 0x8000);
+
+ if ((l & 0x2) || (r & 0x2))
+ ac97_write(w->codec, AC97_MASTER_TONE, tone & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_MASTER_TONE, tone | 0x8000);
+
+ if ((l & 0x4) || (r & 0x4))
+ ac97_write(w->codec, AC97_PHONE, phone & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_PHONE, phone | 0x8000);
+
+ if ((l & 0x8) || (r & 0x8))
+ ac97_write(w->codec, AC97_REC_SEL, rec & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_REC_SEL, rec | 0x8000);
+
+ if ((l & 0x10) || (r & 0x10))
+ ac97_write(w->codec, AC97_PCM, pcm & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_PCM, pcm | 0x8000);
+
+ if ((l & 0x20) || (r & 0x20))
+ ac97_write(w->codec, AC97_AUX, aux & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_AUX, aux | 0x8000);
+
+ return 0;
+}
+
+/* Left Headphone Mixers */
+static const struct snd_kcontrol_new wm9713_hpl_mixer_controls[] = {
+SOC_DAPM_SINGLE("PC Beep Playback Switch", HPL_MIXER, 5, 1, 0),
+SOC_DAPM_SINGLE("Voice Playback Switch", HPL_MIXER, 4, 1, 0),
+SOC_DAPM_SINGLE("Aux Playback Switch", HPL_MIXER, 3, 1, 0),
+SOC_DAPM_SINGLE("PCM Playback Switch", HPL_MIXER, 2, 1, 0),
+SOC_DAPM_SINGLE("MonoIn Playback Switch", HPL_MIXER, 1, 1, 0),
+SOC_DAPM_SINGLE("Bypass Playback Switch", HPL_MIXER, 0, 1, 0),
+};
+
+/* Right Headphone Mixers */
+static const struct snd_kcontrol_new wm9713_hpr_mixer_controls[] = {
+SOC_DAPM_SINGLE("PC Beep Playback Switch", HPR_MIXER, 5, 1, 0),
+SOC_DAPM_SINGLE("Voice Playback Switch", HPR_MIXER, 4, 1, 0),
+SOC_DAPM_SINGLE("Aux Playback Switch", HPR_MIXER, 3, 1, 0),
+SOC_DAPM_SINGLE("PCM Playback Switch", HPR_MIXER, 2, 1, 0),
+SOC_DAPM_SINGLE("MonoIn Playback Switch", HPR_MIXER, 1, 1, 0),
+SOC_DAPM_SINGLE("Bypass Playback Switch", HPR_MIXER, 0, 1, 0),
+};
+
+/* headphone capture mux */
+static const struct snd_kcontrol_new wm9713_hp_rec_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[1]);
+
+/* headphone mic mux */
+static const struct snd_kcontrol_new wm9713_hp_mic_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[0]);
+
+/* Speaker Mixer */
+static const struct snd_kcontrol_new wm9713_speaker_mixer_controls[] = {
+SOC_DAPM_SINGLE("PC Beep Playback Switch", AC97_AUX, 11, 1, 1),
+SOC_DAPM_SINGLE("Voice Playback Switch", AC97_PCM, 11, 1, 1),
+SOC_DAPM_SINGLE("Aux Playback Switch", AC97_REC_SEL, 11, 1, 1),
+SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PHONE, 14, 1, 1),
+SOC_DAPM_SINGLE("MonoIn Playback Switch", AC97_MASTER_TONE, 14, 1, 1),
+SOC_DAPM_SINGLE("Bypass Playback Switch", AC97_PC_BEEP, 14, 1, 1),
+};
+
+/* Mono Mixer */
+static const struct snd_kcontrol_new wm9713_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("PC Beep Playback Switch", AC97_AUX, 7, 1, 1),
+SOC_DAPM_SINGLE("Voice Playback Switch", AC97_PCM, 7, 1, 1),
+SOC_DAPM_SINGLE("Aux Playback Switch", AC97_REC_SEL, 7, 1, 1),
+SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PHONE, 13, 1, 1),
+SOC_DAPM_SINGLE("MonoIn Playback Switch", AC97_MASTER_TONE, 13, 1, 1),
+SOC_DAPM_SINGLE("Bypass Playback Switch", AC97_PC_BEEP, 13, 1, 1),
+SOC_DAPM_SINGLE("Mic 1 Sidetone Switch", AC97_LINE, 7, 1, 1),
+SOC_DAPM_SINGLE("Mic 2 Sidetone Switch", AC97_LINE, 6, 1, 1),
+};
+
+/* mono mic mux */
+static const struct snd_kcontrol_new wm9713_mono_mic_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[2]);
+
+/* mono output mux */
+static const struct snd_kcontrol_new wm9713_mono_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[7]);
+
+/* speaker left output mux */
+static const struct snd_kcontrol_new wm9713_hp_spkl_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[8]);
+
+/* speaker right output mux */
+static const struct snd_kcontrol_new wm9713_hp_spkr_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[9]);
+
+/* headphone left output mux */
+static const struct snd_kcontrol_new wm9713_hpl_out_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[10]);
+
+/* headphone right output mux */
+static const struct snd_kcontrol_new wm9713_hpr_out_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[11]);
+
+/* Out3 mux */
+static const struct snd_kcontrol_new wm9713_out3_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[12]);
+
+/* Out4 mux */
+static const struct snd_kcontrol_new wm9713_out4_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[13]);
+
+/* DAC inv mux 1 */
+static const struct snd_kcontrol_new wm9713_dac_inv1_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[14]);
+
+/* DAC inv mux 2 */
+static const struct snd_kcontrol_new wm9713_dac_inv2_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[15]);
+
+/* Capture source left */
+static const struct snd_kcontrol_new wm9713_rec_srcl_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[3]);
+
+/* Capture source right */
+static const struct snd_kcontrol_new wm9713_rec_srcr_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[4]);
+
+/* mic source */
+static const struct snd_kcontrol_new wm9713_mic_sel_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[18]);
+
+/* mic source B virtual control */
+static const struct snd_kcontrol_new wm9713_micb_sel_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[19]);
+
+static const struct snd_soc_dapm_widget wm9713_dapm_widgets[] = {
+SND_SOC_DAPM_MUX("Capture Headphone Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_hp_rec_mux_controls),
+SND_SOC_DAPM_MUX("Sidetone Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_hp_mic_mux_controls),
+SND_SOC_DAPM_MUX("Capture Mono Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_mono_mic_mux_controls),
+SND_SOC_DAPM_MUX("Mono Out Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_mono_mux_controls),
+SND_SOC_DAPM_MUX("Left Speaker Out Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_hp_spkl_mux_controls),
+SND_SOC_DAPM_MUX("Right Speaker Out Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_hp_spkr_mux_controls),
+SND_SOC_DAPM_MUX("Left Headphone Out Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_hpl_out_mux_controls),
+SND_SOC_DAPM_MUX("Right Headphone Out Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_hpr_out_mux_controls),
+SND_SOC_DAPM_MUX("Out 3 Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_out3_mux_controls),
+SND_SOC_DAPM_MUX("Out 4 Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_out4_mux_controls),
+SND_SOC_DAPM_MUX("DAC Inv Mux 1", SND_SOC_NOPM, 0, 0,
+ &wm9713_dac_inv1_mux_controls),
+SND_SOC_DAPM_MUX("DAC Inv Mux 2", SND_SOC_NOPM, 0, 0,
+ &wm9713_dac_inv2_mux_controls),
+SND_SOC_DAPM_MUX("Left Capture Source", SND_SOC_NOPM, 0, 0,
+ &wm9713_rec_srcl_mux_controls),
+SND_SOC_DAPM_MUX("Right Capture Source", SND_SOC_NOPM, 0, 0,
+ &wm9713_rec_srcr_mux_controls),
+SND_SOC_DAPM_MUX("Mic A Source", SND_SOC_NOPM, 0, 0,
+ &wm9713_mic_sel_mux_controls),
+SND_SOC_DAPM_MUX("Mic B Source", SND_SOC_NOPM, 0, 0,
+ &wm9713_micb_sel_mux_controls),
+SND_SOC_DAPM_MIXER_E("Left HP Mixer", AC97_EXTENDED_MID, 3, 1,
+ &wm9713_hpl_mixer_controls[0], ARRAY_SIZE(wm9713_hpl_mixer_controls),
+ mixer_event, SND_SOC_DAPM_POST_REG),
+SND_SOC_DAPM_MIXER_E("Right HP Mixer", AC97_EXTENDED_MID, 2, 1,
+ &wm9713_hpr_mixer_controls[0], ARRAY_SIZE(wm9713_hpr_mixer_controls),
+ mixer_event, SND_SOC_DAPM_POST_REG),
+SND_SOC_DAPM_MIXER("Mono Mixer", AC97_EXTENDED_MID, 0, 1,
+ &wm9713_mono_mixer_controls[0], ARRAY_SIZE(wm9713_mono_mixer_controls)),
+SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_EXTENDED_MID, 1, 1,
+ &wm9713_speaker_mixer_controls[0],
+ ARRAY_SIZE(wm9713_speaker_mixer_controls)),
+SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", AC97_EXTENDED_MID, 7, 1),
+SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", AC97_EXTENDED_MID, 6, 1),
+SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("HP Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("Line Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("Capture Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", AC97_EXTENDED_MID, 12, 1),
+SND_SOC_DAPM_DAC("Aux DAC", "Aux Playback", AC97_EXTENDED_MID, 11, 1),
+SND_SOC_DAPM_PGA("Left ADC", AC97_EXTENDED_MID, 5, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Right ADC", AC97_EXTENDED_MID, 4, 1, NULL, 0),
+SND_SOC_DAPM_ADC("Left HiFi ADC", "Left HiFi Capture", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_ADC("Right HiFi ADC", "Right HiFi Capture", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_ADC("Left Voice ADC", "Left Voice Capture", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_ADC("Right Voice ADC", "Right Voice Capture", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_PGA("Left Headphone", AC97_EXTENDED_MSTATUS, 10, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Right Headphone", AC97_EXTENDED_MSTATUS, 9, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Left Speaker", AC97_EXTENDED_MSTATUS, 8, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Right Speaker", AC97_EXTENDED_MSTATUS, 7, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Out 3", AC97_EXTENDED_MSTATUS, 11, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Out 4", AC97_EXTENDED_MSTATUS, 12, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mono Out", AC97_EXTENDED_MSTATUS, 13, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Left Line In", AC97_EXTENDED_MSTATUS, 6, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Right Line In", AC97_EXTENDED_MSTATUS, 5, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mono In", AC97_EXTENDED_MSTATUS, 4, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mic A PGA", AC97_EXTENDED_MSTATUS, 3, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mic B PGA", AC97_EXTENDED_MSTATUS, 2, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mic A Pre Amp", AC97_EXTENDED_MSTATUS, 1, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mic B Pre Amp", AC97_EXTENDED_MSTATUS, 0, 1, NULL, 0),
+SND_SOC_DAPM_MICBIAS("Mic Bias", AC97_EXTENDED_MSTATUS, 14, 1),
+SND_SOC_DAPM_OUTPUT("MONO"),
+SND_SOC_DAPM_OUTPUT("HPL"),
+SND_SOC_DAPM_OUTPUT("HPR"),
+SND_SOC_DAPM_OUTPUT("SPKL"),
+SND_SOC_DAPM_OUTPUT("SPKR"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+SND_SOC_DAPM_OUTPUT("OUT4"),
+SND_SOC_DAPM_INPUT("LINEL"),
+SND_SOC_DAPM_INPUT("LINER"),
+SND_SOC_DAPM_INPUT("MONOIN"),
+SND_SOC_DAPM_INPUT("PCBEEP"),
+SND_SOC_DAPM_INPUT("MIC1"),
+SND_SOC_DAPM_INPUT("MIC2A"),
+SND_SOC_DAPM_INPUT("MIC2B"),
+SND_SOC_DAPM_VMID("VMID"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* left HP mixer */
+ {"Left HP Mixer", "PC Beep Playback Switch", "PCBEEP"},
+ {"Left HP Mixer", "Voice Playback Switch", "Voice DAC"},
+ {"Left HP Mixer", "Aux Playback Switch", "Aux DAC"},
+ {"Left HP Mixer", "Bypass Playback Switch", "Left Line In"},
+ {"Left HP Mixer", "PCM Playback Switch", "Left DAC"},
+ {"Left HP Mixer", "MonoIn Playback Switch", "Mono In"},
+ {"Left HP Mixer", NULL, "Capture Headphone Mux"},
+
+ /* right HP mixer */
+ {"Right HP Mixer", "PC Beep Playback Switch", "PCBEEP"},
+ {"Right HP Mixer", "Voice Playback Switch", "Voice DAC"},
+ {"Right HP Mixer", "Aux Playback Switch", "Aux DAC"},
+ {"Right HP Mixer", "Bypass Playback Switch", "Right Line In"},
+ {"Right HP Mixer", "PCM Playback Switch", "Right DAC"},
+ {"Right HP Mixer", "MonoIn Playback Switch", "Mono In"},
+ {"Right HP Mixer", NULL, "Capture Headphone Mux"},
+
+ /* virtual mixer - mixes left & right channels for spk and mono */
+ {"AC97 Mixer", NULL, "Left DAC"},
+ {"AC97 Mixer", NULL, "Right DAC"},
+ {"Line Mixer", NULL, "Right Line In"},
+ {"Line Mixer", NULL, "Left Line In"},
+ {"HP Mixer", NULL, "Left HP Mixer"},
+ {"HP Mixer", NULL, "Right HP Mixer"},
+ {"Capture Mixer", NULL, "Left Capture Source"},
+ {"Capture Mixer", NULL, "Right Capture Source"},
+
+ /* speaker mixer */
+ {"Speaker Mixer", "PC Beep Playback Switch", "PCBEEP"},
+ {"Speaker Mixer", "Voice Playback Switch", "Voice DAC"},
+ {"Speaker Mixer", "Aux Playback Switch", "Aux DAC"},
+ {"Speaker Mixer", "Bypass Playback Switch", "Line Mixer"},
+ {"Speaker Mixer", "PCM Playback Switch", "AC97 Mixer"},
+ {"Speaker Mixer", "MonoIn Playback Switch", "Mono In"},
+
+ /* mono mixer */
+ {"Mono Mixer", "PC Beep Playback Switch", "PCBEEP"},
+ {"Mono Mixer", "Voice Playback Switch", "Voice DAC"},
+ {"Mono Mixer", "Aux Playback Switch", "Aux DAC"},
+ {"Mono Mixer", "Bypass Playback Switch", "Line Mixer"},
+ {"Mono Mixer", "PCM Playback Switch", "AC97 Mixer"},
+ {"Mono Mixer", "Mic 1 Sidetone Switch", "Mic A PGA"},
+ {"Mono Mixer", "Mic 2 Sidetone Switch", "Mic B PGA"},
+ {"Mono Mixer", NULL, "Capture Mono Mux"},
+
+ /* DAC inv mux 1 */
+ {"DAC Inv Mux 1", "Mono", "Mono Mixer"},
+ {"DAC Inv Mux 1", "Speaker", "Speaker Mixer"},
+ {"DAC Inv Mux 1", "Left Headphone", "Left HP Mixer"},
+ {"DAC Inv Mux 1", "Right Headphone", "Right HP Mixer"},
+ {"DAC Inv Mux 1", "Headphone Mono", "HP Mixer"},
+
+ /* DAC inv mux 2 */
+ {"DAC Inv Mux 2", "Mono", "Mono Mixer"},
+ {"DAC Inv Mux 2", "Speaker", "Speaker Mixer"},
+ {"DAC Inv Mux 2", "Left Headphone", "Left HP Mixer"},
+ {"DAC Inv Mux 2", "Right Headphone", "Right HP Mixer"},
+ {"DAC Inv Mux 2", "Headphone Mono", "HP Mixer"},
+
+ /* headphone left mux */
+ {"Left Headphone Out Mux", "Headphone", "Left HP Mixer"},
+
+ /* headphone right mux */
+ {"Right Headphone Out Mux", "Headphone", "Right HP Mixer"},
+
+ /* speaker left mux */
+ {"Left Speaker Out Mux", "Headphone", "Left HP Mixer"},
+ {"Left Speaker Out Mux", "Speaker", "Speaker Mixer"},
+ {"Left Speaker Out Mux", "Inv", "DAC Inv Mux 1"},
+
+ /* speaker right mux */
+ {"Right Speaker Out Mux", "Headphone", "Right HP Mixer"},
+ {"Right Speaker Out Mux", "Speaker", "Speaker Mixer"},
+ {"Right Speaker Out Mux", "Inv", "DAC Inv Mux 2"},
+
+ /* mono mux */
+ {"Mono Out Mux", "Mono", "Mono Mixer"},
+ {"Mono Out Mux", "Inv", "DAC Inv Mux 1"},
+
+ /* out 3 mux */
+ {"Out 3 Mux", "Inv 1", "DAC Inv Mux 1"},
+
+ /* out 4 mux */
+ {"Out 4 Mux", "Inv 2", "DAC Inv Mux 2"},
+
+ /* output pga */
+ {"HPL", NULL, "Left Headphone"},
+ {"Left Headphone", NULL, "Left Headphone Out Mux"},
+ {"HPR", NULL, "Right Headphone"},
+ {"Right Headphone", NULL, "Right Headphone Out Mux"},
+ {"OUT3", NULL, "Out 3"},
+ {"Out 3", NULL, "Out 3 Mux"},
+ {"OUT4", NULL, "Out 4"},
+ {"Out 4", NULL, "Out 4 Mux"},
+ {"SPKL", NULL, "Left Speaker"},
+ {"Left Speaker", NULL, "Left Speaker Out Mux"},
+ {"SPKR", NULL, "Right Speaker"},
+ {"Right Speaker", NULL, "Right Speaker Out Mux"},
+ {"MONO", NULL, "Mono Out"},
+ {"Mono Out", NULL, "Mono Out Mux"},
+
+ /* input pga */
+ {"Left Line In", NULL, "LINEL"},
+ {"Right Line In", NULL, "LINER"},
+ {"Mono In", NULL, "MONOIN"},
+ {"Mic A PGA", NULL, "Mic A Pre Amp"},
+ {"Mic B PGA", NULL, "Mic B Pre Amp"},
+
+ /* left capture select */
+ {"Left Capture Source", "Mic 1", "Mic A Pre Amp"},
+ {"Left Capture Source", "Mic 2", "Mic B Pre Amp"},
+ {"Left Capture Source", "Line", "LINEL"},
+ {"Left Capture Source", "Mono In", "MONOIN"},
+ {"Left Capture Source", "Headphone", "Left HP Mixer"},
+ {"Left Capture Source", "Speaker", "Speaker Mixer"},
+ {"Left Capture Source", "Mono Out", "Mono Mixer"},
+
+ /* right capture select */
+ {"Right Capture Source", "Mic 1", "Mic A Pre Amp"},
+ {"Right Capture Source", "Mic 2", "Mic B Pre Amp"},
+ {"Right Capture Source", "Line", "LINER"},
+ {"Right Capture Source", "Mono In", "MONOIN"},
+ {"Right Capture Source", "Headphone", "Right HP Mixer"},
+ {"Right Capture Source", "Speaker", "Speaker Mixer"},
+ {"Right Capture Source", "Mono Out", "Mono Mixer"},
+
+ /* left ADC */
+ {"Left ADC", NULL, "Left Capture Source"},
+ {"Left Voice ADC", NULL, "Left ADC"},
+ {"Left HiFi ADC", NULL, "Left ADC"},
+
+ /* right ADC */
+ {"Right ADC", NULL, "Right Capture Source"},
+ {"Right Voice ADC", NULL, "Right ADC"},
+ {"Right HiFi ADC", NULL, "Right ADC"},
+
+ /* mic */
+ {"Mic A Pre Amp", NULL, "Mic A Source"},
+ {"Mic A Source", "Mic 1", "MIC1"},
+ {"Mic A Source", "Mic 2 A", "MIC2A"},
+ {"Mic A Source", "Mic 2 B", "Mic B Source"},
+ {"Mic B Pre Amp", "MPB", "Mic B Source"},
+ {"Mic B Source", NULL, "MIC2B"},
+
+ /* headphone capture */
+ {"Capture Headphone Mux", "Stereo", "Capture Mixer"},
+ {"Capture Headphone Mux", "Left", "Left Capture Source"},
+ {"Capture Headphone Mux", "Right", "Right Capture Source"},
+
+ /* mono capture */
+ {"Capture Mono Mux", "Stereo", "Capture Mixer"},
+ {"Capture Mono Mux", "Left", "Left Capture Source"},
+ {"Capture Mono Mux", "Right", "Right Capture Source"},
+};
+
+static int wm9713_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm9713_dapm_widgets,
+ ARRAY_SIZE(wm9713_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+
+ if (reg == AC97_RESET || reg == AC97_GPIO_STATUS ||
+ reg == AC97_VENDOR_ID1 || reg == AC97_VENDOR_ID2 ||
+ reg == AC97_CD)
+ return soc_ac97_ops.read(codec->ac97, reg);
+ else {
+ reg = reg >> 1;
+
+ if (reg > (ARRAY_SIZE(wm9713_reg)))
+ return -EIO;
+
+ return cache[reg];
+ }
+}
+
+static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg < 0x7c)
+ soc_ac97_ops.write(codec->ac97, reg, val);
+ reg = reg >> 1;
+ if (reg <= (ARRAY_SIZE(wm9713_reg)))
+ cache[reg] = val;
+
+ return 0;
+}
+
+/* PLL divisors */
+struct _pll_div {
+ u32 divsel:1;
+ u32 divctl:1;
+ u32 lf:1;
+ u32 n:4;
+ u32 k:24;
+};
+
+/* The size in bits of the PLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 22) * 10)
+
+static void pll_factors(struct _pll_div *pll_div, unsigned int source)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod, target;
+
+ /* The the PLL output is always 98.304MHz. */
+ target = 98304000;
+
+ /* If the input frequency is over 14.4MHz then scale it down. */
+ if (source > 14400000) {
+ source >>= 1;
+ pll_div->divsel = 1;
+
+ if (source > 14400000) {
+ source >>= 1;
+ pll_div->divctl = 1;
+ } else
+ pll_div->divctl = 0;
+
+ } else {
+ pll_div->divsel = 0;
+ pll_div->divctl = 0;
+ }
+
+ /* Low frequency sources require an additional divide in the
+ * loop.
+ */
+ if (source < 8192000) {
+ pll_div->lf = 1;
+ target >>= 2;
+ } else
+ pll_div->lf = 0;
+
+ Ndiv = target / source;
+ if ((Ndiv < 5) || (Ndiv > 12))
+ printk(KERN_WARNING
+ "WM9713 PLL N value %d out of recommended range!\n",
+ Ndiv);
+
+ pll_div->n = Ndiv;
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ /* Check if we need to round */
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ K /= 10;
+
+ pll_div->k = K;
+}
+
+/**
+ * Please note that changing the PLL input frequency may require
+ * resynchronisation with the AC97 controller.
+ */
+static int wm9713_set_pll(struct snd_soc_codec *codec,
+ int pll_id, unsigned int freq_in, unsigned int freq_out)
+{
+ struct wm9713_priv *wm9713 = codec->private_data;
+ u16 reg, reg2;
+ struct _pll_div pll_div;
+
+ /* turn PLL off ? */
+ if (freq_in == 0 || freq_out == 0) {
+ /* disable PLL power and select ext source */
+ reg = ac97_read(codec, AC97_HANDSET_RATE);
+ ac97_write(codec, AC97_HANDSET_RATE, reg | 0x0080);
+ reg = ac97_read(codec, AC97_EXTENDED_MID);
+ ac97_write(codec, AC97_EXTENDED_MID, reg | 0x0200);
+ wm9713->pll_out = 0;
+ return 0;
+ }
+
+ pll_factors(&pll_div, freq_in);
+
+ if (pll_div.k == 0) {
+ reg = (pll_div.n << 12) | (pll_div.lf << 11) |
+ (pll_div.divsel << 9) | (pll_div.divctl << 8);
+ ac97_write(codec, AC97_LINE1_LEVEL, reg);
+ } else {
+ /* write the fractional k to the reg 0x46 pages */
+ reg2 = (pll_div.n << 12) | (pll_div.lf << 11) | (1 << 10) |
+ (pll_div.divsel << 9) | (pll_div.divctl << 8);
+
+ /* K [21:20] */
+ reg = reg2 | (0x5 << 4) | (pll_div.k >> 20);
+ ac97_write(codec, AC97_LINE1_LEVEL, reg);
+
+ /* K [19:16] */
+ reg = reg2 | (0x4 << 4) | ((pll_div.k >> 16) & 0xf);
+ ac97_write(codec, AC97_LINE1_LEVEL, reg);
+
+ /* K [15:12] */
+ reg = reg2 | (0x3 << 4) | ((pll_div.k >> 12) & 0xf);
+ ac97_write(codec, AC97_LINE1_LEVEL, reg);
+
+ /* K [11:8] */
+ reg = reg2 | (0x2 << 4) | ((pll_div.k >> 8) & 0xf);
+ ac97_write(codec, AC97_LINE1_LEVEL, reg);
+
+ /* K [7:4] */
+ reg = reg2 | (0x1 << 4) | ((pll_div.k >> 4) & 0xf);
+ ac97_write(codec, AC97_LINE1_LEVEL, reg);
+
+ reg = reg2 | (0x0 << 4) | (pll_div.k & 0xf); /* K [3:0] */
+ ac97_write(codec, AC97_LINE1_LEVEL, reg);
+ }
+
+ /* turn PLL on and select as source */
+ reg = ac97_read(codec, AC97_EXTENDED_MID);
+ ac97_write(codec, AC97_EXTENDED_MID, reg & 0xfdff);
+ reg = ac97_read(codec, AC97_HANDSET_RATE);
+ ac97_write(codec, AC97_HANDSET_RATE, reg & 0xff7f);
+ wm9713->pll_out = freq_out;
+ wm9713->pll_in = freq_in;
+
+ /* wait 10ms AC97 link frames for the link to stabilise */
+ schedule_timeout_interruptible(msecs_to_jiffies(10));
+ return 0;
+}
+
+static int wm9713_set_dai_pll(struct snd_soc_dai *codec_dai,
+ int pll_id, unsigned int freq_in, unsigned int freq_out)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ return wm9713_set_pll(codec, pll_id, freq_in, freq_out);
+}
+
+/*
+ * Tristate the PCM DAI lines, tristate can be disabled by calling
+ * wm9713_set_dai_fmt()
+ */
+static int wm9713_set_dai_tristate(struct snd_soc_dai *codec_dai,
+ int tristate)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0x9fff;
+
+ if (tristate)
+ ac97_write(codec, AC97_CENTER_LFE_MASTER, reg);
+
+ return 0;
+}
+
+/*
+ * Configure WM9713 clock dividers.
+ * Voice DAC needs 256 FS
+ */
+static int wm9713_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ switch (div_id) {
+ case WM9713_PCMCLK_DIV:
+ reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xf0ff;
+ ac97_write(codec, AC97_HANDSET_RATE, reg | div);
+ break;
+ case WM9713_CLKA_MULT:
+ reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xfffd;
+ ac97_write(codec, AC97_HANDSET_RATE, reg | div);
+ break;
+ case WM9713_CLKB_MULT:
+ reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xfffb;
+ ac97_write(codec, AC97_HANDSET_RATE, reg | div);
+ break;
+ case WM9713_HIFI_DIV:
+ reg = ac97_read(codec, AC97_HANDSET_RATE) & 0x8fff;
+ ac97_write(codec, AC97_HANDSET_RATE, reg | div);
+ break;
+ case WM9713_PCMBCLK_DIV:
+ reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0xf1ff;
+ ac97_write(codec, AC97_CENTER_LFE_MASTER, reg | div);
+ break;
+ case WM9713_PCMCLK_PLL_DIV:
+ reg = ac97_read(codec, AC97_LINE1_LEVEL) & 0xff80;
+ ac97_write(codec, AC97_LINE1_LEVEL, reg | 0x60 | div);
+ break;
+ case WM9713_HIFI_PLL_DIV:
+ reg = ac97_read(codec, AC97_LINE1_LEVEL) & 0xff80;
+ ac97_write(codec, AC97_LINE1_LEVEL, reg | 0x70 | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm9713_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 gpio = ac97_read(codec, AC97_GPIO_CFG) & 0xffc5;
+ u16 reg = 0x8000;
+
+ /* clock masters */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ reg |= 0x4000;
+ gpio |= 0x0010;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ reg |= 0x6000;
+ gpio |= 0x0018;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ reg |= 0x2000;
+ gpio |= 0x001a;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ gpio |= 0x0012;
+ break;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_IF:
+ reg |= 0x00c0;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ reg |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ reg |= 0x0040;
+ break;
+ }
+
+ /* DAI format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ reg |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ reg |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ reg |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ reg |= 0x0043;
+ break;
+ }
+
+ ac97_write(codec, AC97_GPIO_CFG, gpio);
+ ac97_write(codec, AC97_CENTER_LFE_MASTER, reg);
+ return 0;
+}
+
+static int wm9713_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0xfff3;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ reg |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ reg |= 0x0008;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ reg |= 0x000c;
+ break;
+ }
+
+ /* enable PCM interface in master mode */
+ ac97_write(codec, AC97_CENTER_LFE_MASTER, reg);
+ return 0;
+}
+
+static void wm9713_voiceshutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 status;
+
+ /* Gracefully shut down the voice interface. */
+ status = ac97_read(codec, AC97_EXTENDED_STATUS) | 0x1000;
+ ac97_write(codec, AC97_HANDSET_RATE, 0x0280);
+ schedule_timeout_interruptible(msecs_to_jiffies(1));
+ ac97_write(codec, AC97_HANDSET_RATE, 0x0F80);
+ ac97_write(codec, AC97_EXTENDED_MID, status);
+}
+
+static int ac97_hifi_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int reg;
+ u16 vra;
+
+ vra = ac97_read(codec, AC97_EXTENDED_STATUS);
+ ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ reg = AC97_PCM_FRONT_DAC_RATE;
+ else
+ reg = AC97_PCM_LR_ADC_RATE;
+
+ return ac97_write(codec, reg, runtime->rate);
+}
+
+static int ac97_aux_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 vra, xsle;
+
+ vra = ac97_read(codec, AC97_EXTENDED_STATUS);
+ ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
+ xsle = ac97_read(codec, AC97_PCI_SID);
+ ac97_write(codec, AC97_PCI_SID, xsle | 0x8000);
+
+ if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
+ return -ENODEV;
+
+ return ac97_write(codec, AC97_PCM_SURR_DAC_RATE, runtime->rate);
+}
+
+#define WM9713_RATES (SNDRV_PCM_RATE_8000 | \
+ SNDRV_PCM_RATE_11025 | \
+ SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000)
+
+#define WM9713_PCM_RATES (SNDRV_PCM_RATE_8000 | \
+ SNDRV_PCM_RATE_11025 | \
+ SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000)
+
+#define WM9713_PCM_FORMATS \
+ (SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \
+ SNDRV_PCM_FORMAT_S24_LE)
+
+struct snd_soc_dai wm9713_dai[] = {
+{
+ .name = "AC97 HiFi",
+ .type = SND_SOC_DAI_AC97_BUS,
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM9713_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "HiFi Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM9713_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .prepare = ac97_hifi_prepare,},
+ .dai_ops = {
+ .set_clkdiv = wm9713_set_dai_clkdiv,
+ .set_pll = wm9713_set_dai_pll,},
+ },
+ {
+ .name = "AC97 Aux",
+ .playback = {
+ .stream_name = "Aux Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = WM9713_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .prepare = ac97_aux_prepare,},
+ .dai_ops = {
+ .set_clkdiv = wm9713_set_dai_clkdiv,
+ .set_pll = wm9713_set_dai_pll,},
+ },
+ {
+ .name = "WM9713 Voice",
+ .playback = {
+ .stream_name = "Voice Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = WM9713_PCM_RATES,
+ .formats = WM9713_PCM_FORMATS,},
+ .capture = {
+ .stream_name = "Voice Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM9713_PCM_RATES,
+ .formats = WM9713_PCM_FORMATS,},
+ .ops = {
+ .hw_params = wm9713_pcm_hw_params,
+ .shutdown = wm9713_voiceshutdown,},
+ .dai_ops = {
+ .set_clkdiv = wm9713_set_dai_clkdiv,
+ .set_pll = wm9713_set_dai_pll,
+ .set_fmt = wm9713_set_dai_fmt,
+ .set_tristate = wm9713_set_dai_tristate,
+ },
+ },
+};
+EXPORT_SYMBOL_GPL(wm9713_dai);
+
+int wm9713_reset(struct snd_soc_codec *codec, int try_warm)
+{
+ if (try_warm && soc_ac97_ops.warm_reset) {
+ soc_ac97_ops.warm_reset(codec->ac97);
+ if (ac97_read(codec, 0) == wm9713_reg[0])
+ return 1;
+ }
+
+ soc_ac97_ops.reset(codec->ac97);
+ if (ac97_read(codec, 0) != wm9713_reg[0])
+ return -EIO;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm9713_reset);
+
+static int wm9713_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* enable thermal shutdown */
+ reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x1bff;
+ ac97_write(codec, AC97_EXTENDED_MID, reg);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* enable master bias and vmid */
+ reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x3bff;
+ ac97_write(codec, AC97_EXTENDED_MID, reg);
+ ac97_write(codec, AC97_POWERDOWN, 0x0000);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* disable everything including AC link */
+ ac97_write(codec, AC97_EXTENDED_MID, 0xffff);
+ ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff);
+ ac97_write(codec, AC97_POWERDOWN, 0xffff);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+static int wm9713_soc_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 reg;
+
+ /* Disable everything except touchpanel - that will be handled
+ * by the touch driver and left disabled if touch is not in
+ * use. */
+ reg = ac97_read(codec, AC97_EXTENDED_MID);
+ ac97_write(codec, AC97_EXTENDED_MID, reg | 0x7fff);
+ ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff);
+ ac97_write(codec, AC97_POWERDOWN, 0x6f00);
+ ac97_write(codec, AC97_POWERDOWN, 0xffff);
+
+ return 0;
+}
+
+static int wm9713_soc_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ struct wm9713_priv *wm9713 = codec->private_data;
+ int i, ret;
+ u16 *cache = codec->reg_cache;
+
+ ret = wm9713_reset(codec, 1);
+ if (ret < 0) {
+ printk(KERN_ERR "could not reset AC97 codec\n");
+ return ret;
+ }
+
+ wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* do we need to re-start the PLL ? */
+ if (wm9713->pll_out)
+ wm9713_set_pll(codec, 0, wm9713->pll_in, wm9713->pll_out);
+
+ /* only synchronise the codec if warm reset failed */
+ if (ret == 0) {
+ for (i = 2; i < ARRAY_SIZE(wm9713_reg) << 1; i += 2) {
+ if (i == AC97_POWERDOWN || i == AC97_EXTENDED_MID ||
+ i == AC97_EXTENDED_MSTATUS || i > 0x66)
+ continue;
+ soc_ac97_ops.write(codec->ac97, i, cache[i>>1]);
+ }
+ }
+
+ if (codec->suspend_bias_level == SND_SOC_BIAS_ON)
+ wm9713_set_bias_level(codec, SND_SOC_BIAS_ON);
+
+ return ret;
+}
+
+static int wm9713_soc_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret = 0, reg;
+
+ printk(KERN_INFO "WM9713/WM9714 SoC Audio Codec %s\n", WM9713_VERSION);
+
+ socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (socdev->codec == NULL)
+ return -ENOMEM;
+ codec = socdev->codec;
+ mutex_init(&codec->mutex);
+
+ codec->reg_cache = kmemdup(wm9713_reg, sizeof(wm9713_reg), GFP_KERNEL);
+ if (codec->reg_cache == NULL) {
+ ret = -ENOMEM;
+ goto cache_err;
+ }
+ codec->reg_cache_size = sizeof(wm9713_reg);
+ codec->reg_cache_step = 2;
+
+ codec->private_data = kzalloc(sizeof(struct wm9713_priv), GFP_KERNEL);
+ if (codec->private_data == NULL) {
+ ret = -ENOMEM;
+ goto priv_err;
+ }
+
+ codec->name = "WM9713";
+ codec->owner = THIS_MODULE;
+ codec->dai = wm9713_dai;
+ codec->num_dai = ARRAY_SIZE(wm9713_dai);
+ codec->write = ac97_write;
+ codec->read = ac97_read;
+ codec->set_bias_level = wm9713_set_bias_level;
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0);
+ if (ret < 0)
+ goto codec_err;
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0)
+ goto pcm_err;
+
+ /* do a cold reset for the controller and then try
+ * a warm reset followed by an optional cold reset for codec */
+ wm9713_reset(codec, 0);
+ ret = wm9713_reset(codec, 1);
+ if (ret < 0) {
+ printk(KERN_ERR "AC97 link error\n");
+ goto reset_err;
+ }
+
+ wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* unmute the adc - move to kcontrol */
+ reg = ac97_read(codec, AC97_CD) & 0x7fff;
+ ac97_write(codec, AC97_CD, reg);
+
+ wm9713_add_controls(codec);
+ wm9713_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0)
+ goto reset_err;
+ return 0;
+
+reset_err:
+ snd_soc_free_pcms(socdev);
+
+pcm_err:
+ snd_soc_free_ac97_codec(codec);
+
+codec_err:
+ kfree(codec->private_data);
+
+priv_err:
+ kfree(codec->reg_cache);
+
+cache_err:
+ kfree(socdev->codec);
+ socdev->codec = NULL;
+ return ret;
+}
+
+static int wm9713_soc_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec == NULL)
+ return 0;
+
+ snd_soc_dapm_free(socdev);
+ snd_soc_free_pcms(socdev);
+ snd_soc_free_ac97_codec(codec);
+ kfree(codec->private_data);
+ kfree(codec->reg_cache);
+ kfree(codec->dai);
+ kfree(codec);
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm9713 = {
+ .probe = wm9713_soc_probe,
+ .remove = wm9713_soc_remove,
+ .suspend = wm9713_soc_suspend,
+ .resume = wm9713_soc_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm9713);
+
+MODULE_DESCRIPTION("ASoC WM9713/WM9714 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm9713.h b/sound/soc/codecs/wm9713.h
new file mode 100644
index 0000000..63b8d81
--- /dev/null
+++ b/sound/soc/codecs/wm9713.h
@@ -0,0 +1,53 @@
+/*
+ * wm9713.h -- WM9713 Soc Audio driver
+ */
+
+#ifndef _WM9713_H
+#define _WM9713_H
+
+/* clock inputs */
+#define WM9713_CLKA_PIN 0
+#define WM9713_CLKB_PIN 1
+
+/* clock divider ID's */
+#define WM9713_PCMCLK_DIV 0
+#define WM9713_CLKA_MULT 1
+#define WM9713_CLKB_MULT 2
+#define WM9713_HIFI_DIV 3
+#define WM9713_PCMBCLK_DIV 4
+#define WM9713_PCMCLK_PLL_DIV 5
+#define WM9713_HIFI_PLL_DIV 6
+
+/* Calculate the appropriate bit mask for the external PCM clock divider */
+#define WM9713_PCMDIV(x) ((x - 1) << 8)
+
+/* Calculate the appropriate bit mask for the external HiFi clock divider */
+#define WM9713_HIFIDIV(x) ((x - 1) << 12)
+
+/* MCLK clock mulitipliers */
+#define WM9713_CLKA_X1 (0 << 1)
+#define WM9713_CLKA_X2 (1 << 1)
+#define WM9713_CLKB_X1 (0 << 2)
+#define WM9713_CLKB_X2 (1 << 2)
+
+/* MCLK clock MUX */
+#define WM9713_CLK_MUX_A (0 << 0)
+#define WM9713_CLK_MUX_B (1 << 0)
+
+/* Voice DAI BCLK divider */
+#define WM9713_PCMBCLK_DIV_1 (0 << 9)
+#define WM9713_PCMBCLK_DIV_2 (1 << 9)
+#define WM9713_PCMBCLK_DIV_4 (2 << 9)
+#define WM9713_PCMBCLK_DIV_8 (3 << 9)
+#define WM9713_PCMBCLK_DIV_16 (4 << 9)
+
+#define WM9713_DAI_AC97_HIFI 0
+#define WM9713_DAI_AC97_AUX 1
+#define WM9713_DAI_PCM_VOICE 2
+
+extern struct snd_soc_codec_device soc_codec_dev_wm9713;
+extern struct snd_soc_dai wm9713_dai[3];
+
+int wm9713_reset(struct snd_soc_codec *codec, int try_warm);
+
+#endif
OpenPOWER on IntegriCloud