ASoC: twl6040: Add digital mute support
authorPeter Ujfalusi <peter.ujfalusi@ti.com>
Mon, 24 Jun 2013 13:42:06 +0000 (15:42 +0200)
committerMark Brown <broonie@linaro.org>
Mon, 24 Jun 2013 15:06:03 +0000 (16:06 +0100)
To reduce pop noise during playback stream start and stop the codec needs
to have the digital_mute callback implemented.
The codec need to be muted before the CPU dai has been stopped (McPDM).
Stopping the McPDM will generate a pop on the codec since no signal on the
PDM bus means full negative amplitude.
By managing the mute/unmute state of the outputs we can decrease the amount
of pop noise when playback starts or stops.

Signed-off-by: Peter Ujfalusi <peter.ujfalusi@ti.com>
Signed-off-by: Mark Brown <broonie@linaro.org>
sound/soc/codecs/twl6040.c

index 9ea3dbc..44621dd 100644 (file)
@@ -75,6 +75,8 @@ struct twl6040_data {
        int pll_power_mode;
        int hs_power_mode;
        int hs_power_mode_locked;
+       bool dl1_unmuted;
+       bool dl2_unmuted;
        unsigned int clk_in;
        unsigned int sysclk;
        struct twl6040_jack_data hs_jack;
@@ -228,6 +230,25 @@ static int twl6040_read_reg_volatile(struct snd_soc_codec *codec,
        return value;
 }
 
+static bool twl6040_is_path_unmuted(struct snd_soc_codec *codec,
+                                   unsigned int reg)
+{
+       struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+
+       switch (reg) {
+       case TWL6040_REG_HSLCTL:
+       case TWL6040_REG_HSRCTL:
+       case TWL6040_REG_EARCTL:
+               /* DL1 path */
+               return priv->dl1_unmuted;
+       case TWL6040_REG_HFLCTL:
+       case TWL6040_REG_HFRCTL:
+               return priv->dl2_unmuted;
+       default:
+               return 1;
+       };
+}
+
 /*
  * write to the twl6040 register space
  */
@@ -240,7 +261,8 @@ static int twl6040_write(struct snd_soc_codec *codec,
                return -EIO;
 
        twl6040_write_reg_cache(codec, reg, value);
-       if (likely(reg < TWL6040_REG_SW_SHADOW))
+       if (likely(reg < TWL6040_REG_SW_SHADOW) &&
+           twl6040_is_path_unmuted(codec, reg))
                return twl6040_reg_write(twl6040, reg, value);
        else
                return 0;
@@ -1034,11 +1056,78 @@ static int twl6040_set_dai_sysclk(struct snd_soc_dai *codec_dai,
        return 0;
 }
 
+static void twl6040_mute_path(struct snd_soc_codec *codec, enum twl6040_dai_id id,
+                            int mute)
+{
+       struct twl6040 *twl6040 = codec->control_data;
+       struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+       int hslctl, hsrctl, earctl;
+       int hflctl, hfrctl;
+
+       switch (id) {
+       case TWL6040_DAI_DL1:
+               hslctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSLCTL);
+               hsrctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSRCTL);
+               earctl = twl6040_read_reg_cache(codec, TWL6040_REG_EARCTL);
+
+               if (mute) {
+                       /* Power down drivers and DACs */
+                       earctl &= ~0x01;
+                       hslctl &= ~(TWL6040_HSDRVENA | TWL6040_HSDACENA);
+                       hsrctl &= ~(TWL6040_HSDRVENA | TWL6040_HSDACENA);
+
+               }
+
+               twl6040_reg_write(twl6040, TWL6040_REG_EARCTL, earctl);
+               twl6040_reg_write(twl6040, TWL6040_REG_HSLCTL, hslctl);
+               twl6040_reg_write(twl6040, TWL6040_REG_HSRCTL, hsrctl);
+               priv->dl1_unmuted = !mute;
+               break;
+       case TWL6040_DAI_DL2:
+               hflctl = twl6040_read_reg_cache(codec, TWL6040_REG_HFLCTL);
+               hfrctl = twl6040_read_reg_cache(codec, TWL6040_REG_HFRCTL);
+
+               if (mute) {
+                       /* Power down drivers and DACs */
+                       hflctl &= ~(TWL6040_HFDACENA | TWL6040_HFPGAENA |
+                                   TWL6040_HFDRVENA);
+                       hfrctl &= ~(TWL6040_HFDACENA | TWL6040_HFPGAENA |
+                                   TWL6040_HFDRVENA);
+               }
+
+               twl6040_reg_write(twl6040, TWL6040_REG_HFLCTL, hflctl);
+               twl6040_reg_write(twl6040, TWL6040_REG_HFRCTL, hfrctl);
+               priv->dl2_unmuted = !mute;
+               break;
+       default:
+               break;
+       };
+}
+
+static int twl6040_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+       switch (dai->id) {
+       case TWL6040_DAI_LEGACY:
+               twl6040_mute_path(dai->codec, TWL6040_DAI_DL1, mute);
+               twl6040_mute_path(dai->codec, TWL6040_DAI_DL2, mute);
+               break;
+       case TWL6040_DAI_DL1:
+       case TWL6040_DAI_DL2:
+               twl6040_mute_path(dai->codec, dai->id, mute);
+               break;
+       default:
+               break;
+       }
+
+       return 0;
+}
+
 static const struct snd_soc_dai_ops twl6040_dai_ops = {
        .startup        = twl6040_startup,
        .hw_params      = twl6040_hw_params,
        .prepare        = twl6040_prepare,
        .set_sysclk     = twl6040_set_dai_sysclk,
+       .digital_mute   = twl6040_digital_mute,
 };
 
 static struct snd_soc_dai_driver twl6040_dai[] = {