Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 038e6615 authored by Walter Yang's avatar Walter Yang Committed by Banajit Goswami
Browse files

ASoC: mbhc: Update headset detection sequence



Update headset detection sequence to handle button press/release
during headset insertion.
Update impedance detection sequence to get the correct headset
impedance value after headset insertion.

Change-Id: I48e01e39e0993407c8a30542b651999850d70df9
CRs-Fixed: 2010478
Signed-off-by: default avatarWalter Yang <yandongy@codeaurora.org>
parent fc7d3f47
Loading
Loading
Loading
Loading
+101 −26
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ static int wcd_measure_adc_continuous(struct wcd_mbhc *mbhc)
	u8 adc_result = 0;
	int output_mv = 0;
	int retry = 3;
	u8 adc_en = 0;

	pr_debug("%s: enter\n", __func__);

@@ -73,6 +74,8 @@ static int wcd_measure_adc_continuous(struct wcd_mbhc *mbhc)
	WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, 0x00);
	/* Set ADC to continuous measurement */
	WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_MODE, 1);
	/* Read ADC Enable bit to restore after adc measurement */
	WCD_MBHC_REG_READ(WCD_MBHC_ADC_EN, adc_en);
	/* Disable ADC_ENABLE bit */
	WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_EN, 0);
	/* Disable MBHC FSM */
@@ -92,6 +95,8 @@ static int wcd_measure_adc_continuous(struct wcd_mbhc *mbhc)
		WCD_MBHC_REG_READ(WCD_MBHC_ADC_RESULT, adc_result);
	}

	/* Restore ADC Enable */
	WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_EN, adc_en);
	/* Get voltage from ADC result */
	output_mv = wcd_get_voltage_from_adc(adc_result,
					     wcd_mbhc_get_micbias(mbhc));
@@ -106,13 +111,16 @@ static int wcd_measure_adc_once(struct wcd_mbhc *mbhc, int mux_ctl)
	u8 adc_timeout = 0;
	u8 adc_complete = 0;
	u8 adc_result = 0;
	int retry = 5;
	int retry = 6;
	int ret = 0;
	int output_mv = 0;
	u8 adc_en = 0;

	pr_debug("%s: enter\n", __func__);

	WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_MODE, 0);
	/* Read ADC Enable bit to restore after adc measurement */
	WCD_MBHC_REG_READ(WCD_MBHC_ADC_EN, adc_en);
	/* Trigger ADC one time measurement */
	WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_EN, 0);
	WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
@@ -122,8 +130,8 @@ static int wcd_measure_adc_once(struct wcd_mbhc *mbhc, int mux_ctl)
	WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_EN, 1);

	while (retry--) {
		/* wait for 3msec to get adc results */
		usleep_range(3000, 3100);
		/* wait for 600usec to get adc results */
		usleep_range(600, 610);

		/* check for ADC Timeout */
		WCD_MBHC_REG_READ(WCD_MBHC_ADC_TIMEOUT, adc_timeout);
@@ -145,6 +153,9 @@ static int wcd_measure_adc_once(struct wcd_mbhc *mbhc, int mux_ctl)
		break;
	}

	/* Restore ADC Enable */
	WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_EN, adc_en);

	if (retry <= 0) {
		pr_err("%s: adc complete: %d, adc timeout: %d\n",
			__func__, adc_complete, adc_timeout);
@@ -352,8 +363,13 @@ static bool wcd_mbhc_adc_check_for_spl_headset(struct wcd_mbhc *mbhc,
	/* Bump up MB2 to 2.7V */
	mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic(mbhc->codec,
				mbhc->mbhc_cfg->mbhc_micbias, true);
	usleep_range(10000, 10100);

	output_mv = wcd_measure_adc_continuous(mbhc);
	/*
	 * Use ADC single mode to minimize the chance of missing out
	 * btn press/relesae for HEADSET type during correct work.
	 */
	output_mv = wcd_measure_adc_once(mbhc, MUX_CTL_IN2P);
	adc_threshold = ((WCD_MBHC_ADC_HS_THRESHOLD_MV *
			  wcd_mbhc_get_micbias(mbhc))/WCD_MBHC_ADC_MICBIAS_MV);

@@ -521,7 +537,11 @@ static int wcd_mbhc_get_plug_type(struct wcd_mbhc *mbhc)
{
	int result_mv = 0;

	result_mv = wcd_measure_adc_continuous(mbhc);
	/*
	 * Use ADC single mode to minimize the chance of missing out
	 * btn press/release for HEADSET type during correct work.
	 */
	result_mv = wcd_measure_adc_once(mbhc, MUX_CTL_IN2P);

	return wcd_mbhc_get_plug_from_adc(result_mv);
}
@@ -564,7 +584,8 @@ static void wcd_correct_swch_plug(struct work_struct *work)
		goto correct_plug_type;
	}
	/* Find plug type */
	plug_type = wcd_mbhc_get_plug_type(mbhc);
	output_mv = wcd_measure_adc_continuous(mbhc);
	plug_type = wcd_mbhc_get_plug_from_adc(output_mv);

	/*
	 * Report plug type if it is either headset or headphone
@@ -578,6 +599,17 @@ static void wcd_correct_swch_plug(struct work_struct *work)
		WCD_MBHC_RSC_UNLOCK(mbhc);
	}

	/*
	 * Set DETECTION_DONE bit for HEADSET and ANC_HEADPHONE,
	 * so that btn press/release interrupt can be generated.
	 */
	if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADSET ||
		mbhc->current_plug == MBHC_PLUG_TYPE_ANC_HEADPHONE) {
		WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_MODE, 0);
		WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_EN, 0);
		WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_DETECTION_DONE, 1);
	}

correct_plug_type:
	timeout = jiffies + msecs_to_jiffies(HS_DETECT_PLUG_TIME_MS);
	while (!time_after(jiffies, timeout)) {
@@ -587,10 +619,6 @@ static void wcd_correct_swch_plug(struct work_struct *work)
			wcd_micbias_disable(mbhc);
			goto exit;
		}
		if (mbhc->btn_press_intr) {
			wcd_cancel_btn_work(mbhc);
			mbhc->btn_press_intr = false;
		}

		/* allow sometime and re-check stop requested again */
		msleep(20);
@@ -602,7 +630,12 @@ static void wcd_correct_swch_plug(struct work_struct *work)
		}

		msleep(180);
		output_mv = wcd_measure_adc_continuous(mbhc);
		/*
		 * Use ADC single mode to minimize the chance of missing out
		 * btn press/release for HEADSET type during correct work.
		 */
		output_mv = wcd_measure_adc_once(mbhc, MUX_CTL_IN2P);

		/*
		 * instead of hogging system by contineous polling, wait for
		 * sometime and re-check stop request again.
@@ -674,11 +707,10 @@ static void wcd_correct_swch_plug(struct work_struct *work)
				 * and if there is not button press without
				 * release
				 */
				if (((mbhc->current_plug !=
				if ((mbhc->current_plug !=
				      MBHC_PLUG_TYPE_HEADSET) &&
				     (mbhc->current_plug !=
				     MBHC_PLUG_TYPE_ANC_HEADPHONE)) &&
				    !mbhc->btn_press_intr) {
				     MBHC_PLUG_TYPE_ANC_HEADPHONE)) {
					if (plug_type == MBHC_PLUG_TYPE_HEADSET)
						pr_debug("%s: cable is %s headset\n",
							__func__,
@@ -691,12 +723,6 @@ static void wcd_correct_swch_plug(struct work_struct *work)
		}
	}
	if (!wrk_complete) {
		if (mbhc->btn_press_intr) {
			pr_debug("%s: Can be slow insertion of headphone\n",
				 __func__);
			wcd_cancel_btn_work(mbhc);
			plug_type = MBHC_PLUG_TYPE_HEADPHONE;
		}
		/*
		 * If plug_tye is headset, we might have already reported either
		 * in detect_plug-type or in above while loop, no need to report
@@ -726,11 +752,7 @@ static void wcd_correct_swch_plug(struct work_struct *work)
		pr_debug("%s: Switch level is low\n", __func__);
		goto exit;
	}
	if (plug_type == MBHC_PLUG_TYPE_GND_MIC_SWAP && mbhc->btn_press_intr) {
		pr_debug("%s: insertion of headphone with swap\n", __func__);
		wcd_cancel_btn_work(mbhc);
		plug_type = MBHC_PLUG_TYPE_HEADPHONE;
	}

	pr_debug("%s: Valid plug found, plug type %d wrk_cmpt %d btn_intr %d\n",
			__func__, plug_type, wrk_complete,
			mbhc->btn_press_intr);
@@ -742,9 +764,16 @@ static void wcd_correct_swch_plug(struct work_struct *work)
	wcd_mbhc_find_plug_and_report(mbhc, plug_type);
	WCD_MBHC_RSC_UNLOCK(mbhc);
enable_supply:
	/*
	 * Set DETECTION_DONE bit for HEADSET and ANC_HEADPHONE,
	 * so that btn press/release interrupt can be generated.
	 * For other plug type, clear the bit.
	 */
	if (plug_type == MBHC_PLUG_TYPE_HEADSET ||
	    plug_type == MBHC_PLUG_TYPE_ANC_HEADPHONE)
		WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_DETECTION_DONE, 1);
	else
		WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_DETECTION_DONE, 0);

	if (mbhc->mbhc_cb->mbhc_micbias_control)
		wcd_mbhc_adc_update_fsm_source(mbhc, plug_type);
@@ -762,6 +791,18 @@ static void wcd_correct_swch_plug(struct work_struct *work)
		WCD_MBHC_RSC_UNLOCK(mbhc);
	}

	/*
	 * Enable ADC COMPLETE interrupt for HEADPHONE.
	 * Btn release may happen after the correct work, ADC COMPLETE
	 * interrupt needs to be captured to correct plug type.
	 */
	if (plug_type == MBHC_PLUG_TYPE_HEADPHONE) {
		WCD_MBHC_RSC_LOCK(mbhc);
		wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_INS,
				     true);
		WCD_MBHC_RSC_UNLOCK(mbhc);
	}

	if (mbhc->mbhc_cb->hph_pull_down_ctrl)
		mbhc->mbhc_cb->hph_pull_down_ctrl(codec, true);

@@ -775,6 +816,13 @@ static irqreturn_t wcd_mbhc_adc_hs_rem_irq(int irq, void *data)

	pr_debug("%s: enter\n", __func__);
	WCD_MBHC_RSC_LOCK(mbhc);
	/*
	 * ADC COMPLETE and ELEC_REM interrupts are both enabled for HEADPHONE,
	 * need to reject the ADC COMPLETE interrupt which follows ELEC_REM one
	 * when HEADPHONE is removed.
	 */
	if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADPHONE)
		mbhc->extn_cable_hph_rem = true;
	WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_DETECTION_DONE, 0);
	WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_MODE, 0);
	WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_EN, 0);
@@ -789,12 +837,39 @@ static irqreturn_t wcd_mbhc_adc_hs_ins_irq(int irq, void *data)
	struct wcd_mbhc *mbhc = data;

	pr_debug("%s: enter\n", __func__);

	/*
	 * ADC COMPLETE and ELEC_REM interrupts are both enabled for HEADPHONE,
	 * need to reject the ADC COMPLETE interrupt which follows ELEC_REM one
	 * when HEADPHONE is removed.
	 */
	if (mbhc->extn_cable_hph_rem == true) {
		mbhc->extn_cable_hph_rem = false;
		pr_debug("%s: leave\n", __func__);
		return IRQ_HANDLED;
	}

	WCD_MBHC_RSC_LOCK(mbhc);
	/*
	 * If current plug is headphone then there is no chance to
	 * get ADC complete interrupt, so connected cable should be
	 * headset not headphone.
	 */
	if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADPHONE) {
		wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_INS, false);
		WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_DETECTION_DONE, 1);
		wcd_mbhc_find_plug_and_report(mbhc, MBHC_PLUG_TYPE_HEADSET);
		WCD_MBHC_RSC_UNLOCK(mbhc);
		return IRQ_HANDLED;
	}

	if (!mbhc->mbhc_cfg->detect_extn_cable) {
		pr_debug("%s: Returning as Extension cable feature not enabled\n",
			__func__);
		WCD_MBHC_RSC_UNLOCK(mbhc);
		return IRQ_HANDLED;
	}
	WCD_MBHC_RSC_LOCK(mbhc);

	pr_debug("%s: Disable electrical headset insertion interrupt\n",
		 __func__);
	wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_INS, false);
+16 −1
Original line number Diff line number Diff line
@@ -520,6 +520,7 @@ static void wcd_mbhc_report_plug(struct wcd_mbhc *mbhc, int insertion,
{
	struct snd_soc_codec *codec = mbhc->codec;
	bool is_pa_on = false;
	u8 fsm_en = 0;

	WCD_MBHC_RSC_ASSERT_LOCKED(mbhc);

@@ -646,8 +647,16 @@ static void wcd_mbhc_report_plug(struct wcd_mbhc *mbhc, int insertion,
			mbhc->mbhc_cb->compute_impedance &&
			(mbhc->mbhc_cfg->linein_th != 0) &&
			(!is_pa_on)) {
			/* Set MUX_CTL to AUTO for Z-det */
			WCD_MBHC_REG_READ(WCD_MBHC_FSM_EN, fsm_en);
			WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
			WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_MUX_CTL,
						 MUX_CTL_AUTO);
			WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 1);
			mbhc->mbhc_cb->compute_impedance(mbhc,
					&mbhc->zl, &mbhc->zr);
			WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN,
						 fsm_en);
			if ((mbhc->zl > mbhc->mbhc_cfg->linein_th &&
				mbhc->zl < MAX_IMPED) &&
				(mbhc->zr > mbhc->mbhc_cfg->linein_th &&
@@ -906,6 +915,7 @@ static void wcd_mbhc_swch_irq_handler(struct wcd_mbhc *mbhc)
		wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_INS, false);
		WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_DETECTION_TYPE, 1);
		WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, 0);
		mbhc->extn_cable_hph_rem = false;
		wcd_mbhc_report_plug(mbhc, 0, jack_type);

	} else if (!detection_type) {
@@ -915,6 +925,7 @@ static void wcd_mbhc_swch_irq_handler(struct wcd_mbhc *mbhc)
		/* Disable HW FSM */
		WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
		WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_BTN_ISRC_CTL, 0);
		mbhc->extn_cable_hph_rem = false;
	}

	mbhc->in_swch_irq_handler = false;
@@ -1094,8 +1105,11 @@ static irqreturn_t wcd_mbhc_release_handler(int irq, void *data)
	 * If current plug is headphone then there is no chance to
	 * get btn release interrupt, so connected cable should be
	 * headset not headphone.
	 * For ADC MBHC, ADC_COMPLETE interrupt will be generated
	 * in this case. So skip the check here.
	 */
	if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADPHONE) {
	if (!WCD_MBHC_DETECTION &&
		mbhc->current_plug == MBHC_PLUG_TYPE_HEADPHONE) {
		wcd_mbhc_find_plug_and_report(mbhc, MBHC_PLUG_TYPE_HEADSET);
		goto exit;

@@ -1856,6 +1870,7 @@ int wcd_mbhc_init(struct wcd_mbhc *mbhc, struct snd_soc_codec *codec,
	mbhc->btn_press_intr = false;
	mbhc->is_hs_recording = false;
	mbhc->is_extn_cable = false;
	mbhc->extn_cable_hph_rem = false;
	mbhc->hph_type = WCD_MBHC_HPH_NONE;
	mbhc->wcd_mbhc_regs = wcd_mbhc_regs;

+1 −0
Original line number Diff line number Diff line
@@ -528,6 +528,7 @@ struct wcd_mbhc {
	bool is_extn_cable;
	bool skip_imped_detection;
	bool is_btn_already_regd;
	bool extn_cable_hph_rem;

	struct snd_soc_codec *codec;
	/* Work to perform MBHC Firmware Read */