Commit 6dbe6628 authored by Takashi Iwai's avatar Takashi Iwai Committed by Jaroslav Kysela

[ALSA] Add experimental support of aggressive AC97 power-saving mode

Added CONFIG_SND_AC97_POWER_SAVE kernel config to enable the support
of aggressive AC97 power-saving mode.  In this mode, the AC97
powerdown register bits are dynamically controlled at each open/close
of PCM streams.
The mode is activated via power_save option for snd-ac97-codec
driver.  As default it's off.  It can be turned on/off on the fly
via sysfs, too.
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
Signed-off-by: default avatarJaroslav Kysela <perex@suse.cz>
parent 2b29b13c
......@@ -27,6 +27,7 @@
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/workqueue.h>
#include "pcm.h"
#include "control.h"
#include "info.h"
......@@ -140,6 +141,20 @@
#define AC97_GP_DRSS_1011 0x0000 /* LR(C) 10+11(+12) */
#define AC97_GP_DRSS_78 0x0400 /* LR 7+8 */
/* powerdown bits */
#define AC97_PD_ADC_STATUS 0x0001 /* ADC status (RO) */
#define AC97_PD_DAC_STATUS 0x0002 /* DAC status (RO) */
#define AC97_PD_MIXER_STATUS 0x0004 /* Analog mixer status (RO) */
#define AC97_PD_VREF_STATUS 0x0008 /* Vref status (RO) */
#define AC97_PD_PR0 0x0100 /* Power down PCM ADCs and input MUX */
#define AC97_PD_PR1 0x0200 /* Power down PCM front DAC */
#define AC97_PD_PR2 0x0400 /* Power down Mixer (Vref still on) */
#define AC97_PD_PR3 0x0800 /* Power down Mixer (Vref off) */
#define AC97_PD_PR4 0x1000 /* Power down AC-Link */
#define AC97_PD_PR5 0x2000 /* Disable internal clock usage */
#define AC97_PD_PR6 0x4000 /* Headphone amplifier */
#define AC97_PD_EAPD 0x8000 /* External Amplifer Power Down (EAPD) */
/* extended audio ID bit defines */
#define AC97_EI_VRA 0x0001 /* Variable bit rate supported */
#define AC97_EI_DRA 0x0002 /* Double rate supported */
......@@ -359,6 +374,7 @@
#define AC97_SCAP_INV_EAPD (1<<7) /* inverted EAPD */
#define AC97_SCAP_DETECT_BY_VENDOR (1<<8) /* use vendor registers for read tests */
#define AC97_SCAP_NO_SPDIF (1<<9) /* don't build SPDIF controls */
#define AC97_SCAP_EAPD_LED (1<<10) /* EAPD as mute LED */
/* ac97->flags */
#define AC97_HAS_PC_BEEP (1<<0) /* force PC Speaker usage */
......@@ -491,6 +507,12 @@ struct snd_ac97 {
/* jack-sharing info */
unsigned char indep_surround;
unsigned char channel_mode;
#ifdef CONFIG_SND_AC97_POWER_SAVE
unsigned int power_up; /* power states */
struct workqueue_struct *power_workq;
struct work_struct power_work;
#endif
struct device dev;
};
......@@ -532,6 +554,15 @@ unsigned short snd_ac97_read(struct snd_ac97 *ac97, unsigned short reg);
void snd_ac97_write_cache(struct snd_ac97 *ac97, unsigned short reg, unsigned short value);
int snd_ac97_update(struct snd_ac97 *ac97, unsigned short reg, unsigned short value);
int snd_ac97_update_bits(struct snd_ac97 *ac97, unsigned short reg, unsigned short mask, unsigned short value);
#ifdef CONFIG_SND_AC97_POWER_SAVE
int snd_ac97_update_power(struct snd_ac97 *ac97, int reg, int powerup);
#else
static inline int snd_ac97_update_power(struct snd_ac97 *ac97, int reg,
int powerup)
{
return 0;
}
#endif
#ifdef CONFIG_PM
void snd_ac97_suspend(struct snd_ac97 *ac97);
void snd_ac97_resume(struct snd_ac97 *ac97);
......@@ -583,6 +614,7 @@ struct ac97_pcm {
copy_flag: 1, /* lowlevel driver must fill all entries */
spdif: 1; /* spdif pcm */
unsigned short aslots; /* active slots */
unsigned short cur_dbl; /* current double-rate state */
unsigned int rates; /* available rates */
struct {
unsigned short slots; /* driver input: requested AC97 slot numbers */
......
......@@ -100,4 +100,17 @@ config SND_MPU401
To compile this driver as a module, choose M here: the module
will be called snd-mpu401.
config SND_AC97_POWER_SAVE
bool "AC97 Power-Saving Mode"
depends on SND_AC97_CODEC && EXPERIMENTAL
default n
help
Say Y here to enable the aggressive power-saving support of
AC97 codecs. In this mode, the power-mode is dynamically
controlled at each open/close.
The mode is activated by passing power_save=1 option to
snd-ac97-codec driver. You can toggle it dynamically over
sysfs, too.
endmenu
This diff is collapsed.
......@@ -269,6 +269,7 @@ int snd_ac97_set_rate(struct snd_ac97 *ac97, int reg, unsigned int rate)
return -EINVAL;
}
snd_ac97_update_power(ac97, reg, 1);
switch (reg) {
case AC97_PCM_MIC_ADC_RATE:
if ((ac97->regs[AC97_EXTENDED_STATUS] & AC97_EA_VRM) == 0) /* MIC VRA */
......@@ -606,6 +607,7 @@ int snd_ac97_pcm_open(struct ac97_pcm *pcm, unsigned int rate,
goto error;
}
}
pcm->cur_dbl = r;
spin_unlock_irq(&pcm->bus->bus_lock);
for (i = 3; i < 12; i++) {
if (!(slots & (1 << i)))
......@@ -651,6 +653,21 @@ int snd_ac97_pcm_close(struct ac97_pcm *pcm)
unsigned short slots = pcm->aslots;
int i, cidx;
#ifdef CONFIG_SND_AC97_POWER_SAVE
int r = pcm->cur_dbl;
for (i = 3; i < 12; i++) {
if (!(slots & (1 << i)))
continue;
for (cidx = 0; cidx < 4; cidx++) {
if (pcm->r[r].rslots[cidx] & (1 << i)) {
int reg = get_slot_reg(pcm, cidx, i, r);
snd_ac97_update_power(pcm->r[r].codec[cidx],
reg, 0);
}
}
}
#endif
bus = pcm->bus;
spin_lock_irq(&pcm->bus->bus_lock);
for (i = 3; i < 12; i++) {
......@@ -660,6 +677,7 @@ int snd_ac97_pcm_close(struct ac97_pcm *pcm)
bus->used_slots[pcm->stream][cidx] &= ~(1 << i);
}
pcm->aslots = 0;
pcm->cur_dbl = 0;
spin_unlock_irq(&pcm->bus->bus_lock);
return 0;
}
......
......@@ -2251,6 +2251,16 @@ static int snd_intel8x0_ich_chip_init(struct intel8x0 *chip, int probing)
/* ACLink on, 2 channels */
cnt = igetdword(chip, ICHREG(GLOB_CNT));
cnt &= ~(ICH_ACLINK | ICH_PCM_246_MASK);
#ifdef CONFIG_SND_AC97_POWER_SAVE
/* do cold reset - the full ac97 powerdown may leave the controller
* in a warm state but actually it cannot communicate with the codec.
*/
iputdword(chip, ICHREG(GLOB_CNT), cnt & ~ICH_AC97COLD);
cnt = igetdword(chip, ICHREG(GLOB_CNT));
udelay(10);
iputdword(chip, ICHREG(GLOB_CNT), cnt | ICH_AC97COLD);
msleep(1);
#else
/* finish cold or do warm reset */
cnt |= (cnt & ICH_AC97COLD) == 0 ? ICH_AC97COLD : ICH_AC97WARM;
iputdword(chip, ICHREG(GLOB_CNT), cnt);
......@@ -2265,6 +2275,7 @@ static int snd_intel8x0_ich_chip_init(struct intel8x0 *chip, int probing)
return -EIO;
__ok:
#endif
if (probing) {
/* wait for any codec ready status.
* Once it becomes ready it should remain ready
......@@ -2485,7 +2496,7 @@ static int intel8x0_resume(struct pci_dev *pci)
card->shortname, chip);
chip->irq = pci->irq;
synchronize_irq(chip->irq);
snd_intel8x0_chip_init(chip, 1);
snd_intel8x0_chip_init(chip, 0);
/* re-initialize mixer stuff */
if (chip->device_type == DEVICE_INTEL_ICH4) {
......@@ -2615,6 +2626,7 @@ static void __devinit intel8x0_measure_ac97_clock(struct intel8x0 *chip)
/* not 48000Hz, tuning the clock.. */
chip->ac97_bus->clock = (chip->ac97_bus->clock * 48000) / pos;
printk(KERN_INFO "intel8x0: clocking to %d\n", chip->ac97_bus->clock);
snd_ac97_update_power(chip->ac97[0], AC97_PCM_FRONT_DAC_RATE, 0);
}
#ifdef CONFIG_PROC_FS
......
......@@ -1277,7 +1277,18 @@ static int snd_via82xx_pcm_close(struct snd_pcm_substream *substream)
if (! ratep->used)
ratep->rate = 0;
spin_unlock_irq(&ratep->lock);
if (! ratep->rate) {
if (! viadev->direction) {
snd_ac97_update_power(chip->ac97,
AC97_PCM_FRONT_DAC_RATE, 0);
snd_ac97_update_power(chip->ac97,
AC97_PCM_SURR_DAC_RATE, 0);
snd_ac97_update_power(chip->ac97,
AC97_PCM_LFE_DAC_RATE, 0);
} else
snd_ac97_update_power(chip->ac97,
AC97_PCM_LR_ADC_RATE, 0);
}
viadev->substream = NULL;
return 0;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment