ASoC: wcd9310: Introduce MBHC multi button support
So far wcd9310 driver assumes that any headset button press is headset
button 0 press.
Change driver to measure voltage when button is pressed to discriminate and
notify button's number.
In order to have better detection performance, default measurement
samples are shortened.
CRs-fixed: 327081
Change-Id: Ic0e2a5297974e1858684f0ef2a0b424141ee7464
Signed-off-by: Joonwoo Park <joonwoop@codeaurora.org>
diff --git a/sound/soc/codecs/wcd9310.c b/sound/soc/codecs/wcd9310.c
index 2bdb917..e116ca9 100644
--- a/sound/soc/codecs/wcd9310.c
+++ b/sound/soc/codecs/wcd9310.c
@@ -23,7 +23,6 @@
#include <linux/mfd/wcd9310/pdata.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
-#include <sound/jack.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/tlv.h>
@@ -54,6 +53,7 @@
#define TABLA_MCLK_RATE_9600KHZ 9600000
#define TABLA_FAKE_INS_THRESHOLD_MS 2500
+#define TABLA_FAKE_REMOVAL_MIN_PERIOD_MS 50
static const DECLARE_TLV_DB_SCALE(digital_gain, 0, 1, 0);
static const DECLARE_TLV_DB_SCALE(line_gain, 0, 7, 1);
@@ -2756,7 +2756,6 @@
tabla->mbhc_data.npoll);
snd_soc_write(codec, TABLA_A_CDC_MBHC_TIMER_B3_CTL,
tabla->mbhc_data.nbounce_wait);
-
n_cic = tabla_mbhc_cal_btn_det_mp(btn_det, TABLA_BTN_DET_N_CIC);
snd_soc_write(codec, TABLA_A_CDC_MBHC_TIMER_B6_CTL,
n_cic[tabla_codec_mclk_index(tabla)]);
@@ -3119,7 +3118,6 @@
/* Turn off the override after measuring mic voltage */
snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x04, 0x00);
- pr_debug("read microphone bias value %04x\n", bias_value);
return bias_value;
}
@@ -3762,39 +3760,139 @@
}
EXPORT_SYMBOL_GPL(tabla_hs_detect);
+static int tabla_determine_button(const struct tabla_priv *priv,
+ const s32 bias_mv)
+{
+ s16 *v_btn_low, *v_btn_high;
+ struct tabla_mbhc_btn_detect_cfg *btn_det;
+ int i, btn = -1;
+
+ btn_det = TABLA_MBHC_CAL_BTN_DET_PTR(priv->calibration);
+ v_btn_low = tabla_mbhc_cal_btn_det_mp(btn_det, TABLA_BTN_DET_V_BTN_LOW);
+ v_btn_high = tabla_mbhc_cal_btn_det_mp(btn_det,
+ TABLA_BTN_DET_V_BTN_HIGH);
+ for (i = 0; i < btn_det->num_btn; i++) {
+ if ((v_btn_low[i] <= bias_mv) && (v_btn_high[i] >= bias_mv)) {
+ btn = i;
+ break;
+ }
+ }
+
+ if (btn == -1)
+ pr_debug("%s: couldn't find button number for mic mv %d\n",
+ __func__, bias_mv);
+
+ return btn;
+}
+
+static int tabla_get_button_mask(const int btn)
+{
+ int mask = 0;
+ switch (btn) {
+ case 0:
+ mask = SND_JACK_BTN_0;
+ break;
+ case 1:
+ mask = SND_JACK_BTN_1;
+ break;
+ case 2:
+ mask = SND_JACK_BTN_2;
+ break;
+ case 3:
+ mask = SND_JACK_BTN_3;
+ break;
+ case 4:
+ mask = SND_JACK_BTN_4;
+ break;
+ case 5:
+ mask = SND_JACK_BTN_5;
+ break;
+ case 6:
+ mask = SND_JACK_BTN_6;
+ break;
+ case 7:
+ mask = SND_JACK_BTN_7;
+ break;
+ }
+ return mask;
+}
+
static irqreturn_t tabla_dce_handler(int irq, void *data)
{
+ int i, mask;
+ short bias_value_dce;
+ s32 bias_mv_dce;
+ int btn = -1, meas = 0;
struct tabla_priv *priv = data;
+ const struct tabla_mbhc_btn_detect_cfg *d =
+ TABLA_MBHC_CAL_BTN_DET_PTR(priv->calibration);
+ short btnmeas[d->n_btn_meas + 1];
struct snd_soc_codec *codec = priv->codec;
- short bias_value;
tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL);
tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL);
tabla_lock_sleep(priv);
- bias_value = tabla_codec_read_dce_result(codec);
- pr_debug("%s: button press interrupt, DCE: %d,%d\n",
- __func__, bias_value,
- tabla_codec_sta_dce_v(codec, 1, bias_value));
+ bias_value_dce = tabla_codec_read_dce_result(codec);
+ bias_mv_dce = tabla_codec_sta_dce_v(codec, 1, bias_value_dce);
- bias_value = tabla_codec_read_sta_result(codec);
- pr_debug("%s: button press interrupt, STA: %d,%d\n",
- __func__, bias_value,
- tabla_codec_sta_dce_v(codec, 0, bias_value));
- /*
- * TODO: If button pressed is not button 0,
- * report the button press event immediately.
- */
- priv->buttons_pressed |= SND_JACK_BTN_0;
-
- msleep(100);
-
- if (schedule_delayed_work(&priv->btn0_dwork,
- msecs_to_jiffies(400)) == 0) {
- WARN(1, "Button pressed twice without release event\n");
- tabla_unlock_sleep(priv);
+ /* determine pressed button */
+ btnmeas[meas++] = tabla_determine_button(priv, bias_mv_dce);
+ pr_debug("%s: meas %d - DCE %d,%d, button %d\n", __func__,
+ meas - 1, bias_value_dce, bias_mv_dce, btnmeas[meas - 1]);
+ if (d->n_btn_meas == 0)
+ btn = btnmeas[0];
+ for (; ((d->n_btn_meas) && (meas < (d->n_btn_meas + 1))); meas++) {
+ bias_value_dce = tabla_codec_sta_dce(codec, 1);
+ bias_mv_dce = tabla_codec_sta_dce_v(codec, 1, bias_value_dce);
+ btnmeas[meas] = tabla_determine_button(priv, bias_mv_dce);
+ pr_debug("%s: meas %d - DCE %d,%d, button %d\n",
+ __func__, meas, bias_value_dce, bias_mv_dce,
+ btnmeas[meas]);
+ /* if large enough measurements are collected,
+ * start to check if last all n_btn_con measurements were
+ * in same button low/high range */
+ if (meas + 1 >= d->n_btn_con) {
+ for (i = 0; i < d->n_btn_con; i++)
+ if ((btnmeas[meas] < 0) ||
+ (btnmeas[meas] != btnmeas[meas - i]))
+ break;
+ if (i == d->n_btn_con) {
+ /* button pressed */
+ btn = btnmeas[meas];
+ break;
+ }
+ }
+ /* if left measurements are less than n_btn_con,
+ * it's impossible to find button number */
+ if ((d->n_btn_meas - meas) < d->n_btn_con)
+ break;
}
+ if (btn >= 0) {
+ mask = tabla_get_button_mask(btn);
+ priv->buttons_pressed |= mask;
+
+ msleep(100);
+
+ /* XXX: assuming button 0 has the lowest micbias voltage */
+ if (btn == 0) {
+ if (schedule_delayed_work(&priv->btn0_dwork,
+ msecs_to_jiffies(400)) == 0) {
+ WARN(1, "Button pressed twice without release"
+ "event\n");
+ tabla_unlock_sleep(priv);
+ }
+ } else {
+ pr_debug("%s: Reporting short button %d(0x%x) press\n",
+ __func__, btn, mask);
+ tabla_snd_soc_jack_report(priv, priv->button_jack, mask,
+ mask);
+ }
+ } else
+ pr_debug("%s: bogus button press, too short press?\n",
+ __func__);
+
return IRQ_HANDLED;
}
@@ -3804,16 +3902,15 @@
struct snd_soc_codec *codec = priv->codec;
int ret, mb_v;
- pr_debug("%s\n", __func__);
+ pr_debug("%s: enter\n", __func__);
tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_RELEASE);
tabla_lock_sleep(priv);
if (priv->buttons_pressed & SND_JACK_BTN_0) {
ret = cancel_delayed_work(&priv->btn0_dwork);
-
if (ret == 0) {
- pr_debug("%s: Reporting long button release event\n",
- __func__);
+ pr_debug("%s: Reporting long button 0 release event\n",
+ __func__);
if (priv->button_jack)
tabla_snd_soc_jack_report(priv,
priv->button_jack, 0,
@@ -3829,23 +3926,34 @@
if (mb_v < -2000 || mb_v > -670)
pr_debug("%s: Fake buttton press interrupt\n",
- __func__);
+ __func__);
else if (priv->button_jack) {
- pr_debug("%s:reporting short button "
+ pr_debug("%s: Reporting short button 0 "
"press and release\n", __func__);
tabla_snd_soc_jack_report(priv,
priv->button_jack,
SND_JACK_BTN_0,
SND_JACK_BTN_0);
tabla_snd_soc_jack_report(priv,
- priv->button_jack,
- 0, SND_JACK_BTN_0);
+ priv->button_jack, 0,
+ SND_JACK_BTN_0);
}
}
priv->buttons_pressed &= ~SND_JACK_BTN_0;
}
+ if (priv->buttons_pressed) {
+ pr_debug("%s:reporting button release mask 0x%x\n", __func__,
+ priv->buttons_pressed);
+ tabla_snd_soc_jack_report(priv, priv->button_jack, 0,
+ priv->buttons_pressed);
+ /* hardware doesn't detect another button press until
+ * already pressed button is released.
+ * therefore buttons_pressed has only one button's mask. */
+ priv->buttons_pressed &= ~TABLA_JACK_BUTTON_MASK;
+ }
+
tabla_codec_start_hs_polling(codec);
tabla_unlock_sleep(priv);
return IRQ_HANDLED;
@@ -4155,12 +4263,15 @@
static irqreturn_t tabla_hs_remove_irq(int irq, void *data)
{
+ short bias_value;
struct tabla_priv *priv = data;
struct snd_soc_codec *codec = priv->codec;
const struct tabla_mbhc_general_cfg *generic =
TABLA_MBHC_CAL_GENERAL_PTR(priv->calibration);
- short bias_value;
+ int fake_removal = 0;
+ int min_us = TABLA_FAKE_REMOVAL_MIN_PERIOD_MS * 1000;
+ pr_debug("%s: enter, removal interrupt\n", __func__);
tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL);
tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL);
tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_RELEASE);
@@ -4169,11 +4280,18 @@
usleep_range(generic->t_shutdown_plug_rem,
generic->t_shutdown_plug_rem);
- bias_value = tabla_codec_sta_dce(codec, 1);
- pr_debug("removal interrupt, DCE: %d,%d\n",
- bias_value, tabla_codec_sta_dce_v(codec, 1, bias_value));
+ do {
+ bias_value = tabla_codec_sta_dce(codec, 1);
+ pr_debug("%s: DCE %d,%d, %d us left\n", __func__, bias_value,
+ tabla_codec_sta_dce_v(codec, 1, bias_value), min_us);
+ if (bias_value < (short)priv->mbhc_data.v_ins_h) {
+ fake_removal = 1;
+ break;
+ }
+ min_us -= priv->mbhc_data.t_dce;
+ } while (min_us > 0);
- if (bias_value < (short) priv->mbhc_data.v_ins_h) {
+ if (fake_removal) {
pr_debug("False alarm, headset not actually removed\n");
tabla_codec_start_hs_polling(codec);
} else {
diff --git a/sound/soc/codecs/wcd9310.h b/sound/soc/codecs/wcd9310.h
index 7f70010..6f77e9f 100644
--- a/sound/soc/codecs/wcd9310.h
+++ b/sound/soc/codecs/wcd9310.h
@@ -10,6 +10,7 @@
* GNU General Public License for more details.
*/
#include <sound/soc.h>
+#include <sound/jack.h>
#define TABLA_VERSION_1_0 0
#define TABLA_VERSION_1_1 1
@@ -28,6 +29,11 @@
#define STA 0
#define DCE 1
+#define TABLA_JACK_BUTTON_MASK (SND_JACK_BTN_0 | SND_JACK_BTN_1 | \
+ SND_JACK_BTN_2 | SND_JACK_BTN_3 | \
+ SND_JACK_BTN_4 | SND_JACK_BTN_5 | \
+ SND_JACK_BTN_6 | SND_JACK_BTN_7)
+
extern const u8 tabla_reg_readable[TABLA_CACHE_SIZE];
extern const u8 tabla_reg_defaults[TABLA_CACHE_SIZE];
diff --git a/sound/soc/msm/msm8960.c b/sound/soc/msm/msm8960.c
index cb73ab6..75bb75f 100644
--- a/sound/soc/msm/msm8960.c
+++ b/sound/soc/msm/msm8960.c
@@ -55,7 +55,7 @@
#define TABLA_EXT_CLK_RATE 12288000
-#define TABLA_MBHC_DEF_BUTTONS 3
+#define TABLA_MBHC_DEF_BUTTONS 8
#define TABLA_MBHC_DEF_RLOADS 5
static u32 top_spk_pamp_gpio = PM8921_GPIO_PM_TO_SYS(18);
@@ -597,20 +597,16 @@
#undef S
#define S(X, Y) ((TABLA_MBHC_CAL_PLUG_TYPE_PTR(tabla_cal)->X) = (Y))
S(v_no_mic, 30);
- S(v_hs_max, 1450);
+ S(v_hs_max, 1550);
#undef S
#define S(X, Y) ((TABLA_MBHC_CAL_BTN_DET_PTR(tabla_cal)->X) = (Y))
- S(c[0], 6);
- S(c[1], 10);
- S(c[2], 10);
- S(c[3], 14);
- S(c[4], 14);
- S(c[5], 16);
- S(c[6], 0);
- S(c[7], 0);
- S(nc, 5);
- S(n_meas, 11);
+ S(c[0], 62);
+ S(c[1], 124);
+ S(nc, 1);
+ S(n_meas, 3);
S(mbhc_nsc, 11);
+ S(n_btn_meas, 1);
+ S(n_btn_con, 2);
S(num_btn, TABLA_MBHC_DEF_BUTTONS);
S(v_btn_press_delta_sta, 100);
S(v_btn_press_delta_cic, 50);
@@ -618,18 +614,28 @@
btn_cfg = TABLA_MBHC_CAL_BTN_DET_PTR(tabla_cal);
btn_low = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_V_BTN_LOW);
btn_high = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_V_BTN_HIGH);
- btn_low[0] = 0;
- btn_high[0] = 40;
- btn_low[1] = 60;
- btn_high[1] = 140;
- btn_low[2] = 160;
- btn_high[2] = 240;
+ btn_low[0] = -50;
+ btn_high[0] = 10;
+ btn_low[1] = 11;
+ btn_high[1] = 38;
+ btn_low[2] = 39;
+ btn_high[2] = 64;
+ btn_low[3] = 65;
+ btn_high[3] = 91;
+ btn_low[4] = 92;
+ btn_high[4] = 115;
+ btn_low[5] = 116;
+ btn_high[5] = 141;
+ btn_low[6] = 142;
+ btn_high[6] = 163;
+ btn_low[7] = 164;
+ btn_high[7] = 250;
n_ready = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_N_READY);
n_ready[0] = 48;
n_ready[1] = 38;
n_cic = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_N_CIC);
- n_cic[0] = 120;
- n_cic[1] = 94;
+ n_cic[0] = 60;
+ n_cic[1] = 47;
gain = tabla_mbhc_cal_btn_det_mp(btn_cfg, TABLA_BTN_DET_GAIN);
gain[0] = 11;
gain[1] = 9;
@@ -671,15 +677,16 @@
snd_soc_dapm_sync(dapm);
err = snd_soc_jack_new(codec, "Headset Jack",
- (SND_JACK_HEADSET | SND_JACK_OC_HPHL | SND_JACK_OC_HPHR),
- &hs_jack);
+ (SND_JACK_HEADSET | SND_JACK_OC_HPHL |
+ SND_JACK_OC_HPHR),
+ &hs_jack);
if (err) {
pr_err("failed to create new jack\n");
return err;
}
err = snd_soc_jack_new(codec, "Button Jack",
- SND_JACK_BTN_0, &button_jack);
+ TABLA_JACK_BUTTON_MASK, &button_jack);
if (err) {
pr_err("failed to create new jack\n");
return err;