耳機調試學習記錄

耳機阻抗

具有電阻、電感和電容的電路里,對交流電所起的阻礙作用叫做阻抗。阻抗常用Z表示。阻抗由電阻、感抗和容抗三者組成,但不是三者簡單相加,阻抗的單位是歐。

耳機的阻抗是交流阻抗的簡稱,阻抗越小,耳機越容易出聲、越容易驅動。電視等有耳機插孔輸出的機器上,一般使用中高阻抗的耳機比較適宜。低阻抗的耳機一般比較容易推動,因此MP3等便攜、省電的機器應選擇低阻抗耳機。當然,阻抗越高的耳機搭配輸出功率大的音源時聲音效果更好。

耳機的電路設計是保證插入耳機時,讓功放處於MUTE狀態,需要專門的電路來處理;如果是不帶MUTE的功放,接入耳機時要斷開喇叭,由插座完成。和耳機類似的是PC音頻輸入,該電路設計必須保證攻防輸入接AUDIO IN時,傳輸AUDIO信號;不接入信號時跟地聯通。

AUDIO DTSI

高通平臺中,默認使用內部codec的時候,耳機的輸出及控制都是在內部codec中進行的,所以耳機的整個初始化起源過程,是在codec的初始化中, 高通平臺的machine驅動文件一般都是平臺名字開頭的,例如8953的是msm8953.c,
通過adb shell cat proc/asound/cards找到聲卡的名字,根據名字可以找到該平臺的machine驅動文件,同時可以根據machine驅動的compatible的名字,找到dts文件中,sound相關的信息。如 在android/kernel/msm-3.18/arch/arm64/boot/dts/**/c800/msm8937-audio.dtsi文件中,可以找到相關信息:

&int_codec {
 	status = "okay";
	qcom,model = "msm8952-snd-card-mtp";

qcom,model 即註冊的聲卡名字。

可以找到primary mi2s playback,在高通平臺中,這primary_mi2s這一路i2s,都是留給內部codec用的,所以,這路的codec_name和codec_dai_name,就是對應着內部codec的信息:

	/* Backend I2S DAI Links */
2717 	{
2718 		.name = LPASS_BE_PRI_MI2S_RX,
2719 		.stream_name = "Primary MI2S Playback",
2720 		.cpu_dai_name = "msm-dai-q6-mi2s.0",
2721 		.platform_name = "msm-pcm-routing",
2722 		.codec_name     = "cajon_codec",
2723 		.codec_dai_name = "msm8x16_wcd_i2s_rx1",
2724 		.no_pcm = 1,
2725 		.dpcm_playback = 1,
2726 		.async_ops = ASYNC_DPCM_SND_SOC_PREPARE |
2727 			ASYNC_DPCM_SND_SOC_HW_PARAMS,
2728 		.be_id = MSM_BACKEND_DAI_PRI_MI2S_RX,
2729 		.init = &msm_audrx_init,
2730 		.be_hw_params_fixup = msm_mi2s_rx_be_hw_params_fixup,
2731 		.ops = &msm8952_mi2s_be_ops,
2732 		.ignore_suspend = 1,
2733 	},

由此我們可以找到高通平臺默認的codec驅動文件msm8x16-wcd.c,在該文件中,註冊了codec_dai_driver : msm8x16_wcd_i2s_rx1。

在初始化的時候,如何憑藉dai_link中的codec信息找到對應的codec,答案是codec_name。但注意,這裏並不是通過這個名字直接尋找的,例如

android/kernel/msm-3.18/arch/arm64/boot/dts/**/c800/msm8937-audio.dtsi有如下信息:

	asoc-codec = <&stub_codec>, <&pm8937_cajon_dig>, <&hdmi_dba>;
76 	asoc-codec-names = "msm-stub-codec.1", "cajon_codec",
77 						"msm-hdmi-dba-codec-rx";

在初始化的時候,dai_link中的codec_name會跟這裏的asoc-codec-names進行匹配,進而獲取上面asoc-codec中的codec_node :

&pm8937_1 {
133 	pm8937_cajon_dig: 8952_wcd_codec@f000 {
134 		compatible = "qcom,msm8x16_wcd_codec";
135 		reg = <0xf000 0x100>;
136 		interrupt-parent = <&spmi_bus>;
137      	............
172 		qcom,cdc-static-supplies =  "cdc-vdd-io",
173 					   "cdc-vdd-pa",
174 					   "cdc-vdda-cp";
175 
176 		qcom,cdc-on-demand-supplies = "cdc-vdd-mic-bias";
177 		qcom,dig-cdc-base-addr = <0xc0f0000>;
178 	};

而這個node節點正式codec驅動的設備樹節點。在soc_bind_dai_link()函數中,會做出如下處理:


     /*註冊codec的時候,會將所有註冊的codec鏈接到codec_list中*/
      list_for_each_entry(codec, &codec_list, list) {
          if (dai_link->codec_of_node) {
              /*根據設備數節點句柄進行匹配*/
              if (codec->dev->of_node != dai_link->codec_of_node)
                  continue;
        } else {
              /*如果句柄爲空,根據,codec_name進行匹配,在這裏不會走這裏,其實codec_name是 wcd-spmi-core.1*/
              if (strcmp(codec->name, dai_link->codec_name))
                 continue;
         }
 
        rtd->codec = codec;
         /*找到codec之後,根據codec_dai的名字找到對應的codec_dai*/
           list_for_each_entry(codec_dai, &dai_list, list) {
             if (codec->dev == codec_dai->dev &&
                 !strcmp(codec_dai->name,
                    dai_link->codec_dai_name)) {
 
                 rtd->codec_dai = codec_dai;
             }
         }
     }

所以,我們可以根據dai_link中的codec_dai的名字或者codec名字來找到對應的codec驅動。

耳機初始化

耳機部分的初始化是在codec_driver的probe函數中完成的:

調用wcd_mbhc_init(&msm8x16_wcd_priv->mbhc, codec, &mbhc_cb, &intr_ids, true); 進行初始化

調用msm8x16_wcd_set_micb_v(codec); 設置micbias電壓

調用msm8x16_wcd_configure_cap(codec, false, false); 根據外部有沒有接電容來初始化電容模式

初始化函數wcd_mbhc_init(),主要註冊了耳機插拔和耳機按鍵的input設備snd_soc_jack_new,snd_jack_set_key註冊了耳機四個按鍵的鍵值,註冊了一系列的中斷,包括: 申請初測耳機插拔中斷;申請註冊耳機按鍵按下的中斷;申請註冊耳機按鍵鬆開的中斷;註冊檢測高阻抗的耳機延長線設備的插入中斷;註冊檢測高阻抗的耳機延長線設備的拔出中斷等

int wcd_mbhc_init(struct wcd_mbhc *mbhc, struct snd_soc_codec *codec,
 		      const struct wcd_mbhc_cb *mbhc_cb,
		      const struct wcd_mbhc_intr *mbhc_cdc_intr_ids,
 		      struct wcd_mbhc_register *mbhc_reg,
 		      bool impedance_det_en){
......
	/* Register event notifier */
	mbhc->nblock.notifier_call = wcd_event_notify;
	if (mbhc->mbhc_cb->register_notifier) {
		ret = mbhc->mbhc_cb->register_notifier(codec, &mbhc->nblock,
						       true);
		if (ret) {
			pr_err("%s: Failed to register notifier %d\n",
				__func__, ret);
			return ret;
		}
	}

	init_waitqueue_head(&mbhc->wait_btn_press);
	mutex_init(&mbhc->codec_resource_lock);
   /*申請初測耳機插拔中斷*/
	ret = mbhc->mbhc_cb->request_irq(codec, mbhc->intr_ids->mbhc_sw_intr,
				  wcd_mbhc_mech_plug_detect_irq,
				  "mbhc sw intr", mbhc);
	if (ret) {
		pr_err("%s: Failed to request irq %d, ret = %d\n", __func__,
		       mbhc->intr_ids->mbhc_sw_intr, ret);
		goto err_mbhc_sw_irq;
	}
   /*申請註冊耳機按鍵按下的中斷*/
	ret = mbhc->mbhc_cb->request_irq(codec,
					 mbhc->intr_ids->mbhc_btn_press_intr,
					 wcd_mbhc_btn_press_handler,
					 "Button Press detect",
					 mbhc);
	if (ret) {
		pr_err("%s: Failed to request irq %d\n", __func__,
		       mbhc->intr_ids->mbhc_btn_press_intr);
		goto err_btn_press_irq;
	}
   /*申請註冊耳機按鍵鬆開的中斷*/
	ret = mbhc->mbhc_cb->request_irq(codec,
					 mbhc->intr_ids->mbhc_btn_release_intr,
					 wcd_mbhc_release_handler,
					 "Button Release detect", mbhc);
	if (ret) {
		pr_err("%s: Failed to request irq %d\n", __func__,
			mbhc->intr_ids->mbhc_btn_release_intr);
		goto err_btn_release_irq;
	}
   /*註冊檢測高阻抗的耳機延長線設備的插入中斷*/
	ret = mbhc->mbhc_cb->request_irq(codec,
					 mbhc->intr_ids->mbhc_hs_ins_intr,
					 wcd_mbhc_hs_ins_irq,
					 "Elect Insert", mbhc);
	if (ret) {
		pr_err("%s: Failed to request irq %d\n", __func__,
		       mbhc->intr_ids->mbhc_hs_ins_intr);
		goto err_mbhc_hs_ins_irq;
	}

	mbhc->mbhc_cb->irq_control(codec, mbhc->intr_ids->mbhc_hs_ins_intr,
				   false);
	clear_bit(WCD_MBHC_ELEC_HS_INS, &mbhc->intr_status);
	/*這個應該是註冊檢測高阻抗的耳機延長線設備的拔出中斷*/
	ret = mbhc->mbhc_cb->request_irq(codec,
					 mbhc->intr_ids->mbhc_hs_rem_intr,
					 wcd_mbhc_hs_rem_irq,
					 "Elect Remove", mbhc);
	if (ret) {
		pr_err("%s: Failed to request irq %d\n", __func__,
		       mbhc->intr_ids->mbhc_hs_rem_intr);
		goto err_mbhc_hs_rem_irq;
	}
	mbhc->mbhc_cb->irq_control(codec, mbhc->intr_ids->mbhc_hs_rem_intr,
				   false);
	clear_bit(WCD_MBHC_ELEC_HS_REM, &mbhc->intr_status);

	ret = mbhc->mbhc_cb->request_irq(codec, mbhc->intr_ids->hph_left_ocp,
				  wcd_mbhc_hphl_ocp_irq, "HPH_L OCP detect",
				  mbhc);
	if (ret) {
		pr_err("%s: Failed to request irq %d\n", __func__,
		       mbhc->intr_ids->hph_left_ocp);
		goto err_hphl_ocp_irq;
	}

	ret = mbhc->mbhc_cb->request_irq(codec, mbhc->intr_ids->hph_right_ocp,
				  wcd_mbhc_hphr_ocp_irq, "HPH_R OCP detect",
				  mbhc);
	if (ret) {
		pr_err("%s: Failed to request irq %d\n", __func__,
		       mbhc->intr_ids->hph_right_ocp);
		goto err_hphr_ocp_irq;
	}

	pr_debug("%s: leave ret %d\n", __func__, ret);
	return ret;

err_hphr_ocp_irq:
	mbhc->mbhc_cb->free_irq(codec, mbhc->intr_ids->hph_left_ocp, mbhc);
err_hphl_ocp_irq:
	mbhc->mbhc_cb->free_irq(codec, mbhc->intr_ids->mbhc_hs_rem_intr, mbhc);
err_mbhc_hs_rem_irq:
	mbhc->mbhc_cb->free_irq(codec, mbhc->intr_ids->mbhc_hs_ins_intr, mbhc);
err_mbhc_hs_ins_irq:
	mbhc->mbhc_cb->free_irq(codec, mbhc->intr_ids->mbhc_btn_release_intr,
				mbhc);
err_btn_release_irq:
	mbhc->mbhc_cb->free_irq(codec, mbhc->intr_ids->mbhc_btn_press_intr,
				mbhc);
err_btn_press_irq:
	mbhc->mbhc_cb->free_irq(codec, mbhc->intr_ids->mbhc_sw_intr, mbhc);
err_mbhc_sw_irq:
	if (mbhc->mbhc_cb->register_notifier)
		mbhc->mbhc_cb->register_notifier(codec, &mbhc->nblock, false);
	mutex_destroy(&mbhc->codec_resource_lock);
err:
	pr_debug("%s: leave ret %d\n", __func__, ret);
	return ret;
}
EXPORT_SYMBOL(wcd_mbhc_init);
}

初始化函數,主要註冊了耳機插拔和耳機按鍵的input設備,註冊了耳機四個按鍵的鍵值,註冊了一系列的中斷,我們先看看其中比較重要的三個中斷,耳機插入中斷和按鍵按下鬆開中斷。

可能一般項目要求我們耳機按鍵只支持media鍵就好了,而要求我們去掉其他的耳機按鍵,可以在這裏進行更改.

我們看看耳機插拔的中斷處理函數:wcd_mbhc_mech_plug_detect_irq()

耳機插拔的中斷處理

處理函數:wcd_mbhc_mech_plug_detect_irq(),滿足條件調用wcd_mbhc_swch_irq_handler進行處理


static irqreturn_t wcd_mbhc_mech_plug_detect_irq(int irq, void *data)
{
int r = IRQ_HANDLED;
	struct wcd_mbhc *mbhc = data; 
 	pr_debug("%s: enter\n", __func__);
	if (unlikely((mbhc->mbhc_cb->lock_sleep(mbhc, true)) == false)) {
		pr_warn("%s: failed to hold suspend\n", __func__);
		r = IRQ_NONE;
	} else {
		/* Call handler */
		wcd_mbhc_swch_irq_handler(mbhc);
		mbhc->mbhc_cb->lock_sleep(mbhc, false);
	}
	pr_debug("%s: leave %d\n", __func__, r);
	return r;
}

wcd_mbhc_swch_irq_handler函數,會讀取當前的檢測類型,如果detection_type = 1, 是指當前爲插入,檢測插入耳機類型,如果爲0,表示當前拔出,如果當前是檢測耳機插入, 就進行耳機插入的檢測;耳機拔出後,則關閉micbias電壓, 上報耳機拔出事件;如果當前是耳機延長線設備拔出,就關閉相關的中斷檢測,上報LINEOUT設備拔出事件

static void wcd_mbhc_swch_irq_handler(struct wcd_mbhc *mbhc)
{
	bool detection_type;
	bool micbias1 = false;
	struct snd_soc_codec *codec = mbhc->codec;
	dev_dbg(codec->dev, "%s: enter\n", __func__);
	WCD_MBHC_RSC_LOCK(mbhc);
	mbhc->in_swch_irq_handler = true;
	/* cancel pending button press */
	 /*如果有耳機按鍵任務在運行,去掉掉該任務*/
	if (wcd_cancel_btn_work(mbhc))
		pr_debug("%s: button press is canceled\n", __func__);
	/*讀取當前的檢測類型,如果detection_type = 1, 是指當前爲插入,檢測插入耳機類型,如果爲0,表示當前拔出*/
	WCD_MBHC_REG_READ(WCD_MBHC_MECH_DETECTION_TYPE, detection_type);
	/* Set the detection type appropriately */
	WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_MECH_DETECTION_TYPE,
				 !detection_type);

	pr_debug("%s: mbhc->current_plug: %d detection_type: %d\n", __func__,
			mbhc->current_plug, detection_type);
	wcd_cancel_hs_detect_plug(mbhc, &mbhc->correct_plug_swch);

	if (mbhc->mbhc_cb->micbias_enable_status)
    	/*如果當前是檢測耳機插入, 就進行耳機插入的檢測*/
		micbias1 = mbhc->mbhc_cb->micbias_enable_status(mbhc,
						MIC_BIAS_1);

	if ((mbhc->current_plug == MBHC_PLUG_TYPE_NONE) &&
	    detection_type) {
		/* Make sure MASTER_BIAS_CTL is enabled */
		/*下面是使能一系列的micbias相關的寄存器,把micbias2使能*/
		mbhc->mbhc_cb->mbhc_bias(codec, true);
		if (mbhc->mbhc_cb->mbhc_common_micb_ctrl)
			mbhc->mbhc_cb->mbhc_common_micb_ctrl(codec,
					MBHC_COMMON_MICB_TAIL_CURR, true);
		if (!mbhc->mbhc_cfg->hs_ext_micbias &&
		     mbhc->mbhc_cb->micb_internal)
			/*
			 * Enable Tx2 RBias if the headset
			 * is using internal micbias
			 */
			mbhc->mbhc_cb->micb_internal(codec, 1, true);
		/* Remove micbias pulldown */
		WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_PULLDOWN_CTRL, 0);
		/* Apply trim if needed on the device */
		if (mbhc->mbhc_cb->trim_btn_reg)
			mbhc->mbhc_cb->trim_btn_reg(codec);
		/* Enable external voltage source to micbias if present */
		if (mbhc->mbhc_cb->enable_mb_source)
			mbhc->mbhc_cb->enable_mb_source(codec, true);
		mbhc->btn_press_intr = false;
		mbhc->is_btn_press = false;
		wcd_mbhc_detect_plug_type(mbhc); //開始檢測插入耳機類型
    /*下面是檢測耳機拔出,耳機拔出後,關閉micbias電壓, 上報耳機拔出事件*/
	} else if ((mbhc->current_plug != MBHC_PLUG_TYPE_NONE)
			&& !detection_type) {
		/* Disable external voltage source to micbias if present */

		if (mbhc->mbhc_cb->enable_mb_source)
			mbhc->mbhc_cb->enable_mb_source(codec, false);
		/* Disable HW FSM */
		WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
		WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_BTN_ISRC_CTL, 0);
		if (mbhc->mbhc_cb->mbhc_common_micb_ctrl)
			mbhc->mbhc_cb->mbhc_common_micb_ctrl(codec,
					MBHC_COMMON_MICB_TAIL_CURR, false);
		if (mbhc->mbhc_cb->set_cap_mode)
			mbhc->mbhc_cb->set_cap_mode(codec, micbias1, false);
		mbhc->btn_press_intr = false;
		mbhc->is_btn_press = false;
		if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADPHONE) {
			wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_REM,
					     false);
			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);
			wcd_mbhc_report_plug(mbhc, 0, SND_JACK_HEADPHONE);
		} else if (mbhc->current_plug == MBHC_PLUG_TYPE_GND_MIC_SWAP) {
			wcd_mbhc_report_plug(mbhc, 0, SND_JACK_UNSUPPORTED);
		} else if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADSET) {
			/* make sure to turn off Rbias */
			if (mbhc->mbhc_cb->micb_internal)
				mbhc->mbhc_cb->micb_internal(codec, 1, false);
			/* Pulldown micbias */
			WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_PULLDOWN_CTRL, 1);
			wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_REM,
					     false);
			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);
			wcd_mbhc_report_plug(mbhc, 0, SND_JACK_HEADSET);
        /*如果當前是耳機延長線設備拔出,就關閉相關的中斷檢測,上報LINEOUT設備拔出事件*/
		} else if (mbhc->current_plug == MBHC_PLUG_TYPE_HIGH_HPH) {
			mbhc->is_extn_cable = false;
			wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_REM,
					     false);
			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);
			wcd_mbhc_report_plug(mbhc, 0, SND_JACK_LINEOUT);
		} else if (mbhc->current_plug == MBHC_PLUG_TYPE_ANC_HEADPHONE) {
			wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_REM, false);
			wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_INS, false);
			WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_DETECTION_TYPE,
						 0);
			WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, 0);
			wcd_mbhc_report_plug(mbhc, 0, SND_JACK_ANC_HEADPHONE);
		}
	} else if (!detection_type) {
		/* Disable external voltage source to micbias if present */
		if (mbhc->mbhc_cb->enable_mb_source)
			mbhc->mbhc_cb->enable_mb_source(codec, false);
		/* Disable HW FSM */
		WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
		WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_BTN_ISRC_CTL, 0);
		wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_INS, false);
		wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_REM, false);
	}
	mbhc->in_swch_irq_handler = false;
	WCD_MBHC_RSC_UNLOCK(mbhc);
	pr_debug("%s: leave\n", __func__);

耳機插拔中斷處理函數中的處理可以分爲三部分:

1). 檢測到耳機插入, 打開micbias電壓,進行耳機類型的檢測

2). 檢測到耳機拔出,關閉micbias電壓,上報耳機拔出事件

3). 檢測到耳機延長線設備拔出,上報耳機延長線設備拔出事件

我們再看看檢測插入耳機類型的處理函數wcd_mbhc_detect_plug_type()

耳機類型的處理

wcd_mbhc_detect_plug_type():檢測插入耳機類型的處理函數。這裏主要檢測耳機的各段有沒有接反,例如歐標美標耳機mic和gnd是反的;如果檢測到有插反的動作,直接跳到後面調度任務去校準耳機插入類型;如果沒有插反,根據結果,設置不同的耳機類型;根據結果設置耳機類型是三段耳機或者無效耳機;退出的時候,如果是四段耳機或者三段耳機,上報耳機類型,如果其他情況,調度任務去校準耳機類型。

/* called under codec_resource_lock acquisition */
static void wcd_mbhc_detect_plug_type(struct wcd_mbhc *mbhc)
{
	struct snd_soc_codec *codec = mbhc->codec;
	bool micbias1 = false;
	pr_debug("%s: enter\n", __func__);
	WCD_MBHC_RSC_ASSERT_LOCKED(mbhc);

	if (mbhc->mbhc_cb->micbias_enable_status)
		micbias1 = mbhc->mbhc_cb->micbias_enable_status(mbhc,
							MIC_BIAS_1);
	if (mbhc->mbhc_cb->set_cap_mode)
		mbhc->mbhc_cb->set_cap_mode(codec, micbias1, true);
	if (mbhc->mbhc_cb->mbhc_micbias_control)
		mbhc->mbhc_cb->mbhc_micbias_control(codec, MIC_BIAS_2,
						    MICB_ENABLE);
	else
		wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_MB);
	/* Re-initialize button press completion object */
	reinit_completion(&mbhc->btn_press_compl);
	wcd_schedule_hs_detect_plug(mbhc, &mbhc->correct_plug_swch);
	pr_debug("%s: leave\n", __func__);
}

wcd_correct_swch_plug():設置耳機檢測超時時間, 校準耳機類型函數:繼續檢測耳機插入是否有錯位,當檢測達到四次,調度接口函數,去兼容歐標美標耳機,大於四次,判定耳機插反,設置類型爲MBHC_PLUG_TYPE_GND_MIC_SWAP, 檢測耳機類型是否爲高阻抗耳機MBHC_PLUG_TYPE_HIGH_HPH,檢測耳機類型是否爲三段耳機MBHC_PLUG_TYPE_HEADPHONE,最後上報響應的input事件,設置micbias電壓的狀態。
設置耳機檢測超時時間 HS_DETECT_PLUG_TIME_MS

    /*設置耳機檢測超時時間,這裏是3s*/
    timeout = jiffies + msecs_to_jiffies(HS_DETECT_PLUG_TIME_MS);

判定爲高阻抗耳機

 /*判定爲高阻抗耳機*/
         if (result2 == 1) {
             pr_debug("%s: cable is extension cable\n", __func__);
             plug_type = MBHC_PLUG_TYPE_HIGH_HPH;
             wrk_complete = true;

設置micbias電壓的狀態

/*根據耳機的類型,設置micbias電壓的狀態*/
     wcd_enable_mbhc_supply(mbhc, plug_type);

wcd_enable_mbhc_supply():設置micbias電壓。micbias電壓有四種狀態:

WCD_MBHC_EN_CS: 關閉micbias2電壓
WCD_MBHC_EN_MB: 打開micbias電壓
WCD_MBHC_EN_PULLUP: 打開micbias電壓,並設置成PULL_UP狀態
WCD_MBHC_EN_NONE: 關閉micbias電壓

注意:如果沒有外接電容,耳機類型是四段耳機的時候,如果正在錄音,設置micbias狀態爲WCD_MBHC_EN_MB, 如果左右聲道PA在工作,即正在播放音樂,設置WCD_MBHC_EN_PULLUP,否則,設置狀態爲WCD_MBHC_EN_CS。如果沒有外接電容,耳機類型是三段耳機, 設置Micbias電壓狀態爲WCD_MBHC_EN_CS;其他狀態設置爲WCD_MBHC_EN_NONE

通過wcd_mbhc_jack_report將檢測到的耳機數據匯交給snd_soc_jack_report進行上報

阻抗值的讀取

可以通過調節VREF的值,來兼容阻抗大一點的耳機:

調節kernel/sound/soc/codecs/wcd-mbhc-v2.c #define HS_VREF_MIN_VAL 1400

1.4v,最大隻能識別7700歐阻抗的耳機, 這個阻抗指的是mic對地的阻抗,耳機的後兩節之間的阻抗

1.5v,最大能識別11k

1.6v,最大能識別17.6k

1.7v,最大能識別37.4k

再高的阻抗,可以嘗試提高micbias電壓,來進行兼容。

耳機插入後中斷相應,在wcd_mbhc_report_plug上報的時候可以讀取阻抗動態,動態的提高驅動能力。

static void wcd_mbhc_report_plug(struct wcd_mbhc *mbhc, int insertion,
				enum snd_jack_types jack_type)
{
	struct snd_soc_codec *codec = mbhc->codec;
	bool is_pa_on = false;
	WCD_MBHC_RSC_ASSERT_LOCKED(mbhc);
	pr_debug("%s: enter insertion %d hph_status %x\n",
		 __func__, insertion, mbhc->hph_status);
#ifdef CONFIG_SWITCH 
	switch_set_state(&wcd_mbhc_headset_switch, insertion ? 1:0);
#endif

#if defined(CONFIG_TINNO_AUDIO_HEADPHONES_HIGH_IMPED)
	is_headphones_high_imped = false;
#endif

	if (!insertion) {
		/* Report removal */
		mbhc->hph_status &= ~jack_type;
		/*
		 * cancel possibly scheduled btn work and
		 * report release if we reported button press
		 */
		if (wcd_cancel_btn_work(mbhc)) {
			pr_debug("%s: button press is canceled\n", __func__);
		} else if (mbhc->buttons_pressed) {
			pr_debug("%s: release of button press%d\n",
				 __func__, jack_type);
			wcd_mbhc_jack_report(mbhc, &mbhc->button_jack, 0,
					    mbhc->buttons_pressed);
			mbhc->buttons_pressed &=
				~WCD_MBHC_JACK_BUTTON_MASK;
		}
		if (mbhc->micbias_enable) {
			if (mbhc->mbhc_cb->mbhc_micbias_control)
				mbhc->mbhc_cb->mbhc_micbias_control(
						codec, MIC_BIAS_2,
						MICB_DISABLE);
			if (mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic)
				mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic(
						codec,
						MIC_BIAS_2, false);
			if (mbhc->mbhc_cb->set_micbias_value) {
				mbhc->mbhc_cb->set_micbias_value(codec);
				WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_MICB_CTRL, 0);
			}
			mbhc->micbias_enable = false;
		}
		#ifdef CONFIG_TINNO_AUDIO_MICBIAS_2V7
		else {
			if(mbhc->mbhc_cb->mbhc_micb2_2v7_ctrl)
				mbhc->mbhc_cb->mbhc_micb2_2v7_ctrl(
						mbhc->codec,false);
		}
		#endif
		mbhc->hph_type = WCD_MBHC_HPH_NONE;
		mbhc->zl = mbhc->zr = 0;
		pr_debug("%s: Reporting removal %d(%x)\n", __func__,
			 jack_type, mbhc->hph_status);
		wcd_mbhc_jack_report(mbhc, &mbhc->headset_jack,
				mbhc->hph_status, WCD_MBHC_JACK_MASK);
		wcd_mbhc_set_and_turnoff_hph_padac(mbhc);
		hphrocp_off_report(mbhc, SND_JACK_OC_HPHR);
		hphlocp_off_report(mbhc, SND_JACK_OC_HPHL);
		mbhc->current_plug = MBHC_PLUG_TYPE_NONE;
	} else {
		/*
		 * Report removal of current jack type.
		 * Headphone to headset shouldn't report headphone
		 * removal.
		 */
		if (mbhc->mbhc_cfg->detect_extn_cable &&
		    (mbhc->current_plug == MBHC_PLUG_TYPE_HIGH_HPH ||
		    jack_type == SND_JACK_LINEOUT) &&
		    (mbhc->hph_status && mbhc->hph_status != jack_type)) {

			if (mbhc->micbias_enable &&
			    mbhc->current_plug == MBHC_PLUG_TYPE_HEADSET) {
				if (mbhc->mbhc_cb->mbhc_micbias_control)
					mbhc->mbhc_cb->mbhc_micbias_control(
						codec, MIC_BIAS_2,
						MICB_DISABLE);
				if (mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic)
					mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic(
						codec,
						MIC_BIAS_2, false);
				if (mbhc->mbhc_cb->set_micbias_value) {
					mbhc->mbhc_cb->set_micbias_value(
							codec);
					WCD_MBHC_REG_UPDATE_BITS(
							WCD_MBHC_MICB_CTRL, 0);
				}
				mbhc->micbias_enable = false;
			}
			mbhc->hph_type = WCD_MBHC_HPH_NONE;
			mbhc->zl = mbhc->zr = 0;
			pr_debug("%s: Reporting removal (%x)\n",
				 __func__, mbhc->hph_status);
			wcd_mbhc_jack_report(mbhc, &mbhc->headset_jack,
					    0, WCD_MBHC_JACK_MASK);
			if (mbhc->hph_status == SND_JACK_LINEOUT) {
				pr_debug("%s: Enable micbias\n", __func__);
				/* Disable current source and enable micbias */
				wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_MB);
				pr_debug("%s: set up elec removal detection\n",
					  __func__);
				WCD_MBHC_REG_UPDATE_BITS(
						WCD_MBHC_ELECT_DETECTION_TYPE,
						0);
				usleep_range(200, 210);
				wcd_mbhc_hs_elec_irq(mbhc,
						     WCD_MBHC_ELEC_HS_REM,
						     true);
			}
			mbhc->hph_status &= ~(SND_JACK_HEADSET |
						SND_JACK_LINEOUT |
						SND_JACK_ANC_HEADPHONE |
						SND_JACK_UNSUPPORTED);
		}
		if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADSET &&
			jack_type == SND_JACK_HEADPHONE)
			mbhc->hph_status &= ~SND_JACK_HEADSET;
		/* Report insertion */
		if (jack_type == SND_JACK_HEADPHONE)
			mbhc->current_plug = MBHC_PLUG_TYPE_HEADPHONE;
		else if (jack_type == SND_JACK_UNSUPPORTED)
			mbhc->current_plug = MBHC_PLUG_TYPE_GND_MIC_SWAP;
		else if (jack_type == SND_JACK_HEADSET) {
			mbhc->current_plug = MBHC_PLUG_TYPE_HEADSET;
			mbhc->jiffies_atreport = jiffies;
		} else if (jack_type == SND_JACK_LINEOUT) {
			mbhc->current_plug = MBHC_PLUG_TYPE_HIGH_HPH;
		} else if (jack_type == SND_JACK_ANC_HEADPHONE)
			mbhc->current_plug = MBHC_PLUG_TYPE_ANC_HEADPHONE;
		if (mbhc->mbhc_cb->hph_pa_on_status)
			is_pa_on = mbhc->mbhc_cb->hph_pa_on_status(codec);
		if (mbhc->impedance_detect &&
			mbhc->mbhc_cb->compute_impedance &&
			(mbhc->mbhc_cfg->linein_th != 0) &&
			(!is_pa_on)) {
				mbhc->mbhc_cb->compute_impedance(mbhc,
						&mbhc->zl, &mbhc->zr);
			if ((mbhc->zl > mbhc->mbhc_cfg->linein_th &&
				mbhc->zl < MAX_IMPED) &&
				(mbhc->zr > mbhc->mbhc_cfg->linein_th &&
				 mbhc->zr < MAX_IMPED) &&
				(jack_type == SND_JACK_HEADPHONE)) {
				jack_type = SND_JACK_LINEOUT;
				mbhc->current_plug = MBHC_PLUG_TYPE_HIGH_HPH;
				if (mbhc->hph_status) {
					mbhc->hph_status &= ~(SND_JACK_HEADSET |
							SND_JACK_LINEOUT |
							SND_JACK_UNSUPPORTED);
					wcd_mbhc_jack_report(mbhc,
							&mbhc->headset_jack,
							mbhc->hph_status,
							WCD_MBHC_JACK_MASK);
				}
				pr_debug("%s: Marking jack type as SND_JACK_LINEOUT\n",
				__func__);
			}
//  detect headphone impedance
#if defined(CONFIG_TINNO_AUDIO_HEADPHONES_HIGH_IMPED)
			else if ((mbhc->zl > mbhc->mbhc_cfg->headphones_high_imped_th && mbhc->zl < MAX_IMPED) 
                  && (mbhc->zr > mbhc->mbhc_cfg->headphones_high_imped_th && mbhc->zr < MAX_IMPED)
                  && (jack_type == SND_JACK_HEADPHONE)) {
				//switch to high impedace audio path(100<impedance<5000)
				is_headphones_high_imped = true;
			}
#endif
		}

		mbhc->hph_status |= jack_type;
		pr_debug("%s: Reporting insertion %d(%x)\n", __func__,
			 jack_type, mbhc->hph_status);
		wcd_mbhc_jack_report(mbhc, &mbhc->headset_jack,
				    (mbhc->hph_status | SND_JACK_MECHANICAL),
				    WCD_MBHC_JACK_MASK);
		wcd_mbhc_clr_and_turnon_hph_padac(mbhc);
	}
	pr_debug("%s: leave hph_status %x\n", __func__, mbhc->hph_status);
}

一般在調試高通的耳機功能的時候,我們需要更改的東西:

1). 耳機的插拔檢測:

qcom,msm-mbhc-hphl-swh = <1>;       //0是NC,1是NO   

NO是指耳機的accdet腳默認上拉到1.8v,插入耳機後,accdet腳跟左聲道短接到一塊,電平拉低。而NC是指耳機的accdet腳默認和左聲道短接到一塊,爲低電平,插入耳機後,accdet腳與左聲道斷開,accdet腳變爲高電平。如下圖所示:

2). 耳機mic的micbias電壓是外部接過去的還是內部接過去的,如上圖micbias就是從外部接過去的

如果micbias電壓是內部接過去的:

qcom,msm-hs-micbias-type = "internal";

"MIC BIAS Internal2", "Headset Mic",

"AMIC2", "MIC BIAS Internal2",

如果micbias電壓是外部接過去的:

qcom,msm-hs-micbias-type = "external";

"MIC BIAS External2", "Headset Mic",

"AMIC2", "MIC BIAS External2", 

3). 耳機所使用的micbias輸出上是否接有外部電容,如果接有外部電容,需要添加:

qcom,msm-micbias2-ext-cap

4). 耳機是否支持歐標/美標的檢測的兼容,如下圖:

美標耳機的順序爲左/右/地/麥,歐標的順序爲左/右/麥/地,三段耳機是指沒有麥的,從外觀看的話,一般情況下,美標耳機的絕緣環是黑色的,歐標耳機的絕緣環是白色的。

如果要設備支持歐標/美標耳機的兼容檢測的話,需要外加專用於檢測的電路或者IC,所以一般情況下我們是沒有添加兼容的功能的,如下圖所示:

如果支持歐標/美標兼容檢測,注意修改如下pinctrl的GPIO口。

cross-conn-det {

qcom,pins = <&gp 97>;

qcom,num-grp-pins = <1>;

qcom,pin-func = <0>;

label = "cross-conn-det-sw";

cross_conn_det_act: lines_on {

drive-strength = <8>;

output-low;

bias-pull-down;

};

cross_conn_det_sus: lines_off {

drive-strength = <2>;

bias-disable;

};

};

如果不支持歐標/美標的兼容檢測,也記得從設備樹中刪除對以上pinctrl的引用,例如8909平臺上刪除:

pinctrl-names = "cdc_lines_act",

"cdc_lines_sus",

//"cross_conn_det_act",

//"cross_conn_det_sus",

"vdd_spkdrv_act",

"vdd_spkdrv_sus";

pinctrl-0 = <&cdc_pdm_lines_act &vdd_spkdrv_act>;

pinctrl-1 = <&cdc_pdm_lines_sus &vdd_spkdrv_sus>;

// pinctrl-2 = <&cross_conn_det_act>;

// pinctrl-3 = <&cross_conn_det_sus>;

// qcom,cdc-us-euro-gpios = <&msm_gpio 97 0>;

 

5).更改micbias電壓:

類似於蘋果耳機,它的mic的工作電壓是大於1.8v,所以爲了能正常使用蘋果耳機,需要增加micbias電壓:

qcom,cdc-micbias-cfilt-mv = <2700>;

或者更改代碼: kernel/sound/soc/codecs/msm8x16-wcd.c #define MICBIAS_DEFAULT_VAL 2700000

6).有時候,插入了一些大阻抗的耳機,需要我們支持,可以按照如下進行修改:

修改:

kernel/sound/soc/codecs/wcd-mbhc-v2.c         #define HS_VREF_MIN_VAL 1400

1.4v,最大隻能識別7700歐阻抗的耳機, 這個阻抗指的是mic對地的阻抗,耳機的後兩節之間的阻抗

1.5v,最大能識別11k

1.6v,最大能識別17.6k

1.7v,最大能識別37.4k

7).耳機對lineout設備的識別,有時候需要要求設備支持LINEOUT設備,類如接到耳機插孔上的音箱,高通平臺上, 對應LINEOUT設備是上報成SND_JACK_LINEOUT設備,但android層是不支持LINEOUT設備的,它不會對此事件做響應,所以,插入之後無法識別。可以按照如下方式更改:

kernel/sound/soc/msm/msm8x16.c            .linein_th = 5000,改爲 .linein_th = 0,

這樣設備就能將LINEOUT設備識別成三段耳機了

8). 如果外部沒有接兼容歐標美標耳機的電路,但是又想兼容的話,可以播放音樂,但是無法通話,可以做出如下修改:


1 void wcd_correct_swch_plug()
2 {
3  report:
4      if (plug_type == MBHC_PLUG_TYPE_GND_MIC_SWAP)
5          plug_type = MBHC_PLUG_TYPE_HEADPHONE;    
6 }

9). 對耳機按鍵的調試

耳機上報的鍵值定義:

.key_code[0] = KEY_MEDIA,
.key_code[1] = KEY_VOICECOMMAND,

.key_code[2] = KEY_VOLUMEUP,

.key_code[3] = KEY_VOLUMEDOWN,

.key_code[4] = 0,

.key_code[5] = 0,

.key_code[6] = 0,

.key_code[7] = 0,

耳機按鍵的數量定義在:

#define WCD_MBHC_DEF_BUTTONS 4

耳機按鍵的閾值定義在:

static void *def_msm8x16_wcd_mbhc_cal(void)

{

btn_low[0] = 12.5;

btn_high[0] = 12.5;

btn_low[1] = 37.5;

btn_high[1] = 37.5;

btn_low[2] = 75;

btn_high[2] = 62.5;

btn_low[3] = 100;

btn_high[3] = 100;

btn_low[4] = 125;

btn_high[4] = 125;

}

所以如果想修改耳機支持的按鍵數目,按照如下方式修改:


.key_code[0] = KEY_MEDIA,

.key_code[1] = 0,

.key_code[2] = 0,

.key_code[3] = 0,

.key_code[4] = 0,

.key_code[5] = 0,

.key_code[6] = 0,

.key_code[7] = 0,

#define WCD_MBHC_DEF_BUTTONS 1

static void *def_msm8x16_wcd_mbhc_cal(void)

{

btn_low[0] = 12.5;

btn_high[0] = 12.5;

}

如果想調試按鍵的閾值:

分別配置MBHC爲CS(Current Source)和MB(MIC BIAS)模式:
CS mode : 0x144 = 0x00, 0x151 = 0xB0

MB mode : 0x144 = 0x80, 0x151 = 0x80

adb root

cd sys/kernel/debug/soc/<snd-card>/msm8x16_wcd_codec/

echo<Register Address><value>> codec_reg

類如: echo “0x121 0xA0” > codec_reg

按下耳機按鍵,分別測量兩個模式下的耳機mic上的電壓值,把測量的值分別填入高通提供的表格,或者按照80-NK808-15的Table3-3和Table3-4計算出最後的閥值。

耳機阻抗檢測log

//當耳機阻抗爲32歐姆時,插拔3次,得到的檢測結果分別爲45歐姆,31歐姆,34歐姆

 adb shell cat /proc/kmsg 
<6>[  590.157348] wcd_mbhc_report_plug: check impedance: mbhc->zl=45, mbhc->zr=45, jack_type = 3
<6>[  608.547234] wcd_mbhc_report_plug: check impedance: mbhc->zl=31, mbhc->zr=31, jack_type = 3
<6>[  612.957214] wcd_mbhc_report_plug: check impedance: mbhc->zl=34, mbhc->zr=34, jack_type = 3
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章