build/patch/kernel/archive/sunxi-6.4/patches.megous/ASoC-sunxi-sun8i-codec-Improve-jack-button-handling-and-mic-det.patch

400 lines
14 KiB
Diff

From 17e7e10eac2efe017a03476036a1a53fcf067ca1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20Jirman?= <megi@xff.cz>
Date: Wed, 12 May 2021 21:37:03 +0200
Subject: [PATCH 268/469] ASoC: sunxi: sun8i-codec: Improve jack button
handling and mic detection
Setup ADC so that it samples with period of 16ms, disable smoothing
and enable MDATA threshold (should be below idle voltage/HMIC_DATA
value). Also enable HMIC_N, which makes sure we get HMIC_N samples
after HMIC_DATA crosses the threshold.
This allows us to perform steady state detection of HMIC_DATA, by
comparing current and previous ADC samples, to detect end of the
transient when the user de-presses the button. Otherwise ADC could
sample anywhere within the transient, and the driver may mis-issue
key-press events for other buttons attached to the resistor ladder.
Signed-off-by: Ondrej Jirman <megi@xff.cz>
---
sound/soc/sunxi/sun50i-codec-analog.c | 12 ++
sound/soc/sunxi/sun8i-codec.c | 241 +++++++++++++++++++-------
2 files changed, 188 insertions(+), 65 deletions(-)
diff --git a/sound/soc/sunxi/sun50i-codec-analog.c b/sound/soc/sunxi/sun50i-codec-analog.c
index 7ee72c0537c1..b78af30961f2 100644
--- a/sound/soc/sunxi/sun50i-codec-analog.c
+++ b/sound/soc/sunxi/sun50i-codec-analog.c
@@ -116,6 +116,11 @@
#define SUN50I_ADDA_HS_MBIAS_CTRL 0x0e
#define SUN50I_ADDA_HS_MBIAS_CTRL_MMICBIASEN 7
+#define SUN50I_ADDA_MDET_CTRL 0x1c
+#define SUN50I_ADDA_MDET_CTRL_SELDETADC_FS 4
+#define SUN50I_ADDA_MDET_CTRL_SELDETADC_DB 2
+#define SUN50I_ADDA_MDET_CTRL_SELDETADC_BF 0
+
#define SUN50I_ADDA_JACK_MIC_CTRL 0x1d
#define SUN50I_ADDA_JACK_MIC_CTRL_JACKDETEN 7
#define SUN50I_ADDA_JACK_MIC_CTRL_INNERRESEN 6
@@ -501,6 +506,13 @@ static int sun50i_a64_codec_probe(struct snd_soc_component *component)
codec->internal_bias_resistor <<
SUN50I_ADDA_JACK_MIC_CTRL_INNERRESEN);
+ /* Select sample interval of the ADC sample to 32ms */
+ regmap_update_bits(component->regmap, SUN50I_ADDA_MDET_CTRL,
+ 0x7 << SUN50I_ADDA_MDET_CTRL_SELDETADC_FS |
+ 0x3 << SUN50I_ADDA_MDET_CTRL_SELDETADC_BF,
+ 0x3 << SUN50I_ADDA_MDET_CTRL_SELDETADC_FS |
+ 0x3 << SUN50I_ADDA_MDET_CTRL_SELDETADC_BF);
+
return 0;
}
diff --git a/sound/soc/sunxi/sun8i-codec.c b/sound/soc/sunxi/sun8i-codec.c
index da86e93346ea..f71923eef95c 100644
--- a/sound/soc/sunxi/sun8i-codec.c
+++ b/sound/soc/sunxi/sun8i-codec.c
@@ -15,6 +15,7 @@
#include <linux/input.h>
#include <linux/io.h>
#include <linux/irq.h>
+#include <linux/mutex.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
@@ -124,14 +125,18 @@
#define SUN8I_ADC_VOL_CTRL_ADCL_VOL 8
#define SUN8I_ADC_VOL_CTRL_ADCR_VOL 0
#define SUN8I_HMIC_CTRL1 0x110
+#define SUN8I_HMIC_CTRL1_HMIC_M 12
#define SUN8I_HMIC_CTRL1_HMIC_N 8
+#define SUN8I_HMIC_CTRL1_MDATA_THRESHOLD_DB 5
#define SUN8I_HMIC_CTRL1_JACK_OUT_IRQ_EN 4
#define SUN8I_HMIC_CTRL1_JACK_IN_IRQ_EN 3
#define SUN8I_HMIC_CTRL1_HMIC_DATA_IRQ_EN 0
#define SUN8I_HMIC_CTRL2 0x114
#define SUN8I_HMIC_CTRL2_HMIC_SAMPLE 14
+#define SUN8I_HMIC_CTRL2_HMIC_MDATA_THRESHOLD 8
#define SUN8I_HMIC_CTRL2_HMIC_SF 6
#define SUN8I_HMIC_STS 0x118
+#define SUN8I_HMIC_STS_MDATA_DISCARD 13
#define SUN8I_HMIC_STS_HMIC_DATA 8
#define SUN8I_HMIC_STS_JACK_OUT_IRQ_ST 4
#define SUN8I_HMIC_STS_JACK_IN_IRQ_ST 3
@@ -161,7 +166,9 @@
#define SUN8I_AIF_CLK_CTRL_WORD_SIZ_MASK GENMASK(5, 4)
#define SUN8I_AIF_CLK_CTRL_DATA_FMT_MASK GENMASK(3, 2)
#define SUN8I_AIF3_CLK_CTRL_AIF3_CLK_SRC_MASK GENMASK(1, 0)
+#define SUN8I_HMIC_CTRL1_HMIC_M_MASK GENMASK(15, 12)
#define SUN8I_HMIC_CTRL1_HMIC_N_MASK GENMASK(11, 8)
+#define SUN8I_HMIC_CTRL1_MDATA_THRESHOLD_DB_MASK GENMASK(6, 5)
#define SUN8I_HMIC_CTRL2_HMIC_SAMPLE_MASK GENMASK(15, 14)
#define SUN8I_HMIC_CTRL2_HMIC_SF_MASK GENMASK(7, 6)
#define SUN8I_HMIC_STS_HMIC_DATA_MASK GENMASK(12, 8)
@@ -209,6 +216,12 @@ struct sun8i_codec_quirks {
bool lrck_inversion : 1;
};
+enum {
+ SUN8I_JACK_STATUS_DISCONNECTED,
+ SUN8I_JACK_STATUS_WAITING_HBIAS,
+ SUN8I_JACK_STATUS_CONNECTED,
+};
+
#define AC100_NUM_SUPPLIES 4
struct sun8i_codec {
@@ -221,8 +234,13 @@ struct sun8i_codec {
struct snd_soc_jack jack;
struct delayed_work jack_work;
int jack_irq;
- int jack_pending;
+ int jack_status;
+ int jack_last_sample;
+ int jack_last_btn;
+ ktime_t jack_hbias_ready;
int jack_type;
+ int last_hmic_irq;
+ struct mutex jack_mutex;
unsigned int sysclk_rate;
int sysclk_refcnt;
@@ -1419,16 +1437,19 @@ static int sun8i_codec_component_probe(struct snd_soc_component *component)
BIT(SUN8I_HMIC_CTRL1_JACK_IN_IRQ_EN);
/* Reserved value required for jack IRQs to trigger. */
- regmap_update_bits(scodec->regmap, SUN8I_HMIC_CTRL1,
- SUN8I_HMIC_CTRL1_HMIC_N_MASK,
- 0xf << SUN8I_HMIC_CTRL1_HMIC_N);
+ regmap_write(scodec->regmap, SUN8I_HMIC_CTRL1,
+ 0xf << SUN8I_HMIC_CTRL1_HMIC_N |
+ 0x0 << SUN8I_HMIC_CTRL1_MDATA_THRESHOLD_DB |
+ 0x4 << SUN8I_HMIC_CTRL1_HMIC_M);
- /* Sample the ADC at 64 Hz; average across 2 samples. */
- regmap_update_bits(scodec->regmap, SUN8I_HMIC_CTRL2,
- SUN8I_HMIC_CTRL2_HMIC_SAMPLE_MASK |
- SUN8I_HMIC_CTRL2_HMIC_SF_MASK,
- 0x1 << SUN8I_HMIC_CTRL2_HMIC_SAMPLE |
- 0x1 << SUN8I_HMIC_CTRL2_HMIC_SF);
+ /* Sample the ADC at 128 Hz; bypass smooth filter. */
+ regmap_write(scodec->regmap, SUN8I_HMIC_CTRL2,
+ 0x0 << SUN8I_HMIC_CTRL2_HMIC_SAMPLE |
+ 0x17 << SUN8I_HMIC_CTRL2_HMIC_MDATA_THRESHOLD |
+ 0x0 << SUN8I_HMIC_CTRL2_HMIC_SF);
+
+ /* Do not discard any MDATA, enable user written MDATA threshold. */
+ regmap_write(scodec->regmap, SUN8I_HMIC_STS, 0);
regmap_update_bits(scodec->regmap, SUN8I_HMIC_CTRL1,
irq_mask, irq_mask);
@@ -1485,29 +1506,6 @@ static struct regmap_config sun8i_codec_regmap_config = {
.cache_type = REGCACHE_FLAT,
};
-static int sun8i_codec_read_hmic_button(struct sun8i_codec *scodec)
-{
- unsigned int value;
- int button;
-
- regmap_read(scodec->regmap, SUN8I_HMIC_STS, &value);
- value &= SUN8I_HMIC_STS_HMIC_DATA_MASK;
- value >>= SUN8I_HMIC_STS_HMIC_DATA;
-
- if (value < 0x2)
- button = SND_JACK_BTN_0;
- else if (value < 0x7)
- button = SND_JACK_BTN_1;
- else if (value < 0x10)
- button = SND_JACK_BTN_2;
- else
- button = 0;
-
- dev_dbg(scodec->card->dev, "HMIC ADC read %u => %#x\n", value, button);
-
- return button;
-}
-
static void sun8i_codec_set_hmic_bias(struct sun8i_codec *scodec, bool enable)
{
struct snd_soc_dapm_context *dapm = &scodec->card->dapm;
@@ -1529,21 +1527,106 @@ static void sun8i_codec_jack_work(struct work_struct *work)
{
struct sun8i_codec *scodec = container_of(work, struct sun8i_codec,
jack_work.work);
+ unsigned int mdata;
int type;
- /* Prevent a well-timed button press from affecting detection. */
- synchronize_irq(scodec->jack_irq);
- if (!scodec->jack_pending)
- return;
+ mutex_lock(&scodec->jack_mutex);
- if (sun8i_codec_read_hmic_button(scodec)) {
- sun8i_codec_set_hmic_bias(scodec, false);
- type = SND_JACK_HEADPHONE;
- } else {
- type = SND_JACK_HEADSET;
+ if (scodec->jack_status == SUN8I_JACK_STATUS_DISCONNECTED) {
+ if (scodec->last_hmic_irq != SUN8I_HMIC_STS_JACK_IN_IRQ_ST)
+ goto out_unlock;
+
+ scodec->jack_last_sample = -1;
+ scodec->jack_last_btn = 0;
+
+ if (scodec->jack_type & SND_JACK_MICROPHONE) {
+ /*
+ * If we were in disconnected state, we just enable HBIAS and
+ * wait 500ms before reading initial HDATA value.
+ */
+ scodec->jack_hbias_ready = ktime_add_ms(ktime_get(), 600);
+ scodec->jack_status = SUN8I_JACK_STATUS_WAITING_HBIAS;
+ sun8i_codec_set_hmic_bias(scodec, true);
+ queue_delayed_work(system_power_efficient_wq,
+ &scodec->jack_work,
+ msecs_to_jiffies(610));
+ } else {
+ type = SND_JACK_HEADPHONE;
+ snd_soc_jack_report(&scodec->jack, 0, scodec->jack_type);
+ scodec->jack_status = SUN8I_JACK_STATUS_CONNECTED;
+ }
+ } else if (scodec->jack_status == SUN8I_JACK_STATUS_WAITING_HBIAS) {
+ /*
+ * If we're waiting for HBIAS to stabilize, and we get plug-out
+ * interrupt and nothing more for > 100ms, just cancel the
+ * initialization.
+ */
+ if (scodec->last_hmic_irq == SUN8I_HMIC_STS_JACK_OUT_IRQ_ST) {
+ scodec->jack_status = SUN8I_JACK_STATUS_DISCONNECTED;
+ sun8i_codec_set_hmic_bias(scodec, false);
+ goto out_unlock;
+ }
+
+ /*
+ * If we're not done waiting for things to stabilize, wait more.
+ */
+ if (!ktime_after(ktime_get(), scodec->jack_hbias_ready)) {
+ s64 msecs = ktime_ms_delta(scodec->jack_hbias_ready, ktime_get());
+
+ queue_delayed_work(system_power_efficient_wq,
+ &scodec->jack_work,
+ msecs_to_jiffies(msecs + 10));
+ goto out_unlock;
+ }
+
+ /*
+ * Everything is stabilized, determine jack type and report it.
+ */
+ regmap_read(scodec->regmap, SUN8I_HMIC_STS, &mdata);
+ mdata &= SUN8I_HMIC_STS_HMIC_DATA_MASK;
+ mdata >>= SUN8I_HMIC_STS_HMIC_DATA;
+
+ regmap_write(scodec->regmap, SUN8I_HMIC_STS, 0);
+
+ if (mdata < 0x10) {
+ type = SND_JACK_HEADPHONE;
+
+ sun8i_codec_set_hmic_bias(scodec, false);
+ } else {
+ type = SND_JACK_HEADSET;
+
+ /* Set MDATA threshold for triggering DATA interrupts
+ * slightly bellow the initial value read at connection
+ * time. This assumes that user is not pressing a button
+ * when connecting the jack cable. It's an unlinkely
+ * situation, unless the person has 3 hands or more.
+ */
+ /*
+ pr_err("jack: new mdata threshold: %#x\n", mdata);
+ regmap_write(scodec->regmap, SUN8I_HMIC_CTRL2,
+ 0x0 << SUN8I_HMIC_CTRL2_HMIC_SAMPLE |
+ (mdata - 3) << SUN8I_HMIC_CTRL2_HMIC_MDATA_THRESHOLD |
+ 0x0 << SUN8I_HMIC_CTRL2_HMIC_SF);
+ */
+ }
+
+ snd_soc_jack_report(&scodec->jack, type, scodec->jack_type);
+ scodec->jack_status = SUN8I_JACK_STATUS_CONNECTED;
+
+ pr_err("jack: plug-in reported\n");
+ } else if (scodec->jack_status == SUN8I_JACK_STATUS_CONNECTED) {
+ if (scodec->last_hmic_irq == SUN8I_HMIC_STS_JACK_OUT_IRQ_ST) {
+ scodec->jack_status = SUN8I_JACK_STATUS_DISCONNECTED;
+ if (scodec->jack_type & SND_JACK_MICROPHONE)
+ sun8i_codec_set_hmic_bias(scodec, false);
+
+ snd_soc_jack_report(&scodec->jack, 0, scodec->jack_type);
+ pr_err("jack: plug-out reported\n");
+ }
}
- snd_soc_jack_report(&scodec->jack, type, scodec->jack_type);
+out_unlock:
+ mutex_unlock(&scodec->jack_mutex);
}
static irqreturn_t sun8i_codec_jack_irq(int irq, void *dev_id)
@@ -1551,40 +1634,67 @@ static irqreturn_t sun8i_codec_jack_irq(int irq, void *dev_id)
struct sun8i_codec *scodec = dev_id;
unsigned int status;
+ mutex_lock(&scodec->jack_mutex);
+
regmap_read(scodec->regmap, SUN8I_HMIC_STS, &status);
+ regmap_write(scodec->regmap, SUN8I_HMIC_STS, status);
if (status & BIT(SUN8I_HMIC_STS_JACK_OUT_IRQ_ST)) {
- if (scodec->jack_type & SND_JACK_MICROPHONE) {
- sun8i_codec_set_hmic_bias(scodec, false);
- scodec->jack_pending = false;
- }
+ pr_err("jack: irq plug-out\n");
- dev_dbg(scodec->card->dev, "jack out\n");
- snd_soc_jack_report(&scodec->jack, 0, scodec->jack_type);
+ scodec->last_hmic_irq = SUN8I_HMIC_STS_JACK_OUT_IRQ_ST;
+ queue_delayed_work(system_power_efficient_wq,
+ &scodec->jack_work,
+ msecs_to_jiffies(100));
} else if (status & BIT(SUN8I_HMIC_STS_JACK_IN_IRQ_ST)) {
- int type = SND_JACK_HEADPHONE;
+ pr_err("jack: irq plug-in\n");
- if (scodec->jack_type & SND_JACK_MICROPHONE) {
- sun8i_codec_set_hmic_bias(scodec, true);
- scodec->jack_pending = true;
- queue_delayed_work(system_power_efficient_wq,
- &scodec->jack_work,
- msecs_to_jiffies(600));
- }
-
- dev_dbg(scodec->card->dev, "jack in\n");
- snd_soc_jack_report(&scodec->jack, type, type);
+ scodec->last_hmic_irq = SUN8I_HMIC_STS_JACK_IN_IRQ_ST;
+ queue_delayed_work(system_power_efficient_wq,
+ &scodec->jack_work,
+ msecs_to_jiffies(100));
} else if (status & BIT(SUN8I_HMIC_STS_HMIC_DATA_IRQ_ST)) {
- int type = SND_JACK_HEADSET | sun8i_codec_read_hmic_button(scodec);
+ if (scodec->jack_status == SUN8I_JACK_STATUS_CONNECTED) {
+ unsigned int value;
+ int type = SND_JACK_HEADSET;
+ int btn_chg = 0;
+
+ regmap_read(scodec->regmap, SUN8I_HMIC_STS, &value);
+ value &= SUN8I_HMIC_STS_HMIC_DATA_MASK;
+ value >>= SUN8I_HMIC_STS_HMIC_DATA;
+
+ if (value < 0x2)
+ type |= SND_JACK_BTN_0;
+ else if (value < 0x7)
+ type |= SND_JACK_BTN_1;
+ else if (value < 0x10)
+ type |= SND_JACK_BTN_2;
+
+ if (scodec->jack_last_sample >= 0 && scodec->jack_last_sample == value) {
+ btn_chg = (scodec->jack_last_btn ^ type) & 0x7000;
+ scodec->jack_last_btn = type;
+
+ //XXX: temporary for debugging
+ if (btn_chg) {
+ if (btn_chg & SND_JACK_BTN_0)
+ pr_err("jack: key_%spress BTN_0 (%#x)\n", type & SND_JACK_BTN_0 ? "" : "de", value);
+ if (btn_chg & SND_JACK_BTN_1)
+ pr_err("jack: key_%spress BTN_1 (%#x)\n", type & SND_JACK_BTN_1 ? "" : "de", value);
+ if (btn_chg & SND_JACK_BTN_2)
+ pr_err("jack: key_%spress BTN_2 (%#x)\n", type & SND_JACK_BTN_2 ? "" : "de", value);
+ }
+
+ snd_soc_jack_report(&scodec->jack, type, scodec->jack_type);
+ }
- scodec->jack_pending = false;
- snd_soc_jack_report(&scodec->jack, type, scodec->jack_type);
+ scodec->jack_last_sample = value;
+ }
} else {
+ mutex_unlock(&scodec->jack_mutex);
return IRQ_NONE;
}
- regmap_write(scodec->regmap, SUN8I_HMIC_STS, status);
-
+ mutex_unlock(&scodec->jack_mutex);
return IRQ_HANDLED;
}
@@ -1799,6 +1909,7 @@ static int sun8i_codec_probe(struct platform_device *pdev)
return ret;
INIT_DELAYED_WORK(&scodec->jack_work, sun8i_codec_jack_work);
+ mutex_init(&scodec->jack_mutex);
}
regcache_cache_only(scodec->regmap, true);
--
2.34.1