diff --git a/Documentation/devicetree/bindings/sound/qcom,wcd934x.yaml b/Documentation/devicetree/bindings/sound/qcom,wcd934x.yaml index e8f716b5f875..9b225dbf8b79 100644 --- a/Documentation/devicetree/bindings/sound/qcom,wcd934x.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,wcd934x.yaml @@ -77,6 +77,31 @@ properties: minimum: 1800000 maximum: 2850000 + qcom,hphl-jack-type-normally-closed: + description: Indicates that HPHL jack switch type is normally closed + type: boolean + + qcom,ground-jack-type-normally-closed: + description: Indicates that Headset Ground switch type is normally closed + type: boolean + + qcom,mbhc-headset-vthreshold-microvolt: + description: Voltage threshold value for headset detection + minimum: 0 + maximum: 2850000 + + qcom,mbhc-headphone-vthreshold-microvolt: + description: Voltage threshold value for headphone detection + minimum: 0 + maximum: 2850000 + + qcom,mbhc-buttons-vthreshold-microvolt: + description: + Array of 8 Voltage threshold values corresponding to headset + button0 - button7 + minItems: 8 + maxItems: 8 + clock-output-names: const: mclk @@ -159,6 +184,11 @@ examples: qcom,micbias2-microvolt = <1800000>; qcom,micbias3-microvolt = <1800000>; qcom,micbias4-microvolt = <1800000>; + qcom,hphl-jack-type-normally-closed; + qcom,ground-jack-type-normally-closed; + qcom,mbhc-buttons-vthreshold-microvolt = <75000 150000 237000 500000 500000 500000 500000 500000>; + qcom,mbhc-headset-vthreshold-microvolt = <1700000>; + qcom,mbhc-headphone-vthreshold-microvolt = <50000>; clock-names = "extclk"; clocks = <&rpmhcc 2>; diff --git a/include/linux/mfd/wcd934x/registers.h b/include/linux/mfd/wcd934x/registers.h index bb8d2e276668..76a943c83c63 100644 --- a/include/linux/mfd/wcd934x/registers.h +++ b/include/linux/mfd/wcd934x/registers.h @@ -18,6 +18,8 @@ #define WCD934X_EFUSE_SENSE_STATE_DEF 0x10 #define WCD934X_EFUSE_SENSE_EN_MASK BIT(0) #define WCD934X_EFUSE_SENSE_ENABLE BIT(0) +#define WCD934X_CHIP_TIER_CTRL_EFUSE_VAL_OUT1 0x002a +#define WCD934X_CHIP_TIER_CTRL_EFUSE_VAL_OUT2 0x002b #define WCD934X_CHIP_TIER_CTRL_EFUSE_VAL_OUT14 0x0037 #define WCD934X_CHIP_TIER_CTRL_EFUSE_VAL_OUT15 0x0038 #define WCD934X_CHIP_TIER_CTRL_EFUSE_STATUS 0x0039 @@ -103,21 +105,58 @@ #define WCD934X_ANA_AMIC3 0x0610 #define WCD934X_ANA_AMIC4 0x0611 #define WCD934X_ANA_MBHC_MECH 0x0614 +#define WCD934X_MBHC_L_DET_EN_MASK BIT(7) +#define WCD934X_MBHC_L_DET_EN BIT(7) +#define WCD934X_MBHC_GND_DET_EN_MASK BIT(6) +#define WCD934X_MBHC_MECH_DETECT_TYPE_MASK BIT(5) +#define WCD934X_MBHC_MECH_DETECT_TYPE_INS 1 +#define WCD934X_MBHC_HPHL_PLUG_TYPE_MASK BIT(4) +#define WCD934X_MBHC_HPHL_PLUG_TYPE_NO 1 +#define WCD934X_MBHC_GND_PLUG_TYPE_MASK BIT(3) +#define WCD934X_MBHC_GND_PLUG_TYPE_NO 1 +#define WCD934X_MBHC_HSL_PULLUP_COMP_EN BIT(2) +#define WCD934X_MBHC_HSG_PULLUP_COMP_EN BIT(1) +#define WCD934X_MBHC_HPHL_100K_TO_GND_EN BIT(0) #define WCD934X_ANA_MBHC_ELECT 0x0615 +#define WCD934X_ANA_MBHC_BIAS_EN_MASK BIT(0) +#define WCD934X_ANA_MBHC_BIAS_EN BIT(0) #define WCD934X_ANA_MBHC_ZDET 0x0616 #define WCD934X_ANA_MBHC_RESULT_1 0x0617 #define WCD934X_ANA_MBHC_RESULT_2 0x0618 #define WCD934X_ANA_MBHC_RESULT_3 0x0619 +#define WCD934X_ANA_MBHC_BTN0 0x061a +#define WCD934X_VTH_MASK GENMASK(7, 2) +#define WCD934X_ANA_MBHC_BTN1 0x061b +#define WCD934X_ANA_MBHC_BTN2 0x061c +#define WCD934X_ANA_MBHC_BTN3 0x061d +#define WCD934X_ANA_MBHC_BTN4 0x061e +#define WCD934X_ANA_MBHC_BTN5 0x061f +#define WCD934X_ANA_MBHC_BTN6 0x0620 +#define WCD934X_ANA_MBHC_BTN7 0x0621 +#define WCD934X_MBHC_BTN_VTH_MASK GENMASK(7, 2) #define WCD934X_ANA_MICB1 0x0622 #define WCD934X_MICB_VAL_MASK GENMASK(5, 0) #define WCD934X_ANA_MICB_EN_MASK GENMASK(7, 6) +#define WCD934X_MICB_DISABLE 0 +#define WCD934X_MICB_ENABLE 1 +#define WCD934X_MICB_PULL_UP 2 +#define WCD934X_MICB_PULL_DOWN 3 #define WCD934X_ANA_MICB_PULL_UP 0x80 #define WCD934X_ANA_MICB_ENABLE 0x40 #define WCD934X_ANA_MICB_DISABLE 0x0 #define WCD934X_ANA_MICB2 0x0623 +#define WCD934X_ANA_MICB2_ENABLE BIT(6) +#define WCD934X_ANA_MICB2_ENABLE_MASK GENMASK(7, 6) +#define WCD934X_ANA_MICB2_VOUT_MASK GENMASK(5, 0) +#define WCD934X_ANA_MICB2_RAMP 0x0624 +#define WCD934X_RAMP_EN_MASK BIT(7) +#define WCD934X_RAMP_SHIFT_CTRL_MASK GENMASK(4, 2) #define WCD934X_ANA_MICB3 0x0625 #define WCD934X_ANA_MICB4 0x0626 #define WCD934X_BIAS_VBG_FINE_ADJ 0x0629 +#define WCD934X_MBHC_CTL_CLK 0x0656 +#define WCD934X_MBHC_CTL_BCS 0x065a +#define WCD934X_MBHC_STATUS_SPARE_1 0x065b #define WCD934X_MICB1_TEST_CTL_1 0x066b #define WCD934X_MICB1_TEST_CTL_2 0x066c #define WCD934X_MICB2_TEST_CTL_1 0x066e @@ -141,7 +180,11 @@ #define WCD934X_HPH_CNP_WG_CTL 0x06cc #define WCD934X_HPH_GM3_BOOST_EN_MASK BIT(7) #define WCD934X_HPH_GM3_BOOST_ENABLE BIT(7) +#define WCD934X_HPH_CNP_WG_TIME 0x06cd #define WCD934X_HPH_OCP_CTL 0x06ce +#define WCD934X_HPH_PA_CTL2 0x06d2 +#define WCD934X_HPHPA_GND_R_MASK BIT(6) +#define WCD934X_HPHPA_GND_L_MASK BIT(4) #define WCD934X_HPH_L_EN 0x06d3 #define WCD934X_HPH_GAIN_SRC_SEL_MASK BIT(5) #define WCD934X_HPH_GAIN_SRC_SEL_COMPANDER 0 @@ -152,6 +195,8 @@ #define WCD934X_HPH_OCP_DET_MASK BIT(0) #define WCD934X_HPH_OCP_DET_ENABLE BIT(0) #define WCD934X_HPH_OCP_DET_DISABLE 0 +#define WCD934X_HPH_R_ATEST 0x06d8 +#define WCD934X_HPHPA_GND_OVR_MASK BIT(1) #define WCD934X_DIFF_LO_LO2_COMPANDER 0x06ea #define WCD934X_DIFF_LO_LO1_COMPANDER 0x06eb #define WCD934X_CLK_SYS_MCLK_PRG 0x0711 @@ -172,7 +217,19 @@ #define WCD934X_SIDO_NEW_VOUT_D_FREQ2 0x071e #define WCD934X_SIDO_RIPPLE_FREQ_EN_MASK BIT(0) #define WCD934X_SIDO_RIPPLE_FREQ_ENABLE BIT(0) +#define WCD934X_MBHC_NEW_CTL_1 0x0720 +#define WCD934X_MBHC_CTL_RCO_EN_MASK BIT(7) +#define WCD935X_MBHC_CTL_RCO_EN BIT(7) #define WCD934X_MBHC_NEW_CTL_2 0x0721 +#define WCD934X_M_RTH_CTL_MASK GENMASK(3, 2) +#define WCD934X_MBHC_NEW_PLUG_DETECT_CTL 0x0722 +#define WCD934X_HSDET_PULLUP_C_MASK GENMASK(7, 6) +#define WCD934X_MBHC_NEW_ZDET_ANA_CTL 0x0723 +#define WCD934X_ZDET_RANGE_CTL_MASK GENMASK(3, 0) +#define WCD934X_ZDET_MAXV_CTL_MASK GENMASK(6, 4) +#define WCD934X_MBHC_NEW_ZDET_RAMP_CTL 0x0724 +#define WCD934X_MBHC_NEW_FSM_STATUS 0x0725 +#define WCD934X_MBHC_NEW_ADC_RESULT 0x0726 #define WCD934X_TX_NEW_AMIC_4_5_SEL 0x0727 #define WCD934X_HPH_NEW_INT_RDAC_HD2_CTL_L 0x0733 #define WCD934X_HPH_NEW_INT_RDAC_OVERRIDE_CTL 0x0735 diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 7833ca35d193..d8f14b59bddb 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -1536,9 +1536,13 @@ config SND_SOC_WCD9335 Qualcomm Technologies, Inc. (QTI) multimedia solutions, including the MSM8996, MSM8976, and MSM8956 chipsets. +config SND_SOC_WCD_MBHC + tristate + config SND_SOC_WCD934X tristate "WCD9340/WCD9341 Codec" depends on COMMON_CLK + select SND_SOC_WCD_MBHC depends on MFD_WCD934X help The WCD9340/9341 is a audio codec IC Integrated in diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 8c7257035e4c..415ba8236b7f 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -251,6 +251,7 @@ snd-soc-twl6040-objs := twl6040.o snd-soc-uda1334-objs := uda1334.o snd-soc-uda134x-objs := uda134x.o snd-soc-uda1380-objs := uda1380.o +snd-soc-wcd-mbhc-objs := wcd-mbhc-v2.o snd-soc-wcd9335-objs := wcd-clsh-v2.o wcd9335.o snd-soc-wcd934x-objs := wcd-clsh-v2.o wcd934x.o snd-soc-wl1273-objs := wl1273.o @@ -574,6 +575,7 @@ obj-$(CONFIG_SND_SOC_TWL6040) += snd-soc-twl6040.o obj-$(CONFIG_SND_SOC_UDA1334) += snd-soc-uda1334.o obj-$(CONFIG_SND_SOC_UDA134X) += snd-soc-uda134x.o obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o +obj-$(CONFIG_SND_SOC_WCD_MBHC) += snd-soc-wcd-mbhc.o obj-$(CONFIG_SND_SOC_WCD9335) += snd-soc-wcd9335.o obj-$(CONFIG_SND_SOC_WCD934X) += snd-soc-wcd934x.o obj-$(CONFIG_SND_SOC_WL1273) += snd-soc-wl1273.o diff --git a/sound/soc/codecs/wcd-mbhc-v2.c b/sound/soc/codecs/wcd-mbhc-v2.c new file mode 100644 index 000000000000..dee9410650d7 --- /dev/null +++ b/sound/soc/codecs/wcd-mbhc-v2.c @@ -0,0 +1,1475 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2015-2021, The Linux Foundation. All rights reserved. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wcd-mbhc-v2.h" + +#define HS_DETECT_PLUG_TIME_MS (3 * 1000) +#define MBHC_BUTTON_PRESS_THRESHOLD_MIN 250 +#define GND_MIC_SWAP_THRESHOLD 4 +#define WCD_FAKE_REMOVAL_MIN_PERIOD_MS 100 +#define HPHL_CROSS_CONN_THRESHOLD 100 +#define HS_VREF_MIN_VAL 1400 +#define FAKE_REM_RETRY_ATTEMPTS 3 +#define WCD_MBHC_ADC_HS_THRESHOLD_MV 1700 +#define WCD_MBHC_ADC_HPH_THRESHOLD_MV 75 +#define WCD_MBHC_ADC_MICBIAS_MV 1800 +#define WCD_MBHC_FAKE_INS_RETRY 4 + +#define WCD_MBHC_JACK_MASK (SND_JACK_HEADSET | SND_JACK_LINEOUT | \ + SND_JACK_MECHANICAL) + +#define WCD_MBHC_JACK_BUTTON_MASK (SND_JACK_BTN_0 | SND_JACK_BTN_1 | \ + SND_JACK_BTN_2 | SND_JACK_BTN_3 | \ + SND_JACK_BTN_4 | SND_JACK_BTN_5) + +enum wcd_mbhc_adc_mux_ctl { + MUX_CTL_AUTO = 0, + MUX_CTL_IN2P, + MUX_CTL_IN3P, + MUX_CTL_IN4P, + MUX_CTL_HPH_L, + MUX_CTL_HPH_R, + MUX_CTL_NONE, +}; + +struct wcd_mbhc { + struct device *dev; + struct snd_soc_component *component; + struct snd_soc_jack *jack; + struct wcd_mbhc_config *cfg; + const struct wcd_mbhc_cb *mbhc_cb; + const struct wcd_mbhc_intr *intr_ids; + struct wcd_mbhc_field *fields; + /* Delayed work to report long button press */ + struct delayed_work mbhc_btn_dwork; + /* Work to correct accessory type */ + struct work_struct correct_plug_swch; + struct mutex lock; + int buttons_pressed; + u32 hph_status; /* track headhpone status */ + u8 current_plug; + bool is_btn_press; + bool in_swch_irq_handler; + bool hs_detect_work_stop; + bool is_hs_recording; + bool extn_cable_hph_rem; + bool force_linein; + bool impedance_detect; + unsigned long event_state; + unsigned long jiffies_atreport; + /* impedance of hphl and hphr */ + uint32_t zl, zr; + /* Holds type of Headset - Mono/Stereo */ + enum wcd_mbhc_hph_type hph_type; + /* Holds mbhc detection method - ADC/Legacy */ + int mbhc_detection_logic; +}; + +static inline int wcd_mbhc_write_field(const struct wcd_mbhc *mbhc, + int field, int val) +{ + if (!mbhc->fields[field].reg) + return 0; + + return snd_soc_component_write_field(mbhc->component, + mbhc->fields[field].reg, + mbhc->fields[field].mask, val); +} + +static inline int wcd_mbhc_read_field(const struct wcd_mbhc *mbhc, int field) +{ + if (!mbhc->fields[field].reg) + return 0; + + return snd_soc_component_read_field(mbhc->component, + mbhc->fields[field].reg, + mbhc->fields[field].mask); +} + +static void wcd_program_hs_vref(struct wcd_mbhc *mbhc) +{ + u32 reg_val = ((mbhc->cfg->v_hs_max - HS_VREF_MIN_VAL) / 100); + + wcd_mbhc_write_field(mbhc, WCD_MBHC_HS_VREF, reg_val); +} + +static void wcd_program_btn_threshold(const struct wcd_mbhc *mbhc, bool micbias) +{ + struct snd_soc_component *component = mbhc->component; + + mbhc->mbhc_cb->set_btn_thr(component, mbhc->cfg->btn_low, + mbhc->cfg->btn_high, + mbhc->cfg->num_btn, micbias); +} + +static void wcd_mbhc_curr_micbias_control(const struct wcd_mbhc *mbhc, + const enum wcd_mbhc_cs_mb_en_flag cs_mb_en) +{ + + /* + * Some codecs handle micbias/pullup enablement in codec + * drivers itself and micbias is not needed for regular + * plug type detection. So if micbias_control callback function + * is defined, just return. + */ + if (mbhc->mbhc_cb->mbhc_micbias_control) + return; + + switch (cs_mb_en) { + case WCD_MBHC_EN_CS: + wcd_mbhc_write_field(mbhc, WCD_MBHC_MICB_CTRL, 0); + wcd_mbhc_write_field(mbhc, WCD_MBHC_BTN_ISRC_CTL, 3); + /* Program Button threshold registers as per CS */ + wcd_program_btn_threshold(mbhc, false); + break; + case WCD_MBHC_EN_MB: + wcd_mbhc_write_field(mbhc, WCD_MBHC_BTN_ISRC_CTL, 0); + wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, 1); + /* Disable PULL_UP_EN & enable MICBIAS */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_MICB_CTRL, 2); + /* Program Button threshold registers as per MICBIAS */ + wcd_program_btn_threshold(mbhc, true); + break; + case WCD_MBHC_EN_PULLUP: + wcd_mbhc_write_field(mbhc, WCD_MBHC_BTN_ISRC_CTL, 3); + wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, 1); + wcd_mbhc_write_field(mbhc, WCD_MBHC_MICB_CTRL, 1); + /* Program Button threshold registers as per MICBIAS */ + wcd_program_btn_threshold(mbhc, true); + break; + case WCD_MBHC_EN_NONE: + wcd_mbhc_write_field(mbhc, WCD_MBHC_BTN_ISRC_CTL, 0); + wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, 1); + wcd_mbhc_write_field(mbhc, WCD_MBHC_MICB_CTRL, 0); + break; + default: + dev_err(mbhc->dev, "%s: Invalid parameter", __func__); + break; + } +} + +int wcd_mbhc_event_notify(struct wcd_mbhc *mbhc, unsigned long event) +{ + + struct snd_soc_component *component; + bool micbias2 = false; + + if (!mbhc) + return 0; + + component = mbhc->component; + + if (mbhc->mbhc_cb->micbias_enable_status) + micbias2 = mbhc->mbhc_cb->micbias_enable_status(component, MIC_BIAS_2); + + switch (event) { + /* MICBIAS usage change */ + case WCD_EVENT_POST_DAPM_MICBIAS_2_ON: + mbhc->is_hs_recording = true; + break; + case WCD_EVENT_POST_MICBIAS_2_ON: + /* Disable current source if micbias2 enabled */ + if (mbhc->mbhc_cb->mbhc_micbias_control) { + if (wcd_mbhc_read_field(mbhc, WCD_MBHC_FSM_EN)) + wcd_mbhc_write_field(mbhc, WCD_MBHC_BTN_ISRC_CTL, 0); + } else { + mbhc->is_hs_recording = true; + wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_MB); + } + break; + case WCD_EVENT_PRE_MICBIAS_2_OFF: + /* + * Before MICBIAS_2 is turned off, if FSM is enabled, + * make sure current source is enabled so as to detect + * button press/release events + */ + if (mbhc->mbhc_cb->mbhc_micbias_control/* && !mbhc->micbias_enable*/) { + if (wcd_mbhc_read_field(mbhc, WCD_MBHC_FSM_EN)) + wcd_mbhc_write_field(mbhc, WCD_MBHC_BTN_ISRC_CTL, 3); + } + break; + /* MICBIAS usage change */ + case WCD_EVENT_POST_DAPM_MICBIAS_2_OFF: + mbhc->is_hs_recording = false; + break; + case WCD_EVENT_POST_MICBIAS_2_OFF: + if (!mbhc->mbhc_cb->mbhc_micbias_control) + mbhc->is_hs_recording = false; + + /* Enable PULL UP if PA's are enabled */ + if ((test_bit(WCD_MBHC_EVENT_PA_HPHL, &mbhc->event_state)) || + (test_bit(WCD_MBHC_EVENT_PA_HPHR, &mbhc->event_state))) + /* enable pullup and cs, disable mb */ + wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_PULLUP); + else + /* enable current source and disable mb, pullup*/ + wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_CS); + + break; + case WCD_EVENT_POST_HPHL_PA_OFF: + clear_bit(WCD_MBHC_EVENT_PA_HPHL, &mbhc->event_state); + + /* check if micbias is enabled */ + if (micbias2) + /* Disable cs, pullup & enable micbias */ + wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_MB); + else + /* Disable micbias, pullup & enable cs */ + wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_CS); + break; + case WCD_EVENT_POST_HPHR_PA_OFF: + clear_bit(WCD_MBHC_EVENT_PA_HPHR, &mbhc->event_state); + /* check if micbias is enabled */ + if (micbias2) + /* Disable cs, pullup & enable micbias */ + wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_MB); + else + /* Disable micbias, pullup & enable cs */ + wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_CS); + break; + case WCD_EVENT_PRE_HPHL_PA_ON: + set_bit(WCD_MBHC_EVENT_PA_HPHL, &mbhc->event_state); + /* check if micbias is enabled */ + if (micbias2) + /* Disable cs, pullup & enable micbias */ + wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_MB); + else + /* Disable micbias, enable pullup & cs */ + wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_PULLUP); + break; + case WCD_EVENT_PRE_HPHR_PA_ON: + set_bit(WCD_MBHC_EVENT_PA_HPHR, &mbhc->event_state); + /* check if micbias is enabled */ + if (micbias2) + /* Disable cs, pullup & enable micbias */ + wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_MB); + else + /* Disable micbias, enable pullup & cs */ + wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_PULLUP); + break; + default: + break; + } + return 0; +} +EXPORT_SYMBOL_GPL(wcd_mbhc_event_notify); + +static int wcd_cancel_btn_work(struct wcd_mbhc *mbhc) +{ + return cancel_delayed_work_sync(&mbhc->mbhc_btn_dwork); +} + +static void wcd_micbias_disable(struct wcd_mbhc *mbhc) +{ + struct snd_soc_component *component = mbhc->component; + + if (mbhc->mbhc_cb->mbhc_micbias_control) + mbhc->mbhc_cb->mbhc_micbias_control(component, MIC_BIAS_2, MICB_DISABLE); + + if (mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic) + mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic(component, MIC_BIAS_2, false); + + if (mbhc->mbhc_cb->set_micbias_value) { + mbhc->mbhc_cb->set_micbias_value(component); + wcd_mbhc_write_field(mbhc, WCD_MBHC_MICB_CTRL, 0); + } +} + +static void wcd_mbhc_report_plug_removal(struct wcd_mbhc *mbhc, + enum snd_jack_types jack_type) +{ + mbhc->hph_status &= ~jack_type; + /* + * cancel possibly scheduled btn work and + * report release if we reported button press + */ + if (!wcd_cancel_btn_work(mbhc) && mbhc->buttons_pressed) { + snd_soc_jack_report(mbhc->jack, 0, mbhc->buttons_pressed); + mbhc->buttons_pressed &= ~WCD_MBHC_JACK_BUTTON_MASK; + } + + wcd_micbias_disable(mbhc); + mbhc->hph_type = WCD_MBHC_HPH_NONE; + mbhc->zl = mbhc->zr = 0; + snd_soc_jack_report(mbhc->jack, mbhc->hph_status, WCD_MBHC_JACK_MASK); + mbhc->current_plug = MBHC_PLUG_TYPE_NONE; + mbhc->force_linein = false; +} + +static void wcd_mbhc_compute_impedance(struct wcd_mbhc *mbhc) +{ + + if (!mbhc->impedance_detect) + return; + + if (mbhc->cfg->linein_th != 0) { + u8 fsm_en = wcd_mbhc_read_field(mbhc, WCD_MBHC_FSM_EN); + /* Set MUX_CTL to AUTO for Z-det */ + + wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, 0); + wcd_mbhc_write_field(mbhc, WCD_MBHC_MUX_CTL, MUX_CTL_AUTO); + wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, 1); + mbhc->mbhc_cb->compute_impedance(mbhc->component, &mbhc->zl, &mbhc->zr); + wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, fsm_en); + } +} + +static void wcd_mbhc_report_plug_insertion(struct wcd_mbhc *mbhc, + enum snd_jack_types jack_type) +{ + bool is_pa_on; + /* + * Report removal of current jack type. + * Headphone to headset shouldn't report headphone + * removal. + */ + if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADSET && + jack_type == SND_JACK_HEADPHONE) + mbhc->hph_status &= ~SND_JACK_HEADSET; + + /* Report insertion */ + switch (jack_type) { + case SND_JACK_HEADPHONE: + mbhc->current_plug = MBHC_PLUG_TYPE_HEADPHONE; + break; + case SND_JACK_HEADSET: + mbhc->current_plug = MBHC_PLUG_TYPE_HEADSET; + mbhc->jiffies_atreport = jiffies; + break; + case SND_JACK_LINEOUT: + mbhc->current_plug = MBHC_PLUG_TYPE_HIGH_HPH; + break; + default: + break; + } + + + is_pa_on = wcd_mbhc_read_field(mbhc, WCD_MBHC_HPH_PA_EN); + + if (!is_pa_on) { + wcd_mbhc_compute_impedance(mbhc); + if ((mbhc->zl > mbhc->cfg->linein_th) && + (mbhc->zr > mbhc->cfg->linein_th) && + (jack_type == SND_JACK_HEADPHONE)) { + jack_type = SND_JACK_LINEOUT; + mbhc->force_linein = true; + mbhc->current_plug = MBHC_PLUG_TYPE_HIGH_HPH; + if (mbhc->hph_status) { + mbhc->hph_status &= ~(SND_JACK_HEADSET | + SND_JACK_LINEOUT); + snd_soc_jack_report(mbhc->jack, mbhc->hph_status, + WCD_MBHC_JACK_MASK); + } + } + } + + /* Do not calculate impedance again for lineout + * as during playback pa is on and impedance values + * will not be correct resulting in lineout detected + * as headphone. + */ + if (is_pa_on && mbhc->force_linein) { + 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_soc_jack_report(mbhc->jack, mbhc->hph_status, + WCD_MBHC_JACK_MASK); + } + } + + mbhc->hph_status |= jack_type; + + if (jack_type == SND_JACK_HEADPHONE && mbhc->mbhc_cb->mbhc_micb_ramp_control) + mbhc->mbhc_cb->mbhc_micb_ramp_control(mbhc->component, false); + + snd_soc_jack_report(mbhc->jack, (mbhc->hph_status | SND_JACK_MECHANICAL), + WCD_MBHC_JACK_MASK); +} + +static void wcd_mbhc_report_plug(struct wcd_mbhc *mbhc, int insertion, + enum snd_jack_types jack_type) +{ + + WARN_ON(!mutex_is_locked(&mbhc->lock)); + + if (!insertion) /* Report removal */ + wcd_mbhc_report_plug_removal(mbhc, jack_type); + else + wcd_mbhc_report_plug_insertion(mbhc, jack_type); + +} + +static void wcd_cancel_hs_detect_plug(struct wcd_mbhc *mbhc, + struct work_struct *work) +{ + mbhc->hs_detect_work_stop = true; + mutex_unlock(&mbhc->lock); + cancel_work_sync(work); + mutex_lock(&mbhc->lock); +} + +static void wcd_mbhc_cancel_pending_work(struct wcd_mbhc *mbhc) +{ + /* cancel pending button press */ + wcd_cancel_btn_work(mbhc); + /* cancel correct work function */ + wcd_cancel_hs_detect_plug(mbhc, &mbhc->correct_plug_swch); +} + +static void wcd_mbhc_elec_hs_report_unplug(struct wcd_mbhc *mbhc) +{ + wcd_mbhc_cancel_pending_work(mbhc); + /* Report extension cable */ + wcd_mbhc_report_plug(mbhc, 1, SND_JACK_LINEOUT); + /* + * Disable HPHL trigger and MIC Schmitt triggers. + * Setup for insertion detection. + */ + disable_irq_nosync(mbhc->intr_ids->mbhc_hs_rem_intr); + wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_NONE); + /* Disable HW FSM */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, 0); + wcd_mbhc_write_field(mbhc, WCD_MBHC_ELECT_SCHMT_ISRC, 3); + + /* Set the detection type appropriately */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_ELECT_DETECTION_TYPE, 1); + enable_irq(mbhc->intr_ids->mbhc_hs_ins_intr); +} + +static void wcd_mbhc_find_plug_and_report(struct wcd_mbhc *mbhc, + enum wcd_mbhc_plug_type plug_type) +{ + if (mbhc->current_plug == plug_type) + return; + + mutex_lock(&mbhc->lock); + + switch (plug_type) { + case MBHC_PLUG_TYPE_HEADPHONE: + wcd_mbhc_report_plug(mbhc, 1, SND_JACK_HEADPHONE); + break; + case MBHC_PLUG_TYPE_HEADSET: + wcd_mbhc_report_plug(mbhc, 1, SND_JACK_HEADSET); + break; + case MBHC_PLUG_TYPE_HIGH_HPH: + wcd_mbhc_report_plug(mbhc, 1, SND_JACK_LINEOUT); + break; + case MBHC_PLUG_TYPE_GND_MIC_SWAP: + if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADPHONE) + wcd_mbhc_report_plug(mbhc, 0, SND_JACK_HEADPHONE); + if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADSET) + wcd_mbhc_report_plug(mbhc, 0, SND_JACK_HEADSET); + break; + default: + WARN(1, "Unexpected current plug_type %d, plug_type %d\n", + mbhc->current_plug, plug_type); + break; + } + mutex_unlock(&mbhc->lock); +} + +static void wcd_schedule_hs_detect_plug(struct wcd_mbhc *mbhc, + struct work_struct *work) +{ + WARN_ON(!mutex_is_locked(&mbhc->lock)); + mbhc->hs_detect_work_stop = false; + schedule_work(work); +} + +static void wcd_mbhc_adc_detect_plug_type(struct wcd_mbhc *mbhc) +{ + struct snd_soc_component *component = mbhc->component; + + WARN_ON(!mutex_is_locked(&mbhc->lock)); + + if (mbhc->mbhc_cb->hph_pull_down_ctrl) + mbhc->mbhc_cb->hph_pull_down_ctrl(component, false); + + wcd_mbhc_write_field(mbhc, WCD_MBHC_DETECTION_DONE, 0); + + if (mbhc->mbhc_cb->mbhc_micbias_control) { + mbhc->mbhc_cb->mbhc_micbias_control(component, MIC_BIAS_2, + MICB_ENABLE); + wcd_schedule_hs_detect_plug(mbhc, &mbhc->correct_plug_swch); + } +} + +static irqreturn_t wcd_mbhc_mech_plug_detect_irq(int irq, void *data) +{ + struct snd_soc_component *component; + enum snd_jack_types jack_type; + struct wcd_mbhc *mbhc = data; + bool detection_type; + + component = mbhc->component; + mutex_lock(&mbhc->lock); + + mbhc->in_swch_irq_handler = true; + + wcd_mbhc_cancel_pending_work(mbhc); + + detection_type = wcd_mbhc_read_field(mbhc, WCD_MBHC_MECH_DETECTION_TYPE); + + /* Set the detection type appropriately */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_MECH_DETECTION_TYPE, !detection_type); + + /* Enable micbias ramp */ + if (mbhc->mbhc_cb->mbhc_micb_ramp_control) + mbhc->mbhc_cb->mbhc_micb_ramp_control(component, true); + + if (detection_type) { + if (mbhc->current_plug != MBHC_PLUG_TYPE_NONE) + goto exit; + /* Make sure MASTER_BIAS_CTL is enabled */ + mbhc->mbhc_cb->mbhc_bias(component, true); + mbhc->is_btn_press = false; + wcd_mbhc_adc_detect_plug_type(mbhc); + } else { + /* Disable HW FSM */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, 0); + wcd_mbhc_write_field(mbhc, WCD_MBHC_BTN_ISRC_CTL, 0); + mbhc->extn_cable_hph_rem = false; + + if (mbhc->current_plug == MBHC_PLUG_TYPE_NONE) + goto exit; + + mbhc->is_btn_press = false; + switch (mbhc->current_plug) { + case MBHC_PLUG_TYPE_HEADPHONE: + jack_type = SND_JACK_HEADPHONE; + break; + case MBHC_PLUG_TYPE_HEADSET: + jack_type = SND_JACK_HEADSET; + break; + case MBHC_PLUG_TYPE_HIGH_HPH: + if (mbhc->mbhc_detection_logic == WCD_DETECTION_ADC) + wcd_mbhc_write_field(mbhc, WCD_MBHC_ELECT_ISRC_EN, 0); + jack_type = SND_JACK_LINEOUT; + break; + case MBHC_PLUG_TYPE_GND_MIC_SWAP: + dev_err(mbhc->dev, "Ground and Mic Swapped on plug\n"); + goto exit; + default: + dev_err(mbhc->dev, "Invalid current plug: %d\n", + mbhc->current_plug); + goto exit; + } + disable_irq_nosync(mbhc->intr_ids->mbhc_hs_rem_intr); + disable_irq_nosync(mbhc->intr_ids->mbhc_hs_ins_intr); + wcd_mbhc_write_field(mbhc, WCD_MBHC_ELECT_DETECTION_TYPE, 1); + wcd_mbhc_write_field(mbhc, WCD_MBHC_ELECT_SCHMT_ISRC, 0); + wcd_mbhc_report_plug(mbhc, 0, jack_type); + } + +exit: + mbhc->in_swch_irq_handler = false; + mutex_unlock(&mbhc->lock); + return IRQ_HANDLED; +} + +static int wcd_mbhc_get_button_mask(struct wcd_mbhc *mbhc) +{ + int mask = 0; + int btn; + + btn = wcd_mbhc_read_field(mbhc, WCD_MBHC_BTN_RESULT); + + switch (btn) { + case 0: + mask = SND_JACK_BTN_0; + break; + case 1: + mask = SND_JACK_BTN_1; + break; + case 2: + mask = SND_JACK_BTN_2; + break; + case 3: + mask = SND_JACK_BTN_3; + break; + case 4: + mask = SND_JACK_BTN_4; + break; + case 5: + mask = SND_JACK_BTN_5; + break; + default: + break; + } + + return mask; +} + +static void wcd_btn_long_press_fn(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct wcd_mbhc *mbhc = container_of(dwork, struct wcd_mbhc, mbhc_btn_dwork); + + if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADSET) + snd_soc_jack_report(mbhc->jack, mbhc->buttons_pressed, + mbhc->buttons_pressed); +} + +static irqreturn_t wcd_mbhc_btn_press_handler(int irq, void *data) +{ + struct wcd_mbhc *mbhc = data; + int mask; + unsigned long msec_val; + + mutex_lock(&mbhc->lock); + wcd_cancel_btn_work(mbhc); + mbhc->is_btn_press = true; + msec_val = jiffies_to_msecs(jiffies - mbhc->jiffies_atreport); + + /* Too short, ignore button press */ + if (msec_val < MBHC_BUTTON_PRESS_THRESHOLD_MIN) + goto done; + + /* If switch interrupt already kicked in, ignore button press */ + if (mbhc->in_swch_irq_handler) + goto done; + + /* Plug isn't headset, ignore button press */ + if (mbhc->current_plug != MBHC_PLUG_TYPE_HEADSET) + goto done; + + mask = wcd_mbhc_get_button_mask(mbhc); + mbhc->buttons_pressed |= mask; + if (schedule_delayed_work(&mbhc->mbhc_btn_dwork, msecs_to_jiffies(400)) == 0) + WARN(1, "Button pressed twice without release event\n"); +done: + mutex_unlock(&mbhc->lock); + return IRQ_HANDLED; +} + +static irqreturn_t wcd_mbhc_btn_release_handler(int irq, void *data) +{ + struct wcd_mbhc *mbhc = data; + int ret; + + mutex_lock(&mbhc->lock); + if (mbhc->is_btn_press) + mbhc->is_btn_press = false; + else /* fake btn press */ + goto exit; + + if (!(mbhc->buttons_pressed & WCD_MBHC_JACK_BUTTON_MASK)) + goto exit; + + ret = wcd_cancel_btn_work(mbhc); + if (ret == 0) { /* Reporting long button release event */ + snd_soc_jack_report(mbhc->jack, 0, mbhc->buttons_pressed); + } else { + if (!mbhc->in_swch_irq_handler) { + /* Reporting btn press n Release */ + snd_soc_jack_report(mbhc->jack, mbhc->buttons_pressed, + mbhc->buttons_pressed); + snd_soc_jack_report(mbhc->jack, 0, mbhc->buttons_pressed); + } + } + mbhc->buttons_pressed &= ~WCD_MBHC_JACK_BUTTON_MASK; +exit: + mutex_unlock(&mbhc->lock); + + return IRQ_HANDLED; +} + +static irqreturn_t wcd_mbhc_hph_ocp_irq(struct wcd_mbhc *mbhc, bool hphr) +{ + + /* TODO Find a better way to report this to Userspace */ + dev_err(mbhc->dev, "MBHC Over Current on %s detected\n", + hphr ? "HPHR" : "HPHL"); + + wcd_mbhc_write_field(mbhc, WCD_MBHC_OCP_FSM_EN, 0); + wcd_mbhc_write_field(mbhc, WCD_MBHC_OCP_FSM_EN, 1); + + return IRQ_HANDLED; +} + +static irqreturn_t wcd_mbhc_hphl_ocp_irq(int irq, void *data) +{ + return wcd_mbhc_hph_ocp_irq(data, false); +} + +static irqreturn_t wcd_mbhc_hphr_ocp_irq(int irq, void *data) +{ + return wcd_mbhc_hph_ocp_irq(data, true); +} + +static int wcd_mbhc_initialise(struct wcd_mbhc *mbhc) +{ + struct snd_soc_component *component = mbhc->component; + + mutex_lock(&mbhc->lock); + + /* enable HS detection */ + if (mbhc->mbhc_cb->hph_pull_up_control_v2) + mbhc->mbhc_cb->hph_pull_up_control_v2(component, + HS_PULLUP_I_DEFAULT); + else if (mbhc->mbhc_cb->hph_pull_up_control) + mbhc->mbhc_cb->hph_pull_up_control(component, I_DEFAULT); + else + wcd_mbhc_write_field(mbhc, WCD_MBHC_HS_L_DET_PULL_UP_CTRL, 3); + + wcd_mbhc_write_field(mbhc, WCD_MBHC_HPHL_PLUG_TYPE, mbhc->cfg->hphl_swh); + wcd_mbhc_write_field(mbhc, WCD_MBHC_GND_PLUG_TYPE, mbhc->cfg->gnd_swh); + wcd_mbhc_write_field(mbhc, WCD_MBHC_SW_HPH_LP_100K_TO_GND, 1); + if (mbhc->cfg->gnd_det_en && mbhc->mbhc_cb->mbhc_gnd_det_ctrl) + mbhc->mbhc_cb->mbhc_gnd_det_ctrl(component, true); + wcd_mbhc_write_field(mbhc, WCD_MBHC_HS_L_DET_PULL_UP_COMP_CTRL, 1); + + wcd_mbhc_write_field(mbhc, WCD_MBHC_L_DET_EN, 1); + + /* Insertion debounce set to 96ms */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_INSREM_DBNC, 6); + + /* Button Debounce set to 16ms */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_BTN_DBNC, 2); + + /* enable bias */ + mbhc->mbhc_cb->mbhc_bias(component, true); + /* enable MBHC clock */ + if (mbhc->mbhc_cb->clk_setup) + mbhc->mbhc_cb->clk_setup(component, true); + + /* program HS_VREF value */ + wcd_program_hs_vref(mbhc); + + wcd_program_btn_threshold(mbhc, false); + + mutex_unlock(&mbhc->lock); + + return 0; +} + +static int wcd_mbhc_get_micbias(struct wcd_mbhc *mbhc) +{ + int micbias = 0; + + if (mbhc->mbhc_cb->get_micbias_val) { + mbhc->mbhc_cb->get_micbias_val(mbhc->component, &micbias); + } else { + u8 vout_ctl = 0; + /* Read MBHC Micbias (Mic Bias2) voltage */ + vout_ctl = wcd_mbhc_read_field(mbhc, WCD_MBHC_MICB2_VOUT); + /* Formula for getting micbias from vout + * micbias = 1.0V + VOUT_CTL * 50mV + */ + micbias = 1000 + (vout_ctl * 50); + } + return micbias; +} + +static int wcd_get_voltage_from_adc(u8 val, int micbias) +{ + /* Formula for calculating voltage from ADC + * Voltage = ADC_RESULT*12.5mV*V_MICBIAS/1.8 + */ + return ((val * 125 * micbias)/(WCD_MBHC_ADC_MICBIAS_MV * 10)); +} + +static int wcd_measure_adc_continuous(struct wcd_mbhc *mbhc) +{ + u8 adc_result; + int output_mv; + int retry = 3; + u8 adc_en; + + /* Pre-requisites for ADC continuous measurement */ + /* Read legacy electircal detection and disable */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_ELECT_SCHMT_ISRC, 0x00); + /* Set ADC to continuous measurement */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_MODE, 1); + /* Read ADC Enable bit to restore after adc measurement */ + adc_en = wcd_mbhc_read_field(mbhc, WCD_MBHC_ADC_EN); + /* Disable ADC_ENABLE bit */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_EN, 0); + /* Disable MBHC FSM */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, 0); + /* Set the MUX selection to IN2P */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_MUX_CTL, MUX_CTL_IN2P); + /* Enable MBHC FSM */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, 1); + /* Enable ADC_ENABLE bit */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_EN, 1); + + while (retry--) { + /* wait for 3 msec before reading ADC result */ + usleep_range(3000, 3100); + adc_result = wcd_mbhc_read_field(mbhc, WCD_MBHC_ADC_RESULT); + } + + /* Restore ADC Enable */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_EN, adc_en); + /* Get voltage from ADC result */ + output_mv = wcd_get_voltage_from_adc(adc_result, wcd_mbhc_get_micbias(mbhc)); + + return output_mv; +} + +static int wcd_measure_adc_once(struct wcd_mbhc *mbhc, int mux_ctl) +{ + struct device *dev = mbhc->dev; + u8 adc_timeout = 0; + u8 adc_complete = 0; + u8 adc_result; + int retry = 6; + int ret; + int output_mv = 0; + u8 adc_en; + + wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_MODE, 0); + /* Read ADC Enable bit to restore after adc measurement */ + adc_en = wcd_mbhc_read_field(mbhc, WCD_MBHC_ADC_EN); + /* Trigger ADC one time measurement */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_EN, 0); + wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, 0); + /* Set the appropriate MUX selection */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_MUX_CTL, mux_ctl); + wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, 1); + wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_EN, 1); + + while (retry--) { + /* wait for 600usec to get adc results */ + usleep_range(600, 610); + + /* check for ADC Timeout */ + adc_timeout = wcd_mbhc_read_field(mbhc, WCD_MBHC_ADC_TIMEOUT); + if (adc_timeout) + continue; + + /* Read ADC complete bit */ + adc_complete = wcd_mbhc_read_field(mbhc, WCD_MBHC_ADC_COMPLETE); + if (!adc_complete) + continue; + + /* Read ADC result */ + adc_result = wcd_mbhc_read_field(mbhc, WCD_MBHC_ADC_RESULT); + + /* Get voltage from ADC result */ + output_mv = wcd_get_voltage_from_adc(adc_result, + wcd_mbhc_get_micbias(mbhc)); + break; + } + + /* Restore ADC Enable */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_EN, adc_en); + + if (retry <= 0) { + dev_err(dev, "%s: adc complete: %d, adc timeout: %d\n", + __func__, adc_complete, adc_timeout); + ret = -EINVAL; + } else { + ret = output_mv; + } + + return ret; +} + +/* To determine if cross connection occurred */ +static int wcd_check_cross_conn(struct wcd_mbhc *mbhc) +{ + u8 adc_mode, elect_ctl, adc_en, fsm_en; + int hphl_adc_res, hphr_adc_res; + bool is_cross_conn = false; + + /* If PA is enabled, dont check for cross-connection */ + if (wcd_mbhc_read_field(mbhc, WCD_MBHC_HPH_PA_EN)) + return -EINVAL; + + /* Read legacy electircal detection and disable */ + elect_ctl = wcd_mbhc_read_field(mbhc, WCD_MBHC_ELECT_SCHMT_ISRC); + wcd_mbhc_write_field(mbhc, WCD_MBHC_ELECT_SCHMT_ISRC, 0); + + /* Read and set ADC to single measurement */ + adc_mode = wcd_mbhc_read_field(mbhc, WCD_MBHC_ADC_MODE); + /* Read ADC Enable bit to restore after adc measurement */ + adc_en = wcd_mbhc_read_field(mbhc, WCD_MBHC_ADC_EN); + /* Read FSM status */ + fsm_en = wcd_mbhc_read_field(mbhc, WCD_MBHC_FSM_EN); + + /* Get adc result for HPH L */ + hphl_adc_res = wcd_measure_adc_once(mbhc, MUX_CTL_HPH_L); + if (hphl_adc_res < 0) + return hphl_adc_res; + + /* Get adc result for HPH R in mV */ + hphr_adc_res = wcd_measure_adc_once(mbhc, MUX_CTL_HPH_R); + if (hphr_adc_res < 0) + return hphr_adc_res; + + if (hphl_adc_res > HPHL_CROSS_CONN_THRESHOLD || + hphr_adc_res > HPHL_CROSS_CONN_THRESHOLD) + is_cross_conn = true; + + wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, 0); + /* Set the MUX selection to Auto */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_MUX_CTL, MUX_CTL_AUTO); + wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, 1); + /* Restore ADC Enable */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_EN, adc_en); + /* Restore ADC mode */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_MODE, adc_mode); + /* Restore FSM state */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, fsm_en); + /* Restore electrical detection */ + wcd_mbhc_write_field(mbhc, WCD_MBHC_ELECT_SCHMT_ISRC, elect_ctl); + + return is_cross_conn; +} + +static int wcd_mbhc_adc_get_hs_thres(struct wcd_mbhc *mbhc) +{ + int hs_threshold, micbias_mv; + + micbias_mv = wcd_mbhc_get_micbias(mbhc); + if (mbhc->cfg->hs_thr) { + if (mbhc->cfg->micb_mv == micbias_mv) + hs_threshold = mbhc->cfg->hs_thr; + else + hs_threshold = (mbhc->cfg->hs_thr * + micbias_mv) / mbhc->cfg->micb_mv; + } else { + hs_threshold = ((WCD_MBHC_ADC_HS_THRESHOLD_MV * + micbias_mv) / WCD_MBHC_ADC_MICBIAS_MV); + } + return hs_threshold; +} + +static int wcd_mbhc_adc_get_hph_thres(struct wcd_mbhc *mbhc) +{ + int hph_threshold, micbias_mv; + + micbias_mv = wcd_mbhc_get_micbias(mbhc); + if (mbhc->cfg->hph_thr) { + if (mbhc->cfg->micb_mv == micbias_mv) + hph_threshold = mbhc->cfg->hph_thr; + else + hph_threshold = (mbhc->cfg->hph_thr * + micbias_mv) / mbhc->cfg->micb_mv; + } else { + hph_threshold = ((WCD_MBHC_ADC_HPH_THRESHOLD_MV * + micbias_mv) / WCD_MBHC_ADC_MICBIAS_MV); + } + return hph_threshold; +} + +static void wcd_mbhc_adc_update_fsm_source(struct wcd_mbhc *mbhc, + enum wcd_mbhc_plug_type plug_type) +{ + bool micbias2 = false; + + switch (plug_type) { + case MBHC_PLUG_TYPE_HEADPHONE: + wcd_mbhc_write_field(mbhc, WCD_MBHC_BTN_ISRC_CTL, 3); + break; + case MBHC_PLUG_TYPE_HEADSET: + if (mbhc->mbhc_cb->micbias_enable_status) + micbias2 = mbhc->mbhc_cb->micbias_enable_status(mbhc->component, + MIC_BIAS_2); + + if (!mbhc->is_hs_recording && !micbias2) + wcd_mbhc_write_field(mbhc, WCD_MBHC_BTN_ISRC_CTL, 3); + break; + default: + wcd_mbhc_write_field(mbhc, WCD_MBHC_BTN_ISRC_CTL, 0); + break; + + }; +} + +static void wcd_mbhc_bcs_enable(struct wcd_mbhc *mbhc, int plug_type, bool enable) +{ + switch (plug_type) { + case MBHC_PLUG_TYPE_HEADSET: + case MBHC_PLUG_TYPE_HEADPHONE: + if (mbhc->mbhc_cb->bcs_enable) + mbhc->mbhc_cb->bcs_enable(mbhc->component, enable); + break; + default: + break; + } +} + +static int wcd_mbhc_get_plug_from_adc(struct wcd_mbhc *mbhc, int adc_result) + +{ + enum wcd_mbhc_plug_type plug_type; + u32 hph_thr, hs_thr; + + hs_thr = wcd_mbhc_adc_get_hs_thres(mbhc); + hph_thr = wcd_mbhc_adc_get_hph_thres(mbhc); + + if (adc_result < hph_thr) + plug_type = MBHC_PLUG_TYPE_HEADPHONE; + else if (adc_result > hs_thr) + plug_type = MBHC_PLUG_TYPE_HIGH_HPH; + else + plug_type = MBHC_PLUG_TYPE_HEADSET; + + return plug_type; +} + +static void wcd_correct_swch_plug(struct work_struct *work) +{ + struct wcd_mbhc *mbhc; + struct snd_soc_component *component; + enum wcd_mbhc_plug_type plug_type = MBHC_PLUG_TYPE_INVALID; + unsigned long timeout; + int pt_gnd_mic_swap_cnt = 0; + int output_mv, cross_conn, hs_threshold, try = 0; + bool is_pa_on; + + mbhc = container_of(work, struct wcd_mbhc, correct_plug_swch); + component = mbhc->component; + + hs_threshold = wcd_mbhc_adc_get_hs_thres(mbhc); + + /* Mask ADC COMPLETE interrupt */ + disable_irq_nosync(mbhc->intr_ids->mbhc_hs_ins_intr); + + /* Check for cross connection */ + do { + cross_conn = wcd_check_cross_conn(mbhc); + try++; + } while (try < GND_MIC_SWAP_THRESHOLD); + + if (cross_conn > 0) { + plug_type = MBHC_PLUG_TYPE_GND_MIC_SWAP; + dev_err(mbhc->dev, "cross connection found, Plug type %d\n", + plug_type); + goto correct_plug_type; + } + + /* Find plug type */ + output_mv = wcd_measure_adc_continuous(mbhc); + plug_type = wcd_mbhc_get_plug_from_adc(mbhc, output_mv); + + /* + * Report plug type if it is either headset or headphone + * else start the 3 sec loop + */ + switch (plug_type) { + case MBHC_PLUG_TYPE_HEADPHONE: + wcd_mbhc_find_plug_and_report(mbhc, plug_type); + break; + case MBHC_PLUG_TYPE_HEADSET: + wcd_mbhc_find_plug_and_report(mbhc, plug_type); + wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_MODE, 0); + wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_EN, 0); + wcd_mbhc_write_field(mbhc, WCD_MBHC_DETECTION_DONE, 1); + break; + default: + break; + } + +correct_plug_type: + + /* Disable BCS slow insertion detection */ + wcd_mbhc_bcs_enable(mbhc, plug_type, false); + + timeout = jiffies + msecs_to_jiffies(HS_DETECT_PLUG_TIME_MS); + + while (!time_after(jiffies, timeout)) { + if (mbhc->hs_detect_work_stop) { + wcd_micbias_disable(mbhc); + goto exit; + } + + msleep(180); + /* + * Use ADC single mode to minimize the chance of missing out + * btn press/release for HEADSET type during correct work. + */ + output_mv = wcd_measure_adc_once(mbhc, MUX_CTL_IN2P); + plug_type = wcd_mbhc_get_plug_from_adc(mbhc, output_mv); + is_pa_on = wcd_mbhc_read_field(mbhc, WCD_MBHC_HPH_PA_EN); + + if ((output_mv <= hs_threshold) && !is_pa_on) { + /* Check for cross connection*/ + cross_conn = wcd_check_cross_conn(mbhc); + if (cross_conn > 0) { /* cross-connection */ + pt_gnd_mic_swap_cnt++; + if (pt_gnd_mic_swap_cnt < GND_MIC_SWAP_THRESHOLD) + continue; + else + plug_type = MBHC_PLUG_TYPE_GND_MIC_SWAP; + } else if (!cross_conn) { /* no cross connection */ + pt_gnd_mic_swap_cnt = 0; + plug_type = wcd_mbhc_get_plug_from_adc(mbhc, output_mv); + continue; + } else if (cross_conn < 0) /* Error */ + continue; + + if (pt_gnd_mic_swap_cnt == GND_MIC_SWAP_THRESHOLD) { + /* US_EU gpio present, flip switch */ + if (mbhc->cfg->swap_gnd_mic) { + if (mbhc->cfg->swap_gnd_mic(component, true)) + continue; + } + } + } + + if (output_mv > hs_threshold) /* cable is extension cable */ + plug_type = MBHC_PLUG_TYPE_HIGH_HPH; + } + + wcd_mbhc_bcs_enable(mbhc, plug_type, true); + + if (plug_type == MBHC_PLUG_TYPE_HIGH_HPH) + wcd_mbhc_write_field(mbhc, WCD_MBHC_ELECT_ISRC_EN, 1); + + wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_MODE, 0); + wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_EN, 0); + wcd_mbhc_find_plug_and_report(mbhc, plug_type); + + /* + * Set DETECTION_DONE bit for HEADSET + * so that btn press/release interrupt can be generated. + * For other plug type, clear the bit. + */ + if (plug_type == MBHC_PLUG_TYPE_HEADSET) + wcd_mbhc_write_field(mbhc, WCD_MBHC_DETECTION_DONE, 1); + else + wcd_mbhc_write_field(mbhc, WCD_MBHC_DETECTION_DONE, 0); + + if (mbhc->mbhc_cb->mbhc_micbias_control) + wcd_mbhc_adc_update_fsm_source(mbhc, plug_type); + +exit: + if (mbhc->mbhc_cb->mbhc_micbias_control/* && !mbhc->micbias_enable*/) + mbhc->mbhc_cb->mbhc_micbias_control(component, MIC_BIAS_2, MICB_DISABLE); + + /* + * If plug type is corrected from special headset to headphone, + * clear the micbias enable flag, set micbias back to 1.8V and + * disable micbias. + */ + if (plug_type == MBHC_PLUG_TYPE_HEADPHONE) { + wcd_micbias_disable(mbhc); + /* + * Enable ADC COMPLETE interrupt for HEADPHONE. + * Btn release may happen after the correct work, ADC COMPLETE + * interrupt needs to be captured to correct plug type. + */ + enable_irq(mbhc->intr_ids->mbhc_hs_ins_intr); + } + + if (mbhc->mbhc_cb->hph_pull_down_ctrl) + mbhc->mbhc_cb->hph_pull_down_ctrl(component, true); +} + +static irqreturn_t wcd_mbhc_adc_hs_rem_irq(int irq, void *data) +{ + struct wcd_mbhc *mbhc = data; + unsigned long timeout; + int adc_threshold, output_mv, retry = 0; + bool hphpa_on = false; + + mutex_lock(&mbhc->lock); + timeout = jiffies + msecs_to_jiffies(WCD_FAKE_REMOVAL_MIN_PERIOD_MS); + adc_threshold = wcd_mbhc_adc_get_hs_thres(mbhc); + + do { + retry++; + /* + * read output_mv every 10ms to look for + * any change in IN2_P + */ + usleep_range(10000, 10100); + output_mv = wcd_measure_adc_once(mbhc, MUX_CTL_IN2P); + + /* Check for fake removal */ + if ((output_mv <= adc_threshold) && retry > FAKE_REM_RETRY_ATTEMPTS) + goto exit; + } while (!time_after(jiffies, timeout)); + + /* + * ADC COMPLETE and ELEC_REM interrupts are both enabled for + * HEADPHONE, need to reject the ADC COMPLETE interrupt which + * follows ELEC_REM one when HEADPHONE is removed. + */ + if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADPHONE) + mbhc->extn_cable_hph_rem = true; + + wcd_mbhc_write_field(mbhc, WCD_MBHC_DETECTION_DONE, 0); + wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_MODE, 0); + wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_EN, 0); + wcd_mbhc_elec_hs_report_unplug(mbhc); + wcd_mbhc_write_field(mbhc, WCD_MBHC_BTN_ISRC_CTL, 0); + + if (hphpa_on) { + hphpa_on = false; + wcd_mbhc_write_field(mbhc, WCD_MBHC_HPH_PA_EN, 3); + } +exit: + mutex_unlock(&mbhc->lock); + return IRQ_HANDLED; +} + +static irqreturn_t wcd_mbhc_adc_hs_ins_irq(int irq, void *data) +{ + struct wcd_mbhc *mbhc = data; + u8 clamp_state = 0; + u8 clamp_retry = WCD_MBHC_FAKE_INS_RETRY; + + /* + * ADC COMPLETE and ELEC_REM interrupts are both enabled for HEADPHONE, + * need to reject the ADC COMPLETE interrupt which follows ELEC_REM one + * when HEADPHONE is removed. + */ + if (mbhc->extn_cable_hph_rem == true) { + mbhc->extn_cable_hph_rem = false; + return IRQ_HANDLED; + } + + do { + clamp_state = wcd_mbhc_read_field(mbhc, WCD_MBHC_IN2P_CLAMP_STATE); + if (clamp_state) + return IRQ_HANDLED; + /* + * check clamp for 120ms but at 30ms chunks to leave + * room for other interrupts to be processed + */ + usleep_range(30000, 30100); + } while (--clamp_retry); + + /* + * If current plug is headphone then there is no chance to + * get ADC complete interrupt, so connected cable should be + * headset not headphone. + */ + if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADPHONE) { + disable_irq_nosync(mbhc->intr_ids->mbhc_hs_ins_intr); + wcd_mbhc_write_field(mbhc, WCD_MBHC_DETECTION_DONE, 1); + wcd_mbhc_find_plug_and_report(mbhc, MBHC_PLUG_TYPE_HEADSET); + return IRQ_HANDLED; + } + + return IRQ_HANDLED; +} + +int wcd_mbhc_get_impedance(struct wcd_mbhc *mbhc, uint32_t *zl, uint32_t *zr) +{ + *zl = mbhc->zl; + *zr = mbhc->zr; + + if (*zl && *zr) + return 0; + else + return -EINVAL; +} +EXPORT_SYMBOL(wcd_mbhc_get_impedance); + +void wcd_mbhc_set_hph_type(struct wcd_mbhc *mbhc, int hph_type) +{ + mbhc->hph_type = hph_type; +} +EXPORT_SYMBOL(wcd_mbhc_set_hph_type); + +int wcd_mbhc_get_hph_type(struct wcd_mbhc *mbhc) +{ + return mbhc->hph_type; +} +EXPORT_SYMBOL(wcd_mbhc_get_hph_type); + +int wcd_mbhc_start(struct wcd_mbhc *mbhc, struct wcd_mbhc_config *cfg, + struct snd_soc_jack *jack) +{ + if (!mbhc || !cfg || !jack) + return -EINVAL; + + mbhc->cfg = cfg; + mbhc->jack = jack; + + return wcd_mbhc_initialise(mbhc); +} +EXPORT_SYMBOL(wcd_mbhc_start); + +void wcd_mbhc_stop(struct wcd_mbhc *mbhc) +{ + mbhc->current_plug = MBHC_PLUG_TYPE_NONE; + mbhc->hph_status = 0; + disable_irq_nosync(mbhc->intr_ids->hph_left_ocp); + disable_irq_nosync(mbhc->intr_ids->hph_right_ocp); +} +EXPORT_SYMBOL(wcd_mbhc_stop); + +int wcd_dt_parse_mbhc_data(struct device *dev, struct wcd_mbhc_config *cfg) +{ + struct device_node *np = dev->of_node; + int ret, i, microvolt; + + if (of_property_read_bool(np, "qcom,hphl-jack-type-normally-closed")) + cfg->hphl_swh = false; + else + cfg->hphl_swh = true; + + if (of_property_read_bool(np, "qcom,ground-jack-type-normally-closed")) + cfg->gnd_swh = false; + else + cfg->gnd_swh = true; + + ret = of_property_read_u32(np, "qcom,mbhc-headset-vthreshold-microvolt", + µvolt); + if (ret) + dev_dbg(dev, "missing qcom,mbhc-hs-mic-max-vthreshold--microvolt in dt node\n"); + else + cfg->hs_thr = microvolt/1000; + + ret = of_property_read_u32(np, "qcom,mbhc-headphone-vthreshold-microvolt", + µvolt); + if (ret) + dev_dbg(dev, "missing qcom,mbhc-hs-mic-min-vthreshold-microvolt entry\n"); + else + cfg->hph_thr = microvolt/1000; + + ret = of_property_read_u32_array(np, + "qcom,mbhc-buttons-vthreshold-microvolt", + &cfg->btn_high[0], + WCD_MBHC_DEF_BUTTONS); + if (ret) + dev_err(dev, "missing qcom,mbhc-buttons-vthreshold-microvolt entry\n"); + + for (i = 0; i < WCD_MBHC_DEF_BUTTONS; i++) { + if (ret) /* default voltage */ + cfg->btn_high[i] = 500000; + else + /* Micro to Milli Volts */ + cfg->btn_high[i] = cfg->btn_high[i]/1000; + } + + return 0; +} +EXPORT_SYMBOL(wcd_dt_parse_mbhc_data); + +struct wcd_mbhc *wcd_mbhc_init(struct snd_soc_component *component, + const struct wcd_mbhc_cb *mbhc_cb, + const struct wcd_mbhc_intr *intr_ids, + struct wcd_mbhc_field *fields, + bool impedance_det_en) +{ + struct device *dev = component->dev; + struct wcd_mbhc *mbhc; + int ret; + + if (!intr_ids || !fields || !mbhc_cb || !mbhc_cb->mbhc_bias || !mbhc_cb->set_btn_thr) { + dev_err(dev, "%s: Insufficient mbhc configuration\n", __func__); + return ERR_PTR(-EINVAL); + } + + mbhc = devm_kzalloc(dev, sizeof(*mbhc), GFP_KERNEL); + if (!mbhc) + return ERR_PTR(-ENOMEM); + + mbhc->component = component; + mbhc->dev = dev; + mbhc->intr_ids = intr_ids; + mbhc->mbhc_cb = mbhc_cb; + mbhc->fields = fields; + mbhc->mbhc_detection_logic = WCD_DETECTION_ADC; + + if (mbhc_cb->compute_impedance) + mbhc->impedance_detect = impedance_det_en; + + INIT_DELAYED_WORK(&mbhc->mbhc_btn_dwork, wcd_btn_long_press_fn); + + mutex_init(&mbhc->lock); + + INIT_WORK(&mbhc->correct_plug_swch, wcd_correct_swch_plug); + + ret = devm_request_threaded_irq(dev, mbhc->intr_ids->mbhc_sw_intr, NULL, + wcd_mbhc_mech_plug_detect_irq, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + "mbhc sw intr", mbhc); + if (ret) + goto err; + + ret = devm_request_threaded_irq(dev, mbhc->intr_ids->mbhc_btn_press_intr, NULL, + wcd_mbhc_btn_press_handler, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + "Button Press detect", mbhc); + if (ret) + goto err; + + ret = devm_request_threaded_irq(dev, mbhc->intr_ids->mbhc_btn_release_intr, NULL, + wcd_mbhc_btn_release_handler, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + "Button Release detect", mbhc); + if (ret) + goto err; + + ret = devm_request_threaded_irq(dev, mbhc->intr_ids->mbhc_hs_ins_intr, NULL, + wcd_mbhc_adc_hs_ins_irq, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + "Elect Insert", mbhc); + if (ret) + goto err; + + disable_irq_nosync(mbhc->intr_ids->mbhc_hs_ins_intr); + + ret = devm_request_threaded_irq(dev, mbhc->intr_ids->mbhc_hs_rem_intr, NULL, + wcd_mbhc_adc_hs_rem_irq, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + "Elect Remove", mbhc); + if (ret) + goto err; + + disable_irq_nosync(mbhc->intr_ids->mbhc_hs_rem_intr); + + ret = devm_request_threaded_irq(dev, mbhc->intr_ids->hph_left_ocp, NULL, + wcd_mbhc_hphl_ocp_irq, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + "HPH_L OCP detect", mbhc); + if (ret) + goto err; + + ret = devm_request_threaded_irq(dev, mbhc->intr_ids->hph_right_ocp, NULL, + wcd_mbhc_hphr_ocp_irq, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + "HPH_R OCP detect", mbhc); + if (ret) + goto err; + + return mbhc; +err: + dev_err(dev, "Failed to request mbhc interrupts %d\n", ret); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL(wcd_mbhc_init); + +void wcd_mbhc_deinit(struct wcd_mbhc *mbhc) +{ + mutex_lock(&mbhc->lock); + wcd_cancel_hs_detect_plug(mbhc, &mbhc->correct_plug_swch); + mutex_unlock(&mbhc->lock); +} +EXPORT_SYMBOL(wcd_mbhc_deinit); + +static int __init mbhc_init(void) +{ + return 0; +} + +static void __exit mbhc_exit(void) +{ +} + +module_init(mbhc_init); +module_exit(mbhc_exit); + +MODULE_DESCRIPTION("wcd MBHC v2 module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wcd-mbhc-v2.h b/sound/soc/codecs/wcd-mbhc-v2.h new file mode 100644 index 000000000000..006118f3e81f --- /dev/null +++ b/sound/soc/codecs/wcd-mbhc-v2.h @@ -0,0 +1,340 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __WCD_MBHC_V2_H__ +#define __WCD_MBHC_V2_H__ + +#include + +#define WCD_MBHC_FIELD(id, rreg, rmask) \ + [id] = { .reg = rreg, .mask = rmask } + +enum wcd_mbhc_field_function { + WCD_MBHC_L_DET_EN, + WCD_MBHC_GND_DET_EN, + WCD_MBHC_MECH_DETECTION_TYPE, + WCD_MBHC_MIC_CLAMP_CTL, + WCD_MBHC_ELECT_DETECTION_TYPE, + WCD_MBHC_HS_L_DET_PULL_UP_CTRL, + WCD_MBHC_HS_L_DET_PULL_UP_COMP_CTRL, + WCD_MBHC_HPHL_PLUG_TYPE, + WCD_MBHC_GND_PLUG_TYPE, + WCD_MBHC_SW_HPH_LP_100K_TO_GND, + WCD_MBHC_ELECT_SCHMT_ISRC, + WCD_MBHC_FSM_EN, + WCD_MBHC_INSREM_DBNC, + WCD_MBHC_BTN_DBNC, + WCD_MBHC_HS_VREF, + WCD_MBHC_HS_COMP_RESULT, + WCD_MBHC_IN2P_CLAMP_STATE, + WCD_MBHC_MIC_SCHMT_RESULT, + WCD_MBHC_HPHL_SCHMT_RESULT, + WCD_MBHC_HPHR_SCHMT_RESULT, + WCD_MBHC_OCP_FSM_EN, + WCD_MBHC_BTN_RESULT, + WCD_MBHC_BTN_ISRC_CTL, + WCD_MBHC_ELECT_RESULT, + WCD_MBHC_MICB_CTRL, /* Pull-up and micb control */ + WCD_MBHC_HPH_CNP_WG_TIME, + WCD_MBHC_HPHR_PA_EN, + WCD_MBHC_HPHL_PA_EN, + WCD_MBHC_HPH_PA_EN, + WCD_MBHC_SWCH_LEVEL_REMOVE, + WCD_MBHC_PULLDOWN_CTRL, + WCD_MBHC_ANC_DET_EN, + WCD_MBHC_FSM_STATUS, + WCD_MBHC_MUX_CTL, + WCD_MBHC_MOISTURE_STATUS, + WCD_MBHC_HPHR_GND, + WCD_MBHC_HPHL_GND, + WCD_MBHC_HPHL_OCP_DET_EN, + WCD_MBHC_HPHR_OCP_DET_EN, + WCD_MBHC_HPHL_OCP_STATUS, + WCD_MBHC_HPHR_OCP_STATUS, + WCD_MBHC_ADC_EN, + WCD_MBHC_ADC_COMPLETE, + WCD_MBHC_ADC_TIMEOUT, + WCD_MBHC_ADC_RESULT, + WCD_MBHC_MICB2_VOUT, + WCD_MBHC_ADC_MODE, + WCD_MBHC_DETECTION_DONE, + WCD_MBHC_ELECT_ISRC_EN, + WCD_MBHC_REG_FUNC_MAX, +}; + +#define WCD_MBHC_DEF_BUTTONS 8 +#define WCD_MBHC_KEYCODE_NUM 8 +#define WCD_MBHC_USLEEP_RANGE_MARGIN_US 100 +#define WCD_MBHC_THR_HS_MICB_MV 2700 +#define WCD_MONO_HS_MIN_THR 2 + +enum wcd_mbhc_detect_logic { + WCD_DETECTION_LEGACY, + WCD_DETECTION_ADC, +}; + +enum wcd_mbhc_cs_mb_en_flag { + WCD_MBHC_EN_CS = 0, + WCD_MBHC_EN_MB, + WCD_MBHC_EN_PULLUP, + WCD_MBHC_EN_NONE, +}; + +enum { + WCD_MBHC_ELEC_HS_INS, + WCD_MBHC_ELEC_HS_REM, +}; + +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, +}; + +enum pa_dac_ack_flags { + WCD_MBHC_HPHL_PA_OFF_ACK = 0, + WCD_MBHC_HPHR_PA_OFF_ACK, +}; + +enum wcd_mbhc_btn_det_mem { + WCD_MBHC_BTN_DET_V_BTN_LOW, + WCD_MBHC_BTN_DET_V_BTN_HIGH +}; + +enum { + MIC_BIAS_1 = 1, + MIC_BIAS_2, + MIC_BIAS_3, + MIC_BIAS_4 +}; + +enum { + MICB_PULLUP_ENABLE, + MICB_PULLUP_DISABLE, + MICB_ENABLE, + MICB_DISABLE, +}; + +enum wcd_notify_event { + WCD_EVENT_INVALID, + /* events for micbias ON and OFF */ + WCD_EVENT_PRE_MICBIAS_2_OFF, + WCD_EVENT_POST_MICBIAS_2_OFF, + WCD_EVENT_PRE_MICBIAS_2_ON, + WCD_EVENT_POST_MICBIAS_2_ON, + WCD_EVENT_PRE_DAPM_MICBIAS_2_OFF, + WCD_EVENT_POST_DAPM_MICBIAS_2_OFF, + WCD_EVENT_PRE_DAPM_MICBIAS_2_ON, + WCD_EVENT_POST_DAPM_MICBIAS_2_ON, + /* events for PA ON and OFF */ + WCD_EVENT_PRE_HPHL_PA_ON, + WCD_EVENT_POST_HPHL_PA_OFF, + WCD_EVENT_PRE_HPHR_PA_ON, + WCD_EVENT_POST_HPHR_PA_OFF, + WCD_EVENT_PRE_HPHL_PA_OFF, + WCD_EVENT_PRE_HPHR_PA_OFF, + WCD_EVENT_OCP_OFF, + WCD_EVENT_OCP_ON, + WCD_EVENT_LAST, +}; + +enum wcd_mbhc_event_state { + WCD_MBHC_EVENT_PA_HPHL, + WCD_MBHC_EVENT_PA_HPHR, +}; + +enum wcd_mbhc_hph_type { + WCD_MBHC_HPH_NONE = 0, + WCD_MBHC_HPH_MONO, + WCD_MBHC_HPH_STEREO, +}; + +/* + * These enum definitions are directly mapped to the register + * definitions + */ + +enum mbhc_hs_pullup_iref { + I_DEFAULT = -1, + I_OFF = 0, + I_1P0_UA, + I_2P0_UA, + I_3P0_UA, +}; + +enum mbhc_hs_pullup_iref_v2 { + HS_PULLUP_I_DEFAULT = -1, + HS_PULLUP_I_3P0_UA = 0, + HS_PULLUP_I_2P25_UA, + HS_PULLUP_I_1P5_UA, + HS_PULLUP_I_0P75_UA, + HS_PULLUP_I_1P125_UA = 0x05, + HS_PULLUP_I_0P375_UA = 0x07, + HS_PULLUP_I_2P0_UA, + HS_PULLUP_I_1P0_UA = 0x0A, + HS_PULLUP_I_0P5_UA, + HS_PULLUP_I_0P25_UA = 0x0F, + HS_PULLUP_I_0P125_UA = 0x17, + HS_PULLUP_I_OFF, +}; + +enum mbhc_moisture_rref { + R_OFF, + R_24_KOHM, + R_84_KOHM, + R_184_KOHM, +}; + +struct wcd_mbhc_config { + int btn_high[WCD_MBHC_DEF_BUTTONS]; + int btn_low[WCD_MBHC_DEF_BUTTONS]; + int v_hs_max; + int num_btn; + bool mono_stero_detection; + bool (*swap_gnd_mic)(struct snd_soc_component *component, bool active); + bool hs_ext_micbias; + bool gnd_det_en; + uint32_t linein_th; + bool moisture_en; + int mbhc_micbias; + int anc_micbias; + bool moisture_duty_cycle_en; + bool hphl_swh; /*track HPHL switch NC / NO */ + bool gnd_swh; /*track GND switch NC / NO */ + u32 hs_thr; + u32 hph_thr; + u32 micb_mv; + u32 moist_vref; + u32 moist_iref; + u32 moist_rref; +}; + +struct wcd_mbhc_intr { + int mbhc_sw_intr; + int mbhc_btn_press_intr; + int mbhc_btn_release_intr; + int mbhc_hs_ins_intr; + int mbhc_hs_rem_intr; + int hph_left_ocp; + int hph_right_ocp; +}; + +struct wcd_mbhc_field { + u16 reg; + u8 mask; +}; + +struct wcd_mbhc; + +struct wcd_mbhc_cb { + void (*update_cross_conn_thr)(struct snd_soc_component *component); + void (*get_micbias_val)(struct snd_soc_component *component, int *mb); + void (*bcs_enable)(struct snd_soc_component *component, bool bcs_enable); + void (*compute_impedance)(struct snd_soc_component *component, + uint32_t *zl, uint32_t *zr); + void (*set_micbias_value)(struct snd_soc_component *component); + void (*set_auto_zeroing)(struct snd_soc_component *component, + bool enable); + void (*clk_setup)(struct snd_soc_component *component, bool enable); + bool (*micbias_enable_status)(struct snd_soc_component *component, int micb_num); + void (*mbhc_bias)(struct snd_soc_component *component, bool enable); + void (*set_btn_thr)(struct snd_soc_component *component, + int *btn_low, int *btn_high, + int num_btn, bool is_micbias); + void (*hph_pull_up_control)(struct snd_soc_component *component, + enum mbhc_hs_pullup_iref); + int (*mbhc_micbias_control)(struct snd_soc_component *component, + int micb_num, int req); + void (*mbhc_micb_ramp_control)(struct snd_soc_component *component, + bool enable); + bool (*extn_use_mb)(struct snd_soc_component *component); + int (*mbhc_micb_ctrl_thr_mic)(struct snd_soc_component *component, + int micb_num, bool req_en); + void (*mbhc_gnd_det_ctrl)(struct snd_soc_component *component, + bool enable); + void (*hph_pull_down_ctrl)(struct snd_soc_component *component, + bool enable); + void (*mbhc_moisture_config)(struct snd_soc_component *component); + void (*update_anc_state)(struct snd_soc_component *component, + bool enable, int anc_num); + void (*hph_pull_up_control_v2)(struct snd_soc_component *component, + int pull_up_cur); + bool (*mbhc_get_moisture_status)(struct snd_soc_component *component); + void (*mbhc_moisture_polling_ctrl)(struct snd_soc_component *component, bool enable); + void (*mbhc_moisture_detect_en)(struct snd_soc_component *component, bool enable); +}; + +#if IS_ENABLED(CONFIG_SND_SOC_WCD_MBHC) +int wcd_dt_parse_mbhc_data(struct device *dev, struct wcd_mbhc_config *cfg); +int wcd_mbhc_start(struct wcd_mbhc *mbhc, struct wcd_mbhc_config *mbhc_cfg, + struct snd_soc_jack *jack); +void wcd_mbhc_stop(struct wcd_mbhc *mbhc); +void wcd_mbhc_set_hph_type(struct wcd_mbhc *mbhc, int hph_type); +int wcd_mbhc_get_hph_type(struct wcd_mbhc *mbhc); +struct wcd_mbhc *wcd_mbhc_init(struct snd_soc_component *component, + const struct wcd_mbhc_cb *mbhc_cb, + const struct wcd_mbhc_intr *mbhc_cdc_intr_ids, + struct wcd_mbhc_field *fields, + bool impedance_det_en); +int wcd_mbhc_get_impedance(struct wcd_mbhc *mbhc, uint32_t *zl, + uint32_t *zr); +void wcd_mbhc_deinit(struct wcd_mbhc *mbhc); +int wcd_mbhc_event_notify(struct wcd_mbhc *mbhc, unsigned long event); + +#else +static inline int wcd_dt_parse_mbhc_data(struct device *dev, + struct wcd_mbhc_config *cfg) +{ + return -ENOTSUPP; +} + +static inline void wcd_mbhc_stop(struct wcd_mbhc *mbhc) +{ +} + +static inline struct wcd_mbhc *wcd_mbhc_init(struct snd_soc_component *component, + const struct wcd_mbhc_cb *mbhc_cb, + const struct wcd_mbhc_intr *mbhc_cdc_intr_ids, + struct wcd_mbhc_field *fields, + bool impedance_det_en) +{ + return ERR_PTR(-ENOTSUPP); +} + +static inline void wcd_mbhc_set_hph_type(struct wcd_mbhc *mbhc, int hph_type) +{ +} + +static inline int wcd_mbhc_get_hph_type(struct wcd_mbhc *mbhc) +{ + return -ENOTSUPP; +} + +static inline int wcd_mbhc_event_notify(struct wcd_mbhc *mbhc, unsigned long event) +{ + return -ENOTSUPP; +} + +static inline int wcd_mbhc_start(struct wcd_mbhc *mbhc, + struct wcd_mbhc_config *mbhc_cfg, + struct snd_soc_jack *jack) +{ + return 0; +} + +static inline int wcd_mbhc_get_impedance(struct wcd_mbhc *mbhc, + uint32_t *zl, + uint32_t *zr) +{ + *zl = 0; + *zr = 0; + return -EINVAL; +} +static inline void wcd_mbhc_deinit(struct wcd_mbhc *mbhc) +{ +} +#endif + +#endif /* __WCD_MBHC_V2_H__ */ diff --git a/sound/soc/codecs/wcd934x.c b/sound/soc/codecs/wcd934x.c index 046874ef490e..16fd1ab62609 100644 --- a/sound/soc/codecs/wcd934x.c +++ b/sound/soc/codecs/wcd934x.c @@ -21,6 +21,7 @@ #include #include #include "wcd-clsh-v2.h" +#include "wcd-mbhc-v2.h" #define WCD934X_RATES_MASK (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ @@ -131,6 +132,24 @@ } \ } +/* Z value defined in milliohm */ +#define WCD934X_ZDET_VAL_32 32000 +#define WCD934X_ZDET_VAL_400 400000 +#define WCD934X_ZDET_VAL_1200 1200000 +#define WCD934X_ZDET_VAL_100K 100000000 +/* Z floating defined in ohms */ +#define WCD934X_ZDET_FLOATING_IMPEDANCE 0x0FFFFFFE + +#define WCD934X_ZDET_NUM_MEASUREMENTS 900 +#define WCD934X_MBHC_GET_C1(c) ((c & 0xC000) >> 14) +#define WCD934X_MBHC_GET_X1(x) (x & 0x3FFF) +/* Z value compared in milliOhm */ +#define WCD934X_MBHC_IS_SECOND_RAMP_REQUIRED(z) ((z > 400000) || (z < 32000)) +#define WCD934X_MBHC_ZDET_CONST (86 * 16384) +#define WCD934X_MBHC_MOISTURE_RREF R_24_KOHM +#define WCD934X_MBHC_MAX_BUTTONS (8) +#define WCD_MBHC_HS_V_MAX 1600 + #define WCD934X_INTERPOLATOR_PATH(id) \ {"RX INT" #id "_1 MIX1 INP0", "RX0", "SLIM RX0"}, \ {"RX INT" #id "_1 MIX1 INP0", "RX1", "SLIM RX1"}, \ @@ -287,12 +306,7 @@ {"AIF3_CAP Mixer", "SLIM TX" #id, "SLIM TX" #id }, \ {"SLIM TX" #id, NULL, "CDC_IF TX" #id " MUX"} -enum { - MIC_BIAS_1 = 1, - MIC_BIAS_2, - MIC_BIAS_3, - MIC_BIAS_4 -}; +#define WCD934X_MAX_MICBIAS MIC_BIAS_4 enum { SIDO_SOURCE_INTERNAL, @@ -486,6 +500,15 @@ static struct interp_sample_rate sr_val_tbl[] = { {352800, 0xC}, }; +struct wcd934x_mbhc_zdet_param { + u16 ldo_ctl; + u16 noff; + u16 nshift; + u16 btn5; + u16 btn6; + u16 btn7; +}; + struct wcd_slim_codec_dai_data { struct list_head slim_ch_list; struct slim_stream_config sconfig; @@ -541,6 +564,18 @@ struct wcd934x_codec { int comp_enabled[COMPANDER_MAX]; int sysclk_users; struct mutex sysclk_mutex; + /* mbhc module */ + struct wcd_mbhc *mbhc; + struct wcd_mbhc_config mbhc_cfg; + struct wcd_mbhc_intr intr_ids; + bool mbhc_started; + struct mutex micb_lock; + u32 micb_ref[WCD934X_MAX_MICBIAS]; + u32 pullup_ref[WCD934X_MAX_MICBIAS]; + u32 micb1_mv; + u32 micb2_mv; + u32 micb3_mv; + u32 micb4_mv; }; #define to_wcd934x_codec(_hw) container_of(_hw, struct wcd934x_codec, hw) @@ -1183,6 +1218,57 @@ static const struct soc_enum cdc_if_tx13_mux_enum = SOC_ENUM_SINGLE(WCD934X_DATA_HUB_SB_TX13_INP_CFG, 0, ARRAY_SIZE(cdc_if_tx13_mux_text), cdc_if_tx13_mux_text); +static struct wcd_mbhc_field wcd_mbhc_fields[WCD_MBHC_REG_FUNC_MAX] = { + WCD_MBHC_FIELD(WCD_MBHC_L_DET_EN, WCD934X_ANA_MBHC_MECH, 0x80), + WCD_MBHC_FIELD(WCD_MBHC_GND_DET_EN, WCD934X_ANA_MBHC_MECH, 0x40), + WCD_MBHC_FIELD(WCD_MBHC_MECH_DETECTION_TYPE, WCD934X_ANA_MBHC_MECH, 0x20), + WCD_MBHC_FIELD(WCD_MBHC_MIC_CLAMP_CTL, WCD934X_MBHC_NEW_PLUG_DETECT_CTL, 0x30), + WCD_MBHC_FIELD(WCD_MBHC_ELECT_DETECTION_TYPE, WCD934X_ANA_MBHC_ELECT, 0x08), + WCD_MBHC_FIELD(WCD_MBHC_HS_L_DET_PULL_UP_CTRL, WCD934X_MBHC_NEW_PLUG_DETECT_CTL, 0xC0), + WCD_MBHC_FIELD(WCD_MBHC_HS_L_DET_PULL_UP_COMP_CTRL, WCD934X_ANA_MBHC_MECH, 0x04), + WCD_MBHC_FIELD(WCD_MBHC_HPHL_PLUG_TYPE, WCD934X_ANA_MBHC_MECH, 0x10), + WCD_MBHC_FIELD(WCD_MBHC_GND_PLUG_TYPE, WCD934X_ANA_MBHC_MECH, 0x08), + WCD_MBHC_FIELD(WCD_MBHC_SW_HPH_LP_100K_TO_GND, WCD934X_ANA_MBHC_MECH, 0x01), + WCD_MBHC_FIELD(WCD_MBHC_ELECT_SCHMT_ISRC, WCD934X_ANA_MBHC_ELECT, 0x06), + WCD_MBHC_FIELD(WCD_MBHC_FSM_EN, WCD934X_ANA_MBHC_ELECT, 0x80), + WCD_MBHC_FIELD(WCD_MBHC_INSREM_DBNC, WCD934X_MBHC_NEW_PLUG_DETECT_CTL, 0x0F), + WCD_MBHC_FIELD(WCD_MBHC_BTN_DBNC, WCD934X_MBHC_NEW_CTL_1, 0x03), + WCD_MBHC_FIELD(WCD_MBHC_HS_VREF, WCD934X_MBHC_NEW_CTL_2, 0x03), + WCD_MBHC_FIELD(WCD_MBHC_HS_COMP_RESULT, WCD934X_ANA_MBHC_RESULT_3, 0x08), + WCD_MBHC_FIELD(WCD_MBHC_IN2P_CLAMP_STATE, WCD934X_ANA_MBHC_RESULT_3, 0x10), + WCD_MBHC_FIELD(WCD_MBHC_MIC_SCHMT_RESULT, WCD934X_ANA_MBHC_RESULT_3, 0x20), + WCD_MBHC_FIELD(WCD_MBHC_HPHL_SCHMT_RESULT, WCD934X_ANA_MBHC_RESULT_3, 0x80), + WCD_MBHC_FIELD(WCD_MBHC_HPHR_SCHMT_RESULT, WCD934X_ANA_MBHC_RESULT_3, 0x40), + WCD_MBHC_FIELD(WCD_MBHC_OCP_FSM_EN, WCD934X_HPH_OCP_CTL, 0x10), + WCD_MBHC_FIELD(WCD_MBHC_BTN_RESULT, WCD934X_ANA_MBHC_RESULT_3, 0x07), + WCD_MBHC_FIELD(WCD_MBHC_BTN_ISRC_CTL, WCD934X_ANA_MBHC_ELECT, 0x70), + WCD_MBHC_FIELD(WCD_MBHC_ELECT_RESULT, WCD934X_ANA_MBHC_RESULT_3, 0xFF), + WCD_MBHC_FIELD(WCD_MBHC_MICB_CTRL, WCD934X_ANA_MICB2, 0xC0), + WCD_MBHC_FIELD(WCD_MBHC_HPH_CNP_WG_TIME, WCD934X_HPH_CNP_WG_TIME, 0xFF), + WCD_MBHC_FIELD(WCD_MBHC_HPHR_PA_EN, WCD934X_ANA_HPH, 0x40), + WCD_MBHC_FIELD(WCD_MBHC_HPHL_PA_EN, WCD934X_ANA_HPH, 0x80), + WCD_MBHC_FIELD(WCD_MBHC_HPH_PA_EN, WCD934X_ANA_HPH, 0xC0), + WCD_MBHC_FIELD(WCD_MBHC_SWCH_LEVEL_REMOVE, WCD934X_ANA_MBHC_RESULT_3, 0x10), + WCD_MBHC_FIELD(WCD_MBHC_ANC_DET_EN, WCD934X_MBHC_CTL_BCS, 0x02), + WCD_MBHC_FIELD(WCD_MBHC_FSM_STATUS, WCD934X_MBHC_STATUS_SPARE_1, 0x01), + WCD_MBHC_FIELD(WCD_MBHC_MUX_CTL, WCD934X_MBHC_NEW_CTL_2, 0x70), + WCD_MBHC_FIELD(WCD_MBHC_MOISTURE_STATUS, WCD934X_MBHC_NEW_FSM_STATUS, 0x20), + WCD_MBHC_FIELD(WCD_MBHC_HPHR_GND, WCD934X_HPH_PA_CTL2, 0x40), + WCD_MBHC_FIELD(WCD_MBHC_HPHL_GND, WCD934X_HPH_PA_CTL2, 0x10), + WCD_MBHC_FIELD(WCD_MBHC_HPHL_OCP_DET_EN, WCD934X_HPH_L_TEST, 0x01), + WCD_MBHC_FIELD(WCD_MBHC_HPHR_OCP_DET_EN, WCD934X_HPH_R_TEST, 0x01), + WCD_MBHC_FIELD(WCD_MBHC_HPHL_OCP_STATUS, WCD934X_INTR_PIN1_STATUS0, 0x04), + WCD_MBHC_FIELD(WCD_MBHC_HPHR_OCP_STATUS, WCD934X_INTR_PIN1_STATUS0, 0x08), + WCD_MBHC_FIELD(WCD_MBHC_ADC_EN, WCD934X_MBHC_NEW_CTL_1, 0x08), + WCD_MBHC_FIELD(WCD_MBHC_ADC_COMPLETE, WCD934X_MBHC_NEW_FSM_STATUS, 0x40), + WCD_MBHC_FIELD(WCD_MBHC_ADC_TIMEOUT, WCD934X_MBHC_NEW_FSM_STATUS, 0x80), + WCD_MBHC_FIELD(WCD_MBHC_ADC_RESULT, WCD934X_MBHC_NEW_ADC_RESULT, 0xFF), + WCD_MBHC_FIELD(WCD_MBHC_MICB2_VOUT, WCD934X_ANA_MICB2, 0x3F), + WCD_MBHC_FIELD(WCD_MBHC_ADC_MODE, WCD934X_MBHC_NEW_CTL_1, 0x10), + WCD_MBHC_FIELD(WCD_MBHC_DETECTION_DONE, WCD934X_MBHC_NEW_CTL_1, 0x04), + WCD_MBHC_FIELD(WCD_MBHC_ELECT_ISRC_EN, WCD934X_ANA_MBHC_ZDET, 0x02), +}; + static int wcd934x_set_sido_input_src(struct wcd934x_codec *wcd, int sido_src) { if (sido_src == wcd->sido_input_src) @@ -2127,7 +2213,8 @@ static struct clk *wcd934x_register_mclk_output(struct wcd934x_codec *wcd) return NULL; } -static int wcd934x_get_micbias_val(struct device *dev, const char *micbias) +static int wcd934x_get_micbias_val(struct device *dev, const char *micbias, + u32 *micb_mv) { int mv; @@ -2145,6 +2232,8 @@ static int wcd934x_get_micbias_val(struct device *dev, const char *micbias) mv = WCD934X_DEF_MICBIAS_MV; } + *micb_mv = mv; + return (mv - 1000) / 50; } @@ -2155,13 +2244,17 @@ static int wcd934x_init_dmic(struct snd_soc_component *comp) u32 def_dmic_rate, dmic_clk_drv; vout_ctl_1 = wcd934x_get_micbias_val(comp->dev, - "qcom,micbias1-microvolt"); + "qcom,micbias1-microvolt", + &wcd->micb1_mv); vout_ctl_2 = wcd934x_get_micbias_val(comp->dev, - "qcom,micbias2-microvolt"); + "qcom,micbias2-microvolt", + &wcd->micb2_mv); vout_ctl_3 = wcd934x_get_micbias_val(comp->dev, - "qcom,micbias3-microvolt"); + "qcom,micbias3-microvolt", + &wcd->micb3_mv); vout_ctl_4 = wcd934x_get_micbias_val(comp->dev, - "qcom,micbias4-microvolt"); + "qcom,micbias4-microvolt", + &wcd->micb4_mv); snd_soc_component_update_bits(comp, WCD934X_ANA_MICB1, WCD934X_MICB_VAL_MASK, vout_ctl_1); @@ -2287,6 +2380,695 @@ static irqreturn_t wcd934x_slim_irq_handler(int irq, void *data) return ret; } +static void wcd934x_mbhc_clk_setup(struct snd_soc_component *component, + bool enable) +{ + snd_soc_component_write_field(component, WCD934X_MBHC_NEW_CTL_1, + WCD934X_MBHC_CTL_RCO_EN_MASK, enable); +} + +static void wcd934x_mbhc_mbhc_bias_control(struct snd_soc_component *component, + bool enable) +{ + snd_soc_component_write_field(component, WCD934X_ANA_MBHC_ELECT, + WCD934X_ANA_MBHC_BIAS_EN, enable); +} + +static void wcd934x_mbhc_program_btn_thr(struct snd_soc_component *component, + int *btn_low, int *btn_high, + int num_btn, bool is_micbias) +{ + int i, vth; + + if (num_btn > WCD_MBHC_DEF_BUTTONS) { + dev_err(component->dev, "%s: invalid number of buttons: %d\n", + __func__, num_btn); + return; + } + + for (i = 0; i < num_btn; i++) { + vth = ((btn_high[i] * 2) / 25) & 0x3F; + snd_soc_component_write_field(component, WCD934X_ANA_MBHC_BTN0 + i, + WCD934X_MBHC_BTN_VTH_MASK, vth); + } +} + +static bool wcd934x_mbhc_micb_en_status(struct snd_soc_component *component, int micb_num) +{ + u8 val; + + if (micb_num == MIC_BIAS_2) { + val = snd_soc_component_read_field(component, WCD934X_ANA_MICB2, + WCD934X_ANA_MICB2_ENABLE_MASK); + if (val == WCD934X_MICB_ENABLE) + return true; + } + return false; +} + +static void wcd934x_mbhc_hph_l_pull_up_control(struct snd_soc_component *component, + enum mbhc_hs_pullup_iref pull_up_cur) +{ + /* Default pull up current to 2uA */ + if (pull_up_cur < I_OFF || pull_up_cur > I_3P0_UA || + pull_up_cur == I_DEFAULT) + pull_up_cur = I_2P0_UA; + + + snd_soc_component_write_field(component, WCD934X_MBHC_NEW_PLUG_DETECT_CTL, + WCD934X_HSDET_PULLUP_C_MASK, pull_up_cur); +} + +static int wcd934x_micbias_control(struct snd_soc_component *component, + int micb_num, int req, bool is_dapm) +{ + struct wcd934x_codec *wcd934x = snd_soc_component_get_drvdata(component); + int micb_index = micb_num - 1; + u16 micb_reg; + + switch (micb_num) { + case MIC_BIAS_1: + micb_reg = WCD934X_ANA_MICB1; + break; + case MIC_BIAS_2: + micb_reg = WCD934X_ANA_MICB2; + break; + case MIC_BIAS_3: + micb_reg = WCD934X_ANA_MICB3; + break; + case MIC_BIAS_4: + micb_reg = WCD934X_ANA_MICB4; + break; + default: + dev_err(component->dev, "%s: Invalid micbias number: %d\n", + __func__, micb_num); + return -EINVAL; + }; + mutex_lock(&wcd934x->micb_lock); + + switch (req) { + case MICB_PULLUP_ENABLE: + wcd934x->pullup_ref[micb_index]++; + if ((wcd934x->pullup_ref[micb_index] == 1) && + (wcd934x->micb_ref[micb_index] == 0)) + snd_soc_component_write_field(component, micb_reg, + WCD934X_ANA_MICB_EN_MASK, + WCD934X_MICB_PULL_UP); + break; + case MICB_PULLUP_DISABLE: + if (wcd934x->pullup_ref[micb_index] > 0) + wcd934x->pullup_ref[micb_index]--; + + if ((wcd934x->pullup_ref[micb_index] == 0) && + (wcd934x->micb_ref[micb_index] == 0)) + snd_soc_component_write_field(component, micb_reg, + WCD934X_ANA_MICB_EN_MASK, 0); + break; + case MICB_ENABLE: + wcd934x->micb_ref[micb_index]++; + if (wcd934x->micb_ref[micb_index] == 1) { + snd_soc_component_write_field(component, micb_reg, + WCD934X_ANA_MICB_EN_MASK, + WCD934X_MICB_ENABLE); + if (micb_num == MIC_BIAS_2) + wcd_mbhc_event_notify(wcd934x->mbhc, + WCD_EVENT_POST_MICBIAS_2_ON); + } + + if (micb_num == MIC_BIAS_2 && is_dapm) + wcd_mbhc_event_notify(wcd934x->mbhc, + WCD_EVENT_POST_DAPM_MICBIAS_2_ON); + break; + case MICB_DISABLE: + if (wcd934x->micb_ref[micb_index] > 0) + wcd934x->micb_ref[micb_index]--; + + if ((wcd934x->micb_ref[micb_index] == 0) && + (wcd934x->pullup_ref[micb_index] > 0)) + snd_soc_component_write_field(component, micb_reg, + WCD934X_ANA_MICB_EN_MASK, + WCD934X_MICB_PULL_UP); + else if ((wcd934x->micb_ref[micb_index] == 0) && + (wcd934x->pullup_ref[micb_index] == 0)) { + if (micb_num == MIC_BIAS_2) + wcd_mbhc_event_notify(wcd934x->mbhc, + WCD_EVENT_PRE_MICBIAS_2_OFF); + + snd_soc_component_write_field(component, micb_reg, + WCD934X_ANA_MICB_EN_MASK, 0); + if (micb_num == MIC_BIAS_2) + wcd_mbhc_event_notify(wcd934x->mbhc, + WCD_EVENT_POST_MICBIAS_2_OFF); + } + if (is_dapm && micb_num == MIC_BIAS_2) + wcd_mbhc_event_notify(wcd934x->mbhc, + WCD_EVENT_POST_DAPM_MICBIAS_2_OFF); + break; + }; + + mutex_unlock(&wcd934x->micb_lock); + + return 0; +} + +static int wcd934x_mbhc_request_micbias(struct snd_soc_component *component, + int micb_num, int req) +{ + struct wcd934x_codec *wcd = dev_get_drvdata(component->dev); + int ret; + + if (req == MICB_ENABLE) + __wcd934x_cdc_mclk_enable(wcd, true); + + ret = wcd934x_micbias_control(component, micb_num, req, false); + + if (req == MICB_DISABLE) + __wcd934x_cdc_mclk_enable(wcd, false); + + return ret; +} + +static void wcd934x_mbhc_micb_ramp_control(struct snd_soc_component *component, + bool enable) +{ + if (enable) { + snd_soc_component_write_field(component, WCD934X_ANA_MICB2_RAMP, + WCD934X_RAMP_SHIFT_CTRL_MASK, 0x3); + snd_soc_component_write_field(component, WCD934X_ANA_MICB2_RAMP, + WCD934X_RAMP_EN_MASK, 1); + } else { + snd_soc_component_write_field(component, WCD934X_ANA_MICB2_RAMP, + WCD934X_RAMP_EN_MASK, 0); + snd_soc_component_write_field(component, WCD934X_ANA_MICB2_RAMP, + WCD934X_RAMP_SHIFT_CTRL_MASK, 0); + } +} + +static int wcd934x_get_micb_vout_ctl_val(u32 micb_mv) +{ + /* min micbias voltage is 1V and maximum is 2.85V */ + if (micb_mv < 1000 || micb_mv > 2850) + return -EINVAL; + + return (micb_mv - 1000) / 50; +} + +static int wcd934x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + int req_volt, int micb_num) +{ + struct wcd934x_codec *wcd934x = snd_soc_component_get_drvdata(component); + int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en, ret = 0; + + switch (micb_num) { + case MIC_BIAS_1: + micb_reg = WCD934X_ANA_MICB1; + break; + case MIC_BIAS_2: + micb_reg = WCD934X_ANA_MICB2; + break; + case MIC_BIAS_3: + micb_reg = WCD934X_ANA_MICB3; + break; + case MIC_BIAS_4: + micb_reg = WCD934X_ANA_MICB4; + break; + default: + return -EINVAL; + } + mutex_lock(&wcd934x->micb_lock); + /* + * If requested micbias voltage is same as current micbias + * voltage, then just return. Otherwise, adjust voltage as + * per requested value. If micbias is already enabled, then + * to avoid slow micbias ramp-up or down enable pull-up + * momentarily, change the micbias value and then re-enable + * micbias. + */ + micb_en = snd_soc_component_read_field(component, micb_reg, + WCD934X_ANA_MICB_EN_MASK); + cur_vout_ctl = snd_soc_component_read_field(component, micb_reg, + WCD934X_MICB_VAL_MASK); + + req_vout_ctl = wcd934x_get_micb_vout_ctl_val(req_volt); + if (req_vout_ctl < 0) { + ret = -EINVAL; + goto exit; + } + + if (cur_vout_ctl == req_vout_ctl) { + ret = 0; + goto exit; + } + + if (micb_en == WCD934X_MICB_ENABLE) + snd_soc_component_write_field(component, micb_reg, + WCD934X_ANA_MICB_EN_MASK, + WCD934X_MICB_PULL_UP); + + snd_soc_component_write_field(component, micb_reg, + WCD934X_MICB_VAL_MASK, + req_vout_ctl); + + if (micb_en == WCD934X_MICB_ENABLE) { + snd_soc_component_write_field(component, micb_reg, + WCD934X_ANA_MICB_EN_MASK, + WCD934X_MICB_ENABLE); + /* + * Add 2ms delay as per HW requirement after enabling + * micbias + */ + usleep_range(2000, 2100); + } +exit: + mutex_unlock(&wcd934x->micb_lock); + return ret; +} + +static int wcd934x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_component *component, + int micb_num, bool req_en) +{ + struct wcd934x_codec *wcd934x = snd_soc_component_get_drvdata(component); + int rc, micb_mv; + + if (micb_num != MIC_BIAS_2) + return -EINVAL; + /* + * If device tree micbias level is already above the minimum + * voltage needed to detect threshold microphone, then do + * not change the micbias, just return. + */ + if (wcd934x->micb2_mv >= WCD_MBHC_THR_HS_MICB_MV) + return 0; + + micb_mv = req_en ? WCD_MBHC_THR_HS_MICB_MV : wcd934x->micb2_mv; + + rc = wcd934x_mbhc_micb_adjust_voltage(component, micb_mv, MIC_BIAS_2); + + return rc; +} + +static inline void wcd934x_mbhc_get_result_params(struct wcd934x_codec *wcd934x, + s16 *d1_a, u16 noff, + int32_t *zdet) +{ + int i; + int val, val1; + s16 c1; + s32 x1, d1; + int32_t denom; + int minCode_param[] = { + 3277, 1639, 820, 410, 205, 103, 52, 26 + }; + + regmap_update_bits(wcd934x->regmap, WCD934X_ANA_MBHC_ZDET, 0x20, 0x20); + for (i = 0; i < WCD934X_ZDET_NUM_MEASUREMENTS; i++) { + regmap_read(wcd934x->regmap, WCD934X_ANA_MBHC_RESULT_2, &val); + if (val & 0x80) + break; + } + val = val << 0x8; + regmap_read(wcd934x->regmap, WCD934X_ANA_MBHC_RESULT_1, &val1); + val |= val1; + regmap_update_bits(wcd934x->regmap, WCD934X_ANA_MBHC_ZDET, 0x20, 0x00); + x1 = WCD934X_MBHC_GET_X1(val); + c1 = WCD934X_MBHC_GET_C1(val); + /* If ramp is not complete, give additional 5ms */ + if ((c1 < 2) && x1) + usleep_range(5000, 5050); + + if (!c1 || !x1) { + dev_err(wcd934x->dev, "%s: Impedance detect ramp error, c1=%d, x1=0x%x\n", + __func__, c1, x1); + goto ramp_down; + } + d1 = d1_a[c1]; + denom = (x1 * d1) - (1 << (14 - noff)); + if (denom > 0) + *zdet = (WCD934X_MBHC_ZDET_CONST * 1000) / denom; + else if (x1 < minCode_param[noff]) + *zdet = WCD934X_ZDET_FLOATING_IMPEDANCE; + + dev_info(wcd934x->dev, "%s: d1=%d, c1=%d, x1=0x%x, z_val=%d(milliOhm)\n", + __func__, d1, c1, x1, *zdet); +ramp_down: + i = 0; + + while (x1) { + regmap_read(wcd934x->regmap, WCD934X_ANA_MBHC_RESULT_1, &val); + regmap_read(wcd934x->regmap, WCD934X_ANA_MBHC_RESULT_2, &val1); + val = val << 0x08; + val |= val1; + x1 = WCD934X_MBHC_GET_X1(val); + i++; + if (i == WCD934X_ZDET_NUM_MEASUREMENTS) + break; + } +} + +static void wcd934x_mbhc_zdet_ramp(struct snd_soc_component *component, + struct wcd934x_mbhc_zdet_param *zdet_param, + int32_t *zl, int32_t *zr, s16 *d1_a) +{ + struct wcd934x_codec *wcd934x = dev_get_drvdata(component->dev); + int32_t zdet = 0; + + snd_soc_component_write_field(component, WCD934X_MBHC_NEW_ZDET_ANA_CTL, + WCD934X_ZDET_MAXV_CTL_MASK, zdet_param->ldo_ctl); + snd_soc_component_update_bits(component, WCD934X_ANA_MBHC_BTN5, + WCD934X_VTH_MASK, zdet_param->btn5); + snd_soc_component_update_bits(component, WCD934X_ANA_MBHC_BTN6, + WCD934X_VTH_MASK, zdet_param->btn6); + snd_soc_component_update_bits(component, WCD934X_ANA_MBHC_BTN7, + WCD934X_VTH_MASK, zdet_param->btn7); + snd_soc_component_write_field(component, WCD934X_MBHC_NEW_ZDET_ANA_CTL, + WCD934X_ZDET_RANGE_CTL_MASK, zdet_param->noff); + snd_soc_component_update_bits(component, WCD934X_MBHC_NEW_ZDET_RAMP_CTL, + 0x0F, zdet_param->nshift); + + if (!zl) + goto z_right; + /* Start impedance measurement for HPH_L */ + regmap_update_bits(wcd934x->regmap, WCD934X_ANA_MBHC_ZDET, 0x80, 0x80); + wcd934x_mbhc_get_result_params(wcd934x, d1_a, zdet_param->noff, &zdet); + regmap_update_bits(wcd934x->regmap, WCD934X_ANA_MBHC_ZDET, 0x80, 0x00); + + *zl = zdet; + +z_right: + if (!zr) + return; + /* Start impedance measurement for HPH_R */ + regmap_update_bits(wcd934x->regmap, WCD934X_ANA_MBHC_ZDET, 0x40, 0x40); + wcd934x_mbhc_get_result_params(wcd934x, d1_a, zdet_param->noff, &zdet); + regmap_update_bits(wcd934x->regmap, WCD934X_ANA_MBHC_ZDET, 0x40, 0x00); + + *zr = zdet; +} + +static inline void wcd934x_wcd_mbhc_qfuse_cal(struct snd_soc_component *component, + int32_t *z_val, int flag_l_r) +{ + s16 q1; + int q1_cal; + + if (*z_val < (WCD934X_ZDET_VAL_400/1000)) + q1 = snd_soc_component_read(component, + WCD934X_CHIP_TIER_CTRL_EFUSE_VAL_OUT1 + (2 * flag_l_r)); + else + q1 = snd_soc_component_read(component, + WCD934X_CHIP_TIER_CTRL_EFUSE_VAL_OUT2 + (2 * flag_l_r)); + if (q1 & 0x80) + q1_cal = (10000 - ((q1 & 0x7F) * 25)); + else + q1_cal = (10000 + (q1 * 25)); + if (q1_cal > 0) + *z_val = ((*z_val) * 10000) / q1_cal; +} + +static void wcd934x_wcd_mbhc_calc_impedance(struct snd_soc_component *component, + uint32_t *zl, uint32_t *zr) +{ + struct wcd934x_codec *wcd934x = dev_get_drvdata(component->dev); + s16 reg0, reg1, reg2, reg3, reg4; + int32_t z1L, z1R, z1Ls; + int zMono, z_diff1, z_diff2; + bool is_fsm_disable = false; + struct wcd934x_mbhc_zdet_param zdet_param[] = { + {4, 0, 4, 0x08, 0x14, 0x18}, /* < 32ohm */ + {2, 0, 3, 0x18, 0x7C, 0x90}, /* 32ohm < Z < 400ohm */ + {1, 4, 5, 0x18, 0x7C, 0x90}, /* 400ohm < Z < 1200ohm */ + {1, 6, 7, 0x18, 0x7C, 0x90}, /* >1200ohm */ + }; + struct wcd934x_mbhc_zdet_param *zdet_param_ptr = NULL; + s16 d1_a[][4] = { + {0, 30, 90, 30}, + {0, 30, 30, 5}, + {0, 30, 30, 5}, + {0, 30, 30, 5}, + }; + s16 *d1 = NULL; + + reg0 = snd_soc_component_read(component, WCD934X_ANA_MBHC_BTN5); + reg1 = snd_soc_component_read(component, WCD934X_ANA_MBHC_BTN6); + reg2 = snd_soc_component_read(component, WCD934X_ANA_MBHC_BTN7); + reg3 = snd_soc_component_read(component, WCD934X_MBHC_CTL_CLK); + reg4 = snd_soc_component_read(component, WCD934X_MBHC_NEW_ZDET_ANA_CTL); + + if (snd_soc_component_read(component, WCD934X_ANA_MBHC_ELECT) & 0x80) { + is_fsm_disable = true; + regmap_update_bits(wcd934x->regmap, WCD934X_ANA_MBHC_ELECT, 0x80, 0x00); + } + + /* For NO-jack, disable L_DET_EN before Z-det measurements */ + if (wcd934x->mbhc_cfg.hphl_swh) + regmap_update_bits(wcd934x->regmap, WCD934X_ANA_MBHC_MECH, 0x80, 0x00); + + /* Turn off 100k pull down on HPHL */ + regmap_update_bits(wcd934x->regmap, WCD934X_ANA_MBHC_MECH, 0x01, 0x00); + + /* First get impedance on Left */ + d1 = d1_a[1]; + zdet_param_ptr = &zdet_param[1]; + wcd934x_mbhc_zdet_ramp(component, zdet_param_ptr, &z1L, NULL, d1); + + if (!WCD934X_MBHC_IS_SECOND_RAMP_REQUIRED(z1L)) + goto left_ch_impedance; + + /* Second ramp for left ch */ + if (z1L < WCD934X_ZDET_VAL_32) { + zdet_param_ptr = &zdet_param[0]; + d1 = d1_a[0]; + } else if ((z1L > WCD934X_ZDET_VAL_400) && + (z1L <= WCD934X_ZDET_VAL_1200)) { + zdet_param_ptr = &zdet_param[2]; + d1 = d1_a[2]; + } else if (z1L > WCD934X_ZDET_VAL_1200) { + zdet_param_ptr = &zdet_param[3]; + d1 = d1_a[3]; + } + wcd934x_mbhc_zdet_ramp(component, zdet_param_ptr, &z1L, NULL, d1); + +left_ch_impedance: + if ((z1L == WCD934X_ZDET_FLOATING_IMPEDANCE) || + (z1L > WCD934X_ZDET_VAL_100K)) { + *zl = WCD934X_ZDET_FLOATING_IMPEDANCE; + zdet_param_ptr = &zdet_param[1]; + d1 = d1_a[1]; + } else { + *zl = z1L/1000; + wcd934x_wcd_mbhc_qfuse_cal(component, zl, 0); + } + dev_info(component->dev, "%s: impedance on HPH_L = %d(ohms)\n", + __func__, *zl); + + /* Start of right impedance ramp and calculation */ + wcd934x_mbhc_zdet_ramp(component, zdet_param_ptr, NULL, &z1R, d1); + if (WCD934X_MBHC_IS_SECOND_RAMP_REQUIRED(z1R)) { + if (((z1R > WCD934X_ZDET_VAL_1200) && + (zdet_param_ptr->noff == 0x6)) || + ((*zl) != WCD934X_ZDET_FLOATING_IMPEDANCE)) + goto right_ch_impedance; + /* Second ramp for right ch */ + if (z1R < WCD934X_ZDET_VAL_32) { + zdet_param_ptr = &zdet_param[0]; + d1 = d1_a[0]; + } else if ((z1R > WCD934X_ZDET_VAL_400) && + (z1R <= WCD934X_ZDET_VAL_1200)) { + zdet_param_ptr = &zdet_param[2]; + d1 = d1_a[2]; + } else if (z1R > WCD934X_ZDET_VAL_1200) { + zdet_param_ptr = &zdet_param[3]; + d1 = d1_a[3]; + } + wcd934x_mbhc_zdet_ramp(component, zdet_param_ptr, NULL, &z1R, d1); + } +right_ch_impedance: + if ((z1R == WCD934X_ZDET_FLOATING_IMPEDANCE) || + (z1R > WCD934X_ZDET_VAL_100K)) { + *zr = WCD934X_ZDET_FLOATING_IMPEDANCE; + } else { + *zr = z1R/1000; + wcd934x_wcd_mbhc_qfuse_cal(component, zr, 1); + } + dev_err(component->dev, "%s: impedance on HPH_R = %d(ohms)\n", + __func__, *zr); + + /* Mono/stereo detection */ + if ((*zl == WCD934X_ZDET_FLOATING_IMPEDANCE) && + (*zr == WCD934X_ZDET_FLOATING_IMPEDANCE)) { + dev_dbg(component->dev, + "%s: plug type is invalid or extension cable\n", + __func__); + goto zdet_complete; + } + if ((*zl == WCD934X_ZDET_FLOATING_IMPEDANCE) || + (*zr == WCD934X_ZDET_FLOATING_IMPEDANCE) || + ((*zl < WCD_MONO_HS_MIN_THR) && (*zr > WCD_MONO_HS_MIN_THR)) || + ((*zl > WCD_MONO_HS_MIN_THR) && (*zr < WCD_MONO_HS_MIN_THR))) { + dev_dbg(component->dev, + "%s: Mono plug type with one ch floating or shorted to GND\n", + __func__); + wcd_mbhc_set_hph_type(wcd934x->mbhc, WCD_MBHC_HPH_MONO); + goto zdet_complete; + } + snd_soc_component_write_field(component, WCD934X_HPH_R_ATEST, + WCD934X_HPHPA_GND_OVR_MASK, 1); + snd_soc_component_write_field(component, WCD934X_HPH_PA_CTL2, + WCD934X_HPHPA_GND_R_MASK, 1); + if (*zl < (WCD934X_ZDET_VAL_32/1000)) + wcd934x_mbhc_zdet_ramp(component, &zdet_param[0], &z1Ls, NULL, d1); + else + wcd934x_mbhc_zdet_ramp(component, &zdet_param[1], &z1Ls, NULL, d1); + snd_soc_component_write_field(component, WCD934X_HPH_PA_CTL2, + WCD934X_HPHPA_GND_R_MASK, 0); + snd_soc_component_write_field(component, WCD934X_HPH_R_ATEST, + WCD934X_HPHPA_GND_OVR_MASK, 0); + z1Ls /= 1000; + wcd934x_wcd_mbhc_qfuse_cal(component, &z1Ls, 0); + /* Parallel of left Z and 9 ohm pull down resistor */ + zMono = ((*zl) * 9) / ((*zl) + 9); + z_diff1 = (z1Ls > zMono) ? (z1Ls - zMono) : (zMono - z1Ls); + z_diff2 = ((*zl) > z1Ls) ? ((*zl) - z1Ls) : (z1Ls - (*zl)); + if ((z_diff1 * (*zl + z1Ls)) > (z_diff2 * (z1Ls + zMono))) { + dev_err(component->dev, "%s: stereo plug type detected\n", + __func__); + wcd_mbhc_set_hph_type(wcd934x->mbhc, WCD_MBHC_HPH_STEREO); + } else { + dev_err(component->dev, "%s: MONO plug type detected\n", + __func__); + wcd_mbhc_set_hph_type(wcd934x->mbhc, WCD_MBHC_HPH_MONO); + } + +zdet_complete: + snd_soc_component_write(component, WCD934X_ANA_MBHC_BTN5, reg0); + snd_soc_component_write(component, WCD934X_ANA_MBHC_BTN6, reg1); + snd_soc_component_write(component, WCD934X_ANA_MBHC_BTN7, reg2); + /* Turn on 100k pull down on HPHL */ + regmap_update_bits(wcd934x->regmap, WCD934X_ANA_MBHC_MECH, 0x01, 0x01); + + /* For NO-jack, re-enable L_DET_EN after Z-det measurements */ + if (wcd934x->mbhc_cfg.hphl_swh) + regmap_update_bits(wcd934x->regmap, WCD934X_ANA_MBHC_MECH, 0x80, 0x80); + + snd_soc_component_write(component, WCD934X_MBHC_NEW_ZDET_ANA_CTL, reg4); + snd_soc_component_write(component, WCD934X_MBHC_CTL_CLK, reg3); + if (is_fsm_disable) + regmap_update_bits(wcd934x->regmap, WCD934X_ANA_MBHC_ELECT, 0x80, 0x80); +} + +static void wcd934x_mbhc_gnd_det_ctrl(struct snd_soc_component *component, + bool enable) +{ + if (enable) { + snd_soc_component_write_field(component, WCD934X_ANA_MBHC_MECH, + WCD934X_MBHC_HSG_PULLUP_COMP_EN, 1); + snd_soc_component_write_field(component, WCD934X_ANA_MBHC_MECH, + WCD934X_MBHC_GND_DET_EN_MASK, 1); + } else { + snd_soc_component_write_field(component, WCD934X_ANA_MBHC_MECH, + WCD934X_MBHC_GND_DET_EN_MASK, 0); + snd_soc_component_write_field(component, WCD934X_ANA_MBHC_MECH, + WCD934X_MBHC_HSG_PULLUP_COMP_EN, 0); + } +} + +static void wcd934x_mbhc_hph_pull_down_ctrl(struct snd_soc_component *component, + bool enable) +{ + snd_soc_component_write_field(component, WCD934X_HPH_PA_CTL2, + WCD934X_HPHPA_GND_R_MASK, enable); + snd_soc_component_write_field(component, WCD934X_HPH_PA_CTL2, + WCD934X_HPHPA_GND_L_MASK, enable); +} + +static const struct wcd_mbhc_cb mbhc_cb = { + .clk_setup = wcd934x_mbhc_clk_setup, + .mbhc_bias = wcd934x_mbhc_mbhc_bias_control, + .set_btn_thr = wcd934x_mbhc_program_btn_thr, + .micbias_enable_status = wcd934x_mbhc_micb_en_status, + .hph_pull_up_control = wcd934x_mbhc_hph_l_pull_up_control, + .mbhc_micbias_control = wcd934x_mbhc_request_micbias, + .mbhc_micb_ramp_control = wcd934x_mbhc_micb_ramp_control, + .mbhc_micb_ctrl_thr_mic = wcd934x_mbhc_micb_ctrl_threshold_mic, + .compute_impedance = wcd934x_wcd_mbhc_calc_impedance, + .mbhc_gnd_det_ctrl = wcd934x_mbhc_gnd_det_ctrl, + .hph_pull_down_ctrl = wcd934x_mbhc_hph_pull_down_ctrl, +}; + +static int wcd934x_get_hph_type(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wcd934x_codec *wcd = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = wcd_mbhc_get_hph_type(wcd->mbhc); + + return 0; +} + +static int wcd934x_hph_impedance_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + uint32_t zl, zr; + bool hphr; + struct soc_mixer_control *mc; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wcd934x_codec *wcd = snd_soc_component_get_drvdata(component); + + mc = (struct soc_mixer_control *)(kcontrol->private_value); + hphr = mc->shift; + wcd_mbhc_get_impedance(wcd->mbhc, &zl, &zr); + dev_dbg(component->dev, "%s: zl=%u(ohms), zr=%u(ohms)\n", __func__, zl, zr); + ucontrol->value.integer.value[0] = hphr ? zr : zl; + + return 0; +} +static const struct snd_kcontrol_new hph_type_detect_controls[] = { + SOC_SINGLE_EXT("HPH Type", 0, 0, UINT_MAX, 0, + wcd934x_get_hph_type, NULL), +}; + +static const struct snd_kcontrol_new impedance_detect_controls[] = { + SOC_SINGLE_EXT("HPHL Impedance", 0, 0, UINT_MAX, 0, + wcd934x_hph_impedance_get, NULL), + SOC_SINGLE_EXT("HPHR Impedance", 0, 1, UINT_MAX, 0, + wcd934x_hph_impedance_get, NULL), +}; + +static int wcd934x_mbhc_init(struct snd_soc_component *component) +{ + struct wcd934x_ddata *data = dev_get_drvdata(component->dev->parent); + struct wcd934x_codec *wcd = snd_soc_component_get_drvdata(component); + struct wcd_mbhc_intr *intr_ids = &wcd->intr_ids; + + intr_ids->mbhc_sw_intr = regmap_irq_get_virq(data->irq_data, + WCD934X_IRQ_MBHC_SW_DET); + intr_ids->mbhc_btn_press_intr = regmap_irq_get_virq(data->irq_data, + WCD934X_IRQ_MBHC_BUTTON_PRESS_DET); + intr_ids->mbhc_btn_release_intr = regmap_irq_get_virq(data->irq_data, + WCD934X_IRQ_MBHC_BUTTON_RELEASE_DET); + intr_ids->mbhc_hs_ins_intr = regmap_irq_get_virq(data->irq_data, + WCD934X_IRQ_MBHC_ELECT_INS_REM_LEG_DET); + intr_ids->mbhc_hs_rem_intr = regmap_irq_get_virq(data->irq_data, + WCD934X_IRQ_MBHC_ELECT_INS_REM_DET); + intr_ids->hph_left_ocp = regmap_irq_get_virq(data->irq_data, + WCD934X_IRQ_HPH_PA_OCPL_FAULT); + intr_ids->hph_right_ocp = regmap_irq_get_virq(data->irq_data, + WCD934X_IRQ_HPH_PA_OCPR_FAULT); + + wcd->mbhc = wcd_mbhc_init(component, &mbhc_cb, intr_ids, wcd_mbhc_fields, true); + if (IS_ERR(wcd->mbhc)) { + wcd->mbhc = NULL; + return -EINVAL; + } + + snd_soc_add_component_controls(component, impedance_detect_controls, + ARRAY_SIZE(impedance_detect_controls)); + snd_soc_add_component_controls(component, hph_type_detect_controls, + ARRAY_SIZE(hph_type_detect_controls)); + + return 0; +} static int wcd934x_comp_probe(struct snd_soc_component *component) { struct wcd934x_codec *wcd = dev_get_drvdata(component->dev); @@ -2309,6 +3091,10 @@ static int wcd934x_comp_probe(struct snd_soc_component *component) INIT_LIST_HEAD(&wcd->dai[i].slim_ch_list); wcd934x_init_dmic(component); + + if (wcd934x_mbhc_init(component)) + dev_err(component->dev, "Failed to Initialize MBHC\n"); + return 0; } @@ -3756,6 +4542,7 @@ static int wcd934x_codec_enable_hphl_pa(struct snd_soc_dapm_widget *w, int event) { struct snd_soc_component *comp = snd_soc_dapm_to_component(w->dapm); + struct wcd934x_codec *wcd = snd_soc_component_get_drvdata(comp); switch (event) { case SND_SOC_DAPM_POST_PMU: @@ -3788,6 +4575,7 @@ static int wcd934x_codec_enable_hphl_pa(struct snd_soc_dapm_widget *w, WCD934X_CDC_RX_PGA_MUTE_EN_MASK, 0x00); break; case SND_SOC_DAPM_PRE_PMD: + wcd_mbhc_event_notify(wcd->mbhc, WCD_EVENT_POST_HPHL_PA_OFF); /* Enable DSD Mute before PA disable */ snd_soc_component_update_bits(comp, WCD934X_HPH_L_TEST, WCD934X_HPH_OCP_DET_MASK, @@ -3806,6 +4594,7 @@ static int wcd934x_codec_enable_hphl_pa(struct snd_soc_dapm_widget *w, * disabled, then 20ms delay is needed after PA disable. */ usleep_range(20000, 20100); + wcd_mbhc_event_notify(wcd->mbhc, WCD_EVENT_POST_HPHL_PA_OFF); break; } @@ -3817,6 +4606,7 @@ static int wcd934x_codec_enable_hphr_pa(struct snd_soc_dapm_widget *w, int event) { struct snd_soc_component *comp = snd_soc_dapm_to_component(w->dapm); + struct wcd934x_codec *wcd = snd_soc_component_get_drvdata(comp); switch (event) { case SND_SOC_DAPM_POST_PMU: @@ -3851,6 +4641,7 @@ static int wcd934x_codec_enable_hphr_pa(struct snd_soc_dapm_widget *w, WCD934X_CDC_RX_PGA_MUTE_DISABLE); break; case SND_SOC_DAPM_PRE_PMD: + wcd_mbhc_event_notify(wcd->mbhc, WCD_EVENT_PRE_HPHR_PA_OFF); snd_soc_component_update_bits(comp, WCD934X_HPH_R_TEST, WCD934X_HPH_OCP_DET_MASK, WCD934X_HPH_OCP_DET_DISABLE); @@ -3868,6 +4659,7 @@ static int wcd934x_codec_enable_hphr_pa(struct snd_soc_dapm_widget *w, * disabled, then 20ms delay is needed after PA disable. */ usleep_range(20000, 20100); + wcd_mbhc_event_notify(wcd->mbhc, WCD_EVENT_POST_HPHR_PA_OFF); break; } @@ -4323,6 +5115,29 @@ static int wcd934x_codec_enable_adc(struct snd_soc_dapm_widget *w, return 0; } +static int wcd934x_codec_enable_micbias(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + int micb_num = w->shift; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + wcd934x_micbias_control(component, micb_num, MICB_ENABLE, true); + break; + case SND_SOC_DAPM_POST_PMU: + /* 1 msec delay as per HW requirement */ + usleep_range(1000, 1100); + break; + case SND_SOC_DAPM_POST_PMD: + wcd934x_micbias_control(component, micb_num, MICB_DISABLE, true); + break; + }; + + return 0; +} + static const struct snd_soc_dapm_widget wcd934x_dapm_widgets[] = { /* Analog Outputs */ SND_SOC_DAPM_OUTPUT("EAR"), @@ -4778,13 +5593,17 @@ static const struct snd_soc_dapm_widget wcd934x_dapm_widgets[] = { wcd934x_codec_enable_adc, SND_SOC_DAPM_PRE_PMU), SND_SOC_DAPM_ADC_E("ADC4", NULL, WCD934X_ANA_AMIC4, 7, 0, wcd934x_codec_enable_adc, SND_SOC_DAPM_PRE_PMU), - SND_SOC_DAPM_SUPPLY("MIC BIAS1", WCD934X_ANA_MICB1, 6, 0, NULL, + SND_SOC_DAPM_SUPPLY("MIC BIAS1", SND_SOC_NOPM, MIC_BIAS_1, 0, + wcd934x_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), - SND_SOC_DAPM_SUPPLY("MIC BIAS2", WCD934X_ANA_MICB2, 6, 0, NULL, + SND_SOC_DAPM_SUPPLY("MIC BIAS2", SND_SOC_NOPM, MIC_BIAS_2, 0, + wcd934x_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), - SND_SOC_DAPM_SUPPLY("MIC BIAS3", WCD934X_ANA_MICB3, 6, 0, NULL, + SND_SOC_DAPM_SUPPLY("MIC BIAS3", SND_SOC_NOPM, MIC_BIAS_3, 0, + wcd934x_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), - SND_SOC_DAPM_SUPPLY("MIC BIAS4", WCD934X_ANA_MICB4, 6, 0, NULL, + SND_SOC_DAPM_SUPPLY("MIC BIAS4", SND_SOC_NOPM, MIC_BIAS_4, 0, + wcd934x_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MUX("AMIC4_5 SEL", SND_SOC_NOPM, 0, 0, &tx_amic4_5), @@ -4961,6 +5780,26 @@ static const struct snd_soc_dapm_route wcd934x_audio_map[] = { {"SRC1", NULL, "IIR1"}, }; +static int wcd934x_codec_set_jack(struct snd_soc_component *comp, + struct snd_soc_jack *jack, void *data) +{ + struct wcd934x_codec *wcd = dev_get_drvdata(comp->dev); + int ret = 0; + + if (!wcd->mbhc) + return -ENOTSUPP; + + if (jack && !wcd->mbhc_started) { + ret = wcd_mbhc_start(wcd->mbhc, &wcd->mbhc_cfg, jack); + wcd->mbhc_started = true; + } else if (wcd->mbhc_started) { + wcd_mbhc_stop(wcd->mbhc); + wcd->mbhc_started = false; + } + + return ret; +} + static const struct snd_soc_component_driver wcd934x_component_drv = { .probe = wcd934x_comp_probe, .remove = wcd934x_comp_remove, @@ -4971,11 +5810,13 @@ static const struct snd_soc_component_driver wcd934x_component_drv = { .num_dapm_widgets = ARRAY_SIZE(wcd934x_dapm_widgets), .dapm_routes = wcd934x_audio_map, .num_dapm_routes = ARRAY_SIZE(wcd934x_audio_map), + .set_jack = wcd934x_codec_set_jack, }; static int wcd934x_codec_parse_data(struct wcd934x_codec *wcd) { struct device *dev = &wcd->sdev->dev; + struct wcd_mbhc_config *cfg = &wcd->mbhc_cfg; struct device_node *ifc_dev_np; ifc_dev_np = of_parse_phandle(dev->of_node, "slim-ifc-dev", 0); @@ -5001,6 +5842,18 @@ static int wcd934x_codec_parse_data(struct wcd934x_codec *wcd) of_property_read_u32(dev->parent->of_node, "qcom,dmic-sample-rate", &wcd->dmic_sample_rate); + cfg->mbhc_micbias = MIC_BIAS_2; + cfg->anc_micbias = MIC_BIAS_2; + cfg->v_hs_max = WCD_MBHC_HS_V_MAX; + cfg->num_btn = WCD934X_MBHC_MAX_BUTTONS; + cfg->micb_mv = wcd->micb2_mv; + cfg->linein_th = 5000; + cfg->hs_thr = 1700; + cfg->hph_thr = 50; + + wcd_dt_parse_mbhc_data(dev, cfg); + + return 0; } @@ -5020,6 +5873,7 @@ static int wcd934x_codec_probe(struct platform_device *pdev) wcd->extclk = data->extclk; wcd->sdev = to_slim_device(data->dev); mutex_init(&wcd->sysclk_mutex); + mutex_init(&wcd->micb_lock); ret = wcd934x_codec_parse_data(wcd); if (ret) { diff --git a/sound/soc/qcom/sdm845.c b/sound/soc/qcom/sdm845.c index 153e9b2de0b5..0adfc5708949 100644 --- a/sound/soc/qcom/sdm845.c +++ b/sound/soc/qcom/sdm845.c @@ -288,6 +288,14 @@ static int sdm845_dai_init(struct snd_soc_pcm_runtime *rtd) snd_soc_dai_set_sysclk(codec_dai, 0, WCD934X_DEFAULT_MCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); + + rval = snd_soc_component_set_jack(codec_dai->component, + &pdata->jack, NULL); + if (rval != 0 && rval != -ENOTSUPP) { + dev_warn(card->dev, "Failed to set jack: %d\n", rval); + return rval; + } + } break; default: