Merge branch 'topic/asoc' into for-linus
authorTakashi Iwai <tiwai@suse.de>
Wed, 10 Jun 2009 05:26:18 +0000 (07:26 +0200)
committerTakashi Iwai <tiwai@suse.de>
Wed, 10 Jun 2009 05:26:18 +0000 (07:26 +0200)
* topic/asoc: (135 commits)
  ASoC: Apostrophe patrol
  ASoC: codec tlv320aic23 fix bogus divide by 0 message
  ASoC: fix NULL pointer dereference in soc_suspend()
  ASoC: Fix build error in twl4030.c
  ASoC: SSM2602: assign last substream to the master when shutting down
  ASoC: Blackfin: document how anomaly 05000250 is handled
  ASoC: Blackfin: set the transfer size according the ac97_frame size
  ASoC: SSM2602: remove unsupported sample rates
  ASoC: TWL4030: Check the interface format for 4 channel mode
  ASoC: TWL4030: Use reg_cache in twl4030_init_chip
  ASoC: Initialise dev for the dummy S/PDIF DAI
  ASoC: Add dummy S/PDIF codec support
  ASoC: correct print specifiers for unsigneds
  ASoC: Modify mpc5200 AC97 driver to use V9 of spin_event_timeout()
  ASoC: Switch FSL SSI DAI over to symmetric_rates
  ASoC: Mark MPC5200 AC97 as BROKEN until PowerPC merge issues are resolved
  ASoC: Fabric bindings for STAC9766 on the Efika
  ASoC: Support for AC97 on Phytec pmc030 base board.
  ASoC: AC97 driver for mpc5200
  ASoC: Main rewite of the mpc5200 audio DMA code
  ...

101 files changed:
Documentation/sound/alsa/soc/dapm.txt
MAINTAINERS
arch/powerpc/include/asm/mpc52xx_psc.h
include/sound/soc-dai.h
include/sound/soc-dapm.h
include/sound/soc.h
include/sound/wm9081.h [new file with mode: 0644]
sound/aoa/fabrics/layout.c
sound/aoa/soundbus/i2sbus/core.c
sound/soc/Kconfig
sound/soc/Makefile
sound/soc/atmel/Kconfig
sound/soc/atmel/Makefile
sound/soc/atmel/playpaq_wm8510.c
sound/soc/atmel/snd-soc-afeb9260.c [new file with mode: 0644]
sound/soc/blackfin/bf5xx-ac97.c
sound/soc/blackfin/bf5xx-sport.c
sound/soc/codecs/Kconfig
sound/soc/codecs/Makefile
sound/soc/codecs/ac97.c
sound/soc/codecs/ad1980.c
sound/soc/codecs/cs4270.c
sound/soc/codecs/spdif_transciever.c [new file with mode: 0644]
sound/soc/codecs/spdif_transciever.h [new file with mode: 0644]
sound/soc/codecs/ssm2602.c
sound/soc/codecs/stac9766.c [new file with mode: 0644]
sound/soc/codecs/stac9766.h [new file with mode: 0644]
sound/soc/codecs/tlv320aic23.c
sound/soc/codecs/twl4030.c
sound/soc/codecs/twl4030.h
sound/soc/codecs/uda134x.c
sound/soc/codecs/wm8350.c
sound/soc/codecs/wm8350.h
sound/soc/codecs/wm8400.c
sound/soc/codecs/wm8510.c
sound/soc/codecs/wm8580.c
sound/soc/codecs/wm8731.c
sound/soc/codecs/wm8753.c
sound/soc/codecs/wm8900.c
sound/soc/codecs/wm8903.c
sound/soc/codecs/wm8940.c [new file with mode: 0644]
sound/soc/codecs/wm8940.h [new file with mode: 0644]
sound/soc/codecs/wm8960.c [new file with mode: 0644]
sound/soc/codecs/wm8960.h [new file with mode: 0644]
sound/soc/codecs/wm8988.c [new file with mode: 0644]
sound/soc/codecs/wm8988.h [new file with mode: 0644]
sound/soc/codecs/wm8990.c
sound/soc/codecs/wm9081.c [new file with mode: 0644]
sound/soc/codecs/wm9081.h [new file with mode: 0644]
sound/soc/codecs/wm9705.c
sound/soc/codecs/wm9712.c
sound/soc/codecs/wm9713.c
sound/soc/fsl/Kconfig
sound/soc/fsl/Makefile
sound/soc/fsl/efika-audio-fabric.c [new file with mode: 0644]
sound/soc/fsl/fsl_ssi.c
sound/soc/fsl/mpc5200_dma.c [new file with mode: 0644]
sound/soc/fsl/mpc5200_dma.h [new file with mode: 0644]
sound/soc/fsl/mpc5200_psc_ac97.c [new file with mode: 0644]
sound/soc/fsl/mpc5200_psc_ac97.h [new file with mode: 0644]
sound/soc/fsl/mpc5200_psc_i2s.c
sound/soc/fsl/mpc5200_psc_i2s.h [new file with mode: 0644]
sound/soc/fsl/pcm030-audio-fabric.c [new file with mode: 0644]
sound/soc/omap/Kconfig
sound/soc/omap/Makefile
sound/soc/omap/n810.c
sound/soc/omap/omap-mcbsp.c
sound/soc/omap/omap-pcm.c
sound/soc/omap/omap2evm.c
sound/soc/omap/omap3beagle.c
sound/soc/omap/omap3evm.c [new file with mode: 0644]
sound/soc/omap/omap3pandora.c
sound/soc/omap/overo.c
sound/soc/omap/sdp3430.c
sound/soc/pxa/Kconfig
sound/soc/pxa/Makefile
sound/soc/pxa/em-x270.c
sound/soc/pxa/imote2.c [new file with mode: 0644]
sound/soc/pxa/magician.c
sound/soc/pxa/pxa-ssp.c
sound/soc/pxa/pxa2xx-i2s.c
sound/soc/s3c24xx/s3c-i2s-v2.c
sound/soc/s3c24xx/s3c2412-i2s.c
sound/soc/s3c24xx/s3c64xx-i2s.c
sound/soc/s3c24xx/s3c64xx-i2s.h
sound/soc/s6000/Kconfig [new file with mode: 0644]
sound/soc/s6000/Makefile [new file with mode: 0644]
sound/soc/s6000/s6000-i2s.c [new file with mode: 0644]
sound/soc/s6000/s6000-i2s.h [new file with mode: 0644]
sound/soc/s6000/s6000-pcm.c [new file with mode: 0644]
sound/soc/s6000/s6000-pcm.h [new file with mode: 0644]
sound/soc/s6000/s6105-ipcam.c [new file with mode: 0644]
sound/soc/sh/ssi.c
sound/soc/soc-core.c
sound/soc/soc-dapm.c
sound/soc/txx9/Kconfig [new file with mode: 0644]
sound/soc/txx9/Makefile [new file with mode: 0644]
sound/soc/txx9/txx9aclc-ac97.c [new file with mode: 0644]
sound/soc/txx9/txx9aclc-generic.c [new file with mode: 0644]
sound/soc/txx9/txx9aclc.c [new file with mode: 0644]
sound/soc/txx9/txx9aclc.h [new file with mode: 0644]

index 9e6763264a2ee1cb5b549cc27c51f47557d3ba0d..9ac842be9b4fccff2c2841d1fd88ab4be321d467 100644 (file)
@@ -62,6 +62,7 @@ Audio DAPM widgets fall into a number of types:-
  o Mic        - Mic (and optional Jack)
  o Line       - Line Input/Output (and optional Jack)
  o Speaker    - Speaker
+ o Supply     - Power or clock supply widget used by other widgets.
  o Pre        - Special PRE widget (exec before all others)
  o Post       - Special POST widget (exec after all others)
 
index cf4abddfc8a40dcc161e851c26294480fff3076f..e8cb115e33b89a4fd5df668951a40ffed827fafd 100644 (file)
@@ -4574,7 +4574,8 @@ F:        drivers/pcmcia/pxa2xx*
 F:     drivers/spi/pxa2xx*
 F:     drivers/usb/gadget/pxa2*
 F:     include/sound/pxa2xx-lib.h
-F:     sound/soc/pxa/pxa2xx*
+F:     sound/arm/pxa*
+F:     sound/soc/pxa
 
 PXA168 SUPPORT
 P:     Eric Miao
@@ -5302,11 +5303,12 @@ P:      Liam Girdwood
 M:     lrg@slimlogic.co.uk
 P:     Mark Brown
 M:     broonie@opensource.wolfsonmicro.com
-T:     git git://opensource.wolfsonmicro.com/linux-2.6-asoc
+T:     git git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git
 L:     alsa-devel@alsa-project.org (subscribers-only)
 W:     http://alsa-project.org/main/index.php/ASoC
 S:     Supported
 F:     sound/soc/
+F:     include/sound/soc*
 
 SPARC + UltraSPARC (sparc/sparc64)
 P:     David S. Miller
index a218da6bec7c5ea69091196d363b9cb7c51158b0..fb841205745055151c43100d2e15ba33b80f9eed 100644 (file)
 #define MPC52xx_PSC_MAXNUM     6
 
 /* Programmable Serial Controller (PSC) status register bits */
+#define MPC52xx_PSC_SR_UNEX_RX 0x0001
+#define MPC52xx_PSC_SR_DATA_VAL        0x0002
+#define MPC52xx_PSC_SR_DATA_OVR        0x0004
+#define MPC52xx_PSC_SR_CMDSEND 0x0008
 #define MPC52xx_PSC_SR_CDE     0x0080
 #define MPC52xx_PSC_SR_RXRDY   0x0100
 #define MPC52xx_PSC_SR_RXFULL  0x0200
 #define MPC52xx_PSC_RXTX_FIFO_EMPTY    0x0001
 
 /* PSC interrupt status/mask bits */
+#define MPC52xx_PSC_IMR_UNEX_RX_SLOT 0x0001
+#define MPC52xx_PSC_IMR_DATA_VALID     0x0002
+#define MPC52xx_PSC_IMR_DATA_OVR       0x0004
+#define MPC52xx_PSC_IMR_CMD_SEND       0x0008
+#define MPC52xx_PSC_IMR_ERROR          0x0040
+#define MPC52xx_PSC_IMR_DEOF           0x0080
 #define MPC52xx_PSC_IMR_TXRDY          0x0100
 #define MPC52xx_PSC_IMR_RXRDY          0x0200
 #define MPC52xx_PSC_IMR_DB             0x0400
 #define MPC52xx_PSC_SICR_SIM_FIR               (0x6 << 24)
 #define MPC52xx_PSC_SICR_SIM_CODEC_24          (0x7 << 24)
 #define MPC52xx_PSC_SICR_SIM_CODEC_32          (0xf << 24)
+#define MPC52xx_PSC_SICR_AWR                   (1 << 30)
 #define MPC52xx_PSC_SICR_GENCLK                        (1 << 23)
 #define MPC52xx_PSC_SICR_I2S                   (1 << 22)
 #define MPC52xx_PSC_SICR_CLKPOL                        (1 << 21)
index 13676472ddfcca7ace85a4bc407c66d40a746b79..352d7eee9b6d912739fecbf48ec20aef0485a0d0 100644 (file)
@@ -44,24 +44,6 @@ struct snd_pcm_substream;
 #define SND_SOC_DAIFMT_CONT            (0 << 4) /* continuous clock */
 #define SND_SOC_DAIFMT_GATED           (1 << 4) /* clock is gated */
 
-/*
- * DAI Left/Right Clocks.
- *
- * Specifies whether the DAI can support different samples for similtanious
- * playback and capture. This usually requires a seperate physical frame
- * clock for playback and capture.
- */
-#define SND_SOC_DAIFMT_SYNC            (0 << 5) /* Tx FRM = Rx FRM */
-#define SND_SOC_DAIFMT_ASYNC           (1 << 5) /* Tx FRM ~ Rx FRM */
-
-/*
- * TDM
- *
- * Time Division Multiplexing. Allows PCM data to be multplexed with other
- * data on the DAI.
- */
-#define SND_SOC_DAIFMT_TDM             (1 << 6)
-
 /*
  * DAI hardware signal inversions.
  *
@@ -96,6 +78,10 @@ struct snd_pcm_substream;
 #define SND_SOC_CLOCK_IN               0
 #define SND_SOC_CLOCK_OUT              1
 
+#define SND_SOC_STD_AC97_FMTS (SNDRV_PCM_FMTBIT_S16_LE |\
+                               SNDRV_PCM_FMTBIT_S32_LE |\
+                               SNDRV_PCM_FMTBIT_S32_BE)
+
 struct snd_soc_dai_ops;
 struct snd_soc_dai;
 struct snd_ac97_bus_ops;
@@ -208,6 +194,7 @@ struct snd_soc_dai {
        /* DAI capabilities */
        struct snd_soc_pcm_stream capture;
        struct snd_soc_pcm_stream playback;
+       unsigned int symmetric_rates:1;
 
        /* DAI runtime info */
        struct snd_pcm_runtime *runtime;
@@ -219,11 +206,8 @@ struct snd_soc_dai {
        /* DAI private data */
        void *private_data;
 
-       /* parent codec/platform */
-       union {
-               struct snd_soc_codec *codec;
-               struct snd_soc_platform *platform;
-       };
+       /* parent platform */
+       struct snd_soc_platform *platform;
 
        struct list_head list;
 };
index a7def6a9a030e00671cb40ea6da2498ffca9caa2..ec8a45f9a06950f6bf1cfa0ea0d46ed3ca5200f0 100644 (file)
 #define SND_SOC_DAPM_DAC(wname, stname, wreg, wshift, winvert) \
 {      .id = snd_soc_dapm_dac, .name = wname, .sname = stname, .reg = wreg, \
        .shift = wshift, .invert = winvert}
+#define SND_SOC_DAPM_DAC_E(wname, stname, wreg, wshift, winvert, \
+                          wevent, wflags)                              \
+{      .id = snd_soc_dapm_dac, .name = wname, .sname = stname, .reg = wreg, \
+       .shift = wshift, .invert = winvert, \
+       .event = wevent, .event_flags = wflags}
 #define SND_SOC_DAPM_ADC(wname, stname, wreg, wshift, winvert) \
 {      .id = snd_soc_dapm_adc, .name = wname, .sname = stname, .reg = wreg, \
        .shift = wshift, .invert = winvert}
+#define SND_SOC_DAPM_ADC_E(wname, stname, wreg, wshift, winvert, \
+                          wevent, wflags)                              \
+{      .id = snd_soc_dapm_adc, .name = wname, .sname = stname, .reg = wreg, \
+       .shift = wshift, .invert = winvert, \
+       .event = wevent, .event_flags = wflags}
 
-/* generic register modifier widget */
+/* generic widgets */
 #define SND_SOC_DAPM_REG(wid, wname, wreg, wshift, wmask, won_val, woff_val) \
 {      .id = wid, .name = wname, .kcontrols = NULL, .num_kcontrols = 0, \
        .reg = -((wreg) + 1), .shift = wshift, .mask = wmask, \
        .on_val = won_val, .off_val = woff_val, .event = dapm_reg_event, \
        .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD}
+#define SND_SOC_DAPM_SUPPLY(wname, wreg, wshift, winvert, wevent, wflags) \
+{      .id = snd_soc_dapm_supply, .name = wname, .reg = wreg,  \
+       .shift = wshift, .invert = winvert, .event = wevent, \
+       .event_flags = wflags}
 
 /* dapm kcontrol types */
 #define SOC_DAPM_SINGLE(xname, reg, shift, max, invert) \
@@ -265,8 +279,6 @@ int snd_soc_dapm_add_routes(struct snd_soc_codec *codec,
 /* dapm events */
 int snd_soc_dapm_stream_event(struct snd_soc_codec *codec, char *stream,
        int event);
-int snd_soc_dapm_set_bias_level(struct snd_soc_device *socdev,
-       enum snd_soc_bias_level level);
 
 /* dapm sys fs - used by the core */
 int snd_soc_dapm_sys_add(struct device *dev);
@@ -298,6 +310,7 @@ enum snd_soc_dapm_type {
        snd_soc_dapm_vmid,                      /* codec bias/vmid - to minimise pops */
        snd_soc_dapm_pre,                       /* machine specific pre widget - exec first */
        snd_soc_dapm_post,                      /* machine specific post widget - exec last */
+       snd_soc_dapm_supply,            /* power/clock supply */
 };
 
 /*
@@ -357,6 +370,8 @@ struct snd_soc_dapm_widget {
        unsigned char suspend:1;                /* was active before suspend */
        unsigned char pmdown:1;                 /* waiting for timeout */
 
+       int (*power_check)(struct snd_soc_dapm_widget *w);
+
        /* external events */
        unsigned short event_flags;             /* flags to specify event types */
        int (*event)(struct snd_soc_dapm_widget*, struct snd_kcontrol *, int);
@@ -368,6 +383,9 @@ struct snd_soc_dapm_widget {
        /* widget input and outputs */
        struct list_head sources;
        struct list_head sinks;
+
+       /* used during DAPM updates */
+       struct list_head power_list;
 };
 
 #endif
index a40bc6f316fc668b6328374315aded850a40262d..cf6111d72b17015572a131a26c4c7913378adcd0 100644 (file)
        .info = snd_soc_info_volsw, \
        .get = xhandler_get, .put = xhandler_put, \
        .private_value = SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert) }
+#define SOC_DOUBLE_EXT(xname, xreg, shift_left, shift_right, xmax, xinvert,\
+        xhandler_get, xhandler_put) \
+{      .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
+       .info = snd_soc_info_volsw, \
+       .get = xhandler_get, .put = xhandler_put, \
+       .private_value = (unsigned long)&(struct soc_mixer_control) \
+               {.reg = xreg, .shift = shift_left, .rshift = shift_right, \
+                .max = xmax, .invert = xinvert} }
 #define SOC_SINGLE_EXT_TLV(xname, xreg, xshift, xmax, xinvert,\
         xhandler_get, xhandler_put, tlv_array) \
 {      .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
@@ -206,10 +214,6 @@ void snd_soc_jack_free_gpios(struct snd_soc_jack *jack, int count,
                        struct snd_soc_jack_gpio *gpios);
 #endif
 
-/* codec IO */
-#define snd_soc_read(codec, reg) codec->read(codec, reg)
-#define snd_soc_write(codec, reg, value) codec->write(codec, reg, value)
-
 /* codec register bit access */
 int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg,
                                unsigned short mask, unsigned short value);
@@ -331,6 +335,7 @@ struct snd_soc_codec {
        struct module *owner;
        struct mutex mutex;
        struct device *dev;
+       struct snd_soc_device *socdev;
 
        struct list_head list;
 
@@ -364,6 +369,8 @@ struct snd_soc_codec {
        enum snd_soc_bias_level bias_level;
        enum snd_soc_bias_level suspend_bias_level;
        struct delayed_work delayed_work;
+       struct list_head up_list;
+       struct list_head down_list;
 
        /* codec DAI's */
        struct snd_soc_dai *dai;
@@ -417,6 +424,12 @@ struct snd_soc_dai_link  {
        /* codec/machine specific init - e.g. add machine controls */
        int (*init)(struct snd_soc_codec *codec);
 
+       /* Symmetry requirements */
+       unsigned int symmetric_rates:1;
+
+       /* Symmetry data - only valid if symmetry is being enforced */
+       unsigned int rate;
+
        /* DAI pcm */
        struct snd_pcm *pcm;
 };
@@ -490,6 +503,19 @@ struct soc_enum {
        void *dapm;
 };
 
+/* codec IO */
+static inline unsigned int snd_soc_read(struct snd_soc_codec *codec,
+                                       unsigned int reg)
+{
+       return codec->read(codec, reg);
+}
+
+static inline unsigned int snd_soc_write(struct snd_soc_codec *codec,
+                                        unsigned int reg, unsigned int val)
+{
+       return codec->write(codec, reg, val);
+}
+
 #include <sound/soc-dai.h>
 
 #endif
diff --git a/include/sound/wm9081.h b/include/sound/wm9081.h
new file mode 100644 (file)
index 0000000..e173ddb
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * linux/sound/wm9081.h -- Platform data for WM9081
+ *
+ * Copyright 2009 Wolfson Microelectronics. PLC.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __LINUX_SND_WM_9081_H
+#define __LINUX_SND_WM_9081_H
+
+struct wm9081_retune_mobile_setting {
+       const char *name;
+       unsigned int rate;
+       u16 config[20];
+};
+
+struct wm9081_retune_mobile_config {
+       struct wm9081_retune_mobile_setting *configs;
+       int num_configs;
+};
+
+#endif
index fbf5c933baa4aa1487e9f2559210ac61f262be07..586965f9605fd55d4a6cf9c8f804f01d373647bb 100644 (file)
@@ -1037,7 +1037,7 @@ static int aoa_fabric_layout_probe(struct soundbus_dev *sdev)
        }
        ldev->selfptr_headphone.ptr = ldev;
        ldev->selfptr_lineout.ptr = ldev;
-       sdev->ofdev.dev.driver_data = ldev;
+       dev_set_drvdata(&sdev->ofdev.dev, ldev);
        list_add(&ldev->list, &layouts_list);
        layouts_list_items++;
 
@@ -1081,7 +1081,7 @@ static int aoa_fabric_layout_probe(struct soundbus_dev *sdev)
 
 static int aoa_fabric_layout_remove(struct soundbus_dev *sdev)
 {
-       struct layout_dev *ldev = sdev->ofdev.dev.driver_data;
+       struct layout_dev *ldev = dev_get_drvdata(&sdev->ofdev.dev);
        int i;
 
        for (i=0; i<MAX_CODECS_PER_BUS; i++) {
@@ -1114,7 +1114,7 @@ static int aoa_fabric_layout_remove(struct soundbus_dev *sdev)
 #ifdef CONFIG_PM
 static int aoa_fabric_layout_suspend(struct soundbus_dev *sdev, pm_message_t state)
 {
-       struct layout_dev *ldev = sdev->ofdev.dev.driver_data;
+       struct layout_dev *ldev = dev_get_drvdata(&sdev->ofdev.dev);
 
        if (ldev->gpio.methods && ldev->gpio.methods->all_amps_off)
                ldev->gpio.methods->all_amps_off(&ldev->gpio);
@@ -1124,7 +1124,7 @@ static int aoa_fabric_layout_suspend(struct soundbus_dev *sdev, pm_message_t sta
 
 static int aoa_fabric_layout_resume(struct soundbus_dev *sdev)
 {
-       struct layout_dev *ldev = sdev->ofdev.dev.driver_data;
+       struct layout_dev *ldev = dev_get_drvdata(&sdev->ofdev.dev);
 
        if (ldev->gpio.methods && ldev->gpio.methods->all_amps_off)
                ldev->gpio.methods->all_amps_restore(&ldev->gpio);
index 418c84c99d6946d8cb4982569ada0542c1327dfa..4e3b819d49930f07b7ce641c1de0a32b878f8bcd 100644 (file)
@@ -358,14 +358,14 @@ static int i2sbus_probe(struct macio_dev* dev, const struct of_device_id *match)
                return -ENODEV;
        }
 
-       dev->ofdev.dev.driver_data = control;
+       dev_set_drvdata(&dev->ofdev.dev, control);
 
        return 0;
 }
 
 static int i2sbus_remove(struct macio_dev* dev)
 {
-       struct i2sbus_control *control = dev->ofdev.dev.driver_data;
+       struct i2sbus_control *control = dev_get_drvdata(&dev->ofdev.dev);
        struct i2sbus_dev *i2sdev, *tmp;
 
        list_for_each_entry_safe(i2sdev, tmp, &control->list, item)
@@ -377,7 +377,7 @@ static int i2sbus_remove(struct macio_dev* dev)
 #ifdef CONFIG_PM
 static int i2sbus_suspend(struct macio_dev* dev, pm_message_t state)
 {
-       struct i2sbus_control *control = dev->ofdev.dev.driver_data;
+       struct i2sbus_control *control = dev_get_drvdata(&dev->ofdev.dev);
        struct codec_info_item *cii;
        struct i2sbus_dev* i2sdev;
        int err, ret = 0;
@@ -407,7 +407,7 @@ static int i2sbus_suspend(struct macio_dev* dev, pm_message_t state)
 
 static int i2sbus_resume(struct macio_dev* dev)
 {
-       struct i2sbus_control *control = dev->ofdev.dev.driver_data;
+       struct i2sbus_control *control = dev_get_drvdata(&dev->ofdev.dev);
        struct codec_info_item *cii;
        struct i2sbus_dev* i2sdev;
        int err, ret = 0;
index 3d2bb6fc6dcc46ad7f95ef5703059d6144be7849..d3e786a9a0a7ec08a69564700910ef49ac4f4927 100644 (file)
@@ -32,7 +32,9 @@ source "sound/soc/fsl/Kconfig"
 source "sound/soc/omap/Kconfig"
 source "sound/soc/pxa/Kconfig"
 source "sound/soc/s3c24xx/Kconfig"
+source "sound/soc/s6000/Kconfig"
 source "sound/soc/sh/Kconfig"
+source "sound/soc/txx9/Kconfig"
 
 # Supported codecs
 source "sound/soc/codecs/Kconfig"
index 0237879fd4125b47330a7a65a24449643fdaf3a7..6f1e28de23cf325383824299f9eb33fdfe43bec3 100644 (file)
@@ -10,4 +10,6 @@ obj-$(CONFIG_SND_SOC) += fsl/
 obj-$(CONFIG_SND_SOC)  += omap/
 obj-$(CONFIG_SND_SOC)  += pxa/
 obj-$(CONFIG_SND_SOC)  += s3c24xx/
+obj-$(CONFIG_SND_SOC)  += s6000/
 obj-$(CONFIG_SND_SOC)  += sh/
+obj-$(CONFIG_SND_SOC)  += txx9/
index a608d7009dbd61117444570a45f7e1627ae30ff4..e720d5e6f04cfdd135ea42e5b74ed74e75724bed 100644 (file)
@@ -41,3 +41,11 @@ config SND_AT32_SOC_PLAYPAQ_SLAVE
           and FRAME signals on the PlayPaq.  Unless you want to play
           with the AT32 as the SSC master, you probably want to say N here,
           as this will give you better sound quality.
+
+config SND_AT91_SOC_AFEB9260
+       tristate "SoC Audio support for AFEB9260 board"
+       depends on ARCH_AT91 && MACH_AFEB9260 && SND_ATMEL_SOC
+       select SND_ATMEL_SOC_SSC
+       select SND_SOC_TLV320AIC23
+       help
+         Say Y here to support sound on AFEB9260 board.
index f54a7cc68e663bffe6c504a6bfb46fea13a5ed3b..e7ea56bd5f82a94de94d4169c464e809d34dfb5e 100644 (file)
@@ -13,3 +13,4 @@ snd-soc-playpaq-objs := playpaq_wm8510.o
 
 obj-$(CONFIG_SND_AT91_SOC_SAM9G20_WM8731) += snd-soc-sam9g20-wm8731.o
 obj-$(CONFIG_SND_AT32_SOC_PLAYPAQ) += snd-soc-playpaq.o
+obj-$(CONFIG_SND_AT91_SOC_AFEB9260) += snd-soc-afeb9260.o
index 70657534e6b1f68ad3d9a89d4bc7e8698a4fafe5..9eb610c2ba917d42f8e81afc0972879302986928 100644 (file)
@@ -117,7 +117,7 @@ static struct ssc_clock_data playpaq_wm8510_calc_ssc_clock(
         * Find actual rate, compare to requested rate
         */
        actual_rate = (cd.ssc_rate / (cd.cmr_div * 2)) / (2 * (cd.period + 1));
-       pr_debug("playpaq_wm8510: Request rate = %d, actual rate = %d\n",
+       pr_debug("playpaq_wm8510: Request rate = %u, actual rate = %u\n",
                 rate, actual_rate);
 
 
diff --git a/sound/soc/atmel/snd-soc-afeb9260.c b/sound/soc/atmel/snd-soc-afeb9260.c
new file mode 100644 (file)
index 0000000..23349de
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * afeb9260.c  --  SoC audio for AFEB9260
+ *
+ * Copyright (C) 2009 Sergey Lapin <slapin@ossfans.org>
+ *
+ * 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.
+ *
+ * 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, 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/kernel.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+
+#include <linux/atmel-ssc.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <linux/gpio.h>
+
+#include "../codecs/tlv320aic23.h"
+#include "atmel-pcm.h"
+#include "atmel_ssc_dai.h"
+
+#define CODEC_CLOCK    12000000
+
+static int afeb9260_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 *codec_dai = rtd->dai->codec_dai;
+       struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+       int err;
+
+       /* Set codec DAI configuration */
+       err = snd_soc_dai_set_fmt(codec_dai,
+                                 SND_SOC_DAIFMT_I2S|
+                                 SND_SOC_DAIFMT_NB_IF |
+                                 SND_SOC_DAIFMT_CBM_CFM);
+       if (err < 0) {
+               printk(KERN_ERR "can't set codec DAI configuration\n");
+               return err;
+       }
+
+       /* Set cpu DAI configuration */
+       err = snd_soc_dai_set_fmt(cpu_dai,
+                                 SND_SOC_DAIFMT_I2S |
+                                 SND_SOC_DAIFMT_NB_IF |
+                                 SND_SOC_DAIFMT_CBM_CFM);
+       if (err < 0) {
+               printk(KERN_ERR "can't set cpu DAI configuration\n");
+               return err;
+       }
+
+       /* Set the codec system clock for DAC and ADC */
+       err =
+           snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, SND_SOC_CLOCK_IN);
+
+       if (err < 0) {
+               printk(KERN_ERR "can't set codec system clock\n");
+               return err;
+       }
+
+       return err;
+}
+
+static struct snd_soc_ops afeb9260_ops = {
+       .hw_params = afeb9260_hw_params,
+};
+
+static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
+       SND_SOC_DAPM_HP("Headphone Jack", NULL),
+       SND_SOC_DAPM_LINE("Line In", NULL),
+       SND_SOC_DAPM_MIC("Mic Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+       {"Headphone Jack", NULL, "LHPOUT"},
+       {"Headphone Jack", NULL, "RHPOUT"},
+
+       {"LLINEIN", NULL, "Line In"},
+       {"RLINEIN", NULL, "Line In"},
+
+       {"MICIN", NULL, "Mic Jack"},
+};
+
+static int afeb9260_tlv320aic23_init(struct snd_soc_codec *codec)
+{
+
+       /* Add afeb9260 specific widgets */
+       snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets,
+                                 ARRAY_SIZE(tlv320aic23_dapm_widgets));
+
+       /* Set up afeb9260 specific audio path audio_map */
+       snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+       snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+       snd_soc_dapm_enable_pin(codec, "Line In");
+       snd_soc_dapm_enable_pin(codec, "Mic Jack");
+
+       snd_soc_dapm_sync(codec);
+
+       return 0;
+}
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link afeb9260_dai = {
+       .name = "TLV320AIC23",
+       .stream_name = "AIC23",
+       .cpu_dai = &atmel_ssc_dai[0],
+       .codec_dai = &tlv320aic23_dai,
+       .init = afeb9260_tlv320aic23_init,
+       .ops = &afeb9260_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_machine_afeb9260 = {
+       .name = "AFEB9260",
+       .platform = &atmel_soc_platform,
+       .dai_link = &afeb9260_dai,
+       .num_links = 1,
+};
+
+/* Audio subsystem */
+static struct snd_soc_device afeb9260_snd_devdata = {
+       .card = &snd_soc_machine_afeb9260,
+       .codec_dev = &soc_codec_dev_tlv320aic23,
+};
+
+static struct platform_device *afeb9260_snd_device;
+
+static int __init afeb9260_soc_init(void)
+{
+       int err;
+       struct device *dev;
+       struct atmel_ssc_info *ssc_p = afeb9260_dai.cpu_dai->private_data;
+       struct ssc_device *ssc = NULL;
+
+       if (!(machine_is_afeb9260()))
+               return -ENODEV;
+
+       ssc = ssc_request(0);
+       if (IS_ERR(ssc)) {
+               printk(KERN_ERR "ASoC: Failed to request SSC 0\n");
+               err = PTR_ERR(ssc);
+               ssc = NULL;
+               goto err_ssc;
+       }
+       ssc_p->ssc = ssc;
+
+       afeb9260_snd_device = platform_device_alloc("soc-audio", -1);
+       if (!afeb9260_snd_device) {
+               printk(KERN_ERR "ASoC: Platform device allocation failed\n");
+               return -ENOMEM;
+       }
+
+       platform_set_drvdata(afeb9260_snd_device, &afeb9260_snd_devdata);
+       afeb9260_snd_devdata.dev = &afeb9260_snd_device->dev;
+       err = platform_device_add(afeb9260_snd_device);
+       if (err)
+               goto err1;
+
+       dev = &afeb9260_snd_device->dev;
+
+       return 0;
+err1:
+       platform_device_del(afeb9260_snd_device);
+       platform_device_put(afeb9260_snd_device);
+err_ssc:
+       return err;
+
+}
+
+static void __exit afeb9260_soc_exit(void)
+{
+       platform_device_unregister(afeb9260_snd_device);
+}
+
+module_init(afeb9260_soc_init);
+module_exit(afeb9260_soc_exit);
+
+MODULE_AUTHOR("Sergey Lapin <slapin@ossfans.org>");
+MODULE_DESCRIPTION("ALSA SoC for AFEB9260");
+MODULE_LICENSE("GPL");
+
index 8a935f2d17674683186fc26d48d890c835ad3f3c..b1ed423fabd51f56bb278e165e2d8c0369406caa 100644 (file)
 #include "bf5xx-sport.h"
 #include "bf5xx-ac97.h"
 
+/* Anomaly notes:
+ *  05000250 - AD1980 is running in TDM mode and RFS/TFS are generated by SPORT
+ *             contrtoller. But, RFSDIV and TFSDIV are always set to 16*16-1,
+ *             while the max AC97 data size is 13*16. The DIV is always larger
+ *             than data size. AD73311 and ad2602 are not running in TDM mode.
+ *             AD1836 and AD73322 depend on external RFS/TFS only. So, this
+ *             anomaly does not affect blackfin sound drivers.
+*/
+
 static int *cmd_count;
 static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM;
 
index b7953c8cf8382a05df6ef9b280b0403a7b214f77..469ce7fab20ca6aad3d6474d8c63deeacb3f5101 100644 (file)
@@ -190,7 +190,7 @@ static inline int sport_hook_rx_dummy(struct sport_device *sport)
        desc = get_dma_next_desc_ptr(sport->dma_rx_chan);
        /* Copy the descriptor which will be damaged to backup */
        temp_desc = *desc;
-       desc->x_count = 0xa;
+       desc->x_count = sport->dummy_count / 2;
        desc->y_count = 0;
        desc->next_desc_addr = sport->dummy_rx_desc;
        local_irq_restore(flags);
@@ -309,7 +309,7 @@ static inline int sport_hook_tx_dummy(struct sport_device *sport)
        desc = get_dma_next_desc_ptr(sport->dma_tx_chan);
        /* Store the descriptor which will be damaged */
        temp_desc = *desc;
-       desc->x_count = 0xa;
+       desc->x_count = sport->dummy_count / 2;
        desc->y_count = 0;
        desc->next_desc_addr = sport->dummy_tx_desc;
        local_irq_restore(flags);
index b6c7f7a01cb03756a321651b02f5135eccdd2a4a..bbc97fd7664893b58871e9d026cc20e9d9863774 100644 (file)
@@ -18,7 +18,9 @@ config SND_SOC_ALL_CODECS
        select SND_SOC_AK4535 if I2C
        select SND_SOC_CS4270 if I2C
        select SND_SOC_PCM3008
+       select SND_SOC_SPDIF
        select SND_SOC_SSM2602 if I2C
+       select SND_SOC_STAC9766 if SND_SOC_AC97_BUS
        select SND_SOC_TLV320AIC23 if I2C
        select SND_SOC_TLV320AIC26 if SPI_MASTER
        select SND_SOC_TLV320AIC3X if I2C
@@ -35,8 +37,12 @@ config SND_SOC_ALL_CODECS
        select SND_SOC_WM8753 if SND_SOC_I2C_AND_SPI
        select SND_SOC_WM8900 if I2C
        select SND_SOC_WM8903 if I2C
+       select SND_SOC_WM8940 if I2C
+       select SND_SOC_WM8960 if I2C
        select SND_SOC_WM8971 if I2C
+       select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI
        select SND_SOC_WM8990 if I2C
+       select SND_SOC_WM9081 if I2C
        select SND_SOC_WM9705 if SND_SOC_AC97_BUS
        select SND_SOC_WM9712 if SND_SOC_AC97_BUS
        select SND_SOC_WM9713 if SND_SOC_AC97_BUS
@@ -86,9 +92,15 @@ config SND_SOC_L3
 config SND_SOC_PCM3008
        tristate
 
+config SND_SOC_SPDIF
+       tristate
+
 config SND_SOC_SSM2602
        tristate
 
+config SND_SOC_STAC9766
+       tristate
+
 config SND_SOC_TLV320AIC23
        tristate
 
@@ -138,12 +150,24 @@ config SND_SOC_WM8900
 config SND_SOC_WM8903
        tristate
 
+config SND_SOC_WM8940
+        tristate
+
+config SND_SOC_WM8960
+       tristate
+
 config SND_SOC_WM8971
        tristate
 
+config SND_SOC_WM8988
+       tristate
+
 config SND_SOC_WM8990
        tristate
 
+config SND_SOC_WM9081
+       tristate
+
 config SND_SOC_WM9705
        tristate
 
index f2653803ede8faf37be341e40c3ae0f5d5169956..8b7530546f4dece45018f54a4894eaa3875d954f 100644 (file)
@@ -6,7 +6,9 @@ snd-soc-ak4535-objs := ak4535.o
 snd-soc-cs4270-objs := cs4270.o
 snd-soc-l3-objs := l3.o
 snd-soc-pcm3008-objs := pcm3008.o
+snd-soc-spdif-objs := spdif_transciever.o
 snd-soc-ssm2602-objs := ssm2602.o
+snd-soc-stac9766-objs := stac9766.o
 snd-soc-tlv320aic23-objs := tlv320aic23.o
 snd-soc-tlv320aic26-objs := tlv320aic26.o
 snd-soc-tlv320aic3x-objs := tlv320aic3x.o
@@ -23,8 +25,12 @@ snd-soc-wm8750-objs := wm8750.o
 snd-soc-wm8753-objs := wm8753.o
 snd-soc-wm8900-objs := wm8900.o
 snd-soc-wm8903-objs := wm8903.o
+snd-soc-wm8940-objs := wm8940.o
+snd-soc-wm8960-objs := wm8960.o
 snd-soc-wm8971-objs := wm8971.o
+snd-soc-wm8988-objs := wm8988.o
 snd-soc-wm8990-objs := wm8990.o
+snd-soc-wm9081-objs := wm9081.o
 snd-soc-wm9705-objs := wm9705.o
 snd-soc-wm9712-objs := wm9712.o
 snd-soc-wm9713-objs := wm9713.o
@@ -37,7 +43,9 @@ obj-$(CONFIG_SND_SOC_AK4535)  += snd-soc-ak4535.o
 obj-$(CONFIG_SND_SOC_CS4270)   += snd-soc-cs4270.o
 obj-$(CONFIG_SND_SOC_L3)       += snd-soc-l3.o
 obj-$(CONFIG_SND_SOC_PCM3008)  += snd-soc-pcm3008.o
+obj-$(CONFIG_SND_SOC_SPDIF)    += snd-soc-spdif.o
 obj-$(CONFIG_SND_SOC_SSM2602)  += snd-soc-ssm2602.o
+obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o
 obj-$(CONFIG_SND_SOC_TLV320AIC23)      += snd-soc-tlv320aic23.o
 obj-$(CONFIG_SND_SOC_TLV320AIC26)      += snd-soc-tlv320aic26.o
 obj-$(CONFIG_SND_SOC_TLV320AIC3X)      += snd-soc-tlv320aic3x.o
@@ -55,7 +63,11 @@ obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o
 obj-$(CONFIG_SND_SOC_WM8900)   += snd-soc-wm8900.o
 obj-$(CONFIG_SND_SOC_WM8903)   += snd-soc-wm8903.o
 obj-$(CONFIG_SND_SOC_WM8971)   += snd-soc-wm8971.o
+obj-$(CONFIG_SND_SOC_WM8940)   += snd-soc-wm8940.o
+obj-$(CONFIG_SND_SOC_WM8960)   += snd-soc-wm8960.o
+obj-$(CONFIG_SND_SOC_WM8988)   += snd-soc-wm8988.o
 obj-$(CONFIG_SND_SOC_WM8990)   += snd-soc-wm8990.o
+obj-$(CONFIG_SND_SOC_WM9081)   += snd-soc-wm9081.o
 obj-$(CONFIG_SND_SOC_WM9705)   += snd-soc-wm9705.o
 obj-$(CONFIG_SND_SOC_WM9712)   += snd-soc-wm9712.o
 obj-$(CONFIG_SND_SOC_WM9713)   += snd-soc-wm9713.o
index b0d4af145b873900509ce18239e2c85899aa9069..932299bb5d1e006a7f0108bd7d7ab0000b3969bc 100644 (file)
@@ -53,13 +53,13 @@ struct snd_soc_dai ac97_dai = {
                .channels_min = 1,
                .channels_max = 2,
                .rates = STD_AC97_RATES,
-               .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+               .formats = SND_SOC_STD_AC97_FMTS,},
        .capture = {
                .stream_name = "AC97 Capture",
                .channels_min = 1,
                .channels_max = 2,
                .rates = STD_AC97_RATES,
-               .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+               .formats = SND_SOC_STD_AC97_FMTS,},
        .ops = &ac97_dai_ops,
 };
 EXPORT_SYMBOL_GPL(ac97_dai);
index ddb3b08ac23c441dd93112295a7182881c856041..d7440a982d22794928940c3590cde1765fe56d9a 100644 (file)
@@ -137,13 +137,13 @@ struct snd_soc_dai ad1980_dai = {
                .channels_min = 2,
                .channels_max = 6,
                .rates = SNDRV_PCM_RATE_48000,
-               .formats = SNDRV_PCM_FMTBIT_S16_LE, },
+               .formats = SND_SOC_STD_AC97_FMTS, },
        .capture = {
                .stream_name = "Capture",
                .channels_min = 2,
                .channels_max = 2,
                .rates = SNDRV_PCM_RATE_48000,
-               .formats = SNDRV_PCM_FMTBIT_S16_LE, },
+               .formats = SND_SOC_STD_AC97_FMTS, },
 };
 EXPORT_SYMBOL_GPL(ad1980_dai);
 
index 7fa09a387622cbff69b28b88e83819faa876d4e9..a32b8226c8a43bc19a2f3852ef24b3e802238c05 100644 (file)
@@ -18,7 +18,7 @@
  * - The machine driver's 'startup' function must call
  *   cs4270_set_dai_sysclk() with the value of MCLK.
  * - Only I2S and left-justified modes are supported
- * - Power management is not supported
+ * - Power management is supported
  */
 
 #include <linux/module.h>
@@ -27,6 +27,7 @@
 #include <sound/soc.h>
 #include <sound/initval.h>
 #include <linux/i2c.h>
+#include <linux/delay.h>
 
 #include "cs4270.h"
 
@@ -56,6 +57,7 @@
 #define CS4270_FIRSTREG        0x01
 #define CS4270_LASTREG 0x08
 #define CS4270_NUMREGS (CS4270_LASTREG - CS4270_FIRSTREG + 1)
+#define CS4270_I2C_INCR        0x80
 
 /* Bit masks for the CS4270 registers */
 #define CS4270_CHIPID_ID       0xF0
@@ -64,6 +66,8 @@
 #define CS4270_PWRCTL_PDN_ADC  0x20
 #define CS4270_PWRCTL_PDN_DAC  0x02
 #define CS4270_PWRCTL_PDN      0x01
+#define CS4270_PWRCTL_PDN_ALL  \
+       (CS4270_PWRCTL_PDN_ADC | CS4270_PWRCTL_PDN_DAC | CS4270_PWRCTL_PDN)
 #define CS4270_MODE_SPEED_MASK 0x30
 #define CS4270_MODE_1X         0x00
 #define CS4270_MODE_2X         0x10
@@ -109,6 +113,7 @@ struct cs4270_private {
        unsigned int mclk; /* Input frequency of the MCLK pin */
        unsigned int mode; /* The mode (I2S or left-justified) */
        unsigned int slave_mode;
+       unsigned int manual_mute;
 };
 
 /**
@@ -295,7 +300,7 @@ static int cs4270_fill_cache(struct snd_soc_codec *codec)
        s32 length;
 
        length = i2c_smbus_read_i2c_block_data(i2c_client,
-               CS4270_FIRSTREG | 0x80, CS4270_NUMREGS, cache);
+               CS4270_FIRSTREG | CS4270_I2C_INCR, CS4270_NUMREGS, cache);
 
        if (length != CS4270_NUMREGS) {
                dev_err(codec->dev, "i2c read failure, addr=0x%x\n",
@@ -453,7 +458,7 @@ static int cs4270_hw_params(struct snd_pcm_substream *substream,
 }
 
 /**
- * cs4270_mute - enable/disable the CS4270 external mute
+ * cs4270_dai_mute - enable/disable the CS4270 external mute
  * @dai: the SOC DAI
  * @mute: 0 = disable mute, 1 = enable mute
  *
@@ -462,21 +467,52 @@ static int cs4270_hw_params(struct snd_pcm_substream *substream,
  * board does not have the MUTEA or MUTEB pins connected to such circuitry,
  * then this function will do nothing.
  */
-static int cs4270_mute(struct snd_soc_dai *dai, int mute)
+static int cs4270_dai_mute(struct snd_soc_dai *dai, int mute)
 {
        struct snd_soc_codec *codec = dai->codec;
+       struct cs4270_private *cs4270 = codec->private_data;
        int reg6;
 
        reg6 = snd_soc_read(codec, CS4270_MUTE);
 
        if (mute)
                reg6 |= CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B;
-       else
+       else {
                reg6 &= ~(CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B);
+               reg6 |= cs4270->manual_mute;
+       }
 
        return snd_soc_write(codec, CS4270_MUTE, reg6);
 }
 
+/**
+ * cs4270_soc_put_mute - put callback for the 'Master Playback switch'
+ *                      alsa control.
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * This function basically passes the arguments on to the generic
+ * snd_soc_put_volsw() function and saves the mute information in
+ * our private data structure. This is because we want to prevent
+ * cs4270_dai_mute() neglecting the user's decision to manually
+ * mute the codec's output.
+ *
+ * Returns 0 for success.
+ */
+static int cs4270_soc_put_mute(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct cs4270_private *cs4270 = codec->private_data;
+       int left = !ucontrol->value.integer.value[0];
+       int right = !ucontrol->value.integer.value[1];
+
+       cs4270->manual_mute = (left ? CS4270_MUTE_DAC_A : 0) |
+                             (right ? CS4270_MUTE_DAC_B : 0);
+
+       return snd_soc_put_volsw(kcontrol, ucontrol);
+}
+
 /* A list of non-DAPM controls that the CS4270 supports */
 static const struct snd_kcontrol_new cs4270_snd_controls[] = {
        SOC_DOUBLE_R("Master Playback Volume",
@@ -486,7 +522,9 @@ static const struct snd_kcontrol_new cs4270_snd_controls[] = {
        SOC_SINGLE("Zero Cross Switch", CS4270_TRANS, 5, 1, 0),
        SOC_SINGLE("Popguard Switch", CS4270_MODE, 0, 1, 1),
        SOC_SINGLE("Auto-Mute Switch", CS4270_MUTE, 5, 1, 0),
-       SOC_DOUBLE("Master Capture Switch", CS4270_MUTE, 3, 4, 1, 0)
+       SOC_DOUBLE("Master Capture Switch", CS4270_MUTE, 3, 4, 1, 1),
+       SOC_DOUBLE_EXT("Master Playback Switch", CS4270_MUTE, 0, 1, 1, 1,
+               snd_soc_get_volsw, cs4270_soc_put_mute),
 };
 
 /*
@@ -506,7 +544,7 @@ static struct snd_soc_dai_ops cs4270_dai_ops = {
        .hw_params      = cs4270_hw_params,
        .set_sysclk     = cs4270_set_dai_sysclk,
        .set_fmt        = cs4270_set_dai_fmt,
-       .digital_mute   = cs4270_mute,
+       .digital_mute   = cs4270_dai_mute,
 };
 
 struct snd_soc_dai cs4270_dai = {
@@ -753,6 +791,57 @@ static struct i2c_device_id cs4270_id[] = {
 };
 MODULE_DEVICE_TABLE(i2c, cs4270_id);
 
+#ifdef CONFIG_PM
+
+/* This suspend/resume implementation can handle both - a simple standby
+ * where the codec remains powered, and a full suspend, where the voltage
+ * domain the codec is connected to is teared down and/or any other hardware
+ * reset condition is asserted.
+ *
+ * The codec's own power saving features are enabled in the suspend callback,
+ * and all registers are written back to the hardware when resuming.
+ */
+
+static int cs4270_i2c_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+       struct cs4270_private *cs4270 = i2c_get_clientdata(client);
+       struct snd_soc_codec *codec = &cs4270->codec;
+       int reg = snd_soc_read(codec, CS4270_PWRCTL) | CS4270_PWRCTL_PDN_ALL;
+
+       return snd_soc_write(codec, CS4270_PWRCTL, reg);
+}
+
+static int cs4270_i2c_resume(struct i2c_client *client)
+{
+       struct cs4270_private *cs4270 = i2c_get_clientdata(client);
+       struct snd_soc_codec *codec = &cs4270->codec;
+       int reg;
+
+       /* In case the device was put to hard reset during sleep, we need to
+        * wait 500ns here before any I2C communication. */
+       ndelay(500);
+
+       /* first restore the entire register cache ... */
+       for (reg = CS4270_FIRSTREG; reg <= CS4270_LASTREG; reg++) {
+               u8 val = snd_soc_read(codec, reg);
+
+               if (i2c_smbus_write_byte_data(client, reg, val)) {
+                       dev_err(codec->dev, "i2c write failed\n");
+                       return -EIO;
+               }
+       }
+
+       /* ... then disable the power-down bits */
+       reg = snd_soc_read(codec, CS4270_PWRCTL);
+       reg &= ~CS4270_PWRCTL_PDN_ALL;
+
+       return snd_soc_write(codec, CS4270_PWRCTL, reg);
+}
+#else
+#define cs4270_i2c_suspend     NULL
+#define cs4270_i2c_resume      NULL
+#endif /* CONFIG_PM */
+
 /*
  * cs4270_i2c_driver - I2C device identification
  *
@@ -767,6 +856,8 @@ static struct i2c_driver cs4270_i2c_driver = {
        .id_table = cs4270_id,
        .probe = cs4270_i2c_probe,
        .remove = cs4270_i2c_remove,
+       .suspend = cs4270_i2c_suspend,
+       .resume = cs4270_i2c_resume,
 };
 
 /*
diff --git a/sound/soc/codecs/spdif_transciever.c b/sound/soc/codecs/spdif_transciever.c
new file mode 100644 (file)
index 0000000..218b33a
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * ALSA SoC SPDIF DIT driver
+ *
+ *  This driver is used by controllers which can operate in DIT (SPDI/F) where
+ *  no codec is needed.  This file provides stub codec that can be used
+ *  in these configurations. TI DaVinci Audio controller uses this driver.
+ *
+ * Author:      Steve Chen,  <schen@mvista.com>
+ * Copyright:   (C) 2009 MontaVista Software, Inc., <source@mvista.com>
+ * Copyright:   (C) 2009  Texas Instruments, India
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+
+#include "spdif_transciever.h"
+
+#define STUB_RATES     SNDRV_PCM_RATE_8000_96000
+#define STUB_FORMATS   SNDRV_PCM_FMTBIT_S16_LE
+
+struct snd_soc_dai dit_stub_dai = {
+       .name           = "DIT",
+       .playback       = {
+               .stream_name    = "Playback",
+               .channels_min   = 1,
+               .channels_max   = 384,
+               .rates          = STUB_RATES,
+               .formats        = STUB_FORMATS,
+       },
+};
+
+static int spdif_dit_probe(struct platform_device *pdev)
+{
+       dit_stub_dai.dev = &pdev->dev;
+       return snd_soc_register_dai(&dit_stub_dai);
+}
+
+static int spdif_dit_remove(struct platform_device *pdev)
+{
+       snd_soc_unregister_dai(&dit_stub_dai);
+       return 0;
+}
+
+static struct platform_driver spdif_dit_driver = {
+       .probe          = spdif_dit_probe,
+       .remove         = spdif_dit_remove,
+       .driver         = {
+               .name   = "spdif-dit",
+               .owner  = THIS_MODULE,
+       },
+};
+
+static int __init dit_modinit(void)
+{
+       return platform_driver_register(&spdif_dit_driver);
+}
+
+static void __exit dit_exit(void)
+{
+       platform_driver_unregister(&spdif_dit_driver);
+}
+
+module_init(dit_modinit);
+module_exit(dit_exit);
+
diff --git a/sound/soc/codecs/spdif_transciever.h b/sound/soc/codecs/spdif_transciever.h
new file mode 100644 (file)
index 0000000..296f2eb
--- /dev/null
@@ -0,0 +1,17 @@
+/*
+ * ALSA SoC DIT/DIR driver header
+ *
+ * Author:      Steve Chen,  <schen@mvista.com>
+ * Copyright:   (C) 2008 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef CODEC_STUBS_H
+#define CODEC_STUBS_H
+
+extern struct snd_soc_dai dit_stub_dai;
+
+#endif /* CODEC_STUBS_H */
index 87f606c7682292af24e5f213cf7c919465116fd1..1fc4c8e0899c18ec1f7f12750cf80023140cd5b7 100644 (file)
@@ -336,15 +336,17 @@ static int ssm2602_startup(struct snd_pcm_substream *substream,
                        master_runtime->sample_bits,
                        master_runtime->rate);
 
-               snd_pcm_hw_constraint_minmax(substream->runtime,
-                                            SNDRV_PCM_HW_PARAM_RATE,
-                                            master_runtime->rate,
-                                            master_runtime->rate);
-
-               snd_pcm_hw_constraint_minmax(substream->runtime,
-                                            SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
-                                            master_runtime->sample_bits,
-                                            master_runtime->sample_bits);
+               if (master_runtime->rate != 0)
+                       snd_pcm_hw_constraint_minmax(substream->runtime,
+                                                    SNDRV_PCM_HW_PARAM_RATE,
+                                                    master_runtime->rate,
+                                                    master_runtime->rate);
+
+               if (master_runtime->sample_bits != 0)
+                       snd_pcm_hw_constraint_minmax(substream->runtime,
+                                                    SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+                                                    master_runtime->sample_bits,
+                                                    master_runtime->sample_bits);
 
                ssm2602->slave_substream = substream;
        } else
@@ -372,6 +374,11 @@ static void ssm2602_shutdown(struct snd_pcm_substream *substream,
        struct snd_soc_device *socdev = rtd->socdev;
        struct snd_soc_codec *codec = socdev->card->codec;
        struct ssm2602_priv *ssm2602 = codec->private_data;
+
+       if (ssm2602->master_substream == substream)
+               ssm2602->master_substream = ssm2602->slave_substream;
+
+       ssm2602->slave_substream = NULL;
        /* deactivate */
        if (!codec->active)
                ssm2602_write(codec, SSM2602_ACTIVE, 0);
@@ -497,11 +504,9 @@ static int ssm2602_set_bias_level(struct snd_soc_codec *codec,
        return 0;
 }
 
-#define SSM2602_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
-               SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
-               SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
-               SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\
-               SNDRV_PCM_RATE_96000)
+#define SSM2602_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_32000 |\
+               SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
+               SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
 
 #define SSM2602_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
                SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
diff --git a/sound/soc/codecs/stac9766.c b/sound/soc/codecs/stac9766.c
new file mode 100644 (file)
index 0000000..8ad4b7b
--- /dev/null
@@ -0,0 +1,463 @@
+/*
+ * stac9766.c  --  ALSA SoC STAC9766 codec support
+ *
+ * Copyright 2009 Jon Smirl, Digispeaker
+ * Author: Jon Smirl <jonsmirl@gmail.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ *  Features:-
+ *
+ *   o Support for AC97 Codec, S/PDIF
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/soc-of-simple.h>
+
+#include "stac9766.h"
+
+#define STAC9766_VERSION "0.10"
+
+/*
+ * STAC9766 register cache
+ */
+static const u16 stac9766_reg[] = {
+       0x6A90, 0x8000, 0x8000, 0x8000, /* 6 */
+       0x0000, 0x0000, 0x8008, 0x8008, /* e */
+       0x8808, 0x8808, 0x8808, 0x8808, /* 16 */
+       0x8808, 0x0000, 0x8000, 0x0000, /* 1e */
+       0x0000, 0x0000, 0x0000, 0x000f, /* 26 */
+       0x0a05, 0x0400, 0xbb80, 0x0000, /* 2e */
+       0x0000, 0xbb80, 0x0000, 0x0000, /* 36 */
+       0x0000, 0x2000, 0x0000, 0x0100, /* 3e */
+       0x0000, 0x0000, 0x0080, 0x0000, /* 46 */
+       0x0000, 0x0000, 0x0003, 0xffff, /* 4e */
+       0x0000, 0x0000, 0x0000, 0x0000, /* 56 */
+       0x4000, 0x0000, 0x0000, 0x0000, /* 5e */
+       0x1201, 0xFFFF, 0xFFFF, 0x0000, /* 66 */
+       0x0000, 0x0000, 0x0000, 0x0000, /* 6e */
+       0x0000, 0x0000, 0x0000, 0x0006, /* 76 */
+       0x0000, 0x0000, 0x0000, 0x0000, /* 7e */
+};
+
+static const char *stac9766_record_mux[] = {"Mic", "CD", "Video", "AUX",
+                       "Line", "Stereo Mix", "Mono Mix", "Phone"};
+static const char *stac9766_mono_mux[] = {"Mix", "Mic"};
+static const char *stac9766_mic_mux[] = {"Mic1", "Mic2"};
+static const char *stac9766_SPDIF_mux[] = {"PCM", "ADC Record"};
+static const char *stac9766_popbypass_mux[] = {"Normal", "Bypass Mixer"};
+static const char *stac9766_record_all_mux[] = {"All analog",
+       "Analog plus DAC"};
+static const char *stac9766_boost1[] = {"0dB", "10dB"};
+static const char *stac9766_boost2[] = {"0dB", "20dB"};
+static const char *stac9766_stereo_mic[] = {"Off", "On"};
+
+static const struct soc_enum stac9766_record_enum =
+       SOC_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 8, stac9766_record_mux);
+static const struct soc_enum stac9766_mono_enum =
+       SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 9, 2, stac9766_mono_mux);
+static const struct soc_enum stac9766_mic_enum =
+       SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 8, 2, stac9766_mic_mux);
+static const struct soc_enum stac9766_SPDIF_enum =
+       SOC_ENUM_SINGLE(AC97_STAC_DA_CONTROL, 1, 2, stac9766_SPDIF_mux);
+static const struct soc_enum stac9766_popbypass_enum =
+       SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, stac9766_popbypass_mux);
+static const struct soc_enum stac9766_record_all_enum =
+       SOC_ENUM_SINGLE(AC97_STAC_ANALOG_SPECIAL, 12, 2,
+                       stac9766_record_all_mux);
+static const struct soc_enum stac9766_boost1_enum =
+       SOC_ENUM_SINGLE(AC97_MIC, 6, 2, stac9766_boost1); /* 0/10dB */
+static const struct soc_enum stac9766_boost2_enum =
+       SOC_ENUM_SINGLE(AC97_STAC_ANALOG_SPECIAL, 2, 2, stac9766_boost2); /* 0/20dB */
+static const struct soc_enum stac9766_stereo_mic_enum =
+       SOC_ENUM_SINGLE(AC97_STAC_STEREO_MIC, 2, 1, stac9766_stereo_mic);
+
+static const DECLARE_TLV_DB_LINEAR(master_tlv, -4600, 0);
+static const DECLARE_TLV_DB_LINEAR(record_tlv, 0, 2250);
+static const DECLARE_TLV_DB_LINEAR(beep_tlv, -4500, 0);
+static const DECLARE_TLV_DB_LINEAR(mix_tlv, -3450, 1200);
+
+static const struct snd_kcontrol_new stac9766_snd_ac97_controls[] = {
+       SOC_DOUBLE_TLV("Speaker Volume", AC97_MASTER, 8, 0, 31, 1, master_tlv),
+       SOC_SINGLE("Speaker Switch", AC97_MASTER, 15, 1, 1),
+       SOC_DOUBLE_TLV("Headphone Volume", AC97_HEADPHONE, 8, 0, 31, 1,
+                      master_tlv),
+       SOC_SINGLE("Headphone Switch", AC97_HEADPHONE, 15, 1, 1),
+       SOC_SINGLE_TLV("Mono Out Volume", AC97_MASTER_MONO, 0, 31, 1,
+                      master_tlv),
+       SOC_SINGLE("Mono Out Switch", AC97_MASTER_MONO, 15, 1, 1),
+
+       SOC_DOUBLE_TLV("Record Volume", AC97_REC_GAIN, 8, 0, 15, 0, record_tlv),
+       SOC_SINGLE("Record Switch", AC97_REC_GAIN, 15, 1, 1),
+
+
+       SOC_SINGLE_TLV("Beep Volume", AC97_PC_BEEP, 1, 15, 1, beep_tlv),
+       SOC_SINGLE("Beep Switch", AC97_PC_BEEP, 15, 1, 1),
+       SOC_SINGLE("Beep Frequency", AC97_PC_BEEP, 5, 127, 1),
+       SOC_SINGLE_TLV("Phone Volume", AC97_PHONE, 0, 31, 1, mix_tlv),
+       SOC_SINGLE("Phone Switch", AC97_PHONE, 15, 1, 1),
+
+       SOC_ENUM("Mic Boost1", stac9766_boost1_enum),
+       SOC_ENUM("Mic Boost2", stac9766_boost2_enum),
+       SOC_SINGLE_TLV("Mic Volume", AC97_MIC, 0, 31, 1, mix_tlv),
+       SOC_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1),
+       SOC_ENUM("Stereo Mic", stac9766_stereo_mic_enum),
+
+       SOC_DOUBLE_TLV("Line Volume", AC97_LINE, 8, 0, 31, 1, mix_tlv),
+       SOC_SINGLE("Line Switch", AC97_LINE, 15, 1, 1),
+       SOC_DOUBLE_TLV("CD Volume", AC97_CD, 8, 0, 31, 1, mix_tlv),
+       SOC_SINGLE("CD Switch", AC97_CD, 15, 1, 1),
+       SOC_DOUBLE_TLV("AUX Volume", AC97_AUX, 8, 0, 31, 1, mix_tlv),
+       SOC_SINGLE("AUX Switch", AC97_AUX, 15, 1, 1),
+       SOC_DOUBLE_TLV("Video Volume", AC97_VIDEO, 8, 0, 31, 1, mix_tlv),
+       SOC_SINGLE("Video Switch", AC97_VIDEO, 15, 1, 1),
+
+       SOC_DOUBLE_TLV("DAC Volume", AC97_PCM, 8, 0, 31, 1, mix_tlv),
+       SOC_SINGLE("DAC Switch", AC97_PCM, 15, 1, 1),
+       SOC_SINGLE("Loopback Test Switch", AC97_GENERAL_PURPOSE, 7, 1, 0),
+       SOC_SINGLE("3D Volume", AC97_3D_CONTROL, 3, 2, 1),
+       SOC_SINGLE("3D Switch", AC97_GENERAL_PURPOSE, 13, 1, 0),
+
+       SOC_ENUM("SPDIF Mux", stac9766_SPDIF_enum),
+       SOC_ENUM("Mic1/2 Mux", stac9766_mic_enum),
+       SOC_ENUM("Record All Mux", stac9766_record_all_enum),
+       SOC_ENUM("Record Mux", stac9766_record_enum),
+       SOC_ENUM("Mono Mux", stac9766_mono_enum),
+       SOC_ENUM("Pop Bypass Mux", stac9766_popbypass_enum),
+};
+
+static int stac9766_ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+                              unsigned int val)
+{
+       u16 *cache = codec->reg_cache;
+
+       if (reg > AC97_STAC_PAGE0) {
+               stac9766_ac97_write(codec, AC97_INT_PAGING, 0);
+               soc_ac97_ops.write(codec->ac97, reg, val);
+               stac9766_ac97_write(codec, AC97_INT_PAGING, 1);
+               return 0;
+       }
+       if (reg / 2 > ARRAY_SIZE(stac9766_reg))
+               return -EIO;
+
+       soc_ac97_ops.write(codec->ac97, reg, val);
+       cache[reg / 2] = val;
+       return 0;
+}
+
+static unsigned int stac9766_ac97_read(struct snd_soc_codec *codec,
+                                      unsigned int reg)
+{
+       u16 val = 0, *cache = codec->reg_cache;
+
+       if (reg > AC97_STAC_PAGE0) {
+               stac9766_ac97_write(codec, AC97_INT_PAGING, 0);
+               val = soc_ac97_ops.read(codec->ac97, reg - AC97_STAC_PAGE0);
+               stac9766_ac97_write(codec, AC97_INT_PAGING, 1);
+               return val;
+       }
+       if (reg / 2 > ARRAY_SIZE(stac9766_reg))
+               return -EIO;
+
+       if (reg == AC97_RESET || reg == AC97_GPIO_STATUS ||
+               reg == AC97_INT_PAGING || reg == AC97_VENDOR_ID1 ||
+               reg == AC97_VENDOR_ID2) {
+
+               val = soc_ac97_ops.read(codec->ac97, reg);
+               return val;
+       }
+       return cache[reg / 2];
+}
+
+static int ac97_analog_prepare(struct snd_pcm_substream *substream,
+                              struct snd_soc_dai *dai)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       unsigned short reg, vra;
+
+       vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS);
+
+       vra |= 0x1; /* enable variable rate audio */
+
+       stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra);
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+               reg = AC97_PCM_FRONT_DAC_RATE;
+       else
+               reg = AC97_PCM_LR_ADC_RATE;
+
+       return stac9766_ac97_write(codec, reg, runtime->rate);
+}
+
+static int ac97_digital_prepare(struct snd_pcm_substream *substream,
+                               struct snd_soc_dai *dai)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       unsigned short reg, vra;
+
+       stac9766_ac97_write(codec, AC97_SPDIF, 0x2002);
+
+       vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS);
+       vra |= 0x5; /* Enable VRA and SPDIF out */
+
+       stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra);
+
+       reg = AC97_PCM_FRONT_DAC_RATE;
+
+       return stac9766_ac97_write(codec, reg, runtime->rate);
+}
+
+static int ac97_digital_trigger(struct snd_pcm_substream *substream,
+                               int cmd, struct snd_soc_dai *dai)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       unsigned short vra;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_STOP:
+               vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS);
+               vra &= !0x04;
+               stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra);
+               break;
+       }
+       return 0;
+}
+
+static int stac9766_set_bias_level(struct snd_soc_codec *codec,
+                                  enum snd_soc_bias_level level)
+{
+       switch (level) {
+       case SND_SOC_BIAS_ON: /* full On */
+       case SND_SOC_BIAS_PREPARE: /* partial On */
+       case SND_SOC_BIAS_STANDBY: /* Off, with power */
+               stac9766_ac97_write(codec, AC97_POWERDOWN, 0x0000);
+               break;
+       case SND_SOC_BIAS_OFF: /* Off, without power */
+               /* disable everything including AC link */
+               stac9766_ac97_write(codec, AC97_POWERDOWN, 0xffff);
+               break;
+       }
+       codec->bias_level = level;
+       return 0;
+}
+
+static int stac9766_reset(struct snd_soc_codec *codec, int try_warm)
+{
+       if (try_warm && soc_ac97_ops.warm_reset) {
+               soc_ac97_ops.warm_reset(codec->ac97);
+               if (stac9766_ac97_read(codec, 0) == stac9766_reg[0])
+                       return 1;
+       }
+
+       soc_ac97_ops.reset(codec->ac97);
+       if (soc_ac97_ops.warm_reset)
+               soc_ac97_ops.warm_reset(codec->ac97);
+       if (stac9766_ac97_read(codec, 0) != stac9766_reg[0])
+               return -EIO;
+       return 0;
+}
+
+static int stac9766_codec_suspend(struct platform_device *pdev,
+                                 pm_message_t state)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec = socdev->card->codec;
+
+       stac9766_set_bias_level(codec, SND_SOC_BIAS_OFF);
+       return 0;
+}
+
+static int stac9766_codec_resume(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec = socdev->card->codec;
+       u16 id, reset;
+
+       reset = 0;
+       /* give the codec an AC97 warm reset to start the link */
+reset:
+       if (reset > 5) {
+               printk(KERN_ERR "stac9766 failed to resume");
+               return -EIO;
+       }
+       codec->ac97->bus->ops->warm_reset(codec->ac97);
+       id = soc_ac97_ops.read(codec->ac97, AC97_VENDOR_ID2);
+       if (id != 0x4c13) {
+               stac9766_reset(codec, 0);
+               reset++;
+               goto reset;
+       }
+       stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       if (codec->suspend_bias_level == SND_SOC_BIAS_ON)
+               stac9766_set_bias_level(codec, SND_SOC_BIAS_ON);
+
+       return 0;
+}
+
+static struct snd_soc_dai_ops stac9766_dai_ops_analog = {
+       .prepare = ac97_analog_prepare,
+};
+
+static struct snd_soc_dai_ops stac9766_dai_ops_digital = {
+       .prepare = ac97_digital_prepare,
+       .trigger = ac97_digital_trigger,
+};
+
+struct snd_soc_dai stac9766_dai[] = {
+{
+       .name = "stac9766 analog",
+       .id = 0,
+       .ac97_control = 1,
+
+       /* stream cababilities */
+       .playback = {
+               .stream_name = "stac9766 analog",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = SNDRV_PCM_RATE_8000_48000,
+               .formats = SND_SOC_STD_AC97_FMTS,
+       },
+       .capture = {
+               .stream_name = "stac9766 analog",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = SNDRV_PCM_RATE_8000_48000,
+               .formats = SND_SOC_STD_AC97_FMTS,
+       },
+       /* alsa ops */
+       .ops = &stac9766_dai_ops_analog,
+},
+{
+       .name = "stac9766 IEC958",
+       .id = 1,
+       .ac97_control = 1,
+
+       /* stream cababilities */
+       .playback = {
+               .stream_name = "stac9766 IEC958",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = SNDRV_PCM_RATE_32000 | \
+                       SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+               .formats = SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE,
+       },
+       /* alsa ops */
+       .ops = &stac9766_dai_ops_digital,
+}
+};
+EXPORT_SYMBOL_GPL(stac9766_dai);
+
+static int stac9766_codec_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 "STAC9766 SoC Audio Codec %s\n", STAC9766_VERSION);
+
+       socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+       if (socdev->card->codec == NULL)
+               return -ENOMEM;
+       codec = socdev->card->codec;
+       mutex_init(&codec->mutex);
+
+       codec->reg_cache = kmemdup(stac9766_reg, sizeof(stac9766_reg),
+                                  GFP_KERNEL);
+       if (codec->reg_cache == NULL) {
+               ret = -ENOMEM;
+               goto cache_err;
+       }
+       codec->reg_cache_size = sizeof(stac9766_reg);
+       codec->reg_cache_step = 2;
+
+       codec->name = "STAC9766";
+       codec->owner = THIS_MODULE;
+       codec->dai = stac9766_dai;
+       codec->num_dai = ARRAY_SIZE(stac9766_dai);
+       codec->write = stac9766_ac97_write;
+       codec->read = stac9766_ac97_read;
+       codec->set_bias_level = stac9766_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 */
+       stac9766_reset(codec, 0);
+       ret = stac9766_reset(codec, 1);
+       if (ret < 0) {
+               printk(KERN_ERR "Failed to reset STAC9766: AC97 link error\n");
+               goto reset_err;
+       }
+
+       stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       snd_soc_add_controls(codec, stac9766_snd_ac97_controls,
+                            ARRAY_SIZE(stac9766_snd_ac97_controls));
+
+       ret = snd_soc_init_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);
+cache_err:
+       kfree(socdev->card->codec);
+       socdev->card->codec = NULL;
+       return ret;
+}
+
+static int stac9766_codec_remove(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec = socdev->card->codec;
+
+       if (codec == NULL)
+               return 0;
+
+       snd_soc_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_stac9766 = {
+       .probe = stac9766_codec_probe,
+       .remove = stac9766_codec_remove,
+       .suspend = stac9766_codec_suspend,
+       .resume = stac9766_codec_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_stac9766);
+
+MODULE_DESCRIPTION("ASoC stac9766 driver");
+MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/stac9766.h b/sound/soc/codecs/stac9766.h
new file mode 100644 (file)
index 0000000..65642eb
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * stac9766.h  --  STAC9766 Soc Audio driver
+ */
+
+#ifndef _STAC9766_H
+#define _STAC9766_H
+
+#define AC97_STAC_PAGE0 0x1000
+#define AC97_STAC_DA_CONTROL (AC97_STAC_PAGE0 | 0x6A)
+#define AC97_STAC_ANALOG_SPECIAL (AC97_STAC_PAGE0 | 0x6E)
+#define AC97_STAC_STEREO_MIC 0x78
+
+/* STAC9766 DAI ID's */
+#define STAC9766_DAI_AC97_ANALOG               0
+#define STAC9766_DAI_AC97_DIGITAL              1
+
+extern struct snd_soc_dai stac9766_dai[];
+extern struct snd_soc_codec_device soc_codec_dev_stac9766;
+
+
+#endif
index c3f4afb5d0173a581d0bcb18e8670d88c2d4d498..0b8dcb5cd729281bfd80264acdb5a58b9025636e 100644 (file)
@@ -86,7 +86,7 @@ static int tlv320aic23_write(struct snd_soc_codec *codec, unsigned int reg,
         */
 
        if ((reg < 0 || reg > 9) && (reg != 15)) {
-               printk(KERN_WARNING "%s Invalid register R%d\n", __func__, reg);
+               printk(KERN_WARNING "%s Invalid register R%u\n", __func__, reg);
                return -1;
        }
 
@@ -98,7 +98,7 @@ static int tlv320aic23_write(struct snd_soc_codec *codec, unsigned int reg,
        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__,
+       printk(KERN_ERR "%s cannot write %03x to register R%u\n", __func__,
               value, reg);
 
        return -EIO;
@@ -273,14 +273,14 @@ static const unsigned short sr_valid_mask[] = {
  * Every divisor is a factor of 11*12
  */
 #define SR_MULT (11*12)
-#define A(x) (x) ? (SR_MULT/x) : 0
+#define A(x) (SR_MULT/x)
 static const unsigned char sr_adc_mult_table[] = {
-       A(2), A(2), A(12), A(12),  A(0), A(0), A(3), A(1),
-       A(2), A(2), A(11), A(11),  A(0), A(0), A(0), A(1)
+       A(2), A(2), A(12), A(12),  0, 0, A(3), A(1),
+       A(2), A(2), A(11), A(11),  0, 0, 0, A(1)
 };
 static const unsigned char sr_dac_mult_table[] = {
-       A(2), A(12), A(2), A(12),  A(0), A(0), A(3), A(1),
-       A(2), A(11), A(2), A(11),  A(0), A(0), A(0), A(1)
+       A(2), A(12), A(2), A(12),  0, 0, A(3), A(1),
+       A(2), A(11), A(2), A(11),  0, 0, 0, A(1)
 };
 
 static unsigned get_score(int adc, int adc_l, int adc_h, int need_adc,
@@ -523,6 +523,8 @@ static int tlv320aic23_set_dai_fmt(struct snd_soc_dai *codec_dai,
        case SND_SOC_DAIFMT_I2S:
                iface_reg |= TLV320AIC23_FOR_I2S;
                break;
+       case SND_SOC_DAIFMT_DSP_A:
+               iface_reg |= TLV320AIC23_LRP_ON;
        case SND_SOC_DAIFMT_DSP_B:
                iface_reg |= TLV320AIC23_FOR_DSP;
                break;
index df7c8c281d2f8bec0aff69307bb7f1a5985fe1d3..4dbb853eef5ad056fec1e6cca89f0becbe579251 100644 (file)
@@ -115,6 +115,7 @@ static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = {
        0x00, /* REG_VIBRA_PWM_SET      (0x47)  */
        0x00, /* REG_ANAMIC_GAIN        (0x48)  */
        0x00, /* REG_MISC_SET_2         (0x49)  */
+       0x00, /* REG_SW_SHADOW          (0x4A)  - Shadow, non HW register */
 };
 
 /* codec private data */
@@ -125,6 +126,17 @@ struct twl4030_priv {
 
        struct snd_pcm_substream *master_substream;
        struct snd_pcm_substream *slave_substream;
+
+       unsigned int configured;
+       unsigned int rate;
+       unsigned int sample_bits;
+       unsigned int channels;
+
+       unsigned int sysclk;
+
+       /* Headset output state handling */
+       unsigned int hsl_enabled;
+       unsigned int hsr_enabled;
 };
 
 /*
@@ -161,7 +173,11 @@ static int twl4030_write(struct snd_soc_codec *codec,
                        unsigned int reg, unsigned int value)
 {
        twl4030_write_reg_cache(codec, reg, value);
-       return twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, value, reg);
+       if (likely(reg < TWL4030_REG_SW_SHADOW))
+               return twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, value,
+                                           reg);
+       else
+               return 0;
 }
 
 static void twl4030_codec_enable(struct snd_soc_codec *codec, int enable)
@@ -188,6 +204,7 @@ static void twl4030_codec_enable(struct snd_soc_codec *codec, int enable)
 
 static void twl4030_init_chip(struct snd_soc_codec *codec)
 {
+       u8 *cache = codec->reg_cache;
        int i;
 
        /* clear CODECPDZ prior to setting register defaults */
@@ -195,7 +212,7 @@ static void twl4030_init_chip(struct snd_soc_codec *codec)
 
        /* set all audio section registers to reasonable defaults */
        for (i = TWL4030_REG_OPTION; i <= TWL4030_REG_MISC_SET_2; i++)
-               twl4030_write(codec, i, twl4030_reg[i]);
+               twl4030_write(codec, i, cache[i]);
 
 }
 
@@ -232,7 +249,7 @@ static void twl4030_codec_mute(struct snd_soc_codec *codec, int mute)
                                        TWL4030_REG_PRECKL_CTL);
                reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PRECKR_CTL);
                twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
-                                       reg_val & (~TWL4030_PRECKL_GAIN),
+                                       reg_val & (~TWL4030_PRECKR_GAIN),
                                        TWL4030_REG_PRECKR_CTL);
 
                /* Disable PLL */
@@ -316,104 +333,60 @@ static void twl4030_power_down(struct snd_soc_codec *codec)
 }
 
 /* Earpiece */
-static const char *twl4030_earpiece_texts[] =
-               {"Off", "DACL1", "DACL2", "DACR1"};
-
-static const unsigned int twl4030_earpiece_values[] =
-               {0x0, 0x1, 0x2, 0x4};
-
-static const struct soc_enum twl4030_earpiece_enum =
-       SOC_VALUE_ENUM_SINGLE(TWL4030_REG_EAR_CTL, 1, 0x7,
-                       ARRAY_SIZE(twl4030_earpiece_texts),
-                       twl4030_earpiece_texts,
-                       twl4030_earpiece_values);
-
-static const struct snd_kcontrol_new twl4030_dapm_earpiece_control =
-SOC_DAPM_VALUE_ENUM("Route", twl4030_earpiece_enum);
+static const struct snd_kcontrol_new twl4030_dapm_earpiece_controls[] = {
+       SOC_DAPM_SINGLE("Voice", TWL4030_REG_EAR_CTL, 0, 1, 0),
+       SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_EAR_CTL, 1, 1, 0),
+       SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_EAR_CTL, 2, 1, 0),
+       SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_EAR_CTL, 3, 1, 0),
+};
 
 /* PreDrive Left */
-static const char *twl4030_predrivel_texts[] =
-               {"Off", "DACL1", "DACL2", "DACR2"};
-
-static const unsigned int twl4030_predrivel_values[] =
-               {0x0, 0x1, 0x2, 0x4};
-
-static const struct soc_enum twl4030_predrivel_enum =
-       SOC_VALUE_ENUM_SINGLE(TWL4030_REG_PREDL_CTL, 1, 0x7,
-                       ARRAY_SIZE(twl4030_predrivel_texts),
-                       twl4030_predrivel_texts,
-                       twl4030_predrivel_values);
-
-static const struct snd_kcontrol_new twl4030_dapm_predrivel_control =
-SOC_DAPM_VALUE_ENUM("Route", twl4030_predrivel_enum);
+static const struct snd_kcontrol_new twl4030_dapm_predrivel_controls[] = {
+       SOC_DAPM_SINGLE("Voice", TWL4030_REG_PREDL_CTL, 0, 1, 0),
+       SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_PREDL_CTL, 1, 1, 0),
+       SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_PREDL_CTL, 2, 1, 0),
+       SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_PREDL_CTL, 3, 1, 0),
+};
 
 /* PreDrive Right */
-static const char *twl4030_predriver_texts[] =
-               {"Off", "DACR1", "DACR2", "DACL2"};
-
-static const unsigned int twl4030_predriver_values[] =
-               {0x0, 0x1, 0x2, 0x4};
-
-static const struct soc_enum twl4030_predriver_enum =
-       SOC_VALUE_ENUM_SINGLE(TWL4030_REG_PREDR_CTL, 1, 0x7,
-                       ARRAY_SIZE(twl4030_predriver_texts),
-                       twl4030_predriver_texts,
-                       twl4030_predriver_values);
-
-static const struct snd_kcontrol_new twl4030_dapm_predriver_control =
-SOC_DAPM_VALUE_ENUM("Route", twl4030_predriver_enum);
+static const struct snd_kcontrol_new twl4030_dapm_predriver_controls[] = {
+       SOC_DAPM_SINGLE("Voice", TWL4030_REG_PREDR_CTL, 0, 1, 0),
+       SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_PREDR_CTL, 1, 1, 0),
+       SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_PREDR_CTL, 2, 1, 0),
+       SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_PREDR_CTL, 3, 1, 0),
+};
 
 /* Headset Left */
-static const char *twl4030_hsol_texts[] =
-               {"Off", "DACL1", "DACL2"};
-
-static const struct soc_enum twl4030_hsol_enum =
-       SOC_ENUM_SINGLE(TWL4030_REG_HS_SEL, 1,
-                       ARRAY_SIZE(twl4030_hsol_texts),
-                       twl4030_hsol_texts);
-
-static const struct snd_kcontrol_new twl4030_dapm_hsol_control =
-SOC_DAPM_ENUM("Route", twl4030_hsol_enum);
+static const struct snd_kcontrol_new twl4030_dapm_hsol_controls[] = {
+       SOC_DAPM_SINGLE("Voice", TWL4030_REG_HS_SEL, 0, 1, 0),
+       SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_HS_SEL, 1, 1, 0),
+       SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_HS_SEL, 2, 1, 0),
+};
 
 /* Headset Right */
-static const char *twl4030_hsor_texts[] =
-               {"Off", "DACR1", "DACR2"};
-
-static const struct soc_enum twl4030_hsor_enum =
-       SOC_ENUM_SINGLE(TWL4030_REG_HS_SEL, 4,
-                       ARRAY_SIZE(twl4030_hsor_texts),
-                       twl4030_hsor_texts);
-
-static const struct snd_kcontrol_new twl4030_dapm_hsor_control =
-SOC_DAPM_ENUM("Route", twl4030_hsor_enum);
+static const struct snd_kcontrol_new twl4030_dapm_hsor_controls[] = {
+       SOC_DAPM_SINGLE("Voice", TWL4030_REG_HS_SEL, 3, 1, 0),
+       SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_HS_SEL, 4, 1, 0),
+       SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_HS_SEL, 5, 1, 0),
+};
 
 /* Carkit Left */
-static const char *twl4030_carkitl_texts[] =
-               {"Off", "DACL1", "DACL2"};
-
-static const struct soc_enum twl4030_carkitl_enum =
-       SOC_ENUM_SINGLE(TWL4030_REG_PRECKL_CTL, 1,
-                       ARRAY_SIZE(twl4030_carkitl_texts),
-                       twl4030_carkitl_texts);
-
-static const struct snd_kcontrol_new twl4030_dapm_carkitl_control =
-SOC_DAPM_ENUM("Route", twl4030_carkitl_enum);
+static const struct snd_kcontrol_new twl4030_dapm_carkitl_controls[] = {
+       SOC_DAPM_SINGLE("Voice", TWL4030_REG_PRECKL_CTL, 0, 1, 0),
+       SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_PRECKL_CTL, 1, 1, 0),
+       SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_PRECKL_CTL, 2, 1, 0),
+};
 
 /* Carkit Right */
-static const char *twl4030_carkitr_texts[] =
-               {"Off", "DACR1", "DACR2"};
-
-static const struct soc_enum twl4030_carkitr_enum =
-       SOC_ENUM_SINGLE(TWL4030_REG_PRECKR_CTL, 1,
-                       ARRAY_SIZE(twl4030_carkitr_texts),
-                       twl4030_carkitr_texts);
-
-static const struct snd_kcontrol_new twl4030_dapm_carkitr_control =
-SOC_DAPM_ENUM("Route", twl4030_carkitr_enum);
+static const struct snd_kcontrol_new twl4030_dapm_carkitr_controls[] = {
+       SOC_DAPM_SINGLE("Voice", TWL4030_REG_PRECKR_CTL, 0, 1, 0),
+       SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_PRECKR_CTL, 1, 1, 0),
+       SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_PRECKR_CTL, 2, 1, 0),
+};
 
 /* Handsfree Left */
 static const char *twl4030_handsfreel_texts[] =
-               {"Voice", "DACL1", "DACL2", "DACR2"};
+               {"Voice", "AudioL1", "AudioL2", "AudioR2"};
 
 static const struct soc_enum twl4030_handsfreel_enum =
        SOC_ENUM_SINGLE(TWL4030_REG_HFL_CTL, 0,
@@ -423,9 +396,13 @@ static const struct soc_enum twl4030_handsfreel_enum =
 static const struct snd_kcontrol_new twl4030_dapm_handsfreel_control =
 SOC_DAPM_ENUM("Route", twl4030_handsfreel_enum);
 
+/* Handsfree Left virtual mute */
+static const struct snd_kcontrol_new twl4030_dapm_handsfreelmute_control =
+       SOC_DAPM_SINGLE("Switch", TWL4030_REG_SW_SHADOW, 0, 1, 0);
+
 /* Handsfree Right */
 static const char *twl4030_handsfreer_texts[] =
-               {"Voice", "DACR1", "DACR2", "DACL2"};
+               {"Voice", "AudioR1", "AudioR2", "AudioL2"};
 
 static const struct soc_enum twl4030_handsfreer_enum =
        SOC_ENUM_SINGLE(TWL4030_REG_HFR_CTL, 0,
@@ -435,37 +412,48 @@ static const struct soc_enum twl4030_handsfreer_enum =
 static const struct snd_kcontrol_new twl4030_dapm_handsfreer_control =
 SOC_DAPM_ENUM("Route", twl4030_handsfreer_enum);
 
-/* Left analog microphone selection */
-static const char *twl4030_analoglmic_texts[] =
-               {"Off", "Main mic", "Headset mic", "AUXL", "Carkit mic"};
+/* Handsfree Right virtual mute */
+static const struct snd_kcontrol_new twl4030_dapm_handsfreermute_control =
+       SOC_DAPM_SINGLE("Switch", TWL4030_REG_SW_SHADOW, 1, 1, 0);
 
-static const unsigned int twl4030_analoglmic_values[] =
-               {0x0, 0x1, 0x2, 0x4, 0x8};
+/* Vibra */
+/* Vibra audio path selection */
+static const char *twl4030_vibra_texts[] =
+               {"AudioL1", "AudioR1", "AudioL2", "AudioR2"};
 
-static const struct soc_enum twl4030_analoglmic_enum =
-       SOC_VALUE_ENUM_SINGLE(TWL4030_REG_ANAMICL, 0, 0xf,
-                       ARRAY_SIZE(twl4030_analoglmic_texts),
-                       twl4030_analoglmic_texts,
-                       twl4030_analoglmic_values);
+static const struct soc_enum twl4030_vibra_enum =
+       SOC_ENUM_SINGLE(TWL4030_REG_VIBRA_CTL, 2,
+                       ARRAY_SIZE(twl4030_vibra_texts),
+                       twl4030_vibra_texts);
 
-static const struct snd_kcontrol_new twl4030_dapm_analoglmic_control =
-SOC_DAPM_VALUE_ENUM("Route", twl4030_analoglmic_enum);
+static const struct snd_kcontrol_new twl4030_dapm_vibra_control =
+SOC_DAPM_ENUM("Route", twl4030_vibra_enum);
 
-/* Right analog microphone selection */
-static const char *twl4030_analogrmic_texts[] =
-               {"Off", "Sub mic", "AUXR"};
+/* Vibra path selection: local vibrator (PWM) or audio driven */
+static const char *twl4030_vibrapath_texts[] =
+               {"Local vibrator", "Audio"};
 
-static const unsigned int twl4030_analogrmic_values[] =
-               {0x0, 0x1, 0x4};
+static const struct soc_enum twl4030_vibrapath_enum =
+       SOC_ENUM_SINGLE(TWL4030_REG_VIBRA_CTL, 4,
+                       ARRAY_SIZE(twl4030_vibrapath_texts),
+                       twl4030_vibrapath_texts);
 
-static const struct soc_enum twl4030_analogrmic_enum =
-       SOC_VALUE_ENUM_SINGLE(TWL4030_REG_ANAMICR, 0, 0x5,
-                       ARRAY_SIZE(twl4030_analogrmic_texts),
-                       twl4030_analogrmic_texts,
-                       twl4030_analogrmic_values);
+static const struct snd_kcontrol_new twl4030_dapm_vibrapath_control =
+SOC_DAPM_ENUM("Route", twl4030_vibrapath_enum);
 
-static const struct snd_kcontrol_new twl4030_dapm_analogrmic_control =
-SOC_DAPM_VALUE_ENUM("Route", twl4030_analogrmic_enum);
+/* Left analog microphone selection */
+static const struct snd_kcontrol_new twl4030_dapm_analoglmic_controls[] = {
+       SOC_DAPM_SINGLE("Main mic", TWL4030_REG_ANAMICL, 0, 1, 0),
+       SOC_DAPM_SINGLE("Headset mic", TWL4030_REG_ANAMICL, 1, 1, 0),
+       SOC_DAPM_SINGLE("AUXL", TWL4030_REG_ANAMICL, 2, 1, 0),
+       SOC_DAPM_SINGLE("Carkit mic", TWL4030_REG_ANAMICL, 3, 1, 0),
+};
+
+/* Right analog microphone selection */
+static const struct snd_kcontrol_new twl4030_dapm_analogrmic_controls[] = {
+       SOC_DAPM_SINGLE("Sub mic", TWL4030_REG_ANAMICR, 0, 1, 0),
+       SOC_DAPM_SINGLE("AUXR", TWL4030_REG_ANAMICR, 2, 1, 0),
+};
 
 /* TX1 L/R Analog/Digital microphone selection */
 static const char *twl4030_micpathtx1_texts[] =
@@ -507,6 +495,10 @@ static const struct snd_kcontrol_new twl4030_dapm_abypassr2_control =
 static const struct snd_kcontrol_new twl4030_dapm_abypassl2_control =
        SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXL2_APGA_CTL, 2, 1, 0);
 
+/* Analog bypass for Voice */
+static const struct snd_kcontrol_new twl4030_dapm_abypassv_control =
+       SOC_DAPM_SINGLE("Switch", TWL4030_REG_VDL_APGA_CTL, 2, 1, 0);
+
 /* Digital bypass gain, 0 mutes the bypass */
 static const unsigned int twl4030_dapm_dbypass_tlv[] = {
        TLV_DB_RANGE_HEAD(2),
@@ -526,6 +518,18 @@ static const struct snd_kcontrol_new twl4030_dapm_dbypassr_control =
                        TWL4030_REG_ATX2ARXPGA, 0, 7, 0,
                        twl4030_dapm_dbypass_tlv);
 
+/*
+ * Voice Sidetone GAIN volume control:
+ * from -51 to -10 dB in 1 dB steps (mute instead of -51 dB)
+ */
+static DECLARE_TLV_DB_SCALE(twl4030_dapm_dbypassv_tlv, -5100, 100, 1);
+
+/* Digital bypass voice: sidetone (VUL -> VDL)*/
+static const struct snd_kcontrol_new twl4030_dapm_dbypassv_control =
+       SOC_DAPM_SINGLE_TLV("Volume",
+                       TWL4030_REG_VSTPGA, 0, 0x29, 0,
+                       twl4030_dapm_dbypassv_tlv);
+
 static int micpath_event(struct snd_soc_dapm_widget *w,
        struct snd_kcontrol *kcontrol, int event)
 {
@@ -556,63 +560,143 @@ static int micpath_event(struct snd_soc_dapm_widget *w,
        return 0;
 }
 
-static int handsfree_event(struct snd_soc_dapm_widget *w,
-               struct snd_kcontrol *kcontrol, int event)
+static void handsfree_ramp(struct snd_soc_codec *codec, int reg, int ramp)
 {
-       struct soc_enum *e = (struct soc_enum *)w->kcontrols->private_value;
        unsigned char hs_ctl;
 
-       hs_ctl = twl4030_read_reg_cache(w->codec, e->reg);
+       hs_ctl = twl4030_read_reg_cache(codec, reg);
 
-       if (hs_ctl & TWL4030_HF_CTL_REF_EN) {
+       if (ramp) {
+               /* HF ramp-up */
+               hs_ctl |= TWL4030_HF_CTL_REF_EN;
+               twl4030_write(codec, reg, hs_ctl);
+               udelay(10);
                hs_ctl |= TWL4030_HF_CTL_RAMP_EN;
-               twl4030_write(w->codec, e->reg, hs_ctl);
+               twl4030_write(codec, reg, hs_ctl);
+               udelay(40);
                hs_ctl |= TWL4030_HF_CTL_LOOP_EN;
-               twl4030_write(w->codec, e->reg, hs_ctl);
                hs_ctl |= TWL4030_HF_CTL_HB_EN;
-               twl4030_write(w->codec, e->reg, hs_ctl);
+               twl4030_write(codec, reg, hs_ctl);
        } else {
-               hs_ctl &= ~(TWL4030_HF_CTL_RAMP_EN | TWL4030_HF_CTL_LOOP_EN
-                               | TWL4030_HF_CTL_HB_EN);
-               twl4030_write(w->codec, e->reg, hs_ctl);
+               /* HF ramp-down */
+               hs_ctl &= ~TWL4030_HF_CTL_LOOP_EN;
+               hs_ctl &= ~TWL4030_HF_CTL_HB_EN;
+               twl4030_write(codec, reg, hs_ctl);
+               hs_ctl &= ~TWL4030_HF_CTL_RAMP_EN;
+               twl4030_write(codec, reg, hs_ctl);
+               udelay(40);
+               hs_ctl &= ~TWL4030_HF_CTL_REF_EN;
+               twl4030_write(codec, reg, hs_ctl);
        }
+}
 
+static int handsfreelpga_event(struct snd_soc_dapm_widget *w,
+               struct snd_kcontrol *kcontrol, int event)
+{
+       switch (event) {
+       case SND_SOC_DAPM_POST_PMU:
+               handsfree_ramp(w->codec, TWL4030_REG_HFL_CTL, 1);
+               break;
+       case SND_SOC_DAPM_POST_PMD:
+               handsfree_ramp(w->codec, TWL4030_REG_HFL_CTL, 0);
+               break;
+       }
        return 0;
 }
 
-static int headsetl_event(struct snd_soc_dapm_widget *w,
+static int handsfreerpga_event(struct snd_soc_dapm_widget *w,
                struct snd_kcontrol *kcontrol, int event)
+{
+       switch (event) {
+       case SND_SOC_DAPM_POST_PMU:
+               handsfree_ramp(w->codec, TWL4030_REG_HFR_CTL, 1);
+               break;
+       case SND_SOC_DAPM_POST_PMD:
+               handsfree_ramp(w->codec, TWL4030_REG_HFR_CTL, 0);
+               break;
+       }
+       return 0;
+}
+
+static void headset_ramp(struct snd_soc_codec *codec, int ramp)
 {
        unsigned char hs_gain, hs_pop;
+       struct twl4030_priv *twl4030 = codec->private_data;
+       /* Base values for ramp delay calculation: 2^19 - 2^26 */
+       unsigned int ramp_base[] = {524288, 1048576, 2097152, 4194304,
+                                   8388608, 16777216, 33554432, 67108864};
 
-       /* Save the current volume */
-       hs_gain = twl4030_read_reg_cache(w->codec, TWL4030_REG_HS_GAIN_SET);
-       hs_pop = twl4030_read_reg_cache(w->codec, TWL4030_REG_HS_POPN_SET);
+       hs_gain = twl4030_read_reg_cache(codec, TWL4030_REG_HS_GAIN_SET);
+       hs_pop = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET);
 
-       switch (event) {
-       case SND_SOC_DAPM_POST_PMU:
-               /* Do the anti-pop/bias ramp enable according to the TRM */
+       if (ramp) {
+               /* Headset ramp-up according to the TRM */
                hs_pop |= TWL4030_VMID_EN;
-               twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop);
-               /* Is this needed? Can we just use whatever gain here? */
-               twl4030_write(w->codec, TWL4030_REG_HS_GAIN_SET,
-                               (hs_gain & (~0x0f)) | 0x0a);
+               twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+               twl4030_write(codec, TWL4030_REG_HS_GAIN_SET, hs_gain);
                hs_pop |= TWL4030_RAMP_EN;
-               twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop);
-
-               /* Restore the original volume */
-               twl4030_write(w->codec, TWL4030_REG_HS_GAIN_SET, hs_gain);
-               break;
-       case SND_SOC_DAPM_POST_PMD:
-               /* Do the anti-pop/bias ramp disable according to the TRM */
+               twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+       } else {
+               /* Headset ramp-down _not_ according to
+                * the TRM, but in a way that it is working */
                hs_pop &= ~TWL4030_RAMP_EN;
-               twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+               twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+               /* Wait ramp delay time + 1, so the VMID can settle */
+               mdelay((ramp_base[(hs_pop & TWL4030_RAMP_DELAY) >> 2] /
+                       twl4030->sysclk) + 1);
                /* Bypass the reg_cache to mute the headset */
                twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
                                        hs_gain & (~0x0f),
                                        TWL4030_REG_HS_GAIN_SET);
+
                hs_pop &= ~TWL4030_VMID_EN;
-               twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+               twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+       }
+}
+
+static int headsetlpga_event(struct snd_soc_dapm_widget *w,
+               struct snd_kcontrol *kcontrol, int event)
+{
+       struct twl4030_priv *twl4030 = w->codec->private_data;
+
+       switch (event) {
+       case SND_SOC_DAPM_POST_PMU:
+               /* Do the ramp-up only once */
+               if (!twl4030->hsr_enabled)
+                       headset_ramp(w->codec, 1);
+
+               twl4030->hsl_enabled = 1;
+               break;
+       case SND_SOC_DAPM_POST_PMD:
+               /* Do the ramp-down only if both headsetL/R is disabled */
+               if (!twl4030->hsr_enabled)
+                       headset_ramp(w->codec, 0);
+
+               twl4030->hsl_enabled = 0;
+               break;
+       }
+       return 0;
+}
+
+static int headsetrpga_event(struct snd_soc_dapm_widget *w,
+               struct snd_kcontrol *kcontrol, int event)
+{
+       struct twl4030_priv *twl4030 = w->codec->private_data;
+
+       switch (event) {
+       case SND_SOC_DAPM_POST_PMU:
+               /* Do the ramp-up only once */
+               if (!twl4030->hsl_enabled)
+                       headset_ramp(w->codec, 1);
+
+               twl4030->hsr_enabled = 1;
+               break;
+       case SND_SOC_DAPM_POST_PMD:
+               /* Do the ramp-down only if both headsetL/R is disabled */
+               if (!twl4030->hsl_enabled)
+                       headset_ramp(w->codec, 0);
+
+               twl4030->hsr_enabled = 0;
                break;
        }
        return 0;
@@ -624,7 +708,7 @@ static int bypass_event(struct snd_soc_dapm_widget *w,
        struct soc_mixer_control *m =
                (struct soc_mixer_control *)w->kcontrols->private_value;
        struct twl4030_priv *twl4030 = w->codec->private_data;
-       unsigned char reg;
+       unsigned char reg, misc;
 
        reg = twl4030_read_reg_cache(w->codec, m->reg);
 
@@ -636,14 +720,34 @@ static int bypass_event(struct snd_soc_dapm_widget *w,
                else
                        twl4030->bypass_state &=
                                ~(1 << (m->reg - TWL4030_REG_ARXL1_APGA_CTL));
+       } else if (m->reg == TWL4030_REG_VDL_APGA_CTL) {
+               /* Analog voice bypass */
+               if (reg & (1 << m->shift))
+                       twl4030->bypass_state |= (1 << 4);
+               else
+                       twl4030->bypass_state &= ~(1 << 4);
+       } else if (m->reg == TWL4030_REG_VSTPGA) {
+               /* Voice digital bypass */
+               if (reg)
+                       twl4030->bypass_state |= (1 << 5);
+               else
+                       twl4030->bypass_state &= ~(1 << 5);
        } else {
                /* Digital bypass */
                if (reg & (0x7 << m->shift))
-                       twl4030->bypass_state |= (1 << (m->shift ? 5 : 4));
+                       twl4030->bypass_state |= (1 << (m->shift ? 7 : 6));
                else
-                       twl4030->bypass_state &= ~(1 << (m->shift ? 5 : 4));
+                       twl4030->bypass_state &= ~(1 << (m->shift ? 7 : 6));
        }
 
+       /* Enable master analog loopback mode if any analog switch is enabled*/
+       misc = twl4030_read_reg_cache(w->codec, TWL4030_REG_MISC_SET_1);
+       if (twl4030->bypass_state & 0x1F)
+               misc |= TWL4030_FMLOOP_EN;
+       else
+               misc &= ~TWL4030_FMLOOP_EN;
+       twl4030_write(w->codec, TWL4030_REG_MISC_SET_1, misc);
+
        if (w->codec->bias_level == SND_SOC_BIAS_STANDBY) {
                if (twl4030->bypass_state)
                        twl4030_codec_mute(w->codec, 0);
@@ -810,6 +914,48 @@ static int snd_soc_put_volsw_r2_twl4030(struct snd_kcontrol *kcontrol,
        return err;
 }
 
+/* Codec operation modes */
+static const char *twl4030_op_modes_texts[] = {
+       "Option 2 (voice/audio)", "Option 1 (audio)"
+};
+
+static const struct soc_enum twl4030_op_modes_enum =
+       SOC_ENUM_SINGLE(TWL4030_REG_CODEC_MODE, 0,
+                       ARRAY_SIZE(twl4030_op_modes_texts),
+                       twl4030_op_modes_texts);
+
+int snd_soc_put_twl4030_opmode_enum_double(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct twl4030_priv *twl4030 = codec->private_data;
+       struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+       unsigned short val;
+       unsigned short mask, bitmask;
+
+       if (twl4030->configured) {
+               printk(KERN_ERR "twl4030 operation mode cannot be "
+                       "changed on-the-fly\n");
+               return -EBUSY;
+       }
+
+       for (bitmask = 1; bitmask < e->max; bitmask <<= 1)
+               ;
+       if (ucontrol->value.enumerated.item[0] > e->max - 1)
+               return -EINVAL;
+
+       val = ucontrol->value.enumerated.item[0] << e->shift_l;
+       mask = (bitmask - 1) << e->shift_l;
+       if (e->shift_l != e->shift_r) {
+               if (ucontrol->value.enumerated.item[1] > e->max - 1)
+                       return -EINVAL;
+               val |= ucontrol->value.enumerated.item[1] << e->shift_r;
+               mask |= (bitmask - 1) << e->shift_r;
+       }
+
+       return snd_soc_update_bits(codec, e->reg, mask, val);
+}
+
 /*
  * FGAIN volume control:
  * from -62 to 0 dB in 1 dB steps (mute instead of -63 dB)
@@ -823,6 +969,12 @@ static DECLARE_TLV_DB_SCALE(digital_fine_tlv, -6300, 100, 1);
  */
 static DECLARE_TLV_DB_SCALE(digital_coarse_tlv, 0, 600, 0);
 
+/*
+ * Voice Downlink GAIN volume control:
+ * from -37 to 12 dB in 1 dB steps (mute instead of -37 dB)
+ */
+static DECLARE_TLV_DB_SCALE(digital_voice_downlink_tlv, -3700, 100, 1);
+
 /*
  * Analog playback gain
  * -24 dB to 12 dB in 2 dB steps
@@ -864,7 +1016,32 @@ static const struct soc_enum twl4030_rampdelay_enum =
                        ARRAY_SIZE(twl4030_rampdelay_texts),
                        twl4030_rampdelay_texts);
 
+/* Vibra H-bridge direction mode */
+static const char *twl4030_vibradirmode_texts[] = {
+       "Vibra H-bridge direction", "Audio data MSB",
+};
+
+static const struct soc_enum twl4030_vibradirmode_enum =
+       SOC_ENUM_SINGLE(TWL4030_REG_VIBRA_CTL, 5,
+                       ARRAY_SIZE(twl4030_vibradirmode_texts),
+                       twl4030_vibradirmode_texts);
+
+/* Vibra H-bridge direction */
+static const char *twl4030_vibradir_texts[] = {
+       "Positive polarity", "Negative polarity",
+};
+
+static const struct soc_enum twl4030_vibradir_enum =
+       SOC_ENUM_SINGLE(TWL4030_REG_VIBRA_CTL, 1,
+                       ARRAY_SIZE(twl4030_vibradir_texts),
+                       twl4030_vibradir_texts);
+
 static const struct snd_kcontrol_new twl4030_snd_controls[] = {
+       /* Codec operation mode control */
+       SOC_ENUM_EXT("Codec Operation Mode", twl4030_op_modes_enum,
+               snd_soc_get_enum_double,
+               snd_soc_put_twl4030_opmode_enum_double),
+
        /* Common playback gain controls */
        SOC_DOUBLE_R_TLV("DAC1 Digital Fine Playback Volume",
                TWL4030_REG_ARXL1PGA, TWL4030_REG_ARXR1PGA,
@@ -893,6 +1070,16 @@ static const struct snd_kcontrol_new twl4030_snd_controls[] = {
                TWL4030_REG_ARXL2_APGA_CTL, TWL4030_REG_ARXR2_APGA_CTL,
                1, 1, 0),
 
+       /* Common voice downlink gain controls */
+       SOC_SINGLE_TLV("DAC Voice Digital Downlink Volume",
+               TWL4030_REG_VRXPGA, 0, 0x31, 0, digital_voice_downlink_tlv),
+
+       SOC_SINGLE_TLV("DAC Voice Analog Downlink Volume",
+               TWL4030_REG_VDL_APGA_CTL, 3, 0x12, 1, analog_tlv),
+
+       SOC_SINGLE("DAC Voice Analog Downlink Switch",
+               TWL4030_REG_VDL_APGA_CTL, 1, 1, 0),
+
        /* Separate output gain controls */
        SOC_DOUBLE_R_TLV_TWL4030("PreDriv Playback Volume",
                TWL4030_REG_PREDL_CTL, TWL4030_REG_PREDR_CTL,
@@ -920,6 +1107,9 @@ static const struct snd_kcontrol_new twl4030_snd_controls[] = {
                0, 3, 5, 0, input_gain_tlv),
 
        SOC_ENUM("HS ramp delay", twl4030_rampdelay_enum),
+
+       SOC_ENUM("Vibra H-bridge mode", twl4030_vibradirmode_enum),
+       SOC_ENUM("Vibra H-bridge direction", twl4030_vibradir_enum),
 };
 
 static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
@@ -947,26 +1137,19 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
        SND_SOC_DAPM_OUTPUT("CARKITR"),
        SND_SOC_DAPM_OUTPUT("HFL"),
        SND_SOC_DAPM_OUTPUT("HFR"),
+       SND_SOC_DAPM_OUTPUT("VIBRA"),
 
        /* DACs */
-       SND_SOC_DAPM_DAC("DAC Right1", "Right Front Playback",
+       SND_SOC_DAPM_DAC("DAC Right1", "Right Front HiFi Playback",
                        SND_SOC_NOPM, 0, 0),
-       SND_SOC_DAPM_DAC("DAC Left1", "Left Front Playback",
+       SND_SOC_DAPM_DAC("DAC Left1", "Left Front HiFi Playback",
                        SND_SOC_NOPM, 0, 0),
-       SND_SOC_DAPM_DAC("DAC Right2", "Right Rear Playback",
+       SND_SOC_DAPM_DAC("DAC Right2", "Right Rear HiFi Playback",
                        SND_SOC_NOPM, 0, 0),
-       SND_SOC_DAPM_DAC("DAC Left2", "Left Rear Playback",
+       SND_SOC_DAPM_DAC("DAC Left2", "Left Rear HiFi Playback",
+                       SND_SOC_NOPM, 0, 0),
+       SND_SOC_DAPM_DAC("DAC Voice", "Voice Playback",
                        SND_SOC_NOPM, 0, 0),
-
-       /* Analog PGAs */
-       SND_SOC_DAPM_PGA("ARXR1_APGA", TWL4030_REG_ARXR1_APGA_CTL,
-                       0, 0, NULL, 0),
-       SND_SOC_DAPM_PGA("ARXL1_APGA", TWL4030_REG_ARXL1_APGA_CTL,
-                       0, 0, NULL, 0),
-       SND_SOC_DAPM_PGA("ARXR2_APGA", TWL4030_REG_ARXR2_APGA_CTL,
-                       0, 0, NULL, 0),
-       SND_SOC_DAPM_PGA("ARXL2_APGA", TWL4030_REG_ARXL2_APGA_CTL,
-                       0, 0, NULL, 0),
 
        /* Analog bypasses */
        SND_SOC_DAPM_SWITCH_E("Right1 Analog Loopback", SND_SOC_NOPM, 0, 0,
@@ -981,6 +1164,9 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
        SND_SOC_DAPM_SWITCH_E("Left2 Analog Loopback", SND_SOC_NOPM, 0, 0,
                        &twl4030_dapm_abypassl2_control,
                        bypass_event, SND_SOC_DAPM_POST_REG),
+       SND_SOC_DAPM_SWITCH_E("Voice Analog Loopback", SND_SOC_NOPM, 0, 0,
+                       &twl4030_dapm_abypassv_control,
+                       bypass_event, SND_SOC_DAPM_POST_REG),
 
        /* Digital bypasses */
        SND_SOC_DAPM_SWITCH_E("Left Digital Loopback", SND_SOC_NOPM, 0, 0,
@@ -989,43 +1175,88 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
        SND_SOC_DAPM_SWITCH_E("Right Digital Loopback", SND_SOC_NOPM, 0, 0,
                        &twl4030_dapm_dbypassr_control, bypass_event,
                        SND_SOC_DAPM_POST_REG),
+       SND_SOC_DAPM_SWITCH_E("Voice Digital Loopback", SND_SOC_NOPM, 0, 0,
+                       &twl4030_dapm_dbypassv_control, bypass_event,
+                       SND_SOC_DAPM_POST_REG),
 
-       SND_SOC_DAPM_MIXER("Analog R1 Playback Mixer", TWL4030_REG_AVDAC_CTL,
-                       0, 0, NULL, 0),
-       SND_SOC_DAPM_MIXER("Analog L1 Playback Mixer", TWL4030_REG_AVDAC_CTL,
-                       1, 0, NULL, 0),
-       SND_SOC_DAPM_MIXER("Analog R2 Playback Mixer", TWL4030_REG_AVDAC_CTL,
-                       2, 0, NULL, 0),
-       SND_SOC_DAPM_MIXER("Analog L2 Playback Mixer", TWL4030_REG_AVDAC_CTL,
-                       3, 0, NULL, 0),
-
-       /* Output MUX controls */
+       /* Digital mixers, power control for the physical DACs */
+       SND_SOC_DAPM_MIXER("Digital R1 Playback Mixer",
+                       TWL4030_REG_AVDAC_CTL, 0, 0, NULL, 0),
+       SND_SOC_DAPM_MIXER("Digital L1 Playback Mixer",
+                       TWL4030_REG_AVDAC_CTL, 1, 0, NULL, 0),
+       SND_SOC_DAPM_MIXER("Digital R2 Playback Mixer",
+                       TWL4030_REG_AVDAC_CTL, 2, 0, NULL, 0),
+       SND_SOC_DAPM_MIXER("Digital L2 Playback Mixer",
+                       TWL4030_REG_AVDAC_CTL, 3, 0, NULL, 0),
+       SND_SOC_DAPM_MIXER("Digital Voice Playback Mixer",
+                       TWL4030_REG_AVDAC_CTL, 4, 0, NULL, 0),
+
+       /* Analog mixers, power control for the physical PGAs */
+       SND_SOC_DAPM_MIXER("Analog R1 Playback Mixer",
+                       TWL4030_REG_ARXR1_APGA_CTL, 0, 0, NULL, 0),
+       SND_SOC_DAPM_MIXER("Analog L1 Playback Mixer",
+                       TWL4030_REG_ARXL1_APGA_CTL, 0, 0, NULL, 0),
+       SND_SOC_DAPM_MIXER("Analog R2 Playback Mixer",
+                       TWL4030_REG_ARXR2_APGA_CTL, 0, 0, NULL, 0),
+       SND_SOC_DAPM_MIXER("Analog L2 Playback Mixer",
+                       TWL4030_REG_ARXL2_APGA_CTL, 0, 0, NULL, 0),
+       SND_SOC_DAPM_MIXER("Analog Voice Playback Mixer",
+                       TWL4030_REG_VDL_APGA_CTL, 0, 0, NULL, 0),
+
+       /* Output MIXER controls */
        /* Earpiece */
-       SND_SOC_DAPM_VALUE_MUX("Earpiece Mux", SND_SOC_NOPM, 0, 0,
-               &twl4030_dapm_earpiece_control),
+       SND_SOC_DAPM_MIXER("Earpiece Mixer", SND_SOC_NOPM, 0, 0,
+                       &twl4030_dapm_earpiece_controls[0],
+                       ARRAY_SIZE(twl4030_dapm_earpiece_controls)),
        /* PreDrivL/R */
-       SND_SOC_DAPM_VALUE_MUX("PredriveL Mux", SND_SOC_NOPM, 0, 0,
-               &twl4030_dapm_predrivel_control),
-       SND_SOC_DAPM_VALUE_MUX("PredriveR Mux", SND_SOC_NOPM, 0, 0,
-               &twl4030_dapm_predriver_control),
+       SND_SOC_DAPM_MIXER("PredriveL Mixer", SND_SOC_NOPM, 0, 0,
+                       &twl4030_dapm_predrivel_controls[0],
+                       ARRAY_SIZE(twl4030_dapm_predrivel_controls)),
+       SND_SOC_DAPM_MIXER("PredriveR Mixer", SND_SOC_NOPM, 0, 0,
+                       &twl4030_dapm_predriver_controls[0],
+                       ARRAY_SIZE(twl4030_dapm_predriver_controls)),
        /* HeadsetL/R */
-       SND_SOC_DAPM_MUX_E("HeadsetL Mux", SND_SOC_NOPM, 0, 0,
-               &twl4030_dapm_hsol_control, headsetl_event,
-               SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
-       SND_SOC_DAPM_MUX("HeadsetR Mux", SND_SOC_NOPM, 0, 0,
-               &twl4030_dapm_hsor_control),
+       SND_SOC_DAPM_MIXER("HeadsetL Mixer", SND_SOC_NOPM, 0, 0,
+                       &twl4030_dapm_hsol_controls[0],
+                       ARRAY_SIZE(twl4030_dapm_hsol_controls)),
+       SND_SOC_DAPM_PGA_E("HeadsetL PGA", SND_SOC_NOPM,
+                       0, 0, NULL, 0, headsetlpga_event,
+                       SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+       SND_SOC_DAPM_MIXER("HeadsetR Mixer", SND_SOC_NOPM, 0, 0,
+                       &twl4030_dapm_hsor_controls[0],
+                       ARRAY_SIZE(twl4030_dapm_hsor_controls)),
+       SND_SOC_DAPM_PGA_E("HeadsetR PGA", SND_SOC_NOPM,
+                       0, 0, NULL, 0, headsetrpga_event,
+                       SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
        /* CarkitL/R */
-       SND_SOC_DAPM_MUX("CarkitL Mux", SND_SOC_NOPM, 0, 0,
-               &twl4030_dapm_carkitl_control),
-       SND_SOC_DAPM_MUX("CarkitR Mux", SND_SOC_NOPM, 0, 0,
-               &twl4030_dapm_carkitr_control),
+       SND_SOC_DAPM_MIXER("CarkitL Mixer", SND_SOC_NOPM, 0, 0,
+                       &twl4030_dapm_carkitl_controls[0],
+                       ARRAY_SIZE(twl4030_dapm_carkitl_controls)),
+       SND_SOC_DAPM_MIXER("CarkitR Mixer", SND_SOC_NOPM, 0, 0,
+                       &twl4030_dapm_carkitr_controls[0],
+                       ARRAY_SIZE(twl4030_dapm_carkitr_controls)),
+
+       /* Output MUX controls */
        /* HandsfreeL/R */
-       SND_SOC_DAPM_MUX_E("HandsfreeL Mux", TWL4030_REG_HFL_CTL, 5, 0,
-               &twl4030_dapm_handsfreel_control, handsfree_event,
-               SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
-       SND_SOC_DAPM_MUX_E("HandsfreeR Mux", TWL4030_REG_HFR_CTL, 5, 0,
-               &twl4030_dapm_handsfreer_control, handsfree_event,
-               SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+       SND_SOC_DAPM_MUX("HandsfreeL Mux", SND_SOC_NOPM, 0, 0,
+               &twl4030_dapm_handsfreel_control),
+       SND_SOC_DAPM_SWITCH("HandsfreeL Switch", SND_SOC_NOPM, 0, 0,
+                       &twl4030_dapm_handsfreelmute_control),
+       SND_SOC_DAPM_PGA_E("HandsfreeL PGA", SND_SOC_NOPM,
+                       0, 0, NULL, 0, handsfreelpga_event,
+                       SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+       SND_SOC_DAPM_MUX("HandsfreeR Mux", SND_SOC_NOPM, 5, 0,
+               &twl4030_dapm_handsfreer_control),
+       SND_SOC_DAPM_SWITCH("HandsfreeR Switch", SND_SOC_NOPM, 0, 0,
+                       &twl4030_dapm_handsfreermute_control),
+       SND_SOC_DAPM_PGA_E("HandsfreeR PGA", SND_SOC_NOPM,
+                       0, 0, NULL, 0, handsfreerpga_event,
+                       SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+       /* Vibra */
+       SND_SOC_DAPM_MUX("Vibra Mux", TWL4030_REG_VIBRA_CTL, 0, 0,
+               &twl4030_dapm_vibra_control),
+       SND_SOC_DAPM_MUX("Vibra Route", SND_SOC_NOPM, 0, 0,
+               &twl4030_dapm_vibrapath_control),
 
        /* Introducing four virtual ADC, since TWL4030 have four channel for
           capture */
@@ -1050,11 +1281,15 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
                SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD|
                SND_SOC_DAPM_POST_REG),
 
-       /* Analog input muxes with switch for the capture amplifiers */
-       SND_SOC_DAPM_VALUE_MUX("Analog Left Capture Route",
-               TWL4030_REG_ANAMICL, 4, 0, &twl4030_dapm_analoglmic_control),
-       SND_SOC_DAPM_VALUE_MUX("Analog Right Capture Route",
-               TWL4030_REG_ANAMICR, 4, 0, &twl4030_dapm_analogrmic_control),
+       /* Analog input mixers for the capture amplifiers */
+       SND_SOC_DAPM_MIXER("Analog Left Capture Route",
+               TWL4030_REG_ANAMICL, 4, 0,
+               &twl4030_dapm_analoglmic_controls[0],
+               ARRAY_SIZE(twl4030_dapm_analoglmic_controls)),
+       SND_SOC_DAPM_MIXER("Analog Right Capture Route",
+               TWL4030_REG_ANAMICR, 4, 0,
+               &twl4030_dapm_analogrmic_controls[0],
+               ARRAY_SIZE(twl4030_dapm_analogrmic_controls)),
 
        SND_SOC_DAPM_PGA("ADC Physical Left",
                TWL4030_REG_AVADC_CTL, 3, 0, NULL, 0),
@@ -1073,62 +1308,86 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
 };
 
 static const struct snd_soc_dapm_route intercon[] = {
-       {"Analog L1 Playback Mixer", NULL, "DAC Left1"},
-       {"Analog R1 Playback Mixer", NULL, "DAC Right1"},
-       {"Analog L2 Playback Mixer", NULL, "DAC Left2"},
-       {"Analog R2 Playback Mixer", NULL, "DAC Right2"},
-
-       {"ARXL1_APGA", NULL, "Analog L1 Playback Mixer"},
-       {"ARXR1_APGA", NULL, "Analog R1 Playback Mixer"},
-       {"ARXL2_APGA", NULL, "Analog L2 Playback Mixer"},
-       {"ARXR2_APGA", NULL, "Analog R2 Playback Mixer"},
+       {"Digital L1 Playback Mixer", NULL, "DAC Left1"},
+       {"Digital R1 Playback Mixer", NULL, "DAC Right1"},
+       {"Digital L2 Playback Mixer", NULL, "DAC Left2"},
+       {"Digital R2 Playback Mixer", NULL, "DAC Right2"},
+       {"Digital Voice Playback Mixer", NULL, "DAC Voice"},
+
+       {"Analog L1 Playback Mixer", NULL, "Digital L1 Playback Mixer"},
+       {"Analog R1 Playback Mixer", NULL, "Digital R1 Playback Mixer"},
+       {"Analog L2 Playback Mixer", NULL, "Digital L2 Playback Mixer"},
+       {"Analog R2 Playback Mixer", NULL, "Digital R2 Playback Mixer"},
+       {"Analog Voice Playback Mixer", NULL, "Digital Voice Playback Mixer"},
 
        /* Internal playback routings */
        /* Earpiece */
-       {"Earpiece Mux", "DACL1", "ARXL1_APGA"},
-       {"Earpiece Mux", "DACL2", "ARXL2_APGA"},
-       {"Earpiece Mux", "DACR1", "ARXR1_APGA"},
+       {"Earpiece Mixer", "Voice", "Analog Voice Playback Mixer"},
+       {"Earpiece Mixer", "AudioL1", "Analog L1 Playback Mixer"},
+       {"Earpiece Mixer", "AudioL2", "Analog L2 Playback Mixer"},
+       {"Earpiece Mixer", "AudioR1", "Analog R1 Playback Mixer"},
        /* PreDrivL */
-       {"PredriveL Mux", "DACL1", "ARXL1_APGA"},
-       {"PredriveL Mux", "DACL2", "ARXL2_APGA"},
-       {"PredriveL Mux", "DACR2", "ARXR2_APGA"},
+       {"PredriveL Mixer", "Voice", "Analog Voice Playback Mixer"},
+       {"PredriveL Mixer", "AudioL1", "Analog L1 Playback Mixer"},
+       {"PredriveL Mixer", "AudioL2", "Analog L2 Playback Mixer"},
+       {"PredriveL Mixer", "AudioR2", "Analog R2 Playback Mixer"},
        /* PreDrivR */
-       {"PredriveR Mux", "DACR1", "ARXR1_APGA"},
-       {"PredriveR Mux", "DACR2", "ARXR2_APGA"},
-       {"PredriveR Mux", "DACL2", "ARXL2_APGA"},
+       {"PredriveR Mixer", "Voice", "Analog Voice Playback Mixer"},
+       {"PredriveR Mixer", "AudioR1", "Analog R1 Playback Mixer"},
+       {"PredriveR Mixer", "AudioR2", "Analog R2 Playback Mixer"},
+       {"PredriveR Mixer", "AudioL2", "Analog L2 Playback Mixer"},
        /* HeadsetL */
-       {"HeadsetL Mux", "DACL1", "ARXL1_APGA"},
-       {"HeadsetL Mux", "DACL2", "ARXL2_APGA"},
+       {"HeadsetL Mixer", "Voice", "Analog Voice Playback Mixer"},
+       {"HeadsetL Mixer", "AudioL1", "Analog L1 Playback Mixer"},
+       {"HeadsetL Mixer", "AudioL2", "Analog L2 Playback Mixer"},
+       {"HeadsetL PGA", NULL, "HeadsetL Mixer"},
        /* HeadsetR */
-       {"HeadsetR Mux", "DACR1", "ARXR1_APGA"},
-       {"HeadsetR Mux", "DACR2", "ARXR2_APGA"},
+       {"HeadsetR Mixer", "Voice", "Analog Voice Playback Mixer"},
+       {"HeadsetR Mixer", "AudioR1", "Analog R1 Playback Mixer"},
+       {"HeadsetR Mixer", "AudioR2", "Analog R2 Playback Mixer"},
+       {"HeadsetR PGA", NULL, "HeadsetR Mixer"},
        /* CarkitL */
-       {"CarkitL Mux", "DACL1", "ARXL1_APGA"},
-       {"CarkitL Mux", "DACL2", "ARXL2_APGA"},
+       {"CarkitL Mixer", "Voice", "Analog Voice Playback Mixer"},
+       {"CarkitL Mixer", "AudioL1", "Analog L1 Playback Mixer"},
+       {"CarkitL Mixer", "AudioL2", "Analog L2 Playback Mixer"},
        /* CarkitR */
-       {"CarkitR Mux", "DACR1", "ARXR1_APGA"},
-       {"CarkitR Mux", "DACR2", "ARXR2_APGA"},
+       {"CarkitR Mixer", "Voice", "Analog Voice Playback Mixer"},
+       {"CarkitR Mixer", "AudioR1", "Analog R1 Playback Mixer"},
+       {"CarkitR Mixer", "AudioR2", "Analog R2 Playback Mixer"},
        /* HandsfreeL */
-       {"HandsfreeL Mux", "DACL1", "ARXL1_APGA"},
-       {"HandsfreeL Mux", "DACL2", "ARXL2_APGA"},
-       {"HandsfreeL Mux", "DACR2", "ARXR2_APGA"},
+       {"HandsfreeL Mux", "Voice", "Analog Voice Playback Mixer"},
+       {"HandsfreeL Mux", "AudioL1", "Analog L1 Playback Mixer"},
+       {"HandsfreeL Mux", "AudioL2", "Analog L2 Playback Mixer"},
+       {"HandsfreeL Mux", "AudioR2", "Analog R2 Playback Mixer"},
+       {"HandsfreeL Switch", "Switch", "HandsfreeL Mux"},
+       {"HandsfreeL PGA", NULL, "HandsfreeL Switch"},
        /* HandsfreeR */
-       {"HandsfreeR Mux", "DACR1", "ARXR1_APGA"},
-       {"HandsfreeR Mux", "DACR2", "ARXR2_APGA"},
-       {"HandsfreeR Mux", "DACL2", "ARXL2_APGA"},
+       {"HandsfreeR Mux", "Voice", "Analog Voice Playback Mixer"},
+       {"HandsfreeR Mux", "AudioR1", "Analog R1 Playback Mixer"},
+       {"HandsfreeR Mux", "AudioR2", "Analog R2 Playback Mixer"},
+       {"HandsfreeR Mux", "AudioL2", "Analog L2 Playback Mixer"},
+       {"HandsfreeR Switch", "Switch", "HandsfreeR Mux"},
+       {"HandsfreeR PGA", NULL, "HandsfreeR Switch"},
+       /* Vibra */
+       {"Vibra Mux", "AudioL1", "DAC Left1"},
+       {"Vibra Mux", "AudioR1", "DAC Right1"},
+       {"Vibra Mux", "AudioL2", "DAC Left2"},
+       {"Vibra Mux", "AudioR2", "DAC Right2"},
 
        /* outputs */
-       {"OUTL", NULL, "ARXL2_APGA"},
-       {"OUTR", NULL, "ARXR2_APGA"},
-       {"EARPIECE", NULL, "Earpiece Mux"},
-       {"PREDRIVEL", NULL, "PredriveL Mux"},
-       {"PREDRIVER", NULL, "PredriveR Mux"},
-       {"HSOL", NULL, "HeadsetL Mux"},
-       {"HSOR", NULL, "HeadsetR Mux"},
-       {"CARKITL", NULL, "CarkitL Mux"},
-       {"CARKITR", NULL, "CarkitR Mux"},
-       {"HFL", NULL, "HandsfreeL Mux"},
-       {"HFR", NULL, "HandsfreeR Mux"},
+       {"OUTL", NULL, "Analog L2 Playback Mixer"},
+       {"OUTR", NULL, "Analog R2 Playback Mixer"},
+       {"EARPIECE", NULL, "Earpiece Mixer"},
+       {"PREDRIVEL", NULL, "PredriveL Mixer"},
+       {"PREDRIVER", NULL, "PredriveR Mixer"},
+       {"HSOL", NULL, "HeadsetL PGA"},
+       {"HSOR", NULL, "HeadsetR PGA"},
+       {"CARKITL", NULL, "CarkitL Mixer"},
+       {"CARKITR", NULL, "CarkitR Mixer"},
+       {"HFL", NULL, "HandsfreeL PGA"},
+       {"HFR", NULL, "HandsfreeR PGA"},
+       {"Vibra Route", "Audio", "Vibra Mux"},
+       {"VIBRA", NULL, "Vibra Route"},
 
        /* Capture path */
        {"Analog Left Capture Route", "Main mic", "MAINMIC"},
@@ -1168,18 +1427,22 @@ static const struct snd_soc_dapm_route intercon[] = {
        {"Left1 Analog Loopback", "Switch", "Analog Left Capture Route"},
        {"Right2 Analog Loopback", "Switch", "Analog Right Capture Route"},
        {"Left2 Analog Loopback", "Switch", "Analog Left Capture Route"},
+       {"Voice Analog Loopback", "Switch", "Analog Left Capture Route"},
 
        {"Analog R1 Playback Mixer", NULL, "Right1 Analog Loopback"},
        {"Analog L1 Playback Mixer", NULL, "Left1 Analog Loopback"},
        {"Analog R2 Playback Mixer", NULL, "Right2 Analog Loopback"},
        {"Analog L2 Playback Mixer", NULL, "Left2 Analog Loopback"},
+       {"Analog Voice Playback Mixer", NULL, "Voice Analog Loopback"},
 
        /* Digital bypass routes */
        {"Right Digital Loopback", "Volume", "TX1 Capture Route"},
        {"Left Digital Loopback", "Volume", "TX1 Capture Route"},
+       {"Voice Digital Loopback", "Volume", "TX2 Capture Route"},
 
-       {"Analog R2 Playback Mixer", NULL, "Right Digital Loopback"},
-       {"Analog L2 Playback Mixer", NULL, "Left Digital Loopback"},
+       {"Digital R2 Playback Mixer", NULL, "Right Digital Loopback"},
+       {"Digital L2 Playback Mixer", NULL, "Left Digital Loopback"},
+       {"Digital Voice Playback Mixer", NULL, "Voice Digital Loopback"},
 
 };
 
@@ -1226,6 +1489,58 @@ static int twl4030_set_bias_level(struct snd_soc_codec *codec,
        return 0;
 }
 
+static void twl4030_constraints(struct twl4030_priv *twl4030,
+                               struct snd_pcm_substream *mst_substream)
+{
+       struct snd_pcm_substream *slv_substream;
+
+       /* Pick the stream, which need to be constrained */
+       if (mst_substream == twl4030->master_substream)
+               slv_substream = twl4030->slave_substream;
+       else if (mst_substream == twl4030->slave_substream)
+               slv_substream = twl4030->master_substream;
+       else /* This should not happen.. */
+               return;
+
+       /* Set the constraints according to the already configured stream */
+       snd_pcm_hw_constraint_minmax(slv_substream->runtime,
+                               SNDRV_PCM_HW_PARAM_RATE,
+                               twl4030->rate,
+                               twl4030->rate);
+
+       snd_pcm_hw_constraint_minmax(slv_substream->runtime,
+                               SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+                               twl4030->sample_bits,
+                               twl4030->sample_bits);
+
+       snd_pcm_hw_constraint_minmax(slv_substream->runtime,
+                               SNDRV_PCM_HW_PARAM_CHANNELS,
+                               twl4030->channels,
+                               twl4030->channels);
+}
+
+/* In case of 4 channel mode, the RX1 L/R for playback and the TX2 L/R for
+ * capture has to be enabled/disabled. */
+static void twl4030_tdm_enable(struct snd_soc_codec *codec, int direction,
+                               int enable)
+{
+       u8 reg, mask;
+
+       reg = twl4030_read_reg_cache(codec, TWL4030_REG_OPTION);
+
+       if (direction == SNDRV_PCM_STREAM_PLAYBACK)
+               mask = TWL4030_ARXL1_VRX_EN | TWL4030_ARXR1_EN;
+       else
+               mask = TWL4030_ATXL2_VTXL_EN | TWL4030_ATXR2_VTXR_EN;
+
+       if (enable)
+               reg |= mask;
+       else
+               reg &= ~mask;
+
+       twl4030_write(codec, TWL4030_REG_OPTION, reg);
+}
+
 static int twl4030_startup(struct snd_pcm_substream *substream,
                           struct snd_soc_dai *dai)
 {
@@ -1234,26 +1549,25 @@ static int twl4030_startup(struct snd_pcm_substream *substream,
        struct snd_soc_codec *codec = socdev->card->codec;
        struct twl4030_priv *twl4030 = codec->private_data;
 
-       /* If we already have a playback or capture going then constrain
-        * this substream to match it.
-        */
        if (twl4030->master_substream) {
-               struct snd_pcm_runtime *master_runtime;
-               master_runtime = twl4030->master_substream->runtime;
-
-               snd_pcm_hw_constraint_minmax(substream->runtime,
-                                            SNDRV_PCM_HW_PARAM_RATE,
-                                            master_runtime->rate,
-                                            master_runtime->rate);
-
-               snd_pcm_hw_constraint_minmax(substream->runtime,
-                                            SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
-                                            master_runtime->sample_bits,
-                                            master_runtime->sample_bits);
-
                twl4030->slave_substream = substream;
-       } else
+               /* The DAI has one configuration for playback and capture, so
+                * if the DAI has been already configured then constrain this
+                * substream to match it. */
+               if (twl4030->configured)
+                       twl4030_constraints(twl4030, twl4030->master_substream);
+       } else {
+               if (!(twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE) &
+                       TWL4030_OPTION_1)) {
+                       /* In option2 4 channel is not supported, set the
+                        * constraint for the first stream for channels, the
+                        * second stream will 'inherit' this cosntraint */
+                       snd_pcm_hw_constraint_minmax(substream->runtime,
+                                               SNDRV_PCM_HW_PARAM_CHANNELS,
+                                               2, 2);
+               }
                twl4030->master_substream = substream;
+       }
 
        return 0;
 }
@@ -1270,6 +1584,17 @@ static void twl4030_shutdown(struct snd_pcm_substream *substream,
                twl4030->master_substream = twl4030->slave_substream;
 
        twl4030->slave_substream = NULL;
+
+       /* If all streams are closed, or the remaining stream has not yet
+        * been configured than set the DAI as not configured. */
+       if (!twl4030->master_substream)
+               twl4030->configured = 0;
+        else if (!twl4030->master_substream->runtime->channels)
+               twl4030->configured = 0;
+
+        /* If the closing substream had 4 channel, do the necessary cleanup */
+       if (substream->runtime->channels == 4)
+               twl4030_tdm_enable(codec, substream->stream, 0);
 }
 
 static int twl4030_hw_params(struct snd_pcm_substream *substream,
@@ -1282,8 +1607,24 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream,
        struct twl4030_priv *twl4030 = codec->private_data;
        u8 mode, old_mode, format, old_format;
 
-       if (substream == twl4030->slave_substream)
-               /* Ignoring hw_params for slave substream */
+        /* If the substream has 4 channel, do the necessary setup */
+       if (params_channels(params) == 4) {
+               u8 format, mode;
+
+               format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF);
+               mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE);
+
+               /* Safety check: are we in the correct operating mode and
+                * the interface is in TDM mode? */
+               if ((mode & TWL4030_OPTION_1) &&
+                   ((format & TWL4030_AIF_FORMAT) == TWL4030_AIF_FORMAT_TDM))
+                       twl4030_tdm_enable(codec, substream->stream, 1);
+               else
+                       return -EINVAL;
+       }
+
+       if (twl4030->configured)
+               /* Ignoring hw_params for already configured DAI */
                return 0;
 
        /* bit rate */
@@ -1363,6 +1704,21 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream,
                /* set CODECPDZ afterwards */
                twl4030_codec_enable(codec, 1);
        }
+
+       /* Store the important parameters for the DAI configuration and set
+        * the DAI as configured */
+       twl4030->configured = 1;
+       twl4030->rate = params_rate(params);
+       twl4030->sample_bits = hw_param_interval(params,
+                                       SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min;
+       twl4030->channels = params_channels(params);
+
+       /* If both playback and capture streams are open, and one of them
+        * is setting the hw parameters right now (since we are here), set
+        * constraints to the other stream to match the current one. */
+       if (twl4030->slave_substream)
+               twl4030_constraints(twl4030, substream);
+
        return 0;
 }
 
@@ -1370,17 +1726,21 @@ static int twl4030_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 twl4030_priv *twl4030 = codec->private_data;
        u8 infreq;
 
        switch (freq) {
        case 19200000:
                infreq = TWL4030_APLL_INFREQ_19200KHZ;
+               twl4030->sysclk = 19200;
                break;
        case 26000000:
                infreq = TWL4030_APLL_INFREQ_26000KHZ;
+               twl4030->sysclk = 26000;
                break;
        case 38400000:
                infreq = TWL4030_APLL_INFREQ_38400KHZ;
+               twl4030->sysclk = 38400;
                break;
        default:
                printk(KERN_ERR "TWL4030 set sysclk: unknown rate %d\n",
@@ -1424,6 +1784,9 @@ static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai,
        case SND_SOC_DAIFMT_I2S:
                format |= TWL4030_AIF_FORMAT_CODEC;
                break;
+       case SND_SOC_DAIFMT_DSP_A:
+               format |= TWL4030_AIF_FORMAT_TDM;
+               break;
        default:
                return -EINVAL;
        }
@@ -1443,6 +1806,180 @@ static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai,
        return 0;
 }
 
+/* In case of voice mode, the RX1 L(VRX) for downlink and the TX2 L/R
+ * (VTXL, VTXR) for uplink has to be enabled/disabled. */
+static void twl4030_voice_enable(struct snd_soc_codec *codec, int direction,
+                               int enable)
+{
+       u8 reg, mask;
+
+       reg = twl4030_read_reg_cache(codec, TWL4030_REG_OPTION);
+
+       if (direction == SNDRV_PCM_STREAM_PLAYBACK)
+               mask = TWL4030_ARXL1_VRX_EN;
+       else
+               mask = TWL4030_ATXL2_VTXL_EN | TWL4030_ATXR2_VTXR_EN;
+
+       if (enable)
+               reg |= mask;
+       else
+               reg &= ~mask;
+
+       twl4030_write(codec, TWL4030_REG_OPTION, reg);
+}
+
+static int twl4030_voice_startup(struct snd_pcm_substream *substream,
+               struct snd_soc_dai *dai)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_device *socdev = rtd->socdev;
+       struct snd_soc_codec *codec = socdev->card->codec;
+       u8 infreq;
+       u8 mode;
+
+       /* If the system master clock is not 26MHz, the voice PCM interface is
+        * not avilable.
+        */
+       infreq = twl4030_read_reg_cache(codec, TWL4030_REG_APLL_CTL)
+               & TWL4030_APLL_INFREQ;
+
+       if (infreq != TWL4030_APLL_INFREQ_26000KHZ) {
+               printk(KERN_ERR "TWL4030 voice startup: "
+                       "MCLK is not 26MHz, call set_sysclk() on init\n");
+               return -EINVAL;
+       }
+
+       /* If the codec mode is not option2, the voice PCM interface is not
+        * avilable.
+        */
+       mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE)
+               & TWL4030_OPT_MODE;
+
+       if (mode != TWL4030_OPTION_2) {
+               printk(KERN_ERR "TWL4030 voice startup: "
+                       "the codec mode is not option2\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static void twl4030_voice_shutdown(struct snd_pcm_substream *substream,
+                               struct snd_soc_dai *dai)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_device *socdev = rtd->socdev;
+       struct snd_soc_codec *codec = socdev->card->codec;
+
+       /* Enable voice digital filters */
+       twl4030_voice_enable(codec, substream->stream, 0);
+}
+
+static int twl4030_voice_hw_params(struct snd_pcm_substream *substream,
+               struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_device *socdev = rtd->socdev;
+       struct snd_soc_codec *codec = socdev->card->codec;
+       u8 old_mode, mode;
+
+       /* Enable voice digital filters */
+       twl4030_voice_enable(codec, substream->stream, 1);
+
+       /* bit rate */
+       old_mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE)
+               & ~(TWL4030_CODECPDZ);
+       mode = old_mode;
+
+       switch (params_rate(params)) {
+       case 8000:
+               mode &= ~(TWL4030_SEL_16K);
+               break;
+       case 16000:
+               mode |= TWL4030_SEL_16K;
+               break;
+       default:
+               printk(KERN_ERR "TWL4030 voice hw params: unknown rate %d\n",
+                       params_rate(params));
+               return -EINVAL;
+       }
+
+       if (mode != old_mode) {
+               /* change rate and set CODECPDZ */
+               twl4030_codec_enable(codec, 0);
+               twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode);
+               twl4030_codec_enable(codec, 1);
+       }
+
+       return 0;
+}
+
+static int twl4030_voice_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;
+       u8 infreq;
+
+       switch (freq) {
+       case 26000000:
+               infreq = TWL4030_APLL_INFREQ_26000KHZ;
+               break;
+       default:
+               printk(KERN_ERR "TWL4030 voice set sysclk: unknown rate %d\n",
+                       freq);
+               return -EINVAL;
+       }
+
+       infreq |= TWL4030_APLL_EN;
+       twl4030_write(codec, TWL4030_REG_APLL_CTL, infreq);
+
+       return 0;
+}
+
+static int twl4030_voice_set_dai_fmt(struct snd_soc_dai *codec_dai,
+               unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       u8 old_format, format;
+
+       /* get format */
+       old_format = twl4030_read_reg_cache(codec, TWL4030_REG_VOICE_IF);
+       format = old_format;
+
+       /* set master/slave audio interface */
+       switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+       case SND_SOC_DAIFMT_CBS_CFM:
+               format &= ~(TWL4030_VIF_SLAVE_EN);
+               break;
+       case SND_SOC_DAIFMT_CBS_CFS:
+               format |= TWL4030_VIF_SLAVE_EN;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       /* clock inversion */
+       switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+       case SND_SOC_DAIFMT_IB_NF:
+               format &= ~(TWL4030_VIF_FORMAT);
+               break;
+       case SND_SOC_DAIFMT_NB_IF:
+               format |= TWL4030_VIF_FORMAT;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (format != old_format) {
+               /* change format and set CODECPDZ */
+               twl4030_codec_enable(codec, 0);
+               twl4030_write(codec, TWL4030_REG_VOICE_IF, format);
+               twl4030_codec_enable(codec, 1);
+       }
+
+       return 0;
+}
+
 #define TWL4030_RATES   (SNDRV_PCM_RATE_8000_48000)
 #define TWL4030_FORMATS         (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE)
 
@@ -1454,21 +1991,47 @@ static struct snd_soc_dai_ops twl4030_dai_ops = {
        .set_fmt        = twl4030_set_dai_fmt,
 };
 
-struct snd_soc_dai twl4030_dai = {
+static struct snd_soc_dai_ops twl4030_dai_voice_ops = {
+       .startup        = twl4030_voice_startup,
+       .shutdown       = twl4030_voice_shutdown,
+       .hw_params      = twl4030_voice_hw_params,
+       .set_sysclk     = twl4030_voice_set_dai_sysclk,
+       .set_fmt        = twl4030_voice_set_dai_fmt,
+};
+
+struct snd_soc_dai twl4030_dai[] = {
+{
        .name = "twl4030",
        .playback = {
-               .stream_name = "Playback",
+               .stream_name = "HiFi Playback",
                .channels_min = 2,
-               .channels_max = 2,
+               .channels_max = 4,
                .rates = TWL4030_RATES | SNDRV_PCM_RATE_96000,
                .formats = TWL4030_FORMATS,},
        .capture = {
                .stream_name = "Capture",
                .channels_min = 2,
-               .channels_max = 2,
+               .channels_max = 4,
                .rates = TWL4030_RATES,
                .formats = TWL4030_FORMATS,},
        .ops = &twl4030_dai_ops,
+},
+{
+       .name = "twl4030 Voice",
+       .playback = {
+               .stream_name = "Voice Playback",
+               .channels_min = 1,
+               .channels_max = 1,
+               .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+               .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+       .capture = {
+               .stream_name = "Capture",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+               .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+       .ops = &twl4030_dai_voice_ops,
+},
 };
 EXPORT_SYMBOL_GPL(twl4030_dai);
 
@@ -1500,6 +2063,8 @@ static int twl4030_resume(struct platform_device *pdev)
 static int twl4030_init(struct snd_soc_device *socdev)
 {
        struct snd_soc_codec *codec = socdev->card->codec;
+       struct twl4030_setup_data *setup = socdev->codec_data;
+       struct twl4030_priv *twl4030 = codec->private_data;
        int ret = 0;
 
        printk(KERN_INFO "TWL4030 Audio Codec init \n");
@@ -1509,14 +2074,31 @@ static int twl4030_init(struct snd_soc_device *socdev)
        codec->read = twl4030_read_reg_cache;
        codec->write = twl4030_write;
        codec->set_bias_level = twl4030_set_bias_level;
-       codec->dai = &twl4030_dai;
-       codec->num_dai = 1;
+       codec->dai = twl4030_dai;
+       codec->num_dai = ARRAY_SIZE(twl4030_dai),
        codec->reg_cache_size = sizeof(twl4030_reg);
        codec->reg_cache = kmemdup(twl4030_reg, sizeof(twl4030_reg),
                                        GFP_KERNEL);
        if (codec->reg_cache == NULL)
                return -ENOMEM;
 
+       /* Configuration for headset ramp delay from setup data */
+       if (setup) {
+               unsigned char hs_pop;
+
+               if (setup->sysclk)
+                       twl4030->sysclk = setup->sysclk;
+               else
+                       twl4030->sysclk = 26000;
+
+               hs_pop = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET);
+               hs_pop &= ~TWL4030_RAMP_DELAY;
+               hs_pop |= (setup->ramp_delay_value << 2);
+               twl4030_write_reg_cache(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+       } else {
+               twl4030->sysclk = 26000;
+       }
+
        /* register pcms */
        ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
        if (ret < 0) {
@@ -1604,13 +2186,13 @@ EXPORT_SYMBOL_GPL(soc_codec_dev_twl4030);
 
 static int __init twl4030_modinit(void)
 {
-       return snd_soc_register_dai(&twl4030_dai);
+       return snd_soc_register_dais(&twl4030_dai[0], ARRAY_SIZE(twl4030_dai));
 }
 module_init(twl4030_modinit);
 
 static void __exit twl4030_exit(void)
 {
-       snd_soc_unregister_dai(&twl4030_dai);
+       snd_soc_unregister_dais(&twl4030_dai[0], ARRAY_SIZE(twl4030_dai));
 }
 module_exit(twl4030_exit);
 
index cb63765db1df13a898b6c4380d7afdbf3c50af79..fe5f395d9e4fe8bdcce9b4ca29c82aceecb57a7c 100644 (file)
@@ -92,8 +92,9 @@
 #define TWL4030_REG_VIBRA_PWM_SET      0x47
 #define TWL4030_REG_ANAMIC_GAIN                0x48
 #define TWL4030_REG_MISC_SET_2         0x49
+#define TWL4030_REG_SW_SHADOW          0x4A
 
-#define TWL4030_CACHEREGNUM    (TWL4030_REG_MISC_SET_2 + 1)
+#define TWL4030_CACHEREGNUM    (TWL4030_REG_SW_SHADOW + 1)
 
 /* Bitfield Definitions */
 
 #define TWL4030_APLL_RATE_44100                0x90
 #define TWL4030_APLL_RATE_48000                0xA0
 #define TWL4030_APLL_RATE_96000                0xE0
-#define TWL4030_SEL_16K                        0x04
+#define TWL4030_SEL_16K                        0x08
 #define TWL4030_CODECPDZ               0x02
 #define TWL4030_OPT_MODE               0x01
+#define TWL4030_OPTION_1               (1 << 0)
+#define TWL4030_OPTION_2               (0 << 0)
+
+/* TWL4030_OPTION (0x02) Fields */
+
+#define TWL4030_ATXL1_EN               (1 << 0)
+#define TWL4030_ATXR1_EN               (1 << 1)
+#define TWL4030_ATXL2_VTXL_EN          (1 << 2)
+#define TWL4030_ATXR2_VTXR_EN          (1 << 3)
+#define TWL4030_ARXL1_VRX_EN           (1 << 4)
+#define TWL4030_ARXR1_EN               (1 << 5)
+#define TWL4030_ARXL2_EN               (1 << 6)
+#define TWL4030_ARXR2_EN               (1 << 7)
 
 /* TWL4030_REG_MICBIAS_CTL (0x04) Fields */
 
 #define TWL4030_CLK256FS_EN            0x02
 #define TWL4030_AIF_EN                 0x01
 
+/* VOICE_IF (0x0F) Fields */
+
+#define TWL4030_VIF_SLAVE_EN           0x80
+#define TWL4030_VIF_DIN_EN             0x40
+#define TWL4030_VIF_DOUT_EN            0x20
+#define TWL4030_VIF_SWAP               0x10
+#define TWL4030_VIF_FORMAT             0x08
+#define TWL4030_VIF_TRI_EN             0x04
+#define TWL4030_VIF_SUB_EN             0x02
+#define TWL4030_VIF_EN                 0x01
+
 /* EAR_CTL (0x21) */
 #define TWL4030_EAR_GAIN               0x30
 
 #define TWL4030_SMOOTH_ANAVOL_EN       0x02
 #define TWL4030_DIGMIC_LR_SWAP_EN      0x01
 
-extern struct snd_soc_dai twl4030_dai;
+/* TWL4030_REG_SW_SHADOW (0x4A) Fields */
+#define TWL4030_HFL_EN                 0x01
+#define TWL4030_HFR_EN                 0x02
+
+#define TWL4030_DAI_HIFI               0
+#define TWL4030_DAI_VOICE              1
+
+extern struct snd_soc_dai twl4030_dai[2];
 extern struct snd_soc_codec_device soc_codec_dev_twl4030;
 
+struct twl4030_setup_data {
+       unsigned int ramp_delay_value;
+       unsigned int sysclk;
+};
+
 #endif /* End of __TWL4030_AUDIO_H__ */
index ddefb8f80145bb1830253cde7277f3bf6ae50818..269b108e1de612f6aaff359b09b59be3f904abaa 100644 (file)
@@ -101,7 +101,7 @@ static int uda134x_write(struct snd_soc_codec *codec, unsigned int reg,
        pr_debug("%s reg: %02X, value:%02X\n", __func__, reg, value);
 
        if (reg >= UDA134X_REGS_NUM) {
-               printk(KERN_ERR "%s unkown register: reg: %d",
+               printk(KERN_ERR "%s unkown register: reg: %u",
                       __func__, reg);
                return -EINVAL;
        }
@@ -296,7 +296,7 @@ static int uda134x_set_dai_sysclk(struct snd_soc_dai *codec_dai,
        struct snd_soc_codec *codec = codec_dai->codec;
        struct uda134x_priv *uda134x = codec->private_data;
 
-       pr_debug("%s clk_id: %d, freq: %d, dir: %d\n", __func__,
+       pr_debug("%s clk_id: %d, freq: %u, dir: %d\n", __func__,
                 clk_id, freq, dir);
 
        /* Anything between 256fs*8Khz and 512fs*48Khz should be acceptable
index 0275321ff8ab29840c994bb107c3b54d8529f1aa..e7348d341b761d5870889a97f814f8f5a927b0cd 100644 (file)
@@ -1108,7 +1108,7 @@ static int wm8350_set_fll(struct snd_soc_dai *codec_dai,
        if (ret < 0)
                return ret;
        dev_dbg(wm8350->dev,
-               "FLL in %d FLL out %d N 0x%x K 0x%x div %d ratio %d",
+               "FLL in %u FLL out %u N 0x%x K 0x%x div %d ratio %d",
                freq_in, freq_out, fll_div.n, fll_div.k, fll_div.div,
                fll_div.ratio);
 
index d11bd9288cf9170761e735e2ff4094c52353a437..d088eb4b88bb85ca2ee2d260dc46003ea9e5241e 100644 (file)
@@ -13,6 +13,7 @@
 #define _WM8350_H
 
 #include <sound/soc.h>
+#include <linux/mfd/wm8350/audio.h>
 
 extern struct snd_soc_dai wm8350_dai;
 extern struct snd_soc_codec_device soc_codec_dev_wm8350;
index 510efa6040088a03cc30f39ac8bfd015f8ba74f9..502eefac1ecd797dd02ff8382e31e19f8d8b972b 100644 (file)
@@ -954,7 +954,7 @@ static int fll_factors(struct wm8400_priv *wm8400, struct fll_factors *factors,
                factors->outdiv *= 2;
                if (factors->outdiv > 32) {
                        dev_err(wm8400->wm8400->dev,
-                               "Unsupported FLL output frequency %dHz\n",
+                               "Unsupported FLL output frequency %uHz\n",
                                Fout);
                        return -EINVAL;
                }
@@ -1003,7 +1003,7 @@ static int fll_factors(struct wm8400_priv *wm8400, struct fll_factors *factors,
        factors->k = K / 10;
 
        dev_dbg(wm8400->wm8400->dev,
-               "FLL: Fref=%d Fout=%d N=%x K=%x, FRATIO=%x OUTDIV=%x\n",
+               "FLL: Fref=%u Fout=%u N=%x K=%x, FRATIO=%x OUTDIV=%x\n",
                Fref, Fout,
                factors->n, factors->k, factors->fratio, factors->outdiv);
 
@@ -1473,8 +1473,8 @@ static int wm8400_codec_probe(struct platform_device *dev)
 
        codec = &priv->codec;
        codec->private_data = priv;
-       codec->control_data = dev->dev.driver_data;
-       priv->wm8400 = dev->dev.driver_data;
+       codec->control_data = dev_get_drvdata(&dev->dev);
+       priv->wm8400 = dev_get_drvdata(&dev->dev);
 
        ret = regulator_bulk_get(priv->wm8400->dev,
                                 ARRAY_SIZE(power), &power[0]);
index 6a4cea09c45dbe624ba2941442c68e3fdb5971c2..c8b8dba858907d0ec7d9bc3f64b2a19cc6b876fc 100644 (file)
@@ -298,7 +298,7 @@ static void pll_factors(unsigned int target, unsigned int source)
 
        if ((Ndiv < 6) || (Ndiv > 12))
                printk(KERN_WARNING
-                       "WM8510 N value %d outwith recommended range!d\n",
+                       "WM8510 N value %u outwith recommended range!d\n",
                        Ndiv);
 
        pll_div.n = Ndiv;
index 9f6be3d31ac0a8268473463c39ac6c3066b01b75..86c4b24db8172aa610d283120476440587035c3d 100644 (file)
@@ -415,7 +415,7 @@ static int pll_factors(struct _pll_div *pll_div, unsigned int target,
        unsigned int K, Ndiv, Nmod;
        int i;
 
-       pr_debug("wm8580: PLL %dHz->%dHz\n", source, target);
+       pr_debug("wm8580: PLL %uHz->%uHz\n", source, target);
 
        /* Scale the output frequency up; the PLL should run in the
         * region of 90-100MHz.
@@ -447,7 +447,7 @@ static int pll_factors(struct _pll_div *pll_div, unsigned int target,
 
        if ((Ndiv < 5) || (Ndiv > 13)) {
                printk(KERN_ERR
-                       "WM8580 N=%d outside supported range\n", Ndiv);
+                       "WM8580 N=%u outside supported range\n", Ndiv);
                return -EINVAL;
        }
 
index e043e3f60008b46579e4ae97e411a0df1790e15b..7a205876ef4f0dc4e0982855694c62f2ea5e4def 100644 (file)
@@ -666,14 +666,14 @@ static int __devinit wm8731_spi_probe(struct spi_device *spi)
        codec->hw_write = (hw_write_t)wm8731_spi_write;
        codec->dev = &spi->dev;
 
-       spi->dev.driver_data = wm8731;
+       dev_set_drvdata(&spi->dev, wm8731);
 
        return wm8731_register(wm8731);
 }
 
 static int __devexit wm8731_spi_remove(struct spi_device *spi)
 {
-       struct wm8731_priv *wm8731 = spi->dev.driver_data;
+       struct wm8731_priv *wm8731 = dev_get_drvdata(&spi->dev);
 
        wm8731_unregister(wm8731);
 
index a6e8f3f7f052ee4dc45b8ef4540f4cf31c9f411e..d28eeaceb8573a504f3c7c5471f9f2f296c5618c 100644 (file)
@@ -703,7 +703,7 @@ static void pll_factors(struct _pll_div *pll_div, unsigned int target,
 
        if ((Ndiv < 6) || (Ndiv > 12))
                printk(KERN_WARNING
-                       "wm8753: unsupported N = %d\n", Ndiv);
+                       "wm8753: unsupported N = %u\n", Ndiv);
 
        pll_div->n = Ndiv;
        Nmod = target % source;
@@ -1822,14 +1822,14 @@ static int __devinit wm8753_spi_probe(struct spi_device *spi)
        codec->hw_write = (hw_write_t)wm8753_spi_write;
        codec->dev = &spi->dev;
 
-       spi->dev.driver_data = wm8753;
+       dev_set_drvdata(&spi->dev, wm8753);
 
        return wm8753_register(wm8753);
 }
 
 static int __devexit wm8753_spi_remove(struct spi_device *spi)
 {
-       struct wm8753_priv *wm8753 = spi->dev.driver_data;
+       struct wm8753_priv *wm8753 = dev_get_drvdata(&spi->dev);
        wm8753_unregister(wm8753);
        return 0;
 }
index 46c5ea1ff921aea8c3f6be8fd13ed7bd8de1f897..3c78945244b8df516f12f0181f0194a4b1a1b949 100644 (file)
@@ -778,11 +778,11 @@ static int fll_factors(struct _fll_div *fll_div, unsigned int Fref,
        }
 
        if (target > 100000000)
-               printk(KERN_WARNING "wm8900: FLL rate %d out of range, Fref=%d"
-                      " Fout=%d\n", target, Fref, Fout);
+               printk(KERN_WARNING "wm8900: FLL rate %u out of range, Fref=%u"
+                      " Fout=%u\n", target, Fref, Fout);
        if (div > 32) {
                printk(KERN_ERR "wm8900: Invalid FLL division rate %u, "
-                      "Fref=%d, Fout=%d, target=%d\n",
+                      "Fref=%u, Fout=%u, target=%u\n",
                       div, Fref, Fout, target);
                return -EINVAL;
        }
index 8cf571f1a803d3ae7b1b1e561aedb01470cc19dd..d8a9222fbf745c53077bd3a5f12e89c3f70bded0 100644 (file)
@@ -217,7 +217,6 @@ struct wm8903_priv {
        int sysclk;
 
        /* Reference counts */
-       int charge_pump_users;
        int class_w_users;
        int playback_active;
        int capture_active;
@@ -373,6 +372,15 @@ static void wm8903_reset(struct snd_soc_codec *codec)
 #define WM8903_OUTPUT_INT   0x2
 #define WM8903_OUTPUT_IN    0x1
 
+static int wm8903_cp_event(struct snd_soc_dapm_widget *w,
+                          struct snd_kcontrol *kcontrol, int event)
+{
+       WARN_ON(event != SND_SOC_DAPM_POST_PMU);
+       mdelay(4);
+
+       return 0;
+}
+
 /*
  * Event for headphone and line out amplifier power changes.  Special
  * power up/down sequences are required in order to maximise pop/click
@@ -382,19 +390,20 @@ 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;
+       u16 dcs_reg;
+       u16 dcs_bit;
        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;
+               dcs_bit = 0 + w->shift;
                break;
        case WM8903_POWER_MANAGEMENT_3:
                reg = WM8903_ANALOGUE_LINEOUT_0;
+               dcs_bit = 2 + w->shift;
                break;
        default:
                BUG();
@@ -419,18 +428,6 @@ static int wm8903_output_event(struct snd_soc_dapm_widget *w,
                /* 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) {
@@ -446,6 +443,11 @@ static int wm8903_output_event(struct snd_soc_dapm_widget *w,
                val |= (WM8903_OUTPUT_OUT << shift);
                wm8903_write(codec, reg, val);
 
+               /* Enable the DC servo */
+               dcs_reg = wm8903_read(codec, WM8903_DC_SERVO_0);
+               dcs_reg |= dcs_bit;
+               wm8903_write(codec, WM8903_DC_SERVO_0, dcs_reg);
+
                /* Remove the short */
                val |= (WM8903_OUTPUT_SHORT << shift);
                wm8903_write(codec, reg, val);
@@ -458,25 +460,17 @@ static int wm8903_output_event(struct snd_soc_dapm_widget *w,
                val &= ~(WM8903_OUTPUT_SHORT << shift);
                wm8903_write(codec, reg, val);
 
+               /* Disable the DC servo */
+               dcs_reg = wm8903_read(codec, WM8903_DC_SERVO_0);
+               dcs_reg &= ~dcs_bit;
+               wm8903_write(codec, WM8903_DC_SERVO_0, dcs_reg);
+
                /* 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;
 }
 
@@ -539,6 +533,7 @@ static int wm8903_class_w_put(struct snd_kcontrol *kcontrol,
 /* ALSA can only do steps of .01dB */
 static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1);
 
+static const DECLARE_TLV_DB_SCALE(digital_sidetone_tlv, -3600, 300, 0);
 static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0);
 
 static const DECLARE_TLV_DB_SCALE(drc_tlv_thresh, 0, 75, 0);
@@ -657,6 +652,16 @@ static const struct soc_enum rinput_inv_enum =
        SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 4, 3, rinput_mux_text);
 
 
+static const char *sidetone_text[] = {
+       "None", "Left", "Right"
+};
+
+static const struct soc_enum lsidetone_enum =
+       SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_0, 2, 3, sidetone_text);
+
+static const struct soc_enum rsidetone_enum =
+       SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_0, 0, 3, sidetone_text);
+
 static const struct snd_kcontrol_new wm8903_snd_controls[] = {
 
 /* Input PGAs - No TLV since the scale depends on PGA mode */
@@ -700,6 +705,9 @@ SOC_DOUBLE_R_TLV("Digital Capture Volume", WM8903_ADC_DIGITAL_VOLUME_LEFT,
 SOC_ENUM("ADC Companding Mode", adc_companding),
 SOC_SINGLE("ADC Companding Switch", WM8903_AUDIO_INTERFACE_0, 3, 1, 0),
 
+SOC_DOUBLE_TLV("Digital Sidetone Volume", WM8903_DAC_DIGITAL_0, 4, 8,
+              12, 0, digital_sidetone_tlv),
+
 /* DAC */
 SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8903_DAC_DIGITAL_VOLUME_LEFT,
                 WM8903_DAC_DIGITAL_VOLUME_RIGHT, 1, 120, 0, digital_tlv),
@@ -762,6 +770,12 @@ static const struct snd_kcontrol_new rinput_mux =
 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 lsidetone_mux =
+       SOC_DAPM_ENUM("DACL Sidetone Mux", lsidetone_enum);
+
+static const struct snd_kcontrol_new rsidetone_mux =
+       SOC_DAPM_ENUM("DACR Sidetone Mux", rsidetone_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),
@@ -828,6 +842,9 @@ 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_MUX("DACL Sidetone", SND_SOC_NOPM, 0, 0, &lsidetone_mux),
+SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &rsidetone_mux),
+
 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),
 
@@ -844,26 +861,29 @@ SND_SOC_DAPM_MIXER("Right Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 0, 0,
 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_PRE_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_PRE_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_PRE_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_PRE_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),
 
+SND_SOC_DAPM_SUPPLY("Charge Pump", WM8903_CHARGE_PUMP_0, 0, 0,
+                   wm8903_cp_event, SND_SOC_DAPM_POST_PMU),
+SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8903_CLOCK_RATES_2, 1, 0, NULL, 0),
 };
 
 static const struct snd_soc_dapm_route intercon[] = {
@@ -909,7 +929,19 @@ static const struct snd_soc_dapm_route intercon[] = {
        { "Right Input PGA", NULL, "Right Input Mode Mux" },
 
        { "ADCL", NULL, "Left Input PGA" },
+       { "ADCL", NULL, "CLK_DSP" },
        { "ADCR", NULL, "Right Input PGA" },
+       { "ADCR", NULL, "CLK_DSP" },
+
+       { "DACL Sidetone", "Left", "ADCL" },
+       { "DACL Sidetone", "Right", "ADCR" },
+       { "DACR Sidetone", "Left", "ADCL" },
+       { "DACR Sidetone", "Right", "ADCR" },
+
+       { "DACL", NULL, "DACL Sidetone" },
+       { "DACL", NULL, "CLK_DSP" },
+       { "DACR", NULL, "DACR Sidetone" },
+       { "DACR", NULL, "CLK_DSP" },
 
        { "Left Output Mixer", "Left Bypass Switch", "Left Input PGA" },
        { "Left Output Mixer", "Right Bypass Switch", "Right Input PGA" },
@@ -951,6 +983,11 @@ static const struct snd_soc_dapm_route intercon[] = {
 
        { "ROP", NULL, "Right Speaker PGA" },
        { "RON", NULL, "Right Speaker PGA" },
+
+       { "Left Headphone Output PGA", NULL, "Charge Pump" },
+       { "Right Headphone Output PGA", NULL, "Charge Pump" },
+       { "Left Line Output PGA", NULL, "Charge Pump" },
+       { "Right Line Output PGA", NULL, "Charge Pump" },
 };
 
 static int wm8903_add_widgets(struct snd_soc_codec *codec)
@@ -985,6 +1022,11 @@ static int wm8903_set_bias_level(struct snd_soc_codec *codec,
                        wm8903_write(codec, WM8903_CLOCK_RATES_2,
                                     WM8903_CLK_SYS_ENA);
 
+                       /* Change DC servo dither level in startup sequence */
+                       wm8903_write(codec, WM8903_WRITE_SEQUENCER_0, 0x11);
+                       wm8903_write(codec, WM8903_WRITE_SEQUENCER_1, 0x1257);
+                       wm8903_write(codec, WM8903_WRITE_SEQUENCER_2, 0x2);
+
                        wm8903_run_sequence(codec, 0);
                        wm8903_sync_reg_cache(codec, codec->reg_cache);
 
@@ -1277,14 +1319,8 @@ static int wm8903_startup(struct snd_pcm_substream *substream,
        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);
+               dev_dbg(&i2c->dev, "Constraining to %d bits\n",
+                       master_runtime->sample_bits);
 
                snd_pcm_hw_constraint_minmax(substream->runtime,
                                             SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
@@ -1523,6 +1559,7 @@ struct snd_soc_dai wm8903_dai = {
                 .formats = WM8903_FORMATS,
         },
        .ops = &wm8903_dai_ops,
+       .symmetric_rates = 1,
 };
 EXPORT_SYMBOL_GPL(wm8903_dai);
 
diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c
new file mode 100644 (file)
index 0000000..b8e17d6
--- /dev/null
@@ -0,0 +1,955 @@
+/*
+ * wm8940.c  --  WM8940 ALSA Soc Audio driver
+ *
+ * Author: Jonathan Cameron <jic23@cam.ac.uk>
+ *
+ * Based on wm8510.c
+ *    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.
+ *
+ * Not currently handled:
+ * Notch filter control
+ * AUXMode (inverting vs mixer)
+ * No means to obtain current gain if alc enabled.
+ * No use made of gpio
+ * Fast VMID discharge for power down
+ * Soft Start
+ * DLR and ALR Swaps not enabled
+ * Digital Sidetone not 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 <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 "wm8940.h"
+
+struct wm8940_priv {
+       unsigned int sysclk;
+       u16 reg_cache[WM8940_CACHEREGNUM];
+       struct snd_soc_codec codec;
+};
+
+static u16 wm8940_reg_defaults[] = {
+       0x8940, /* Soft Reset */
+       0x0000, /* Power 1 */
+       0x0000, /* Power 2 */
+       0x0000, /* Power 3 */
+       0x0010, /* Interface Control */
+       0x0000, /* Companding Control */
+       0x0140, /* Clock Control */
+       0x0000, /* Additional Controls */
+       0x0000, /* GPIO Control */
+       0x0002, /* Auto Increment Control */
+       0x0000, /* DAC Control */
+       0x00FF, /* DAC Volume */
+       0,
+       0,
+       0x0100, /* ADC Control */
+       0x00FF, /* ADC Volume */
+       0x0000, /* Notch Filter 1 Control 1 */
+       0x0000, /* Notch Filter 1 Control 2 */
+       0x0000, /* Notch Filter 2 Control 1 */
+       0x0000, /* Notch Filter 2 Control 2 */
+       0x0000, /* Notch Filter 3 Control 1 */
+       0x0000, /* Notch Filter 3 Control 2 */
+       0x0000, /* Notch Filter 4 Control 1 */
+       0x0000, /* Notch Filter 4 Control 2 */
+       0x0032, /* DAC Limit Control 1 */
+       0x0000, /* DAC Limit Control 2 */
+       0,
+       0,
+       0,
+       0,
+       0,
+       0,
+       0x0038, /* ALC Control 1 */
+       0x000B, /* ALC Control 2 */
+       0x0032, /* ALC Control 3 */
+       0x0000, /* Noise Gate */
+       0x0041, /* PLLN */
+       0x000C, /* PLLK1 */
+       0x0093, /* PLLK2 */
+       0x00E9, /* PLLK3 */
+       0,
+       0,
+       0x0030, /* ALC Control 4 */
+       0,
+       0x0002, /* Input Control */
+       0x0050, /* PGA Gain */
+       0,
+       0x0002, /* ADC Boost Control */
+       0,
+       0x0002, /* Output Control */
+       0x0000, /* Speaker Mixer Control */
+       0,
+       0,
+       0,
+       0x0079, /* Speaker Volume */
+       0,
+       0x0000, /* Mono Mixer Control */
+};
+
+static inline unsigned int wm8940_read_reg_cache(struct snd_soc_codec *codec,
+                                                unsigned int reg)
+{
+       u16 *cache = codec->reg_cache;
+
+       if (reg >= ARRAY_SIZE(wm8940_reg_defaults))
+               return -1;
+
+       return cache[reg];
+}
+
+static inline int wm8940_write_reg_cache(struct snd_soc_codec *codec,
+                                         u16 reg, unsigned int value)
+{
+       u16 *cache = codec->reg_cache;
+
+       if (reg >= ARRAY_SIZE(wm8940_reg_defaults))
+               return -1;
+
+       cache[reg] = value;
+
+       return 0;
+}
+
+static int wm8940_write(struct snd_soc_codec *codec, unsigned int reg,
+                       unsigned int value)
+{
+       int ret;
+       u8 data[3] = { reg,
+                      (value & 0xff00) >> 8,
+                      (value & 0x00ff)
+       };
+
+       wm8940_write_reg_cache(codec, reg, value);
+
+       ret = codec->hw_write(codec->control_data, data, 3);
+
+       if (ret < 0)
+               return ret;
+       else if (ret != 3)
+               return -EIO;
+       return 0;
+}
+
+static const char *wm8940_companding[] = { "Off", "NC", "u-law", "A-law" };
+static const struct soc_enum wm8940_adc_companding_enum
+= SOC_ENUM_SINGLE(WM8940_COMPANDINGCTL, 1, 4, wm8940_companding);
+static const struct soc_enum wm8940_dac_companding_enum
+= SOC_ENUM_SINGLE(WM8940_COMPANDINGCTL, 3, 4, wm8940_companding);
+
+static const char *wm8940_alc_mode_text[] = {"ALC", "Limiter"};
+static const struct soc_enum wm8940_alc_mode_enum
+= SOC_ENUM_SINGLE(WM8940_ALC3, 8, 2, wm8940_alc_mode_text);
+
+static const char *wm8940_mic_bias_level_text[] = {"0.9", "0.65"};
+static const struct soc_enum wm8940_mic_bias_level_enum
+= SOC_ENUM_SINGLE(WM8940_INPUTCTL, 8, 2, wm8940_mic_bias_level_text);
+
+static const char *wm8940_filter_mode_text[] = {"Audio", "Application"};
+static const struct soc_enum wm8940_filter_mode_enum
+= SOC_ENUM_SINGLE(WM8940_ADC, 7, 2, wm8940_filter_mode_text);
+
+static DECLARE_TLV_DB_SCALE(wm8940_spk_vol_tlv, -5700, 100, 1);
+static DECLARE_TLV_DB_SCALE(wm8940_att_tlv, -1000, 1000, 0);
+static DECLARE_TLV_DB_SCALE(wm8940_pga_vol_tlv, -1200, 75, 0);
+static DECLARE_TLV_DB_SCALE(wm8940_alc_min_tlv, -1200, 600, 0);
+static DECLARE_TLV_DB_SCALE(wm8940_alc_max_tlv, 675, 600, 0);
+static DECLARE_TLV_DB_SCALE(wm8940_alc_tar_tlv, -2250, 50, 0);
+static DECLARE_TLV_DB_SCALE(wm8940_lim_boost_tlv, 0, 100, 0);
+static DECLARE_TLV_DB_SCALE(wm8940_lim_thresh_tlv, -600, 100, 0);
+static DECLARE_TLV_DB_SCALE(wm8940_adc_tlv, -12750, 50, 1);
+static DECLARE_TLV_DB_SCALE(wm8940_capture_boost_vol_tlv, 0, 2000, 0);
+
+static const struct snd_kcontrol_new wm8940_snd_controls[] = {
+       SOC_SINGLE("Digital Loopback Switch", WM8940_COMPANDINGCTL,
+                  6, 1, 0),
+       SOC_ENUM("DAC Companding", wm8940_dac_companding_enum),
+       SOC_ENUM("ADC Companding", wm8940_adc_companding_enum),
+
+       SOC_ENUM("ALC Mode", wm8940_alc_mode_enum),
+       SOC_SINGLE("ALC Switch", WM8940_ALC1, 8, 1, 0),
+       SOC_SINGLE_TLV("ALC Capture Max Gain", WM8940_ALC1,
+                      3, 7, 1, wm8940_alc_max_tlv),
+       SOC_SINGLE_TLV("ALC Capture Min Gain", WM8940_ALC1,
+                      0, 7, 0, wm8940_alc_min_tlv),
+       SOC_SINGLE_TLV("ALC Capture Target", WM8940_ALC2,
+                      0, 14, 0, wm8940_alc_tar_tlv),
+       SOC_SINGLE("ALC Capture Hold", WM8940_ALC2, 4, 10, 0),
+       SOC_SINGLE("ALC Capture Decay", WM8940_ALC3, 4, 10, 0),
+       SOC_SINGLE("ALC Capture Attach", WM8940_ALC3, 0, 10, 0),
+       SOC_SINGLE("ALC ZC Switch", WM8940_ALC4, 1, 1, 0),
+       SOC_SINGLE("ALC Capture Noise Gate Switch", WM8940_NOISEGATE,
+                  3, 1, 0),
+       SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8940_NOISEGATE,
+                  0, 7, 0),
+
+       SOC_SINGLE("DAC Playback Limiter Switch", WM8940_DACLIM1, 8, 1, 0),
+       SOC_SINGLE("DAC Playback Limiter Attack", WM8940_DACLIM1, 0, 9, 0),
+       SOC_SINGLE("DAC Playback Limiter Decay", WM8940_DACLIM1, 4, 11, 0),
+       SOC_SINGLE_TLV("DAC Playback Limiter Threshold", WM8940_DACLIM2,
+                      4, 9, 1, wm8940_lim_thresh_tlv),
+       SOC_SINGLE_TLV("DAC Playback Limiter Boost", WM8940_DACLIM2,
+                      0, 12, 0, wm8940_lim_boost_tlv),
+
+       SOC_SINGLE("Capture PGA ZC Switch", WM8940_PGAGAIN, 7, 1, 0),
+       SOC_SINGLE_TLV("Capture PGA Volume", WM8940_PGAGAIN,
+                      0, 63, 0, wm8940_pga_vol_tlv),
+       SOC_SINGLE_TLV("Digital Playback Volume", WM8940_DACVOL,
+                      0, 255, 0, wm8940_adc_tlv),
+       SOC_SINGLE_TLV("Digital Capture Volume", WM8940_ADCVOL,
+                      0, 255, 0, wm8940_adc_tlv),
+       SOC_ENUM("Mic Bias Level", wm8940_mic_bias_level_enum),
+       SOC_SINGLE_TLV("Capture Boost Volue", WM8940_ADCBOOST,
+                      8, 1, 0, wm8940_capture_boost_vol_tlv),
+       SOC_SINGLE_TLV("Speaker Playback Volume", WM8940_SPKVOL,
+                      0, 63, 0, wm8940_spk_vol_tlv),
+       SOC_SINGLE("Speaker Playback Switch", WM8940_SPKVOL,  6, 1, 1),
+
+       SOC_SINGLE_TLV("Speaker Mixer Line Bypass Volume", WM8940_SPKVOL,
+                      8, 1, 1, wm8940_att_tlv),
+       SOC_SINGLE("Speaker Playback ZC Switch", WM8940_SPKVOL, 7, 1, 0),
+
+       SOC_SINGLE("Mono Out Switch", WM8940_MONOMIX, 6, 1, 1),
+       SOC_SINGLE_TLV("Mono Mixer Line Bypass Volume", WM8940_MONOMIX,
+                      7, 1, 1, wm8940_att_tlv),
+
+       SOC_SINGLE("High Pass Filter Switch", WM8940_ADC, 8, 1, 0),
+       SOC_ENUM("High Pass Filter Mode", wm8940_filter_mode_enum),
+       SOC_SINGLE("High Pass Filter Cut Off", WM8940_ADC, 4, 7, 0),
+       SOC_SINGLE("ADC Inversion Switch", WM8940_ADC, 0, 1, 0),
+       SOC_SINGLE("DAC Inversion Switch", WM8940_DAC, 0, 1, 0),
+       SOC_SINGLE("DAC Auto Mute Switch", WM8940_DAC, 2, 1, 0),
+       SOC_SINGLE("ZC Timeout Clock Switch", WM8940_ADDCNTRL, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8940_speaker_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Line Bypass Switch", WM8940_SPKMIX, 1, 1, 0),
+       SOC_DAPM_SINGLE("Aux Playback Switch", WM8940_SPKMIX, 5, 1, 0),
+       SOC_DAPM_SINGLE("PCM Playback Switch", WM8940_SPKMIX, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8940_mono_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Line Bypass Switch", WM8940_MONOMIX, 1, 1, 0),
+       SOC_DAPM_SINGLE("Aux Playback Switch", WM8940_MONOMIX, 2, 1, 0),
+       SOC_DAPM_SINGLE("PCM Playback Switch", WM8940_MONOMIX, 0, 1, 0),
+};
+
+static DECLARE_TLV_DB_SCALE(wm8940_boost_vol_tlv, -1500, 300, 1);
+static const struct snd_kcontrol_new wm8940_input_boost_controls[] = {
+       SOC_DAPM_SINGLE("Mic PGA Switch", WM8940_PGAGAIN, 6, 1, 1),
+       SOC_DAPM_SINGLE_TLV("Aux Volume", WM8940_ADCBOOST,
+                           0, 7, 0, wm8940_boost_vol_tlv),
+       SOC_DAPM_SINGLE_TLV("Mic Volume", WM8940_ADCBOOST,
+                           4, 7, 0, wm8940_boost_vol_tlv),
+};
+
+static const struct snd_kcontrol_new wm8940_micpga_controls[] = {
+       SOC_DAPM_SINGLE("AUX Switch", WM8940_INPUTCTL, 2, 1, 0),
+       SOC_DAPM_SINGLE("MICP Switch", WM8940_INPUTCTL, 0, 1, 0),
+       SOC_DAPM_SINGLE("MICN Switch", WM8940_INPUTCTL, 1, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8940_dapm_widgets[] = {
+       SND_SOC_DAPM_MIXER("Speaker Mixer", WM8940_POWER3, 2, 0,
+                          &wm8940_speaker_mixer_controls[0],
+                          ARRAY_SIZE(wm8940_speaker_mixer_controls)),
+       SND_SOC_DAPM_MIXER("Mono Mixer", WM8940_POWER3, 3, 0,
+                          &wm8940_mono_mixer_controls[0],
+                          ARRAY_SIZE(wm8940_mono_mixer_controls)),
+       SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8940_POWER3, 0, 0),
+
+       SND_SOC_DAPM_PGA("SpkN Out", WM8940_POWER3, 5, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("SpkP Out", WM8940_POWER3, 6, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("Mono Out", WM8940_POWER3, 7, 0, NULL, 0),
+       SND_SOC_DAPM_OUTPUT("MONOOUT"),
+       SND_SOC_DAPM_OUTPUT("SPKOUTP"),
+       SND_SOC_DAPM_OUTPUT("SPKOUTN"),
+
+       SND_SOC_DAPM_PGA("Aux Input", WM8940_POWER1, 6, 0, NULL, 0),
+       SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8940_POWER2, 0, 0),
+       SND_SOC_DAPM_MIXER("Mic PGA", WM8940_POWER2, 2, 0,
+                          &wm8940_micpga_controls[0],
+                          ARRAY_SIZE(wm8940_micpga_controls)),
+       SND_SOC_DAPM_MIXER("Boost Mixer", WM8940_POWER2, 4, 0,
+                          &wm8940_input_boost_controls[0],
+                          ARRAY_SIZE(wm8940_input_boost_controls)),
+       SND_SOC_DAPM_MICBIAS("Mic Bias", WM8940_POWER1, 4, 0),
+
+       SND_SOC_DAPM_INPUT("MICN"),
+       SND_SOC_DAPM_INPUT("MICP"),
+       SND_SOC_DAPM_INPUT("AUX"),
+};
+
+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"},
+
+       /* 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 wm8940_add_widgets(struct snd_soc_codec *codec)
+{
+       int ret;
+
+       ret = snd_soc_dapm_new_controls(codec, wm8940_dapm_widgets,
+                                       ARRAY_SIZE(wm8940_dapm_widgets));
+       if (ret)
+               goto error_ret;
+       ret = snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+       if (ret)
+               goto error_ret;
+       ret = snd_soc_dapm_new_widgets(codec);
+
+error_ret:
+       return ret;
+}
+
+#define wm8940_reset(c) wm8940_write(c, WM8940_SOFTRESET, 0);
+
+static int wm8940_set_dai_fmt(struct snd_soc_dai *codec_dai,
+                             unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       u16 iface = wm8940_read_reg_cache(codec, WM8940_IFACE) & 0xFE67;
+       u16 clk = wm8940_read_reg_cache(codec, WM8940_CLOCK) & 0x1fe;
+
+       switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+       case SND_SOC_DAIFMT_CBM_CFM:
+               clk |= 1;
+               break;
+       case SND_SOC_DAIFMT_CBS_CFS:
+               break;
+       default:
+               return -EINVAL;
+       }
+       wm8940_write(codec, WM8940_CLOCK, clk);
+
+       switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+       case SND_SOC_DAIFMT_I2S:
+               iface |= (2 << 3);
+               break;
+       case SND_SOC_DAIFMT_LEFT_J:
+               iface |= (1 << 3);
+               break;
+       case SND_SOC_DAIFMT_RIGHT_J:
+               break;
+       case SND_SOC_DAIFMT_DSP_A:
+               iface |= (3 << 3);
+               break;
+       case SND_SOC_DAIFMT_DSP_B:
+               iface |= (3 << 3) | (1 << 7);
+               break;
+       }
+
+       switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+       case SND_SOC_DAIFMT_NB_NF:
+               break;
+       case SND_SOC_DAIFMT_NB_IF:
+               iface |= (1 << 7);
+               break;
+       case SND_SOC_DAIFMT_IB_NF:
+               iface |= (1 << 8);
+               break;
+       case SND_SOC_DAIFMT_IB_IF:
+               iface |= (1 << 8) | (1 << 7);
+               break;
+       }
+
+       wm8940_write(codec, WM8940_IFACE, iface);
+
+       return 0;
+}
+
+static int wm8940_i2s_hw_params(struct snd_pcm_substream *substream,
+                               struct snd_pcm_hw_params *params,
+                               struct snd_soc_dai *dai)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_device *socdev = rtd->socdev;
+       struct snd_soc_codec *codec = socdev->card->codec;
+       u16 iface = wm8940_read_reg_cache(codec, WM8940_IFACE) & 0xFD9F;
+       u16 addcntrl = wm8940_read_reg_cache(codec, WM8940_ADDCNTRL) & 0xFFF1;
+       u16 companding =  wm8940_read_reg_cache(codec,
+                                               WM8940_COMPANDINGCTL) & 0xFFDF;
+       int ret;
+
+       /* LoutR control */
+       if (substream->stream == SNDRV_PCM_STREAM_CAPTURE
+           && params_channels(params) == 2)
+               iface |= (1 << 9);
+
+       switch (params_rate(params)) {
+       case SNDRV_PCM_RATE_8000:
+               addcntrl |= (0x5 << 1);
+               break;
+       case SNDRV_PCM_RATE_11025:
+               addcntrl |= (0x4 << 1);
+               break;
+       case SNDRV_PCM_RATE_16000:
+               addcntrl |= (0x3 << 1);
+               break;
+       case SNDRV_PCM_RATE_22050:
+               addcntrl |= (0x2 << 1);
+               break;
+       case SNDRV_PCM_RATE_32000:
+               addcntrl |= (0x1 << 1);
+               break;
+       case SNDRV_PCM_RATE_44100:
+       case SNDRV_PCM_RATE_48000:
+               break;
+       }
+       ret = wm8940_write(codec, WM8940_ADDCNTRL, addcntrl);
+       if (ret)
+               goto error_ret;
+
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S8:
+               companding = companding | (1 << 5);
+               break;
+       case SNDRV_PCM_FORMAT_S16_LE:
+               break;
+       case SNDRV_PCM_FORMAT_S20_3LE:
+               iface |= (1 << 5);
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               iface |= (2 << 5);
+               break;
+       case SNDRV_PCM_FORMAT_S32_LE:
+               iface |= (3 << 5);
+               break;
+       }
+       ret = wm8940_write(codec, WM8940_COMPANDINGCTL, companding);
+       if (ret)
+               goto error_ret;
+       ret = wm8940_write(codec, WM8940_IFACE, iface);
+
+error_ret:
+       return ret;
+}
+
+static int wm8940_mute(struct snd_soc_dai *dai, int mute)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       u16 mute_reg = wm8940_read_reg_cache(codec, WM8940_DAC) & 0xffbf;
+
+       if (mute)
+               mute_reg |= 0x40;
+
+       return wm8940_write(codec, WM8940_DAC, mute_reg);
+}
+
+static int wm8940_set_bias_level(struct snd_soc_codec *codec,
+                                enum snd_soc_bias_level level)
+{
+       u16 val;
+       u16 pwr_reg = wm8940_read_reg_cache(codec, WM8940_POWER1) & 0x1F0;
+       int ret = 0;
+
+       switch (level) {
+       case SND_SOC_BIAS_ON:
+               /* ensure bufioen and biasen */
+               pwr_reg |= (1 << 2) | (1 << 3);
+               /* Enable thermal shutdown */
+               val = wm8940_read_reg_cache(codec, WM8940_OUTPUTCTL);
+               ret = wm8940_write(codec, WM8940_OUTPUTCTL, val | 0x2);
+               if (ret)
+                       break;
+               /* set vmid to 75k */
+               ret = wm8940_write(codec, WM8940_POWER1, pwr_reg | 0x1);
+               break;
+       case SND_SOC_BIAS_PREPARE:
+               /* ensure bufioen and biasen */
+               pwr_reg |= (1 << 2) | (1 << 3);
+               ret = wm8940_write(codec, WM8940_POWER1, pwr_reg | 0x1);
+               break;
+       case SND_SOC_BIAS_STANDBY:
+               /* ensure bufioen and biasen */
+               pwr_reg |= (1 << 2) | (1 << 3);
+               /* set vmid to 300k for standby */
+               ret = wm8940_write(codec, WM8940_POWER1, pwr_reg | 0x2);
+               break;
+       case SND_SOC_BIAS_OFF:
+               ret = wm8940_write(codec, WM8940_POWER1, pwr_reg);
+               break;
+       }
+
+       return ret;
+}
+
+struct pll_ {
+       unsigned int pre_scale:2;
+       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;
+       /* The left shift ist to avoid accuracy loss when right shifting */
+       Ndiv = target / source;
+
+       if (Ndiv > 12) {
+               source <<= 1;
+               /* Multiply by 2 */
+               pll_div.pre_scale = 0;
+               Ndiv = target / source;
+       } else if (Ndiv < 3) {
+               source >>= 2;
+               /* Divide by 4 */
+               pll_div.pre_scale = 3;
+               Ndiv = target / source;
+       } else if (Ndiv < 6) {
+               source >>= 1;
+               /* divide by 2 */
+               pll_div.pre_scale = 2;
+               Ndiv = target / source;
+       } else
+               pll_div.pre_scale = 1;
+
+       if ((Ndiv < 6) || (Ndiv > 12))
+               printk(KERN_WARNING
+                       "WM8940 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;
+}
+
+/* Untested at the moment */
+static int wm8940_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;
+
+       /* Turn off PLL */
+       reg = wm8940_read_reg_cache(codec, WM8940_POWER1);
+       wm8940_write(codec, WM8940_POWER1, reg & 0x1df);
+
+       if (freq_in == 0 || freq_out == 0) {
+               /* Clock CODEC directly from MCLK */
+               reg = wm8940_read_reg_cache(codec, WM8940_CLOCK);
+               wm8940_write(codec, WM8940_CLOCK, reg & 0x0ff);
+               /* Pll power down */
+               wm8940_write(codec, WM8940_PLLN, (1 << 7));
+               return 0;
+       }
+
+       /* Pll is followed by a frequency divide by 4 */
+       pll_factors(freq_out*4, freq_in);
+       if (pll_div.k)
+               wm8940_write(codec, WM8940_PLLN,
+                            (pll_div.pre_scale << 4) | pll_div.n | (1 << 6));
+       else /* No factional component */
+               wm8940_write(codec, WM8940_PLLN,
+                            (pll_div.pre_scale << 4) | pll_div.n);
+       wm8940_write(codec, WM8940_PLLK1, pll_div.k >> 18);
+       wm8940_write(codec, WM8940_PLLK2, (pll_div.k >> 9) & 0x1ff);
+       wm8940_write(codec, WM8940_PLLK3, pll_div.k & 0x1ff);
+       /* Enable the PLL */
+       reg = wm8940_read_reg_cache(codec, WM8940_POWER1);
+       wm8940_write(codec, WM8940_POWER1, reg | 0x020);
+
+       /* Run CODEC from PLL instead of MCLK */
+       reg = wm8940_read_reg_cache(codec, WM8940_CLOCK);
+       wm8940_write(codec, WM8940_CLOCK, reg | 0x100);
+
+       return 0;
+}
+
+static int wm8940_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 wm8940_priv *wm8940 = codec->private_data;
+
+       switch (freq) {
+       case 11289600:
+       case 12000000:
+       case 12288000:
+       case 16934400:
+       case 18432000:
+               wm8940->sysclk = freq;
+               return 0;
+       }
+       return -EINVAL;
+}
+
+static int wm8940_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+                                int div_id, int div)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       u16 reg;
+       int ret = 0;
+
+       switch (div_id) {
+       case WM8940_BCLKDIV:
+               reg = wm8940_read_reg_cache(codec, WM8940_CLOCK) & 0xFFEF3;
+               ret = wm8940_write(codec, WM8940_CLOCK, reg | (div << 2));
+               break;
+       case WM8940_MCLKDIV:
+               reg = wm8940_read_reg_cache(codec, WM8940_CLOCK) & 0xFF1F;
+               ret = wm8940_write(codec, WM8940_CLOCK, reg | (div << 5));
+               break;
+       case WM8940_OPCLKDIV:
+               reg = wm8940_read_reg_cache(codec, WM8940_ADDCNTRL) & 0xFFCF;
+               ret = wm8940_write(codec, WM8940_ADDCNTRL, reg | (div << 4));
+               break;
+       }
+       return ret;
+}
+
+#define WM8940_RATES SNDRV_PCM_RATE_8000_48000
+
+#define WM8940_FORMATS (SNDRV_PCM_FMTBIT_S8 |                          \
+                       SNDRV_PCM_FMTBIT_S16_LE |                       \
+                       SNDRV_PCM_FMTBIT_S20_3LE |                      \
+                       SNDRV_PCM_FMTBIT_S24_LE |                       \
+                       SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8940_dai_ops = {
+       .hw_params = wm8940_i2s_hw_params,
+       .set_sysclk = wm8940_set_dai_sysclk,
+       .digital_mute = wm8940_mute,
+       .set_fmt = wm8940_set_dai_fmt,
+       .set_clkdiv = wm8940_set_dai_clkdiv,
+       .set_pll = wm8940_set_dai_pll,
+};
+
+struct snd_soc_dai wm8940_dai = {
+       .name = "WM8940",
+       .playback = {
+               .stream_name = "Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = WM8940_RATES,
+               .formats = WM8940_FORMATS,
+       },
+       .capture = {
+               .stream_name = "Capture",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = WM8940_RATES,
+               .formats = WM8940_FORMATS,
+       },
+       .ops = &wm8940_dai_ops,
+       .symmetric_rates = 1,
+};
+EXPORT_SYMBOL_GPL(wm8940_dai);
+
+static int wm8940_suspend(struct platform_device *pdev, pm_message_t state)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec = socdev->card->codec;
+
+       return wm8940_set_bias_level(codec, SND_SOC_BIAS_OFF);
+}
+
+static int wm8940_resume(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec = socdev->card->codec;
+       int i;
+       int ret;
+       u8 data[3];
+       u16 *cache = codec->reg_cache;
+
+       /* Sync reg_cache with the hardware
+        * Could use auto incremented writes to speed this up
+        */
+       for (i = 0; i < ARRAY_SIZE(wm8940_reg_defaults); i++) {
+               data[0] = i;
+               data[1] = (cache[i] & 0xFF00) >> 8;
+               data[2] = cache[i] & 0x00FF;
+               ret = codec->hw_write(codec->control_data, data, 3);
+               if (ret < 0)
+                       goto error_ret;
+               else if (ret != 3) {
+                       ret = -EIO;
+                       goto error_ret;
+               }
+       }
+       ret = wm8940_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+       if (ret)
+               goto error_ret;
+       ret = wm8940_set_bias_level(codec, codec->suspend_bias_level);
+
+error_ret:
+       return ret;
+}
+
+static struct snd_soc_codec *wm8940_codec;
+
+static int wm8940_probe(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec;
+
+       int ret = 0;
+
+       if (wm8940_codec == NULL) {
+               dev_err(&pdev->dev, "Codec device not registered\n");
+               return -ENODEV;
+       }
+
+       socdev->card->codec = wm8940_codec;
+       codec = wm8940_codec;
+
+       mutex_init(&codec->mutex);
+       /* register pcms */
+       ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+       if (ret < 0) {
+               dev_err(codec->dev, "failed to create pcms: %d\n", ret);
+               goto pcm_err;
+       }
+
+       ret = snd_soc_add_controls(codec, wm8940_snd_controls,
+                            ARRAY_SIZE(wm8940_snd_controls));
+       if (ret)
+               goto error_free_pcms;
+       ret = wm8940_add_widgets(codec);
+       if (ret)
+               goto error_free_pcms;
+
+       ret = snd_soc_init_card(socdev);
+       if (ret < 0) {
+               dev_err(codec->dev, "failed to register card: %d\n", ret);
+               goto error_free_pcms;
+       }
+
+       return ret;
+
+error_free_pcms:
+       snd_soc_free_pcms(socdev);
+       snd_soc_dapm_free(socdev);
+pcm_err:
+       return ret;
+}
+
+static int wm8940_remove(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+       snd_soc_free_pcms(socdev);
+       snd_soc_dapm_free(socdev);
+
+       return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8940 = {
+       .probe = wm8940_probe,
+       .remove = wm8940_remove,
+       .suspend = wm8940_suspend,
+       .resume = wm8940_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8940);
+
+static int wm8940_register(struct wm8940_priv *wm8940)
+{
+       struct wm8940_setup_data *pdata = wm8940->codec.dev->platform_data;
+       struct snd_soc_codec *codec = &wm8940->codec;
+       int ret;
+       u16 reg;
+       if (wm8940_codec) {
+               dev_err(codec->dev, "Another WM8940 is registered\n");
+               return -EINVAL;
+       }
+
+       INIT_LIST_HEAD(&codec->dapm_widgets);
+       INIT_LIST_HEAD(&codec->dapm_paths);
+
+       codec->private_data = wm8940;
+       codec->name = "WM8940";
+       codec->owner = THIS_MODULE;
+       codec->read = wm8940_read_reg_cache;
+       codec->write = wm8940_write;
+       codec->bias_level = SND_SOC_BIAS_OFF;
+       codec->set_bias_level = wm8940_set_bias_level;
+       codec->dai = &wm8940_dai;
+       codec->num_dai = 1;
+       codec->reg_cache_size = ARRAY_SIZE(wm8940_reg_defaults);
+       codec->reg_cache = &wm8940->reg_cache;
+
+       memcpy(codec->reg_cache, wm8940_reg_defaults,
+              sizeof(wm8940_reg_defaults));
+
+       ret = wm8940_reset(codec);
+       if (ret < 0) {
+               dev_err(codec->dev, "Failed to issue reset\n");
+               return ret;
+       }
+
+       wm8940_dai.dev = codec->dev;
+
+       wm8940_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       ret = wm8940_write(codec, WM8940_POWER1, 0x180);
+       if (ret < 0)
+               return ret;
+
+       if (!pdata)
+               dev_warn(codec->dev, "No platform data supplied\n");
+       else {
+               reg = wm8940_read_reg_cache(codec, WM8940_OUTPUTCTL);
+               ret = wm8940_write(codec, WM8940_OUTPUTCTL, reg | pdata->vroi);
+               if (ret < 0)
+                       return ret;
+       }
+
+
+       wm8940_codec = codec;
+
+       ret = snd_soc_register_codec(codec);
+       if (ret) {
+               dev_err(codec->dev, "Failed to register codec: %d\n", ret);
+               return ret;
+       }
+
+       ret = snd_soc_register_dai(&wm8940_dai);
+       if (ret) {
+               dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
+               snd_soc_unregister_codec(codec);
+               return ret;
+       }
+
+       return 0;
+}
+
+static void wm8940_unregister(struct wm8940_priv *wm8940)
+{
+       wm8940_set_bias_level(&wm8940->codec, SND_SOC_BIAS_OFF);
+       snd_soc_unregister_dai(&wm8940_dai);
+       snd_soc_unregister_codec(&wm8940->codec);
+       kfree(wm8940);
+       wm8940_codec = NULL;
+}
+
+static int wm8940_i2c_probe(struct i2c_client *i2c,
+                           const struct i2c_device_id *id)
+{
+       struct wm8940_priv *wm8940;
+       struct snd_soc_codec *codec;
+
+       wm8940 = kzalloc(sizeof *wm8940, GFP_KERNEL);
+       if (wm8940 == NULL)
+               return -ENOMEM;
+
+       codec = &wm8940->codec;
+       codec->hw_write = (hw_write_t)i2c_master_send;
+       i2c_set_clientdata(i2c, wm8940);
+       codec->control_data = i2c;
+       codec->dev = &i2c->dev;
+
+       return wm8940_register(wm8940);
+}
+
+static int __devexit wm8940_i2c_remove(struct i2c_client *client)
+{
+       struct wm8940_priv *wm8940 = i2c_get_clientdata(client);
+
+       wm8940_unregister(wm8940);
+
+       return 0;
+}
+
+static const struct i2c_device_id wm8940_i2c_id[] = {
+       { "wm8940", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8940_i2c_id);
+
+static struct i2c_driver wm8940_i2c_driver = {
+       .driver = {
+               .name = "WM8940 I2C Codec",
+               .owner = THIS_MODULE,
+       },
+       .probe = wm8940_i2c_probe,
+       .remove = __devexit_p(wm8940_i2c_remove),
+       .id_table = wm8940_i2c_id,
+};
+
+static int __init wm8940_modinit(void)
+{
+       int ret;
+
+       ret = i2c_add_driver(&wm8940_i2c_driver);
+       if (ret)
+               printk(KERN_ERR "Failed to register WM8940 I2C driver: %d\n",
+                      ret);
+       return ret;
+}
+module_init(wm8940_modinit);
+
+static void __exit wm8940_exit(void)
+{
+       i2c_del_driver(&wm8940_i2c_driver);
+}
+module_exit(wm8940_exit);
+
+MODULE_DESCRIPTION("ASoC WM8940 driver");
+MODULE_AUTHOR("Jonathan Cameron");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8940.h b/sound/soc/codecs/wm8940.h
new file mode 100644 (file)
index 0000000..8410eed
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * wm8940.h -- WM8940 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 _WM8940_H
+#define _WM8940_H
+
+struct wm8940_setup_data {
+       /* Vref to analogue output resistance */
+#define WM8940_VROI_1K 0
+#define WM8940_VROI_30K 1
+       unsigned int vroi:1;
+};
+extern struct snd_soc_dai wm8940_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8940;
+
+/* WM8940 register space */
+#define WM8940_SOFTRESET       0x00
+#define WM8940_POWER1          0x01
+#define WM8940_POWER2          0x02
+#define WM8940_POWER3          0x03
+#define WM8940_IFACE           0x04
+#define WM8940_COMPANDINGCTL   0x05
+#define WM8940_CLOCK           0x06
+#define WM8940_ADDCNTRL                0x07
+#define WM8940_GPIO            0x08
+#define WM8940_CTLINT          0x09
+#define WM8940_DAC             0x0A
+#define WM8940_DACVOL          0x0B
+
+#define WM8940_ADC             0x0E
+#define WM8940_ADCVOL          0x0F
+#define WM8940_NOTCH1          0x10
+#define WM8940_NOTCH2          0x11
+#define WM8940_NOTCH3          0x12
+#define WM8940_NOTCH4          0x13
+#define WM8940_NOTCH5          0x14
+#define WM8940_NOTCH6          0x15
+#define WM8940_NOTCH7          0x16
+#define WM8940_NOTCH8          0x17
+#define WM8940_DACLIM1         0x18
+#define WM8940_DACLIM2         0x19
+
+#define WM8940_ALC1            0x20
+#define WM8940_ALC2            0x21
+#define WM8940_ALC3            0x22
+#define WM8940_NOISEGATE       0x23
+#define WM8940_PLLN            0x24
+#define WM8940_PLLK1           0x25
+#define WM8940_PLLK2           0x26
+#define WM8940_PLLK3           0x27
+
+#define WM8940_ALC4            0x2A
+
+#define WM8940_INPUTCTL                0x2C
+#define WM8940_PGAGAIN         0x2D
+
+#define WM8940_ADCBOOST                0x2F
+
+#define WM8940_OUTPUTCTL       0x31
+#define WM8940_SPKMIX          0x32
+
+#define WM8940_SPKVOL          0x36
+
+#define WM8940_MONOMIX         0x38
+
+#define WM8940_CACHEREGNUM  0x57
+
+
+/* Clock divider Id's */
+#define WM8940_BCLKDIV 0
+#define WM8940_MCLKDIV 1
+#define WM8940_OPCLKDIV 2
+
+/* MCLK clock dividers */
+#define WM8940_MCLKDIV_1       0
+#define WM8940_MCLKDIV_1_5     1
+#define WM8940_MCLKDIV_2       2
+#define WM8940_MCLKDIV_3       3
+#define WM8940_MCLKDIV_4       4
+#define WM8940_MCLKDIV_6       5
+#define WM8940_MCLKDIV_8       6
+#define WM8940_MCLKDIV_12      7
+
+/* BCLK clock dividers */
+#define WM8940_BCLKDIV_1 0
+#define WM8940_BCLKDIV_2 1
+#define WM8940_BCLKDIV_4 2
+#define WM8940_BCLKDIV_8 3
+#define WM8940_BCLKDIV_16 4
+#define WM8940_BCLKDIV_32 5
+
+/* PLL Out Dividers */
+#define WM8940_OPCLKDIV_1 0
+#define WM8940_OPCLKDIV_2 1
+#define WM8940_OPCLKDIV_3 2
+#define WM8940_OPCLKDIV_4 3
+
+#endif /* _WM8940_H */
+
diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c
new file mode 100644 (file)
index 0000000..e224d8a
--- /dev/null
@@ -0,0 +1,969 @@
+/*
+ * wm8960.c  --  WM8960 ALSA SoC Audio driver
+ *
+ * Author: 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 <sound/tlv.h>
+
+#include "wm8960.h"
+
+#define AUDIO_NAME "wm8960"
+
+struct snd_soc_codec_device soc_codec_dev_wm8960;
+
+/* R25 - Power 1 */
+#define WM8960_VREF      0x40
+
+/* R28 - Anti-pop 1 */
+#define WM8960_POBCTRL   0x80
+#define WM8960_BUFDCOPEN 0x10
+#define WM8960_BUFIOEN   0x08
+#define WM8960_SOFT_ST   0x04
+#define WM8960_HPSTBY    0x01
+
+/* R29 - Anti-pop 2 */
+#define WM8960_DISOP     0x40
+
+/*
+ * wm8960 register cache
+ * We can't read the WM8960 register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8960_reg[WM8960_CACHEREGNUM] = {
+       0x0097, 0x0097, 0x0000, 0x0000,
+       0x0000, 0x0008, 0x0000, 0x000a,
+       0x01c0, 0x0000, 0x00ff, 0x00ff,
+       0x0000, 0x0000, 0x0000, 0x0000,
+       0x0000, 0x007b, 0x0100, 0x0032,
+       0x0000, 0x00c3, 0x00c3, 0x01c0,
+       0x0000, 0x0000, 0x0000, 0x0000,
+       0x0000, 0x0000, 0x0000, 0x0000,
+       0x0100, 0x0100, 0x0050, 0x0050,
+       0x0050, 0x0050, 0x0000, 0x0000,
+       0x0000, 0x0000, 0x0040, 0x0000,
+       0x0000, 0x0050, 0x0050, 0x0000,
+       0x0002, 0x0037, 0x004d, 0x0080,
+       0x0008, 0x0031, 0x0026, 0x00e9,
+};
+
+struct wm8960_priv {
+       u16 reg_cache[WM8960_CACHEREGNUM];
+       struct snd_soc_codec codec;
+};
+
+/*
+ * read wm8960 register cache
+ */
+static inline unsigned int wm8960_read_reg_cache(struct snd_soc_codec *codec,
+       unsigned int reg)
+{
+       u16 *cache = codec->reg_cache;
+       if (reg == WM8960_RESET)
+               return 0;
+       if (reg >= WM8960_CACHEREGNUM)
+               return -1;
+       return cache[reg];
+}
+
+/*
+ * write wm8960 register cache
+ */
+static inline void wm8960_write_reg_cache(struct snd_soc_codec *codec,
+       u16 reg, unsigned int value)
+{
+       u16 *cache = codec->reg_cache;
+       if (reg >= WM8960_CACHEREGNUM)
+               return;
+       cache[reg] = value;
+}
+
+static inline unsigned int wm8960_read(struct snd_soc_codec *codec,
+       unsigned int reg)
+{
+       return wm8960_read_reg_cache(codec, reg);
+}
+
+/*
+ * write to the WM8960 register space
+ */
+static int wm8960_write(struct snd_soc_codec *codec, unsigned int reg,
+       unsigned int value)
+{
+       u8 data[2];
+
+       /* data is
+        *   D15..D9 WM8960 register offset
+        *   D8...D0 register data
+        */
+       data[0] = (reg << 1) | ((value >> 8) & 0x0001);
+       data[1] = value & 0x00ff;
+
+       wm8960_write_reg_cache(codec, reg, value);
+       if (codec->hw_write(codec->control_data, data, 2) == 2)
+               return 0;
+       else
+               return -EIO;
+}
+
+#define wm8960_reset(c)        wm8960_write(c, WM8960_RESET, 0)
+
+/* enumerated controls */
+static const char *wm8960_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+static const char *wm8960_polarity[] = {"No Inversion", "Left Inverted",
+       "Right Inverted", "Stereo Inversion"};
+static const char *wm8960_3d_upper_cutoff[] = {"High", "Low"};
+static const char *wm8960_3d_lower_cutoff[] = {"Low", "High"};
+static const char *wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"};
+static const char *wm8960_alcmode[] = {"ALC", "Limiter"};
+
+static const struct soc_enum wm8960_enum[] = {
+       SOC_ENUM_SINGLE(WM8960_DACCTL1, 1, 4, wm8960_deemph),
+       SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity),
+       SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity),
+       SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff),
+       SOC_ENUM_SINGLE(WM8960_3D, 5, 2, wm8960_3d_lower_cutoff),
+       SOC_ENUM_SINGLE(WM8960_ALC1, 7, 4, wm8960_alcfunc),
+       SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode),
+};
+
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -9700, 50, 0);
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1);
+static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
+
+static const struct snd_kcontrol_new wm8960_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL,
+                0, 63, 0, adc_tlv),
+SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL,
+       6, 1, 0),
+SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL,
+       7, 1, 0),
+
+SOC_DOUBLE_R_TLV("Playback Volume", WM8960_LDAC, WM8960_RDAC,
+                0, 255, 0, dac_tlv),
+
+SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8960_LOUT1, WM8960_ROUT1,
+                0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8960_LOUT1, WM8960_ROUT1,
+       7, 1, 0),
+
+SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8960_LOUT2, WM8960_ROUT2,
+                0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8960_LOUT2, WM8960_ROUT2,
+       7, 1, 0),
+SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0),
+SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0),
+
+SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0),
+SOC_ENUM("ADC Polarity", wm8960_enum[1]),
+SOC_ENUM("Playback De-emphasis", wm8960_enum[0]),
+SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0),
+
+SOC_ENUM("DAC Polarity", wm8960_enum[2]),
+
+SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[3]),
+SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[4]),
+SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0),
+SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0),
+
+SOC_ENUM("ALC Function", wm8960_enum[5]),
+SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0),
+SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1),
+SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0),
+SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0),
+SOC_ENUM("ALC Mode", wm8960_enum[6]),
+SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0),
+SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0),
+
+SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0),
+SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0),
+
+SOC_DOUBLE_R("ADC PCM Capture Volume", WM8960_LINPATH, WM8960_RINPATH,
+       0, 127, 0),
+
+SOC_SINGLE_TLV("Left Output Mixer Boost Bypass Volume",
+              WM8960_BYPASS1, 4, 7, 1, bypass_tlv),
+SOC_SINGLE_TLV("Left Output Mixer LINPUT3 Volume",
+              WM8960_LOUTMIX, 4, 7, 1, bypass_tlv),
+SOC_SINGLE_TLV("Right Output Mixer Boost Bypass Volume",
+              WM8960_BYPASS2, 4, 7, 1, bypass_tlv),
+SOC_SINGLE_TLV("Right Output Mixer RINPUT3 Volume",
+              WM8960_ROUTMIX, 4, 7, 1, bypass_tlv),
+};
+
+static const struct snd_kcontrol_new wm8960_lin_boost[] = {
+SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0),
+SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0),
+SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_lin[] = {
+SOC_DAPM_SINGLE("Boost Switch", WM8960_LINPATH, 3, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_rin_boost[] = {
+SOC_DAPM_SINGLE("RINPUT2 Switch", WM8960_RINPATH, 6, 1, 0),
+SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_RINPATH, 7, 1, 0),
+SOC_DAPM_SINGLE("RINPUT1 Switch", WM8960_RINPATH, 8, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_rin[] = {
+SOC_DAPM_SINGLE("Boost Switch", WM8960_RINPATH, 3, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_loutput_mixer[] = {
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0),
+SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0),
+SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_routput_mixer[] = {
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_ROUTMIX, 8, 1, 0),
+SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_ROUTMIX, 7, 1, 0),
+SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS2, 7, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_mono_out[] = {
+SOC_DAPM_SINGLE("Left Switch", WM8960_MONOMIX1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Switch", WM8960_MONOMIX2, 7, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("LINPUT1"),
+SND_SOC_DAPM_INPUT("RINPUT1"),
+SND_SOC_DAPM_INPUT("LINPUT2"),
+SND_SOC_DAPM_INPUT("RINPUT2"),
+SND_SOC_DAPM_INPUT("LINPUT3"),
+SND_SOC_DAPM_INPUT("RINPUT3"),
+
+SND_SOC_DAPM_MICBIAS("MICB", WM8960_POWER1, 1, 0),
+
+SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0,
+                  wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)),
+SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8960_POWER1, 4, 0,
+                  wm8960_rin_boost, ARRAY_SIZE(wm8960_rin_boost)),
+
+SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0,
+                  wm8960_lin, ARRAY_SIZE(wm8960_lin)),
+SND_SOC_DAPM_MIXER("Right Input Mixer", WM8960_POWER3, 4, 0,
+                  wm8960_rin, ARRAY_SIZE(wm8960_rin)),
+
+SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER2, 3, 0),
+SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER2, 2, 0),
+
+SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0),
+SND_SOC_DAPM_DAC("Right DAC", "Playback", WM8960_POWER2, 7, 0),
+
+SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0,
+       &wm8960_loutput_mixer[0],
+       ARRAY_SIZE(wm8960_loutput_mixer)),
+SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0,
+       &wm8960_routput_mixer[0],
+       ARRAY_SIZE(wm8960_routput_mixer)),
+
+SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0,
+       &wm8960_mono_out[0],
+       ARRAY_SIZE(wm8960_mono_out)),
+
+SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Speaker PGA", WM8960_POWER2, 3, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA("Right Speaker Output", WM8960_CLASSD1, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0),
+
+SND_SOC_DAPM_OUTPUT("SPK_LP"),
+SND_SOC_DAPM_OUTPUT("SPK_LN"),
+SND_SOC_DAPM_OUTPUT("HP_L"),
+SND_SOC_DAPM_OUTPUT("HP_R"),
+SND_SOC_DAPM_OUTPUT("SPK_RP"),
+SND_SOC_DAPM_OUTPUT("SPK_RN"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+};
+
+static const struct snd_soc_dapm_route audio_paths[] = {
+       { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" },
+       { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" },
+       { "Left Boost Mixer", "LINPUT3 Switch", "LINPUT3" },
+
+       { "Left Input Mixer", "Boost Switch", "Left Boost Mixer", },
+       { "Left Input Mixer", NULL, "LINPUT1", },  /* Really Boost Switch */
+       { "Left Input Mixer", NULL, "LINPUT2" },
+       { "Left Input Mixer", NULL, "LINPUT3" },
+
+       { "Right Boost Mixer", "RINPUT1 Switch", "RINPUT1" },
+       { "Right Boost Mixer", "RINPUT2 Switch", "RINPUT2" },
+       { "Right Boost Mixer", "RINPUT3 Switch", "RINPUT3" },
+
+       { "Right Input Mixer", "Boost Switch", "Right Boost Mixer", },
+       { "Right Input Mixer", NULL, "RINPUT1", },  /* Really Boost Switch */
+       { "Right Input Mixer", NULL, "RINPUT2" },
+       { "Right Input Mixer", NULL, "LINPUT3" },
+
+       { "Left ADC", NULL, "Left Input Mixer" },
+       { "Right ADC", NULL, "Right Input Mixer" },
+
+       { "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" },
+       { "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer"} ,
+       { "Left Output Mixer", "PCM Playback Switch", "Left DAC" },
+
+       { "Right Output Mixer", "RINPUT3 Switch", "RINPUT3" },
+       { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" } ,
+       { "Right Output Mixer", "PCM Playback Switch", "Right DAC" },
+
+       { "Mono Output Mixer", "Left Switch", "Left Output Mixer" },
+       { "Mono Output Mixer", "Right Switch", "Right Output Mixer" },
+
+       { "LOUT1 PGA", NULL, "Left Output Mixer" },
+       { "ROUT1 PGA", NULL, "Right Output Mixer" },
+
+       { "HP_L", NULL, "LOUT1 PGA" },
+       { "HP_R", NULL, "ROUT1 PGA" },
+
+       { "Left Speaker PGA", NULL, "Left Output Mixer" },
+       { "Right Speaker PGA", NULL, "Right Output Mixer" },
+
+       { "Left Speaker Output", NULL, "Left Speaker PGA" },
+       { "Right Speaker Output", NULL, "Right Speaker PGA" },
+
+       { "SPK_LN", NULL, "Left Speaker Output" },
+       { "SPK_LP", NULL, "Left Speaker Output" },
+       { "SPK_RN", NULL, "Right Speaker Output" },
+       { "SPK_RP", NULL, "Right Speaker Output" },
+
+       { "OUT3", NULL, "Mono Output Mixer", }
+};
+
+static int wm8960_add_widgets(struct snd_soc_codec *codec)
+{
+       snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets,
+                                 ARRAY_SIZE(wm8960_dapm_widgets));
+
+       snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths));
+
+       snd_soc_dapm_new_widgets(codec);
+       return 0;
+}
+
+static int wm8960_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 */
+       wm8960_write(codec, WM8960_IFACE1, iface);
+       return 0;
+}
+
+static int wm8960_hw_params(struct snd_pcm_substream *substream,
+                           struct snd_pcm_hw_params *params,
+                           struct snd_soc_dai *dai)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_device *socdev = rtd->socdev;
+       struct snd_soc_codec *codec = socdev->card->codec;
+       u16 iface = wm8960_read(codec, WM8960_IFACE1) & 0xfff3;
+
+       /* 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;
+       }
+
+       /* set iface */
+       wm8960_write(codec, WM8960_IFACE1, iface);
+       return 0;
+}
+
+static int wm8960_mute(struct snd_soc_dai *dai, int mute)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       u16 mute_reg = wm8960_read(codec, WM8960_DACCTL1) & 0xfff7;
+
+       if (mute)
+               wm8960_write(codec, WM8960_DACCTL1, mute_reg | 0x8);
+       else
+               wm8960_write(codec, WM8960_DACCTL1, mute_reg);
+       return 0;
+}
+
+static int wm8960_set_bias_level(struct snd_soc_codec *codec,
+                                enum snd_soc_bias_level level)
+{
+       struct wm8960_data *pdata = codec->dev->platform_data;
+       u16 reg;
+
+       switch (level) {
+       case SND_SOC_BIAS_ON:
+               break;
+
+       case SND_SOC_BIAS_PREPARE:
+               /* Set VMID to 2x50k */
+               reg = wm8960_read(codec, WM8960_POWER1);
+               reg &= ~0x180;
+               reg |= 0x80;
+               wm8960_write(codec, WM8960_POWER1, reg);
+               break;
+
+       case SND_SOC_BIAS_STANDBY:
+               if (codec->bias_level == SND_SOC_BIAS_OFF) {
+                       /* Enable anti-pop features */
+                       wm8960_write(codec, WM8960_APOP1,
+                                    WM8960_POBCTRL | WM8960_SOFT_ST |
+                                    WM8960_BUFDCOPEN | WM8960_BUFIOEN);
+
+                       /* Discharge HP output */
+                       reg = WM8960_DISOP;
+                       if (pdata)
+                               reg |= pdata->dres << 4;
+                       wm8960_write(codec, WM8960_APOP2, reg);
+
+                       msleep(400);
+
+                       wm8960_write(codec, WM8960_APOP2, 0);
+
+                       /* Enable & ramp VMID at 2x50k */
+                       reg = wm8960_read(codec, WM8960_POWER1);
+                       reg |= 0x80;
+                       wm8960_write(codec, WM8960_POWER1, reg);
+                       msleep(100);
+
+                       /* Enable VREF */
+                       wm8960_write(codec, WM8960_POWER1, reg | WM8960_VREF);
+
+                       /* Disable anti-pop features */
+                       wm8960_write(codec, WM8960_APOP1, WM8960_BUFIOEN);
+               }
+
+               /* Set VMID to 2x250k */
+               reg = wm8960_read(codec, WM8960_POWER1);
+               reg &= ~0x180;
+               reg |= 0x100;
+               wm8960_write(codec, WM8960_POWER1, reg);
+               break;
+
+       case SND_SOC_BIAS_OFF:
+               /* Enable anti-pop features */
+               wm8960_write(codec, WM8960_APOP1,
+                            WM8960_POBCTRL | WM8960_SOFT_ST |
+                            WM8960_BUFDCOPEN | WM8960_BUFIOEN);
+
+               /* Disable VMID and VREF, let them discharge */
+               wm8960_write(codec, WM8960_POWER1, 0);
+               msleep(600);
+
+               wm8960_write(codec, WM8960_APOP1, 0);
+               break;
+       }
+
+       codec->bias_level = level;
+
+       return 0;
+}
+
+/* PLL divisors */
+struct _pll_div {
+       u32 pre_div: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 << 24) * 10)
+
+static int pll_factors(unsigned int source, unsigned int target,
+                      struct _pll_div *pll_div)
+{
+       unsigned long long Kpart;
+       unsigned int K, Ndiv, Nmod;
+
+       pr_debug("WM8960 PLL: setting %dHz->%dHz\n", source, target);
+
+       /* Scale up target to PLL operating frequency */
+       target *= 4;
+
+       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)) {
+               pr_err("WM8960 PLL: Unsupported N=%d\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;
+
+       /* 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;
+
+       pr_debug("WM8960 PLL: N=%x K=%x pre_div=%d\n",
+                pll_div->n, pll_div->k, pll_div->pre_div);
+
+       return 0;
+}
+
+static int wm8960_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;
+       static struct _pll_div pll_div;
+       int ret;
+
+       if (freq_in && freq_out) {
+               ret = pll_factors(freq_in, freq_out, &pll_div);
+               if (ret != 0)
+                       return ret;
+       }
+
+       /* Disable the PLL: even if we are changing the frequency the
+        * PLL needs to be disabled while we do so. */
+       wm8960_write(codec, WM8960_CLOCK1,
+                    wm8960_read(codec, WM8960_CLOCK1) & ~1);
+       wm8960_write(codec, WM8960_POWER2,
+                    wm8960_read(codec, WM8960_POWER2) & ~1);
+
+       if (!freq_in || !freq_out)
+               return 0;
+
+       reg = wm8960_read(codec, WM8960_PLL1) & ~0x3f;
+       reg |= pll_div.pre_div << 4;
+       reg |= pll_div.n;
+
+       if (pll_div.k) {
+               reg |= 0x20;
+
+               wm8960_write(codec, WM8960_PLL2, (pll_div.k >> 18) & 0x3f);
+               wm8960_write(codec, WM8960_PLL3, (pll_div.k >> 9) & 0x1ff);
+               wm8960_write(codec, WM8960_PLL4, pll_div.k & 0x1ff);
+       }
+       wm8960_write(codec, WM8960_PLL1, reg);
+
+       /* Turn it on */
+       wm8960_write(codec, WM8960_POWER2,
+                    wm8960_read(codec, WM8960_POWER2) | 1);
+       msleep(250);
+       wm8960_write(codec, WM8960_CLOCK1,
+                    wm8960_read(codec, WM8960_CLOCK1) | 1);
+
+       return 0;
+}
+
+static int wm8960_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 WM8960_SYSCLKSEL:
+               reg = wm8960_read(codec, WM8960_CLOCK1) & 0x1fe;
+               wm8960_write(codec, WM8960_CLOCK1, reg | div);
+               break;
+       case WM8960_SYSCLKDIV:
+               reg = wm8960_read(codec, WM8960_CLOCK1) & 0x1f9;
+               wm8960_write(codec, WM8960_CLOCK1, reg | div);
+               break;
+       case WM8960_DACDIV:
+               reg = wm8960_read(codec, WM8960_CLOCK1) & 0x1c7;
+               wm8960_write(codec, WM8960_CLOCK1, reg | div);
+               break;
+       case WM8960_OPCLKDIV:
+               reg = wm8960_read(codec, WM8960_PLL1) & 0x03f;
+               wm8960_write(codec, WM8960_PLL1, reg | div);
+               break;
+       case WM8960_DCLKDIV:
+               reg = wm8960_read(codec, WM8960_CLOCK2) & 0x03f;
+               wm8960_write(codec, WM8960_CLOCK2, reg | div);
+               break;
+       case WM8960_TOCLKSEL:
+               reg = wm8960_read(codec, WM8960_ADDCTL1) & 0x1fd;
+               wm8960_write(codec, WM8960_ADDCTL1, reg | div);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+#define WM8960_RATES SNDRV_PCM_RATE_8000_48000
+
+#define WM8960_FORMATS \
+       (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+       SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8960_dai_ops = {
+       .hw_params = wm8960_hw_params,
+       .digital_mute = wm8960_mute,
+       .set_fmt = wm8960_set_dai_fmt,
+       .set_clkdiv = wm8960_set_dai_clkdiv,
+       .set_pll = wm8960_set_dai_pll,
+};
+
+struct snd_soc_dai wm8960_dai = {
+       .name = "WM8960",
+       .playback = {
+               .stream_name = "Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = WM8960_RATES,
+               .formats = WM8960_FORMATS,},
+       .capture = {
+               .stream_name = "Capture",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = WM8960_RATES,
+               .formats = WM8960_FORMATS,},
+       .ops = &wm8960_dai_ops,
+       .symmetric_rates = 1,
+};
+EXPORT_SYMBOL_GPL(wm8960_dai);
+
+static int wm8960_suspend(struct platform_device *pdev, pm_message_t state)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec = socdev->card->codec;
+
+       wm8960_set_bias_level(codec, SND_SOC_BIAS_OFF);
+       return 0;
+}
+
+static int wm8960_resume(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec = socdev->card->codec;
+       int i;
+       u8 data[2];
+       u16 *cache = codec->reg_cache;
+
+       /* Sync reg_cache with the hardware */
+       for (i = 0; i < ARRAY_SIZE(wm8960_reg); i++) {
+               data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+               data[1] = cache[i] & 0x00ff;
+               codec->hw_write(codec->control_data, data, 2);
+       }
+
+       wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+       wm8960_set_bias_level(codec, codec->suspend_bias_level);
+       return 0;
+}
+
+static struct snd_soc_codec *wm8960_codec;
+
+static int wm8960_probe(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec;
+       int ret = 0;
+
+       if (wm8960_codec == NULL) {
+               dev_err(&pdev->dev, "Codec device not registered\n");
+               return -ENODEV;
+       }
+
+       socdev->card->codec = wm8960_codec;
+       codec = wm8960_codec;
+
+       /* register pcms */
+       ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+       if (ret < 0) {
+               dev_err(codec->dev, "failed to create pcms: %d\n", ret);
+               goto pcm_err;
+       }
+
+       snd_soc_add_controls(codec, wm8960_snd_controls,
+                            ARRAY_SIZE(wm8960_snd_controls));
+       wm8960_add_widgets(codec);
+       ret = snd_soc_init_card(socdev);
+       if (ret < 0) {
+               dev_err(codec->dev, "failed to register card: %d\n", ret);
+               goto card_err;
+       }
+
+       return ret;
+
+card_err:
+       snd_soc_free_pcms(socdev);
+       snd_soc_dapm_free(socdev);
+pcm_err:
+       return ret;
+}
+
+/* power down chip */
+static int wm8960_remove(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+       snd_soc_free_pcms(socdev);
+       snd_soc_dapm_free(socdev);
+
+       return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8960 = {
+       .probe =        wm8960_probe,
+       .remove =       wm8960_remove,
+       .suspend =      wm8960_suspend,
+       .resume =       wm8960_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8960);
+
+static int wm8960_register(struct wm8960_priv *wm8960)
+{
+       struct wm8960_data *pdata = wm8960->codec.dev->platform_data;
+       struct snd_soc_codec *codec = &wm8960->codec;
+       int ret;
+       u16 reg;
+
+       if (wm8960_codec) {
+               dev_err(codec->dev, "Another WM8960 is registered\n");
+               return -EINVAL;
+       }
+
+       if (!pdata) {
+               dev_warn(codec->dev, "No platform data supplied\n");
+       } else {
+               if (pdata->dres > WM8960_DRES_MAX) {
+                       dev_err(codec->dev, "Invalid DRES: %d\n", pdata->dres);
+                       pdata->dres = 0;
+               }
+       }
+
+       mutex_init(&codec->mutex);
+       INIT_LIST_HEAD(&codec->dapm_widgets);
+       INIT_LIST_HEAD(&codec->dapm_paths);
+
+       codec->private_data = wm8960;
+       codec->name = "WM8960";
+       codec->owner = THIS_MODULE;
+       codec->read = wm8960_read_reg_cache;
+       codec->write = wm8960_write;
+       codec->bias_level = SND_SOC_BIAS_OFF;
+       codec->set_bias_level = wm8960_set_bias_level;
+       codec->dai = &wm8960_dai;
+       codec->num_dai = 1;
+       codec->reg_cache_size = WM8960_CACHEREGNUM;
+       codec->reg_cache = &wm8960->reg_cache;
+
+       memcpy(codec->reg_cache, wm8960_reg, sizeof(wm8960_reg));
+
+       ret = wm8960_reset(codec);
+       if (ret < 0) {
+               dev_err(codec->dev, "Failed to issue reset\n");
+               return ret;
+       }
+
+       wm8960_dai.dev = codec->dev;
+
+       wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       /* Latch the update bits */
+       reg = wm8960_read(codec, WM8960_LINVOL);
+       wm8960_write(codec, WM8960_LINVOL, reg | 0x100);
+       reg = wm8960_read(codec, WM8960_RINVOL);
+       wm8960_write(codec, WM8960_RINVOL, reg | 0x100);
+       reg = wm8960_read(codec, WM8960_LADC);
+       wm8960_write(codec, WM8960_LADC, reg | 0x100);
+       reg = wm8960_read(codec, WM8960_RADC);
+       wm8960_write(codec, WM8960_RADC, reg | 0x100);
+       reg = wm8960_read(codec, WM8960_LDAC);
+       wm8960_write(codec, WM8960_LDAC, reg | 0x100);
+       reg = wm8960_read(codec, WM8960_RDAC);
+       wm8960_write(codec, WM8960_RDAC, reg | 0x100);
+       reg = wm8960_read(codec, WM8960_LOUT1);
+       wm8960_write(codec, WM8960_LOUT1, reg | 0x100);
+       reg = wm8960_read(codec, WM8960_ROUT1);
+       wm8960_write(codec, WM8960_ROUT1, reg | 0x100);
+       reg = wm8960_read(codec, WM8960_LOUT2);
+       wm8960_write(codec, WM8960_LOUT2, reg | 0x100);
+       reg = wm8960_read(codec, WM8960_ROUT2);
+       wm8960_write(codec, WM8960_ROUT2, reg | 0x100);
+
+       wm8960_codec = codec;
+
+       ret = snd_soc_register_codec(codec);
+       if (ret != 0) {
+               dev_err(codec->dev, "Failed to register codec: %d\n", ret);
+               return ret;
+       }
+
+       ret = snd_soc_register_dai(&wm8960_dai);
+       if (ret != 0) {
+               dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
+               snd_soc_unregister_codec(codec);
+               return ret;
+       }
+
+       return 0;
+}
+
+static void wm8960_unregister(struct wm8960_priv *wm8960)
+{
+       wm8960_set_bias_level(&wm8960->codec, SND_SOC_BIAS_OFF);
+       snd_soc_unregister_dai(&wm8960_dai);
+       snd_soc_unregister_codec(&wm8960->codec);
+       kfree(wm8960);
+       wm8960_codec = NULL;
+}
+
+static __devinit int wm8960_i2c_probe(struct i2c_client *i2c,
+                                     const struct i2c_device_id *id)
+{
+       struct wm8960_priv *wm8960;
+       struct snd_soc_codec *codec;
+
+       wm8960 = kzalloc(sizeof(struct wm8960_priv), GFP_KERNEL);
+       if (wm8960 == NULL)
+               return -ENOMEM;
+
+       codec = &wm8960->codec;
+       codec->hw_write = (hw_write_t)i2c_master_send;
+
+       i2c_set_clientdata(i2c, wm8960);
+       codec->control_data = i2c;
+
+       codec->dev = &i2c->dev;
+
+       return wm8960_register(wm8960);
+}
+
+static __devexit int wm8960_i2c_remove(struct i2c_client *client)
+{
+       struct wm8960_priv *wm8960 = i2c_get_clientdata(client);
+       wm8960_unregister(wm8960);
+       return 0;
+}
+
+static const struct i2c_device_id wm8960_i2c_id[] = {
+       { "wm8960", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id);
+
+static struct i2c_driver wm8960_i2c_driver = {
+       .driver = {
+               .name = "WM8960 I2C Codec",
+               .owner = THIS_MODULE,
+       },
+       .probe =    wm8960_i2c_probe,
+       .remove =   __devexit_p(wm8960_i2c_remove),
+       .id_table = wm8960_i2c_id,
+};
+
+static int __init wm8960_modinit(void)
+{
+       int ret;
+
+       ret = i2c_add_driver(&wm8960_i2c_driver);
+       if (ret != 0) {
+               printk(KERN_ERR "Failed to register WM8960 I2C driver: %d\n",
+                      ret);
+       }
+
+       return ret;
+}
+module_init(wm8960_modinit);
+
+static void __exit wm8960_exit(void)
+{
+       i2c_del_driver(&wm8960_i2c_driver);
+}
+module_exit(wm8960_exit);
+
+
+MODULE_DESCRIPTION("ASoC WM8960 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8960.h b/sound/soc/codecs/wm8960.h
new file mode 100644 (file)
index 0000000..c9af56c
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * wm8960.h  --  WM8960 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 _WM8960_H
+#define _WM8960_H
+
+/* WM8960 register space */
+
+
+#define WM8960_CACHEREGNUM     56
+
+#define WM8960_LINVOL          0x0
+#define WM8960_RINVOL          0x1
+#define WM8960_LOUT1           0x2
+#define WM8960_ROUT1           0x3
+#define WM8960_CLOCK1          0x4
+#define WM8960_DACCTL1         0x5
+#define WM8960_DACCTL2         0x6
+#define WM8960_IFACE1          0x7
+#define WM8960_CLOCK2          0x8
+#define WM8960_IFACE2          0x9
+#define WM8960_LDAC            0xa
+#define WM8960_RDAC            0xb
+
+#define WM8960_RESET           0xf
+#define WM8960_3D              0x10
+#define WM8960_ALC1            0x11
+#define WM8960_ALC2            0x12
+#define WM8960_ALC3            0x13
+#define WM8960_NOISEG          0x14
+#define WM8960_LADC            0x15
+#define WM8960_RADC            0x16
+#define WM8960_ADDCTL1         0x17
+#define WM8960_ADDCTL2         0x18
+#define WM8960_POWER1          0x19
+#define WM8960_POWER2          0x1a
+#define WM8960_ADDCTL3         0x1b
+#define WM8960_APOP1           0x1c
+#define WM8960_APOP2           0x1d
+
+#define WM8960_LINPATH         0x20
+#define WM8960_RINPATH         0x21
+#define WM8960_LOUTMIX         0x22
+
+#define WM8960_ROUTMIX         0x25
+#define WM8960_MONOMIX1                0x26
+#define WM8960_MONOMIX2                0x27
+#define WM8960_LOUT2           0x28
+#define WM8960_ROUT2           0x29
+#define WM8960_MONO            0x2a
+#define WM8960_INBMIX1         0x2b
+#define WM8960_INBMIX2         0x2c
+#define WM8960_BYPASS1         0x2d
+#define WM8960_BYPASS2         0x2e
+#define WM8960_POWER3          0x2f
+#define WM8960_ADDCTL4         0x30
+#define WM8960_CLASSD1         0x31
+
+#define WM8960_CLASSD3         0x33
+#define WM8960_PLL1            0x34
+#define WM8960_PLL2            0x35
+#define WM8960_PLL3            0x36
+#define WM8960_PLL4            0x37
+
+
+/*
+ * WM8960 Clock dividers
+ */
+#define WM8960_SYSCLKDIV               0
+#define WM8960_DACDIV                  1
+#define WM8960_OPCLKDIV                        2
+#define WM8960_DCLKDIV                 3
+#define WM8960_TOCLKSEL                        4
+#define WM8960_SYSCLKSEL               5
+
+#define WM8960_SYSCLK_DIV_1            (0 << 1)
+#define WM8960_SYSCLK_DIV_2            (2 << 1)
+
+#define WM8960_SYSCLK_MCLK             (0 << 0)
+#define WM8960_SYSCLK_PLL              (1 << 0)
+
+#define WM8960_DAC_DIV_1               (0 << 3)
+#define WM8960_DAC_DIV_1_5             (1 << 3)
+#define WM8960_DAC_DIV_2               (2 << 3)
+#define WM8960_DAC_DIV_3               (3 << 3)
+#define WM8960_DAC_DIV_4               (4 << 3)
+#define WM8960_DAC_DIV_5_5             (5 << 3)
+#define WM8960_DAC_DIV_6               (6 << 3)
+
+#define WM8960_DCLK_DIV_1_5            (0 << 6)
+#define WM8960_DCLK_DIV_2              (1 << 6)
+#define WM8960_DCLK_DIV_3              (2 << 6)
+#define WM8960_DCLK_DIV_4              (3 << 6)
+#define WM8960_DCLK_DIV_6              (4 << 6)
+#define WM8960_DCLK_DIV_8              (5 << 6)
+#define WM8960_DCLK_DIV_12             (6 << 6)
+#define WM8960_DCLK_DIV_16             (7 << 6)
+
+#define WM8960_TOCLK_F19               (0 << 1)
+#define WM8960_TOCLK_F21               (1 << 1)
+
+#define WM8960_OPCLK_DIV_1             (0 << 0)
+#define WM8960_OPCLK_DIV_2             (1 << 0)
+#define WM8960_OPCLK_DIV_3             (2 << 0)
+#define WM8960_OPCLK_DIV_4             (3 << 0)
+#define WM8960_OPCLK_DIV_5_5           (4 << 0)
+#define WM8960_OPCLK_DIV_6             (5 << 0)
+
+extern struct snd_soc_dai wm8960_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8960;
+
+#define WM8960_DRES_400R 0
+#define WM8960_DRES_200R 1
+#define WM8960_DRES_600R 2
+#define WM8960_DRES_150R 3
+#define WM8960_DRES_MAX  3
+
+struct wm8960_data {
+       int dres;
+};
+
+#endif
diff --git a/sound/soc/codecs/wm8988.c b/sound/soc/codecs/wm8988.c
new file mode 100644 (file)
index 0000000..c05f718
--- /dev/null
@@ -0,0 +1,1097 @@
+/*
+ * wm8988.c -- WM8988 ALSA SoC audio driver
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * 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.
+ */
+
+#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/spi/spi.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 "wm8988.h"
+
+/*
+ * wm8988 register cache
+ * We can't read the WM8988 register space when we
+ * are using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8988_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 */
+};
+
+/* codec private data */
+struct wm8988_priv {
+       unsigned int sysclk;
+       struct snd_soc_codec codec;
+       struct snd_pcm_hw_constraint_list *sysclk_constraints;
+       u16 reg_cache[WM8988_NUM_REG];
+};
+
+
+/*
+ * read wm8988 register cache
+ */
+static inline unsigned int wm8988_read_reg_cache(struct snd_soc_codec *codec,
+       unsigned int reg)
+{
+       u16 *cache = codec->reg_cache;
+       if (reg > WM8988_NUM_REG)
+               return -1;
+       return cache[reg];
+}
+
+/*
+ * write wm8988 register cache
+ */
+static inline void wm8988_write_reg_cache(struct snd_soc_codec *codec,
+       unsigned int reg, unsigned int value)
+{
+       u16 *cache = codec->reg_cache;
+       if (reg > WM8988_NUM_REG)
+               return;
+       cache[reg] = value;
+}
+
+static int wm8988_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;
+
+       wm8988_write_reg_cache(codec, reg, value);
+       if (codec->hw_write(codec->control_data, data, 2) == 2)
+               return 0;
+       else
+               return -EIO;
+}
+
+#define wm8988_reset(c)        wm8988_write(c, WM8988_RESET, 0)
+
+/*
+ * WM8988 Controls
+ */
+
+static const char *bass_boost_txt[] = {"Linear Control", "Adaptive Boost"};
+static const struct soc_enum bass_boost =
+       SOC_ENUM_SINGLE(WM8988_BASS, 7, 2, bass_boost_txt);
+
+static const char *bass_filter_txt[] = { "130Hz @ 48kHz", "200Hz @ 48kHz" };
+static const struct soc_enum bass_filter =
+       SOC_ENUM_SINGLE(WM8988_BASS, 6, 2, bass_filter_txt);
+
+static const char *treble_txt[] = {"8kHz", "4kHz"};
+static const struct soc_enum treble =
+       SOC_ENUM_SINGLE(WM8988_TREBLE, 6, 2, treble_txt);
+
+static const char *stereo_3d_lc_txt[] = {"200Hz", "500Hz"};
+static const struct soc_enum stereo_3d_lc =
+       SOC_ENUM_SINGLE(WM8988_3D, 5, 2, stereo_3d_lc_txt);
+
+static const char *stereo_3d_uc_txt[] = {"2.2kHz", "1.5kHz"};
+static const struct soc_enum stereo_3d_uc =
+       SOC_ENUM_SINGLE(WM8988_3D, 6, 2, stereo_3d_uc_txt);
+
+static const char *stereo_3d_func_txt[] = {"Capture", "Playback"};
+static const struct soc_enum stereo_3d_func =
+       SOC_ENUM_SINGLE(WM8988_3D, 7, 2, stereo_3d_func_txt);
+
+static const char *alc_func_txt[] = {"Off", "Right", "Left", "Stereo"};
+static const struct soc_enum alc_func =
+       SOC_ENUM_SINGLE(WM8988_ALC1, 7, 4, alc_func_txt);
+
+static const char *ng_type_txt[] = {"Constant PGA Gain",
+                                   "Mute ADC Output"};
+static const struct soc_enum ng_type =
+       SOC_ENUM_SINGLE(WM8988_NGATE, 1, 2, ng_type_txt);
+
+static const char *deemph_txt[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+static const struct soc_enum deemph =
+       SOC_ENUM_SINGLE(WM8988_ADCDAC, 1, 4, deemph_txt);
+
+static const char *adcpol_txt[] = {"Normal", "L Invert", "R Invert",
+                                  "L + R Invert"};
+static const struct soc_enum adcpol =
+       SOC_ENUM_SINGLE(WM8988_ADCDAC, 5, 4, adcpol_txt);
+
+static const DECLARE_TLV_DB_SCALE(pga_tlv, -1725, 75, 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 DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
+static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0);
+
+static const struct snd_kcontrol_new wm8988_snd_controls[] = {
+
+SOC_ENUM("Bass Boost", bass_boost),
+SOC_ENUM("Bass Filter", bass_filter),
+SOC_SINGLE("Bass Volume", WM8988_BASS, 0, 15, 1),
+
+SOC_SINGLE("Treble Volume", WM8988_TREBLE, 0, 15, 0),
+SOC_ENUM("Treble Cut-off", treble),
+
+SOC_SINGLE("3D Switch", WM8988_3D, 0, 1, 0),
+SOC_SINGLE("3D Volume", WM8988_3D, 1, 15, 0),
+SOC_ENUM("3D Lower Cut-off", stereo_3d_lc),
+SOC_ENUM("3D Upper Cut-off", stereo_3d_uc),
+SOC_ENUM("3D Mode", stereo_3d_func),
+
+SOC_SINGLE("ALC Capture Target Volume", WM8988_ALC1, 0, 7, 0),
+SOC_SINGLE("ALC Capture Max Volume", WM8988_ALC1, 4, 7, 0),
+SOC_ENUM("ALC Capture Function", alc_func),
+SOC_SINGLE("ALC Capture ZC Switch", WM8988_ALC2, 7, 1, 0),
+SOC_SINGLE("ALC Capture Hold Time", WM8988_ALC2, 0, 15, 0),
+SOC_SINGLE("ALC Capture Decay Time", WM8988_ALC3, 4, 15, 0),
+SOC_SINGLE("ALC Capture Attack Time", WM8988_ALC3, 0, 15, 0),
+SOC_SINGLE("ALC Capture NG Threshold", WM8988_NGATE, 3, 31, 0),
+SOC_ENUM("ALC Capture NG Type", ng_type),
+SOC_SINGLE("ALC Capture NG Switch", WM8988_NGATE, 0, 1, 0),
+
+SOC_SINGLE("ZC Timeout Switch", WM8988_ADCTL1, 0, 1, 0),
+
+SOC_DOUBLE_R_TLV("Capture Digital Volume", WM8988_LADC, WM8988_RADC,
+                0, 255, 0, adc_tlv),
+SOC_DOUBLE_R_TLV("Capture Volume", WM8988_LINVOL, WM8988_RINVOL,
+                0, 63, 0, pga_tlv),
+SOC_DOUBLE_R("Capture ZC Switch", WM8988_LINVOL, WM8988_RINVOL, 6, 1, 0),
+SOC_DOUBLE_R("Capture Switch", WM8988_LINVOL, WM8988_RINVOL, 7, 1, 1),
+
+SOC_ENUM("Playback De-emphasis", deemph),
+
+SOC_ENUM("Capture Polarity", adcpol),
+SOC_SINGLE("Playback 6dB Attenuate", WM8988_ADCDAC, 7, 1, 0),
+SOC_SINGLE("Capture 6dB Attenuate", WM8988_ADCDAC, 8, 1, 0),
+
+SOC_DOUBLE_R_TLV("PCM Volume", WM8988_LDAC, WM8988_RDAC, 0, 255, 0, dac_tlv),
+
+SOC_SINGLE_TLV("Left Mixer Left Bypass Volume", WM8988_LOUTM1, 4, 7, 1,
+              bypass_tlv),
+SOC_SINGLE_TLV("Left Mixer Right Bypass Volume", WM8988_LOUTM2, 4, 7, 1,
+              bypass_tlv),
+SOC_SINGLE_TLV("Right Mixer Left Bypass Volume", WM8988_ROUTM1, 4, 7, 1,
+              bypass_tlv),
+SOC_SINGLE_TLV("Right Mixer Right Bypass Volume", WM8988_ROUTM2, 4, 7, 1,
+              bypass_tlv),
+
+SOC_DOUBLE_R("Output 1 Playback ZC Switch", WM8988_LOUT1V,
+            WM8988_ROUT1V, 7, 1, 0),
+SOC_DOUBLE_R_TLV("Output 1 Playback Volume", WM8988_LOUT1V, WM8988_ROUT1V,
+                0, 127, 0, out_tlv),
+
+SOC_DOUBLE_R("Output 2 Playback ZC Switch", WM8988_LOUT2V,
+            WM8988_ROUT2V, 7, 1, 0),
+SOC_DOUBLE_R_TLV("Output 2 Playback Volume", WM8988_LOUT2V, WM8988_ROUT2V,
+                0, 127, 0, out_tlv),
+
+};
+
+/*
+ * DAPM Controls
+ */
+
+static int wm8988_lrc_control(struct snd_soc_dapm_widget *w,
+                             struct snd_kcontrol *kcontrol, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       u16 adctl2 = wm8988_read_reg_cache(codec, WM8988_ADCTL2);
+
+       /* Use the DAC to gate LRC if active, otherwise use ADC */
+       if (wm8988_read_reg_cache(codec, WM8988_PWR2) & 0x180)
+               adctl2 &= ~0x4;
+       else
+               adctl2 |= 0x4;
+
+       return wm8988_write(codec, WM8988_ADCTL2, adctl2);
+}
+
+static const char *wm8988_line_texts[] = {
+       "Line 1", "Line 2", "PGA", "Differential"};
+
+static const unsigned int wm8988_line_values[] = {
+       0, 1, 3, 4};
+
+static const struct soc_enum wm8988_lline_enum =
+       SOC_VALUE_ENUM_SINGLE(WM8988_LOUTM1, 0, 7,
+                             ARRAY_SIZE(wm8988_line_texts),
+                             wm8988_line_texts,
+                             wm8988_line_values);
+static const struct snd_kcontrol_new wm8988_left_line_controls =
+       SOC_DAPM_VALUE_ENUM("Route", wm8988_lline_enum);
+
+static const struct soc_enum wm8988_rline_enum =
+       SOC_VALUE_ENUM_SINGLE(WM8988_ROUTM1, 0, 7,
+                             ARRAY_SIZE(wm8988_line_texts),
+                             wm8988_line_texts,
+                             wm8988_line_values);
+static const struct snd_kcontrol_new wm8988_right_line_controls =
+       SOC_DAPM_VALUE_ENUM("Route", wm8988_lline_enum);
+
+/* Left Mixer */
+static const struct snd_kcontrol_new wm8988_left_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Playback Switch", WM8988_LOUTM1, 8, 1, 0),
+       SOC_DAPM_SINGLE("Left Bypass Switch", WM8988_LOUTM1, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right Playback Switch", WM8988_LOUTM2, 8, 1, 0),
+       SOC_DAPM_SINGLE("Right Bypass Switch", WM8988_LOUTM2, 7, 1, 0),
+};
+
+/* Right Mixer */
+static const struct snd_kcontrol_new wm8988_right_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left Playback Switch", WM8988_ROUTM1, 8, 1, 0),
+       SOC_DAPM_SINGLE("Left Bypass Switch", WM8988_ROUTM1, 7, 1, 0),
+       SOC_DAPM_SINGLE("Playback Switch", WM8988_ROUTM2, 8, 1, 0),
+       SOC_DAPM_SINGLE("Right Bypass Switch", WM8988_ROUTM2, 7, 1, 0),
+};
+
+static const char *wm8988_pga_sel[] = {"Line 1", "Line 2", "Differential"};
+static const unsigned int wm8988_pga_val[] = { 0, 1, 3 };
+
+/* Left PGA Mux */
+static const struct soc_enum wm8988_lpga_enum =
+       SOC_VALUE_ENUM_SINGLE(WM8988_LADCIN, 6, 3,
+                             ARRAY_SIZE(wm8988_pga_sel),
+                             wm8988_pga_sel,
+                             wm8988_pga_val);
+static const struct snd_kcontrol_new wm8988_left_pga_controls =
+       SOC_DAPM_VALUE_ENUM("Route", wm8988_lpga_enum);
+
+/* Right PGA Mux */
+static const struct soc_enum wm8988_rpga_enum =
+       SOC_VALUE_ENUM_SINGLE(WM8988_RADCIN, 6, 3,
+                             ARRAY_SIZE(wm8988_pga_sel),
+                             wm8988_pga_sel,
+                             wm8988_pga_val);
+static const struct snd_kcontrol_new wm8988_right_pga_controls =
+       SOC_DAPM_VALUE_ENUM("Route", wm8988_rpga_enum);
+
+/* Differential Mux */
+static const char *wm8988_diff_sel[] = {"Line 1", "Line 2"};
+static const struct soc_enum diffmux =
+       SOC_ENUM_SINGLE(WM8988_ADCIN, 8, 2, wm8988_diff_sel);
+static const struct snd_kcontrol_new wm8988_diffmux_controls =
+       SOC_DAPM_ENUM("Route", diffmux);
+
+/* Mono ADC Mux */
+static const char *wm8988_mono_mux[] = {"Stereo", "Mono (Left)",
+       "Mono (Right)", "Digital Mono"};
+static const struct soc_enum monomux =
+       SOC_ENUM_SINGLE(WM8988_ADCIN, 6, 4, wm8988_mono_mux);
+static const struct snd_kcontrol_new wm8988_monomux_controls =
+       SOC_DAPM_ENUM("Route", monomux);
+
+static const struct snd_soc_dapm_widget wm8988_dapm_widgets[] = {
+       SND_SOC_DAPM_MICBIAS("Mic Bias", WM8988_PWR1, 1, 0),
+
+       SND_SOC_DAPM_MUX("Differential Mux", SND_SOC_NOPM, 0, 0,
+               &wm8988_diffmux_controls),
+       SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0,
+               &wm8988_monomux_controls),
+       SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0,
+               &wm8988_monomux_controls),
+
+       SND_SOC_DAPM_MUX("Left PGA Mux", WM8988_PWR1, 5, 0,
+               &wm8988_left_pga_controls),
+       SND_SOC_DAPM_MUX("Right PGA Mux", WM8988_PWR1, 4, 0,
+               &wm8988_right_pga_controls),
+
+       SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0,
+               &wm8988_left_line_controls),
+       SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0,
+               &wm8988_right_line_controls),
+
+       SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8988_PWR1, 2, 0),
+       SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8988_PWR1, 3, 0),
+
+       SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8988_PWR2, 7, 0),
+       SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8988_PWR2, 8, 0),
+
+       SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0,
+               &wm8988_left_mixer_controls[0],
+               ARRAY_SIZE(wm8988_left_mixer_controls)),
+       SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0,
+               &wm8988_right_mixer_controls[0],
+               ARRAY_SIZE(wm8988_right_mixer_controls)),
+
+       SND_SOC_DAPM_PGA("Right Out 2", WM8988_PWR2, 3, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("Left Out 2", WM8988_PWR2, 4, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("Right Out 1", WM8988_PWR2, 5, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("Left Out 1", WM8988_PWR2, 6, 0, NULL, 0),
+
+       SND_SOC_DAPM_POST("LRC control", wm8988_lrc_control),
+
+       SND_SOC_DAPM_OUTPUT("LOUT1"),
+       SND_SOC_DAPM_OUTPUT("ROUT1"),
+       SND_SOC_DAPM_OUTPUT("LOUT2"),
+       SND_SOC_DAPM_OUTPUT("ROUT2"),
+       SND_SOC_DAPM_OUTPUT("VREF"),
+
+       SND_SOC_DAPM_INPUT("LINPUT1"),
+       SND_SOC_DAPM_INPUT("LINPUT2"),
+       SND_SOC_DAPM_INPUT("RINPUT1"),
+       SND_SOC_DAPM_INPUT("RINPUT2"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+
+       { "Left Line Mux", "Line 1", "LINPUT1" },
+       { "Left Line Mux", "Line 2", "LINPUT2" },
+       { "Left Line Mux", "PGA", "Left PGA Mux" },
+       { "Left Line Mux", "Differential", "Differential Mux" },
+
+       { "Right Line Mux", "Line 1", "RINPUT1" },
+       { "Right Line Mux", "Line 2", "RINPUT2" },
+       { "Right Line Mux", "PGA", "Right PGA Mux" },
+       { "Right Line Mux", "Differential", "Differential Mux" },
+
+       { "Left PGA Mux", "Line 1", "LINPUT1" },
+       { "Left PGA Mux", "Line 2", "LINPUT2" },
+       { "Left PGA Mux", "Differential", "Differential Mux" },
+
+       { "Right PGA Mux", "Line 1", "RINPUT1" },
+       { "Right PGA Mux", "Line 2", "RINPUT2" },
+       { "Right PGA Mux", "Differential", "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", "Stereo", "Left PGA Mux" },
+       { "Left ADC Mux", "Mono (Left)", "Left PGA Mux" },
+       { "Left ADC Mux", "Digital Mono", "Left PGA Mux" },
+
+       { "Right ADC Mux", "Stereo", "Right PGA Mux" },
+       { "Right ADC Mux", "Mono (Right)", "Right PGA Mux" },
+       { "Right ADC Mux", "Digital Mono", "Right PGA Mux" },
+
+       { "Left ADC", NULL, "Left ADC Mux" },
+       { "Right ADC", NULL, "Right ADC Mux" },
+
+       { "Left Line Mux", "Line 1", "LINPUT1" },
+       { "Left Line Mux", "Line 2", "LINPUT2" },
+       { "Left Line Mux", "PGA", "Left PGA Mux" },
+       { "Left Line Mux", "Differential", "Differential Mux" },
+
+       { "Right Line Mux", "Line 1", "RINPUT1" },
+       { "Right Line Mux", "Line 2", "RINPUT2" },
+       { "Right Line Mux", "PGA", "Right PGA Mux" },
+       { "Right Line Mux", "Differential", "Differential Mux" },
+
+       { "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", "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", NULL, "Left Mixer" },
+       { "LOUT1", NULL, "Left Out 1" },
+       { "Right Out 1", NULL, "Right Mixer" },
+       { "ROUT1", NULL, "Right Out 1" },
+
+       { "Left Out 2", NULL, "Left Mixer" },
+       { "LOUT2", NULL, "Left Out 2" },
+       { "Right Out 2", NULL, "Right Mixer" },
+       { "ROUT2", NULL, "Right Out 2" },
+};
+
+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;
+       }
+
+       return -EINVAL;
+}
+
+/* The set of rates we can generate from the above for each SYSCLK */
+
+static unsigned int rates_12288[] = {
+       8000, 12000, 16000, 24000, 24000, 32000, 48000, 96000,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_12288 = {
+       .count  = ARRAY_SIZE(rates_12288),
+       .list   = rates_12288,
+};
+
+static unsigned int rates_112896[] = {
+       8000, 11025, 22050, 44100,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_112896 = {
+       .count  = ARRAY_SIZE(rates_112896),
+       .list   = rates_112896,
+};
+
+static unsigned int rates_12[] = {
+       8000, 11025, 12000, 16000, 22050, 2400, 32000, 41100, 48000,
+       48000, 88235, 96000,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_12 = {
+       .count  = ARRAY_SIZE(rates_12),
+       .list   = rates_12,
+};
+
+/*
+ * Note that this should be called from init rather than from hw_params.
+ */
+static int wm8988_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 wm8988_priv *wm8988 = codec->private_data;
+
+       switch (freq) {
+       case 11289600:
+       case 18432000:
+       case 22579200:
+       case 36864000:
+               wm8988->sysclk_constraints = &constraints_112896;
+               wm8988->sysclk = freq;
+               return 0;
+
+       case 12288000:
+       case 16934400:
+       case 24576000:
+       case 33868800:
+               wm8988->sysclk_constraints = &constraints_12288;
+               wm8988->sysclk = freq;
+               return 0;
+
+       case 12000000:
+       case 24000000:
+               wm8988->sysclk_constraints = &constraints_12;
+               wm8988->sysclk = freq;
+               return 0;
+       }
+       return -EINVAL;
+}
+
+static int wm8988_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;
+       }
+
+       wm8988_write(codec, WM8988_IFACE, iface);
+       return 0;
+}
+
+static int wm8988_pcm_startup(struct snd_pcm_substream *substream,
+                             struct snd_soc_dai *dai)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct wm8988_priv *wm8988 = codec->private_data;
+
+       /* The set of sample rates that can be supported depends on the
+        * MCLK supplied to the CODEC - enforce this.
+        */
+       if (!wm8988->sysclk) {
+               dev_err(codec->dev,
+                       "No MCLK configured, call set_sysclk() on init\n");
+               return -EINVAL;
+       }
+
+       snd_pcm_hw_constraint_list(substream->runtime, 0,
+                                  SNDRV_PCM_HW_PARAM_RATE,
+                                  wm8988->sysclk_constraints);
+
+       return 0;
+}
+
+static int wm8988_pcm_hw_params(struct snd_pcm_substream *substream,
+                               struct snd_pcm_hw_params *params,
+                               struct snd_soc_dai *dai)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_device *socdev = rtd->socdev;
+       struct snd_soc_codec *codec = socdev->card->codec;
+       struct wm8988_priv *wm8988 = codec->private_data;
+       u16 iface = wm8988_read_reg_cache(codec, WM8988_IFACE) & 0x1f3;
+       u16 srate = wm8988_read_reg_cache(codec, WM8988_SRATE) & 0x180;
+       int coeff;
+
+       coeff = get_coeff(wm8988->sysclk, params_rate(params));
+       if (coeff < 0) {
+               coeff = get_coeff(wm8988->sysclk / 2, params_rate(params));
+               srate |= 0x40;
+       }
+       if (coeff < 0) {
+               dev_err(codec->dev,
+                       "Unable to configure sample rate %dHz with %dHz MCLK\n",
+                       params_rate(params), wm8988->sysclk);
+               return coeff;
+       }
+
+       /* 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 */
+       wm8988_write(codec, WM8988_IFACE, iface);
+       if (coeff >= 0)
+               wm8988_write(codec, WM8988_SRATE, srate |
+                       (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb);
+
+       return 0;
+}
+
+static int wm8988_mute(struct snd_soc_dai *dai, int mute)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       u16 mute_reg = wm8988_read_reg_cache(codec, WM8988_ADCDAC) & 0xfff7;
+
+       if (mute)
+               wm8988_write(codec, WM8988_ADCDAC, mute_reg | 0x8);
+       else
+               wm8988_write(codec, WM8988_ADCDAC, mute_reg);
+       return 0;
+}
+
+static int wm8988_set_bias_level(struct snd_soc_codec *codec,
+                                enum snd_soc_bias_level level)
+{
+       u16 pwr_reg = wm8988_read_reg_cache(codec, WM8988_PWR1) & ~0x1c1;
+
+       switch (level) {
+       case SND_SOC_BIAS_ON:
+               break;
+
+       case SND_SOC_BIAS_PREPARE:
+               /* VREF, VMID=2x50k, digital enabled */
+               wm8988_write(codec, WM8988_PWR1, pwr_reg | 0x00c0);
+               break;
+
+       case SND_SOC_BIAS_STANDBY:
+               if (codec->bias_level == SND_SOC_BIAS_OFF) {
+                       /* VREF, VMID=2x5k */
+                       wm8988_write(codec, WM8988_PWR1, pwr_reg | 0x1c1);
+
+                       /* Charge caps */
+                       msleep(100);
+               }
+
+               /* VREF, VMID=2*500k, digital stopped */
+               wm8988_write(codec, WM8988_PWR1, pwr_reg | 0x0141);
+               break;
+
+       case SND_SOC_BIAS_OFF:
+               wm8988_write(codec, WM8988_PWR1, 0x0000);
+               break;
+       }
+       codec->bias_level = level;
+       return 0;
+}
+
+#define WM8988_RATES SNDRV_PCM_RATE_8000_96000
+
+#define WM8988_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+       SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8988_ops = {
+       .startup = wm8988_pcm_startup,
+       .hw_params = wm8988_pcm_hw_params,
+       .set_fmt = wm8988_set_dai_fmt,
+       .set_sysclk = wm8988_set_dai_sysclk,
+       .digital_mute = wm8988_mute,
+};
+
+struct snd_soc_dai wm8988_dai = {
+       .name = "WM8988",
+       .playback = {
+               .stream_name = "Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = WM8988_RATES,
+               .formats = WM8988_FORMATS,
+       },
+       .capture = {
+               .stream_name = "Capture",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = WM8988_RATES,
+               .formats = WM8988_FORMATS,
+        },
+       .ops = &wm8988_ops,
+       .symmetric_rates = 1,
+};
+EXPORT_SYMBOL_GPL(wm8988_dai);
+
+static int wm8988_suspend(struct platform_device *pdev, pm_message_t state)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec = socdev->card->codec;
+
+       wm8988_set_bias_level(codec, SND_SOC_BIAS_OFF);
+       return 0;
+}
+
+static int wm8988_resume(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec = socdev->card->codec;
+       int i;
+       u8 data[2];
+       u16 *cache = codec->reg_cache;
+
+       /* Sync reg_cache with the hardware */
+       for (i = 0; i < WM8988_NUM_REG; i++) {
+               if (i == WM8988_RESET)
+                       continue;
+               data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+               data[1] = cache[i] & 0x00ff;
+               codec->hw_write(codec->control_data, data, 2);
+       }
+
+       wm8988_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       return 0;
+}
+
+static struct snd_soc_codec *wm8988_codec;
+
+static int wm8988_probe(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec;
+       int ret = 0;
+
+       if (wm8988_codec == NULL) {
+               dev_err(&pdev->dev, "Codec device not registered\n");
+               return -ENODEV;
+       }
+
+       socdev->card->codec = wm8988_codec;
+       codec = wm8988_codec;
+
+       /* register pcms */
+       ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+       if (ret < 0) {
+               dev_err(codec->dev, "failed to create pcms: %d\n", ret);
+               goto pcm_err;
+       }
+
+       snd_soc_add_controls(codec, wm8988_snd_controls,
+                               ARRAY_SIZE(wm8988_snd_controls));
+       snd_soc_dapm_new_controls(codec, wm8988_dapm_widgets,
+                                 ARRAY_SIZE(wm8988_dapm_widgets));
+       snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+       snd_soc_dapm_new_widgets(codec);
+
+       ret = snd_soc_init_card(socdev);
+       if (ret < 0) {
+               dev_err(codec->dev, "failed to register card: %d\n", ret);
+               goto card_err;
+       }
+
+       return ret;
+
+card_err:
+       snd_soc_free_pcms(socdev);
+       snd_soc_dapm_free(socdev);
+pcm_err:
+       return ret;
+}
+
+static int wm8988_remove(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+       snd_soc_free_pcms(socdev);
+       snd_soc_dapm_free(socdev);
+
+       return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8988 = {
+       .probe =        wm8988_probe,
+       .remove =       wm8988_remove,
+       .suspend =      wm8988_suspend,
+       .resume =       wm8988_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8988);
+
+static int wm8988_register(struct wm8988_priv *wm8988)
+{
+       struct snd_soc_codec *codec = &wm8988->codec;
+       int ret;
+       u16 reg;
+
+       if (wm8988_codec) {
+               dev_err(codec->dev, "Another WM8988 is registered\n");
+               ret = -EINVAL;
+               goto err;
+       }
+
+       mutex_init(&codec->mutex);
+       INIT_LIST_HEAD(&codec->dapm_widgets);
+       INIT_LIST_HEAD(&codec->dapm_paths);
+
+       codec->private_data = wm8988;
+       codec->name = "WM8988";
+       codec->owner = THIS_MODULE;
+       codec->read = wm8988_read_reg_cache;
+       codec->write = wm8988_write;
+       codec->dai = &wm8988_dai;
+       codec->num_dai = 1;
+       codec->reg_cache_size = ARRAY_SIZE(wm8988->reg_cache);
+       codec->reg_cache = &wm8988->reg_cache;
+       codec->bias_level = SND_SOC_BIAS_OFF;
+       codec->set_bias_level = wm8988_set_bias_level;
+
+       memcpy(codec->reg_cache, wm8988_reg,
+              sizeof(wm8988_reg));
+
+       ret = wm8988_reset(codec);
+       if (ret < 0) {
+               dev_err(codec->dev, "Failed to issue reset\n");
+               return ret;
+       }
+
+       /* set the update bits (we always update left then right) */
+       reg = wm8988_read_reg_cache(codec, WM8988_RADC);
+       wm8988_write(codec, WM8988_RADC, reg | 0x100);
+       reg = wm8988_read_reg_cache(codec, WM8988_RDAC);
+       wm8988_write(codec, WM8988_RDAC, reg | 0x0100);
+       reg = wm8988_read_reg_cache(codec, WM8988_ROUT1V);
+       wm8988_write(codec, WM8988_ROUT1V, reg | 0x0100);
+       reg = wm8988_read_reg_cache(codec, WM8988_ROUT2V);
+       wm8988_write(codec, WM8988_ROUT2V, reg | 0x0100);
+       reg = wm8988_read_reg_cache(codec, WM8988_RINVOL);
+       wm8988_write(codec, WM8988_RINVOL, reg | 0x0100);
+
+       wm8988_set_bias_level(&wm8988->codec, SND_SOC_BIAS_STANDBY);
+
+       wm8988_dai.dev = codec->dev;
+
+       wm8988_codec = codec;
+
+       ret = snd_soc_register_codec(codec);
+       if (ret != 0) {
+               dev_err(codec->dev, "Failed to register codec: %d\n", ret);
+               return ret;
+       }
+
+       ret = snd_soc_register_dai(&wm8988_dai);
+       if (ret != 0) {
+               dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
+               snd_soc_unregister_codec(codec);
+               return ret;
+       }
+
+       return 0;
+
+err:
+       kfree(wm8988);
+       return ret;
+}
+
+static void wm8988_unregister(struct wm8988_priv *wm8988)
+{
+       wm8988_set_bias_level(&wm8988->codec, SND_SOC_BIAS_OFF);
+       snd_soc_unregister_dai(&wm8988_dai);
+       snd_soc_unregister_codec(&wm8988->codec);
+       kfree(wm8988);
+       wm8988_codec = NULL;
+}
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static int wm8988_i2c_probe(struct i2c_client *i2c,
+                           const struct i2c_device_id *id)
+{
+       struct wm8988_priv *wm8988;
+       struct snd_soc_codec *codec;
+
+       wm8988 = kzalloc(sizeof(struct wm8988_priv), GFP_KERNEL);
+       if (wm8988 == NULL)
+               return -ENOMEM;
+
+       codec = &wm8988->codec;
+       codec->hw_write = (hw_write_t)i2c_master_send;
+
+       i2c_set_clientdata(i2c, wm8988);
+       codec->control_data = i2c;
+
+       codec->dev = &i2c->dev;
+
+       return wm8988_register(wm8988);
+}
+
+static int wm8988_i2c_remove(struct i2c_client *client)
+{
+       struct wm8988_priv *wm8988 = i2c_get_clientdata(client);
+       wm8988_unregister(wm8988);
+       return 0;
+}
+
+static const struct i2c_device_id wm8988_i2c_id[] = {
+       { "wm8988", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8988_i2c_id);
+
+static struct i2c_driver wm8988_i2c_driver = {
+       .driver = {
+               .name = "WM8988",
+               .owner = THIS_MODULE,
+       },
+       .probe = wm8988_i2c_probe,
+       .remove = wm8988_i2c_remove,
+       .id_table = wm8988_i2c_id,
+};
+#endif
+
+#if defined(CONFIG_SPI_MASTER)
+static int wm8988_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;
+}
+
+static int __devinit wm8988_spi_probe(struct spi_device *spi)
+{
+       struct wm8988_priv *wm8988;
+       struct snd_soc_codec *codec;
+
+       wm8988 = kzalloc(sizeof(struct wm8988_priv), GFP_KERNEL);
+       if (wm8988 == NULL)
+               return -ENOMEM;
+
+       codec = &wm8988->codec;
+       codec->hw_write = (hw_write_t)wm8988_spi_write;
+       codec->control_data = spi;
+       codec->dev = &spi->dev;
+
+       spi->dev.driver_data = wm8988;
+
+       return wm8988_register(wm8988);
+}
+
+static int __devexit wm8988_spi_remove(struct spi_device *spi)
+{
+       struct wm8988_priv *wm8988 = spi->dev.driver_data;
+
+       wm8988_unregister(wm8988);
+
+       return 0;
+}
+
+static struct spi_driver wm8988_spi_driver = {
+       .driver = {
+               .name   = "wm8988",
+               .bus    = &spi_bus_type,
+               .owner  = THIS_MODULE,
+       },
+       .probe          = wm8988_spi_probe,
+       .remove         = __devexit_p(wm8988_spi_remove),
+};
+#endif
+
+static int __init wm8988_modinit(void)
+{
+       int ret;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+       ret = i2c_add_driver(&wm8988_i2c_driver);
+       if (ret != 0)
+               pr_err("WM8988: Unable to register I2C driver: %d\n", ret);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+       ret = spi_register_driver(&wm8988_spi_driver);
+       if (ret != 0)
+               pr_err("WM8988: Unable to register SPI driver: %d\n", ret);
+#endif
+       return ret;
+}
+module_init(wm8988_modinit);
+
+static void __exit wm8988_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+       i2c_del_driver(&wm8988_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+       spi_unregister_driver(&wm8988_spi_driver);
+#endif
+}
+module_exit(wm8988_exit);
+
+
+MODULE_DESCRIPTION("ASoC WM8988 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8988.h b/sound/soc/codecs/wm8988.h
new file mode 100644 (file)
index 0000000..4552d37
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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 _WM8988_H
+#define _WM8988_H
+
+/* WM8988 register space */
+
+#define WM8988_LINVOL    0x00
+#define WM8988_RINVOL    0x01
+#define WM8988_LOUT1V    0x02
+#define WM8988_ROUT1V    0x03
+#define WM8988_ADCDAC    0x05
+#define WM8988_IFACE     0x07
+#define WM8988_SRATE     0x08
+#define WM8988_LDAC      0x0a
+#define WM8988_RDAC      0x0b
+#define WM8988_BASS      0x0c
+#define WM8988_TREBLE    0x0d
+#define WM8988_RESET     0x0f
+#define WM8988_3D        0x10
+#define WM8988_ALC1      0x11
+#define WM8988_ALC2      0x12
+#define WM8988_ALC3      0x13
+#define WM8988_NGATE     0x14
+#define WM8988_LADC      0x15
+#define WM8988_RADC      0x16
+#define WM8988_ADCTL1    0x17
+#define WM8988_ADCTL2    0x18
+#define WM8988_PWR1      0x19
+#define WM8988_PWR2      0x1a
+#define WM8988_ADCTL3    0x1b
+#define WM8988_ADCIN     0x1f
+#define WM8988_LADCIN    0x20
+#define WM8988_RADCIN    0x21
+#define WM8988_LOUTM1    0x22
+#define WM8988_LOUTM2    0x23
+#define WM8988_ROUTM1    0x24
+#define WM8988_ROUTM2    0x25
+#define WM8988_LOUT2V    0x28
+#define WM8988_ROUT2V    0x29
+#define WM8988_LPPB      0x43
+#define WM8988_NUM_REG   0x44
+
+#define WM8988_SYSCLK  0
+
+extern struct snd_soc_dai wm8988_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8988;
+
+#endif
index 40cd274eb1ef51f60588ffaf7ba09e6c44f10a1f..d029818350e9fe9d5778c3fbb4087b0245d58023 100644 (file)
@@ -998,7 +998,7 @@ static void pll_factors(struct _pll_div *pll_div, unsigned int target,
 
        if ((Ndiv < 6) || (Ndiv > 12))
                printk(KERN_WARNING
-               "WM8990 N value outwith recommended range! N = %d\n", Ndiv);
+               "WM8990 N value outwith recommended range! N = %u\n", Ndiv);
 
        pll_div->n = Ndiv;
        Nmod = target % source;
diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c
new file mode 100644 (file)
index 0000000..86fc57e
--- /dev/null
@@ -0,0 +1,1534 @@
+/*
+ * wm9081.c  --  WM9081 ALSA SoC Audio driver
+ *
+ * Author: Mark Brown
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 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 <sound/tlv.h>
+
+#include <sound/wm9081.h>
+#include "wm9081.h"
+
+static u16 wm9081_reg_defaults[] = {
+       0x0000,     /* R0  - Software Reset */
+       0x0000,     /* R1 */
+       0x00B9,     /* R2  - Analogue Lineout */
+       0x00B9,     /* R3  - Analogue Speaker PGA */
+       0x0001,     /* R4  - VMID Control */
+       0x0068,     /* R5  - Bias Control 1 */
+       0x0000,     /* R6 */
+       0x0000,     /* R7  - Analogue Mixer */
+       0x0000,     /* R8  - Anti Pop Control */
+       0x01DB,     /* R9  - Analogue Speaker 1 */
+       0x0018,     /* R10 - Analogue Speaker 2 */
+       0x0180,     /* R11 - Power Management */
+       0x0000,     /* R12 - Clock Control 1 */
+       0x0038,     /* R13 - Clock Control 2 */
+       0x4000,     /* R14 - Clock Control 3 */
+       0x0000,     /* R15 */
+       0x0000,     /* R16 - FLL Control 1 */
+       0x0200,     /* R17 - FLL Control 2 */
+       0x0000,     /* R18 - FLL Control 3 */
+       0x0204,     /* R19 - FLL Control 4 */
+       0x0000,     /* R20 - FLL Control 5 */
+       0x0000,     /* R21 */
+       0x0000,     /* R22 - Audio Interface 1 */
+       0x0002,     /* R23 - Audio Interface 2 */
+       0x0008,     /* R24 - Audio Interface 3 */
+       0x0022,     /* R25 - Audio Interface 4 */
+       0x0000,     /* R26 - Interrupt Status */
+       0x0006,     /* R27 - Interrupt Status Mask */
+       0x0000,     /* R28 - Interrupt Polarity */
+       0x0000,     /* R29 - Interrupt Control */
+       0x00C0,     /* R30 - DAC Digital 1 */
+       0x0008,     /* R31 - DAC Digital 2 */
+       0x09AF,     /* R32 - DRC 1 */
+       0x4201,     /* R33 - DRC 2 */
+       0x0000,     /* R34 - DRC 3 */
+       0x0000,     /* R35 - DRC 4 */
+       0x0000,     /* R36 */
+       0x0000,     /* R37 */
+       0x0000,     /* R38 - Write Sequencer 1 */
+       0x0000,     /* R39 - Write Sequencer 2 */
+       0x0002,     /* R40 - MW Slave 1 */
+       0x0000,     /* R41 */
+       0x0000,     /* R42 - EQ 1 */
+       0x0000,     /* R43 - EQ 2 */
+       0x0FCA,     /* R44 - EQ 3 */
+       0x0400,     /* R45 - EQ 4 */
+       0x00B8,     /* R46 - EQ 5 */
+       0x1EB5,     /* R47 - EQ 6 */
+       0xF145,     /* R48 - EQ 7 */
+       0x0B75,     /* R49 - EQ 8 */
+       0x01C5,     /* R50 - EQ 9 */
+       0x169E,     /* R51 - EQ 10 */
+       0xF829,     /* R52 - EQ 11 */
+       0x07AD,     /* R53 - EQ 12 */
+       0x1103,     /* R54 - EQ 13 */
+       0x1C58,     /* R55 - EQ 14 */
+       0xF373,     /* R56 - EQ 15 */
+       0x0A54,     /* R57 - EQ 16 */
+       0x0558,     /* R58 - EQ 17 */
+       0x0564,     /* R59 - EQ 18 */
+       0x0559,     /* R60 - EQ 19 */
+       0x4000,     /* R61 - EQ 20 */
+};
+
+static struct {
+       int ratio;
+       int clk_sys_rate;
+} clk_sys_rates[] = {
+       { 64,   0 },
+       { 128,  1 },
+       { 192,  2 },
+       { 256,  3 },
+       { 384,  4 },
+       { 512,  5 },
+       { 768,  6 },
+       { 1024, 7 },
+       { 1408, 8 },
+       { 1536, 9 },
+};
+
+static struct {
+       int rate;
+       int sample_rate;
+} sample_rates[] = {
+       { 8000,  0  },
+       { 11025, 1  },
+       { 12000, 2  },
+       { 16000, 3  },
+       { 22050, 4  },
+       { 24000, 5  },
+       { 32000, 6  },
+       { 44100, 7  },
+       { 48000, 8  },
+       { 88200, 9  },
+       { 96000, 10 },
+};
+
+static struct {
+       int div; /* *10 due to .5s */
+       int bclk_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 },
+};
+
+struct wm9081_priv {
+       struct snd_soc_codec codec;
+       u16 reg_cache[WM9081_MAX_REGISTER + 1];
+       int sysclk_source;
+       int mclk_rate;
+       int sysclk_rate;
+       int fs;
+       int bclk;
+       int master;
+       int fll_fref;
+       int fll_fout;
+       struct wm9081_retune_mobile_config *retune;
+};
+
+static int wm9081_reg_is_volatile(int reg)
+{
+       switch (reg) {
+       default:
+               return 0;
+       }
+}
+
+static unsigned int wm9081_read_reg_cache(struct snd_soc_codec *codec,
+                                         unsigned int reg)
+{
+       u16 *cache = codec->reg_cache;
+       BUG_ON(reg > WM9081_MAX_REGISTER);
+       return cache[reg];
+}
+
+static unsigned int wm9081_read_hw(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 > WM9081_MAX_REGISTER);
+
+       /* 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) {
+               dev_err(&client->dev, "i2c_transfer() returned %d\n", ret);
+               return 0;
+       }
+
+       return (data >> 8) | ((data & 0xff) << 8);
+}
+
+static unsigned int wm9081_read(struct snd_soc_codec *codec, unsigned int reg)
+{
+       if (wm9081_reg_is_volatile(reg))
+               return wm9081_read_hw(codec, reg);
+       else
+               return wm9081_read_reg_cache(codec, reg);
+}
+
+static int wm9081_write(struct snd_soc_codec *codec, unsigned int reg,
+                       unsigned int value)
+{
+       u16 *cache = codec->reg_cache;
+       u8 data[3];
+
+       BUG_ON(reg > WM9081_MAX_REGISTER);
+
+       if (!wm9081_reg_is_volatile(reg))
+               cache[reg] = value;
+
+       data[0] = reg;
+       data[1] = value >> 8;
+       data[2] = value & 0x00ff;
+
+       if (codec->hw_write(codec->control_data, data, 3) == 3)
+               return 0;
+       else
+               return -EIO;
+}
+
+static int wm9081_reset(struct snd_soc_codec *codec)
+{
+       return wm9081_write(codec, WM9081_SOFTWARE_RESET, 0);
+}
+
+static const DECLARE_TLV_DB_SCALE(drc_in_tlv, -4500, 75, 0);
+static const DECLARE_TLV_DB_SCALE(drc_out_tlv, -2250, 75, 0);
+static const DECLARE_TLV_DB_SCALE(drc_min_tlv, -1800, 600, 0);
+static unsigned int drc_max_tlv[] = {
+       TLV_DB_RANGE_HEAD(4),
+       0, 0, TLV_DB_SCALE_ITEM(1200, 0, 0),
+       1, 1, TLV_DB_SCALE_ITEM(1800, 0, 0),
+       2, 2, TLV_DB_SCALE_ITEM(2400, 0, 0),
+       3, 3, TLV_DB_SCALE_ITEM(3600, 0, 0),
+};
+static const DECLARE_TLV_DB_SCALE(drc_qr_tlv, 1200, 600, 0);
+static const DECLARE_TLV_DB_SCALE(drc_startup_tlv, -300, 50, 0);
+
+static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
+
+static const DECLARE_TLV_DB_SCALE(in_tlv, -600, 600, 0);
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -7200, 75, 1);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0);
+
+static const char *drc_high_text[] = {
+       "1",
+       "1/2",
+       "1/4",
+       "1/8",
+       "1/16",
+       "0",
+};
+
+static const struct soc_enum drc_high =
+       SOC_ENUM_SINGLE(WM9081_DRC_3, 3, 6, drc_high_text);
+
+static const char *drc_low_text[] = {
+       "1",
+       "1/2",
+       "1/4",
+       "1/8",
+       "0",
+};
+
+static const struct soc_enum drc_low =
+       SOC_ENUM_SINGLE(WM9081_DRC_3, 0, 5, drc_low_text);
+
+static const char *drc_atk_text[] = {
+       "181us",
+       "181us",
+       "363us",
+       "726us",
+       "1.45ms",
+       "2.9ms",
+       "5.8ms",
+       "11.6ms",
+       "23.2ms",
+       "46.4ms",
+       "92.8ms",
+       "185.6ms",
+};
+
+static const struct soc_enum drc_atk =
+       SOC_ENUM_SINGLE(WM9081_DRC_2, 12, 12, drc_atk_text);
+
+static const char *drc_dcy_text[] = {
+       "186ms",
+       "372ms",
+       "743ms",
+       "1.49s",
+       "2.97s",
+       "5.94s",
+       "11.89s",
+       "23.78s",
+       "47.56s",
+};
+
+static const struct soc_enum drc_dcy =
+       SOC_ENUM_SINGLE(WM9081_DRC_2, 8, 9, drc_dcy_text);
+
+static const char *drc_qr_dcy_text[] = {
+       "0.725ms",
+       "1.45ms",
+       "5.8ms",
+};
+
+static const struct soc_enum drc_qr_dcy =
+       SOC_ENUM_SINGLE(WM9081_DRC_2, 4, 3, drc_qr_dcy_text);
+
+static const char *dac_deemph_text[] = {
+       "None",
+       "32kHz",
+       "44.1kHz",
+       "48kHz",
+};
+
+static const struct soc_enum dac_deemph =
+       SOC_ENUM_SINGLE(WM9081_DAC_DIGITAL_2, 1, 4, dac_deemph_text);
+
+static const char *speaker_mode_text[] = {
+       "Class D",
+       "Class AB",
+};
+
+static const struct soc_enum speaker_mode =
+       SOC_ENUM_SINGLE(WM9081_ANALOGUE_SPEAKER_2, 6, 2, speaker_mode_text);
+
+static int speaker_mode_get(struct snd_kcontrol *kcontrol,
+                           struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       unsigned int reg;
+
+       reg = wm9081_read(codec, WM9081_ANALOGUE_SPEAKER_2);
+       if (reg & WM9081_SPK_MODE)
+               ucontrol->value.integer.value[0] = 1;
+       else
+               ucontrol->value.integer.value[0] = 0;
+
+       return 0;
+}
+
+/*
+ * Stop any attempts to change speaker mode while the speaker is enabled.
+ *
+ * We also have some special anti-pop controls dependant on speaker
+ * mode which must be changed along with the mode.
+ */
+static int speaker_mode_put(struct snd_kcontrol *kcontrol,
+                           struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       unsigned int reg_pwr = wm9081_read(codec, WM9081_POWER_MANAGEMENT);
+       unsigned int reg2 = wm9081_read(codec, WM9081_ANALOGUE_SPEAKER_2);
+
+       /* Are we changing anything? */
+       if (ucontrol->value.integer.value[0] ==
+           ((reg2 & WM9081_SPK_MODE) != 0))
+               return 0;
+
+       /* Don't try to change modes while enabled */
+       if (reg_pwr & WM9081_SPK_ENA)
+               return -EINVAL;
+
+       if (ucontrol->value.integer.value[0]) {
+               /* Class AB */
+               reg2 &= ~(WM9081_SPK_INV_MUTE | WM9081_OUT_SPK_CTRL);
+               reg2 |= WM9081_SPK_MODE;
+       } else {
+               /* Class D */
+               reg2 |= WM9081_SPK_INV_MUTE | WM9081_OUT_SPK_CTRL;
+               reg2 &= ~WM9081_SPK_MODE;
+       }
+
+       wm9081_write(codec, WM9081_ANALOGUE_SPEAKER_2, reg2);
+
+       return 0;
+}
+
+static const struct snd_kcontrol_new wm9081_snd_controls[] = {
+SOC_SINGLE_TLV("IN1 Volume", WM9081_ANALOGUE_MIXER, 1, 1, 1, in_tlv),
+SOC_SINGLE_TLV("IN2 Volume", WM9081_ANALOGUE_MIXER, 3, 1, 1, in_tlv),
+
+SOC_SINGLE_TLV("Playback Volume", WM9081_DAC_DIGITAL_1, 1, 96, 0, dac_tlv),
+
+SOC_SINGLE("LINEOUT Switch", WM9081_ANALOGUE_LINEOUT, 7, 1, 1),
+SOC_SINGLE("LINEOUT ZC Switch", WM9081_ANALOGUE_LINEOUT, 6, 1, 0),
+SOC_SINGLE_TLV("LINEOUT Volume", WM9081_ANALOGUE_LINEOUT, 0, 63, 0, out_tlv),
+
+SOC_SINGLE("DRC Switch", WM9081_DRC_1, 15, 1, 0),
+SOC_ENUM("DRC High Slope", drc_high),
+SOC_ENUM("DRC Low Slope", drc_low),
+SOC_SINGLE_TLV("DRC Input Volume", WM9081_DRC_4, 5, 60, 1, drc_in_tlv),
+SOC_SINGLE_TLV("DRC Output Volume", WM9081_DRC_4, 0, 30, 1, drc_out_tlv),
+SOC_SINGLE_TLV("DRC Minimum Volume", WM9081_DRC_2, 2, 3, 1, drc_min_tlv),
+SOC_SINGLE_TLV("DRC Maximum Volume", WM9081_DRC_2, 0, 3, 0, drc_max_tlv),
+SOC_ENUM("DRC Attack", drc_atk),
+SOC_ENUM("DRC Decay", drc_dcy),
+SOC_SINGLE("DRC Quick Release Switch", WM9081_DRC_1, 2, 1, 0),
+SOC_SINGLE_TLV("DRC Quick Release Volume", WM9081_DRC_2, 6, 3, 0, drc_qr_tlv),
+SOC_ENUM("DRC Quick Release Decay", drc_qr_dcy),
+SOC_SINGLE_TLV("DRC Startup Volume", WM9081_DRC_1, 6, 18, 0, drc_startup_tlv),
+
+SOC_SINGLE("EQ Switch", WM9081_EQ_1, 0, 1, 0),
+
+SOC_SINGLE("Speaker DC Volume", WM9081_ANALOGUE_SPEAKER_1, 3, 5, 0),
+SOC_SINGLE("Speaker AC Volume", WM9081_ANALOGUE_SPEAKER_1, 0, 5, 0),
+SOC_SINGLE("Speaker Switch", WM9081_ANALOGUE_SPEAKER_PGA, 7, 1, 1),
+SOC_SINGLE("Speaker ZC Switch", WM9081_ANALOGUE_SPEAKER_PGA, 6, 1, 0),
+SOC_SINGLE_TLV("Speaker Volume", WM9081_ANALOGUE_SPEAKER_PGA, 0, 63, 0,
+              out_tlv),
+SOC_ENUM("DAC Deemphasis", dac_deemph),
+SOC_ENUM_EXT("Speaker Mode", speaker_mode, speaker_mode_get, speaker_mode_put),
+};
+
+static const struct snd_kcontrol_new wm9081_eq_controls[] = {
+SOC_SINGLE_TLV("EQ1 Volume", WM9081_EQ_1, 11, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ2 Volume", WM9081_EQ_1, 6, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ3 Volume", WM9081_EQ_1, 1, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ4 Volume", WM9081_EQ_2, 11, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ5 Volume", WM9081_EQ_2, 6, 24, 0, eq_tlv),
+};
+
+static const struct snd_kcontrol_new mixer[] = {
+SOC_DAPM_SINGLE("IN1 Switch", WM9081_ANALOGUE_MIXER, 0, 1, 0),
+SOC_DAPM_SINGLE("IN2 Switch", WM9081_ANALOGUE_MIXER, 2, 1, 0),
+SOC_DAPM_SINGLE("Playback Switch", WM9081_ANALOGUE_MIXER, 4, 1, 0),
+};
+
+static int speaker_event(struct snd_soc_dapm_widget *w,
+                        struct snd_kcontrol *kcontrol, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       unsigned int reg = wm9081_read(codec, WM9081_POWER_MANAGEMENT);
+
+       switch (event) {
+       case SND_SOC_DAPM_POST_PMU:
+               reg |= WM9081_SPK_ENA;
+               break;
+
+       case SND_SOC_DAPM_PRE_PMD:
+               reg &= ~WM9081_SPK_ENA;
+               break;
+       }
+
+       wm9081_write(codec, WM9081_POWER_MANAGEMENT, reg);
+
+       return 0;
+}
+
+struct _fll_div {
+       u16 fll_fratio;
+       u16 fll_outdiv;
+       u16 fll_clk_ref_div;
+       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 struct {
+       unsigned int min;
+       unsigned int max;
+       u16 fll_fratio;
+       int ratio;
+} fll_fratios[] = {
+       {       0,    64000, 4, 16 },
+       {   64000,   128000, 3,  8 },
+       {  128000,   256000, 2,  4 },
+       {  256000,  1000000, 1,  2 },
+       { 1000000, 13500000, 0,  1 },
+};
+
+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;
+       int i;
+
+       /* Fref must be <=13.5MHz */
+       div = 1;
+       while ((Fref / div) > 13500000) {
+               div *= 2;
+
+               if (div > 8) {
+                       pr_err("Can't scale %dMHz input down to <=13.5MHz\n",
+                              Fref);
+                       return -EINVAL;
+               }
+       }
+       fll_div->fll_clk_ref_div = div / 2;
+
+       pr_debug("Fref=%u Fout=%u\n", Fref, Fout);
+
+       /* Apply the division for our remaining calculations */
+       Fref /= div;
+
+       /* Fvco should be 90-100MHz; don't check the upper bound */
+       div = 0;
+       target = Fout * 2;
+       while (target < 90000000) {
+               div++;
+               target *= 2;
+               if (div > 7) {
+                       pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n",
+                              Fout);
+                       return -EINVAL;
+               }
+       }
+       fll_div->fll_outdiv = div;
+
+       pr_debug("Fvco=%dHz\n", target);
+
+       /* Find an appropraite FLL_FRATIO and factor it out of the target */
+       for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) {
+               if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) {
+                       fll_div->fll_fratio = fll_fratios[i].fll_fratio;
+                       target /= fll_fratios[i].ratio;
+                       break;
+               }
+       }
+       if (i == ARRAY_SIZE(fll_fratios)) {
+               pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref);
+               return -EINVAL;
+       }
+
+       /* Now, calculate N.K */
+       Ndiv = target / Fref;
+
+       fll_div->n = Ndiv;
+       Nmod = target % Fref;
+       pr_debug("Nmod=%d\n", Nmod);
+
+       /* 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;
+
+       pr_debug("N=%x K=%x FLL_FRATIO=%x FLL_OUTDIV=%x FLL_CLK_REF_DIV=%x\n",
+                fll_div->n, fll_div->k,
+                fll_div->fll_fratio, fll_div->fll_outdiv,
+                fll_div->fll_clk_ref_div);
+
+       return 0;
+}
+
+static int wm9081_set_fll(struct snd_soc_codec *codec, int fll_id,
+                         unsigned int Fref, unsigned int Fout)
+{
+       struct wm9081_priv *wm9081 = codec->private_data;
+       u16 reg1, reg4, reg5;
+       struct _fll_div fll_div;
+       int ret;
+       int clk_sys_reg;
+
+       /* Any change? */
+       if (Fref == wm9081->fll_fref && Fout == wm9081->fll_fout)
+               return 0;
+
+       /* Disable the FLL */
+       if (Fout == 0) {
+               dev_dbg(codec->dev, "FLL disabled\n");
+               wm9081->fll_fref = 0;
+               wm9081->fll_fout = 0;
+
+               return 0;
+       }
+
+       ret = fll_factors(&fll_div, Fref, Fout);
+       if (ret != 0)
+               return ret;
+
+       reg5 = wm9081_read(codec, WM9081_FLL_CONTROL_5);
+       reg5 &= ~WM9081_FLL_CLK_SRC_MASK;
+
+       switch (fll_id) {
+       case WM9081_SYSCLK_FLL_MCLK:
+               reg5 |= 0x1;
+               break;
+
+       default:
+               dev_err(codec->dev, "Unknown FLL ID %d\n", fll_id);
+               return -EINVAL;
+       }
+
+       /* Disable CLK_SYS while we reconfigure */
+       clk_sys_reg = wm9081_read(codec, WM9081_CLOCK_CONTROL_3);
+       if (clk_sys_reg & WM9081_CLK_SYS_ENA)