ASoC: Implement WM5100 accessory detection support
authorMark Brown <broonie@opensource.wolfsonmicro.com>
Tue, 27 Sep 2011 16:39:50 +0000 (17:39 +0100)
committerMark Brown <broonie@opensource.wolfsonmicro.com>
Tue, 11 Oct 2011 17:59:01 +0000 (18:59 +0100)
The WM5100 includes an advanced, low power, accessory detect subsystem
capable of detecting both accessory presence and button presses while
the device is in an ultra low power mode. Implement initial support for
this.

Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
sound/soc/codecs/wm5100.c
sound/soc/codecs/wm5100.h

index 8d90ba9..02c011d 100644 (file)
@@ -26,6 +26,7 @@
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
 #include <sound/soc.h>
+#include <sound/jack.h>
 #include <sound/initval.h>
 #include <sound/tlv.h>
 #include <sound/wm5100.h>
@@ -68,6 +69,11 @@ struct wm5100_priv {
 
        bool out_ena[2];
 
+       struct snd_soc_jack *jack;
+       bool jack_detecting;
+       bool jack_mic;
+       int jack_mode;
+
        struct wm5100_fll fll[2];
 
        struct wm5100_pdata pdata;
@@ -2113,6 +2119,159 @@ static int wm5100_dig_vu[] = {
        WM5100_DAC_DIGITAL_VOLUME_6R,
 };
 
+static void wm5100_set_detect_mode(struct snd_soc_codec *codec, int the_mode)
+{
+       struct wm5100_priv *wm5100 = snd_soc_codec_get_drvdata(codec);
+       struct wm5100_jack_mode *mode = &wm5100->pdata.jack_modes[the_mode];
+
+       BUG_ON(the_mode >= ARRAY_SIZE(wm5100->pdata.jack_modes));
+
+       gpio_set_value_cansleep(wm5100->pdata.hp_pol, mode->hp_pol);
+       snd_soc_update_bits(codec, WM5100_ACCESSORY_DETECT_MODE_1,
+                           WM5100_ACCDET_BIAS_SRC_MASK |
+                           WM5100_ACCDET_SRC,
+                           (mode->bias << WM5100_ACCDET_BIAS_SRC_SHIFT) |
+                           mode->micd_src << WM5100_ACCDET_SRC_SHIFT);
+
+       wm5100->jack_mode = the_mode;
+
+       dev_dbg(codec->dev, "Set microphone polarity to %d\n",
+               wm5100->jack_mode);
+}
+
+static void wm5100_micd_irq(struct snd_soc_codec *codec)
+{
+       struct wm5100_priv *wm5100 = snd_soc_codec_get_drvdata(codec);
+       int val;
+
+       val = snd_soc_read(codec, WM5100_MIC_DETECT_3);
+
+       dev_dbg(codec->dev, "Microphone event: %x\n", val);
+
+       if (!(val & WM5100_ACCDET_VALID)) {
+               dev_warn(codec->dev, "Microphone detection state invalid\n");
+               return;
+       }
+
+       /* No accessory, reset everything and report removal */
+       if (!(val & WM5100_ACCDET_STS)) {
+               dev_dbg(codec->dev, "Jack removal detected\n");
+               wm5100->jack_mic = false;
+               wm5100->jack_detecting = true;
+               snd_soc_jack_report(wm5100->jack, 0,
+                                   SND_JACK_LINEOUT | SND_JACK_HEADSET |
+                                   SND_JACK_BTN_0);
+
+               snd_soc_update_bits(codec, WM5100_MIC_DETECT_1,
+                                   WM5100_ACCDET_RATE_MASK,
+                                   WM5100_ACCDET_RATE_MASK);
+               return;
+       }
+
+       /* If the measurement is very high we've got a microphone,
+        * either we just detected one or if we already reported then
+        * we've got a button release event.
+        */
+       if (val & 0x400) {
+               if (wm5100->jack_detecting) {
+                       dev_dbg(codec->dev, "Microphone detected\n");
+                       wm5100->jack_mic = true;
+                       snd_soc_jack_report(wm5100->jack,
+                                           SND_JACK_HEADSET,
+                                           SND_JACK_HEADSET | SND_JACK_BTN_0);
+
+                       /* Increase poll rate to give better responsiveness
+                        * for buttons */
+                       snd_soc_update_bits(codec, WM5100_MIC_DETECT_1,
+                                           WM5100_ACCDET_RATE_MASK,
+                                           5 << WM5100_ACCDET_RATE_SHIFT);
+               } else {
+                       dev_dbg(codec->dev, "Mic button up\n");
+                       snd_soc_jack_report(wm5100->jack, 0, SND_JACK_BTN_0);
+               }
+
+               return;
+       }
+
+       /* If we detected a lower impedence during initial startup
+        * then we probably have the wrong polarity, flip it.  Don't
+        * do this for the lowest impedences to speed up detection of
+        * plain headphones.
+        */
+       if (wm5100->jack_detecting && (val & 0x3f8)) {
+               wm5100_set_detect_mode(codec, !wm5100->jack_mode);
+
+               return;
+       }
+
+       /* Don't distinguish between buttons, just report any low
+        * impedence as BTN_0.
+        */
+       if (val & 0x3fc) {
+               if (wm5100->jack_mic) {
+                       dev_dbg(codec->dev, "Mic button detected\n");
+                       snd_soc_jack_report(wm5100->jack, SND_JACK_BTN_0,
+                                           SND_JACK_BTN_0);
+               } else if (wm5100->jack_detecting) {
+                       dev_dbg(codec->dev, "Headphone detected\n");
+                       snd_soc_jack_report(wm5100->jack, SND_JACK_HEADPHONE,
+                                           SND_JACK_HEADPHONE);
+
+                       /* Increase the detection rate a bit for
+                        * responsiveness.
+                        */
+                       snd_soc_update_bits(codec, WM5100_MIC_DETECT_1,
+                                           WM5100_ACCDET_RATE_MASK,
+                                           7 << WM5100_ACCDET_RATE_SHIFT);
+               }
+       }
+}
+
+int wm5100_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack)
+{
+       struct wm5100_priv *wm5100 = snd_soc_codec_get_drvdata(codec);
+
+       if (jack) {
+               wm5100->jack = jack;
+               wm5100->jack_detecting = true;
+
+               wm5100_set_detect_mode(codec, 0);
+
+               /* Slowest detection rate, gives debounce for initial
+                * detection */
+               snd_soc_update_bits(codec, WM5100_MIC_DETECT_1,
+                                   WM5100_ACCDET_BIAS_STARTTIME_MASK |
+                                   WM5100_ACCDET_RATE_MASK,
+                                   (7 << WM5100_ACCDET_BIAS_STARTTIME_SHIFT) |
+                                   WM5100_ACCDET_RATE_MASK);
+
+               /* We need the charge pump to power MICBIAS */
+               snd_soc_dapm_force_enable_pin(&codec->dapm, "CP2");
+               snd_soc_dapm_force_enable_pin(&codec->dapm, "SYSCLK");
+               snd_soc_dapm_sync(&codec->dapm);
+
+               /* We start off just enabling microphone detection - even a
+                * plain headphone will trigger detection.
+                */
+               snd_soc_update_bits(codec, WM5100_MIC_DETECT_1,
+                                   WM5100_ACCDET_ENA, WM5100_ACCDET_ENA);
+
+               snd_soc_update_bits(codec, WM5100_INTERRUPT_STATUS_3_MASK,
+                                   WM5100_IM_ACCDET_EINT, 0);
+       } else {
+               snd_soc_update_bits(codec, WM5100_INTERRUPT_STATUS_3_MASK,
+                                   WM5100_IM_HPDET_EINT |
+                                   WM5100_IM_ACCDET_EINT,
+                                   WM5100_IM_HPDET_EINT |
+                                   WM5100_IM_ACCDET_EINT);
+               snd_soc_update_bits(codec, WM5100_MIC_DETECT_1,
+                                   WM5100_ACCDET_ENA, 0);
+               wm5100->jack = NULL;
+       }
+
+       return 0;
+}
+
 static irqreturn_t wm5100_irq(int irq, void *data)
 {
        struct snd_soc_codec *codec = data;
@@ -2144,6 +2303,9 @@ static irqreturn_t wm5100_irq(int irq, void *data)
                complete(&wm5100->fll[1].lock);
        }
 
+       if (irq_val & WM5100_ACCDET_EINT)
+               wm5100_micd_irq(codec);
+
        irq_val = snd_soc_read(codec, WM5100_INTERRUPT_STATUS_4);
        if (irq_val < 0) {
                dev_err(codec->dev, "Failed to read IRQ status 4: %d\n",
index 345b3cf..fa32b12 100644 (file)
@@ -16,6 +16,8 @@
 
 #include <sound/soc.h>
 
+int wm5100_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack);
+
 #define WM5100_CLK_AIF1     1
 #define WM5100_CLK_AIF2     2
 #define WM5100_CLK_AIF3     3