From 92fad90ae55abe93a7d8abb7eb11e2f48068fc98 Mon Sep 17 00:00:00 2001 From: Vatsal Bucha Date: Mon, 30 Jul 2018 20:17:41 +0530 Subject: [PATCH] ASoC: Add MBHC and IRQ driver for WCD937X Add MBHC and IRQ driver for tanggu codec. MBHC and IRQ for tanggu are initialized from core driver. IRQ driver uses REGMAP-IRQ framework. CRs-Fixed: 2281591 Change-Id: I06adb3081dd17f896b3e6a3ce0c4c46b5ef1cbea Signed-off-by: Vatsal Bucha --- asoc/codecs/wcd937x/Kbuild | 1 + asoc/codecs/wcd937x/internal.h | 55 +- asoc/codecs/wcd937x/wcd937x-mbhc.c | 1011 ++++++++++++++++++++++++++++ asoc/codecs/wcd937x/wcd937x-mbhc.h | 70 ++ asoc/codecs/wcd937x/wcd937x.c | 248 ++++++- 5 files changed, 1379 insertions(+), 6 deletions(-) create mode 100644 asoc/codecs/wcd937x/wcd937x-mbhc.c create mode 100644 asoc/codecs/wcd937x/wcd937x-mbhc.h diff --git a/asoc/codecs/wcd937x/Kbuild b/asoc/codecs/wcd937x/Kbuild index 20c4d4682b..c3d8e38c38 100644 --- a/asoc/codecs/wcd937x/Kbuild +++ b/asoc/codecs/wcd937x/Kbuild @@ -54,6 +54,7 @@ ifdef CONFIG_SND_SOC_WCD937X WCD937X_OBJS += wcd937x.o WCD937X_OBJS += wcd937x-regmap.o WCD937X_OBJS += wcd937x-tables.o + WCD937X_OBJS += wcd937x-mbhc.o endif ifdef CONFIG_SND_SOC_WCD937X_SLAVE diff --git a/asoc/codecs/wcd937x/internal.h b/asoc/codecs/wcd937x/internal.h index dcb7fd8dd7..e8ac70d9a9 100644 --- a/asoc/codecs/wcd937x/internal.h +++ b/asoc/codecs/wcd937x/internal.h @@ -14,9 +14,14 @@ #define _WCD937X_INTERNAL_H #include "../wcd-mbhc-v2.h" +#include "asoc/wcd-irq.h" +#include "wcd937x-mbhc.h" #define WCD937X_MAX_MICBIAS 3 +/* Convert from vout ctl to micbias voltage in mV */ +#define WCD_VOUT_CTL_TO_MICB(v) (1000 + v * 50) + extern struct regmap_config wcd937x_regmap_config; struct wcd937x_priv { @@ -36,23 +41,69 @@ struct wcd937x_priv { struct fw_info *fw_data; struct device_node *wcd_rst_np; + struct mutex micb_lock; s32 dmic_0_1_clk_cnt; s32 dmic_2_3_clk_cnt; s32 dmic_4_5_clk_cnt; /* mbhc module */ - struct wcd_mbhc mbhc; + struct wcd937x_mbhc *mbhc; struct blocking_notifier_head notifier; - struct mutex micb_lock; u32 hph_mode; + struct irq_domain *virq; + struct wcd_irq_info *irq_info; u32 rx_clk_cnt; + int num_irq_regs; +}; + +struct wcd937x_micbias_setting { + u8 ldoh_v; + u32 cfilt1_mv; + u32 micb2_mv; + u8 bias1_cfilt_sel; }; struct wcd937x_pdata { struct device_node *rst_np; struct device_node *rx_slave; struct device_node *tx_slave; + struct wcd937x_micbias_setting micbias; }; +enum { + /* INTR_CTRL_INT_MASK_0 */ + WCD937X_IRQ_MBHC_BUTTON_RELEASE_DET = 0, + WCD937X_IRQ_MBHC_BUTTON_PRESS_DET, + WCD937X_IRQ_MBHC_ELECT_INS_REM_DET, + WCD937X_IRQ_MBHC_ELECT_INS_REM_LEG_DET, + WCD937X_IRQ_MBHC_SW_DET, + WCD937X_IRQ_HPHR_OCP_INT, + WCD937X_IRQ_HPHR_CNP_INT, + WCD937X_IRQ_HPHL_OCP_INT, + + /* INTR_CTRL_INT_MASK_1 */ + WCD937X_IRQ_HPHL_CNP_INT, + WCD937X_IRQ_EAR_CNP_INT, + WCD937X_IRQ_EAR_SCD_INT, + WCD937X_IRQ_AUX_CNP_INT, + WCD937X_IRQ_AUX_SCD_INT, + WCD937X_IRQ_HPHL_PDM_WD_INT, + WCD937X_IRQ_HPHR_PDM_WD_INT, + WCD937X_IRQ_AUX_PDM_WD_INT, + + /* INTR_CTRL_INT_MASK_2 */ + WCD937X_IRQ_LDORT_SCD_INT, + WCD937X_IRQ_MBHC_MOISTURE_INT, + WCD937X_IRQ_HPHL_SURGE_DET_INT, + WCD937X_IRQ_HPHR_SURGE_DET_INT, + WCD937X_NUM_IRQS, +}; + +extern struct wcd937x_mbhc *wcd937x_soc_get_mbhc(struct snd_soc_codec *codec); +extern int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_codec *codec, + int volt, int micb_num); +extern int wcd937x_get_micb_vout_ctl_val(u32 micb_mv); +extern int wcd937x_micbias_control(struct snd_soc_codec *codec, int micb_num, + int req, bool is_dapm); #endif diff --git a/asoc/codecs/wcd937x/wcd937x-mbhc.c b/asoc/codecs/wcd937x/wcd937x-mbhc.c new file mode 100644 index 0000000000..d1a36ba72d --- /dev/null +++ b/asoc/codecs/wcd937x/wcd937x-mbhc.c @@ -0,0 +1,1011 @@ +/* Copyright (c) 2018, 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wcd937x-registers.h" +#include "../wcdcal-hwdep.h" +#include "../wcd-mbhc-v2-api.h" +#include "internal.h" + +#define WCD937X_ZDET_SUPPORTED true +/* Z value defined in milliohm */ +#define WCD937X_ZDET_VAL_32 32000 +#define WCD937X_ZDET_VAL_400 400000 +#define WCD937X_ZDET_VAL_1200 1200000 +#define WCD937X_ZDET_VAL_100K 100000000 +/* Z floating defined in ohms */ +#define WCD937X_ZDET_FLOATING_IMPEDANCE 0x0FFFFFFE + +#define WCD937X_ZDET_NUM_MEASUREMENTS 900 +#define WCD937X_MBHC_GET_C1(c) ((c & 0xC000) >> 14) +#define WCD937X_MBHC_GET_X1(x) (x & 0x3FFF) +/* Z value compared in milliOhm */ +#define WCD937X_MBHC_IS_SECOND_RAMP_REQUIRED(z) ((z > 400000) || (z < 32000)) +#define WCD937X_MBHC_ZDET_CONST (86 * 16384) +#define WCD937X_MBHC_MOISTURE_RREF R_24_KOHM + +static struct wcd_mbhc_register + wcd_mbhc_registers[WCD_MBHC_REG_FUNC_MAX] = { + WCD_MBHC_REGISTER("WCD_MBHC_L_DET_EN", + WCD937X_ANA_MBHC_MECH, 0x80, 7, 0), + WCD_MBHC_REGISTER("WCD_MBHC_GND_DET_EN", + WCD937X_ANA_MBHC_MECH, 0x40, 6, 0), + WCD_MBHC_REGISTER("WCD_MBHC_MECH_DETECTION_TYPE", + WCD937X_ANA_MBHC_MECH, 0x20, 5, 0), + WCD_MBHC_REGISTER("WCD_MBHC_MIC_CLAMP_CTL", + WCD937X_MBHC_NEW_PLUG_DETECT_CTL, 0x30, 4, 0), + WCD_MBHC_REGISTER("WCD_MBHC_ELECT_DETECTION_TYPE", + WCD937X_ANA_MBHC_ELECT, 0x08, 3, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HS_L_DET_PULL_UP_CTRL", + WCD937X_MBHC_NEW_INT_MECH_DET_CURRENT, 0x1F, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HS_L_DET_PULL_UP_COMP_CTRL", + WCD937X_ANA_MBHC_MECH, 0x04, 2, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HPHL_PLUG_TYPE", + WCD937X_ANA_MBHC_MECH, 0x10, 4, 0), + WCD_MBHC_REGISTER("WCD_MBHC_GND_PLUG_TYPE", + WCD937X_ANA_MBHC_MECH, 0x08, 3, 0), + WCD_MBHC_REGISTER("WCD_MBHC_SW_HPH_LP_100K_TO_GND", + WCD937X_ANA_MBHC_MECH, 0x01, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_ELECT_SCHMT_ISRC", + WCD937X_ANA_MBHC_ELECT, 0x06, 1, 0), + WCD_MBHC_REGISTER("WCD_MBHC_FSM_EN", + WCD937X_ANA_MBHC_ELECT, 0x80, 7, 0), + WCD_MBHC_REGISTER("WCD_MBHC_INSREM_DBNC", + WCD937X_MBHC_NEW_PLUG_DETECT_CTL, 0x0F, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_BTN_DBNC", + WCD937X_MBHC_NEW_CTL_1, 0x03, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HS_VREF", + WCD937X_MBHC_NEW_CTL_2, 0x03, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HS_COMP_RESULT", + WCD937X_ANA_MBHC_RESULT_3, 0x08, 3, 0), + WCD_MBHC_REGISTER("WCD_MBHC_MIC_SCHMT_RESULT", + WCD937X_ANA_MBHC_RESULT_3, 0x20, 5, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HPHL_SCHMT_RESULT", + WCD937X_ANA_MBHC_RESULT_3, 0x80, 7, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HPHR_SCHMT_RESULT", + WCD937X_ANA_MBHC_RESULT_3, 0x40, 6, 0), + WCD_MBHC_REGISTER("WCD_MBHC_OCP_FSM_EN", + WCD937X_HPH_OCP_CTL, 0x10, 4, 0), + WCD_MBHC_REGISTER("WCD_MBHC_BTN_RESULT", + WCD937X_ANA_MBHC_RESULT_3, 0x07, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_BTN_ISRC_CTL", + WCD937X_ANA_MBHC_ELECT, 0x70, 4, 0), + WCD_MBHC_REGISTER("WCD_MBHC_ELECT_RESULT", + WCD937X_ANA_MBHC_RESULT_3, 0xFF, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_MICB_CTRL", + WCD937X_ANA_MICB2, 0xC0, 6, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HPH_CNP_WG_TIME", + WCD937X_HPH_CNP_WG_TIME, 0xFF, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HPHR_PA_EN", + WCD937X_ANA_HPH, 0x40, 6, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HPHL_PA_EN", + WCD937X_ANA_HPH, 0x80, 7, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HPH_PA_EN", + WCD937X_ANA_HPH, 0xC0, 6, 0), + WCD_MBHC_REGISTER("WCD_MBHC_SWCH_LEVEL_REMOVE", + WCD937X_ANA_MBHC_RESULT_3, 0x10, 4, 0), + WCD_MBHC_REGISTER("WCD_MBHC_PULLDOWN_CTRL", + 0, 0, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_ANC_DET_EN", + WCD937X_MBHC_CTL_BCS, 0x02, 1, 0), + WCD_MBHC_REGISTER("WCD_MBHC_FSM_STATUS", + WCD937X_MBHC_NEW_FSM_STATUS, 0x01, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_MUX_CTL", + WCD937X_MBHC_NEW_CTL_2, 0x70, 4, 0), + WCD_MBHC_REGISTER("WCD_MBHC_MOISTURE_STATUS", + WCD937X_MBHC_NEW_FSM_STATUS, 0x20, 5, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HPHR_GND", + WCD937X_HPH_PA_CTL2, 0x40, 6, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HPHL_GND", + WCD937X_HPH_PA_CTL2, 0x10, 4, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HPHL_OCP_DET_EN", + WCD937X_HPH_L_TEST, 0x01, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HPHR_OCP_DET_EN", + WCD937X_HPH_R_TEST, 0x01, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HPHL_OCP_STATUS", + WCD937X_DIGITAL_INTR_STATUS_0, 0x80, 7, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HPHR_OCP_STATUS", + WCD937X_DIGITAL_INTR_STATUS_0, 0x20, 5, 0), + WCD_MBHC_REGISTER("WCD_MBHC_ADC_EN", + WCD937X_MBHC_NEW_CTL_1, 0x08, 3, 0), + WCD_MBHC_REGISTER("WCD_MBHC_ADC_COMPLETE", WCD937X_MBHC_NEW_FSM_STATUS, + 0x40, 6, 0), + WCD_MBHC_REGISTER("WCD_MBHC_ADC_TIMEOUT", WCD937X_MBHC_NEW_FSM_STATUS, + 0x80, 7, 0), + WCD_MBHC_REGISTER("WCD_MBHC_ADC_RESULT", WCD937X_MBHC_NEW_ADC_RESULT, + 0xFF, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_MICB2_VOUT", WCD937X_ANA_MICB1, 0x3F, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_ADC_MODE", + WCD937X_MBHC_NEW_CTL_1, 0x10, 4, 0), + WCD_MBHC_REGISTER("WCD_MBHC_DETECTION_DONE", + WCD937X_MBHC_NEW_CTL_1, 0x04, 2, 0), + WCD_MBHC_REGISTER("WCD_MBHC_ELECT_ISRC_EN", + WCD937X_ANA_MBHC_ZDET, 0x02, 1, 0), +}; + +static const struct wcd_mbhc_intr intr_ids = { + .mbhc_sw_intr = WCD937X_IRQ_MBHC_SW_DET, + .mbhc_btn_press_intr = WCD937X_IRQ_MBHC_BUTTON_PRESS_DET, + .mbhc_btn_release_intr = WCD937X_IRQ_MBHC_BUTTON_RELEASE_DET, + .mbhc_hs_ins_intr = WCD937X_IRQ_MBHC_ELECT_INS_REM_LEG_DET, + .mbhc_hs_rem_intr = WCD937X_IRQ_MBHC_ELECT_INS_REM_DET, + .hph_left_ocp = WCD937X_IRQ_HPHL_OCP_INT, + .hph_right_ocp = WCD937X_IRQ_HPHR_OCP_INT, +}; + +struct wcd937x_mbhc_zdet_param { + u16 ldo_ctl; + u16 noff; + u16 nshift; + u16 btn5; + u16 btn6; + u16 btn7; +}; + +static int wcd937x_mbhc_request_irq(struct snd_soc_codec *codec, + int irq, irq_handler_t handler, + const char *name, void *data) +{ + struct wcd937x_priv *wcd937x = dev_get_drvdata(codec->dev); + + return wcd_request_irq(wcd937x->irq_info, irq, name, handler, data); +} + +static void wcd937x_mbhc_irq_control(struct snd_soc_codec *codec, + int irq, bool enable) +{ + struct wcd937x_priv *wcd937x = dev_get_drvdata(codec->dev); + + if (enable) + wcd_enable_irq(wcd937x->irq_info, irq); + else + wcd_disable_irq(wcd937x->irq_info, irq); +} + +static int wcd937x_mbhc_free_irq(struct snd_soc_codec *codec, + int irq, void *data) +{ + struct wcd937x_priv *wcd937x = dev_get_drvdata(codec->dev); + + wcd_free_irq(wcd937x->irq_info, irq, data); + + return 0; +} + +static void wcd937x_mbhc_clk_setup(struct snd_soc_codec *codec, + bool enable) +{ + if (enable) + snd_soc_update_bits(codec, WCD937X_MBHC_NEW_CTL_1, + 0x80, 0x80); + else + snd_soc_update_bits(codec, WCD937X_MBHC_NEW_CTL_1, + 0x80, 0x00); +} + +static int wcd937x_mbhc_btn_to_num(struct snd_soc_codec *codec) +{ + return snd_soc_read(codec, WCD937X_ANA_MBHC_RESULT_3) & 0x7; +} + +static void wcd937x_mbhc_mbhc_bias_control(struct snd_soc_codec *codec, + bool enable) +{ + if (enable) + snd_soc_update_bits(codec, WCD937X_ANA_MBHC_ELECT, + 0x01, 0x01); + else + snd_soc_update_bits(codec, WCD937X_ANA_MBHC_ELECT, + 0x01, 0x00); +} + +static void wcd937x_mbhc_program_btn_thr(struct snd_soc_codec *codec, + s16 *btn_low, s16 *btn_high, + int num_btn, bool is_micbias) +{ + int i; + int vth; + + if (num_btn > WCD_MBHC_DEF_BUTTONS) { + dev_err(codec->dev, "%s: invalid number of buttons: %d\n", + __func__, num_btn); + return; + } + + for (i = 0; i < num_btn; i++) { + vth = ((btn_high[i] * 2) / 25) & 0x3F; + snd_soc_update_bits(codec, WCD937X_ANA_MBHC_BTN0 + i, + 0xFC, vth << 2); + dev_dbg(codec->dev, "%s: btn_high[%d]: %d, vth: %d\n", + __func__, i, btn_high[i], vth); + } +} + +static bool wcd937x_mbhc_lock_sleep(struct wcd_mbhc *mbhc, bool lock) +{ + return true; +} + +static int wcd937x_mbhc_register_notifier(struct wcd_mbhc *mbhc, + struct notifier_block *nblock, + bool enable) +{ + struct wcd937x_mbhc *wcd937x_mbhc; + + wcd937x_mbhc = container_of(mbhc, struct wcd937x_mbhc, wcd_mbhc); + + if (enable) + return blocking_notifier_chain_register(&wcd937x_mbhc->notifier, + nblock); + else + return blocking_notifier_chain_unregister( + &wcd937x_mbhc->notifier, nblock); +} + +static bool wcd937x_mbhc_micb_en_status(struct wcd_mbhc *mbhc, int micb_num) +{ + u8 val = 0; + + if (micb_num == MIC_BIAS_2) { + val = ((snd_soc_read(mbhc->codec, WCD937X_ANA_MICB2) & 0xC0) + >> 6); + if (val == 0x01) + return true; + } + return false; +} + +static bool wcd937x_mbhc_hph_pa_on_status(struct snd_soc_codec *codec) +{ + return (snd_soc_read(codec, WCD937X_ANA_HPH) & 0xC0) ? true : false; +} + +static void wcd937x_mbhc_hph_l_pull_up_control(struct snd_soc_codec *codec, + int pull_up_cur) +{ + /* Default pull up current to 2uA */ + if (pull_up_cur > HS_PULLUP_I_OFF || pull_up_cur < HS_PULLUP_I_3P0_UA || + pull_up_cur == HS_PULLUP_I_DEFAULT) + pull_up_cur = HS_PULLUP_I_2P0_UA; + + dev_dbg(codec->dev, "%s: HS pull up current:%d\n", + __func__, pull_up_cur); + + snd_soc_update_bits(codec, WCD937X_MBHC_NEW_INT_MECH_DET_CURRENT, + 0x1F, pull_up_cur); +} + +static int wcd937x_mbhc_request_micbias(struct snd_soc_codec *codec, + int micb_num, int req) +{ + int ret = 0; + + ret = wcd937x_micbias_control(codec, micb_num, req, false); + + return ret; +} + +static void wcd937x_mbhc_micb_ramp_control(struct snd_soc_codec *codec, + bool enable) +{ + if (enable) { + snd_soc_update_bits(codec, WCD937X_ANA_MICB2_RAMP, + 0x1C, 0x0C); + snd_soc_update_bits(codec, WCD937X_ANA_MICB2_RAMP, + 0x80, 0x80); + } else { + snd_soc_update_bits(codec, WCD937X_ANA_MICB2_RAMP, + 0x80, 0x00); + snd_soc_update_bits(codec, WCD937X_ANA_MICB2_RAMP, + 0x1C, 0x00); + } +} + +static struct firmware_cal *wcd937x_get_hwdep_fw_cal(struct wcd_mbhc *mbhc, + enum wcd_cal_type type) +{ + struct wcd937x_mbhc *wcd937x_mbhc; + struct firmware_cal *hwdep_cal; + struct snd_soc_codec *codec = mbhc->codec; + + wcd937x_mbhc = container_of(mbhc, struct wcd937x_mbhc, wcd_mbhc); + + if (!codec) { + pr_err("%s: NULL codec pointer\n", __func__); + return NULL; + } + hwdep_cal = wcdcal_get_fw_cal(wcd937x_mbhc->fw_data, type); + if (!hwdep_cal) + dev_err(codec->dev, "%s: cal not sent by %d\n", + __func__, type); + + return hwdep_cal; +} + +static int wcd937x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_codec *codec, + int micb_num, bool req_en) +{ + struct wcd937x_pdata *pdata = dev_get_platdata(codec->dev); + int rc, micb_mv; + + if (micb_num != MIC_BIAS_2) + return -EINVAL; + /* + * If device tree micbias level is already above the minimum + * voltage needed to detect threshold microphone, then do + * not change the micbias, just return. + */ + if (pdata->micbias.micb2_mv >= WCD_MBHC_THR_HS_MICB_MV) + return 0; + + micb_mv = req_en ? WCD_MBHC_THR_HS_MICB_MV : pdata->micbias.micb2_mv; + + rc = wcd937x_mbhc_micb_adjust_voltage(codec, micb_mv, MIC_BIAS_2); + + return rc; +} + +static inline void wcd937x_mbhc_get_result_params(struct wcd937x_priv *wcd937x, + s16 *d1_a, u16 noff, + int32_t *zdet) +{ + int i; + int val, val1; + s16 c1; + s32 x1, d1; + int32_t denom; + int minCode_param[] = { + 3277, 1639, 820, 410, 205, 103, 52, 26 + }; + + regmap_update_bits(wcd937x->regmap, WCD937X_ANA_MBHC_ZDET, 0x20, 0x20); + for (i = 0; i < WCD937X_ZDET_NUM_MEASUREMENTS; i++) { + regmap_read(wcd937x->regmap, WCD937X_ANA_MBHC_RESULT_2, &val); + if (val & 0x80) + break; + } + val = val << 0x8; + regmap_read(wcd937x->regmap, WCD937X_ANA_MBHC_RESULT_1, &val1); + val |= val1; + regmap_update_bits(wcd937x->regmap, WCD937X_ANA_MBHC_ZDET, 0x20, 0x00); + x1 = WCD937X_MBHC_GET_X1(val); + c1 = WCD937X_MBHC_GET_C1(val); + /* If ramp is not complete, give additional 5ms */ + if ((c1 < 2) && x1) + usleep_range(5000, 5050); + + if (!c1 || !x1) { + dev_dbg(wcd937x->dev, + "%s: Impedance detect ramp error, c1=%d, x1=0x%x\n", + __func__, c1, x1); + goto ramp_down; + } + d1 = d1_a[c1]; + denom = (x1 * d1) - (1 << (14 - noff)); + if (denom > 0) + *zdet = (WCD937X_MBHC_ZDET_CONST * 1000) / denom; + else if (x1 < minCode_param[noff]) + *zdet = WCD937X_ZDET_FLOATING_IMPEDANCE; + + dev_dbg(wcd937x->dev, "%s: d1=%d, c1=%d, x1=0x%x, z_val=%d(milliOhm)\n", + __func__, d1, c1, x1, *zdet); +ramp_down: + i = 0; + while (x1) { + regmap_bulk_read(wcd937x->regmap, + WCD937X_ANA_MBHC_RESULT_1, (u8 *)&val, 2); + x1 = WCD937X_MBHC_GET_X1(val); + i++; + if (i == WCD937X_ZDET_NUM_MEASUREMENTS) + break; + } +} + +static void wcd937x_mbhc_zdet_ramp(struct snd_soc_codec *codec, + struct wcd937x_mbhc_zdet_param *zdet_param, + int32_t *zl, int32_t *zr, s16 *d1_a) +{ + struct wcd937x_priv *wcd937x = dev_get_drvdata(codec->dev); + int32_t zdet = 0; + + snd_soc_update_bits(codec, WCD937X_MBHC_NEW_ZDET_ANA_CTL, 0x70, + zdet_param->ldo_ctl << 4); + snd_soc_update_bits(codec, WCD937X_ANA_MBHC_BTN5, 0xFC, + zdet_param->btn5); + snd_soc_update_bits(codec, WCD937X_ANA_MBHC_BTN6, 0xFC, + zdet_param->btn6); + snd_soc_update_bits(codec, WCD937X_ANA_MBHC_BTN7, 0xFC, + zdet_param->btn7); + snd_soc_update_bits(codec, WCD937X_MBHC_NEW_ZDET_ANA_CTL, 0x0F, + zdet_param->noff); + snd_soc_update_bits(codec, WCD937X_MBHC_NEW_ZDET_RAMP_CTL, 0x0F, + zdet_param->nshift); + + if (!zl) + goto z_right; + /* Start impedance measurement for HPH_L */ + regmap_update_bits(wcd937x->regmap, + WCD937X_ANA_MBHC_ZDET, 0x80, 0x80); + dev_dbg(wcd937x->dev, "%s: ramp for HPH_L, noff = %d\n", + __func__, zdet_param->noff); + wcd937x_mbhc_get_result_params(wcd937x, d1_a, zdet_param->noff, &zdet); + regmap_update_bits(wcd937x->regmap, + WCD937X_ANA_MBHC_ZDET, 0x80, 0x00); + + *zl = zdet; + +z_right: + if (!zr) + return; + /* Start impedance measurement for HPH_R */ + regmap_update_bits(wcd937x->regmap, + WCD937X_ANA_MBHC_ZDET, 0x40, 0x40); + dev_dbg(wcd937x->dev, "%s: ramp for HPH_R, noff = %d\n", + __func__, zdet_param->noff); + wcd937x_mbhc_get_result_params(wcd937x, d1_a, zdet_param->noff, &zdet); + regmap_update_bits(wcd937x->regmap, + WCD937X_ANA_MBHC_ZDET, 0x40, 0x00); + + *zr = zdet; +} + +static inline void wcd937x_wcd_mbhc_qfuse_cal(struct snd_soc_codec *codec, + int32_t *z_val, int flag_l_r) +{ + s16 q1; + int q1_cal; + + if (*z_val < (WCD937X_ZDET_VAL_400/1000)) + q1 = snd_soc_read(codec, + WCD937X_DIGITAL_EFUSE_REG_23 + (2 * flag_l_r)); + else + q1 = snd_soc_read(codec, + WCD937X_DIGITAL_EFUSE_REG_24 + (2 * flag_l_r)); + if (q1 & 0x80) + q1_cal = (10000 - ((q1 & 0x7F) * 25)); + else + q1_cal = (10000 + (q1 * 25)); + if (q1_cal > 0) + *z_val = ((*z_val) * 10000) / q1_cal; +} + +static void wcd937x_wcd_mbhc_calc_impedance(struct wcd_mbhc *mbhc, uint32_t *zl, + uint32_t *zr) +{ + struct snd_soc_codec *codec = mbhc->codec; + struct wcd937x_priv *wcd937x = dev_get_drvdata(codec->dev); + s16 reg0, reg1, reg2, reg3, reg4; + int32_t z1L, z1R, z1Ls; + int zMono, z_diff1, z_diff2; + bool is_fsm_disable = false; + struct wcd937x_mbhc_zdet_param zdet_param[] = { + {4, 0, 4, 0x08, 0x14, 0x18}, /* < 32ohm */ + {2, 0, 3, 0x18, 0x7C, 0x90}, /* 32ohm < Z < 400ohm */ + {1, 4, 5, 0x18, 0x7C, 0x90}, /* 400ohm < Z < 1200ohm */ + {1, 6, 7, 0x18, 0x7C, 0x90}, /* >1200ohm */ + }; + struct wcd937x_mbhc_zdet_param *zdet_param_ptr = NULL; + s16 d1_a[][4] = { + {0, 30, 90, 30}, + {0, 30, 30, 5}, + {0, 30, 30, 5}, + {0, 30, 30, 5}, + }; + s16 *d1 = NULL; + + WCD_MBHC_RSC_ASSERT_LOCKED(mbhc); + + reg0 = snd_soc_read(codec, WCD937X_ANA_MBHC_BTN5); + reg1 = snd_soc_read(codec, WCD937X_ANA_MBHC_BTN6); + reg2 = snd_soc_read(codec, WCD937X_ANA_MBHC_BTN7); + reg3 = snd_soc_read(codec, WCD937X_MBHC_CTL_CLK); + reg4 = snd_soc_read(codec, WCD937X_MBHC_NEW_ZDET_ANA_CTL); + + if (snd_soc_read(codec, WCD937X_ANA_MBHC_ELECT) & 0x80) { + is_fsm_disable = true; + regmap_update_bits(wcd937x->regmap, + WCD937X_ANA_MBHC_ELECT, 0x80, 0x00); + } + + /* For NO-jack, disable L_DET_EN before Z-det measurements */ + if (mbhc->hphl_swh) + regmap_update_bits(wcd937x->regmap, + WCD937X_ANA_MBHC_MECH, 0x80, 0x00); + + /* Turn off 100k pull down on HPHL */ + regmap_update_bits(wcd937x->regmap, + WCD937X_ANA_MBHC_MECH, 0x01, 0x00); + + /* First get impedance on Left */ + d1 = d1_a[1]; + zdet_param_ptr = &zdet_param[1]; + wcd937x_mbhc_zdet_ramp(codec, zdet_param_ptr, &z1L, NULL, d1); + + if (!WCD937X_MBHC_IS_SECOND_RAMP_REQUIRED(z1L)) + goto left_ch_impedance; + + /* Second ramp for left ch */ + if (z1L < WCD937X_ZDET_VAL_32) { + zdet_param_ptr = &zdet_param[0]; + d1 = d1_a[0]; + } else if ((z1L > WCD937X_ZDET_VAL_400) && + (z1L <= WCD937X_ZDET_VAL_1200)) { + zdet_param_ptr = &zdet_param[2]; + d1 = d1_a[2]; + } else if (z1L > WCD937X_ZDET_VAL_1200) { + zdet_param_ptr = &zdet_param[3]; + d1 = d1_a[3]; + } + wcd937x_mbhc_zdet_ramp(codec, zdet_param_ptr, &z1L, NULL, d1); + +left_ch_impedance: + if ((z1L == WCD937X_ZDET_FLOATING_IMPEDANCE) || + (z1L > WCD937X_ZDET_VAL_100K)) { + *zl = WCD937X_ZDET_FLOATING_IMPEDANCE; + zdet_param_ptr = &zdet_param[1]; + d1 = d1_a[1]; + } else { + *zl = z1L/1000; + wcd937x_wcd_mbhc_qfuse_cal(codec, zl, 0); + } + dev_dbg(codec->dev, "%s: impedance on HPH_L = %d(ohms)\n", + __func__, *zl); + + /* Start of right impedance ramp and calculation */ + wcd937x_mbhc_zdet_ramp(codec, zdet_param_ptr, NULL, &z1R, d1); + if (WCD937X_MBHC_IS_SECOND_RAMP_REQUIRED(z1R)) { + if (((z1R > WCD937X_ZDET_VAL_1200) && + (zdet_param_ptr->noff == 0x6)) || + ((*zl) != WCD937X_ZDET_FLOATING_IMPEDANCE)) + goto right_ch_impedance; + /* Second ramp for right ch */ + if (z1R < WCD937X_ZDET_VAL_32) { + zdet_param_ptr = &zdet_param[0]; + d1 = d1_a[0]; + } else if ((z1R > WCD937X_ZDET_VAL_400) && + (z1R <= WCD937X_ZDET_VAL_1200)) { + zdet_param_ptr = &zdet_param[2]; + d1 = d1_a[2]; + } else if (z1R > WCD937X_ZDET_VAL_1200) { + zdet_param_ptr = &zdet_param[3]; + d1 = d1_a[3]; + } + wcd937x_mbhc_zdet_ramp(codec, zdet_param_ptr, NULL, &z1R, d1); + } +right_ch_impedance: + if ((z1R == WCD937X_ZDET_FLOATING_IMPEDANCE) || + (z1R > WCD937X_ZDET_VAL_100K)) { + *zr = WCD937X_ZDET_FLOATING_IMPEDANCE; + } else { + *zr = z1R/1000; + wcd937x_wcd_mbhc_qfuse_cal(codec, zr, 1); + } + dev_dbg(codec->dev, "%s: impedance on HPH_R = %d(ohms)\n", + __func__, *zr); + + /* Mono/stereo detection */ + if ((*zl == WCD937X_ZDET_FLOATING_IMPEDANCE) && + (*zr == WCD937X_ZDET_FLOATING_IMPEDANCE)) { + dev_dbg(codec->dev, + "%s: plug type is invalid or extension cable\n", + __func__); + goto zdet_complete; + } + if ((*zl == WCD937X_ZDET_FLOATING_IMPEDANCE) || + (*zr == WCD937X_ZDET_FLOATING_IMPEDANCE) || + ((*zl < WCD_MONO_HS_MIN_THR) && (*zr > WCD_MONO_HS_MIN_THR)) || + ((*zl > WCD_MONO_HS_MIN_THR) && (*zr < WCD_MONO_HS_MIN_THR))) { + dev_dbg(codec->dev, + "%s: Mono plug type with one ch floating or shorted to GND\n", + __func__); + mbhc->hph_type = WCD_MBHC_HPH_MONO; + goto zdet_complete; + } + snd_soc_update_bits(codec, WCD937X_HPH_R_ATEST, 0x02, 0x02); + snd_soc_update_bits(codec, WCD937X_HPH_PA_CTL2, 0x40, 0x01); + if (*zl < (WCD937X_ZDET_VAL_32/1000)) + wcd937x_mbhc_zdet_ramp(codec, &zdet_param[0], &z1Ls, NULL, d1); + else + wcd937x_mbhc_zdet_ramp(codec, &zdet_param[1], &z1Ls, NULL, d1); + snd_soc_update_bits(codec, WCD937X_HPH_PA_CTL2, 0x40, 0x00); + snd_soc_update_bits(codec, WCD937X_HPH_R_ATEST, 0x02, 0x00); + z1Ls /= 1000; + wcd937x_wcd_mbhc_qfuse_cal(codec, &z1Ls, 0); + /* Parallel of left Z and 9 ohm pull down resistor */ + zMono = ((*zl) * 9) / ((*zl) + 9); + z_diff1 = (z1Ls > zMono) ? (z1Ls - zMono) : (zMono - z1Ls); + z_diff2 = ((*zl) > z1Ls) ? ((*zl) - z1Ls) : (z1Ls - (*zl)); + if ((z_diff1 * (*zl + z1Ls)) > (z_diff2 * (z1Ls + zMono))) { + dev_dbg(codec->dev, "%s: stereo plug type detected\n", + __func__); + mbhc->hph_type = WCD_MBHC_HPH_STEREO; + } else { + dev_dbg(codec->dev, "%s: MONO plug type detected\n", + __func__); + mbhc->hph_type = WCD_MBHC_HPH_MONO; + } + +zdet_complete: + snd_soc_write(codec, WCD937X_ANA_MBHC_BTN5, reg0); + snd_soc_write(codec, WCD937X_ANA_MBHC_BTN6, reg1); + snd_soc_write(codec, WCD937X_ANA_MBHC_BTN7, reg2); + /* Turn on 100k pull down on HPHL */ + regmap_update_bits(wcd937x->regmap, + WCD937X_ANA_MBHC_MECH, 0x01, 0x01); + + /* For NO-jack, re-enable L_DET_EN after Z-det measurements */ + if (mbhc->hphl_swh) + regmap_update_bits(wcd937x->regmap, + WCD937X_ANA_MBHC_MECH, 0x80, 0x80); + + snd_soc_write(codec, WCD937X_MBHC_NEW_ZDET_ANA_CTL, reg4); + snd_soc_write(codec, WCD937X_MBHC_CTL_CLK, reg3); + if (is_fsm_disable) + regmap_update_bits(wcd937x->regmap, + WCD937X_ANA_MBHC_ELECT, 0x80, 0x80); +} + +static void wcd937x_mbhc_gnd_det_ctrl(struct snd_soc_codec *codec, bool enable) +{ + if (enable) { + snd_soc_update_bits(codec, WCD937X_ANA_MBHC_MECH, + 0x02, 0x02); + snd_soc_update_bits(codec, WCD937X_ANA_MBHC_MECH, + 0x40, 0x40); + } else { + snd_soc_update_bits(codec, WCD937X_ANA_MBHC_MECH, + 0x40, 0x00); + snd_soc_update_bits(codec, WCD937X_ANA_MBHC_MECH, + 0x02, 0x00); + } +} + +static void wcd937x_mbhc_hph_pull_down_ctrl(struct snd_soc_codec *codec, + bool enable) +{ + if (enable) { + snd_soc_update_bits(codec, WCD937X_HPH_PA_CTL2, + 0x40, 0x40); + snd_soc_update_bits(codec, WCD937X_HPH_PA_CTL2, + 0x10, 0x10); + } else { + snd_soc_update_bits(codec, WCD937X_HPH_PA_CTL2, + 0x40, 0x00); + snd_soc_update_bits(codec, WCD937X_HPH_PA_CTL2, + 0x10, 0x00); + } +} + +static void wcd937x_mbhc_moisture_config(struct wcd_mbhc *mbhc) +{ + struct snd_soc_codec *codec = mbhc->codec; + + if ((mbhc->moist_rref == R_OFF) || + (mbhc->mbhc_cfg->enable_usbc_analog)) { + snd_soc_update_bits(codec, WCD937X_MBHC_NEW_CTL_2, + 0x0C, R_OFF << 2); + return; + } + + /* Do not enable moisture detection if jack type is NC */ + if (!mbhc->hphl_swh) { + dev_dbg(codec->dev, "%s: disable moisture detection for NC\n", + __func__); + snd_soc_update_bits(codec, WCD937X_MBHC_NEW_CTL_2, + 0x0C, R_OFF << 2); + return; + } + + snd_soc_update_bits(codec, WCD937X_MBHC_NEW_CTL_2, + 0x0C, mbhc->moist_rref << 2); +} + +static const struct wcd_mbhc_cb mbhc_cb = { + .request_irq = wcd937x_mbhc_request_irq, + .irq_control = wcd937x_mbhc_irq_control, + .free_irq = wcd937x_mbhc_free_irq, + .clk_setup = wcd937x_mbhc_clk_setup, + .map_btn_code_to_num = wcd937x_mbhc_btn_to_num, + .mbhc_bias = wcd937x_mbhc_mbhc_bias_control, + .set_btn_thr = wcd937x_mbhc_program_btn_thr, + .lock_sleep = wcd937x_mbhc_lock_sleep, + .register_notifier = wcd937x_mbhc_register_notifier, + .micbias_enable_status = wcd937x_mbhc_micb_en_status, + .hph_pa_on_status = wcd937x_mbhc_hph_pa_on_status, + .hph_pull_up_control_v2 = wcd937x_mbhc_hph_l_pull_up_control, + .mbhc_micbias_control = wcd937x_mbhc_request_micbias, + .mbhc_micb_ramp_control = wcd937x_mbhc_micb_ramp_control, + .get_hwdep_fw_cal = wcd937x_get_hwdep_fw_cal, + .mbhc_micb_ctrl_thr_mic = wcd937x_mbhc_micb_ctrl_threshold_mic, + .compute_impedance = wcd937x_wcd_mbhc_calc_impedance, + .mbhc_gnd_det_ctrl = wcd937x_mbhc_gnd_det_ctrl, + .hph_pull_down_ctrl = wcd937x_mbhc_hph_pull_down_ctrl, + .mbhc_moisture_config = wcd937x_mbhc_moisture_config, +}; + +static int wcd937x_get_hph_type(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct wcd937x_mbhc *wcd937x_mbhc = wcd937x_soc_get_mbhc(codec); + struct wcd_mbhc *mbhc; + + if (!wcd937x_mbhc) { + dev_err(codec->dev, "%s: mbhc not initialized!\n", __func__); + return -EINVAL; + } + + mbhc = &wcd937x_mbhc->wcd_mbhc; + + ucontrol->value.integer.value[0] = (u32) mbhc->hph_type; + dev_dbg(codec->dev, "%s: hph_type = %u\n", __func__, mbhc->hph_type); + + return 0; +} + +static int wcd937x_hph_impedance_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + uint32_t zl, zr; + bool hphr; + struct soc_multi_mixer_control *mc; + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct wcd937x_mbhc *wcd937x_mbhc = wcd937x_soc_get_mbhc(codec); + + if (!wcd937x_mbhc) { + dev_err(codec->dev, "%s: mbhc not initialized!\n", __func__); + return -EINVAL; + } + + mc = (struct soc_multi_mixer_control *)(kcontrol->private_value); + hphr = mc->shift; + wcd_mbhc_get_impedance(&wcd937x_mbhc->wcd_mbhc, &zl, &zr); + dev_dbg(codec->dev, "%s: zl=%u(ohms), zr=%u(ohms)\n", __func__, zl, zr); + ucontrol->value.integer.value[0] = hphr ? zr : zl; + + return 0; +} + +static const struct snd_kcontrol_new hph_type_detect_controls[] = { + SOC_SINGLE_EXT("HPH Type", 0, 0, UINT_MAX, 0, + wcd937x_get_hph_type, NULL), +}; + +static const struct snd_kcontrol_new impedance_detect_controls[] = { + SOC_SINGLE_EXT("HPHL Impedance", 0, 0, UINT_MAX, 0, + wcd937x_hph_impedance_get, NULL), + SOC_SINGLE_EXT("HPHR Impedance", 0, 1, UINT_MAX, 0, + wcd937x_hph_impedance_get, NULL), +}; + +/* + * wcd937x_mbhc_get_impedance: get impedance of headphone + * left and right channels + * @wcd937x_mbhc: handle to struct wcd937x_mbhc * + * @zl: handle to left-ch impedance + * @zr: handle to right-ch impedance + * return 0 for success or error code in case of failure + */ +int wcd937x_mbhc_get_impedance(struct wcd937x_mbhc *wcd937x_mbhc, + uint32_t *zl, uint32_t *zr) +{ + if (!wcd937x_mbhc) { + pr_err("%s: mbhc not initialized!\n", __func__); + return -EINVAL; + } + if (!zl || !zr) { + pr_err("%s: zl or zr null!\n", __func__); + return -EINVAL; + } + + return wcd_mbhc_get_impedance(&wcd937x_mbhc->wcd_mbhc, zl, zr); +} +EXPORT_SYMBOL(wcd937x_mbhc_get_impedance); + +/* + * wcd937x_mbhc_hs_detect: starts mbhc insertion/removal functionality + * @codec: handle to snd_soc_codec * + * @mbhc_cfg: handle to mbhc configuration structure + * return 0 if mbhc_start is success or error code in case of failure + */ +int wcd937x_mbhc_hs_detect(struct snd_soc_codec *codec, + struct wcd_mbhc_config *mbhc_cfg) +{ + struct wcd937x_priv *wcd937x = NULL; + struct wcd937x_mbhc *wcd937x_mbhc = NULL; + + if (!codec) { + pr_err("%s: codec is NULL\n", __func__); + return -EINVAL; + } + + wcd937x = snd_soc_codec_get_drvdata(codec); + if (!wcd937x) { + pr_err("%s: wcd937x is NULL\n", __func__); + return -EINVAL; + } + + wcd937x_mbhc = wcd937x->mbhc; + if (!wcd937x_mbhc) { + dev_err(codec->dev, "%s: mbhc not initialized!\n", __func__); + return -EINVAL; + } + + return wcd_mbhc_start(&wcd937x_mbhc->wcd_mbhc, mbhc_cfg); +} +EXPORT_SYMBOL(wcd937x_mbhc_hs_detect); + +/* + * wcd937x_mbhc_hs_detect_exit: stop mbhc insertion/removal functionality + * @codec: handle to snd_soc_codec * + */ +void wcd937x_mbhc_hs_detect_exit(struct snd_soc_codec *codec) +{ + struct wcd937x_priv *wcd937x = NULL; + struct wcd937x_mbhc *wcd937x_mbhc = NULL; + + if (!codec) { + pr_err("%s: codec is NULL\n", __func__); + return; + } + + wcd937x = snd_soc_codec_get_drvdata(codec); + if (!wcd937x) { + pr_err("%s: wcd937x is NULL\n", __func__); + return; + } + + wcd937x_mbhc = wcd937x->mbhc; + if (!wcd937x_mbhc) { + dev_err(codec->dev, "%s: mbhc not initialized!\n", __func__); + return; + } + wcd_mbhc_stop(&wcd937x_mbhc->wcd_mbhc); +} +EXPORT_SYMBOL(wcd937x_mbhc_hs_detect_exit); + +/* + * wcd937x_mbhc_post_ssr_init: initialize mbhc for + * wcd937x post subsystem restart + * @mbhc: poniter to wcd937x_mbhc structure + * @codec: handle to snd_soc_codec * + * + * return 0 if mbhc_init is success or error code in case of failure + */ +int wcd937x_mbhc_post_ssr_init(struct wcd937x_mbhc *mbhc, + struct snd_soc_codec *codec) +{ + int ret = 0; + struct wcd_mbhc *wcd_mbhc = NULL; + + if (!mbhc || !codec) + return -EINVAL; + + wcd_mbhc = &mbhc->wcd_mbhc; + if (wcd_mbhc == NULL) { + pr_err("%s: wcd_mbhc is NULL\n", __func__); + return -EINVAL; + } + + wcd_mbhc_deinit(wcd_mbhc); + ret = wcd_mbhc_init(wcd_mbhc, codec, &mbhc_cb, &intr_ids, + wcd_mbhc_registers, WCD937X_ZDET_SUPPORTED); + if (ret) { + dev_err(codec->dev, "%s: mbhc initialization failed\n", + __func__); + goto done; + } + +done: + return ret; +} +EXPORT_SYMBOL(wcd937x_mbhc_post_ssr_init); + +/* + * wcd937x_mbhc_init: initialize mbhc for wcd937x + * @mbhc: poniter to wcd937x_mbhc struct pointer to store the configs + * @codec: handle to snd_soc_codec * + * @fw_data: handle to firmware data + * + * return 0 if mbhc_init is success or error code in case of failure + */ +int wcd937x_mbhc_init(struct wcd937x_mbhc **mbhc, struct snd_soc_codec *codec, + struct fw_info *fw_data) +{ + struct wcd937x_mbhc *wcd937x_mbhc = NULL; + struct wcd_mbhc *wcd_mbhc = NULL; + int ret = 0; + + if (!codec) { + pr_err("%s: codec is NULL\n", __func__); + return -EINVAL; + } + + wcd937x_mbhc = devm_kzalloc(codec->dev, sizeof(struct wcd937x_mbhc), + GFP_KERNEL); + if (!wcd937x_mbhc) + return -ENOMEM; + + wcd937x_mbhc->fw_data = fw_data; + BLOCKING_INIT_NOTIFIER_HEAD(&wcd937x_mbhc->notifier); + wcd_mbhc = &wcd937x_mbhc->wcd_mbhc; + if (wcd_mbhc == NULL) { + pr_err("%s: wcd_mbhc is NULL\n", __func__); + ret = -EINVAL; + goto err; + } + + + /* Setting default mbhc detection logic to ADC */ + wcd_mbhc->mbhc_detection_logic = WCD_DETECTION_ADC; + + ret = wcd_mbhc_init(wcd_mbhc, codec, &mbhc_cb, + &intr_ids, wcd_mbhc_registers, + WCD937X_ZDET_SUPPORTED); + if (ret) { + dev_err(codec->dev, "%s: mbhc initialization failed\n", + __func__); + goto err; + } + + (*mbhc) = wcd937x_mbhc; + snd_soc_add_codec_controls(codec, impedance_detect_controls, + ARRAY_SIZE(impedance_detect_controls)); + snd_soc_add_codec_controls(codec, hph_type_detect_controls, + ARRAY_SIZE(hph_type_detect_controls)); + + return 0; +err: + devm_kfree(codec->dev, wcd937x_mbhc); + return ret; +} +EXPORT_SYMBOL(wcd937x_mbhc_init); + +/* + * wcd937x_mbhc_deinit: deinitialize mbhc for wcd937x + * @codec: handle to snd_soc_codec * + */ +void wcd937x_mbhc_deinit(struct snd_soc_codec *codec) +{ + struct wcd937x_priv *wcd937x; + struct wcd937x_mbhc *wcd937x_mbhc; + + if (!codec) { + pr_err("%s: codec is NULL\n", __func__); + return; + } + + wcd937x = snd_soc_codec_get_drvdata(codec); + if (!wcd937x) { + pr_err("%s: wcd937x is NULL\n", __func__); + return; + } + + wcd937x_mbhc = wcd937x->mbhc; + if (wcd937x_mbhc) { + wcd_mbhc_deinit(&wcd937x_mbhc->wcd_mbhc); + devm_kfree(codec->dev, wcd937x_mbhc); + } +} +EXPORT_SYMBOL(wcd937x_mbhc_deinit); diff --git a/asoc/codecs/wcd937x/wcd937x-mbhc.h b/asoc/codecs/wcd937x/wcd937x-mbhc.h new file mode 100644 index 0000000000..648d81c9be --- /dev/null +++ b/asoc/codecs/wcd937x/wcd937x-mbhc.h @@ -0,0 +1,70 @@ +/* Copyright (c) 2018, 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. + */ +#ifndef __WCD937X_MBHC_H__ +#define __WCD937X_MBHC_H__ +#include "../wcd-mbhc-v2.h" + +struct wcd937x_mbhc { + struct wcd_mbhc wcd_mbhc; + struct blocking_notifier_head notifier; + struct fw_info *fw_data; + bool mbhc_started; +}; + +#if IS_ENABLED(CONFIG_SND_SOC_WCD937X) +extern int wcd937x_mbhc_init(struct wcd937x_mbhc **mbhc, + struct snd_soc_codec *codec, + struct fw_info *fw_data); +extern void wcd937x_mbhc_hs_detect_exit(struct snd_soc_codec *codec); +extern int wcd937x_mbhc_hs_detect(struct snd_soc_codec *codec, + struct wcd_mbhc_config *mbhc_cfg); +extern void wcd937x_mbhc_deinit(struct snd_soc_codec *codec); +extern int wcd937x_mbhc_post_ssr_init(struct wcd937x_mbhc *mbhc, + struct snd_soc_codec *codec); +extern int wcd937x_mbhc_get_impedance(struct wcd937x_mbhc *wcd937x_mbhc, + uint32_t *zl, uint32_t *zr); +#else +static inline int wcd937x_mbhc_init(struct wcd937x_mbhc **mbhc, + struct snd_soc_codec *codec, + struct fw_info *fw_data) +{ + return 0; +} +static inline void wcd937x_mbhc_hs_detect_exit(struct snd_soc_codec *codec) +{ +} +static inline int wcd937x_mbhc_hs_detect(struct snd_soc_codec *codec, + struct wcd_mbhc_config *mbhc_cfg) +{ + return 0; +} +static inline void wcd937x_mbhc_deinit(struct snd_soc_codec *codec) +{ +} +static inline int wcd937x_mbhc_post_ssr_init(struct wcd937x_mbhc *mbhc, + struct snd_soc_codec *codec) +{ + return 0; +} + +static inline int wcd937x_mbhc_get_impedance(struct wcd937x_mbhc *wcd937x_mbhc, + uint32_t *zl, uint32_t *zr) +{ + if (zl) + *zl = 0; + if (zr) + *zr = 0; + return -EINVAL; +} +#endif + +#endif /* __WCD937X_MBHC_H__ */ diff --git a/asoc/codecs/wcd937x/wcd937x.c b/asoc/codecs/wcd937x/wcd937x.c index 78bd8634fa..008f8f55e4 100644 --- a/asoc/codecs/wcd937x/wcd937x.c +++ b/asoc/codecs/wcd937x/wcd937x.c @@ -34,6 +34,99 @@ static const DECLARE_TLV_DB_SCALE(line_gain, 0, 7, 1); static const DECLARE_TLV_DB_SCALE(analog_gain, 0, 25, 1); +static int wcd937x_handle_pre_irq(void *data); +static int wcd937x_handle_post_irq(void *data); + +static const struct regmap_irq wcd937x_irqs[WCD937X_NUM_IRQS] = { + REGMAP_IRQ_REG(WCD937X_IRQ_MBHC_BUTTON_PRESS_DET, 0, 0x01), + REGMAP_IRQ_REG(WCD937X_IRQ_MBHC_BUTTON_RELEASE_DET, 0, 0x02), + REGMAP_IRQ_REG(WCD937X_IRQ_MBHC_ELECT_INS_REM_DET, 0, 0x04), + REGMAP_IRQ_REG(WCD937X_IRQ_MBHC_ELECT_INS_REM_LEG_DET, 0, 0x08), + REGMAP_IRQ_REG(WCD937X_IRQ_MBHC_SW_DET, 0, 0x10), + REGMAP_IRQ_REG(WCD937X_IRQ_HPHR_OCP_INT, 0, 0x20), + REGMAP_IRQ_REG(WCD937X_IRQ_HPHR_CNP_INT, 0, 0x40), + REGMAP_IRQ_REG(WCD937X_IRQ_HPHL_OCP_INT, 0, 0x80), + REGMAP_IRQ_REG(WCD937X_IRQ_HPHL_CNP_INT, 1, 0x01), + REGMAP_IRQ_REG(WCD937X_IRQ_EAR_CNP_INT, 1, 0x02), + REGMAP_IRQ_REG(WCD937X_IRQ_EAR_SCD_INT, 1, 0x04), + REGMAP_IRQ_REG(WCD937X_IRQ_AUX_CNP_INT, 1, 0x08), + REGMAP_IRQ_REG(WCD937X_IRQ_AUX_SCD_INT, 1, 0x10), + REGMAP_IRQ_REG(WCD937X_IRQ_HPHL_PDM_WD_INT, 1, 0x20), + REGMAP_IRQ_REG(WCD937X_IRQ_HPHR_PDM_WD_INT, 1, 0x40), + REGMAP_IRQ_REG(WCD937X_IRQ_AUX_PDM_WD_INT, 1, 0x80), + REGMAP_IRQ_REG(WCD937X_IRQ_LDORT_SCD_INT, 2, 0x01), + REGMAP_IRQ_REG(WCD937X_IRQ_MBHC_MOISTURE_INT, 2, 0x02), + REGMAP_IRQ_REG(WCD937X_IRQ_HPHL_SURGE_DET_INT, 2, 0x04), + REGMAP_IRQ_REG(WCD937X_IRQ_HPHR_SURGE_DET_INT, 2, 0x08), +}; + +static struct regmap_irq_chip wcd937x_regmap_irq_chip = { + .name = "wcd937x", + .irqs = wcd937x_irqs, + .num_irqs = ARRAY_SIZE(wcd937x_irqs), + .num_regs = 3, + .status_base = WCD937X_DIGITAL_INTR_STATUS_0, + .mask_base = WCD937X_DIGITAL_INTR_MASK_0, + .type_base = WCD937X_DIGITAL_INTR_LEVEL_0, + .runtime_pm = true, + .handle_post_irq = wcd937x_handle_post_irq, + .handle_pre_irq = wcd937x_handle_pre_irq, +}; + +static int wcd937x_handle_pre_irq(void *data) +{ + struct wcd937x_priv *wcd937x = data; + int num_irq_regs = wcd937x->num_irq_regs; + int ret = 0; + u8 sts[num_irq_regs]; + struct wcd937x_pdata *pdata; + + pdata = dev_get_platdata(wcd937x->dev); + + memset(sts, 0, sizeof(sts)); + ret = regmap_bulk_read(wcd937x->regmap, WCD937X_DIGITAL_INTR_STATUS_0, + sts, num_irq_regs); + if (ret < 0) { + dev_err(wcd937x->dev, "%s: Failed to read intr status: %d\n", + __func__, ret); + } else if (ret == 0) { + dev_dbg(wcd937x->dev, + "%s: clear interrupts except OCP and SCD\n", __func__); + /* Do not affect OCP and SCD interrupts */ + sts[0] = sts[0] & 0x5F; + sts[1] = sts[1] & 0xEB; + regmap_write(wcd937x->regmap, WCD937X_DIGITAL_INTR_CLEAR_0, + sts[0]); + regmap_write(wcd937x->regmap, WCD937X_DIGITAL_INTR_CLEAR_1, + sts[1]); + regmap_write(wcd937x->regmap, WCD937X_DIGITAL_INTR_CLEAR_2, + sts[2]); + } + return IRQ_HANDLED; +} + +static int wcd937x_handle_post_irq(void *data) +{ + struct wcd937x_priv *wcd937x = data; + int val = 0; + struct wcd937x_pdata *pdata = NULL; + + pdata = dev_get_platdata(wcd937x->dev); + + regmap_read(wcd937x->regmap, WCD937X_DIGITAL_INTR_STATUS_0, &val); + if ((val & 0xA0) != 0) { + dev_dbg(wcd937x->dev, "%s Clear OCP interupts\n", __func__); + regmap_update_bits(wcd937x->regmap, + WCD937X_DIGITAL_INTR_CLEAR_0, 0xA0, 0x00); + } + regmap_read(wcd937x->regmap, WCD937X_DIGITAL_INTR_STATUS_1, &val); + if ((val & 0x14) != 0) { + dev_dbg(wcd937x->dev, "%s Clear SCD interupts\n", __func__); + regmap_update_bits(wcd937x->regmap, + WCD937X_DIGITAL_INTR_CLEAR_1, 0x14, 0x00); + } + return IRQ_HANDLED; +} static int wcd937x_init_reg(struct snd_soc_codec *codec) { @@ -90,6 +183,31 @@ static int wcd937x_rx_clk_disable(struct snd_soc_codec *codec) return 0; } +/* + * wcd937x_soc_get_mbhc: get wcd937x_mbhc handle of corresponding codec + * @codec: handle to snd_soc_codec * + * + * return wcd937x_mbhc handle or error code in case of failure + */ +struct wcd937x_mbhc *wcd937x_soc_get_mbhc(struct snd_soc_codec *codec) +{ + struct wcd937x_priv *wcd937x; + + if (!codec) { + pr_err("%s: Invalid params, NULL codec\n", __func__); + return NULL; + } + wcd937x = snd_soc_codec_get_drvdata(codec); + + if (!wcd937x) { + pr_err("%s: Invalid params, NULL tavil\n", __func__); + return NULL; + } + + return wcd937x->mbhc; +} +EXPORT_SYMBOL(wcd937x_soc_get_mbhc); + static int wcd937x_codec_hphl_dac_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) @@ -505,6 +623,100 @@ static int wcd937x_codec_enable_dmic(struct snd_soc_dapm_widget *w, return 0; } +/* + * wcd937x_get_micb_vout_ctl_val: converts micbias from volts to register value + * @micb_mv: micbias in mv + * + * return register value converted + */ +int wcd937x_get_micb_vout_ctl_val(u32 micb_mv) +{ + /* min micbias voltage is 1V and maximum is 2.85V */ + if (micb_mv < 1000 || micb_mv > 2850) { + pr_err("%s: unsupported micbias voltage\n", __func__); + return -EINVAL; + } + + return (micb_mv - 1000) / 50; +} +EXPORT_SYMBOL(wcd937x_get_micb_vout_ctl_val); + +/* + * wcd937x_mbhc_micb_adjust_voltage: adjust specific micbias voltage + * @codec: handle to snd_soc_codec * + * @req_volt: micbias voltage to be set + * @micb_num: micbias to be set, e.g. micbias1 or micbias2 + * + * return 0 if adjustment is success or error code in case of failure + */ +int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_codec *codec, + int req_volt, int micb_num) +{ + struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); + int cur_vout_ctl, req_vout_ctl; + int micb_reg, micb_val, micb_en; + int ret = 0; + + switch (micb_num) { + case MIC_BIAS_1: + micb_reg = WCD937X_ANA_MICB1; + break; + case MIC_BIAS_2: + micb_reg = WCD937X_ANA_MICB2; + break; + case MIC_BIAS_3: + micb_reg = WCD937X_ANA_MICB3; + break; + default: + return -EINVAL; + } + mutex_lock(&wcd937x->micb_lock); + + /* + * If requested micbias voltage is same as current micbias + * voltage, then just return. Otherwise, adjust voltage as + * per requested value. If micbias is already enabled, then + * to avoid slow micbias ramp-up or down enable pull-up + * momentarily, change the micbias value and then re-enable + * micbias. + */ + micb_val = snd_soc_read(codec, micb_reg); + micb_en = (micb_val & 0xC0) >> 6; + cur_vout_ctl = micb_val & 0x3F; + + req_vout_ctl = wcd937x_get_micb_vout_ctl_val(req_volt); + if (req_vout_ctl < 0) { + ret = -EINVAL; + goto exit; + } + if (cur_vout_ctl == req_vout_ctl) { + ret = 0; + goto exit; + } + + dev_dbg(codec->dev, "%s: micb_num: %d, cur_mv: %d, req_mv: %d, micb_en: %d\n", + __func__, micb_num, WCD_VOUT_CTL_TO_MICB(cur_vout_ctl), + req_volt, micb_en); + + if (micb_en == 0x1) + snd_soc_update_bits(codec, micb_reg, 0xC0, 0x80); + + snd_soc_update_bits(codec, micb_reg, 0x3F, req_vout_ctl); + + if (micb_en == 0x1) { + snd_soc_update_bits(codec, micb_reg, 0xC0, 0x40); + /* + * Add 2ms delay as per HW requirement after enabling + * micbias + */ + usleep_range(2000, 2100); + } +exit: + mutex_unlock(&wcd937x->micb_lock); + return ret; +} +EXPORT_SYMBOL(wcd937x_mbhc_micb_adjust_voltage); + static int wcd937x_codec_enable_adc(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event){ @@ -565,8 +777,8 @@ static int wcd937x_enable_req(struct snd_soc_dapm_widget *w, return 0; } -static int wcd937x_micbias_control(struct snd_soc_codec *codec, - int micb_num, int req, bool is_dapm) +int wcd937x_micbias_control(struct snd_soc_codec *codec, + int micb_num, int req, bool is_dapm) { struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec); @@ -663,6 +875,7 @@ static int wcd937x_micbias_control(struct snd_soc_codec *codec, return 0; } +EXPORT_SYMBOL(wcd937x_micbias_control); static int __wcd937x_codec_enable_micbias(struct snd_soc_dapm_widget *w, int event) @@ -1115,6 +1328,12 @@ static int wcd937x_soc_codec_probe(struct snd_soc_codec *codec) goto err_hwdep; } + ret = wcd937x_mbhc_init(&wcd937x->mbhc, codec, wcd937x->fw_data); + if (ret) { + pr_err("%s: mbhc initialization failed\n", __func__); + goto err_hwdep; + } + wcd937x_init_reg(codec); if (wcd937x->variant == WCD9375_VARIANT) { @@ -1250,7 +1469,7 @@ struct wcd937x_pdata *wcd937x_populate_dt_data(struct device *dev) static int wcd937x_bind(struct device *dev) { - int ret = 0; + int ret = 0, i = 0; struct wcd937x_priv *wcd937x = NULL; struct wcd937x_pdata *pdata = NULL; @@ -1305,15 +1524,35 @@ static int wcd937x_bind(struct device *dev) goto err; } + /* Set all interupts as edge triggered */ + for (i = 0; i < wcd937x_regmap_irq_chip.num_regs; i++) + regmap_write(wcd937x->regmap, + (WCD937X_DIGITAL_INTR_LEVEL_0 + i), 0); + + wcd937x->irq_info->wcd_regmap_irq_chip = &wcd937x_regmap_irq_chip; + wcd937x->irq_info->codec_name = "WCD937X"; + wcd937x->irq_info->regmap = wcd937x->regmap; + wcd937x->irq_info->dev = dev; + ret = wcd_irq_init(wcd937x->irq_info, &wcd937x->virq); + + if (ret) { + dev_err(wcd937x->dev, "%s: IRQ init failed: %d\n", + __func__, ret); + goto err; + } + wcd937x->tx_swr_dev->slave_irq = wcd937x->virq; + ret = snd_soc_register_codec(dev, &soc_codec_dev_wcd937x, NULL, 0); if (ret) { dev_err(dev, "%s: Codec registration failed\n", __func__); - goto err; + goto err_irq; } return ret; +err_irq: + wcd_irq_exit(wcd937x->irq_info, wcd937x->virq); err: component_unbind_all(dev, wcd937x); return ret; @@ -1323,6 +1562,7 @@ static void wcd937x_unbind(struct device *dev) { struct wcd937x_priv *wcd937x = dev_get_drvdata(dev); + wcd_irq_exit(wcd937x->irq_info, wcd937x->virq); snd_soc_unregister_codec(dev); component_unbind_all(dev, wcd937x); }