1 |
1626 |
jcastillo |
/*
|
2 |
|
|
* Audio Command Interface (ACI) driver (sound/aci.c)
|
3 |
|
|
*
|
4 |
|
|
* ACI is a protocol used to communicate with the microcontroller on
|
5 |
|
|
* some sound cards produced by miro, e.g. the miroSOUND PCM12 and
|
6 |
|
|
* PCM20. The ACI has been developed for miro by Norberto Pellicci
|
7 |
|
|
* <pellicci@ix.netcom.com>. Special thanks to both him and miro for
|
8 |
|
|
* providing the ACI specification.
|
9 |
|
|
*
|
10 |
|
|
* The main function of the ACI is to control the mixer and to get a
|
11 |
|
|
* product identification. On the PCM20, ACI also controls the radio
|
12 |
|
|
* tuner on this card, however this is not yet supported in this
|
13 |
|
|
* software.
|
14 |
|
|
*
|
15 |
|
|
* This Voxware ACI driver currently only supports the ACI functions
|
16 |
|
|
* on the miroSOUND PCM12 card. Support for miro soundcards with
|
17 |
|
|
* additional ACI functions can easily be added later.
|
18 |
|
|
*
|
19 |
|
|
* Revision history:
|
20 |
|
|
*
|
21 |
|
|
* 1995-11-10 Markus Kuhn <mskuhn@cip.informatik.uni-erlangen.de>
|
22 |
|
|
* First version written.
|
23 |
|
|
* 1995-12-31 Markus Kuhn
|
24 |
|
|
* Second revision, general code cleanup.
|
25 |
|
|
* 1996-05-16 Hannu Savolainen
|
26 |
|
|
* Integrated with other parts of the driver.
|
27 |
|
|
* 1996-05-28 Markus Kuhn
|
28 |
|
|
* Initialize CS4231A mixer, make ACI first mixer,
|
29 |
|
|
* use new private mixer API for solo mode.
|
30 |
|
|
*/
|
31 |
|
|
|
32 |
|
|
/*
|
33 |
|
|
* Some driver specific information and features:
|
34 |
|
|
*
|
35 |
|
|
* This mixer driver identifies itself to applications as "ACI" in
|
36 |
|
|
* mixer_info.id as retrieved by ioctl(fd, SOUND_MIXER_INFO, &mixer_info).
|
37 |
|
|
*
|
38 |
|
|
* Proprietary mixer features that go beyond the standard USS mixer
|
39 |
|
|
* interface are:
|
40 |
|
|
*
|
41 |
|
|
* Full duplex solo configuration:
|
42 |
|
|
*
|
43 |
|
|
* int solo_mode;
|
44 |
|
|
* ioctl(fd, SOUND_MIXER_PRIVATE1, &solo_mode);
|
45 |
|
|
*
|
46 |
|
|
* solo_mode = 0: deactivate solo mode (default)
|
47 |
|
|
* solo_mode > 0: activate solo mode
|
48 |
|
|
* With activated solo mode, the PCM input can not any
|
49 |
|
|
* longer hear the signals produced by the PCM output.
|
50 |
|
|
* Activating solo mode is important in duplex mode in order
|
51 |
|
|
* to avoid feedback distortions.
|
52 |
|
|
* solo_mode < 0: do not change solo mode (just retrieve the status)
|
53 |
|
|
*
|
54 |
|
|
* When the ioctl() returns 0, solo_mode contains the previous
|
55 |
|
|
* status (0 = deactivated, 1 = activated). If solo mode is not
|
56 |
|
|
* implemented on this card, ioctl() returns -1 and sets errno to
|
57 |
|
|
* EINVAL.
|
58 |
|
|
*
|
59 |
|
|
*/
|
60 |
|
|
|
61 |
|
|
#include <linux/config.h> /* CONFIG_ACI_MIXER */
|
62 |
|
|
#include "../sound_config.h"
|
63 |
|
|
#ifdef CONFIG_ACI_MIXER
|
64 |
|
|
|
65 |
|
|
#undef DEBUG /* if defined, produce a verbose report via syslog */
|
66 |
|
|
|
67 |
|
|
int aci_port = 0x354; /* as determined by bit 4 in the OPTi 929 MC4 register */
|
68 |
|
|
unsigned char aci_idcode[2] = {0, 0}; /* manufacturer and product ID */
|
69 |
|
|
unsigned char aci_version = 0; /* ACI firmware version */
|
70 |
|
|
int aci_solo; /* status bit of the card that can't be *
|
71 |
|
|
* checked with ACI versions prior to 0xb0 */
|
72 |
|
|
|
73 |
|
|
static int aci_present = 0;
|
74 |
|
|
|
75 |
|
|
#define COMMAND_REGISTER (aci_port)
|
76 |
|
|
#define STATUS_REGISTER (aci_port + 1)
|
77 |
|
|
#define BUSY_REGISTER (aci_port + 2)
|
78 |
|
|
|
79 |
|
|
/*
|
80 |
|
|
* Wait until the ACI microcontroller has set the READYFLAG in the
|
81 |
|
|
* Busy/IRQ Source Register to 0. This is required to avoid
|
82 |
|
|
* overrunning the soundcard microcontroller. We do a busy wait here,
|
83 |
|
|
* because the microcontroller is not supposed to signal a busy
|
84 |
|
|
* condition for more than a few clock cycles. In case of a time-out,
|
85 |
|
|
* this function returns -1.
|
86 |
|
|
*
|
87 |
|
|
* This busy wait code normally requires less than 15 loops and
|
88 |
|
|
* practically always less than 100 loops on my i486/DX2 66 MHz.
|
89 |
|
|
*
|
90 |
|
|
* Warning: Waiting on the general status flag after reseting the MUTE
|
91 |
|
|
* function can take a VERY long time, because the PCM12 does some kind
|
92 |
|
|
* of fade-in effect. For this reason, access to the MUTE function has
|
93 |
|
|
* not been implemented at all.
|
94 |
|
|
*/
|
95 |
|
|
|
96 |
|
|
static int busy_wait(void)
|
97 |
|
|
{
|
98 |
|
|
long timeout;
|
99 |
|
|
|
100 |
|
|
for (timeout = 0; timeout < 10000000L; timeout++)
|
101 |
|
|
if ((inb_p(BUSY_REGISTER) & 1) == 0)
|
102 |
|
|
return 0;
|
103 |
|
|
|
104 |
|
|
#ifdef DEBUG
|
105 |
|
|
printk("ACI: READYFLAG timed out.\n");
|
106 |
|
|
#endif
|
107 |
|
|
|
108 |
|
|
return -1;
|
109 |
|
|
}
|
110 |
|
|
|
111 |
|
|
|
112 |
|
|
/*
|
113 |
|
|
* Read the GENERAL STATUS register.
|
114 |
|
|
*/
|
115 |
|
|
|
116 |
|
|
static int read_general_status(void)
|
117 |
|
|
{
|
118 |
|
|
unsigned long flags;
|
119 |
|
|
int status;
|
120 |
|
|
|
121 |
|
|
save_flags(flags);
|
122 |
|
|
cli();
|
123 |
|
|
if (busy_wait()) { restore_flags(flags); return -1; }
|
124 |
|
|
status = (unsigned) inb_p(STATUS_REGISTER);
|
125 |
|
|
restore_flags(flags);
|
126 |
|
|
return status;
|
127 |
|
|
}
|
128 |
|
|
|
129 |
|
|
|
130 |
|
|
/*
|
131 |
|
|
* The four ACI command types (implied, write, read and indexed) can
|
132 |
|
|
* be sent to the microcontroller using the following four functions.
|
133 |
|
|
* If a problem occurred, they return -1.
|
134 |
|
|
*/
|
135 |
|
|
|
136 |
|
|
static int implied_cmd(unsigned char opcode)
|
137 |
|
|
{
|
138 |
|
|
unsigned long flags;
|
139 |
|
|
|
140 |
|
|
#ifdef DEBUG
|
141 |
|
|
printk("ACI: implied_cmd(0x%02x)\n", opcode);
|
142 |
|
|
#endif
|
143 |
|
|
|
144 |
|
|
save_flags(flags);
|
145 |
|
|
cli();
|
146 |
|
|
|
147 |
|
|
if (read_general_status() < 0 || busy_wait()) {
|
148 |
|
|
restore_flags(flags);
|
149 |
|
|
return -1;
|
150 |
|
|
}
|
151 |
|
|
outb_p(opcode, COMMAND_REGISTER);
|
152 |
|
|
|
153 |
|
|
restore_flags(flags);
|
154 |
|
|
return 0;
|
155 |
|
|
}
|
156 |
|
|
|
157 |
|
|
|
158 |
|
|
static int write_cmd(unsigned char opcode, unsigned char parameter)
|
159 |
|
|
{
|
160 |
|
|
unsigned long flags;
|
161 |
|
|
int status;
|
162 |
|
|
|
163 |
|
|
#ifdef DEBUG
|
164 |
|
|
printk("ACI: write_cmd(0x%02x, 0x%02x)\n", opcode, parameter);
|
165 |
|
|
#endif
|
166 |
|
|
|
167 |
|
|
save_flags(flags);
|
168 |
|
|
cli();
|
169 |
|
|
|
170 |
|
|
if (read_general_status() < 0 || busy_wait()) {
|
171 |
|
|
restore_flags(flags);
|
172 |
|
|
return -1;
|
173 |
|
|
}
|
174 |
|
|
outb_p(opcode, COMMAND_REGISTER);
|
175 |
|
|
if (busy_wait()) { restore_flags(flags); return -1; }
|
176 |
|
|
outb_p(parameter, COMMAND_REGISTER);
|
177 |
|
|
|
178 |
|
|
if ((status = read_general_status()) < 0) {
|
179 |
|
|
restore_flags(flags);
|
180 |
|
|
return -1;
|
181 |
|
|
}
|
182 |
|
|
/* polarity of the INVALID flag depends on ACI version */
|
183 |
|
|
if ((aci_version < 0xb0 && (status & 0x40) != 0) ||
|
184 |
|
|
(aci_version >= 0xb0 && (status & 0x40) == 0)) {
|
185 |
|
|
restore_flags(flags);
|
186 |
|
|
printk("ACI: invalid write command 0x%02x, 0x%02x.\n",
|
187 |
|
|
opcode, parameter);
|
188 |
|
|
return -1;
|
189 |
|
|
}
|
190 |
|
|
|
191 |
|
|
restore_flags(flags);
|
192 |
|
|
return 0;
|
193 |
|
|
}
|
194 |
|
|
|
195 |
|
|
|
196 |
|
|
static int read_cmd(unsigned char opcode, int length, unsigned char *parameter)
|
197 |
|
|
{
|
198 |
|
|
unsigned long flags;
|
199 |
|
|
int i = 0;
|
200 |
|
|
|
201 |
|
|
save_flags(flags);
|
202 |
|
|
cli();
|
203 |
|
|
|
204 |
|
|
if (read_general_status() < 0) { restore_flags(flags); return -1; }
|
205 |
|
|
while (i < length) {
|
206 |
|
|
if (busy_wait()) { restore_flags(flags); return -1; }
|
207 |
|
|
outb_p(opcode, COMMAND_REGISTER);
|
208 |
|
|
if (busy_wait()) { restore_flags(flags); return -1; }
|
209 |
|
|
parameter[i++] = inb_p(STATUS_REGISTER);
|
210 |
|
|
#ifdef DEBUG
|
211 |
|
|
if (i == 1)
|
212 |
|
|
printk("ACI: read_cmd(0x%02x, %d) = 0x%02x\n", opcode, length,
|
213 |
|
|
parameter[i-1]);
|
214 |
|
|
else
|
215 |
|
|
printk("ACI: read_cmd cont.: 0x%02x\n", parameter[i-1]);
|
216 |
|
|
#endif
|
217 |
|
|
}
|
218 |
|
|
|
219 |
|
|
restore_flags(flags);
|
220 |
|
|
return 0;
|
221 |
|
|
}
|
222 |
|
|
|
223 |
|
|
|
224 |
|
|
static int indexed_cmd(unsigned char opcode, unsigned char index,
|
225 |
|
|
unsigned char *parameter)
|
226 |
|
|
{
|
227 |
|
|
unsigned long flags;
|
228 |
|
|
|
229 |
|
|
save_flags(flags);
|
230 |
|
|
cli();
|
231 |
|
|
|
232 |
|
|
if (read_general_status() < 0 || busy_wait()) {
|
233 |
|
|
restore_flags(flags);
|
234 |
|
|
return -1;
|
235 |
|
|
}
|
236 |
|
|
outb_p(opcode, COMMAND_REGISTER);
|
237 |
|
|
if (busy_wait()) { restore_flags(flags); return -1; }
|
238 |
|
|
outb_p(index, COMMAND_REGISTER);
|
239 |
|
|
if (busy_wait()) { restore_flags(flags); return -1; }
|
240 |
|
|
*parameter = inb_p(STATUS_REGISTER);
|
241 |
|
|
#ifdef DEBUG
|
242 |
|
|
printk("ACI: indexed_cmd(0x%02x, 0x%02x) = 0x%02x\n", opcode, index,
|
243 |
|
|
*parameter);
|
244 |
|
|
#endif
|
245 |
|
|
|
246 |
|
|
restore_flags(flags);
|
247 |
|
|
return 0;
|
248 |
|
|
}
|
249 |
|
|
|
250 |
|
|
|
251 |
|
|
/*
|
252 |
|
|
* The following macro SCALE can be used to scale one integer volume
|
253 |
|
|
* value into another one using only integer arithmetic. If the input
|
254 |
|
|
* value x is in the range 0 <= x <= xmax, then the result will be in
|
255 |
|
|
* the range 0 <= SCALE(xmax,ymax,x) <= ymax.
|
256 |
|
|
*
|
257 |
|
|
* This macro has for all xmax, ymax > 0 and all 0 <= x <= xmax the
|
258 |
|
|
* following nice properties:
|
259 |
|
|
*
|
260 |
|
|
* - SCALE(xmax,ymax,xmax) = ymax
|
261 |
|
|
* - SCALE(xmax,ymax,0) = 0
|
262 |
|
|
* - SCALE(xmax,ymax,SCALE(ymax,xmax,SCALE(xmax,ymax,x))) = SCALE(xmax,ymax,x)
|
263 |
|
|
*
|
264 |
|
|
* In addition, the rounding error is minimal and nicely distributed.
|
265 |
|
|
* The proofs are left as an exercise to the reader.
|
266 |
|
|
*/
|
267 |
|
|
|
268 |
|
|
#define SCALE(xmax,ymax,x) (((x)*(ymax)+(xmax)/2)/(xmax))
|
269 |
|
|
|
270 |
|
|
|
271 |
|
|
static int getvolume(caddr_t arg,
|
272 |
|
|
unsigned char left_index, unsigned char right_index)
|
273 |
|
|
{
|
274 |
|
|
int vol;
|
275 |
|
|
unsigned char buf;
|
276 |
|
|
|
277 |
|
|
/* left channel */
|
278 |
|
|
if (indexed_cmd(0xf0, left_index, &buf)) return -EIO;
|
279 |
|
|
vol = SCALE(0x20, 100, buf < 0x20 ? 0x20-buf : 0);
|
280 |
|
|
/* right channel */
|
281 |
|
|
if (indexed_cmd(0xf0, right_index, &buf)) return -EIO;
|
282 |
|
|
vol |= SCALE(0x20, 100, buf < 0x20 ? 0x20-buf : 0) << 8;
|
283 |
|
|
|
284 |
|
|
return snd_ioctl_return((int *) arg, vol);
|
285 |
|
|
}
|
286 |
|
|
|
287 |
|
|
|
288 |
|
|
static int setvolume(caddr_t arg,
|
289 |
|
|
unsigned char left_index, unsigned char right_index)
|
290 |
|
|
{
|
291 |
|
|
int vol, ret;
|
292 |
|
|
unsigned param;
|
293 |
|
|
|
294 |
|
|
param = get_user((int *) arg);
|
295 |
|
|
/* left channel */
|
296 |
|
|
vol = param & 0xff;
|
297 |
|
|
if (vol > 100) vol = 100;
|
298 |
|
|
vol = SCALE(100, 0x20, vol);
|
299 |
|
|
if (write_cmd(left_index, 0x20 - vol)) return -EIO;
|
300 |
|
|
ret = SCALE(0x20, 100, vol);
|
301 |
|
|
/* right channel */
|
302 |
|
|
vol = (param >> 8) & 0xff;
|
303 |
|
|
if (vol > 100) vol = 100;
|
304 |
|
|
vol = SCALE(100, 0x20, vol);
|
305 |
|
|
if (write_cmd(right_index, 0x20 - vol)) return -EIO;
|
306 |
|
|
ret |= SCALE(0x20, 100, vol) << 8;
|
307 |
|
|
|
308 |
|
|
return snd_ioctl_return((int *) arg, ret);
|
309 |
|
|
}
|
310 |
|
|
|
311 |
|
|
|
312 |
|
|
static int
|
313 |
|
|
aci_mixer_ioctl (int dev, unsigned int cmd, caddr_t arg)
|
314 |
|
|
{
|
315 |
|
|
int status, vol;
|
316 |
|
|
unsigned char buf;
|
317 |
|
|
|
318 |
|
|
/* handle solo mode control */
|
319 |
|
|
if (cmd == SOUND_MIXER_PRIVATE1) {
|
320 |
|
|
if (get_user((int *) arg) >= 0) {
|
321 |
|
|
aci_solo = !!get_user((int *) arg);
|
322 |
|
|
if (write_cmd(0xd2, aci_solo)) return -EIO;
|
323 |
|
|
} else if (aci_version >= 0xb0) {
|
324 |
|
|
if ((status = read_general_status()) < 0) return -EIO;
|
325 |
|
|
return snd_ioctl_return ((int *) arg, (status & 0x20) == 0);
|
326 |
|
|
}
|
327 |
|
|
return snd_ioctl_return((int *) arg, aci_solo);
|
328 |
|
|
}
|
329 |
|
|
|
330 |
|
|
if (((cmd >> 8) & 0xff) == 'M') {
|
331 |
|
|
if (cmd & IOC_IN)
|
332 |
|
|
/* read and write */
|
333 |
|
|
switch (cmd & 0xff) {
|
334 |
|
|
case SOUND_MIXER_VOLUME:
|
335 |
|
|
return setvolume(arg, 0x01, 0x00);
|
336 |
|
|
case SOUND_MIXER_CD:
|
337 |
|
|
return setvolume(arg, 0x3c, 0x34);
|
338 |
|
|
case SOUND_MIXER_MIC:
|
339 |
|
|
return setvolume(arg, 0x38, 0x30);
|
340 |
|
|
case SOUND_MIXER_LINE:
|
341 |
|
|
return setvolume(arg, 0x39, 0x31);
|
342 |
|
|
case SOUND_MIXER_SYNTH:
|
343 |
|
|
return setvolume(arg, 0x3b, 0x33);
|
344 |
|
|
case SOUND_MIXER_PCM:
|
345 |
|
|
return setvolume(arg, 0x3a, 0x32);
|
346 |
|
|
case SOUND_MIXER_LINE1: /* AUX1 */
|
347 |
|
|
return setvolume(arg, 0x3d, 0x35);
|
348 |
|
|
case SOUND_MIXER_LINE2: /* AUX2 */
|
349 |
|
|
return setvolume(arg, 0x3e, 0x36);
|
350 |
|
|
case SOUND_MIXER_IGAIN: /* MIC pre-amp */
|
351 |
|
|
vol = get_user((int *) arg) & 0xff;
|
352 |
|
|
if (vol > 100) vol = 100;
|
353 |
|
|
vol = SCALE(100, 3, vol);
|
354 |
|
|
if (write_cmd(0x03, vol)) return -EIO;
|
355 |
|
|
vol = SCALE(3, 100, vol);
|
356 |
|
|
return snd_ioctl_return((int *) arg, vol | (vol << 8));
|
357 |
|
|
case SOUND_MIXER_RECSRC:
|
358 |
|
|
return snd_ioctl_return ((int *) arg, 0);
|
359 |
|
|
break;
|
360 |
|
|
default:
|
361 |
|
|
return -EINVAL;
|
362 |
|
|
}
|
363 |
|
|
else
|
364 |
|
|
/* only read */
|
365 |
|
|
switch (cmd & 0xff) {
|
366 |
|
|
case SOUND_MIXER_DEVMASK:
|
367 |
|
|
return snd_ioctl_return ((int *) arg,
|
368 |
|
|
SOUND_MASK_VOLUME | SOUND_MASK_CD |
|
369 |
|
|
SOUND_MASK_MIC | SOUND_MASK_LINE |
|
370 |
|
|
SOUND_MASK_SYNTH | SOUND_MASK_PCM |
|
371 |
|
|
#if 0
|
372 |
|
|
SOUND_MASK_IGAIN |
|
373 |
|
|
#endif
|
374 |
|
|
SOUND_MASK_LINE1 | SOUND_MASK_LINE2);
|
375 |
|
|
break;
|
376 |
|
|
case SOUND_MIXER_STEREODEVS:
|
377 |
|
|
return snd_ioctl_return ((int *) arg,
|
378 |
|
|
SOUND_MASK_VOLUME | SOUND_MASK_CD |
|
379 |
|
|
SOUND_MASK_MIC | SOUND_MASK_LINE |
|
380 |
|
|
SOUND_MASK_SYNTH | SOUND_MASK_PCM |
|
381 |
|
|
SOUND_MASK_LINE1 | SOUND_MASK_LINE2);
|
382 |
|
|
break;
|
383 |
|
|
case SOUND_MIXER_RECMASK:
|
384 |
|
|
return snd_ioctl_return ((int *) arg, 0);
|
385 |
|
|
break;
|
386 |
|
|
case SOUND_MIXER_RECSRC:
|
387 |
|
|
return snd_ioctl_return ((int *) arg, 0);
|
388 |
|
|
break;
|
389 |
|
|
case SOUND_MIXER_CAPS:
|
390 |
|
|
return snd_ioctl_return ((int *) arg, 0);
|
391 |
|
|
break;
|
392 |
|
|
case SOUND_MIXER_VOLUME:
|
393 |
|
|
return getvolume(arg, 0x04, 0x03);
|
394 |
|
|
case SOUND_MIXER_CD:
|
395 |
|
|
return getvolume(arg, 0x0a, 0x09);
|
396 |
|
|
case SOUND_MIXER_MIC:
|
397 |
|
|
return getvolume(arg, 0x06, 0x05);
|
398 |
|
|
case SOUND_MIXER_LINE:
|
399 |
|
|
return getvolume(arg, 0x08, 0x07);
|
400 |
|
|
case SOUND_MIXER_SYNTH:
|
401 |
|
|
return getvolume(arg, 0x0c, 0x0b);
|
402 |
|
|
case SOUND_MIXER_PCM:
|
403 |
|
|
return getvolume(arg, 0x0e, 0x0d);
|
404 |
|
|
case SOUND_MIXER_LINE1: /* AUX1 */
|
405 |
|
|
return getvolume(arg, 0x11, 0x10);
|
406 |
|
|
case SOUND_MIXER_LINE2: /* AUX2 */
|
407 |
|
|
return getvolume(arg, 0x13, 0x12);
|
408 |
|
|
case SOUND_MIXER_IGAIN: /* MIC pre-amp */
|
409 |
|
|
if (indexed_cmd(0xf0, 0x21, &buf)) return -EIO;
|
410 |
|
|
vol = SCALE(3, 100, buf <= 3 ? buf : 3);
|
411 |
|
|
vol |= vol << 8;
|
412 |
|
|
return snd_ioctl_return((int *) arg, vol);
|
413 |
|
|
default:
|
414 |
|
|
return -EINVAL;
|
415 |
|
|
}
|
416 |
|
|
}
|
417 |
|
|
|
418 |
|
|
return -EINVAL;
|
419 |
|
|
}
|
420 |
|
|
|
421 |
|
|
|
422 |
|
|
static struct mixer_operations aci_mixer_operations =
|
423 |
|
|
{
|
424 |
|
|
"ACI",
|
425 |
|
|
"ACI mixer",
|
426 |
|
|
aci_mixer_ioctl,
|
427 |
|
|
NULL
|
428 |
|
|
};
|
429 |
|
|
|
430 |
|
|
static unsigned char
|
431 |
|
|
mad_read (int port)
|
432 |
|
|
{
|
433 |
|
|
outb (0xE3, 0xf8f); /* Write MAD16 password */
|
434 |
|
|
return inb (port); /* Read from port */
|
435 |
|
|
}
|
436 |
|
|
|
437 |
|
|
|
438 |
|
|
/*
|
439 |
|
|
* Check, whether there actually is any ACI port operational and if
|
440 |
|
|
* one was found, then initialize the ACI interface, reserve the I/O
|
441 |
|
|
* addresses and attach the new mixer to the relevant VoxWare data
|
442 |
|
|
* structures.
|
443 |
|
|
*
|
444 |
|
|
* Returns: 1 ACI mixer detected
|
445 |
|
|
* 0 nothing there
|
446 |
|
|
*
|
447 |
|
|
* There is also an internal mixer in the codec (CS4231A or AD1845),
|
448 |
|
|
* that deserves no purpose in an ACI based system which uses an
|
449 |
|
|
* external ACI controlled stereo mixer. Make sure that this codec
|
450 |
|
|
* mixer has the AUX1 input selected as the recording source, that the
|
451 |
|
|
* input gain is set near maximum and that the other channels going
|
452 |
|
|
* from the inputs to the codec output are muted.
|
453 |
|
|
*/
|
454 |
|
|
|
455 |
|
|
int attach_aci(void)
|
456 |
|
|
{
|
457 |
|
|
char *boardname = "unknown";
|
458 |
|
|
int volume;
|
459 |
|
|
|
460 |
|
|
#define MC4_PORT 0xf90
|
461 |
|
|
|
462 |
|
|
aci_port =
|
463 |
|
|
(mad_read(MC4_PORT) & 0x10) ? 0x344 : 0x354;
|
464 |
|
|
|
465 |
|
|
if (check_region(aci_port, 3)) {
|
466 |
|
|
#ifdef DEBUG
|
467 |
|
|
printk("ACI: I/O area 0x%03x-0x%03x already used.\n",
|
468 |
|
|
aci_port, aci_port+2);
|
469 |
|
|
#endif
|
470 |
|
|
return 0;
|
471 |
|
|
}
|
472 |
|
|
|
473 |
|
|
if (read_cmd(0xf2, 2, aci_idcode)) {
|
474 |
|
|
#ifdef DEBUG
|
475 |
|
|
printk("ACI: Failed to read idcode.\n");
|
476 |
|
|
#endif
|
477 |
|
|
return 0;
|
478 |
|
|
}
|
479 |
|
|
if (read_cmd(0xf1, 1, &aci_version)) {
|
480 |
|
|
#ifdef DEBUG
|
481 |
|
|
printk("ACI: Failed to read version.\n");
|
482 |
|
|
#endif
|
483 |
|
|
return 0;
|
484 |
|
|
}
|
485 |
|
|
|
486 |
|
|
if (aci_idcode[0] == 0x6d) {
|
487 |
|
|
/* it looks like a miro soundcard */
|
488 |
|
|
switch (aci_idcode[1]) {
|
489 |
|
|
case 0x41:
|
490 |
|
|
boardname = "PCM1 pro / early PCM12";
|
491 |
|
|
break;
|
492 |
|
|
case 0x42:
|
493 |
|
|
boardname = "PCM12";
|
494 |
|
|
break;
|
495 |
|
|
case 0x43:
|
496 |
|
|
boardname = "PCM20";
|
497 |
|
|
break;
|
498 |
|
|
default:
|
499 |
|
|
boardname = "unknown miro";
|
500 |
|
|
}
|
501 |
|
|
} else
|
502 |
|
|
#ifndef DEBUG
|
503 |
|
|
return 0;
|
504 |
|
|
#endif
|
505 |
|
|
|
506 |
|
|
printk("<ACI %02x, id %02x %02x (%s)> at 0x%03x\n",
|
507 |
|
|
aci_version, aci_idcode[0], aci_idcode[1], boardname, aci_port);
|
508 |
|
|
|
509 |
|
|
/* initialize ACI mixer */
|
510 |
|
|
implied_cmd(0xff);
|
511 |
|
|
aci_solo = 0;
|
512 |
|
|
|
513 |
|
|
/* attach the mixer */
|
514 |
|
|
request_region(aci_port, 3, "sound mixer (ACI)");
|
515 |
|
|
if (num_mixers < MAX_MIXER_DEV) {
|
516 |
|
|
if (num_mixers > 0 &&
|
517 |
|
|
!strcmp("MAD16 WSS (CS4231A)", mixer_devs[num_mixers-1]->name)) {
|
518 |
|
|
/*
|
519 |
|
|
* The previously registered mixer device is the CS4231A which
|
520 |
|
|
* has no function on an ACI card. Make the ACI mixer the first
|
521 |
|
|
* of the two mixer devices.
|
522 |
|
|
*/
|
523 |
|
|
mixer_devs[num_mixers] = mixer_devs[num_mixers-1];
|
524 |
|
|
mixer_devs[num_mixers-1] = &aci_mixer_operations;
|
525 |
|
|
/*
|
526 |
|
|
* Initialize the CS4231A mixer with reasonable values. It is
|
527 |
|
|
* unlikely that the user ever will want to change these as all
|
528 |
|
|
* channels can be mixed via ACI.
|
529 |
|
|
*/
|
530 |
|
|
volume = 0x6464;
|
531 |
|
|
mixer_devs[num_mixers]->
|
532 |
|
|
ioctl(num_mixers, SOUND_MIXER_WRITE_PCM, (caddr_t) &volume);
|
533 |
|
|
volume = 0x6464;
|
534 |
|
|
mixer_devs[num_mixers]->
|
535 |
|
|
ioctl(num_mixers, SOUND_MIXER_WRITE_IGAIN, (caddr_t) &volume);
|
536 |
|
|
volume = 0;
|
537 |
|
|
mixer_devs[num_mixers]->
|
538 |
|
|
ioctl(num_mixers, SOUND_MIXER_WRITE_SPEAKER, (caddr_t) &volume);
|
539 |
|
|
volume = 0;
|
540 |
|
|
mixer_devs[num_mixers]->
|
541 |
|
|
ioctl(num_mixers, SOUND_MIXER_WRITE_MIC, (caddr_t) &volume);
|
542 |
|
|
volume = 0;
|
543 |
|
|
mixer_devs[num_mixers]->
|
544 |
|
|
ioctl(num_mixers, SOUND_MIXER_WRITE_IMIX, (caddr_t) &volume);
|
545 |
|
|
volume = 0;
|
546 |
|
|
mixer_devs[num_mixers]->
|
547 |
|
|
ioctl(num_mixers, SOUND_MIXER_WRITE_LINE1, (caddr_t) &volume);
|
548 |
|
|
volume = 0;
|
549 |
|
|
mixer_devs[num_mixers]->
|
550 |
|
|
ioctl(num_mixers, SOUND_MIXER_WRITE_LINE2, (caddr_t) &volume);
|
551 |
|
|
volume = 0;
|
552 |
|
|
mixer_devs[num_mixers]->
|
553 |
|
|
ioctl(num_mixers, SOUND_MIXER_WRITE_LINE3, (caddr_t) &volume);
|
554 |
|
|
volume = SOUND_MASK_LINE1;
|
555 |
|
|
mixer_devs[num_mixers]->
|
556 |
|
|
ioctl(num_mixers, SOUND_MIXER_WRITE_RECSRC, (caddr_t) &volume);
|
557 |
|
|
num_mixers++;
|
558 |
|
|
} else
|
559 |
|
|
mixer_devs[num_mixers++] = &aci_mixer_operations;
|
560 |
|
|
}
|
561 |
|
|
|
562 |
|
|
/* Initialize ACI mixer with reasonable power-up values */
|
563 |
|
|
volume = 0x3232;
|
564 |
|
|
aci_mixer_ioctl(num_mixers-1, SOUND_MIXER_WRITE_VOLUME, (caddr_t) &volume);
|
565 |
|
|
volume = 0x3232;
|
566 |
|
|
aci_mixer_ioctl(num_mixers-1, SOUND_MIXER_WRITE_SYNTH, (caddr_t) &volume);
|
567 |
|
|
volume = 0x3232;
|
568 |
|
|
aci_mixer_ioctl(num_mixers-1, SOUND_MIXER_WRITE_PCM, (caddr_t) &volume);
|
569 |
|
|
volume = 0x3232;
|
570 |
|
|
aci_mixer_ioctl(num_mixers-1, SOUND_MIXER_WRITE_LINE, (caddr_t) &volume);
|
571 |
|
|
volume = 0x3232;
|
572 |
|
|
aci_mixer_ioctl(num_mixers-1, SOUND_MIXER_WRITE_MIC, (caddr_t) &volume);
|
573 |
|
|
volume = 0x3232;
|
574 |
|
|
aci_mixer_ioctl(num_mixers-1, SOUND_MIXER_WRITE_CD, (caddr_t) &volume);
|
575 |
|
|
volume = 0x3232;
|
576 |
|
|
aci_mixer_ioctl(num_mixers-1, SOUND_MIXER_WRITE_LINE1, (caddr_t) &volume);
|
577 |
|
|
volume = 0x3232;
|
578 |
|
|
aci_mixer_ioctl(num_mixers-1, SOUND_MIXER_WRITE_LINE2, (caddr_t) &volume);
|
579 |
|
|
|
580 |
|
|
aci_present = 1;
|
581 |
|
|
|
582 |
|
|
return 1;
|
583 |
|
|
}
|
584 |
|
|
|
585 |
|
|
void unload_aci(void)
|
586 |
|
|
{
|
587 |
|
|
if (aci_present)
|
588 |
|
|
release_region(aci_port, 3);
|
589 |
|
|
}
|
590 |
|
|
|
591 |
|
|
#endif
|