* linux/kernel/chr_drv/sound/pas2_mixer.c
* Mixer routines for the Pro Audio Spectrum cards.
* (C) 1992 Hannu Savolainen (hsavolai@cs.helsinki.fi) Craig Metz
* (cmetz@thor.tjhsst.edu) See COPYING for further details. Should be
* distributed with this file.
#include "sound_config.h"
#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_PAS)
#define TRACE(what) /* (what) */
extern int translat_code
;
static int rec_devices
= (SOUND_MASK_MIC
); /* Default recording source */
static int mode_control
= 0;
#define POSSIBLE_RECORDING_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | \
SOUND_MASK_CD | SOUND_MASK_ALTPCM)
#define SUPPORTED_MIXER_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | \
SOUND_MASK_CD /*|SOUND_MASK_ALTPCM*/ | SOUND_MASK_IMIX | \
SOUND_MASK_VOLUME | SOUND_MASK_BASS | SOUND_MASK_TREBLE | SOUND_MASK_RECLEV | \
SOUND_MASK_MUTE | SOUND_MASK_ENHANCE | SOUND_MASK_LOUD)
static unsigned short levels
[SOUND_MIXER_NRDEVICES
] =
0x3232, /* Master Volume */
0x6464, /* Recording monitor */
0x6464}; /* Recording level */
mixer_output (int right_vol
, int left_vol
, int div
, int bits
,
int mixer
/* Input or output mixer */ )
int left
= left_vol
* div
/ 100;
int right
= right_vol
* div
/ 100;
* The Revision D cards have a problem with their MVA508 interface. The
* kludge-o-rama fix is to make a 16-bit quantity with identical LSB and
* MSBs out of the output byte and to do a 16-bit out to the mixer port -
* 1. We don't need to do this because the call to pas_write more than
* compensates for the timing problems.
if (bits
& P_M_MV508_MIXER
)
{ /* Select input or output mixer */
if (bits
== P_M_MV508_BASS
|| bits
== P_M_MV508_TREBLE
)
{ /* Bass and trebble are mono devices */
pas_write (P_M_MV508_ADDRESS
| bits
, PARALLEL_MIXER
);
pas_write (left
, PARALLEL_MIXER
);
pas_write (P_M_MV508_ADDRESS
| P_M_MV508_LEFT
| bits
, PARALLEL_MIXER
);
pas_write (left
, PARALLEL_MIXER
);
pas_write (P_M_MV508_ADDRESS
| P_M_MV508_RIGHT
| bits
, PARALLEL_MIXER
);
pas_write (right
, PARALLEL_MIXER
);
return (left_vol
| (right_vol
<< 8));
pas_write (P_M_MV508_ADDRESS
| P_M_MV508_MODE
, PARALLEL_MIXER
);
pas_write (new_mode
, PARALLEL_MIXER
);
pas_mixer_set (int whichDev
, unsigned int level
)
int left
, right
, devmask
, changed
, i
, mixer
= 0;
TRACE (printk ("static int pas_mixer_set(int whichDev = %d, unsigned int level = %X)\n", whichDev
, level
));
right
= (level
& 0x7f00) >> 8;
if (whichDev
< SOUND_MIXER_NRDEVICES
)
if ((1 << whichDev
) & rec_devices
)
mixer
= P_M_MV508_INPUTMIX
;
mixer
= P_M_MV508_OUTPUTMIX
;
case SOUND_MIXER_VOLUME
: /* Master volume (0-63) */
levels
[whichDev
] = mixer_output (right
, left
, 63, P_M_MV508_MASTER_A
, 0);
* Note! Bass and Treble are mono devices. Will use just the left
case SOUND_MIXER_BASS
: /* Bass (0-12) */
levels
[whichDev
] = mixer_output (right
, left
, 12, P_M_MV508_BASS
, 0);
case SOUND_MIXER_TREBLE
: /* Treble (0-12) */
levels
[whichDev
] = mixer_output (right
, left
, 12, P_M_MV508_TREBLE
, 0);
case SOUND_MIXER_SYNTH
: /* Internal synthesizer (0-31) */
levels
[whichDev
] = mixer_output (right
, left
, 31, P_M_MV508_MIXER
| P_M_MV508_FM
, mixer
);
case SOUND_MIXER_PCM
: /* PAS PCM (0-31) */
levels
[whichDev
] = mixer_output (right
, left
, 31, P_M_MV508_MIXER
| P_M_MV508_PCM
, mixer
);
case SOUND_MIXER_ALTPCM
: /* SB PCM (0-31) */
levels
[whichDev
] = mixer_output (right
, left
, 31, P_M_MV508_MIXER
| P_M_MV508_SB
, mixer
);
case SOUND_MIXER_SPEAKER
: /* PC speaker (0-31) */
levels
[whichDev
] = mixer_output (right
, left
, 31, P_M_MV508_MIXER
| P_M_MV508_SPEAKER
, mixer
);
case SOUND_MIXER_LINE
: /* External line (0-31) */
levels
[whichDev
] = mixer_output (right
, left
, 31, P_M_MV508_MIXER
| P_M_MV508_LINE
, mixer
);
case SOUND_MIXER_CD
: /* CD (0-31) */
levels
[whichDev
] = mixer_output (right
, left
, 31, P_M_MV508_MIXER
| P_M_MV508_CDROM
, mixer
);
case SOUND_MIXER_MIC
: /* External microphone (0-31) */
levels
[whichDev
] = mixer_output (right
, left
, 31, P_M_MV508_MIXER
| P_M_MV508_MIC
, mixer
);
case SOUND_MIXER_IMIX
: /* Recording monitor (0-31) (Only available
* on the Output Mixer) */
levels
[whichDev
] = mixer_output (right
, left
, 31, P_M_MV508_MIXER
| P_M_MV508_IMIXER
,
case SOUND_MIXER_RECLEV
: /* Recording level (0-15) */
levels
[whichDev
] = mixer_output (right
, left
, 15, P_M_MV508_MASTER_B
, 0);
case SOUND_MIXER_ENHANCE
:
mode_control
&= ~P_M_MV508_ENHANCE_BITS
;
mode_control
|= P_M_MV508_ENHANCE_BITS
;
mode_control
&= ~P_M_MV508_LOUDNESS
;
mode_control
|= P_M_MV508_LOUDNESS
;
return !!level
; /* 0 or 1 */
devmask
= level
& POSSIBLE_RECORDING_DEVICES
;
changed
= devmask
^ rec_devices
;
for (i
= 0; i
< SOUND_MIXER_NRDEVICES
; i
++)
pas_mixer_set (i
, levels
[i
]);
return RET_ERROR (EINVAL
);
return (levels
[whichDev
]);
mixer_set_levels (struct sb_mixer_levels
*user_l
)
#define cmix(v) ((((v.r*100+7)/15)<<8)| ((v.l*100+7)/15))
struct sb_mixer_levels l
;
IOCTL_FROM_USER ((char *) &l
, (char *) user_l
, 0, sizeof (l
));
if (l
.master
.l
& ~0xF || l
.master
.r
& ~0xF
|| l
.line
.l
& ~0xF || l
.line
.r
& ~0xF
|| l
.voc
.l
& ~0xF || l
.voc
.r
& ~0xF
|| l
.fm
.l
& ~0xF || l
.fm
.r
& ~0xF
|| l
.cd
.l
& ~0xF || l
.cd
.r
& ~0xF
return (RET_ERROR (EINVAL
));
pas_mixer_set (SOUND_MIXER_VOLUME
, cmix (l
.master
));
pas_mixer_set (SOUND_MIXER_LINE
, cmix (l
.line
));
pas_mixer_set (SOUND_MIXER_PCM
, cmix (l
.voc
));
pas_mixer_set (SOUND_MIXER_ALTPCM
, cmix (l
.voc
));
pas_mixer_set (SOUND_MIXER_SYNTH
, cmix (l
.fm
));
pas_mixer_set (SOUND_MIXER_CD
, cmix (l
.cd
));
pas_mixer_set (SOUND_MIXER_MIC
, ((l
.mic
* 100 + 3) / 7) | (((l
.mic
* 100 + 3) / 7) << 8));
* This sets aspects of the Mixer that are not volume levels. (Recording
* source, filter level, I/O filtering, and stereo.)
mixer_set_params (struct sb_mixer_params
*user_p
)
struct sb_mixer_params p
;
IOCTL_FROM_USER ((char *) &p
, (char *) user_p
, 0, sizeof (p
));
if (p
.record_source
!= SRC_MIC
&& p
.record_source
!= SRC_CD
&& p
.record_source
!= SRC_LINE
)
return (RET_ERROR (EINVAL
));
* I'm not sure if this is The Right Thing. Should stereo be entirely
* under control of DSP? I like being able to toggle it while a sound is
* playing, so I do this... because I can.
val
= (pas_read (PCM_CONTROL
) & ~P_C_MIXER_CROSS_FIELD
) | P_C_MIXER_CROSS_R_TO_R
| P_C_MIXER_CROSS_L_TO_L
;
val
|= (P_C_MIXER_CROSS_R_TO_L
| P_C_MIXER_CROSS_L_TO_R
); /* Mono */
pas_write (val
, PCM_CONTROL
);
pas_mixer_set (SOUND_MIXER_RECSRC
, src
);
* setmixer (OUT_FILTER, ((dsp_stereo ? STEREO_DAC : MONO_DAC) |
* (p.filter_output ? FILT_ON : FILT_OFF)));
getmixer (int dev
, int chn
)
if (chn
== P_M_MV508_RIGHT
)
return (levels
[dev
] >> 8) & 0x7f;
return levels
[dev
] & 0x7f;
/* Read the current mixer level settings into the user's struct. */
mixer_get_levels (struct sb_mixer_levels
*user_l
)
struct sb_mixer_levels l
;
l
.master
.r
= ((((levels
[SOUND_MIXER_VOLUME
] >> 8) & 0x7f) * 15) + 50) / 100; /* Master */
l
.master
.l
= (((levels
[SOUND_MIXER_VOLUME
] & 0x7f) * 15) + 50) / 100; /* Master */
l
.line
.r
= ((getmixer (SOUND_MIXER_LINE
, P_M_MV508_RIGHT
) * 15) + 50) / 100; /* Line */
l
.line
.l
= ((getmixer (SOUND_MIXER_LINE
, P_M_MV508_LEFT
) * 15) + 50) / 100;
l
.voc
.r
= ((getmixer (SOUND_MIXER_PCM
, P_M_MV508_RIGHT
) * 15) + 50) / 100; /* DAC */
l
.voc
.l
= ((getmixer (SOUND_MIXER_PCM
, P_M_MV508_LEFT
) * 15) + 50) / 100;
l
.fm
.r
= ((getmixer (SOUND_MIXER_SYNTH
, P_M_MV508_RIGHT
) * 15) + 50) / 100; /* FM */
l
.fm
.l
= ((getmixer (SOUND_MIXER_SYNTH
, P_M_MV508_LEFT
) * 15) + 50) / 100;
l
.cd
.r
= ((getmixer (SOUND_MIXER_CD
, P_M_MV508_RIGHT
) * 15) + 50) / 100; /* CD */
l
.cd
.l
= ((getmixer (SOUND_MIXER_CD
, P_M_MV508_LEFT
) * 15) + 50) / 100;
l
.mic
= ((getmixer (SOUND_MIXER_MIC
, P_M_MV508_LEFT
) * 7) + 50) / 100; /* Microphone */
IOCTL_TO_USER ((char *) user_l
, 0, (char *) &l
, sizeof (l
));
/* Read the current mixer parameters into the user's struct. */
mixer_get_params (struct sb_mixer_params
*user_params
)
struct sb_mixer_params params
;
params
.record_source
= SRC_CD
;
params
.record_source
= SRC_LINE
;
params
.record_source
= SRC_MIC
;
params
.record_source
= SRC_MIC
;
pas_mixer_set (SOUND_MIXER_RECSRC
, SOUND_MASK_MIC
); /* Adjust */
params
.hifreq_filter
= OFF
;
params
.filter_input
= OFF
;
params
.filter_output
= OFF
;
params
.dsp_stereo
= ((val
& P_C_MIXER_CROSS_FIELD
) == (P_C_MIXER_CROSS_L_TO_L
| P_C_MIXER_CROSS_R_TO_R
));
IOCTL_TO_USER ((char *) user_params
, 0, (char *) ¶ms
, sizeof (params
));
TRACE (printk ("pas2_mixer.c: void pas_mixer_reset(void)\n"));
for (foo
= 0; foo
< SOUND_MIXER_NRDEVICES
; foo
++)
pas_mixer_set (foo
, levels
[foo
]);
set_mode (P_M_MV508_LOUDNESS
| P_M_MV508_ENHANCE_40
);
pas_mixer_ioctl (int dev
, unsigned int cmd
, unsigned int arg
)
TRACE (printk ("pas2_mixer.c: int pas_mixer_ioctl(unsigned int cmd = %X, unsigned int arg = %X)\n", cmd
, arg
));
if (((cmd
>> 8) & 0xff) == 'M')
return IOCTL_OUT (arg
, pas_mixer_set (cmd
& 0xff, IOCTL_IN (arg
)));
return IOCTL_OUT (arg
, rec_devices
);
case SOUND_MIXER_STEREODEVS
:
return IOCTL_OUT (arg
, SUPPORTED_MIXER_DEVICES
& ~(SOUND_MASK_BASS
| SOUND_MASK_TREBLE
));
case SOUND_MIXER_DEVMASK
:
return IOCTL_OUT (arg
, SUPPORTED_MIXER_DEVICES
);
case SOUND_MIXER_RECMASK
:
return IOCTL_OUT (arg
, POSSIBLE_RECORDING_DEVICES
& SUPPORTED_MIXER_DEVICES
);
return IOCTL_OUT (arg
, 0); /* No special capabilities */
return IOCTL_OUT (arg
, 0); /* No mute yet */
case SOUND_MIXER_ENHANCE
:
if (!(mode_control
& P_M_MV508_ENHANCE_BITS
))
return IOCTL_OUT (arg
, 0);
return IOCTL_OUT (arg
, ((mode_control
& P_M_MV508_ENHANCE_BITS
) + 1) * 20);
if (mode_control
& P_M_MV508_LOUDNESS
)
return IOCTL_OUT (arg
, 1);
return IOCTL_OUT (arg
, 0);
return IOCTL_OUT (arg
, levels
[cmd
& 0xff]);
case MIXER_IOCTL_SET_LEVELS
:
mixer_set_levels ((struct sb_mixer_levels
*) arg
);
return mixer_get_levels ((struct sb_mixer_levels
*) arg
);
case MIXER_IOCTL_SET_PARAMS
:
mixer_set_params ((struct sb_mixer_params
*) arg
);
return mixer_get_params ((struct sb_mixer_params
*) arg
);
case MIXER_IOCTL_READ_LEVELS
:
return mixer_get_levels ((struct sb_mixer_levels
*) arg
);
case MIXER_IOCTL_READ_PARAMS
:
return mixer_get_params ((struct sb_mixer_params
*) arg
);
return RET_ERROR (EINVAL
);
return RET_ERROR (EINVAL
);
static struct mixer_operations pas_mixer_operations
=
mixer_devs
[num_mixers
++] = &pas_mixer_operations
;