ASoC: WCD9310: Perform microphone polling to correct plug type
During slow insertion of headset, it may be possible that the
headset is wrongly detected as a headphone. This results in
the headset mic being non-functional.
Fix by polling the microphone voltage after a plug is detected
as a valid Headphone. In case the microphone voltage settles to
a valid headset voltage, correct the plug type from Headphone to
Headset.
CRs-fixed: 370332
Change-Id: I5280542e857940f8d228c5f0ded1d2fde301168f
Signed-off-by: Bhalchandra Gajare <gajare@codeaurora.org>
diff --git a/sound/soc/codecs/wcd9310.c b/sound/soc/codecs/wcd9310.c
index ed10ebe..74ae595 100644
--- a/sound/soc/codecs/wcd9310.c
+++ b/sound/soc/codecs/wcd9310.c
@@ -329,6 +329,12 @@
*/
struct mutex codec_resource_lock;
+ /* Work to perform polling on microphone voltage
+ * in order to correct plug type once plug type
+ * is detected as headphone
+ */
+ struct work_struct hs_correct_plug_work_nogpio;
+
#ifdef CONFIG_DEBUG_FS
struct dentry *debugfs_poke;
struct dentry *debugfs_mbhc;
@@ -6287,22 +6293,24 @@
}
/* should be called under interrupt context that hold suspend */
-static void tabla_schedule_hs_detect_plug(struct tabla_priv *tabla)
+static void tabla_schedule_hs_detect_plug(struct tabla_priv *tabla,
+ struct work_struct *correct_plug_work)
{
pr_debug("%s: scheduling tabla_hs_correct_gpio_plug\n", __func__);
tabla->hs_detect_work_stop = false;
wcd9xxx_lock_sleep(tabla->codec->control_data);
- schedule_work(&tabla->hs_correct_plug_work);
+ schedule_work(correct_plug_work);
}
/* called under codec_resource_lock acquisition */
-static void tabla_cancel_hs_detect_plug(struct tabla_priv *tabla)
+static void tabla_cancel_hs_detect_plug(struct tabla_priv *tabla,
+ struct work_struct *correct_plug_work)
{
pr_debug("%s: canceling hs_correct_plug_work\n", __func__);
tabla->hs_detect_work_stop = true;
wmb();
TABLA_RELEASE_LOCK(tabla->codec_resource_lock);
- if (cancel_work_sync(&tabla->hs_correct_plug_work)) {
+ if (cancel_work_sync(correct_plug_work)) {
pr_debug("%s: hs_correct_plug_work is canceled\n", __func__);
wcd9xxx_unlock_sleep(tabla->codec->control_data);
}
@@ -6574,11 +6582,13 @@
if (plug_type == PLUG_TYPE_INVALID ||
plug_type == PLUG_TYPE_GND_MIC_SWAP) {
- tabla_schedule_hs_detect_plug(tabla);
+ tabla_schedule_hs_detect_plug(tabla,
+ &tabla->hs_correct_plug_work);
} else if (plug_type == PLUG_TYPE_HEADPHONE) {
tabla_codec_report_plug(codec, 1, SND_JACK_HEADPHONE);
- tabla_schedule_hs_detect_plug(tabla);
+ tabla_schedule_hs_detect_plug(tabla,
+ &tabla->hs_correct_plug_work);
} else {
pr_debug("%s: Valid plug found, determine plug type %d\n",
__func__, plug_type);
@@ -6635,6 +6645,8 @@
tabla_codec_report_plug(codec, 1, SND_JACK_HEADPHONE);
tabla_codec_cleanup_hs_polling(codec);
tabla_codec_enable_hs_detect(codec, 0, 0, false);
+ tabla_schedule_hs_detect_plug(tabla,
+ &tabla->hs_correct_plug_work_nogpio);
} else if (plug_type == PLUG_TYPE_HEADSET) {
pr_debug("%s: Headset detected\n", __func__);
tabla_codec_report_plug(codec, 1, SND_JACK_HEADSET);
@@ -6681,10 +6693,13 @@
int ret;
struct snd_soc_codec *codec = priv->codec;
struct wcd9xxx *core = dev_get_drvdata(priv->codec->dev->parent);
+ struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec);
+
+ /* Cancel possibly running hs_detect_work */
+ tabla_cancel_hs_detect_plug(tabla,
+ &tabla->hs_correct_plug_work_nogpio);
if (is_removal) {
- /* cancel possiblely running hs detect work */
- tabla_cancel_hs_detect_plug(priv);
/*
* If headphone is removed while playback is in progress,
@@ -6898,8 +6913,9 @@
} while (min_us > 0);
if (removed) {
- /* cancel possiblely running hs detect work */
- tabla_cancel_hs_detect_plug(priv);
+ /* Cancel possibly running hs_detect_work */
+ tabla_cancel_hs_detect_plug(priv,
+ &priv->hs_correct_plug_work_nogpio);
/*
* If this removal is not false, first check the micbias
* switch status and switch it to LDOH if it is already
@@ -6994,7 +7010,8 @@
wmb();
/* cancel detect plug */
- tabla_cancel_hs_detect_plug(tabla);
+ tabla_cancel_hs_detect_plug(tabla,
+ &tabla->hs_correct_plug_work);
/* Disable Mic Bias pull down and HPH Switch to GND */
snd_soc_update_bits(codec, tabla->mbhc_bias_regs.ctl_reg, 0x01,
@@ -7006,7 +7023,8 @@
wmb();
/* cancel detect plug */
- tabla_cancel_hs_detect_plug(tabla);
+ tabla_cancel_hs_detect_plug(tabla,
+ &tabla->hs_correct_plug_work);
if (tabla->current_plug == PLUG_TYPE_HEADPHONE) {
tabla_codec_report_plug(codec, 0, SND_JACK_HEADPHONE);
@@ -7066,6 +7084,76 @@
return r;
}
+static void tabla_hs_correct_plug_nogpio(struct work_struct *work)
+{
+ struct tabla_priv *tabla;
+ struct snd_soc_codec *codec;
+ unsigned long timeout;
+ int retry = 0;
+ enum tabla_mbhc_plug_type plug_type;
+ bool is_headset = false;
+
+ pr_debug("%s(): Poll Microphone voltage for %d seconds\n",
+ __func__, TABLA_HS_DETECT_PLUG_TIME_MS / 1000);
+
+ tabla = container_of(work, struct tabla_priv,
+ hs_correct_plug_work_nogpio);
+ codec = tabla->codec;
+
+ /* Make sure the MBHC mux is connected to MIC Path */
+ snd_soc_write(codec, TABLA_A_MBHC_SCALING_MUX_1, 0x84);
+
+ /* setup for microphone polling */
+ tabla_turn_onoff_override(codec, true);
+ tabla->mbhc_cfg.mclk_cb_fn(codec, 1, false);
+
+ timeout = jiffies + msecs_to_jiffies(TABLA_HS_DETECT_PLUG_TIME_MS);
+ while (!time_after(jiffies, timeout)) {
+ ++retry;
+
+ msleep(TABLA_HS_DETECT_PLUG_INERVAL_MS);
+ TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock);
+ plug_type = tabla_codec_get_plug_type(codec, false);
+ TABLA_RELEASE_LOCK(tabla->codec_resource_lock);
+
+ if (plug_type == PLUG_TYPE_HIGH_HPH
+ || plug_type == PLUG_TYPE_INVALID) {
+
+ /* this means the plug is removed
+ * End microphone polling and setup
+ * for low power removal detection.
+ */
+ pr_debug("%s(): Plug may be removed, setup removal\n",
+ __func__);
+ break;
+ } else if (plug_type == PLUG_TYPE_HEADSET) {
+ /* Plug is corrected from headphone to headset,
+ * report headset and end the polling
+ */
+ is_headset = true;
+ TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock);
+ tabla_turn_onoff_override(codec, false);
+ tabla_codec_report_plug(codec, 1, SND_JACK_HEADSET);
+ tabla_codec_start_hs_polling(codec);
+ TABLA_RELEASE_LOCK(tabla->codec_resource_lock);
+ pr_debug("%s(): corrected from headphone to headset\n",
+ __func__);
+ break;
+ }
+ }
+
+ /* Undo setup for microphone polling depending
+ * result from polling
+ */
+ tabla->mbhc_cfg.mclk_cb_fn(codec, 0, false);
+ if (!is_headset) {
+ tabla_turn_onoff_override(codec, false);
+ tabla_codec_cleanup_hs_polling(codec);
+ tabla_codec_enable_hs_detect(codec, 0, 0, false);
+ }
+ wcd9xxx_unlock_sleep(codec->control_data);
+}
+
static int tabla_mbhc_init_and_calibrate(struct tabla_priv *tabla)
{
int ret = 0;
@@ -7078,6 +7166,8 @@
tabla->mbhc_cfg.mclk_cb_fn(codec, 0, false);
tabla_codec_calibrate_hs_polling(codec);
if (!tabla->mbhc_cfg.gpio) {
+ INIT_WORK(&tabla->hs_correct_plug_work_nogpio,
+ tabla_hs_correct_plug_nogpio);
ret = tabla_codec_enable_hs_detect(codec, 1,
MBHC_USE_MB_TRIGGER |
MBHC_USE_HPHL_TRIGGER,