ASoC: twl6040: Add digital mute support

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>
This commit is contained in:
Peter Ujfalusi 2013-06-24 15:42:06 +02:00 committed by Mark Brown
parent 68897497aa
commit 98c5fb1f87
1 changed files with 90 additions and 1 deletions

View 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[] = {