ALSA: hda - Create jack-detection kcontrols
authorTakashi Iwai <tiwai@suse.de>
Thu, 27 Oct 2011 22:03:22 +0000 (00:03 +0200)
committerTakashi Iwai <tiwai@suse.de>
Wed, 16 Nov 2011 10:12:17 +0000 (11:12 +0100)
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
sound/pci/hda/hda_jack.h
sound/pci/hda/patch_cirrus.c
sound/pci/hda/patch_conexant.c
sound/pci/hda/patch_hdmi.c
sound/pci/hda/patch_realtek.c
sound/pci/hda/patch_sigmatel.c
sound/pci/hda/patch_via.c

index 64b78a2e20e0d4c368f5597ddb8770f22e113e98..cee6a00bd85a2311681433196f26da22baf38174 100644 (file)
@@ -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_codec *codec)
 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 hda_codec *codec, hda_nid_t nid,
        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);
index 5c1bcb8cf56535006ad0f387c585c7edcfd00111..b5983eaea51ef0982e0361831902a049924ba21b 100644 (file)
 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(struct hda_codec *codec, hda_nid_t nid)
        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 */
index 6f158777f71ac0d6a9e3a8ab70072ad733d80a58..135fd49cd49d9f25170be48d22041981db13e355 100644 (file)
@@ -1192,11 +1192,14 @@ static int cs_init(struct hda_codec *codec)
        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);
@@ -1211,7 +1214,15 @@ static int cs_build_controls(struct hda_codec *codec)
        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)
@@ -1234,6 +1245,7 @@ static void cs_unsol_event(struct hda_codec *codec, unsigned int res)
                cs_automic(codec);
                break;
        }
+       snd_hda_jack_report_sync(codec);
 }
 
 static const struct hda_codec_ops cs_patch_ops = {
@@ -1611,6 +1623,7 @@ static int cs421x_init(struct hda_codec *codec)
        init_output(codec);
        init_input(codec);
        init_cs421x_digital(codec);
+       snd_hda_jack_report_sync(codec);
 
        return 0;
 }
@@ -1786,6 +1799,7 @@ static int build_cs421x_output(struct hda_codec *codec)
 
 static int cs421x_build_controls(struct hda_codec *codec)
 {
+       struct cs_spec *spec = codec->spec;
        int err;
 
        err = build_cs421x_output(codec);
@@ -1797,7 +1811,15 @@ static int cs421x_build_controls(struct hda_codec *codec)
        err = build_digital_output(codec);
        if (err < 0)
                return err;
-       return cs421x_init(codec);
+       err =  cs421x_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 cs421x_unsol_event(struct hda_codec *codec, unsigned int res)
@@ -1814,6 +1836,7 @@ static void cs421x_unsol_event(struct hda_codec *codec, unsigned int res)
                cs_automic(codec);
                break;
        }
+       snd_hda_jack_report_sync(codec);
 }
 
 static int parse_cs421x_input(struct hda_codec *codec)
index 220e567ccfff2d38ff0d72628fbe9ed60608257b..25fdd1e9561f1c0f185454927afc0e9311d42d5d 100644 (file)
@@ -3770,6 +3770,7 @@ static void cx_auto_unsol_event(struct hda_codec *codec, unsigned int res)
                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;
@@ -4095,6 +4096,7 @@ static int cx_auto_init(struct hda_codec *codec)
        cx_auto_init_output(codec);
        cx_auto_init_input(codec);
        cx_auto_init_digital(codec);
+       snd_hda_jack_report_sync(codec);
        return 0;
 }
 
index ea6d85d48444502b0c93d090f81ae4b2fbdd80c6..f01c5efde8e19c28536f20ef965c7ff4d641c1ed 100644 (file)
@@ -769,6 +769,7 @@ static void hdmi_intrinsic_event(struct hda_codec *codec, unsigned int res)
 
        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)
@@ -1268,6 +1269,10 @@ static int generic_hdmi_build_controls(struct hda_codec *codec)
 
                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;
@@ -1290,6 +1295,7 @@ static int generic_hdmi_init(struct hda_codec *codec)
                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;
 }
 
index da9d2276e68bf5c2287b4cc3f36173a369d529ea..04beae034fea608db37e69d4d1308cd5663359dd 100644 (file)
@@ -677,6 +677,7 @@ static void alc_sku_unsol_event(struct hda_codec *codec, unsigned int res)
                alc_mic_automute(codec);
                break;
        }
+       snd_hda_jack_report_sync(codec);
 }
 
 /* call init functions of standard auto-mute helpers */
@@ -2054,6 +2055,10 @@ static int alc_build_controls(struct hda_codec *codec)
 
        alc_free_kctls(codec); /* no longer needed */
 
+       err = snd_hda_jack_add_kctls(codec, &spec->autocfg);
+       if (err < 0)
+               return err;
+
        return 0;
 }
 
@@ -2081,6 +2086,8 @@ static int alc_init(struct hda_codec *codec)
 
        alc_apply_fixup(codec, ALC_FIXUP_ACT_INIT);
 
+       snd_hda_jack_report_sync(codec);
+
        hda_call_check_power_status(codec, 0x01);
        return 0;
 }
index 97c6df9db5e96552e52478db2b7e538a12cc3f56..90954b8269c3a7e50af88e31df07cb8d7f22759b 100644 (file)
@@ -1212,6 +1212,10 @@ static int stac92xx_build_controls(struct hda_codec *codec)
                        return err;
        }
 
+       err = snd_hda_jack_add_kctls(codec, &spec->autocfg);
+       if (err < 0)
+               return err;
+
        return 0;       
 }
 
@@ -4473,6 +4477,8 @@ static int stac92xx_init(struct hda_codec *codec)
                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);
@@ -4868,6 +4874,7 @@ static void stac92xx_unsol_event(struct hda_codec *codec, unsigned int res)
                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);
index 3467d0c23fe94395b0deb3b2382b0ae3cd8568a7..852939658ddbd8f4f37be12a75328a34880d7903 100644 (file)
@@ -1500,6 +1500,11 @@ static int via_build_controls(struct hda_codec *codec)
        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;
 }
 
@@ -1722,6 +1727,7 @@ static void via_unsol_event(struct hda_codec *codec,
                via_hp_automute(codec);
        else if (res == VIA_GPIO_EVENT)
                via_gpio_control(codec);
+       snd_hda_jack_report_sync(codec);
 }
 
 #ifdef CONFIG_PM
@@ -2771,6 +2777,7 @@ static int via_init(struct hda_codec *codec)
        via_auto_init_unsol_event(codec);
 
        via_hp_automute(codec);
+       snd_hda_jack_report_sync(codec);
 
        return 0;
 }