]> bbs.cooldavid.org Git - net-next-2.6.git/blobdiff - sound/soc/codecs/wm8962.c
ASoC: Implement interrupt based jack detection
[net-next-2.6.git] / sound / soc / codecs / wm8962.c
index 4fa5de873c8b2b8792e119d219a8da60fa958106..3a1db4a76a648c88ec9e1e1ae9c94eff54203752 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/delay.h>
 #include <linux/pm.h>
 #include <linux/gcd.h>
+#include <linux/gpio.h>
 #include <linux/i2c.h>
 #include <linux/input.h>
 #include <linux/platform_device.h>
@@ -24,6 +25,7 @@
 #include <linux/slab.h>
 #include <linux/workqueue.h>
 #include <sound/core.h>
+#include <sound/jack.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
 #include <sound/soc.h>
@@ -62,6 +64,9 @@ struct wm8962_priv {
        int fll_fref;
        int fll_fout;
 
+       struct delayed_work mic_work;
+       struct snd_soc_jack *jack;
+
        struct regulator_bulk_data supplies[WM8962_NUM_SUPPLIES];
        struct notifier_block disable_nb[WM8962_NUM_SUPPLIES];
 
@@ -70,6 +75,10 @@ struct wm8962_priv {
        struct work_struct beep_work;
        int beep_rate;
 #endif
+
+#ifdef CONFIG_GPIOLIB
+       struct gpio_chip gpio_chip;
+#endif
 };
 
 /* We can't use the same notifier block for more than one supply and
@@ -630,6 +639,8 @@ SND_SOC_DAPM_INPUT("IN4L"),
 SND_SOC_DAPM_INPUT("IN4R"),
 SND_SOC_DAPM_INPUT("Beep"),
 
+SND_SOC_DAPM_MICBIAS("MICBIAS", WM8962_PWR_MGMT_1, 1, 0),
+
 SND_SOC_DAPM_SUPPLY("Class G", WM8962_CHARGE_PUMP_B, 0, 1, NULL, 0),
 SND_SOC_DAPM_SUPPLY("SYSCLK", WM8962_CLOCKING2, 5, 0, sysclk_event,
                    SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
@@ -909,12 +920,6 @@ static void wm8962_configure_bclk(struct snd_soc_codec *codec)
        int clocking2 = 0;
        int aif2 = 0;
 
-       /* If the CODEC is powered on we can configure BCLK */
-       if (codec->bias_level != SND_SOC_BIAS_OFF) {
-               dev_dbg(codec->dev, "Bias is off, can't configure BCLK\n");
-               return;
-       }
-
        if (!wm8962->bclk) {
                dev_dbg(codec->dev, "No BCLK rate configured\n");
                return;
@@ -1461,9 +1466,40 @@ static struct snd_soc_dai_driver wm8962_dai = {
        .symmetric_rates = 1,
 };
 
+static void wm8962_mic_work(struct work_struct *work)
+{
+       struct wm8962_priv *wm8962 = container_of(work,
+                                                 struct wm8962_priv,
+                                                 mic_work.work);
+       struct snd_soc_codec *codec = wm8962->codec;
+       int status = 0;
+       int irq_pol = 0;
+       int reg;
+
+       reg = snd_soc_read(codec, WM8962_ADDITIONAL_CONTROL_4);
+
+       if (reg & WM8962_MICDET_STS) {
+               status |= SND_JACK_MICROPHONE;
+               irq_pol |= WM8962_MICD_IRQ_POL;
+       }
+
+       if (reg & WM8962_MICSHORT_STS) {
+               status |= SND_JACK_BTN_0;
+               irq_pol |= WM8962_MICSCD_IRQ_POL;
+       }
+
+       snd_soc_jack_report(wm8962->jack, status,
+                           SND_JACK_MICROPHONE | SND_JACK_BTN_0);
+
+       snd_soc_update_bits(codec, WM8962_MICINT_SOURCE_POL,
+                           WM8962_MICSCD_IRQ_POL |
+                           WM8962_MICD_IRQ_POL, irq_pol);
+}
+
 static irqreturn_t wm8962_irq(int irq, void *data)
 {
        struct snd_soc_codec *codec = data;
+       struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
        int mask;
        int active;
 
@@ -1478,12 +1514,59 @@ static irqreturn_t wm8962_irq(int irq, void *data)
        if (active & WM8962_TEMP_SHUT_EINT)
                dev_crit(codec->dev, "Thermal shutdown\n");
 
+       if (active & (WM8962_MICSCD_EINT | WM8962_MICD_EINT)) {
+               dev_dbg(codec->dev, "Microphone event detected\n");
+
+               schedule_delayed_work(&wm8962->mic_work,
+                                     msecs_to_jiffies(250));
+       }
+
        /* Acknowledge the interrupts */
        snd_soc_write(codec, WM8962_INTERRUPT_STATUS_2, active);
 
        return IRQ_HANDLED;
 }
 
+/**
+ * wm8962_mic_detect - Enable microphone detection via the WM8962 IRQ
+ *
+ * @codec:  WM8962 codec
+ * @jack:   jack to report detection events on
+ *
+ * Enable microphone detection via IRQ on the WM8962.  If GPIOs are
+ * being used to bring out signals to the processor then only platform
+ * data configuration is needed for WM8962 and processor GPIOs should
+ * be configured using snd_soc_jack_add_gpios() instead.
+ *
+ * If no jack is supplied detection will be disabled.
+ */
+int wm8962_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack)
+{
+       struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+       int irq_mask, enable;
+
+       wm8962->jack = jack;
+       if (jack) {
+               irq_mask = 0;
+               enable = WM8962_MICDET_ENA;
+       } else {
+               irq_mask = WM8962_MICD_EINT | WM8962_MICSCD_EINT;
+               enable = 0;
+       }
+
+       snd_soc_update_bits(codec, WM8962_INTERRUPT_STATUS_2_MASK,
+                           WM8962_MICD_EINT | WM8962_MICSCD_EINT, irq_mask);
+       snd_soc_update_bits(codec, WM8962_ADDITIONAL_CONTROL_4,
+                           WM8962_MICDET_ENA, enable);
+
+       /* Send an initial empty report */
+       snd_soc_jack_report(wm8962->jack, 0,
+                           SND_JACK_MICROPHONE | SND_JACK_BTN_0);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(wm8962_mic_detect);
+
 #ifdef CONFIG_PM
 static int wm8962_resume(struct snd_soc_codec *codec)
 {
@@ -1650,6 +1733,118 @@ static void wm8962_free_beep(struct snd_soc_codec *codec)
 }
 #endif
 
+#ifdef CONFIG_GPIOLIB
+static inline struct wm8962_priv *gpio_to_wm8962(struct gpio_chip *chip)
+{
+       return container_of(chip, struct wm8962_priv, gpio_chip);
+}
+
+static int wm8962_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+       struct wm8962_priv *wm8962 = gpio_to_wm8962(chip);
+       struct snd_soc_codec *codec = wm8962->codec;
+       int mask = 0;
+       int val;
+
+       /* The WM8962 GPIOs aren't linearly numbered.  For simplicity
+        * we export linear numbers and error out if the unsupported
+        * ones are requsted.
+        */
+       switch (offset + 1) {
+       case 2:
+               mask = WM8962_CLKOUT2_SEL_MASK;
+               val = 1 << WM8962_CLKOUT2_SEL_SHIFT;
+               break;
+       case 3:
+               mask = WM8962_CLKOUT3_SEL_MASK;
+               val = 1 << WM8962_CLKOUT3_SEL_SHIFT;
+               break;
+       case 5:
+       case 6:
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       /* Some of the GPIOs are behind MFP configuration */
+       if (mask)
+               snd_soc_update_bits(codec, WM8962_ANALOGUE_CLOCKING1,
+                                   mask, val);
+
+       return 0;
+}
+
+static void wm8962_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+       struct wm8962_priv *wm8962 = gpio_to_wm8962(chip);
+       struct snd_soc_codec *codec = wm8962->codec;
+
+       snd_soc_update_bits(codec, WM8962_GPIO_BASE + offset,
+                           WM8962_GP2_LVL, value << WM8962_GP2_LVL_SHIFT);
+}
+
+static int wm8962_gpio_direction_out(struct gpio_chip *chip,
+                                    unsigned offset, int value)
+{
+       struct wm8962_priv *wm8962 = gpio_to_wm8962(chip);
+       struct snd_soc_codec *codec = wm8962->codec;
+       int val;
+
+       /* Force function 1 (logic output) */
+       val = (1 << WM8962_GP2_FN_SHIFT) | (value << WM8962_GP2_LVL_SHIFT);
+
+       return snd_soc_update_bits(codec, WM8962_GPIO_BASE + offset,
+                                  WM8962_GP2_FN_MASK | WM8962_GP2_LVL, val);
+}
+
+static struct gpio_chip wm8962_template_chip = {
+       .label                  = "wm8962",
+       .owner                  = THIS_MODULE,
+       .request                = wm8962_gpio_request,
+       .direction_output       = wm8962_gpio_direction_out,
+       .set                    = wm8962_gpio_set,
+       .can_sleep              = 1,
+};
+
+static void wm8962_init_gpio(struct snd_soc_codec *codec)
+{
+       struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+       struct wm8962_pdata *pdata = dev_get_platdata(codec->dev);
+       int ret;
+
+       wm8962->gpio_chip = wm8962_template_chip;
+       wm8962->gpio_chip.ngpio = WM8962_MAX_GPIO;
+       wm8962->gpio_chip.dev = codec->dev;
+
+       if (pdata && pdata->gpio_base)
+               wm8962->gpio_chip.base = pdata->gpio_base;
+       else
+               wm8962->gpio_chip.base = -1;
+
+       ret = gpiochip_add(&wm8962->gpio_chip);
+       if (ret != 0)
+               dev_err(codec->dev, "Failed to add GPIOs: %d\n", ret);
+}
+
+static void wm8962_free_gpio(struct snd_soc_codec *codec)
+{
+       struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+       int ret;
+
+       ret = gpiochip_remove(&wm8962->gpio_chip);
+       if (ret != 0)
+               dev_err(codec->dev, "Failed to remove GPIOs: %d\n", ret);
+}
+#else
+static void wm8962_init_gpio(struct snd_soc_codec *codec)
+{
+}
+
+static void wm8962_free_gpio(struct snd_soc_codec *codec)
+{
+}
+#endif
+
 static int wm8962_probe(struct snd_soc_codec *codec)
 {
        int ret;
@@ -1660,6 +1855,7 @@ static int wm8962_probe(struct snd_soc_codec *codec)
        int i, trigger, irq_pol;
 
        wm8962->codec = codec;
+       INIT_DELAYED_WORK(&wm8962->mic_work, wm8962_mic_work);
 
        codec->cache_sync = 1;
        codec->idle_bias_off = 1;
@@ -1755,6 +1951,16 @@ static int wm8962_probe(struct snd_soc_codec *codec)
                if (pdata->spk_mono)
                        wm8962->reg_cache[WM8962_CLASS_D_CONTROL_2]
                                |= WM8962_SPK_MONO;
+
+               /* Micbias setup, detection enable and detection
+                * threasholds. */
+               if (pdata->mic_cfg)
+                       snd_soc_update_bits(codec, WM8962_ADDITIONAL_CONTROL_4,
+                                           WM8962_MICDET_ENA |
+                                           WM8962_MICDET_THR_MASK |
+                                           WM8962_MICSHORT_THR_MASK |
+                                           WM8962_MICBIAS_LVL,
+                                           pdata->mic_cfg);
        }
 
        /* Latch volume update bits */
@@ -1772,6 +1978,7 @@ static int wm8962_probe(struct snd_soc_codec *codec)
        wm8962_add_widgets(codec);
 
        wm8962_init_beep(codec);
+       wm8962_init_gpio(codec);
 
        if (i2c->irq) {
                if (pdata && pdata->irq_active_low) {
@@ -1822,6 +2029,9 @@ static int wm8962_remove(struct snd_soc_codec *codec)
        if (i2c->irq)
                free_irq(i2c->irq, codec);
 
+       cancel_delayed_work_sync(&wm8962->mic_work);
+
+       wm8962_free_gpio(codec);
        wm8962_free_beep(codec);
        for (i = 0; i < ARRAY_SIZE(wm8962->supplies); i++)
                regulator_unregister_notifier(wm8962->supplies[i].consumer,