Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 92fad90a authored by Vatsal Bucha's avatar Vatsal Bucha
Browse files

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: default avatarVatsal Bucha <vbucha@codeaurora.org>
parent 9b37ac95
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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
+53 −2
Original line number Diff line number Diff line
@@ -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
+1011 −0

File added.

Preview size limit exceeded, changes collapsed.

+70 −0
Original line number Diff line number Diff line
/* 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__ */
+244 −4
Original line number Diff line number Diff line
@@ -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,7 +777,7 @@ static int wcd937x_enable_req(struct snd_soc_dapm_widget *w,
	return 0;
}

static int wcd937x_micbias_control(struct snd_soc_codec *codec,
int wcd937x_micbias_control(struct snd_soc_codec *codec,
				int micb_num, int req, bool is_dapm)
{

@@ -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);
}