ASoC: rt5651: Add button press support
authorHans de Goede <hdegoede@redhat.com>
Wed, 4 Jul 2018 22:59:33 +0000 (00:59 +0200)
committerMark Brown <broonie@kernel.org>
Tue, 10 Jul 2018 17:49:26 +0000 (18:49 +0100)
Enable button press detection for headsets by using the ovcd IRQ to get
notified of button presses.

This is modelled after (almost exactly copied from) the button press code
for the rt5640 which has identical ovcd hardware.

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

index 40bd1e7..0462049 100644 (file)
@@ -1581,6 +1581,24 @@ static void rt5651_disable_micbias1_for_ovcd(struct snd_soc_component *component
        snd_soc_dapm_mutex_unlock(dapm);
 }
 
+static void rt5651_enable_micbias1_ovcd_irq(struct snd_soc_component *component)
+{
+       struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component);
+
+       snd_soc_component_update_bits(component, RT5651_IRQ_CTRL2,
+               RT5651_IRQ_MB1_OC_MASK, RT5651_IRQ_MB1_OC_NOR);
+       rt5651->ovcd_irq_enabled = true;
+}
+
+static void rt5651_disable_micbias1_ovcd_irq(struct snd_soc_component *component)
+{
+       struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component);
+
+       snd_soc_component_update_bits(component, RT5651_IRQ_CTRL2,
+               RT5651_IRQ_MB1_OC_MASK, RT5651_IRQ_MB1_OC_BP);
+       rt5651->ovcd_irq_enabled = false;
+}
+
 static void rt5651_clear_micbias1_ovcd(struct snd_soc_component *component)
 {
        snd_soc_component_update_bits(component, RT5651_IRQ_CTRL2,
@@ -1622,10 +1640,80 @@ static bool rt5651_jack_inserted(struct snd_soc_component *component)
        return val == 0;
 }
 
-/* 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 rt5651_start_button_press_work(struct snd_soc_component *component)
+{
+       struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component);
+
+       rt5651->poll_count = 0;
+       rt5651->press_count = 0;
+       rt5651->release_count = 0;
+       rt5651->pressed = false;
+       rt5651->press_reported = false;
+       rt5651_clear_micbias1_ovcd(component);
+       schedule_delayed_work(&rt5651->bp_work, msecs_to_jiffies(BP_POLL_TIME));
+}
+
+static void rt5651_button_press_work(struct work_struct *work)
+{
+       struct rt5651_priv *rt5651 =
+               container_of(work, struct rt5651_priv, bp_work.work);
+       struct snd_soc_component *component = rt5651->component;
+
+       /* Check the jack was not removed underneath us */
+       if (!rt5651_jack_inserted(component))
+               return;
+
+       if (rt5651_micbias1_ovcd(component)) {
+               rt5651->release_count = 0;
+               rt5651->press_count++;
+               /* Remember till after JACK_UNPLUG_TIME wait */
+               if (rt5651->press_count >= BP_THRESHOLD)
+                       rt5651->pressed = true;
+               rt5651_clear_micbias1_ovcd(component);
+       } else {
+               rt5651->press_count = 0;
+               rt5651->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.
+        */
+       rt5651->poll_count++;
+       if (rt5651->poll_count < (JACK_UNPLUG_TIME / BP_POLL_TIME)) {
+               schedule_delayed_work(&rt5651->bp_work,
+                                     msecs_to_jiffies(BP_POLL_TIME));
+               return;
+       }
+
+       if (rt5651->pressed && !rt5651->press_reported) {
+               dev_dbg(component->dev, "headset button press\n");
+               snd_soc_jack_report(rt5651->hp_jack, SND_JACK_BTN_0,
+                                   SND_JACK_BTN_0);
+               rt5651->press_reported = true;
+       }
+
+       if (rt5651->release_count >= BP_THRESHOLD) {
+               if (rt5651->press_reported) {
+                       dev_dbg(component->dev, "headset button release\n");
+                       snd_soc_jack_report(rt5651->hp_jack, 0, SND_JACK_BTN_0);
+               }
+               /* Re-enable OVCD IRQ to detect next press */
+               rt5651_enable_micbias1_ovcd_irq(component);
+               return; /* Stop polling */
+       }
+
+       schedule_delayed_work(&rt5651->bp_work, msecs_to_jiffies(BP_POLL_TIME));
+}
 
 static int rt5651_detect_headset(struct snd_soc_component *component)
 {
@@ -1676,15 +1764,58 @@ static void rt5651_jack_detect_work(struct work_struct *work)
 {
        struct rt5651_priv *rt5651 =
                container_of(work, struct rt5651_priv, jack_detect_work);
+       struct snd_soc_component *component = rt5651->component;
        int report = 0;
 
-       if (rt5651_jack_inserted(rt5651->component)) {
-               rt5651_enable_micbias1_for_ovcd(rt5651->component);
-               report = rt5651_detect_headset(rt5651->component);
-               rt5651_disable_micbias1_for_ovcd(rt5651->component);
+       if (!rt5651_jack_inserted(component)) {
+               /* Jack removed, or spurious IRQ? */
+               if (rt5651->hp_jack->status & SND_JACK_HEADPHONE) {
+                       if (rt5651->hp_jack->status & SND_JACK_MICROPHONE) {
+                               cancel_delayed_work_sync(&rt5651->bp_work);
+                               rt5651_disable_micbias1_ovcd_irq(component);
+                               rt5651_disable_micbias1_for_ovcd(component);
+                       }
+                       snd_soc_jack_report(rt5651->hp_jack, 0,
+                                           SND_JACK_HEADSET | SND_JACK_BTN_0);
+                       dev_dbg(component->dev, "jack unplugged\n");
+               }
+       } else if (!(rt5651->hp_jack->status & SND_JACK_HEADPHONE)) {
+               /* Jack inserted */
+               WARN_ON(rt5651->ovcd_irq_enabled);
+               rt5651_enable_micbias1_for_ovcd(component);
+               report = rt5651_detect_headset(component);
+               if (report == SND_JACK_HEADSET) {
+                       /* Enable ovcd IRQ for button press detect. */
+                       rt5651_enable_micbias1_ovcd_irq(component);
+               } else {
+                       /* No more need for overcurrent detect. */
+                       rt5651_disable_micbias1_for_ovcd(component);
+               }
+               dev_dbg(component->dev, "detect report %#02x\n", report);
+               snd_soc_jack_report(rt5651->hp_jack, report, SND_JACK_HEADSET);
+       } else if (rt5651->ovcd_irq_enabled && rt5651_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.
+                */
+               rt5651_disable_micbias1_ovcd_irq(component);
+               rt5651_start_button_press_work(component);
+
+               /*
+                * If the jack-detect IRQ flag goes high (unplug) after our
+                * above rt5651_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, &rt5651->jack_detect_work);
        }
-
-       snd_soc_jack_report(rt5651->hp_jack, report, SND_JACK_HEADSET);
 }
 
 static irqreturn_t rt5651_irq(int irq, void *data)
@@ -1701,6 +1832,7 @@ static void rt5651_cancel_work(void *data)
        struct rt5651_priv *rt5651 = data;
 
        cancel_work_sync(&rt5651->jack_detect_work);
+       cancel_delayed_work_sync(&rt5651->bp_work);
 }
 
 static void rt5651_enable_jack_detect(struct snd_soc_component *component,
@@ -1770,6 +1902,11 @@ static void rt5651_enable_jack_detect(struct snd_soc_component *component,
                RT5651_MB1_OC_STKY_MASK, RT5651_MB1_OC_STKY_EN);
 
        rt5651->hp_jack = hp_jack;
+       if (rt5651->hp_jack->status & SND_JACK_MICROPHONE) {
+               rt5651_enable_micbias1_for_ovcd(component);
+               rt5651_enable_micbias1_ovcd_irq(component);
+       }
+
        enable_irq(rt5651->irq);
        /* sync initial jack state */
        queue_work(system_power_efficient_wq, &rt5651->jack_detect_work);
@@ -1782,6 +1919,12 @@ static void rt5651_disable_jack_detect(struct snd_soc_component *component)
        disable_irq(rt5651->irq);
        rt5651_cancel_work(rt5651);
 
+       if (rt5651->hp_jack->status & SND_JACK_MICROPHONE) {
+               rt5651_disable_micbias1_ovcd_irq(component);
+               rt5651_disable_micbias1_for_ovcd(component);
+               snd_soc_jack_report(rt5651->hp_jack, 0, SND_JACK_BTN_0);
+       }
+
        rt5651->hp_jack = NULL;
 }
 
@@ -2046,6 +2189,7 @@ static int rt5651_i2c_probe(struct i2c_client *i2c,
        rt5651->irq = i2c->irq;
        rt5651->hp_mute = 1;
 
+       INIT_DELAYED_WORK(&rt5651->bp_work, rt5651_button_press_work);
        INIT_WORK(&rt5651->jack_detect_work, rt5651_jack_detect_work);
 
        /* Make sure work is stopped on probe-error / remove */
index 3a0968c..ac6de6f 100644 (file)
@@ -2071,8 +2071,16 @@ struct rt5651_pll_code {
 struct rt5651_priv {
        struct snd_soc_component *component;
        struct regmap *regmap;
+       /* Jack and button detect data */
        struct snd_soc_jack *hp_jack;
        struct work_struct jack_detect_work;
+       struct delayed_work bp_work;
+       bool ovcd_irq_enabled;
+       bool pressed;
+       bool press_reported;
+       int press_count;
+       int release_count;
+       int poll_count;
        unsigned int jd_src;
        unsigned int ovcd_th;
        unsigned int ovcd_sf;