ASoC: rt5640: Add button press support
authorHans de Goede <hdegoede@redhat.com>
Tue, 8 May 2018 15:35:52 +0000 (17:35 +0200)
committerMark Brown <broonie@kernel.org>
Fri, 11 May 2018 02:23:37 +0000 (11:23 +0900)
Enable button press detection for headsets by using the ovcd IRQ to get
notified of button presses.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/codecs/rt5640.c
sound/soc/codecs/rt5640.h

index 8e306f5..8bf8d36 100644 (file)
@@ -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);
-               rt5640_disable_micbias1_for_ovcd(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);
 
-       snd_soc_component_write(component, RT5640_IRQ_CTRL1,
-                               RT5640_IRQ_JD_NOR);
+       /*
+        * 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 */
index 9d08471..e29e3e7 100644 (file)
@@ -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;