diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig old mode 100755 new mode 100644 index 2d5147f876aa..49c4087c7408 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -731,6 +731,12 @@ config SND_SOC_WCD934X select AUDIO_EXT_CLK select SND_SOC_WCD_DSP_MGR select SND_SOC_WCD_SPI + select SND_SOC_WCD934X_MBHC + +config SND_SOC_WCD934X_MBHC + tristate + depends on SND_SOC_WCD934X + select SND_SOC_WCD_MBHC config SND_SOC_WSA881X tristate diff --git a/sound/soc/codecs/wcd-mbhc-v2.c b/sound/soc/codecs/wcd-mbhc-v2.c index 63ffacad61e1..71cc156cd8ad 100644 --- a/sound/soc/codecs/wcd-mbhc-v2.c +++ b/sound/soc/codecs/wcd-mbhc-v2.c @@ -1525,14 +1525,14 @@ static void wcd_mbhc_swch_irq_handler(struct wcd_mbhc *mbhc) mbhc->mbhc_cb->trim_btn_reg(codec); /* Enable external voltage source to micbias if present */ if (mbhc->mbhc_cb->enable_mb_source) - mbhc->mbhc_cb->enable_mb_source(codec, true); + mbhc->mbhc_cb->enable_mb_source(mbhc, true); mbhc->btn_press_intr = false; wcd_mbhc_detect_plug_type(mbhc); } else if ((mbhc->current_plug != MBHC_PLUG_TYPE_NONE) && !detection_type) { /* Disable external voltage source to micbias if present */ if (mbhc->mbhc_cb->enable_mb_source) - mbhc->mbhc_cb->enable_mb_source(codec, false); + mbhc->mbhc_cb->enable_mb_source(mbhc, false); /* Disable HW FSM */ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0); WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_BTN_ISRC_CTL, 0); @@ -1588,7 +1588,7 @@ static void wcd_mbhc_swch_irq_handler(struct wcd_mbhc *mbhc) } else if (!detection_type) { /* Disable external voltage source to micbias if present */ if (mbhc->mbhc_cb->enable_mb_source) - mbhc->mbhc_cb->enable_mb_source(codec, false); + mbhc->mbhc_cb->enable_mb_source(mbhc, false); /* Disable HW FSM */ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0); WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_BTN_ISRC_CTL, 0); @@ -2142,7 +2142,7 @@ static void wcd_mbhc_fw_read(struct work_struct *work) pr_debug("%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, + fw_data = mbhc->mbhc_cb->get_hwdep_fw_cal(mbhc, WCD9XXX_MBHC_CAL); if (!fw_data) ret = request_firmware(&fw, "wcd9320/wcd9320_mbhc.bin", @@ -2427,7 +2427,7 @@ int wcd_mbhc_init(struct wcd_mbhc *mbhc, struct snd_soc_codec *codec, /* Register event notifier */ mbhc->nblock.notifier_call = wcd_event_notify; if (mbhc->mbhc_cb->register_notifier) { - ret = mbhc->mbhc_cb->register_notifier(codec, &mbhc->nblock, + ret = mbhc->mbhc_cb->register_notifier(mbhc, &mbhc->nblock, true); if (ret) { pr_err("%s: Failed to register notifier %d\n", @@ -2532,7 +2532,7 @@ err_btn_press_irq: mbhc->mbhc_cb->free_irq(codec, mbhc->intr_ids->mbhc_sw_intr, mbhc); err_mbhc_sw_irq: if (mbhc->mbhc_cb->register_notifier) - mbhc->mbhc_cb->register_notifier(codec, &mbhc->nblock, false); + mbhc->mbhc_cb->register_notifier(mbhc, &mbhc->nblock, false); mutex_destroy(&mbhc->codec_resource_lock); err: pr_debug("%s: leave ret %d\n", __func__, ret); @@ -2554,7 +2554,7 @@ void wcd_mbhc_deinit(struct wcd_mbhc *mbhc) mbhc->mbhc_cb->free_irq(codec, mbhc->intr_ids->hph_left_ocp, mbhc); mbhc->mbhc_cb->free_irq(codec, mbhc->intr_ids->hph_right_ocp, mbhc); if (mbhc->mbhc_cb && mbhc->mbhc_cb->register_notifier) - mbhc->mbhc_cb->register_notifier(codec, &mbhc->nblock, false); + mbhc->mbhc_cb->register_notifier(mbhc, &mbhc->nblock, false); mutex_destroy(&mbhc->codec_resource_lock); } EXPORT_SYMBOL(wcd_mbhc_deinit); diff --git a/sound/soc/codecs/wcd-mbhc-v2.h b/sound/soc/codecs/wcd-mbhc-v2.h index bc48cbff4576..e3eb60729e94 100644 --- a/sound/soc/codecs/wcd-mbhc-v2.h +++ b/sound/soc/codecs/wcd-mbhc-v2.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. +/* Copyright (c) 2014-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 @@ -325,15 +325,15 @@ do { \ } while (0) struct wcd_mbhc_cb { - int (*enable_mb_source)(struct snd_soc_codec *, bool); + int (*enable_mb_source)(struct wcd_mbhc *, bool); void (*trim_btn_reg)(struct snd_soc_codec *); void (*compute_impedance)(struct wcd_mbhc *, uint32_t *, uint32_t *); void (*set_micbias_value)(struct snd_soc_codec *); void (*set_auto_zeroing)(struct snd_soc_codec *, bool); - struct firmware_cal * (*get_hwdep_fw_cal)(struct snd_soc_codec *, + struct firmware_cal * (*get_hwdep_fw_cal)(struct wcd_mbhc *, enum wcd_cal_type); void (*set_cap_mode)(struct snd_soc_codec *, bool, bool); - int (*register_notifier)(struct snd_soc_codec *, + int (*register_notifier)(struct wcd_mbhc *, struct notifier_block *nblock, bool enable); int (*request_irq)(struct snd_soc_codec *, diff --git a/sound/soc/codecs/wcd9335.c b/sound/soc/codecs/wcd9335.c index 5a52b25764a4..e6338b081cba 100644 --- a/sound/soc/codecs/wcd9335.c +++ b/sound/soc/codecs/wcd9335.c @@ -1460,10 +1460,11 @@ static bool tasha_mbhc_lock_sleep(struct wcd_mbhc *mbhc, bool lock) } } -static int tasha_mbhc_register_notifier(struct snd_soc_codec *codec, +static int tasha_mbhc_register_notifier(struct wcd_mbhc *mbhc, struct notifier_block *nblock, bool enable) { + struct snd_soc_codec *codec = mbhc->codec; struct tasha_priv *tasha = snd_soc_codec_get_drvdata(codec); if (enable) @@ -1514,9 +1515,10 @@ static void tasha_mbhc_hph_l_pull_up_control(struct snd_soc_codec *codec, 0xC0, 0x40); } -static int tasha_enable_ext_mb_source(struct snd_soc_codec *codec, +static int tasha_enable_ext_mb_source(struct wcd_mbhc *mbhc, bool turn_on) { + struct snd_soc_codec *codec = mbhc->codec; struct tasha_priv *tasha = snd_soc_codec_get_drvdata(codec); int ret = 0; struct on_demand_supply *supply; @@ -1701,11 +1703,12 @@ static void tasha_mbhc_micb_ramp_control(struct snd_soc_codec *codec, } } -static struct firmware_cal *tasha_get_hwdep_fw_cal(struct snd_soc_codec *codec, +static struct firmware_cal *tasha_get_hwdep_fw_cal(struct wcd_mbhc *mbhc, enum wcd_cal_type type) { struct tasha_priv *tasha; struct firmware_cal *hwdep_cal; + struct snd_soc_codec *codec = mbhc->codec; if (!codec) { pr_err("%s: NULL codec pointer\n", __func__); diff --git a/sound/soc/codecs/wcd934x/Makefile b/sound/soc/codecs/wcd934x/Makefile index 6a04bd0ce74c..09adfba3f0a4 100644 --- a/sound/soc/codecs/wcd934x/Makefile +++ b/sound/soc/codecs/wcd934x/Makefile @@ -3,3 +3,5 @@ # snd-soc-wcd934x-objs := wcd934x.o wcd934x-dsp-cntl.o obj-$(CONFIG_SND_SOC_WCD934X) += snd-soc-wcd934x.o +snd-soc-wcd934x-mbhc-objs := wcd934x-mbhc.o +obj-$(CONFIG_SND_SOC_WCD934X_MBHC) += snd-soc-wcd934x-mbhc.o diff --git a/sound/soc/codecs/wcd934x/wcd934x-mbhc.c b/sound/soc/codecs/wcd934x/wcd934x-mbhc.c new file mode 100644 index 000000000000..4c5ddec807b2 --- /dev/null +++ b/sound/soc/codecs/wcd934x/wcd934x-mbhc.c @@ -0,0 +1,909 @@ +/* + * Copyright (c) 2015-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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wcd934x.h" +#include "wcd934x-mbhc.h" +#include "../wcdcal-hwdep.h" + +#define TAVIL_ZDET_SUPPORTED true +/* Z value defined in milliohm */ +#define TAVIL_ZDET_VAL_32 32000 +#define TAVIL_ZDET_VAL_400 400000 +#define TAVIL_ZDET_VAL_1200 1200000 +#define TAVIL_ZDET_VAL_100K 100000000 +/* Z floating defined in ohms */ +#define TAVIL_ZDET_FLOATING_IMPEDANCE 0x0FFFFFFE + +#define TAVIL_ZDET_NUM_MEASUREMENTS 150 +#define TAVIL_MBHC_GET_C1(c) ((c & 0xC000) >> 14) +#define TAVIL_MBHC_GET_X1(x) (x & 0x3FFF) +/* Z value compared in milliOhm */ +#define TAVIL_MBHC_IS_SECOND_RAMP_REQUIRED(z) ((z > 400000) || (z < 32000)) +#define TAVIL_MBHC_ZDET_CONST (86 * 16384) + +static struct wcd_mbhc_register + wcd_mbhc_registers[WCD_MBHC_REG_FUNC_MAX] = { + WCD_MBHC_REGISTER("WCD_MBHC_L_DET_EN", + WCD934X_ANA_MBHC_MECH, 0x80, 7, 0), + WCD_MBHC_REGISTER("WCD_MBHC_GND_DET_EN", + WCD934X_ANA_MBHC_MECH, 0x40, 6, 0), + WCD_MBHC_REGISTER("WCD_MBHC_MECH_DETECTION_TYPE", + WCD934X_ANA_MBHC_MECH, 0x20, 5, 0), + WCD_MBHC_REGISTER("WCD_MBHC_MIC_CLAMP_CTL", + WCD934X_MBHC_NEW_PLUG_DETECT_CTL, 0x30, 4, 0), + WCD_MBHC_REGISTER("WCD_MBHC_ELECT_DETECTION_TYPE", + WCD934X_ANA_MBHC_ELECT, 0x08, 3, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HS_L_DET_PULL_UP_CTRL", + WCD934X_MBHC_NEW_PLUG_DETECT_CTL, 0xC0, 6, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HS_L_DET_PULL_UP_COMP_CTRL", + WCD934X_ANA_MBHC_MECH, 0x04, 2, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HPHL_PLUG_TYPE", + WCD934X_ANA_MBHC_MECH, 0x10, 4, 0), + WCD_MBHC_REGISTER("WCD_MBHC_GND_PLUG_TYPE", + WCD934X_ANA_MBHC_MECH, 0x08, 3, 0), + WCD_MBHC_REGISTER("WCD_MBHC_SW_HPH_LP_100K_TO_GND", + WCD934X_ANA_MBHC_MECH, 0x01, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_ELECT_SCHMT_ISRC", + WCD934X_ANA_MBHC_ELECT, 0x06, 1, 0), + WCD_MBHC_REGISTER("WCD_MBHC_FSM_EN", + WCD934X_ANA_MBHC_ELECT, 0x80, 7, 0), + WCD_MBHC_REGISTER("WCD_MBHC_INSREM_DBNC", + WCD934X_MBHC_NEW_PLUG_DETECT_CTL, 0x0F, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_BTN_DBNC", + WCD934X_MBHC_NEW_CTL_1, 0x03, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HS_VREF", + WCD934X_MBHC_NEW_CTL_2, 0x03, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HS_COMP_RESULT", + WCD934X_ANA_MBHC_RESULT_3, 0x08, 3, 0), + WCD_MBHC_REGISTER("WCD_MBHC_MIC_SCHMT_RESULT", + WCD934X_ANA_MBHC_RESULT_3, 0x20, 5, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HPHL_SCHMT_RESULT", + WCD934X_ANA_MBHC_RESULT_3, 0x80, 7, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HPHR_SCHMT_RESULT", + WCD934X_ANA_MBHC_RESULT_3, 0x40, 6, 0), + WCD_MBHC_REGISTER("WCD_MBHC_OCP_FSM_EN", + WCD934X_HPH_OCP_CTL, 0x10, 4, 0), + WCD_MBHC_REGISTER("WCD_MBHC_BTN_RESULT", + WCD934X_ANA_MBHC_RESULT_3, 0x07, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_BTN_ISRC_CTL", + WCD934X_ANA_MBHC_ELECT, 0x70, 4, 0), + WCD_MBHC_REGISTER("WCD_MBHC_ELECT_RESULT", + WCD934X_ANA_MBHC_RESULT_3, 0xFF, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_MICB_CTRL", + WCD934X_ANA_MICB2, 0xC0, 6, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HPH_CNP_WG_TIME", + WCD934X_HPH_CNP_WG_TIME, 0xFF, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HPHR_PA_EN", + WCD934X_ANA_HPH, 0x40, 6, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HPHL_PA_EN", + WCD934X_ANA_HPH, 0x80, 7, 0), + WCD_MBHC_REGISTER("WCD_MBHC_HPH_PA_EN", + WCD934X_ANA_HPH, 0xC0, 6, 0), + WCD_MBHC_REGISTER("WCD_MBHC_SWCH_LEVEL_REMOVE", + WCD934X_ANA_MBHC_RESULT_3, 0x10, 4, 0), + WCD_MBHC_REGISTER("WCD_MBHC_MOISTURE_VREF", + WCD934X_MBHC_NEW_CTL_2, 0x0C, 2, 0), + WCD_MBHC_REGISTER("WCD_MBHC_PULLDOWN_CTRL", + 0, 0, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_ANC_DET_EN", + WCD934X_ANA_MBHC_ZDET, 0x01, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_FSM_STATUS", + WCD934X_MBHC_STATUS_SPARE_1, 0x01, 0, 0), + WCD_MBHC_REGISTER("WCD_MBHC_MUX_CTL", + WCD934X_MBHC_NEW_CTL_2, 0x70, 4, 0), +}; + +static const struct wcd_mbhc_intr intr_ids = { + .mbhc_sw_intr = WCD934X_IRQ_MBHC_SW_DET, + .mbhc_btn_press_intr = WCD934X_IRQ_MBHC_BUTTON_PRESS_DET, + .mbhc_btn_release_intr = WCD934X_IRQ_MBHC_BUTTON_RELEASE_DET, + .mbhc_hs_ins_intr = WCD934X_IRQ_MBHC_ELECT_INS_REM_LEG_DET, + .mbhc_hs_rem_intr = WCD934X_IRQ_MBHC_ELECT_INS_REM_DET, + .hph_left_ocp = WCD934X_IRQ_HPH_PA_OCPL_FAULT, + .hph_right_ocp = WCD934X_IRQ_HPH_PA_OCPR_FAULT, +}; + + +static char on_demand_supply_name[][MAX_ON_DEMAND_SUPPLY_NAME_LENGTH] = { + "cdc-vdd-mic-bias", +}; + +struct tavil_mbhc_zdet_param { + u16 ldo_ctl; + u16 noff; + u16 nshift; + u16 btn5; + u16 btn6; + u16 btn7; +}; + +static int tavil_mbhc_request_irq(struct snd_soc_codec *codec, + int irq, irq_handler_t handler, + const char *name, void *data) +{ + struct wcd9xxx *wcd9xxx = dev_get_drvdata(codec->dev->parent); + struct wcd9xxx_core_resource *core_res = + &wcd9xxx->core_res; + + return wcd9xxx_request_irq(core_res, irq, handler, name, data); +} + +static void tavil_mbhc_irq_control(struct snd_soc_codec *codec, + int irq, bool enable) +{ + struct wcd9xxx *wcd9xxx = dev_get_drvdata(codec->dev->parent); + struct wcd9xxx_core_resource *core_res = + &wcd9xxx->core_res; + if (enable) + wcd9xxx_enable_irq(core_res, irq); + else + wcd9xxx_disable_irq(core_res, irq); +} + +static int tavil_mbhc_free_irq(struct snd_soc_codec *codec, + int irq, void *data) +{ + struct wcd9xxx *wcd9xxx = dev_get_drvdata(codec->dev->parent); + struct wcd9xxx_core_resource *core_res = + &wcd9xxx->core_res; + + wcd9xxx_free_irq(core_res, irq, data); + return 0; +} + +static void tavil_mbhc_clk_setup(struct snd_soc_codec *codec, + bool enable) +{ + if (enable) + snd_soc_update_bits(codec, WCD934X_MBHC_NEW_CTL_1, + 0x80, 0x80); + else + snd_soc_update_bits(codec, WCD934X_MBHC_NEW_CTL_1, + 0x80, 0x00); +} + +static int tavil_mbhc_btn_to_num(struct snd_soc_codec *codec) +{ + return snd_soc_read(codec, WCD934X_ANA_MBHC_RESULT_3) & 0x7; +} + +static int tavil_enable_ext_mb_source(struct wcd_mbhc *mbhc, + bool turn_on) +{ + struct wcd934x_mbhc *wcd934x_mbhc; + struct snd_soc_codec *codec = mbhc->codec; + struct wcd934x_on_demand_supply *supply; + int ret = 0; + + wcd934x_mbhc = container_of(mbhc, struct wcd934x_mbhc, wcd_mbhc); + + supply = &wcd934x_mbhc->on_demand_list[WCD934X_ON_DEMAND_MICBIAS]; + if (!supply->supply) { + dev_dbg(codec->dev, "%s: warning supply not present ond for %s\n", + __func__, "onDemand Micbias"); + return ret; + } + + dev_dbg(codec->dev, "%s turn_on: %d count: %d\n", __func__, turn_on, + supply->ondemand_supply_count); + + if (turn_on) { + if (!(supply->ondemand_supply_count)) { + ret = snd_soc_dapm_force_enable_pin( + snd_soc_codec_get_dapm(codec), + "MICBIAS_REGULATOR"); + snd_soc_dapm_sync(snd_soc_codec_get_dapm(codec)); + } + supply->ondemand_supply_count++; + } else { + if (supply->ondemand_supply_count > 0) + supply->ondemand_supply_count--; + if (!(supply->ondemand_supply_count)) { + ret = snd_soc_dapm_disable_pin( + snd_soc_codec_get_dapm(codec), + "MICBIAS_REGULATOR"); + snd_soc_dapm_sync(snd_soc_codec_get_dapm(codec)); + } + } + + if (ret) + dev_err(codec->dev, "%s: Failed to %s external micbias source\n", + __func__, turn_on ? "enable" : "disabled"); + else + dev_dbg(codec->dev, "%s: %s external micbias source\n", + __func__, turn_on ? "Enabled" : "Disabled"); + + return ret; +} + +static void tavil_mbhc_mbhc_bias_control(struct snd_soc_codec *codec, + bool enable) +{ + if (enable) + snd_soc_update_bits(codec, WCD934X_ANA_MBHC_ELECT, + 0x01, 0x01); + else + snd_soc_update_bits(codec, WCD934X_ANA_MBHC_ELECT, + 0x01, 0x00); +} + +static void tavil_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; + } + /* + * Tavil just needs one set of thresholds for button detection + * due to micbias voltage ramp to pullup upon button press. So + * btn_low and is_micbias are ignored and always program button + * thresholds using btn_high. + */ + for (i = 0; i < num_btn; i++) { + vth = ((btn_high[i] * 2) / 25) & 0x3F; + snd_soc_update_bits(codec, WCD934X_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 tavil_mbhc_lock_sleep(struct wcd_mbhc *mbhc, bool lock) +{ + struct snd_soc_codec *codec = mbhc->codec; + struct wcd9xxx *wcd9xxx = dev_get_drvdata(codec->dev->parent); + struct wcd9xxx_core_resource *core_res = + &wcd9xxx->core_res; + bool ret = 0; + + if (lock) + ret = wcd9xxx_lock_sleep(core_res); + else + wcd9xxx_unlock_sleep(core_res); + + return ret; +} + +static int tavil_mbhc_register_notifier(struct wcd_mbhc *mbhc, + struct notifier_block *nblock, + bool enable) +{ + struct wcd934x_mbhc *wcd934x_mbhc; + + wcd934x_mbhc = container_of(mbhc, struct wcd934x_mbhc, wcd_mbhc); + + if (enable) + return blocking_notifier_chain_register(&wcd934x_mbhc->notifier, + nblock); + else + return blocking_notifier_chain_unregister( + &wcd934x_mbhc->notifier, nblock); +} + +static bool tavil_mbhc_micb_en_status(struct wcd_mbhc *mbhc, int micb_num) +{ + u8 val; + + if (micb_num == MIC_BIAS_2) { + val = (snd_soc_read(mbhc->codec, WCD934X_ANA_MICB2) >> 6); + if (val == 0x01) + return true; + } + return false; +} + +static bool tavil_mbhc_hph_pa_on_status(struct snd_soc_codec *codec) +{ + return (snd_soc_read(codec, WCD934X_ANA_HPH) & 0xC0) ? true : false; +} + +static void tavil_mbhc_hph_l_pull_up_control( + struct snd_soc_codec *codec, + enum mbhc_hs_pullup_iref pull_up_cur) +{ + /* Default pull up current to 2uA */ + if (pull_up_cur < I_OFF || pull_up_cur > I_3P0_UA || + pull_up_cur == I_DEFAULT) + pull_up_cur = I_2P0_UA; + + dev_dbg(codec->dev, "%s: HS pull up current:%d\n", + __func__, pull_up_cur); + + snd_soc_update_bits(codec, WCD934X_MBHC_NEW_PLUG_DETECT_CTL, + 0xC0, pull_up_cur << 6); +} + +static int tavil_mbhc_request_micbias(struct snd_soc_codec *codec, + int micb_num, int req) +{ + int ret; + + /* + * If micbias is requested, make sure that there + * is vote to enable mclk + */ + if (req == MICB_ENABLE) + tavil_cdc_mclk_enable(codec, true); + + ret = tavil_micbias_control(codec, micb_num, req, false); + + /* + * Release vote for mclk while requesting for + * micbias disable + */ + if (req == MICB_DISABLE) + tavil_cdc_mclk_enable(codec, false); + + return ret; +} + +static void tavil_mbhc_micb_ramp_control(struct snd_soc_codec *codec, + bool enable) +{ + if (enable) { + snd_soc_update_bits(codec, WCD934X_ANA_MICB2_RAMP, + 0x1C, 0x0C); + snd_soc_update_bits(codec, WCD934X_ANA_MICB2_RAMP, + 0x80, 0x80); + } else { + snd_soc_update_bits(codec, WCD934X_ANA_MICB2_RAMP, + 0x80, 0x00); + snd_soc_update_bits(codec, WCD934X_ANA_MICB2_RAMP, + 0x1C, 0x00); + } +} + +static struct firmware_cal *tavil_get_hwdep_fw_cal(struct wcd_mbhc *mbhc, + enum wcd_cal_type type) +{ + struct wcd934x_mbhc *wcd934x_mbhc; + struct firmware_cal *hwdep_cal; + struct snd_soc_codec *codec = mbhc->codec; + + wcd934x_mbhc = container_of(mbhc, struct wcd934x_mbhc, wcd_mbhc); + + if (!codec) { + pr_err("%s: NULL codec pointer\n", __func__); + return NULL; + } + hwdep_cal = wcdcal_get_fw_cal(wcd934x_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 tavil_mbhc_micb_ctrl_threshold_mic(struct snd_soc_codec *codec, + int micb_num, bool req_en) +{ + struct wcd9xxx_pdata *pdata = dev_get_platdata(codec->dev->parent); + 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 = tavil_mbhc_micb_adjust_voltage(codec, micb_mv, MIC_BIAS_2); + + return rc; +} + +static inline void tavil_mbhc_get_result_params(struct wcd9xxx *wcd9xxx, + 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(wcd9xxx->regmap, WCD934X_ANA_MBHC_ZDET, 0x20, 0x20); + for (i = 0; i < TAVIL_ZDET_NUM_MEASUREMENTS; i++) { + regmap_read(wcd9xxx->regmap, WCD934X_ANA_MBHC_RESULT_2, &val); + if (val & 0x80) + break; + } + val = val << 0x8; + regmap_read(wcd9xxx->regmap, WCD934X_ANA_MBHC_RESULT_1, &val1); + val |= val1; + regmap_update_bits(wcd9xxx->regmap, WCD934X_ANA_MBHC_ZDET, 0x20, 0x00); + x1 = TAVIL_MBHC_GET_X1(val); + c1 = TAVIL_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(wcd9xxx->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 = (TAVIL_MBHC_ZDET_CONST * 1000) / denom; + else if (x1 < minCode_param[noff]) + *zdet = TAVIL_ZDET_FLOATING_IMPEDANCE; + + dev_dbg(wcd9xxx->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(wcd9xxx->regmap, + WCD934X_ANA_MBHC_RESULT_1, (u8 *)&val, 2); + x1 = TAVIL_MBHC_GET_X1(val); + i++; + if (i == TAVIL_ZDET_NUM_MEASUREMENTS) + break; + } +} + +static void tavil_mbhc_zdet_ramp(struct snd_soc_codec *codec, + struct tavil_mbhc_zdet_param *zdet_param, + int32_t *zl, int32_t *zr, s16 *d1_a) +{ + struct wcd9xxx *wcd9xxx = dev_get_drvdata(codec->dev->parent); + int32_t zdet = 0; + + snd_soc_update_bits(codec, WCD934X_MBHC_NEW_ZDET_ANA_CTL, 0x70, + zdet_param->ldo_ctl << 4); + snd_soc_update_bits(codec, WCD934X_ANA_MBHC_BTN5, 0xFC, + zdet_param->btn5); + snd_soc_update_bits(codec, WCD934X_ANA_MBHC_BTN6, 0xFC, + zdet_param->btn6); + snd_soc_update_bits(codec, WCD934X_ANA_MBHC_BTN7, 0xFC, + zdet_param->btn7); + snd_soc_update_bits(codec, WCD934X_MBHC_NEW_ZDET_ANA_CTL, 0x0F, + zdet_param->noff); + snd_soc_update_bits(codec, WCD934X_MBHC_NEW_ZDET_RAMP_CTL, 0x0F, + zdet_param->nshift); + + if (!zl) + goto z_right; + /* Start impedance measurement for HPH_L */ + regmap_update_bits(wcd9xxx->regmap, + WCD934X_ANA_MBHC_ZDET, 0x80, 0x80); + dev_dbg(wcd9xxx->dev, "%s: ramp for HPH_L, noff = %d\n", + __func__, zdet_param->noff); + tavil_mbhc_get_result_params(wcd9xxx, d1_a, zdet_param->noff, &zdet); + regmap_update_bits(wcd9xxx->regmap, + WCD934X_ANA_MBHC_ZDET, 0x80, 0x00); + + *zl = zdet; + +z_right: + if (!zr) + return; + /* Start impedance measurement for HPH_R */ + regmap_update_bits(wcd9xxx->regmap, + WCD934X_ANA_MBHC_ZDET, 0x40, 0x40); + dev_dbg(wcd9xxx->dev, "%s: ramp for HPH_R, noff = %d\n", + __func__, zdet_param->noff); + tavil_mbhc_get_result_params(wcd9xxx, d1_a, zdet_param->noff, &zdet); + regmap_update_bits(wcd9xxx->regmap, + WCD934X_ANA_MBHC_ZDET, 0x40, 0x00); + + *zr = zdet; +} + +static inline void tavil_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 < (TAVIL_ZDET_VAL_400/1000)) + q1 = snd_soc_read(codec, + WCD934X_CHIP_TIER_CTRL_EFUSE_VAL_OUT1 + (2 * flag_l_r)); + else + q1 = snd_soc_read(codec, + WCD934X_CHIP_TIER_CTRL_EFUSE_VAL_OUT2 + (2 * flag_l_r)); + if (q1 & 0x80) + q1_cal = (10000 - ((q1 & 0x7F) * 25)); + else + q1_cal = (10000 + (q1 * 25)); + if (q1_cal > 0) + *z_val = ((*z_val) * 10000) / q1_cal; +} + +static void tavil_wcd_mbhc_calc_impedance(struct wcd_mbhc *mbhc, uint32_t *zl, + uint32_t *zr) +{ + struct snd_soc_codec *codec = mbhc->codec; + struct wcd9xxx *wcd9xxx = dev_get_drvdata(codec->dev->parent); + s16 reg0, reg1, reg2, reg3, reg4; + int32_t z1L, z1R, z1Ls; + int zMono, z_diff1, z_diff2; + bool is_fsm_disable = false; + struct tavil_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 tavil_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, WCD934X_ANA_MBHC_BTN5); + reg1 = snd_soc_read(codec, WCD934X_ANA_MBHC_BTN6); + reg2 = snd_soc_read(codec, WCD934X_ANA_MBHC_BTN7); + reg3 = snd_soc_read(codec, WCD934X_MBHC_CTL_CLK); + reg4 = snd_soc_read(codec, WCD934X_MBHC_NEW_ZDET_ANA_CTL); + + if (snd_soc_read(codec, WCD934X_ANA_MBHC_ELECT) & 0x80) { + is_fsm_disable = true; + regmap_update_bits(wcd9xxx->regmap, + WCD934X_ANA_MBHC_ELECT, 0x80, 0x00); + } + + /* For NO-jack, disable L_DET_EN before Z-det measurements */ + if (mbhc->hphl_swh) + regmap_update_bits(wcd9xxx->regmap, + WCD934X_ANA_MBHC_MECH, 0x80, 0x00); + + /* Turn off 100k pull down on HPHL */ + regmap_update_bits(wcd9xxx->regmap, + WCD934X_ANA_MBHC_MECH, 0x01, 0x00); + + /* First get impedance on Left */ + d1 = d1_a[1]; + zdet_param_ptr = &zdet_param[1]; + tavil_mbhc_zdet_ramp(codec, zdet_param_ptr, &z1L, NULL, d1); + + if (!TAVIL_MBHC_IS_SECOND_RAMP_REQUIRED(z1L)) + goto left_ch_impedance; + + /* Second ramp for left ch */ + if (z1L < TAVIL_ZDET_VAL_32) { + zdet_param_ptr = &zdet_param[0]; + d1 = d1_a[0]; + } else if ((z1L > TAVIL_ZDET_VAL_400) && (z1L <= TAVIL_ZDET_VAL_1200)) { + zdet_param_ptr = &zdet_param[2]; + d1 = d1_a[2]; + } else if (z1L > TAVIL_ZDET_VAL_1200) { + zdet_param_ptr = &zdet_param[3]; + d1 = d1_a[3]; + } + tavil_mbhc_zdet_ramp(codec, zdet_param_ptr, &z1L, NULL, d1); + +left_ch_impedance: + if ((z1L == TAVIL_ZDET_FLOATING_IMPEDANCE) || + (z1L > TAVIL_ZDET_VAL_100K)) { + *zl = TAVIL_ZDET_FLOATING_IMPEDANCE; + zdet_param_ptr = &zdet_param[1]; + d1 = d1_a[1]; + } else { + *zl = z1L/1000; + tavil_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 */ + tavil_mbhc_zdet_ramp(codec, zdet_param_ptr, NULL, &z1R, d1); + if (TAVIL_MBHC_IS_SECOND_RAMP_REQUIRED(z1R)) { + if (((z1R > TAVIL_ZDET_VAL_1200) && + (zdet_param_ptr->noff == 0x6)) || + ((*zl) != TAVIL_ZDET_FLOATING_IMPEDANCE)) + goto right_ch_impedance; + /* Second ramp for right ch */ + if (z1R < TAVIL_ZDET_VAL_32) { + zdet_param_ptr = &zdet_param[0]; + d1 = d1_a[0]; + } else if ((z1R > TAVIL_ZDET_VAL_400) && + (z1R <= TAVIL_ZDET_VAL_1200)) { + zdet_param_ptr = &zdet_param[2]; + d1 = d1_a[2]; + } else if (z1R > TAVIL_ZDET_VAL_1200) { + zdet_param_ptr = &zdet_param[3]; + d1 = d1_a[3]; + } + tavil_mbhc_zdet_ramp(codec, zdet_param_ptr, NULL, &z1R, d1); + } +right_ch_impedance: + if ((z1R == TAVIL_ZDET_FLOATING_IMPEDANCE) || + (z1R > TAVIL_ZDET_VAL_100K)) { + *zr = TAVIL_ZDET_FLOATING_IMPEDANCE; + } else { + *zr = z1R/1000; + tavil_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 == TAVIL_ZDET_FLOATING_IMPEDANCE) && + (*zr == TAVIL_ZDET_FLOATING_IMPEDANCE)) { + dev_dbg(codec->dev, + "%s: plug type is invalid or extension cable\n", + __func__); + goto zdet_complete; + } + if ((*zl == TAVIL_ZDET_FLOATING_IMPEDANCE) || + (*zr == TAVIL_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, WCD934X_HPH_R_ATEST, 0x02, 0x02); + snd_soc_update_bits(codec, WCD934X_HPH_PA_CTL2, 0x40, 0x01); + if (*zl < (TAVIL_ZDET_VAL_32/1000)) + tavil_mbhc_zdet_ramp(codec, &zdet_param[0], &z1Ls, NULL, d1); + else + tavil_mbhc_zdet_ramp(codec, &zdet_param[1], &z1Ls, NULL, d1); + snd_soc_update_bits(codec, WCD934X_HPH_PA_CTL2, 0x40, 0x00); + snd_soc_update_bits(codec, WCD934X_HPH_R_ATEST, 0x02, 0x00); + z1Ls /= 1000; + tavil_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, WCD934X_ANA_MBHC_BTN5, reg0); + snd_soc_write(codec, WCD934X_ANA_MBHC_BTN6, reg1); + snd_soc_write(codec, WCD934X_ANA_MBHC_BTN7, reg2); + /* Turn on 100k pull down on HPHL */ + regmap_update_bits(wcd9xxx->regmap, + WCD934X_ANA_MBHC_MECH, 0x01, 0x01); + + /* For NO-jack, re-enable L_DET_EN after Z-det measurements */ + if (mbhc->hphl_swh) + regmap_update_bits(wcd9xxx->regmap, + WCD934X_ANA_MBHC_MECH, 0x80, 0x80); + + snd_soc_write(codec, WCD934X_MBHC_NEW_ZDET_ANA_CTL, reg4); + snd_soc_write(codec, WCD934X_MBHC_CTL_CLK, reg3); + if (is_fsm_disable) + regmap_update_bits(wcd9xxx->regmap, + WCD934X_ANA_MBHC_ELECT, 0x80, 0x80); +} + +static void tavil_mbhc_gnd_det_ctrl(struct snd_soc_codec *codec, bool enable) +{ + if (enable) { + snd_soc_update_bits(codec, WCD934X_ANA_MBHC_MECH, + 0x02, 0x02); + snd_soc_update_bits(codec, WCD934X_ANA_MBHC_MECH, + 0x40, 0x40); + } else { + snd_soc_update_bits(codec, WCD934X_ANA_MBHC_MECH, + 0x40, 0x00); + snd_soc_update_bits(codec, WCD934X_ANA_MBHC_MECH, + 0x02, 0x00); + } +} + +static void tavil_mbhc_hph_pull_down_ctrl(struct snd_soc_codec *codec, + bool enable) +{ + if (enable) { + snd_soc_update_bits(codec, WCD934X_HPH_PA_CTL2, + 0x40, 0x40); + snd_soc_update_bits(codec, WCD934X_HPH_PA_CTL2, + 0x10, 0x10); + } else { + snd_soc_update_bits(codec, WCD934X_HPH_PA_CTL2, + 0x40, 0x00); + snd_soc_update_bits(codec, WCD934X_HPH_PA_CTL2, + 0x10, 0x00); + } +} + +static const struct wcd_mbhc_cb mbhc_cb = { + .request_irq = tavil_mbhc_request_irq, + .irq_control = tavil_mbhc_irq_control, + .free_irq = tavil_mbhc_free_irq, + .clk_setup = tavil_mbhc_clk_setup, + .map_btn_code_to_num = tavil_mbhc_btn_to_num, + .enable_mb_source = tavil_enable_ext_mb_source, + .mbhc_bias = tavil_mbhc_mbhc_bias_control, + .set_btn_thr = tavil_mbhc_program_btn_thr, + .lock_sleep = tavil_mbhc_lock_sleep, + .register_notifier = tavil_mbhc_register_notifier, + .micbias_enable_status = tavil_mbhc_micb_en_status, + .hph_pa_on_status = tavil_mbhc_hph_pa_on_status, + .hph_pull_up_control = tavil_mbhc_hph_l_pull_up_control, + .mbhc_micbias_control = tavil_mbhc_request_micbias, + .mbhc_micb_ramp_control = tavil_mbhc_micb_ramp_control, + .get_hwdep_fw_cal = tavil_get_hwdep_fw_cal, + .mbhc_micb_ctrl_thr_mic = tavil_mbhc_micb_ctrl_threshold_mic, + .compute_impedance = tavil_wcd_mbhc_calc_impedance, + .mbhc_gnd_det_ctrl = tavil_mbhc_gnd_det_ctrl, + .hph_pull_down_ctrl = tavil_mbhc_hph_pull_down_ctrl, +}; + +static struct regulator *tavil_codec_find_ondemand_regulator( + struct snd_soc_codec *codec, const char *name) +{ + int i; + struct wcd9xxx *wcd9xxx = dev_get_drvdata(codec->dev->parent); + struct wcd9xxx_pdata *pdata = dev_get_platdata(codec->dev->parent); + + for (i = 0; i < wcd9xxx->num_of_supplies; ++i) { + if (pdata->regulator[i].ondemand && + wcd9xxx->supplies[i].supply && + !strcmp(wcd9xxx->supplies[i].supply, name)) + return wcd9xxx->supplies[i].consumer; + } + + dev_dbg(codec->dev, "Warning: regulator not found:%s\n", + name); + return NULL; +} + +/* + * tavil_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 tavil_mbhc_hs_detect(struct snd_soc_codec *codec, + struct wcd_mbhc_config *mbhc_cfg) +{ + struct wcd934x_mbhc *wcd934x_mbhc = tavil_soc_get_mbhc(codec); + + if (!wcd934x_mbhc) { + dev_err(codec->dev, "%s: mbhc not initialized!\n", __func__); + return -EINVAL; + } + + return wcd_mbhc_start(&wcd934x_mbhc->wcd_mbhc, mbhc_cfg); +} +EXPORT_SYMBOL(tavil_mbhc_hs_detect); + +/* + * tavil_mbhc_hs_detect_exit: stop mbhc insertion/removal functionality + * @codec: handle to snd_soc_codec * + */ +void tavil_mbhc_hs_detect_exit(struct snd_soc_codec *codec) +{ + struct wcd934x_mbhc *wcd934x_mbhc = tavil_soc_get_mbhc(codec); + + if (!wcd934x_mbhc) { + dev_err(codec->dev, "%s: mbhc not initialized!\n", __func__); + return; + } + wcd_mbhc_stop(&wcd934x_mbhc->wcd_mbhc); +} +EXPORT_SYMBOL(tavil_mbhc_hs_detect_exit); + +/* + * tavil_mbhc_init: initialize mbhc for tavil + * @mbhc: poniter to wcd934x_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 tavil_mbhc_init(struct wcd934x_mbhc **mbhc, struct snd_soc_codec *codec, + struct fw_info *fw_data) +{ + struct regulator *supply; + struct wcd934x_mbhc *wcd934x_mbhc; + int ret; + + wcd934x_mbhc = devm_kzalloc(codec->dev, sizeof(struct wcd934x_mbhc), + GFP_KERNEL); + if (!wcd934x_mbhc) + return -ENOMEM; + + wcd934x_mbhc->wcd9xxx = dev_get_drvdata(codec->dev->parent); + wcd934x_mbhc->fw_data = fw_data; + BLOCKING_INIT_NOTIFIER_HEAD(&wcd934x_mbhc->notifier); + + ret = wcd_mbhc_init(&wcd934x_mbhc->wcd_mbhc, codec, &mbhc_cb, &intr_ids, + wcd_mbhc_registers, TAVIL_ZDET_SUPPORTED); + if (ret) { + dev_err(codec->dev, "%s: mbhc initialization failed\n", + __func__); + goto err; + } + + supply = tavil_codec_find_ondemand_regulator(codec, + on_demand_supply_name[WCD934X_ON_DEMAND_MICBIAS]); + if (supply) { + wcd934x_mbhc->on_demand_list[ + WCD934X_ON_DEMAND_MICBIAS].supply = + supply; + wcd934x_mbhc->on_demand_list[ + WCD934X_ON_DEMAND_MICBIAS].ondemand_supply_count = + 0; + } + + snd_soc_update_bits(codec, WCD934X_MBHC_NEW_CTL_1, 0x04, 0x04); + snd_soc_update_bits(codec, WCD934X_MBHC_CTL_BCS, 0x01, 0x01); + + (*mbhc) = wcd934x_mbhc; + + return 0; +err: + devm_kfree(codec->dev, wcd934x_mbhc); + return ret; +} +EXPORT_SYMBOL(tavil_mbhc_init); + +/* + * tavil_mbhc_deinit: deinitialize mbhc for tavil + * @codec: handle to snd_soc_codec * + */ +void tavil_mbhc_deinit(struct snd_soc_codec *codec) +{ + struct wcd934x_mbhc *wcd934x_mbhc = tavil_soc_get_mbhc(codec); + + if (!wcd934x_mbhc) + devm_kfree(codec->dev, wcd934x_mbhc); +} +EXPORT_SYMBOL(tavil_mbhc_deinit); diff --git a/sound/soc/codecs/wcd934x/wcd934x-mbhc.h b/sound/soc/codecs/wcd934x/wcd934x-mbhc.h new file mode 100644 index 000000000000..b747b120b605 --- /dev/null +++ b/sound/soc/codecs/wcd934x/wcd934x-mbhc.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2015-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. + */ +#ifndef __WCD934X_MBHC_H__ +#define __WCD934X_MBHC_H__ +#include "../wcd-mbhc-v2.h" + +enum wcd934x_on_demand_supply_name { + WCD934X_ON_DEMAND_MICBIAS = 0, + WCD934X_ON_DEMAND_SUPPLIES_MAX, +}; + +struct wcd934x_on_demand_supply { + struct regulator *supply; + int ondemand_supply_count; +}; + +struct wcd934x_mbhc { + struct wcd_mbhc wcd_mbhc; + struct blocking_notifier_head notifier; + struct wcd934x_on_demand_supply on_demand_list[ + WCD934X_ON_DEMAND_SUPPLIES_MAX]; + struct wcd9xxx *wcd9xxx; + struct fw_info *fw_data; + bool mbhc_started; +}; + +extern int tavil_mbhc_init(struct wcd934x_mbhc **mbhc, + struct snd_soc_codec *codec, + struct fw_info *fw_data); +extern void tavil_mbhc_hs_detect_exit(struct snd_soc_codec *codec); +extern int tavil_mbhc_hs_detect(struct snd_soc_codec *codec, + struct wcd_mbhc_config *mbhc_cfg); +extern void tavil_mbhc_deinit(struct snd_soc_codec *codec); +#endif /* __WCD934X_MBHC_H__ */ + + diff --git a/sound/soc/codecs/wcd934x/wcd934x.c b/sound/soc/codecs/wcd934x/wcd934x.c index 8cd6d56b9a06..631727fbdd8c 100644 --- a/sound/soc/codecs/wcd934x/wcd934x.c +++ b/sound/soc/codecs/wcd934x/wcd934x.c @@ -41,6 +41,7 @@ #include #include #include "wcd934x.h" +#include "wcd934x-mbhc.h" #include "wcd934x-routing.h" #include "wcd934x-dsp-cntl.h" #include "../wcd9xxx-common-v2.h" @@ -133,20 +134,6 @@ enum { HPH_PA_DELAY, }; -enum { - MIC_BIAS_1 = 1, - MIC_BIAS_2, - MIC_BIAS_3, - MIC_BIAS_4 -}; - -enum { - MICB_PULLUP_ENABLE, - MICB_PULLUP_DISABLE, - MICB_ENABLE, - MICB_DISABLE, -}; - enum { AIF1_PB = 0, AIF1_CAP, @@ -453,6 +440,8 @@ struct tavil_priv { s32 dmic_0_1_clk_cnt; s32 dmic_2_3_clk_cnt; s32 dmic_4_5_clk_cnt; + s32 micb_ref[TAVIL_MAX_MICBIAS]; + s32 pullup_ref[TAVIL_MAX_MICBIAS]; /* compander */ int comp_enabled[COMPANDER_MAX]; @@ -475,9 +464,13 @@ struct tavil_priv { struct wcd9xxx_resmgr_v2 *resmgr; struct wcd934x_swr swr; + struct mutex micb_lock; struct clk *wcd_ext_clk; + /* mbhc module */ + struct wcd934x_mbhc *mbhc; + struct mutex codec_mutex; struct work_struct tavil_add_child_devices_work; struct hpf_work tx_hpf_work[WCD934X_NUM_DECIMATORS]; @@ -2594,13 +2587,21 @@ static int tavil_codec_enable_dmic(struct snd_soc_dapm_widget *w, return 0; } -static int tavil_micbias_control(struct snd_soc_codec *codec, - int micb_num, - int req, bool is_dapm) +/* + * tavil_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 tavil_mbhc_micb_adjust_voltage(struct snd_soc_codec *codec, + int req_volt, int micb_num) { - - - u16 micb_reg; + struct tavil_priv *tavil = 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: @@ -2615,22 +2616,167 @@ static int tavil_micbias_control(struct snd_soc_codec *codec, case MIC_BIAS_4: micb_reg = WCD934X_ANA_MICB4; break; + default: + return -EINVAL; + } + mutex_lock(&tavil->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 = wcd934x_get_micb_vout_ctl_val(req_volt); + if (IS_ERR_VALUE(req_vout_ctl)) { + 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(&tavil->micb_lock); + return ret; +} +EXPORT_SYMBOL(tavil_mbhc_micb_adjust_voltage); + +/* + * tavil_micbias_control: enable/disable micbias + * @codec: handle to snd_soc_codec * + * @micb_num: micbias to be enabled/disabled, e.g. micbias1 or micbias2 + * @req: control requested, enable/disable or pullup enable/disable + * @is_dapm: triggered by dapm or not + * + * return 0 if control is success or error code in case of failure + */ +int tavil_micbias_control(struct snd_soc_codec *codec, + int micb_num, int req, bool is_dapm) +{ + struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); + int micb_index = micb_num - 1; + u16 micb_reg; + int pre_off_event = 0, post_off_event = 0; + int post_on_event = 0, post_dapm_off = 0; + int post_dapm_on = 0; + + if ((micb_index < 0) || (micb_index > TAVIL_MAX_MICBIAS - 1)) { + dev_err(codec->dev, "%s: Invalid micbias index, micb_ind:%d\n", + __func__, micb_index); + return -EINVAL; + } + + switch (micb_num) { + case MIC_BIAS_1: + micb_reg = WCD934X_ANA_MICB1; + break; + case MIC_BIAS_2: + micb_reg = WCD934X_ANA_MICB2; + pre_off_event = WCD_EVENT_PRE_MICBIAS_2_OFF; + post_off_event = WCD_EVENT_POST_MICBIAS_2_OFF; + post_on_event = WCD_EVENT_POST_MICBIAS_2_ON; + post_dapm_on = WCD_EVENT_POST_DAPM_MICBIAS_2_ON; + post_dapm_off = WCD_EVENT_POST_DAPM_MICBIAS_2_OFF; + break; + case MIC_BIAS_3: + micb_reg = WCD934X_ANA_MICB3; + break; + case MIC_BIAS_4: + micb_reg = WCD934X_ANA_MICB4; + break; default: dev_err(codec->dev, "%s: Invalid micbias number: %d\n", __func__, micb_num); return -EINVAL; } + mutex_lock(&tavil->micb_lock); switch (req) { + case MICB_PULLUP_ENABLE: + tavil->pullup_ref[micb_index]++; + if ((tavil->pullup_ref[micb_index] == 1) && + (tavil->micb_ref[micb_index] == 0)) + snd_soc_update_bits(codec, micb_reg, 0xC0, 0x80); + break; + case MICB_PULLUP_DISABLE: + tavil->pullup_ref[micb_index]--; + if ((tavil->pullup_ref[micb_index] == 0) && + (tavil->micb_ref[micb_index] == 0)) + snd_soc_update_bits(codec, micb_reg, 0xC0, 0x00); + break; case MICB_ENABLE: - snd_soc_update_bits(codec, micb_reg, 0xC0, 0x40); + tavil->micb_ref[micb_index]++; + if (tavil->micb_ref[micb_index] == 1) { + snd_soc_update_bits(codec, micb_reg, 0xC0, 0x40); + if (post_on_event && tavil->mbhc) + blocking_notifier_call_chain( + &tavil->mbhc->notifier, + post_on_event, + &tavil->mbhc->wcd_mbhc); + } + if (is_dapm && post_dapm_on && tavil->mbhc) + blocking_notifier_call_chain(&tavil->mbhc->notifier, + post_dapm_on, &tavil->mbhc->wcd_mbhc); break; case MICB_DISABLE: - snd_soc_update_bits(codec, micb_reg, 0xC0, 0x80); + tavil->micb_ref[micb_index]--; + if ((tavil->micb_ref[micb_index] == 0) && + (tavil->pullup_ref[micb_index] > 0)) + snd_soc_update_bits(codec, micb_reg, 0xC0, 0x80); + else if ((tavil->micb_ref[micb_index] == 0) && + (tavil->pullup_ref[micb_index] == 0)) { + if (pre_off_event && tavil->mbhc) + blocking_notifier_call_chain( + &tavil->mbhc->notifier, + pre_off_event, + &tavil->mbhc->wcd_mbhc); + snd_soc_update_bits(codec, micb_reg, 0xC0, 0x00); + if (post_off_event && tavil->mbhc) + blocking_notifier_call_chain( + &tavil->mbhc->notifier, + post_off_event, + &tavil->mbhc->wcd_mbhc); + } + if (is_dapm && post_dapm_off && tavil->mbhc) + blocking_notifier_call_chain(&tavil->mbhc->notifier, + post_dapm_off, &tavil->mbhc->wcd_mbhc); + break; }; + dev_dbg(codec->dev, "%s: micb_num:%d, micb_ref: %d, pullup_ref: %d\n", + __func__, micb_num, tavil->micb_ref[micb_index], + tavil->pullup_ref[micb_index]); + + mutex_unlock(&tavil->micb_lock); + return 0; } +EXPORT_SYMBOL(tavil_micbias_control); static int __tavil_codec_enable_micbias(struct snd_soc_dapm_widget *w, int event) @@ -5474,7 +5620,13 @@ static void tavil_cleanup_irqs(struct tavil_priv *tavil) wcd9xxx_free_irq(core_res, WCD9XXX_IRQ_SLIMBUS, tavil); } -static int wcd934x_get_micb_vout_ctl_val(u32 micb_mv) +/* + * wcd934x_get_micb_vout_ctl_val: converts micbias from volts to register value + * @micb_mv: micbias in mv + * + * return register value converted + */ +int wcd934x_get_micb_vout_ctl_val(u32 micb_mv) { /* min micbias voltage is 1V and maximum is 2.85V */ if (micb_mv < 1000 || micb_mv > 2850) { @@ -5484,6 +5636,7 @@ static int wcd934x_get_micb_vout_ctl_val(u32 micb_mv) return (micb_mv - 1000) / 50; } +EXPORT_SYMBOL(wcd934x_get_micb_vout_ctl_val); static int tavil_handle_pdata(struct tavil_priv *tavil, struct wcd9xxx_pdata *pdata) @@ -5563,6 +5716,31 @@ static int tavil_wdsp_initialize(struct snd_soc_codec *codec) return ret; } +/* + * tavil_soc_get_mbhc: get wcd934x_mbhc handle of corresponding codec + * @codec: handle to snd_soc_codec * + * + * return wcd934x_mbhc handle or error code in case of failure + */ +struct wcd934x_mbhc *tavil_soc_get_mbhc(struct snd_soc_codec *codec) +{ + struct tavil_priv *tavil; + + if (!codec) { + pr_err("%s: Invalid params, NULL codec\n", __func__); + return NULL; + } + tavil = snd_soc_codec_get_drvdata(codec); + + if (!tavil) { + pr_err("%s: Invalid params, NULL tavil\n", __func__); + return NULL; + } + + return tavil->mbhc; +} +EXPORT_SYMBOL(tavil_soc_get_mbhc); + static int tavil_soc_codec_probe(struct snd_soc_codec *codec) { struct wcd9xxx *control; @@ -5606,6 +5784,13 @@ static int tavil_soc_codec_probe(struct snd_soc_codec *codec) goto err_hwdep; } + /* Initialize MBHC module */ + ret = tavil_mbhc_init(&tavil->mbhc, codec, tavil->fw_data); + if (ret) { + pr_err("%s: mbhc initialization failed\n", __func__); + goto err_hwdep; + } + tavil->codec = codec; for (i = 0; i < COMPANDER_MAX; i++) tavil->comp_enabled[i] = 0; @@ -5686,6 +5871,7 @@ err_pdata: control->tx_chs = NULL; err_hwdep: devm_kfree(codec->dev, tavil->fw_data); + tavil->fw_data = NULL; err: return ret; } @@ -5704,6 +5890,10 @@ static int tavil_soc_codec_remove(struct snd_soc_codec *codec) if (tavil->wdsp_cntl) wcd_dsp_cntl_deinit(&tavil->wdsp_cntl); + /* Deinitialize MBHC module */ + tavil_mbhc_deinit(codec); + tavil->mbhc = NULL; + return 0; } @@ -6212,6 +6402,7 @@ static int tavil_probe(struct platform_device *pdev) tavil->dev = &pdev->dev; INIT_WORK(&tavil->tavil_add_child_devices_work, tavil_add_child_devices); + mutex_init(&tavil->micb_lock); mutex_init(&tavil->swr.read_mutex); mutex_init(&tavil->swr.write_mutex); mutex_init(&tavil->swr.clk_mutex); @@ -6277,6 +6468,7 @@ err_cdc_reg: err_clk: wcd_resmgr_remove(tavil->resmgr); err_resmgr: + mutex_destroy(&tavil->micb_lock); mutex_destroy(&tavil->codec_mutex); mutex_destroy(&tavil->swr.read_mutex); mutex_destroy(&tavil->swr.write_mutex); @@ -6294,6 +6486,7 @@ static int tavil_remove(struct platform_device *pdev) if (!tavil) return -EINVAL; + mutex_destroy(&tavil->micb_lock); mutex_destroy(&tavil->codec_mutex); mutex_destroy(&tavil->swr.read_mutex); mutex_destroy(&tavil->swr.write_mutex); diff --git a/sound/soc/codecs/wcd934x/wcd934x.h b/sound/soc/codecs/wcd934x/wcd934x.h index c8a5328f5a72..8bc2ded2763f 100644 --- a/sound/soc/codecs/wcd934x/wcd934x.h +++ b/sound/soc/codecs/wcd934x/wcd934x.h @@ -16,6 +16,8 @@ #include #include #include "wcd934x-dsp-cntl.h" +#include "../wcd9xxx-common-v2.h" +#include "../wcd-mbhc-v2.h" #define WCD934X_REGISTER_START_OFFSET 0x800 #define WCD934X_SB_PGD_PORT_RX_BASE 0x40 @@ -29,6 +31,13 @@ #define WCD934X_DMIC_CLK_DIV_16 0x5 #define WCD934X_DMIC_CLK_DRIVE_DEFAULT 0x02 +#define TAVIL_MAX_MICBIAS 4 +#define TAVIL_NUM_INTERPOLATORS 9 +#define MAX_ON_DEMAND_SUPPLY_NAME_LENGTH 64 + +/* Convert from vout ctl to micbias voltage in mV */ +#define WCD_VOUT_CTL_TO_MICB(v) (1000 + v * 50) + /* Number of input and output Slimbus port */ enum { WCD934X_RX0 = 0, @@ -146,4 +155,12 @@ extern int tavil_cdc_mclk_enable(struct snd_soc_codec *codec, bool enable); extern int tavil_set_spkr_mode(struct snd_soc_codec *codec, int mode); extern int tavil_set_spkr_gain_offset(struct snd_soc_codec *codec, int offset); extern struct wcd_dsp_cntl *tavil_get_wcd_dsp_cntl(struct device *dev); +extern int wcd934x_get_micb_vout_ctl_val(u32 micb_mv); +extern int tavil_micbias_control(struct snd_soc_codec *codec, + int micb_num, + int req, bool is_dapm); +extern int tavil_mbhc_micb_adjust_voltage(struct snd_soc_codec *codec, + int req_volt, + int micb_num); +extern struct wcd934x_mbhc *tavil_soc_get_mbhc(struct snd_soc_codec *codec); #endif