提交 cb53c626 编写于 作者: T Takashi Iwai 提交者: Jaroslav Kysela

[ALSA] hda-intel - Add POWER_SAVE option

Added CONFIG_SND_HDA_POWER_SAVE kconfig.  It's an experimental option
to achieve an aggressive power-saving.  With this option, the driver
will turn on/off the power of each codec and controller chip dynamically
on demand.
The patch introduces a new module option 'power_save'.  It specifies
the second of time-out for automatic power-down.  As default, it's
10 seconds.  Setting 0 means to suppress the power-saving feature.
The codec may have analog-input loopbacks, which are usually represented
by mixer elements such as 'Mic Playback Switch' or 'CD Playback Switch'.
When these are on, we cannot turn off the mixer and the codec chip has
to be kept on.  For bookkeeping these states, a new codec-callback is
introduced.
For the bus-controller side, a new callback pm_notify is introduced,
which can be used to turn on/off the contoller appropriately.
Note that this power-saving might cause slight click-noise at
power-on/off.  Also, it might take some time to wake up the codec, and
might even drop some tones at the very beginning.  This seems to be the
side-effect of turning off the controller chip.
This turn-off of the controller can be disabled by undefining
HDA_POWER_SAVE_RESET_CONTOLLER in hda_intel.c.
Signed-off-by: NTakashi Iwai <tiwai@suse.de>
Signed-off-by: NJaroslav Kysela <perex@suse.cz>
上级 cca3b371
...@@ -581,6 +581,14 @@ config SND_HDA_GENERIC ...@@ -581,6 +581,14 @@ config SND_HDA_GENERIC
Say Y here to enable the generic HD-audio codec parser Say Y here to enable the generic HD-audio codec parser
in snd-hda-intel driver. in snd-hda-intel driver.
config SND_HDA_POWER_SAVE
bool "Aggressive power-saving on HD-audio"
depends on SND_HDA_INTEL && EXPERIMENTAL
help
Say Y here to enable more aggressive power-saving mode on
HD-audio driver. The power-saving timeout can be configured
via power_save option or over sysfs on-the-fly.
config SND_HDSP config SND_HDSP
tristate "RME Hammerfall DSP Audio" tristate "RME Hammerfall DSP Audio"
depends on SND depends on SND
......
...@@ -33,6 +33,13 @@ ...@@ -33,6 +33,13 @@
#include "hda_local.h" #include "hda_local.h"
#include <sound/hda_hwdep.h> #include <sound/hda_hwdep.h>
#ifdef CONFIG_SND_HDA_POWER_SAVE
/* define this option here to hide as static */
static int power_save = 10;
module_param(power_save, int, 0644);
MODULE_PARM_DESC(power_save, "Automatic power-saving timeout "
"(in second, 0 = disable).");
#endif
/* /*
* vendor / preset table * vendor / preset table
...@@ -60,6 +67,13 @@ static struct hda_vendor_id hda_vendor_ids[] = { ...@@ -60,6 +67,13 @@ static struct hda_vendor_id hda_vendor_ids[] = {
#include "hda_patch.h" #include "hda_patch.h"
#ifdef CONFIG_SND_HDA_POWER_SAVE
static void hda_power_work(struct work_struct *work);
static void hda_keep_power_on(struct hda_codec *codec);
#else
static inline void hda_keep_power_on(struct hda_codec *codec) {}
#endif
/** /**
* snd_hda_codec_read - send a command and get the response * snd_hda_codec_read - send a command and get the response
* @codec: the HDA codec * @codec: the HDA codec
...@@ -77,12 +91,14 @@ unsigned int snd_hda_codec_read(struct hda_codec *codec, hda_nid_t nid, ...@@ -77,12 +91,14 @@ unsigned int snd_hda_codec_read(struct hda_codec *codec, hda_nid_t nid,
unsigned int verb, unsigned int parm) unsigned int verb, unsigned int parm)
{ {
unsigned int res; unsigned int res;
snd_hda_power_up(codec);
mutex_lock(&codec->bus->cmd_mutex); mutex_lock(&codec->bus->cmd_mutex);
if (!codec->bus->ops.command(codec, nid, direct, verb, parm)) if (!codec->bus->ops.command(codec, nid, direct, verb, parm))
res = codec->bus->ops.get_response(codec); res = codec->bus->ops.get_response(codec);
else else
res = (unsigned int)-1; res = (unsigned int)-1;
mutex_unlock(&codec->bus->cmd_mutex); mutex_unlock(&codec->bus->cmd_mutex);
snd_hda_power_down(codec);
return res; return res;
} }
...@@ -102,9 +118,11 @@ int snd_hda_codec_write(struct hda_codec *codec, hda_nid_t nid, int direct, ...@@ -102,9 +118,11 @@ int snd_hda_codec_write(struct hda_codec *codec, hda_nid_t nid, int direct,
unsigned int verb, unsigned int parm) unsigned int verb, unsigned int parm)
{ {
int err; int err;
snd_hda_power_up(codec);
mutex_lock(&codec->bus->cmd_mutex); mutex_lock(&codec->bus->cmd_mutex);
err = codec->bus->ops.command(codec, nid, direct, verb, parm); err = codec->bus->ops.command(codec, nid, direct, verb, parm);
mutex_unlock(&codec->bus->cmd_mutex); mutex_unlock(&codec->bus->cmd_mutex);
snd_hda_power_down(codec);
return err; return err;
} }
...@@ -505,6 +523,9 @@ static void snd_hda_codec_free(struct hda_codec *codec) ...@@ -505,6 +523,9 @@ static void snd_hda_codec_free(struct hda_codec *codec)
{ {
if (!codec) if (!codec)
return; return;
#ifdef CONFIG_SND_HDA_POWER_SAVE
cancel_delayed_work(&codec->power_work);
#endif
list_del(&codec->list); list_del(&codec->list);
codec->bus->caddr_tbl[codec->addr] = NULL; codec->bus->caddr_tbl[codec->addr] = NULL;
if (codec->patch_ops.free) if (codec->patch_ops.free)
...@@ -551,6 +572,15 @@ int __devinit snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr, ...@@ -551,6 +572,15 @@ int __devinit snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr,
init_hda_cache(&codec->amp_cache, sizeof(struct hda_amp_info)); init_hda_cache(&codec->amp_cache, sizeof(struct hda_amp_info));
init_hda_cache(&codec->cmd_cache, sizeof(struct hda_cache_head)); init_hda_cache(&codec->cmd_cache, sizeof(struct hda_cache_head));
#ifdef CONFIG_SND_HDA_POWER_SAVE
INIT_DELAYED_WORK(&codec->power_work, hda_power_work);
/* snd_hda_codec_new() marks the codec as power-up, and leave it as is.
* the caller has to power down appropriatley after initialization
* phase.
*/
hda_keep_power_on(codec);
#endif
list_add_tail(&codec->list, &bus->codec_list); list_add_tail(&codec->list, &bus->codec_list);
bus->caddr_tbl[codec_addr] = codec; bus->caddr_tbl[codec_addr] = codec;
...@@ -855,7 +885,7 @@ int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid, ...@@ -855,7 +885,7 @@ int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid,
return ret; return ret;
} }
#ifdef CONFIG_PM #ifdef SND_HDA_NEEDS_RESUME
/* resume the all amp commands from the cache */ /* resume the all amp commands from the cache */
void snd_hda_codec_resume_amp(struct hda_codec *codec) void snd_hda_codec_resume_amp(struct hda_codec *codec)
{ {
...@@ -879,7 +909,7 @@ void snd_hda_codec_resume_amp(struct hda_codec *codec) ...@@ -879,7 +909,7 @@ void snd_hda_codec_resume_amp(struct hda_codec *codec)
} }
} }
} }
#endif /* CONFIG_PM */ #endif /* SND_HDA_NEEDS_RESUME */
/* /*
* AMP control callbacks * AMP control callbacks
...@@ -945,6 +975,7 @@ int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol, ...@@ -945,6 +975,7 @@ int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol,
long *valp = ucontrol->value.integer.value; long *valp = ucontrol->value.integer.value;
int change = 0; int change = 0;
snd_hda_power_up(codec);
if (chs & 1) { if (chs & 1) {
change = snd_hda_codec_amp_update(codec, nid, 0, dir, idx, change = snd_hda_codec_amp_update(codec, nid, 0, dir, idx,
0x7f, *valp); 0x7f, *valp);
...@@ -953,6 +984,7 @@ int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol, ...@@ -953,6 +984,7 @@ int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol,
if (chs & 2) if (chs & 2)
change |= snd_hda_codec_amp_update(codec, nid, 1, dir, idx, change |= snd_hda_codec_amp_update(codec, nid, 1, dir, idx,
0x7f, *valp); 0x7f, *valp);
snd_hda_power_down(codec);
return change; return change;
} }
...@@ -1025,6 +1057,7 @@ int snd_hda_mixer_amp_switch_put(struct snd_kcontrol *kcontrol, ...@@ -1025,6 +1057,7 @@ int snd_hda_mixer_amp_switch_put(struct snd_kcontrol *kcontrol,
long *valp = ucontrol->value.integer.value; long *valp = ucontrol->value.integer.value;
int change = 0; int change = 0;
snd_hda_power_up(codec);
if (chs & 1) { if (chs & 1) {
change = snd_hda_codec_amp_update(codec, nid, 0, dir, idx, change = snd_hda_codec_amp_update(codec, nid, 0, dir, idx,
HDA_AMP_MUTE, HDA_AMP_MUTE,
...@@ -1035,7 +1068,11 @@ int snd_hda_mixer_amp_switch_put(struct snd_kcontrol *kcontrol, ...@@ -1035,7 +1068,11 @@ int snd_hda_mixer_amp_switch_put(struct snd_kcontrol *kcontrol,
change |= snd_hda_codec_amp_update(codec, nid, 1, dir, idx, change |= snd_hda_codec_amp_update(codec, nid, 1, dir, idx,
HDA_AMP_MUTE, HDA_AMP_MUTE,
*valp ? 0 : HDA_AMP_MUTE); *valp ? 0 : HDA_AMP_MUTE);
#ifdef CONFIG_SND_HDA_POWER_SAVE
if (codec->patch_ops.check_power_status)
codec->patch_ops.check_power_status(codec, nid);
#endif
snd_hda_power_down(codec);
return change; return change;
} }
...@@ -1502,7 +1539,7 @@ int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid) ...@@ -1502,7 +1539,7 @@ int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid)
return 0; return 0;
} }
#ifdef CONFIG_PM #ifdef SND_HDA_NEEDS_RESUME
/* /*
* command cache * command cache
*/ */
...@@ -1528,6 +1565,7 @@ int snd_hda_codec_write_cache(struct hda_codec *codec, hda_nid_t nid, ...@@ -1528,6 +1565,7 @@ int snd_hda_codec_write_cache(struct hda_codec *codec, hda_nid_t nid,
int direct, unsigned int verb, unsigned int parm) int direct, unsigned int verb, unsigned int parm)
{ {
int err; int err;
snd_hda_power_up(codec);
mutex_lock(&codec->bus->cmd_mutex); mutex_lock(&codec->bus->cmd_mutex);
err = codec->bus->ops.command(codec, nid, direct, verb, parm); err = codec->bus->ops.command(codec, nid, direct, verb, parm);
if (!err) { if (!err) {
...@@ -1538,6 +1576,7 @@ int snd_hda_codec_write_cache(struct hda_codec *codec, hda_nid_t nid, ...@@ -1538,6 +1576,7 @@ int snd_hda_codec_write_cache(struct hda_codec *codec, hda_nid_t nid,
c->val = parm; c->val = parm;
} }
mutex_unlock(&codec->bus->cmd_mutex); mutex_unlock(&codec->bus->cmd_mutex);
snd_hda_power_down(codec);
return err; return err;
} }
...@@ -1572,7 +1611,7 @@ void snd_hda_sequence_write_cache(struct hda_codec *codec, ...@@ -1572,7 +1611,7 @@ void snd_hda_sequence_write_cache(struct hda_codec *codec,
snd_hda_codec_write_cache(codec, seq->nid, 0, seq->verb, snd_hda_codec_write_cache(codec, seq->nid, 0, seq->verb,
seq->param); seq->param);
} }
#endif /* CONFIG_PM */ #endif /* SND_HDA_NEEDS_RESUME */
/* /*
* set power state of the codec * set power state of the codec
...@@ -1580,24 +1619,70 @@ void snd_hda_sequence_write_cache(struct hda_codec *codec, ...@@ -1580,24 +1619,70 @@ void snd_hda_sequence_write_cache(struct hda_codec *codec,
static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg, static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg,
unsigned int power_state) unsigned int power_state)
{ {
hda_nid_t nid, nid_start; hda_nid_t nid;
int nodes; int i;
snd_hda_codec_write(codec, fg, 0, AC_VERB_SET_POWER_STATE, snd_hda_codec_write(codec, fg, 0, AC_VERB_SET_POWER_STATE,
power_state); power_state);
nodes = snd_hda_get_sub_nodes(codec, fg, &nid_start); nid = codec->start_nid;
for (nid = nid_start; nid < nodes + nid_start; nid++) { for (i = 0; i < codec->num_nodes; i++, nid++) {
if (get_wcaps(codec, nid) & AC_WCAP_POWER) if (get_wcaps(codec, nid) & AC_WCAP_POWER)
snd_hda_codec_write(codec, nid, 0, snd_hda_codec_write(codec, nid, 0,
AC_VERB_SET_POWER_STATE, AC_VERB_SET_POWER_STATE,
power_state); power_state);
} }
if (power_state == AC_PWRST_D0) if (power_state == AC_PWRST_D0) {
unsigned long end_time;
int state;
msleep(10); msleep(10);
/* wait until the codec reachs to D0 */
end_time = jiffies + msecs_to_jiffies(500);
do {
state = snd_hda_codec_read(codec, fg, 0,
AC_VERB_GET_POWER_STATE, 0);
if (state == power_state)
break;
msleep(1);
} while (time_after_eq(end_time, jiffies));
}
}
#ifdef SND_HDA_NEEDS_RESUME
/*
* call suspend and power-down; used both from PM and power-save
*/
static void hda_call_codec_suspend(struct hda_codec *codec)
{
if (codec->patch_ops.suspend)
codec->patch_ops.suspend(codec, PMSG_SUSPEND);
hda_set_power_state(codec,
codec->afg ? codec->afg : codec->mfg,
AC_PWRST_D3);
#ifdef CONFIG_SND_HDA_POWER_SAVE
cancel_delayed_work(&codec->power_work);
#endif
} }
/*
* kick up codec; used both from PM and power-save
*/
static void hda_call_codec_resume(struct hda_codec *codec)
{
hda_set_power_state(codec,
codec->afg ? codec->afg : codec->mfg,
AC_PWRST_D0);
if (codec->patch_ops.resume)
codec->patch_ops.resume(codec);
else {
codec->patch_ops.init(codec);
snd_hda_codec_resume_amp(codec);
snd_hda_codec_resume_cache(codec);
}
}
#endif /* SND_HDA_NEEDS_RESUME */
/** /**
* snd_hda_build_controls - build mixer controls * snd_hda_build_controls - build mixer controls
...@@ -1611,28 +1696,24 @@ int __devinit snd_hda_build_controls(struct hda_bus *bus) ...@@ -1611,28 +1696,24 @@ int __devinit snd_hda_build_controls(struct hda_bus *bus)
{ {
struct hda_codec *codec; struct hda_codec *codec;
/* build controls */
list_for_each_entry(codec, &bus->codec_list, list) { list_for_each_entry(codec, &bus->codec_list, list) {
int err; int err = 0;
if (!codec->patch_ops.build_controls) /* fake as if already powered-on */
continue; hda_keep_power_on(codec);
err = codec->patch_ops.build_controls(codec); /* then fire up */
if (err < 0)
return err;
}
/* initialize */
list_for_each_entry(codec, &bus->codec_list, list) {
int err;
hda_set_power_state(codec, hda_set_power_state(codec,
codec->afg ? codec->afg : codec->mfg, codec->afg ? codec->afg : codec->mfg,
AC_PWRST_D0); AC_PWRST_D0);
if (!codec->patch_ops.init) /* continue to initialize... */
continue; if (codec->patch_ops.init)
err = codec->patch_ops.init(codec); err = codec->patch_ops.init(codec);
if (!err && codec->patch_ops.build_controls)
err = codec->patch_ops.build_controls(codec);
snd_hda_power_down(codec);
if (err < 0) if (err < 0)
return err; return err;
} }
return 0; return 0;
} }
...@@ -2078,7 +2159,7 @@ int snd_hda_check_board_config(struct hda_codec *codec, ...@@ -2078,7 +2159,7 @@ int snd_hda_check_board_config(struct hda_codec *codec,
*/ */
int snd_hda_add_new_ctls(struct hda_codec *codec, struct snd_kcontrol_new *knew) int snd_hda_add_new_ctls(struct hda_codec *codec, struct snd_kcontrol_new *knew)
{ {
int err; int err;
for (; knew->name; knew++) { for (; knew->name; knew++) {
struct snd_kcontrol *kctl; struct snd_kcontrol *kctl;
...@@ -2101,6 +2182,89 @@ int snd_hda_add_new_ctls(struct hda_codec *codec, struct snd_kcontrol_new *knew) ...@@ -2101,6 +2182,89 @@ int snd_hda_add_new_ctls(struct hda_codec *codec, struct snd_kcontrol_new *knew)
return 0; return 0;
} }
#ifdef CONFIG_SND_HDA_POWER_SAVE
static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg,
unsigned int power_state);
static void hda_power_work(struct work_struct *work)
{
struct hda_codec *codec =
container_of(work, struct hda_codec, power_work.work);
if (!codec->power_on || codec->power_count)
return;
hda_call_codec_suspend(codec);
codec->power_on = 0;
if (codec->bus->ops.pm_notify)
codec->bus->ops.pm_notify(codec);
}
static void hda_keep_power_on(struct hda_codec *codec)
{
codec->power_count++;
codec->power_on = 1;
}
void snd_hda_power_up(struct hda_codec *codec)
{
codec->power_count++;
if (codec->power_on)
return;
codec->power_on = 1;
if (codec->bus->ops.pm_notify)
codec->bus->ops.pm_notify(codec);
hda_call_codec_resume(codec);
cancel_delayed_work(&codec->power_work);
}
void snd_hda_power_down(struct hda_codec *codec)
{
--codec->power_count;
if (!codec->power_on)
return;
if (power_save)
schedule_delayed_work(&codec->power_work,
msecs_to_jiffies(power_save * 1000));
}
int snd_hda_check_amp_list_power(struct hda_codec *codec,
struct hda_loopback_check *check,
hda_nid_t nid)
{
struct hda_amp_list *p;
int ch, v;
if (!check->amplist)
return 0;
for (p = check->amplist; p->nid; p++) {
if (p->nid == nid)
break;
}
if (!p->nid)
return 0; /* nothing changed */
for (p = check->amplist; p->nid; p++) {
for (ch = 0; ch < 2; ch++) {
v = snd_hda_codec_amp_read(codec, p->nid, ch, p->dir,
p->idx);
if (!(v & HDA_AMP_MUTE) && v > 0) {
if (!check->power_on) {
check->power_on = 1;
snd_hda_power_up(codec);
}
return 1;
}
}
}
if (check->power_on) {
check->power_on = 0;
snd_hda_power_down(codec);
}
return 0;
}
#endif
/* /*
* Channel mode helper * Channel mode helper
...@@ -2605,41 +2769,32 @@ int snd_hda_suspend(struct hda_bus *bus, pm_message_t state) ...@@ -2605,41 +2769,32 @@ int snd_hda_suspend(struct hda_bus *bus, pm_message_t state)
{ {
struct hda_codec *codec; struct hda_codec *codec;
/* FIXME: should handle power widget capabilities */
list_for_each_entry(codec, &bus->codec_list, list) { list_for_each_entry(codec, &bus->codec_list, list) {
if (codec->patch_ops.suspend) hda_call_codec_suspend(codec);
codec->patch_ops.suspend(codec, state);
hda_set_power_state(codec,
codec->afg ? codec->afg : codec->mfg,
AC_PWRST_D3);
} }
return 0; return 0;
} }
#ifndef CONFIG_SND_HDA_POWER_SAVE
/** /**
* snd_hda_resume - resume the codecs * snd_hda_resume - resume the codecs
* @bus: the HDA bus * @bus: the HDA bus
* @state: resume state * @state: resume state
* *
* Returns 0 if successful. * Returns 0 if successful.
*
* This fucntion is defined only when POWER_SAVE isn't set.
* In the power-save mode, the codec is resumed dynamically.
*/ */
int snd_hda_resume(struct hda_bus *bus) int snd_hda_resume(struct hda_bus *bus)
{ {
struct hda_codec *codec; struct hda_codec *codec;
list_for_each_entry(codec, &bus->codec_list, list) { list_for_each_entry(codec, &bus->codec_list, list) {
hda_set_power_state(codec, hda_call_codec_resume(codec);
codec->afg ? codec->afg : codec->mfg,
AC_PWRST_D0);
if (codec->patch_ops.resume)
codec->patch_ops.resume(codec);
else {
codec->patch_ops.init(codec);
snd_hda_codec_resume_amp(codec);
snd_hda_codec_resume_cache(codec);
}
} }
return 0; return 0;
} }
#endif /* !CONFIG_SND_HDA_POWER_SAVE */
#endif #endif
...@@ -26,6 +26,10 @@ ...@@ -26,6 +26,10 @@
#include <sound/pcm.h> #include <sound/pcm.h>
#include <sound/hwdep.h> #include <sound/hwdep.h>
#if defined(CONFIG_PM) || defined(CONFIG_SND_HDA_POWER_SAVE)
#define SND_HDA_NEEDS_RESUME /* resume control code is required */
#endif
/* /*
* nodes * nodes
*/ */
...@@ -412,6 +416,10 @@ struct hda_bus_ops { ...@@ -412,6 +416,10 @@ struct hda_bus_ops {
unsigned int (*get_response)(struct hda_codec *codec); unsigned int (*get_response)(struct hda_codec *codec);
/* free the private data */ /* free the private data */
void (*private_free)(struct hda_bus *); void (*private_free)(struct hda_bus *);
#ifdef CONFIG_SND_HDA_POWER_SAVE
/* notify power-up/down from codec to contoller */
void (*pm_notify)(struct hda_codec *codec);
#endif
}; };
/* template to pass to the bus constructor */ /* template to pass to the bus constructor */
...@@ -473,10 +481,13 @@ struct hda_codec_ops { ...@@ -473,10 +481,13 @@ struct hda_codec_ops {
int (*init)(struct hda_codec *codec); int (*init)(struct hda_codec *codec);
void (*free)(struct hda_codec *codec); void (*free)(struct hda_codec *codec);
void (*unsol_event)(struct hda_codec *codec, unsigned int res); void (*unsol_event)(struct hda_codec *codec, unsigned int res);
#ifdef CONFIG_PM #ifdef SND_HDA_NEEDS_RESUME
int (*suspend)(struct hda_codec *codec, pm_message_t state); int (*suspend)(struct hda_codec *codec, pm_message_t state);
int (*resume)(struct hda_codec *codec); int (*resume)(struct hda_codec *codec);
#endif #endif
#ifdef CONFIG_SND_HDA_POWER_SAVE
int (*check_power_status)(struct hda_codec *codec, hda_nid_t nid);
#endif
}; };
/* record for amp information cache */ /* record for amp information cache */
...@@ -573,6 +584,12 @@ struct hda_codec { ...@@ -573,6 +584,12 @@ struct hda_codec {
unsigned int spdif_in_enable; /* SPDIF input enable? */ unsigned int spdif_in_enable; /* SPDIF input enable? */
struct snd_hwdep *hwdep; /* assigned hwdep device */ struct snd_hwdep *hwdep; /* assigned hwdep device */
#ifdef CONFIG_SND_HDA_POWER_SAVE
int power_on; /* current (global) power-state */
int power_count; /* current (global) power refcount */
struct delayed_work power_work; /* delayed task for powerdown */
#endif
}; };
/* direction */ /* direction */
...@@ -617,7 +634,7 @@ void snd_hda_sequence_write(struct hda_codec *codec, ...@@ -617,7 +634,7 @@ void snd_hda_sequence_write(struct hda_codec *codec,
int snd_hda_queue_unsol_event(struct hda_bus *bus, u32 res, u32 res_ex); int snd_hda_queue_unsol_event(struct hda_bus *bus, u32 res, u32 res_ex);
/* cached write */ /* cached write */
#ifdef CONFIG_PM #ifdef SND_HDA_NEEDS_RESUME
int snd_hda_codec_write_cache(struct hda_codec *codec, hda_nid_t nid, int snd_hda_codec_write_cache(struct hda_codec *codec, hda_nid_t nid,
int direct, unsigned int verb, unsigned int parm); int direct, unsigned int verb, unsigned int parm);
void snd_hda_sequence_write_cache(struct hda_codec *codec, void snd_hda_sequence_write_cache(struct hda_codec *codec,
...@@ -662,4 +679,15 @@ int snd_hda_suspend(struct hda_bus *bus, pm_message_t state); ...@@ -662,4 +679,15 @@ int snd_hda_suspend(struct hda_bus *bus, pm_message_t state);
int snd_hda_resume(struct hda_bus *bus); int snd_hda_resume(struct hda_bus *bus);
#endif #endif
/*
* power saving
*/
#ifdef CONFIG_SND_HDA_POWER_SAVE
void snd_hda_power_up(struct hda_codec *codec);
void snd_hda_power_down(struct hda_codec *codec);
#else
static inline void snd_hda_power_up(struct hda_codec *codec) {}
static inline void snd_hda_power_down(struct hda_codec *codec) {}
#endif
#endif /* __SOUND_HDA_CODEC_H */ #endif /* __SOUND_HDA_CODEC_H */
...@@ -70,6 +70,13 @@ struct hda_gspec { ...@@ -70,6 +70,13 @@ struct hda_gspec {
struct hda_pcm pcm_rec; /* PCM information */ struct hda_pcm pcm_rec; /* PCM information */
struct list_head nid_list; /* list of widgets */ struct list_head nid_list; /* list of widgets */
#ifdef CONFIG_SND_HDA_POWER_SAVE
#define MAX_LOOPBACK_AMPS 7
struct hda_loopback_check loopback;
int num_loopbacks;
struct hda_amp_list loopback_list[MAX_LOOPBACK_AMPS + 1];
#endif
}; };
/* /*
...@@ -682,11 +689,33 @@ static int parse_input(struct hda_codec *codec) ...@@ -682,11 +689,33 @@ static int parse_input(struct hda_codec *codec)
return 0; return 0;
} }
#ifdef CONFIG_SND_HDA_POWER_SAVE
static void add_input_loopback(struct hda_codec *codec, hda_nid_t nid,
int dir, int idx)
{
struct hda_gspec *spec = codec->spec;
struct hda_amp_list *p;
if (spec->num_loopbacks >= MAX_LOOPBACK_AMPS) {
snd_printk(KERN_ERR "hda_generic: Too many loopback ctls\n");
return;
}
p = &spec->loopback_list[spec->num_loopbacks++];
p->nid = nid;
p->dir = dir;
p->idx = idx;
spec->loopback.amplist = spec->loopback_list;
}
#else
#define add_input_loopback(codec,nid,dir,idx)
#endif
/* /*
* create mixer controls if possible * create mixer controls if possible
*/ */
static int create_mixer(struct hda_codec *codec, struct hda_gnode *node, static int create_mixer(struct hda_codec *codec, struct hda_gnode *node,
unsigned int index, const char *type, const char *dir_sfx) unsigned int index, const char *type,
const char *dir_sfx, int is_loopback)
{ {
char name[32]; char name[32];
int err; int err;
...@@ -700,6 +729,8 @@ static int create_mixer(struct hda_codec *codec, struct hda_gnode *node, ...@@ -700,6 +729,8 @@ static int create_mixer(struct hda_codec *codec, struct hda_gnode *node,
if ((node->wid_caps & AC_WCAP_IN_AMP) && if ((node->wid_caps & AC_WCAP_IN_AMP) &&
(node->amp_in_caps & AC_AMPCAP_MUTE)) { (node->amp_in_caps & AC_AMPCAP_MUTE)) {
knew = (struct snd_kcontrol_new)HDA_CODEC_MUTE(name, node->nid, index, HDA_INPUT); knew = (struct snd_kcontrol_new)HDA_CODEC_MUTE(name, node->nid, index, HDA_INPUT);
if (is_loopback)
add_input_loopback(codec, node->nid, HDA_INPUT, index);
snd_printdd("[%s] NID=0x%x, DIR=IN, IDX=0x%x\n", name, node->nid, index); snd_printdd("[%s] NID=0x%x, DIR=IN, IDX=0x%x\n", name, node->nid, index);
if ((err = snd_ctl_add(codec->bus->card, snd_ctl_new1(&knew, codec))) < 0) if ((err = snd_ctl_add(codec->bus->card, snd_ctl_new1(&knew, codec))) < 0)
return err; return err;
...@@ -707,6 +738,8 @@ static int create_mixer(struct hda_codec *codec, struct hda_gnode *node, ...@@ -707,6 +738,8 @@ static int create_mixer(struct hda_codec *codec, struct hda_gnode *node,
} else if ((node->wid_caps & AC_WCAP_OUT_AMP) && } else if ((node->wid_caps & AC_WCAP_OUT_AMP) &&
(node->amp_out_caps & AC_AMPCAP_MUTE)) { (node->amp_out_caps & AC_AMPCAP_MUTE)) {
knew = (struct snd_kcontrol_new)HDA_CODEC_MUTE(name, node->nid, 0, HDA_OUTPUT); knew = (struct snd_kcontrol_new)HDA_CODEC_MUTE(name, node->nid, 0, HDA_OUTPUT);
if (is_loopback)
add_input_loopback(codec, node->nid, HDA_OUTPUT, 0);
snd_printdd("[%s] NID=0x%x, DIR=OUT\n", name, node->nid); snd_printdd("[%s] NID=0x%x, DIR=OUT\n", name, node->nid);
if ((err = snd_ctl_add(codec->bus->card, snd_ctl_new1(&knew, codec))) < 0) if ((err = snd_ctl_add(codec->bus->card, snd_ctl_new1(&knew, codec))) < 0)
return err; return err;
...@@ -765,7 +798,7 @@ static int create_output_mixers(struct hda_codec *codec, const char **names) ...@@ -765,7 +798,7 @@ static int create_output_mixers(struct hda_codec *codec, const char **names)
for (i = 0; i < spec->pcm_vol_nodes; i++) { for (i = 0; i < spec->pcm_vol_nodes; i++) {
err = create_mixer(codec, spec->pcm_vol[i].node, err = create_mixer(codec, spec->pcm_vol[i].node,
spec->pcm_vol[i].index, spec->pcm_vol[i].index,
names[i], "Playback"); names[i], "Playback", 0);
if (err < 0) if (err < 0)
return err; return err;
} }
...@@ -782,7 +815,7 @@ static int build_output_controls(struct hda_codec *codec) ...@@ -782,7 +815,7 @@ static int build_output_controls(struct hda_codec *codec)
case 1: case 1:
return create_mixer(codec, spec->pcm_vol[0].node, return create_mixer(codec, spec->pcm_vol[0].node,
spec->pcm_vol[0].index, spec->pcm_vol[0].index,
"Master", "Playback"); "Master", "Playback", 0);
case 2: case 2:
if (defcfg_type(spec->out_pin_node[0]) == AC_JACK_SPEAKER) if (defcfg_type(spec->out_pin_node[0]) == AC_JACK_SPEAKER)
return create_output_mixers(codec, types_speaker); return create_output_mixers(codec, types_speaker);
...@@ -818,7 +851,7 @@ static int build_input_controls(struct hda_codec *codec) ...@@ -818,7 +851,7 @@ static int build_input_controls(struct hda_codec *codec)
if (spec->input_mux.num_items == 1) { if (spec->input_mux.num_items == 1) {
err = create_mixer(codec, adc_node, err = create_mixer(codec, adc_node,
spec->input_mux.items[0].index, spec->input_mux.items[0].index,
NULL, "Capture"); NULL, "Capture", 0);
if (err < 0) if (err < 0)
return err; return err;
return 0; return 0;
...@@ -884,7 +917,8 @@ static int parse_loopback_path(struct hda_codec *codec, struct hda_gspec *spec, ...@@ -884,7 +917,8 @@ static int parse_loopback_path(struct hda_codec *codec, struct hda_gspec *spec,
return err; return err;
else if (err >= 1) { else if (err >= 1) {
if (err == 1) { if (err == 1) {
err = create_mixer(codec, node, i, type, "Playback"); err = create_mixer(codec, node, i, type,
"Playback", 1);
if (err < 0) if (err < 0)
return err; return err;
if (err > 0) if (err > 0)
...@@ -1020,6 +1054,14 @@ static int build_generic_pcms(struct hda_codec *codec) ...@@ -1020,6 +1054,14 @@ static int build_generic_pcms(struct hda_codec *codec)
return 0; return 0;
} }
#ifdef CONFIG_SND_HDA_POWER_SAVE
static int generic_check_power_status(struct hda_codec *codec, hda_nid_t nid)
{
struct hda_gspec *spec = codec->spec;
return snd_hda_check_amp_list_power(codec, &spec->loopback, nid);
}
#endif
/* /*
*/ */
...@@ -1027,6 +1069,9 @@ static struct hda_codec_ops generic_patch_ops = { ...@@ -1027,6 +1069,9 @@ static struct hda_codec_ops generic_patch_ops = {
.build_controls = build_generic_controls, .build_controls = build_generic_controls,
.build_pcms = build_generic_pcms, .build_pcms = build_generic_pcms,
.free = snd_hda_generic_free, .free = snd_hda_generic_free,
#ifdef CONFIG_SND_HDA_POWER_SAVE
.check_power_status = generic_check_power_status,
#endif
}; };
/* /*
......
...@@ -75,6 +75,7 @@ MODULE_PARM_DESC(single_cmd, "Use single command to communicate with codecs " ...@@ -75,6 +75,7 @@ MODULE_PARM_DESC(single_cmd, "Use single command to communicate with codecs "
module_param(enable_msi, int, 0); module_param(enable_msi, int, 0);
MODULE_PARM_DESC(enable_msi, "Enable Message Signaled Interrupt (MSI)"); MODULE_PARM_DESC(enable_msi, "Enable Message Signaled Interrupt (MSI)");
/* power_save option is defined in hda_codec.c */
/* just for backward compatibility */ /* just for backward compatibility */
static int enable; static int enable;
...@@ -101,6 +102,18 @@ MODULE_DESCRIPTION("Intel HDA driver"); ...@@ -101,6 +102,18 @@ MODULE_DESCRIPTION("Intel HDA driver");
#define SFX "hda-intel: " #define SFX "hda-intel: "
/*
* build flags
*/
/*
* reset the HD-audio controller in power save mode.
* this may give more power-saving, but will take longer time to
* wake up.
*/
#define HDA_POWER_SAVE_RESET_CONTROLLER
/* /*
* registers * registers
*/ */
...@@ -345,6 +358,7 @@ struct azx { ...@@ -345,6 +358,7 @@ struct azx {
/* flags */ /* flags */
int position_fix; int position_fix;
unsigned int running :1;
unsigned int initialized :1; unsigned int initialized :1;
unsigned int single_cmd :1; unsigned int single_cmd :1;
unsigned int polling_mode :1; unsigned int polling_mode :1;
...@@ -665,6 +679,9 @@ static unsigned int azx_get_response(struct hda_codec *codec) ...@@ -665,6 +679,9 @@ static unsigned int azx_get_response(struct hda_codec *codec)
return azx_rirb_get_response(codec); return azx_rirb_get_response(codec);
} }
#ifdef CONFIG_SND_HDA_POWER_SAVE
static void azx_power_notify(struct hda_codec *codec);
#endif
/* reset codec link */ /* reset codec link */
static int azx_reset(struct azx *chip) static int azx_reset(struct azx *chip)
...@@ -790,19 +807,12 @@ static void azx_stream_stop(struct azx *chip, struct azx_dev *azx_dev) ...@@ -790,19 +807,12 @@ static void azx_stream_stop(struct azx *chip, struct azx_dev *azx_dev)
/* /*
* initialize the chip * reset and start the controller registers
*/ */
static void azx_init_chip(struct azx *chip) static void azx_init_chip(struct azx *chip)
{ {
unsigned char reg; if (chip->initialized)
return;
/* Clear bits 0-2 of PCI register TCSEL (at offset 0x44)
* TCSEL == Traffic Class Select Register, which sets PCI express QOS
* Ensuring these bits are 0 clears playback static on some HD Audio
* codecs
*/
pci_read_config_byte (chip->pci, ICH6_PCIREG_TCSEL, &reg);
pci_write_config_byte(chip->pci, ICH6_PCIREG_TCSEL, reg & 0xf8);
/* reset controller */ /* reset controller */
azx_reset(chip); azx_reset(chip);
...@@ -819,22 +829,45 @@ static void azx_init_chip(struct azx *chip) ...@@ -819,22 +829,45 @@ static void azx_init_chip(struct azx *chip)
azx_writel(chip, DPLBASE, (u32)chip->posbuf.addr); azx_writel(chip, DPLBASE, (u32)chip->posbuf.addr);
azx_writel(chip, DPUBASE, upper_32bit(chip->posbuf.addr)); azx_writel(chip, DPUBASE, upper_32bit(chip->posbuf.addr));
chip->initialized = 1;
}
/*
* initialize the PCI registers
*/
/* update bits in a PCI register byte */
static void update_pci_byte(struct pci_dev *pci, unsigned int reg,
unsigned char mask, unsigned char val)
{
unsigned char data;
pci_read_config_byte(pci, reg, &data);
data &= ~mask;
data |= (val & mask);
pci_write_config_byte(pci, reg, data);
}
static void azx_init_pci(struct azx *chip)
{
/* Clear bits 0-2 of PCI register TCSEL (at offset 0x44)
* TCSEL == Traffic Class Select Register, which sets PCI express QOS
* Ensuring these bits are 0 clears playback static on some HD Audio
* codecs
*/
update_pci_byte(chip->pci, ICH6_PCIREG_TCSEL, 0x07, 0);
switch (chip->driver_type) { switch (chip->driver_type) {
case AZX_DRIVER_ATI: case AZX_DRIVER_ATI:
/* For ATI SB450 azalia HD audio, we need to enable snoop */ /* For ATI SB450 azalia HD audio, we need to enable snoop */
pci_read_config_byte(chip->pci, update_pci_byte(chip->pci,
ATI_SB450_HDAUDIO_MISC_CNTR2_ADDR, ATI_SB450_HDAUDIO_MISC_CNTR2_ADDR,
&reg); 0x07, ATI_SB450_HDAUDIO_ENABLE_SNOOP);
pci_write_config_byte(chip->pci,
ATI_SB450_HDAUDIO_MISC_CNTR2_ADDR,
(reg & 0xf8) |
ATI_SB450_HDAUDIO_ENABLE_SNOOP);
break; break;
case AZX_DRIVER_NVIDIA: case AZX_DRIVER_NVIDIA:
/* For NVIDIA HDA, enable snoop */ /* For NVIDIA HDA, enable snoop */
pci_read_config_byte(chip->pci,NVIDIA_HDA_TRANSREG_ADDR, &reg); update_pci_byte(chip->pci,
pci_write_config_byte(chip->pci,NVIDIA_HDA_TRANSREG_ADDR, NVIDIA_HDA_TRANSREG_ADDR,
(reg & 0xf0) | NVIDIA_HDA_ENABLE_COHBITS); 0x0f, NVIDIA_HDA_ENABLE_COHBITS);
break; break;
} }
} }
...@@ -1007,6 +1040,9 @@ static int __devinit azx_codec_create(struct azx *chip, const char *model) ...@@ -1007,6 +1040,9 @@ static int __devinit azx_codec_create(struct azx *chip, const char *model)
bus_temp.pci = chip->pci; bus_temp.pci = chip->pci;
bus_temp.ops.command = azx_send_cmd; bus_temp.ops.command = azx_send_cmd;
bus_temp.ops.get_response = azx_get_response; bus_temp.ops.get_response = azx_get_response;
#ifdef CONFIG_SND_HDA_POWER_SAVE
bus_temp.ops.pm_notify = azx_power_notify;
#endif
err = snd_hda_bus_new(chip->card, &bus_temp, &chip->bus); err = snd_hda_bus_new(chip->card, &bus_temp, &chip->bus);
if (err < 0) if (err < 0)
...@@ -1128,9 +1164,11 @@ static int azx_pcm_open(struct snd_pcm_substream *substream) ...@@ -1128,9 +1164,11 @@ static int azx_pcm_open(struct snd_pcm_substream *substream)
128); 128);
snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
128); 128);
snd_hda_power_up(apcm->codec);
err = hinfo->ops.open(hinfo, apcm->codec, substream); err = hinfo->ops.open(hinfo, apcm->codec, substream);
if (err < 0) { if (err < 0) {
azx_release_device(azx_dev); azx_release_device(azx_dev);
snd_hda_power_down(apcm->codec);
mutex_unlock(&chip->open_mutex); mutex_unlock(&chip->open_mutex);
return err; return err;
} }
...@@ -1159,6 +1197,7 @@ static int azx_pcm_close(struct snd_pcm_substream *substream) ...@@ -1159,6 +1197,7 @@ static int azx_pcm_close(struct snd_pcm_substream *substream)
spin_unlock_irqrestore(&chip->reg_lock, flags); spin_unlock_irqrestore(&chip->reg_lock, flags);
azx_release_device(azx_dev); azx_release_device(azx_dev);
hinfo->ops.close(hinfo, apcm->codec, substream); hinfo->ops.close(hinfo, apcm->codec, substream);
snd_hda_power_down(apcm->codec);
mutex_unlock(&chip->open_mutex); mutex_unlock(&chip->open_mutex);
return 0; return 0;
} }
...@@ -1459,6 +1498,48 @@ static int azx_acquire_irq(struct azx *chip, int do_disconnect) ...@@ -1459,6 +1498,48 @@ static int azx_acquire_irq(struct azx *chip, int do_disconnect)
} }
static void azx_stop_chip(struct azx *chip)
{
if (chip->initialized)
return;
/* disable interrupts */
azx_int_disable(chip);
azx_int_clear(chip);
/* disable CORB/RIRB */
azx_free_cmd_io(chip);
/* disable position buffer */
azx_writel(chip, DPLBASE, 0);
azx_writel(chip, DPUBASE, 0);
chip->initialized = 0;
}
#ifdef CONFIG_SND_HDA_POWER_SAVE
/* power-up/down the controller */
static void azx_power_notify(struct hda_codec *codec)
{
struct azx *chip = codec->bus->private_data;
struct hda_codec *c;
int power_on = 0;
list_for_each_entry(c, &codec->bus->codec_list, list) {
if (c->power_on) {
power_on = 1;
break;
}
}
if (power_on)
azx_init_chip(chip);
#ifdef HDA_POWER_SAVE_RESET_CONTROLLER
else if (chip->running)
azx_stop_chip(chip);
#endif
}
#endif /* CONFIG_SND_HDA_POWER_SAVE */
#ifdef CONFIG_PM #ifdef CONFIG_PM
/* /*
* power management * power management
...@@ -1473,7 +1554,7 @@ static int azx_suspend(struct pci_dev *pci, pm_message_t state) ...@@ -1473,7 +1554,7 @@ static int azx_suspend(struct pci_dev *pci, pm_message_t state)
for (i = 0; i < chip->pcm_devs; i++) for (i = 0; i < chip->pcm_devs; i++)
snd_pcm_suspend_all(chip->pcm[i]); snd_pcm_suspend_all(chip->pcm[i]);
snd_hda_suspend(chip->bus, state); snd_hda_suspend(chip->bus, state);
azx_free_cmd_io(chip); azx_stop_chip(chip);
if (chip->irq >= 0) { if (chip->irq >= 0) {
synchronize_irq(chip->irq); synchronize_irq(chip->irq);
free_irq(chip->irq, chip); free_irq(chip->irq, chip);
...@@ -1506,8 +1587,12 @@ static int azx_resume(struct pci_dev *pci) ...@@ -1506,8 +1587,12 @@ static int azx_resume(struct pci_dev *pci)
chip->msi = 0; chip->msi = 0;
if (azx_acquire_irq(chip, 1) < 0) if (azx_acquire_irq(chip, 1) < 0)
return -EIO; return -EIO;
azx_init_pci(chip);
#ifndef CONFIG_SND_HDA_POWER_SAVE
/* the explicit resume is needed only when POWER_SAVE isn't set */
azx_init_chip(chip); azx_init_chip(chip);
snd_hda_resume(chip->bus); snd_hda_resume(chip->bus);
#endif
snd_power_change_state(card, SNDRV_CTL_POWER_D0); snd_power_change_state(card, SNDRV_CTL_POWER_D0);
return 0; return 0;
} }
...@@ -1521,20 +1606,9 @@ static int azx_free(struct azx *chip) ...@@ -1521,20 +1606,9 @@ static int azx_free(struct azx *chip)
{ {
if (chip->initialized) { if (chip->initialized) {
int i; int i;
for (i = 0; i < chip->num_streams; i++) for (i = 0; i < chip->num_streams; i++)
azx_stream_stop(chip, &chip->azx_dev[i]); azx_stream_stop(chip, &chip->azx_dev[i]);
azx_stop_chip(chip);
/* disable interrupts */
azx_int_disable(chip);
azx_int_clear(chip);
/* disable CORB/RIRB */
azx_free_cmd_io(chip);
/* disable position buffer */
azx_writel(chip, DPLBASE, 0);
azx_writel(chip, DPUBASE, 0);
} }
if (chip->irq >= 0) { if (chip->irq >= 0) {
...@@ -1720,10 +1794,9 @@ static int __devinit azx_create(struct snd_card *card, struct pci_dev *pci, ...@@ -1720,10 +1794,9 @@ static int __devinit azx_create(struct snd_card *card, struct pci_dev *pci,
azx_init_stream(chip); azx_init_stream(chip);
/* initialize chip */ /* initialize chip */
azx_init_pci(chip);
azx_init_chip(chip); azx_init_chip(chip);
chip->initialized = 1;
/* codec detection */ /* codec detection */
if (!chip->codec_mask) { if (!chip->codec_mask) {
snd_printk(KERN_ERR SFX "no codecs found!\n"); snd_printk(KERN_ERR SFX "no codecs found!\n");
...@@ -1750,6 +1823,19 @@ static int __devinit azx_create(struct snd_card *card, struct pci_dev *pci, ...@@ -1750,6 +1823,19 @@ static int __devinit azx_create(struct snd_card *card, struct pci_dev *pci,
return err; return err;
} }
static void power_down_all_codecs(struct azx *chip)
{
#ifdef CONFIG_SND_HDA_POWER_SAVE
/* The codecs were powered up in snd_hda_codec_new().
* Now all initialization done, so turn them down if possible
*/
struct hda_codec *codec;
list_for_each_entry(codec, &chip->bus->codec_list, list) {
snd_hda_power_down(codec);
}
#endif
}
static int __devinit azx_probe(struct pci_dev *pci, static int __devinit azx_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id) const struct pci_device_id *pci_id)
{ {
...@@ -1800,6 +1886,8 @@ static int __devinit azx_probe(struct pci_dev *pci, ...@@ -1800,6 +1886,8 @@ static int __devinit azx_probe(struct pci_dev *pci,
} }
pci_set_drvdata(pci, card); pci_set_drvdata(pci, card);
chip->running = 1;
power_down_all_codecs(chip);
return err; return err;
} }
......
...@@ -86,7 +86,7 @@ int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid, int ch, ...@@ -86,7 +86,7 @@ int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid, int ch,
int direction, int idx, int mask, int val); int direction, int idx, int mask, int val);
int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid, int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid,
int dir, int idx, int mask, int val); int dir, int idx, int mask, int val);
#ifdef CONFIG_PM #ifdef SND_HDA_NEEDS_RESUME
void snd_hda_codec_resume_amp(struct hda_codec *codec); void snd_hda_codec_resume_amp(struct hda_codec *codec);
#endif #endif
...@@ -366,4 +366,27 @@ int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir, ...@@ -366,4 +366,27 @@ int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir,
*/ */
int snd_hda_create_hwdep(struct hda_codec *codec); int snd_hda_create_hwdep(struct hda_codec *codec);
/*
* power-management
*/
#ifdef CONFIG_SND_HDA_POWER_SAVE
void snd_hda_schedule_power_save(struct hda_codec *codec);
struct hda_amp_list {
hda_nid_t nid;
unsigned char dir;
unsigned char idx;
};
struct hda_loopback_check {
struct hda_amp_list *amplist;
int power_on;
};
int snd_hda_check_amp_list_power(struct hda_codec *codec,
struct hda_loopback_check *check,
hda_nid_t nid);
#endif /* CONFIG_SND_HDA_POWER_SAVE */
#endif /* __SOUND_HDA_LOCAL_H */ #endif /* __SOUND_HDA_LOCAL_H */
...@@ -262,6 +262,7 @@ static void print_codec_info(struct snd_info_entry *entry, ...@@ -262,6 +262,7 @@ static void print_codec_info(struct snd_info_entry *entry,
if (! codec->afg) if (! codec->afg)
return; return;
snd_hda_power_up(codec);
snd_iprintf(buffer, "Default PCM:\n"); snd_iprintf(buffer, "Default PCM:\n");
print_pcm_caps(buffer, codec, codec->afg); print_pcm_caps(buffer, codec, codec->afg);
snd_iprintf(buffer, "Default Amp-In caps: "); snd_iprintf(buffer, "Default Amp-In caps: ");
...@@ -272,6 +273,7 @@ static void print_codec_info(struct snd_info_entry *entry, ...@@ -272,6 +273,7 @@ static void print_codec_info(struct snd_info_entry *entry,
nodes = snd_hda_get_sub_nodes(codec, codec->afg, &nid); nodes = snd_hda_get_sub_nodes(codec, codec->afg, &nid);
if (! nid || nodes < 0) { if (! nid || nodes < 0) {
snd_iprintf(buffer, "Invalid AFG subtree\n"); snd_iprintf(buffer, "Invalid AFG subtree\n");
snd_hda_power_down(codec);
return; return;
} }
for (i = 0; i < nodes; i++, nid++) { for (i = 0; i < nodes; i++, nid++) {
...@@ -359,6 +361,7 @@ static void print_codec_info(struct snd_info_entry *entry, ...@@ -359,6 +361,7 @@ static void print_codec_info(struct snd_info_entry *entry,
snd_iprintf(buffer, "\n"); snd_iprintf(buffer, "\n");
} }
} }
snd_hda_power_down(codec);
} }
/* /*
......
...@@ -73,6 +73,10 @@ struct ad198x_spec { ...@@ -73,6 +73,10 @@ struct ad198x_spec {
struct snd_kcontrol_new *kctl_alloc; struct snd_kcontrol_new *kctl_alloc;
struct hda_input_mux private_imux; struct hda_input_mux private_imux;
hda_nid_t private_dac_nids[4]; hda_nid_t private_dac_nids[4];
#ifdef CONFIG_SND_HDA_POWER_SAVE
struct hda_loopback_check loopback;
#endif
}; };
/* /*
...@@ -144,6 +148,14 @@ static int ad198x_build_controls(struct hda_codec *codec) ...@@ -144,6 +148,14 @@ static int ad198x_build_controls(struct hda_codec *codec)
return 0; return 0;
} }
#ifdef CONFIG_SND_HDA_POWER_SAVE
static int ad198x_check_power_status(struct hda_codec *codec, hda_nid_t nid)
{
struct ad198x_spec *spec = codec->spec;
return snd_hda_check_amp_list_power(codec, &spec->loopback, nid);
}
#endif
/* /*
* Analog playback callbacks * Analog playback callbacks
*/ */
...@@ -323,6 +335,9 @@ static struct hda_codec_ops ad198x_patch_ops = { ...@@ -323,6 +335,9 @@ static struct hda_codec_ops ad198x_patch_ops = {
.build_pcms = ad198x_build_pcms, .build_pcms = ad198x_build_pcms,
.init = ad198x_init, .init = ad198x_init,
.free = ad198x_free, .free = ad198x_free,
#ifdef CONFIG_SND_HDA_POWER_SAVE
.check_power_status = ad198x_check_power_status,
#endif
}; };
...@@ -736,6 +751,17 @@ static struct snd_pci_quirk ad1986a_cfg_tbl[] = { ...@@ -736,6 +751,17 @@ static struct snd_pci_quirk ad1986a_cfg_tbl[] = {
{} {}
}; };
#ifdef CONFIG_SND_HDA_POWER_SAVE
static struct hda_amp_list ad1986a_loopbacks[] = {
{ 0x13, HDA_OUTPUT, 0 }, /* Mic */
{ 0x14, HDA_OUTPUT, 0 }, /* Phone */
{ 0x15, HDA_OUTPUT, 0 }, /* CD */
{ 0x16, HDA_OUTPUT, 0 }, /* Aux */
{ 0x17, HDA_OUTPUT, 0 }, /* Line */
{ } /* end */
};
#endif
static int patch_ad1986a(struct hda_codec *codec) static int patch_ad1986a(struct hda_codec *codec)
{ {
struct ad198x_spec *spec; struct ad198x_spec *spec;
...@@ -759,6 +785,9 @@ static int patch_ad1986a(struct hda_codec *codec) ...@@ -759,6 +785,9 @@ static int patch_ad1986a(struct hda_codec *codec)
spec->mixers[0] = ad1986a_mixers; spec->mixers[0] = ad1986a_mixers;
spec->num_init_verbs = 1; spec->num_init_verbs = 1;
spec->init_verbs[0] = ad1986a_init_verbs; spec->init_verbs[0] = ad1986a_init_verbs;
#ifdef CONFIG_SND_HDA_POWER_SAVE
spec->loopback.amplist = ad1986a_loopbacks;
#endif
codec->patch_ops = ad198x_patch_ops; codec->patch_ops = ad198x_patch_ops;
...@@ -944,6 +973,13 @@ static struct hda_verb ad1983_init_verbs[] = { ...@@ -944,6 +973,13 @@ static struct hda_verb ad1983_init_verbs[] = {
{ } /* end */ { } /* end */
}; };
#ifdef CONFIG_SND_HDA_POWER_SAVE
static struct hda_amp_list ad1983_loopbacks[] = {
{ 0x12, HDA_OUTPUT, 0 }, /* Mic */
{ 0x13, HDA_OUTPUT, 0 }, /* Line */
{ } /* end */
};
#endif
static int patch_ad1983(struct hda_codec *codec) static int patch_ad1983(struct hda_codec *codec)
{ {
...@@ -968,6 +1004,9 @@ static int patch_ad1983(struct hda_codec *codec) ...@@ -968,6 +1004,9 @@ static int patch_ad1983(struct hda_codec *codec)
spec->num_init_verbs = 1; spec->num_init_verbs = 1;
spec->init_verbs[0] = ad1983_init_verbs; spec->init_verbs[0] = ad1983_init_verbs;
spec->spdif_route = 0; spec->spdif_route = 0;
#ifdef CONFIG_SND_HDA_POWER_SAVE
spec->loopback.amplist = ad1983_loopbacks;
#endif
codec->patch_ops = ad198x_patch_ops; codec->patch_ops = ad198x_patch_ops;
...@@ -1091,6 +1130,17 @@ static struct hda_verb ad1981_init_verbs[] = { ...@@ -1091,6 +1130,17 @@ static struct hda_verb ad1981_init_verbs[] = {
{ } /* end */ { } /* end */
}; };
#ifdef CONFIG_SND_HDA_POWER_SAVE
static struct hda_amp_list ad1981_loopbacks[] = {
{ 0x12, HDA_OUTPUT, 0 }, /* Front Mic */
{ 0x13, HDA_OUTPUT, 0 }, /* Line */
{ 0x1b, HDA_OUTPUT, 0 }, /* Aux */
{ 0x1c, HDA_OUTPUT, 0 }, /* Mic */
{ 0x1d, HDA_OUTPUT, 0 }, /* CD */
{ } /* end */
};
#endif
/* /*
* Patch for HP nx6320 * Patch for HP nx6320
* *
...@@ -1350,6 +1400,9 @@ static int patch_ad1981(struct hda_codec *codec) ...@@ -1350,6 +1400,9 @@ static int patch_ad1981(struct hda_codec *codec)
spec->num_init_verbs = 1; spec->num_init_verbs = 1;
spec->init_verbs[0] = ad1981_init_verbs; spec->init_verbs[0] = ad1981_init_verbs;
spec->spdif_route = 0; spec->spdif_route = 0;
#ifdef CONFIG_SND_HDA_POWER_SAVE
spec->loopback.amplist = ad1981_loopbacks;
#endif
codec->patch_ops = ad198x_patch_ops; codec->patch_ops = ad198x_patch_ops;
...@@ -2103,6 +2156,15 @@ static void ad1988_laptop_unsol_event(struct hda_codec *codec, unsigned int res) ...@@ -2103,6 +2156,15 @@ static void ad1988_laptop_unsol_event(struct hda_codec *codec, unsigned int res)
snd_hda_sequence_write(codec, ad1988_laptop_hp_off); snd_hda_sequence_write(codec, ad1988_laptop_hp_off);
} }
#ifdef CONFIG_SND_HDA_POWER_SAVE
static struct hda_amp_list ad1988_loopbacks[] = {
{ 0x20, HDA_INPUT, 0 }, /* Front Mic */
{ 0x20, HDA_INPUT, 1 }, /* Line */
{ 0x20, HDA_INPUT, 4 }, /* Mic */
{ 0x20, HDA_INPUT, 6 }, /* CD */
{ } /* end */
};
#endif
/* /*
* Automatic parse of I/O pins from the BIOS configuration * Automatic parse of I/O pins from the BIOS configuration
...@@ -2647,6 +2709,9 @@ static int patch_ad1988(struct hda_codec *codec) ...@@ -2647,6 +2709,9 @@ static int patch_ad1988(struct hda_codec *codec)
codec->patch_ops.unsol_event = ad1988_laptop_unsol_event; codec->patch_ops.unsol_event = ad1988_laptop_unsol_event;
break; break;
} }
#ifdef CONFIG_SND_HDA_POWER_SAVE
spec->loopback.amplist = ad1988_loopbacks;
#endif
return 0; return 0;
} }
...@@ -2803,6 +2868,16 @@ static struct hda_verb ad1884_init_verbs[] = { ...@@ -2803,6 +2868,16 @@ static struct hda_verb ad1884_init_verbs[] = {
{ } /* end */ { } /* end */
}; };
#ifdef CONFIG_SND_HDA_POWER_SAVE
static struct hda_amp_list ad1884_loopbacks[] = {
{ 0x20, HDA_INPUT, 0 }, /* Front Mic */
{ 0x20, HDA_INPUT, 1 }, /* Mic */
{ 0x20, HDA_INPUT, 2 }, /* CD */
{ 0x20, HDA_INPUT, 4 }, /* Docking */
{ } /* end */
};
#endif
static int patch_ad1884(struct hda_codec *codec) static int patch_ad1884(struct hda_codec *codec)
{ {
struct ad198x_spec *spec; struct ad198x_spec *spec;
...@@ -2827,6 +2902,9 @@ static int patch_ad1884(struct hda_codec *codec) ...@@ -2827,6 +2902,9 @@ static int patch_ad1884(struct hda_codec *codec)
spec->num_init_verbs = 1; spec->num_init_verbs = 1;
spec->init_verbs[0] = ad1884_init_verbs; spec->init_verbs[0] = ad1884_init_verbs;
spec->spdif_route = 0; spec->spdif_route = 0;
#ifdef CONFIG_SND_HDA_POWER_SAVE
spec->loopback.amplist = ad1884_loopbacks;
#endif
codec->patch_ops = ad198x_patch_ops; codec->patch_ops = ad198x_patch_ops;
...@@ -3208,6 +3286,16 @@ static struct hda_verb ad1882_init_verbs[] = { ...@@ -3208,6 +3286,16 @@ static struct hda_verb ad1882_init_verbs[] = {
{ } /* end */ { } /* end */
}; };
#ifdef CONFIG_SND_HDA_POWER_SAVE
static struct hda_amp_list ad1882_loopbacks[] = {
{ 0x20, HDA_INPUT, 0 }, /* Front Mic */
{ 0x20, HDA_INPUT, 1 }, /* Mic */
{ 0x20, HDA_INPUT, 4 }, /* Line */
{ 0x20, HDA_INPUT, 6 }, /* CD */
{ } /* end */
};
#endif
/* models */ /* models */
enum { enum {
AD1882_3STACK, AD1882_3STACK,
...@@ -3246,6 +3334,9 @@ static int patch_ad1882(struct hda_codec *codec) ...@@ -3246,6 +3334,9 @@ static int patch_ad1882(struct hda_codec *codec)
spec->num_init_verbs = 1; spec->num_init_verbs = 1;
spec->init_verbs[0] = ad1882_init_verbs; spec->init_verbs[0] = ad1882_init_verbs;
spec->spdif_route = 0; spec->spdif_route = 0;
#ifdef CONFIG_SND_HDA_POWER_SAVE
spec->loopback.amplist = ad1882_loopbacks;
#endif
codec->patch_ops = ad198x_patch_ops; codec->patch_ops = ad198x_patch_ops;
......
此差异已折叠。
...@@ -1946,7 +1946,7 @@ static void stac92xx_unsol_event(struct hda_codec *codec, unsigned int res) ...@@ -1946,7 +1946,7 @@ static void stac92xx_unsol_event(struct hda_codec *codec, unsigned int res)
} }
} }
#ifdef CONFIG_PM #ifdef SND_HDA_NEEDS_RESUME
static int stac92xx_resume(struct hda_codec *codec) static int stac92xx_resume(struct hda_codec *codec)
{ {
stac92xx_set_config_regs(codec); stac92xx_set_config_regs(codec);
...@@ -1963,7 +1963,7 @@ static struct hda_codec_ops stac92xx_patch_ops = { ...@@ -1963,7 +1963,7 @@ static struct hda_codec_ops stac92xx_patch_ops = {
.init = stac92xx_init, .init = stac92xx_init,
.free = stac92xx_free, .free = stac92xx_free,
.unsol_event = stac92xx_unsol_event, .unsol_event = stac92xx_unsol_event,
#ifdef CONFIG_PM #ifdef SND_HDA_NEEDS_RESUME
.resume = stac92xx_resume, .resume = stac92xx_resume,
#endif #endif
}; };
...@@ -2460,7 +2460,7 @@ static struct hda_codec_ops stac9872_patch_ops = { ...@@ -2460,7 +2460,7 @@ static struct hda_codec_ops stac9872_patch_ops = {
.build_pcms = stac92xx_build_pcms, .build_pcms = stac92xx_build_pcms,
.init = stac92xx_init, .init = stac92xx_init,
.free = stac92xx_free, .free = stac92xx_free,
#ifdef CONFIG_PM #ifdef SND_HDA_NEEDS_RESUME
.resume = stac92xx_resume, .resume = stac92xx_resume,
#endif #endif
}; };
......
...@@ -115,6 +115,10 @@ struct via_spec { ...@@ -115,6 +115,10 @@ struct via_spec {
struct snd_kcontrol_new *kctl_alloc; struct snd_kcontrol_new *kctl_alloc;
struct hda_input_mux private_imux; struct hda_input_mux private_imux;
hda_nid_t private_dac_nids[4]; hda_nid_t private_dac_nids[4];
#ifdef CONFIG_SND_HDA_POWER_SAVE
struct hda_loopback_check loopback;
#endif
}; };
static hda_nid_t vt1708_adc_nids[2] = { static hda_nid_t vt1708_adc_nids[2] = {
...@@ -305,15 +309,15 @@ static struct hda_verb vt1708_volume_init_verbs[] = { ...@@ -305,15 +309,15 @@ static struct hda_verb vt1708_volume_init_verbs[] = {
{0x27, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, {0x27, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
/* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback /* Mute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback
* mixer widget * mixer widget
*/ */
/* Amp Indices: CD = 1, Mic1 = 2, Line = 3, Mic2 = 4 */ /* Amp Indices: CD = 1, Mic1 = 2, Line = 3, Mic2 = 4 */
{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, /* master */
{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)}, {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)}, {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
/* /*
* Set up output mixers (0x19 - 0x1b) * Set up output mixers (0x19 - 0x1b)
...@@ -543,6 +547,14 @@ static int via_init(struct hda_codec *codec) ...@@ -543,6 +547,14 @@ static int via_init(struct hda_codec *codec)
return 0; return 0;
} }
#ifdef CONFIG_SND_HDA_POWER_SAVE
static int via_check_power_status(struct hda_codec *codec, hda_nid_t nid)
{
struct via_spec *spec = codec->spec;
return snd_hda_check_amp_list_power(codec, &spec->loopback, nid);
}
#endif
/* /*
*/ */
static struct hda_codec_ops via_patch_ops = { static struct hda_codec_ops via_patch_ops = {
...@@ -550,6 +562,9 @@ static struct hda_codec_ops via_patch_ops = { ...@@ -550,6 +562,9 @@ static struct hda_codec_ops via_patch_ops = {
.build_pcms = via_build_pcms, .build_pcms = via_build_pcms,
.init = via_init, .init = via_init,
.free = via_free, .free = via_free,
#ifdef CONFIG_SND_HDA_POWER_SAVE
.check_power_status = via_check_power_status,
#endif
}; };
/* fill in the dac_nids table from the parsed pin configuration */ /* fill in the dac_nids table from the parsed pin configuration */
...@@ -738,6 +753,16 @@ static int vt1708_auto_create_analog_input_ctls(struct via_spec *spec, ...@@ -738,6 +753,16 @@ static int vt1708_auto_create_analog_input_ctls(struct via_spec *spec,
return 0; return 0;
} }
#ifdef CONFIG_SND_HDA_POWER_SAVE
static struct hda_amp_list vt1708_loopbacks[] = {
{ 0x17, HDA_INPUT, 1 },
{ 0x17, HDA_INPUT, 2 },
{ 0x17, HDA_INPUT, 3 },
{ 0x17, HDA_INPUT, 4 },
{ } /* end */
};
#endif
static int vt1708_parse_auto_config(struct hda_codec *codec) static int vt1708_parse_auto_config(struct hda_codec *codec)
{ {
struct via_spec *spec = codec->spec; struct via_spec *spec = codec->spec;
...@@ -831,6 +856,9 @@ static int patch_vt1708(struct hda_codec *codec) ...@@ -831,6 +856,9 @@ static int patch_vt1708(struct hda_codec *codec)
codec->patch_ops = via_patch_ops; codec->patch_ops = via_patch_ops;
codec->patch_ops.init = via_auto_init; codec->patch_ops.init = via_auto_init;
#ifdef CONFIG_SND_HDA_POWER_SAVE
spec->loopback.amplist = vt1708_loopbacks;
#endif
return 0; return 0;
} }
...@@ -871,15 +899,15 @@ static struct hda_verb vt1709_10ch_volume_init_verbs[] = { ...@@ -871,15 +899,15 @@ static struct hda_verb vt1709_10ch_volume_init_verbs[] = {
{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
/* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback /* Mute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback
* mixer widget * mixer widget
*/ */
/* Amp Indices: AOW0=0, CD = 1, Mic1 = 2, Line = 3, Mic2 = 4 */ /* Amp Indices: AOW0=0, CD = 1, Mic1 = 2, Line = 3, Mic2 = 4 */
{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, /* unmute master */
{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)}, {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)}, {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
/* /*
* Set up output selector (0x1a, 0x1b, 0x29) * Set up output selector (0x1a, 0x1b, 0x29)
...@@ -1227,6 +1255,16 @@ static int vt1709_parse_auto_config(struct hda_codec *codec) ...@@ -1227,6 +1255,16 @@ static int vt1709_parse_auto_config(struct hda_codec *codec)
return 1; return 1;
} }
#ifdef CONFIG_SND_HDA_POWER_SAVE
static struct hda_amp_list vt1709_loopbacks[] = {
{ 0x18, HDA_INPUT, 1 },
{ 0x18, HDA_INPUT, 2 },
{ 0x18, HDA_INPUT, 3 },
{ 0x18, HDA_INPUT, 4 },
{ } /* end */
};
#endif
static int patch_vt1709_10ch(struct hda_codec *codec) static int patch_vt1709_10ch(struct hda_codec *codec)
{ {
struct via_spec *spec; struct via_spec *spec;
...@@ -1269,6 +1307,9 @@ static int patch_vt1709_10ch(struct hda_codec *codec) ...@@ -1269,6 +1307,9 @@ static int patch_vt1709_10ch(struct hda_codec *codec)
codec->patch_ops = via_patch_ops; codec->patch_ops = via_patch_ops;
codec->patch_ops.init = via_auto_init; codec->patch_ops.init = via_auto_init;
#ifdef CONFIG_SND_HDA_POWER_SAVE
spec->loopback.amplist = vt1709_loopbacks;
#endif
return 0; return 0;
} }
...@@ -1359,6 +1400,9 @@ static int patch_vt1709_6ch(struct hda_codec *codec) ...@@ -1359,6 +1400,9 @@ static int patch_vt1709_6ch(struct hda_codec *codec)
codec->patch_ops = via_patch_ops; codec->patch_ops = via_patch_ops;
codec->patch_ops.init = via_auto_init; codec->patch_ops.init = via_auto_init;
#ifdef CONFIG_SND_HDA_POWER_SAVE
spec->loopback.amplist = vt1709_loopbacks;
#endif
return 0; return 0;
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册