耳机调试学习记录

耳机阻抗

具有电阻、电感和电容的电路里,对交流电所起的阻碍作用叫做阻抗。阻抗常用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
  • 2
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值