#include "hda_patch.h"
 #include "hda_beep.h"
 
+#define STAC_INSERT_EVENT      0x10
 #define STAC_PWR_EVENT         0x20
 #define STAC_HP_EVENT          0x30
 #define STAC_VREF_EVENT                0x40
        STAC_927X_MODELS
 };
 
+struct sigmatel_event {
+       hda_nid_t nid;
+       int data;
+};
+
+struct sigmatel_jack {
+       hda_nid_t nid;
+       int type;
+       struct snd_jack *jack;
+};
+
 struct sigmatel_spec {
        struct snd_kcontrol_new *mixers[4];
        unsigned int num_mixers;
        hda_nid_t *pwr_nids;
        hda_nid_t *dac_list;
 
+       /* jack detection */
+       struct snd_array jacks;
+
+       /* events */
+       struct snd_array events;
+
        /* playback */
        struct hda_input_mux *mono_mux;
        struct hda_input_mux *amp_mux;
 
        struct hda_pcm pcm_rec[2];      /* PCM information */
 
-       /* jack detection */
-       struct snd_jack *jack;
-
        /* dynamic controls and input_mux */
        struct auto_pin_cfg autocfg;
        struct snd_array kctls;
 {
        struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
        struct sigmatel_spec *spec = codec->spec;
+       struct auto_pin_cfg *cfg = &spec->autocfg;
+       int nid = cfg->hp_pins[cfg->hp_outs - 1];
 
        spec->hp_switch = ucontrol->value.integer.value[0];
 
        /* check to be sure that the ports are upto date with
         * switch changes
         */
-       codec->patch_ops.unsol_event(codec, STAC_HP_EVENT << 26);
+       codec->patch_ops.unsol_event(codec, (STAC_HP_EVENT | nid) << 26);
 
        return 1;
 }
         * appropriately according to the pin direction
         */
        if (spec->hp_detect)
-               codec->patch_ops.unsol_event(codec, STAC_HP_EVENT << 26);
+               codec->patch_ops.unsol_event(codec,
+                       (STAC_HP_EVENT | nid) << 26);
 
         return 1;
 }
                           AC_VERB_SET_GPIO_DATA, gpiostate); /* sync */
 }
 
+static int stac92xx_add_jack(struct hda_codec *codec,
+               hda_nid_t nid, int type)
+{
+       struct sigmatel_spec *spec = codec->spec;
+       struct sigmatel_jack *jack;
+       int def_conf = snd_hda_codec_read(codec, nid,
+                       0, AC_VERB_GET_CONFIG_DEFAULT, 0);
+       int connectivity = get_defcfg_connect(def_conf);
+       char name[32];
+
+       if (connectivity && connectivity != AC_JACK_PORT_FIXED)
+               return 0;
+
+       snd_array_init(&spec->jacks, sizeof(*jack), 32);
+       jack = snd_array_new(&spec->jacks);
+       if (!jack)
+               return -ENOMEM;
+       jack->nid = nid;
+       jack->type = type;
+
+       sprintf(name, "%s at %s %s Jack",
+               snd_hda_get_jack_type(def_conf),
+               snd_hda_get_jack_connectivity(def_conf),
+               snd_hda_get_jack_location(def_conf));
+
+       return snd_jack_new(codec->bus->card, name, type, &jack->jack);
+}
+
+static int stac92xx_add_event(struct sigmatel_spec *spec, hda_nid_t nid,
+                            int data)
+{
+       struct sigmatel_event *event;
+
+       snd_array_init(&spec->events, sizeof(*event), 32);
+       event = snd_array_new(&spec->events);
+       if (!event)
+               return -ENOMEM;
+       event->nid = nid;
+       event->data = data;
+
+       return 0;
+}
+
+static int stac92xx_event_data(struct hda_codec *codec, hda_nid_t nid)
+{
+       struct sigmatel_spec *spec = codec->spec;
+       struct sigmatel_event *events = spec->events.list;
+       if (events) {
+               int i;
+               for (i = 0; i < spec->events.used; i++)
+                       if (events[i].nid == nid)
+                               return events[i].data;
+       }
+       return 0;
+}
+
 static void enable_pin_detect(struct hda_codec *codec, hda_nid_t nid,
                              unsigned int event)
 {
-       if (get_wcaps(codec, nid) & AC_WCAP_UNSOL_CAP)
+       if (get_wcaps(codec, nid) & AC_WCAP_UNSOL_CAP) {
                snd_hda_codec_write_cache(codec, nid, 0,
                                          AC_VERB_SET_UNSOLICITED_ENABLE,
-                                         (AC_USRSP_EN | event));
+                                         (AC_USRSP_EN | event | nid));
+       }
 }
 
 static int is_nid_hp_pin(struct auto_pin_cfg *cfg, hda_nid_t nid)
        /* set up pins */
        if (spec->hp_detect) {
                /* Enable unsolicited responses on the HP widget */
-               for (i = 0; i < cfg->hp_outs; i++)
-                       enable_pin_detect(codec, cfg->hp_pins[i],
-                                         STAC_HP_EVENT);
+               for (i = 0; i < cfg->hp_outs; i++) {
+                       int type = SND_JACK_HEADPHONE;
+                       hda_nid_t nid = cfg->hp_pins[i];
+                       enable_pin_detect(codec, nid, STAC_HP_EVENT | nid);
+                       /* jack detection */
+                       if (cfg->hp_outs == i)
+                               type |= SND_JACK_LINEOUT;
+                       err = stac92xx_add_jack(codec, nid, type);
+                       if (err < 0)
+                               return err;
+
+               }
                /* force to enable the first line-out; the others are set up
                 * in unsol_event
                 */
                stac92xx_auto_set_pinctl(codec, spec->autocfg.line_out_pins[0],
-                                        AC_PINCTL_OUT_EN);
-               stac92xx_auto_init_hp_out(codec);
-               /* jack detection */
-               err = snd_jack_new(codec->bus->card,
-                       "Headphone Jack",
-                       SND_JACK_HEADPHONE, &spec->jack);
-               if (err < 0)
-                       return err;
+                               AC_PINCTL_OUT_EN);
                /* fake event to set up pins */
-               codec->patch_ops.unsol_event(codec, STAC_HP_EVENT << 26);
+               codec->patch_ops.unsol_event(codec,
+                       (STAC_HP_EVENT | spec->autocfg.hp_pins[0]) << 26);
        } else {
                stac92xx_auto_init_multi_out(codec);
                stac92xx_auto_init_hp_out(codec);
        }
+       for (i = 0; i < cfg->line_outs; i++) {
+               err = stac92xx_add_jack(codec,
+                               cfg->line_out_pins[i], SND_JACK_LINEOUT);
+               if (err < 0)
+                       return err;
+       }
        for (i = 0; i < AUTO_PIN_LAST; i++) {
                hda_nid_t nid = cfg->input_pins[i];
                if (nid) {
                        if (i == AUTO_PIN_MIC || i == AUTO_PIN_FRONT_MIC)
                                pinctl |= stac92xx_get_vref(codec, nid);
                        stac92xx_auto_set_pinctl(codec, nid, pinctl);
+                       err = stac92xx_add_jack(codec, nid,
+                               SND_JACK_MICROPHONE);
+                       if (err < 0)
+                               return err;
+                       enable_pin_detect(codec, nid, STAC_INSERT_EVENT | nid);
                }
        }
        for (i = 0; i < spec->num_dmics; i++)
        return 0;
 }
 
+static void stac92xx_free_jacks(struct hda_codec *codec)
+{
+       struct sigmatel_spec *spec = codec->spec;
+       if (spec->jacks.list) {
+               struct sigmatel_jack *jacks = spec->jacks.list;
+               int i;
+               for (i = 0; i < spec->jacks.used; i++)
+                       snd_device_free(codec->bus->card, &jacks[i].jack);
+       }
+       snd_array_free(&spec->jacks);
+}
+
 static void stac92xx_free_kctls(struct hda_codec *codec)
 {
        struct sigmatel_spec *spec = codec->spec;
        if (! spec)
                return;
 
-       if (spec->jack)
-               snd_device_free(codec->bus->card, spec->jack);
-
        if (spec->bios_pin_configs)
                kfree(spec->bios_pin_configs);
+       stac92xx_free_jacks(codec);
+       snd_array_free(&spec->events);
 
        kfree(spec);
        snd_hda_detach_beep_device(codec);
                        break;
                presence = get_hp_pin_presence(codec, cfg->hp_pins[i]);
        }
-       snd_jack_report(spec->jack,
-               presence ? SND_JACK_HEADPHONE : 0);
 
        if (presence) {
                /* disable lineouts, enable hp */
 
        /* power down unused output ports */
        snd_hda_codec_write(codec, codec->afg, 0, 0x7ec, val);
-};
+}
+
+static void stac92xx_report_jack(struct hda_codec *codec, hda_nid_t nid)
+{
+       struct sigmatel_spec *spec = codec->spec;
+       struct sigmatel_jack *jacks = spec->jacks.list;
+
+       if (jacks) {
+               int i;
+               for (i = 0; i < spec->jacks.used; i++) {
+                       if (jacks->nid == nid) {
+                               unsigned int pin_ctl =
+                                       snd_hda_codec_read(codec, nid,
+                                       0, AC_VERB_GET_PIN_WIDGET_CONTROL,
+                                        0x00);
+                               int type = jacks->type;
+                               if (type == (SND_JACK_LINEOUT
+                                               | SND_JACK_HEADPHONE))
+                                       type = (pin_ctl & AC_PINCTL_HP_EN)
+                                       ? SND_JACK_HEADPHONE : SND_JACK_LINEOUT;
+                               snd_jack_report(jacks->jack,
+                                       get_hp_pin_presence(codec, nid)
+                                       ? type : 0);
+                       }
+                       jacks++;
+               }
+       }
+}
 
 static void stac92xx_unsol_event(struct hda_codec *codec, unsigned int res)
 {
        struct sigmatel_spec *spec = codec->spec;
-       int idx = res >> 26 & 0x0f;
+       int event = (res >> 26) & 0x70;
+       int nid = res >> 26 & 0x0f;
 
-       switch ((res >> 26) & 0x70) {
+       switch (event) {
        case STAC_HP_EVENT:
                stac92xx_hp_detect(codec, res);
                /* fallthru */
+       case STAC_INSERT_EVENT:
        case STAC_PWR_EVENT:
-               if (spec->num_pwrs > 0)
-                       stac92xx_pin_sense(codec, idx);
+               if (nid) {
+                       if (spec->num_pwrs > 0)
+                               stac92xx_pin_sense(codec, nid);
+                       stac92xx_report_jack(codec, nid);
+               }
                break;
        case STAC_VREF_EVENT: {
                int data = snd_hda_codec_read(codec, codec->afg, 0,
                        AC_VERB_GET_GPIO_DATA, 0);
+               int idx = stac92xx_event_data(codec, nid);
                /* toggle VREF state based on GPIOx status */
                snd_hda_codec_write(codec, codec->afg, 0, 0x7e0,
                        !!(data & (1 << idx)));
                        snd_hda_codec_write(codec, codec->afg, 0,
                                AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK, 0x02);
                        snd_hda_codec_write_cache(codec, codec->afg, 0,
-                                       AC_VERB_SET_UNSOLICITED_ENABLE,
-                                       (AC_USRSP_EN | STAC_VREF_EVENT | 0x01));
+                               AC_VERB_SET_UNSOLICITED_ENABLE,
+                               (AC_USRSP_EN | STAC_VREF_EVENT | codec->afg));
+                       err = stac92xx_add_event(spec, codec->afg, 0x02);
+                       if (err < 0)
+                               return err;
                        spec->gpio_mask |= 0x02;
                        break;
                }
                snd_hda_codec_write(codec, codec->afg, 0,
                        AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK, 0x10);
                snd_hda_codec_write_cache(codec, codec->afg, 0,
-                                         AC_VERB_SET_UNSOLICITED_ENABLE,
-                                         (AC_USRSP_EN | STAC_HP_EVENT));
+                       AC_VERB_SET_UNSOLICITED_ENABLE,
+                       (AC_USRSP_EN | STAC_VREF_EVENT | codec->afg));
+               err = stac92xx_add_event(spec, codec->afg, 0x01);
+               if (err < 0)
+                       return err;
 
                spec->gpio_dir = 0x0b;
                spec->eapd_mask = 0x01;