Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

[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
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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;
+1 −1
Original line number Diff line number Diff line
@@ -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 */
+366 −1
Original line number Diff line number Diff line
@@ -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
 */
+1 −0
Original line number Diff line number Diff line
@@ -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);