ALSA: HDA VIA: Add VT2002P support.
authorLydia Wang <lydiawang@viatech.com.cn>
Sat, 10 Oct 2009 11:08:43 +0000 (19:08 +0800)
committerTakashi Iwai <tiwai@suse.de>
Sun, 11 Oct 2009 15:58:59 +0000 (17:58 +0200)
Signed-off-by: Lydia Wang <lydiawang@viatech.com.cn>
Signed-off-by: Logan Li <loganli@viatech.com.cn>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/pci/hda/patch_via.c

index 2977004..a94cc91 100644 (file)
@@ -88,6 +88,7 @@ enum VIA_HDA_CODEC {
        VT1702,
        VT1718S,
        VT1716S,
+       VT2002P,
        CODEC_TYPES,
 };
 
@@ -184,6 +185,8 @@ static enum VIA_HDA_CODEC get_codec_type(struct hda_codec *codec)
                codec_type = VT1716S;
        else if (dev_id == 0x0441 || dev_id == 0x4441)
                codec_type = VT1718S;
+       else if (dev_id == 0x0438 || dev_id == 0x4438)
+               codec_type = VT2002P;
        else
                codec_type = UNKNOWN;
        return codec_type;
@@ -193,11 +196,14 @@ static enum VIA_HDA_CODEC get_codec_type(struct hda_codec *codec)
 #define VIA_GPIO_EVENT         0x02
 #define VIA_JACK_EVENT         0x04
 #define VIA_MONO_EVENT         0x08
+#define VIA_SPEAKER_EVENT      0x10
+#define VIA_BIND_HP_EVENT      0x20
 
 enum {
        VIA_CTL_WIDGET_VOL,
        VIA_CTL_WIDGET_MUTE,
        VIA_CTL_WIDGET_ANALOG_MUTE,
+       VIA_CTL_WIDGET_BIND_PIN_MUTE,
 };
 
 enum {
@@ -235,6 +241,7 @@ static void vt1708_stop_hp_work(struct via_spec *spec)
        flush_scheduled_work();
 }
 
+
 static int analog_input_switch_put(struct snd_kcontrol *kcontrol,
                                   struct snd_ctl_elem_value *ucontrol)
 {
@@ -262,13 +269,108 @@ static int analog_input_switch_put(struct snd_kcontrol *kcontrol,
                        .put = analog_input_switch_put,                 \
                        .private_value = HDA_COMPOSE_AMP_VAL(0, 3, 0, 0) }
 
+static void via_hp_bind_automute(struct hda_codec *codec);
+
+static int bind_pin_switch_put(struct snd_kcontrol *kcontrol,
+                              struct snd_ctl_elem_value *ucontrol)
+{
+       struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct via_spec *spec = codec->spec;
+       int i;
+       int change = 0;
+
+       long *valp = ucontrol->value.integer.value;
+       int lmute, rmute;
+       if (strstr(kcontrol->id.name, "Switch") == NULL) {
+               snd_printd("Invalid control!\n");
+               return change;
+       }
+       change = snd_hda_mixer_amp_switch_put(kcontrol,
+                                             ucontrol);
+       /* Get mute value */
+       lmute = *valp ? 0 : HDA_AMP_MUTE;
+       valp++;
+       rmute = *valp ? 0 : HDA_AMP_MUTE;
+
+       /* Set hp pins */
+       if (!spec->hp_independent_mode) {
+               for (i = 0; i < spec->autocfg.hp_outs; i++) {
+                       snd_hda_codec_amp_update(
+                               codec, spec->autocfg.hp_pins[i],
+                               0, HDA_OUTPUT, 0, HDA_AMP_MUTE,
+                               lmute);
+                       snd_hda_codec_amp_update(
+                               codec, spec->autocfg.hp_pins[i],
+                               1, HDA_OUTPUT, 0, HDA_AMP_MUTE,
+                               rmute);
+               }
+       }
+
+       if (!lmute && !rmute) {
+               /* Line Outs */
+               for (i = 0; i < spec->autocfg.line_outs; i++)
+                       snd_hda_codec_amp_stereo(
+                               codec, spec->autocfg.line_out_pins[i],
+                               HDA_OUTPUT, 0, HDA_AMP_MUTE, 0);
+               /* Speakers */
+               for (i = 0; i < spec->autocfg.speaker_outs; i++)
+                       snd_hda_codec_amp_stereo(
+                               codec, spec->autocfg.speaker_pins[i],
+                               HDA_OUTPUT, 0, HDA_AMP_MUTE, 0);
+               /* unmute */
+               via_hp_bind_automute(codec);
+
+       } else {
+               if (lmute) {
+                       /* Mute all left channels */
+                       for (i = 1; i < spec->autocfg.line_outs; i++)
+                               snd_hda_codec_amp_update(
+                                       codec,
+                                       spec->autocfg.line_out_pins[i],
+                                       0, HDA_OUTPUT, 0, HDA_AMP_MUTE,
+                                       lmute);
+                       for (i = 0; i < spec->autocfg.speaker_outs; i++)
+                               snd_hda_codec_amp_update(
+                                       codec,
+                                       spec->autocfg.speaker_pins[i],
+                                       0, HDA_OUTPUT, 0, HDA_AMP_MUTE,
+                                       lmute);
+               }
+               if (rmute) {
+                       /* mute all right channels */
+                       for (i = 1; i < spec->autocfg.line_outs; i++)
+                               snd_hda_codec_amp_update(
+                                       codec,
+                                       spec->autocfg.line_out_pins[i],
+                                       1, HDA_OUTPUT, 0, HDA_AMP_MUTE,
+                                       rmute);
+                       for (i = 0; i < spec->autocfg.speaker_outs; i++)
+                               snd_hda_codec_amp_update(
+                                       codec,
+                                       spec->autocfg.speaker_pins[i],
+                                       1, HDA_OUTPUT, 0, HDA_AMP_MUTE,
+                                       rmute);
+               }
+       }
+       return change;
+}
+
+#define BIND_PIN_MUTE                                                  \
+       {               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,            \
+                       .name = NULL,                                   \
+                       .index = 0,                                     \
+                       .info = snd_hda_mixer_amp_switch_info,          \
+                       .get = snd_hda_mixer_amp_switch_get,            \
+                       .put = bind_pin_switch_put,                     \
+                       .private_value = HDA_COMPOSE_AMP_VAL(0, 3, 0, 0) }
+
 static struct snd_kcontrol_new vt1708_control_templates[] = {
        HDA_CODEC_VOLUME(NULL, 0, 0, 0),
        HDA_CODEC_MUTE(NULL, 0, 0, 0),
        ANALOG_INPUT_MUTE,
+       BIND_PIN_MUTE,
 };
 
-
 static hda_nid_t vt1708_adc_nids[2] = {
        /* ADC1-2 */
        0x15, 0x27
@@ -304,6 +406,11 @@ static hda_nid_t vt1716S_adc_nids[2] = {
        0x13, 0x14
 };
 
+static hda_nid_t vt2002P_adc_nids[2] = {
+       /* ADC1-2 */
+       0x10, 0x11
+};
+
 /* add dynamic controls */
 static int via_add_control(struct via_spec *spec, int type, const char *name,
                           unsigned long val)
@@ -386,10 +493,13 @@ static void via_auto_init_hp_out(struct hda_codec *codec)
 {
        struct via_spec *spec = codec->spec;
        hda_nid_t pin;
+       int i;
 
-       pin = spec->autocfg.hp_pins[0];
-       if (pin) /* connect to front */
-               via_auto_set_output_and_unmute(codec, pin, PIN_HP, 0);
+       for (i = 0; i < spec->autocfg.hp_outs; i++) {
+               pin = spec->autocfg.hp_pins[i];
+               if (pin) /* connect to front */
+                       via_auto_set_output_and_unmute(codec, pin, PIN_HP, 0);
+       }
 }
 
 static void via_auto_init_analog_input(struct hda_codec *codec)
@@ -693,6 +803,107 @@ static void set_jack_power_state(struct hda_codec *codec)
                                    imux_is_smixer ? AC_PWRST_D0 : parm);
                snd_hda_codec_write(codec, 0x10, 0, AC_VERB_SET_POWER_STATE,
                                    mono_out ? AC_PWRST_D0 : parm);
+       } else if (spec->codec_type == VT2002P) {
+               unsigned int present;
+               /* MUX9 (1eh) = stereo mixer */
+               imux_is_smixer = snd_hda_codec_read(
+                       codec, 0x1e, 0, AC_VERB_GET_CONNECT_SEL, 0x00) == 3;
+               /* inputs */
+               /* PW 5/6/7 (29h/2ah/2bh) */
+               parm = AC_PWRST_D3;
+               set_pin_power_state(codec, 0x29, &parm);
+               set_pin_power_state(codec, 0x2a, &parm);
+               set_pin_power_state(codec, 0x2b, &parm);
+               if (imux_is_smixer)
+                       parm = AC_PWRST_D0;
+               /* MUX9/10 (1eh/1fh), AIW 0/1 (10h/11h) */
+               snd_hda_codec_write(codec, 0x1e, 0,
+                                   AC_VERB_SET_POWER_STATE, parm);
+               snd_hda_codec_write(codec, 0x1f, 0,
+                                   AC_VERB_SET_POWER_STATE, parm);
+               snd_hda_codec_write(codec, 0x10, 0,
+                                   AC_VERB_SET_POWER_STATE, parm);
+               snd_hda_codec_write(codec, 0x11, 0,
+                                   AC_VERB_SET_POWER_STATE, parm);
+
+               /* outputs */
+               /* AOW0 (8h)*/
+               snd_hda_codec_write(codec, 0x8, 0,
+                                   AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
+
+               /* PW4 (26h), MW4 (1ch), MUX4(37h) */
+               parm = AC_PWRST_D3;
+               set_pin_power_state(codec, 0x26, &parm);
+               snd_hda_codec_write(codec, 0x1c, 0,
+                                   AC_VERB_SET_POWER_STATE, parm);
+               snd_hda_codec_write(codec, 0x37,
+                                   0, AC_VERB_SET_POWER_STATE, parm);
+
+               /* PW1 (25h), MW1 (19h), MUX1(35h), AOW1 (9h) */
+               parm = AC_PWRST_D3;
+               set_pin_power_state(codec, 0x25, &parm);
+               snd_hda_codec_write(codec, 0x19, 0,
+                                   AC_VERB_SET_POWER_STATE, parm);
+               snd_hda_codec_write(codec, 0x35, 0,
+                                   AC_VERB_SET_POWER_STATE, parm);
+               if (spec->hp_independent_mode)  {
+                       snd_hda_codec_write(codec, 0x9, 0,
+                                           AC_VERB_SET_POWER_STATE, parm);
+               }
+
+               /* Class-D */
+               /* PW0 (24h), MW0(18h), MUX0(34h) */
+               present = snd_hda_codec_read(
+                       codec, 0x25, 0, AC_VERB_GET_PIN_SENSE, 0) & 0x80000000;
+               parm = AC_PWRST_D3;
+               set_pin_power_state(codec, 0x24, &parm);
+               if (present) {
+                       snd_hda_codec_write(
+                               codec, 0x18, 0,
+                               AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
+                       snd_hda_codec_write(
+                               codec, 0x34, 0,
+                               AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
+               } else {
+                       snd_hda_codec_write(
+                               codec, 0x18, 0,
+                               AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
+                       snd_hda_codec_write(
+                               codec, 0x34, 0,
+                               AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
+               }
+
+               /* Mono Out */
+               /* PW15 (31h), MW8(17h), MUX8(3bh) */
+               present = snd_hda_codec_read(
+                       codec, 0x26, 0, AC_VERB_GET_PIN_SENSE, 0) & 0x80000000;
+               parm = AC_PWRST_D3;
+               set_pin_power_state(codec, 0x31, &parm);
+               if (present) {
+                       snd_hda_codec_write(
+                               codec, 0x17, 0,
+                               AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
+                       snd_hda_codec_write(
+                               codec, 0x3b, 0,
+                               AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
+               } else {
+                       snd_hda_codec_write(
+                               codec, 0x17, 0,
+                               AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
+                       snd_hda_codec_write(
+                               codec, 0x3b, 0,
+                               AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
+               }
+
+               /* MW9 (21h) */
+               if (imux_is_smixer || !is_aa_path_mute(codec))
+                       snd_hda_codec_write(
+                               codec, 0x21, 0,
+                               AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
+               else
+                       snd_hda_codec_write(
+                               codec, 0x21, 0,
+                               AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
        }
 }
 
@@ -760,6 +971,9 @@ static int via_independent_hp_get(struct snd_kcontrol *kcontrol,
        case VT1718S:
                nid = 0x34;
                break;
+       case VT2002P:
+               nid = 0x35;
+               break;
        default:
                nid = spec->autocfg.hp_pins[0];
                break;
@@ -832,6 +1046,9 @@ static int via_independent_hp_put(struct snd_kcontrol *kcontrol,
                pinsel = pinsel ? 2 : 0; /* indep HP use AOW4 (index 2) */
                spec->multiout.num_dacs = 4;
                break;
+       case VT2002P:
+               nid = 0x35;
+               break;
        default:
                nid = spec->autocfg.hp_pins[0];
                break;
@@ -848,7 +1065,8 @@ static int via_independent_hp_put(struct snd_kcontrol *kcontrol,
        if (spec->codec_type == VT1708S
            || spec->codec_type == VT1702
            || spec->codec_type == VT1718S
-           || spec->codec_type == VT1716S) {
+           || spec->codec_type == VT1716S
+           || spec->codec_type == VT2002P) {
                activate_ctl(codec, "Headphone Playback Volume",
                             spec->hp_independent_mode);
                activate_ctl(codec, "Headphone Playback Switch",
@@ -1088,6 +1306,11 @@ static int is_aa_path_mute(struct hda_codec *codec)
                start_idx = 1;
                end_idx = 3;
                break;
+       case VT2002P:
+               nid_mixer = 0x21;
+               start_idx = 0;
+               end_idx = 2;
+               break;
        default:
                return 0;
        }
@@ -1146,6 +1369,10 @@ static void analog_low_current_mode(struct hda_codec *codec, int stream_idle)
                verb = 0xf73;
                parm = enable ? 0x01 : 0x1d; /* 0x01: 4/40x, 0x1d: 1x */
                break;
+       case VT2002P:
+               verb = 0xf93;
+               parm = enable ? 0x00 : 0xe0; /* 0x00: 4/40x, 0xe0: 1x */
+               break;
        default:
                return;         /* other codecs are not supported */
        }
@@ -1645,6 +1872,66 @@ static void via_gpio_control(struct hda_codec *codec)
        }
 }
 
+/* mute Internal-Speaker if HP is plugged */
+static void via_speaker_automute(struct hda_codec *codec)
+{
+       unsigned int hp_present;
+       struct via_spec *spec = codec->spec;
+
+       if (spec->codec_type != VT2002P)
+               return;
+
+       hp_present = snd_hda_codec_read(codec, spec->autocfg.hp_pins[0], 0,
+                                    AC_VERB_GET_PIN_SENSE, 0) & 0x80000000;
+
+       if (!spec->hp_independent_mode) {
+               struct snd_ctl_elem_id id;
+               snd_hda_codec_amp_stereo(
+                       codec, spec->autocfg.speaker_pins[0], HDA_OUTPUT, 0,
+                       HDA_AMP_MUTE, hp_present ? HDA_AMP_MUTE : 0);
+               /* notify change */
+               memset(&id, 0, sizeof(id));
+               id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+               strcpy(id.name, "Speaker Playback Switch");
+               snd_ctl_notify(codec->bus->card, SNDRV_CTL_EVENT_MASK_VALUE,
+                              &id);
+       }
+}
+
+/* mute line-out and internal speaker if HP is plugged */
+static void via_hp_bind_automute(struct hda_codec *codec)
+{
+       unsigned int hp_present, present = 0;
+       struct via_spec *spec = codec->spec;
+       int i;
+
+       if (!spec->autocfg.hp_pins[0] || !spec->autocfg.line_out_pins[0])
+               return;
+
+       hp_present = snd_hda_codec_read(codec, spec->autocfg.hp_pins[0], 0,
+                                       AC_VERB_GET_PIN_SENSE, 0) & 0x80000000;
+
+       present = snd_hda_codec_read(codec, spec->autocfg.line_out_pins[0], 0,
+                                    AC_VERB_GET_PIN_SENSE, 0) & 0x80000000;
+
+       if (!spec->hp_independent_mode) {
+               /* Mute Line-Outs */
+               for (i = 0; i < spec->autocfg.line_outs; i++)
+                       snd_hda_codec_amp_stereo(
+                               codec, spec->autocfg.line_out_pins[i],
+                               HDA_OUTPUT, 0,
+                               HDA_AMP_MUTE, hp_present ? HDA_AMP_MUTE : 0);
+               if (hp_present)
+                       present = hp_present;
+       }
+       /* Speakers */
+       for (i = 0; i < spec->autocfg.speaker_outs; i++)
+               snd_hda_codec_amp_stereo(
+                       codec, spec->autocfg.speaker_pins[i], HDA_OUTPUT, 0,
+                       HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0);
+}
+
+
 /* unsolicited event for jack sensing */
 static void via_unsol_event(struct hda_codec *codec,
                                  unsigned int res)
@@ -1658,6 +1945,10 @@ static void via_unsol_event(struct hda_codec *codec,
                set_jack_power_state(codec);
        if (res & VIA_MONO_EVENT)
                via_mono_automute(codec);
+       if (res & VIA_SPEAKER_EVENT)
+               via_speaker_automute(codec);
+       if (res & VIA_BIND_HP_EVENT)
+               via_hp_bind_automute(codec);
 }
 
 static int via_init(struct hda_codec *codec)
@@ -2067,10 +2358,19 @@ static int vt1708_parse_auto_config(struct hda_codec *codec)
 /* init callback for auto-configuration model -- overriding the default init */
 static int via_auto_init(struct hda_codec *codec)
 {
+       struct via_spec *spec = codec->spec;
+
        via_init(codec);
        via_auto_init_multi_out(codec);
        via_auto_init_hp_out(codec);
        via_auto_init_analog_input(codec);
+       if (spec->codec_type == VT2002P) {
+               via_hp_bind_automute(codec);
+       } else {
+               via_hp_automute(codec);
+               via_speaker_automute(codec);
+       }
+
        return 0;
 }
 
@@ -5001,6 +5301,359 @@ static int patch_vt1716S(struct hda_codec *codec)
 
        return 0;
 }
+
+/* for vt2002P */
+
+/* capture mixer elements */
+static struct snd_kcontrol_new vt2002P_capture_mixer[] = {
+       HDA_CODEC_VOLUME("Capture Volume", 0x10, 0x0, HDA_INPUT),
+       HDA_CODEC_MUTE("Capture Switch", 0x10, 0x0, HDA_INPUT),
+       HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x11, 0x0, HDA_INPUT),
+       HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x11, 0x0, HDA_INPUT),
+       HDA_CODEC_VOLUME("Mic Boost Capture Volume", 0x2b, 0x0, HDA_INPUT),
+       HDA_CODEC_VOLUME("Front Mic Boost Capture Volume", 0x29, 0x0,
+                        HDA_INPUT),
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               /* The multiple "Capture Source" controls confuse alsamixer
+                * So call somewhat different..
+                */
+               /* .name = "Capture Source", */
+               .name = "Input Source",
+               .count = 2,
+               .info = via_mux_enum_info,
+               .get = via_mux_enum_get,
+               .put = via_mux_enum_put,
+       },
+       { } /* end */
+};
+
+static struct hda_verb vt2002P_volume_init_verbs[] = {
+       /*
+        * Unmute ADC0-1 and set the default input to mic-in
+        */
+       {0x8, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+       {0x9, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+
+       /* Mute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback
+        * mixer widget
+        */
+       /* Amp Indices: CD = 1, Mic1 = 2, Line = 3, Mic2 = 4 */
+       {0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+       {0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+       {0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+       {0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+       {0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+
+       /* MUX Indices: Mic = 0 */
+       {0x1e, AC_VERB_SET_CONNECT_SEL, 0},
+       {0x1f, AC_VERB_SET_CONNECT_SEL, 0},
+
+       /* PW9 Output enable */
+       {0x2d, AC_VERB_SET_PIN_WIDGET_CONTROL, AC_PINCTL_OUT_EN},
+
+       /* Enable Boost Volume backdoor */
+       {0x1, 0xfb9, 0x24},
+
+       /* MW0/1/4/8: un-mute index 0 (MUXx), un-mute index 1 (MW9) */
+       {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+       {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+       {0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+       {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+       {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+       {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+       {0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+       {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+       /* set MUX0/1/4/8 = 0 (AOW0) */
+       {0x34, AC_VERB_SET_CONNECT_SEL, 0},
+       {0x35, AC_VERB_SET_CONNECT_SEL, 0},
+       {0x37, AC_VERB_SET_CONNECT_SEL, 0},
+       {0x3b, AC_VERB_SET_CONNECT_SEL, 0},
+
+       /* set PW0 index=0 (MW0) */
+       {0x24, AC_VERB_SET_CONNECT_SEL, 0},
+
+       /* Enable AOW0 to MW9 */
+       {0x1, 0xfb8, 0x88},
+       { }
+};
+
+
+static struct hda_verb vt2002P_uniwill_init_verbs[] = {
+       {0x25, AC_VERB_SET_UNSOLICITED_ENABLE,
+        AC_USRSP_EN | VIA_JACK_EVENT | VIA_BIND_HP_EVENT},
+       {0x26, AC_VERB_SET_UNSOLICITED_ENABLE,
+        AC_USRSP_EN | VIA_JACK_EVENT | VIA_BIND_HP_EVENT},
+       {0x29, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+       {0x2a, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+       {0x2b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+       { }
+};
+
+static struct hda_pcm_stream vt2002P_pcm_analog_playback = {
+       .substreams = 2,
+       .channels_min = 2,
+       .channels_max = 2,
+       .nid = 0x8, /* NID to query formats and rates */
+       .ops = {
+               .open = via_playback_pcm_open,
+               .prepare = via_playback_multi_pcm_prepare,
+               .cleanup = via_playback_multi_pcm_cleanup,
+               .close = via_pcm_open_close,
+       },
+};
+
+static struct hda_pcm_stream vt2002P_pcm_analog_capture = {
+       .substreams = 2,
+       .channels_min = 2,
+       .channels_max = 2,
+       .nid = 0x10, /* NID to query formats and rates */
+       .ops = {
+               .open = via_pcm_open_close,
+               .prepare = via_capture_pcm_prepare,
+               .cleanup = via_capture_pcm_cleanup,
+               .close = via_pcm_open_close,
+       },
+};
+
+static struct hda_pcm_stream vt2002P_pcm_digital_playback = {
+       .substreams = 1,
+       .channels_min = 2,
+       .channels_max = 2,
+       .rates = SNDRV_PCM_RATE_48000,
+       /* NID is set in via_build_pcms */
+       .ops = {
+               .open = via_dig_playback_pcm_open,
+               .close = via_dig_playback_pcm_close,
+               .prepare = via_dig_playback_pcm_prepare,
+               .cleanup = via_dig_playback_pcm_cleanup
+       },
+};
+
+/* fill in the dac_nids table from the parsed pin configuration */
+static int vt2002P_auto_fill_dac_nids(struct via_spec *spec,
+                                     const struct auto_pin_cfg *cfg)
+{
+       spec->multiout.num_dacs = 1;
+       spec->multiout.dac_nids = spec->private_dac_nids;
+       if (cfg->line_out_pins[0])
+               spec->multiout.dac_nids[0] = 0x8;
+       return 0;
+}
+
+/* add playback controls from the parsed DAC table */
+static int vt2002P_auto_create_multi_out_ctls(struct via_spec *spec,
+                                            const struct auto_pin_cfg *cfg)
+{
+       int err;
+
+       if (!cfg->line_out_pins[0])
+               return -1;
+
+
+       /* Line-Out: PortE */
+       err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+                             "Master Front Playback Volume",
+                             HDA_COMPOSE_AMP_VAL(0x8, 3, 0, HDA_OUTPUT));
+       if (err < 0)
+               return err;
+       err = via_add_control(spec, VIA_CTL_WIDGET_BIND_PIN_MUTE,
+                             "Master Front Playback Switch",
+                             HDA_COMPOSE_AMP_VAL(0x26, 3, 0, HDA_OUTPUT));
+       if (err < 0)
+               return err;
+
+       return 0;
+}
+
+static int vt2002P_auto_create_hp_ctls(struct via_spec *spec, hda_nid_t pin)
+{
+       int err;
+
+       if (!pin)
+               return 0;
+
+       spec->multiout.hp_nid = 0x9;
+       spec->hp_independent_mode_index = 1;
+
+       err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+                             "Headphone Playback Volume",
+                             HDA_COMPOSE_AMP_VAL(
+                                     spec->multiout.hp_nid, 3, 0, HDA_OUTPUT));
+       if (err < 0)
+               return err;
+
+       err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+                             "Headphone Playback Switch",
+                             HDA_COMPOSE_AMP_VAL(0x25, 3, 0, HDA_OUTPUT));
+       if (err < 0)
+               return err;
+
+       create_hp_imux(spec);
+       return 0;
+}
+
+/* create playback/capture controls for input pins */
+static int vt2002P_auto_create_analog_input_ctls(struct via_spec *spec,
+                                               const struct auto_pin_cfg *cfg)
+{
+       static char *labels[] = {
+               "Mic", "Front Mic", "Line", "Front Line", "CD", "Aux", NULL
+       };
+       struct hda_input_mux *imux = &spec->private_imux[0];
+       int i, err, idx = 0;
+
+       for (i = 0; i < AUTO_PIN_LAST; i++) {
+               if (!cfg->input_pins[i])
+                       continue;
+
+               switch (cfg->input_pins[i]) {
+               case 0x2b: /* Mic */
+                       idx = 0;
+                       break;
+
+               case 0x2a: /* Line In */
+                       idx = 1;
+                       break;
+
+               case 0x29: /* Front Mic */
+                       idx = 2;
+                       break;
+               }
+               err = via_new_analog_input(spec, labels[i], idx, 0x21);
+               if (err < 0)
+                       return err;
+               imux->items[imux->num_items].label = labels[i];
+               imux->items[imux->num_items].index = idx;
+               imux->num_items++;
+       }
+
+       /* build volume/mute control of loopback */
+       err = via_new_analog_input(spec, "Stereo Mixer", 3, 0x21);
+       if (err < 0)
+               return err;
+
+       /* for internal loopback recording select */
+       imux->items[imux->num_items].label = "Stereo Mixer";
+       imux->items[imux->num_items].index = 3;
+       imux->num_items++;
+
+       /* for digital mic select */
+       imux->items[imux->num_items].label = "Digital Mic";
+       imux->items[imux->num_items].index = 4;
+       imux->num_items++;
+
+       return 0;
+}
+
+static int vt2002P_parse_auto_config(struct hda_codec *codec)
+{
+       struct via_spec *spec = codec->spec;
+       int err;
+
+
+       err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL);
+       if (err < 0)
+               return err;
+
+       err = vt2002P_auto_fill_dac_nids(spec, &spec->autocfg);
+       if (err < 0)
+               return err;
+
+       if (!spec->autocfg.line_outs && !spec->autocfg.hp_pins[0])
+               return 0; /* can't find valid BIOS pin config */
+
+       err = vt2002P_auto_create_multi_out_ctls(spec, &spec->autocfg);
+       if (err < 0)
+               return err;
+       err = vt2002P_auto_create_hp_ctls(spec, spec->autocfg.hp_pins[0]);
+       if (err < 0)
+               return err;
+       err = vt2002P_auto_create_analog_input_ctls(spec, &spec->autocfg);
+       if (err < 0)
+               return err;
+
+       spec->multiout.max_channels = spec->multiout.num_dacs * 2;
+
+       fill_dig_outs(codec);
+
+       if (spec->kctls.list)
+               spec->mixers[spec->num_mixers++] = spec->kctls.list;
+
+       spec->input_mux = &spec->private_imux[0];
+
+       if (spec->hp_mux)
+               spec->mixers[spec->num_mixers++] = via_hp_mixer;
+
+       return 1;
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static struct hda_amp_list vt2002P_loopbacks[] = {
+       { 0x21, HDA_INPUT, 0 },
+       { 0x21, HDA_INPUT, 1 },
+       { 0x21, HDA_INPUT, 2 },
+       { } /* end */
+};
+#endif
+
+
+/* patch for vt2002P */
+static int patch_vt2002P(struct hda_codec *codec)
+{
+       struct via_spec *spec;
+       int err;
+
+       /* create a codec specific record */
+       spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+       if (spec == NULL)
+               return -ENOMEM;
+
+       codec->spec = spec;
+
+       /* automatic parse from the BIOS config */
+       err = vt2002P_parse_auto_config(codec);
+       if (err < 0) {
+               via_free(codec);
+               return err;
+       } else if (!err) {
+               printk(KERN_INFO "hda_codec: Cannot set up configuration "
+                      "from BIOS.  Using genenic mode...\n");
+       }
+
+       spec->init_verbs[spec->num_iverbs++]  = vt2002P_volume_init_verbs;
+       spec->init_verbs[spec->num_iverbs++] = vt2002P_uniwill_init_verbs;
+
+       spec->stream_name_analog = "VT2002P Analog";
+       spec->stream_analog_playback = &vt2002P_pcm_analog_playback;
+       spec->stream_analog_capture = &vt2002P_pcm_analog_capture;
+
+       spec->stream_name_digital = "VT2002P Digital";
+       spec->stream_digital_playback = &vt2002P_pcm_digital_playback;
+
+       if (!spec->adc_nids && spec->input_mux) {
+               spec->adc_nids = vt2002P_adc_nids;
+               spec->num_adc_nids = ARRAY_SIZE(vt2002P_adc_nids);
+               get_mux_nids(codec);
+               override_mic_boost(codec, 0x2b, 0, 3, 40);
+               override_mic_boost(codec, 0x29, 0, 3, 40);
+               spec->mixers[spec->num_mixers] = vt2002P_capture_mixer;
+               spec->num_mixers++;
+       }
+
+       codec->patch_ops = via_patch_ops;
+
+       codec->patch_ops.init = via_auto_init;
+       codec->patch_ops.unsol_event = via_unsol_event,
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+       spec->loopback.amplist = vt2002P_loopbacks;
+#endif
+
+       return 0;
+}
 /*
  * patch entries
  */
@@ -5085,6 +5738,8 @@ static struct hda_codec_preset snd_hda_preset_via[] = {
          .patch = patch_vt1716S},
        { .id = 0x1106a721, .name = "VT1716S",
          .patch = patch_vt1716S},
+       { .id = 0x11060438, .name = "VT2002P", .patch = patch_vt2002P},
+       { .id = 0x11064438, .name = "VT2002P", .patch = patch_vt2002P},
        {} /* terminator */
 };