400 lines
14 KiB
Diff
400 lines
14 KiB
Diff
From 276f411d94f8b62729ef4acf23cc01a1e5d1f482 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 262/391] 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 7ee72c053..b78af3096 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 6a1f94165..342b097b5 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;
|
|
-
|
|
- 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));
|
|
- }
|
|
+ pr_err("jack: irq plug-in\n");
|
|
|
|
- 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.35.3
|
|
|