summaryrefslogtreecommitdiffstats
path: root/sound/pci/ac97/ac97_codec.c
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2006-06-27 18:28:53 +0200
committerJaroslav Kysela <perex@suse.cz>2006-09-23 10:37:08 +0200
commit6dbe662874ba08585eaf732d126762c25ac8e3f7 (patch)
tree7460c36d4d848f223b682f7a700866bcf6dbc7d5 /sound/pci/ac97/ac97_codec.c
parent[ALSA] Deprecate snd_card_free_in_thread() (diff)
downloadlinux-6dbe662874ba08585eaf732d126762c25ac8e3f7.tar.xz
linux-6dbe662874ba08585eaf732d126762c25ac8e3f7.zip
[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: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
Diffstat (limited to 'sound/pci/ac97/ac97_codec.c')
-rw-r--r--sound/pci/ac97/ac97_codec.c264
1 files changed, 227 insertions, 37 deletions
diff --git a/sound/pci/ac97/ac97_codec.c b/sound/pci/ac97/ac97_codec.c
index b35280ca2465..f82c636e99a9 100644
--- a/sound/pci/ac97/ac97_codec.c
+++ b/sound/pci/ac97/ac97_codec.c
@@ -47,6 +47,11 @@ static int enable_loopback;
module_param(enable_loopback, bool, 0444);
MODULE_PARM_DESC(enable_loopback, "Enable AC97 ADC/DAC Loopback Control");
+#ifdef CONFIG_SND_AC97_POWER_SAVE
+static int power_save;
+module_param(power_save, bool, 0644);
+MODULE_PARM_DESC(power_save, "Enable AC97 power-saving control");
+#endif
/*
*/
@@ -187,6 +192,8 @@ static const struct ac97_codec_id snd_ac97_codec_ids[] = {
};
+static void update_power_regs(struct snd_ac97 *ac97);
+
/*
* I/O routines
*/
@@ -554,6 +561,18 @@ int snd_ac97_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value
}
err = snd_ac97_update_bits(ac97, reg, val_mask, val);
snd_ac97_page_restore(ac97, page_save);
+#ifdef CONFIG_SND_AC97_POWER_SAVE
+ /* check analog mixer power-down */
+ if ((val_mask & 0x8000) &&
+ (kcontrol->private_value & (1<<30))) {
+ if (val & 0x8000)
+ ac97->power_up &= ~(1 << (reg>>1));
+ else
+ ac97->power_up |= 1 << (reg>>1);
+ if (power_save)
+ update_power_regs(ac97);
+ }
+#endif
return err;
}
@@ -962,6 +981,10 @@ static int snd_ac97_bus_dev_free(struct snd_device *device)
static int snd_ac97_free(struct snd_ac97 *ac97)
{
if (ac97) {
+#ifdef CONFIG_SND_AC97_POWER_SAVE
+ if (ac97->power_workq)
+ destroy_workqueue(ac97->power_workq);
+#endif
snd_ac97_proc_done(ac97);
if (ac97->bus)
ac97->bus->codec[ac97->num] = NULL;
@@ -1117,7 +1140,9 @@ struct snd_kcontrol *snd_ac97_cnew(const struct snd_kcontrol_new *_template, str
/*
* create mute switch(es) for normal stereo controls
*/
-static int snd_ac97_cmute_new_stereo(struct snd_card *card, char *name, int reg, int check_stereo, struct snd_ac97 *ac97)
+static int snd_ac97_cmute_new_stereo(struct snd_card *card, char *name, int reg,
+ int check_stereo, int check_amix,
+ struct snd_ac97 *ac97)
{
struct snd_kcontrol *kctl;
int err;
@@ -1137,10 +1162,14 @@ static int snd_ac97_cmute_new_stereo(struct snd_card *card, char *name, int reg,
}
if (mute_mask == 0x8080) {
struct snd_kcontrol_new tmp = AC97_DOUBLE(name, reg, 15, 7, 1, 1);
+ if (check_amix)
+ tmp.private_value |= (1 << 30);
tmp.index = ac97->num;
kctl = snd_ctl_new1(&tmp, ac97);
} else {
struct snd_kcontrol_new tmp = AC97_SINGLE(name, reg, 15, 1, 1);
+ if (check_amix)
+ tmp.private_value |= (1 << 30);
tmp.index = ac97->num;
kctl = snd_ctl_new1(&tmp, ac97);
}
@@ -1186,7 +1215,9 @@ static int snd_ac97_cvol_new(struct snd_card *card, char *name, int reg, unsigne
/*
* create a mute-switch and a volume for normal stereo/mono controls
*/
-static int snd_ac97_cmix_new_stereo(struct snd_card *card, const char *pfx, int reg, int check_stereo, struct snd_ac97 *ac97)
+static int snd_ac97_cmix_new_stereo(struct snd_card *card, const char *pfx,
+ int reg, int check_stereo, int check_amix,
+ struct snd_ac97 *ac97)
{
int err;
char name[44];
@@ -1197,7 +1228,9 @@ static int snd_ac97_cmix_new_stereo(struct snd_card *card, const char *pfx, int
if (snd_ac97_try_bit(ac97, reg, 15)) {
sprintf(name, "%s Switch", pfx);
- if ((err = snd_ac97_cmute_new_stereo(card, name, reg, check_stereo, ac97)) < 0)
+ if ((err = snd_ac97_cmute_new_stereo(card, name, reg,
+ check_stereo, check_amix,
+ ac97)) < 0)
return err;
}
check_volume_resolution(ac97, reg, &lo_max, &hi_max);
@@ -1209,8 +1242,10 @@ static int snd_ac97_cmix_new_stereo(struct snd_card *card, const char *pfx, int
return 0;
}
-#define snd_ac97_cmix_new(card, pfx, reg, ac97) snd_ac97_cmix_new_stereo(card, pfx, reg, 0, ac97)
-#define snd_ac97_cmute_new(card, name, reg, ac97) snd_ac97_cmute_new_stereo(card, name, reg, 0, ac97)
+#define snd_ac97_cmix_new(card, pfx, reg, acheck, ac97) \
+ snd_ac97_cmix_new_stereo(card, pfx, reg, 0, acheck, ac97)
+#define snd_ac97_cmute_new(card, name, reg, acheck, ac97) \
+ snd_ac97_cmute_new_stereo(card, name, reg, 0, acheck, ac97)
static unsigned int snd_ac97_determine_spdif_rates(struct snd_ac97 *ac97);
@@ -1226,9 +1261,11 @@ static int snd_ac97_mixer_build(struct snd_ac97 * ac97)
/* AD claims to remove this control from AD1887, although spec v2.2 does not allow this */
if (snd_ac97_try_volume_mix(ac97, AC97_MASTER)) {
if (ac97->flags & AC97_HAS_NO_MASTER_VOL)
- err = snd_ac97_cmute_new(card, "Master Playback Switch", AC97_MASTER, ac97);
+ err = snd_ac97_cmute_new(card, "Master Playback Switch",
+ AC97_MASTER, 0, ac97);
else
- err = snd_ac97_cmix_new(card, "Master Playback", AC97_MASTER, ac97);
+ err = snd_ac97_cmix_new(card, "Master Playback",
+ AC97_MASTER, 0, ac97);
if (err < 0)
return err;
}
@@ -1265,19 +1302,23 @@ static int snd_ac97_mixer_build(struct snd_ac97 * ac97)
if ((snd_ac97_try_volume_mix(ac97, AC97_SURROUND_MASTER))
&& !(ac97->flags & AC97_AD_MULTI)) {
/* Surround Master (0x38) is with stereo mutes */
- if ((err = snd_ac97_cmix_new_stereo(card, "Surround Playback", AC97_SURROUND_MASTER, 1, ac97)) < 0)
+ if ((err = snd_ac97_cmix_new_stereo(card, "Surround Playback",
+ AC97_SURROUND_MASTER, 1, 0,
+ ac97)) < 0)
return err;
}
/* build headphone controls */
if (snd_ac97_try_volume_mix(ac97, AC97_HEADPHONE)) {
- if ((err = snd_ac97_cmix_new(card, "Headphone Playback", AC97_HEADPHONE, ac97)) < 0)
+ if ((err = snd_ac97_cmix_new(card, "Headphone Playback",
+ AC97_HEADPHONE, 0, ac97)) < 0)
return err;
}
/* build master mono controls */
if (snd_ac97_try_volume_mix(ac97, AC97_MASTER_MONO)) {
- if ((err = snd_ac97_cmix_new(card, "Master Mono Playback", AC97_MASTER_MONO, ac97)) < 0)
+ if ((err = snd_ac97_cmix_new(card, "Master Mono Playback",
+ AC97_MASTER_MONO, 0, ac97)) < 0)
return err;
}
@@ -1310,7 +1351,8 @@ static int snd_ac97_mixer_build(struct snd_ac97 * ac97)
/* build Phone controls */
if (!(ac97->flags & AC97_HAS_NO_PHONE)) {
if (snd_ac97_try_volume_mix(ac97, AC97_PHONE)) {
- if ((err = snd_ac97_cmix_new(card, "Phone Playback", AC97_PHONE, ac97)) < 0)
+ if ((err = snd_ac97_cmix_new(card, "Phone Playback",
+ AC97_PHONE, 1, ac97)) < 0)
return err;
}
}
@@ -1318,7 +1360,8 @@ static int snd_ac97_mixer_build(struct snd_ac97 * ac97)
/* build MIC controls */
if (!(ac97->flags & AC97_HAS_NO_MIC)) {
if (snd_ac97_try_volume_mix(ac97, AC97_MIC)) {
- if ((err = snd_ac97_cmix_new(card, "Mic Playback", AC97_MIC, ac97)) < 0)
+ if ((err = snd_ac97_cmix_new(card, "Mic Playback",
+ AC97_MIC, 1, ac97)) < 0)
return err;
if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_mic_boost, ac97))) < 0)
return err;
@@ -1327,14 +1370,16 @@ static int snd_ac97_mixer_build(struct snd_ac97 * ac97)
/* build Line controls */
if (snd_ac97_try_volume_mix(ac97, AC97_LINE)) {
- if ((err = snd_ac97_cmix_new(card, "Line Playback", AC97_LINE, ac97)) < 0)
+ if ((err = snd_ac97_cmix_new(card, "Line Playback",
+ AC97_LINE, 1, ac97)) < 0)
return err;
}
/* build CD controls */
if (!(ac97->flags & AC97_HAS_NO_CD)) {
if (snd_ac97_try_volume_mix(ac97, AC97_CD)) {
- if ((err = snd_ac97_cmix_new(card, "CD Playback", AC97_CD, ac97)) < 0)
+ if ((err = snd_ac97_cmix_new(card, "CD Playback",
+ AC97_CD, 1, ac97)) < 0)
return err;
}
}
@@ -1342,7 +1387,8 @@ static int snd_ac97_mixer_build(struct snd_ac97 * ac97)
/* build Video controls */
if (!(ac97->flags & AC97_HAS_NO_VIDEO)) {
if (snd_ac97_try_volume_mix(ac97, AC97_VIDEO)) {
- if ((err = snd_ac97_cmix_new(card, "Video Playback", AC97_VIDEO, ac97)) < 0)
+ if ((err = snd_ac97_cmix_new(card, "Video Playback",
+ AC97_VIDEO, 1, ac97)) < 0)
return err;
}
}
@@ -1350,7 +1396,8 @@ static int snd_ac97_mixer_build(struct snd_ac97 * ac97)
/* build Aux controls */
if (!(ac97->flags & AC97_HAS_NO_AUX)) {
if (snd_ac97_try_volume_mix(ac97, AC97_AUX)) {
- if ((err = snd_ac97_cmix_new(card, "Aux Playback", AC97_AUX, ac97)) < 0)
+ if ((err = snd_ac97_cmix_new(card, "Aux Playback",
+ AC97_AUX, 1, ac97)) < 0)
return err;
}
}
@@ -1385,9 +1432,12 @@ static int snd_ac97_mixer_build(struct snd_ac97 * ac97)
} else {
if (!(ac97->flags & AC97_HAS_NO_STD_PCM)) {
if (ac97->flags & AC97_HAS_NO_PCM_VOL)
- err = snd_ac97_cmute_new(card, "PCM Playback Switch", AC97_PCM, ac97);
+ err = snd_ac97_cmute_new(card,
+ "PCM Playback Switch",
+ AC97_PCM, 0, ac97);
else
- err = snd_ac97_cmix_new(card, "PCM Playback", AC97_PCM, ac97);
+ err = snd_ac97_cmix_new(card, "PCM Playback",
+ AC97_PCM, 0, ac97);
if (err < 0)
return err;
}
@@ -1398,7 +1448,9 @@ static int snd_ac97_mixer_build(struct snd_ac97 * ac97)
if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_control_capture_src, ac97))) < 0)
return err;
if (snd_ac97_try_bit(ac97, AC97_REC_GAIN, 15)) {
- if ((err = snd_ac97_cmute_new(card, "Capture Switch", AC97_REC_GAIN, ac97)) < 0)
+ err = snd_ac97_cmute_new(card, "Capture Switch",
+ AC97_REC_GAIN, 0, ac97);
+ if (err < 0)
return err;
}
if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_control_capture_vol, ac97))) < 0)
@@ -1829,6 +1881,13 @@ static int snd_ac97_dev_disconnect(struct snd_device *device)
/* build_ops to do nothing */
static struct snd_ac97_build_ops null_build_ops;
+#ifdef CONFIG_SND_AC97_POWER_SAVE
+static void do_update_power(void *data)
+{
+ update_power_regs(data);
+}
+#endif
+
/**
* snd_ac97_mixer - create an Codec97 component
* @bus: the AC97 bus which codec is attached to
@@ -1883,6 +1942,10 @@ int snd_ac97_mixer(struct snd_ac97_bus *bus, struct snd_ac97_template *template,
bus->codec[ac97->num] = ac97;
mutex_init(&ac97->reg_mutex);
mutex_init(&ac97->page_mutex);
+#ifdef CONFIG_SND_AC97_POWER_SAVE
+ ac97->power_workq = create_workqueue("ac97");
+ INIT_WORK(&ac97->power_work, do_update_power, ac97);
+#endif
#ifdef CONFIG_PCI
if (ac97->pci) {
@@ -2117,15 +2180,8 @@ int snd_ac97_mixer(struct snd_ac97_bus *bus, struct snd_ac97_template *template,
return -ENOMEM;
}
}
- /* make sure the proper powerdown bits are cleared */
- if (ac97->scaps && ac97_is_audio(ac97)) {
- 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_STATUS, reg);
- }
+ if (ac97_is_audio(ac97))
+ update_power_regs(ac97);
snd_ac97_proc_init(ac97);
if ((err = snd_device_new(card, SNDRV_DEV_CODEC, ac97, &ops)) < 0) {
snd_ac97_free(ac97);
@@ -2153,22 +2209,155 @@ static void snd_ac97_powerdown(struct snd_ac97 *ac97)
snd_ac97_write(ac97, AC97_HEADPHONE, 0x9f9f);
}
- power = ac97->regs[AC97_POWERDOWN] | 0x8000; /* EAPD */
- power |= 0x4000; /* Headphone amplifier powerdown */
- power |= 0x0300; /* ADC & DAC powerdown */
+ /* surround, CLFE, mic powerdown */
+ power = ac97->regs[AC97_EXTENDED_STATUS];
+ if (ac97->scaps & AC97_SCAP_SURROUND_DAC)
+ power |= AC97_EA_PRJ;
+ if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC)
+ power |= AC97_EA_PRI | AC97_EA_PRK;
+ power |= AC97_EA_PRL;
+ snd_ac97_write(ac97, AC97_EXTENDED_STATUS, power);
+
+ /* powerdown external amplifier */
+ if (ac97->scaps & AC97_SCAP_INV_EAPD)
+ power = ac97->regs[AC97_POWERDOWN] & ~AC97_PD_EAPD;
+ else if (! (ac97->scaps & AC97_SCAP_EAPD_LED))
+ power = ac97->regs[AC97_POWERDOWN] | AC97_PD_EAPD;
+ power |= AC97_PD_PR6; /* Headphone amplifier powerdown */
+ power |= AC97_PD_PR0 | AC97_PD_PR1; /* ADC & DAC powerdown */
snd_ac97_write(ac97, AC97_POWERDOWN, power);
udelay(100);
- 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 */
+ power |= AC97_PD_PR2 | AC97_PD_PR3; /* Analog Mixer powerdown */
snd_ac97_write(ac97, AC97_POWERDOWN, power);
+#ifdef CONFIG_SND_AC97_POWER_SAVE
+ if (power_save) {
+ udelay(100);
+ /* AC-link powerdown, internal Clk disable */
+ /* FIXME: this may cause click noises on some boards */
+ power |= AC97_PD_PR4 | AC97_PD_PR5;
+ snd_ac97_write(ac97, AC97_POWERDOWN, power);
+ }
#endif
}
+struct ac97_power_reg {
+ unsigned short reg;
+ unsigned short power_reg;
+ unsigned short mask;
+};
+
+enum { PWIDX_ADC, PWIDX_FRONT, PWIDX_CLFE, PWIDX_SURR, PWIDX_MIC, PWIDX_SIZE };
+
+static struct ac97_power_reg power_regs[PWIDX_SIZE] = {
+ [PWIDX_ADC] = { AC97_PCM_LR_ADC_RATE, AC97_POWERDOWN, AC97_PD_PR0},
+ [PWIDX_FRONT] = { AC97_PCM_FRONT_DAC_RATE, AC97_POWERDOWN, AC97_PD_PR1},
+ [PWIDX_CLFE] = { AC97_PCM_LFE_DAC_RATE, AC97_EXTENDED_STATUS,
+ AC97_EA_PRI | AC97_EA_PRK},
+ [PWIDX_SURR] = { AC97_PCM_SURR_DAC_RATE, AC97_EXTENDED_STATUS,
+ AC97_EA_PRJ},
+ [PWIDX_MIC] = { AC97_PCM_MIC_ADC_RATE, AC97_EXTENDED_STATUS,
+ AC97_EA_PRL},
+};
+
+#ifdef CONFIG_SND_AC97_POWER_SAVE
+/**
+ * snd_ac97_update_power - update the powerdown register
+ * @ac97: the codec instance
+ * @reg: the rate register, e.g. AC97_PCM_FRONT_DAC_RATE
+ * @powerup: non-zero when power up the part
+ *
+ * Update the AC97 powerdown register bits of the given part.
+ */
+int snd_ac97_update_power(struct snd_ac97 *ac97, int reg, int powerup)
+{
+ int i;
+
+ if (! ac97)
+ return 0;
+
+ if (reg) {
+ /* SPDIF requires DAC power, too */
+ if (reg == AC97_SPDIF)
+ reg = AC97_PCM_FRONT_DAC_RATE;
+ for (i = 0; i < PWIDX_SIZE; i++) {
+ if (power_regs[i].reg == reg) {
+ if (powerup)
+ ac97->power_up |= (1 << i);
+ else
+ ac97->power_up &= ~(1 << i);
+ break;
+ }
+ }
+ }
+
+ if (! power_save)
+ return 0;
+
+ if (! powerup && ac97->power_workq)
+ /* adjust power-down bits after two seconds delay
+ * (for avoiding loud click noises for many (OSS) apps
+ * that open/close frequently)
+ */
+ queue_delayed_work(ac97->power_workq, &ac97->power_work, HZ*2);
+ else
+ update_power_regs(ac97);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(snd_ac97_update_power);
+#endif /* CONFIG_SND_AC97_POWER_SAVE */
+
+static void update_power_regs(struct snd_ac97 *ac97)
+{
+ unsigned int power_up, bits;
+ int i;
+
+#ifdef CONFIG_SND_AC97_POWER_SAVE
+ if (power_save)
+ power_up = ac97->power_up;
+ else {
+#endif
+ power_up = (1 << PWIDX_FRONT) | (1 << PWIDX_ADC);
+ power_up |= (1 << PWIDX_MIC);
+ if (ac97->scaps & AC97_SCAP_SURROUND_DAC)
+ power_up |= (1 << PWIDX_SURR);
+ if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC)
+ power_up |= (1 << PWIDX_CLFE);
+#ifdef CONFIG_SND_AC97_POWER_SAVE
+ }
+#endif
+ if (power_up) {
+ if (ac97->regs[AC97_POWERDOWN] & AC97_PD_PR2) {
+ /* needs power-up analog mix and vref */
+ snd_ac97_update_bits(ac97, AC97_POWERDOWN,
+ AC97_PD_PR3, 0);
+ msleep(1);
+ snd_ac97_update_bits(ac97, AC97_POWERDOWN,
+ AC97_PD_PR2, 0);
+ }
+ }
+ for (i = 0; i < PWIDX_SIZE; i++) {
+ if (power_up & (1 << i))
+ bits = 0;
+ else
+ bits = power_regs[i].mask;
+ snd_ac97_update_bits(ac97, power_regs[i].power_reg,
+ power_regs[i].mask, bits);
+ }
+ if (! power_up) {
+ if (! (ac97->regs[AC97_POWERDOWN] & AC97_PD_PR2)) {
+ /* power down analog mix and vref */
+ snd_ac97_update_bits(ac97, AC97_POWERDOWN,
+ AC97_PD_PR2, AC97_PD_PR2);
+ snd_ac97_update_bits(ac97, AC97_POWERDOWN,
+ AC97_PD_PR3, AC97_PD_PR3);
+ }
+ }
+}
+
+
#ifdef CONFIG_PM
/**
* snd_ac97_suspend - General suspend function for AC97 codec
@@ -2484,6 +2673,7 @@ static int tune_mute_led(struct snd_ac97 *ac97)
msw->put = master_mute_sw_put;
snd_ac97_remove_ctl(ac97, "External Amplifier", NULL);
snd_ac97_update_bits(ac97, AC97_POWERDOWN, 0x8000, 0x8000); /* mute LED on */
+ ac97->scaps |= AC97_SCAP_EAPD_LED;
return 0;
}