*/
#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"
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);
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];
{
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;
}
{
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];
if (tas->hw_enabled)
tas_set_volume(tas);
+ mutex_unlock(&tas->mtx);
return 1;
}
{
struct tas *tas = snd_kcontrol_chip(kcontrol);
+ 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;
}
{
struct tas *tas = snd_kcontrol_chip(kcontrol);
+ mutex_lock(&tas->mtx);
if (tas->mute_l == !ucontrol->value.integer.value[0]
- && tas->mute_r == !ucontrol->value.integer.value[1])
+ && tas->mute_r == !ucontrol->value.integer.value[1]) {
+ mutex_unlock(&tas->mtx);
return 0;
+ }
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;
}
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;
}
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];
if (tas->hw_enabled)
tas_set_mixer(tas);
+ mutex_unlock(&tas->mtx);
return 1;
}
{
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;
}
{
struct tas *tas = snd_kcontrol_chip(kcontrol);
- if (tas->drc_range == ucontrol->value.integer.value[0])
+ 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;
}
{
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;
}
{
struct tas *tas = snd_kcontrol_chip(kcontrol);
- if (tas->drc_enabled == ucontrol->value.integer.value[0])
+ 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;
}
{
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;
}
struct snd_ctl_elem_value *ucontrol)
{
struct tas *tas = snd_kcontrol_chip(kcontrol);
- int oldacr = tas->acr;
+ int oldacr;
- tas->acr &= ~TAS_ACR_INPUT_B;
+ mutex_lock(&tas->mtx);
+ oldacr = tas->acr;
+
+ /*
+ * 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;
+ }
if (tas->hw_enabled)
tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr);
+ mutex_unlock(&tas->mtx);
return 1;
}
.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[] = {
{
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 | TAS_ACR_B_MONAUREAL |
- TAS_ACR_B_MON_SEL_RIGHT;
+ tas->acr |= TAS_ACR_ANALOG_PDOWN;
if (tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr))
- return -ENODEV;
+ 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 */
- tmp = 114;
- tas_write_reg(tas, TAS_REG_TREBLE, 1, &tmp);
- tas_write_reg(tas, TAS_REG_BASS, 1, &tmp);
+ 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))
- return -ENODEV;
+ goto outerr;
return 0;
+ outerr:
+ return -ENODEV;
}
static int tas_switch_clock(struct codec_info_item *cii, enum clock_switch clock)
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 */
* 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;
}
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(),
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);
if (!tas)
return -ENOMEM;
+ mutex_init(&tas->mtx);
tas->i2c.driver = &tas_driver;
tas->i2c.adapter = adapter;
tas->i2c.addr = addr;
detach:
i2c_detach_client(&tas->i2c);
fail:
+ mutex_destroy(&tas->mtx);
kfree(tas);
return -EINVAL;
}
/* power down codec chip */
tas_write_reg(tas, TAS_REG_ACR, 1, &tmp);
+ mutex_destroy(&tas->mtx);
kfree(tas);
return 0;
}