ALSA: hda - Reduce click noise at power-saving
[pandora-kernel.git] / sound / pci / hda / patch_sigmatel.c
index d2fd8ef..00c702d 100644 (file)
@@ -100,6 +100,7 @@ enum {
        STAC_HP_M4,
        STAC_HP_DV5,
        STAC_HP_HDX,
+       STAC_HP_DV4_1222NR,
        STAC_92HD71BXX_MODELS
 };
 
@@ -193,6 +194,7 @@ struct sigmatel_spec {
        unsigned int gpio_dir;
        unsigned int gpio_data;
        unsigned int gpio_mute;
+       unsigned int gpio_led;
 
        /* stream */
        unsigned int stream_delay;
@@ -634,6 +636,40 @@ static int stac92xx_smux_enum_put(struct snd_kcontrol *kcontrol,
        return 0;
 }
 
+static unsigned int stac92xx_vref_set(struct hda_codec *codec,
+                                       hda_nid_t nid, unsigned int new_vref)
+{
+       int error;
+       unsigned int pincfg;
+       pincfg = snd_hda_codec_read(codec, nid, 0,
+                               AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+
+       pincfg &= 0xff;
+       pincfg &= ~(AC_PINCTL_VREFEN | AC_PINCTL_IN_EN | AC_PINCTL_OUT_EN);
+       pincfg |= new_vref;
+
+       if (new_vref == AC_PINCTL_VREF_HIZ)
+               pincfg |= AC_PINCTL_OUT_EN;
+       else
+               pincfg |= AC_PINCTL_IN_EN;
+
+       error = snd_hda_codec_write_cache(codec, nid, 0,
+                                       AC_VERB_SET_PIN_WIDGET_CONTROL, pincfg);
+       if (error < 0)
+               return error;
+       else
+               return 1;
+}
+
+static unsigned int stac92xx_vref_get(struct hda_codec *codec, hda_nid_t nid)
+{
+       unsigned int vref;
+       vref = snd_hda_codec_read(codec, nid, 0,
+                               AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+       vref &= AC_PINCTL_VREFEN;
+       return vref;
+}
+
 static int stac92xx_mux_enum_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
 {
        struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
@@ -995,6 +1031,17 @@ static struct hda_verb stac9205_core_init[] = {
                .private_value = verb_read | (verb_write << 16), \
        }
 
+#define DC_BIAS(xname, idx, nid) \
+       { \
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+               .name = xname, \
+               .index = idx, \
+               .info = stac92xx_dc_bias_info, \
+               .get = stac92xx_dc_bias_get, \
+               .put = stac92xx_dc_bias_put, \
+               .private_value = nid, \
+       }
+
 static struct snd_kcontrol_new stac9200_mixer[] = {
        HDA_CODEC_VOLUME("Master Playback Volume", 0xb, 0, HDA_OUTPUT),
        HDA_CODEC_MUTE("Master Playback Switch", 0xb, 0, HDA_OUTPUT),
@@ -1837,6 +1884,7 @@ static unsigned int *stac92hd71bxx_brd_tbl[STAC_92HD71BXX_MODELS] = {
        [STAC_HP_M4]            = NULL,
        [STAC_HP_DV5]           = NULL,
        [STAC_HP_HDX]           = NULL,
+       [STAC_HP_DV4_1222NR]    = NULL,
 };
 
 static const char *stac92hd71bxx_models[STAC_92HD71BXX_MODELS] = {
@@ -1848,6 +1896,7 @@ static const char *stac92hd71bxx_models[STAC_92HD71BXX_MODELS] = {
        [STAC_HP_M4] = "hp-m4",
        [STAC_HP_DV5] = "hp-dv5",
        [STAC_HP_HDX] = "hp-hdx",
+       [STAC_HP_DV4_1222NR] = "hp-dv4-1222nr",
 };
 
 static struct snd_pci_quirk stac92hd71bxx_cfg_tbl[] = {
@@ -1856,6 +1905,8 @@ static struct snd_pci_quirk stac92hd71bxx_cfg_tbl[] = {
                      "DFI LanParty", STAC_92HD71BXX_REF),
        SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101,
                      "DFI LanParty", STAC_92HD71BXX_REF),
+       SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x30fb,
+                     "HP dv4-1222nr", STAC_HP_DV4_1222NR),
        SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x3080,
                      "HP", STAC_HP_DV5),
        SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x30f0,
@@ -2291,6 +2342,8 @@ static struct snd_pci_quirk stac9205_cfg_tbl[] = {
        /* SigmaTel reference board */
        SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668,
                      "DFI LanParty", STAC_9205_REF),
+       SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0xfb30,
+                     "SigmaTel", STAC_9205_REF),
        SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101,
                      "DFI LanParty", STAC_9205_REF),
        /* Dell */
@@ -2545,7 +2598,8 @@ static int stac92xx_build_pcms(struct hda_codec *codec)
        return 0;
 }
 
-static unsigned int stac92xx_get_vref(struct hda_codec *codec, hda_nid_t nid)
+static unsigned int stac92xx_get_default_vref(struct hda_codec *codec,
+                                       hda_nid_t nid)
 {
        unsigned int pincap = snd_hda_query_pin_caps(codec, nid);
        pincap = (pincap & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT;
@@ -2599,15 +2653,108 @@ static int stac92xx_hp_switch_put(struct snd_kcontrol *kcontrol,
        return 1;
 }
 
-#define stac92xx_io_switch_info                snd_ctl_boolean_mono_info
+static int stac92xx_dc_bias_info(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_info *uinfo)
+{
+       int i;
+       static char *texts[] = {
+               "Mic In", "Line In", "Line Out"
+       };
+
+       struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct sigmatel_spec *spec = codec->spec;
+       hda_nid_t nid = kcontrol->private_value;
+
+       if (nid == spec->mic_switch || nid == spec->line_switch)
+               i = 3;
+       else
+               i = 2;
+
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+       uinfo->value.enumerated.items = i;
+       uinfo->count = 1;
+       if (uinfo->value.enumerated.item >= i)
+               uinfo->value.enumerated.item = i-1;
+       strcpy(uinfo->value.enumerated.name,
+               texts[uinfo->value.enumerated.item]);
+
+       return 0;
+}
+
+static int stac92xx_dc_bias_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;
+       unsigned int vref = stac92xx_vref_get(codec, nid);
+
+       if (vref == stac92xx_get_default_vref(codec, nid))
+               ucontrol->value.enumerated.item[0] = 0;
+       else if (vref == AC_PINCTL_VREF_GRD)
+               ucontrol->value.enumerated.item[0] = 1;
+       else if (vref == AC_PINCTL_VREF_HIZ)
+               ucontrol->value.enumerated.item[0] = 2;
+
+       return 0;
+}
+
+static int stac92xx_dc_bias_put(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+       unsigned int new_vref = 0;
+       int error;
+       hda_nid_t nid = kcontrol->private_value;
+
+       if (ucontrol->value.enumerated.item[0] == 0)
+               new_vref = stac92xx_get_default_vref(codec, nid);
+       else if (ucontrol->value.enumerated.item[0] == 1)
+               new_vref = AC_PINCTL_VREF_GRD;
+       else if (ucontrol->value.enumerated.item[0] == 2)
+               new_vref = AC_PINCTL_VREF_HIZ;
+       else
+               return 0;
+
+       if (new_vref != stac92xx_vref_get(codec, nid)) {
+               error = stac92xx_vref_set(codec, nid, new_vref);
+               return error;
+       }
+
+       return 0;
+}
+
+static int stac92xx_io_switch_info(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_info *uinfo)
+{
+       static char *texts[2];
+       struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct sigmatel_spec *spec = codec->spec;
+
+       if (kcontrol->private_value == spec->line_switch)
+               texts[0] = "Line In";
+       else
+               texts[0] = "Mic In";
+       texts[1] = "Line Out";
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+       uinfo->value.enumerated.items = 2;
+       uinfo->count = 1;
+
+       if (uinfo->value.enumerated.item >= 2)
+               uinfo->value.enumerated.item = 1;
+       strcpy(uinfo->value.enumerated.name,
+               texts[uinfo->value.enumerated.item]);
+
+       return 0;
+}
 
 static int stac92xx_io_switch_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
 {
        struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
        struct sigmatel_spec *spec = codec->spec;
-       int io_idx = kcontrol-> private_value & 0xff;
+       hda_nid_t nid = kcontrol->private_value;
+       int io_idx = (nid == spec->mic_switch) ? 1 : 0;
 
-       ucontrol->value.integer.value[0] = spec->io_switch[io_idx];
+       ucontrol->value.enumerated.item[0] = spec->io_switch[io_idx];
        return 0;
 }
 
@@ -2615,9 +2762,9 @@ static int stac92xx_io_switch_put(struct snd_kcontrol *kcontrol, struct snd_ctl_
 {
         struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
        struct sigmatel_spec *spec = codec->spec;
-        hda_nid_t nid = kcontrol->private_value >> 8;
-       int io_idx = kcontrol-> private_value & 0xff;
-       unsigned short val = !!ucontrol->value.integer.value[0];
+       hda_nid_t nid = kcontrol->private_value;
+       int io_idx = (nid == spec->mic_switch) ? 1 : 0;
+       unsigned short val = !!ucontrol->value.enumerated.item[0];
 
        spec->io_switch[io_idx] = val;
 
@@ -2626,7 +2773,7 @@ static int stac92xx_io_switch_put(struct snd_kcontrol *kcontrol, struct snd_ctl_
        else {
                unsigned int pinctl = AC_PINCTL_IN_EN;
                if (io_idx) /* set VREF for mic */
-                       pinctl |= stac92xx_get_vref(codec, nid);
+                       pinctl |= stac92xx_get_default_vref(codec, nid);
                stac92xx_auto_set_pinctl(codec, nid, pinctl);
        }
 
@@ -2707,7 +2854,8 @@ enum {
        STAC_CTL_WIDGET_AMP_VOL,
        STAC_CTL_WIDGET_HP_SWITCH,
        STAC_CTL_WIDGET_IO_SWITCH,
-       STAC_CTL_WIDGET_CLFE_SWITCH
+       STAC_CTL_WIDGET_CLFE_SWITCH,
+       STAC_CTL_WIDGET_DC_BIAS
 };
 
 static struct snd_kcontrol_new stac92xx_control_templates[] = {
@@ -2719,6 +2867,7 @@ static struct snd_kcontrol_new stac92xx_control_templates[] = {
        STAC_CODEC_HP_SWITCH(NULL),
        STAC_CODEC_IO_SWITCH(NULL, 0),
        STAC_CODEC_CLFE_SWITCH(NULL, 0),
+       DC_BIAS(NULL, 0, 0),
 };
 
 /* add dynamic controls */
@@ -2782,6 +2931,34 @@ static struct snd_kcontrol_new stac_input_src_temp = {
        .put = stac92xx_mux_enum_put,
 };
 
+static inline int stac92xx_add_jack_mode_control(struct hda_codec *codec,
+                                               hda_nid_t nid, int idx)
+{
+       int def_conf = snd_hda_codec_get_pincfg(codec, nid);
+       int control = 0;
+       struct sigmatel_spec *spec = codec->spec;
+       char name[22];
+
+       if (!((get_defcfg_connect(def_conf)) & AC_JACK_PORT_FIXED)) {
+               if (stac92xx_get_default_vref(codec, nid) == AC_PINCTL_VREF_GRD
+                       && nid == spec->line_switch)
+                       control = STAC_CTL_WIDGET_IO_SWITCH;
+               else if (snd_hda_query_pin_caps(codec, nid)
+                       & (AC_PINCAP_VREF_GRD << AC_PINCAP_VREF_SHIFT))
+                       control = STAC_CTL_WIDGET_DC_BIAS;
+               else if (nid == spec->mic_switch)
+                       control = STAC_CTL_WIDGET_IO_SWITCH;
+       }
+
+       if (control) {
+               strcpy(name, auto_pin_cfg_labels[idx]);
+               return stac92xx_add_control(codec->spec, control,
+                                       strcat(name, " Jack Mode"), nid);
+       }
+
+       return 0;
+}
+
 static int stac92xx_add_input_source(struct sigmatel_spec *spec)
 {
        struct snd_kcontrol_new *knew;
@@ -3144,7 +3321,9 @@ static int stac92xx_auto_create_multi_out_ctls(struct hda_codec *codec,
                                               const struct auto_pin_cfg *cfg)
 {
        struct sigmatel_spec *spec = codec->spec;
+       hda_nid_t nid;
        int err;
+       int idx;
 
        err = create_multi_out_ctls(codec, cfg->line_outs, cfg->line_out_pins,
                                    spec->multiout.dac_nids,
@@ -3161,20 +3340,13 @@ static int stac92xx_auto_create_multi_out_ctls(struct hda_codec *codec,
                        return err;
        }
 
-       if (spec->line_switch) {
-               err = stac92xx_add_control(spec, STAC_CTL_WIDGET_IO_SWITCH,
-                                          "Line In as Output Switch",
-                                          spec->line_switch << 8);
-               if (err < 0)
-                       return err;
-       }
-
-       if (spec->mic_switch) {
-               err = stac92xx_add_control(spec, STAC_CTL_WIDGET_IO_SWITCH,
-                                          "Mic as Output Switch",
-                                          (spec->mic_switch << 8) | 1);
-               if (err < 0)
-                       return err;
+       for (idx = AUTO_PIN_MIC; idx <= AUTO_PIN_FRONT_LINE; idx++) {
+               nid = cfg->input_pins[idx];
+               if (nid) {
+                       err = stac92xx_add_jack_mode_control(codec, nid, idx);
+                       if (err < 0)
+                               return err;
+               }
        }
 
        return 0;
@@ -3639,6 +3811,8 @@ static int stac92xx_parse_auto_config(struct hda_codec *codec, hda_nid_t dig_out
                err = snd_hda_attach_beep_device(codec, nid);
                if (err < 0)
                        return err;
+               /* IDT/STAC codecs have linear beep tone parameter */
+               codec->beep->linear_tone = 1;
                /* if no beep switch is available, make its own one */
                caps = query_amp_caps(codec, nid, HDA_OUTPUT);
                if (codec->beep &&
@@ -3861,7 +4035,7 @@ static void stac_gpio_set(struct hda_codec *codec, unsigned int mask,
                           AC_VERB_SET_GPIO_DATA, gpiostate); /* sync */
 }
 
-#ifdef CONFIG_SND_JACK
+#ifdef CONFIG_SND_HDA_INPUT_JACK
 static void stac92xx_free_jack_priv(struct snd_jack *jack)
 {
        struct sigmatel_jack *jacks = jack->private_data;
@@ -3873,7 +4047,7 @@ static void stac92xx_free_jack_priv(struct snd_jack *jack)
 static int stac92xx_add_jack(struct hda_codec *codec,
                hda_nid_t nid, int type)
 {
-#ifdef CONFIG_SND_JACK
+#ifdef CONFIG_SND_HDA_INPUT_JACK
        struct sigmatel_spec *spec = codec->spec;
        struct sigmatel_jack *jack;
        int def_conf = snd_hda_codec_get_pincfg(codec, nid);
@@ -4082,7 +4256,7 @@ static int stac92xx_init(struct hda_codec *codec)
                        unsigned int pinctl, conf;
                        if (i == AUTO_PIN_MIC || i == AUTO_PIN_FRONT_MIC) {
                                /* for mic pins, force to initialize */
-                               pinctl = stac92xx_get_vref(codec, nid);
+                               pinctl = stac92xx_get_default_vref(codec, nid);
                                pinctl |= AC_PINCTL_IN_EN;
                                stac92xx_auto_set_pinctl(codec, nid, pinctl);
                        } else {
@@ -4162,7 +4336,7 @@ static int stac92xx_init(struct hda_codec *codec)
 
 static void stac92xx_free_jacks(struct hda_codec *codec)
 {
-#ifdef CONFIG_SND_JACK
+#ifdef CONFIG_SND_HDA_INPUT_JACK
        /* free jack instances manually when clearing/reconfiguring */
        struct sigmatel_spec *spec = codec->spec;
        if (!codec->bus->shutdown && spec->jacks.list) {
@@ -4535,17 +4709,19 @@ static int stac92xx_resume(struct hda_codec *codec)
        return 0;
 }
 
-
 /*
- * using power check for controlling mute led of HP HDX notebooks
+ * using power check for controlling mute led of HP notebooks
  * check for mute state only on Speakers (nid = 0x10)
  *
  * For this feature CONFIG_SND_HDA_POWER_SAVE is needed, otherwise
  * the LED is NOT working properly !
+ *
+ * Changed name to reflect that it now works for any designated
+ * model, not just HP HDX.
  */
 
 #ifdef CONFIG_SND_HDA_POWER_SAVE
-static int stac92xx_hp_hdx_check_power_status(struct hda_codec *codec,
+static int stac92xx_hp_check_power_status(struct hda_codec *codec,
                                              hda_nid_t nid)
 {
        struct sigmatel_spec *spec = codec->spec;
@@ -4553,9 +4729,9 @@ static int stac92xx_hp_hdx_check_power_status(struct hda_codec *codec,
        if (nid == 0x10) {
                if (snd_hda_codec_amp_read(codec, nid, 0, HDA_OUTPUT, 0) &
                    HDA_AMP_MUTE)
-                       spec->gpio_data &= ~0x08;  /* orange */
+                       spec->gpio_data &= ~spec->gpio_led; /* orange */
                else
-                       spec->gpio_data |= 0x08;   /* white */
+                       spec->gpio_data |= spec->gpio_led; /* white */
 
                stac_gpio_set(codec, spec->gpio_mask,
                              spec->gpio_dir,
@@ -4569,6 +4745,20 @@ static int stac92xx_hp_hdx_check_power_status(struct hda_codec *codec,
 static int stac92xx_suspend(struct hda_codec *codec, pm_message_t state)
 {
        struct sigmatel_spec *spec = codec->spec;
+       int i;
+       hda_nid_t nid;
+
+       /* reset each pin before powering down DAC/ADC to avoid click noise */
+       nid = codec->start_nid;
+       for (i = 0; i < codec->num_nodes; i++, nid++) {
+               unsigned int wcaps = get_wcaps(codec, nid);
+               unsigned int wid_type = (wcaps & AC_WCAP_TYPE) >>
+                       AC_WCAP_TYPE_SHIFT;
+               if (wid_type == AC_WID_PIN)
+                       snd_hda_codec_read(codec, nid, 0,
+                               AC_VERB_SET_PIN_WIDGET_CONTROL, 0);
+       }
+
        if (spec->eapd_mask)
                stac_gpio_set(codec, spec->gpio_mask,
                                spec->gpio_dir, spec->gpio_data &
@@ -5201,6 +5391,15 @@ again:
        if (get_wcaps(codec, 0xa) & AC_WCAP_IN_AMP)
                snd_hda_sequence_write_cache(codec, unmute_init);
 
+       /* Some HP machines seem to have unstable codec communications
+        * especially with ATI fglrx driver.  For recovering from the
+        * CORB/RIRB stall, allow the BUS reset and keep always sync
+        */
+       if (spec->board_config == STAC_HP_DV5) {
+               codec->bus->sync_write = 1;
+               codec->bus->allow_bus_reset = 1;
+       }
+
        spec->aloopback_ctl = stac92hd71bxx_loopback;
        spec->aloopback_mask = 0x50;
        spec->aloopback_shift = 0;
@@ -5234,6 +5433,15 @@ again:
                spec->num_smuxes = 0;
                spec->num_dmuxes = 1;
                break;
+       case STAC_HP_DV4_1222NR:
+               spec->num_dmics = 1;
+               /* I don't know if it needs 1 or 2 smuxes - will wait for
+                * bug reports to fix if needed
+                */
+               spec->num_smuxes = 1;
+               spec->num_dmuxes = 1;
+               spec->gpio_led = 0x01;
+               /* fallthrough */
        case STAC_HP_DV5:
                snd_hda_codec_set_pincfg(codec, 0x0d, 0x90170010);
                stac92xx_auto_set_pinctl(codec, 0x0d, AC_PINCTL_OUT_EN);
@@ -5242,22 +5450,21 @@ again:
                spec->num_dmics = 1;
                spec->num_dmuxes = 1;
                spec->num_smuxes = 1;
-               /*
-                * For controlling MUTE LED on HP HDX16/HDX18 notebooks,
-                * the CONFIG_SND_HDA_POWER_SAVE is needed to be set.
-                */
-#ifdef CONFIG_SND_HDA_POWER_SAVE
                /* orange/white mute led on GPIO3, orange=0, white=1 */
-               spec->gpio_mask |= 0x08;
-               spec->gpio_dir  |= 0x08;
-               spec->gpio_data |= 0x08;  /* set to white */
+               spec->gpio_led = 0x08;
+               break;
+       }
 
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+       if (spec->gpio_led) {
+               spec->gpio_mask |= spec->gpio_led;
+               spec->gpio_dir |= spec->gpio_led;
+               spec->gpio_data |= spec->gpio_led;
                /* register check_power_status callback. */
                codec->patch_ops.check_power_status =
-                   stac92xx_hp_hdx_check_power_status;
+                       stac92xx_hp_check_power_status;
+       }
 #endif 
-               break;
-       };
 
        spec->multiout.dac_nids = spec->dac_nids;
        if (spec->dinput_mux)
@@ -5282,7 +5489,7 @@ again:
        codec->proc_widget_hook = stac92hd7x_proc_hook;
 
        return 0;
-};
+}
 
 static int patch_stac922x(struct hda_codec *codec)
 {
@@ -5437,7 +5644,7 @@ static int patch_stac927x(struct hda_codec *codec)
                        /* correct the device field to SPDIF out */
                        snd_hda_codec_set_pincfg(codec, 0x21, 0x01442070);
                        break;
-               };
+               }
                /* configure the analog microphone on some laptops */
                snd_hda_codec_set_pincfg(codec, 0x0c, 0x90a79130);
                /* correct the front output jack as a hp out */
@@ -5747,6 +5954,7 @@ static struct hda_codec_preset snd_hda_preset_sigmatel[] = {
        { .id = 0x83847661, .name = "CXD9872RD/K", .patch = patch_stac9872 },
        { .id = 0x83847662, .name = "STAC9872AK", .patch = patch_stac9872 },
        { .id = 0x83847664, .name = "CXD9872AKD", .patch = patch_stac9872 },
+       { .id = 0x83847698, .name = "STAC9205", .patch = patch_stac9205 },
        { .id = 0x838476a0, .name = "STAC9205", .patch = patch_stac9205 },
        { .id = 0x838476a1, .name = "STAC9205D", .patch = patch_stac9205 },
        { .id = 0x838476a2, .name = "STAC9204", .patch = patch_stac9205 },