Commit 67e9f4b6 authored by Randy Cushman's avatar Randy Cushman Committed by Jaroslav Kysela

[ALSA] ac97 - fix various issues with AD1986/AD1986A support

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: default avatarRandy Cushman <rcushman_linux@earthlink.net>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
Signed-off-by: default avatarJaroslav Kysela <perex@suse.cz>
parent 6428ea1b
......@@ -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;
......
......@@ -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 */
......
......@@ -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
*/
......
......@@ -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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment