#include <linux/init.h>
#include <linux/slab.h>
#include <linux/pci.h>
+#include <linux/moduleparam.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/ac97_codec.h>
static int enable_loopback;
-MODULE_PARM(enable_loopback, "i");
+module_param(enable_loopback, bool, 0444);
MODULE_PARM_DESC(enable_loopback, "Enable AC97 ADC/DAC Loopback Control");
MODULE_PARM_SYNTAX(enable_loopback, SNDRV_BOOLEAN_FALSE_DESC);
return ac97->bus->read(ac97, reg);
}
+/* read a register - return the cached value if already read */
+static inline unsigned short snd_ac97_read_cache(ac97_t *ac97, unsigned short reg)
+{
+ if (! test_bit(reg, ac97->reg_accessed)) {
+ ac97->regs[reg] = ac97->bus->read(ac97, reg);
+ // set_bit(reg, ac97->reg_accessed);
+ }
+ return ac97->regs[reg];
+}
+
/**
* snd_ac97_write_cache - write a value on the given register and update the cache
* @ac97: the ac97 instance
if (!snd_ac97_valid_reg(ac97, reg))
return -EINVAL;
spin_lock(&ac97->reg_lock);
- old = ac97->regs[reg];
+ old = snd_ac97_read_cache(ac97, reg);
new = (old & ~mask) | value;
change = old != new;
if (change) {
static int snd_ac97_ad18xx_update_pcm_bits(ac97_t *ac97, int codec, unsigned short mask, unsigned short value)
{
int change;
- unsigned short old, new;
+ unsigned short old, new, cfg;
down(&ac97->spec.ad18xx.mutex);
spin_lock(&ac97->reg_lock);
old = ac97->spec.ad18xx.pcmreg[codec];
new = (old & ~mask) | value;
+ cfg = snd_ac97_read_cache(ac97, AC97_AD_SERIAL_CFG);
change = old != new;
if (change) {
ac97->spec.ad18xx.pcmreg[codec] = new;
spin_unlock(&ac97->reg_lock);
/* select single codec */
ac97->bus->write(ac97, AC97_AD_SERIAL_CFG,
- (ac97->regs[AC97_AD_SERIAL_CFG] & ~0x7000) |
+ (cfg & ~0x7000) |
ac97->spec.ad18xx.unchained[codec] | ac97->spec.ad18xx.chained[codec]);
/* update PCM bits */
ac97->bus->write(ac97, AC97_PCM, new);
/* select all codecs */
ac97->bus->write(ac97, AC97_AD_SERIAL_CFG,
- ac97->regs[AC97_AD_SERIAL_CFG] | 0x7000);
+ cfg | 0x7000);
} else
spin_unlock(&ac97->reg_lock);
up(&ac97->spec.ad18xx.mutex);
ac97_t *ac97 = snd_kcontrol_chip(kcontrol);
unsigned short val;
- val = ac97->regs[AC97_REC_SEL];
+ val = snd_ac97_read_cache(ac97, AC97_REC_SEL);
ucontrol->value.enumerated.item[0] = (val >> 8) & 7;
ucontrol->value.enumerated.item[1] = (val >> 0) & 7;
return 0;
int shift = (kcontrol->private_value >> 8) & 0xff;
int invert = (kcontrol->private_value >> 24) & 0xff;
- val = (ac97->regs[reg] >> shift) & 1;
+ val = (snd_ac97_read_cache(ac97, reg) >> shift) & 1;
if (invert)
val ^= 1;
ucontrol->value.enumerated.item[0] = val;
int mask = (kcontrol->private_value >> 16) & 0xff;
int invert = (kcontrol->private_value >> 24) & 0xff;
- ucontrol->value.integer.value[0] = (ac97->regs[reg] >> shift) & mask;
+ ucontrol->value.integer.value[0] = (snd_ac97_read_cache(ac97, reg) >> shift) & mask;
if (invert)
ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
return 0;
int invert = (kcontrol->private_value >> 24) & 0xff;
spin_lock(&ac97->reg_lock);
- ucontrol->value.integer.value[0] = (ac97->regs[reg] >> shift_left) & mask;
- ucontrol->value.integer.value[1] = (ac97->regs[reg] >> shift_right) & mask;
+ ucontrol->value.integer.value[0] = (snd_ac97_read_cache(ac97, reg) >> shift_left) & mask;
+ ucontrol->value.integer.value[1] = (snd_ac97_read_cache(ac97, reg) >> shift_right) & mask;
spin_unlock(&ac97->reg_lock);
if (invert) {
ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
AC97_CXR_SPDIF_MASK | AC97_CXR_COPYRGT,
v);
} else {
- unsigned short extst = ac97->regs[AC97_EXTENDED_STATUS];
+ unsigned short extst = snd_ac97_read_cache(ac97, AC97_EXTENDED_STATUS);
snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0); /* turn off */
change |= snd_ac97_update_bits(ac97, AC97_SPDIF, 0x3fff, val);
mask <<= shift;
value <<= shift;
spin_lock(&ac97->reg_lock);
- old = ac97->regs[reg];
+ old = snd_ac97_read_cache(ac97, reg);
new = (old & ~mask) | value;
spin_unlock(&ac97->reg_lock);
if (old != new) {
int change;
- unsigned short extst = ac97->regs[AC97_EXTENDED_STATUS];
+ unsigned short extst = snd_ac97_read_cache(ac97, AC97_EXTENDED_STATUS);
snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0); /* turn off */
change = snd_ac97_update_bits(ac97, reg, mask, value);
if (extst & AC97_EA_SPDIF)
.info = snd_ac97_info_single,
.get = snd_ac97_get_single,
.put = snd_ac97_put_spsa,
- .private_value = AC97_EXTENDED_STATUS | (4 << 8) | (3 << 16) | (0 << 24),
+ .private_value = AC97_SINGLE_VALUE(AC97_EXTENDED_STATUS, 4, 3, 0)
},
};
#define AD18XX_PCM_BITS(xname, codec, lshift, rshift, mask) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ac97_ad18xx_pcm_info_bits, \
.get = snd_ac97_ad18xx_pcm_get_bits, .put = snd_ac97_ad18xx_pcm_put_bits, \
- .private_value = (codec) | ((lshift) << 8) | ((rshift) << 12) | ((mask) << 24) }
+ .private_value = (codec) | ((lshift) << 8) | ((rshift) << 12) | ((mask) << 16) }
static int snd_ac97_ad18xx_pcm_info_bits(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
{
*
*/
+static void snd_ac97_powerdown(ac97_t *ac97);
+
static int snd_ac97_bus_free(ac97_bus_t *bus)
{
if (bus) {
static int snd_ac97_dev_free(snd_device_t *device)
{
ac97_t *ac97 = snd_magic_cast(ac97_t, device->device_data, return -ENXIO);
+ snd_ac97_powerdown(ac97); /* for avoiding click noises during shut down */
return snd_ac97_free(ac97);
}
*r_result = result;
}
+/* check AC97_SPDIF register to accept which sample rates */
+static unsigned int snd_ac97_determine_spdif_rates(ac97_t *ac97)
+{
+ unsigned int result = 0;
+ int i;
+ static unsigned short ctl_bits[] = {
+ AC97_SC_SPSR_44K, AC97_SC_SPSR_32K, AC97_SC_SPSR_48K
+ };
+ static unsigned int rate_bits[] = {
+ SNDRV_PCM_RATE_44100, SNDRV_PCM_RATE_32000, SNDRV_PCM_RATE_48000
+ };
+
+ for (i = 0; i < (int)ARRAY_SIZE(ctl_bits); i++) {
+ snd_ac97_update_bits(ac97, AC97_SPDIF, AC97_SC_SPSR_MASK, ctl_bits[i]);
+ if ((snd_ac97_read(ac97, AC97_SPDIF) & AC97_SC_SPSR_MASK) == ctl_bits[i])
+ result |= rate_bits[i];
+ }
+ return result;
+}
+
void snd_ac97_get_name(ac97_t *ac97, unsigned int id, char *name, int modem)
{
const ac97_codec_id_t *pid;
else if (ac97->id == AC97_ID_CM9739)
ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000;
else
- ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000 |
- SNDRV_PCM_RATE_44100 |
- SNDRV_PCM_RATE_32000;
+ ac97->rates[AC97_RATES_SPDIF] = snd_ac97_determine_spdif_rates(ac97);
}
if (ac97->ext_id & AC97_EI_VRM) { /* MIC VRA support */
snd_ac97_determine_rates(ac97, AC97_PCM_MIC_ADC_RATE, 0, &ac97->rates[AC97_RATES_MIC_ADC]);
}
/* make sure the proper powerdown bits are cleared */
if (ac97->scaps) {
- reg = snd_ac97_read(ac97, AC97_EXTENDED_ID);
+ reg = snd_ac97_read(ac97, AC97_EXTENDED_STATUS);
if (ac97->scaps & AC97_SCAP_SURROUND_DAC)
reg &= ~AC97_EA_PRJ;
if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC)
reg &= ~(AC97_EA_PRI | AC97_EA_PRK);
- snd_ac97_write_cache(ac97, AC97_EXTENDED_ID, reg);
+ snd_ac97_write_cache(ac97, AC97_EXTENDED_STATUS, reg);
}
snd_ac97_proc_init(ac97);
if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, ac97, &ops)) < 0) {
return 0;
}
-#ifdef CONFIG_PM
-/**
- * snd_ac97_suspend - General suspend function for AC97 codec
- * @ac97: the ac97 instance
+
+/*
+ * Power down the chip.
*
- * Suspends the codec, power down the chip.
+ * MASTER and HEADPHONE registers are muted but the register cache values
+ * are not changed, so that the values can be restored in snd_ac97_resume().
*/
-void snd_ac97_suspend(ac97_t *ac97)
+static void snd_ac97_powerdown(ac97_t *ac97)
{
- unsigned short power = (ac97->regs[AC97_POWERDOWN] ^ 0x8000) & ~0x8000; /* invert EAPD */
+ unsigned short power;
+
+ if (ac97_is_audio(ac97)) {
+ /* some codecs have stereo mute bits */
+ snd_ac97_write(ac97, AC97_MASTER, 0x9f9f);
+ snd_ac97_write(ac97, AC97_HEADPHONE, 0x9f9f);
+ }
+ power = ac97->regs[AC97_POWERDOWN] | 0x8000; /* EAPD */
power |= 0x4000; /* Headphone amplifier powerdown */
power |= 0x0300; /* ADC & DAC powerdown */
snd_ac97_write(ac97, AC97_POWERDOWN, power);
power |= 0x0400; /* Analog Mixer powerdown (Vref on) */
snd_ac97_write(ac97, AC97_POWERDOWN, power);
udelay(100);
+#if 0
+ /* FIXME: this causes click noises on some boards at resume */
power |= 0x3800; /* AC-link powerdown, internal Clk disable */
snd_ac97_write(ac97, AC97_POWERDOWN, power);
+#endif
+}
+
+
+#ifdef CONFIG_PM
+/**
+ * snd_ac97_suspend - General suspend function for AC97 codec
+ * @ac97: the ac97 instance
+ *
+ * Suspends the codec, power down the chip.
+ */
+void snd_ac97_suspend(ac97_t *ac97)
+{
+ snd_ac97_powerdown(ac97);
}
/**
EXPORT_SYMBOL(snd_ac97_set_rate);
#ifdef CONFIG_PM
EXPORT_SYMBOL(snd_ac97_resume);
+EXPORT_SYMBOL(snd_ac97_suspend);
#endif
/*