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

ASoC: rt5640: Add button press support



Enable button press detection for headsets by using the ovcd IRQ to get
notified of button presses.

Signed-off-by: default avatarHans de Goede <hdegoede@redhat.com>
Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent 8210804b
Loading
Loading
Loading
Loading
+151 −6
Original line number Diff line number Diff line
@@ -2119,6 +2119,24 @@ static void rt5640_disable_micbias1_for_ovcd(struct snd_soc_component *component
	snd_soc_dapm_mutex_unlock(dapm);
}

static void rt5640_enable_micbias1_ovcd_irq(struct snd_soc_component *component)
{
	struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component);

	snd_soc_component_update_bits(component, RT5640_IRQ_CTRL2,
		RT5640_IRQ_MB1_OC_MASK, RT5640_IRQ_MB1_OC_NOR);
	rt5640->ovcd_irq_enabled = true;
}

static void rt5640_disable_micbias1_ovcd_irq(struct snd_soc_component *component)
{
	struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component);

	snd_soc_component_update_bits(component, RT5640_IRQ_CTRL2,
		RT5640_IRQ_MB1_OC_MASK, RT5640_IRQ_MB1_OC_BP);
	rt5640->ovcd_irq_enabled = false;
}

static void rt5640_clear_micbias1_ovcd(struct snd_soc_component *component)
{
	snd_soc_component_update_bits(component, RT5640_IRQ_CTRL2,
@@ -2149,10 +2167,80 @@ static bool rt5640_jack_inserted(struct snd_soc_component *component)
		return (val & RT5640_JD_STATUS);
}

/* Jack detect timings */
/* Jack detect and button-press timings */
#define JACK_SETTLE_TIME	100 /* milli seconds */
#define JACK_DETECT_COUNT	5
#define JACK_DETECT_MAXCOUNT	20  /* Aprox. 2 seconds worth of tries */
#define JACK_UNPLUG_TIME	80  /* milli seconds */
#define BP_POLL_TIME		10  /* milli seconds */
#define BP_POLL_MAXCOUNT	200 /* assume something is wrong after this */
#define BP_THRESHOLD		3

static void rt5640_start_button_press_work(struct snd_soc_component *component)
{
	struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component);

	rt5640->poll_count = 0;
	rt5640->press_count = 0;
	rt5640->release_count = 0;
	rt5640->pressed = false;
	rt5640->press_reported = false;
	rt5640_clear_micbias1_ovcd(component);
	schedule_delayed_work(&rt5640->bp_work, msecs_to_jiffies(BP_POLL_TIME));
}

static void rt5640_button_press_work(struct work_struct *work)
{
	struct rt5640_priv *rt5640 =
		container_of(work, struct rt5640_priv, bp_work.work);
	struct snd_soc_component *component = rt5640->component;

	/* Check the jack was not removed underneath us */
	if (!rt5640_jack_inserted(component))
		return;

	if (rt5640_micbias1_ovcd(component)) {
		rt5640->release_count = 0;
		rt5640->press_count++;
		/* Remember till after JACK_UNPLUG_TIME wait */
		if (rt5640->press_count >= BP_THRESHOLD)
			rt5640->pressed = true;
		rt5640_clear_micbias1_ovcd(component);
	} else {
		rt5640->press_count = 0;
		rt5640->release_count++;
	}

	/*
	 * The pins get temporarily shorted on jack unplug, so we poll for
	 * at least JACK_UNPLUG_TIME milli-seconds before reporting a press.
	 */
	rt5640->poll_count++;
	if (rt5640->poll_count < (JACK_UNPLUG_TIME / BP_POLL_TIME)) {
		schedule_delayed_work(&rt5640->bp_work,
				      msecs_to_jiffies(BP_POLL_TIME));
		return;
	}

	if (rt5640->pressed && !rt5640->press_reported) {
		dev_dbg(component->dev, "headset button press\n");
		snd_soc_jack_report(rt5640->jack, SND_JACK_BTN_0,
				    SND_JACK_BTN_0);
		rt5640->press_reported = true;
	}

	if (rt5640->release_count >= BP_THRESHOLD) {
		if (rt5640->press_reported) {
			dev_dbg(component->dev, "headset button release\n");
			snd_soc_jack_report(rt5640->jack, 0, SND_JACK_BTN_0);
		}
		/* Re-enable OVCD IRQ to detect next press */
		rt5640_enable_micbias1_ovcd_irq(component);
		return; /* Stop polling */
	}

	schedule_delayed_work(&rt5640->bp_work, msecs_to_jiffies(BP_POLL_TIME));
}

static int rt5640_detect_headset(struct snd_soc_component *component)
{
@@ -2209,17 +2297,51 @@ static void rt5640_jack_work(struct work_struct *work)
	if (!rt5640_jack_inserted(component)) {
		/* Jack removed, or spurious IRQ? */
		if (rt5640->jack->status & SND_JACK_HEADPHONE) {
			snd_soc_jack_report(rt5640->jack, 0, SND_JACK_HEADSET);
			if (rt5640->jack->status & SND_JACK_MICROPHONE) {
				cancel_delayed_work_sync(&rt5640->bp_work);
				rt5640_disable_micbias1_ovcd_irq(component);
				rt5640_disable_micbias1_for_ovcd(component);
			}
			snd_soc_jack_report(rt5640->jack, 0,
					    SND_JACK_HEADSET | SND_JACK_BTN_0);
			dev_dbg(component->dev, "jack unplugged\n");
		}
	} else if (!(rt5640->jack->status & SND_JACK_HEADPHONE)) {
		/* Jack inserted */
		WARN_ON(rt5640->ovcd_irq_enabled);
		rt5640_enable_micbias1_for_ovcd(component);
		status = rt5640_detect_headset(component);
		if (status == SND_JACK_HEADSET) {
			/* Enable ovcd IRQ for button press detect. */
			rt5640_enable_micbias1_ovcd_irq(component);
		} else {
			/* No more need for overcurrent detect. */
			rt5640_disable_micbias1_for_ovcd(component);

		}
		dev_dbg(component->dev, "detect status %#02x\n", status);
		snd_soc_jack_report(rt5640->jack, status, SND_JACK_HEADSET);
	} else if (rt5640->ovcd_irq_enabled && rt5640_micbias1_ovcd(component)) {
		dev_dbg(component->dev, "OVCD IRQ\n");

		/*
		 * The ovcd IRQ keeps firing while the button is pressed, so
		 * we disable it and start polling the button until released.
		 *
		 * The disable will make the IRQ pin 0 again and since we get
		 * IRQs on both edges (so as to detect both jack plugin and
		 * unplug) this means we will immediately get another IRQ.
		 * The ovcd_irq_enabled check above makes the 2ND IRQ a NOP.
		 */
		rt5640_disable_micbias1_ovcd_irq(component);
		rt5640_start_button_press_work(component);

		/*
		 * If the jack-detect IRQ flag goes high (unplug) after our
		 * above rt5640_jack_inserted() check and before we have
		 * disabled the OVCD IRQ, the IRQ pin will stay high and as
		 * we react to edges, we miss the unplug event -> recheck.
		 */
		queue_work(system_long_wq, &rt5640->jack_work);
	}
}

@@ -2238,6 +2360,7 @@ static void rt5640_cancel_work(void *data)
	struct rt5640_priv *rt5640 = data;

	cancel_work_sync(&rt5640->jack_work);
	cancel_delayed_work_sync(&rt5640->bp_work);
}

static void rt5640_enable_jack_detect(struct snd_soc_component *component,
@@ -2282,10 +2405,25 @@ static void rt5640_enable_jack_detect(struct snd_soc_component *component,
	snd_soc_component_update_bits(component, RT5640_IRQ_CTRL2,
		RT5640_MB1_OC_STKY_MASK, RT5640_MB1_OC_STKY_EN);

	/*
	 * All IRQs get or-ed together, so we need the jack IRQ to report 0
	 * when a jack is inserted so that the OVCD IRQ then toggles the IRQ
	 * pin 0/1 instead of it being stuck to 1. So we invert the JD polarity
	 * on systems where the hardware does not already do this.
	 */
	if (rt5640->jd_inverted)
		snd_soc_component_write(component, RT5640_IRQ_CTRL1,
					RT5640_IRQ_JD_NOR);
	else
		snd_soc_component_write(component, RT5640_IRQ_CTRL1,
					RT5640_IRQ_JD_NOR | RT5640_JD_P_INV);

	rt5640->jack = jack;
	if (rt5640->jack->status & SND_JACK_MICROPHONE) {
		rt5640_enable_micbias1_for_ovcd(component);
		rt5640_enable_micbias1_ovcd_irq(component);
	}

	enable_irq(rt5640->irq);
	/* sync initial jack state */
	queue_work(system_long_wq, &rt5640->jack_work);
@@ -2298,6 +2436,12 @@ static void rt5640_disable_jack_detect(struct snd_soc_component *component)
	disable_irq(rt5640->irq);
	rt5640_cancel_work(rt5640);

	if (rt5640->jack->status & SND_JACK_MICROPHONE) {
		rt5640_disable_micbias1_ovcd_irq(component);
		rt5640_disable_micbias1_for_ovcd(component);
		snd_soc_jack_report(rt5640->jack, 0, SND_JACK_BTN_0);
	}

	rt5640->jack = NULL;
}

@@ -2677,6 +2821,7 @@ static int rt5640_i2c_probe(struct i2c_client *i2c,

	rt5640->hp_mute = 1;
	rt5640->irq = i2c->irq;
	INIT_DELAYED_WORK(&rt5640->bp_work, rt5640_button_press_work);
	INIT_WORK(&rt5640->jack_work, rt5640_jack_work);

	/* Make sure work is stopped on probe-error / remove */
+8 −1
Original line number Diff line number Diff line
@@ -2139,7 +2139,14 @@ struct rt5640_priv {
	bool hp_mute;
	bool asrc_en;

	/* Jack detect data */
	/* Jack and button detect data */
	bool ovcd_irq_enabled;
	bool pressed;
	bool press_reported;
	int press_count;
	int release_count;
	int poll_count;
	struct delayed_work bp_work;
	struct work_struct jack_work;
	struct snd_soc_jack *jack;
	unsigned int jd_src;