ASoC: Decouple DAPM from CODECs
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / sound / soc / codecs / tlv320aic3x.c
index 86e5a5868c3cf59b9c63042647a4f62bf594aa26..6173c2b4c3645efc66b1dc52f02f53b1b3ec66c3 100644 (file)
@@ -61,15 +61,25 @@ static const char *aic3x_supply_names[AIC3X_NUM_SUPPLIES] = {
        "DRVDD",        /* ADC Analog and Output Driver Voltage */
 };
 
+struct aic3x_priv;
+
+struct aic3x_disable_nb {
+       struct notifier_block nb;
+       struct aic3x_priv *aic3x;
+};
+
 /* codec private data */
 struct aic3x_priv {
+       struct snd_soc_codec *codec;
        struct regulator_bulk_data supplies[AIC3X_NUM_SUPPLIES];
+       struct aic3x_disable_nb disable_nb[AIC3X_NUM_SUPPLIES];
        enum snd_soc_control_type control_type;
        struct aic3x_setup_data *setup;
        void *control_data;
        unsigned int sysclk;
        int master;
        int gpio_reset;
+       int power;
 #define AIC3X_MODEL_3X 0
 #define AIC3X_MODEL_33 1
 #define AIC3X_MODEL_3007 2
@@ -112,28 +122,23 @@ static const u8 aic3x_reg[AIC3X_CACHEREGNUM] = {
 };
 
 /*
- * write aic3x register cache
- */
-static inline void aic3x_write_reg_cache(struct snd_soc_codec *codec,
-                                        u8 reg, u8 value)
-{
-       u8 *cache = codec->reg_cache;
-       if (reg >= AIC3X_CACHEREGNUM)
-               return;
-       cache[reg] = value;
-}
-
-/*
- * read from the aic3x register space
+ * read from the aic3x register space. Only use for this function is if
+ * wanting to read volatile bits from those registers that has both read-only
+ * and read/write bits. All other cases should use snd_soc_read.
  */
 static int aic3x_read(struct snd_soc_codec *codec, unsigned int reg,
                      u8 *value)
 {
-       *value = reg & 0xff;
+       u8 *cache = codec->reg_cache;
 
-       value[0] = i2c_smbus_read_byte_data(codec->control_data, value[0]);
+       if (codec->cache_only)
+               return -EINVAL;
+       if (reg >= AIC3X_CACHEREGNUM)
+               return -1;
+
+       *value = codec->hw_read(codec, reg);
+       cache[reg] = *value;
 
-       aic3x_write_reg_cache(codec, reg, *value);
        return 0;
 }
 
@@ -178,7 +183,7 @@ static int snd_soc_dapm_put_volsw_aic3x(struct snd_kcontrol *kcontrol,
 
        if (snd_soc_test_bits(widget->codec, reg, val_mask, val)) {
                /* find dapm widget path assoc with kcontrol */
-               list_for_each_entry(path, &widget->codec->dapm_paths, list) {
+               list_for_each_entry(path, &widget->dapm->paths, list) {
                        if (path->kcontrol != kcontrol)
                                continue;
 
@@ -194,7 +199,7 @@ static int snd_soc_dapm_put_volsw_aic3x(struct snd_kcontrol *kcontrol,
                }
 
                if (found)
-                       snd_soc_dapm_sync(widget->codec);
+                       snd_soc_dapm_sync(widget->dapm);
        }
 
        ret = snd_soc_update_bits(widget->codec, reg, val_mask, val);
@@ -612,6 +617,14 @@ static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = {
        SND_SOC_DAPM_INPUT("LINE1R"),
        SND_SOC_DAPM_INPUT("LINE2L"),
        SND_SOC_DAPM_INPUT("LINE2R"),
+
+       /*
+        * Virtual output pin to detection block inside codec. This can be
+        * used to keep codec bias on if gpio or detection features are needed.
+        * Force pin on or construct a path with an input jack and mic bias
+        * widgets.
+        */
+       SND_SOC_DAPM_OUTPUT("Detection"),
 };
 
 static const struct snd_soc_dapm_widget aic3007_dapm_widgets[] = {
@@ -775,17 +788,19 @@ static const struct snd_soc_dapm_route intercon_3007[] = {
 static int aic3x_add_widgets(struct snd_soc_codec *codec)
 {
        struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+       struct snd_soc_dapm_context *dapm = &codec->dapm;
 
-       snd_soc_dapm_new_controls(codec, aic3x_dapm_widgets,
+       snd_soc_dapm_new_controls(dapm, aic3x_dapm_widgets,
                                  ARRAY_SIZE(aic3x_dapm_widgets));
 
        /* set up audio path interconnects */
-       snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+       snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
 
        if (aic3x->model == AIC3X_MODEL_3007) {
-               snd_soc_dapm_new_controls(codec, aic3007_dapm_widgets,
+               snd_soc_dapm_new_controls(dapm, aic3007_dapm_widgets,
                        ARRAY_SIZE(aic3007_dapm_widgets));
-               snd_soc_dapm_add_routes(codec, intercon_3007, ARRAY_SIZE(intercon_3007));
+               snd_soc_dapm_add_routes(dapm, intercon_3007,
+                                       ARRAY_SIZE(intercon_3007));
        }
 
        return 0;
@@ -1027,6 +1042,91 @@ static int aic3x_set_dai_fmt(struct snd_soc_dai *codec_dai,
        return 0;
 }
 
+static int aic3x_init_3007(struct snd_soc_codec *codec)
+{
+       u8 tmp1, tmp2, *cache = codec->reg_cache;
+
+       /*
+        * There is no need to cache writes to undocumented page 0xD but
+        * respective page 0 register cache entries must be preserved
+        */
+       tmp1 = cache[0xD];
+       tmp2 = cache[0x8];
+       /* Class-D speaker driver init; datasheet p. 46 */
+       snd_soc_write(codec, AIC3X_PAGE_SELECT, 0x0D);
+       snd_soc_write(codec, 0xD, 0x0D);
+       snd_soc_write(codec, 0x8, 0x5C);
+       snd_soc_write(codec, 0x8, 0x5D);
+       snd_soc_write(codec, 0x8, 0x5C);
+       snd_soc_write(codec, AIC3X_PAGE_SELECT, 0x00);
+       cache[0xD] = tmp1;
+       cache[0x8] = tmp2;
+
+       return 0;
+}
+
+static int aic3x_regulator_event(struct notifier_block *nb,
+                                unsigned long event, void *data)
+{
+       struct aic3x_disable_nb *disable_nb =
+               container_of(nb, struct aic3x_disable_nb, nb);
+       struct aic3x_priv *aic3x = disable_nb->aic3x;
+
+       if (event & REGULATOR_EVENT_DISABLE) {
+               /*
+                * Put codec to reset and require cache sync as at least one
+                * of the supplies was disabled
+                */
+               if (aic3x->gpio_reset >= 0)
+                       gpio_set_value(aic3x->gpio_reset, 0);
+               aic3x->codec->cache_sync = 1;
+       }
+
+       return 0;
+}
+
+static int aic3x_set_power(struct snd_soc_codec *codec, int power)
+{
+       struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+       int i, ret;
+       u8 *cache = codec->reg_cache;
+
+       if (power) {
+               ret = regulator_bulk_enable(ARRAY_SIZE(aic3x->supplies),
+                                           aic3x->supplies);
+               if (ret)
+                       goto out;
+               aic3x->power = 1;
+               /*
+                * Reset release and cache sync is necessary only if some
+                * supply was off or if there were cached writes
+                */
+               if (!codec->cache_sync)
+                       goto out;
+
+               if (aic3x->gpio_reset >= 0) {
+                       udelay(1);
+                       gpio_set_value(aic3x->gpio_reset, 1);
+               }
+
+               /* Sync reg_cache with the hardware */
+               codec->cache_only = 0;
+               for (i = 0; i < ARRAY_SIZE(aic3x_reg); i++)
+                       snd_soc_write(codec, i, cache[i]);
+               if (aic3x->model == AIC3X_MODEL_3007)
+                       aic3x_init_3007(codec);
+               codec->cache_sync = 0;
+       } else {
+               aic3x->power = 0;
+               /* HW writes are needless when bias is off */
+               codec->cache_only = 1;
+               ret = regulator_bulk_disable(ARRAY_SIZE(aic3x->supplies),
+                                            aic3x->supplies);
+       }
+out:
+       return ret;
+}
+
 static int aic3x_set_bias_level(struct snd_soc_codec *codec,
                                enum snd_soc_bias_level level)
 {
@@ -1037,7 +1137,7 @@ static int aic3x_set_bias_level(struct snd_soc_codec *codec,
        case SND_SOC_BIAS_ON:
                break;
        case SND_SOC_BIAS_PREPARE:
-               if (codec->bias_level == SND_SOC_BIAS_STANDBY &&
+               if (codec->dapm.bias_level == SND_SOC_BIAS_STANDBY &&
                    aic3x->master) {
                        /* enable pll */
                        reg = snd_soc_read(codec, AIC3X_PLL_PROGA_REG);
@@ -1046,7 +1146,9 @@ static int aic3x_set_bias_level(struct snd_soc_codec *codec,
                }
                break;
        case SND_SOC_BIAS_STANDBY:
-               if (codec->bias_level == SND_SOC_BIAS_PREPARE &&
+               if (!aic3x->power)
+                       aic3x_set_power(codec, 1);
+               if (codec->dapm.bias_level == SND_SOC_BIAS_PREPARE &&
                    aic3x->master) {
                        /* disable pll */
                        reg = snd_soc_read(codec, AIC3X_PLL_PROGA_REG);
@@ -1055,9 +1157,11 @@ static int aic3x_set_bias_level(struct snd_soc_codec *codec,
                }
                break;
        case SND_SOC_BIAS_OFF:
+               if (aic3x->power)
+                       aic3x_set_power(codec, 0);
                break;
        }
-       codec->bias_level = level;
+       codec->dapm.bias_level = level;
 
        return 0;
 }
@@ -1154,17 +1258,6 @@ static int aic3x_suspend(struct snd_soc_codec *codec, pm_message_t state)
 
 static int aic3x_resume(struct snd_soc_codec *codec)
 {
-       int i;
-       u8 data[2];
-       u8 *cache = codec->reg_cache;
-
-       /* Sync reg_cache with the hardware */
-       for (i = 0; i < ARRAY_SIZE(aic3x_reg); i++) {
-               data[0] = i;
-               data[1] = cache[i];
-               codec->hw_write(codec->control_data, data, 2);
-       }
-
        aic3x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
 
        return 0;
@@ -1246,28 +1339,21 @@ static int aic3x_init(struct snd_soc_codec *codec)
        snd_soc_write(codec, LINE2R_2_MONOLOPM_VOL, DEFAULT_VOL);
 
        if (aic3x->model == AIC3X_MODEL_3007) {
-               /* Class-D speaker driver init; datasheet p. 46 */
-               snd_soc_write(codec, AIC3X_PAGE_SELECT, 0x0D);
-               snd_soc_write(codec, 0xD, 0x0D);
-               snd_soc_write(codec, 0x8, 0x5C);
-               snd_soc_write(codec, 0x8, 0x5D);
-               snd_soc_write(codec, 0x8, 0x5C);
-               snd_soc_write(codec, AIC3X_PAGE_SELECT, 0x00);
+               aic3x_init_3007(codec);
                snd_soc_write(codec, CLASSD_CTRL, 0);
        }
 
-       /* off, with power on */
-       aic3x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
-
        return 0;
 }
 
 static int aic3x_probe(struct snd_soc_codec *codec)
 {
        struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
-       int ret;
+       int ret, i;
 
        codec->control_data = aic3x->control_data;
+       aic3x->codec = codec;
+       codec->dapm.idle_bias_off = 1;
 
        ret = snd_soc_codec_set_cache_io(codec, 8, 8, aic3x->control_type);
        if (ret != 0) {
@@ -1275,6 +1361,36 @@ static int aic3x_probe(struct snd_soc_codec *codec)
                return ret;
        }
 
+       if (aic3x->gpio_reset >= 0) {
+               ret = gpio_request(aic3x->gpio_reset, "tlv320aic3x reset");
+               if (ret != 0)
+                       goto err_gpio;
+               gpio_direction_output(aic3x->gpio_reset, 0);
+       }
+
+       for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++)
+               aic3x->supplies[i].supply = aic3x_supply_names[i];
+
+       ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(aic3x->supplies),
+                                aic3x->supplies);
+       if (ret != 0) {
+               dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+               goto err_get;
+       }
+       for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) {
+               aic3x->disable_nb[i].nb.notifier_call = aic3x_regulator_event;
+               aic3x->disable_nb[i].aic3x = aic3x;
+               ret = regulator_register_notifier(aic3x->supplies[i].consumer,
+                                                 &aic3x->disable_nb[i].nb);
+               if (ret) {
+                       dev_err(codec->dev,
+                               "Failed to request regulator notifier: %d\n",
+                                ret);
+                       goto err_notif;
+               }
+       }
+
+       codec->cache_only = 1;
        aic3x_init(codec);
 
        if (aic3x->setup) {
@@ -1293,11 +1409,35 @@ static int aic3x_probe(struct snd_soc_codec *codec)
        aic3x_add_widgets(codec);
 
        return 0;
+
+err_notif:
+       while (i--)
+               regulator_unregister_notifier(aic3x->supplies[i].consumer,
+                                             &aic3x->disable_nb[i].nb);
+       regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies);
+err_get:
+       if (aic3x->gpio_reset >= 0)
+               gpio_free(aic3x->gpio_reset);
+err_gpio:
+       kfree(aic3x);
+       return ret;
 }
 
 static int aic3x_remove(struct snd_soc_codec *codec)
 {
+       struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+       int i;
+
        aic3x_set_bias_level(codec, SND_SOC_BIAS_OFF);
+       if (aic3x->gpio_reset >= 0) {
+               gpio_set_value(aic3x->gpio_reset, 0);
+               gpio_free(aic3x->gpio_reset);
+       }
+       for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++)
+               regulator_unregister_notifier(aic3x->supplies[i].consumer,
+                                             &aic3x->disable_nb[i].nb);
+       regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies);
+
        return 0;
 }
 
@@ -1335,7 +1475,7 @@ static int aic3x_i2c_probe(struct i2c_client *i2c,
 {
        struct aic3x_pdata *pdata = i2c->dev.platform_data;
        struct aic3x_priv *aic3x;
-       int ret, i;
+       int ret;
        const struct i2c_device_id *tbl;
 
        aic3x = kzalloc(sizeof(struct aic3x_priv), GFP_KERNEL);
@@ -1355,68 +1495,21 @@ static int aic3x_i2c_probe(struct i2c_client *i2c,
                aic3x->gpio_reset = -1;
        }
 
-       if (aic3x->gpio_reset >= 0) {
-               ret = gpio_request(aic3x->gpio_reset, "tlv320aic3x reset");
-               if (ret != 0)
-                       goto err_gpio;
-               gpio_direction_output(aic3x->gpio_reset, 0);
-       }
-
        for (tbl = aic3x_i2c_id; tbl->name[0]; tbl++) {
                if (!strcmp(tbl->name, id->name))
                        break;
        }
        aic3x->model = tbl - aic3x_i2c_id;
 
-       for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++)
-               aic3x->supplies[i].supply = aic3x_supply_names[i];
-
-       ret = regulator_bulk_get(&i2c->dev, ARRAY_SIZE(aic3x->supplies),
-                                aic3x->supplies);
-       if (ret != 0) {
-               dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret);
-               goto err_get;
-       }
-
-       ret = regulator_bulk_enable(ARRAY_SIZE(aic3x->supplies),
-                                   aic3x->supplies);
-       if (ret != 0) {
-               dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret);
-               goto err_enable;
-       }
-
-       if (aic3x->gpio_reset >= 0) {
-               udelay(1);
-               gpio_set_value(aic3x->gpio_reset, 1);
-       }
-
        ret = snd_soc_register_codec(&i2c->dev,
                        &soc_codec_dev_aic3x, &aic3x_dai, 1);
        if (ret < 0)
-               goto err_enable;
-       return ret;
-
-err_enable:
-       regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies);
-err_get:
-       if (aic3x->gpio_reset >= 0)
-               gpio_free(aic3x->gpio_reset);
-err_gpio:
-       kfree(aic3x);
+               kfree(aic3x);
        return ret;
 }
 
 static int aic3x_i2c_remove(struct i2c_client *client)
 {
-       struct aic3x_priv *aic3x = i2c_get_clientdata(client);
-
-       if (aic3x->gpio_reset >= 0) {
-               gpio_set_value(aic3x->gpio_reset, 0);
-               gpio_free(aic3x->gpio_reset);
-       }
-       regulator_bulk_disable(ARRAY_SIZE(aic3x->supplies), aic3x->supplies);
-       regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies);
-
        snd_soc_unregister_codec(&client->dev);
        kfree(i2c_get_clientdata(client));
        return 0;