188447a05SGarrett D'Amore /*
288447a05SGarrett D'Amore  * CDDL HEADER START
388447a05SGarrett D'Amore  *
488447a05SGarrett D'Amore  * The contents of this file are subject to the terms of the
588447a05SGarrett D'Amore  * Common Development and Distribution License (the "License").
688447a05SGarrett D'Amore  * You may not use this file except in compliance with the License.
788447a05SGarrett D'Amore  *
888447a05SGarrett D'Amore  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
988447a05SGarrett D'Amore  * or http://www.opensolaris.org/os/licensing.
1088447a05SGarrett D'Amore  * See the License for the specific language governing permissions
1188447a05SGarrett D'Amore  * and limitations under the License.
1288447a05SGarrett D'Amore  *
1388447a05SGarrett D'Amore  * When distributing Covered Code, include this CDDL HEADER in each
1488447a05SGarrett D'Amore  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
1588447a05SGarrett D'Amore  * If applicable, add the following below this CDDL HEADER, with the
1688447a05SGarrett D'Amore  * fields enclosed by brackets "[]" replaced with your own identifying
1788447a05SGarrett D'Amore  * information: Portions Copyright [yyyy] [name of copyright owner]
1888447a05SGarrett D'Amore  *
1988447a05SGarrett D'Amore  * CDDL HEADER END
2088447a05SGarrett D'Amore  */
2188447a05SGarrett D'Amore /*
22*2c30fa45SGarrett D'Amore  * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
2388447a05SGarrett D'Amore  */
2488447a05SGarrett D'Amore 
2588447a05SGarrett D'Amore /*
2688447a05SGarrett D'Amore  * CMI (C-Media) codec extensions.
2788447a05SGarrett D'Amore  */
2888447a05SGarrett D'Amore 
2988447a05SGarrett D'Amore #include <sys/types.h>
3088447a05SGarrett D'Amore #include <sys/ddi.h>
3188447a05SGarrett D'Amore #include <sys/sunddi.h>
3288447a05SGarrett D'Amore #include <sys/audio/audio_driver.h>
3388447a05SGarrett D'Amore #include <sys/audio/ac97.h>
3488447a05SGarrett D'Amore #include <sys/note.h>
3588447a05SGarrett D'Amore #include "ac97_impl.h"
3688447a05SGarrett D'Amore 
3788447a05SGarrett D'Amore /*
3888447a05SGarrett D'Amore  * C-Media 9739 part is weird.  Instead of having independent volume
3988447a05SGarrett D'Amore  * controls for each of the channels, it uses a single master volume
4088447a05SGarrett D'Amore  * and just provides mute support for the other bits.  It does this
4188447a05SGarrett D'Amore  * for PCM volume as well, so we can't use it either.  Ugh.  It also
4288447a05SGarrett D'Amore  * has an optional 30 dB mic boost.  Apparently the 9761 behaves in
4388447a05SGarrett D'Amore  * much the same fashion as the 9739.
4488447a05SGarrett D'Amore  *
4588447a05SGarrett D'Amore  * C-Media 9738 is a more or less typical 4CH device according to the
4688447a05SGarrett D'Amore  * datasheet.  It however supports jack retasking allowing the line in
4788447a05SGarrett D'Amore  * jack to function as a surround output.  Google suggests that the
4888447a05SGarrett D'Amore  * volume controls on this part are about as busted as on the other
4988447a05SGarrett D'Amore  * parts.  So, we just use synthetic volume for it.
5088447a05SGarrett D'Amore  *
5188447a05SGarrett D'Amore  * C-Media 9780 is largely a mystery (ENODATASHEET).
5288447a05SGarrett D'Amore  */
5388447a05SGarrett D'Amore 
5488447a05SGarrett D'Amore 
5588447a05SGarrett D'Amore #define	CMI_TASK_REGISTER	0x5A	/* 9738 jack retasking */
5688447a05SGarrett D'Amore #define	CTR_F2R			0x2000	/* front routed to rear */
5788447a05SGarrett D'Amore #define	CTR_S2LNI		0x0400	/* surround to line in */
5888447a05SGarrett D'Amore 
5988447a05SGarrett D'Amore #define	CMI_MULTICH_REGISTER	0x64	/* 9739 and 9761a */
6088447a05SGarrett D'Amore #define	CMR_PCBSW		0x8000	/* PC Beep volume bypass */
6188447a05SGarrett D'Amore #define	CMR_P47			0x4000	/* configure P47 function */
6288447a05SGarrett D'Amore #define	CMR_REFCTL		0x2000	/* enable vref output */
6388447a05SGarrett D'Amore #define	CMR_CLCTL		0x1000	/* center/lfe output enable */
6488447a05SGarrett D'Amore #define	CMR_S2LNI		0x0400	/* surround to line in */
6588447a05SGarrett D'Amore #define	CMR_MIX2S		0x0200	/* analog input pass to surround */
6688447a05SGarrett D'Amore #define	CMR_BSTSEL		0x0001	/* micboost use 30dB */
6788447a05SGarrett D'Amore 
6888447a05SGarrett D'Amore static void
cmi_set_micboost(ac97_ctrl_t * actrl,uint64_t value)6988447a05SGarrett D'Amore cmi_set_micboost(ac97_ctrl_t *actrl, uint64_t value)
7088447a05SGarrett D'Amore {
7188447a05SGarrett D'Amore 	ac97_t	*ac = actrl->actrl_ac97;
7288447a05SGarrett D'Amore 
7333ab04abSGarrett D'Amore 	ac_wr(ac, AC97_INTERRUPT_PAGING_REGISTER, 0);	/* select page 0 */
7488447a05SGarrett D'Amore 	switch (value) {
7588447a05SGarrett D'Amore 	case 0x1:
7688447a05SGarrett D'Amore 		/* 0db */
7733ab04abSGarrett D'Amore 		ac_clr(ac, AC97_MIC_VOLUME_REGISTER, MICVR_20dB_BOOST);
7833ab04abSGarrett D'Amore 		ac_clr(ac, CMI_MULTICH_REGISTER, CMR_BSTSEL);
7988447a05SGarrett D'Amore 		break;
8088447a05SGarrett D'Amore 	case 0x2:
8188447a05SGarrett D'Amore 		/* 20dB */
8233ab04abSGarrett D'Amore 		ac_set(ac, AC97_MIC_VOLUME_REGISTER, MICVR_20dB_BOOST);
8333ab04abSGarrett D'Amore 		ac_clr(ac, CMI_MULTICH_REGISTER, CMR_BSTSEL);
8488447a05SGarrett D'Amore 		break;
8588447a05SGarrett D'Amore 	case 0x4:
8688447a05SGarrett D'Amore 		/* 30dB */
8733ab04abSGarrett D'Amore 		ac_set(ac, AC97_MIC_VOLUME_REGISTER, MICVR_20dB_BOOST);
8833ab04abSGarrett D'Amore 		ac_set(ac, CMI_MULTICH_REGISTER, CMR_BSTSEL);
8988447a05SGarrett D'Amore 		break;
9088447a05SGarrett D'Amore 	}
9188447a05SGarrett D'Amore }
9288447a05SGarrett D'Amore 
9388447a05SGarrett D'Amore static void
cmi_set_linein_func(ac97_ctrl_t * actrl,uint64_t value)9488447a05SGarrett D'Amore cmi_set_linein_func(ac97_ctrl_t *actrl, uint64_t value)
9588447a05SGarrett D'Amore {
9688447a05SGarrett D'Amore 	ac97_t		*ac = actrl->actrl_ac97;
9788447a05SGarrett D'Amore 
9833ab04abSGarrett D'Amore 	ac_wr(ac, AC97_INTERRUPT_PAGING_REGISTER, 0);	/* select page 0 */
9988447a05SGarrett D'Amore 	if (value & 2) {
10033ab04abSGarrett D'Amore 		ac_set(ac, CMI_MULTICH_REGISTER, CMR_S2LNI);
10188447a05SGarrett D'Amore 	} else {
10233ab04abSGarrett D'Amore 		ac_clr(ac, CMI_MULTICH_REGISTER, CMR_S2LNI);
10388447a05SGarrett D'Amore 	}
10488447a05SGarrett D'Amore }
10588447a05SGarrett D'Amore 
10688447a05SGarrett D'Amore static void
cmi_set_mic_func(ac97_ctrl_t * actrl,uint64_t value)10788447a05SGarrett D'Amore cmi_set_mic_func(ac97_ctrl_t *actrl, uint64_t value)
10888447a05SGarrett D'Amore {
10988447a05SGarrett D'Amore 	ac97_t		*ac = actrl->actrl_ac97;
11088447a05SGarrett D'Amore 
11133ab04abSGarrett D'Amore 	ac_wr(ac, AC97_INTERRUPT_PAGING_REGISTER, 0);	/* select page 0 */
11288447a05SGarrett D'Amore 	if (value & 2) {
11333ab04abSGarrett D'Amore 		ac_set(ac, CMI_MULTICH_REGISTER, CMR_CLCTL);
11488447a05SGarrett D'Amore 	} else {
11533ab04abSGarrett D'Amore 		ac_clr(ac, CMI_MULTICH_REGISTER, CMR_CLCTL);
11688447a05SGarrett D'Amore 	}
11788447a05SGarrett D'Amore }
11888447a05SGarrett D'Amore 
11988447a05SGarrett D'Amore static void
cmi_setup_micboost(ac97_t * ac)12088447a05SGarrett D'Amore cmi_setup_micboost(ac97_t *ac)
12188447a05SGarrett D'Amore {
12288447a05SGarrett D'Amore 	ac97_ctrl_t		*ctrl;
12388447a05SGarrett D'Amore 
12488447a05SGarrett D'Amore 	static const char	*values[] = {
12588447a05SGarrett D'Amore 		AUDIO_VALUE_OFF,	/* 0dB */
12688447a05SGarrett D'Amore 		AUDIO_VALUE_MEDIUM,	/* 20dB */
12788447a05SGarrett D'Amore 		AUDIO_VALUE_HIGH,	/* 30dB */
12888447a05SGarrett D'Amore 		NULL
12988447a05SGarrett D'Amore 	};
13088447a05SGarrett D'Amore 	ac97_ctrl_probe_t cpt = {
13188447a05SGarrett D'Amore 		AUDIO_CTRL_ID_MICBOOST, 1, 0xf, 0xf, AUDIO_CTRL_TYPE_ENUM,
13288447a05SGarrett D'Amore 		AC97_FLAGS | AUDIO_CTRL_FLAG_REC, 0, cmi_set_micboost,
13388447a05SGarrett D'Amore 		NULL, 0, values };
13488447a05SGarrett D'Amore 
13588447a05SGarrett D'Amore 	ctrl = ac97_control_find(ac, AUDIO_CTRL_ID_MICBOOST);
13688447a05SGarrett D'Amore 	if (ctrl) {
13788447a05SGarrett D'Amore 		if (ctrl->actrl_initval) {
13888447a05SGarrett D'Amore 			/* 20dB by default */
13988447a05SGarrett D'Amore 			cpt.cp_initval = 1;
14088447a05SGarrett D'Amore 		}
14188447a05SGarrett D'Amore 	}
14288447a05SGarrett D'Amore 
14333ab04abSGarrett D'Amore 	ac_add_control(ac, &cpt);
14488447a05SGarrett D'Amore }
14588447a05SGarrett D'Amore 
14688447a05SGarrett D'Amore static const char *cmi_linein_funcs[] = {
14788447a05SGarrett D'Amore 	AUDIO_PORT_LINEIN,
14888447a05SGarrett D'Amore 	AUDIO_PORT_SURROUND,
14988447a05SGarrett D'Amore 	NULL
15088447a05SGarrett D'Amore };
15188447a05SGarrett D'Amore 
15288447a05SGarrett D'Amore static const char *cmi_mic_funcs[] = {
15388447a05SGarrett D'Amore 	AUDIO_PORT_MIC,
15488447a05SGarrett D'Amore 	AUDIO_PORT_CENLFE,
15588447a05SGarrett D'Amore 	NULL
15688447a05SGarrett D'Amore };
15788447a05SGarrett D'Amore 
15888447a05SGarrett D'Amore static void
cmi_setup_jack_funcs(ac97_t * ac)15988447a05SGarrett D'Amore cmi_setup_jack_funcs(ac97_t *ac)
16088447a05SGarrett D'Amore {
16188447a05SGarrett D'Amore 	ac97_ctrl_probe_t	cp;
16288447a05SGarrett D'Amore 	int			ival;
16388447a05SGarrett D'Amore 
16488447a05SGarrett D'Amore 	ac97_ctrl_probe_t linein_cpt = {
16588447a05SGarrett D'Amore 		AUDIO_CTRL_ID_JACK1, 1, 3, 3, AUDIO_CTRL_TYPE_ENUM, AC97_FLAGS,
16688447a05SGarrett D'Amore 		0, cmi_set_linein_func, NULL, 0, cmi_linein_funcs
16788447a05SGarrett D'Amore 	};
16888447a05SGarrett D'Amore 	ac97_ctrl_probe_t mic_cpt = {
16988447a05SGarrett D'Amore 		AUDIO_CTRL_ID_JACK2, 1, 3, 3, AUDIO_CTRL_TYPE_ENUM, AC97_FLAGS,
17088447a05SGarrett D'Amore 		0, cmi_set_mic_func, NULL, 0, cmi_mic_funcs
17188447a05SGarrett D'Amore 	};
17288447a05SGarrett D'Amore 
17388447a05SGarrett D'Amore 	bcopy(&linein_cpt, &cp, sizeof (cp));
17433ab04abSGarrett D'Amore 	ival = ac_get_prop(ac, AC97_PROP_LINEIN_FUNC, 0);
17588447a05SGarrett D'Amore 	if ((ival >= 1) && (ival <= 2)) {
17688447a05SGarrett D'Amore 		cp.cp_initval = ival;
17788447a05SGarrett D'Amore 	}
17833ab04abSGarrett D'Amore 	ac_add_control(ac, &cp);
17988447a05SGarrett D'Amore 
18088447a05SGarrett D'Amore 	bcopy(&mic_cpt, &cp, sizeof (cp));
18133ab04abSGarrett D'Amore 	ival = ac_get_prop(ac, AC97_PROP_MIC_FUNC, 0);
18288447a05SGarrett D'Amore 	if ((ival >= 1) && (ival <= 2)) {
18388447a05SGarrett D'Amore 		cp.cp_initval = ival;
18488447a05SGarrett D'Amore 	}
18533ab04abSGarrett D'Amore 	ac_add_control(ac, &cp);
18688447a05SGarrett D'Amore }
18788447a05SGarrett D'Amore 
18888447a05SGarrett D'Amore static void
cmi_set_linein_func_9738(ac97_ctrl_t * actrl,uint64_t value)18988447a05SGarrett D'Amore cmi_set_linein_func_9738(ac97_ctrl_t *actrl, uint64_t value)
19088447a05SGarrett D'Amore {
19188447a05SGarrett D'Amore 	ac97_t		*ac = actrl->actrl_ac97;
19288447a05SGarrett D'Amore 
19388447a05SGarrett D'Amore 	if (value & 2) {
19433ab04abSGarrett D'Amore 		ac_set(ac, CMI_TASK_REGISTER, CTR_S2LNI);
19588447a05SGarrett D'Amore 	} else {
19633ab04abSGarrett D'Amore 		ac_clr(ac, CMI_TASK_REGISTER, CTR_S2LNI);
19788447a05SGarrett D'Amore 	}
19888447a05SGarrett D'Amore }
19988447a05SGarrett D'Amore 
20088447a05SGarrett D'Amore static void
cmi_set_spread_9738(ac97_ctrl_t * actrl,uint64_t value)20188447a05SGarrett D'Amore cmi_set_spread_9738(ac97_ctrl_t *actrl, uint64_t value)
20288447a05SGarrett D'Amore {
20388447a05SGarrett D'Amore 	ac97_t		*ac = actrl->actrl_ac97;
20488447a05SGarrett D'Amore 
20588447a05SGarrett D'Amore 	if (value) {
20633ab04abSGarrett D'Amore 		ac_set(ac, CMI_TASK_REGISTER, CTR_F2R);
20788447a05SGarrett D'Amore 	} else {
20833ab04abSGarrett D'Amore 		ac_clr(ac, CMI_TASK_REGISTER, CTR_F2R);
20988447a05SGarrett D'Amore 	}
21088447a05SGarrett D'Amore }
21188447a05SGarrett D'Amore 
21288447a05SGarrett D'Amore static void
cmi_setup_jack_func_9738(ac97_t * ac)21388447a05SGarrett D'Amore cmi_setup_jack_func_9738(ac97_t *ac)
21488447a05SGarrett D'Amore {
21588447a05SGarrett D'Amore 	ac97_ctrl_probe_t	cp;
21688447a05SGarrett D'Amore 	int			ival;
21788447a05SGarrett D'Amore 
21888447a05SGarrett D'Amore 	ac97_ctrl_probe_t linein_cpt = {
21988447a05SGarrett D'Amore 		AUDIO_CTRL_ID_JACK1, 1, 3, 3, AUDIO_CTRL_TYPE_ENUM, AC97_FLAGS,
22088447a05SGarrett D'Amore 		0, cmi_set_linein_func_9738, NULL, 0, cmi_linein_funcs
22188447a05SGarrett D'Amore 	};
22288447a05SGarrett D'Amore 	ac97_ctrl_probe_t spread_cpt = {
22388447a05SGarrett D'Amore 		AUDIO_CTRL_ID_SPREAD, 0, 0, 1, AUDIO_CTRL_TYPE_BOOLEAN,
22488447a05SGarrett D'Amore 		AC97_FLAGS, 0, cmi_set_spread_9738,
22588447a05SGarrett D'Amore 	};
22688447a05SGarrett D'Amore 
22788447a05SGarrett D'Amore 	bcopy(&linein_cpt, &cp, sizeof (cp));
22833ab04abSGarrett D'Amore 	ival = ac_get_prop(ac, AC97_PROP_LINEIN_FUNC, 0);
22988447a05SGarrett D'Amore 	if ((ival >= 1) && (ival <= 2)) {
23088447a05SGarrett D'Amore 		cp.cp_initval = ival;
23188447a05SGarrett D'Amore 	}
23233ab04abSGarrett D'Amore 	ac_add_control(ac, &cp);
23388447a05SGarrett D'Amore 
23488447a05SGarrett D'Amore 	bcopy(&spread_cpt, &cp, sizeof (cp));
23533ab04abSGarrett D'Amore 	ival = ac_get_prop(ac, AC97_PROP_SPREAD, -1);
23688447a05SGarrett D'Amore 	if ((ival >= 0) && (ival <= 1)) {
23788447a05SGarrett D'Amore 		cp.cp_initval = ival;
23888447a05SGarrett D'Amore 	}
23933ab04abSGarrett D'Amore 	ac_add_control(ac, &cp);
24088447a05SGarrett D'Amore }
24188447a05SGarrett D'Amore 
24288447a05SGarrett D'Amore 
24388447a05SGarrett D'Amore static void
cmi_setup_volume(ac97_t * ac)24488447a05SGarrett D'Amore cmi_setup_volume(ac97_t *ac)
24588447a05SGarrett D'Amore {
24688447a05SGarrett D'Amore 	ac97_ctrl_t	*ctrl;
24788447a05SGarrett D'Amore 
24888447a05SGarrett D'Amore 	/*
24988447a05SGarrett D'Amore 	 * These CMI parts seem to be really weird.  They don't have
25088447a05SGarrett D'Amore 	 * *any* functioning volume controls on them (mute only) apart
25188447a05SGarrett D'Amore 	 * from the record and monitor sources (excluding PCM).  I
25288447a05SGarrett D'Amore 	 * don't understand why not.  We just eliminate all of the
25388447a05SGarrett D'Amore 	 * volume controls and replace with a soft volume control.
25488447a05SGarrett D'Amore 	 * Its not an ideal situation, but I don't know what else I
25588447a05SGarrett D'Amore 	 * can do about it.
25688447a05SGarrett D'Amore 	 */
25788447a05SGarrett D'Amore 	ctrl = ac97_control_find(ac, AUDIO_CTRL_ID_VOLUME);
25888447a05SGarrett D'Amore 	if (ctrl) {
25933ab04abSGarrett D'Amore 		ac97_control_remove(ctrl);
26088447a05SGarrett D'Amore 	}
26188447a05SGarrett D'Amore 	ctrl = ac97_control_find(ac, AUDIO_CTRL_ID_FRONT);
26288447a05SGarrett D'Amore 	if (ctrl) {
26333ab04abSGarrett D'Amore 		ac97_control_remove(ctrl);
26488447a05SGarrett D'Amore 	}
26588447a05SGarrett D'Amore 	ctrl = ac97_control_find(ac, AUDIO_CTRL_ID_SURROUND);
26688447a05SGarrett D'Amore 	if (ctrl) {
26733ab04abSGarrett D'Amore 		ac97_control_remove(ctrl);
26888447a05SGarrett D'Amore 	}
26988447a05SGarrett D'Amore 	ctrl = ac97_control_find(ac, AUDIO_CTRL_ID_CENTER);
27088447a05SGarrett D'Amore 	if (ctrl) {
27133ab04abSGarrett D'Amore 		ac97_control_remove(ctrl);
27288447a05SGarrett D'Amore 	}
27388447a05SGarrett D'Amore 	ctrl = ac97_control_find(ac, AUDIO_CTRL_ID_LFE);
27488447a05SGarrett D'Amore 	if (ctrl) {
27533ab04abSGarrett D'Amore 		ac97_control_remove(ctrl);
27688447a05SGarrett D'Amore 	}
27788447a05SGarrett D'Amore 
27888447a05SGarrett D'Amore 	/* make sure we have disabled mute and attenuation on physical ctrls */
27933ab04abSGarrett D'Amore 	ac_wr(ac, AC97_INTERRUPT_PAGING_REGISTER, 0);	/* select page 0 */
28033ab04abSGarrett D'Amore 	ac_wr(ac, AC97_PCM_OUT_VOLUME_REGISTER, 0);
28133ab04abSGarrett D'Amore 	ac_wr(ac, AC97_MASTER_VOLUME_REGISTER, 0);
28233ab04abSGarrett D'Amore 	ac_wr(ac, AC97_EXTENDED_C_LFE_VOLUME_REGISTER, 0);
28333ab04abSGarrett D'Amore 	ac_wr(ac, AC97_EXTENDED_LRS_VOLUME_REGISTER, 0);
28488447a05SGarrett D'Amore 
28533ab04abSGarrett D'Amore 	/*
28633ab04abSGarrett D'Amore 	 * NB: This is probably not the best way to do this, because
28733ab04abSGarrett D'Amore 	 * it will make overriding this hard for drivers that desire
28833ab04abSGarrett D'Amore 	 * to.  Fortunately, we don't think any drivers that want to
28933ab04abSGarrett D'Amore 	 * override or fine tune AC'97 controls (i.e. creative cards)
29033ab04abSGarrett D'Amore 	 * use these C-Media codecs.
29133ab04abSGarrett D'Amore 	 */
292*2c30fa45SGarrett D'Amore 	audio_dev_add_soft_volume(ac_get_dev(ac));
29388447a05SGarrett D'Amore }
29488447a05SGarrett D'Amore 
29588447a05SGarrett D'Amore void
cmi9739_init(ac97_t * ac)29688447a05SGarrett D'Amore cmi9739_init(ac97_t *ac)
29788447a05SGarrett D'Amore {
29888447a05SGarrett D'Amore 	cmi_setup_volume(ac);
29988447a05SGarrett D'Amore 	cmi_setup_micboost(ac);
30088447a05SGarrett D'Amore 	cmi_setup_jack_funcs(ac);
30188447a05SGarrett D'Amore }
30288447a05SGarrett D'Amore 
30388447a05SGarrett D'Amore void
cmi9761_init(ac97_t * ac)30488447a05SGarrett D'Amore cmi9761_init(ac97_t *ac)
30588447a05SGarrett D'Amore {
30688447a05SGarrett D'Amore 	cmi_setup_volume(ac);
30788447a05SGarrett D'Amore 	cmi_setup_micboost(ac);
30888447a05SGarrett D'Amore 	cmi_setup_jack_funcs(ac);
30988447a05SGarrett D'Amore }
31088447a05SGarrett D'Amore 
31188447a05SGarrett D'Amore void
cmi9738_init(ac97_t * ac)31288447a05SGarrett D'Amore cmi9738_init(ac97_t *ac)
31388447a05SGarrett D'Amore {
31488447a05SGarrett D'Amore 	cmi_setup_volume(ac);
31588447a05SGarrett D'Amore 	cmi_setup_jack_func_9738(ac);
31688447a05SGarrett D'Amore }
317