Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cb53c626 authored by Takashi Iwai's avatar Takashi Iwai Committed by Jaroslav Kysela
Browse files

[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: default avatarTakashi Iwai <tiwai@suse.de>
Signed-off-by: default avatarJaroslav Kysela <perex@suse.cz>
parent cca3b371
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -581,6 +581,14 @@ config SND_HDA_GENERIC
	  Say Y here to enable the generic HD-audio codec parser
	  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
	tristate "RME Hammerfall DSP Audio"
	depends on SND
+197 −42
Original line number Diff line number Diff line
@@ -33,6 +33,13 @@
#include "hda_local.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
@@ -60,6 +67,13 @@ static struct hda_vendor_id hda_vendor_ids[] = {
#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
 * @codec: the HDA codec
@@ -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 res;
	snd_hda_power_up(codec);
	mutex_lock(&codec->bus->cmd_mutex);
	if (!codec->bus->ops.command(codec, nid, direct, verb, parm))
		res = codec->bus->ops.get_response(codec);
	else
		res = (unsigned int)-1;
	mutex_unlock(&codec->bus->cmd_mutex);
	snd_hda_power_down(codec);
	return res;
}

@@ -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)
{
	int err;
	snd_hda_power_up(codec);
	mutex_lock(&codec->bus->cmd_mutex);
	err = codec->bus->ops.command(codec, nid, direct, verb, parm);
	mutex_unlock(&codec->bus->cmd_mutex);
	snd_hda_power_down(codec);
	return err;
}

@@ -505,6 +523,9 @@ static void snd_hda_codec_free(struct hda_codec *codec)
{
	if (!codec)
		return;
#ifdef CONFIG_SND_HDA_POWER_SAVE
	cancel_delayed_work(&codec->power_work);
#endif
	list_del(&codec->list);
	codec->bus->caddr_tbl[codec->addr] = NULL;
	if (codec->patch_ops.free)
@@ -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->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);
	bus->caddr_tbl[codec_addr] = codec;

@@ -855,7 +885,7 @@ int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid,
	return ret;
}

#ifdef CONFIG_PM
#ifdef SND_HDA_NEEDS_RESUME
/* resume the all amp commands from the cache */
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
@@ -945,6 +975,7 @@ int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol,
	long *valp = ucontrol->value.integer.value;
	int change = 0;

	snd_hda_power_up(codec);
	if (chs & 1) {
		change = snd_hda_codec_amp_update(codec, nid, 0, dir, idx,
						  0x7f, *valp);
@@ -953,6 +984,7 @@ int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol,
	if (chs & 2)
		change |= snd_hda_codec_amp_update(codec, nid, 1, dir, idx,
						   0x7f, *valp);
	snd_hda_power_down(codec);
	return change;
}

@@ -1025,6 +1057,7 @@ int snd_hda_mixer_amp_switch_put(struct snd_kcontrol *kcontrol,
	long *valp = ucontrol->value.integer.value;
	int change = 0;

	snd_hda_power_up(codec);
	if (chs & 1) {
		change = snd_hda_codec_amp_update(codec, nid, 0, dir, idx,
						  HDA_AMP_MUTE,
@@ -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,
						   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;
}

@@ -1502,7 +1539,7 @@ int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid)
	return 0;
}

#ifdef CONFIG_PM
#ifdef SND_HDA_NEEDS_RESUME
/*
 * command cache
 */
@@ -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 err;
	snd_hda_power_up(codec);
	mutex_lock(&codec->bus->cmd_mutex);
	err = codec->bus->ops.command(codec, nid, direct, verb, parm);
	if (!err) {
@@ -1538,6 +1576,7 @@ int snd_hda_codec_write_cache(struct hda_codec *codec, hda_nid_t nid,
			c->val = parm;
	}
	mutex_unlock(&codec->bus->cmd_mutex);
	snd_hda_power_down(codec);
	return err;
}

@@ -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,
					  seq->param);
}
#endif /* CONFIG_PM */
#endif /* SND_HDA_NEEDS_RESUME */

/*
 * set power state of the 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,
				unsigned int power_state)
{
	hda_nid_t nid, nid_start;
	int nodes;
	hda_nid_t nid;
	int i;

	snd_hda_codec_write(codec, fg, 0, AC_VERB_SET_POWER_STATE,
			    power_state);

	nodes = snd_hda_get_sub_nodes(codec, fg, &nid_start);
	for (nid = nid_start; nid < nodes + nid_start; nid++) {
	nid = codec->start_nid;
	for (i = 0; i < codec->num_nodes; i++, nid++) {
		if (get_wcaps(codec, nid) & AC_WCAP_POWER)
			snd_hda_codec_write(codec, nid, 0,
					    AC_VERB_SET_POWER_STATE,
					    power_state);
	}

	if (power_state == AC_PWRST_D0)
	if (power_state == AC_PWRST_D0) {
		unsigned long end_time;
		int state;
		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
@@ -1611,28 +1696,24 @@ int __devinit snd_hda_build_controls(struct hda_bus *bus)
{
	struct hda_codec *codec;

	/* build controls */
	list_for_each_entry(codec, &bus->codec_list, list) {
		int err;
		if (!codec->patch_ops.build_controls)
			continue;
		err = codec->patch_ops.build_controls(codec);
		if (err < 0)
			return err;
	}

	/* initialize */
	list_for_each_entry(codec, &bus->codec_list, list) {
		int err;
		int err = 0;
		/* fake as if already powered-on */
		hda_keep_power_on(codec);
		/* then fire up */
		hda_set_power_state(codec,
				    codec->afg ? codec->afg : codec->mfg,
				    AC_PWRST_D0);
		if (!codec->patch_ops.init)
			continue;
		/* continue to initialize... */
		if (codec->patch_ops.init)
			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)
			return err;
	}

	return 0;
}

@@ -2101,6 +2182,89 @@ int snd_hda_add_new_ctls(struct hda_codec *codec, struct snd_kcontrol_new *knew)
	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
@@ -2605,41 +2769,32 @@ int snd_hda_suspend(struct hda_bus *bus, pm_message_t state)
{
	struct hda_codec *codec;

	/* FIXME: should handle power widget capabilities */
	list_for_each_entry(codec, &bus->codec_list, list) {
		if (codec->patch_ops.suspend)
			codec->patch_ops.suspend(codec, state);
		hda_set_power_state(codec,
				    codec->afg ? codec->afg : codec->mfg,
				    AC_PWRST_D3);
		hda_call_codec_suspend(codec);
	}
	return 0;
}

#ifndef CONFIG_SND_HDA_POWER_SAVE
/**
 * snd_hda_resume - resume the codecs
 * @bus: the HDA bus
 * @state: resume state
 *
 * 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)
{
	struct hda_codec *codec;

	list_for_each_entry(codec, &bus->codec_list, list) {
		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);
		}
		hda_call_codec_resume(codec);
	}
	return 0;
}
#endif /* !CONFIG_SND_HDA_POWER_SAVE */

#endif
+30 −2
Original line number Diff line number Diff line
@@ -26,6 +26,10 @@
#include <sound/pcm.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
 */
@@ -412,6 +416,10 @@ struct hda_bus_ops {
	unsigned int (*get_response)(struct hda_codec *codec);
	/* free the private data */
	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 */
@@ -473,10 +481,13 @@ struct hda_codec_ops {
	int (*init)(struct hda_codec *codec);
	void (*free)(struct hda_codec *codec);
	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 (*resume)(struct hda_codec *codec);
#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 */
@@ -573,6 +584,12 @@ struct hda_codec {
	unsigned int spdif_in_enable;	/* SPDIF input enable? */

	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 */
@@ -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);

/* 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 direct, unsigned int verb, unsigned int parm);
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);
int snd_hda_resume(struct hda_bus *bus);
#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 */
+50 −5
Original line number Diff line number Diff line
@@ -70,6 +70,13 @@ struct hda_gspec {
	struct hda_pcm pcm_rec;		/* PCM information */

	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)
	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
 */
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];
	int err;
@@ -700,6 +729,8 @@ static int create_mixer(struct hda_codec *codec, struct hda_gnode *node,
	if ((node->wid_caps & AC_WCAP_IN_AMP) &&
	    (node->amp_in_caps & AC_AMPCAP_MUTE)) {
		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);
		if ((err = snd_ctl_add(codec->bus->card, snd_ctl_new1(&knew, codec))) < 0)
			return err;
@@ -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) &&
		   (node->amp_out_caps & AC_AMPCAP_MUTE)) {
		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);
		if ((err = snd_ctl_add(codec->bus->card, snd_ctl_new1(&knew, codec))) < 0)
			return err;
@@ -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++) {
		err = create_mixer(codec, spec->pcm_vol[i].node,
				   spec->pcm_vol[i].index,
				   names[i], "Playback");
				   names[i], "Playback", 0);
		if (err < 0)
			return err;
	}
@@ -782,7 +815,7 @@ static int build_output_controls(struct hda_codec *codec)
	case 1:
		return create_mixer(codec, spec->pcm_vol[0].node,
				    spec->pcm_vol[0].index,
				    "Master", "Playback");
				    "Master", "Playback", 0);
	case 2:
		if (defcfg_type(spec->out_pin_node[0]) == AC_JACK_SPEAKER)
			return create_output_mixers(codec, types_speaker);
@@ -818,7 +851,7 @@ static int build_input_controls(struct hda_codec *codec)
	if (spec->input_mux.num_items == 1) {
		err = create_mixer(codec, adc_node,
				   spec->input_mux.items[0].index,
				   NULL, "Capture");
				   NULL, "Capture", 0);
		if (err < 0)
			return err;
		return 0;
@@ -884,7 +917,8 @@ static int parse_loopback_path(struct hda_codec *codec, struct hda_gspec *spec,
			return err;
		else 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)
					return err;
				if (err > 0)
@@ -1020,6 +1054,14 @@ static int build_generic_pcms(struct hda_codec *codec)
	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 = {
	.build_controls = build_generic_controls,
	.build_pcms = build_generic_pcms,
	.free = snd_hda_generic_free,
#ifdef CONFIG_SND_HDA_POWER_SAVE
	.check_power_status = generic_check_power_status,
#endif
};

/*
+123 −35
Original line number Diff line number Diff line
@@ -75,6 +75,7 @@ MODULE_PARM_DESC(single_cmd, "Use single command to communicate with codecs "
module_param(enable_msi, int, 0);
MODULE_PARM_DESC(enable_msi, "Enable Message Signaled Interrupt (MSI)");

/* power_save option is defined in hda_codec.c */

/* just for backward compatibility */
static int enable;
@@ -101,6 +102,18 @@ MODULE_DESCRIPTION("Intel HDA driver");

#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
 */
@@ -345,6 +358,7 @@ struct azx {

	/* flags */
	int position_fix;
	unsigned int running :1;
	unsigned int initialized :1;
	unsigned int single_cmd :1;
	unsigned int polling_mode :1;
@@ -665,6 +679,9 @@ static unsigned int azx_get_response(struct hda_codec *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 */
static int azx_reset(struct azx *chip)
@@ -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)
{
	unsigned char reg;

	/* 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);
	if (chip->initialized)
		return;

	/* reset controller */
	azx_reset(chip);
@@ -819,22 +829,45 @@ static void azx_init_chip(struct azx *chip)
	azx_writel(chip, DPLBASE, (u32)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) {
	case AZX_DRIVER_ATI:
		/* 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, 
				     &reg);
		pci_write_config_byte(chip->pci,
				      ATI_SB450_HDAUDIO_MISC_CNTR2_ADDR, 
				      (reg & 0xf8) |
				      ATI_SB450_HDAUDIO_ENABLE_SNOOP);
				0x07, ATI_SB450_HDAUDIO_ENABLE_SNOOP);
		break;
	case AZX_DRIVER_NVIDIA:
		/* For NVIDIA HDA, enable snoop */
		pci_read_config_byte(chip->pci,NVIDIA_HDA_TRANSREG_ADDR, &reg);
		pci_write_config_byte(chip->pci,NVIDIA_HDA_TRANSREG_ADDR,
				      (reg & 0xf0) | NVIDIA_HDA_ENABLE_COHBITS);
		update_pci_byte(chip->pci,
				NVIDIA_HDA_TRANSREG_ADDR,
				0x0f, NVIDIA_HDA_ENABLE_COHBITS);
		break;
        }
}
@@ -1007,6 +1040,9 @@ static int __devinit azx_codec_create(struct azx *chip, const char *model)
	bus_temp.pci = chip->pci;
	bus_temp.ops.command = azx_send_cmd;
	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);
	if (err < 0)
@@ -1128,9 +1164,11 @@ static int azx_pcm_open(struct snd_pcm_substream *substream)
				   128);
	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
				   128);
	snd_hda_power_up(apcm->codec);
	err = hinfo->ops.open(hinfo, apcm->codec, substream);
	if (err < 0) {
		azx_release_device(azx_dev);
		snd_hda_power_down(apcm->codec);
		mutex_unlock(&chip->open_mutex);
		return err;
	}
@@ -1159,6 +1197,7 @@ static int azx_pcm_close(struct snd_pcm_substream *substream)
	spin_unlock_irqrestore(&chip->reg_lock, flags);
	azx_release_device(azx_dev);
	hinfo->ops.close(hinfo, apcm->codec, substream);
	snd_hda_power_down(apcm->codec);
	mutex_unlock(&chip->open_mutex);
	return 0;
}
@@ -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
/*
 * power management
@@ -1473,7 +1554,7 @@ static int azx_suspend(struct pci_dev *pci, pm_message_t state)
	for (i = 0; i < chip->pcm_devs; i++)
		snd_pcm_suspend_all(chip->pcm[i]);
	snd_hda_suspend(chip->bus, state);
	azx_free_cmd_io(chip);
	azx_stop_chip(chip);
	if (chip->irq >= 0) {
		synchronize_irq(chip->irq);
		free_irq(chip->irq, chip);
@@ -1506,8 +1587,12 @@ static int azx_resume(struct pci_dev *pci)
			chip->msi = 0;
	if (azx_acquire_irq(chip, 1) < 0)
		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);
	snd_hda_resume(chip->bus);
#endif
	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
	return 0;
}
@@ -1521,20 +1606,9 @@ static int azx_free(struct azx *chip)
{
	if (chip->initialized) {
		int i;

		for (i = 0; i < chip->num_streams; i++)
			azx_stream_stop(chip, &chip->azx_dev[i]);

		/* 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);
		azx_stop_chip(chip);
	}

	if (chip->irq >= 0) {
@@ -1720,10 +1794,9 @@ static int __devinit azx_create(struct snd_card *card, struct pci_dev *pci,
	azx_init_stream(chip);

	/* initialize chip */
	azx_init_pci(chip);
	azx_init_chip(chip);

	chip->initialized = 1;

	/* codec detection */
	if (!chip->codec_mask) {
		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,
	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,
			       const struct pci_device_id *pci_id)
{
@@ -1800,6 +1886,8 @@ static int __devinit azx_probe(struct pci_dev *pci,
	}

	pci_set_drvdata(pci, card);
	chip->running = 1;
	power_down_all_codecs(chip);

	return err;
}
Loading