ASoC: Implement basic WM8993 interrupt support
authorMark Brown <broonie@opensource.wolfsonmicro.com>
Tue, 17 Jan 2012 16:42:05 +0000 (16:42 +0000)
committerMark Brown <broonie@opensource.wolfsonmicro.com>
Fri, 20 Jan 2012 13:58:30 +0000 (13:58 +0000)
If an interrupt is supplied then use it for thermal warning and FLL lock
notifications. When using the interrupt raise the timeout for the FLL lock
substantially to reduce the chances of spurious warnings.

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

index e7ae9fd..eca9352 100644 (file)
@@ -204,9 +204,11 @@ static struct {
 
 struct wm8993_priv {
        struct wm_hubs_data hubs_data;
+       struct device *dev;
        struct regmap *regmap;
        struct regulator_bulk_data supplies[WM8993_NUM_SUPPLIES];
        struct wm8993_platform_data pdata;
+       struct completion fll_lock;
        int master;
        int sysclk_source;
        int tdm_slots;
@@ -225,6 +227,7 @@ static bool wm8993_volatile(struct device *dev, unsigned int reg)
 {
        switch (reg) {
        case WM8993_SOFTWARE_RESET:
+       case WM8993_GPIO_CTRL_1:
        case WM8993_DC_SERVO_0:
        case WM8993_DC_SERVO_READBACK_0:
        case WM8993_DC_SERVO_READBACK_1:
@@ -467,8 +470,10 @@ static int _wm8993_set_fll(struct snd_soc_codec *codec, int fll_id, int source,
                          unsigned int Fref, unsigned int Fout)
 {
        struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+       struct i2c_client *i2c = to_i2c_client(codec->dev);
        u16 reg1, reg4, reg5;
        struct _fll_div fll_div;
+       unsigned int timeout;
        int ret;
 
        /* Any change? */
@@ -539,14 +544,22 @@ static int _wm8993_set_fll(struct snd_soc_codec *codec, int fll_id, int source,
        reg5 |= fll_div.fll_clk_ref_div << WM8993_FLL_CLK_REF_DIV_SHIFT;
        snd_soc_write(codec, WM8993_FLL_CONTROL_5, reg5);
 
+       /* If we've got an interrupt wired up make sure we get it */
+       if (i2c->irq)
+               timeout = msecs_to_jiffies(20);
+       else if (Fref < 1000000)
+               timeout = msecs_to_jiffies(3);
+       else
+               timeout = msecs_to_jiffies(1);
+
+       try_wait_for_completion(&wm8993->fll_lock);
+
        /* Enable the FLL */
        snd_soc_write(codec, WM8993_FLL_CONTROL_1, reg1 | WM8993_FLL_ENA);
 
-       /* Both overestimates */
-       if (Fref < 1000000)
-               msleep(3);
-       else
-               msleep(1);
+       timeout = wait_for_completion_timeout(&wm8993->fll_lock, timeout);
+       if (i2c->irq && !timeout)
+               dev_warn(codec->dev, "Timed out waiting for FLL\n");
 
        dev_dbg(codec->dev, "FLL enabled at %dHz->%dHz\n", Fref, Fout);
 
@@ -1471,6 +1484,45 @@ out:
        return 0;
 }
 
+static irqreturn_t wm8993_irq(int irq, void *data)
+{
+       struct wm8993_priv *wm8993 = data;
+       int mask, val, ret;
+
+       ret = regmap_read(wm8993->regmap, WM8993_GPIO_CTRL_1, &val);
+       if (ret != 0) {
+               dev_err(wm8993->dev, "Failed to read interrupt status: %d\n",
+                       ret);
+               return IRQ_NONE;
+       }
+
+       ret = regmap_read(wm8993->regmap, WM8993_GPIOCTRL_2, &mask);
+       if (ret != 0) {
+               dev_err(wm8993->dev, "Failed to read interrupt mask: %d\n",
+                       ret);
+               return IRQ_NONE;
+       }
+
+       /* The IRQ pin status is visible in the register too */
+       val &= ~(mask | WM8993_IRQ);
+       if (!val)
+               return IRQ_NONE;
+
+       if (val & WM8993_TEMPOK_EINT)
+               dev_crit(wm8993->dev, "Thermal warning\n");
+
+       if (val & WM8993_FLL_LOCK_EINT) {
+               dev_dbg(wm8993->dev, "FLL locked\n");
+               complete(&wm8993->fll_lock);
+       }
+
+       ret = regmap_write(wm8993->regmap, WM8993_GPIO_CTRL_1, val);
+       if (ret != 0)
+               dev_err(wm8993->dev, "Failed to ack interrupt: %d\n", ret);
+
+       return IRQ_HANDLED;
+}
+
 static const struct snd_soc_dai_ops wm8993_ops = {
        .set_sysclk = wm8993_set_sysclk,
        .set_fmt = wm8993_set_dai_fmt,
@@ -1671,6 +1723,9 @@ static __devinit int wm8993_i2c_probe(struct i2c_client *i2c,
        if (wm8993 == NULL)
                return -ENOMEM;
 
+       wm8993->dev = &i2c->dev;
+       init_completion(&wm8993->fll_lock);
+
        wm8993->regmap = regmap_init_i2c(i2c, &wm8993_regmap);
        if (IS_ERR(wm8993->regmap)) {
                ret = PTR_ERR(wm8993->regmap);
@@ -1713,6 +1768,22 @@ static __devinit int wm8993_i2c_probe(struct i2c_client *i2c,
        if (ret != 0)
                goto err_enable;
 
+       if (i2c->irq) {
+               /* Put GPIO1 into interrupt mode (only GPIO1 can output IRQ) */
+               ret = regmap_update_bits(wm8993->regmap, WM8993_GPIO1,
+                                        WM8993_GPIO1_PD |
+                                        WM8993_GPIO1_SEL_MASK, 7);
+               if (ret != 0)
+                       goto err_enable;
+
+               ret = request_threaded_irq(i2c->irq, NULL, wm8993_irq,
+                                          IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+                                          "wm8993", wm8993);
+               if (ret != 0)
+                       goto err_enable;
+
+       }
+
        regulator_bulk_disable(ARRAY_SIZE(wm8993->supplies), wm8993->supplies);
 
        regcache_cache_only(wm8993->regmap, true);
@@ -1721,11 +1792,14 @@ static __devinit int wm8993_i2c_probe(struct i2c_client *i2c,
                        &soc_codec_dev_wm8993, &wm8993_dai, 1);
        if (ret != 0) {
                dev_err(&i2c->dev, "Failed to register CODEC: %d\n", ret);
-               goto err_enable;
+               goto err_irq;
        }
 
        return 0;
 
+err_irq:
+       if (i2c->irq)
+               free_irq(i2c->irq, wm8993);
 err_enable:
        regulator_bulk_disable(ARRAY_SIZE(wm8993->supplies), wm8993->supplies);
 err_get:
@@ -1735,11 +1809,13 @@ err:
        return ret;
 }
 
-static __devexit int wm8993_i2c_remove(struct i2c_client *client)
+static __devexit int wm8993_i2c_remove(struct i2c_client *i2c)
 {
-       struct wm8993_priv *wm8993 = i2c_get_clientdata(client);
+       struct wm8993_priv *wm8993 = i2c_get_clientdata(i2c);
 
-       snd_soc_unregister_codec(&client->dev);
+       snd_soc_unregister_codec(&i2c->dev);
+       if (i2c->irq)
+               free_irq(i2c->irq, wm8993);
        regmap_exit(wm8993->regmap);
        regulator_bulk_disable(ARRAY_SIZE(wm8993->supplies), wm8993->supplies);
        regulator_bulk_free(ARRAY_SIZE(wm8993->supplies), wm8993->supplies);