ASoC: msm: Add support for 4-pole extension cable
- Add the support to report lineout device when inserted plug
has high impedance on microphone line.
- This feature is enabled only with gpio headset detection.
Change-Id: I55acbba62603ddfe93f4e6a32aca240bad2849fd
Signed-off-by: Ravi Kumar Alamanda <ralama@codeaurora.org>
diff --git a/sound/soc/codecs/wcd9310.c b/sound/soc/codecs/wcd9310.c
index 38b1da8..41186eb 100644
--- a/sound/soc/codecs/wcd9310.c
+++ b/sound/soc/codecs/wcd9310.c
@@ -64,7 +64,8 @@
#define NUM_ATTEMPTS_TO_REPORT 5
#define TABLA_JACK_MASK (SND_JACK_HEADSET | SND_JACK_OC_HPHL | \
- SND_JACK_OC_HPHR | SND_JACK_UNSUPPORTED)
+ SND_JACK_OC_HPHR | SND_JACK_LINEOUT | \
+ SND_JACK_UNSUPPORTED)
#define TABLA_I2S_MASTER_MODE_MASK 0x08
@@ -5308,7 +5309,8 @@
enum snd_jack_types jack_type)
{
struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec);
-
+ pr_debug("%s: enter insertion %d hph_status %x\n",
+ __func__, insertion, tabla->hph_status);
if (!insertion) {
/* Report removal */
tabla->hph_status &= ~jack_type;
@@ -5343,6 +5345,18 @@
tabla->current_plug = PLUG_TYPE_NONE;
tabla->mbhc_polling_active = false;
} else {
+ if (tabla->mbhc_cfg.detect_extn_cable) {
+ /* Report removal of current jack type */
+ if (tabla->hph_status != jack_type &&
+ tabla->mbhc_cfg.headset_jack) {
+ pr_debug("%s: Reporting removal (%x)\n",
+ __func__, tabla->hph_status);
+ tabla_snd_soc_jack_report(tabla,
+ tabla->mbhc_cfg.headset_jack,
+ 0, TABLA_JACK_MASK);
+ tabla->hph_status = 0;
+ }
+ }
/* Report insertion */
tabla->hph_status |= jack_type;
@@ -5353,7 +5367,8 @@
else if (jack_type == SND_JACK_HEADSET) {
tabla->mbhc_polling_active = true;
tabla->current_plug = PLUG_TYPE_HEADSET;
- }
+ } else if (jack_type == SND_JACK_LINEOUT)
+ tabla->current_plug = PLUG_TYPE_HIGH_HPH;
if (tabla->mbhc_cfg.headset_jack) {
pr_debug("%s: Reporting insertion %d(%x)\n", __func__,
jack_type, tabla->hph_status);
@@ -5364,6 +5379,7 @@
}
tabla_clr_and_turnon_hph_padac(tabla);
}
+ pr_debug("%s: leave hph_status %x\n", __func__, tabla->hph_status);
}
static int tabla_codec_enable_hs_detect(struct snd_soc_codec *codec,
@@ -5377,6 +5393,9 @@
const struct tabla_mbhc_plug_detect_cfg *plug_det =
TABLA_MBHC_CAL_PLUG_DET_PTR(tabla->mbhc_cfg.calibration);
+ pr_debug("%s: enter insertion(%d) trigger(0x%x)\n",
+ __func__, insertion, trigger);
+
if (!tabla->mbhc_cfg.calibration) {
pr_err("Error, no tabla calibration\n");
return -EINVAL;
@@ -5487,6 +5506,7 @@
wcd9xxx_enable_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION);
snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_INT_CTL, 0x1, 0x1);
+ pr_debug("%s: leave\n", __func__);
return 0;
}
@@ -6389,6 +6409,9 @@
{
struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec);
+ pr_debug("%s: enter current_plug(%d) new_plug(%d)\n",
+ __func__, tabla->current_plug, plug_type);
+
if (plug_type == PLUG_TYPE_HEADPHONE &&
tabla->current_plug == PLUG_TYPE_NONE) {
/* Nothing was reported previously
@@ -6397,11 +6420,14 @@
tabla_codec_report_plug(codec, 1, SND_JACK_HEADPHONE);
tabla_codec_cleanup_hs_polling(codec);
} else if (plug_type == PLUG_TYPE_GND_MIC_SWAP) {
- if (tabla->current_plug == PLUG_TYPE_HEADSET)
- tabla_codec_report_plug(codec, 0, SND_JACK_HEADSET);
- else if (tabla->current_plug == PLUG_TYPE_HEADPHONE)
- tabla_codec_report_plug(codec, 0, SND_JACK_HEADPHONE);
-
+ if (!tabla->mbhc_cfg.detect_extn_cable) {
+ if (tabla->current_plug == PLUG_TYPE_HEADSET)
+ tabla_codec_report_plug(codec, 0,
+ SND_JACK_HEADSET);
+ else if (tabla->current_plug == PLUG_TYPE_HEADPHONE)
+ tabla_codec_report_plug(codec, 0,
+ SND_JACK_HEADPHONE);
+ }
tabla_codec_report_plug(codec, 1, SND_JACK_UNSUPPORTED);
tabla_codec_cleanup_hs_polling(codec);
} else if (plug_type == PLUG_TYPE_HEADSET) {
@@ -6412,19 +6438,37 @@
msleep(100);
tabla_codec_start_hs_polling(codec);
} else if (plug_type == PLUG_TYPE_HIGH_HPH) {
- if (tabla->current_plug == PLUG_TYPE_NONE)
- tabla_codec_report_plug(codec, 1, SND_JACK_HEADPHONE);
- tabla_codec_cleanup_hs_polling(codec);
- pr_debug("setup mic trigger for further detection\n");
- tabla->lpi_enabled = true;
- tabla_codec_enable_hs_detect(codec, 1,
- MBHC_USE_MB_TRIGGER |
- MBHC_USE_HPHL_TRIGGER,
- false);
+ if (tabla->mbhc_cfg.detect_extn_cable) {
+ /* High impedance device found. Report as LINEOUT*/
+ tabla_codec_report_plug(codec, 1, SND_JACK_LINEOUT);
+ tabla_codec_cleanup_hs_polling(codec);
+ pr_debug("%s: setup mic trigger for further detection\n",
+ __func__);
+ tabla->lpi_enabled = true;
+ /*
+ * Do not enable HPHL trigger. If playback is active,
+ * it might lead to continuous false HPHL triggers
+ */
+ tabla_codec_enable_hs_detect(codec, 1,
+ MBHC_USE_MB_TRIGGER,
+ false);
+ } else {
+ if (tabla->current_plug == PLUG_TYPE_NONE)
+ tabla_codec_report_plug(codec, 1,
+ SND_JACK_HEADPHONE);
+ tabla_codec_cleanup_hs_polling(codec);
+ pr_debug("setup mic trigger for further detection\n");
+ tabla->lpi_enabled = true;
+ tabla_codec_enable_hs_detect(codec, 1,
+ MBHC_USE_MB_TRIGGER |
+ MBHC_USE_HPHL_TRIGGER,
+ false);
+ }
} else {
WARN(1, "Unexpected current plug_type %d, plug_type %d\n",
tabla->current_plug, plug_type);
}
+ pr_debug("%s: leave\n", __func__);
}
/* should be called under interrupt context that hold suspend */
@@ -6484,6 +6528,8 @@
bool ahighv = false, highv;
bool gndmicswapped = false;
+ pr_debug("%s: enter\n", __func__);
+
/* make sure override is on */
WARN_ON(!(snd_soc_read(codec, TABLA_A_CDC_MBHC_B1_CTL) & 0x04));
@@ -6593,6 +6639,7 @@
}
pr_debug("%s: Detected plug type %d\n", __func__, plug_type[0]);
+ pr_debug("%s: leave\n", __func__);
return plug_type[0];
}
@@ -6602,7 +6649,7 @@
struct snd_soc_codec *codec;
int retry = 0, pt_gnd_mic_swap_cnt = 0;
bool correction = false;
- enum tabla_mbhc_plug_type plug_type;
+ enum tabla_mbhc_plug_type plug_type = PLUG_TYPE_INVALID;
unsigned long timeout;
tabla = container_of(work, struct tabla_priv, hs_correct_plug_work);
@@ -6642,16 +6689,23 @@
plug_type = tabla_codec_get_plug_type(codec, true);
TABLA_RELEASE_LOCK(tabla->codec_resource_lock);
+ pr_debug("%s: attempt(%d) current_plug(%d) new_plug(%d)\n",
+ __func__, retry, tabla->current_plug, plug_type);
if (plug_type == PLUG_TYPE_INVALID) {
pr_debug("Invalid plug in attempt # %d\n", retry);
- if (retry == NUM_ATTEMPTS_TO_REPORT &&
+ if (!tabla->mbhc_cfg.detect_extn_cable &&
+ retry == NUM_ATTEMPTS_TO_REPORT &&
tabla->current_plug == PLUG_TYPE_NONE) {
tabla_codec_report_plug(codec, 1,
SND_JACK_HEADPHONE);
}
} else if (plug_type == PLUG_TYPE_HEADPHONE) {
pr_debug("Good headphone detected, continue polling mic\n");
- if (tabla->current_plug == PLUG_TYPE_NONE)
+ if (tabla->mbhc_cfg.detect_extn_cable) {
+ if (tabla->current_plug != plug_type)
+ tabla_codec_report_plug(codec, 1,
+ SND_JACK_HEADPHONE);
+ } else if (tabla->current_plug == PLUG_TYPE_NONE)
tabla_codec_report_plug(codec, 1,
SND_JACK_HEADPHONE);
} else {
@@ -6692,7 +6746,21 @@
tabla_turn_onoff_override(codec, false);
tabla->mbhc_cfg.mclk_cb_fn(codec, 0, false);
- pr_debug("%s: leave\n", __func__);
+
+ if (tabla->mbhc_cfg.detect_extn_cable) {
+ TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock);
+ if (tabla->current_plug == PLUG_TYPE_HEADPHONE ||
+ tabla->current_plug == PLUG_TYPE_GND_MIC_SWAP ||
+ tabla->current_plug == PLUG_TYPE_INVALID ||
+ plug_type == PLUG_TYPE_INVALID) {
+ /* Enable removal detection */
+ tabla_codec_cleanup_hs_polling(codec);
+ tabla_codec_enable_hs_detect(codec, 0, 0, false);
+ }
+ TABLA_RELEASE_LOCK(tabla->codec_resource_lock);
+ }
+ pr_debug("%s: leave current_plug(%d)\n",
+ __func__, tabla->current_plug);
/* unlock sleep */
wcd9xxx_unlock_sleep(tabla->codec->control_data);
}
@@ -6729,6 +6797,7 @@
__func__, plug_type);
tabla_find_plug_and_report(codec, plug_type);
}
+ pr_debug("%s: leave\n", __func__);
}
/* called under codec_resource_lock acquisition */
@@ -6738,7 +6807,7 @@
struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec);
const struct tabla_mbhc_plug_detect_cfg *plug_det =
TABLA_MBHC_CAL_PLUG_DET_PTR(tabla->mbhc_cfg.calibration);
-
+ pr_debug("%s: enter\n", __func__);
/* Turn on the override,
* tabla_codec_setup_hs_polling requires override on */
tabla_turn_onoff_override(codec, true);
@@ -6757,6 +6826,7 @@
"plug\n", __func__);
else
tabla_codec_decide_gpio_plug(codec);
+ pr_debug("%s: leave\n", __func__);
return;
}
@@ -6788,13 +6858,23 @@
/* avoid false button press detect */
msleep(50);
tabla_codec_start_hs_polling(codec);
+ } else if (tabla->mbhc_cfg.detect_extn_cable &&
+ plug_type == PLUG_TYPE_HIGH_HPH) {
+ pr_debug("%s: High impedance plug type detected\n", __func__);
+ tabla_codec_report_plug(codec, 1, SND_JACK_LINEOUT);
+ /* Enable insertion detection on the other end of cable */
+ tabla_codec_cleanup_hs_polling(codec);
+ tabla_codec_enable_hs_detect(codec, 1,
+ MBHC_USE_MB_TRIGGER, false);
}
+ pr_debug("%s: leave\n", __func__);
}
/* called only from interrupt which is under codec_resource_lock acquisition */
static void tabla_hs_insert_irq_gpio(struct tabla_priv *priv, bool is_removal)
{
struct snd_soc_codec *codec = priv->codec;
+ struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec);
if (!is_removal) {
pr_debug("%s: MIC trigger insertion interrupt\n", __func__);
@@ -6815,6 +6895,19 @@
pr_debug("%s: Invalid insertion, "
"stop plug detection\n", __func__);
}
+ } else if (tabla->mbhc_cfg.detect_extn_cable) {
+ pr_debug("%s: Removal\n", __func__);
+ if (!tabla_hs_gpio_level_remove(tabla)) {
+ /*
+ * gpio says, something is still inserted, could be
+ * extension cable i.e. headset is removed from
+ * extension cable
+ */
+ /* cancel detect plug */
+ tabla_cancel_hs_detect_plug(tabla,
+ &tabla->hs_correct_plug_work);
+ tabla_codec_decide_gpio_plug(codec);
+ }
} else {
pr_err("%s: GPIO used, invalid MBHC Removal\n", __func__);
}
@@ -6880,6 +6973,29 @@
}
}
+/* called only from interrupt which is under codec_resource_lock acquisition */
+static void tabla_hs_insert_irq_extn(struct tabla_priv *priv,
+ bool is_mb_trigger)
+{
+ struct snd_soc_codec *codec = priv->codec;
+ 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);
+
+ if (is_mb_trigger) {
+ pr_debug("%s: Waiting for Headphone left trigger\n",
+ __func__);
+ tabla_codec_enable_hs_detect(codec, 1, MBHC_USE_HPHL_TRIGGER,
+ false);
+ } else {
+ pr_debug("%s: HPHL trigger received, detecting plug type\n",
+ __func__);
+ tabla_codec_detect_plug_type(codec);
+ }
+}
+
static irqreturn_t tabla_hs_insert_irq(int irq, void *data)
{
bool is_mb_trigger, is_removal;
@@ -6900,7 +7016,10 @@
snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x13, 0x00);
snd_soc_update_bits(codec, priv->mbhc_bias_regs.ctl_reg, 0x01, 0x00);
- if (priv->mbhc_cfg.gpio)
+ if (priv->mbhc_cfg.detect_extn_cable &&
+ priv->current_plug == PLUG_TYPE_HIGH_HPH)
+ tabla_hs_insert_irq_extn(priv, is_mb_trigger);
+ else if (priv->mbhc_cfg.gpio)
tabla_hs_insert_irq_gpio(priv, is_removal);
else
tabla_hs_insert_irq_nogpio(priv, is_removal, is_mb_trigger);
@@ -7004,10 +7123,10 @@
static void tabla_hs_remove_irq_gpio(struct tabla_priv *priv)
{
struct snd_soc_codec *codec = priv->codec;
-
+ pr_debug("%s: enter\n", __func__);
if (tabla_hs_remove_settle(codec))
tabla_codec_start_hs_polling(codec);
- pr_debug("%s: remove settle done\n", __func__);
+ pr_debug("%s: leave\n", __func__);
}
/* called only from interrupt which is under codec_resource_lock acquisition */
@@ -7020,6 +7139,7 @@
TABLA_MBHC_CAL_GENERAL_PTR(priv->mbhc_cfg.calibration);
int min_us = TABLA_FAKE_REMOVAL_MIN_PERIOD_MS * 1000;
+ pr_debug("%s: enter\n", __func__);
if (priv->current_plug != PLUG_TYPE_HEADSET) {
pr_debug("%s(): Headset is not inserted, ignore removal\n",
__func__);
@@ -7047,25 +7167,41 @@
} while (min_us > 0);
if (removed) {
- /* Cancel possibly running hs_detect_work */
- tabla_cancel_hs_detect_plug(priv,
+ if (priv->mbhc_cfg.detect_extn_cable) {
+ if (!tabla_hs_gpio_level_remove(priv)) {
+ /*
+ * extension cable is still plugged in
+ * report it as LINEOUT device
+ */
+ tabla_codec_report_plug(codec, 1,
+ SND_JACK_LINEOUT);
+ tabla_codec_cleanup_hs_polling(codec);
+ tabla_codec_enable_hs_detect(codec, 1,
+ MBHC_USE_MB_TRIGGER,
+ false);
+ }
+ } else {
+ /* 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
- * switched to VDDIO.
- */
- tabla_codec_switch_micbias(codec, 0);
+ /*
+ * If this removal is not false, first check the micbias
+ * switch status and switch it to LDOH if it is already
+ * switched to VDDIO.
+ */
+ tabla_codec_switch_micbias(codec, 0);
- tabla_codec_report_plug(codec, 0, SND_JACK_HEADSET);
- tabla_codec_cleanup_hs_polling(codec);
- tabla_codec_enable_hs_detect(codec, 1,
- MBHC_USE_MB_TRIGGER |
- MBHC_USE_HPHL_TRIGGER,
- true);
+ tabla_codec_report_plug(codec, 0, SND_JACK_HEADSET);
+ tabla_codec_cleanup_hs_polling(codec);
+ tabla_codec_enable_hs_detect(codec, 1,
+ MBHC_USE_MB_TRIGGER |
+ MBHC_USE_HPHL_TRIGGER,
+ true);
+ }
} else {
tabla_codec_start_hs_polling(codec);
}
+ pr_debug("%s: leave\n", __func__);
}
static irqreturn_t tabla_hs_remove_irq(int irq, void *data)
@@ -7080,10 +7216,12 @@
if (vddio)
__tabla_codec_switch_micbias(priv->codec, 0, false, true);
- if (priv->mbhc_cfg.gpio)
- tabla_hs_remove_irq_gpio(priv);
- else
+ if ((priv->mbhc_cfg.detect_extn_cable &&
+ !tabla_hs_gpio_level_remove(priv)) ||
+ !priv->mbhc_cfg.gpio) {
tabla_hs_remove_irq_nogpio(priv);
+ } else
+ tabla_hs_remove_irq_gpio(priv);
/* if driver turned off vddio switch and headset is not removed,
* turn on the vddio switch back, if headset is removed then vddio
@@ -7171,6 +7309,9 @@
tabla_codec_cleanup_hs_polling(codec);
tabla_codec_report_plug(codec, 0, SND_JACK_HEADSET);
is_removed = true;
+ } else if (tabla->current_plug == PLUG_TYPE_HIGH_HPH) {
+ tabla_codec_report_plug(codec, 0, SND_JACK_LINEOUT);
+ is_removed = true;
}
if (is_removed) {
diff --git a/sound/soc/codecs/wcd9310.h b/sound/soc/codecs/wcd9310.h
index 1cca360..4c9f8b4 100644
--- a/sound/soc/codecs/wcd9310.h
+++ b/sound/soc/codecs/wcd9310.h
@@ -176,6 +176,7 @@
unsigned int gpio;
unsigned int gpio_irq;
int gpio_level_insert;
+ bool detect_extn_cable;
/* swap_gnd_mic returns true if extern GND/MIC swap switch toggled */
bool (*swap_gnd_mic) (struct snd_soc_codec *);
};
diff --git a/sound/soc/msm/apq8064.c b/sound/soc/msm/apq8064.c
index ece0a15..e46064b 100644
--- a/sound/soc/msm/apq8064.c
+++ b/sound/soc/msm/apq8064.c
@@ -105,6 +105,10 @@
module_param(apq8064_hs_detect_use_gpio, int, 0444);
MODULE_PARM_DESC(apq8064_hs_detect_use_gpio, "Use GPIO for headset detection");
+static bool apq8064_hs_detect_extn_cable;
+module_param(apq8064_hs_detect_extn_cable, bool, 0444);
+MODULE_PARM_DESC(apq8064_hs_detect_extn_cable, "Enable extension cable feature");
+
static bool apq8064_hs_detect_use_firmware;
module_param(apq8064_hs_detect_use_firmware, bool, 0444);
MODULE_PARM_DESC(apq8064_hs_detect_use_firmware, "Use firmware for headset "
@@ -124,6 +128,7 @@
.gpio = 0,
.gpio_irq = 0,
.gpio_level_insert = 1,
+ .detect_extn_cable = false,
};
static struct mutex cdc_mclk_mutex;
@@ -1152,8 +1157,9 @@
snd_soc_dapm_sync(dapm);
err = snd_soc_jack_new(codec, "Headset Jack",
- (SND_JACK_HEADSET | SND_JACK_OC_HPHL |
- SND_JACK_OC_HPHR | SND_JACK_UNSUPPORTED),
+ (SND_JACK_HEADSET | SND_JACK_LINEOUT |
+ SND_JACK_OC_HPHL | SND_JACK_OC_HPHR |
+ SND_JACK_UNSUPPORTED),
&hs_jack);
if (err) {
pr_err("failed to create new jack\n");
@@ -1206,6 +1212,8 @@
return err;
}
gpio_direction_input(JACK_DETECT_GPIO);
+ if (apq8064_hs_detect_extn_cable)
+ mbhc_cfg.detect_extn_cable = true;
} else
pr_debug("%s: Not using MBHC mechanical switch\n", __func__);
diff --git a/sound/soc/msm/msm8960.c b/sound/soc/msm/msm8960.c
index e98fe64..6bd6adc 100644
--- a/sound/soc/msm/msm8960.c
+++ b/sound/soc/msm/msm8960.c
@@ -88,6 +88,11 @@
module_param(hs_detect_use_gpio, bool, 0444);
MODULE_PARM_DESC(hs_detect_use_gpio, "Use GPIO for headset detection");
+static bool hs_detect_extn_cable;
+module_param(hs_detect_extn_cable, bool, 0444);
+MODULE_PARM_DESC(hs_detect_extn_cable, "Enable extension cable feature");
+
+
static bool hs_detect_use_firmware;
module_param(hs_detect_use_firmware, bool, 0444);
MODULE_PARM_DESC(hs_detect_use_firmware, "Use firmware for headset detection");
@@ -108,6 +113,7 @@
.gpio_irq = 0,
.gpio_level_insert = 1,
.swap_gnd_mic = NULL,
+ .detect_extn_cable = false,
};
static u32 us_euro_sel_gpio = PM8921_GPIO_PM_TO_SYS(JACK_US_EURO_SEL_GPIO);
@@ -911,9 +917,10 @@
snd_soc_dapm_sync(dapm);
err = snd_soc_jack_new(codec, "Headset Jack",
- (SND_JACK_HEADSET | SND_JACK_OC_HPHL |
- SND_JACK_OC_HPHR | SND_JACK_UNSUPPORTED),
- &hs_jack);
+ (SND_JACK_HEADSET | SND_JACK_LINEOUT |
+ SND_JACK_OC_HPHL | SND_JACK_OC_HPHR |
+ SND_JACK_UNSUPPORTED),
+ &hs_jack);
if (err) {
pr_err("failed to create new jack\n");
return err;
@@ -934,6 +941,8 @@
if (hs_detect_use_gpio) {
mbhc_cfg.gpio = PM8921_GPIO_PM_TO_SYS(JACK_DETECT_GPIO);
mbhc_cfg.gpio_irq = JACK_DETECT_INT;
+ if (hs_detect_extn_cable)
+ mbhc_cfg.detect_extn_cable = true;
}
if (mbhc_cfg.gpio) {