[ALSA] hda: add sigmatel hp detect support

HDA Codec driver
Adds support for detecting hp insertion/removal and enable/disable of
lineouts based on unsolicited events.

Signed-off-by: Matt <matt@embeddedalley.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h
index 5999156..dd0d99d 100644
--- a/sound/pci/hda/hda_codec.h
+++ b/sound/pci/hda/hda_codec.h
@@ -262,6 +262,9 @@
 #define AC_PINCTL_OUT_EN		(1<<6)
 #define AC_PINCTL_HP_EN			(1<<7)
 
+/* Unsolicited response - 8bit */
+#define AC_USRSP_EN			(1<<7)
+
 /* configuration default - 32bit */
 #define AC_DEFCFG_SEQUENCE		(0xf<<0)
 #define AC_DEFCFG_DEF_ASSOC		(0xf<<4)
diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c
index 07d06f7..9d503da 100644
--- a/sound/pci/hda/patch_sigmatel.c
+++ b/sound/pci/hda/patch_sigmatel.c
@@ -36,6 +36,10 @@
 
 #undef STAC_TEST
 
+#define NUM_CONTROL_ALLOC	32
+#define STAC_HP_EVENT		0x37
+#define STAC_UNSOL_ENABLE 	(AC_USRSP_EN | STAC_HP_EVENT)
+
 struct sigmatel_spec {
 	snd_kcontrol_new_t *mixers[4];
 	unsigned int num_mixers;
@@ -507,8 +511,6 @@
 	return 0;
 }
 
-#define NUM_CONTROL_ALLOC	32
-
 enum {
 	STAC_CTL_WIDGET_VOL,
 	STAC_CTL_WIDGET_MUTE,
@@ -617,10 +619,18 @@
 	hda_nid_t pin = cfg->hp_pin;
 	hda_nid_t nid;
 	int i, err;
+	unsigned int wid_caps;
 
 	if (! pin)
 		return 0;
 
+	wid_caps = snd_hda_param_read(codec, pin, AC_PAR_AUDIO_WIDGET_CAP);
+	if (wid_caps & AC_WCAP_UNSOL_CAP)
+		/* Enable unsolicited responses on the HP widget */
+		snd_hda_codec_write(codec, pin, 0,
+				AC_VERB_SET_UNSOLICITED_ENABLE,
+				STAC_UNSOL_ENABLE);
+
 	nid = snd_hda_codec_read(codec, pin, 0, AC_VERB_GET_CONNECT_LIST, 0) & 0xff;
 	for (i = 0; i < cfg->line_outs; i++) {
 		if (! spec->multiout.dac_nids[i])
@@ -828,6 +838,53 @@
 	kfree(spec);
 }
 
+static void stac92xx_set_pinctl(struct hda_codec *codec, hda_nid_t nid,
+				unsigned int flag)
+{
+	unsigned int pin_ctl = snd_hda_codec_read(codec, nid,
+			0, AC_VERB_GET_PIN_WIDGET_CONTROL, 0x00);
+	snd_hda_codec_write(codec, nid, 0,
+			AC_VERB_SET_PIN_WIDGET_CONTROL,
+			pin_ctl | flag);
+}
+
+static void stac92xx_reset_pinctl(struct hda_codec *codec, hda_nid_t nid,
+				  unsigned int flag)
+{
+	unsigned int pin_ctl = snd_hda_codec_read(codec, nid,
+			0, AC_VERB_GET_PIN_WIDGET_CONTROL, 0x00);
+	snd_hda_codec_write(codec, nid, 0,
+			AC_VERB_SET_PIN_WIDGET_CONTROL,
+			pin_ctl & ~flag);
+}
+
+static void stac92xx_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i, presence;
+
+	if ((res >> 26) != STAC_HP_EVENT)
+		return;
+
+	presence = snd_hda_codec_read(codec, cfg->hp_pin, 0,
+			AC_VERB_GET_PIN_SENSE, 0x00) >> 31;
+
+	if (presence) {
+		/* disable lineouts, enable hp */
+		for (i = 0; i < cfg->line_outs; i++)
+			stac92xx_reset_pinctl(codec, cfg->line_out_pins[i],
+						AC_PINCTL_OUT_EN);
+		stac92xx_set_pinctl(codec, cfg->hp_pin, AC_PINCTL_OUT_EN);
+	} else {
+		/* enable lineouts, disable hp */
+		for (i = 0; i < cfg->line_outs; i++)
+			stac92xx_set_pinctl(codec, cfg->line_out_pins[i],
+						AC_PINCTL_OUT_EN);
+		stac92xx_reset_pinctl(codec, cfg->hp_pin, AC_PINCTL_OUT_EN);
+	}
+} 
+
 #ifdef CONFIG_PM
 static int stac92xx_resume(struct hda_codec *codec)
 {
@@ -851,6 +908,7 @@
 	.build_pcms = stac92xx_build_pcms,
 	.init = stac92xx_init,
 	.free = stac92xx_free,
+	.unsol_event = stac92xx_unsol_event,
 #ifdef CONFIG_PM
 	.resume = stac92xx_resume,
 #endif