高通平台耳机知识记录
2018-01-15 20:51 wulizhi 阅读(8903) 评论(0) 编辑 收藏 举报一.
在高通平台中,默认使用内部codec的时候,耳机的输出及控制都是在内部codec中进行的,所以,可以想象得到,耳机的整个初始化起源过程,是在codec的初始化中。高通平台的machine驱动文件一般都是平台名字开头的,例如8974的是msm8974.c, 8998的是msm8998.c,8909的是msm8x16.c。可以通过cat proc/asound/cards找到声卡的名字,根据名字可以找到该平台的machine驱动文件。同时可以根据machine驱动的compatible的名字,找到dts文件中,sound相关的信息。如qcom,msm8x16-audio-codec, 在arch/arm/boot/dts/NX505J/msm8974.dtsi文件中,可以找到sound相关信息。
1 sound { 2 compatible = "qcom,msm8x16-audio-codec"; 3 qcom,model = "msm8909-skue-snd-card"; 4 qcom,msm-snd-card-id = <0>; 5 qcom,msm-codec-type = "internal"; 6 qcom,msm-ext-pa = "primary"; 7 qcom,msm-mclk-freq = <9600000>; 8 qcom,msm-mbhc-hphl-swh = <1>; 9 qcom,msm-mbhc-gnd-swh = <0>; 10 qcom,msm-hs-micbias-type = "internal"; 11 qcom,msm-micbias1-ext-cap; 12 qcom,msm-micbias2-ext-cap; 13 ...
其中 qcom,model = "msm8909-skue-snd-card", 即注册的声卡名字。
然后找到系统注册进去的dai_link的地方,在后端的dai_link中,可以找到primary mi2s playback那路dai_link,在高通平台中,这primary_mi2s这一路i2s,都是留给内部codec用的,所以,这路的
dai_link上的codec_name和codec_dai_name,就是对应着内部codec的信息:
1 { 2 .name = LPASS_BE_PRI_MI2S_RX, 3 .stream_name = "Primary MI2S Playback", 4 .cpu_dai_name = "msm-dai-q6-mi2s.0", 5 .platform_name = "msm-pcm-routing", 6 .codec_name = "tombak_codec", 7 .codec_dai_name = "msm8x16_wcd_i2s_rx1", 8 .no_pcm = 1, 9 .be_id = MSM_BACKEND_DAI_PRI_MI2S_RX, 10 .init = &msm_audrx_init, 11 .be_hw_params_fixup = msm_pri_rx_be_hw_params_fixup, 12 .ops = &msm8x16_mi2s_be_ops, 13 .ignore_suspend = 1, 14 },
由此我们可以找到高通平台默认的codec驱动文件msm8x16-wcd.c,在该文件中,注册了codec_dai_driver : msm8x16_wcd_i2s_rx1。
那这里就要谈论一个问题,在初始化的时候,如何凭借dai_link中的codec信息找到对应的codec,答案是codec_name。但注意,这里并不是通过这个名字直接寻找的,例如8909平台。
在设备树文件msm8909-mtp.dtsi中,sound节点下有如下信息:
1 asoc-codec = <&stub_codec>, <&pm8909_conga_dig>; 2 asoc-codec-names = "msm-stub-codec.1", "tombak_codec";
在初始化的时候,dai_link中的codec_name会跟这里的asoc-codec-names进行匹配,进而获取上面asoc-codec中的codec_node :
1 pm8909_conga_dig: 8909_wcd_codec@f000 { 2 compatible = "qcom,msm8x16_wcd_codec"; 3 reg = <0xf000 0x100>; 4 interrupt-parent = <&spmi_bus>; 5 ... 6 }
而这个node节点正式codec驱动的设备树节点。在soc_bind_dai_link()函数中,会做出如下处理:
1 /*注册codec的时候,会将所有注册的codec链接到codec_list中*/ 2 list_for_each_entry(codec, &codec_list, list) { 3 if (dai_link->codec_of_node) { 4 /*根据设备数节点句柄进行匹配*/ 5 if (codec->dev->of_node != dai_link->codec_of_node) 6 continue; 7 } else { 8 /*如果句柄为空,根据,codec_name进行匹配,在这里不会走这里,其实codec_name是 wcd-spmi-core.1*/ 9 if (strcmp(codec->name, dai_link->codec_name)) 10 continue; 11 } 12 13 rtd->codec = codec; 14 15 /*找到codec之后,根据codec_dai的名字找到对应的codec_dai*/ 16 list_for_each_entry(codec_dai, &dai_list, list) { 17 if (codec->dev == codec_dai->dev && 18 !strcmp(codec_dai->name, 19 dai_link->codec_dai_name)) { 20 21 rtd->codec_dai = codec_dai; 22 } 23 } 24 }
所以,我们可以根据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); 根据外部有没有接电容来初始化电容模式
后面两个个处理比较简单:
1). msm8x16_wcd_set_micb_v(codec); 设置Micbias电压:
有时候一些大阻抗的耳机,比如苹果耳机,它的mic工作的时候要求的电压要高些,比如2.7v,而一般高通平台是把micbias电压设置成1.8v,所以,这里就需要更改来满足要求。
如果设备树中定义了属性qcom,cdc-micbias-cfilt-mv, 就更改该属性的值,如果没有就采用默认值MICBIAS_DEFAULT_VAL。
所以,可以通过修改 #define MICBIAS_DEFAULT_VAL 1800000 或者更改 qcom,cdc-micbias-cfilt-mv = <2700>; 来修改micbias电压值
2). msm8x16_wcd_configure_cap()这个函数是根据micbias1和micbias2外部有没有接电容,来配置设备树中的qcom,msm-micbias2-ext-cap属性,见下图,在micbias1和micbias2上面都外接了一个电容,所以,需要在dtsi文件中配置qcom,msm-micbias2-ext-cap; 和qcom,msm-micbias1-ext-cap
3). 初始化函数wcd_mbhc_init():
1 int wcd_mbhc_init(struct wcd_mbhc *mbhc, struct snd_soc_codec *codec, 2 const struct wcd_mbhc_cb *mbhc_cb, 3 const struct wcd_mbhc_intr *mbhc_cdc_intr_ids, 4 bool impedance_det_en) 5 { 6 /*注册耳机插拔和按键的input设备,设置耳机按键值,*/ 7 if (mbhc->headset_jack.jack == NULL) { 8 ret = snd_soc_jack_new(codec, "Headset Jack", 9 WCD_MBHC_JACK_MASK, &mbhc->headset_jack); 10 11 ret = snd_soc_jack_new(codec, "Button Jack", 12 WCD_MBHC_JACK_BUTTON_MASK, 13 &mbhc->button_jack); 14 15 ret = snd_jack_set_key(mbhc->button_jack.jack, 16 SND_JACK_BTN_0, 17 KEY_MEDIA); 18 19 ret = snd_jack_set_key(mbhc->button_jack.jack, 20 SND_JACK_BTN_1, 21 KEY_VOICECOMMAND); 22 23 ret = snd_jack_set_key(mbhc->button_jack.jack, 24 SND_JACK_BTN_2, 25 KEY_VOLUMEUP); 26 27 ret = snd_jack_set_key(mbhc->button_jack.jack, 28 SND_JACK_BTN_3, 29 KEY_VOLUMEDOWN); 30 31 INIT_DELAYED_WORK(&mbhc->mbhc_firmware_dwork, 32 wcd_mbhc_fw_read); 33 INIT_DELAYED_WORK(&mbhc->mbhc_btn_dwork, wcd_btn_lpress_fn); 34 } 35 36 /* Register event notifier */ 37 mbhc->nblock.notifier_call = wcd_event_notify; 38 ret = msm8x16_register_notifier(codec, &mbhc->nblock); 39 if (ret) { 40 pr_err("%s: Failed to register notifier %d\n", __func__, ret); 41 return ret; 42 } 43 44 init_waitqueue_head(&mbhc->wait_btn_press); 45 mutex_init(&mbhc->codec_resource_lock); 46 47 /*申请初测耳机插拔中断*/ 48 ret = wcd9xxx_spmi_request_irq(mbhc->intr_ids->mbhc_sw_intr, 49 wcd_mbhc_mech_plug_detect_irq, 50 "mbhc sw intr", mbhc); 51 if (ret) { 52 pr_err("%s: Failed to request irq %d, ret = %d\n", __func__, 53 mbhc->intr_ids->mbhc_sw_intr, ret); 54 goto err_mbhc_sw_irq; 55 } 56 57 /*申请注册耳机按键按下的中断*/ 58 ret = wcd9xxx_spmi_request_irq(mbhc->intr_ids->mbhc_btn_press_intr, 59 wcd_mbhc_btn_press_handler, 60 "Button Press detect", 61 mbhc); 62 if (ret) { 63 pr_err("%s: Failed to request irq %d\n", __func__, 64 mbhc->intr_ids->mbhc_btn_press_intr); 65 goto err_btn_press_irq; 66 } 67 68 /*申请注册耳机按键松开的中断*/ 69 ret = wcd9xxx_spmi_request_irq(mbhc->intr_ids->mbhc_btn_release_intr, 70 wcd_mbhc_release_handler, 71 "Button Release detect", mbhc); 72 if (ret) { 73 pr_err("%s: Failed to request irq %d\n", __func__, 74 mbhc->intr_ids->mbhc_btn_release_intr); 75 goto err_btn_release_irq; 76 } 77 78 /*这个应该是注册检测高阻抗的耳机延长线设备的插入中断*/ 79 ret = wcd9xxx_spmi_request_irq(mbhc->intr_ids->mbhc_hs_ins_intr, 80 wcd_mbhc_hs_ins_irq, 81 "Elect Insert", mbhc); 82 if (ret) { 83 pr_err("%s: Failed to request irq %d\n", __func__, 84 mbhc->intr_ids->mbhc_hs_ins_intr); 85 goto err_mbhc_hs_ins_irq; 86 } 87 wcd9xxx_spmi_disable_irq(mbhc->intr_ids->mbhc_hs_ins_intr); 88 89 /*这个应该是注册检测高阻抗的耳机延长线设备的拔出中断*/ 90 ret = wcd9xxx_spmi_request_irq(mbhc->intr_ids->mbhc_hs_rem_intr, 91 wcd_mbhc_hs_rem_irq, 92 "Elect Remove", mbhc); 93 if (ret) { 94 pr_err("%s: Failed to request irq %d\n", __func__, 95 mbhc->intr_ids->mbhc_hs_rem_intr); 96 goto err_mbhc_hs_rem_irq; 97 } 98 wcd9xxx_spmi_disable_irq(mbhc->intr_ids->mbhc_hs_rem_intr); 99 100 ret = wcd9xxx_spmi_request_irq(mbhc->intr_ids->hph_left_ocp, 101 wcd_mbhc_hphl_ocp_irq, "HPH_L OCP detect", 102 mbhc); 103 if (ret) { 104 pr_err("%s: Failed to request irq %d\n", __func__, 105 mbhc->intr_ids->hph_left_ocp); 106 goto err_hphl_ocp_irq; 107 } 108 109 ret = wcd9xxx_spmi_request_irq(mbhc->intr_ids->hph_right_ocp, 110 wcd_mbhc_hphr_ocp_irq, "HPH_R OCP detect", 111 mbhc); 112 if (ret) { 113 pr_err("%s: Failed to request irq %d\n", __func__, 114 mbhc->intr_ids->hph_right_ocp); 115 goto err_hphr_ocp_irq; 116 } 117 118 }
初始化函数,主要注册了耳机插拔和耳机按键的input设备,注册了耳机四个按键的键值,注册了一系列的中断,我们先看看其中比较重要的三个中断,耳机插入中断和按键按下松开中断。
可能一般项目要求我们耳机按键只支持media键就好了,而要求我们去掉其他的耳机按键,可以在这里进行更改.
我们看看耳机插拔的中断处理函数:wcd_mbhc_mech_plug_detect_irq()
1 static irqreturn_t wcd_mbhc_mech_plug_detect_irq(int irq, void *data) 2 { 3 wcd_mbhc_swch_irq_handler(mbhc); 4 } 5 6 7 static void wcd_mbhc_swch_irq_handler(struct wcd_mbhc *mbhc) 8 { 9 bool detection_type; 10 bool micbias1; 11 struct snd_soc_codec *codec = mbhc->codec; 12 pr_debug("%s: enter\n", __func__); 13 14 WCD_MBHC_RSC_LOCK(mbhc); 15 16 mbhc->in_swch_irq_handler = true; 17 18 /*如果有耳机按键任务在运行,去掉掉该任务*/ 19 if (wcd_cancel_btn_work(mbhc)) 20 pr_debug("%s: button press is canceled\n", __func__); 21 22 /*读取当前的检测类型,如果detection_type = 1, 是指当前为插入,检测插入耳机类型,如果为0,表示当前拔出*/ 23 detection_type = (snd_soc_read(codec, 24 MSM8X16_WCD_A_ANALOG_MBHC_DET_CTL_1)) & 0x20; 25 26 /* Set the detection type appropriately */ 27 snd_soc_update_bits(codec, MSM8X16_WCD_A_ANALOG_MBHC_DET_CTL_1, 28 0x20, (!detection_type << 5)); 29 30 pr_debug("%s: mbhc->current_plug: %d detection_type: %d\n", __func__, 31 mbhc->current_plug, detection_type); 32 wcd_cancel_hs_detect_plug(mbhc, &mbhc->correct_plug_swch); 33 34 /*如果当前是检测耳机插入, 就进行耳机插入的检测*/ 35 micbias1 = (snd_soc_read(codec, MSM8X16_WCD_A_ANALOG_MICB_1_EN) & 0x80); 36 if ((mbhc->current_plug == MBHC_PLUG_TYPE_NONE) && 37 detection_type) { 38 /*下面是使能一系列的micbias相关的寄存器,把micbias2使能*/ 39 /* Make sure MASTER_BIAS_CTL is enabled */ 40 snd_soc_update_bits(codec, 41 MSM8X16_WCD_A_ANALOG_MASTER_BIAS_CTL, 42 0x30, 0x30); 43 snd_soc_update_bits(codec, 44 MSM8X16_WCD_A_ANALOG_MICB_1_EN, 45 0x04, 0x04); 46 if (!mbhc->mbhc_cfg->hs_ext_micbias) 47 /* Enable Tx2 RBias if the headset 48 * is using internal micbias*/ 49 snd_soc_update_bits(codec, 50 MSM8X16_WCD_A_ANALOG_MICB_1_INT_RBIAS, 51 0x10, 0x10); 52 /* Remove pull down on MIC BIAS2 */ 53 snd_soc_update_bits(codec, 54 MSM8X16_WCD_A_ANALOG_MICB_2_EN, 55 0x20, 0x00); 56 /* Enable HW FSM */ 57 snd_soc_update_bits(codec, 58 MSM8X16_WCD_A_ANALOG_MBHC_FSM_CTL, 59 0x80, 0x80); 60 /* Apply trim if needed on the device */ 61 if (mbhc->mbhc_cb && mbhc->mbhc_cb->trim_btn_reg) 62 mbhc->mbhc_cb->trim_btn_reg(codec); 63 /*如果micbias电压是外供的,这里把它使能*/ 64 if (mbhc->mbhc_cb && mbhc->mbhc_cb->enable_mb_source) 65 mbhc->mbhc_cb->enable_mb_source(codec, true); 66 mbhc->btn_press_intr = false; 67 /*开始检测插入耳机类型*/ 68 wcd_mbhc_detect_plug_type(mbhc); 69 } 70 /*下面是检测耳机拔出,耳机拔出后,关闭micbias电压, 上报耳机拔出事件*/ 71 else if ((mbhc->current_plug != MBHC_PLUG_TYPE_NONE) 72 && !detection_type) { 73 /* Disable external voltage source to micbias if present */ 74 if (mbhc->mbhc_cb && mbhc->mbhc_cb->enable_mb_source) 75 mbhc->mbhc_cb->enable_mb_source(codec, false); 76 /* Disable HW FSM */ 77 snd_soc_update_bits(codec, 78 MSM8X16_WCD_A_ANALOG_MBHC_FSM_CTL, 79 0xB0, 0x00); 80 snd_soc_update_bits(codec, 81 MSM8X16_WCD_A_ANALOG_MICB_1_EN, 82 0x04, 0x00); 83 if (mbhc->mbhc_cb && mbhc->mbhc_cb->set_cap_mode) 84 mbhc->mbhc_cb->set_cap_mode(codec, micbias1, false); 85 mbhc->btn_press_intr = false; 86 if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADPHONE) { 87 wcd_mbhc_report_plug(mbhc, 0, SND_JACK_HEADPHONE); 88 } else if (mbhc->current_plug == MBHC_PLUG_TYPE_GND_MIC_SWAP) { 89 wcd_mbhc_report_plug(mbhc, 0, SND_JACK_UNSUPPORTED); 90 } else if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADSET) { 91 /* make sure to turn off Rbias */ 92 snd_soc_update_bits(codec, 93 MSM8X16_WCD_A_ANALOG_MICB_1_INT_RBIAS, 94 0x18, 0x08); 95 snd_soc_update_bits(codec, 96 MSM8X16_WCD_A_ANALOG_MICB_2_EN, 97 0x20, 0x20); 98 wcd9xxx_spmi_disable_irq( 99 mbhc->intr_ids->mbhc_hs_rem_intr); 100 wcd9xxx_spmi_disable_irq( 101 mbhc->intr_ids->mbhc_hs_ins_intr); 102 snd_soc_update_bits(codec, 103 MSM8X16_WCD_A_ANALOG_MBHC_DET_CTL_1, 104 0x01, 0x01); 105 snd_soc_update_bits(codec, 106 MSM8X16_WCD_A_ANALOG_MBHC_DET_CTL_2, 107 0x06, 0x00); 108 wcd_mbhc_report_plug(mbhc, 0, SND_JACK_HEADSET); 109 } 110 /*如果当前是耳机延长线设备拔出,就关闭相关的中断检测,上报LINEOUT设备拔出事件*/ 111 else if (mbhc->current_plug == MBHC_PLUG_TYPE_HIGH_HPH) { 112 mbhc->is_extn_cable = false; 113 wcd9xxx_spmi_disable_irq( 114 mbhc->intr_ids->mbhc_hs_rem_intr); 115 wcd9xxx_spmi_disable_irq( 116 mbhc->intr_ids->mbhc_hs_ins_intr); 117 snd_soc_update_bits(codec, 118 MSM8X16_WCD_A_ANALOG_MBHC_DET_CTL_1, 119 0x01, 0x01); 120 snd_soc_update_bits(codec, 121 MSM8X16_WCD_A_ANALOG_MBHC_DET_CTL_2, 122 0x06, 0x00); 123 wcd_mbhc_report_plug(mbhc, 0, SND_JACK_LINEOUT); 124 } 125 } else if (!detection_type) { 126 /* Disable external voltage source to micbias if present */ 127 if (mbhc->mbhc_cb && mbhc->mbhc_cb->enable_mb_source) 128 mbhc->mbhc_cb->enable_mb_source(codec, false); 129 /* Disable HW FSM */ 130 snd_soc_update_bits(codec, 131 MSM8X16_WCD_A_ANALOG_MBHC_FSM_CTL, 132 0xB0, 0x00); 133 } 134 135 mbhc->in_swch_irq_handler = false; 136 WCD_MBHC_RSC_UNLOCK(mbhc); 137 pr_debug("%s: leave\n", __func__); 138 }
耳机插拔中断处理函数中的处理可以分为三部分:
1). 检测到耳机插入, 打开micbias电压,进行耳机类型的检测
2). 检测到耳机拔出,关闭micbias电压,上报耳机拔出事件
3). 检测到耳机延长线设备拔出,上报耳机延长线设备拔出事件
我们再看看检测插入耳机类型的处理函数wcd_mbhc_detect_plug_type()
1 static void wcd_mbhc_detect_plug_type(struct wcd_mbhc *mbhc) 2 { 3 timeout_result = wait_event_interruptible_timeout(mbhc->wait_btn_press, 4 mbhc->is_btn_press, timeout); 5 6 WCD_MBHC_RSC_LOCK(mbhc); 7 result1 = snd_soc_read(codec, MSM8X16_WCD_A_ANALOG_MBHC_BTN_RESULT); 8 result2 = snd_soc_read(codec, 9 MSM8X16_WCD_A_ANALOG_MBHC_ZDET_ELECT_RESULT); 10 11 /*没有按键按下, 检测耳机类型*/ 12 if (!timeout_result) { 13 pr_debug("%s No btn press interrupt\n", __func__); 14 /* 15 * Check if there is any cross connection, 16 * Micbias and schmitt trigger (HPHL-HPHR) 17 * needs to be enabled. 18 */ 19 pr_debug("%s: result1 %x, result2 %x\n", __func__, 20 result1, result2); 21 if (!(result2 & 0x01)) { 22 /* 23 * Cross connection result is not reliable 24 * so do check for it for 4 times to conclude 25 * cross connection occured or not. 26 */ 27 /*检测耳机的各段有没有接反,例如欧标美标耳机mic和gnd是反的, 28 例如耳机只插入三截,导致耳机的左声道跟插孔的检测脚,左声道,右声道短接到一起,耳机的右声道跟插孔的地或者Mic短接到一起*/ 29 do { 30 cross_conn = wcd_check_cross_conn(mbhc); 31 try++; 32 } while (try < GND_MIC_SWAP_THRESHOLD); 33 /*如果检测到有插反的动作,直接跳到后面调度任务去校准耳机插入类型*/ 34 if (cross_conn) { 35 pr_debug("%s: cross con found, start polling\n", 36 __func__); 37 plug_type = MBHC_PLUG_TYPE_GND_MIC_SWAP; 38 goto exit; 39 } 40 } 41 42 /* Read back result1 and result2 value again to reconfirm*/ 43 result1 = snd_soc_read(codec, 44 MSM8X16_WCD_A_ANALOG_MBHC_BTN_RESULT); 45 result2 = snd_soc_read(codec, 46 MSM8X16_WCD_A_ANALOG_MBHC_ZDET_ELECT_RESULT); 47 48 /*如果没有插反,根据结果,设置不同的耳机类型*/ 49 if (!result1 && !(result2 & 0x01)) 50 plug_type = MBHC_PLUG_TYPE_HEADSET; 51 else if (!result1 && (result2 & 0x01)) 52 plug_type = MBHC_PLUG_TYPE_HIGH_HPH; 53 else { 54 plug_type = MBHC_PLUG_TYPE_INVALID; 55 goto exit; 56 } 57 } else { 58 /*根据结果设置耳机类型是三段耳机或者无效耳机*/ 59 if (!result1 && !(result2 & 0x01)) 60 plug_type = MBHC_PLUG_TYPE_HEADPHONE; 61 else { 62 plug_type = MBHC_PLUG_TYPE_INVALID; 63 goto exit; 64 } 65 } 66 exit: 67 pr_debug("%s: Valid plug found, plug type is %d\n", 68 __func__, plug_type); 69 /*退出的时候,如果是四段耳机或者三段耳机,上报耳机类型,如果其他情况,调度任务去校准耳机类型*/ 70 if (plug_type == MBHC_PLUG_TYPE_HEADSET || 71 plug_type == MBHC_PLUG_TYPE_HEADPHONE) { 72 wcd_mbhc_find_plug_and_report(mbhc, plug_type); 73 wcd_schedule_hs_detect_plug(mbhc, &mbhc->correct_plug_swch); 74 } else { 75 wcd_schedule_hs_detect_plug(mbhc, &mbhc->correct_plug_swch); 76 } 77 pr_debug("%s: leave\n", __func__); 78 }
耳机类型检测函数做的事情可以概括为:初步检测下耳机的插入状态,如果是三段四段耳机就上报耳机类型,然后,都调度任务进行进一步的耳机类型的检测。
我们大概可以想象的到校准耳机类型的函数做了哪些事情,设置各种状态寄存器,进一步去细分耳机类型,再上报。函数为wcd_correct_swch_plug();
1 static void wcd_correct_swch_plug(struct work_struct *work) 2 { 3 struct wcd_mbhc *mbhc; 4 struct snd_soc_codec *codec; 5 enum wcd_mbhc_plug_type plug_type = MBHC_PLUG_TYPE_INVALID; 6 unsigned long timeout; 7 u16 result1, result2; 8 bool wrk_complete = false; 9 int pt_gnd_mic_swap_cnt = 0; 10 int no_gnd_mic_swap_cnt = 0; 11 bool is_pa_on; 12 bool micbias2; 13 bool micbias1; 14 15 pr_debug("%s: enter\n", __func__); 16 17 mbhc = container_of(work, struct wcd_mbhc, correct_plug_swch); 18 codec = mbhc->codec; 19 20 /* Enable micbias for detection in correct work*/ 21 wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_MB); 22 /*设置耳机检测超时时间,这里是3s*/ 23 timeout = jiffies + msecs_to_jiffies(HS_DETECT_PLUG_TIME_MS); 24 while (!time_after(jiffies, timeout)) { 25 if (mbhc->hs_detect_work_stop) { 26 pr_debug("%s: stop requested: %d\n", __func__, 27 mbhc->hs_detect_work_stop); 28 wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_NONE); 29 goto exit; 30 } 31 mbhc->btn_press_intr = false; 32 /* Toggle FSM */ 33 snd_soc_update_bits(codec, 34 MSM8X16_WCD_A_ANALOG_MBHC_FSM_CTL, 35 0x80, 0x00); 36 snd_soc_update_bits(codec, 37 MSM8X16_WCD_A_ANALOG_MBHC_FSM_CTL, 38 0x80, 0x80); 39 /* allow sometime and re-check stop requested again */ 40 msleep(20); 41 if (mbhc->hs_detect_work_stop) { 42 pr_debug("%s: stop requested: %d\n", __func__, 43 mbhc->hs_detect_work_stop); 44 wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_NONE); 45 goto exit; 46 } 47 result1 = snd_soc_read(codec, 48 MSM8X16_WCD_A_ANALOG_MBHC_BTN_RESULT); 49 result2 = snd_soc_read(codec, 50 MSM8X16_WCD_A_ANALOG_MBHC_ZDET_ELECT_RESULT); 51 pr_debug("%s: result2 = %x\n", __func__, result2); 52 53 is_pa_on = snd_soc_read(codec, 54 MSM8X16_WCD_A_ANALOG_RX_HPH_CNP_EN) & 55 0x30; 56 57 /* 58 * instead of hogging system by contineous polling, wait for 59 * sometime and re-check stop request again. 60 */ 61 msleep(180); 62 if ((!(result2 & 0x01)) && (!is_pa_on)) { 63 /*再次检测耳机脚插入,有没有错位,如果检测次数小于4次,就一直检测, 64 如果大于四次,就判定当前类型为MBHC_PLUG_TYPE_GND_MIC_SWAP反了, 65 如果等于四次,如果外面有接兼容欧标美标耳机的电路,就调接口函数去校准结果,兼容欧标美标耳机 66 */ 67 if (wcd_check_cross_conn(mbhc)) { 68 pt_gnd_mic_swap_cnt++; 69 no_gnd_mic_swap_cnt = 0; 70 if (pt_gnd_mic_swap_cnt < 71 GND_MIC_SWAP_THRESHOLD) { 72 continue; 73 } else if (pt_gnd_mic_swap_cnt > 74 GND_MIC_SWAP_THRESHOLD) { 75 /* 76 * This is due to GND/MIC switch didn't 77 * work, Report unsupported plug. 78 */ 79 pr_debug("%s: switch didnt work\n", 80 __func__); 81 plug_type = MBHC_PLUG_TYPE_GND_MIC_SWAP; 82 goto report; 83 } else { 84 plug_type = MBHC_PLUG_TYPE_GND_MIC_SWAP; 85 } 86 } else { /*如果没有错位现象,就将耳机类型设置为MBHC_PLUG_TYPE_HEADSET*/ 87 no_gnd_mic_swap_cnt++; 88 pt_gnd_mic_swap_cnt = 0; 89 plug_type = MBHC_PLUG_TYPE_HEADSET; 90 if (no_gnd_mic_swap_cnt < 91 GND_MIC_SWAP_THRESHOLD) { 92 continue; 93 } else { 94 no_gnd_mic_swap_cnt = 0; 95 } 96 } 97 } 98 /*如果判定为gnd和mic反了,调用接口函数去兼容欧标美标耳机*/ 99 if ((pt_gnd_mic_swap_cnt == GND_MIC_SWAP_THRESHOLD) && 100 (plug_type == MBHC_PLUG_TYPE_GND_MIC_SWAP)) { 101 /* 102 * if switch is toggled, check again, 103 * otherwise report unsupported plug 104 */ 105 if (mbhc->mbhc_cfg->swap_gnd_mic && 106 mbhc->mbhc_cfg->swap_gnd_mic(codec)) { 107 pr_debug("%s: US_EU gpio present,flip switch\n" 108 , __func__); 109 continue; 110 } 111 } 112 113 /*判定为高阻抗耳机*/ 114 if (result2 == 1) { 115 pr_debug("%s: cable is extension cable\n", __func__); 116 plug_type = MBHC_PLUG_TYPE_HIGH_HPH; 117 wrk_complete = true; 118 } else { 119 /*重新判定当前耳机类型是四段耳机,如果之前判定的不是四段的,这里就去上报四段键值*/ 120 pr_debug("%s: cable might be headset: %d\n", __func__, 121 plug_type); 122 if (!(plug_type == MBHC_PLUG_TYPE_GND_MIC_SWAP)) { 123 plug_type = MBHC_PLUG_TYPE_HEADSET; 124 /* 125 * Report headset only if not already reported 126 * and if there is not button press without 127 * release 128 */ 129 if (mbhc->current_plug != 130 MBHC_PLUG_TYPE_HEADSET && 131 !mbhc->btn_press_intr) { 132 pr_debug("%s: cable is headset\n", 133 __func__); 134 goto report; 135 } 136 } 137 wrk_complete = false; 138 } 139 } 140 /*判定为三段耳机*/ 141 if (!wrk_complete && mbhc->btn_press_intr) { 142 pr_debug("%s: Can be slow insertion of headphone\n", __func__); 143 wcd_cancel_btn_work(mbhc); 144 plug_type = MBHC_PLUG_TYPE_HEADPHONE; 145 } 146 /* 147 * If plug_tye is headset, we might have already reported either in 148 * detect_plug-type or in above while loop, no need to report again 149 */ 150 if (!wrk_complete && plug_type == MBHC_PLUG_TYPE_HEADSET) { 151 pr_debug("%s: Headset already reported\n", __func__); 152 goto enable_supply; 153 } 154 155 if (plug_type == MBHC_PLUG_TYPE_HIGH_HPH && 156 (!det_extn_cable_en)) { 157 if (wcd_is_special_headset(mbhc)) { 158 pr_debug("%s: Special headset found %d\n", 159 __func__, plug_type); 160 plug_type = MBHC_PLUG_TYPE_HEADSET; 161 goto report; 162 } 163 } 164 165 report: 166 pr_debug("%s: Valid plug found, plug type %d wrk_cmpt %d btn_intr %d\n", 167 __func__, plug_type, wrk_complete, 168 mbhc->btn_press_intr); 169 WCD_MBHC_RSC_LOCK(mbhc); 170 wcd_mbhc_find_plug_and_report(mbhc, plug_type); 171 WCD_MBHC_RSC_UNLOCK(mbhc); 172 enable_supply: 173 /*根据耳机的类型,设置micbias电压的状态*/ 174 wcd_enable_mbhc_supply(mbhc, plug_type); 175 exit: 176 micbias1 = (snd_soc_read(codec, MSM8X16_WCD_A_ANALOG_MICB_1_EN) & 0x80); 177 micbias2 = (snd_soc_read(codec, MSM8X16_WCD_A_ANALOG_MICB_2_EN) & 0x80); 178 if (mbhc->mbhc_cb && mbhc->mbhc_cb->set_cap_mode) 179 mbhc->mbhc_cb->set_cap_mode(codec, micbias1, micbias2); 180 wcd9xxx_spmi_unlock_sleep(); 181 pr_debug("%s: leave\n", __func__); 182 }
wcd_correct_swch_plug()校准耳机类型函数:继续检测耳机插入是否有错位,当检测达到四次,调度接口函数,去兼容欧标美标耳机,大于四次,判定耳机插反,设置类型为MBHC_PLUG_TYPE_GND_MIC_SWAP,
检测耳机类型是否为高阻抗耳机MBHC_PLUG_TYPE_HIGH_HPH,检测耳机类型是否为三段耳机MBHC_PLUG_TYPE_HEADPHONE,最后上报响应的input事件,设置micbias电压的状态。
这里micbias电压是通过wcd_enable_mbhc_supply()设置的:
1 static void wcd_enable_mbhc_supply(struct wcd_mbhc *mbhc, 2 enum wcd_mbhc_plug_type plug_type) 3 { 4 /* 5 * Do not disable micbias if recording is going on or 6 * headset is inserted on the other side of the extn 7 * cable. If headset has been detected current source 8 * needs to be kept enabled for button detection to work. 9 * If the accessory type is invalid or unsupported, we 10 * dont need to enable either of them. 11 */ 12 if (det_extn_cable_en && mbhc->is_extn_cable) { 13 if (plug_type == MBHC_PLUG_TYPE_HEADPHONE || 14 plug_type == MBHC_PLUG_TYPE_HEADSET) 15 wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_MB); 16 } else { 17 if (plug_type == MBHC_PLUG_TYPE_HEADSET) { 18 if (mbhc->is_hs_recording) 19 wcd_enable_curr_micbias(mbhc, 20 WCD_MBHC_EN_MB); 21 else if ((test_bit(WCD_MBHC_EVENT_PA_HPHL, 22 &mbhc->event_state)) || 23 (test_bit(WCD_MBHC_EVENT_PA_HPHR, 24 &mbhc->event_state))) 25 wcd_enable_curr_micbias(mbhc, 26 WCD_MBHC_EN_PULLUP); 27 else 28 wcd_enable_curr_micbias(mbhc, 29 WCD_MBHC_EN_CS); 30 } else if (plug_type == MBHC_PLUG_TYPE_HEADPHONE) { 31 wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_CS); 32 } else { 33 wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_NONE); 34 } 35 } 36 }
micbias电压有四种状态:
WCD_MBHC_EN_CS: 关闭micbias2电压
WCD_MBHC_EN_MB: 打开micbias电压
WCD_MBHC_EN_PULLUP: 打开micbias电压,并设置成PULL_UP状态
WCD_MBHC_EN_NONE: 关闭micbias电压
如果micbias外部接有电容,耳机类型是三段和四段耳机,设置micbias状态为WCD_MBHC_EN_MB
如果没有外接电容,耳机类型是四段耳机的时候,如果正在录音,设置micbias状态为WCD_MBHC_EN_MB, 如果左右声道PA在工作,即正在播放音乐,设置为WCD_MBHC_EN_PULLUP,
否则,设置状态为WCD_MBHC_EN_CS。
如果没有外接电容,耳机类型是三段耳机, 设置Micbias电压状态为WCD_MBHC_EN_CS
其他状态设置为WCD_MBHC_EN_NONE
三。以上过程,可以引用高通文档中的流程图:
上面有一个地方可以注意下,可以通过调节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电压,来进行兼容。
我们总结下,一般在调试高通的耳机功能的时候,我们需要更改的东西:
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计算出最后的阀值。