ac97.c revision 83612
1/*
2 * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <dev/sound/pcm/sound.h>
28#include <dev/sound/pcm/ac97.h>
29
30#include "mixer_if.h"
31
32SND_DECLARE_FILE("$FreeBSD: head/sys/dev/sound/pcm/ac97.c 83612 2001-09-18 14:34:59Z cg $");
33
34MALLOC_DEFINE(M_AC97, "ac97", "ac97 codec");
35
36struct ac97mixtable_entry {
37	int		reg:8;
38	unsigned	bits:4;
39	unsigned	ofs:4;
40	unsigned	stereo:1;
41	unsigned	mute:1;
42	unsigned	recidx:4;
43	unsigned        mask:1;
44	unsigned	enable:1;
45};
46
47#define AC97_NAMELEN	16
48struct ac97_info {
49	kobj_t methods;
50	device_t dev;
51	void *devinfo;
52	char *id;
53	char rev;
54	unsigned count, caps, se, extcaps, extid, extstat, noext:1;
55	u_int32_t flags;
56	struct ac97mixtable_entry mix[32];
57	char name[AC97_NAMELEN];
58	void *lock;
59};
60
61struct ac97_codecid {
62	u_int32_t id, noext:1;
63	char *name;
64};
65
66static const struct ac97mixtable_entry ac97mixtable_default[32] = {
67	[SOUND_MIXER_VOLUME]	= { AC97_MIX_MASTER, 	5, 0, 1, 1, 6, 0, 1 },
68	[SOUND_MIXER_MONITOR]	= { AC97_MIX_PHONES, 	5, 0, 1, 1, 0, 0, 0 },
69	[SOUND_MIXER_PHONEOUT]	= { AC97_MIX_MONO, 	5, 0, 0, 1, 7, 0, 0 },
70	[SOUND_MIXER_BASS]	= { AC97_MIX_TONE, 	4, 8, 0, 0, 0, 1, 0 },
71	[SOUND_MIXER_TREBLE]	= { AC97_MIX_TONE, 	4, 0, 0, 0, 0, 1, 0 },
72	[SOUND_MIXER_PCM]	= { AC97_MIX_PCM, 	5, 0, 1, 1, 0, 0, 1 },
73	[SOUND_MIXER_SPEAKER]	= { AC97_MIX_BEEP, 	4, 1, 0, 1, 0, 0, 0 },
74	[SOUND_MIXER_LINE]	= { AC97_MIX_LINE, 	5, 0, 1, 1, 5, 0, 1 },
75	[SOUND_MIXER_PHONEIN]	= { AC97_MIX_PHONE, 	5, 0, 0, 1, 8, 0, 0 },
76	[SOUND_MIXER_MIC] 	= { AC97_MIX_MIC, 	5, 0, 0, 1, 1, 0, 1 },
77	[SOUND_MIXER_CD]	= { AC97_MIX_CD, 	5, 0, 1, 1, 2, 0, 1 },
78	[SOUND_MIXER_LINE1]	= { AC97_MIX_AUX, 	5, 0, 1, 1, 4, 0, 0 },
79	[SOUND_MIXER_VIDEO]	= { AC97_MIX_VIDEO, 	5, 0, 1, 1, 3, 0, 0 },
80	[SOUND_MIXER_RECLEV]	= { -AC97_MIX_RGAIN, 	4, 0, 1, 1, 0, 0, 1 }
81};
82
83static struct ac97_codecid ac97codecid[] = {
84	{ 0x41445303, 0, "Analog Devices AD1819" },
85	{ 0x41445340, 0, "Analog Devices AD1881" },
86	{ 0x41445348, 0, "Analog Devices AD1881A" },
87	{ 0x41445360, 0, "Analog Devices AD1885" },
88	{ 0x414b4d00, 1, "Asahi Kasei AK4540" },
89	{ 0x414b4d01, 1, "Asahi Kasei AK4542" },
90	{ 0x414b4d02, 1, "Asahi Kasei AK4543" },
91	{ 0x414c4710, 0, "Avance Logic ALC200/200P" },
92	{ 0x43525900, 0, "Cirrus Logic CS4297" },
93	{ 0x43525903, 0, "Cirrus Logic CS4297" },
94	{ 0x43525913, 0, "Cirrus Logic CS4297A" },
95	{ 0x43525914, 0, "Cirrus Logic CS4297B" },
96	{ 0x43525923, 0, "Cirrus Logic CS4294C" },
97	{ 0x4352592b, 0, "Cirrus Logic CS4298C" },
98	{ 0x43525931, 0, "Cirrus Logic CS4299A" },
99	{ 0x43525933, 0, "Cirrus Logic CS4299C" },
100	{ 0x43525934, 0, "Cirrus Logic CS4299D" },
101	{ 0x43525941, 0, "Cirrus Logic CS4201A" },
102	{ 0x43525951, 0, "Cirrus Logic CS4205A" },
103	{ 0x43525961, 0, "Cirrus Logic CS4291A" },
104	{ 0x45838308, 0, "ESS Technology ES1921" },
105	{ 0x49434511, 0, "ICEnsemble ICE1232" },
106	{ 0x4e534331, 0, "National Semiconductor LM4549" },
107	{ 0x83847600, 0, "SigmaTel STAC9700/9783/9784" },
108	{ 0x83847604, 0, "SigmaTel STAC9701/9703/9704/9705" },
109	{ 0x83847605, 0, "SigmaTel STAC9704" },
110	{ 0x83847608, 0, "SigmaTel STAC9708/9711" },
111	{ 0x83847609, 0, "SigmaTel STAC9721/9723" },
112	{ 0x83847644, 0, "SigmaTel STAC9744" },
113	{ 0x83847656, 0, "SigmaTel STAC9756/9757" },
114	{ 0x53494c22, 0, "Silicon Laboratory Si3036" },
115	{ 0x53494c23, 0, "Silicon Laboratory Si3038" },
116	{ 0x54524103, 0, "TriTech TR?????" },
117	{ 0x54524106, 0, "TriTech TR28026" },
118	{ 0x54524108, 0, "TriTech TR28028" },
119	{ 0x54524123, 0, "TriTech TR28602" },
120	{ 0x574d4c00, 0, "Wolfson WM9701A" },
121	{ 0x574d4c03, 0, "Wolfson WM9703/9704" },
122	{ 0x574d4c04, 0, "Wolfson WM9704 (quad)" },
123	{ 0, 0, NULL }
124};
125
126static char *ac97enhancement[] = {
127	"no 3D Stereo Enhancement",
128	"Analog Devices Phat Stereo",
129	"Creative Stereo Enhancement",
130	"National Semi 3D Stereo Enhancement",
131	"Yamaha Ymersion",
132	"BBE 3D Stereo Enhancement",
133	"Crystal Semi 3D Stereo Enhancement",
134	"Qsound QXpander",
135	"Spatializer 3D Stereo Enhancement",
136	"SRS 3D Stereo Enhancement",
137	"Platform Tech 3D Stereo Enhancement",
138	"AKM 3D Audio",
139	"Aureal Stereo Enhancement",
140	"Aztech 3D Enhancement",
141	"Binaura 3D Audio Enhancement",
142	"ESS Technology Stereo Enhancement",
143	"Harman International VMAx",
144	"Nvidea 3D Stereo Enhancement",
145	"Philips Incredible Sound",
146	"Texas Instruments 3D Stereo Enhancement",
147	"VLSI Technology 3D Stereo Enhancement",
148	"TriTech 3D Stereo Enhancement",
149	"Realtek 3D Stereo Enhancement",
150	"Samsung 3D Stereo Enhancement",
151	"Wolfson Microelectronics 3D Enhancement",
152	"Delta Integration 3D Enhancement",
153	"SigmaTel 3D Enhancement",
154	"Reserved 27",
155	"Rockwell 3D Stereo Enhancement",
156	"Reserved 29",
157	"Reserved 30",
158	"Reserved 31"
159};
160
161static char *ac97feature[] = {
162	"mic channel",
163	"reserved",
164	"tone",
165	"simulated stereo",
166	"headphone",
167	"bass boost",
168	"18 bit DAC",
169	"20 bit DAC",
170	"18 bit ADC",
171	"20 bit ADC"
172};
173
174static char *ac97extfeature[] = {
175	"variable rate PCM",
176	"double rate PCM",
177	"reserved 1",
178	"variable rate mic",
179	"reserved 2",
180	"reserved 3",
181	"center DAC",
182	"surround DAC",
183	"LFE DAC",
184	"AMAP",
185	"reserved 4",
186	"reserved 5",
187	"reserved 6",
188	"reserved 7",
189};
190
191static u_int16_t
192rdcd(struct ac97_info *codec, int reg)
193{
194	return AC97_READ(codec->methods, codec->devinfo, reg);
195}
196
197static void
198wrcd(struct ac97_info *codec, int reg, u_int16_t val)
199{
200	AC97_WRITE(codec->methods, codec->devinfo, reg, val);
201}
202
203int
204ac97_setrate(struct ac97_info *codec, int which, int rate)
205{
206	u_int16_t v;
207
208	switch(which) {
209	case AC97_REGEXT_FDACRATE:
210	case AC97_REGEXT_SDACRATE:
211	case AC97_REGEXT_LDACRATE:
212	case AC97_REGEXT_LADCRATE:
213	case AC97_REGEXT_MADCRATE:
214		break;
215
216	default:
217		return -1;
218	}
219
220	snd_mtxlock(codec->lock);
221	if (rate != 0) {
222		v = rate;
223		if (codec->extstat & AC97_EXTCAP_DRA)
224			v >>= 1;
225		wrcd(codec, which, v);
226	}
227	v = rdcd(codec, which);
228	if (codec->extstat & AC97_EXTCAP_DRA)
229		v <<= 1;
230	snd_mtxunlock(codec->lock);
231	return v;
232}
233
234int
235ac97_setextmode(struct ac97_info *codec, u_int16_t mode)
236{
237	mode &= AC97_EXTCAPS;
238	if ((mode & ~codec->extcaps) != 0)
239		return -1;
240	snd_mtxlock(codec->lock);
241	wrcd(codec, AC97_REGEXT_STAT, mode);
242	codec->extstat = rdcd(codec, AC97_REGEXT_STAT) & AC97_EXTCAPS;
243	snd_mtxunlock(codec->lock);
244	return (mode == codec->extstat)? 0 : -1;
245}
246
247u_int16_t
248ac97_getextmode(struct ac97_info *codec)
249{
250	return codec->extstat;
251}
252
253u_int16_t
254ac97_getextcaps(struct ac97_info *codec)
255{
256	return codec->extcaps;
257}
258
259u_int16_t
260ac97_getcaps(struct ac97_info *codec)
261{
262	return codec->caps;
263}
264
265static int
266ac97_setrecsrc(struct ac97_info *codec, int channel)
267{
268	struct ac97mixtable_entry *e = &codec->mix[channel];
269
270	if (e->recidx > 0) {
271		int val = e->recidx - 1;
272		val |= val << 8;
273		snd_mtxlock(codec->lock);
274		wrcd(codec, AC97_REG_RECSEL, val);
275		snd_mtxunlock(codec->lock);
276		return 0;
277	} else
278		return -1;
279}
280
281static int
282ac97_setmixer(struct ac97_info *codec, unsigned channel, unsigned left, unsigned right)
283{
284	struct ac97mixtable_entry *e = &codec->mix[channel];
285
286	if (e->reg && e->enable && e->bits) {
287		int max, val, reg = (e->reg >= 0)? e->reg : -e->reg;
288
289		if (!e->stereo)
290			right = left;
291		if (e->reg > 0) {
292			left = 100 - left;
293			right = 100 - right;
294		}
295
296		max = (1 << e->bits) - 1;
297		left = (left * max) / 100;
298		right = (right * max) / 100;
299
300		val = (left << 8) | right;
301
302		left = (left * 100) / max;
303		right = (right * 100) / max;
304
305		if (e->reg > 0) {
306			left = 100 - left;
307			right = 100 - right;
308		}
309
310		if (!e->stereo) {
311			val &= max;
312			val <<= e->ofs;
313			if (e->mask) {
314				int cur = rdcd(codec, e->reg);
315				val |= cur & ~(max << e->ofs);
316			}
317		}
318		if (left == 0 && right == 0 && e->mute == 1)
319			val = AC97_MUTE;
320		snd_mtxlock(codec->lock);
321		wrcd(codec, reg, val);
322		snd_mtxunlock(codec->lock);
323		return left | (right << 8);
324	} else {
325		/* printf("ac97_setmixer: reg=%d, bits=%d, enable=%d\n", e->reg, e->bits, e->enable); */
326		return -1;
327	}
328}
329
330#if 0
331static int
332ac97_getmixer(struct ac97_info *codec, int channel)
333{
334	struct ac97mixtable_entry *e = &codec->mix[channel];
335	if (channel < SOUND_MIXER_NRDEVICES && e->reg != 0) {
336		int max, val, volume;
337
338		max = (1 << e->bits) - 1;
339		val = rdcd(code, e->reg);
340		if (val == AC97_MUTE && e->mute == 1)
341			volume = 0;
342		else {
343			if (e->stereo == 0) val >>= e->ofs;
344			val &= max;
345			volume = (val * 100) / max;
346			if (e->reg > 0) volume = 100 - volume;
347		}
348		return volume;
349	} else
350		return -1;
351}
352#endif
353
354static unsigned
355ac97_initmixer(struct ac97_info *codec)
356{
357	unsigned i, j, k, old;
358	u_int32_t id;
359
360	snd_mtxlock(codec->lock);
361	for (i = 0; i < 32; i++)
362		codec->mix[i] = ac97mixtable_default[i];
363
364	codec->count = AC97_INIT(codec->methods, codec->devinfo);
365	if (codec->count == 0) {
366		device_printf(codec->dev, "ac97 codec init failed\n");
367		snd_mtxunlock(codec->lock);
368		return ENODEV;
369	}
370
371	wrcd(codec, AC97_REG_POWER, (codec->flags & AC97_F_EAPD_INV)? 0x8000 : 0x0000);
372	wrcd(codec, AC97_REG_RESET, 0);
373	DELAY(100000);
374	wrcd(codec, AC97_REG_POWER, (codec->flags & AC97_F_EAPD_INV)? 0x8000 : 0x0000);
375
376	i = rdcd(codec, AC97_REG_RESET);
377	codec->caps = i & 0x03ff;
378	codec->se =  (i & 0x7c00) >> 10;
379
380	id = (rdcd(codec, AC97_REG_ID1) << 16) | rdcd(codec, AC97_REG_ID2);
381	codec->rev = id & 0x000000ff;
382	if (id == 0 || id == 0xffffffff) {
383		device_printf(codec->dev, "ac97 codec invalid or not present (id == %x)\n", id);
384		snd_mtxunlock(codec->lock);
385		return ENODEV;
386	}
387
388	codec->noext = 0;
389	codec->id = NULL;
390	for (i = 0; ac97codecid[i].id; i++) {
391		if (ac97codecid[i].id == id) {
392			codec->id = ac97codecid[i].name;
393			codec->noext = ac97codecid[i].noext;
394		}
395	}
396
397	codec->extcaps = 0;
398	codec->extid = 0;
399	codec->extstat = 0;
400	if (!codec->noext) {
401		i = rdcd(codec, AC97_REGEXT_ID);
402		if (i != 0xffff) {
403			codec->extcaps = i & 0x3fff;
404			codec->extid =  (i & 0xc000) >> 14;
405			codec->extstat = rdcd(codec, AC97_REGEXT_STAT) & AC97_EXTCAPS;
406		}
407	}
408
409	for (i = 0; i < 32; i++) {
410		k = codec->noext? codec->mix[i].enable : 1;
411		if (k && (codec->mix[i].reg > 0)) {
412			old = rdcd(codec, codec->mix[i].reg);
413			wrcd(codec, codec->mix[i].reg, 0x3f);
414			j = rdcd(codec, codec->mix[i].reg);
415			wrcd(codec, codec->mix[i].reg, old);
416			codec->mix[i].enable = (j != 0 && j != old)? 1 : 0;
417			for (k = 1; j & (1 << k); k++);
418			codec->mix[i].bits = j? k - codec->mix[i].ofs : 0;
419		}
420		/* printf("mixch %d, en=%d, b=%d\n", i, codec->mix[i].enable, codec->mix[i].bits); */
421	}
422
423	if (bootverbose) {
424		device_printf(codec->dev, "ac97 codec id 0x%08x", id);
425		if (codec->id)
426			printf(" (%s)", codec->id);
427		printf("\n");
428		device_printf(codec->dev, "ac97 codec features ");
429		for (i = j = 0; i < 10; i++)
430			if (codec->caps & (1 << i))
431				printf("%s%s", j++? ", " : "", ac97feature[i]);
432		printf("%s%d bit master volume", j++? ", " : "", codec->mix[SOUND_MIXER_VOLUME].bits);
433		printf("%s%s\n", j? ", " : "", ac97enhancement[codec->se]);
434
435		if (codec->extcaps != 0 || codec->extid) {
436			device_printf(codec->dev, "ac97 %s codec",
437				      codec->extid? "secondary" : "primary");
438			if (codec->extcaps)
439				printf(" extended features ");
440			for (i = j = 0; i < 14; i++)
441				if (codec->extcaps & (1 << i))
442					printf("%s%s", j++? ", " : "", ac97extfeature[i]);
443			printf("\n");
444		}
445	}
446
447	if ((rdcd(codec, AC97_REG_POWER) & 2) == 0)
448		device_printf(codec->dev, "ac97 codec reports dac not ready\n");
449	snd_mtxunlock(codec->lock);
450	return 0;
451}
452
453static unsigned
454ac97_reinitmixer(struct ac97_info *codec)
455{
456	unsigned i;
457
458	snd_mtxlock(codec->lock);
459	codec->count = AC97_INIT(codec->methods, codec->devinfo);
460	if (codec->count == 0) {
461		device_printf(codec->dev, "ac97 codec init failed\n");
462		snd_mtxunlock(codec->lock);
463		return ENODEV;
464	}
465
466	wrcd(codec, AC97_REG_POWER, (codec->flags & AC97_F_EAPD_INV)? 0x8000 : 0x0000);
467	wrcd(codec, AC97_REG_RESET, 0);
468	DELAY(100000);
469	wrcd(codec, AC97_REG_POWER, (codec->flags & AC97_F_EAPD_INV)? 0x8000 : 0x0000);
470	i = rdcd(codec, AC97_REG_RESET);
471
472	if (!codec->noext) {
473		wrcd(codec, AC97_REGEXT_STAT, codec->extstat);
474		if (rdcd(codec, AC97_REGEXT_STAT) != codec->extstat)
475			device_printf(codec->dev, "ac97 codec failed to reset extended mode (%x, got %x)\n",
476				      codec->extstat, rdcd(codec, AC97_REGEXT_STAT));
477	}
478
479	if ((rdcd(codec, AC97_REG_POWER) & 2) == 0)
480		device_printf(codec->dev, "ac97 codec reports dac not ready\n");
481	snd_mtxunlock(codec->lock);
482	return 0;
483}
484
485struct ac97_info *
486ac97_create(device_t dev, void *devinfo, kobj_class_t cls)
487{
488	struct ac97_info *codec;
489
490	codec = (struct ac97_info *)malloc(sizeof *codec, M_AC97, M_NOWAIT);
491	if (codec == NULL)
492		return NULL;
493
494	snprintf(codec->name, AC97_NAMELEN, "%s:ac97", device_get_nameunit(dev));
495	codec->lock = snd_mtxcreate(codec->name);
496	codec->methods = kobj_create(cls, M_AC97, M_WAITOK);
497	if (codec->methods == NULL) {
498		snd_mtxlock(codec->lock);
499		snd_mtxfree(codec->lock);
500		free(codec, M_AC97);
501		return NULL;
502	}
503
504	codec->dev = dev;
505	codec->devinfo = devinfo;
506	codec->flags = 0;
507	return codec;
508}
509
510void
511ac97_destroy(struct ac97_info *codec)
512{
513	snd_mtxlock(codec->lock);
514	if (codec->methods != NULL)
515		kobj_delete(codec->methods, M_AC97);
516	snd_mtxfree(codec->lock);
517	free(codec, M_AC97);
518}
519
520void
521ac97_setflags(struct ac97_info *codec, u_int32_t val)
522{
523	codec->flags = val;
524}
525
526u_int32_t
527ac97_getflags(struct ac97_info *codec)
528{
529	return codec->flags;
530}
531
532/* -------------------------------------------------------------------- */
533
534static int
535ac97mix_init(struct snd_mixer *m)
536{
537	struct ac97_info *codec = mix_getdevinfo(m);
538	u_int32_t i, mask;
539
540	if (codec == NULL)
541		return -1;
542
543	if (ac97_initmixer(codec))
544		return -1;
545
546	mask = 0;
547	for (i = 0; i < 32; i++)
548		mask |= codec->mix[i].enable? 1 << i : 0;
549	mix_setdevs(m, mask);
550
551	mask = 0;
552	for (i = 0; i < 32; i++)
553		mask |= codec->mix[i].recidx? 1 << i : 0;
554	mix_setrecdevs(m, mask);
555	return 0;
556}
557
558static int
559ac97mix_uninit(struct snd_mixer *m)
560{
561	struct ac97_info *codec = mix_getdevinfo(m);
562
563	if (codec == NULL)
564		return -1;
565	/*
566	if (ac97_uninitmixer(codec))
567		return -1;
568	*/
569	ac97_destroy(codec);
570	return 0;
571}
572
573static int
574ac97mix_reinit(struct snd_mixer *m)
575{
576	struct ac97_info *codec = mix_getdevinfo(m);
577
578	if (codec == NULL)
579		return -1;
580	return ac97_reinitmixer(codec);
581}
582
583static int
584ac97mix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right)
585{
586	struct ac97_info *codec = mix_getdevinfo(m);
587
588	if (codec == NULL)
589		return -1;
590	return ac97_setmixer(codec, dev, left, right);
591}
592
593static int
594ac97mix_setrecsrc(struct snd_mixer *m, u_int32_t src)
595{
596	int i;
597	struct ac97_info *codec = mix_getdevinfo(m);
598
599	if (codec == NULL)
600		return -1;
601	for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
602		if ((src & (1 << i)) != 0)
603			break;
604	return (ac97_setrecsrc(codec, i) == 0)? 1 << i : -1;
605}
606
607static kobj_method_t ac97mixer_methods[] = {
608    	KOBJMETHOD(mixer_init,		ac97mix_init),
609    	KOBJMETHOD(mixer_uninit,	ac97mix_uninit),
610    	KOBJMETHOD(mixer_reinit,	ac97mix_reinit),
611    	KOBJMETHOD(mixer_set,		ac97mix_set),
612    	KOBJMETHOD(mixer_setrecsrc,	ac97mix_setrecsrc),
613	{ 0, 0 }
614};
615MIXER_DECLARE(ac97mixer);
616
617/* -------------------------------------------------------------------- */
618
619kobj_class_t
620ac97_getmixerclass(void)
621{
622	return &ac97mixer_class;
623}
624
625
626