Unverified Commit 82225766 authored by Hans de Goede's avatar Hans de Goede Committed by Mark Brown
Browse files

ASoC: es8316: Add jack-detect support



Adding jack-detect support may seem weird for a codec with only
a single output, but it is necessary. The ES8316 appnote showing
the intended usage uses a jack-receptacle which physically disconnects
the speakers from the output when a jack is plugged in.

But all 3 devices using the es8316 which I have (2 Cherry Trail
devices and one Bay Trail CR device), use an analog mux to disconnect
the speakers, driven by a GPIO. In order to enable/disable the speakers
at the right time, we need jack-detect.

The same goes for the microphone where we must correctly set the mux
for the single ADC to either the internal or the headset microphone.

All devices I have support the es8316's builtin jack-detect functionality.

Signed-off-by: default avatarHans de Goede <hdegoede@redhat.com>
Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent e1de3d23
Loading
Loading
Loading
Loading
+191 −4
Original line number Diff line number Diff line
@@ -15,12 +15,14 @@
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/mod_devicetable.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/tlv.h>
#include <sound/jack.h>
#include "es8316.h"

/* In slave mode at single speed, the codec is documented as accepting 5
@@ -33,6 +35,11 @@ static const unsigned int supported_mclk_lrck_ratios[] = {
};

struct es8316_priv {
	struct mutex lock;
	struct regmap *regmap;
	struct snd_soc_component *component;
	struct snd_soc_jack *jack;
	int irq;
	unsigned int sysclk;
	unsigned int allowed_rates[NR_SUPPORTED_MCLK_LRCK_RATIOS];
	struct snd_pcm_hw_constraint_list sysclk_constraints;
@@ -529,8 +536,162 @@ static struct snd_soc_dai_driver es8316_dai = {
	.symmetric_rates = 1,
};

static void es8316_enable_micbias_for_mic_gnd_short_detect(
	struct snd_soc_component *component)
{
	struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);

	snd_soc_dapm_mutex_lock(dapm);
	snd_soc_dapm_force_enable_pin_unlocked(dapm, "Bias");
	snd_soc_dapm_force_enable_pin_unlocked(dapm, "Analog power");
	snd_soc_dapm_force_enable_pin_unlocked(dapm, "Mic Bias");
	snd_soc_dapm_sync_unlocked(dapm);
	snd_soc_dapm_mutex_unlock(dapm);

	msleep(20);
}

static void es8316_disable_micbias_for_mic_gnd_short_detect(
	struct snd_soc_component *component)
{
	struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);

	snd_soc_dapm_mutex_lock(dapm);
	snd_soc_dapm_disable_pin_unlocked(dapm, "Mic Bias");
	snd_soc_dapm_disable_pin_unlocked(dapm, "Analog power");
	snd_soc_dapm_disable_pin_unlocked(dapm, "Bias");
	snd_soc_dapm_sync_unlocked(dapm);
	snd_soc_dapm_mutex_unlock(dapm);
}

static irqreturn_t es8316_irq(int irq, void *data)
{
	struct es8316_priv *es8316 = data;
	struct snd_soc_component *comp = es8316->component;
	unsigned int flags;

	mutex_lock(&es8316->lock);

	regmap_read(es8316->regmap, ES8316_GPIO_FLAG, &flags);
	if (flags == 0x00)
		goto out; /* Powered-down / reset */

	/* Catch spurious IRQ before set_jack is called */
	if (!es8316->jack)
		goto out;

	dev_dbg(comp->dev, "gpio flags %#04x\n", flags);
	if (flags & ES8316_GPIO_FLAG_HP_NOT_INSERTED) {
		/* Jack removed, or spurious IRQ? */
		if (es8316->jack->status & SND_JACK_MICROPHONE)
			es8316_disable_micbias_for_mic_gnd_short_detect(comp);

		if (es8316->jack->status & SND_JACK_HEADPHONE) {
			snd_soc_jack_report(es8316->jack, 0,
					    SND_JACK_HEADSET | SND_JACK_BTN_0);
			dev_dbg(comp->dev, "jack unplugged\n");
		}
	} else if (!(es8316->jack->status & SND_JACK_HEADPHONE)) {
		/* Jack inserted, determine type */
		es8316_enable_micbias_for_mic_gnd_short_detect(comp);
		regmap_read(es8316->regmap, ES8316_GPIO_FLAG, &flags);
		dev_dbg(comp->dev, "gpio flags %#04x\n", flags);
		if (flags & ES8316_GPIO_FLAG_HP_NOT_INSERTED) {
			/* Jack unplugged underneath us */
			es8316_disable_micbias_for_mic_gnd_short_detect(comp);
		} else if (flags & ES8316_GPIO_FLAG_GM_NOT_SHORTED) {
			/* Open, headset */
			snd_soc_jack_report(es8316->jack,
					    SND_JACK_HEADSET,
					    SND_JACK_HEADSET);
			/* Keep mic-gnd-short detection on for button press */
		} else {
			/* Shorted, headphones */
			snd_soc_jack_report(es8316->jack,
					    SND_JACK_HEADPHONE,
					    SND_JACK_HEADSET);
			/* No longer need mic-gnd-short detection */
			es8316_disable_micbias_for_mic_gnd_short_detect(comp);
		}
	} else if (es8316->jack->status & SND_JACK_MICROPHONE) {
		/* Interrupt while jack inserted, report button state */
		if (flags & ES8316_GPIO_FLAG_GM_NOT_SHORTED) {
			/* Open, button release */
			snd_soc_jack_report(es8316->jack, 0, SND_JACK_BTN_0);
		} else {
			/* Short, button press */
			snd_soc_jack_report(es8316->jack,
					    SND_JACK_BTN_0,
					    SND_JACK_BTN_0);
		}
	}

out:
	mutex_unlock(&es8316->lock);
	return IRQ_HANDLED;
}

static void es8316_enable_jack_detect(struct snd_soc_component *component,
				      struct snd_soc_jack *jack)
{
	struct es8316_priv *es8316 = snd_soc_component_get_drvdata(component);

	mutex_lock(&es8316->lock);

	es8316->jack = jack;

	if (es8316->jack->status & SND_JACK_MICROPHONE)
		es8316_enable_micbias_for_mic_gnd_short_detect(component);

	snd_soc_component_update_bits(component, ES8316_GPIO_DEBOUNCE,
				      ES8316_GPIO_ENABLE_INTERRUPT,
				      ES8316_GPIO_ENABLE_INTERRUPT);

	mutex_unlock(&es8316->lock);

	/* Enable irq and sync initial jack state */
	enable_irq(es8316->irq);
	es8316_irq(es8316->irq, es8316);
}

static void es8316_disable_jack_detect(struct snd_soc_component *component)
{
	struct es8316_priv *es8316 = snd_soc_component_get_drvdata(component);

	disable_irq(es8316->irq);

	mutex_lock(&es8316->lock);

	snd_soc_component_update_bits(component, ES8316_GPIO_DEBOUNCE,
				      ES8316_GPIO_ENABLE_INTERRUPT, 0);

	if (es8316->jack->status & SND_JACK_MICROPHONE) {
		es8316_disable_micbias_for_mic_gnd_short_detect(component);
		snd_soc_jack_report(es8316->jack, 0, SND_JACK_BTN_0);
	}

	es8316->jack = NULL;

	mutex_unlock(&es8316->lock);
}

static int es8316_set_jack(struct snd_soc_component *component,
			   struct snd_soc_jack *jack, void *data)
{
	if (jack)
		es8316_enable_jack_detect(component, jack);
	else
		es8316_disable_jack_detect(component);

	return 0;
}

static int es8316_probe(struct snd_soc_component *component)
{
	struct es8316_priv *es8316 = snd_soc_component_get_drvdata(component);

	es8316->component = component;

	/* Reset codec and enable current state machine */
	snd_soc_component_write(component, ES8316_RESET, 0x3f);
	usleep_range(5000, 5500);
@@ -555,6 +716,7 @@ static int es8316_probe(struct snd_soc_component *component)

static const struct snd_soc_component_driver soc_component_dev_es8316 = {
	.probe			= es8316_probe,
	.set_jack		= es8316_set_jack,
	.controls		= es8316_snd_controls,
	.num_controls		= ARRAY_SIZE(es8316_snd_controls),
	.dapm_widgets		= es8316_dapm_widgets,
@@ -566,18 +728,29 @@ static const struct snd_soc_component_driver soc_component_dev_es8316 = {
	.non_legacy_dai_naming	= 1,
};

static const struct regmap_range es8316_volatile_ranges[] = {
	regmap_reg_range(ES8316_GPIO_FLAG, ES8316_GPIO_FLAG),
};

static const struct regmap_access_table es8316_volatile_table = {
	.yes_ranges	= es8316_volatile_ranges,
	.n_yes_ranges	= ARRAY_SIZE(es8316_volatile_ranges),
};

static const struct regmap_config es8316_regmap = {
	.reg_bits = 8,
	.val_bits = 8,
	.max_register = 0x53,
	.volatile_table	= &es8316_volatile_table,
	.cache_type = REGCACHE_RBTREE,
};

static int es8316_i2c_probe(struct i2c_client *i2c_client,
			    const struct i2c_device_id *id)
{
	struct device *dev = &i2c_client->dev;
	struct es8316_priv *es8316;
	struct regmap *regmap;
	int ret;

	es8316 = devm_kzalloc(&i2c_client->dev, sizeof(struct es8316_priv),
			      GFP_KERNEL);
@@ -586,9 +759,23 @@ static int es8316_i2c_probe(struct i2c_client *i2c_client,

	i2c_set_clientdata(i2c_client, es8316);

	regmap = devm_regmap_init_i2c(i2c_client, &es8316_regmap);
	if (IS_ERR(regmap))
		return PTR_ERR(regmap);
	es8316->regmap = devm_regmap_init_i2c(i2c_client, &es8316_regmap);
	if (IS_ERR(es8316->regmap))
		return PTR_ERR(es8316->regmap);

	es8316->irq = i2c_client->irq;
	mutex_init(&es8316->lock);

	ret = devm_request_threaded_irq(dev, es8316->irq, NULL, es8316_irq,
					IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
					"es8316", es8316);
	if (ret == 0) {
		/* Gets re-enabled by es8316_set_jack() */
		disable_irq(es8316->irq);
	} else {
		dev_warn(dev, "Failed to get IRQ %d: %d\n", es8316->irq, ret);
		es8316->irq = -ENXIO;
	}

	return devm_snd_soc_register_component(&i2c_client->dev,
				      &soc_component_dev_es8316,
+7 −0
Original line number Diff line number Diff line
@@ -126,4 +126,11 @@
#define ES8316_SERDATA2_LEN_16		0x0c
#define ES8316_SERDATA2_LEN_32		0x10

/* ES8316_GPIO_DEBOUNCE	*/
#define ES8316_GPIO_ENABLE_INTERRUPT		0x02

/* ES8316_GPIO_FLAG */
#define ES8316_GPIO_FLAG_GM_NOT_SHORTED		0x02
#define ES8316_GPIO_FLAG_HP_NOT_INSERTED	0x04

#endif