记录高通msm8909耳机检测机制,相关代码
kernel/arch/arm/boot/dts/qcom/msm8909-qrd-skuc.dtsi
sound { compatible = "qcom,msm8x16-audio-codec"; qcom,model = "msm8909-skuc-snd-card"; qcom,msm-snd-card-id = <0>; qcom,msm-codec-type = "internal"; qcom,msm-ext-pa = "primary"; qcom,msm-mclk-freq = <9600000>; qcom,msm-mbhc-hphl-swh = <0>; qcom,msm-mbhc-gnd-swh = <0>; qcom,msm-hs-micbias-type = "internal"; qcom,msm-micbias1-ext-cap; qcom,msm-micbias2-ext-cap; qcom,audio-routing = "RX_BIAS", "MCLK", "SPK_RX_BIAS", "MCLK", "INT_LDO_H", "MCLK", "MIC BIAS Internal1", "Handset Mic", "MIC BIAS Internal2", "Headset Mic", "MIC BIAS Internal3", "Secondary Mic", "AMIC1", "MIC BIAS Internal1", "AMIC2", "MIC BIAS Internal2", "AMIC3", "MIC BIAS Internal3"; pinctrl-names = "cdc_lines_act", "cdc_lines_sus"; pinctrl-0 = <&cdc_pdm_lines_act>; pinctrl-1 = <&cdc_pdm_lines_sus>; asoc-platform = <&pcm0>, <&pcm1>, <&voip>, <&voice>, <&loopback>, <&compress>, <&hostless>, <&afe>, <&lsm>, <&routing>, <&lpa>, <&voice_svc>; asoc-platform-names = "msm-pcm-dsp.0", "msm-pcm-dsp.1", "msm-voip-dsp", "msm-pcm-voice", "msm-pcm-loopback", "msm-compress-dsp", "msm-pcm-hostless", "msm-pcm-afe", "msm-lsm-client", "msm-pcm-routing", "msm-pcm-lpa", "msm-voice-svc"; asoc-cpu = <&dai_pri_auxpcm>, <&dai_hdmi>, <&dai_mi2s0>, <&dai_mi2s1>, <&dai_mi2s2>, <&dai_mi2s3>, <&sb_0_rx>, <&sb_0_tx>, <&sb_1_rx>, <&sb_1_tx>, <&sb_3_rx>, <&sb_3_tx>, <&sb_4_rx>, <&sb_4_tx>, <&bt_sco_rx>, <&bt_sco_tx>, <&int_fm_rx>, <&int_fm_tx>, <&afe_pcm_rx>, <&afe_pcm_tx>, <&afe_proxy_rx>, <&afe_proxy_tx>, <&incall_record_rx>, <&incall_record_tx>, <&incall_music_rx>, <&incall_music_2_rx>; asoc-cpu-names = "msm-dai-q6-auxpcm.1", "msm-dai-q6-hdmi.8", "msm-dai-q6-mi2s.0", "msm-dai-q6-mi2s.1", &bsp; "msm-dai-q6-mi2s.2", "msm-dai-q6-mi2s.3", "msm-dai-q6-dev.16384", "msm-dai-q6-dev.16385", "msm-dai-q6-dev.16386", "msm-dai-q6-dev.16387", "msm-dai-q6-dev.16390", "msm-dai-q6-dev.16391", "msm-dai-q6-dev.16392", "msm-dai-q6-dev.16393", "msm-dai-q6-dev.12288", "msm-dai-q6-dev.12289", "msm-dai-q6-dev.12292", "msm-dai-q6-dev.12293", "msm-dai-q6-dev.224", "msm-dai-q6-dev.225", "msm-dai-q6-dev.241", "msm-dai-q6-dev.240", "msm-dai-q6-dev.32771", "msm-dai-q6-dev.32772", "msm-dai-q6-dev.32773", "msm-dai-q6-dev.32770"; asoc-codec = <&stub_codec>, <&pm8909_conga_dig>; asoc-codec-names = "msm-stub-codec.1", "tombak_codec"; }; kernel/arch/arm/boot/dts/qcom/msm-pm8909.dtsi
pm8909_conga_dig: 8909_wcd_codec@f000 { compatible = "qcom,msm8x16_wcd_codec"; reg = <0xf000 0x100>; interrupt-parent = <&spmi_bus>; interrupts = <0x1 0xf0 0x0>, <0x1 0xf0 0x1>, <0x1 0xf0 0x2>, <0x1 0xf0 0x3>, <0x1 0xf0 0x4>, <0x1 0xf0 0x5>, <0x1 0xf0 0x6>, <0x1 0xf0 0x7>; interrupt-names = "spk_cnp_int", "spk_clip_int", "spk_ocp_int", "ins_rem_det1", "but_rel_det", "but_press_det", "ins_rem_det", "mbhc_int"; cdc-vdda-cp-supply = <&pm8909_s2>; qcom,cdc-vdda-cp-voltage = <1800000 2200000>; qcom,cdc-vdda-cp-current = <500000>; cdc-vdda-h-supply = <&pm8909_l5>; qcom,cdc-vdda-h-voltage = <1800000 1800000>; qcom,cdc-vdda-h-current = <10000>; cdc-vdd-px-supply = <&pm8909_l5>; qcom,cdc-vdd-px-voltage = <1800000 1800000>; qcom,cdc-vdd-px-current = <5000>; cdc-vdd-pa-supply = <&pm8909_s2>; qcom,cdc-vdd-pa-voltage = <1800000 2200000>; qcom,cdc-vdd-pa-current = <260000>; cdc-vdd-mic-bias-supply = <&pm8909_l13>; qcom,cdc-vdd-mic-bias-voltage = <3075000 3075000>; qcom,cdc-vdd-mic-bias-current = <5000>; qcom,cdc-mclk-clk-rate = <9600000>; qcom,cdc-static-supplies = "cdc-vdda-h", "cdc-vdd-px", "cdc-vdd-pa", "cdc-vdda-cp"; qcom,cdc-on-demand-supplies = "cdc-vdd-mic-bias"; }; pm8909_conga_analog: 8909_wcd_codec@f100 { compatible = "qcom,msm8x16_wcd_codec"; reg = <0xf100 0x100>; interrupt-parent = <&spmi_bus>; interrupts = <0x1 0xf1 0x0>, <0x1 0xf1 0x1>, <0x1 0xf1 0x2>, <0x1 0xf1 0x3>, <0x1 0xf1 0x4>, <0x1 0xf1 0x5>; interrupt-names = "ear_ocp_int", "hphr_ocp_int", "hphl_ocp_det", "ear_cnp_int", "hphr_cnp_int", "hphl_cnp_int"; };
msm8909的耳机接口在pm8909上
耳机的基本知识http://yunzhi.github.io/headset_knowledge
HPH (耳机简写headphone)
MIC_BIAS (耳机麦克风偏置电压)
MICx_IN(耳机的mic输入口)
HPH_L(耳机左声道)
HPH_R(耳机右声道)
HS_DET(耳机检测脚) headset_detect
HPH_REF(耳机参考地) 一些接耳机通道的外置PA,如果耳机地不接这里,接主板的地,可能通话过中会电流声
NC 常关耳机插座(hp_det和hph_l短路,插入耳机,hp_det和hph_l断路)
NO 常开耳机插座(这种类型常见,hp_det和hph_l断路,插入耳机,hph_l接了一个喇叭(小电阻)到地,相当有hp_det和hph_l短路)
耳机的左/右声道到地电阻大概10欧姆,mic到地大概1K欧姆
这里是no的耳机插座,设置
qcom,msm-mbhc-hphl-swh = <0>;
kernel/sound/soc/codecs/msm8x16-wcd.c
static const struct wcd_mbhc_intr intr_ids = { .mbhc_sw_intr = MSM8X16_WCD_IRQ_MBHC_HS_DET, .mbhc_btn_press_intr = MSM8X16_WCD_IRQ_MBHC_PRESS, .mbhc_btn_release_intr = MSM8X16_WCD_IRQ_MBHC_RELEASE, .mbhc_hs_ins_intr = MSM8X16_WCD_IRQ_MBHC_INSREM_DET1, .mbhc_hs_rem_intr = MSM8X16_WCD_IRQ_MBHC_INSREM_DET, .hph_left_ocp = MSM8X16_WCD_IRQ_HPHL_OCP, .hph_right_ocp = MSM8X16_WCD_IRQ_HPHR_OCP, };
static int msm8x16_wcd_codec_probe(struct snd_soc_codec *codec) { struct msm8x16_wcd_priv *msm8x16_wcd_priv; struct msm8x16_wcd *msm8x16_wcd; int i, ret; dev_dbg(codec->dev, "%s()\n", __func__); msm8x16_wcd_priv = kzalloc(sizeof(struct msm8x16_wcd_priv), GFP_KERNEL); if (!msm8x16_wcd_priv) { dev_err(codec->dev, "Failed to allocate private data\n"); return -ENOMEM; } for (i = 0; i < NUM_DECIMATORS; i++) { tx_hpf_work[i].msm8x16_wcd = msm8x16_wcd_priv; tx_hpf_work[i].decimator = i + 1; INIT_DELAYED_WORK(&tx_hpf_work[i].dwork, tx_hpf_corner_freq_callback); } codec->control_data = dev_get_drvdata(codec->dev); snd_soc_codec_set_drvdata(codec, msm8x16_wcd_priv); msm8x16_wcd_priv->codec = codec; /* codec resmgr module init */ msm8x16_wcd = codec->control_data; msm8x16_wcd->dig_base = ioremap(MSM8X16_DIGITAL_CODEC_BASE_ADDR, MSM8X16_DIGITAL_CODEC_REG_SIZE); if (msm8x16_wcd->dig_base == NULL) { dev_err(codec->dev, "%s ioremap failed\n", __func__); kfree(msm8x16_wcd_priv); return -ENOMEM; } msm8x16_wcd_priv->spkdrv_reg = wcd8x16_wcd_codec_find_regulator(codec->control_data, MSM89XX_VDD_SPKDRV_NAME); msm8x16_wcd_priv->pmic_rev = snd_soc_read(codec, MSM8X16_WCD_A_DIGITAL_REVISION1); msm8x16_wcd_priv->codec_version = snd_soc_read(codec, MSM8X16_WCD_A_DIGITAL_PERPH_SUBTYPE); if (msm8x16_wcd_priv->codec_version == CONGA) { dev_dbg(codec->dev, "%s :Conga REV: %d\n", __func__, msm8x16_wcd_priv->codec_version); msm8x16_wcd_priv->ext_spk_boost_set = true; } else { dev_dbg(codec->dev, "%s :PMIC REV: %d\n", __func__, msm8x16_wcd_priv->pmic_rev); } /* * set to default boost option BOOST_SWITCH, user mixer path can change * it to BOOST_ALWAYS or BOOST_BYPASS based on solution chosen. */ msm8x16_wcd_priv->boost_option = BOOST_SWITCH; msm8x16_wcd_dt_parse_boost_info(codec); msm8x16_wcd_set_boost_v(codec); snd_soc_add_codec_controls(codec, impedance_detect_controls, ARRAY_SIZE(impedance_detect_controls)); msm8x16_wcd_bringup(codec); msm8x16_wcd_codec_init_reg(codec); msm8x16_wcd_update_reg_defaults(codec); wcd9xxx_spmi_set_codec(codec); msm8x16_wcd_priv->on_demand_list[ON_DEMAND_MICBIAS].supply = wcd8x16_wcd_codec_find_regulator( codec->control_data, on_demand_supply_name[ON_DEMAND_MICBIAS]); atomic_set(&msm8x16_wcd_priv->on_demand_list[ON_DEMAND_MICBIAS].ref, 0); BLOCKING_INIT_NOTIFIER_HEAD(&msm8x16_wcd_priv->notifier); msm8x16_wcd_priv->fw_data = kzalloc(sizeof(*(msm8x16_wcd_priv->fw_data)) , GFP_KERNEL); if (!msm8x16_wcd_priv->fw_data) { dev_err(codec->dev, "Failed to allocate fw_data\n"); iounmap(msm8x16_wcd->dig_base); kfree(msm8x16_wcd_priv); return -ENOMEM; } set_bit(WCD9XXX_MBHC_CAL, msm8x16_wcd_priv->fw_data->cal_bit); ret = wcd_cal_create_hwdep(msm8x16_wcd_priv->fw_data, WCD9XXX_CODEC_HWDEP_NODE, codec); if (ret < 0) { dev_err(codec->dev, "%s hwdep failed %d\n", __func__, ret); iounmap(msm8x16_wcd->dig_base); kfree(msm8x16_wcd_priv->fw_data); kfree(msm8x16_wcd_priv); return ret; } wcd_mbhc_init(&msm8x16_wcd_priv->mbhc, codec, &mbhc_cb, &intr_ids, true); msm8x16_wcd_priv->mclk_enabled = false; msm8x16_wcd_priv->clock_active = false; msm8x16_wcd_priv->config_mode_active = false; /* Set initial MICBIAS voltage level */ msm8x16_wcd_set_micb_v(codec); /* Set initial cap mode */ msm8x16_wcd_configure_cap(codec, false, false); registered_codec = codec; modem_state_notifier = subsys_notif_register_notifier("modem", &modem_state_notifier_block); if (!modem_state_notifier) { dev_err(codec->dev, "Failed to register modem state notifier\n" ); iounmap(msm8x16_wcd->dig_base); kfree(msm8x16_wcd_priv->fw_data); kfree(msm8x16_wcd_priv); registered_codec = NULL; return -ENOMEM; } return 0; } kernel/sound/soc/codecs/wcd-mbhc-v2.c
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, bool impedance_det_en) { int ret = 0; int hph_swh = 0; int gnd_swh = 0; struct snd_soc_card *card = codec->card; const char *hph_switch = "qcom,msm-mbhc-hphl-swh"; const char *gnd_switch = "qcom,msm-mbhc-gnd-swh"; fb_notifier.notifier_call = fb_notifierier_callback; ret = fb_register_client(&fb_notifier); if (ret) printk( KERN_CRIT"%s: micbias Unable to register fb_notifier: %d\n", __func__,ret); ret = of_property_read_u32(card->dev->of_node, hph_switch, &hph_swh); if (ret) { dev_err(card->dev, "%s: missing %s in dt node\n", __func__, hph_switch); goto err; } ret = of_property_read_u32(card->dev->of_node, gnd_switch, &gnd_swh); if (ret) { dev_err(card->dev, "%s: missing %s in dt node\n", __func__, gnd_switch); goto err; } mbhc->in_swch_irq_handler = false; mbhc->current_plug = MBHC_PLUG_TYPE_NONE; mbhc->is_btn_press = false; mbhc->codec = codec; mbhc->intr_ids = mbhc_cdc_intr_ids; mbhc->impedance_detect = impedance_det_en; mbhc->hphl_swh = hph_swh; mbhc->gnd_swh = gnd_swh; mbhc->micbias_enable = false; mbhc->mbhc_cb = mbhc_cb; mbhc->btn_press_intr = false; mbhc->is_hs_recording = false; mbhc->is_extn_cable = false; mbhc->skip_imped_detection = false; if (mbhc->intr_ids == NULL) { pr_err("%s: Interrupt mapping not provided\n", __func__); return -EINVAL; } if (mbhc->headset_jack.jack == NULL) { ret = snd_soc_jack_new(codec, "Headset Jack", WCD_MBHC_JACK_MASK, &mbhc->headset_jack); if (ret) { pr_err("%s: Failed to create new jack\n", __func__); return ret; } ret = snd_soc_jack_new(codec, "Button Jack", WCD_MBHC_JACK_BUTTON_MASK, &mbhc->button_jack); if (ret) { pr_err("Failed to create new jack\n"); return ret; } ret = snd_jack_set_key(mbhc->button_jack.jack, SND_JACK_BTN_0, KEY_MEDIA); if (ret) { pr_err("%s: Failed to set code for btn-0\n", __func__); return ret; } ret = snd_jack_set_key(mbhc->button_jack.jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); if (ret) { pr_err("%s: Failed to set code for btn-1:%d\n", __func__, ret); return ret; } ret = snd_jack_set_key(mbhc->button_jack.jack, SND_JACK_BTN_2, KEY_VOLUMEUP); if (ret) { pr_err("%s: Failed to set code for btn-2:%d\n", __func__, ret); return ret; } ret = snd_jack_set_key(mbhc->button_jack.jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); if (ret) { pr_err("%s: Failed to set code for btn-3:%d\n", __func__, ret); return ret; } INIT_DELAYED_WORK(&mbhc->mbhc_firmware_dwork, wcd_mbhc_fw_read); INIT_DELAYED_WORK(&mbhc->mbhc_btn_dwork, wcd_btn_lpress_fn); } /* Register event notifier */ mbhc->nblock.notifier_call = wcd_event_notify; ret = msm8x16_register_notifier(codec, &mbhc->nblock); 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 = wcd9xxx_spmi_request_irq(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 = wcd9xxx_spmi_request_irq(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 = wcd9xxx_spmi_request_irq(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 = wcd9xxx_spmi_request_irq(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; } wcd9xxx_spmi_disable_irq(mbhc->intr_ids->mbhc_hs_ins_intr); ret = wcd9xxx_spmi_request_irq(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; } wcd9xxx_spmi_disable_irq(mbhc->intr_ids->mbhc_hs_rem_intr); ret = wcd9xxx_spmi_request_irq(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 = wcd9xxx_spmi_request_irq(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: wcd9xxx_spmi_free_irq(mbhc->intr_ids->hph_left_ocp, mbhc); err_hphl_ocp_irq: wcd9xxx_spmi_free_irq(mbhc->intr_ids->mbhc_hs_rem_intr, mbhc); err_mbhc_hs_rem_irq: wcd9xxx_spmi_free_irq(mbhc->intr_ids->mbhc_hs_ins_intr, mbhc); err_mbhc_hs_ins_irq: wcd9xxx_spmi_free_irq(mbhc->intr_ids->mbhc_btn_release_intr, mbhc); err_btn_release_irq: wcd9xxx_spmi_free_irq(mbhc->intr_ids->mbhc_btn_press_intr, mbhc); err_btn_press_irq: wcd9xxx_spmi_free_irq(mbhc->intr_ids->mbhc_sw_intr, mbhc); err_mbhc_sw_irq: msm8x16_unregister_notifier(codec, &mbhc->nblock); mutex_destroy(&mbhc->codec_resource_lock); err: pr_debug("%s: leave ret %d\n", __func__, ret); return ret; } EXPORT_SYMBOL(wcd_mbhc_init); 耳机检测中断函数
static void wcd_mbhc_swch_irq_handler(struct wcd_mbhc *mbhc) { bool detection_type; bool micbias1; struct snd_soc_codec *codec = mbhc->codec; pr_debug("%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 = (snd_soc_read(codec, MSM8X16_WCD_A_ANALOG_MBHC_DET_CTL_1)) & 0x20; /* Set the detection type appropriately */ snd_soc_update_bits(codec, MSM8X16_WCD_A_ANALOG_MBHC_DET_CTL_1, 0x20, (!detection_type << 5)); 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); micbias1 = (snd_soc_read(codec, MSM8X16_WCD_A_ANALOG_MICB_1_EN) & 0x80); if ((mbhc->current_plug == MBHC_PLUG_TYPE_NONE) && detection_type) { /* Make sure MASTER_BIAS_CTL is enabled */ snd_soc_update_bits(codec, MSM8X16_WCD_A_ANALOG_MASTER_BIAS_CTL, 0x30, 0x30); snd_soc_update_bits(codec, MSM8X16_WCD_A_ANALOG_MICB_1_EN, 0x04, 0x04); if (!mbhc->mbhc_cfg->hs_ext_micbias) /* Enable Tx2 RBias if the headset * is using internal micbias*/ snd_soc_update_bits(codec, MSM8X16_WCD_A_ANALOG_MICB_1_INT_RBIAS, 0x10, 0x10); /* Remove pull down on MIC BIAS2 */ snd_soc_update_bits(codec, MSM8X16_WCD_A_ANALOG_MICB_2_EN, 0x20, 0x00); /* Enable HW FSM */ snd_soc_update_bits(codec, MSM8X16_WCD_A_ANALOG_MBHC_FSM_CTL, 0x80, 0x80); /* Apply trim if needed on the device */ if (mbhc->mbhc_cb && 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 && mbhc->mbhc_cb->enable_mb_source) mbhc->mbhc_cb->enable_mb_source(codec, true); mbhc->btn_press_intr = false; wcd_mbhc_detect_plug_type(mbhc); } else if ((mbhc->current_plug != MBHC_PLUG_TYPE_NONE) && !detection_type) { /* Disable external voltage source to micbias if present */ if (mbhc->mbhc_cb && mbhc->mbhc_cb->enable_mb_source) mbhc->mbhc_cb->enable_mb_source(codec, false); /* Disable HW FSM */ snd_soc_update_bits(codec, MSM8X16_WCD_A_ANALOG_MBHC_FSM_CTL, 0xB0, 0x00); snd_soc_update_bits(codec, MSM8X16_WCD_A_ANALOG_MICB_1_EN, 0x04, 0x00); if (mbhc->mbhc_cb && mbhc->mbhc_cb->set_cap_mode) mbhc->mbhc_cb->set_cap_mode(codec, micbias1, false); mbhc->btn_press_intr = false; if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADPHONE) { 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 */ snd_soc_update_bits(codec, MSM8X16_WCD_A_ANALOG_MICB_1_INT_RBIAS, 0x18, 0x08); snd_soc_update_bits(codec, MSM8X16_WCD_A_ANALOG_MICB_2_EN, 0x20, 0x20); wcd9xxx_spmi_disable_irq( mbhc->intr_ids->mbhc_hs_rem_intr); wcd9xxx_spmi_disable_irq( mbhc->intr_ids->mbhc_hs_ins_intr); snd_soc_update_bits(codec, MSM8X16_WCD_A_ANALOG_MBHC_DET_CTL_1, 0x01, 0x01); snd_soc_update_bits(codec, MSM8X16_WCD_A_ANALOG_MBHC_DET_CTL_2, 0x06, 0x00); wcd_mbhc_report_plug(mbhc, 0, SND_JACK_HEADSET); } else if (mbhc->current_plug == MBHC_PLUG_TYPE_HIGH_HPH) { mbhc->is_extn_cable = false; wcd9xxx_spmi_disable_irq( mbhc->intr_ids->mbhc_hs_rem_intr); wcd9xxx_spmi_disable_irq( mbhc->intr_ids->mbhc_hs_ins_intr); snd_soc_update_bits(codec, MSM8X16_WCD_A_ANALOG_MBHC_DET_CTL_1, 0x01, 0x01); snd_soc_update_bits(codec, MSM8X16_WCD_A_ANALOG_MBHC_DET_CTL_2, 0x06, 0x00); wcd_mbhc_report_plug(mbhc, 0, SND_JACK_LINEOUT); } } else if (!detection_type) { /* Disable external voltage source to micbias if present */ if (mbhc->mbhc_cb && mbhc->mbhc_cb->enable_mb_source) mbhc->mbhc_cb->enable_mb_source(codec, false); /* Disable HW FSM */ snd_soc_update_bits(codec, MSM8X16_WCD_A_ANALOG_MBHC_FSM_CTL, 0xB0, 0x00); } mbhc->in_swch_irq_handler = false; WCD_MBHC_RSC_UNLOCK(mbhc); pr_debug("%s: leave\n", __func__); } 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(wcd9xxx_spmi_lock_sleep() == false)) { pr_warn("%s: failed to hold suspend\n", __func__); r = IRQ_NONE; } else { /* Call handler */ wcd_mbhc_swch_irq_handler(mbhc); wcd9xxx_spmi_unlock_sleep(); } pr_debug("%s: leave %d\n", __func__, r); return r; } 经过一系列的检测,判断是headset,headphones等,如果是headphones,最终通过wcd_mbhc_jack_report将数据汇报上去。
static void wcd_mbhc_jack_report(struct wcd_mbhc *mbhc, struct snd_soc_jack *jack, int status, int mask) { snd_soc_jack_report_no_dapm(jack, status, mask); } 内核通用的耳机汇报函数
void snd_soc_jack_report_no_dapm(struct snd_soc_jack *jack, int status, int mask) { jack->status &= ~mask; jack->status |= status & mask; snd_jack_report(jack->jack, jack->status); } EXPORT_SYMBOL_GPL(snd_soc_jack_report_no_dapm);
void snd_jack_report(struct snd_jack *jack, int status) { int i; if (!jack) return; for (i = 0; i < ARRAY_SIZE(jack->key); i++) { int testbit = SND_JACK_BTN_0 >> i; if (jack->type & testbit) input_report_key(jack->input_dev, jack->key[i], status & testbit); } for (i = 0; i < ARRAY_SIZE(jack_switch_types); i++) { int testbit = 1 << i; if (jack->type & testbit) input_report_switch(jack->input_dev, jack_switch_types[i], status & testbit); }//分别判断具体的位,有没有相应的事件 input_sync(jack->input_dev); } EXPORT_SYMBOL(snd_jack_report); static inline void input_report_switch(struct input_dev *dev, unsigned int code, int value) { input_event(dev, EV_SW, code, !!value); } #define EV_SW 0x05 三段耳机最终汇报上去的有效数据是
5 2 1 (插头类型 耳机信号 插入/拔出)
0 0 0(同步)
static int jack_switch_types[] = { SW_HEADPHONE_INSERT, SW_MICROPHONE_INSERT, SW_LINEOUT_INSERT, SW_JACK_PHYSICAL_INSERT, SW_VIDEOOUT_INSERT, SW_LINEIN_INSERT, SW_HPHL_OVERCURRENT, SW_HPHR_OVERCURRENT, SW_UNSUPPORT_INSERT, SW_MICROPHONE2_INSERT, }; enum wcd_mbhc_plug_type { MBHC_PLUG_TYPE_INVALID = -1,//无效设备 MBHC_PLUG_TYPE_NONE,//未接入设备 MBHC_PLUG_TYPE_HEADSET,//四段耳机 MBHC_PLUG_TYPE_HEADPHONE,//三段耳机 MBHC_PLUG_TYPE_HIGH_HPH,//高阻抗耳机 MBHC_PLUG_TYPE_GND_MIC_SWAP,//欧美标标志位 };
当然也可以手工通过sendevent 发送事件,systemui就能显示耳机接入的图标
getevent -i获取Headset Jack对应的input,假设是/dev/input/event2,
则可以通过如下方法模拟耳机接入和拔出(getevent -i找到具体的eventn)
sendevent /dev/input/event2 5 2 1 sendevent /dev/input/event2 0 0 0 或者拨出耳机
sendevent /dev/input/event2 5 2 0 sendevent /dev/input/event2 0 0 0
关于线控耳机原理可参考http://www.guokr.com/post/615349/