ALSA: hda - Create jack-detection kcontrols
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>
diff --git a/sound/pci/hda/hda_jack.c b/sound/pci/hda/hda_jack.c
index 64b78a2..cee6a00 100644
--- 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 @@
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 @@
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);
diff --git a/sound/pci/hda/hda_jack.h b/sound/pci/hda/hda_jack.h
index 5c1bcb8..b5983ea 100644
--- 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 @@
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 */
diff --git a/sound/pci/hda/patch_cirrus.c b/sound/pci/hda/patch_cirrus.c
index 6f15877..135fd49 100644
--- a/sound/pci/hda/patch_cirrus.c
+++ b/sound/pci/hda/patch_cirrus.c
@@ -1192,11 +1192,14 @@
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 @@
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 @@
cs_automic(codec);
break;
}
+ snd_hda_jack_report_sync(codec);
}
static const struct hda_codec_ops cs_patch_ops = {
@@ -1611,6 +1623,7 @@
init_output(codec);
init_input(codec);
init_cs421x_digital(codec);
+ snd_hda_jack_report_sync(codec);
return 0;
}
@@ -1786,6 +1799,7 @@
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 @@
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 @@
cs_automic(codec);
break;
}
+ snd_hda_jack_report_sync(codec);
}
static int parse_cs421x_input(struct hda_codec *codec)
diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c
index 220e567..25fdd1e 100644
--- a/sound/pci/hda/patch_conexant.c
+++ b/sound/pci/hda/patch_conexant.c
@@ -3770,6 +3770,7 @@
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 @@
cx_auto_init_output(codec);
cx_auto_init_input(codec);
cx_auto_init_digital(codec);
+ snd_hda_jack_report_sync(codec);
return 0;
}
diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c
index ea6d85d..f01c5ef 100644
--- a/sound/pci/hda/patch_hdmi.c
+++ b/sound/pci/hda/patch_hdmi.c
@@ -769,6 +769,7 @@
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 @@
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 @@
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;
}
diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
index da9d227..04beae0 100644
--- a/sound/pci/hda/patch_realtek.c
+++ b/sound/pci/hda/patch_realtek.c
@@ -677,6 +677,7 @@
alc_mic_automute(codec);
break;
}
+ snd_hda_jack_report_sync(codec);
}
/* call init functions of standard auto-mute helpers */
@@ -2054,6 +2055,10 @@
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 @@
alc_apply_fixup(codec, ALC_FIXUP_ACT_INIT);
+ snd_hda_jack_report_sync(codec);
+
hda_call_check_power_status(codec, 0x01);
return 0;
}
diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c
index 97c6df9..90954b8 100644
--- a/sound/pci/hda/patch_sigmatel.c
+++ b/sound/pci/hda/patch_sigmatel.c
@@ -1212,6 +1212,10 @@
return err;
}
+ err = snd_hda_jack_add_kctls(codec, &spec->autocfg);
+ if (err < 0)
+ return err;
+
return 0;
}
@@ -4473,6 +4477,8 @@
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 @@
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);
diff --git a/sound/pci/hda/patch_via.c b/sound/pci/hda/patch_via.c
index 3467d0c..8529396 100644
--- a/sound/pci/hda/patch_via.c
+++ b/sound/pci/hda/patch_via.c
@@ -1500,6 +1500,11 @@
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 @@
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 @@
via_auto_init_unsol_event(codec);
via_hp_automute(codec);
+ snd_hda_jack_report_sync(codec);
return 0;
}