From 01a61e12b4602c82bde9797d0e153f3e53c95b04 Mon Sep 17 00:00:00 2001
From: Takashi Iwai <tiwai@suse.de>
Date: Fri, 28 Oct 2011 00:03:22 +0200
Subject: [PATCH] ALSA: hda - Create jack-detection kcontrols
Git-commit: 01a61e12b4602c82bde9797d0e153f3e53c95b04
Patch-mainline: 3.3-rc1
References: FATE#314106

Create kcontrols for pin jack-detections, which work similarly like
jack-input layer.  Each control will notify when the jack is plugged or
unplugged, and also user can read the value at any time via the normal
control API.

The control elements are created with iface=CARD, so that they won't
appear in the mixer apps.

So far, only the pins that enabled the jack-detection are registered.
For covering all pins, the transition of the common unsol-tag handling
would be needed.  Stay tuned.

Signed-off-by: Takashi Iwai <tiwai@suse.de>

---
 sound/pci/hda/hda_jack.c       |  168 ++++++++++++++++++++++++++++++++++++++++-
 sound/pci/hda/hda_jack.h       |   12 ++
 sound/pci/hda/patch_cirrus.c   |   14 +++
 sound/pci/hda/patch_conexant.c |    2 
 sound/pci/hda/patch_hdmi.c     |    6 +
 sound/pci/hda/patch_realtek.c  |    7 +
 sound/pci/hda/patch_sigmatel.c |    7 +
 sound/pci/hda/patch_via.c      |    6 +
 8 files changed, 219 insertions(+), 3 deletions(-)

--- a/sound/pci/hda/hda_jack.c
+++ b/sound/pci/hda/hda_jack.c
@@ -12,6 +12,7 @@
 #include <linux/init.h>
 #include <linux/slab.h>
 #include <sound/core.h>
+#include <sound/control.h>
 #include "hda_codec.h"
 #include "hda_local.h"
 #include "hda_jack.h"
@@ -76,9 +77,13 @@ void snd_hda_jack_tbl_clear(struct hda_c
 static void jack_detect_update(struct hda_codec *codec,
 			       struct hda_jack_tbl *jack)
 {
-	if (jack->jack_dirty) {
-		jack->pin_sense = read_pin_sense(codec, jack->nid);
+	if (jack->jack_dirty || !jack->jack_cachable) {
+		unsigned int val = read_pin_sense(codec, jack->nid);
 		jack->jack_dirty = 0;
+		if (val != jack->pin_sense) {
+			jack->need_notify = 1;
+			jack->pin_sense = val;
+		}
 	}
 }
 
@@ -141,8 +146,167 @@ int snd_hda_jack_detect_enable(struct hd
 	struct hda_jack_tbl *jack = snd_hda_jack_tbl_new(codec, nid);
 	if (!jack)
 		return -ENOMEM;
+	if (jack->jack_cachable)
+		return 0; /* already registered */
+	jack->jack_cachable = 1;
 	return snd_hda_codec_write_cache(codec, nid, 0,
 					 AC_VERB_SET_UNSOLICITED_ENABLE,
 					 AC_USRSP_EN | tag);
 }
 EXPORT_SYMBOL_HDA(snd_hda_jack_detect_enable);
+
+/* queue the notification when needed */
+static void jack_detect_report(struct hda_codec *codec,
+			       struct hda_jack_tbl *jack)
+{
+	jack_detect_update(codec, jack);
+	if (jack->need_notify) {
+		snd_ctl_notify(codec->bus->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &jack->kctl->id);
+		jack->need_notify = 0;
+	}
+}
+
+/**
+ * snd_hda_jack_report - notify kctl when the jack state was changed
+ */
+void snd_hda_jack_report(struct hda_codec *codec, hda_nid_t nid)
+{
+	struct hda_jack_tbl *jack = snd_hda_jack_tbl_get(codec, nid);
+
+	if (jack)
+		jack_detect_report(codec, jack);
+}
+EXPORT_SYMBOL_HDA(snd_hda_jack_report);
+
+/**
+ * snd_hda_jack_report_sync - sync the states of all jacks and report if changed
+ */
+void snd_hda_jack_report_sync(struct hda_codec *codec)
+{
+	struct hda_jack_tbl *jack = codec->jacktbl.list;
+	int i;
+
+	for (i = 0; i < codec->jacktbl.used; i++, jack++)
+		if (jack->nid) {
+			jack_detect_update(codec, jack);
+			jack_detect_report(codec, jack);
+		}
+}
+EXPORT_SYMBOL_HDA(snd_hda_jack_report_sync);
+
+/*
+ * jack-detection kcontrols
+ */
+
+#define jack_detect_kctl_info	snd_ctl_boolean_mono_info
+
+static int jack_detect_kctl_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = kcontrol->private_value;
+
+	ucontrol->value.integer.value[0] = snd_hda_jack_detect(codec, nid);
+	return 0;
+}
+
+static struct snd_kcontrol_new jack_detect_kctl = {
+	/* name is filled later */
+	.iface = SNDRV_CTL_ELEM_IFACE_CARD,
+	.access = SNDRV_CTL_ELEM_ACCESS_READ,
+	.info = jack_detect_kctl_info,
+	.get = jack_detect_kctl_get,
+};
+
+/**
+ * snd_hda_jack_add_kctl - Add a kctl for the given pin
+ *
+ * This assigns a jack-detection kctl to the given pin.  The kcontrol
+ * will have the given name and index.
+ */
+int snd_hda_jack_add_kctl(struct hda_codec *codec, hda_nid_t nid,
+			  const char *name, int idx)
+{
+	struct hda_jack_tbl *jack;
+	struct snd_kcontrol *kctl;
+
+	jack = snd_hda_jack_tbl_get(codec, nid);
+	if (!jack)
+		return 0;
+	if (jack->kctl)
+		return 0; /* already created */
+	kctl = snd_ctl_new1(&jack_detect_kctl, codec);
+	if (!kctl)
+		return -ENOMEM;
+	snprintf(kctl->id.name, sizeof(kctl->id.name), "%s Jack", name);
+	kctl->id.index = idx;
+	kctl->private_value = nid;
+	if (snd_hda_ctl_add(codec, nid, kctl) < 0)
+		return -ENOMEM;
+	jack->kctl = kctl;
+	return 0;
+}
+
+static int add_jack_kctl(struct hda_codec *codec, hda_nid_t nid, int idx,
+			 const struct auto_pin_cfg *cfg)
+{
+	if (!nid)
+		return 0;
+	if (!is_jack_detectable(codec, nid))
+		return 0;
+	return snd_hda_jack_add_kctl(codec, nid,
+				     snd_hda_get_pin_label(codec, nid, cfg),
+				     idx);
+}
+
+/**
+ * snd_hda_jack_add_kctls - Add kctls for all pins included in the given pincfg
+ *
+ * As of now, it assigns only to the pins that enabled the detection.
+ * Usually this is called at the end of build_controls callback.
+ */
+int snd_hda_jack_add_kctls(struct hda_codec *codec,
+			   const struct auto_pin_cfg *cfg)
+{
+	const hda_nid_t *p;
+	int i, err;
+
+	for (i = 0, p = cfg->line_out_pins; i < cfg->line_outs; i++, p++) {
+		err = add_jack_kctl(codec, *p, i, cfg);
+		if (err < 0)
+			return err;
+	}
+	for (i = 0, p = cfg->hp_pins; i < cfg->hp_outs; i++, p++) {
+		if (*p == *cfg->line_out_pins) /* might be duplicated */
+			break;
+		err = add_jack_kctl(codec, *p, i, cfg);
+		if (err < 0)
+			return err;
+	}
+	for (i = 0, p = cfg->speaker_pins; i < cfg->speaker_outs; i++, p++) {
+		if (*p == *cfg->line_out_pins) /* might be duplicated */
+			break;
+		err = add_jack_kctl(codec, *p, i, cfg);
+		if (err < 0)
+			return err;
+	}
+	for (i = 0; i < cfg->num_inputs; i++) {
+		err = add_jack_kctl(codec, cfg->inputs[i].pin, 0, cfg);
+		if (err < 0)
+			return err;
+	}
+	for (i = 0, p = cfg->dig_out_pins; i < cfg->dig_outs; i++, p++) {
+		err = add_jack_kctl(codec, *p, i, cfg);
+		if (err < 0)
+			return err;
+	}
+	err = add_jack_kctl(codec, cfg->dig_in_pin, 0, cfg);
+	if (err < 0)
+		return err;
+	err = add_jack_kctl(codec, cfg->mono_out_pin, 0, cfg);
+	if (err < 0)
+		return err;
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_jack_add_kctls);
--- a/sound/pci/hda/hda_jack.h
+++ b/sound/pci/hda/hda_jack.h
@@ -15,7 +15,10 @@
 struct hda_jack_tbl {
 	hda_nid_t nid;
 	unsigned int pin_sense;		/* cached pin-sense value */
+	unsigned int jack_cachable:1;	/* can be updated via unsol events */
 	unsigned int jack_dirty:1;	/* needs to update? */
+	unsigned int need_notify:1;	/* to be notified? */
+	struct snd_kcontrol *kctl;	/* assigned kctl for jack-detection */
 };
 
 struct hda_jack_tbl *
@@ -60,4 +63,13 @@ static inline bool is_jack_detectable(st
 	return true;
 }
 
+int snd_hda_jack_add_kctl(struct hda_codec *codec, hda_nid_t nid,
+			  const char *name, int idx);
+int snd_hda_jack_add_kctls(struct hda_codec *codec,
+			   const struct auto_pin_cfg *cfg);
+
+void snd_hda_jack_report(struct hda_codec *codec, hda_nid_t nid);
+void snd_hda_jack_report_sync(struct hda_codec *codec);
+
+
 #endif /* __SOUND_HDA_JACK_H */
--- a/sound/pci/hda/patch_cirrus.c
+++ b/sound/pci/hda/patch_cirrus.c
@@ -1075,11 +1075,14 @@ static int cs_init(struct hda_codec *cod
 	init_output(codec);
 	init_input(codec);
 	init_digital(codec);
+	snd_hda_jack_report_sync(codec);
+
 	return 0;
 }
 
 static int cs_build_controls(struct hda_codec *codec)
 {
+	struct cs_spec *spec = codec->spec;
 	int err;
 
 	err = build_output(codec);
@@ -1094,7 +1097,15 @@ static int cs_build_controls(struct hda_
 	err = build_digital_input(codec);
 	if (err < 0)
 		return err;
-	return cs_init(codec);
+	err = cs_init(codec);
+	if (err < 0)
+		return err;
+
+	err = snd_hda_jack_add_kctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	return 0;
 }
 
 static void cs_free(struct hda_codec *codec)
@@ -1117,6 +1128,7 @@ static void cs_unsol_event(struct hda_co
 		cs_automic(codec);
 		break;
 	}
+	snd_hda_jack_report_sync(codec);
 }
 
 static const struct hda_codec_ops cs_patch_ops = {
--- a/sound/pci/hda/patch_conexant.c
+++ b/sound/pci/hda/patch_conexant.c
@@ -3778,6 +3778,7 @@ static void cx_auto_unsol_event(struct h
 		snd_hda_input_jack_report(codec, nid);
 		break;
 	}
+	snd_hda_jack_report_sync(codec);
 }
 
 /* check whether the pin config is suitable for auto-mic switching;
@@ -4108,6 +4109,7 @@ static int cx_auto_init(struct hda_codec
 	cx_auto_init_output(codec);
 	cx_auto_init_input(codec);
 	cx_auto_init_digital(codec);
+	snd_hda_jack_report_sync(codec);
 	return 0;
 }
 
--- a/sound/pci/hda/patch_hdmi.c
+++ b/sound/pci/hda/patch_hdmi.c
@@ -770,6 +770,7 @@ static void hdmi_intrinsic_event(struct
 
 	snd_hda_jack_set_dirty(codec, pin_nid);
 	hdmi_present_sense(&spec->pins[pin_idx], true);
+	snd_hda_jack_report_sync(codec);
 }
 
 static void hdmi_non_intrinsic_event(struct hda_codec *codec, unsigned int res)
@@ -1272,6 +1273,10 @@ static int generic_hdmi_build_controls(s
 
 		if (err < 0)
 			return err;
+		err = snd_hda_jack_add_kctl(codec, per_pin->pin_nid,
+					    "HDMI", pin_idx);
+		if (err < 0)
+			return err;
 	}
 
 	return 0;
@@ -1294,6 +1299,7 @@ static int generic_hdmi_init(struct hda_
 		INIT_DELAYED_WORK(&per_pin->work, hdmi_repoll_eld);
 		snd_hda_eld_proc_new(codec, eld, pin_idx);
 	}
+	snd_hda_jack_report_sync(codec);
 	return 0;
 }
 
--- a/sound/pci/hda/patch_realtek.c
+++ b/sound/pci/hda/patch_realtek.c
@@ -683,6 +683,7 @@ static void alc_sku_unsol_event(struct h
 		alc_mic_automute(codec);
 		break;
 	}
+	snd_hda_jack_report_sync(codec);
 }
 
 /* call init functions of standard auto-mute helpers */
@@ -2069,6 +2070,10 @@ static int alc_build_controls(struct hda
 
 	alc_free_kctls(codec); /* no longer needed */
 
+	err = snd_hda_jack_add_kctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
 	return 0;
 }
 
@@ -2096,6 +2101,8 @@ static int alc_init(struct hda_codec *co
 
 	alc_apply_fixup(codec, ALC_FIXUP_ACT_INIT);
 
+	snd_hda_jack_report_sync(codec);
+
 	hda_call_check_power_status(codec, 0x01);
 	return 0;
 }
--- a/sound/pci/hda/patch_sigmatel.c
+++ b/sound/pci/hda/patch_sigmatel.c
@@ -1210,6 +1210,10 @@ static int stac92xx_build_controls(struc
 			return err;
 	}
 
+	err = snd_hda_jack_add_kctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
 	return 0;	
 }
 
@@ -4474,6 +4478,8 @@ static int stac92xx_init(struct hda_code
 		stac_toggle_power_map(codec, nid, 0);
 	}
 
+	snd_hda_jack_report_sync(codec);
+
 	/* sync mute LED */
 	if (spec->gpio_led)
 		hda_call_check_power_status(codec, 0x01);
@@ -4869,6 +4875,7 @@ static void stac92xx_unsol_event(struct
 		return;
 	snd_hda_jack_set_dirty(codec, event->nid);
 	handle_unsol_event(codec, event);
+	snd_hda_jack_report_sync(codec);
 }
 
 static int hp_blike_system(u32 subsystem_id);
--- a/sound/pci/hda/patch_via.c
+++ b/sound/pci/hda/patch_via.c
@@ -1503,6 +1503,11 @@ static int via_build_controls(struct hda
 	analog_low_current_mode(codec);
 
 	via_free_kctls(codec); /* no longer needed */
+
+	err = snd_hda_jack_add_kctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
 	return 0;
 }
 
@@ -1726,6 +1731,7 @@ static void via_unsol_event(struct hda_c
 		via_hp_automute(codec);
 	else if (res == VIA_GPIO_EVENT)
 		via_gpio_control(codec);
+	snd_hda_jack_report_sync(codec);
 }
 
 #ifdef CONFIG_PM
