]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - sound/soc/codecs/wm8960.c
ASoC: multi-component - ASoC Multi-Component Support
[mv-sheeva.git] / sound / soc / codecs / wm8960.c
index f1e63e01b04d3621ef2124946b954d92cac7f525..8d5efb333c33f260b271af07e9352b14679f0502 100644 (file)
 #include <sound/soc-dapm.h>
 #include <sound/initval.h>
 #include <sound/tlv.h>
+#include <sound/wm8960.h>
 
 #include "wm8960.h"
 
 #define AUDIO_NAME "wm8960"
 
-struct snd_soc_codec_device soc_codec_dev_wm8960;
-
 /* R25 - Power 1 */
+#define WM8960_VMID_MASK 0x180
 #define WM8960_VREF      0x40
 
+/* R26 - Power 2 */
+#define WM8960_PWR2_LOUT1      0x40
+#define WM8960_PWR2_ROUT1      0x20
+#define WM8960_PWR2_OUT3       0x02
+
 /* R28 - Anti-pop 1 */
 #define WM8960_POBCTRL   0x80
 #define WM8960_BUFDCOPEN 0x10
@@ -42,6 +47,7 @@ struct snd_soc_codec_device soc_codec_dev_wm8960;
 
 /* R29 - Anti-pop 2 */
 #define WM8960_DISOP     0x40
+#define WM8960_DRES_MASK 0x30
 
 /*
  * wm8960 register cache
@@ -67,13 +73,20 @@ static const u16 wm8960_reg[WM8960_CACHEREGNUM] = {
 
 struct wm8960_priv {
        u16 reg_cache[WM8960_CACHEREGNUM];
-       struct snd_soc_codec codec;
+       enum snd_soc_control_type control_type;
+       void *control_data;
+       int (*set_bias_level)(struct snd_soc_codec *,
+                             enum snd_soc_bias_level level);
+       struct snd_soc_dapm_widget *lout1;
+       struct snd_soc_dapm_widget *rout1;
+       struct snd_soc_dapm_widget *out3;
+       bool deemph;
+       int playback_fs;
 };
 
 #define wm8960_reset(c)        snd_soc_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"};
@@ -82,7 +95,6 @@ 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),
@@ -91,6 +103,59 @@ static const struct soc_enum wm8960_enum[] = {
        SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode),
 };
 
+static const int deemph_settings[] = { 0, 32000, 44100, 48000 };
+
+static int wm8960_set_deemph(struct snd_soc_codec *codec)
+{
+       struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+       int val, i, best;
+
+       /* If we're using deemphasis select the nearest available sample
+        * rate.
+        */
+       if (wm8960->deemph) {
+               best = 1;
+               for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) {
+                       if (abs(deemph_settings[i] - wm8960->playback_fs) <
+                           abs(deemph_settings[best] - wm8960->playback_fs))
+                               best = i;
+               }
+
+               val = best << 1;
+       } else {
+               val = 0;
+       }
+
+       dev_dbg(codec->dev, "Set deemphasis %d\n", val);
+
+       return snd_soc_update_bits(codec, WM8960_DACCTL1,
+                                  0x6, val);
+}
+
+static int wm8960_get_deemph(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+
+       return wm8960->deemph;
+}
+
+static int wm8960_put_deemph(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+       int deemph = ucontrol->value.enumerated.item[0];
+
+       if (deemph > 1)
+               return -EINVAL;
+
+       wm8960->deemph = deemph;
+
+       return wm8960_set_deemph(codec);
+}
+
 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);
@@ -120,23 +185,24 @@ 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_ENUM("ADC Polarity", wm8960_enum[0]),
 SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0),
 
 SOC_ENUM("DAC Polarity", wm8960_enum[2]),
+SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0,
+                   wm8960_get_deemph, wm8960_put_deemph),
 
-SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[3]),
-SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[4]),
+SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[2]),
+SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[3]),
 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_ENUM("ALC Function", wm8960_enum[4]),
 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_ENUM("ALC Mode", wm8960_enum[5]),
 SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0),
 SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0),
 
@@ -226,10 +292,6 @@ 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),
 
@@ -248,6 +310,17 @@ SND_SOC_DAPM_OUTPUT("SPK_RN"),
 SND_SOC_DAPM_OUTPUT("OUT3"),
 };
 
+static const struct snd_soc_dapm_widget wm8960_dapm_widgets_out3[] = {
+SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0,
+       &wm8960_mono_out[0],
+       ARRAY_SIZE(wm8960_mono_out)),
+};
+
+/* Represent OUT3 as a PGA so that it gets turned on with LOUT1/ROUT1 */
+static const struct snd_soc_dapm_widget wm8960_dapm_widgets_capless[] = {
+SND_SOC_DAPM_PGA("OUT3 VMID", WM8960_POWER2, 1, 0, NULL, 0),
+};
+
 static const struct snd_soc_dapm_route audio_paths[] = {
        { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" },
        { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" },
@@ -278,9 +351,6 @@ static const struct snd_soc_dapm_route audio_paths[] = {
        { "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" },
 
@@ -297,17 +367,65 @@ static const struct snd_soc_dapm_route audio_paths[] = {
        { "SPK_LP", NULL, "Left Speaker Output" },
        { "SPK_RN", NULL, "Right Speaker Output" },
        { "SPK_RP", NULL, "Right Speaker Output" },
+};
+
+static const struct snd_soc_dapm_route audio_paths_out3[] = {
+       { "Mono Output Mixer", "Left Switch", "Left Output Mixer" },
+       { "Mono Output Mixer", "Right Switch", "Right Output Mixer" },
 
        { "OUT3", NULL, "Mono Output Mixer", }
 };
 
+static const struct snd_soc_dapm_route audio_paths_capless[] = {
+       { "HP_L", NULL, "OUT3 VMID" },
+       { "HP_R", NULL, "OUT3 VMID" },
+
+       { "OUT3 VMID", NULL, "Left Output Mixer" },
+       { "OUT3 VMID", NULL, "Right Output Mixer" },
+};
+
 static int wm8960_add_widgets(struct snd_soc_codec *codec)
 {
+       struct wm8960_data *pdata = codec->dev->platform_data;
+       struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+       struct snd_soc_dapm_widget *w;
+
        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));
 
+       /* In capless mode OUT3 is used to provide VMID for the
+        * headphone outputs, otherwise it is used as a mono mixer.
+        */
+       if (pdata && pdata->capless) {
+               snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets_capless,
+                                         ARRAY_SIZE(wm8960_dapm_widgets_capless));
+
+               snd_soc_dapm_add_routes(codec, audio_paths_capless,
+                                       ARRAY_SIZE(audio_paths_capless));
+       } else {
+               snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets_out3,
+                                         ARRAY_SIZE(wm8960_dapm_widgets_out3));
+
+               snd_soc_dapm_add_routes(codec, audio_paths_out3,
+                                       ARRAY_SIZE(audio_paths_out3));
+       }
+
+       /* We need to power up the headphone output stage out of
+        * sequence for capless mode.  To save scanning the widget
+        * list each time to find the desired power state do so now
+        * and save the result.
+        */
+       list_for_each_entry(w, &codec->dapm_widgets, list) {
+               if (strcmp(w->name, "LOUT1 PGA") == 0)
+                       wm8960->lout1 = w;
+               if (strcmp(w->name, "ROUT1 PGA") == 0)
+                       wm8960->rout1 = w;
+               if (strcmp(w->name, "OUT3 VMID") == 0)
+                       wm8960->out3 = w;
+       }
+       
        return 0;
 }
 
@@ -370,14 +488,30 @@ static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai,
        return 0;
 }
 
+static struct {
+       int rate;
+       unsigned int val;
+} alc_rates[] = {
+       { 48000, 0 },
+       { 44100, 0 },
+       { 32000, 1 },
+       { 22050, 2 },
+       { 24000, 2 },
+       { 16000, 3 },
+       { 11250, 4 },
+       { 12000, 4 },
+       {  8000, 5 },
+};
+
 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;
+       struct snd_soc_codec *codec = rtd->codec;
+       struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
        u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3;
+       int i;
 
        /* bit size */
        switch (params_format(params)) {
@@ -391,6 +525,18 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream,
                break;
        }
 
+       /* Update filters for the new rate */
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+               wm8960->playback_fs = params_rate(params);
+               wm8960_set_deemph(codec);
+       } else {
+               for (i = 0; i < ARRAY_SIZE(alc_rates); i++)
+                       if (alc_rates[i].rate == params_rate(params))
+                               snd_soc_update_bits(codec,
+                                                   WM8960_ADDCTL3, 0x7,
+                                                   alc_rates[i].val);
+       }
+
        /* set iface */
        snd_soc_write(codec, WM8960_IFACE1, iface);
        return 0;
@@ -408,10 +554,9 @@ static int wm8960_mute(struct snd_soc_dai *dai, int mute)
        return 0;
 }
 
-static int wm8960_set_bias_level(struct snd_soc_codec *codec,
-                                enum snd_soc_bias_level level)
+static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec,
+                                     enum snd_soc_bias_level level)
 {
-       struct wm8960_data *pdata = codec->dev->platform_data;
        u16 reg;
 
        switch (level) {
@@ -430,18 +575,8 @@ static int wm8960_set_bias_level(struct snd_soc_codec *codec,
                if (codec->bias_level == SND_SOC_BIAS_OFF) {
                        /* Enable anti-pop features */
                        snd_soc_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;
-                       snd_soc_write(codec, WM8960_APOP2, reg);
-
-                       msleep(400);
-
-                       snd_soc_write(codec, WM8960_APOP2, 0);
+                                     WM8960_POBCTRL | WM8960_SOFT_ST |
+                                     WM8960_BUFDCOPEN | WM8960_BUFIOEN);
 
                        /* Enable & ramp VMID at 2x50k */
                        reg = snd_soc_read(codec, WM8960_POWER1);
@@ -472,8 +607,101 @@ static int wm8960_set_bias_level(struct snd_soc_codec *codec,
                /* Disable VMID and VREF, let them discharge */
                snd_soc_write(codec, WM8960_POWER1, 0);
                msleep(600);
+               break;
+       }
+
+       codec->bias_level = level;
+
+       return 0;
+}
+
+static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec,
+                                        enum snd_soc_bias_level level)
+{
+       struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+       int reg;
+
+       switch (level) {
+       case SND_SOC_BIAS_ON:
+               break;
+
+       case SND_SOC_BIAS_PREPARE:
+               switch (codec->bias_level) {
+               case SND_SOC_BIAS_STANDBY:
+                       /* Enable anti pop mode */
+                       snd_soc_update_bits(codec, WM8960_APOP1,
+                                           WM8960_POBCTRL | WM8960_SOFT_ST |
+                                           WM8960_BUFDCOPEN,
+                                           WM8960_POBCTRL | WM8960_SOFT_ST |
+                                           WM8960_BUFDCOPEN);
+
+                       /* Enable LOUT1, ROUT1 and OUT3 if they're enabled */
+                       reg = 0;
+                       if (wm8960->lout1 && wm8960->lout1->power)
+                               reg |= WM8960_PWR2_LOUT1;
+                       if (wm8960->rout1 && wm8960->rout1->power)
+                               reg |= WM8960_PWR2_ROUT1;
+                       if (wm8960->out3 && wm8960->out3->power)
+                               reg |= WM8960_PWR2_OUT3;
+                       snd_soc_update_bits(codec, WM8960_POWER2,
+                                           WM8960_PWR2_LOUT1 |
+                                           WM8960_PWR2_ROUT1 |
+                                           WM8960_PWR2_OUT3, reg);
+
+                       /* Enable VMID at 2*50k */
+                       snd_soc_update_bits(codec, WM8960_POWER1,
+                                           WM8960_VMID_MASK, 0x80);
+
+                       /* Ramp */
+                       msleep(100);
 
-               snd_soc_write(codec, WM8960_APOP1, 0);
+                       /* Enable VREF */
+                       snd_soc_update_bits(codec, WM8960_POWER1,
+                                           WM8960_VREF, WM8960_VREF);
+
+                       msleep(100);
+                       break;
+
+               case SND_SOC_BIAS_ON:
+                       /* Enable anti-pop mode */
+                       snd_soc_update_bits(codec, WM8960_APOP1,
+                                           WM8960_POBCTRL | WM8960_SOFT_ST |
+                                           WM8960_BUFDCOPEN,
+                                           WM8960_POBCTRL | WM8960_SOFT_ST |
+                                           WM8960_BUFDCOPEN);
+
+                       /* Disable VMID and VREF */
+                       snd_soc_update_bits(codec, WM8960_POWER1,
+                                           WM8960_VREF | WM8960_VMID_MASK, 0);
+                       break;
+
+               default:
+                       break;
+               }
+               break;
+
+       case SND_SOC_BIAS_STANDBY:
+               switch (codec->bias_level) {
+               case SND_SOC_BIAS_PREPARE:
+                       /* Disable HP discharge */
+                       snd_soc_update_bits(codec, WM8960_APOP2,
+                                           WM8960_DISOP | WM8960_DRES_MASK,
+                                           0);
+
+                       /* Disable anti-pop features */
+                       snd_soc_update_bits(codec, WM8960_APOP1,
+                                           WM8960_POBCTRL | WM8960_SOFT_ST |
+                                           WM8960_BUFDCOPEN,
+                                           WM8960_POBCTRL | WM8960_SOFT_ST |
+                                           WM8960_BUFDCOPEN);
+                       break;
+
+               default:
+                       break;
+               }
+               break;
+
+       case SND_SOC_BIAS_OFF:
                break;
        }
 
@@ -594,10 +822,6 @@ static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
        u16 reg;
 
        switch (div_id) {
-       case WM8960_SYSCLKSEL:
-               reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1fe;
-               snd_soc_write(codec, WM8960_CLOCK1, reg | div);
-               break;
        case WM8960_SYSCLKDIV:
                reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1f9;
                snd_soc_write(codec, WM8960_CLOCK1, reg | div);
@@ -625,6 +849,14 @@ static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
        return 0;
 }
 
+static int wm8960_set_bias_level(struct snd_soc_codec *codec,
+                                enum snd_soc_bias_level level)
+{
+       struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+
+       return wm8960->set_bias_level(codec, level);
+}
+
 #define WM8960_RATES SNDRV_PCM_RATE_8000_48000
 
 #define WM8960_FORMATS \
@@ -639,8 +871,8 @@ static struct snd_soc_dai_ops wm8960_dai_ops = {
        .set_pll = wm8960_set_dai_pll,
 };
 
-struct snd_soc_dai wm8960_dai = {
-       .name = "WM8960",
+static struct snd_soc_dai_driver wm8960_dai = {
+       .name = "wm8960-hifi",
        .playback = {
                .stream_name = "Playback",
                .channels_min = 1,
@@ -656,21 +888,18 @@ struct snd_soc_dai wm8960_dai = {
        .ops = &wm8960_dai_ops,
        .symmetric_rates = 1,
 };
-EXPORT_SYMBOL_GPL(wm8960_dai);
 
-static int wm8960_suspend(struct platform_device *pdev, pm_message_t state)
+static int wm8960_suspend(struct snd_soc_codec *codec, pm_message_t state)
 {
-       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
-       struct snd_soc_codec *codec = socdev->card->codec;
+       struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
 
-       wm8960_set_bias_level(codec, SND_SOC_BIAS_OFF);
+       wm8960->set_bias_level(codec, SND_SOC_BIAS_OFF);
        return 0;
 }
 
-static int wm8960_resume(struct platform_device *pdev)
+static int wm8960_resume(struct snd_soc_codec *codec)
 {
-       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
-       struct snd_soc_codec *codec = socdev->card->codec;
+       struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
        int i;
        u8 data[2];
        u16 *cache = codec->reg_cache;
@@ -682,76 +911,19 @@ static int wm8960_resume(struct platform_device *pdev)
                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);
-
-       return ret;
-
-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);
-
+       wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY);
        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,
-                          enum snd_soc_control_type control)
+static int wm8960_probe(struct snd_soc_codec *codec)
 {
-       struct wm8960_data *pdata = wm8960->codec.dev->platform_data;
-       struct snd_soc_codec *codec = &wm8960->codec;
+       struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+       struct wm8960_data *pdata = dev_get_platdata(codec->dev);
        int ret;
        u16 reg;
 
-       if (wm8960_codec) {
-               dev_err(codec->dev, "Another WM8960 is registered\n");
-               ret = -EINVAL;
-               goto err;
-       }
+       wm8960->set_bias_level = wm8960_set_bias_level_out3;
+       codec->control_data = wm8960->control_data;
 
        if (!pdata) {
                dev_warn(codec->dev, "No platform data supplied\n");
@@ -760,39 +932,24 @@ static int wm8960_register(struct wm8960_priv *wm8960,
                        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->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));
+               if (pdata->capless)
+                       wm8960->set_bias_level = wm8960_set_bias_level_capless;
+       }
 
-       ret = snd_soc_codec_set_cache_io(codec, 7, 9, control);
+       ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8960->control_type);
        if (ret < 0) {
                dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
-               goto err;
+               return ret;
        }
 
        ret = wm8960_reset(codec);
        if (ret < 0) {
                dev_err(codec->dev, "Failed to issue reset\n");
-               goto err;
+               return ret;
        }
 
-       wm8960_dai.dev = codec->dev;
-
-       wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+       wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY);
 
        /* Latch the update bits */
        reg = snd_soc_read(codec, WM8960_LINVOL);
@@ -816,62 +973,58 @@ static int wm8960_register(struct wm8960_priv *wm8960,
        reg = snd_soc_read(codec, WM8960_ROUT2);
        snd_soc_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);
-               goto err;
-       }
-
-       ret = snd_soc_register_dai(&wm8960_dai);
-       if (ret != 0) {
-               dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
-               goto err_codec;
-       }
+       snd_soc_add_controls(codec, wm8960_snd_controls,
+                                    ARRAY_SIZE(wm8960_snd_controls));
+       wm8960_add_widgets(codec);
 
        return 0;
-
-err_codec:
-       snd_soc_unregister_codec(codec);
-err:
-       kfree(wm8960);
-       return ret;
 }
 
-static void wm8960_unregister(struct wm8960_priv *wm8960)
+/* power down chip */
+static int wm8960_remove(struct snd_soc_codec *codec)
 {
-       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;
+       struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+
+       wm8960->set_bias_level(codec, SND_SOC_BIAS_OFF);
+       return 0;
 }
 
+static struct snd_soc_codec_driver soc_codec_dev_wm8960 = {
+       .probe =        wm8960_probe,
+       .remove =       wm8960_remove,
+       .suspend =      wm8960_suspend,
+       .resume =       wm8960_resume,
+       .set_bias_level = wm8960_set_bias_level,
+       .reg_cache_size = ARRAY_SIZE(wm8960_reg),
+       .reg_word_size = sizeof(u16),
+       .reg_cache_default = wm8960_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
 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;
+       int ret;
 
        wm8960 = kzalloc(sizeof(struct wm8960_priv), GFP_KERNEL);
        if (wm8960 == NULL)
                return -ENOMEM;
 
-       codec = &wm8960->codec;
-
        i2c_set_clientdata(i2c, wm8960);
-       codec->control_data = i2c;
+       wm8960->control_data = i2c;
 
-       codec->dev = &i2c->dev;
-
-       return wm8960_register(wm8960, SND_SOC_I2C);
+       ret = snd_soc_register_codec(&i2c->dev,
+                       &soc_codec_dev_wm8960, &wm8960_dai, 1);
+       if (ret < 0)
+               kfree(wm8960);
+       return ret;
 }
 
 static __devexit int wm8960_i2c_remove(struct i2c_client *client)
 {
-       struct wm8960_priv *wm8960 = i2c_get_clientdata(client);
-       wm8960_unregister(wm8960);
+       snd_soc_unregister_codec(&client->dev);
+       kfree(i2c_get_clientdata(client));
        return 0;
 }
 
@@ -883,35 +1036,37 @@ MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id);
 
 static struct i2c_driver wm8960_i2c_driver = {
        .driver = {
-               .name = "WM8960 I2C Codec",
+               .name = "wm8960-codec",
                .owner = THIS_MODULE,
        },
        .probe =    wm8960_i2c_probe,
        .remove =   __devexit_p(wm8960_i2c_remove),
        .id_table = wm8960_i2c_id,
 };
+#endif
 
 static int __init wm8960_modinit(void)
 {
-       int ret;
-
+       int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
        ret = i2c_add_driver(&wm8960_i2c_driver);
        if (ret != 0) {
                printk(KERN_ERR "Failed to register WM8960 I2C driver: %d\n",
                       ret);
        }
-
+#endif
        return ret;
 }
 module_init(wm8960_modinit);
 
 static void __exit wm8960_exit(void)
 {
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
        i2c_del_driver(&wm8960_i2c_driver);
+#endif
 }
 module_exit(wm8960_exit);
 
-
 MODULE_DESCRIPTION("ASoC WM8960 driver");
 MODULE_AUTHOR("Liam Girdwood");
 MODULE_LICENSE("GPL");