一.
在高通平台中,默認使用內部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計算出最后的閥值。