xref: /freebsd/sys/dev/sound/pcm/ac97.c (revision 0b4e32912566d802c7a6501d9ce8119f04dbc2fb)
1098ca2bdSWarner Losh /*-
24d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
3718cf2ccSPedro F. Giffuni  *
43f225978SCameron Grant  * Copyright (c) 1999 Cameron Grant <cg@freebsd.org>
5987e5972SCameron Grant  * All rights reserved.
6987e5972SCameron Grant  *
7987e5972SCameron Grant  * Redistribution and use in source and binary forms, with or without
8987e5972SCameron Grant  * modification, are permitted provided that the following conditions
9987e5972SCameron Grant  * are met:
10987e5972SCameron Grant  * 1. Redistributions of source code must retain the above copyright
11987e5972SCameron Grant  *    notice, this list of conditions and the following disclaimer.
12987e5972SCameron Grant  * 2. Redistributions in binary form must reproduce the above copyright
13987e5972SCameron Grant  *    notice, this list of conditions and the following disclaimer in the
14987e5972SCameron Grant  *    documentation and/or other materials provided with the distribution.
15987e5972SCameron Grant  *
16987e5972SCameron Grant  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17987e5972SCameron Grant  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18987e5972SCameron Grant  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19987e5972SCameron Grant  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20987e5972SCameron Grant  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21987e5972SCameron Grant  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22987e5972SCameron Grant  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23987e5972SCameron Grant  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24987e5972SCameron Grant  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25987e5972SCameron Grant  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26987e5972SCameron Grant  * SUCH DAMAGE.
27987e5972SCameron Grant  */
28987e5972SCameron Grant 
2990da2b28SAriff Abdullah #ifdef HAVE_KERNEL_OPTION_HEADERS
3090da2b28SAriff Abdullah #include "opt_snd.h"
3190da2b28SAriff Abdullah #endif
3290da2b28SAriff Abdullah 
33ef9308b1SCameron Grant #include <dev/sound/pcm/sound.h>
34ef9308b1SCameron Grant #include <dev/sound/pcm/ac97.h>
35987e5972SCameron Grant 
36a580b31aSAriff Abdullah #include <dev/pci/pcivar.h>
37a580b31aSAriff Abdullah 
380f55ac6cSCameron Grant #include "mixer_if.h"
39987e5972SCameron Grant 
40d745c852SEd Schouten static MALLOC_DEFINE(M_AC97, "ac97", "ac97 codec");
41987e5972SCameron Grant 
42*0b4e3291SChristos Margiolis typedef void (*ac97_patch)(struct ac97_info *);
43*0b4e3291SChristos Margiolis 
4479bb7d52SCameron Grant struct ac97mixtable_entry {
4583234531SAriff Abdullah 	int reg;		/* register index		*/
4696524a52SOrion Hodson 				/* reg < 0 if inverted polarity	*/
4796524a52SOrion Hodson 	unsigned bits:4;	/* width of control field	*/
4896524a52SOrion Hodson 	unsigned ofs:4;		/* offset (only if stereo=0)	*/
4996524a52SOrion Hodson 	unsigned stereo:1;	/* set for stereo controls	*/
5096524a52SOrion Hodson 	unsigned mute:1;	/* bit15 is MUTE		*/
5196524a52SOrion Hodson 	unsigned recidx:4;	/* index in rec mux		*/
5296524a52SOrion Hodson 	unsigned mask:1;	/* use only masked bits		*/
5396524a52SOrion Hodson 	unsigned enable:1;	/* entry is enabled		*/
5479bb7d52SCameron Grant };
5579bb7d52SCameron Grant 
5669f6d261SAriff Abdullah #define AC97_MIXER_SIZE		SOUND_MIXER_NRDEVICES
5769f6d261SAriff Abdullah 
5866ef8af5SCameron Grant struct ac97_info {
5966ef8af5SCameron Grant 	kobj_t methods;
6066ef8af5SCameron Grant 	device_t dev;
6166ef8af5SCameron Grant 	void *devinfo;
6219921b23SOrion Hodson 	u_int32_t id;
63fd7390d6SAriff Abdullah 	u_int32_t subvendor;
6466ef8af5SCameron Grant 	unsigned count, caps, se, extcaps, extid, extstat, noext:1;
6579bb7d52SCameron Grant 	u_int32_t flags;
6669f6d261SAriff Abdullah 	struct ac97mixtable_entry mix[AC97_MIXER_SIZE];
6769f6d261SAriff Abdullah 	char name[16];
6800acb133SCameron Grant 	struct mtx *lock;
6966ef8af5SCameron Grant };
7066ef8af5SCameron Grant 
7119921b23SOrion Hodson struct ac97_vendorid {
7219921b23SOrion Hodson 	u_int32_t   id;
7319921b23SOrion Hodson 	const char *name;
7419921b23SOrion Hodson };
7519921b23SOrion Hodson 
76987e5972SCameron Grant struct ac97_codecid {
7719921b23SOrion Hodson 	u_int32_t  id;
7819921b23SOrion Hodson 	u_int8_t   stepmask;
7919921b23SOrion Hodson 	u_int8_t   noext:1;
80987e5972SCameron Grant 	char 	  *name;
81f9eb1409SOrion Hodson 	ac97_patch patch;
82987e5972SCameron Grant };
83987e5972SCameron Grant 
8469f6d261SAriff Abdullah static const struct ac97mixtable_entry ac97mixtable_default[AC97_MIXER_SIZE] = {
8596524a52SOrion Hodson     /*	[offset]			reg	     bits of st mu re mk en */
86341f16ccSCameron Grant 	[SOUND_MIXER_VOLUME]	= { AC97_MIX_MASTER, 	5, 0, 1, 1, 6, 0, 1 },
87a52604cfSOrion Hodson 	[SOUND_MIXER_OGAIN]	= { AC97_MIX_AUXOUT, 	5, 0, 1, 1, 0, 0, 0 },
88341f16ccSCameron Grant 	[SOUND_MIXER_PHONEOUT]	= { AC97_MIX_MONO, 	5, 0, 0, 1, 7, 0, 0 },
89341f16ccSCameron Grant 	[SOUND_MIXER_BASS]	= { AC97_MIX_TONE, 	4, 8, 0, 0, 0, 1, 0 },
90341f16ccSCameron Grant 	[SOUND_MIXER_TREBLE]	= { AC97_MIX_TONE, 	4, 0, 0, 0, 0, 1, 0 },
91341f16ccSCameron Grant 	[SOUND_MIXER_PCM]	= { AC97_MIX_PCM, 	5, 0, 1, 1, 0, 0, 1 },
92341f16ccSCameron Grant 	[SOUND_MIXER_SPEAKER]	= { AC97_MIX_BEEP, 	4, 1, 0, 1, 0, 0, 0 },
93341f16ccSCameron Grant 	[SOUND_MIXER_LINE]	= { AC97_MIX_LINE, 	5, 0, 1, 1, 5, 0, 1 },
94341f16ccSCameron Grant 	[SOUND_MIXER_PHONEIN]	= { AC97_MIX_PHONE, 	5, 0, 0, 1, 8, 0, 0 },
9596524a52SOrion Hodson 	[SOUND_MIXER_MIC] 	= { AC97_MIX_MIC, 	5, 0, 0, 1, 1, 1, 1 },
9696524a52SOrion Hodson 	/* use igain for the mic 20dB boost */
9796524a52SOrion Hodson 	[SOUND_MIXER_IGAIN] 	= { -AC97_MIX_MIC, 	1, 6, 0, 0, 0, 1, 1 },
98341f16ccSCameron Grant 	[SOUND_MIXER_CD]	= { AC97_MIX_CD, 	5, 0, 1, 1, 2, 0, 1 },
99341f16ccSCameron Grant 	[SOUND_MIXER_LINE1]	= { AC97_MIX_AUX, 	5, 0, 1, 1, 4, 0, 0 },
100341f16ccSCameron Grant 	[SOUND_MIXER_VIDEO]	= { AC97_MIX_VIDEO, 	5, 0, 1, 1, 3, 0, 0 },
101341f16ccSCameron Grant 	[SOUND_MIXER_RECLEV]	= { -AC97_MIX_RGAIN, 	4, 0, 1, 1, 0, 0, 1 }
102987e5972SCameron Grant };
103987e5972SCameron Grant 
10419921b23SOrion Hodson static const struct ac97_vendorid ac97vendorid[] = {
10519921b23SOrion Hodson 	{ 0x41445300, "Analog Devices" },
10619921b23SOrion Hodson 	{ 0x414b4d00, "Asahi Kasei" },
10719921b23SOrion Hodson 	{ 0x414c4300, "Realtek" },
10819921b23SOrion Hodson 	{ 0x414c4700, "Avance Logic" },
10919921b23SOrion Hodson 	{ 0x43525900, "Cirrus Logic" },
11019921b23SOrion Hodson 	{ 0x434d4900, "C-Media Electronics" },
11119921b23SOrion Hodson 	{ 0x43585400, "Conexant" },
1123c6b655dSMathew Kanner 	{ 0x44543000, "Diamond Technology" },
11361a0da1dSOrion Hodson 	{ 0x454d4300, "eMicro" },
11419921b23SOrion Hodson 	{ 0x45838300, "ESS Technology" },
1153c6b655dSMathew Kanner 	{ 0x48525300, "Intersil" },
11619921b23SOrion Hodson 	{ 0x49434500, "ICEnsemble" },
1173c6b655dSMathew Kanner 	{ 0x49544500, "ITE, Inc." },
11819921b23SOrion Hodson 	{ 0x4e534300, "National Semiconductor" },
11919921b23SOrion Hodson 	{ 0x50534300, "Philips Semiconductor" },
12019921b23SOrion Hodson 	{ 0x83847600, "SigmaTel" },
1213c6b655dSMathew Kanner 	{ 0x53494c00, "Silicon Laboratories" },
12219921b23SOrion Hodson 	{ 0x54524100, "TriTech" },
1233c6b655dSMathew Kanner 	{ 0x54584e00, "Texas Instruments" },
12419921b23SOrion Hodson 	{ 0x56494100, "VIA Technologies" },
1253c6b655dSMathew Kanner 	{ 0x57454300, "Winbond" },
12619921b23SOrion Hodson 	{ 0x574d4c00, "Wolfson" },
12719921b23SOrion Hodson 	{ 0x594d4800, "Yamaha" },
12886336196SAlexander Leidinger 	/*
12986336196SAlexander Leidinger 	 * XXX This is a fluke, really! The real vendor
13086336196SAlexander Leidinger 	 * should be SigmaTel, not this! This should be
13186336196SAlexander Leidinger 	 * removed someday!
13286336196SAlexander Leidinger 	 */
1336f0182bdSOrion Hodson 	{ 0x01408300, "Creative" },
13419921b23SOrion Hodson 	{ 0x00000000, NULL }
13519921b23SOrion Hodson };
13619921b23SOrion Hodson 
137*0b4e3291SChristos Margiolis static void ad1886_patch(struct ac97_info *);
138*0b4e3291SChristos Margiolis static void ad198x_patch(struct ac97_info *);
139*0b4e3291SChristos Margiolis static void ad1981b_patch(struct ac97_info *);
140*0b4e3291SChristos Margiolis static void cmi9739_patch(struct ac97_info *);
141*0b4e3291SChristos Margiolis static void alc655_patch(struct ac97_info *);
142*0b4e3291SChristos Margiolis 
143987e5972SCameron Grant static struct ac97_codecid ac97codecid[] = {
14419921b23SOrion Hodson 	{ 0x41445303, 0x00, 0, "AD1819",	0 },
14519921b23SOrion Hodson 	{ 0x41445340, 0x00, 0, "AD1881",	0 },
14619921b23SOrion Hodson 	{ 0x41445348, 0x00, 0, "AD1881A",	0 },
14719921b23SOrion Hodson 	{ 0x41445360, 0x00, 0, "AD1885",	0 },
14819921b23SOrion Hodson 	{ 0x41445361, 0x00, 0, "AD1886", 	ad1886_patch },
149ba548c64SOrion Hodson 	{ 0x41445362, 0x00, 0, "AD1887", 	0 },
150ba548c64SOrion Hodson 	{ 0x41445363, 0x00, 0, "AD1886A", 	0 },
151c5cb8d60SScott Long 	{ 0x41445368, 0x00, 0, "AD1888", 	ad198x_patch },
152a52604cfSOrion Hodson 	{ 0x41445370, 0x00, 0, "AD1980",	ad198x_patch },
153ba548c64SOrion Hodson 	{ 0x41445372, 0x00, 0, "AD1981A",	0 },
1547699548fSAriff Abdullah 	{ 0x41445374, 0x00, 0, "AD1981B",	ad1981b_patch },
155a52604cfSOrion Hodson 	{ 0x41445375, 0x00, 0, "AD1985",	ad198x_patch },
156e1e05d5dSAriff Abdullah 	{ 0x41445378, 0x00, 0, "AD1986",	ad198x_patch },
15719921b23SOrion Hodson 	{ 0x414b4d00, 0x00, 1, "AK4540", 	0 },
15819921b23SOrion Hodson 	{ 0x414b4d01, 0x00, 1, "AK4542", 	0 },
15919921b23SOrion Hodson 	{ 0x414b4d02, 0x00, 1, "AK4543", 	0 },
1603c6b655dSMathew Kanner 	{ 0x414b4d06, 0x00, 0, "AK4544A",	0 },
1613c6b655dSMathew Kanner 	{ 0x454b4d07, 0x00, 0, "AK4545",	0 },
16219921b23SOrion Hodson 	{ 0x414c4320, 0x0f, 0, "ALC100",	0 },
1639963235aSOrion Hodson 	{ 0x414c4730, 0x0f, 0, "ALC101",	0 },
16419921b23SOrion Hodson 	{ 0x414c4710, 0x0f, 0, "ALC200", 	0 },
16519921b23SOrion Hodson 	{ 0x414c4740, 0x0f, 0, "ALC202", 	0 },
16619921b23SOrion Hodson 	{ 0x414c4720, 0x0f, 0, "ALC650", 	0 },
167b327ee51SAriff Abdullah 	{ 0x414c4752, 0x0f, 0, "ALC250",	0 },
168fd7390d6SAriff Abdullah 	{ 0x414c4760, 0x0f, 0, "ALC655",	alc655_patch },
169b7994e34SPyun YongHyeon 	{ 0x414c4770, 0x0f, 0, "ALC203",	0 },
17094ed763dSJun Kuriyama 	{ 0x414c4780, 0x0f, 0, "ALC658",	0 },
1713c6b655dSMathew Kanner 	{ 0x414c4790, 0x0f, 0, "ALC850",	0 },
17219921b23SOrion Hodson 	{ 0x43525900, 0x07, 0, "CS4297", 	0 },
17319921b23SOrion Hodson 	{ 0x43525910, 0x07, 0, "CS4297A", 	0 },
17419921b23SOrion Hodson 	{ 0x43525920, 0x07, 0, "CS4294/98",	0 },
175e5728f83SMIHIRA Sanpei Yoshiro 	{ 0x4352592d, 0x07, 0, "CS4294",	0 },
17619921b23SOrion Hodson 	{ 0x43525930, 0x07, 0, "CS4299",	0 },
17719921b23SOrion Hodson 	{ 0x43525940, 0x07, 0, "CS4201",	0 },
178b2a0f525SOrion Hodson 	{ 0x43525958, 0x07, 0, "CS4205",	0 },
17919921b23SOrion Hodson 	{ 0x43525960, 0x07, 0, "CS4291A",	0 },
180cb44f623SAlexander Leidinger 	{ 0x434d4961, 0x00, 0, "CMI9739",	cmi9739_patch },
18119921b23SOrion Hodson 	{ 0x434d4941, 0x00, 0, "CMI9738",	0 },
182cb44f623SAlexander Leidinger 	{ 0x434d4978, 0x00, 0, "CMI9761",	0 },
183cb44f623SAlexander Leidinger 	{ 0x434d4982, 0x00, 0, "CMI9761",	0 },
184cb44f623SAlexander Leidinger 	{ 0x434d4983, 0x00, 0, "CMI9761",	0 },
1853c6b655dSMathew Kanner 	{ 0x43585421, 0x00, 0, "HSD11246",	0 },
1863c6b655dSMathew Kanner 	{ 0x43585428, 0x07, 0, "CX20468",	0 },
18780138937SAriff Abdullah 	{ 0x43585430, 0x00, 0, "CX20468-21",	0 },
1883c6b655dSMathew Kanner 	{ 0x44543000, 0x00, 0, "DT0398",	0 },
18961a0da1dSOrion Hodson 	{ 0x454d4323, 0x00, 0, "EM28023",	0 },
19061a0da1dSOrion Hodson 	{ 0x454d4328, 0x00, 0, "EM28028",	0 },
19119921b23SOrion Hodson 	{ 0x45838308, 0x00, 0, "ES1988",	0 }, /* Formerly ES1921(?) */
1923c6b655dSMathew Kanner 	{ 0x48525300, 0x00, 0, "HMP9701",	0 },
19319921b23SOrion Hodson 	{ 0x49434501, 0x00, 0, "ICE1230",	0 },
19419921b23SOrion Hodson 	{ 0x49434511, 0x00, 0, "ICE1232",	0 },
19519921b23SOrion Hodson 	{ 0x49434514, 0x00, 0, "ICE1232A",	0 },
19649fd6905SOrion Hodson 	{ 0x49434551, 0x03, 0, "VT1616",	0 }, /* Via badged ICE */
1973c6b655dSMathew Kanner 	{ 0x49544520, 0x00, 0, "ITE2226E",	0 },
1983c6b655dSMathew Kanner 	{ 0x49544560, 0x07, 0, "ITE2646E",	0 }, /* XXX: patch needed */
19919921b23SOrion Hodson 	{ 0x4e534340, 0x00, 0, "LM4540",	0 }, /* Spec blank on revid */
20019921b23SOrion Hodson 	{ 0x4e534343, 0x00, 0, "LM4543",	0 }, /* Ditto */
20119921b23SOrion Hodson 	{ 0x4e534346, 0x00, 0, "LM4546A",	0 },
20219921b23SOrion Hodson 	{ 0x4e534348, 0x00, 0, "LM4548A",	0 },
2033c6b655dSMathew Kanner 	{ 0x4e534331, 0x00, 0, "LM4549",	0 },
20419921b23SOrion Hodson 	{ 0x4e534349, 0x00, 0, "LM4549A",	0 },
20519921b23SOrion Hodson 	{ 0x4e534350, 0x00, 0, "LM4550",	0 },
20619921b23SOrion Hodson 	{ 0x50534301, 0x00, 0, "UCB1510",	0 },
20719921b23SOrion Hodson 	{ 0x50534304, 0x00, 0, "UCB1400",	0 },
20819921b23SOrion Hodson 	{ 0x83847600, 0x00, 0, "STAC9700/83/84",	0 },
20919921b23SOrion Hodson 	{ 0x83847604, 0x00, 0, "STAC9701/03/04/05", 0 },
21019921b23SOrion Hodson 	{ 0x83847605, 0x00, 0, "STAC9704",	0 },
21119921b23SOrion Hodson 	{ 0x83847608, 0x00, 0, "STAC9708/11",	0 },
21219921b23SOrion Hodson 	{ 0x83847609, 0x00, 0, "STAC9721/23",	0 },
21319921b23SOrion Hodson 	{ 0x83847644, 0x00, 0, "STAC9744/45",	0 },
21419921b23SOrion Hodson 	{ 0x83847650, 0x00, 0, "STAC9750/51",	0 },
21519921b23SOrion Hodson 	{ 0x83847652, 0x00, 0, "STAC9752/53",	0 },
21619921b23SOrion Hodson 	{ 0x83847656, 0x00, 0, "STAC9756/57",	0 },
21719921b23SOrion Hodson 	{ 0x83847658, 0x00, 0, "STAC9758/59",	0 },
21819921b23SOrion Hodson 	{ 0x83847660, 0x00, 0, "STAC9760/61",	0 }, /* Extrapolated */
21919921b23SOrion Hodson 	{ 0x83847662, 0x00, 0, "STAC9762/63",	0 }, /* Extrapolated */
220cb44f623SAlexander Leidinger 	{ 0x83847666, 0x00, 0, "STAC9766/67",	0 },
22119921b23SOrion Hodson 	{ 0x53494c22, 0x00, 0, "Si3036",	0 },
22219921b23SOrion Hodson 	{ 0x53494c23, 0x00, 0, "Si3038",	0 },
22319921b23SOrion Hodson 	{ 0x54524103, 0x00, 0, "TR28023",	0 }, /* Extrapolated */
22419921b23SOrion Hodson 	{ 0x54524106, 0x00, 0, "TR28026",	0 },
22519921b23SOrion Hodson 	{ 0x54524108, 0x00, 0, "TR28028",	0 },
22619921b23SOrion Hodson 	{ 0x54524123, 0x00, 0, "TR28602",	0 },
2273c6b655dSMathew Kanner 	{ 0x54524e03, 0x07, 0, "TLV320AIC27",	0 },
2283c6b655dSMathew Kanner 	{ 0x54584e20, 0x00, 0, "TLC320AD90",	0 },
22919921b23SOrion Hodson 	{ 0x56494161, 0x00, 0, "VIA1612A",      0 },
23080138937SAriff Abdullah 	{ 0x56494170, 0x00, 0, "VIA1617A",      0 },
23119921b23SOrion Hodson 	{ 0x574d4c00, 0x00, 0, "WM9701A",	0 },
23219921b23SOrion Hodson 	{ 0x574d4c03, 0x00, 0, "WM9703/4/7/8",	0 },
23319921b23SOrion Hodson 	{ 0x574d4c04, 0x00, 0, "WM9704Q",	0 },
23419921b23SOrion Hodson 	{ 0x574d4c05, 0x00, 0, "WM9705/10",	0 },
2353c6b655dSMathew Kanner 	{ 0x574d4d09, 0x00, 0, "WM9709",	0 },
2363c6b655dSMathew Kanner 	{ 0x574d4c12, 0x00, 0, "WM9711/12",	0 }, /* XXX: patch needed */
2373c6b655dSMathew Kanner 	{ 0x57454301, 0x00, 0, "W83971D",	0 },
23819921b23SOrion Hodson 	{ 0x594d4800, 0x00, 0, "YMF743",	0 },
23919921b23SOrion Hodson 	{ 0x594d4802, 0x00, 0, "YMF752",	0 },
24019921b23SOrion Hodson 	{ 0x594d4803, 0x00, 0, "YMF753",	0 },
24186336196SAlexander Leidinger 	/*
24286336196SAlexander Leidinger 	 * XXX This is a fluke, really! The real codec
24386336196SAlexander Leidinger 	 * should be STAC9704, not this! This should be
24486336196SAlexander Leidinger 	 * removed someday!
24586336196SAlexander Leidinger 	 */
2466f0182bdSOrion Hodson 	{ 0x01408384, 0x00, 0, "EV1938",	0 },
24719921b23SOrion Hodson 	{ 0, 0, 0, NULL, 0 }
248987e5972SCameron Grant };
249987e5972SCameron Grant 
250987e5972SCameron Grant static char *ac97enhancement[] = {
25104553e63SCameron Grant 	"no 3D Stereo Enhancement",
252987e5972SCameron Grant 	"Analog Devices Phat Stereo",
253987e5972SCameron Grant 	"Creative Stereo Enhancement",
254987e5972SCameron Grant 	"National Semi 3D Stereo Enhancement",
255987e5972SCameron Grant 	"Yamaha Ymersion",
256987e5972SCameron Grant 	"BBE 3D Stereo Enhancement",
257987e5972SCameron Grant 	"Crystal Semi 3D Stereo Enhancement",
258987e5972SCameron Grant 	"Qsound QXpander",
259987e5972SCameron Grant 	"Spatializer 3D Stereo Enhancement",
260987e5972SCameron Grant 	"SRS 3D Stereo Enhancement",
261987e5972SCameron Grant 	"Platform Tech 3D Stereo Enhancement",
262987e5972SCameron Grant 	"AKM 3D Audio",
263987e5972SCameron Grant 	"Aureal Stereo Enhancement",
264987e5972SCameron Grant 	"Aztech 3D Enhancement",
265987e5972SCameron Grant 	"Binaura 3D Audio Enhancement",
266987e5972SCameron Grant 	"ESS Technology Stereo Enhancement",
267987e5972SCameron Grant 	"Harman International VMAx",
268987e5972SCameron Grant 	"Nvidea 3D Stereo Enhancement",
269987e5972SCameron Grant 	"Philips Incredible Sound",
270987e5972SCameron Grant 	"Texas Instruments 3D Stereo Enhancement",
271987e5972SCameron Grant 	"VLSI Technology 3D Stereo Enhancement",
272987e5972SCameron Grant 	"TriTech 3D Stereo Enhancement",
273987e5972SCameron Grant 	"Realtek 3D Stereo Enhancement",
274987e5972SCameron Grant 	"Samsung 3D Stereo Enhancement",
275987e5972SCameron Grant 	"Wolfson Microelectronics 3D Enhancement",
276987e5972SCameron Grant 	"Delta Integration 3D Enhancement",
277987e5972SCameron Grant 	"SigmaTel 3D Enhancement",
278987e5972SCameron Grant 	"Reserved 27",
279987e5972SCameron Grant 	"Rockwell 3D Stereo Enhancement",
280987e5972SCameron Grant 	"Reserved 29",
281987e5972SCameron Grant 	"Reserved 30",
282987e5972SCameron Grant 	"Reserved 31"
283987e5972SCameron Grant };
284987e5972SCameron Grant 
285987e5972SCameron Grant static char *ac97feature[] = {
286987e5972SCameron Grant 	"mic channel",
287987e5972SCameron Grant 	"reserved",
288987e5972SCameron Grant 	"tone",
289987e5972SCameron Grant 	"simulated stereo",
290987e5972SCameron Grant 	"headphone",
291987e5972SCameron Grant 	"bass boost",
292987e5972SCameron Grant 	"18 bit DAC",
293987e5972SCameron Grant 	"20 bit DAC",
294987e5972SCameron Grant 	"18 bit ADC",
295987e5972SCameron Grant 	"20 bit ADC"
296987e5972SCameron Grant };
297987e5972SCameron Grant 
29839004e69SCameron Grant static char *ac97extfeature[] = {
29939004e69SCameron Grant 	"variable rate PCM",
30039004e69SCameron Grant 	"double rate PCM",
30139004e69SCameron Grant 	"reserved 1",
30239004e69SCameron Grant 	"variable rate mic",
30339004e69SCameron Grant 	"reserved 2",
30439004e69SCameron Grant 	"reserved 3",
30539004e69SCameron Grant 	"center DAC",
30639004e69SCameron Grant 	"surround DAC",
30739004e69SCameron Grant 	"LFE DAC",
30839004e69SCameron Grant 	"AMAP",
30939004e69SCameron Grant 	"reserved 4",
31039004e69SCameron Grant 	"reserved 5",
31139004e69SCameron Grant 	"reserved 6",
31239004e69SCameron Grant 	"reserved 7",
31339004e69SCameron Grant };
31439004e69SCameron Grant 
315f9eb1409SOrion Hodson u_int16_t
316f9eb1409SOrion Hodson ac97_rdcd(struct ac97_info *codec, int reg)
31739004e69SCameron Grant {
31886336196SAlexander Leidinger 	if (codec->flags & AC97_F_RDCD_BUG) {
31986336196SAlexander Leidinger 		u_int16_t i[2], j = 100;
32086336196SAlexander Leidinger 
32186336196SAlexander Leidinger 		i[0] = AC97_READ(codec->methods, codec->devinfo, reg);
32286336196SAlexander Leidinger 		i[1] = AC97_READ(codec->methods, codec->devinfo, reg);
32386336196SAlexander Leidinger 		while (i[0] != i[1] && j)
32486336196SAlexander Leidinger 			i[j-- & 1] = AC97_READ(codec->methods, codec->devinfo, reg);
32586336196SAlexander Leidinger #if 0
32686336196SAlexander Leidinger 		if (j < 100) {
32786336196SAlexander Leidinger 			device_printf(codec->dev, "%s(): Inconsistent register value at"
32886336196SAlexander Leidinger 					" 0x%08x (retry: %d)\n", __func__, reg, 100 - j);
32986336196SAlexander Leidinger 		}
33086336196SAlexander Leidinger #endif
33186336196SAlexander Leidinger 		return i[!(j & 1)];
33286336196SAlexander Leidinger 	}
3330f55ac6cSCameron Grant 	return AC97_READ(codec->methods, codec->devinfo, reg);
33439004e69SCameron Grant }
33539004e69SCameron Grant 
336f9eb1409SOrion Hodson void
337f9eb1409SOrion Hodson ac97_wrcd(struct ac97_info *codec, int reg, u_int16_t val)
33839004e69SCameron Grant {
3390f55ac6cSCameron Grant 	AC97_WRITE(codec->methods, codec->devinfo, reg, val);
34039004e69SCameron Grant }
34139004e69SCameron Grant 
342c6d4b83aSOrion Hodson static void
343c6d4b83aSOrion Hodson ac97_reset(struct ac97_info *codec)
344c6d4b83aSOrion Hodson {
345c6d4b83aSOrion Hodson 	u_int32_t i, ps;
346f9eb1409SOrion Hodson 	ac97_wrcd(codec, AC97_REG_RESET, 0);
347c6d4b83aSOrion Hodson 	for (i = 0; i < 500; i++) {
348f9eb1409SOrion Hodson 		ps = ac97_rdcd(codec, AC97_REG_POWER) & AC97_POWER_STATUS;
349c6d4b83aSOrion Hodson 		if (ps == AC97_POWER_STATUS)
350c6d4b83aSOrion Hodson 			return;
351c6d4b83aSOrion Hodson 		DELAY(1000);
352c6d4b83aSOrion Hodson 	}
353a825c6e5SOrion Hodson 	device_printf(codec->dev, "AC97 reset timed out.\n");
354c6d4b83aSOrion Hodson }
355c6d4b83aSOrion Hodson 
35639004e69SCameron Grant int
35739004e69SCameron Grant ac97_setrate(struct ac97_info *codec, int which, int rate)
35839004e69SCameron Grant {
35939004e69SCameron Grant 	u_int16_t v;
36039004e69SCameron Grant 
36139004e69SCameron Grant 	switch(which) {
36239004e69SCameron Grant 	case AC97_REGEXT_FDACRATE:
36339004e69SCameron Grant 	case AC97_REGEXT_SDACRATE:
36439004e69SCameron Grant 	case AC97_REGEXT_LDACRATE:
36539004e69SCameron Grant 	case AC97_REGEXT_LADCRATE:
36639004e69SCameron Grant 	case AC97_REGEXT_MADCRATE:
36739004e69SCameron Grant 		break;
36839004e69SCameron Grant 
36939004e69SCameron Grant 	default:
37039004e69SCameron Grant 		return -1;
37139004e69SCameron Grant 	}
37239004e69SCameron Grant 
37366ef8af5SCameron Grant 	snd_mtxlock(codec->lock);
37439004e69SCameron Grant 	if (rate != 0) {
37539004e69SCameron Grant 		v = rate;
37639004e69SCameron Grant 		if (codec->extstat & AC97_EXTCAP_DRA)
37739004e69SCameron Grant 			v >>= 1;
378f9eb1409SOrion Hodson 		ac97_wrcd(codec, which, v);
37939004e69SCameron Grant 	}
380f9eb1409SOrion Hodson 	v = ac97_rdcd(codec, which);
38139004e69SCameron Grant 	if (codec->extstat & AC97_EXTCAP_DRA)
38239004e69SCameron Grant 		v <<= 1;
38366ef8af5SCameron Grant 	snd_mtxunlock(codec->lock);
38439004e69SCameron Grant 	return v;
38539004e69SCameron Grant }
38639004e69SCameron Grant 
38739004e69SCameron Grant int
38839004e69SCameron Grant ac97_setextmode(struct ac97_info *codec, u_int16_t mode)
38939004e69SCameron Grant {
39039004e69SCameron Grant 	mode &= AC97_EXTCAPS;
391647fbfebSOrion Hodson 	if ((mode & ~codec->extcaps) != 0) {
392647fbfebSOrion Hodson 		device_printf(codec->dev, "ac97 invalid mode set 0x%04x\n",
393647fbfebSOrion Hodson 			      mode);
39439004e69SCameron Grant 		return -1;
395647fbfebSOrion Hodson 	}
39666ef8af5SCameron Grant 	snd_mtxlock(codec->lock);
397f9eb1409SOrion Hodson 	ac97_wrcd(codec, AC97_REGEXT_STAT, mode);
398f9eb1409SOrion Hodson 	codec->extstat = ac97_rdcd(codec, AC97_REGEXT_STAT) & AC97_EXTCAPS;
39966ef8af5SCameron Grant 	snd_mtxunlock(codec->lock);
40039004e69SCameron Grant 	return (mode == codec->extstat)? 0 : -1;
40139004e69SCameron Grant }
40239004e69SCameron Grant 
4039ec437a3SCameron Grant u_int16_t
4049ec437a3SCameron Grant ac97_getextmode(struct ac97_info *codec)
4059ec437a3SCameron Grant {
4069ec437a3SCameron Grant 	return codec->extstat;
4079ec437a3SCameron Grant }
4089ec437a3SCameron Grant 
4099ec437a3SCameron Grant u_int16_t
4109ec437a3SCameron Grant ac97_getextcaps(struct ac97_info *codec)
4119ec437a3SCameron Grant {
4129ec437a3SCameron Grant 	return codec->extcaps;
4139ec437a3SCameron Grant }
4149ec437a3SCameron Grant 
4155d91ad67SCameron Grant u_int16_t
4165d91ad67SCameron Grant ac97_getcaps(struct ac97_info *codec)
4175d91ad67SCameron Grant {
4185d91ad67SCameron Grant 	return codec->caps;
4195d91ad67SCameron Grant }
4205d91ad67SCameron Grant 
421fd7390d6SAriff Abdullah u_int32_t
422fd7390d6SAriff Abdullah ac97_getsubvendor(struct ac97_info *codec)
423fd7390d6SAriff Abdullah {
424fd7390d6SAriff Abdullah 	return codec->subvendor;
425fd7390d6SAriff Abdullah }
426fd7390d6SAriff Abdullah 
427987e5972SCameron Grant static int
428987e5972SCameron Grant ac97_setrecsrc(struct ac97_info *codec, int channel)
429987e5972SCameron Grant {
430987e5972SCameron Grant 	struct ac97mixtable_entry *e = &codec->mix[channel];
431341f16ccSCameron Grant 
432987e5972SCameron Grant 	if (e->recidx > 0) {
433987e5972SCameron Grant 		int val = e->recidx - 1;
434987e5972SCameron Grant 		val |= val << 8;
43566ef8af5SCameron Grant 		snd_mtxlock(codec->lock);
436f9eb1409SOrion Hodson 		ac97_wrcd(codec, AC97_REG_RECSEL, val);
43766ef8af5SCameron Grant 		snd_mtxunlock(codec->lock);
438987e5972SCameron Grant 		return 0;
43939004e69SCameron Grant 	} else
44039004e69SCameron Grant 		return -1;
441987e5972SCameron Grant }
442987e5972SCameron Grant 
443987e5972SCameron Grant static int
444987e5972SCameron Grant ac97_setmixer(struct ac97_info *codec, unsigned channel, unsigned left, unsigned right)
445987e5972SCameron Grant {
446987e5972SCameron Grant 	struct ac97mixtable_entry *e = &codec->mix[channel];
447341f16ccSCameron Grant 
448341f16ccSCameron Grant 	if (e->reg && e->enable && e->bits) {
44996524a52SOrion Hodson 		int mask, max, val, reg;
45096524a52SOrion Hodson 
45196524a52SOrion Hodson 		reg = (e->reg >= 0) ? e->reg : -e->reg;	/* AC97 register    */
45296524a52SOrion Hodson 		max = (1 << e->bits) - 1;		/* actual range	    */
45396524a52SOrion Hodson 		mask = (max << 8) | max;		/* bits of interest */
454987e5972SCameron Grant 
45539004e69SCameron Grant 		if (!e->stereo)
45639004e69SCameron Grant 			right = left;
45796524a52SOrion Hodson 
45896524a52SOrion Hodson 		/*
45996524a52SOrion Hodson 		 * Invert the range if the polarity requires so,
46096524a52SOrion Hodson 		 * then scale to 0..max-1 to compute the value to
46196524a52SOrion Hodson 		 * write into the codec, and scale back to 0..100
46296524a52SOrion Hodson 		 * for the return value.
46396524a52SOrion Hodson 		 */
464987e5972SCameron Grant 		if (e->reg > 0) {
465987e5972SCameron Grant 			left = 100 - left;
466987e5972SCameron Grant 			right = 100 - right;
467987e5972SCameron Grant 		}
468987e5972SCameron Grant 
469987e5972SCameron Grant 		left = (left * max) / 100;
470987e5972SCameron Grant 		right = (right * max) / 100;
471987e5972SCameron Grant 
472987e5972SCameron Grant 		val = (left << 8) | right;
473987e5972SCameron Grant 
474987e5972SCameron Grant 		left = (left * 100) / max;
475987e5972SCameron Grant 		right = (right * 100) / max;
476987e5972SCameron Grant 
477987e5972SCameron Grant 		if (e->reg > 0) {
478987e5972SCameron Grant 			left = 100 - left;
479987e5972SCameron Grant 			right = 100 - right;
480987e5972SCameron Grant 		}
481987e5972SCameron Grant 
48296524a52SOrion Hodson 		/*
48396524a52SOrion Hodson 		 * For mono controls, trim val and mask, also taking
48496524a52SOrion Hodson 		 * care of e->ofs (offset of control field).
48596524a52SOrion Hodson 		 */
48696524a52SOrion Hodson 		if (e->ofs) {
487987e5972SCameron Grant 			val &= max;
488987e5972SCameron Grant 			val <<= e->ofs;
48996524a52SOrion Hodson 			mask = (max << e->ofs);
49096524a52SOrion Hodson 		}
49196524a52SOrion Hodson 
49296524a52SOrion Hodson 		/*
49396524a52SOrion Hodson 		 * If we have a mute bit, add it to the mask and
49496524a52SOrion Hodson 		 * update val and set mute if both channels require a
49596524a52SOrion Hodson 		 * zero volume.
49696524a52SOrion Hodson 		 */
49796524a52SOrion Hodson 		if (e->mute == 1) {
49896524a52SOrion Hodson 			mask |= AC97_MUTE;
49996524a52SOrion Hodson 			if (left == 0 && right == 0)
50096524a52SOrion Hodson 				val = AC97_MUTE;
50196524a52SOrion Hodson 		}
50296524a52SOrion Hodson 
50396524a52SOrion Hodson 		/*
50496524a52SOrion Hodson 		 * If the mask bit is set, do not alter the other bits.
50596524a52SOrion Hodson 		 */
50696524a52SOrion Hodson 		snd_mtxlock(codec->lock);
507987e5972SCameron Grant 		if (e->mask) {
50886336196SAlexander Leidinger 			int cur = ac97_rdcd(codec, reg);
50996524a52SOrion Hodson 			val |= cur & ~(mask);
510987e5972SCameron Grant 		}
511f9eb1409SOrion Hodson 		ac97_wrcd(codec, reg, val);
51266ef8af5SCameron Grant 		snd_mtxunlock(codec->lock);
513987e5972SCameron Grant 		return left | (right << 8);
514341f16ccSCameron Grant 	} else {
51586336196SAlexander Leidinger #if 0
51686336196SAlexander Leidinger 		printf("ac97_setmixer: reg=%d, bits=%d, enable=%d\n", e->reg, e->bits, e->enable);
51786336196SAlexander Leidinger #endif
51839004e69SCameron Grant 		return -1;
519987e5972SCameron Grant 	}
520341f16ccSCameron Grant }
521987e5972SCameron Grant 
522108082c4SOrion Hodson static void
523108082c4SOrion Hodson ac97_fix_auxout(struct ac97_info *codec)
524108082c4SOrion Hodson {
525cfd5696dSOrion Hodson 	int keep_ogain;
526cfd5696dSOrion Hodson 
527a52604cfSOrion Hodson 	/*
528cfd5696dSOrion Hodson 	 * By default, The ac97 aux_out register (0x04) corresponds to OSS's
529cfd5696dSOrion Hodson 	 * OGAIN setting.
530a52604cfSOrion Hodson 	 *
531cfd5696dSOrion Hodson 	 * We first check whether aux_out is a valid register.  If not
532cfd5696dSOrion Hodson 	 * we may not want to keep ogain.
533a52604cfSOrion Hodson 	 */
534cfd5696dSOrion Hodson 	keep_ogain = ac97_rdcd(codec, AC97_MIX_AUXOUT) & 0x8000;
535a52604cfSOrion Hodson 
536a52604cfSOrion Hodson 	/*
537a52604cfSOrion Hodson 	 * Determine what AUX_OUT really means, it can be:
538108082c4SOrion Hodson 	 *
539108082c4SOrion Hodson 	 * 1. Headphone out.
540108082c4SOrion Hodson 	 * 2. 4-Channel Out
541108082c4SOrion Hodson 	 * 3. True line level out (effectively master volume).
542108082c4SOrion Hodson 	 *
543108082c4SOrion Hodson 	 * See Sections 5.2.1 and 5.27 for AUX_OUT Options in AC97r2.{2,3}.
544108082c4SOrion Hodson 	 */
545a52604cfSOrion Hodson 	if (codec->extcaps & AC97_EXTCAP_SDAC &&
546f9eb1409SOrion Hodson 	    ac97_rdcd(codec, AC97_MIXEXT_SURROUND) == 0x8080) {
547cfd5696dSOrion Hodson 		codec->mix[SOUND_MIXER_OGAIN].reg = AC97_MIXEXT_SURROUND;
548cfd5696dSOrion Hodson 		keep_ogain = 1;
549cfd5696dSOrion Hodson 	}
550cfd5696dSOrion Hodson 
551cfd5696dSOrion Hodson 	if (keep_ogain == 0) {
552cfd5696dSOrion Hodson 		bzero(&codec->mix[SOUND_MIXER_OGAIN],
553cfd5696dSOrion Hodson 		      sizeof(codec->mix[SOUND_MIXER_OGAIN]));
554108082c4SOrion Hodson 	}
555a52604cfSOrion Hodson }
556a52604cfSOrion Hodson 
557a52604cfSOrion Hodson static void
558a52604cfSOrion Hodson ac97_fix_tone(struct ac97_info *codec)
559a52604cfSOrion Hodson {
560f0c4d272SAriff Abdullah 	/*
561f0c4d272SAriff Abdullah 	 * YMF chips does not indicate tone and 3D enhancement capability
562f0c4d272SAriff Abdullah 	 * in the AC97_REG_RESET register.
563f0c4d272SAriff Abdullah 	 */
564f0c4d272SAriff Abdullah 	switch (codec->id) {
565f0c4d272SAriff Abdullah 	case 0x594d4800:	/* YMF743 */
566f0c4d272SAriff Abdullah 	case 0x594d4803:	/* YMF753 */
567f0c4d272SAriff Abdullah 		codec->caps |= AC97_CAP_TONE;
568f0c4d272SAriff Abdullah 		codec->se |= 0x04;
569f0c4d272SAriff Abdullah 		break;
570f0c4d272SAriff Abdullah 	case 0x594d4802:	/* YMF752 */
571f0c4d272SAriff Abdullah 		codec->se |= 0x04;
572f0c4d272SAriff Abdullah 		break;
573f0c4d272SAriff Abdullah 	default:
574f0c4d272SAriff Abdullah 		break;
575f0c4d272SAriff Abdullah 	}
576f0c4d272SAriff Abdullah 
577a52604cfSOrion Hodson 	/* Hide treble and bass if they don't exist */
578a52604cfSOrion Hodson 	if ((codec->caps & AC97_CAP_TONE) == 0) {
579a52604cfSOrion Hodson 		bzero(&codec->mix[SOUND_MIXER_BASS],
580a52604cfSOrion Hodson 		      sizeof(codec->mix[SOUND_MIXER_BASS]));
581a52604cfSOrion Hodson 		bzero(&codec->mix[SOUND_MIXER_TREBLE],
582a52604cfSOrion Hodson 		      sizeof(codec->mix[SOUND_MIXER_TREBLE]));
583a52604cfSOrion Hodson 	}
584108082c4SOrion Hodson }
585108082c4SOrion Hodson 
58619921b23SOrion Hodson static const char*
58719921b23SOrion Hodson ac97_hw_desc(u_int32_t id, const char* vname, const char* cname, char* buf)
58819921b23SOrion Hodson {
58919921b23SOrion Hodson 	if (cname == NULL) {
59019921b23SOrion Hodson 		sprintf(buf, "Unknown AC97 Codec (id = 0x%08x)", id);
59119921b23SOrion Hodson 		return buf;
59219921b23SOrion Hodson 	}
59319921b23SOrion Hodson 
59419921b23SOrion Hodson 	if (vname == NULL) vname = "Unknown";
59519921b23SOrion Hodson 
59619921b23SOrion Hodson 	if (bootverbose) {
59719921b23SOrion Hodson 		sprintf(buf, "%s %s AC97 Codec (id = 0x%08x)", vname, cname, id);
59819921b23SOrion Hodson 	} else {
59919921b23SOrion Hodson 		sprintf(buf, "%s %s AC97 Codec", vname, cname);
60019921b23SOrion Hodson 	}
60119921b23SOrion Hodson 	return buf;
60219921b23SOrion Hodson }
60319921b23SOrion Hodson 
604987e5972SCameron Grant static unsigned
60539004e69SCameron Grant ac97_initmixer(struct ac97_info *codec)
606987e5972SCameron Grant {
607f9eb1409SOrion Hodson 	ac97_patch codec_patch;
60819921b23SOrion Hodson 	const char *cname, *vname;
60919921b23SOrion Hodson 	char desc[80];
6102acbe677STai-hwa Liang 	device_t pdev;
61186336196SAlexander Leidinger 	unsigned i, j, k, bit, old;
612987e5972SCameron Grant 	u_int32_t id;
61386336196SAlexander Leidinger 	int reg;
614987e5972SCameron Grant 
61566ef8af5SCameron Grant 	snd_mtxlock(codec->lock);
6160f55ac6cSCameron Grant 	codec->count = AC97_INIT(codec->methods, codec->devinfo);
617cd2c103aSCameron Grant 	if (codec->count == 0) {
61804553e63SCameron Grant 		device_printf(codec->dev, "ac97 codec init failed\n");
61966ef8af5SCameron Grant 		snd_mtxunlock(codec->lock);
62004553e63SCameron Grant 		return ENODEV;
62104553e63SCameron Grant 	}
6229ec437a3SCameron Grant 
623f9eb1409SOrion Hodson 	ac97_wrcd(codec, AC97_REG_POWER, (codec->flags & AC97_F_EAPD_INV)? 0x8000 : 0x0000);
624c6d4b83aSOrion Hodson 	ac97_reset(codec);
625f9eb1409SOrion Hodson 	ac97_wrcd(codec, AC97_REG_POWER, (codec->flags & AC97_F_EAPD_INV)? 0x8000 : 0x0000);
626987e5972SCameron Grant 
627f9eb1409SOrion Hodson 	i = ac97_rdcd(codec, AC97_REG_RESET);
62886336196SAlexander Leidinger 	j = ac97_rdcd(codec, AC97_REG_RESET);
62969f6d261SAriff Abdullah 	k = ac97_rdcd(codec, AC97_REG_RESET);
63086336196SAlexander Leidinger 	/*
63186336196SAlexander Leidinger 	 * Let see if this codec can return consistent value.
63286336196SAlexander Leidinger 	 * If not, turn on aggressive read workaround
63386336196SAlexander Leidinger 	 * (STAC9704 comes in mind).
63486336196SAlexander Leidinger 	 */
63569f6d261SAriff Abdullah 	if (i != j || j != k) {
63686336196SAlexander Leidinger 		codec->flags |= AC97_F_RDCD_BUG;
63786336196SAlexander Leidinger 		i = ac97_rdcd(codec, AC97_REG_RESET);
63886336196SAlexander Leidinger 	}
639987e5972SCameron Grant 	codec->caps = i & 0x03ff;
640987e5972SCameron Grant 	codec->se =  (i & 0x7c00) >> 10;
641987e5972SCameron Grant 
642f9eb1409SOrion Hodson 	id = (ac97_rdcd(codec, AC97_REG_ID1) << 16) | ac97_rdcd(codec, AC97_REG_ID2);
643e620d959SCameron Grant 	if (id == 0 || id == 0xffffffff) {
644e620d959SCameron Grant 		device_printf(codec->dev, "ac97 codec invalid or not present (id == %x)\n", id);
64566ef8af5SCameron Grant 		snd_mtxunlock(codec->lock);
646e620d959SCameron Grant 		return ENODEV;
647e620d959SCameron Grant 	}
6486b4b88f7SCameron Grant 
6492acbe677STai-hwa Liang 	pdev = codec->dev;
6502acbe677STai-hwa Liang 	while (strcmp(device_get_name(device_get_parent(pdev)), "pci") != 0) {
6512acbe677STai-hwa Liang 		/* find the top-level PCI device handler */
6522acbe677STai-hwa Liang 		pdev = device_get_parent(pdev);
6532acbe677STai-hwa Liang 	}
65419921b23SOrion Hodson 	codec->id = id;
6552acbe677STai-hwa Liang 	codec->subvendor = (u_int32_t)pci_get_subdevice(pdev) << 16;
6562acbe677STai-hwa Liang 	codec->subvendor |= (u_int32_t)pci_get_subvendor(pdev) &
657fd7390d6SAriff Abdullah 	    0x0000ffff;
658cd2c103aSCameron Grant 	codec->noext = 0;
659f9eb1409SOrion Hodson 	codec_patch = NULL;
66019921b23SOrion Hodson 
66119921b23SOrion Hodson 	cname = NULL;
662cd2c103aSCameron Grant 	for (i = 0; ac97codecid[i].id; i++) {
66319921b23SOrion Hodson 		u_int32_t modelmask = 0xffffffff ^ ac97codecid[i].stepmask;
66419921b23SOrion Hodson 		if ((ac97codecid[i].id & modelmask) == (id & modelmask)) {
665cd2c103aSCameron Grant 			codec->noext = ac97codecid[i].noext;
666f9eb1409SOrion Hodson 			codec_patch = ac97codecid[i].patch;
66719921b23SOrion Hodson 			cname = ac97codecid[i].name;
66819921b23SOrion Hodson 			break;
66919921b23SOrion Hodson 		}
67019921b23SOrion Hodson 	}
67119921b23SOrion Hodson 
67219921b23SOrion Hodson 	vname = NULL;
67319921b23SOrion Hodson 	for (i = 0; ac97vendorid[i].id; i++) {
67419921b23SOrion Hodson 		if (ac97vendorid[i].id == (id & 0xffffff00)) {
67519921b23SOrion Hodson 			vname = ac97vendorid[i].name;
67619921b23SOrion Hodson 			break;
677cd2c103aSCameron Grant 		}
678cd2c103aSCameron Grant 	}
6796b4b88f7SCameron Grant 
680cd2c103aSCameron Grant 	codec->extcaps = 0;
681cd2c103aSCameron Grant 	codec->extid = 0;
682cd2c103aSCameron Grant 	codec->extstat = 0;
6836a6ee5bbSCameron Grant 	if (!codec->noext) {
684f9eb1409SOrion Hodson 		i = ac97_rdcd(codec, AC97_REGEXT_ID);
6856a6ee5bbSCameron Grant 		if (i != 0xffff) {
68639004e69SCameron Grant 			codec->extcaps = i & 0x3fff;
68739004e69SCameron Grant 			codec->extid =  (i & 0xc000) >> 14;
688f9eb1409SOrion Hodson 			codec->extstat = ac97_rdcd(codec, AC97_REGEXT_STAT) & AC97_EXTCAPS;
6896b4b88f7SCameron Grant 		}
6906a6ee5bbSCameron Grant 	}
691987e5972SCameron Grant 
69269f6d261SAriff Abdullah 	for (i = 0; i < AC97_MIXER_SIZE; i++) {
693108082c4SOrion Hodson 		codec->mix[i] = ac97mixtable_default[i];
694108082c4SOrion Hodson 	}
695108082c4SOrion Hodson 	ac97_fix_auxout(codec);
696a52604cfSOrion Hodson 	ac97_fix_tone(codec);
697f9eb1409SOrion Hodson 	if (codec_patch)
698f9eb1409SOrion Hodson 		codec_patch(codec);
699108082c4SOrion Hodson 
70069f6d261SAriff Abdullah 	for (i = 0; i < AC97_MIXER_SIZE; i++) {
70133c878f0SCameron Grant 		k = codec->noext? codec->mix[i].enable : 1;
70286336196SAlexander Leidinger 		reg = codec->mix[i].reg;
70386336196SAlexander Leidinger 		if (reg < 0)
70486336196SAlexander Leidinger 			reg = -reg;
70586336196SAlexander Leidinger 		if (k && reg) {
70686336196SAlexander Leidinger 			j = old = ac97_rdcd(codec, reg);
70786336196SAlexander Leidinger 			/*
70886336196SAlexander Leidinger 			 * Test for mute bit (except for AC97_MIX_TONE,
70986336196SAlexander Leidinger 			 * where we simply assume it as available).
71086336196SAlexander Leidinger 			 */
71186336196SAlexander Leidinger 			if (codec->mix[i].mute) {
71286336196SAlexander Leidinger 				ac97_wrcd(codec, reg, j | 0x8000);
71386336196SAlexander Leidinger 				j = ac97_rdcd(codec, reg);
71486336196SAlexander Leidinger 			} else
715fe435202SAlexander Leidinger 				j |= 0x8000;
71686336196SAlexander Leidinger 			if ((j & 0x8000)) {
71786336196SAlexander Leidinger 				/*
71886336196SAlexander Leidinger 				 * Test whether the control width should be
71986336196SAlexander Leidinger 				 * 4, 5 or 6 bit. For 5bit register, we should
72086336196SAlexander Leidinger 				 * test it whether it's really 5 or 6bit. Leave
72186336196SAlexander Leidinger 				 * 4bit register alone, because sometimes an
72286336196SAlexander Leidinger 				 * attempt to write past 4th bit may cause
72386336196SAlexander Leidinger 				 * incorrect result especially for AC97_MIX_BEEP
72486336196SAlexander Leidinger 				 * (ac97 2.3).
72586336196SAlexander Leidinger 				 */
72686336196SAlexander Leidinger 				bit = codec->mix[i].bits;
72786336196SAlexander Leidinger 				if (bit == 5)
72886336196SAlexander Leidinger 					bit++;
72986336196SAlexander Leidinger 				j = ((1 << bit) - 1) << codec->mix[i].ofs;
73086336196SAlexander Leidinger 				ac97_wrcd(codec, reg,
73186336196SAlexander Leidinger 					j | (codec->mix[i].mute ? 0x8000 : 0));
73286336196SAlexander Leidinger 				k = ac97_rdcd(codec, reg) & j;
73386336196SAlexander Leidinger 				k >>= codec->mix[i].ofs;
73486336196SAlexander Leidinger 				if (reg == AC97_MIX_TONE &&
73586336196SAlexander Leidinger 							((k & 0x0001) == 0x0000))
736fe435202SAlexander Leidinger 					k >>= 1;
73786336196SAlexander Leidinger 				for (j = 0; k >> j; j++)
73886336196SAlexander Leidinger 					;
739fe435202SAlexander Leidinger 				if (j != 0) {
740fe435202SAlexander Leidinger #if 0
74186336196SAlexander Leidinger 					device_printf(codec->dev, "%2d: [ac97_rdcd() = %d] [Testbit = %d] %d -> %d\n",
74286336196SAlexander Leidinger 						i, k, bit, codec->mix[i].bits, j);
743fe435202SAlexander Leidinger #endif
74486336196SAlexander Leidinger 					codec->mix[i].enable = 1;
74586336196SAlexander Leidinger 					codec->mix[i].bits = j;
746eaf70083SAriff Abdullah 				} else if (reg == AC97_MIX_BEEP) {
747eaf70083SAriff Abdullah 					/*
748eaf70083SAriff Abdullah 					 * Few codec such as CX20468-21 does
749eaf70083SAriff Abdullah 					 * have this control register, although
750eaf70083SAriff Abdullah 					 * the only usable part is the mute bit.
751eaf70083SAriff Abdullah 					 */
752eaf70083SAriff Abdullah 					codec->mix[i].enable = 1;
753fe435202SAlexander Leidinger 				} else
754fe435202SAlexander Leidinger 					codec->mix[i].enable = 0;
755fe435202SAlexander Leidinger 			} else
756fe435202SAlexander Leidinger 				codec->mix[i].enable = 0;
75786336196SAlexander Leidinger 			ac97_wrcd(codec, reg, old);
758fe435202SAlexander Leidinger 		}
759fe435202SAlexander Leidinger #if 0
760fe435202SAlexander Leidinger 		printf("mixch %d, en=%d, b=%d\n", i, codec->mix[i].enable, codec->mix[i].bits);
761fe435202SAlexander Leidinger #endif
762341f16ccSCameron Grant 	}
763987e5972SCameron Grant 
76419921b23SOrion Hodson 	device_printf(codec->dev, "<%s>\n",
76519921b23SOrion Hodson 		      ac97_hw_desc(codec->id, vname, cname, desc));
766a825c6e5SOrion Hodson 
767987e5972SCameron Grant 	if (bootverbose) {
76886336196SAlexander Leidinger 		if (codec->flags & AC97_F_RDCD_BUG)
76986336196SAlexander Leidinger 			device_printf(codec->dev, "Buggy AC97 Codec: aggressive ac97_rdcd() workaround enabled\n");
77019921b23SOrion Hodson 		device_printf(codec->dev, "Codec features ");
77139004e69SCameron Grant 		for (i = j = 0; i < 10; i++)
77239004e69SCameron Grant 			if (codec->caps & (1 << i))
77339004e69SCameron Grant 				printf("%s%s", j++? ", " : "", ac97feature[i]);
77439004e69SCameron Grant 		printf("%s%d bit master volume", j++? ", " : "", codec->mix[SOUND_MIXER_VOLUME].bits);
775987e5972SCameron Grant 		printf("%s%s\n", j? ", " : "", ac97enhancement[codec->se]);
77639004e69SCameron Grant 
77739004e69SCameron Grant 		if (codec->extcaps != 0 || codec->extid) {
77819921b23SOrion Hodson 			device_printf(codec->dev, "%s codec",
77919921b23SOrion Hodson 				      codec->extid? "Secondary" : "Primary");
78039004e69SCameron Grant 			if (codec->extcaps)
78139004e69SCameron Grant 				printf(" extended features ");
78239004e69SCameron Grant 			for (i = j = 0; i < 14; i++)
78339004e69SCameron Grant 				if (codec->extcaps & (1 << i))
78439004e69SCameron Grant 					printf("%s%s", j++? ", " : "", ac97extfeature[i]);
78539004e69SCameron Grant 			printf("\n");
78639004e69SCameron Grant 		}
787987e5972SCameron Grant 	}
788987e5972SCameron Grant 
789fe435202SAlexander Leidinger 	i = 0;
790fe435202SAlexander Leidinger 	while ((ac97_rdcd(codec, AC97_REG_POWER) & 2) == 0) {
791fe435202SAlexander Leidinger 		if (++i == 100) {
79203a00905SCameron Grant 			device_printf(codec->dev, "ac97 codec reports dac not ready\n");
793fe435202SAlexander Leidinger 			break;
794fe435202SAlexander Leidinger 		}
795fe435202SAlexander Leidinger 		DELAY(1000);
796fe435202SAlexander Leidinger 	}
797fe435202SAlexander Leidinger 	if (bootverbose)
798fe435202SAlexander Leidinger 		device_printf(codec->dev, "ac97 codec dac ready count: %d\n", i);
79966ef8af5SCameron Grant 	snd_mtxunlock(codec->lock);
800987e5972SCameron Grant 	return 0;
801987e5972SCameron Grant }
802987e5972SCameron Grant 
8039ec437a3SCameron Grant static unsigned
8049ec437a3SCameron Grant ac97_reinitmixer(struct ac97_info *codec)
8059ec437a3SCameron Grant {
80666ef8af5SCameron Grant 	snd_mtxlock(codec->lock);
8070f55ac6cSCameron Grant 	codec->count = AC97_INIT(codec->methods, codec->devinfo);
8089ec437a3SCameron Grant 	if (codec->count == 0) {
8099ec437a3SCameron Grant 		device_printf(codec->dev, "ac97 codec init failed\n");
81066ef8af5SCameron Grant 		snd_mtxunlock(codec->lock);
8119ec437a3SCameron Grant 		return ENODEV;
8129ec437a3SCameron Grant 	}
8139ec437a3SCameron Grant 
814f9eb1409SOrion Hodson 	ac97_wrcd(codec, AC97_REG_POWER, (codec->flags & AC97_F_EAPD_INV)? 0x8000 : 0x0000);
815c6d4b83aSOrion Hodson 	ac97_reset(codec);
816f9eb1409SOrion Hodson 	ac97_wrcd(codec, AC97_REG_POWER, (codec->flags & AC97_F_EAPD_INV)? 0x8000 : 0x0000);
8179ec437a3SCameron Grant 
8189ec437a3SCameron Grant 	if (!codec->noext) {
819f9eb1409SOrion Hodson 		ac97_wrcd(codec, AC97_REGEXT_STAT, codec->extstat);
820f9eb1409SOrion Hodson 		if ((ac97_rdcd(codec, AC97_REGEXT_STAT) & AC97_EXTCAPS)
821b60e55dbSGuido van Rooij 		    != codec->extstat)
8229ec437a3SCameron Grant 			device_printf(codec->dev, "ac97 codec failed to reset extended mode (%x, got %x)\n",
823b60e55dbSGuido van Rooij 				      codec->extstat,
824f9eb1409SOrion Hodson 				      ac97_rdcd(codec, AC97_REGEXT_STAT) &
825b60e55dbSGuido van Rooij 				      AC97_EXTCAPS);
8269ec437a3SCameron Grant 	}
8279ec437a3SCameron Grant 
828f9eb1409SOrion Hodson 	if ((ac97_rdcd(codec, AC97_REG_POWER) & 2) == 0)
8299ec437a3SCameron Grant 		device_printf(codec->dev, "ac97 codec reports dac not ready\n");
83066ef8af5SCameron Grant 	snd_mtxunlock(codec->lock);
8319ec437a3SCameron Grant 	return 0;
8329ec437a3SCameron Grant }
8339ec437a3SCameron Grant 
834987e5972SCameron Grant struct ac97_info *
8350f55ac6cSCameron Grant ac97_create(device_t dev, void *devinfo, kobj_class_t cls)
836987e5972SCameron Grant {
837987e5972SCameron Grant 	struct ac97_info *codec;
83890da2b28SAriff Abdullah 	int i;
839987e5972SCameron Grant 
840082f6383SAriff Abdullah 	codec = malloc(sizeof(*codec), M_AC97, M_WAITOK | M_ZERO);
84169f6d261SAriff Abdullah 	snprintf(codec->name, sizeof(codec->name), "%s:ac97",
84269f6d261SAriff Abdullah 	    device_get_nameunit(dev));
843489c22ebSJohn Baldwin 	codec->lock = snd_mtxcreate(codec->name, "ac97 codec");
8447699548fSAriff Abdullah 	codec->methods = kobj_create(cls, M_AC97, M_WAITOK | M_ZERO);
8450f55ac6cSCameron Grant 	codec->dev = dev;
8460f55ac6cSCameron Grant 	codec->devinfo = devinfo;
84779bb7d52SCameron Grant 	codec->flags = 0;
84890da2b28SAriff Abdullah 
8497699548fSAriff Abdullah 	if (resource_int_value(device_get_name(dev), device_get_unit(dev),
85090da2b28SAriff Abdullah 	    "eapdinv", &i) == 0 && i != 0)
8517699548fSAriff Abdullah 		codec->flags |= AC97_F_EAPD_INV;
85290da2b28SAriff Abdullah 
85390da2b28SAriff Abdullah 	if (resource_int_value(device_get_name(dev), device_get_unit(dev),
85490da2b28SAriff Abdullah 	    "softpcmvol", &i) == 0 && i != 0)
85590da2b28SAriff Abdullah 		pcm_setflags(dev, pcm_getflags(dev) | SD_F_SOFTPCMVOL);
85690da2b28SAriff Abdullah 
857987e5972SCameron Grant 	return codec;
858987e5972SCameron Grant }
859987e5972SCameron Grant 
86033dbf14aSCameron Grant void
86133dbf14aSCameron Grant ac97_destroy(struct ac97_info *codec)
86233dbf14aSCameron Grant {
86366ef8af5SCameron Grant 	snd_mtxlock(codec->lock);
8640f55ac6cSCameron Grant 	if (codec->methods != NULL)
8650f55ac6cSCameron Grant 		kobj_delete(codec->methods, M_AC97);
86666ef8af5SCameron Grant 	snd_mtxfree(codec->lock);
8670f55ac6cSCameron Grant 	free(codec, M_AC97);
86833dbf14aSCameron Grant }
86933dbf14aSCameron Grant 
87079bb7d52SCameron Grant void
87179bb7d52SCameron Grant ac97_setflags(struct ac97_info *codec, u_int32_t val)
87279bb7d52SCameron Grant {
87379bb7d52SCameron Grant 	codec->flags = val;
87479bb7d52SCameron Grant }
87579bb7d52SCameron Grant 
87679bb7d52SCameron Grant u_int32_t
87779bb7d52SCameron Grant ac97_getflags(struct ac97_info *codec)
87879bb7d52SCameron Grant {
87979bb7d52SCameron Grant 	return codec->flags;
88079bb7d52SCameron Grant }
88179bb7d52SCameron Grant 
882*0b4e3291SChristos Margiolis static void
883*0b4e3291SChristos Margiolis ad1886_patch(struct ac97_info *codec)
884*0b4e3291SChristos Margiolis {
885*0b4e3291SChristos Margiolis #define AC97_AD_JACK_SPDIF 0x72
886*0b4e3291SChristos Margiolis 	/*
887*0b4e3291SChristos Margiolis 	 *    Presario700 workaround
888*0b4e3291SChristos Margiolis 	 *     for Jack Sense/SPDIF Register misetting causing
889*0b4e3291SChristos Margiolis 	 *    no audible output
890*0b4e3291SChristos Margiolis 	 *    by Santiago Nullo 04/05/2002
891*0b4e3291SChristos Margiolis 	 */
892*0b4e3291SChristos Margiolis 	ac97_wrcd(codec, AC97_AD_JACK_SPDIF, 0x0010);
893*0b4e3291SChristos Margiolis }
894*0b4e3291SChristos Margiolis 
895*0b4e3291SChristos Margiolis static void
896*0b4e3291SChristos Margiolis ad198x_patch(struct ac97_info *codec)
897*0b4e3291SChristos Margiolis {
898*0b4e3291SChristos Margiolis 	switch (ac97_getsubvendor(codec)) {
899*0b4e3291SChristos Margiolis 	case 0x11931043:	/* Not for ASUS A9T (probably else too). */
900*0b4e3291SChristos Margiolis 		break;
901*0b4e3291SChristos Margiolis 	default:
902*0b4e3291SChristos Margiolis 		ac97_wrcd(codec, 0x76, ac97_rdcd(codec, 0x76) | 0x0420);
903*0b4e3291SChristos Margiolis 		break;
904*0b4e3291SChristos Margiolis 	}
905*0b4e3291SChristos Margiolis }
906*0b4e3291SChristos Margiolis 
907*0b4e3291SChristos Margiolis static void
908*0b4e3291SChristos Margiolis ad1981b_patch(struct ac97_info *codec)
909*0b4e3291SChristos Margiolis {
910*0b4e3291SChristos Margiolis 	/*
911*0b4e3291SChristos Margiolis 	 * Enable headphone jack sensing.
912*0b4e3291SChristos Margiolis 	 */
913*0b4e3291SChristos Margiolis 	switch (ac97_getsubvendor(codec)) {
914*0b4e3291SChristos Margiolis 	case 0x02d91014:	/* IBM Thinkcentre */
915*0b4e3291SChristos Margiolis 	case 0x099c103c:	/* HP nx6110 */
916*0b4e3291SChristos Margiolis 		ac97_wrcd(codec, AC97_AD_JACK_SPDIF,
917*0b4e3291SChristos Margiolis 		    ac97_rdcd(codec, AC97_AD_JACK_SPDIF) | 0x0800);
918*0b4e3291SChristos Margiolis 		break;
919*0b4e3291SChristos Margiolis 	default:
920*0b4e3291SChristos Margiolis 		break;
921*0b4e3291SChristos Margiolis 	}
922*0b4e3291SChristos Margiolis }
923*0b4e3291SChristos Margiolis 
924*0b4e3291SChristos Margiolis static void
925*0b4e3291SChristos Margiolis cmi9739_patch(struct ac97_info *codec)
926*0b4e3291SChristos Margiolis {
927*0b4e3291SChristos Margiolis 	/*
928*0b4e3291SChristos Margiolis 	 * Few laptops need extra register initialization
929*0b4e3291SChristos Margiolis 	 * to power up the internal speakers.
930*0b4e3291SChristos Margiolis 	 */
931*0b4e3291SChristos Margiolis 	switch (ac97_getsubvendor(codec)) {
932*0b4e3291SChristos Margiolis 	case 0x18431043:	/* ASUS W1000N */
933*0b4e3291SChristos Margiolis 		ac97_wrcd(codec, AC97_REG_POWER, 0x000f);
934*0b4e3291SChristos Margiolis 		ac97_wrcd(codec, AC97_MIXEXT_CLFE, 0x0000);
935*0b4e3291SChristos Margiolis 		ac97_wrcd(codec, 0x64, 0x7110);
936*0b4e3291SChristos Margiolis 		break;
937*0b4e3291SChristos Margiolis 	default:
938*0b4e3291SChristos Margiolis 		break;
939*0b4e3291SChristos Margiolis 	}
940*0b4e3291SChristos Margiolis }
941*0b4e3291SChristos Margiolis 
942*0b4e3291SChristos Margiolis static void
943*0b4e3291SChristos Margiolis alc655_patch(struct ac97_info *codec)
944*0b4e3291SChristos Margiolis {
945*0b4e3291SChristos Margiolis 	/*
946*0b4e3291SChristos Margiolis 	 * MSI (Micro-Star International) specific EAPD quirk.
947*0b4e3291SChristos Margiolis 	 */
948*0b4e3291SChristos Margiolis 	switch (ac97_getsubvendor(codec)) {
949*0b4e3291SChristos Margiolis 	case 0x00611462:	/* MSI S250 */
950*0b4e3291SChristos Margiolis 	case 0x01311462:	/* MSI S270 */
951*0b4e3291SChristos Margiolis 	case 0x01611462:	/* LG K1 Express */
952*0b4e3291SChristos Margiolis 	case 0x03511462:	/* MSI L725 */
953*0b4e3291SChristos Margiolis 		ac97_wrcd(codec, 0x7a, ac97_rdcd(codec, 0x7a) & 0xfffd);
954*0b4e3291SChristos Margiolis 		break;
955*0b4e3291SChristos Margiolis 	case 0x10ca1734:
956*0b4e3291SChristos Margiolis 		/*
957*0b4e3291SChristos Margiolis 		 * Amilo Pro V2055 with ALC655 has phone out by default
958*0b4e3291SChristos Margiolis 		 * disabled (surround on), leaving us only with internal
959*0b4e3291SChristos Margiolis 		 * speakers. This should really go to mixer. We write the
960*0b4e3291SChristos Margiolis 		 * Data Flow Control reg.
961*0b4e3291SChristos Margiolis 		 */
962*0b4e3291SChristos Margiolis 		ac97_wrcd(codec, 0x6a, ac97_rdcd(codec, 0x6a) | 0x0001);
963*0b4e3291SChristos Margiolis 		break;
964*0b4e3291SChristos Margiolis 	default:
965*0b4e3291SChristos Margiolis 		break;
966*0b4e3291SChristos Margiolis 	}
967*0b4e3291SChristos Margiolis }
968*0b4e3291SChristos Margiolis 
9690f55ac6cSCameron Grant /* -------------------------------------------------------------------- */
9700f55ac6cSCameron Grant 
971a580b31aSAriff Abdullah static int
972a580b31aSAriff Abdullah sysctl_hw_snd_ac97_eapd(SYSCTL_HANDLER_ARGS)
973a580b31aSAriff Abdullah {
974a580b31aSAriff Abdullah 	struct ac97_info *codec;
975a580b31aSAriff Abdullah 	int ea, inv, err = 0;
976a580b31aSAriff Abdullah 	u_int16_t val;
977a580b31aSAriff Abdullah 
978a580b31aSAriff Abdullah 	codec = oidp->oid_arg1;
979a580b31aSAriff Abdullah 	if (codec == NULL || codec->id == 0 || codec->lock == NULL)
980a580b31aSAriff Abdullah 		return EINVAL;
981a580b31aSAriff Abdullah 	snd_mtxlock(codec->lock);
982a580b31aSAriff Abdullah 	val = ac97_rdcd(codec, AC97_REG_POWER);
983a580b31aSAriff Abdullah 	inv = (codec->flags & AC97_F_EAPD_INV) ? 0 : 1;
984a580b31aSAriff Abdullah 	ea = (val >> 15) ^ inv;
985a580b31aSAriff Abdullah 	snd_mtxunlock(codec->lock);
986041b706bSDavid Malone 	err = sysctl_handle_int(oidp, &ea, 0, req);
987a580b31aSAriff Abdullah 	if (err == 0 && req->newptr != NULL) {
988a580b31aSAriff Abdullah 		if (ea != 0 && ea != 1)
989a580b31aSAriff Abdullah 			return EINVAL;
990a580b31aSAriff Abdullah 		if (ea != ((val >> 15) ^ inv)) {
991a580b31aSAriff Abdullah 			snd_mtxlock(codec->lock);
992a580b31aSAriff Abdullah 			ac97_wrcd(codec, AC97_REG_POWER, val ^ 0x8000);
993a580b31aSAriff Abdullah 			snd_mtxunlock(codec->lock);
994a580b31aSAriff Abdullah 		}
995a580b31aSAriff Abdullah 	}
996a580b31aSAriff Abdullah 	return err;
997a580b31aSAriff Abdullah }
998a580b31aSAriff Abdullah 
999a580b31aSAriff Abdullah static void
1000a580b31aSAriff Abdullah ac97_init_sysctl(struct ac97_info *codec)
1001a580b31aSAriff Abdullah {
1002a580b31aSAriff Abdullah 	u_int16_t orig, val;
1003a580b31aSAriff Abdullah 
1004a580b31aSAriff Abdullah 	if (codec == NULL || codec->dev == NULL)
1005a580b31aSAriff Abdullah 		return;
1006a580b31aSAriff Abdullah 	snd_mtxlock(codec->lock);
1007a580b31aSAriff Abdullah 	orig = ac97_rdcd(codec, AC97_REG_POWER);
1008a580b31aSAriff Abdullah 	ac97_wrcd(codec, AC97_REG_POWER, orig ^ 0x8000);
1009a580b31aSAriff Abdullah 	val = ac97_rdcd(codec, AC97_REG_POWER);
1010a580b31aSAriff Abdullah 	ac97_wrcd(codec, AC97_REG_POWER, orig);
1011a580b31aSAriff Abdullah 	snd_mtxunlock(codec->lock);
1012a580b31aSAriff Abdullah 	if ((val & 0x8000) == (orig & 0x8000))
1013a580b31aSAriff Abdullah 		return;
1014a580b31aSAriff Abdullah 	SYSCTL_ADD_PROC(device_get_sysctl_ctx(codec->dev),
1015a580b31aSAriff Abdullah 	    SYSCTL_CHILDREN(device_get_sysctl_tree(codec->dev)),
10167029da5cSPawel Biernacki             OID_AUTO, "eapd",
10177029da5cSPawel Biernacki 	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE,
1018a580b31aSAriff Abdullah 	    codec, sizeof(codec), sysctl_hw_snd_ac97_eapd,
1019a580b31aSAriff Abdullah 	    "I", "AC97 External Amplifier");
1020a580b31aSAriff Abdullah }
1021a580b31aSAriff Abdullah 
1022987e5972SCameron Grant static int
102366ef8af5SCameron Grant ac97mix_init(struct snd_mixer *m)
1024987e5972SCameron Grant {
1025987e5972SCameron Grant 	struct ac97_info *codec = mix_getdevinfo(m);
1026341f16ccSCameron Grant 	u_int32_t i, mask;
1027341f16ccSCameron Grant 
102839004e69SCameron Grant 	if (codec == NULL)
102939004e69SCameron Grant 		return -1;
1030341f16ccSCameron Grant 
1031e620d959SCameron Grant 	if (ac97_initmixer(codec))
1032e620d959SCameron Grant 		return -1;
1033341f16ccSCameron Grant 
10347699548fSAriff Abdullah 	switch (codec->id) {
10357699548fSAriff Abdullah 	case 0x41445374:	/* AD1981B */
103655431491SAriff Abdullah 		switch (codec->subvendor) {
103755431491SAriff Abdullah 		case 0x02d91014:
1038fd7390d6SAriff Abdullah 			/*
1039fd7390d6SAriff Abdullah 			 * IBM Thinkcentre:
104055431491SAriff Abdullah 			 *
104155431491SAriff Abdullah 			 * Tie "ogain" and "phout" to "vol" since its
1042fd7390d6SAriff Abdullah 			 * master volume is basically useless and can't
1043fd7390d6SAriff Abdullah 			 * control anything.
1044fd7390d6SAriff Abdullah 			 */
10457699548fSAriff Abdullah 			mask = 0;
10467699548fSAriff Abdullah 			if (codec->mix[SOUND_MIXER_OGAIN].enable)
10477699548fSAriff Abdullah 				mask |= SOUND_MASK_OGAIN;
10487699548fSAriff Abdullah 			if (codec->mix[SOUND_MIXER_PHONEOUT].enable)
10497699548fSAriff Abdullah 				mask |= SOUND_MASK_PHONEOUT;
10507699548fSAriff Abdullah 			if (codec->mix[SOUND_MIXER_VOLUME].enable)
1051a580b31aSAriff Abdullah 				mix_setparentchild(m, SOUND_MIXER_VOLUME,
1052a580b31aSAriff Abdullah 				    mask);
10537699548fSAriff Abdullah 			else {
1054a580b31aSAriff Abdullah 				mix_setparentchild(m, SOUND_MIXER_VOLUME,
1055a580b31aSAriff Abdullah 				    mask);
1056a580b31aSAriff Abdullah 				mix_setrealdev(m, SOUND_MIXER_VOLUME,
1057a580b31aSAriff Abdullah 				    SOUND_MIXER_NONE);
10587699548fSAriff Abdullah 			}
105955431491SAriff Abdullah 			break;
106055431491SAriff Abdullah 		case 0x099c103c:
106155431491SAriff Abdullah 			/*
106255431491SAriff Abdullah 			 * HP nx6110:
106355431491SAriff Abdullah 			 *
106455431491SAriff Abdullah 			 * By default, "vol" is controlling internal speakers
106555431491SAriff Abdullah 			 * (not a master volume!) and "ogain" is controlling
106655431491SAriff Abdullah 			 * headphone. Enable dummy "phout" so it can be
106755431491SAriff Abdullah 			 * remapped to internal speakers and virtualize
106855431491SAriff Abdullah 			 * "vol" to control both.
106955431491SAriff Abdullah 			 */
107055431491SAriff Abdullah 			codec->mix[SOUND_MIXER_OGAIN].enable = 1;
107155431491SAriff Abdullah 			codec->mix[SOUND_MIXER_PHONEOUT].enable = 1;
107255431491SAriff Abdullah 			mix_setrealdev(m, SOUND_MIXER_PHONEOUT,
107355431491SAriff Abdullah 			    SOUND_MIXER_VOLUME);
107455431491SAriff Abdullah 			mix_setrealdev(m, SOUND_MIXER_VOLUME,
107555431491SAriff Abdullah 			    SOUND_MIXER_NONE);
107655431491SAriff Abdullah 			mix_setparentchild(m, SOUND_MIXER_VOLUME,
107755431491SAriff Abdullah 			    SOUND_MASK_OGAIN | SOUND_MASK_PHONEOUT);
107855431491SAriff Abdullah 			break;
107955431491SAriff Abdullah 		default:
108055431491SAriff Abdullah 			break;
1081a580b31aSAriff Abdullah 		}
10827699548fSAriff Abdullah 		break;
10837699548fSAriff Abdullah 	case 0x434d4941:	/* CMI9738 */
10847699548fSAriff Abdullah 	case 0x434d4961:	/* CMI9739 */
10857699548fSAriff Abdullah 	case 0x434d4978:	/* CMI9761 */
10867699548fSAriff Abdullah 	case 0x434d4982:	/* CMI9761 */
10877699548fSAriff Abdullah 	case 0x434d4983:	/* CMI9761 */
10887699548fSAriff Abdullah 		bzero(&codec->mix[SOUND_MIXER_PCM],
10897699548fSAriff Abdullah 		    sizeof(codec->mix[SOUND_MIXER_PCM]));
1090e510f521SAriff Abdullah 		pcm_setflags(codec->dev, pcm_getflags(codec->dev) |
1091e510f521SAriff Abdullah 		    SD_F_SOFTPCMVOL);
10927699548fSAriff Abdullah 		/* XXX How about master volume ? */
10937699548fSAriff Abdullah 		break;
10947699548fSAriff Abdullah 	default:
10957699548fSAriff Abdullah 		break;
10967699548fSAriff Abdullah 	}
10977699548fSAriff Abdullah 
109890da2b28SAriff Abdullah 	if (pcm_getflags(codec->dev) & SD_F_SOFTPCMVOL)
109990da2b28SAriff Abdullah 		ac97_wrcd(codec, AC97_MIX_PCM, 0);
11007699548fSAriff Abdullah #if 0
11017699548fSAriff Abdullah 	/* XXX For the sake of debugging purposes */
11027699548fSAriff Abdullah 	mix_setparentchild(m, SOUND_MIXER_VOLUME,
11037699548fSAriff Abdullah 	    SOUND_MASK_PCM | SOUND_MASK_CD);
11047699548fSAriff Abdullah 	mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE);
11057699548fSAriff Abdullah 	ac97_wrcd(codec, AC97_MIX_MASTER, 0);
11067699548fSAriff Abdullah #endif
11077699548fSAriff Abdullah 
1108341f16ccSCameron Grant 	mask = 0;
110969f6d261SAriff Abdullah 	for (i = 0; i < AC97_MIXER_SIZE; i++)
1110341f16ccSCameron Grant 		mask |= codec->mix[i].enable? 1 << i : 0;
1111341f16ccSCameron Grant 	mix_setdevs(m, mask);
1112341f16ccSCameron Grant 
1113341f16ccSCameron Grant 	mask = 0;
111469f6d261SAriff Abdullah 	for (i = 0; i < AC97_MIXER_SIZE; i++)
1115341f16ccSCameron Grant 		mask |= codec->mix[i].recidx? 1 << i : 0;
1116341f16ccSCameron Grant 	mix_setrecdevs(m, mask);
1117a580b31aSAriff Abdullah 
1118a580b31aSAriff Abdullah 	ac97_init_sysctl(codec);
1119a580b31aSAriff Abdullah 
1120987e5972SCameron Grant 	return 0;
1121987e5972SCameron Grant }
1122987e5972SCameron Grant 
1123987e5972SCameron Grant static int
112466ef8af5SCameron Grant ac97mix_uninit(struct snd_mixer *m)
112533dbf14aSCameron Grant {
112633dbf14aSCameron Grant 	struct ac97_info *codec = mix_getdevinfo(m);
1127341f16ccSCameron Grant 
112833dbf14aSCameron Grant 	if (codec == NULL)
112933dbf14aSCameron Grant 		return -1;
113033dbf14aSCameron Grant 	/*
113133dbf14aSCameron Grant 	if (ac97_uninitmixer(codec))
113233dbf14aSCameron Grant 		return -1;
113333dbf14aSCameron Grant 	*/
113433dbf14aSCameron Grant 	ac97_destroy(codec);
113533dbf14aSCameron Grant 	return 0;
113633dbf14aSCameron Grant }
113733dbf14aSCameron Grant 
113833dbf14aSCameron Grant static int
113966ef8af5SCameron Grant ac97mix_reinit(struct snd_mixer *m)
11409ec437a3SCameron Grant {
11419ec437a3SCameron Grant 	struct ac97_info *codec = mix_getdevinfo(m);
11429ec437a3SCameron Grant 
11439ec437a3SCameron Grant 	if (codec == NULL)
11449ec437a3SCameron Grant 		return -1;
11459ec437a3SCameron Grant 	return ac97_reinitmixer(codec);
11469ec437a3SCameron Grant }
11479ec437a3SCameron Grant 
11489ec437a3SCameron Grant static int
114966ef8af5SCameron Grant ac97mix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right)
1150987e5972SCameron Grant {
1151987e5972SCameron Grant 	struct ac97_info *codec = mix_getdevinfo(m);
1152341f16ccSCameron Grant 
115369f6d261SAriff Abdullah 	if (codec == NULL || dev >= AC97_MIXER_SIZE)
115439004e69SCameron Grant 		return -1;
1155987e5972SCameron Grant 	return ac97_setmixer(codec, dev, left, right);
1156987e5972SCameron Grant }
1157987e5972SCameron Grant 
115890da2b28SAriff Abdullah static u_int32_t
115966ef8af5SCameron Grant ac97mix_setrecsrc(struct snd_mixer *m, u_int32_t src)
1160987e5972SCameron Grant {
1161987e5972SCameron Grant 	int i;
1162987e5972SCameron Grant 	struct ac97_info *codec = mix_getdevinfo(m);
1163341f16ccSCameron Grant 
116439004e69SCameron Grant 	if (codec == NULL)
116539004e69SCameron Grant 		return -1;
116669f6d261SAriff Abdullah 	for (i = 0; i < AC97_MIXER_SIZE; i++)
116739004e69SCameron Grant 		if ((src & (1 << i)) != 0)
116839004e69SCameron Grant 			break;
116990da2b28SAriff Abdullah 	return (ac97_setrecsrc(codec, i) == 0)? 1U << i : 0xffffffffU;
1170987e5972SCameron Grant }
1171987e5972SCameron Grant 
11720f55ac6cSCameron Grant static kobj_method_t ac97mixer_methods[] = {
11730f55ac6cSCameron Grant     	KOBJMETHOD(mixer_init,		ac97mix_init),
11740f55ac6cSCameron Grant     	KOBJMETHOD(mixer_uninit,	ac97mix_uninit),
11750f55ac6cSCameron Grant     	KOBJMETHOD(mixer_reinit,	ac97mix_reinit),
11760f55ac6cSCameron Grant     	KOBJMETHOD(mixer_set,		ac97mix_set),
11770f55ac6cSCameron Grant     	KOBJMETHOD(mixer_setrecsrc,	ac97mix_setrecsrc),
117890da2b28SAriff Abdullah 	KOBJMETHOD_END
1179987e5972SCameron Grant };
11800f55ac6cSCameron Grant MIXER_DECLARE(ac97mixer);
11810f55ac6cSCameron Grant 
11820f55ac6cSCameron Grant /* -------------------------------------------------------------------- */
11830f55ac6cSCameron Grant 
11840f55ac6cSCameron Grant kobj_class_t
11850f55ac6cSCameron Grant ac97_getmixerclass(void)
11860f55ac6cSCameron Grant {
11870f55ac6cSCameron Grant 	return &ac97mixer_class;
11880f55ac6cSCameron Grant }
1189