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