[MIPS] Use ARRAY_SIZE macro when appropriate
[sfrench/cifs-2.6.git] / sound / aoa / codecs / snd-aoa-codec-tas.c
index 2e39ff6ee349b0888196d1e1a7521e2b7c6acfaa..2cd81fa07ce100b2e12b3ba89425457845270b42 100644 (file)
  */
 #include <stddef.h>
 #include <linux/i2c.h>
-#include <linux/i2c-dev.h>
 #include <asm/pmac_low_i2c.h>
 #include <asm/prom.h>
 #include <linux/delay.h>
 #include <linux/module.h>
+#include <linux/mutex.h>
+
 MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
 MODULE_LICENSE("GPL");
 MODULE_DESCRIPTION("tas codec driver for snd-aoa");
 
 #include "snd-aoa-codec-tas.h"
 #include "snd-aoa-codec-tas-gain-table.h"
+#include "snd-aoa-codec-tas-basstreble.h"
 #include "../aoa.h"
 #include "../soundbus/soundbus.h"
 
-
 #define PFX "snd-aoa-codec-tas: "
 
+
 struct tas {
        struct aoa_codec        codec;
        struct i2c_client       i2c;
-       u32                     muted_l:1, muted_r:1,
-                               controls_created:1;
+       u32                     mute_l:1, mute_r:1 ,
+                               controls_created:1 ,
+                               drc_enabled:1,
+                               hw_enabled:1;
        u8                      cached_volume_l, cached_volume_r;
        u8                      mixer_l[3], mixer_r[3];
+       u8                      bass, treble;
        u8                      acr;
+       int                     drc_range;
+       /* protects hardware access against concurrency from
+        * userspace when hitting controls and during
+        * codec init/suspend/resume */
+       struct mutex            mtx;
 };
 
+static int tas_reset_init(struct tas *tas);
+
 static struct tas *codec_to_tas(struct aoa_codec *codec)
 {
        return container_of(codec, struct tas, codec);
@@ -101,6 +113,44 @@ static inline int tas_write_reg(struct tas *tas, u8 reg, u8 len, u8 *data)
                return i2c_smbus_write_i2c_block_data(&tas->i2c, reg, len, data);
 }
 
+static void tas3004_set_drc(struct tas *tas)
+{
+       unsigned char val[6];
+
+       if (tas->drc_enabled)
+               val[0] = 0x50; /* 3:1 above threshold */
+       else
+               val[0] = 0x51; /* disabled */
+       val[1] = 0x02; /* 1:1 below threshold */
+       if (tas->drc_range > 0xef)
+               val[2] = 0xef;
+       else if (tas->drc_range < 0)
+               val[2] = 0x00;
+       else
+               val[2] = tas->drc_range;
+       val[3] = 0xb0;
+       val[4] = 0x60;
+       val[5] = 0xa0;
+
+       tas_write_reg(tas, TAS_REG_DRC, 6, val);
+}
+
+static void tas_set_treble(struct tas *tas)
+{
+       u8 tmp;
+
+       tmp = tas3004_treble(tas->treble);
+       tas_write_reg(tas, TAS_REG_TREBLE, 1, &tmp);
+}
+
+static void tas_set_bass(struct tas *tas)
+{
+       u8 tmp;
+
+       tmp = tas3004_bass(tas->bass);
+       tas_write_reg(tas, TAS_REG_BASS, 1, &tmp);
+}
+
 static void tas_set_volume(struct tas *tas)
 {
        u8 block[6];
@@ -113,8 +163,8 @@ static void tas_set_volume(struct tas *tas)
        if (left > 177) left = 177;
        if (right > 177) right = 177;
 
-       if (tas->muted_l) left = 0;
-       if (tas->muted_r) right = 0;
+       if (tas->mute_l) left = 0;
+       if (tas->mute_r) right = 0;
 
        /* analysing the volume and mixer tables shows
         * that they are similar enough when we shift
@@ -186,8 +236,10 @@ static int tas_snd_vol_get(struct snd_kcontrol *kcontrol,
 {
        struct tas *tas = snd_kcontrol_chip(kcontrol);
 
+       mutex_lock(&tas->mtx);
        ucontrol->value.integer.value[0] = tas->cached_volume_l;
        ucontrol->value.integer.value[1] = tas->cached_volume_r;
+       mutex_unlock(&tas->mtx);
        return 0;
 }
 
@@ -196,13 +248,18 @@ static int tas_snd_vol_put(struct snd_kcontrol *kcontrol,
 {
        struct tas *tas = snd_kcontrol_chip(kcontrol);
 
+       mutex_lock(&tas->mtx);
        if (tas->cached_volume_l == ucontrol->value.integer.value[0]
-        && tas->cached_volume_r == ucontrol->value.integer.value[1])
+        && tas->cached_volume_r == ucontrol->value.integer.value[1]) {
+               mutex_unlock(&tas->mtx);
                return 0;
+       }
 
        tas->cached_volume_l = ucontrol->value.integer.value[0];
        tas->cached_volume_r = ucontrol->value.integer.value[1];
-       tas_set_volume(tas);
+       if (tas->hw_enabled)
+               tas_set_volume(tas);
+       mutex_unlock(&tas->mtx);
        return 1;
 }
 
@@ -230,8 +287,10 @@ static int tas_snd_mute_get(struct snd_kcontrol *kcontrol,
 {
        struct tas *tas = snd_kcontrol_chip(kcontrol);
 
-       ucontrol->value.integer.value[0] = !tas->muted_l;
-       ucontrol->value.integer.value[1] = !tas->muted_r;
+       mutex_lock(&tas->mtx);
+       ucontrol->value.integer.value[0] = !tas->mute_l;
+       ucontrol->value.integer.value[1] = !tas->mute_r;
+       mutex_unlock(&tas->mtx);
        return 0;
 }
 
@@ -240,13 +299,18 @@ static int tas_snd_mute_put(struct snd_kcontrol *kcontrol,
 {
        struct tas *tas = snd_kcontrol_chip(kcontrol);
 
-       if (tas->muted_l == !ucontrol->value.integer.value[0]
-        && tas->muted_r == !ucontrol->value.integer.value[1])
+       mutex_lock(&tas->mtx);
+       if (tas->mute_l == !ucontrol->value.integer.value[0]
+        && tas->mute_r == !ucontrol->value.integer.value[1]) {
+               mutex_unlock(&tas->mtx);
                return 0;
+       }
 
-       tas->muted_l = !ucontrol->value.integer.value[0];
-       tas->muted_r = !ucontrol->value.integer.value[1];
-       tas_set_volume(tas);
+       tas->mute_l = !ucontrol->value.integer.value[0];
+       tas->mute_r = !ucontrol->value.integer.value[1];
+       if (tas->hw_enabled)
+               tas_set_volume(tas);
+       mutex_unlock(&tas->mtx);
        return 1;
 }
 
@@ -275,8 +339,10 @@ static int tas_snd_mixer_get(struct snd_kcontrol *kcontrol,
        struct tas *tas = snd_kcontrol_chip(kcontrol);
        int idx = kcontrol->private_value;
 
+       mutex_lock(&tas->mtx);
        ucontrol->value.integer.value[0] = tas->mixer_l[idx];
        ucontrol->value.integer.value[1] = tas->mixer_r[idx];
+       mutex_unlock(&tas->mtx);
 
        return 0;
 }
@@ -287,14 +353,19 @@ static int tas_snd_mixer_put(struct snd_kcontrol *kcontrol,
        struct tas *tas = snd_kcontrol_chip(kcontrol);
        int idx = kcontrol->private_value;
 
+       mutex_lock(&tas->mtx);
        if (tas->mixer_l[idx] == ucontrol->value.integer.value[0]
-        && tas->mixer_r[idx] == ucontrol->value.integer.value[1])
+        && tas->mixer_r[idx] == ucontrol->value.integer.value[1]) {
+               mutex_unlock(&tas->mtx);
                return 0;
+       }
 
        tas->mixer_l[idx] = ucontrol->value.integer.value[0];
        tas->mixer_r[idx] = ucontrol->value.integer.value[1];
 
-       tas_set_mixer(tas);
+       if (tas->hw_enabled)
+               tas_set_mixer(tas);
+       mutex_unlock(&tas->mtx);
        return 1;
 }
 
@@ -309,9 +380,105 @@ static struct snd_kcontrol_new n##_control = {            \
        .private_value = idx,                           \
 }
 
-MIXER_CONTROL(pcm1, "PCM1", 0);
+MIXER_CONTROL(pcm1, "PCM", 0);
 MIXER_CONTROL(monitor, "Monitor", 2);
 
+static int tas_snd_drc_range_info(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_info *uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = 1;
+       uinfo->value.integer.min = 0;
+       uinfo->value.integer.max = TAS3004_DRC_MAX;
+       return 0;
+}
+
+static int tas_snd_drc_range_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+       mutex_lock(&tas->mtx);
+       ucontrol->value.integer.value[0] = tas->drc_range;
+       mutex_unlock(&tas->mtx);
+       return 0;
+}
+
+static int tas_snd_drc_range_put(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+       mutex_lock(&tas->mtx);
+       if (tas->drc_range == ucontrol->value.integer.value[0]) {
+               mutex_unlock(&tas->mtx);
+               return 0;
+       }
+
+       tas->drc_range = ucontrol->value.integer.value[0];
+       if (tas->hw_enabled)
+               tas3004_set_drc(tas);
+       mutex_unlock(&tas->mtx);
+       return 1;
+}
+
+static struct snd_kcontrol_new drc_range_control = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "DRC Range",
+       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+       .info = tas_snd_drc_range_info,
+       .get = tas_snd_drc_range_get,
+       .put = tas_snd_drc_range_put,
+};
+
+static int tas_snd_drc_switch_info(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_info *uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+       uinfo->count = 1;
+       uinfo->value.integer.min = 0;
+       uinfo->value.integer.max = 1;
+       return 0;
+}
+
+static int tas_snd_drc_switch_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+       mutex_lock(&tas->mtx);
+       ucontrol->value.integer.value[0] = tas->drc_enabled;
+       mutex_unlock(&tas->mtx);
+       return 0;
+}
+
+static int tas_snd_drc_switch_put(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+       mutex_lock(&tas->mtx);
+       if (tas->drc_enabled == ucontrol->value.integer.value[0]) {
+               mutex_unlock(&tas->mtx);
+               return 0;
+       }
+
+       tas->drc_enabled = ucontrol->value.integer.value[0];
+       if (tas->hw_enabled)
+               tas3004_set_drc(tas);
+       mutex_unlock(&tas->mtx);
+       return 1;
+}
+
+static struct snd_kcontrol_new drc_switch_control = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "DRC Range Switch",
+       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+       .info = tas_snd_drc_switch_info,
+       .get = tas_snd_drc_switch_get,
+       .put = tas_snd_drc_switch_put,
+};
+
 static int tas_snd_capture_source_info(struct snd_kcontrol *kcontrol,
        struct snd_ctl_elem_info *uinfo)
 {
@@ -331,7 +498,9 @@ static int tas_snd_capture_source_get(struct snd_kcontrol *kcontrol,
 {
        struct tas *tas = snd_kcontrol_chip(kcontrol);
 
+       mutex_lock(&tas->mtx);
        ucontrol->value.enumerated.item[0] = !!(tas->acr & TAS_ACR_INPUT_B);
+       mutex_unlock(&tas->mtx);
        return 0;
 }
 
@@ -339,14 +508,27 @@ static int tas_snd_capture_source_put(struct snd_kcontrol *kcontrol,
        struct snd_ctl_elem_value *ucontrol)
 {
        struct tas *tas = snd_kcontrol_chip(kcontrol);
-       int oldacr = tas->acr;
+       int oldacr;
+
+       mutex_lock(&tas->mtx);
+       oldacr = tas->acr;
 
-       tas->acr &= ~TAS_ACR_INPUT_B;
+       /*
+        * Despite what the data sheet says in one place, the
+        * TAS_ACR_B_MONAUREAL bit forces mono output even when
+        * input A (line in) is selected.
+        */
+       tas->acr &= ~(TAS_ACR_INPUT_B | TAS_ACR_B_MONAUREAL);
        if (ucontrol->value.enumerated.item[0])
-               tas->acr |= TAS_ACR_INPUT_B;
-       if (oldacr == tas->acr)
+               tas->acr |= TAS_ACR_INPUT_B | TAS_ACR_B_MONAUREAL |
+                     TAS_ACR_B_MON_SEL_RIGHT;
+       if (oldacr == tas->acr) {
+               mutex_unlock(&tas->mtx);
                return 0;
-       tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr);
+       }
+       if (tas->hw_enabled)
+               tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr);
+       mutex_unlock(&tas->mtx);
        return 1;
 }
 
@@ -370,6 +552,101 @@ static struct snd_kcontrol_new capture_source_control = {
        .put = tas_snd_capture_source_put,
 };
 
+static int tas_snd_treble_info(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_info *uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = 1;
+       uinfo->value.integer.min = TAS3004_TREBLE_MIN;
+       uinfo->value.integer.max = TAS3004_TREBLE_MAX;
+       return 0;
+}
+
+static int tas_snd_treble_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+       mutex_lock(&tas->mtx);
+       ucontrol->value.integer.value[0] = tas->treble;
+       mutex_unlock(&tas->mtx);
+       return 0;
+}
+
+static int tas_snd_treble_put(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+       mutex_lock(&tas->mtx);
+       if (tas->treble == ucontrol->value.integer.value[0]) {
+               mutex_unlock(&tas->mtx);
+               return 0;
+       }
+
+       tas->treble = ucontrol->value.integer.value[0];
+       if (tas->hw_enabled)
+               tas_set_treble(tas);
+       mutex_unlock(&tas->mtx);
+       return 1;
+}
+
+static struct snd_kcontrol_new treble_control = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "Treble",
+       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+       .info = tas_snd_treble_info,
+       .get = tas_snd_treble_get,
+       .put = tas_snd_treble_put,
+};
+
+static int tas_snd_bass_info(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_info *uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = 1;
+       uinfo->value.integer.min = TAS3004_BASS_MIN;
+       uinfo->value.integer.max = TAS3004_BASS_MAX;
+       return 0;
+}
+
+static int tas_snd_bass_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+       mutex_lock(&tas->mtx);
+       ucontrol->value.integer.value[0] = tas->bass;
+       mutex_unlock(&tas->mtx);
+       return 0;
+}
+
+static int tas_snd_bass_put(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+       mutex_lock(&tas->mtx);
+       if (tas->bass == ucontrol->value.integer.value[0]) {
+               mutex_unlock(&tas->mtx);
+               return 0;
+       }
+
+       tas->bass = ucontrol->value.integer.value[0];
+       if (tas->hw_enabled)
+               tas_set_bass(tas);
+       mutex_unlock(&tas->mtx);
+       return 1;
+}
+
+static struct snd_kcontrol_new bass_control = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "Bass",
+       .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+       .info = tas_snd_bass_info,
+       .get = tas_snd_bass_get,
+       .put = tas_snd_bass_put,
+};
 
 static struct transfer_info tas_transfers[] = {
        {
@@ -399,27 +676,71 @@ static int tas_usable(struct codec_info_item *cii,
 static int tas_reset_init(struct tas *tas)
 {
        u8 tmp;
+
+       tas->codec.gpio->methods->all_amps_off(tas->codec.gpio);
+       msleep(5);
        tas->codec.gpio->methods->set_hw_reset(tas->codec.gpio, 0);
-       msleep(1);
+       msleep(5);
        tas->codec.gpio->methods->set_hw_reset(tas->codec.gpio, 1);
-       msleep(1);
+       msleep(20);
        tas->codec.gpio->methods->set_hw_reset(tas->codec.gpio, 0);
-       msleep(1);
-
-       tas->acr &= ~TAS_ACR_ANALOG_PDOWN;
-       tas->acr |= TAS_ACR_B_MONAUREAL | TAS_ACR_B_MON_SEL_RIGHT;
-       if (tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr))
-               return -ENODEV;
+       msleep(10);
+       tas->codec.gpio->methods->all_amps_restore(tas->codec.gpio);
 
        tmp = TAS_MCS_SCLK64 | TAS_MCS_SPORT_MODE_I2S | TAS_MCS_SPORT_WL_24BIT;
        if (tas_write_reg(tas, TAS_REG_MCS, 1, &tmp))
-               return -ENODEV;
+               goto outerr;
+
+       tas->acr |= TAS_ACR_ANALOG_PDOWN;
+       if (tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr))
+               goto outerr;
 
        tmp = 0;
        if (tas_write_reg(tas, TAS_REG_MCS2, 1, &tmp))
-               return -ENODEV;
+               goto outerr;
+
+       tas3004_set_drc(tas);
+
+       /* Set treble & bass to 0dB */
+       tas->treble = TAS3004_TREBLE_ZERO;
+       tas->bass = TAS3004_BASS_ZERO;
+       tas_set_treble(tas);
+       tas_set_bass(tas);
+
+       tas->acr &= ~TAS_ACR_ANALOG_PDOWN;
+       if (tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr))
+               goto outerr;
 
        return 0;
+ outerr:
+       return -ENODEV;
+}
+
+static int tas_switch_clock(struct codec_info_item *cii, enum clock_switch clock)
+{
+       struct tas *tas = cii->codec_data;
+
+       switch(clock) {
+       case CLOCK_SWITCH_PREPARE_SLAVE:
+               /* Clocks are going away, mute mute mute */
+               tas->codec.gpio->methods->all_amps_off(tas->codec.gpio);
+               tas->hw_enabled = 0;
+               break;
+       case CLOCK_SWITCH_SLAVE:
+               /* Clocks are back, re-init the codec */
+               mutex_lock(&tas->mtx);
+               tas_reset_init(tas);
+               tas_set_volume(tas);
+               tas_set_mixer(tas);
+               tas->hw_enabled = 1;
+               tas->codec.gpio->methods->all_amps_restore(tas->codec.gpio);
+               mutex_unlock(&tas->mtx);
+               break;
+       default:
+               /* doesn't happen as of now */
+               return -EINVAL;
+       }
+       return 0;
 }
 
 /* we are controlled via i2c and assume that is always up
@@ -427,17 +748,23 @@ static int tas_reset_init(struct tas *tas)
  * our i2c device is suspended, and then take note of that! */
 static int tas_suspend(struct tas *tas)
 {
+       mutex_lock(&tas->mtx);
+       tas->hw_enabled = 0;
        tas->acr |= TAS_ACR_ANALOG_PDOWN;
        tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr);
+       mutex_unlock(&tas->mtx);
        return 0;
 }
 
 static int tas_resume(struct tas *tas)
 {
        /* reset codec */
+       mutex_lock(&tas->mtx);
        tas_reset_init(tas);
        tas_set_volume(tas);
        tas_set_mixer(tas);
+       tas->hw_enabled = 1;
+       mutex_unlock(&tas->mtx);
        return 0;
 }
 
@@ -463,6 +790,7 @@ static struct codec_info tas_codec_info = {
        .bus_factor = 64,
        .owner = THIS_MODULE,
        .usable = tas_usable,
+       .switch_clock = tas_switch_clock,
 #ifdef CONFIG_PM
        .suspend = _tas_suspend,
        .resume = _tas_resume,
@@ -479,10 +807,14 @@ static int tas_init_codec(struct aoa_codec *codec)
                return -EINVAL;
        }
 
+       mutex_lock(&tas->mtx);
        if (tas_reset_init(tas)) {
                printk(KERN_ERR PFX "tas failed to initialise\n");
+               mutex_unlock(&tas->mtx);
                return -ENXIO;
        }
+       tas->hw_enabled = 1;
+       mutex_unlock(&tas->mtx);
 
        if (tas->codec.soundbus_dev->attach_codec(tas->codec.soundbus_dev,
                                                   aoa_get_card(),
@@ -515,6 +847,22 @@ static int tas_init_codec(struct aoa_codec *codec)
        if (err)
                goto error;
 
+       err = aoa_snd_ctl_add(snd_ctl_new1(&drc_range_control, tas));
+       if (err)
+               goto error;
+
+       err = aoa_snd_ctl_add(snd_ctl_new1(&drc_switch_control, tas));
+       if (err)
+               goto error;
+
+       err = aoa_snd_ctl_add(snd_ctl_new1(&treble_control, tas));
+       if (err)
+               goto error;
+
+       err = aoa_snd_ctl_add(snd_ctl_new1(&bass_control, tas));
+       if (err)
+               goto error;
+
        return 0;
  error:
        tas->codec.soundbus_dev->detach_codec(tas->codec.soundbus_dev, tas);
@@ -545,9 +893,12 @@ static int tas_create(struct i2c_adapter *adapter,
        if (!tas)
                return -ENOMEM;
 
+       mutex_init(&tas->mtx);
        tas->i2c.driver = &tas_driver;
        tas->i2c.adapter = adapter;
        tas->i2c.addr = addr;
+       /* seems that half is a saner default */
+       tas->drc_range = TAS3004_DRC_MAX / 2;
        strlcpy(tas->i2c.name, "tas audio codec", I2C_NAME_SIZE-1);
 
        if (i2c_attach_client(&tas->i2c)) {
@@ -564,11 +915,14 @@ static int tas_create(struct i2c_adapter *adapter,
        if (aoa_codec_register(&tas->codec)) {
                goto detach;
        }
-       printk(KERN_DEBUG "snd-aoa-codec-tas: created and attached tas instance\n");
+       printk(KERN_DEBUG
+              "snd-aoa-codec-tas: tas found, addr 0x%02x on %s\n",
+              addr, node->full_name);
        return 0;
  detach:
        i2c_detach_client(&tas->i2c);
  fail:
+       mutex_destroy(&tas->mtx);
        kfree(tas);
        return -EINVAL;
 }
@@ -627,6 +981,7 @@ static int tas_i2c_detach(struct i2c_client *client)
        /* power down codec chip */
        tas_write_reg(tas, TAS_REG_ACR, 1, &tmp);
 
+       mutex_destroy(&tas->mtx);
        kfree(tas);
        return 0;
 }