耳機阻抗
具有電阻、電感和電容的電路里,對交流電所起的阻礙作用叫做阻抗。阻抗常用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