78ed81a3 |
1 | #define _PAS2_MIXER_C_ |
2 | |
3 | /* |
4 | * linux/kernel/chr_drv/sound/pas2_mixer.c |
5 | * |
6 | * Mixer routines for the Pro Audio Spectrum cards. |
7 | * |
8 | * (C) 1992 Hannu Savolainen (hsavolai@cs.helsinki.fi) Craig Metz |
9 | * (cmetz@thor.tjhsst.edu) See COPYING for further details. Should be |
10 | * distributed with this file. |
11 | */ |
12 | |
13 | #include "sound_config.h" |
14 | |
15 | #if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_PAS) |
16 | |
17 | #include "pas.h" |
18 | |
19 | #define TRACE(what) /* (what) */ |
20 | |
21 | extern int translat_code; |
22 | |
23 | static int rec_devices = (SOUND_MASK_MIC); /* Default recording source */ |
24 | static int mode_control = 0; |
25 | |
26 | #define POSSIBLE_RECORDING_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | \ |
27 | SOUND_MASK_CD | SOUND_MASK_ALTPCM) |
28 | |
29 | #define SUPPORTED_MIXER_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | \ |
30 | SOUND_MASK_CD /*|SOUND_MASK_ALTPCM*/ | SOUND_MASK_IMIX | \ |
31 | SOUND_MASK_VOLUME | SOUND_MASK_BASS | SOUND_MASK_TREBLE | SOUND_MASK_RECLEV | \ |
32 | SOUND_MASK_MUTE | SOUND_MASK_ENHANCE | SOUND_MASK_LOUD) |
33 | |
34 | static unsigned short levels[SOUND_MIXER_NRDEVICES] = |
35 | { |
36 | 0x3232, /* Master Volume */ |
37 | 0x3232, /* Bass */ |
38 | 0x3232, /* Treble */ |
39 | 0x5050, /* FM */ |
40 | 0x4b4b, /* PCM */ |
41 | 0x3232, /* PC Speaker */ |
42 | 0x4b4b, /* Ext Line */ |
43 | 0x4b4b, /* Mic */ |
44 | 0x4b4b, /* CD */ |
45 | 0x6464, /* Recording monitor */ |
46 | 0x4b4b, /* SB PCM */ |
47 | 0x6464}; /* Recording level */ |
48 | |
49 | static int |
50 | mixer_output (int right_vol, int left_vol, int div, int bits, |
51 | int mixer /* Input or output mixer */ ) |
52 | { |
53 | int left = left_vol * div / 100; |
54 | int right = right_vol * div / 100; |
55 | |
56 | /* |
57 | * The Revision D cards have a problem with their MVA508 interface. The |
58 | * kludge-o-rama fix is to make a 16-bit quantity with identical LSB and |
59 | * MSBs out of the output byte and to do a 16-bit out to the mixer port - |
60 | * 1. We don't need to do this because the call to pas_write more than |
61 | * compensates for the timing problems. |
62 | */ |
63 | |
64 | if (bits & P_M_MV508_MIXER) |
65 | { /* Select input or output mixer */ |
66 | left |= mixer; |
67 | right |= mixer; |
68 | } |
69 | |
70 | if (bits == P_M_MV508_BASS || bits == P_M_MV508_TREBLE) |
71 | { /* Bass and trebble are mono devices */ |
72 | pas_write (P_M_MV508_ADDRESS | bits, PARALLEL_MIXER); |
73 | pas_write (left, PARALLEL_MIXER); |
74 | right_vol = left_vol; |
75 | } |
76 | else |
77 | { |
78 | pas_write (P_M_MV508_ADDRESS | P_M_MV508_LEFT | bits, PARALLEL_MIXER); |
79 | pas_write (left, PARALLEL_MIXER); |
80 | pas_write (P_M_MV508_ADDRESS | P_M_MV508_RIGHT | bits, PARALLEL_MIXER); |
81 | pas_write (right, PARALLEL_MIXER); |
82 | } |
83 | |
84 | return (left_vol | (right_vol << 8)); |
85 | } |
86 | |
87 | void |
88 | set_mode (int new_mode) |
89 | { |
90 | pas_write (P_M_MV508_ADDRESS | P_M_MV508_MODE, PARALLEL_MIXER); |
91 | pas_write (new_mode, PARALLEL_MIXER); |
92 | |
93 | mode_control = new_mode; |
94 | } |
95 | |
96 | static int |
97 | pas_mixer_set (int whichDev, unsigned int level) |
98 | { |
99 | int left, right, devmask, changed, i, mixer = 0; |
100 | |
101 | TRACE (printk ("static int pas_mixer_set(int whichDev = %d, unsigned int level = %X)\n", whichDev, level)); |
102 | |
103 | left = level & 0x7f; |
104 | right = (level & 0x7f00) >> 8; |
105 | |
106 | if (whichDev < SOUND_MIXER_NRDEVICES) |
107 | if ((1 << whichDev) & rec_devices) |
108 | mixer = P_M_MV508_INPUTMIX; |
109 | else |
110 | mixer = P_M_MV508_OUTPUTMIX; |
111 | |
112 | switch (whichDev) |
113 | { |
114 | case SOUND_MIXER_VOLUME: /* Master volume (0-63) */ |
115 | levels[whichDev] = mixer_output (right, left, 63, P_M_MV508_MASTER_A, 0); |
116 | break; |
117 | |
118 | /* |
119 | * Note! Bass and Treble are mono devices. Will use just the left |
120 | * channel. |
121 | */ |
122 | case SOUND_MIXER_BASS: /* Bass (0-12) */ |
123 | levels[whichDev] = mixer_output (right, left, 12, P_M_MV508_BASS, 0); |
124 | break; |
125 | case SOUND_MIXER_TREBLE: /* Treble (0-12) */ |
126 | levels[whichDev] = mixer_output (right, left, 12, P_M_MV508_TREBLE, 0); |
127 | break; |
128 | |
129 | case SOUND_MIXER_SYNTH: /* Internal synthesizer (0-31) */ |
130 | levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_FM, mixer); |
131 | break; |
132 | case SOUND_MIXER_PCM: /* PAS PCM (0-31) */ |
133 | levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_PCM, mixer); |
134 | break; |
135 | case SOUND_MIXER_ALTPCM: /* SB PCM (0-31) */ |
136 | levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_SB, mixer); |
137 | break; |
138 | case SOUND_MIXER_SPEAKER: /* PC speaker (0-31) */ |
139 | levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_SPEAKER, mixer); |
140 | break; |
141 | case SOUND_MIXER_LINE: /* External line (0-31) */ |
142 | levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_LINE, mixer); |
143 | break; |
144 | case SOUND_MIXER_CD: /* CD (0-31) */ |
145 | levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_CDROM, mixer); |
146 | break; |
147 | case SOUND_MIXER_MIC: /* External microphone (0-31) */ |
148 | levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_MIC, mixer); |
149 | break; |
150 | case SOUND_MIXER_IMIX: /* Recording monitor (0-31) (Only available |
151 | * on the Output Mixer) */ |
152 | levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_IMIXER, |
153 | P_M_MV508_OUTPUTMIX); |
154 | break; |
155 | case SOUND_MIXER_RECLEV: /* Recording level (0-15) */ |
156 | levels[whichDev] = mixer_output (right, left, 15, P_M_MV508_MASTER_B, 0); |
157 | break; |
158 | |
159 | case SOUND_MIXER_MUTE: |
160 | return 0; |
161 | break; |
162 | |
163 | case SOUND_MIXER_ENHANCE: |
164 | i = 0; |
165 | level &= 0x7f; |
166 | if (level) |
167 | i = (level / 20) - 1; |
168 | |
169 | mode_control &= ~P_M_MV508_ENHANCE_BITS; |
170 | mode_control |= P_M_MV508_ENHANCE_BITS; |
171 | set_mode (mode_control); |
172 | |
173 | if (i) |
174 | i = (i + 1) * 20; |
175 | return i; |
176 | break; |
177 | |
178 | case SOUND_MIXER_LOUD: |
179 | mode_control &= ~P_M_MV508_LOUDNESS; |
180 | if (level) |
181 | mode_control |= P_M_MV508_LOUDNESS; |
182 | set_mode (mode_control); |
183 | return !!level; /* 0 or 1 */ |
184 | break; |
185 | |
186 | case SOUND_MIXER_RECSRC: |
187 | devmask = level & POSSIBLE_RECORDING_DEVICES; |
188 | |
189 | changed = devmask ^ rec_devices; |
190 | rec_devices = devmask; |
191 | |
192 | for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) |
193 | if (changed & (1 << i)) |
194 | { |
195 | pas_mixer_set (i, levels[i]); |
196 | } |
197 | return rec_devices; |
198 | break; |
199 | |
200 | default: |
201 | return RET_ERROR (EINVAL); |
202 | } |
203 | |
204 | return (levels[whichDev]); |
205 | } |
206 | |
207 | /*****/ |
208 | |
209 | static int |
210 | mixer_set_levels (struct sb_mixer_levels *user_l) |
211 | { |
212 | #define cmix(v) ((((v.r*100+7)/15)<<8)| ((v.l*100+7)/15)) |
213 | |
214 | struct sb_mixer_levels l; |
215 | |
216 | IOCTL_FROM_USER ((char *) &l, (char *) user_l, 0, sizeof (l)); |
217 | |
218 | if (l.master.l & ~0xF || l.master.r & ~0xF |
219 | || l.line.l & ~0xF || l.line.r & ~0xF |
220 | || l.voc.l & ~0xF || l.voc.r & ~0xF |
221 | || l.fm.l & ~0xF || l.fm.r & ~0xF |
222 | || l.cd.l & ~0xF || l.cd.r & ~0xF |
223 | || l.mic & ~0x7) |
224 | return (RET_ERROR (EINVAL)); |
225 | |
226 | pas_mixer_set (SOUND_MIXER_VOLUME, cmix (l.master)); |
227 | pas_mixer_set (SOUND_MIXER_LINE, cmix (l.line)); |
228 | pas_mixer_set (SOUND_MIXER_PCM, cmix (l.voc)); |
229 | pas_mixer_set (SOUND_MIXER_ALTPCM, cmix (l.voc)); |
230 | pas_mixer_set (SOUND_MIXER_SYNTH, cmix (l.fm)); |
231 | pas_mixer_set (SOUND_MIXER_CD, cmix (l.cd)); |
232 | pas_mixer_set (SOUND_MIXER_MIC, ((l.mic * 100 + 3) / 7) | (((l.mic * 100 + 3) / 7) << 8)); |
233 | return (0); |
234 | } |
235 | |
236 | /* |
237 | * This sets aspects of the Mixer that are not volume levels. (Recording |
238 | * source, filter level, I/O filtering, and stereo.) |
239 | */ |
240 | static int |
241 | mixer_set_params (struct sb_mixer_params *user_p) |
242 | { |
243 | struct sb_mixer_params p; |
244 | S_BYTE val; |
245 | int src; |
246 | unsigned long flags; |
247 | |
248 | IOCTL_FROM_USER ((char *) &p, (char *) user_p, 0, sizeof (p)); |
249 | |
250 | if (p.record_source != SRC_MIC |
251 | && p.record_source != SRC_CD |
252 | && p.record_source != SRC_LINE) |
253 | return (RET_ERROR (EINVAL)); |
254 | |
255 | /* |
256 | * I'm not sure if this is The Right Thing. Should stereo be entirely |
257 | * under control of DSP? I like being able to toggle it while a sound is |
258 | * playing, so I do this... because I can. |
259 | */ |
260 | |
261 | DISABLE_INTR (flags); |
262 | |
263 | val = (pas_read (PCM_CONTROL) & ~P_C_MIXER_CROSS_FIELD) | P_C_MIXER_CROSS_R_TO_R | P_C_MIXER_CROSS_L_TO_L; |
264 | if (!p.dsp_stereo) |
265 | val |= (P_C_MIXER_CROSS_R_TO_L | P_C_MIXER_CROSS_L_TO_R); /* Mono */ |
266 | pas_write (val, PCM_CONTROL); |
267 | |
268 | RESTORE_INTR (flags); |
269 | |
270 | switch (p.record_source) |
271 | { |
272 | case SRC_CD: |
273 | src = SOUND_MASK_CD; |
274 | break; |
275 | |
276 | case SRC_LINE: |
277 | src = SOUND_MASK_LINE; |
278 | break; |
279 | |
280 | default: |
281 | src = SOUND_MASK_MIC; |
282 | break; |
283 | } |
284 | |
285 | pas_mixer_set (SOUND_MIXER_RECSRC, src); |
286 | |
287 | /* |
288 | * setmixer (OUT_FILTER, ((dsp_stereo ? STEREO_DAC : MONO_DAC) | |
289 | * (p.filter_output ? FILT_ON : FILT_OFF))); |
290 | */ |
291 | return (0); |
292 | } |
293 | |
294 | static int |
295 | getmixer (int dev, int chn) |
296 | { |
297 | if (chn == P_M_MV508_RIGHT) |
298 | { |
299 | return (levels[dev] >> 8) & 0x7f; |
300 | } |
301 | else |
302 | { |
303 | return levels[dev] & 0x7f; |
304 | } |
305 | } |
306 | |
307 | /* Read the current mixer level settings into the user's struct. */ |
308 | static int |
309 | mixer_get_levels (struct sb_mixer_levels *user_l) |
310 | { |
311 | |
312 | struct sb_mixer_levels l; |
313 | |
314 | l.master.r = ((((levels[SOUND_MIXER_VOLUME] >> 8) & 0x7f) * 15) + 50) / 100; /* Master */ |
315 | l.master.l = (((levels[SOUND_MIXER_VOLUME] & 0x7f) * 15) + 50) / 100; /* Master */ |
316 | |
317 | l.line.r = ((getmixer (SOUND_MIXER_LINE, P_M_MV508_RIGHT) * 15) + 50) / 100; /* Line */ |
318 | l.line.l = ((getmixer (SOUND_MIXER_LINE, P_M_MV508_LEFT) * 15) + 50) / 100; |
319 | |
320 | l.voc.r = ((getmixer (SOUND_MIXER_PCM, P_M_MV508_RIGHT) * 15) + 50) / 100; /* DAC */ |
321 | l.voc.l = ((getmixer (SOUND_MIXER_PCM, P_M_MV508_LEFT) * 15) + 50) / 100; |
322 | |
323 | l.fm.r = ((getmixer (SOUND_MIXER_SYNTH, P_M_MV508_RIGHT) * 15) + 50) / 100; /* FM */ |
324 | l.fm.l = ((getmixer (SOUND_MIXER_SYNTH, P_M_MV508_LEFT) * 15) + 50) / 100; |
325 | |
326 | l.cd.r = ((getmixer (SOUND_MIXER_CD, P_M_MV508_RIGHT) * 15) + 50) / 100; /* CD */ |
327 | l.cd.l = ((getmixer (SOUND_MIXER_CD, P_M_MV508_LEFT) * 15) + 50) / 100; |
328 | |
329 | l.mic = ((getmixer (SOUND_MIXER_MIC, P_M_MV508_LEFT) * 7) + 50) / 100; /* Microphone */ |
330 | |
331 | IOCTL_TO_USER ((char *) user_l, 0, (char *) &l, sizeof (l)); |
332 | return (0); |
333 | } |
334 | |
335 | /* Read the current mixer parameters into the user's struct. */ |
336 | static int |
337 | mixer_get_params (struct sb_mixer_params *user_params) |
338 | { |
339 | S_BYTE val; |
340 | struct sb_mixer_params params; |
341 | |
342 | switch (rec_devices) |
343 | { |
344 | case SOUND_MASK_CD: |
345 | params.record_source = SRC_CD; |
346 | break; |
347 | |
348 | case SOUND_MASK_LINE: |
349 | params.record_source = SRC_LINE; |
350 | break; |
351 | |
352 | case SOUND_MASK_MIC: |
353 | params.record_source = SRC_MIC; |
354 | break; |
355 | |
356 | default: |
357 | params.record_source = SRC_MIC; |
358 | pas_mixer_set (SOUND_MIXER_RECSRC, SOUND_MASK_MIC); /* Adjust */ |
359 | } |
360 | |
361 | params.hifreq_filter = OFF; |
362 | params.filter_input = OFF; |
363 | params.filter_output = OFF; |
364 | |
365 | val = INB (PCM_CONTROL); |
366 | params.dsp_stereo = ((val & P_C_MIXER_CROSS_FIELD) == (P_C_MIXER_CROSS_L_TO_L | P_C_MIXER_CROSS_R_TO_R)); |
367 | |
368 | IOCTL_TO_USER ((char *) user_params, 0, (char *) ¶ms, sizeof (params)); |
369 | return (0); |
370 | } |
371 | |
372 | /*****/ |
373 | |
374 | static void |
375 | pas_mixer_reset (void) |
376 | { |
377 | int foo; |
378 | |
379 | TRACE (printk ("pas2_mixer.c: void pas_mixer_reset(void)\n")); |
380 | |
381 | for (foo = 0; foo < SOUND_MIXER_NRDEVICES; foo++) |
382 | pas_mixer_set (foo, levels[foo]); |
383 | |
384 | set_mode (P_M_MV508_LOUDNESS | P_M_MV508_ENHANCE_40); |
385 | } |
386 | |
387 | int |
388 | pas_mixer_ioctl (int dev, unsigned int cmd, unsigned int arg) |
389 | { |
390 | TRACE (printk ("pas2_mixer.c: int pas_mixer_ioctl(unsigned int cmd = %X, unsigned int arg = %X)\n", cmd, arg)); |
391 | |
392 | if (((cmd >> 8) & 0xff) == 'M') |
393 | { |
394 | if (cmd & IOC_IN) |
395 | return IOCTL_OUT (arg, pas_mixer_set (cmd & 0xff, IOCTL_IN (arg))); |
396 | else |
397 | { /* Read parameters */ |
398 | |
399 | switch (cmd & 0xff) |
400 | { |
401 | |
402 | case SOUND_MIXER_RECSRC: |
403 | return IOCTL_OUT (arg, rec_devices); |
404 | break; |
405 | |
406 | case SOUND_MIXER_STEREODEVS: |
407 | return IOCTL_OUT (arg, SUPPORTED_MIXER_DEVICES & ~(SOUND_MASK_BASS | SOUND_MASK_TREBLE)); |
408 | break; |
409 | |
410 | case SOUND_MIXER_DEVMASK: |
411 | return IOCTL_OUT (arg, SUPPORTED_MIXER_DEVICES); |
412 | break; |
413 | |
414 | case SOUND_MIXER_RECMASK: |
415 | return IOCTL_OUT (arg, POSSIBLE_RECORDING_DEVICES & SUPPORTED_MIXER_DEVICES); |
416 | break; |
417 | |
418 | case SOUND_MIXER_CAPS: |
419 | return IOCTL_OUT (arg, 0); /* No special capabilities */ |
420 | break; |
421 | |
422 | case SOUND_MIXER_MUTE: |
423 | return IOCTL_OUT (arg, 0); /* No mute yet */ |
424 | break; |
425 | |
426 | case SOUND_MIXER_ENHANCE: |
427 | if (!(mode_control & P_M_MV508_ENHANCE_BITS)) |
428 | return IOCTL_OUT (arg, 0); |
429 | return IOCTL_OUT (arg, ((mode_control & P_M_MV508_ENHANCE_BITS) + 1) * 20); |
430 | break; |
431 | |
432 | case SOUND_MIXER_LOUD: |
433 | if (mode_control & P_M_MV508_LOUDNESS) |
434 | return IOCTL_OUT (arg, 1); |
435 | return IOCTL_OUT (arg, 0); |
436 | break; |
437 | |
438 | default: |
439 | return IOCTL_OUT (arg, levels[cmd & 0xff]); |
440 | } |
441 | } |
442 | } |
443 | else |
444 | { |
445 | switch (cmd) |
446 | { |
447 | case MIXER_IOCTL_SET_LEVELS: |
448 | mixer_set_levels ((struct sb_mixer_levels *) arg); |
449 | return mixer_get_levels ((struct sb_mixer_levels *) arg); |
450 | case MIXER_IOCTL_SET_PARAMS: |
451 | mixer_set_params ((struct sb_mixer_params *) arg); |
452 | return mixer_get_params ((struct sb_mixer_params *) arg); |
453 | case MIXER_IOCTL_READ_LEVELS: |
454 | return mixer_get_levels ((struct sb_mixer_levels *) arg); |
455 | case MIXER_IOCTL_READ_PARAMS: |
456 | return mixer_get_params ((struct sb_mixer_params *) arg); |
457 | case MIXER_IOCTL_RESET: |
458 | pas_mixer_reset (); |
459 | return (0); |
460 | default: |
461 | return RET_ERROR (EINVAL); |
462 | } |
463 | } |
464 | return RET_ERROR (EINVAL); |
465 | } |
466 | |
467 | static struct mixer_operations pas_mixer_operations = |
468 | { |
469 | pas_mixer_ioctl |
470 | }; |
471 | |
472 | int |
473 | pas_init_mixer (void) |
474 | { |
475 | pas_mixer_reset (); |
476 | |
477 | mixer_devs[num_mixers++] = &pas_mixer_operations; |
478 | return 1; |
479 | } |
480 | |
481 | #endif |