ASoC: Implement interrupt driven microphone detection for WM8903
authorMark Brown <broonie@opensource.wolfsonmicro.com>
Mon, 15 Mar 2010 21:22:58 +0000 (21:22 +0000)
committerMark Brown <broonie@opensource.wolfsonmicro.com>
Tue, 16 Mar 2010 15:57:43 +0000 (15:57 +0000)
Support use of the WM8903 IRQ for reporting of microphone presence
and short detection.

Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Acked-by: Liam Girdwood <lrg@slimlogic.co.uk>
include/sound/wm8903.h
sound/soc/codecs/wm8903.c
sound/soc/codecs/wm8903.h

index 49bb1a8..b4a0db2 100644 (file)
@@ -241,6 +241,8 @@ struct wm8903_platform_data {
         */
        u16 micdet_cfg;
 
+       int micdet_delay;      /* Delay after microphone detection (ms) */
+
        u32 gpio_cfg[5];       /* Default register values for GPIO pin mux */
 };
 
index 7aa2adb..b5427b4 100644 (file)
@@ -23,6 +23,7 @@
 #include <linux/i2c.h>
 #include <linux/platform_device.h>
 #include <sound/core.h>
+#include <sound/jack.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
 #include <sound/tlv.h>
@@ -223,6 +224,12 @@ struct wm8903_priv {
 
        struct completion wseq;
 
+       struct snd_soc_jack *mic_jack;
+       int mic_det;
+       int mic_short;
+       int mic_last_report;
+       int mic_delay;
+
        struct snd_pcm_substream *master_substream;
        struct snd_pcm_substream *slave_substream;
 };
@@ -1437,19 +1444,112 @@ static int wm8903_hw_params(struct snd_pcm_substream *substream,
        return 0;
 }
 
+/**
+ * wm8903_mic_detect - Enable microphone detection via the WM8903 IRQ
+ *
+ * @codec:  WM8903 codec
+ * @jack:   jack to report detection events on
+ * @det:    value to report for presence detection
+ * @shrt:   value to report for short detection
+ *
+ * Enable microphone detection via IRQ on the WM8903.  If GPIOs are
+ * being used to bring out signals to the processor then only platform
+ * data configuration is needed for WM8903 and processor GPIOs should
+ * be configured using snd_soc_jack_add_gpios() instead.
+ *
+ * The current threasholds for detection should be configured using
+ * micdet_cfg in the platform data.  Using this function will force on
+ * the microphone bias for the device.
+ */
+int wm8903_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack,
+                     int det, int shrt)
+{
+       struct wm8903_priv *wm8903 = codec->private_data;
+       int irq_mask = 0;
+
+       dev_dbg(codec->dev, "Enabling microphone detection: %x %x\n",
+               det, shrt);
+
+       /* Store the configuration */
+       wm8903->mic_jack = jack;
+       wm8903->mic_det = det;
+       wm8903->mic_short = shrt;
+
+       /* Enable interrupts we've got a report configured for */
+       if (det)
+               irq_mask &= ~WM8903_MICDET_EINT;
+       if (shrt)
+               irq_mask &= ~WM8903_MICSHRT_EINT;
+
+       snd_soc_update_bits(codec, WM8903_INTERRUPT_STATUS_1_MASK,
+                           WM8903_MICDET_EINT | WM8903_MICSHRT_EINT,
+                           irq_mask);
+
+       /* Enable mic detection, this may not have been set through
+        * platform data (eg, if the defaults are OK). */
+       snd_soc_update_bits(codec, WM8903_WRITE_SEQUENCER_0,
+                           WM8903_WSEQ_ENA, WM8903_WSEQ_ENA);
+       snd_soc_update_bits(codec, WM8903_MIC_BIAS_CONTROL_0,
+                           WM8903_MICDET_ENA, WM8903_MICDET_ENA);
+
+       /* Force the microphone bias on; this will trigger an initial
+        * detection. */
+       snd_soc_dapm_force_enable_pin(codec, "Mic Bias");
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(wm8903_mic_detect);
+
 static irqreturn_t wm8903_irq(int irq, void *data)
 {
        struct wm8903_priv *wm8903 = data;
        struct snd_soc_codec *codec = &wm8903->codec;
-       int reg;
+       int mic_report;
+       int int_pol;
+       int int_val = 0;
+       int mask = ~snd_soc_read(codec, WM8903_INTERRUPT_STATUS_1_MASK);
 
-       reg = snd_soc_read(codec, WM8903_INTERRUPT_STATUS_1);
+       int_val = snd_soc_read(codec, WM8903_INTERRUPT_STATUS_1) & mask;
 
-       if (reg & WM8903_WSEQ_BUSY_EINT) {
+       if (int_val & WM8903_WSEQ_BUSY_EINT) {
                dev_dbg(codec->dev, "Write sequencer done\n");
                complete(&wm8903->wseq);
        }
 
+       /*
+        * The rest is microphone jack detection.  We need to manually
+        * invert the polarity of the interrupt after each event - to
+        * simplify the code keep track of the last state we reported
+        * and just invert the relevant bits in both the report and
+        * the polarity register.
+        */
+       mic_report = wm8903->mic_last_report;
+       int_pol = snd_soc_read(codec, WM8903_INTERRUPT_POLARITY_1);
+
+       if (int_val & WM8903_MICSHRT_EINT) {
+               dev_dbg(codec->dev, "Microphone short (pol=%x)\n", int_pol);
+
+               mic_report ^= wm8903->mic_short;
+               int_pol ^= WM8903_MICSHRT_INV;
+       }
+
+       if (int_val & WM8903_MICDET_EINT) {
+               dev_dbg(codec->dev, "Microphone detect (pol=%x)\n", int_pol);
+
+               mic_report ^= wm8903->mic_det;
+               int_pol ^= WM8903_MICDET_INV;
+
+               msleep(wm8903->mic_delay);
+       }
+
+       snd_soc_update_bits(codec, WM8903_INTERRUPT_POLARITY_1,
+                           WM8903_MICSHRT_INV | WM8903_MICDET_INV, int_pol);
+
+       snd_soc_jack_report(wm8903->mic_jack, mic_report,
+                           wm8903->mic_short | wm8903->mic_det);
+
+       wm8903->mic_last_report = mic_report;
+
        return IRQ_HANDLED;
 }
 
index 8f19a24..ce384a2 100644 (file)
 extern struct snd_soc_dai wm8903_dai;
 extern struct snd_soc_codec_device soc_codec_dev_wm8903;
 
+extern int wm8903_mic_detect(struct snd_soc_codec *codec,
+                            struct snd_soc_jack *jack,
+                            int det, int shrt);
+
 #define WM8903_MCLK_DIV_2 1
 #define WM8903_CLK_SYS    2
 #define WM8903_BCLK       3