Commit 3b607e3d authored by Takashi Iwai's avatar Takashi Iwai
Browse files

ALSA: hda - Switch HP DAC dynamically with indep-HP switch for VIA



This patch changes the behavior of independent-HP enum switch.  Now
instead of returning a busy error, the driver switches dynamically the
stream of the HP (and shared) DACs according to the current mode.
The logic is similar like the dual-mic ADC switch, but a bit more
complicated because of the presence of shared DAC.

Together with the change, a mutex is introduced to protect against the
possible races for the indep-HP mode setting.

Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent 3214b966
Loading
Loading
Loading
Loading
+125 −34
Original line number Original line Diff line number Diff line
@@ -109,6 +109,11 @@ struct via_input {


#define VIA_MAX_ADCS	3
#define VIA_MAX_ADCS	3


enum {
	STREAM_MULTI_OUT = (1 << 0),
	STREAM_INDEP_HP = (1 << 1),
};

struct via_spec {
struct via_spec {
	/* codec parameterization */
	/* codec parameterization */
	const struct snd_kcontrol_new *mixers[6];
	const struct snd_kcontrol_new *mixers[6];
@@ -132,7 +137,8 @@ struct via_spec {
	hda_nid_t hp_dac_nid;
	hda_nid_t hp_dac_nid;
	hda_nid_t speaker_dac_nid;
	hda_nid_t speaker_dac_nid;
	int hp_indep_shared;	/* indep HP-DAC is shared with side ch */
	int hp_indep_shared;	/* indep HP-DAC is shared with side ch */
	int num_active_streams;
	int opened_streams;	/* STREAM_* bits */
	int active_streams;	/* STREAM_* bits */
	int aamix_mode;		/* loopback is enabled for output-path? */
	int aamix_mode;		/* loopback is enabled for output-path? */


	/* Output-paths:
	/* Output-paths:
@@ -166,6 +172,12 @@ struct via_spec {
	struct via_input inputs[AUTO_CFG_MAX_INS + 1];
	struct via_input inputs[AUTO_CFG_MAX_INS + 1];
	unsigned int cur_mux[VIA_MAX_ADCS];
	unsigned int cur_mux[VIA_MAX_ADCS];


	/* dynamic DAC switching */
	unsigned int cur_dac_stream_tag;
	unsigned int cur_dac_format;
	unsigned int cur_hp_stream_tag;
	unsigned int cur_hp_format;

	/* dynamic ADC switching */
	/* dynamic ADC switching */
	hda_nid_t cur_adc;
	hda_nid_t cur_adc;
	unsigned int cur_adc_stream_tag;
	unsigned int cur_adc_stream_tag;
@@ -207,6 +219,8 @@ struct via_spec {
	/* bind capture-volume */
	/* bind capture-volume */
	struct hda_bind_ctls *bind_cap_vol;
	struct hda_bind_ctls *bind_cap_vol;
	struct hda_bind_ctls *bind_cap_sw;
	struct hda_bind_ctls *bind_cap_sw;

	struct mutex config_mutex;
};
};


static enum VIA_HDA_CODEC get_codec_type(struct hda_codec *codec);
static enum VIA_HDA_CODEC get_codec_type(struct hda_codec *codec);
@@ -218,6 +232,7 @@ static struct via_spec * via_new_spec(struct hda_codec *codec)
	if (spec == NULL)
	if (spec == NULL)
		return NULL;
		return NULL;


	mutex_init(&spec->config_mutex);
	codec->spec = spec;
	codec->spec = spec;
	spec->codec = codec;
	spec->codec = codec;
	spec->codec_type = get_codec_type(codec);
	spec->codec_type = get_codec_type(codec);
@@ -756,6 +771,67 @@ static int via_independent_hp_get(struct snd_kcontrol *kcontrol,
	return 0;
	return 0;
}
}


/* adjust spec->multiout setup according to the current flags */
static void setup_playback_multi_pcm(struct via_spec *spec)
{
	const struct auto_pin_cfg *cfg = &spec->autocfg;
	spec->multiout.num_dacs = cfg->line_outs + spec->smart51_nums;
	spec->multiout.hp_nid = 0;
	if (!spec->hp_independent_mode) {
		if (!spec->hp_indep_shared)
			spec->multiout.hp_nid = spec->hp_dac_nid;
	} else {
		if (spec->hp_indep_shared)
			spec->multiout.num_dacs = cfg->line_outs - 1;
	}
}

/* update DAC setups according to indep-HP switch;
 * this function is called only when indep-HP is modified
 */
static void switch_indep_hp_dacs(struct hda_codec *codec)
{
	struct via_spec *spec = codec->spec;
	int shared = spec->hp_indep_shared;
	hda_nid_t shared_dac, hp_dac;

	if (!spec->opened_streams)
		return;

	shared_dac = shared ? spec->multiout.dac_nids[shared] : 0;
	hp_dac = spec->hp_dac_nid;
	if (spec->hp_independent_mode) {
		/* switch to indep-HP mode */
		if (spec->active_streams & STREAM_MULTI_OUT) {
			__snd_hda_codec_cleanup_stream(codec, hp_dac, 1);
			__snd_hda_codec_cleanup_stream(codec, shared_dac, 1);
		}
		if (spec->active_streams & STREAM_INDEP_HP)
			snd_hda_codec_setup_stream(codec, hp_dac,
						   spec->cur_hp_stream_tag, 0,
						   spec->cur_hp_format);
	} else {
		/* back to HP or shared-DAC */
		if (spec->active_streams & STREAM_INDEP_HP)
			__snd_hda_codec_cleanup_stream(codec, hp_dac, 1);
		if (spec->active_streams & STREAM_MULTI_OUT) {
			hda_nid_t dac;
			int ch;
			if (shared_dac) { /* reset mutli-ch DAC */
				dac = shared_dac;
				ch = shared * 2;
			} else { /* reset HP DAC */
				dac = hp_dac;
				ch = 0;
			}
			snd_hda_codec_setup_stream(codec, dac,
						   spec->cur_dac_stream_tag, ch,
						   spec->cur_dac_format);
		}
	}
	setup_playback_multi_pcm(spec);
}

static int via_independent_hp_put(struct snd_kcontrol *kcontrol,
static int via_independent_hp_put(struct snd_kcontrol *kcontrol,
				  struct snd_ctl_elem_value *ucontrol)
				  struct snd_ctl_elem_value *ucontrol)
{
{
@@ -763,13 +839,12 @@ static int via_independent_hp_put(struct snd_kcontrol *kcontrol,
	struct via_spec *spec = codec->spec;
	struct via_spec *spec = codec->spec;
	int cur, shared;
	int cur, shared;


	/* no independent-hp status change during PCM playback is running */
	mutex_lock(&spec->config_mutex);
	if (spec->num_active_streams)
		return -EBUSY;

	cur = !!ucontrol->value.enumerated.item[0];
	cur = !!ucontrol->value.enumerated.item[0];
	if (spec->hp_independent_mode == cur)
	if (spec->hp_independent_mode == cur) {
		mutex_unlock(&spec->config_mutex);
		return 0;
		return 0;
	}
	spec->hp_independent_mode = cur;
	spec->hp_independent_mode = cur;
	shared = spec->hp_indep_shared;
	shared = spec->hp_indep_shared;
	if (cur) {
	if (cur) {
@@ -786,6 +861,9 @@ static int via_independent_hp_put(struct snd_kcontrol *kcontrol,
		activate_output_path(codec, &spec->hp_mix_path, true, false);
		activate_output_path(codec, &spec->hp_mix_path, true, false);
	}
	}


	switch_indep_hp_dacs(codec);
	mutex_unlock(&spec->config_mutex);

	/* update jack power state */
	/* update jack power state */
	set_widgets_power_state(codec);
	set_widgets_power_state(codec);
	via_hp_automute(codec);
	via_hp_automute(codec);
@@ -948,7 +1026,7 @@ static void analog_low_current_mode(struct hda_codec *codec)
	bool enable;
	bool enable;
	unsigned int verb, parm;
	unsigned int verb, parm;


	enable = is_aa_path_mute(codec) && (spec->num_active_streams > 0);
	enable = is_aa_path_mute(codec) && (spec->opened_streams != 0);


	/* decide low current mode's verb & parameter */
	/* decide low current mode's verb & parameter */
	switch (spec->codec_type) {
	switch (spec->codec_type) {
@@ -989,14 +1067,14 @@ static const struct hda_verb vt1708_init_verbs[] = {
	{ }
	{ }
};
};


static void set_stream_active(struct hda_codec *codec, bool active)
static void set_stream_open(struct hda_codec *codec, int bit, bool active)
{
{
	struct via_spec *spec = codec->spec;
	struct via_spec *spec = codec->spec;


	if (active)
	if (active)
		spec->num_active_streams++;
		spec->opened_streams |= bit;
	else
	else
		spec->num_active_streams--;
		spec->opened_streams &= ~bit;
	analog_low_current_mode(codec);
	analog_low_current_mode(codec);
}
}


@@ -1008,22 +1086,13 @@ static int via_playback_multi_pcm_open(struct hda_pcm_stream *hinfo,
	const struct auto_pin_cfg *cfg = &spec->autocfg;
	const struct auto_pin_cfg *cfg = &spec->autocfg;
	int err;
	int err;


	spec->multiout.hp_nid = 0;
	spec->multiout.num_dacs = cfg->line_outs + spec->smart51_nums;
	spec->multiout.num_dacs = cfg->line_outs + spec->smart51_nums;
	if (!spec->hp_independent_mode) {
		if (!spec->hp_indep_shared)
			spec->multiout.hp_nid = spec->hp_dac_nid;
	} else {
		if (spec->hp_indep_shared)
			spec->multiout.num_dacs = cfg->line_outs - 1;
	}
	spec->multiout.max_channels = spec->multiout.num_dacs * 2;
	spec->multiout.max_channels = spec->multiout.num_dacs * 2;
	set_stream_active(codec, true);
	set_stream_open(codec, STREAM_MULTI_OUT, true);
	err = snd_hda_multi_out_analog_open(codec, &spec->multiout, substream,
	err = snd_hda_multi_out_analog_open(codec, &spec->multiout, substream,
					    hinfo);
					    hinfo);
	if (err < 0) {
	if (err < 0) {
		spec->multiout.hp_nid = 0;
		set_stream_open(codec, STREAM_MULTI_OUT, false);
		set_stream_active(codec, false);
		return err;
		return err;
	}
	}
	return 0;
	return 0;
@@ -1033,10 +1102,7 @@ static int via_playback_multi_pcm_close(struct hda_pcm_stream *hinfo,
				  struct hda_codec *codec,
				  struct hda_codec *codec,
				  struct snd_pcm_substream *substream)
				  struct snd_pcm_substream *substream)
{
{
	struct via_spec *spec = codec->spec;
	set_stream_open(codec, STREAM_MULTI_OUT, false);

	spec->multiout.hp_nid = 0;
	set_stream_active(codec, false);
	return 0;
	return 0;
}
}


@@ -1048,9 +1114,7 @@ static int via_playback_hp_pcm_open(struct hda_pcm_stream *hinfo,


	if (snd_BUG_ON(!spec->hp_dac_nid))
	if (snd_BUG_ON(!spec->hp_dac_nid))
		return -EINVAL;
		return -EINVAL;
	if (!spec->hp_independent_mode || spec->multiout.hp_nid)
	set_stream_open(codec, STREAM_INDEP_HP, true);
		return -EBUSY;
	set_stream_active(codec, true);
	return 0;
	return 0;
}
}


@@ -1058,7 +1122,7 @@ static int via_playback_hp_pcm_close(struct hda_pcm_stream *hinfo,
				     struct hda_codec *codec,
				     struct hda_codec *codec,
				     struct snd_pcm_substream *substream)
				     struct snd_pcm_substream *substream)
{
{
	set_stream_active(codec, false);
	set_stream_open(codec, STREAM_INDEP_HP, false);
	return 0;
	return 0;
}
}


@@ -1070,8 +1134,15 @@ static int via_playback_multi_pcm_prepare(struct hda_pcm_stream *hinfo,
{
{
	struct via_spec *spec = codec->spec;
	struct via_spec *spec = codec->spec;


	mutex_lock(&spec->config_mutex);
	setup_playback_multi_pcm(spec);
	snd_hda_multi_out_analog_prepare(codec, &spec->multiout, stream_tag,
	snd_hda_multi_out_analog_prepare(codec, &spec->multiout, stream_tag,
					 format, substream);
					 format, substream);
	/* remember for dynamic DAC switch with indep-HP */
	spec->active_streams |= STREAM_MULTI_OUT;
	spec->cur_dac_stream_tag = stream_tag;
	spec->cur_dac_format = format;
	mutex_unlock(&spec->config_mutex);
	vt1708_start_hp_work(spec);
	vt1708_start_hp_work(spec);
	return 0;
	return 0;
}
}
@@ -1084,8 +1155,14 @@ static int via_playback_hp_pcm_prepare(struct hda_pcm_stream *hinfo,
{
{
	struct via_spec *spec = codec->spec;
	struct via_spec *spec = codec->spec;


	mutex_lock(&spec->config_mutex);
	if (spec->hp_independent_mode)
		snd_hda_codec_setup_stream(codec, spec->hp_dac_nid,
		snd_hda_codec_setup_stream(codec, spec->hp_dac_nid,
					   stream_tag, 0, format);
					   stream_tag, 0, format);
	spec->active_streams |= STREAM_INDEP_HP;
	spec->cur_hp_stream_tag = stream_tag;
	spec->cur_hp_format = format;
	mutex_unlock(&spec->config_mutex);
	vt1708_start_hp_work(spec);
	vt1708_start_hp_work(spec);
	return 0;
	return 0;
}
}
@@ -1096,7 +1173,10 @@ static int via_playback_multi_pcm_cleanup(struct hda_pcm_stream *hinfo,
{
{
	struct via_spec *spec = codec->spec;
	struct via_spec *spec = codec->spec;


	mutex_lock(&spec->config_mutex);
	snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
	snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
	spec->active_streams &= ~STREAM_MULTI_OUT;
	mutex_unlock(&spec->config_mutex);
	vt1708_stop_hp_work(spec);
	vt1708_stop_hp_work(spec);
	return 0;
	return 0;
}
}
@@ -1107,7 +1187,11 @@ static int via_playback_hp_pcm_cleanup(struct hda_pcm_stream *hinfo,
{
{
	struct via_spec *spec = codec->spec;
	struct via_spec *spec = codec->spec;


	mutex_lock(&spec->config_mutex);
	if (spec->hp_independent_mode)
		snd_hda_codec_setup_stream(codec, spec->hp_dac_nid, 0, 0, 0);
		snd_hda_codec_setup_stream(codec, spec->hp_dac_nid, 0, 0, 0);
	spec->active_streams &= ~STREAM_INDEP_HP;
	mutex_unlock(&spec->config_mutex);
	vt1708_stop_hp_work(spec);
	vt1708_stop_hp_work(spec);
	return 0;
	return 0;
}
}
@@ -1186,10 +1270,12 @@ static int via_dyn_adc_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
	struct via_spec *spec = codec->spec;
	struct via_spec *spec = codec->spec;
	int adc_idx = spec->inputs[spec->cur_mux[0]].adc_idx;
	int adc_idx = spec->inputs[spec->cur_mux[0]].adc_idx;


	mutex_lock(&spec->config_mutex);
	spec->cur_adc = spec->adc_nids[adc_idx];
	spec->cur_adc = spec->adc_nids[adc_idx];
	spec->cur_adc_stream_tag = stream_tag;
	spec->cur_adc_stream_tag = stream_tag;
	spec->cur_adc_format = format;
	spec->cur_adc_format = format;
	snd_hda_codec_setup_stream(codec, spec->cur_adc, stream_tag, 0, format);
	snd_hda_codec_setup_stream(codec, spec->cur_adc, stream_tag, 0, format);
	mutex_unlock(&spec->config_mutex);
	return 0;
	return 0;
}
}


@@ -1199,8 +1285,10 @@ static int via_dyn_adc_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
{
{
	struct via_spec *spec = codec->spec;
	struct via_spec *spec = codec->spec;


	mutex_lock(&spec->config_mutex);
	snd_hda_codec_cleanup_stream(codec, spec->cur_adc);
	snd_hda_codec_cleanup_stream(codec, spec->cur_adc);
	spec->cur_adc = 0;
	spec->cur_adc = 0;
	mutex_unlock(&spec->config_mutex);
	return 0;
	return 0;
}
}


@@ -1210,7 +1298,9 @@ static bool via_dyn_adc_pcm_resetup(struct hda_codec *codec, int cur)
	struct via_spec *spec = codec->spec;
	struct via_spec *spec = codec->spec;
	int adc_idx = spec->inputs[cur].adc_idx;
	int adc_idx = spec->inputs[cur].adc_idx;
	hda_nid_t adc = spec->adc_nids[adc_idx];
	hda_nid_t adc = spec->adc_nids[adc_idx];
	bool ret = false;


	mutex_lock(&spec->config_mutex);
	if (spec->cur_adc && spec->cur_adc != adc) {
	if (spec->cur_adc && spec->cur_adc != adc) {
		/* stream is running, let's swap the current ADC */
		/* stream is running, let's swap the current ADC */
		__snd_hda_codec_cleanup_stream(codec, spec->cur_adc, 1);
		__snd_hda_codec_cleanup_stream(codec, spec->cur_adc, 1);
@@ -1218,9 +1308,10 @@ static bool via_dyn_adc_pcm_resetup(struct hda_codec *codec, int cur)
		snd_hda_codec_setup_stream(codec, adc,
		snd_hda_codec_setup_stream(codec, adc,
					   spec->cur_adc_stream_tag, 0,
					   spec->cur_adc_stream_tag, 0,
					   spec->cur_adc_format);
					   spec->cur_adc_format);
		return true;
		ret = true;
	}
	}
	return false;
	mutex_unlock(&spec->config_mutex);
	return ret;
}
}


static const struct hda_pcm_stream via_pcm_analog_playback = {
static const struct hda_pcm_stream via_pcm_analog_playback = {