ASoC: Convert WM8962 register access map to modern style
[deliverable/linux.git] / sound / soc / codecs / sta32x.c
index bb82408ab8e1bb93f187dac610e5a1141501bd72..7db6fa515028d566cfd75c5ee048db8b52f2ca54 100644 (file)
@@ -24,9 +24,9 @@
 #include <linux/delay.h>
 #include <linux/pm.h>
 #include <linux/i2c.h>
-#include <linux/platform_device.h>
 #include <linux/regulator/consumer.h>
 #include <linux/slab.h>
+#include <linux/workqueue.h>
 #include <sound/core.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
@@ -35,6 +35,7 @@
 #include <sound/initval.h>
 #include <sound/tlv.h>
 
+#include <sound/sta32x.h>
 #include "sta32x.h"
 
 #define STA32X_RATES (SNDRV_PCM_RATE_32000 | \
@@ -73,9 +74,14 @@ static const char *sta32x_supply_names[] = {
 struct sta32x_priv {
        struct regulator_bulk_data supplies[ARRAY_SIZE(sta32x_supply_names)];
        struct snd_soc_codec *codec;
+       struct sta32x_platform_data *pdata;
 
        unsigned int mclk;
        unsigned int format;
+
+       u32 coef_shadow[STA32X_COEF_COUNT];
+       struct delayed_work watchdog_work;
+       int shutdown;
 };
 
 static const DECLARE_TLV_DB_SCALE(mvol_tlv, -12700, 50, 1);
@@ -227,6 +233,7 @@ static int sta32x_coefficient_put(struct snd_kcontrol *kcontrol,
                                  struct snd_ctl_elem_value *ucontrol)
 {
        struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec);
        int numcoef = kcontrol->private_value >> 16;
        int index = kcontrol->private_value & 0xffff;
        unsigned int cfud;
@@ -239,6 +246,11 @@ static int sta32x_coefficient_put(struct snd_kcontrol *kcontrol,
        snd_soc_write(codec, STA32X_CFUD, cfud);
 
        snd_soc_write(codec, STA32X_CFADDR2, index);
+       for (i = 0; i < numcoef && (index + i < STA32X_COEF_COUNT); i++)
+               sta32x->coef_shadow[index + i] =
+                         (ucontrol->value.bytes.data[3 * i] << 16)
+                       | (ucontrol->value.bytes.data[3 * i + 1] << 8)
+                       | (ucontrol->value.bytes.data[3 * i + 2]);
        for (i = 0; i < 3 * numcoef; i++)
                snd_soc_write(codec, STA32X_B1CF1 + i,
                              ucontrol->value.bytes.data[i]);
@@ -252,6 +264,88 @@ static int sta32x_coefficient_put(struct snd_kcontrol *kcontrol,
        return 0;
 }
 
+static int sta32x_sync_coef_shadow(struct snd_soc_codec *codec)
+{
+       struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec);
+       unsigned int cfud;
+       int i;
+
+       /* preserve reserved bits in STA32X_CFUD */
+       cfud = snd_soc_read(codec, STA32X_CFUD) & 0xf0;
+
+       for (i = 0; i < STA32X_COEF_COUNT; i++) {
+               snd_soc_write(codec, STA32X_CFADDR2, i);
+               snd_soc_write(codec, STA32X_B1CF1,
+                             (sta32x->coef_shadow[i] >> 16) & 0xff);
+               snd_soc_write(codec, STA32X_B1CF2,
+                             (sta32x->coef_shadow[i] >> 8) & 0xff);
+               snd_soc_write(codec, STA32X_B1CF3,
+                             (sta32x->coef_shadow[i]) & 0xff);
+               /* chip documentation does not say if the bits are
+                * self-clearing, so do it explicitly */
+               snd_soc_write(codec, STA32X_CFUD, cfud);
+               snd_soc_write(codec, STA32X_CFUD, cfud | 0x01);
+       }
+       return 0;
+}
+
+static int sta32x_cache_sync(struct snd_soc_codec *codec)
+{
+       unsigned int mute;
+       int rc;
+
+       if (!codec->cache_sync)
+               return 0;
+
+       /* mute during register sync */
+       mute = snd_soc_read(codec, STA32X_MMUTE);
+       snd_soc_write(codec, STA32X_MMUTE, mute | STA32X_MMUTE_MMUTE);
+       sta32x_sync_coef_shadow(codec);
+       rc = snd_soc_cache_sync(codec);
+       snd_soc_write(codec, STA32X_MMUTE, mute);
+       return rc;
+}
+
+/* work around ESD issue where sta32x resets and loses all configuration */
+static void sta32x_watchdog(struct work_struct *work)
+{
+       struct sta32x_priv *sta32x = container_of(work, struct sta32x_priv,
+                                                 watchdog_work.work);
+       struct snd_soc_codec *codec = sta32x->codec;
+       unsigned int confa, confa_cached;
+
+       /* check if sta32x has reset itself */
+       confa_cached = snd_soc_read(codec, STA32X_CONFA);
+       codec->cache_bypass = 1;
+       confa = snd_soc_read(codec, STA32X_CONFA);
+       codec->cache_bypass = 0;
+       if (confa != confa_cached) {
+               codec->cache_sync = 1;
+               sta32x_cache_sync(codec);
+       }
+
+       if (!sta32x->shutdown)
+               schedule_delayed_work(&sta32x->watchdog_work,
+                                     round_jiffies_relative(HZ));
+}
+
+static void sta32x_watchdog_start(struct sta32x_priv *sta32x)
+{
+       if (sta32x->pdata->needs_esd_watchdog) {
+               sta32x->shutdown = 0;
+               schedule_delayed_work(&sta32x->watchdog_work,
+                                     round_jiffies_relative(HZ));
+       }
+}
+
+static void sta32x_watchdog_stop(struct sta32x_priv *sta32x)
+{
+       if (sta32x->pdata->needs_esd_watchdog) {
+               sta32x->shutdown = 1;
+               cancel_delayed_work_sync(&sta32x->watchdog_work);
+       }
+}
+
 #define SINGLE_COEF(xname, index) \
 {      .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
        .info = sta32x_coefficient_info, \
@@ -428,6 +522,7 @@ static int sta32x_set_dai_sysclk(struct snd_soc_dai *codec_dai,
                                                rate_min = fs;
                                        if (fs > rate_max)
                                                rate_max = fs;
+                                       break;
                                }
                        }
                }
@@ -661,7 +756,8 @@ static int sta32x_set_bias_level(struct snd_soc_codec *codec,
                                return ret;
                        }
 
-                       snd_soc_cache_sync(codec);
+                       sta32x_cache_sync(codec);
+                       sta32x_watchdog_start(sta32x);
                }
 
                /* Power up to mute */
@@ -678,7 +774,7 @@ static int sta32x_set_bias_level(struct snd_soc_codec *codec,
                                    STA32X_CONFF_PWDN | STA32X_CONFF_EAPD,
                                    STA32X_CONFF_PWDN);
                msleep(300);
-
+               sta32x_watchdog_stop(sta32x);
                regulator_bulk_disable(ARRAY_SIZE(sta32x->supplies),
                                       sta32x->supplies);
                break;
@@ -687,7 +783,7 @@ static int sta32x_set_bias_level(struct snd_soc_codec *codec,
        return 0;
 }
 
-static struct snd_soc_dai_ops sta32x_dai_ops = {
+static const struct snd_soc_dai_ops sta32x_dai_ops = {
        .hw_params      = sta32x_hw_params,
        .set_sysclk     = sta32x_set_dai_sysclk,
        .set_fmt        = sta32x_set_dai_fmt,
@@ -706,7 +802,7 @@ static struct snd_soc_dai_driver sta32x_dai = {
 };
 
 #ifdef CONFIG_PM
-static int sta32x_suspend(struct snd_soc_codec *codec, pm_message_t state)
+static int sta32x_suspend(struct snd_soc_codec *codec)
 {
        sta32x_set_bias_level(codec, SND_SOC_BIAS_OFF);
        return 0;
@@ -725,9 +821,10 @@ static int sta32x_resume(struct snd_soc_codec *codec)
 static int sta32x_probe(struct snd_soc_codec *codec)
 {
        struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec);
-       int i, ret = 0;
+       int i, ret = 0, thermal = 0;
 
        sta32x->codec = codec;
+       sta32x->pdata = dev_get_platdata(codec->dev);
 
        /* regulators */
        for (i = 0; i < ARRAY_SIZE(sta32x->supplies); i++)
@@ -770,25 +867,48 @@ static int sta32x_probe(struct snd_soc_codec *codec)
        snd_soc_cache_write(codec, STA32X_AUTO3, 0x00);
        snd_soc_cache_write(codec, STA32X_C3CFG, 0x40);
 
-       /* FIXME enable thermal warning adjustment and recovery  */
+       /* set thermal warning adjustment and recovery */
+       if (!(sta32x->pdata->thermal_conf & STA32X_THERMAL_ADJUSTMENT_ENABLE))
+               thermal |= STA32X_CONFA_TWAB;
+       if (!(sta32x->pdata->thermal_conf & STA32X_THERMAL_RECOVERY_ENABLE))
+               thermal |= STA32X_CONFA_TWRB;
        snd_soc_update_bits(codec, STA32X_CONFA,
-                           STA32X_CONFA_TWAB | STA32X_CONFA_TWRB, 0);
+                           STA32X_CONFA_TWAB | STA32X_CONFA_TWRB,
+                           thermal);
 
-       /* FIXME select 2.1 mode  */
+       /* select output configuration  */
        snd_soc_update_bits(codec, STA32X_CONFF,
                            STA32X_CONFF_OCFG_MASK,
-                           1 << STA32X_CONFF_OCFG_SHIFT);
+                           sta32x->pdata->output_conf
+                           << STA32X_CONFF_OCFG_SHIFT);
 
-       /* FIXME channel to output mapping */
+       /* channel to output mapping */
        snd_soc_update_bits(codec, STA32X_C1CFG,
                            STA32X_CxCFG_OM_MASK,
-                           0 << STA32X_CxCFG_OM_SHIFT);
+                           sta32x->pdata->ch1_output_mapping
+                           << STA32X_CxCFG_OM_SHIFT);
        snd_soc_update_bits(codec, STA32X_C2CFG,
                            STA32X_CxCFG_OM_MASK,
-                           1 << STA32X_CxCFG_OM_SHIFT);
+                           sta32x->pdata->ch2_output_mapping
+                           << STA32X_CxCFG_OM_SHIFT);
        snd_soc_update_bits(codec, STA32X_C3CFG,
                            STA32X_CxCFG_OM_MASK,
-                           2 << STA32X_CxCFG_OM_SHIFT);
+                           sta32x->pdata->ch3_output_mapping
+                           << STA32X_CxCFG_OM_SHIFT);
+
+       /* initialize coefficient shadow RAM with reset values */
+       for (i = 4; i <= 49; i += 5)
+               sta32x->coef_shadow[i] = 0x400000;
+       for (i = 50; i <= 54; i++)
+               sta32x->coef_shadow[i] = 0x7fffff;
+       sta32x->coef_shadow[55] = 0x5a9df7;
+       sta32x->coef_shadow[56] = 0x7fffff;
+       sta32x->coef_shadow[59] = 0x7fffff;
+       sta32x->coef_shadow[60] = 0x400000;
+       sta32x->coef_shadow[61] = 0x400000;
+
+       if (sta32x->pdata->needs_esd_watchdog)
+               INIT_DELAYED_WORK(&sta32x->watchdog_work, sta32x_watchdog);
 
        sta32x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
        /* Bias level configuration will have done an extra enable */
@@ -806,6 +926,7 @@ static int sta32x_remove(struct snd_soc_codec *codec)
 {
        struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec);
 
+       sta32x_watchdog_stop(sta32x);
        sta32x_set_bias_level(codec, SND_SOC_BIAS_OFF);
        regulator_bulk_disable(ARRAY_SIZE(sta32x->supplies), sta32x->supplies);
        regulator_bulk_free(ARRAY_SIZE(sta32x->supplies), sta32x->supplies);
@@ -848,28 +969,23 @@ static __devinit int sta32x_i2c_probe(struct i2c_client *i2c,
        struct sta32x_priv *sta32x;
        int ret;
 
-       sta32x = kzalloc(sizeof(struct sta32x_priv), GFP_KERNEL);
+       sta32x = devm_kzalloc(&i2c->dev, sizeof(struct sta32x_priv),
+                             GFP_KERNEL);
        if (!sta32x)
                return -ENOMEM;
 
        i2c_set_clientdata(i2c, sta32x);
 
        ret = snd_soc_register_codec(&i2c->dev, &sta32x_codec, &sta32x_dai, 1);
-       if (ret != 0) {
+       if (ret != 0)
                dev_err(&i2c->dev, "Failed to register codec (%d)\n", ret);
-               kfree(sta32x);
-               return ret;
-       }
 
-       return 0;
+       return ret;
 }
 
 static __devexit int sta32x_i2c_remove(struct i2c_client *client)
 {
-       struct sta32x_priv *sta32x = i2c_get_clientdata(client);
-
        snd_soc_unregister_codec(&client->dev);
-       kfree(sta32x);
        return 0;
 }
 
This page took 0.028889 seconds and 5 git commands to generate.