具有电阻、电感和电容的电路里,对交流电所起的阻碍作用叫做阻抗。阻抗常用Z表示。阻抗由电阻、感抗和容抗三者组成,但不是三者简单相加,阻抗的单位是欧。
耳机的阻抗是交流阻抗的简称,阻抗越小,耳机越容易出声、越容易驱动。电视等有耳机插孔输出的机器上,一般使用中高阻抗的耳机比较适宜。低阻抗的耳机一般比较容易推动,因此MP3等便携、省电的机器应选择低阻抗耳机。当然,阻抗越高的耳机搭配输出功率大的音源时声音效果更好。
耳机的电路设计是保证插入耳机时,让功放处于MUTE状态,需要专门的电路来处理;如果是不带MUTE的功放,接入耳机时要断开喇叭,由插座完成。和耳机类似的是PC音频输入,该电路设计必须保证攻防输入接AUDIO IN时,传输AUDIO信号;不接入信号时跟地联通。
高通平台中,默认使用内部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
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计算出最后的阀值。
//当耳机阻抗为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