Merge branch 'for-2.6.30' into for-2.6.31
[sfrench/cifs-2.6.git] / sound / soc / codecs / twl4030.c
index f985bef40a39a94c727adecde5c69ed567f46dfc..921b205de28ab51ef5239dd7e07fe01cfaac2066 100644 (file)
@@ -117,6 +117,16 @@ static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = {
        0x00, /* REG_MISC_SET_2         (0x49)  */
 };
 
+/* codec private data */
+struct twl4030_priv {
+       unsigned int bypass_state;
+       unsigned int codec_powered;
+       unsigned int codec_muted;
+
+       struct snd_pcm_substream *master_substream;
+       struct snd_pcm_substream *slave_substream;
+};
+
 /*
  * read twl4030 register cache
  */
@@ -156,8 +166,12 @@ static int twl4030_write(struct snd_soc_codec *codec,
 
 static void twl4030_codec_enable(struct snd_soc_codec *codec, int enable)
 {
+       struct twl4030_priv *twl4030 = codec->private_data;
        u8 mode;
 
+       if (enable == twl4030->codec_powered)
+               return;
+
        mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE);
        if (enable)
                mode |= TWL4030_CODECPDZ;
@@ -165,6 +179,7 @@ static void twl4030_codec_enable(struct snd_soc_codec *codec, int enable)
                mode &= ~TWL4030_CODECPDZ;
 
        twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode);
+       twl4030->codec_powered = enable;
 
        /* REVISIT: this delay is present in TI sample drivers */
        /* but there seems to be no TRM requirement for it     */
@@ -184,11 +199,82 @@ static void twl4030_init_chip(struct snd_soc_codec *codec)
 
 }
 
+static void twl4030_codec_mute(struct snd_soc_codec *codec, int mute)
+{
+       struct twl4030_priv *twl4030 = codec->private_data;
+       u8 reg_val;
+
+       if (mute == twl4030->codec_muted)
+               return;
+
+       if (mute) {
+               /* Bypass the reg_cache and mute the volumes
+                * Headset mute is done in it's own event handler
+                * Things to mute:  Earpiece, PreDrivL/R, CarkitL/R
+                */
+               reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_EAR_CTL);
+               twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
+                                       reg_val & (~TWL4030_EAR_GAIN),
+                                       TWL4030_REG_EAR_CTL);
+
+               reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PREDL_CTL);
+               twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
+                                       reg_val & (~TWL4030_PREDL_GAIN),
+                                       TWL4030_REG_PREDL_CTL);
+               reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PREDR_CTL);
+               twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
+                                       reg_val & (~TWL4030_PREDR_GAIN),
+                                       TWL4030_REG_PREDL_CTL);
+
+               reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PRECKL_CTL);
+               twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
+                                       reg_val & (~TWL4030_PRECKL_GAIN),
+                                       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),
+                                       TWL4030_REG_PRECKR_CTL);
+
+               /* Disable PLL */
+               reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_APLL_CTL);
+               reg_val &= ~TWL4030_APLL_EN;
+               twl4030_write(codec, TWL4030_REG_APLL_CTL, reg_val);
+       } else {
+               /* Restore the volumes
+                * Headset mute is done in it's own event handler
+                * Things to restore:  Earpiece, PreDrivL/R, CarkitL/R
+                */
+               twl4030_write(codec, TWL4030_REG_EAR_CTL,
+                       twl4030_read_reg_cache(codec, TWL4030_REG_EAR_CTL));
+
+               twl4030_write(codec, TWL4030_REG_PREDL_CTL,
+                       twl4030_read_reg_cache(codec, TWL4030_REG_PREDL_CTL));
+               twl4030_write(codec, TWL4030_REG_PREDR_CTL,
+                       twl4030_read_reg_cache(codec, TWL4030_REG_PREDR_CTL));
+
+               twl4030_write(codec, TWL4030_REG_PRECKL_CTL,
+                       twl4030_read_reg_cache(codec, TWL4030_REG_PRECKL_CTL));
+               twl4030_write(codec, TWL4030_REG_PRECKR_CTL,
+                       twl4030_read_reg_cache(codec, TWL4030_REG_PRECKR_CTL));
+
+               /* Enable PLL */
+               reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_APLL_CTL);
+               reg_val |= TWL4030_APLL_EN;
+               twl4030_write(codec, TWL4030_REG_APLL_CTL, reg_val);
+       }
+
+       twl4030->codec_muted = mute;
+}
+
 static void twl4030_power_up(struct snd_soc_codec *codec)
 {
+       struct twl4030_priv *twl4030 = codec->private_data;
        u8 anamicl, regmisc1, byte;
        int i = 0;
 
+       if (twl4030->codec_powered)
+               return;
+
        /* set CODECPDZ to turn on codec */
        twl4030_codec_enable(codec, 1);
 
@@ -220,6 +306,9 @@ static void twl4030_power_up(struct snd_soc_codec *codec)
        twl4030_codec_enable(codec, 1);
 }
 
+/*
+ * Unconditional power down
+ */
 static void twl4030_power_down(struct snd_soc_codec *codec)
 {
        /* power down */
@@ -402,6 +491,41 @@ static const struct soc_enum twl4030_micpathtx2_enum =
 static const struct snd_kcontrol_new twl4030_dapm_micpathtx2_control =
 SOC_DAPM_ENUM("Route", twl4030_micpathtx2_enum);
 
+/* Analog bypass for AudioR1 */
+static const struct snd_kcontrol_new twl4030_dapm_abypassr1_control =
+       SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXR1_APGA_CTL, 2, 1, 0);
+
+/* Analog bypass for AudioL1 */
+static const struct snd_kcontrol_new twl4030_dapm_abypassl1_control =
+       SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXL1_APGA_CTL, 2, 1, 0);
+
+/* Analog bypass for AudioR2 */
+static const struct snd_kcontrol_new twl4030_dapm_abypassr2_control =
+       SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXR2_APGA_CTL, 2, 1, 0);
+
+/* Analog bypass for AudioL2 */
+static const struct snd_kcontrol_new twl4030_dapm_abypassl2_control =
+       SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXL2_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),
+       0, 3, TLV_DB_SCALE_ITEM(-2400, 0, 1),
+       4, 7, TLV_DB_SCALE_ITEM(-1800, 600, 0),
+};
+
+/* Digital bypass left (TX1L -> RX2L) */
+static const struct snd_kcontrol_new twl4030_dapm_dbypassl_control =
+       SOC_DAPM_SINGLE_TLV("Volume",
+                       TWL4030_REG_ATX2ARXPGA, 3, 7, 0,
+                       twl4030_dapm_dbypass_tlv);
+
+/* Digital bypass right (TX1R -> RX2R) */
+static const struct snd_kcontrol_new twl4030_dapm_dbypassr_control =
+       SOC_DAPM_SINGLE_TLV("Volume",
+                       TWL4030_REG_ATX2ARXPGA, 0, 7, 0,
+                       twl4030_dapm_dbypass_tlv);
+
 static int micpath_event(struct snd_soc_dapm_widget *w,
        struct snd_kcontrol *kcontrol, int event)
 {
@@ -463,12 +587,11 @@ static int headsetl_event(struct snd_soc_dapm_widget *w,
 
        /* 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);
 
        switch (event) {
        case SND_SOC_DAPM_POST_PMU:
                /* Do the anti-pop/bias ramp enable according to the TRM */
-               hs_pop = TWL4030_RAMP_DELAY_645MS;
-               twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop);
                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? */
@@ -482,8 +605,6 @@ static int headsetl_event(struct snd_soc_dapm_widget *w,
                break;
        case SND_SOC_DAPM_POST_PMD:
                /* Do the anti-pop/bias ramp disable according to the TRM */
-               hs_pop = twl4030_read_reg_cache(w->codec,
-                                               TWL4030_REG_HS_POPN_SET);
                hs_pop &= ~TWL4030_RAMP_EN;
                twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop);
                /* Bypass the reg_cache to mute the headset */
@@ -497,6 +618,41 @@ static int headsetl_event(struct snd_soc_dapm_widget *w,
        return 0;
 }
 
+static int bypass_event(struct snd_soc_dapm_widget *w,
+               struct snd_kcontrol *kcontrol, int event)
+{
+       struct soc_mixer_control *m =
+               (struct soc_mixer_control *)w->kcontrols->private_value;
+       struct twl4030_priv *twl4030 = w->codec->private_data;
+       unsigned char reg;
+
+       reg = twl4030_read_reg_cache(w->codec, m->reg);
+
+       if (m->reg <= TWL4030_REG_ARXR2_APGA_CTL) {
+               /* Analog bypass */
+               if (reg & (1 << m->shift))
+                       twl4030->bypass_state |=
+                               (1 << (m->reg - TWL4030_REG_ARXL1_APGA_CTL));
+               else
+                       twl4030->bypass_state &=
+                               ~(1 << (m->reg - TWL4030_REG_ARXL1_APGA_CTL));
+       } else {
+               /* Digital bypass */
+               if (reg & (0x7 << m->shift))
+                       twl4030->bypass_state |= (1 << (m->shift ? 5 : 4));
+               else
+                       twl4030->bypass_state &= ~(1 << (m->shift ? 5 : 4));
+       }
+
+       if (w->codec->bias_level == SND_SOC_BIAS_STANDBY) {
+               if (twl4030->bypass_state)
+                       twl4030_codec_mute(w->codec, 0);
+               else
+                       twl4030_codec_mute(w->codec, 1);
+       }
+       return 0;
+}
+
 /*
  * Some of the gain controls in TWL (mostly those which are associated with
  * the outputs) are implemented in an interesting way:
@@ -691,6 +847,17 @@ static DECLARE_TLV_DB_SCALE(digital_capture_tlv, 0, 100, 0);
  */
 static DECLARE_TLV_DB_SCALE(input_gain_tlv, 0, 600, 0);
 
+static const char *twl4030_rampdelay_texts[] = {
+       "27/20/14 ms", "55/40/27 ms", "109/81/55 ms", "218/161/109 ms",
+       "437/323/218 ms", "874/645/437 ms", "1748/1291/874 ms",
+       "3495/2581/1748 ms"
+};
+
+static const struct soc_enum twl4030_rampdelay_enum =
+       SOC_ENUM_SINGLE(TWL4030_REG_HS_POPN_SET, 2,
+                       ARRAY_SIZE(twl4030_rampdelay_texts),
+                       twl4030_rampdelay_texts);
+
 static const struct snd_kcontrol_new twl4030_snd_controls[] = {
        /* Common playback gain controls */
        SOC_DOUBLE_R_TLV("DAC1 Digital Fine Playback Volume",
@@ -745,6 +912,8 @@ static const struct snd_kcontrol_new twl4030_snd_controls[] = {
 
        SOC_DOUBLE_TLV("Analog Capture Volume", TWL4030_REG_ANAMIC_GAIN,
                0, 3, 5, 0, input_gain_tlv),
+
+       SOC_ENUM("HS ramp delay", twl4030_rampdelay_enum),
 };
 
 static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
@@ -775,13 +944,13 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
 
        /* DACs */
        SND_SOC_DAPM_DAC("DAC Right1", "Right Front Playback",
-                       TWL4030_REG_AVDAC_CTL, 0, 0),
+                       SND_SOC_NOPM, 0, 0),
        SND_SOC_DAPM_DAC("DAC Left1", "Left Front Playback",
-                       TWL4030_REG_AVDAC_CTL, 1, 0),
+                       SND_SOC_NOPM, 0, 0),
        SND_SOC_DAPM_DAC("DAC Right2", "Right Rear Playback",
-                       TWL4030_REG_AVDAC_CTL, 2, 0),
+                       SND_SOC_NOPM, 0, 0),
        SND_SOC_DAPM_DAC("DAC Left2", "Left Rear Playback",
-                       TWL4030_REG_AVDAC_CTL, 3, 0),
+                       SND_SOC_NOPM, 0, 0),
 
        /* Analog PGAs */
        SND_SOC_DAPM_PGA("ARXR1_APGA", TWL4030_REG_ARXR1_APGA_CTL,
@@ -793,6 +962,37 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
        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,
+                       &twl4030_dapm_abypassr1_control, bypass_event,
+                       SND_SOC_DAPM_POST_REG),
+       SND_SOC_DAPM_SWITCH_E("Left1 Analog Loopback", SND_SOC_NOPM, 0, 0,
+                       &twl4030_dapm_abypassl1_control,
+                       bypass_event, SND_SOC_DAPM_POST_REG),
+       SND_SOC_DAPM_SWITCH_E("Right2 Analog Loopback", SND_SOC_NOPM, 0, 0,
+                       &twl4030_dapm_abypassr2_control,
+                       bypass_event, SND_SOC_DAPM_POST_REG),
+       SND_SOC_DAPM_SWITCH_E("Left2 Analog Loopback", SND_SOC_NOPM, 0, 0,
+                       &twl4030_dapm_abypassl2_control,
+                       bypass_event, SND_SOC_DAPM_POST_REG),
+
+       /* Digital bypasses */
+       SND_SOC_DAPM_SWITCH_E("Left Digital Loopback", SND_SOC_NOPM, 0, 0,
+                       &twl4030_dapm_dbypassl_control, bypass_event,
+                       SND_SOC_DAPM_POST_REG),
+       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_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 */
        /* Earpiece */
        SND_SOC_DAPM_VALUE_MUX("Earpiece Mux", SND_SOC_NOPM, 0, 0,
@@ -863,13 +1063,19 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
        SND_SOC_DAPM_MICBIAS("Mic Bias 1", TWL4030_REG_MICBIAS_CTL, 0, 0),
        SND_SOC_DAPM_MICBIAS("Mic Bias 2", TWL4030_REG_MICBIAS_CTL, 1, 0),
        SND_SOC_DAPM_MICBIAS("Headset Mic Bias", TWL4030_REG_MICBIAS_CTL, 2, 0),
+
 };
 
 static const struct snd_soc_dapm_route intercon[] = {
-       {"ARXL1_APGA", NULL, "DAC Left1"},
-       {"ARXR1_APGA", NULL, "DAC Right1"},
-       {"ARXL2_APGA", NULL, "DAC Left2"},
-       {"ARXR2_APGA", NULL, "DAC Right2"},
+       {"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"},
 
        /* Internal playback routings */
        /* Earpiece */
@@ -951,6 +1157,24 @@ static const struct snd_soc_dapm_route intercon[] = {
        {"ADC Virtual Left2", NULL, "TX2 Capture Route"},
        {"ADC Virtual Right2", NULL, "TX2 Capture Route"},
 
+       /* Analog bypass routes */
+       {"Right1 Analog Loopback", "Switch", "Analog Right Capture Route"},
+       {"Left1 Analog Loopback", "Switch", "Analog Left Capture Route"},
+       {"Right2 Analog Loopback", "Switch", "Analog Right Capture Route"},
+       {"Left2 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"},
+
+       /* Digital bypass routes */
+       {"Right Digital Loopback", "Volume", "TX1 Capture Route"},
+       {"Left Digital Loopback", "Volume", "TX1 Capture Route"},
+
+       {"Analog R2 Playback Mixer", NULL, "Right Digital Loopback"},
+       {"Analog L2 Playback Mixer", NULL, "Left Digital Loopback"},
+
 };
 
 static int twl4030_add_widgets(struct snd_soc_codec *codec)
@@ -967,16 +1191,25 @@ static int twl4030_add_widgets(struct snd_soc_codec *codec)
 static int twl4030_set_bias_level(struct snd_soc_codec *codec,
                                  enum snd_soc_bias_level level)
 {
+       struct twl4030_priv *twl4030 = codec->private_data;
+
        switch (level) {
        case SND_SOC_BIAS_ON:
-               twl4030_power_up(codec);
+               twl4030_codec_mute(codec, 0);
                break;
        case SND_SOC_BIAS_PREPARE:
-               /* TODO: develop a twl4030_prepare function */
+               twl4030_power_up(codec);
+               if (twl4030->bypass_state)
+                       twl4030_codec_mute(codec, 0);
+               else
+                       twl4030_codec_mute(codec, 1);
                break;
        case SND_SOC_BIAS_STANDBY:
-               /* TODO: develop a twl4030_standby function */
-               twl4030_power_down(codec);
+               twl4030_power_up(codec);
+               if (twl4030->bypass_state)
+                       twl4030_codec_mute(codec, 0);
+               else
+                       twl4030_codec_mute(codec, 1);
                break;
        case SND_SOC_BIAS_OFF:
                twl4030_power_down(codec);
@@ -987,6 +1220,52 @@ static int twl4030_set_bias_level(struct snd_soc_codec *codec,
        return 0;
 }
 
+static int twl4030_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;
+       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
+               twl4030->master_substream = substream;
+
+       return 0;
+}
+
+static void twl4030_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;
+       struct twl4030_priv *twl4030 = codec->private_data;
+
+       if (twl4030->master_substream == substream)
+               twl4030->master_substream = twl4030->slave_substream;
+
+       twl4030->slave_substream = NULL;
+}
+
 static int twl4030_hw_params(struct snd_pcm_substream *substream,
                           struct snd_pcm_hw_params *params,
                           struct snd_soc_dai *dai)
@@ -994,8 +1273,12 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream,
        struct snd_soc_pcm_runtime *rtd = substream->private_data;
        struct snd_soc_device *socdev = rtd->socdev;
        struct snd_soc_codec *codec = socdev->card->codec;
+       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 */
+               return 0;
 
        /* bit rate */
        old_mode = twl4030_read_reg_cache(codec,
@@ -1030,6 +1313,9 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream,
        case 48000:
                mode |= TWL4030_APLL_RATE_48000;
                break;
+       case 96000:
+               mode |= TWL4030_APLL_RATE_96000;
+               break;
        default:
                printk(KERN_ERR "TWL4030 hw params: unknown rate %d\n",
                        params_rate(params));
@@ -1038,6 +1324,7 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream,
 
        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);
        }
@@ -1153,13 +1440,21 @@ static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai,
 #define TWL4030_RATES   (SNDRV_PCM_RATE_8000_48000)
 #define TWL4030_FORMATS         (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE)
 
+static struct snd_soc_dai_ops twl4030_dai_ops = {
+       .startup        = twl4030_startup,
+       .shutdown       = twl4030_shutdown,
+       .hw_params      = twl4030_hw_params,
+       .set_sysclk     = twl4030_set_dai_sysclk,
+       .set_fmt        = twl4030_set_dai_fmt,
+};
+
 struct snd_soc_dai twl4030_dai = {
        .name = "twl4030",
        .playback = {
                .stream_name = "Playback",
                .channels_min = 2,
                .channels_max = 2,
-               .rates = TWL4030_RATES,
+               .rates = TWL4030_RATES | SNDRV_PCM_RATE_96000,
                .formats = TWL4030_FORMATS,},
        .capture = {
                .stream_name = "Capture",
@@ -1167,11 +1462,7 @@ struct snd_soc_dai twl4030_dai = {
                .channels_max = 2,
                .rates = TWL4030_RATES,
                .formats = TWL4030_FORMATS,},
-       .ops = {
-               .hw_params = twl4030_hw_params,
-               .set_sysclk = twl4030_set_dai_sysclk,
-               .set_fmt = twl4030_set_dai_fmt,
-       }
+       .ops = &twl4030_dai_ops,
 };
 EXPORT_SYMBOL_GPL(twl4030_dai);
 
@@ -1258,11 +1549,19 @@ static int twl4030_probe(struct platform_device *pdev)
 {
        struct snd_soc_device *socdev = platform_get_drvdata(pdev);
        struct snd_soc_codec *codec;
+       struct twl4030_priv *twl4030;
 
        codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
        if (codec == NULL)
                return -ENOMEM;
 
+       twl4030 = kzalloc(sizeof(struct twl4030_priv), GFP_KERNEL);
+       if (twl4030 == NULL) {
+               kfree(codec);
+               return -ENOMEM;
+       }
+
+       codec->private_data = twl4030;
        socdev->card->codec = codec;
        mutex_init(&codec->mutex);
        INIT_LIST_HEAD(&codec->dapm_widgets);
@@ -1280,8 +1579,10 @@ static int twl4030_remove(struct platform_device *pdev)
        struct snd_soc_codec *codec = socdev->card->codec;
 
        printk(KERN_INFO "TWL4030 Audio Codec remove\n");
+       twl4030_set_bias_level(codec, SND_SOC_BIAS_OFF);
        snd_soc_free_pcms(socdev);
        snd_soc_dapm_free(socdev);
+       kfree(codec->private_data);
        kfree(codec);
 
        return 0;