diff options
Diffstat (limited to 'sound/soc/codecs/wcd9xxx-mbhc.c')
| -rw-r--r-- | sound/soc/codecs/wcd9xxx-mbhc.c | 5643 |
1 files changed, 5643 insertions, 0 deletions
diff --git a/sound/soc/codecs/wcd9xxx-mbhc.c b/sound/soc/codecs/wcd9xxx-mbhc.c new file mode 100644 index 000000000000..2012e4617ee1 --- /dev/null +++ b/sound/soc/codecs/wcd9xxx-mbhc.c @@ -0,0 +1,5643 @@ +/* Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/firmware.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/device.h> +#include <linux/printk.h> +#include <linux/ratelimit.h> +#include <linux/debugfs.h> +#include <linux/list.h> +#include <linux/mfd/wcd9xxx/core.h> +#include <linux/mfd/wcd9xxx/wcd9xxx-irq.h> +#include <linux/mfd/wcd9xxx/wcd9xxx_registers.h> +#include <linux/mfd/wcd9xxx/wcd9320_registers.h> +#include <linux/mfd/wcd9xxx/pdata.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/jack.h> +#include <sound/tlv.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/pm_runtime.h> +#include <linux/kernel.h> +#include <linux/gpio.h> +#include <linux/input.h> +#include "wcd9xxx-mbhc.h" +#include "wcdcal-hwdep.h" +#include "wcd9xxx-resmgr.h" +#include "wcd9xxx-common.h" + +#define WCD9XXX_JACK_MASK (SND_JACK_HEADSET | SND_JACK_OC_HPHL | \ + SND_JACK_OC_HPHR | SND_JACK_LINEOUT | \ + SND_JACK_UNSUPPORTED | SND_JACK_MICROPHONE2 | \ + SND_JACK_MECHANICAL) +#define WCD9XXX_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 ) + +#define NUM_DCE_PLUG_DETECT 3 +#define NUM_DCE_PLUG_INS_DETECT 5 +#define NUM_ATTEMPTS_INSERT_DETECT 25 +#define NUM_ATTEMPTS_TO_REPORT 5 + +#define FAKE_INS_LOW 10 +#define FAKE_INS_HIGH 80 +#define FAKE_INS_HIGH_NO_SWCH 150 +#define FAKE_REMOVAL_MIN_PERIOD_MS 50 +#define FAKE_INS_DELTA_SCALED_MV 300 + +#define BUTTON_MIN 0x8000 +#define STATUS_REL_DETECTION 0x0C + +#define HS_DETECT_PLUG_TIME_MS (5 * 1000) +#define ANC_HPH_DETECT_PLUG_TIME_MS (5 * 1000) +#define HS_DETECT_PLUG_INERVAL_MS 100 +#define SWCH_REL_DEBOUNCE_TIME_MS 50 +#define SWCH_IRQ_DEBOUNCE_TIME_US 5000 +#define BTN_RELEASE_DEBOUNCE_TIME_MS 25 + +#define GND_MIC_SWAP_THRESHOLD 2 +#define OCP_ATTEMPT 1 + +#define FW_READ_ATTEMPTS 15 +#define FW_READ_TIMEOUT 4000000 + +#define BUTTON_POLLING_SUPPORTED true + +#define MCLK_RATE_12288KHZ 12288000 +#define MCLK_RATE_9600KHZ 9600000 + +#define DEFAULT_DCE_STA_WAIT 55 +#define DEFAULT_DCE_WAIT 60000 +#define DEFAULT_STA_WAIT 5000 + +#define VDDIO_MICBIAS_MV 1800 + +#define WCD9XXX_MICBIAS_PULLDOWN_SETTLE_US 5000 + +#define WCD9XXX_HPHL_STATUS_READY_WAIT_US 1000 +#define WCD9XXX_MUX_SWITCH_READY_WAIT_MS 50 +#define WCD9XXX_MEAS_DELTA_MAX_MV 120 +#define WCD9XXX_MEAS_INVALD_RANGE_LOW_MV 20 +#define WCD9XXX_MEAS_INVALD_RANGE_HIGH_MV 80 + +/* Threshold in milliohm used for mono/stereo + * plug classification + */ +#define WCD9XXX_MONO_HS_DIFF_THR 20000000 +#define WCD9XXX_MONO_HS_MIN_THR 2000 + +/* + * Invalid voltage range for the detection + * of plug type with current source + */ +#define WCD9XXX_CS_MEAS_INVALD_RANGE_LOW_MV 160 +#define WCD9XXX_CS_MEAS_INVALD_RANGE_HIGH_MV 265 + +/* + * Threshold used to detect euro headset + * with current source + */ +#define WCD9XXX_CS_GM_SWAP_THRES_MIN_MV 10 +#define WCD9XXX_CS_GM_SWAP_THRES_MAX_MV 40 + +#define WCD9XXX_MBHC_NSC_CS 9 +#define WCD9XXX_GM_SWAP_THRES_MIN_MV 150 +#define WCD9XXX_GM_SWAP_THRES_MAX_MV 650 +#define WCD9XXX_THRESHOLD_MIC_THRESHOLD 200 + +#define WCD9XXX_USLEEP_RANGE_MARGIN_US 100 + +/* RX_HPH_CNP_WG_TIME increases by 0.24ms */ +#define WCD9XXX_WG_TIME_FACTOR_US 240 + +#define WCD9XXX_V_CS_HS_MAX 500 +#define WCD9XXX_V_CS_NO_MIC 5 +#define WCD9XXX_MB_MEAS_DELTA_MAX_MV 80 +#define WCD9XXX_CS_MEAS_DELTA_MAX_MV 12 + +#define WCD9XXX_ZDET_ZONE_1 80000 +#define WCD9XXX_ZDET_ZONE_2 800000 + +#define WCD9XXX_IS_IN_ZDET_ZONE_1(x) (x < WCD9XXX_ZDET_ZONE_1 ? 1 : 0) +#define WCD9XXX_IS_IN_ZDET_ZONE_2(x) ((x > WCD9XXX_ZDET_ZONE_1 && \ + x < WCD9XXX_ZDET_ZONE_2) ? 1 : 0) +#define WCD9XXX_IS_IN_ZDET_ZONE_3(x) (x > WCD9XXX_ZDET_ZONE_2 ? 1 : 0) +#define WCD9XXX_BOX_CAR_AVRG_MIN 1 +#define WCD9XXX_BOX_CAR_AVRG_MAX 10 + +/* + * Need to report LINEIN if H/L impedance + * is larger than 5K ohm + */ +#define WCD9XXX_LINEIN_THRESHOLD 5000000 + +static int impedance_detect_en; +module_param(impedance_detect_en, int, + S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(impedance_detect_en, "enable/disable impedance detect"); +static unsigned int z_det_box_car_avg = 1; +module_param(z_det_box_car_avg, int, + S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(z_det_box_car_avg, + "Number of samples for impedance detection"); + +static bool detect_use_vddio_switch; + +struct wcd9xxx_mbhc_detect { + u16 dce; + u16 sta; + u16 hphl_status; + bool swap_gnd; + bool vddio; + bool hwvalue; + bool mic_bias; + /* internal purpose from here */ + bool _above_no_mic; + bool _below_v_hs_max; + s16 _vdces; + enum wcd9xxx_mbhc_plug_type _type; +}; + +enum meas_type { + STA = 0, + DCE, +}; + +enum { + MBHC_USE_HPHL_TRIGGER = 1, + MBHC_USE_MB_TRIGGER = 2 +}; + +/* + * Flags to track of PA and DAC state. + * PA and DAC should be tracked separately as AUXPGA loopback requires + * only PA to be turned on without DAC being on. + */ +enum pa_dac_ack_flags { + WCD9XXX_HPHL_PA_OFF_ACK = 0, + WCD9XXX_HPHR_PA_OFF_ACK, + WCD9XXX_HPHL_DAC_OFF_ACK, + WCD9XXX_HPHR_DAC_OFF_ACK +}; + +enum wcd9xxx_current_v_idx { + WCD9XXX_CURRENT_V_INS_H, + WCD9XXX_CURRENT_V_INS_HU, + WCD9XXX_CURRENT_V_B1_H, + WCD9XXX_CURRENT_V_B1_HU, + WCD9XXX_CURRENT_V_BR_H, +}; + +static int wcd9xxx_detect_impedance(struct wcd9xxx_mbhc *mbhc, uint32_t *zl, + uint32_t *zr); +static s16 wcd9xxx_get_current_v(struct wcd9xxx_mbhc *mbhc, + const enum wcd9xxx_current_v_idx idx); +static void wcd9xxx_get_z(struct wcd9xxx_mbhc *mbhc, s16 *dce_z, s16 *sta_z, + struct mbhc_micbias_regs *micb_regs, + bool norel); + +static void wcd9xxx_mbhc_calc_thres(struct wcd9xxx_mbhc *mbhc); + +static u16 wcd9xxx_codec_v_sta_dce(struct wcd9xxx_mbhc *mbhc, + enum meas_type dce, s16 vin_mv, + bool cs_enable); + +static bool wcd9xxx_mbhc_polling(struct wcd9xxx_mbhc *mbhc) +{ + return snd_soc_read(mbhc->codec, WCD9XXX_A_CDC_MBHC_EN_CTL) & 0x1; +} + +static void wcd9xxx_turn_onoff_override(struct wcd9xxx_mbhc *mbhc, bool on) +{ + struct snd_soc_codec *codec = mbhc->codec; + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_B1_CTL, + 0x04, on ? 0x04 : 0x00); +} + +/* called under codec_resource_lock acquisition */ +static void wcd9xxx_pause_hs_polling(struct wcd9xxx_mbhc *mbhc) +{ + struct snd_soc_codec *codec = mbhc->codec; + + pr_debug("%s: enter\n", __func__); + if (!mbhc->polling_active) { + pr_debug("polling not active, nothing to pause\n"); + return; + } + + /* Soft reset MBHC block */ + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + pr_debug("%s: leave\n", __func__); +} + +/* called under codec_resource_lock acquisition */ +static void wcd9xxx_start_hs_polling(struct wcd9xxx_mbhc *mbhc) +{ + struct snd_soc_codec *codec = mbhc->codec; + int mbhc_state = mbhc->mbhc_state; + + pr_debug("%s: enter\n", __func__); + if (!mbhc->polling_active) { + pr_debug("Polling is not active, do not start polling\n"); + return; + } + + /* + * setup internal micbias if codec uses internal micbias for + * headset detection + */ + if (mbhc->mbhc_cfg->use_int_rbias) { + if (mbhc->mbhc_cb && mbhc->mbhc_cb->setup_int_rbias) + mbhc->mbhc_cb->setup_int_rbias(codec, true); + else + pr_err("%s: internal bias requested but codec did not provide callback\n", + __func__); + } + + snd_soc_write(codec, WCD9XXX_A_MBHC_SCALING_MUX_1, 0x04); + if (mbhc->mbhc_cb && mbhc->mbhc_cb->enable_mux_bias_block) + mbhc->mbhc_cb->enable_mux_bias_block(codec); + else + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_SCALING_MUX_1, + 0x80, 0x80); + + if (!mbhc->no_mic_headset_override && + mbhc_state == MBHC_STATE_POTENTIAL) { + pr_debug("%s recovering MBHC state machine\n", __func__); + mbhc->mbhc_state = MBHC_STATE_POTENTIAL_RECOVERY; + /* set to max button press threshold */ + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B2_CTL, 0x7F); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B1_CTL, 0xFF); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B4_CTL, 0x7F); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B3_CTL, 0xFF); + /* set to max */ + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B6_CTL, 0x7F); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B5_CTL, 0xFF); + } + + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_EN_CTL, 0x1); + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x8, 0x0); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_EN_CTL, 0x1); + pr_debug("%s: leave\n", __func__); +} + +static int __wcd9xxx_resmgr_get_k_val(struct wcd9xxx_mbhc *mbhc, + unsigned int cfilt_mv) +{ + return wcd9xxx_resmgr_get_k_val(mbhc->resmgr, cfilt_mv); +} + +/* + * called under codec_resource_lock acquisition + * return old status + */ +static bool __wcd9xxx_switch_micbias(struct wcd9xxx_mbhc *mbhc, + int vddio_switch, bool restartpolling, + bool checkpolling) +{ + bool ret; + int cfilt_k_val; + bool override; + struct snd_soc_codec *codec; + struct mbhc_internal_cal_data *d = &mbhc->mbhc_data; + + codec = mbhc->codec; + + if (mbhc->micbias_enable) { + pr_debug("%s: micbias is already on\n", __func__); + ret = mbhc->mbhc_micbias_switched; + return ret; + } + + ret = mbhc->mbhc_micbias_switched; + if (vddio_switch && !mbhc->mbhc_micbias_switched && + (!checkpolling || mbhc->polling_active)) { + if (restartpolling) + wcd9xxx_pause_hs_polling(mbhc); + override = snd_soc_read(codec, WCD9XXX_A_CDC_MBHC_B1_CTL) & + 0x04; + if (!override) + wcd9xxx_turn_onoff_override(mbhc, true); + + snd_soc_update_bits(codec, WCD9XXX_A_MAD_ANA_CTRL, + 0x10, 0x00); + snd_soc_update_bits(codec, WCD9XXX_A_LDO_H_MODE_1, + 0x20, 0x00); + /* Adjust threshold if Mic Bias voltage changes */ + if (d->micb_mv != VDDIO_MICBIAS_MV) { + cfilt_k_val = __wcd9xxx_resmgr_get_k_val(mbhc, + VDDIO_MICBIAS_MV); + usleep_range(10000, 10100); + snd_soc_update_bits(codec, + mbhc->mbhc_bias_regs.cfilt_val, + 0xFC, (cfilt_k_val << 2)); + usleep_range(10000, 10100); + /* Threshods for insertion/removal */ + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B1_CTL, + d->v_ins_hu[MBHC_V_IDX_VDDIO] & 0xFF); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B2_CTL, + (d->v_ins_hu[MBHC_V_IDX_VDDIO] >> 8) & + 0xFF); + + if (mbhc->mbhc_state != MBHC_STATE_POTENTIAL_RECOVERY) { + /* Threshods for button press */ + snd_soc_write(codec, + WCD9XXX_A_CDC_MBHC_VOLT_B3_CTL, + d->v_b1_hu[MBHC_V_IDX_VDDIO] & 0xFF); + snd_soc_write(codec, + WCD9XXX_A_CDC_MBHC_VOLT_B4_CTL, + (d->v_b1_hu[MBHC_V_IDX_VDDIO] >> 8) & + 0xFF); + snd_soc_write(codec, + WCD9XXX_A_CDC_MBHC_VOLT_B5_CTL, + d->v_b1_h[MBHC_V_IDX_VDDIO] & 0xFF); + snd_soc_write(codec, + WCD9XXX_A_CDC_MBHC_VOLT_B6_CTL, + (d->v_b1_h[MBHC_V_IDX_VDDIO] >> 8) & + 0xFF); + /* Threshods for button release */ + snd_soc_write(codec, + WCD9XXX_A_CDC_MBHC_VOLT_B9_CTL, + d->v_brh[MBHC_V_IDX_VDDIO] & 0xFF); + snd_soc_write(codec, + WCD9XXX_A_CDC_MBHC_VOLT_B10_CTL, + (d->v_brh[MBHC_V_IDX_VDDIO] >> 8) & + 0xFF); + } + pr_debug("%s: Programmed MBHC thresholds to VDDIO\n", + __func__); + } + + /* Enable MIC BIAS Switch to VDDIO */ + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.mbhc_reg, + 0x80, 0x80); + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.mbhc_reg, + 0x10, 0x00); + if (!override) + wcd9xxx_turn_onoff_override(mbhc, false); + if (restartpolling) + wcd9xxx_start_hs_polling(mbhc); + + mbhc->mbhc_micbias_switched = true; + pr_debug("%s: VDDIO switch enabled\n", __func__); + } else if (!vddio_switch && mbhc->mbhc_micbias_switched) { + if ((!checkpolling || mbhc->polling_active) && + restartpolling) + wcd9xxx_pause_hs_polling(mbhc); + + snd_soc_update_bits(codec, WCD9XXX_A_MAD_ANA_CTRL, + 0x10, 0x10); + snd_soc_update_bits(codec, WCD9XXX_A_LDO_H_MODE_1, + 0x20, 0x20); + /* Reprogram thresholds */ + if (d->micb_mv != VDDIO_MICBIAS_MV) { + cfilt_k_val = + __wcd9xxx_resmgr_get_k_val(mbhc, + d->micb_mv); + snd_soc_update_bits(codec, + mbhc->mbhc_bias_regs.cfilt_val, + 0xFC, (cfilt_k_val << 2)); + usleep_range(10000, 10100); + /* Revert threshods for insertion/removal */ + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B1_CTL, + d->v_ins_hu[MBHC_V_IDX_CFILT] & 0xFF); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B2_CTL, + (d->v_ins_hu[MBHC_V_IDX_CFILT] >> 8) & + 0xFF); + if (mbhc->mbhc_state != MBHC_STATE_POTENTIAL_RECOVERY) { + /* Revert threshods for button press */ + snd_soc_write(codec, + WCD9XXX_A_CDC_MBHC_VOLT_B3_CTL, + d->v_b1_hu[MBHC_V_IDX_CFILT] & 0xFF); + snd_soc_write(codec, + WCD9XXX_A_CDC_MBHC_VOLT_B4_CTL, + (d->v_b1_hu[MBHC_V_IDX_CFILT] >> 8) & + 0xFF); + snd_soc_write(codec, + WCD9XXX_A_CDC_MBHC_VOLT_B5_CTL, + d->v_b1_h[MBHC_V_IDX_CFILT] & 0xFF); + snd_soc_write(codec, + WCD9XXX_A_CDC_MBHC_VOLT_B6_CTL, + (d->v_b1_h[MBHC_V_IDX_CFILT] >> 8) & + 0xFF); + /* Revert threshods for button release */ + snd_soc_write(codec, + WCD9XXX_A_CDC_MBHC_VOLT_B9_CTL, + d->v_brh[MBHC_V_IDX_CFILT] & 0xFF); + snd_soc_write(codec, + WCD9XXX_A_CDC_MBHC_VOLT_B10_CTL, + (d->v_brh[MBHC_V_IDX_CFILT] >> 8) & + 0xFF); + } + pr_debug("%s: Programmed MBHC thresholds to MICBIAS\n", + __func__); + } + + /* Disable MIC BIAS Switch to VDDIO */ + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.mbhc_reg, 0x80, + 0x00); + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.mbhc_reg, 0x10, + 0x00); + + if ((!checkpolling || mbhc->polling_active) && restartpolling) + wcd9xxx_start_hs_polling(mbhc); + + mbhc->mbhc_micbias_switched = false; + pr_debug("%s: VDDIO switch disabled\n", __func__); + } + + return ret; +} + +static void wcd9xxx_switch_micbias(struct wcd9xxx_mbhc *mbhc, int vddio_switch) +{ + __wcd9xxx_switch_micbias(mbhc, vddio_switch, true, true); +} + +static s16 wcd9xxx_get_current_v(struct wcd9xxx_mbhc *mbhc, + const enum wcd9xxx_current_v_idx idx) +{ + enum mbhc_v_index vidx; + s16 ret = -EINVAL; + + if ((mbhc->mbhc_data.micb_mv != VDDIO_MICBIAS_MV) && + mbhc->mbhc_micbias_switched) + vidx = MBHC_V_IDX_VDDIO; + else + vidx = MBHC_V_IDX_CFILT; + + switch (idx) { + case WCD9XXX_CURRENT_V_INS_H: + ret = (s16)mbhc->mbhc_data.v_ins_h[vidx]; + break; + case WCD9XXX_CURRENT_V_INS_HU: + ret = (s16)mbhc->mbhc_data.v_ins_hu[vidx]; + break; + case WCD9XXX_CURRENT_V_B1_H: + ret = (s16)mbhc->mbhc_data.v_b1_h[vidx]; + break; + case WCD9XXX_CURRENT_V_B1_HU: + ret = (s16)mbhc->mbhc_data.v_b1_hu[vidx]; + break; + case WCD9XXX_CURRENT_V_BR_H: + ret = (s16)mbhc->mbhc_data.v_brh[vidx]; + break; + } + + return ret; +} + +void *wcd9xxx_mbhc_cal_btn_det_mp( + const struct wcd9xxx_mbhc_btn_detect_cfg *btn_det, + const enum wcd9xxx_mbhc_btn_det_mem mem) +{ + void *ret = (void *)&btn_det->_v_btn_low; + + switch (mem) { + case MBHC_BTN_DET_GAIN: + ret += sizeof(btn_det->_n_cic); + case MBHC_BTN_DET_N_CIC: + ret += sizeof(btn_det->_n_ready); + case MBHC_BTN_DET_N_READY: + ret += sizeof(btn_det->_v_btn_high[0]) * btn_det->num_btn; + case MBHC_BTN_DET_V_BTN_HIGH: + ret += sizeof(btn_det->_v_btn_low[0]) * btn_det->num_btn; + case MBHC_BTN_DET_V_BTN_LOW: + /* do nothing */ + break; + default: + ret = NULL; + } + + return ret; +} +EXPORT_SYMBOL(wcd9xxx_mbhc_cal_btn_det_mp); + +static void wcd9xxx_calibrate_hs_polling(struct wcd9xxx_mbhc *mbhc) +{ + struct snd_soc_codec *codec = mbhc->codec; + const s16 v_ins_hu = wcd9xxx_get_current_v(mbhc, + WCD9XXX_CURRENT_V_INS_HU); + const s16 v_b1_hu = wcd9xxx_get_current_v(mbhc, + WCD9XXX_CURRENT_V_B1_HU); + const s16 v_b1_h = wcd9xxx_get_current_v(mbhc, WCD9XXX_CURRENT_V_B1_H); + const s16 v_brh = wcd9xxx_get_current_v(mbhc, WCD9XXX_CURRENT_V_BR_H); + + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B1_CTL, v_ins_hu & 0xFF); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B2_CTL, + (v_ins_hu >> 8) & 0xFF); + + if (mbhc->mbhc_state != MBHC_STATE_POTENTIAL_RECOVERY) { + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B3_CTL, v_b1_hu & + 0xFF); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B4_CTL, + (v_b1_hu >> 8) & 0xFF); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B5_CTL, v_b1_h & + 0xFF); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B6_CTL, + (v_b1_h >> 8) & 0xFF); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B9_CTL, v_brh & + 0xFF); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B10_CTL, + (v_brh >> 8) & 0xFF); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B11_CTL, + mbhc->mbhc_data.v_brl & 0xFF); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B12_CTL, + (mbhc->mbhc_data.v_brl >> 8) & 0xFF); + } +} + +static void wcd9xxx_codec_switch_cfilt_mode(struct wcd9xxx_mbhc *mbhc, + bool fast) +{ + struct snd_soc_codec *codec = mbhc->codec; + struct wcd9xxx_cfilt_mode cfilt_mode; + + if (mbhc->mbhc_cb && mbhc->mbhc_cb->switch_cfilt_mode) { + cfilt_mode = mbhc->mbhc_cb->switch_cfilt_mode(mbhc, fast); + } else { + if (fast) + cfilt_mode.reg_mode_val = WCD9XXX_CFILT_FAST_MODE; + else + cfilt_mode.reg_mode_val = WCD9XXX_CFILT_SLOW_MODE; + + cfilt_mode.reg_mask = 0x40; + cfilt_mode.cur_mode_val = + snd_soc_read(codec, mbhc->mbhc_bias_regs.cfilt_ctl) & 0x40; + } + + if (cfilt_mode.cur_mode_val + != cfilt_mode.reg_mode_val) { + if (mbhc->polling_active && wcd9xxx_mbhc_polling(mbhc)) + wcd9xxx_pause_hs_polling(mbhc); + snd_soc_update_bits(codec, + mbhc->mbhc_bias_regs.cfilt_ctl, + cfilt_mode.reg_mask, + cfilt_mode.reg_mode_val); + if (mbhc->polling_active && wcd9xxx_mbhc_polling(mbhc)) + wcd9xxx_start_hs_polling(mbhc); + pr_debug("%s: CFILT mode change (%x to %x)\n", __func__, + cfilt_mode.cur_mode_val, + cfilt_mode.reg_mode_val); + } else { + pr_debug("%s: CFILT Value is already %x\n", + __func__, cfilt_mode.cur_mode_val); + } +} + +static void wcd9xxx_jack_report(struct wcd9xxx_mbhc *mbhc, + struct snd_soc_jack *jack, int status, int mask) +{ + if (jack == &mbhc->headset_jack) { + wcd9xxx_resmgr_cond_update_cond(mbhc->resmgr, + WCD9XXX_COND_HPH_MIC, + status & SND_JACK_MICROPHONE); + wcd9xxx_resmgr_cond_update_cond(mbhc->resmgr, + WCD9XXX_COND_HPH, + status & SND_JACK_HEADPHONE); + } + + snd_soc_jack_report(jack, status, mask); +} + +static void __hphocp_off_report(struct wcd9xxx_mbhc *mbhc, u32 jack_status, + int irq) +{ + struct snd_soc_codec *codec; + + pr_debug("%s: clear ocp status %x\n", __func__, jack_status); + codec = mbhc->codec; + if (mbhc->hph_status & jack_status) { + mbhc->hph_status &= ~jack_status; + wcd9xxx_jack_report(mbhc, &mbhc->headset_jack, + mbhc->hph_status, WCD9XXX_JACK_MASK); + snd_soc_update_bits(codec, WCD9XXX_A_RX_HPH_OCP_CTL, 0x10, + 0x00); + snd_soc_update_bits(codec, WCD9XXX_A_RX_HPH_OCP_CTL, 0x10, + 0x10); + /* + * reset retry counter as PA is turned off signifying + * start of new OCP detection session + */ + if (mbhc->intr_ids->hph_left_ocp) + mbhc->hphlocp_cnt = 0; + else + mbhc->hphrocp_cnt = 0; + wcd9xxx_enable_irq(mbhc->resmgr->core_res, irq); + } +} + +static void hphrocp_off_report(struct wcd9xxx_mbhc *mbhc, u32 jack_status) +{ + __hphocp_off_report(mbhc, SND_JACK_OC_HPHR, + mbhc->intr_ids->hph_right_ocp); +} + +static void hphlocp_off_report(struct wcd9xxx_mbhc *mbhc, u32 jack_status) +{ + __hphocp_off_report(mbhc, SND_JACK_OC_HPHL, + mbhc->intr_ids->hph_left_ocp); +} + +static void wcd9xxx_get_mbhc_micbias_regs(struct wcd9xxx_mbhc *mbhc, + enum wcd9xxx_mbhc_micbias_type mb_type) +{ + unsigned int cfilt; + struct wcd9xxx_micbias_setting *micbias_pdata = + mbhc->resmgr->micbias_pdata; + struct mbhc_micbias_regs *micbias_regs; + enum wcd9xxx_micbias_num mb_num; + + if (mb_type == MBHC_ANC_MIC_MB) { + micbias_regs = &mbhc->mbhc_anc_bias_regs; + mb_num = mbhc->mbhc_cfg->anc_micbias; + } else { + micbias_regs = &mbhc->mbhc_bias_regs; + mb_num = mbhc->mbhc_cfg->micbias; + } + + switch (mb_num) { + case MBHC_MICBIAS1: + cfilt = micbias_pdata->bias1_cfilt_sel; + micbias_regs->mbhc_reg = WCD9XXX_A_MICB_1_MBHC; + micbias_regs->int_rbias = WCD9XXX_A_MICB_1_INT_RBIAS; + micbias_regs->ctl_reg = WCD9XXX_A_MICB_1_CTL; + break; + case MBHC_MICBIAS2: + cfilt = micbias_pdata->bias2_cfilt_sel; + micbias_regs->mbhc_reg = WCD9XXX_A_MICB_2_MBHC; + micbias_regs->int_rbias = WCD9XXX_A_MICB_2_INT_RBIAS; + micbias_regs->ctl_reg = WCD9XXX_A_MICB_2_CTL; + break; + case MBHC_MICBIAS3: + cfilt = micbias_pdata->bias3_cfilt_sel; + micbias_regs->mbhc_reg = WCD9XXX_A_MICB_3_MBHC; + micbias_regs->int_rbias = WCD9XXX_A_MICB_3_INT_RBIAS; + micbias_regs->ctl_reg = WCD9XXX_A_MICB_3_CTL; + break; + case MBHC_MICBIAS4: + cfilt = micbias_pdata->bias4_cfilt_sel; + micbias_regs->mbhc_reg = mbhc->resmgr->reg_addr->micb_4_mbhc; + micbias_regs->int_rbias = + mbhc->resmgr->reg_addr->micb_4_int_rbias; + micbias_regs->ctl_reg = mbhc->resmgr->reg_addr->micb_4_ctl; + break; + default: + /* Should never reach here */ + pr_err("%s: Invalid MIC BIAS for MBHC\n", __func__); + return; + } + + micbias_regs->cfilt_sel = cfilt; + + switch (cfilt) { + case WCD9XXX_CFILT1_SEL: + micbias_regs->cfilt_val = WCD9XXX_A_MICB_CFILT_1_VAL; + micbias_regs->cfilt_ctl = WCD9XXX_A_MICB_CFILT_1_CTL; + break; + case WCD9XXX_CFILT2_SEL: + micbias_regs->cfilt_val = WCD9XXX_A_MICB_CFILT_2_VAL; + micbias_regs->cfilt_ctl = WCD9XXX_A_MICB_CFILT_2_CTL; + break; + case WCD9XXX_CFILT3_SEL: + micbias_regs->cfilt_val = WCD9XXX_A_MICB_CFILT_3_VAL; + micbias_regs->cfilt_ctl = WCD9XXX_A_MICB_CFILT_3_CTL; + break; + } + + if (mb_type == MBHC_PRIMARY_MIC_MB) { + switch (cfilt) { + case WCD9XXX_CFILT1_SEL: + mbhc->mbhc_data.micb_mv = micbias_pdata->cfilt1_mv; + break; + case WCD9XXX_CFILT2_SEL: + mbhc->mbhc_data.micb_mv = micbias_pdata->cfilt2_mv; + break; + case WCD9XXX_CFILT3_SEL: + mbhc->mbhc_data.micb_mv = micbias_pdata->cfilt3_mv; + break; + } + } + +} + +static void wcd9xxx_clr_and_turnon_hph_padac(struct wcd9xxx_mbhc *mbhc) +{ + bool pa_turned_on = false; + struct snd_soc_codec *codec = mbhc->codec; + u8 wg_time; + + wg_time = snd_soc_read(codec, WCD9XXX_A_RX_HPH_CNP_WG_TIME); + wg_time += 1; + + if (test_and_clear_bit(WCD9XXX_HPHR_DAC_OFF_ACK, + &mbhc->hph_pa_dac_state)) { + pr_debug("%s: HPHR clear flag and enable DAC\n", __func__); + snd_soc_update_bits(codec, WCD9XXX_A_RX_HPH_R_DAC_CTL, + 0xC0, 0xC0); + } + if (test_and_clear_bit(WCD9XXX_HPHL_DAC_OFF_ACK, + &mbhc->hph_pa_dac_state)) { + pr_debug("%s: HPHL clear flag and enable DAC\n", __func__); + snd_soc_update_bits(codec, WCD9XXX_A_RX_HPH_L_DAC_CTL, + 0x80, 0x80); + } + + if (test_and_clear_bit(WCD9XXX_HPHR_PA_OFF_ACK, + &mbhc->hph_pa_dac_state)) { + pr_debug("%s: HPHR clear flag and enable PA\n", __func__); + snd_soc_update_bits(codec, WCD9XXX_A_RX_HPH_CNP_EN, 0x10, + 1 << 4); + pa_turned_on = true; + } + if (test_and_clear_bit(WCD9XXX_HPHL_PA_OFF_ACK, + &mbhc->hph_pa_dac_state)) { + pr_debug("%s: HPHL clear flag and enable PA\n", __func__); + snd_soc_update_bits(codec, WCD9XXX_A_RX_HPH_CNP_EN, 0x20, 1 + << 5); + pa_turned_on = true; + } + + if (pa_turned_on) { + pr_debug("%s: PA was turned on by MBHC and not by DAPM\n", + __func__); + usleep_range(wg_time * 1000, wg_time * 1000 + 50); + } +} + +static int wcd9xxx_cancel_btn_work(struct wcd9xxx_mbhc *mbhc) +{ + int r; + r = cancel_delayed_work_sync(&mbhc->mbhc_btn_dwork); + if (r) + /* if scheduled mbhc.mbhc_btn_dwork is canceled from here, + * we have to unlock from here instead btn_work */ + wcd9xxx_unlock_sleep(mbhc->resmgr->core_res); + return r; +} + +static bool wcd9xxx_is_hph_dac_on(struct snd_soc_codec *codec, int left) +{ + u8 hph_reg_val = 0; + if (left) + hph_reg_val = snd_soc_read(codec, WCD9XXX_A_RX_HPH_L_DAC_CTL); + else + hph_reg_val = snd_soc_read(codec, WCD9XXX_A_RX_HPH_R_DAC_CTL); + + return (hph_reg_val & 0xC0) ? true : false; +} + +static bool wcd9xxx_is_hph_pa_on(struct snd_soc_codec *codec) +{ + u8 hph_reg_val = 0; + hph_reg_val = snd_soc_read(codec, WCD9XXX_A_RX_HPH_CNP_EN); + + return (hph_reg_val & 0x30) ? true : false; +} + +/* called under codec_resource_lock acquisition */ +static void wcd9xxx_set_and_turnoff_hph_padac(struct wcd9xxx_mbhc *mbhc) +{ + u8 wg_time; + struct snd_soc_codec *codec = mbhc->codec; + + wg_time = snd_soc_read(codec, WCD9XXX_A_RX_HPH_CNP_WG_TIME); + wg_time += 1; + + /* If headphone PA is on, check if userspace receives + * removal event to sync-up PA's state */ + if (wcd9xxx_is_hph_pa_on(codec)) { + pr_debug("%s PA is on, setting PA_OFF_ACK\n", __func__); + set_bit(WCD9XXX_HPHL_PA_OFF_ACK, &mbhc->hph_pa_dac_state); + set_bit(WCD9XXX_HPHR_PA_OFF_ACK, &mbhc->hph_pa_dac_state); + } else { + pr_debug("%s PA is off\n", __func__); + } + + if (wcd9xxx_is_hph_dac_on(codec, 1)) + set_bit(WCD9XXX_HPHL_DAC_OFF_ACK, &mbhc->hph_pa_dac_state); + if (wcd9xxx_is_hph_dac_on(codec, 0)) + set_bit(WCD9XXX_HPHR_DAC_OFF_ACK, &mbhc->hph_pa_dac_state); + + snd_soc_update_bits(codec, WCD9XXX_A_RX_HPH_CNP_EN, 0x30, 0x00); + snd_soc_update_bits(codec, WCD9XXX_A_RX_HPH_L_DAC_CTL, 0x80, 0x00); + snd_soc_update_bits(codec, WCD9XXX_A_RX_HPH_R_DAC_CTL, 0xC0, 0x00); + usleep_range(wg_time * 1000, wg_time * 1000 + 50); +} + +static void wcd9xxx_insert_detect_setup(struct wcd9xxx_mbhc *mbhc, bool ins) +{ + if (!mbhc->mbhc_cfg->insert_detect) + return; + pr_debug("%s: Setting up %s detection\n", __func__, + ins ? "insert" : "removal"); + /* Disable detection to avoid glitch */ + snd_soc_update_bits(mbhc->codec, WCD9XXX_A_MBHC_INSERT_DETECT, 1, 0); + if (mbhc->mbhc_cfg->gpio_level_insert) + snd_soc_write(mbhc->codec, WCD9XXX_A_MBHC_INSERT_DETECT, + (0x68 | (ins ? (1 << 1) : 0))); + else + snd_soc_write(mbhc->codec, WCD9XXX_A_MBHC_INSERT_DETECT, + (0x6C | (ins ? (1 << 1) : 0))); + /* Re-enable detection */ + snd_soc_update_bits(mbhc->codec, WCD9XXX_A_MBHC_INSERT_DETECT, 1, 1); +} + +/* called under codec_resource_lock acquisition */ +static void wcd9xxx_report_plug(struct wcd9xxx_mbhc *mbhc, int insertion, + enum snd_jack_types jack_type) +{ + WCD9XXX_BCL_ASSERT_LOCKED(mbhc->resmgr); + + pr_debug("%s: enter insertion %d hph_status %x\n", + __func__, insertion, mbhc->hph_status); + if (!insertion) { + /* Report removal */ + mbhc->hph_status &= ~jack_type; + /* + * cancel possibly scheduled btn work and + * report release if we reported button press + */ + if (wcd9xxx_cancel_btn_work(mbhc)) + pr_debug("%s: button press is canceled\n", __func__); + else if (mbhc->buttons_pressed) { + pr_debug("%s: release of button press%d\n", + __func__, jack_type); + wcd9xxx_jack_report(mbhc, &mbhc->button_jack, 0, + mbhc->buttons_pressed); + mbhc->buttons_pressed &= + ~WCD9XXX_JACK_BUTTON_MASK; + } + + if (mbhc->micbias_enable && mbhc->micbias_enable_cb) { + pr_debug("%s: Disabling micbias\n", __func__); + mbhc->micbias_enable = false; + mbhc->micbias_enable_cb(mbhc->codec, false, + mbhc->mbhc_cfg->micbias); + } + mbhc->zl = mbhc->zr = 0; + mbhc->hph_type = MBHC_HPH_NONE; + pr_debug("%s: Reporting removal %d(%x)\n", __func__, + jack_type, mbhc->hph_status); + wcd9xxx_jack_report(mbhc, &mbhc->headset_jack, mbhc->hph_status, + WCD9XXX_JACK_MASK); + wcd9xxx_set_and_turnoff_hph_padac(mbhc); + hphrocp_off_report(mbhc, SND_JACK_OC_HPHR); + hphlocp_off_report(mbhc, SND_JACK_OC_HPHL); + mbhc->current_plug = PLUG_TYPE_NONE; + mbhc->polling_active = false; + if (mbhc->mbhc_cb && mbhc->mbhc_cb->hph_auto_pulldown_ctrl) + mbhc->mbhc_cb->hph_auto_pulldown_ctrl(mbhc->codec, + false); + } else { + /* + * Report removal of current jack type. + * Headphone to headset shouldn't report headphone + * removal. + */ + if (mbhc->mbhc_cfg->detect_extn_cable && + !(mbhc->current_plug == PLUG_TYPE_HEADPHONE && + jack_type == SND_JACK_HEADSET) && + (mbhc->hph_status && mbhc->hph_status != jack_type)) { + if (mbhc->micbias_enable && mbhc->micbias_enable_cb && + mbhc->hph_status == SND_JACK_HEADSET) { + pr_debug("%s: Disabling micbias\n", __func__); + mbhc->micbias_enable = false; + mbhc->micbias_enable_cb(mbhc->codec, false, + mbhc->mbhc_cfg->micbias); + } + + pr_debug("%s: Reporting removal (%x)\n", + __func__, mbhc->hph_status); + mbhc->zl = mbhc->zr = 0; + wcd9xxx_jack_report(mbhc, &mbhc->headset_jack, + 0, WCD9XXX_JACK_MASK); + mbhc->hph_status &= ~(SND_JACK_HEADSET | + SND_JACK_LINEOUT | + SND_JACK_ANC_HEADPHONE | + SND_JACK_UNSUPPORTED); + if (mbhc->mbhc_cb && + mbhc->mbhc_cb->hph_auto_pulldown_ctrl) + mbhc->mbhc_cb->hph_auto_pulldown_ctrl( + mbhc->codec, + false); + } + + /* Report insertion */ + if (jack_type == SND_JACK_HEADPHONE) { + mbhc->current_plug = PLUG_TYPE_HEADPHONE; + } else if (jack_type == SND_JACK_UNSUPPORTED) { + mbhc->current_plug = PLUG_TYPE_GND_MIC_SWAP; + } else if (jack_type == SND_JACK_HEADSET) { + mbhc->polling_active = BUTTON_POLLING_SUPPORTED; + mbhc->current_plug = PLUG_TYPE_HEADSET; + mbhc->update_z = true; + } else if (jack_type == SND_JACK_LINEOUT) { + mbhc->current_plug = PLUG_TYPE_HIGH_HPH; + } else if (jack_type == SND_JACK_ANC_HEADPHONE) { + mbhc->polling_active = BUTTON_POLLING_SUPPORTED; + mbhc->current_plug = PLUG_TYPE_ANC_HEADPHONE; + } + + if (mbhc->impedance_detect && impedance_detect_en) { + wcd9xxx_detect_impedance(mbhc, + &mbhc->zl, &mbhc->zr); + if ((mbhc->zl > WCD9XXX_LINEIN_THRESHOLD) && + (mbhc->zr > WCD9XXX_LINEIN_THRESHOLD)) { + jack_type = SND_JACK_LINEOUT; + mbhc->current_plug = PLUG_TYPE_HIGH_HPH; + pr_debug("%s: Replace with SND_JACK_LINEOUT\n", + __func__); + } + } + + mbhc->hph_status |= jack_type; + + if (mbhc->micbias_enable && mbhc->micbias_enable_cb) { + pr_debug("%s: Enabling micbias\n", __func__); + mbhc->micbias_enable_cb(mbhc->codec, true, + mbhc->mbhc_cfg->micbias); + } + + pr_debug("%s: Reporting insertion %d(%x)\n", __func__, + jack_type, mbhc->hph_status); + wcd9xxx_jack_report(mbhc, &mbhc->headset_jack, + (mbhc->hph_status | SND_JACK_MECHANICAL), + WCD9XXX_JACK_MASK); + /* + * if PA is already on, switch micbias + * source to VDDIO + */ + if (((mbhc->current_plug == PLUG_TYPE_HEADSET) || + (mbhc->current_plug == PLUG_TYPE_ANC_HEADPHONE)) && + ((mbhc->event_state & (1 << MBHC_EVENT_PA_HPHL | + 1 << MBHC_EVENT_PA_HPHR)))) + __wcd9xxx_switch_micbias(mbhc, 1, false, + false); + wcd9xxx_clr_and_turnon_hph_padac(mbhc); + } + /* Setup insert detect */ + wcd9xxx_insert_detect_setup(mbhc, !insertion); + + pr_debug("%s: leave hph_status %x\n", __func__, mbhc->hph_status); +} + +/* should be called under interrupt context that hold suspend */ +static void wcd9xxx_schedule_hs_detect_plug(struct wcd9xxx_mbhc *mbhc, + struct work_struct *work) +{ + pr_debug("%s: scheduling wcd9xxx_correct_swch_plug\n", __func__); + WCD9XXX_BCL_ASSERT_LOCKED(mbhc->resmgr); + mbhc->hs_detect_work_stop = false; + wcd9xxx_lock_sleep(mbhc->resmgr->core_res); + schedule_work(work); +} + +/* called under codec_resource_lock acquisition */ +static void wcd9xxx_cancel_hs_detect_plug(struct wcd9xxx_mbhc *mbhc, + struct work_struct *work) +{ + pr_debug("%s: Canceling correct_plug_swch\n", __func__); + WCD9XXX_BCL_ASSERT_LOCKED(mbhc->resmgr); + mbhc->hs_detect_work_stop = true; + wmb(); + WCD9XXX_BCL_UNLOCK(mbhc->resmgr); + if (cancel_work_sync(work)) { + pr_debug("%s: correct_plug_swch is canceled\n", + __func__); + wcd9xxx_unlock_sleep(mbhc->resmgr->core_res); + } + WCD9XXX_BCL_LOCK(mbhc->resmgr); +} + +static s16 scale_v_micb_vddio(struct wcd9xxx_mbhc *mbhc, int v, bool tovddio) +{ + int r; + int vddio_k, mb_k; + vddio_k = __wcd9xxx_resmgr_get_k_val(mbhc, VDDIO_MICBIAS_MV); + mb_k = __wcd9xxx_resmgr_get_k_val(mbhc, mbhc->mbhc_data.micb_mv); + if (tovddio) + r = v * (vddio_k + 4) / (mb_k + 4); + else + r = v * (mb_k + 4) / (vddio_k + 4); + return r; +} + +static s16 wcd9xxx_get_current_v_hs_max(struct wcd9xxx_mbhc *mbhc) +{ + s16 v_hs_max; + struct wcd9xxx_mbhc_plug_type_cfg *plug_type; + + plug_type = WCD9XXX_MBHC_CAL_PLUG_TYPE_PTR(mbhc->mbhc_cfg->calibration); + if ((mbhc->mbhc_data.micb_mv != VDDIO_MICBIAS_MV) && + mbhc->mbhc_micbias_switched) + v_hs_max = scale_v_micb_vddio(mbhc, plug_type->v_hs_max, true); + else + v_hs_max = plug_type->v_hs_max; + return v_hs_max; +} + +static short wcd9xxx_read_sta_result(struct snd_soc_codec *codec) +{ + u8 bias_msb, bias_lsb; + short bias_value; + + bias_msb = snd_soc_read(codec, WCD9XXX_A_CDC_MBHC_B3_STATUS); + bias_lsb = snd_soc_read(codec, WCD9XXX_A_CDC_MBHC_B2_STATUS); + bias_value = (bias_msb << 8) | bias_lsb; + return bias_value; +} + +static short wcd9xxx_read_dce_result(struct snd_soc_codec *codec) +{ + u8 bias_msb, bias_lsb; + short bias_value; + + bias_msb = snd_soc_read(codec, WCD9XXX_A_CDC_MBHC_B5_STATUS); + bias_lsb = snd_soc_read(codec, WCD9XXX_A_CDC_MBHC_B4_STATUS); + bias_value = (bias_msb << 8) | bias_lsb; + return bias_value; +} + +static void wcd9xxx_turn_onoff_rel_detection(struct snd_soc_codec *codec, + bool on) +{ + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_B1_CTL, 0x02, on << 1); +} + +static short __wcd9xxx_codec_sta_dce(struct wcd9xxx_mbhc *mbhc, int dce, + bool override_bypass, bool noreldetection) +{ + short bias_value; + struct snd_soc_codec *codec = mbhc->codec; + + wcd9xxx_disable_irq(mbhc->resmgr->core_res, + mbhc->intr_ids->dce_est_complete); + if (noreldetection) + wcd9xxx_turn_onoff_rel_detection(codec, false); + + if (mbhc->mbhc_cfg->do_recalibration) + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x2, + 0x0); + /* Turn on the override */ + if (!override_bypass) + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_B1_CTL, 0x4, 0x4); + if (dce) { + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x8, + 0x8); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_EN_CTL, 0x4); + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x8, + 0x0); + if (mbhc->mbhc_cfg->do_recalibration) + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, + 0x2, 0x2); + usleep_range(mbhc->mbhc_data.t_sta_dce, + mbhc->mbhc_data.t_sta_dce + 50); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_EN_CTL, 0x4); + usleep_range(mbhc->mbhc_data.t_dce, mbhc->mbhc_data.t_dce + 50); + bias_value = wcd9xxx_read_dce_result(codec); + } else { + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x8, + 0x8); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_EN_CTL, 0x2); + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x8, + 0x0); + if (mbhc->mbhc_cfg->do_recalibration) + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, + 0x2, 0x2); + usleep_range(mbhc->mbhc_data.t_sta_dce, + mbhc->mbhc_data.t_sta_dce + 50); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_EN_CTL, 0x2); + usleep_range(mbhc->mbhc_data.t_sta, + mbhc->mbhc_data.t_sta + 50); + bias_value = wcd9xxx_read_sta_result(codec); + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x8, + 0x8); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_EN_CTL, 0x0); + } + /* Turn off the override after measuring mic voltage */ + if (!override_bypass) + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_B1_CTL, 0x04, + 0x00); + + if (noreldetection) + wcd9xxx_turn_onoff_rel_detection(codec, true); + wcd9xxx_enable_irq(mbhc->resmgr->core_res, + mbhc->intr_ids->dce_est_complete); + + return bias_value; +} + +static short wcd9xxx_codec_sta_dce(struct wcd9xxx_mbhc *mbhc, int dce, + bool norel) +{ + bool override_bypass; + + /* Bypass override if it is already enabled */ + override_bypass = (snd_soc_read(mbhc->codec, + WCD9XXX_A_CDC_MBHC_B1_CTL) & + 0x04) ? true : false; + + return __wcd9xxx_codec_sta_dce(mbhc, dce, override_bypass, norel); +} + +static s32 __wcd9xxx_codec_sta_dce_v(struct wcd9xxx_mbhc *mbhc, s8 dce, + u16 bias_value, s16 z, u32 micb_mv) +{ + s16 value, mb; + s32 mv = 0; + + value = bias_value; + if (dce) { + mb = (mbhc->mbhc_data.dce_mb); + if (mb - z) + mv = (value - z) * (s32)micb_mv / (mb - z); + } else { + mb = (mbhc->mbhc_data.sta_mb); + if (mb - z) + mv = (value - z) * (s32)micb_mv / (mb - z); + } + + return mv; +} + +static s32 wcd9xxx_codec_sta_dce_v(struct wcd9xxx_mbhc *mbhc, s8 dce, + u16 bias_value) +{ + s16 z; + z = dce ? (s16)mbhc->mbhc_data.dce_z : (s16)mbhc->mbhc_data.sta_z; + return __wcd9xxx_codec_sta_dce_v(mbhc, dce, bias_value, z, + mbhc->mbhc_data.micb_mv); +} + +/* To enable/disable bandgap and RC oscillator */ +static void wcd9xxx_mbhc_ctrl_clk_bandgap(struct wcd9xxx_mbhc *mbhc, + bool enable) +{ + if (enable) { + WCD9XXX_BG_CLK_LOCK(mbhc->resmgr); + wcd9xxx_resmgr_get_bandgap(mbhc->resmgr, + WCD9XXX_BANDGAP_AUDIO_MODE); + if (mbhc->mbhc_cb && mbhc->mbhc_cb->codec_rco_ctrl) { + WCD9XXX_BG_CLK_UNLOCK(mbhc->resmgr); + mbhc->mbhc_cb->codec_rco_ctrl(mbhc->codec, true); + } else { + wcd9xxx_resmgr_get_clk_block(mbhc->resmgr, + WCD9XXX_CLK_RCO); + WCD9XXX_BG_CLK_UNLOCK(mbhc->resmgr); + } + } else { + if (mbhc->mbhc_cb && mbhc->mbhc_cb->codec_rco_ctrl) { + mbhc->mbhc_cb->codec_rco_ctrl(mbhc->codec, false); + WCD9XXX_BG_CLK_LOCK(mbhc->resmgr); + } else { + WCD9XXX_BG_CLK_LOCK(mbhc->resmgr); + wcd9xxx_resmgr_put_clk_block(mbhc->resmgr, + WCD9XXX_CLK_RCO); + } + wcd9xxx_resmgr_put_bandgap(mbhc->resmgr, + WCD9XXX_BANDGAP_AUDIO_MODE); + WCD9XXX_BG_CLK_UNLOCK(mbhc->resmgr); + } +} + +/* called only from interrupt which is under codec_resource_lock acquisition */ +static short wcd9xxx_mbhc_setup_hs_polling(struct wcd9xxx_mbhc *mbhc, + struct mbhc_micbias_regs *mbhc_micb_regs, + bool is_cs_enable) +{ + struct snd_soc_codec *codec = mbhc->codec; + short bias_value; + u8 cfilt_mode; + + WCD9XXX_BCL_ASSERT_LOCKED(mbhc->resmgr); + + pr_debug("%s: enter\n", __func__); + if (!mbhc->mbhc_cfg->calibration) { + pr_err("%s: Error, no calibration exists\n", __func__); + return -ENODEV; + } + + /* Enable external voltage source to micbias if present */ + if (mbhc->mbhc_cb && mbhc->mbhc_cb->enable_mb_source) + mbhc->mbhc_cb->enable_mb_source(codec, true, true); + + /* + * setup internal micbias if codec uses internal micbias for + * headset detection + */ + if (mbhc->mbhc_cfg->use_int_rbias) { + if (mbhc->mbhc_cb && mbhc->mbhc_cb->setup_int_rbias) + mbhc->mbhc_cb->setup_int_rbias(codec, true); + else + pr_err("%s: internal bias requested but codec did not provide callback\n", + __func__); + } + + snd_soc_update_bits(codec, WCD9XXX_A_CLK_BUFF_EN1, 0x05, 0x01); + + /* Make sure CFILT is in fast mode, save current mode */ + cfilt_mode = snd_soc_read(codec, mbhc_micb_regs->cfilt_ctl); + if (mbhc->mbhc_cb && mbhc->mbhc_cb->cfilt_fast_mode) + mbhc->mbhc_cb->cfilt_fast_mode(codec, mbhc); + else + snd_soc_update_bits(codec, mbhc_micb_regs->cfilt_ctl, + 0x70, 0x00); + + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x2, 0x2); + snd_soc_write(codec, WCD9XXX_A_MBHC_SCALING_MUX_1, + mbhc->scaling_mux_in); + pr_debug("%s: scaling_mux_input: %d\n", __func__, + mbhc->scaling_mux_in); + + if (mbhc->mbhc_cb && mbhc->mbhc_cb->enable_mux_bias_block) + mbhc->mbhc_cb->enable_mux_bias_block(codec); + else + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_SCALING_MUX_1, + 0x80, 0x80); + + snd_soc_update_bits(codec, WCD9XXX_A_TX_7_MBHC_EN, 0x80, 0x80); + snd_soc_update_bits(codec, WCD9XXX_A_TX_7_MBHC_EN, 0x1F, 0x1C); + snd_soc_update_bits(codec, WCD9XXX_A_TX_7_MBHC_TEST_CTL, 0x40, 0x40); + + snd_soc_update_bits(codec, WCD9XXX_A_TX_7_MBHC_EN, 0x80, 0x00); + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x8, 0x00); + + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_B1_CTL, 0x2, 0x2); + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + + if (!mbhc->mbhc_cfg->do_recalibration) { + if (!is_cs_enable) + wcd9xxx_calibrate_hs_polling(mbhc); + } + + /* don't flip override */ + bias_value = __wcd9xxx_codec_sta_dce(mbhc, 1, true, true); + snd_soc_write(codec, mbhc_micb_regs->cfilt_ctl, cfilt_mode); + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_HPH, 0x13, 0x00); + + return bias_value; +} + +static void wcd9xxx_recalibrate(struct wcd9xxx_mbhc *mbhc, + struct mbhc_micbias_regs *mbhc_micb_regs, + bool is_cs_enable) +{ + struct snd_soc_codec *codec = mbhc->codec; + s16 reg; + int change; + struct wcd9xxx_mbhc_btn_detect_cfg *btn_det; + s16 sta_z = 0, dce_z = 0; + + btn_det = WCD9XXX_MBHC_CAL_BTN_DET_PTR(mbhc->mbhc_cfg->calibration); + + if (mbhc->mbhc_cfg->do_recalibration) { + /* recalibrate dce_z and sta_z */ + reg = snd_soc_read(codec, WCD9XXX_A_CDC_MBHC_B1_CTL); + change = snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_B1_CTL, + 0x78, btn_det->mbhc_nsc << 3); + wcd9xxx_get_z(mbhc, &dce_z, &sta_z, mbhc_micb_regs, true); + if (change) + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_B1_CTL, reg); + if (dce_z && sta_z) { + pr_debug("%s: sta_z 0x%x -> 0x%x, dce_z 0x%x -> 0x%x\n", + __func__, + mbhc->mbhc_data.sta_z, sta_z & 0xffff, + mbhc->mbhc_data.dce_z, dce_z & 0xffff); + mbhc->mbhc_data.dce_z = dce_z; + mbhc->mbhc_data.sta_z = sta_z; + wcd9xxx_mbhc_calc_thres(mbhc); + wcd9xxx_calibrate_hs_polling(mbhc); + } else { + pr_warn("%s: failed get new dce_z/sta_z 0x%x/0x%x\n", + __func__, dce_z, sta_z); + } + + if (is_cs_enable) { + /* recalibrate dce_nsc_cs_z */ + reg = snd_soc_read(mbhc->codec, + WCD9XXX_A_CDC_MBHC_B1_CTL); + snd_soc_update_bits(mbhc->codec, + WCD9XXX_A_CDC_MBHC_B1_CTL, + 0x78, WCD9XXX_MBHC_NSC_CS << 3); + wcd9xxx_get_z(mbhc, &dce_z, NULL, mbhc_micb_regs, + true); + snd_soc_write(mbhc->codec, WCD9XXX_A_CDC_MBHC_B1_CTL, + reg); + if (dce_z) { + mbhc->mbhc_data.dce_nsc_cs_z = dce_z; + /* update v_cs_ins_h with new dce_nsc_cs_z */ + mbhc->mbhc_data.v_cs_ins_h = + wcd9xxx_codec_v_sta_dce( + mbhc, DCE, + WCD9XXX_V_CS_HS_MAX, + is_cs_enable); + pr_debug("%s: dce_nsc_cs_z 0x%x -> 0x%x, v_cs_ins_h 0x%x\n", + __func__, + mbhc->mbhc_data.dce_nsc_cs_z, + dce_z & 0xffff, + mbhc->mbhc_data.v_cs_ins_h); + } else { + pr_debug("%s: failed get new dce_nsc_cs_z\n", + __func__); + } + } + } +} + +static void wcd9xxx_shutdown_hs_removal_detect(struct wcd9xxx_mbhc *mbhc) +{ + struct snd_soc_codec *codec = mbhc->codec; + const struct wcd9xxx_mbhc_general_cfg *generic = + WCD9XXX_MBHC_CAL_GENERAL_PTR(mbhc->mbhc_cfg->calibration); + + /* Need MBHC clock */ + if (mbhc->mbhc_cb && mbhc->mbhc_cb->codec_rco_ctrl) + mbhc->mbhc_cb->codec_rco_ctrl(mbhc->codec, true); + else { + WCD9XXX_BG_CLK_LOCK(mbhc->resmgr); + wcd9xxx_resmgr_get_clk_block(mbhc->resmgr, WCD9XXX_CLK_RCO); + WCD9XXX_BG_CLK_UNLOCK(mbhc->resmgr); + } + + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x2, 0x2); + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_B1_CTL, 0x6, 0x0); + __wcd9xxx_switch_micbias(mbhc, 0, false, false); + + usleep_range(generic->t_shutdown_plug_rem, + generic->t_shutdown_plug_rem + 50); + + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0xA, 0x8); + + if (mbhc->mbhc_cb && mbhc->mbhc_cb->codec_rco_ctrl) + mbhc->mbhc_cb->codec_rco_ctrl(mbhc->codec, false); + else { + WCD9XXX_BG_CLK_LOCK(mbhc->resmgr); + /* Put requested CLK back */ + wcd9xxx_resmgr_put_clk_block(mbhc->resmgr, WCD9XXX_CLK_RCO); + WCD9XXX_BG_CLK_UNLOCK(mbhc->resmgr); + } + + snd_soc_write(codec, WCD9XXX_A_MBHC_SCALING_MUX_1, 0x00); +} + +static void wcd9xxx_cleanup_hs_polling(struct wcd9xxx_mbhc *mbhc) +{ + + pr_debug("%s: enter\n", __func__); + WCD9XXX_BCL_ASSERT_LOCKED(mbhc->resmgr); + + wcd9xxx_shutdown_hs_removal_detect(mbhc); + + + /* Disable external voltage source to micbias if present */ + if (mbhc->mbhc_cb && mbhc->mbhc_cb->enable_mb_source) + mbhc->mbhc_cb->enable_mb_source(mbhc->codec, false, true); + + mbhc->polling_active = false; + mbhc->mbhc_state = MBHC_STATE_NONE; + pr_debug("%s: leave\n", __func__); +} + +/* called under codec_resource_lock acquisition */ +static void wcd9xxx_codec_hphr_gnd_switch(struct snd_soc_codec *codec, bool on) +{ + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_HPH, 0x01, on); + if (on) + usleep_range(5000, 5100); +} + +static void wcd9xxx_onoff_vddio_switch(struct wcd9xxx_mbhc *mbhc, bool on) +{ + pr_debug("%s: vddio %d\n", __func__, on); + + if (mbhc->mbhc_cb && mbhc->mbhc_cb->pull_mb_to_vddio) { + mbhc->mbhc_cb->pull_mb_to_vddio(mbhc->codec, on); + goto exit; + } + + if (on) { + snd_soc_update_bits(mbhc->codec, mbhc->mbhc_bias_regs.mbhc_reg, + 1 << 7, 1 << 7); + snd_soc_update_bits(mbhc->codec, WCD9XXX_A_MAD_ANA_CTRL, + 1 << 4, 0); + } else { + snd_soc_update_bits(mbhc->codec, WCD9XXX_A_MAD_ANA_CTRL, + 1 << 4, 1 << 4); + snd_soc_update_bits(mbhc->codec, mbhc->mbhc_bias_regs.mbhc_reg, + 1 << 7, 0); + } + +exit: + /* + * Wait for the micbias to settle down to vddio + * when the micbias to vddio switch is enabled. + */ + if (on) + usleep_range(10000, 10100); +} + +static int wcd9xxx_hphl_status(struct wcd9xxx_mbhc *mbhc) +{ + u16 hph, status; + struct snd_soc_codec *codec = mbhc->codec; + + WCD9XXX_BCL_ASSERT_LOCKED(mbhc->resmgr); + hph = snd_soc_read(codec, WCD9XXX_A_MBHC_HPH); + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_HPH, 0x12, 0x02); + usleep_range(WCD9XXX_HPHL_STATUS_READY_WAIT_US, + WCD9XXX_HPHL_STATUS_READY_WAIT_US + + WCD9XXX_USLEEP_RANGE_MARGIN_US); + status = snd_soc_read(codec, WCD9XXX_A_RX_HPH_L_STATUS); + snd_soc_write(codec, WCD9XXX_A_MBHC_HPH, hph); + return status; +} + +static enum wcd9xxx_mbhc_plug_type +wcd9xxx_cs_find_plug_type(struct wcd9xxx_mbhc *mbhc, + struct wcd9xxx_mbhc_detect *dt, const int size, + bool highhph, + unsigned long event_state) +{ + int i; + int vdce, mb_mv; + int ch, sz, delta_thr; + int minv = 0, maxv = INT_MIN; + struct wcd9xxx_mbhc_detect *d = dt; + struct wcd9xxx_mbhc_detect *dprev = d, *dmicbias = NULL, *dgnd = NULL; + enum wcd9xxx_mbhc_plug_type type = PLUG_TYPE_INVALID; + + const struct wcd9xxx_mbhc_plug_type_cfg *plug_type = + WCD9XXX_MBHC_CAL_PLUG_TYPE_PTR(mbhc->mbhc_cfg->calibration); + s16 hs_max, no_mic, dce_z; + int highhph_cnt = 0; + + pr_debug("%s: enter\n", __func__); + pr_debug("%s: event_state 0x%lx\n", __func__, event_state); + + sz = size - 1; + for (i = 0, d = dt, ch = 0; i < sz; i++, d++) { + if (d->mic_bias) { + dce_z = mbhc->mbhc_data.dce_z; + mb_mv = mbhc->mbhc_data.micb_mv; + hs_max = plug_type->v_hs_max; + no_mic = plug_type->v_no_mic; + } else { + dce_z = mbhc->mbhc_data.dce_nsc_cs_z; + mb_mv = VDDIO_MICBIAS_MV; + hs_max = WCD9XXX_V_CS_HS_MAX; + no_mic = WCD9XXX_V_CS_NO_MIC; + } + + vdce = __wcd9xxx_codec_sta_dce_v(mbhc, true, d->dce, + dce_z, (u32)mb_mv); + d->_vdces = vdce; + if (d->_vdces < no_mic) + d->_type = PLUG_TYPE_HEADPHONE; + else if (d->_vdces >= hs_max) { + d->_type = PLUG_TYPE_HIGH_HPH; + highhph_cnt++; + } else + d->_type = PLUG_TYPE_HEADSET; + + pr_debug("%s: DCE #%d, %04x, V %04d(%04d), HPHL %d TYPE %d\n", + __func__, i, d->dce, vdce, d->_vdces, + d->hphl_status & 0x01, + d->_type); + + ch += d->hphl_status & 0x01; + if (!d->swap_gnd && !d->mic_bias) { + if (maxv < d->_vdces) + maxv = d->_vdces; + if (!minv || minv > d->_vdces) + minv = d->_vdces; + } + if ((!d->mic_bias && + (d->_vdces >= WCD9XXX_CS_MEAS_INVALD_RANGE_LOW_MV && + d->_vdces <= WCD9XXX_CS_MEAS_INVALD_RANGE_HIGH_MV)) || + (d->mic_bias && + (d->_vdces >= WCD9XXX_MEAS_INVALD_RANGE_LOW_MV && + d->_vdces <= WCD9XXX_MEAS_INVALD_RANGE_HIGH_MV))) { + pr_debug("%s: within invalid range\n", __func__); + type = PLUG_TYPE_INVALID; + goto exit; + } + } + + delta_thr = ((highhph_cnt == sz) || highhph) ? + WCD9XXX_MB_MEAS_DELTA_MAX_MV : + WCD9XXX_CS_MEAS_DELTA_MAX_MV; + + for (i = 0, d = dt; i < sz; i++, d++) { + if ((i > 0) && !d->mic_bias && !d->swap_gnd && + (d->_type != dprev->_type)) { + pr_debug("%s: Invalid, inconsistent types\n", __func__); + type = PLUG_TYPE_INVALID; + goto exit; + } + + if (!d->swap_gnd && !d->mic_bias && + (abs(minv - d->_vdces) > delta_thr || + abs(maxv - d->_vdces) > delta_thr)) { + pr_debug("%s: Invalid, delta %dmv, %dmv and %dmv\n", + __func__, d->_vdces, minv, maxv); + type = PLUG_TYPE_INVALID; + goto exit; + } else if (d->swap_gnd) { + dgnd = d; + } + + if (!d->mic_bias && !d->swap_gnd) + dprev = d; + else if (d->mic_bias) + dmicbias = d; + } + if (dgnd && dt->_type != PLUG_TYPE_HEADSET && + dt->_type != dgnd->_type) { + pr_debug("%s: Invalid, inconsistent types\n", __func__); + type = PLUG_TYPE_INVALID; + goto exit; + } + + type = dt->_type; + if (dmicbias) { + if (dmicbias->_type == PLUG_TYPE_HEADSET && + (dt->_type == PLUG_TYPE_HIGH_HPH || + dt->_type == PLUG_TYPE_HEADSET)) { + type = PLUG_TYPE_HEADSET; + if (dt->_type == PLUG_TYPE_HIGH_HPH) { + pr_debug("%s: Headset with threshold on MIC detected\n", + __func__); + if (mbhc->mbhc_cfg->micbias_enable_flags & + (1 << MBHC_MICBIAS_ENABLE_THRESHOLD_HEADSET)) + mbhc->micbias_enable = true; + } + } + } + + if (type == PLUG_TYPE_HEADSET && dgnd && !dgnd->mic_bias) { + /* if plug type is Headphone report as GND_MIC_SWAP */ + if (dgnd->_type == PLUG_TYPE_HEADPHONE) { + pr_debug("%s: GND_MIC_SWAP\n", __func__); + type = PLUG_TYPE_GND_MIC_SWAP; + /* + * if type is GND_MIC_SWAP we should not check + * HPHL status hence goto exit + */ + goto exit; + } else if (dgnd->_type != PLUG_TYPE_HEADSET && !dmicbias) { + pr_debug("%s: Invalid, inconsistent types\n", __func__); + type = PLUG_TYPE_INVALID; + } + } + + if (event_state & (1 << MBHC_EVENT_PA_HPHL)) { + pr_debug("%s: HPHL PA was ON\n", __func__); + } else if (ch != sz && ch > 0) { + pr_debug("%s: Invalid, inconsistent HPHL..\n", __func__); + type = PLUG_TYPE_INVALID; + goto exit; + } + + if (!(event_state & (1UL << MBHC_EVENT_PA_HPHL))) { + if (((type == PLUG_TYPE_HEADSET || + type == PLUG_TYPE_HEADPHONE) && ch != sz)) { + pr_debug("%s: Invalid, not fully inserted, TYPE %d\n", + __func__, type); + type = PLUG_TYPE_INVALID; + } + } + + if (type == PLUG_TYPE_HEADSET && + (mbhc->mbhc_cfg->micbias_enable_flags & + (1 << MBHC_MICBIAS_ENABLE_REGULAR_HEADSET))) + mbhc->micbias_enable = true; + +exit: + pr_debug("%s: Plug type %d detected\n", __func__, type); + return type; +} + +/* + * wcd9xxx_find_plug_type : Find out and return the best plug type with given + * list of wcd9xxx_mbhc_detect structure. + * param mbhc wcd9xxx_mbhc structure + * param dt collected measurements + * param size array size of dt + * param event_state mbhc->event_state when dt is collected + */ +static enum wcd9xxx_mbhc_plug_type +wcd9xxx_find_plug_type(struct wcd9xxx_mbhc *mbhc, + struct wcd9xxx_mbhc_detect *dt, const int size, + unsigned long event_state) +{ + int i; + int ch; + enum wcd9xxx_mbhc_plug_type type; + int vdce; + struct wcd9xxx_mbhc_detect *d, *dprev, *dgnd = NULL, *dvddio = NULL; + int maxv = 0, minv = 0; + const struct wcd9xxx_mbhc_plug_type_cfg *plug_type = + WCD9XXX_MBHC_CAL_PLUG_TYPE_PTR(mbhc->mbhc_cfg->calibration); + const s16 hs_max = plug_type->v_hs_max; + const s16 no_mic = plug_type->v_no_mic; + + pr_debug("%s: event_state 0x%lx\n", __func__, event_state); + + for (i = 0, d = dt, ch = 0; i < size; i++, d++) { + vdce = wcd9xxx_codec_sta_dce_v(mbhc, true, d->dce); + if (d->vddio) + d->_vdces = scale_v_micb_vddio(mbhc, vdce, false); + else + d->_vdces = vdce; + + if (d->_vdces >= no_mic && d->_vdces < hs_max) + d->_type = PLUG_TYPE_HEADSET; + else if (d->_vdces < no_mic) + d->_type = PLUG_TYPE_HEADPHONE; + else + d->_type = PLUG_TYPE_HIGH_HPH; + + ch += d->hphl_status & 0x01; + if (!d->swap_gnd && !d->hwvalue && !d->vddio) { + if (maxv < d->_vdces) + maxv = d->_vdces; + if (!minv || minv > d->_vdces) + minv = d->_vdces; + } + + pr_debug("%s: DCE #%d, %04x, V %04d(%04d), GND %d, VDDIO %d, HPHL %d TYPE %d\n", + __func__, i, d->dce, vdce, d->_vdces, + d->swap_gnd, d->vddio, d->hphl_status & 0x01, + d->_type); + + + /* + * If GND and MIC prongs are aligned to HPHR and GND of + * headphone, codec measures the voltage based on + * impedance between HPHR and GND which results in ~80mv. + * Avoid this. + */ + if (d->_vdces >= WCD9XXX_MEAS_INVALD_RANGE_LOW_MV && + d->_vdces <= WCD9XXX_MEAS_INVALD_RANGE_HIGH_MV) { + pr_debug("%s: within invalid range\n", __func__); + type = PLUG_TYPE_INVALID; + goto exit; + } + } + + if (event_state & (1 << MBHC_EVENT_PA_HPHL)) { + pr_debug("%s: HPHL PA was ON\n", __func__); + } else if (ch != size && ch > 0) { + pr_debug("%s: Invalid, inconsistent HPHL\n", __func__); + type = PLUG_TYPE_INVALID; + goto exit; + } + + for (i = 0, dprev = NULL, d = dt; i < size; i++, d++) { + if (d->vddio) { + dvddio = d; + continue; + } + + if ((i > 0) && (dprev != NULL) && (d->_type != dprev->_type)) { + pr_debug("%s: Invalid, inconsistent types\n", __func__); + type = PLUG_TYPE_INVALID; + goto exit; + } + + if (!d->swap_gnd && !d->hwvalue && + (abs(minv - d->_vdces) > WCD9XXX_MEAS_DELTA_MAX_MV || + abs(maxv - d->_vdces) > WCD9XXX_MEAS_DELTA_MAX_MV)) { + pr_debug("%s: Invalid, delta %dmv, %dmv and %dmv\n", + __func__, d->_vdces, minv, maxv); + type = PLUG_TYPE_INVALID; + goto exit; + } else if (d->swap_gnd) { + dgnd = d; + } + dprev = d; + } + + WARN_ON(i != size); + type = dt->_type; + if (type == PLUG_TYPE_HEADSET && dgnd) { + if ((dgnd->_vdces + WCD9XXX_GM_SWAP_THRES_MIN_MV < + minv) && + (dgnd->_vdces + WCD9XXX_GM_SWAP_THRES_MAX_MV > + maxv)) + type = PLUG_TYPE_GND_MIC_SWAP; + } + + /* if HPHL PA was on, we cannot use hphl status */ + if (!(event_state & (1UL << MBHC_EVENT_PA_HPHL))) { + if (((type == PLUG_TYPE_HEADSET || + type == PLUG_TYPE_HEADPHONE) && ch != size) || + (type == PLUG_TYPE_GND_MIC_SWAP && ch)) { + pr_debug("%s: Invalid, not fully inserted, TYPE %d\n", + __func__, type); + type = PLUG_TYPE_INVALID; + } + } + + if (type == PLUG_TYPE_HEADSET) { + if (dvddio && ((dvddio->_vdces > hs_max) || + (dvddio->_vdces > minv + WCD9XXX_THRESHOLD_MIC_THRESHOLD))) { + pr_debug("%s: Headset with threshold on MIC detected\n", + __func__); + if (mbhc->mbhc_cfg->micbias_enable_flags & + (1 << MBHC_MICBIAS_ENABLE_THRESHOLD_HEADSET)) + mbhc->micbias_enable = true; + } else { + pr_debug("%s: Headset with regular MIC detected\n", + __func__); + if (mbhc->mbhc_cfg->micbias_enable_flags & + (1 << MBHC_MICBIAS_ENABLE_REGULAR_HEADSET)) + mbhc->micbias_enable = true; + } + } +exit: + pr_debug("%s: Plug type %d detected, micbias_enable %d\n", __func__, + type, mbhc->micbias_enable); + return type; +} + +/* + * Pull down MBHC micbias for provided duration in microsecond. + */ +static int wcd9xxx_pull_down_micbias(struct wcd9xxx_mbhc *mbhc, int us) +{ + bool micbiasconn = false; + struct snd_soc_codec *codec = mbhc->codec; + const u16 ctlreg = mbhc->mbhc_bias_regs.ctl_reg; + + /* + * Disable MBHC to micbias connection to pull down + * micbias and pull down micbias for a moment. + */ + if ((snd_soc_read(mbhc->codec, ctlreg) & 0x01)) { + WARN_ONCE(1, "MBHC micbias is already pulled down unexpectedly\n"); + return -EFAULT; + } + + if ((snd_soc_read(mbhc->codec, WCD9XXX_A_MAD_ANA_CTRL) & 1 << 4)) { + snd_soc_update_bits(mbhc->codec, WCD9XXX_A_MAD_ANA_CTRL, + 1 << 4, 0); + micbiasconn = true; + } + + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.ctl_reg, 0x01, 0x01); + + /* + * Pull down for 1ms to discharge bias. Give small margin (10us) to be + * able to get consistent result across DCEs. + */ + usleep_range(1000, 1000 + 10); + + if (micbiasconn) + snd_soc_update_bits(mbhc->codec, WCD9XXX_A_MAD_ANA_CTRL, + 1 << 4, 1 << 4); + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.ctl_reg, 0x01, 0x00); + usleep_range(us, us + WCD9XXX_USLEEP_RANGE_MARGIN_US); + + return 0; +} + +/* Called under codec resource lock acquisition */ +void wcd9xxx_turn_onoff_current_source(struct wcd9xxx_mbhc *mbhc, + struct mbhc_micbias_regs *mbhc_micb_regs, + bool on, bool highhph) +{ + struct snd_soc_codec *codec; + struct wcd9xxx_mbhc_btn_detect_cfg *btn_det; + const struct wcd9xxx_mbhc_plug_detect_cfg *plug_det = + WCD9XXX_MBHC_CAL_PLUG_DET_PTR(mbhc->mbhc_cfg->calibration); + + btn_det = WCD9XXX_MBHC_CAL_BTN_DET_PTR(mbhc->mbhc_cfg->calibration); + codec = mbhc->codec; + + WCD9XXX_BCL_ASSERT_LOCKED(mbhc->resmgr); + + if ((on && mbhc->is_cs_enabled) || + (!on && !mbhc->is_cs_enabled)) { + pr_debug("%s: Current source is already %s\n", + __func__, on ? "ON" : "OFF"); + return; + } + + if (on) { + pr_debug("%s: enabling current source\n", __func__); + /* Nsc to 9 */ + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_B1_CTL, + 0x78, 0x48); + /* pull down diode bit to 0 */ + snd_soc_update_bits(codec, mbhc_micb_regs->mbhc_reg, + 0x01, 0x00); + /* + * Keep the low power insertion/removal + * detection (reg 0x3DD) disabled + */ + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_INT_CTL, + 0x01, 0x00); + /* + * Enable the Mic Bias current source + * Write bits[6:5] of register MICB_2_MBHC to 0x3 (V_20_UA) + * Write bit[7] of register MICB_2_MBHC to 1 + * (INS_DET_ISRC_EN__ENABLE) + * MICB_2_MBHC__SCHT_TRIG_EN to 1 + */ + snd_soc_update_bits(codec, mbhc_micb_regs->mbhc_reg, + 0xF0, 0xF0); + /* Disconnect MBHC Override from MicBias and LDOH */ + snd_soc_update_bits(codec, WCD9XXX_A_MAD_ANA_CTRL, 0x10, 0x00); + mbhc->is_cs_enabled = true; + } else { + pr_debug("%s: disabling current source\n", __func__); + /* Connect MBHC Override from MicBias and LDOH */ + snd_soc_update_bits(codec, WCD9XXX_A_MAD_ANA_CTRL, 0x10, 0x10); + /* INS_DET_ISRC_CTL to acdb value */ + snd_soc_update_bits(codec, mbhc_micb_regs->mbhc_reg, + 0x60, plug_det->mic_current << 5); + if (!highhph) { + /* INS_DET_ISRC_EN__ENABLE to 0 */ + snd_soc_update_bits(codec, + mbhc_micb_regs->mbhc_reg, + 0x80, 0x00); + /* MICB_2_MBHC__SCHT_TRIG_EN to 0 */ + snd_soc_update_bits(codec, + mbhc_micb_regs->mbhc_reg, + 0x10, 0x00); + } + /* Nsc to acdb value */ + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_B1_CTL, 0x78, + btn_det->mbhc_nsc << 3); + mbhc->is_cs_enabled = false; + } +} + +static enum wcd9xxx_mbhc_plug_type +wcd9xxx_codec_cs_get_plug_type(struct wcd9xxx_mbhc *mbhc, bool highhph) +{ + struct snd_soc_codec *codec = mbhc->codec; + struct wcd9xxx_mbhc_detect rt[NUM_DCE_PLUG_INS_DETECT]; + enum wcd9xxx_mbhc_plug_type type = PLUG_TYPE_INVALID; + int i; + + pr_debug("%s: enter\n", __func__); + WCD9XXX_BCL_ASSERT_LOCKED(mbhc->resmgr); + + BUG_ON(NUM_DCE_PLUG_INS_DETECT < 4); + + wcd9xxx_mbhc_ctrl_clk_bandgap(mbhc, true); + rt[0].swap_gnd = false; + rt[0].vddio = false; + rt[0].hwvalue = true; + rt[0].hphl_status = wcd9xxx_hphl_status(mbhc); + rt[0].dce = wcd9xxx_mbhc_setup_hs_polling(mbhc, &mbhc->mbhc_bias_regs, + true); + rt[0].mic_bias = false; + + for (i = 1; i < NUM_DCE_PLUG_INS_DETECT - 1; i++) { + rt[i].swap_gnd = (i == NUM_DCE_PLUG_INS_DETECT - 3); + rt[i].mic_bias = ((i == NUM_DCE_PLUG_INS_DETECT - 4) && + highhph); + rt[i].hphl_status = wcd9xxx_hphl_status(mbhc); + if (rt[i].swap_gnd) + wcd9xxx_codec_hphr_gnd_switch(codec, true); + + if (rt[i].mic_bias) + wcd9xxx_turn_onoff_current_source(mbhc, + &mbhc->mbhc_bias_regs, + false, false); + + rt[i].dce = __wcd9xxx_codec_sta_dce(mbhc, 1, !highhph, true); + if (rt[i].mic_bias) + wcd9xxx_turn_onoff_current_source(mbhc, + &mbhc->mbhc_bias_regs, + true, false); + if (rt[i].swap_gnd) + wcd9xxx_codec_hphr_gnd_switch(codec, false); + } + + /* recalibrate DCE/STA GND voltages */ + wcd9xxx_recalibrate(mbhc, &mbhc->mbhc_bias_regs, true); + + type = wcd9xxx_cs_find_plug_type(mbhc, rt, ARRAY_SIZE(rt), highhph, + mbhc->event_state); + + wcd9xxx_mbhc_ctrl_clk_bandgap(mbhc, false); + pr_debug("%s: plug_type:%d\n", __func__, type); + + return type; +} + +static enum wcd9xxx_mbhc_plug_type +wcd9xxx_codec_get_plug_type(struct wcd9xxx_mbhc *mbhc, bool highhph) +{ + int i; + bool vddioon; + struct wcd9xxx_mbhc_plug_type_cfg *plug_type_ptr; + struct wcd9xxx_mbhc_detect rt[NUM_DCE_PLUG_INS_DETECT]; + enum wcd9xxx_mbhc_plug_type type = PLUG_TYPE_INVALID; + struct snd_soc_codec *codec = mbhc->codec; + + pr_debug("%s: enter\n", __func__); + WCD9XXX_BCL_ASSERT_LOCKED(mbhc->resmgr); + + /* make sure override is on */ + WARN_ON(!(snd_soc_read(codec, WCD9XXX_A_CDC_MBHC_B1_CTL) & 0x04)); + + /* GND and MIC swap detection requires at least 2 rounds of DCE */ + BUG_ON(NUM_DCE_PLUG_INS_DETECT < 2); + detect_use_vddio_switch = mbhc->mbhc_cfg->use_vddio_meas; + + /* + * There are chances vddio switch is on and cfilt voltage is adjusted + * to vddio voltage even after plug type removal reported. + */ + vddioon = __wcd9xxx_switch_micbias(mbhc, 0, false, false); + pr_debug("%s: vddio switch was %s\n", __func__, vddioon ? "on" : "off"); + + plug_type_ptr = + WCD9XXX_MBHC_CAL_PLUG_TYPE_PTR(mbhc->mbhc_cfg->calibration); + + /* + * cfilter in fast mode requires 1ms to charge up and down micbias + * fully. + */ + (void) wcd9xxx_pull_down_micbias(mbhc, + WCD9XXX_MICBIAS_PULLDOWN_SETTLE_US); + + wcd9xxx_mbhc_ctrl_clk_bandgap(mbhc, true); + rt[0].hphl_status = wcd9xxx_hphl_status(mbhc); + rt[0].dce = wcd9xxx_mbhc_setup_hs_polling(mbhc, &mbhc->mbhc_bias_regs, + false); + rt[0].swap_gnd = false; + rt[0].vddio = false; + rt[0].hwvalue = true; + for (i = 1; i < NUM_DCE_PLUG_INS_DETECT; i++) { + rt[i].swap_gnd = (i == NUM_DCE_PLUG_INS_DETECT - 2); + if (detect_use_vddio_switch) + rt[i].vddio = (i == 1); + else + rt[i].vddio = false; + rt[i].hphl_status = wcd9xxx_hphl_status(mbhc); + rt[i].hwvalue = false; + if (rt[i].swap_gnd) + wcd9xxx_codec_hphr_gnd_switch(codec, true); + if (rt[i].vddio) + wcd9xxx_onoff_vddio_switch(mbhc, true); + /* + * Pull down micbias to detect headset with mic which has + * threshold and to have more consistent voltage measurements. + * + * cfilter in fast mode requires 1ms to charge up and down + * micbias fully. + */ + (void) wcd9xxx_pull_down_micbias(mbhc, + WCD9XXX_MICBIAS_PULLDOWN_SETTLE_US); + rt[i].dce = __wcd9xxx_codec_sta_dce(mbhc, 1, true, true); + if (rt[i].vddio) + wcd9xxx_onoff_vddio_switch(mbhc, false); + if (rt[i].swap_gnd) + wcd9xxx_codec_hphr_gnd_switch(codec, false); + } + /* recalibrate DCE/STA GND voltages */ + wcd9xxx_recalibrate(mbhc, &mbhc->mbhc_bias_regs, false); + + if (vddioon) + __wcd9xxx_switch_micbias(mbhc, 1, false, false); + + type = wcd9xxx_find_plug_type(mbhc, rt, ARRAY_SIZE(rt), + mbhc->event_state); + + wcd9xxx_mbhc_ctrl_clk_bandgap(mbhc, false); + pr_debug("%s: leave\n", __func__); + return type; +} + +static bool wcd9xxx_swch_level_remove(struct wcd9xxx_mbhc *mbhc) +{ + if (mbhc->mbhc_cfg->gpio) + return (gpio_get_value_cansleep(mbhc->mbhc_cfg->gpio) != + mbhc->mbhc_cfg->gpio_level_insert); + else if (mbhc->mbhc_cfg->insert_detect) { + if (mbhc->mbhc_cb && mbhc->mbhc_cb->insert_rem_status) + return mbhc->mbhc_cb->insert_rem_status(mbhc->codec); + else + return snd_soc_read(mbhc->codec, + WCD9XXX_A_MBHC_INSERT_DET_STATUS) & + (1 << 2); + } else + WARN(1, "Invalid jack detection configuration\n"); + + return true; +} + +static bool is_clk_active(struct snd_soc_codec *codec) +{ + return !!(snd_soc_read(codec, WCD9XXX_A_CDC_CLK_MCLK_CTL) & 0x05); +} + +static int wcd9xxx_enable_hs_detect(struct wcd9xxx_mbhc *mbhc, + int insertion, int trigger, bool padac_off) +{ + struct snd_soc_codec *codec = mbhc->codec; + int central_bias_enabled = 0; + const struct wcd9xxx_mbhc_general_cfg *generic = + WCD9XXX_MBHC_CAL_GENERAL_PTR(mbhc->mbhc_cfg->calibration); + const struct wcd9xxx_mbhc_plug_detect_cfg *plug_det = + WCD9XXX_MBHC_CAL_PLUG_DET_PTR(mbhc->mbhc_cfg->calibration); + + pr_debug("%s: enter insertion(%d) trigger(0x%x)\n", + __func__, insertion, trigger); + + if (!mbhc->mbhc_cfg->calibration) { + pr_err("Error, no wcd9xxx calibration\n"); + return -EINVAL; + } + + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_INT_CTL, 0x1, 0); + + /* + * Make sure mic bias and Mic line schmitt trigger + * are turned OFF + */ + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.ctl_reg, 0x01, 0x01); + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.mbhc_reg, 0x90, 0x00); + + if (insertion) { + wcd9xxx_switch_micbias(mbhc, 0); + + /* DAPM can manipulate PA/DAC bits concurrently */ + if (padac_off == true) + wcd9xxx_set_and_turnoff_hph_padac(mbhc); + + if (trigger & MBHC_USE_HPHL_TRIGGER) { + /* Enable HPH Schmitt Trigger */ + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_HPH, 0x11, + 0x11); + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_HPH, 0x0C, + plug_det->hph_current << 2); + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_HPH, 0x02, + 0x02); + } + if (trigger & MBHC_USE_MB_TRIGGER) { + /* enable the mic line schmitt trigger */ + snd_soc_update_bits(codec, + mbhc->mbhc_bias_regs.mbhc_reg, + 0x60, plug_det->mic_current << 5); + snd_soc_update_bits(codec, + mbhc->mbhc_bias_regs.mbhc_reg, + 0x80, 0x80); + usleep_range(plug_det->t_mic_pid, plug_det->t_mic_pid + + WCD9XXX_USLEEP_RANGE_MARGIN_US); + snd_soc_update_bits(codec, + mbhc->mbhc_bias_regs.ctl_reg, 0x01, + 0x00); + snd_soc_update_bits(codec, + mbhc->mbhc_bias_regs.mbhc_reg, + 0x10, 0x10); + } + + /* setup for insetion detection */ + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_INT_CTL, 0x2, 0); + } else { + pr_debug("setup for removal detection\n"); + /* Make sure the HPH schmitt trigger is OFF */ + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_HPH, 0x12, 0x00); + + /* enable the mic line schmitt trigger */ + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.ctl_reg, + 0x01, 0x00); + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.mbhc_reg, 0x60, + plug_det->mic_current << 5); + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.mbhc_reg, + 0x80, 0x80); + usleep_range(plug_det->t_mic_pid, plug_det->t_mic_pid + + WCD9XXX_USLEEP_RANGE_MARGIN_US); + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.mbhc_reg, + 0x10, 0x10); + + /* Setup for low power removal detection */ + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_INT_CTL, 0x2, + 0x2); + } + + if (snd_soc_read(codec, WCD9XXX_A_CDC_MBHC_B1_CTL) & 0x4) { + /* called by interrupt */ + if (!is_clk_active(codec)) { + wcd9xxx_resmgr_enable_config_mode(mbhc->resmgr, 1); + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_B1_CTL, + 0x06, 0); + usleep_range(generic->t_shutdown_plug_rem, + generic->t_shutdown_plug_rem + + WCD9XXX_USLEEP_RANGE_MARGIN_US); + wcd9xxx_resmgr_enable_config_mode(mbhc->resmgr, 0); + } else + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_B1_CTL, + 0x06, 0); + } + + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.int_rbias, 0x80, 0); + + /* If central bandgap disabled */ + if (!(snd_soc_read(codec, WCD9XXX_A_PIN_CTL_OE1) & 1)) { + snd_soc_update_bits(codec, WCD9XXX_A_PIN_CTL_OE1, 0x3, 0x3); + usleep_range(generic->t_bg_fast_settle, + generic->t_bg_fast_settle + + WCD9XXX_USLEEP_RANGE_MARGIN_US); + central_bias_enabled = 1; + } + + /* If LDO_H disabled */ + if (snd_soc_read(codec, WCD9XXX_A_PIN_CTL_OE0) & 0x80) { + snd_soc_update_bits(codec, WCD9XXX_A_PIN_CTL_OE0, 0x10, 0); + snd_soc_update_bits(codec, WCD9XXX_A_PIN_CTL_OE0, 0x80, 0x80); + usleep_range(generic->t_ldoh, generic->t_ldoh + + WCD9XXX_USLEEP_RANGE_MARGIN_US); + snd_soc_update_bits(codec, WCD9XXX_A_PIN_CTL_OE0, 0x80, 0); + + if (central_bias_enabled) + snd_soc_update_bits(codec, WCD9XXX_A_PIN_CTL_OE1, 0x1, + 0); + } + + if (mbhc->resmgr->reg_addr && mbhc->resmgr->reg_addr->micb_4_mbhc) + snd_soc_update_bits(codec, mbhc->resmgr->reg_addr->micb_4_mbhc, + 0x3, mbhc->mbhc_cfg->micbias); + + wcd9xxx_enable_irq(mbhc->resmgr->core_res, mbhc->intr_ids->insertion); + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_INT_CTL, 0x1, 0x1); + pr_debug("%s: leave\n", __func__); + + return 0; +} + +/* + * Function to determine whether anc microphone is preset or not. + * Return true if anc microphone is detected or false if not detected. + */ +static bool wcd9xxx_detect_anc_plug_type(struct wcd9xxx_mbhc *mbhc) +{ + struct wcd9xxx_mbhc_detect rt[NUM_DCE_PLUG_INS_DETECT - 1]; + bool anc_mic_found = true; + int i, mb_mv; + const struct wcd9xxx_mbhc_plug_type_cfg *plug_type = + WCD9XXX_MBHC_CAL_PLUG_TYPE_PTR(mbhc->mbhc_cfg->calibration); + s16 hs_max, dce_z; + s16 no_mic; + bool override_en; + bool timedout; + unsigned long timeout, retry = 0; + enum wcd9xxx_mbhc_plug_type type; + bool cs_enable; + + if (mbhc->mbhc_cfg->anc_micbias != MBHC_MICBIAS3 && + mbhc->mbhc_cfg->anc_micbias != MBHC_MICBIAS2) + return false; + + pr_debug("%s: enter\n", __func__); + + override_en = (snd_soc_read(mbhc->codec, WCD9XXX_A_CDC_MBHC_B1_CTL) & + 0x04) ? true : false; + cs_enable = ((mbhc->mbhc_cfg->cs_enable_flags & + (1 << MBHC_CS_ENABLE_DET_ANC)) != 0) && + (!(snd_soc_read(mbhc->codec, + mbhc->mbhc_anc_bias_regs.ctl_reg) & 0x80)) && + (mbhc->mbhc_cfg->micbias != mbhc->mbhc_cfg->anc_micbias); + + if (cs_enable) { + wcd9xxx_turn_onoff_current_source(mbhc, + &mbhc->mbhc_anc_bias_regs, + true, false); + } else { + if (mbhc->mbhc_cfg->anc_micbias == MBHC_MICBIAS3) { + if (mbhc->micbias_enable_cb) + mbhc->micbias_enable_cb(mbhc->codec, true, + mbhc->mbhc_cfg->anc_micbias); + else + return false; + } else { + /* Enable override */ + if (!override_en) + wcd9xxx_turn_onoff_override(mbhc, true); + } + } + + if (!cs_enable) { + hs_max = plug_type->v_hs_max; + no_mic = plug_type->v_no_mic; + dce_z = mbhc->mbhc_data.dce_z; + mb_mv = mbhc->mbhc_data.micb_mv; + } else { + hs_max = WCD9XXX_V_CS_HS_MAX; + no_mic = WCD9XXX_V_CS_NO_MIC; + mb_mv = VDDIO_MICBIAS_MV; + dce_z = mbhc->mbhc_data.dce_nsc_cs_z; + } + + wcd9xxx_mbhc_ctrl_clk_bandgap(mbhc, true); + + timeout = jiffies + msecs_to_jiffies(ANC_HPH_DETECT_PLUG_TIME_MS); + anc_mic_found = true; + + while (!(timedout = time_after(jiffies, timeout))) { + retry++; + + if (wcd9xxx_swch_level_remove(mbhc)) { + pr_debug("%s: Switch level is low\n", __func__); + anc_mic_found = false; + break; + } + + pr_debug("%s: Retry attempt %lu", __func__, retry - 1); + + rt[0].hphl_status = wcd9xxx_hphl_status(mbhc); + rt[0].dce = wcd9xxx_mbhc_setup_hs_polling(mbhc, + &mbhc->mbhc_anc_bias_regs, + cs_enable); + rt[0]._vdces = __wcd9xxx_codec_sta_dce_v(mbhc, true, rt[0].dce, + dce_z, (u32)mb_mv); + + if (rt[0]._vdces >= no_mic && rt[0]._vdces < hs_max) + rt[0]._type = PLUG_TYPE_HEADSET; + else if (rt[0]._vdces < no_mic) + rt[0]._type = PLUG_TYPE_HEADPHONE; + else + rt[0]._type = PLUG_TYPE_HIGH_HPH; + + pr_debug("%s: DCE #%d, V %04d, HPHL %d TYPE %d\n", + __func__, 0, rt[0]._vdces, + rt[0].hphl_status & 0x01, + rt[0]._type); + + for (i = 1; i < NUM_DCE_PLUG_INS_DETECT - 1; i++) { + rt[i].dce = __wcd9xxx_codec_sta_dce(mbhc, 1, + true, true); + rt[i]._vdces = __wcd9xxx_codec_sta_dce_v(mbhc, true, + rt[i].dce, dce_z, + (u32) mb_mv); + + if (rt[i]._vdces >= no_mic && rt[i]._vdces < hs_max) + rt[i]._type = PLUG_TYPE_HEADSET; + else if (rt[i]._vdces < no_mic) + rt[i]._type = PLUG_TYPE_HEADPHONE; + else + rt[i]._type = PLUG_TYPE_HIGH_HPH; + + rt[i].hphl_status = wcd9xxx_hphl_status(mbhc); + + pr_debug("%s: DCE #%d, V %04d, HPHL %d TYPE %d\n", + __func__, i, rt[i]._vdces, + rt[i].hphl_status & 0x01, + rt[i]._type); + } + + /* + * Check for the "type" of all the 4 measurements + * If all 4 measurements have the Type as PLUG_TYPE_HEADSET + * then it is proper mic and declare that the plug has two mics + */ + for (i = 0; i < NUM_DCE_PLUG_INS_DETECT - 1; i++) { + if (i > 0 && (rt[i - 1]._type != rt[i]._type)) { + type = PLUG_TYPE_INVALID; + break; + } else { + type = rt[0]._type; + } + } + + pr_debug("%s: Plug type found in ANC detection :%d", + __func__, type); + + if (type != PLUG_TYPE_HEADSET) + anc_mic_found = false; + if (anc_mic_found || (type == PLUG_TYPE_HEADPHONE && + mbhc->mbhc_cfg->hw_jack_type == FIVE_POLE_JACK) || + (type == PLUG_TYPE_HIGH_HPH && + mbhc->mbhc_cfg->hw_jack_type == SIX_POLE_JACK)) + break; + } + + wcd9xxx_mbhc_ctrl_clk_bandgap(mbhc, false); + if (cs_enable) { + wcd9xxx_turn_onoff_current_source(mbhc, + &mbhc->mbhc_anc_bias_regs, + false, false); + } else { + if (mbhc->mbhc_cfg->anc_micbias == MBHC_MICBIAS3) { + if (mbhc->micbias_enable_cb) + mbhc->micbias_enable_cb(mbhc->codec, false, + mbhc->mbhc_cfg->anc_micbias); + } else { + /* Disable override */ + if (!override_en) + wcd9xxx_turn_onoff_override(mbhc, false); + } + } + pr_debug("%s: leave\n", __func__); + return anc_mic_found; +} + +/* called under codec_resource_lock acquisition */ +static void wcd9xxx_find_plug_and_report(struct wcd9xxx_mbhc *mbhc, + enum wcd9xxx_mbhc_plug_type plug_type) +{ + bool anc_mic_found = false; + + pr_debug("%s: enter current_plug(%d) new_plug(%d)\n", + __func__, mbhc->current_plug, plug_type); + + WCD9XXX_BCL_ASSERT_LOCKED(mbhc->resmgr); + + if (plug_type == PLUG_TYPE_HEADPHONE && + mbhc->current_plug == PLUG_TYPE_NONE) { + /* + * Nothing was reported previously + * report a headphone or unsupported + */ + wcd9xxx_report_plug(mbhc, 1, SND_JACK_HEADPHONE); + wcd9xxx_cleanup_hs_polling(mbhc); + } else if (plug_type == PLUG_TYPE_GND_MIC_SWAP) { + if (!mbhc->mbhc_cfg->detect_extn_cable) { + if (mbhc->current_plug == PLUG_TYPE_HEADSET) + wcd9xxx_report_plug(mbhc, 0, + SND_JACK_HEADSET); + else if (mbhc->current_plug == PLUG_TYPE_HEADPHONE) + wcd9xxx_report_plug(mbhc, 0, + SND_JACK_HEADPHONE); + } + wcd9xxx_report_plug(mbhc, 1, SND_JACK_UNSUPPORTED); + wcd9xxx_cleanup_hs_polling(mbhc); + } else if (plug_type == PLUG_TYPE_HEADSET) { + + if (mbhc->mbhc_cfg->enable_anc_mic_detect) { + /* + * Do not report Headset, because at this point + * it could be a ANC headphone having two mics. + * So, proceed further to detect if there is a + * second mic. + */ + mbhc->scaling_mux_in = 0x08; + anc_mic_found = wcd9xxx_detect_anc_plug_type(mbhc); + } + + if (anc_mic_found) { + /* Report ANC headphone */ + wcd9xxx_report_plug(mbhc, 1, SND_JACK_ANC_HEADPHONE); + } else { + /* + * If Headphone was reported previously, this will + * only report the mic line + */ + wcd9xxx_report_plug(mbhc, 1, SND_JACK_HEADSET); + } + /* Button detection required RC oscillator */ + wcd9xxx_mbhc_ctrl_clk_bandgap(mbhc, true); + /* + * sleep so that audio path completely tears down + * before report plug insertion to the user space + */ + msleep(100); + + wcd9xxx_start_hs_polling(mbhc); + } else if (plug_type == PLUG_TYPE_HIGH_HPH) { + if (mbhc->mbhc_cfg->detect_extn_cable) { + /* High impedance device found. Report as LINEOUT*/ + if (mbhc->current_plug == PLUG_TYPE_NONE) + wcd9xxx_report_plug(mbhc, 1, SND_JACK_LINEOUT); + wcd9xxx_cleanup_hs_polling(mbhc); + pr_debug("%s: setup mic trigger for further detection\n", + __func__); + mbhc->lpi_enabled = true; + /* + * Do not enable HPHL trigger. If playback is active, + * it might lead to continuous false HPHL triggers + */ + wcd9xxx_enable_hs_detect(mbhc, 1, MBHC_USE_MB_TRIGGER, + false); + } else { + if (mbhc->current_plug == PLUG_TYPE_NONE) + wcd9xxx_report_plug(mbhc, 1, + SND_JACK_HEADPHONE); + wcd9xxx_cleanup_hs_polling(mbhc); + pr_debug("setup mic trigger for further detection\n"); + mbhc->lpi_enabled = true; + wcd9xxx_enable_hs_detect(mbhc, 1, MBHC_USE_MB_TRIGGER | + MBHC_USE_HPHL_TRIGGER, + false); + } + } else { + WARN(1, "Unexpected current plug_type %d, plug_type %d\n", + mbhc->current_plug, plug_type); + } + pr_debug("%s: leave\n", __func__); +} + +/* called under codec_resource_lock acquisition */ +static void wcd9xxx_mbhc_decide_swch_plug(struct wcd9xxx_mbhc *mbhc) +{ + enum wcd9xxx_mbhc_plug_type plug_type; + bool current_source_enable; + + pr_debug("%s: enter\n", __func__); + + WCD9XXX_BCL_ASSERT_LOCKED(mbhc->resmgr); + + current_source_enable = (((mbhc->mbhc_cfg->cs_enable_flags & + (1 << MBHC_CS_ENABLE_INSERTION)) != 0) && + (!(snd_soc_read(mbhc->codec, + mbhc->mbhc_bias_regs.ctl_reg) & 0x80))); + + mbhc->scaling_mux_in = 0x04; + + if (current_source_enable) { + wcd9xxx_turn_onoff_current_source(mbhc, &mbhc->mbhc_bias_regs, + true, false); + plug_type = wcd9xxx_codec_cs_get_plug_type(mbhc, false); + /* + * For other plug types, the current source disable + * will be done from wcd9xxx_correct_swch_plug + */ + if (plug_type == PLUG_TYPE_HEADSET) + wcd9xxx_turn_onoff_current_source(mbhc, + &mbhc->mbhc_bias_regs, + false, false); + } else { + wcd9xxx_turn_onoff_override(mbhc, true); + plug_type = wcd9xxx_codec_get_plug_type(mbhc, true); + wcd9xxx_turn_onoff_override(mbhc, false); + } + + if (wcd9xxx_swch_level_remove(mbhc)) { + if (current_source_enable && mbhc->is_cs_enabled) { + wcd9xxx_turn_onoff_current_source(mbhc, + &mbhc->mbhc_bias_regs, + false, false); + } + pr_debug("%s: Switch level is low when determining plug\n", + __func__); + return; + } + + if (plug_type == PLUG_TYPE_INVALID || + plug_type == PLUG_TYPE_GND_MIC_SWAP) { + wcd9xxx_cleanup_hs_polling(mbhc); + wcd9xxx_schedule_hs_detect_plug(mbhc, + &mbhc->correct_plug_swch); + } else if (plug_type == PLUG_TYPE_HEADPHONE) { + wcd9xxx_report_plug(mbhc, 1, SND_JACK_HEADPHONE); + wcd9xxx_cleanup_hs_polling(mbhc); + wcd9xxx_schedule_hs_detect_plug(mbhc, + &mbhc->correct_plug_swch); + } else if (plug_type == PLUG_TYPE_HIGH_HPH) { + wcd9xxx_cleanup_hs_polling(mbhc); + wcd9xxx_schedule_hs_detect_plug(mbhc, + &mbhc->correct_plug_swch); + } else { + pr_debug("%s: Valid plug found, determine plug type %d\n", + __func__, plug_type); + wcd9xxx_find_plug_and_report(mbhc, plug_type); + } + pr_debug("%s: leave\n", __func__); +} + +/* called under codec_resource_lock acquisition */ +static void wcd9xxx_mbhc_detect_plug_type(struct wcd9xxx_mbhc *mbhc) +{ + pr_debug("%s: enter\n", __func__); + WCD9XXX_BCL_ASSERT_LOCKED(mbhc->resmgr); + + if (wcd9xxx_swch_level_remove(mbhc)) + pr_debug("%s: Switch level low when determining plug\n", + __func__); + else + wcd9xxx_mbhc_decide_swch_plug(mbhc); + pr_debug("%s: leave\n", __func__); +} + +/* called only from interrupt which is under codec_resource_lock acquisition */ +static void wcd9xxx_hs_insert_irq_swch(struct wcd9xxx_mbhc *mbhc, + bool is_removal) +{ + if (!is_removal) { + pr_debug("%s: MIC trigger insertion interrupt\n", __func__); + + rmb(); + if (mbhc->lpi_enabled) + msleep(100); + + rmb(); + if (!mbhc->lpi_enabled) { + pr_debug("%s: lpi is disabled\n", __func__); + } else if (!wcd9xxx_swch_level_remove(mbhc)) { + pr_debug("%s: Valid insertion, detect plug type\n", + __func__); + wcd9xxx_mbhc_decide_swch_plug(mbhc); + } else { + pr_debug("%s: Invalid insertion stop plug detection\n", + __func__); + } + } else if (mbhc->mbhc_cfg->detect_extn_cable) { + pr_debug("%s: Removal\n", __func__); + if (!wcd9xxx_swch_level_remove(mbhc)) { + /* + * Switch indicates, something is still inserted. + * This could be extension cable i.e. headset is + * removed from extension cable. + */ + /* cancel detect plug */ + wcd9xxx_cancel_hs_detect_plug(mbhc, + &mbhc->correct_plug_swch); + wcd9xxx_mbhc_decide_swch_plug(mbhc); + } + } else { + pr_err("%s: Switch IRQ used, invalid MBHC Removal\n", __func__); + } +} + +static bool is_valid_mic_voltage(struct wcd9xxx_mbhc *mbhc, s32 mic_mv, + bool cs_enable) +{ + const struct wcd9xxx_mbhc_plug_type_cfg *plug_type = + WCD9XXX_MBHC_CAL_PLUG_TYPE_PTR(mbhc->mbhc_cfg->calibration); + const s16 v_hs_max = wcd9xxx_get_current_v_hs_max(mbhc); + + if (cs_enable) + return ((mic_mv > WCD9XXX_V_CS_NO_MIC) && + (mic_mv < WCD9XXX_V_CS_HS_MAX)) ? true : false; + else + return (!(mic_mv > WCD9XXX_MEAS_INVALD_RANGE_LOW_MV && + mic_mv < WCD9XXX_MEAS_INVALD_RANGE_HIGH_MV) && + (mic_mv > plug_type->v_no_mic) && + (mic_mv < v_hs_max)) ? true : false; +} + +/* + * called under codec_resource_lock acquisition + * returns true if mic voltage range is back to normal insertion + * returns false either if timedout or removed + */ +static bool wcd9xxx_hs_remove_settle(struct wcd9xxx_mbhc *mbhc) +{ + int i; + bool timedout, settled = false; + s32 mic_mv[NUM_DCE_PLUG_DETECT]; + short mb_v[NUM_DCE_PLUG_DETECT]; + unsigned long retry = 0, timeout; + bool cs_enable; + + cs_enable = (((mbhc->mbhc_cfg->cs_enable_flags & + (1 << MBHC_CS_ENABLE_REMOVAL)) != 0) && + (!(snd_soc_read(mbhc->codec, + mbhc->mbhc_bias_regs.ctl_reg) & 0x80))); + if (cs_enable) + wcd9xxx_turn_onoff_current_source(mbhc, &mbhc->mbhc_bias_regs, + true, false); + + timeout = jiffies + msecs_to_jiffies(HS_DETECT_PLUG_TIME_MS); + while (!(timedout = time_after(jiffies, timeout))) { + retry++; + if (wcd9xxx_swch_level_remove(mbhc)) { + pr_debug("%s: Switch indicates removal\n", __func__); + break; + } + + if (retry > 1) + msleep(250); + else + msleep(50); + + if (wcd9xxx_swch_level_remove(mbhc)) { + pr_debug("%s: Switch indicates removal\n", __func__); + break; + } + + if (cs_enable) { + for (i = 0; i < NUM_DCE_PLUG_DETECT; i++) { + mb_v[i] = __wcd9xxx_codec_sta_dce(mbhc, 1, + true, true); + mic_mv[i] = __wcd9xxx_codec_sta_dce_v(mbhc, + true, + mb_v[i], + mbhc->mbhc_data.dce_nsc_cs_z, + (u32)VDDIO_MICBIAS_MV); + pr_debug("%s : DCE run %lu, mic_mv = %d(%x)\n", + __func__, retry, mic_mv[i], mb_v[i]); + } + } else { + for (i = 0; i < NUM_DCE_PLUG_DETECT; i++) { + mb_v[i] = wcd9xxx_codec_sta_dce(mbhc, 1, + true); + mic_mv[i] = wcd9xxx_codec_sta_dce_v(mbhc, 1, + mb_v[i]); + pr_debug("%s : DCE run %lu, mic_mv = %d(%x)\n", + __func__, retry, mic_mv[i], + mb_v[i]); + } + } + + if (wcd9xxx_swch_level_remove(mbhc)) { + pr_debug("%s: Switcn indicates removal\n", __func__); + break; + } + + if (mbhc->current_plug == PLUG_TYPE_NONE) { + pr_debug("%s : headset/headphone is removed\n", + __func__); + break; + } + + for (i = 0; i < NUM_DCE_PLUG_DETECT; i++) + if (!is_valid_mic_voltage(mbhc, mic_mv[i], cs_enable)) + break; + + if (i == NUM_DCE_PLUG_DETECT) { + pr_debug("%s: MIC voltage settled\n", __func__); + settled = true; + msleep(200); + break; + } + } + + if (cs_enable) + wcd9xxx_turn_onoff_current_source(mbhc, &mbhc->mbhc_bias_regs, + false, false); + + if (timedout) + pr_debug("%s: Microphone did not settle in %d seconds\n", + __func__, HS_DETECT_PLUG_TIME_MS); + return settled; +} + +/* called only from interrupt which is under codec_resource_lock acquisition */ +static void wcd9xxx_hs_remove_irq_swch(struct wcd9xxx_mbhc *mbhc) +{ + pr_debug("%s: enter\n", __func__); + if (wcd9xxx_hs_remove_settle(mbhc)) + wcd9xxx_start_hs_polling(mbhc); + pr_debug("%s: leave\n", __func__); +} + +/* called only from interrupt which is under codec_resource_lock acquisition */ +static void wcd9xxx_hs_remove_irq_noswch(struct wcd9xxx_mbhc *mbhc) +{ + s16 dce, dcez; + unsigned long timeout; + bool removed = true; + struct snd_soc_codec *codec = mbhc->codec; + const struct wcd9xxx_mbhc_general_cfg *generic = + WCD9XXX_MBHC_CAL_GENERAL_PTR(mbhc->mbhc_cfg->calibration); + bool cs_enable; + s16 cur_v_ins_h; + u32 mb_mv; + + pr_debug("%s: enter\n", __func__); + if (mbhc->current_plug != PLUG_TYPE_HEADSET && + mbhc->current_plug != PLUG_TYPE_ANC_HEADPHONE) { + pr_debug("%s(): Headset is not inserted, ignore removal\n", + __func__); + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, + 0x08, 0x08); + return; + } + + usleep_range(generic->t_shutdown_plug_rem, + generic->t_shutdown_plug_rem + + WCD9XXX_USLEEP_RANGE_MARGIN_US); + + /* If micbias is enabled, don't enable current source */ + cs_enable = (((mbhc->mbhc_cfg->cs_enable_flags & + (1 << MBHC_CS_ENABLE_REMOVAL)) != 0) && + (!(snd_soc_read(codec, + mbhc->mbhc_bias_regs.ctl_reg) & 0x80))); + if (cs_enable) + wcd9xxx_turn_onoff_current_source(mbhc, &mbhc->mbhc_bias_regs, + true, false); + + timeout = jiffies + msecs_to_jiffies(FAKE_REMOVAL_MIN_PERIOD_MS); + do { + if (cs_enable) { + dce = __wcd9xxx_codec_sta_dce(mbhc, 1, true, true); + dcez = mbhc->mbhc_data.dce_nsc_cs_z; + mb_mv = VDDIO_MICBIAS_MV; + } else { + dce = wcd9xxx_codec_sta_dce(mbhc, 1, true); + dcez = mbhc->mbhc_data.dce_z; + mb_mv = mbhc->mbhc_data.micb_mv; + } + + pr_debug("%s: DCE 0x%x,%d\n", __func__, dce, + __wcd9xxx_codec_sta_dce_v(mbhc, true, dce, + dcez, mb_mv)); + + cur_v_ins_h = cs_enable ? (s16) mbhc->mbhc_data.v_cs_ins_h : + (wcd9xxx_get_current_v(mbhc, + WCD9XXX_CURRENT_V_INS_H)); + + if (dce < cur_v_ins_h) { + removed = false; + break; + } + } while (!time_after(jiffies, timeout)); + pr_debug("%s: headset %sactually removed\n", __func__, + removed ? "" : "not "); + + if (cs_enable) + wcd9xxx_turn_onoff_current_source(mbhc, &mbhc->mbhc_bias_regs, + false, false); + + if (removed) { + if (mbhc->mbhc_cfg->detect_extn_cable) { + if (!wcd9xxx_swch_level_remove(mbhc)) { + /* + * extension cable is still plugged in + * report it as LINEOUT device + */ + if (mbhc->hph_status == SND_JACK_HEADSET) + wcd9xxx_mbhc_ctrl_clk_bandgap(mbhc, + false); + wcd9xxx_report_plug(mbhc, 1, SND_JACK_LINEOUT); + wcd9xxx_cleanup_hs_polling(mbhc); + wcd9xxx_enable_hs_detect(mbhc, 1, + MBHC_USE_MB_TRIGGER, + false); + } + } else { + /* Cancel possibly running hs_detect_work */ + wcd9xxx_cancel_hs_detect_plug(mbhc, + &mbhc->correct_plug_noswch); + /* + * If this removal is not false, first check the micbias + * switch status and switch it to LDOH if it is already + * switched to VDDIO. + */ + wcd9xxx_switch_micbias(mbhc, 0); + + wcd9xxx_report_plug(mbhc, 0, SND_JACK_HEADSET); + wcd9xxx_mbhc_ctrl_clk_bandgap(mbhc, false); + wcd9xxx_cleanup_hs_polling(mbhc); + wcd9xxx_enable_hs_detect(mbhc, 1, MBHC_USE_MB_TRIGGER | + MBHC_USE_HPHL_TRIGGER, + true); + } + } else { + wcd9xxx_start_hs_polling(mbhc); + } + pr_debug("%s: leave\n", __func__); +} + +/* called only from interrupt which is under codec_resource_lock acquisition */ +static void wcd9xxx_hs_insert_irq_extn(struct wcd9xxx_mbhc *mbhc, + bool is_mb_trigger) +{ + /* Cancel possibly running hs_detect_work */ + wcd9xxx_cancel_hs_detect_plug(mbhc, &mbhc->correct_plug_swch); + + if (is_mb_trigger) { + pr_debug("%s: Waiting for Headphone left trigger\n", __func__); + wcd9xxx_enable_hs_detect(mbhc, 1, MBHC_USE_HPHL_TRIGGER, false); + } else { + pr_debug("%s: HPHL trigger received, detecting plug type\n", + __func__); + wcd9xxx_mbhc_detect_plug_type(mbhc); + } +} + +static irqreturn_t wcd9xxx_hs_remove_irq(int irq, void *data) +{ + struct wcd9xxx_mbhc *mbhc = data; + + pr_debug("%s: enter, removal interrupt\n", __func__); + WCD9XXX_BCL_LOCK(mbhc->resmgr); + /* + * While we don't know whether MIC is there or not, let the resmgr know + * so micbias can be disabled temporarily + */ + if (mbhc->current_plug == PLUG_TYPE_HEADSET) { + wcd9xxx_resmgr_cond_update_cond(mbhc->resmgr, + WCD9XXX_COND_HPH_MIC, false); + wcd9xxx_resmgr_cond_update_cond(mbhc->resmgr, + WCD9XXX_COND_HPH, false); + } else if (mbhc->current_plug == PLUG_TYPE_HEADPHONE) { + wcd9xxx_resmgr_cond_update_cond(mbhc->resmgr, + WCD9XXX_COND_HPH, false); + } + + if (mbhc->mbhc_cfg->detect_extn_cable && + !wcd9xxx_swch_level_remove(mbhc)) + wcd9xxx_hs_remove_irq_noswch(mbhc); + else + wcd9xxx_hs_remove_irq_swch(mbhc); + + if (mbhc->current_plug == PLUG_TYPE_HEADSET) { + wcd9xxx_resmgr_cond_update_cond(mbhc->resmgr, + WCD9XXX_COND_HPH, true); + wcd9xxx_resmgr_cond_update_cond(mbhc->resmgr, + WCD9XXX_COND_HPH_MIC, true); + } else if (mbhc->current_plug == PLUG_TYPE_HEADPHONE) { + wcd9xxx_resmgr_cond_update_cond(mbhc->resmgr, + WCD9XXX_COND_HPH, true); + } + WCD9XXX_BCL_UNLOCK(mbhc->resmgr); + + return IRQ_HANDLED; +} + +static irqreturn_t wcd9xxx_hs_insert_irq(int irq, void *data) +{ + bool is_mb_trigger, is_removal; + struct wcd9xxx_mbhc *mbhc = data; + struct snd_soc_codec *codec = mbhc->codec; + + pr_debug("%s: enter\n", __func__); + WCD9XXX_BCL_LOCK(mbhc->resmgr); + wcd9xxx_disable_irq(mbhc->resmgr->core_res, mbhc->intr_ids->insertion); + + is_mb_trigger = !!(snd_soc_read(codec, mbhc->mbhc_bias_regs.mbhc_reg) & + 0x10); + is_removal = !!(snd_soc_read(codec, WCD9XXX_A_CDC_MBHC_INT_CTL) & 0x02); + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_INT_CTL, 0x03, 0x00); + + /* Turn off both HPH and MIC line schmitt triggers */ + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.mbhc_reg, 0x90, 0x00); + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_HPH, 0x13, 0x00); + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.ctl_reg, 0x01, 0x00); + + if (mbhc->mbhc_cfg->detect_extn_cable && + mbhc->current_plug == PLUG_TYPE_HIGH_HPH) + wcd9xxx_hs_insert_irq_extn(mbhc, is_mb_trigger); + else + wcd9xxx_hs_insert_irq_swch(mbhc, is_removal); + + WCD9XXX_BCL_UNLOCK(mbhc->resmgr); + return IRQ_HANDLED; +} + +static void wcd9xxx_btn_lpress_fn(struct work_struct *work) +{ + struct delayed_work *dwork; + short bias_value; + int dce_mv, sta_mv; + struct wcd9xxx_mbhc *mbhc; + + pr_debug("%s:\n", __func__); + + dwork = to_delayed_work(work); + mbhc = container_of(dwork, struct wcd9xxx_mbhc, mbhc_btn_dwork); + + bias_value = wcd9xxx_read_sta_result(mbhc->codec); + sta_mv = wcd9xxx_codec_sta_dce_v(mbhc, 0, bias_value); + + bias_value = wcd9xxx_read_dce_result(mbhc->codec); + dce_mv = wcd9xxx_codec_sta_dce_v(mbhc, 1, bias_value); + pr_debug("%s: STA: %d, DCE: %d\n", __func__, sta_mv, dce_mv); + + pr_debug("%s: Reporting long button press event\n", __func__); + wcd9xxx_jack_report(mbhc, &mbhc->button_jack, mbhc->buttons_pressed, + mbhc->buttons_pressed); + + pr_debug("%s: leave\n", __func__); + wcd9xxx_unlock_sleep(mbhc->resmgr->core_res); +} + +static void wcd9xxx_mbhc_insert_work(struct work_struct *work) +{ + struct delayed_work *dwork; + struct wcd9xxx_mbhc *mbhc; + struct snd_soc_codec *codec; + struct wcd9xxx_core_resource *core_res; + + dwork = to_delayed_work(work); + mbhc = container_of(dwork, struct wcd9xxx_mbhc, mbhc_insert_dwork); + codec = mbhc->codec; + core_res = mbhc->resmgr->core_res; + + pr_debug("%s:\n", __func__); + + /* Turn off both HPH and MIC line schmitt triggers */ + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.mbhc_reg, 0x90, 0x00); + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_HPH, 0x13, 0x00); + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.ctl_reg, 0x01, 0x00); + wcd9xxx_disable_irq_sync(core_res, mbhc->intr_ids->insertion); + wcd9xxx_mbhc_detect_plug_type(mbhc); + wcd9xxx_unlock_sleep(core_res); +} + +static bool wcd9xxx_mbhc_fw_validate(const void *data, size_t size) +{ + u32 cfg_offset; + struct wcd9xxx_mbhc_imped_detect_cfg *imped_cfg; + struct wcd9xxx_mbhc_btn_detect_cfg *btn_cfg; + struct firmware_cal fw; + + fw.data = (void *)data; + fw.size = size; + + if (fw.size < WCD9XXX_MBHC_CAL_MIN_SIZE) + return false; + + /* + * Previous check guarantees that there is enough fw data up + * to num_btn + */ + btn_cfg = WCD9XXX_MBHC_CAL_BTN_DET_PTR(fw.data); + cfg_offset = (u32) ((void *) btn_cfg - (void *) fw.data); + if (fw.size < (cfg_offset + WCD9XXX_MBHC_CAL_BTN_SZ(btn_cfg))) + return false; + + /* + * Previous check guarantees that there is enough fw data up + * to start of impedance detection configuration + */ + imped_cfg = WCD9XXX_MBHC_CAL_IMPED_DET_PTR(fw.data); + cfg_offset = (u32) ((void *) imped_cfg - (void *) fw.data); + + if (fw.size < (cfg_offset + WCD9XXX_MBHC_CAL_IMPED_MIN_SZ)) + return false; + + if (fw.size < (cfg_offset + WCD9XXX_MBHC_CAL_IMPED_SZ(imped_cfg))) + return false; + + return true; +} + +static u16 wcd9xxx_codec_v_sta_dce(struct wcd9xxx_mbhc *mbhc, + enum meas_type dce, s16 vin_mv, + bool cs_enable) +{ + s16 diff, zero; + u32 mb_mv, in; + u16 value; + s16 dce_z; + + mb_mv = mbhc->mbhc_data.micb_mv; + dce_z = mbhc->mbhc_data.dce_z; + + if (mb_mv == 0) { + pr_err("%s: Mic Bias voltage is set to zero\n", __func__); + return -EINVAL; + } + if (cs_enable) { + mb_mv = VDDIO_MICBIAS_MV; + dce_z = mbhc->mbhc_data.dce_nsc_cs_z; + } + + if (dce) { + diff = (mbhc->mbhc_data.dce_mb) - (dce_z); + zero = (dce_z); + } else { + diff = (mbhc->mbhc_data.sta_mb) - (mbhc->mbhc_data.sta_z); + zero = (mbhc->mbhc_data.sta_z); + } + in = (u32) diff * vin_mv; + + value = (u16) (in / mb_mv) + zero; + return value; +} + +static void wcd9xxx_mbhc_calc_thres(struct wcd9xxx_mbhc *mbhc) +{ + struct snd_soc_codec *codec; + s16 adj_v_hs_max; + s16 btn_mv = 0, btn_mv_sta[MBHC_V_IDX_NUM], btn_mv_dce[MBHC_V_IDX_NUM]; + struct wcd9xxx_mbhc_btn_detect_cfg *btn_det; + struct wcd9xxx_mbhc_plug_type_cfg *plug_type; + u16 *btn_high; + int i; + + pr_debug("%s: enter\n", __func__); + codec = mbhc->codec; + btn_det = WCD9XXX_MBHC_CAL_BTN_DET_PTR(mbhc->mbhc_cfg->calibration); + plug_type = WCD9XXX_MBHC_CAL_PLUG_TYPE_PTR(mbhc->mbhc_cfg->calibration); + + mbhc->mbhc_data.v_ins_hu[MBHC_V_IDX_CFILT] = + wcd9xxx_codec_v_sta_dce(mbhc, STA, plug_type->v_hs_max, false); + mbhc->mbhc_data.v_ins_h[MBHC_V_IDX_CFILT] = + wcd9xxx_codec_v_sta_dce(mbhc, DCE, plug_type->v_hs_max, false); + + mbhc->mbhc_data.v_inval_ins_low = FAKE_INS_LOW; + mbhc->mbhc_data.v_inval_ins_high = FAKE_INS_HIGH; + + if (mbhc->mbhc_data.micb_mv != VDDIO_MICBIAS_MV) { + adj_v_hs_max = scale_v_micb_vddio(mbhc, plug_type->v_hs_max, + true); + mbhc->mbhc_data.v_ins_hu[MBHC_V_IDX_VDDIO] = + wcd9xxx_codec_v_sta_dce(mbhc, STA, adj_v_hs_max, false); + mbhc->mbhc_data.v_ins_h[MBHC_V_IDX_VDDIO] = + wcd9xxx_codec_v_sta_dce(mbhc, DCE, adj_v_hs_max, false); + mbhc->mbhc_data.v_inval_ins_low = + scale_v_micb_vddio(mbhc, mbhc->mbhc_data.v_inval_ins_low, + false); + mbhc->mbhc_data.v_inval_ins_high = + scale_v_micb_vddio(mbhc, mbhc->mbhc_data.v_inval_ins_high, + false); + } + mbhc->mbhc_data.v_cs_ins_h = wcd9xxx_codec_v_sta_dce(mbhc, DCE, + WCD9XXX_V_CS_HS_MAX, + true); + pr_debug("%s: v_ins_h for current source: 0x%x\n", __func__, + mbhc->mbhc_data.v_cs_ins_h); + + btn_high = wcd9xxx_mbhc_cal_btn_det_mp(btn_det, + MBHC_BTN_DET_V_BTN_HIGH); + for (i = 0; i < btn_det->num_btn; i++) + btn_mv = btn_high[i] > btn_mv ? btn_high[i] : btn_mv; + + btn_mv_sta[MBHC_V_IDX_CFILT] = btn_mv + btn_det->v_btn_press_delta_sta; + btn_mv_dce[MBHC_V_IDX_CFILT] = btn_mv + btn_det->v_btn_press_delta_cic; + btn_mv_sta[MBHC_V_IDX_VDDIO] = + scale_v_micb_vddio(mbhc, btn_mv_sta[MBHC_V_IDX_CFILT], true); + btn_mv_dce[MBHC_V_IDX_VDDIO] = + scale_v_micb_vddio(mbhc, btn_mv_dce[MBHC_V_IDX_CFILT], true); + + mbhc->mbhc_data.v_b1_hu[MBHC_V_IDX_CFILT] = + wcd9xxx_codec_v_sta_dce(mbhc, STA, btn_mv_sta[MBHC_V_IDX_CFILT], + false); + mbhc->mbhc_data.v_b1_h[MBHC_V_IDX_CFILT] = + wcd9xxx_codec_v_sta_dce(mbhc, DCE, btn_mv_dce[MBHC_V_IDX_CFILT], + false); + mbhc->mbhc_data.v_b1_hu[MBHC_V_IDX_VDDIO] = + wcd9xxx_codec_v_sta_dce(mbhc, STA, btn_mv_sta[MBHC_V_IDX_VDDIO], + false); + mbhc->mbhc_data.v_b1_h[MBHC_V_IDX_VDDIO] = + wcd9xxx_codec_v_sta_dce(mbhc, DCE, btn_mv_dce[MBHC_V_IDX_VDDIO], + false); + + mbhc->mbhc_data.v_brh[MBHC_V_IDX_CFILT] = + mbhc->mbhc_data.v_b1_h[MBHC_V_IDX_CFILT]; + mbhc->mbhc_data.v_brh[MBHC_V_IDX_VDDIO] = + mbhc->mbhc_data.v_b1_h[MBHC_V_IDX_VDDIO]; + + mbhc->mbhc_data.v_brl = BUTTON_MIN; + + mbhc->mbhc_data.v_no_mic = + wcd9xxx_codec_v_sta_dce(mbhc, STA, plug_type->v_no_mic, false); + pr_debug("%s: leave\n", __func__); +} + +static void wcd9xxx_onoff_ext_mclk(struct wcd9xxx_mbhc *mbhc, bool on) +{ + /* + * XXX: {codec}_mclk_enable holds WCD9XXX_BCL_LOCK, + * therefore wcd9xxx_onoff_ext_mclk caller SHOULDN'T hold + * WCD9XXX_BCL_LOCK when it calls wcd9xxx_onoff_ext_mclk() + */ + if (mbhc && mbhc->mbhc_cfg && mbhc->mbhc_cfg->mclk_cb_fn) + mbhc->mbhc_cfg->mclk_cb_fn(mbhc->codec, on, false); +} + +/* + * Mic Bias Enable Decision + * Return true if high_hph_cnt is a power of 2 (!= 2) + * otherwise return false + */ +static bool wcd9xxx_mbhc_enable_mb_decision(int high_hph_cnt) +{ + return (high_hph_cnt > 2) && !(high_hph_cnt & (high_hph_cnt - 1)); +} + +static inline void wcd9xxx_handle_gnd_mic_swap(struct wcd9xxx_mbhc *mbhc, + int pt_gnd_mic_swap_cnt, + enum wcd9xxx_mbhc_plug_type plug_type) +{ + if (mbhc->mbhc_cfg->swap_gnd_mic && + (pt_gnd_mic_swap_cnt == GND_MIC_SWAP_THRESHOLD)) { + /* + * if switch is toggled, check again, + * otherwise report unsupported plug + */ + mbhc->mbhc_cfg->swap_gnd_mic(mbhc->codec); + } else if (pt_gnd_mic_swap_cnt >= GND_MIC_SWAP_THRESHOLD) { + /* Report UNSUPPORTED plug + * and continue polling + */ + WCD9XXX_BCL_LOCK(mbhc->resmgr); + if (!mbhc->mbhc_cfg->detect_extn_cable) { + if (mbhc->current_plug == PLUG_TYPE_HEADPHONE) + wcd9xxx_report_plug(mbhc, 0, + SND_JACK_HEADPHONE); + else if (mbhc->current_plug == PLUG_TYPE_HEADSET) + wcd9xxx_report_plug(mbhc, 0, + SND_JACK_HEADSET); + } + if (mbhc->current_plug != plug_type) + wcd9xxx_report_plug(mbhc, 1, + SND_JACK_UNSUPPORTED); + WCD9XXX_BCL_UNLOCK(mbhc->resmgr); + } +} + +static void wcd9xxx_correct_swch_plug(struct work_struct *work) +{ + struct wcd9xxx_mbhc *mbhc; + struct snd_soc_codec *codec; + enum wcd9xxx_mbhc_plug_type plug_type = PLUG_TYPE_INVALID; + unsigned long timeout; + int retry = 0, pt_gnd_mic_swap_cnt = 0; + int highhph_cnt = 0; + bool correction = false; + bool current_source_enable; + bool wrk_complete = true, highhph = false; + + pr_debug("%s: enter\n", __func__); + + mbhc = container_of(work, struct wcd9xxx_mbhc, correct_plug_swch); + codec = mbhc->codec; + + current_source_enable = (((mbhc->mbhc_cfg->cs_enable_flags & + (1 << MBHC_CS_ENABLE_POLLING)) != 0) && + (!(snd_soc_read(codec, + mbhc->mbhc_bias_regs.ctl_reg) & 0x80))); + + wcd9xxx_onoff_ext_mclk(mbhc, true); + + /* + * Keep override on during entire plug type correction work. + * + * This is okay under the assumption that any switch irqs which use + * MBHC block cancel and sync this work so override is off again + * prior to switch interrupt handler's MBHC block usage. + * Also while this correction work is running, we can guarantee + * DAPM doesn't use any MBHC block as this work only runs with + * headphone detection. + */ + if (current_source_enable) { + WCD9XXX_BCL_LOCK(mbhc->resmgr); + wcd9xxx_turn_onoff_current_source(mbhc, &mbhc->mbhc_bias_regs, + true, false); + WCD9XXX_BCL_UNLOCK(mbhc->resmgr); + } else { + wcd9xxx_turn_onoff_override(mbhc, true); + } + + timeout = jiffies + msecs_to_jiffies(HS_DETECT_PLUG_TIME_MS); + while (!time_after(jiffies, timeout)) { + ++retry; + rmb(); + if (mbhc->hs_detect_work_stop) { + wrk_complete = false; + pr_debug("%s: stop requested\n", __func__); + break; + } + + msleep(HS_DETECT_PLUG_INERVAL_MS); + if (wcd9xxx_swch_level_remove(mbhc)) { + wrk_complete = false; + pr_debug("%s: Switch level is low\n", __func__); + break; + } + + /* can race with removal interrupt */ + WCD9XXX_BCL_LOCK(mbhc->resmgr); + if (current_source_enable) + plug_type = wcd9xxx_codec_cs_get_plug_type(mbhc, + highhph); + else + plug_type = wcd9xxx_codec_get_plug_type(mbhc, true); + WCD9XXX_BCL_UNLOCK(mbhc->resmgr); + + pr_debug("%s: attempt(%d) current_plug(%d) new_plug(%d)\n", + __func__, retry, mbhc->current_plug, plug_type); + + highhph_cnt = (plug_type == PLUG_TYPE_HIGH_HPH) ? + (highhph_cnt + 1) : + 0; + highhph = wcd9xxx_mbhc_enable_mb_decision(highhph_cnt); + if (plug_type == PLUG_TYPE_INVALID) { + pr_debug("Invalid plug in attempt # %d\n", retry); + if (!mbhc->mbhc_cfg->detect_extn_cable && + retry == NUM_ATTEMPTS_TO_REPORT && + mbhc->current_plug == PLUG_TYPE_NONE) { + WCD9XXX_BCL_LOCK(mbhc->resmgr); + wcd9xxx_report_plug(mbhc, 1, + SND_JACK_HEADPHONE); + WCD9XXX_BCL_UNLOCK(mbhc->resmgr); + } + } else if (plug_type == PLUG_TYPE_HEADPHONE) { + pr_debug("Good headphone detected, continue polling\n"); + WCD9XXX_BCL_LOCK(mbhc->resmgr); + if (mbhc->mbhc_cfg->detect_extn_cable) { + if (mbhc->current_plug != plug_type) + wcd9xxx_report_plug(mbhc, 1, + SND_JACK_HEADPHONE); + } else if (mbhc->current_plug == PLUG_TYPE_NONE) { + wcd9xxx_report_plug(mbhc, 1, + SND_JACK_HEADPHONE); + } + WCD9XXX_BCL_UNLOCK(mbhc->resmgr); + } else if (plug_type == PLUG_TYPE_HIGH_HPH) { + pr_debug("%s: High HPH detected, continue polling\n", + __func__); + WCD9XXX_BCL_LOCK(mbhc->resmgr); + if (mbhc->mbhc_cfg->detect_extn_cable) { + if (mbhc->current_plug != plug_type) + wcd9xxx_report_plug(mbhc, 1, + SND_JACK_LINEOUT); + } else if (mbhc->current_plug == PLUG_TYPE_NONE) { + wcd9xxx_report_plug(mbhc, 1, + SND_JACK_HEADPHONE); + } + WCD9XXX_BCL_UNLOCK(mbhc->resmgr); + } else { + if (plug_type == PLUG_TYPE_GND_MIC_SWAP) { + pt_gnd_mic_swap_cnt++; + if (pt_gnd_mic_swap_cnt >= + GND_MIC_SWAP_THRESHOLD) + wcd9xxx_handle_gnd_mic_swap(mbhc, + pt_gnd_mic_swap_cnt, + plug_type); + pr_debug("%s: unsupported HS detected, continue polling\n", + __func__); + continue; + } else { + pt_gnd_mic_swap_cnt = 0; + + WCD9XXX_BCL_LOCK(mbhc->resmgr); + /* Turn off override/current source */ + if (current_source_enable) + wcd9xxx_turn_onoff_current_source(mbhc, + &mbhc->mbhc_bias_regs, + false, false); + else + wcd9xxx_turn_onoff_override(mbhc, + false); + /* + * The valid plug also includes + * PLUG_TYPE_GND_MIC_SWAP + */ + wcd9xxx_find_plug_and_report(mbhc, plug_type); + WCD9XXX_BCL_UNLOCK(mbhc->resmgr); + pr_debug("Attempt %d found correct plug %d\n", + retry, + plug_type); + correction = true; + } + break; + } + } + + highhph = false; + if (wrk_complete && plug_type == PLUG_TYPE_HIGH_HPH) { + pr_debug("%s: polling is done, still HPH, so enabling MIC trigger\n", + __func__); + WCD9XXX_BCL_LOCK(mbhc->resmgr); + wcd9xxx_find_plug_and_report(mbhc, plug_type); + highhph = true; + WCD9XXX_BCL_UNLOCK(mbhc->resmgr); + } + + if (plug_type == PLUG_TYPE_HEADPHONE) { + if (mbhc->mbhc_cb && mbhc->mbhc_cb->hph_auto_pulldown_ctrl) + mbhc->mbhc_cb->hph_auto_pulldown_ctrl(codec, true); + } + + if (!correction && current_source_enable) { + WCD9XXX_BCL_LOCK(mbhc->resmgr); + wcd9xxx_turn_onoff_current_source(mbhc, &mbhc->mbhc_bias_regs, + false, highhph); + WCD9XXX_BCL_UNLOCK(mbhc->resmgr); + } else if (!correction) { + wcd9xxx_turn_onoff_override(mbhc, false); + } + + wcd9xxx_onoff_ext_mclk(mbhc, false); + + if (mbhc->mbhc_cfg->detect_extn_cable) { + WCD9XXX_BCL_LOCK(mbhc->resmgr); + if ((mbhc->current_plug == PLUG_TYPE_HEADPHONE && + wrk_complete) || + mbhc->current_plug == PLUG_TYPE_GND_MIC_SWAP || + mbhc->current_plug == PLUG_TYPE_INVALID || + (plug_type == PLUG_TYPE_INVALID && wrk_complete)) { + /* Enable removal detection */ + wcd9xxx_cleanup_hs_polling(mbhc); + wcd9xxx_enable_hs_detect(mbhc, 0, 0, false); + } + WCD9XXX_BCL_UNLOCK(mbhc->resmgr); + } + pr_debug("%s: leave current_plug(%d)\n", __func__, mbhc->current_plug); + /* unlock sleep */ + wcd9xxx_unlock_sleep(mbhc->resmgr->core_res); +} + +static void wcd9xxx_swch_irq_handler(struct wcd9xxx_mbhc *mbhc) +{ + bool insert; + bool is_removed = false; + struct snd_soc_codec *codec = mbhc->codec; + + pr_debug("%s: enter\n", __func__); + + mbhc->in_swch_irq_handler = true; + /* Wait here for debounce time */ + usleep_range(SWCH_IRQ_DEBOUNCE_TIME_US, SWCH_IRQ_DEBOUNCE_TIME_US + + WCD9XXX_USLEEP_RANGE_MARGIN_US); + + WCD9XXX_BCL_LOCK(mbhc->resmgr); + + /* cancel pending button press */ + if (wcd9xxx_cancel_btn_work(mbhc)) + pr_debug("%s: button press is canceled\n", __func__); + + insert = !wcd9xxx_swch_level_remove(mbhc); + pr_debug("%s: Current plug type %d, insert %d\n", __func__, + mbhc->current_plug, insert); + if ((mbhc->current_plug == PLUG_TYPE_NONE) && insert) { + + mbhc->lpi_enabled = false; + wmb(); + /* cancel detect plug */ + wcd9xxx_cancel_hs_detect_plug(mbhc, + &mbhc->correct_plug_swch); + + if ((mbhc->current_plug != PLUG_TYPE_NONE) && + (mbhc->current_plug != PLUG_TYPE_HIGH_HPH) && + !(snd_soc_read(codec, WCD9XXX_A_MBHC_INSERT_DETECT) & + (1 << 1))) { + pr_debug("%s: current plug: %d\n", __func__, + mbhc->current_plug); + goto exit; + } + + /* Disable Mic Bias pull down and HPH Switch to GND */ + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.ctl_reg, 0x01, + 0x00); + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_HPH, 0x01, 0x00); + wcd9xxx_mbhc_detect_plug_type(mbhc); + } else if ((mbhc->current_plug != PLUG_TYPE_NONE) && !insert) { + mbhc->lpi_enabled = false; + wmb(); + /* cancel detect plug */ + wcd9xxx_cancel_hs_detect_plug(mbhc, + &mbhc->correct_plug_swch); + + if (mbhc->current_plug == PLUG_TYPE_HEADPHONE) { + wcd9xxx_report_plug(mbhc, 0, SND_JACK_HEADPHONE); + is_removed = true; + } else if (mbhc->current_plug == PLUG_TYPE_GND_MIC_SWAP) { + wcd9xxx_report_plug(mbhc, 0, SND_JACK_UNSUPPORTED); + is_removed = true; + } else if (mbhc->current_plug == PLUG_TYPE_HEADSET) { + wcd9xxx_pause_hs_polling(mbhc); + wcd9xxx_mbhc_ctrl_clk_bandgap(mbhc, false); + wcd9xxx_cleanup_hs_polling(mbhc); + wcd9xxx_report_plug(mbhc, 0, SND_JACK_HEADSET); + is_removed = true; + } else if (mbhc->current_plug == PLUG_TYPE_HIGH_HPH) { + wcd9xxx_report_plug(mbhc, 0, SND_JACK_LINEOUT); + is_removed = true; + } else if (mbhc->current_plug == PLUG_TYPE_ANC_HEADPHONE) { + wcd9xxx_pause_hs_polling(mbhc); + wcd9xxx_mbhc_ctrl_clk_bandgap(mbhc, false); + wcd9xxx_cleanup_hs_polling(mbhc); + wcd9xxx_report_plug(mbhc, 0, SND_JACK_ANC_HEADPHONE); + is_removed = true; + } + + if (is_removed) { + snd_soc_write(codec, WCD9XXX_A_MBHC_SCALING_MUX_1, + 0x00); + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_B1_CTL, + 0x02, 0x00); + + /* Enable Mic Bias pull down and HPH Switch to GND */ + snd_soc_update_bits(codec, + mbhc->mbhc_bias_regs.ctl_reg, 0x01, + 0x01); + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_HPH, 0x01, + 0x01); + /* Make sure mic trigger is turned off */ + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.ctl_reg, + 0x01, 0x01); + snd_soc_update_bits(codec, + mbhc->mbhc_bias_regs.mbhc_reg, + 0x90, 0x00); + /* Reset MBHC State Machine */ + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, + 0x08, 0x08); + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, + 0x08, 0x00); + /* Turn off override */ + wcd9xxx_turn_onoff_override(mbhc, false); + } + } +exit: + mbhc->in_swch_irq_handler = false; + WCD9XXX_BCL_UNLOCK(mbhc->resmgr); + pr_debug("%s: leave\n", __func__); +} + +static irqreturn_t wcd9xxx_mech_plug_detect_irq(int irq, void *data) +{ + int r = IRQ_HANDLED; + struct wcd9xxx_mbhc *mbhc = data; + + pr_debug("%s: enter\n", __func__); + if (unlikely(wcd9xxx_lock_sleep(mbhc->resmgr->core_res) == false)) { + pr_warn("%s: failed to hold suspend\n", __func__); + r = IRQ_NONE; + } else { + /* Call handler */ + wcd9xxx_swch_irq_handler(mbhc); + wcd9xxx_unlock_sleep(mbhc->resmgr->core_res); + } + + pr_debug("%s: leave %d\n", __func__, r); + return r; +} + +static int wcd9xxx_is_false_press(struct wcd9xxx_mbhc *mbhc) +{ + s16 mb_v; + int i = 0; + int r = 0; + const s16 v_ins_hu = + wcd9xxx_get_current_v(mbhc, WCD9XXX_CURRENT_V_INS_HU); + const s16 v_ins_h = + wcd9xxx_get_current_v(mbhc, WCD9XXX_CURRENT_V_INS_H); + const s16 v_b1_hu = + wcd9xxx_get_current_v(mbhc, WCD9XXX_CURRENT_V_B1_HU); + const s16 v_b1_h = + wcd9xxx_get_current_v(mbhc, WCD9XXX_CURRENT_V_B1_H); + const unsigned long timeout = + jiffies + msecs_to_jiffies(BTN_RELEASE_DEBOUNCE_TIME_MS); + + while (time_before(jiffies, timeout)) { + /* + * This function needs to run measurements just few times during + * release debounce time. Make 1ms interval to avoid + * unnecessary excessive measurements. + */ + usleep_range(1000, 1000 + WCD9XXX_USLEEP_RANGE_MARGIN_US); + if (i == 0) { + mb_v = wcd9xxx_codec_sta_dce(mbhc, 0, true); + pr_debug("%s: STA[0]: %d,%d\n", __func__, mb_v, + wcd9xxx_codec_sta_dce_v(mbhc, 0, mb_v)); + if (mb_v < v_b1_hu || mb_v > v_ins_hu) { + r = 1; + break; + } + } else { + mb_v = wcd9xxx_codec_sta_dce(mbhc, 1, true); + pr_debug("%s: DCE[%d]: %d,%d\n", __func__, i, mb_v, + wcd9xxx_codec_sta_dce_v(mbhc, 1, mb_v)); + if (mb_v < v_b1_h || mb_v > v_ins_h) { + r = 1; + break; + } + } + i++; + } + + return r; +} + +/* called under codec_resource_lock acquisition */ +static int wcd9xxx_determine_button(const struct wcd9xxx_mbhc *mbhc, + const s32 micmv) +{ + s16 *v_btn_low, *v_btn_high; + struct wcd9xxx_mbhc_btn_detect_cfg *btn_det; + int i, btn = -1; + + btn_det = WCD9XXX_MBHC_CAL_BTN_DET_PTR(mbhc->mbhc_cfg->calibration); + v_btn_low = wcd9xxx_mbhc_cal_btn_det_mp(btn_det, + MBHC_BTN_DET_V_BTN_LOW); + v_btn_high = wcd9xxx_mbhc_cal_btn_det_mp(btn_det, + MBHC_BTN_DET_V_BTN_HIGH); + + for (i = 0; i < btn_det->num_btn; i++) { + if ((v_btn_low[i] <= micmv) && (v_btn_high[i] >= micmv)) { + btn = i; + break; + } + } + + if (btn == -1) + pr_debug("%s: couldn't find button number for mic mv %d\n", + __func__, micmv); + + return btn; +} + +static int wcd9xxx_get_button_mask(const int btn) +{ + int mask = 0; + 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; + } + return mask; +} + +static void wcd9xxx_get_z(struct wcd9xxx_mbhc *mbhc, s16 *dce_z, s16 *sta_z, + struct mbhc_micbias_regs *micb_regs, + bool norel_detection) +{ + s16 reg0, reg1; + int change; + struct snd_soc_codec *codec = mbhc->codec; + + WCD9XXX_BCL_ASSERT_LOCKED(mbhc->resmgr); + /* Pull down micbias to ground and disconnect vddio switch */ + reg0 = snd_soc_read(codec, micb_regs->ctl_reg); + snd_soc_update_bits(codec, micb_regs->ctl_reg, 0x81, 0x1); + reg1 = snd_soc_read(codec, micb_regs->mbhc_reg); + snd_soc_update_bits(codec, micb_regs->mbhc_reg, 1 << 7, 0); + + /* Disconnect override from micbias */ + change = snd_soc_update_bits(codec, WCD9XXX_A_MAD_ANA_CTRL, 1 << 4, + 1 << 0); + usleep_range(1000, 1000 + 1000); + if (sta_z) { + *sta_z = wcd9xxx_codec_sta_dce(mbhc, 0, norel_detection); + pr_debug("%s: sta_z 0x%x\n", __func__, *sta_z & 0xFFFF); + } + if (dce_z) { + *dce_z = wcd9xxx_codec_sta_dce(mbhc, 1, norel_detection); + pr_debug("%s: dce_z 0x%x\n", __func__, *dce_z & 0xFFFF); + } + + /* Connect override from micbias */ + if (change) + snd_soc_update_bits(codec, WCD9XXX_A_MAD_ANA_CTRL, 1 << 4, + 1 << 4); + /* Disable pull down micbias to ground */ + snd_soc_write(codec, micb_regs->mbhc_reg, reg1); + snd_soc_write(codec, micb_regs->ctl_reg, reg0); +} + +/* + * This function recalibrates dce_z and sta_z parameters. + * No release detection will be false when this function is + * used. + */ +void wcd9xxx_update_z(struct wcd9xxx_mbhc *mbhc) +{ + const u16 sta_z = mbhc->mbhc_data.sta_z; + const u16 dce_z = mbhc->mbhc_data.dce_z; + + wcd9xxx_get_z(mbhc, &mbhc->mbhc_data.dce_z, &mbhc->mbhc_data.sta_z, + &mbhc->mbhc_bias_regs, false); + pr_debug("%s: sta_z 0x%x,dce_z 0x%x -> sta_z 0x%x,dce_z 0x%x\n", + __func__, sta_z & 0xFFFF, dce_z & 0xFFFF, + mbhc->mbhc_data.sta_z & 0xFFFF, + mbhc->mbhc_data.dce_z & 0xFFFF); + + wcd9xxx_mbhc_calc_thres(mbhc); + wcd9xxx_calibrate_hs_polling(mbhc); +} + +/* + * wcd9xxx_update_rel_threshold : update mbhc release upper bound threshold + * to ceilmv + buffer + */ +static int wcd9xxx_update_rel_threshold(struct wcd9xxx_mbhc *mbhc, int ceilmv, + bool vddio) +{ + u16 v_brh, v_b1_hu; + int mv; + struct wcd9xxx_mbhc_btn_detect_cfg *btn_det; + void *calibration = mbhc->mbhc_cfg->calibration; + struct snd_soc_codec *codec = mbhc->codec; + + btn_det = WCD9XXX_MBHC_CAL_BTN_DET_PTR(calibration); + mv = ceilmv + btn_det->v_btn_press_delta_cic; + if (vddio) + mv = scale_v_micb_vddio(mbhc, mv, true); + pr_debug("%s: reprogram vb1hu/vbrh to %dmv\n", __func__, mv); + + if (mbhc->mbhc_state != MBHC_STATE_POTENTIAL_RECOVERY) { + /* + * update LSB first so mbhc hardware block + * doesn't see too low value. + */ + v_b1_hu = wcd9xxx_codec_v_sta_dce(mbhc, STA, mv, false); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B3_CTL, v_b1_hu & + 0xFF); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B4_CTL, + (v_b1_hu >> 8) & 0xFF); + v_brh = wcd9xxx_codec_v_sta_dce(mbhc, DCE, mv, false); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B9_CTL, v_brh & + 0xFF); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_VOLT_B10_CTL, + (v_brh >> 8) & 0xFF); + } + return 0; +} + +irqreturn_t wcd9xxx_dce_handler(int irq, void *data) +{ + int i, mask; + bool vddio; + u8 mbhc_status; + s16 dce_z, sta_z; + s32 stamv, stamv_s; + s16 *v_btn_high; + struct wcd9xxx_mbhc_btn_detect_cfg *btn_det; + int btn = -1, meas = 0; + struct wcd9xxx_mbhc *mbhc = data; + const struct wcd9xxx_mbhc_btn_detect_cfg *d = + WCD9XXX_MBHC_CAL_BTN_DET_PTR(mbhc->mbhc_cfg->calibration); + short btnmeas[d->n_btn_meas + 1]; + short dce[d->n_btn_meas + 1], sta; + s32 mv[d->n_btn_meas + 1], mv_s[d->n_btn_meas + 1]; + struct snd_soc_codec *codec = mbhc->codec; + struct wcd9xxx_core_resource *core_res = mbhc->resmgr->core_res; + int n_btn_meas = d->n_btn_meas; + void *calibration = mbhc->mbhc_cfg->calibration; + + pr_debug("%s: enter\n", __func__); + + WCD9XXX_BCL_LOCK(mbhc->resmgr); + mutex_lock(&mbhc->mbhc_lock); + mbhc_status = snd_soc_read(codec, WCD9XXX_A_CDC_MBHC_B1_STATUS) & 0x3E; + + if (mbhc->mbhc_state == MBHC_STATE_POTENTIAL_RECOVERY) { + pr_debug("%s: mbhc is being recovered, skip button press\n", + __func__); + goto done; + } + + mbhc->mbhc_state = MBHC_STATE_POTENTIAL; + + if (!mbhc->polling_active) { + pr_warn("%s: mbhc polling is not active, skip button press\n", + __func__); + goto done; + } + + /* If switch nterrupt already kicked in, ignore button press */ + if (mbhc->in_swch_irq_handler) { + pr_debug("%s: Swtich level changed, ignore button press\n", + __func__); + btn = -1; + goto done; + } + + /* + * setup internal micbias if codec uses internal micbias for + * headset detection + */ + if (mbhc->mbhc_cfg->use_int_rbias) { + if (mbhc->mbhc_cb && mbhc->mbhc_cb->setup_int_rbias) + mbhc->mbhc_cb->setup_int_rbias(codec, true); + else + pr_err("%s: internal bias requested but codec did not provide callback\n", + __func__); + } + + + /* Measure scaled HW DCE */ + vddio = (mbhc->mbhc_data.micb_mv != VDDIO_MICBIAS_MV && + mbhc->mbhc_micbias_switched); + + dce_z = mbhc->mbhc_data.dce_z; + sta_z = mbhc->mbhc_data.sta_z; + + /* Measure scaled HW STA */ + dce[0] = wcd9xxx_read_dce_result(codec); + sta = wcd9xxx_read_sta_result(codec); + if (mbhc_status != STATUS_REL_DETECTION) { + if (mbhc->mbhc_last_resume && + !time_after(jiffies, mbhc->mbhc_last_resume + HZ)) { + pr_debug("%s: Button is released after resume\n", + __func__); + n_btn_meas = 0; + } else { + pr_debug("%s: Button is released without resume", + __func__); + if (mbhc->update_z) { + wcd9xxx_update_z(mbhc); + dce_z = mbhc->mbhc_data.dce_z; + sta_z = mbhc->mbhc_data.sta_z; + mbhc->update_z = true; + } + stamv = __wcd9xxx_codec_sta_dce_v(mbhc, 0, sta, sta_z, + mbhc->mbhc_data.micb_mv); + if (vddio) + stamv_s = scale_v_micb_vddio(mbhc, stamv, + false); + else + stamv_s = stamv; + mv[0] = __wcd9xxx_codec_sta_dce_v(mbhc, 1, dce[0], + dce_z, mbhc->mbhc_data.micb_mv); + mv_s[0] = vddio ? scale_v_micb_vddio(mbhc, mv[0], + false) : mv[0]; + btn = wcd9xxx_determine_button(mbhc, mv_s[0]); + if (btn != wcd9xxx_determine_button(mbhc, stamv_s)) + btn = -1; + goto done; + } + } + + for (meas = 1; ((d->n_btn_meas) && (meas < (d->n_btn_meas + 1))); + meas++) + dce[meas] = wcd9xxx_codec_sta_dce(mbhc, 1, false); + + if (mbhc->update_z) { + wcd9xxx_update_z(mbhc); + dce_z = mbhc->mbhc_data.dce_z; + sta_z = mbhc->mbhc_data.sta_z; + mbhc->update_z = true; + } + + stamv = __wcd9xxx_codec_sta_dce_v(mbhc, 0, sta, sta_z, + mbhc->mbhc_data.micb_mv); + if (vddio) + stamv_s = scale_v_micb_vddio(mbhc, stamv, false); + else + stamv_s = stamv; + pr_debug("%s: Meas HW - STA 0x%x,%d,%d\n", __func__, + sta & 0xFFFF, stamv, stamv_s); + + /* determine pressed button */ + mv[0] = __wcd9xxx_codec_sta_dce_v(mbhc, 1, dce[0], dce_z, + mbhc->mbhc_data.micb_mv); + mv_s[0] = vddio ? scale_v_micb_vddio(mbhc, mv[0], false) : mv[0]; + btnmeas[0] = wcd9xxx_determine_button(mbhc, mv_s[0]); + pr_debug("%s: Meas HW - DCE 0x%x,%d,%d button %d\n", __func__, + dce[0] & 0xFFFF, mv[0], mv_s[0], btnmeas[0]); + if (n_btn_meas == 0) + btn = btnmeas[0]; + for (meas = 1; (n_btn_meas && d->n_btn_meas && + (meas < (d->n_btn_meas + 1))); meas++) { + mv[meas] = __wcd9xxx_codec_sta_dce_v(mbhc, 1, dce[meas], dce_z, + mbhc->mbhc_data.micb_mv); + mv_s[meas] = vddio ? scale_v_micb_vddio(mbhc, mv[meas], false) : + mv[meas]; + btnmeas[meas] = wcd9xxx_determine_button(mbhc, mv_s[meas]); + pr_debug("%s: Meas %d - DCE 0x%x,%d,%d button %d\n", + __func__, meas, dce[meas] & 0xFFFF, mv[meas], + mv_s[meas], btnmeas[meas]); + /* + * if large enough measurements are collected, + * start to check if last all n_btn_con measurements were + * in same button low/high range + */ + if (meas + 1 >= d->n_btn_con) { + for (i = 0; i < d->n_btn_con; i++) + if ((btnmeas[meas] < 0) || + (btnmeas[meas] != btnmeas[meas - i])) + break; + if (i == d->n_btn_con) { + /* button pressed */ + btn = btnmeas[meas]; + break; + } else if ((n_btn_meas - meas) < (d->n_btn_con - 1)) { + /* + * if left measurements are less than n_btn_con, + * it's impossible to find button number + */ + break; + } + } + } + + if (btn >= 0) { + if (mbhc->in_swch_irq_handler) { + pr_debug( + "%s: Switch irq triggered, ignore button press\n", + __func__); + goto done; + } + btn_det = WCD9XXX_MBHC_CAL_BTN_DET_PTR(calibration); + v_btn_high = wcd9xxx_mbhc_cal_btn_det_mp(btn_det, + MBHC_BTN_DET_V_BTN_HIGH); + WARN_ON(btn >= btn_det->num_btn); + /* reprogram release threshold to catch voltage ramp up early */ + wcd9xxx_update_rel_threshold(mbhc, v_btn_high[btn], vddio); + + mask = wcd9xxx_get_button_mask(btn); + mbhc->buttons_pressed |= mask; + wcd9xxx_lock_sleep(core_res); + if (schedule_delayed_work(&mbhc->mbhc_btn_dwork, + msecs_to_jiffies(400)) == 0) { + WARN(1, "Button pressed twice without release event\n"); + wcd9xxx_unlock_sleep(core_res); + } + } else { + pr_debug("%s: bogus button press, too short press?\n", + __func__); + } + + done: + pr_debug("%s: leave\n", __func__); + mutex_unlock(&mbhc->mbhc_lock); + WCD9XXX_BCL_UNLOCK(mbhc->resmgr); + return IRQ_HANDLED; +} + +static irqreturn_t wcd9xxx_release_handler(int irq, void *data) +{ + int ret; + bool waitdebounce = true; + struct wcd9xxx_mbhc *mbhc = data; + + pr_debug("%s: enter\n", __func__); + WCD9XXX_BCL_LOCK(mbhc->resmgr); + mbhc->mbhc_state = MBHC_STATE_RELEASE; + + if (mbhc->buttons_pressed & WCD9XXX_JACK_BUTTON_MASK) { + ret = wcd9xxx_cancel_btn_work(mbhc); + if (ret == 0) { + pr_debug("%s: Reporting long button release event\n", + __func__); + wcd9xxx_jack_report(mbhc, &mbhc->button_jack, 0, + mbhc->buttons_pressed); + } else { + if (wcd9xxx_is_false_press(mbhc)) { + pr_debug("%s: Fake button press interrupt\n", + __func__); + } else { + if (mbhc->in_swch_irq_handler) { + pr_debug("%s: Switch irq kicked in, ignore\n", + __func__); + } else { + pr_debug("%s: Reporting btn press\n", + __func__); + wcd9xxx_jack_report(mbhc, + &mbhc->button_jack, + mbhc->buttons_pressed, + mbhc->buttons_pressed); + pr_debug("%s: Reporting btn release\n", + __func__); + wcd9xxx_jack_report(mbhc, + &mbhc->button_jack, + 0, mbhc->buttons_pressed); + waitdebounce = false; + } + } + } + + mbhc->buttons_pressed &= ~WCD9XXX_JACK_BUTTON_MASK; + } + + wcd9xxx_calibrate_hs_polling(mbhc); + + if (waitdebounce) + msleep(SWCH_REL_DEBOUNCE_TIME_MS); + wcd9xxx_start_hs_polling(mbhc); + + pr_debug("%s: leave\n", __func__); + WCD9XXX_BCL_UNLOCK(mbhc->resmgr); + return IRQ_HANDLED; +} + +static irqreturn_t wcd9xxx_hphl_ocp_irq(int irq, void *data) +{ + struct wcd9xxx_mbhc *mbhc = data; + struct snd_soc_codec *codec; + + pr_info("%s: received HPHL OCP irq\n", __func__); + + if (mbhc) { + codec = mbhc->codec; + if ((mbhc->hphlocp_cnt < OCP_ATTEMPT) && + (!mbhc->hphrocp_cnt)) { + pr_info("%s: retry\n", __func__); + mbhc->hphlocp_cnt++; + snd_soc_update_bits(codec, WCD9XXX_A_RX_HPH_OCP_CTL, + 0x10, 0x00); + snd_soc_update_bits(codec, WCD9XXX_A_RX_HPH_OCP_CTL, + 0x10, 0x10); + } else { + wcd9xxx_disable_irq(mbhc->resmgr->core_res, + mbhc->intr_ids->hph_left_ocp); + mbhc->hph_status |= SND_JACK_OC_HPHL; + wcd9xxx_jack_report(mbhc, &mbhc->headset_jack, + mbhc->hph_status, + WCD9XXX_JACK_MASK); + } + } else { + pr_err("%s: Bad wcd9xxx private data\n", __func__); + } + + return IRQ_HANDLED; +} + +static irqreturn_t wcd9xxx_hphr_ocp_irq(int irq, void *data) +{ + struct wcd9xxx_mbhc *mbhc = data; + struct snd_soc_codec *codec; + + pr_info("%s: received HPHR OCP irq\n", __func__); + codec = mbhc->codec; + if ((mbhc->hphrocp_cnt < OCP_ATTEMPT) && + (!mbhc->hphlocp_cnt)) { + pr_info("%s: retry\n", __func__); + mbhc->hphrocp_cnt++; + snd_soc_update_bits(codec, WCD9XXX_A_RX_HPH_OCP_CTL, 0x10, + 0x00); + snd_soc_update_bits(codec, WCD9XXX_A_RX_HPH_OCP_CTL, 0x10, + 0x10); + } else { + wcd9xxx_disable_irq(mbhc->resmgr->core_res, + mbhc->intr_ids->hph_right_ocp); + mbhc->hph_status |= SND_JACK_OC_HPHR; + wcd9xxx_jack_report(mbhc, &mbhc->headset_jack, + mbhc->hph_status, WCD9XXX_JACK_MASK); + } + + return IRQ_HANDLED; +} + +static int wcd9xxx_acdb_mclk_index(const int rate) +{ + if (rate == MCLK_RATE_12288KHZ) + return 0; + else if (rate == MCLK_RATE_9600KHZ) + return 1; + else { + BUG_ON(1); + return -EINVAL; + } +} + +static void wcd9xxx_update_mbhc_clk_rate(struct wcd9xxx_mbhc *mbhc, u32 rate) +{ + u32 dce_wait, sta_wait; + u8 ncic, nmeas, navg; + void *calibration; + u8 *n_cic, *n_ready; + struct wcd9xxx_mbhc_btn_detect_cfg *btn_det; + u8 npoll = 4, nbounce_wait = 30; + struct snd_soc_codec *codec = mbhc->codec; + int idx = wcd9xxx_acdb_mclk_index(rate); + int idxmclk = wcd9xxx_acdb_mclk_index(mbhc->mbhc_cfg->mclk_rate); + + pr_debug("%s: Updating clock rate dependents, rate = %u\n", __func__, + rate); + calibration = mbhc->mbhc_cfg->calibration; + + /* + * First compute the DCE / STA wait times depending on tunable + * parameters. The value is computed in microseconds + */ + btn_det = WCD9XXX_MBHC_CAL_BTN_DET_PTR(calibration); + n_ready = wcd9xxx_mbhc_cal_btn_det_mp(btn_det, MBHC_BTN_DET_N_READY); + n_cic = wcd9xxx_mbhc_cal_btn_det_mp(btn_det, MBHC_BTN_DET_N_CIC); + nmeas = WCD9XXX_MBHC_CAL_BTN_DET_PTR(calibration)->n_meas; + navg = WCD9XXX_MBHC_CAL_GENERAL_PTR(calibration)->mbhc_navg; + + /* ncic stays with the same what we had during calibration */ + ncic = n_cic[idxmclk]; + dce_wait = (1000 * 512 * ncic * (nmeas + 1)) / (rate / 1000); + sta_wait = (1000 * 128 * (navg + 1)) / (rate / 1000); + mbhc->mbhc_data.t_dce = dce_wait; + /* give extra margin to sta for safety */ + mbhc->mbhc_data.t_sta = sta_wait + 250; + mbhc->mbhc_data.t_sta_dce = ((1000 * 256) / (rate / 1000) * + n_ready[idx]) + 10; + + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_TIMER_B1_CTL, n_ready[idx]); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_TIMER_B6_CTL, ncic); + + if (rate == MCLK_RATE_12288KHZ) { + npoll = 4; + nbounce_wait = 30; + } else if (rate == MCLK_RATE_9600KHZ) { + npoll = 3; + nbounce_wait = 23; + } + + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_TIMER_B2_CTL, npoll); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_TIMER_B3_CTL, nbounce_wait); + pr_debug("%s: leave\n", __func__); +} + +static void wcd9xxx_mbhc_cal(struct wcd9xxx_mbhc *mbhc) +{ + u8 cfilt_mode; + u16 reg0, reg1, reg2; + struct snd_soc_codec *codec = mbhc->codec; + + pr_debug("%s: enter\n", __func__); + wcd9xxx_disable_irq(mbhc->resmgr->core_res, + mbhc->intr_ids->dce_est_complete); + wcd9xxx_turn_onoff_rel_detection(codec, false); + + /* t_dce and t_sta are updated by wcd9xxx_update_mbhc_clk_rate() */ + WARN_ON(!mbhc->mbhc_data.t_dce); + WARN_ON(!mbhc->mbhc_data.t_sta); + + /* + * LDOH and CFILT are already configured during pdata handling. + * Only need to make sure CFILT and bandgap are in Fast mode. + * Need to restore defaults once calculation is done. + * + * In case when Micbias is powered by external source, request + * turn on the external voltage source for Calibration. + */ + if (mbhc->mbhc_cb && mbhc->mbhc_cb->enable_mb_source) + mbhc->mbhc_cb->enable_mb_source(codec, true, false); + + cfilt_mode = snd_soc_read(codec, mbhc->mbhc_bias_regs.cfilt_ctl); + if (mbhc->mbhc_cb && mbhc->mbhc_cb->cfilt_fast_mode) + mbhc->mbhc_cb->cfilt_fast_mode(codec, mbhc); + else + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.cfilt_ctl, + 0x40, 0x00); + + if (mbhc->mbhc_cb && mbhc->mbhc_cb->micbias_pulldown_ctrl) + mbhc->mbhc_cb->micbias_pulldown_ctrl(mbhc, false); + + /* + * Micbias, CFILT, LDOH, MBHC MUX mode settings + * to perform ADC calibration + */ + if (mbhc->mbhc_cb && mbhc->mbhc_cb->select_cfilt) + mbhc->mbhc_cb->select_cfilt(codec, mbhc); + else + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.ctl_reg, 0x60, + mbhc->mbhc_cfg->micbias << 5); + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.ctl_reg, 0x01, 0x00); + snd_soc_update_bits(codec, WCD9XXX_A_LDO_H_MODE_1, 0x60, 0x60); + snd_soc_write(codec, WCD9XXX_A_TX_7_MBHC_TEST_CTL, 0x78); + if (mbhc->mbhc_cb && mbhc->mbhc_cb->codec_specific_cal) + mbhc->mbhc_cb->codec_specific_cal(codec, mbhc); + else + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_B1_CTL, + 0x04, 0x04); + + /* Pull down micbias to ground */ + reg0 = snd_soc_read(codec, mbhc->mbhc_bias_regs.ctl_reg); + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.ctl_reg, 1, 1); + /* Disconnect override from micbias */ + reg1 = snd_soc_read(codec, WCD9XXX_A_MAD_ANA_CTRL); + snd_soc_update_bits(codec, WCD9XXX_A_MAD_ANA_CTRL, 1 << 4, 1 << 0); + /* Connect the MUX to micbias */ + snd_soc_write(codec, WCD9XXX_A_MBHC_SCALING_MUX_1, 0x02); + if (mbhc->mbhc_cb && mbhc->mbhc_cb->enable_mux_bias_block) + mbhc->mbhc_cb->enable_mux_bias_block(codec); + else + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_SCALING_MUX_1, + 0x80, 0x80); + /* + * Hardware that has external cap can delay mic bias ramping down up + * to 50ms. + */ + msleep(WCD9XXX_MUX_SWITCH_READY_WAIT_MS); + /* DCE measurement for 0 voltage */ + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x0A); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x02); + mbhc->mbhc_data.dce_z = __wcd9xxx_codec_sta_dce(mbhc, 1, true, false); + + /* compute dce_z for current source */ + reg2 = snd_soc_read(codec, WCD9XXX_A_CDC_MBHC_B1_CTL); + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_B1_CTL, 0x78, + WCD9XXX_MBHC_NSC_CS << 3); + + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x0A); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x02); + mbhc->mbhc_data.dce_nsc_cs_z = __wcd9xxx_codec_sta_dce(mbhc, 1, true, + false); + pr_debug("%s: dce_z with nsc cs: 0x%x\n", __func__, + mbhc->mbhc_data.dce_nsc_cs_z); + + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_B1_CTL, reg2); + + /* STA measurement for 0 voltage */ + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x0A); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x02); + mbhc->mbhc_data.sta_z = __wcd9xxx_codec_sta_dce(mbhc, 0, true, false); + + /* Restore registers */ + snd_soc_write(codec, mbhc->mbhc_bias_regs.ctl_reg, reg0); + snd_soc_write(codec, WCD9XXX_A_MAD_ANA_CTRL, reg1); + + /* DCE measurment for MB voltage */ + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x0A); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x02); + snd_soc_write(codec, WCD9XXX_A_MBHC_SCALING_MUX_1, 0x02); + if (mbhc->mbhc_cb && mbhc->mbhc_cb->enable_mux_bias_block) + mbhc->mbhc_cb->enable_mux_bias_block(codec); + else + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_SCALING_MUX_1, + 0x80, 0x80); + /* + * Hardware that has external cap can delay mic bias ramping down up + * to 50ms. + */ + msleep(WCD9XXX_MUX_SWITCH_READY_WAIT_MS); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_EN_CTL, 0x04); + usleep_range(mbhc->mbhc_data.t_dce, mbhc->mbhc_data.t_dce + + WCD9XXX_USLEEP_RANGE_MARGIN_US); + mbhc->mbhc_data.dce_mb = wcd9xxx_read_dce_result(codec); + + /* STA Measurement for MB Voltage */ + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x0A); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_EN_CTL, 0x02); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_CLK_CTL, 0x02); + snd_soc_write(codec, WCD9XXX_A_MBHC_SCALING_MUX_1, 0x02); + if (mbhc->mbhc_cb && mbhc->mbhc_cb->enable_mux_bias_block) + mbhc->mbhc_cb->enable_mux_bias_block(codec); + else + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_SCALING_MUX_1, + 0x80, 0x80); + /* + * Hardware that has external cap can delay mic bias ramping down up + * to 50ms. + */ + msleep(WCD9XXX_MUX_SWITCH_READY_WAIT_MS); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_EN_CTL, 0x02); + usleep_range(mbhc->mbhc_data.t_sta, mbhc->mbhc_data.t_sta + + WCD9XXX_USLEEP_RANGE_MARGIN_US); + mbhc->mbhc_data.sta_mb = wcd9xxx_read_sta_result(codec); + + /* Restore default settings. */ + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_B1_CTL, 0x04, 0x00); + snd_soc_write(codec, mbhc->mbhc_bias_regs.cfilt_ctl, cfilt_mode); + snd_soc_write(codec, WCD9XXX_A_MBHC_SCALING_MUX_1, 0x04); + if (mbhc->mbhc_cb && mbhc->mbhc_cb->enable_mux_bias_block) + mbhc->mbhc_cb->enable_mux_bias_block(codec); + else + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_SCALING_MUX_1, + 0x80, 0x80); + usleep_range(100, 110); + + if (mbhc->mbhc_cb && mbhc->mbhc_cb->enable_mb_source) + mbhc->mbhc_cb->enable_mb_source(codec, false, false); + + if (mbhc->mbhc_cb && mbhc->mbhc_cb->micbias_pulldown_ctrl) + mbhc->mbhc_cb->micbias_pulldown_ctrl(mbhc, true); + + wcd9xxx_enable_irq(mbhc->resmgr->core_res, + mbhc->intr_ids->dce_est_complete); + wcd9xxx_turn_onoff_rel_detection(codec, true); + + pr_debug("%s: leave\n", __func__); +} + +static void wcd9xxx_mbhc_setup(struct wcd9xxx_mbhc *mbhc) +{ + int n; + u8 *gain; + struct wcd9xxx_mbhc_general_cfg *generic; + struct wcd9xxx_mbhc_btn_detect_cfg *btn_det; + struct snd_soc_codec *codec = mbhc->codec; + const int idx = wcd9xxx_acdb_mclk_index(mbhc->mbhc_cfg->mclk_rate); + + pr_debug("%s: enter\n", __func__); + generic = WCD9XXX_MBHC_CAL_GENERAL_PTR(mbhc->mbhc_cfg->calibration); + btn_det = WCD9XXX_MBHC_CAL_BTN_DET_PTR(mbhc->mbhc_cfg->calibration); + + for (n = 0; n < 8; n++) { + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_FIR_B1_CFG, + 0x07, n); + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_FIR_B2_CFG, + btn_det->c[n]); + } + + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_B2_CTL, 0x07, + btn_det->nc); + + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_TIMER_B4_CTL, 0x70, + generic->mbhc_nsa << 4); + + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_TIMER_B4_CTL, 0x0F, + btn_det->n_meas); + + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_TIMER_B5_CTL, + generic->mbhc_navg); + + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_B1_CTL, 0x80, 0x80); + + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_B1_CTL, 0x78, + btn_det->mbhc_nsc << 3); + + if (mbhc->mbhc_cb && mbhc->mbhc_cb->get_cdc_type && + mbhc->mbhc_cb->get_cdc_type() != + WCD9XXX_CDC_TYPE_HELICON) { + if (mbhc->resmgr->reg_addr->micb_4_mbhc) + snd_soc_update_bits(codec, + mbhc->resmgr->reg_addr->micb_4_mbhc, + 0x03, MBHC_MICBIAS2); + } + + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_B1_CTL, 0x02, 0x02); + + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_SCALING_MUX_2, 0xF0, 0xF0); + + gain = wcd9xxx_mbhc_cal_btn_det_mp(btn_det, MBHC_BTN_DET_GAIN); + snd_soc_update_bits(codec, WCD9XXX_A_CDC_MBHC_B2_CTL, 0x78, + gain[idx] << 3); + snd_soc_update_bits(codec, WCD9XXX_A_MICB_2_MBHC, 0x04, 0x04); + + pr_debug("%s: leave\n", __func__); +} + +static int wcd9xxx_setup_jack_detect_irq(struct wcd9xxx_mbhc *mbhc) +{ + int ret = 0; + void *core_res = mbhc->resmgr->core_res; + + if (mbhc->mbhc_cfg->gpio) { + ret = request_threaded_irq(mbhc->mbhc_cfg->gpio_irq, NULL, + wcd9xxx_mech_plug_detect_irq, + (IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING), + "headset detect", mbhc); + if (ret) { + pr_err("%s: Failed to request gpio irq %d\n", __func__, + mbhc->mbhc_cfg->gpio_irq); + } else { + ret = enable_irq_wake(mbhc->mbhc_cfg->gpio_irq); + if (ret) + pr_err("%s: Failed to enable wake up irq %d\n", + __func__, mbhc->mbhc_cfg->gpio_irq); + } + } else if (mbhc->mbhc_cfg->insert_detect) { + /* Enable HPHL_10K_SW */ + snd_soc_update_bits(mbhc->codec, WCD9XXX_A_RX_HPH_OCP_CTL, + 1 << 1, 1 << 1); + + ret = wcd9xxx_request_irq(core_res, + mbhc->intr_ids->hs_jack_switch, + wcd9xxx_mech_plug_detect_irq, + "Jack Detect", + mbhc); + if (ret) + pr_err("%s: Failed to request insert detect irq %d\n", + __func__, mbhc->intr_ids->hs_jack_switch); + } + + return ret; +} + +static int wcd9xxx_init_and_calibrate(struct wcd9xxx_mbhc *mbhc) +{ + int ret = 0; + struct snd_soc_codec *codec = mbhc->codec; + + pr_debug("%s: enter\n", __func__); + + /* Enable MCLK during calibration */ + wcd9xxx_onoff_ext_mclk(mbhc, true); + wcd9xxx_mbhc_setup(mbhc); + wcd9xxx_mbhc_cal(mbhc); + wcd9xxx_mbhc_calc_thres(mbhc); + wcd9xxx_onoff_ext_mclk(mbhc, false); + wcd9xxx_calibrate_hs_polling(mbhc); + + /* Enable Mic Bias pull down and HPH Switch to GND */ + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.ctl_reg, 0x01, 0x01); + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_HPH, 0x01, 0x01); + INIT_WORK(&mbhc->correct_plug_swch, wcd9xxx_correct_swch_plug); + + if (!IS_ERR_VALUE(ret)) { + snd_soc_update_bits(codec, WCD9XXX_A_RX_HPH_OCP_CTL, 0x10, + 0x10); + wcd9xxx_enable_irq(mbhc->resmgr->core_res, + mbhc->intr_ids->hph_left_ocp); + wcd9xxx_enable_irq(mbhc->resmgr->core_res, + mbhc->intr_ids->hph_right_ocp); + + /* Initialize mechanical mbhc */ + ret = wcd9xxx_setup_jack_detect_irq(mbhc); + + if (!ret && mbhc->mbhc_cfg->gpio) { + /* Requested with IRQF_DISABLED */ + enable_irq(mbhc->mbhc_cfg->gpio_irq); + + /* Bootup time detection */ + wcd9xxx_swch_irq_handler(mbhc); + } else if (!ret && mbhc->mbhc_cfg->insert_detect) { + pr_debug("%s: Setting up codec own insert detection\n", + __func__); + /* Setup for insertion detection */ + wcd9xxx_insert_detect_setup(mbhc, true); + } + } + + pr_debug("%s: leave\n", __func__); + + return ret; +} + +static void wcd9xxx_mbhc_fw_read(struct work_struct *work) +{ + struct delayed_work *dwork; + struct wcd9xxx_mbhc *mbhc; + struct snd_soc_codec *codec; + const struct firmware *fw; + struct firmware_cal *fw_data = NULL; + int ret = -1, retry = 0; + bool use_default_cal = false; + + dwork = to_delayed_work(work); + mbhc = container_of(dwork, struct wcd9xxx_mbhc, mbhc_firmware_dwork); + codec = mbhc->codec; + + while (retry < FW_READ_ATTEMPTS) { + retry++; + pr_info("%s:Attempt %d to request MBHC firmware\n", + __func__, retry); + if (mbhc->mbhc_cb->get_hwdep_fw_cal) + fw_data = mbhc->mbhc_cb->get_hwdep_fw_cal(codec, + WCD9XXX_MBHC_CAL); + if (!fw_data) + ret = request_firmware(&fw, "wcd9320/wcd9320_mbhc.bin", + codec->dev); + /* + * if request_firmware and hwdep cal both fail then + * retry for few times before bailing out + */ + if ((ret != 0) && !fw_data) { + usleep_range(FW_READ_TIMEOUT, FW_READ_TIMEOUT + + WCD9XXX_USLEEP_RANGE_MARGIN_US); + } else { + pr_info("%s: MBHC Firmware read succesful\n", + __func__); + break; + } + } + if (!fw_data) + pr_info("%s: using request_firmware\n", __func__); + else + pr_info("%s: using hwdep cal\n", __func__); + if (ret != 0 && !fw_data) { + pr_err("%s: Cannot load MBHC firmware use default cal\n", + __func__); + use_default_cal = true; + } + if (!use_default_cal) { + const void *data; + size_t size; + + if (fw_data) { + data = fw_data->data; + size = fw_data->size; + } else { + data = fw->data; + size = fw->size; + } + if (wcd9xxx_mbhc_fw_validate(data, size) == false) { + pr_err("%s: Invalid MBHC cal data size use default cal\n", + __func__); + if (!fw_data) + release_firmware(fw); + } else { + if (fw_data) { + mbhc->mbhc_cfg->calibration = + (void *)fw_data->data; + mbhc->mbhc_cal = fw_data; + } else { + mbhc->mbhc_cfg->calibration = + (void *)fw->data; + mbhc->mbhc_fw = fw; + } + } + } + + (void) wcd9xxx_init_and_calibrate(mbhc); +} + +#ifdef CONFIG_DEBUG_FS +ssize_t codec_mbhc_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + const int size = 768; + char buffer[size]; + int n = 0; + struct wcd9xxx_mbhc *mbhc = file->private_data; + const struct mbhc_internal_cal_data *p = &mbhc->mbhc_data; + const s16 v_ins_hu = + wcd9xxx_get_current_v(mbhc, WCD9XXX_CURRENT_V_INS_HU); + const s16 v_ins_h = + wcd9xxx_get_current_v(mbhc, WCD9XXX_CURRENT_V_INS_H); + const s16 v_b1_hu = + wcd9xxx_get_current_v(mbhc, WCD9XXX_CURRENT_V_B1_HU); + const s16 v_b1_h = + wcd9xxx_get_current_v(mbhc, WCD9XXX_CURRENT_V_B1_H); + const s16 v_br_h = + wcd9xxx_get_current_v(mbhc, WCD9XXX_CURRENT_V_BR_H); + + n = scnprintf(buffer, size - n, "dce_z = %x(%dmv)\n", + p->dce_z, wcd9xxx_codec_sta_dce_v(mbhc, 1, p->dce_z)); + n += scnprintf(buffer + n, size - n, "dce_mb = %x(%dmv)\n", + p->dce_mb, wcd9xxx_codec_sta_dce_v(mbhc, 1, p->dce_mb)); + n += scnprintf(buffer + n, size - n, "dce_nsc_cs_z = %x(%dmv)\n", + p->dce_nsc_cs_z, + __wcd9xxx_codec_sta_dce_v(mbhc, 1, p->dce_nsc_cs_z, + p->dce_nsc_cs_z, + VDDIO_MICBIAS_MV)); + n += scnprintf(buffer + n, size - n, "sta_z = %x(%dmv)\n", + p->sta_z, wcd9xxx_codec_sta_dce_v(mbhc, 0, p->sta_z)); + n += scnprintf(buffer + n, size - n, "sta_mb = %x(%dmv)\n", + p->sta_mb, wcd9xxx_codec_sta_dce_v(mbhc, 0, p->sta_mb)); + n += scnprintf(buffer + n, size - n, "t_dce = %d\n", p->t_dce); + n += scnprintf(buffer + n, size - n, "t_sta = %d\n", p->t_sta); + n += scnprintf(buffer + n, size - n, "micb_mv = %dmv\n", p->micb_mv); + n += scnprintf(buffer + n, size - n, "v_ins_hu = %x(%dmv)\n", + v_ins_hu, wcd9xxx_codec_sta_dce_v(mbhc, 0, v_ins_hu)); + n += scnprintf(buffer + n, size - n, "v_ins_h = %x(%dmv)\n", + v_ins_h, wcd9xxx_codec_sta_dce_v(mbhc, 1, v_ins_h)); + n += scnprintf(buffer + n, size - n, "v_b1_hu = %x(%dmv)\n", + v_b1_hu, wcd9xxx_codec_sta_dce_v(mbhc, 0, v_b1_hu)); + n += scnprintf(buffer + n, size - n, "v_b1_h = %x(%dmv)\n", + v_b1_h, wcd9xxx_codec_sta_dce_v(mbhc, 1, v_b1_h)); + n += scnprintf(buffer + n, size - n, "v_brh = %x(%dmv)\n", + v_br_h, wcd9xxx_codec_sta_dce_v(mbhc, 1, v_br_h)); + n += scnprintf(buffer + n, size - n, "v_brl = %x(%dmv)\n", p->v_brl, + wcd9xxx_codec_sta_dce_v(mbhc, 0, p->v_brl)); + n += scnprintf(buffer + n, size - n, "v_no_mic = %x(%dmv)\n", + p->v_no_mic, + wcd9xxx_codec_sta_dce_v(mbhc, 0, p->v_no_mic)); + n += scnprintf(buffer + n, size - n, "v_inval_ins_low = %d\n", + p->v_inval_ins_low); + n += scnprintf(buffer + n, size - n, "v_inval_ins_high = %d\n", + p->v_inval_ins_high); + n += scnprintf(buffer + n, size - n, "Insert detect insert = %d\n", + !wcd9xxx_swch_level_remove(mbhc)); + buffer[n] = 0; + + return simple_read_from_buffer(buf, count, pos, buffer, n); +} + +static int codec_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t codec_debug_write(struct file *filp, + const char __user *ubuf, size_t cnt, + loff_t *ppos) +{ + char lbuf[32]; + char *buf; + int rc; + struct wcd9xxx_mbhc *mbhc = filp->private_data; + + if (cnt > sizeof(lbuf) - 1) + return -EINVAL; + + rc = copy_from_user(lbuf, ubuf, cnt); + if (rc) + return -EFAULT; + + lbuf[cnt] = '\0'; + buf = (char *)lbuf; + mbhc->no_mic_headset_override = (*strsep(&buf, " ") == '0') ? + false : true; + return rc; +} + +static const struct file_operations mbhc_trrs_debug_ops = { + .open = codec_debug_open, + .write = codec_debug_write, +}; + +static const struct file_operations mbhc_debug_ops = { + .open = codec_debug_open, + .read = codec_mbhc_debug_read, +}; + +static void wcd9xxx_init_debugfs(struct wcd9xxx_mbhc *mbhc) +{ + mbhc->debugfs_poke = + debugfs_create_file("TRRS", S_IFREG | S_IRUGO, NULL, mbhc, + &mbhc_trrs_debug_ops); + mbhc->debugfs_mbhc = + debugfs_create_file("wcd9xxx_mbhc", S_IFREG | S_IRUGO, + NULL, mbhc, &mbhc_debug_ops); +} + +static void wcd9xxx_cleanup_debugfs(struct wcd9xxx_mbhc *mbhc) +{ + debugfs_remove(mbhc->debugfs_poke); + debugfs_remove(mbhc->debugfs_mbhc); +} +#else +static void wcd9xxx_init_debugfs(struct wcd9xxx_mbhc *mbhc) +{ +} + +static void wcd9xxx_cleanup_debugfs(struct wcd9xxx_mbhc *mbhc) +{ +} +#endif + +int wcd9xxx_mbhc_set_keycode(struct wcd9xxx_mbhc *mbhc) +{ + enum snd_jack_types type = SND_JACK_BTN_0; + int i, ret, result = 0; + int *btn_key_code; + + btn_key_code = mbhc->mbhc_cfg->key_code; + + for (i = 0 ; i < 8 ; i++) { + if (btn_key_code[i] != 0) { + switch (i) { + case 0: + type = SND_JACK_BTN_0; + break; + case 1: + type = SND_JACK_BTN_1; + break; + case 2: + type = SND_JACK_BTN_2; + break; + case 3: + type = SND_JACK_BTN_3; + break; + case 4: + type = SND_JACK_BTN_4; + break; + case 5: + type = SND_JACK_BTN_5; + break; + default: + WARN_ONCE(1, "Wrong button number:%d\n", i); + result = -1; + break; + } + ret = snd_jack_set_key(mbhc->button_jack.jack, + type, + btn_key_code[i]); + if (ret) { + pr_err("%s: Failed to set code for %d\n", + __func__, btn_key_code[i]); + result = -1; + } + input_set_capability( + mbhc->button_jack.jack->input_dev, + EV_KEY, btn_key_code[i]); + pr_debug("%s: set btn%d key code:%d\n", __func__, + i, btn_key_code[i]); + } + } + return result; +} + +int wcd9xxx_mbhc_start(struct wcd9xxx_mbhc *mbhc, + struct wcd9xxx_mbhc_config *mbhc_cfg) +{ + int rc = 0; + struct snd_soc_codec *codec = mbhc->codec; + + pr_debug("%s: enter\n", __func__); + + if (!codec) { + pr_err("%s: no codec\n", __func__); + return -EINVAL; + } + + if (mbhc_cfg->mclk_rate != MCLK_RATE_12288KHZ && + mbhc_cfg->mclk_rate != MCLK_RATE_9600KHZ) { + pr_err("Error: unsupported clock rate %d\n", + mbhc_cfg->mclk_rate); + return -EINVAL; + } + + /* Save mbhc config */ + mbhc->mbhc_cfg = mbhc_cfg; + + /* Set btn key code */ + if (wcd9xxx_mbhc_set_keycode(mbhc)) + pr_err("Set btn key code error!!!\n"); + + /* Get HW specific mbhc registers' address */ + wcd9xxx_get_mbhc_micbias_regs(mbhc, MBHC_PRIMARY_MIC_MB); + + /* Get HW specific mbhc registers' address for anc */ + wcd9xxx_get_mbhc_micbias_regs(mbhc, MBHC_ANC_MIC_MB); + + /* Put CFILT in fast mode by default */ + if (mbhc->mbhc_cb && mbhc->mbhc_cb->cfilt_fast_mode) + mbhc->mbhc_cb->cfilt_fast_mode(codec, mbhc); + else + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.cfilt_ctl, + 0x40, WCD9XXX_CFILT_FAST_MODE); + + /* + * setup internal micbias if codec uses internal micbias for + * headset detection + */ + if (mbhc->mbhc_cfg->use_int_rbias) { + if (mbhc->mbhc_cb && mbhc->mbhc_cb->setup_int_rbias) { + mbhc->mbhc_cb->setup_int_rbias(codec, true); + } else { + pr_info("%s: internal bias requested but codec did not provide callback\n", + __func__); + } + } + + /* + * If codec has specific clock gating for MBHC, + * remove the clock gate + */ + if (mbhc->mbhc_cb && + mbhc->mbhc_cb->enable_clock_gate) + mbhc->mbhc_cb->enable_clock_gate(mbhc->codec, true); + + if (!mbhc->mbhc_cfg->read_fw_bin || + (mbhc->mbhc_cfg->read_fw_bin && mbhc->mbhc_fw) || + (mbhc->mbhc_cfg->read_fw_bin && mbhc->mbhc_cal)) { + rc = wcd9xxx_init_and_calibrate(mbhc); + } else { + if (!mbhc->mbhc_fw || !mbhc->mbhc_cal) + schedule_delayed_work(&mbhc->mbhc_firmware_dwork, + usecs_to_jiffies(FW_READ_TIMEOUT)); + else + pr_debug("%s: Skipping to read mbhc fw, 0x%pK %pK\n", + __func__, mbhc->mbhc_fw, mbhc->mbhc_cal); + } + + pr_debug("%s: leave %d\n", __func__, rc); + return rc; +} +EXPORT_SYMBOL(wcd9xxx_mbhc_start); + +void wcd9xxx_mbhc_stop(struct wcd9xxx_mbhc *mbhc) +{ + if (mbhc->mbhc_fw || mbhc->mbhc_cal) { + cancel_delayed_work_sync(&mbhc->mbhc_firmware_dwork); + if (!mbhc->mbhc_cal) + release_firmware(mbhc->mbhc_fw); + mbhc->mbhc_fw = NULL; + mbhc->mbhc_cal = NULL; + } +} +EXPORT_SYMBOL(wcd9xxx_mbhc_stop); + +static enum wcd9xxx_micbias_num +wcd9xxx_event_to_micbias(const enum wcd9xxx_notify_event event) +{ + enum wcd9xxx_micbias_num ret; + switch (event) { + case WCD9XXX_EVENT_PRE_MICBIAS_1_ON: + case WCD9XXX_EVENT_PRE_MICBIAS_1_OFF: + case WCD9XXX_EVENT_POST_MICBIAS_1_ON: + case WCD9XXX_EVENT_POST_MICBIAS_1_OFF: + ret = MBHC_MICBIAS1; + break; + case WCD9XXX_EVENT_PRE_MICBIAS_2_ON: + case WCD9XXX_EVENT_PRE_MICBIAS_2_OFF: + case WCD9XXX_EVENT_POST_MICBIAS_2_ON: + case WCD9XXX_EVENT_POST_MICBIAS_2_OFF: + ret = MBHC_MICBIAS2; + break; + case WCD9XXX_EVENT_PRE_MICBIAS_3_ON: + case WCD9XXX_EVENT_PRE_MICBIAS_3_OFF: + case WCD9XXX_EVENT_POST_MICBIAS_3_ON: + case WCD9XXX_EVENT_POST_MICBIAS_3_OFF: + ret = MBHC_MICBIAS3; + break; + case WCD9XXX_EVENT_PRE_MICBIAS_4_ON: + case WCD9XXX_EVENT_PRE_MICBIAS_4_OFF: + case WCD9XXX_EVENT_POST_MICBIAS_4_ON: + case WCD9XXX_EVENT_POST_MICBIAS_4_OFF: + ret = MBHC_MICBIAS4; + break; + default: + WARN_ONCE(1, "Cannot convert event %d to micbias\n", event); + ret = MBHC_MICBIAS_INVALID; + break; + } + return ret; +} + +static int wcd9xxx_event_to_cfilt(const enum wcd9xxx_notify_event event) +{ + int ret; + switch (event) { + case WCD9XXX_EVENT_PRE_CFILT_1_OFF: + case WCD9XXX_EVENT_POST_CFILT_1_OFF: + case WCD9XXX_EVENT_PRE_CFILT_1_ON: + case WCD9XXX_EVENT_POST_CFILT_1_ON: + ret = WCD9XXX_CFILT1_SEL; + break; + case WCD9XXX_EVENT_PRE_CFILT_2_OFF: + case WCD9XXX_EVENT_POST_CFILT_2_OFF: + case WCD9XXX_EVENT_PRE_CFILT_2_ON: + case WCD9XXX_EVENT_POST_CFILT_2_ON: + ret = WCD9XXX_CFILT2_SEL; + break; + case WCD9XXX_EVENT_PRE_CFILT_3_OFF: + case WCD9XXX_EVENT_POST_CFILT_3_OFF: + case WCD9XXX_EVENT_PRE_CFILT_3_ON: + case WCD9XXX_EVENT_POST_CFILT_3_ON: + ret = WCD9XXX_CFILT3_SEL; + break; + default: + ret = -1; + } + return ret; +} + +static int wcd9xxx_get_mbhc_cfilt_sel(struct wcd9xxx_mbhc *mbhc) +{ + int cfilt; + const struct wcd9xxx_micbias_setting *mb_pdata = + mbhc->resmgr->micbias_pdata; + + switch (mbhc->mbhc_cfg->micbias) { + case MBHC_MICBIAS1: + cfilt = mb_pdata->bias1_cfilt_sel; + break; + case MBHC_MICBIAS2: + cfilt = mb_pdata->bias2_cfilt_sel; + break; + case MBHC_MICBIAS3: + cfilt = mb_pdata->bias3_cfilt_sel; + break; + case MBHC_MICBIAS4: + cfilt = mb_pdata->bias4_cfilt_sel; + break; + default: + cfilt = MBHC_MICBIAS_INVALID; + break; + } + return cfilt; +} + +static void wcd9xxx_enable_mbhc_txfe(struct wcd9xxx_mbhc *mbhc, bool on) +{ + if (mbhc->mbhc_cb && mbhc->mbhc_cb->enable_mbhc_txfe) + mbhc->mbhc_cb->enable_mbhc_txfe(mbhc->codec, on); + else + snd_soc_update_bits(mbhc->codec, WCD9XXX_A_TX_7_MBHC_TEST_CTL, + 0x40, on ? 0x40 : 0x00); +} + +static int wcd9xxx_event_notify(struct notifier_block *self, unsigned long val, + void *data) +{ + int ret = 0; + struct wcd9xxx_mbhc *mbhc = ((struct wcd9xxx_resmgr *)data)->mbhc; + struct snd_soc_codec *codec; + enum wcd9xxx_notify_event event = (enum wcd9xxx_notify_event)val; + + pr_debug("%s: enter event %s(%d)\n", __func__, + wcd9xxx_get_event_string(event), event); + + if (!mbhc || !mbhc->mbhc_cfg) { + pr_debug("mbhc not initialized\n"); + return 0; + } + codec = mbhc->codec; + mutex_lock(&mbhc->mbhc_lock); + switch (event) { + /* MICBIAS usage change */ + case WCD9XXX_EVENT_PRE_MICBIAS_1_ON: + case WCD9XXX_EVENT_PRE_MICBIAS_2_ON: + case WCD9XXX_EVENT_PRE_MICBIAS_3_ON: + case WCD9XXX_EVENT_PRE_MICBIAS_4_ON: + if (mbhc->mbhc_cfg && mbhc->mbhc_cfg->micbias == + wcd9xxx_event_to_micbias(event)) { + wcd9xxx_switch_micbias(mbhc, 0); + /* + * Enable MBHC TxFE whenever micbias is + * turned ON and polling is active + */ + if (mbhc->polling_active) + wcd9xxx_enable_mbhc_txfe(mbhc, true); + } + break; + case WCD9XXX_EVENT_POST_MICBIAS_1_ON: + case WCD9XXX_EVENT_POST_MICBIAS_2_ON: + case WCD9XXX_EVENT_POST_MICBIAS_3_ON: + case WCD9XXX_EVENT_POST_MICBIAS_4_ON: + if (mbhc->mbhc_cfg && mbhc->mbhc_cfg->micbias == + wcd9xxx_event_to_micbias(event) && + wcd9xxx_mbhc_polling(mbhc)) { + /* if polling is on, restart it */ + wcd9xxx_pause_hs_polling(mbhc); + wcd9xxx_start_hs_polling(mbhc); + } + break; + case WCD9XXX_EVENT_POST_MICBIAS_1_OFF: + case WCD9XXX_EVENT_POST_MICBIAS_2_OFF: + case WCD9XXX_EVENT_POST_MICBIAS_3_OFF: + case WCD9XXX_EVENT_POST_MICBIAS_4_OFF: + if (mbhc->mbhc_cfg && mbhc->mbhc_cfg->micbias == + wcd9xxx_event_to_micbias(event)) { + if (mbhc->event_state & + (1 << MBHC_EVENT_PA_HPHL | 1 << MBHC_EVENT_PA_HPHR)) + wcd9xxx_switch_micbias(mbhc, 1); + /* + * Disable MBHC TxFE, in case it was enabled earlier + * when micbias was enabled and polling is not active. + */ + if (!mbhc->polling_active) + wcd9xxx_enable_mbhc_txfe(mbhc, false); + } + if (mbhc->micbias_enable && mbhc->polling_active && + !(snd_soc_read(mbhc->codec, mbhc->mbhc_bias_regs.ctl_reg) + & 0x80)) { + pr_debug("%s:Micbias turned off by recording, set up again", + __func__); + snd_soc_update_bits(codec, mbhc->mbhc_bias_regs.ctl_reg, + 0x80, 0x80); + } + break; + /* PA usage change */ + case WCD9XXX_EVENT_PRE_HPHL_PA_ON: + set_bit(MBHC_EVENT_PA_HPHL, &mbhc->event_state); + if (!(snd_soc_read(codec, mbhc->mbhc_bias_regs.ctl_reg) & 0x80)) + /* if micbias is not enabled, switch to vddio */ + wcd9xxx_switch_micbias(mbhc, 1); + break; + case WCD9XXX_EVENT_PRE_HPHR_PA_ON: + set_bit(MBHC_EVENT_PA_HPHR, &mbhc->event_state); + break; + case WCD9XXX_EVENT_POST_HPHL_PA_OFF: + clear_bit(MBHC_EVENT_PA_HPHL, &mbhc->event_state); + /* if HPH PAs are off, report OCP and switch back to CFILT */ + clear_bit(WCD9XXX_HPHL_PA_OFF_ACK, &mbhc->hph_pa_dac_state); + clear_bit(WCD9XXX_HPHL_DAC_OFF_ACK, &mbhc->hph_pa_dac_state); + if (mbhc->hph_status & SND_JACK_OC_HPHL) + hphlocp_off_report(mbhc, SND_JACK_OC_HPHL); + if (!(mbhc->event_state & + (1 << MBHC_EVENT_PA_HPHL | 1 << MBHC_EVENT_PA_HPHR | + 1 << MBHC_EVENT_PRE_TX_3_ON))) + wcd9xxx_switch_micbias(mbhc, 0); + break; + case WCD9XXX_EVENT_POST_HPHR_PA_OFF: + clear_bit(MBHC_EVENT_PA_HPHR, &mbhc->event_state); + /* if HPH PAs are off, report OCP and switch back to CFILT */ + clear_bit(WCD9XXX_HPHR_PA_OFF_ACK, &mbhc->hph_pa_dac_state); + clear_bit(WCD9XXX_HPHR_DAC_OFF_ACK, &mbhc->hph_pa_dac_state); + if (mbhc->hph_status & SND_JACK_OC_HPHR) + hphrocp_off_report(mbhc, SND_JACK_OC_HPHL); + if (!(mbhc->event_state & + (1 << MBHC_EVENT_PA_HPHL | 1 << MBHC_EVENT_PA_HPHR | + 1 << MBHC_EVENT_PRE_TX_3_ON))) + wcd9xxx_switch_micbias(mbhc, 0); + break; + /* Clock usage change */ + case WCD9XXX_EVENT_PRE_MCLK_ON: + break; + case WCD9XXX_EVENT_POST_MCLK_ON: + /* Change to lower TxAAF frequency */ + snd_soc_update_bits(codec, WCD9XXX_A_TX_COM_BIAS, 1 << 4, + 1 << 4); + /* Re-calibrate clock rate dependent values */ + wcd9xxx_update_mbhc_clk_rate(mbhc, mbhc->mbhc_cfg->mclk_rate); + /* If clock source changes, stop and restart polling */ + if (wcd9xxx_mbhc_polling(mbhc)) { + wcd9xxx_calibrate_hs_polling(mbhc); + wcd9xxx_start_hs_polling(mbhc); + } + break; + case WCD9XXX_EVENT_PRE_MCLK_OFF: + /* If clock source changes, stop and restart polling */ + if (wcd9xxx_mbhc_polling(mbhc)) + wcd9xxx_pause_hs_polling(mbhc); + break; + case WCD9XXX_EVENT_POST_MCLK_OFF: + break; + case WCD9XXX_EVENT_PRE_RCO_ON: + break; + case WCD9XXX_EVENT_POST_RCO_ON: + /* Change to higher TxAAF frequency */ + snd_soc_update_bits(codec, WCD9XXX_A_TX_COM_BIAS, 1 << 4, + 0 << 4); + /* Re-calibrate clock rate dependent values */ + wcd9xxx_update_mbhc_clk_rate(mbhc, mbhc->rco_clk_rate); + /* If clock source changes, stop and restart polling */ + if (wcd9xxx_mbhc_polling(mbhc)) { + wcd9xxx_calibrate_hs_polling(mbhc); + wcd9xxx_start_hs_polling(mbhc); + } + break; + case WCD9XXX_EVENT_PRE_RCO_OFF: + /* If clock source changes, stop and restart polling */ + if (wcd9xxx_mbhc_polling(mbhc)) + wcd9xxx_pause_hs_polling(mbhc); + break; + case WCD9XXX_EVENT_POST_RCO_OFF: + break; + /* CFILT usage change */ + case WCD9XXX_EVENT_PRE_CFILT_1_ON: + case WCD9XXX_EVENT_PRE_CFILT_2_ON: + case WCD9XXX_EVENT_PRE_CFILT_3_ON: + if (wcd9xxx_get_mbhc_cfilt_sel(mbhc) == + wcd9xxx_event_to_cfilt(event)) + /* + * Switch CFILT to slow mode if MBHC CFILT is being + * used. + */ + wcd9xxx_codec_switch_cfilt_mode(mbhc, false); + break; + case WCD9XXX_EVENT_POST_CFILT_1_OFF: + case WCD9XXX_EVENT_POST_CFILT_2_OFF: + case WCD9XXX_EVENT_POST_CFILT_3_OFF: + if (wcd9xxx_get_mbhc_cfilt_sel(mbhc) == + wcd9xxx_event_to_cfilt(event)) + /* + * Switch CFILT to fast mode if MBHC CFILT is not + * used anymore. + */ + wcd9xxx_codec_switch_cfilt_mode(mbhc, true); + break; + /* System resume */ + case WCD9XXX_EVENT_POST_RESUME: + mbhc->mbhc_last_resume = jiffies; + break; + /* BG mode chage */ + case WCD9XXX_EVENT_PRE_BG_OFF: + case WCD9XXX_EVENT_POST_BG_OFF: + case WCD9XXX_EVENT_PRE_BG_AUDIO_ON: + case WCD9XXX_EVENT_POST_BG_AUDIO_ON: + case WCD9XXX_EVENT_PRE_BG_MBHC_ON: + case WCD9XXX_EVENT_POST_BG_MBHC_ON: + /* Not used for now */ + break; + case WCD9XXX_EVENT_PRE_TX_3_ON: + /* + * if polling is ON, mbhc micbias not enabled + * switch micbias source to VDDIO + */ + set_bit(MBHC_EVENT_PRE_TX_3_ON, &mbhc->event_state); + if (!(snd_soc_read(codec, mbhc->mbhc_bias_regs.ctl_reg) + & 0x80) && + mbhc->polling_active && !mbhc->mbhc_micbias_switched) + wcd9xxx_switch_micbias(mbhc, 1); + break; + case WCD9XXX_EVENT_POST_TX_3_OFF: + /* + * Switch back to micbias if HPH PA or TX3 path + * is disabled + */ + clear_bit(MBHC_EVENT_PRE_TX_3_ON, &mbhc->event_state); + if (mbhc->polling_active && mbhc->mbhc_micbias_switched && + !(mbhc->event_state & (1 << MBHC_EVENT_PA_HPHL | + 1 << MBHC_EVENT_PA_HPHR))) + wcd9xxx_switch_micbias(mbhc, 0); + break; + default: + WARN(1, "Unknown event %d\n", event); + ret = -EINVAL; + } + mutex_unlock(&mbhc->mbhc_lock); + + pr_debug("%s: leave\n", __func__); + + return ret; +} + +static s16 wcd9xxx_read_impedance_regs(struct wcd9xxx_mbhc *mbhc) +{ + struct snd_soc_codec *codec = mbhc->codec; + short bias_value; + int i; + s32 z_t = 0; + s32 z_loop = z_det_box_car_avg; + + /* Box Car avrg of less than a particular loop count will not be + * accomodated. Similarly if the count is more than a particular number + * it will not be counted. Set z_loop counter to a limit, if its more + * or less than the value in WCD9XXX_BOX_CAR_AVRG_MAX or + * WCD9XXX_BOX_CAR_AVRG_MIN + */ + if (z_loop < WCD9XXX_BOX_CAR_AVRG_MIN) { + dev_dbg(codec->dev, + "%s: Box Car avrg counter < %d. Limiting it to %d\n", + __func__, WCD9XXX_BOX_CAR_AVRG_MIN, + WCD9XXX_BOX_CAR_AVRG_MIN); + z_loop = WCD9XXX_BOX_CAR_AVRG_MIN; + } else if (z_loop > WCD9XXX_BOX_CAR_AVRG_MAX) { + dev_dbg(codec->dev, + "%s: Box Car avrg counter > %d. Limiting it to %d\n", + __func__, WCD9XXX_BOX_CAR_AVRG_MAX, + WCD9XXX_BOX_CAR_AVRG_MAX); + z_loop = WCD9XXX_BOX_CAR_AVRG_MAX; + } + + /* Take box car average if needed */ + for (i = 0; i < z_loop; i++) { + snd_soc_write(codec, WCD9XXX_A_CDC_MBHC_EN_CTL, 0x2); + /* Wait for atleast 1800uS to let register write to settle */ + usleep_range(1800, 1800 + WCD9XXX_USLEEP_RANGE_MARGIN_US); + z_t += wcd9xxx_read_sta_result(codec); + } + /* Take average of the Z values read */ + bias_value = (s16) (z_t / z_loop); + return bias_value; +} + +static int wcd9xxx_remeasure_z_values(struct wcd9xxx_mbhc *mbhc, + s16 l[3], s16 r[3], + uint32_t *zl, uint32_t *zr, + u32 *zl_stereo, u32 *zl_mono) +{ + s16 l_t[3] = {0}, r_t[3] = {0}; + s16 l2_stereo, l2_mono; + bool left, right; + struct snd_soc_codec *codec = mbhc->codec; + + if (!mbhc->mbhc_cb || !mbhc->mbhc_cb->setup_zdet || + !mbhc->mbhc_cb->compute_impedance) { + dev_err(codec->dev, "%s: Invalid parameters\n", __func__); + return -EINVAL; + } + + left = !!(l); + right = !!(r); + + dev_dbg(codec->dev, "%s: Remeasuring impedance values\n", __func__); + dev_dbg(codec->dev, "%s: l: %pK, r: %pK, left=%d, right=%d\n", __func__, + l, r, left, right); + + /* Remeasure V2 values */ + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_SCALING_MUX_2, 0xFF, 0xF0); + if (right) + r_t[2] = wcd9xxx_read_impedance_regs(mbhc); + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_SCALING_MUX_1, 0xFF, 0xC0); + if (left) + l_t[2] = wcd9xxx_read_impedance_regs(mbhc); + + /* Ramp down HPHR */ + mbhc->mbhc_cb->setup_zdet(mbhc, MBHC_ZDET_HPHR_RAMP_DISABLE); + + if (right) { + /* Take R0'/R1' */ + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_SCALING_MUX_2, + 0xFF, 0xF8); + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_SCALING_MUX_1, + 0xFF, 0xA0); + r_t[1] = wcd9xxx_read_impedance_regs(mbhc); + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_SCALING_MUX_2, + 0xFF, 0xF0); + r_t[0] = wcd9xxx_read_impedance_regs(mbhc); + } + + /* Put back gain to 1x */ + if (!left && right) + mbhc->mbhc_cb->setup_zdet(mbhc, MBHC_ZDET_GAIN_0); + + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_SCALING_MUX_1, 0xFF, 0xC0); + /* Take L2'' measurement */ + l2_stereo = wcd9xxx_read_impedance_regs(mbhc); + + /* Turn off HPHR PA and take L2''' */ + mbhc->mbhc_cb->setup_zdet(mbhc, MBHC_ZDET_HPHR_PA_DISABLE); + l2_mono = wcd9xxx_read_impedance_regs(mbhc); + + /* Ramp HPHL from -15mV to 0V */ + mbhc->mbhc_cb->setup_zdet(mbhc, MBHC_ZDET_HPHL_RAMP_DISABLE); + + /* Take L0' and L1' with iCal */ + l_t[0] = wcd9xxx_read_impedance_regs(mbhc); + snd_soc_update_bits(codec, WCD9XXX_A_MBHC_SCALING_MUX_2, 0xFF, 0xF8); + l_t[1] = wcd9xxx_read_impedance_regs(mbhc); + + if (left) { + l[0] = l_t[0]; + l[1] = l_t[1]; + l[2] = l_t[2]; + } + if (right) { + r[0] = r_t[0]; + r[1] = r_t[1]; + r[2] = r_t[2]; + } + + /* compute the new impedance values */ + mbhc->mbhc_cb->compute_impedance(mbhc, l, r, zl, zr); + + if (!left && right) + mbhc->mbhc_cb->setup_zdet(mbhc, MBHC_ZDET_GAIN_UPDATE_1X); + /* compute the new ZL'' value */ + l_t[2] = l2_stereo; + mbhc->mbhc_cb->compute_impedance(mbhc, l_t, NULL, zl_stereo, NULL); + /* compute the new ZL''' value */ + l_t[2] = l2_mono; + mbhc->mbhc_cb->compute_impedance(mbhc, l_t, NULL, zl_mono, NULL); + + pr_debug("%s: L0': 0x%x, L1': 0x%x L2_stereo: 0x%x, L2_mono: 0x%x\n", + __func__, l_t[0] & 0xffff, l_t[1] & 0xffff, + l2_stereo & 0xffff, l2_mono & 0xffff); + pr_debug("%s: ZL_stereo = %u, ZL_mono = %u\n", + __func__, *zl_stereo, *zl_mono); + + return 0; +} + +static enum mbhc_zdet_zones wcd9xxx_assign_zdet_zone(uint32_t zl, uint32_t zr, + int32_t *gain) +{ + enum mbhc_zdet_zones zdet_zone; + + if (WCD9XXX_IS_IN_ZDET_ZONE_1(zl) && + WCD9XXX_IS_IN_ZDET_ZONE_1(zr)) { + zdet_zone = ZL_ZONE1__ZR_ZONE1; + *gain = 0; + } else if (WCD9XXX_IS_IN_ZDET_ZONE_2(zl) && + WCD9XXX_IS_IN_ZDET_ZONE_2(zr)) { + zdet_zone = ZL_ZONE2__ZR_ZONE2; + *gain = MBHC_ZDET_GAIN_1; + } else if (WCD9XXX_IS_IN_ZDET_ZONE_3(zl) && + WCD9XXX_IS_IN_ZDET_ZONE_3(zr)) { + zdet_zone = ZL_ZONE3__ZR_ZONE3; + *gain = MBHC_ZDET_GAIN_2; + } else if (WCD9XXX_IS_IN_ZDET_ZONE_2(zl) && + WCD9XXX_IS_IN_ZDET_ZONE_1(zr)) { + zdet_zone = ZL_ZONE2__ZR_ZONE1; + *gain = MBHC_ZDET_GAIN_1; + } else if (WCD9XXX_IS_IN_ZDET_ZONE_3(zl) && + WCD9XXX_IS_IN_ZDET_ZONE_1(zr)) { + zdet_zone = ZL_ZONE3__ZR_ZONE1; + *gain = MBHC_ZDET_GAIN_2; + } else if (WCD9XXX_IS_IN_ZDET_ZONE_1(zl) && + WCD9XXX_IS_IN_ZDET_ZONE_2(zr)) { + zdet_zone = ZL_ZONE1__ZR_ZONE2; + *gain = MBHC_ZDET_GAIN_1; + } else if (WCD9XXX_IS_IN_ZDET_ZONE_1(zl) && + WCD9XXX_IS_IN_ZDET_ZONE_3(zr)) { + zdet_zone = ZL_ZONE1__ZR_ZONE3; + *gain = MBHC_ZDET_GAIN_2; + } else { + zdet_zone = ZL_ZR_NOT_IN_ZONE1; + *gain = MBHC_ZDET_GAIN_1; + } + + return zdet_zone; +} + +static int wcd9xxx_detect_impedance(struct wcd9xxx_mbhc *mbhc, uint32_t *zl, + uint32_t *zr) +{ + int i; + int ret = 0; + u8 micb_mbhc_val; + s16 l[3], r[3]; + s16 *z[] = { + &l[0], &r[0], &r[1], &l[1], &l[2], &r[2], + }; + u32 zl_stereo, zl_mono; + u32 zl_diff_1, zl_diff_2; + bool override_en; + struct snd_soc_codec *codec = mbhc->codec; + const int mux_wait_us = 25; + const struct wcd9xxx_reg_mask_val reg_set_mux[] = { + /* Phase 1 */ + /* Set MBHC_MUX for HPHL without ical */ + {WCD9XXX_A_MBHC_SCALING_MUX_2, 0xFF, 0xF0}, + /* Set MBHC_MUX for HPHR without ical */ + {WCD9XXX_A_MBHC_SCALING_MUX_1, 0xFF, 0xA0}, + /* Set MBHC_MUX for HPHR with ical */ + {WCD9XXX_A_MBHC_SCALING_MUX_2, 0xFF, 0xF8}, + /* Set MBHC_MUX for HPHL with ical */ + {WCD9XXX_A_MBHC_SCALING_MUX_1, 0xFF, 0xC0}, + + /* Phase 2 */ + {WCD9XXX_A_MBHC_SCALING_MUX_2, 0xFF, 0xF0}, + /* Set MBHC_MUX for HPHR without ical and wait for 25us */ + {WCD9XXX_A_MBHC_SCALING_MUX_1, 0xFF, 0xA0}, + }; + + pr_debug("%s: enter\n", __func__); + WCD9XXX_BCL_ASSERT_LOCKED(mbhc->resmgr); + + if (!mbhc->mbhc_cb || !mbhc->mbhc_cb->setup_zdet || + !mbhc->mbhc_cb->compute_impedance || !zl || !zr) { + return -EINVAL; + } + + /* + * Impedance detection is an intrusive function as it mutes RX paths, + * enable PAs and etc. Therefore codec drvier including ALSA + * shouldn't read and write hardware registers during detection. + */ + wcd9xxx_onoff_ext_mclk(mbhc, true); + + /* + * For impedance detection, make sure to disable micbias from + * override signal so that override does not cause micbias + * to be enabled. This setting will be undone after completing + * impedance measurement. + */ + micb_mbhc_val = snd_soc_read(codec, WCD9XXX_A_MAD_ANA_CTRL); + snd_soc_update_bits(codec, WCD9XXX_A_MAD_ANA_CTRL, + 0x10, 0x00); + + override_en = (snd_soc_read(codec, WCD9XXX_A_CDC_MBHC_B1_CTL) & 0x04) ? + true : false; + if (!override_en) + wcd9xxx_turn_onoff_override(mbhc, true); + pr_debug("%s: Setting impedance detection\n", __func__); + + /* Codec specific setup for L0, R0, L1 and R1 measurements */ + mbhc->mbhc_cb->setup_zdet(mbhc, MBHC_ZDET_PRE_MEASURE); + + pr_debug("%s: Performing impedance detection\n", __func__); + for (i = 0; i < ARRAY_SIZE(reg_set_mux) - 2; i++) { + snd_soc_update_bits(codec, reg_set_mux[i].reg, + reg_set_mux[i].mask, + reg_set_mux[i].val); + if (mbhc->mbhc_cb->get_cdc_type && + mbhc->mbhc_cb->get_cdc_type() == + WCD9XXX_CDC_TYPE_TOMTOM) { + *(z[i]) = wcd9xxx_read_impedance_regs(mbhc); + } else { + if (mbhc->mbhc_cb->enable_mux_bias_block) + mbhc->mbhc_cb->enable_mux_bias_block(codec); + else + snd_soc_update_bits(codec, + WCD9XXX_A_MBHC_SCALING_MUX_1, + 0x80, 0x80); + /* 25us is required after mux change to settle down */ + usleep_range(mux_wait_us, + mux_wait_us + WCD9XXX_USLEEP_RANGE_MARGIN_US); + *(z[i]) = __wcd9xxx_codec_sta_dce(mbhc, 0, + true, false); + } + } + + /* Codec specific setup for L2 and R2 measurements */ + mbhc->mbhc_cb->setup_zdet(mbhc, MBHC_ZDET_POST_MEASURE); + + for (; i < ARRAY_SIZE(reg_set_mux); i++) { + snd_soc_update_bits(codec, reg_set_mux[i].reg, + reg_set_mux[i].mask, + reg_set_mux[i].val); + if (mbhc->mbhc_cb->get_cdc_type && + mbhc->mbhc_cb->get_cdc_type() == + WCD9XXX_CDC_TYPE_TOMTOM) { + *(z[i]) = wcd9xxx_read_impedance_regs(mbhc); + } else { + if (mbhc->mbhc_cb->enable_mux_bias_block) + mbhc->mbhc_cb->enable_mux_bias_block(codec); + else + snd_soc_update_bits(codec, + WCD9XXX_A_MBHC_SCALING_MUX_1, + 0x80, 0x80); + /* 25us is required after mux change to settle down */ + usleep_range(mux_wait_us, + mux_wait_us + WCD9XXX_USLEEP_RANGE_MARGIN_US); + *(z[i]) = __wcd9xxx_codec_sta_dce(mbhc, 0, + true, false); + } + } + + mbhc->mbhc_cb->compute_impedance(mbhc, l, r, zl, zr); + + /* + * For some codecs, an additional step of zdet is needed + * to overcome effects of noise and for better accuracy of + * z values + */ + if (mbhc->mbhc_cb->get_cdc_type && + mbhc->mbhc_cb->get_cdc_type() == WCD9XXX_CDC_TYPE_TOMTOM) { + uint32_t zl_t = 0, zr_t = 0; + s16 *l_p, *r_p; + enum mbhc_zdet_zones zdet_zone; + int32_t gain; + + zdet_zone = wcd9xxx_assign_zdet_zone(*zl, *zr, &gain); + switch (zdet_zone) { + case ZL_ZONE1__ZR_ZONE1: + l_p = NULL; + r_p = NULL; + break; + case ZL_ZONE2__ZR_ZONE2: + case ZL_ZONE3__ZR_ZONE3: + case ZL_ZR_NOT_IN_ZONE1: + l_p = l; + r_p = r; + break; + case ZL_ZONE2__ZR_ZONE1: + case ZL_ZONE3__ZR_ZONE1: + /* If ZR falls in Zone 1, further computations with + * gain update are not required + */ + l_p = l; + r_p = NULL; + break; + case ZL_ZONE1__ZR_ZONE2: + case ZL_ZONE1__ZR_ZONE3: + /* If ZL falls in Zone 1, further computations with + * gain update are not required + */ + l_p = NULL; + r_p = r; + break; + } + pr_debug("%s:zdet_zone = %d, gain = %d\n", __func__, + zdet_zone, gain); + if (gain) + mbhc->mbhc_cb->setup_zdet(mbhc, gain); + + wcd9xxx_remeasure_z_values(mbhc, l_p, r_p, &zl_t, &zr_t, + &zl_stereo, &zl_mono); + + *zl = (zl_t) ? zl_t : *zl; + *zr = (zr_t) ? zr_t : *zr; + + /* Check for Mono/Stereo Type + * Conditions to classify Mono/Stereo + * i. Difference of zl_stereo and zl_mono > (1/2) of zl_mono + * ii. Absolute difference of zl and zr above a threshold + */ + zl_diff_1 = (zl_mono > zl_stereo) ? (zl_mono - zl_stereo) : + (zl_stereo - zl_mono); + zl_diff_2 = (*zl > *zr) ? (*zl - *zr) : (*zr - *zl); + + mbhc->hph_type = MBHC_HPH_NONE; + if (mbhc->current_plug != PLUG_TYPE_HIGH_HPH) { + if ((zl_diff_1 > (zl_mono >> 1)) || + (zl_diff_2 > WCD9XXX_MONO_HS_DIFF_THR) || + ((*zl < WCD9XXX_MONO_HS_MIN_THR) && + (*zr > WCD9XXX_MONO_HS_MIN_THR)) || + ((*zr < WCD9XXX_MONO_HS_MIN_THR) && + (*zl > WCD9XXX_MONO_HS_MIN_THR))) { + pr_debug("%s: MONO plug type detected\n", + __func__); + mbhc->hph_type = MBHC_HPH_MONO; + *zl = zl_mono; + } else { + pr_debug("%s: STEREO plug type detected\n", + __func__); + mbhc->hph_type = MBHC_HPH_STEREO; + } + } + } + + mbhc->mbhc_cb->setup_zdet(mbhc, MBHC_ZDET_PA_DISABLE); + + /* Calculate z values based on the Q-fuse registers, if used */ + if (mbhc->mbhc_cb->zdet_error_approx) + mbhc->mbhc_cb->zdet_error_approx(mbhc, zl, zr); + + wcd9xxx_onoff_ext_mclk(mbhc, false); + + if (!override_en) + wcd9xxx_turn_onoff_override(mbhc, false); + + /* Undo the micbias disable for override */ + snd_soc_write(codec, WCD9XXX_A_MAD_ANA_CTRL, micb_mbhc_val); + + pr_debug("%s: L0: 0x%x(%d), L1: 0x%x(%d), L2: 0x%x(%d)\n", + __func__, + l[0] & 0xffff, l[0], l[1] & 0xffff, l[1], l[2] & 0xffff, l[2]); + pr_debug("%s: R0: 0x%x(%d), R1: 0x%x(%d), R2: 0x%x(%d)\n", + __func__, + r[0] & 0xffff, r[0], r[1] & 0xffff, r[1], r[2] & 0xffff, r[2]); + pr_debug("%s: RL %u milliohm, RR %u milliohm\n", __func__, *zl, *zr); + pr_debug("%s: Impedance detection completed\n", __func__); + + return ret; +} + +int wcd9xxx_mbhc_get_impedance(struct wcd9xxx_mbhc *mbhc, uint32_t *zl, + uint32_t *zr) +{ + *zl = mbhc->zl; + *zr = mbhc->zr; + + if (*zl && *zr) + return 0; + else + return -EINVAL; +} + +/* + * wcd9xxx_mbhc_init : initialize MBHC internal structures. + * + * NOTE: mbhc->mbhc_cfg is not YET configure so shouldn't be used + */ +int wcd9xxx_mbhc_init(struct wcd9xxx_mbhc *mbhc, struct wcd9xxx_resmgr *resmgr, + struct snd_soc_codec *codec, + int (*micbias_enable_cb) (struct snd_soc_codec*, bool, + enum wcd9xxx_micbias_num), + const struct wcd9xxx_mbhc_cb *mbhc_cb, + const struct wcd9xxx_mbhc_intr *mbhc_cdc_intr_ids, + int rco_clk_rate, + bool impedance_det_en) +{ + int ret; + void *core_res; + + pr_debug("%s: enter\n", __func__); + memset(&mbhc->mbhc_bias_regs, 0, sizeof(struct mbhc_micbias_regs)); + memset(&mbhc->mbhc_data, 0, sizeof(struct mbhc_internal_cal_data)); + + mbhc->mbhc_data.t_sta_dce = DEFAULT_DCE_STA_WAIT; + mbhc->mbhc_data.t_dce = DEFAULT_DCE_WAIT; + mbhc->mbhc_data.t_sta = DEFAULT_STA_WAIT; + mbhc->mbhc_micbias_switched = false; + mbhc->polling_active = false; + mbhc->mbhc_state = MBHC_STATE_NONE; + mbhc->in_swch_irq_handler = false; + mbhc->current_plug = PLUG_TYPE_NONE; + mbhc->lpi_enabled = false; + mbhc->no_mic_headset_override = false; + mbhc->mbhc_last_resume = 0; + mbhc->codec = codec; + mbhc->resmgr = resmgr; + mbhc->resmgr->mbhc = mbhc; + mbhc->micbias_enable_cb = micbias_enable_cb; + mbhc->rco_clk_rate = rco_clk_rate; + mbhc->mbhc_cb = mbhc_cb; + mbhc->intr_ids = mbhc_cdc_intr_ids; + mbhc->impedance_detect = impedance_det_en; + mbhc->hph_type = MBHC_HPH_NONE; + + if (mbhc->intr_ids == NULL) { + pr_err("%s: Interrupt mapping not provided\n", __func__); + return -EINVAL; + } + + if (mbhc->headset_jack.jack == NULL) { + ret = snd_soc_card_jack_new(codec->component.card, + "Headset Jack", WCD9XXX_JACK_MASK, + &mbhc->headset_jack, NULL, 0); + if (ret) { + pr_err("%s: Failed to create new jack\n", __func__); + return ret; + } + + ret = snd_soc_card_jack_new(codec->component.card, + "Button Jack", + WCD9XXX_JACK_BUTTON_MASK, + &mbhc->button_jack, NULL, 0); + if (ret) { + pr_err("Failed to create new jack\n"); + return ret; + } + + ret = snd_jack_set_key(mbhc->button_jack.jack, + SND_JACK_BTN_0, + KEY_MEDIA); + if (ret) { + pr_err("%s: Failed to set code for btn-0\n", + __func__); + return ret; + } + + set_bit(INPUT_PROP_NO_DUMMY_RELEASE, + mbhc->button_jack.jack->input_dev->propbit); + + INIT_DELAYED_WORK(&mbhc->mbhc_firmware_dwork, + wcd9xxx_mbhc_fw_read); + INIT_DELAYED_WORK(&mbhc->mbhc_btn_dwork, wcd9xxx_btn_lpress_fn); + INIT_DELAYED_WORK(&mbhc->mbhc_insert_dwork, + wcd9xxx_mbhc_insert_work); + } + + mutex_init(&mbhc->mbhc_lock); + + /* Register event notifier */ + mbhc->nblock.notifier_call = wcd9xxx_event_notify; + ret = wcd9xxx_resmgr_register_notifier(mbhc->resmgr, &mbhc->nblock); + if (ret) { + pr_err("%s: Failed to register notifier %d\n", __func__, ret); + mutex_destroy(&mbhc->mbhc_lock); + return ret; + } + + wcd9xxx_init_debugfs(mbhc); + + /* Disable Impedance detection by default for certain codec types */ + if (mbhc->mbhc_cb && mbhc->mbhc_cb->get_cdc_type && + (mbhc->mbhc_cb->get_cdc_type() == WCD9XXX_CDC_TYPE_HELICON)) + impedance_detect_en = 0; + else + impedance_detect_en = impedance_det_en ? 1 : 0; + + core_res = mbhc->resmgr->core_res; + ret = wcd9xxx_request_irq(core_res, mbhc->intr_ids->insertion, + wcd9xxx_hs_insert_irq, + "Headset insert detect", mbhc); + if (ret) { + pr_err("%s: Failed to request irq %d, ret = %d\n", __func__, + mbhc->intr_ids->insertion, ret); + goto err_insert_irq; + } + wcd9xxx_disable_irq(core_res, mbhc->intr_ids->insertion); + + ret = wcd9xxx_request_irq(core_res, mbhc->intr_ids->poll_plug_rem, + wcd9xxx_hs_remove_irq, + "Headset remove detect", mbhc); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + mbhc->intr_ids->poll_plug_rem); + goto err_remove_irq; + } + + ret = wcd9xxx_request_irq(core_res, mbhc->intr_ids->dce_est_complete, + wcd9xxx_dce_handler, "DC Estimation detect", + mbhc); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + mbhc->intr_ids->dce_est_complete); + goto err_potential_irq; + } + + ret = wcd9xxx_request_irq(core_res, mbhc->intr_ids->button_release, + wcd9xxx_release_handler, + "Button Release detect", mbhc); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + mbhc->intr_ids->button_release); + goto err_release_irq; + } + + ret = wcd9xxx_request_irq(core_res, mbhc->intr_ids->hph_left_ocp, + wcd9xxx_hphl_ocp_irq, "HPH_L OCP detect", + mbhc); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + mbhc->intr_ids->hph_left_ocp); + goto err_hphl_ocp_irq; + } + wcd9xxx_disable_irq(core_res, mbhc->intr_ids->hph_left_ocp); + + ret = wcd9xxx_request_irq(core_res, mbhc->intr_ids->hph_right_ocp, + wcd9xxx_hphr_ocp_irq, "HPH_R OCP detect", + mbhc); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + mbhc->intr_ids->hph_right_ocp); + goto err_hphr_ocp_irq; + } + wcd9xxx_disable_irq(core_res, mbhc->intr_ids->hph_right_ocp); + + wcd9xxx_regmgr_cond_register(resmgr, 1 << WCD9XXX_COND_HPH_MIC | + 1 << WCD9XXX_COND_HPH); + + pr_debug("%s: leave ret %d\n", __func__, ret); + return ret; + +err_hphr_ocp_irq: + wcd9xxx_free_irq(core_res, mbhc->intr_ids->hph_left_ocp, mbhc); +err_hphl_ocp_irq: + wcd9xxx_free_irq(core_res, mbhc->intr_ids->button_release, mbhc); +err_release_irq: + wcd9xxx_free_irq(core_res, mbhc->intr_ids->dce_est_complete, mbhc); +err_potential_irq: + wcd9xxx_free_irq(core_res, mbhc->intr_ids->poll_plug_rem, mbhc); +err_remove_irq: + wcd9xxx_free_irq(core_res, mbhc->intr_ids->insertion, mbhc); +err_insert_irq: + wcd9xxx_resmgr_unregister_notifier(mbhc->resmgr, &mbhc->nblock); + + mutex_destroy(&mbhc->mbhc_lock); + + pr_debug("%s: leave ret %d\n", __func__, ret); + return ret; +} +EXPORT_SYMBOL(wcd9xxx_mbhc_init); + +void wcd9xxx_mbhc_deinit(struct wcd9xxx_mbhc *mbhc) +{ + struct wcd9xxx_core_resource *core_res = + mbhc->resmgr->core_res; + + wcd9xxx_regmgr_cond_deregister(mbhc->resmgr, 1 << WCD9XXX_COND_HPH_MIC | + 1 << WCD9XXX_COND_HPH); + + wcd9xxx_free_irq(core_res, mbhc->intr_ids->button_release, mbhc); + wcd9xxx_free_irq(core_res, mbhc->intr_ids->dce_est_complete, mbhc); + wcd9xxx_free_irq(core_res, mbhc->intr_ids->poll_plug_rem, mbhc); + wcd9xxx_free_irq(core_res, mbhc->intr_ids->insertion, mbhc); + wcd9xxx_free_irq(core_res, mbhc->intr_ids->hs_jack_switch, mbhc); + wcd9xxx_free_irq(core_res, mbhc->intr_ids->hph_left_ocp, mbhc); + wcd9xxx_free_irq(core_res, mbhc->intr_ids->hph_right_ocp, mbhc); + + mutex_destroy(&mbhc->mbhc_lock); + wcd9xxx_resmgr_unregister_notifier(mbhc->resmgr, &mbhc->nblock); + wcd9xxx_cleanup_debugfs(mbhc); +} +EXPORT_SYMBOL(wcd9xxx_mbhc_deinit); + +MODULE_DESCRIPTION("wcd9xxx MBHC module"); +MODULE_LICENSE("GPL v2"); |
