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>
This commit is contained in:
Takashi Iwai 2011-10-28 00:03:22 +02:00
parent 1835a0f9a2
commit 01a61e12b4
8 changed files with 232 additions and 4 deletions

View 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);

View File

@ -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(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 */

View 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)

View 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;
}

View 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;
}

View 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;
}

View 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);

View 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;
}