From e898ee3d83fc85c4587525e3413a171487a61aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Jirman?= Date: Wed, 12 May 2021 21:37:03 +0200 Subject: [PATCH 268/464] 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 --- 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 #include #include +#include #include #include #include @@ -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