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

ASoC: rt5640: Add jack-detect support

Add jack-detect support, loosely based on earlier work on this by:

Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Francisco mendez <francisco.mendez@intel.com>

Note getting the OVCD to work reliable was sort of finicky, so there are
quite a few comments on this to hopefully avoid people breaking it in the
future.

This (and the follow-up button press support) has been tested on the
following devices:

Acer Iconia Tab 8 W1-810
Asus T100CHI
Asus T100TA
Asus T200TA
Axxo WT1011
Chuwi Vi8
Dell Venue 8 Pro 5830
HP Pavilion X2 10-n000nd
HP Stream 7
I.T. Works TW891
Lamina I8270
MSI S100
Peaq C1010
Pipo W4
PoV MobiiTAB-P800W (v2.0)
Toshiba Click Mini L9W-B

BugLink: https://bugzilla.kernel.org/show_bug.cgi?id=196377


Signed-off-by: default avatarHans de Goede <hdegoede@redhat.com>
Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent fb509fa9
Loading
Loading
Loading
Loading
+286 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@
#include <linux/spi/spi.h>
#include <linux/acpi.h>
#include <sound/core.h>
#include <sound/jack.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
@@ -2093,6 +2094,224 @@ int rt5640_sel_asrc_clk_src(struct snd_soc_component *component,
}
EXPORT_SYMBOL_GPL(rt5640_sel_asrc_clk_src);

static void rt5640_enable_micbias1_for_ovcd(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, "LDO2");
	snd_soc_dapm_force_enable_pin_unlocked(dapm, "MICBIAS1");
	/* OVCD is unreliable when used with RCCLK as sysclk-source */
	snd_soc_dapm_force_enable_pin_unlocked(dapm, "Platform Clock");
	snd_soc_dapm_sync_unlocked(dapm);
	snd_soc_dapm_mutex_unlock(dapm);
}

static void rt5640_disable_micbias1_for_ovcd(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, "Platform Clock");
	snd_soc_dapm_disable_pin_unlocked(dapm, "MICBIAS1");
	snd_soc_dapm_disable_pin_unlocked(dapm, "LDO2");
	snd_soc_dapm_sync_unlocked(dapm);
	snd_soc_dapm_mutex_unlock(dapm);
}

static void rt5640_clear_micbias1_ovcd(struct snd_soc_component *component)
{
	snd_soc_component_update_bits(component, RT5640_IRQ_CTRL2,
		RT5640_MB1_OC_STATUS, 0);
}

static bool rt5640_micbias1_ovcd(struct snd_soc_component *component)
{
	int val;

	val = snd_soc_component_read32(component, RT5640_IRQ_CTRL2);
	dev_dbg(component->dev, "irq ctrl2 %#04x\n", val);

	return (val & RT5640_MB1_OC_STATUS);
}

static bool rt5640_jack_inserted(struct snd_soc_component *component)
{
	struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component);
	int val;

	val = snd_soc_component_read32(component, RT5640_INT_IRQ_ST);
	dev_dbg(component->dev, "irq status %#04x\n", val);

	if (rt5640->jd_inverted)
		return !(val & RT5640_JD_STATUS);
	else
		return (val & RT5640_JD_STATUS);
}

/* Jack detect timings */
#define JACK_SETTLE_TIME	100 /* milli seconds */
#define JACK_DETECT_COUNT	5
#define JACK_DETECT_MAXCOUNT	20  /* Aprox. 2 seconds worth of tries */

static int rt5640_detect_headset(struct snd_soc_component *component)
{
	int i, headset_count = 0, headphone_count = 0;

	/*
	 * We get the insertion event before the jack is fully inserted at which
	 * point the second ring on a TRRS connector may short the 2nd ring and
	 * sleeve contacts, also the overcurrent detection is not entirely
	 * reliable. So we try several times with a wait in between until we
	 * detect the same type JACK_DETECT_COUNT times in a row.
	 */
	for (i = 0; i < JACK_DETECT_MAXCOUNT; i++) {
		/* Clear any previous over-current status flag */
		rt5640_clear_micbias1_ovcd(component);

		msleep(JACK_SETTLE_TIME);

		/* Check the jack is still connected before checking ovcd */
		if (!rt5640_jack_inserted(component))
			return 0;

		if (rt5640_micbias1_ovcd(component)) {
			/*
			 * Over current detected, there is a short between the
			 * 2nd ring contact and the ground, so a TRS connector
			 * without a mic contact and thus plain headphones.
			 */
			dev_dbg(component->dev, "jack mic-gnd shorted\n");
			headset_count = 0;
			headphone_count++;
			if (headphone_count == JACK_DETECT_COUNT)
				return SND_JACK_HEADPHONE;
		} else {
			dev_dbg(component->dev, "jack mic-gnd open\n");
			headphone_count = 0;
			headset_count++;
			if (headset_count == JACK_DETECT_COUNT)
				return SND_JACK_HEADSET;
		}
	}

	dev_err(component->dev, "Error detecting headset vs headphones, bad contact?, assuming headphones\n");
	return SND_JACK_HEADPHONE;
}

static void rt5640_jack_work(struct work_struct *work)
{
	struct rt5640_priv *rt5640 =
		container_of(work, struct rt5640_priv, jack_work);
	struct snd_soc_component *component = rt5640->component;
	int status;

	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);
			dev_dbg(component->dev, "jack unplugged\n");
		}
	} else if (!(rt5640->jack->status & SND_JACK_HEADPHONE)) {
		/* Jack inserted */
		rt5640_enable_micbias1_for_ovcd(component);
		status = rt5640_detect_headset(component);
		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);
	}
}

static irqreturn_t rt5640_irq(int irq, void *data)
{
	struct rt5640_priv *rt5640 = data;

	if (rt5640->jack)
		queue_work(system_long_wq, &rt5640->jack_work);

	return IRQ_HANDLED;
}

static void rt5640_cancel_work(void *data)
{
	struct rt5640_priv *rt5640 = data;

	cancel_work_sync(&rt5640->jack_work);
}

static void rt5640_enable_jack_detect(struct snd_soc_component *component,
				      struct snd_soc_jack *jack)
{
	struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component);

	/* Select JD-source */
	snd_soc_component_update_bits(component, RT5640_JD_CTRL,
		RT5640_JD_MASK, rt5640->jd_src);

	/* Selecting GPIO01 as an interrupt */
	snd_soc_component_update_bits(component, RT5640_GPIO_CTRL1,
		RT5640_GP1_PIN_MASK, RT5640_GP1_PIN_IRQ);

	/* Set GPIO1 output */
	snd_soc_component_update_bits(component, RT5640_GPIO_CTRL3,
		RT5640_GP1_PF_MASK, RT5640_GP1_PF_OUT);

	/* Enabling jd2 in general control 1 */
	snd_soc_component_write(component, RT5640_DUMMY1, 0x3f41);

	/* Enabling jd2 in general control 2 */
	snd_soc_component_write(component, RT5640_DUMMY2, 0x4001);

	snd_soc_component_write(component, RT5640_PR_BASE + RT5640_BIAS_CUR4,
		0xa800 | rt5640->ovcd_sf);

	snd_soc_component_update_bits(component, RT5640_MICBIAS,
		RT5640_MIC1_OVTH_MASK | RT5640_MIC1_OVCD_MASK,
		rt5640->ovcd_th | RT5640_MIC1_OVCD_EN);

	/*
	 * The over-current-detect is only reliable in detecting the absence
	 * of over-current, when the mic-contact in the jack is short-circuited,
	 * the hardware periodically retries if it can apply the bias-current
	 * leading to the ovcd status flip-flopping 1-0-1 with it being 0 about
	 * 10% of the time, as we poll the ovcd status bit we might hit that
	 * 10%, so we enable sticky mode and when checking OVCD we clear the
	 * status, msleep() a bit and then check to get a reliable reading.
	 */
	snd_soc_component_update_bits(component, RT5640_IRQ_CTRL2,
		RT5640_MB1_OC_STKY_MASK, RT5640_MB1_OC_STKY_EN);

	snd_soc_component_write(component, RT5640_IRQ_CTRL1,
				RT5640_IRQ_JD_NOR);

	rt5640->jack = jack;
	enable_irq(rt5640->irq);
	/* sync initial jack state */
	queue_work(system_long_wq, &rt5640->jack_work);
}

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

	disable_irq(rt5640->irq);
	rt5640_cancel_work(rt5640);

	rt5640->jack = NULL;
}

static int rt5640_set_jack(struct snd_soc_component *component,
			   struct snd_soc_jack *jack, void *data)
{
	if (jack)
		rt5640_enable_jack_detect(component, jack);
	else
		rt5640_disable_jack_detect(component);

	return 0;
}

static int rt5640_probe(struct snd_soc_component *component)
{
	struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
@@ -2176,6 +2395,53 @@ static int rt5640_probe(struct snd_soc_component *component)
	if (dmic_en)
		rt5640_dmic_enable(component, dmic1_data_pin, dmic2_data_pin);

	if (device_property_read_u32(component->dev,
				     "realtek,jack-detect-source", &val) == 0) {
		if (val <= RT5640_JD_SRC_GPIO4)
			rt5640->jd_src = val << RT5640_JD_SFT;
		else
			dev_warn(component->dev, "Warning: Invalid jack-detect-source value: %d, leaving jack-detect disabled\n",
				 val);
	}

	if (!device_property_read_bool(component->dev, "realtek,jack-detect-not-inverted"))
		rt5640->jd_inverted = true;

	/*
	 * Testing on various boards has shown that good defaults for the OVCD
	 * threshold and scale-factor are 2000µA and 0.75. For an effective
	 * limit of 1500µA, this seems to be more reliable then 1500µA and 1.0.
	 */
	rt5640->ovcd_th = RT5640_MIC1_OVTH_2000UA;
	rt5640->ovcd_sf = RT5640_MIC_OVCD_SF_0P75;

	if (device_property_read_u32(component->dev,
			"realtek,over-current-threshold-microamp", &val) == 0) {
		switch (val) {
		case 600:
			rt5640->ovcd_th = RT5640_MIC1_OVTH_600UA;
			break;
		case 1500:
			rt5640->ovcd_th = RT5640_MIC1_OVTH_1500UA;
			break;
		case 2000:
			rt5640->ovcd_th = RT5640_MIC1_OVTH_2000UA;
			break;
		default:
			dev_warn(component->dev, "Warning: Invalid over-current-threshold-microamp value: %d, defaulting to 2000uA\n",
				 val);
		}
	}

	if (device_property_read_u32(component->dev,
			"realtek,over-current-scale-factor", &val) == 0) {
		if (val <= RT5640_OVCD_SF_1P5)
			rt5640->ovcd_sf = val << RT5640_MIC_OVCD_SF_SFT;
		else
			dev_warn(component->dev, "Warning: Invalid over-current-scale-factor value: %d, defaulting to 0.75\n",
				 val);
	}

	return 0;
}

@@ -2276,6 +2542,7 @@ static const struct snd_soc_component_driver soc_component_dev_rt5640 = {
	.suspend		= rt5640_suspend,
	.resume			= rt5640_resume,
	.set_bias_level		= rt5640_set_bias_level,
	.set_jack		= rt5640_set_jack,
	.controls		= rt5640_snd_controls,
	.num_controls		= ARRAY_SIZE(rt5640_snd_controls),
	.dapm_widgets		= rt5640_dapm_widgets,
@@ -2409,6 +2676,25 @@ static int rt5640_i2c_probe(struct i2c_client *i2c,
				RT5640_MCLK_DET, RT5640_MCLK_DET);

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

	/* Make sure work is stopped on probe-error / remove */
	ret = devm_add_action_or_reset(&i2c->dev, rt5640_cancel_work, rt5640);
	if (ret)
		return ret;

	ret = devm_request_irq(&i2c->dev, rt5640->irq, rt5640_irq,
			       IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
			       | IRQF_ONESHOT, "rt5640", rt5640);
	if (ret == 0) {
		/* Gets re-enabled by rt5640_set_jack() */
		disable_irq(rt5640->irq);
	} else {
		dev_warn(&i2c->dev, "Failed to reguest IRQ %d: %d\n",
			 rt5640->irq, ret);
		rt5640->irq = -ENXIO;
	}

	return devm_snd_soc_register_component(&i2c->dev,
				      &soc_component_dev_rt5640,
+32 −4
Original line number Diff line number Diff line
@@ -13,6 +13,8 @@
#define _RT5640_H

#include <linux/clk.h>
#include <linux/workqueue.h>
#include <dt-bindings/sound/rt5640.h>

/* Info */
#define RT5640_RESET				0x00
@@ -145,6 +147,7 @@


/* Index of Codec Private Register definition */
#define RT5640_BIAS_CUR4			0x15
#define RT5640_CHPUMP_INT_REG1			0x24
#define RT5640_MAMP_INT_REG2			0x37
#define RT5640_3D_SPK				0x63
@@ -1606,10 +1609,17 @@
#define RT5640_MB2_OC_P_SFT			6
#define RT5640_MB2_OC_P_NOR			(0x0 << 6)
#define RT5640_MB2_OC_P_INV			(0x1 << 6)
#define RT5640_MB1_OC_CLR			(0x1 << 3)
#define RT5640_MB1_OC_CLR_SFT			3
#define RT5640_MB2_OC_CLR			(0x1 << 2)
#define RT5640_MB2_OC_CLR_SFT			2
#define RT5640_MB1_OC_STATUS			(0x1 << 3)
#define RT5640_MB1_OC_STATUS_SFT		3
#define RT5640_MB2_OC_STATUS			(0x1 << 2)
#define RT5640_MB2_OC_STATUS_SFT		2

/* GPIO and Internal Status (0xbf) */
#define RT5640_GPIO1_STATUS			(0x1 << 8)
#define RT5640_GPIO2_STATUS			(0x1 << 7)
#define RT5640_JD_STATUS			(0x1 << 4)
#define RT5640_OVT_STATUS			(0x1 << 3)
#define RT5640_CLS_D_OVCD_STATUS		(0x1 << 0)

/* GPIO Control 1 (0xc0) */
#define RT5640_GP1_PIN_MASK			(0x1 << 15)
@@ -1977,6 +1987,15 @@
#define RT5640_MCLK_DET				(0x1 << 11)

/* Codec Private Register definition */

/* MIC Over current threshold scale factor (0x15) */
#define RT5640_MIC_OVCD_SF_MASK			(0x3 << 8)
#define RT5640_MIC_OVCD_SF_SFT			8
#define RT5640_MIC_OVCD_SF_0P5			(0x0 << 8)
#define RT5640_MIC_OVCD_SF_0P75			(0x1 << 8)
#define RT5640_MIC_OVCD_SF_1P0			(0x2 << 8)
#define RT5640_MIC_OVCD_SF_1P5			(0x3 << 8)

/* 3D Speaker Control (0x63) */
#define RT5640_3D_SPK_MASK			(0x1 << 15)
#define RT5640_3D_SPK_SFT			15
@@ -2106,6 +2125,7 @@ struct rt5640_priv {
	struct clk *mclk;

	int ldo1_en; /* GPIO for LDO1_EN */
	int irq;
	int sysclk;
	int sysclk_src;
	int lrck[RT5640_AIFS];
@@ -2118,6 +2138,14 @@ struct rt5640_priv {

	bool hp_mute;
	bool asrc_en;

	/* Jack detect data */
	struct work_struct jack_work;
	struct snd_soc_jack *jack;
	unsigned int jd_src;
	bool jd_inverted;
	unsigned int ovcd_th;
	unsigned int ovcd_sf;
};

int rt5640_dmic_enable(struct snd_soc_component *component,