ASoC: wcd9310: Sync headphone/headset PA state
Upon headphone/headset removal, CODEC driver goes ahead disable
PA and RDAC to avoid fake insertion detection.
If userspace receives headphone/headset removal event followed
by insertion event quickly, user can miss earlier removal event so
it thinks later insertion event is also meaningless.
As a result PA and DAC are never turned on by user again.
Fixes to catch if previous removal event was handled by userspace to
enable PA and DAC from kernel if it wasn't.
CRs-Fixed: 318567
Change-Id: I8101bf18b4d42ebeee00b85be061083af2abbb04
Signed-off-by: Joonwoo Park <joonwoop@codeaurora.org>
diff --git a/sound/soc/codecs/wcd9310.c b/sound/soc/codecs/wcd9310.c
index 75864702..825556f2 100644
--- a/sound/soc/codecs/wcd9310.c
+++ b/sound/soc/codecs/wcd9310.c
@@ -81,6 +81,16 @@
BAND_MAX,
};
+/* Flags to track of PA and DAC state.
+ * PA and DAC should be tracked separately as AUXPGA loopback requires
+ * only PA to be turned on without DAC being on. */
+enum tabla_priv_ack_flags {
+ TABLA_HPHL_PA_OFF_ACK = 0,
+ TABLA_HPHR_PA_OFF_ACK,
+ TABLA_HPHL_DAC_OFF_ACK,
+ TABLA_HPHR_DAC_OFF_ACK
+};
+
struct tabla_priv {
struct snd_soc_codec *codec;
u32 adc_count;
@@ -112,6 +122,9 @@
u8 cfilt_k_value;
bool mbhc_micbias_switched;
+ /* track PA/DAC state */
+ unsigned long hph_pa_dac_state;
+
/*track tabla interface type*/
u8 intf_type;
@@ -1353,6 +1366,19 @@
return (hph_reg_val & 0x30) ? true : false;
}
+static bool tabla_is_hph_dac_on(struct snd_soc_codec *codec, int left)
+{
+ u8 hph_reg_val = 0;
+ if (left)
+ hph_reg_val = snd_soc_read(codec,
+ TABLA_A_RX_HPH_L_DAC_CTL);
+ else
+ hph_reg_val = snd_soc_read(codec,
+ TABLA_A_RX_HPH_R_DAC_CTL);
+
+ return (hph_reg_val & 0xC0) ? true : false;
+}
+
static void tabla_codec_switch_micbias(struct snd_soc_codec *codec,
int vddio_switch)
{
@@ -1678,12 +1704,22 @@
* would have been locked while snd_soc_jack_report also
* attempts to acquire same lock.
*/
- if ((tabla->hph_status & SND_JACK_OC_HPHL) &&
- strnstr(w->name, "HPHL", 4))
- schedule_work(&tabla->hphlocp_work);
- else if ((tabla->hph_status & SND_JACK_OC_HPHR) &&
- strnstr(w->name, "HPHR", 4))
- schedule_work(&tabla->hphrocp_work);
+ if (w->shift == 5) {
+ clear_bit(TABLA_HPHL_PA_OFF_ACK,
+ &tabla->hph_pa_dac_state);
+ clear_bit(TABLA_HPHL_DAC_OFF_ACK,
+ &tabla->hph_pa_dac_state);
+ if (tabla->hph_status & SND_JACK_OC_HPHL)
+ schedule_work(&tabla->hphlocp_work);
+ } else if (w->shift == 4) {
+ clear_bit(TABLA_HPHR_PA_OFF_ACK,
+ &tabla->hph_pa_dac_state);
+ clear_bit(TABLA_HPHR_DAC_OFF_ACK,
+ &tabla->hph_pa_dac_state);
+ if (tabla->hph_status & SND_JACK_OC_HPHR)
+ schedule_work(&tabla->hphrocp_work);
+ }
+
if (tabla->mbhc_micbias_switched)
tabla_codec_switch_micbias(codec, 0);
@@ -3349,6 +3385,35 @@
return IRQ_HANDLED;
}
+static void tabla_sync_hph_state(struct tabla_priv *tabla)
+{
+ if (test_and_clear_bit(TABLA_HPHR_PA_OFF_ACK,
+ &tabla->hph_pa_dac_state)) {
+ pr_debug("%s: HPHR clear flag and enable PA\n", __func__);
+ snd_soc_update_bits(tabla->codec, TABLA_A_RX_HPH_CNP_EN, 0x10,
+ 1 << 4);
+ }
+ if (test_and_clear_bit(TABLA_HPHL_PA_OFF_ACK,
+ &tabla->hph_pa_dac_state)) {
+ pr_debug("%s: HPHL clear flag and enable PA\n", __func__);
+ snd_soc_update_bits(tabla->codec, TABLA_A_RX_HPH_CNP_EN, 0x20,
+ 1 << 5);
+ }
+
+ if (test_and_clear_bit(TABLA_HPHR_DAC_OFF_ACK,
+ &tabla->hph_pa_dac_state)) {
+ pr_debug("%s: HPHR clear flag and enable DAC\n", __func__);
+ snd_soc_update_bits(tabla->codec, TABLA_A_RX_HPH_R_DAC_CTL,
+ 0xC0, 0xC0);
+ }
+ if (test_and_clear_bit(TABLA_HPHL_DAC_OFF_ACK,
+ &tabla->hph_pa_dac_state)) {
+ pr_debug("%s: HPHL clear flag and enable DAC\n", __func__);
+ snd_soc_update_bits(tabla->codec, TABLA_A_RX_HPH_L_DAC_CTL,
+ 0xC0, 0xC0);
+ }
+}
+
static irqreturn_t tabla_hs_insert_irq(int irq, void *data)
{
struct tabla_priv *priv = data;
@@ -3408,6 +3473,21 @@
if (priv->mbhc_micbias_switched)
tabla_codec_switch_micbias(codec, 0);
priv->hph_status &= ~SND_JACK_HEADPHONE;
+
+ /* If headphone PA is on, check if userspace receives
+ * removal event to sync-up PA's state */
+ if (tabla_is_hph_pa_on(codec)) {
+ set_bit(TABLA_HPHL_PA_OFF_ACK, &priv->hph_pa_dac_state);
+ set_bit(TABLA_HPHR_PA_OFF_ACK, &priv->hph_pa_dac_state);
+ }
+
+ if (tabla_is_hph_dac_on(codec, 1))
+ set_bit(TABLA_HPHL_DAC_OFF_ACK,
+ &priv->hph_pa_dac_state);
+ if (tabla_is_hph_dac_on(codec, 0))
+ set_bit(TABLA_HPHR_DAC_OFF_ACK,
+ &priv->hph_pa_dac_state);
+
if (priv->headset_jack) {
pr_debug("%s: Reporting removal\n", __func__);
tabla_snd_soc_jack_report(priv, priv->headset_jack,
@@ -3457,7 +3537,7 @@
}
tabla_codec_shutdown_hs_polling(codec);
tabla_codec_enable_hs_detect(codec, 0);
-
+ tabla_sync_hph_state(priv);
} else {
pr_debug("%s: Headset detected, mic_voltage = %x\n",
__func__, mic_voltage);
@@ -3470,6 +3550,7 @@
TABLA_JACK_MASK);
}
tabla_codec_start_hs_polling(codec);
+ tabla_sync_hph_state(priv);
}
tabla_unlock_sleep(priv);