1. 程式人生 > >高通平臺的耳機檢測(msm8909)

高通平臺的耳機檢測(msm8909)

          記錄高通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",
				"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: [email protected] {
			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: 
[email protected]
{ 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上

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

相關推薦

平臺耳機插拔檢測

高通耳機的插拔檢測需要配置NC或NO,並且使用匹配的耳機(歐標,美標)。 歐標,美標 市面的耳機有兩種標準,即歐標(也叫國標)和美標。高通低端晶片無法相容,只能支援一種標準,具體是支援支援哪種標準是由耳機底座決定的。 歐標,美標的本質區別是地和mic的位置不同,如下圖。

平臺耳機檢測(msm8909)

          記錄高通msm8909耳機檢測的機制,相關程式碼 kernel/arch/arm/boot/dts/qcom/msm8909-qrd-skuc.dtsi sound { compatible = "qcom,msm8x16-audio-codec

基於Android7.1 8953 平臺下零死角玩轉裝置樹DTS

【基於Android7.1 8953 高通平臺下零死角玩轉裝置樹DTS】 更新內容:  【創科之龍_安卓開發】第01課_為什麼引用linux裝置樹和對比優勢  【創科之龍_安卓開發】第02課_如何在Linux-3.x核心

平臺啟動log概述(PBL log、sbl1 log、kernel log)【轉】

本文轉自:https://blog.csdn.net/RadianceBlau/article/details/78416776?utm_source=blogxgwz9 高通平臺啟動log概述(PBL log、sbl1 log、kernel log)在嵌入式linux的除錯過程中log有著至關重要的地位,

平臺讀寫nv總結【轉】

本文轉載自:https://blog.csdn.net/suofeng12345/article/details/52713993 一,引言      1. 什麼是NV      &nbs

平臺 ramdump-parser 簡介

ramdump 是什麼?簡單來說就是把系統memory中的某一個時間點的的資料資訊通過一定手段取出來儲存起來的記憶體崩潰檔案,屬於ELF檔案格式。 當系統發生致命錯誤無法恢復的時候,主動觸發抓取ramdump把異常現場保留下來供離線分析定位問題是一種非常重要的高階除錯手段。 高

通過平臺簡單總結的許可權問題

 android 5.x開始,引入了非常嚴格的selinux許可權管理機制,我們經常會遇到因為selinux許可權問題造成的各種avc denied困擾。有時候我們在添加了一些驅動或應用的時候,發現不能用,但是程式本身查不出問題來,那就要抓log看一下了,很多時候就是因為沒有新增許可權,下

平臺電池曲線

跟電池相關的一些名詞: FCC Full-Charge Capacity 滿電荷電量 UC Remaining Capacity RC 剩餘電量 CC Coulumb Counter 電量計 UUC Unusable Capacity 不可用電量 PC Percentage

平臺SPI外設的片選訊號配置

spi master 和spi device有各自的dts配置: 這是spi master的配置示例,也就是spi控制器 這是一個spi device的配置示例,也就是spi外設(從裝置) 一個spi控制器上可以連線多個從裝置,在任何時刻只有一個從裝置可以通

平臺新增或者移植一個完整的camera

OV8865 1、kernel部分: A、kernel_driver: 把驅動檔案ov8865_qtech_f8865ac.c 放到kernel/drivers/media/platform/msm/camera_v2/sensor/目錄下 B、kernel_dt

平臺GPIO模擬PWM控制背光

    很多時候由於節省硬體資源,降低成本,會把PWM控制晶片去掉或者是改做它用,導致當我們想用PWM方式控制背光時只能使用帶有clk功能的GPIO口。本篇文件就來講解下如何使用GPIO模擬PWM功能進行背光的控制。本文以MSM8909為例。 一、選取GPIO口並進行配置

平臺 efs.mbn的製作與修改

1.修改需要修改的mbn相關檔案 如MDM9607/trunk/modem_proc/mcfg/mcfg_gen/genneric/China/CT下的 2.EFS相關程式碼修改 開啟相關巨集定義 #define FEATURE_EFS_NAND_FACTORY_STAR

[Android]平臺BootLoader啟動流程

一、什麼是BootLoader BootLoader程式碼是晶片復位後,進入作業系統之前執行的一段程式碼。主要用於完成由硬體啟動到作業系統啟動的過渡,從而為作業系統提供基本的執行環境。 BootLoder主要的啟動流程可以概括為:PBL階段、SBL階段、LK階段。之後會載入並啟動kern

平臺framework,hal,kernel開啟log【轉】

本文轉載自:https://blog.csdn.net/u010164190/article/details/78625636 1 1.Add framework log 2 #define LOG_NDEBUG 0 3 2.Add hal log 4 #define LOG_NDEBU

Android平臺下編譯時能生成(拷貝)預編譯的so到system的lib目錄

  參考hardware\qcom\display\libcopybit 通過編譯log可以知道編譯到這裡,生成的copybit.msm8937.so在out\target\product\msm8937_64\system\lib\hw下。libcopybit\Andr

平臺 UART log 開啟方法

1  配置kernel  DTS      例如:  kernel\arch\arm\boot\dts\qcom\msm8909-mtp.dtsi,  配置status屬性 okay.   同時檢查DTS 確認UART PIN  沒有配作他用      &blsp1

平臺bootloader裡面串列埠log輸出配置方法

1:在modem端將相應的gpio設定成相應功能。 2:在/lk/project/xxx.mk開啟uart輸出log功能。 3:在platform/msm_shared/uart.c中定義uart幾輸出log, #if PLATFORM_MSM7X30 static uns

[平臺小米4]的電源管理配置學習

1.溫控調頻 /system/etc/thermal-engine-8974.conf sampling 5000 [CPU0_MONITOR] algo_type monitor sensor cp

平臺中用devicetree註冊裝置及驅動匹配

1、在裝置樹檔案(.dts)中的相應節點下新增要註冊裝置的節點         &soc {                     ......                     plf_char_dev: mytest,plf_char_dev {

Android 平臺開機logo的修改

修改開機logo 修改開機logo有兩種方法,一種直接去改c語言程式碼,第二種替換圖片用python生成splash。第一種方法我沒試過,感覺挺麻煩的,還有解析度限制,超過多少解析度就不能用第一種方法。 修改的檔案路徑LINUX/android/boota