diff options
Diffstat (limited to 'sound/oss/sb_mixer.c')
-rw-r--r-- | sound/oss/sb_mixer.c | 768 |
1 files changed, 768 insertions, 0 deletions
diff --git a/sound/oss/sb_mixer.c b/sound/oss/sb_mixer.c new file mode 100644 index 00000000000..f56898c3981 --- /dev/null +++ b/sound/oss/sb_mixer.c @@ -0,0 +1,768 @@ +/* + * sound/sb_mixer.c + * + * The low level mixer driver for the Sound Blaster compatible cards. + */ +/* + * Copyright (C) by Hannu Savolainen 1993-1997 + * + * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL) + * Version 2 (June 1991). See the "COPYING" file distributed with this software + * for more info. + * + * + * Thomas Sailer : ioctl code reworked (vmalloc/vfree removed) + * Rolf Fokkens (Dec 20 1998) : Moved ESS stuff into sb_ess.[ch] + * Stanislav Voronyi <stas@esc.kharkov.com> : Support for AWE 3DSE device (Jun 7 1999) + */ + +#include "sound_config.h" + +#define __SB_MIXER_C__ + +#include "sb.h" +#include "sb_mixer.h" + +#include "sb_ess.h" + +#define SBPRO_RECORDING_DEVICES (SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD) + +/* Same as SB Pro, unless I find otherwise */ +#define SGNXPRO_RECORDING_DEVICES SBPRO_RECORDING_DEVICES + +#define SBPRO_MIXER_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_LINE | SOUND_MASK_MIC | \ + SOUND_MASK_CD | SOUND_MASK_VOLUME) + +/* SG NX Pro has treble and bass settings on the mixer. The 'speaker' + * channel is the COVOX/DisneySoundSource emulation volume control + * on the mixer. It does NOT control speaker volume. Should have own + * mask eventually? + */ +#define SGNXPRO_MIXER_DEVICES (SBPRO_MIXER_DEVICES|SOUND_MASK_BASS| \ + SOUND_MASK_TREBLE|SOUND_MASK_SPEAKER ) + +#define SB16_RECORDING_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_LINE | SOUND_MASK_MIC | \ + SOUND_MASK_CD) + +#define SB16_OUTFILTER_DEVICES (SOUND_MASK_LINE | SOUND_MASK_MIC | \ + SOUND_MASK_CD) + +#define SB16_MIXER_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | \ + SOUND_MASK_CD | \ + SOUND_MASK_IGAIN | SOUND_MASK_OGAIN | \ + SOUND_MASK_VOLUME | SOUND_MASK_BASS | SOUND_MASK_TREBLE | \ + SOUND_MASK_IMIX) + +/* These are the only devices that are working at the moment. Others could + * be added once they are identified and a method is found to control them. + */ +#define ALS007_MIXER_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_LINE | \ + SOUND_MASK_PCM | SOUND_MASK_MIC | \ + SOUND_MASK_CD | \ + SOUND_MASK_VOLUME) + +static mixer_tab sbpro_mix = { +MIX_ENT(SOUND_MIXER_VOLUME, 0x22, 7, 4, 0x22, 3, 4), +MIX_ENT(SOUND_MIXER_BASS, 0x00, 0, 0, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_TREBLE, 0x00, 0, 0, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_SYNTH, 0x26, 7, 4, 0x26, 3, 4), +MIX_ENT(SOUND_MIXER_PCM, 0x04, 7, 4, 0x04, 3, 4), +MIX_ENT(SOUND_MIXER_SPEAKER, 0x00, 0, 0, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_LINE, 0x2e, 7, 4, 0x2e, 3, 4), +MIX_ENT(SOUND_MIXER_MIC, 0x0a, 2, 3, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_CD, 0x28, 7, 4, 0x28, 3, 4), +MIX_ENT(SOUND_MIXER_IMIX, 0x00, 0, 0, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_ALTPCM, 0x00, 0, 0, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_RECLEV, 0x00, 0, 0, 0x00, 0, 0) +}; + +static mixer_tab sb16_mix = { +MIX_ENT(SOUND_MIXER_VOLUME, 0x30, 7, 5, 0x31, 7, 5), +MIX_ENT(SOUND_MIXER_BASS, 0x46, 7, 4, 0x47, 7, 4), +MIX_ENT(SOUND_MIXER_TREBLE, 0x44, 7, 4, 0x45, 7, 4), +MIX_ENT(SOUND_MIXER_SYNTH, 0x34, 7, 5, 0x35, 7, 5), +MIX_ENT(SOUND_MIXER_PCM, 0x32, 7, 5, 0x33, 7, 5), +MIX_ENT(SOUND_MIXER_SPEAKER, 0x3b, 7, 2, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_LINE, 0x38, 7, 5, 0x39, 7, 5), +MIX_ENT(SOUND_MIXER_MIC, 0x3a, 7, 5, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_CD, 0x36, 7, 5, 0x37, 7, 5), +MIX_ENT(SOUND_MIXER_IMIX, 0x3c, 0, 1, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_ALTPCM, 0x00, 0, 0, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_RECLEV, 0x3f, 7, 2, 0x40, 7, 2), /* Obsolete. Use IGAIN */ +MIX_ENT(SOUND_MIXER_IGAIN, 0x3f, 7, 2, 0x40, 7, 2), +MIX_ENT(SOUND_MIXER_OGAIN, 0x41, 7, 2, 0x42, 7, 2) +}; + +static mixer_tab als007_mix = +{ +MIX_ENT(SOUND_MIXER_VOLUME, 0x62, 7, 4, 0x62, 3, 4), +MIX_ENT(SOUND_MIXER_BASS, 0x00, 0, 0, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_TREBLE, 0x00, 0, 0, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_SYNTH, 0x66, 7, 4, 0x66, 3, 4), +MIX_ENT(SOUND_MIXER_PCM, 0x64, 7, 4, 0x64, 3, 4), +MIX_ENT(SOUND_MIXER_SPEAKER, 0x00, 0, 0, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_LINE, 0x6e, 7, 4, 0x6e, 3, 4), +MIX_ENT(SOUND_MIXER_MIC, 0x6a, 2, 3, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_CD, 0x68, 7, 4, 0x68, 3, 4), +MIX_ENT(SOUND_MIXER_IMIX, 0x00, 0, 0, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_ALTPCM, 0x00, 0, 0, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_RECLEV, 0x00, 0, 0, 0x00, 0, 0), /* Obsolete. Use IGAIN */ +MIX_ENT(SOUND_MIXER_IGAIN, 0x00, 0, 0, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_OGAIN, 0x00, 0, 0, 0x00, 0, 0) +}; + + +/* SM_GAMES Master volume is lower and PCM & FM volumes + higher than with SB Pro. This improves the + sound quality */ + +static int smg_default_levels[32] = +{ + 0x2020, /* Master Volume */ + 0x4b4b, /* Bass */ + 0x4b4b, /* Treble */ + 0x6464, /* FM */ + 0x6464, /* PCM */ + 0x4b4b, /* PC Speaker */ + 0x4b4b, /* Ext Line */ + 0x0000, /* Mic */ + 0x4b4b, /* CD */ + 0x4b4b, /* Recording monitor */ + 0x4b4b, /* SB PCM */ + 0x4b4b, /* Recording level */ + 0x4b4b, /* Input gain */ + 0x4b4b, /* Output gain */ + 0x4040, /* Line1 */ + 0x4040, /* Line2 */ + 0x1515 /* Line3 */ +}; + +static int sb_default_levels[32] = +{ + 0x5a5a, /* Master Volume */ + 0x4b4b, /* Bass */ + 0x4b4b, /* Treble */ + 0x4b4b, /* FM */ + 0x4b4b, /* PCM */ + 0x4b4b, /* PC Speaker */ + 0x4b4b, /* Ext Line */ + 0x1010, /* Mic */ + 0x4b4b, /* CD */ + 0x0000, /* Recording monitor */ + 0x4b4b, /* SB PCM */ + 0x4b4b, /* Recording level */ + 0x4b4b, /* Input gain */ + 0x4b4b, /* Output gain */ + 0x4040, /* Line1 */ + 0x4040, /* Line2 */ + 0x1515 /* Line3 */ +}; + +static unsigned char sb16_recmasks_L[SOUND_MIXER_NRDEVICES] = +{ + 0x00, /* SOUND_MIXER_VOLUME */ + 0x00, /* SOUND_MIXER_BASS */ + 0x00, /* SOUND_MIXER_TREBLE */ + 0x40, /* SOUND_MIXER_SYNTH */ + 0x00, /* SOUND_MIXER_PCM */ + 0x00, /* SOUND_MIXER_SPEAKER */ + 0x10, /* SOUND_MIXER_LINE */ + 0x01, /* SOUND_MIXER_MIC */ + 0x04, /* SOUND_MIXER_CD */ + 0x00, /* SOUND_MIXER_IMIX */ + 0x00, /* SOUND_MIXER_ALTPCM */ + 0x00, /* SOUND_MIXER_RECLEV */ + 0x00, /* SOUND_MIXER_IGAIN */ + 0x00 /* SOUND_MIXER_OGAIN */ +}; + +static unsigned char sb16_recmasks_R[SOUND_MIXER_NRDEVICES] = +{ + 0x00, /* SOUND_MIXER_VOLUME */ + 0x00, /* SOUND_MIXER_BASS */ + 0x00, /* SOUND_MIXER_TREBLE */ + 0x20, /* SOUND_MIXER_SYNTH */ + 0x00, /* SOUND_MIXER_PCM */ + 0x00, /* SOUND_MIXER_SPEAKER */ + 0x08, /* SOUND_MIXER_LINE */ + 0x01, /* SOUND_MIXER_MIC */ + 0x02, /* SOUND_MIXER_CD */ + 0x00, /* SOUND_MIXER_IMIX */ + 0x00, /* SOUND_MIXER_ALTPCM */ + 0x00, /* SOUND_MIXER_RECLEV */ + 0x00, /* SOUND_MIXER_IGAIN */ + 0x00 /* SOUND_MIXER_OGAIN */ +}; + +static char smw_mix_regs[] = /* Left mixer registers */ +{ + 0x0b, /* SOUND_MIXER_VOLUME */ + 0x0d, /* SOUND_MIXER_BASS */ + 0x0d, /* SOUND_MIXER_TREBLE */ + 0x05, /* SOUND_MIXER_SYNTH */ + 0x09, /* SOUND_MIXER_PCM */ + 0x00, /* SOUND_MIXER_SPEAKER */ + 0x03, /* SOUND_MIXER_LINE */ + 0x01, /* SOUND_MIXER_MIC */ + 0x07, /* SOUND_MIXER_CD */ + 0x00, /* SOUND_MIXER_IMIX */ + 0x00, /* SOUND_MIXER_ALTPCM */ + 0x00, /* SOUND_MIXER_RECLEV */ + 0x00, /* SOUND_MIXER_IGAIN */ + 0x00, /* SOUND_MIXER_OGAIN */ + 0x00, /* SOUND_MIXER_LINE1 */ + 0x00, /* SOUND_MIXER_LINE2 */ + 0x00 /* SOUND_MIXER_LINE3 */ +}; + +static int sbmixnum = 1; + +static void sb_mixer_reset(sb_devc * devc); + +void sb_mixer_set_stereo(sb_devc * devc, int mode) +{ + sb_chgmixer(devc, OUT_FILTER, STEREO_DAC, (mode ? STEREO_DAC : MONO_DAC)); +} + +static int detect_mixer(sb_devc * devc) +{ + /* Just trust the mixer is there */ + return 1; +} + +static void change_bits(sb_devc * devc, unsigned char *regval, int dev, int chn, int newval) +{ + unsigned char mask; + int shift; + + mask = (1 << (*devc->iomap)[dev][chn].nbits) - 1; + newval = (int) ((newval * mask) + 50) / 100; /* Scale */ + + shift = (*devc->iomap)[dev][chn].bitoffs - (*devc->iomap)[dev][LEFT_CHN].nbits + 1; + + *regval &= ~(mask << shift); /* Mask out previous value */ + *regval |= (newval & mask) << shift; /* Set the new value */ +} + +static int sb_mixer_get(sb_devc * devc, int dev) +{ + if (!((1 << dev) & devc->supported_devices)) + return -EINVAL; + return devc->levels[dev]; +} + +void smw_mixer_init(sb_devc * devc) +{ + int i; + + sb_setmixer(devc, 0x00, 0x18); /* Mute unused (Telephone) line */ + sb_setmixer(devc, 0x10, 0x38); /* Config register 2 */ + + devc->supported_devices = 0; + for (i = 0; i < sizeof(smw_mix_regs); i++) + if (smw_mix_regs[i] != 0) + devc->supported_devices |= (1 << i); + + devc->supported_rec_devices = devc->supported_devices & + ~(SOUND_MASK_BASS | SOUND_MASK_TREBLE | SOUND_MASK_PCM | SOUND_MASK_VOLUME); + sb_mixer_reset(devc); +} + +int sb_common_mixer_set(sb_devc * devc, int dev, int left, int right) +{ + int regoffs; + unsigned char val; + + regoffs = (*devc->iomap)[dev][LEFT_CHN].regno; + + if (regoffs == 0) + return -EINVAL; + + if ((dev < 0) || (dev >= devc->iomap_sz)) + return -EINVAL; + + val = sb_getmixer(devc, regoffs); + change_bits(devc, &val, dev, LEFT_CHN, left); + + if ((*devc->iomap)[dev][RIGHT_CHN].regno != regoffs) /* + * Change register + */ + { + sb_setmixer(devc, regoffs, val); /* + * Save the old one + */ + regoffs = (*devc->iomap)[dev][RIGHT_CHN].regno; + + if (regoffs == 0) + return left | (left << 8); /* + * Just left channel present + */ + + val = sb_getmixer(devc, regoffs); /* + * Read the new one + */ + } + change_bits(devc, &val, dev, RIGHT_CHN, right); + + sb_setmixer(devc, regoffs, val); + + return left | (right << 8); +} + +static int smw_mixer_set(sb_devc * devc, int dev, int left, int right) +{ + int reg, val; + + switch (dev) + { + case SOUND_MIXER_VOLUME: + sb_setmixer(devc, 0x0b, 96 - (96 * left / 100)); /* 96=mute, 0=max */ + sb_setmixer(devc, 0x0c, 96 - (96 * right / 100)); + break; + + case SOUND_MIXER_BASS: + case SOUND_MIXER_TREBLE: + devc->levels[dev] = left | (right << 8); + /* Set left bass and treble values */ + val = ((devc->levels[SOUND_MIXER_TREBLE] & 0xff) * 16 / (unsigned) 100) << 4; + val |= ((devc->levels[SOUND_MIXER_BASS] & 0xff) * 16 / (unsigned) 100) & 0x0f; + sb_setmixer(devc, 0x0d, val); + + /* Set right bass and treble values */ + val = (((devc->levels[SOUND_MIXER_TREBLE] >> 8) & 0xff) * 16 / (unsigned) 100) << 4; + val |= (((devc->levels[SOUND_MIXER_BASS] >> 8) & 0xff) * 16 / (unsigned) 100) & 0x0f; + sb_setmixer(devc, 0x0e, val); + + break; + + default: + /* bounds check */ + if (dev < 0 || dev >= ARRAY_SIZE(smw_mix_regs)) + return -EINVAL; + reg = smw_mix_regs[dev]; + if (reg == 0) + return -EINVAL; + sb_setmixer(devc, reg, (24 - (24 * left / 100)) | 0x20); /* 24=mute, 0=max */ + sb_setmixer(devc, reg + 1, (24 - (24 * right / 100)) | 0x40); + } + + devc->levels[dev] = left | (right << 8); + return left | (right << 8); +} + +static int sb_mixer_set(sb_devc * devc, int dev, int value) +{ + int left = value & 0x000000ff; + int right = (value & 0x0000ff00) >> 8; + int retval; + + if (left > 100) + left = 100; + if (right > 100) + right = 100; + + if ((dev < 0) || (dev > 31)) + return -EINVAL; + + if (!(devc->supported_devices & (1 << dev))) /* + * Not supported + */ + return -EINVAL; + + /* Differentiate depending on the chipsets */ + switch (devc->model) { + case MDL_SMW: + retval = smw_mixer_set(devc, dev, left, right); + break; + case MDL_ESS: + retval = ess_mixer_set(devc, dev, left, right); + break; + default: + retval = sb_common_mixer_set(devc, dev, left, right); + } + if (retval >= 0) devc->levels[dev] = retval; + + return retval; +} + +/* + * set_recsrc doesn't apply to ES188x + */ +static void set_recsrc(sb_devc * devc, int src) +{ + sb_setmixer(devc, RECORD_SRC, (sb_getmixer(devc, RECORD_SRC) & ~7) | (src & 0x7)); +} + +static int set_recmask(sb_devc * devc, int mask) +{ + int devmask, i; + unsigned char regimageL, regimageR; + + devmask = mask & devc->supported_rec_devices; + + switch (devc->model) + { + case MDL_SBPRO: + case MDL_ESS: + case MDL_JAZZ: + case MDL_SMW: + if (devc->model == MDL_ESS && ess_set_recmask (devc, &devmask)) { + break; + }; + if (devmask != SOUND_MASK_MIC && + devmask != SOUND_MASK_LINE && + devmask != SOUND_MASK_CD) + { + /* + * More than one device selected. Drop the + * previous selection + */ + devmask &= ~devc->recmask; + } + if (devmask != SOUND_MASK_MIC && + devmask != SOUND_MASK_LINE && + devmask != SOUND_MASK_CD) + { + /* + * More than one device selected. Default to + * mic + */ + devmask = SOUND_MASK_MIC; + } + if (devmask ^ devc->recmask) /* + * Input source changed + */ + { + switch (devmask) + { + case SOUND_MASK_MIC: + set_recsrc(devc, SRC__MIC); + break; + + case SOUND_MASK_LINE: + set_recsrc(devc, SRC__LINE); + break; + + case SOUND_MASK_CD: + set_recsrc(devc, SRC__CD); + break; + + default: + set_recsrc(devc, SRC__MIC); + } + } + break; + + case MDL_SB16: + if (!devmask) + devmask = SOUND_MASK_MIC; + + if (devc->submodel == SUBMDL_ALS007) + { + switch (devmask) + { + case SOUND_MASK_LINE: + sb_setmixer(devc, ALS007_RECORD_SRC, ALS007_LINE); + break; + case SOUND_MASK_CD: + sb_setmixer(devc, ALS007_RECORD_SRC, ALS007_CD); + break; + case SOUND_MASK_SYNTH: + sb_setmixer(devc, ALS007_RECORD_SRC, ALS007_SYNTH); + break; + default: /* Also takes care of SOUND_MASK_MIC case */ + sb_setmixer(devc, ALS007_RECORD_SRC, ALS007_MIC); + break; + } + } + else + { + regimageL = regimageR = 0; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + { + if ((1 << i) & devmask) + { + regimageL |= sb16_recmasks_L[i]; + regimageR |= sb16_recmasks_R[i]; + } + sb_setmixer (devc, SB16_IMASK_L, regimageL); + sb_setmixer (devc, SB16_IMASK_R, regimageR); + } + } + break; + } + devc->recmask = devmask; + return devc->recmask; +} + +static int set_outmask(sb_devc * devc, int mask) +{ + int devmask, i; + unsigned char regimage; + + devmask = mask & devc->supported_out_devices; + + switch (devc->model) + { + case MDL_SB16: + if (devc->submodel == SUBMDL_ALS007) + break; + else + { + regimage = 0; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + { + if ((1 << i) & devmask) + { + regimage |= (sb16_recmasks_L[i] | sb16_recmasks_R[i]); + } + sb_setmixer (devc, SB16_OMASK, regimage); + } + } + break; + default: + break; + } + + devc->outmask = devmask; + return devc->outmask; +} + +static int sb_mixer_ioctl(int dev, unsigned int cmd, void __user *arg) +{ + sb_devc *devc = mixer_devs[dev]->devc; + int val, ret; + int __user *p = arg; + + /* + * Use ioctl(fd, SOUND_MIXER_AGC, &mode) to turn AGC off (0) or on (1). + * Use ioctl(fd, SOUND_MIXER_3DSE, &mode) to turn 3DSE off (0) or on (1) + * or mode==2 put 3DSE state to mode. + */ + if (devc->model == MDL_SB16) { + if (cmd == SOUND_MIXER_AGC) + { + if (get_user(val, p)) + return -EFAULT; + sb_setmixer(devc, 0x43, (~val) & 0x01); + return 0; + } + if (cmd == SOUND_MIXER_3DSE) + { + /* I put here 15, but I don't know the exact version. + At least my 4.13 havn't 3DSE, 4.16 has it. */ + if (devc->minor < 15) + return -EINVAL; + if (get_user(val, p)) + return -EFAULT; + if (val == 0 || val == 1) + sb_chgmixer(devc, AWE_3DSE, 0x01, val); + else if (val == 2) + { + ret = sb_getmixer(devc, AWE_3DSE)&0x01; + return put_user(ret, p); + } + else + return -EINVAL; + return 0; + } + } + if (((cmd >> 8) & 0xff) == 'M') + { + if (_SIOC_DIR(cmd) & _SIOC_WRITE) + { + if (get_user(val, p)) + return -EFAULT; + switch (cmd & 0xff) + { + case SOUND_MIXER_RECSRC: + ret = set_recmask(devc, val); + break; + + case SOUND_MIXER_OUTSRC: + ret = set_outmask(devc, val); + break; + + default: + ret = sb_mixer_set(devc, cmd & 0xff, val); + } + } + else switch (cmd & 0xff) + { + case SOUND_MIXER_RECSRC: + ret = devc->recmask; + break; + + case SOUND_MIXER_OUTSRC: + ret = devc->outmask; + break; + + case SOUND_MIXER_DEVMASK: + ret = devc->supported_devices; + break; + + case SOUND_MIXER_STEREODEVS: + ret = devc->supported_devices; + /* The ESS seems to have stereo mic controls */ + if (devc->model == MDL_ESS) + ret &= ~(SOUND_MASK_SPEAKER|SOUND_MASK_IMIX); + else if (devc->model != MDL_JAZZ && devc->model != MDL_SMW) + ret &= ~(SOUND_MASK_MIC | SOUND_MASK_SPEAKER | SOUND_MASK_IMIX); + break; + + case SOUND_MIXER_RECMASK: + ret = devc->supported_rec_devices; + break; + + case SOUND_MIXER_OUTMASK: + ret = devc->supported_out_devices; + break; + + case SOUND_MIXER_CAPS: + ret = devc->mixer_caps; + break; + + default: + ret = sb_mixer_get(devc, cmd & 0xff); + break; + } + return put_user(ret, p); + } else + return -EINVAL; +} + +static struct mixer_operations sb_mixer_operations = +{ + .owner = THIS_MODULE, + .id = "SB", + .name = "Sound Blaster", + .ioctl = sb_mixer_ioctl +}; + +static struct mixer_operations als007_mixer_operations = +{ + .owner = THIS_MODULE, + .id = "ALS007", + .name = "Avance ALS-007", + .ioctl = sb_mixer_ioctl +}; + +static void sb_mixer_reset(sb_devc * devc) +{ + char name[32]; + int i; + + sprintf(name, "SB_%d", devc->sbmixnum); + + if (devc->sbmo.sm_games) + devc->levels = load_mixer_volumes(name, smg_default_levels, 1); + else + devc->levels = load_mixer_volumes(name, sb_default_levels, 1); + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + sb_mixer_set(devc, i, devc->levels[i]); + + if (devc->model != MDL_ESS || !ess_mixer_reset (devc)) { + set_recmask(devc, SOUND_MASK_MIC); + }; +} + +int sb_mixer_init(sb_devc * devc, struct module *owner) +{ + int mixer_type = 0; + int m; + + devc->sbmixnum = sbmixnum++; + devc->levels = NULL; + + sb_setmixer(devc, 0x00, 0); /* Reset mixer */ + + if (!(mixer_type = detect_mixer(devc))) + return 0; /* No mixer. Why? */ + + switch (devc->model) + { + case MDL_ESSPCI: + case MDL_YMPCI: + case MDL_SBPRO: + case MDL_AZTECH: + case MDL_JAZZ: + devc->mixer_caps = SOUND_CAP_EXCL_INPUT; + devc->supported_devices = SBPRO_MIXER_DEVICES; + devc->supported_rec_devices = SBPRO_RECORDING_DEVICES; + devc->iomap = &sbpro_mix; + devc->iomap_sz = ARRAY_SIZE(sbpro_mix); + break; + + case MDL_ESS: + ess_mixer_init (devc); + break; + + case MDL_SMW: + devc->mixer_caps = SOUND_CAP_EXCL_INPUT; + devc->supported_devices = 0; + devc->supported_rec_devices = 0; + devc->iomap = &sbpro_mix; + devc->iomap_sz = ARRAY_SIZE(sbpro_mix); + smw_mixer_init(devc); + break; + + case MDL_SB16: + devc->mixer_caps = 0; + devc->supported_rec_devices = SB16_RECORDING_DEVICES; + devc->supported_out_devices = SB16_OUTFILTER_DEVICES; + if (devc->submodel != SUBMDL_ALS007) + { + devc->supported_devices = SB16_MIXER_DEVICES; + devc->iomap = &sb16_mix; + devc->iomap_sz = ARRAY_SIZE(sb16_mix); + } + else + { + devc->supported_devices = ALS007_MIXER_DEVICES; + devc->iomap = &als007_mix; + devc->iomap_sz = ARRAY_SIZE(als007_mix); + } + break; + + default: + printk(KERN_WARNING "sb_mixer: Unsupported mixer type %d\n", devc->model); + return 0; + } + + m = sound_alloc_mixerdev(); + if (m == -1) + return 0; + + mixer_devs[m] = (struct mixer_operations *)kmalloc(sizeof(struct mixer_operations), GFP_KERNEL); + if (mixer_devs[m] == NULL) + { + printk(KERN_ERR "sb_mixer: Can't allocate memory\n"); + sound_unload_mixerdev(m); + return 0; + } + + if (devc->submodel != SUBMDL_ALS007) + memcpy ((char *) mixer_devs[m], (char *) &sb_mixer_operations, sizeof (struct mixer_operations)); + else + memcpy ((char *) mixer_devs[m], (char *) &als007_mixer_operations, sizeof (struct mixer_operations)); + + mixer_devs[m]->devc = devc; + + if (owner) + mixer_devs[m]->owner = owner; + + devc->my_mixerdev = m; + sb_mixer_reset(devc); + return 1; +} + +void sb_mixer_unload(sb_devc *devc) +{ + if (devc->my_mixerdev == -1) + return; + + kfree(mixer_devs[devc->my_mixerdev]); + sound_unload_mixerdev(devc->my_mixerdev); + sbmixnum--; +} |