[ALSA] ac97 - fix various issues with AD1986/AD1986A support
authorRandy Cushman <rcushman_linux@earthlink.net>
Fri, 22 Dec 2006 11:44:25 +0000 (12:44 +0100)
committerJaroslav Kysela <perex@suse.cz>
Fri, 9 Feb 2007 08:02:46 +0000 (09:02 +0100)
Previously, ac97_codec.c was coded to support AD1986 and AD1986A
CODECs using code written for the AD1985 CODEC.  This allowed the
LINE_OUT and HEADPHONE jacks to function properly, however register
differences between the CODECs prevented line and microphone inputs
from functioning.
Specifically, this patch fixes issues with the following mixer
controls:  'V_REFOUT', 'Spread Front to Surround and Center/LFE',
'Exchange Front/Surround', 'Surround Jack Mode', and 'Channel Mode'.
This patch removes the undocumented AD1888 control
'High Pass Filter Enable' and adds the new control
'Exchange Mic/Line In'.

Signed-off-by: Randy Cushman <rcushman_linux@earthlink.net>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@suse.cz>
include/sound/ac97_codec.h
sound/pci/ac97/ac97_codec.c
sound/pci/ac97/ac97_patch.c
sound/pci/ac97/ac97_patch.h

index 5d3f0d8c0e61e20384b4a7bde26a78bf46801e36..246ac23534bdeae9693032f5ee09ac88ffbc91fe 100644 (file)
@@ -503,6 +503,7 @@ struct snd_ac97 {
                        unsigned short id[3];           // codec IDs (lower 16-bit word)
                        unsigned short pcmreg[3];       // PCM registers
                        unsigned short codec_cfg[3];    // CODEC_CFG bits
+                       unsigned char swap_mic_linein;  // AD1986/AD1986A only
                } ad18xx;
                unsigned int dev_flags;         /* device specific */
        } spec;
index 9da4977c0a0c2f08ff8d22da5f49f31ca5927bdc..bd8cfdcfbdf1f24aed53abb5e90d4cc1920220c9 100644 (file)
@@ -111,7 +111,7 @@ static const struct ac97_codec_id snd_ac97_codec_ids[] = {
 { 0x41445372, 0xffffffff, "AD1981A",           patch_ad1981a,  NULL },
 { 0x41445374, 0xffffffff, "AD1981B",           patch_ad1981b,  NULL },
 { 0x41445375, 0xffffffff, "AD1985",            patch_ad1985,   NULL },
-{ 0x41445378, 0xffffffff, "AD1986",            patch_ad1985,   NULL },
+{ 0x41445378, 0xffffffff, "AD1986",            patch_ad1986,   NULL },
 { 0x414c4300, 0xffffff00, "ALC100,100P",       NULL,           NULL },
 { 0x414c4710, 0xfffffff0, "ALC200,200P",       NULL,           NULL },
 { 0x414c4721, 0xffffffff, "ALC650D",           NULL,   NULL }, /* already patched */
index bd27531a0f0e265a253dac69ff71a5dc49217f98..f5b4b44bbdab27cf6c0711df83b141417afc3b6f 100644 (file)
@@ -1626,7 +1626,7 @@ int patch_ad1886(struct snd_ac97 * ac97)
        return 0;
 }
 
-/* MISC bits */
+/* MISC bits (AD1888/AD1980/AD1985 register 0x76) */
 #define AC97_AD198X_MBC                0x0003  /* mic boost */
 #define AC97_AD198X_MBC_20     0x0000  /* +20dB */
 #define AC97_AD198X_MBC_10     0x0001  /* +10dB */
@@ -1650,6 +1650,83 @@ int patch_ad1886(struct snd_ac97 * ac97)
 #define AC97_AD198X_AC97NC     0x4000  /* AC97 no compatible mode */
 #define AC97_AD198X_DACZ       0x8000  /* DAC zero-fill mode */
 
+/* MISC 1 bits (AD1986 register 0x76) */
+#define AC97_AD1986_MBC                0x0003  /* mic boost */
+#define AC97_AD1986_MBC_20     0x0000  /* +20dB */
+#define AC97_AD1986_MBC_10     0x0001  /* +10dB */
+#define AC97_AD1986_MBC_30     0x0002  /* +30dB */
+#define AC97_AD1986_LISEL0     0x0004  /* LINE_IN select bit 0 */
+#define AC97_AD1986_LISEL1     0x0008  /* LINE_IN select bit 1 */
+#define AC97_AD1986_LISEL_MASK (AC97_AD1986_LISEL1 | AC97_AD1986_LISEL0)
+#define AC97_AD1986_LISEL_LI   0x0000  /* LINE_IN pins as LINE_IN source */
+#define AC97_AD1986_LISEL_SURR 0x0004  /* SURROUND pins as LINE_IN source */
+#define AC97_AD1986_LISEL_MIC  0x0008  /* MIC_1/2 pins as LINE_IN source */
+#define AC97_AD1986_SRU                0x0010  /* sample rate unlock */
+#define AC97_AD1986_SOSEL      0x0020  /* SURROUND_OUT amplifiers input sel */
+#define AC97_AD1986_2MIC       0x0040  /* 2-channel mic select */
+#define AC97_AD1986_SPRD       0x0080  /* SPREAD enable */
+#define AC97_AD1986_DMIX0      0x0100  /* downmix mode: */
+                                       /*  0 = 6-to-4, 1 = 6-to-2 downmix */
+#define AC97_AD1986_DMIX1      0x0200  /* downmix mode: 1 = enabled */
+#define AC97_AD1986_CLDIS      0x0800  /* center/lfe disable */
+#define AC97_AD1986_SODIS      0x1000  /* SURROUND_OUT disable */
+#define AC97_AD1986_MSPLT      0x2000  /* mute split (read only 1) */
+#define AC97_AD1986_AC97NC     0x4000  /* AC97 no compatible mode (r/o 1) */
+#define AC97_AD1986_DACZ       0x8000  /* DAC zero-fill mode */
+
+/* MISC 2 bits (AD1986 register 0x70) */
+#define AC97_AD_MISC2          0x70    /* Misc Control Bits 2 (AD1986) */
+
+#define AC97_AD1986_CVREF0     0x0004  /* C/LFE VREF_OUT 2.25V */
+#define AC97_AD1986_CVREF1     0x0008  /* C/LFE VREF_OUT 0V */
+#define AC97_AD1986_CVREF2     0x0010  /* C/LFE VREF_OUT 3.7V */
+#define AC97_AD1986_CVREF_MASK \
+       (AC97_AD1986_CVREF2 | AC97_AD1986_CVREF1 | AC97_AD1986_CVREF0)
+#define AC97_AD1986_JSMAP      0x0020  /* Jack Sense Mapping 1 = alternate */
+#define AC97_AD1986_MMDIS      0x0080  /* Mono Mute Disable */
+#define AC97_AD1986_MVREF0     0x0400  /* MIC VREF_OUT 2.25V */
+#define AC97_AD1986_MVREF1     0x0800  /* MIC VREF_OUT 0V */
+#define AC97_AD1986_MVREF2     0x1000  /* MIC VREF_OUT 3.7V */
+#define AC97_AD1986_MVREF_MASK \
+       (AC97_AD1986_MVREF2 | AC97_AD1986_MVREF1 | AC97_AD1986_MVREF0)
+
+/* MISC 3 bits (AD1986 register 0x7a) */
+#define AC97_AD_MISC3          0x7a    /* Misc Control Bits 3 (AD1986) */
+
+#define AC97_AD1986_MMIX       0x0004  /* Mic Mix, left/right */
+#define AC97_AD1986_GPO                0x0008  /* General Purpose Out */
+#define AC97_AD1986_LOHPEN     0x0010  /* LINE_OUT headphone drive */
+#define AC97_AD1986_LVREF0     0x0100  /* LINE_OUT VREF_OUT 2.25V */
+#define AC97_AD1986_LVREF1     0x0200  /* LINE_OUT VREF_OUT 0V */
+#define AC97_AD1986_LVREF2     0x0400  /* LINE_OUT VREF_OUT 3.7V */
+#define AC97_AD1986_LVREF_MASK \
+       (AC97_AD1986_LVREF2 | AC97_AD1986_LVREF1 | AC97_AD1986_LVREF0)
+#define AC97_AD1986_JSINVA     0x0800  /* Jack Sense Invert SENSE_A */
+#define AC97_AD1986_LOSEL      0x1000  /* LINE_OUT amplifiers input select */
+#define AC97_AD1986_HPSEL0     0x2000  /* Headphone amplifiers */
+                                       /*   input select Surround DACs */
+#define AC97_AD1986_HPSEL1     0x4000  /* Headphone amplifiers input */
+                                       /*   select C/LFE DACs */
+#define AC97_AD1986_JSINVB     0x8000  /* Jack Sense Invert SENSE_B */
+
+/* Serial Config bits (AD1986 register 0x74) (incomplete) */
+#define AC97_AD1986_OMS0       0x0100  /* Optional Mic Selector bit 0 */
+#define AC97_AD1986_OMS1       0x0200  /* Optional Mic Selector bit 1 */
+#define AC97_AD1986_OMS2       0x0400  /* Optional Mic Selector bit 2 */
+#define AC97_AD1986_OMS_MASK \
+       (AC97_AD1986_OMS2 | AC97_AD1986_OMS1 | AC97_AD1986_OMS0)
+#define AC97_AD1986_OMS_M      0x0000  /* MIC_1/2 pins are MIC sources */
+#define AC97_AD1986_OMS_L      0x0100  /* LINE_IN pins are MIC sources */
+#define AC97_AD1986_OMS_C      0x0200  /* Center/LFE pins are MCI sources */
+#define AC97_AD1986_OMS_MC     0x0400  /* Mix of MIC and C/LFE pins */
+                                       /*   are MIC sources */
+#define AC97_AD1986_OMS_ML     0x0500  /* MIX of MIC and LINE_IN pins */
+                                       /*   are MIC sources */
+#define AC97_AD1986_OMS_LC     0x0600  /* MIX of LINE_IN and C/LFE pins */
+                                       /*   are MIC sources */
+#define AC97_AD1986_OMS_MLC    0x0700  /* MIX of MIC, LINE_IN, C/LFE pins */
+                                       /*   are MIC sources */
+
 
 static int snd_ac97_ad198x_spdif_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
 {
@@ -2105,6 +2182,294 @@ int patch_ad1985(struct snd_ac97 * ac97)
        return 0;
 }
 
+static int snd_ac97_ad1986_bool_info(struct snd_kcontrol *kcontrol,
+                                    struct snd_ctl_elem_info *uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+       uinfo->count = 1;
+       uinfo->value.integer.min = 0;
+       uinfo->value.integer.max = 1;
+       return 0;
+}
+
+static int snd_ac97_ad1986_lososel_get(struct snd_kcontrol *kcontrol,
+                                      struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+       unsigned short val;
+
+       val = ac97->regs[AC97_AD_MISC3];
+       ucontrol->value.integer.value[0] = (val & AC97_AD1986_LOSEL) != 0;
+       return 0;
+}
+
+static int snd_ac97_ad1986_lososel_put(struct snd_kcontrol *kcontrol,
+                                      struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+       int ret0;
+       int ret1;
+       int sprd = (ac97->regs[AC97_AD_MISC] & AC97_AD1986_SPRD) != 0;
+
+       ret0 = snd_ac97_update_bits(ac97, AC97_AD_MISC3, AC97_AD1986_LOSEL,
+                                       ucontrol->value.integer.value[0] != 0
+                                   ? AC97_AD1986_LOSEL : 0);
+       if (ret0 < 0)
+               return ret0;
+
+       /* SOSEL is set to values of "Spread" or "Exchange F/S" controls */
+       ret1 = snd_ac97_update_bits(ac97, AC97_AD_MISC, AC97_AD1986_SOSEL,
+                                   (ucontrol->value.integer.value[0] != 0
+                                    || sprd)
+                                   ? AC97_AD1986_SOSEL : 0);
+       if (ret1 < 0)
+               return ret1;
+
+       return (ret0 > 0 || ret1 > 0) ? 1 : 0;
+}
+
+static int snd_ac97_ad1986_spread_get(struct snd_kcontrol *kcontrol,
+                                     struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+       unsigned short val;
+
+       val = ac97->regs[AC97_AD_MISC];
+       ucontrol->value.integer.value[0] = (val & AC97_AD1986_SPRD) != 0;
+       return 0;
+}
+
+static int snd_ac97_ad1986_spread_put(struct snd_kcontrol *kcontrol,
+                                     struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+       int ret0;
+       int ret1;
+       int sprd = (ac97->regs[AC97_AD_MISC3] & AC97_AD1986_LOSEL) != 0;
+
+       ret0 = snd_ac97_update_bits(ac97, AC97_AD_MISC, AC97_AD1986_SPRD,
+                                       ucontrol->value.integer.value[0] != 0
+                                   ? AC97_AD1986_SPRD : 0);
+       if (ret0 < 0)
+               return ret0;
+
+       /* SOSEL is set to values of "Spread" or "Exchange F/S" controls */
+       ret1 = snd_ac97_update_bits(ac97, AC97_AD_MISC, AC97_AD1986_SOSEL,
+                                   (ucontrol->value.integer.value[0] != 0
+                                    || sprd)
+                                   ? AC97_AD1986_SOSEL : 0);
+       if (ret1 < 0)
+               return ret1;
+
+       return (ret0 > 0 || ret1 > 0) ? 1 : 0;
+}
+
+static int snd_ac97_ad1986_miclisel_get(struct snd_kcontrol *kcontrol,
+                                       struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+
+       ucontrol->value.integer.value[0] = ac97->spec.ad18xx.swap_mic_linein;
+       return 0;
+}
+
+static int snd_ac97_ad1986_miclisel_put(struct snd_kcontrol *kcontrol,
+                                       struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+       unsigned char swap = ucontrol->value.integer.value[0] != 0;
+
+       if (swap != ac97->spec.ad18xx.swap_mic_linein) {
+               ac97->spec.ad18xx.swap_mic_linein = swap;
+               if (ac97->build_ops->update_jacks)
+                       ac97->build_ops->update_jacks(ac97);
+               return 1;
+       }
+       return 0;
+}
+
+static int snd_ac97_ad1986_vrefout_get(struct snd_kcontrol *kcontrol,
+                                      struct snd_ctl_elem_value *ucontrol)
+{
+       /* Use MIC_1/2 V_REFOUT as the "get" value */
+       struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+       unsigned short val;
+       unsigned short reg = ac97->regs[AC97_AD_MISC2];
+       if ((reg & AC97_AD1986_MVREF0) != 0)
+               val = 2;
+       else if ((reg & AC97_AD1986_MVREF1) != 0)
+               val = 3;
+       else if ((reg & AC97_AD1986_MVREF2) != 0)
+               val = 1;
+       else
+               val = 0;
+       ucontrol->value.enumerated.item[0] = val;
+       return 0;
+}
+
+static int snd_ac97_ad1986_vrefout_put(struct snd_kcontrol *kcontrol,
+                                      struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+       unsigned short cval;
+       unsigned short lval;
+       unsigned short mval;
+       int cret;
+       int lret;
+       int mret;
+
+       switch (ucontrol->value.enumerated.item[0])
+       {
+       case 0: /* High-Z */
+               cval = 0;
+               lval = 0;
+               mval = 0;
+               break;
+       case 1: /* 3.7 V */
+               cval = AC97_AD1986_CVREF2;
+               lval = AC97_AD1986_LVREF2;
+               mval = AC97_AD1986_MVREF2;
+               break;
+       case 2: /* 2.25 V */
+               cval = AC97_AD1986_CVREF0;
+               lval = AC97_AD1986_LVREF0;
+               mval = AC97_AD1986_MVREF0;
+               break;
+       case 3: /* 0 V */
+               cval = AC97_AD1986_CVREF1;
+               lval = AC97_AD1986_LVREF1;
+               mval = AC97_AD1986_MVREF1;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       cret = snd_ac97_update_bits(ac97, AC97_AD_MISC2,
+                                   AC97_AD1986_CVREF_MASK, cval);
+       if (cret < 0)
+               return cret;
+       lret = snd_ac97_update_bits(ac97, AC97_AD_MISC3,
+                                   AC97_AD1986_LVREF_MASK, lval);
+       if (lret < 0)
+               return lret;
+       mret = snd_ac97_update_bits(ac97, AC97_AD_MISC2,
+                                   AC97_AD1986_MVREF_MASK, mval);
+       if (mret < 0)
+               return mret;
+
+       return (cret > 0 || lret > 0 || mret > 0) ? 1 : 0;
+}
+
+static const struct snd_kcontrol_new snd_ac97_ad1986_controls[] = {
+       AC97_SINGLE("Exchange Center/LFE", AC97_AD_SERIAL_CFG, 3, 1, 0),
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name = "Exchange Front/Surround",
+               .info = snd_ac97_ad1986_bool_info,
+               .get = snd_ac97_ad1986_lososel_get,
+               .put = snd_ac97_ad1986_lososel_put
+       },
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name = "Exchange Mic/Line In",
+               .info = snd_ac97_ad1986_bool_info,
+               .get = snd_ac97_ad1986_miclisel_get,
+               .put = snd_ac97_ad1986_miclisel_put
+       },
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name = "Spread Front to Surround and Center/LFE",
+               .info = snd_ac97_ad1986_bool_info,
+               .get = snd_ac97_ad1986_spread_get,
+               .put = snd_ac97_ad1986_spread_put
+       },
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name = "Downmix",
+               .info = snd_ac97_ad1888_downmix_info,
+               .get = snd_ac97_ad1888_downmix_get,
+               .put = snd_ac97_ad1888_downmix_put
+       },
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name = "V_REFOUT",
+               .info = snd_ac97_ad1985_vrefout_info,
+               .get = snd_ac97_ad1986_vrefout_get,
+               .put = snd_ac97_ad1986_vrefout_put
+       },
+       AC97_SURROUND_JACK_MODE_CTL,
+       AC97_CHANNEL_MODE_CTL,
+
+       AC97_SINGLE("Headphone Jack Sense", AC97_AD_JACK_SPDIF, 10, 1, 0),
+       AC97_SINGLE("Line Jack Sense", AC97_AD_JACK_SPDIF, 12, 1, 0)
+};
+
+static void ad1986_update_jacks(struct snd_ac97 *ac97)
+{
+       unsigned short misc_val = 0;
+       unsigned short ser_val;
+
+       /* disable SURROUND and CENTER/LFE if not surround mode */
+       if (! is_surround_on(ac97))
+               misc_val |= AC97_AD1986_SODIS;
+       if (! is_clfe_on(ac97))
+               misc_val |= AC97_AD1986_CLDIS;
+
+       /* select line input (default=LINE_IN, SURROUND or MIC_1/2) */
+       if (is_shared_linein(ac97))
+               misc_val |= AC97_AD1986_LISEL_SURR;
+       else if (ac97->spec.ad18xx.swap_mic_linein != 0)
+               misc_val |= AC97_AD1986_LISEL_MIC;
+       snd_ac97_update_bits(ac97, AC97_AD_MISC,
+                            AC97_AD1986_SODIS | AC97_AD1986_CLDIS |
+                            AC97_AD1986_LISEL_MASK,
+                            misc_val);
+
+       /* select microphone input (MIC_1/2, Center/LFE or LINE_IN) */
+       if (is_shared_micin(ac97))
+               ser_val = AC97_AD1986_OMS_C;
+       else if (ac97->spec.ad18xx.swap_mic_linein != 0)
+               ser_val = AC97_AD1986_OMS_L;
+       else
+               ser_val = AC97_AD1986_OMS_M;
+       snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG,
+                            AC97_AD1986_OMS_MASK,
+                            ser_val);
+}
+
+static int patch_ad1986_specific(struct snd_ac97 *ac97)
+{
+       int err;
+
+       if ((err = patch_build_controls(ac97, &snd_ac97_ad198x_2cmic, 1)) < 0)
+               return err;
+
+       return patch_build_controls(ac97, snd_ac97_ad1986_controls,
+                                   ARRAY_SIZE(snd_ac97_ad1985_controls));
+}
+
+static struct snd_ac97_build_ops patch_ad1986_build_ops = {
+       .build_post_spdif = patch_ad198x_post_spdif,
+       .build_specific = patch_ad1986_specific,
+#ifdef CONFIG_PM
+       .resume = ad18xx_resume,
+#endif
+       .update_jacks = ad1986_update_jacks,
+};
+
+int patch_ad1986(struct snd_ac97 * ac97)
+{
+       patch_ad1881(ac97);
+       ac97->build_ops = &patch_ad1986_build_ops;
+       ac97->flags |= AC97_STEREO_MUTES;
+
+       /* update current jack configuration */
+       ad1986_update_jacks(ac97);
+
+       return 0;
+}
+
+
 /*
  * realtek ALC65x/850 codecs
  */
index 741979217207501992d8e9493923ff0bfb498f0f..94340daaaf1f7a64835c2923b0780230fd01a260 100644 (file)
@@ -48,6 +48,7 @@ int patch_ad1980(struct snd_ac97 * ac97);
 int patch_ad1981a(struct snd_ac97 * ac97);
 int patch_ad1981b(struct snd_ac97 * ac97);
 int patch_ad1985(struct snd_ac97 * ac97);
+int patch_ad1986(struct snd_ac97 * ac97);
 int patch_alc650(struct snd_ac97 * ac97);
 int patch_alc655(struct snd_ac97 * ac97);
 int patch_alc850(struct snd_ac97 * ac97);