diff options
Diffstat (limited to 'sound/pci/hda/hda_jack.c')
| -rw-r--r-- | sound/pci/hda/hda_jack.c | 307 |
1 files changed, 247 insertions, 60 deletions
diff --git a/sound/pci/hda/hda_jack.c b/sound/pci/hda/hda_jack.c index 9d819c4b492..9746d73cec5 100644 --- a/sound/pci/hda/hda_jack.c +++ b/sound/pci/hda/hda_jack.c @@ -17,12 +17,30 @@ #include <sound/jack.h> #include "hda_codec.h" #include "hda_local.h" +#include "hda_auto_parser.h" #include "hda_jack.h" +bool is_jack_detectable(struct hda_codec *codec, hda_nid_t nid) +{ + if (codec->no_jack_detect) + return false; + if (!(snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_PRES_DETECT)) + return false; + if (get_defcfg_misc(snd_hda_codec_get_pincfg(codec, nid)) & + AC_DEFCFG_MISC_NO_PRESENCE) + return false; + if (!(get_wcaps(codec, nid) & AC_WCAP_UNSOL_CAP) && + !codec->jackpoll_interval) + return false; + return true; +} +EXPORT_SYMBOL_GPL(is_jack_detectable); + /* execute pin sense measurement */ static u32 read_pin_sense(struct hda_codec *codec, hda_nid_t nid) { u32 pincap; + u32 val; if (!codec->no_trigger_sense) { pincap = snd_hda_query_pin_caps(codec, nid); @@ -30,8 +48,11 @@ static u32 read_pin_sense(struct hda_codec *codec, hda_nid_t nid) snd_hda_codec_read(codec, nid, 0, AC_VERB_SET_PIN_SENSE, 0); } - return snd_hda_codec_read(codec, nid, 0, + val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_PIN_SENSE, 0); + if (codec->inv_jack_detect) + val ^= AC_PINSENSE_PRESENCE; + return val; } /** @@ -50,7 +71,7 @@ snd_hda_jack_tbl_get(struct hda_codec *codec, hda_nid_t nid) return jack; return NULL; } -EXPORT_SYMBOL_HDA(snd_hda_jack_tbl_get); +EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get); /** * snd_hda_jack_tbl_get_from_tag - query the jack-table entry for the given tag @@ -68,7 +89,7 @@ snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec, unsigned char tag) return jack; return NULL; } -EXPORT_SYMBOL_HDA(snd_hda_jack_tbl_get_from_tag); +EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get_from_tag); /** * snd_hda_jack_tbl_new - create a jack-table entry for the given NID @@ -79,7 +100,6 @@ snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid) struct hda_jack_tbl *jack = snd_hda_jack_tbl_get(codec, nid); if (jack) return jack; - snd_array_init(&codec->jacktbl, sizeof(*jack), 16); jack = snd_array_new(&codec->jacktbl); if (!jack) return NULL; @@ -88,7 +108,7 @@ snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid) jack->tag = codec->jacktbl.used; return jack; } -EXPORT_SYMBOL_HDA(snd_hda_jack_tbl_new); +EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_new); void snd_hda_jack_tbl_clear(struct hda_codec *codec) { @@ -106,13 +126,34 @@ void snd_hda_jack_tbl_clear(struct hda_codec *codec) snd_array_free(&codec->jacktbl); } +#define get_jack_plug_state(sense) !!(sense & AC_PINSENSE_PRESENCE) + /* update the cached value and notification flag if needed */ static void jack_detect_update(struct hda_codec *codec, struct hda_jack_tbl *jack) { - if (jack->jack_dirty || !jack->jack_detect) { + if (!jack->jack_dirty) + return; + + if (jack->phantom_jack) + jack->pin_sense = AC_PINSENSE_PRESENCE; + else jack->pin_sense = read_pin_sense(codec, jack->nid); - jack->jack_dirty = 0; + + /* A gating jack indicates the jack is invalid if gating is unplugged */ + if (jack->gating_jack && !snd_hda_jack_detect(codec, jack->gating_jack)) + jack->pin_sense &= ~AC_PINSENSE_PRESENCE; + + jack->jack_dirty = 0; + + /* If a jack is gated by this one update it. */ + if (jack->gated_jack) { + struct hda_jack_tbl *gated = + snd_hda_jack_tbl_get(codec, jack->gated_jack); + if (gated) { + gated->jack_dirty = 1; + jack_detect_update(codec, gated); + } } } @@ -131,7 +172,7 @@ void snd_hda_jack_set_dirty_all(struct hda_codec *codec) if (jack->nid) jack->jack_dirty = 1; } -EXPORT_SYMBOL_HDA(snd_hda_jack_set_dirty_all); +EXPORT_SYMBOL_GPL(snd_hda_jack_set_dirty_all); /** * snd_hda_pin_sense - execute pin sense measurement @@ -150,29 +191,34 @@ u32 snd_hda_pin_sense(struct hda_codec *codec, hda_nid_t nid) } return read_pin_sense(codec, nid); } -EXPORT_SYMBOL_HDA(snd_hda_pin_sense); - -#define get_jack_plug_state(sense) !!(sense & AC_PINSENSE_PRESENCE) +EXPORT_SYMBOL_GPL(snd_hda_pin_sense); /** - * snd_hda_jack_detect - query pin Presence Detect status + * snd_hda_jack_detect_state - query pin Presence Detect status * @codec: the CODEC to sense * @nid: the pin NID to sense * - * Query and return the pin's Presence Detect status. + * Query and return the pin's Presence Detect status, as either + * HDA_JACK_NOT_PRESENT, HDA_JACK_PRESENT or HDA_JACK_PHANTOM. */ -int snd_hda_jack_detect(struct hda_codec *codec, hda_nid_t nid) +int snd_hda_jack_detect_state(struct hda_codec *codec, hda_nid_t nid) { - u32 sense = snd_hda_pin_sense(codec, nid); - return get_jack_plug_state(sense); + struct hda_jack_tbl *jack = snd_hda_jack_tbl_get(codec, nid); + if (jack && jack->phantom_jack) + return HDA_JACK_PHANTOM; + else if (snd_hda_pin_sense(codec, nid) & AC_PINSENSE_PRESENCE) + return HDA_JACK_PRESENT; + else + return HDA_JACK_NOT_PRESENT; } -EXPORT_SYMBOL_HDA(snd_hda_jack_detect); +EXPORT_SYMBOL_GPL(snd_hda_jack_detect_state); /** * snd_hda_jack_detect_enable - enable the jack-detection */ -int snd_hda_jack_detect_enable(struct hda_codec *codec, hda_nid_t nid, - unsigned char action) +int snd_hda_jack_detect_enable_callback(struct hda_codec *codec, hda_nid_t nid, + unsigned char action, + hda_jack_callback cb) { struct hda_jack_tbl *jack = snd_hda_jack_tbl_new(codec, nid); if (!jack) @@ -182,24 +228,65 @@ int snd_hda_jack_detect_enable(struct hda_codec *codec, hda_nid_t nid, jack->jack_detect = 1; if (action) jack->action = action; + if (cb) + jack->callback = cb; + if (codec->jackpoll_interval > 0) + return 0; /* No unsol if we're polling instead */ return snd_hda_codec_write_cache(codec, nid, 0, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | jack->tag); } -EXPORT_SYMBOL_HDA(snd_hda_jack_detect_enable); +EXPORT_SYMBOL_GPL(snd_hda_jack_detect_enable_callback); + +int snd_hda_jack_detect_enable(struct hda_codec *codec, hda_nid_t nid, + unsigned char action) +{ + return snd_hda_jack_detect_enable_callback(codec, nid, action, NULL); +} +EXPORT_SYMBOL_GPL(snd_hda_jack_detect_enable); + +/** + * snd_hda_jack_set_gating_jack - Set gating jack. + * + * Indicates the gated jack is only valid when the gating jack is plugged. + */ +int snd_hda_jack_set_gating_jack(struct hda_codec *codec, hda_nid_t gated_nid, + hda_nid_t gating_nid) +{ + struct hda_jack_tbl *gated = snd_hda_jack_tbl_new(codec, gated_nid); + struct hda_jack_tbl *gating = snd_hda_jack_tbl_new(codec, gating_nid); + + if (!gated || !gating) + return -EINVAL; + + gated->gating_jack = gating_nid; + gating->gated_jack = gated_nid; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_jack_set_gating_jack); /** * 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; + struct hda_jack_tbl *jack; int i, state; + /* update all jacks at first */ + jack = codec->jacktbl.list; for (i = 0; i < codec->jacktbl.used; i++, jack++) - if (jack->nid) { + if (jack->nid) jack_detect_update(codec, jack); - if (!jack->kctl) + + /* report the updated jacks; it's done after updating all jacks + * to make sure that all gating jacks properly have been set + */ + jack = codec->jacktbl.list; + for (i = 0; i < codec->jacktbl.used; i++, jack++) + if (jack->nid) { + if (!jack->kctl || jack->block_report) continue; state = get_jack_plug_state(jack->pin_sense); snd_kctl_jack_report(codec->bus->card, jack->kctl, state); @@ -210,7 +297,7 @@ void snd_hda_jack_report_sync(struct hda_codec *codec) #endif } } -EXPORT_SYMBOL_HDA(snd_hda_jack_report_sync); +EXPORT_SYMBOL_GPL(snd_hda_jack_report_sync); #ifdef CONFIG_SND_HDA_INPUT_JACK /* guess the jack type from the pin-config */ @@ -247,8 +334,8 @@ static void hda_free_jack_priv(struct snd_jack *jack) * 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) +static int __snd_hda_jack_add_kctl(struct hda_codec *codec, hda_nid_t nid, + const char *name, int idx, bool phantom_jack) { struct hda_jack_tbl *jack; struct snd_kcontrol *kctl; @@ -266,47 +353,86 @@ int snd_hda_jack_add_kctl(struct hda_codec *codec, hda_nid_t nid, if (err < 0) return err; jack->kctl = kctl; + jack->phantom_jack = !!phantom_jack; + state = snd_hda_jack_detect(codec, nid); snd_kctl_jack_report(codec->bus->card, kctl, state); #ifdef CONFIG_SND_HDA_INPUT_JACK - jack->type = get_input_jack_type(codec, nid); - err = snd_jack_new(codec->bus->card, name, jack->type, &jack->jack); - if (err < 0) - return err; - jack->jack->private_data = jack; - jack->jack->private_free = hda_free_jack_priv; - snd_jack_report(jack->jack, state ? jack->type : 0); + if (!phantom_jack) { + jack->type = get_input_jack_type(codec, nid); + err = snd_jack_new(codec->bus->card, name, jack->type, + &jack->jack); + if (err < 0) + return err; + jack->jack->private_data = jack; + jack->jack->private_free = hda_free_jack_priv; + snd_jack_report(jack->jack, state ? jack->type : 0); + } #endif return 0; } -EXPORT_SYMBOL_HDA(snd_hda_jack_add_kctl); + +int snd_hda_jack_add_kctl(struct hda_codec *codec, hda_nid_t nid, + const char *name, int idx) +{ + return __snd_hda_jack_add_kctl(codec, nid, name, idx, false); +} +EXPORT_SYMBOL_GPL(snd_hda_jack_add_kctl); + +/* get the unique index number for the given kctl name */ +static int get_unique_index(struct hda_codec *codec, const char *name, int idx) +{ + struct hda_jack_tbl *jack; + int i, len = strlen(name); + again: + jack = codec->jacktbl.list; + for (i = 0; i < codec->jacktbl.used; i++, jack++) { + /* jack->kctl.id contains "XXX Jack" name string with index */ + if (jack->kctl && + !strncmp(name, jack->kctl->id.name, len) && + !strcmp(" Jack", jack->kctl->id.name + len) && + jack->kctl->id.index == idx) { + idx++; + goto again; + } + } + return idx; +} static int add_jack_kctl(struct hda_codec *codec, hda_nid_t nid, const struct auto_pin_cfg *cfg, - char *lastname, int *lastidx) + const char *base_name) { unsigned int def_conf, conn; - char name[44]; + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; int idx, err; + bool phantom_jack; if (!nid) return 0; - if (!is_jack_detectable(codec, nid)) - return 0; def_conf = snd_hda_codec_get_pincfg(codec, nid); conn = get_defcfg_connect(def_conf); - if (conn != AC_JACK_PORT_COMPLEX) + if (conn == AC_JACK_PORT_NONE) return 0; + phantom_jack = (conn != AC_JACK_PORT_COMPLEX) || + !is_jack_detectable(codec, nid); - snd_hda_get_pin_label(codec, nid, cfg, name, sizeof(name), &idx); - if (!strcmp(name, lastname) && idx == *lastidx) - idx++; - strncpy(lastname, name, 44); - *lastidx = idx; - err = snd_hda_jack_add_kctl(codec, nid, name, idx); + if (base_name) { + strlcpy(name, base_name, sizeof(name)); + idx = 0; + } else + snd_hda_get_pin_label(codec, nid, cfg, name, sizeof(name), &idx); + if (phantom_jack) + /* Example final name: "Internal Mic Phantom Jack" */ + strncat(name, " Phantom", sizeof(name) - strlen(name) - 1); + idx = get_unique_index(codec, name, idx); + err = __snd_hda_jack_add_kctl(codec, nid, name, idx, phantom_jack); if (err < 0) return err; - return snd_hda_jack_detect_enable(codec, nid, 0); + + if (!phantom_jack) + return snd_hda_jack_detect_enable(codec, nid, 0); + return 0; } /** @@ -316,44 +442,105 @@ int snd_hda_jack_add_kctls(struct hda_codec *codec, const struct auto_pin_cfg *cfg) { const hda_nid_t *p; - int i, err, lastidx = 0; - char lastname[44] = ""; + int i, err; + + for (i = 0; i < cfg->num_inputs; i++) { + /* If we have headphone mics; make sure they get the right name + before grabbed by output pins */ + if (cfg->inputs[i].is_headphone_mic) { + if (auto_cfg_hp_outs(cfg) == 1) + err = add_jack_kctl(codec, auto_cfg_hp_pins(cfg)[0], + cfg, "Headphone Mic"); + else + err = add_jack_kctl(codec, cfg->inputs[i].pin, + cfg, "Headphone Mic"); + } else + err = add_jack_kctl(codec, cfg->inputs[i].pin, cfg, + NULL); + if (err < 0) + return err; + } for (i = 0, p = cfg->line_out_pins; i < cfg->line_outs; i++, p++) { - err = add_jack_kctl(codec, *p, cfg, lastname, &lastidx); + err = add_jack_kctl(codec, *p, cfg, NULL); 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, cfg, lastname, &lastidx); + err = add_jack_kctl(codec, *p, cfg, NULL); 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, cfg, lastname, &lastidx); - if (err < 0) - return err; - } - for (i = 0; i < cfg->num_inputs; i++) { - err = add_jack_kctl(codec, cfg->inputs[i].pin, cfg, lastname, &lastidx); + err = add_jack_kctl(codec, *p, cfg, NULL); 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, cfg, lastname, &lastidx); + err = add_jack_kctl(codec, *p, cfg, NULL); if (err < 0) return err; } - err = add_jack_kctl(codec, cfg->dig_in_pin, cfg, lastname, &lastidx); + err = add_jack_kctl(codec, cfg->dig_in_pin, cfg, NULL); if (err < 0) return err; - err = add_jack_kctl(codec, cfg->mono_out_pin, cfg, lastname, &lastidx); + err = add_jack_kctl(codec, cfg->mono_out_pin, cfg, NULL); if (err < 0) return err; return 0; } -EXPORT_SYMBOL_HDA(snd_hda_jack_add_kctls); +EXPORT_SYMBOL_GPL(snd_hda_jack_add_kctls); + +static void call_jack_callback(struct hda_codec *codec, + struct hda_jack_tbl *jack) +{ + if (jack->callback) + jack->callback(codec, jack); + if (jack->gated_jack) { + struct hda_jack_tbl *gated = + snd_hda_jack_tbl_get(codec, jack->gated_jack); + if (gated && gated->callback) + gated->callback(codec, gated); + } +} + +void snd_hda_jack_unsol_event(struct hda_codec *codec, unsigned int res) +{ + struct hda_jack_tbl *event; + int tag = (res >> AC_UNSOL_RES_TAG_SHIFT) & 0x7f; + + event = snd_hda_jack_tbl_get_from_tag(codec, tag); + if (!event) + return; + event->jack_dirty = 1; + + call_jack_callback(codec, event); + snd_hda_jack_report_sync(codec); +} +EXPORT_SYMBOL_GPL(snd_hda_jack_unsol_event); + +void snd_hda_jack_poll_all(struct hda_codec *codec) +{ + struct hda_jack_tbl *jack = codec->jacktbl.list; + int i, changes = 0; + + for (i = 0; i < codec->jacktbl.used; i++, jack++) { + unsigned int old_sense; + if (!jack->nid || !jack->jack_dirty || jack->phantom_jack) + continue; + old_sense = get_jack_plug_state(jack->pin_sense); + jack_detect_update(codec, jack); + if (old_sense == get_jack_plug_state(jack->pin_sense)) + continue; + changes = 1; + call_jack_callback(codec, jack); + } + if (changes) + snd_hda_jack_report_sync(codec); +} +EXPORT_SYMBOL_GPL(snd_hda_jack_poll_all); + |
