From bfcfd3245c56993057b52641baf694ac73501022 Mon Sep 17 00:00:00 2001
From: Raymond Yau <superquad.vortex2@gmail.com>
Date: Wed, 4 May 2011 21:44:28 +0800
Subject: [PATCH ALSA hda] Add Multistreaming Playback using Front Panel Headphone for realtek codec


Signed-off-by: Raymond Yau <superquad.vortex2@gmail.com>

diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
index 4dd0ccc..b1d5e01 100644
--- a/sound/pci/hda/patch_realtek.c
+++ b/sound/pci/hda/patch_realtek.c
@@ -340,6 +340,7 @@ struct alc_spec {
 					 * dig_out_nid and hp_nid are optional
 					 */
 	hda_nid_t alt_dac_nid;
+	int independent_hp;
 	hda_nid_t slave_dig_outs[3];	/* optional - for auto-parsing */
 	int dig_out_type;
 
@@ -1157,7 +1158,8 @@ static void update_speakers(struct hda_codec *codec)
 static void alc_hp_automute(struct hda_codec *codec)
 {
 	struct alc_spec *spec = codec->spec;
-
+	if (spec->independent_hp)
+		return;
 	if (!spec->automute)
 		return;
 	spec->jack_present =
@@ -1645,6 +1647,85 @@ static void alc_init_auto_mic(struct hda_codec *codec)
 	spec->unsol_event = alc_sku_unsol_event;
 }
 
+static int alc_independent_hp_info(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_info *uinfo)
+{
+	static const char * const texts[] = { "OFF", "ON", NULL};
+	int index;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	index = uinfo->value.enumerated.item;
+	if (index >= 2)
+		index = 1;
+	strcpy(uinfo->value.enumerated.name, texts[index]);
+	return 0;
+}
+
+static int alc_independent_hp_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct alc_spec *spec = codec->spec;
+	ucontrol->value.enumerated.item[0] = spec->independent_hp;
+	return 0;
+}
+
+static int alc_independent_hp_put(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct alc_spec *spec = codec->spec;
+	unsigned int select = ucontrol->value.enumerated.item[0];
+	if (spec->independent_hp != select) {
+		spec->independent_hp = select;
+		if (spec->independent_hp)
+			spec->multiout.hp_nid = 0;
+		else
+			spec->multiout.hp_nid = spec->alt_dac_nid;
+		return 1;
+	}
+	return 0;
+}
+
+static const struct snd_kcontrol_new alc_independent_hp_switch = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Independent HP",
+	.info = alc_independent_hp_info,
+	.get = alc_independent_hp_get,
+	.put = alc_independent_hp_put,
+};
+
+/*
+   Multistreaming playback using Headphone at Front Panel
+   Add Independent Headphone control only when there are
+   1) HP at Ext Front and Line Out at Ext Rear
+*/
+static void add_independent_hp(struct hda_codec *codec, hda_nid_t pin,
+				hda_nid_t nid)
+{
+	struct alc_spec *spec = codec->spec;
+	struct snd_kcontrol_new *knew;
+	u32 hp_cfg, lo_cfg;
+	hp_cfg = snd_hda_codec_get_pincfg(codec, pin);
+	lo_cfg = snd_hda_codec_get_pincfg(codec, spec->autocfg.line_out_pins[0]);
+	if ((spec->autocfg.line_outs >= 1) &&
+		(nid != spec->multiout.dac_nids[HDA_FRONT]) &&
+		(get_defcfg_device(hp_cfg) == AC_JACK_HP_OUT) &&
+		(get_defcfg_location(hp_cfg) == AC_JACK_LOC_FRONT) &&
+		(get_defcfg_device(lo_cfg) == AC_JACK_LINE_OUT) &&
+		(get_defcfg_location(lo_cfg) == AC_JACK_LOC_REAR)) {
+
+		spec->alt_dac_nid = nid;
+
+		knew = alc_kcontrol_new(spec);
+		if (knew) {
+			*knew = alc_independent_hp_switch;
+			knew->name = kstrdup("Independent HP", GFP_KERNEL);
+		}
+	}
+}
+
 /* Could be any non-zero and even value. When used as fixup, tells
  * the driver to ignore any present sku defines.
  */
@@ -4138,6 +4219,19 @@ static int alc_check_power_status(struct hda_codec *codec, hda_nid_t nid)
 }
 #endif
 
+static void activate_ctl(struct hda_codec *codec, const char *name, int active)
+{
+	struct snd_kcontrol *ctl = snd_hda_find_mixer_ctl(codec, name);
+	if (ctl) {
+		ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+		ctl->vd[0].access |= active ? 0 : SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+		ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_WRITE;
+		ctl->vd[0].access |= active ? SNDRV_CTL_ELEM_ACCESS_WRITE : 0;
+		snd_ctl_notify(codec->bus->card,
+			       SNDRV_CTL_EVENT_MASK_INFO, &ctl->id);
+	}
+}
+
 /*
  * Analog playback callbacks
  */
@@ -4169,6 +4263,34 @@ static int alc880_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
 	return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
 }
 
+static int alc880_playback_pcm_close(struct hda_pcm_stream *hinfo,
+				     struct hda_codec *codec,
+				     struct snd_pcm_substream *substream)
+{
+	activate_ctl(codec, "Independent HP", 1);
+	return 0;
+}
+
+static int alc880_alt_playback_pcm_open(struct hda_pcm_stream *hinfo,
+				    struct hda_codec *codec,
+				    struct snd_pcm_substream *substream)
+{
+	struct alc_spec *spec = codec->spec;
+	if (spec->independent_hp) {
+		activate_ctl(codec, "Independent HP", 0);
+		return 0;
+	}
+	return -EBUSY;
+}
+
+static int alc880_alt_playback_pcm_close(struct hda_pcm_stream *hinfo,
+					 struct hda_codec *codec,
+					 struct snd_pcm_substream *substream)
+{
+	activate_ctl(codec, "Independent HP", 1);
+	return 0;
+}
+
 /*
  * Digital out
  */
@@ -4280,7 +4402,8 @@ static const struct hda_pcm_stream alc880_pcm_analog_playback = {
 	.ops = {
 		.open = alc880_playback_pcm_open,
 		.prepare = alc880_playback_pcm_prepare,
-		.cleanup = alc880_playback_pcm_cleanup
+		.cleanup = alc880_playback_pcm_cleanup,
+		.close = alc880_playback_pcm_close
 	},
 };
 
@@ -4296,6 +4419,10 @@ static const struct hda_pcm_stream alc880_pcm_analog_alt_playback = {
 	.channels_min = 2,
 	.channels_max = 2,
 	/* NID is set in alc_build_pcms */
+	.ops = {
+		.open = alc880_alt_playback_pcm_open,
+		.close = alc880_alt_playback_pcm_close
+	},
 };
 
 static const struct hda_pcm_stream alc880_pcm_analog_alt_capture = {
@@ -19049,6 +19176,9 @@ static int alc662_auto_create_extra_out(struct hda_codec *codec, hda_nid_t pin,
 	err = alc662_add_sw_ctl(spec, pfx, mix, 3);
 	if (err < 0)
 		return err;
+
+	if (strcmp(pfx, "Headphone") == 0 && spec->autocfg.line_outs == 4)
+		add_independent_hp(codec, pin, nid);
 	return nid;
 }
 
@@ -19500,6 +19630,10 @@ static int patch_alc662(struct hda_codec *codec)
 	spec->stream_analog_playback = &alc662_pcm_analog_playback;
 	spec->stream_analog_capture = &alc662_pcm_analog_capture;
 
+	if (spec->alt_dac_nid)
+		spec->stream_analog_alt_playback =
+			&alc880_pcm_analog_alt_playback;
+
 	spec->stream_digital_playback = &alc662_pcm_digital_playback;
 	spec->stream_digital_capture = &alc662_pcm_digital_capture;
 
-- 
1.6.0.6

