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 64b78a2..cee6a00 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 5c1bcb8..b5983ea 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 6f15877..135fd49 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 220e567..25fdd1e 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 ea6d85d..f01c5ef 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 da9d227..04beae0 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 97c6df9..90954b8 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 3467d0c..8529396 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;
 }