xref: /freebsd/sys/dev/sound/pcm/ac97.c (revision 987e59726a445bc92ba45938a71e905de1696bd5)
1987e5972SCameron Grant /*
2987e5972SCameron Grant  * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk>
3987e5972SCameron Grant  * All rights reserved.
4987e5972SCameron Grant  *
5987e5972SCameron Grant  * Redistribution and use in source and binary forms, with or without
6987e5972SCameron Grant  * modification, are permitted provided that the following conditions
7987e5972SCameron Grant  * are met:
8987e5972SCameron Grant  * 1. Redistributions of source code must retain the above copyright
9987e5972SCameron Grant  *    notice, this list of conditions and the following disclaimer.
10987e5972SCameron Grant  * 2. Redistributions in binary form must reproduce the above copyright
11987e5972SCameron Grant  *    notice, this list of conditions and the following disclaimer in the
12987e5972SCameron Grant  *    documentation and/or other materials provided with the distribution.
13987e5972SCameron Grant  *
14987e5972SCameron Grant  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15987e5972SCameron Grant  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16987e5972SCameron Grant  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17987e5972SCameron Grant  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18987e5972SCameron Grant  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19987e5972SCameron Grant  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20987e5972SCameron Grant  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21987e5972SCameron Grant  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22987e5972SCameron Grant  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23987e5972SCameron Grant  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24987e5972SCameron Grant  * SUCH DAMAGE.
25987e5972SCameron Grant  *
26987e5972SCameron Grant  * $Id$
27987e5972SCameron Grant  */
28987e5972SCameron Grant 
29987e5972SCameron Grant #include <dev/pcm/sound.h>
30987e5972SCameron Grant #include <dev/pcm/ac97.h>
31987e5972SCameron Grant 
32987e5972SCameron Grant #define AC97_MUTE	0x8000
33987e5972SCameron Grant 
34987e5972SCameron Grant #define AC97_REG_RESET	0x00
35987e5972SCameron Grant #define AC97_MIX_MASTER	0x02
36987e5972SCameron Grant #define AC97_MIX_PHONES	0x04
37987e5972SCameron Grant #define AC97_MIX_MONO 	0x06
38987e5972SCameron Grant #define AC97_MIX_TONE	0x08
39987e5972SCameron Grant #define AC97_MIX_BEEP	0x0a
40987e5972SCameron Grant #define AC97_MIX_PHONE	0x0c
41987e5972SCameron Grant #define AC97_MIX_MIC	0x0e
42987e5972SCameron Grant #define AC97_MIX_LINE	0x10
43987e5972SCameron Grant #define AC97_MIX_CD	0x12
44987e5972SCameron Grant #define AC97_MIX_VIDEO	0x14
45987e5972SCameron Grant #define AC97_MIX_AUX	0x16
46987e5972SCameron Grant #define AC97_MIX_PCM	0x18
47987e5972SCameron Grant #define AC97_REG_RECSEL	0x1a
48987e5972SCameron Grant #define AC97_MIX_RGAIN	0x1c
49987e5972SCameron Grant #define AC97_MIX_MGAIN	0x1e
50987e5972SCameron Grant #define AC97_REG_GEN	0x20
51987e5972SCameron Grant #define AC97_REG_3D	0x22
52987e5972SCameron Grant #define AC97_REG_POWER	0x26
53987e5972SCameron Grant #define AC97_REG_ID1	0x7c
54987e5972SCameron Grant #define AC97_REG_ID2	0x7e
55987e5972SCameron Grant 
56987e5972SCameron Grant struct ac97mixtable_entry {
57987e5972SCameron Grant 	int		reg:8;
58987e5972SCameron Grant 	unsigned	bits:4;
59987e5972SCameron Grant 	unsigned	ofs:4;
60987e5972SCameron Grant 	unsigned	stereo:1;
61987e5972SCameron Grant 	unsigned	mute:1;
62987e5972SCameron Grant 	unsigned	recidx:4;
63987e5972SCameron Grant 	unsigned        mask:1;
64987e5972SCameron Grant };
65987e5972SCameron Grant 
66987e5972SCameron Grant struct ac97_info {
67987e5972SCameron Grant 	ac97_read *read;
68987e5972SCameron Grant 	ac97_write *write;
69987e5972SCameron Grant 	void *devinfo;
70987e5972SCameron Grant 	char id[4];
71987e5972SCameron Grant 	char rev;
72987e5972SCameron Grant 	unsigned caps, se;
73987e5972SCameron Grant 	struct ac97mixtable_entry mix[32];
74987e5972SCameron Grant };
75987e5972SCameron Grant 
76987e5972SCameron Grant struct ac97_codecid {
77987e5972SCameron Grant 	u_int32_t id;
78987e5972SCameron Grant 	char *name;
79987e5972SCameron Grant };
80987e5972SCameron Grant 
81987e5972SCameron Grant static const struct ac97mixtable_entry ac97mixtable_default[32] = {
82987e5972SCameron Grant 	[SOUND_MIXER_VOLUME]	= { AC97_MIX_MASTER, 	5, 0, 1, 1, 6, 0 },
83987e5972SCameron Grant 	[SOUND_MIXER_BASS]	= { AC97_MIX_TONE, 	4, 8, 0, 0, 0, 1 },
84987e5972SCameron Grant 	[SOUND_MIXER_TREBLE]	= { AC97_MIX_TONE, 	4, 0, 0, 0, 0, 1 },
85987e5972SCameron Grant 	[SOUND_MIXER_PCM]	= { AC97_MIX_PCM, 	5, 0, 1, 1, 0, 0 },
86987e5972SCameron Grant 	[SOUND_MIXER_SPEAKER]	= { AC97_MIX_BEEP, 	4, 1, 0, 1, 0, 0 },
87987e5972SCameron Grant 	[SOUND_MIXER_LINE]	= { AC97_MIX_LINE, 	5, 0, 1, 1, 5, 0 },
88987e5972SCameron Grant 	[SOUND_MIXER_MIC] 	= { AC97_MIX_MIC, 	5, 0, 0, 1, 1, 0 },
89987e5972SCameron Grant 	[SOUND_MIXER_CD]	= { AC97_MIX_CD, 	5, 0, 1, 1, 2, 0 },
90987e5972SCameron Grant 	[SOUND_MIXER_LINE1]	= { AC97_MIX_AUX, 	5, 0, 1, 1, 4, 0 },
91987e5972SCameron Grant 	[SOUND_MIXER_VIDEO]	= { AC97_MIX_VIDEO, 	5, 0, 1, 1, 3, 0 },
92987e5972SCameron Grant 	[SOUND_MIXER_RECLEV]	= { -AC97_MIX_RGAIN, 	4, 0, 1, 1, 0, 0 }
93987e5972SCameron Grant };
94987e5972SCameron Grant 
95987e5972SCameron Grant static const unsigned ac97mixdevs =
96987e5972SCameron Grant 	SOUND_MASK_VOLUME |
97987e5972SCameron Grant 	SOUND_MASK_PCM | SOUND_MASK_SPEAKER | SOUND_MASK_LINE |
98987e5972SCameron Grant 	SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_LINE1 |
99987e5972SCameron Grant 	SOUND_MASK_VIDEO | SOUND_MASK_RECLEV;
100987e5972SCameron Grant 
101987e5972SCameron Grant static const unsigned ac97recdevs =
102987e5972SCameron Grant 	SOUND_MASK_VOLUME | SOUND_MASK_LINE | SOUND_MASK_MIC |
103987e5972SCameron Grant 	SOUND_MASK_CD | SOUND_MASK_LINE1 | SOUND_MASK_VIDEO;
104987e5972SCameron Grant 
105987e5972SCameron Grant static struct ac97_codecid ac97codecid[] = {
106987e5972SCameron Grant 	{ 0x414B4D00, "Asahi Kasei AK4540" 	},
107987e5972SCameron Grant 	{ 0x43525900, "Cirrus Logic CS4297" 	},
108987e5972SCameron Grant 	{ 0x83847600, "SigmaTel STAC????" 	},
109987e5972SCameron Grant 	{ 0x83847604, "SigmaTel STAC9701/3/4/5" },
110987e5972SCameron Grant 	{ 0x83847605, "SigmaTel STAC9704" 	},
111987e5972SCameron Grant 	{ 0x83847608, "SigmaTel STAC9708" 	},
112987e5972SCameron Grant 	{ 0x83847609, "SigmaTel STAC9721" 	},
113987e5972SCameron Grant 	{ 0, 	      NULL			}
114987e5972SCameron Grant };
115987e5972SCameron Grant 
116987e5972SCameron Grant static char *ac97enhancement[] = {
117987e5972SCameron Grant 	"",
118987e5972SCameron Grant 	"Analog Devices Phat Stereo",
119987e5972SCameron Grant 	"Creative Stereo Enhancement",
120987e5972SCameron Grant 	"National Semi 3D Stereo Enhancement",
121987e5972SCameron Grant 	"Yamaha Ymersion",
122987e5972SCameron Grant 	"BBE 3D Stereo Enhancement",
123987e5972SCameron Grant 	"Crystal Semi 3D Stereo Enhancement",
124987e5972SCameron Grant 	"Qsound QXpander",
125987e5972SCameron Grant 	"Spatializer 3D Stereo Enhancement",
126987e5972SCameron Grant 	"SRS 3D Stereo Enhancement",
127987e5972SCameron Grant 	"Platform Tech 3D Stereo Enhancement",
128987e5972SCameron Grant 	"AKM 3D Audio",
129987e5972SCameron Grant 	"Aureal Stereo Enhancement",
130987e5972SCameron Grant 	"Aztech 3D Enhancement",
131987e5972SCameron Grant 	"Binaura 3D Audio Enhancement",
132987e5972SCameron Grant 	"ESS Technology Stereo Enhancement",
133987e5972SCameron Grant 	"Harman International VMAx",
134987e5972SCameron Grant 	"Nvidea 3D Stereo Enhancement",
135987e5972SCameron Grant 	"Philips Incredible Sound",
136987e5972SCameron Grant 	"Texas Instruments 3D Stereo Enhancement",
137987e5972SCameron Grant 	"VLSI Technology 3D Stereo Enhancement",
138987e5972SCameron Grant 	"TriTech 3D Stereo Enhancement",
139987e5972SCameron Grant 	"Realtek 3D Stereo Enhancement",
140987e5972SCameron Grant 	"Samsung 3D Stereo Enhancement",
141987e5972SCameron Grant 	"Wolfson Microelectronics 3D Enhancement",
142987e5972SCameron Grant 	"Delta Integration 3D Enhancement",
143987e5972SCameron Grant 	"SigmaTel 3D Enhancement",
144987e5972SCameron Grant 	"Reserved 27",
145987e5972SCameron Grant 	"Rockwell 3D Stereo Enhancement",
146987e5972SCameron Grant 	"Reserved 29",
147987e5972SCameron Grant 	"Reserved 30",
148987e5972SCameron Grant 	"Reserved 31"
149987e5972SCameron Grant };
150987e5972SCameron Grant 
151987e5972SCameron Grant static char *ac97feature[] = {
152987e5972SCameron Grant 	"mic channel",
153987e5972SCameron Grant 	"reserved",
154987e5972SCameron Grant 	"tone",
155987e5972SCameron Grant 	"simulated stereo",
156987e5972SCameron Grant 	"headphone",
157987e5972SCameron Grant 	"bass boost",
158987e5972SCameron Grant 	"18 bit DAC",
159987e5972SCameron Grant 	"20 bit DAC",
160987e5972SCameron Grant 	"18 bit ADC",
161987e5972SCameron Grant 	"20 bit ADC"
162987e5972SCameron Grant };
163987e5972SCameron Grant 
164987e5972SCameron Grant static int
165987e5972SCameron Grant ac97_setrecsrc(struct ac97_info *codec, int channel)
166987e5972SCameron Grant {
167987e5972SCameron Grant 	struct ac97mixtable_entry *e = &codec->mix[channel];
168987e5972SCameron Grant 	if (e->recidx > 0) {
169987e5972SCameron Grant 		int val = e->recidx - 1;
170987e5972SCameron Grant 		val |= val << 8;
171987e5972SCameron Grant 		codec->write(codec->devinfo, AC97_REG_RECSEL, val);
172987e5972SCameron Grant 		return 0;
173987e5972SCameron Grant 	} else return -1;
174987e5972SCameron Grant }
175987e5972SCameron Grant 
176987e5972SCameron Grant static int
177987e5972SCameron Grant ac97_setmixer(struct ac97_info *codec, unsigned channel, unsigned left, unsigned right)
178987e5972SCameron Grant {
179987e5972SCameron Grant 	struct ac97mixtable_entry *e = &codec->mix[channel];
180987e5972SCameron Grant 	if (e->reg != 0) {
181987e5972SCameron Grant 		int max, val;
182987e5972SCameron Grant 
183987e5972SCameron Grant 		if (!e->stereo) right = left;
184987e5972SCameron Grant 		if (e->reg > 0) {
185987e5972SCameron Grant 			left = 100 - left;
186987e5972SCameron Grant 			right = 100 - right;
187987e5972SCameron Grant 		}
188987e5972SCameron Grant 
189987e5972SCameron Grant 		max = (1 << e->bits) - 1;
190987e5972SCameron Grant 		left = (left * max) / 100;
191987e5972SCameron Grant 		right = (right * max) / 100;
192987e5972SCameron Grant 
193987e5972SCameron Grant 		val = (left << 8) | right;
194987e5972SCameron Grant 
195987e5972SCameron Grant 		left = (left * 100) / max;
196987e5972SCameron Grant 		right = (right * 100) / max;
197987e5972SCameron Grant 
198987e5972SCameron Grant 		if (e->reg > 0) {
199987e5972SCameron Grant 			left = 100 - left;
200987e5972SCameron Grant 			right = 100 - right;
201987e5972SCameron Grant 		}
202987e5972SCameron Grant 
203987e5972SCameron Grant 		if (!e->stereo) {
204987e5972SCameron Grant 			val &= max;
205987e5972SCameron Grant 			val <<= e->ofs;
206987e5972SCameron Grant 			if (e->mask) {
207987e5972SCameron Grant 				int cur = codec->read(codec->devinfo, e->reg);
208987e5972SCameron Grant 				val |= cur & ~(max << e->ofs);
209987e5972SCameron Grant 			}
210987e5972SCameron Grant 		}
211987e5972SCameron Grant 		if (left == 0 && right == 0 && e->mute == 1) val = AC97_MUTE;
212987e5972SCameron Grant 		codec->write(codec->devinfo, abs(e->reg), val);
213987e5972SCameron Grant 		return left | (right << 8);
214987e5972SCameron Grant 	} else return -1;
215987e5972SCameron Grant }
216987e5972SCameron Grant 
217987e5972SCameron Grant #if 0
218987e5972SCameron Grant static int
219987e5972SCameron Grant ac97_getmixer(struct ac97_info *codec, int channel)
220987e5972SCameron Grant {
221987e5972SCameron Grant 	struct ac97mixtable_entry *e = &codec->mix[channel];
222987e5972SCameron Grant 	if (channel < SOUND_MIXER_NRDEVICES && e->reg != 0) {
223987e5972SCameron Grant 		int max, val, volume;
224987e5972SCameron Grant 
225987e5972SCameron Grant 		max = (1 << e->bits) - 1;
226987e5972SCameron Grant 		val = codec->read(codec->devinfo, e->reg);
227987e5972SCameron Grant 		if (val == AC97_MUTE && e->mute == 1) volume = 0;
228987e5972SCameron Grant 		else {
229987e5972SCameron Grant 			if (e->stereo == 0) val >>= e->ofs;
230987e5972SCameron Grant 			val &= max;
231987e5972SCameron Grant 			volume = (val * 100) / max;
232987e5972SCameron Grant 			if (e->reg > 0) volume = 100 - volume;
233987e5972SCameron Grant 		}
234987e5972SCameron Grant 		return volume;
235987e5972SCameron Grant 	} else return -1;
236987e5972SCameron Grant }
237987e5972SCameron Grant #endif
238987e5972SCameron Grant 
239987e5972SCameron Grant static unsigned
240987e5972SCameron Grant ac97_init(struct ac97_info *codec)
241987e5972SCameron Grant {
242987e5972SCameron Grant 	unsigned i, j;
243987e5972SCameron Grant 	u_int32_t id;
244987e5972SCameron Grant 
245987e5972SCameron Grant 	for (i = 0; i < 32; i++) codec->mix[i] = ac97mixtable_default[i];
246987e5972SCameron Grant 
247987e5972SCameron Grant 	codec->write(codec->devinfo, AC97_REG_POWER, 0);
248987e5972SCameron Grant 	codec->write(codec->devinfo, AC97_REG_RESET, 0);
249987e5972SCameron Grant 	DELAY(10000);
250987e5972SCameron Grant 
251987e5972SCameron Grant 	i = codec->read(codec->devinfo, AC97_REG_RESET);
252987e5972SCameron Grant 	codec->caps = i & 0x03ff;
253987e5972SCameron Grant 	codec->se =  (i & 0x7c00) >> 10;
254987e5972SCameron Grant 
255987e5972SCameron Grant 	id = (codec->read(codec->devinfo, AC97_REG_ID1) << 16) |
256987e5972SCameron Grant 	      codec->read(codec->devinfo, AC97_REG_ID2);
257987e5972SCameron Grant 	codec->rev = id & 0x000000ff;
258987e5972SCameron Grant 
259987e5972SCameron Grant 	codec->write(codec->devinfo, AC97_MIX_MASTER, 0x20);
260987e5972SCameron Grant 	if ((codec->read(codec->devinfo, AC97_MIX_MASTER) & 0x20) == 0x20)
261987e5972SCameron Grant 		codec->mix[SOUND_MIXER_VOLUME].bits++;
262987e5972SCameron Grant 	codec->write(codec->devinfo, AC97_MIX_MASTER, 0x00);
263987e5972SCameron Grant 
264987e5972SCameron Grant 	if (bootverbose) {
265987e5972SCameron Grant 		printf("ac97: codec id 0x%8x", id);
266987e5972SCameron Grant 		for (i = 0; ac97codecid[i].id; i++) {
267987e5972SCameron Grant 			if (ac97codecid[i].id == id) printf(" (%s)", ac97codecid[i].name);
268987e5972SCameron Grant 		}
269987e5972SCameron Grant 		printf("\nac97: codec features ");
270987e5972SCameron Grant 		for (i = j = 0; i < 10; i++) {
271987e5972SCameron Grant 			if (codec->caps & (1 << i)) {
272987e5972SCameron Grant 				printf("%s%s", j? ", " : "", ac97feature[i]);
273987e5972SCameron Grant 				j++;
274987e5972SCameron Grant 			}
275987e5972SCameron Grant 		}
276987e5972SCameron Grant 		printf("%s%d bit master volume", j? ", " : "", codec->mix[SOUND_MIXER_VOLUME].bits);
277987e5972SCameron Grant 		printf("%s%s\n", j? ", " : "", ac97enhancement[codec->se]);
278987e5972SCameron Grant 	}
279987e5972SCameron Grant 
280987e5972SCameron Grant 	if ((codec->read(codec->devinfo, AC97_REG_POWER) & 2) == 0)
281987e5972SCameron Grant 		printf("ac97: dac not ready\n");
282987e5972SCameron Grant 	return 0;
283987e5972SCameron Grant }
284987e5972SCameron Grant 
285987e5972SCameron Grant struct ac97_info *
286987e5972SCameron Grant ac97_create(void *devinfo, ac97_read *rd, ac97_write *wr)
287987e5972SCameron Grant {
288987e5972SCameron Grant 	struct ac97_info *codec;
289987e5972SCameron Grant 
290987e5972SCameron Grant 	codec = (struct ac97_info *)malloc(sizeof *codec, M_DEVBUF, M_NOWAIT);
291987e5972SCameron Grant 	if (codec != NULL) {
292987e5972SCameron Grant 		codec->read = rd;
293987e5972SCameron Grant 		codec->write = wr;
294987e5972SCameron Grant 		codec->devinfo = devinfo;
295987e5972SCameron Grant 	}
296987e5972SCameron Grant 	return codec;
297987e5972SCameron Grant }
298987e5972SCameron Grant 
299987e5972SCameron Grant static int
300987e5972SCameron Grant ac97mix_init(snd_mixer *m)
301987e5972SCameron Grant {
302987e5972SCameron Grant 	struct ac97_info *codec = mix_getdevinfo(m);
303987e5972SCameron Grant 	if (codec == NULL) return -1;
304987e5972SCameron Grant 	ac97_init(codec);
305987e5972SCameron Grant 	mix_setdevs(m, ac97mixdevs | ((codec->caps & 4)? SOUND_MASK_BASS | SOUND_MASK_TREBLE : 0));
306987e5972SCameron Grant 	mix_setrecdevs(m, ac97recdevs);
307987e5972SCameron Grant 	return 0;
308987e5972SCameron Grant }
309987e5972SCameron Grant 
310987e5972SCameron Grant static int
311987e5972SCameron Grant ac97mix_set(snd_mixer *m, unsigned dev, unsigned left, unsigned right)
312987e5972SCameron Grant {
313987e5972SCameron Grant 	struct ac97_info *codec = mix_getdevinfo(m);
314987e5972SCameron Grant 	if (codec == NULL) return -1;
315987e5972SCameron Grant 	return ac97_setmixer(codec, dev, left, right);
316987e5972SCameron Grant }
317987e5972SCameron Grant 
318987e5972SCameron Grant static int
319987e5972SCameron Grant ac97mix_setrecsrc(snd_mixer *m, u_int32_t src)
320987e5972SCameron Grant {
321987e5972SCameron Grant 	int i;
322987e5972SCameron Grant 	struct ac97_info *codec = mix_getdevinfo(m);
323987e5972SCameron Grant 	if (codec == NULL) return -1;
324987e5972SCameron Grant 	for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
325987e5972SCameron Grant 		if ((src & (1 << i)) != 0) break;
326987e5972SCameron Grant 	return (ac97_setrecsrc(codec, i) == 0)? 1 << i : -1;
327987e5972SCameron Grant }
328987e5972SCameron Grant 
329987e5972SCameron Grant snd_mixer ac97_mixer = {
330987e5972SCameron Grant 	"AC97 mixer",
331987e5972SCameron Grant 	ac97mix_init,
332987e5972SCameron Grant 	ac97mix_set,
333987e5972SCameron Grant 	ac97mix_setrecsrc,
334987e5972SCameron Grant };
335987e5972SCameron Grant 
336